@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.
@@ -1813,6 +1813,35 @@ function decodeChunkVector(vector, onMalformed) {
1813
1813
  }
1814
1814
  }
1815
1815
 
1816
+ // src/lib/scheduling.ts
1817
+ function nowMs() {
1818
+ return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
1819
+ }
1820
+ function yieldToEventLoop() {
1821
+ const scheduler = globalThis.scheduler;
1822
+ if (typeof scheduler?.yield === "function") return scheduler.yield();
1823
+ if (typeof MessageChannel === "function") {
1824
+ return new Promise((resolve) => {
1825
+ const channel = new MessageChannel();
1826
+ channel.port1.onmessage = () => {
1827
+ channel.port1.close();
1828
+ channel.port2.close();
1829
+ resolve();
1830
+ };
1831
+ channel.port2.postMessage(null);
1832
+ });
1833
+ }
1834
+ return new Promise((resolve) => setTimeout(resolve, 0));
1835
+ }
1836
+ function createTimeSliceYielder(budgetMs) {
1837
+ let sliceStart = nowMs();
1838
+ return async () => {
1839
+ if (nowMs() - sliceStart < budgetMs) return;
1840
+ await yieldToEventLoop();
1841
+ sliceStart = nowMs();
1842
+ };
1843
+ }
1844
+
1816
1845
  // src/lib/db/encryption-utils.ts
1817
1846
  var ENCRYPTION_PREFIX = "enc:v3:";
1818
1847
  function isEncrypted(value) {
@@ -3193,6 +3222,95 @@ async function searchMessagesOp(ctx, queryVector, options) {
3193
3222
  })
3194
3223
  );
3195
3224
  }
3225
+ var CHUNK_VECTOR_BATCH_SIZE = 50;
3226
+ var CHUNK_SCAN_YIELD_BUDGET_MS = 12;
3227
+ var hasEmbeddingSql = `(coalesce("chunks", '') != '' or coalesce("vector", '') != '')`;
3228
+ function toChunkCandidateKey(raw) {
3229
+ return {
3230
+ id: raw.id,
3231
+ conversationId: String(raw.conversation_id),
3232
+ version: Number(raw.updated_at) || 0,
3233
+ embeddingModel: raw.embedding_model ?? null
3234
+ };
3235
+ }
3236
+ async function fetchRowsByIds(ctx, ids) {
3237
+ if (ids.length === 0) return [];
3238
+ return await ctx.messagesCollection.query(Q.where("id", Q.oneOf(ids))).unsafeFetchRaw();
3239
+ }
3240
+ async function projectedChunkRowSource(ctx, filter) {
3241
+ const clauses = [
3242
+ `"history"."_status" is not 'deleted'`,
3243
+ hasEmbeddingSql,
3244
+ `exists (select 1 from "conversations" where "conversations"."conversation_id" = "history"."conversation_id" and "conversations"."is_deleted" = 0 and "conversations"."_status" is not 'deleted')`
3245
+ ];
3246
+ const args = [];
3247
+ if (filter.conversationId !== void 0) {
3248
+ clauses.push(`"history"."conversation_id" = ?`);
3249
+ args.push(filter.conversationId);
3250
+ }
3251
+ if (filter.excludeConversationId !== void 0) {
3252
+ clauses.push(`"history"."conversation_id" != ?`);
3253
+ args.push(filter.excludeConversationId);
3254
+ }
3255
+ const keyRows = await ctx.messagesCollection.query(
3256
+ Q.unsafeSqlQuery(
3257
+ `select "id", "conversation_id", "updated_at", "embedding_model" from "history" where ${clauses.join(" and ")}`,
3258
+ args
3259
+ )
3260
+ ).unsafeFetchRaw();
3261
+ return {
3262
+ keys: keyRows.map(toChunkCandidateKey),
3263
+ loadVectorRows: async (ids) => {
3264
+ if (ids.length === 0) return [];
3265
+ return await ctx.messagesCollection.query(
3266
+ Q.unsafeSqlQuery(
3267
+ `select "id", "updated_at", "chunks", "vector" from "history" where "_status" is not 'deleted' and "id" in (${ids.map(() => "?").join(",")})`,
3268
+ ids
3269
+ )
3270
+ ).unsafeFetchRaw();
3271
+ },
3272
+ loadFullRows: (ids) => fetchRowsByIds(ctx, ids)
3273
+ };
3274
+ }
3275
+ async function fullScanChunkRowSource(ctx, filter) {
3276
+ const activeConversations = await ctx.conversationsCollection.query(Q.where("is_deleted", false)).fetch();
3277
+ const activeConversationIds = new Set(
3278
+ activeConversations.map((c) => String(c._getRaw("conversation_id")))
3279
+ );
3280
+ const queryConditions = filter.conversationId !== void 0 ? [Q.where("conversation_id", filter.conversationId)] : [];
3281
+ const rows = await ctx.messagesCollection.query(...queryConditions).unsafeFetchRaw();
3282
+ const rowsById = /* @__PURE__ */ new Map();
3283
+ for (const row of rows) {
3284
+ const conversationId = String(row.conversation_id);
3285
+ if (!activeConversationIds.has(conversationId)) continue;
3286
+ if (filter.excludeConversationId !== void 0 && conversationId === filter.excludeConversationId) {
3287
+ continue;
3288
+ }
3289
+ if (!row.chunks && !row.vector) continue;
3290
+ rowsById.set(row.id, row);
3291
+ }
3292
+ const pick = (ids) => Promise.resolve(
3293
+ ids.flatMap((id) => {
3294
+ const row = rowsById.get(id);
3295
+ return row ? [row] : [];
3296
+ })
3297
+ );
3298
+ return {
3299
+ keys: Array.from(rowsById.values(), toChunkCandidateKey),
3300
+ loadVectorRows: pick,
3301
+ loadFullRows: pick
3302
+ };
3303
+ }
3304
+ async function chunkRowSource(ctx, filter) {
3305
+ try {
3306
+ return await projectedChunkRowSource(ctx, filter);
3307
+ } catch (err) {
3308
+ getLogger().debug(
3309
+ "searchChunksOp: projected SQL unavailable, using full-load fallback: " + (err instanceof Error ? err.message : String(err))
3310
+ );
3311
+ return fullScanChunkRowSource(ctx, filter);
3312
+ }
3313
+ }
3196
3314
  async function searchChunksOp(ctx, queryVector, options) {
3197
3315
  const {
3198
3316
  limit = 10,
@@ -3200,83 +3318,111 @@ async function searchChunksOp(ctx, queryVector, options) {
3200
3318
  conversationId,
3201
3319
  excludeConversationId,
3202
3320
  embeddingModel,
3203
- chunkCache
3321
+ chunkCache,
3322
+ onStats
3204
3323
  } = options || {};
3205
- const activeConversations = await ctx.conversationsCollection.query(Q.where("is_deleted", false)).fetch();
3206
- const activeConversationIds = new Set(
3207
- activeConversations.map((c) => String(c._getRaw("conversation_id")))
3208
- );
3209
- const queryConditions = conversationId ? [Q.where("conversation_id", conversationId)] : [];
3210
- const messages = await ctx.messagesCollection.query(...queryConditions).unsafeFetchRaw();
3324
+ const source = await chunkRowSource(ctx, { conversationId, excludeConversationId });
3325
+ const yieldIfDue = createTimeSliceYielder(CHUNK_SCAN_YIELD_BUDGET_MS);
3211
3326
  const candidates = [];
3212
3327
  let staleSkipped = 0;
3213
3328
  let unreadableVectors = 0;
3214
- for (const message of messages) {
3215
- const msgConvId = String(message.conversation_id);
3216
- if (!activeConversationIds.has(msgConvId)) continue;
3217
- if (excludeConversationId !== void 0 && msgConvId === excludeConversationId) continue;
3218
- if (embeddingModel) {
3219
- const storedModel = message.embedding_model;
3220
- if (storedModel !== null && storedModel !== void 0 && storedModel !== embeddingModel) {
3221
- staleSkipped++;
3222
- continue;
3223
- }
3329
+ let cacheHits = 0;
3330
+ const missIds = [];
3331
+ const orderById = /* @__PURE__ */ new Map();
3332
+ for (const [order, key] of source.keys.entries()) {
3333
+ await yieldIfDue();
3334
+ orderById.set(key.id, order);
3335
+ if (embeddingModel && key.embeddingModel !== null && key.embeddingModel !== embeddingModel) {
3336
+ staleSkipped++;
3337
+ continue;
3224
3338
  }
3225
- const version = Number(message.updated_at) || 0;
3226
- const cached = chunkCache?.get(message.id);
3227
- if (cached && cached.version === version) {
3228
- if (cached.chunks.length > 0) {
3229
- for (let ci = 0; ci < cached.chunks.length; ci++) {
3230
- const vec = cached.chunks[ci];
3231
- if (vec.length === 0) continue;
3232
- const similarity = cosineSimilarity(queryVector, vec);
3233
- if (similarity >= minSimilarity) {
3234
- candidates.push({
3235
- message,
3236
- similarity,
3237
- chunkTextSource: { kind: "chunk-cached", chunkIndex: ci, version }
3238
- });
3239
- }
3240
- }
3241
- } else if (cached.fallback) {
3242
- const similarity = cosineSimilarity(queryVector, cached.fallback);
3243
- if (similarity >= minSimilarity) {
3244
- candidates.push({ message, similarity, chunkTextSource: { kind: "message" } });
3245
- }
3246
- }
3339
+ const cached = chunkCache?.get(key.id);
3340
+ if (!cached || cached.version !== key.version) {
3341
+ missIds.push(key.id);
3247
3342
  continue;
3248
3343
  }
3249
- const chunks3 = await readRawJsonField(message, "chunks", ctx.walletAddress);
3250
- if (chunks3 && chunks3.length > 0) {
3251
- const vectors = chunks3.map(
3252
- (chunk) => decodeChunkVector(chunk.vector, () => {
3253
- unreadableVectors++;
3254
- })
3255
- );
3256
- chunkCache?.set(message.id, { version, chunks: vectors });
3257
- for (let ci = 0; ci < chunks3.length; ci++) {
3258
- const vec = vectors[ci];
3344
+ cacheHits++;
3345
+ if (cached.chunks.length > 0) {
3346
+ for (let ci = 0; ci < cached.chunks.length; ci++) {
3347
+ const vec = cached.chunks[ci];
3259
3348
  if (vec.length === 0) continue;
3260
3349
  const similarity = cosineSimilarity(queryVector, vec);
3261
3350
  if (similarity >= minSimilarity) {
3262
3351
  candidates.push({
3263
- message,
3352
+ id: key.id,
3353
+ version: key.version,
3354
+ order,
3264
3355
  similarity,
3265
- chunkTextSource: { kind: "chunk", chunk: chunks3[ci], siblings: chunks3 }
3356
+ chunkTextSource: { kind: "chunk-cached", chunkIndex: ci, version: key.version }
3266
3357
  });
3267
3358
  }
3268
3359
  }
3269
- } else {
3270
- const messageVector = await readRawJsonField(message, "vector", ctx.walletAddress);
3271
- if (!messageVector || messageVector.length === 0) continue;
3272
- const fallback = Float32Array.from(messageVector);
3273
- chunkCache?.set(message.id, { version, chunks: [], fallback });
3274
- const similarity = cosineSimilarity(queryVector, fallback);
3360
+ } else if (cached.fallback) {
3361
+ const similarity = cosineSimilarity(queryVector, cached.fallback);
3275
3362
  if (similarity >= minSimilarity) {
3276
- candidates.push({ message, similarity, chunkTextSource: { kind: "message" } });
3363
+ candidates.push({
3364
+ id: key.id,
3365
+ version: key.version,
3366
+ order,
3367
+ similarity,
3368
+ chunkTextSource: { kind: "message" }
3369
+ });
3370
+ }
3371
+ }
3372
+ }
3373
+ let rowsFetched = 0;
3374
+ for (let offset = 0; offset < missIds.length; offset += CHUNK_VECTOR_BATCH_SIZE) {
3375
+ const rows = await source.loadVectorRows(
3376
+ missIds.slice(offset, offset + CHUNK_VECTOR_BATCH_SIZE)
3377
+ );
3378
+ rowsFetched += rows.length;
3379
+ for (const row of rows) {
3380
+ await yieldIfDue();
3381
+ const id = row.id;
3382
+ const version = Number(row.updated_at) || 0;
3383
+ const order = orderById.get(id) ?? Number.MAX_SAFE_INTEGER;
3384
+ const chunks3 = await readRawJsonField(row, "chunks", ctx.walletAddress);
3385
+ if (chunks3 && chunks3.length > 0) {
3386
+ const vectors = chunks3.map(
3387
+ (chunk) => decodeChunkVector(chunk.vector, () => {
3388
+ unreadableVectors++;
3389
+ })
3390
+ );
3391
+ chunkCache?.set(id, { version, chunks: vectors });
3392
+ for (let ci = 0; ci < chunks3.length; ci++) {
3393
+ const vec = vectors[ci];
3394
+ if (vec.length === 0) continue;
3395
+ const similarity = cosineSimilarity(queryVector, vec);
3396
+ if (similarity >= minSimilarity) {
3397
+ candidates.push({
3398
+ id,
3399
+ version,
3400
+ order,
3401
+ similarity,
3402
+ chunkTextSource: { kind: "chunk", chunk: chunks3[ci], siblings: chunks3 }
3403
+ });
3404
+ }
3405
+ }
3406
+ } else {
3407
+ const messageVector = await readRawJsonField(row, "vector", ctx.walletAddress);
3408
+ if (messageVector && messageVector.length > 0) {
3409
+ const fallback = Float32Array.from(messageVector);
3410
+ chunkCache?.set(id, { version, chunks: [], fallback });
3411
+ const similarity = cosineSimilarity(queryVector, fallback);
3412
+ if (similarity >= minSimilarity) {
3413
+ candidates.push({
3414
+ id,
3415
+ version,
3416
+ order,
3417
+ similarity,
3418
+ chunkTextSource: { kind: "message" }
3419
+ });
3420
+ }
3421
+ }
3277
3422
  }
3278
3423
  }
3279
3424
  }
3425
+ onStats?.({ rowsScanned: source.keys.length, rowsFetched, cacheHits });
3280
3426
  if (staleSkipped > 0) {
3281
3427
  getLogger().warn(
3282
3428
  `searchChunksOp: skipped ${staleSkipped} messages whose embedding model differs from the current model (${embeddingModel}) \u2014 re-embed via chunkAndEmbedAllMessages`
@@ -3287,12 +3433,17 @@ async function searchChunksOp(ctx, queryVector, options) {
3287
3433
  `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`
3288
3434
  );
3289
3435
  }
3290
- const topK = candidates.sort((a, b) => b.similarity - a.similarity).slice(0, limit);
3291
- const uniqueMessages = /* @__PURE__ */ new Map();
3292
- for (const c of topK) uniqueMessages.set(c.message.id, c.message);
3436
+ const topK = candidates.sort((a, b) => b.similarity - a.similarity || a.order - b.order).slice(0, limit);
3437
+ const topIds = Array.from(new Set(topK.map((c) => c.id)));
3438
+ const fullRows = await source.loadFullRows(topIds);
3439
+ const rowById = new Map(fullRows.map((row) => [row.id, row]));
3440
+ const liveTopK = topK.filter((c) => {
3441
+ const row = rowById.get(c.id);
3442
+ return row !== void 0 && (Number(row.updated_at) || 0) === c.version;
3443
+ });
3293
3444
  const storedById = new Map(
3294
3445
  await Promise.all(
3295
- Array.from(uniqueMessages, async ([id, m]) => {
3446
+ Array.from(rowById, async ([id, m]) => {
3296
3447
  const stored2 = await messageRawToStored(
3297
3448
  m,
3298
3449
  ctx.walletAddress,
@@ -3305,24 +3456,24 @@ async function searchChunksOp(ctx, queryVector, options) {
3305
3456
  );
3306
3457
  const chunkStateByMsgId = /* @__PURE__ */ new Map();
3307
3458
  const needsChunkText = /* @__PURE__ */ new Set();
3308
- for (const c of topK) {
3309
- if (c.chunkTextSource.kind === "chunk-cached") needsChunkText.add(c.message.id);
3459
+ for (const c of liveTopK) {
3460
+ if (c.chunkTextSource.kind === "chunk-cached") needsChunkText.add(c.id);
3310
3461
  }
3311
3462
  await Promise.all(
3312
3463
  Array.from(needsChunkText, async (id) => {
3313
- const m = uniqueMessages.get(id);
3464
+ const m = rowById.get(id);
3314
3465
  const parsed = await readRawJsonField(m, "chunks", ctx.walletAddress);
3315
3466
  const version = Number(m.updated_at) || 0;
3316
3467
  if (parsed) chunkStateByMsgId.set(id, { version, chunks: parsed });
3317
3468
  })
3318
3469
  );
3319
- return topK.map(({ message, similarity, chunkTextSource }) => {
3320
- const stored2 = { ...storedById.get(message.id) };
3470
+ return liveTopK.map(({ id, similarity, chunkTextSource }) => {
3471
+ const stored2 = { ...storedById.get(id) };
3321
3472
  let chunkText2;
3322
3473
  if (chunkTextSource.kind === "chunk") {
3323
3474
  chunkText2 = resolveChunkText(stored2.content, chunkTextSource.chunk, chunkTextSource.siblings);
3324
3475
  } else if (chunkTextSource.kind === "chunk-cached") {
3325
- const state = chunkStateByMsgId.get(message.id);
3476
+ const state = chunkStateByMsgId.get(id);
3326
3477
  const chunk = state && state.version === chunkTextSource.version ? state.chunks[chunkTextSource.chunkIndex] : void 0;
3327
3478
  chunkText2 = resolveChunkText(stored2.content, chunk, state?.chunks);
3328
3479
  } else {
@@ -4476,13 +4627,35 @@ async function generateEmbedding(text14, options) {
4476
4627
  if (total === void 0) return embedOne(text14, options, void 0);
4477
4628
  return withDeadline((signal) => embedOne(text14, options, signal), total, "embedding");
4478
4629
  }
4630
+ var inFlightByCache = /* @__PURE__ */ new WeakMap();
4479
4631
  async function embedOne(text14, options, outer) {
4480
- const { baseUrl = BASE_URL, model, cache } = options;
4481
- const timeoutMs = options.timeoutMs ?? DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS;
4482
- if (cache) {
4483
- const cached = cache.get(text14);
4484
- if (cached) return Array.from(cached);
4632
+ const { cache } = options;
4633
+ if (!cache) return Array.from(await requestEmbedding(text14, options, outer));
4634
+ const cached = cache.get(text14);
4635
+ if (cached) return Array.from(cached);
4636
+ const inFlight = inFlightByCache.get(cache) ?? /* @__PURE__ */ new Map();
4637
+ inFlightByCache.set(cache, inFlight);
4638
+ const pending = inFlight.get(text14);
4639
+ if (pending) {
4640
+ try {
4641
+ return Array.from(await pending);
4642
+ } catch (err) {
4643
+ if (outer?.aborted) throw err;
4644
+ }
4645
+ }
4646
+ const request = requestEmbedding(text14, options, outer);
4647
+ inFlight.set(text14, request);
4648
+ try {
4649
+ const embedding2 = await request;
4650
+ cache.set(text14, embedding2);
4651
+ return Array.from(embedding2);
4652
+ } finally {
4653
+ if (inFlight.get(text14) === request) inFlight.delete(text14);
4485
4654
  }
4655
+ }
4656
+ async function requestEmbedding(text14, options, outer) {
4657
+ const { baseUrl = BASE_URL, model } = options;
4658
+ const timeoutMs = options.timeoutMs ?? DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS;
4486
4659
  const headers = {
4487
4660
  ...await resolveAuthHeaders(options, outer),
4488
4661
  ...taskTypeHeader(options.taskType)
@@ -4517,11 +4690,7 @@ async function embedOne(text14, options, outer) {
4517
4690
  totalTokens: response.data.usage.total_tokens ?? 0
4518
4691
  });
4519
4692
  }
4520
- const f32Embedding = Float32Array.from(embedding2);
4521
- if (cache) {
4522
- cache.set(text14, f32Embedding);
4523
- }
4524
- return Array.from(f32Embedding);
4693
+ return Float32Array.from(embedding2);
4525
4694
  }
4526
4695
  var DEFAULT_EMBEDDING_BATCH_SIZE = 100;
4527
4696
  var DEFAULT_EMBEDDING_BATCH_CONCURRENCY = 3;
@@ -15801,9 +15970,9 @@ function recencyMultiplier(updatedAt, opts = {}) {
15801
15970
  const updatedMs = updatedAt.getTime();
15802
15971
  if (!Number.isFinite(updatedMs)) return noDate;
15803
15972
  const now = opts.now ?? /* @__PURE__ */ new Date();
15804
- const nowMs3 = now.getTime();
15805
- if (!Number.isFinite(nowMs3)) return noDate;
15806
- const ageDays2 = Math.max(0, (nowMs3 - updatedMs) / DAY_MS2);
15973
+ const nowMs2 = now.getTime();
15974
+ if (!Number.isFinite(nowMs2)) return noDate;
15975
+ const ageDays2 = Math.max(0, (nowMs2 - updatedMs) / DAY_MS2);
15807
15976
  const perYearDecay = opts.perYearDecay ?? DEFAULT_PER_YEAR_DECAY;
15808
15977
  const floor = opts.floor ?? DEFAULT_FLOOR;
15809
15978
  const decay = perYearDecay * (ageDays2 / 365);
@@ -16312,9 +16481,6 @@ function createVaultEmbeddingCache(maxSize = DEFAULT_VAULT_CACHE_SIZE) {
16312
16481
 
16313
16482
  // src/lib/memoryVault/searchTool.ts
16314
16483
  var DEFAULT_RERANK_TOP_N = 5;
16315
- function nowMs() {
16316
- return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
16317
- }
16318
16484
  var warnedProgrammaticDecomposeIgnored = false;
16319
16485
  function normalizeEventTimeKind(kind) {
16320
16486
  return kind === "point" || kind === "range" || kind === "ongoing" ? kind : null;
@@ -19205,6 +19371,32 @@ function rankProfileCandidates(inputs, now = Date.now(), options = {}) {
19205
19371
  return ranked;
19206
19372
  }
19207
19373
 
19374
+ // src/lib/db/chat/embeddingCache.ts
19375
+ var MaskScopedEmbeddingCache = class extends Map {
19376
+ constructor(inner, masked) {
19377
+ super();
19378
+ this.inner = inner;
19379
+ this.masked = masked;
19380
+ }
19381
+ key(text14) {
19382
+ return `${this.masked ? "m" : "r"}:${text14}`;
19383
+ }
19384
+ get(text14) {
19385
+ return this.inner.get(this.key(text14));
19386
+ }
19387
+ set(text14, value) {
19388
+ this.inner.set(this.key(text14), value);
19389
+ return this;
19390
+ }
19391
+ };
19392
+ var scopedViews = /* @__PURE__ */ new WeakMap();
19393
+ function maskScopedEmbeddingCache(cache, masked) {
19394
+ const views = scopedViews.get(cache) ?? {};
19395
+ scopedViews.set(cache, views);
19396
+ const slot = masked ? "masked" : "raw";
19397
+ return views[slot] ?? (views[slot] = new MaskScopedEmbeddingCache(cache, masked));
19398
+ }
19399
+
19208
19400
  // src/lib/memory/queryTemporal.ts
19209
19401
  var RELATIVE_DAY_ENTRIES = [
19210
19402
  [/\btoday\b/, "today", 0],
@@ -19412,9 +19604,6 @@ function scoreEventTimeOverlap(memoryStart, memoryEnd, memoryKind, window2) {
19412
19604
  var DEFAULT_LIMIT = 8;
19413
19605
  var DEFAULT_BUDGET = "low";
19414
19606
  var DEFAULT_FACT_MIN_SCORE = 0.1;
19415
- function nowMs2() {
19416
- return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
19417
- }
19418
19607
  var DEFAULT_CHUNK_MIN_SCORE = 0.5;
19419
19608
  var DEFAULT_QUERY_EMBED_TOTAL_TIMEOUT_MS = 8e3;
19420
19609
  function flagsForBudget(budget) {
@@ -19452,12 +19641,19 @@ async function recall(query, ctx, options = {}) {
19452
19641
  const subQueries = normalizedFacets.length >= 2 ? normalizedFacets : void 0;
19453
19642
  const embeddingOptions = {
19454
19643
  ...ctx.embeddingOptions,
19455
- taskType: "memory_recall_embed"
19644
+ taskType: "memory_recall_embed",
19645
+ ...options.embeddingCache && !ctx.embeddingOptions.cache && {
19646
+ cache: maskScopedEmbeddingCache(
19647
+ options.embeddingCache,
19648
+ Boolean(ctx.embeddingOptions.maskInput)
19649
+ )
19650
+ }
19456
19651
  };
19457
- const t0 = nowMs2();
19652
+ const t0 = nowMs();
19458
19653
  let prepMs = 0;
19459
19654
  let factLaneMs = 0;
19460
19655
  let chunkLaneMs = 0;
19656
+ let chunkStats;
19461
19657
  let fuseMs = 0;
19462
19658
  const factResults = [];
19463
19659
  const chunkResults = [];
@@ -19512,6 +19708,11 @@ async function recall(query, ctx, options = {}) {
19512
19708
  ...decryptLastRan !== void 0 && { decryptLast: decryptLastRan },
19513
19709
  ...vaultRowsDecrypted !== void 0 && { vaultRowsDecrypted },
19514
19710
  ...vaultRowsEmbedded !== void 0 && { vaultRowsEmbedded },
19711
+ ...chunkStats && {
19712
+ chunkRowsScanned: chunkStats.rowsScanned,
19713
+ chunkRowsFetched: chunkStats.rowsFetched,
19714
+ chunkCacheHits: chunkStats.cacheHits
19715
+ },
19515
19716
  factCount: factResults.length,
19516
19717
  chunkCount: chunkResults.length,
19517
19718
  admittedCount: admitted.length,
@@ -19523,7 +19724,7 @@ async function recall(query, ctx, options = {}) {
19523
19724
  temporalLaneCount,
19524
19725
  emptyReason: emptyReasonFor(admitted.length, laneRan),
19525
19726
  timings: {
19526
- total: nowMs2() - t0,
19727
+ total: nowMs() - t0,
19527
19728
  prep: prepMs,
19528
19729
  factLane: factLaneMs,
19529
19730
  rerank: rerankMs,
@@ -19545,14 +19746,14 @@ async function recall(query, ctx, options = {}) {
19545
19746
  const needsChunkEmbedding = types.includes("chunk") && ctx.storageCtx;
19546
19747
  const wantsTemporal = types.includes("fact") && (ctx.factSource || ctx.vaultCtx);
19547
19748
  const queryEmbedTotalTimeoutMs = options.queryEmbedTotalTimeoutMs ?? DEFAULT_QUERY_EMBED_TOTAL_TIMEOUT_MS;
19548
- const prepStart = nowMs2();
19749
+ const prepStart = nowMs();
19549
19750
  let sharedEmbedMs = 0;
19550
19751
  const [queryEmbedding, entityRanking, temporalRanking] = await Promise.all([
19551
19752
  needsChunkEmbedding ? generateEmbedding(query, {
19552
19753
  ...embeddingOptions,
19553
19754
  totalTimeoutMs: queryEmbedTotalTimeoutMs
19554
19755
  }).finally(() => {
19555
- sharedEmbedMs = nowMs2() - prepStart;
19756
+ sharedEmbedMs = nowMs() - prepStart;
19556
19757
  }).then((vec) => {
19557
19758
  if (vec.length === 0) {
19558
19759
  getLogger().warn(
@@ -19585,11 +19786,11 @@ async function recall(query, ctx, options = {}) {
19585
19786
  () => ctx.factSource ? ctx.factSource.temporalRanking(query, options.now) : buildTemporalLaneRanking(query, ctx.vaultCtx, options.now)
19586
19787
  ) : Promise.resolve([])
19587
19788
  ]);
19588
- prepMs = nowMs2() - prepStart;
19789
+ prepMs = nowMs() - prepStart;
19589
19790
  graphLaneCount = entityRanking.length;
19590
19791
  temporalLaneCount = temporalRanking.length;
19591
19792
  if (types.includes("fact") && (ctx.factSource || ctx.vaultCtx && ctx.vaultCache)) {
19592
- const factStart = nowMs2();
19793
+ const factStart = nowMs();
19593
19794
  const vaultMinScore = options.minScore ?? DEFAULT_FACT_MIN_SCORE;
19594
19795
  factFloor = vaultMinScore;
19595
19796
  const {
@@ -19665,11 +19866,11 @@ async function recall(query, ctx, options = {}) {
19665
19866
  factLaneRankedOnCosine = factRankedOnCosine;
19666
19867
  decryptLastRan = factDecryptLast;
19667
19868
  vaultRowsDecrypted = factRowsDecrypted;
19668
- factLaneMs = nowMs2() - factStart;
19869
+ factLaneMs = nowMs() - factStart;
19669
19870
  }
19670
19871
  if (chunkEmbedFailed && !factLaneRankedOnCosine) embeddingsUnavailable = true;
19671
19872
  if (types.includes("chunk") && ctx.storageCtx && queryEmbedding) {
19672
- const chunkStart = nowMs2();
19873
+ const chunkStart = nowMs();
19673
19874
  const chunkMinScore = options.minScore ?? DEFAULT_CHUNK_MIN_SCORE;
19674
19875
  chunkFloor = chunkMinScore;
19675
19876
  const results = await searchChunksOp(ctx.storageCtx, queryEmbedding, {
@@ -19680,12 +19881,15 @@ async function recall(query, ctx, options = {}) {
19680
19881
  ...options.excludeConversationId && {
19681
19882
  excludeConversationId: options.excludeConversationId
19682
19883
  },
19683
- ...ctx.chunkCache && { chunkCache: ctx.chunkCache }
19884
+ ...ctx.chunkCache && { chunkCache: ctx.chunkCache },
19885
+ onStats: (stats) => {
19886
+ chunkStats = stats;
19887
+ }
19684
19888
  });
19685
19889
  chunkResults.push(...dedupeBy(results, (r) => r.chunkText.trim()));
19686
- chunkLaneMs = nowMs2() - chunkStart;
19890
+ chunkLaneMs = nowMs() - chunkStart;
19687
19891
  }
19688
- const fuseStart = nowMs2();
19892
+ const fuseStart = nowMs();
19689
19893
  if (types.length === 1 || factResults.length === 0 || chunkResults.length === 0) {
19690
19894
  const memories2 = [
19691
19895
  ...factResults.map((r) => toFactMemory(r, options.now)),
@@ -19693,7 +19897,7 @@ async function recall(query, ctx, options = {}) {
19693
19897
  ];
19694
19898
  memories2.sort((a, b) => b.score - a.score);
19695
19899
  const candidateCount = factResults.length + chunkResults.length;
19696
- fuseMs = nowMs2() - fuseStart;
19900
+ fuseMs = nowMs() - fuseStart;
19697
19901
  const admitted = memories2.slice(0, limit);
19698
19902
  hitLimit = memories2.length > limit;
19699
19903
  emitDiagnostics(candidateCount, admitted);
@@ -19757,7 +19961,7 @@ async function recall(query, ctx, options = {}) {
19757
19961
  memories = selection.out;
19758
19962
  }
19759
19963
  hitLimit = selection.cut;
19760
- fuseMs = nowMs2() - fuseStart;
19964
+ fuseMs = nowMs() - fuseStart;
19761
19965
  emitDiagnostics(byId.size, memories);
19762
19966
  return {
19763
19967
  memories,
@@ -29760,28 +29964,6 @@ async function cleanupConversationSummary(database, conversationId) {
29760
29964
  }
29761
29965
  }
29762
29966
 
29763
- // src/lib/db/chat/embeddingCache.ts
29764
- var MaskScopedEmbeddingCache = class extends Map {
29765
- constructor(inner, masked) {
29766
- super();
29767
- this.inner = inner;
29768
- this.masked = masked;
29769
- }
29770
- key(text14) {
29771
- return `${this.masked ? "m" : "r"}:${text14}`;
29772
- }
29773
- get(text14) {
29774
- return this.inner.get(this.key(text14));
29775
- }
29776
- set(text14, value) {
29777
- this.inner.set(this.key(text14), value);
29778
- return this;
29779
- }
29780
- };
29781
- function maskScopedEmbeddingCache(cache, masked) {
29782
- return new MaskScopedEmbeddingCache(cache, masked);
29783
- }
29784
-
29785
29967
  // src/lib/tools/selection/activeToolSets.ts
29786
29968
  function mergeActiveToolSets(current, derived) {
29787
29969
  if (derived.length === 0) return current;