@anuma/sdk 9.1.3 → 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.
@@ -923,15 +923,16 @@ async function refreshEncryptionKeyIfMatches(walletAddress, probeCiphertext, sig
923
923
  if (!parsed) {
924
924
  return false;
925
925
  }
926
+ const startEpoch = sessionEpoch;
926
927
  const cached = refreshCandidatesByAddress.get(walletAddress);
927
928
  if (cached) {
928
- return tryCommitCandidatesIfProbeMatches(walletAddress, parsed, cached);
929
+ return tryCommitCandidatesIfProbeMatches(walletAddress, parsed, cached, startEpoch);
929
930
  }
930
931
  let derivePromise = pendingCandidateDerives.get(walletAddress);
931
932
  if (!derivePromise) {
932
933
  derivePromise = (async () => {
933
934
  const candidates2 = await deriveRefreshCandidates(signMessage, embeddedWalletSigner);
934
- if (candidates2) {
935
+ if (candidates2 && sessionEpoch === startEpoch) {
935
936
  refreshCandidatesByAddress.set(walletAddress, candidates2);
936
937
  }
937
938
  return candidates2;
@@ -943,10 +944,10 @@ async function refreshEncryptionKeyIfMatches(walletAddress, probeCiphertext, sig
943
944
  pendingCandidateDerives.set(walletAddress, derivePromise);
944
945
  }
945
946
  const candidates = await derivePromise;
946
- if (!candidates) {
947
+ if (!candidates || sessionEpoch !== startEpoch) {
947
948
  return false;
948
949
  }
949
- return tryCommitCandidatesIfProbeMatches(walletAddress, parsed, candidates);
950
+ return tryCommitCandidatesIfProbeMatches(walletAddress, parsed, candidates, startEpoch);
950
951
  }
951
952
  function parsePrefixedCiphertext(probeCiphertext) {
952
953
  const version = probeCiphertext.startsWith("enc:v3:") ? "v3" : probeCiphertext.startsWith("enc:v2:") ? "v2" : null;
@@ -989,7 +990,7 @@ async function deriveRefreshCandidates(signMessage, embeddedWalletSigner) {
989
990
  }
990
991
  return { legacy: legacyKey, current: currentKey };
991
992
  }
992
- async function tryCommitCandidatesIfProbeMatches(walletAddress, parsed, candidates) {
993
+ async function tryCommitCandidatesIfProbeMatches(walletAddress, parsed, candidates, startEpoch) {
993
994
  const candidateHex = parsed.version === "v2" ? candidates.legacy : candidates.current;
994
995
  const keyBytes = hexToBytes(candidateHex);
995
996
  const candidateCryptoKey = await crypto.subtle.importKey(
@@ -1007,6 +1008,9 @@ async function tryCommitCandidatesIfProbeMatches(walletAddress, parsed, candidat
1007
1008
  );
1008
1009
  return false;
1009
1010
  }
1011
+ if (sessionEpoch !== startEpoch) {
1012
+ return false;
1013
+ }
1010
1014
  setStoredKey(walletAddress, { legacy: candidates.legacy, current: candidates.current });
1011
1015
  cryptoKeyCache.delete(`${walletAddress}:v2`);
1012
1016
  cryptoKeyCache.delete(`${walletAddress}:v3`);
@@ -15635,6 +15639,104 @@ function validate(parsed, originalQuery) {
15635
15639
  return { mode, subQueries };
15636
15640
  }
15637
15641
 
15642
+ // src/lib/memoryVault/vaultPreparation.ts
15643
+ function createVaultPreparation() {
15644
+ return { corpora: /* @__PURE__ */ new WeakMap(), rows: /* @__PURE__ */ new WeakMap() };
15645
+ }
15646
+ var pendingRows = /* @__PURE__ */ new WeakMap();
15647
+ function loadVaultCorpus(ctx, options, preparation) {
15648
+ if (!preparation) return getAllVaultMemoriesOp(ctx, options);
15649
+ const corpusCache = preparation.corpora;
15650
+ let pending = corpusCache.get(ctx);
15651
+ if (!pending) {
15652
+ pending = /* @__PURE__ */ new Map();
15653
+ corpusCache.set(ctx, pending);
15654
+ }
15655
+ const key = JSON.stringify(options ?? {});
15656
+ let request = pending.get(key);
15657
+ if (!request) {
15658
+ const entries = pending;
15659
+ request = getAllVaultMemoriesOp(ctx, options).catch((error) => {
15660
+ entries.delete(key);
15661
+ throw error;
15662
+ });
15663
+ pending.set(key, request);
15664
+ }
15665
+ return request;
15666
+ }
15667
+ function rowRequestsFor(registry2, ctx, maskInput) {
15668
+ let byMask = registry2.get(ctx);
15669
+ if (!byMask) {
15670
+ byMask = /* @__PURE__ */ new Map();
15671
+ registry2.set(ctx, byMask);
15672
+ }
15673
+ let requests = byMask.get(maskInput);
15674
+ if (!requests) {
15675
+ requests = /* @__PURE__ */ new Map();
15676
+ byMask.set(maskInput, requests);
15677
+ }
15678
+ return requests;
15679
+ }
15680
+ function embedVaultRows(ctx, options, rows, preparation) {
15681
+ const pending = rowRequestsFor(pendingRows, ctx, options.maskInput);
15682
+ const prepared = preparation && rowRequestsFor(preparation.rows, ctx, options.maskInput);
15683
+ const model = options.model ?? DEFAULT_API_EMBEDDING_MODEL;
15684
+ const keys = rows.map(
15685
+ (row) => JSON.stringify([
15686
+ row.uniqueId,
15687
+ row.updatedAt?.getTime(),
15688
+ row.content,
15689
+ model,
15690
+ options.baseUrl,
15691
+ options.timeoutMs,
15692
+ options.tokenTimeoutMs,
15693
+ options.totalTimeoutMs
15694
+ ])
15695
+ );
15696
+ const missing = /* @__PURE__ */ new Map();
15697
+ rows.forEach((row, index) => {
15698
+ if (!pending.has(keys[index]) && !prepared?.has(keys[index])) missing.set(keys[index], row);
15699
+ });
15700
+ if (missing.size > 0) {
15701
+ const entries = pending;
15702
+ const batch = Promise.resolve().then(
15703
+ () => generateEmbeddings(
15704
+ [...missing.values()].map((row) => row.content),
15705
+ options
15706
+ )
15707
+ );
15708
+ [...missing].forEach(([key, row], index) => {
15709
+ const request = batch.then((vectors) => {
15710
+ const vector = vectors[index];
15711
+ updateVaultMemoryEmbeddingOp(ctx, row.uniqueId, JSON.stringify(vector), model, {
15712
+ content: row.content,
15713
+ updatedAt: row.updatedAt?.getTime()
15714
+ }).catch(() => {
15715
+ });
15716
+ return vector;
15717
+ }).finally(() => {
15718
+ entries.delete(key);
15719
+ if (entries.size === 0) pendingRows.get(ctx)?.delete(options.maskInput);
15720
+ });
15721
+ pending.set(key, request);
15722
+ });
15723
+ }
15724
+ return Promise.all(
15725
+ keys.map((key) => {
15726
+ const cached = prepared?.get(key);
15727
+ if (cached) return cached;
15728
+ const request = pending.get(key);
15729
+ if (!prepared) return request;
15730
+ const retained = request.catch((error) => {
15731
+ prepared.delete(key);
15732
+ throw error;
15733
+ });
15734
+ prepared.set(key, retained);
15735
+ return retained;
15736
+ })
15737
+ );
15738
+ }
15739
+
15638
15740
  // src/lib/memoryVault/vectorVersion.ts
15639
15741
  var vectorRowVersion = /* @__PURE__ */ new WeakMap();
15640
15742
  function contentFingerprint(content) {
@@ -16197,11 +16299,11 @@ async function rankComposite(originalQuery, originalQueryEmbedding, subQueries,
16197
16299
  return combined.slice(0, limit);
16198
16300
  }
16199
16301
  async function preEmbedVaultMemories(vaultCtx, embeddingOptions, cache, options) {
16200
- const memories = await getAllVaultMemoriesOp(vaultCtx, options);
16302
+ const memories = await loadVaultCorpus(vaultCtx, options);
16201
16303
  const currentModel = embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
16202
16304
  const uncachedTexts = [];
16203
16305
  const uncachedKeys = [];
16204
- const uncachedIds = [];
16306
+ const uncachedRows = [];
16205
16307
  const uncachedVersions = [];
16206
16308
  for (const m of memories) {
16207
16309
  const content = m.content;
@@ -16220,12 +16322,12 @@ async function preEmbedVaultMemories(vaultCtx, embeddingOptions, cache, options)
16220
16322
  }
16221
16323
  uncachedTexts.push(m.content);
16222
16324
  uncachedKeys.push(m.uniqueId);
16223
- uncachedIds.push(m.uniqueId);
16325
+ uncachedRows.push(m);
16224
16326
  uncachedVersions.push(m.updatedAt);
16225
16327
  }
16226
16328
  }
16227
16329
  if (uncachedTexts.length > 0) {
16228
- const embeddings = await generateEmbeddings(uncachedTexts, embeddingOptions);
16330
+ const embeddings = await embedVaultRows(vaultCtx, embeddingOptions, uncachedRows);
16229
16331
  for (let i = 0; i < uncachedKeys.length; i++) {
16230
16332
  cacheRowVector(
16231
16333
  cache,
@@ -16234,13 +16336,6 @@ async function preEmbedVaultMemories(vaultCtx, embeddingOptions, cache, options)
16234
16336
  uncachedVersions[i],
16235
16337
  uncachedTexts[i]
16236
16338
  );
16237
- updateVaultMemoryEmbeddingOp(
16238
- vaultCtx,
16239
- uncachedIds[i],
16240
- JSON.stringify(embeddings[i]),
16241
- currentModel
16242
- ).catch(() => {
16243
- });
16244
16339
  }
16245
16340
  }
16246
16341
  }
@@ -16382,10 +16477,7 @@ async function buildProjectedCorpus(query, vaultCtx, embeddingOptions, cache, qu
16382
16477
  let laneVecs;
16383
16478
  if (opts.embedStats) opts.embedStats.rowsEmbedded += laneRows.length;
16384
16479
  try {
16385
- laneVecs = await generateEmbeddings(
16386
- laneRows.map((m) => m.content),
16387
- embeddingOptions
16388
- );
16480
+ laneVecs = await embedVaultRows(vaultCtx, embeddingOptions, laneRows);
16389
16481
  } catch (err) {
16390
16482
  laneEmbedFailed = true;
16391
16483
  getLogger().warn(
@@ -16399,13 +16491,6 @@ async function buildProjectedCorpus(query, vaultCtx, embeddingOptions, cache, qu
16399
16491
  cacheRowVector(cache, m.uniqueId, vec, m.updatedAt, m.content);
16400
16492
  resolvedThisCall.add(m.uniqueId);
16401
16493
  vectored.push({ uniqueId: m.uniqueId, embedding: vec, updatedAt: m.updatedAt });
16402
- updateVaultMemoryEmbeddingOp(
16403
- vaultCtx,
16404
- m.uniqueId,
16405
- JSON.stringify(vecs[i]),
16406
- currentModel
16407
- ).catch(() => {
16408
- });
16409
16494
  });
16410
16495
  }
16411
16496
  }
@@ -16597,9 +16682,10 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
16597
16682
  };
16598
16683
  }
16599
16684
  } else {
16600
- const loaded = await getAllVaultMemoriesOp(
16685
+ const loaded = await loadVaultCorpus(
16601
16686
  vaultCtx,
16602
- Object.keys(queryOpts).length > 0 ? queryOpts : void 0
16687
+ Object.keys(queryOpts).length > 0 ? queryOpts : void 0,
16688
+ searchOptions?.vaultPreparation
16603
16689
  );
16604
16690
  vaultSize = loaded.length;
16605
16691
  rowsDecrypted = loaded.length;
@@ -16672,7 +16758,12 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
16672
16758
  let newEmbeddings;
16673
16759
  embedStats.rowsEmbedded += uncachedTexts.length;
16674
16760
  try {
16675
- newEmbeddings = await generateEmbeddings(uncachedTexts, embeddingOptions);
16761
+ newEmbeddings = await embedVaultRows(
16762
+ vaultCtx,
16763
+ embeddingOptions,
16764
+ uncachedIndices.map((index) => memories[index]),
16765
+ searchOptions?.vaultPreparation
16766
+ );
16676
16767
  } catch (err) {
16677
16768
  embeddingFailure = true;
16678
16769
  getLogger().warn(
@@ -16688,13 +16779,6 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
16688
16779
  memories[uncachedIndices[j]].updatedAt,
16689
16780
  memories[uncachedIndices[j]].content
16690
16781
  );
16691
- updateVaultMemoryEmbeddingOp(
16692
- vaultCtx,
16693
- memories[uncachedIndices[j]].uniqueId,
16694
- JSON.stringify(newEmbeddings[j]),
16695
- currentModel
16696
- ).catch(() => {
16697
- });
16698
16782
  }
16699
16783
  }
16700
16784
  }
@@ -19235,6 +19319,7 @@ async function recall(query, ctx, options = {}) {
19235
19319
  },
19236
19320
  ...options.rrfK !== void 0 && { rrfK: options.rrfK },
19237
19321
  ...options.decryptLast !== void 0 && { decryptLast: options.decryptLast },
19322
+ ...options.vaultPreparation && { vaultPreparation: options.vaultPreparation },
19238
19323
  ...subQueries && { subQueries },
19239
19324
  ...options.scopes && { scopes: options.scopes },
19240
19325
  ...options.folderId !== void 0 && { folderId: options.folderId },
@@ -21252,6 +21337,8 @@ async function backfillTopics(ctx) {
21252
21337
  if (topicless.length > 0) await backfillMemoryTopicsOp(ctx, topicless);
21253
21338
  }
21254
21339
  async function migrateMemoriesToRemote(options) {
21340
+ if (typeof options.withWriteBarrier !== "function" || typeof options.onActivated !== "function")
21341
+ throw new Error("A write barrier and a remote store handoff are required");
21255
21342
  const { canonical, signal } = options;
21256
21343
  const rings = [canonical, ...options.fallbacks ?? []];
21257
21344
  const model = options.embeddingOptions?.model ?? DEFAULT_API_EMBEDDING_MODEL;
@@ -21279,93 +21366,98 @@ async function migrateMemoriesToRemote(options) {
21279
21366
  const placeholders = /* @__PURE__ */ new Set();
21280
21367
  const truncated = /* @__PURE__ */ new Set();
21281
21368
  let imported = 0;
21282
- for (let pass = 0; ; pass++) {
21283
- const rows = await getAllVaultMemoryRowsOp(options.vaultCtx);
21284
- for (const raw of rows) if (invalid2(raw)) skipped.set(String(raw.id), "invalid");
21285
- const candidates = rows.filter((raw) => !skipped.has(String(raw.id)));
21286
- const missing = await missingIds(request, candidates.map(revision), signal);
21287
- const pending = /* @__PURE__ */ new Map();
21288
- for (const raw of candidates)
21289
- if (missing.has(raw.id)) pending.set(raw.id, raw.updated_at);
21290
- if (pending.size === 0 && !active) {
21291
- const remote = await remoteMetadata(
21292
- request,
21293
- candidates.map((raw) => raw.id),
21294
- signal
21295
- );
21296
- const needsVector = (raw, server) => !!options.embeddingOptions && !embedding(raw, model) && server.embedding_model !== model;
21297
- for (const raw of candidates) {
21298
- const server = remote.get(raw.id);
21299
- if (server && server.updated_at === raw.updated_at && (!sameMetadata2(raw, server, model) || needsVector(raw, server)))
21300
- pending.set(raw.id, raw.updated_at + 1);
21301
- }
21302
- }
21303
- if (pending.size === 0) {
21304
- const result = (state) => ({
21305
- state,
21306
- total: rows.length,
21307
- imported,
21308
- skipped: [...skipped].map(([memoryId, reason]) => ({ memoryId, reason })),
21309
- reembedded: reembedded.size,
21310
- unembedded: unembedded.size,
21311
- placeholderTombstones: placeholders.size,
21312
- truncatedSourceChunkIds: truncated.size
21313
- });
21314
- if (skipped.size > 0 && !options.allowSkipped) return result("migrating");
21315
- if (!claimed) await importBatch(request, canonical.keyId, [], signal);
21316
- if (!active)
21317
- await request(
21318
- "/account",
21319
- { method: "PUT", body: JSON.stringify({ state: "active" }) },
21369
+ const uploadPasses = async (finalizing = false) => {
21370
+ for (let pass = 0; ; pass++) {
21371
+ const rows = await getAllVaultMemoryRowsOp(options.vaultCtx);
21372
+ for (const raw of rows) if (invalid2(raw)) skipped.set(String(raw.id), "invalid");
21373
+ const candidates = rows.filter((raw) => !skipped.has(String(raw.id)));
21374
+ const missing = await missingIds(request, candidates.map(revision), signal);
21375
+ const pending = /* @__PURE__ */ new Map();
21376
+ for (const raw of candidates)
21377
+ if (missing.has(raw.id)) pending.set(raw.id, raw.updated_at);
21378
+ if (pending.size === 0 && !active) {
21379
+ const remote = await remoteMetadata(
21380
+ request,
21381
+ candidates.map((raw) => raw.id),
21320
21382
  signal
21321
21383
  );
21322
- return result("active");
21323
- }
21324
- if (pass >= MAX_UPLOAD_PASSES)
21325
- throw new RemoteMemoryError(
21326
- "Nearby still reports memories missing after importing them",
21327
- 409,
21328
- "migration_incomplete"
21329
- );
21330
- const prepared = [];
21331
- for (const raw of candidates) {
21332
- const updatedAt = pending.get(raw.id);
21333
- if (updatedAt === void 0) continue;
21334
- try {
21335
- prepared.push(await prepareRow(raw, canonical, rings, model, updatedAt));
21336
- } catch (error) {
21337
- if (!(error instanceof MemoryKeyError)) throw error;
21338
- skipped.set(raw.id, "unreadable");
21339
- }
21340
- }
21341
- const usable = new Set(prepared.filter((p) => p.row.embedding).map((p) => p.row.memory_id));
21342
- if (options.embeddingOptions)
21343
- await embedMissing(prepared, options.embeddingOptions, model, signal);
21344
- const local = new Map(candidates.map((raw) => [raw.id, raw]));
21345
- const upload = [];
21346
- for (const { row, plaintext } of prepared) {
21347
- const id2 = row.memory_id;
21348
- if (!fitsNearby(row)) {
21349
- skipped.set(id2, "invalid");
21350
- continue;
21384
+ const needsVector = (raw, server) => !!options.embeddingOptions && !embedding(raw, model) && server.embedding_model !== model;
21385
+ for (const raw of candidates) {
21386
+ const server = remote.get(raw.id);
21387
+ if (server && server.updated_at === raw.updated_at && (!sameMetadata2(raw, server, model) || needsVector(raw, server)))
21388
+ pending.set(raw.id, raw.updated_at + 1);
21389
+ }
21351
21390
  }
21352
- upload.push(row);
21353
- const track = (set, on) => on ? set.add(id2) : set.delete(id2);
21354
- track(reembedded, !!row.embedding && !usable.has(id2));
21355
- track(unembedded, !row.embedding);
21356
- track(placeholders, plaintext === void 0);
21357
- track(truncated, row.source_chunk_ids !== text13(local.get(id2).source_chunk_ids));
21358
- }
21359
- let sent = 0;
21360
- options.onProgress?.({ imported: sent, toImport: upload.length });
21361
- for (const batch of chunks(upload, IMPORT_BATCH)) {
21362
- await importBatch(request, canonical.keyId, batch, signal);
21363
- claimed = true;
21364
- sent += batch.length;
21391
+ if (pending.size === 0) {
21392
+ const result = (state) => ({
21393
+ state,
21394
+ total: rows.length,
21395
+ imported,
21396
+ skipped: [...skipped].map(([memoryId, reason]) => ({ memoryId, reason })),
21397
+ reembedded: reembedded.size,
21398
+ unembedded: unembedded.size,
21399
+ placeholderTombstones: placeholders.size,
21400
+ truncatedSourceChunkIds: truncated.size
21401
+ });
21402
+ if (skipped.size > 0 && !options.allowSkipped) return result("migrating");
21403
+ if (!finalizing) return options.withWriteBarrier(() => uploadPasses(true));
21404
+ if (!claimed) await importBatch(request, canonical.keyId, [], signal);
21405
+ if (!active)
21406
+ await request(
21407
+ "/account",
21408
+ { method: "PUT", body: JSON.stringify({ state: "active" }) },
21409
+ signal
21410
+ );
21411
+ await options.onActivated();
21412
+ return result("active");
21413
+ }
21414
+ if (pass >= MAX_UPLOAD_PASSES)
21415
+ throw new RemoteMemoryError(
21416
+ "Nearby still reports memories missing after importing them",
21417
+ 409,
21418
+ "migration_incomplete"
21419
+ );
21420
+ const prepared = [];
21421
+ for (const raw of candidates) {
21422
+ const updatedAt = pending.get(raw.id);
21423
+ if (updatedAt === void 0) continue;
21424
+ try {
21425
+ prepared.push(await prepareRow(raw, canonical, rings, model, updatedAt));
21426
+ } catch (error) {
21427
+ if (!(error instanceof MemoryKeyError)) throw error;
21428
+ skipped.set(raw.id, "unreadable");
21429
+ }
21430
+ }
21431
+ const usable = new Set(prepared.filter((p) => p.row.embedding).map((p) => p.row.memory_id));
21432
+ if (options.embeddingOptions)
21433
+ await embedMissing(prepared, options.embeddingOptions, model, signal);
21434
+ const local = new Map(candidates.map((raw) => [raw.id, raw]));
21435
+ const upload = [];
21436
+ for (const { row, plaintext } of prepared) {
21437
+ const id2 = row.memory_id;
21438
+ if (!fitsNearby(row)) {
21439
+ skipped.set(id2, "invalid");
21440
+ continue;
21441
+ }
21442
+ upload.push(row);
21443
+ const track = (set, on) => on ? set.add(id2) : set.delete(id2);
21444
+ track(reembedded, !!row.embedding && !usable.has(id2));
21445
+ track(unembedded, !row.embedding);
21446
+ track(placeholders, plaintext === void 0);
21447
+ track(truncated, row.source_chunk_ids !== text13(local.get(id2).source_chunk_ids));
21448
+ }
21449
+ let sent = 0;
21365
21450
  options.onProgress?.({ imported: sent, toImport: upload.length });
21451
+ for (const batch of chunks(upload, IMPORT_BATCH)) {
21452
+ await importBatch(request, canonical.keyId, batch, signal);
21453
+ claimed = true;
21454
+ sent += batch.length;
21455
+ options.onProgress?.({ imported: sent, toImport: upload.length });
21456
+ }
21457
+ imported += sent;
21366
21458
  }
21367
- imported += sent;
21368
- }
21459
+ };
21460
+ return uploadPasses();
21369
21461
  }
21370
21462
 
21371
21463
  // src/lib/memory/store/remoteStore.ts
@@ -22818,13 +22910,20 @@ async function synthesizeProfile(ctx, options = {}) {
22818
22910
  }
22819
22911
  }
22820
22912
  }
22913
+ const vaultPreparation = createVaultPreparation();
22821
22914
  const settled = await Promise.allSettled(
22822
22915
  facets.map(async (facet) => {
22823
22916
  const prior = previous?.sections.find((s) => s.key === facet.key);
22824
22917
  if (prior && !staleKeys.has(facet.key)) {
22825
22918
  return prior;
22826
22919
  }
22827
- return synthesizeFacet(facet, recallCtx, options, fallbackPriors.get(facet.key));
22920
+ return synthesizeFacet(
22921
+ facet,
22922
+ recallCtx,
22923
+ options,
22924
+ fallbackPriors.get(facet.key),
22925
+ vaultPreparation
22926
+ );
22828
22927
  })
22829
22928
  );
22830
22929
  const sections = settled.map((r, i) => {
@@ -22978,7 +23077,7 @@ async function attributeFacts(ctx, candidates, facets) {
22978
23077
  }
22979
23078
  return keys;
22980
23079
  }
22981
- async function synthesizeFacet(facet, ctx, options, prior) {
23080
+ async function synthesizeFacet(facet, ctx, options, prior, vaultPreparation) {
22982
23081
  const scopes = options.scopes ?? DEFAULT_SCOPES;
22983
23082
  const limit = options.limit ?? DEFAULT_FACET_RECALL_LIMIT;
22984
23083
  const factTypeWeights = { ...DEFAULT_PROFILE_FACT_TYPE_WEIGHTS, ...options.factTypeWeights };
@@ -22995,6 +23094,7 @@ async function synthesizeFacet(facet, ctx, options, prior) {
22995
23094
  };
22996
23095
  }
22997
23096
  const recalled = await recall(facet.query, ctx, {
23097
+ vaultPreparation,
22998
23098
  scopes,
22999
23099
  limit: hasReviewGate ? RECALL_MAX_LIMIT : limit,
23000
23100
  types: ["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']. */