@molroo-io/sdk 0.5.2
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/LICENSE +21 -0
- package/README.md +251 -0
- package/dist/cjs/api-client.d.ts +23 -0
- package/dist/cjs/api-client.d.ts.map +1 -0
- package/dist/cjs/api-client.js +55 -0
- package/dist/cjs/defaults/index.d.ts +8 -0
- package/dist/cjs/defaults/index.d.ts.map +1 -0
- package/dist/cjs/defaults/index.js +30 -0
- package/dist/cjs/defaults/persona.json +17 -0
- package/dist/cjs/embedding/cloudflare.d.ts +15 -0
- package/dist/cjs/embedding/cloudflare.d.ts.map +1 -0
- package/dist/cjs/embedding/cloudflare.js +16 -0
- package/dist/cjs/embedding/cohere.d.ts +8 -0
- package/dist/cjs/embedding/cohere.d.ts.map +1 -0
- package/dist/cjs/embedding/cohere.js +31 -0
- package/dist/cjs/embedding/index.d.ts +9 -0
- package/dist/cjs/embedding/index.d.ts.map +1 -0
- package/dist/cjs/embedding/index.js +11 -0
- package/dist/cjs/embedding/local.d.ts +6 -0
- package/dist/cjs/embedding/local.d.ts.map +1 -0
- package/dist/cjs/embedding/local.js +28 -0
- package/dist/cjs/embedding/openai.d.ts +9 -0
- package/dist/cjs/embedding/openai.d.ts.map +1 -0
- package/dist/cjs/embedding/openai.js +26 -0
- package/dist/cjs/errors.d.ts +17 -0
- package/dist/cjs/errors.d.ts.map +1 -0
- package/dist/cjs/errors.js +21 -0
- package/dist/cjs/events/console.d.ts +25 -0
- package/dist/cjs/events/console.d.ts.map +1 -0
- package/dist/cjs/events/console.js +41 -0
- package/dist/cjs/events/types.d.ts +28 -0
- package/dist/cjs/events/types.d.ts.map +1 -0
- package/dist/cjs/events/types.js +13 -0
- package/dist/cjs/events/webhook.d.ts +30 -0
- package/dist/cjs/events/webhook.d.ts.map +1 -0
- package/dist/cjs/events/webhook.js +79 -0
- package/dist/cjs/generate/persona.d.ts +16 -0
- package/dist/cjs/generate/persona.d.ts.map +1 -0
- package/dist/cjs/generate/persona.js +42 -0
- package/dist/cjs/generate/prompt.d.ts +7 -0
- package/dist/cjs/generate/prompt.d.ts.map +1 -0
- package/dist/cjs/generate/prompt.js +41 -0
- package/dist/cjs/generate/schema.d.ts +32 -0
- package/dist/cjs/generate/schema.d.ts.map +1 -0
- package/dist/cjs/generate/schema.js +54 -0
- package/dist/cjs/generated/index.d.ts +2 -0
- package/dist/cjs/generated/index.d.ts.map +1 -0
- package/dist/cjs/generated/index.js +2 -0
- package/dist/cjs/index.d.ts +66 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +69 -0
- package/dist/cjs/llm/adapter.d.ts +61 -0
- package/dist/cjs/llm/adapter.d.ts.map +1 -0
- package/dist/cjs/llm/adapter.js +2 -0
- package/dist/cjs/llm/resolve.d.ts +28 -0
- package/dist/cjs/llm/resolve.d.ts.map +1 -0
- package/dist/cjs/llm/resolve.js +20 -0
- package/dist/cjs/llm/schema.d.ts +60 -0
- package/dist/cjs/llm/schema.d.ts.map +1 -0
- package/dist/cjs/llm/schema.js +72 -0
- package/dist/cjs/llm/types.d.ts +24 -0
- package/dist/cjs/llm/types.d.ts.map +1 -0
- package/dist/cjs/llm/types.js +2 -0
- package/dist/cjs/llm/vercel-ai/adapter.d.ts +29 -0
- package/dist/cjs/llm/vercel-ai/adapter.d.ts.map +1 -0
- package/dist/cjs/llm/vercel-ai/adapter.js +234 -0
- package/dist/cjs/llm/vercel-ai/config.d.ts +9 -0
- package/dist/cjs/llm/vercel-ai/config.d.ts.map +1 -0
- package/dist/cjs/llm/vercel-ai/config.js +2 -0
- package/dist/cjs/llm/vercel-ai/index.d.ts +9 -0
- package/dist/cjs/llm/vercel-ai/index.d.ts.map +1 -0
- package/dist/cjs/llm/vercel-ai/index.js +13 -0
- package/dist/cjs/memory/cloudflare/index.d.ts +3 -0
- package/dist/cjs/memory/cloudflare/index.d.ts.map +1 -0
- package/dist/cjs/memory/cloudflare/index.js +5 -0
- package/dist/cjs/memory/cloudflare/vectorize.d.ts +62 -0
- package/dist/cjs/memory/cloudflare/vectorize.d.ts.map +1 -0
- package/dist/cjs/memory/cloudflare/vectorize.js +55 -0
- package/dist/cjs/memory/in-memory-semantic.d.ts +16 -0
- package/dist/cjs/memory/in-memory-semantic.d.ts.map +1 -0
- package/dist/cjs/memory/in-memory-semantic.js +57 -0
- package/dist/cjs/memory/in-memory.d.ts +46 -0
- package/dist/cjs/memory/in-memory.d.ts.map +1 -0
- package/dist/cjs/memory/in-memory.js +115 -0
- package/dist/cjs/memory/pinecone/index.d.ts +7 -0
- package/dist/cjs/memory/pinecone/index.d.ts.map +1 -0
- package/dist/cjs/memory/pinecone/index.js +8 -0
- package/dist/cjs/memory/pinecone/memory-adapter.d.ts +62 -0
- package/dist/cjs/memory/pinecone/memory-adapter.d.ts.map +1 -0
- package/dist/cjs/memory/pinecone/memory-adapter.js +220 -0
- package/dist/cjs/memory/pinecone/semantic.d.ts +44 -0
- package/dist/cjs/memory/pinecone/semantic.d.ts.map +1 -0
- package/dist/cjs/memory/pinecone/semantic.js +90 -0
- package/dist/cjs/memory/recall.d.ts +58 -0
- package/dist/cjs/memory/recall.d.ts.map +1 -0
- package/dist/cjs/memory/recall.js +220 -0
- package/dist/cjs/memory/semantic.d.ts +24 -0
- package/dist/cjs/memory/semantic.d.ts.map +1 -0
- package/dist/cjs/memory/semantic.js +2 -0
- package/dist/cjs/memory/sqlite/index.d.ts +3 -0
- package/dist/cjs/memory/sqlite/index.d.ts.map +1 -0
- package/dist/cjs/memory/sqlite/index.js +5 -0
- package/dist/cjs/memory/sqlite/memory-adapter.d.ts +58 -0
- package/dist/cjs/memory/sqlite/memory-adapter.d.ts.map +1 -0
- package/dist/cjs/memory/sqlite/memory-adapter.js +336 -0
- package/dist/cjs/memory/sqlite/schema.d.ts +4 -0
- package/dist/cjs/memory/sqlite/schema.d.ts.map +1 -0
- package/dist/cjs/memory/sqlite/schema.js +91 -0
- package/dist/cjs/memory/supabase/index.d.ts +7 -0
- package/dist/cjs/memory/supabase/index.d.ts.map +1 -0
- package/dist/cjs/memory/supabase/index.js +8 -0
- package/dist/cjs/memory/supabase/memory-adapter.d.ts +67 -0
- package/dist/cjs/memory/supabase/memory-adapter.d.ts.map +1 -0
- package/dist/cjs/memory/supabase/memory-adapter.js +335 -0
- package/dist/cjs/memory/supabase/semantic.d.ts +44 -0
- package/dist/cjs/memory/supabase/semantic.d.ts.map +1 -0
- package/dist/cjs/memory/supabase/semantic.js +72 -0
- package/dist/cjs/memory/types.d.ts +231 -0
- package/dist/cjs/memory/types.d.ts.map +1 -0
- package/dist/cjs/memory/types.js +12 -0
- package/dist/cjs/persona.d.ts +326 -0
- package/dist/cjs/persona.d.ts.map +1 -0
- package/dist/cjs/persona.js +824 -0
- package/dist/cjs/types.d.ts +263 -0
- package/dist/cjs/types.d.ts.map +1 -0
- package/dist/cjs/types.js +15 -0
- package/dist/cjs/world/client.d.ts +36 -0
- package/dist/cjs/world/client.d.ts.map +1 -0
- package/dist/cjs/world/client.js +59 -0
- package/dist/cjs/world/errors.d.ts +9 -0
- package/dist/cjs/world/errors.d.ts.map +1 -0
- package/dist/cjs/world/errors.js +15 -0
- package/dist/cjs/world/index.d.ts +10 -0
- package/dist/cjs/world/index.d.ts.map +1 -0
- package/dist/cjs/world/index.js +16 -0
- package/dist/cjs/world/types.d.ts +101 -0
- package/dist/cjs/world/types.d.ts.map +1 -0
- package/dist/cjs/world/types.js +8 -0
- package/dist/cjs/world/village.d.ts +75 -0
- package/dist/cjs/world/village.d.ts.map +1 -0
- package/dist/cjs/world/village.js +278 -0
- package/dist/cjs/world/world-persona.d.ts +182 -0
- package/dist/cjs/world/world-persona.d.ts.map +1 -0
- package/dist/cjs/world/world-persona.js +192 -0
- package/dist/cjs/world/world.d.ts +41 -0
- package/dist/cjs/world/world.d.ts.map +1 -0
- package/dist/cjs/world/world.js +91 -0
- package/dist/esm/api-client.d.ts +23 -0
- package/dist/esm/api-client.d.ts.map +1 -0
- package/dist/esm/api-client.js +48 -0
- package/dist/esm/defaults/index.d.ts +8 -0
- package/dist/esm/defaults/index.d.ts.map +1 -0
- package/dist/esm/defaults/index.js +23 -0
- package/dist/esm/defaults/persona.json +17 -0
- package/dist/esm/embedding/cloudflare.d.ts +15 -0
- package/dist/esm/embedding/cloudflare.d.ts.map +1 -0
- package/dist/esm/embedding/cloudflare.js +13 -0
- package/dist/esm/embedding/cohere.d.ts +8 -0
- package/dist/esm/embedding/cohere.d.ts.map +1 -0
- package/dist/esm/embedding/cohere.js +28 -0
- package/dist/esm/embedding/index.d.ts +9 -0
- package/dist/esm/embedding/index.d.ts.map +1 -0
- package/dist/esm/embedding/index.js +4 -0
- package/dist/esm/embedding/local.d.ts +6 -0
- package/dist/esm/embedding/local.d.ts.map +1 -0
- package/dist/esm/embedding/local.js +25 -0
- package/dist/esm/embedding/openai.d.ts +9 -0
- package/dist/esm/embedding/openai.d.ts.map +1 -0
- package/dist/esm/embedding/openai.js +23 -0
- package/dist/esm/errors.d.ts +17 -0
- package/dist/esm/errors.d.ts.map +1 -0
- package/dist/esm/errors.js +17 -0
- package/dist/esm/events/console.d.ts +25 -0
- package/dist/esm/events/console.d.ts.map +1 -0
- package/dist/esm/events/console.js +37 -0
- package/dist/esm/events/types.d.ts +28 -0
- package/dist/esm/events/types.d.ts.map +1 -0
- package/dist/esm/events/types.js +12 -0
- package/dist/esm/events/webhook.d.ts +30 -0
- package/dist/esm/events/webhook.d.ts.map +1 -0
- package/dist/esm/events/webhook.js +75 -0
- package/dist/esm/generate/persona.d.ts +16 -0
- package/dist/esm/generate/persona.d.ts.map +1 -0
- package/dist/esm/generate/persona.js +39 -0
- package/dist/esm/generate/prompt.d.ts +7 -0
- package/dist/esm/generate/prompt.d.ts.map +1 -0
- package/dist/esm/generate/prompt.js +38 -0
- package/dist/esm/generate/schema.d.ts +32 -0
- package/dist/esm/generate/schema.d.ts.map +1 -0
- package/dist/esm/generate/schema.js +51 -0
- package/dist/esm/generated/index.d.ts +2 -0
- package/dist/esm/generated/index.d.ts.map +1 -0
- package/dist/esm/generated/index.js +1 -0
- package/dist/esm/index.d.ts +66 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +49 -0
- package/dist/esm/llm/adapter.d.ts +61 -0
- package/dist/esm/llm/adapter.d.ts.map +1 -0
- package/dist/esm/llm/adapter.js +1 -0
- package/dist/esm/llm/resolve.d.ts +28 -0
- package/dist/esm/llm/resolve.d.ts.map +1 -0
- package/dist/esm/llm/resolve.js +17 -0
- package/dist/esm/llm/schema.d.ts +60 -0
- package/dist/esm/llm/schema.d.ts.map +1 -0
- package/dist/esm/llm/schema.js +69 -0
- package/dist/esm/llm/types.d.ts +24 -0
- package/dist/esm/llm/types.d.ts.map +1 -0
- package/dist/esm/llm/types.js +1 -0
- package/dist/esm/llm/vercel-ai/adapter.d.ts +29 -0
- package/dist/esm/llm/vercel-ai/adapter.d.ts.map +1 -0
- package/dist/esm/llm/vercel-ai/adapter.js +196 -0
- package/dist/esm/llm/vercel-ai/config.d.ts +9 -0
- package/dist/esm/llm/vercel-ai/config.d.ts.map +1 -0
- package/dist/esm/llm/vercel-ai/config.js +1 -0
- package/dist/esm/llm/vercel-ai/index.d.ts +9 -0
- package/dist/esm/llm/vercel-ai/index.d.ts.map +1 -0
- package/dist/esm/llm/vercel-ai/index.js +8 -0
- package/dist/esm/memory/cloudflare/index.d.ts +3 -0
- package/dist/esm/memory/cloudflare/index.d.ts.map +1 -0
- package/dist/esm/memory/cloudflare/index.js +1 -0
- package/dist/esm/memory/cloudflare/vectorize.d.ts +62 -0
- package/dist/esm/memory/cloudflare/vectorize.d.ts.map +1 -0
- package/dist/esm/memory/cloudflare/vectorize.js +51 -0
- package/dist/esm/memory/in-memory-semantic.d.ts +16 -0
- package/dist/esm/memory/in-memory-semantic.d.ts.map +1 -0
- package/dist/esm/memory/in-memory-semantic.js +53 -0
- package/dist/esm/memory/in-memory.d.ts +46 -0
- package/dist/esm/memory/in-memory.d.ts.map +1 -0
- package/dist/esm/memory/in-memory.js +111 -0
- package/dist/esm/memory/pinecone/index.d.ts +7 -0
- package/dist/esm/memory/pinecone/index.d.ts.map +1 -0
- package/dist/esm/memory/pinecone/index.js +3 -0
- package/dist/esm/memory/pinecone/memory-adapter.d.ts +62 -0
- package/dist/esm/memory/pinecone/memory-adapter.d.ts.map +1 -0
- package/dist/esm/memory/pinecone/memory-adapter.js +216 -0
- package/dist/esm/memory/pinecone/semantic.d.ts +44 -0
- package/dist/esm/memory/pinecone/semantic.d.ts.map +1 -0
- package/dist/esm/memory/pinecone/semantic.js +86 -0
- package/dist/esm/memory/recall.d.ts +58 -0
- package/dist/esm/memory/recall.d.ts.map +1 -0
- package/dist/esm/memory/recall.js +215 -0
- package/dist/esm/memory/semantic.d.ts +24 -0
- package/dist/esm/memory/semantic.d.ts.map +1 -0
- package/dist/esm/memory/semantic.js +1 -0
- package/dist/esm/memory/sqlite/index.d.ts +3 -0
- package/dist/esm/memory/sqlite/index.d.ts.map +1 -0
- package/dist/esm/memory/sqlite/index.js +1 -0
- package/dist/esm/memory/sqlite/memory-adapter.d.ts +58 -0
- package/dist/esm/memory/sqlite/memory-adapter.d.ts.map +1 -0
- package/dist/esm/memory/sqlite/memory-adapter.js +296 -0
- package/dist/esm/memory/sqlite/schema.d.ts +4 -0
- package/dist/esm/memory/sqlite/schema.d.ts.map +1 -0
- package/dist/esm/memory/sqlite/schema.js +86 -0
- package/dist/esm/memory/supabase/index.d.ts +7 -0
- package/dist/esm/memory/supabase/index.d.ts.map +1 -0
- package/dist/esm/memory/supabase/index.js +3 -0
- package/dist/esm/memory/supabase/memory-adapter.d.ts +67 -0
- package/dist/esm/memory/supabase/memory-adapter.d.ts.map +1 -0
- package/dist/esm/memory/supabase/memory-adapter.js +331 -0
- package/dist/esm/memory/supabase/semantic.d.ts +44 -0
- package/dist/esm/memory/supabase/semantic.d.ts.map +1 -0
- package/dist/esm/memory/supabase/semantic.js +68 -0
- package/dist/esm/memory/types.d.ts +231 -0
- package/dist/esm/memory/types.d.ts.map +1 -0
- package/dist/esm/memory/types.js +9 -0
- package/dist/esm/persona.d.ts +326 -0
- package/dist/esm/persona.d.ts.map +1 -0
- package/dist/esm/persona.js +787 -0
- package/dist/esm/types.d.ts +263 -0
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +11 -0
- package/dist/esm/world/client.d.ts +36 -0
- package/dist/esm/world/client.d.ts.map +1 -0
- package/dist/esm/world/client.js +52 -0
- package/dist/esm/world/errors.d.ts +9 -0
- package/dist/esm/world/errors.d.ts.map +1 -0
- package/dist/esm/world/errors.js +11 -0
- package/dist/esm/world/index.d.ts +10 -0
- package/dist/esm/world/index.d.ts.map +1 -0
- package/dist/esm/world/index.js +8 -0
- package/dist/esm/world/types.d.ts +101 -0
- package/dist/esm/world/types.d.ts.map +1 -0
- package/dist/esm/world/types.js +7 -0
- package/dist/esm/world/village.d.ts +75 -0
- package/dist/esm/world/village.d.ts.map +1 -0
- package/dist/esm/world/village.js +274 -0
- package/dist/esm/world/world-persona.d.ts +182 -0
- package/dist/esm/world/world-persona.d.ts.map +1 -0
- package/dist/esm/world/world-persona.js +188 -0
- package/dist/esm/world/world.d.ts +41 -0
- package/dist/esm/world/world.d.ts.map +1 -0
- package/dist/esm/world/world.js +87 -0
- package/package.json +207 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared memory recall and prompt-building utilities.
|
|
3
|
+
*
|
|
4
|
+
* Used by MolrooPersona to recall episodic/semantic memories and
|
|
5
|
+
* format them for LLM system prompts. Supports both:
|
|
6
|
+
* - Single MemoryAdapter (new, preferred)
|
|
7
|
+
* - Split EpisodeAdapter + SemanticAdapter + EmbeddingAdapter (backward compat)
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Strip leading `[...]` metadata tags from text before embedding.
|
|
11
|
+
* These tags (channel info, scene hints, timestamps) add noise to
|
|
12
|
+
* semantic similarity and should not influence vector search.
|
|
13
|
+
*/
|
|
14
|
+
export function stripMetaTags(text) {
|
|
15
|
+
return text.replace(/^\s*(\[.*?\]\s*)+/s, '').trim();
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Recall from a single MemoryAdapter. The adapter handles all storage
|
|
19
|
+
* and vector search internally.
|
|
20
|
+
*/
|
|
21
|
+
async function recallFromAdapter(message, adapter, config, sourceEntity, reflectionEntity) {
|
|
22
|
+
const episodicLimit = config.episodicLimit ?? 5;
|
|
23
|
+
const semanticLimit = config.semanticLimit ?? 3;
|
|
24
|
+
const reflectionLimit = config.reflectionLimit ?? 3;
|
|
25
|
+
const minImportance = config.minImportance ?? 0;
|
|
26
|
+
// Episodic recall — conversations (filtered by sourceEntity)
|
|
27
|
+
const conversationP = adapter
|
|
28
|
+
.recall({
|
|
29
|
+
sourceEntity,
|
|
30
|
+
limit: episodicLimit,
|
|
31
|
+
minImportance,
|
|
32
|
+
...(config.types ? { type: config.types } : {}),
|
|
33
|
+
})
|
|
34
|
+
.catch(() => []);
|
|
35
|
+
// Episodic recall — non-chat events (no sourceEntity filter)
|
|
36
|
+
const hasTypeFilter = config.types && config.types.length > 0;
|
|
37
|
+
const eventP = !hasTypeFilter
|
|
38
|
+
? adapter
|
|
39
|
+
.recall({
|
|
40
|
+
limit: episodicLimit,
|
|
41
|
+
minImportance,
|
|
42
|
+
})
|
|
43
|
+
.then((all) => all.filter((ep) => ep.type && ep.type !== 'chat_message'))
|
|
44
|
+
.catch(() => [])
|
|
45
|
+
: Promise.resolve([]);
|
|
46
|
+
// Semantic recall (vector) — adapter handles embedding internally
|
|
47
|
+
const semanticP = adapter.semanticRecall
|
|
48
|
+
? adapter
|
|
49
|
+
.semanticRecall(stripMetaTags(message), {
|
|
50
|
+
topK: semanticLimit,
|
|
51
|
+
filter: { minImportance },
|
|
52
|
+
})
|
|
53
|
+
.catch(() => [])
|
|
54
|
+
: Promise.resolve([]);
|
|
55
|
+
// Reflection recall
|
|
56
|
+
const reflectionP = adapter.getReflections
|
|
57
|
+
? adapter
|
|
58
|
+
.getReflections(reflectionEntity)
|
|
59
|
+
.then((refs) => refs.slice(0, reflectionLimit))
|
|
60
|
+
.catch(() => [])
|
|
61
|
+
: Promise.resolve([]);
|
|
62
|
+
const [conversations, events, semantic, reflections] = await Promise.all([
|
|
63
|
+
conversationP,
|
|
64
|
+
eventP,
|
|
65
|
+
semanticP,
|
|
66
|
+
reflectionP,
|
|
67
|
+
]);
|
|
68
|
+
// Merge conversations + events, deduplicate, and re-sort by recency
|
|
69
|
+
const seenIds = new Set();
|
|
70
|
+
const episodic = [];
|
|
71
|
+
for (const ep of [...conversations, ...events]) {
|
|
72
|
+
if (!seenIds.has(ep.id)) {
|
|
73
|
+
seenIds.add(ep.id);
|
|
74
|
+
episodic.push(ep);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
episodic.sort((a, b) => b.timestamp - a.timestamp);
|
|
78
|
+
episodic.splice(episodicLimit);
|
|
79
|
+
return { episodic, semantic, reflections };
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Recall episodic + semantic + reflection memories in parallel.
|
|
83
|
+
*
|
|
84
|
+
* Supports two paths:
|
|
85
|
+
* 1. Single MemoryAdapter (`memoryAdapter` param) — adapter handles everything
|
|
86
|
+
* 2. Split stores (`episodeStore` + `semanticStore` + `embeddingProvider`) — legacy
|
|
87
|
+
*
|
|
88
|
+
* @param message - Current user message (used for semantic embedding).
|
|
89
|
+
* @param opts - Stores, provider, config, and entity filter.
|
|
90
|
+
* @returns Recalled memories grouped by type.
|
|
91
|
+
*/
|
|
92
|
+
export async function recallMemories(message, opts) {
|
|
93
|
+
const config = opts.config ?? {};
|
|
94
|
+
// Path 1: Single MemoryAdapter
|
|
95
|
+
if (opts.memoryAdapter) {
|
|
96
|
+
return recallFromAdapter(message, opts.memoryAdapter, config, opts.sourceEntity, opts.reflectionEntity);
|
|
97
|
+
}
|
|
98
|
+
// Path 2: Split stores (backward compat)
|
|
99
|
+
const { episodeStore, semanticStore, embeddingProvider } = opts;
|
|
100
|
+
const episodicLimit = config.episodicLimit ?? 5;
|
|
101
|
+
const semanticLimit = config.semanticLimit ?? 3;
|
|
102
|
+
const reflectionLimit = config.reflectionLimit ?? 3;
|
|
103
|
+
const minImportance = config.minImportance ?? 0;
|
|
104
|
+
// Episodic recall — conversations (filtered by sourceEntity)
|
|
105
|
+
const conversationP = episodeStore
|
|
106
|
+
? episodeStore
|
|
107
|
+
.recall({
|
|
108
|
+
sourceEntity: opts.sourceEntity,
|
|
109
|
+
limit: episodicLimit,
|
|
110
|
+
minImportance,
|
|
111
|
+
...(config.types ? { type: config.types } : {}),
|
|
112
|
+
})
|
|
113
|
+
.catch(() => [])
|
|
114
|
+
: Promise.resolve([]);
|
|
115
|
+
// Episodic recall — non-chat events (no sourceEntity filter).
|
|
116
|
+
// Events like attacks/gifts come from various entities and must be
|
|
117
|
+
// recalled regardless of who the persona is currently talking to.
|
|
118
|
+
const hasTypeFilter = config.types && config.types.length > 0;
|
|
119
|
+
const eventP = (episodeStore && !hasTypeFilter)
|
|
120
|
+
? episodeStore
|
|
121
|
+
.recall({
|
|
122
|
+
// No sourceEntity — recall events from all sources
|
|
123
|
+
limit: episodicLimit,
|
|
124
|
+
minImportance,
|
|
125
|
+
})
|
|
126
|
+
.then((all) => all.filter((ep) => ep.type && ep.type !== 'chat_message'))
|
|
127
|
+
.catch(() => [])
|
|
128
|
+
: Promise.resolve([]);
|
|
129
|
+
// Semantic recall (vector)
|
|
130
|
+
const semanticP = semanticStore && embeddingProvider && episodeStore?.getByIds
|
|
131
|
+
? embeddingProvider
|
|
132
|
+
.embed(stripMetaTags(message))
|
|
133
|
+
.then((embedding) => semanticStore.search({ embedding, topK: semanticLimit, filter: { minImportance } }))
|
|
134
|
+
.then((results) => results.length > 0
|
|
135
|
+
? episodeStore.getByIds(results.map((r) => r.id))
|
|
136
|
+
: [])
|
|
137
|
+
.catch(() => [])
|
|
138
|
+
: Promise.resolve([]);
|
|
139
|
+
// Reflection recall (RDB)
|
|
140
|
+
const reflectionP = episodeStore
|
|
141
|
+
? episodeStore
|
|
142
|
+
.getReflections(opts.reflectionEntity, reflectionLimit)
|
|
143
|
+
.catch(() => [])
|
|
144
|
+
: Promise.resolve([]);
|
|
145
|
+
const [conversations, events, semantic, reflections] = await Promise.all([
|
|
146
|
+
conversationP,
|
|
147
|
+
eventP,
|
|
148
|
+
semanticP,
|
|
149
|
+
reflectionP,
|
|
150
|
+
]);
|
|
151
|
+
// Merge conversations + events, deduplicate, and re-sort by recency
|
|
152
|
+
const seenIds = new Set();
|
|
153
|
+
const episodic = [];
|
|
154
|
+
for (const ep of [...conversations, ...events]) {
|
|
155
|
+
if (!seenIds.has(ep.id)) {
|
|
156
|
+
seenIds.add(ep.id);
|
|
157
|
+
episodic.push(ep);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
episodic.sort((a, b) => b.timestamp - a.timestamp);
|
|
161
|
+
episodic.splice(episodicLimit);
|
|
162
|
+
return { episodic, semantic, reflections };
|
|
163
|
+
}
|
|
164
|
+
/** Format a single episode into a display line. */
|
|
165
|
+
function formatEpisodeLine(ep) {
|
|
166
|
+
const v = ep.emotionSnapshot;
|
|
167
|
+
const isEvent = ep.type && ep.type !== 'chat_message';
|
|
168
|
+
const prefix = isEvent ? `[${ep.type}] ` : '';
|
|
169
|
+
return (`- [${new Date(ep.timestamp).toISOString()}] `
|
|
170
|
+
+ `${prefix}${ep.sourceEntity ?? 'unknown'}: ${ep.context ?? 'no context'} `
|
|
171
|
+
+ `(V:${v.V.toFixed(2)} A:${v.A.toFixed(2)} importance:${ep.importance.toFixed(2)})`);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Format recalled memories into a text block for LLM system prompt injection.
|
|
175
|
+
* Groups episodic memories by type: conversations vs events.
|
|
176
|
+
* Deduplicates semantic results that overlap with episodic results.
|
|
177
|
+
*/
|
|
178
|
+
export function buildMemoryBlock(episodicMemories, semanticMemories, reflections) {
|
|
179
|
+
const parts = [];
|
|
180
|
+
if (episodicMemories && episodicMemories.length > 0) {
|
|
181
|
+
const conversations = episodicMemories.filter((ep) => !ep.type || ep.type === 'chat_message');
|
|
182
|
+
const events = episodicMemories.filter((ep) => ep.type && ep.type !== 'chat_message');
|
|
183
|
+
if (conversations.length > 0) {
|
|
184
|
+
parts.push('Recent conversations:');
|
|
185
|
+
for (const ep of conversations)
|
|
186
|
+
parts.push(formatEpisodeLine(ep));
|
|
187
|
+
}
|
|
188
|
+
if (events.length > 0) {
|
|
189
|
+
if (parts.length > 0)
|
|
190
|
+
parts.push('');
|
|
191
|
+
parts.push('Recent events:');
|
|
192
|
+
for (const ep of events)
|
|
193
|
+
parts.push(formatEpisodeLine(ep));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (semanticMemories && semanticMemories.length > 0) {
|
|
197
|
+
const episodicIds = new Set(episodicMemories?.map((e) => e.id) ?? []);
|
|
198
|
+
const unique = semanticMemories.filter((e) => !episodicIds.has(e.id));
|
|
199
|
+
if (unique.length > 0) {
|
|
200
|
+
if (parts.length > 0)
|
|
201
|
+
parts.push('');
|
|
202
|
+
parts.push('Related past experiences:');
|
|
203
|
+
for (const ep of unique)
|
|
204
|
+
parts.push(formatEpisodeLine(ep));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (reflections && reflections.length > 0) {
|
|
208
|
+
if (parts.length > 0)
|
|
209
|
+
parts.push('');
|
|
210
|
+
parts.push('Your recent self-reflections:');
|
|
211
|
+
for (const ref of reflections)
|
|
212
|
+
parts.push(`- ${ref.content}`);
|
|
213
|
+
}
|
|
214
|
+
return parts.join('\n');
|
|
215
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Semantic (vector-based) memory interface. */
|
|
2
|
+
export interface SemanticMemory {
|
|
3
|
+
/** Embed and store text. Returns the stored entry ID. */
|
|
4
|
+
store(text: string, metadata?: Record<string, unknown>): Promise<string>;
|
|
5
|
+
/** Embed query and return similar entries by cosine similarity. */
|
|
6
|
+
search(query: string, options?: SearchOptions): Promise<SearchResult[]>;
|
|
7
|
+
/** Delete entries by ID. */
|
|
8
|
+
delete?(ids: string[]): Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
export interface SearchOptions {
|
|
11
|
+
/** Maximum number of results. Default: 5. */
|
|
12
|
+
topK?: number;
|
|
13
|
+
/** Minimum similarity score threshold (0–1). */
|
|
14
|
+
scoreThreshold?: number;
|
|
15
|
+
/** Backend-specific metadata filter. */
|
|
16
|
+
filter?: Record<string, unknown>;
|
|
17
|
+
}
|
|
18
|
+
export interface SearchResult {
|
|
19
|
+
id: string;
|
|
20
|
+
text: string;
|
|
21
|
+
score: number;
|
|
22
|
+
metadata: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=semantic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semantic.d.ts","sourceRoot":"","sources":["../../../src/memory/semantic.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAChD,MAAM,WAAW,cAAc;IAC7B,yDAAyD;IACzD,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACzE,mEAAmE;IACnE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;IACxE,4BAA4B;IAC5B,MAAM,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,aAAa;IAC5B,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/memory/sqlite/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,YAAY,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SqliteMemoryAdapter } from './memory-adapter';
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import type { MemoryAdapter, Episode, Reflection, RecallQuery, SemanticRecallOptions, EpisodePatch, CountQuery, EmbeddingAdapter } from '../types';
|
|
3
|
+
export interface SqliteMemoryAdapterOptions {
|
|
4
|
+
/** Path to SQLite database file, or ':memory:' for in-memory. */
|
|
5
|
+
dbPath: string;
|
|
6
|
+
/** Enable WAL mode for better concurrent reads. Default: true. */
|
|
7
|
+
walMode?: boolean;
|
|
8
|
+
/** Enable vector search. When absent, semanticRecall is not available. */
|
|
9
|
+
vector?: {
|
|
10
|
+
/** Embedding provider (embed function + dimension). */
|
|
11
|
+
embedding: EmbeddingAdapter;
|
|
12
|
+
/** sqlite-vec load function. Auto-imports if not provided. */
|
|
13
|
+
loadVec?: (db: Database.Database) => void;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* SQLite-backed MemoryAdapter with optional vector search.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* // RDB only (no vector search)
|
|
22
|
+
* const adapter = new SqliteMemoryAdapter({ dbPath: './memory.db' });
|
|
23
|
+
* await adapter.init();
|
|
24
|
+
*
|
|
25
|
+
* // With vector search
|
|
26
|
+
* const adapter = new SqliteMemoryAdapter({
|
|
27
|
+
* dbPath: './memory.db',
|
|
28
|
+
* vector: {
|
|
29
|
+
* embedding: openaiEmbedding({ apiKey: '...' }),
|
|
30
|
+
* },
|
|
31
|
+
* });
|
|
32
|
+
* await adapter.init();
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare class SqliteMemoryAdapter implements MemoryAdapter {
|
|
36
|
+
private db;
|
|
37
|
+
private opts;
|
|
38
|
+
private vectorEnabled;
|
|
39
|
+
private stmts;
|
|
40
|
+
constructor(options: SqliteMemoryAdapterOptions);
|
|
41
|
+
/** Initialize the database and create tables. Must be called before use. */
|
|
42
|
+
init(): Promise<void>;
|
|
43
|
+
private prepareStatements;
|
|
44
|
+
saveEpisode(episode: Episode): Promise<void>;
|
|
45
|
+
recall(query: RecallQuery): Promise<Episode[]>;
|
|
46
|
+
saveReflection(reflection: Reflection): Promise<void>;
|
|
47
|
+
getReflections(sourceEntity?: string): Promise<Reflection[]>;
|
|
48
|
+
semanticRecall(query: string, options?: SemanticRecallOptions): Promise<Episode[]>;
|
|
49
|
+
getByIds(ids: string[]): Promise<Episode[]>;
|
|
50
|
+
countEpisodes(query: CountQuery): Promise<number>;
|
|
51
|
+
updateEpisode(id: string, patch: EpisodePatch): Promise<void>;
|
|
52
|
+
deleteEpisode(id: string): Promise<void>;
|
|
53
|
+
expireEpisodes(before: number, minImportance?: number): Promise<number>;
|
|
54
|
+
private indexEmbedding;
|
|
55
|
+
/** Close the database connection. */
|
|
56
|
+
close(): void;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=memory-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-adapter.d.ts","sourceRoot":"","sources":["../../../../src/memory/sqlite/memory-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,KAAK,EACV,aAAa,EACb,OAAO,EACP,UAAU,EACV,WAAW,EACX,qBAAqB,EACrB,YAAY,EACZ,UAAU,EACV,gBAAgB,EACjB,MAAM,UAAU,CAAC;AAGlB,MAAM,WAAW,0BAA0B;IACzC,iEAAiE;IACjE,MAAM,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,0EAA0E;IAC1E,MAAM,CAAC,EAAE;QACP,uDAAuD;QACvD,SAAS,EAAE,gBAAgB,CAAC;QAC5B,8DAA8D;QAC9D,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,KAAK,IAAI,CAAC;KAC3C,CAAC;CACH;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,mBAAoB,YAAW,aAAa;IACvD,OAAO,CAAC,EAAE,CAAqB;IAC/B,OAAO,CAAC,IAAI,CAA6B;IACzC,OAAO,CAAC,aAAa,CAAS;IAE9B,OAAO,CAAC,KAAK,CAQX;gBAEU,OAAO,EAAE,0BAA0B;IAI/C,4EAA4E;IACtE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAuB3B,OAAO,CAAC,iBAAiB;IAkCnB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IA2B5C,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAkC9C,cAAc,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAarD,cAAc,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAU5D,cAAc,CAClB,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,OAAO,EAAE,CAAC;IAuCf,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAM3C,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;IAuBjD,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAe7D,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWxC,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAqB7E,OAAO,CAAC,cAAc;IAYtB,qCAAqC;IACrC,KAAK,IAAI,IAAI;CAGd"}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import { createSchema, createSchemaWithoutVec } from './schema';
|
|
3
|
+
/**
|
|
4
|
+
* SQLite-backed MemoryAdapter with optional vector search.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* // RDB only (no vector search)
|
|
9
|
+
* const adapter = new SqliteMemoryAdapter({ dbPath: './memory.db' });
|
|
10
|
+
* await adapter.init();
|
|
11
|
+
*
|
|
12
|
+
* // With vector search
|
|
13
|
+
* const adapter = new SqliteMemoryAdapter({
|
|
14
|
+
* dbPath: './memory.db',
|
|
15
|
+
* vector: {
|
|
16
|
+
* embedding: openaiEmbedding({ apiKey: '...' }),
|
|
17
|
+
* },
|
|
18
|
+
* });
|
|
19
|
+
* await adapter.init();
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export class SqliteMemoryAdapter {
|
|
23
|
+
constructor(options) {
|
|
24
|
+
this.vectorEnabled = false;
|
|
25
|
+
this.opts = options;
|
|
26
|
+
}
|
|
27
|
+
/** Initialize the database and create tables. Must be called before use. */
|
|
28
|
+
async init() {
|
|
29
|
+
this.db = new Database(this.opts.dbPath);
|
|
30
|
+
if (this.opts.walMode !== false) {
|
|
31
|
+
this.db.pragma('journal_mode = WAL');
|
|
32
|
+
}
|
|
33
|
+
if (this.opts.vector) {
|
|
34
|
+
const dim = this.opts.vector.embedding.dimension;
|
|
35
|
+
if (this.opts.vector.loadVec) {
|
|
36
|
+
this.opts.vector.loadVec(this.db);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
const sqliteVec = await import('sqlite-vec');
|
|
40
|
+
sqliteVec.load(this.db);
|
|
41
|
+
}
|
|
42
|
+
this.db.exec(createSchema(dim));
|
|
43
|
+
this.vectorEnabled = true;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
this.db.exec(createSchemaWithoutVec());
|
|
47
|
+
}
|
|
48
|
+
this.prepareStatements();
|
|
49
|
+
}
|
|
50
|
+
prepareStatements() {
|
|
51
|
+
this.stmts = {
|
|
52
|
+
insertEpisode: this.db.prepare(`
|
|
53
|
+
INSERT OR REPLACE INTO episodes
|
|
54
|
+
(id, timestamp, source_entity, context, appraisal, emotion_v, emotion_a, emotion_d, intensity, importance)
|
|
55
|
+
VALUES
|
|
56
|
+
(@id, @timestamp, @sourceEntity, @context, @appraisal, @emotionV, @emotionA, @emotionD, @intensity, @importance)
|
|
57
|
+
`),
|
|
58
|
+
insertReflection: this.db.prepare(`
|
|
59
|
+
INSERT OR REPLACE INTO reflections
|
|
60
|
+
(id, timestamp, source_entity, content, trigger_type, emotion_v, emotion_a, emotion_d)
|
|
61
|
+
VALUES
|
|
62
|
+
(@id, @timestamp, @sourceEntity, @content, @triggerType, @emotionV, @emotionA, @emotionD)
|
|
63
|
+
`),
|
|
64
|
+
getReflections: this.db.prepare(`SELECT * FROM reflections WHERE source_entity = ? ORDER BY timestamp DESC LIMIT ?`),
|
|
65
|
+
getReflectionsAll: this.db.prepare(`SELECT * FROM reflections ORDER BY timestamp DESC LIMIT ?`),
|
|
66
|
+
getByIds: this.db.prepare(`SELECT * FROM episodes WHERE id IN (SELECT value FROM json_each(?))`),
|
|
67
|
+
updateEpisode: this.db.prepare(`
|
|
68
|
+
UPDATE episodes SET importance = @importance, intensity = @intensity,
|
|
69
|
+
emotion_v = @emotionV, emotion_a = @emotionA, emotion_d = @emotionD
|
|
70
|
+
WHERE id = @id
|
|
71
|
+
`),
|
|
72
|
+
deleteEpisode: this.db.prepare(`DELETE FROM episodes WHERE id = ?`),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
// ── Required methods ──
|
|
76
|
+
async saveEpisode(episode) {
|
|
77
|
+
this.stmts.insertEpisode.run({
|
|
78
|
+
id: episode.id,
|
|
79
|
+
timestamp: episode.timestamp,
|
|
80
|
+
sourceEntity: episode.sourceEntity ?? null,
|
|
81
|
+
context: episode.context ?? null,
|
|
82
|
+
appraisal: JSON.stringify(episode.appraisal),
|
|
83
|
+
emotionV: episode.emotionSnapshot.V,
|
|
84
|
+
emotionA: episode.emotionSnapshot.A,
|
|
85
|
+
emotionD: episode.emotionSnapshot.D,
|
|
86
|
+
intensity: episode.intensity,
|
|
87
|
+
importance: episode.importance,
|
|
88
|
+
});
|
|
89
|
+
if (this.vectorEnabled && this.opts.vector) {
|
|
90
|
+
try {
|
|
91
|
+
const text = episode.context ?? episode.sourceEntity ?? '';
|
|
92
|
+
if (text) {
|
|
93
|
+
const embedding = await this.opts.vector.embedding.embed(text);
|
|
94
|
+
this.indexEmbedding(episode.id, embedding);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Best-effort: vector indexing failure doesn't block episode save.
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async recall(query) {
|
|
103
|
+
const conditions = [];
|
|
104
|
+
const params = [];
|
|
105
|
+
if (query.sourceEntity) {
|
|
106
|
+
conditions.push('source_entity = ?');
|
|
107
|
+
params.push(query.sourceEntity);
|
|
108
|
+
}
|
|
109
|
+
if (query.context) {
|
|
110
|
+
conditions.push('context LIKE ?');
|
|
111
|
+
params.push(`%${query.context}%`);
|
|
112
|
+
}
|
|
113
|
+
if (query.minImportance !== undefined) {
|
|
114
|
+
conditions.push('importance >= ?');
|
|
115
|
+
params.push(query.minImportance);
|
|
116
|
+
}
|
|
117
|
+
if (query.timeRange) {
|
|
118
|
+
conditions.push('timestamp >= ? AND timestamp <= ?');
|
|
119
|
+
params.push(query.timeRange[0], query.timeRange[1]);
|
|
120
|
+
}
|
|
121
|
+
if (query.valenceRange) {
|
|
122
|
+
conditions.push('emotion_v >= ? AND emotion_v <= ?');
|
|
123
|
+
params.push(query.valenceRange[0], query.valenceRange[1]);
|
|
124
|
+
}
|
|
125
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
126
|
+
const limit = query.limit ?? 10;
|
|
127
|
+
params.push(limit);
|
|
128
|
+
const sql = `SELECT * FROM episodes ${where} ORDER BY timestamp DESC LIMIT ?`;
|
|
129
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
130
|
+
return rows.map(rowToEpisode);
|
|
131
|
+
}
|
|
132
|
+
async saveReflection(reflection) {
|
|
133
|
+
this.stmts.insertReflection.run({
|
|
134
|
+
id: reflection.id,
|
|
135
|
+
timestamp: reflection.timestamp,
|
|
136
|
+
sourceEntity: reflection.sourceEntity ?? null,
|
|
137
|
+
content: reflection.content,
|
|
138
|
+
triggerType: reflection.trigger,
|
|
139
|
+
emotionV: reflection.emotionSnapshot.V,
|
|
140
|
+
emotionA: reflection.emotionSnapshot.A,
|
|
141
|
+
emotionD: reflection.emotionSnapshot.D,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
async getReflections(sourceEntity) {
|
|
145
|
+
const maxRows = 100;
|
|
146
|
+
const rows = sourceEntity
|
|
147
|
+
? this.stmts.getReflections.all(sourceEntity, maxRows)
|
|
148
|
+
: this.stmts.getReflectionsAll.all(maxRows);
|
|
149
|
+
return rows.map(rowToReflection);
|
|
150
|
+
}
|
|
151
|
+
// ── Optional methods ──
|
|
152
|
+
async semanticRecall(query, options) {
|
|
153
|
+
if (!this.vectorEnabled || !this.opts.vector || !this.db.open) {
|
|
154
|
+
return this.recall({ context: query, limit: options?.topK ?? 10 });
|
|
155
|
+
}
|
|
156
|
+
const embedding = await this.opts.vector.embedding.embed(query);
|
|
157
|
+
const topK = options?.topK ?? 5;
|
|
158
|
+
const hasFilter = options?.filter?.sourceEntity || options?.filter?.minImportance !== undefined;
|
|
159
|
+
const fetchK = hasFilter ? topK * 4 : topK;
|
|
160
|
+
const vec = new Float32Array(embedding);
|
|
161
|
+
const vecRows = this.db.prepare(`SELECT id, distance FROM embeddings WHERE embedding MATCH ? ORDER BY distance LIMIT ?`).all(vec, fetchK);
|
|
162
|
+
if (vecRows.length === 0)
|
|
163
|
+
return [];
|
|
164
|
+
const ids = vecRows.map(r => r.id);
|
|
165
|
+
const distMap = new Map(vecRows.map(r => [r.id, r.distance]));
|
|
166
|
+
let sql = `SELECT * FROM episodes WHERE id IN (${ids.map(() => '?').join(',')})`;
|
|
167
|
+
const params = [...ids];
|
|
168
|
+
if (options?.filter?.sourceEntity) {
|
|
169
|
+
sql += ` AND source_entity = ?`;
|
|
170
|
+
params.push(options.filter.sourceEntity);
|
|
171
|
+
}
|
|
172
|
+
if (options?.filter?.minImportance !== undefined) {
|
|
173
|
+
sql += ` AND importance >= ?`;
|
|
174
|
+
params.push(options.filter.minImportance);
|
|
175
|
+
}
|
|
176
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
177
|
+
return rows
|
|
178
|
+
.map(row => rowToEpisode(row))
|
|
179
|
+
.sort((a, b) => (distMap.get(a.id) ?? 1) - (distMap.get(b.id) ?? 1))
|
|
180
|
+
.slice(0, topK);
|
|
181
|
+
}
|
|
182
|
+
async getByIds(ids) {
|
|
183
|
+
if (ids.length === 0)
|
|
184
|
+
return [];
|
|
185
|
+
const rows = this.stmts.getByIds.all(JSON.stringify(ids));
|
|
186
|
+
return rows.map(rowToEpisode);
|
|
187
|
+
}
|
|
188
|
+
async countEpisodes(query) {
|
|
189
|
+
const conditions = [];
|
|
190
|
+
const params = [];
|
|
191
|
+
if (query.sourceEntity) {
|
|
192
|
+
conditions.push('source_entity = ?');
|
|
193
|
+
params.push(query.sourceEntity);
|
|
194
|
+
}
|
|
195
|
+
if (query.context) {
|
|
196
|
+
conditions.push('context LIKE ?');
|
|
197
|
+
params.push(`%${query.context}%`);
|
|
198
|
+
}
|
|
199
|
+
if (query.timeRange) {
|
|
200
|
+
conditions.push('timestamp >= ? AND timestamp <= ?');
|
|
201
|
+
params.push(query.timeRange[0], query.timeRange[1]);
|
|
202
|
+
}
|
|
203
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
204
|
+
const sql = `SELECT COUNT(*) as cnt FROM episodes ${where}`;
|
|
205
|
+
const row = this.db.prepare(sql).get(...params);
|
|
206
|
+
return row.cnt;
|
|
207
|
+
}
|
|
208
|
+
async updateEpisode(id, patch) {
|
|
209
|
+
const existing = this.stmts.getByIds.all(JSON.stringify([id]));
|
|
210
|
+
if (existing.length === 0)
|
|
211
|
+
return;
|
|
212
|
+
const row = existing[0];
|
|
213
|
+
this.stmts.updateEpisode.run({
|
|
214
|
+
id,
|
|
215
|
+
importance: patch.importance ?? row.importance,
|
|
216
|
+
intensity: patch.intensity ?? row.intensity,
|
|
217
|
+
emotionV: patch.emotionSnapshot?.V ?? row.emotion_v,
|
|
218
|
+
emotionA: patch.emotionSnapshot?.A ?? row.emotion_a,
|
|
219
|
+
emotionD: patch.emotionSnapshot?.D ?? row.emotion_d,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
async deleteEpisode(id) {
|
|
223
|
+
this.stmts.deleteEpisode.run(id);
|
|
224
|
+
if (this.vectorEnabled) {
|
|
225
|
+
try {
|
|
226
|
+
this.db.prepare(`DELETE FROM embeddings WHERE id = ?`).run(id);
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
// Best-effort: vector cleanup failure is non-fatal.
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
async expireEpisodes(before, minImportance) {
|
|
234
|
+
const threshold = minImportance ?? 0;
|
|
235
|
+
const result = this.db.prepare(`DELETE FROM episodes WHERE timestamp < ? AND importance < ?`).run(before, threshold);
|
|
236
|
+
if (this.vectorEnabled) {
|
|
237
|
+
try {
|
|
238
|
+
this.db.prepare(`DELETE FROM embeddings WHERE id NOT IN (SELECT id FROM episodes)`).run();
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
// Best-effort cleanup.
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return result.changes;
|
|
245
|
+
}
|
|
246
|
+
// ── Utility ──
|
|
247
|
+
indexEmbedding(id, embedding) {
|
|
248
|
+
if (!this.db.open)
|
|
249
|
+
return;
|
|
250
|
+
try {
|
|
251
|
+
const vec = new Float32Array(embedding);
|
|
252
|
+
const exists = this.db.prepare(`SELECT 1 FROM embeddings WHERE id = ?`).get(id);
|
|
253
|
+
if (exists)
|
|
254
|
+
return;
|
|
255
|
+
this.db.prepare(`INSERT INTO embeddings (id, embedding) VALUES (?, ?)`).run(id, vec);
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
// Silently ignore.
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/** Close the database connection. */
|
|
262
|
+
close() {
|
|
263
|
+
this.db.close();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// ── Row mappers ──
|
|
267
|
+
function rowToEpisode(row) {
|
|
268
|
+
return {
|
|
269
|
+
id: row.id,
|
|
270
|
+
timestamp: row.timestamp,
|
|
271
|
+
sourceEntity: row.source_entity ?? undefined,
|
|
272
|
+
context: row.context ?? undefined,
|
|
273
|
+
appraisal: JSON.parse(row.appraisal),
|
|
274
|
+
emotionSnapshot: {
|
|
275
|
+
V: row.emotion_v,
|
|
276
|
+
A: row.emotion_a,
|
|
277
|
+
D: row.emotion_d,
|
|
278
|
+
},
|
|
279
|
+
intensity: row.intensity,
|
|
280
|
+
importance: row.importance,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
function rowToReflection(row) {
|
|
284
|
+
return {
|
|
285
|
+
id: row.id,
|
|
286
|
+
timestamp: row.timestamp,
|
|
287
|
+
sourceEntity: row.source_entity ?? undefined,
|
|
288
|
+
content: row.content,
|
|
289
|
+
trigger: row.trigger_type,
|
|
290
|
+
emotionSnapshot: {
|
|
291
|
+
V: row.emotion_v,
|
|
292
|
+
A: row.emotion_a,
|
|
293
|
+
D: row.emotion_d,
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../../src/memory/sqlite/schema.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,IAAI,CAAC;AAEhC,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CA4CzD;AAED,wBAAgB,sBAAsB,IAAI,MAAM,CAuC/C"}
|