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