mnemocyte 0.0.7 → 0.0.8
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 +63 -1
- package/dist/index.d.mts +500 -5
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +364 -10
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -16
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/client.ts","../src/errors.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/client.ts","../src/errors.ts"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { drizzle } from "drizzle-orm/postgres-js";
|
|
2
2
|
import postgres from "postgres";
|
|
3
|
-
import { and, eq, gt, inArray, isNull, lt, sql } from "drizzle-orm";
|
|
3
|
+
import { and, eq, gt, inArray, isNotNull, isNull, lt, or, sql } from "drizzle-orm";
|
|
4
4
|
import { customType, index, integer, jsonb, pgTable, real, text, timestamp } from "drizzle-orm/pg-core";
|
|
5
5
|
//#region \0rolldown/runtime.js
|
|
6
6
|
var __defProp = Object.defineProperty;
|
|
@@ -153,9 +153,35 @@ async function buildContext(options) {
|
|
|
153
153
|
}
|
|
154
154
|
//#endregion
|
|
155
155
|
//#region src/errors.ts
|
|
156
|
+
/**
|
|
157
|
+
* Error type thrown by every public Mnemocyte API.
|
|
158
|
+
*
|
|
159
|
+
* Use the {@link MnemocyteError.code} property — not the message — to branch
|
|
160
|
+
* on failure modes, or call {@link isMnemocyteError} as a type guard when
|
|
161
|
+
* working with `unknown` errors.
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```ts
|
|
165
|
+
* try {
|
|
166
|
+
* await client.recall({ entityId: "u1", query: "hi" });
|
|
167
|
+
* } catch (err) {
|
|
168
|
+
* if (isMnemocyteError(err) && err.code === "DB") {
|
|
169
|
+
* // handle database failure
|
|
170
|
+
* }
|
|
171
|
+
* throw err;
|
|
172
|
+
* }
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
156
175
|
var MnemocyteError = class extends Error {
|
|
176
|
+
/** Stable, machine-readable failure code. See {@link MnemocyteErrorCode}. */
|
|
157
177
|
code;
|
|
178
|
+
/** Underlying cause, if any (e.g. an original driver or embedder error). */
|
|
158
179
|
cause;
|
|
180
|
+
/**
|
|
181
|
+
* @param message - Human-readable error message.
|
|
182
|
+
* @param code - Stable failure code; see {@link MnemocyteErrorCode}.
|
|
183
|
+
* @param cause - Optional underlying cause (driver error, network error, etc.).
|
|
184
|
+
*/
|
|
159
185
|
constructor(message, code, cause) {
|
|
160
186
|
super(message);
|
|
161
187
|
this.name = "MnemocyteError";
|
|
@@ -163,6 +189,15 @@ var MnemocyteError = class extends Error {
|
|
|
163
189
|
this.cause = cause;
|
|
164
190
|
}
|
|
165
191
|
};
|
|
192
|
+
/**
|
|
193
|
+
* Type guard for {@link MnemocyteError}.
|
|
194
|
+
*
|
|
195
|
+
* Prefer this over `instanceof` when working across module boundaries or
|
|
196
|
+
* bundlers where multiple copies of the class may exist.
|
|
197
|
+
*
|
|
198
|
+
* @param error - Value to test (typically a caught `unknown`).
|
|
199
|
+
* @returns `true` if `error` is a {@link MnemocyteError}.
|
|
200
|
+
*/
|
|
166
201
|
function isMnemocyteError(error) {
|
|
167
202
|
return error instanceof MnemocyteError;
|
|
168
203
|
}
|
|
@@ -216,6 +251,176 @@ async function observe(config, backend, operation, metadata, action, successMeta
|
|
|
216
251
|
throw error;
|
|
217
252
|
}
|
|
218
253
|
}
|
|
254
|
+
//#endregion
|
|
255
|
+
//#region src/resilience.ts
|
|
256
|
+
/**
|
|
257
|
+
* Default per-provider-call timeout. Disabled by default so that callers
|
|
258
|
+
* who do not configure {@link ProviderResilienceConfig.timeoutMs} are
|
|
259
|
+
* unaffected.
|
|
260
|
+
*/
|
|
261
|
+
const DEFAULT_TIMEOUT_MS = 0;
|
|
262
|
+
/** Default number of retry attempts on transient provider failures. */
|
|
263
|
+
const DEFAULT_RETRIES = 0;
|
|
264
|
+
/** Default base backoff delay used between retry attempts. */
|
|
265
|
+
const DEFAULT_BASE_DELAY_MS = 100;
|
|
266
|
+
/** Default ceiling for retry backoff delay. */
|
|
267
|
+
const DEFAULT_MAX_DELAY_MS = 2e3;
|
|
268
|
+
/**
|
|
269
|
+
* Throw a {@link MnemocyteError} with code `"ABORTED"` if `signal` is
|
|
270
|
+
* already aborted. Safe to call with `undefined`.
|
|
271
|
+
*/
|
|
272
|
+
function throwIfAborted(signal) {
|
|
273
|
+
if (signal?.aborted) throw new MnemocyteError("Operation was aborted.", "ABORTED", signal.reason);
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Heuristic default for whether an error should be retried.
|
|
277
|
+
*
|
|
278
|
+
* Conservative by design: only retries on common transient failure
|
|
279
|
+
* indicators (network errors, 5xx-ish messages, `ECONN*` etc.). Never
|
|
280
|
+
* retries on validation, configuration, or {@link MnemocyteError} of
|
|
281
|
+
* code `"VALIDATION"` / `"CONFIG"` / `"ABORTED"`.
|
|
282
|
+
*/
|
|
283
|
+
function defaultShouldRetry(error) {
|
|
284
|
+
if (error instanceof MnemocyteError) {
|
|
285
|
+
if (error.code === "VALIDATION" || error.code === "CONFIG" || error.code === "ABORTED") return false;
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
if (error instanceof Error) {
|
|
289
|
+
const message = error.message.toLowerCase();
|
|
290
|
+
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;
|
|
291
|
+
}
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Compute the backoff delay for the given retry attempt, using exponential
|
|
296
|
+
* growth capped at {@link ProviderResilienceConfig.maxDelayMs}.
|
|
297
|
+
*
|
|
298
|
+
* `attempt` is 1-indexed: the delay before the first retry uses
|
|
299
|
+
* `attempt = 1`, before the second retry `attempt = 2`, etc.
|
|
300
|
+
*/
|
|
301
|
+
function computeDelayMs(attempt, baseDelayMs, maxDelayMs) {
|
|
302
|
+
const exponential = baseDelayMs * 2 ** (attempt - 1);
|
|
303
|
+
return Math.max(0, Math.min(maxDelayMs, exponential));
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Promise-based `setTimeout` that rejects with an `"ABORTED"`
|
|
307
|
+
* {@link MnemocyteError} if `signal` aborts while waiting.
|
|
308
|
+
*/
|
|
309
|
+
function delay(ms, signal) {
|
|
310
|
+
if (ms <= 0) {
|
|
311
|
+
throwIfAborted(signal);
|
|
312
|
+
return Promise.resolve();
|
|
313
|
+
}
|
|
314
|
+
return new Promise((resolve, reject) => {
|
|
315
|
+
const timer = setTimeout(() => {
|
|
316
|
+
signal?.removeEventListener("abort", onAbort);
|
|
317
|
+
resolve();
|
|
318
|
+
}, ms);
|
|
319
|
+
const onAbort = () => {
|
|
320
|
+
clearTimeout(timer);
|
|
321
|
+
reject(new MnemocyteError("Operation was aborted.", "ABORTED", signal?.reason));
|
|
322
|
+
};
|
|
323
|
+
if (signal) {
|
|
324
|
+
if (signal.aborted) {
|
|
325
|
+
clearTimeout(timer);
|
|
326
|
+
reject(new MnemocyteError("Operation was aborted.", "ABORTED", signal.reason));
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Race `promise` against the configured `timeoutMs` and the caller's
|
|
335
|
+
* `signal`. Rejects with `"TIMEOUT"` or `"ABORTED"` respectively. Resolves
|
|
336
|
+
* with the original promise value if it settles first.
|
|
337
|
+
*/
|
|
338
|
+
function withTimeout(promise, timeoutMs, signal) {
|
|
339
|
+
if (timeoutMs <= 0 && !signal) return promise;
|
|
340
|
+
return new Promise((resolve, reject) => {
|
|
341
|
+
let settled = false;
|
|
342
|
+
const timer = timeoutMs > 0 ? setTimeout(() => {
|
|
343
|
+
if (settled) return;
|
|
344
|
+
settled = true;
|
|
345
|
+
signal?.removeEventListener("abort", onAbort);
|
|
346
|
+
reject(new MnemocyteError(`Provider call timed out after ${timeoutMs}ms.`, "TIMEOUT"));
|
|
347
|
+
}, timeoutMs) : void 0;
|
|
348
|
+
const onAbort = () => {
|
|
349
|
+
if (settled) return;
|
|
350
|
+
settled = true;
|
|
351
|
+
if (timer) clearTimeout(timer);
|
|
352
|
+
reject(new MnemocyteError("Operation was aborted.", "ABORTED", signal?.reason));
|
|
353
|
+
};
|
|
354
|
+
if (signal) {
|
|
355
|
+
if (signal.aborted) {
|
|
356
|
+
if (timer) clearTimeout(timer);
|
|
357
|
+
settled = true;
|
|
358
|
+
reject(new MnemocyteError("Operation was aborted.", "ABORTED", signal.reason));
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
362
|
+
}
|
|
363
|
+
promise.then((value) => {
|
|
364
|
+
if (settled) return;
|
|
365
|
+
settled = true;
|
|
366
|
+
if (timer) clearTimeout(timer);
|
|
367
|
+
signal?.removeEventListener("abort", onAbort);
|
|
368
|
+
resolve(value);
|
|
369
|
+
}, (error) => {
|
|
370
|
+
if (settled) return;
|
|
371
|
+
settled = true;
|
|
372
|
+
if (timer) clearTimeout(timer);
|
|
373
|
+
signal?.removeEventListener("abort", onAbort);
|
|
374
|
+
reject(error);
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Run `action` with timeout, retry, and abort handling derived from
|
|
380
|
+
* `options.resilience` and `options.signal`. Each retry attempt receives
|
|
381
|
+
* a fresh `AbortSignal` linked to the caller's signal — so callers may
|
|
382
|
+
* abort mid-retry and the in-flight attempt will cancel promptly if the
|
|
383
|
+
* underlying provider honours signals.
|
|
384
|
+
*
|
|
385
|
+
* Errors from {@link MnemocyteError} with code `"VALIDATION"` /
|
|
386
|
+
* `"CONFIG"` / `"ABORTED"` are never retried.
|
|
387
|
+
*/
|
|
388
|
+
async function withResilience(action, options = {}) {
|
|
389
|
+
const config = options.resilience ?? {};
|
|
390
|
+
const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
391
|
+
const retries = Math.max(0, config.retries ?? DEFAULT_RETRIES);
|
|
392
|
+
const baseDelayMs = Math.max(0, config.baseDelayMs ?? DEFAULT_BASE_DELAY_MS);
|
|
393
|
+
const maxDelayMs = Math.max(baseDelayMs, config.maxDelayMs ?? DEFAULT_MAX_DELAY_MS);
|
|
394
|
+
const shouldRetry = config.shouldRetry ?? defaultShouldRetry;
|
|
395
|
+
const externalSignal = options.signal;
|
|
396
|
+
throwIfAborted(externalSignal);
|
|
397
|
+
const maxAttempts = retries + 1;
|
|
398
|
+
let lastError;
|
|
399
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
400
|
+
throwIfAborted(externalSignal);
|
|
401
|
+
try {
|
|
402
|
+
return await withTimeout(action(externalSignal), timeoutMs, externalSignal);
|
|
403
|
+
} catch (error) {
|
|
404
|
+
lastError = error;
|
|
405
|
+
if (error instanceof MnemocyteError && error.code === "ABORTED") throw error;
|
|
406
|
+
if (attempt >= maxAttempts) throw error;
|
|
407
|
+
if (!shouldRetry(error, attempt)) throw error;
|
|
408
|
+
await delay(computeDelayMs(attempt, baseDelayMs, maxDelayMs), externalSignal);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
throw lastError;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Ordering of {@link ImportanceLevel} from least to most important. Used
|
|
415
|
+
* by `prune({ maxImportance })` to compare a memory's importance against
|
|
416
|
+
* the caller-supplied ceiling.
|
|
417
|
+
*/
|
|
418
|
+
const IMPORTANCE_RANK = {
|
|
419
|
+
low: 0,
|
|
420
|
+
normal: 1,
|
|
421
|
+
high: 2,
|
|
422
|
+
critical: 3
|
|
423
|
+
};
|
|
219
424
|
function assertNonEmptyString(value, field) {
|
|
220
425
|
if (value.trim().length === 0) throw new MnemocyteError(`${field} must be a non-empty string.`, "VALIDATION");
|
|
221
426
|
}
|
|
@@ -272,11 +477,15 @@ function rowToMemory(row) {
|
|
|
272
477
|
updatedAt: row.updatedAt
|
|
273
478
|
};
|
|
274
479
|
}
|
|
275
|
-
async function embedOne(embedder, text) {
|
|
480
|
+
async function embedOne(embedder, text, options = {}) {
|
|
276
481
|
let embeddings;
|
|
277
482
|
try {
|
|
278
|
-
embeddings = await embedder.embed([text])
|
|
483
|
+
embeddings = await withResilience((signal) => signal === void 0 ? embedder.embed([text]) : embedder.embed([text], { signal }), {
|
|
484
|
+
...options.signal === void 0 ? {} : { signal: options.signal },
|
|
485
|
+
...options.resilience === void 0 ? {} : { resilience: options.resilience }
|
|
486
|
+
});
|
|
279
487
|
} catch (error) {
|
|
488
|
+
if (error instanceof MnemocyteError && (error.code === "TIMEOUT" || error.code === "ABORTED")) throw error;
|
|
280
489
|
throw new MnemocyteError("Failed to embed text.", "EMBEDDING", error);
|
|
281
490
|
}
|
|
282
491
|
const embedding = embeddings[0];
|
|
@@ -307,6 +516,36 @@ function contextInputToRecallInput(input) {
|
|
|
307
516
|
...input.includeExpired === void 0 ? {} : { includeExpired: input.includeExpired }
|
|
308
517
|
};
|
|
309
518
|
}
|
|
519
|
+
/**
|
|
520
|
+
* Validate a {@link PruneInput} and throw a `"VALIDATION"`
|
|
521
|
+
* {@link MnemocyteError} when no selector is supplied. Mnemocyte
|
|
522
|
+
* deliberately rejects `prune({})` to avoid surprise full-table deletes.
|
|
523
|
+
*/
|
|
524
|
+
function validatePruneInput(input) {
|
|
525
|
+
if (input.entityId !== void 0) assertNonEmptyString(input.entityId, "entityId");
|
|
526
|
+
if (!(input.entityId !== void 0 || input.expired === true || input.superseded === true || input.createdBefore !== void 0 || input.notAccessedSince !== void 0 || input.types !== void 0 && input.types.length > 0 || input.tags !== void 0 && input.tags.length > 0 || input.maxImportance !== void 0)) throw new MnemocyteError("prune requires at least one selector (entityId, expired, superseded, createdBefore, notAccessedSince, types, tags, or maxImportance).", "VALIDATION");
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Test whether `memory` matches a {@link PruneInput}. All specified
|
|
530
|
+
* selectors are AND-combined; unspecified selectors do not restrict.
|
|
531
|
+
*
|
|
532
|
+
* `now` is supplied by the caller so callers can share a consistent
|
|
533
|
+
* timestamp across a batch.
|
|
534
|
+
*/
|
|
535
|
+
function matchesPruneFilter(memory, input, now) {
|
|
536
|
+
if (input.entityId !== void 0 && memory.entityId !== input.entityId) return false;
|
|
537
|
+
if (input.expired === true && !isExpired(memory, now)) return false;
|
|
538
|
+
if (input.superseded === true && memory.supersededBy === null) return false;
|
|
539
|
+
if (input.createdBefore !== void 0 && memory.createdAt.getTime() >= input.createdBefore.getTime()) return false;
|
|
540
|
+
if (input.notAccessedSince !== void 0) {
|
|
541
|
+
const last = memory.lastAccessedAt;
|
|
542
|
+
if (last !== null && last.getTime() >= input.notAccessedSince.getTime()) return false;
|
|
543
|
+
}
|
|
544
|
+
if (input.types !== void 0 && !input.types.includes(memory.type)) return false;
|
|
545
|
+
if (input.tags !== void 0 && input.tags.length > 0 && !input.tags.every((tag) => memory.tags.includes(tag))) return false;
|
|
546
|
+
if (input.maxImportance !== void 0 && IMPORTANCE_RANK[memory.importance] > IMPORTANCE_RANK[input.maxImportance]) return false;
|
|
547
|
+
return true;
|
|
548
|
+
}
|
|
310
549
|
function matchesRecallFilter(memory, input, now) {
|
|
311
550
|
if (memory.entityId !== input.entityId) return false;
|
|
312
551
|
if (!input.includeSuperseded && memory.supersededBy !== null) return false;
|
|
@@ -427,7 +666,10 @@ function createInMemoryClient(config) {
|
|
|
427
666
|
return observe(config, "in-memory", "remember", { entityId: input.entityId }, async () => {
|
|
428
667
|
assertOpen();
|
|
429
668
|
validateRememberInput(input);
|
|
430
|
-
const embedding = await embedOne(config.embedder, input.content
|
|
669
|
+
const embedding = await embedOne(config.embedder, input.content, {
|
|
670
|
+
...input.signal === void 0 ? {} : { signal: input.signal },
|
|
671
|
+
...config.provider === void 0 ? {} : { resilience: config.provider }
|
|
672
|
+
});
|
|
431
673
|
const now = /* @__PURE__ */ new Date();
|
|
432
674
|
const memory = {
|
|
433
675
|
id: createId(),
|
|
@@ -474,7 +716,10 @@ function createInMemoryClient(config) {
|
|
|
474
716
|
const minScore = input.minScore ?? config.defaults?.minScore ?? 0;
|
|
475
717
|
assertLimit(limit);
|
|
476
718
|
assertMinScore(minScore);
|
|
477
|
-
const queryEmbedding = await embedOne(config.embedder, input.query
|
|
719
|
+
const queryEmbedding = await embedOne(config.embedder, input.query, {
|
|
720
|
+
...input.signal === void 0 ? {} : { signal: input.signal },
|
|
721
|
+
...config.provider === void 0 ? {} : { resilience: config.provider }
|
|
722
|
+
});
|
|
478
723
|
const now = /* @__PURE__ */ new Date();
|
|
479
724
|
const scored = Array.from(memories.values()).filter((memory) => matchesRecallFilter(memory, input, now)).map((memory) => toScoredMemory(memory, Math.max(0, cosineSimilarity(memory.embedding, queryEmbedding)), lexicalScore(memory.content, input.query), input, config.retrieval)).filter((memory) => memory.score >= minScore).sort((a, b) => b.score - a.score).slice(0, limit);
|
|
480
725
|
for (const memory of scored) {
|
|
@@ -493,7 +738,13 @@ function createInMemoryClient(config) {
|
|
|
493
738
|
assertOpen();
|
|
494
739
|
return buildContext({
|
|
495
740
|
input,
|
|
496
|
-
recall: (contextInput) =>
|
|
741
|
+
recall: (contextInput) => {
|
|
742
|
+
const recallInput = contextInputToRecallInput(contextInput);
|
|
743
|
+
return this.recall(input.signal === void 0 ? recallInput : {
|
|
744
|
+
...recallInput,
|
|
745
|
+
signal: input.signal
|
|
746
|
+
});
|
|
747
|
+
}
|
|
497
748
|
});
|
|
498
749
|
});
|
|
499
750
|
},
|
|
@@ -522,6 +773,22 @@ function createInMemoryClient(config) {
|
|
|
522
773
|
return deleted;
|
|
523
774
|
}, (count) => ({ count })).then(() => void 0);
|
|
524
775
|
},
|
|
776
|
+
async prune(input) {
|
|
777
|
+
return observe(config, "in-memory", "prune", input.entityId === void 0 ? {} : { entityId: input.entityId }, async () => {
|
|
778
|
+
assertOpen();
|
|
779
|
+
validatePruneInput(input);
|
|
780
|
+
const now = /* @__PURE__ */ new Date();
|
|
781
|
+
const matched = [];
|
|
782
|
+
for (const memory of memories.values()) if (matchesPruneFilter(memory, input, now)) matched.push(memory.id);
|
|
783
|
+
const dryRun = input.dryRun === true;
|
|
784
|
+
if (!dryRun) for (const id of matched) memories.delete(id);
|
|
785
|
+
return {
|
|
786
|
+
matchedCount: matched.length,
|
|
787
|
+
deletedCount: dryRun ? 0 : matched.length,
|
|
788
|
+
dryRun
|
|
789
|
+
};
|
|
790
|
+
}, (result) => ({ count: result.deletedCount }));
|
|
791
|
+
},
|
|
525
792
|
async stats(input) {
|
|
526
793
|
return observe(config, "in-memory", "stats", input?.entityId ? { entityId: input.entityId } : {}, async () => {
|
|
527
794
|
assertOpen();
|
|
@@ -582,6 +849,15 @@ async function deleteMemory(db, entityId, memoryId) {
|
|
|
582
849
|
async function deleteMemoriesForEntity(db, entityId) {
|
|
583
850
|
return (await db.delete(memoriesTable).where(eq(memoriesTable.entityId, entityId)).returning({ id: memoriesTable.id })).length;
|
|
584
851
|
}
|
|
852
|
+
function pruneConditions(filter) {
|
|
853
|
+
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, filter.tags && filter.tags.length > 0 ? sql`${memoriesTable.tags} @> ${filter.tags}` : void 0, filter.maxImportanceLevels && filter.maxImportanceLevels.length > 0 ? inArray(memoriesTable.importance, [...filter.maxImportanceLevels]) : void 0);
|
|
854
|
+
}
|
|
855
|
+
async function countPruneMatches(db, filter) {
|
|
856
|
+
return (await db.select({ count: sql`count(*)::int` }).from(memoriesTable).where(pruneConditions(filter)))[0]?.count ?? 0;
|
|
857
|
+
}
|
|
858
|
+
async function pruneMemories(db, filter) {
|
|
859
|
+
return (await db.delete(memoriesTable).where(pruneConditions(filter)).returning({ id: memoriesTable.id })).length;
|
|
860
|
+
}
|
|
585
861
|
async function markMemoryAccessed(db, memoryIds) {
|
|
586
862
|
if (memoryIds.length === 0) return;
|
|
587
863
|
await db.update(memoriesTable).set({
|
|
@@ -670,7 +946,10 @@ async function lexicalSearch(db, input) {
|
|
|
670
946
|
//#endregion
|
|
671
947
|
//#region src/retrieval/index.ts
|
|
672
948
|
async function hybridRecall(input) {
|
|
673
|
-
const queryEmbedding = await embedOne(input.embedder, input.input.query
|
|
949
|
+
const queryEmbedding = await embedOne(input.embedder, input.input.query, {
|
|
950
|
+
...input.signal === void 0 ? {} : { signal: input.signal },
|
|
951
|
+
...input.resilience === void 0 ? {} : { resilience: input.resilience }
|
|
952
|
+
});
|
|
674
953
|
const candidateMultiplier = input.retrieval?.candidateMultiplier ?? 3;
|
|
675
954
|
const candidateLimit = Math.max(input.limit, input.limit * candidateMultiplier);
|
|
676
955
|
const [vectorRows, lexicalRows] = await Promise.all([vectorSearch(input.db, {
|
|
@@ -703,6 +982,27 @@ async function hybridRecall(input) {
|
|
|
703
982
|
}
|
|
704
983
|
//#endregion
|
|
705
984
|
//#region src/memory/postgres.ts
|
|
985
|
+
const IMPORTANCE_LEVELS = [
|
|
986
|
+
"low",
|
|
987
|
+
"normal",
|
|
988
|
+
"high",
|
|
989
|
+
"critical"
|
|
990
|
+
];
|
|
991
|
+
function importanceCeilingLevels(max) {
|
|
992
|
+
return IMPORTANCE_LEVELS.filter((level) => IMPORTANCE_RANK[level] <= IMPORTANCE_RANK[max]);
|
|
993
|
+
}
|
|
994
|
+
function toPruneFilter(input) {
|
|
995
|
+
const filter = {};
|
|
996
|
+
if (input.entityId !== void 0) filter.entityId = input.entityId;
|
|
997
|
+
if (input.expired === true) filter.expired = true;
|
|
998
|
+
if (input.superseded === true) filter.superseded = true;
|
|
999
|
+
if (input.createdBefore !== void 0) filter.createdBefore = input.createdBefore;
|
|
1000
|
+
if (input.notAccessedSince !== void 0) filter.notAccessedSince = input.notAccessedSince;
|
|
1001
|
+
if (input.types !== void 0 && input.types.length > 0) filter.types = input.types;
|
|
1002
|
+
if (input.tags !== void 0 && input.tags.length > 0) filter.tags = input.tags;
|
|
1003
|
+
if (input.maxImportance !== void 0) filter.maxImportanceLevels = importanceCeilingLevels(input.maxImportance);
|
|
1004
|
+
return filter;
|
|
1005
|
+
}
|
|
706
1006
|
function createPostgresClient(config, handle) {
|
|
707
1007
|
let closed = false;
|
|
708
1008
|
function assertOpen() {
|
|
@@ -712,7 +1012,10 @@ function createPostgresClient(config, handle) {
|
|
|
712
1012
|
return observe(config, "postgres", "remember", { entityId: input.entityId }, async () => {
|
|
713
1013
|
assertOpen();
|
|
714
1014
|
validateRememberInput(input);
|
|
715
|
-
const embedding = await embedOne(config.embedder, input.content
|
|
1015
|
+
const embedding = await embedOne(config.embedder, input.content, {
|
|
1016
|
+
...input.signal === void 0 ? {} : { signal: input.signal },
|
|
1017
|
+
...config.provider === void 0 ? {} : { resilience: config.provider }
|
|
1018
|
+
});
|
|
716
1019
|
const now = /* @__PURE__ */ new Date();
|
|
717
1020
|
return rowToMemory(await insertMemory(handle.db, {
|
|
718
1021
|
id: createId(),
|
|
@@ -764,7 +1067,9 @@ function createPostgresClient(config, handle) {
|
|
|
764
1067
|
input,
|
|
765
1068
|
limit,
|
|
766
1069
|
minScore,
|
|
767
|
-
retrieval: config.retrieval
|
|
1070
|
+
retrieval: config.retrieval,
|
|
1071
|
+
...input.signal === void 0 ? {} : { signal: input.signal },
|
|
1072
|
+
...config.provider === void 0 ? {} : { resilience: config.provider }
|
|
768
1073
|
});
|
|
769
1074
|
}, (result) => ({ count: result.length }));
|
|
770
1075
|
},
|
|
@@ -773,7 +1078,13 @@ function createPostgresClient(config, handle) {
|
|
|
773
1078
|
assertOpen();
|
|
774
1079
|
return buildContext({
|
|
775
1080
|
input,
|
|
776
|
-
recall: (contextInput) =>
|
|
1081
|
+
recall: (contextInput) => {
|
|
1082
|
+
const recallInput = contextInputToRecallInput(contextInput);
|
|
1083
|
+
return this.recall(input.signal === void 0 ? recallInput : {
|
|
1084
|
+
...recallInput,
|
|
1085
|
+
signal: input.signal
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
777
1088
|
});
|
|
778
1089
|
});
|
|
779
1090
|
},
|
|
@@ -795,6 +1106,24 @@ function createPostgresClient(config, handle) {
|
|
|
795
1106
|
await Promise.all([deleteMemoriesForEntity(handle.db, input.entityId), deleteEventsForEntity(handle.db, input.entityId)]);
|
|
796
1107
|
});
|
|
797
1108
|
},
|
|
1109
|
+
async prune(input) {
|
|
1110
|
+
return observe(config, "postgres", "prune", input.entityId === void 0 ? {} : { entityId: input.entityId }, async () => {
|
|
1111
|
+
assertOpen();
|
|
1112
|
+
validatePruneInput(input);
|
|
1113
|
+
const filter = toPruneFilter(input);
|
|
1114
|
+
if (input.dryRun === true) return {
|
|
1115
|
+
matchedCount: await countPruneMatches(handle.db, filter),
|
|
1116
|
+
deletedCount: 0,
|
|
1117
|
+
dryRun: true
|
|
1118
|
+
};
|
|
1119
|
+
const deletedCount = await pruneMemories(handle.db, filter);
|
|
1120
|
+
return {
|
|
1121
|
+
matchedCount: deletedCount,
|
|
1122
|
+
deletedCount,
|
|
1123
|
+
dryRun: false
|
|
1124
|
+
};
|
|
1125
|
+
}, (result) => ({ count: result.deletedCount }));
|
|
1126
|
+
},
|
|
798
1127
|
async stats(input) {
|
|
799
1128
|
return observe(config, "postgres", "stats", input?.entityId ? { entityId: input.entityId } : {}, async () => {
|
|
800
1129
|
assertOpen();
|
|
@@ -833,6 +1162,31 @@ function createPostgresClient(config, handle) {
|
|
|
833
1162
|
}
|
|
834
1163
|
//#endregion
|
|
835
1164
|
//#region src/client.ts
|
|
1165
|
+
/**
|
|
1166
|
+
* Create a Mnemocyte client.
|
|
1167
|
+
*
|
|
1168
|
+
* When {@link MnemocyteConfig.databaseUrl} is provided, a Postgres-backed
|
|
1169
|
+
* client is returned (requires the `pgvector` extension and the bundled
|
|
1170
|
+
* migration applied). Otherwise an in-memory client is returned, intended
|
|
1171
|
+
* for tests, demos, and short-lived processes.
|
|
1172
|
+
*
|
|
1173
|
+
* The returned client owns any underlying resources (e.g. the Postgres
|
|
1174
|
+
* connection pool). Call {@link MnemocyteClient.close} when finished to
|
|
1175
|
+
* release them.
|
|
1176
|
+
*
|
|
1177
|
+
* @param config - Client configuration. {@link MnemocyteConfig.embedder} is required.
|
|
1178
|
+
* @returns A {@link MnemocyteClient} instance.
|
|
1179
|
+
* @throws {MnemocyteError} With code `"CONFIG"` if `embedder` is missing or invalid,
|
|
1180
|
+
* or `"VALIDATION"` if `databaseUrl` is provided but empty.
|
|
1181
|
+
*
|
|
1182
|
+
* @example Postgres-backed client
|
|
1183
|
+
* ```ts
|
|
1184
|
+
* const client = createMnemocyte({
|
|
1185
|
+
* databaseUrl: process.env.DATABASE_URL,
|
|
1186
|
+
* embedder,
|
|
1187
|
+
* });
|
|
1188
|
+
* ```
|
|
1189
|
+
*/
|
|
836
1190
|
function createMnemocyte(config) {
|
|
837
1191
|
assertEmbedder(config.embedder);
|
|
838
1192
|
if (config.databaseUrl) {
|