@agentskit/memory 0.5.4 → 0.6.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 +34 -6
- package/dist/index.cjs +516 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +239 -2
- package/dist/index.d.ts +239 -2
- package/dist/index.js +507 -1
- package/dist/index.js.map +1 -1
- package/package.json +14 -3
package/README.md
CHANGED
|
@@ -4,8 +4,8 @@ Persist conversations and add vector search to your agents — swap backends wit
|
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@agentskit/memory)
|
|
6
6
|
[](https://www.npmjs.com/package/@agentskit/memory)
|
|
7
|
-
[](https://bundlejs.com/?q=@agentskit/memory)
|
|
8
|
+
[](../../LICENSE)
|
|
9
9
|
[](../../docs/STABILITY.md)
|
|
10
10
|
[](https://github.com/AgentsKit-io/agentskit)
|
|
11
11
|
|
|
@@ -49,10 +49,38 @@ Use a **vector** backend with [`@agentskit/rag`](https://www.npmjs.com/package/@
|
|
|
49
49
|
|
|
50
50
|
## Features
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
- `
|
|
55
|
-
-
|
|
52
|
+
### Chat memory (3)
|
|
53
|
+
|
|
54
|
+
- `fileChatMemory({ path })` — JSON on disk; zero infra.
|
|
55
|
+
- `sqliteChatMemory({ path })` — WAL-mode SQLite; indexed by session.
|
|
56
|
+
- `redisChatMemory({ client, keyPrefix })` — distributed, serverless-friendly.
|
|
57
|
+
|
|
58
|
+
All on top of `createInMemoryMemory` / `createLocalStorageMemory` from
|
|
59
|
+
`@agentskit/core`.
|
|
60
|
+
|
|
61
|
+
### Vector memory (7)
|
|
62
|
+
|
|
63
|
+
- `fileVectorMemory` — pure-JS, file-persisted (good to ~10k vectors).
|
|
64
|
+
- `redisVectorMemory` — Redis Stack / Redis 8+ HNSW.
|
|
65
|
+
- `pgvector` — BYO SQL runner (`postgres.js`, `pg`, Drizzle, Prisma, Neon).
|
|
66
|
+
- `pinecone` — managed; namespaces + metadata filters.
|
|
67
|
+
- `qdrant` — self-hosted or cloud via HTTP.
|
|
68
|
+
- `chroma` — HTTP collection client.
|
|
69
|
+
- `upstashVector` — serverless HTTP.
|
|
70
|
+
|
|
71
|
+
Same 3-method `VectorStore` contract — swap without touching agent code.
|
|
72
|
+
|
|
73
|
+
### Higher-order wrappers (6)
|
|
74
|
+
|
|
75
|
+
- `createHierarchicalMemory` — MemGPT-style tiers: working / recall / archival. [Recipe](https://www.agentskit.io/docs/recipes/hierarchical-memory).
|
|
76
|
+
- `createVirtualizedMemory` — hot window + cold retriever for long sessions. [Recipe](https://www.agentskit.io/docs/recipes/virtualized-memory).
|
|
77
|
+
- `createAutoSummarizingMemory` *(via `@agentskit/core/auto-summarize`)* — fold oldest turns into a running summary. [Recipe](https://www.agentskit.io/docs/recipes/auto-summarize).
|
|
78
|
+
- `createEncryptedMemory` — AES-GCM-256 envelope over any `ChatMemory`; keys never leave the caller. [Recipe](https://www.agentskit.io/docs/recipes/encrypted-memory).
|
|
79
|
+
- `createInMemoryGraph` — knowledge graph (nodes + edges + BFS). [Recipe](https://www.agentskit.io/docs/recipes/graph-memory).
|
|
80
|
+
- `createInMemoryPersonalization` + `renderProfileContext` — per-user trait profile. [Recipe](https://www.agentskit.io/docs/recipes/personalization).
|
|
81
|
+
|
|
82
|
+
Memory contract v1 (ADR 0003) — substitutable across `runtime`,
|
|
83
|
+
`useChat`, and every framework binding.
|
|
56
84
|
|
|
57
85
|
## Ecosystem
|
|
58
86
|
|
package/dist/index.cjs
CHANGED
|
@@ -366,10 +366,526 @@ function fileVectorMemory(config) {
|
|
|
366
366
|
};
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
+
// src/personalization.ts
|
|
370
|
+
function createInMemoryPersonalization() {
|
|
371
|
+
const profiles = /* @__PURE__ */ new Map();
|
|
372
|
+
return {
|
|
373
|
+
async get(subjectId) {
|
|
374
|
+
const hit = profiles.get(subjectId);
|
|
375
|
+
return hit ? { ...hit, traits: { ...hit.traits } } : null;
|
|
376
|
+
},
|
|
377
|
+
async set(profile) {
|
|
378
|
+
profiles.set(profile.subjectId, {
|
|
379
|
+
...profile,
|
|
380
|
+
traits: { ...profile.traits },
|
|
381
|
+
updatedAt: profile.updatedAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
382
|
+
});
|
|
383
|
+
},
|
|
384
|
+
async merge(subjectId, traits) {
|
|
385
|
+
const existing = profiles.get(subjectId);
|
|
386
|
+
const next = {
|
|
387
|
+
subjectId,
|
|
388
|
+
traits: { ...existing?.traits ?? {}, ...traits },
|
|
389
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
390
|
+
};
|
|
391
|
+
profiles.set(subjectId, next);
|
|
392
|
+
return { ...next, traits: { ...next.traits } };
|
|
393
|
+
},
|
|
394
|
+
async delete(subjectId) {
|
|
395
|
+
profiles.delete(subjectId);
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
function renderProfileContext(profile) {
|
|
400
|
+
if (!profile || Object.keys(profile.traits).length === 0) return "";
|
|
401
|
+
const lines = Object.entries(profile.traits).filter(([, value]) => value !== void 0 && value !== null && value !== "").map(([key, value]) => `- ${key}: ${typeof value === "string" ? value : JSON.stringify(value)}`);
|
|
402
|
+
if (lines.length === 0) return "";
|
|
403
|
+
return `## User profile
|
|
404
|
+
${lines.join("\n")}`;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// src/graph.ts
|
|
408
|
+
function createInMemoryGraph() {
|
|
409
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
410
|
+
const edges = /* @__PURE__ */ new Map();
|
|
411
|
+
const matchesNode = (node, query) => {
|
|
412
|
+
if (!query) return true;
|
|
413
|
+
if (query.kind && node.kind !== query.kind) return false;
|
|
414
|
+
return true;
|
|
415
|
+
};
|
|
416
|
+
const matchesEdge = (edge, query) => {
|
|
417
|
+
if (!query) return true;
|
|
418
|
+
if (query.label && edge.label !== query.label) return false;
|
|
419
|
+
if (query.from && edge.from !== query.from) return false;
|
|
420
|
+
if (query.to && edge.to !== query.to) return false;
|
|
421
|
+
return true;
|
|
422
|
+
};
|
|
423
|
+
return {
|
|
424
|
+
async upsertNode(node) {
|
|
425
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
426
|
+
const existing = nodes.get(node.id);
|
|
427
|
+
const merged = {
|
|
428
|
+
...existing ?? {},
|
|
429
|
+
...node,
|
|
430
|
+
createdAt: existing?.createdAt ?? now,
|
|
431
|
+
updatedAt: now
|
|
432
|
+
};
|
|
433
|
+
nodes.set(node.id, merged);
|
|
434
|
+
return merged;
|
|
435
|
+
},
|
|
436
|
+
async upsertEdge(edge) {
|
|
437
|
+
edges.set(edge.id, edge);
|
|
438
|
+
return edge;
|
|
439
|
+
},
|
|
440
|
+
async getNode(id) {
|
|
441
|
+
const hit = nodes.get(id);
|
|
442
|
+
return hit ? { ...hit } : null;
|
|
443
|
+
},
|
|
444
|
+
async findNodes(query) {
|
|
445
|
+
return Array.from(nodes.values()).filter((n) => matchesNode(n, query)).map((n) => ({ ...n }));
|
|
446
|
+
},
|
|
447
|
+
async findEdges(query) {
|
|
448
|
+
return Array.from(edges.values()).filter((e) => matchesEdge(e, query)).map((e) => ({ ...e }));
|
|
449
|
+
},
|
|
450
|
+
async neighbors(id, options = {}) {
|
|
451
|
+
const depth = Math.max(1, options.depth ?? 1);
|
|
452
|
+
const visited = /* @__PURE__ */ new Set([id]);
|
|
453
|
+
let frontier = /* @__PURE__ */ new Set([id]);
|
|
454
|
+
for (let i = 0; i < depth; i++) {
|
|
455
|
+
const next = /* @__PURE__ */ new Set();
|
|
456
|
+
for (const edge of edges.values()) {
|
|
457
|
+
if (options.label && edge.label !== options.label) continue;
|
|
458
|
+
if (frontier.has(edge.from) && !visited.has(edge.to)) next.add(edge.to);
|
|
459
|
+
if (frontier.has(edge.to) && !visited.has(edge.from)) next.add(edge.from);
|
|
460
|
+
}
|
|
461
|
+
for (const n of next) visited.add(n);
|
|
462
|
+
frontier = next;
|
|
463
|
+
if (next.size === 0) break;
|
|
464
|
+
}
|
|
465
|
+
visited.delete(id);
|
|
466
|
+
return Array.from(visited, (nid) => nodes.get(nid)).filter((n) => Boolean(n)).map((n) => ({ ...n }));
|
|
467
|
+
},
|
|
468
|
+
async deleteNode(id) {
|
|
469
|
+
nodes.delete(id);
|
|
470
|
+
for (const [eid, edge] of edges) {
|
|
471
|
+
if (edge.from === id || edge.to === id) edges.delete(eid);
|
|
472
|
+
}
|
|
473
|
+
},
|
|
474
|
+
async deleteEdge(id) {
|
|
475
|
+
edges.delete(id);
|
|
476
|
+
},
|
|
477
|
+
async clear() {
|
|
478
|
+
nodes.clear();
|
|
479
|
+
edges.clear();
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// src/vector/pgvector.ts
|
|
485
|
+
function formatVector(embedding) {
|
|
486
|
+
return `[${embedding.join(",")}]`;
|
|
487
|
+
}
|
|
488
|
+
function pgvector(config) {
|
|
489
|
+
const table = config.table ?? "agentskit_vectors";
|
|
490
|
+
const defaultTopK = Math.max(1, config.topK ?? 10);
|
|
491
|
+
return {
|
|
492
|
+
async store(docs) {
|
|
493
|
+
if (docs.length === 0) return;
|
|
494
|
+
const values = [];
|
|
495
|
+
const placeholders = docs.map((doc, i) => {
|
|
496
|
+
const base = i * 4;
|
|
497
|
+
values.push(doc.id, doc.content, formatVector(doc.embedding), JSON.stringify(doc.metadata ?? {}));
|
|
498
|
+
return `($${base + 1}, $${base + 2}, $${base + 3}::vector, $${base + 4}::jsonb)`;
|
|
499
|
+
}).join(", ");
|
|
500
|
+
await config.runner.query(
|
|
501
|
+
`INSERT INTO ${table} (id, content, embedding, metadata) VALUES ${placeholders}
|
|
502
|
+
ON CONFLICT (id) DO UPDATE SET content = EXCLUDED.content, embedding = EXCLUDED.embedding, metadata = EXCLUDED.metadata`,
|
|
503
|
+
values
|
|
504
|
+
);
|
|
505
|
+
},
|
|
506
|
+
async search(embedding, options = {}) {
|
|
507
|
+
const topK = options.topK ?? defaultTopK;
|
|
508
|
+
const threshold = options.threshold ?? 0;
|
|
509
|
+
const { rows } = await config.runner.query(
|
|
510
|
+
`SELECT id, content, metadata, (embedding <=> $1::vector) AS distance
|
|
511
|
+
FROM ${table}
|
|
512
|
+
ORDER BY embedding <=> $1::vector
|
|
513
|
+
LIMIT $2`,
|
|
514
|
+
[formatVector(embedding), topK]
|
|
515
|
+
);
|
|
516
|
+
return rows.map((r) => ({
|
|
517
|
+
id: r.id,
|
|
518
|
+
content: r.content,
|
|
519
|
+
metadata: r.metadata ?? void 0,
|
|
520
|
+
score: 1 - r.distance
|
|
521
|
+
})).filter((r) => (r.score ?? 0) >= threshold);
|
|
522
|
+
},
|
|
523
|
+
async delete(ids) {
|
|
524
|
+
if (ids.length === 0) return;
|
|
525
|
+
const placeholders = ids.map((_, i) => `$${i + 1}`).join(",");
|
|
526
|
+
await config.runner.query(`DELETE FROM ${table} WHERE id IN (${placeholders})`, ids);
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// src/vector/pinecone.ts
|
|
532
|
+
async function call(config, path, body) {
|
|
533
|
+
const fetchImpl = config.fetch ?? globalThis.fetch;
|
|
534
|
+
const response = await fetchImpl(`${config.indexUrl}${path}`, {
|
|
535
|
+
method: "POST",
|
|
536
|
+
headers: {
|
|
537
|
+
"content-type": "application/json",
|
|
538
|
+
"api-key": config.apiKey
|
|
539
|
+
},
|
|
540
|
+
body: JSON.stringify(body)
|
|
541
|
+
});
|
|
542
|
+
const text = await response.text();
|
|
543
|
+
if (!response.ok) throw new Error(`pinecone ${response.status}: ${text.slice(0, 200)}`);
|
|
544
|
+
return text.length > 0 ? JSON.parse(text) : {};
|
|
545
|
+
}
|
|
546
|
+
function pinecone(config) {
|
|
547
|
+
const defaultTopK = Math.max(1, config.topK ?? 10);
|
|
548
|
+
const namespace = config.namespace ?? "";
|
|
549
|
+
return {
|
|
550
|
+
async store(docs) {
|
|
551
|
+
if (docs.length === 0) return;
|
|
552
|
+
await call(config, "/vectors/upsert", {
|
|
553
|
+
namespace,
|
|
554
|
+
vectors: docs.map((d) => ({
|
|
555
|
+
id: d.id,
|
|
556
|
+
values: d.embedding,
|
|
557
|
+
metadata: { content: d.content, ...d.metadata ?? {} }
|
|
558
|
+
}))
|
|
559
|
+
});
|
|
560
|
+
},
|
|
561
|
+
async search(embedding, options = {}) {
|
|
562
|
+
const topK = options.topK ?? defaultTopK;
|
|
563
|
+
const threshold = options.threshold ?? 0;
|
|
564
|
+
const result = await call(config, "/query", {
|
|
565
|
+
namespace,
|
|
566
|
+
topK,
|
|
567
|
+
vector: embedding,
|
|
568
|
+
includeMetadata: true
|
|
569
|
+
});
|
|
570
|
+
return (result.matches ?? []).filter((m) => m.score >= threshold).map((m) => ({
|
|
571
|
+
id: m.id,
|
|
572
|
+
content: String((m.metadata ?? {}).content ?? ""),
|
|
573
|
+
metadata: m.metadata,
|
|
574
|
+
score: m.score
|
|
575
|
+
}));
|
|
576
|
+
},
|
|
577
|
+
async delete(ids) {
|
|
578
|
+
if (ids.length === 0) return;
|
|
579
|
+
await call(config, "/vectors/delete", { namespace, ids });
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// src/vector/qdrant.ts
|
|
585
|
+
async function call2(config, method, path, body) {
|
|
586
|
+
const fetchImpl = config.fetch ?? globalThis.fetch;
|
|
587
|
+
const response = await fetchImpl(`${config.url}${path}`, {
|
|
588
|
+
method,
|
|
589
|
+
headers: {
|
|
590
|
+
"content-type": "application/json",
|
|
591
|
+
...config.apiKey ? { "api-key": config.apiKey } : {}
|
|
592
|
+
},
|
|
593
|
+
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
594
|
+
});
|
|
595
|
+
const text = await response.text();
|
|
596
|
+
if (!response.ok) throw new Error(`qdrant ${response.status}: ${text.slice(0, 200)}`);
|
|
597
|
+
return text.length > 0 ? JSON.parse(text) : {};
|
|
598
|
+
}
|
|
599
|
+
function qdrant(config) {
|
|
600
|
+
const defaultTopK = Math.max(1, config.topK ?? 10);
|
|
601
|
+
return {
|
|
602
|
+
async store(docs) {
|
|
603
|
+
if (docs.length === 0) return;
|
|
604
|
+
await call2(config, "PUT", `/collections/${config.collection}/points`, {
|
|
605
|
+
points: docs.map((d) => ({
|
|
606
|
+
id: d.id,
|
|
607
|
+
vector: d.embedding,
|
|
608
|
+
payload: { content: d.content, ...d.metadata ?? {} }
|
|
609
|
+
}))
|
|
610
|
+
});
|
|
611
|
+
},
|
|
612
|
+
async search(embedding, options = {}) {
|
|
613
|
+
const topK = options.topK ?? defaultTopK;
|
|
614
|
+
const threshold = options.threshold ?? 0;
|
|
615
|
+
const result = await call2(config, "POST", `/collections/${config.collection}/points/search`, {
|
|
616
|
+
vector: embedding,
|
|
617
|
+
limit: topK,
|
|
618
|
+
with_payload: true
|
|
619
|
+
});
|
|
620
|
+
return (result.result ?? []).filter((m) => m.score >= threshold).map((m) => ({
|
|
621
|
+
id: String(m.id),
|
|
622
|
+
content: String((m.payload ?? {}).content ?? ""),
|
|
623
|
+
metadata: m.payload,
|
|
624
|
+
score: m.score
|
|
625
|
+
}));
|
|
626
|
+
},
|
|
627
|
+
async delete(ids) {
|
|
628
|
+
if (ids.length === 0) return;
|
|
629
|
+
await call2(config, "POST", `/collections/${config.collection}/points/delete`, {
|
|
630
|
+
points: ids
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// src/vector/chroma.ts
|
|
637
|
+
async function call3(config, method, path, body) {
|
|
638
|
+
const fetchImpl = config.fetch ?? globalThis.fetch;
|
|
639
|
+
const response = await fetchImpl(`${config.url}${path}`, {
|
|
640
|
+
method,
|
|
641
|
+
headers: { "content-type": "application/json" },
|
|
642
|
+
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
643
|
+
});
|
|
644
|
+
const text = await response.text();
|
|
645
|
+
if (!response.ok) throw new Error(`chroma ${response.status}: ${text.slice(0, 200)}`);
|
|
646
|
+
return text.length > 0 ? JSON.parse(text) : {};
|
|
647
|
+
}
|
|
648
|
+
function chroma(config) {
|
|
649
|
+
const defaultTopK = Math.max(1, config.topK ?? 10);
|
|
650
|
+
return {
|
|
651
|
+
async store(docs) {
|
|
652
|
+
if (docs.length === 0) return;
|
|
653
|
+
await call3(config, "POST", `/api/v1/collections/${config.collection}/upsert`, {
|
|
654
|
+
ids: docs.map((d) => d.id),
|
|
655
|
+
embeddings: docs.map((d) => d.embedding),
|
|
656
|
+
documents: docs.map((d) => d.content),
|
|
657
|
+
metadatas: docs.map((d) => d.metadata ?? {})
|
|
658
|
+
});
|
|
659
|
+
},
|
|
660
|
+
async search(embedding, options = {}) {
|
|
661
|
+
const topK = options.topK ?? defaultTopK;
|
|
662
|
+
const threshold = options.threshold ?? 0;
|
|
663
|
+
const result = await call3(config, "POST", `/api/v1/collections/${config.collection}/query`, {
|
|
664
|
+
query_embeddings: [embedding],
|
|
665
|
+
n_results: topK
|
|
666
|
+
});
|
|
667
|
+
const ids = result.ids?.[0] ?? [];
|
|
668
|
+
const documents = result.documents?.[0] ?? [];
|
|
669
|
+
const metadatas = result.metadatas?.[0] ?? [];
|
|
670
|
+
const distances = result.distances?.[0] ?? [];
|
|
671
|
+
return ids.map((id, i) => ({
|
|
672
|
+
id,
|
|
673
|
+
content: documents[i] ?? "",
|
|
674
|
+
metadata: metadatas[i],
|
|
675
|
+
score: distances[i] !== void 0 ? 1 - distances[i] : 0
|
|
676
|
+
})).filter((r) => (r.score ?? 0) >= threshold);
|
|
677
|
+
},
|
|
678
|
+
async delete(ids) {
|
|
679
|
+
if (ids.length === 0) return;
|
|
680
|
+
await call3(config, "POST", `/api/v1/collections/${config.collection}/delete`, { ids });
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// src/vector/upstash.ts
|
|
686
|
+
async function call4(config, path, body) {
|
|
687
|
+
const fetchImpl = config.fetch ?? globalThis.fetch;
|
|
688
|
+
const response = await fetchImpl(`${config.url}${path}`, {
|
|
689
|
+
method: "POST",
|
|
690
|
+
headers: {
|
|
691
|
+
"content-type": "application/json",
|
|
692
|
+
authorization: `Bearer ${config.token}`
|
|
693
|
+
},
|
|
694
|
+
body: JSON.stringify(body)
|
|
695
|
+
});
|
|
696
|
+
const text = await response.text();
|
|
697
|
+
if (!response.ok) throw new Error(`upstash-vector ${response.status}: ${text.slice(0, 200)}`);
|
|
698
|
+
return text.length > 0 ? JSON.parse(text) : {};
|
|
699
|
+
}
|
|
700
|
+
function upstashVector(config) {
|
|
701
|
+
const defaultTopK = Math.max(1, config.topK ?? 10);
|
|
702
|
+
return {
|
|
703
|
+
async store(docs) {
|
|
704
|
+
if (docs.length === 0) return;
|
|
705
|
+
await call4(
|
|
706
|
+
config,
|
|
707
|
+
"/upsert",
|
|
708
|
+
docs.map((d) => ({
|
|
709
|
+
id: d.id,
|
|
710
|
+
vector: d.embedding,
|
|
711
|
+
metadata: { content: d.content, ...d.metadata ?? {} }
|
|
712
|
+
}))
|
|
713
|
+
);
|
|
714
|
+
},
|
|
715
|
+
async search(embedding, options = {}) {
|
|
716
|
+
const topK = options.topK ?? defaultTopK;
|
|
717
|
+
const threshold = options.threshold ?? 0;
|
|
718
|
+
const result = await call4(config, "/query", { vector: embedding, topK, includeMetadata: true });
|
|
719
|
+
return (result.result ?? []).filter((m) => m.score >= threshold).map((m) => ({
|
|
720
|
+
id: m.id,
|
|
721
|
+
content: String((m.metadata ?? {}).content ?? ""),
|
|
722
|
+
metadata: m.metadata,
|
|
723
|
+
score: m.score
|
|
724
|
+
}));
|
|
725
|
+
},
|
|
726
|
+
async delete(ids) {
|
|
727
|
+
if (ids.length === 0) return;
|
|
728
|
+
await call4(config, "/delete", { ids });
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// src/encrypted.ts
|
|
734
|
+
function toBase64(bytes) {
|
|
735
|
+
if (typeof Buffer !== "undefined") return Buffer.from(bytes).toString("base64");
|
|
736
|
+
let binary = "";
|
|
737
|
+
for (const b of bytes) binary += String.fromCharCode(b);
|
|
738
|
+
return btoa(binary);
|
|
739
|
+
}
|
|
740
|
+
function fromBase64(value) {
|
|
741
|
+
if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(value, "base64"));
|
|
742
|
+
const binary = atob(value);
|
|
743
|
+
const bytes = new Uint8Array(binary.length);
|
|
744
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
745
|
+
return bytes;
|
|
746
|
+
}
|
|
747
|
+
async function resolveKey(subtle, material) {
|
|
748
|
+
if ("type" in material && material.type === "secret") return material;
|
|
749
|
+
const raw = material;
|
|
750
|
+
if (raw.byteLength !== 32) {
|
|
751
|
+
throw new Error(`createEncryptedMemory: key must be 32 bytes (got ${raw.byteLength})`);
|
|
752
|
+
}
|
|
753
|
+
return subtle.importKey("raw", raw, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
|
|
754
|
+
}
|
|
755
|
+
async function createEncryptedMemory(options) {
|
|
756
|
+
const subtle = options.subtle ?? globalThis.crypto?.subtle;
|
|
757
|
+
const random = options.getRandomValues ?? ((v) => globalThis.crypto.getRandomValues(v));
|
|
758
|
+
if (!subtle) throw new Error("createEncryptedMemory: SubtleCrypto not available");
|
|
759
|
+
const key = await resolveKey(subtle, options.key);
|
|
760
|
+
const aad = options.aad;
|
|
761
|
+
const encrypt = async (plain) => {
|
|
762
|
+
const iv = random(new Uint8Array(12));
|
|
763
|
+
const data = new TextEncoder().encode(plain);
|
|
764
|
+
const params = aad ? { name: "AES-GCM", iv, additionalData: aad } : { name: "AES-GCM", iv };
|
|
765
|
+
const cipher = await subtle.encrypt(params, key, data);
|
|
766
|
+
return {
|
|
767
|
+
ciphertext: toBase64(new Uint8Array(cipher)),
|
|
768
|
+
iv: toBase64(iv),
|
|
769
|
+
length: plain.length
|
|
770
|
+
};
|
|
771
|
+
};
|
|
772
|
+
const decrypt = async (envelope) => {
|
|
773
|
+
const iv = fromBase64(envelope.iv);
|
|
774
|
+
const params = aad ? { name: "AES-GCM", iv, additionalData: aad } : { name: "AES-GCM", iv };
|
|
775
|
+
const plain = await subtle.decrypt(params, key, fromBase64(envelope.ciphertext));
|
|
776
|
+
return new TextDecoder().decode(plain);
|
|
777
|
+
};
|
|
778
|
+
const encryptMessage = async (m) => {
|
|
779
|
+
if (m.metadata?.agentskitEncrypted) return m;
|
|
780
|
+
const envelope = await encrypt(m.content ?? "");
|
|
781
|
+
return {
|
|
782
|
+
...m,
|
|
783
|
+
content: "",
|
|
784
|
+
metadata: {
|
|
785
|
+
...m.metadata ?? {},
|
|
786
|
+
agentskitEncrypted: true,
|
|
787
|
+
ciphertext: envelope.ciphertext,
|
|
788
|
+
iv: envelope.iv,
|
|
789
|
+
length: envelope.length
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
};
|
|
793
|
+
const decryptMessage = async (m) => {
|
|
794
|
+
if (!m.metadata?.agentskitEncrypted) return m;
|
|
795
|
+
const envelope = {
|
|
796
|
+
ciphertext: String(m.metadata.ciphertext),
|
|
797
|
+
iv: String(m.metadata.iv),
|
|
798
|
+
length: Number(m.metadata.length ?? 0)
|
|
799
|
+
};
|
|
800
|
+
const content = await decrypt(envelope);
|
|
801
|
+
const { agentskitEncrypted: _, ciphertext: __, iv: ___, length: ____, ...rest } = m.metadata;
|
|
802
|
+
return { ...m, content, metadata: Object.keys(rest).length > 0 ? rest : void 0 };
|
|
803
|
+
};
|
|
804
|
+
return {
|
|
805
|
+
async load() {
|
|
806
|
+
const stored = await options.backing.load();
|
|
807
|
+
return Promise.all(stored.map(decryptMessage));
|
|
808
|
+
},
|
|
809
|
+
async save(messages) {
|
|
810
|
+
const encrypted = await Promise.all(messages.map(encryptMessage));
|
|
811
|
+
await options.backing.save(encrypted);
|
|
812
|
+
},
|
|
813
|
+
async clear() {
|
|
814
|
+
await options.backing.clear?.();
|
|
815
|
+
}
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// src/hierarchical.ts
|
|
820
|
+
function mergeChronological(a, b) {
|
|
821
|
+
const out = [...a, ...b];
|
|
822
|
+
out.sort((x, y) => x.createdAt.getTime() - y.createdAt.getTime());
|
|
823
|
+
return out;
|
|
824
|
+
}
|
|
825
|
+
function createHierarchicalMemory(options) {
|
|
826
|
+
const workingLimit = Math.max(1, options.workingLimit ?? 50);
|
|
827
|
+
const recallTopK = Math.max(0, options.recallTopK ?? 5);
|
|
828
|
+
const loadAll = async (source) => [...await source.load()];
|
|
829
|
+
return {
|
|
830
|
+
async load() {
|
|
831
|
+
const hot = await loadAll(options.working);
|
|
832
|
+
if (!options.recall || recallTopK === 0) return hot;
|
|
833
|
+
let recalled = [];
|
|
834
|
+
try {
|
|
835
|
+
recalled = [...await options.recall.query({ working: hot, topK: recallTopK })];
|
|
836
|
+
} catch {
|
|
837
|
+
recalled = [];
|
|
838
|
+
}
|
|
839
|
+
const hotIds = new Set(hot.map((m) => m.id));
|
|
840
|
+
return mergeChronological(
|
|
841
|
+
recalled.filter((m) => !hotIds.has(m.id)),
|
|
842
|
+
hot
|
|
843
|
+
);
|
|
844
|
+
},
|
|
845
|
+
async save(messages) {
|
|
846
|
+
const knownArchival = await loadAll(options.archival);
|
|
847
|
+
const archivalIds = new Set(knownArchival.map((m) => m.id));
|
|
848
|
+
const fresh = messages.filter((m) => !archivalIds.has(m.id));
|
|
849
|
+
if (fresh.length > 0) {
|
|
850
|
+
await options.archival.save(mergeChronological(knownArchival, fresh));
|
|
851
|
+
}
|
|
852
|
+
const tail = messages.slice(Math.max(0, messages.length - workingLimit));
|
|
853
|
+
const overflow = messages.slice(0, Math.max(0, messages.length - workingLimit));
|
|
854
|
+
await options.working.save(tail);
|
|
855
|
+
if (options.recall) {
|
|
856
|
+
const knownOverflow = overflow.filter((m) => fresh.some((f) => f.id === m.id));
|
|
857
|
+
for (const m of knownOverflow) await options.recall.index(m);
|
|
858
|
+
const appended = fresh.filter((m) => !tail.some((t) => t.id === m.id));
|
|
859
|
+
for (const m of appended) await options.recall.index(m);
|
|
860
|
+
}
|
|
861
|
+
},
|
|
862
|
+
async clear() {
|
|
863
|
+
await options.working.clear?.();
|
|
864
|
+
await options.archival.clear?.();
|
|
865
|
+
},
|
|
866
|
+
async archival() {
|
|
867
|
+
return loadAll(options.archival);
|
|
868
|
+
},
|
|
869
|
+
async working() {
|
|
870
|
+
return loadAll(options.working);
|
|
871
|
+
}
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
exports.chroma = chroma;
|
|
876
|
+
exports.createEncryptedMemory = createEncryptedMemory;
|
|
877
|
+
exports.createHierarchicalMemory = createHierarchicalMemory;
|
|
878
|
+
exports.createInMemoryGraph = createInMemoryGraph;
|
|
879
|
+
exports.createInMemoryPersonalization = createInMemoryPersonalization;
|
|
369
880
|
exports.fileChatMemory = fileChatMemory;
|
|
370
881
|
exports.fileVectorMemory = fileVectorMemory;
|
|
882
|
+
exports.pgvector = pgvector;
|
|
883
|
+
exports.pinecone = pinecone;
|
|
884
|
+
exports.qdrant = qdrant;
|
|
371
885
|
exports.redisChatMemory = redisChatMemory;
|
|
372
886
|
exports.redisVectorMemory = redisVectorMemory;
|
|
887
|
+
exports.renderProfileContext = renderProfileContext;
|
|
373
888
|
exports.sqliteChatMemory = sqliteChatMemory;
|
|
889
|
+
exports.upstashVector = upstashVector;
|
|
374
890
|
//# sourceMappingURL=index.cjs.map
|
|
375
891
|
//# sourceMappingURL=index.cjs.map
|