mnemocyte 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -7
- package/dist/embedders/openai.d.mts +1 -1
- package/dist/embedders/openai.mjs +1 -1
- package/dist/{errors-B_7ZdjEO.mjs → errors-DIPysVWl.mjs} +1 -1
- package/dist/{errors-B_7ZdjEO.mjs.map → errors-DIPysVWl.mjs.map} +1 -1
- package/dist/index.d.mts +13 -11
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +49 -2106
- package/dist/index.mjs.map +1 -1
- package/dist/postgres-BHQ1pj16.mjs +913 -0
- package/dist/postgres-BHQ1pj16.mjs.map +1 -0
- package/dist/postgres-runtime-DxJxns07.mjs +9 -0
- package/dist/postgres-runtime-DxJxns07.mjs.map +1 -0
- package/dist/store-config-BFFTm1sj.mjs +1255 -0
- package/dist/store-config-BFFTm1sj.mjs.map +1 -0
- package/dist/stores/drizzle.d.mts +18 -0
- package/dist/stores/drizzle.d.mts.map +1 -0
- package/dist/stores/drizzle.mjs +20 -0
- package/dist/stores/drizzle.mjs.map +1 -0
- package/dist/{types-DH0inEay.d.mts → types-XUWYDoXM.d.mts} +35 -13
- package/dist/types-XUWYDoXM.d.mts.map +1 -0
- package/package.json +7 -3
- package/dist/types-DH0inEay.d.mts.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,1335 +1,5 @@
|
|
|
1
|
-
import { n as isMnemocyteError, t as MnemocyteError } from "./errors-
|
|
2
|
-
import {
|
|
3
|
-
import postgres from "postgres";
|
|
4
|
-
import { and, count, countDistinct, eq, inArray, isNotNull, isNull, lt, or, sql } from "drizzle-orm";
|
|
5
|
-
import { PgDialect, customType, index, integer, jsonb, pgTable, real, text, timestamp } from "drizzle-orm/pg-core";
|
|
6
|
-
//#region \0rolldown/runtime.js
|
|
7
|
-
var __defProp = Object.defineProperty;
|
|
8
|
-
var __exportAll = (all, no_symbols) => {
|
|
9
|
-
let target = {};
|
|
10
|
-
for (var name in all) __defProp(target, name, {
|
|
11
|
-
get: all[name],
|
|
12
|
-
enumerable: true
|
|
13
|
-
});
|
|
14
|
-
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
15
|
-
return target;
|
|
16
|
-
};
|
|
17
|
-
//#endregion
|
|
18
|
-
//#region src/db/vector.ts
|
|
19
|
-
function formatVectorComponent(value) {
|
|
20
|
-
if (!Number.isFinite(value)) throw new Error("Vector values must be finite numbers.");
|
|
21
|
-
return Object.is(value, -0) ? "0" : value.toString();
|
|
22
|
-
}
|
|
23
|
-
//#endregion
|
|
24
|
-
//#region src/db/schema.ts
|
|
25
|
-
var schema_exports = /* @__PURE__ */ __exportAll({
|
|
26
|
-
eventsTable: () => eventsTable,
|
|
27
|
-
memoriesTable: () => memoriesTable,
|
|
28
|
-
metaTable: () => metaTable
|
|
29
|
-
});
|
|
30
|
-
const vector = customType({
|
|
31
|
-
dataType(config) {
|
|
32
|
-
return `vector(${config?.dimensions ?? 1536})`;
|
|
33
|
-
},
|
|
34
|
-
toDriver(value) {
|
|
35
|
-
return `[${value.map(formatVectorComponent).join(",")}]`;
|
|
36
|
-
},
|
|
37
|
-
fromDriver(value) {
|
|
38
|
-
return value.slice(1, -1).split(",").filter(Boolean).map(Number);
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
const memoriesTable = pgTable("mnemocyte_memories", {
|
|
42
|
-
id: text("id").primaryKey(),
|
|
43
|
-
entityId: text("entity_id").notNull(),
|
|
44
|
-
content: text("content").notNull(),
|
|
45
|
-
type: text("type").notNull().default("fact"),
|
|
46
|
-
importance: text("importance").notNull().default("normal"),
|
|
47
|
-
tags: text("tags").array().notNull().default(sql`'{}'::text[]`),
|
|
48
|
-
source: text("source"),
|
|
49
|
-
metadata: jsonb("metadata").$type().notNull().default({}),
|
|
50
|
-
confidence: real("confidence").notNull().default(1),
|
|
51
|
-
embedding: vector("embedding", { dimensions: 1536 }),
|
|
52
|
-
embeddingModel: text("embedding_model").notNull(),
|
|
53
|
-
embeddingDimensions: integer("embedding_dimensions").notNull(),
|
|
54
|
-
supersededBy: text("superseded_by").references(() => memoriesTable.id),
|
|
55
|
-
supersededAt: timestamp("superseded_at", { withTimezone: true }),
|
|
56
|
-
expiresAt: timestamp("expires_at", { withTimezone: true }),
|
|
57
|
-
lastAccessedAt: timestamp("last_accessed_at", { withTimezone: true }),
|
|
58
|
-
accessCount: integer("access_count").notNull().default(0),
|
|
59
|
-
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
60
|
-
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
61
|
-
}, (table) => [
|
|
62
|
-
index("mnemocyte_memories_entity_idx").on(table.entityId),
|
|
63
|
-
index("mnemocyte_memories_entity_type_idx").on(table.entityId, table.type),
|
|
64
|
-
index("mnemocyte_memories_embedding_hnsw_idx").using("hnsw", table.embedding.op("vector_cosine_ops"))
|
|
65
|
-
]);
|
|
66
|
-
const eventsTable = pgTable("mnemocyte_events", {
|
|
67
|
-
id: text("id").primaryKey(),
|
|
68
|
-
entityId: text("entity_id").notNull(),
|
|
69
|
-
description: text("description").notNull(),
|
|
70
|
-
metadata: jsonb("metadata").$type().notNull().default({}),
|
|
71
|
-
timestamp: timestamp("timestamp", { withTimezone: true }).notNull().defaultNow()
|
|
72
|
-
}, (table) => [index("mnemocyte_events_entity_time_idx").on(table.entityId, table.timestamp)]);
|
|
73
|
-
const metaTable = pgTable("mnemocyte_meta", {
|
|
74
|
-
key: text("key").primaryKey(),
|
|
75
|
-
embeddingDimensions: integer("embedding_dimensions").notNull(),
|
|
76
|
-
embeddingModel: text("embedding_model")
|
|
77
|
-
});
|
|
78
|
-
//#endregion
|
|
79
|
-
//#region src/db/index.ts
|
|
80
|
-
function parseSslFromUrl(url) {
|
|
81
|
-
const mode = url.searchParams.get("sslmode");
|
|
82
|
-
if (!mode || mode === "disable") return void 0;
|
|
83
|
-
if (mode === "require" || mode === "prefer" || mode === "allow") return { rejectUnauthorized: false };
|
|
84
|
-
if (mode === "verify-full") return "verify-full";
|
|
85
|
-
}
|
|
86
|
-
function createDatabase(databaseUrl) {
|
|
87
|
-
let url;
|
|
88
|
-
try {
|
|
89
|
-
url = new URL(databaseUrl);
|
|
90
|
-
} catch (error) {
|
|
91
|
-
throw new MnemocyteError("databaseUrl must be a valid Postgres connection URL.", "CONFIG", error);
|
|
92
|
-
}
|
|
93
|
-
if (url.protocol !== "postgres:" && url.protocol !== "postgresql:") throw new MnemocyteError("databaseUrl must use the postgres: or postgresql: protocol.", "CONFIG");
|
|
94
|
-
const ssl = parseSslFromUrl(url);
|
|
95
|
-
const isPooler = url.port === "6543" || url.searchParams.get("pgbouncer") === "true";
|
|
96
|
-
const client = postgres(databaseUrl, {
|
|
97
|
-
max: 10,
|
|
98
|
-
idle_timeout: 20,
|
|
99
|
-
connect_timeout: 10,
|
|
100
|
-
...ssl ? { ssl } : {},
|
|
101
|
-
...isPooler ? { prepare: false } : {}
|
|
102
|
-
});
|
|
103
|
-
return {
|
|
104
|
-
db: drizzle(client, { schema: schema_exports }),
|
|
105
|
-
async close() {
|
|
106
|
-
await client.end();
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
//#endregion
|
|
111
|
-
//#region src/resilience.ts
|
|
112
|
-
/**
|
|
113
|
-
* Default per-provider-call timeout. Disabled by default so that callers
|
|
114
|
-
* who do not configure {@link ProviderResilienceConfig.timeoutMs} are
|
|
115
|
-
* unaffected.
|
|
116
|
-
*/
|
|
117
|
-
const DEFAULT_TIMEOUT_MS = 0;
|
|
118
|
-
/** Default number of retry attempts on transient provider failures. */
|
|
119
|
-
const DEFAULT_RETRIES = 0;
|
|
120
|
-
/** Default base backoff delay used between retry attempts. */
|
|
121
|
-
const DEFAULT_BASE_DELAY_MS = 100;
|
|
122
|
-
/** Default ceiling for retry backoff delay. */
|
|
123
|
-
const DEFAULT_MAX_DELAY_MS = 2e3;
|
|
124
|
-
/**
|
|
125
|
-
* Throw a {@link MnemocyteError} with code `"ABORTED"` if `signal` is
|
|
126
|
-
* already aborted. Safe to call with `undefined`.
|
|
127
|
-
*/
|
|
128
|
-
function throwIfAborted(signal) {
|
|
129
|
-
if (signal?.aborted) throw new MnemocyteError("Operation was aborted.", "ABORTED", signal.reason);
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Heuristic default for whether an error should be retried.
|
|
133
|
-
*
|
|
134
|
-
* Conservative by design: only retries on common transient failure
|
|
135
|
-
* indicators (network errors, 5xx-ish messages, `ECONN*` etc.). Never
|
|
136
|
-
* retries on validation, configuration, or {@link MnemocyteError} of
|
|
137
|
-
* code `"VALIDATION"` / `"CONFIG"` / `"CONFLICT"` / `"ABORTED"`.
|
|
138
|
-
*/
|
|
139
|
-
function defaultShouldRetry(error) {
|
|
140
|
-
if (error instanceof MnemocyteError) {
|
|
141
|
-
if (error.code === "VALIDATION" || error.code === "CONFIG" || error.code === "CONFLICT" || error.code === "ABORTED") return false;
|
|
142
|
-
return true;
|
|
143
|
-
}
|
|
144
|
-
if (typeof error === "object" && error !== null && "status" in error && typeof error.status === "number" && [
|
|
145
|
-
429,
|
|
146
|
-
500,
|
|
147
|
-
502,
|
|
148
|
-
503,
|
|
149
|
-
504
|
|
150
|
-
].includes(error.status)) return true;
|
|
151
|
-
if (error instanceof Error) {
|
|
152
|
-
const message = error.message.toLowerCase();
|
|
153
|
-
if (message.includes("econn") || message.includes("etimedout") || message.includes("network") || message.includes("timeout") || message.includes("temporarily") || message.includes("rate limit") || message.includes("503") || message.includes("502") || message.includes("504") || message.includes("500")) return true;
|
|
154
|
-
}
|
|
155
|
-
return false;
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Compute the backoff delay for the given retry attempt, using exponential
|
|
159
|
-
* growth capped at {@link ProviderResilienceConfig.maxDelayMs}.
|
|
160
|
-
*
|
|
161
|
-
* `attempt` is 1-indexed: the delay before the first retry uses
|
|
162
|
-
* `attempt = 1`, before the second retry `attempt = 2`, etc.
|
|
163
|
-
*/
|
|
164
|
-
function computeDelayMs(attempt, baseDelayMs, maxDelayMs) {
|
|
165
|
-
const exponential = baseDelayMs * 2 ** (attempt - 1);
|
|
166
|
-
return Math.max(0, Math.min(maxDelayMs, exponential));
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* Promise-based `setTimeout` that rejects with an `"ABORTED"`
|
|
170
|
-
* {@link MnemocyteError} if `signal` aborts while waiting.
|
|
171
|
-
*/
|
|
172
|
-
function delay(ms, signal) {
|
|
173
|
-
if (ms <= 0) {
|
|
174
|
-
throwIfAborted(signal);
|
|
175
|
-
return Promise.resolve();
|
|
176
|
-
}
|
|
177
|
-
return new Promise((resolve, reject) => {
|
|
178
|
-
const timer = setTimeout(() => {
|
|
179
|
-
signal?.removeEventListener("abort", onAbort);
|
|
180
|
-
resolve();
|
|
181
|
-
}, ms);
|
|
182
|
-
const onAbort = () => {
|
|
183
|
-
clearTimeout(timer);
|
|
184
|
-
reject(new MnemocyteError("Operation was aborted.", "ABORTED", signal?.reason));
|
|
185
|
-
};
|
|
186
|
-
if (signal) {
|
|
187
|
-
if (signal.aborted) {
|
|
188
|
-
clearTimeout(timer);
|
|
189
|
-
reject(new MnemocyteError("Operation was aborted.", "ABORTED", signal.reason));
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
function timeoutError(timeoutMs) {
|
|
197
|
-
return new MnemocyteError(`Provider call timed out after ${timeoutMs}ms.`, "TIMEOUT");
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* Run one provider attempt with a signal linked to the caller's signal and
|
|
201
|
-
* Mnemocyte's configured timeout. Timeout aborts are actively propagated to
|
|
202
|
-
* providers that honor `AbortSignal`.
|
|
203
|
-
*/
|
|
204
|
-
function runAttempt(action, timeoutMs, signal) {
|
|
205
|
-
if (timeoutMs <= 0 && !signal) return action(void 0);
|
|
206
|
-
return new Promise((resolve, reject) => {
|
|
207
|
-
let settled = false;
|
|
208
|
-
const controller = new AbortController();
|
|
209
|
-
const timer = timeoutMs > 0 ? setTimeout(() => {
|
|
210
|
-
if (settled) return;
|
|
211
|
-
settled = true;
|
|
212
|
-
signal?.removeEventListener("abort", onAbort);
|
|
213
|
-
controller.abort(timeoutError(timeoutMs));
|
|
214
|
-
reject(timeoutError(timeoutMs));
|
|
215
|
-
}, timeoutMs) : void 0;
|
|
216
|
-
const onAbort = () => {
|
|
217
|
-
if (settled) return;
|
|
218
|
-
settled = true;
|
|
219
|
-
if (timer) clearTimeout(timer);
|
|
220
|
-
controller.abort(signal?.reason);
|
|
221
|
-
reject(new MnemocyteError("Operation was aborted.", "ABORTED", signal?.reason));
|
|
222
|
-
};
|
|
223
|
-
if (signal) {
|
|
224
|
-
if (signal.aborted) {
|
|
225
|
-
if (timer) clearTimeout(timer);
|
|
226
|
-
settled = true;
|
|
227
|
-
controller.abort(signal.reason);
|
|
228
|
-
reject(new MnemocyteError("Operation was aborted.", "ABORTED", signal.reason));
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
232
|
-
}
|
|
233
|
-
action(controller.signal).then((value) => {
|
|
234
|
-
if (settled) return;
|
|
235
|
-
settled = true;
|
|
236
|
-
if (timer) clearTimeout(timer);
|
|
237
|
-
signal?.removeEventListener("abort", onAbort);
|
|
238
|
-
resolve(value);
|
|
239
|
-
}, (error) => {
|
|
240
|
-
if (settled) return;
|
|
241
|
-
settled = true;
|
|
242
|
-
if (timer) clearTimeout(timer);
|
|
243
|
-
signal?.removeEventListener("abort", onAbort);
|
|
244
|
-
reject(error);
|
|
245
|
-
});
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
/**
|
|
249
|
-
* Run `action` with timeout, retry, and abort handling derived from
|
|
250
|
-
* `options.resilience` and `options.signal`. Each retry attempt receives
|
|
251
|
-
* a fresh `AbortSignal` linked to the caller's signal — so callers may
|
|
252
|
-
* abort mid-retry and the in-flight attempt will cancel promptly if the
|
|
253
|
-
* underlying provider honours signals.
|
|
254
|
-
*
|
|
255
|
-
* Errors from {@link MnemocyteError} with code `"VALIDATION"` /
|
|
256
|
-
* `"CONFIG"` / `"CONFLICT"` / `"ABORTED"` are never retried.
|
|
257
|
-
*/
|
|
258
|
-
async function withResilience(action, options = {}) {
|
|
259
|
-
const config = options.resilience ?? {};
|
|
260
|
-
const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
261
|
-
const retries = Math.max(0, config.retries ?? DEFAULT_RETRIES);
|
|
262
|
-
const baseDelayMs = Math.max(0, config.baseDelayMs ?? DEFAULT_BASE_DELAY_MS);
|
|
263
|
-
const maxDelayMs = Math.max(baseDelayMs, config.maxDelayMs ?? DEFAULT_MAX_DELAY_MS);
|
|
264
|
-
const shouldRetry = config.shouldRetry ?? defaultShouldRetry;
|
|
265
|
-
const externalSignal = options.signal;
|
|
266
|
-
throwIfAborted(externalSignal);
|
|
267
|
-
const maxAttempts = retries + 1;
|
|
268
|
-
let lastError;
|
|
269
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
270
|
-
throwIfAborted(externalSignal);
|
|
271
|
-
try {
|
|
272
|
-
return await runAttempt(action, timeoutMs, externalSignal);
|
|
273
|
-
} catch (error) {
|
|
274
|
-
lastError = error;
|
|
275
|
-
if (error instanceof MnemocyteError && error.code === "ABORTED") throw error;
|
|
276
|
-
if (attempt >= maxAttempts) throw error;
|
|
277
|
-
if (!shouldRetry(error, attempt)) throw error;
|
|
278
|
-
await delay(computeDelayMs(attempt, baseDelayMs, maxDelayMs), externalSignal);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
throw lastError;
|
|
282
|
-
}
|
|
283
|
-
//#endregion
|
|
284
|
-
//#region src/memory/json.ts
|
|
285
|
-
function invalidJsonValue(path, cause) {
|
|
286
|
-
return new MnemocyteError(`${path} must contain only JSON-compatible value data.`, "VALIDATION", cause);
|
|
287
|
-
}
|
|
288
|
-
function cloneJsonValue(value, path, ancestors) {
|
|
289
|
-
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
290
|
-
if (typeof value === "number") {
|
|
291
|
-
if (!Number.isFinite(value)) throw invalidJsonValue(path);
|
|
292
|
-
return Object.is(value, -0) ? 0 : value;
|
|
293
|
-
}
|
|
294
|
-
if (typeof value !== "object") throw invalidJsonValue(path);
|
|
295
|
-
if (ancestors.has(value)) throw invalidJsonValue(path);
|
|
296
|
-
ancestors.add(value);
|
|
297
|
-
try {
|
|
298
|
-
if (Array.isArray(value)) {
|
|
299
|
-
if (Object.getOwnPropertySymbols(value).length > 0 || Object.keys(value).some((key) => !/^(0|[1-9]\d*)$/.test(key))) throw invalidJsonValue(path);
|
|
300
|
-
return Array.from({ length: value.length }, (_, index) => {
|
|
301
|
-
if (!Object.hasOwn(value, index)) throw invalidJsonValue(`${path}[${index}]`);
|
|
302
|
-
return cloneJsonValue(value[index], `${path}[${index}]`, ancestors);
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
const prototype = Object.getPrototypeOf(value);
|
|
306
|
-
if (prototype !== Object.prototype && prototype !== null) throw invalidJsonValue(path);
|
|
307
|
-
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
308
|
-
const entries = [];
|
|
309
|
-
for (const key of Reflect.ownKeys(descriptors)) {
|
|
310
|
-
if (typeof key !== "string") throw invalidJsonValue(path);
|
|
311
|
-
const descriptor = descriptors[key];
|
|
312
|
-
if (descriptor === void 0 || descriptor.enumerable !== true || !("value" in descriptor)) throw invalidJsonValue(`${path}.${key}`);
|
|
313
|
-
entries.push([key, cloneJsonValue(descriptor.value, `${path}.${key}`, ancestors)]);
|
|
314
|
-
}
|
|
315
|
-
return Object.fromEntries(entries);
|
|
316
|
-
} finally {
|
|
317
|
-
ancestors.delete(value);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
/** Validate and deep-clone a JSON-compatible object. */
|
|
321
|
-
function cloneJsonObject(value, path = "metadata") {
|
|
322
|
-
try {
|
|
323
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) throw invalidJsonValue(path);
|
|
324
|
-
return cloneJsonValue(value, path, /* @__PURE__ */ new Set());
|
|
325
|
-
} catch (error) {
|
|
326
|
-
if (error instanceof MnemocyteError) throw error;
|
|
327
|
-
throw invalidJsonValue(path, error);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
//#endregion
|
|
331
|
-
//#region src/memory/records.ts
|
|
332
|
-
function normalizeTags(tags) {
|
|
333
|
-
if (!tags) return [];
|
|
334
|
-
return [...new Set(tags.map((tag) => tag.trim()).filter(Boolean))];
|
|
335
|
-
}
|
|
336
|
-
function createId() {
|
|
337
|
-
return `mem_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
338
|
-
}
|
|
339
|
-
/** Generate a unique audit-event id (`evt_*`). */
|
|
340
|
-
function createEventId() {
|
|
341
|
-
return `evt_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
342
|
-
}
|
|
343
|
-
function cloneMemory(memory) {
|
|
344
|
-
return {
|
|
345
|
-
id: memory.id,
|
|
346
|
-
entityId: memory.entityId,
|
|
347
|
-
content: memory.content,
|
|
348
|
-
type: memory.type,
|
|
349
|
-
importance: memory.importance,
|
|
350
|
-
tags: [...memory.tags],
|
|
351
|
-
source: memory.source,
|
|
352
|
-
metadata: cloneJsonObject(memory.metadata),
|
|
353
|
-
confidence: memory.confidence,
|
|
354
|
-
embeddingModel: memory.embeddingModel,
|
|
355
|
-
embeddingDimensions: memory.embeddingDimensions,
|
|
356
|
-
supersededBy: memory.supersededBy,
|
|
357
|
-
supersededAt: memory.supersededAt ? new Date(memory.supersededAt) : null,
|
|
358
|
-
expiresAt: memory.expiresAt ? new Date(memory.expiresAt) : null,
|
|
359
|
-
lastAccessedAt: memory.lastAccessedAt ? new Date(memory.lastAccessedAt) : null,
|
|
360
|
-
accessCount: memory.accessCount,
|
|
361
|
-
createdAt: new Date(memory.createdAt),
|
|
362
|
-
updatedAt: new Date(memory.updatedAt)
|
|
363
|
-
};
|
|
364
|
-
}
|
|
365
|
-
//#endregion
|
|
366
|
-
//#region src/retrieval/scorer.ts
|
|
367
|
-
const IMPORTANCE_SCORES = {
|
|
368
|
-
low: 0,
|
|
369
|
-
normal: .5,
|
|
370
|
-
high: .8,
|
|
371
|
-
critical: 1
|
|
372
|
-
};
|
|
373
|
-
const DEFAULT_RETRIEVAL_WEIGHTS = {
|
|
374
|
-
vector: .55,
|
|
375
|
-
lexical: .2,
|
|
376
|
-
recency: .1,
|
|
377
|
-
confidence: .05,
|
|
378
|
-
access: .05,
|
|
379
|
-
importance: .05
|
|
380
|
-
};
|
|
381
|
-
const DEFAULT_RECENCY_HALF_LIFE_DAYS = 90;
|
|
382
|
-
const DEFAULT_ACCESS_SATURATION = 10;
|
|
383
|
-
function cosineSimilarity(a, b) {
|
|
384
|
-
let dot = 0;
|
|
385
|
-
let aMagnitude = 0;
|
|
386
|
-
let bMagnitude = 0;
|
|
387
|
-
for (let i = 0; i < a.length; i += 1) {
|
|
388
|
-
const av = a[i] ?? 0;
|
|
389
|
-
const bv = b[i] ?? 0;
|
|
390
|
-
dot += av * bv;
|
|
391
|
-
aMagnitude += av * av;
|
|
392
|
-
bMagnitude += bv * bv;
|
|
393
|
-
}
|
|
394
|
-
if (aMagnitude === 0 || bMagnitude === 0) return 0;
|
|
395
|
-
return dot / (Math.sqrt(aMagnitude) * Math.sqrt(bMagnitude));
|
|
396
|
-
}
|
|
397
|
-
function tokenizeQuery(query) {
|
|
398
|
-
return query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
399
|
-
}
|
|
400
|
-
function lexicalScoreWithTerms(content, terms) {
|
|
401
|
-
if (terms.length === 0) return 0;
|
|
402
|
-
const lowerContent = content.toLowerCase();
|
|
403
|
-
return terms.filter((term) => lowerContent.includes(term)).length / terms.length;
|
|
404
|
-
}
|
|
405
|
-
function createLexicalScorer(query) {
|
|
406
|
-
const terms = tokenizeQuery(query);
|
|
407
|
-
return (content) => lexicalScoreWithTerms(content, terms);
|
|
408
|
-
}
|
|
409
|
-
function clampScore(value) {
|
|
410
|
-
if (!Number.isFinite(value)) return 0;
|
|
411
|
-
return Math.max(0, Math.min(1, value));
|
|
412
|
-
}
|
|
413
|
-
function toVectorScore(cosineSimilarity) {
|
|
414
|
-
return clampScore(cosineSimilarity);
|
|
415
|
-
}
|
|
416
|
-
function normalizeWeights(weights) {
|
|
417
|
-
const merged = {
|
|
418
|
-
...DEFAULT_RETRIEVAL_WEIGHTS,
|
|
419
|
-
...weights
|
|
420
|
-
};
|
|
421
|
-
const total = merged.vector + merged.lexical + merged.recency + merged.confidence + merged.access + merged.importance;
|
|
422
|
-
if (total <= 0) return { ...DEFAULT_RETRIEVAL_WEIGHTS };
|
|
423
|
-
return {
|
|
424
|
-
vector: merged.vector / total,
|
|
425
|
-
lexical: merged.lexical / total,
|
|
426
|
-
recency: merged.recency / total,
|
|
427
|
-
confidence: merged.confidence / total,
|
|
428
|
-
access: merged.access / total,
|
|
429
|
-
importance: merged.importance / total
|
|
430
|
-
};
|
|
431
|
-
}
|
|
432
|
-
function createScoringConfig(config) {
|
|
433
|
-
return {
|
|
434
|
-
weights: normalizeWeights(config?.weights),
|
|
435
|
-
recencyHalfLifeDays: config?.recencyHalfLifeDays ?? DEFAULT_RECENCY_HALF_LIFE_DAYS,
|
|
436
|
-
accessSaturation: config?.accessSaturation ?? DEFAULT_ACCESS_SATURATION
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
function recencyScore(createdAt, halfLifeDays) {
|
|
440
|
-
const ageMs = Date.now() - createdAt.getTime();
|
|
441
|
-
const ageDays = Math.max(0, ageMs / 864e5);
|
|
442
|
-
return Math.exp(-Math.LN2 * ageDays / halfLifeDays);
|
|
443
|
-
}
|
|
444
|
-
function accessScore(accessCount, saturation) {
|
|
445
|
-
if (accessCount <= 0) return 0;
|
|
446
|
-
return Math.min(1, Math.log1p(accessCount) / Math.log1p(saturation));
|
|
447
|
-
}
|
|
448
|
-
function toScoredMemoryWithConfig(memory, vector, lexical, input, config) {
|
|
449
|
-
const { weights } = config;
|
|
450
|
-
const recency = recencyScore(memory.createdAt, config.recencyHalfLifeDays);
|
|
451
|
-
const confidence = clampScore(memory.confidence);
|
|
452
|
-
const access = accessScore(memory.accessCount, config.accessSaturation);
|
|
453
|
-
const importance = IMPORTANCE_SCORES[memory.importance];
|
|
454
|
-
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));
|
|
455
|
-
return {
|
|
456
|
-
...cloneMemory(memory),
|
|
457
|
-
score,
|
|
458
|
-
scores: {
|
|
459
|
-
vector: clampScore(vector),
|
|
460
|
-
lexical: clampScore(lexical),
|
|
461
|
-
recency,
|
|
462
|
-
confidence,
|
|
463
|
-
access,
|
|
464
|
-
importance
|
|
465
|
-
},
|
|
466
|
-
explanation: input.explain ? {
|
|
467
|
-
vectorScore: clampScore(vector),
|
|
468
|
-
lexicalScore: clampScore(lexical),
|
|
469
|
-
recencyScore: recency,
|
|
470
|
-
confidenceScore: confidence,
|
|
471
|
-
accessScore: access,
|
|
472
|
-
importanceScore: importance,
|
|
473
|
-
importanceBoost: importance * weights.importance,
|
|
474
|
-
weights,
|
|
475
|
-
finalScore: score
|
|
476
|
-
} : null
|
|
477
|
-
};
|
|
478
|
-
}
|
|
479
|
-
//#endregion
|
|
480
|
-
//#region src/context/formatter.ts
|
|
481
|
-
function escapeXml(value) {
|
|
482
|
-
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
483
|
-
}
|
|
484
|
-
function formatMetadata(memory) {
|
|
485
|
-
const parts = [
|
|
486
|
-
`type=${memory.type}`,
|
|
487
|
-
`importance=${memory.importance}`,
|
|
488
|
-
`score=${memory.score.toFixed(3)}`
|
|
489
|
-
];
|
|
490
|
-
if (memory.source) parts.push(`source=${memory.source}`);
|
|
491
|
-
if (memory.tags.length > 0) parts.push(`tags=${memory.tags.join(",")}`);
|
|
492
|
-
return parts.join("; ");
|
|
493
|
-
}
|
|
494
|
-
function markdownFence(value) {
|
|
495
|
-
const longestBacktickRun = Math.max(0, ...Array.from(value.matchAll(/`+/g), (match) => match[0].length));
|
|
496
|
-
return "`".repeat(Math.max(3, longestBacktickRun + 1));
|
|
497
|
-
}
|
|
498
|
-
function plainFence(values) {
|
|
499
|
-
const longestEqualsRun = Math.max(0, ...values.flatMap((value) => Array.from(value.matchAll(/=+/g), (match) => match[0].length)));
|
|
500
|
-
return "=".repeat(Math.max(8, longestEqualsRun + 1));
|
|
501
|
-
}
|
|
502
|
-
function formatMarkdown(memories, query, omittedCount) {
|
|
503
|
-
const lines = [
|
|
504
|
-
"# Memory Context",
|
|
505
|
-
"",
|
|
506
|
-
`Query: ${query}`,
|
|
507
|
-
"",
|
|
508
|
-
"## Relevant Memories"
|
|
509
|
-
];
|
|
510
|
-
for (const memory of memories) {
|
|
511
|
-
const fence = markdownFence(memory.content);
|
|
512
|
-
lines.push("", `- ${formatMetadata(memory)}`, ` ${fence}text`, ...memory.content.split("\n").map((line) => ` ${line}`), ` ${fence}`);
|
|
513
|
-
}
|
|
514
|
-
if (omittedCount > 0) lines.push("", `[${omittedCount} memories omitted to fit token budget]`);
|
|
515
|
-
return lines.join("\n");
|
|
516
|
-
}
|
|
517
|
-
function formatPlain(memories, query, omittedCount) {
|
|
518
|
-
const formattedMemories = memories.map((memory) => ({
|
|
519
|
-
memory,
|
|
520
|
-
metadata: formatMetadata(memory)
|
|
521
|
-
}));
|
|
522
|
-
const fence = plainFence([query, ...formattedMemories.flatMap(({ memory, metadata }) => [metadata, memory.content])]);
|
|
523
|
-
const lines = [
|
|
524
|
-
"MEMORY CONTEXT",
|
|
525
|
-
"",
|
|
526
|
-
`QUERY: ${query}`,
|
|
527
|
-
"",
|
|
528
|
-
"RELEVANT MEMORIES"
|
|
529
|
-
];
|
|
530
|
-
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}`);
|
|
531
|
-
if (omittedCount > 0) lines.push("", `[${omittedCount} memories omitted to fit token budget]`);
|
|
532
|
-
return lines.join("\n");
|
|
533
|
-
}
|
|
534
|
-
function formatXml(memories, query, omittedCount) {
|
|
535
|
-
const lines = [`<memory_context query="${escapeXml(query)}">`];
|
|
536
|
-
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>");
|
|
537
|
-
if (omittedCount > 0) lines.push(` <omitted count="${omittedCount}" reason="token_budget" />`);
|
|
538
|
-
lines.push("</memory_context>");
|
|
539
|
-
return lines.join("\n");
|
|
540
|
-
}
|
|
541
|
-
function formatContext(memories, query, format, omittedCount = 0) {
|
|
542
|
-
switch (format) {
|
|
543
|
-
case "markdown": return formatMarkdown(memories, query, omittedCount);
|
|
544
|
-
case "plain": return formatPlain(memories, query, omittedCount);
|
|
545
|
-
case "xml": return formatXml(memories, query, omittedCount);
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
//#endregion
|
|
549
|
-
//#region src/context/tokens.ts
|
|
550
|
-
const TRUNCATION_MARKER = "[truncated to fit token budget]";
|
|
551
|
-
const heuristicTokenCounter = { count(text) {
|
|
552
|
-
return Math.ceil(text.length / 4);
|
|
553
|
-
} };
|
|
554
|
-
function trimToTokenBudget(text, maxTokens, tokenCounter) {
|
|
555
|
-
if (maxTokens < 1 || tokenCounter.count(text) <= maxTokens) return text;
|
|
556
|
-
let low = 0;
|
|
557
|
-
let high = text.length;
|
|
558
|
-
let best = "";
|
|
559
|
-
while (low <= high) {
|
|
560
|
-
const mid = Math.floor((low + high) / 2);
|
|
561
|
-
const output = `${text.slice(0, mid).trimEnd()}\n${TRUNCATION_MARKER}`;
|
|
562
|
-
if (tokenCounter.count(output) <= maxTokens) {
|
|
563
|
-
best = output;
|
|
564
|
-
low = mid + 1;
|
|
565
|
-
} else high = mid - 1;
|
|
566
|
-
}
|
|
567
|
-
if (best) return best;
|
|
568
|
-
for (let length = 31; length > 0; length -= 1) {
|
|
569
|
-
const markerFragment = TRUNCATION_MARKER.slice(0, length);
|
|
570
|
-
if (tokenCounter.count(markerFragment) <= maxTokens) return markerFragment;
|
|
571
|
-
}
|
|
572
|
-
return "";
|
|
573
|
-
}
|
|
574
|
-
//#endregion
|
|
575
|
-
//#region src/context/builder.ts
|
|
576
|
-
async function buildContext(options) {
|
|
577
|
-
const format = options.input.format ?? "markdown";
|
|
578
|
-
const maxTokens = options.input.maxTokens ?? 1200;
|
|
579
|
-
const tokenCounter = options.input.tokenCounter ?? heuristicTokenCounter;
|
|
580
|
-
const memories = await options.recall(options.input);
|
|
581
|
-
let low = 0;
|
|
582
|
-
let high = memories.length;
|
|
583
|
-
let bestContext = "";
|
|
584
|
-
while (low <= high) {
|
|
585
|
-
const mid = Math.floor((low + high) / 2);
|
|
586
|
-
const omittedCount = memories.length - mid;
|
|
587
|
-
const context = formatContext(memories.slice(0, mid), options.input.query, format, omittedCount);
|
|
588
|
-
if (tokenCounter.count(context) <= maxTokens) {
|
|
589
|
-
bestContext = context;
|
|
590
|
-
low = mid + 1;
|
|
591
|
-
} else high = mid - 1;
|
|
592
|
-
}
|
|
593
|
-
if (bestContext || memories.length === 0) return bestContext;
|
|
594
|
-
return trimToTokenBudget(formatContext([], options.input.query, format, memories.length), maxTokens, tokenCounter);
|
|
595
|
-
}
|
|
596
|
-
//#endregion
|
|
597
|
-
//#region src/observability.ts
|
|
598
|
-
function withMetadata(event, metadata) {
|
|
599
|
-
return {
|
|
600
|
-
...event,
|
|
601
|
-
...metadata.entityId === void 0 ? {} : { entityId: metadata.entityId },
|
|
602
|
-
...metadata.memoryId === void 0 ? {} : { memoryId: metadata.memoryId },
|
|
603
|
-
...metadata.count === void 0 ? {} : { count: metadata.count }
|
|
604
|
-
};
|
|
605
|
-
}
|
|
606
|
-
async function emitObservation(config, event) {
|
|
607
|
-
try {
|
|
608
|
-
await config.observability?.onEvent?.(event);
|
|
609
|
-
} catch {
|
|
610
|
-
return;
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
async function observeFromStartedAt(config, backend, operation, metadata, startedAt, action, successMetadata) {
|
|
614
|
-
await emitObservation(config, withMetadata({
|
|
615
|
-
operation,
|
|
616
|
-
phase: "start",
|
|
617
|
-
backend,
|
|
618
|
-
timestamp: new Date(startedAt)
|
|
619
|
-
}, metadata));
|
|
620
|
-
try {
|
|
621
|
-
const result = await action();
|
|
622
|
-
await emitObservation(config, withMetadata({
|
|
623
|
-
operation,
|
|
624
|
-
phase: "success",
|
|
625
|
-
backend,
|
|
626
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
627
|
-
durationMs: Date.now() - startedAt
|
|
628
|
-
}, {
|
|
629
|
-
...metadata,
|
|
630
|
-
...successMetadata?.(result)
|
|
631
|
-
}));
|
|
632
|
-
return result;
|
|
633
|
-
} catch (error) {
|
|
634
|
-
await emitObservation(config, withMetadata({
|
|
635
|
-
operation,
|
|
636
|
-
phase: "error",
|
|
637
|
-
backend,
|
|
638
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
639
|
-
durationMs: Date.now() - startedAt,
|
|
640
|
-
error
|
|
641
|
-
}, metadata));
|
|
642
|
-
throw error;
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
function observe(config, backend, operation, metadata, action, successMetadata) {
|
|
646
|
-
return observeFromStartedAt(config, backend, operation, metadata, Date.now(), action, successMetadata);
|
|
647
|
-
}
|
|
648
|
-
function observePrepared(config, backend, operation, metadata, prepare, action, successMetadata) {
|
|
649
|
-
const startedAt = Date.now();
|
|
650
|
-
try {
|
|
651
|
-
const prepared = prepare();
|
|
652
|
-
return observeFromStartedAt(config, backend, operation, metadata, startedAt, () => action(prepared), successMetadata);
|
|
653
|
-
} catch (error) {
|
|
654
|
-
return observeFromStartedAt(config, backend, operation, metadata, startedAt, async () => {
|
|
655
|
-
throw error;
|
|
656
|
-
});
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
/**
|
|
660
|
-
* Ordering of {@link ImportanceLevel} from least to most important. Used
|
|
661
|
-
* by `prune({ maxImportance })` to compare a memory's importance against
|
|
662
|
-
* the caller-supplied ceiling.
|
|
663
|
-
*/
|
|
664
|
-
const IMPORTANCE_RANK = {
|
|
665
|
-
low: 0,
|
|
666
|
-
normal: 1,
|
|
667
|
-
high: 2,
|
|
668
|
-
critical: 3
|
|
669
|
-
};
|
|
670
|
-
//#endregion
|
|
671
|
-
//#region src/memory/embeddings.ts
|
|
672
|
-
function validateEmbedding(embedding, dimensions) {
|
|
673
|
-
if (embedding.length !== dimensions) throw new MnemocyteError("Embedder returned an embedding with unexpected dimensions.", "EMBEDDING");
|
|
674
|
-
if (embedding.some((component) => !Number.isFinite(component))) throw new MnemocyteError("Embedder returned an embedding with non-finite values.", "EMBEDDING");
|
|
675
|
-
if (embedding.every((component) => component === 0)) throw new MnemocyteError("Embedder returned an embedding with zero norm.", "EMBEDDING");
|
|
676
|
-
}
|
|
677
|
-
async function embedOne(embedder, text, options = {}) {
|
|
678
|
-
let embeddings;
|
|
679
|
-
try {
|
|
680
|
-
embeddings = await withResilience((signal) => signal === void 0 ? embedder.embed([text]) : embedder.embed([text], { signal }), {
|
|
681
|
-
...options.signal === void 0 ? {} : { signal: options.signal },
|
|
682
|
-
...options.resilience === void 0 ? {} : { resilience: options.resilience }
|
|
683
|
-
});
|
|
684
|
-
} catch (error) {
|
|
685
|
-
if (error instanceof MnemocyteError && (error.code === "TIMEOUT" || error.code === "ABORTED")) throw error;
|
|
686
|
-
throw new MnemocyteError("Failed to embed text.", "EMBEDDING", error);
|
|
687
|
-
}
|
|
688
|
-
const embedding = embeddings[0];
|
|
689
|
-
if (!embedding) throw new MnemocyteError("Embedder returned no embedding.", "EMBEDDING");
|
|
690
|
-
validateEmbedding(embedding, embedder.dimensions);
|
|
691
|
-
return embedding;
|
|
692
|
-
}
|
|
693
|
-
async function embedMany(embedder, texts, options = {}) {
|
|
694
|
-
if (texts.length === 0) return [];
|
|
695
|
-
let embeddings;
|
|
696
|
-
try {
|
|
697
|
-
embeddings = await withResilience((signal) => signal === void 0 ? embedder.embed(texts) : embedder.embed(texts, { signal }), {
|
|
698
|
-
...options.signal === void 0 ? {} : { signal: options.signal },
|
|
699
|
-
...options.resilience === void 0 ? {} : { resilience: options.resilience }
|
|
700
|
-
});
|
|
701
|
-
} catch (error) {
|
|
702
|
-
if (error instanceof MnemocyteError && (error.code === "TIMEOUT" || error.code === "ABORTED")) throw error;
|
|
703
|
-
throw new MnemocyteError("Failed to embed texts.", "EMBEDDING", error);
|
|
704
|
-
}
|
|
705
|
-
if (embeddings.length !== texts.length) throw new MnemocyteError(`Embedder returned ${embeddings.length} embeddings for ${texts.length} texts.`, "EMBEDDING");
|
|
706
|
-
for (const embedding of embeddings) validateEmbedding(embedding, embedder.dimensions);
|
|
707
|
-
return embeddings;
|
|
708
|
-
}
|
|
709
|
-
//#endregion
|
|
710
|
-
//#region src/memory/store.ts
|
|
711
|
-
function hasPruneSelector(filter) {
|
|
712
|
-
return filter.entityId !== void 0 || filter.expired === true || filter.superseded === true || filter.createdBefore !== void 0 || filter.notAccessedSince !== void 0 || filter.types !== void 0 && filter.types.length > 0 || filter.tags !== void 0 && filter.tags.length > 0 || filter.maxImportance !== void 0;
|
|
713
|
-
}
|
|
714
|
-
function assertPruneFilterHasSelector(filter) {
|
|
715
|
-
if (!hasPruneSelector(filter)) throw new MnemocyteError("prune requires at least one selector (entityId, expired, superseded, createdBefore, notAccessedSince, types, tags, or maxImportance).", "VALIDATION");
|
|
716
|
-
}
|
|
717
|
-
//#endregion
|
|
718
|
-
//#region src/memory/validation.ts
|
|
719
|
-
const MEMORY_TYPES = [
|
|
720
|
-
"fact",
|
|
721
|
-
"preference",
|
|
722
|
-
"instruction",
|
|
723
|
-
"backstory",
|
|
724
|
-
"episode",
|
|
725
|
-
"session"
|
|
726
|
-
];
|
|
727
|
-
const IMPORTANCE_LEVELS$1 = [
|
|
728
|
-
"low",
|
|
729
|
-
"normal",
|
|
730
|
-
"high",
|
|
731
|
-
"critical"
|
|
732
|
-
];
|
|
733
|
-
function isMemoryType(value) {
|
|
734
|
-
return typeof value === "string" && MEMORY_TYPES.includes(value);
|
|
735
|
-
}
|
|
736
|
-
function isImportanceLevel(value) {
|
|
737
|
-
return typeof value === "string" && IMPORTANCE_LEVELS$1.includes(value);
|
|
738
|
-
}
|
|
739
|
-
function assertMemoryType(value, field) {
|
|
740
|
-
if (!isMemoryType(value)) throw new MnemocyteError(`${field} must be a known memory type.`, "VALIDATION");
|
|
741
|
-
}
|
|
742
|
-
function assertImportanceLevel(value, field) {
|
|
743
|
-
if (!isImportanceLevel(value)) throw new MnemocyteError(`${field} must be a known importance level.`, "VALIDATION");
|
|
744
|
-
}
|
|
745
|
-
function assertMemoryTypeArray(value, field) {
|
|
746
|
-
if (!Array.isArray(value)) throw new MnemocyteError(`${field} must be an array.`, "VALIDATION");
|
|
747
|
-
for (const item of value) if (!isMemoryType(item)) throw new MnemocyteError(`${field} must contain only known memory types.`, "VALIDATION");
|
|
748
|
-
}
|
|
749
|
-
function assertValidDate(value, field) {
|
|
750
|
-
if (!(value instanceof Date) || !Number.isFinite(value.getTime())) throw new MnemocyteError(`${field} must be a valid Date.`, "VALIDATION");
|
|
751
|
-
}
|
|
752
|
-
function assertNonEmptyString(value, field, code = "VALIDATION") {
|
|
753
|
-
if (typeof value !== "string" || value.trim().length === 0) throw new MnemocyteError(`${field} must be a non-empty string.`, code);
|
|
754
|
-
}
|
|
755
|
-
function assertLimit(value) {
|
|
756
|
-
if (!Number.isInteger(value) || value < 1) throw new MnemocyteError("limit must be a positive integer.", "VALIDATION");
|
|
757
|
-
}
|
|
758
|
-
function assertMinScore(value) {
|
|
759
|
-
if (!Number.isFinite(value) || value < 0 || value > 1) throw new MnemocyteError("minScore must be a number between 0 and 1.", "VALIDATION");
|
|
760
|
-
}
|
|
761
|
-
function assertEmbedder(embedder) {
|
|
762
|
-
if (!embedder || typeof embedder.embed !== "function") throw new MnemocyteError("embedder with an embed function is required.", "CONFIG");
|
|
763
|
-
assertNonEmptyString(embedder.model, "embedder.model", "CONFIG");
|
|
764
|
-
if (!Number.isInteger(embedder.dimensions) || embedder.dimensions < 1) throw new MnemocyteError("embedder.dimensions must be a positive integer.", "CONFIG");
|
|
765
|
-
}
|
|
766
|
-
const RETRIEVAL_WEIGHT_KEYS = [
|
|
767
|
-
"vector",
|
|
768
|
-
"lexical",
|
|
769
|
-
"recency",
|
|
770
|
-
"confidence",
|
|
771
|
-
"access",
|
|
772
|
-
"importance"
|
|
773
|
-
];
|
|
774
|
-
function assertPositiveFiniteConfigValue(value, field) {
|
|
775
|
-
if (!Number.isFinite(value) || value <= 0) throw new MnemocyteError(`${field} must be a positive finite number.`, "CONFIG");
|
|
776
|
-
}
|
|
777
|
-
function validateRetrievalConfig(config) {
|
|
778
|
-
if (config === void 0) return;
|
|
779
|
-
let weightTotal = 0;
|
|
780
|
-
for (const key of RETRIEVAL_WEIGHT_KEYS) {
|
|
781
|
-
const weight = config.weights?.[key] ?? DEFAULT_RETRIEVAL_WEIGHTS[key];
|
|
782
|
-
if (!Number.isFinite(weight) || weight < 0) throw new MnemocyteError(`retrieval.weights.${key} must be a non-negative finite number.`, "CONFIG");
|
|
783
|
-
weightTotal += weight;
|
|
784
|
-
}
|
|
785
|
-
if (weightTotal === 0) throw new MnemocyteError("retrieval.weights must have an effective total greater than zero.", "CONFIG");
|
|
786
|
-
if (config.recencyHalfLifeDays !== void 0) assertPositiveFiniteConfigValue(config.recencyHalfLifeDays, "retrieval.recencyHalfLifeDays");
|
|
787
|
-
if (config.accessSaturation !== void 0) assertPositiveFiniteConfigValue(config.accessSaturation, "retrieval.accessSaturation");
|
|
788
|
-
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");
|
|
789
|
-
}
|
|
790
|
-
function validateProviderResilienceConfig(config) {
|
|
791
|
-
if (config === void 0) return;
|
|
792
|
-
if (typeof config !== "object" || config === null || Array.isArray(config)) throw new MnemocyteError("provider must be an object.", "CONFIG");
|
|
793
|
-
for (const [field, value] of [
|
|
794
|
-
["provider.timeoutMs", config.timeoutMs],
|
|
795
|
-
["provider.baseDelayMs", config.baseDelayMs],
|
|
796
|
-
["provider.maxDelayMs", config.maxDelayMs]
|
|
797
|
-
]) if (value !== void 0 && (!Number.isFinite(value) || value < 0)) throw new MnemocyteError(`${field} must be a non-negative finite number.`, "CONFIG");
|
|
798
|
-
if (config.retries !== void 0 && (!Number.isInteger(config.retries) || config.retries < 0)) throw new MnemocyteError("provider.retries must be a non-negative integer.", "CONFIG");
|
|
799
|
-
if (config.shouldRetry !== void 0 && typeof config.shouldRetry !== "function") throw new MnemocyteError("provider.shouldRetry must be a function.", "CONFIG");
|
|
800
|
-
}
|
|
801
|
-
function validateBuildContextInput(input) {
|
|
802
|
-
if (input.maxTokens !== void 0 && (!Number.isInteger(input.maxTokens) || input.maxTokens < 1)) throw new MnemocyteError("maxTokens must be a positive integer when supplied.", "VALIDATION");
|
|
803
|
-
}
|
|
804
|
-
function validateRememberInput(input) {
|
|
805
|
-
assertNonEmptyString(input.entityId, "entityId");
|
|
806
|
-
assertNonEmptyString(input.content, "content");
|
|
807
|
-
if (input.type !== void 0) assertMemoryType(input.type, "type");
|
|
808
|
-
if (input.importance !== void 0) assertImportanceLevel(input.importance, "importance");
|
|
809
|
-
if (input.tags !== void 0) {
|
|
810
|
-
if (!Array.isArray(input.tags)) throw new MnemocyteError("tags must be an array.", "VALIDATION");
|
|
811
|
-
for (const tag of input.tags) if (typeof tag !== "string") throw new MnemocyteError("tags must contain only strings.", "VALIDATION");
|
|
812
|
-
}
|
|
813
|
-
if (input.source !== void 0 && typeof input.source !== "string") throw new MnemocyteError("source must be a string.", "VALIDATION");
|
|
814
|
-
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");
|
|
815
|
-
if (input.expiresAt !== void 0) assertValidDate(input.expiresAt, "expiresAt");
|
|
816
|
-
}
|
|
817
|
-
function validateRecallInput(input) {
|
|
818
|
-
assertNonEmptyString(input.entityId, "entityId");
|
|
819
|
-
assertNonEmptyString(input.query, "query");
|
|
820
|
-
if (input.limit !== void 0) assertLimit(input.limit);
|
|
821
|
-
if (input.minScore !== void 0) assertMinScore(input.minScore);
|
|
822
|
-
if (input.types !== void 0) assertMemoryTypeArray(input.types, "types");
|
|
823
|
-
}
|
|
824
|
-
function contextInputToRecallInput(input) {
|
|
825
|
-
return {
|
|
826
|
-
entityId: input.entityId,
|
|
827
|
-
query: input.query,
|
|
828
|
-
...input.limit === void 0 ? {} : { limit: input.limit },
|
|
829
|
-
...input.minScore === void 0 ? {} : { minScore: input.minScore },
|
|
830
|
-
...input.types === void 0 ? {} : { types: input.types },
|
|
831
|
-
...input.tags === void 0 ? {} : { tags: input.tags },
|
|
832
|
-
...input.includeSuperseded === void 0 ? {} : { includeSuperseded: input.includeSuperseded },
|
|
833
|
-
...input.includeExpired === void 0 ? {} : { includeExpired: input.includeExpired }
|
|
834
|
-
};
|
|
835
|
-
}
|
|
836
|
-
/**
|
|
837
|
-
* Validate and normalize a {@link PruneInput}. Throws a `"VALIDATION"`
|
|
838
|
-
* {@link MnemocyteError} for malformed fields or when normalization leaves no
|
|
839
|
-
* effective selector, preventing surprise full-table deletes.
|
|
840
|
-
*/
|
|
841
|
-
function validatePruneInput(input) {
|
|
842
|
-
if (input.entityId !== void 0) assertNonEmptyString(input.entityId, "entityId");
|
|
843
|
-
for (const [field, value] of [
|
|
844
|
-
["expired", input.expired],
|
|
845
|
-
["superseded", input.superseded],
|
|
846
|
-
["dryRun", input.dryRun]
|
|
847
|
-
]) if (value !== void 0 && typeof value !== "boolean") throw new MnemocyteError(`${field} must be a boolean.`, "VALIDATION");
|
|
848
|
-
for (const [field, value] of [["createdBefore", input.createdBefore], ["notAccessedSince", input.notAccessedSince]]) if (value !== void 0) assertValidDate(value, field);
|
|
849
|
-
let types;
|
|
850
|
-
if (input.types !== void 0) {
|
|
851
|
-
assertMemoryTypeArray(input.types, "types");
|
|
852
|
-
const normalized = new Set(input.types);
|
|
853
|
-
if (normalized.size > 0) types = [...normalized];
|
|
854
|
-
}
|
|
855
|
-
let tags;
|
|
856
|
-
if (input.tags !== void 0) {
|
|
857
|
-
if (!Array.isArray(input.tags)) throw new MnemocyteError("tags must be an array.", "VALIDATION");
|
|
858
|
-
const normalized = /* @__PURE__ */ new Set();
|
|
859
|
-
for (const value of input.tags) {
|
|
860
|
-
if (typeof value !== "string" || value.trim().length === 0) throw new MnemocyteError("tags must contain only non-empty strings.", "VALIDATION");
|
|
861
|
-
normalized.add(value.trim());
|
|
862
|
-
}
|
|
863
|
-
if (normalized.size > 0) tags = [...normalized];
|
|
864
|
-
}
|
|
865
|
-
if (input.maxImportance !== void 0) assertImportanceLevel(input.maxImportance, "maxImportance");
|
|
866
|
-
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");
|
|
867
|
-
const filter = {
|
|
868
|
-
...input.entityId === void 0 ? {} : { entityId: input.entityId },
|
|
869
|
-
...input.expired === true ? { expired: true } : {},
|
|
870
|
-
...input.superseded === true ? { superseded: true } : {},
|
|
871
|
-
...input.createdBefore === void 0 ? {} : { createdBefore: new Date(input.createdBefore) },
|
|
872
|
-
...input.notAccessedSince === void 0 ? {} : { notAccessedSince: new Date(input.notAccessedSince) },
|
|
873
|
-
...types === void 0 ? {} : { types },
|
|
874
|
-
...tags === void 0 ? {} : { tags },
|
|
875
|
-
...input.maxImportance === void 0 ? {} : { maxImportance: input.maxImportance },
|
|
876
|
-
dryRun: input.dryRun === true
|
|
877
|
-
};
|
|
878
|
-
assertPruneFilterHasSelector(filter);
|
|
879
|
-
return filter;
|
|
880
|
-
}
|
|
881
|
-
/**
|
|
882
|
-
* Validate a {@link FindDuplicatesInput} and throw a `"VALIDATION"`
|
|
883
|
-
* {@link MnemocyteError} for malformed `threshold` / `limit` values.
|
|
884
|
-
*/
|
|
885
|
-
function validateFindDuplicatesInput(input) {
|
|
886
|
-
assertNonEmptyString(input.entityId, "entityId");
|
|
887
|
-
if (input.threshold !== void 0) {
|
|
888
|
-
if (!Number.isFinite(input.threshold) || input.threshold < 0 || input.threshold > 1) throw new MnemocyteError("threshold must be a number between 0 and 1.", "VALIDATION");
|
|
889
|
-
}
|
|
890
|
-
if (input.limit !== void 0) assertLimit(input.limit);
|
|
891
|
-
if (input.types !== void 0) assertMemoryTypeArray(input.types, "types");
|
|
892
|
-
}
|
|
893
|
-
/**
|
|
894
|
-
* Validate a {@link ConsolidateInput} and throw a `"VALIDATION"`
|
|
895
|
-
* {@link MnemocyteError} for malformed inputs (empty `supersededIds`
|
|
896
|
-
* or a `survivorId` appearing in `supersededIds`).
|
|
897
|
-
*/
|
|
898
|
-
function validateConsolidateInput(input) {
|
|
899
|
-
assertNonEmptyString(input.entityId, "entityId");
|
|
900
|
-
assertNonEmptyString(input.survivorId, "survivorId");
|
|
901
|
-
if (input.supersededIds.length === 0) throw new MnemocyteError("supersededIds must contain at least one memory id.", "VALIDATION");
|
|
902
|
-
if (input.supersededIds.includes(input.survivorId)) throw new MnemocyteError("survivorId must not appear in supersededIds.", "VALIDATION");
|
|
903
|
-
for (const id of input.supersededIds) assertNonEmptyString(id, "supersededIds[*]");
|
|
904
|
-
}
|
|
905
|
-
/**
|
|
906
|
-
* Validate a {@link ListAuditLogInput} and throw a `"VALIDATION"`
|
|
907
|
-
* {@link MnemocyteError} when malformed.
|
|
908
|
-
*/
|
|
909
|
-
function validateListAuditLogInput(input) {
|
|
910
|
-
assertNonEmptyString(input.entityId, "entityId");
|
|
911
|
-
if (input.limit !== void 0) assertLimit(input.limit);
|
|
912
|
-
for (const [field, cursor] of [["beforeCursor", input.beforeCursor], ["afterCursor", input.afterCursor]]) {
|
|
913
|
-
if (cursor === void 0) continue;
|
|
914
|
-
assertAuditLogCursor(cursor, field);
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
function assertAuditLogCursor(value, field) {
|
|
918
|
-
if (typeof value !== "object" || value === null) throw new MnemocyteError(`${field} must be an audit-log cursor.`, "VALIDATION");
|
|
919
|
-
const cursor = value;
|
|
920
|
-
assertValidDate(cursor.timestamp, `${field}.timestamp`);
|
|
921
|
-
assertNonEmptyString(cursor.id, `${field}.id`);
|
|
922
|
-
}
|
|
923
|
-
//#endregion
|
|
924
|
-
//#region src/memory/client-core.ts
|
|
925
|
-
function isPositionalRememberManyInput(input) {
|
|
926
|
-
return Array.isArray(input);
|
|
927
|
-
}
|
|
928
|
-
function providerOptions(config, signal) {
|
|
929
|
-
return {
|
|
930
|
-
...signal === void 0 ? {} : { signal },
|
|
931
|
-
...config.provider === void 0 ? {} : { resilience: config.provider }
|
|
932
|
-
};
|
|
933
|
-
}
|
|
934
|
-
function storeOptions(signal) {
|
|
935
|
-
return signal === void 0 ? void 0 : { signal };
|
|
936
|
-
}
|
|
937
|
-
function snapshotRememberInput(input) {
|
|
938
|
-
return {
|
|
939
|
-
...input,
|
|
940
|
-
...input.tags === void 0 ? {} : { tags: Array.isArray(input.tags) ? [...input.tags] : input.tags },
|
|
941
|
-
metadata: cloneJsonObject(input.metadata ?? {}),
|
|
942
|
-
...input.expiresAt === void 0 ? {} : { expiresAt: input.expiresAt instanceof Date ? new Date(input.expiresAt) : input.expiresAt }
|
|
943
|
-
};
|
|
944
|
-
}
|
|
945
|
-
function createStoredMemory(config, input, embedding, now) {
|
|
946
|
-
return {
|
|
947
|
-
id: createId(),
|
|
948
|
-
entityId: input.entityId,
|
|
949
|
-
content: input.content,
|
|
950
|
-
type: input.type ?? "fact",
|
|
951
|
-
importance: input.importance ?? "normal",
|
|
952
|
-
tags: normalizeTags(input.tags),
|
|
953
|
-
source: input.source ?? null,
|
|
954
|
-
metadata: input.metadata,
|
|
955
|
-
confidence: input.confidence ?? 1,
|
|
956
|
-
embeddingModel: config.embedder.model,
|
|
957
|
-
embeddingDimensions: config.embedder.dimensions,
|
|
958
|
-
supersededBy: null,
|
|
959
|
-
supersededAt: null,
|
|
960
|
-
expiresAt: input.expiresAt ?? null,
|
|
961
|
-
lastAccessedAt: null,
|
|
962
|
-
accessCount: 0,
|
|
963
|
-
createdAt: now,
|
|
964
|
-
updatedAt: now,
|
|
965
|
-
embedding
|
|
966
|
-
};
|
|
967
|
-
}
|
|
968
|
-
function auditEvent(entityId, description, metadata, timestamp = /* @__PURE__ */ new Date()) {
|
|
969
|
-
return {
|
|
970
|
-
id: createEventId(),
|
|
971
|
-
entityId,
|
|
972
|
-
description,
|
|
973
|
-
metadata,
|
|
974
|
-
timestamp
|
|
975
|
-
};
|
|
976
|
-
}
|
|
977
|
-
function normalizeStoreRows(expected, returned, operation) {
|
|
978
|
-
const expectedIds = new Set(expected.map((memory) => memory.id));
|
|
979
|
-
const returnedById = /* @__PURE__ */ new Map();
|
|
980
|
-
for (const row of returned) {
|
|
981
|
-
if (!expectedIds.has(row.id)) throw new MnemocyteError(`${operation} returned an unknown ID.`, "DB");
|
|
982
|
-
if (returnedById.has(row.id)) throw new MnemocyteError(`${operation} returned a duplicate ID.`, "DB");
|
|
983
|
-
returnedById.set(row.id, row);
|
|
984
|
-
}
|
|
985
|
-
return expected.map((row) => {
|
|
986
|
-
const returnedRow = returnedById.get(row.id);
|
|
987
|
-
if (!returnedRow) throw new MnemocyteError(`${operation} did not return every requested ID.`, "DB");
|
|
988
|
-
return returnedRow;
|
|
989
|
-
});
|
|
990
|
-
}
|
|
991
|
-
function normalizePruneEntityDeletions(result, expectedEntityId) {
|
|
992
|
-
const seenEntityIds = /* @__PURE__ */ new Set();
|
|
993
|
-
let detailedDeletedCount = 0;
|
|
994
|
-
for (const detail of result.deletedByEntity) {
|
|
995
|
-
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");
|
|
996
|
-
seenEntityIds.add(detail.entityId);
|
|
997
|
-
detailedDeletedCount += detail.deletedCount;
|
|
998
|
-
}
|
|
999
|
-
if (detailedDeletedCount !== result.deletedCount || result.dryRun && result.deletedByEntity.length > 0) throw new MnemocyteError("Memory prune deletion details did not match the deleted count.", "DB");
|
|
1000
|
-
return [...result.deletedByEntity].sort((a, b) => a.entityId.localeCompare(b.entityId));
|
|
1001
|
-
}
|
|
1002
|
-
function toDuplicatePair(pair) {
|
|
1003
|
-
return {
|
|
1004
|
-
a: cloneMemory(pair.a),
|
|
1005
|
-
b: cloneMemory(pair.b),
|
|
1006
|
-
similarity: Math.max(0, Math.min(1, pair.similarity))
|
|
1007
|
-
};
|
|
1008
|
-
}
|
|
1009
|
-
function createMemoryClient(config, store) {
|
|
1010
|
-
let state = "open";
|
|
1011
|
-
let activeOperations = 0;
|
|
1012
|
-
let operationsDrainedPromise;
|
|
1013
|
-
let resolveOperationsDrained;
|
|
1014
|
-
let closePromise;
|
|
1015
|
-
function assertOpen() {
|
|
1016
|
-
if (state === "closed") throw new MnemocyteError("Mnemocyte client is closed.", "DB");
|
|
1017
|
-
}
|
|
1018
|
-
function beginOperation() {
|
|
1019
|
-
if (state !== "open") throw new MnemocyteError("Mnemocyte client is closed.", "DB");
|
|
1020
|
-
activeOperations += 1;
|
|
1021
|
-
let completed = false;
|
|
1022
|
-
return () => {
|
|
1023
|
-
if (completed) return;
|
|
1024
|
-
completed = true;
|
|
1025
|
-
activeOperations -= 1;
|
|
1026
|
-
if (activeOperations === 0 && resolveOperationsDrained) {
|
|
1027
|
-
const resolve = resolveOperationsDrained;
|
|
1028
|
-
resolveOperationsDrained = void 0;
|
|
1029
|
-
operationsDrainedPromise = void 0;
|
|
1030
|
-
resolve();
|
|
1031
|
-
}
|
|
1032
|
-
};
|
|
1033
|
-
}
|
|
1034
|
-
function runOperation(operationName, metadata, operation) {
|
|
1035
|
-
let complete;
|
|
1036
|
-
try {
|
|
1037
|
-
complete = beginOperation();
|
|
1038
|
-
} catch (error) {
|
|
1039
|
-
return observe(config, store.backend, operationName, metadata, async () => {
|
|
1040
|
-
throw error;
|
|
1041
|
-
});
|
|
1042
|
-
}
|
|
1043
|
-
try {
|
|
1044
|
-
return operation().then((result) => {
|
|
1045
|
-
complete();
|
|
1046
|
-
return result;
|
|
1047
|
-
}, (error) => {
|
|
1048
|
-
complete();
|
|
1049
|
-
throw error;
|
|
1050
|
-
});
|
|
1051
|
-
} catch (error) {
|
|
1052
|
-
complete();
|
|
1053
|
-
return Promise.reject(error);
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
function waitForActiveOperations() {
|
|
1057
|
-
if (activeOperations === 0) return Promise.resolve();
|
|
1058
|
-
if (!operationsDrainedPromise) operationsDrainedPromise = new Promise((resolve) => {
|
|
1059
|
-
resolveOperationsDrained = resolve;
|
|
1060
|
-
});
|
|
1061
|
-
return operationsDrainedPromise;
|
|
1062
|
-
}
|
|
1063
|
-
async function ensureSchema() {
|
|
1064
|
-
await store.ensureSchema();
|
|
1065
|
-
}
|
|
1066
|
-
async function ensureEmbeddingCompatibility() {
|
|
1067
|
-
await store.ensureEmbeddingCompatibility(config.embedder);
|
|
1068
|
-
}
|
|
1069
|
-
async function recordAudit(events) {
|
|
1070
|
-
if (events.length === 0 || config.audit?.enabled !== true) return;
|
|
1071
|
-
try {
|
|
1072
|
-
await store.addAuditEvents(events);
|
|
1073
|
-
} catch {}
|
|
1074
|
-
}
|
|
1075
|
-
function remember(input) {
|
|
1076
|
-
return runOperation("remember", { entityId: input.entityId }, () => observePrepared(config, store.backend, "remember", { entityId: input.entityId }, () => snapshotRememberInput(input), async (preparedInput) => {
|
|
1077
|
-
assertOpen();
|
|
1078
|
-
validateRememberInput(preparedInput);
|
|
1079
|
-
await ensureEmbeddingCompatibility();
|
|
1080
|
-
const stored = [createStoredMemory(config, preparedInput, await embedOne(config.embedder, preparedInput.content, providerOptions(config, preparedInput.signal)), /* @__PURE__ */ new Date())];
|
|
1081
|
-
const [memory] = normalizeStoreRows(stored, await store.insertMemories(stored), "Memory insert");
|
|
1082
|
-
if (!memory) throw new MnemocyteError("Memory insert did not return the inserted ID.", "DB");
|
|
1083
|
-
await recordAudit([auditEvent(memory.entityId, "memory.created", {
|
|
1084
|
-
memoryId: memory.id,
|
|
1085
|
-
type: memory.type,
|
|
1086
|
-
importance: memory.importance
|
|
1087
|
-
})]);
|
|
1088
|
-
return memory;
|
|
1089
|
-
}, (memory) => ({
|
|
1090
|
-
memoryId: memory.id,
|
|
1091
|
-
count: 1
|
|
1092
|
-
})));
|
|
1093
|
-
}
|
|
1094
|
-
function rememberMany(input) {
|
|
1095
|
-
const count = isPositionalRememberManyInput(input) ? input.length : input.inputs.length;
|
|
1096
|
-
return runOperation("rememberMany", { count }, () => observePrepared(config, store.backend, "rememberMany", { count }, () => {
|
|
1097
|
-
const positional = isPositionalRememberManyInput(input);
|
|
1098
|
-
return {
|
|
1099
|
-
preparedInputs: (positional ? input : input.inputs).map(snapshotRememberInput),
|
|
1100
|
-
signal: positional ? input[0]?.signal : input.signal
|
|
1101
|
-
};
|
|
1102
|
-
}, async ({ preparedInputs, signal }) => {
|
|
1103
|
-
assertOpen();
|
|
1104
|
-
if (preparedInputs.length === 0) return [];
|
|
1105
|
-
for (const item of preparedInputs) validateRememberInput(item);
|
|
1106
|
-
await ensureEmbeddingCompatibility();
|
|
1107
|
-
const embeddings = await embedMany(config.embedder, preparedInputs.map((item) => item.content), providerOptions(config, signal));
|
|
1108
|
-
const now = /* @__PURE__ */ new Date();
|
|
1109
|
-
const stored = preparedInputs.map((item, idx) => createStoredMemory(config, item, embeddings[idx], now));
|
|
1110
|
-
const memories = normalizeStoreRows(stored, await store.insertMemories(stored), "Memory insert");
|
|
1111
|
-
await recordAudit(memories.map((memory) => auditEvent(memory.entityId, "memory.created", {
|
|
1112
|
-
memoryId: memory.id,
|
|
1113
|
-
type: memory.type,
|
|
1114
|
-
importance: memory.importance
|
|
1115
|
-
})));
|
|
1116
|
-
return memories;
|
|
1117
|
-
}, (result) => ({ count: result.length })));
|
|
1118
|
-
}
|
|
1119
|
-
function recall(input) {
|
|
1120
|
-
return observe(config, store.backend, "recall", { entityId: input.entityId }, async () => {
|
|
1121
|
-
assertOpen();
|
|
1122
|
-
validateRecallInput(input);
|
|
1123
|
-
const limit = input.limit ?? config.defaults?.limit ?? 10;
|
|
1124
|
-
const minScore = input.minScore ?? config.defaults?.minScore ?? 0;
|
|
1125
|
-
assertLimit(limit);
|
|
1126
|
-
assertMinScore(minScore);
|
|
1127
|
-
await ensureEmbeddingCompatibility();
|
|
1128
|
-
const queryEmbedding = await embedOne(config.embedder, input.query, providerOptions(config, input.signal));
|
|
1129
|
-
const candidateMultiplier = config.retrieval?.candidateMultiplier ?? 3;
|
|
1130
|
-
const candidateLimit = Math.max(limit, limit * candidateMultiplier);
|
|
1131
|
-
const scoreLexical = createLexicalScorer(input.query);
|
|
1132
|
-
const scoringConfig = createScoringConfig(config.retrieval);
|
|
1133
|
-
const [vectorCandidates, lexicalCandidates] = await Promise.all([store.vectorSearch({
|
|
1134
|
-
...input,
|
|
1135
|
-
embedding: queryEmbedding,
|
|
1136
|
-
limit: candidateLimit,
|
|
1137
|
-
minVectorScore: 0
|
|
1138
|
-
}), store.lexicalSearch({
|
|
1139
|
-
...input,
|
|
1140
|
-
limit: candidateLimit
|
|
1141
|
-
})]);
|
|
1142
|
-
const merged = /* @__PURE__ */ new Map();
|
|
1143
|
-
for (const candidate of vectorCandidates) merged.set(candidate.memory.id, {
|
|
1144
|
-
memory: candidate.memory,
|
|
1145
|
-
vectorScore: candidate.vectorScore,
|
|
1146
|
-
lexicalScore: scoreLexical(candidate.memory.content)
|
|
1147
|
-
});
|
|
1148
|
-
const lexicalOnly = lexicalCandidates.filter((candidate) => !merged.has(candidate.memory.id));
|
|
1149
|
-
const lexicalOnlyEmbeddings = await store.getMemoryEmbeddings(lexicalOnly.map((candidate) => candidate.memory.id));
|
|
1150
|
-
for (const candidate of lexicalCandidates) {
|
|
1151
|
-
const existing = merged.get(candidate.memory.id);
|
|
1152
|
-
if (existing) {
|
|
1153
|
-
existing.lexicalScore = candidate.lexicalScore;
|
|
1154
|
-
continue;
|
|
1155
|
-
}
|
|
1156
|
-
const embedding = lexicalOnlyEmbeddings.get(candidate.memory.id);
|
|
1157
|
-
const similarity = embedding ? cosineSimilarity(embedding, queryEmbedding) : 0;
|
|
1158
|
-
merged.set(candidate.memory.id, {
|
|
1159
|
-
memory: candidate.memory,
|
|
1160
|
-
vectorScore: toVectorScore(similarity),
|
|
1161
|
-
lexicalScore: candidate.lexicalScore
|
|
1162
|
-
});
|
|
1163
|
-
}
|
|
1164
|
-
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);
|
|
1165
|
-
const accessUpdates = normalizeStoreRows(scored, await store.markMemoriesAccessed(scored.map((memory) => memory.id)), "Memory access update");
|
|
1166
|
-
return scored.map((memory, index) => {
|
|
1167
|
-
const update = accessUpdates[index];
|
|
1168
|
-
if (!update) throw new MnemocyteError("Memory access update did not return every requested ID.", "DB");
|
|
1169
|
-
return {
|
|
1170
|
-
...memory,
|
|
1171
|
-
lastAccessedAt: new Date(update.lastAccessedAt),
|
|
1172
|
-
accessCount: update.accessCount,
|
|
1173
|
-
updatedAt: new Date(update.updatedAt)
|
|
1174
|
-
};
|
|
1175
|
-
});
|
|
1176
|
-
}, (result) => ({ count: result.length }));
|
|
1177
|
-
}
|
|
1178
|
-
return {
|
|
1179
|
-
remember,
|
|
1180
|
-
rememberMany,
|
|
1181
|
-
recall(input) {
|
|
1182
|
-
return runOperation("recall", { entityId: input.entityId }, () => recall(input));
|
|
1183
|
-
},
|
|
1184
|
-
buildContext(input) {
|
|
1185
|
-
return runOperation("buildContext", { entityId: input.entityId }, () => observe(config, store.backend, "buildContext", { entityId: input.entityId }, async () => {
|
|
1186
|
-
assertOpen();
|
|
1187
|
-
validateBuildContextInput(input);
|
|
1188
|
-
return buildContext({
|
|
1189
|
-
input,
|
|
1190
|
-
recall: (contextInput) => {
|
|
1191
|
-
const recallInput = contextInputToRecallInput(contextInput);
|
|
1192
|
-
return recall(input.signal === void 0 ? recallInput : {
|
|
1193
|
-
...recallInput,
|
|
1194
|
-
signal: input.signal
|
|
1195
|
-
});
|
|
1196
|
-
}
|
|
1197
|
-
});
|
|
1198
|
-
}));
|
|
1199
|
-
},
|
|
1200
|
-
forget(input) {
|
|
1201
|
-
return runOperation("forget", {
|
|
1202
|
-
entityId: input.entityId,
|
|
1203
|
-
memoryId: input.memoryId
|
|
1204
|
-
}, () => observe(config, store.backend, "forget", {
|
|
1205
|
-
entityId: input.entityId,
|
|
1206
|
-
memoryId: input.memoryId
|
|
1207
|
-
}, async () => {
|
|
1208
|
-
assertOpen();
|
|
1209
|
-
assertNonEmptyString(input.entityId, "entityId");
|
|
1210
|
-
assertNonEmptyString(input.memoryId, "memoryId");
|
|
1211
|
-
await ensureSchema();
|
|
1212
|
-
if (!await store.deleteMemory(input.entityId, input.memoryId)) throw new MnemocyteError("Memory was not found.", "NOT_FOUND");
|
|
1213
|
-
await recordAudit([auditEvent(input.entityId, "memory.deleted", { memoryId: input.memoryId })]);
|
|
1214
|
-
}));
|
|
1215
|
-
},
|
|
1216
|
-
forgetAll(input) {
|
|
1217
|
-
return runOperation("forgetAll", { entityId: input.entityId }, () => observe(config, store.backend, "forgetAll", { entityId: input.entityId }, async () => {
|
|
1218
|
-
assertOpen();
|
|
1219
|
-
assertNonEmptyString(input.entityId, "entityId");
|
|
1220
|
-
await ensureSchema();
|
|
1221
|
-
const deletedCount = await store.deleteMemoriesForEntity(input.entityId);
|
|
1222
|
-
await recordAudit([auditEvent(input.entityId, "entity.cleared", { count: deletedCount })]);
|
|
1223
|
-
return deletedCount;
|
|
1224
|
-
}, (count) => ({ count })).then(() => void 0));
|
|
1225
|
-
},
|
|
1226
|
-
prune(input) {
|
|
1227
|
-
return runOperation("prune", input.entityId === void 0 ? {} : { entityId: input.entityId }, () => observe(config, store.backend, "prune", input.entityId === void 0 ? {} : { entityId: input.entityId }, async () => {
|
|
1228
|
-
assertOpen();
|
|
1229
|
-
throwIfAborted(input.signal);
|
|
1230
|
-
const filter = validatePruneInput(input);
|
|
1231
|
-
await ensureSchema();
|
|
1232
|
-
const result = await store.prune(filter, storeOptions(input.signal));
|
|
1233
|
-
const deletedByEntity = normalizePruneEntityDeletions(result, input.entityId);
|
|
1234
|
-
if (result.deletedCount > 0 && result.dryRun === false) await recordAudit(deletedByEntity.map((detail) => auditEvent(detail.entityId, "memory.pruned", { count: detail.deletedCount })));
|
|
1235
|
-
return {
|
|
1236
|
-
matchedCount: result.matchedCount,
|
|
1237
|
-
deletedCount: result.deletedCount,
|
|
1238
|
-
dryRun: result.dryRun
|
|
1239
|
-
};
|
|
1240
|
-
}, (result) => ({ count: result.deletedCount })));
|
|
1241
|
-
},
|
|
1242
|
-
findDuplicates(input) {
|
|
1243
|
-
return runOperation("findDuplicates", { entityId: input.entityId }, () => observe(config, store.backend, "findDuplicates", { entityId: input.entityId }, async () => {
|
|
1244
|
-
assertOpen();
|
|
1245
|
-
throwIfAborted(input.signal);
|
|
1246
|
-
validateFindDuplicatesInput(input);
|
|
1247
|
-
await ensureEmbeddingCompatibility();
|
|
1248
|
-
return (await store.findDuplicatePairs(input, storeOptions(input.signal))).map(toDuplicatePair);
|
|
1249
|
-
}, (result) => ({ count: result.length })));
|
|
1250
|
-
},
|
|
1251
|
-
listAuditLog(input) {
|
|
1252
|
-
return runOperation("listAuditLog", { entityId: input.entityId }, () => observe(config, store.backend, "listAuditLog", { entityId: input.entityId }, async () => {
|
|
1253
|
-
assertOpen();
|
|
1254
|
-
throwIfAborted(input.signal);
|
|
1255
|
-
validateListAuditLogInput(input);
|
|
1256
|
-
await ensureSchema();
|
|
1257
|
-
return await store.listAuditLog(input, storeOptions(input.signal));
|
|
1258
|
-
}, (result) => ({ count: result.length })));
|
|
1259
|
-
},
|
|
1260
|
-
stats(input) {
|
|
1261
|
-
return runOperation("stats", input?.entityId ? { entityId: input.entityId } : {}, () => observe(config, store.backend, "stats", input?.entityId ? { entityId: input.entityId } : {}, async () => {
|
|
1262
|
-
assertOpen();
|
|
1263
|
-
await ensureSchema();
|
|
1264
|
-
return store.stats(input, /* @__PURE__ */ new Date());
|
|
1265
|
-
}));
|
|
1266
|
-
},
|
|
1267
|
-
experimental: { consolidate(input) {
|
|
1268
|
-
return runOperation("consolidate", {
|
|
1269
|
-
entityId: input.entityId,
|
|
1270
|
-
memoryId: input.survivorId
|
|
1271
|
-
}, () => observe(config, store.backend, "consolidate", {
|
|
1272
|
-
entityId: input.entityId,
|
|
1273
|
-
memoryId: input.survivorId
|
|
1274
|
-
}, async () => {
|
|
1275
|
-
assertOpen();
|
|
1276
|
-
throwIfAborted(input.signal);
|
|
1277
|
-
validateConsolidateInput(input);
|
|
1278
|
-
await ensureSchema();
|
|
1279
|
-
const survivor = await store.getMemory(input.entityId, input.survivorId, storeOptions(input.signal));
|
|
1280
|
-
throwIfAborted(input.signal);
|
|
1281
|
-
if (!survivor) throw new MnemocyteError("Survivor memory was not found.", "NOT_FOUND");
|
|
1282
|
-
if (survivor.supersededBy !== null) throw new MnemocyteError("Survivor memory is itself superseded.", "VALIDATION");
|
|
1283
|
-
const targets = await store.loadConsolidationTargets(input.entityId, input.supersededIds, storeOptions(input.signal));
|
|
1284
|
-
throwIfAborted(input.signal);
|
|
1285
|
-
const foundIds = new Set(targets.map((target) => target.id));
|
|
1286
|
-
for (const id of input.supersededIds) if (!foundIds.has(id)) throw new MnemocyteError("Superseded memory was not found.", "NOT_FOUND");
|
|
1287
|
-
if (targets.some((target) => target.supersededBy !== null && target.supersededBy !== survivor.id)) throw new MnemocyteError("Superseded memory already belongs to a different survivor.", "CONFLICT");
|
|
1288
|
-
if (targets.filter((target) => target.supersededBy === null).length === 0) return {
|
|
1289
|
-
survivorId: survivor.id,
|
|
1290
|
-
supersededCount: 0,
|
|
1291
|
-
supersededIds: []
|
|
1292
|
-
};
|
|
1293
|
-
const result = await store.consolidate({
|
|
1294
|
-
entityId: input.entityId,
|
|
1295
|
-
survivorId: survivor.id,
|
|
1296
|
-
survivorTags: survivor.tags,
|
|
1297
|
-
supersededIds: input.supersededIds,
|
|
1298
|
-
mergeTags: input.mergeTags !== false,
|
|
1299
|
-
now: /* @__PURE__ */ new Date(),
|
|
1300
|
-
auditEnabled: config.audit?.enabled === true
|
|
1301
|
-
}, storeOptions(input.signal));
|
|
1302
|
-
return {
|
|
1303
|
-
survivorId: survivor.id,
|
|
1304
|
-
supersededCount: result.supersededIds.length,
|
|
1305
|
-
supersededIds: result.supersededIds
|
|
1306
|
-
};
|
|
1307
|
-
}, (result) => ({ count: result.supersededCount })));
|
|
1308
|
-
} },
|
|
1309
|
-
close() {
|
|
1310
|
-
if (closePromise) return closePromise;
|
|
1311
|
-
state = "closing";
|
|
1312
|
-
closePromise = observe(config, store.backend, "close", {}, async () => {
|
|
1313
|
-
await waitForActiveOperations();
|
|
1314
|
-
try {
|
|
1315
|
-
await store.close();
|
|
1316
|
-
state = "closed";
|
|
1317
|
-
} catch (error) {
|
|
1318
|
-
state = "open";
|
|
1319
|
-
closePromise = void 0;
|
|
1320
|
-
throw error;
|
|
1321
|
-
}
|
|
1322
|
-
});
|
|
1323
|
-
return closePromise;
|
|
1324
|
-
}
|
|
1325
|
-
};
|
|
1326
|
-
}
|
|
1327
|
-
//#endregion
|
|
1328
|
-
//#region src/memory/deletion.ts
|
|
1329
|
-
function memoryHasDependentsError(cause) {
|
|
1330
|
-
return new MnemocyteError("Memory cannot be deleted while other memories reference it as their consolidation survivor.", "CONFLICT", cause);
|
|
1331
|
-
}
|
|
1332
|
-
//#endregion
|
|
1
|
+
import { n as isMnemocyteError, t as MnemocyteError } from "./errors-DIPysVWl.mjs";
|
|
2
|
+
import { _ as cloneJsonObject, a as assertEmbedder, c as validateRetrievalConfig, d as IMPORTANCE_RANK, f as cosineSimilarity, g as createEventId, h as cloneMemory, i as createMemoryClient, l as assertPruneFilterHasSelector, m as toVectorScore, n as unwrapMnemocyteStoreConfig, o as assertNonEmptyString, p as createLexicalScorer, r as memoryHasDependentsError, s as validateProviderResilienceConfig, v as throwIfAborted, y as parsePostgresDatabaseUrl } from "./store-config-BFFTm1sj.mjs";
|
|
1333
3
|
//#region src/memory/filters.ts
|
|
1334
4
|
function isExpired(memory, now) {
|
|
1335
5
|
return memory.expiresAt !== null && memory.expiresAt.getTime() <= now.getTime();
|
|
@@ -1565,6 +235,7 @@ function createInMemoryStore() {
|
|
|
1565
235
|
async consolidate(input, options) {
|
|
1566
236
|
throwIfAborted(options?.signal);
|
|
1567
237
|
const survivor = memories.get(input.survivorId);
|
|
238
|
+
if (!survivor || survivor.entityId !== input.entityId || survivor.supersededBy !== null) throw new MnemocyteError("Consolidation survivor changed before mutation.", "CONFLICT");
|
|
1568
239
|
const newlySuperseded = [];
|
|
1569
240
|
const targets = [];
|
|
1570
241
|
for (const id of input.supersededIds) {
|
|
@@ -1576,8 +247,8 @@ function createInMemoryStore() {
|
|
|
1576
247
|
if (targets.some((memory) => memory.supersededBy !== null && memory.supersededBy !== input.survivorId)) throw new MnemocyteError("Superseded memory already belongs to a different survivor.", "CONFLICT");
|
|
1577
248
|
for (const memory of targets) if (memory.supersededBy === null) newlySuperseded.push(memory);
|
|
1578
249
|
let mergedSurvivorTags;
|
|
1579
|
-
if (
|
|
1580
|
-
const mergedTags = new Set(
|
|
250
|
+
if (input.mergeTags && newlySuperseded.length > 0) {
|
|
251
|
+
const mergedTags = new Set(survivor.tags);
|
|
1581
252
|
for (const memory of newlySuperseded) {
|
|
1582
253
|
throwIfAborted(options?.signal);
|
|
1583
254
|
for (const tag of memory.tags) mergedTags.add(tag);
|
|
@@ -1600,7 +271,7 @@ function createInMemoryStore() {
|
|
|
1600
271
|
memory.supersededAt = input.now;
|
|
1601
272
|
memory.updatedAt = input.now;
|
|
1602
273
|
}
|
|
1603
|
-
if (
|
|
274
|
+
if (mergedSurvivorTags) {
|
|
1604
275
|
survivor.tags = mergedSurvivorTags;
|
|
1605
276
|
survivor.updatedAt = input.now;
|
|
1606
277
|
}
|
|
@@ -1638,824 +309,93 @@ function createInMemoryClient(config) {
|
|
|
1638
309
|
return createMemoryClient(config, createInMemoryStore());
|
|
1639
310
|
}
|
|
1640
311
|
//#endregion
|
|
1641
|
-
//#region src/
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
query.cancel();
|
|
1648
|
-
};
|
|
1649
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
1650
|
-
try {
|
|
1651
|
-
return await query;
|
|
1652
|
-
} catch (error) {
|
|
1653
|
-
if (signal.aborted) throw new MnemocyteError("Operation was aborted.", "ABORTED", signal.reason);
|
|
1654
|
-
throw error;
|
|
1655
|
-
} finally {
|
|
1656
|
-
signal.removeEventListener("abort", onAbort);
|
|
1657
|
-
}
|
|
1658
|
-
}
|
|
1659
|
-
async function executeCancelableSql(db, statement, signal) {
|
|
1660
|
-
const query = postgresDialect.sqlToQuery(statement);
|
|
1661
|
-
const client = db.$client;
|
|
1662
|
-
if (!client) throw new MnemocyteError("Cancelable Postgres query requires the postgres.js client.", "DB");
|
|
1663
|
-
return await awaitCancelableQuery(client.unsafe(query.sql, query.params), signal);
|
|
1664
|
-
}
|
|
1665
|
-
//#endregion
|
|
1666
|
-
//#region src/db/queries/events.ts
|
|
1667
|
-
function timestampParam$1(value) {
|
|
1668
|
-
return sql`${value.toISOString()}::timestamptz`;
|
|
1669
|
-
}
|
|
1670
|
-
async function insertEvent(db, row) {
|
|
1671
|
-
const inserted = (await db.insert(eventsTable).values(row).returning())[0];
|
|
1672
|
-
if (!inserted) throw new MnemocyteError("Event insert returned no rows.", "DB");
|
|
1673
|
-
return inserted;
|
|
1674
|
-
}
|
|
1675
|
-
async function listEvents(db, filter, signal) {
|
|
1676
|
-
return executeCancelableSql(db, sql`
|
|
1677
|
-
SELECT
|
|
1678
|
-
id,
|
|
1679
|
-
entity_id AS "entityId",
|
|
1680
|
-
description,
|
|
1681
|
-
metadata,
|
|
1682
|
-
timestamp
|
|
1683
|
-
FROM mnemocyte_events
|
|
1684
|
-
WHERE entity_id = ${filter.entityId}
|
|
1685
|
-
${filter.before ? sql`AND timestamp < ${timestampParam$1(filter.before)}` : sql``}
|
|
1686
|
-
${filter.after ? sql`AND timestamp > ${timestampParam$1(filter.after)}` : sql``}
|
|
1687
|
-
${filter.beforeCursor ? sql`AND (timestamp, id) < (${timestampParam$1(filter.beforeCursor.timestamp)}, ${filter.beforeCursor.id})` : sql``}
|
|
1688
|
-
${filter.afterCursor ? sql`AND (timestamp, id) > (${timestampParam$1(filter.afterCursor.timestamp)}, ${filter.afterCursor.id})` : sql``}
|
|
1689
|
-
ORDER BY timestamp DESC, id DESC
|
|
1690
|
-
LIMIT ${filter.limit ?? 50}
|
|
1691
|
-
`, signal);
|
|
1692
|
-
}
|
|
1693
|
-
//#endregion
|
|
1694
|
-
//#region src/db/queries/memories.ts
|
|
1695
|
-
function vectorLiteral(embedding) {
|
|
1696
|
-
return `[${embedding.map(formatVectorComponent).join(",")}]`;
|
|
1697
|
-
}
|
|
1698
|
-
function clampedVectorScore(embedding) {
|
|
1699
|
-
const rawScore = sql`1 - (embedding <=> ${embedding}::vector)`;
|
|
1700
|
-
return sql`
|
|
1701
|
-
CASE
|
|
1702
|
-
WHEN ${rawScore} = 'NaN'::double precision
|
|
1703
|
-
OR ${rawScore} = 'Infinity'::double precision
|
|
1704
|
-
OR ${rawScore} = '-Infinity'::double precision
|
|
1705
|
-
THEN 0::double precision
|
|
1706
|
-
ELSE GREATEST(
|
|
1707
|
-
0::double precision,
|
|
1708
|
-
LEAST(1::double precision, ${rawScore})
|
|
1709
|
-
)
|
|
1710
|
-
END
|
|
1711
|
-
`;
|
|
1712
|
-
}
|
|
1713
|
-
function timestampParam(value) {
|
|
1714
|
-
return sql`${value.toISOString()}::timestamptz`;
|
|
1715
|
-
}
|
|
1716
|
-
function textArrayLiteral(values) {
|
|
1717
|
-
return sql`ARRAY[${sql.join(values.map((value) => sql`${value}`), sql`, `)}]::text[]`;
|
|
1718
|
-
}
|
|
1719
|
-
function tagsContainAll(tags) {
|
|
1720
|
-
return tags && tags.length > 0 ? sql`${memoriesTable.tags} @> ${textArrayLiteral(tags)}` : void 0;
|
|
1721
|
-
}
|
|
1722
|
-
function rawTagsFilter(tags) {
|
|
1723
|
-
return tags && tags.length > 0 ? sql`AND tags @> ${textArrayLiteral(tags)}` : sql``;
|
|
1724
|
-
}
|
|
1725
|
-
function duplicateTagsFilter(tags) {
|
|
1726
|
-
return tags && tags.length > 0 ? sql`AND a.tags @> ${textArrayLiteral(tags)} AND b.tags @> ${textArrayLiteral(tags)}` : sql``;
|
|
1727
|
-
}
|
|
1728
|
-
function memoryStatsCountFields(now) {
|
|
1729
|
-
return {
|
|
1730
|
-
memoryCount: count(),
|
|
1731
|
-
activeMemoryCount: sql`
|
|
1732
|
-
(count(*) FILTER (
|
|
1733
|
-
WHERE ${memoriesTable.supersededBy} IS NULL
|
|
1734
|
-
AND (${memoriesTable.expiresAt} IS NULL OR ${memoriesTable.expiresAt} > ${timestampParam(now)})
|
|
1735
|
-
))::int
|
|
1736
|
-
`.mapWith(Number),
|
|
1737
|
-
expiredMemoryCount: sql`
|
|
1738
|
-
(count(*) FILTER (
|
|
1739
|
-
WHERE ${memoriesTable.expiresAt} IS NOT NULL
|
|
1740
|
-
AND ${memoriesTable.expiresAt} <= ${timestampParam(now)}
|
|
1741
|
-
))::int
|
|
1742
|
-
`.mapWith(Number),
|
|
1743
|
-
supersededMemoryCount: sql`(count(*) FILTER (WHERE ${memoriesTable.supersededBy} IS NOT NULL))::int`.mapWith(Number)
|
|
1744
|
-
};
|
|
1745
|
-
}
|
|
1746
|
-
function emptyMemoryStatsCounts() {
|
|
1747
|
-
return {
|
|
1748
|
-
memoryCount: 0,
|
|
1749
|
-
activeMemoryCount: 0,
|
|
1750
|
-
expiredMemoryCount: 0,
|
|
1751
|
-
supersededMemoryCount: 0
|
|
1752
|
-
};
|
|
1753
|
-
}
|
|
1754
|
-
async function insertMemories(db, rows) {
|
|
1755
|
-
if (rows.length === 0) return [];
|
|
1756
|
-
return db.insert(memoriesTable).values(rows).returning();
|
|
1757
|
-
}
|
|
1758
|
-
async function getMemoryById(db, entityId, memoryId) {
|
|
1759
|
-
return (await db.select().from(memoriesTable).where(and(eq(memoriesTable.entityId, entityId), eq(memoriesTable.id, memoryId))).limit(1))[0] ?? null;
|
|
1760
|
-
}
|
|
1761
|
-
async function getEntityMemoryStatsCounts(db, entityId, now) {
|
|
1762
|
-
return (await db.select(memoryStatsCountFields(now)).from(memoriesTable).where(eq(memoriesTable.entityId, entityId)))[0] ?? emptyMemoryStatsCounts();
|
|
1763
|
-
}
|
|
1764
|
-
async function getGlobalMemoryStatsCounts(db, now) {
|
|
1765
|
-
return (await db.select({
|
|
1766
|
-
entityCount: countDistinct(memoriesTable.entityId),
|
|
1767
|
-
...memoryStatsCountFields(now)
|
|
1768
|
-
}).from(memoriesTable))[0] ?? {
|
|
1769
|
-
entityCount: 0,
|
|
1770
|
-
...emptyMemoryStatsCounts()
|
|
1771
|
-
};
|
|
1772
|
-
}
|
|
1773
|
-
async function deleteMemory(db, entityId, memoryId) {
|
|
1774
|
-
return deleteMemoriesWithDependentGuard(db, and(eq(memoriesTable.entityId, entityId), eq(memoriesTable.id, memoryId)));
|
|
1775
|
-
}
|
|
1776
|
-
async function deleteMemoriesForEntity(db, entityId) {
|
|
1777
|
-
return deleteMemoriesWithDependentGuard(db, eq(memoriesTable.entityId, entityId));
|
|
1778
|
-
}
|
|
1779
|
-
function pruneConditions(filter) {
|
|
1780
|
-
return and(filter.entityId !== void 0 ? eq(memoriesTable.entityId, filter.entityId) : void 0, filter.expired ? and(isNotNull(memoriesTable.expiresAt), sql`${memoriesTable.expiresAt} <= now()`) : void 0, filter.superseded ? isNotNull(memoriesTable.supersededBy) : void 0, filter.createdBefore ? lt(memoriesTable.createdAt, filter.createdBefore) : void 0, filter.notAccessedSince ? or(isNull(memoriesTable.lastAccessedAt), lt(memoriesTable.lastAccessedAt, filter.notAccessedSince)) : void 0, filter.types && filter.types.length > 0 ? inArray(memoriesTable.type, [...filter.types]) : void 0, tagsContainAll(filter.tags), filter.maxImportanceLevels && filter.maxImportanceLevels.length > 0 ? inArray(memoriesTable.importance, [...filter.maxImportanceLevels]) : void 0);
|
|
1781
|
-
}
|
|
1782
|
-
async function countPruneMatches(db, filter, signal) {
|
|
1783
|
-
const conditions = pruneConditions(filter);
|
|
1784
|
-
const rows = await executeCancelableSql(db, sql`
|
|
1785
|
-
SELECT count(*)::int AS count
|
|
1786
|
-
FROM ${memoriesTable}
|
|
1787
|
-
${conditions ? sql`WHERE ${conditions}` : sql``}
|
|
1788
|
-
`, signal);
|
|
1789
|
-
return Number(rows[0]?.count ?? 0);
|
|
1790
|
-
}
|
|
1791
|
-
async function pruneMemories(db, filter, signal) {
|
|
1792
|
-
return deleteMemoriesWithDependentGuard(db, pruneConditions(filter), signal);
|
|
1793
|
-
}
|
|
1794
|
-
async function deleteMemoriesWithDependentGuard(db, conditions, signal) {
|
|
1795
|
-
const rows = await executeCancelableSql(db, sql`
|
|
1796
|
-
WITH candidates AS MATERIALIZED (
|
|
1797
|
-
SELECT id, entity_id
|
|
1798
|
-
FROM ${memoriesTable}
|
|
1799
|
-
${conditions ? sql`WHERE ${conditions}` : sql``}
|
|
1800
|
-
),
|
|
1801
|
-
dependents AS MATERIALIZED (
|
|
1802
|
-
SELECT 1
|
|
1803
|
-
FROM ${memoriesTable} AS dependent
|
|
1804
|
-
INNER JOIN candidates AS target
|
|
1805
|
-
ON dependent.superseded_by = target.id
|
|
1806
|
-
LIMIT 1
|
|
1807
|
-
),
|
|
1808
|
-
deleted AS (
|
|
1809
|
-
DELETE FROM ${memoriesTable}
|
|
1810
|
-
WHERE ${memoriesTable.id} IN (SELECT id FROM candidates)
|
|
1811
|
-
AND NOT EXISTS (SELECT 1 FROM dependents)
|
|
1812
|
-
RETURNING id, entity_id
|
|
1813
|
-
),
|
|
1814
|
-
deleted_counts AS (
|
|
1815
|
-
SELECT entity_id, count(*)::int AS deleted_count
|
|
1816
|
-
FROM deleted
|
|
1817
|
-
GROUP BY entity_id
|
|
1818
|
-
),
|
|
1819
|
-
summary AS (
|
|
1820
|
-
SELECT
|
|
1821
|
-
(SELECT count(*)::int FROM candidates) AS matched_count,
|
|
1822
|
-
(SELECT count(*)::int FROM deleted) AS deleted_count,
|
|
1823
|
-
EXISTS (SELECT 1 FROM dependents) AS has_dependents
|
|
1824
|
-
)
|
|
1825
|
-
SELECT
|
|
1826
|
-
summary.matched_count AS "matchedCount",
|
|
1827
|
-
summary.deleted_count AS "deletedCount",
|
|
1828
|
-
summary.has_dependents AS "hasDependents",
|
|
1829
|
-
deleted_counts.entity_id AS "entityId",
|
|
1830
|
-
deleted_counts.deleted_count AS "entityDeletedCount"
|
|
1831
|
-
FROM summary
|
|
1832
|
-
LEFT JOIN deleted_counts ON true
|
|
1833
|
-
ORDER BY deleted_counts.entity_id
|
|
1834
|
-
`, signal);
|
|
1835
|
-
const row = rows[0];
|
|
1836
|
-
return {
|
|
1837
|
-
matchedCount: Number(row?.matchedCount ?? 0),
|
|
1838
|
-
deletedCount: Number(row?.deletedCount ?? 0),
|
|
1839
|
-
hasDependents: row?.hasDependents === true,
|
|
1840
|
-
deletedByEntity: rows.flatMap((detail) => detail.entityId === null ? [] : [{
|
|
1841
|
-
entityId: detail.entityId,
|
|
1842
|
-
deletedCount: Number(detail.entityDeletedCount ?? 0)
|
|
1843
|
-
}])
|
|
1844
|
-
};
|
|
1845
|
-
}
|
|
1846
|
-
async function findDuplicatePairs(db, input, signal) {
|
|
1847
|
-
return (await executeCancelableSql(db, sql`
|
|
1848
|
-
SELECT
|
|
1849
|
-
a.id AS "aId",
|
|
1850
|
-
a.entity_id AS "aEntityId",
|
|
1851
|
-
a.content AS "aContent",
|
|
1852
|
-
a.type AS "aType",
|
|
1853
|
-
a.importance AS "aImportance",
|
|
1854
|
-
a.tags AS "aTags",
|
|
1855
|
-
a.source AS "aSource",
|
|
1856
|
-
a.metadata AS "aMetadata",
|
|
1857
|
-
a.confidence AS "aConfidence",
|
|
1858
|
-
a.embedding AS "aEmbedding",
|
|
1859
|
-
a.embedding_model AS "aEmbeddingModel",
|
|
1860
|
-
a.embedding_dimensions AS "aEmbeddingDimensions",
|
|
1861
|
-
a.superseded_by AS "aSupersededBy",
|
|
1862
|
-
a.superseded_at AS "aSupersededAt",
|
|
1863
|
-
a.expires_at AS "aExpiresAt",
|
|
1864
|
-
a.last_accessed_at AS "aLastAccessedAt",
|
|
1865
|
-
a.access_count AS "aAccessCount",
|
|
1866
|
-
a.created_at AS "aCreatedAt",
|
|
1867
|
-
a.updated_at AS "aUpdatedAt",
|
|
1868
|
-
b.id AS "bId",
|
|
1869
|
-
b.entity_id AS "bEntityId",
|
|
1870
|
-
b.content AS "bContent",
|
|
1871
|
-
b.type AS "bType",
|
|
1872
|
-
b.importance AS "bImportance",
|
|
1873
|
-
b.tags AS "bTags",
|
|
1874
|
-
b.source AS "bSource",
|
|
1875
|
-
b.metadata AS "bMetadata",
|
|
1876
|
-
b.confidence AS "bConfidence",
|
|
1877
|
-
b.embedding AS "bEmbedding",
|
|
1878
|
-
b.embedding_model AS "bEmbeddingModel",
|
|
1879
|
-
b.embedding_dimensions AS "bEmbeddingDimensions",
|
|
1880
|
-
b.superseded_by AS "bSupersededBy",
|
|
1881
|
-
b.superseded_at AS "bSupersededAt",
|
|
1882
|
-
b.expires_at AS "bExpiresAt",
|
|
1883
|
-
b.last_accessed_at AS "bLastAccessedAt",
|
|
1884
|
-
b.access_count AS "bAccessCount",
|
|
1885
|
-
b.created_at AS "bCreatedAt",
|
|
1886
|
-
b.updated_at AS "bUpdatedAt",
|
|
1887
|
-
1 - (a.embedding <=> b.embedding) AS "similarity"
|
|
1888
|
-
FROM mnemocyte_memories a
|
|
1889
|
-
JOIN mnemocyte_memories b
|
|
1890
|
-
ON b.entity_id = a.entity_id
|
|
1891
|
-
AND a.id < b.id
|
|
1892
|
-
AND a.embedding IS NOT NULL
|
|
1893
|
-
AND b.embedding IS NOT NULL
|
|
1894
|
-
WHERE
|
|
1895
|
-
a.entity_id = ${input.entityId}
|
|
1896
|
-
${input.includeSuperseded ? sql`` : sql`AND a.superseded_by IS NULL AND b.superseded_by IS NULL`}
|
|
1897
|
-
${input.includeExpired ? sql`` : sql`AND (a.expires_at IS NULL OR a.expires_at > now()) AND (b.expires_at IS NULL OR b.expires_at > now())`}
|
|
1898
|
-
${input.types && input.types.length > 0 ? sql`AND a.type IN (${sql.join(input.types.map((type) => sql`${type}`), sql`, `)}) AND b.type IN (${sql.join(input.types.map((type) => sql`${type}`), sql`, `)})` : sql``}
|
|
1899
|
-
${duplicateTagsFilter(input.tags)}
|
|
1900
|
-
AND 1 - (a.embedding <=> b.embedding) >= ${input.threshold}
|
|
1901
|
-
ORDER BY "similarity" DESC
|
|
1902
|
-
LIMIT ${input.limit}
|
|
1903
|
-
`, signal)).map((row) => {
|
|
1904
|
-
return {
|
|
1905
|
-
a: {
|
|
1906
|
-
id: row.aId,
|
|
1907
|
-
entityId: row.aEntityId,
|
|
1908
|
-
content: row.aContent,
|
|
1909
|
-
type: row.aType,
|
|
1910
|
-
importance: row.aImportance,
|
|
1911
|
-
tags: row.aTags,
|
|
1912
|
-
source: row.aSource,
|
|
1913
|
-
metadata: row.aMetadata,
|
|
1914
|
-
confidence: row.aConfidence,
|
|
1915
|
-
embedding: row.aEmbedding,
|
|
1916
|
-
embeddingModel: row.aEmbeddingModel,
|
|
1917
|
-
embeddingDimensions: row.aEmbeddingDimensions,
|
|
1918
|
-
supersededBy: row.aSupersededBy,
|
|
1919
|
-
supersededAt: row.aSupersededAt,
|
|
1920
|
-
expiresAt: row.aExpiresAt,
|
|
1921
|
-
lastAccessedAt: row.aLastAccessedAt,
|
|
1922
|
-
accessCount: row.aAccessCount,
|
|
1923
|
-
createdAt: row.aCreatedAt,
|
|
1924
|
-
updatedAt: row.aUpdatedAt
|
|
1925
|
-
},
|
|
1926
|
-
b: {
|
|
1927
|
-
id: row.bId,
|
|
1928
|
-
entityId: row.bEntityId,
|
|
1929
|
-
content: row.bContent,
|
|
1930
|
-
type: row.bType,
|
|
1931
|
-
importance: row.bImportance,
|
|
1932
|
-
tags: row.bTags,
|
|
1933
|
-
source: row.bSource,
|
|
1934
|
-
metadata: row.bMetadata,
|
|
1935
|
-
confidence: row.bConfidence,
|
|
1936
|
-
embedding: row.bEmbedding,
|
|
1937
|
-
embeddingModel: row.bEmbeddingModel,
|
|
1938
|
-
embeddingDimensions: row.bEmbeddingDimensions,
|
|
1939
|
-
supersededBy: row.bSupersededBy,
|
|
1940
|
-
supersededAt: row.bSupersededAt,
|
|
1941
|
-
expiresAt: row.bExpiresAt,
|
|
1942
|
-
lastAccessedAt: row.bLastAccessedAt,
|
|
1943
|
-
accessCount: row.bAccessCount,
|
|
1944
|
-
createdAt: row.bCreatedAt,
|
|
1945
|
-
updatedAt: row.bUpdatedAt
|
|
1946
|
-
},
|
|
1947
|
-
similarity: Number(row.similarity) || 0
|
|
1948
|
-
};
|
|
1949
|
-
});
|
|
1950
|
-
}
|
|
1951
|
-
async function loadConsolidationTargets(db, entityId, ids) {
|
|
1952
|
-
if (ids.length === 0) return [];
|
|
1953
|
-
return await db.select({
|
|
1954
|
-
id: memoriesTable.id,
|
|
1955
|
-
tags: memoriesTable.tags,
|
|
1956
|
-
supersededBy: memoriesTable.supersededBy
|
|
1957
|
-
}).from(memoriesTable).where(and(eq(memoriesTable.entityId, entityId), inArray(memoriesTable.id, [...ids])));
|
|
1958
|
-
}
|
|
1959
|
-
async function lockConsolidationTargets(db, entityId, ids) {
|
|
1960
|
-
if (ids.length === 0) return [];
|
|
1961
|
-
return db.select({
|
|
1962
|
-
id: memoriesTable.id,
|
|
1963
|
-
tags: memoriesTable.tags,
|
|
1964
|
-
supersededBy: memoriesTable.supersededBy
|
|
1965
|
-
}).from(memoriesTable).where(and(eq(memoriesTable.entityId, entityId), inArray(memoriesTable.id, [...ids]))).orderBy(memoriesTable.id).for("update");
|
|
1966
|
-
}
|
|
1967
|
-
async function markMemoriesSuperseded(db, params) {
|
|
1968
|
-
if (params.ids.length === 0) return [];
|
|
1969
|
-
return await db.update(memoriesTable).set({
|
|
1970
|
-
supersededBy: params.survivorId,
|
|
1971
|
-
supersededAt: params.now,
|
|
1972
|
-
updatedAt: params.now
|
|
1973
|
-
}).where(and(eq(memoriesTable.entityId, params.entityId), inArray(memoriesTable.id, [...params.ids]), isNull(memoriesTable.supersededBy))).returning({
|
|
1974
|
-
id: memoriesTable.id,
|
|
1975
|
-
tags: memoriesTable.tags
|
|
1976
|
-
});
|
|
1977
|
-
}
|
|
1978
|
-
async function setMemoryTags(db, params) {
|
|
1979
|
-
await db.update(memoriesTable).set({
|
|
1980
|
-
tags: [...params.tags],
|
|
1981
|
-
updatedAt: params.now
|
|
1982
|
-
}).where(and(eq(memoriesTable.entityId, params.entityId), eq(memoriesTable.id, params.memoryId)));
|
|
1983
|
-
}
|
|
1984
|
-
async function markMemoriesAccessed(db, memoryIds) {
|
|
1985
|
-
const now = /* @__PURE__ */ new Date();
|
|
1986
|
-
if (memoryIds.length === 0) return [];
|
|
1987
|
-
return db.update(memoriesTable).set({
|
|
1988
|
-
lastAccessedAt: now,
|
|
1989
|
-
accessCount: sql`${memoriesTable.accessCount} + 1`,
|
|
1990
|
-
updatedAt: now
|
|
1991
|
-
}).where(inArray(memoriesTable.id, [...memoryIds])).returning({
|
|
1992
|
-
id: memoriesTable.id,
|
|
1993
|
-
lastAccessedAt: memoriesTable.lastAccessedAt,
|
|
1994
|
-
accessCount: memoriesTable.accessCount,
|
|
1995
|
-
updatedAt: memoriesTable.updatedAt
|
|
1996
|
-
});
|
|
1997
|
-
}
|
|
1998
|
-
async function getMemoryEmbeddings(db, memoryIds) {
|
|
1999
|
-
if (memoryIds.length === 0) return /* @__PURE__ */ new Map();
|
|
2000
|
-
const rows = await db.select({
|
|
2001
|
-
id: memoriesTable.id,
|
|
2002
|
-
embedding: memoriesTable.embedding
|
|
2003
|
-
}).from(memoriesTable).where(inArray(memoriesTable.id, [...memoryIds]));
|
|
2004
|
-
return new Map(rows.flatMap((row) => row.embedding ? [[row.id, row.embedding]] : []));
|
|
2005
|
-
}
|
|
2006
|
-
async function vectorSearch(db, input) {
|
|
2007
|
-
const embedding = vectorLiteral(input.embedding);
|
|
2008
|
-
const vectorScore = clampedVectorScore(embedding);
|
|
2009
|
-
const minVectorScore = input.minVectorScore ?? 0;
|
|
2010
|
-
return await db.execute(sql`
|
|
2011
|
-
SELECT
|
|
2012
|
-
id,
|
|
2013
|
-
entity_id AS "entityId",
|
|
2014
|
-
content,
|
|
2015
|
-
type,
|
|
2016
|
-
importance,
|
|
2017
|
-
tags,
|
|
2018
|
-
source,
|
|
2019
|
-
metadata,
|
|
2020
|
-
confidence,
|
|
2021
|
-
embedding_model AS "embeddingModel",
|
|
2022
|
-
embedding_dimensions AS "embeddingDimensions",
|
|
2023
|
-
superseded_by AS "supersededBy",
|
|
2024
|
-
superseded_at AS "supersededAt",
|
|
2025
|
-
expires_at AS "expiresAt",
|
|
2026
|
-
last_accessed_at AS "lastAccessedAt",
|
|
2027
|
-
access_count AS "accessCount",
|
|
2028
|
-
created_at AS "createdAt",
|
|
2029
|
-
updated_at AS "updatedAt",
|
|
2030
|
-
${vectorScore} AS "vectorScore"
|
|
2031
|
-
FROM mnemocyte_memories
|
|
2032
|
-
WHERE
|
|
2033
|
-
entity_id = ${input.entityId}
|
|
2034
|
-
AND embedding IS NOT NULL
|
|
2035
|
-
${input.includeSuperseded ? sql`` : sql`AND superseded_by IS NULL`}
|
|
2036
|
-
${input.includeExpired ? sql`` : sql`AND (expires_at IS NULL OR expires_at > now())`}
|
|
2037
|
-
${input.types && input.types.length > 0 ? sql`AND type IN (${sql.join(input.types.map((type) => sql`${type}`), sql`, `)})` : sql``}
|
|
2038
|
-
${rawTagsFilter(input.tags)}
|
|
2039
|
-
${input.before ? sql`AND created_at < ${timestampParam(input.before)}` : sql``}
|
|
2040
|
-
${input.after ? sql`AND created_at > ${timestampParam(input.after)}` : sql``}
|
|
2041
|
-
AND ${vectorScore} >= ${minVectorScore}
|
|
2042
|
-
ORDER BY embedding <=> ${embedding}::vector
|
|
2043
|
-
LIMIT ${input.limit}
|
|
2044
|
-
`);
|
|
2045
|
-
}
|
|
2046
|
-
async function lexicalSearch(db, input) {
|
|
2047
|
-
return await db.execute(sql`
|
|
2048
|
-
SELECT
|
|
2049
|
-
id,
|
|
2050
|
-
entity_id AS "entityId",
|
|
2051
|
-
content,
|
|
2052
|
-
type,
|
|
2053
|
-
importance,
|
|
2054
|
-
tags,
|
|
2055
|
-
source,
|
|
2056
|
-
metadata,
|
|
2057
|
-
confidence,
|
|
2058
|
-
embedding_model AS "embeddingModel",
|
|
2059
|
-
embedding_dimensions AS "embeddingDimensions",
|
|
2060
|
-
superseded_by AS "supersededBy",
|
|
2061
|
-
superseded_at AS "supersededAt",
|
|
2062
|
-
expires_at AS "expiresAt",
|
|
2063
|
-
last_accessed_at AS "lastAccessedAt",
|
|
2064
|
-
access_count AS "accessCount",
|
|
2065
|
-
created_at AS "createdAt",
|
|
2066
|
-
updated_at AS "updatedAt",
|
|
2067
|
-
ts_rank(to_tsvector('english', content), websearch_to_tsquery('english', ${input.query})) AS "lexicalScore"
|
|
2068
|
-
FROM mnemocyte_memories
|
|
2069
|
-
WHERE
|
|
2070
|
-
entity_id = ${input.entityId}
|
|
2071
|
-
${input.includeSuperseded ? sql`` : sql`AND superseded_by IS NULL`}
|
|
2072
|
-
${input.includeExpired ? sql`` : sql`AND (expires_at IS NULL OR expires_at > now())`}
|
|
2073
|
-
${input.types && input.types.length > 0 ? sql`AND type IN (${sql.join(input.types.map((type) => sql`${type}`), sql`, `)})` : sql``}
|
|
2074
|
-
${rawTagsFilter(input.tags)}
|
|
2075
|
-
${input.before ? sql`AND created_at < ${timestampParam(input.before)}` : sql``}
|
|
2076
|
-
${input.after ? sql`AND created_at > ${timestampParam(input.after)}` : sql``}
|
|
2077
|
-
AND to_tsvector('english', content) @@ websearch_to_tsquery('english', ${input.query})
|
|
2078
|
-
ORDER BY "lexicalScore" DESC
|
|
2079
|
-
LIMIT ${input.limit}
|
|
2080
|
-
`);
|
|
2081
|
-
}
|
|
2082
|
-
async function getInstallationMeta(db) {
|
|
2083
|
-
return (await db.select().from(metaTable).where(eq(metaTable.key, "installation")).limit(1))[0] ?? null;
|
|
2084
|
-
}
|
|
2085
|
-
async function getStoredEmbeddingModels(db) {
|
|
2086
|
-
return (await db.selectDistinct({ embeddingModel: memoriesTable.embeddingModel }).from(memoriesTable).limit(2)).map((row) => row.embeddingModel);
|
|
2087
|
-
}
|
|
2088
|
-
async function recordInstallationEmbeddingModel(db, embeddingModel) {
|
|
2089
|
-
return (await db.update(metaTable).set({ embeddingModel }).where(and(eq(metaTable.key, "installation"), isNull(metaTable.embeddingModel))).returning())[0] ?? null;
|
|
2090
|
-
}
|
|
2091
|
-
//#endregion
|
|
2092
|
-
//#region src/memory/postgres-records.ts
|
|
2093
|
-
function toDate(value) {
|
|
2094
|
-
return value instanceof Date ? value : new Date(value);
|
|
2095
|
-
}
|
|
2096
|
-
function toNullableDate(value) {
|
|
2097
|
-
return value === null ? null : toDate(value);
|
|
2098
|
-
}
|
|
2099
|
-
function rowToMemory(row) {
|
|
2100
|
-
return {
|
|
2101
|
-
id: row.id,
|
|
2102
|
-
entityId: row.entityId,
|
|
2103
|
-
content: row.content,
|
|
2104
|
-
type: row.type,
|
|
2105
|
-
importance: row.importance,
|
|
2106
|
-
tags: row.tags,
|
|
2107
|
-
source: row.source,
|
|
2108
|
-
metadata: cloneJsonObject(row.metadata),
|
|
2109
|
-
confidence: row.confidence,
|
|
2110
|
-
embeddingModel: row.embeddingModel,
|
|
2111
|
-
embeddingDimensions: row.embeddingDimensions,
|
|
2112
|
-
supersededBy: row.supersededBy,
|
|
2113
|
-
supersededAt: toNullableDate(row.supersededAt),
|
|
2114
|
-
expiresAt: toNullableDate(row.expiresAt),
|
|
2115
|
-
lastAccessedAt: toNullableDate(row.lastAccessedAt),
|
|
2116
|
-
accessCount: row.accessCount,
|
|
2117
|
-
createdAt: toDate(row.createdAt),
|
|
2118
|
-
updatedAt: toDate(row.updatedAt)
|
|
2119
|
-
};
|
|
2120
|
-
}
|
|
2121
|
-
//#endregion
|
|
2122
|
-
//#region src/memory/postgres.ts
|
|
2123
|
-
const IMPORTANCE_LEVELS = [
|
|
2124
|
-
"low",
|
|
2125
|
-
"normal",
|
|
2126
|
-
"high",
|
|
2127
|
-
"critical"
|
|
2128
|
-
];
|
|
2129
|
-
const MIGRATION_ERROR_CODES = new Set([
|
|
2130
|
-
"42P01",
|
|
2131
|
-
"42703",
|
|
2132
|
-
"42704",
|
|
2133
|
-
"42883"
|
|
2134
|
-
]);
|
|
2135
|
-
const CONSOLIDATION_SURVIVOR_FOREIGN_KEY = "mnemocyte_memories_superseded_by_mnemocyte_memories_id_fk";
|
|
2136
|
-
function rowToAuditEvent(row) {
|
|
2137
|
-
return {
|
|
2138
|
-
id: row.id,
|
|
2139
|
-
entityId: row.entityId,
|
|
2140
|
-
description: row.description,
|
|
2141
|
-
metadata: cloneJsonObject(row.metadata),
|
|
2142
|
-
timestamp: new Date(row.timestamp)
|
|
2143
|
-
};
|
|
2144
|
-
}
|
|
2145
|
-
function importanceCeilingLevels(max) {
|
|
2146
|
-
return IMPORTANCE_LEVELS.filter((level) => IMPORTANCE_RANK[level] <= IMPORTANCE_RANK[max]);
|
|
2147
|
-
}
|
|
2148
|
-
function hasPostgresErrorCode(error, code) {
|
|
2149
|
-
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
2150
|
-
}
|
|
2151
|
-
function hasPostgresConstraintName(error, name) {
|
|
2152
|
-
return typeof error === "object" && error !== null && "constraint_name" in error && error.constraint_name === name;
|
|
2153
|
-
}
|
|
2154
|
-
function getPostgresErrorCode(error) {
|
|
2155
|
-
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
2156
|
-
}
|
|
2157
|
-
function normalizePostgresError(error) {
|
|
2158
|
-
if (error instanceof MnemocyteError) return error;
|
|
2159
|
-
const code = getPostgresErrorCode(error);
|
|
2160
|
-
if (code && MIGRATION_ERROR_CODES.has(code)) return new MnemocyteError("Postgres schema is missing or incompatible. Apply the bundled Mnemocyte migrations before using the Postgres backend.", "MIGRATION", error);
|
|
2161
|
-
return new MnemocyteError("Postgres operation failed.", "DB", error);
|
|
2162
|
-
}
|
|
2163
|
-
async function runPostgresOperation(operation) {
|
|
2164
|
-
try {
|
|
2165
|
-
return await operation();
|
|
2166
|
-
} catch (error) {
|
|
2167
|
-
throw normalizePostgresError(error);
|
|
2168
|
-
}
|
|
2169
|
-
}
|
|
2170
|
-
async function runPostgresDeleteOperation(operation) {
|
|
2171
|
-
try {
|
|
2172
|
-
return await operation();
|
|
2173
|
-
} catch (error) {
|
|
2174
|
-
if (error instanceof MnemocyteError) throw error;
|
|
2175
|
-
if (hasPostgresErrorCode(error, "23503") && hasPostgresConstraintName(error, CONSOLIDATION_SURVIVOR_FOREIGN_KEY)) throw memoryHasDependentsError(error);
|
|
2176
|
-
throw normalizePostgresError(error);
|
|
2177
|
-
}
|
|
2178
|
-
}
|
|
2179
|
-
function toPruneFilter(input) {
|
|
2180
|
-
const filter = {};
|
|
2181
|
-
if (input.entityId !== void 0) filter.entityId = input.entityId;
|
|
2182
|
-
if (input.expired === true) filter.expired = true;
|
|
2183
|
-
if (input.superseded === true) filter.superseded = true;
|
|
2184
|
-
if (input.createdBefore !== void 0) filter.createdBefore = input.createdBefore;
|
|
2185
|
-
if (input.notAccessedSince !== void 0) filter.notAccessedSince = input.notAccessedSince;
|
|
2186
|
-
if (input.types !== void 0 && input.types.length > 0) filter.types = input.types;
|
|
2187
|
-
if (input.tags !== void 0 && input.tags.length > 0) filter.tags = input.tags;
|
|
2188
|
-
if (input.maxImportance !== void 0) filter.maxImportanceLevels = importanceCeilingLevels(input.maxImportance);
|
|
2189
|
-
return filter;
|
|
2190
|
-
}
|
|
2191
|
-
function toMemoryRow(memory) {
|
|
2192
|
-
return {
|
|
2193
|
-
...memory,
|
|
2194
|
-
tags: [...memory.tags],
|
|
2195
|
-
embedding: [...memory.embedding]
|
|
2196
|
-
};
|
|
2197
|
-
}
|
|
2198
|
-
function createPostgresStore(handle) {
|
|
2199
|
-
let schemaValidated = false;
|
|
2200
|
-
let schemaValidationPromise;
|
|
2201
|
-
async function ensureEmbeddingCompatibility(embedder) {
|
|
2202
|
-
if (schemaValidated) return;
|
|
2203
|
-
if (!schemaValidationPromise) schemaValidationPromise = validateEmbeddingCompatibility(embedder).catch((error) => {
|
|
2204
|
-
schemaValidationPromise = void 0;
|
|
2205
|
-
throw error;
|
|
2206
|
-
});
|
|
2207
|
-
await schemaValidationPromise;
|
|
2208
|
-
}
|
|
2209
|
-
async function validateEmbeddingCompatibility(embedder) {
|
|
2210
|
-
let meta;
|
|
2211
|
-
try {
|
|
2212
|
-
meta = await getInstallationMeta(handle.db);
|
|
2213
|
-
} catch (error) {
|
|
2214
|
-
if (hasPostgresErrorCode(error, "42P01") || hasPostgresErrorCode(error, "42703")) throw new MnemocyteError("Postgres embedding metadata is missing or outdated. Apply the bundled migrations through 0002_add_embedding_model.sql, or render a fresh dimension-specific initial migration, before using embedding-dependent operations.", "MIGRATION", error);
|
|
2215
|
-
throw normalizePostgresError(error);
|
|
2216
|
-
}
|
|
2217
|
-
if (!meta) throw new MnemocyteError("mnemocyte_meta is missing the \"installation\" row. Apply the bundled migrations, or render a fresh dimension-specific initial migration, before using embedding-dependent operations.", "MIGRATION");
|
|
2218
|
-
if (meta.embeddingDimensions !== embedder.dimensions) throw new MnemocyteError(`embedder.dimensions (${embedder.dimensions}) must match mnemocyte_meta.embedding_dimensions (${meta.embeddingDimensions}). Render and apply a migration for the selected embedding dimension, or configure an embedder that matches this installation.`, "CONFIG");
|
|
2219
|
-
let installationModel = meta.embeddingModel;
|
|
2220
|
-
if (installationModel === null) {
|
|
2221
|
-
try {
|
|
2222
|
-
const storedModels = await getStoredEmbeddingModels(handle.db);
|
|
2223
|
-
if (storedModels.length > 1) throw new MnemocyteError("mnemocyte_meta.embedding_model is unset, but stored memories contain multiple embedding_model values. Re-embed or remove the mixed historical rows, then explicitly record the installation model before using embedding-dependent operations.", "MIGRATION");
|
|
2224
|
-
const inferredModel = storedModels[0] ?? embedder.model;
|
|
2225
|
-
const recorded = await recordInstallationEmbeddingModel(handle.db, inferredModel);
|
|
2226
|
-
if (recorded) installationModel = recorded.embeddingModel;
|
|
2227
|
-
else installationModel = (await getInstallationMeta(handle.db))?.embeddingModel ?? null;
|
|
2228
|
-
} catch (error) {
|
|
2229
|
-
throw normalizePostgresError(error);
|
|
2230
|
-
}
|
|
2231
|
-
if (installationModel === null) throw new MnemocyteError("mnemocyte_meta.embedding_model could not be recorded. Verify the installation metadata row and retry.", "MIGRATION");
|
|
2232
|
-
}
|
|
2233
|
-
if (installationModel !== embedder.model) throw new MnemocyteError(`embedder.model ("${embedder.model}") must match mnemocyte_meta.embedding_model ("${installationModel}"). Configure the recorded model, or explicitly re-embed existing memories and update the installation metadata.`, "CONFIG");
|
|
2234
|
-
schemaValidated = true;
|
|
312
|
+
//#region src/memory/lazy-postgres.ts
|
|
313
|
+
function createLazyPostgresStore(databaseUrl) {
|
|
314
|
+
let storePromise;
|
|
315
|
+
function loadStore() {
|
|
316
|
+
storePromise ??= import("./postgres-runtime-DxJxns07.mjs").then(({ createPostgresStoreFromUrl }) => createPostgresStoreFromUrl(databaseUrl));
|
|
317
|
+
return storePromise;
|
|
2235
318
|
}
|
|
2236
319
|
return {
|
|
2237
320
|
backend: "postgres",
|
|
2238
|
-
async ensureSchema() {
|
|
2239
|
-
|
|
321
|
+
async ensureSchema() {
|
|
322
|
+
return (await loadStore()).ensureSchema();
|
|
323
|
+
},
|
|
324
|
+
async ensureEmbeddingCompatibility(embedder) {
|
|
325
|
+
return (await loadStore()).ensureEmbeddingCompatibility(embedder);
|
|
326
|
+
},
|
|
2240
327
|
async insertMemories(memories) {
|
|
2241
|
-
return
|
|
2242
|
-
return (await insertMemories(handle.db, memories.map(toMemoryRow))).map(rowToMemory);
|
|
2243
|
-
});
|
|
328
|
+
return (await loadStore()).insertMemories(memories);
|
|
2244
329
|
},
|
|
2245
330
|
async vectorSearch(input) {
|
|
2246
|
-
return
|
|
2247
|
-
return (await vectorSearch(handle.db, input)).map((row) => ({
|
|
2248
|
-
memory: rowToMemory(row),
|
|
2249
|
-
vectorScore: row.vectorScore
|
|
2250
|
-
}));
|
|
2251
|
-
});
|
|
331
|
+
return (await loadStore()).vectorSearch(input);
|
|
2252
332
|
},
|
|
2253
333
|
async lexicalSearch(input) {
|
|
2254
|
-
return
|
|
2255
|
-
return (await lexicalSearch(handle.db, input)).map((row) => ({
|
|
2256
|
-
memory: rowToMemory(row),
|
|
2257
|
-
lexicalScore: row.lexicalScore
|
|
2258
|
-
}));
|
|
2259
|
-
});
|
|
334
|
+
return (await loadStore()).lexicalSearch(input);
|
|
2260
335
|
},
|
|
2261
336
|
async getMemoryEmbeddings(memoryIds) {
|
|
2262
|
-
return
|
|
337
|
+
return (await loadStore()).getMemoryEmbeddings(memoryIds);
|
|
2263
338
|
},
|
|
2264
339
|
async markMemoriesAccessed(memoryIds) {
|
|
2265
|
-
return
|
|
2266
|
-
return (await markMemoriesAccessed(handle.db, memoryIds)).map((row) => {
|
|
2267
|
-
if (row.lastAccessedAt === null) throw new MnemocyteError("Postgres access update returned no timestamp.", "DB");
|
|
2268
|
-
return {
|
|
2269
|
-
...row,
|
|
2270
|
-
lastAccessedAt: row.lastAccessedAt
|
|
2271
|
-
};
|
|
2272
|
-
});
|
|
2273
|
-
});
|
|
340
|
+
return (await loadStore()).markMemoriesAccessed(memoryIds);
|
|
2274
341
|
},
|
|
2275
342
|
async deleteMemory(entityId, memoryId) {
|
|
2276
|
-
return
|
|
2277
|
-
const result = await deleteMemory(handle.db, entityId, memoryId);
|
|
2278
|
-
if (result.hasDependents) throw memoryHasDependentsError();
|
|
2279
|
-
return result.deletedCount > 0;
|
|
2280
|
-
});
|
|
343
|
+
return (await loadStore()).deleteMemory(entityId, memoryId);
|
|
2281
344
|
},
|
|
2282
345
|
async deleteMemoriesForEntity(entityId) {
|
|
2283
|
-
return
|
|
2284
|
-
const result = await deleteMemoriesForEntity(handle.db, entityId);
|
|
2285
|
-
if (result.hasDependents) throw memoryHasDependentsError();
|
|
2286
|
-
return result.deletedCount;
|
|
2287
|
-
});
|
|
346
|
+
return (await loadStore()).deleteMemoriesForEntity(entityId);
|
|
2288
347
|
},
|
|
2289
348
|
async prune(input, options) {
|
|
2290
|
-
return
|
|
2291
|
-
throwIfAborted(options?.signal);
|
|
2292
|
-
assertPruneFilterHasSelector(input);
|
|
2293
|
-
const filter = toPruneFilter(input);
|
|
2294
|
-
if (input.dryRun === true) return {
|
|
2295
|
-
matchedCount: await countPruneMatches(handle.db, filter, options?.signal),
|
|
2296
|
-
deletedCount: 0,
|
|
2297
|
-
dryRun: true,
|
|
2298
|
-
deletedByEntity: []
|
|
2299
|
-
};
|
|
2300
|
-
const result = await pruneMemories(handle.db, filter, options?.signal);
|
|
2301
|
-
if (result.hasDependents) throw memoryHasDependentsError();
|
|
2302
|
-
return {
|
|
2303
|
-
matchedCount: result.matchedCount,
|
|
2304
|
-
deletedCount: result.deletedCount,
|
|
2305
|
-
dryRun: false,
|
|
2306
|
-
deletedByEntity: result.deletedByEntity
|
|
2307
|
-
};
|
|
2308
|
-
});
|
|
349
|
+
return (await loadStore()).prune(input, options);
|
|
2309
350
|
},
|
|
2310
351
|
async findDuplicatePairs(input, options) {
|
|
2311
|
-
return
|
|
2312
|
-
return (await findDuplicatePairs(handle.db, {
|
|
2313
|
-
entityId: input.entityId,
|
|
2314
|
-
threshold: input.threshold ?? .95,
|
|
2315
|
-
limit: input.limit ?? 100,
|
|
2316
|
-
...input.types === void 0 ? {} : { types: input.types },
|
|
2317
|
-
...input.tags === void 0 ? {} : { tags: input.tags },
|
|
2318
|
-
...input.includeSuperseded === void 0 ? {} : { includeSuperseded: input.includeSuperseded },
|
|
2319
|
-
...input.includeExpired === void 0 ? {} : { includeExpired: input.includeExpired }
|
|
2320
|
-
}, options?.signal)).map((row) => ({
|
|
2321
|
-
a: rowToMemory(row.a),
|
|
2322
|
-
b: rowToMemory(row.b),
|
|
2323
|
-
similarity: Math.max(0, Math.min(1, row.similarity))
|
|
2324
|
-
}));
|
|
2325
|
-
});
|
|
352
|
+
return (await loadStore()).findDuplicatePairs(input, options);
|
|
2326
353
|
},
|
|
2327
354
|
async addAuditEvents(events) {
|
|
2328
|
-
return
|
|
2329
|
-
for (const event of events) await insertEvent(handle.db, {
|
|
2330
|
-
id: event.id,
|
|
2331
|
-
entityId: event.entityId,
|
|
2332
|
-
description: event.description,
|
|
2333
|
-
metadata: cloneJsonObject(event.metadata),
|
|
2334
|
-
timestamp: event.timestamp
|
|
2335
|
-
});
|
|
2336
|
-
});
|
|
355
|
+
return (await loadStore()).addAuditEvents(events);
|
|
2337
356
|
},
|
|
2338
357
|
async listAuditLog(input, options) {
|
|
2339
|
-
return
|
|
2340
|
-
return (await listEvents(handle.db, {
|
|
2341
|
-
entityId: input.entityId,
|
|
2342
|
-
limit: input.limit ?? 50,
|
|
2343
|
-
...input.before === void 0 ? {} : { before: input.before },
|
|
2344
|
-
...input.after === void 0 ? {} : { after: input.after },
|
|
2345
|
-
...input.beforeCursor === void 0 ? {} : { beforeCursor: input.beforeCursor },
|
|
2346
|
-
...input.afterCursor === void 0 ? {} : { afterCursor: input.afterCursor }
|
|
2347
|
-
}, options?.signal)).map(rowToAuditEvent);
|
|
2348
|
-
});
|
|
358
|
+
return (await loadStore()).listAuditLog(input, options);
|
|
2349
359
|
},
|
|
2350
360
|
async getMemory(entityId, memoryId, options) {
|
|
2351
|
-
return
|
|
2352
|
-
throwIfAborted(options?.signal);
|
|
2353
|
-
const row = await getMemoryById(handle.db, entityId, memoryId);
|
|
2354
|
-
throwIfAborted(options?.signal);
|
|
2355
|
-
return row ? rowToMemory(row) : null;
|
|
2356
|
-
});
|
|
361
|
+
return (await loadStore()).getMemory(entityId, memoryId, options);
|
|
2357
362
|
},
|
|
2358
363
|
async loadConsolidationTargets(entityId, ids, options) {
|
|
2359
|
-
return
|
|
2360
|
-
throwIfAborted(options?.signal);
|
|
2361
|
-
const targets = await loadConsolidationTargets(handle.db, entityId, ids);
|
|
2362
|
-
throwIfAborted(options?.signal);
|
|
2363
|
-
return targets;
|
|
2364
|
-
});
|
|
364
|
+
return (await loadStore()).loadConsolidationTargets(entityId, ids, options);
|
|
2365
365
|
},
|
|
2366
366
|
async consolidate(input, options) {
|
|
2367
|
-
return
|
|
2368
|
-
throwIfAborted(options?.signal);
|
|
2369
|
-
return { supersededIds: await handle.db.transaction(async (tx) => {
|
|
2370
|
-
throwIfAborted(options?.signal);
|
|
2371
|
-
const targets = await lockConsolidationTargets(tx, input.entityId, input.supersededIds);
|
|
2372
|
-
throwIfAborted(options?.signal);
|
|
2373
|
-
if (targets.some((target) => target.supersededBy !== null && target.supersededBy !== input.survivorId)) throw new MnemocyteError("Superseded memory already belongs to a different survivor.", "CONFLICT");
|
|
2374
|
-
const activeIds = targets.filter((target) => target.supersededBy === null).map((target) => target.id);
|
|
2375
|
-
const updated = await markMemoriesSuperseded(tx, {
|
|
2376
|
-
survivorId: input.survivorId,
|
|
2377
|
-
entityId: input.entityId,
|
|
2378
|
-
ids: activeIds,
|
|
2379
|
-
now: input.now
|
|
2380
|
-
});
|
|
2381
|
-
throwIfAborted(options?.signal);
|
|
2382
|
-
const ids = updated.map((row) => row.id);
|
|
2383
|
-
if (input.auditEnabled) for (const id of ids) {
|
|
2384
|
-
throwIfAborted(options?.signal);
|
|
2385
|
-
await insertEvent(tx, {
|
|
2386
|
-
id: createEventId(),
|
|
2387
|
-
entityId: input.entityId,
|
|
2388
|
-
description: "memory.superseded",
|
|
2389
|
-
metadata: {
|
|
2390
|
-
memoryId: id,
|
|
2391
|
-
supersededBy: input.survivorId
|
|
2392
|
-
},
|
|
2393
|
-
timestamp: input.now
|
|
2394
|
-
});
|
|
2395
|
-
throwIfAborted(options?.signal);
|
|
2396
|
-
}
|
|
2397
|
-
if (input.mergeTags && updated.length > 0) {
|
|
2398
|
-
const mergedTags = new Set(input.survivorTags);
|
|
2399
|
-
for (const row of updated) {
|
|
2400
|
-
throwIfAborted(options?.signal);
|
|
2401
|
-
for (const tag of row.tags) mergedTags.add(tag);
|
|
2402
|
-
}
|
|
2403
|
-
if (mergedTags.size !== input.survivorTags.length) {
|
|
2404
|
-
throwIfAborted(options?.signal);
|
|
2405
|
-
await setMemoryTags(tx, {
|
|
2406
|
-
entityId: input.entityId,
|
|
2407
|
-
memoryId: input.survivorId,
|
|
2408
|
-
tags: [...mergedTags],
|
|
2409
|
-
now: input.now
|
|
2410
|
-
});
|
|
2411
|
-
throwIfAborted(options?.signal);
|
|
2412
|
-
}
|
|
2413
|
-
}
|
|
2414
|
-
throwIfAborted(options?.signal);
|
|
2415
|
-
return ids;
|
|
2416
|
-
}) };
|
|
2417
|
-
});
|
|
367
|
+
return (await loadStore()).consolidate(input, options);
|
|
2418
368
|
},
|
|
2419
369
|
async stats(input, now) {
|
|
2420
|
-
return
|
|
2421
|
-
if (input?.entityId) {
|
|
2422
|
-
const counts = await getEntityMemoryStatsCounts(handle.db, input.entityId, now);
|
|
2423
|
-
return {
|
|
2424
|
-
entityId: input.entityId,
|
|
2425
|
-
...counts
|
|
2426
|
-
};
|
|
2427
|
-
}
|
|
2428
|
-
return await getGlobalMemoryStatsCounts(handle.db, now);
|
|
2429
|
-
});
|
|
370
|
+
return (await loadStore()).stats(input, now);
|
|
2430
371
|
},
|
|
2431
372
|
async close() {
|
|
2432
|
-
return
|
|
373
|
+
return (await loadStore()).close();
|
|
2433
374
|
}
|
|
2434
375
|
};
|
|
2435
376
|
}
|
|
2436
|
-
function createPostgresClient(config, handle) {
|
|
2437
|
-
return createMemoryClient(config, createPostgresStore(handle));
|
|
2438
|
-
}
|
|
2439
377
|
//#endregion
|
|
2440
378
|
//#region src/client.ts
|
|
2441
379
|
/**
|
|
2442
380
|
* Create a Mnemocyte client.
|
|
2443
381
|
*
|
|
2444
|
-
* When {@link MnemocyteConfig.
|
|
2445
|
-
*
|
|
2446
|
-
*
|
|
2447
|
-
* for tests, demos,
|
|
382
|
+
* When {@link MnemocyteConfig.store} is provided, that adapter is used. When
|
|
383
|
+
* {@link MnemocyteConfig.databaseUrl} is provided, a URL-owned Postgres client
|
|
384
|
+
* is used (requires the `pgvector` extension and the bundled migrations).
|
|
385
|
+
* When neither is supplied, an in-memory client is returned for tests, demos,
|
|
386
|
+
* and short-lived processes.
|
|
2448
387
|
*
|
|
2449
|
-
*
|
|
2450
|
-
*
|
|
2451
|
-
*
|
|
388
|
+
* Call {@link MnemocyteClient.close} when finished. URL-created database
|
|
389
|
+
* resources are owned and closed by Mnemocyte; resources behind a supplied
|
|
390
|
+
* store retain the ownership policy documented by that adapter.
|
|
2452
391
|
*
|
|
2453
392
|
* @param config - Client configuration. {@link MnemocyteConfig.embedder} is required.
|
|
2454
393
|
* @returns A {@link MnemocyteClient} instance.
|
|
2455
394
|
* @throws {MnemocyteError} With code `"CONFIG"` if `embedder`, retrieval
|
|
2456
395
|
* tuning, provider resilience, or the database URL is malformed or does not
|
|
2457
|
-
* use the `postgres:` / `postgresql:` protocol, or `
|
|
2458
|
-
* `databaseUrl` is explicitly
|
|
396
|
+
* use the `postgres:` / `postgresql:` protocol, or if `databaseUrl` and
|
|
397
|
+
* `store` are both provided; or `"VALIDATION"` if `databaseUrl` is explicitly
|
|
398
|
+
* empty.
|
|
2459
399
|
*
|
|
2460
400
|
* @example Postgres-backed client
|
|
2461
401
|
* ```ts
|
|
@@ -2469,10 +409,13 @@ function createMnemocyte(config) {
|
|
|
2469
409
|
assertEmbedder(config.embedder);
|
|
2470
410
|
validateRetrievalConfig(config.retrieval);
|
|
2471
411
|
validateProviderResilienceConfig(config.provider);
|
|
412
|
+
if (config.databaseUrl !== void 0 && config.store !== void 0) throw new MnemocyteError("databaseUrl and store cannot be provided together.", "CONFIG");
|
|
2472
413
|
if (config.databaseUrl !== void 0) {
|
|
2473
414
|
assertNonEmptyString(config.databaseUrl, "databaseUrl");
|
|
2474
|
-
|
|
415
|
+
parsePostgresDatabaseUrl(config.databaseUrl);
|
|
416
|
+
return createMemoryClient(config, createLazyPostgresStore(config.databaseUrl));
|
|
2475
417
|
}
|
|
418
|
+
if (config.store !== void 0) return createMemoryClient(config, unwrapMnemocyteStoreConfig(config.store));
|
|
2476
419
|
return createInMemoryClient(config);
|
|
2477
420
|
}
|
|
2478
421
|
//#endregion
|