@anuma/sdk 9.1.4 → 9.1.5

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.
@@ -183,6 +183,51 @@ interface EntityOperationsContext {
183
183
  canWrite?: () => Promise<boolean>;
184
184
  }
185
185
 
186
+ /**
187
+ * Class-B task types — the vocabulary of the `X-Anuma-Task-Type` header.
188
+ *
189
+ * A Class-B flow has ONE fixed purpose and no conversation: extract memories
190
+ * from a snippet, pick a topic, consolidate duplicates, classify a query. Today
191
+ * each of those builds its own system prompt on the client and sends it as a
192
+ * `role:system` message, which makes the prompt a suggestion rather than a
193
+ * contract — a caller can reword it into "you are a general assistant" and get
194
+ * general chat at internal-endpoint prices.
195
+ *
196
+ * The fix is that the SERVER owns the text and the client only names the task.
197
+ * This header is the name. The portal allowlists it against its own copy of this
198
+ * enum (ai-portal `internal/systemprompt/tasks.go`) and DROPS anything it does
199
+ * not recognise, so an unknown value degrades to "no task declared" rather than
200
+ * failing the request.
201
+ *
202
+ * ⚠ These string values are a wire contract with ai-portal. Adding one here
203
+ * before the portal knows it is harmless (it is dropped); renaming or removing
204
+ * one silently stops that flow being counted. Keep them in step with
205
+ * `AllTaskTypes` on the portal side, and see ai-portal
206
+ * `docs/BACKEND_OWNED_TASK_PROMPTS.md` for the rollout.
207
+ *
208
+ * ⚠ RELEASE ORDER. This union is a PUBLIC type, so a split is breaking in both
209
+ * directions and has to be sequenced:
210
+ *
211
+ * 1. merge + publish this package;
212
+ * 2. bump `@anuma/sdk` in the consumer and switch its call sites to the new
213
+ * names in the same commit.
214
+ *
215
+ * A consumer on the OLD version cannot compile the new names, and a consumer on
216
+ * the NEW version cannot compile the retired ones (`app_inspiration` is gone
217
+ * here, replaced by the three `app_inspiration_*` names). Both halves of that
218
+ * are type-only — the wire itself always degrades safely, because the portal
219
+ * drops any value it does not recognise. Concretely: ai-memoryless-client#5782
220
+ * is blocked on this publish for exactly that reason.
221
+ *
222
+ * Deliberately ABSENT: agent personas, `reflect()` when it answers the user, and
223
+ * the app-idea chat. Those carry an open conversation with the user, so a fixed
224
+ * server-owned prompt would be wrong rather than merely premature — the same
225
+ * boundary {@link INTERNAL_FLOW_MARKER} draws.
226
+ *
227
+ * @public
228
+ */
229
+ type TaskType = "title" | "commit_message" | "memory_extract" | "memory_import_extract" | "memory_dedup" | "memory_decay" | "memory_injection_check" | "memory_verify_support" | "memory_autosort" | "memory_topic" | "memory_consolidate" | "memory_decompose" | "memory_profile_synth" | "memory_graph" | "memory_recall_embed" | "memory_retain_embed" | "folder_context" | "selection_memory" | "summarize" | "style_analysis" | "classify_search" | "app_inspiration_generate" | "app_inspiration_trends" | "app_inspiration_remix" | "spotlight_image" | "slide_image" | "slide_video" | "slide_music" | "slide_sfx" | "media_video" | "media_audio" | "media_bg_removal" | "media_image_preset" | "tool_app_builder" | "tool_document_builder" | "tool_slides" | "tool_slides_image_gen" | "connector_guidance" | "cf_task" | "xbot_intent" | "xbot_council" | "scheduled_task";
230
+
186
231
  /**
187
232
  * Auth for portal LLM calls (extraction, consolidation, decomposition,
188
233
  * reflection). Mirrors `memoryEngine`'s `EmbeddingOptions` dual-auth:
@@ -202,6 +247,76 @@ interface PortalLlmAuth {
202
247
  getToken?: () => Promise<string | null>;
203
248
  }
204
249
 
250
+ /**
251
+ * Options for embedding generation
252
+ *
253
+ * Supports two auth methods:
254
+ * - `getToken`: For Privy identity tokens (uses Authorization: Bearer header)
255
+ * - `apiKey`: For direct API keys (uses X-API-Key header)
256
+ *
257
+ * At least one of `getToken` or `apiKey` must be provided.
258
+ */
259
+ interface EmbeddingOptions {
260
+ taskType?: TaskType;
261
+ /** Function to get auth token (e.g., Privy's getIdentityToken). Uses Authorization: Bearer header. */
262
+ getToken?: () => Promise<string | null>;
263
+ /** Direct API key for server-side usage. Uses X-API-Key header. */
264
+ apiKey?: string;
265
+ /** Base URL for the API */
266
+ baseUrl?: string;
267
+ /** Embedding model to use */
268
+ model?: string;
269
+ /** Max texts per API call for batch embeddings (default: 100). Larger arrays are split into chunks. */
270
+ batchSize?: number;
271
+ /**
272
+ * Optional in-memory cache for embedding vectors. When provided, texts
273
+ * are looked up in this map before calling the API, and new embeddings
274
+ * are stored after generation. Useful when the same texts are embedded
275
+ * repeatedly (e.g., across eval iterations or re-indexing runs).
276
+ *
277
+ * Values are stored as `Float32Array` (the embedding model's native
278
+ * precision) rather than a float64 `number[]`, halving the resident RAM of
279
+ * the cache with no precision loss. `generateEmbedding(s)` still return
280
+ * `number[]` at the API boundary, so callers are unaffected.
281
+ */
282
+ cache?: Map<string, Float32Array>;
283
+ /** Called after each embedding API call with the token usage from the response. */
284
+ onUsage?: (usage: {
285
+ promptTokens: number;
286
+ totalTokens: number;
287
+ }) => void;
288
+ /**
289
+ * Optional transform applied to each text immediately before it is sent to
290
+ * the embeddings endpoint (e.g. `PiiRedactor.maskText`). The cache and result
291
+ * ordering still key on the original text — only the API request body is
292
+ * transformed — so callers can keep storing/displaying the original value
293
+ * while real PII never reaches the server. Used when PII redaction is active.
294
+ */
295
+ maskInput?: (text: string) => string;
296
+ /**
297
+ * Deadline, in ms, for EACH embeddings HTTP attempt (default 15000). An
298
+ * attempt that exceeds it is aborted and counts as a transient failure, so the
299
+ * bounded retry still applies. `0` disables the deadline.
300
+ */
301
+ timeoutMs?: number;
302
+ /**
303
+ * Deadline, in ms, for the `getToken()` read that precedes a request (default
304
+ * 10000). A provider that never settles rejects instead of hanging the
305
+ * embedding — and the recall waiting on it. `0` disables the deadline.
306
+ */
307
+ tokenTimeoutMs?: number;
308
+ /**
309
+ * Overall deadline, in ms, for one `generateEmbedding` or `generateEmbeddings` call — the token read,
310
+ * every retry attempt and the backoff between them. Unset (the default) means
311
+ * only the per-attempt deadlines apply, which is right for background/bulk
312
+ * embeds. The recall query path sets it (see
313
+ * `RecallOptions.queryEmbedTotalTimeoutMs`) so an outage degrades a turn to
314
+ * BM25 within a few seconds instead of ~4 x `timeoutMs`. Batch calls share
315
+ * one budget across authentication, all chunks, retries and backoff.
316
+ */
317
+ totalTimeoutMs?: number;
318
+ }
319
+
205
320
  interface RecencyOptions {
206
321
  /** Override "now" — useful for deterministic tests and back-dated benchmarks. */
207
322
  now?: Date;
@@ -213,6 +328,13 @@ interface RecencyOptions {
213
328
  noDateMultiplier?: number;
214
329
  }
215
330
 
331
+ type VaultCorpusCache = WeakMap<VaultMemoryOperationsContext, Map<string, Promise<StoredVaultMemory[]>>>;
332
+ type RowRequests = Map<EmbeddingOptions["maskInput"], Map<string, Promise<number[]>>>;
333
+ interface VaultPreparation {
334
+ corpora: VaultCorpusCache;
335
+ rows: WeakMap<VaultMemoryOperationsContext, RowRequests>;
336
+ }
337
+
216
338
  /**
217
339
  * How a fact's evidence has been evolving.
218
340
  *
@@ -300,6 +422,7 @@ interface RankedMemory {
300
422
  updatedAt: Date;
301
423
  }
302
424
  interface RecallOptions {
425
+ /** @internal */ vaultPreparation?: VaultPreparation;
303
426
  /** Embedding cache shared with the rest of the turn, so the query is embedded once across lanes and the send. */
304
427
  embeddingCache?: Map<string, Float32Array>;
305
428
  /** Which kinds to search. Default: ['fact']. */
@@ -183,6 +183,51 @@ interface EntityOperationsContext {
183
183
  canWrite?: () => Promise<boolean>;
184
184
  }
185
185
 
186
+ /**
187
+ * Class-B task types — the vocabulary of the `X-Anuma-Task-Type` header.
188
+ *
189
+ * A Class-B flow has ONE fixed purpose and no conversation: extract memories
190
+ * from a snippet, pick a topic, consolidate duplicates, classify a query. Today
191
+ * each of those builds its own system prompt on the client and sends it as a
192
+ * `role:system` message, which makes the prompt a suggestion rather than a
193
+ * contract — a caller can reword it into "you are a general assistant" and get
194
+ * general chat at internal-endpoint prices.
195
+ *
196
+ * The fix is that the SERVER owns the text and the client only names the task.
197
+ * This header is the name. The portal allowlists it against its own copy of this
198
+ * enum (ai-portal `internal/systemprompt/tasks.go`) and DROPS anything it does
199
+ * not recognise, so an unknown value degrades to "no task declared" rather than
200
+ * failing the request.
201
+ *
202
+ * ⚠ These string values are a wire contract with ai-portal. Adding one here
203
+ * before the portal knows it is harmless (it is dropped); renaming or removing
204
+ * one silently stops that flow being counted. Keep them in step with
205
+ * `AllTaskTypes` on the portal side, and see ai-portal
206
+ * `docs/BACKEND_OWNED_TASK_PROMPTS.md` for the rollout.
207
+ *
208
+ * ⚠ RELEASE ORDER. This union is a PUBLIC type, so a split is breaking in both
209
+ * directions and has to be sequenced:
210
+ *
211
+ * 1. merge + publish this package;
212
+ * 2. bump `@anuma/sdk` in the consumer and switch its call sites to the new
213
+ * names in the same commit.
214
+ *
215
+ * A consumer on the OLD version cannot compile the new names, and a consumer on
216
+ * the NEW version cannot compile the retired ones (`app_inspiration` is gone
217
+ * here, replaced by the three `app_inspiration_*` names). Both halves of that
218
+ * are type-only — the wire itself always degrades safely, because the portal
219
+ * drops any value it does not recognise. Concretely: ai-memoryless-client#5782
220
+ * is blocked on this publish for exactly that reason.
221
+ *
222
+ * Deliberately ABSENT: agent personas, `reflect()` when it answers the user, and
223
+ * the app-idea chat. Those carry an open conversation with the user, so a fixed
224
+ * server-owned prompt would be wrong rather than merely premature — the same
225
+ * boundary {@link INTERNAL_FLOW_MARKER} draws.
226
+ *
227
+ * @public
228
+ */
229
+ type TaskType = "title" | "commit_message" | "memory_extract" | "memory_import_extract" | "memory_dedup" | "memory_decay" | "memory_injection_check" | "memory_verify_support" | "memory_autosort" | "memory_topic" | "memory_consolidate" | "memory_decompose" | "memory_profile_synth" | "memory_graph" | "memory_recall_embed" | "memory_retain_embed" | "folder_context" | "selection_memory" | "summarize" | "style_analysis" | "classify_search" | "app_inspiration_generate" | "app_inspiration_trends" | "app_inspiration_remix" | "spotlight_image" | "slide_image" | "slide_video" | "slide_music" | "slide_sfx" | "media_video" | "media_audio" | "media_bg_removal" | "media_image_preset" | "tool_app_builder" | "tool_document_builder" | "tool_slides" | "tool_slides_image_gen" | "connector_guidance" | "cf_task" | "xbot_intent" | "xbot_council" | "scheduled_task";
230
+
186
231
  /**
187
232
  * Auth for portal LLM calls (extraction, consolidation, decomposition,
188
233
  * reflection). Mirrors `memoryEngine`'s `EmbeddingOptions` dual-auth:
@@ -202,6 +247,76 @@ interface PortalLlmAuth {
202
247
  getToken?: () => Promise<string | null>;
203
248
  }
204
249
 
250
+ /**
251
+ * Options for embedding generation
252
+ *
253
+ * Supports two auth methods:
254
+ * - `getToken`: For Privy identity tokens (uses Authorization: Bearer header)
255
+ * - `apiKey`: For direct API keys (uses X-API-Key header)
256
+ *
257
+ * At least one of `getToken` or `apiKey` must be provided.
258
+ */
259
+ interface EmbeddingOptions {
260
+ taskType?: TaskType;
261
+ /** Function to get auth token (e.g., Privy's getIdentityToken). Uses Authorization: Bearer header. */
262
+ getToken?: () => Promise<string | null>;
263
+ /** Direct API key for server-side usage. Uses X-API-Key header. */
264
+ apiKey?: string;
265
+ /** Base URL for the API */
266
+ baseUrl?: string;
267
+ /** Embedding model to use */
268
+ model?: string;
269
+ /** Max texts per API call for batch embeddings (default: 100). Larger arrays are split into chunks. */
270
+ batchSize?: number;
271
+ /**
272
+ * Optional in-memory cache for embedding vectors. When provided, texts
273
+ * are looked up in this map before calling the API, and new embeddings
274
+ * are stored after generation. Useful when the same texts are embedded
275
+ * repeatedly (e.g., across eval iterations or re-indexing runs).
276
+ *
277
+ * Values are stored as `Float32Array` (the embedding model's native
278
+ * precision) rather than a float64 `number[]`, halving the resident RAM of
279
+ * the cache with no precision loss. `generateEmbedding(s)` still return
280
+ * `number[]` at the API boundary, so callers are unaffected.
281
+ */
282
+ cache?: Map<string, Float32Array>;
283
+ /** Called after each embedding API call with the token usage from the response. */
284
+ onUsage?: (usage: {
285
+ promptTokens: number;
286
+ totalTokens: number;
287
+ }) => void;
288
+ /**
289
+ * Optional transform applied to each text immediately before it is sent to
290
+ * the embeddings endpoint (e.g. `PiiRedactor.maskText`). The cache and result
291
+ * ordering still key on the original text — only the API request body is
292
+ * transformed — so callers can keep storing/displaying the original value
293
+ * while real PII never reaches the server. Used when PII redaction is active.
294
+ */
295
+ maskInput?: (text: string) => string;
296
+ /**
297
+ * Deadline, in ms, for EACH embeddings HTTP attempt (default 15000). An
298
+ * attempt that exceeds it is aborted and counts as a transient failure, so the
299
+ * bounded retry still applies. `0` disables the deadline.
300
+ */
301
+ timeoutMs?: number;
302
+ /**
303
+ * Deadline, in ms, for the `getToken()` read that precedes a request (default
304
+ * 10000). A provider that never settles rejects instead of hanging the
305
+ * embedding — and the recall waiting on it. `0` disables the deadline.
306
+ */
307
+ tokenTimeoutMs?: number;
308
+ /**
309
+ * Overall deadline, in ms, for one `generateEmbedding` or `generateEmbeddings` call — the token read,
310
+ * every retry attempt and the backoff between them. Unset (the default) means
311
+ * only the per-attempt deadlines apply, which is right for background/bulk
312
+ * embeds. The recall query path sets it (see
313
+ * `RecallOptions.queryEmbedTotalTimeoutMs`) so an outage degrades a turn to
314
+ * BM25 within a few seconds instead of ~4 x `timeoutMs`. Batch calls share
315
+ * one budget across authentication, all chunks, retries and backoff.
316
+ */
317
+ totalTimeoutMs?: number;
318
+ }
319
+
205
320
  interface RecencyOptions {
206
321
  /** Override "now" — useful for deterministic tests and back-dated benchmarks. */
207
322
  now?: Date;
@@ -213,6 +328,13 @@ interface RecencyOptions {
213
328
  noDateMultiplier?: number;
214
329
  }
215
330
 
331
+ type VaultCorpusCache = WeakMap<VaultMemoryOperationsContext, Map<string, Promise<StoredVaultMemory[]>>>;
332
+ type RowRequests = Map<EmbeddingOptions["maskInput"], Map<string, Promise<number[]>>>;
333
+ interface VaultPreparation {
334
+ corpora: VaultCorpusCache;
335
+ rows: WeakMap<VaultMemoryOperationsContext, RowRequests>;
336
+ }
337
+
216
338
  /**
217
339
  * How a fact's evidence has been evolving.
218
340
  *
@@ -300,6 +422,7 @@ interface RankedMemory {
300
422
  updatedAt: Date;
301
423
  }
302
424
  interface RecallOptions {
425
+ /** @internal */ vaultPreparation?: VaultPreparation;
303
426
  /** Embedding cache shared with the rest of the turn, so the query is embedded once across lanes and the send. */
304
427
  embeddingCache?: Map<string, Float32Array>;
305
428
  /** Which kinds to search. Default: ['fact']. */
@@ -2338,15 +2338,16 @@ async function refreshEncryptionKeyIfMatches(walletAddress, probeCiphertext, sig
2338
2338
  if (!parsed) {
2339
2339
  return false;
2340
2340
  }
2341
+ const startEpoch = sessionEpoch;
2341
2342
  const cached = refreshCandidatesByAddress.get(walletAddress);
2342
2343
  if (cached) {
2343
- return tryCommitCandidatesIfProbeMatches(walletAddress, parsed, cached);
2344
+ return tryCommitCandidatesIfProbeMatches(walletAddress, parsed, cached, startEpoch);
2344
2345
  }
2345
2346
  let derivePromise = pendingCandidateDerives.get(walletAddress);
2346
2347
  if (!derivePromise) {
2347
2348
  derivePromise = (async () => {
2348
2349
  const candidates2 = await deriveRefreshCandidates(signMessage, embeddedWalletSigner);
2349
- if (candidates2) {
2350
+ if (candidates2 && sessionEpoch === startEpoch) {
2350
2351
  refreshCandidatesByAddress.set(walletAddress, candidates2);
2351
2352
  }
2352
2353
  return candidates2;
@@ -2358,10 +2359,10 @@ async function refreshEncryptionKeyIfMatches(walletAddress, probeCiphertext, sig
2358
2359
  pendingCandidateDerives.set(walletAddress, derivePromise);
2359
2360
  }
2360
2361
  const candidates = await derivePromise;
2361
- if (!candidates) {
2362
+ if (!candidates || sessionEpoch !== startEpoch) {
2362
2363
  return false;
2363
2364
  }
2364
- return tryCommitCandidatesIfProbeMatches(walletAddress, parsed, candidates);
2365
+ return tryCommitCandidatesIfProbeMatches(walletAddress, parsed, candidates, startEpoch);
2365
2366
  }
2366
2367
  function parsePrefixedCiphertext(probeCiphertext) {
2367
2368
  const version = probeCiphertext.startsWith("enc:v3:") ? "v3" : probeCiphertext.startsWith("enc:v2:") ? "v2" : null;
@@ -2404,7 +2405,7 @@ async function deriveRefreshCandidates(signMessage, embeddedWalletSigner) {
2404
2405
  }
2405
2406
  return { legacy: legacyKey, current: currentKey };
2406
2407
  }
2407
- async function tryCommitCandidatesIfProbeMatches(walletAddress, parsed, candidates) {
2408
+ async function tryCommitCandidatesIfProbeMatches(walletAddress, parsed, candidates, startEpoch) {
2408
2409
  const candidateHex = parsed.version === "v2" ? candidates.legacy : candidates.current;
2409
2410
  const keyBytes = hexToBytes(candidateHex);
2410
2411
  const candidateCryptoKey = await crypto.subtle.importKey(
@@ -2422,6 +2423,9 @@ async function tryCommitCandidatesIfProbeMatches(walletAddress, parsed, candidat
2422
2423
  );
2423
2424
  return false;
2424
2425
  }
2426
+ if (sessionEpoch !== startEpoch) {
2427
+ return false;
2428
+ }
2425
2429
  setStoredKey(walletAddress, { legacy: candidates.legacy, current: candidates.current });
2426
2430
  cryptoKeyCache.delete(`${walletAddress}:v2`);
2427
2431
  cryptoKeyCache.delete(`${walletAddress}:v3`);
@@ -19571,6 +19575,104 @@ function validate(parsed, originalQuery) {
19571
19575
  return { mode, subQueries };
19572
19576
  }
19573
19577
 
19578
+ // src/lib/memoryVault/vaultPreparation.ts
19579
+ function createVaultPreparation() {
19580
+ return { corpora: /* @__PURE__ */ new WeakMap(), rows: /* @__PURE__ */ new WeakMap() };
19581
+ }
19582
+ var pendingRows = /* @__PURE__ */ new WeakMap();
19583
+ function loadVaultCorpus(ctx, options, preparation) {
19584
+ if (!preparation) return getAllVaultMemoriesOp(ctx, options);
19585
+ const corpusCache = preparation.corpora;
19586
+ let pending = corpusCache.get(ctx);
19587
+ if (!pending) {
19588
+ pending = /* @__PURE__ */ new Map();
19589
+ corpusCache.set(ctx, pending);
19590
+ }
19591
+ const key = JSON.stringify(options ?? {});
19592
+ let request = pending.get(key);
19593
+ if (!request) {
19594
+ const entries = pending;
19595
+ request = getAllVaultMemoriesOp(ctx, options).catch((error) => {
19596
+ entries.delete(key);
19597
+ throw error;
19598
+ });
19599
+ pending.set(key, request);
19600
+ }
19601
+ return request;
19602
+ }
19603
+ function rowRequestsFor(registry2, ctx, maskInput) {
19604
+ let byMask = registry2.get(ctx);
19605
+ if (!byMask) {
19606
+ byMask = /* @__PURE__ */ new Map();
19607
+ registry2.set(ctx, byMask);
19608
+ }
19609
+ let requests = byMask.get(maskInput);
19610
+ if (!requests) {
19611
+ requests = /* @__PURE__ */ new Map();
19612
+ byMask.set(maskInput, requests);
19613
+ }
19614
+ return requests;
19615
+ }
19616
+ function embedVaultRows(ctx, options, rows, preparation) {
19617
+ const pending = rowRequestsFor(pendingRows, ctx, options.maskInput);
19618
+ const prepared = preparation && rowRequestsFor(preparation.rows, ctx, options.maskInput);
19619
+ const model = options.model ?? DEFAULT_API_EMBEDDING_MODEL;
19620
+ const keys = rows.map(
19621
+ (row) => JSON.stringify([
19622
+ row.uniqueId,
19623
+ row.updatedAt?.getTime(),
19624
+ row.content,
19625
+ model,
19626
+ options.baseUrl,
19627
+ options.timeoutMs,
19628
+ options.tokenTimeoutMs,
19629
+ options.totalTimeoutMs
19630
+ ])
19631
+ );
19632
+ const missing = /* @__PURE__ */ new Map();
19633
+ rows.forEach((row, index) => {
19634
+ if (!pending.has(keys[index]) && !prepared?.has(keys[index])) missing.set(keys[index], row);
19635
+ });
19636
+ if (missing.size > 0) {
19637
+ const entries = pending;
19638
+ const batch = Promise.resolve().then(
19639
+ () => generateEmbeddings(
19640
+ [...missing.values()].map((row) => row.content),
19641
+ options
19642
+ )
19643
+ );
19644
+ [...missing].forEach(([key, row], index) => {
19645
+ const request = batch.then((vectors) => {
19646
+ const vector = vectors[index];
19647
+ updateVaultMemoryEmbeddingOp(ctx, row.uniqueId, JSON.stringify(vector), model, {
19648
+ content: row.content,
19649
+ updatedAt: row.updatedAt?.getTime()
19650
+ }).catch(() => {
19651
+ });
19652
+ return vector;
19653
+ }).finally(() => {
19654
+ entries.delete(key);
19655
+ if (entries.size === 0) pendingRows.get(ctx)?.delete(options.maskInput);
19656
+ });
19657
+ pending.set(key, request);
19658
+ });
19659
+ }
19660
+ return Promise.all(
19661
+ keys.map((key) => {
19662
+ const cached = prepared?.get(key);
19663
+ if (cached) return cached;
19664
+ const request = pending.get(key);
19665
+ if (!prepared) return request;
19666
+ const retained = request.catch((error) => {
19667
+ prepared.delete(key);
19668
+ throw error;
19669
+ });
19670
+ prepared.set(key, retained);
19671
+ return retained;
19672
+ })
19673
+ );
19674
+ }
19675
+
19574
19676
  // src/lib/memoryVault/vectorVersion.ts
19575
19677
  var vectorRowVersion = /* @__PURE__ */ new WeakMap();
19576
19678
  function contentFingerprint(content) {
@@ -20133,11 +20235,11 @@ async function rankComposite(originalQuery, originalQueryEmbedding, subQueries,
20133
20235
  return combined.slice(0, limit);
20134
20236
  }
20135
20237
  async function preEmbedVaultMemories(vaultCtx, embeddingOptions, cache, options) {
20136
- const memories = await getAllVaultMemoriesOp(vaultCtx, options);
20238
+ const memories = await loadVaultCorpus(vaultCtx, options);
20137
20239
  const currentModel = embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
20138
20240
  const uncachedTexts = [];
20139
20241
  const uncachedKeys = [];
20140
- const uncachedIds = [];
20242
+ const uncachedRows = [];
20141
20243
  const uncachedVersions = [];
20142
20244
  for (const m of memories) {
20143
20245
  const content = m.content;
@@ -20156,12 +20258,12 @@ async function preEmbedVaultMemories(vaultCtx, embeddingOptions, cache, options)
20156
20258
  }
20157
20259
  uncachedTexts.push(m.content);
20158
20260
  uncachedKeys.push(m.uniqueId);
20159
- uncachedIds.push(m.uniqueId);
20261
+ uncachedRows.push(m);
20160
20262
  uncachedVersions.push(m.updatedAt);
20161
20263
  }
20162
20264
  }
20163
20265
  if (uncachedTexts.length > 0) {
20164
- const embeddings = await generateEmbeddings(uncachedTexts, embeddingOptions);
20266
+ const embeddings = await embedVaultRows(vaultCtx, embeddingOptions, uncachedRows);
20165
20267
  for (let i = 0; i < uncachedKeys.length; i++) {
20166
20268
  cacheRowVector(
20167
20269
  cache,
@@ -20170,13 +20272,6 @@ async function preEmbedVaultMemories(vaultCtx, embeddingOptions, cache, options)
20170
20272
  uncachedVersions[i],
20171
20273
  uncachedTexts[i]
20172
20274
  );
20173
- updateVaultMemoryEmbeddingOp(
20174
- vaultCtx,
20175
- uncachedIds[i],
20176
- JSON.stringify(embeddings[i]),
20177
- currentModel
20178
- ).catch(() => {
20179
- });
20180
20275
  }
20181
20276
  }
20182
20277
  }
@@ -20318,10 +20413,7 @@ async function buildProjectedCorpus(query, vaultCtx, embeddingOptions, cache, qu
20318
20413
  let laneVecs;
20319
20414
  if (opts.embedStats) opts.embedStats.rowsEmbedded += laneRows.length;
20320
20415
  try {
20321
- laneVecs = await generateEmbeddings(
20322
- laneRows.map((m) => m.content),
20323
- embeddingOptions
20324
- );
20416
+ laneVecs = await embedVaultRows(vaultCtx, embeddingOptions, laneRows);
20325
20417
  } catch (err) {
20326
20418
  laneEmbedFailed = true;
20327
20419
  getLogger().warn(
@@ -20335,13 +20427,6 @@ async function buildProjectedCorpus(query, vaultCtx, embeddingOptions, cache, qu
20335
20427
  cacheRowVector(cache, m.uniqueId, vec, m.updatedAt, m.content);
20336
20428
  resolvedThisCall.add(m.uniqueId);
20337
20429
  vectored.push({ uniqueId: m.uniqueId, embedding: vec, updatedAt: m.updatedAt });
20338
- updateVaultMemoryEmbeddingOp(
20339
- vaultCtx,
20340
- m.uniqueId,
20341
- JSON.stringify(vecs[i]),
20342
- currentModel
20343
- ).catch(() => {
20344
- });
20345
20430
  });
20346
20431
  }
20347
20432
  }
@@ -20533,9 +20618,10 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
20533
20618
  };
20534
20619
  }
20535
20620
  } else {
20536
- const loaded = await getAllVaultMemoriesOp(
20621
+ const loaded = await loadVaultCorpus(
20537
20622
  vaultCtx,
20538
- Object.keys(queryOpts).length > 0 ? queryOpts : void 0
20623
+ Object.keys(queryOpts).length > 0 ? queryOpts : void 0,
20624
+ searchOptions?.vaultPreparation
20539
20625
  );
20540
20626
  vaultSize = loaded.length;
20541
20627
  rowsDecrypted = loaded.length;
@@ -20608,7 +20694,12 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
20608
20694
  let newEmbeddings;
20609
20695
  embedStats.rowsEmbedded += uncachedTexts.length;
20610
20696
  try {
20611
- newEmbeddings = await generateEmbeddings(uncachedTexts, embeddingOptions);
20697
+ newEmbeddings = await embedVaultRows(
20698
+ vaultCtx,
20699
+ embeddingOptions,
20700
+ uncachedIndices.map((index) => memories[index]),
20701
+ searchOptions?.vaultPreparation
20702
+ );
20612
20703
  } catch (err) {
20613
20704
  embeddingFailure = true;
20614
20705
  getLogger().warn(
@@ -20624,13 +20715,6 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
20624
20715
  memories[uncachedIndices[j]].updatedAt,
20625
20716
  memories[uncachedIndices[j]].content
20626
20717
  );
20627
- updateVaultMemoryEmbeddingOp(
20628
- vaultCtx,
20629
- memories[uncachedIndices[j]].uniqueId,
20630
- JSON.stringify(newEmbeddings[j]),
20631
- currentModel
20632
- ).catch(() => {
20633
- });
20634
20718
  }
20635
20719
  }
20636
20720
  }
@@ -23211,6 +23295,7 @@ async function recall(query, ctx, options = {}) {
23211
23295
  },
23212
23296
  ...options.rrfK !== void 0 && { rrfK: options.rrfK },
23213
23297
  ...options.decryptLast !== void 0 && { decryptLast: options.decryptLast },
23298
+ ...options.vaultPreparation && { vaultPreparation: options.vaultPreparation },
23214
23299
  ...subQueries && { subQueries },
23215
23300
  ...options.scopes && { scopes: options.scopes },
23216
23301
  ...options.folderId !== void 0 && { folderId: options.folderId },
@@ -26816,13 +26901,20 @@ async function synthesizeProfile(ctx, options = {}) {
26816
26901
  }
26817
26902
  }
26818
26903
  }
26904
+ const vaultPreparation = createVaultPreparation();
26819
26905
  const settled = await Promise.allSettled(
26820
26906
  facets.map(async (facet) => {
26821
26907
  const prior = previous?.sections.find((s) => s.key === facet.key);
26822
26908
  if (prior && !staleKeys.has(facet.key)) {
26823
26909
  return prior;
26824
26910
  }
26825
- return synthesizeFacet(facet, recallCtx, options, fallbackPriors.get(facet.key));
26911
+ return synthesizeFacet(
26912
+ facet,
26913
+ recallCtx,
26914
+ options,
26915
+ fallbackPriors.get(facet.key),
26916
+ vaultPreparation
26917
+ );
26826
26918
  })
26827
26919
  );
26828
26920
  const sections = settled.map((r, i) => {
@@ -26976,7 +27068,7 @@ async function attributeFacts(ctx, candidates, facets) {
26976
27068
  }
26977
27069
  return keys;
26978
27070
  }
26979
- async function synthesizeFacet(facet, ctx, options, prior) {
27071
+ async function synthesizeFacet(facet, ctx, options, prior, vaultPreparation) {
26980
27072
  const scopes = options.scopes ?? DEFAULT_SCOPES;
26981
27073
  const limit = options.limit ?? DEFAULT_FACET_RECALL_LIMIT;
26982
27074
  const factTypeWeights = { ...DEFAULT_PROFILE_FACT_TYPE_WEIGHTS, ...options.factTypeWeights };
@@ -26993,6 +27085,7 @@ async function synthesizeFacet(facet, ctx, options, prior) {
26993
27085
  };
26994
27086
  }
26995
27087
  const recalled = await recall(facet.query, ctx, {
27088
+ vaultPreparation,
26996
27089
  scopes,
26997
27090
  limit: hasReviewGate ? RECALL_MAX_LIMIT : limit,
26998
27091
  types: ["fact"],
@@ -4402,6 +4402,13 @@ interface RecencyOptions {
4402
4402
  noDateMultiplier?: number;
4403
4403
  }
4404
4404
 
4405
+ type VaultCorpusCache = WeakMap<VaultMemoryOperationsContext, Map<string, Promise<StoredVaultMemory[]>>>;
4406
+ type RowRequests = Map<EmbeddingOptions["maskInput"], Map<string, Promise<number[]>>>;
4407
+ interface VaultPreparation {
4408
+ corpora: VaultCorpusCache;
4409
+ rows: WeakMap<VaultMemoryOperationsContext, RowRequests>;
4410
+ }
4411
+
4405
4412
  declare const DEFAULT_VAULT_CACHE_SIZE = 5000;
4406
4413
  /**
4407
4414
  * Create a VaultEmbeddingCache backed by an LRU with a default cap of
@@ -4419,6 +4426,7 @@ type VaultEmbeddingCache = Map<string, Float32Array>;
4419
4426
  * Options for the vault search tool.
4420
4427
  */
4421
4428
  interface MemoryVaultSearchOptions {
4429
+ /** @internal */ vaultPreparation?: VaultPreparation;
4422
4430
  onCandidateCount?: (count: number) => void;
4423
4431
  /** Maximum number of results to return (default: 5) */
4424
4432
  limit?: number;
@@ -4957,6 +4965,7 @@ interface RankedMemory {
4957
4965
  updatedAt: Date;
4958
4966
  }
4959
4967
  interface RecallOptions {
4968
+ /** @internal */ vaultPreparation?: VaultPreparation;
4960
4969
  /** Embedding cache shared with the rest of the turn, so the query is embedded once across lanes and the send. */
4961
4970
  embeddingCache?: Map<string, Float32Array>;
4962
4971
  /** Which kinds to search. Default: ['fact']. */
@@ -4402,6 +4402,13 @@ interface RecencyOptions {
4402
4402
  noDateMultiplier?: number;
4403
4403
  }
4404
4404
 
4405
+ type VaultCorpusCache = WeakMap<VaultMemoryOperationsContext, Map<string, Promise<StoredVaultMemory[]>>>;
4406
+ type RowRequests = Map<EmbeddingOptions["maskInput"], Map<string, Promise<number[]>>>;
4407
+ interface VaultPreparation {
4408
+ corpora: VaultCorpusCache;
4409
+ rows: WeakMap<VaultMemoryOperationsContext, RowRequests>;
4410
+ }
4411
+
4405
4412
  declare const DEFAULT_VAULT_CACHE_SIZE = 5000;
4406
4413
  /**
4407
4414
  * Create a VaultEmbeddingCache backed by an LRU with a default cap of
@@ -4419,6 +4426,7 @@ type VaultEmbeddingCache = Map<string, Float32Array>;
4419
4426
  * Options for the vault search tool.
4420
4427
  */
4421
4428
  interface MemoryVaultSearchOptions {
4429
+ /** @internal */ vaultPreparation?: VaultPreparation;
4422
4430
  onCandidateCount?: (count: number) => void;
4423
4431
  /** Maximum number of results to return (default: 5) */
4424
4432
  limit?: number;
@@ -4957,6 +4965,7 @@ interface RankedMemory {
4957
4965
  updatedAt: Date;
4958
4966
  }
4959
4967
  interface RecallOptions {
4968
+ /** @internal */ vaultPreparation?: VaultPreparation;
4960
4969
  /** Embedding cache shared with the rest of the turn, so the query is embedded once across lanes and the send. */
4961
4970
  embeddingCache?: Map<string, Float32Array>;
4962
4971
  /** Which kinds to search. Default: ['fact']. */