@memstack/core 0.2.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +321 -69
- package/dist/index.cjs +2480 -269
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +537 -27
- package/dist/index.d.ts +537 -27
- package/dist/index.js +2470 -267
- package/dist/index.js.map +1 -1
- package/package.json +27 -24
- package/src/adapters/embedding/cohere.ts +0 -77
- package/src/adapters/embedding/openai.ts +0 -65
- package/src/adapters/llm/anthropic.ts +0 -69
- package/src/adapters/llm/groq.ts +0 -185
- package/src/adapters/llm/ollama.ts +0 -85
- package/src/adapters/llm/openai.ts +0 -178
- package/src/adapters/storage/disk.ts +0 -334
- package/src/adapters/storage/memory.ts +0 -147
- package/src/adapters/storage/postgres.ts +0 -310
- package/src/adapters/storage/redis.ts +0 -274
- package/src/client.ts +0 -236
- package/src/errors.ts +0 -47
- package/src/index.ts +0 -52
- package/src/interfaces.ts +0 -154
- package/src/memory/ContextCompiler.ts +0 -158
- package/src/memory/MemoryStore.ts +0 -266
- package/src/memory/Pruner.ts +0 -66
- package/src/memory/Summarizer.ts +0 -46
- package/src/types.ts +0 -50
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdir, readFile, writeFile, readdir } from 'fs/promises';
|
|
1
|
+
import { mkdir, readFile, writeFile, readdir, appendFile } from 'fs/promises';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
|
|
4
4
|
// src/errors.ts
|
|
@@ -45,6 +45,10 @@ var Summarizer = class {
|
|
|
45
45
|
if (memories.length === 0) {
|
|
46
46
|
throw new MemStackError("VALIDATION_ERROR", "No memories to summarize");
|
|
47
47
|
}
|
|
48
|
+
const chunkSize = options.chunkSize ?? 50;
|
|
49
|
+
if (memories.length > chunkSize) {
|
|
50
|
+
return this._summarizeChunked(memories, options, promptOverride);
|
|
51
|
+
}
|
|
48
52
|
const sorted = [...memories].sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
|
|
49
53
|
const memoryTexts = sorted.map((m) => `[${m.createdAt.toISOString()}] ${m.content}`);
|
|
50
54
|
const system = promptOverride ?? this.defaultPrompt;
|
|
@@ -64,6 +68,76 @@ var Summarizer = class {
|
|
|
64
68
|
);
|
|
65
69
|
}
|
|
66
70
|
}
|
|
71
|
+
async _summarizeChunked(memories, options, promptOverride) {
|
|
72
|
+
const chunkSize = options.chunkSize ?? 50;
|
|
73
|
+
const sorted = [...memories].sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
|
|
74
|
+
const chunks = [];
|
|
75
|
+
for (let i = 0; i < sorted.length; i += chunkSize) {
|
|
76
|
+
chunks.push(sorted.slice(i, i + chunkSize));
|
|
77
|
+
}
|
|
78
|
+
let totalTokens = 0;
|
|
79
|
+
const chunkSummaries = [];
|
|
80
|
+
for (const chunk of chunks) {
|
|
81
|
+
const memoryTexts = chunk.map((m) => `[${m.createdAt.toISOString()}] ${m.content}`);
|
|
82
|
+
const system = promptOverride ?? this.defaultPrompt;
|
|
83
|
+
const user = memoryTexts.join("\n\n");
|
|
84
|
+
try {
|
|
85
|
+
const response = await this.llm.complete({ system, user });
|
|
86
|
+
chunkSummaries.push(response.text.trim());
|
|
87
|
+
totalTokens += response.tokens.total;
|
|
88
|
+
} catch (err) {
|
|
89
|
+
if (err instanceof MemStackError) throw err;
|
|
90
|
+
throw new MemStackError(
|
|
91
|
+
"LLM_ERROR",
|
|
92
|
+
`Chunked summarization failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
93
|
+
{ retryable: true, details: { cause: err instanceof Error ? err.message : String(err) } }
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (chunkSummaries.length === 1) {
|
|
98
|
+
return { summaryContent: chunkSummaries[0], tokenCount: totalTokens };
|
|
99
|
+
}
|
|
100
|
+
const summaryMemories = chunkSummaries.map((content, i) => ({
|
|
101
|
+
id: `_chunk_summary_${i}`,
|
|
102
|
+
actorId: sorted[0].actorId,
|
|
103
|
+
memoryType: "summary",
|
|
104
|
+
content,
|
|
105
|
+
importance: 0.5,
|
|
106
|
+
emotionalValence: 0,
|
|
107
|
+
tags: ["summary"],
|
|
108
|
+
createdAt: sorted[sorted.length - 1].createdAt
|
|
109
|
+
}));
|
|
110
|
+
const merged = await this._summarizeChunked(summaryMemories, options, promptOverride);
|
|
111
|
+
return {
|
|
112
|
+
summaryContent: merged.summaryContent,
|
|
113
|
+
tokenCount: totalTokens + merged.tokenCount
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
async *summarizeStream(memories, options = {}, promptOverride) {
|
|
117
|
+
if (memories.length === 0) {
|
|
118
|
+
throw new MemStackError("VALIDATION_ERROR", "No memories to summarize");
|
|
119
|
+
}
|
|
120
|
+
if (!this.llm.completeStream) {
|
|
121
|
+
const result = await this.summarize(memories, options, promptOverride);
|
|
122
|
+
yield { chunk: result.summaryContent, text: result.summaryContent };
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const chunkSize = options.chunkSize ?? 50;
|
|
126
|
+
let toSummarize = memories;
|
|
127
|
+
if (memories.length > chunkSize) {
|
|
128
|
+
const chunked = await this._summarizeChunked(memories, options, promptOverride);
|
|
129
|
+
toSummarize = [{ id: "_chunked", actorId: "summarizer", memoryType: "summary", content: chunked.summaryContent, importance: 0.5, emotionalValence: 0, tags: [], createdAt: /* @__PURE__ */ new Date() }];
|
|
130
|
+
}
|
|
131
|
+
const sorted = [...toSummarize].sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
|
|
132
|
+
const memoryTexts = sorted.map((m) => `[${m.createdAt.toISOString()}] ${m.content}`);
|
|
133
|
+
const system = promptOverride ?? this.defaultPrompt;
|
|
134
|
+
const user = memoryTexts.join("\n\n");
|
|
135
|
+
let fullText = "";
|
|
136
|
+
for await (const chunk of this.llm.completeStream({ system, user })) {
|
|
137
|
+
fullText += chunk.text;
|
|
138
|
+
yield { chunk: chunk.text, text: fullText };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
67
141
|
};
|
|
68
142
|
|
|
69
143
|
// src/memory/Pruner.ts
|
|
@@ -81,6 +155,8 @@ var Pruner = class {
|
|
|
81
155
|
return this.removeByType(memories, strategy);
|
|
82
156
|
case "custom":
|
|
83
157
|
return this.removeCustom(memories, strategy);
|
|
158
|
+
case "compose":
|
|
159
|
+
return this.keepCompose(memories, strategy);
|
|
84
160
|
default:
|
|
85
161
|
return memories;
|
|
86
162
|
}
|
|
@@ -122,21 +198,36 @@ var Pruner = class {
|
|
|
122
198
|
if (!strategy.shouldRemove) return memories;
|
|
123
199
|
return memories.filter((m) => !strategy.shouldRemove(m));
|
|
124
200
|
}
|
|
201
|
+
/** Apply sub-strategies cumulatively: kept memories must pass ALL. */
|
|
202
|
+
keepCompose(memories, strategy) {
|
|
203
|
+
if (!strategy.strategies || strategy.strategies.length === 0) {
|
|
204
|
+
return memories;
|
|
205
|
+
}
|
|
206
|
+
let kept = memories;
|
|
207
|
+
for (const sub of strategy.strategies) {
|
|
208
|
+
kept = this.execute(kept, sub);
|
|
209
|
+
}
|
|
210
|
+
return kept;
|
|
211
|
+
}
|
|
125
212
|
};
|
|
126
213
|
|
|
127
214
|
// src/memory/ContextCompiler.ts
|
|
128
215
|
var ContextCompiler = class {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
216
|
+
tokenCounter;
|
|
217
|
+
importantRatio;
|
|
218
|
+
constructor(tokenCounter, importantRatio) {
|
|
219
|
+
this.tokenCounter = tokenCounter ?? this._heuristicTokens.bind(this);
|
|
220
|
+
this.importantRatio = importantRatio ?? 0.6;
|
|
221
|
+
}
|
|
222
|
+
_heuristicTokens(text) {
|
|
135
223
|
const charEst = Math.ceil(text.length / 4);
|
|
136
224
|
const words = text.split(/\s+/).filter((w) => w.length > 0);
|
|
137
225
|
const wordEst = Math.ceil(words.length * 1.3);
|
|
138
226
|
return Math.max(charEst, Math.max(1, wordEst));
|
|
139
227
|
}
|
|
228
|
+
estimateTokens(text) {
|
|
229
|
+
return this.tokenCounter(text);
|
|
230
|
+
}
|
|
140
231
|
/**
|
|
141
232
|
* Truncate text to fit within a token budget, preserving whole words.
|
|
142
233
|
* Appends "..." when content is cut. Falls back to character-based
|
|
@@ -173,6 +264,15 @@ var ContextCompiler = class {
|
|
|
173
264
|
);
|
|
174
265
|
const { recentMemories, importantMemories, systemPrompt } = this.assembleTokenBudgeted(sortedRecent, sortedImportant, maxTokens);
|
|
175
266
|
const tokenEstimate = this.estimateTokens(systemPrompt);
|
|
267
|
+
if (options.format === "messages") {
|
|
268
|
+
return {
|
|
269
|
+
systemPrompt,
|
|
270
|
+
messages: [{ role: "system", content: systemPrompt }],
|
|
271
|
+
recentMemories,
|
|
272
|
+
importantMemories,
|
|
273
|
+
tokenEstimate
|
|
274
|
+
};
|
|
275
|
+
}
|
|
176
276
|
return { systemPrompt, recentMemories, importantMemories, tokenEstimate };
|
|
177
277
|
}
|
|
178
278
|
assembleTokenBudgeted(recent, important, maxTokens) {
|
|
@@ -187,7 +287,7 @@ var ContextCompiler = class {
|
|
|
187
287
|
`;
|
|
188
288
|
const overhead = this.estimateTokens(prefix + suffix);
|
|
189
289
|
let tokens = this.estimateTokens(m.content) + overhead;
|
|
190
|
-
const remaining = Math.floor(maxTokens *
|
|
290
|
+
const remaining = Math.floor(maxTokens * this.importantRatio) - usedTokens;
|
|
191
291
|
if (remaining <= 0) break;
|
|
192
292
|
if (tokens > remaining) {
|
|
193
293
|
const contentBudget = Math.max(0, remaining - overhead);
|
|
@@ -258,7 +358,6 @@ var ContextCompiler = class {
|
|
|
258
358
|
};
|
|
259
359
|
|
|
260
360
|
// src/memory/MemoryStore.ts
|
|
261
|
-
var EMBEDDING_BATCH_SIZE = 2048;
|
|
262
361
|
var MemoryStore = class {
|
|
263
362
|
storage;
|
|
264
363
|
embedding;
|
|
@@ -266,6 +365,15 @@ var MemoryStore = class {
|
|
|
266
365
|
pruner;
|
|
267
366
|
compiler;
|
|
268
367
|
embedOnStore;
|
|
368
|
+
limits;
|
|
369
|
+
contentHashIndex = null;
|
|
370
|
+
initialized = false;
|
|
371
|
+
async _ensureInit() {
|
|
372
|
+
if (!this.initialized) {
|
|
373
|
+
await this.storage.initialize();
|
|
374
|
+
this.initialized = true;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
269
377
|
constructor(config) {
|
|
270
378
|
this.storage = config.storage;
|
|
271
379
|
this.embedding = config.embedding;
|
|
@@ -274,31 +382,43 @@ var MemoryStore = class {
|
|
|
274
382
|
this.summarizer = new Summarizer(config.llm, config.summarizationPrompt);
|
|
275
383
|
}
|
|
276
384
|
this.pruner = new Pruner();
|
|
277
|
-
this.compiler = new ContextCompiler();
|
|
385
|
+
this.compiler = new ContextCompiler(config.tokenCounter, config.contextImportantRatio);
|
|
386
|
+
this.limits = {
|
|
387
|
+
dedupScan: 1e3,
|
|
388
|
+
contextScan: 50,
|
|
389
|
+
summarizeScan: 1e3,
|
|
390
|
+
pruneScan: 1e4,
|
|
391
|
+
exportScan: 1e5,
|
|
392
|
+
purgeScan: 1e5,
|
|
393
|
+
...config.limits
|
|
394
|
+
};
|
|
278
395
|
}
|
|
279
396
|
async store(input) {
|
|
397
|
+
await this._ensureInit();
|
|
280
398
|
if (input.onConflict === "append") {
|
|
399
|
+
await this._ensureHashIndex();
|
|
281
400
|
const hash = this._hashContent(input.actorId, input.content);
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
401
|
+
const existingId = this.contentHashIndex.get(hash);
|
|
402
|
+
if (existingId) {
|
|
403
|
+
const match = await this.storage.get(existingId);
|
|
404
|
+
if (match) {
|
|
405
|
+
const mergedTags = [.../* @__PURE__ */ new Set([...match.tags ?? [], ...input.tags ?? []])];
|
|
406
|
+
const updated = await this.storage.store({
|
|
407
|
+
id: match.id,
|
|
408
|
+
actorId: match.actorId,
|
|
409
|
+
content: match.content,
|
|
410
|
+
memoryType: match.memoryType,
|
|
411
|
+
importance: input.importance ?? match.importance,
|
|
412
|
+
emotionalValence: input.emotionalValence ?? match.emotionalValence,
|
|
413
|
+
tags: mergedTags,
|
|
414
|
+
embedding: match.embedding,
|
|
415
|
+
sourceId: input.sourceId ?? match.sourceId,
|
|
416
|
+
metadata: { ...match.metadata, ...input.metadata },
|
|
417
|
+
expiresAt: input.expiresAt ?? match.expiresAt
|
|
418
|
+
});
|
|
419
|
+
this.contentHashIndex.set(hash, updated.id);
|
|
420
|
+
return updated;
|
|
421
|
+
}
|
|
302
422
|
}
|
|
303
423
|
}
|
|
304
424
|
let withEmbedding = input;
|
|
@@ -307,9 +427,15 @@ var MemoryStore = class {
|
|
|
307
427
|
withEmbedding = { ...input, embedding: embeddings[0] };
|
|
308
428
|
}
|
|
309
429
|
const { onConflict: _, ...clean } = withEmbedding;
|
|
310
|
-
|
|
430
|
+
const memory = await this.storage.store(clean);
|
|
431
|
+
if (this.contentHashIndex !== null) {
|
|
432
|
+
const hash = this._hashContent(memory.actorId, memory.content);
|
|
433
|
+
this.contentHashIndex.set(hash, memory.id);
|
|
434
|
+
}
|
|
435
|
+
return memory;
|
|
311
436
|
}
|
|
312
437
|
async storeBatch(inputs) {
|
|
438
|
+
await this._ensureInit();
|
|
313
439
|
const appendInputs = inputs.filter((i) => i.onConflict === "append");
|
|
314
440
|
const normalInputs = inputs.filter((i) => i.onConflict !== "append");
|
|
315
441
|
const results = [];
|
|
@@ -317,74 +443,90 @@ var MemoryStore = class {
|
|
|
317
443
|
results.push(await this.store(input));
|
|
318
444
|
}
|
|
319
445
|
if (normalInputs.length === 0) return results;
|
|
446
|
+
let stored;
|
|
320
447
|
if (this.embedding && this.embedOnStore) {
|
|
321
|
-
|
|
322
|
-
|
|
448
|
+
const enrichedResults = [];
|
|
449
|
+
const batchSize = this.embedding.maxBatchSize ?? 2048;
|
|
450
|
+
for (let i = 0; i < normalInputs.length; i += batchSize) {
|
|
451
|
+
const batch = normalInputs.slice(i, i + batchSize);
|
|
323
452
|
const texts = batch.map((inp) => inp.content);
|
|
324
453
|
const embeddings = await this.embedding.embed(texts);
|
|
325
454
|
const enriched = batch.map((input, j) => ({ ...input, embedding: embeddings[j] }));
|
|
326
|
-
const
|
|
327
|
-
|
|
455
|
+
const batchResults = await this.storage.storeBatch(enriched);
|
|
456
|
+
enrichedResults.push(...batchResults);
|
|
328
457
|
}
|
|
458
|
+
stored = enrichedResults;
|
|
329
459
|
} else {
|
|
330
|
-
|
|
331
|
-
|
|
460
|
+
stored = await this.storage.storeBatch(normalInputs);
|
|
461
|
+
}
|
|
462
|
+
if (this.contentHashIndex !== null) {
|
|
463
|
+
for (const mem of stored) {
|
|
464
|
+
const hash = this._hashContent(mem.actorId, mem.content);
|
|
465
|
+
this.contentHashIndex.set(hash, mem.id);
|
|
466
|
+
}
|
|
332
467
|
}
|
|
468
|
+
results.push(...stored);
|
|
333
469
|
return results;
|
|
334
470
|
}
|
|
335
471
|
async get(id) {
|
|
472
|
+
await this._ensureInit();
|
|
336
473
|
return this.storage.get(id);
|
|
337
474
|
}
|
|
338
475
|
async delete(id) {
|
|
476
|
+
await this._ensureInit();
|
|
339
477
|
return this.storage.delete(id);
|
|
340
478
|
}
|
|
341
479
|
async deleteMany(ids) {
|
|
480
|
+
await this._ensureInit();
|
|
342
481
|
return this.storage.deleteMany(ids);
|
|
343
482
|
}
|
|
344
483
|
async touch(id) {
|
|
484
|
+
await this._ensureInit();
|
|
345
485
|
const memory = await this.storage.get(id);
|
|
346
486
|
if (!memory) throw notFound("Memory", id);
|
|
347
487
|
if (this.storage.touch) {
|
|
348
488
|
return this.storage.touch(id);
|
|
349
489
|
}
|
|
350
|
-
await this.storage.delete(id);
|
|
351
|
-
await this.storage.store({
|
|
352
|
-
actorId: memory.actorId,
|
|
353
|
-
content: memory.content,
|
|
354
|
-
memoryType: memory.memoryType,
|
|
355
|
-
importance: memory.importance,
|
|
356
|
-
emotionalValence: memory.emotionalValence,
|
|
357
|
-
tags: memory.tags,
|
|
358
|
-
embedding: memory.embedding,
|
|
359
|
-
sourceId: memory.sourceId,
|
|
360
|
-
metadata: memory.metadata,
|
|
361
|
-
expiresAt: memory.expiresAt
|
|
362
|
-
});
|
|
363
490
|
}
|
|
364
491
|
async count(filter) {
|
|
492
|
+
await this._ensureInit();
|
|
365
493
|
return this.storage.count(filter);
|
|
366
494
|
}
|
|
367
495
|
async retrieve(query) {
|
|
496
|
+
await this._ensureInit();
|
|
368
497
|
let embedding;
|
|
369
498
|
if (this.embedding && query.query && (query.strategy === "semantic" || query.strategy === "hybrid")) {
|
|
370
499
|
const vecs = await this.embedding.embed([query.query]);
|
|
371
500
|
embedding = vecs[0];
|
|
372
501
|
}
|
|
373
|
-
|
|
502
|
+
let results = await this.storage.retrieve(query, embedding);
|
|
503
|
+
if (query.createdAfter) {
|
|
504
|
+
results = results.filter((m) => m.createdAt >= query.createdAfter);
|
|
505
|
+
}
|
|
506
|
+
if (query.createdBefore) {
|
|
507
|
+
results = results.filter((m) => m.createdAt <= query.createdBefore);
|
|
508
|
+
}
|
|
509
|
+
return results;
|
|
374
510
|
}
|
|
375
511
|
async compileContext(options) {
|
|
512
|
+
await this._ensureInit();
|
|
376
513
|
const maxTokens = options.maxTokens ?? 2e3;
|
|
377
|
-
const memories = await this.storage.retrieve({
|
|
514
|
+
const memories = await this.storage.retrieve({
|
|
515
|
+
actorId: options.actorId,
|
|
516
|
+
limit: this.limits.contextScan,
|
|
517
|
+
strategy: options.retrieveStrategy
|
|
518
|
+
});
|
|
378
519
|
return this.compiler.compile(memories, { ...options, maxTokens });
|
|
379
520
|
}
|
|
380
521
|
async summarize(options, onError) {
|
|
522
|
+
await this._ensureInit();
|
|
381
523
|
if (!this.summarizer) {
|
|
382
524
|
throw validationError("No LLM configured for summarization");
|
|
383
525
|
}
|
|
384
526
|
const allMemories = await this.storage.retrieve({
|
|
385
527
|
actorId: options.actorId,
|
|
386
528
|
memoryTypes: options.memoryTypes ?? ["interaction"],
|
|
387
|
-
limit:
|
|
529
|
+
limit: this.limits.summarizeScan,
|
|
388
530
|
strategy: "recent"
|
|
389
531
|
});
|
|
390
532
|
let toSummarize = allMemories;
|
|
@@ -404,7 +546,7 @@ var MemoryStore = class {
|
|
|
404
546
|
}
|
|
405
547
|
let summaryContent;
|
|
406
548
|
try {
|
|
407
|
-
const result = await this.summarizer.summarize(toSummarize, options);
|
|
549
|
+
const result = await this.summarizer.summarize(toSummarize, options, options.prompt);
|
|
408
550
|
summaryContent = result.summaryContent;
|
|
409
551
|
} catch (err) {
|
|
410
552
|
onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
@@ -426,8 +568,37 @@ var MemoryStore = class {
|
|
|
426
568
|
}
|
|
427
569
|
return { summary, deletedCount };
|
|
428
570
|
}
|
|
571
|
+
async *summarizeStream(options) {
|
|
572
|
+
await this._ensureInit();
|
|
573
|
+
if (!this.summarizer) {
|
|
574
|
+
throw validationError("No LLM configured for summarization");
|
|
575
|
+
}
|
|
576
|
+
const allMemories = await this.storage.retrieve({
|
|
577
|
+
actorId: options.actorId,
|
|
578
|
+
memoryTypes: options.memoryTypes ?? ["interaction"],
|
|
579
|
+
limit: this.limits.summarizeScan,
|
|
580
|
+
strategy: "recent"
|
|
581
|
+
});
|
|
582
|
+
let toSummarize = allMemories;
|
|
583
|
+
if (options.olderThan) {
|
|
584
|
+
toSummarize = toSummarize.filter((m) => m.createdAt < options.olderThan);
|
|
585
|
+
}
|
|
586
|
+
if (options.skipMostRecent) {
|
|
587
|
+
const sorted = [...toSummarize].sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
|
|
588
|
+
toSummarize = sorted.slice(0, Math.max(0, sorted.length - options.skipMostRecent));
|
|
589
|
+
}
|
|
590
|
+
if (options.targetCount && toSummarize.length > options.targetCount) {
|
|
591
|
+
const sorted = [...toSummarize].sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
|
|
592
|
+
toSummarize = sorted.slice(0, options.targetCount);
|
|
593
|
+
}
|
|
594
|
+
if (toSummarize.length === 0) {
|
|
595
|
+
throw validationError("No memories to summarize");
|
|
596
|
+
}
|
|
597
|
+
yield* this.summarizer.summarizeStream(toSummarize, options, options.prompt);
|
|
598
|
+
}
|
|
429
599
|
async prune(strategy) {
|
|
430
|
-
|
|
600
|
+
await this._ensureInit();
|
|
601
|
+
const allMemories = await this.storage.retrieve({ limit: this.limits.pruneScan });
|
|
431
602
|
const kept = this.pruner.execute(allMemories, strategy);
|
|
432
603
|
const keptIds = new Set(kept.map((k) => k.id));
|
|
433
604
|
const prunedIds = allMemories.filter((m) => !keptIds.has(m.id)).map((m) => m.id);
|
|
@@ -438,14 +609,107 @@ var MemoryStore = class {
|
|
|
438
609
|
return { pruned: prunedIds, count };
|
|
439
610
|
}
|
|
440
611
|
async dryRunPrune(strategy) {
|
|
441
|
-
|
|
612
|
+
await this._ensureInit();
|
|
613
|
+
const allMemories = await this.storage.retrieve({ limit: this.limits.pruneScan });
|
|
442
614
|
const kept = this.pruner.execute(allMemories, strategy);
|
|
443
615
|
const keptIds = new Set(kept.map((k) => k.id));
|
|
444
616
|
const wouldPrune = allMemories.filter((m) => !keptIds.has(m.id)).map((m) => m.id);
|
|
445
617
|
return { wouldPrune, count: wouldPrune.length };
|
|
446
618
|
}
|
|
447
|
-
async export() {
|
|
448
|
-
|
|
619
|
+
async export(actorId) {
|
|
620
|
+
await this._ensureInit();
|
|
621
|
+
return this.storage.retrieve({ actorId, limit: this.limits.exportScan });
|
|
622
|
+
}
|
|
623
|
+
async stats(actorId) {
|
|
624
|
+
await this._ensureInit();
|
|
625
|
+
const memories = await this.storage.retrieve({ actorId, limit: this.limits.exportScan });
|
|
626
|
+
const now = /* @__PURE__ */ new Date();
|
|
627
|
+
const byType = {};
|
|
628
|
+
const byActor = {};
|
|
629
|
+
let totalImportance = 0;
|
|
630
|
+
let expired = 0;
|
|
631
|
+
let oldest = null;
|
|
632
|
+
let newest = null;
|
|
633
|
+
for (const m of memories) {
|
|
634
|
+
totalImportance += m.importance;
|
|
635
|
+
byType[m.memoryType] = (byType[m.memoryType] ?? 0) + 1;
|
|
636
|
+
const actor = byActor[m.actorId] ?? { count: 0, oldest: m.createdAt, newest: m.createdAt, totalImportance: 0 };
|
|
637
|
+
actor.count++;
|
|
638
|
+
actor.totalImportance += m.importance;
|
|
639
|
+
if (m.createdAt < actor.oldest) actor.oldest = m.createdAt;
|
|
640
|
+
if (m.createdAt > actor.newest) actor.newest = m.createdAt;
|
|
641
|
+
byActor[m.actorId] = actor;
|
|
642
|
+
if (m.expiresAt && m.expiresAt <= now) expired++;
|
|
643
|
+
if (!oldest || m.createdAt < oldest) oldest = m.createdAt;
|
|
644
|
+
if (!newest || m.createdAt > newest) newest = m.createdAt;
|
|
645
|
+
}
|
|
646
|
+
const byActorFinal = {};
|
|
647
|
+
for (const [id, a] of Object.entries(byActor)) {
|
|
648
|
+
byActorFinal[id] = { count: a.count, oldest: a.oldest, newest: a.newest, avgImportance: a.totalImportance / a.count };
|
|
649
|
+
}
|
|
650
|
+
return {
|
|
651
|
+
total: memories.length,
|
|
652
|
+
expired,
|
|
653
|
+
oldest,
|
|
654
|
+
newest,
|
|
655
|
+
avgImportance: memories.length > 0 ? totalImportance / memories.length : 0,
|
|
656
|
+
byType,
|
|
657
|
+
byActor: byActorFinal
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
async purgeActor(actorId) {
|
|
661
|
+
await this._ensureInit();
|
|
662
|
+
const memories = await this.storage.retrieve({ actorId, limit: this.limits.purgeScan ?? 1e5 });
|
|
663
|
+
if (memories.length === 0) return 0;
|
|
664
|
+
return this.storage.deleteMany(memories.map((m) => m.id));
|
|
665
|
+
}
|
|
666
|
+
async merge(ids) {
|
|
667
|
+
await this._ensureInit();
|
|
668
|
+
if (ids.length < 2) throw validationError("merge requires at least 2 memory IDs");
|
|
669
|
+
const memories = [];
|
|
670
|
+
for (const id of ids) {
|
|
671
|
+
const m = await this.storage.get(id);
|
|
672
|
+
if (m) memories.push(m);
|
|
673
|
+
}
|
|
674
|
+
if (memories.length < 2) throw validationError("At least 2 of the provided IDs must exist");
|
|
675
|
+
const sorted = memories.sort((a, b) => b.importance - a.importance);
|
|
676
|
+
const winner = sorted[0];
|
|
677
|
+
const rest = sorted.slice(1);
|
|
678
|
+
const mergedContent = [
|
|
679
|
+
winner.content,
|
|
680
|
+
"",
|
|
681
|
+
"--- merged from " + rest.length + " related memories ---",
|
|
682
|
+
...rest.map((m) => "- " + m.content)
|
|
683
|
+
].join("\n");
|
|
684
|
+
const mergedTags = [...new Set(memories.flatMap((m) => m.tags))];
|
|
685
|
+
const mergedMetadata = {
|
|
686
|
+
...winner.metadata,
|
|
687
|
+
mergedFrom: rest.map((m) => m.id)
|
|
688
|
+
};
|
|
689
|
+
const updated = await this.storage.store({
|
|
690
|
+
id: winner.id,
|
|
691
|
+
actorId: winner.actorId,
|
|
692
|
+
content: mergedContent,
|
|
693
|
+
memoryType: winner.memoryType,
|
|
694
|
+
importance: winner.importance,
|
|
695
|
+
emotionalValence: winner.emotionalValence,
|
|
696
|
+
tags: mergedTags,
|
|
697
|
+
embedding: winner.embedding,
|
|
698
|
+
sourceId: winner.sourceId,
|
|
699
|
+
metadata: mergedMetadata,
|
|
700
|
+
expiresAt: winner.expiresAt
|
|
701
|
+
});
|
|
702
|
+
await this.storage.deleteMany(rest.map((m) => m.id));
|
|
703
|
+
return updated;
|
|
704
|
+
}
|
|
705
|
+
async _ensureHashIndex() {
|
|
706
|
+
if (this.contentHashIndex !== null) return;
|
|
707
|
+
this.contentHashIndex = /* @__PURE__ */ new Map();
|
|
708
|
+
const existing = await this.storage.retrieve({ limit: this.limits.dedupScan });
|
|
709
|
+
for (const m of existing) {
|
|
710
|
+
const hash = this._hashContent(m.actorId, m.content);
|
|
711
|
+
this.contentHashIndex.set(hash, m.id);
|
|
712
|
+
}
|
|
449
713
|
}
|
|
450
714
|
_hashContent(actorId, content) {
|
|
451
715
|
const normalized = `${actorId}::${content.toLowerCase().trim()}`;
|
|
@@ -459,7 +723,7 @@ var MemoryStore = class {
|
|
|
459
723
|
};
|
|
460
724
|
|
|
461
725
|
// src/adapters/storage/memory.ts
|
|
462
|
-
var
|
|
726
|
+
var InMemoryStorageAdapter = class {
|
|
463
727
|
memories = /* @__PURE__ */ new Map();
|
|
464
728
|
async initialize() {
|
|
465
729
|
}
|
|
@@ -533,6 +797,12 @@ var InMemoryStorage = class {
|
|
|
533
797
|
const q = query.query.toLowerCase();
|
|
534
798
|
results = results.filter((m) => m.content.toLowerCase().includes(q));
|
|
535
799
|
}
|
|
800
|
+
if (query.createdAfter) {
|
|
801
|
+
results = results.filter((m) => m.createdAt >= query.createdAfter);
|
|
802
|
+
}
|
|
803
|
+
if (query.createdBefore) {
|
|
804
|
+
results = results.filter((m) => m.createdAt <= query.createdBefore);
|
|
805
|
+
}
|
|
536
806
|
switch (query.strategy) {
|
|
537
807
|
case "recent":
|
|
538
808
|
results.sort((a, b) => b._touchedAt.getTime() - a._touchedAt.getTime());
|
|
@@ -584,13 +854,14 @@ var MemStack = class {
|
|
|
584
854
|
memory;
|
|
585
855
|
config;
|
|
586
856
|
storage;
|
|
587
|
-
|
|
857
|
+
processCounts = /* @__PURE__ */ new Map();
|
|
858
|
+
pruneCheckCount = 0;
|
|
588
859
|
constructor(config) {
|
|
589
860
|
if (!config.llm) {
|
|
590
861
|
throw configError("LLM provider is required");
|
|
591
862
|
}
|
|
592
863
|
this.config = config;
|
|
593
|
-
this.storage = config.storage ?? new
|
|
864
|
+
this.storage = config.storage ?? new InMemoryStorageAdapter();
|
|
594
865
|
this.memory = new MemoryStore({
|
|
595
866
|
storage: this.storage,
|
|
596
867
|
embedding: config.embedding,
|
|
@@ -656,35 +927,34 @@ var MemStack = class {
|
|
|
656
927
|
};
|
|
657
928
|
const memory = await this.memory.store(memoryInput);
|
|
658
929
|
this.config.hooks?.onMemoryStored?.(memory);
|
|
659
|
-
this.
|
|
930
|
+
const count = (this.processCounts.get(input.actorId) ?? 0) + 1;
|
|
931
|
+
this.processCounts.set(input.actorId, count);
|
|
660
932
|
let summaryCreated;
|
|
661
933
|
const threshold = this.config.defaults?.summarizationThreshold ?? 100;
|
|
662
|
-
if (threshold > 0) {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
"summarize"
|
|
681
|
-
);
|
|
682
|
-
}
|
|
934
|
+
if (threshold > 0 && count % threshold === 0) {
|
|
935
|
+
try {
|
|
936
|
+
const result = await this.memory.summarize(
|
|
937
|
+
{
|
|
938
|
+
actorId: input.actorId,
|
|
939
|
+
memoryTypes: ["interaction"],
|
|
940
|
+
skipMostRecent: 5,
|
|
941
|
+
targetCount: Math.min(15, count)
|
|
942
|
+
},
|
|
943
|
+
this.config.hooks?.onError ? (err) => this.config.hooks.onError(err, "summarize") : void 0
|
|
944
|
+
);
|
|
945
|
+
summaryCreated = result.summary;
|
|
946
|
+
this.config.hooks?.onSummaryCreated?.(result.summary, result.deletedCount);
|
|
947
|
+
} catch (err) {
|
|
948
|
+
this.config.hooks?.onError?.(
|
|
949
|
+
err instanceof Error ? err : new Error(String(err)),
|
|
950
|
+
"summarize"
|
|
951
|
+
);
|
|
683
952
|
}
|
|
684
953
|
}
|
|
954
|
+
this.pruneCheckCount++;
|
|
685
955
|
const pruneStrategy = this.config.defaults?.pruneStrategy;
|
|
686
956
|
const pruneInterval = this.config.defaults?.pruneInterval ?? 100;
|
|
687
|
-
if (pruneStrategy && this.
|
|
957
|
+
if (pruneStrategy && this.pruneCheckCount % pruneInterval === 0) {
|
|
688
958
|
try {
|
|
689
959
|
const pruneResult = await this.memory.prune(pruneStrategy);
|
|
690
960
|
if (pruneResult.count > 0) {
|
|
@@ -699,8 +969,8 @@ var MemStack = class {
|
|
|
699
969
|
}
|
|
700
970
|
return { memory, summaryCreated };
|
|
701
971
|
}
|
|
702
|
-
async export() {
|
|
703
|
-
const memories = await this.memory.export();
|
|
972
|
+
async export(actorId) {
|
|
973
|
+
const memories = await this.memory.export(actorId);
|
|
704
974
|
return {
|
|
705
975
|
version: 1,
|
|
706
976
|
memories,
|
|
@@ -770,14 +1040,16 @@ var MemStack = class {
|
|
|
770
1040
|
return cleaned;
|
|
771
1041
|
}
|
|
772
1042
|
};
|
|
773
|
-
var
|
|
1043
|
+
var DiskStorageAdapter = class {
|
|
774
1044
|
dir;
|
|
775
1045
|
_writeLocks = /* @__PURE__ */ new Map();
|
|
1046
|
+
_idIndex = /* @__PURE__ */ new Map();
|
|
776
1047
|
constructor(config = {}) {
|
|
777
1048
|
this.dir = config.storageDir ?? "./memstack-data";
|
|
778
1049
|
}
|
|
779
1050
|
async initialize() {
|
|
780
1051
|
await mkdir(this.dir, { recursive: true });
|
|
1052
|
+
await this._buildIndex();
|
|
781
1053
|
}
|
|
782
1054
|
generateId() {
|
|
783
1055
|
return `mem_${Date.now().toString(36)}_${crypto.randomUUID().slice(0, 8)}`;
|
|
@@ -807,6 +1079,7 @@ var DiskStorage = class {
|
|
|
807
1079
|
} else {
|
|
808
1080
|
records.push(memory);
|
|
809
1081
|
}
|
|
1082
|
+
this._idIndex.set(memory.id, input.actorId);
|
|
810
1083
|
await this._writeFile(input.actorId, records);
|
|
811
1084
|
});
|
|
812
1085
|
return this._toExternal(memory);
|
|
@@ -846,6 +1119,7 @@ var DiskStorage = class {
|
|
|
846
1119
|
} else {
|
|
847
1120
|
existing.push(mem);
|
|
848
1121
|
}
|
|
1122
|
+
this._idIndex.set(mem.id, actorId);
|
|
849
1123
|
}
|
|
850
1124
|
await this._writeFile(actorId, existing);
|
|
851
1125
|
});
|
|
@@ -853,8 +1127,8 @@ var DiskStorage = class {
|
|
|
853
1127
|
return all.map((m) => this._toExternal(m));
|
|
854
1128
|
}
|
|
855
1129
|
async get(id) {
|
|
856
|
-
const
|
|
857
|
-
|
|
1130
|
+
const actorId = this._idIndex.get(id);
|
|
1131
|
+
if (actorId) {
|
|
858
1132
|
const records = await this._readFile(actorId);
|
|
859
1133
|
const record = records.find((r) => r.id === id);
|
|
860
1134
|
if (record) {
|
|
@@ -862,26 +1136,43 @@ var DiskStorage = class {
|
|
|
862
1136
|
return this._toExternal(record);
|
|
863
1137
|
}
|
|
864
1138
|
}
|
|
1139
|
+
const actorIds = await this._listActors();
|
|
1140
|
+
for (const aid of actorIds) {
|
|
1141
|
+
const records = await this._readFile(aid);
|
|
1142
|
+
const record = records.find((r) => r.id === id);
|
|
1143
|
+
if (record) {
|
|
1144
|
+
this._idIndex.set(id, aid);
|
|
1145
|
+
if (record.expiresAt && new Date(record.expiresAt) <= /* @__PURE__ */ new Date()) return null;
|
|
1146
|
+
return this._toExternal(record);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
865
1149
|
return null;
|
|
866
1150
|
}
|
|
867
1151
|
async delete(id) {
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
const
|
|
871
|
-
const
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
fresh.splice(i, 1);
|
|
878
|
-
await this._writeFile(actorId, fresh);
|
|
879
|
-
}
|
|
880
|
-
});
|
|
881
|
-
return;
|
|
1152
|
+
let actorId = this._idIndex.get(id);
|
|
1153
|
+
if (!actorId) {
|
|
1154
|
+
const actorIds = await this._listActors();
|
|
1155
|
+
for (const aid of actorIds) {
|
|
1156
|
+
const records = await this._readFile(aid);
|
|
1157
|
+
if (records.some((r) => r.id === id)) {
|
|
1158
|
+
actorId = aid;
|
|
1159
|
+
break;
|
|
1160
|
+
}
|
|
882
1161
|
}
|
|
883
1162
|
}
|
|
884
|
-
throw notFound("Memory", id);
|
|
1163
|
+
if (!actorId) throw notFound("Memory", id);
|
|
1164
|
+
let deleted = false;
|
|
1165
|
+
await this._withWriteLock(actorId, async () => {
|
|
1166
|
+
const fresh = await this._readFile(actorId);
|
|
1167
|
+
const i = fresh.findIndex((r) => r.id === id);
|
|
1168
|
+
if (i !== -1) {
|
|
1169
|
+
fresh.splice(i, 1);
|
|
1170
|
+
this._idIndex.delete(id);
|
|
1171
|
+
await this._writeFile(actorId, fresh);
|
|
1172
|
+
deleted = true;
|
|
1173
|
+
}
|
|
1174
|
+
});
|
|
1175
|
+
if (!deleted) throw notFound("Memory", id);
|
|
885
1176
|
}
|
|
886
1177
|
async deleteMany(ids) {
|
|
887
1178
|
const idSet = new Set(ids);
|
|
@@ -894,6 +1185,9 @@ var DiskStorage = class {
|
|
|
894
1185
|
const filtered = records.filter((r) => !idSet.has(r.id));
|
|
895
1186
|
count += before - filtered.length;
|
|
896
1187
|
if (before !== filtered.length) {
|
|
1188
|
+
for (const r of records) {
|
|
1189
|
+
if (idSet.has(r.id)) this._idIndex.delete(r.id);
|
|
1190
|
+
}
|
|
897
1191
|
await this._writeFile(actorId, filtered);
|
|
898
1192
|
}
|
|
899
1193
|
});
|
|
@@ -976,24 +1270,16 @@ var DiskStorage = class {
|
|
|
976
1270
|
return total;
|
|
977
1271
|
}
|
|
978
1272
|
async touch(id) {
|
|
979
|
-
const
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
match._touchedAt = nowStr;
|
|
990
|
-
await this._writeFile(actorId, fresh);
|
|
991
|
-
}
|
|
992
|
-
});
|
|
993
|
-
return;
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
throw notFound("Memory", id);
|
|
1273
|
+
const actorId = this._idIndex.get(id);
|
|
1274
|
+
if (!actorId) throw notFound("Memory", id);
|
|
1275
|
+
const nowStr = (/* @__PURE__ */ new Date()).toISOString();
|
|
1276
|
+
await this._withWriteLock(actorId, async () => {
|
|
1277
|
+
const fresh = await this._readFile(actorId);
|
|
1278
|
+
const match = fresh.find((r) => r.id === id);
|
|
1279
|
+
if (!match) throw notFound("Memory", id);
|
|
1280
|
+
match._touchedAt = nowStr;
|
|
1281
|
+
await this._writeFile(actorId, fresh);
|
|
1282
|
+
});
|
|
997
1283
|
}
|
|
998
1284
|
async close() {
|
|
999
1285
|
this._writeLocks.clear();
|
|
@@ -1046,6 +1332,15 @@ var DiskStorage = class {
|
|
|
1046
1332
|
throw storageError(`Failed to list actors: ${err.message}`);
|
|
1047
1333
|
}
|
|
1048
1334
|
}
|
|
1335
|
+
async _buildIndex() {
|
|
1336
|
+
const actorIds = await this._listActors();
|
|
1337
|
+
for (const actorId of actorIds) {
|
|
1338
|
+
const records = await this._readFile(actorId);
|
|
1339
|
+
for (const r of records) {
|
|
1340
|
+
this._idIndex.set(r.id, actorId);
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1049
1344
|
_toExternal(record) {
|
|
1050
1345
|
const { _touchedAt: _, ...rest } = record;
|
|
1051
1346
|
return {
|
|
@@ -1055,16 +1350,18 @@ var DiskStorage = class {
|
|
|
1055
1350
|
};
|
|
1056
1351
|
}
|
|
1057
1352
|
};
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1353
|
+
var MarkdownStorageAdapter = class {
|
|
1354
|
+
dir;
|
|
1355
|
+
oneFilePerActor;
|
|
1356
|
+
_cache = null;
|
|
1357
|
+
_deletedIds = /* @__PURE__ */ new Set();
|
|
1358
|
+
_cacheDirty = true;
|
|
1063
1359
|
constructor(config) {
|
|
1064
|
-
this.
|
|
1065
|
-
this.
|
|
1360
|
+
this.dir = config.dir;
|
|
1361
|
+
this.oneFilePerActor = config.oneFilePerActor ?? true;
|
|
1066
1362
|
}
|
|
1067
1363
|
async initialize() {
|
|
1364
|
+
await mkdir(this.dir, { recursive: true });
|
|
1068
1365
|
}
|
|
1069
1366
|
generateId() {
|
|
1070
1367
|
return `mem_${Date.now().toString(36)}_${crypto.randomUUID().slice(0, 8)}`;
|
|
@@ -1083,99 +1380,89 @@ var RedisStorage = class {
|
|
|
1083
1380
|
sourceId: input.sourceId,
|
|
1084
1381
|
metadata: input.metadata ?? {},
|
|
1085
1382
|
expiresAt: input.expiresAt,
|
|
1086
|
-
createdAt: now
|
|
1087
|
-
_touchedAt: now.toISOString()
|
|
1383
|
+
createdAt: now
|
|
1088
1384
|
};
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
await
|
|
1092
|
-
|
|
1385
|
+
const filePath = this._filePath(input.actorId);
|
|
1386
|
+
const block = this._formatBlock(memory);
|
|
1387
|
+
await appendFile(filePath, block, "utf-8");
|
|
1388
|
+
this._cacheDirty = true;
|
|
1389
|
+
return memory;
|
|
1093
1390
|
}
|
|
1094
1391
|
async storeBatch(inputs) {
|
|
1095
|
-
|
|
1096
|
-
const now = /* @__PURE__ */ new Date();
|
|
1392
|
+
const results = [];
|
|
1097
1393
|
for (const input of inputs) {
|
|
1098
|
-
|
|
1099
|
-
id: input.id ?? this.generateId(),
|
|
1100
|
-
actorId: input.actorId,
|
|
1101
|
-
memoryType: input.memoryType ?? "interaction",
|
|
1102
|
-
content: input.content,
|
|
1103
|
-
importance: input.importance ?? 0.5,
|
|
1104
|
-
emotionalValence: input.emotionalValence ?? 0,
|
|
1105
|
-
tags: input.tags ?? [],
|
|
1106
|
-
embedding: input.embedding,
|
|
1107
|
-
sourceId: input.sourceId,
|
|
1108
|
-
metadata: input.metadata ?? {},
|
|
1109
|
-
expiresAt: input.expiresAt,
|
|
1110
|
-
createdAt: now,
|
|
1111
|
-
_touchedAt: now.toISOString()
|
|
1112
|
-
};
|
|
1113
|
-
await this.redis.set(this._memKey(memory.id), JSON.stringify(memory));
|
|
1114
|
-
await this.redis.sadd(this._actorKey(memory.actorId), memory.id);
|
|
1115
|
-
await this.redis.sadd(this._actorsSetKey(), memory.actorId);
|
|
1116
|
-
results.push(this._toExternal(memory));
|
|
1394
|
+
results.push(await this.store(input));
|
|
1117
1395
|
}
|
|
1118
1396
|
return results;
|
|
1119
1397
|
}
|
|
1120
1398
|
async get(id) {
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
if (
|
|
1125
|
-
|
|
1399
|
+
await this._ensureCache();
|
|
1400
|
+
const memory = this._cache.get(id);
|
|
1401
|
+
if (!memory) return null;
|
|
1402
|
+
if (memory.expiresAt && memory.expiresAt <= /* @__PURE__ */ new Date()) return null;
|
|
1403
|
+
if (this._deletedIds.has(id)) return null;
|
|
1404
|
+
return memory;
|
|
1126
1405
|
}
|
|
1127
1406
|
async delete(id) {
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1407
|
+
await this._ensureCache();
|
|
1408
|
+
const memory = this._cache.get(id);
|
|
1409
|
+
if (!memory || this._deletedIds.has(id)) {
|
|
1410
|
+
throw notFound("Memory", id);
|
|
1411
|
+
}
|
|
1412
|
+
if (memory.expiresAt && memory.expiresAt <= /* @__PURE__ */ new Date()) {
|
|
1413
|
+
throw notFound("Memory", id);
|
|
1414
|
+
}
|
|
1415
|
+
const filePath = this._filePath(memory.actorId);
|
|
1416
|
+
const tombstone = this._formatTombstone(id);
|
|
1417
|
+
await appendFile(filePath, tombstone, "utf-8");
|
|
1418
|
+
this._cacheDirty = true;
|
|
1133
1419
|
}
|
|
1134
1420
|
async deleteMany(ids) {
|
|
1135
|
-
|
|
1136
|
-
|
|
1421
|
+
await this._ensureCache();
|
|
1422
|
+
new Set(ids);
|
|
1423
|
+
const toDelete = [];
|
|
1137
1424
|
for (const id of ids) {
|
|
1138
|
-
const
|
|
1139
|
-
if (
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
byActor.set(record.actorId, list);
|
|
1425
|
+
const memory = this._cache.get(id);
|
|
1426
|
+
if (memory && !this._deletedIds.has(id)) {
|
|
1427
|
+
if (!memory.expiresAt || memory.expiresAt > /* @__PURE__ */ new Date()) {
|
|
1428
|
+
toDelete.push({ id, actorId: memory.actorId });
|
|
1429
|
+
}
|
|
1144
1430
|
}
|
|
1145
1431
|
}
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1432
|
+
const byActor = /* @__PURE__ */ new Map();
|
|
1433
|
+
for (const { id, actorId } of toDelete) {
|
|
1434
|
+
const group = byActor.get(actorId) ?? [];
|
|
1435
|
+
group.push(id);
|
|
1436
|
+
byActor.set(actorId, group);
|
|
1150
1437
|
}
|
|
1151
|
-
|
|
1438
|
+
for (const [actorId, groupIds] of byActor) {
|
|
1439
|
+
const filePath = this._filePath(actorId);
|
|
1440
|
+
const blocks = groupIds.map((gid) => this._formatTombstone(gid)).join("");
|
|
1441
|
+
await appendFile(filePath, blocks, "utf-8");
|
|
1442
|
+
}
|
|
1443
|
+
this._cacheDirty = true;
|
|
1444
|
+
return toDelete.length;
|
|
1152
1445
|
}
|
|
1153
1446
|
async retrieve(query, _embedding) {
|
|
1154
|
-
|
|
1447
|
+
await this._ensureCache();
|
|
1448
|
+
const now = /* @__PURE__ */ new Date();
|
|
1449
|
+
let results = Array.from(this._cache.values()).filter((m) => !this._deletedIds.has(m.id)).filter((m) => !m.expiresAt || m.expiresAt > now);
|
|
1155
1450
|
if (query.actorId) {
|
|
1156
|
-
|
|
1157
|
-
} else {
|
|
1158
|
-
const actorIds = await this._listActors();
|
|
1159
|
-
for (const actorId of actorIds) {
|
|
1160
|
-
const records = await this._getActorRecords(actorId);
|
|
1161
|
-
allRecords.push(...records);
|
|
1162
|
-
}
|
|
1451
|
+
results = results.filter((m) => m.actorId === query.actorId);
|
|
1163
1452
|
}
|
|
1164
|
-
const now = /* @__PURE__ */ new Date();
|
|
1165
|
-
let results = allRecords.filter((r) => !r.expiresAt || new Date(r.expiresAt) > now);
|
|
1166
1453
|
if (query.memoryTypes && query.memoryTypes.length > 0) {
|
|
1167
|
-
results = results.filter((
|
|
1454
|
+
results = results.filter((m) => query.memoryTypes.includes(m.memoryType));
|
|
1168
1455
|
}
|
|
1169
1456
|
if (query.tags && query.tags.length > 0) {
|
|
1170
|
-
results = results.filter((
|
|
1457
|
+
results = results.filter((m) => m.tags?.some((t) => query.tags.includes(t)));
|
|
1171
1458
|
}
|
|
1172
1459
|
if (query.query) {
|
|
1173
1460
|
const q = query.query.toLowerCase();
|
|
1174
|
-
results = results.filter((
|
|
1461
|
+
results = results.filter((m) => m.content.toLowerCase().includes(q));
|
|
1175
1462
|
}
|
|
1176
1463
|
switch (query.strategy) {
|
|
1177
1464
|
case "recent":
|
|
1178
|
-
results.sort((a, b) =>
|
|
1465
|
+
results.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
|
1179
1466
|
break;
|
|
1180
1467
|
case "important":
|
|
1181
1468
|
results.sort((a, b) => b.importance - a.importance);
|
|
@@ -1184,97 +1471,472 @@ var RedisStorage = class {
|
|
|
1184
1471
|
case "semantic":
|
|
1185
1472
|
default:
|
|
1186
1473
|
results.sort(
|
|
1187
|
-
(a, b) => b.importance - a.importance ||
|
|
1474
|
+
(a, b) => b.importance - a.importance || b.createdAt.getTime() - a.createdAt.getTime()
|
|
1188
1475
|
);
|
|
1189
1476
|
break;
|
|
1190
1477
|
}
|
|
1191
|
-
const nowStr = (/* @__PURE__ */ new Date()).toISOString();
|
|
1192
|
-
const toTouch = results.slice(0, query.limit ?? 10);
|
|
1193
|
-
for (const r of toTouch) {
|
|
1194
|
-
r._touchedAt = nowStr;
|
|
1195
|
-
await this.redis.set(this._memKey(r.id), JSON.stringify(r));
|
|
1196
|
-
}
|
|
1197
1478
|
const limit = query.limit ?? 10;
|
|
1198
|
-
return results.slice(0, limit)
|
|
1479
|
+
return results.slice(0, limit);
|
|
1199
1480
|
}
|
|
1200
1481
|
async count(filter) {
|
|
1201
|
-
|
|
1482
|
+
await this._ensureCache();
|
|
1483
|
+
const now = /* @__PURE__ */ new Date();
|
|
1484
|
+
let results = Array.from(this._cache.values()).filter((m) => !this._deletedIds.has(m.id)).filter((m) => !m.expiresAt || m.expiresAt > now);
|
|
1202
1485
|
if (filter?.actorId) {
|
|
1203
|
-
|
|
1204
|
-
} else {
|
|
1205
|
-
const actorIds = await this._listActors();
|
|
1206
|
-
for (const actorId of actorIds) {
|
|
1207
|
-
const records = await this._getActorRecords(actorId);
|
|
1208
|
-
allRecords.push(...records);
|
|
1209
|
-
}
|
|
1486
|
+
results = results.filter((m) => m.actorId === filter.actorId);
|
|
1210
1487
|
}
|
|
1211
|
-
const now = /* @__PURE__ */ new Date();
|
|
1212
|
-
let results = allRecords.filter((r) => !r.expiresAt || new Date(r.expiresAt) > now);
|
|
1213
1488
|
if (filter?.memoryType) {
|
|
1214
|
-
results = results.filter((
|
|
1489
|
+
results = results.filter((m) => m.memoryType === filter.memoryType);
|
|
1215
1490
|
}
|
|
1216
1491
|
if (filter?.minImportance !== void 0) {
|
|
1217
|
-
results = results.filter((
|
|
1492
|
+
results = results.filter((m) => m.importance >= filter.minImportance);
|
|
1218
1493
|
}
|
|
1219
1494
|
return results.length;
|
|
1220
1495
|
}
|
|
1221
1496
|
async close() {
|
|
1497
|
+
this._cache = null;
|
|
1498
|
+
this._deletedIds.clear();
|
|
1499
|
+
this._cacheDirty = true;
|
|
1222
1500
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1501
|
+
async vacuum() {
|
|
1502
|
+
if (this.oneFilePerActor) {
|
|
1503
|
+
const actorIds = await this._listActors();
|
|
1504
|
+
for (const actorId of actorIds) {
|
|
1505
|
+
await this._vacuumFile(this._filePath(actorId));
|
|
1506
|
+
}
|
|
1507
|
+
} else {
|
|
1508
|
+
await this._vacuumFile(this._filePath(""));
|
|
1509
|
+
}
|
|
1510
|
+
this._cacheDirty = true;
|
|
1226
1511
|
}
|
|
1227
|
-
|
|
1512
|
+
// ── Internal ──
|
|
1513
|
+
_filePath(actorId) {
|
|
1514
|
+
if (!this.oneFilePerActor) {
|
|
1515
|
+
return join(this.dir, "memories.md");
|
|
1516
|
+
}
|
|
1228
1517
|
const safe = actorId.replace(/[^a-zA-Z0-9_\-.,@]/g, "_");
|
|
1229
|
-
return
|
|
1518
|
+
return join(this.dir, `${safe}.md`);
|
|
1230
1519
|
}
|
|
1231
|
-
|
|
1232
|
-
|
|
1520
|
+
_formatBlock(memory) {
|
|
1521
|
+
const iso = memory.createdAt.toISOString();
|
|
1522
|
+
const meta = JSON.stringify({
|
|
1523
|
+
id: memory.id,
|
|
1524
|
+
actorId: memory.actorId,
|
|
1525
|
+
memoryType: memory.memoryType,
|
|
1526
|
+
importance: memory.importance,
|
|
1527
|
+
emotionalValence: memory.emotionalValence,
|
|
1528
|
+
tags: memory.tags,
|
|
1529
|
+
embedding: memory.embedding,
|
|
1530
|
+
sourceId: memory.sourceId ?? null,
|
|
1531
|
+
metadata: memory.metadata ?? {},
|
|
1532
|
+
createdAt: memory.createdAt.toISOString(),
|
|
1533
|
+
expiresAt: memory.expiresAt?.toISOString() ?? null
|
|
1534
|
+
});
|
|
1535
|
+
return `## ${iso}
|
|
1536
|
+
|
|
1537
|
+
~~~json
|
|
1538
|
+
${meta}
|
|
1539
|
+
~~~
|
|
1540
|
+
|
|
1541
|
+
${memory.content}
|
|
1542
|
+
|
|
1543
|
+
---
|
|
1544
|
+
`;
|
|
1233
1545
|
}
|
|
1234
|
-
|
|
1235
|
-
const
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1546
|
+
_formatTombstone(id) {
|
|
1547
|
+
const iso = (/* @__PURE__ */ new Date()).toISOString();
|
|
1548
|
+
return `## ${iso}
|
|
1549
|
+
|
|
1550
|
+
<!-- deleted: ${id} -->
|
|
1551
|
+
|
|
1552
|
+
---
|
|
1553
|
+
`;
|
|
1554
|
+
}
|
|
1555
|
+
async _ensureCache() {
|
|
1556
|
+
if (!this._cacheDirty) return;
|
|
1557
|
+
const allMemories = /* @__PURE__ */ new Map();
|
|
1558
|
+
const allDeletedIds = /* @__PURE__ */ new Set();
|
|
1559
|
+
if (this.oneFilePerActor) {
|
|
1560
|
+
const actorIds = await this._listActors();
|
|
1561
|
+
for (const actorId of actorIds) {
|
|
1562
|
+
const { memories, deletedIds } = await this._parseFile(this._filePath(actorId));
|
|
1563
|
+
for (const mem of memories) {
|
|
1564
|
+
allMemories.set(mem.id, mem);
|
|
1565
|
+
}
|
|
1566
|
+
for (const did of deletedIds) {
|
|
1567
|
+
allDeletedIds.add(did);
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
} else {
|
|
1571
|
+
const { memories, deletedIds } = await this._parseFile(this._filePath(""));
|
|
1572
|
+
for (const mem of memories) {
|
|
1573
|
+
allMemories.set(mem.id, mem);
|
|
1574
|
+
}
|
|
1575
|
+
for (const did of deletedIds) {
|
|
1576
|
+
allDeletedIds.add(did);
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
this._cache = allMemories;
|
|
1580
|
+
this._deletedIds = allDeletedIds;
|
|
1581
|
+
this._cacheDirty = false;
|
|
1582
|
+
}
|
|
1583
|
+
async _parseFile(filePath) {
|
|
1584
|
+
let raw;
|
|
1585
|
+
try {
|
|
1586
|
+
raw = await readFile(filePath, "utf-8");
|
|
1587
|
+
} catch (err) {
|
|
1588
|
+
if (err.code === "ENOENT") {
|
|
1589
|
+
return { memories: [], deletedIds: [] };
|
|
1590
|
+
}
|
|
1591
|
+
throw storageError(`Failed to read file ${filePath}: ${err.message}`);
|
|
1592
|
+
}
|
|
1593
|
+
const memories = [];
|
|
1594
|
+
const deletedIds = [];
|
|
1595
|
+
const sections = raw.split("\n---\n");
|
|
1596
|
+
for (const section of sections) {
|
|
1597
|
+
const trimmed = section.trim();
|
|
1598
|
+
if (!trimmed) continue;
|
|
1599
|
+
const cleanBlock = trimmed.replace(/\n---$/, "");
|
|
1600
|
+
const lines = cleanBlock.split("\n");
|
|
1601
|
+
let i = 0;
|
|
1602
|
+
if (!lines[i]?.startsWith("## ")) continue;
|
|
1603
|
+
i++;
|
|
1604
|
+
while (i < lines.length && lines[i].trim() === "") i++;
|
|
1605
|
+
if (i >= lines.length) continue;
|
|
1606
|
+
const tombstoneMatch = lines[i].trim().match(/^<!-- deleted:\s*(\S+)\s*-->/);
|
|
1607
|
+
if (tombstoneMatch) {
|
|
1608
|
+
deletedIds.push(tombstoneMatch[1]);
|
|
1609
|
+
continue;
|
|
1610
|
+
}
|
|
1611
|
+
if (lines[i].trim() !== "~~~json") continue;
|
|
1612
|
+
i++;
|
|
1613
|
+
const jsonLines = [];
|
|
1614
|
+
while (i < lines.length && lines[i].trim() !== "~~~") {
|
|
1615
|
+
jsonLines.push(lines[i]);
|
|
1616
|
+
i++;
|
|
1617
|
+
}
|
|
1618
|
+
i++;
|
|
1619
|
+
let metadata = null;
|
|
1620
|
+
try {
|
|
1621
|
+
metadata = JSON.parse(jsonLines.join("\n"));
|
|
1622
|
+
} catch {
|
|
1623
|
+
continue;
|
|
1624
|
+
}
|
|
1625
|
+
while (i < lines.length && lines[i].trim() === "") i++;
|
|
1626
|
+
const contentLines = [];
|
|
1627
|
+
while (i < lines.length) {
|
|
1628
|
+
contentLines.push(lines[i]);
|
|
1629
|
+
i++;
|
|
1630
|
+
}
|
|
1631
|
+
const content = contentLines.join("\n").trim();
|
|
1632
|
+
if (metadata && typeof metadata.id === "string") {
|
|
1633
|
+
memories.push({
|
|
1634
|
+
id: metadata.id,
|
|
1635
|
+
actorId: metadata.actorId,
|
|
1636
|
+
memoryType: metadata.memoryType ?? "interaction",
|
|
1637
|
+
content,
|
|
1638
|
+
importance: metadata.importance ?? 0.5,
|
|
1639
|
+
emotionalValence: metadata.emotionalValence ?? 0,
|
|
1640
|
+
tags: metadata.tags ?? [],
|
|
1641
|
+
embedding: metadata.embedding,
|
|
1642
|
+
sourceId: metadata.sourceId ?? void 0,
|
|
1643
|
+
metadata: metadata.metadata ?? {},
|
|
1644
|
+
createdAt: new Date(metadata.createdAt),
|
|
1645
|
+
expiresAt: metadata.expiresAt ? new Date(metadata.expiresAt) : void 0
|
|
1646
|
+
});
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
return { memories, deletedIds };
|
|
1650
|
+
}
|
|
1651
|
+
async _vacuumFile(filePath) {
|
|
1652
|
+
await this._ensureCache();
|
|
1653
|
+
let raw;
|
|
1654
|
+
try {
|
|
1655
|
+
raw = await readFile(filePath, "utf-8");
|
|
1656
|
+
} catch (err) {
|
|
1657
|
+
if (err.code === "ENOENT") return;
|
|
1658
|
+
throw storageError(`Failed to read file for vacuum: ${err.message}`);
|
|
1659
|
+
}
|
|
1660
|
+
const sections = raw.split("\n---\n");
|
|
1661
|
+
const kept = [];
|
|
1662
|
+
for (const section of sections) {
|
|
1663
|
+
const trimmed = section.trim();
|
|
1664
|
+
if (!trimmed) continue;
|
|
1665
|
+
const cleanBlock = trimmed.replace(/\n---$/, "");
|
|
1666
|
+
const lines = cleanBlock.split("\n");
|
|
1667
|
+
let i = 0;
|
|
1668
|
+
if (!lines[i]?.startsWith("## ")) {
|
|
1669
|
+
kept.push(trimmed);
|
|
1670
|
+
continue;
|
|
1671
|
+
}
|
|
1672
|
+
i++;
|
|
1673
|
+
while (i < lines.length && lines[i].trim() === "") i++;
|
|
1674
|
+
if (i >= lines.length) {
|
|
1675
|
+
kept.push(trimmed);
|
|
1676
|
+
continue;
|
|
1677
|
+
}
|
|
1678
|
+
const tombstoneMatch = lines[i].trim().match(/^<!-- deleted:\s*(\S+)\s*-->/);
|
|
1679
|
+
if (tombstoneMatch) continue;
|
|
1680
|
+
if (lines[i].trim() !== "~~~json") {
|
|
1681
|
+
kept.push(trimmed);
|
|
1682
|
+
continue;
|
|
1683
|
+
}
|
|
1684
|
+
i++;
|
|
1685
|
+
while (i < lines.length && lines[i].trim() !== "~~~") i++;
|
|
1686
|
+
i++;
|
|
1687
|
+
while (i < lines.length && lines[i].trim() === "") i++;
|
|
1688
|
+
const contentLines = [];
|
|
1689
|
+
while (i < lines.length) {
|
|
1690
|
+
contentLines.push(lines[i]);
|
|
1691
|
+
i++;
|
|
1692
|
+
}
|
|
1693
|
+
try {
|
|
1694
|
+
const jsonStartIdx = lines.findIndex((l) => l.trim() === "~~~json");
|
|
1695
|
+
const jsonEndIdx = lines.findIndex((l, idx) => idx > jsonStartIdx && l.trim() === "~~~");
|
|
1696
|
+
const jsonText = lines.slice(jsonStartIdx + 1, jsonEndIdx).join("\n");
|
|
1697
|
+
const meta = JSON.parse(jsonText);
|
|
1698
|
+
if (this._deletedIds.has(meta.id)) continue;
|
|
1699
|
+
} catch {
|
|
1700
|
+
}
|
|
1701
|
+
const content = contentLines.join("\n").trim();
|
|
1702
|
+
if (content || trimmed.includes("~~~json")) {
|
|
1703
|
+
kept.push(trimmed);
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
const output = kept.length > 0 ? kept.join("\n---\n") + "\n---\n" : "";
|
|
1707
|
+
await writeFile(filePath, output, "utf-8");
|
|
1708
|
+
}
|
|
1709
|
+
async _listActors() {
|
|
1710
|
+
try {
|
|
1711
|
+
const entries = await readdir(this.dir);
|
|
1712
|
+
return entries.filter((e) => e.endsWith(".md")).map((e) => e.slice(0, -3));
|
|
1713
|
+
} catch (err) {
|
|
1714
|
+
if (err.code === "ENOENT") return [];
|
|
1715
|
+
throw storageError(`Failed to list actors: ${err.message}`);
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
};
|
|
1719
|
+
|
|
1720
|
+
// src/adapters/storage/hybrid.ts
|
|
1721
|
+
var HybridStorageAdapter = class {
|
|
1722
|
+
cache;
|
|
1723
|
+
durable;
|
|
1724
|
+
writeBoth;
|
|
1725
|
+
readFromCache;
|
|
1726
|
+
syncOnInit;
|
|
1727
|
+
constructor(config) {
|
|
1728
|
+
this.cache = config.cache;
|
|
1729
|
+
this.durable = config.durable;
|
|
1730
|
+
this.writeBoth = config.writeBoth ?? true;
|
|
1731
|
+
this.readFromCache = config.readFromCache ?? true;
|
|
1732
|
+
this.syncOnInit = config.syncOnInit ?? false;
|
|
1733
|
+
}
|
|
1734
|
+
async initialize() {
|
|
1735
|
+
await Promise.all([this.cache.initialize(), this.durable.initialize()]);
|
|
1736
|
+
if (this.syncOnInit) {
|
|
1737
|
+
const results = await this.durable.retrieve({ limit: 1e5 });
|
|
1738
|
+
if (results.length > 0) {
|
|
1739
|
+
await this.cache.storeBatch(results.map((m) => ({
|
|
1740
|
+
actorId: m.actorId,
|
|
1741
|
+
content: m.content,
|
|
1742
|
+
memoryType: m.memoryType,
|
|
1743
|
+
importance: m.importance,
|
|
1744
|
+
emotionalValence: m.emotionalValence,
|
|
1745
|
+
tags: m.tags,
|
|
1746
|
+
embedding: m.embedding,
|
|
1747
|
+
sourceId: m.sourceId,
|
|
1748
|
+
metadata: m.metadata,
|
|
1749
|
+
expiresAt: m.expiresAt,
|
|
1750
|
+
id: m.id
|
|
1751
|
+
}))).catch(() => {
|
|
1752
|
+
});
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
async store(input) {
|
|
1757
|
+
const result = await this.durable.store(input);
|
|
1758
|
+
if (this.writeBoth) {
|
|
1759
|
+
try {
|
|
1760
|
+
await this.cache.store({ ...input, id: result.id });
|
|
1761
|
+
} catch {
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
return result;
|
|
1765
|
+
}
|
|
1766
|
+
async storeBatch(inputs) {
|
|
1767
|
+
const results = await this.durable.storeBatch(inputs);
|
|
1768
|
+
if (this.writeBoth) {
|
|
1769
|
+
const cacheInputs = results.map((r, i) => ({
|
|
1770
|
+
...inputs[i],
|
|
1771
|
+
id: r.id
|
|
1772
|
+
}));
|
|
1773
|
+
try {
|
|
1774
|
+
await this.cache.storeBatch(cacheInputs);
|
|
1775
|
+
} catch {
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
return results;
|
|
1779
|
+
}
|
|
1780
|
+
async get(id) {
|
|
1781
|
+
if (this.readFromCache) {
|
|
1782
|
+
const cached = await this.cache.get(id);
|
|
1783
|
+
if (cached) return cached;
|
|
1784
|
+
const result = await this.durable.get(id);
|
|
1785
|
+
if (result) {
|
|
1242
1786
|
try {
|
|
1243
|
-
|
|
1787
|
+
await this.cache.store({
|
|
1788
|
+
actorId: result.actorId,
|
|
1789
|
+
content: result.content,
|
|
1790
|
+
memoryType: result.memoryType,
|
|
1791
|
+
importance: result.importance,
|
|
1792
|
+
emotionalValence: result.emotionalValence,
|
|
1793
|
+
tags: result.tags,
|
|
1794
|
+
embedding: result.embedding,
|
|
1795
|
+
sourceId: result.sourceId,
|
|
1796
|
+
metadata: result.metadata,
|
|
1797
|
+
expiresAt: result.expiresAt,
|
|
1798
|
+
id: result.id
|
|
1799
|
+
});
|
|
1244
1800
|
} catch {
|
|
1245
1801
|
}
|
|
1246
1802
|
}
|
|
1803
|
+
return result;
|
|
1247
1804
|
}
|
|
1248
|
-
return
|
|
1805
|
+
return this.durable.get(id);
|
|
1249
1806
|
}
|
|
1250
|
-
async
|
|
1251
|
-
|
|
1807
|
+
async retrieve(query, embedding) {
|
|
1808
|
+
if (this.readFromCache) {
|
|
1809
|
+
const cached = await this.cache.retrieve(query, embedding);
|
|
1810
|
+
if (cached.length > 0) return cached;
|
|
1811
|
+
const results = await this.durable.retrieve(query, embedding);
|
|
1812
|
+
if (results.length > 0) {
|
|
1813
|
+
const inputs = results.map((r) => ({
|
|
1814
|
+
actorId: r.actorId,
|
|
1815
|
+
content: r.content,
|
|
1816
|
+
memoryType: r.memoryType,
|
|
1817
|
+
importance: r.importance,
|
|
1818
|
+
emotionalValence: r.emotionalValence,
|
|
1819
|
+
tags: r.tags,
|
|
1820
|
+
embedding: r.embedding,
|
|
1821
|
+
sourceId: r.sourceId,
|
|
1822
|
+
metadata: r.metadata,
|
|
1823
|
+
expiresAt: r.expiresAt,
|
|
1824
|
+
id: r.id
|
|
1825
|
+
}));
|
|
1826
|
+
try {
|
|
1827
|
+
await this.cache.storeBatch(inputs);
|
|
1828
|
+
} catch {
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
return results;
|
|
1832
|
+
}
|
|
1833
|
+
return this.durable.retrieve(query, embedding);
|
|
1252
1834
|
}
|
|
1253
|
-
|
|
1254
|
-
const
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1835
|
+
async delete(id) {
|
|
1836
|
+
const results = await Promise.allSettled([
|
|
1837
|
+
this.cache.delete(id),
|
|
1838
|
+
this.durable.delete(id)
|
|
1839
|
+
]);
|
|
1840
|
+
const durableResult = results[1];
|
|
1841
|
+
if (durableResult.status === "rejected") {
|
|
1842
|
+
throw durableResult.reason;
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
async deleteMany(ids) {
|
|
1846
|
+
const [cacheResult, durableCount] = await Promise.all([
|
|
1847
|
+
this.cache.deleteMany(ids).catch(() => 0),
|
|
1848
|
+
this.durable.deleteMany(ids)
|
|
1849
|
+
]);
|
|
1850
|
+
return durableCount;
|
|
1851
|
+
}
|
|
1852
|
+
async count(filter) {
|
|
1853
|
+
return this.durable.count(filter);
|
|
1854
|
+
}
|
|
1855
|
+
async touch(id) {
|
|
1856
|
+
if (this.durable.touch) {
|
|
1857
|
+
await this.durable.touch(id);
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
async close() {
|
|
1861
|
+
await Promise.all([this.cache.close(), this.durable.close()]);
|
|
1260
1862
|
}
|
|
1261
1863
|
};
|
|
1262
1864
|
|
|
1263
1865
|
// src/adapters/storage/postgres.ts
|
|
1264
|
-
var
|
|
1866
|
+
var PostgresStorageAdapter = class {
|
|
1265
1867
|
pool;
|
|
1266
1868
|
table;
|
|
1267
|
-
|
|
1869
|
+
vectorDimensions;
|
|
1870
|
+
_ownsPool = false;
|
|
1268
1871
|
constructor(config) {
|
|
1269
|
-
this.pool = config.pool;
|
|
1270
1872
|
this.table = config.tableName ?? "memstack_memories";
|
|
1271
1873
|
if (!/^[a-zA-Z0-9_]+$/.test(this.table)) {
|
|
1272
|
-
throw
|
|
1874
|
+
throw configError(`Invalid table name: "${this.table}". Use only alphanumeric characters and underscores.`);
|
|
1875
|
+
}
|
|
1876
|
+
this.vectorDimensions = config.vectorDimensions ?? 1536;
|
|
1877
|
+
if (config.pool) {
|
|
1878
|
+
this.pool = config.pool;
|
|
1879
|
+
} else if (config.connectionString) {
|
|
1880
|
+
this._connectionString = config.connectionString;
|
|
1881
|
+
this._ssl = config.ssl;
|
|
1882
|
+
} else if (config.host || config.database || config.user) {
|
|
1883
|
+
this._connParts = { host: config.host, port: config.port, database: config.database, user: config.user, password: config.password, ssl: config.ssl };
|
|
1884
|
+
} else {
|
|
1885
|
+
throw configError("PostgresStorageAdapter requires a pool, connectionString, or connection details");
|
|
1273
1886
|
}
|
|
1274
|
-
this.autoMigrate = config.autoMigrate ?? true;
|
|
1275
1887
|
}
|
|
1888
|
+
_connectionString;
|
|
1889
|
+
_connParts;
|
|
1890
|
+
_ssl;
|
|
1276
1891
|
async initialize() {
|
|
1277
|
-
if (
|
|
1892
|
+
if (this._connectionString || this._connParts) {
|
|
1893
|
+
this.pool = await this._createPool();
|
|
1894
|
+
this._ownsPool = true;
|
|
1895
|
+
}
|
|
1896
|
+
await this._runMigration();
|
|
1897
|
+
}
|
|
1898
|
+
async _createPool() {
|
|
1899
|
+
const connStr = this._connectionString ?? this._buildConnString();
|
|
1900
|
+
try {
|
|
1901
|
+
const { default: createPostgres } = await import('postgres');
|
|
1902
|
+
const options = {};
|
|
1903
|
+
if (this._ssl) {
|
|
1904
|
+
options.ssl = typeof this._ssl === "object" ? this._ssl : "require";
|
|
1905
|
+
}
|
|
1906
|
+
const sql = createPostgres(connStr, options);
|
|
1907
|
+
return {
|
|
1908
|
+
query: (text, params) => sql.unsafe(text, params).then((r) => ({ rows: r })),
|
|
1909
|
+
end: () => sql.end()
|
|
1910
|
+
};
|
|
1911
|
+
} catch {
|
|
1912
|
+
try {
|
|
1913
|
+
const { Pool } = await import('pg');
|
|
1914
|
+
const pool = new Pool({ connectionString: connStr, ssl: this._ssl });
|
|
1915
|
+
return { query: (text, params) => pool.query(text, params), end: () => pool.end() };
|
|
1916
|
+
} catch {
|
|
1917
|
+
throw configError("connectionString requires 'postgres' (postgres.js) or 'pg' (node-postgres) to be installed");
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
_buildConnString() {
|
|
1922
|
+
const p = this._connParts;
|
|
1923
|
+
const host = p.host ?? "localhost";
|
|
1924
|
+
const port = p.port ?? 5432;
|
|
1925
|
+
const db = p.database ?? "postgres";
|
|
1926
|
+
const user = encodeURIComponent(p.user ?? "postgres");
|
|
1927
|
+
const pw = p.password ? `:${encodeURIComponent(p.password)}` : "";
|
|
1928
|
+
let connStr = `postgres://${user}${pw}@${host}:${port}/${db}`;
|
|
1929
|
+
if (this._ssl) {
|
|
1930
|
+
const sslmode = typeof this._ssl === "object" ? "no-verify" : "require";
|
|
1931
|
+
connStr += `?sslmode=${sslmode}`;
|
|
1932
|
+
}
|
|
1933
|
+
return connStr;
|
|
1934
|
+
}
|
|
1935
|
+
async _runMigration() {
|
|
1936
|
+
try {
|
|
1937
|
+
await this.pool.query("CREATE EXTENSION IF NOT EXISTS vector");
|
|
1938
|
+
} catch {
|
|
1939
|
+
}
|
|
1278
1940
|
await this.pool.query(`
|
|
1279
1941
|
CREATE TABLE IF NOT EXISTS ${this.table} (
|
|
1280
1942
|
id TEXT PRIMARY KEY,
|
|
@@ -1284,7 +1946,7 @@ var PostgresStorage = class {
|
|
|
1284
1946
|
importance REAL NOT NULL DEFAULT 0.5,
|
|
1285
1947
|
emotional_valence REAL NOT NULL DEFAULT 0,
|
|
1286
1948
|
tags JSONB NOT NULL DEFAULT '[]',
|
|
1287
|
-
embedding vector(
|
|
1949
|
+
embedding vector(${this.vectorDimensions}),
|
|
1288
1950
|
source_id TEXT,
|
|
1289
1951
|
metadata JSONB NOT NULL DEFAULT '{}',
|
|
1290
1952
|
expires_at TIMESTAMPTZ,
|
|
@@ -1294,7 +1956,9 @@ var PostgresStorage = class {
|
|
|
1294
1956
|
`);
|
|
1295
1957
|
try {
|
|
1296
1958
|
await this.pool.query(`CREATE INDEX IF NOT EXISTS idx_${this.table}_actor ON ${this.table} (actor_id)`);
|
|
1297
|
-
await this.pool.query(`CREATE INDEX IF NOT EXISTS idx_${this.table}
|
|
1959
|
+
await this.pool.query(`CREATE INDEX IF NOT EXISTS idx_${this.table}_created ON ${this.table} (created_at DESC)`);
|
|
1960
|
+
await this.pool.query(`CREATE INDEX IF NOT EXISTS idx_${this.table}_importance ON ${this.table} (importance DESC)`);
|
|
1961
|
+
await this.pool.query(`CREATE INDEX IF NOT EXISTS idx_${this.table}_embedding ON ${this.table} USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 64)`);
|
|
1298
1962
|
} catch {
|
|
1299
1963
|
}
|
|
1300
1964
|
}
|
|
@@ -1376,6 +2040,11 @@ var PostgresStorage = class {
|
|
|
1376
2040
|
);
|
|
1377
2041
|
return rows.length;
|
|
1378
2042
|
}
|
|
2043
|
+
async touch(id) {
|
|
2044
|
+
const { rows } = await this.pool.query(`SELECT id FROM ${this.table} WHERE id = $1`, [id]);
|
|
2045
|
+
if (rows.length === 0) throw notFound("Memory", id);
|
|
2046
|
+
await this.pool.query(`UPDATE ${this.table} SET touched_at = NOW() WHERE id = $1`, [id]);
|
|
2047
|
+
}
|
|
1379
2048
|
async retrieve(query, embedding) {
|
|
1380
2049
|
const conditions = ["(expires_at IS NULL OR expires_at > NOW())"];
|
|
1381
2050
|
const params = [];
|
|
@@ -1396,6 +2065,14 @@ var PostgresStorage = class {
|
|
|
1396
2065
|
conditions.push(`content ILIKE $${paramIdx++}`);
|
|
1397
2066
|
params.push(`%${query.query}%`);
|
|
1398
2067
|
}
|
|
2068
|
+
if (query.createdAfter) {
|
|
2069
|
+
conditions.push(`created_at >= $${paramIdx++}`);
|
|
2070
|
+
params.push(query.createdAfter.toISOString());
|
|
2071
|
+
}
|
|
2072
|
+
if (query.createdBefore) {
|
|
2073
|
+
conditions.push(`created_at <= $${paramIdx++}`);
|
|
2074
|
+
params.push(query.createdBefore.toISOString());
|
|
2075
|
+
}
|
|
1399
2076
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1400
2077
|
let orderBy;
|
|
1401
2078
|
switch (query.strategy) {
|
|
@@ -1408,8 +2085,8 @@ var PostgresStorage = class {
|
|
|
1408
2085
|
case "hybrid":
|
|
1409
2086
|
case "semantic":
|
|
1410
2087
|
default:
|
|
1411
|
-
if (embedding
|
|
1412
|
-
return this._semanticRetrieve(embedding, query, params, where);
|
|
2088
|
+
if (embedding) {
|
|
2089
|
+
return this._semanticRetrieve(embedding, query, params, where, paramIdx);
|
|
1413
2090
|
}
|
|
1414
2091
|
orderBy = "importance DESC, touched_at DESC";
|
|
1415
2092
|
break;
|
|
@@ -1420,10 +2097,7 @@ var PostgresStorage = class {
|
|
|
1420
2097
|
const { rows } = await this.pool.query(sql, params);
|
|
1421
2098
|
for (const row of rows.slice(0, limit)) {
|
|
1422
2099
|
const r = row;
|
|
1423
|
-
await this.pool.query(
|
|
1424
|
-
`UPDATE ${this.table} SET touched_at = NOW() WHERE id = $1`,
|
|
1425
|
-
[r.id]
|
|
1426
|
-
);
|
|
2100
|
+
await this.pool.query(`UPDATE ${this.table} SET touched_at = NOW() WHERE id = $1`, [r.id]);
|
|
1427
2101
|
}
|
|
1428
2102
|
return rows.map((r) => this._rowToMemory(r));
|
|
1429
2103
|
}
|
|
@@ -1443,6 +2117,14 @@ var PostgresStorage = class {
|
|
|
1443
2117
|
conditions.push(`importance >= $${paramIdx++}`);
|
|
1444
2118
|
params.push(filter.minImportance);
|
|
1445
2119
|
}
|
|
2120
|
+
if (filter?.createdAfter) {
|
|
2121
|
+
conditions.push(`created_at >= $${paramIdx++}`);
|
|
2122
|
+
params.push(filter.createdAfter.toISOString());
|
|
2123
|
+
}
|
|
2124
|
+
if (filter?.createdBefore) {
|
|
2125
|
+
conditions.push(`created_at <= $${paramIdx++}`);
|
|
2126
|
+
params.push(filter.createdBefore.toISOString());
|
|
2127
|
+
}
|
|
1446
2128
|
const where = conditions.join(" AND ");
|
|
1447
2129
|
const { rows } = await this.pool.query(
|
|
1448
2130
|
`SELECT COUNT(*) as count FROM ${this.table} WHERE ${where}`,
|
|
@@ -1451,22 +2133,32 @@ var PostgresStorage = class {
|
|
|
1451
2133
|
return Number(rows[0].count);
|
|
1452
2134
|
}
|
|
1453
2135
|
async close() {
|
|
2136
|
+
if (this._ownsPool && this.pool?.end) {
|
|
2137
|
+
await this.pool.end();
|
|
2138
|
+
}
|
|
1454
2139
|
}
|
|
1455
|
-
|
|
1456
|
-
async _semanticRetrieve(embedding, query, params, baseWhere) {
|
|
1457
|
-
const vecParam = `{${embedding.join(",")}}`;
|
|
2140
|
+
async _semanticRetrieve(embedding, query, params, baseWhere, paramIdx) {
|
|
1458
2141
|
const limit = query.limit ?? 10;
|
|
1459
2142
|
const conditions = baseWhere.replace(/^WHERE\s+/i, "");
|
|
1460
2143
|
const whereClause = conditions ? `WHERE ${conditions} AND embedding IS NOT NULL` : `WHERE embedding IS NOT NULL`;
|
|
1461
|
-
const
|
|
2144
|
+
const isHybrid = query.strategy === "hybrid";
|
|
2145
|
+
const sql = isHybrid ? `
|
|
1462
2146
|
SELECT *,
|
|
1463
|
-
(embedding <-> $${
|
|
2147
|
+
(embedding <-> $${paramIdx}::float8[]::vector) AS distance,
|
|
2148
|
+
(1.0 - (embedding <-> $${paramIdx}::float8[]::vector) / 2.0 + importance) AS score
|
|
2149
|
+
FROM ${this.table}
|
|
2150
|
+
${whereClause}
|
|
2151
|
+
ORDER BY score DESC
|
|
2152
|
+
LIMIT $${paramIdx + 1}
|
|
2153
|
+
` : `
|
|
2154
|
+
SELECT *,
|
|
2155
|
+
(embedding <-> $${paramIdx}::float8[]::vector) AS distance
|
|
1464
2156
|
FROM ${this.table}
|
|
1465
2157
|
${whereClause}
|
|
1466
2158
|
ORDER BY distance ASC
|
|
1467
|
-
LIMIT $${
|
|
2159
|
+
LIMIT $${paramIdx + 1}
|
|
1468
2160
|
`;
|
|
1469
|
-
params.push(
|
|
2161
|
+
params.push(embedding);
|
|
1470
2162
|
params.push(limit);
|
|
1471
2163
|
const { rows } = await this.pool.query(sql, params);
|
|
1472
2164
|
for (const row of rows.slice(0, limit)) {
|
|
@@ -1493,6 +2185,1515 @@ var PostgresStorage = class {
|
|
|
1493
2185
|
}
|
|
1494
2186
|
};
|
|
1495
2187
|
|
|
2188
|
+
// src/adapters/storage/redis.ts
|
|
2189
|
+
var RedisStorageAdapter = class {
|
|
2190
|
+
redis;
|
|
2191
|
+
prefix;
|
|
2192
|
+
constructor(config) {
|
|
2193
|
+
this.redis = config.redis;
|
|
2194
|
+
this.prefix = config.keyPrefix ?? "memstack";
|
|
2195
|
+
}
|
|
2196
|
+
async initialize() {
|
|
2197
|
+
}
|
|
2198
|
+
generateId() {
|
|
2199
|
+
return `mem_${Date.now().toString(36)}_${crypto.randomUUID().slice(0, 8)}`;
|
|
2200
|
+
}
|
|
2201
|
+
async store(input) {
|
|
2202
|
+
const now = /* @__PURE__ */ new Date();
|
|
2203
|
+
const memory = {
|
|
2204
|
+
id: input.id ?? this.generateId(),
|
|
2205
|
+
actorId: input.actorId,
|
|
2206
|
+
memoryType: input.memoryType ?? "interaction",
|
|
2207
|
+
content: input.content,
|
|
2208
|
+
importance: input.importance ?? 0.5,
|
|
2209
|
+
emotionalValence: input.emotionalValence ?? 0,
|
|
2210
|
+
tags: input.tags ?? [],
|
|
2211
|
+
embedding: input.embedding,
|
|
2212
|
+
sourceId: input.sourceId,
|
|
2213
|
+
metadata: input.metadata ?? {},
|
|
2214
|
+
expiresAt: input.expiresAt,
|
|
2215
|
+
createdAt: now,
|
|
2216
|
+
_touchedAt: now.toISOString()
|
|
2217
|
+
};
|
|
2218
|
+
await this.redis.set(this._memKey(memory.id), JSON.stringify(memory));
|
|
2219
|
+
await this.redis.sadd(this._actorKey(memory.actorId), memory.id);
|
|
2220
|
+
await this.redis.sadd(this._actorsSetKey(), memory.actorId);
|
|
2221
|
+
return this._toExternal(memory);
|
|
2222
|
+
}
|
|
2223
|
+
async storeBatch(inputs) {
|
|
2224
|
+
const results = [];
|
|
2225
|
+
const now = /* @__PURE__ */ new Date();
|
|
2226
|
+
for (const input of inputs) {
|
|
2227
|
+
const memory = {
|
|
2228
|
+
id: input.id ?? this.generateId(),
|
|
2229
|
+
actorId: input.actorId,
|
|
2230
|
+
memoryType: input.memoryType ?? "interaction",
|
|
2231
|
+
content: input.content,
|
|
2232
|
+
importance: input.importance ?? 0.5,
|
|
2233
|
+
emotionalValence: input.emotionalValence ?? 0,
|
|
2234
|
+
tags: input.tags ?? [],
|
|
2235
|
+
embedding: input.embedding,
|
|
2236
|
+
sourceId: input.sourceId,
|
|
2237
|
+
metadata: input.metadata ?? {},
|
|
2238
|
+
expiresAt: input.expiresAt,
|
|
2239
|
+
createdAt: now,
|
|
2240
|
+
_touchedAt: now.toISOString()
|
|
2241
|
+
};
|
|
2242
|
+
await this.redis.set(this._memKey(memory.id), JSON.stringify(memory));
|
|
2243
|
+
await this.redis.sadd(this._actorKey(memory.actorId), memory.id);
|
|
2244
|
+
await this.redis.sadd(this._actorsSetKey(), memory.actorId);
|
|
2245
|
+
results.push(this._toExternal(memory));
|
|
2246
|
+
}
|
|
2247
|
+
return results;
|
|
2248
|
+
}
|
|
2249
|
+
async get(id) {
|
|
2250
|
+
const raw = await this.redis.get(this._memKey(id));
|
|
2251
|
+
if (!raw) return null;
|
|
2252
|
+
const record = JSON.parse(raw);
|
|
2253
|
+
if (record.expiresAt && new Date(record.expiresAt) <= /* @__PURE__ */ new Date()) return null;
|
|
2254
|
+
return this._toExternal(record);
|
|
2255
|
+
}
|
|
2256
|
+
async delete(id) {
|
|
2257
|
+
const raw = await this.redis.get(this._memKey(id));
|
|
2258
|
+
if (!raw) throw notFound("Memory", id);
|
|
2259
|
+
const record = JSON.parse(raw);
|
|
2260
|
+
await this.redis.del(this._memKey(id));
|
|
2261
|
+
await this.redis.srem(this._actorKey(record.actorId), id);
|
|
2262
|
+
}
|
|
2263
|
+
async deleteMany(ids) {
|
|
2264
|
+
let count = 0;
|
|
2265
|
+
const byActor = /* @__PURE__ */ new Map();
|
|
2266
|
+
for (const id of ids) {
|
|
2267
|
+
const raw = await this.redis.get(this._memKey(id));
|
|
2268
|
+
if (raw) {
|
|
2269
|
+
const record = JSON.parse(raw);
|
|
2270
|
+
const list = byActor.get(record.actorId) ?? [];
|
|
2271
|
+
list.push(id);
|
|
2272
|
+
byActor.set(record.actorId, list);
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
for (const [actorId, memIds] of byActor) {
|
|
2276
|
+
await this.redis.del(memIds.map((id) => this._memKey(id)));
|
|
2277
|
+
await this.redis.srem(this._actorKey(actorId), ...memIds);
|
|
2278
|
+
count += memIds.length;
|
|
2279
|
+
}
|
|
2280
|
+
return count;
|
|
2281
|
+
}
|
|
2282
|
+
async touch(id) {
|
|
2283
|
+
const raw = await this.redis.get(this._memKey(id));
|
|
2284
|
+
if (!raw) throw notFound("Memory", id);
|
|
2285
|
+
const record = JSON.parse(raw);
|
|
2286
|
+
record._touchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2287
|
+
await this.redis.set(this._memKey(id), JSON.stringify(record));
|
|
2288
|
+
}
|
|
2289
|
+
async retrieve(query, _embedding) {
|
|
2290
|
+
let allRecords = [];
|
|
2291
|
+
if (query.actorId) {
|
|
2292
|
+
allRecords = await this._getActorRecords(query.actorId);
|
|
2293
|
+
} else {
|
|
2294
|
+
const actorIds = await this._listActors();
|
|
2295
|
+
for (const actorId of actorIds) {
|
|
2296
|
+
const records = await this._getActorRecords(actorId);
|
|
2297
|
+
allRecords.push(...records);
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
const now = /* @__PURE__ */ new Date();
|
|
2301
|
+
let results = allRecords.filter((r) => !r.expiresAt || new Date(r.expiresAt) > now);
|
|
2302
|
+
if (query.memoryTypes && query.memoryTypes.length > 0) {
|
|
2303
|
+
results = results.filter((r) => query.memoryTypes.includes(r.memoryType));
|
|
2304
|
+
}
|
|
2305
|
+
if (query.tags && query.tags.length > 0) {
|
|
2306
|
+
results = results.filter((r) => r.tags?.some((t) => query.tags.includes(t)));
|
|
2307
|
+
}
|
|
2308
|
+
if (query.query) {
|
|
2309
|
+
const q = query.query.toLowerCase();
|
|
2310
|
+
results = results.filter((r) => r.content.toLowerCase().includes(q));
|
|
2311
|
+
}
|
|
2312
|
+
switch (query.strategy) {
|
|
2313
|
+
case "recent":
|
|
2314
|
+
results.sort((a, b) => new Date(b._touchedAt).getTime() - new Date(a._touchedAt).getTime());
|
|
2315
|
+
break;
|
|
2316
|
+
case "important":
|
|
2317
|
+
results.sort((a, b) => b.importance - a.importance);
|
|
2318
|
+
break;
|
|
2319
|
+
case "hybrid":
|
|
2320
|
+
case "semantic":
|
|
2321
|
+
default:
|
|
2322
|
+
results.sort(
|
|
2323
|
+
(a, b) => b.importance - a.importance || new Date(b._touchedAt).getTime() - new Date(a._touchedAt).getTime()
|
|
2324
|
+
);
|
|
2325
|
+
break;
|
|
2326
|
+
}
|
|
2327
|
+
const nowStr = (/* @__PURE__ */ new Date()).toISOString();
|
|
2328
|
+
const toTouch = results.slice(0, query.limit ?? 10);
|
|
2329
|
+
for (const r of toTouch) {
|
|
2330
|
+
r._touchedAt = nowStr;
|
|
2331
|
+
await this.redis.set(this._memKey(r.id), JSON.stringify(r));
|
|
2332
|
+
}
|
|
2333
|
+
const limit = query.limit ?? 10;
|
|
2334
|
+
return results.slice(0, limit).map((r) => this._toExternal(r));
|
|
2335
|
+
}
|
|
2336
|
+
async count(filter) {
|
|
2337
|
+
let allRecords = [];
|
|
2338
|
+
if (filter?.actorId) {
|
|
2339
|
+
allRecords = await this._getActorRecords(filter.actorId);
|
|
2340
|
+
} else {
|
|
2341
|
+
const actorIds = await this._listActors();
|
|
2342
|
+
for (const actorId of actorIds) {
|
|
2343
|
+
const records = await this._getActorRecords(actorId);
|
|
2344
|
+
allRecords.push(...records);
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
const now = /* @__PURE__ */ new Date();
|
|
2348
|
+
let results = allRecords.filter((r) => !r.expiresAt || new Date(r.expiresAt) > now);
|
|
2349
|
+
if (filter?.memoryType) {
|
|
2350
|
+
results = results.filter((r) => r.memoryType === filter.memoryType);
|
|
2351
|
+
}
|
|
2352
|
+
if (filter?.minImportance !== void 0) {
|
|
2353
|
+
results = results.filter((r) => r.importance >= filter.minImportance);
|
|
2354
|
+
}
|
|
2355
|
+
return results.length;
|
|
2356
|
+
}
|
|
2357
|
+
async close() {
|
|
2358
|
+
}
|
|
2359
|
+
_actorsSetKey() {
|
|
2360
|
+
return `${this.prefix}:actors`;
|
|
2361
|
+
}
|
|
2362
|
+
_actorKey(actorId) {
|
|
2363
|
+
const safe = actorId.replace(/[^a-zA-Z0-9_\-.,@]/g, "_");
|
|
2364
|
+
return `${this.prefix}:a:${safe}`;
|
|
2365
|
+
}
|
|
2366
|
+
_memKey(id) {
|
|
2367
|
+
return `${this.prefix}:m:${id}`;
|
|
2368
|
+
}
|
|
2369
|
+
async _getActorRecords(actorId) {
|
|
2370
|
+
const ids = await this.redis.smembers(this._actorKey(actorId));
|
|
2371
|
+
if (ids.length === 0) return [];
|
|
2372
|
+
const keys = ids.map((id) => this._memKey(id));
|
|
2373
|
+
const raws = await this.redis.mget(...keys);
|
|
2374
|
+
const records = [];
|
|
2375
|
+
for (const raw of raws) {
|
|
2376
|
+
if (raw) {
|
|
2377
|
+
try {
|
|
2378
|
+
records.push(JSON.parse(raw));
|
|
2379
|
+
} catch {
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
return records;
|
|
2384
|
+
}
|
|
2385
|
+
async _listActors() {
|
|
2386
|
+
return this.redis.smembers(this._actorsSetKey());
|
|
2387
|
+
}
|
|
2388
|
+
_toExternal(record) {
|
|
2389
|
+
const { _touchedAt: _, ...rest } = record;
|
|
2390
|
+
return {
|
|
2391
|
+
...rest,
|
|
2392
|
+
createdAt: new Date(record.createdAt),
|
|
2393
|
+
expiresAt: record.expiresAt ? new Date(record.expiresAt) : void 0
|
|
2394
|
+
};
|
|
2395
|
+
}
|
|
2396
|
+
};
|
|
2397
|
+
|
|
2398
|
+
// src/adapters/storage/qdrant.ts
|
|
2399
|
+
var QdrantStorageAdapter = class {
|
|
2400
|
+
client;
|
|
2401
|
+
collectionName;
|
|
2402
|
+
vectorSize;
|
|
2403
|
+
_idToQdrant = /* @__PURE__ */ new Map();
|
|
2404
|
+
constructor(config) {
|
|
2405
|
+
this.client = config.client;
|
|
2406
|
+
this.collectionName = config.collectionName ?? "memstack";
|
|
2407
|
+
this.vectorSize = config.vectorSize ?? 1536;
|
|
2408
|
+
}
|
|
2409
|
+
async initialize() {
|
|
2410
|
+
try {
|
|
2411
|
+
await this.client.getCollection(this.collectionName);
|
|
2412
|
+
} catch {
|
|
2413
|
+
await this.client.createCollection(this.collectionName, {
|
|
2414
|
+
vectors: { size: this.vectorSize, distance: "Cosine" }
|
|
2415
|
+
});
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
generateId() {
|
|
2419
|
+
return `mem_${Date.now().toString(36)}_${crypto.randomUUID().slice(0, 8)}`;
|
|
2420
|
+
}
|
|
2421
|
+
async store(input) {
|
|
2422
|
+
const now = /* @__PURE__ */ new Date();
|
|
2423
|
+
const memory = {
|
|
2424
|
+
id: input.id ?? this.generateId(),
|
|
2425
|
+
actorId: input.actorId,
|
|
2426
|
+
memoryType: input.memoryType ?? "interaction",
|
|
2427
|
+
content: input.content,
|
|
2428
|
+
importance: input.importance ?? 0.5,
|
|
2429
|
+
emotionalValence: input.emotionalValence ?? 0,
|
|
2430
|
+
tags: input.tags ?? [],
|
|
2431
|
+
embedding: input.embedding,
|
|
2432
|
+
sourceId: input.sourceId,
|
|
2433
|
+
metadata: input.metadata ?? {},
|
|
2434
|
+
expiresAt: input.expiresAt,
|
|
2435
|
+
createdAt: now
|
|
2436
|
+
};
|
|
2437
|
+
const qdrantId = crypto.randomUUID();
|
|
2438
|
+
await this.client.upsert(this.collectionName, {
|
|
2439
|
+
points: [
|
|
2440
|
+
{
|
|
2441
|
+
id: qdrantId,
|
|
2442
|
+
vector: input.embedding ?? new Array(this.vectorSize).fill(0),
|
|
2443
|
+
payload: this._toPayload(memory)
|
|
2444
|
+
}
|
|
2445
|
+
]
|
|
2446
|
+
});
|
|
2447
|
+
this._idToQdrant.set(memory.id, qdrantId);
|
|
2448
|
+
return memory;
|
|
2449
|
+
}
|
|
2450
|
+
async storeBatch(inputs) {
|
|
2451
|
+
const results = [];
|
|
2452
|
+
const now = /* @__PURE__ */ new Date();
|
|
2453
|
+
const points = [];
|
|
2454
|
+
for (const input of inputs) {
|
|
2455
|
+
const memory = {
|
|
2456
|
+
id: input.id ?? this.generateId(),
|
|
2457
|
+
actorId: input.actorId,
|
|
2458
|
+
memoryType: input.memoryType ?? "interaction",
|
|
2459
|
+
content: input.content,
|
|
2460
|
+
importance: input.importance ?? 0.5,
|
|
2461
|
+
emotionalValence: input.emotionalValence ?? 0,
|
|
2462
|
+
tags: input.tags ?? [],
|
|
2463
|
+
embedding: input.embedding,
|
|
2464
|
+
sourceId: input.sourceId,
|
|
2465
|
+
metadata: input.metadata ?? {},
|
|
2466
|
+
expiresAt: input.expiresAt,
|
|
2467
|
+
createdAt: now
|
|
2468
|
+
};
|
|
2469
|
+
results.push(memory);
|
|
2470
|
+
const qid = crypto.randomUUID();
|
|
2471
|
+
points.push({
|
|
2472
|
+
id: qid,
|
|
2473
|
+
vector: input.embedding ?? new Array(this.vectorSize).fill(0),
|
|
2474
|
+
payload: this._toPayload(memory)
|
|
2475
|
+
});
|
|
2476
|
+
this._idToQdrant.set(memory.id, qid);
|
|
2477
|
+
}
|
|
2478
|
+
await this.client.upsert(this.collectionName, { points });
|
|
2479
|
+
return results;
|
|
2480
|
+
}
|
|
2481
|
+
async get(id) {
|
|
2482
|
+
const qdrantId = this._idToQdrant.get(id);
|
|
2483
|
+
if (qdrantId) {
|
|
2484
|
+
const points = await this.client.retrieve(this.collectionName, { ids: [qdrantId] });
|
|
2485
|
+
if (points && points.length > 0) {
|
|
2486
|
+
const point = points[0];
|
|
2487
|
+
if (point.payload.expiresAt && new Date(point.payload.expiresAt) <= /* @__PURE__ */ new Date()) return null;
|
|
2488
|
+
return this._fromPoint(point);
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
return null;
|
|
2492
|
+
}
|
|
2493
|
+
async delete(id) {
|
|
2494
|
+
const qdrantId = this._idToQdrant.get(id);
|
|
2495
|
+
if (!qdrantId) throw notFound("Memory", id);
|
|
2496
|
+
await this.client.delete(this.collectionName, { points: [qdrantId] });
|
|
2497
|
+
this._idToQdrant.delete(id);
|
|
2498
|
+
}
|
|
2499
|
+
async deleteMany(ids) {
|
|
2500
|
+
const qdrantIds = [];
|
|
2501
|
+
for (const id of ids) {
|
|
2502
|
+
const qid = this._idToQdrant.get(id);
|
|
2503
|
+
if (qid) {
|
|
2504
|
+
qdrantIds.push(qid);
|
|
2505
|
+
this._idToQdrant.delete(id);
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
if (qdrantIds.length === 0) return 0;
|
|
2509
|
+
await this.client.delete(this.collectionName, { points: qdrantIds });
|
|
2510
|
+
return qdrantIds.length;
|
|
2511
|
+
}
|
|
2512
|
+
async retrieve(query, embedding) {
|
|
2513
|
+
const must = [];
|
|
2514
|
+
if (query.actorId) {
|
|
2515
|
+
must.push({ key: "actorId", match: { value: query.actorId } });
|
|
2516
|
+
}
|
|
2517
|
+
if (query.memoryTypes && query.memoryTypes.length > 0) {
|
|
2518
|
+
must.push({ key: "memoryType", match: { any: query.memoryTypes } });
|
|
2519
|
+
}
|
|
2520
|
+
if (query.tags && query.tags.length > 0) {
|
|
2521
|
+
must.push({ key: "tags", match: { any: query.tags } });
|
|
2522
|
+
}
|
|
2523
|
+
const filter = must.length > 0 ? { must } : void 0;
|
|
2524
|
+
const limit = query.limit ?? 10;
|
|
2525
|
+
if (query.strategy === "semantic" || query.strategy === "hybrid") {
|
|
2526
|
+
const vector2 = embedding ?? new Array(this.vectorSize).fill(0);
|
|
2527
|
+
const results2 = await this.client.search(this.collectionName, {
|
|
2528
|
+
vector: vector2,
|
|
2529
|
+
limit,
|
|
2530
|
+
filter
|
|
2531
|
+
});
|
|
2532
|
+
const points2 = results2;
|
|
2533
|
+
return points2.map((p) => this._fromPoint(p));
|
|
2534
|
+
}
|
|
2535
|
+
const vector = new Array(this.vectorSize).fill(0);
|
|
2536
|
+
const results = await this.client.search(this.collectionName, {
|
|
2537
|
+
vector,
|
|
2538
|
+
limit: 1e4,
|
|
2539
|
+
filter
|
|
2540
|
+
});
|
|
2541
|
+
let points = results;
|
|
2542
|
+
switch (query.strategy) {
|
|
2543
|
+
case "recent":
|
|
2544
|
+
points.sort((a, b) => {
|
|
2545
|
+
const aTime = new Date(a.payload.createdAt).getTime();
|
|
2546
|
+
const bTime = new Date(b.payload.createdAt).getTime();
|
|
2547
|
+
return bTime - aTime;
|
|
2548
|
+
});
|
|
2549
|
+
break;
|
|
2550
|
+
case "important":
|
|
2551
|
+
points.sort((a, b) => b.payload.importance - a.payload.importance);
|
|
2552
|
+
break;
|
|
2553
|
+
}
|
|
2554
|
+
return points.slice(0, limit).map((p) => this._fromPoint(p));
|
|
2555
|
+
}
|
|
2556
|
+
async count(filter) {
|
|
2557
|
+
const must = [];
|
|
2558
|
+
if (filter?.actorId) {
|
|
2559
|
+
must.push({ key: "actorId", match: { value: filter.actorId } });
|
|
2560
|
+
}
|
|
2561
|
+
if (filter?.memoryType) {
|
|
2562
|
+
must.push({ key: "memoryType", match: { value: filter.memoryType } });
|
|
2563
|
+
}
|
|
2564
|
+
if (filter?.minImportance !== void 0) {
|
|
2565
|
+
must.push({ key: "importance", range: { gte: filter.minImportance } });
|
|
2566
|
+
}
|
|
2567
|
+
const filterObj = must.length > 0 ? { must } : void 0;
|
|
2568
|
+
const result = await this.client.count(this.collectionName, filterObj ? { filter: filterObj } : void 0);
|
|
2569
|
+
return result.count;
|
|
2570
|
+
}
|
|
2571
|
+
async close() {
|
|
2572
|
+
}
|
|
2573
|
+
_toPayload(memory) {
|
|
2574
|
+
return {
|
|
2575
|
+
_id: memory.id,
|
|
2576
|
+
actorId: memory.actorId,
|
|
2577
|
+
memoryType: memory.memoryType,
|
|
2578
|
+
importance: memory.importance,
|
|
2579
|
+
emotionalValence: memory.emotionalValence,
|
|
2580
|
+
tags: memory.tags,
|
|
2581
|
+
content: memory.content,
|
|
2582
|
+
metadata: memory.metadata ?? {},
|
|
2583
|
+
createdAt: memory.createdAt.toISOString(),
|
|
2584
|
+
sourceId: memory.sourceId ?? null,
|
|
2585
|
+
expiresAt: memory.expiresAt?.toISOString() ?? null
|
|
2586
|
+
};
|
|
2587
|
+
}
|
|
2588
|
+
_fromPoint(point) {
|
|
2589
|
+
const p = point.payload;
|
|
2590
|
+
return {
|
|
2591
|
+
id: p._id ?? point.id,
|
|
2592
|
+
actorId: p.actorId,
|
|
2593
|
+
memoryType: p.memoryType,
|
|
2594
|
+
content: p.content,
|
|
2595
|
+
importance: p.importance,
|
|
2596
|
+
emotionalValence: p.emotionalValence ?? 0,
|
|
2597
|
+
tags: p.tags ?? [],
|
|
2598
|
+
embedding: point.vector,
|
|
2599
|
+
sourceId: p.sourceId ?? void 0,
|
|
2600
|
+
metadata: p.metadata ?? {},
|
|
2601
|
+
expiresAt: p.expiresAt ? new Date(p.expiresAt) : void 0,
|
|
2602
|
+
createdAt: new Date(p.createdAt)
|
|
2603
|
+
};
|
|
2604
|
+
}
|
|
2605
|
+
};
|
|
2606
|
+
|
|
2607
|
+
// src/adapters/storage/neo4j.ts
|
|
2608
|
+
var Neo4jStorageAdapter = class {
|
|
2609
|
+
driver;
|
|
2610
|
+
database;
|
|
2611
|
+
constructor(config) {
|
|
2612
|
+
this.driver = config.driver;
|
|
2613
|
+
this.database = config.database ?? "neo4j";
|
|
2614
|
+
}
|
|
2615
|
+
async initialize() {
|
|
2616
|
+
const session = this.driver.session({ database: this.database });
|
|
2617
|
+
try {
|
|
2618
|
+
await session.run(
|
|
2619
|
+
"CREATE CONSTRAINT memory_id IF NOT EXISTS FOR (m:Memory) REQUIRE m.id IS UNIQUE"
|
|
2620
|
+
);
|
|
2621
|
+
await session.run(
|
|
2622
|
+
`CREATE VECTOR INDEX memory_embedding IF NOT EXISTS FOR (m:Memory) ON m.embedding
|
|
2623
|
+
OPTIONS { indexConfig: { \`vector.dimensions\`: 1536, \`vector.similarity_function\`: "cosine" } }`
|
|
2624
|
+
);
|
|
2625
|
+
} finally {
|
|
2626
|
+
await session.close();
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
generateId() {
|
|
2630
|
+
return `mem_${Date.now().toString(36)}_${crypto.randomUUID().slice(0, 8)}`;
|
|
2631
|
+
}
|
|
2632
|
+
async store(input) {
|
|
2633
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2634
|
+
const id = input.id ?? this.generateId();
|
|
2635
|
+
const props = {
|
|
2636
|
+
actorId: input.actorId,
|
|
2637
|
+
memoryType: input.memoryType ?? "interaction",
|
|
2638
|
+
content: input.content,
|
|
2639
|
+
importance: input.importance ?? 0.5,
|
|
2640
|
+
emotionalValence: input.emotionalValence ?? 0,
|
|
2641
|
+
tags: input.tags ?? [],
|
|
2642
|
+
sourceId: input.sourceId ?? null,
|
|
2643
|
+
metadata: JSON.stringify(input.metadata ?? {}),
|
|
2644
|
+
expiresAt: input.expiresAt?.toISOString() ?? null,
|
|
2645
|
+
createdAt: now,
|
|
2646
|
+
embedding: input.embedding ?? null
|
|
2647
|
+
};
|
|
2648
|
+
const { createdAt: _, ...updateProps } = props;
|
|
2649
|
+
const session = this.driver.session({ database: this.database });
|
|
2650
|
+
try {
|
|
2651
|
+
await session.run(
|
|
2652
|
+
"MERGE (m:Memory {id: $id}) ON CREATE SET m += $props ON MATCH SET m += $updateProps",
|
|
2653
|
+
{ id, props, updateProps }
|
|
2654
|
+
);
|
|
2655
|
+
} finally {
|
|
2656
|
+
await session.close();
|
|
2657
|
+
}
|
|
2658
|
+
return {
|
|
2659
|
+
id,
|
|
2660
|
+
actorId: input.actorId,
|
|
2661
|
+
memoryType: input.memoryType ?? "interaction",
|
|
2662
|
+
content: input.content,
|
|
2663
|
+
importance: input.importance ?? 0.5,
|
|
2664
|
+
emotionalValence: input.emotionalValence ?? 0,
|
|
2665
|
+
tags: input.tags ?? [],
|
|
2666
|
+
embedding: input.embedding,
|
|
2667
|
+
sourceId: input.sourceId,
|
|
2668
|
+
metadata: input.metadata ?? {},
|
|
2669
|
+
expiresAt: input.expiresAt,
|
|
2670
|
+
createdAt: new Date(now)
|
|
2671
|
+
};
|
|
2672
|
+
}
|
|
2673
|
+
async storeBatch(inputs) {
|
|
2674
|
+
const results = [];
|
|
2675
|
+
for (const input of inputs) {
|
|
2676
|
+
results.push(await this.store(input));
|
|
2677
|
+
}
|
|
2678
|
+
return results;
|
|
2679
|
+
}
|
|
2680
|
+
async get(id) {
|
|
2681
|
+
const session = this.driver.session({ database: this.database });
|
|
2682
|
+
try {
|
|
2683
|
+
const result = await session.run(
|
|
2684
|
+
"MATCH (m:Memory {id: $id}) WHERE m.expiresAt IS NULL OR m.expiresAt > datetime() RETURN m",
|
|
2685
|
+
{ id }
|
|
2686
|
+
);
|
|
2687
|
+
if (result.records.length === 0) return null;
|
|
2688
|
+
const raw = result.records[0].get("m");
|
|
2689
|
+
const node = raw.properties ?? raw;
|
|
2690
|
+
return this._nodeToMemory(node);
|
|
2691
|
+
} finally {
|
|
2692
|
+
await session.close();
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
async delete(id) {
|
|
2696
|
+
const session = this.driver.session({ database: this.database });
|
|
2697
|
+
try {
|
|
2698
|
+
const check = await session.run("MATCH (m:Memory {id: $id}) RETURN m.id as id", { id });
|
|
2699
|
+
if (check.records.length === 0) throw notFound("Memory", id);
|
|
2700
|
+
await session.run("MATCH (m:Memory {id: $id}) DETACH DELETE m", { id });
|
|
2701
|
+
} finally {
|
|
2702
|
+
await session.close();
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
async deleteMany(ids) {
|
|
2706
|
+
const session = this.driver.session({ database: this.database });
|
|
2707
|
+
try {
|
|
2708
|
+
const result = await session.run(
|
|
2709
|
+
"UNWIND $ids AS mid MATCH (m:Memory {id: mid}) DETACH DELETE m RETURN count(m) as deleted",
|
|
2710
|
+
{ ids }
|
|
2711
|
+
);
|
|
2712
|
+
const record = result.records[0];
|
|
2713
|
+
if (!record) return 0;
|
|
2714
|
+
return record.get("deleted");
|
|
2715
|
+
} finally {
|
|
2716
|
+
await session.close();
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
async retrieve(query, embedding) {
|
|
2720
|
+
const session = this.driver.session({ database: this.database });
|
|
2721
|
+
try {
|
|
2722
|
+
const limit = Math.floor(query.limit ?? 10);
|
|
2723
|
+
const strategy = query.strategy ?? "hybrid";
|
|
2724
|
+
if (strategy === "semantic" && embedding) {
|
|
2725
|
+
return this._semanticRetrieve(session, embedding, query, limit);
|
|
2726
|
+
}
|
|
2727
|
+
const strLimit = String(limit);
|
|
2728
|
+
let cypher;
|
|
2729
|
+
const params = {};
|
|
2730
|
+
const hasActor = query.actorId !== void 0;
|
|
2731
|
+
switch (strategy) {
|
|
2732
|
+
case "recent":
|
|
2733
|
+
if (hasActor) {
|
|
2734
|
+
cypher = "MATCH (m:Memory) WHERE m.actorId = $actorId RETURN m ORDER BY m.createdAt DESC LIMIT " + strLimit;
|
|
2735
|
+
params.actorId = query.actorId;
|
|
2736
|
+
} else {
|
|
2737
|
+
cypher = "MATCH (m:Memory) RETURN m ORDER BY m.createdAt DESC LIMIT " + strLimit;
|
|
2738
|
+
}
|
|
2739
|
+
break;
|
|
2740
|
+
case "important":
|
|
2741
|
+
if (hasActor) {
|
|
2742
|
+
cypher = "MATCH (m:Memory) WHERE m.actorId = $actorId RETURN m ORDER BY m.importance DESC LIMIT " + strLimit;
|
|
2743
|
+
params.actorId = query.actorId;
|
|
2744
|
+
} else {
|
|
2745
|
+
cypher = "MATCH (m:Memory) RETURN m ORDER BY m.importance DESC LIMIT " + strLimit;
|
|
2746
|
+
}
|
|
2747
|
+
break;
|
|
2748
|
+
case "hybrid":
|
|
2749
|
+
default:
|
|
2750
|
+
if (embedding) {
|
|
2751
|
+
return this._semanticRetrieve(session, embedding, query, limit);
|
|
2752
|
+
}
|
|
2753
|
+
if (hasActor) {
|
|
2754
|
+
cypher = "MATCH (m:Memory) WHERE m.actorId = $actorId RETURN m ORDER BY m.importance DESC, m.createdAt DESC LIMIT " + strLimit;
|
|
2755
|
+
params.actorId = query.actorId;
|
|
2756
|
+
} else {
|
|
2757
|
+
cypher = "MATCH (m:Memory) RETURN m ORDER BY m.importance DESC, m.createdAt DESC LIMIT " + strLimit;
|
|
2758
|
+
}
|
|
2759
|
+
break;
|
|
2760
|
+
}
|
|
2761
|
+
const result = await session.run(cypher, params);
|
|
2762
|
+
const memories = result.records.map((r) => {
|
|
2763
|
+
const raw = r.get("m");
|
|
2764
|
+
const node = raw.properties ?? raw;
|
|
2765
|
+
return this._nodeToMemory(node);
|
|
2766
|
+
});
|
|
2767
|
+
return this._postFilter(memories, query);
|
|
2768
|
+
} finally {
|
|
2769
|
+
await session.close();
|
|
2770
|
+
}
|
|
2771
|
+
}
|
|
2772
|
+
async count(filter) {
|
|
2773
|
+
const session = this.driver.session({ database: this.database });
|
|
2774
|
+
try {
|
|
2775
|
+
const conditions = [];
|
|
2776
|
+
const params = {};
|
|
2777
|
+
if (filter?.actorId) {
|
|
2778
|
+
conditions.push("m.actorId = $actorId");
|
|
2779
|
+
params.actorId = filter.actorId;
|
|
2780
|
+
}
|
|
2781
|
+
if (filter?.memoryType) {
|
|
2782
|
+
conditions.push("m.memoryType = $memoryType");
|
|
2783
|
+
params.memoryType = filter.memoryType;
|
|
2784
|
+
}
|
|
2785
|
+
if (filter?.minImportance !== void 0) {
|
|
2786
|
+
conditions.push("m.importance >= $minImportance");
|
|
2787
|
+
params.minImportance = filter.minImportance;
|
|
2788
|
+
}
|
|
2789
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
2790
|
+
const result = await session.run(
|
|
2791
|
+
`MATCH (m:Memory) ${where} RETURN count(m) as count`,
|
|
2792
|
+
params
|
|
2793
|
+
);
|
|
2794
|
+
const rawCount = result.records[0].get("count");
|
|
2795
|
+
return typeof rawCount === "number" ? rawCount : Number(rawCount);
|
|
2796
|
+
} finally {
|
|
2797
|
+
await session.close();
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
async graphQuery(cypher, params) {
|
|
2801
|
+
const session = this.driver.session({ database: this.database });
|
|
2802
|
+
try {
|
|
2803
|
+
const result = await session.run(cypher, params);
|
|
2804
|
+
return result.records.map((r) => {
|
|
2805
|
+
const obj = r.toObject();
|
|
2806
|
+
const mKey = Object.keys(obj).find((k) => obj[k] && typeof obj[k] === "object" && "properties" in obj[k]);
|
|
2807
|
+
if (mKey) {
|
|
2808
|
+
const raw = obj[mKey];
|
|
2809
|
+
const node = raw.properties ?? raw;
|
|
2810
|
+
return this._nodeToMemory(node);
|
|
2811
|
+
}
|
|
2812
|
+
return this._nodeToMemory(obj);
|
|
2813
|
+
});
|
|
2814
|
+
} finally {
|
|
2815
|
+
await session.close();
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
async close() {
|
|
2819
|
+
await this.driver.close();
|
|
2820
|
+
}
|
|
2821
|
+
async _semanticRetrieve(session, embedding, query, limit) {
|
|
2822
|
+
const hasActor = query.actorId !== void 0;
|
|
2823
|
+
const strLimit = String(limit);
|
|
2824
|
+
const cypher = hasActor ? `CALL db.index.vector.queryNodes('memory_embedding', ${strLimit}, $embedding) YIELD node, score
|
|
2825
|
+
MATCH (node) WHERE node.actorId = $actorId
|
|
2826
|
+
RETURN node ORDER BY score LIMIT ${strLimit}` : `CALL db.index.vector.queryNodes('memory_embedding', ${strLimit}, $embedding) YIELD node, score
|
|
2827
|
+
RETURN node ORDER BY score LIMIT ${strLimit}`;
|
|
2828
|
+
const params = { embedding };
|
|
2829
|
+
if (hasActor) params.actorId = query.actorId;
|
|
2830
|
+
const result = await session.run(cypher, params);
|
|
2831
|
+
const memories = result.records.map((r) => {
|
|
2832
|
+
const raw = r.get("node");
|
|
2833
|
+
const node = raw.properties ?? raw;
|
|
2834
|
+
return this._nodeToMemory(node);
|
|
2835
|
+
});
|
|
2836
|
+
return this._postFilter(memories, query);
|
|
2837
|
+
}
|
|
2838
|
+
_postFilter(memories, query) {
|
|
2839
|
+
const now = /* @__PURE__ */ new Date();
|
|
2840
|
+
let results = memories.filter((m) => !m.expiresAt || m.expiresAt > now);
|
|
2841
|
+
if (query.memoryTypes && query.memoryTypes.length > 0) {
|
|
2842
|
+
results = results.filter((m) => query.memoryTypes.includes(m.memoryType));
|
|
2843
|
+
}
|
|
2844
|
+
if (query.tags && query.tags.length > 0) {
|
|
2845
|
+
results = results.filter((m) => m.tags?.some((t) => query.tags.includes(t)));
|
|
2846
|
+
}
|
|
2847
|
+
if (query.query) {
|
|
2848
|
+
const q = query.query.toLowerCase();
|
|
2849
|
+
results = results.filter((m) => m.content.toLowerCase().includes(q));
|
|
2850
|
+
}
|
|
2851
|
+
return results;
|
|
2852
|
+
}
|
|
2853
|
+
_nodeToMemory(node) {
|
|
2854
|
+
return {
|
|
2855
|
+
id: node.id,
|
|
2856
|
+
actorId: node.actorId,
|
|
2857
|
+
memoryType: node.memoryType ?? "interaction",
|
|
2858
|
+
content: node.content,
|
|
2859
|
+
importance: node.importance ?? 0.5,
|
|
2860
|
+
emotionalValence: node.emotionalValence ?? 0,
|
|
2861
|
+
tags: Array.isArray(node.tags) ? node.tags : [],
|
|
2862
|
+
embedding: node.embedding ?? void 0,
|
|
2863
|
+
sourceId: node.sourceId ?? void 0,
|
|
2864
|
+
metadata: typeof node.metadata === "string" ? JSON.parse(node.metadata) : node.metadata ?? {},
|
|
2865
|
+
expiresAt: node.expiresAt ? new Date(node.expiresAt) : void 0,
|
|
2866
|
+
createdAt: new Date(node.createdAt)
|
|
2867
|
+
};
|
|
2868
|
+
}
|
|
2869
|
+
};
|
|
2870
|
+
|
|
2871
|
+
// src/adapters/storage/weaviate.ts
|
|
2872
|
+
var WeaviateStorageAdapter = class {
|
|
2873
|
+
client;
|
|
2874
|
+
className;
|
|
2875
|
+
_collection = null;
|
|
2876
|
+
_idToWv = /* @__PURE__ */ new Map();
|
|
2877
|
+
constructor(config) {
|
|
2878
|
+
this.client = config.client;
|
|
2879
|
+
this.className = config.className ?? "MemstackMemory";
|
|
2880
|
+
}
|
|
2881
|
+
async initialize() {
|
|
2882
|
+
this._collection = this.client.collections.get(this.className);
|
|
2883
|
+
}
|
|
2884
|
+
collection() {
|
|
2885
|
+
if (!this._collection) {
|
|
2886
|
+
this._collection = this.client.collections.get(this.className);
|
|
2887
|
+
}
|
|
2888
|
+
return this._collection;
|
|
2889
|
+
}
|
|
2890
|
+
generateId() {
|
|
2891
|
+
return `mem_${Date.now().toString(36)}_${crypto.randomUUID().slice(0, 8)}`;
|
|
2892
|
+
}
|
|
2893
|
+
async store(input) {
|
|
2894
|
+
const now = /* @__PURE__ */ new Date();
|
|
2895
|
+
const memory = {
|
|
2896
|
+
id: input.id ?? this.generateId(),
|
|
2897
|
+
actorId: input.actorId,
|
|
2898
|
+
memoryType: input.memoryType ?? "interaction",
|
|
2899
|
+
content: input.content,
|
|
2900
|
+
importance: input.importance ?? 0.5,
|
|
2901
|
+
emotionalValence: input.emotionalValence ?? 0,
|
|
2902
|
+
tags: input.tags ?? [],
|
|
2903
|
+
embedding: input.embedding,
|
|
2904
|
+
sourceId: input.sourceId,
|
|
2905
|
+
metadata: input.metadata ?? {},
|
|
2906
|
+
expiresAt: input.expiresAt,
|
|
2907
|
+
createdAt: now
|
|
2908
|
+
};
|
|
2909
|
+
const wvId = await this.collection().data.insert({
|
|
2910
|
+
id: crypto.randomUUID(),
|
|
2911
|
+
properties: this._toProperties(memory),
|
|
2912
|
+
vectors: input.embedding ?? new Array(1536).fill(0)
|
|
2913
|
+
});
|
|
2914
|
+
this._idToWv.set(memory.id, wvId);
|
|
2915
|
+
return memory;
|
|
2916
|
+
}
|
|
2917
|
+
async storeBatch(inputs) {
|
|
2918
|
+
const results = [];
|
|
2919
|
+
for (const input of inputs) {
|
|
2920
|
+
results.push(await this.store(input));
|
|
2921
|
+
}
|
|
2922
|
+
return results;
|
|
2923
|
+
}
|
|
2924
|
+
async get(id) {
|
|
2925
|
+
const wvId = this._idToWv.get(id);
|
|
2926
|
+
if (!wvId) return null;
|
|
2927
|
+
const obj = await this.collection().query.fetchObjectById(wvId, { includeVector: true });
|
|
2928
|
+
if (!obj) return null;
|
|
2929
|
+
const memory = this._fromProperties(obj.properties, obj.vectors?.default ?? void 0);
|
|
2930
|
+
if (memory.expiresAt && memory.expiresAt <= /* @__PURE__ */ new Date()) return null;
|
|
2931
|
+
return memory;
|
|
2932
|
+
}
|
|
2933
|
+
async delete(id) {
|
|
2934
|
+
const wvId = this._idToWv.get(id);
|
|
2935
|
+
if (!wvId) throw notFound("Memory", id);
|
|
2936
|
+
await this.collection().data.deleteById(wvId);
|
|
2937
|
+
this._idToWv.delete(id);
|
|
2938
|
+
}
|
|
2939
|
+
async deleteMany(ids) {
|
|
2940
|
+
let count = 0;
|
|
2941
|
+
for (const id of ids) {
|
|
2942
|
+
try {
|
|
2943
|
+
await this.delete(id);
|
|
2944
|
+
count++;
|
|
2945
|
+
} catch {
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
return count;
|
|
2949
|
+
}
|
|
2950
|
+
async retrieve(query, embedding) {
|
|
2951
|
+
const strategy = query.strategy ?? "recent";
|
|
2952
|
+
const limit = query.limit ?? 10;
|
|
2953
|
+
if ((strategy === "semantic" || strategy === "hybrid") && embedding) {
|
|
2954
|
+
const filters2 = this._buildFilter(query);
|
|
2955
|
+
const result2 = await this.collection().query.nearVector(embedding, {
|
|
2956
|
+
limit: 100,
|
|
2957
|
+
filters: filters2,
|
|
2958
|
+
includeVector: true
|
|
2959
|
+
});
|
|
2960
|
+
let objects2 = result2.objects.map((o) => ({
|
|
2961
|
+
...this._fromProperties(o.properties, o.vectors?.default),
|
|
2962
|
+
_score: o.metadata?.creationTimeUnix ?? 0
|
|
2963
|
+
}));
|
|
2964
|
+
objects2 = objects2.filter((o) => !o.expiresAt || o.expiresAt > /* @__PURE__ */ new Date());
|
|
2965
|
+
if (query.actorId) {
|
|
2966
|
+
objects2 = objects2.filter((o) => o.actorId === query.actorId);
|
|
2967
|
+
}
|
|
2968
|
+
if (strategy === "hybrid") {
|
|
2969
|
+
objects2.sort((a, b) => b._score + b.importance - (a._score + a.importance));
|
|
2970
|
+
}
|
|
2971
|
+
return objects2.slice(0, limit);
|
|
2972
|
+
}
|
|
2973
|
+
const filters = this._buildFilter(query);
|
|
2974
|
+
const result = await this.collection().query.fetchObjects({
|
|
2975
|
+
limit: 1e4,
|
|
2976
|
+
filters
|
|
2977
|
+
});
|
|
2978
|
+
let objects = result.objects.map((o) => ({
|
|
2979
|
+
...this._fromProperties(o.properties, o.vectors?.default),
|
|
2980
|
+
_createdAt: o.properties.createdAt,
|
|
2981
|
+
_importance: o.properties.importance ?? 0.5
|
|
2982
|
+
}));
|
|
2983
|
+
objects = objects.filter((o) => !o.expiresAt || o.expiresAt > /* @__PURE__ */ new Date());
|
|
2984
|
+
if (query.actorId) {
|
|
2985
|
+
objects = objects.filter((o) => o.actorId === query.actorId);
|
|
2986
|
+
}
|
|
2987
|
+
switch (strategy) {
|
|
2988
|
+
case "recent":
|
|
2989
|
+
objects.sort((a, b) => new Date(b._createdAt).getTime() - new Date(a._createdAt).getTime());
|
|
2990
|
+
break;
|
|
2991
|
+
case "important":
|
|
2992
|
+
objects.sort((a, b) => b._importance - a._importance);
|
|
2993
|
+
break;
|
|
2994
|
+
case "hybrid":
|
|
2995
|
+
case "semantic":
|
|
2996
|
+
default:
|
|
2997
|
+
objects.sort((a, b) => b._importance - a._importance || new Date(b._createdAt).getTime() - new Date(a._createdAt).getTime());
|
|
2998
|
+
break;
|
|
2999
|
+
}
|
|
3000
|
+
return objects.slice(0, limit);
|
|
3001
|
+
}
|
|
3002
|
+
async count(filter) {
|
|
3003
|
+
const and = [];
|
|
3004
|
+
if (filter?.actorId) {
|
|
3005
|
+
and.push({ operator: "Equal", target: { property: "actorId" }, value: filter.actorId });
|
|
3006
|
+
}
|
|
3007
|
+
if (filter?.memoryType) {
|
|
3008
|
+
and.push({ operator: "Equal", target: { property: "memoryType" }, value: filter.memoryType });
|
|
3009
|
+
}
|
|
3010
|
+
if (filter?.minImportance !== void 0) {
|
|
3011
|
+
and.push({ operator: "GreaterThanEqual", target: { property: "importance" }, value: filter.minImportance });
|
|
3012
|
+
}
|
|
3013
|
+
const finalFilter = and.length === 0 ? void 0 : and.length === 1 ? and[0] : { operator: "And", filters: and };
|
|
3014
|
+
const result = await this.collection().query.fetchObjects({
|
|
3015
|
+
limit: 1e4,
|
|
3016
|
+
filters: finalFilter
|
|
3017
|
+
});
|
|
3018
|
+
return result.objects.length;
|
|
3019
|
+
}
|
|
3020
|
+
async close() {
|
|
3021
|
+
}
|
|
3022
|
+
_buildFilter(query) {
|
|
3023
|
+
const and = [];
|
|
3024
|
+
if (query.actorId) {
|
|
3025
|
+
and.push({ operator: "Equal", target: { property: "actorId" }, value: query.actorId });
|
|
3026
|
+
}
|
|
3027
|
+
if (query.memoryTypes && query.memoryTypes.length > 0) {
|
|
3028
|
+
if (query.memoryTypes.length === 1) {
|
|
3029
|
+
and.push({ operator: "Equal", target: { property: "memoryType" }, value: query.memoryTypes[0] });
|
|
3030
|
+
} else {
|
|
3031
|
+
and.push({
|
|
3032
|
+
operator: "Or",
|
|
3033
|
+
filters: query.memoryTypes.map((t) => ({ operator: "Equal", target: { property: "memoryType" }, value: t }))
|
|
3034
|
+
});
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
if (query.tags && query.tags.length > 0) {
|
|
3038
|
+
if (query.tags.length === 1) {
|
|
3039
|
+
and.push({ operator: "ContainsAny", target: { property: "tags" }, values: query.tags });
|
|
3040
|
+
} else {
|
|
3041
|
+
and.push({
|
|
3042
|
+
operator: "Or",
|
|
3043
|
+
filters: query.tags.map((t) => ({ operator: "ContainsAny", target: { property: "tags" }, values: [t] }))
|
|
3044
|
+
});
|
|
3045
|
+
}
|
|
3046
|
+
}
|
|
3047
|
+
if (query.query) {
|
|
3048
|
+
and.push({ operator: "Like", target: { property: "content" }, value: `*${query.query}*` });
|
|
3049
|
+
}
|
|
3050
|
+
return and.length === 0 ? void 0 : and.length === 1 ? and[0] : { operator: "And", filters: and };
|
|
3051
|
+
}
|
|
3052
|
+
_toProperties(memory) {
|
|
3053
|
+
return {
|
|
3054
|
+
_memstackId: memory.id,
|
|
3055
|
+
actorId: memory.actorId,
|
|
3056
|
+
memoryType: memory.memoryType,
|
|
3057
|
+
content: memory.content,
|
|
3058
|
+
importance: memory.importance,
|
|
3059
|
+
emotionalValence: memory.emotionalValence,
|
|
3060
|
+
tags: memory.tags,
|
|
3061
|
+
metadata: JSON.stringify(memory.metadata ?? {}),
|
|
3062
|
+
sourceId: memory.sourceId ?? null,
|
|
3063
|
+
expiresAt: memory.expiresAt?.toISOString() ?? null,
|
|
3064
|
+
createdAt: memory.createdAt.toISOString()
|
|
3065
|
+
};
|
|
3066
|
+
}
|
|
3067
|
+
_fromProperties(props, vectors) {
|
|
3068
|
+
return {
|
|
3069
|
+
id: props._memstackId ?? "",
|
|
3070
|
+
actorId: props.actorId ?? "",
|
|
3071
|
+
memoryType: props.memoryType ?? "interaction",
|
|
3072
|
+
content: props.content ?? "",
|
|
3073
|
+
importance: props.importance ?? 0.5,
|
|
3074
|
+
emotionalValence: props.emotionalValence ?? 0,
|
|
3075
|
+
tags: Array.isArray(props.tags) ? props.tags : [],
|
|
3076
|
+
embedding: vectors,
|
|
3077
|
+
sourceId: props.sourceId ?? void 0,
|
|
3078
|
+
metadata: typeof props.metadata === "string" ? JSON.parse(props.metadata) : props.metadata ?? {},
|
|
3079
|
+
expiresAt: props.expiresAt ? new Date(props.expiresAt) : void 0,
|
|
3080
|
+
createdAt: new Date(props.createdAt ?? (/* @__PURE__ */ new Date()).toISOString())
|
|
3081
|
+
};
|
|
3082
|
+
}
|
|
3083
|
+
};
|
|
3084
|
+
|
|
3085
|
+
// src/adapters/storage/lancedb.ts
|
|
3086
|
+
var LanceDBStorageAdapter = class {
|
|
3087
|
+
connection;
|
|
3088
|
+
table;
|
|
3089
|
+
tableName;
|
|
3090
|
+
_zeroVector;
|
|
3091
|
+
constructor(config) {
|
|
3092
|
+
if (!config.connection) {
|
|
3093
|
+
throw configError("LanceDBStorageAdapter requires a LanceDB connection instance");
|
|
3094
|
+
}
|
|
3095
|
+
this.connection = config.connection;
|
|
3096
|
+
this.tableName = config.tableName ?? "memstack";
|
|
3097
|
+
this._zeroVector = new Array(1536).fill(0);
|
|
3098
|
+
}
|
|
3099
|
+
async initialize() {
|
|
3100
|
+
const tables = await this.connection.tableNames();
|
|
3101
|
+
if (!tables.includes(this.tableName)) {
|
|
3102
|
+
this.table = await this.connection.createTable(this.tableName, []);
|
|
3103
|
+
} else {
|
|
3104
|
+
this.table = await this.connection.openTable(this.tableName);
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
generateId() {
|
|
3108
|
+
return `mem_${Date.now().toString(36)}_${crypto.randomUUID().slice(0, 8)}`;
|
|
3109
|
+
}
|
|
3110
|
+
async store(input) {
|
|
3111
|
+
const now = /* @__PURE__ */ new Date();
|
|
3112
|
+
const id = input.id ?? this.generateId();
|
|
3113
|
+
const row = {
|
|
3114
|
+
id,
|
|
3115
|
+
vector: input.embedding ?? this._zeroVector,
|
|
3116
|
+
actor_id: input.actorId,
|
|
3117
|
+
memory_type: input.memoryType ?? "interaction",
|
|
3118
|
+
content: input.content,
|
|
3119
|
+
importance: input.importance ?? 0.5,
|
|
3120
|
+
emotional_valence: input.emotionalValence ?? 0,
|
|
3121
|
+
tags: JSON.stringify(input.tags ?? []),
|
|
3122
|
+
source_id: input.sourceId ?? "",
|
|
3123
|
+
metadata: JSON.stringify(input.metadata ?? {}),
|
|
3124
|
+
expires_at: input.expiresAt?.toISOString() ?? "",
|
|
3125
|
+
created_at: now.toISOString()
|
|
3126
|
+
};
|
|
3127
|
+
await this.table.add([row]);
|
|
3128
|
+
return {
|
|
3129
|
+
id,
|
|
3130
|
+
actorId: input.actorId,
|
|
3131
|
+
memoryType: input.memoryType ?? "interaction",
|
|
3132
|
+
content: input.content,
|
|
3133
|
+
importance: input.importance ?? 0.5,
|
|
3134
|
+
emotionalValence: input.emotionalValence ?? 0,
|
|
3135
|
+
tags: input.tags ?? [],
|
|
3136
|
+
embedding: input.embedding,
|
|
3137
|
+
sourceId: input.sourceId,
|
|
3138
|
+
metadata: input.metadata ?? {},
|
|
3139
|
+
expiresAt: input.expiresAt,
|
|
3140
|
+
createdAt: now
|
|
3141
|
+
};
|
|
3142
|
+
}
|
|
3143
|
+
async storeBatch(inputs) {
|
|
3144
|
+
const results = [];
|
|
3145
|
+
for (const input of inputs) {
|
|
3146
|
+
results.push(await this.store(input));
|
|
3147
|
+
}
|
|
3148
|
+
return results;
|
|
3149
|
+
}
|
|
3150
|
+
async get(id) {
|
|
3151
|
+
const results = await this.table.search(this._zeroVector).limit(1).where(`id = "${id}"`).execute();
|
|
3152
|
+
if (results.length === 0) return null;
|
|
3153
|
+
const row = results[0];
|
|
3154
|
+
if (row.expires_at) {
|
|
3155
|
+
const expiresAt = new Date(row.expires_at);
|
|
3156
|
+
if (expiresAt <= /* @__PURE__ */ new Date()) {
|
|
3157
|
+
await this.table.delete(`id = "${id}"`);
|
|
3158
|
+
return null;
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
return this._rowToMemory(row);
|
|
3162
|
+
}
|
|
3163
|
+
async delete(id) {
|
|
3164
|
+
const existing = await this.get(id);
|
|
3165
|
+
if (!existing) throw notFound("Memory", id);
|
|
3166
|
+
await this.table.delete(`id = "${id}"`);
|
|
3167
|
+
}
|
|
3168
|
+
async deleteMany(ids) {
|
|
3169
|
+
const before = await this.table.countRows();
|
|
3170
|
+
const inClause = ids.map((id) => `"${id}"`).join(", ");
|
|
3171
|
+
await this.table.delete(`id IN (${inClause})`);
|
|
3172
|
+
const after = await this.table.countRows();
|
|
3173
|
+
return before - after;
|
|
3174
|
+
}
|
|
3175
|
+
async retrieve(query, embedding) {
|
|
3176
|
+
const conditions = [];
|
|
3177
|
+
if (query.actorId) {
|
|
3178
|
+
conditions.push(`actor_id = "${query.actorId}"`);
|
|
3179
|
+
}
|
|
3180
|
+
const where = conditions.length > 0 ? conditions.join(" AND ") : "";
|
|
3181
|
+
const isSemantic = (query.strategy === "semantic" || query.strategy === "hybrid") && embedding && embedding.length > 0;
|
|
3182
|
+
const searchVec = isSemantic ? embedding : this._zeroVector;
|
|
3183
|
+
const scanLimit = isSemantic ? query.limit ?? 10 : 1e4;
|
|
3184
|
+
const results = await this.table.search(searchVec).limit(scanLimit).where(where).execute();
|
|
3185
|
+
let rows = results;
|
|
3186
|
+
const now = /* @__PURE__ */ new Date();
|
|
3187
|
+
rows = rows.filter((r) => !r.expires_at || new Date(r.expires_at) > now);
|
|
3188
|
+
if (query.actorId) {
|
|
3189
|
+
rows = rows.filter((r) => r.actor_id === query.actorId);
|
|
3190
|
+
}
|
|
3191
|
+
if (query.memoryTypes && query.memoryTypes.length > 0) {
|
|
3192
|
+
rows = rows.filter((r) => query.memoryTypes.includes(r.memory_type));
|
|
3193
|
+
}
|
|
3194
|
+
if (query.tags && query.tags.length > 0) {
|
|
3195
|
+
rows = rows.filter((r) => {
|
|
3196
|
+
const tags = JSON.parse(r.tags);
|
|
3197
|
+
return tags.some((t) => query.tags.includes(t));
|
|
3198
|
+
});
|
|
3199
|
+
}
|
|
3200
|
+
if (query.query) {
|
|
3201
|
+
const q = query.query.toLowerCase();
|
|
3202
|
+
rows = rows.filter((r) => r.content.toLowerCase().includes(q));
|
|
3203
|
+
}
|
|
3204
|
+
const limit = query.limit ?? 10;
|
|
3205
|
+
if (isSemantic && query.strategy === "hybrid") {
|
|
3206
|
+
rows.sort((a, b) => {
|
|
3207
|
+
const scoreA = (a._distance ?? 0) + a.importance;
|
|
3208
|
+
const scoreB = (b._distance ?? 0) + b.importance;
|
|
3209
|
+
return scoreB - scoreA;
|
|
3210
|
+
});
|
|
3211
|
+
} else if (query.strategy === "recent") {
|
|
3212
|
+
rows.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
|
|
3213
|
+
} else if (query.strategy === "important") {
|
|
3214
|
+
rows.sort((a, b) => b.importance - a.importance);
|
|
3215
|
+
} else if (!isSemantic) {
|
|
3216
|
+
rows.sort((a, b) => b.importance - a.importance || new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
|
|
3217
|
+
}
|
|
3218
|
+
return rows.slice(0, limit).map((r) => this._rowToMemory(r));
|
|
3219
|
+
}
|
|
3220
|
+
async count(filter) {
|
|
3221
|
+
const conditions = [];
|
|
3222
|
+
if (filter?.actorId) {
|
|
3223
|
+
conditions.push(`actor_id = "${filter.actorId}"`);
|
|
3224
|
+
}
|
|
3225
|
+
const where = conditions.length > 0 ? conditions.join(" AND ") : "";
|
|
3226
|
+
const results = await this.table.search(this._zeroVector).limit(1e4).where(where).execute();
|
|
3227
|
+
const now = /* @__PURE__ */ new Date();
|
|
3228
|
+
let rows = results.filter((r) => !r.expires_at || new Date(r.expires_at) > now);
|
|
3229
|
+
if (filter?.actorId) {
|
|
3230
|
+
rows = rows.filter((r) => r.actor_id === filter.actorId);
|
|
3231
|
+
}
|
|
3232
|
+
if (filter?.memoryType) {
|
|
3233
|
+
rows = rows.filter((r) => r.memory_type === filter.memoryType);
|
|
3234
|
+
}
|
|
3235
|
+
if (filter?.minImportance !== void 0) {
|
|
3236
|
+
rows = rows.filter((r) => r.importance >= filter.minImportance);
|
|
3237
|
+
}
|
|
3238
|
+
return rows.length;
|
|
3239
|
+
}
|
|
3240
|
+
async close() {
|
|
3241
|
+
}
|
|
3242
|
+
_rowToMemory(row) {
|
|
3243
|
+
return {
|
|
3244
|
+
id: row.id,
|
|
3245
|
+
actorId: row.actor_id,
|
|
3246
|
+
memoryType: row.memory_type,
|
|
3247
|
+
content: row.content,
|
|
3248
|
+
importance: row.importance,
|
|
3249
|
+
emotionalValence: row.emotional_valence,
|
|
3250
|
+
tags: row.tags ? JSON.parse(row.tags) : [],
|
|
3251
|
+
embedding: row.vector && row.vector.length > 0 ? row.vector : void 0,
|
|
3252
|
+
sourceId: row.source_id || void 0,
|
|
3253
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : {},
|
|
3254
|
+
expiresAt: row.expires_at ? new Date(row.expires_at) : void 0,
|
|
3255
|
+
createdAt: new Date(row.created_at)
|
|
3256
|
+
};
|
|
3257
|
+
}
|
|
3258
|
+
};
|
|
3259
|
+
|
|
3260
|
+
// src/adapters/storage/mongodb.ts
|
|
3261
|
+
var MongoDBStorageAdapter = class {
|
|
3262
|
+
collection;
|
|
3263
|
+
vectorDimensions;
|
|
3264
|
+
constructor(config) {
|
|
3265
|
+
this.collection = config.collection;
|
|
3266
|
+
this.vectorDimensions = config.vectorDimensions ?? 1536;
|
|
3267
|
+
}
|
|
3268
|
+
async initialize() {
|
|
3269
|
+
try {
|
|
3270
|
+
const col = this.collection;
|
|
3271
|
+
if (col.createSearchIndex) {
|
|
3272
|
+
await col.createSearchIndex({
|
|
3273
|
+
name: "memstack_vector",
|
|
3274
|
+
definition: {
|
|
3275
|
+
mappings: {
|
|
3276
|
+
dynamic: false,
|
|
3277
|
+
fields: {
|
|
3278
|
+
embedding: {
|
|
3279
|
+
type: "knnVector",
|
|
3280
|
+
dimensions: this.vectorDimensions,
|
|
3281
|
+
similarity: "cosine"
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
});
|
|
3287
|
+
}
|
|
3288
|
+
} catch {
|
|
3289
|
+
}
|
|
3290
|
+
}
|
|
3291
|
+
generateId() {
|
|
3292
|
+
return `mem_${Date.now().toString(36)}_${crypto.randomUUID().slice(0, 8)}`;
|
|
3293
|
+
}
|
|
3294
|
+
async store(input) {
|
|
3295
|
+
const now = /* @__PURE__ */ new Date();
|
|
3296
|
+
const memory = {
|
|
3297
|
+
id: input.id ?? this.generateId(),
|
|
3298
|
+
actorId: input.actorId,
|
|
3299
|
+
memoryType: input.memoryType ?? "interaction",
|
|
3300
|
+
content: input.content,
|
|
3301
|
+
importance: input.importance ?? 0.5,
|
|
3302
|
+
emotionalValence: input.emotionalValence ?? 0,
|
|
3303
|
+
tags: input.tags ?? [],
|
|
3304
|
+
embedding: input.embedding,
|
|
3305
|
+
sourceId: input.sourceId,
|
|
3306
|
+
metadata: input.metadata ?? {},
|
|
3307
|
+
expiresAt: input.expiresAt,
|
|
3308
|
+
createdAt: now
|
|
3309
|
+
};
|
|
3310
|
+
await this.collection.insertOne(this.toDoc(memory));
|
|
3311
|
+
return memory;
|
|
3312
|
+
}
|
|
3313
|
+
async storeBatch(inputs) {
|
|
3314
|
+
const results = [];
|
|
3315
|
+
for (const input of inputs) {
|
|
3316
|
+
results.push(await this.store(input));
|
|
3317
|
+
}
|
|
3318
|
+
return results;
|
|
3319
|
+
}
|
|
3320
|
+
async get(id) {
|
|
3321
|
+
const doc = await this.collection.findOne({ _id: id });
|
|
3322
|
+
if (!doc) return null;
|
|
3323
|
+
const memory = this.toMemory(doc);
|
|
3324
|
+
if (memory.expiresAt && memory.expiresAt <= /* @__PURE__ */ new Date()) return null;
|
|
3325
|
+
return memory;
|
|
3326
|
+
}
|
|
3327
|
+
async delete(id) {
|
|
3328
|
+
const result = await this.collection.deleteOne({ _id: id });
|
|
3329
|
+
if (result.deletedCount === 0) throw notFound("Memory", id);
|
|
3330
|
+
}
|
|
3331
|
+
async deleteMany(ids) {
|
|
3332
|
+
const result = await this.collection.deleteMany({ _id: { $in: ids } });
|
|
3333
|
+
return result.deletedCount;
|
|
3334
|
+
}
|
|
3335
|
+
async retrieve(query, embedding) {
|
|
3336
|
+
if (query.strategy === "semantic" && embedding) {
|
|
3337
|
+
return this.retrieveSemantic(query, embedding);
|
|
3338
|
+
}
|
|
3339
|
+
return this.retrieveStandard(query);
|
|
3340
|
+
}
|
|
3341
|
+
async count(filter) {
|
|
3342
|
+
const mongoFilter = {};
|
|
3343
|
+
if (filter?.actorId) mongoFilter.actorId = filter.actorId;
|
|
3344
|
+
if (filter?.memoryType) mongoFilter.memoryType = filter.memoryType;
|
|
3345
|
+
if (filter?.minImportance !== void 0) {
|
|
3346
|
+
mongoFilter.importance = { $gte: filter.minImportance };
|
|
3347
|
+
}
|
|
3348
|
+
return this.collection.countDocuments(mongoFilter);
|
|
3349
|
+
}
|
|
3350
|
+
async close() {
|
|
3351
|
+
}
|
|
3352
|
+
async retrieveStandard(query) {
|
|
3353
|
+
const filter = {};
|
|
3354
|
+
if (query.actorId) filter.actorId = query.actorId;
|
|
3355
|
+
if (query.tags && query.tags.length > 0) {
|
|
3356
|
+
filter.tags = { $in: query.tags };
|
|
3357
|
+
}
|
|
3358
|
+
if (query.memoryTypes && query.memoryTypes.length > 0) {
|
|
3359
|
+
filter.memoryType = { $in: query.memoryTypes };
|
|
3360
|
+
}
|
|
3361
|
+
let sortObj;
|
|
3362
|
+
switch (query.strategy) {
|
|
3363
|
+
case "recent":
|
|
3364
|
+
sortObj = { createdAt: -1 };
|
|
3365
|
+
break;
|
|
3366
|
+
case "important":
|
|
3367
|
+
sortObj = { importance: -1 };
|
|
3368
|
+
break;
|
|
3369
|
+
default:
|
|
3370
|
+
sortObj = { importance: -1, createdAt: -1 };
|
|
3371
|
+
break;
|
|
3372
|
+
}
|
|
3373
|
+
const docs = await this.collection.find(filter).sort(sortObj).limit(1e4).toArray();
|
|
3374
|
+
const now = /* @__PURE__ */ new Date();
|
|
3375
|
+
let results = docs.map((doc) => this.toMemory(doc)).filter((m) => !m.expiresAt || m.expiresAt > now);
|
|
3376
|
+
if (query.query) {
|
|
3377
|
+
const q = query.query.toLowerCase();
|
|
3378
|
+
results = results.filter((m) => m.content.toLowerCase().includes(q));
|
|
3379
|
+
}
|
|
3380
|
+
return results.slice(0, query.limit ?? 10);
|
|
3381
|
+
}
|
|
3382
|
+
async retrieveSemantic(query, embedding) {
|
|
3383
|
+
try {
|
|
3384
|
+
const filter = {};
|
|
3385
|
+
if (query.actorId) filter.actorId = query.actorId;
|
|
3386
|
+
const pipeline = [
|
|
3387
|
+
{
|
|
3388
|
+
$vectorSearch: {
|
|
3389
|
+
index: "memstack_vector",
|
|
3390
|
+
path: "embedding",
|
|
3391
|
+
queryVector: embedding,
|
|
3392
|
+
numCandidates: 100,
|
|
3393
|
+
limit: query.limit ?? 10,
|
|
3394
|
+
filter
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
3397
|
+
];
|
|
3398
|
+
const docs = await this.collection.aggregate(pipeline).toArray();
|
|
3399
|
+
const now = /* @__PURE__ */ new Date();
|
|
3400
|
+
return docs.map((doc) => this.toMemory(doc)).filter((m) => !m.expiresAt || m.expiresAt > now);
|
|
3401
|
+
} catch {
|
|
3402
|
+
return this.retrieveStandard(query);
|
|
3403
|
+
}
|
|
3404
|
+
}
|
|
3405
|
+
toMemory(doc) {
|
|
3406
|
+
return {
|
|
3407
|
+
id: doc._id,
|
|
3408
|
+
actorId: doc.actorId,
|
|
3409
|
+
memoryType: doc.memoryType,
|
|
3410
|
+
content: doc.content,
|
|
3411
|
+
importance: doc.importance,
|
|
3412
|
+
emotionalValence: doc.emotionalValence,
|
|
3413
|
+
tags: doc.tags,
|
|
3414
|
+
embedding: doc.embedding,
|
|
3415
|
+
sourceId: doc.sourceId,
|
|
3416
|
+
metadata: doc.metadata,
|
|
3417
|
+
expiresAt: doc.expiresAt,
|
|
3418
|
+
createdAt: doc.createdAt
|
|
3419
|
+
};
|
|
3420
|
+
}
|
|
3421
|
+
toDoc(memory) {
|
|
3422
|
+
return {
|
|
3423
|
+
_id: memory.id,
|
|
3424
|
+
actorId: memory.actorId,
|
|
3425
|
+
memoryType: memory.memoryType,
|
|
3426
|
+
content: memory.content,
|
|
3427
|
+
importance: memory.importance,
|
|
3428
|
+
emotionalValence: memory.emotionalValence,
|
|
3429
|
+
tags: memory.tags,
|
|
3430
|
+
embedding: memory.embedding,
|
|
3431
|
+
sourceId: memory.sourceId,
|
|
3432
|
+
metadata: memory.metadata,
|
|
3433
|
+
expiresAt: memory.expiresAt,
|
|
3434
|
+
createdAt: memory.createdAt
|
|
3435
|
+
};
|
|
3436
|
+
}
|
|
3437
|
+
};
|
|
3438
|
+
|
|
3439
|
+
// src/adapters/storage/sqlite.ts
|
|
3440
|
+
function cosineSimilarity(a, b) {
|
|
3441
|
+
if (a.length === 0 || b.length === 0) return 0;
|
|
3442
|
+
const dims = Math.min(a.length, b.length);
|
|
3443
|
+
let dot = 0;
|
|
3444
|
+
let magA = 0;
|
|
3445
|
+
let magB = 0;
|
|
3446
|
+
for (let i = 0; i < dims; i++) {
|
|
3447
|
+
dot += a[i] * b[i];
|
|
3448
|
+
magA += a[i] * a[i];
|
|
3449
|
+
magB += b[i] * b[i];
|
|
3450
|
+
}
|
|
3451
|
+
const mag = Math.sqrt(magA) * Math.sqrt(magB);
|
|
3452
|
+
return mag === 0 ? 0 : dot / mag;
|
|
3453
|
+
}
|
|
3454
|
+
var SQLiteStorageAdapter = class {
|
|
3455
|
+
db;
|
|
3456
|
+
table;
|
|
3457
|
+
vectorDimensions;
|
|
3458
|
+
constructor(config) {
|
|
3459
|
+
if (!config.db) {
|
|
3460
|
+
throw configError("SQLiteStorageAdapter requires a better-sqlite3 database instance");
|
|
3461
|
+
}
|
|
3462
|
+
this.db = config.db;
|
|
3463
|
+
this.table = config.tableName ?? "memstack_memories";
|
|
3464
|
+
if (!/^[a-zA-Z0-9_]+$/.test(this.table)) {
|
|
3465
|
+
throw configError(`Invalid table name: "${this.table}". Use only alphanumeric characters and underscores.`);
|
|
3466
|
+
}
|
|
3467
|
+
this.vectorDimensions = config.vectorDimensions ?? 1536;
|
|
3468
|
+
}
|
|
3469
|
+
async initialize() {
|
|
3470
|
+
this.db.exec(`
|
|
3471
|
+
CREATE TABLE IF NOT EXISTS ${this.table} (
|
|
3472
|
+
id TEXT PRIMARY KEY,
|
|
3473
|
+
actor_id TEXT NOT NULL,
|
|
3474
|
+
memory_type TEXT NOT NULL DEFAULT 'interaction',
|
|
3475
|
+
content TEXT NOT NULL,
|
|
3476
|
+
importance REAL NOT NULL DEFAULT 0.5,
|
|
3477
|
+
emotional_valence REAL NOT NULL DEFAULT 0,
|
|
3478
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
3479
|
+
embedding TEXT,
|
|
3480
|
+
source_id TEXT,
|
|
3481
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
3482
|
+
expires_at TEXT,
|
|
3483
|
+
created_at TEXT NOT NULL,
|
|
3484
|
+
touched_at TEXT NOT NULL
|
|
3485
|
+
)
|
|
3486
|
+
`);
|
|
3487
|
+
this.db.exec(`CREATE INDEX IF NOT EXISTS idx_${this.table}_actor ON ${this.table}(actor_id)`);
|
|
3488
|
+
this.db.exec(`CREATE INDEX IF NOT EXISTS idx_${this.table}_created ON ${this.table}(created_at DESC)`);
|
|
3489
|
+
this.db.exec(`CREATE INDEX IF NOT EXISTS idx_${this.table}_importance ON ${this.table}(importance DESC)`);
|
|
3490
|
+
}
|
|
3491
|
+
generateId() {
|
|
3492
|
+
return `mem_${Date.now().toString(36)}_${crypto.randomUUID().slice(0, 8)}`;
|
|
3493
|
+
}
|
|
3494
|
+
async store(input) {
|
|
3495
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3496
|
+
const id = input.id ?? this.generateId();
|
|
3497
|
+
this.db.prepare(
|
|
3498
|
+
`INSERT INTO ${this.table} (id, actor_id, memory_type, content, importance, emotional_valence, tags, embedding, source_id, metadata, expires_at, created_at, touched_at)
|
|
3499
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3500
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
3501
|
+
content = excluded.content,
|
|
3502
|
+
importance = excluded.importance,
|
|
3503
|
+
emotional_valence = excluded.emotional_valence,
|
|
3504
|
+
tags = excluded.tags,
|
|
3505
|
+
embedding = excluded.embedding,
|
|
3506
|
+
source_id = excluded.source_id,
|
|
3507
|
+
metadata = excluded.metadata,
|
|
3508
|
+
expires_at = excluded.expires_at,
|
|
3509
|
+
touched_at = excluded.touched_at`
|
|
3510
|
+
).run(
|
|
3511
|
+
id,
|
|
3512
|
+
input.actorId,
|
|
3513
|
+
input.memoryType ?? "interaction",
|
|
3514
|
+
input.content,
|
|
3515
|
+
input.importance ?? 0.5,
|
|
3516
|
+
input.emotionalValence ?? 0,
|
|
3517
|
+
JSON.stringify(input.tags ?? []),
|
|
3518
|
+
input.embedding ? JSON.stringify(input.embedding) : null,
|
|
3519
|
+
input.sourceId ?? null,
|
|
3520
|
+
JSON.stringify(input.metadata ?? {}),
|
|
3521
|
+
input.expiresAt?.toISOString() ?? null,
|
|
3522
|
+
now,
|
|
3523
|
+
now
|
|
3524
|
+
);
|
|
3525
|
+
return {
|
|
3526
|
+
id,
|
|
3527
|
+
actorId: input.actorId,
|
|
3528
|
+
memoryType: input.memoryType ?? "interaction",
|
|
3529
|
+
content: input.content,
|
|
3530
|
+
importance: input.importance ?? 0.5,
|
|
3531
|
+
emotionalValence: input.emotionalValence ?? 0,
|
|
3532
|
+
tags: input.tags ?? [],
|
|
3533
|
+
embedding: input.embedding,
|
|
3534
|
+
sourceId: input.sourceId,
|
|
3535
|
+
metadata: input.metadata ?? {},
|
|
3536
|
+
expiresAt: input.expiresAt,
|
|
3537
|
+
createdAt: new Date(now)
|
|
3538
|
+
};
|
|
3539
|
+
}
|
|
3540
|
+
async storeBatch(inputs) {
|
|
3541
|
+
const results = [];
|
|
3542
|
+
for (const input of inputs) {
|
|
3543
|
+
results.push(await this.store(input));
|
|
3544
|
+
}
|
|
3545
|
+
return results;
|
|
3546
|
+
}
|
|
3547
|
+
async get(id) {
|
|
3548
|
+
const row = this.db.prepare(
|
|
3549
|
+
`SELECT * FROM ${this.table} WHERE id = ? AND (expires_at IS NULL OR expires_at > ?)`
|
|
3550
|
+
).get(id, (/* @__PURE__ */ new Date()).toISOString());
|
|
3551
|
+
if (!row) return null;
|
|
3552
|
+
return this._rowToMemory(row);
|
|
3553
|
+
}
|
|
3554
|
+
async delete(id) {
|
|
3555
|
+
const row = this.db.prepare(`SELECT id FROM ${this.table} WHERE id = ?`).get(id);
|
|
3556
|
+
if (!row) throw notFound("Memory", id);
|
|
3557
|
+
this.db.prepare(`DELETE FROM ${this.table} WHERE id = ?`).run(id);
|
|
3558
|
+
}
|
|
3559
|
+
async deleteMany(ids) {
|
|
3560
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
3561
|
+
const result = this.db.prepare(
|
|
3562
|
+
`DELETE FROM ${this.table} WHERE id IN (${placeholders})`
|
|
3563
|
+
).run(...ids);
|
|
3564
|
+
return result.changes;
|
|
3565
|
+
}
|
|
3566
|
+
async touch(id) {
|
|
3567
|
+
const row = this.db.prepare(`SELECT id FROM ${this.table} WHERE id = ?`).get(id);
|
|
3568
|
+
if (!row) throw notFound("Memory", id);
|
|
3569
|
+
this.db.prepare(`UPDATE ${this.table} SET touched_at = ? WHERE id = ?`).run((/* @__PURE__ */ new Date()).toISOString(), id);
|
|
3570
|
+
}
|
|
3571
|
+
async retrieve(query, embedding) {
|
|
3572
|
+
const conditions = ["(expires_at IS NULL OR expires_at > ?)"];
|
|
3573
|
+
const params = [(/* @__PURE__ */ new Date()).toISOString()];
|
|
3574
|
+
if (query.actorId) {
|
|
3575
|
+
conditions.push("actor_id = ?");
|
|
3576
|
+
params.push(query.actorId);
|
|
3577
|
+
}
|
|
3578
|
+
if (query.memoryTypes && query.memoryTypes.length > 0) {
|
|
3579
|
+
const placeholders = query.memoryTypes.map(() => "?").join(", ");
|
|
3580
|
+
conditions.push(`memory_type IN (${placeholders})`);
|
|
3581
|
+
params.push(...query.memoryTypes);
|
|
3582
|
+
}
|
|
3583
|
+
if (query.createdAfter) {
|
|
3584
|
+
conditions.push("created_at >= ?");
|
|
3585
|
+
params.push(query.createdAfter.toISOString());
|
|
3586
|
+
}
|
|
3587
|
+
if (query.createdBefore) {
|
|
3588
|
+
conditions.push("created_at <= ?");
|
|
3589
|
+
params.push(query.createdBefore.toISOString());
|
|
3590
|
+
}
|
|
3591
|
+
const where = `WHERE ${conditions.join(" AND ")}`;
|
|
3592
|
+
const hasSemantic = (query.strategy === "semantic" || query.strategy === "hybrid") && embedding && embedding.length > 0;
|
|
3593
|
+
let orderBy;
|
|
3594
|
+
switch (query.strategy) {
|
|
3595
|
+
case "recent":
|
|
3596
|
+
orderBy = "touched_at DESC";
|
|
3597
|
+
break;
|
|
3598
|
+
case "important":
|
|
3599
|
+
orderBy = "importance DESC";
|
|
3600
|
+
break;
|
|
3601
|
+
case "hybrid":
|
|
3602
|
+
case "semantic":
|
|
3603
|
+
default:
|
|
3604
|
+
if (hasSemantic) {
|
|
3605
|
+
orderBy = "touched_at DESC";
|
|
3606
|
+
} else {
|
|
3607
|
+
orderBy = "importance DESC, touched_at DESC";
|
|
3608
|
+
}
|
|
3609
|
+
break;
|
|
3610
|
+
}
|
|
3611
|
+
const limit = query.limit ?? 10;
|
|
3612
|
+
const sql = `SELECT * FROM ${this.table} ${where} ORDER BY ${orderBy}`;
|
|
3613
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
3614
|
+
let results = rows;
|
|
3615
|
+
if (query.query) {
|
|
3616
|
+
const q = query.query.toLowerCase();
|
|
3617
|
+
results = results.filter((r) => r.content.toLowerCase().includes(q));
|
|
3618
|
+
}
|
|
3619
|
+
if (query.tags && query.tags.length > 0) {
|
|
3620
|
+
results = results.filter((r) => {
|
|
3621
|
+
const tags = JSON.parse(r.tags);
|
|
3622
|
+
return tags.some((t) => query.tags.includes(t));
|
|
3623
|
+
});
|
|
3624
|
+
}
|
|
3625
|
+
if (hasSemantic && embedding) {
|
|
3626
|
+
const scored = results.filter((r) => r.embedding !== null).map((r) => {
|
|
3627
|
+
const emb = JSON.parse(r.embedding);
|
|
3628
|
+
const score = cosineSimilarity(embedding, emb);
|
|
3629
|
+
return { row: r, score };
|
|
3630
|
+
}).sort((a, b) => b.score - a.score);
|
|
3631
|
+
if (query.strategy === "hybrid") {
|
|
3632
|
+
scored.sort((a, b) => b.score + b.row.importance - (a.score + a.row.importance));
|
|
3633
|
+
}
|
|
3634
|
+
const topK = scored.slice(0, limit);
|
|
3635
|
+
for (const { row } of topK) {
|
|
3636
|
+
this.db.prepare(`UPDATE ${this.table} SET touched_at = ? WHERE id = ?`).run((/* @__PURE__ */ new Date()).toISOString(), row.id);
|
|
3637
|
+
}
|
|
3638
|
+
return topK.map(({ row }) => this._rowToMemory(row));
|
|
3639
|
+
}
|
|
3640
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3641
|
+
const limited = results.slice(0, limit);
|
|
3642
|
+
for (const row of limited) {
|
|
3643
|
+
this.db.prepare(`UPDATE ${this.table} SET touched_at = ? WHERE id = ?`).run(now, row.id);
|
|
3644
|
+
}
|
|
3645
|
+
return limited.map((r) => this._rowToMemory(r));
|
|
3646
|
+
}
|
|
3647
|
+
async count(filter) {
|
|
3648
|
+
const conditions = ["(expires_at IS NULL OR expires_at > ?)"];
|
|
3649
|
+
const params = [(/* @__PURE__ */ new Date()).toISOString()];
|
|
3650
|
+
if (filter?.actorId) {
|
|
3651
|
+
conditions.push("actor_id = ?");
|
|
3652
|
+
params.push(filter.actorId);
|
|
3653
|
+
}
|
|
3654
|
+
if (filter?.memoryType) {
|
|
3655
|
+
conditions.push("memory_type = ?");
|
|
3656
|
+
params.push(filter.memoryType);
|
|
3657
|
+
}
|
|
3658
|
+
if (filter?.minImportance !== void 0) {
|
|
3659
|
+
conditions.push("importance >= ?");
|
|
3660
|
+
params.push(filter.minImportance);
|
|
3661
|
+
}
|
|
3662
|
+
if (filter?.createdAfter) {
|
|
3663
|
+
conditions.push("created_at >= ?");
|
|
3664
|
+
params.push(filter.createdAfter.toISOString());
|
|
3665
|
+
}
|
|
3666
|
+
if (filter?.createdBefore) {
|
|
3667
|
+
conditions.push("created_at <= ?");
|
|
3668
|
+
params.push(filter.createdBefore.toISOString());
|
|
3669
|
+
}
|
|
3670
|
+
const where = conditions.join(" AND ");
|
|
3671
|
+
const row = this.db.prepare(
|
|
3672
|
+
`SELECT COUNT(*) as count FROM ${this.table} WHERE ${where}`
|
|
3673
|
+
).get(...params);
|
|
3674
|
+
return row?.count ?? 0;
|
|
3675
|
+
}
|
|
3676
|
+
async close() {
|
|
3677
|
+
this.db.close();
|
|
3678
|
+
}
|
|
3679
|
+
_rowToMemory(row) {
|
|
3680
|
+
return {
|
|
3681
|
+
id: row.id,
|
|
3682
|
+
actorId: row.actor_id,
|
|
3683
|
+
memoryType: row.memory_type,
|
|
3684
|
+
content: row.content,
|
|
3685
|
+
importance: row.importance,
|
|
3686
|
+
emotionalValence: row.emotional_valence,
|
|
3687
|
+
tags: JSON.parse(row.tags),
|
|
3688
|
+
embedding: row.embedding ? JSON.parse(row.embedding) : void 0,
|
|
3689
|
+
sourceId: row.source_id ?? void 0,
|
|
3690
|
+
metadata: JSON.parse(row.metadata),
|
|
3691
|
+
expiresAt: row.expires_at ? new Date(row.expires_at) : void 0,
|
|
3692
|
+
createdAt: new Date(row.created_at)
|
|
3693
|
+
};
|
|
3694
|
+
}
|
|
3695
|
+
};
|
|
3696
|
+
|
|
1496
3697
|
// src/adapters/llm/openai.ts
|
|
1497
3698
|
var OpenAILLMAdapter = class {
|
|
1498
3699
|
apiKey;
|
|
@@ -1632,7 +3833,6 @@ var AnthropicLLMAdapter = class {
|
|
|
1632
3833
|
baseURL;
|
|
1633
3834
|
defaultModel;
|
|
1634
3835
|
defaultMaxTokens;
|
|
1635
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1636
3836
|
sdk;
|
|
1637
3837
|
constructor(config) {
|
|
1638
3838
|
this.apiKey = config.apiKey;
|
|
@@ -1648,7 +3848,8 @@ var AnthropicLLMAdapter = class {
|
|
|
1648
3848
|
const { default: Anthropic } = await import('@anthropic-ai/sdk');
|
|
1649
3849
|
this.sdk = new Anthropic({ apiKey: this.apiKey, baseURL: this.baseURL });
|
|
1650
3850
|
}
|
|
1651
|
-
const
|
|
3851
|
+
const sdk = this.sdk;
|
|
3852
|
+
const response = await sdk.messages.create({
|
|
1652
3853
|
model,
|
|
1653
3854
|
system: request.system,
|
|
1654
3855
|
messages: [{ role: "user", content: request.user }],
|
|
@@ -1679,6 +3880,7 @@ var OpenAIEmbeddingAdapter = class {
|
|
|
1679
3880
|
baseURL;
|
|
1680
3881
|
model;
|
|
1681
3882
|
dimensions;
|
|
3883
|
+
maxBatchSize = 2048;
|
|
1682
3884
|
constructor(config) {
|
|
1683
3885
|
this.apiKey = config.apiKey;
|
|
1684
3886
|
this.baseURL = config.baseURL ?? "https://api.openai.com/v1";
|
|
@@ -1687,8 +3889,8 @@ var OpenAIEmbeddingAdapter = class {
|
|
|
1687
3889
|
}
|
|
1688
3890
|
async embed(texts) {
|
|
1689
3891
|
if (texts.length === 0) return [];
|
|
1690
|
-
if (texts.length >
|
|
1691
|
-
throw new MemStackError("VALIDATION_ERROR", `Max
|
|
3892
|
+
if (texts.length > this.maxBatchSize) {
|
|
3893
|
+
throw new MemStackError("VALIDATION_ERROR", `Max ${this.maxBatchSize} texts per batch, got ${texts.length}`);
|
|
1692
3894
|
}
|
|
1693
3895
|
try {
|
|
1694
3896
|
const response = await fetch(`${this.baseURL}/embeddings`, {
|
|
@@ -1735,6 +3937,7 @@ var CohereEmbeddingAdapter = class {
|
|
|
1735
3937
|
baseURL;
|
|
1736
3938
|
model;
|
|
1737
3939
|
dimensions;
|
|
3940
|
+
maxBatchSize = 96;
|
|
1738
3941
|
constructor(config) {
|
|
1739
3942
|
this.apiKey = config.apiKey;
|
|
1740
3943
|
this.baseURL = config.baseURL ?? "https://api.cohere.ai/v1";
|
|
@@ -1743,8 +3946,8 @@ var CohereEmbeddingAdapter = class {
|
|
|
1743
3946
|
}
|
|
1744
3947
|
async embed(texts) {
|
|
1745
3948
|
if (texts.length === 0) return [];
|
|
1746
|
-
if (texts.length >
|
|
1747
|
-
throw new MemStackError("VALIDATION_ERROR", `Cohere max
|
|
3949
|
+
if (texts.length > this.maxBatchSize) {
|
|
3950
|
+
throw new MemStackError("VALIDATION_ERROR", `Cohere max ${this.maxBatchSize} texts per batch, got ${texts.length}`);
|
|
1748
3951
|
}
|
|
1749
3952
|
try {
|
|
1750
3953
|
const response = await fetch(`${this.baseURL}/embed`, {
|
|
@@ -1969,6 +4172,6 @@ var GroqLLMAdapter = class {
|
|
|
1969
4172
|
}
|
|
1970
4173
|
};
|
|
1971
4174
|
|
|
1972
|
-
export { AnthropicLLMAdapter, CohereEmbeddingAdapter,
|
|
4175
|
+
export { AnthropicLLMAdapter, CohereEmbeddingAdapter, DiskStorageAdapter, GroqLLMAdapter, HybridStorageAdapter, InMemoryStorageAdapter, LanceDBStorageAdapter, MarkdownStorageAdapter, MemStack, MemStackError, MongoDBStorageAdapter, Neo4jStorageAdapter, OllamaLLMAdapter, OpenAIEmbeddingAdapter, OpenAILLMAdapter, PostgresStorageAdapter, QdrantStorageAdapter, RedisStorageAdapter, SQLiteStorageAdapter, WeaviateStorageAdapter, configError, notFound, storageError, validationError };
|
|
1973
4176
|
//# sourceMappingURL=index.js.map
|
|
1974
4177
|
//# sourceMappingURL=index.js.map
|