@memstack/core 0.6.4 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -17
- package/dist/index.cjs +113 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -15
- package/dist/index.d.ts +28 -15
- package/dist/index.js +114 -34
- package/dist/index.js.map +1 -1
- package/package.json +14 -5
package/README.md
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
# MemStack
|
|
2
2
|
|
|
3
|
+
> Implementation priority is maintained in the [canonical roadmap](docs/ROADMAP.md).
|
|
4
|
+
|
|
3
5
|
> The open-source memory layer for AI agents — store, retrieve, summarize, and prune.
|
|
4
6
|
|
|
5
7
|
[](https://www.npmjs.com/package/@memstack/core)
|
|
8
|
+
[](https://github.com/isiomaC/memstack/actions/workflows/ci.yml)
|
|
6
9
|
[](https://opensource.org/licenses/MIT)
|
|
10
|
+
[](https://gitmcp.io/isiomaC/memstack)
|
|
7
11
|
|
|
8
12
|
```bash
|
|
9
13
|
npm install @memstack/core
|
|
@@ -671,9 +675,16 @@ new OpenAIEmbeddingAdapter({ apiKey: "...", baseURL: "https://api.voyageai.com/v
|
|
|
671
675
|
|
|
672
676
|
### Storage Adapters
|
|
673
677
|
|
|
674
|
-
MemStack
|
|
678
|
+
MemStack contains 18 storage-adapter implementations. Twelve are exported from `@memstack/core`; six remain experimental source implementations. Core has no runtime dependencies, and database clients are injected by callers.
|
|
679
|
+
|
|
680
|
+
**Support levels:**
|
|
675
681
|
|
|
676
|
-
|
|
682
|
+
- **Production-ready** means exported from the public package, covered by unit tests, and supported as part of the public API.
|
|
683
|
+
- **Real-service E2E verified** means the adapter also passes against its actual database implementation in `pnpm test:e2e`.
|
|
684
|
+
- **Mock-tested** means unit coverage uses an injected fake client rather than a live cloud service.
|
|
685
|
+
- **Experimental** means implemented in source but not exported from the published package.
|
|
686
|
+
|
|
687
|
+
### Public package exports
|
|
677
688
|
|
|
678
689
|
**Built-in (zero external deps):**
|
|
679
690
|
| Adapter | Backend | Use case |
|
|
@@ -687,6 +698,7 @@ MemStack ships with **11 production-ready storage adapters (7 experimental)**
|
|
|
687
698
|
| Adapter | Backend | Vector search |
|
|
688
699
|
|---|---|---|
|
|
689
700
|
| `PostgresStorageAdapter` | PostgreSQL + pgvector | HNSW native |
|
|
701
|
+
| `SQLiteStorageAdapter` | SQLite (better-sqlite3) | Cosine in-memory |
|
|
690
702
|
|
|
691
703
|
**Vector databases:**
|
|
692
704
|
| Adapter | Backend |
|
|
@@ -706,13 +718,12 @@ MemStack ships with **11 production-ready storage adapters (7 experimental)**
|
|
|
706
718
|
|---|---|
|
|
707
719
|
| `Neo4jStorageAdapter` | Neo4j |
|
|
708
720
|
|
|
709
|
-
### Experimental (mock-tested
|
|
721
|
+
### Experimental (mock-tested or missing an optional E2E capability)
|
|
710
722
|
|
|
711
|
-
|
|
723
|
+
These implementations are available to source contributors but are not part of the published package API.
|
|
712
724
|
|
|
713
725
|
| Adapter | Backend | Blocker |
|
|
714
726
|
|---|---|---|
|
|
715
|
-
| `SQLiteStorageAdapter` | SQLite (better-sqlite3) | Native binary for Node 24 |
|
|
716
727
|
| `TursoStorageAdapter` | Turso (libsql) | Cloud-only (needs Turso account) |
|
|
717
728
|
| `ChromaStorageAdapter` | ChromaDB | Embedding function dependency |
|
|
718
729
|
| `PineconeStorageAdapter` | Pinecone | Cloud-only (needs API key) |
|
|
@@ -720,7 +731,7 @@ Available via direct source import. Not yet in the barrel export — uncomment i
|
|
|
720
731
|
| `Mem0StorageAdapter` | Mem0 OSS or Cloud | Cloud-only (needs API key) |
|
|
721
732
|
| `ZepStorageAdapter` | Zep Cloud or CE | Cloud-only (needs API key) |
|
|
722
733
|
|
|
723
|
-
|
|
734
|
+
Live cloud compatibility remains unverified for Pinecone, Upstash, Mem0, Zep, and Turso. Chroma's real-client E2E suite is skipped when its optional default embedding function is unavailable. LLM and embedding-provider tests use mocks; live-provider testing is opt-in and is not part of CI.
|
|
724
735
|
|
|
725
736
|
**Quick-start per backend:**
|
|
726
737
|
|
|
@@ -781,7 +792,6 @@ class MyStorage implements StorageProvider {
|
|
|
781
792
|
| Neo4j | Neo4j vector index | No | ✅ Production |
|
|
782
793
|
| Hybrid | Delegates to cache/durable | If durable supports | ✅ Production |
|
|
783
794
|
| SQLite | Cosine in-memory | Yes | ✅ Production |
|
|
784
|
-
| Hybrid | Delegates to cache/durable | If durable supports | ✅ Production |
|
|
785
795
|
|
|
786
796
|
---
|
|
787
797
|
|
|
@@ -797,18 +807,28 @@ const ms = new MemStack({
|
|
|
797
807
|
embedding?: EmbeddingProvider, // Optional — for semantic search
|
|
798
808
|
storage?: StorageProvider, // Optional — defaults to InMemoryStorageAdapter
|
|
799
809
|
defaults?: {
|
|
800
|
-
summarizationThreshold?: number, // Auto-summarize every N
|
|
810
|
+
summarizationThreshold?: number, // Auto-summarize every N process() calls. Default: 100
|
|
801
811
|
embedOnStore?: boolean, // Auto-embed on store(). Default: true
|
|
802
|
-
pruneStrategy?: PruneStrategy, // Auto-prune
|
|
812
|
+
pruneStrategy?: PruneStrategy, // Auto-prune during process() (throttled). Default: disabled
|
|
813
|
+
pruneInterval?: number, // Run auto-prune every N process() calls. Default: 100
|
|
814
|
+
autoImportance?: boolean, // LLM-score importance in process() when not provided. Default: false
|
|
815
|
+
autoTags?: boolean, // LLM-extract tags in process() when not provided. Default: false
|
|
816
|
+
summarizationPrompt?: string, // Custom prompt for the summarizer
|
|
803
817
|
},
|
|
804
818
|
hooks?: {
|
|
805
819
|
onMemoryStored?: (memory: Memory) => void;
|
|
806
820
|
onMemoryPruned?: (ids: string[]) => void;
|
|
807
821
|
onSummaryCreated?: (summary: Memory, deletedCount: number) => void;
|
|
822
|
+
onError?: (error: Error, context: string) => void;
|
|
808
823
|
},
|
|
809
824
|
});
|
|
810
825
|
```
|
|
811
826
|
|
|
827
|
+
> **Auto-behaviors run inside `process()`, not `store()`.** `process()` tracks a
|
|
828
|
+
> per-actor call count: summarization fires every `summarizationThreshold` calls,
|
|
829
|
+
> and pruning fires every `pruneInterval` calls (when `pruneStrategy` is set).
|
|
830
|
+
> `store()` is the low-level write and never triggers these.
|
|
831
|
+
|
|
812
832
|
### Memory Subsystem
|
|
813
833
|
|
|
814
834
|
All methods accessible via `ms.memory.*`:
|
|
@@ -857,6 +877,8 @@ const data = JSON.parse(fs.readFileSync("state.json", "utf-8"));
|
|
|
857
877
|
await ms2.import(data);
|
|
858
878
|
```
|
|
859
879
|
|
|
880
|
+
Each memory's original `createdAt` is preserved on import, so `export` → `import` is a lossless round-trip — safe for backups and cross-backend migration (e.g. disk → Postgres). All storage adapters honor a `createdAt` supplied on `store()`/`storeBatch()`; when omitted, they default to the current time.
|
|
881
|
+
|
|
860
882
|
### Health & Close
|
|
861
883
|
|
|
862
884
|
```typescript
|
|
@@ -874,14 +896,17 @@ await ms.close(); // graceful shutdown
|
|
|
874
896
|
const ms = new MemStack({
|
|
875
897
|
llm: new OpenAILLMAdapter({ apiKey: "..." }),
|
|
876
898
|
|
|
877
|
-
// Defaults control auto-behavior
|
|
899
|
+
// Defaults control auto-behavior (all applied during process())
|
|
878
900
|
defaults: {
|
|
879
|
-
summarizationThreshold: 50, // Summarize every 50
|
|
901
|
+
summarizationThreshold: 50, // Summarize every 50 process() calls (default: 100)
|
|
880
902
|
embedOnStore: false, // Don't auto-embed — saves API costs
|
|
881
|
-
pruneStrategy: { // Auto-clean
|
|
903
|
+
pruneStrategy: { // Auto-clean during process(), throttled by pruneInterval
|
|
882
904
|
type: "byAge",
|
|
883
905
|
maxAge: 90 * 86400000, // 90 days
|
|
884
906
|
},
|
|
907
|
+
pruneInterval: 100, // Run the prune check every 100 process() calls (default: 100)
|
|
908
|
+
autoImportance: true, // Let the LLM score importance when you don't pass one
|
|
909
|
+
autoTags: true, // Let the LLM extract tags when you don't pass any
|
|
885
910
|
},
|
|
886
911
|
|
|
887
912
|
// Hooks for observability
|
|
@@ -889,6 +914,7 @@ const ms = new MemStack({
|
|
|
889
914
|
onMemoryStored: (m) => logger.debug("memory:stored", { id: m.id, actor: m.actorId }),
|
|
890
915
|
onMemoryPruned: (ids) => logger.info("memory:pruned", { count: ids.length }),
|
|
891
916
|
onSummaryCreated: (summary, n) => logger.info("memory:summarized", { count: n }),
|
|
917
|
+
onError: (err, context) => logger.error("memory:error", { context, message: err.message }),
|
|
892
918
|
},
|
|
893
919
|
});
|
|
894
920
|
```
|
|
@@ -948,13 +974,21 @@ git clone https://github.com/isiomaC/memstack.git
|
|
|
948
974
|
cd memstack
|
|
949
975
|
pnpm install
|
|
950
976
|
|
|
951
|
-
pnpm test
|
|
952
|
-
pnpm test:
|
|
953
|
-
pnpm test:
|
|
954
|
-
pnpm
|
|
955
|
-
pnpm
|
|
977
|
+
pnpm test # 407 core tests, no external services needed
|
|
978
|
+
pnpm test:packages # 78 package tests after dependency-ordered builds
|
|
979
|
+
pnpm test:e2e # 80 pass, 1 optional Chroma skip (requires Docker services)
|
|
980
|
+
pnpm test:e2e:run # Start services, run E2E once, preserve failure logs, clean up
|
|
981
|
+
pnpm smoke:artifacts # Built core, CLI, MCP, and server black-box checks
|
|
982
|
+
pnpm smoke:packages # Pack and install publishable tarballs in a clean project
|
|
983
|
+
pnpm smoke:docker # Build and exercise the server image
|
|
984
|
+
pnpm verify # Complete local verification pipeline
|
|
985
|
+
pnpm test:watch # Watch core tests
|
|
986
|
+
pnpm build:all # Build core and all workspace packages
|
|
987
|
+
pnpm check:all # Type-check core and all workspace packages
|
|
956
988
|
```
|
|
957
989
|
|
|
990
|
+
CI exposes a stable `verification` job. Configure that job as a required status check in GitHub branch protection for `main`.
|
|
991
|
+
|
|
958
992
|
### Debugging
|
|
959
993
|
|
|
960
994
|
Use hooks for observability — MemStack has no built-in logging:
|
package/dist/index.cjs
CHANGED
|
@@ -600,7 +600,7 @@ var MemoryStore = class {
|
|
|
600
600
|
}
|
|
601
601
|
async prune(strategy) {
|
|
602
602
|
await this._ensureInit();
|
|
603
|
-
const allMemories = await this.storage.retrieve({ limit: this.limits.pruneScan });
|
|
603
|
+
const allMemories = await this.storage.retrieve({ actorId: strategy.actorId, limit: this.limits.pruneScan });
|
|
604
604
|
const kept = this.pruner.execute(allMemories, strategy);
|
|
605
605
|
const keptIds = new Set(kept.map((k) => k.id));
|
|
606
606
|
const prunedIds = allMemories.filter((m) => !keptIds.has(m.id)).map((m) => m.id);
|
|
@@ -612,7 +612,7 @@ var MemoryStore = class {
|
|
|
612
612
|
}
|
|
613
613
|
async dryRunPrune(strategy) {
|
|
614
614
|
await this._ensureInit();
|
|
615
|
-
const allMemories = await this.storage.retrieve({ limit: this.limits.pruneScan });
|
|
615
|
+
const allMemories = await this.storage.retrieve({ actorId: strategy.actorId, limit: this.limits.pruneScan });
|
|
616
616
|
const kept = this.pruner.execute(allMemories, strategy);
|
|
617
617
|
const keptIds = new Set(kept.map((k) => k.id));
|
|
618
618
|
const wouldPrune = allMemories.filter((m) => !keptIds.has(m.id)).map((m) => m.id);
|
|
@@ -746,7 +746,7 @@ var InMemoryStorageAdapter = class {
|
|
|
746
746
|
sourceId: input.sourceId,
|
|
747
747
|
metadata: input.metadata ?? {},
|
|
748
748
|
expiresAt: input.expiresAt,
|
|
749
|
-
createdAt: now,
|
|
749
|
+
createdAt: input.createdAt ?? now,
|
|
750
750
|
_touchedAt: now
|
|
751
751
|
};
|
|
752
752
|
this.memories.set(memory.id, memory);
|
|
@@ -796,8 +796,11 @@ var InMemoryStorageAdapter = class {
|
|
|
796
796
|
results = results.filter((m) => m.tags?.some((t) => query.tags.includes(t)));
|
|
797
797
|
}
|
|
798
798
|
if (query.query) {
|
|
799
|
-
const
|
|
800
|
-
results = results.filter((m) =>
|
|
799
|
+
const terms = query.query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
800
|
+
results = results.filter((m) => {
|
|
801
|
+
const content = m.content.toLowerCase();
|
|
802
|
+
return terms.some((t) => content.includes(t));
|
|
803
|
+
});
|
|
801
804
|
}
|
|
802
805
|
if (query.createdAfter) {
|
|
803
806
|
results = results.filter((m) => m.createdAt >= query.createdAfter);
|
|
@@ -983,7 +986,19 @@ var MemStack = class {
|
|
|
983
986
|
if (snapshot.version !== 1) {
|
|
984
987
|
throw validationError(`Unsupported snapshot version: ${snapshot.version}`);
|
|
985
988
|
}
|
|
986
|
-
|
|
989
|
+
const memories = snapshot.memories.map((m, i) => {
|
|
990
|
+
const createdAt = typeof m.createdAt === "string" ? new Date(m.createdAt) : m.createdAt;
|
|
991
|
+
const expiresAt = typeof m.expiresAt === "string" ? new Date(m.expiresAt) : m.expiresAt;
|
|
992
|
+
const label = m.id ? `id: ${m.id}` : `index ${i}`;
|
|
993
|
+
if (createdAt && isNaN(createdAt.getTime())) {
|
|
994
|
+
throw validationError(`Invalid createdAt for memory (${label}): ${String(m.createdAt)}`);
|
|
995
|
+
}
|
|
996
|
+
if (expiresAt && isNaN(expiresAt.getTime())) {
|
|
997
|
+
throw validationError(`Invalid expiresAt for memory (${label}): ${String(m.expiresAt)}`);
|
|
998
|
+
}
|
|
999
|
+
return { ...m, createdAt, expiresAt };
|
|
1000
|
+
});
|
|
1001
|
+
await this.memory.storeBatch(memories);
|
|
987
1002
|
}
|
|
988
1003
|
async health() {
|
|
989
1004
|
const status = { storage: false, llm: false, embedding: false };
|
|
@@ -1070,7 +1085,7 @@ var DiskStorageAdapter = class {
|
|
|
1070
1085
|
sourceId: input.sourceId,
|
|
1071
1086
|
metadata: input.metadata ?? {},
|
|
1072
1087
|
expiresAt: input.expiresAt,
|
|
1073
|
-
createdAt: now,
|
|
1088
|
+
createdAt: input.createdAt ?? now,
|
|
1074
1089
|
_touchedAt: now.toISOString()
|
|
1075
1090
|
};
|
|
1076
1091
|
await this._withWriteLock(input.actorId, async () => {
|
|
@@ -1103,7 +1118,7 @@ var DiskStorageAdapter = class {
|
|
|
1103
1118
|
sourceId: input.sourceId,
|
|
1104
1119
|
metadata: input.metadata ?? {},
|
|
1105
1120
|
expiresAt: input.expiresAt,
|
|
1106
|
-
createdAt: now,
|
|
1121
|
+
createdAt: input.createdAt ?? now,
|
|
1107
1122
|
_touchedAt: now.toISOString()
|
|
1108
1123
|
};
|
|
1109
1124
|
all.push(memory);
|
|
@@ -1210,8 +1225,11 @@ var DiskStorageAdapter = class {
|
|
|
1210
1225
|
records = records.filter((r) => r.tags?.some((t) => query.tags.includes(t)));
|
|
1211
1226
|
}
|
|
1212
1227
|
if (query.query) {
|
|
1213
|
-
const
|
|
1214
|
-
records = records.filter((r) =>
|
|
1228
|
+
const terms = query.query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
1229
|
+
records = records.filter((r) => {
|
|
1230
|
+
const content = r.content.toLowerCase();
|
|
1231
|
+
return terms.some((t) => content.includes(t));
|
|
1232
|
+
});
|
|
1215
1233
|
}
|
|
1216
1234
|
results.push(...records);
|
|
1217
1235
|
}
|
|
@@ -1302,9 +1320,14 @@ var DiskStorageAdapter = class {
|
|
|
1302
1320
|
}
|
|
1303
1321
|
}
|
|
1304
1322
|
async _writeFile(actorId, records) {
|
|
1323
|
+
const filePath = this._filePath(actorId);
|
|
1324
|
+
const tmpPath = `${filePath}.${process.pid}.${Date.now().toString(36)}.tmp`;
|
|
1305
1325
|
try {
|
|
1306
|
-
await promises.writeFile(
|
|
1326
|
+
await promises.writeFile(tmpPath, JSON.stringify(records, null, 2), "utf-8");
|
|
1327
|
+
await promises.rename(tmpPath, filePath);
|
|
1307
1328
|
} catch (err) {
|
|
1329
|
+
await promises.unlink(tmpPath).catch(() => {
|
|
1330
|
+
});
|
|
1308
1331
|
throw storageError(`Failed to write memories for actor ${actorId}: ${err.message}`);
|
|
1309
1332
|
}
|
|
1310
1333
|
}
|
|
@@ -1316,15 +1339,57 @@ var DiskStorageAdapter = class {
|
|
|
1316
1339
|
});
|
|
1317
1340
|
this._writeLocks.set(actorId, next);
|
|
1318
1341
|
await prev;
|
|
1342
|
+
const releaseFileLock = await this._acquireFileLock(actorId);
|
|
1319
1343
|
try {
|
|
1320
1344
|
await fn();
|
|
1321
1345
|
} finally {
|
|
1346
|
+
await releaseFileLock();
|
|
1322
1347
|
resolve();
|
|
1323
1348
|
if (this._writeLocks.get(actorId) === next) {
|
|
1324
1349
|
this._writeLocks.delete(actorId);
|
|
1325
1350
|
}
|
|
1326
1351
|
}
|
|
1327
1352
|
}
|
|
1353
|
+
/**
|
|
1354
|
+
* Acquires an exclusive cross-process lock for `actorId` via `open(path, "wx")`
|
|
1355
|
+
* (atomic create-if-absent). Retries with jittered backoff while the lock is
|
|
1356
|
+
* held elsewhere, and steals locks left behind by a crashed process once
|
|
1357
|
+
* they're older than `staleMs`.
|
|
1358
|
+
*/
|
|
1359
|
+
async _acquireFileLock(actorId) {
|
|
1360
|
+
const lockPath = `${this._filePath(actorId)}.lock`;
|
|
1361
|
+
const staleMs = 1e4;
|
|
1362
|
+
const timeoutMs = 3e4;
|
|
1363
|
+
const start = Date.now();
|
|
1364
|
+
while (true) {
|
|
1365
|
+
try {
|
|
1366
|
+
const handle = await promises.open(lockPath, "wx");
|
|
1367
|
+
await handle.close();
|
|
1368
|
+
return async () => {
|
|
1369
|
+
await promises.unlink(lockPath).catch(() => {
|
|
1370
|
+
});
|
|
1371
|
+
};
|
|
1372
|
+
} catch (err) {
|
|
1373
|
+
if (err.code !== "EEXIST") {
|
|
1374
|
+
throw storageError(`Failed to acquire lock for actor ${actorId}: ${err.message}`);
|
|
1375
|
+
}
|
|
1376
|
+
try {
|
|
1377
|
+
const st = await promises.stat(lockPath);
|
|
1378
|
+
if (Date.now() - st.mtimeMs > staleMs) {
|
|
1379
|
+
await promises.unlink(lockPath).catch(() => {
|
|
1380
|
+
});
|
|
1381
|
+
continue;
|
|
1382
|
+
}
|
|
1383
|
+
} catch {
|
|
1384
|
+
continue;
|
|
1385
|
+
}
|
|
1386
|
+
if (Date.now() - start > timeoutMs) {
|
|
1387
|
+
throw storageError(`Timed out waiting for disk storage lock: ${lockPath}`);
|
|
1388
|
+
}
|
|
1389
|
+
await new Promise((r) => setTimeout(r, 15 + Math.random() * 35));
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1328
1393
|
async _listActors() {
|
|
1329
1394
|
try {
|
|
1330
1395
|
const entries = await promises.readdir(this.dir);
|
|
@@ -1382,7 +1447,7 @@ var MarkdownStorageAdapter = class {
|
|
|
1382
1447
|
sourceId: input.sourceId,
|
|
1383
1448
|
metadata: input.metadata ?? {},
|
|
1384
1449
|
expiresAt: input.expiresAt,
|
|
1385
|
-
createdAt: now
|
|
1450
|
+
createdAt: input.createdAt ?? now
|
|
1386
1451
|
};
|
|
1387
1452
|
const filePath = this._filePath(input.actorId);
|
|
1388
1453
|
const block = this._formatBlock(memory);
|
|
@@ -1459,8 +1524,11 @@ var MarkdownStorageAdapter = class {
|
|
|
1459
1524
|
results = results.filter((m) => m.tags?.some((t) => query.tags.includes(t)));
|
|
1460
1525
|
}
|
|
1461
1526
|
if (query.query) {
|
|
1462
|
-
const
|
|
1463
|
-
results = results.filter((m) =>
|
|
1527
|
+
const terms = query.query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
1528
|
+
results = results.filter((m) => {
|
|
1529
|
+
const content = m.content.toLowerCase();
|
|
1530
|
+
return terms.some((t) => content.includes(t));
|
|
1531
|
+
});
|
|
1464
1532
|
}
|
|
1465
1533
|
switch (query.strategy) {
|
|
1466
1534
|
case "recent":
|
|
@@ -2011,7 +2079,7 @@ var PostgresStorageAdapter = class {
|
|
|
2011
2079
|
sourceId: input.sourceId,
|
|
2012
2080
|
metadata: input.metadata ?? {},
|
|
2013
2081
|
expiresAt: input.expiresAt,
|
|
2014
|
-
createdAt: new Date(now)
|
|
2082
|
+
createdAt: input.createdAt ?? new Date(now)
|
|
2015
2083
|
};
|
|
2016
2084
|
}
|
|
2017
2085
|
async storeBatch(inputs) {
|
|
@@ -2214,7 +2282,7 @@ var RedisStorageAdapter = class {
|
|
|
2214
2282
|
sourceId: input.sourceId,
|
|
2215
2283
|
metadata: input.metadata ?? {},
|
|
2216
2284
|
expiresAt: input.expiresAt,
|
|
2217
|
-
createdAt: now,
|
|
2285
|
+
createdAt: input.createdAt ?? now,
|
|
2218
2286
|
_touchedAt: now.toISOString()
|
|
2219
2287
|
};
|
|
2220
2288
|
await this.redis.set(this._memKey(memory.id), JSON.stringify(memory));
|
|
@@ -2238,7 +2306,7 @@ var RedisStorageAdapter = class {
|
|
|
2238
2306
|
sourceId: input.sourceId,
|
|
2239
2307
|
metadata: input.metadata ?? {},
|
|
2240
2308
|
expiresAt: input.expiresAt,
|
|
2241
|
-
createdAt: now,
|
|
2309
|
+
createdAt: input.createdAt ?? now,
|
|
2242
2310
|
_touchedAt: now.toISOString()
|
|
2243
2311
|
};
|
|
2244
2312
|
await this.redis.set(this._memKey(memory.id), JSON.stringify(memory));
|
|
@@ -2434,7 +2502,7 @@ var QdrantStorageAdapter = class {
|
|
|
2434
2502
|
sourceId: input.sourceId,
|
|
2435
2503
|
metadata: input.metadata ?? {},
|
|
2436
2504
|
expiresAt: input.expiresAt,
|
|
2437
|
-
createdAt: now
|
|
2505
|
+
createdAt: input.createdAt ?? now
|
|
2438
2506
|
};
|
|
2439
2507
|
const qdrantId = crypto.randomUUID();
|
|
2440
2508
|
await this.client.upsert(this.collectionName, {
|
|
@@ -2466,7 +2534,7 @@ var QdrantStorageAdapter = class {
|
|
|
2466
2534
|
sourceId: input.sourceId,
|
|
2467
2535
|
metadata: input.metadata ?? {},
|
|
2468
2536
|
expiresAt: input.expiresAt,
|
|
2469
|
-
createdAt: now
|
|
2537
|
+
createdAt: input.createdAt ?? now
|
|
2470
2538
|
};
|
|
2471
2539
|
results.push(memory);
|
|
2472
2540
|
const qid = crypto.randomUUID();
|
|
@@ -2644,10 +2712,13 @@ var Neo4jStorageAdapter = class {
|
|
|
2644
2712
|
sourceId: input.sourceId ?? null,
|
|
2645
2713
|
metadata: JSON.stringify(input.metadata ?? {}),
|
|
2646
2714
|
expiresAt: input.expiresAt?.toISOString() ?? null,
|
|
2647
|
-
createdAt: now,
|
|
2715
|
+
createdAt: input.createdAt?.toISOString() ?? now,
|
|
2648
2716
|
embedding: input.embedding ?? null
|
|
2649
2717
|
};
|
|
2650
|
-
const
|
|
2718
|
+
const updateProps = { ...props };
|
|
2719
|
+
if (!input.createdAt) {
|
|
2720
|
+
delete updateProps.createdAt;
|
|
2721
|
+
}
|
|
2651
2722
|
const session = this.driver.session({ database: this.database });
|
|
2652
2723
|
try {
|
|
2653
2724
|
await session.run(
|
|
@@ -2669,7 +2740,7 @@ var Neo4jStorageAdapter = class {
|
|
|
2669
2740
|
sourceId: input.sourceId,
|
|
2670
2741
|
metadata: input.metadata ?? {},
|
|
2671
2742
|
expiresAt: input.expiresAt,
|
|
2672
|
-
createdAt: new Date(now)
|
|
2743
|
+
createdAt: input.createdAt ?? new Date(now)
|
|
2673
2744
|
};
|
|
2674
2745
|
}
|
|
2675
2746
|
async storeBatch(inputs) {
|
|
@@ -2906,7 +2977,7 @@ var WeaviateStorageAdapter = class {
|
|
|
2906
2977
|
sourceId: input.sourceId,
|
|
2907
2978
|
metadata: input.metadata ?? {},
|
|
2908
2979
|
expiresAt: input.expiresAt,
|
|
2909
|
-
createdAt: now
|
|
2980
|
+
createdAt: input.createdAt ?? now
|
|
2910
2981
|
};
|
|
2911
2982
|
const wvId = await this.collection().data.insert({
|
|
2912
2983
|
id: crypto.randomUUID(),
|
|
@@ -3139,7 +3210,7 @@ var LanceDBStorageAdapter = class {
|
|
|
3139
3210
|
sourceId: input.sourceId,
|
|
3140
3211
|
metadata: input.metadata ?? {},
|
|
3141
3212
|
expiresAt: input.expiresAt,
|
|
3142
|
-
createdAt: now
|
|
3213
|
+
createdAt: input.createdAt ?? now
|
|
3143
3214
|
};
|
|
3144
3215
|
}
|
|
3145
3216
|
async storeBatch(inputs) {
|
|
@@ -3150,7 +3221,7 @@ var LanceDBStorageAdapter = class {
|
|
|
3150
3221
|
return results;
|
|
3151
3222
|
}
|
|
3152
3223
|
async get(id) {
|
|
3153
|
-
const results = await this.table.search(this._zeroVector).limit(1).where(`id = "${id}"`).execute();
|
|
3224
|
+
const results = await this.table.search(this._zeroVector).limit(1).where(`id = "${id}"`).prefilter(true).execute();
|
|
3154
3225
|
if (results.length === 0) return null;
|
|
3155
3226
|
const row = results[0];
|
|
3156
3227
|
if (row.expires_at) {
|
|
@@ -3307,7 +3378,7 @@ var MongoDBStorageAdapter = class {
|
|
|
3307
3378
|
sourceId: input.sourceId,
|
|
3308
3379
|
metadata: input.metadata ?? {},
|
|
3309
3380
|
expiresAt: input.expiresAt,
|
|
3310
|
-
createdAt: now
|
|
3381
|
+
createdAt: input.createdAt ?? now
|
|
3311
3382
|
};
|
|
3312
3383
|
await this.collection.insertOne(this.toDoc(memory));
|
|
3313
3384
|
return memory;
|
|
@@ -3536,7 +3607,7 @@ var SQLiteStorageAdapter = class {
|
|
|
3536
3607
|
sourceId: input.sourceId,
|
|
3537
3608
|
metadata: input.metadata ?? {},
|
|
3538
3609
|
expiresAt: input.expiresAt,
|
|
3539
|
-
createdAt: new Date(now)
|
|
3610
|
+
createdAt: input.createdAt ?? new Date(now)
|
|
3540
3611
|
};
|
|
3541
3612
|
}
|
|
3542
3613
|
async storeBatch(inputs) {
|
|
@@ -3809,11 +3880,12 @@ var OpenAILLMAdapter = class {
|
|
|
3809
3880
|
try {
|
|
3810
3881
|
const parsed = JSON.parse(data);
|
|
3811
3882
|
const content = parsed.choices?.[0]?.delta?.content ?? "";
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3883
|
+
const usage = parsed.usage;
|
|
3884
|
+
if (usage) {
|
|
3885
|
+
promptTokens = usage.prompt_tokens;
|
|
3886
|
+
completionTokens = usage.completion_tokens;
|
|
3815
3887
|
}
|
|
3816
|
-
if (content) {
|
|
3888
|
+
if (content || usage) {
|
|
3817
3889
|
yield {
|
|
3818
3890
|
text: content,
|
|
3819
3891
|
tokens: { prompt: promptTokens, completion: completionTokens, total: promptTokens + completionTokens }
|
|
@@ -3869,7 +3941,15 @@ var AnthropicLLMAdapter = class {
|
|
|
3869
3941
|
};
|
|
3870
3942
|
} catch (err) {
|
|
3871
3943
|
if (err instanceof MemStackError) throw err;
|
|
3872
|
-
|
|
3944
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3945
|
+
const code = err?.code;
|
|
3946
|
+
const isMissingSdk = msg.includes("@anthropic-ai/sdk") && (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND" || /cannot find (package|module)/i.test(msg) || /failed to (load|resolve)/i.test(msg));
|
|
3947
|
+
if (isMissingSdk) {
|
|
3948
|
+
throw new MemStackError("LLM_ERROR", "Anthropic adapter requires @anthropic-ai/sdk. Install it: npm install @anthropic-ai/sdk", {
|
|
3949
|
+
retryable: false
|
|
3950
|
+
});
|
|
3951
|
+
}
|
|
3952
|
+
throw new MemStackError("LLM_ERROR", `Anthropic request failed: ${msg}`, {
|
|
3873
3953
|
retryable: true
|
|
3874
3954
|
});
|
|
3875
3955
|
}
|
|
@@ -4158,7 +4238,7 @@ var GroqLLMAdapter = class {
|
|
|
4158
4238
|
promptTokens = usage.prompt_tokens;
|
|
4159
4239
|
completionTokens = usage.completion_tokens;
|
|
4160
4240
|
}
|
|
4161
|
-
if (content) {
|
|
4241
|
+
if (content || usage) {
|
|
4162
4242
|
yield {
|
|
4163
4243
|
text: content,
|
|
4164
4244
|
tokens: { prompt: promptTokens, completion: completionTokens, total: promptTokens + completionTokens }
|