@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.
@@ -2884,6 +2884,35 @@ function decodeChunkVector(vector, onMalformed) {
2884
2884
  }
2885
2885
  }
2886
2886
 
2887
+ // src/lib/scheduling.ts
2888
+ function nowMs() {
2889
+ return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
2890
+ }
2891
+ function yieldToEventLoop() {
2892
+ const scheduler = globalThis.scheduler;
2893
+ if (typeof scheduler?.yield === "function") return scheduler.yield();
2894
+ if (typeof MessageChannel === "function") {
2895
+ return new Promise((resolve) => {
2896
+ const channel = new MessageChannel();
2897
+ channel.port1.onmessage = () => {
2898
+ channel.port1.close();
2899
+ channel.port2.close();
2900
+ resolve();
2901
+ };
2902
+ channel.port2.postMessage(null);
2903
+ });
2904
+ }
2905
+ return new Promise((resolve) => setTimeout(resolve, 0));
2906
+ }
2907
+ function createTimeSliceYielder(budgetMs) {
2908
+ let sliceStart = nowMs();
2909
+ return async () => {
2910
+ if (nowMs() - sliceStart < budgetMs) return;
2911
+ await yieldToEventLoop();
2912
+ sliceStart = nowMs();
2913
+ };
2914
+ }
2915
+
2887
2916
  // src/lib/db/encryption-utils.ts
2888
2917
  init_logger();
2889
2918
  var ENCRYPTION_PREFIX = "enc:v3:";
@@ -4220,6 +4249,95 @@ async function readRawJsonField(raw, column, walletAddress) {
4220
4249
  return void 0;
4221
4250
  }
4222
4251
  }
4252
+ var CHUNK_VECTOR_BATCH_SIZE = 50;
4253
+ var CHUNK_SCAN_YIELD_BUDGET_MS = 12;
4254
+ var hasEmbeddingSql = `(coalesce("chunks", '') != '' or coalesce("vector", '') != '')`;
4255
+ function toChunkCandidateKey(raw) {
4256
+ return {
4257
+ id: raw.id,
4258
+ conversationId: String(raw.conversation_id),
4259
+ version: Number(raw.updated_at) || 0,
4260
+ embeddingModel: raw.embedding_model ?? null
4261
+ };
4262
+ }
4263
+ async function fetchRowsByIds(ctx, ids) {
4264
+ if (ids.length === 0) return [];
4265
+ return await ctx.messagesCollection.query(import_watermelondb2.Q.where("id", import_watermelondb2.Q.oneOf(ids))).unsafeFetchRaw();
4266
+ }
4267
+ async function projectedChunkRowSource(ctx, filter) {
4268
+ const clauses = [
4269
+ `"history"."_status" is not 'deleted'`,
4270
+ hasEmbeddingSql,
4271
+ `exists (select 1 from "conversations" where "conversations"."conversation_id" = "history"."conversation_id" and "conversations"."is_deleted" = 0 and "conversations"."_status" is not 'deleted')`
4272
+ ];
4273
+ const args = [];
4274
+ if (filter.conversationId !== void 0) {
4275
+ clauses.push(`"history"."conversation_id" = ?`);
4276
+ args.push(filter.conversationId);
4277
+ }
4278
+ if (filter.excludeConversationId !== void 0) {
4279
+ clauses.push(`"history"."conversation_id" != ?`);
4280
+ args.push(filter.excludeConversationId);
4281
+ }
4282
+ const keyRows = await ctx.messagesCollection.query(
4283
+ import_watermelondb2.Q.unsafeSqlQuery(
4284
+ `select "id", "conversation_id", "updated_at", "embedding_model" from "history" where ${clauses.join(" and ")}`,
4285
+ args
4286
+ )
4287
+ ).unsafeFetchRaw();
4288
+ return {
4289
+ keys: keyRows.map(toChunkCandidateKey),
4290
+ loadVectorRows: async (ids) => {
4291
+ if (ids.length === 0) return [];
4292
+ return await ctx.messagesCollection.query(
4293
+ import_watermelondb2.Q.unsafeSqlQuery(
4294
+ `select "id", "updated_at", "chunks", "vector" from "history" where "_status" is not 'deleted' and "id" in (${ids.map(() => "?").join(",")})`,
4295
+ ids
4296
+ )
4297
+ ).unsafeFetchRaw();
4298
+ },
4299
+ loadFullRows: (ids) => fetchRowsByIds(ctx, ids)
4300
+ };
4301
+ }
4302
+ async function fullScanChunkRowSource(ctx, filter) {
4303
+ const activeConversations = await ctx.conversationsCollection.query(import_watermelondb2.Q.where("is_deleted", false)).fetch();
4304
+ const activeConversationIds = new Set(
4305
+ activeConversations.map((c) => String(c._getRaw("conversation_id")))
4306
+ );
4307
+ const queryConditions = filter.conversationId !== void 0 ? [import_watermelondb2.Q.where("conversation_id", filter.conversationId)] : [];
4308
+ const rows = await ctx.messagesCollection.query(...queryConditions).unsafeFetchRaw();
4309
+ const rowsById = /* @__PURE__ */ new Map();
4310
+ for (const row of rows) {
4311
+ const conversationId = String(row.conversation_id);
4312
+ if (!activeConversationIds.has(conversationId)) continue;
4313
+ if (filter.excludeConversationId !== void 0 && conversationId === filter.excludeConversationId) {
4314
+ continue;
4315
+ }
4316
+ if (!row.chunks && !row.vector) continue;
4317
+ rowsById.set(row.id, row);
4318
+ }
4319
+ const pick = (ids) => Promise.resolve(
4320
+ ids.flatMap((id) => {
4321
+ const row = rowsById.get(id);
4322
+ return row ? [row] : [];
4323
+ })
4324
+ );
4325
+ return {
4326
+ keys: Array.from(rowsById.values(), toChunkCandidateKey),
4327
+ loadVectorRows: pick,
4328
+ loadFullRows: pick
4329
+ };
4330
+ }
4331
+ async function chunkRowSource(ctx, filter) {
4332
+ try {
4333
+ return await projectedChunkRowSource(ctx, filter);
4334
+ } catch (err) {
4335
+ getLogger().debug(
4336
+ "searchChunksOp: projected SQL unavailable, using full-load fallback: " + (err instanceof Error ? err.message : String(err))
4337
+ );
4338
+ return fullScanChunkRowSource(ctx, filter);
4339
+ }
4340
+ }
4223
4341
  async function searchChunksOp(ctx, queryVector, options) {
4224
4342
  const {
4225
4343
  limit = 10,
@@ -4227,83 +4345,111 @@ async function searchChunksOp(ctx, queryVector, options) {
4227
4345
  conversationId,
4228
4346
  excludeConversationId,
4229
4347
  embeddingModel,
4230
- chunkCache
4348
+ chunkCache,
4349
+ onStats
4231
4350
  } = options || {};
4232
- const activeConversations = await ctx.conversationsCollection.query(import_watermelondb2.Q.where("is_deleted", false)).fetch();
4233
- const activeConversationIds = new Set(
4234
- activeConversations.map((c) => String(c._getRaw("conversation_id")))
4235
- );
4236
- const queryConditions = conversationId ? [import_watermelondb2.Q.where("conversation_id", conversationId)] : [];
4237
- const messages = await ctx.messagesCollection.query(...queryConditions).unsafeFetchRaw();
4351
+ const source = await chunkRowSource(ctx, { conversationId, excludeConversationId });
4352
+ const yieldIfDue = createTimeSliceYielder(CHUNK_SCAN_YIELD_BUDGET_MS);
4238
4353
  const candidates = [];
4239
4354
  let staleSkipped = 0;
4240
4355
  let unreadableVectors = 0;
4241
- for (const message of messages) {
4242
- const msgConvId = String(message.conversation_id);
4243
- if (!activeConversationIds.has(msgConvId)) continue;
4244
- if (excludeConversationId !== void 0 && msgConvId === excludeConversationId) continue;
4245
- if (embeddingModel) {
4246
- const storedModel = message.embedding_model;
4247
- if (storedModel !== null && storedModel !== void 0 && storedModel !== embeddingModel) {
4248
- staleSkipped++;
4249
- continue;
4250
- }
4356
+ let cacheHits = 0;
4357
+ const missIds = [];
4358
+ const orderById = /* @__PURE__ */ new Map();
4359
+ for (const [order, key] of source.keys.entries()) {
4360
+ await yieldIfDue();
4361
+ orderById.set(key.id, order);
4362
+ if (embeddingModel && key.embeddingModel !== null && key.embeddingModel !== embeddingModel) {
4363
+ staleSkipped++;
4364
+ continue;
4251
4365
  }
4252
- const version = Number(message.updated_at) || 0;
4253
- const cached = chunkCache?.get(message.id);
4254
- if (cached && cached.version === version) {
4255
- if (cached.chunks.length > 0) {
4256
- for (let ci = 0; ci < cached.chunks.length; ci++) {
4257
- const vec = cached.chunks[ci];
4258
- if (vec.length === 0) continue;
4259
- const similarity = cosineSimilarity(queryVector, vec);
4260
- if (similarity >= minSimilarity) {
4261
- candidates.push({
4262
- message,
4263
- similarity,
4264
- chunkTextSource: { kind: "chunk-cached", chunkIndex: ci, version }
4265
- });
4266
- }
4267
- }
4268
- } else if (cached.fallback) {
4269
- const similarity = cosineSimilarity(queryVector, cached.fallback);
4270
- if (similarity >= minSimilarity) {
4271
- candidates.push({ message, similarity, chunkTextSource: { kind: "message" } });
4272
- }
4273
- }
4366
+ const cached = chunkCache?.get(key.id);
4367
+ if (!cached || cached.version !== key.version) {
4368
+ missIds.push(key.id);
4274
4369
  continue;
4275
4370
  }
4276
- const chunks3 = await readRawJsonField(message, "chunks", ctx.walletAddress);
4277
- if (chunks3 && chunks3.length > 0) {
4278
- const vectors = chunks3.map(
4279
- (chunk) => decodeChunkVector(chunk.vector, () => {
4280
- unreadableVectors++;
4281
- })
4282
- );
4283
- chunkCache?.set(message.id, { version, chunks: vectors });
4284
- for (let ci = 0; ci < chunks3.length; ci++) {
4285
- const vec = vectors[ci];
4371
+ cacheHits++;
4372
+ if (cached.chunks.length > 0) {
4373
+ for (let ci = 0; ci < cached.chunks.length; ci++) {
4374
+ const vec = cached.chunks[ci];
4286
4375
  if (vec.length === 0) continue;
4287
4376
  const similarity = cosineSimilarity(queryVector, vec);
4288
4377
  if (similarity >= minSimilarity) {
4289
4378
  candidates.push({
4290
- message,
4379
+ id: key.id,
4380
+ version: key.version,
4381
+ order,
4291
4382
  similarity,
4292
- chunkTextSource: { kind: "chunk", chunk: chunks3[ci], siblings: chunks3 }
4383
+ chunkTextSource: { kind: "chunk-cached", chunkIndex: ci, version: key.version }
4293
4384
  });
4294
4385
  }
4295
4386
  }
4296
- } else {
4297
- const messageVector = await readRawJsonField(message, "vector", ctx.walletAddress);
4298
- if (!messageVector || messageVector.length === 0) continue;
4299
- const fallback = Float32Array.from(messageVector);
4300
- chunkCache?.set(message.id, { version, chunks: [], fallback });
4301
- const similarity = cosineSimilarity(queryVector, fallback);
4387
+ } else if (cached.fallback) {
4388
+ const similarity = cosineSimilarity(queryVector, cached.fallback);
4302
4389
  if (similarity >= minSimilarity) {
4303
- candidates.push({ message, similarity, chunkTextSource: { kind: "message" } });
4390
+ candidates.push({
4391
+ id: key.id,
4392
+ version: key.version,
4393
+ order,
4394
+ similarity,
4395
+ chunkTextSource: { kind: "message" }
4396
+ });
4397
+ }
4398
+ }
4399
+ }
4400
+ let rowsFetched = 0;
4401
+ for (let offset = 0; offset < missIds.length; offset += CHUNK_VECTOR_BATCH_SIZE) {
4402
+ const rows = await source.loadVectorRows(
4403
+ missIds.slice(offset, offset + CHUNK_VECTOR_BATCH_SIZE)
4404
+ );
4405
+ rowsFetched += rows.length;
4406
+ for (const row of rows) {
4407
+ await yieldIfDue();
4408
+ const id = row.id;
4409
+ const version = Number(row.updated_at) || 0;
4410
+ const order = orderById.get(id) ?? Number.MAX_SAFE_INTEGER;
4411
+ const chunks3 = await readRawJsonField(row, "chunks", ctx.walletAddress);
4412
+ if (chunks3 && chunks3.length > 0) {
4413
+ const vectors = chunks3.map(
4414
+ (chunk) => decodeChunkVector(chunk.vector, () => {
4415
+ unreadableVectors++;
4416
+ })
4417
+ );
4418
+ chunkCache?.set(id, { version, chunks: vectors });
4419
+ for (let ci = 0; ci < chunks3.length; ci++) {
4420
+ const vec = vectors[ci];
4421
+ if (vec.length === 0) continue;
4422
+ const similarity = cosineSimilarity(queryVector, vec);
4423
+ if (similarity >= minSimilarity) {
4424
+ candidates.push({
4425
+ id,
4426
+ version,
4427
+ order,
4428
+ similarity,
4429
+ chunkTextSource: { kind: "chunk", chunk: chunks3[ci], siblings: chunks3 }
4430
+ });
4431
+ }
4432
+ }
4433
+ } else {
4434
+ const messageVector = await readRawJsonField(row, "vector", ctx.walletAddress);
4435
+ if (messageVector && messageVector.length > 0) {
4436
+ const fallback = Float32Array.from(messageVector);
4437
+ chunkCache?.set(id, { version, chunks: [], fallback });
4438
+ const similarity = cosineSimilarity(queryVector, fallback);
4439
+ if (similarity >= minSimilarity) {
4440
+ candidates.push({
4441
+ id,
4442
+ version,
4443
+ order,
4444
+ similarity,
4445
+ chunkTextSource: { kind: "message" }
4446
+ });
4447
+ }
4448
+ }
4304
4449
  }
4305
4450
  }
4306
4451
  }
4452
+ onStats?.({ rowsScanned: source.keys.length, rowsFetched, cacheHits });
4307
4453
  if (staleSkipped > 0) {
4308
4454
  getLogger().warn(
4309
4455
  `searchChunksOp: skipped ${staleSkipped} messages whose embedding model differs from the current model (${embeddingModel}) \u2014 re-embed via chunkAndEmbedAllMessages`
@@ -4314,12 +4460,17 @@ async function searchChunksOp(ctx, queryVector, options) {
4314
4460
  `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`
4315
4461
  );
4316
4462
  }
4317
- const topK = candidates.sort((a, b) => b.similarity - a.similarity).slice(0, limit);
4318
- const uniqueMessages = /* @__PURE__ */ new Map();
4319
- for (const c of topK) uniqueMessages.set(c.message.id, c.message);
4463
+ const topK = candidates.sort((a, b) => b.similarity - a.similarity || a.order - b.order).slice(0, limit);
4464
+ const topIds = Array.from(new Set(topK.map((c) => c.id)));
4465
+ const fullRows = await source.loadFullRows(topIds);
4466
+ const rowById = new Map(fullRows.map((row) => [row.id, row]));
4467
+ const liveTopK = topK.filter((c) => {
4468
+ const row = rowById.get(c.id);
4469
+ return row !== void 0 && (Number(row.updated_at) || 0) === c.version;
4470
+ });
4320
4471
  const storedById = new Map(
4321
4472
  await Promise.all(
4322
- Array.from(uniqueMessages, async ([id, m]) => {
4473
+ Array.from(rowById, async ([id, m]) => {
4323
4474
  const stored2 = await messageRawToStored(
4324
4475
  m,
4325
4476
  ctx.walletAddress,
@@ -4332,24 +4483,24 @@ async function searchChunksOp(ctx, queryVector, options) {
4332
4483
  );
4333
4484
  const chunkStateByMsgId = /* @__PURE__ */ new Map();
4334
4485
  const needsChunkText = /* @__PURE__ */ new Set();
4335
- for (const c of topK) {
4336
- if (c.chunkTextSource.kind === "chunk-cached") needsChunkText.add(c.message.id);
4486
+ for (const c of liveTopK) {
4487
+ if (c.chunkTextSource.kind === "chunk-cached") needsChunkText.add(c.id);
4337
4488
  }
4338
4489
  await Promise.all(
4339
4490
  Array.from(needsChunkText, async (id) => {
4340
- const m = uniqueMessages.get(id);
4491
+ const m = rowById.get(id);
4341
4492
  const parsed = await readRawJsonField(m, "chunks", ctx.walletAddress);
4342
4493
  const version = Number(m.updated_at) || 0;
4343
4494
  if (parsed) chunkStateByMsgId.set(id, { version, chunks: parsed });
4344
4495
  })
4345
4496
  );
4346
- return topK.map(({ message, similarity, chunkTextSource }) => {
4347
- const stored2 = { ...storedById.get(message.id) };
4497
+ return liveTopK.map(({ id, similarity, chunkTextSource }) => {
4498
+ const stored2 = { ...storedById.get(id) };
4348
4499
  let chunkText2;
4349
4500
  if (chunkTextSource.kind === "chunk") {
4350
4501
  chunkText2 = resolveChunkText(stored2.content, chunkTextSource.chunk, chunkTextSource.siblings);
4351
4502
  } else if (chunkTextSource.kind === "chunk-cached") {
4352
- const state = chunkStateByMsgId.get(message.id);
4503
+ const state = chunkStateByMsgId.get(id);
4353
4504
  const chunk = state && state.version === chunkTextSource.version ? state.chunks[chunkTextSource.chunkIndex] : void 0;
4354
4505
  chunkText2 = resolveChunkText(stored2.content, chunk, state?.chunks);
4355
4506
  } else {
@@ -5434,13 +5585,35 @@ async function generateEmbedding(text14, options) {
5434
5585
  if (total === void 0) return embedOne(text14, options, void 0);
5435
5586
  return withDeadline((signal) => embedOne(text14, options, signal), total, "embedding");
5436
5587
  }
5588
+ var inFlightByCache = /* @__PURE__ */ new WeakMap();
5437
5589
  async function embedOne(text14, options, outer) {
5438
- const { baseUrl = BASE_URL, model, cache } = options;
5439
- const timeoutMs = options.timeoutMs ?? DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS;
5440
- if (cache) {
5441
- const cached = cache.get(text14);
5442
- if (cached) return Array.from(cached);
5590
+ const { cache } = options;
5591
+ if (!cache) return Array.from(await requestEmbedding(text14, options, outer));
5592
+ const cached = cache.get(text14);
5593
+ if (cached) return Array.from(cached);
5594
+ const inFlight = inFlightByCache.get(cache) ?? /* @__PURE__ */ new Map();
5595
+ inFlightByCache.set(cache, inFlight);
5596
+ const pending = inFlight.get(text14);
5597
+ if (pending) {
5598
+ try {
5599
+ return Array.from(await pending);
5600
+ } catch (err) {
5601
+ if (outer?.aborted) throw err;
5602
+ }
5443
5603
  }
5604
+ const request = requestEmbedding(text14, options, outer);
5605
+ inFlight.set(text14, request);
5606
+ try {
5607
+ const embedding2 = await request;
5608
+ cache.set(text14, embedding2);
5609
+ return Array.from(embedding2);
5610
+ } finally {
5611
+ if (inFlight.get(text14) === request) inFlight.delete(text14);
5612
+ }
5613
+ }
5614
+ async function requestEmbedding(text14, options, outer) {
5615
+ const { baseUrl = BASE_URL, model } = options;
5616
+ const timeoutMs = options.timeoutMs ?? DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS;
5444
5617
  const headers = {
5445
5618
  ...await resolveAuthHeaders(options, outer),
5446
5619
  ...taskTypeHeader(options.taskType)
@@ -5475,11 +5648,7 @@ async function embedOne(text14, options, outer) {
5475
5648
  totalTokens: response.data.usage.total_tokens ?? 0
5476
5649
  });
5477
5650
  }
5478
- const f32Embedding = Float32Array.from(embedding2);
5479
- if (cache) {
5480
- cache.set(text14, f32Embedding);
5481
- }
5482
- return Array.from(f32Embedding);
5651
+ return Float32Array.from(embedding2);
5483
5652
  }
5484
5653
  var DEFAULT_EMBEDDING_BATCH_SIZE = 100;
5485
5654
  var DEFAULT_EMBEDDING_BATCH_CONCURRENCY = 3;
@@ -10975,8 +11144,12 @@ var MaskScopedEmbeddingCache = class extends Map {
10975
11144
  return this;
10976
11145
  }
10977
11146
  };
11147
+ var scopedViews = /* @__PURE__ */ new WeakMap();
10978
11148
  function maskScopedEmbeddingCache(cache, masked) {
10979
- return new MaskScopedEmbeddingCache(cache, masked);
11149
+ const views = scopedViews.get(cache) ?? {};
11150
+ scopedViews.set(cache, views);
11151
+ const slot = masked ? "masked" : "raw";
11152
+ return views[slot] ?? (views[slot] = new MaskScopedEmbeddingCache(cache, masked));
10980
11153
  }
10981
11154
 
10982
11155
  // src/lib/db/conversationMemory/models.ts
@@ -15337,9 +15510,9 @@ function recencyMultiplier(updatedAt, opts = {}) {
15337
15510
  const updatedMs = updatedAt.getTime();
15338
15511
  if (!Number.isFinite(updatedMs)) return noDate;
15339
15512
  const now = opts.now ?? /* @__PURE__ */ new Date();
15340
- const nowMs3 = now.getTime();
15341
- if (!Number.isFinite(nowMs3)) return noDate;
15342
- const ageDays2 = Math.max(0, (nowMs3 - updatedMs) / DAY_MS2);
15513
+ const nowMs2 = now.getTime();
15514
+ if (!Number.isFinite(nowMs2)) return noDate;
15515
+ const ageDays2 = Math.max(0, (nowMs2 - updatedMs) / DAY_MS2);
15343
15516
  const perYearDecay = opts.perYearDecay ?? DEFAULT_PER_YEAR_DECAY;
15344
15517
  const floor = opts.floor ?? DEFAULT_FLOOR;
15345
15518
  const decay = perYearDecay * (ageDays2 / 365);
@@ -15851,9 +16024,6 @@ function createVaultEmbeddingCache(maxSize = DEFAULT_VAULT_CACHE_SIZE) {
15851
16024
 
15852
16025
  // src/lib/memoryVault/searchTool.ts
15853
16026
  var DEFAULT_RERANK_TOP_N = 5;
15854
- function nowMs() {
15855
- return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
15856
- }
15857
16027
  var warnedProgrammaticDecomposeIgnored = false;
15858
16028
  function normalizeEventTimeKind(kind) {
15859
16029
  return kind === "point" || kind === "range" || kind === "ongoing" ? kind : null;
@@ -18965,9 +19135,6 @@ function scoreEventTimeOverlap(memoryStart, memoryEnd, memoryKind, window2) {
18965
19135
  var DEFAULT_LIMIT = 8;
18966
19136
  var DEFAULT_BUDGET = "low";
18967
19137
  var DEFAULT_FACT_MIN_SCORE = 0.1;
18968
- function nowMs2() {
18969
- return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
18970
- }
18971
19138
  var DEFAULT_CHUNK_MIN_SCORE = 0.5;
18972
19139
  var DEFAULT_QUERY_EMBED_TOTAL_TIMEOUT_MS = 8e3;
18973
19140
  function flagsForBudget(budget) {
@@ -19005,12 +19172,19 @@ async function recall(query, ctx, options = {}) {
19005
19172
  const subQueries = normalizedFacets.length >= 2 ? normalizedFacets : void 0;
19006
19173
  const embeddingOptions = {
19007
19174
  ...ctx.embeddingOptions,
19008
- taskType: "memory_recall_embed"
19175
+ taskType: "memory_recall_embed",
19176
+ ...options.embeddingCache && !ctx.embeddingOptions.cache && {
19177
+ cache: maskScopedEmbeddingCache(
19178
+ options.embeddingCache,
19179
+ Boolean(ctx.embeddingOptions.maskInput)
19180
+ )
19181
+ }
19009
19182
  };
19010
- const t0 = nowMs2();
19183
+ const t0 = nowMs();
19011
19184
  let prepMs = 0;
19012
19185
  let factLaneMs = 0;
19013
19186
  let chunkLaneMs = 0;
19187
+ let chunkStats;
19014
19188
  let fuseMs = 0;
19015
19189
  const factResults = [];
19016
19190
  const chunkResults = [];
@@ -19065,6 +19239,11 @@ async function recall(query, ctx, options = {}) {
19065
19239
  ...decryptLastRan !== void 0 && { decryptLast: decryptLastRan },
19066
19240
  ...vaultRowsDecrypted !== void 0 && { vaultRowsDecrypted },
19067
19241
  ...vaultRowsEmbedded !== void 0 && { vaultRowsEmbedded },
19242
+ ...chunkStats && {
19243
+ chunkRowsScanned: chunkStats.rowsScanned,
19244
+ chunkRowsFetched: chunkStats.rowsFetched,
19245
+ chunkCacheHits: chunkStats.cacheHits
19246
+ },
19068
19247
  factCount: factResults.length,
19069
19248
  chunkCount: chunkResults.length,
19070
19249
  admittedCount: admitted.length,
@@ -19076,7 +19255,7 @@ async function recall(query, ctx, options = {}) {
19076
19255
  temporalLaneCount,
19077
19256
  emptyReason: emptyReasonFor(admitted.length, laneRan),
19078
19257
  timings: {
19079
- total: nowMs2() - t0,
19258
+ total: nowMs() - t0,
19080
19259
  prep: prepMs,
19081
19260
  factLane: factLaneMs,
19082
19261
  rerank: rerankMs,
@@ -19098,14 +19277,14 @@ async function recall(query, ctx, options = {}) {
19098
19277
  const needsChunkEmbedding = types.includes("chunk") && ctx.storageCtx;
19099
19278
  const wantsTemporal = types.includes("fact") && (ctx.factSource || ctx.vaultCtx);
19100
19279
  const queryEmbedTotalTimeoutMs = options.queryEmbedTotalTimeoutMs ?? DEFAULT_QUERY_EMBED_TOTAL_TIMEOUT_MS;
19101
- const prepStart = nowMs2();
19280
+ const prepStart = nowMs();
19102
19281
  let sharedEmbedMs = 0;
19103
19282
  const [queryEmbedding, entityRanking, temporalRanking] = await Promise.all([
19104
19283
  needsChunkEmbedding ? generateEmbedding(query, {
19105
19284
  ...embeddingOptions,
19106
19285
  totalTimeoutMs: queryEmbedTotalTimeoutMs
19107
19286
  }).finally(() => {
19108
- sharedEmbedMs = nowMs2() - prepStart;
19287
+ sharedEmbedMs = nowMs() - prepStart;
19109
19288
  }).then((vec) => {
19110
19289
  if (vec.length === 0) {
19111
19290
  getLogger().warn(
@@ -19138,11 +19317,11 @@ async function recall(query, ctx, options = {}) {
19138
19317
  () => ctx.factSource ? ctx.factSource.temporalRanking(query, options.now) : buildTemporalLaneRanking(query, ctx.vaultCtx, options.now)
19139
19318
  ) : Promise.resolve([])
19140
19319
  ]);
19141
- prepMs = nowMs2() - prepStart;
19320
+ prepMs = nowMs() - prepStart;
19142
19321
  graphLaneCount = entityRanking.length;
19143
19322
  temporalLaneCount = temporalRanking.length;
19144
19323
  if (types.includes("fact") && (ctx.factSource || ctx.vaultCtx && ctx.vaultCache)) {
19145
- const factStart = nowMs2();
19324
+ const factStart = nowMs();
19146
19325
  const vaultMinScore = options.minScore ?? DEFAULT_FACT_MIN_SCORE;
19147
19326
  factFloor = vaultMinScore;
19148
19327
  const {
@@ -19218,11 +19397,11 @@ async function recall(query, ctx, options = {}) {
19218
19397
  factLaneRankedOnCosine = factRankedOnCosine;
19219
19398
  decryptLastRan = factDecryptLast;
19220
19399
  vaultRowsDecrypted = factRowsDecrypted;
19221
- factLaneMs = nowMs2() - factStart;
19400
+ factLaneMs = nowMs() - factStart;
19222
19401
  }
19223
19402
  if (chunkEmbedFailed && !factLaneRankedOnCosine) embeddingsUnavailable = true;
19224
19403
  if (types.includes("chunk") && ctx.storageCtx && queryEmbedding) {
19225
- const chunkStart = nowMs2();
19404
+ const chunkStart = nowMs();
19226
19405
  const chunkMinScore = options.minScore ?? DEFAULT_CHUNK_MIN_SCORE;
19227
19406
  chunkFloor = chunkMinScore;
19228
19407
  const results = await searchChunksOp(ctx.storageCtx, queryEmbedding, {
@@ -19233,12 +19412,15 @@ async function recall(query, ctx, options = {}) {
19233
19412
  ...options.excludeConversationId && {
19234
19413
  excludeConversationId: options.excludeConversationId
19235
19414
  },
19236
- ...ctx.chunkCache && { chunkCache: ctx.chunkCache }
19415
+ ...ctx.chunkCache && { chunkCache: ctx.chunkCache },
19416
+ onStats: (stats) => {
19417
+ chunkStats = stats;
19418
+ }
19237
19419
  });
19238
19420
  chunkResults.push(...dedupeBy(results, (r) => r.chunkText.trim()));
19239
- chunkLaneMs = nowMs2() - chunkStart;
19421
+ chunkLaneMs = nowMs() - chunkStart;
19240
19422
  }
19241
- const fuseStart = nowMs2();
19423
+ const fuseStart = nowMs();
19242
19424
  if (types.length === 1 || factResults.length === 0 || chunkResults.length === 0) {
19243
19425
  const memories2 = [
19244
19426
  ...factResults.map((r) => toFactMemory(r, options.now)),
@@ -19246,7 +19428,7 @@ async function recall(query, ctx, options = {}) {
19246
19428
  ];
19247
19429
  memories2.sort((a, b) => b.score - a.score);
19248
19430
  const candidateCount = factResults.length + chunkResults.length;
19249
- fuseMs = nowMs2() - fuseStart;
19431
+ fuseMs = nowMs() - fuseStart;
19250
19432
  const admitted = memories2.slice(0, limit);
19251
19433
  hitLimit = memories2.length > limit;
19252
19434
  emitDiagnostics(candidateCount, admitted);
@@ -19310,7 +19492,7 @@ async function recall(query, ctx, options = {}) {
19310
19492
  memories = selection.out;
19311
19493
  }
19312
19494
  hitLimit = selection.cut;
19313
- fuseMs = nowMs2() - fuseStart;
19495
+ fuseMs = nowMs() - fuseStart;
19314
19496
  emitDiagnostics(byId.size, memories);
19315
19497
  return {
19316
19498
  memories,