mnemocyte 0.5.0 → 0.5.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 +68 -177
- package/dist/embedders/index.mjs +1 -2
- package/dist/embedders/openai.d.mts +3 -8
- package/dist/embedders/openai.mjs +3 -4
- package/dist/embedders/openai.mjs.map +1 -1
- package/dist/{errors-DIPysVWl.mjs → errors-Dgt2nAs1.mjs} +2 -3
- package/dist/{errors-DIPysVWl.mjs.map → errors-Dgt2nAs1.mjs.map} +1 -1
- package/dist/index.d.mts +5 -11
- package/dist/index.mjs +947 -12
- package/dist/index.mjs.map +1 -1
- package/dist/{postgres-BHQ1pj16.mjs → postgres-BDti0rrm.mjs} +14 -23
- package/dist/{postgres-BHQ1pj16.mjs.map → postgres-BDti0rrm.mjs.map} +1 -1
- package/dist/{postgres-runtime-DxJxns07.mjs → postgres-runtime-51fQVwqa.mjs} +3 -4
- package/dist/{postgres-runtime-DxJxns07.mjs.map → postgres-runtime-51fQVwqa.mjs.map} +1 -1
- package/dist/store-config-BZ4J1hrM.mjs +304 -0
- package/dist/store-config-BZ4J1hrM.mjs.map +1 -0
- package/dist/stores/drizzle.d.mts +2 -7
- package/dist/stores/drizzle.mjs +3 -4
- package/dist/{types-XUWYDoXM.d.mts → types-DXYtLiOT.d.mts} +3 -4
- package/migrations/fresh-install.sql +43 -0
- package/package.json +23 -20
- package/dist/embedders/openai.d.mts.map +0 -1
- package/dist/index.d.mts.map +0 -1
- package/dist/store-config-BFFTm1sj.mjs +0 -1255
- package/dist/store-config-BFFTm1sj.mjs.map +0 -1
- package/dist/stores/drizzle.d.mts.map +0 -1
- package/dist/types-XUWYDoXM.d.mts.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,945 @@
|
|
|
1
|
-
import { n as isMnemocyteError, t as MnemocyteError } from "./errors-
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { n as isMnemocyteError, t as MnemocyteError } from "./errors-Dgt2nAs1.mjs";
|
|
2
|
+
import { c as IMPORTANCE_RANK, d as createId, f as normalizeTags, g as parsePostgresDatabaseUrl, h as withResilience, i as assertPruneFilterHasSelector, l as cloneMemory, m as throwIfAborted, n as unwrapMnemocyteStoreConfig, p as cloneJsonObject, r as memoryHasDependentsError, u as createEventId } from "./store-config-BZ4J1hrM.mjs";
|
|
3
|
+
// src/context/formatter.ts
|
|
4
|
+
function escapeXml(value) {
|
|
5
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
6
|
+
}
|
|
7
|
+
function formatMetadata(memory) {
|
|
8
|
+
const parts = [
|
|
9
|
+
`type=${memory.type}`,
|
|
10
|
+
`importance=${memory.importance}`,
|
|
11
|
+
`score=${memory.score.toFixed(3)}`
|
|
12
|
+
];
|
|
13
|
+
if (memory.source) parts.push(`source=${memory.source}`);
|
|
14
|
+
if (memory.tags.length > 0) parts.push(`tags=${memory.tags.join(",")}`);
|
|
15
|
+
return parts.join("; ");
|
|
16
|
+
}
|
|
17
|
+
function markdownFence(value) {
|
|
18
|
+
const longestBacktickRun = Math.max(0, ...Array.from(value.matchAll(/`+/g), (match) => match[0].length));
|
|
19
|
+
return "`".repeat(Math.max(3, longestBacktickRun + 1));
|
|
20
|
+
}
|
|
21
|
+
function plainFence(values) {
|
|
22
|
+
const longestEqualsRun = Math.max(0, ...values.flatMap((value) => Array.from(value.matchAll(/=+/g), (match) => match[0].length)));
|
|
23
|
+
return "=".repeat(Math.max(8, longestEqualsRun + 1));
|
|
24
|
+
}
|
|
25
|
+
function formatMarkdown(memories, query, omittedCount) {
|
|
26
|
+
const lines = [
|
|
27
|
+
"# Memory Context",
|
|
28
|
+
"",
|
|
29
|
+
`Query: ${query}`,
|
|
30
|
+
"",
|
|
31
|
+
"## Relevant Memories"
|
|
32
|
+
];
|
|
33
|
+
for (const memory of memories) {
|
|
34
|
+
const fence = markdownFence(memory.content);
|
|
35
|
+
lines.push("", `- ${formatMetadata(memory)}`, ` ${fence}text`, ...memory.content.split("\n").map((line) => ` ${line}`), ` ${fence}`);
|
|
36
|
+
}
|
|
37
|
+
if (omittedCount > 0) lines.push("", `[${omittedCount} memories omitted to fit token budget]`);
|
|
38
|
+
return lines.join("\n");
|
|
39
|
+
}
|
|
40
|
+
function formatPlain(memories, query, omittedCount) {
|
|
41
|
+
const formattedMemories = memories.map((memory) => ({
|
|
42
|
+
memory,
|
|
43
|
+
metadata: formatMetadata(memory)
|
|
44
|
+
}));
|
|
45
|
+
const fence = plainFence([query, ...formattedMemories.flatMap(({ memory, metadata }) => [metadata, memory.content])]);
|
|
46
|
+
const lines = [
|
|
47
|
+
"MEMORY CONTEXT",
|
|
48
|
+
"",
|
|
49
|
+
`QUERY: ${query}`,
|
|
50
|
+
"",
|
|
51
|
+
"RELEVANT MEMORIES"
|
|
52
|
+
];
|
|
53
|
+
for (const [index, { memory, metadata }] of formattedMemories.entries()) lines.push("", `${fence} MEMORY ${index + 1} START ${fence}`, metadata, memory.content, `${fence} MEMORY ${index + 1} END ${fence}`);
|
|
54
|
+
if (omittedCount > 0) lines.push("", `[${omittedCount} memories omitted to fit token budget]`);
|
|
55
|
+
return lines.join("\n");
|
|
56
|
+
}
|
|
57
|
+
function formatXml(memories, query, omittedCount) {
|
|
58
|
+
const lines = [`<memory_context query="${escapeXml(query)}">`];
|
|
59
|
+
for (const memory of memories) lines.push(` <memory id="${escapeXml(memory.id)}" type="${escapeXml(memory.type)}" importance="${escapeXml(memory.importance)}" score="${memory.score.toFixed(3)}">`, ` <content>${escapeXml(memory.content)}</content>`, ` <metadata>${escapeXml(formatMetadata(memory))}</metadata>`, " </memory>");
|
|
60
|
+
if (omittedCount > 0) lines.push(` <omitted count="${omittedCount}" reason="token_budget" />`);
|
|
61
|
+
lines.push("</memory_context>");
|
|
62
|
+
return lines.join("\n");
|
|
63
|
+
}
|
|
64
|
+
function formatContext(memories, query, format, omittedCount = 0) {
|
|
65
|
+
switch (format) {
|
|
66
|
+
case "markdown": return formatMarkdown(memories, query, omittedCount);
|
|
67
|
+
case "plain": return formatPlain(memories, query, omittedCount);
|
|
68
|
+
case "xml": return formatXml(memories, query, omittedCount);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// src/context/tokens.ts
|
|
72
|
+
const TRUNCATION_MARKER = "[truncated to fit token budget]";
|
|
73
|
+
const heuristicTokenCounter = { count(text) {
|
|
74
|
+
return Math.ceil(text.length / 4);
|
|
75
|
+
} };
|
|
76
|
+
function trimToTokenBudget(text, maxTokens, tokenCounter) {
|
|
77
|
+
if (maxTokens < 1 || tokenCounter.count(text) <= maxTokens) return text;
|
|
78
|
+
let low = 0;
|
|
79
|
+
let high = text.length;
|
|
80
|
+
let best = "";
|
|
81
|
+
while (low <= high) {
|
|
82
|
+
const mid = Math.floor((low + high) / 2);
|
|
83
|
+
const output = `${text.slice(0, mid).trimEnd()}\n${TRUNCATION_MARKER}`;
|
|
84
|
+
if (tokenCounter.count(output) <= maxTokens) {
|
|
85
|
+
best = output;
|
|
86
|
+
low = mid + 1;
|
|
87
|
+
} else high = mid - 1;
|
|
88
|
+
}
|
|
89
|
+
if (best) return best;
|
|
90
|
+
for (let length = 31; length > 0; length -= 1) {
|
|
91
|
+
const markerFragment = TRUNCATION_MARKER.slice(0, length);
|
|
92
|
+
if (tokenCounter.count(markerFragment) <= maxTokens) return markerFragment;
|
|
93
|
+
}
|
|
94
|
+
return "";
|
|
95
|
+
}
|
|
96
|
+
// src/context/builder.ts
|
|
97
|
+
async function buildContext(options) {
|
|
98
|
+
const format = options.input.format ?? "markdown";
|
|
99
|
+
const maxTokens = options.input.maxTokens ?? 1200;
|
|
100
|
+
const tokenCounter = options.input.tokenCounter ?? heuristicTokenCounter;
|
|
101
|
+
const memories = await options.recall(options.input);
|
|
102
|
+
let low = 0;
|
|
103
|
+
let high = memories.length;
|
|
104
|
+
let bestContext = "";
|
|
105
|
+
while (low <= high) {
|
|
106
|
+
const mid = Math.floor((low + high) / 2);
|
|
107
|
+
const omittedCount = memories.length - mid;
|
|
108
|
+
const context = formatContext(memories.slice(0, mid), options.input.query, format, omittedCount);
|
|
109
|
+
if (tokenCounter.count(context) <= maxTokens) {
|
|
110
|
+
bestContext = context;
|
|
111
|
+
low = mid + 1;
|
|
112
|
+
} else high = mid - 1;
|
|
113
|
+
}
|
|
114
|
+
if (bestContext || memories.length === 0) return bestContext;
|
|
115
|
+
return trimToTokenBudget(formatContext([], options.input.query, format, memories.length), maxTokens, tokenCounter);
|
|
116
|
+
}
|
|
117
|
+
// src/observability.ts
|
|
118
|
+
function withMetadata(event, metadata) {
|
|
119
|
+
return {
|
|
120
|
+
...event,
|
|
121
|
+
...metadata.entityId === void 0 ? {} : { entityId: metadata.entityId },
|
|
122
|
+
...metadata.memoryId === void 0 ? {} : { memoryId: metadata.memoryId },
|
|
123
|
+
...metadata.count === void 0 ? {} : { count: metadata.count }
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
async function emitObservation(config, event) {
|
|
127
|
+
try {
|
|
128
|
+
await config.observability?.onEvent?.(event);
|
|
129
|
+
} catch {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async function observeFromStartedAt(config, backend, operation, metadata, startedAt, action, successMetadata) {
|
|
134
|
+
await emitObservation(config, withMetadata({
|
|
135
|
+
operation,
|
|
136
|
+
phase: "start",
|
|
137
|
+
backend,
|
|
138
|
+
timestamp: new Date(startedAt)
|
|
139
|
+
}, metadata));
|
|
140
|
+
try {
|
|
141
|
+
const result = await action();
|
|
142
|
+
await emitObservation(config, withMetadata({
|
|
143
|
+
operation,
|
|
144
|
+
phase: "success",
|
|
145
|
+
backend,
|
|
146
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
147
|
+
durationMs: Date.now() - startedAt
|
|
148
|
+
}, {
|
|
149
|
+
...metadata,
|
|
150
|
+
...successMetadata?.(result)
|
|
151
|
+
}));
|
|
152
|
+
return result;
|
|
153
|
+
} catch (error) {
|
|
154
|
+
await emitObservation(config, withMetadata({
|
|
155
|
+
operation,
|
|
156
|
+
phase: "error",
|
|
157
|
+
backend,
|
|
158
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
159
|
+
durationMs: Date.now() - startedAt,
|
|
160
|
+
error
|
|
161
|
+
}, metadata));
|
|
162
|
+
throw error;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function observe(config, backend, operation, metadata, action, successMetadata) {
|
|
166
|
+
return observeFromStartedAt(config, backend, operation, metadata, Date.now(), action, successMetadata);
|
|
167
|
+
}
|
|
168
|
+
function observePrepared(config, backend, operation, metadata, prepare, action, successMetadata) {
|
|
169
|
+
const startedAt = Date.now();
|
|
170
|
+
try {
|
|
171
|
+
const prepared = prepare();
|
|
172
|
+
return observeFromStartedAt(config, backend, operation, metadata, startedAt, () => action(prepared), successMetadata);
|
|
173
|
+
} catch (error) {
|
|
174
|
+
return observeFromStartedAt(config, backend, operation, metadata, startedAt, async () => {
|
|
175
|
+
throw error;
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// src/retrieval/scorer.ts
|
|
180
|
+
const IMPORTANCE_SCORES = {
|
|
181
|
+
low: 0,
|
|
182
|
+
normal: .5,
|
|
183
|
+
high: .8,
|
|
184
|
+
critical: 1
|
|
185
|
+
};
|
|
186
|
+
const DEFAULT_RETRIEVAL_WEIGHTS = {
|
|
187
|
+
vector: .55,
|
|
188
|
+
lexical: .2,
|
|
189
|
+
recency: .1,
|
|
190
|
+
confidence: .05,
|
|
191
|
+
access: .05,
|
|
192
|
+
importance: .05
|
|
193
|
+
};
|
|
194
|
+
const DEFAULT_RECENCY_HALF_LIFE_DAYS = 90;
|
|
195
|
+
const DEFAULT_ACCESS_SATURATION = 10;
|
|
196
|
+
function cosineSimilarity(a, b) {
|
|
197
|
+
let dot = 0;
|
|
198
|
+
let aMagnitude = 0;
|
|
199
|
+
let bMagnitude = 0;
|
|
200
|
+
for (let i = 0; i < a.length; i += 1) {
|
|
201
|
+
const av = a[i] ?? 0;
|
|
202
|
+
const bv = b[i] ?? 0;
|
|
203
|
+
dot += av * bv;
|
|
204
|
+
aMagnitude += av * av;
|
|
205
|
+
bMagnitude += bv * bv;
|
|
206
|
+
}
|
|
207
|
+
if (aMagnitude === 0 || bMagnitude === 0) return 0;
|
|
208
|
+
return dot / (Math.sqrt(aMagnitude) * Math.sqrt(bMagnitude));
|
|
209
|
+
}
|
|
210
|
+
function tokenizeQuery(query) {
|
|
211
|
+
return query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
212
|
+
}
|
|
213
|
+
function lexicalScoreWithTerms(content, terms) {
|
|
214
|
+
if (terms.length === 0) return 0;
|
|
215
|
+
const lowerContent = content.toLowerCase();
|
|
216
|
+
return terms.filter((term) => lowerContent.includes(term)).length / terms.length;
|
|
217
|
+
}
|
|
218
|
+
function createLexicalScorer(query) {
|
|
219
|
+
const terms = tokenizeQuery(query);
|
|
220
|
+
return (content) => lexicalScoreWithTerms(content, terms);
|
|
221
|
+
}
|
|
222
|
+
function clampScore(value) {
|
|
223
|
+
if (!Number.isFinite(value)) return 0;
|
|
224
|
+
return Math.max(0, Math.min(1, value));
|
|
225
|
+
}
|
|
226
|
+
function toVectorScore(cosineSimilarity) {
|
|
227
|
+
return clampScore(cosineSimilarity);
|
|
228
|
+
}
|
|
229
|
+
function normalizeWeights(weights) {
|
|
230
|
+
const merged = {
|
|
231
|
+
...DEFAULT_RETRIEVAL_WEIGHTS,
|
|
232
|
+
...weights
|
|
233
|
+
};
|
|
234
|
+
const total = merged.vector + merged.lexical + merged.recency + merged.confidence + merged.access + merged.importance;
|
|
235
|
+
if (total <= 0) return { ...DEFAULT_RETRIEVAL_WEIGHTS };
|
|
236
|
+
return {
|
|
237
|
+
vector: merged.vector / total,
|
|
238
|
+
lexical: merged.lexical / total,
|
|
239
|
+
recency: merged.recency / total,
|
|
240
|
+
confidence: merged.confidence / total,
|
|
241
|
+
access: merged.access / total,
|
|
242
|
+
importance: merged.importance / total
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function createScoringConfig(config) {
|
|
246
|
+
return {
|
|
247
|
+
weights: normalizeWeights(config?.weights),
|
|
248
|
+
recencyHalfLifeDays: config?.recencyHalfLifeDays ?? DEFAULT_RECENCY_HALF_LIFE_DAYS,
|
|
249
|
+
accessSaturation: config?.accessSaturation ?? DEFAULT_ACCESS_SATURATION
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function recencyScore(createdAt, halfLifeDays) {
|
|
253
|
+
const ageMs = Date.now() - createdAt.getTime();
|
|
254
|
+
const ageDays = Math.max(0, ageMs / 864e5);
|
|
255
|
+
return Math.exp(-Math.LN2 * ageDays / halfLifeDays);
|
|
256
|
+
}
|
|
257
|
+
function accessScore(accessCount, saturation) {
|
|
258
|
+
if (accessCount <= 0) return 0;
|
|
259
|
+
return Math.min(1, Math.log1p(accessCount) / Math.log1p(saturation));
|
|
260
|
+
}
|
|
261
|
+
function toScoredMemoryWithConfig(memory, vector, lexical, input, config) {
|
|
262
|
+
const { weights } = config;
|
|
263
|
+
const recency = recencyScore(memory.createdAt, config.recencyHalfLifeDays);
|
|
264
|
+
const confidence = clampScore(memory.confidence);
|
|
265
|
+
const access = accessScore(memory.accessCount, config.accessSaturation);
|
|
266
|
+
const importance = IMPORTANCE_SCORES[memory.importance];
|
|
267
|
+
const score = Math.max(0, Math.min(1, clampScore(vector) * weights.vector + clampScore(lexical) * weights.lexical + recency * weights.recency + confidence * weights.confidence + access * weights.access + importance * weights.importance));
|
|
268
|
+
return {
|
|
269
|
+
...cloneMemory(memory),
|
|
270
|
+
score,
|
|
271
|
+
scores: {
|
|
272
|
+
vector: clampScore(vector),
|
|
273
|
+
lexical: clampScore(lexical),
|
|
274
|
+
recency,
|
|
275
|
+
confidence,
|
|
276
|
+
access,
|
|
277
|
+
importance
|
|
278
|
+
},
|
|
279
|
+
explanation: input.explain ? {
|
|
280
|
+
vectorScore: clampScore(vector),
|
|
281
|
+
lexicalScore: clampScore(lexical),
|
|
282
|
+
recencyScore: recency,
|
|
283
|
+
confidenceScore: confidence,
|
|
284
|
+
accessScore: access,
|
|
285
|
+
importanceScore: importance,
|
|
286
|
+
importanceBoost: importance * weights.importance,
|
|
287
|
+
weights,
|
|
288
|
+
finalScore: score
|
|
289
|
+
} : null
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
// src/memory/embeddings.ts
|
|
293
|
+
function validateEmbedding(embedding, dimensions) {
|
|
294
|
+
if (embedding.length !== dimensions) throw new MnemocyteError("Embedder returned an embedding with unexpected dimensions.", "EMBEDDING");
|
|
295
|
+
if (embedding.some((component) => !Number.isFinite(component))) throw new MnemocyteError("Embedder returned an embedding with non-finite values.", "EMBEDDING");
|
|
296
|
+
if (embedding.every((component) => component === 0)) throw new MnemocyteError("Embedder returned an embedding with zero norm.", "EMBEDDING");
|
|
297
|
+
}
|
|
298
|
+
async function embedOne(embedder, text, options = {}) {
|
|
299
|
+
let embeddings;
|
|
300
|
+
try {
|
|
301
|
+
embeddings = await withResilience((signal) => signal === void 0 ? embedder.embed([text]) : embedder.embed([text], { signal }), {
|
|
302
|
+
...options.signal === void 0 ? {} : { signal: options.signal },
|
|
303
|
+
...options.resilience === void 0 ? {} : { resilience: options.resilience }
|
|
304
|
+
});
|
|
305
|
+
} catch (error) {
|
|
306
|
+
if (error instanceof MnemocyteError && (error.code === "TIMEOUT" || error.code === "ABORTED")) throw error;
|
|
307
|
+
throw new MnemocyteError("Failed to embed text.", "EMBEDDING", error);
|
|
308
|
+
}
|
|
309
|
+
const embedding = embeddings[0];
|
|
310
|
+
if (!embedding) throw new MnemocyteError("Embedder returned no embedding.", "EMBEDDING");
|
|
311
|
+
validateEmbedding(embedding, embedder.dimensions);
|
|
312
|
+
return embedding;
|
|
313
|
+
}
|
|
314
|
+
async function embedMany(embedder, texts, options = {}) {
|
|
315
|
+
if (texts.length === 0) return [];
|
|
316
|
+
let embeddings;
|
|
317
|
+
try {
|
|
318
|
+
embeddings = await withResilience((signal) => signal === void 0 ? embedder.embed(texts) : embedder.embed(texts, { signal }), {
|
|
319
|
+
...options.signal === void 0 ? {} : { signal: options.signal },
|
|
320
|
+
...options.resilience === void 0 ? {} : { resilience: options.resilience }
|
|
321
|
+
});
|
|
322
|
+
} catch (error) {
|
|
323
|
+
if (error instanceof MnemocyteError && (error.code === "TIMEOUT" || error.code === "ABORTED")) throw error;
|
|
324
|
+
throw new MnemocyteError("Failed to embed texts.", "EMBEDDING", error);
|
|
325
|
+
}
|
|
326
|
+
if (embeddings.length !== texts.length) throw new MnemocyteError(`Embedder returned ${embeddings.length} embeddings for ${texts.length} texts.`, "EMBEDDING");
|
|
327
|
+
for (const embedding of embeddings) validateEmbedding(embedding, embedder.dimensions);
|
|
328
|
+
return embeddings;
|
|
329
|
+
}
|
|
330
|
+
// src/memory/validation.ts
|
|
331
|
+
const MEMORY_TYPES = [
|
|
332
|
+
"fact",
|
|
333
|
+
"preference",
|
|
334
|
+
"instruction",
|
|
335
|
+
"backstory",
|
|
336
|
+
"episode",
|
|
337
|
+
"session"
|
|
338
|
+
];
|
|
339
|
+
const IMPORTANCE_LEVELS = [
|
|
340
|
+
"low",
|
|
341
|
+
"normal",
|
|
342
|
+
"high",
|
|
343
|
+
"critical"
|
|
344
|
+
];
|
|
345
|
+
function isMemoryType(value) {
|
|
346
|
+
return typeof value === "string" && MEMORY_TYPES.includes(value);
|
|
347
|
+
}
|
|
348
|
+
function isImportanceLevel(value) {
|
|
349
|
+
return typeof value === "string" && IMPORTANCE_LEVELS.includes(value);
|
|
350
|
+
}
|
|
351
|
+
function assertMemoryType(value, field) {
|
|
352
|
+
if (!isMemoryType(value)) throw new MnemocyteError(`${field} must be a known memory type.`, "VALIDATION");
|
|
353
|
+
}
|
|
354
|
+
function assertImportanceLevel(value, field) {
|
|
355
|
+
if (!isImportanceLevel(value)) throw new MnemocyteError(`${field} must be a known importance level.`, "VALIDATION");
|
|
356
|
+
}
|
|
357
|
+
function assertMemoryTypeArray(value, field) {
|
|
358
|
+
if (!Array.isArray(value)) throw new MnemocyteError(`${field} must be an array.`, "VALIDATION");
|
|
359
|
+
for (const item of value) if (!isMemoryType(item)) throw new MnemocyteError(`${field} must contain only known memory types.`, "VALIDATION");
|
|
360
|
+
}
|
|
361
|
+
function assertValidDate(value, field) {
|
|
362
|
+
if (!(value instanceof Date) || !Number.isFinite(value.getTime())) throw new MnemocyteError(`${field} must be a valid Date.`, "VALIDATION");
|
|
363
|
+
}
|
|
364
|
+
function assertNonEmptyString(value, field, code = "VALIDATION") {
|
|
365
|
+
if (typeof value !== "string" || value.trim().length === 0) throw new MnemocyteError(`${field} must be a non-empty string.`, code);
|
|
366
|
+
}
|
|
367
|
+
function assertLimit(value) {
|
|
368
|
+
if (!Number.isInteger(value) || value < 1) throw new MnemocyteError("limit must be a positive integer.", "VALIDATION");
|
|
369
|
+
}
|
|
370
|
+
function assertMinScore(value) {
|
|
371
|
+
if (!Number.isFinite(value) || value < 0 || value > 1) throw new MnemocyteError("minScore must be a number between 0 and 1.", "VALIDATION");
|
|
372
|
+
}
|
|
373
|
+
function assertEmbedder(embedder) {
|
|
374
|
+
if (!embedder || typeof embedder.embed !== "function") throw new MnemocyteError("embedder with an embed function is required.", "CONFIG");
|
|
375
|
+
assertNonEmptyString(embedder.model, "embedder.model", "CONFIG");
|
|
376
|
+
if (!Number.isInteger(embedder.dimensions) || embedder.dimensions < 1) throw new MnemocyteError("embedder.dimensions must be a positive integer.", "CONFIG");
|
|
377
|
+
}
|
|
378
|
+
const RETRIEVAL_WEIGHT_KEYS = [
|
|
379
|
+
"vector",
|
|
380
|
+
"lexical",
|
|
381
|
+
"recency",
|
|
382
|
+
"confidence",
|
|
383
|
+
"access",
|
|
384
|
+
"importance"
|
|
385
|
+
];
|
|
386
|
+
function assertPositiveFiniteConfigValue(value, field) {
|
|
387
|
+
if (!Number.isFinite(value) || value <= 0) throw new MnemocyteError(`${field} must be a positive finite number.`, "CONFIG");
|
|
388
|
+
}
|
|
389
|
+
function validateRetrievalConfig(config) {
|
|
390
|
+
if (config === void 0) return;
|
|
391
|
+
let weightTotal = 0;
|
|
392
|
+
for (const key of RETRIEVAL_WEIGHT_KEYS) {
|
|
393
|
+
const weight = config.weights?.[key] ?? DEFAULT_RETRIEVAL_WEIGHTS[key];
|
|
394
|
+
if (!Number.isFinite(weight) || weight < 0) throw new MnemocyteError(`retrieval.weights.${key} must be a non-negative finite number.`, "CONFIG");
|
|
395
|
+
weightTotal += weight;
|
|
396
|
+
}
|
|
397
|
+
if (weightTotal === 0) throw new MnemocyteError("retrieval.weights must have an effective total greater than zero.", "CONFIG");
|
|
398
|
+
if (config.recencyHalfLifeDays !== void 0) assertPositiveFiniteConfigValue(config.recencyHalfLifeDays, "retrieval.recencyHalfLifeDays");
|
|
399
|
+
if (config.accessSaturation !== void 0) assertPositiveFiniteConfigValue(config.accessSaturation, "retrieval.accessSaturation");
|
|
400
|
+
if (config.candidateMultiplier !== void 0 && (!Number.isInteger(config.candidateMultiplier) || config.candidateMultiplier < 1)) throw new MnemocyteError("retrieval.candidateMultiplier must be an integer greater than or equal to 1.", "CONFIG");
|
|
401
|
+
}
|
|
402
|
+
function validateProviderResilienceConfig(config) {
|
|
403
|
+
if (config === void 0) return;
|
|
404
|
+
if (typeof config !== "object" || config === null || Array.isArray(config)) throw new MnemocyteError("provider must be an object.", "CONFIG");
|
|
405
|
+
for (const [field, value] of [
|
|
406
|
+
["provider.timeoutMs", config.timeoutMs],
|
|
407
|
+
["provider.baseDelayMs", config.baseDelayMs],
|
|
408
|
+
["provider.maxDelayMs", config.maxDelayMs]
|
|
409
|
+
]) if (value !== void 0 && (!Number.isFinite(value) || value < 0)) throw new MnemocyteError(`${field} must be a non-negative finite number.`, "CONFIG");
|
|
410
|
+
if (config.retries !== void 0 && (!Number.isInteger(config.retries) || config.retries < 0)) throw new MnemocyteError("provider.retries must be a non-negative integer.", "CONFIG");
|
|
411
|
+
if (config.shouldRetry !== void 0 && typeof config.shouldRetry !== "function") throw new MnemocyteError("provider.shouldRetry must be a function.", "CONFIG");
|
|
412
|
+
}
|
|
413
|
+
function validateBuildContextInput(input) {
|
|
414
|
+
if (input.maxTokens !== void 0 && (!Number.isInteger(input.maxTokens) || input.maxTokens < 1)) throw new MnemocyteError("maxTokens must be a positive integer when supplied.", "VALIDATION");
|
|
415
|
+
}
|
|
416
|
+
function validateRememberInput(input) {
|
|
417
|
+
assertNonEmptyString(input.entityId, "entityId");
|
|
418
|
+
assertNonEmptyString(input.content, "content");
|
|
419
|
+
if (input.type !== void 0) assertMemoryType(input.type, "type");
|
|
420
|
+
if (input.importance !== void 0) assertImportanceLevel(input.importance, "importance");
|
|
421
|
+
if (input.tags !== void 0) {
|
|
422
|
+
if (!Array.isArray(input.tags)) throw new MnemocyteError("tags must be an array.", "VALIDATION");
|
|
423
|
+
for (const tag of input.tags) if (typeof tag !== "string") throw new MnemocyteError("tags must contain only strings.", "VALIDATION");
|
|
424
|
+
}
|
|
425
|
+
if (input.source !== void 0 && typeof input.source !== "string") throw new MnemocyteError("source must be a string.", "VALIDATION");
|
|
426
|
+
if (input.confidence !== void 0 && (!Number.isFinite(input.confidence) || input.confidence < 0 || input.confidence > 1)) throw new MnemocyteError("confidence must be a number between 0 and 1.", "VALIDATION");
|
|
427
|
+
if (input.expiresAt !== void 0) assertValidDate(input.expiresAt, "expiresAt");
|
|
428
|
+
}
|
|
429
|
+
function validateRecallInput(input) {
|
|
430
|
+
assertNonEmptyString(input.entityId, "entityId");
|
|
431
|
+
assertNonEmptyString(input.query, "query");
|
|
432
|
+
if (input.limit !== void 0) assertLimit(input.limit);
|
|
433
|
+
if (input.minScore !== void 0) assertMinScore(input.minScore);
|
|
434
|
+
if (input.types !== void 0) assertMemoryTypeArray(input.types, "types");
|
|
435
|
+
}
|
|
436
|
+
function contextInputToRecallInput(input) {
|
|
437
|
+
return {
|
|
438
|
+
entityId: input.entityId,
|
|
439
|
+
query: input.query,
|
|
440
|
+
...input.limit === void 0 ? {} : { limit: input.limit },
|
|
441
|
+
...input.minScore === void 0 ? {} : { minScore: input.minScore },
|
|
442
|
+
...input.types === void 0 ? {} : { types: input.types },
|
|
443
|
+
...input.tags === void 0 ? {} : { tags: input.tags },
|
|
444
|
+
...input.includeSuperseded === void 0 ? {} : { includeSuperseded: input.includeSuperseded },
|
|
445
|
+
...input.includeExpired === void 0 ? {} : { includeExpired: input.includeExpired }
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Validate and normalize a {@link PruneInput}. Throws a `"VALIDATION"`
|
|
450
|
+
* {@link MnemocyteError} for malformed fields or when normalization leaves no
|
|
451
|
+
* effective selector, preventing surprise full-table deletes.
|
|
452
|
+
*/
|
|
453
|
+
function validatePruneInput(input) {
|
|
454
|
+
if (input.entityId !== void 0) assertNonEmptyString(input.entityId, "entityId");
|
|
455
|
+
for (const [field, value] of [
|
|
456
|
+
["expired", input.expired],
|
|
457
|
+
["superseded", input.superseded],
|
|
458
|
+
["dryRun", input.dryRun]
|
|
459
|
+
]) if (value !== void 0 && typeof value !== "boolean") throw new MnemocyteError(`${field} must be a boolean.`, "VALIDATION");
|
|
460
|
+
for (const [field, value] of [["createdBefore", input.createdBefore], ["notAccessedSince", input.notAccessedSince]]) if (value !== void 0) assertValidDate(value, field);
|
|
461
|
+
let types;
|
|
462
|
+
if (input.types !== void 0) {
|
|
463
|
+
assertMemoryTypeArray(input.types, "types");
|
|
464
|
+
const normalized = new Set(input.types);
|
|
465
|
+
if (normalized.size > 0) types = [...normalized];
|
|
466
|
+
}
|
|
467
|
+
let tags;
|
|
468
|
+
if (input.tags !== void 0) {
|
|
469
|
+
if (!Array.isArray(input.tags)) throw new MnemocyteError("tags must be an array.", "VALIDATION");
|
|
470
|
+
const normalized = /* @__PURE__ */ new Set();
|
|
471
|
+
for (const value of input.tags) {
|
|
472
|
+
if (typeof value !== "string" || value.trim().length === 0) throw new MnemocyteError("tags must contain only non-empty strings.", "VALIDATION");
|
|
473
|
+
normalized.add(value.trim());
|
|
474
|
+
}
|
|
475
|
+
if (normalized.size > 0) tags = [...normalized];
|
|
476
|
+
}
|
|
477
|
+
if (input.maxImportance !== void 0) assertImportanceLevel(input.maxImportance, "maxImportance");
|
|
478
|
+
if (input.signal !== void 0 && (typeof input.signal !== "object" || input.signal === null || typeof input.signal.aborted !== "boolean" || typeof input.signal.addEventListener !== "function" || typeof input.signal.removeEventListener !== "function")) throw new MnemocyteError("signal must be an AbortSignal.", "VALIDATION");
|
|
479
|
+
const filter = {
|
|
480
|
+
...input.entityId === void 0 ? {} : { entityId: input.entityId },
|
|
481
|
+
...input.expired === true ? { expired: true } : {},
|
|
482
|
+
...input.superseded === true ? { superseded: true } : {},
|
|
483
|
+
...input.createdBefore === void 0 ? {} : { createdBefore: new Date(input.createdBefore) },
|
|
484
|
+
...input.notAccessedSince === void 0 ? {} : { notAccessedSince: new Date(input.notAccessedSince) },
|
|
485
|
+
...types === void 0 ? {} : { types },
|
|
486
|
+
...tags === void 0 ? {} : { tags },
|
|
487
|
+
...input.maxImportance === void 0 ? {} : { maxImportance: input.maxImportance },
|
|
488
|
+
dryRun: input.dryRun === true
|
|
489
|
+
};
|
|
490
|
+
assertPruneFilterHasSelector(filter);
|
|
491
|
+
return filter;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Validate a {@link FindDuplicatesInput} and throw a `"VALIDATION"`
|
|
495
|
+
* {@link MnemocyteError} for malformed `threshold` / `limit` values.
|
|
496
|
+
*/
|
|
497
|
+
function validateFindDuplicatesInput(input) {
|
|
498
|
+
assertNonEmptyString(input.entityId, "entityId");
|
|
499
|
+
if (input.threshold !== void 0) {
|
|
500
|
+
if (!Number.isFinite(input.threshold) || input.threshold < 0 || input.threshold > 1) throw new MnemocyteError("threshold must be a number between 0 and 1.", "VALIDATION");
|
|
501
|
+
}
|
|
502
|
+
if (input.limit !== void 0) assertLimit(input.limit);
|
|
503
|
+
if (input.types !== void 0) assertMemoryTypeArray(input.types, "types");
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Validate a {@link ConsolidateInput} and throw a `"VALIDATION"`
|
|
507
|
+
* {@link MnemocyteError} for malformed inputs (empty `supersededIds`
|
|
508
|
+
* a `survivorId` appearing in `supersededIds`, or duplicate loser IDs).
|
|
509
|
+
*/
|
|
510
|
+
function validateConsolidateInput(input) {
|
|
511
|
+
assertNonEmptyString(input.entityId, "entityId");
|
|
512
|
+
assertNonEmptyString(input.survivorId, "survivorId");
|
|
513
|
+
if (input.supersededIds.length === 0) throw new MnemocyteError("supersededIds must contain at least one memory id.", "VALIDATION");
|
|
514
|
+
if (input.supersededIds.includes(input.survivorId)) throw new MnemocyteError("survivorId must not appear in supersededIds.", "VALIDATION");
|
|
515
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
516
|
+
for (const id of input.supersededIds) {
|
|
517
|
+
assertNonEmptyString(id, "supersededIds[*]");
|
|
518
|
+
if (seenIds.has(id)) throw new MnemocyteError("supersededIds must not contain duplicate memory ids.", "VALIDATION");
|
|
519
|
+
seenIds.add(id);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Validate a {@link ListAuditLogInput} and throw a `"VALIDATION"`
|
|
524
|
+
* {@link MnemocyteError} when malformed.
|
|
525
|
+
*/
|
|
526
|
+
function validateListAuditLogInput(input) {
|
|
527
|
+
assertNonEmptyString(input.entityId, "entityId");
|
|
528
|
+
if (input.limit !== void 0) assertLimit(input.limit);
|
|
529
|
+
for (const [field, cursor] of [["beforeCursor", input.beforeCursor], ["afterCursor", input.afterCursor]]) {
|
|
530
|
+
if (cursor === void 0) continue;
|
|
531
|
+
assertAuditLogCursor(cursor, field);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
function assertAuditLogCursor(value, field) {
|
|
535
|
+
if (typeof value !== "object" || value === null) throw new MnemocyteError(`${field} must be an audit-log cursor.`, "VALIDATION");
|
|
536
|
+
const cursor = value;
|
|
537
|
+
assertValidDate(cursor.timestamp, `${field}.timestamp`);
|
|
538
|
+
assertNonEmptyString(cursor.id, `${field}.id`);
|
|
539
|
+
}
|
|
540
|
+
// src/memory/client-core.ts
|
|
541
|
+
function isPositionalRememberManyInput(input) {
|
|
542
|
+
return Array.isArray(input);
|
|
543
|
+
}
|
|
544
|
+
function providerOptions(config, signal) {
|
|
545
|
+
return {
|
|
546
|
+
...signal === void 0 ? {} : { signal },
|
|
547
|
+
...config.provider === void 0 ? {} : { resilience: config.provider }
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
function storeOptions(signal) {
|
|
551
|
+
return signal === void 0 ? void 0 : { signal };
|
|
552
|
+
}
|
|
553
|
+
function snapshotRememberInput(input) {
|
|
554
|
+
return {
|
|
555
|
+
...input,
|
|
556
|
+
...input.tags === void 0 ? {} : { tags: Array.isArray(input.tags) ? [...input.tags] : input.tags },
|
|
557
|
+
metadata: cloneJsonObject(input.metadata ?? {}),
|
|
558
|
+
...input.expiresAt === void 0 ? {} : { expiresAt: input.expiresAt instanceof Date ? new Date(input.expiresAt) : input.expiresAt }
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
function createStoredMemory(config, input, embedding, now) {
|
|
562
|
+
return {
|
|
563
|
+
id: createId(),
|
|
564
|
+
entityId: input.entityId,
|
|
565
|
+
content: input.content,
|
|
566
|
+
type: input.type ?? "fact",
|
|
567
|
+
importance: input.importance ?? "normal",
|
|
568
|
+
tags: normalizeTags(input.tags),
|
|
569
|
+
source: input.source ?? null,
|
|
570
|
+
metadata: input.metadata,
|
|
571
|
+
confidence: input.confidence ?? 1,
|
|
572
|
+
embeddingModel: config.embedder.model,
|
|
573
|
+
embeddingDimensions: config.embedder.dimensions,
|
|
574
|
+
supersededBy: null,
|
|
575
|
+
supersededAt: null,
|
|
576
|
+
expiresAt: input.expiresAt ?? null,
|
|
577
|
+
lastAccessedAt: null,
|
|
578
|
+
accessCount: 0,
|
|
579
|
+
createdAt: now,
|
|
580
|
+
updatedAt: now,
|
|
581
|
+
embedding
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
function auditEvent(entityId, description, metadata, timestamp = /* @__PURE__ */ new Date()) {
|
|
585
|
+
return {
|
|
586
|
+
id: createEventId(),
|
|
587
|
+
entityId,
|
|
588
|
+
description,
|
|
589
|
+
metadata,
|
|
590
|
+
timestamp
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
function normalizeStoreRows(expected, returned, operation) {
|
|
594
|
+
const expectedIds = new Set(expected.map((memory) => memory.id));
|
|
595
|
+
const returnedById = /* @__PURE__ */ new Map();
|
|
596
|
+
for (const row of returned) {
|
|
597
|
+
if (!expectedIds.has(row.id)) throw new MnemocyteError(`${operation} returned an unknown ID.`, "DB");
|
|
598
|
+
if (returnedById.has(row.id)) throw new MnemocyteError(`${operation} returned a duplicate ID.`, "DB");
|
|
599
|
+
returnedById.set(row.id, row);
|
|
600
|
+
}
|
|
601
|
+
return expected.map((row) => {
|
|
602
|
+
const returnedRow = returnedById.get(row.id);
|
|
603
|
+
if (!returnedRow) throw new MnemocyteError(`${operation} did not return every requested ID.`, "DB");
|
|
604
|
+
return returnedRow;
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
function normalizePruneEntityDeletions(result, expectedEntityId) {
|
|
608
|
+
const seenEntityIds = /* @__PURE__ */ new Set();
|
|
609
|
+
let detailedDeletedCount = 0;
|
|
610
|
+
for (const detail of result.deletedByEntity) {
|
|
611
|
+
if (detail.entityId.length === 0 || !Number.isInteger(detail.deletedCount) || detail.deletedCount <= 0 || seenEntityIds.has(detail.entityId) || expectedEntityId !== void 0 && detail.entityId !== expectedEntityId) throw new MnemocyteError("Memory prune returned invalid per-entity deletion details.", "DB");
|
|
612
|
+
seenEntityIds.add(detail.entityId);
|
|
613
|
+
detailedDeletedCount += detail.deletedCount;
|
|
614
|
+
}
|
|
615
|
+
if (detailedDeletedCount !== result.deletedCount || result.dryRun && result.deletedByEntity.length > 0) throw new MnemocyteError("Memory prune deletion details did not match the deleted count.", "DB");
|
|
616
|
+
return [...result.deletedByEntity].sort((a, b) => a.entityId.localeCompare(b.entityId));
|
|
617
|
+
}
|
|
618
|
+
function toDuplicatePair(pair) {
|
|
619
|
+
return {
|
|
620
|
+
a: cloneMemory(pair.a),
|
|
621
|
+
b: cloneMemory(pair.b),
|
|
622
|
+
similarity: Math.max(0, Math.min(1, pair.similarity))
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
function createMemoryClient(config, store) {
|
|
626
|
+
let state = "open";
|
|
627
|
+
let activeOperations = 0;
|
|
628
|
+
let operationsDrainedPromise;
|
|
629
|
+
let resolveOperationsDrained;
|
|
630
|
+
let closePromise;
|
|
631
|
+
function assertOpen() {
|
|
632
|
+
if (state === "closed") throw new MnemocyteError("Mnemocyte client is closed.", "DB");
|
|
633
|
+
}
|
|
634
|
+
function beginOperation() {
|
|
635
|
+
if (state !== "open") throw new MnemocyteError("Mnemocyte client is closed.", "DB");
|
|
636
|
+
activeOperations += 1;
|
|
637
|
+
let completed = false;
|
|
638
|
+
return () => {
|
|
639
|
+
if (completed) return;
|
|
640
|
+
completed = true;
|
|
641
|
+
activeOperations -= 1;
|
|
642
|
+
if (activeOperations === 0 && resolveOperationsDrained) {
|
|
643
|
+
const resolve = resolveOperationsDrained;
|
|
644
|
+
resolveOperationsDrained = void 0;
|
|
645
|
+
operationsDrainedPromise = void 0;
|
|
646
|
+
resolve();
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
function runOperation(operationName, metadata, operation) {
|
|
651
|
+
let complete;
|
|
652
|
+
try {
|
|
653
|
+
complete = beginOperation();
|
|
654
|
+
} catch (error) {
|
|
655
|
+
return observe(config, store.backend, operationName, metadata, async () => {
|
|
656
|
+
throw error;
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
try {
|
|
660
|
+
return operation().then((result) => {
|
|
661
|
+
complete();
|
|
662
|
+
return result;
|
|
663
|
+
}, (error) => {
|
|
664
|
+
complete();
|
|
665
|
+
throw error;
|
|
666
|
+
});
|
|
667
|
+
} catch (error) {
|
|
668
|
+
complete();
|
|
669
|
+
return Promise.reject(error);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
function waitForActiveOperations() {
|
|
673
|
+
if (activeOperations === 0) return Promise.resolve();
|
|
674
|
+
if (!operationsDrainedPromise) operationsDrainedPromise = new Promise((resolve) => {
|
|
675
|
+
resolveOperationsDrained = resolve;
|
|
676
|
+
});
|
|
677
|
+
return operationsDrainedPromise;
|
|
678
|
+
}
|
|
679
|
+
async function ensureSchema() {
|
|
680
|
+
await store.ensureSchema();
|
|
681
|
+
}
|
|
682
|
+
async function ensureEmbeddingCompatibility() {
|
|
683
|
+
await store.ensureEmbeddingCompatibility(config.embedder);
|
|
684
|
+
}
|
|
685
|
+
async function recordAudit(events) {
|
|
686
|
+
if (events.length === 0 || config.audit?.enabled !== true) return;
|
|
687
|
+
try {
|
|
688
|
+
await store.addAuditEvents(events);
|
|
689
|
+
} catch {}
|
|
690
|
+
}
|
|
691
|
+
function remember(input) {
|
|
692
|
+
return runOperation("remember", { entityId: input.entityId }, () => observePrepared(config, store.backend, "remember", { entityId: input.entityId }, () => snapshotRememberInput(input), async (preparedInput) => {
|
|
693
|
+
assertOpen();
|
|
694
|
+
validateRememberInput(preparedInput);
|
|
695
|
+
await ensureEmbeddingCompatibility();
|
|
696
|
+
const stored = [createStoredMemory(config, preparedInput, await embedOne(config.embedder, preparedInput.content, providerOptions(config, preparedInput.signal)), /* @__PURE__ */ new Date())];
|
|
697
|
+
const [memory] = normalizeStoreRows(stored, await store.insertMemories(stored), "Memory insert");
|
|
698
|
+
if (!memory) throw new MnemocyteError("Memory insert did not return the inserted ID.", "DB");
|
|
699
|
+
await recordAudit([auditEvent(memory.entityId, "memory.created", {
|
|
700
|
+
memoryId: memory.id,
|
|
701
|
+
type: memory.type,
|
|
702
|
+
importance: memory.importance
|
|
703
|
+
})]);
|
|
704
|
+
return memory;
|
|
705
|
+
}, (memory) => ({
|
|
706
|
+
memoryId: memory.id,
|
|
707
|
+
count: 1
|
|
708
|
+
})));
|
|
709
|
+
}
|
|
710
|
+
function rememberMany(input) {
|
|
711
|
+
const count = isPositionalRememberManyInput(input) ? input.length : input.inputs.length;
|
|
712
|
+
return runOperation("rememberMany", { count }, () => observePrepared(config, store.backend, "rememberMany", { count }, () => {
|
|
713
|
+
const positional = isPositionalRememberManyInput(input);
|
|
714
|
+
return {
|
|
715
|
+
preparedInputs: (positional ? input : input.inputs).map(snapshotRememberInput),
|
|
716
|
+
signal: positional ? input[0]?.signal : input.signal
|
|
717
|
+
};
|
|
718
|
+
}, async ({ preparedInputs, signal }) => {
|
|
719
|
+
assertOpen();
|
|
720
|
+
if (preparedInputs.length === 0) return [];
|
|
721
|
+
for (const item of preparedInputs) validateRememberInput(item);
|
|
722
|
+
await ensureEmbeddingCompatibility();
|
|
723
|
+
const embeddings = await embedMany(config.embedder, preparedInputs.map((item) => item.content), providerOptions(config, signal));
|
|
724
|
+
const now = /* @__PURE__ */ new Date();
|
|
725
|
+
const stored = preparedInputs.map((item, idx) => createStoredMemory(config, item, embeddings[idx], now));
|
|
726
|
+
const memories = normalizeStoreRows(stored, await store.insertMemories(stored), "Memory insert");
|
|
727
|
+
await recordAudit(memories.map((memory) => auditEvent(memory.entityId, "memory.created", {
|
|
728
|
+
memoryId: memory.id,
|
|
729
|
+
type: memory.type,
|
|
730
|
+
importance: memory.importance
|
|
731
|
+
})));
|
|
732
|
+
return memories;
|
|
733
|
+
}, (result) => ({ count: result.length })));
|
|
734
|
+
}
|
|
735
|
+
function recall(input) {
|
|
736
|
+
return observe(config, store.backend, "recall", { entityId: input.entityId }, async () => {
|
|
737
|
+
assertOpen();
|
|
738
|
+
validateRecallInput(input);
|
|
739
|
+
const limit = input.limit ?? config.defaults?.limit ?? 10;
|
|
740
|
+
const minScore = input.minScore ?? config.defaults?.minScore ?? 0;
|
|
741
|
+
assertLimit(limit);
|
|
742
|
+
assertMinScore(minScore);
|
|
743
|
+
await ensureEmbeddingCompatibility();
|
|
744
|
+
const queryEmbedding = await embedOne(config.embedder, input.query, providerOptions(config, input.signal));
|
|
745
|
+
const candidateMultiplier = config.retrieval?.candidateMultiplier ?? 3;
|
|
746
|
+
const candidateLimit = Math.max(limit, limit * candidateMultiplier);
|
|
747
|
+
const scoreLexical = createLexicalScorer(input.query);
|
|
748
|
+
const scoringConfig = createScoringConfig(config.retrieval);
|
|
749
|
+
const [vectorCandidates, lexicalCandidates] = await Promise.all([store.vectorSearch({
|
|
750
|
+
...input,
|
|
751
|
+
embedding: queryEmbedding,
|
|
752
|
+
limit: candidateLimit,
|
|
753
|
+
minVectorScore: 0
|
|
754
|
+
}), store.lexicalSearch({
|
|
755
|
+
...input,
|
|
756
|
+
limit: candidateLimit
|
|
757
|
+
})]);
|
|
758
|
+
const merged = /* @__PURE__ */ new Map();
|
|
759
|
+
for (const candidate of vectorCandidates) merged.set(candidate.memory.id, {
|
|
760
|
+
memory: candidate.memory,
|
|
761
|
+
vectorScore: candidate.vectorScore,
|
|
762
|
+
lexicalScore: scoreLexical(candidate.memory.content)
|
|
763
|
+
});
|
|
764
|
+
const lexicalOnly = lexicalCandidates.filter((candidate) => !merged.has(candidate.memory.id));
|
|
765
|
+
const lexicalOnlyEmbeddings = await store.getMemoryEmbeddings(lexicalOnly.map((candidate) => candidate.memory.id));
|
|
766
|
+
for (const candidate of lexicalCandidates) {
|
|
767
|
+
const existing = merged.get(candidate.memory.id);
|
|
768
|
+
if (existing) {
|
|
769
|
+
existing.lexicalScore = candidate.lexicalScore;
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
const embedding = lexicalOnlyEmbeddings.get(candidate.memory.id);
|
|
773
|
+
const similarity = embedding ? cosineSimilarity(embedding, queryEmbedding) : 0;
|
|
774
|
+
merged.set(candidate.memory.id, {
|
|
775
|
+
memory: candidate.memory,
|
|
776
|
+
vectorScore: toVectorScore(similarity),
|
|
777
|
+
lexicalScore: candidate.lexicalScore
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
const scored = Array.from(merged.values()).map((entry) => toScoredMemoryWithConfig(entry.memory, Math.max(0, entry.vectorScore), Math.max(0, entry.lexicalScore), input, scoringConfig)).filter((memory) => memory.score >= minScore).sort((a, b) => b.score - a.score).slice(0, limit);
|
|
781
|
+
const accessUpdates = normalizeStoreRows(scored, await store.markMemoriesAccessed(scored.map((memory) => memory.id)), "Memory access update");
|
|
782
|
+
return scored.map((memory, index) => {
|
|
783
|
+
const update = accessUpdates[index];
|
|
784
|
+
if (!update) throw new MnemocyteError("Memory access update did not return every requested ID.", "DB");
|
|
785
|
+
return {
|
|
786
|
+
...memory,
|
|
787
|
+
lastAccessedAt: new Date(update.lastAccessedAt),
|
|
788
|
+
accessCount: update.accessCount,
|
|
789
|
+
updatedAt: new Date(update.updatedAt)
|
|
790
|
+
};
|
|
791
|
+
});
|
|
792
|
+
}, (result) => ({ count: result.length }));
|
|
793
|
+
}
|
|
794
|
+
return {
|
|
795
|
+
remember,
|
|
796
|
+
rememberMany,
|
|
797
|
+
recall(input) {
|
|
798
|
+
return runOperation("recall", { entityId: input.entityId }, () => recall(input));
|
|
799
|
+
},
|
|
800
|
+
buildContext(input) {
|
|
801
|
+
return runOperation("buildContext", { entityId: input.entityId }, () => observe(config, store.backend, "buildContext", { entityId: input.entityId }, async () => {
|
|
802
|
+
assertOpen();
|
|
803
|
+
validateBuildContextInput(input);
|
|
804
|
+
return buildContext({
|
|
805
|
+
input,
|
|
806
|
+
recall: (contextInput) => {
|
|
807
|
+
const recallInput = contextInputToRecallInput(contextInput);
|
|
808
|
+
return recall(input.signal === void 0 ? recallInput : {
|
|
809
|
+
...recallInput,
|
|
810
|
+
signal: input.signal
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
});
|
|
814
|
+
}));
|
|
815
|
+
},
|
|
816
|
+
forget(input) {
|
|
817
|
+
return runOperation("forget", {
|
|
818
|
+
entityId: input.entityId,
|
|
819
|
+
memoryId: input.memoryId
|
|
820
|
+
}, () => observe(config, store.backend, "forget", {
|
|
821
|
+
entityId: input.entityId,
|
|
822
|
+
memoryId: input.memoryId
|
|
823
|
+
}, async () => {
|
|
824
|
+
assertOpen();
|
|
825
|
+
assertNonEmptyString(input.entityId, "entityId");
|
|
826
|
+
assertNonEmptyString(input.memoryId, "memoryId");
|
|
827
|
+
await ensureSchema();
|
|
828
|
+
if (!await store.deleteMemory(input.entityId, input.memoryId)) throw new MnemocyteError("Memory was not found.", "NOT_FOUND");
|
|
829
|
+
await recordAudit([auditEvent(input.entityId, "memory.deleted", { memoryId: input.memoryId })]);
|
|
830
|
+
}));
|
|
831
|
+
},
|
|
832
|
+
forgetAll(input) {
|
|
833
|
+
return runOperation("forgetAll", { entityId: input.entityId }, () => observe(config, store.backend, "forgetAll", { entityId: input.entityId }, async () => {
|
|
834
|
+
assertOpen();
|
|
835
|
+
assertNonEmptyString(input.entityId, "entityId");
|
|
836
|
+
await ensureSchema();
|
|
837
|
+
const deletedCount = await store.deleteMemoriesForEntity(input.entityId);
|
|
838
|
+
await recordAudit([auditEvent(input.entityId, "entity.cleared", { count: deletedCount })]);
|
|
839
|
+
return deletedCount;
|
|
840
|
+
}, (count) => ({ count })).then(() => void 0));
|
|
841
|
+
},
|
|
842
|
+
prune(input) {
|
|
843
|
+
return runOperation("prune", input.entityId === void 0 ? {} : { entityId: input.entityId }, () => observe(config, store.backend, "prune", input.entityId === void 0 ? {} : { entityId: input.entityId }, async () => {
|
|
844
|
+
assertOpen();
|
|
845
|
+
throwIfAborted(input.signal);
|
|
846
|
+
const filter = validatePruneInput(input);
|
|
847
|
+
await ensureSchema();
|
|
848
|
+
const result = await store.prune(filter, storeOptions(input.signal));
|
|
849
|
+
const deletedByEntity = normalizePruneEntityDeletions(result, input.entityId);
|
|
850
|
+
if (result.deletedCount > 0 && result.dryRun === false) await recordAudit(deletedByEntity.map((detail) => auditEvent(detail.entityId, "memory.pruned", { count: detail.deletedCount })));
|
|
851
|
+
return {
|
|
852
|
+
matchedCount: result.matchedCount,
|
|
853
|
+
deletedCount: result.deletedCount,
|
|
854
|
+
dryRun: result.dryRun
|
|
855
|
+
};
|
|
856
|
+
}, (result) => ({ count: result.deletedCount })));
|
|
857
|
+
},
|
|
858
|
+
findDuplicates(input) {
|
|
859
|
+
return runOperation("findDuplicates", { entityId: input.entityId }, () => observe(config, store.backend, "findDuplicates", { entityId: input.entityId }, async () => {
|
|
860
|
+
assertOpen();
|
|
861
|
+
throwIfAborted(input.signal);
|
|
862
|
+
validateFindDuplicatesInput(input);
|
|
863
|
+
await ensureEmbeddingCompatibility();
|
|
864
|
+
return (await store.findDuplicatePairs(input, storeOptions(input.signal))).map(toDuplicatePair);
|
|
865
|
+
}, (result) => ({ count: result.length })));
|
|
866
|
+
},
|
|
867
|
+
listAuditLog(input) {
|
|
868
|
+
return runOperation("listAuditLog", { entityId: input.entityId }, () => observe(config, store.backend, "listAuditLog", { entityId: input.entityId }, async () => {
|
|
869
|
+
assertOpen();
|
|
870
|
+
throwIfAborted(input.signal);
|
|
871
|
+
validateListAuditLogInput(input);
|
|
872
|
+
await ensureSchema();
|
|
873
|
+
return await store.listAuditLog(input, storeOptions(input.signal));
|
|
874
|
+
}, (result) => ({ count: result.length })));
|
|
875
|
+
},
|
|
876
|
+
stats(input) {
|
|
877
|
+
return runOperation("stats", input?.entityId ? { entityId: input.entityId } : {}, () => observe(config, store.backend, "stats", input?.entityId ? { entityId: input.entityId } : {}, async () => {
|
|
878
|
+
assertOpen();
|
|
879
|
+
await ensureSchema();
|
|
880
|
+
return store.stats(input, /* @__PURE__ */ new Date());
|
|
881
|
+
}));
|
|
882
|
+
},
|
|
883
|
+
experimental: { consolidate(input) {
|
|
884
|
+
return runOperation("consolidate", {
|
|
885
|
+
entityId: input.entityId,
|
|
886
|
+
memoryId: input.survivorId
|
|
887
|
+
}, () => observe(config, store.backend, "consolidate", {
|
|
888
|
+
entityId: input.entityId,
|
|
889
|
+
memoryId: input.survivorId
|
|
890
|
+
}, async () => {
|
|
891
|
+
assertOpen();
|
|
892
|
+
throwIfAborted(input.signal);
|
|
893
|
+
validateConsolidateInput(input);
|
|
894
|
+
await ensureSchema();
|
|
895
|
+
const survivor = await store.getMemory(input.entityId, input.survivorId, storeOptions(input.signal));
|
|
896
|
+
throwIfAborted(input.signal);
|
|
897
|
+
if (!survivor) throw new MnemocyteError("Survivor memory was not found.", "NOT_FOUND");
|
|
898
|
+
if (survivor.supersededBy !== null) throw new MnemocyteError("Survivor memory is itself superseded.", "VALIDATION");
|
|
899
|
+
const targets = await store.loadConsolidationTargets(input.entityId, input.supersededIds, storeOptions(input.signal));
|
|
900
|
+
throwIfAborted(input.signal);
|
|
901
|
+
const foundIds = new Set(targets.map((target) => target.id));
|
|
902
|
+
for (const id of input.supersededIds) if (!foundIds.has(id)) throw new MnemocyteError("Superseded memory was not found.", "NOT_FOUND");
|
|
903
|
+
if (targets.some((target) => target.supersededBy !== null && target.supersededBy !== survivor.id)) throw new MnemocyteError("Superseded memory already belongs to a different survivor.", "CONFLICT");
|
|
904
|
+
if (targets.filter((target) => target.supersededBy === null).length === 0) return {
|
|
905
|
+
survivorId: survivor.id,
|
|
906
|
+
supersededCount: 0,
|
|
907
|
+
supersededIds: []
|
|
908
|
+
};
|
|
909
|
+
const result = await store.consolidate({
|
|
910
|
+
entityId: input.entityId,
|
|
911
|
+
survivorId: survivor.id,
|
|
912
|
+
supersededIds: input.supersededIds,
|
|
913
|
+
mergeTags: input.mergeTags !== false,
|
|
914
|
+
now: /* @__PURE__ */ new Date(),
|
|
915
|
+
auditEnabled: config.audit?.enabled === true
|
|
916
|
+
}, storeOptions(input.signal));
|
|
917
|
+
return {
|
|
918
|
+
survivorId: survivor.id,
|
|
919
|
+
supersededCount: result.supersededIds.length,
|
|
920
|
+
supersededIds: result.supersededIds
|
|
921
|
+
};
|
|
922
|
+
}, (result) => ({ count: result.supersededCount })));
|
|
923
|
+
} },
|
|
924
|
+
close() {
|
|
925
|
+
if (closePromise) return closePromise;
|
|
926
|
+
state = "closing";
|
|
927
|
+
closePromise = observe(config, store.backend, "close", {}, async () => {
|
|
928
|
+
await waitForActiveOperations();
|
|
929
|
+
try {
|
|
930
|
+
await store.close();
|
|
931
|
+
state = "closed";
|
|
932
|
+
} catch (error) {
|
|
933
|
+
state = "open";
|
|
934
|
+
closePromise = void 0;
|
|
935
|
+
throw error;
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
return closePromise;
|
|
939
|
+
}
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
// src/memory/filters.ts
|
|
4
943
|
function isExpired(memory, now) {
|
|
5
944
|
return memory.expiresAt !== null && memory.expiresAt.getTime() <= now.getTime();
|
|
6
945
|
}
|
|
@@ -47,8 +986,7 @@ function matchesRecallFilter(memory, input, now) {
|
|
|
47
986
|
if (input.after && memory.createdAt <= input.after) return false;
|
|
48
987
|
return true;
|
|
49
988
|
}
|
|
50
|
-
|
|
51
|
-
//#region src/memory/in-memory.ts
|
|
989
|
+
// src/memory/in-memory.ts
|
|
52
990
|
function cloneAuditEvent(event) {
|
|
53
991
|
return {
|
|
54
992
|
id: event.id,
|
|
@@ -128,7 +1066,7 @@ function createInMemoryStore() {
|
|
|
128
1066
|
async deleteMemory(entityId, memoryId) {
|
|
129
1067
|
const memory = memories.get(memoryId);
|
|
130
1068
|
if (!memory || memory.entityId !== entityId) return false;
|
|
131
|
-
assertNoDependentMemories(memories, new Set([memoryId]));
|
|
1069
|
+
assertNoDependentMemories(memories, /* @__PURE__ */ new Set([memoryId]));
|
|
132
1070
|
return memories.delete(memoryId);
|
|
133
1071
|
},
|
|
134
1072
|
async deleteMemoriesForEntity(entityId) {
|
|
@@ -308,12 +1246,11 @@ function createInMemoryStore() {
|
|
|
308
1246
|
function createInMemoryClient(config) {
|
|
309
1247
|
return createMemoryClient(config, createInMemoryStore());
|
|
310
1248
|
}
|
|
311
|
-
|
|
312
|
-
//#region src/memory/lazy-postgres.ts
|
|
1249
|
+
// src/memory/lazy-postgres.ts
|
|
313
1250
|
function createLazyPostgresStore(databaseUrl) {
|
|
314
1251
|
let storePromise;
|
|
315
1252
|
function loadStore() {
|
|
316
|
-
storePromise ??= import("./postgres-runtime-
|
|
1253
|
+
storePromise ??= import("./postgres-runtime-51fQVwqa.mjs").then(({ createPostgresStoreFromUrl }) => createPostgresStoreFromUrl(databaseUrl));
|
|
317
1254
|
return storePromise;
|
|
318
1255
|
}
|
|
319
1256
|
return {
|
|
@@ -374,8 +1311,7 @@ function createLazyPostgresStore(databaseUrl) {
|
|
|
374
1311
|
}
|
|
375
1312
|
};
|
|
376
1313
|
}
|
|
377
|
-
|
|
378
|
-
//#region src/client.ts
|
|
1314
|
+
// src/client.ts
|
|
379
1315
|
/**
|
|
380
1316
|
* Create a Mnemocyte client.
|
|
381
1317
|
*
|
|
@@ -418,7 +1354,6 @@ function createMnemocyte(config) {
|
|
|
418
1354
|
if (config.store !== void 0) return createMemoryClient(config, unwrapMnemocyteStoreConfig(config.store));
|
|
419
1355
|
return createInMemoryClient(config);
|
|
420
1356
|
}
|
|
421
|
-
//#endregion
|
|
422
1357
|
export { MnemocyteError, createMnemocyte, isMnemocyteError };
|
|
423
1358
|
|
|
424
1359
|
//# sourceMappingURL=index.mjs.map
|