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