wolbarg 0.5.3 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.cjs +874 -373
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -3
- package/dist/index.d.ts +10 -3
- package/dist/index.js +874 -373
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -689,6 +689,45 @@ function withEmbeddingCache(provider, store, config) {
|
|
|
689
689
|
};
|
|
690
690
|
}
|
|
691
691
|
|
|
692
|
+
// src/utils/vector.ts
|
|
693
|
+
function cosineDistance(a, b) {
|
|
694
|
+
if (a.length !== b.length) {
|
|
695
|
+
throw new Error(
|
|
696
|
+
`cosineDistance dimension mismatch: a.length=${a.length}, b.length=${b.length}`
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
const len = a.length;
|
|
700
|
+
let dot = 0;
|
|
701
|
+
let normA = 0;
|
|
702
|
+
let normB = 0;
|
|
703
|
+
for (let i = 0; i < len; i += 1) {
|
|
704
|
+
const av = a[i] ?? 0;
|
|
705
|
+
const bv = b[i] ?? 0;
|
|
706
|
+
dot += av * bv;
|
|
707
|
+
normA += av * av;
|
|
708
|
+
normB += bv * bv;
|
|
709
|
+
}
|
|
710
|
+
if (normA === 0 || normB === 0) {
|
|
711
|
+
return 1;
|
|
712
|
+
}
|
|
713
|
+
const similarity = dot / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
714
|
+
return 1 - similarity;
|
|
715
|
+
}
|
|
716
|
+
function bufferToEmbedding(data) {
|
|
717
|
+
const byteOffset = data.byteOffset ?? 0;
|
|
718
|
+
const byteLength = data.byteLength;
|
|
719
|
+
if (byteOffset % Float32Array.BYTES_PER_ELEMENT === 0) {
|
|
720
|
+
return new Float32Array(
|
|
721
|
+
data.buffer,
|
|
722
|
+
byteOffset,
|
|
723
|
+
byteLength / Float32Array.BYTES_PER_ELEMENT
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
const copy = new Uint8Array(byteLength);
|
|
727
|
+
copy.set(data);
|
|
728
|
+
return new Float32Array(copy.buffer);
|
|
729
|
+
}
|
|
730
|
+
|
|
692
731
|
// src/embedding/cache-store.ts
|
|
693
732
|
var TOUCH_FLUSH_THRESHOLD = 64;
|
|
694
733
|
var L1_DEFAULT_MAX = 5e4;
|
|
@@ -773,7 +812,9 @@ var SqliteEmbeddingCacheStore = class {
|
|
|
773
812
|
}
|
|
774
813
|
this.stmts = {
|
|
775
814
|
get: db.prepare(
|
|
776
|
-
`SELECT vector, last_used_at, created_at
|
|
815
|
+
`SELECT model, vector, last_used_at, created_at
|
|
816
|
+
FROM embedding_cache
|
|
817
|
+
WHERE cache_key = ?`
|
|
777
818
|
),
|
|
778
819
|
delete: db.prepare(`DELETE FROM embedding_cache WHERE cache_key = ?`),
|
|
779
820
|
set: db.prepare(
|
|
@@ -808,7 +849,29 @@ var SqliteEmbeddingCacheStore = class {
|
|
|
808
849
|
this.l1.set(cacheKey, pending.model, pending.vector);
|
|
809
850
|
return pending.vector;
|
|
810
851
|
}
|
|
811
|
-
|
|
852
|
+
const db = this.requireDb();
|
|
853
|
+
if (!db) return null;
|
|
854
|
+
try {
|
|
855
|
+
const stmts = this.ensureStatements(db);
|
|
856
|
+
const row = stmts.get.get(cacheKey);
|
|
857
|
+
if (!row) return null;
|
|
858
|
+
if (this.ttlMs !== null) {
|
|
859
|
+
const createdMs = row.created_at ? Date.parse(row.created_at) : 0;
|
|
860
|
+
if (createdMs && Date.now() - createdMs > this.ttlMs) {
|
|
861
|
+
stmts.delete.run(cacheKey);
|
|
862
|
+
return null;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
const vector = bufferToEmbedding(row.vector);
|
|
866
|
+
this.l1.set(cacheKey, row.model, vector);
|
|
867
|
+
this.pendingTouches.add(cacheKey);
|
|
868
|
+
if (this.pendingTouches.size >= TOUCH_FLUSH_THRESHOLD) {
|
|
869
|
+
this.schedulePersist();
|
|
870
|
+
}
|
|
871
|
+
return vector;
|
|
872
|
+
} catch {
|
|
873
|
+
return null;
|
|
874
|
+
}
|
|
812
875
|
}
|
|
813
876
|
async set(cacheKey, model, vector) {
|
|
814
877
|
this.l1.set(cacheKey, model, vector);
|
|
@@ -860,6 +923,16 @@ var SqliteEmbeddingCacheStore = class {
|
|
|
860
923
|
this.pendingSets.clear();
|
|
861
924
|
const touches = [...this.pendingTouches];
|
|
862
925
|
this.pendingTouches.clear();
|
|
926
|
+
const requeue = () => {
|
|
927
|
+
for (const [key, value] of sets) {
|
|
928
|
+
this.pendingSets.set(key, value);
|
|
929
|
+
}
|
|
930
|
+
for (const key of touches) {
|
|
931
|
+
this.pendingTouches.add(key);
|
|
932
|
+
}
|
|
933
|
+
this.stmts = null;
|
|
934
|
+
this.schedulePersist();
|
|
935
|
+
};
|
|
863
936
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
864
937
|
try {
|
|
865
938
|
const stmts = this.ensureStatements(db);
|
|
@@ -894,10 +967,10 @@ var SqliteEmbeddingCacheStore = class {
|
|
|
894
967
|
db.exec("ROLLBACK");
|
|
895
968
|
} catch {
|
|
896
969
|
}
|
|
897
|
-
|
|
970
|
+
requeue();
|
|
898
971
|
}
|
|
899
972
|
} catch {
|
|
900
|
-
|
|
973
|
+
requeue();
|
|
901
974
|
}
|
|
902
975
|
}
|
|
903
976
|
};
|
|
@@ -1206,12 +1279,27 @@ var DocxParser = class {
|
|
|
1206
1279
|
name = "docx";
|
|
1207
1280
|
extensions = [".docx"];
|
|
1208
1281
|
async parse(input) {
|
|
1282
|
+
let mod;
|
|
1283
|
+
try {
|
|
1284
|
+
mod = await import('mammoth');
|
|
1285
|
+
} catch (error) {
|
|
1286
|
+
throw new ConfigurationError(
|
|
1287
|
+
'DOCX ingest requires the optional peer package "mammoth". Install it with: npm install mammoth',
|
|
1288
|
+
{
|
|
1289
|
+
cause: error instanceof Error ? error : void 0,
|
|
1290
|
+
suggestion: "npm install mammoth",
|
|
1291
|
+
operation: "parse"
|
|
1292
|
+
}
|
|
1293
|
+
);
|
|
1294
|
+
}
|
|
1295
|
+
const extractRawText = mod.extractRawText ?? mod.default?.extractRawText;
|
|
1296
|
+
if (!extractRawText) {
|
|
1297
|
+
throw new ConfigurationError(
|
|
1298
|
+
"DOCX ingest could not find mammoth.extractRawText. Ensure your mammoth version is compatible.",
|
|
1299
|
+
{ operation: "parse" }
|
|
1300
|
+
);
|
|
1301
|
+
}
|
|
1209
1302
|
try {
|
|
1210
|
-
const mod = await import('mammoth');
|
|
1211
|
-
const extractRawText = mod.extractRawText ?? mod.default?.extractRawText;
|
|
1212
|
-
if (!extractRawText) {
|
|
1213
|
-
throw new Error("mammoth.extractRawText unavailable");
|
|
1214
|
-
}
|
|
1215
1303
|
const result = await extractRawText({ buffer: input.buffer });
|
|
1216
1304
|
return {
|
|
1217
1305
|
text: result.value ?? "",
|
|
@@ -1219,9 +1307,14 @@ var DocxParser = class {
|
|
|
1219
1307
|
filename: input.filename,
|
|
1220
1308
|
isImage: false
|
|
1221
1309
|
};
|
|
1222
|
-
} catch {
|
|
1310
|
+
} catch (error) {
|
|
1223
1311
|
throw new ConfigurationError(
|
|
1224
|
-
|
|
1312
|
+
"DOCX ingest failed to parse the document (it may be corrupt or an unsupported .docx).",
|
|
1313
|
+
{
|
|
1314
|
+
cause: error instanceof Error ? error : void 0,
|
|
1315
|
+
suggestion: "Try a different DOCX file or ensure it is not password-protected.",
|
|
1316
|
+
operation: "parse"
|
|
1317
|
+
}
|
|
1225
1318
|
);
|
|
1226
1319
|
}
|
|
1227
1320
|
}
|
|
@@ -1368,6 +1461,12 @@ function compileComparison(field, op) {
|
|
|
1368
1461
|
const extract = `json_extract(metadata_json, '${path8}')`;
|
|
1369
1462
|
if ("eq" in op) {
|
|
1370
1463
|
const value = op.eq;
|
|
1464
|
+
if (value === null) {
|
|
1465
|
+
return {
|
|
1466
|
+
expression: `json_type(metadata_json, '${path8}') = 'null'`,
|
|
1467
|
+
params: []
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1371
1470
|
if (value !== null && typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
|
|
1372
1471
|
return null;
|
|
1373
1472
|
}
|
|
@@ -1448,6 +1547,59 @@ function compileMetadataFilterToSql(filter) {
|
|
|
1448
1547
|
return compileComparison(filter.field, filter.op);
|
|
1449
1548
|
}
|
|
1450
1549
|
|
|
1550
|
+
// src/telemetry/logger.ts
|
|
1551
|
+
var LEVEL_ORDER = {
|
|
1552
|
+
off: 100,
|
|
1553
|
+
error: 50,
|
|
1554
|
+
warn: 40,
|
|
1555
|
+
info: 30,
|
|
1556
|
+
debug: 20,
|
|
1557
|
+
trace: 10
|
|
1558
|
+
};
|
|
1559
|
+
var WolbargLogger = class {
|
|
1560
|
+
constructor(level = "info") {
|
|
1561
|
+
this.level = level;
|
|
1562
|
+
}
|
|
1563
|
+
level;
|
|
1564
|
+
setLevel(level) {
|
|
1565
|
+
this.level = level;
|
|
1566
|
+
}
|
|
1567
|
+
getLevel() {
|
|
1568
|
+
return this.level;
|
|
1569
|
+
}
|
|
1570
|
+
error(message, extra) {
|
|
1571
|
+
this.write("error", message, extra);
|
|
1572
|
+
}
|
|
1573
|
+
warn(message, extra) {
|
|
1574
|
+
this.write("warn", message, extra);
|
|
1575
|
+
}
|
|
1576
|
+
info(message, extra) {
|
|
1577
|
+
this.write("info", message, extra);
|
|
1578
|
+
}
|
|
1579
|
+
debug(message, extra) {
|
|
1580
|
+
this.write("debug", message, extra);
|
|
1581
|
+
}
|
|
1582
|
+
trace(message, extra) {
|
|
1583
|
+
this.write("trace", message, extra);
|
|
1584
|
+
}
|
|
1585
|
+
write(level, message, extra) {
|
|
1586
|
+
if (LEVEL_ORDER[level] < LEVEL_ORDER[this.level]) {
|
|
1587
|
+
return;
|
|
1588
|
+
}
|
|
1589
|
+
if (this.level === "off") {
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1592
|
+
const line = `[wolbarg:${level}] ${message}`;
|
|
1593
|
+
if (level === "error") {
|
|
1594
|
+
console.error(line, extra ?? "");
|
|
1595
|
+
} else if (level === "warn") {
|
|
1596
|
+
console.warn(line, extra ?? "");
|
|
1597
|
+
} else {
|
|
1598
|
+
console.log(line, extra ?? "");
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
};
|
|
1602
|
+
|
|
1451
1603
|
// src/schema/index.ts
|
|
1452
1604
|
var SCHEMA_VERSION = 4;
|
|
1453
1605
|
var META_KEYS = {
|
|
@@ -1762,40 +1914,6 @@ var SQL = {
|
|
|
1762
1914
|
`
|
|
1763
1915
|
};
|
|
1764
1916
|
|
|
1765
|
-
// src/utils/vector.ts
|
|
1766
|
-
function cosineDistance(a, b) {
|
|
1767
|
-
const len = Math.min(a.length, b.length);
|
|
1768
|
-
let dot = 0;
|
|
1769
|
-
let normA = 0;
|
|
1770
|
-
let normB = 0;
|
|
1771
|
-
for (let i = 0; i < len; i += 1) {
|
|
1772
|
-
const av = a[i] ?? 0;
|
|
1773
|
-
const bv = b[i] ?? 0;
|
|
1774
|
-
dot += av * bv;
|
|
1775
|
-
normA += av * av;
|
|
1776
|
-
normB += bv * bv;
|
|
1777
|
-
}
|
|
1778
|
-
if (normA === 0 || normB === 0) {
|
|
1779
|
-
return 1;
|
|
1780
|
-
}
|
|
1781
|
-
const similarity = dot / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
1782
|
-
return 1 - similarity;
|
|
1783
|
-
}
|
|
1784
|
-
function bufferToEmbedding(data) {
|
|
1785
|
-
const byteOffset = data.byteOffset ?? 0;
|
|
1786
|
-
const byteLength = data.byteLength;
|
|
1787
|
-
if (byteOffset % Float32Array.BYTES_PER_ELEMENT === 0) {
|
|
1788
|
-
return new Float32Array(
|
|
1789
|
-
data.buffer,
|
|
1790
|
-
byteOffset,
|
|
1791
|
-
byteLength / Float32Array.BYTES_PER_ELEMENT
|
|
1792
|
-
);
|
|
1793
|
-
}
|
|
1794
|
-
const copy = new Uint8Array(byteLength);
|
|
1795
|
-
copy.set(data);
|
|
1796
|
-
return new Float32Array(copy.buffer);
|
|
1797
|
-
}
|
|
1798
|
-
|
|
1799
1917
|
// src/utils/vector-index.ts
|
|
1800
1918
|
var InMemoryVectorIndex = class {
|
|
1801
1919
|
dims;
|
|
@@ -1888,24 +2006,33 @@ var InMemoryVectorIndex = class {
|
|
|
1888
2006
|
dot += queryNormalized[d] * data[base + d];
|
|
1889
2007
|
}
|
|
1890
2008
|
const distance = 1 - dot;
|
|
2009
|
+
const rowid = rowids[i];
|
|
1891
2010
|
if (heapSize < k) {
|
|
1892
2011
|
heapDist[heapSize] = distance;
|
|
1893
|
-
heapRow[heapSize] =
|
|
2012
|
+
heapRow[heapSize] = rowid;
|
|
1894
2013
|
heapSize += 1;
|
|
1895
2014
|
if (heapSize === k) {
|
|
1896
2015
|
buildMaxHeap(heapDist, heapRow, k);
|
|
1897
2016
|
}
|
|
1898
|
-
} else
|
|
1899
|
-
heapDist[0]
|
|
1900
|
-
|
|
1901
|
-
|
|
2017
|
+
} else {
|
|
2018
|
+
const worstDist = heapDist[0];
|
|
2019
|
+
const worstRow = heapRow[0];
|
|
2020
|
+
if (distance < worstDist || distance === worstDist && rowid < worstRow) {
|
|
2021
|
+
heapDist[0] = distance;
|
|
2022
|
+
heapRow[0] = rowid;
|
|
2023
|
+
siftDown(heapDist, heapRow, 0, k);
|
|
2024
|
+
}
|
|
1902
2025
|
}
|
|
1903
2026
|
}
|
|
1904
2027
|
const hits = new Array(heapSize);
|
|
1905
2028
|
for (let i = 0; i < heapSize; i += 1) {
|
|
1906
2029
|
hits[i] = { memoryRowid: heapRow[i], distance: heapDist[i] };
|
|
1907
2030
|
}
|
|
1908
|
-
hits.sort((a, b) =>
|
|
2031
|
+
hits.sort((a, b) => {
|
|
2032
|
+
const diff = a.distance - b.distance;
|
|
2033
|
+
if (diff !== 0) return diff;
|
|
2034
|
+
return a.memoryRowid - b.memoryRowid;
|
|
2035
|
+
});
|
|
1909
2036
|
return hits;
|
|
1910
2037
|
}
|
|
1911
2038
|
ensureCapacity(needed) {
|
|
@@ -1951,8 +2078,8 @@ function siftDown(dist, rows, i, n) {
|
|
|
1951
2078
|
let largest = i;
|
|
1952
2079
|
const left = (i << 1) + 1;
|
|
1953
2080
|
const right = left + 1;
|
|
1954
|
-
if (left < n && dist[left] > dist[largest]) largest = left;
|
|
1955
|
-
if (right < n && dist[right] > dist[largest]) largest = right;
|
|
2081
|
+
if (left < n && (dist[left] > dist[largest] || dist[left] === dist[largest] && rows[left] > rows[largest])) largest = left;
|
|
2082
|
+
if (right < n && (dist[right] > dist[largest] || dist[right] === dist[largest] && rows[right] > rows[largest])) largest = right;
|
|
1956
2083
|
if (largest === i) return;
|
|
1957
2084
|
const td = dist[i];
|
|
1958
2085
|
dist[i] = dist[largest];
|
|
@@ -2094,6 +2221,9 @@ var BATCH_INSERT_CHUNK = 96;
|
|
|
2094
2221
|
var MAX_ANN_OVERFETCH = 8192;
|
|
2095
2222
|
var INSERT_COALESCE_THRESHOLD = 24;
|
|
2096
2223
|
var INSERT_COALESCE_MAX = 96;
|
|
2224
|
+
var warnLogger = new WolbargLogger("warn");
|
|
2225
|
+
var warnedSqliteVecFallback = false;
|
|
2226
|
+
var warnedFtsKeywordSearch = false;
|
|
2097
2227
|
var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
2098
2228
|
name = "sqlite";
|
|
2099
2229
|
connectionString;
|
|
@@ -2103,6 +2233,10 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2103
2233
|
vectorDimensions = null;
|
|
2104
2234
|
vectorBackend = null;
|
|
2105
2235
|
sqliteVecLoaded = false;
|
|
2236
|
+
/** Tracks nesting depth for {@link withTransaction} so we can use savepoints. */
|
|
2237
|
+
transactionDepth = 0;
|
|
2238
|
+
/** Incrementing counter for deterministic savepoint names. */
|
|
2239
|
+
savepointCounter = 0;
|
|
2106
2240
|
/** Hot in-process ANN for blob backend (sqlite-vec unavailable platforms). */
|
|
2107
2241
|
memoryIndex = null;
|
|
2108
2242
|
memoryIndexDirty = false;
|
|
@@ -2169,6 +2303,12 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2169
2303
|
} else if (this.sqliteVecLoaded) {
|
|
2170
2304
|
this.vectorBackend = "sqlite-vec";
|
|
2171
2305
|
} else {
|
|
2306
|
+
if (!warnedSqliteVecFallback) {
|
|
2307
|
+
warnedSqliteVecFallback = true;
|
|
2308
|
+
warnLogger.warn(
|
|
2309
|
+
"sqlite-vec unavailable on this platform; using blob ANN fallback."
|
|
2310
|
+
);
|
|
2311
|
+
}
|
|
2172
2312
|
this.vectorBackend = "blob";
|
|
2173
2313
|
}
|
|
2174
2314
|
if (dims !== null) {
|
|
@@ -2412,6 +2552,12 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2412
2552
|
async searchKeyword(query, organization, topK) {
|
|
2413
2553
|
const stmts = this.requireStatements();
|
|
2414
2554
|
if (!stmts.searchFts) {
|
|
2555
|
+
if (!warnedFtsKeywordSearch) {
|
|
2556
|
+
warnedFtsKeywordSearch = true;
|
|
2557
|
+
warnLogger.warn(
|
|
2558
|
+
"FTS keyword search is unavailable; returning empty keyword hits."
|
|
2559
|
+
);
|
|
2560
|
+
}
|
|
2415
2561
|
return [];
|
|
2416
2562
|
}
|
|
2417
2563
|
try {
|
|
@@ -2425,7 +2571,16 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2425
2571
|
// bm25 returns lower (more negative) for better matches — invert to [0, ∞)
|
|
2426
2572
|
score: 1 / (1 + Math.abs(row.rank))
|
|
2427
2573
|
}));
|
|
2428
|
-
} catch {
|
|
2574
|
+
} catch (error) {
|
|
2575
|
+
if (!warnedFtsKeywordSearch) {
|
|
2576
|
+
warnedFtsKeywordSearch = true;
|
|
2577
|
+
warnLogger.warn(
|
|
2578
|
+
"FTS keyword search failed; returning empty keyword hits.",
|
|
2579
|
+
{
|
|
2580
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
2581
|
+
}
|
|
2582
|
+
);
|
|
2583
|
+
}
|
|
2429
2584
|
return [];
|
|
2430
2585
|
}
|
|
2431
2586
|
}
|
|
@@ -2656,16 +2811,39 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2656
2811
|
}
|
|
2657
2812
|
async withTransaction(fn) {
|
|
2658
2813
|
const db = this.requireDb();
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2814
|
+
if (this.transactionDepth > 0) {
|
|
2815
|
+
const savepointName = `wolbarg_sp_${this.savepointCounter++}`;
|
|
2816
|
+
db.exec(`SAVEPOINT ${savepointName}`);
|
|
2817
|
+
try {
|
|
2818
|
+
this.transactionDepth += 1;
|
|
2819
|
+
const result = await fn();
|
|
2820
|
+
db.exec(`RELEASE SAVEPOINT ${savepointName}`);
|
|
2821
|
+
return result;
|
|
2822
|
+
} catch (error) {
|
|
2823
|
+
try {
|
|
2824
|
+
db.exec(`ROLLBACK TO SAVEPOINT ${savepointName}`);
|
|
2825
|
+
} catch {
|
|
2826
|
+
}
|
|
2827
|
+
throw error;
|
|
2828
|
+
} finally {
|
|
2829
|
+
this.transactionDepth -= 1;
|
|
2667
2830
|
}
|
|
2668
|
-
|
|
2831
|
+
}
|
|
2832
|
+
this.transactionDepth = 1;
|
|
2833
|
+
try {
|
|
2834
|
+
return await withImmediateTransaction(
|
|
2835
|
+
db,
|
|
2836
|
+
this.concurrency,
|
|
2837
|
+
fn,
|
|
2838
|
+
(attempt, delayMs) => {
|
|
2839
|
+
this.retryLog?.(
|
|
2840
|
+
`SQLITE_BUSY retry attempt=${attempt} backoffMs=${Math.round(delayMs)}`
|
|
2841
|
+
);
|
|
2842
|
+
}
|
|
2843
|
+
);
|
|
2844
|
+
} finally {
|
|
2845
|
+
this.transactionDepth = 0;
|
|
2846
|
+
}
|
|
2669
2847
|
}
|
|
2670
2848
|
// ─── internals ───────────────────────────────────────────────────────────
|
|
2671
2849
|
tryLoadSqliteVec(db) {
|
|
@@ -3819,9 +3997,13 @@ function createPostgresListenerFromPool(pool, onError) {
|
|
|
3819
3997
|
|
|
3820
3998
|
// src/storage/providers/postgres.ts
|
|
3821
3999
|
var txStore = new async_hooks.AsyncLocalStorage();
|
|
4000
|
+
var warnLogger2 = new WolbargLogger("warn");
|
|
4001
|
+
var warnedPgvectorFallback = false;
|
|
4002
|
+
var warnedFtsKeywordSearch2 = false;
|
|
4003
|
+
var warnedHnswSoftFail = false;
|
|
3822
4004
|
var STMT = {
|
|
3823
4005
|
insertOne: "Wolbarg_insert_one_v5",
|
|
3824
|
-
insertBatch: "
|
|
4006
|
+
insertBatch: "Wolbarg_insert_batch_v6"
|
|
3825
4007
|
};
|
|
3826
4008
|
function toFloat4Param(embedding) {
|
|
3827
4009
|
const out = new Array(embedding.length);
|
|
@@ -3892,13 +4074,13 @@ var INSERT_ONE_SQL = `WITH mem AS (
|
|
|
3892
4074
|
var INSERT_BATCH_SQL = `WITH mem AS (
|
|
3893
4075
|
INSERT INTO memories (
|
|
3894
4076
|
id, organization, agent, content_text, metadata_json,
|
|
3895
|
-
archived, compressed_into, created_at, updated_at
|
|
4077
|
+
archived, compressed_into, content_hash, created_at, updated_at
|
|
3896
4078
|
)
|
|
3897
|
-
SELECT id, org, agent, txt, meta::jsonb, false, NULL, c, u
|
|
4079
|
+
SELECT id, org, agent, txt, meta::jsonb, false, NULL, h, c, u
|
|
3898
4080
|
FROM unnest(
|
|
3899
4081
|
$1::text[], $2::text[], $3::text[], $4::text[],
|
|
3900
|
-
$5::text[], $6::timestamptz[], $7::timestamptz[]
|
|
3901
|
-
) AS t(id, org, agent, txt, meta, c, u)
|
|
4082
|
+
$5::text[], $6::timestamptz[], $7::timestamptz[], $8::text[]
|
|
4083
|
+
) AS t(id, org, agent, txt, meta, c, u, h)
|
|
3902
4084
|
RETURNING id
|
|
3903
4085
|
),
|
|
3904
4086
|
hist AS (
|
|
@@ -3914,7 +4096,7 @@ var INSERT_BATCH_SQL = `WITH mem AS (
|
|
|
3914
4096
|
emb AS (
|
|
3915
4097
|
INSERT INTO memory_embeddings (memory_id, embedding, organization, agent, archived)
|
|
3916
4098
|
SELECT id, emb::vector, org, agent, false
|
|
3917
|
-
FROM unnest($1::text[], $
|
|
4099
|
+
FROM unnest($1::text[], $9::text[], $2::text[], $3::text[]) AS t(id, emb, org, agent)
|
|
3918
4100
|
)
|
|
3919
4101
|
SELECT id FROM mem`;
|
|
3920
4102
|
var COALESCE_FLUSH_MAX = 128;
|
|
@@ -4183,6 +4365,12 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4183
4365
|
return;
|
|
4184
4366
|
}
|
|
4185
4367
|
this.hnswCreateFailures += 1;
|
|
4368
|
+
if (this.hnswCreateFailures === 1 && !warnedHnswSoftFail) {
|
|
4369
|
+
warnedHnswSoftFail = true;
|
|
4370
|
+
warnLogger2.warn(
|
|
4371
|
+
"HNSW index creation failed; ANN will fall back to sequential scan."
|
|
4372
|
+
);
|
|
4373
|
+
}
|
|
4186
4374
|
if (this.hnswCreateFailures >= 3) {
|
|
4187
4375
|
throw new DatabaseError(
|
|
4188
4376
|
`Failed to create HNSW index after ${this.hnswCreateFailures} attempts: ${this.describe(error)}`,
|
|
@@ -4327,6 +4515,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4327
4515
|
const metas = new Array(inputs.length);
|
|
4328
4516
|
const created = new Array(inputs.length);
|
|
4329
4517
|
const updated = new Array(inputs.length);
|
|
4518
|
+
const contentHashes = new Array(inputs.length);
|
|
4330
4519
|
const vectors = new Array(inputs.length);
|
|
4331
4520
|
for (let i = 0; i < inputs.length; i += 1) {
|
|
4332
4521
|
const input = inputs[i];
|
|
@@ -4337,6 +4526,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4337
4526
|
metas[i] = serializeMetadata(input.metadata);
|
|
4338
4527
|
created[i] = input.createdAt;
|
|
4339
4528
|
updated[i] = input.updatedAt;
|
|
4529
|
+
contentHashes[i] = input.contentHash !== void 0 ? input.contentHash : hashMemoryContent(input.contentText);
|
|
4340
4530
|
vectors[i] = toVectorLiteral(input.embedding);
|
|
4341
4531
|
}
|
|
4342
4532
|
await this.queryNamed(STMT.insertBatch, INSERT_BATCH_SQL, [
|
|
@@ -4347,9 +4537,10 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4347
4537
|
metas,
|
|
4348
4538
|
created,
|
|
4349
4539
|
updated,
|
|
4540
|
+
contentHashes,
|
|
4350
4541
|
vectors
|
|
4351
4542
|
]);
|
|
4352
|
-
return inputs.map((
|
|
4543
|
+
return inputs.map((_, i) => ({
|
|
4353
4544
|
id: ids[i],
|
|
4354
4545
|
organization: orgs[i],
|
|
4355
4546
|
agent: agents[i],
|
|
@@ -4357,7 +4548,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4357
4548
|
metadata_json: metas[i],
|
|
4358
4549
|
archived: 0,
|
|
4359
4550
|
compressed_into: null,
|
|
4360
|
-
content_hash:
|
|
4551
|
+
content_hash: contentHashes[i] ?? null,
|
|
4361
4552
|
created_at: created[i],
|
|
4362
4553
|
updated_at: updated[i]
|
|
4363
4554
|
}));
|
|
@@ -4398,80 +4589,84 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4398
4589
|
return out;
|
|
4399
4590
|
}
|
|
4400
4591
|
async insertOneBlob(input) {
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4592
|
+
return this.withTransaction(async () => {
|
|
4593
|
+
const buf = Buffer.from(
|
|
4594
|
+
input.embedding.buffer,
|
|
4595
|
+
input.embedding.byteOffset,
|
|
4596
|
+
input.embedding.byteLength
|
|
4597
|
+
);
|
|
4598
|
+
const contentHash = input.contentHash !== void 0 ? input.contentHash : hashMemoryContent(input.contentText);
|
|
4599
|
+
const inserted = await this.query(
|
|
4600
|
+
`INSERT INTO memories (
|
|
4601
|
+
id, organization, agent, content_text, metadata_json,
|
|
4602
|
+
archived, compressed_into, content_hash, created_at, updated_at
|
|
4603
|
+
) VALUES ($1,$2,$3,$4,$5::jsonb,false,NULL,$8,$6,$7)
|
|
4604
|
+
RETURNING id, organization, agent, content_text, metadata_json,
|
|
4605
|
+
archived::int AS archived, compressed_into, content_hash, created_at, updated_at`,
|
|
4606
|
+
[
|
|
4607
|
+
input.id,
|
|
4608
|
+
input.organization,
|
|
4609
|
+
input.agent,
|
|
4610
|
+
input.contentText,
|
|
4611
|
+
serializeMetadata(input.metadata),
|
|
4612
|
+
input.createdAt,
|
|
4613
|
+
input.updatedAt,
|
|
4614
|
+
contentHash
|
|
4615
|
+
]
|
|
4616
|
+
);
|
|
4617
|
+
const row = this.mapRow(inserted.rows[0]);
|
|
4618
|
+
await this.query(
|
|
4619
|
+
`WITH mapped AS (
|
|
4620
|
+
INSERT INTO memory_row_map (memory_id) VALUES ($1)
|
|
4621
|
+
ON CONFLICT (memory_id) DO NOTHING
|
|
4622
|
+
)
|
|
4623
|
+
INSERT INTO memory_embeddings_blob (memory_id, embedding)
|
|
4624
|
+
VALUES ($1, $2)
|
|
4625
|
+
ON CONFLICT (memory_id) DO UPDATE SET embedding = EXCLUDED.embedding`,
|
|
4626
|
+
[input.id, buf]
|
|
4627
|
+
);
|
|
4628
|
+
await this.query(
|
|
4629
|
+
`INSERT INTO memory_history (id, memory_id, event_type, related_memory_id, created_at)
|
|
4630
|
+
VALUES ($1,$2,'created',NULL,$3)`,
|
|
4631
|
+
[crypto.randomUUID(), input.id, input.createdAt]
|
|
4632
|
+
);
|
|
4633
|
+
return row;
|
|
4634
|
+
});
|
|
4442
4635
|
}
|
|
4443
4636
|
async updateMemory(input) {
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4637
|
+
return this.withTransaction(async () => {
|
|
4638
|
+
const existing = await this.getMemoryById(input.id, input.organization);
|
|
4639
|
+
if (!existing) {
|
|
4640
|
+
return null;
|
|
4641
|
+
}
|
|
4642
|
+
const contentHash = input.contentHash !== void 0 ? input.contentHash : input.contentText !== void 0 ? hashMemoryContent(input.contentText) : existing.content_hash ?? null;
|
|
4643
|
+
await this.query(
|
|
4644
|
+
`UPDATE memories SET
|
|
4645
|
+
content_text = COALESCE($1, content_text),
|
|
4646
|
+
metadata_json = COALESCE($2::jsonb, metadata_json),
|
|
4647
|
+
content_hash = COALESCE($3, content_hash),
|
|
4648
|
+
updated_at = $4
|
|
4649
|
+
WHERE id = $5 AND organization = $6`,
|
|
4650
|
+
[
|
|
4651
|
+
input.contentText ?? null,
|
|
4652
|
+
input.metadata !== void 0 ? serializeMetadata(input.metadata) : null,
|
|
4653
|
+
contentHash,
|
|
4654
|
+
input.updatedAt,
|
|
4655
|
+
input.id,
|
|
4656
|
+
input.organization
|
|
4657
|
+
]
|
|
4658
|
+
);
|
|
4659
|
+
if (input.embedding) {
|
|
4660
|
+
await this.deleteEmbedding(input.id);
|
|
4661
|
+
await this.insertEmbedding(input.id, input.embedding);
|
|
4662
|
+
}
|
|
4663
|
+
await this.query(
|
|
4664
|
+
`INSERT INTO memory_history (id, memory_id, event_type, related_memory_id, created_at)
|
|
4665
|
+
VALUES ($1,$2,'updated',NULL,$3)`,
|
|
4666
|
+
[crypto.randomUUID(), input.id, input.updatedAt]
|
|
4667
|
+
);
|
|
4668
|
+
return this.getMemoryById(input.id, input.organization);
|
|
4669
|
+
});
|
|
4475
4670
|
}
|
|
4476
4671
|
async findActiveByContentHash(organization, agent, contentHash) {
|
|
4477
4672
|
const result = await this.query(
|
|
@@ -4620,7 +4815,16 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4620
4815
|
memoryId: String(row.memory_id),
|
|
4621
4816
|
score: Number(row.rank)
|
|
4622
4817
|
}));
|
|
4623
|
-
} catch {
|
|
4818
|
+
} catch (error) {
|
|
4819
|
+
if (!warnedFtsKeywordSearch2) {
|
|
4820
|
+
warnedFtsKeywordSearch2 = true;
|
|
4821
|
+
warnLogger2.warn(
|
|
4822
|
+
"FTS keyword search failed; returning empty keyword hits.",
|
|
4823
|
+
{
|
|
4824
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
4825
|
+
}
|
|
4826
|
+
);
|
|
4827
|
+
}
|
|
4624
4828
|
return [];
|
|
4625
4829
|
}
|
|
4626
4830
|
}
|
|
@@ -4738,44 +4942,46 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4738
4942
|
return mapHits(result.rows);
|
|
4739
4943
|
}
|
|
4740
4944
|
async archiveMemories(ids, organization, compressedIntoId, archivedAt) {
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4945
|
+
return this.withTransaction(async () => {
|
|
4946
|
+
if (ids.length === 0) {
|
|
4947
|
+
return [];
|
|
4948
|
+
}
|
|
4949
|
+
const result = await this.query(
|
|
4950
|
+
`UPDATE memories
|
|
4951
|
+
SET archived = true, compressed_into = $1, updated_at = $2
|
|
4952
|
+
WHERE organization = $3 AND archived = false AND id = ANY($4::text[])
|
|
4953
|
+
RETURNING id`,
|
|
4954
|
+
[compressedIntoId, archivedAt, organization, ids]
|
|
4955
|
+
);
|
|
4956
|
+
const archived = result.rows.map((r) => String(r.id));
|
|
4957
|
+
if (archived.length === 0) {
|
|
4958
|
+
return [];
|
|
4959
|
+
}
|
|
4960
|
+
await this.query(
|
|
4961
|
+
`UPDATE memory_embeddings
|
|
4962
|
+
SET archived = true
|
|
4963
|
+
WHERE memory_id = ANY($1::text[])`,
|
|
4964
|
+
[archived]
|
|
4965
|
+
);
|
|
4966
|
+
const histIds = [];
|
|
4967
|
+
const memIds = [];
|
|
4968
|
+
const types = [];
|
|
4969
|
+
const related = [];
|
|
4970
|
+
const times = [];
|
|
4971
|
+
for (const id of archived) {
|
|
4972
|
+
histIds.push(crypto.randomUUID(), crypto.randomUUID());
|
|
4973
|
+
memIds.push(id, compressedIntoId);
|
|
4974
|
+
types.push("archived", "compressed");
|
|
4975
|
+
related.push(compressedIntoId, id);
|
|
4976
|
+
times.push(archivedAt, archivedAt);
|
|
4977
|
+
}
|
|
4978
|
+
await this.query(
|
|
4979
|
+
`INSERT INTO memory_history (id, memory_id, event_type, related_memory_id, created_at)
|
|
4980
|
+
SELECT * FROM unnest($1::text[], $2::text[], $3::text[], $4::text[], $5::timestamptz[])`,
|
|
4981
|
+
[histIds, memIds, types, related, times]
|
|
4982
|
+
);
|
|
4983
|
+
return archived;
|
|
4984
|
+
});
|
|
4779
4985
|
}
|
|
4780
4986
|
async deleteMemoryById(id, organization) {
|
|
4781
4987
|
const result = await this.query(
|
|
@@ -4886,6 +5092,11 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4886
5092
|
[META_KEYS.schemaVersion]
|
|
4887
5093
|
).catch(() => ({ rows: [] }));
|
|
4888
5094
|
const current = versionRow.rows[0]?.value !== void 0 ? Number(versionRow.rows[0].value) : null;
|
|
5095
|
+
if (current !== null && current > SCHEMA_VERSION) {
|
|
5096
|
+
throw new InitializationError(
|
|
5097
|
+
`Database schema version ${current} is newer than this Wolbarg SDK (${SCHEMA_VERSION}). Please upgrade Wolbarg to open this database.`
|
|
5098
|
+
);
|
|
5099
|
+
}
|
|
4889
5100
|
if (current === SCHEMA_VERSION) {
|
|
4890
5101
|
const tsvProbe2 = await this.query(
|
|
4891
5102
|
`SELECT 1 FROM information_schema.columns
|
|
@@ -5036,8 +5247,17 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
5036
5247
|
await this.query(`CREATE EXTENSION IF NOT EXISTS vector`);
|
|
5037
5248
|
_PostgresStorageProvider.pgvectorByConn.set(this.connectionString, true);
|
|
5038
5249
|
return true;
|
|
5039
|
-
} catch {
|
|
5250
|
+
} catch (error) {
|
|
5040
5251
|
_PostgresStorageProvider.pgvectorByConn.set(this.connectionString, false);
|
|
5252
|
+
if (!warnedPgvectorFallback) {
|
|
5253
|
+
warnedPgvectorFallback = true;
|
|
5254
|
+
warnLogger2.warn(
|
|
5255
|
+
"pgvector extension is unavailable; using blob cosine search fallback.",
|
|
5256
|
+
{
|
|
5257
|
+
cause: error instanceof Error ? error.message : `unknown error: ${String(error)}`
|
|
5258
|
+
}
|
|
5259
|
+
);
|
|
5260
|
+
}
|
|
5041
5261
|
return false;
|
|
5042
5262
|
}
|
|
5043
5263
|
}
|
|
@@ -5178,11 +5398,12 @@ function createDatabaseProvider(config) {
|
|
|
5178
5398
|
}
|
|
5179
5399
|
|
|
5180
5400
|
// src/version.ts
|
|
5181
|
-
var SDK_VERSION = "0.5.
|
|
5401
|
+
var SDK_VERSION = "0.5.4";
|
|
5182
5402
|
|
|
5183
5403
|
// src/memory/transfer.ts
|
|
5404
|
+
var warnedMissingExportManifest = false;
|
|
5184
5405
|
var SqliteMemoryTransferProvider = class {
|
|
5185
|
-
async exportTo(exportPath, sourcePath, organization) {
|
|
5406
|
+
async exportTo(exportPath, sourcePath, organization, embeddingModel, embeddingDimensions) {
|
|
5186
5407
|
const resolvedSource = resolvePath(sourcePath);
|
|
5187
5408
|
if (resolvedSource === ":memory:") {
|
|
5188
5409
|
throw new ConfigurationError(
|
|
@@ -5209,7 +5430,9 @@ Initialize Wolbarg and verify the database path.`
|
|
|
5209
5430
|
sdkVersion: SDK_VERSION,
|
|
5210
5431
|
provider: "sqlite",
|
|
5211
5432
|
sourcePath: resolvedSource,
|
|
5212
|
-
organization
|
|
5433
|
+
organization,
|
|
5434
|
+
...embeddingModel ? { embeddingModel } : {},
|
|
5435
|
+
...embeddingDimensions ? { embeddingDimensions } : {}
|
|
5213
5436
|
};
|
|
5214
5437
|
fs6__default.default.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
|
|
5215
5438
|
return {
|
|
@@ -5218,7 +5441,7 @@ Initialize Wolbarg and verify the database path.`
|
|
|
5218
5441
|
sizeBytes: fs6__default.default.statSync(dbExportPath).size
|
|
5219
5442
|
};
|
|
5220
5443
|
}
|
|
5221
|
-
async importFrom(exportPath, targetPath) {
|
|
5444
|
+
async importFrom(exportPath, targetPath, expected) {
|
|
5222
5445
|
const resolvedExport = resolvePath(exportPath);
|
|
5223
5446
|
const dbExportPath = resolvedExport.endsWith(".db") ? resolvedExport : fs6__default.default.existsSync(`${resolvedExport}.db`) ? `${resolvedExport}.db` : resolvedExport;
|
|
5224
5447
|
if (!fs6__default.default.existsSync(dbExportPath)) {
|
|
@@ -5236,30 +5459,61 @@ Pass the path returned by export().`
|
|
|
5236
5459
|
manifest = JSON.parse(fs6__default.default.readFileSync(manifestPath, "utf8"));
|
|
5237
5460
|
if (manifest.format !== "wolbarg-export-v1") {
|
|
5238
5461
|
throw new ValidationError(
|
|
5239
|
-
`Unsupported export format: ${String(manifest.format)}`
|
|
5462
|
+
`Unsupported export format: ${String(manifest.format)}`
|
|
5463
|
+
);
|
|
5464
|
+
}
|
|
5465
|
+
} else {
|
|
5466
|
+
if (!warnedMissingExportManifest) {
|
|
5467
|
+
warnedMissingExportManifest = true;
|
|
5468
|
+
console.warn(
|
|
5469
|
+
"[wolbarg:warn] export manifest is missing; skipping manifest-based import validation."
|
|
5470
|
+
);
|
|
5471
|
+
}
|
|
5472
|
+
}
|
|
5473
|
+
if (manifest) {
|
|
5474
|
+
if (expected?.organization && manifest.organization && expected.organization !== manifest.organization) {
|
|
5475
|
+
throw new ValidationError(
|
|
5476
|
+
"Import failed: export organization does not match the current Wolbarg organization."
|
|
5477
|
+
);
|
|
5478
|
+
}
|
|
5479
|
+
if (expected?.embeddingDimensions != null && manifest.embeddingDimensions != null && expected.embeddingDimensions !== manifest.embeddingDimensions) {
|
|
5480
|
+
throw new ValidationError(
|
|
5481
|
+
`Import failed: embedding dimension mismatch (expected ${expected.embeddingDimensions}, got ${manifest.embeddingDimensions}).`
|
|
5482
|
+
);
|
|
5483
|
+
}
|
|
5484
|
+
if (expected?.embeddingModel && manifest.embeddingModel && expected.embeddingModel !== manifest.embeddingModel) {
|
|
5485
|
+
throw new ValidationError(
|
|
5486
|
+
`Import failed: embedding model mismatch (expected ${expected.embeddingModel}, got ${manifest.embeddingModel}).`
|
|
5240
5487
|
);
|
|
5241
5488
|
}
|
|
5242
|
-
} else {
|
|
5243
|
-
manifest = {
|
|
5244
|
-
format: "wolbarg-export-v1",
|
|
5245
|
-
exportedAt: nowIso(),
|
|
5246
|
-
sdkVersion: SDK_VERSION,
|
|
5247
|
-
provider: "sqlite",
|
|
5248
|
-
sourcePath: dbExportPath
|
|
5249
|
-
};
|
|
5250
5489
|
}
|
|
5251
5490
|
const resolvedTarget = resolvePath(targetPath);
|
|
5252
5491
|
if (resolvedTarget === ":memory:") {
|
|
5253
5492
|
throw new ConfigurationError("Cannot import into an in-memory database.");
|
|
5254
5493
|
}
|
|
5494
|
+
const tmpTarget = `${resolvedTarget}.tmp-import-${Date.now()}`;
|
|
5495
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
5496
|
+
const side = `${tmpTarget}${suffix}`;
|
|
5497
|
+
if (fs6__default.default.existsSync(side)) {
|
|
5498
|
+
fs6__default.default.rmSync(side, { force: true });
|
|
5499
|
+
}
|
|
5500
|
+
}
|
|
5501
|
+
await copySqlite(dbExportPath, tmpTarget);
|
|
5255
5502
|
for (const suffix of ["", "-wal", "-shm"]) {
|
|
5256
5503
|
const side = `${resolvedTarget}${suffix}`;
|
|
5257
5504
|
if (fs6__default.default.existsSync(side)) {
|
|
5258
5505
|
fs6__default.default.rmSync(side, { force: true });
|
|
5259
5506
|
}
|
|
5260
5507
|
}
|
|
5261
|
-
|
|
5262
|
-
|
|
5508
|
+
fs6__default.default.renameSync(tmpTarget, resolvedTarget);
|
|
5509
|
+
for (const suffix of ["-wal", "-shm"]) {
|
|
5510
|
+
const from = `${tmpTarget}${suffix}`;
|
|
5511
|
+
const to = `${resolvedTarget}${suffix}`;
|
|
5512
|
+
if (fs6__default.default.existsSync(from)) {
|
|
5513
|
+
fs6__default.default.renameSync(from, to);
|
|
5514
|
+
}
|
|
5515
|
+
}
|
|
5516
|
+
return { path: resolvedTarget, manifest: manifest ?? void 0 };
|
|
5263
5517
|
}
|
|
5264
5518
|
};
|
|
5265
5519
|
async function copySqlite(sourcePath, destPath) {
|
|
@@ -5488,7 +5742,8 @@ function applyMmr(candidates, topK, lambda) {
|
|
|
5488
5742
|
);
|
|
5489
5743
|
}
|
|
5490
5744
|
const score = lambda * relevance - (1 - lambda) * maxSim;
|
|
5491
|
-
|
|
5745
|
+
const best = remaining[bestIdx];
|
|
5746
|
+
if (score > bestScore || score === bestScore && candidate.id < best.id) {
|
|
5492
5747
|
bestScore = score;
|
|
5493
5748
|
bestIdx = i;
|
|
5494
5749
|
}
|
|
@@ -6048,24 +6303,26 @@ var Neo4jGraphProvider = class {
|
|
|
6048
6303
|
}
|
|
6049
6304
|
}
|
|
6050
6305
|
async run(cypher, params = {}) {
|
|
6051
|
-
return this.withSession(
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6306
|
+
return this.withSession(
|
|
6307
|
+
(session) => this.runOnSession(session, cypher, params)
|
|
6308
|
+
);
|
|
6309
|
+
}
|
|
6310
|
+
async runOnSession(session, cypher, params = {}) {
|
|
6311
|
+
const result = await session.run(cypher, params);
|
|
6312
|
+
return result.records.map((rec) => {
|
|
6313
|
+
try {
|
|
6314
|
+
return rec.toObject();
|
|
6315
|
+
} catch {
|
|
6316
|
+
const obj = {};
|
|
6317
|
+
for (const key of rec.keys) {
|
|
6318
|
+
obj[key] = unwrapNeo4jValue(rec.get(key));
|
|
6062
6319
|
}
|
|
6063
|
-
|
|
6320
|
+
return obj;
|
|
6321
|
+
}
|
|
6064
6322
|
});
|
|
6065
6323
|
}
|
|
6066
|
-
async ensureMemoryNode(id) {
|
|
6067
|
-
|
|
6068
|
-
`MERGE (m:Memory { id: $id })
|
|
6324
|
+
async ensureMemoryNode(id, session) {
|
|
6325
|
+
const cypher = `MERGE (m:Memory { id: $id })
|
|
6069
6326
|
ON CREATE SET
|
|
6070
6327
|
m.organization = '',
|
|
6071
6328
|
m.agent = '',
|
|
@@ -6074,24 +6331,30 @@ var Neo4jGraphProvider = class {
|
|
|
6074
6331
|
m.archived = false,
|
|
6075
6332
|
m.compressed_into = '',
|
|
6076
6333
|
m.created_at = '',
|
|
6077
|
-
m.updated_at = ''
|
|
6078
|
-
|
|
6079
|
-
|
|
6334
|
+
m.updated_at = ''`;
|
|
6335
|
+
if (session) {
|
|
6336
|
+
await this.runOnSession(session, cypher, { id });
|
|
6337
|
+
return;
|
|
6338
|
+
}
|
|
6339
|
+
await this.run(cypher, { id });
|
|
6080
6340
|
}
|
|
6081
6341
|
async linkMemories(fromId, toId, relation, metadata) {
|
|
6082
|
-
await this.
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6342
|
+
await this.withSession(async (session) => {
|
|
6343
|
+
await this.ensureMemoryNode(fromId, session);
|
|
6344
|
+
await this.ensureMemoryNode(toId, session);
|
|
6345
|
+
await this.runOnSession(
|
|
6346
|
+
session,
|
|
6347
|
+
`MATCH (a:Memory { id: $fromId }), (b:Memory { id: $toId })
|
|
6348
|
+
MERGE (a)-[r:RELATED { relation: $relation }]->(b)
|
|
6349
|
+
SET r.metadata_json = $metadata`,
|
|
6350
|
+
{
|
|
6351
|
+
fromId,
|
|
6352
|
+
toId,
|
|
6353
|
+
relation,
|
|
6354
|
+
metadata: serializeMetadata2(metadata)
|
|
6355
|
+
}
|
|
6356
|
+
);
|
|
6357
|
+
});
|
|
6095
6358
|
}
|
|
6096
6359
|
async unlinkMemories(fromId, toId, relation) {
|
|
6097
6360
|
if (relation !== void 0) {
|
|
@@ -6153,22 +6416,26 @@ var Neo4jGraphProvider = class {
|
|
|
6153
6416
|
return id;
|
|
6154
6417
|
}
|
|
6155
6418
|
async linkEntityToMemory(entityId, memoryId, role) {
|
|
6156
|
-
await this.
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6419
|
+
await this.withSession(async (session) => {
|
|
6420
|
+
await this.ensureMemoryNode(memoryId, session);
|
|
6421
|
+
const found = await this.runOnSession(
|
|
6422
|
+
session,
|
|
6423
|
+
`MATCH (e:Entity { id: $id }) RETURN e.id AS id`,
|
|
6424
|
+
{ id: entityId }
|
|
6425
|
+
);
|
|
6426
|
+
if (found.length === 0) {
|
|
6427
|
+
throw new DatabaseError(`Entity not found: ${entityId}`, {
|
|
6428
|
+
operation: "linkEntityToMemory"
|
|
6429
|
+
});
|
|
6430
|
+
}
|
|
6431
|
+
await this.runOnSession(
|
|
6432
|
+
session,
|
|
6433
|
+
`MATCH (e:Entity { id: $entityId }), (m:Memory { id: $memoryId })
|
|
6434
|
+
MERGE (e)-[r:MENTIONS]->(m)
|
|
6435
|
+
SET r.role = $role`,
|
|
6436
|
+
{ entityId, memoryId, role: role ?? "" }
|
|
6437
|
+
);
|
|
6438
|
+
});
|
|
6172
6439
|
}
|
|
6173
6440
|
async deleteMemory(memoryId) {
|
|
6174
6441
|
await this.run(
|
|
@@ -6241,6 +6508,21 @@ function assertNonEmpty(value, fieldName) {
|
|
|
6241
6508
|
throw new ConfigurationError(`${fieldName} must be a non-empty string`);
|
|
6242
6509
|
}
|
|
6243
6510
|
}
|
|
6511
|
+
function assertFiniteNumber2(value, fieldName) {
|
|
6512
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
6513
|
+
throw new ConfigurationError(`${fieldName} must be a finite number`);
|
|
6514
|
+
}
|
|
6515
|
+
}
|
|
6516
|
+
function assertNumberMin(value, fieldName, min) {
|
|
6517
|
+
if (!Number.isFinite(value) || value < min) {
|
|
6518
|
+
throw new ConfigurationError(`${fieldName} must be >= ${min}`);
|
|
6519
|
+
}
|
|
6520
|
+
}
|
|
6521
|
+
function assertNumberInRange(value, fieldName, min, max) {
|
|
6522
|
+
if (!Number.isFinite(value) || value < min || value > max) {
|
|
6523
|
+
throw new ConfigurationError(`${fieldName} must be between ${min} and ${max}`);
|
|
6524
|
+
}
|
|
6525
|
+
}
|
|
6244
6526
|
function assertUrl(value, fieldName) {
|
|
6245
6527
|
assertNonEmpty(value, fieldName);
|
|
6246
6528
|
try {
|
|
@@ -6287,6 +6569,135 @@ function validateLlmConfig(config) {
|
|
|
6287
6569
|
...config.timeoutMs !== void 0 ? { timeoutMs: config.timeoutMs } : {}
|
|
6288
6570
|
};
|
|
6289
6571
|
}
|
|
6572
|
+
function validateRetrievalConfig(config) {
|
|
6573
|
+
if (config === null || typeof config !== "object") {
|
|
6574
|
+
throw new ConfigurationError("retrieval must be an object");
|
|
6575
|
+
}
|
|
6576
|
+
const out = {};
|
|
6577
|
+
if (config.overFetchFactor !== void 0) {
|
|
6578
|
+
assertFiniteNumber2(config.overFetchFactor, "retrieval.overFetchFactor");
|
|
6579
|
+
assertNumberMin(config.overFetchFactor, "retrieval.overFetchFactor", 0);
|
|
6580
|
+
if (config.overFetchFactor <= 0) {
|
|
6581
|
+
throw new ConfigurationError("retrieval.overFetchFactor must be > 0");
|
|
6582
|
+
}
|
|
6583
|
+
out.overFetchFactor = config.overFetchFactor;
|
|
6584
|
+
}
|
|
6585
|
+
if (config.hybrid !== void 0) {
|
|
6586
|
+
if (config.hybrid === null || typeof config.hybrid !== "object") {
|
|
6587
|
+
throw new ConfigurationError("retrieval.hybrid must be an object");
|
|
6588
|
+
}
|
|
6589
|
+
const hybrid = config.hybrid;
|
|
6590
|
+
const outHybrid = {};
|
|
6591
|
+
if (hybrid.semanticWeight !== void 0) {
|
|
6592
|
+
assertFiniteNumber2(hybrid.semanticWeight, "retrieval.hybrid.semanticWeight");
|
|
6593
|
+
if (hybrid.semanticWeight < 0) {
|
|
6594
|
+
throw new ConfigurationError(
|
|
6595
|
+
"retrieval.hybrid.semanticWeight must be >= 0"
|
|
6596
|
+
);
|
|
6597
|
+
}
|
|
6598
|
+
outHybrid.semanticWeight = hybrid.semanticWeight;
|
|
6599
|
+
}
|
|
6600
|
+
if (hybrid.keywordWeight !== void 0) {
|
|
6601
|
+
assertFiniteNumber2(hybrid.keywordWeight, "retrieval.hybrid.keywordWeight");
|
|
6602
|
+
if (hybrid.keywordWeight < 0) {
|
|
6603
|
+
throw new ConfigurationError(
|
|
6604
|
+
"retrieval.hybrid.keywordWeight must be >= 0"
|
|
6605
|
+
);
|
|
6606
|
+
}
|
|
6607
|
+
outHybrid.keywordWeight = hybrid.keywordWeight;
|
|
6608
|
+
}
|
|
6609
|
+
if (Object.keys(outHybrid).length > 0) {
|
|
6610
|
+
out.hybrid = outHybrid;
|
|
6611
|
+
}
|
|
6612
|
+
}
|
|
6613
|
+
if (config.mmr !== void 0) {
|
|
6614
|
+
if (config.mmr === null || typeof config.mmr !== "object") {
|
|
6615
|
+
throw new ConfigurationError("retrieval.mmr must be an object");
|
|
6616
|
+
}
|
|
6617
|
+
const mmr = config.mmr;
|
|
6618
|
+
const outMmr = {};
|
|
6619
|
+
if (mmr.lambda !== void 0) {
|
|
6620
|
+
assertFiniteNumber2(mmr.lambda, "retrieval.mmr.lambda");
|
|
6621
|
+
assertNumberInRange(mmr.lambda, "retrieval.mmr.lambda", 0, 1);
|
|
6622
|
+
outMmr.lambda = mmr.lambda;
|
|
6623
|
+
}
|
|
6624
|
+
if (Object.keys(outMmr).length > 0) {
|
|
6625
|
+
out.mmr = outMmr;
|
|
6626
|
+
}
|
|
6627
|
+
}
|
|
6628
|
+
return out;
|
|
6629
|
+
}
|
|
6630
|
+
function validateMemoryDedupeConfig(config) {
|
|
6631
|
+
if (config === null || typeof config !== "object") {
|
|
6632
|
+
throw new ConfigurationError("memory.dedupe must be an object");
|
|
6633
|
+
}
|
|
6634
|
+
const out = {};
|
|
6635
|
+
if (config.enabled !== void 0) {
|
|
6636
|
+
if (typeof config.enabled !== "boolean") {
|
|
6637
|
+
throw new ConfigurationError("memory.dedupe.enabled must be a boolean");
|
|
6638
|
+
}
|
|
6639
|
+
out.enabled = config.enabled;
|
|
6640
|
+
}
|
|
6641
|
+
if (config.strategy !== void 0) {
|
|
6642
|
+
const allowed = /* @__PURE__ */ new Set(["exact", "near", "exact-or-near"]);
|
|
6643
|
+
if (!allowed.has(config.strategy)) {
|
|
6644
|
+
throw new ConfigurationError(
|
|
6645
|
+
`memory.dedupe.strategy must be one of ${Array.from(allowed).join(", ")}`
|
|
6646
|
+
);
|
|
6647
|
+
}
|
|
6648
|
+
out.strategy = config.strategy;
|
|
6649
|
+
}
|
|
6650
|
+
if (config.nearThreshold !== void 0) {
|
|
6651
|
+
assertFiniteNumber2(config.nearThreshold, "memory.dedupe.nearThreshold");
|
|
6652
|
+
assertNumberInRange(
|
|
6653
|
+
config.nearThreshold,
|
|
6654
|
+
"memory.dedupe.nearThreshold",
|
|
6655
|
+
0,
|
|
6656
|
+
1
|
|
6657
|
+
);
|
|
6658
|
+
out.nearThreshold = config.nearThreshold;
|
|
6659
|
+
}
|
|
6660
|
+
if (config.nearCandidateLimit !== void 0) {
|
|
6661
|
+
assertFiniteNumber2(
|
|
6662
|
+
config.nearCandidateLimit,
|
|
6663
|
+
"memory.dedupe.nearCandidateLimit"
|
|
6664
|
+
);
|
|
6665
|
+
assertNumberMin(
|
|
6666
|
+
config.nearCandidateLimit,
|
|
6667
|
+
"memory.dedupe.nearCandidateLimit",
|
|
6668
|
+
1
|
|
6669
|
+
);
|
|
6670
|
+
out.nearCandidateLimit = config.nearCandidateLimit;
|
|
6671
|
+
}
|
|
6672
|
+
return out;
|
|
6673
|
+
}
|
|
6674
|
+
function validateEmbeddingCacheConfig(config) {
|
|
6675
|
+
if (config === null || typeof config !== "object") {
|
|
6676
|
+
throw new ConfigurationError("embeddingCache must be an object");
|
|
6677
|
+
}
|
|
6678
|
+
const out = {};
|
|
6679
|
+
if (config.enabled !== void 0) {
|
|
6680
|
+
if (typeof config.enabled !== "boolean") {
|
|
6681
|
+
throw new ConfigurationError("embeddingCache.enabled must be a boolean");
|
|
6682
|
+
}
|
|
6683
|
+
out.enabled = config.enabled;
|
|
6684
|
+
}
|
|
6685
|
+
if (config.ttlMs !== void 0) {
|
|
6686
|
+
assertFiniteNumber2(config.ttlMs, "embeddingCache.ttlMs");
|
|
6687
|
+
if (config.ttlMs < 0) {
|
|
6688
|
+
throw new ConfigurationError("embeddingCache.ttlMs must be >= 0");
|
|
6689
|
+
}
|
|
6690
|
+
out.ttlMs = config.ttlMs;
|
|
6691
|
+
}
|
|
6692
|
+
if (config.maxEntries !== void 0) {
|
|
6693
|
+
assertFiniteNumber2(config.maxEntries, "embeddingCache.maxEntries");
|
|
6694
|
+
if (config.maxEntries < 0) {
|
|
6695
|
+
throw new ConfigurationError("embeddingCache.maxEntries must be >= 0");
|
|
6696
|
+
}
|
|
6697
|
+
out.maxEntries = config.maxEntries;
|
|
6698
|
+
}
|
|
6699
|
+
return out;
|
|
6700
|
+
}
|
|
6290
6701
|
function normalizeDatabaseConfig(config) {
|
|
6291
6702
|
const provider = config.provider;
|
|
6292
6703
|
if (provider !== "sqlite" && provider !== "postgres") {
|
|
@@ -6396,11 +6807,22 @@ function validateWolbargOptions(options) {
|
|
|
6396
6807
|
throw new ConfigurationError("Wolbarg options must be an object");
|
|
6397
6808
|
}
|
|
6398
6809
|
assertNonEmpty(options.organization, "organization");
|
|
6810
|
+
const checkpointDirectory = options.checkpointDirectory !== void 0 ? (() => {
|
|
6811
|
+
assertNonEmpty(options.checkpointDirectory, "checkpointDirectory");
|
|
6812
|
+
return options.checkpointDirectory.trim();
|
|
6813
|
+
})() : void 0;
|
|
6399
6814
|
const storageInput = resolveStorageInput(options);
|
|
6400
6815
|
let storage = storageInput;
|
|
6401
6816
|
if (!isStorageProvider(storageInput)) {
|
|
6402
6817
|
storage = normalizeDatabaseConfig(storageInput);
|
|
6403
6818
|
}
|
|
6819
|
+
if (!isStorageProvider(storageInput) && storageInput.provider === "postgres" && storageInput.maxPoolSize !== void 0) {
|
|
6820
|
+
assertFiniteNumber2(
|
|
6821
|
+
storageInput.maxPoolSize,
|
|
6822
|
+
"database.maxPoolSize"
|
|
6823
|
+
);
|
|
6824
|
+
assertNumberMin(storageInput.maxPoolSize, "database.maxPoolSize", 1);
|
|
6825
|
+
}
|
|
6404
6826
|
if (!options.embedding) {
|
|
6405
6827
|
throw new ConfigurationError("embedding is required");
|
|
6406
6828
|
}
|
|
@@ -6418,13 +6840,20 @@ function validateWolbargOptions(options) {
|
|
|
6418
6840
|
if (graph !== void 0) {
|
|
6419
6841
|
graph = resolveGraphInput(graph);
|
|
6420
6842
|
}
|
|
6843
|
+
const retrieval = options.retrieval !== void 0 ? validateRetrievalConfig(options.retrieval) : void 0;
|
|
6844
|
+
const embeddingCache = options.embeddingCache !== void 0 ? validateEmbeddingCacheConfig(options.embeddingCache) : void 0;
|
|
6845
|
+
const memory = options.memory !== void 0 && options.memory.dedupe !== void 0 ? { ...options.memory, dedupe: validateMemoryDedupeConfig(options.memory.dedupe) } : options.memory;
|
|
6421
6846
|
return {
|
|
6422
6847
|
...options,
|
|
6423
6848
|
organization: options.organization.trim(),
|
|
6424
6849
|
storage,
|
|
6425
6850
|
database: void 0,
|
|
6426
6851
|
...telemetry ? { telemetry } : {},
|
|
6427
|
-
...graph !== void 0 ? { graph } : {}
|
|
6852
|
+
...graph !== void 0 ? { graph } : {},
|
|
6853
|
+
...retrieval !== void 0 ? { retrieval } : {},
|
|
6854
|
+
...embeddingCache !== void 0 ? { embeddingCache } : {},
|
|
6855
|
+
...checkpointDirectory !== void 0 ? { checkpointDirectory } : {},
|
|
6856
|
+
...memory !== void 0 ? { memory } : {}
|
|
6428
6857
|
};
|
|
6429
6858
|
}
|
|
6430
6859
|
function resolveGraphInput(input) {
|
|
@@ -6483,59 +6912,6 @@ function resolveGraphInput(input) {
|
|
|
6483
6912
|
);
|
|
6484
6913
|
}
|
|
6485
6914
|
|
|
6486
|
-
// src/telemetry/logger.ts
|
|
6487
|
-
var LEVEL_ORDER = {
|
|
6488
|
-
off: 100,
|
|
6489
|
-
error: 50,
|
|
6490
|
-
warn: 40,
|
|
6491
|
-
info: 30,
|
|
6492
|
-
debug: 20,
|
|
6493
|
-
trace: 10
|
|
6494
|
-
};
|
|
6495
|
-
var WolbargLogger = class {
|
|
6496
|
-
constructor(level = "info") {
|
|
6497
|
-
this.level = level;
|
|
6498
|
-
}
|
|
6499
|
-
level;
|
|
6500
|
-
setLevel(level) {
|
|
6501
|
-
this.level = level;
|
|
6502
|
-
}
|
|
6503
|
-
getLevel() {
|
|
6504
|
-
return this.level;
|
|
6505
|
-
}
|
|
6506
|
-
error(message, extra) {
|
|
6507
|
-
this.write("error", message, extra);
|
|
6508
|
-
}
|
|
6509
|
-
warn(message, extra) {
|
|
6510
|
-
this.write("warn", message, extra);
|
|
6511
|
-
}
|
|
6512
|
-
info(message, extra) {
|
|
6513
|
-
this.write("info", message, extra);
|
|
6514
|
-
}
|
|
6515
|
-
debug(message, extra) {
|
|
6516
|
-
this.write("debug", message, extra);
|
|
6517
|
-
}
|
|
6518
|
-
trace(message, extra) {
|
|
6519
|
-
this.write("trace", message, extra);
|
|
6520
|
-
}
|
|
6521
|
-
write(level, message, extra) {
|
|
6522
|
-
if (LEVEL_ORDER[level] < LEVEL_ORDER[this.level]) {
|
|
6523
|
-
return;
|
|
6524
|
-
}
|
|
6525
|
-
if (this.level === "off") {
|
|
6526
|
-
return;
|
|
6527
|
-
}
|
|
6528
|
-
const line = `[wolbarg:${level}] ${message}`;
|
|
6529
|
-
if (level === "error") {
|
|
6530
|
-
console.error(line, extra ?? "");
|
|
6531
|
-
} else if (level === "warn") {
|
|
6532
|
-
console.warn(line, extra ?? "");
|
|
6533
|
-
} else {
|
|
6534
|
-
console.log(line, extra ?? "");
|
|
6535
|
-
}
|
|
6536
|
-
}
|
|
6537
|
-
};
|
|
6538
|
-
|
|
6539
6915
|
// src/telemetry/trace.ts
|
|
6540
6916
|
function createSessionId() {
|
|
6541
6917
|
return createId();
|
|
@@ -6620,14 +6996,14 @@ var TelemetryEmitter = class {
|
|
|
6620
6996
|
const totalMs = performance.now() - startedAt;
|
|
6621
6997
|
const errMessage = error instanceof Error ? error.message : error ? String(error) : fields?.error ?? null;
|
|
6622
6998
|
const errStack = error instanceof Error ? error.stack ?? null : fields?.errorStack ?? null;
|
|
6999
|
+
if (!self.config.enabled) {
|
|
7000
|
+
return;
|
|
7001
|
+
}
|
|
6623
7002
|
if (status === "error") {
|
|
6624
7003
|
self.logger.error(`${operation} failed: ${errMessage ?? "unknown"}`);
|
|
6625
7004
|
} else {
|
|
6626
7005
|
self.logger.debug(`${operation} completed in ${totalMs.toFixed(2)}ms`);
|
|
6627
7006
|
}
|
|
6628
|
-
if (!self.config.enabled) {
|
|
6629
|
-
return;
|
|
6630
|
-
}
|
|
6631
7007
|
const event = {
|
|
6632
7008
|
id: createId(),
|
|
6633
7009
|
timestamp: nowIso(),
|
|
@@ -6929,8 +7305,10 @@ var SqliteEventDatabase = class {
|
|
|
6929
7305
|
}
|
|
6930
7306
|
}
|
|
6931
7307
|
if (options.memoryId) {
|
|
6932
|
-
clauses.push(
|
|
6933
|
-
|
|
7308
|
+
clauses.push(
|
|
7309
|
+
`EXISTS (SELECT 1 FROM json_each(memory_ids_json) WHERE json_each.value = ?)`
|
|
7310
|
+
);
|
|
7311
|
+
params.push(options.memoryId);
|
|
6934
7312
|
}
|
|
6935
7313
|
if (options.queryText) {
|
|
6936
7314
|
clauses.push(`query LIKE ?`);
|
|
@@ -7107,6 +7485,7 @@ function describe2(error) {
|
|
|
7107
7485
|
}
|
|
7108
7486
|
|
|
7109
7487
|
// src/providers/sqlite/sqliteTelemetryProvider.ts
|
|
7488
|
+
var MAX_QUEUE_SIZE = 1e4;
|
|
7110
7489
|
var SqliteTelemetryProvider = class {
|
|
7111
7490
|
name = "sqlite";
|
|
7112
7491
|
db;
|
|
@@ -7114,6 +7493,8 @@ var SqliteTelemetryProvider = class {
|
|
|
7114
7493
|
flushing = null;
|
|
7115
7494
|
closed = false;
|
|
7116
7495
|
openPromise = null;
|
|
7496
|
+
warnedQueueDrop = false;
|
|
7497
|
+
warnedFlushFailure = false;
|
|
7117
7498
|
constructor(options) {
|
|
7118
7499
|
this.db = new SqliteEventDatabase({ url: options.url });
|
|
7119
7500
|
}
|
|
@@ -7134,6 +7515,15 @@ var SqliteTelemetryProvider = class {
|
|
|
7134
7515
|
if (this.closed) {
|
|
7135
7516
|
return;
|
|
7136
7517
|
}
|
|
7518
|
+
if (this.queue.length >= MAX_QUEUE_SIZE) {
|
|
7519
|
+
this.queue.shift();
|
|
7520
|
+
if (!this.warnedQueueDrop) {
|
|
7521
|
+
this.warnedQueueDrop = true;
|
|
7522
|
+
console.warn(
|
|
7523
|
+
`[wolbarg telemetry] event queue capped at ${MAX_QUEUE_SIZE}; dropping oldest events`
|
|
7524
|
+
);
|
|
7525
|
+
}
|
|
7526
|
+
}
|
|
7137
7527
|
this.queue.push(event);
|
|
7138
7528
|
void this.scheduleFlush();
|
|
7139
7529
|
}
|
|
@@ -7176,7 +7566,13 @@ var SqliteTelemetryProvider = class {
|
|
|
7176
7566
|
await this.db.insertEvent(event);
|
|
7177
7567
|
}
|
|
7178
7568
|
}
|
|
7179
|
-
} catch {
|
|
7569
|
+
} catch (error) {
|
|
7570
|
+
if (!this.warnedFlushFailure) {
|
|
7571
|
+
this.warnedFlushFailure = true;
|
|
7572
|
+
console.warn(
|
|
7573
|
+
`[wolbarg telemetry] flush failed; dropping batch: ${error instanceof Error ? error.message : String(error)}`
|
|
7574
|
+
);
|
|
7575
|
+
}
|
|
7180
7576
|
}
|
|
7181
7577
|
}
|
|
7182
7578
|
};
|
|
@@ -7219,8 +7615,20 @@ Ensure Wolbarg has been initialized and the database path is correct.`
|
|
|
7219
7615
|
);
|
|
7220
7616
|
}
|
|
7221
7617
|
const snapshotPath = this.snapshotPath(name);
|
|
7222
|
-
|
|
7223
|
-
|
|
7618
|
+
const tmpSnapshotPath = `${snapshotPath}.tmp-${Date.now()}`;
|
|
7619
|
+
try {
|
|
7620
|
+
await safeSqliteBackup(resolvedSource, tmpSnapshotPath);
|
|
7621
|
+
} catch (error) {
|
|
7622
|
+
if (fs6__default.default.existsSync(tmpSnapshotPath)) {
|
|
7623
|
+
fs6__default.default.rmSync(tmpSnapshotPath, { force: true });
|
|
7624
|
+
}
|
|
7625
|
+
throw error;
|
|
7626
|
+
}
|
|
7627
|
+
const stats = fs6__default.default.statSync(tmpSnapshotPath);
|
|
7628
|
+
if (fs6__default.default.existsSync(snapshotPath)) {
|
|
7629
|
+
fs6__default.default.rmSync(snapshotPath, { force: true });
|
|
7630
|
+
}
|
|
7631
|
+
fs6__default.default.renameSync(tmpSnapshotPath, snapshotPath);
|
|
7224
7632
|
const meta2 = {
|
|
7225
7633
|
name,
|
|
7226
7634
|
description: options?.description ?? null,
|
|
@@ -7231,7 +7639,9 @@ Ensure Wolbarg has been initialized and the database path is correct.`
|
|
|
7231
7639
|
sourcePath: resolvedSource,
|
|
7232
7640
|
sizeBytes: stats.size
|
|
7233
7641
|
};
|
|
7234
|
-
|
|
7642
|
+
const tmpMetaPath = `${metaPath}.tmp-${Date.now()}`;
|
|
7643
|
+
fs6__default.default.writeFileSync(tmpMetaPath, JSON.stringify(meta2, null, 2), "utf8");
|
|
7644
|
+
fs6__default.default.renameSync(tmpMetaPath, metaPath);
|
|
7235
7645
|
return meta2;
|
|
7236
7646
|
}
|
|
7237
7647
|
async rollback(name, targetPath) {
|
|
@@ -7495,6 +7905,9 @@ var SqliteSubscribeEmitter = class {
|
|
|
7495
7905
|
};
|
|
7496
7906
|
|
|
7497
7907
|
// src/core/wolbarg.ts
|
|
7908
|
+
var warnLogger3 = new WolbargLogger("warn");
|
|
7909
|
+
var warnedHybridNoKeyword = false;
|
|
7910
|
+
var warnedRerankNoProvider = false;
|
|
7498
7911
|
var Wolbarg = class {
|
|
7499
7912
|
initialized = false;
|
|
7500
7913
|
booting = null;
|
|
@@ -7600,6 +8013,7 @@ var Wolbarg = class {
|
|
|
7600
8013
|
this.storage = createStorageProvider(validated.database);
|
|
7601
8014
|
this.memoryDbPath = resolveDatabaseUrl(validated.database);
|
|
7602
8015
|
this.embedding = createEmbeddingProvider(validated.embedding);
|
|
8016
|
+
this.rawEmbedding = this.embedding;
|
|
7603
8017
|
if (validated.llm) {
|
|
7604
8018
|
this.llm = createLlmProvider(validated.llm);
|
|
7605
8019
|
this.compression = createCompressionProvider(this.llm);
|
|
@@ -7921,7 +8335,7 @@ var Wolbarg = class {
|
|
|
7921
8335
|
* Update an existing memory by id (re-embeds when content changes).
|
|
7922
8336
|
*/
|
|
7923
8337
|
async update(options) {
|
|
7924
|
-
const trace = this.telemetry.start("
|
|
8338
|
+
const trace = this.telemetry.start("update");
|
|
7925
8339
|
try {
|
|
7926
8340
|
const { storage, embedding, organization } = await this.requireReady();
|
|
7927
8341
|
assertNonEmptyString(options.id, "id");
|
|
@@ -8447,17 +8861,37 @@ var Wolbarg = class {
|
|
|
8447
8861
|
}
|
|
8448
8862
|
}
|
|
8449
8863
|
}
|
|
8864
|
+
if (hybridWeights && keywordSignal === "disabled" && !warnedHybridNoKeyword) {
|
|
8865
|
+
warnedHybridNoKeyword = true;
|
|
8866
|
+
warnLogger3.warn(
|
|
8867
|
+
"hybrid search requested but no keyword channel is available; using semantic-only results."
|
|
8868
|
+
);
|
|
8869
|
+
}
|
|
8450
8870
|
const tRank = performance.now();
|
|
8451
|
-
let results = [...byId.values()].sort(
|
|
8452
|
-
|
|
8453
|
-
|
|
8871
|
+
let results = [...byId.values()].sort((a, b) => {
|
|
8872
|
+
const diff = b.similarity - a.similarity;
|
|
8873
|
+
if (diff !== 0) return diff;
|
|
8874
|
+
return a.id.localeCompare(b.id);
|
|
8875
|
+
});
|
|
8454
8876
|
const mmrLambda = resolveMmr(options.mmr) ?? resolveMmr(
|
|
8455
8877
|
this.retrievalConfig.mmr ? { lambda: this.retrievalConfig.mmr.lambda } : void 0
|
|
8456
8878
|
);
|
|
8457
8879
|
let rankingReason = "cosine similarity";
|
|
8880
|
+
let mmrApplied = false;
|
|
8458
8881
|
if (mmrLambda !== null) {
|
|
8459
|
-
|
|
8460
|
-
|
|
8882
|
+
mmrApplied = results.length > topK;
|
|
8883
|
+
if (mmrApplied) {
|
|
8884
|
+
results = applyMmr(results, topK, mmrLambda);
|
|
8885
|
+
rankingReason = `MMR(lambda=${mmrLambda})`;
|
|
8886
|
+
} else {
|
|
8887
|
+
rankingReason = `MMR(lambda=${mmrLambda})(skipped:insufficient_candidates)`;
|
|
8888
|
+
}
|
|
8889
|
+
}
|
|
8890
|
+
if (options.rerank === true && !this.reranker && !warnedRerankNoProvider) {
|
|
8891
|
+
warnedRerankNoProvider = true;
|
|
8892
|
+
warnLogger3.warn(
|
|
8893
|
+
"rerank is enabled but no reranker provider is configured; using identity order."
|
|
8894
|
+
);
|
|
8461
8895
|
}
|
|
8462
8896
|
if (options.rerank && this.reranker) {
|
|
8463
8897
|
const reranked = await this.reranker.rerank(
|
|
@@ -8492,14 +8926,16 @@ var Wolbarg = class {
|
|
|
8492
8926
|
agentId: options.filter?.agent ?? commonAgent(serialized.map((result) => result.agent)),
|
|
8493
8927
|
tags: commonTags(serialized.map((result) => result.metadata))
|
|
8494
8928
|
};
|
|
8495
|
-
if (
|
|
8496
|
-
|
|
8497
|
-
|
|
8498
|
-
|
|
8929
|
+
if (options.includeGraph === true && this.graph) {
|
|
8930
|
+
const tGraph = performance.now();
|
|
8931
|
+
await Promise.all(
|
|
8932
|
+
serialized.map(async (hit) => {
|
|
8499
8933
|
hit.related = await this.hydrateRelated(hit.id);
|
|
8500
|
-
}
|
|
8501
|
-
|
|
8502
|
-
|
|
8934
|
+
})
|
|
8935
|
+
);
|
|
8936
|
+
trace.mark("databaseReadMs", performance.now() - tGraph);
|
|
8937
|
+
}
|
|
8938
|
+
if (!explain) {
|
|
8503
8939
|
trace.success(telemetryFields);
|
|
8504
8940
|
return serialized;
|
|
8505
8941
|
}
|
|
@@ -8528,7 +8964,7 @@ var Wolbarg = class {
|
|
|
8528
8964
|
semantic: "enabled",
|
|
8529
8965
|
keyword: keywordSignal,
|
|
8530
8966
|
reranker: options.rerank === true && this.reranker ? "enabled" : "disabled",
|
|
8531
|
-
mmr: mmrLambda === null ? "disabled" : "enabled",
|
|
8967
|
+
mmr: mmrLambda === null ? "disabled" : mmrApplied ? "enabled" : "disabled",
|
|
8532
8968
|
recency: "disabled"
|
|
8533
8969
|
},
|
|
8534
8970
|
results: explanations.map((hit) => ({
|
|
@@ -8637,31 +9073,34 @@ var Wolbarg = class {
|
|
|
8637
9073
|
const timestamp = nowIso();
|
|
8638
9074
|
const result = await this.withWriteLock(async () => {
|
|
8639
9075
|
const tWrite = performance.now();
|
|
8640
|
-
const
|
|
8641
|
-
|
|
8642
|
-
|
|
8643
|
-
|
|
8644
|
-
|
|
8645
|
-
|
|
8646
|
-
|
|
8647
|
-
|
|
8648
|
-
|
|
8649
|
-
|
|
8650
|
-
|
|
8651
|
-
|
|
8652
|
-
|
|
9076
|
+
const txResult = await storage.withTransaction(async () => {
|
|
9077
|
+
const summaryRow = await storage.insertMemory({
|
|
9078
|
+
id: summaryId,
|
|
9079
|
+
organization,
|
|
9080
|
+
agent: options.agent.trim(),
|
|
9081
|
+
contentText: summaryText,
|
|
9082
|
+
metadata: {
|
|
9083
|
+
compressed: true,
|
|
9084
|
+
sourceCount: records.length,
|
|
9085
|
+
sourceIds: records.map((r) => r.id)
|
|
9086
|
+
},
|
|
9087
|
+
embedding: vector,
|
|
9088
|
+
createdAt: timestamp,
|
|
9089
|
+
updatedAt: timestamp
|
|
9090
|
+
});
|
|
9091
|
+
const archivedIds = await storage.archiveMemories(
|
|
9092
|
+
records.map((r) => r.id),
|
|
9093
|
+
organization,
|
|
9094
|
+
summaryId,
|
|
9095
|
+
timestamp
|
|
9096
|
+
);
|
|
9097
|
+
return {
|
|
9098
|
+
summary: toMemoryRecord(summaryRow),
|
|
9099
|
+
archivedIds
|
|
9100
|
+
};
|
|
8653
9101
|
});
|
|
8654
|
-
const archivedIds = await storage.archiveMemories(
|
|
8655
|
-
records.map((r) => r.id),
|
|
8656
|
-
organization,
|
|
8657
|
-
summaryId,
|
|
8658
|
-
timestamp
|
|
8659
|
-
);
|
|
8660
9102
|
trace.mark("databaseWriteMs", performance.now() - tWrite);
|
|
8661
|
-
return
|
|
8662
|
-
summary: toMemoryRecord(summaryRow),
|
|
8663
|
-
archivedIds
|
|
8664
|
-
};
|
|
9103
|
+
return txResult;
|
|
8665
9104
|
});
|
|
8666
9105
|
trace.success({
|
|
8667
9106
|
provider: storage.name,
|
|
@@ -8893,8 +9332,23 @@ var Wolbarg = class {
|
|
|
8893
9332
|
await this.requireReady();
|
|
8894
9333
|
const graph = this.requireGraph("getRelated");
|
|
8895
9334
|
assertNonEmptyString(memoryId, "memoryId");
|
|
9335
|
+
let validatedOptions = options;
|
|
9336
|
+
if (options?.depth !== void 0) {
|
|
9337
|
+
if (!Number.isFinite(options.depth)) {
|
|
9338
|
+
throw new ConfigurationError("graph.depth must be a finite number");
|
|
9339
|
+
}
|
|
9340
|
+
if (options.depth < 1) {
|
|
9341
|
+
throw new ConfigurationError("graph.depth must be >= 1");
|
|
9342
|
+
}
|
|
9343
|
+
if (options.depth > 16) {
|
|
9344
|
+
validatedOptions = { ...options, depth: 16 };
|
|
9345
|
+
}
|
|
9346
|
+
}
|
|
8896
9347
|
const tGraph = performance.now();
|
|
8897
|
-
const related = await this.hydrateRelated(
|
|
9348
|
+
const related = await this.hydrateRelated(
|
|
9349
|
+
memoryId.trim(),
|
|
9350
|
+
validatedOptions
|
|
9351
|
+
);
|
|
8898
9352
|
trace.mark("databaseReadMs", performance.now() - tGraph);
|
|
8899
9353
|
trace.success({
|
|
8900
9354
|
provider: graph.name,
|
|
@@ -9063,7 +9517,15 @@ var Wolbarg = class {
|
|
|
9063
9517
|
this.assertGraphCheckpointSupported("checkpoint");
|
|
9064
9518
|
const tCheckpoint = performance.now();
|
|
9065
9519
|
const meta2 = await provider.checkpoint(name, source, options);
|
|
9066
|
-
|
|
9520
|
+
try {
|
|
9521
|
+
await this.snapshotGraphAlongside(meta2.snapshotPath, "checkpoint");
|
|
9522
|
+
} catch (error) {
|
|
9523
|
+
const sidecar = this.graphSnapshotDir(meta2.snapshotPath);
|
|
9524
|
+
if (fs6__default.default.existsSync(sidecar)) {
|
|
9525
|
+
fs6__default.default.rmSync(sidecar, { recursive: true, force: true });
|
|
9526
|
+
}
|
|
9527
|
+
throw error;
|
|
9528
|
+
}
|
|
9067
9529
|
trace.mark("databaseWriteMs", performance.now() - tCheckpoint);
|
|
9068
9530
|
trace.success({
|
|
9069
9531
|
provider: provider.name,
|
|
@@ -9127,8 +9589,15 @@ var Wolbarg = class {
|
|
|
9127
9589
|
try {
|
|
9128
9590
|
await this.requireReady();
|
|
9129
9591
|
const provider = this.requireCheckpointProvider();
|
|
9592
|
+
const existing = await provider.getCheckpoint(name);
|
|
9130
9593
|
const tDelete = performance.now();
|
|
9131
9594
|
const removed = await provider.deleteCheckpoint(name);
|
|
9595
|
+
if (removed && existing) {
|
|
9596
|
+
const sidecar = this.graphSnapshotDir(existing.snapshotPath);
|
|
9597
|
+
if (fs6__default.default.existsSync(sidecar)) {
|
|
9598
|
+
fs6__default.default.rmSync(sidecar, { recursive: true, force: true });
|
|
9599
|
+
}
|
|
9600
|
+
}
|
|
9132
9601
|
trace.mark("databaseWriteMs", performance.now() - tDelete);
|
|
9133
9602
|
trace.success({
|
|
9134
9603
|
provider: provider.name,
|
|
@@ -9190,7 +9659,9 @@ var Wolbarg = class {
|
|
|
9190
9659
|
const result = await this.transfer.exportTo(
|
|
9191
9660
|
exportPath,
|
|
9192
9661
|
source,
|
|
9193
|
-
this.organization ?? void 0
|
|
9662
|
+
this.organization ?? void 0,
|
|
9663
|
+
this.embedding?.model,
|
|
9664
|
+
this.embeddingDimensions ?? void 0
|
|
9194
9665
|
);
|
|
9195
9666
|
await this.snapshotGraphAlongside(result.path, "export");
|
|
9196
9667
|
trace.success({
|
|
@@ -9213,16 +9684,21 @@ var Wolbarg = class {
|
|
|
9213
9684
|
let storageClosed = false;
|
|
9214
9685
|
try {
|
|
9215
9686
|
await this.requireReady();
|
|
9687
|
+
this.assertGraphCheckpointSupported("import");
|
|
9216
9688
|
const target = this.requireMemoryDbPath();
|
|
9217
9689
|
if (this.storage) {
|
|
9218
9690
|
await this.storage.close();
|
|
9219
9691
|
storageClosed = true;
|
|
9220
9692
|
}
|
|
9221
9693
|
const graphClosed = await this.closeGraphForSnapshot();
|
|
9222
|
-
this.assertGraphCheckpointSupported("import");
|
|
9223
9694
|
const result = await this.transfer.importFrom(
|
|
9224
9695
|
exportPath,
|
|
9225
|
-
target
|
|
9696
|
+
target,
|
|
9697
|
+
{
|
|
9698
|
+
organization: this.organization ?? void 0,
|
|
9699
|
+
embeddingModel: this.embedding?.model,
|
|
9700
|
+
embeddingDimensions: this.embeddingDimensions ?? void 0
|
|
9701
|
+
}
|
|
9226
9702
|
);
|
|
9227
9703
|
await this.restoreGraphAlongside(result.path, "import");
|
|
9228
9704
|
if (graphClosed) {
|
|
@@ -9417,18 +9893,26 @@ var Wolbarg = class {
|
|
|
9417
9893
|
const graph = this.requireGraph("getRelated");
|
|
9418
9894
|
const related = await graph.getRelated(memoryId, options);
|
|
9419
9895
|
const { storage, organization } = await this.requireReady();
|
|
9896
|
+
const rows = await Promise.all(
|
|
9897
|
+
related.map((stub) => storage.getMemoryById(stub.id, organization))
|
|
9898
|
+
);
|
|
9420
9899
|
const out = [];
|
|
9421
|
-
for (
|
|
9422
|
-
const
|
|
9423
|
-
|
|
9424
|
-
|
|
9425
|
-
} else {
|
|
9426
|
-
out.push(stub);
|
|
9427
|
-
}
|
|
9900
|
+
for (let i = 0; i < related.length; i += 1) {
|
|
9901
|
+
const stub = related[i];
|
|
9902
|
+
const row = rows[i];
|
|
9903
|
+
out.push(row ? toMemoryRecord(row) : stub);
|
|
9428
9904
|
}
|
|
9429
9905
|
return out;
|
|
9430
9906
|
}
|
|
9431
9907
|
requireMemoryDbPath() {
|
|
9908
|
+
if (this.storage && !(this.storage instanceof SqliteStorageProvider)) {
|
|
9909
|
+
throw new ConfigurationError(
|
|
9910
|
+
"This operation requires a file-backed SQLite database (checkpoints / export-import are not supported for PostgreSQL).",
|
|
9911
|
+
{
|
|
9912
|
+
suggestion: 'Use database: { provider: "sqlite", url: "./memory.db" }'
|
|
9913
|
+
}
|
|
9914
|
+
);
|
|
9915
|
+
}
|
|
9432
9916
|
if (!this.memoryDbPath || this.memoryDbPath === ":memory:") {
|
|
9433
9917
|
throw new ConfigurationError(
|
|
9434
9918
|
"This operation requires a file-backed SQLite memory database (not :memory:).",
|
|
@@ -9657,7 +10141,14 @@ var HttpRerankerProvider = class {
|
|
|
9657
10141
|
}));
|
|
9658
10142
|
}
|
|
9659
10143
|
const parsed = this.options.parseResults(body, documents);
|
|
9660
|
-
|
|
10144
|
+
const hits = parsed.slice(0, topK);
|
|
10145
|
+
if (hits.length === 0) {
|
|
10146
|
+
return documents.slice(0, topK).map((d, i) => ({
|
|
10147
|
+
id: d.id,
|
|
10148
|
+
score: 1 - i / Math.max(documents.length, 1)
|
|
10149
|
+
}));
|
|
10150
|
+
}
|
|
10151
|
+
return hits;
|
|
9661
10152
|
} catch {
|
|
9662
10153
|
return documents.slice(0, topK).map((d, i) => ({
|
|
9663
10154
|
id: d.id,
|
|
@@ -9842,21 +10333,31 @@ function tesseract() {
|
|
|
9842
10333
|
return {
|
|
9843
10334
|
name: "tesseract",
|
|
9844
10335
|
async recognize(image) {
|
|
10336
|
+
let mod;
|
|
9845
10337
|
try {
|
|
9846
|
-
|
|
9847
|
-
|
|
9848
|
-
|
|
9849
|
-
|
|
9850
|
-
|
|
9851
|
-
|
|
9852
|
-
|
|
9853
|
-
|
|
9854
|
-
|
|
9855
|
-
|
|
9856
|
-
|
|
9857
|
-
|
|
9858
|
-
|
|
9859
|
-
|
|
10338
|
+
mod = await import('tesseract.js');
|
|
10339
|
+
} catch (error) {
|
|
10340
|
+
throw new ConfigurationError(
|
|
10341
|
+
'OCR provider "tesseract" requires the optional peer package "tesseract.js". Install it with: npm install tesseract.js',
|
|
10342
|
+
{
|
|
10343
|
+
cause: error instanceof Error ? error : void 0,
|
|
10344
|
+
suggestion: "npm install tesseract.js",
|
|
10345
|
+
operation: "recognize"
|
|
10346
|
+
}
|
|
10347
|
+
);
|
|
10348
|
+
}
|
|
10349
|
+
const createWorker = mod.createWorker ?? mod.default?.createWorker;
|
|
10350
|
+
if (!createWorker) {
|
|
10351
|
+
throw new ConfigurationError(
|
|
10352
|
+
'OCR provider "tesseract" could not find tesseract.js.createWorker. Ensure your tesseract.js version is compatible.'
|
|
10353
|
+
);
|
|
10354
|
+
}
|
|
10355
|
+
const worker = await createWorker("eng");
|
|
10356
|
+
try {
|
|
10357
|
+
const result = await worker.recognize(image);
|
|
10358
|
+
return { text: result.data.text.trim() };
|
|
10359
|
+
} finally {
|
|
10360
|
+
await worker.terminate();
|
|
9860
10361
|
}
|
|
9861
10362
|
}
|
|
9862
10363
|
};
|