@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.
@@ -3585,6 +3585,35 @@ function decodeChunkVector(vector, onMalformed) {
3585
3585
  }
3586
3586
  }
3587
3587
 
3588
+ // src/lib/scheduling.ts
3589
+ function nowMs() {
3590
+ return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
3591
+ }
3592
+ function yieldToEventLoop() {
3593
+ const scheduler = globalThis.scheduler;
3594
+ if (typeof scheduler?.yield === "function") return scheduler.yield();
3595
+ if (typeof MessageChannel === "function") {
3596
+ return new Promise((resolve) => {
3597
+ const channel = new MessageChannel();
3598
+ channel.port1.onmessage = () => {
3599
+ channel.port1.close();
3600
+ channel.port2.close();
3601
+ resolve();
3602
+ };
3603
+ channel.port2.postMessage(null);
3604
+ });
3605
+ }
3606
+ return new Promise((resolve) => setTimeout(resolve, 0));
3607
+ }
3608
+ function createTimeSliceYielder(budgetMs) {
3609
+ let sliceStart = nowMs();
3610
+ return async () => {
3611
+ if (nowMs() - sliceStart < budgetMs) return;
3612
+ await yieldToEventLoop();
3613
+ sliceStart = nowMs();
3614
+ };
3615
+ }
3616
+
3588
3617
  // src/lib/db/encryption-utils.ts
3589
3618
  init_logger();
3590
3619
  var ENCRYPTION_PREFIX = "enc:v3:";
@@ -4968,6 +4997,95 @@ async function searchMessagesOp(ctx, queryVector, options) {
4968
4997
  })
4969
4998
  );
4970
4999
  }
5000
+ var CHUNK_VECTOR_BATCH_SIZE = 50;
5001
+ var CHUNK_SCAN_YIELD_BUDGET_MS = 12;
5002
+ var hasEmbeddingSql = `(coalesce("chunks", '') != '' or coalesce("vector", '') != '')`;
5003
+ function toChunkCandidateKey(raw) {
5004
+ return {
5005
+ id: raw.id,
5006
+ conversationId: String(raw.conversation_id),
5007
+ version: Number(raw.updated_at) || 0,
5008
+ embeddingModel: raw.embedding_model ?? null
5009
+ };
5010
+ }
5011
+ async function fetchRowsByIds(ctx, ids) {
5012
+ if (ids.length === 0) return [];
5013
+ return await ctx.messagesCollection.query(import_watermelondb2.Q.where("id", import_watermelondb2.Q.oneOf(ids))).unsafeFetchRaw();
5014
+ }
5015
+ async function projectedChunkRowSource(ctx, filter) {
5016
+ const clauses = [
5017
+ `"history"."_status" is not 'deleted'`,
5018
+ hasEmbeddingSql,
5019
+ `exists (select 1 from "conversations" where "conversations"."conversation_id" = "history"."conversation_id" and "conversations"."is_deleted" = 0 and "conversations"."_status" is not 'deleted')`
5020
+ ];
5021
+ const args = [];
5022
+ if (filter.conversationId !== void 0) {
5023
+ clauses.push(`"history"."conversation_id" = ?`);
5024
+ args.push(filter.conversationId);
5025
+ }
5026
+ if (filter.excludeConversationId !== void 0) {
5027
+ clauses.push(`"history"."conversation_id" != ?`);
5028
+ args.push(filter.excludeConversationId);
5029
+ }
5030
+ const keyRows = await ctx.messagesCollection.query(
5031
+ import_watermelondb2.Q.unsafeSqlQuery(
5032
+ `select "id", "conversation_id", "updated_at", "embedding_model" from "history" where ${clauses.join(" and ")}`,
5033
+ args
5034
+ )
5035
+ ).unsafeFetchRaw();
5036
+ return {
5037
+ keys: keyRows.map(toChunkCandidateKey),
5038
+ loadVectorRows: async (ids) => {
5039
+ if (ids.length === 0) return [];
5040
+ return await ctx.messagesCollection.query(
5041
+ import_watermelondb2.Q.unsafeSqlQuery(
5042
+ `select "id", "updated_at", "chunks", "vector" from "history" where "_status" is not 'deleted' and "id" in (${ids.map(() => "?").join(",")})`,
5043
+ ids
5044
+ )
5045
+ ).unsafeFetchRaw();
5046
+ },
5047
+ loadFullRows: (ids) => fetchRowsByIds(ctx, ids)
5048
+ };
5049
+ }
5050
+ async function fullScanChunkRowSource(ctx, filter) {
5051
+ const activeConversations = await ctx.conversationsCollection.query(import_watermelondb2.Q.where("is_deleted", false)).fetch();
5052
+ const activeConversationIds = new Set(
5053
+ activeConversations.map((c) => String(c._getRaw("conversation_id")))
5054
+ );
5055
+ const queryConditions = filter.conversationId !== void 0 ? [import_watermelondb2.Q.where("conversation_id", filter.conversationId)] : [];
5056
+ const rows = await ctx.messagesCollection.query(...queryConditions).unsafeFetchRaw();
5057
+ const rowsById = /* @__PURE__ */ new Map();
5058
+ for (const row of rows) {
5059
+ const conversationId = String(row.conversation_id);
5060
+ if (!activeConversationIds.has(conversationId)) continue;
5061
+ if (filter.excludeConversationId !== void 0 && conversationId === filter.excludeConversationId) {
5062
+ continue;
5063
+ }
5064
+ if (!row.chunks && !row.vector) continue;
5065
+ rowsById.set(row.id, row);
5066
+ }
5067
+ const pick = (ids) => Promise.resolve(
5068
+ ids.flatMap((id) => {
5069
+ const row = rowsById.get(id);
5070
+ return row ? [row] : [];
5071
+ })
5072
+ );
5073
+ return {
5074
+ keys: Array.from(rowsById.values(), toChunkCandidateKey),
5075
+ loadVectorRows: pick,
5076
+ loadFullRows: pick
5077
+ };
5078
+ }
5079
+ async function chunkRowSource(ctx, filter) {
5080
+ try {
5081
+ return await projectedChunkRowSource(ctx, filter);
5082
+ } catch (err) {
5083
+ getLogger().debug(
5084
+ "searchChunksOp: projected SQL unavailable, using full-load fallback: " + (err instanceof Error ? err.message : String(err))
5085
+ );
5086
+ return fullScanChunkRowSource(ctx, filter);
5087
+ }
5088
+ }
4971
5089
  async function searchChunksOp(ctx, queryVector, options) {
4972
5090
  const {
4973
5091
  limit = 10,
@@ -4975,83 +5093,111 @@ async function searchChunksOp(ctx, queryVector, options) {
4975
5093
  conversationId,
4976
5094
  excludeConversationId,
4977
5095
  embeddingModel,
4978
- chunkCache
5096
+ chunkCache,
5097
+ onStats
4979
5098
  } = options || {};
4980
- const activeConversations = await ctx.conversationsCollection.query(import_watermelondb2.Q.where("is_deleted", false)).fetch();
4981
- const activeConversationIds = new Set(
4982
- activeConversations.map((c) => String(c._getRaw("conversation_id")))
4983
- );
4984
- const queryConditions = conversationId ? [import_watermelondb2.Q.where("conversation_id", conversationId)] : [];
4985
- const messages = await ctx.messagesCollection.query(...queryConditions).unsafeFetchRaw();
5099
+ const source = await chunkRowSource(ctx, { conversationId, excludeConversationId });
5100
+ const yieldIfDue = createTimeSliceYielder(CHUNK_SCAN_YIELD_BUDGET_MS);
4986
5101
  const candidates = [];
4987
5102
  let staleSkipped = 0;
4988
5103
  let unreadableVectors = 0;
4989
- for (const message of messages) {
4990
- const msgConvId = String(message.conversation_id);
4991
- if (!activeConversationIds.has(msgConvId)) continue;
4992
- if (excludeConversationId !== void 0 && msgConvId === excludeConversationId) continue;
4993
- if (embeddingModel) {
4994
- const storedModel = message.embedding_model;
4995
- if (storedModel !== null && storedModel !== void 0 && storedModel !== embeddingModel) {
4996
- staleSkipped++;
4997
- continue;
4998
- }
5104
+ let cacheHits = 0;
5105
+ const missIds = [];
5106
+ const orderById = /* @__PURE__ */ new Map();
5107
+ for (const [order, key] of source.keys.entries()) {
5108
+ await yieldIfDue();
5109
+ orderById.set(key.id, order);
5110
+ if (embeddingModel && key.embeddingModel !== null && key.embeddingModel !== embeddingModel) {
5111
+ staleSkipped++;
5112
+ continue;
4999
5113
  }
5000
- const version = Number(message.updated_at) || 0;
5001
- const cached = chunkCache?.get(message.id);
5002
- if (cached && cached.version === version) {
5003
- if (cached.chunks.length > 0) {
5004
- for (let ci = 0; ci < cached.chunks.length; ci++) {
5005
- const vec = cached.chunks[ci];
5006
- if (vec.length === 0) continue;
5007
- const similarity = cosineSimilarity(queryVector, vec);
5008
- if (similarity >= minSimilarity) {
5009
- candidates.push({
5010
- message,
5011
- similarity,
5012
- chunkTextSource: { kind: "chunk-cached", chunkIndex: ci, version }
5013
- });
5014
- }
5015
- }
5016
- } else if (cached.fallback) {
5017
- const similarity = cosineSimilarity(queryVector, cached.fallback);
5018
- if (similarity >= minSimilarity) {
5019
- candidates.push({ message, similarity, chunkTextSource: { kind: "message" } });
5020
- }
5021
- }
5114
+ const cached = chunkCache?.get(key.id);
5115
+ if (!cached || cached.version !== key.version) {
5116
+ missIds.push(key.id);
5022
5117
  continue;
5023
5118
  }
5024
- const chunks3 = await readRawJsonField(message, "chunks", ctx.walletAddress);
5025
- if (chunks3 && chunks3.length > 0) {
5026
- const vectors = chunks3.map(
5027
- (chunk) => decodeChunkVector(chunk.vector, () => {
5028
- unreadableVectors++;
5029
- })
5030
- );
5031
- chunkCache?.set(message.id, { version, chunks: vectors });
5032
- for (let ci = 0; ci < chunks3.length; ci++) {
5033
- const vec = vectors[ci];
5119
+ cacheHits++;
5120
+ if (cached.chunks.length > 0) {
5121
+ for (let ci = 0; ci < cached.chunks.length; ci++) {
5122
+ const vec = cached.chunks[ci];
5034
5123
  if (vec.length === 0) continue;
5035
5124
  const similarity = cosineSimilarity(queryVector, vec);
5036
5125
  if (similarity >= minSimilarity) {
5037
5126
  candidates.push({
5038
- message,
5127
+ id: key.id,
5128
+ version: key.version,
5129
+ order,
5039
5130
  similarity,
5040
- chunkTextSource: { kind: "chunk", chunk: chunks3[ci], siblings: chunks3 }
5131
+ chunkTextSource: { kind: "chunk-cached", chunkIndex: ci, version: key.version }
5041
5132
  });
5042
5133
  }
5043
5134
  }
5044
- } else {
5045
- const messageVector = await readRawJsonField(message, "vector", ctx.walletAddress);
5046
- if (!messageVector || messageVector.length === 0) continue;
5047
- const fallback = Float32Array.from(messageVector);
5048
- chunkCache?.set(message.id, { version, chunks: [], fallback });
5049
- const similarity = cosineSimilarity(queryVector, fallback);
5135
+ } else if (cached.fallback) {
5136
+ const similarity = cosineSimilarity(queryVector, cached.fallback);
5050
5137
  if (similarity >= minSimilarity) {
5051
- candidates.push({ message, similarity, chunkTextSource: { kind: "message" } });
5138
+ candidates.push({
5139
+ id: key.id,
5140
+ version: key.version,
5141
+ order,
5142
+ similarity,
5143
+ chunkTextSource: { kind: "message" }
5144
+ });
5145
+ }
5146
+ }
5147
+ }
5148
+ let rowsFetched = 0;
5149
+ for (let offset = 0; offset < missIds.length; offset += CHUNK_VECTOR_BATCH_SIZE) {
5150
+ const rows = await source.loadVectorRows(
5151
+ missIds.slice(offset, offset + CHUNK_VECTOR_BATCH_SIZE)
5152
+ );
5153
+ rowsFetched += rows.length;
5154
+ for (const row of rows) {
5155
+ await yieldIfDue();
5156
+ const id = row.id;
5157
+ const version = Number(row.updated_at) || 0;
5158
+ const order = orderById.get(id) ?? Number.MAX_SAFE_INTEGER;
5159
+ const chunks3 = await readRawJsonField(row, "chunks", ctx.walletAddress);
5160
+ if (chunks3 && chunks3.length > 0) {
5161
+ const vectors = chunks3.map(
5162
+ (chunk) => decodeChunkVector(chunk.vector, () => {
5163
+ unreadableVectors++;
5164
+ })
5165
+ );
5166
+ chunkCache?.set(id, { version, chunks: vectors });
5167
+ for (let ci = 0; ci < chunks3.length; ci++) {
5168
+ const vec = vectors[ci];
5169
+ if (vec.length === 0) continue;
5170
+ const similarity = cosineSimilarity(queryVector, vec);
5171
+ if (similarity >= minSimilarity) {
5172
+ candidates.push({
5173
+ id,
5174
+ version,
5175
+ order,
5176
+ similarity,
5177
+ chunkTextSource: { kind: "chunk", chunk: chunks3[ci], siblings: chunks3 }
5178
+ });
5179
+ }
5180
+ }
5181
+ } else {
5182
+ const messageVector = await readRawJsonField(row, "vector", ctx.walletAddress);
5183
+ if (messageVector && messageVector.length > 0) {
5184
+ const fallback = Float32Array.from(messageVector);
5185
+ chunkCache?.set(id, { version, chunks: [], fallback });
5186
+ const similarity = cosineSimilarity(queryVector, fallback);
5187
+ if (similarity >= minSimilarity) {
5188
+ candidates.push({
5189
+ id,
5190
+ version,
5191
+ order,
5192
+ similarity,
5193
+ chunkTextSource: { kind: "message" }
5194
+ });
5195
+ }
5196
+ }
5052
5197
  }
5053
5198
  }
5054
5199
  }
5200
+ onStats?.({ rowsScanned: source.keys.length, rowsFetched, cacheHits });
5055
5201
  if (staleSkipped > 0) {
5056
5202
  getLogger().warn(
5057
5203
  `searchChunksOp: skipped ${staleSkipped} messages whose embedding model differs from the current model (${embeddingModel}) \u2014 re-embed via chunkAndEmbedAllMessages`
@@ -5062,12 +5208,17 @@ async function searchChunksOp(ctx, queryVector, options) {
5062
5208
  `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`
5063
5209
  );
5064
5210
  }
5065
- const topK = candidates.sort((a, b) => b.similarity - a.similarity).slice(0, limit);
5066
- const uniqueMessages = /* @__PURE__ */ new Map();
5067
- for (const c of topK) uniqueMessages.set(c.message.id, c.message);
5211
+ const topK = candidates.sort((a, b) => b.similarity - a.similarity || a.order - b.order).slice(0, limit);
5212
+ const topIds = Array.from(new Set(topK.map((c) => c.id)));
5213
+ const fullRows = await source.loadFullRows(topIds);
5214
+ const rowById = new Map(fullRows.map((row) => [row.id, row]));
5215
+ const liveTopK = topK.filter((c) => {
5216
+ const row = rowById.get(c.id);
5217
+ return row !== void 0 && (Number(row.updated_at) || 0) === c.version;
5218
+ });
5068
5219
  const storedById = new Map(
5069
5220
  await Promise.all(
5070
- Array.from(uniqueMessages, async ([id, m]) => {
5221
+ Array.from(rowById, async ([id, m]) => {
5071
5222
  const stored2 = await messageRawToStored(
5072
5223
  m,
5073
5224
  ctx.walletAddress,
@@ -5080,24 +5231,24 @@ async function searchChunksOp(ctx, queryVector, options) {
5080
5231
  );
5081
5232
  const chunkStateByMsgId = /* @__PURE__ */ new Map();
5082
5233
  const needsChunkText = /* @__PURE__ */ new Set();
5083
- for (const c of topK) {
5084
- if (c.chunkTextSource.kind === "chunk-cached") needsChunkText.add(c.message.id);
5234
+ for (const c of liveTopK) {
5235
+ if (c.chunkTextSource.kind === "chunk-cached") needsChunkText.add(c.id);
5085
5236
  }
5086
5237
  await Promise.all(
5087
5238
  Array.from(needsChunkText, async (id) => {
5088
- const m = uniqueMessages.get(id);
5239
+ const m = rowById.get(id);
5089
5240
  const parsed = await readRawJsonField(m, "chunks", ctx.walletAddress);
5090
5241
  const version = Number(m.updated_at) || 0;
5091
5242
  if (parsed) chunkStateByMsgId.set(id, { version, chunks: parsed });
5092
5243
  })
5093
5244
  );
5094
- return topK.map(({ message, similarity, chunkTextSource }) => {
5095
- const stored2 = { ...storedById.get(message.id) };
5245
+ return liveTopK.map(({ id, similarity, chunkTextSource }) => {
5246
+ const stored2 = { ...storedById.get(id) };
5096
5247
  let chunkText2;
5097
5248
  if (chunkTextSource.kind === "chunk") {
5098
5249
  chunkText2 = resolveChunkText(stored2.content, chunkTextSource.chunk, chunkTextSource.siblings);
5099
5250
  } else if (chunkTextSource.kind === "chunk-cached") {
5100
- const state = chunkStateByMsgId.get(message.id);
5251
+ const state = chunkStateByMsgId.get(id);
5101
5252
  const chunk = state && state.version === chunkTextSource.version ? state.chunks[chunkTextSource.chunkIndex] : void 0;
5102
5253
  chunkText2 = resolveChunkText(stored2.content, chunk, state?.chunks);
5103
5254
  } else {
@@ -6257,13 +6408,35 @@ async function generateEmbedding(text14, options) {
6257
6408
  if (total === void 0) return embedOne(text14, options, void 0);
6258
6409
  return withDeadline((signal) => embedOne(text14, options, signal), total, "embedding");
6259
6410
  }
6411
+ var inFlightByCache = /* @__PURE__ */ new WeakMap();
6260
6412
  async function embedOne(text14, options, outer) {
6261
- const { baseUrl = BASE_URL, model, cache } = options;
6262
- const timeoutMs = options.timeoutMs ?? DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS;
6263
- if (cache) {
6264
- const cached = cache.get(text14);
6265
- if (cached) return Array.from(cached);
6413
+ const { cache } = options;
6414
+ if (!cache) return Array.from(await requestEmbedding(text14, options, outer));
6415
+ const cached = cache.get(text14);
6416
+ if (cached) return Array.from(cached);
6417
+ const inFlight = inFlightByCache.get(cache) ?? /* @__PURE__ */ new Map();
6418
+ inFlightByCache.set(cache, inFlight);
6419
+ const pending = inFlight.get(text14);
6420
+ if (pending) {
6421
+ try {
6422
+ return Array.from(await pending);
6423
+ } catch (err) {
6424
+ if (outer?.aborted) throw err;
6425
+ }
6426
+ }
6427
+ const request = requestEmbedding(text14, options, outer);
6428
+ inFlight.set(text14, request);
6429
+ try {
6430
+ const embedding2 = await request;
6431
+ cache.set(text14, embedding2);
6432
+ return Array.from(embedding2);
6433
+ } finally {
6434
+ if (inFlight.get(text14) === request) inFlight.delete(text14);
6266
6435
  }
6436
+ }
6437
+ async function requestEmbedding(text14, options, outer) {
6438
+ const { baseUrl = BASE_URL, model } = options;
6439
+ const timeoutMs = options.timeoutMs ?? DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS;
6267
6440
  const headers = {
6268
6441
  ...await resolveAuthHeaders(options, outer),
6269
6442
  ...taskTypeHeader(options.taskType)
@@ -6298,11 +6471,7 @@ async function embedOne(text14, options, outer) {
6298
6471
  totalTokens: response.data.usage.total_tokens ?? 0
6299
6472
  });
6300
6473
  }
6301
- const f32Embedding = Float32Array.from(embedding2);
6302
- if (cache) {
6303
- cache.set(text14, f32Embedding);
6304
- }
6305
- return Array.from(f32Embedding);
6474
+ return Float32Array.from(embedding2);
6306
6475
  }
6307
6476
  var DEFAULT_EMBEDDING_BATCH_SIZE = 100;
6308
6477
  var DEFAULT_EMBEDDING_BATCH_CONCURRENCY = 3;
@@ -17619,9 +17788,9 @@ function recencyMultiplier(updatedAt, opts = {}) {
17619
17788
  const updatedMs = updatedAt.getTime();
17620
17789
  if (!Number.isFinite(updatedMs)) return noDate;
17621
17790
  const now = opts.now ?? /* @__PURE__ */ new Date();
17622
- const nowMs3 = now.getTime();
17623
- if (!Number.isFinite(nowMs3)) return noDate;
17624
- const ageDays2 = Math.max(0, (nowMs3 - updatedMs) / DAY_MS2);
17791
+ const nowMs2 = now.getTime();
17792
+ if (!Number.isFinite(nowMs2)) return noDate;
17793
+ const ageDays2 = Math.max(0, (nowMs2 - updatedMs) / DAY_MS2);
17625
17794
  const perYearDecay = opts.perYearDecay ?? DEFAULT_PER_YEAR_DECAY;
17626
17795
  const floor = opts.floor ?? DEFAULT_FLOOR;
17627
17796
  const decay = perYearDecay * (ageDays2 / 365);
@@ -18133,9 +18302,6 @@ function createVaultEmbeddingCache(maxSize = DEFAULT_VAULT_CACHE_SIZE) {
18133
18302
 
18134
18303
  // src/lib/memoryVault/searchTool.ts
18135
18304
  var DEFAULT_RERANK_TOP_N = 5;
18136
- function nowMs() {
18137
- return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
18138
- }
18139
18305
  var warnedProgrammaticDecomposeIgnored = false;
18140
18306
  function normalizeEventTimeKind(kind) {
18141
18307
  return kind === "point" || kind === "range" || kind === "ongoing" ? kind : null;
@@ -21037,6 +21203,32 @@ function rankProfileCandidates(inputs, now = Date.now(), options = {}) {
21037
21203
  return ranked;
21038
21204
  }
21039
21205
 
21206
+ // src/lib/db/chat/embeddingCache.ts
21207
+ var MaskScopedEmbeddingCache = class extends Map {
21208
+ constructor(inner, masked) {
21209
+ super();
21210
+ this.inner = inner;
21211
+ this.masked = masked;
21212
+ }
21213
+ key(text14) {
21214
+ return `${this.masked ? "m" : "r"}:${text14}`;
21215
+ }
21216
+ get(text14) {
21217
+ return this.inner.get(this.key(text14));
21218
+ }
21219
+ set(text14, value) {
21220
+ this.inner.set(this.key(text14), value);
21221
+ return this;
21222
+ }
21223
+ };
21224
+ var scopedViews = /* @__PURE__ */ new WeakMap();
21225
+ function maskScopedEmbeddingCache(cache, masked) {
21226
+ const views = scopedViews.get(cache) ?? {};
21227
+ scopedViews.set(cache, views);
21228
+ const slot = masked ? "masked" : "raw";
21229
+ return views[slot] ?? (views[slot] = new MaskScopedEmbeddingCache(cache, masked));
21230
+ }
21231
+
21040
21232
  // src/lib/memory/recall.ts
21041
21233
  init_logger();
21042
21234
 
@@ -21247,9 +21439,6 @@ function scoreEventTimeOverlap(memoryStart, memoryEnd, memoryKind, window2) {
21247
21439
  var DEFAULT_LIMIT = 8;
21248
21440
  var DEFAULT_BUDGET = "low";
21249
21441
  var DEFAULT_FACT_MIN_SCORE = 0.1;
21250
- function nowMs2() {
21251
- return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
21252
- }
21253
21442
  var DEFAULT_CHUNK_MIN_SCORE = 0.5;
21254
21443
  var DEFAULT_QUERY_EMBED_TOTAL_TIMEOUT_MS = 8e3;
21255
21444
  function flagsForBudget(budget) {
@@ -21287,12 +21476,19 @@ async function recall(query, ctx, options = {}) {
21287
21476
  const subQueries = normalizedFacets.length >= 2 ? normalizedFacets : void 0;
21288
21477
  const embeddingOptions = {
21289
21478
  ...ctx.embeddingOptions,
21290
- taskType: "memory_recall_embed"
21479
+ taskType: "memory_recall_embed",
21480
+ ...options.embeddingCache && !ctx.embeddingOptions.cache && {
21481
+ cache: maskScopedEmbeddingCache(
21482
+ options.embeddingCache,
21483
+ Boolean(ctx.embeddingOptions.maskInput)
21484
+ )
21485
+ }
21291
21486
  };
21292
- const t0 = nowMs2();
21487
+ const t0 = nowMs();
21293
21488
  let prepMs = 0;
21294
21489
  let factLaneMs = 0;
21295
21490
  let chunkLaneMs = 0;
21491
+ let chunkStats;
21296
21492
  let fuseMs = 0;
21297
21493
  const factResults = [];
21298
21494
  const chunkResults = [];
@@ -21347,6 +21543,11 @@ async function recall(query, ctx, options = {}) {
21347
21543
  ...decryptLastRan !== void 0 && { decryptLast: decryptLastRan },
21348
21544
  ...vaultRowsDecrypted !== void 0 && { vaultRowsDecrypted },
21349
21545
  ...vaultRowsEmbedded !== void 0 && { vaultRowsEmbedded },
21546
+ ...chunkStats && {
21547
+ chunkRowsScanned: chunkStats.rowsScanned,
21548
+ chunkRowsFetched: chunkStats.rowsFetched,
21549
+ chunkCacheHits: chunkStats.cacheHits
21550
+ },
21350
21551
  factCount: factResults.length,
21351
21552
  chunkCount: chunkResults.length,
21352
21553
  admittedCount: admitted.length,
@@ -21358,7 +21559,7 @@ async function recall(query, ctx, options = {}) {
21358
21559
  temporalLaneCount,
21359
21560
  emptyReason: emptyReasonFor(admitted.length, laneRan),
21360
21561
  timings: {
21361
- total: nowMs2() - t0,
21562
+ total: nowMs() - t0,
21362
21563
  prep: prepMs,
21363
21564
  factLane: factLaneMs,
21364
21565
  rerank: rerankMs,
@@ -21380,14 +21581,14 @@ async function recall(query, ctx, options = {}) {
21380
21581
  const needsChunkEmbedding = types.includes("chunk") && ctx.storageCtx;
21381
21582
  const wantsTemporal = types.includes("fact") && (ctx.factSource || ctx.vaultCtx);
21382
21583
  const queryEmbedTotalTimeoutMs = options.queryEmbedTotalTimeoutMs ?? DEFAULT_QUERY_EMBED_TOTAL_TIMEOUT_MS;
21383
- const prepStart = nowMs2();
21584
+ const prepStart = nowMs();
21384
21585
  let sharedEmbedMs = 0;
21385
21586
  const [queryEmbedding, entityRanking, temporalRanking] = await Promise.all([
21386
21587
  needsChunkEmbedding ? generateEmbedding(query, {
21387
21588
  ...embeddingOptions,
21388
21589
  totalTimeoutMs: queryEmbedTotalTimeoutMs
21389
21590
  }).finally(() => {
21390
- sharedEmbedMs = nowMs2() - prepStart;
21591
+ sharedEmbedMs = nowMs() - prepStart;
21391
21592
  }).then((vec) => {
21392
21593
  if (vec.length === 0) {
21393
21594
  getLogger().warn(
@@ -21420,11 +21621,11 @@ async function recall(query, ctx, options = {}) {
21420
21621
  () => ctx.factSource ? ctx.factSource.temporalRanking(query, options.now) : buildTemporalLaneRanking(query, ctx.vaultCtx, options.now)
21421
21622
  ) : Promise.resolve([])
21422
21623
  ]);
21423
- prepMs = nowMs2() - prepStart;
21624
+ prepMs = nowMs() - prepStart;
21424
21625
  graphLaneCount = entityRanking.length;
21425
21626
  temporalLaneCount = temporalRanking.length;
21426
21627
  if (types.includes("fact") && (ctx.factSource || ctx.vaultCtx && ctx.vaultCache)) {
21427
- const factStart = nowMs2();
21628
+ const factStart = nowMs();
21428
21629
  const vaultMinScore = options.minScore ?? DEFAULT_FACT_MIN_SCORE;
21429
21630
  factFloor = vaultMinScore;
21430
21631
  const {
@@ -21500,11 +21701,11 @@ async function recall(query, ctx, options = {}) {
21500
21701
  factLaneRankedOnCosine = factRankedOnCosine;
21501
21702
  decryptLastRan = factDecryptLast;
21502
21703
  vaultRowsDecrypted = factRowsDecrypted;
21503
- factLaneMs = nowMs2() - factStart;
21704
+ factLaneMs = nowMs() - factStart;
21504
21705
  }
21505
21706
  if (chunkEmbedFailed && !factLaneRankedOnCosine) embeddingsUnavailable = true;
21506
21707
  if (types.includes("chunk") && ctx.storageCtx && queryEmbedding) {
21507
- const chunkStart = nowMs2();
21708
+ const chunkStart = nowMs();
21508
21709
  const chunkMinScore = options.minScore ?? DEFAULT_CHUNK_MIN_SCORE;
21509
21710
  chunkFloor = chunkMinScore;
21510
21711
  const results = await searchChunksOp(ctx.storageCtx, queryEmbedding, {
@@ -21515,12 +21716,15 @@ async function recall(query, ctx, options = {}) {
21515
21716
  ...options.excludeConversationId && {
21516
21717
  excludeConversationId: options.excludeConversationId
21517
21718
  },
21518
- ...ctx.chunkCache && { chunkCache: ctx.chunkCache }
21719
+ ...ctx.chunkCache && { chunkCache: ctx.chunkCache },
21720
+ onStats: (stats) => {
21721
+ chunkStats = stats;
21722
+ }
21519
21723
  });
21520
21724
  chunkResults.push(...dedupeBy(results, (r) => r.chunkText.trim()));
21521
- chunkLaneMs = nowMs2() - chunkStart;
21725
+ chunkLaneMs = nowMs() - chunkStart;
21522
21726
  }
21523
- const fuseStart = nowMs2();
21727
+ const fuseStart = nowMs();
21524
21728
  if (types.length === 1 || factResults.length === 0 || chunkResults.length === 0) {
21525
21729
  const memories2 = [
21526
21730
  ...factResults.map((r) => toFactMemory(r, options.now)),
@@ -21528,7 +21732,7 @@ async function recall(query, ctx, options = {}) {
21528
21732
  ];
21529
21733
  memories2.sort((a, b) => b.score - a.score);
21530
21734
  const candidateCount = factResults.length + chunkResults.length;
21531
- fuseMs = nowMs2() - fuseStart;
21735
+ fuseMs = nowMs() - fuseStart;
21532
21736
  const admitted = memories2.slice(0, limit);
21533
21737
  hitLimit = memories2.length > limit;
21534
21738
  emitDiagnostics(candidateCount, admitted);
@@ -21592,7 +21796,7 @@ async function recall(query, ctx, options = {}) {
21592
21796
  memories = selection.out;
21593
21797
  }
21594
21798
  hitLimit = selection.cut;
21595
- fuseMs = nowMs2() - fuseStart;
21799
+ fuseMs = nowMs() - fuseStart;
21596
21800
  emitDiagnostics(byId.size, memories);
21597
21801
  return {
21598
21802
  memories,
@@ -31612,28 +31816,6 @@ async function cleanupConversationSummary(database, conversationId) {
31612
31816
  }
31613
31817
  }
31614
31818
 
31615
- // src/lib/db/chat/embeddingCache.ts
31616
- var MaskScopedEmbeddingCache = class extends Map {
31617
- constructor(inner, masked) {
31618
- super();
31619
- this.inner = inner;
31620
- this.masked = masked;
31621
- }
31622
- key(text14) {
31623
- return `${this.masked ? "m" : "r"}:${text14}`;
31624
- }
31625
- get(text14) {
31626
- return this.inner.get(this.key(text14));
31627
- }
31628
- set(text14, value) {
31629
- this.inner.set(this.key(text14), value);
31630
- return this;
31631
- }
31632
- };
31633
- function maskScopedEmbeddingCache(cache, masked) {
31634
- return new MaskScopedEmbeddingCache(cache, masked);
31635
- }
31636
-
31637
31819
  // src/react/useChatStorage.ts
31638
31820
  init_logger();
31639
31821
  init_redactor();