@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.
@@ -16019,6 +16019,35 @@ function decodeChunkVector(vector, onMalformed) {
16019
16019
  }
16020
16020
  }
16021
16021
 
16022
+ // src/lib/scheduling.ts
16023
+ function nowMs() {
16024
+ return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
16025
+ }
16026
+ function yieldToEventLoop() {
16027
+ const scheduler = globalThis.scheduler;
16028
+ if (typeof scheduler?.yield === "function") return scheduler.yield();
16029
+ if (typeof MessageChannel === "function") {
16030
+ return new Promise((resolve) => {
16031
+ const channel = new MessageChannel();
16032
+ channel.port1.onmessage = () => {
16033
+ channel.port1.close();
16034
+ channel.port2.close();
16035
+ resolve();
16036
+ };
16037
+ channel.port2.postMessage(null);
16038
+ });
16039
+ }
16040
+ return new Promise((resolve) => setTimeout(resolve, 0));
16041
+ }
16042
+ function createTimeSliceYielder(budgetMs) {
16043
+ let sliceStart = nowMs();
16044
+ return async () => {
16045
+ if (nowMs() - sliceStart < budgetMs) return;
16046
+ await yieldToEventLoop();
16047
+ sliceStart = nowMs();
16048
+ };
16049
+ }
16050
+
16022
16051
  // src/lib/db/encryption-utils.ts
16023
16052
  var ENCRYPTION_PREFIX = "enc:v3:";
16024
16053
  function isEncrypted(value) {
@@ -17163,6 +17192,95 @@ async function searchMessagesOp(ctx, queryVector, options) {
17163
17192
  })
17164
17193
  );
17165
17194
  }
17195
+ var CHUNK_VECTOR_BATCH_SIZE = 50;
17196
+ var CHUNK_SCAN_YIELD_BUDGET_MS = 12;
17197
+ var hasEmbeddingSql = `(coalesce("chunks", '') != '' or coalesce("vector", '') != '')`;
17198
+ function toChunkCandidateKey(raw) {
17199
+ return {
17200
+ id: raw.id,
17201
+ conversationId: String(raw.conversation_id),
17202
+ version: Number(raw.updated_at) || 0,
17203
+ embeddingModel: raw.embedding_model ?? null
17204
+ };
17205
+ }
17206
+ async function fetchRowsByIds(ctx, ids) {
17207
+ if (ids.length === 0) return [];
17208
+ return await ctx.messagesCollection.query(import_watermelondb2.Q.where("id", import_watermelondb2.Q.oneOf(ids))).unsafeFetchRaw();
17209
+ }
17210
+ async function projectedChunkRowSource(ctx, filter) {
17211
+ const clauses = [
17212
+ `"history"."_status" is not 'deleted'`,
17213
+ hasEmbeddingSql,
17214
+ `exists (select 1 from "conversations" where "conversations"."conversation_id" = "history"."conversation_id" and "conversations"."is_deleted" = 0 and "conversations"."_status" is not 'deleted')`
17215
+ ];
17216
+ const args = [];
17217
+ if (filter.conversationId !== void 0) {
17218
+ clauses.push(`"history"."conversation_id" = ?`);
17219
+ args.push(filter.conversationId);
17220
+ }
17221
+ if (filter.excludeConversationId !== void 0) {
17222
+ clauses.push(`"history"."conversation_id" != ?`);
17223
+ args.push(filter.excludeConversationId);
17224
+ }
17225
+ const keyRows = await ctx.messagesCollection.query(
17226
+ import_watermelondb2.Q.unsafeSqlQuery(
17227
+ `select "id", "conversation_id", "updated_at", "embedding_model" from "history" where ${clauses.join(" and ")}`,
17228
+ args
17229
+ )
17230
+ ).unsafeFetchRaw();
17231
+ return {
17232
+ keys: keyRows.map(toChunkCandidateKey),
17233
+ loadVectorRows: async (ids) => {
17234
+ if (ids.length === 0) return [];
17235
+ return await ctx.messagesCollection.query(
17236
+ import_watermelondb2.Q.unsafeSqlQuery(
17237
+ `select "id", "updated_at", "chunks", "vector" from "history" where "_status" is not 'deleted' and "id" in (${ids.map(() => "?").join(",")})`,
17238
+ ids
17239
+ )
17240
+ ).unsafeFetchRaw();
17241
+ },
17242
+ loadFullRows: (ids) => fetchRowsByIds(ctx, ids)
17243
+ };
17244
+ }
17245
+ async function fullScanChunkRowSource(ctx, filter) {
17246
+ const activeConversations = await ctx.conversationsCollection.query(import_watermelondb2.Q.where("is_deleted", false)).fetch();
17247
+ const activeConversationIds = new Set(
17248
+ activeConversations.map((c) => String(c._getRaw("conversation_id")))
17249
+ );
17250
+ const queryConditions = filter.conversationId !== void 0 ? [import_watermelondb2.Q.where("conversation_id", filter.conversationId)] : [];
17251
+ const rows = await ctx.messagesCollection.query(...queryConditions).unsafeFetchRaw();
17252
+ const rowsById = /* @__PURE__ */ new Map();
17253
+ for (const row of rows) {
17254
+ const conversationId = String(row.conversation_id);
17255
+ if (!activeConversationIds.has(conversationId)) continue;
17256
+ if (filter.excludeConversationId !== void 0 && conversationId === filter.excludeConversationId) {
17257
+ continue;
17258
+ }
17259
+ if (!row.chunks && !row.vector) continue;
17260
+ rowsById.set(row.id, row);
17261
+ }
17262
+ const pick = (ids) => Promise.resolve(
17263
+ ids.flatMap((id) => {
17264
+ const row = rowsById.get(id);
17265
+ return row ? [row] : [];
17266
+ })
17267
+ );
17268
+ return {
17269
+ keys: Array.from(rowsById.values(), toChunkCandidateKey),
17270
+ loadVectorRows: pick,
17271
+ loadFullRows: pick
17272
+ };
17273
+ }
17274
+ async function chunkRowSource(ctx, filter) {
17275
+ try {
17276
+ return await projectedChunkRowSource(ctx, filter);
17277
+ } catch (err) {
17278
+ getLogger().debug(
17279
+ "searchChunksOp: projected SQL unavailable, using full-load fallback: " + (err instanceof Error ? err.message : String(err))
17280
+ );
17281
+ return fullScanChunkRowSource(ctx, filter);
17282
+ }
17283
+ }
17166
17284
  async function searchChunksOp(ctx, queryVector, options) {
17167
17285
  const {
17168
17286
  limit = 10,
@@ -17170,83 +17288,111 @@ async function searchChunksOp(ctx, queryVector, options) {
17170
17288
  conversationId,
17171
17289
  excludeConversationId,
17172
17290
  embeddingModel,
17173
- chunkCache
17291
+ chunkCache,
17292
+ onStats
17174
17293
  } = options || {};
17175
- const activeConversations = await ctx.conversationsCollection.query(import_watermelondb2.Q.where("is_deleted", false)).fetch();
17176
- const activeConversationIds = new Set(
17177
- activeConversations.map((c) => String(c._getRaw("conversation_id")))
17178
- );
17179
- const queryConditions = conversationId ? [import_watermelondb2.Q.where("conversation_id", conversationId)] : [];
17180
- const messages = await ctx.messagesCollection.query(...queryConditions).unsafeFetchRaw();
17294
+ const source = await chunkRowSource(ctx, { conversationId, excludeConversationId });
17295
+ const yieldIfDue = createTimeSliceYielder(CHUNK_SCAN_YIELD_BUDGET_MS);
17181
17296
  const candidates = [];
17182
17297
  let staleSkipped = 0;
17183
17298
  let unreadableVectors = 0;
17184
- for (const message of messages) {
17185
- const msgConvId = String(message.conversation_id);
17186
- if (!activeConversationIds.has(msgConvId)) continue;
17187
- if (excludeConversationId !== void 0 && msgConvId === excludeConversationId) continue;
17188
- if (embeddingModel) {
17189
- const storedModel = message.embedding_model;
17190
- if (storedModel !== null && storedModel !== void 0 && storedModel !== embeddingModel) {
17191
- staleSkipped++;
17192
- continue;
17193
- }
17299
+ let cacheHits = 0;
17300
+ const missIds = [];
17301
+ const orderById = /* @__PURE__ */ new Map();
17302
+ for (const [order, key] of source.keys.entries()) {
17303
+ await yieldIfDue();
17304
+ orderById.set(key.id, order);
17305
+ if (embeddingModel && key.embeddingModel !== null && key.embeddingModel !== embeddingModel) {
17306
+ staleSkipped++;
17307
+ continue;
17194
17308
  }
17195
- const version = Number(message.updated_at) || 0;
17196
- const cached = chunkCache?.get(message.id);
17197
- if (cached && cached.version === version) {
17198
- if (cached.chunks.length > 0) {
17199
- for (let ci = 0; ci < cached.chunks.length; ci++) {
17200
- const vec = cached.chunks[ci];
17201
- if (vec.length === 0) continue;
17202
- const similarity = cosineSimilarity(queryVector, vec);
17203
- if (similarity >= minSimilarity) {
17204
- candidates.push({
17205
- message,
17206
- similarity,
17207
- chunkTextSource: { kind: "chunk-cached", chunkIndex: ci, version }
17208
- });
17209
- }
17210
- }
17211
- } else if (cached.fallback) {
17212
- const similarity = cosineSimilarity(queryVector, cached.fallback);
17213
- if (similarity >= minSimilarity) {
17214
- candidates.push({ message, similarity, chunkTextSource: { kind: "message" } });
17215
- }
17216
- }
17309
+ const cached = chunkCache?.get(key.id);
17310
+ if (!cached || cached.version !== key.version) {
17311
+ missIds.push(key.id);
17217
17312
  continue;
17218
17313
  }
17219
- const chunks = await readRawJsonField(message, "chunks", ctx.walletAddress);
17220
- if (chunks && chunks.length > 0) {
17221
- const vectors = chunks.map(
17222
- (chunk) => decodeChunkVector(chunk.vector, () => {
17223
- unreadableVectors++;
17224
- })
17225
- );
17226
- chunkCache?.set(message.id, { version, chunks: vectors });
17227
- for (let ci = 0; ci < chunks.length; ci++) {
17228
- const vec = vectors[ci];
17314
+ cacheHits++;
17315
+ if (cached.chunks.length > 0) {
17316
+ for (let ci = 0; ci < cached.chunks.length; ci++) {
17317
+ const vec = cached.chunks[ci];
17229
17318
  if (vec.length === 0) continue;
17230
17319
  const similarity = cosineSimilarity(queryVector, vec);
17231
17320
  if (similarity >= minSimilarity) {
17232
17321
  candidates.push({
17233
- message,
17322
+ id: key.id,
17323
+ version: key.version,
17324
+ order,
17234
17325
  similarity,
17235
- chunkTextSource: { kind: "chunk", chunk: chunks[ci], siblings: chunks }
17326
+ chunkTextSource: { kind: "chunk-cached", chunkIndex: ci, version: key.version }
17236
17327
  });
17237
17328
  }
17238
17329
  }
17239
- } else {
17240
- const messageVector = await readRawJsonField(message, "vector", ctx.walletAddress);
17241
- if (!messageVector || messageVector.length === 0) continue;
17242
- const fallback = Float32Array.from(messageVector);
17243
- chunkCache?.set(message.id, { version, chunks: [], fallback });
17244
- const similarity = cosineSimilarity(queryVector, fallback);
17330
+ } else if (cached.fallback) {
17331
+ const similarity = cosineSimilarity(queryVector, cached.fallback);
17245
17332
  if (similarity >= minSimilarity) {
17246
- candidates.push({ message, similarity, chunkTextSource: { kind: "message" } });
17333
+ candidates.push({
17334
+ id: key.id,
17335
+ version: key.version,
17336
+ order,
17337
+ similarity,
17338
+ chunkTextSource: { kind: "message" }
17339
+ });
17247
17340
  }
17248
17341
  }
17249
17342
  }
17343
+ let rowsFetched = 0;
17344
+ for (let offset = 0; offset < missIds.length; offset += CHUNK_VECTOR_BATCH_SIZE) {
17345
+ const rows = await source.loadVectorRows(
17346
+ missIds.slice(offset, offset + CHUNK_VECTOR_BATCH_SIZE)
17347
+ );
17348
+ rowsFetched += rows.length;
17349
+ for (const row of rows) {
17350
+ await yieldIfDue();
17351
+ const id = row.id;
17352
+ const version = Number(row.updated_at) || 0;
17353
+ const order = orderById.get(id) ?? Number.MAX_SAFE_INTEGER;
17354
+ const chunks = await readRawJsonField(row, "chunks", ctx.walletAddress);
17355
+ if (chunks && chunks.length > 0) {
17356
+ const vectors = chunks.map(
17357
+ (chunk) => decodeChunkVector(chunk.vector, () => {
17358
+ unreadableVectors++;
17359
+ })
17360
+ );
17361
+ chunkCache?.set(id, { version, chunks: vectors });
17362
+ for (let ci = 0; ci < chunks.length; ci++) {
17363
+ const vec = vectors[ci];
17364
+ if (vec.length === 0) continue;
17365
+ const similarity = cosineSimilarity(queryVector, vec);
17366
+ if (similarity >= minSimilarity) {
17367
+ candidates.push({
17368
+ id,
17369
+ version,
17370
+ order,
17371
+ similarity,
17372
+ chunkTextSource: { kind: "chunk", chunk: chunks[ci], siblings: chunks }
17373
+ });
17374
+ }
17375
+ }
17376
+ } else {
17377
+ const messageVector = await readRawJsonField(row, "vector", ctx.walletAddress);
17378
+ if (messageVector && messageVector.length > 0) {
17379
+ const fallback = Float32Array.from(messageVector);
17380
+ chunkCache?.set(id, { version, chunks: [], fallback });
17381
+ const similarity = cosineSimilarity(queryVector, fallback);
17382
+ if (similarity >= minSimilarity) {
17383
+ candidates.push({
17384
+ id,
17385
+ version,
17386
+ order,
17387
+ similarity,
17388
+ chunkTextSource: { kind: "message" }
17389
+ });
17390
+ }
17391
+ }
17392
+ }
17393
+ }
17394
+ }
17395
+ onStats?.({ rowsScanned: source.keys.length, rowsFetched, cacheHits });
17250
17396
  if (staleSkipped > 0) {
17251
17397
  getLogger().warn(
17252
17398
  `searchChunksOp: skipped ${staleSkipped} messages whose embedding model differs from the current model (${embeddingModel}) \u2014 re-embed via chunkAndEmbedAllMessages`
@@ -17257,12 +17403,17 @@ async function searchChunksOp(ctx, queryVector, options) {
17257
17403
  `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`
17258
17404
  );
17259
17405
  }
17260
- const topK = candidates.sort((a, b) => b.similarity - a.similarity).slice(0, limit);
17261
- const uniqueMessages = /* @__PURE__ */ new Map();
17262
- for (const c of topK) uniqueMessages.set(c.message.id, c.message);
17406
+ const topK = candidates.sort((a, b) => b.similarity - a.similarity || a.order - b.order).slice(0, limit);
17407
+ const topIds = Array.from(new Set(topK.map((c) => c.id)));
17408
+ const fullRows = await source.loadFullRows(topIds);
17409
+ const rowById = new Map(fullRows.map((row) => [row.id, row]));
17410
+ const liveTopK = topK.filter((c) => {
17411
+ const row = rowById.get(c.id);
17412
+ return row !== void 0 && (Number(row.updated_at) || 0) === c.version;
17413
+ });
17263
17414
  const storedById = new Map(
17264
17415
  await Promise.all(
17265
- Array.from(uniqueMessages, async ([id, m]) => {
17416
+ Array.from(rowById, async ([id, m]) => {
17266
17417
  const stored = await messageRawToStored(
17267
17418
  m,
17268
17419
  ctx.walletAddress,
@@ -17275,24 +17426,24 @@ async function searchChunksOp(ctx, queryVector, options) {
17275
17426
  );
17276
17427
  const chunkStateByMsgId = /* @__PURE__ */ new Map();
17277
17428
  const needsChunkText = /* @__PURE__ */ new Set();
17278
- for (const c of topK) {
17279
- if (c.chunkTextSource.kind === "chunk-cached") needsChunkText.add(c.message.id);
17429
+ for (const c of liveTopK) {
17430
+ if (c.chunkTextSource.kind === "chunk-cached") needsChunkText.add(c.id);
17280
17431
  }
17281
17432
  await Promise.all(
17282
17433
  Array.from(needsChunkText, async (id) => {
17283
- const m = uniqueMessages.get(id);
17434
+ const m = rowById.get(id);
17284
17435
  const parsed = await readRawJsonField(m, "chunks", ctx.walletAddress);
17285
17436
  const version = Number(m.updated_at) || 0;
17286
17437
  if (parsed) chunkStateByMsgId.set(id, { version, chunks: parsed });
17287
17438
  })
17288
17439
  );
17289
- return topK.map(({ message, similarity, chunkTextSource }) => {
17290
- const stored = { ...storedById.get(message.id) };
17440
+ return liveTopK.map(({ id, similarity, chunkTextSource }) => {
17441
+ const stored = { ...storedById.get(id) };
17291
17442
  let chunkText2;
17292
17443
  if (chunkTextSource.kind === "chunk") {
17293
17444
  chunkText2 = resolveChunkText(stored.content, chunkTextSource.chunk, chunkTextSource.siblings);
17294
17445
  } else if (chunkTextSource.kind === "chunk-cached") {
17295
- const state = chunkStateByMsgId.get(message.id);
17446
+ const state = chunkStateByMsgId.get(id);
17296
17447
  const chunk = state && state.version === chunkTextSource.version ? state.chunks[chunkTextSource.chunkIndex] : void 0;
17297
17448
  chunkText2 = resolveChunkText(stored.content, chunk, state?.chunks);
17298
17449
  } else {
@@ -18388,13 +18539,35 @@ async function generateEmbedding(text12, options) {
18388
18539
  if (total === void 0) return embedOne(text12, options, void 0);
18389
18540
  return withDeadline((signal) => embedOne(text12, options, signal), total, "embedding");
18390
18541
  }
18542
+ var inFlightByCache = /* @__PURE__ */ new WeakMap();
18391
18543
  async function embedOne(text12, options, outer) {
18392
- const { baseUrl = BASE_URL, model, cache } = options;
18393
- const timeoutMs = options.timeoutMs ?? DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS;
18394
- if (cache) {
18395
- const cached = cache.get(text12);
18396
- if (cached) return Array.from(cached);
18544
+ const { cache } = options;
18545
+ if (!cache) return Array.from(await requestEmbedding(text12, options, outer));
18546
+ const cached = cache.get(text12);
18547
+ if (cached) return Array.from(cached);
18548
+ const inFlight = inFlightByCache.get(cache) ?? /* @__PURE__ */ new Map();
18549
+ inFlightByCache.set(cache, inFlight);
18550
+ const pending = inFlight.get(text12);
18551
+ if (pending) {
18552
+ try {
18553
+ return Array.from(await pending);
18554
+ } catch (err) {
18555
+ if (outer?.aborted) throw err;
18556
+ }
18557
+ }
18558
+ const request = requestEmbedding(text12, options, outer);
18559
+ inFlight.set(text12, request);
18560
+ try {
18561
+ const embedding = await request;
18562
+ cache.set(text12, embedding);
18563
+ return Array.from(embedding);
18564
+ } finally {
18565
+ if (inFlight.get(text12) === request) inFlight.delete(text12);
18397
18566
  }
18567
+ }
18568
+ async function requestEmbedding(text12, options, outer) {
18569
+ const { baseUrl = BASE_URL, model } = options;
18570
+ const timeoutMs = options.timeoutMs ?? DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS;
18398
18571
  const headers = {
18399
18572
  ...await resolveAuthHeaders(options, outer),
18400
18573
  ...taskTypeHeader(options.taskType)
@@ -18429,11 +18602,7 @@ async function embedOne(text12, options, outer) {
18429
18602
  totalTokens: response.data.usage.total_tokens ?? 0
18430
18603
  });
18431
18604
  }
18432
- const f32Embedding = Float32Array.from(embedding);
18433
- if (cache) {
18434
- cache.set(text12, f32Embedding);
18435
- }
18436
- return Array.from(f32Embedding);
18605
+ return Float32Array.from(embedding);
18437
18606
  }
18438
18607
  var DEFAULT_EMBEDDING_BATCH_SIZE = 100;
18439
18608
  var DEFAULT_EMBEDDING_BATCH_CONCURRENCY = 3;
@@ -28856,9 +29025,9 @@ function recencyMultiplier(updatedAt, opts = {}) {
28856
29025
  const updatedMs = updatedAt.getTime();
28857
29026
  if (!Number.isFinite(updatedMs)) return noDate;
28858
29027
  const now = opts.now ?? /* @__PURE__ */ new Date();
28859
- const nowMs3 = now.getTime();
28860
- if (!Number.isFinite(nowMs3)) return noDate;
28861
- const ageDays2 = Math.max(0, (nowMs3 - updatedMs) / DAY_MS2);
29028
+ const nowMs2 = now.getTime();
29029
+ if (!Number.isFinite(nowMs2)) return noDate;
29030
+ const ageDays2 = Math.max(0, (nowMs2 - updatedMs) / DAY_MS2);
28862
29031
  const perYearDecay = opts.perYearDecay ?? DEFAULT_PER_YEAR_DECAY;
28863
29032
  const floor = opts.floor ?? DEFAULT_FLOOR;
28864
29033
  const decay = perYearDecay * (ageDays2 / 365);
@@ -29367,9 +29536,6 @@ function createVaultEmbeddingCache(maxSize = DEFAULT_VAULT_CACHE_SIZE) {
29367
29536
 
29368
29537
  // src/lib/memoryVault/searchTool.ts
29369
29538
  var DEFAULT_RERANK_TOP_N = 5;
29370
- function nowMs() {
29371
- return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
29372
- }
29373
29539
  var warnedProgrammaticDecomposeIgnored = false;
29374
29540
  function normalizeEventTimeKind(kind) {
29375
29541
  return kind === "point" || kind === "range" || kind === "ongoing" ? kind : null;
@@ -32260,6 +32426,32 @@ function rankProfileCandidates(inputs, now = Date.now(), options = {}) {
32260
32426
  return ranked;
32261
32427
  }
32262
32428
 
32429
+ // src/lib/db/chat/embeddingCache.ts
32430
+ var MaskScopedEmbeddingCache = class extends Map {
32431
+ constructor(inner, masked) {
32432
+ super();
32433
+ this.inner = inner;
32434
+ this.masked = masked;
32435
+ }
32436
+ key(text12) {
32437
+ return `${this.masked ? "m" : "r"}:${text12}`;
32438
+ }
32439
+ get(text12) {
32440
+ return this.inner.get(this.key(text12));
32441
+ }
32442
+ set(text12, value) {
32443
+ this.inner.set(this.key(text12), value);
32444
+ return this;
32445
+ }
32446
+ };
32447
+ var scopedViews = /* @__PURE__ */ new WeakMap();
32448
+ function maskScopedEmbeddingCache(cache, masked) {
32449
+ const views = scopedViews.get(cache) ?? {};
32450
+ scopedViews.set(cache, views);
32451
+ const slot = masked ? "masked" : "raw";
32452
+ return views[slot] ?? (views[slot] = new MaskScopedEmbeddingCache(cache, masked));
32453
+ }
32454
+
32263
32455
  // src/lib/memory/queryTemporal.ts
32264
32456
  var RELATIVE_DAY_ENTRIES = [
32265
32457
  [/\btoday\b/, "today", 0],
@@ -32467,9 +32659,6 @@ function scoreEventTimeOverlap(memoryStart, memoryEnd, memoryKind, window2) {
32467
32659
  var DEFAULT_LIMIT = 8;
32468
32660
  var DEFAULT_BUDGET = "low";
32469
32661
  var DEFAULT_FACT_MIN_SCORE = 0.1;
32470
- function nowMs2() {
32471
- return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
32472
- }
32473
32662
  var DEFAULT_CHUNK_MIN_SCORE = 0.5;
32474
32663
  var DEFAULT_QUERY_EMBED_TOTAL_TIMEOUT_MS = 8e3;
32475
32664
  function flagsForBudget(budget) {
@@ -32507,12 +32696,19 @@ async function recall(query, ctx, options = {}) {
32507
32696
  const subQueries = normalizedFacets.length >= 2 ? normalizedFacets : void 0;
32508
32697
  const embeddingOptions = {
32509
32698
  ...ctx.embeddingOptions,
32510
- taskType: "memory_recall_embed"
32699
+ taskType: "memory_recall_embed",
32700
+ ...options.embeddingCache && !ctx.embeddingOptions.cache && {
32701
+ cache: maskScopedEmbeddingCache(
32702
+ options.embeddingCache,
32703
+ Boolean(ctx.embeddingOptions.maskInput)
32704
+ )
32705
+ }
32511
32706
  };
32512
- const t0 = nowMs2();
32707
+ const t0 = nowMs();
32513
32708
  let prepMs = 0;
32514
32709
  let factLaneMs = 0;
32515
32710
  let chunkLaneMs = 0;
32711
+ let chunkStats;
32516
32712
  let fuseMs = 0;
32517
32713
  const factResults = [];
32518
32714
  const chunkResults = [];
@@ -32567,6 +32763,11 @@ async function recall(query, ctx, options = {}) {
32567
32763
  ...decryptLastRan !== void 0 && { decryptLast: decryptLastRan },
32568
32764
  ...vaultRowsDecrypted !== void 0 && { vaultRowsDecrypted },
32569
32765
  ...vaultRowsEmbedded !== void 0 && { vaultRowsEmbedded },
32766
+ ...chunkStats && {
32767
+ chunkRowsScanned: chunkStats.rowsScanned,
32768
+ chunkRowsFetched: chunkStats.rowsFetched,
32769
+ chunkCacheHits: chunkStats.cacheHits
32770
+ },
32570
32771
  factCount: factResults.length,
32571
32772
  chunkCount: chunkResults.length,
32572
32773
  admittedCount: admitted.length,
@@ -32578,7 +32779,7 @@ async function recall(query, ctx, options = {}) {
32578
32779
  temporalLaneCount,
32579
32780
  emptyReason: emptyReasonFor(admitted.length, laneRan),
32580
32781
  timings: {
32581
- total: nowMs2() - t0,
32782
+ total: nowMs() - t0,
32582
32783
  prep: prepMs,
32583
32784
  factLane: factLaneMs,
32584
32785
  rerank: rerankMs,
@@ -32600,14 +32801,14 @@ async function recall(query, ctx, options = {}) {
32600
32801
  const needsChunkEmbedding = types.includes("chunk") && ctx.storageCtx;
32601
32802
  const wantsTemporal = types.includes("fact") && (ctx.factSource || ctx.vaultCtx);
32602
32803
  const queryEmbedTotalTimeoutMs = options.queryEmbedTotalTimeoutMs ?? DEFAULT_QUERY_EMBED_TOTAL_TIMEOUT_MS;
32603
- const prepStart = nowMs2();
32804
+ const prepStart = nowMs();
32604
32805
  let sharedEmbedMs = 0;
32605
32806
  const [queryEmbedding, entityRanking, temporalRanking] = await Promise.all([
32606
32807
  needsChunkEmbedding ? generateEmbedding(query, {
32607
32808
  ...embeddingOptions,
32608
32809
  totalTimeoutMs: queryEmbedTotalTimeoutMs
32609
32810
  }).finally(() => {
32610
- sharedEmbedMs = nowMs2() - prepStart;
32811
+ sharedEmbedMs = nowMs() - prepStart;
32611
32812
  }).then((vec) => {
32612
32813
  if (vec.length === 0) {
32613
32814
  getLogger().warn(
@@ -32640,11 +32841,11 @@ async function recall(query, ctx, options = {}) {
32640
32841
  () => ctx.factSource ? ctx.factSource.temporalRanking(query, options.now) : buildTemporalLaneRanking(query, ctx.vaultCtx, options.now)
32641
32842
  ) : Promise.resolve([])
32642
32843
  ]);
32643
- prepMs = nowMs2() - prepStart;
32844
+ prepMs = nowMs() - prepStart;
32644
32845
  graphLaneCount = entityRanking.length;
32645
32846
  temporalLaneCount = temporalRanking.length;
32646
32847
  if (types.includes("fact") && (ctx.factSource || ctx.vaultCtx && ctx.vaultCache)) {
32647
- const factStart = nowMs2();
32848
+ const factStart = nowMs();
32648
32849
  const vaultMinScore = options.minScore ?? DEFAULT_FACT_MIN_SCORE;
32649
32850
  factFloor = vaultMinScore;
32650
32851
  const {
@@ -32720,11 +32921,11 @@ async function recall(query, ctx, options = {}) {
32720
32921
  factLaneRankedOnCosine = factRankedOnCosine;
32721
32922
  decryptLastRan = factDecryptLast;
32722
32923
  vaultRowsDecrypted = factRowsDecrypted;
32723
- factLaneMs = nowMs2() - factStart;
32924
+ factLaneMs = nowMs() - factStart;
32724
32925
  }
32725
32926
  if (chunkEmbedFailed && !factLaneRankedOnCosine) embeddingsUnavailable = true;
32726
32927
  if (types.includes("chunk") && ctx.storageCtx && queryEmbedding) {
32727
- const chunkStart = nowMs2();
32928
+ const chunkStart = nowMs();
32728
32929
  const chunkMinScore = options.minScore ?? DEFAULT_CHUNK_MIN_SCORE;
32729
32930
  chunkFloor = chunkMinScore;
32730
32931
  const results = await searchChunksOp(ctx.storageCtx, queryEmbedding, {
@@ -32735,12 +32936,15 @@ async function recall(query, ctx, options = {}) {
32735
32936
  ...options.excludeConversationId && {
32736
32937
  excludeConversationId: options.excludeConversationId
32737
32938
  },
32738
- ...ctx.chunkCache && { chunkCache: ctx.chunkCache }
32939
+ ...ctx.chunkCache && { chunkCache: ctx.chunkCache },
32940
+ onStats: (stats) => {
32941
+ chunkStats = stats;
32942
+ }
32739
32943
  });
32740
32944
  chunkResults.push(...dedupeBy(results, (r) => r.chunkText.trim()));
32741
- chunkLaneMs = nowMs2() - chunkStart;
32945
+ chunkLaneMs = nowMs() - chunkStart;
32742
32946
  }
32743
- const fuseStart = nowMs2();
32947
+ const fuseStart = nowMs();
32744
32948
  if (types.length === 1 || factResults.length === 0 || chunkResults.length === 0) {
32745
32949
  const memories2 = [
32746
32950
  ...factResults.map((r) => toFactMemory(r, options.now)),
@@ -32748,7 +32952,7 @@ async function recall(query, ctx, options = {}) {
32748
32952
  ];
32749
32953
  memories2.sort((a, b) => b.score - a.score);
32750
32954
  const candidateCount = factResults.length + chunkResults.length;
32751
- fuseMs = nowMs2() - fuseStart;
32955
+ fuseMs = nowMs() - fuseStart;
32752
32956
  const admitted = memories2.slice(0, limit);
32753
32957
  hitLimit = memories2.length > limit;
32754
32958
  emitDiagnostics(candidateCount, admitted);
@@ -32812,7 +33016,7 @@ async function recall(query, ctx, options = {}) {
32812
33016
  memories = selection.out;
32813
33017
  }
32814
33018
  hitLimit = selection.cut;
32815
- fuseMs = nowMs2() - fuseStart;
33019
+ fuseMs = nowMs() - fuseStart;
32816
33020
  emitDiagnostics(byId.size, memories);
32817
33021
  return {
32818
33022
  memories,
@@ -332,6 +332,12 @@ interface RecallDiagnostics {
332
332
  * LEGACY read path this batch is uncapped, so it can be the whole vault.
333
333
  */
334
334
  vaultRowsEmbedded?: number;
335
+ /** Embedded messages the chunk lane considered; absent when the lane did not run. */
336
+ chunkRowsScanned?: number;
337
+ /** Messages whose vectors the chunk lane read from storage on a cache miss. */
338
+ chunkRowsFetched?: number;
339
+ /** Messages the chunk lane scored from the vector cache. */
340
+ chunkCacheHits?: number;
335
341
  /** Facts the fact lane returned (post-dedupe, pre-fusion). */
336
342
  factCount: number;
337
343
  /** Chunks the chunk lane returned (post-dedupe, pre-fusion). */
@@ -332,6 +332,12 @@ interface RecallDiagnostics {
332
332
  * LEGACY read path this batch is uncapped, so it can be the whole vault.
333
333
  */
334
334
  vaultRowsEmbedded?: number;
335
+ /** Embedded messages the chunk lane considered; absent when the lane did not run. */
336
+ chunkRowsScanned?: number;
337
+ /** Messages whose vectors the chunk lane read from storage on a cache miss. */
338
+ chunkRowsFetched?: number;
339
+ /** Messages the chunk lane scored from the vector cache. */
340
+ chunkCacheHits?: number;
335
341
  /** Facts the fact lane returned (post-dedupe, pre-fusion). */
336
342
  factCount: number;
337
343
  /** Chunks the chunk lane returned (post-dedupe, pre-fusion). */
@@ -1018,13 +1018,35 @@ async function generateEmbedding(text, options) {
1018
1018
  if (total === void 0) return embedOne(text, options, void 0);
1019
1019
  return withDeadline((signal) => embedOne(text, options, signal), total, "embedding");
1020
1020
  }
1021
+ var inFlightByCache = /* @__PURE__ */ new WeakMap();
1021
1022
  async function embedOne(text, options, outer) {
1022
- const { baseUrl = BASE_URL, model, cache } = options;
1023
- const timeoutMs = options.timeoutMs ?? DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS;
1024
- if (cache) {
1025
- const cached = cache.get(text);
1026
- if (cached) return Array.from(cached);
1023
+ const { cache } = options;
1024
+ if (!cache) return Array.from(await requestEmbedding(text, options, outer));
1025
+ const cached = cache.get(text);
1026
+ if (cached) return Array.from(cached);
1027
+ const inFlight = inFlightByCache.get(cache) ?? /* @__PURE__ */ new Map();
1028
+ inFlightByCache.set(cache, inFlight);
1029
+ const pending = inFlight.get(text);
1030
+ if (pending) {
1031
+ try {
1032
+ return Array.from(await pending);
1033
+ } catch (err) {
1034
+ if (outer?.aborted) throw err;
1035
+ }
1036
+ }
1037
+ const request = requestEmbedding(text, options, outer);
1038
+ inFlight.set(text, request);
1039
+ try {
1040
+ const embedding = await request;
1041
+ cache.set(text, embedding);
1042
+ return Array.from(embedding);
1043
+ } finally {
1044
+ if (inFlight.get(text) === request) inFlight.delete(text);
1027
1045
  }
1046
+ }
1047
+ async function requestEmbedding(text, options, outer) {
1048
+ const { baseUrl = BASE_URL, model } = options;
1049
+ const timeoutMs = options.timeoutMs ?? DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS;
1028
1050
  const headers = {
1029
1051
  ...await resolveAuthHeaders(options, outer),
1030
1052
  ...taskTypeHeader(options.taskType)
@@ -1059,11 +1081,7 @@ async function embedOne(text, options, outer) {
1059
1081
  totalTokens: response.data.usage.total_tokens ?? 0
1060
1082
  });
1061
1083
  }
1062
- const f32Embedding = Float32Array.from(embedding);
1063
- if (cache) {
1064
- cache.set(text, f32Embedding);
1065
- }
1066
- return Array.from(f32Embedding);
1084
+ return Float32Array.from(embedding);
1067
1085
  }
1068
1086
  var DEFAULT_EMBEDDING_BATCH_SIZE = 100;
1069
1087
  var DEFAULT_EMBEDDING_BATCH_CONCURRENCY = 3;