wolbarg 0.5.3 → 0.5.5
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/CHANGELOG.md +16 -0
- package/README.md +1 -1
- package/dist/index.cjs +1542 -396
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2011 -140
- package/dist/index.d.ts +2011 -140
- package/dist/index.js +1542 -396
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -15,6 +15,11 @@ var WolbargError = class extends Error {
|
|
|
15
15
|
reason;
|
|
16
16
|
suggestion;
|
|
17
17
|
operation;
|
|
18
|
+
/**
|
|
19
|
+
* @param message - Human-readable error message.
|
|
20
|
+
* @param code - Stable error code string.
|
|
21
|
+
* @param options - Optional cause, reason, suggestion, and operation name.
|
|
22
|
+
*/
|
|
18
23
|
constructor(message, code, options) {
|
|
19
24
|
super(message, options);
|
|
20
25
|
this.name = "WolbargError";
|
|
@@ -26,48 +31,80 @@ var WolbargError = class extends Error {
|
|
|
26
31
|
}
|
|
27
32
|
};
|
|
28
33
|
var InitializationError = class extends WolbargError {
|
|
34
|
+
/**
|
|
35
|
+
* @param message - Description of the initialization failure.
|
|
36
|
+
* @param options - Optional cause and structured hints.
|
|
37
|
+
*/
|
|
29
38
|
constructor(message, options) {
|
|
30
39
|
super(message, "INITIALIZATION_ERROR", options);
|
|
31
40
|
this.name = "InitializationError";
|
|
32
41
|
}
|
|
33
42
|
};
|
|
34
43
|
var ConfigurationError = class extends WolbargError {
|
|
44
|
+
/**
|
|
45
|
+
* @param message - Description of the misconfiguration.
|
|
46
|
+
* @param options - Optional cause, reason, suggestion, and operation name.
|
|
47
|
+
*/
|
|
35
48
|
constructor(message, options) {
|
|
36
49
|
super(message, "CONFIGURATION_ERROR", options);
|
|
37
50
|
this.name = "ConfigurationError";
|
|
38
51
|
}
|
|
39
52
|
};
|
|
40
53
|
var ValidationError = class extends WolbargError {
|
|
54
|
+
/**
|
|
55
|
+
* @param message - Which argument failed and why.
|
|
56
|
+
* @param options - Optional cause and structured hints.
|
|
57
|
+
*/
|
|
41
58
|
constructor(message, options) {
|
|
42
59
|
super(message, "VALIDATION_ERROR", options);
|
|
43
60
|
this.name = "ValidationError";
|
|
44
61
|
}
|
|
45
62
|
};
|
|
46
63
|
var DatabaseError = class extends WolbargError {
|
|
64
|
+
/**
|
|
65
|
+
* @param message - Operation-scoped failure message.
|
|
66
|
+
* @param options - Optional underlying `cause` and hints.
|
|
67
|
+
*/
|
|
47
68
|
constructor(message, options) {
|
|
48
69
|
super(message, "DATABASE_ERROR", options);
|
|
49
70
|
this.name = "DatabaseError";
|
|
50
71
|
}
|
|
51
72
|
};
|
|
52
73
|
var StorageLockedError = class extends WolbargError {
|
|
74
|
+
/**
|
|
75
|
+
* @param message - Lock contention description.
|
|
76
|
+
* @param options - Typically includes suggestion to tune concurrency or use Postgres.
|
|
77
|
+
*/
|
|
53
78
|
constructor(message, options) {
|
|
54
79
|
super(message, "WOLBARG_STORAGE_LOCKED", options);
|
|
55
80
|
this.name = "StorageLockedError";
|
|
56
81
|
}
|
|
57
82
|
};
|
|
58
83
|
var EmbeddingError = class extends WolbargError {
|
|
84
|
+
/**
|
|
85
|
+
* @param message - Embedding failure description.
|
|
86
|
+
* @param options - Optional HTTP cause and provider hints.
|
|
87
|
+
*/
|
|
59
88
|
constructor(message, options) {
|
|
60
89
|
super(message, "EMBEDDING_ERROR", options);
|
|
61
90
|
this.name = "EmbeddingError";
|
|
62
91
|
}
|
|
63
92
|
};
|
|
64
93
|
var CompressionError = class extends WolbargError {
|
|
94
|
+
/**
|
|
95
|
+
* @param message - Compression failure description.
|
|
96
|
+
* @param options - Optional LLM cause chain.
|
|
97
|
+
*/
|
|
65
98
|
constructor(message, options) {
|
|
66
99
|
super(message, "COMPRESSION_ERROR", options);
|
|
67
100
|
this.name = "CompressionError";
|
|
68
101
|
}
|
|
69
102
|
};
|
|
70
103
|
var MemoryNotFoundError = class extends WolbargError {
|
|
104
|
+
/**
|
|
105
|
+
* @param message - Which memory was not found.
|
|
106
|
+
* @param options - Optional operation context.
|
|
107
|
+
*/
|
|
71
108
|
constructor(message, options) {
|
|
72
109
|
super(message, "MEMORY_NOT_FOUND", options);
|
|
73
110
|
this.name = "MemoryNotFoundError";
|
|
@@ -75,6 +112,11 @@ var MemoryNotFoundError = class extends WolbargError {
|
|
|
75
112
|
};
|
|
76
113
|
var ProviderNotConfiguredError = class extends ConfigurationError {
|
|
77
114
|
provider;
|
|
115
|
+
/**
|
|
116
|
+
* @param provider - Provider name (e.g. `"reranker"`, `"graph"`).
|
|
117
|
+
* @param method - Facade method that requires the provider.
|
|
118
|
+
* @param hint - Install or config instruction shown to the developer.
|
|
119
|
+
*/
|
|
78
120
|
constructor(provider, method, hint) {
|
|
79
121
|
super(`${method} requires ${provider} \u2014 ${hint}`, {
|
|
80
122
|
operation: method,
|
|
@@ -86,6 +128,10 @@ var ProviderNotConfiguredError = class extends ConfigurationError {
|
|
|
86
128
|
}
|
|
87
129
|
};
|
|
88
130
|
var GraphCheckpointNotSupportedError = class extends WolbargError {
|
|
131
|
+
/**
|
|
132
|
+
* @param backend - Graph backend name (e.g. `"neo4j"`).
|
|
133
|
+
* @param operation - Requested operation (e.g. `"checkpoint"`).
|
|
134
|
+
*/
|
|
89
135
|
constructor(backend, operation) {
|
|
90
136
|
super(
|
|
91
137
|
`graph checkpoint not supported for network-backed graph providers (${backend})`,
|
|
@@ -158,9 +204,11 @@ Rules:
|
|
|
158
204
|
var LlmCompressionProvider = class {
|
|
159
205
|
name = "llm";
|
|
160
206
|
llm;
|
|
207
|
+
/** @param llm - Configured LLM used for summarization. */
|
|
161
208
|
constructor(llm) {
|
|
162
209
|
this.llm = llm;
|
|
163
210
|
}
|
|
211
|
+
/** @inheritdoc */
|
|
164
212
|
async compress(memories) {
|
|
165
213
|
return compressMemories(this.llm, memories);
|
|
166
214
|
}
|
|
@@ -269,6 +317,7 @@ function slidingWindow(text, chunkSize, overlap) {
|
|
|
269
317
|
}
|
|
270
318
|
var FixedChunkingStrategy = class {
|
|
271
319
|
name = "fixed";
|
|
320
|
+
/** @inheritdoc */
|
|
272
321
|
chunk(text, options) {
|
|
273
322
|
const { chunkSize, overlap } = clampOptions(options);
|
|
274
323
|
return slidingWindow(text, chunkSize, overlap);
|
|
@@ -276,6 +325,7 @@ var FixedChunkingStrategy = class {
|
|
|
276
325
|
};
|
|
277
326
|
var SentenceChunkingStrategy = class {
|
|
278
327
|
name = "sentence";
|
|
328
|
+
/** @inheritdoc */
|
|
279
329
|
chunk(text, options) {
|
|
280
330
|
const { chunkSize, overlap } = clampOptions(options);
|
|
281
331
|
const sentences = text.split(/(?<=[.!?])\s+/).map((s) => s.trim()).filter(Boolean);
|
|
@@ -284,6 +334,7 @@ var SentenceChunkingStrategy = class {
|
|
|
284
334
|
};
|
|
285
335
|
var ParagraphChunkingStrategy = class {
|
|
286
336
|
name = "paragraph";
|
|
337
|
+
/** @inheritdoc */
|
|
287
338
|
chunk(text, options) {
|
|
288
339
|
const { chunkSize, overlap } = clampOptions(options);
|
|
289
340
|
const paragraphs = text.split(/\n\s*\n/).map((p) => p.trim()).filter(Boolean);
|
|
@@ -292,6 +343,7 @@ var ParagraphChunkingStrategy = class {
|
|
|
292
343
|
};
|
|
293
344
|
var MarkdownChunkingStrategy = class {
|
|
294
345
|
name = "markdown";
|
|
346
|
+
/** @inheritdoc */
|
|
295
347
|
chunk(text, options) {
|
|
296
348
|
const { chunkSize, overlap } = clampOptions(options);
|
|
297
349
|
const sections = text.split(/(?=^#{1,6}\s)/m).map((s) => s.trim()).filter(Boolean);
|
|
@@ -303,6 +355,7 @@ var MarkdownChunkingStrategy = class {
|
|
|
303
355
|
};
|
|
304
356
|
var HeadingChunkingStrategy = class {
|
|
305
357
|
name = "heading";
|
|
358
|
+
/** @inheritdoc */
|
|
306
359
|
chunk(text, options) {
|
|
307
360
|
return new MarkdownChunkingStrategy().chunk(text, options);
|
|
308
361
|
}
|
|
@@ -384,6 +437,12 @@ function distanceToSimilarity(distance) {
|
|
|
384
437
|
}
|
|
385
438
|
var AsyncMutex = class {
|
|
386
439
|
chain = Promise.resolve();
|
|
440
|
+
/**
|
|
441
|
+
* Run `fn` exclusively — concurrent callers queue on the same mutex.
|
|
442
|
+
*
|
|
443
|
+
* @param fn - Async or sync work to serialize.
|
|
444
|
+
* @returns The value returned by `fn`.
|
|
445
|
+
*/
|
|
387
446
|
async runExclusive(fn) {
|
|
388
447
|
let release;
|
|
389
448
|
const next = new Promise((resolve) => {
|
|
@@ -407,16 +466,32 @@ function joinUrl(baseUrl, path8) {
|
|
|
407
466
|
|
|
408
467
|
// src/embedding/index.ts
|
|
409
468
|
var OpenAICompatibleEmbeddingProvider = class {
|
|
469
|
+
/** Model name sent in the request body. */
|
|
410
470
|
model;
|
|
411
471
|
baseUrl;
|
|
412
472
|
apiKey;
|
|
413
473
|
timeoutMs;
|
|
474
|
+
/**
|
|
475
|
+
* @param config - OpenAI-compatible embedding endpoint settings.
|
|
476
|
+
* @param config.baseUrl - API root, e.g. `https://api.openai.com/v1`.
|
|
477
|
+
* @param config.apiKey - Bearer token. Use any non-empty string for local
|
|
478
|
+
* servers that ignore auth.
|
|
479
|
+
* @param config.model - Embedding model id (e.g. `"text-embedding-3-small"`).
|
|
480
|
+
* @param config.timeoutMs - Abort after this many ms. Defaults to `30_000`.
|
|
481
|
+
*/
|
|
414
482
|
constructor(config) {
|
|
415
483
|
this.baseUrl = config.baseUrl;
|
|
416
484
|
this.apiKey = config.apiKey;
|
|
417
485
|
this.model = config.model;
|
|
418
486
|
this.timeoutMs = config.timeoutMs ?? 3e4;
|
|
419
487
|
}
|
|
488
|
+
/**
|
|
489
|
+
* Embed a single string.
|
|
490
|
+
*
|
|
491
|
+
* @param text - Input text.
|
|
492
|
+
* @returns Embedding vector.
|
|
493
|
+
* @throws {EmbeddingError} On HTTP errors, timeouts, or empty vectors.
|
|
494
|
+
*/
|
|
420
495
|
async embed(text) {
|
|
421
496
|
const response = await this.request(text);
|
|
422
497
|
const vector = response.data?.[0]?.embedding;
|
|
@@ -425,6 +500,13 @@ var OpenAICompatibleEmbeddingProvider = class {
|
|
|
425
500
|
}
|
|
426
501
|
return Float32Array.from(vector);
|
|
427
502
|
}
|
|
503
|
+
/**
|
|
504
|
+
* Embed multiple strings in one API call when the server supports batch input.
|
|
505
|
+
* Falls back to sequential {@link embed} if the response length mismatches.
|
|
506
|
+
*
|
|
507
|
+
* @param texts - Inputs to embed.
|
|
508
|
+
* @returns One vector per input, same order.
|
|
509
|
+
*/
|
|
428
510
|
async embedBatch(texts) {
|
|
429
511
|
if (texts.length === 0) {
|
|
430
512
|
return [];
|
|
@@ -451,6 +533,12 @@ var OpenAICompatibleEmbeddingProvider = class {
|
|
|
451
533
|
return Float32Array.from(item.embedding);
|
|
452
534
|
});
|
|
453
535
|
}
|
|
536
|
+
/**
|
|
537
|
+
* Probe the endpoint and report vector dimensionality.
|
|
538
|
+
*
|
|
539
|
+
* @returns `{ dimensions }` from a fixed health-check string.
|
|
540
|
+
* @throws {EmbeddingError} When the probe fails.
|
|
541
|
+
*/
|
|
454
542
|
async validate() {
|
|
455
543
|
try {
|
|
456
544
|
const embedding = await this.embed("Wolbarg health check");
|
|
@@ -465,6 +553,12 @@ var OpenAICompatibleEmbeddingProvider = class {
|
|
|
465
553
|
);
|
|
466
554
|
}
|
|
467
555
|
}
|
|
556
|
+
/**
|
|
557
|
+
* Low-level HTTP request to `/embeddings`.
|
|
558
|
+
*
|
|
559
|
+
* @param input - Single string or batch of strings.
|
|
560
|
+
* @returns Parsed OpenAI-style JSON body.
|
|
561
|
+
*/
|
|
468
562
|
async request(input) {
|
|
469
563
|
const url = joinUrl(this.baseUrl, "/embeddings");
|
|
470
564
|
const controller = new AbortController();
|
|
@@ -512,6 +606,7 @@ var OpenAICompatibleEmbeddingProvider = class {
|
|
|
512
606
|
clearTimeout(timer);
|
|
513
607
|
}
|
|
514
608
|
}
|
|
609
|
+
/** @param error - Unknown thrown value. @returns Human-readable message. */
|
|
515
610
|
describe(error) {
|
|
516
611
|
if (error instanceof Error) {
|
|
517
612
|
return error.message;
|
|
@@ -662,6 +757,45 @@ function withEmbeddingCache(provider, store, config) {
|
|
|
662
757
|
};
|
|
663
758
|
}
|
|
664
759
|
|
|
760
|
+
// src/utils/vector.ts
|
|
761
|
+
function cosineDistance(a, b) {
|
|
762
|
+
if (a.length !== b.length) {
|
|
763
|
+
throw new Error(
|
|
764
|
+
`cosineDistance dimension mismatch: a.length=${a.length}, b.length=${b.length}`
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
const len = a.length;
|
|
768
|
+
let dot = 0;
|
|
769
|
+
let normA = 0;
|
|
770
|
+
let normB = 0;
|
|
771
|
+
for (let i = 0; i < len; i += 1) {
|
|
772
|
+
const av = a[i] ?? 0;
|
|
773
|
+
const bv = b[i] ?? 0;
|
|
774
|
+
dot += av * bv;
|
|
775
|
+
normA += av * av;
|
|
776
|
+
normB += bv * bv;
|
|
777
|
+
}
|
|
778
|
+
if (normA === 0 || normB === 0) {
|
|
779
|
+
return 1;
|
|
780
|
+
}
|
|
781
|
+
const similarity = dot / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
782
|
+
return 1 - similarity;
|
|
783
|
+
}
|
|
784
|
+
function bufferToEmbedding(data) {
|
|
785
|
+
const byteOffset = data.byteOffset ?? 0;
|
|
786
|
+
const byteLength = data.byteLength;
|
|
787
|
+
if (byteOffset % Float32Array.BYTES_PER_ELEMENT === 0) {
|
|
788
|
+
return new Float32Array(
|
|
789
|
+
data.buffer,
|
|
790
|
+
byteOffset,
|
|
791
|
+
byteLength / Float32Array.BYTES_PER_ELEMENT
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
const copy = new Uint8Array(byteLength);
|
|
795
|
+
copy.set(data);
|
|
796
|
+
return new Float32Array(copy.buffer);
|
|
797
|
+
}
|
|
798
|
+
|
|
665
799
|
// src/embedding/cache-store.ts
|
|
666
800
|
var TOUCH_FLUSH_THRESHOLD = 64;
|
|
667
801
|
var L1_DEFAULT_MAX = 5e4;
|
|
@@ -729,6 +863,10 @@ var SqliteEmbeddingCacheStore = class {
|
|
|
729
863
|
pendingSets = /* @__PURE__ */ new Map();
|
|
730
864
|
persistScheduled = false;
|
|
731
865
|
stmts = null;
|
|
866
|
+
/**
|
|
867
|
+
* @param dbOrGetter - SQLite `DatabaseSync` or lazy getter (allows late bind).
|
|
868
|
+
* @param options.ttlMs - Optional TTL for cache entries on read.
|
|
869
|
+
*/
|
|
732
870
|
constructor(dbOrGetter, options) {
|
|
733
871
|
this.ttlMs = options?.ttlMs ?? null;
|
|
734
872
|
this.getDb = typeof dbOrGetter === "function" ? dbOrGetter : () => dbOrGetter;
|
|
@@ -746,7 +884,9 @@ var SqliteEmbeddingCacheStore = class {
|
|
|
746
884
|
}
|
|
747
885
|
this.stmts = {
|
|
748
886
|
get: db.prepare(
|
|
749
|
-
`SELECT vector, last_used_at, created_at
|
|
887
|
+
`SELECT model, vector, last_used_at, created_at
|
|
888
|
+
FROM embedding_cache
|
|
889
|
+
WHERE cache_key = ?`
|
|
750
890
|
),
|
|
751
891
|
delete: db.prepare(`DELETE FROM embedding_cache WHERE cache_key = ?`),
|
|
752
892
|
set: db.prepare(
|
|
@@ -781,7 +921,29 @@ var SqliteEmbeddingCacheStore = class {
|
|
|
781
921
|
this.l1.set(cacheKey, pending.model, pending.vector);
|
|
782
922
|
return pending.vector;
|
|
783
923
|
}
|
|
784
|
-
|
|
924
|
+
const db = this.requireDb();
|
|
925
|
+
if (!db) return null;
|
|
926
|
+
try {
|
|
927
|
+
const stmts = this.ensureStatements(db);
|
|
928
|
+
const row = stmts.get.get(cacheKey);
|
|
929
|
+
if (!row) return null;
|
|
930
|
+
if (this.ttlMs !== null) {
|
|
931
|
+
const createdMs = row.created_at ? Date.parse(row.created_at) : 0;
|
|
932
|
+
if (createdMs && Date.now() - createdMs > this.ttlMs) {
|
|
933
|
+
stmts.delete.run(cacheKey);
|
|
934
|
+
return null;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
const vector = bufferToEmbedding(row.vector);
|
|
938
|
+
this.l1.set(cacheKey, row.model, vector);
|
|
939
|
+
this.pendingTouches.add(cacheKey);
|
|
940
|
+
if (this.pendingTouches.size >= TOUCH_FLUSH_THRESHOLD) {
|
|
941
|
+
this.schedulePersist();
|
|
942
|
+
}
|
|
943
|
+
return vector;
|
|
944
|
+
} catch {
|
|
945
|
+
return null;
|
|
946
|
+
}
|
|
785
947
|
}
|
|
786
948
|
async set(cacheKey, model, vector) {
|
|
787
949
|
this.l1.set(cacheKey, model, vector);
|
|
@@ -833,6 +995,16 @@ var SqliteEmbeddingCacheStore = class {
|
|
|
833
995
|
this.pendingSets.clear();
|
|
834
996
|
const touches = [...this.pendingTouches];
|
|
835
997
|
this.pendingTouches.clear();
|
|
998
|
+
const requeue = () => {
|
|
999
|
+
for (const [key, value] of sets) {
|
|
1000
|
+
this.pendingSets.set(key, value);
|
|
1001
|
+
}
|
|
1002
|
+
for (const key of touches) {
|
|
1003
|
+
this.pendingTouches.add(key);
|
|
1004
|
+
}
|
|
1005
|
+
this.stmts = null;
|
|
1006
|
+
this.schedulePersist();
|
|
1007
|
+
};
|
|
836
1008
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
837
1009
|
try {
|
|
838
1010
|
const stmts = this.ensureStatements(db);
|
|
@@ -867,10 +1039,10 @@ var SqliteEmbeddingCacheStore = class {
|
|
|
867
1039
|
db.exec("ROLLBACK");
|
|
868
1040
|
} catch {
|
|
869
1041
|
}
|
|
870
|
-
|
|
1042
|
+
requeue();
|
|
871
1043
|
}
|
|
872
1044
|
} catch {
|
|
873
|
-
|
|
1045
|
+
requeue();
|
|
874
1046
|
}
|
|
875
1047
|
}
|
|
876
1048
|
};
|
|
@@ -971,12 +1143,23 @@ var PostgresEmbeddingCacheStore = class {
|
|
|
971
1143
|
|
|
972
1144
|
// src/llm/index.ts
|
|
973
1145
|
var OpenAICompatibleLlmProvider = class {
|
|
1146
|
+
/** Model name sent in the request body and exposed on the provider. */
|
|
974
1147
|
model;
|
|
975
1148
|
baseUrl;
|
|
976
1149
|
apiKey;
|
|
977
1150
|
temperature;
|
|
978
1151
|
maxTokens;
|
|
979
1152
|
timeoutMs;
|
|
1153
|
+
/**
|
|
1154
|
+
* @param config - OpenAI-compatible endpoint settings.
|
|
1155
|
+
* @param config.baseUrl - API root, e.g. `https://api.openai.com/v1` (no trailing `/chat/completions`).
|
|
1156
|
+
* @param config.apiKey - Bearer token (`Authorization: Bearer …`). Use any non-empty
|
|
1157
|
+
* string for local servers that ignore auth (e.g. `"ollama"`).
|
|
1158
|
+
* @param config.model - Chat model id (e.g. `"gpt-4o-mini"`, `"llama3.2"`).
|
|
1159
|
+
* @param config.temperature - Sampling temperature. Defaults to `0.2`.
|
|
1160
|
+
* @param config.maxTokens - Max completion tokens. Defaults to `4096`.
|
|
1161
|
+
* @param config.timeoutMs - Abort after this many ms. Defaults to `60_000`.
|
|
1162
|
+
*/
|
|
980
1163
|
constructor(config) {
|
|
981
1164
|
this.baseUrl = config.baseUrl;
|
|
982
1165
|
this.apiKey = config.apiKey;
|
|
@@ -985,6 +1168,13 @@ var OpenAICompatibleLlmProvider = class {
|
|
|
985
1168
|
this.maxTokens = config.maxTokens ?? 4096;
|
|
986
1169
|
this.timeoutMs = config.timeoutMs ?? 6e4;
|
|
987
1170
|
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Call `/chat/completions` and return the first choice’s message content.
|
|
1173
|
+
*
|
|
1174
|
+
* @param messages - Chat turns in OpenAI message format.
|
|
1175
|
+
* @returns Trimmed assistant text.
|
|
1176
|
+
* @throws {CompressionError} On HTTP errors, timeouts, or empty content.
|
|
1177
|
+
*/
|
|
988
1178
|
async complete(messages) {
|
|
989
1179
|
const response = await this.request(messages);
|
|
990
1180
|
const content = response.choices?.[0]?.message?.content;
|
|
@@ -993,6 +1183,11 @@ var OpenAICompatibleLlmProvider = class {
|
|
|
993
1183
|
}
|
|
994
1184
|
return content.trim();
|
|
995
1185
|
}
|
|
1186
|
+
/**
|
|
1187
|
+
* Probe the endpoint with a fixed `"ok"` prompt.
|
|
1188
|
+
*
|
|
1189
|
+
* @throws {CompressionError} When validation fails.
|
|
1190
|
+
*/
|
|
996
1191
|
async validate() {
|
|
997
1192
|
try {
|
|
998
1193
|
await this.complete([
|
|
@@ -1014,6 +1209,12 @@ var OpenAICompatibleLlmProvider = class {
|
|
|
1014
1209
|
);
|
|
1015
1210
|
}
|
|
1016
1211
|
}
|
|
1212
|
+
/**
|
|
1213
|
+
* Low-level HTTP request to `/chat/completions`.
|
|
1214
|
+
*
|
|
1215
|
+
* @param messages - Chat turns to send.
|
|
1216
|
+
* @returns Parsed OpenAI-style JSON body.
|
|
1217
|
+
*/
|
|
1017
1218
|
async request(messages) {
|
|
1018
1219
|
const url = joinUrl(this.baseUrl, "/chat/completions");
|
|
1019
1220
|
const controller = new AbortController();
|
|
@@ -1062,6 +1263,7 @@ var OpenAICompatibleLlmProvider = class {
|
|
|
1062
1263
|
clearTimeout(timer);
|
|
1063
1264
|
}
|
|
1064
1265
|
}
|
|
1266
|
+
/** @param error - Unknown thrown value. @returns Human-readable message. */
|
|
1065
1267
|
describe(error) {
|
|
1066
1268
|
if (error instanceof Error) {
|
|
1067
1269
|
return error.message;
|
|
@@ -1179,12 +1381,27 @@ var DocxParser = class {
|
|
|
1179
1381
|
name = "docx";
|
|
1180
1382
|
extensions = [".docx"];
|
|
1181
1383
|
async parse(input) {
|
|
1384
|
+
let mod;
|
|
1385
|
+
try {
|
|
1386
|
+
mod = await import('mammoth');
|
|
1387
|
+
} catch (error) {
|
|
1388
|
+
throw new ConfigurationError(
|
|
1389
|
+
'DOCX ingest requires the optional peer package "mammoth". Install it with: npm install mammoth',
|
|
1390
|
+
{
|
|
1391
|
+
cause: error instanceof Error ? error : void 0,
|
|
1392
|
+
suggestion: "npm install mammoth",
|
|
1393
|
+
operation: "parse"
|
|
1394
|
+
}
|
|
1395
|
+
);
|
|
1396
|
+
}
|
|
1397
|
+
const extractRawText = mod.extractRawText ?? mod.default?.extractRawText;
|
|
1398
|
+
if (!extractRawText) {
|
|
1399
|
+
throw new ConfigurationError(
|
|
1400
|
+
"DOCX ingest could not find mammoth.extractRawText. Ensure your mammoth version is compatible.",
|
|
1401
|
+
{ operation: "parse" }
|
|
1402
|
+
);
|
|
1403
|
+
}
|
|
1182
1404
|
try {
|
|
1183
|
-
const mod = await import('mammoth');
|
|
1184
|
-
const extractRawText = mod.extractRawText ?? mod.default?.extractRawText;
|
|
1185
|
-
if (!extractRawText) {
|
|
1186
|
-
throw new Error("mammoth.extractRawText unavailable");
|
|
1187
|
-
}
|
|
1188
1405
|
const result = await extractRawText({ buffer: input.buffer });
|
|
1189
1406
|
return {
|
|
1190
1407
|
text: result.value ?? "",
|
|
@@ -1192,9 +1409,14 @@ var DocxParser = class {
|
|
|
1192
1409
|
filename: input.filename,
|
|
1193
1410
|
isImage: false
|
|
1194
1411
|
};
|
|
1195
|
-
} catch {
|
|
1412
|
+
} catch (error) {
|
|
1196
1413
|
throw new ConfigurationError(
|
|
1197
|
-
|
|
1414
|
+
"DOCX ingest failed to parse the document (it may be corrupt or an unsupported .docx).",
|
|
1415
|
+
{
|
|
1416
|
+
cause: error instanceof Error ? error : void 0,
|
|
1417
|
+
suggestion: "Try a different DOCX file or ensure it is not password-protected.",
|
|
1418
|
+
operation: "parse"
|
|
1419
|
+
}
|
|
1198
1420
|
);
|
|
1199
1421
|
}
|
|
1200
1422
|
}
|
|
@@ -1341,6 +1563,12 @@ function compileComparison(field, op) {
|
|
|
1341
1563
|
const extract = `json_extract(metadata_json, '${path8}')`;
|
|
1342
1564
|
if ("eq" in op) {
|
|
1343
1565
|
const value = op.eq;
|
|
1566
|
+
if (value === null) {
|
|
1567
|
+
return {
|
|
1568
|
+
expression: `json_type(metadata_json, '${path8}') = 'null'`,
|
|
1569
|
+
params: []
|
|
1570
|
+
};
|
|
1571
|
+
}
|
|
1344
1572
|
if (value !== null && typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
|
|
1345
1573
|
return null;
|
|
1346
1574
|
}
|
|
@@ -1421,6 +1649,79 @@ function compileMetadataFilterToSql(filter) {
|
|
|
1421
1649
|
return compileComparison(filter.field, filter.op);
|
|
1422
1650
|
}
|
|
1423
1651
|
|
|
1652
|
+
// src/telemetry/logger.ts
|
|
1653
|
+
var LEVEL_ORDER = {
|
|
1654
|
+
off: 100,
|
|
1655
|
+
error: 50,
|
|
1656
|
+
warn: 40,
|
|
1657
|
+
info: 30,
|
|
1658
|
+
debug: 20,
|
|
1659
|
+
trace: 10
|
|
1660
|
+
};
|
|
1661
|
+
var WolbargLogger = class {
|
|
1662
|
+
/** @param level - Minimum level to print (default `"info"`). */
|
|
1663
|
+
constructor(level = "info") {
|
|
1664
|
+
this.level = level;
|
|
1665
|
+
}
|
|
1666
|
+
level;
|
|
1667
|
+
/** Change the minimum log level at runtime. */
|
|
1668
|
+
setLevel(level) {
|
|
1669
|
+
this.level = level;
|
|
1670
|
+
}
|
|
1671
|
+
/** @returns Current minimum log level. */
|
|
1672
|
+
getLevel() {
|
|
1673
|
+
return this.level;
|
|
1674
|
+
}
|
|
1675
|
+
/** Log at error level. */
|
|
1676
|
+
error(message, extra) {
|
|
1677
|
+
this.write("error", message, extra);
|
|
1678
|
+
}
|
|
1679
|
+
/** Log at warn level.
|
|
1680
|
+
* @param message - Primary log line.
|
|
1681
|
+
* @param extra - Optional structured payload.
|
|
1682
|
+
*/
|
|
1683
|
+
warn(message, extra) {
|
|
1684
|
+
this.write("warn", message, extra);
|
|
1685
|
+
}
|
|
1686
|
+
/** Log at info level.
|
|
1687
|
+
* @param message - Primary log line.
|
|
1688
|
+
* @param extra - Optional structured payload.
|
|
1689
|
+
*/
|
|
1690
|
+
info(message, extra) {
|
|
1691
|
+
this.write("info", message, extra);
|
|
1692
|
+
}
|
|
1693
|
+
/** Log at debug level.
|
|
1694
|
+
* @param message - Primary log line.
|
|
1695
|
+
* @param extra - Optional structured payload.
|
|
1696
|
+
*/
|
|
1697
|
+
debug(message, extra) {
|
|
1698
|
+
this.write("debug", message, extra);
|
|
1699
|
+
}
|
|
1700
|
+
/** Log at trace level.
|
|
1701
|
+
* @param message - Primary log line.
|
|
1702
|
+
* @param extra - Optional structured payload.
|
|
1703
|
+
*/
|
|
1704
|
+
trace(message, extra) {
|
|
1705
|
+
this.write("trace", message, extra);
|
|
1706
|
+
}
|
|
1707
|
+
write(level, message, extra) {
|
|
1708
|
+
if (LEVEL_ORDER[level] < LEVEL_ORDER[this.level]) {
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
if (this.level === "off") {
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1714
|
+
const line = `[wolbarg:${level}] ${message}`;
|
|
1715
|
+
if (level === "error") {
|
|
1716
|
+
console.error(line, extra ?? "");
|
|
1717
|
+
} else if (level === "warn") {
|
|
1718
|
+
console.warn(line, extra ?? "");
|
|
1719
|
+
} else {
|
|
1720
|
+
console.log(line, extra ?? "");
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
};
|
|
1724
|
+
|
|
1424
1725
|
// src/schema/index.ts
|
|
1425
1726
|
var SCHEMA_VERSION = 4;
|
|
1426
1727
|
var META_KEYS = {
|
|
@@ -1735,40 +2036,6 @@ var SQL = {
|
|
|
1735
2036
|
`
|
|
1736
2037
|
};
|
|
1737
2038
|
|
|
1738
|
-
// src/utils/vector.ts
|
|
1739
|
-
function cosineDistance(a, b) {
|
|
1740
|
-
const len = Math.min(a.length, b.length);
|
|
1741
|
-
let dot = 0;
|
|
1742
|
-
let normA = 0;
|
|
1743
|
-
let normB = 0;
|
|
1744
|
-
for (let i = 0; i < len; i += 1) {
|
|
1745
|
-
const av = a[i] ?? 0;
|
|
1746
|
-
const bv = b[i] ?? 0;
|
|
1747
|
-
dot += av * bv;
|
|
1748
|
-
normA += av * av;
|
|
1749
|
-
normB += bv * bv;
|
|
1750
|
-
}
|
|
1751
|
-
if (normA === 0 || normB === 0) {
|
|
1752
|
-
return 1;
|
|
1753
|
-
}
|
|
1754
|
-
const similarity = dot / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
1755
|
-
return 1 - similarity;
|
|
1756
|
-
}
|
|
1757
|
-
function bufferToEmbedding(data) {
|
|
1758
|
-
const byteOffset = data.byteOffset ?? 0;
|
|
1759
|
-
const byteLength = data.byteLength;
|
|
1760
|
-
if (byteOffset % Float32Array.BYTES_PER_ELEMENT === 0) {
|
|
1761
|
-
return new Float32Array(
|
|
1762
|
-
data.buffer,
|
|
1763
|
-
byteOffset,
|
|
1764
|
-
byteLength / Float32Array.BYTES_PER_ELEMENT
|
|
1765
|
-
);
|
|
1766
|
-
}
|
|
1767
|
-
const copy = new Uint8Array(byteLength);
|
|
1768
|
-
copy.set(data);
|
|
1769
|
-
return new Float32Array(copy.buffer);
|
|
1770
|
-
}
|
|
1771
|
-
|
|
1772
2039
|
// src/utils/vector-index.ts
|
|
1773
2040
|
var InMemoryVectorIndex = class {
|
|
1774
2041
|
dims;
|
|
@@ -1776,18 +2043,29 @@ var InMemoryVectorIndex = class {
|
|
|
1776
2043
|
data;
|
|
1777
2044
|
count = 0;
|
|
1778
2045
|
rowidToSlot = /* @__PURE__ */ new Map();
|
|
2046
|
+
/**
|
|
2047
|
+
* @param dimensions - Embedding vector length.
|
|
2048
|
+
* @param initialCapacity - Initial slot capacity (default `256`).
|
|
2049
|
+
*/
|
|
1779
2050
|
constructor(dimensions, initialCapacity = 256) {
|
|
1780
2051
|
this.dims = dimensions;
|
|
1781
2052
|
this.data = new Float32Array(initialCapacity * dimensions);
|
|
1782
2053
|
}
|
|
2054
|
+
/** Number of indexed vectors. */
|
|
1783
2055
|
get size() {
|
|
1784
2056
|
return this.count;
|
|
1785
2057
|
}
|
|
2058
|
+
/** Remove all vectors from the index. */
|
|
1786
2059
|
clear() {
|
|
1787
2060
|
this.rowids.length = 0;
|
|
1788
2061
|
this.rowidToSlot.clear();
|
|
1789
2062
|
this.count = 0;
|
|
1790
2063
|
}
|
|
2064
|
+
/**
|
|
2065
|
+
* Insert or replace a vector for `rowid` (L2-normalized internally).
|
|
2066
|
+
* @param rowid - Memory table row identifier.
|
|
2067
|
+
* @param embedding - Raw embedding vector.
|
|
2068
|
+
*/
|
|
1791
2069
|
upsert(rowid, embedding) {
|
|
1792
2070
|
const existing = this.rowidToSlot.get(rowid);
|
|
1793
2071
|
const slot = existing !== void 0 ? existing : (() => {
|
|
@@ -1818,6 +2096,10 @@ var InMemoryVectorIndex = class {
|
|
|
1818
2096
|
}
|
|
1819
2097
|
}
|
|
1820
2098
|
}
|
|
2099
|
+
/**
|
|
2100
|
+
* Remove a vector by `rowid`.
|
|
2101
|
+
* @param rowid - Memory table row identifier.
|
|
2102
|
+
*/
|
|
1821
2103
|
remove(rowid) {
|
|
1822
2104
|
const slot = this.rowidToSlot.get(rowid);
|
|
1823
2105
|
if (slot === void 0) {
|
|
@@ -1837,7 +2119,12 @@ var InMemoryVectorIndex = class {
|
|
|
1837
2119
|
this.rowidToSlot.delete(rowid);
|
|
1838
2120
|
this.count = last;
|
|
1839
2121
|
}
|
|
1840
|
-
/**
|
|
2122
|
+
/**
|
|
2123
|
+
* Top-k by cosine distance (1 - dot) assuming query is L2-normalized.
|
|
2124
|
+
*
|
|
2125
|
+
* @param queryNormalized - L2-normalized query vector (use {@link normalizeEmbedding}).
|
|
2126
|
+
* @param topK - Maximum hits to return.
|
|
2127
|
+
*/
|
|
1841
2128
|
search(queryNormalized, topK) {
|
|
1842
2129
|
const n = this.count;
|
|
1843
2130
|
if (n === 0 || topK <= 0) {
|
|
@@ -1861,24 +2148,33 @@ var InMemoryVectorIndex = class {
|
|
|
1861
2148
|
dot += queryNormalized[d] * data[base + d];
|
|
1862
2149
|
}
|
|
1863
2150
|
const distance = 1 - dot;
|
|
2151
|
+
const rowid = rowids[i];
|
|
1864
2152
|
if (heapSize < k) {
|
|
1865
2153
|
heapDist[heapSize] = distance;
|
|
1866
|
-
heapRow[heapSize] =
|
|
2154
|
+
heapRow[heapSize] = rowid;
|
|
1867
2155
|
heapSize += 1;
|
|
1868
2156
|
if (heapSize === k) {
|
|
1869
2157
|
buildMaxHeap(heapDist, heapRow, k);
|
|
1870
2158
|
}
|
|
1871
|
-
} else
|
|
1872
|
-
heapDist[0]
|
|
1873
|
-
|
|
1874
|
-
|
|
2159
|
+
} else {
|
|
2160
|
+
const worstDist = heapDist[0];
|
|
2161
|
+
const worstRow = heapRow[0];
|
|
2162
|
+
if (distance < worstDist || distance === worstDist && rowid < worstRow) {
|
|
2163
|
+
heapDist[0] = distance;
|
|
2164
|
+
heapRow[0] = rowid;
|
|
2165
|
+
siftDown(heapDist, heapRow, 0, k);
|
|
2166
|
+
}
|
|
1875
2167
|
}
|
|
1876
2168
|
}
|
|
1877
2169
|
const hits = new Array(heapSize);
|
|
1878
2170
|
for (let i = 0; i < heapSize; i += 1) {
|
|
1879
2171
|
hits[i] = { memoryRowid: heapRow[i], distance: heapDist[i] };
|
|
1880
2172
|
}
|
|
1881
|
-
hits.sort((a, b) =>
|
|
2173
|
+
hits.sort((a, b) => {
|
|
2174
|
+
const diff = a.distance - b.distance;
|
|
2175
|
+
if (diff !== 0) return diff;
|
|
2176
|
+
return a.memoryRowid - b.memoryRowid;
|
|
2177
|
+
});
|
|
1882
2178
|
return hits;
|
|
1883
2179
|
}
|
|
1884
2180
|
ensureCapacity(needed) {
|
|
@@ -1924,8 +2220,8 @@ function siftDown(dist, rows, i, n) {
|
|
|
1924
2220
|
let largest = i;
|
|
1925
2221
|
const left = (i << 1) + 1;
|
|
1926
2222
|
const right = left + 1;
|
|
1927
|
-
if (left < n && dist[left] > dist[largest]) largest = left;
|
|
1928
|
-
if (right < n && dist[right] > dist[largest]) largest = right;
|
|
2223
|
+
if (left < n && (dist[left] > dist[largest] || dist[left] === dist[largest] && rows[left] > rows[largest])) largest = left;
|
|
2224
|
+
if (right < n && (dist[right] > dist[largest] || dist[right] === dist[largest] && rows[right] > rows[largest])) largest = right;
|
|
1929
2225
|
if (largest === i) return;
|
|
1930
2226
|
const td = dist[i];
|
|
1931
2227
|
dist[i] = dist[largest];
|
|
@@ -2067,6 +2363,9 @@ var BATCH_INSERT_CHUNK = 96;
|
|
|
2067
2363
|
var MAX_ANN_OVERFETCH = 8192;
|
|
2068
2364
|
var INSERT_COALESCE_THRESHOLD = 24;
|
|
2069
2365
|
var INSERT_COALESCE_MAX = 96;
|
|
2366
|
+
var warnLogger = new WolbargLogger("warn");
|
|
2367
|
+
var warnedSqliteVecFallback = false;
|
|
2368
|
+
var warnedFtsKeywordSearch = false;
|
|
2070
2369
|
var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
2071
2370
|
name = "sqlite";
|
|
2072
2371
|
connectionString;
|
|
@@ -2076,6 +2375,10 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2076
2375
|
vectorDimensions = null;
|
|
2077
2376
|
vectorBackend = null;
|
|
2078
2377
|
sqliteVecLoaded = false;
|
|
2378
|
+
/** Tracks nesting depth for {@link withTransaction} so we can use savepoints. */
|
|
2379
|
+
transactionDepth = 0;
|
|
2380
|
+
/** Incrementing counter for deterministic savepoint names. */
|
|
2381
|
+
savepointCounter = 0;
|
|
2079
2382
|
/** Hot in-process ANN for blob backend (sqlite-vec unavailable platforms). */
|
|
2080
2383
|
memoryIndex = null;
|
|
2081
2384
|
memoryIndexDirty = false;
|
|
@@ -2097,6 +2400,9 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2097
2400
|
/** Coalesce concurrent insertMemory callers into one IMMEDIATE transaction. */
|
|
2098
2401
|
insertQueue = [];
|
|
2099
2402
|
insertFlushScheduled = false;
|
|
2403
|
+
/**
|
|
2404
|
+
* @param options - SQLite path / `:memory:` and optional concurrency config.
|
|
2405
|
+
*/
|
|
2100
2406
|
constructor(options) {
|
|
2101
2407
|
this.connectionString = options.connectionString;
|
|
2102
2408
|
this.concurrency = resolveConcurrencyConfig(options.concurrency);
|
|
@@ -2109,9 +2415,11 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2109
2415
|
getDatabase() {
|
|
2110
2416
|
return this.db;
|
|
2111
2417
|
}
|
|
2418
|
+
/** Register a callback invoked when SQLite busy retries occur (tests / diagnostics). */
|
|
2112
2419
|
setRetryLogger(fn) {
|
|
2113
2420
|
this.retryLog = fn;
|
|
2114
2421
|
}
|
|
2422
|
+
/** Open the database, run migrations, and prepare statements. */
|
|
2115
2423
|
async open() {
|
|
2116
2424
|
try {
|
|
2117
2425
|
const dbPath = this.resolvePath(this.connectionString);
|
|
@@ -2142,6 +2450,12 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2142
2450
|
} else if (this.sqliteVecLoaded) {
|
|
2143
2451
|
this.vectorBackend = "sqlite-vec";
|
|
2144
2452
|
} else {
|
|
2453
|
+
if (!warnedSqliteVecFallback) {
|
|
2454
|
+
warnedSqliteVecFallback = true;
|
|
2455
|
+
warnLogger.warn(
|
|
2456
|
+
"sqlite-vec unavailable on this platform; using blob ANN fallback."
|
|
2457
|
+
);
|
|
2458
|
+
}
|
|
2145
2459
|
this.vectorBackend = "blob";
|
|
2146
2460
|
}
|
|
2147
2461
|
if (dims !== null) {
|
|
@@ -2165,6 +2479,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2165
2479
|
);
|
|
2166
2480
|
}
|
|
2167
2481
|
}
|
|
2482
|
+
/** Drain pending inserts, optimize, and close the SQLite connection. */
|
|
2168
2483
|
async close() {
|
|
2169
2484
|
const deadline = Date.now() + 2e3;
|
|
2170
2485
|
while (this.insertQueue.length > 0 && Date.now() < deadline) {
|
|
@@ -2195,6 +2510,11 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2195
2510
|
this.batchBlobEmbStatements.clear();
|
|
2196
2511
|
}
|
|
2197
2512
|
}
|
|
2513
|
+
/**
|
|
2514
|
+
* Create or validate vec0 / blob vector storage for the given dimensionality.
|
|
2515
|
+
*
|
|
2516
|
+
* @param dimensions - Embedding length from the configured model.
|
|
2517
|
+
*/
|
|
2198
2518
|
async ensureVectorSchema(dimensions) {
|
|
2199
2519
|
const existing = await this.getEmbeddingDimensions();
|
|
2200
2520
|
if (existing !== null && existing !== dimensions) {
|
|
@@ -2217,13 +2537,16 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2217
2537
|
this.reprepareVectorStatements();
|
|
2218
2538
|
this.hydrateMemoryIndex();
|
|
2219
2539
|
}
|
|
2540
|
+
/** @inheritdoc StorageProvider.getEmbeddingDimensions */
|
|
2220
2541
|
async getEmbeddingDimensions() {
|
|
2221
2542
|
return this.readMetaNumber(META_KEYS.embeddingDimensions);
|
|
2222
2543
|
}
|
|
2544
|
+
/** @inheritdoc StorageProvider.setEmbeddingDimensions */
|
|
2223
2545
|
async setEmbeddingDimensions(dimensions) {
|
|
2224
2546
|
await this.setMeta(META_KEYS.embeddingDimensions, String(dimensions));
|
|
2225
2547
|
this.vectorDimensions = dimensions;
|
|
2226
2548
|
}
|
|
2549
|
+
/** Insert a single memory row (coalesced under concurrent writers). */
|
|
2227
2550
|
async insertMemory(input) {
|
|
2228
2551
|
this.requireVectorReady();
|
|
2229
2552
|
return new Promise((resolve, reject) => {
|
|
@@ -2305,6 +2628,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2305
2628
|
return row;
|
|
2306
2629
|
});
|
|
2307
2630
|
}
|
|
2631
|
+
/** Batch insert memories in chunked multi-row transactions. */
|
|
2308
2632
|
async insertMemoriesBatch(inputs) {
|
|
2309
2633
|
if (inputs.length === 0) {
|
|
2310
2634
|
return [];
|
|
@@ -2320,6 +2644,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2320
2644
|
return rows;
|
|
2321
2645
|
});
|
|
2322
2646
|
}
|
|
2647
|
+
/** Update memory content, metadata, embedding, and content hash. */
|
|
2323
2648
|
async updateMemory(input) {
|
|
2324
2649
|
const stmts = this.requireStatements();
|
|
2325
2650
|
return this.withTransaction(() => {
|
|
@@ -2363,6 +2688,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2363
2688
|
return stmts.getMemoryById.get(input.id, input.organization);
|
|
2364
2689
|
});
|
|
2365
2690
|
}
|
|
2691
|
+
/** Find an active memory by org, agent, and content hash (dedupe). */
|
|
2366
2692
|
async findActiveByContentHash(organization, agent, contentHash) {
|
|
2367
2693
|
const stmts = this.requireStatements();
|
|
2368
2694
|
const row = stmts.findActiveByContentHash.get(
|
|
@@ -2372,6 +2698,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2372
2698
|
);
|
|
2373
2699
|
return row ?? null;
|
|
2374
2700
|
}
|
|
2701
|
+
/** Scan memories matching metadata filters (may paginate in SQL). */
|
|
2375
2702
|
async searchByMetadata(filter, limit) {
|
|
2376
2703
|
const rows = await this.listMemories(filter, limit);
|
|
2377
2704
|
if (!filter.metadata) {
|
|
@@ -2382,9 +2709,16 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2382
2709
|
);
|
|
2383
2710
|
}
|
|
2384
2711
|
/** Keyword search via FTS5 BM25. Returns memory IDs ranked by relevance. */
|
|
2712
|
+
/** BM25 keyword search via FTS5 (`memories_fts`). */
|
|
2385
2713
|
async searchKeyword(query, organization, topK) {
|
|
2386
2714
|
const stmts = this.requireStatements();
|
|
2387
2715
|
if (!stmts.searchFts) {
|
|
2716
|
+
if (!warnedFtsKeywordSearch) {
|
|
2717
|
+
warnedFtsKeywordSearch = true;
|
|
2718
|
+
warnLogger.warn(
|
|
2719
|
+
"FTS keyword search is unavailable; returning empty keyword hits."
|
|
2720
|
+
);
|
|
2721
|
+
}
|
|
2388
2722
|
return [];
|
|
2389
2723
|
}
|
|
2390
2724
|
try {
|
|
@@ -2398,20 +2732,32 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2398
2732
|
// bm25 returns lower (more negative) for better matches — invert to [0, ∞)
|
|
2399
2733
|
score: 1 / (1 + Math.abs(row.rank))
|
|
2400
2734
|
}));
|
|
2401
|
-
} catch {
|
|
2735
|
+
} catch (error) {
|
|
2736
|
+
if (!warnedFtsKeywordSearch) {
|
|
2737
|
+
warnedFtsKeywordSearch = true;
|
|
2738
|
+
warnLogger.warn(
|
|
2739
|
+
"FTS keyword search failed; returning empty keyword hits.",
|
|
2740
|
+
{
|
|
2741
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
2742
|
+
}
|
|
2743
|
+
);
|
|
2744
|
+
}
|
|
2402
2745
|
return [];
|
|
2403
2746
|
}
|
|
2404
2747
|
}
|
|
2748
|
+
/** Fetch a memory row by UUID within an organization. */
|
|
2405
2749
|
async getMemoryById(id, organization) {
|
|
2406
2750
|
const stmts = this.requireStatements();
|
|
2407
2751
|
const row = stmts.getMemoryById.get(id, organization);
|
|
2408
2752
|
return row ?? null;
|
|
2409
2753
|
}
|
|
2754
|
+
/** Fetch a memory row by SQLite integer rowid within an organization. */
|
|
2410
2755
|
async getMemoryByRowid(rowid, organization) {
|
|
2411
2756
|
const stmts = this.requireStatements();
|
|
2412
2757
|
const row = stmts.getMemoryByRowid.get(rowid, organization);
|
|
2413
2758
|
return row ?? null;
|
|
2414
2759
|
}
|
|
2760
|
+
/** Batch-fetch memory rows by SQLite rowids within an organization. */
|
|
2415
2761
|
async getMemoriesByRowids(rowids, organization) {
|
|
2416
2762
|
const out = /* @__PURE__ */ new Map();
|
|
2417
2763
|
if (rowids.length === 0) {
|
|
@@ -2430,6 +2776,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2430
2776
|
}
|
|
2431
2777
|
return out;
|
|
2432
2778
|
}
|
|
2779
|
+
/** List memories matching repository filters with optional limit. */
|
|
2433
2780
|
async listMemories(filter, limit) {
|
|
2434
2781
|
try {
|
|
2435
2782
|
if (filter.metadata) {
|
|
@@ -2442,6 +2789,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2442
2789
|
});
|
|
2443
2790
|
}
|
|
2444
2791
|
}
|
|
2792
|
+
/** Approximate nearest-neighbor search (vec0 or in-memory blob index). */
|
|
2445
2793
|
async searchVectors(embedding, topK) {
|
|
2446
2794
|
this.requireVectorReady();
|
|
2447
2795
|
if (this.vectorBackend === "sqlite-vec") {
|
|
@@ -2453,6 +2801,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2453
2801
|
* Org-scoped KNN + memory rows with adaptive overfetch.
|
|
2454
2802
|
* Global ANN is post-filtered by org/agent/archived; underfill triggers larger k.
|
|
2455
2803
|
*/
|
|
2804
|
+
/** ANN search joined with memory rows and org/archived post-filters. */
|
|
2456
2805
|
async searchVectorsWithMemories(embedding, topK, organization, options) {
|
|
2457
2806
|
this.requireVectorReady();
|
|
2458
2807
|
if (topK <= 0) {
|
|
@@ -2491,6 +2840,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2491
2840
|
}
|
|
2492
2841
|
return out;
|
|
2493
2842
|
}
|
|
2843
|
+
/** Soft-archive memories (compression / forget paths). */
|
|
2494
2844
|
async archiveMemories(ids, organization, compressedIntoId, archivedAt) {
|
|
2495
2845
|
const stmts = this.requireStatements();
|
|
2496
2846
|
const archived = [];
|
|
@@ -2528,6 +2878,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2528
2878
|
return archived;
|
|
2529
2879
|
});
|
|
2530
2880
|
}
|
|
2881
|
+
/** Hard-delete a single memory by id; returns whether a row was removed. */
|
|
2531
2882
|
async deleteMemoryById(id, organization) {
|
|
2532
2883
|
const stmts = this.requireStatements();
|
|
2533
2884
|
return this.withTransaction(() => {
|
|
@@ -2541,6 +2892,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2541
2892
|
return Number(result.changes) > 0;
|
|
2542
2893
|
});
|
|
2543
2894
|
}
|
|
2895
|
+
/** Hard-delete memories matching org / agent / metadata filters. */
|
|
2544
2896
|
async deleteMemoriesByFilter(filter) {
|
|
2545
2897
|
const stmts = this.requireStatements();
|
|
2546
2898
|
const agent = filter.agent;
|
|
@@ -2557,6 +2909,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2557
2909
|
return Number(result.changes);
|
|
2558
2910
|
});
|
|
2559
2911
|
}
|
|
2912
|
+
/** Remove all memories (and side tables) for an organization. */
|
|
2560
2913
|
async clearOrganization(organization) {
|
|
2561
2914
|
const stmts = this.requireStatements();
|
|
2562
2915
|
return this.withTransaction(() => {
|
|
@@ -2573,10 +2926,12 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2573
2926
|
return Number(result.changes);
|
|
2574
2927
|
});
|
|
2575
2928
|
}
|
|
2929
|
+
/** List audit history events for a memory id. */
|
|
2576
2930
|
async getHistory(memoryId) {
|
|
2577
2931
|
const stmts = this.requireStatements();
|
|
2578
2932
|
return stmts.getHistory.all(memoryId);
|
|
2579
2933
|
}
|
|
2934
|
+
/** Append a history row (created / archived / compressed / updated). */
|
|
2580
2935
|
async insertHistoryEvent(event) {
|
|
2581
2936
|
const stmts = this.requireStatements();
|
|
2582
2937
|
stmts.insertHistory.run(
|
|
@@ -2587,6 +2942,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2587
2942
|
event.created_at
|
|
2588
2943
|
);
|
|
2589
2944
|
}
|
|
2945
|
+
/** Aggregate memory counts for an organization (single-pass SQL). */
|
|
2590
2946
|
async getStats(organization) {
|
|
2591
2947
|
const stmts = this.requireStatements();
|
|
2592
2948
|
const row = stmts.getStats.get(organization);
|
|
@@ -2597,6 +2953,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2597
2953
|
totalAgents: Number(row.agents)
|
|
2598
2954
|
};
|
|
2599
2955
|
}
|
|
2956
|
+
/** On-disk database file size in bytes (0 for `:memory:`). */
|
|
2600
2957
|
async getDatabaseSizeBytes() {
|
|
2601
2958
|
const db = this.requireDb();
|
|
2602
2959
|
if (this.connectionString === ":memory:") {
|
|
@@ -2629,21 +2986,44 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2629
2986
|
}
|
|
2630
2987
|
async withTransaction(fn) {
|
|
2631
2988
|
const db = this.requireDb();
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2989
|
+
if (this.transactionDepth > 0) {
|
|
2990
|
+
const savepointName = `wolbarg_sp_${this.savepointCounter++}`;
|
|
2991
|
+
db.exec(`SAVEPOINT ${savepointName}`);
|
|
2992
|
+
try {
|
|
2993
|
+
this.transactionDepth += 1;
|
|
2994
|
+
const result = await fn();
|
|
2995
|
+
db.exec(`RELEASE SAVEPOINT ${savepointName}`);
|
|
2996
|
+
return result;
|
|
2997
|
+
} catch (error) {
|
|
2998
|
+
try {
|
|
2999
|
+
db.exec(`ROLLBACK TO SAVEPOINT ${savepointName}`);
|
|
3000
|
+
} catch {
|
|
3001
|
+
}
|
|
3002
|
+
throw error;
|
|
3003
|
+
} finally {
|
|
3004
|
+
this.transactionDepth -= 1;
|
|
2640
3005
|
}
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
3006
|
+
}
|
|
3007
|
+
this.transactionDepth = 1;
|
|
3008
|
+
try {
|
|
3009
|
+
return await withImmediateTransaction(
|
|
3010
|
+
db,
|
|
3011
|
+
this.concurrency,
|
|
3012
|
+
fn,
|
|
3013
|
+
(attempt, delayMs) => {
|
|
3014
|
+
this.retryLog?.(
|
|
3015
|
+
`SQLITE_BUSY retry attempt=${attempt} backoffMs=${Math.round(delayMs)}`
|
|
3016
|
+
);
|
|
3017
|
+
}
|
|
3018
|
+
);
|
|
3019
|
+
} finally {
|
|
3020
|
+
this.transactionDepth = 0;
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
// ─── internals ───────────────────────────────────────────────────────────
|
|
3024
|
+
tryLoadSqliteVec(db) {
|
|
3025
|
+
const plat = `${process.platform}-${process.arch}`;
|
|
3026
|
+
if (_SqliteStorageProvider.sqliteVecUnsupported.has(plat)) {
|
|
2647
3027
|
return false;
|
|
2648
3028
|
}
|
|
2649
3029
|
try {
|
|
@@ -3632,11 +4012,17 @@ var PostgresSubscribeListener = class {
|
|
|
3632
4012
|
reconnectDelayMs;
|
|
3633
4013
|
connect;
|
|
3634
4014
|
onError;
|
|
4015
|
+
/**
|
|
4016
|
+
* @param options.connect - Factory returning a dedicated LISTEN client.
|
|
4017
|
+
* @param options.reconnectDelayMs - Delay before reconnect attempts (default 1000).
|
|
4018
|
+
* @param options.onError - Optional error hook for connection failures.
|
|
4019
|
+
*/
|
|
3635
4020
|
constructor(options) {
|
|
3636
4021
|
this.connect = options.connect;
|
|
3637
4022
|
this.reconnectDelayMs = options.reconnectDelayMs ?? 1e3;
|
|
3638
4023
|
this.onError = options.onError;
|
|
3639
4024
|
}
|
|
4025
|
+
/** Open the LISTEN connection (lazy — also started on first subscribe). */
|
|
3640
4026
|
async start() {
|
|
3641
4027
|
if (this.closed) {
|
|
3642
4028
|
return;
|
|
@@ -3733,6 +4119,13 @@ var PostgresSubscribeListener = class {
|
|
|
3733
4119
|
}
|
|
3734
4120
|
}
|
|
3735
4121
|
}
|
|
4122
|
+
/**
|
|
4123
|
+
* Register a filtered callback for memory change events.
|
|
4124
|
+
*
|
|
4125
|
+
* @param filter - Organization / agent / event-type filter.
|
|
4126
|
+
* @param callback - Invoked synchronously on each matching NOTIFY.
|
|
4127
|
+
* @returns Unsubscribe function.
|
|
4128
|
+
*/
|
|
3736
4129
|
subscribe(filter, callback) {
|
|
3737
4130
|
if (this.closed) {
|
|
3738
4131
|
throw new Error("Subscribe backend is closed");
|
|
@@ -3754,6 +4147,7 @@ var PostgresSubscribeListener = class {
|
|
|
3754
4147
|
*/
|
|
3755
4148
|
emit(_event) {
|
|
3756
4149
|
}
|
|
4150
|
+
/** Tear down LISTEN connection and clear subscriptions. */
|
|
3757
4151
|
async close() {
|
|
3758
4152
|
this.closed = true;
|
|
3759
4153
|
this.subscriptions.clear();
|
|
@@ -3792,9 +4186,13 @@ function createPostgresListenerFromPool(pool, onError) {
|
|
|
3792
4186
|
|
|
3793
4187
|
// src/storage/providers/postgres.ts
|
|
3794
4188
|
var txStore = new AsyncLocalStorage();
|
|
4189
|
+
var warnLogger2 = new WolbargLogger("warn");
|
|
4190
|
+
var warnedPgvectorFallback = false;
|
|
4191
|
+
var warnedFtsKeywordSearch2 = false;
|
|
4192
|
+
var warnedHnswSoftFail = false;
|
|
3795
4193
|
var STMT = {
|
|
3796
4194
|
insertOne: "Wolbarg_insert_one_v5",
|
|
3797
|
-
insertBatch: "
|
|
4195
|
+
insertBatch: "Wolbarg_insert_batch_v6"
|
|
3798
4196
|
};
|
|
3799
4197
|
function toFloat4Param(embedding) {
|
|
3800
4198
|
const out = new Array(embedding.length);
|
|
@@ -3865,13 +4263,13 @@ var INSERT_ONE_SQL = `WITH mem AS (
|
|
|
3865
4263
|
var INSERT_BATCH_SQL = `WITH mem AS (
|
|
3866
4264
|
INSERT INTO memories (
|
|
3867
4265
|
id, organization, agent, content_text, metadata_json,
|
|
3868
|
-
archived, compressed_into, created_at, updated_at
|
|
4266
|
+
archived, compressed_into, content_hash, created_at, updated_at
|
|
3869
4267
|
)
|
|
3870
|
-
SELECT id, org, agent, txt, meta::jsonb, false, NULL, c, u
|
|
4268
|
+
SELECT id, org, agent, txt, meta::jsonb, false, NULL, h, c, u
|
|
3871
4269
|
FROM unnest(
|
|
3872
4270
|
$1::text[], $2::text[], $3::text[], $4::text[],
|
|
3873
|
-
$5::text[], $6::timestamptz[], $7::timestamptz[]
|
|
3874
|
-
) AS t(id, org, agent, txt, meta, c, u)
|
|
4271
|
+
$5::text[], $6::timestamptz[], $7::timestamptz[], $8::text[]
|
|
4272
|
+
) AS t(id, org, agent, txt, meta, c, u, h)
|
|
3875
4273
|
RETURNING id
|
|
3876
4274
|
),
|
|
3877
4275
|
hist AS (
|
|
@@ -3887,7 +4285,7 @@ var INSERT_BATCH_SQL = `WITH mem AS (
|
|
|
3887
4285
|
emb AS (
|
|
3888
4286
|
INSERT INTO memory_embeddings (memory_id, embedding, organization, agent, archived)
|
|
3889
4287
|
SELECT id, emb::vector, org, agent, false
|
|
3890
|
-
FROM unnest($1::text[], $
|
|
4288
|
+
FROM unnest($1::text[], $9::text[], $2::text[], $3::text[]) AS t(id, emb, org, agent)
|
|
3891
4289
|
)
|
|
3892
4290
|
SELECT id FROM mem`;
|
|
3893
4291
|
var COALESCE_FLUSH_MAX = 128;
|
|
@@ -3914,6 +4312,9 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
3914
4312
|
insertFlushScheduled = false;
|
|
3915
4313
|
insertFlushTimer = null;
|
|
3916
4314
|
insertFlushInFlight = 0;
|
|
4315
|
+
/**
|
|
4316
|
+
* @param options - Connection string, pool size, and durability flags.
|
|
4317
|
+
*/
|
|
3917
4318
|
constructor(options) {
|
|
3918
4319
|
this.maxPoolSize = options.maxPoolSize ?? 64;
|
|
3919
4320
|
this.durableWrites = options.durableWrites !== false;
|
|
@@ -3922,6 +4323,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
3922
4323
|
this.durableWrites
|
|
3923
4324
|
);
|
|
3924
4325
|
}
|
|
4326
|
+
/** Current pg pool occupancy stats (for diagnostics / subscribe setup). */
|
|
3925
4327
|
getPoolStats() {
|
|
3926
4328
|
const pool = this.pool;
|
|
3927
4329
|
return {
|
|
@@ -3935,6 +4337,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
3935
4337
|
getPool() {
|
|
3936
4338
|
return this.pool;
|
|
3937
4339
|
}
|
|
4340
|
+
/** Open the connection pool and run migrations. */
|
|
3938
4341
|
async open() {
|
|
3939
4342
|
let PoolCtor;
|
|
3940
4343
|
try {
|
|
@@ -3983,6 +4386,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
3983
4386
|
);
|
|
3984
4387
|
}
|
|
3985
4388
|
}
|
|
4389
|
+
/** Drain coalesced inserts and end the connection pool. */
|
|
3986
4390
|
async close() {
|
|
3987
4391
|
if (this.insertFlushTimer) {
|
|
3988
4392
|
clearTimeout(this.insertFlushTimer);
|
|
@@ -4023,6 +4427,11 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4023
4427
|
async ensureVectorIndex() {
|
|
4024
4428
|
await this.ensureHnswIndex();
|
|
4025
4429
|
}
|
|
4430
|
+
/**
|
|
4431
|
+
* Create pgvector tables and HNSW index for the given dimensionality.
|
|
4432
|
+
*
|
|
4433
|
+
* @param dimensions - Embedding length from the configured model.
|
|
4434
|
+
*/
|
|
4026
4435
|
async ensureVectorSchema(dimensions) {
|
|
4027
4436
|
const existing = await this.getEmbeddingDimensions();
|
|
4028
4437
|
if (existing !== null && existing !== dimensions) {
|
|
@@ -4087,6 +4496,11 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4087
4496
|
}
|
|
4088
4497
|
}
|
|
4089
4498
|
/** Cross-process subscribe: NOTIFY after a committed write. */
|
|
4499
|
+
/**
|
|
4500
|
+
* Issue `pg_notify` for cross-process {@link subscribe} delivery.
|
|
4501
|
+
*
|
|
4502
|
+
* @param event - Memory change payload (IDs + metadata only).
|
|
4503
|
+
*/
|
|
4090
4504
|
async notifyChange(event) {
|
|
4091
4505
|
await notifyMemoryChange(this.requirePool(), event);
|
|
4092
4506
|
}
|
|
@@ -4094,6 +4508,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4094
4508
|
* Soft reset for a single organization. Drops HNSW only when the embeddings
|
|
4095
4509
|
* table is empty so other corpora on a shared bench DB stay intact.
|
|
4096
4510
|
*/
|
|
4511
|
+
/** Delete all rows for an organization (dev / test helper). */
|
|
4097
4512
|
async resetOrganization(organization) {
|
|
4098
4513
|
await this.query(`DELETE FROM memories WHERE organization = $1`, [
|
|
4099
4514
|
organization
|
|
@@ -4111,6 +4526,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4111
4526
|
/**
|
|
4112
4527
|
* Wipe all Wolbarg tables (explicit opt-in). Prefer {@link resetOrganization}.
|
|
4113
4528
|
*/
|
|
4529
|
+
/** Truncate all Wolbarg tables (dev / test helper). */
|
|
4114
4530
|
async wipeAllData() {
|
|
4115
4531
|
await this.query(`TRUNCATE TABLE memories CASCADE`).catch(() => void 0);
|
|
4116
4532
|
await this.query(
|
|
@@ -4156,6 +4572,12 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4156
4572
|
return;
|
|
4157
4573
|
}
|
|
4158
4574
|
this.hnswCreateFailures += 1;
|
|
4575
|
+
if (this.hnswCreateFailures === 1 && !warnedHnswSoftFail) {
|
|
4576
|
+
warnedHnswSoftFail = true;
|
|
4577
|
+
warnLogger2.warn(
|
|
4578
|
+
"HNSW index creation failed; ANN will fall back to sequential scan."
|
|
4579
|
+
);
|
|
4580
|
+
}
|
|
4159
4581
|
if (this.hnswCreateFailures >= 3) {
|
|
4160
4582
|
throw new DatabaseError(
|
|
4161
4583
|
`Failed to create HNSW index after ${this.hnswCreateFailures} attempts: ${this.describe(error)}`,
|
|
@@ -4164,6 +4586,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4164
4586
|
}
|
|
4165
4587
|
}
|
|
4166
4588
|
}
|
|
4589
|
+
/** @inheritdoc StorageProvider.getEmbeddingDimensions */
|
|
4167
4590
|
async getEmbeddingDimensions() {
|
|
4168
4591
|
const result = await this.query(
|
|
4169
4592
|
`SELECT value FROM Wolbarg_meta WHERE key = $1`,
|
|
@@ -4176,6 +4599,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4176
4599
|
const parsed = Number(value);
|
|
4177
4600
|
return Number.isFinite(parsed) ? parsed : null;
|
|
4178
4601
|
}
|
|
4602
|
+
/** @inheritdoc StorageProvider.setEmbeddingDimensions */
|
|
4179
4603
|
async setEmbeddingDimensions(dimensions) {
|
|
4180
4604
|
await this.query(
|
|
4181
4605
|
`INSERT INTO Wolbarg_meta (key, value) VALUES ($1, $2)
|
|
@@ -4184,6 +4608,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4184
4608
|
);
|
|
4185
4609
|
this.vectorDimensions = dimensions;
|
|
4186
4610
|
}
|
|
4611
|
+
/** Insert a single memory row (coalesced under concurrent writers). */
|
|
4187
4612
|
async insertMemory(input) {
|
|
4188
4613
|
this.requireVectorReady();
|
|
4189
4614
|
return new Promise((resolve, reject) => {
|
|
@@ -4270,6 +4695,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4270
4695
|
}
|
|
4271
4696
|
return this.insertOneBlob(input);
|
|
4272
4697
|
}
|
|
4698
|
+
/** Batch insert via unnest / COPY paths with optional HNSW deferral. */
|
|
4273
4699
|
async insertMemoriesBatch(inputs) {
|
|
4274
4700
|
if (inputs.length === 0) {
|
|
4275
4701
|
return [];
|
|
@@ -4300,6 +4726,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4300
4726
|
const metas = new Array(inputs.length);
|
|
4301
4727
|
const created = new Array(inputs.length);
|
|
4302
4728
|
const updated = new Array(inputs.length);
|
|
4729
|
+
const contentHashes = new Array(inputs.length);
|
|
4303
4730
|
const vectors = new Array(inputs.length);
|
|
4304
4731
|
for (let i = 0; i < inputs.length; i += 1) {
|
|
4305
4732
|
const input = inputs[i];
|
|
@@ -4310,6 +4737,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4310
4737
|
metas[i] = serializeMetadata(input.metadata);
|
|
4311
4738
|
created[i] = input.createdAt;
|
|
4312
4739
|
updated[i] = input.updatedAt;
|
|
4740
|
+
contentHashes[i] = input.contentHash !== void 0 ? input.contentHash : hashMemoryContent(input.contentText);
|
|
4313
4741
|
vectors[i] = toVectorLiteral(input.embedding);
|
|
4314
4742
|
}
|
|
4315
4743
|
await this.queryNamed(STMT.insertBatch, INSERT_BATCH_SQL, [
|
|
@@ -4320,9 +4748,10 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4320
4748
|
metas,
|
|
4321
4749
|
created,
|
|
4322
4750
|
updated,
|
|
4751
|
+
contentHashes,
|
|
4323
4752
|
vectors
|
|
4324
4753
|
]);
|
|
4325
|
-
return inputs.map((
|
|
4754
|
+
return inputs.map((_, i) => ({
|
|
4326
4755
|
id: ids[i],
|
|
4327
4756
|
organization: orgs[i],
|
|
4328
4757
|
agent: agents[i],
|
|
@@ -4330,7 +4759,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4330
4759
|
metadata_json: metas[i],
|
|
4331
4760
|
archived: 0,
|
|
4332
4761
|
compressed_into: null,
|
|
4333
|
-
content_hash:
|
|
4762
|
+
content_hash: contentHashes[i] ?? null,
|
|
4334
4763
|
created_at: created[i],
|
|
4335
4764
|
updated_at: updated[i]
|
|
4336
4765
|
}));
|
|
@@ -4371,81 +4800,87 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4371
4800
|
return out;
|
|
4372
4801
|
}
|
|
4373
4802
|
async insertOneBlob(input) {
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4803
|
+
return this.withTransaction(async () => {
|
|
4804
|
+
const buf = Buffer.from(
|
|
4805
|
+
input.embedding.buffer,
|
|
4806
|
+
input.embedding.byteOffset,
|
|
4807
|
+
input.embedding.byteLength
|
|
4808
|
+
);
|
|
4809
|
+
const contentHash = input.contentHash !== void 0 ? input.contentHash : hashMemoryContent(input.contentText);
|
|
4810
|
+
const inserted = await this.query(
|
|
4811
|
+
`INSERT INTO memories (
|
|
4812
|
+
id, organization, agent, content_text, metadata_json,
|
|
4813
|
+
archived, compressed_into, content_hash, created_at, updated_at
|
|
4814
|
+
) VALUES ($1,$2,$3,$4,$5::jsonb,false,NULL,$8,$6,$7)
|
|
4815
|
+
RETURNING id, organization, agent, content_text, metadata_json,
|
|
4816
|
+
archived::int AS archived, compressed_into, content_hash, created_at, updated_at`,
|
|
4817
|
+
[
|
|
4818
|
+
input.id,
|
|
4819
|
+
input.organization,
|
|
4820
|
+
input.agent,
|
|
4821
|
+
input.contentText,
|
|
4822
|
+
serializeMetadata(input.metadata),
|
|
4823
|
+
input.createdAt,
|
|
4824
|
+
input.updatedAt,
|
|
4825
|
+
contentHash
|
|
4826
|
+
]
|
|
4827
|
+
);
|
|
4828
|
+
const row = this.mapRow(inserted.rows[0]);
|
|
4829
|
+
await this.query(
|
|
4830
|
+
`WITH mapped AS (
|
|
4831
|
+
INSERT INTO memory_row_map (memory_id) VALUES ($1)
|
|
4832
|
+
ON CONFLICT (memory_id) DO NOTHING
|
|
4833
|
+
)
|
|
4834
|
+
INSERT INTO memory_embeddings_blob (memory_id, embedding)
|
|
4835
|
+
VALUES ($1, $2)
|
|
4836
|
+
ON CONFLICT (memory_id) DO UPDATE SET embedding = EXCLUDED.embedding`,
|
|
4837
|
+
[input.id, buf]
|
|
4838
|
+
);
|
|
4839
|
+
await this.query(
|
|
4840
|
+
`INSERT INTO memory_history (id, memory_id, event_type, related_memory_id, created_at)
|
|
4841
|
+
VALUES ($1,$2,'created',NULL,$3)`,
|
|
4842
|
+
[crypto.randomUUID(), input.id, input.createdAt]
|
|
4843
|
+
);
|
|
4844
|
+
return row;
|
|
4845
|
+
});
|
|
4415
4846
|
}
|
|
4847
|
+
/** Update memory content, metadata, embedding, and content hash. */
|
|
4416
4848
|
async updateMemory(input) {
|
|
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
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4849
|
+
return this.withTransaction(async () => {
|
|
4850
|
+
const existing = await this.getMemoryById(input.id, input.organization);
|
|
4851
|
+
if (!existing) {
|
|
4852
|
+
return null;
|
|
4853
|
+
}
|
|
4854
|
+
const contentHash = input.contentHash !== void 0 ? input.contentHash : input.contentText !== void 0 ? hashMemoryContent(input.contentText) : existing.content_hash ?? null;
|
|
4855
|
+
await this.query(
|
|
4856
|
+
`UPDATE memories SET
|
|
4857
|
+
content_text = COALESCE($1, content_text),
|
|
4858
|
+
metadata_json = COALESCE($2::jsonb, metadata_json),
|
|
4859
|
+
content_hash = COALESCE($3, content_hash),
|
|
4860
|
+
updated_at = $4
|
|
4861
|
+
WHERE id = $5 AND organization = $6`,
|
|
4862
|
+
[
|
|
4863
|
+
input.contentText ?? null,
|
|
4864
|
+
input.metadata !== void 0 ? serializeMetadata(input.metadata) : null,
|
|
4865
|
+
contentHash,
|
|
4866
|
+
input.updatedAt,
|
|
4867
|
+
input.id,
|
|
4868
|
+
input.organization
|
|
4869
|
+
]
|
|
4870
|
+
);
|
|
4871
|
+
if (input.embedding) {
|
|
4872
|
+
await this.deleteEmbedding(input.id);
|
|
4873
|
+
await this.insertEmbedding(input.id, input.embedding);
|
|
4874
|
+
}
|
|
4875
|
+
await this.query(
|
|
4876
|
+
`INSERT INTO memory_history (id, memory_id, event_type, related_memory_id, created_at)
|
|
4877
|
+
VALUES ($1,$2,'updated',NULL,$3)`,
|
|
4878
|
+
[crypto.randomUUID(), input.id, input.updatedAt]
|
|
4879
|
+
);
|
|
4880
|
+
return this.getMemoryById(input.id, input.organization);
|
|
4881
|
+
});
|
|
4448
4882
|
}
|
|
4883
|
+
/** Find an active memory by org, agent, and content hash (dedupe). */
|
|
4449
4884
|
async findActiveByContentHash(organization, agent, contentHash) {
|
|
4450
4885
|
const result = await this.query(
|
|
4451
4886
|
`SELECT id, organization, agent, content_text, metadata_json,
|
|
@@ -4458,6 +4893,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4458
4893
|
const row = result.rows[0];
|
|
4459
4894
|
return row ? this.mapRow(row) : null;
|
|
4460
4895
|
}
|
|
4896
|
+
/** Fetch a memory row by UUID within an organization. */
|
|
4461
4897
|
async getMemoryById(id, organization) {
|
|
4462
4898
|
const result = await this.query(
|
|
4463
4899
|
`SELECT id, organization, agent, content_text, metadata_json,
|
|
@@ -4468,6 +4904,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4468
4904
|
const row = result.rows[0];
|
|
4469
4905
|
return row ? this.mapRow(row) : null;
|
|
4470
4906
|
}
|
|
4907
|
+
/** Fetch a memory row by internal rowid within an organization. */
|
|
4471
4908
|
async getMemoryByRowid(rowid, organization) {
|
|
4472
4909
|
const result = await this.query(
|
|
4473
4910
|
`SELECT m.id, m.organization, m.agent, m.content_text, m.metadata_json,
|
|
@@ -4481,6 +4918,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4481
4918
|
const row = result.rows[0];
|
|
4482
4919
|
return row ? this.mapRow(row) : null;
|
|
4483
4920
|
}
|
|
4921
|
+
/** Batch-fetch memory rows by rowids within an organization. */
|
|
4484
4922
|
async getMemoriesByRowids(rowids, organization) {
|
|
4485
4923
|
const out = /* @__PURE__ */ new Map();
|
|
4486
4924
|
if (rowids.length === 0) {
|
|
@@ -4503,6 +4941,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4503
4941
|
}
|
|
4504
4942
|
return out;
|
|
4505
4943
|
}
|
|
4944
|
+
/** List memories matching repository filters with optional limit. */
|
|
4506
4945
|
async listMemories(filter, limit) {
|
|
4507
4946
|
const want = limit !== void 0 ? limit : filter.metadata ? 1e4 : void 0;
|
|
4508
4947
|
const compiled = filter.metadata ? compileMetadataFilterToPostgres(filter.metadata, 2) : null;
|
|
@@ -4564,9 +5003,11 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4564
5003
|
}
|
|
4565
5004
|
return rows;
|
|
4566
5005
|
}
|
|
5006
|
+
/** Scan memories matching metadata filters. */
|
|
4567
5007
|
async searchByMetadata(filter, limit) {
|
|
4568
5008
|
return this.listMemories(filter, limit);
|
|
4569
5009
|
}
|
|
5010
|
+
/** Full-text keyword search via `tsvector` / GIN index. */
|
|
4570
5011
|
async searchKeyword(query, organization, topK) {
|
|
4571
5012
|
const trimmed = query.trim();
|
|
4572
5013
|
if (!trimmed || topK <= 0) {
|
|
@@ -4593,10 +5034,20 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4593
5034
|
memoryId: String(row.memory_id),
|
|
4594
5035
|
score: Number(row.rank)
|
|
4595
5036
|
}));
|
|
4596
|
-
} catch {
|
|
5037
|
+
} catch (error) {
|
|
5038
|
+
if (!warnedFtsKeywordSearch2) {
|
|
5039
|
+
warnedFtsKeywordSearch2 = true;
|
|
5040
|
+
warnLogger2.warn(
|
|
5041
|
+
"FTS keyword search failed; returning empty keyword hits.",
|
|
5042
|
+
{
|
|
5043
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
5044
|
+
}
|
|
5045
|
+
);
|
|
5046
|
+
}
|
|
4597
5047
|
return [];
|
|
4598
5048
|
}
|
|
4599
5049
|
}
|
|
5050
|
+
/** pgvector cosine ANN search (HNSW when index is present). */
|
|
4600
5051
|
async searchVectors(embedding, topK) {
|
|
4601
5052
|
this.requireVectorReady();
|
|
4602
5053
|
if (this.hasPgvector) {
|
|
@@ -4646,6 +5097,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4646
5097
|
scored.sort((a, b) => a.distance - b.distance);
|
|
4647
5098
|
return scored.slice(0, topK);
|
|
4648
5099
|
}
|
|
5100
|
+
/** ANN search joined with memory rows and org/archived post-filters. */
|
|
4649
5101
|
async searchVectorsWithMemories(embedding, topK, organization, options) {
|
|
4650
5102
|
this.requireVectorReady();
|
|
4651
5103
|
if (!this.hasPgvector) {
|
|
@@ -4710,46 +5162,50 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4710
5162
|
);
|
|
4711
5163
|
return mapHits(result.rows);
|
|
4712
5164
|
}
|
|
5165
|
+
/** Soft-archive memories (compression / forget paths). */
|
|
4713
5166
|
async archiveMemories(ids, organization, compressedIntoId, archivedAt) {
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
5167
|
+
return this.withTransaction(async () => {
|
|
5168
|
+
if (ids.length === 0) {
|
|
5169
|
+
return [];
|
|
5170
|
+
}
|
|
5171
|
+
const result = await this.query(
|
|
5172
|
+
`UPDATE memories
|
|
5173
|
+
SET archived = true, compressed_into = $1, updated_at = $2
|
|
5174
|
+
WHERE organization = $3 AND archived = false AND id = ANY($4::text[])
|
|
5175
|
+
RETURNING id`,
|
|
5176
|
+
[compressedIntoId, archivedAt, organization, ids]
|
|
5177
|
+
);
|
|
5178
|
+
const archived = result.rows.map((r) => String(r.id));
|
|
5179
|
+
if (archived.length === 0) {
|
|
5180
|
+
return [];
|
|
5181
|
+
}
|
|
5182
|
+
await this.query(
|
|
5183
|
+
`UPDATE memory_embeddings
|
|
5184
|
+
SET archived = true
|
|
5185
|
+
WHERE memory_id = ANY($1::text[])`,
|
|
5186
|
+
[archived]
|
|
5187
|
+
);
|
|
5188
|
+
const histIds = [];
|
|
5189
|
+
const memIds = [];
|
|
5190
|
+
const types = [];
|
|
5191
|
+
const related = [];
|
|
5192
|
+
const times = [];
|
|
5193
|
+
for (const id of archived) {
|
|
5194
|
+
histIds.push(crypto.randomUUID(), crypto.randomUUID());
|
|
5195
|
+
memIds.push(id, compressedIntoId);
|
|
5196
|
+
types.push("archived", "compressed");
|
|
5197
|
+
related.push(compressedIntoId, id);
|
|
5198
|
+
times.push(archivedAt, archivedAt);
|
|
5199
|
+
}
|
|
5200
|
+
await this.query(
|
|
5201
|
+
`INSERT INTO memory_history (id, memory_id, event_type, related_memory_id, created_at)
|
|
5202
|
+
SELECT * FROM unnest($1::text[], $2::text[], $3::text[], $4::text[], $5::timestamptz[])`,
|
|
5203
|
+
[histIds, memIds, types, related, times]
|
|
5204
|
+
);
|
|
5205
|
+
return archived;
|
|
5206
|
+
});
|
|
4752
5207
|
}
|
|
5208
|
+
/** Hard-delete a single memory by id; returns whether a row was removed. */
|
|
4753
5209
|
async deleteMemoryById(id, organization) {
|
|
4754
5210
|
const result = await this.query(
|
|
4755
5211
|
`DELETE FROM memories WHERE id = $1 AND organization = $2`,
|
|
@@ -4757,6 +5213,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4757
5213
|
);
|
|
4758
5214
|
return (result.rowCount ?? 0) > 0;
|
|
4759
5215
|
}
|
|
5216
|
+
/** Hard-delete memories matching org / agent / metadata filters. */
|
|
4760
5217
|
async deleteMemoriesByFilter(filter) {
|
|
4761
5218
|
if (!filter.agent) {
|
|
4762
5219
|
throw new DatabaseError("deleteMemoriesByFilter requires an agent filter");
|
|
@@ -4767,6 +5224,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4767
5224
|
);
|
|
4768
5225
|
return result.rowCount ?? 0;
|
|
4769
5226
|
}
|
|
5227
|
+
/** Remove all memories for an organization. */
|
|
4770
5228
|
async clearOrganization(organization) {
|
|
4771
5229
|
const result = await this.query(
|
|
4772
5230
|
`DELETE FROM memories WHERE organization = $1`,
|
|
@@ -4774,6 +5232,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4774
5232
|
);
|
|
4775
5233
|
return result.rowCount ?? 0;
|
|
4776
5234
|
}
|
|
5235
|
+
/** List audit history events for a memory id. */
|
|
4777
5236
|
async getHistory(memoryId) {
|
|
4778
5237
|
const result = await this.query(
|
|
4779
5238
|
`SELECT id, memory_id, event_type, related_memory_id, created_at
|
|
@@ -4788,6 +5247,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4788
5247
|
created_at: String(row.created_at)
|
|
4789
5248
|
}));
|
|
4790
5249
|
}
|
|
5250
|
+
/** Append a history row (created / archived / compressed / updated). */
|
|
4791
5251
|
async insertHistoryEvent(event) {
|
|
4792
5252
|
await this.query(
|
|
4793
5253
|
`INSERT INTO memory_history (id, memory_id, event_type, related_memory_id, created_at)
|
|
@@ -4801,6 +5261,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4801
5261
|
]
|
|
4802
5262
|
);
|
|
4803
5263
|
}
|
|
5264
|
+
/** Aggregate memory counts for an organization. */
|
|
4804
5265
|
async getStats(organization) {
|
|
4805
5266
|
const result = await this.query(
|
|
4806
5267
|
`SELECT
|
|
@@ -4818,6 +5279,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4818
5279
|
totalAgents: Number(result.rows[0]?.agents ?? 0)
|
|
4819
5280
|
};
|
|
4820
5281
|
}
|
|
5282
|
+
/** Approximate on-disk database size in bytes (`pg_database_size`). */
|
|
4821
5283
|
async getDatabaseSizeBytes() {
|
|
4822
5284
|
const result = await this.query(
|
|
4823
5285
|
`SELECT pg_database_size(current_database())::bigint AS size`
|
|
@@ -4859,6 +5321,11 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
4859
5321
|
[META_KEYS.schemaVersion]
|
|
4860
5322
|
).catch(() => ({ rows: [] }));
|
|
4861
5323
|
const current = versionRow.rows[0]?.value !== void 0 ? Number(versionRow.rows[0].value) : null;
|
|
5324
|
+
if (current !== null && current > SCHEMA_VERSION) {
|
|
5325
|
+
throw new InitializationError(
|
|
5326
|
+
`Database schema version ${current} is newer than this Wolbarg SDK (${SCHEMA_VERSION}). Please upgrade Wolbarg to open this database.`
|
|
5327
|
+
);
|
|
5328
|
+
}
|
|
4862
5329
|
if (current === SCHEMA_VERSION) {
|
|
4863
5330
|
const tsvProbe2 = await this.query(
|
|
4864
5331
|
`SELECT 1 FROM information_schema.columns
|
|
@@ -5009,8 +5476,17 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
|
|
|
5009
5476
|
await this.query(`CREATE EXTENSION IF NOT EXISTS vector`);
|
|
5010
5477
|
_PostgresStorageProvider.pgvectorByConn.set(this.connectionString, true);
|
|
5011
5478
|
return true;
|
|
5012
|
-
} catch {
|
|
5479
|
+
} catch (error) {
|
|
5013
5480
|
_PostgresStorageProvider.pgvectorByConn.set(this.connectionString, false);
|
|
5481
|
+
if (!warnedPgvectorFallback) {
|
|
5482
|
+
warnedPgvectorFallback = true;
|
|
5483
|
+
warnLogger2.warn(
|
|
5484
|
+
"pgvector extension is unavailable; using blob cosine search fallback.",
|
|
5485
|
+
{
|
|
5486
|
+
cause: error instanceof Error ? error.message : `unknown error: ${String(error)}`
|
|
5487
|
+
}
|
|
5488
|
+
);
|
|
5489
|
+
}
|
|
5014
5490
|
return false;
|
|
5015
5491
|
}
|
|
5016
5492
|
}
|
|
@@ -5151,11 +5627,12 @@ function createDatabaseProvider(config) {
|
|
|
5151
5627
|
}
|
|
5152
5628
|
|
|
5153
5629
|
// src/version.ts
|
|
5154
|
-
var SDK_VERSION = "0.5.
|
|
5630
|
+
var SDK_VERSION = "0.5.5";
|
|
5155
5631
|
|
|
5156
5632
|
// src/memory/transfer.ts
|
|
5633
|
+
var warnedMissingExportManifest = false;
|
|
5157
5634
|
var SqliteMemoryTransferProvider = class {
|
|
5158
|
-
async exportTo(exportPath, sourcePath, organization) {
|
|
5635
|
+
async exportTo(exportPath, sourcePath, organization, embeddingModel, embeddingDimensions) {
|
|
5159
5636
|
const resolvedSource = resolvePath(sourcePath);
|
|
5160
5637
|
if (resolvedSource === ":memory:") {
|
|
5161
5638
|
throw new ConfigurationError(
|
|
@@ -5182,7 +5659,9 @@ Initialize Wolbarg and verify the database path.`
|
|
|
5182
5659
|
sdkVersion: SDK_VERSION,
|
|
5183
5660
|
provider: "sqlite",
|
|
5184
5661
|
sourcePath: resolvedSource,
|
|
5185
|
-
organization
|
|
5662
|
+
organization,
|
|
5663
|
+
...embeddingModel ? { embeddingModel } : {},
|
|
5664
|
+
...embeddingDimensions ? { embeddingDimensions } : {}
|
|
5186
5665
|
};
|
|
5187
5666
|
fs6.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
|
|
5188
5667
|
return {
|
|
@@ -5191,7 +5670,7 @@ Initialize Wolbarg and verify the database path.`
|
|
|
5191
5670
|
sizeBytes: fs6.statSync(dbExportPath).size
|
|
5192
5671
|
};
|
|
5193
5672
|
}
|
|
5194
|
-
async importFrom(exportPath, targetPath) {
|
|
5673
|
+
async importFrom(exportPath, targetPath, expected) {
|
|
5195
5674
|
const resolvedExport = resolvePath(exportPath);
|
|
5196
5675
|
const dbExportPath = resolvedExport.endsWith(".db") ? resolvedExport : fs6.existsSync(`${resolvedExport}.db`) ? `${resolvedExport}.db` : resolvedExport;
|
|
5197
5676
|
if (!fs6.existsSync(dbExportPath)) {
|
|
@@ -5213,26 +5692,57 @@ Pass the path returned by export().`
|
|
|
5213
5692
|
);
|
|
5214
5693
|
}
|
|
5215
5694
|
} else {
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5695
|
+
if (!warnedMissingExportManifest) {
|
|
5696
|
+
warnedMissingExportManifest = true;
|
|
5697
|
+
console.warn(
|
|
5698
|
+
"[wolbarg:warn] export manifest is missing; skipping manifest-based import validation."
|
|
5699
|
+
);
|
|
5700
|
+
}
|
|
5701
|
+
}
|
|
5702
|
+
if (manifest) {
|
|
5703
|
+
if (expected?.organization && manifest.organization && expected.organization !== manifest.organization) {
|
|
5704
|
+
throw new ValidationError(
|
|
5705
|
+
"Import failed: export organization does not match the current Wolbarg organization."
|
|
5706
|
+
);
|
|
5707
|
+
}
|
|
5708
|
+
if (expected?.embeddingDimensions != null && manifest.embeddingDimensions != null && expected.embeddingDimensions !== manifest.embeddingDimensions) {
|
|
5709
|
+
throw new ValidationError(
|
|
5710
|
+
`Import failed: embedding dimension mismatch (expected ${expected.embeddingDimensions}, got ${manifest.embeddingDimensions}).`
|
|
5711
|
+
);
|
|
5712
|
+
}
|
|
5713
|
+
if (expected?.embeddingModel && manifest.embeddingModel && expected.embeddingModel !== manifest.embeddingModel) {
|
|
5714
|
+
throw new ValidationError(
|
|
5715
|
+
`Import failed: embedding model mismatch (expected ${expected.embeddingModel}, got ${manifest.embeddingModel}).`
|
|
5716
|
+
);
|
|
5717
|
+
}
|
|
5223
5718
|
}
|
|
5224
5719
|
const resolvedTarget = resolvePath(targetPath);
|
|
5225
5720
|
if (resolvedTarget === ":memory:") {
|
|
5226
5721
|
throw new ConfigurationError("Cannot import into an in-memory database.");
|
|
5227
5722
|
}
|
|
5723
|
+
const tmpTarget = `${resolvedTarget}.tmp-import-${Date.now()}`;
|
|
5724
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
5725
|
+
const side = `${tmpTarget}${suffix}`;
|
|
5726
|
+
if (fs6.existsSync(side)) {
|
|
5727
|
+
fs6.rmSync(side, { force: true });
|
|
5728
|
+
}
|
|
5729
|
+
}
|
|
5730
|
+
await copySqlite(dbExportPath, tmpTarget);
|
|
5228
5731
|
for (const suffix of ["", "-wal", "-shm"]) {
|
|
5229
5732
|
const side = `${resolvedTarget}${suffix}`;
|
|
5230
5733
|
if (fs6.existsSync(side)) {
|
|
5231
5734
|
fs6.rmSync(side, { force: true });
|
|
5232
5735
|
}
|
|
5233
5736
|
}
|
|
5234
|
-
|
|
5235
|
-
|
|
5737
|
+
fs6.renameSync(tmpTarget, resolvedTarget);
|
|
5738
|
+
for (const suffix of ["-wal", "-shm"]) {
|
|
5739
|
+
const from = `${tmpTarget}${suffix}`;
|
|
5740
|
+
const to = `${resolvedTarget}${suffix}`;
|
|
5741
|
+
if (fs6.existsSync(from)) {
|
|
5742
|
+
fs6.renameSync(from, to);
|
|
5743
|
+
}
|
|
5744
|
+
}
|
|
5745
|
+
return { path: resolvedTarget, manifest: manifest ?? void 0 };
|
|
5236
5746
|
}
|
|
5237
5747
|
};
|
|
5238
5748
|
async function copySqlite(sourcePath, destPath) {
|
|
@@ -5461,7 +5971,8 @@ function applyMmr(candidates, topK, lambda) {
|
|
|
5461
5971
|
);
|
|
5462
5972
|
}
|
|
5463
5973
|
const score = lambda * relevance - (1 - lambda) * maxSim;
|
|
5464
|
-
|
|
5974
|
+
const best = remaining[bestIdx];
|
|
5975
|
+
if (score > bestScore || score === bestScore && candidate.id < best.id) {
|
|
5465
5976
|
bestScore = score;
|
|
5466
5977
|
bestIdx = i;
|
|
5467
5978
|
}
|
|
@@ -5585,6 +6096,9 @@ var SqliteGraphProvider = class {
|
|
|
5585
6096
|
concurrency;
|
|
5586
6097
|
db = null;
|
|
5587
6098
|
opened = false;
|
|
6099
|
+
/**
|
|
6100
|
+
* @param options - Graph SQLite path and optional concurrency tuning.
|
|
6101
|
+
*/
|
|
5588
6102
|
constructor(options) {
|
|
5589
6103
|
if (!options?.path || typeof options.path !== "string" || !options.path.trim()) {
|
|
5590
6104
|
throw new ConfigurationError("sqlite graph requires a non-empty path");
|
|
@@ -5592,12 +6106,15 @@ var SqliteGraphProvider = class {
|
|
|
5592
6106
|
this.dbPath = path7.resolve(options.path.trim());
|
|
5593
6107
|
this.concurrency = resolveConcurrencyConfig(options.concurrency);
|
|
5594
6108
|
}
|
|
6109
|
+
/** Whether this provider supports file snapshots for checkpoint / export. */
|
|
5595
6110
|
supportsFileSnapshot() {
|
|
5596
6111
|
return this.dbPath !== ":memory:";
|
|
5597
6112
|
}
|
|
6113
|
+
/** Absolute graph database path, or `null` for `:memory:`. */
|
|
5598
6114
|
getDataPath() {
|
|
5599
6115
|
return this.dbPath === ":memory:" ? null : this.dbPath;
|
|
5600
6116
|
}
|
|
6117
|
+
/** Open the graph SQLite file, apply pragmas, and create schema if needed. */
|
|
5601
6118
|
async open() {
|
|
5602
6119
|
if (this.opened) return;
|
|
5603
6120
|
try {
|
|
@@ -5634,6 +6151,7 @@ var SqliteGraphProvider = class {
|
|
|
5634
6151
|
);
|
|
5635
6152
|
}
|
|
5636
6153
|
}
|
|
6154
|
+
/** Close the graph database connection. */
|
|
5637
6155
|
async close() {
|
|
5638
6156
|
if (!this.db) {
|
|
5639
6157
|
this.opened = false;
|
|
@@ -5655,6 +6173,14 @@ var SqliteGraphProvider = class {
|
|
|
5655
6173
|
this.opened = false;
|
|
5656
6174
|
}
|
|
5657
6175
|
}
|
|
6176
|
+
/**
|
|
6177
|
+
* Create or replace a directed memory↔memory edge.
|
|
6178
|
+
*
|
|
6179
|
+
* @param fromId - Source memory UUID.
|
|
6180
|
+
* @param toId - Target memory UUID.
|
|
6181
|
+
* @param relation - Edge label (e.g. `"related_to"`, `"caused_by"`).
|
|
6182
|
+
* @param metadata - Optional JSON metadata stored on the edge.
|
|
6183
|
+
*/
|
|
5658
6184
|
async linkMemories(fromId, toId, relation, metadata) {
|
|
5659
6185
|
const db = this.requireDb();
|
|
5660
6186
|
await this.withTx(() => {
|
|
@@ -5677,6 +6203,13 @@ var SqliteGraphProvider = class {
|
|
|
5677
6203
|
);
|
|
5678
6204
|
});
|
|
5679
6205
|
}
|
|
6206
|
+
/**
|
|
6207
|
+
* Remove edges between two memories.
|
|
6208
|
+
*
|
|
6209
|
+
* @param fromId - Source memory UUID.
|
|
6210
|
+
* @param toId - Target memory UUID.
|
|
6211
|
+
* @param relation - When set, only delete edges with this relation label.
|
|
6212
|
+
*/
|
|
5680
6213
|
async unlinkMemories(fromId, toId, relation) {
|
|
5681
6214
|
const db = this.requireDb();
|
|
5682
6215
|
await this.withTx(() => {
|
|
@@ -5726,6 +6259,12 @@ var SqliteGraphProvider = class {
|
|
|
5726
6259
|
}
|
|
5727
6260
|
return out;
|
|
5728
6261
|
}
|
|
6262
|
+
/**
|
|
6263
|
+
* Insert or update a named entity node.
|
|
6264
|
+
*
|
|
6265
|
+
* @param entity - Entity name, type, and optional metadata.
|
|
6266
|
+
* @returns Stable entity id ({@link entityIdFrom}).
|
|
6267
|
+
*/
|
|
5729
6268
|
async upsertEntity(entity) {
|
|
5730
6269
|
const id = entityIdFrom(entity.name, entity.type);
|
|
5731
6270
|
const db = this.requireDb();
|
|
@@ -5750,6 +6289,13 @@ var SqliteGraphProvider = class {
|
|
|
5750
6289
|
});
|
|
5751
6290
|
return id;
|
|
5752
6291
|
}
|
|
6292
|
+
/**
|
|
6293
|
+
* Link an entity to a memory via a `MENTIONS` edge (not traversed by {@link getRelated}).
|
|
6294
|
+
*
|
|
6295
|
+
* @param entityId - Entity id from {@link upsertEntity}.
|
|
6296
|
+
* @param memoryId - Target memory UUID.
|
|
6297
|
+
* @param role - Optional role string stored on the edge metadata.
|
|
6298
|
+
*/
|
|
5753
6299
|
async linkEntityToMemory(entityId, memoryId, role) {
|
|
5754
6300
|
const db = this.requireDb();
|
|
5755
6301
|
await this.withTx(() => {
|
|
@@ -5779,6 +6325,11 @@ var SqliteGraphProvider = class {
|
|
|
5779
6325
|
);
|
|
5780
6326
|
});
|
|
5781
6327
|
}
|
|
6328
|
+
/**
|
|
6329
|
+
* Remove the memory node and incident edges when a memory is hard-deleted.
|
|
6330
|
+
*
|
|
6331
|
+
* @param memoryId - Wolbarg memory UUID.
|
|
6332
|
+
*/
|
|
5782
6333
|
async deleteMemory(memoryId) {
|
|
5783
6334
|
const db = this.requireDb();
|
|
5784
6335
|
await this.withTx(() => {
|
|
@@ -5798,6 +6349,7 @@ var SqliteGraphProvider = class {
|
|
|
5798
6349
|
}
|
|
5799
6350
|
);
|
|
5800
6351
|
}
|
|
6352
|
+
/** Lightweight connectivity and schema statistics check. */
|
|
5801
6353
|
async health() {
|
|
5802
6354
|
try {
|
|
5803
6355
|
if (!this.opened || !this.db) {
|
|
@@ -5943,6 +6495,9 @@ var Neo4jGraphProvider = class {
|
|
|
5943
6495
|
database;
|
|
5944
6496
|
driver = null;
|
|
5945
6497
|
opened = false;
|
|
6498
|
+
/**
|
|
6499
|
+
* @param options - Bolt URL, credentials, and optional database name.
|
|
6500
|
+
*/
|
|
5946
6501
|
constructor(options) {
|
|
5947
6502
|
if (!options?.url?.trim()) {
|
|
5948
6503
|
throw new ConfigurationError("neo4j graph requires a non-empty url");
|
|
@@ -5958,12 +6513,15 @@ var Neo4jGraphProvider = class {
|
|
|
5958
6513
|
this.password = options.password;
|
|
5959
6514
|
this.database = options.database?.trim() || void 0;
|
|
5960
6515
|
}
|
|
6516
|
+
/** Neo4j does not support local file snapshots for checkpoint pairing. */
|
|
5961
6517
|
supportsFileSnapshot() {
|
|
5962
6518
|
return false;
|
|
5963
6519
|
}
|
|
6520
|
+
/** Always `null` — graph data lives in the remote Neo4j cluster. */
|
|
5964
6521
|
getDataPath() {
|
|
5965
6522
|
return null;
|
|
5966
6523
|
}
|
|
6524
|
+
/** Connect to Neo4j, verify connectivity, and ensure uniqueness constraints. */
|
|
5967
6525
|
async open() {
|
|
5968
6526
|
if (this.opened) return;
|
|
5969
6527
|
let mod;
|
|
@@ -5995,6 +6553,7 @@ var Neo4jGraphProvider = class {
|
|
|
5995
6553
|
}
|
|
5996
6554
|
});
|
|
5997
6555
|
}
|
|
6556
|
+
/** Close the neo4j-driver connection. */
|
|
5998
6557
|
async close() {
|
|
5999
6558
|
if (this.driver) {
|
|
6000
6559
|
await this.driver.close();
|
|
@@ -6021,24 +6580,26 @@ var Neo4jGraphProvider = class {
|
|
|
6021
6580
|
}
|
|
6022
6581
|
}
|
|
6023
6582
|
async run(cypher, params = {}) {
|
|
6024
|
-
return this.withSession(
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6583
|
+
return this.withSession(
|
|
6584
|
+
(session) => this.runOnSession(session, cypher, params)
|
|
6585
|
+
);
|
|
6586
|
+
}
|
|
6587
|
+
async runOnSession(session, cypher, params = {}) {
|
|
6588
|
+
const result = await session.run(cypher, params);
|
|
6589
|
+
return result.records.map((rec) => {
|
|
6590
|
+
try {
|
|
6591
|
+
return rec.toObject();
|
|
6592
|
+
} catch {
|
|
6593
|
+
const obj = {};
|
|
6594
|
+
for (const key of rec.keys) {
|
|
6595
|
+
obj[key] = unwrapNeo4jValue(rec.get(key));
|
|
6035
6596
|
}
|
|
6036
|
-
|
|
6597
|
+
return obj;
|
|
6598
|
+
}
|
|
6037
6599
|
});
|
|
6038
6600
|
}
|
|
6039
|
-
async ensureMemoryNode(id) {
|
|
6040
|
-
|
|
6041
|
-
`MERGE (m:Memory { id: $id })
|
|
6601
|
+
async ensureMemoryNode(id, session) {
|
|
6602
|
+
const cypher = `MERGE (m:Memory { id: $id })
|
|
6042
6603
|
ON CREATE SET
|
|
6043
6604
|
m.organization = '',
|
|
6044
6605
|
m.agent = '',
|
|
@@ -6047,25 +6608,46 @@ var Neo4jGraphProvider = class {
|
|
|
6047
6608
|
m.archived = false,
|
|
6048
6609
|
m.compressed_into = '',
|
|
6049
6610
|
m.created_at = '',
|
|
6050
|
-
m.updated_at = ''
|
|
6051
|
-
|
|
6052
|
-
|
|
6611
|
+
m.updated_at = ''`;
|
|
6612
|
+
if (session) {
|
|
6613
|
+
await this.runOnSession(session, cypher, { id });
|
|
6614
|
+
return;
|
|
6615
|
+
}
|
|
6616
|
+
await this.run(cypher, { id });
|
|
6053
6617
|
}
|
|
6618
|
+
/**
|
|
6619
|
+
* MERGE a directed `RELATED` relationship between two memory nodes.
|
|
6620
|
+
*
|
|
6621
|
+
* @param fromId - Source memory UUID.
|
|
6622
|
+
* @param toId - Target memory UUID.
|
|
6623
|
+
* @param relation - Relationship label stored on `RELATED.relation`.
|
|
6624
|
+
* @param metadata - Optional JSON metadata on the relationship.
|
|
6625
|
+
*/
|
|
6054
6626
|
async linkMemories(fromId, toId, relation, metadata) {
|
|
6055
|
-
await this.
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
6627
|
+
await this.withSession(async (session) => {
|
|
6628
|
+
await this.ensureMemoryNode(fromId, session);
|
|
6629
|
+
await this.ensureMemoryNode(toId, session);
|
|
6630
|
+
await this.runOnSession(
|
|
6631
|
+
session,
|
|
6632
|
+
`MATCH (a:Memory { id: $fromId }), (b:Memory { id: $toId })
|
|
6633
|
+
MERGE (a)-[r:RELATED { relation: $relation }]->(b)
|
|
6634
|
+
SET r.metadata_json = $metadata`,
|
|
6635
|
+
{
|
|
6636
|
+
fromId,
|
|
6637
|
+
toId,
|
|
6638
|
+
relation,
|
|
6639
|
+
metadata: serializeMetadata2(metadata)
|
|
6640
|
+
}
|
|
6641
|
+
);
|
|
6642
|
+
});
|
|
6068
6643
|
}
|
|
6644
|
+
/**
|
|
6645
|
+
* Delete `RELATED` relationship(s) between two memories.
|
|
6646
|
+
*
|
|
6647
|
+
* @param fromId - Source memory UUID.
|
|
6648
|
+
* @param toId - Target memory UUID.
|
|
6649
|
+
* @param relation - When set, only delete edges with this relation property.
|
|
6650
|
+
*/
|
|
6069
6651
|
async unlinkMemories(fromId, toId, relation) {
|
|
6070
6652
|
if (relation !== void 0) {
|
|
6071
6653
|
await this.run(
|
|
@@ -6111,6 +6693,12 @@ var Neo4jGraphProvider = class {
|
|
|
6111
6693
|
(row) => rowToMemoryRecord(unwrapRow(row))
|
|
6112
6694
|
);
|
|
6113
6695
|
}
|
|
6696
|
+
/**
|
|
6697
|
+
* MERGE an `Entity` node keyed by deterministic id.
|
|
6698
|
+
*
|
|
6699
|
+
* @param entity - Entity name, type, and optional metadata.
|
|
6700
|
+
* @returns Stable entity id ({@link entityIdFrom}).
|
|
6701
|
+
*/
|
|
6114
6702
|
async upsertEntity(entity) {
|
|
6115
6703
|
const id = entityIdFrom(entity.name, entity.type);
|
|
6116
6704
|
await this.run(
|
|
@@ -6125,24 +6713,40 @@ var Neo4jGraphProvider = class {
|
|
|
6125
6713
|
);
|
|
6126
6714
|
return id;
|
|
6127
6715
|
}
|
|
6716
|
+
/**
|
|
6717
|
+
* MERGE a `MENTIONS` relationship from entity to memory.
|
|
6718
|
+
*
|
|
6719
|
+
* @param entityId - Entity id from {@link upsertEntity}.
|
|
6720
|
+
* @param memoryId - Target memory UUID.
|
|
6721
|
+
* @param role - Optional role string on the relationship.
|
|
6722
|
+
*/
|
|
6128
6723
|
async linkEntityToMemory(entityId, memoryId, role) {
|
|
6129
|
-
await this.
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
6724
|
+
await this.withSession(async (session) => {
|
|
6725
|
+
await this.ensureMemoryNode(memoryId, session);
|
|
6726
|
+
const found = await this.runOnSession(
|
|
6727
|
+
session,
|
|
6728
|
+
`MATCH (e:Entity { id: $id }) RETURN e.id AS id`,
|
|
6729
|
+
{ id: entityId }
|
|
6730
|
+
);
|
|
6731
|
+
if (found.length === 0) {
|
|
6732
|
+
throw new DatabaseError(`Entity not found: ${entityId}`, {
|
|
6733
|
+
operation: "linkEntityToMemory"
|
|
6734
|
+
});
|
|
6735
|
+
}
|
|
6736
|
+
await this.runOnSession(
|
|
6737
|
+
session,
|
|
6738
|
+
`MATCH (e:Entity { id: $entityId }), (m:Memory { id: $memoryId })
|
|
6739
|
+
MERGE (e)-[r:MENTIONS]->(m)
|
|
6740
|
+
SET r.role = $role`,
|
|
6741
|
+
{ entityId, memoryId, role: role ?? "" }
|
|
6742
|
+
);
|
|
6743
|
+
});
|
|
6145
6744
|
}
|
|
6745
|
+
/**
|
|
6746
|
+
* DETACH DELETE the memory node and all incident relationships.
|
|
6747
|
+
*
|
|
6748
|
+
* @param memoryId - Wolbarg memory UUID.
|
|
6749
|
+
*/
|
|
6146
6750
|
async deleteMemory(memoryId) {
|
|
6147
6751
|
await this.run(
|
|
6148
6752
|
`MATCH (m:Memory { id: $id })
|
|
@@ -6150,9 +6754,17 @@ var Neo4jGraphProvider = class {
|
|
|
6150
6754
|
{ id: memoryId }
|
|
6151
6755
|
);
|
|
6152
6756
|
}
|
|
6757
|
+
/**
|
|
6758
|
+
* Run arbitrary parameterized Cypher (escape hatch for advanced graph queries).
|
|
6759
|
+
*
|
|
6760
|
+
* @param cypher - Cypher query string.
|
|
6761
|
+
* @param params - Bound parameters for the query.
|
|
6762
|
+
* @returns Raw row objects from the driver.
|
|
6763
|
+
*/
|
|
6153
6764
|
async query(cypher, params) {
|
|
6154
6765
|
return this.run(cypher, params ?? {});
|
|
6155
6766
|
}
|
|
6767
|
+
/** Verify connectivity and return server metadata when available. */
|
|
6156
6768
|
async health() {
|
|
6157
6769
|
try {
|
|
6158
6770
|
if (!this.opened || !this.driver) {
|
|
@@ -6214,6 +6826,21 @@ function assertNonEmpty(value, fieldName) {
|
|
|
6214
6826
|
throw new ConfigurationError(`${fieldName} must be a non-empty string`);
|
|
6215
6827
|
}
|
|
6216
6828
|
}
|
|
6829
|
+
function assertFiniteNumber2(value, fieldName) {
|
|
6830
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
6831
|
+
throw new ConfigurationError(`${fieldName} must be a finite number`);
|
|
6832
|
+
}
|
|
6833
|
+
}
|
|
6834
|
+
function assertNumberMin(value, fieldName, min) {
|
|
6835
|
+
if (!Number.isFinite(value) || value < min) {
|
|
6836
|
+
throw new ConfigurationError(`${fieldName} must be >= ${min}`);
|
|
6837
|
+
}
|
|
6838
|
+
}
|
|
6839
|
+
function assertNumberInRange(value, fieldName, min, max) {
|
|
6840
|
+
if (!Number.isFinite(value) || value < min || value > max) {
|
|
6841
|
+
throw new ConfigurationError(`${fieldName} must be between ${min} and ${max}`);
|
|
6842
|
+
}
|
|
6843
|
+
}
|
|
6217
6844
|
function assertUrl(value, fieldName) {
|
|
6218
6845
|
assertNonEmpty(value, fieldName);
|
|
6219
6846
|
try {
|
|
@@ -6260,6 +6887,135 @@ function validateLlmConfig(config) {
|
|
|
6260
6887
|
...config.timeoutMs !== void 0 ? { timeoutMs: config.timeoutMs } : {}
|
|
6261
6888
|
};
|
|
6262
6889
|
}
|
|
6890
|
+
function validateRetrievalConfig(config) {
|
|
6891
|
+
if (config === null || typeof config !== "object") {
|
|
6892
|
+
throw new ConfigurationError("retrieval must be an object");
|
|
6893
|
+
}
|
|
6894
|
+
const out = {};
|
|
6895
|
+
if (config.overFetchFactor !== void 0) {
|
|
6896
|
+
assertFiniteNumber2(config.overFetchFactor, "retrieval.overFetchFactor");
|
|
6897
|
+
assertNumberMin(config.overFetchFactor, "retrieval.overFetchFactor", 0);
|
|
6898
|
+
if (config.overFetchFactor <= 0) {
|
|
6899
|
+
throw new ConfigurationError("retrieval.overFetchFactor must be > 0");
|
|
6900
|
+
}
|
|
6901
|
+
out.overFetchFactor = config.overFetchFactor;
|
|
6902
|
+
}
|
|
6903
|
+
if (config.hybrid !== void 0) {
|
|
6904
|
+
if (config.hybrid === null || typeof config.hybrid !== "object") {
|
|
6905
|
+
throw new ConfigurationError("retrieval.hybrid must be an object");
|
|
6906
|
+
}
|
|
6907
|
+
const hybrid = config.hybrid;
|
|
6908
|
+
const outHybrid = {};
|
|
6909
|
+
if (hybrid.semanticWeight !== void 0) {
|
|
6910
|
+
assertFiniteNumber2(hybrid.semanticWeight, "retrieval.hybrid.semanticWeight");
|
|
6911
|
+
if (hybrid.semanticWeight < 0) {
|
|
6912
|
+
throw new ConfigurationError(
|
|
6913
|
+
"retrieval.hybrid.semanticWeight must be >= 0"
|
|
6914
|
+
);
|
|
6915
|
+
}
|
|
6916
|
+
outHybrid.semanticWeight = hybrid.semanticWeight;
|
|
6917
|
+
}
|
|
6918
|
+
if (hybrid.keywordWeight !== void 0) {
|
|
6919
|
+
assertFiniteNumber2(hybrid.keywordWeight, "retrieval.hybrid.keywordWeight");
|
|
6920
|
+
if (hybrid.keywordWeight < 0) {
|
|
6921
|
+
throw new ConfigurationError(
|
|
6922
|
+
"retrieval.hybrid.keywordWeight must be >= 0"
|
|
6923
|
+
);
|
|
6924
|
+
}
|
|
6925
|
+
outHybrid.keywordWeight = hybrid.keywordWeight;
|
|
6926
|
+
}
|
|
6927
|
+
if (Object.keys(outHybrid).length > 0) {
|
|
6928
|
+
out.hybrid = outHybrid;
|
|
6929
|
+
}
|
|
6930
|
+
}
|
|
6931
|
+
if (config.mmr !== void 0) {
|
|
6932
|
+
if (config.mmr === null || typeof config.mmr !== "object") {
|
|
6933
|
+
throw new ConfigurationError("retrieval.mmr must be an object");
|
|
6934
|
+
}
|
|
6935
|
+
const mmr = config.mmr;
|
|
6936
|
+
const outMmr = {};
|
|
6937
|
+
if (mmr.lambda !== void 0) {
|
|
6938
|
+
assertFiniteNumber2(mmr.lambda, "retrieval.mmr.lambda");
|
|
6939
|
+
assertNumberInRange(mmr.lambda, "retrieval.mmr.lambda", 0, 1);
|
|
6940
|
+
outMmr.lambda = mmr.lambda;
|
|
6941
|
+
}
|
|
6942
|
+
if (Object.keys(outMmr).length > 0) {
|
|
6943
|
+
out.mmr = outMmr;
|
|
6944
|
+
}
|
|
6945
|
+
}
|
|
6946
|
+
return out;
|
|
6947
|
+
}
|
|
6948
|
+
function validateMemoryDedupeConfig(config) {
|
|
6949
|
+
if (config === null || typeof config !== "object") {
|
|
6950
|
+
throw new ConfigurationError("memory.dedupe must be an object");
|
|
6951
|
+
}
|
|
6952
|
+
const out = {};
|
|
6953
|
+
if (config.enabled !== void 0) {
|
|
6954
|
+
if (typeof config.enabled !== "boolean") {
|
|
6955
|
+
throw new ConfigurationError("memory.dedupe.enabled must be a boolean");
|
|
6956
|
+
}
|
|
6957
|
+
out.enabled = config.enabled;
|
|
6958
|
+
}
|
|
6959
|
+
if (config.strategy !== void 0) {
|
|
6960
|
+
const allowed = /* @__PURE__ */ new Set(["exact", "near", "exact-or-near"]);
|
|
6961
|
+
if (!allowed.has(config.strategy)) {
|
|
6962
|
+
throw new ConfigurationError(
|
|
6963
|
+
`memory.dedupe.strategy must be one of ${Array.from(allowed).join(", ")}`
|
|
6964
|
+
);
|
|
6965
|
+
}
|
|
6966
|
+
out.strategy = config.strategy;
|
|
6967
|
+
}
|
|
6968
|
+
if (config.nearThreshold !== void 0) {
|
|
6969
|
+
assertFiniteNumber2(config.nearThreshold, "memory.dedupe.nearThreshold");
|
|
6970
|
+
assertNumberInRange(
|
|
6971
|
+
config.nearThreshold,
|
|
6972
|
+
"memory.dedupe.nearThreshold",
|
|
6973
|
+
0,
|
|
6974
|
+
1
|
|
6975
|
+
);
|
|
6976
|
+
out.nearThreshold = config.nearThreshold;
|
|
6977
|
+
}
|
|
6978
|
+
if (config.nearCandidateLimit !== void 0) {
|
|
6979
|
+
assertFiniteNumber2(
|
|
6980
|
+
config.nearCandidateLimit,
|
|
6981
|
+
"memory.dedupe.nearCandidateLimit"
|
|
6982
|
+
);
|
|
6983
|
+
assertNumberMin(
|
|
6984
|
+
config.nearCandidateLimit,
|
|
6985
|
+
"memory.dedupe.nearCandidateLimit",
|
|
6986
|
+
1
|
|
6987
|
+
);
|
|
6988
|
+
out.nearCandidateLimit = config.nearCandidateLimit;
|
|
6989
|
+
}
|
|
6990
|
+
return out;
|
|
6991
|
+
}
|
|
6992
|
+
function validateEmbeddingCacheConfig(config) {
|
|
6993
|
+
if (config === null || typeof config !== "object") {
|
|
6994
|
+
throw new ConfigurationError("embeddingCache must be an object");
|
|
6995
|
+
}
|
|
6996
|
+
const out = {};
|
|
6997
|
+
if (config.enabled !== void 0) {
|
|
6998
|
+
if (typeof config.enabled !== "boolean") {
|
|
6999
|
+
throw new ConfigurationError("embeddingCache.enabled must be a boolean");
|
|
7000
|
+
}
|
|
7001
|
+
out.enabled = config.enabled;
|
|
7002
|
+
}
|
|
7003
|
+
if (config.ttlMs !== void 0) {
|
|
7004
|
+
assertFiniteNumber2(config.ttlMs, "embeddingCache.ttlMs");
|
|
7005
|
+
if (config.ttlMs < 0) {
|
|
7006
|
+
throw new ConfigurationError("embeddingCache.ttlMs must be >= 0");
|
|
7007
|
+
}
|
|
7008
|
+
out.ttlMs = config.ttlMs;
|
|
7009
|
+
}
|
|
7010
|
+
if (config.maxEntries !== void 0) {
|
|
7011
|
+
assertFiniteNumber2(config.maxEntries, "embeddingCache.maxEntries");
|
|
7012
|
+
if (config.maxEntries < 0) {
|
|
7013
|
+
throw new ConfigurationError("embeddingCache.maxEntries must be >= 0");
|
|
7014
|
+
}
|
|
7015
|
+
out.maxEntries = config.maxEntries;
|
|
7016
|
+
}
|
|
7017
|
+
return out;
|
|
7018
|
+
}
|
|
6263
7019
|
function normalizeDatabaseConfig(config) {
|
|
6264
7020
|
const provider = config.provider;
|
|
6265
7021
|
if (provider !== "sqlite" && provider !== "postgres") {
|
|
@@ -6369,11 +7125,22 @@ function validateWolbargOptions(options) {
|
|
|
6369
7125
|
throw new ConfigurationError("Wolbarg options must be an object");
|
|
6370
7126
|
}
|
|
6371
7127
|
assertNonEmpty(options.organization, "organization");
|
|
7128
|
+
const checkpointDirectory = options.checkpointDirectory !== void 0 ? (() => {
|
|
7129
|
+
assertNonEmpty(options.checkpointDirectory, "checkpointDirectory");
|
|
7130
|
+
return options.checkpointDirectory.trim();
|
|
7131
|
+
})() : void 0;
|
|
6372
7132
|
const storageInput = resolveStorageInput(options);
|
|
6373
7133
|
let storage = storageInput;
|
|
6374
7134
|
if (!isStorageProvider(storageInput)) {
|
|
6375
7135
|
storage = normalizeDatabaseConfig(storageInput);
|
|
6376
7136
|
}
|
|
7137
|
+
if (!isStorageProvider(storageInput) && storageInput.provider === "postgres" && storageInput.maxPoolSize !== void 0) {
|
|
7138
|
+
assertFiniteNumber2(
|
|
7139
|
+
storageInput.maxPoolSize,
|
|
7140
|
+
"database.maxPoolSize"
|
|
7141
|
+
);
|
|
7142
|
+
assertNumberMin(storageInput.maxPoolSize, "database.maxPoolSize", 1);
|
|
7143
|
+
}
|
|
6377
7144
|
if (!options.embedding) {
|
|
6378
7145
|
throw new ConfigurationError("embedding is required");
|
|
6379
7146
|
}
|
|
@@ -6391,13 +7158,20 @@ function validateWolbargOptions(options) {
|
|
|
6391
7158
|
if (graph !== void 0) {
|
|
6392
7159
|
graph = resolveGraphInput(graph);
|
|
6393
7160
|
}
|
|
7161
|
+
const retrieval = options.retrieval !== void 0 ? validateRetrievalConfig(options.retrieval) : void 0;
|
|
7162
|
+
const embeddingCache = options.embeddingCache !== void 0 ? validateEmbeddingCacheConfig(options.embeddingCache) : void 0;
|
|
7163
|
+
const memory = options.memory !== void 0 && options.memory.dedupe !== void 0 ? { ...options.memory, dedupe: validateMemoryDedupeConfig(options.memory.dedupe) } : options.memory;
|
|
6394
7164
|
return {
|
|
6395
7165
|
...options,
|
|
6396
7166
|
organization: options.organization.trim(),
|
|
6397
7167
|
storage,
|
|
6398
7168
|
database: void 0,
|
|
6399
7169
|
...telemetry ? { telemetry } : {},
|
|
6400
|
-
...graph !== void 0 ? { graph } : {}
|
|
7170
|
+
...graph !== void 0 ? { graph } : {},
|
|
7171
|
+
...retrieval !== void 0 ? { retrieval } : {},
|
|
7172
|
+
...embeddingCache !== void 0 ? { embeddingCache } : {},
|
|
7173
|
+
...checkpointDirectory !== void 0 ? { checkpointDirectory } : {},
|
|
7174
|
+
...memory !== void 0 ? { memory } : {}
|
|
6401
7175
|
};
|
|
6402
7176
|
}
|
|
6403
7177
|
function resolveGraphInput(input) {
|
|
@@ -6456,59 +7230,6 @@ function resolveGraphInput(input) {
|
|
|
6456
7230
|
);
|
|
6457
7231
|
}
|
|
6458
7232
|
|
|
6459
|
-
// src/telemetry/logger.ts
|
|
6460
|
-
var LEVEL_ORDER = {
|
|
6461
|
-
off: 100,
|
|
6462
|
-
error: 50,
|
|
6463
|
-
warn: 40,
|
|
6464
|
-
info: 30,
|
|
6465
|
-
debug: 20,
|
|
6466
|
-
trace: 10
|
|
6467
|
-
};
|
|
6468
|
-
var WolbargLogger = class {
|
|
6469
|
-
constructor(level = "info") {
|
|
6470
|
-
this.level = level;
|
|
6471
|
-
}
|
|
6472
|
-
level;
|
|
6473
|
-
setLevel(level) {
|
|
6474
|
-
this.level = level;
|
|
6475
|
-
}
|
|
6476
|
-
getLevel() {
|
|
6477
|
-
return this.level;
|
|
6478
|
-
}
|
|
6479
|
-
error(message, extra) {
|
|
6480
|
-
this.write("error", message, extra);
|
|
6481
|
-
}
|
|
6482
|
-
warn(message, extra) {
|
|
6483
|
-
this.write("warn", message, extra);
|
|
6484
|
-
}
|
|
6485
|
-
info(message, extra) {
|
|
6486
|
-
this.write("info", message, extra);
|
|
6487
|
-
}
|
|
6488
|
-
debug(message, extra) {
|
|
6489
|
-
this.write("debug", message, extra);
|
|
6490
|
-
}
|
|
6491
|
-
trace(message, extra) {
|
|
6492
|
-
this.write("trace", message, extra);
|
|
6493
|
-
}
|
|
6494
|
-
write(level, message, extra) {
|
|
6495
|
-
if (LEVEL_ORDER[level] < LEVEL_ORDER[this.level]) {
|
|
6496
|
-
return;
|
|
6497
|
-
}
|
|
6498
|
-
if (this.level === "off") {
|
|
6499
|
-
return;
|
|
6500
|
-
}
|
|
6501
|
-
const line = `[wolbarg:${level}] ${message}`;
|
|
6502
|
-
if (level === "error") {
|
|
6503
|
-
console.error(line, extra ?? "");
|
|
6504
|
-
} else if (level === "warn") {
|
|
6505
|
-
console.warn(line, extra ?? "");
|
|
6506
|
-
} else {
|
|
6507
|
-
console.log(line, extra ?? "");
|
|
6508
|
-
}
|
|
6509
|
-
}
|
|
6510
|
-
};
|
|
6511
|
-
|
|
6512
7233
|
// src/telemetry/trace.ts
|
|
6513
7234
|
function createSessionId() {
|
|
6514
7235
|
return createId();
|
|
@@ -6534,21 +7255,32 @@ function createChildTrace(parent) {
|
|
|
6534
7255
|
// src/telemetry/emitter.ts
|
|
6535
7256
|
var NoopTelemetryProvider = class {
|
|
6536
7257
|
name = "noop";
|
|
7258
|
+
/** @inheritdoc TelemetryProvider.open */
|
|
6537
7259
|
async open() {
|
|
6538
7260
|
}
|
|
7261
|
+
/** @inheritdoc TelemetryProvider.close */
|
|
6539
7262
|
async close() {
|
|
6540
7263
|
}
|
|
7264
|
+
/** @inheritdoc TelemetryProvider.emit */
|
|
6541
7265
|
emit(_event) {
|
|
6542
7266
|
}
|
|
7267
|
+
/** @inheritdoc TelemetryProvider.flush */
|
|
6543
7268
|
async flush() {
|
|
6544
7269
|
}
|
|
6545
7270
|
};
|
|
6546
7271
|
var TelemetryEmitter = class {
|
|
7272
|
+
/** Stable session id for this Wolbarg instance lifetime. */
|
|
6547
7273
|
sessionId;
|
|
7274
|
+
/** Structured logger gated by telemetry log level. */
|
|
6548
7275
|
logger;
|
|
6549
7276
|
provider;
|
|
6550
7277
|
config;
|
|
6551
7278
|
context;
|
|
7279
|
+
/**
|
|
7280
|
+
* @param provider - Telemetry backend, or `null` for {@link NoopTelemetryProvider}.
|
|
7281
|
+
* @param config - Capture flags and log level.
|
|
7282
|
+
* @param context - Default organization injected into events.
|
|
7283
|
+
*/
|
|
6552
7284
|
constructor(provider, config, context = {}) {
|
|
6553
7285
|
this.sessionId = createSessionId();
|
|
6554
7286
|
this.provider = provider ?? new NoopTelemetryProvider();
|
|
@@ -6564,25 +7296,37 @@ var TelemetryEmitter = class {
|
|
|
6564
7296
|
this.context = context;
|
|
6565
7297
|
this.logger = new WolbargLogger(this.config.level);
|
|
6566
7298
|
}
|
|
7299
|
+
/** Whether telemetry is enabled and backed by a non-noop provider. */
|
|
6567
7300
|
get enabled() {
|
|
6568
7301
|
return this.config.enabled && this.provider.name !== "noop";
|
|
6569
7302
|
}
|
|
7303
|
+
/** Replace the underlying telemetry provider (e.g. after lazy init). */
|
|
6570
7304
|
setProvider(provider) {
|
|
6571
7305
|
this.provider = provider;
|
|
6572
7306
|
}
|
|
7307
|
+
/** Open the underlying telemetry provider when enabled. */
|
|
6573
7308
|
async open() {
|
|
6574
7309
|
if (!this.config.enabled) {
|
|
6575
7310
|
return;
|
|
6576
7311
|
}
|
|
6577
7312
|
await this.provider.open();
|
|
6578
7313
|
}
|
|
7314
|
+
/** Flush pending events and close the telemetry provider. */
|
|
6579
7315
|
async close() {
|
|
6580
7316
|
await this.provider.flush();
|
|
6581
7317
|
await this.provider.close();
|
|
6582
7318
|
}
|
|
7319
|
+
/** Flush pending telemetry events without closing the provider. */
|
|
6583
7320
|
async flush() {
|
|
6584
7321
|
await this.provider.flush();
|
|
6585
7322
|
}
|
|
7323
|
+
/**
|
|
7324
|
+
* Begin tracing an operation.
|
|
7325
|
+
*
|
|
7326
|
+
* @param operation - Public telemetry operation name.
|
|
7327
|
+
* @param parent - Optional parent trace for nested spans.
|
|
7328
|
+
* @returns Handle — call `success()` or `failure()` when done.
|
|
7329
|
+
*/
|
|
6586
7330
|
start(operation, parent) {
|
|
6587
7331
|
const context = parent ? createChildTrace(parent) : createRootTrace(this.sessionId);
|
|
6588
7332
|
const startedAt = performance.now();
|
|
@@ -6593,14 +7337,14 @@ var TelemetryEmitter = class {
|
|
|
6593
7337
|
const totalMs = performance.now() - startedAt;
|
|
6594
7338
|
const errMessage = error instanceof Error ? error.message : error ? String(error) : fields?.error ?? null;
|
|
6595
7339
|
const errStack = error instanceof Error ? error.stack ?? null : fields?.errorStack ?? null;
|
|
7340
|
+
if (!self.config.enabled) {
|
|
7341
|
+
return;
|
|
7342
|
+
}
|
|
6596
7343
|
if (status === "error") {
|
|
6597
7344
|
self.logger.error(`${operation} failed: ${errMessage ?? "unknown"}`);
|
|
6598
7345
|
} else {
|
|
6599
7346
|
self.logger.debug(`${operation} completed in ${totalMs.toFixed(2)}ms`);
|
|
6600
7347
|
}
|
|
6601
|
-
if (!self.config.enabled) {
|
|
6602
|
-
return;
|
|
6603
|
-
}
|
|
6604
7348
|
const event = {
|
|
6605
7349
|
id: createId(),
|
|
6606
7350
|
timestamp: nowIso(),
|
|
@@ -6657,10 +7401,12 @@ var TelemetryEmitter = class {
|
|
|
6657
7401
|
}
|
|
6658
7402
|
};
|
|
6659
7403
|
}
|
|
7404
|
+
/** Emit a startup telemetry event. */
|
|
6660
7405
|
emitStartup(provider) {
|
|
6661
7406
|
const trace = this.start("startup");
|
|
6662
7407
|
trace.success({ provider });
|
|
6663
7408
|
}
|
|
7409
|
+
/** Emit a shutdown telemetry event. */
|
|
6664
7410
|
emitShutdown(provider) {
|
|
6665
7411
|
const trace = this.start("shutdown");
|
|
6666
7412
|
trace.success({ provider });
|
|
@@ -6726,10 +7472,15 @@ var SqliteEventDatabase = class {
|
|
|
6726
7472
|
db = null;
|
|
6727
7473
|
insertStmt = null;
|
|
6728
7474
|
columns = /* @__PURE__ */ new Set();
|
|
7475
|
+
/**
|
|
7476
|
+
* @param options.url - Path to the telemetry SQLite file.
|
|
7477
|
+
* @param options.readonly - Open read-only (Studio / analytics).
|
|
7478
|
+
*/
|
|
6729
7479
|
constructor(options) {
|
|
6730
7480
|
this.url = options.url;
|
|
6731
7481
|
this.readonly = options.readonly ?? false;
|
|
6732
7482
|
}
|
|
7483
|
+
/** Open the telemetry database and run schema migrations. */
|
|
6733
7484
|
async open() {
|
|
6734
7485
|
try {
|
|
6735
7486
|
const dbPath = this.resolvePath(this.url);
|
|
@@ -6784,6 +7535,7 @@ var SqliteEventDatabase = class {
|
|
|
6784
7535
|
);
|
|
6785
7536
|
}
|
|
6786
7537
|
}
|
|
7538
|
+
/** Close the telemetry database connection. */
|
|
6787
7539
|
async close() {
|
|
6788
7540
|
if (!this.db) return;
|
|
6789
7541
|
try {
|
|
@@ -6794,6 +7546,7 @@ var SqliteEventDatabase = class {
|
|
|
6794
7546
|
this.columns.clear();
|
|
6795
7547
|
}
|
|
6796
7548
|
}
|
|
7549
|
+
/** Insert one normalized telemetry event row. */
|
|
6797
7550
|
async insertEvent(input) {
|
|
6798
7551
|
const event = normalizeEvent(input);
|
|
6799
7552
|
const stmt = this.insertStmt;
|
|
@@ -6839,6 +7592,7 @@ var SqliteEventDatabase = class {
|
|
|
6839
7592
|
);
|
|
6840
7593
|
}
|
|
6841
7594
|
}
|
|
7595
|
+
/** Insert many events in a single transaction. */
|
|
6842
7596
|
async insertEvents(inputs) {
|
|
6843
7597
|
const db = this.requireDb();
|
|
6844
7598
|
const out = [];
|
|
@@ -6857,6 +7611,7 @@ var SqliteEventDatabase = class {
|
|
|
6857
7611
|
throw error;
|
|
6858
7612
|
}
|
|
6859
7613
|
}
|
|
7614
|
+
/** Query telemetry events with filters, sort, and pagination. */
|
|
6860
7615
|
async query(options) {
|
|
6861
7616
|
const db = this.requireDb();
|
|
6862
7617
|
const clauses = [];
|
|
@@ -6902,8 +7657,10 @@ var SqliteEventDatabase = class {
|
|
|
6902
7657
|
}
|
|
6903
7658
|
}
|
|
6904
7659
|
if (options.memoryId) {
|
|
6905
|
-
clauses.push(
|
|
6906
|
-
|
|
7660
|
+
clauses.push(
|
|
7661
|
+
`EXISTS (SELECT 1 FROM json_each(memory_ids_json) WHERE json_each.value = ?)`
|
|
7662
|
+
);
|
|
7663
|
+
params.push(options.memoryId);
|
|
6907
7664
|
}
|
|
6908
7665
|
if (options.queryText) {
|
|
6909
7666
|
clauses.push(`query LIKE ?`);
|
|
@@ -6935,11 +7692,13 @@ var SqliteEventDatabase = class {
|
|
|
6935
7692
|
offset
|
|
6936
7693
|
};
|
|
6937
7694
|
}
|
|
7695
|
+
/** Fetch one event by primary key. */
|
|
6938
7696
|
async getEvent(id) {
|
|
6939
7697
|
const db = this.requireDb();
|
|
6940
7698
|
const row = db.prepare(`SELECT * FROM telemetry_events WHERE id = ?`).get(id);
|
|
6941
7699
|
return row ? rowToEvent(row) : null;
|
|
6942
7700
|
}
|
|
7701
|
+
/** Count events optionally filtered by time and operation. */
|
|
6943
7702
|
async countEvents(filter) {
|
|
6944
7703
|
const db = this.requireDb();
|
|
6945
7704
|
const clauses = [];
|
|
@@ -7080,6 +7839,7 @@ function describe2(error) {
|
|
|
7080
7839
|
}
|
|
7081
7840
|
|
|
7082
7841
|
// src/providers/sqlite/sqliteTelemetryProvider.ts
|
|
7842
|
+
var MAX_QUEUE_SIZE = 1e4;
|
|
7083
7843
|
var SqliteTelemetryProvider = class {
|
|
7084
7844
|
name = "sqlite";
|
|
7085
7845
|
db;
|
|
@@ -7087,9 +7847,15 @@ var SqliteTelemetryProvider = class {
|
|
|
7087
7847
|
flushing = null;
|
|
7088
7848
|
closed = false;
|
|
7089
7849
|
openPromise = null;
|
|
7850
|
+
warnedQueueDrop = false;
|
|
7851
|
+
warnedFlushFailure = false;
|
|
7852
|
+
/**
|
|
7853
|
+
* @param options.url - Path to the independent telemetry SQLite database file.
|
|
7854
|
+
*/
|
|
7090
7855
|
constructor(options) {
|
|
7091
7856
|
this.db = new SqliteEventDatabase({ url: options.url });
|
|
7092
7857
|
}
|
|
7858
|
+
/** Open the telemetry EventDatabase (lazy — also invoked on first emit). */
|
|
7093
7859
|
async open() {
|
|
7094
7860
|
if (!this.openPromise) {
|
|
7095
7861
|
this.openPromise = this.db.open();
|
|
@@ -7097,19 +7863,35 @@ var SqliteTelemetryProvider = class {
|
|
|
7097
7863
|
await this.openPromise;
|
|
7098
7864
|
this.closed = false;
|
|
7099
7865
|
}
|
|
7866
|
+
/** Flush pending events and close the telemetry database. */
|
|
7100
7867
|
async close() {
|
|
7101
7868
|
this.closed = true;
|
|
7102
7869
|
await this.flush();
|
|
7103
7870
|
await this.db.close();
|
|
7104
7871
|
this.openPromise = null;
|
|
7105
7872
|
}
|
|
7873
|
+
/**
|
|
7874
|
+
* Enqueue a telemetry event for async persistence (never throws).
|
|
7875
|
+
*
|
|
7876
|
+
* @param event - Partial or complete {@link TelemetryEventInput}.
|
|
7877
|
+
*/
|
|
7106
7878
|
emit(event) {
|
|
7107
7879
|
if (this.closed) {
|
|
7108
7880
|
return;
|
|
7109
7881
|
}
|
|
7882
|
+
if (this.queue.length >= MAX_QUEUE_SIZE) {
|
|
7883
|
+
this.queue.shift();
|
|
7884
|
+
if (!this.warnedQueueDrop) {
|
|
7885
|
+
this.warnedQueueDrop = true;
|
|
7886
|
+
console.warn(
|
|
7887
|
+
`[wolbarg telemetry] event queue capped at ${MAX_QUEUE_SIZE}; dropping oldest events`
|
|
7888
|
+
);
|
|
7889
|
+
}
|
|
7890
|
+
}
|
|
7110
7891
|
this.queue.push(event);
|
|
7111
7892
|
void this.scheduleFlush();
|
|
7112
7893
|
}
|
|
7894
|
+
/** Block until all queued events are written (or dropped on failure). */
|
|
7113
7895
|
async flush() {
|
|
7114
7896
|
while (this.queue.length > 0 || this.flushing) {
|
|
7115
7897
|
await this.scheduleFlush();
|
|
@@ -7118,10 +7900,12 @@ var SqliteTelemetryProvider = class {
|
|
|
7118
7900
|
}
|
|
7119
7901
|
}
|
|
7120
7902
|
}
|
|
7903
|
+
/** Query persisted telemetry events with filters and pagination. */
|
|
7121
7904
|
async query(options) {
|
|
7122
7905
|
await this.open();
|
|
7123
7906
|
return this.db.query(options);
|
|
7124
7907
|
}
|
|
7908
|
+
/** Fetch a single telemetry event by id. */
|
|
7125
7909
|
async getEvent(id) {
|
|
7126
7910
|
await this.open();
|
|
7127
7911
|
return this.db.getEvent(id);
|
|
@@ -7149,7 +7933,13 @@ var SqliteTelemetryProvider = class {
|
|
|
7149
7933
|
await this.db.insertEvent(event);
|
|
7150
7934
|
}
|
|
7151
7935
|
}
|
|
7152
|
-
} catch {
|
|
7936
|
+
} catch (error) {
|
|
7937
|
+
if (!this.warnedFlushFailure) {
|
|
7938
|
+
this.warnedFlushFailure = true;
|
|
7939
|
+
console.warn(
|
|
7940
|
+
`[wolbarg telemetry] flush failed; dropping batch: ${error instanceof Error ? error.message : String(error)}`
|
|
7941
|
+
);
|
|
7942
|
+
}
|
|
7153
7943
|
}
|
|
7154
7944
|
}
|
|
7155
7945
|
};
|
|
@@ -7157,16 +7947,28 @@ var SqliteCheckpointProvider = class {
|
|
|
7157
7947
|
name = "sqlite";
|
|
7158
7948
|
directory;
|
|
7159
7949
|
ready = false;
|
|
7950
|
+
/**
|
|
7951
|
+
* @param options.directory - Folder for `.json` metadata and `.db` snapshot files.
|
|
7952
|
+
*/
|
|
7160
7953
|
constructor(options) {
|
|
7161
7954
|
this.directory = options?.directory ?? path7.resolve(process.cwd(), ".wolbarg", "checkpoints");
|
|
7162
7955
|
}
|
|
7956
|
+
/** Ensure the checkpoint directory exists. */
|
|
7163
7957
|
async open() {
|
|
7164
7958
|
fs6.mkdirSync(this.directory, { recursive: true });
|
|
7165
7959
|
this.ready = true;
|
|
7166
7960
|
}
|
|
7961
|
+
/** Mark the provider closed (does not delete snapshots). */
|
|
7167
7962
|
async close() {
|
|
7168
7963
|
this.ready = false;
|
|
7169
7964
|
}
|
|
7965
|
+
/**
|
|
7966
|
+
* Create an immutable SQLite backup of `sourcePath` under a unique name.
|
|
7967
|
+
*
|
|
7968
|
+
* @param name - Checkpoint label (must not already exist).
|
|
7969
|
+
* @param sourcePath - Live memory database path to snapshot.
|
|
7970
|
+
* @param options.description - Optional human-readable description stored in metadata.
|
|
7971
|
+
*/
|
|
7170
7972
|
async checkpoint(name, sourcePath, options) {
|
|
7171
7973
|
this.requireReady();
|
|
7172
7974
|
assertCheckpointName(name);
|
|
@@ -7192,8 +7994,20 @@ Ensure Wolbarg has been initialized and the database path is correct.`
|
|
|
7192
7994
|
);
|
|
7193
7995
|
}
|
|
7194
7996
|
const snapshotPath = this.snapshotPath(name);
|
|
7195
|
-
|
|
7196
|
-
|
|
7997
|
+
const tmpSnapshotPath = `${snapshotPath}.tmp-${Date.now()}`;
|
|
7998
|
+
try {
|
|
7999
|
+
await safeSqliteBackup(resolvedSource, tmpSnapshotPath);
|
|
8000
|
+
} catch (error) {
|
|
8001
|
+
if (fs6.existsSync(tmpSnapshotPath)) {
|
|
8002
|
+
fs6.rmSync(tmpSnapshotPath, { force: true });
|
|
8003
|
+
}
|
|
8004
|
+
throw error;
|
|
8005
|
+
}
|
|
8006
|
+
const stats = fs6.statSync(tmpSnapshotPath);
|
|
8007
|
+
if (fs6.existsSync(snapshotPath)) {
|
|
8008
|
+
fs6.rmSync(snapshotPath, { force: true });
|
|
8009
|
+
}
|
|
8010
|
+
fs6.renameSync(tmpSnapshotPath, snapshotPath);
|
|
7197
8011
|
const meta2 = {
|
|
7198
8012
|
name,
|
|
7199
8013
|
description: options?.description ?? null,
|
|
@@ -7204,9 +8018,17 @@ Ensure Wolbarg has been initialized and the database path is correct.`
|
|
|
7204
8018
|
sourcePath: resolvedSource,
|
|
7205
8019
|
sizeBytes: stats.size
|
|
7206
8020
|
};
|
|
7207
|
-
|
|
8021
|
+
const tmpMetaPath = `${metaPath}.tmp-${Date.now()}`;
|
|
8022
|
+
fs6.writeFileSync(tmpMetaPath, JSON.stringify(meta2, null, 2), "utf8");
|
|
8023
|
+
fs6.renameSync(tmpMetaPath, metaPath);
|
|
7208
8024
|
return meta2;
|
|
7209
8025
|
}
|
|
8026
|
+
/**
|
|
8027
|
+
* Restore a named checkpoint over `targetPath` (replaces WAL/SHM side files).
|
|
8028
|
+
*
|
|
8029
|
+
* @param name - Existing checkpoint name.
|
|
8030
|
+
* @param targetPath - Live memory database path to overwrite.
|
|
8031
|
+
*/
|
|
7210
8032
|
async rollback(name, targetPath) {
|
|
7211
8033
|
this.requireReady();
|
|
7212
8034
|
const meta2 = await this.getCheckpoint(name);
|
|
@@ -7229,6 +8051,7 @@ Ensure Wolbarg has been initialized and the database path is correct.`
|
|
|
7229
8051
|
await safeSqliteBackup(meta2.snapshotPath, resolvedTarget);
|
|
7230
8052
|
return meta2;
|
|
7231
8053
|
}
|
|
8054
|
+
/** Delete checkpoint metadata and snapshot files for `name`. */
|
|
7232
8055
|
async deleteCheckpoint(name) {
|
|
7233
8056
|
this.requireReady();
|
|
7234
8057
|
const metaPath = this.metaPath(name);
|
|
@@ -7244,6 +8067,7 @@ Ensure Wolbarg has been initialized and the database path is correct.`
|
|
|
7244
8067
|
}
|
|
7245
8068
|
return removed;
|
|
7246
8069
|
}
|
|
8070
|
+
/** List all checkpoints sorted by creation time. */
|
|
7247
8071
|
async listCheckpoints() {
|
|
7248
8072
|
this.requireReady();
|
|
7249
8073
|
if (!fs6.existsSync(this.directory)) {
|
|
@@ -7260,6 +8084,7 @@ Ensure Wolbarg has been initialized and the database path is correct.`
|
|
|
7260
8084
|
}
|
|
7261
8085
|
return out.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
7262
8086
|
}
|
|
8087
|
+
/** Load checkpoint metadata by name, or `null` if missing / corrupt. */
|
|
7263
8088
|
async getCheckpoint(name) {
|
|
7264
8089
|
this.requireReady();
|
|
7265
8090
|
const metaPath = this.metaPath(name);
|
|
@@ -7444,6 +8269,13 @@ var SqliteSubscribeEmitter = class {
|
|
|
7444
8269
|
}
|
|
7445
8270
|
});
|
|
7446
8271
|
}
|
|
8272
|
+
/**
|
|
8273
|
+
* Register a filtered callback for in-process memory change events.
|
|
8274
|
+
*
|
|
8275
|
+
* @param filter - Organization / agent / event-type filter.
|
|
8276
|
+
* @param callback - Invoked synchronously on each matching emit.
|
|
8277
|
+
* @returns Unsubscribe function.
|
|
8278
|
+
*/
|
|
7447
8279
|
subscribe(filter, callback) {
|
|
7448
8280
|
if (this.closed) {
|
|
7449
8281
|
throw new Error("Subscribe backend is closed");
|
|
@@ -7454,12 +8286,14 @@ var SqliteSubscribeEmitter = class {
|
|
|
7454
8286
|
this.subscriptions.delete(id);
|
|
7455
8287
|
};
|
|
7456
8288
|
}
|
|
8289
|
+
/** Emit a memory change event to registered subscribers. */
|
|
7457
8290
|
emit(event) {
|
|
7458
8291
|
if (this.closed) {
|
|
7459
8292
|
return;
|
|
7460
8293
|
}
|
|
7461
8294
|
this.emitter.emit("change", event);
|
|
7462
8295
|
}
|
|
8296
|
+
/** Remove all listeners and mark the emitter closed. */
|
|
7463
8297
|
async close() {
|
|
7464
8298
|
this.closed = true;
|
|
7465
8299
|
this.subscriptions.clear();
|
|
@@ -7468,6 +8302,9 @@ var SqliteSubscribeEmitter = class {
|
|
|
7468
8302
|
};
|
|
7469
8303
|
|
|
7470
8304
|
// src/core/wolbarg.ts
|
|
8305
|
+
var warnLogger3 = new WolbargLogger("warn");
|
|
8306
|
+
var warnedHybridNoKeyword = false;
|
|
8307
|
+
var warnedRerankNoProvider = false;
|
|
7471
8308
|
var Wolbarg = class {
|
|
7472
8309
|
initialized = false;
|
|
7473
8310
|
booting = null;
|
|
@@ -7494,6 +8331,20 @@ var Wolbarg = class {
|
|
|
7494
8331
|
memoryDedupe = resolveMemoryDedupeConfig();
|
|
7495
8332
|
embeddingCacheConfig = resolveEmbeddingCacheConfig();
|
|
7496
8333
|
rawEmbedding = null;
|
|
8334
|
+
/**
|
|
8335
|
+
* @param options - Full {@link WolbargOptions}. Key fields:
|
|
8336
|
+
* - `organization` — namespace isolating memories in a shared DB
|
|
8337
|
+
* - `database` / `storage` — SQLite/Postgres config **or** a custom
|
|
8338
|
+
* {@link StorageProvider} instance
|
|
8339
|
+
* - `embedding` — {@link EmbeddingProvider} instance **or**
|
|
8340
|
+
* {@link EmbeddingConfig} / {@link openaiEmbedding} (etc.)
|
|
8341
|
+
* - `llm` — optional {@link LlmProvider} or {@link LlmConfig} / {@link openaiLlm};
|
|
8342
|
+
* required for {@link Wolbarg.compress} and extract-mode
|
|
8343
|
+
* {@link Wolbarg.rememberFromMessages}
|
|
8344
|
+
* - `graph`, `telemetry`, `reranker`, `keywordSearch`, `ocr`, `vision`,
|
|
8345
|
+
* `compression`, `chunking`, `retrieval`, `concurrency`, `embeddingCache`,
|
|
8346
|
+
* `memory.dedupe` — optional plugins / tuning
|
|
8347
|
+
*/
|
|
7497
8348
|
constructor(options) {
|
|
7498
8349
|
this.telemetry = new TelemetryEmitter(null, { enabled: false, level: "off" });
|
|
7499
8350
|
if (!options) {
|
|
@@ -7552,6 +8403,12 @@ var Wolbarg = class {
|
|
|
7552
8403
|
}
|
|
7553
8404
|
/**
|
|
7554
8405
|
* Backwards-compatible initialization (v0.1 API).
|
|
8406
|
+
* Prefer constructing with options + {@link Wolbarg.ready} instead.
|
|
8407
|
+
*
|
|
8408
|
+
* @param options - Organization, database, embedding, and optional LLM config.
|
|
8409
|
+
* @returns Resolves when storage is open and the vector schema is ready.
|
|
8410
|
+
* @throws {InitializationError} If already initialized.
|
|
8411
|
+
* @throws {ConfigurationError} | {ValidationError} On invalid options.
|
|
7555
8412
|
*/
|
|
7556
8413
|
async init(options) {
|
|
7557
8414
|
if (this.initialized || this.storage) {
|
|
@@ -7573,6 +8430,7 @@ var Wolbarg = class {
|
|
|
7573
8430
|
this.storage = createStorageProvider(validated.database);
|
|
7574
8431
|
this.memoryDbPath = resolveDatabaseUrl(validated.database);
|
|
7575
8432
|
this.embedding = createEmbeddingProvider(validated.embedding);
|
|
8433
|
+
this.rawEmbedding = this.embedding;
|
|
7576
8434
|
if (validated.llm) {
|
|
7577
8435
|
this.llm = createLlmProvider(validated.llm);
|
|
7578
8436
|
this.compression = createCompressionProvider(this.llm);
|
|
@@ -7582,7 +8440,14 @@ var Wolbarg = class {
|
|
|
7582
8440
|
}
|
|
7583
8441
|
await this.ready();
|
|
7584
8442
|
}
|
|
7585
|
-
/**
|
|
8443
|
+
/**
|
|
8444
|
+
* Open storage, optional telemetry / checkpoint / graph providers, wrap the
|
|
8445
|
+
* embedding cache, and probe embedding dimensions. Safe to call multiple times;
|
|
8446
|
+
* concurrent callers share one boot promise.
|
|
8447
|
+
*
|
|
8448
|
+
* @returns Resolves when the instance is ready for remember/recall.
|
|
8449
|
+
* @throws {InitializationError} If neither constructor options nor {@link init} were used.
|
|
8450
|
+
*/
|
|
7586
8451
|
async ready() {
|
|
7587
8452
|
if (this.initialized) {
|
|
7588
8453
|
return;
|
|
@@ -7598,6 +8463,7 @@ var Wolbarg = class {
|
|
|
7598
8463
|
this.booting = null;
|
|
7599
8464
|
}
|
|
7600
8465
|
}
|
|
8466
|
+
/** Internal boot sequence invoked by {@link Wolbarg.ready}. */
|
|
7601
8467
|
async boot() {
|
|
7602
8468
|
if (!this.storage || !this.embedding || !this.organization) {
|
|
7603
8469
|
throw new InitializationError(
|
|
@@ -7673,7 +8539,17 @@ var Wolbarg = class {
|
|
|
7673
8539
|
);
|
|
7674
8540
|
}
|
|
7675
8541
|
}
|
|
7676
|
-
/**
|
|
8542
|
+
/**
|
|
8543
|
+
* Store a semantic memory for an agent. Embeds `content.text`, writes the row,
|
|
8544
|
+
* and may **upsert** an existing active memory when dedupe is enabled
|
|
8545
|
+
* (constructor `memory.dedupe` or per-call `options.dedupe`).
|
|
8546
|
+
*
|
|
8547
|
+
* @param options - Agent id, text content, optional metadata / dedupe overrides.
|
|
8548
|
+
* See {@link RememberOptions}.
|
|
8549
|
+
* @returns The stored record plus `action` (`"created"` | `"updated"`).
|
|
8550
|
+
* @throws {ValidationError} On empty agent/content.
|
|
8551
|
+
* @throws {InitializationError} If not configured / ready.
|
|
8552
|
+
*/
|
|
7677
8553
|
async remember(options) {
|
|
7678
8554
|
const trace = this.telemetry.start("remember");
|
|
7679
8555
|
try {
|
|
@@ -7695,7 +8571,15 @@ var Wolbarg = class {
|
|
|
7695
8571
|
throw wrapOperationError("remember", error);
|
|
7696
8572
|
}
|
|
7697
8573
|
}
|
|
7698
|
-
/**
|
|
8574
|
+
/**
|
|
8575
|
+
* Remember many memories in one call. Runs sequentially when any item (or the
|
|
8576
|
+
* global config) enables dedupe; otherwise embeds in batch and inserts in one
|
|
8577
|
+
* transaction for throughput.
|
|
8578
|
+
*
|
|
8579
|
+
* @param items - Non-empty list of {@link RememberOptions}.
|
|
8580
|
+
* @returns One {@link RememberResult} per input, same order.
|
|
8581
|
+
* @throws {ValidationError} If `items` is empty or an entry is invalid.
|
|
8582
|
+
*/
|
|
7699
8583
|
async rememberBatch(items) {
|
|
7700
8584
|
const parent = this.telemetry.start("rememberBatch");
|
|
7701
8585
|
try {
|
|
@@ -7821,7 +8705,15 @@ var Wolbarg = class {
|
|
|
7821
8705
|
* **Experimental** until 1.0 — API shape may change.
|
|
7822
8706
|
*
|
|
7823
8707
|
* - `mode: "raw"` (default) — remember user message text (no LLM).
|
|
7824
|
-
* - `mode: "extract"` —
|
|
8708
|
+
* - `mode: "extract"` — requires configured `llm`; extracts atomic facts then
|
|
8709
|
+
* remembers each. Pass a custom {@link LlmProvider} or {@link openaiLlm}
|
|
8710
|
+
* in the constructor.
|
|
8711
|
+
*
|
|
8712
|
+
* @param messages - Conversation turns (`role` + `content`).
|
|
8713
|
+
* @param options - Agent, mode, optional metadata/dedupe. See
|
|
8714
|
+
* {@link RememberFromMessagesOptions}.
|
|
8715
|
+
* @returns One {@link RememberResult} per remembered fact/message.
|
|
8716
|
+
* @throws {ProviderNotConfiguredError} When `mode: "extract"` but no `llm` was configured.
|
|
7825
8717
|
*/
|
|
7826
8718
|
async rememberFromMessages(messages, options) {
|
|
7827
8719
|
const parent = this.telemetry.start("rememberFromMessages");
|
|
@@ -7891,10 +8783,17 @@ var Wolbarg = class {
|
|
|
7891
8783
|
}
|
|
7892
8784
|
}
|
|
7893
8785
|
/**
|
|
7894
|
-
* Update an existing memory by id
|
|
8786
|
+
* Update an existing memory by id. Re-embeds when `content` changes; merges
|
|
8787
|
+
* metadata when provided.
|
|
8788
|
+
*
|
|
8789
|
+
* @param options.id - Memory id to update.
|
|
8790
|
+
* @param options.content - Optional new `{ text }` (triggers re-embed + content hash).
|
|
8791
|
+
* @param options.metadata - Optional metadata merged onto the existing record.
|
|
8792
|
+
* @returns Updated record with `action: "updated"`.
|
|
8793
|
+
* @throws {MemoryNotFoundError} If the id is unknown in this organization.
|
|
7895
8794
|
*/
|
|
7896
8795
|
async update(options) {
|
|
7897
|
-
const trace = this.telemetry.start("
|
|
8796
|
+
const trace = this.telemetry.start("update");
|
|
7898
8797
|
try {
|
|
7899
8798
|
const { storage, embedding, organization } = await this.requireReady();
|
|
7900
8799
|
assertNonEmptyString(options.id, "id");
|
|
@@ -7961,10 +8860,18 @@ var Wolbarg = class {
|
|
|
7961
8860
|
}
|
|
7962
8861
|
}
|
|
7963
8862
|
/**
|
|
7964
|
-
* Subscribe to memory change events.
|
|
8863
|
+
* Subscribe to memory change events (remember / update / forget / compress / clear).
|
|
7965
8864
|
*
|
|
7966
8865
|
* **SQLite:** delivers events only within this Node.js process.
|
|
7967
8866
|
* A second process writing the same `memory.db` will not notify subscribers here.
|
|
8867
|
+
*
|
|
8868
|
+
* **Postgres:** uses LISTEN/NOTIFY across processes (lazy connection on first subscribe).
|
|
8869
|
+
*
|
|
8870
|
+
* @param filter - Scope by `organization` (defaults to this instance’s org),
|
|
8871
|
+
* optional `agent`, and optional `events` allow-list. See {@link SubscribeFilter}.
|
|
8872
|
+
* @param callback - Invoked with each {@link MemoryChangeEvent}.
|
|
8873
|
+
* @returns {@link Unsubscribe} function — call to stop receiving events.
|
|
8874
|
+
* @throws {ValidationError} If organization cannot be resolved.
|
|
7968
8875
|
*/
|
|
7969
8876
|
subscribe(filter, callback) {
|
|
7970
8877
|
const org = filter.organization || this.organization;
|
|
@@ -7993,6 +8900,7 @@ var Wolbarg = class {
|
|
|
7993
8900
|
}
|
|
7994
8901
|
return this.subscribeBackend.subscribe(normalized, callback);
|
|
7995
8902
|
}
|
|
8903
|
+
/** Broadcast a memory change event to in-process or Postgres NOTIFY subscribers. */
|
|
7996
8904
|
emitChange(event) {
|
|
7997
8905
|
try {
|
|
7998
8906
|
if (this.storage instanceof PostgresStorageProvider) {
|
|
@@ -8218,6 +9126,17 @@ var Wolbarg = class {
|
|
|
8218
9126
|
throw error;
|
|
8219
9127
|
}
|
|
8220
9128
|
}
|
|
9129
|
+
/**
|
|
9130
|
+
* Find the best active near-duplicate memory for upsert (cosine similarity ≥ threshold).
|
|
9131
|
+
*
|
|
9132
|
+
* @param storage - Open storage provider.
|
|
9133
|
+
* @param organization - Org namespace.
|
|
9134
|
+
* @param agent - Agent scope for the search.
|
|
9135
|
+
* @param vector - Embedding of the candidate text.
|
|
9136
|
+
* @param threshold - Minimum similarity (0–1) to treat as a duplicate.
|
|
9137
|
+
* @param limit - Max vector candidates to inspect.
|
|
9138
|
+
* @returns Matching memory row, or `null` if none qualify.
|
|
9139
|
+
*/
|
|
8221
9140
|
async findNearDuplicate(storage, organization, agent, vector, threshold, limit) {
|
|
8222
9141
|
if (typeof storage.searchVectorsWithMemories === "function") {
|
|
8223
9142
|
const hits2 = await storage.searchVectorsWithMemories(
|
|
@@ -8420,17 +9339,37 @@ var Wolbarg = class {
|
|
|
8420
9339
|
}
|
|
8421
9340
|
}
|
|
8422
9341
|
}
|
|
9342
|
+
if (hybridWeights && keywordSignal === "disabled" && !warnedHybridNoKeyword) {
|
|
9343
|
+
warnedHybridNoKeyword = true;
|
|
9344
|
+
warnLogger3.warn(
|
|
9345
|
+
"hybrid search requested but no keyword channel is available; using semantic-only results."
|
|
9346
|
+
);
|
|
9347
|
+
}
|
|
8423
9348
|
const tRank = performance.now();
|
|
8424
|
-
let results = [...byId.values()].sort(
|
|
8425
|
-
|
|
8426
|
-
|
|
9349
|
+
let results = [...byId.values()].sort((a, b) => {
|
|
9350
|
+
const diff = b.similarity - a.similarity;
|
|
9351
|
+
if (diff !== 0) return diff;
|
|
9352
|
+
return a.id.localeCompare(b.id);
|
|
9353
|
+
});
|
|
8427
9354
|
const mmrLambda = resolveMmr(options.mmr) ?? resolveMmr(
|
|
8428
9355
|
this.retrievalConfig.mmr ? { lambda: this.retrievalConfig.mmr.lambda } : void 0
|
|
8429
9356
|
);
|
|
8430
9357
|
let rankingReason = "cosine similarity";
|
|
9358
|
+
let mmrApplied = false;
|
|
8431
9359
|
if (mmrLambda !== null) {
|
|
8432
|
-
|
|
8433
|
-
|
|
9360
|
+
mmrApplied = results.length > topK;
|
|
9361
|
+
if (mmrApplied) {
|
|
9362
|
+
results = applyMmr(results, topK, mmrLambda);
|
|
9363
|
+
rankingReason = `MMR(lambda=${mmrLambda})`;
|
|
9364
|
+
} else {
|
|
9365
|
+
rankingReason = `MMR(lambda=${mmrLambda})(skipped:insufficient_candidates)`;
|
|
9366
|
+
}
|
|
9367
|
+
}
|
|
9368
|
+
if (options.rerank === true && !this.reranker && !warnedRerankNoProvider) {
|
|
9369
|
+
warnedRerankNoProvider = true;
|
|
9370
|
+
warnLogger3.warn(
|
|
9371
|
+
"rerank is enabled but no reranker provider is configured; using identity order."
|
|
9372
|
+
);
|
|
8434
9373
|
}
|
|
8435
9374
|
if (options.rerank && this.reranker) {
|
|
8436
9375
|
const reranked = await this.reranker.rerank(
|
|
@@ -8465,14 +9404,16 @@ var Wolbarg = class {
|
|
|
8465
9404
|
agentId: options.filter?.agent ?? commonAgent(serialized.map((result) => result.agent)),
|
|
8466
9405
|
tags: commonTags(serialized.map((result) => result.metadata))
|
|
8467
9406
|
};
|
|
8468
|
-
if (
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
|
|
9407
|
+
if (options.includeGraph === true && this.graph) {
|
|
9408
|
+
const tGraph = performance.now();
|
|
9409
|
+
await Promise.all(
|
|
9410
|
+
serialized.map(async (hit) => {
|
|
8472
9411
|
hit.related = await this.hydrateRelated(hit.id);
|
|
8473
|
-
}
|
|
8474
|
-
|
|
8475
|
-
|
|
9412
|
+
})
|
|
9413
|
+
);
|
|
9414
|
+
trace.mark("databaseReadMs", performance.now() - tGraph);
|
|
9415
|
+
}
|
|
9416
|
+
if (!explain) {
|
|
8476
9417
|
trace.success(telemetryFields);
|
|
8477
9418
|
return serialized;
|
|
8478
9419
|
}
|
|
@@ -8501,7 +9442,7 @@ var Wolbarg = class {
|
|
|
8501
9442
|
semantic: "enabled",
|
|
8502
9443
|
keyword: keywordSignal,
|
|
8503
9444
|
reranker: options.rerank === true && this.reranker ? "enabled" : "disabled",
|
|
8504
|
-
mmr: mmrLambda === null ? "disabled" : "enabled",
|
|
9445
|
+
mmr: mmrLambda === null ? "disabled" : mmrApplied ? "enabled" : "disabled",
|
|
8505
9446
|
recency: "disabled"
|
|
8506
9447
|
},
|
|
8507
9448
|
results: explanations.map((hit) => ({
|
|
@@ -8533,7 +9474,13 @@ var Wolbarg = class {
|
|
|
8533
9474
|
throw wrapOperationError("recall", error);
|
|
8534
9475
|
}
|
|
8535
9476
|
}
|
|
8536
|
-
/**
|
|
9477
|
+
/**
|
|
9478
|
+
* Run multiple recalls sequentially under one parent telemetry span.
|
|
9479
|
+
*
|
|
9480
|
+
* @param queries - Non-empty list of recall options (`explain` is forced off).
|
|
9481
|
+
* @returns One hit array per query, same order.
|
|
9482
|
+
* @throws {ValidationError} If `queries` is empty.
|
|
9483
|
+
*/
|
|
8537
9484
|
async recallBatch(queries) {
|
|
8538
9485
|
const parent = this.telemetry.start("recallBatch");
|
|
8539
9486
|
try {
|
|
@@ -8567,10 +9514,21 @@ var Wolbarg = class {
|
|
|
8567
9514
|
throw wrapOperationError("recallBatch", error);
|
|
8568
9515
|
}
|
|
8569
9516
|
}
|
|
9517
|
+
/**
|
|
9518
|
+
* Compress the newest active memories for an agent into one summary memory
|
|
9519
|
+
* and archive the sources. Requires an `llm` (or custom `compression`) at
|
|
9520
|
+
* construction — typed only on `Wolbarg<true>`.
|
|
9521
|
+
*
|
|
9522
|
+
* @param options - `agent` required; optional `limit` (default 50, min 2).
|
|
9523
|
+
* See {@link CompressOptions}.
|
|
9524
|
+
* @returns Summary record plus archived source ids. See {@link CompressResult}.
|
|
9525
|
+
* @throws {ProviderNotConfiguredError} If no LLM/compression provider is set.
|
|
9526
|
+
* @throws {ValidationError} If fewer than 2 active memories exist for the agent.
|
|
9527
|
+
*/
|
|
8570
9528
|
async compress(options) {
|
|
8571
9529
|
return this.runCompress(options);
|
|
8572
9530
|
}
|
|
8573
|
-
/** @internal */
|
|
9531
|
+
/** @internal Runs compress for both typed and untyped call sites. */
|
|
8574
9532
|
async runCompress(options) {
|
|
8575
9533
|
const trace = this.telemetry.start("compress");
|
|
8576
9534
|
try {
|
|
@@ -8610,31 +9568,34 @@ var Wolbarg = class {
|
|
|
8610
9568
|
const timestamp = nowIso();
|
|
8611
9569
|
const result = await this.withWriteLock(async () => {
|
|
8612
9570
|
const tWrite = performance.now();
|
|
8613
|
-
const
|
|
8614
|
-
|
|
8615
|
-
|
|
8616
|
-
|
|
8617
|
-
|
|
8618
|
-
|
|
8619
|
-
|
|
8620
|
-
|
|
8621
|
-
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
|
|
8625
|
-
|
|
9571
|
+
const txResult = await storage.withTransaction(async () => {
|
|
9572
|
+
const summaryRow = await storage.insertMemory({
|
|
9573
|
+
id: summaryId,
|
|
9574
|
+
organization,
|
|
9575
|
+
agent: options.agent.trim(),
|
|
9576
|
+
contentText: summaryText,
|
|
9577
|
+
metadata: {
|
|
9578
|
+
compressed: true,
|
|
9579
|
+
sourceCount: records.length,
|
|
9580
|
+
sourceIds: records.map((r) => r.id)
|
|
9581
|
+
},
|
|
9582
|
+
embedding: vector,
|
|
9583
|
+
createdAt: timestamp,
|
|
9584
|
+
updatedAt: timestamp
|
|
9585
|
+
});
|
|
9586
|
+
const archivedIds = await storage.archiveMemories(
|
|
9587
|
+
records.map((r) => r.id),
|
|
9588
|
+
organization,
|
|
9589
|
+
summaryId,
|
|
9590
|
+
timestamp
|
|
9591
|
+
);
|
|
9592
|
+
return {
|
|
9593
|
+
summary: toMemoryRecord(summaryRow),
|
|
9594
|
+
archivedIds
|
|
9595
|
+
};
|
|
8626
9596
|
});
|
|
8627
|
-
const archivedIds = await storage.archiveMemories(
|
|
8628
|
-
records.map((r) => r.id),
|
|
8629
|
-
organization,
|
|
8630
|
-
summaryId,
|
|
8631
|
-
timestamp
|
|
8632
|
-
);
|
|
8633
9597
|
trace.mark("databaseWriteMs", performance.now() - tWrite);
|
|
8634
|
-
return
|
|
8635
|
-
summary: toMemoryRecord(summaryRow),
|
|
8636
|
-
archivedIds
|
|
8637
|
-
};
|
|
9598
|
+
return txResult;
|
|
8638
9599
|
});
|
|
8639
9600
|
trace.success({
|
|
8640
9601
|
provider: storage.name,
|
|
@@ -8656,6 +9617,16 @@ var Wolbarg = class {
|
|
|
8656
9617
|
throw wrapOperationError("compress", error);
|
|
8657
9618
|
}
|
|
8658
9619
|
}
|
|
9620
|
+
/**
|
|
9621
|
+
* Ingest a document (path, URL, Buffer, or text), chunk it, embed chunks, and
|
|
9622
|
+
* store each as a memory. Images use optional `ocr` / `vision` providers from
|
|
9623
|
+
* the constructor.
|
|
9624
|
+
*
|
|
9625
|
+
* @param options - `agent`, `source`, optional chunking overrides / metadata.
|
|
9626
|
+
* See {@link IngestOptions}.
|
|
9627
|
+
* @returns Counts and created memory ids. See {@link IngestResult}.
|
|
9628
|
+
* @throws {ValidationError} If no text can be extracted or chunking yields zero chunks.
|
|
9629
|
+
*/
|
|
8659
9630
|
async ingest(options) {
|
|
8660
9631
|
const trace = this.telemetry.start("ingest");
|
|
8661
9632
|
try {
|
|
@@ -8826,8 +9797,14 @@ var Wolbarg = class {
|
|
|
8826
9797
|
}
|
|
8827
9798
|
}
|
|
8828
9799
|
/**
|
|
8829
|
-
* Link two memories in the optional graph layer.
|
|
8830
|
-
*
|
|
9800
|
+
* Link two memories in the optional graph layer with a typed relation edge.
|
|
9801
|
+
*
|
|
9802
|
+
* @param fromId - Source memory id.
|
|
9803
|
+
* @param toId - Target memory id.
|
|
9804
|
+
* @param relation - Edge label (e.g. `"supports"`, `"caused_by"`).
|
|
9805
|
+
* @param metadata - Optional edge properties stored on the graph provider.
|
|
9806
|
+
* @throws {ProviderNotConfiguredError} When no `graph` was configured.
|
|
9807
|
+
* @throws {ValidationError} On empty ids/relation.
|
|
8831
9808
|
*/
|
|
8832
9809
|
async linkMemories(fromId, toId, relation, metadata) {
|
|
8833
9810
|
const trace = this.telemetry.start("linkMemories");
|
|
@@ -8857,8 +9834,13 @@ var Wolbarg = class {
|
|
|
8857
9834
|
}
|
|
8858
9835
|
/**
|
|
8859
9836
|
* Traverse related memories via the optional graph layer.
|
|
8860
|
-
*
|
|
8861
|
-
*
|
|
9837
|
+
* Neighbor stubs from the graph are re-hydrated from SQL storage when available.
|
|
9838
|
+
*
|
|
9839
|
+
* @param memoryId - Seed memory id.
|
|
9840
|
+
* @param options - Optional `relation`, `direction`, `depth`, `limit`.
|
|
9841
|
+
* See {@link GetRelatedOptions}.
|
|
9842
|
+
* @returns Related {@link MemoryRecord} list (may be empty).
|
|
9843
|
+
* @throws {ProviderNotConfiguredError} When no `graph` was configured.
|
|
8862
9844
|
*/
|
|
8863
9845
|
async getRelated(memoryId, options) {
|
|
8864
9846
|
const trace = this.telemetry.start("getRelated");
|
|
@@ -8866,8 +9848,23 @@ var Wolbarg = class {
|
|
|
8866
9848
|
await this.requireReady();
|
|
8867
9849
|
const graph = this.requireGraph("getRelated");
|
|
8868
9850
|
assertNonEmptyString(memoryId, "memoryId");
|
|
9851
|
+
let validatedOptions = options;
|
|
9852
|
+
if (options?.depth !== void 0) {
|
|
9853
|
+
if (!Number.isFinite(options.depth)) {
|
|
9854
|
+
throw new ConfigurationError("graph.depth must be a finite number");
|
|
9855
|
+
}
|
|
9856
|
+
if (options.depth < 1) {
|
|
9857
|
+
throw new ConfigurationError("graph.depth must be >= 1");
|
|
9858
|
+
}
|
|
9859
|
+
if (options.depth > 16) {
|
|
9860
|
+
validatedOptions = { ...options, depth: 16 };
|
|
9861
|
+
}
|
|
9862
|
+
}
|
|
8869
9863
|
const tGraph = performance.now();
|
|
8870
|
-
const related = await this.hydrateRelated(
|
|
9864
|
+
const related = await this.hydrateRelated(
|
|
9865
|
+
memoryId.trim(),
|
|
9866
|
+
validatedOptions
|
|
9867
|
+
);
|
|
8871
9868
|
trace.mark("databaseReadMs", performance.now() - tGraph);
|
|
8872
9869
|
trace.success({
|
|
8873
9870
|
provider: graph.name,
|
|
@@ -8880,6 +9877,14 @@ var Wolbarg = class {
|
|
|
8880
9877
|
throw wrapOperationError("getRelated", error);
|
|
8881
9878
|
}
|
|
8882
9879
|
}
|
|
9880
|
+
/**
|
|
9881
|
+
* Delete memories by id or by agent filter. Cascades graph nodes/edges when
|
|
9882
|
+
* a graph provider is configured.
|
|
9883
|
+
*
|
|
9884
|
+
* @param options - Either `{ id }` or `{ filter: { agent } }`. See {@link ForgetOptions}.
|
|
9885
|
+
* @returns Number of memories deleted.
|
|
9886
|
+
* @throws {ValidationError} If neither `id` nor `filter.agent` is provided.
|
|
9887
|
+
*/
|
|
8883
9888
|
async forget(options) {
|
|
8884
9889
|
const trace = this.telemetry.start("forget");
|
|
8885
9890
|
try {
|
|
@@ -8944,6 +9949,13 @@ var Wolbarg = class {
|
|
|
8944
9949
|
throw wrapOperationError("forget", error);
|
|
8945
9950
|
}
|
|
8946
9951
|
}
|
|
9952
|
+
/**
|
|
9953
|
+
* Return the audit/history timeline for a single memory.
|
|
9954
|
+
*
|
|
9955
|
+
* @param options - `{ id }` of the memory. See {@link HistoryOptions}.
|
|
9956
|
+
* @returns Memory record plus ordered {@link HistoryEvent} list.
|
|
9957
|
+
* @throws {MemoryNotFoundError} If the id is unknown.
|
|
9958
|
+
*/
|
|
8947
9959
|
async history(options) {
|
|
8948
9960
|
const trace = this.telemetry.start("history");
|
|
8949
9961
|
try {
|
|
@@ -8971,6 +9983,11 @@ var Wolbarg = class {
|
|
|
8971
9983
|
throw wrapOperationError("history", error);
|
|
8972
9984
|
}
|
|
8973
9985
|
}
|
|
9986
|
+
/**
|
|
9987
|
+
* Aggregate counts for this organization (active / archived / agents / etc.).
|
|
9988
|
+
*
|
|
9989
|
+
* @returns {@link StatsResult} snapshot.
|
|
9990
|
+
*/
|
|
8974
9991
|
async stats() {
|
|
8975
9992
|
const trace = this.telemetry.start("stats");
|
|
8976
9993
|
try {
|
|
@@ -8998,6 +10015,13 @@ var Wolbarg = class {
|
|
|
8998
10015
|
throw wrapOperationError("stats", error);
|
|
8999
10016
|
}
|
|
9000
10017
|
}
|
|
10018
|
+
/**
|
|
10019
|
+
* Delete all memories for this organization (optionally scoped by agent).
|
|
10020
|
+
* Cascades graph cleanup when a graph provider is configured.
|
|
10021
|
+
*
|
|
10022
|
+
* @param options - Optional `{ agent }` scope. See {@link ClearOptions}.
|
|
10023
|
+
* @returns Number of memories deleted.
|
|
10024
|
+
*/
|
|
9001
10025
|
async clear(options) {
|
|
9002
10026
|
const trace = this.telemetry.start("clear");
|
|
9003
10027
|
try {
|
|
@@ -9026,7 +10050,16 @@ var Wolbarg = class {
|
|
|
9026
10050
|
throw wrapOperationError("clear", error);
|
|
9027
10051
|
}
|
|
9028
10052
|
}
|
|
9029
|
-
/**
|
|
10053
|
+
/**
|
|
10054
|
+
* Create an immutable named checkpoint of the file-backed SQLite memory DB
|
|
10055
|
+
* (and SQLite graph snapshot when applicable).
|
|
10056
|
+
*
|
|
10057
|
+
* @param name - Checkpoint name (unique).
|
|
10058
|
+
* @param options - Optional metadata. See {@link CheckpointOptions}.
|
|
10059
|
+
* @returns {@link CheckpointInfo} for the new checkpoint.
|
|
10060
|
+
* @throws {ProviderNotConfiguredError} Without a checkpoint provider / file DB.
|
|
10061
|
+
* @throws {GraphCheckpointNotSupportedError} For Neo4j graph backends.
|
|
10062
|
+
*/
|
|
9030
10063
|
async checkpoint(name, options) {
|
|
9031
10064
|
const trace = this.telemetry.start("checkpoint");
|
|
9032
10065
|
try {
|
|
@@ -9036,7 +10069,15 @@ var Wolbarg = class {
|
|
|
9036
10069
|
this.assertGraphCheckpointSupported("checkpoint");
|
|
9037
10070
|
const tCheckpoint = performance.now();
|
|
9038
10071
|
const meta2 = await provider.checkpoint(name, source, options);
|
|
9039
|
-
|
|
10072
|
+
try {
|
|
10073
|
+
await this.snapshotGraphAlongside(meta2.snapshotPath, "checkpoint");
|
|
10074
|
+
} catch (error) {
|
|
10075
|
+
const sidecar = this.graphSnapshotDir(meta2.snapshotPath);
|
|
10076
|
+
if (fs6.existsSync(sidecar)) {
|
|
10077
|
+
fs6.rmSync(sidecar, { recursive: true, force: true });
|
|
10078
|
+
}
|
|
10079
|
+
throw error;
|
|
10080
|
+
}
|
|
9040
10081
|
trace.mark("databaseWriteMs", performance.now() - tCheckpoint);
|
|
9041
10082
|
trace.success({
|
|
9042
10083
|
provider: provider.name,
|
|
@@ -9049,7 +10090,12 @@ var Wolbarg = class {
|
|
|
9049
10090
|
throw wrapOperationError("checkpoint", error);
|
|
9050
10091
|
}
|
|
9051
10092
|
}
|
|
9052
|
-
/**
|
|
10093
|
+
/**
|
|
10094
|
+
* Restore the memory database from a named checkpoint (replaces the live DB file).
|
|
10095
|
+
*
|
|
10096
|
+
* @param name - Checkpoint name previously created with {@link Wolbarg.checkpoint}.
|
|
10097
|
+
* @returns {@link CheckpointInfo} of the restored checkpoint.
|
|
10098
|
+
*/
|
|
9053
10099
|
async rollback(name) {
|
|
9054
10100
|
const trace = this.telemetry.start("rollback");
|
|
9055
10101
|
let storageClosed = false;
|
|
@@ -9095,13 +10141,26 @@ var Wolbarg = class {
|
|
|
9095
10141
|
throw wrapOperationError("rollback", error);
|
|
9096
10142
|
}
|
|
9097
10143
|
}
|
|
10144
|
+
/**
|
|
10145
|
+
* Delete a named checkpoint from disk / the checkpoint store.
|
|
10146
|
+
*
|
|
10147
|
+
* @param name - Checkpoint name.
|
|
10148
|
+
* @returns `true` if a checkpoint was removed.
|
|
10149
|
+
*/
|
|
9098
10150
|
async deleteCheckpoint(name) {
|
|
9099
10151
|
const trace = this.telemetry.start("deleteCheckpoint");
|
|
9100
10152
|
try {
|
|
9101
10153
|
await this.requireReady();
|
|
9102
10154
|
const provider = this.requireCheckpointProvider();
|
|
10155
|
+
const existing = await provider.getCheckpoint(name);
|
|
9103
10156
|
const tDelete = performance.now();
|
|
9104
10157
|
const removed = await provider.deleteCheckpoint(name);
|
|
10158
|
+
if (removed && existing) {
|
|
10159
|
+
const sidecar = this.graphSnapshotDir(existing.snapshotPath);
|
|
10160
|
+
if (fs6.existsSync(sidecar)) {
|
|
10161
|
+
fs6.rmSync(sidecar, { recursive: true, force: true });
|
|
10162
|
+
}
|
|
10163
|
+
}
|
|
9105
10164
|
trace.mark("databaseWriteMs", performance.now() - tDelete);
|
|
9106
10165
|
trace.success({
|
|
9107
10166
|
provider: provider.name,
|
|
@@ -9114,6 +10173,11 @@ var Wolbarg = class {
|
|
|
9114
10173
|
throw wrapOperationError("deleteCheckpoint", error);
|
|
9115
10174
|
}
|
|
9116
10175
|
}
|
|
10176
|
+
/**
|
|
10177
|
+
* List all known checkpoints for this instance.
|
|
10178
|
+
*
|
|
10179
|
+
* @returns Array of {@link CheckpointInfo} (may be empty).
|
|
10180
|
+
*/
|
|
9117
10181
|
async listCheckpoints() {
|
|
9118
10182
|
const trace = this.telemetry.start("listCheckpoints");
|
|
9119
10183
|
try {
|
|
@@ -9132,6 +10196,12 @@ var Wolbarg = class {
|
|
|
9132
10196
|
throw wrapOperationError("listCheckpoints", error);
|
|
9133
10197
|
}
|
|
9134
10198
|
}
|
|
10199
|
+
/**
|
|
10200
|
+
* Look up a single checkpoint by name.
|
|
10201
|
+
*
|
|
10202
|
+
* @param name - Checkpoint name.
|
|
10203
|
+
* @returns {@link CheckpointInfo} or `null` if missing.
|
|
10204
|
+
*/
|
|
9135
10205
|
async getCheckpoint(name) {
|
|
9136
10206
|
const trace = this.telemetry.start("getCheckpoint");
|
|
9137
10207
|
try {
|
|
@@ -9151,7 +10221,13 @@ var Wolbarg = class {
|
|
|
9151
10221
|
throw wrapOperationError("getCheckpoint", error);
|
|
9152
10222
|
}
|
|
9153
10223
|
}
|
|
9154
|
-
/**
|
|
10224
|
+
/**
|
|
10225
|
+
* Export the memory database to a portable SQLite + manifest bundle
|
|
10226
|
+
* (includes SQLite graph sidecar when applicable).
|
|
10227
|
+
*
|
|
10228
|
+
* @param exportPath - Destination file path for the export bundle.
|
|
10229
|
+
* @returns {@link ExportResult} with path, size, and timestamp.
|
|
10230
|
+
*/
|
|
9155
10231
|
async export(exportPath) {
|
|
9156
10232
|
const trace = this.telemetry.start("export");
|
|
9157
10233
|
try {
|
|
@@ -9163,7 +10239,9 @@ var Wolbarg = class {
|
|
|
9163
10239
|
const result = await this.transfer.exportTo(
|
|
9164
10240
|
exportPath,
|
|
9165
10241
|
source,
|
|
9166
|
-
this.organization ?? void 0
|
|
10242
|
+
this.organization ?? void 0,
|
|
10243
|
+
this.embedding?.model,
|
|
10244
|
+
this.embeddingDimensions ?? void 0
|
|
9167
10245
|
);
|
|
9168
10246
|
await this.snapshotGraphAlongside(result.path, "export");
|
|
9169
10247
|
trace.success({
|
|
@@ -9180,22 +10258,33 @@ var Wolbarg = class {
|
|
|
9180
10258
|
throw wrapOperationError("export", error);
|
|
9181
10259
|
}
|
|
9182
10260
|
}
|
|
9183
|
-
/**
|
|
10261
|
+
/**
|
|
10262
|
+
* Import a previously exported memory database, replacing the current file.
|
|
10263
|
+
* Closes and reopens storage around the swap.
|
|
10264
|
+
*
|
|
10265
|
+
* @param exportPath - Path to a bundle produced by {@link Wolbarg.export}.
|
|
10266
|
+
* @returns {@link ImportResult} with restored path and timestamp.
|
|
10267
|
+
*/
|
|
9184
10268
|
async import(exportPath) {
|
|
9185
10269
|
const trace = this.telemetry.start("import");
|
|
9186
10270
|
let storageClosed = false;
|
|
9187
10271
|
try {
|
|
9188
10272
|
await this.requireReady();
|
|
10273
|
+
this.assertGraphCheckpointSupported("import");
|
|
9189
10274
|
const target = this.requireMemoryDbPath();
|
|
9190
10275
|
if (this.storage) {
|
|
9191
10276
|
await this.storage.close();
|
|
9192
10277
|
storageClosed = true;
|
|
9193
10278
|
}
|
|
9194
10279
|
const graphClosed = await this.closeGraphForSnapshot();
|
|
9195
|
-
this.assertGraphCheckpointSupported("import");
|
|
9196
10280
|
const result = await this.transfer.importFrom(
|
|
9197
10281
|
exportPath,
|
|
9198
|
-
target
|
|
10282
|
+
target,
|
|
10283
|
+
{
|
|
10284
|
+
organization: this.organization ?? void 0,
|
|
10285
|
+
embeddingModel: this.embedding?.model,
|
|
10286
|
+
embeddingDimensions: this.embeddingDimensions ?? void 0
|
|
10287
|
+
}
|
|
9199
10288
|
);
|
|
9200
10289
|
await this.restoreGraphAlongside(result.path, "import");
|
|
9201
10290
|
if (graphClosed) {
|
|
@@ -9225,11 +10314,17 @@ var Wolbarg = class {
|
|
|
9225
10314
|
throw wrapOperationError("import", error);
|
|
9226
10315
|
}
|
|
9227
10316
|
}
|
|
9228
|
-
/**
|
|
10317
|
+
/**
|
|
10318
|
+
* Flush pending telemetry writes (useful in tests / before process exit).
|
|
10319
|
+
*
|
|
10320
|
+
* @returns Resolves when the telemetry provider has flushed.
|
|
10321
|
+
*/
|
|
9229
10322
|
async flushTelemetry() {
|
|
9230
10323
|
await this.telemetry.flush();
|
|
9231
10324
|
}
|
|
9232
|
-
/**
|
|
10325
|
+
/**
|
|
10326
|
+
* Session id for this SDK instance (attached to telemetry traces and change events).
|
|
10327
|
+
*/
|
|
9233
10328
|
get sessionId() {
|
|
9234
10329
|
return this.telemetry.sessionId;
|
|
9235
10330
|
}
|
|
@@ -9244,6 +10339,12 @@ var Wolbarg = class {
|
|
|
9244
10339
|
}
|
|
9245
10340
|
return fn();
|
|
9246
10341
|
}
|
|
10342
|
+
/**
|
|
10343
|
+
* Close storage, graph, telemetry, checkpoints, and subscribe backends.
|
|
10344
|
+
* The instance can be discarded afterward; construct a new one to reopen.
|
|
10345
|
+
*
|
|
10346
|
+
* @returns Resolves when all providers have closed (errors are swallowed per-provider).
|
|
10347
|
+
*/
|
|
9247
10348
|
async close() {
|
|
9248
10349
|
if (this.storage) {
|
|
9249
10350
|
this.telemetry.emitShutdown(this.storage.name);
|
|
@@ -9268,9 +10369,11 @@ var Wolbarg = class {
|
|
|
9268
10369
|
this.embeddingDimensions = null;
|
|
9269
10370
|
this.initialized = false;
|
|
9270
10371
|
}
|
|
10372
|
+
/** Whether {@link Wolbarg.ready} / {@link Wolbarg.init} has completed successfully. */
|
|
9271
10373
|
get isInitialized() {
|
|
9272
10374
|
return this.initialized;
|
|
9273
10375
|
}
|
|
10376
|
+
/** Ensure {@link Wolbarg.ready} completed and core providers are available. */
|
|
9274
10377
|
async requireReady() {
|
|
9275
10378
|
await this.ready();
|
|
9276
10379
|
if (!this.initialized || !this.storage || !this.embedding || !this.organization) {
|
|
@@ -9284,6 +10387,7 @@ var Wolbarg = class {
|
|
|
9284
10387
|
organization: this.organization
|
|
9285
10388
|
};
|
|
9286
10389
|
}
|
|
10390
|
+
/** Reject embeddings whose dimensionality differs from the initialized model. */
|
|
9287
10391
|
assertEmbeddingDimensions(dimensions) {
|
|
9288
10392
|
if (this.embeddingDimensions !== null && dimensions !== this.embeddingDimensions) {
|
|
9289
10393
|
throw new ValidationError(
|
|
@@ -9291,6 +10395,7 @@ var Wolbarg = class {
|
|
|
9291
10395
|
);
|
|
9292
10396
|
}
|
|
9293
10397
|
}
|
|
10398
|
+
/** Return the configured checkpoint provider or throw {@link ProviderNotConfiguredError}. */
|
|
9294
10399
|
requireCheckpointProvider() {
|
|
9295
10400
|
if (!this.checkpointProvider) {
|
|
9296
10401
|
throw new ProviderNotConfiguredError(
|
|
@@ -9301,6 +10406,7 @@ var Wolbarg = class {
|
|
|
9301
10406
|
}
|
|
9302
10407
|
return this.checkpointProvider;
|
|
9303
10408
|
}
|
|
10409
|
+
/** Return the configured graph provider or throw with a setup hint for `method`. */
|
|
9304
10410
|
requireGraph(method) {
|
|
9305
10411
|
if (!this.graph) {
|
|
9306
10412
|
throw new ProviderNotConfiguredError(
|
|
@@ -9311,20 +10417,24 @@ var Wolbarg = class {
|
|
|
9311
10417
|
}
|
|
9312
10418
|
return this.graph;
|
|
9313
10419
|
}
|
|
10420
|
+
/** Throw when graph checkpoint pairing is requested on a non-file-backed graph backend. */
|
|
9314
10421
|
assertGraphCheckpointSupported(operation) {
|
|
9315
10422
|
if (!this.graph) return;
|
|
9316
10423
|
if (!this.graph.supportsFileSnapshot()) {
|
|
9317
10424
|
throw new GraphCheckpointNotSupportedError(this.graph.name, operation);
|
|
9318
10425
|
}
|
|
9319
10426
|
}
|
|
10427
|
+
/** Directory path where a graph file snapshot is stored alongside a memory checkpoint. */
|
|
9320
10428
|
graphSnapshotDir(alongsidePath) {
|
|
9321
10429
|
return `${alongsidePath}.graph`;
|
|
9322
10430
|
}
|
|
10431
|
+
/** Close the graph provider before copying files; returns whether a close occurred. */
|
|
9323
10432
|
async closeGraphForSnapshot() {
|
|
9324
10433
|
if (!this.graph || !this.graph.supportsFileSnapshot()) return false;
|
|
9325
10434
|
await this.graph.close();
|
|
9326
10435
|
return true;
|
|
9327
10436
|
}
|
|
10437
|
+
/** Copy the SQLite graph database next to a memory checkpoint directory. */
|
|
9328
10438
|
async snapshotGraphAlongside(alongsidePath, operation) {
|
|
9329
10439
|
if (!this.graph) return;
|
|
9330
10440
|
if (!this.graph.supportsFileSnapshot()) {
|
|
@@ -9363,6 +10473,7 @@ var Wolbarg = class {
|
|
|
9363
10473
|
await this.graph.open();
|
|
9364
10474
|
}
|
|
9365
10475
|
}
|
|
10476
|
+
/** Restore a graph file snapshot written by {@link snapshotGraphAlongside}. */
|
|
9366
10477
|
async restoreGraphAlongside(alongsidePath, operation) {
|
|
9367
10478
|
if (!this.graph) return;
|
|
9368
10479
|
if (!this.graph.supportsFileSnapshot()) {
|
|
@@ -9386,22 +10497,32 @@ var Wolbarg = class {
|
|
|
9386
10497
|
fs6.cpSync(src, dataPath, { recursive: true });
|
|
9387
10498
|
}
|
|
9388
10499
|
}
|
|
10500
|
+
/** Run graph traversal then re-hydrate stub records from storage when rows exist. */
|
|
9389
10501
|
async hydrateRelated(memoryId, options) {
|
|
9390
10502
|
const graph = this.requireGraph("getRelated");
|
|
9391
10503
|
const related = await graph.getRelated(memoryId, options);
|
|
9392
10504
|
const { storage, organization } = await this.requireReady();
|
|
10505
|
+
const rows = await Promise.all(
|
|
10506
|
+
related.map((stub) => storage.getMemoryById(stub.id, organization))
|
|
10507
|
+
);
|
|
9393
10508
|
const out = [];
|
|
9394
|
-
for (
|
|
9395
|
-
const
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
} else {
|
|
9399
|
-
out.push(stub);
|
|
9400
|
-
}
|
|
10509
|
+
for (let i = 0; i < related.length; i += 1) {
|
|
10510
|
+
const stub = related[i];
|
|
10511
|
+
const row = rows[i];
|
|
10512
|
+
out.push(row ? toMemoryRecord(row) : stub);
|
|
9401
10513
|
}
|
|
9402
10514
|
return out;
|
|
9403
10515
|
}
|
|
10516
|
+
/** Resolve the on-disk SQLite memory database path (throws for Postgres / `:memory:`). */
|
|
9404
10517
|
requireMemoryDbPath() {
|
|
10518
|
+
if (this.storage && !(this.storage instanceof SqliteStorageProvider)) {
|
|
10519
|
+
throw new ConfigurationError(
|
|
10520
|
+
"This operation requires a file-backed SQLite database (checkpoints / export-import are not supported for PostgreSQL).",
|
|
10521
|
+
{
|
|
10522
|
+
suggestion: 'Use database: { provider: "sqlite", url: "./memory.db" }'
|
|
10523
|
+
}
|
|
10524
|
+
);
|
|
10525
|
+
}
|
|
9405
10526
|
if (!this.memoryDbPath || this.memoryDbPath === ":memory:") {
|
|
9406
10527
|
throw new ConfigurationError(
|
|
9407
10528
|
"This operation requires a file-backed SQLite memory database (not :memory:).",
|
|
@@ -9444,6 +10565,7 @@ function commonTags(metadata) {
|
|
|
9444
10565
|
|
|
9445
10566
|
// src/filters/types.ts
|
|
9446
10567
|
var meta = {
|
|
10568
|
+
/** Field equals value (strict equality). */
|
|
9447
10569
|
eq: (field, value) => ({
|
|
9448
10570
|
field,
|
|
9449
10571
|
op: { eq: value }
|
|
@@ -9527,16 +10649,23 @@ function createTelemetryProvider(config) {
|
|
|
9527
10649
|
function wolbarg2(options) {
|
|
9528
10650
|
return new Wolbarg(options);
|
|
9529
10651
|
}
|
|
10652
|
+
var createWolbarg = wolbarg2;
|
|
9530
10653
|
|
|
9531
10654
|
// src/keyword/index.ts
|
|
9532
10655
|
var Bm25KeywordSearchProvider = class {
|
|
9533
10656
|
name = "bm25";
|
|
9534
10657
|
k1;
|
|
9535
10658
|
b;
|
|
10659
|
+
/**
|
|
10660
|
+
* @param options - BM25 tuning parameters.
|
|
10661
|
+
* @param options.k1 - Term frequency saturation (default `1.2`).
|
|
10662
|
+
* @param options.b - Length normalization (default `0.75`).
|
|
10663
|
+
*/
|
|
9536
10664
|
constructor(options) {
|
|
9537
10665
|
this.k1 = options?.k1 ?? 1.2;
|
|
9538
10666
|
this.b = options?.b ?? 0.75;
|
|
9539
10667
|
}
|
|
10668
|
+
/** @inheritdoc */
|
|
9540
10669
|
async search(query, documents, topK) {
|
|
9541
10670
|
if (documents.length === 0 || topK <= 0) {
|
|
9542
10671
|
return [];
|
|
@@ -9630,7 +10759,14 @@ var HttpRerankerProvider = class {
|
|
|
9630
10759
|
}));
|
|
9631
10760
|
}
|
|
9632
10761
|
const parsed = this.options.parseResults(body, documents);
|
|
9633
|
-
|
|
10762
|
+
const hits = parsed.slice(0, topK);
|
|
10763
|
+
if (hits.length === 0) {
|
|
10764
|
+
return documents.slice(0, topK).map((d, i) => ({
|
|
10765
|
+
id: d.id,
|
|
10766
|
+
score: 1 - i / Math.max(documents.length, 1)
|
|
10767
|
+
}));
|
|
10768
|
+
}
|
|
10769
|
+
return hits;
|
|
9634
10770
|
} catch {
|
|
9635
10771
|
return documents.slice(0, topK).map((d, i) => ({
|
|
9636
10772
|
id: d.id,
|
|
@@ -9815,21 +10951,31 @@ function tesseract() {
|
|
|
9815
10951
|
return {
|
|
9816
10952
|
name: "tesseract",
|
|
9817
10953
|
async recognize(image) {
|
|
10954
|
+
let mod;
|
|
9818
10955
|
try {
|
|
9819
|
-
|
|
9820
|
-
|
|
9821
|
-
|
|
9822
|
-
|
|
9823
|
-
|
|
9824
|
-
|
|
9825
|
-
|
|
9826
|
-
|
|
9827
|
-
|
|
9828
|
-
|
|
9829
|
-
|
|
9830
|
-
|
|
9831
|
-
|
|
9832
|
-
|
|
10956
|
+
mod = await import('tesseract.js');
|
|
10957
|
+
} catch (error) {
|
|
10958
|
+
throw new ConfigurationError(
|
|
10959
|
+
'OCR provider "tesseract" requires the optional peer package "tesseract.js". Install it with: npm install tesseract.js',
|
|
10960
|
+
{
|
|
10961
|
+
cause: error instanceof Error ? error : void 0,
|
|
10962
|
+
suggestion: "npm install tesseract.js",
|
|
10963
|
+
operation: "recognize"
|
|
10964
|
+
}
|
|
10965
|
+
);
|
|
10966
|
+
}
|
|
10967
|
+
const createWorker = mod.createWorker ?? mod.default?.createWorker;
|
|
10968
|
+
if (!createWorker) {
|
|
10969
|
+
throw new ConfigurationError(
|
|
10970
|
+
'OCR provider "tesseract" could not find tesseract.js.createWorker. Ensure your tesseract.js version is compatible.'
|
|
10971
|
+
);
|
|
10972
|
+
}
|
|
10973
|
+
const worker = await createWorker("eng");
|
|
10974
|
+
try {
|
|
10975
|
+
const result = await worker.recognize(image);
|
|
10976
|
+
return { text: result.data.text.trim() };
|
|
10977
|
+
} finally {
|
|
10978
|
+
await worker.terminate();
|
|
9833
10979
|
}
|
|
9834
10980
|
}
|
|
9835
10981
|
};
|
|
@@ -9972,6 +11118,6 @@ function percentile(sorted, p) {
|
|
|
9972
11118
|
return sorted[idx];
|
|
9973
11119
|
}
|
|
9974
11120
|
|
|
9975
|
-
export { CompressionError, ConfigurationError, DatabaseError, EmbeddingError, FixedChunkingStrategy, GraphCheckpointNotSupportedError, HeadingChunkingStrategy, InitializationError, LlmCompressionProvider, MarkdownChunkingStrategy, MemoryNotFoundError, Neo4jGraphProvider, NoopTelemetryProvider, ParagraphChunkingStrategy, PostgresStorageProvider, ProviderNotConfiguredError, SDK_VERSION, SentenceChunkingStrategy, SqliteCheckpointProvider, SqliteDatabaseProvider, SqliteEventDatabase, SqliteGraphProvider, SqliteStorageProvider, SqliteTelemetryProvider, StorageLockedError, TelemetryEmitter, ValidationError, Wolbarg, WolbargError, WolbargLogger, bgeReranker, bm25, cohereReranker, createChunkingStrategy, createCompressionProvider, createDatabaseProvider, createEmbeddingProvider, createLlmProvider, createStorageProvider, createTelemetryProvider,
|
|
11121
|
+
export { CompressionError, ConfigurationError, DatabaseError, EmbeddingError, FixedChunkingStrategy, GraphCheckpointNotSupportedError, HeadingChunkingStrategy, InitializationError, LlmCompressionProvider, MarkdownChunkingStrategy, MemoryNotFoundError, Neo4jGraphProvider, NoopTelemetryProvider, ParagraphChunkingStrategy, PostgresStorageProvider, ProviderNotConfiguredError, SDK_VERSION, SentenceChunkingStrategy, SqliteCheckpointProvider, SqliteDatabaseProvider, SqliteEventDatabase, SqliteGraphProvider, SqliteStorageProvider, SqliteTelemetryProvider, StorageLockedError, TelemetryEmitter, ValidationError, Wolbarg, WolbargError, WolbargLogger, bgeReranker, bm25, cohereReranker, createChunkingStrategy, createCompressionProvider, createDatabaseProvider, createEmbeddingProvider, createLlmProvider, createStorageProvider, createTelemetryProvider, createWolbarg, crossEncoder, geminiEmbedding, geminiVision, jinaReranker, lmStudioEmbedding, meta, neo4jGraph, ollamaEmbedding, ollamaLlm, openRouterEmbedding, openRouterLlm, openaiCompatibleEmbedding, openaiCompatibleLlm, openaiEmbedding, openaiLlm, openaiReranker, openaiVision, postgres, postgresConfig, runBenchmark, sqlite, sqliteCheckpoint, sqliteConfig, sqliteGraph, sqliteTelemetry, summarizeBenchmark, tesseract, togetherEmbedding, vllmEmbedding, wolbarg, wrapOperationError };
|
|
9976
11122
|
//# sourceMappingURL=index.js.map
|
|
9977
11123
|
//# sourceMappingURL=index.js.map
|