@anuma/sdk 7.0.1 → 7.0.2

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.
@@ -17669,6 +17669,35 @@ function decodeChunkVector(vector, onMalformed) {
17669
17669
  }
17670
17670
  }
17671
17671
 
17672
+ // src/lib/scheduling.ts
17673
+ function nowMs() {
17674
+ return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
17675
+ }
17676
+ function yieldToEventLoop() {
17677
+ const scheduler = globalThis.scheduler;
17678
+ if (typeof scheduler?.yield === "function") return scheduler.yield();
17679
+ if (typeof MessageChannel === "function") {
17680
+ return new Promise((resolve) => {
17681
+ const channel = new MessageChannel();
17682
+ channel.port1.onmessage = () => {
17683
+ channel.port1.close();
17684
+ channel.port2.close();
17685
+ resolve();
17686
+ };
17687
+ channel.port2.postMessage(null);
17688
+ });
17689
+ }
17690
+ return new Promise((resolve) => setTimeout(resolve, 0));
17691
+ }
17692
+ function createTimeSliceYielder(budgetMs) {
17693
+ let sliceStart = nowMs();
17694
+ return async () => {
17695
+ if (nowMs() - sliceStart < budgetMs) return;
17696
+ await yieldToEventLoop();
17697
+ sliceStart = nowMs();
17698
+ };
17699
+ }
17700
+
17672
17701
  // src/lib/db/encryption-utils.ts
17673
17702
  init_logger();
17674
17703
  var ENCRYPTION_PREFIX = "enc:v3:";
@@ -18816,6 +18845,95 @@ async function searchMessagesOp(ctx, queryVector, options) {
18816
18845
  })
18817
18846
  );
18818
18847
  }
18848
+ var CHUNK_VECTOR_BATCH_SIZE = 50;
18849
+ var CHUNK_SCAN_YIELD_BUDGET_MS = 12;
18850
+ var hasEmbeddingSql = `(coalesce("chunks", '') != '' or coalesce("vector", '') != '')`;
18851
+ function toChunkCandidateKey(raw) {
18852
+ return {
18853
+ id: raw.id,
18854
+ conversationId: String(raw.conversation_id),
18855
+ version: Number(raw.updated_at) || 0,
18856
+ embeddingModel: raw.embedding_model ?? null
18857
+ };
18858
+ }
18859
+ async function fetchRowsByIds(ctx, ids) {
18860
+ if (ids.length === 0) return [];
18861
+ return await ctx.messagesCollection.query(import_watermelondb2.Q.where("id", import_watermelondb2.Q.oneOf(ids))).unsafeFetchRaw();
18862
+ }
18863
+ async function projectedChunkRowSource(ctx, filter) {
18864
+ const clauses = [
18865
+ `"history"."_status" is not 'deleted'`,
18866
+ hasEmbeddingSql,
18867
+ `exists (select 1 from "conversations" where "conversations"."conversation_id" = "history"."conversation_id" and "conversations"."is_deleted" = 0 and "conversations"."_status" is not 'deleted')`
18868
+ ];
18869
+ const args = [];
18870
+ if (filter.conversationId !== void 0) {
18871
+ clauses.push(`"history"."conversation_id" = ?`);
18872
+ args.push(filter.conversationId);
18873
+ }
18874
+ if (filter.excludeConversationId !== void 0) {
18875
+ clauses.push(`"history"."conversation_id" != ?`);
18876
+ args.push(filter.excludeConversationId);
18877
+ }
18878
+ const keyRows = await ctx.messagesCollection.query(
18879
+ import_watermelondb2.Q.unsafeSqlQuery(
18880
+ `select "id", "conversation_id", "updated_at", "embedding_model" from "history" where ${clauses.join(" and ")}`,
18881
+ args
18882
+ )
18883
+ ).unsafeFetchRaw();
18884
+ return {
18885
+ keys: keyRows.map(toChunkCandidateKey),
18886
+ loadVectorRows: async (ids) => {
18887
+ if (ids.length === 0) return [];
18888
+ return await ctx.messagesCollection.query(
18889
+ import_watermelondb2.Q.unsafeSqlQuery(
18890
+ `select "id", "updated_at", "chunks", "vector" from "history" where "_status" is not 'deleted' and "id" in (${ids.map(() => "?").join(",")})`,
18891
+ ids
18892
+ )
18893
+ ).unsafeFetchRaw();
18894
+ },
18895
+ loadFullRows: (ids) => fetchRowsByIds(ctx, ids)
18896
+ };
18897
+ }
18898
+ async function fullScanChunkRowSource(ctx, filter) {
18899
+ const activeConversations = await ctx.conversationsCollection.query(import_watermelondb2.Q.where("is_deleted", false)).fetch();
18900
+ const activeConversationIds = new Set(
18901
+ activeConversations.map((c) => String(c._getRaw("conversation_id")))
18902
+ );
18903
+ const queryConditions = filter.conversationId !== void 0 ? [import_watermelondb2.Q.where("conversation_id", filter.conversationId)] : [];
18904
+ const rows = await ctx.messagesCollection.query(...queryConditions).unsafeFetchRaw();
18905
+ const rowsById = /* @__PURE__ */ new Map();
18906
+ for (const row of rows) {
18907
+ const conversationId = String(row.conversation_id);
18908
+ if (!activeConversationIds.has(conversationId)) continue;
18909
+ if (filter.excludeConversationId !== void 0 && conversationId === filter.excludeConversationId) {
18910
+ continue;
18911
+ }
18912
+ if (!row.chunks && !row.vector) continue;
18913
+ rowsById.set(row.id, row);
18914
+ }
18915
+ const pick = (ids) => Promise.resolve(
18916
+ ids.flatMap((id) => {
18917
+ const row = rowsById.get(id);
18918
+ return row ? [row] : [];
18919
+ })
18920
+ );
18921
+ return {
18922
+ keys: Array.from(rowsById.values(), toChunkCandidateKey),
18923
+ loadVectorRows: pick,
18924
+ loadFullRows: pick
18925
+ };
18926
+ }
18927
+ async function chunkRowSource(ctx, filter) {
18928
+ try {
18929
+ return await projectedChunkRowSource(ctx, filter);
18930
+ } catch (err) {
18931
+ getLogger().debug(
18932
+ "searchChunksOp: projected SQL unavailable, using full-load fallback: " + (err instanceof Error ? err.message : String(err))
18933
+ );
18934
+ return fullScanChunkRowSource(ctx, filter);
18935
+ }
18936
+ }
18819
18937
  async function searchChunksOp(ctx, queryVector, options) {
18820
18938
  const {
18821
18939
  limit = 10,
@@ -18823,83 +18941,111 @@ async function searchChunksOp(ctx, queryVector, options) {
18823
18941
  conversationId,
18824
18942
  excludeConversationId,
18825
18943
  embeddingModel,
18826
- chunkCache
18944
+ chunkCache,
18945
+ onStats
18827
18946
  } = options || {};
18828
- const activeConversations = await ctx.conversationsCollection.query(import_watermelondb2.Q.where("is_deleted", false)).fetch();
18829
- const activeConversationIds = new Set(
18830
- activeConversations.map((c) => String(c._getRaw("conversation_id")))
18831
- );
18832
- const queryConditions = conversationId ? [import_watermelondb2.Q.where("conversation_id", conversationId)] : [];
18833
- const messages = await ctx.messagesCollection.query(...queryConditions).unsafeFetchRaw();
18947
+ const source = await chunkRowSource(ctx, { conversationId, excludeConversationId });
18948
+ const yieldIfDue = createTimeSliceYielder(CHUNK_SCAN_YIELD_BUDGET_MS);
18834
18949
  const candidates = [];
18835
18950
  let staleSkipped = 0;
18836
18951
  let unreadableVectors = 0;
18837
- for (const message of messages) {
18838
- const msgConvId = String(message.conversation_id);
18839
- if (!activeConversationIds.has(msgConvId)) continue;
18840
- if (excludeConversationId !== void 0 && msgConvId === excludeConversationId) continue;
18841
- if (embeddingModel) {
18842
- const storedModel = message.embedding_model;
18843
- if (storedModel !== null && storedModel !== void 0 && storedModel !== embeddingModel) {
18844
- staleSkipped++;
18845
- continue;
18846
- }
18952
+ let cacheHits = 0;
18953
+ const missIds = [];
18954
+ const orderById = /* @__PURE__ */ new Map();
18955
+ for (const [order, key] of source.keys.entries()) {
18956
+ await yieldIfDue();
18957
+ orderById.set(key.id, order);
18958
+ if (embeddingModel && key.embeddingModel !== null && key.embeddingModel !== embeddingModel) {
18959
+ staleSkipped++;
18960
+ continue;
18847
18961
  }
18848
- const version = Number(message.updated_at) || 0;
18849
- const cached = chunkCache?.get(message.id);
18850
- if (cached && cached.version === version) {
18851
- if (cached.chunks.length > 0) {
18852
- for (let ci = 0; ci < cached.chunks.length; ci++) {
18853
- const vec = cached.chunks[ci];
18854
- if (vec.length === 0) continue;
18855
- const similarity = cosineSimilarity(queryVector, vec);
18856
- if (similarity >= minSimilarity) {
18857
- candidates.push({
18858
- message,
18859
- similarity,
18860
- chunkTextSource: { kind: "chunk-cached", chunkIndex: ci, version }
18861
- });
18862
- }
18863
- }
18864
- } else if (cached.fallback) {
18865
- const similarity = cosineSimilarity(queryVector, cached.fallback);
18866
- if (similarity >= minSimilarity) {
18867
- candidates.push({ message, similarity, chunkTextSource: { kind: "message" } });
18868
- }
18869
- }
18962
+ const cached = chunkCache?.get(key.id);
18963
+ if (!cached || cached.version !== key.version) {
18964
+ missIds.push(key.id);
18870
18965
  continue;
18871
18966
  }
18872
- const chunks = await readRawJsonField(message, "chunks", ctx.walletAddress);
18873
- if (chunks && chunks.length > 0) {
18874
- const vectors = chunks.map(
18875
- (chunk) => decodeChunkVector(chunk.vector, () => {
18876
- unreadableVectors++;
18877
- })
18878
- );
18879
- chunkCache?.set(message.id, { version, chunks: vectors });
18880
- for (let ci = 0; ci < chunks.length; ci++) {
18881
- const vec = vectors[ci];
18967
+ cacheHits++;
18968
+ if (cached.chunks.length > 0) {
18969
+ for (let ci = 0; ci < cached.chunks.length; ci++) {
18970
+ const vec = cached.chunks[ci];
18882
18971
  if (vec.length === 0) continue;
18883
18972
  const similarity = cosineSimilarity(queryVector, vec);
18884
18973
  if (similarity >= minSimilarity) {
18885
18974
  candidates.push({
18886
- message,
18975
+ id: key.id,
18976
+ version: key.version,
18977
+ order,
18887
18978
  similarity,
18888
- chunkTextSource: { kind: "chunk", chunk: chunks[ci], siblings: chunks }
18979
+ chunkTextSource: { kind: "chunk-cached", chunkIndex: ci, version: key.version }
18889
18980
  });
18890
18981
  }
18891
18982
  }
18892
- } else {
18893
- const messageVector = await readRawJsonField(message, "vector", ctx.walletAddress);
18894
- if (!messageVector || messageVector.length === 0) continue;
18895
- const fallback = Float32Array.from(messageVector);
18896
- chunkCache?.set(message.id, { version, chunks: [], fallback });
18897
- const similarity = cosineSimilarity(queryVector, fallback);
18983
+ } else if (cached.fallback) {
18984
+ const similarity = cosineSimilarity(queryVector, cached.fallback);
18898
18985
  if (similarity >= minSimilarity) {
18899
- candidates.push({ message, similarity, chunkTextSource: { kind: "message" } });
18986
+ candidates.push({
18987
+ id: key.id,
18988
+ version: key.version,
18989
+ order,
18990
+ similarity,
18991
+ chunkTextSource: { kind: "message" }
18992
+ });
18900
18993
  }
18901
18994
  }
18902
18995
  }
18996
+ let rowsFetched = 0;
18997
+ for (let offset = 0; offset < missIds.length; offset += CHUNK_VECTOR_BATCH_SIZE) {
18998
+ const rows = await source.loadVectorRows(
18999
+ missIds.slice(offset, offset + CHUNK_VECTOR_BATCH_SIZE)
19000
+ );
19001
+ rowsFetched += rows.length;
19002
+ for (const row of rows) {
19003
+ await yieldIfDue();
19004
+ const id = row.id;
19005
+ const version = Number(row.updated_at) || 0;
19006
+ const order = orderById.get(id) ?? Number.MAX_SAFE_INTEGER;
19007
+ const chunks = await readRawJsonField(row, "chunks", ctx.walletAddress);
19008
+ if (chunks && chunks.length > 0) {
19009
+ const vectors = chunks.map(
19010
+ (chunk) => decodeChunkVector(chunk.vector, () => {
19011
+ unreadableVectors++;
19012
+ })
19013
+ );
19014
+ chunkCache?.set(id, { version, chunks: vectors });
19015
+ for (let ci = 0; ci < chunks.length; ci++) {
19016
+ const vec = vectors[ci];
19017
+ if (vec.length === 0) continue;
19018
+ const similarity = cosineSimilarity(queryVector, vec);
19019
+ if (similarity >= minSimilarity) {
19020
+ candidates.push({
19021
+ id,
19022
+ version,
19023
+ order,
19024
+ similarity,
19025
+ chunkTextSource: { kind: "chunk", chunk: chunks[ci], siblings: chunks }
19026
+ });
19027
+ }
19028
+ }
19029
+ } else {
19030
+ const messageVector = await readRawJsonField(row, "vector", ctx.walletAddress);
19031
+ if (messageVector && messageVector.length > 0) {
19032
+ const fallback = Float32Array.from(messageVector);
19033
+ chunkCache?.set(id, { version, chunks: [], fallback });
19034
+ const similarity = cosineSimilarity(queryVector, fallback);
19035
+ if (similarity >= minSimilarity) {
19036
+ candidates.push({
19037
+ id,
19038
+ version,
19039
+ order,
19040
+ similarity,
19041
+ chunkTextSource: { kind: "message" }
19042
+ });
19043
+ }
19044
+ }
19045
+ }
19046
+ }
19047
+ }
19048
+ onStats?.({ rowsScanned: source.keys.length, rowsFetched, cacheHits });
18903
19049
  if (staleSkipped > 0) {
18904
19050
  getLogger().warn(
18905
19051
  `searchChunksOp: skipped ${staleSkipped} messages whose embedding model differs from the current model (${embeddingModel}) \u2014 re-embed via chunkAndEmbedAllMessages`
@@ -18910,12 +19056,17 @@ async function searchChunksOp(ctx, queryVector, options) {
18910
19056
  `searchChunksOp: could not read ${unreadableVectors} chunk vectors and left them out of ranking \u2014 the stored value is corrupt, so those chunks stay unsearchable until the message is re-embedded via chunkAndEmbedAllMessages`
18911
19057
  );
18912
19058
  }
18913
- const topK = candidates.sort((a, b) => b.similarity - a.similarity).slice(0, limit);
18914
- const uniqueMessages = /* @__PURE__ */ new Map();
18915
- for (const c of topK) uniqueMessages.set(c.message.id, c.message);
19059
+ const topK = candidates.sort((a, b) => b.similarity - a.similarity || a.order - b.order).slice(0, limit);
19060
+ const topIds = Array.from(new Set(topK.map((c) => c.id)));
19061
+ const fullRows = await source.loadFullRows(topIds);
19062
+ const rowById = new Map(fullRows.map((row) => [row.id, row]));
19063
+ const liveTopK = topK.filter((c) => {
19064
+ const row = rowById.get(c.id);
19065
+ return row !== void 0 && (Number(row.updated_at) || 0) === c.version;
19066
+ });
18916
19067
  const storedById = new Map(
18917
19068
  await Promise.all(
18918
- Array.from(uniqueMessages, async ([id, m]) => {
19069
+ Array.from(rowById, async ([id, m]) => {
18919
19070
  const stored = await messageRawToStored(
18920
19071
  m,
18921
19072
  ctx.walletAddress,
@@ -18928,24 +19079,24 @@ async function searchChunksOp(ctx, queryVector, options) {
18928
19079
  );
18929
19080
  const chunkStateByMsgId = /* @__PURE__ */ new Map();
18930
19081
  const needsChunkText = /* @__PURE__ */ new Set();
18931
- for (const c of topK) {
18932
- if (c.chunkTextSource.kind === "chunk-cached") needsChunkText.add(c.message.id);
19082
+ for (const c of liveTopK) {
19083
+ if (c.chunkTextSource.kind === "chunk-cached") needsChunkText.add(c.id);
18933
19084
  }
18934
19085
  await Promise.all(
18935
19086
  Array.from(needsChunkText, async (id) => {
18936
- const m = uniqueMessages.get(id);
19087
+ const m = rowById.get(id);
18937
19088
  const parsed = await readRawJsonField(m, "chunks", ctx.walletAddress);
18938
19089
  const version = Number(m.updated_at) || 0;
18939
19090
  if (parsed) chunkStateByMsgId.set(id, { version, chunks: parsed });
18940
19091
  })
18941
19092
  );
18942
- return topK.map(({ message, similarity, chunkTextSource }) => {
18943
- const stored = { ...storedById.get(message.id) };
19093
+ return liveTopK.map(({ id, similarity, chunkTextSource }) => {
19094
+ const stored = { ...storedById.get(id) };
18944
19095
  let chunkText2;
18945
19096
  if (chunkTextSource.kind === "chunk") {
18946
19097
  chunkText2 = resolveChunkText(stored.content, chunkTextSource.chunk, chunkTextSource.siblings);
18947
19098
  } else if (chunkTextSource.kind === "chunk-cached") {
18948
- const state = chunkStateByMsgId.get(message.id);
19099
+ const state = chunkStateByMsgId.get(id);
18949
19100
  const chunk = state && state.version === chunkTextSource.version ? state.chunks[chunkTextSource.chunkIndex] : void 0;
18950
19101
  chunkText2 = resolveChunkText(stored.content, chunk, state?.chunks);
18951
19102
  } else {
@@ -20047,13 +20198,35 @@ async function generateEmbedding(text12, options) {
20047
20198
  if (total === void 0) return embedOne(text12, options, void 0);
20048
20199
  return withDeadline((signal) => embedOne(text12, options, signal), total, "embedding");
20049
20200
  }
20201
+ var inFlightByCache = /* @__PURE__ */ new WeakMap();
20050
20202
  async function embedOne(text12, options, outer) {
20051
- const { baseUrl = BASE_URL, model, cache } = options;
20052
- const timeoutMs = options.timeoutMs ?? DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS;
20053
- if (cache) {
20054
- const cached = cache.get(text12);
20055
- if (cached) return Array.from(cached);
20203
+ const { cache } = options;
20204
+ if (!cache) return Array.from(await requestEmbedding(text12, options, outer));
20205
+ const cached = cache.get(text12);
20206
+ if (cached) return Array.from(cached);
20207
+ const inFlight = inFlightByCache.get(cache) ?? /* @__PURE__ */ new Map();
20208
+ inFlightByCache.set(cache, inFlight);
20209
+ const pending = inFlight.get(text12);
20210
+ if (pending) {
20211
+ try {
20212
+ return Array.from(await pending);
20213
+ } catch (err) {
20214
+ if (outer?.aborted) throw err;
20215
+ }
20216
+ }
20217
+ const request = requestEmbedding(text12, options, outer);
20218
+ inFlight.set(text12, request);
20219
+ try {
20220
+ const embedding = await request;
20221
+ cache.set(text12, embedding);
20222
+ return Array.from(embedding);
20223
+ } finally {
20224
+ if (inFlight.get(text12) === request) inFlight.delete(text12);
20056
20225
  }
20226
+ }
20227
+ async function requestEmbedding(text12, options, outer) {
20228
+ const { baseUrl = BASE_URL, model } = options;
20229
+ const timeoutMs = options.timeoutMs ?? DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS;
20057
20230
  const headers = {
20058
20231
  ...await resolveAuthHeaders(options, outer),
20059
20232
  ...taskTypeHeader(options.taskType)
@@ -20088,11 +20261,7 @@ async function embedOne(text12, options, outer) {
20088
20261
  totalTokens: response.data.usage.total_tokens ?? 0
20089
20262
  });
20090
20263
  }
20091
- const f32Embedding = Float32Array.from(embedding);
20092
- if (cache) {
20093
- cache.set(text12, f32Embedding);
20094
- }
20095
- return Array.from(f32Embedding);
20264
+ return Float32Array.from(embedding);
20096
20265
  }
20097
20266
  var DEFAULT_EMBEDDING_BATCH_SIZE = 100;
20098
20267
  var DEFAULT_EMBEDDING_BATCH_CONCURRENCY = 3;
@@ -30556,9 +30725,9 @@ function recencyMultiplier(updatedAt, opts = {}) {
30556
30725
  const updatedMs = updatedAt.getTime();
30557
30726
  if (!Number.isFinite(updatedMs)) return noDate;
30558
30727
  const now = opts.now ?? /* @__PURE__ */ new Date();
30559
- const nowMs3 = now.getTime();
30560
- if (!Number.isFinite(nowMs3)) return noDate;
30561
- const ageDays2 = Math.max(0, (nowMs3 - updatedMs) / DAY_MS2);
30728
+ const nowMs2 = now.getTime();
30729
+ if (!Number.isFinite(nowMs2)) return noDate;
30730
+ const ageDays2 = Math.max(0, (nowMs2 - updatedMs) / DAY_MS2);
30562
30731
  const perYearDecay = opts.perYearDecay ?? DEFAULT_PER_YEAR_DECAY;
30563
30732
  const floor = opts.floor ?? DEFAULT_FLOOR;
30564
30733
  const decay = perYearDecay * (ageDays2 / 365);
@@ -31070,9 +31239,6 @@ function createVaultEmbeddingCache(maxSize = DEFAULT_VAULT_CACHE_SIZE) {
31070
31239
 
31071
31240
  // src/lib/memoryVault/searchTool.ts
31072
31241
  var DEFAULT_RERANK_TOP_N = 5;
31073
- function nowMs() {
31074
- return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
31075
- }
31076
31242
  var warnedProgrammaticDecomposeIgnored = false;
31077
31243
  function normalizeEventTimeKind(kind) {
31078
31244
  return kind === "point" || kind === "range" || kind === "ongoing" ? kind : null;
@@ -33974,6 +34140,32 @@ function rankProfileCandidates(inputs, now = Date.now(), options = {}) {
33974
34140
  return ranked;
33975
34141
  }
33976
34142
 
34143
+ // src/lib/db/chat/embeddingCache.ts
34144
+ var MaskScopedEmbeddingCache = class extends Map {
34145
+ constructor(inner, masked) {
34146
+ super();
34147
+ this.inner = inner;
34148
+ this.masked = masked;
34149
+ }
34150
+ key(text12) {
34151
+ return `${this.masked ? "m" : "r"}:${text12}`;
34152
+ }
34153
+ get(text12) {
34154
+ return this.inner.get(this.key(text12));
34155
+ }
34156
+ set(text12, value) {
34157
+ this.inner.set(this.key(text12), value);
34158
+ return this;
34159
+ }
34160
+ };
34161
+ var scopedViews = /* @__PURE__ */ new WeakMap();
34162
+ function maskScopedEmbeddingCache(cache, masked) {
34163
+ const views = scopedViews.get(cache) ?? {};
34164
+ scopedViews.set(cache, views);
34165
+ const slot = masked ? "masked" : "raw";
34166
+ return views[slot] ?? (views[slot] = new MaskScopedEmbeddingCache(cache, masked));
34167
+ }
34168
+
33977
34169
  // src/lib/memory/recall.ts
33978
34170
  init_logger();
33979
34171
 
@@ -34184,9 +34376,6 @@ function scoreEventTimeOverlap(memoryStart, memoryEnd, memoryKind, window2) {
34184
34376
  var DEFAULT_LIMIT = 8;
34185
34377
  var DEFAULT_BUDGET = "low";
34186
34378
  var DEFAULT_FACT_MIN_SCORE = 0.1;
34187
- function nowMs2() {
34188
- return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
34189
- }
34190
34379
  var DEFAULT_CHUNK_MIN_SCORE = 0.5;
34191
34380
  var DEFAULT_QUERY_EMBED_TOTAL_TIMEOUT_MS = 8e3;
34192
34381
  function flagsForBudget(budget) {
@@ -34224,12 +34413,19 @@ async function recall(query, ctx, options = {}) {
34224
34413
  const subQueries = normalizedFacets.length >= 2 ? normalizedFacets : void 0;
34225
34414
  const embeddingOptions = {
34226
34415
  ...ctx.embeddingOptions,
34227
- taskType: "memory_recall_embed"
34416
+ taskType: "memory_recall_embed",
34417
+ ...options.embeddingCache && !ctx.embeddingOptions.cache && {
34418
+ cache: maskScopedEmbeddingCache(
34419
+ options.embeddingCache,
34420
+ Boolean(ctx.embeddingOptions.maskInput)
34421
+ )
34422
+ }
34228
34423
  };
34229
- const t0 = nowMs2();
34424
+ const t0 = nowMs();
34230
34425
  let prepMs = 0;
34231
34426
  let factLaneMs = 0;
34232
34427
  let chunkLaneMs = 0;
34428
+ let chunkStats;
34233
34429
  let fuseMs = 0;
34234
34430
  const factResults = [];
34235
34431
  const chunkResults = [];
@@ -34284,6 +34480,11 @@ async function recall(query, ctx, options = {}) {
34284
34480
  ...decryptLastRan !== void 0 && { decryptLast: decryptLastRan },
34285
34481
  ...vaultRowsDecrypted !== void 0 && { vaultRowsDecrypted },
34286
34482
  ...vaultRowsEmbedded !== void 0 && { vaultRowsEmbedded },
34483
+ ...chunkStats && {
34484
+ chunkRowsScanned: chunkStats.rowsScanned,
34485
+ chunkRowsFetched: chunkStats.rowsFetched,
34486
+ chunkCacheHits: chunkStats.cacheHits
34487
+ },
34287
34488
  factCount: factResults.length,
34288
34489
  chunkCount: chunkResults.length,
34289
34490
  admittedCount: admitted.length,
@@ -34295,7 +34496,7 @@ async function recall(query, ctx, options = {}) {
34295
34496
  temporalLaneCount,
34296
34497
  emptyReason: emptyReasonFor(admitted.length, laneRan),
34297
34498
  timings: {
34298
- total: nowMs2() - t0,
34499
+ total: nowMs() - t0,
34299
34500
  prep: prepMs,
34300
34501
  factLane: factLaneMs,
34301
34502
  rerank: rerankMs,
@@ -34317,14 +34518,14 @@ async function recall(query, ctx, options = {}) {
34317
34518
  const needsChunkEmbedding = types.includes("chunk") && ctx.storageCtx;
34318
34519
  const wantsTemporal = types.includes("fact") && (ctx.factSource || ctx.vaultCtx);
34319
34520
  const queryEmbedTotalTimeoutMs = options.queryEmbedTotalTimeoutMs ?? DEFAULT_QUERY_EMBED_TOTAL_TIMEOUT_MS;
34320
- const prepStart = nowMs2();
34521
+ const prepStart = nowMs();
34321
34522
  let sharedEmbedMs = 0;
34322
34523
  const [queryEmbedding, entityRanking, temporalRanking] = await Promise.all([
34323
34524
  needsChunkEmbedding ? generateEmbedding(query, {
34324
34525
  ...embeddingOptions,
34325
34526
  totalTimeoutMs: queryEmbedTotalTimeoutMs
34326
34527
  }).finally(() => {
34327
- sharedEmbedMs = nowMs2() - prepStart;
34528
+ sharedEmbedMs = nowMs() - prepStart;
34328
34529
  }).then((vec) => {
34329
34530
  if (vec.length === 0) {
34330
34531
  getLogger().warn(
@@ -34357,11 +34558,11 @@ async function recall(query, ctx, options = {}) {
34357
34558
  () => ctx.factSource ? ctx.factSource.temporalRanking(query, options.now) : buildTemporalLaneRanking(query, ctx.vaultCtx, options.now)
34358
34559
  ) : Promise.resolve([])
34359
34560
  ]);
34360
- prepMs = nowMs2() - prepStart;
34561
+ prepMs = nowMs() - prepStart;
34361
34562
  graphLaneCount = entityRanking.length;
34362
34563
  temporalLaneCount = temporalRanking.length;
34363
34564
  if (types.includes("fact") && (ctx.factSource || ctx.vaultCtx && ctx.vaultCache)) {
34364
- const factStart = nowMs2();
34565
+ const factStart = nowMs();
34365
34566
  const vaultMinScore = options.minScore ?? DEFAULT_FACT_MIN_SCORE;
34366
34567
  factFloor = vaultMinScore;
34367
34568
  const {
@@ -34437,11 +34638,11 @@ async function recall(query, ctx, options = {}) {
34437
34638
  factLaneRankedOnCosine = factRankedOnCosine;
34438
34639
  decryptLastRan = factDecryptLast;
34439
34640
  vaultRowsDecrypted = factRowsDecrypted;
34440
- factLaneMs = nowMs2() - factStart;
34641
+ factLaneMs = nowMs() - factStart;
34441
34642
  }
34442
34643
  if (chunkEmbedFailed && !factLaneRankedOnCosine) embeddingsUnavailable = true;
34443
34644
  if (types.includes("chunk") && ctx.storageCtx && queryEmbedding) {
34444
- const chunkStart = nowMs2();
34645
+ const chunkStart = nowMs();
34445
34646
  const chunkMinScore = options.minScore ?? DEFAULT_CHUNK_MIN_SCORE;
34446
34647
  chunkFloor = chunkMinScore;
34447
34648
  const results = await searchChunksOp(ctx.storageCtx, queryEmbedding, {
@@ -34452,12 +34653,15 @@ async function recall(query, ctx, options = {}) {
34452
34653
  ...options.excludeConversationId && {
34453
34654
  excludeConversationId: options.excludeConversationId
34454
34655
  },
34455
- ...ctx.chunkCache && { chunkCache: ctx.chunkCache }
34656
+ ...ctx.chunkCache && { chunkCache: ctx.chunkCache },
34657
+ onStats: (stats) => {
34658
+ chunkStats = stats;
34659
+ }
34456
34660
  });
34457
34661
  chunkResults.push(...dedupeBy(results, (r) => r.chunkText.trim()));
34458
- chunkLaneMs = nowMs2() - chunkStart;
34662
+ chunkLaneMs = nowMs() - chunkStart;
34459
34663
  }
34460
- const fuseStart = nowMs2();
34664
+ const fuseStart = nowMs();
34461
34665
  if (types.length === 1 || factResults.length === 0 || chunkResults.length === 0) {
34462
34666
  const memories2 = [
34463
34667
  ...factResults.map((r) => toFactMemory(r, options.now)),
@@ -34465,7 +34669,7 @@ async function recall(query, ctx, options = {}) {
34465
34669
  ];
34466
34670
  memories2.sort((a, b) => b.score - a.score);
34467
34671
  const candidateCount = factResults.length + chunkResults.length;
34468
- fuseMs = nowMs2() - fuseStart;
34672
+ fuseMs = nowMs() - fuseStart;
34469
34673
  const admitted = memories2.slice(0, limit);
34470
34674
  hitLimit = memories2.length > limit;
34471
34675
  emitDiagnostics(candidateCount, admitted);
@@ -34529,7 +34733,7 @@ async function recall(query, ctx, options = {}) {
34529
34733
  memories = selection.out;
34530
34734
  }
34531
34735
  hitLimit = selection.cut;
34532
- fuseMs = nowMs2() - fuseStart;
34736
+ fuseMs = nowMs() - fuseStart;
34533
34737
  emitDiagnostics(byId.size, memories);
34534
34738
  return {
34535
34739
  memories,