@anuma/sdk 7.0.0 → 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.
@@ -16424,6 +16424,7 @@ __export(index_exports, {
16424
16424
  encryptDataBatch: () => encryptDataBatch,
16425
16425
  encryptDataBytes: () => encryptDataBytes,
16426
16426
  encryptDataWithKey: () => encryptDataWithKey,
16427
+ encryptToolCallEventsOp: () => encryptToolCallEventsOp,
16427
16428
  estimateMessagesTokens: () => estimateMessagesTokens,
16428
16429
  estimateTokens: () => estimateTokens,
16429
16430
  exportPublicKey: () => exportPublicKey,
@@ -17668,6 +17669,35 @@ function decodeChunkVector(vector, onMalformed) {
17668
17669
  }
17669
17670
  }
17670
17671
 
17672
+ // src/lib/scheduling.ts
17673
+ function nowMs() {
17674
+ return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
17675
+ }
17676
+ function yieldToEventLoop() {
17677
+ const scheduler = globalThis.scheduler;
17678
+ if (typeof scheduler?.yield === "function") return scheduler.yield();
17679
+ if (typeof MessageChannel === "function") {
17680
+ return new Promise((resolve) => {
17681
+ const channel = new MessageChannel();
17682
+ channel.port1.onmessage = () => {
17683
+ channel.port1.close();
17684
+ channel.port2.close();
17685
+ resolve();
17686
+ };
17687
+ channel.port2.postMessage(null);
17688
+ });
17689
+ }
17690
+ return new Promise((resolve) => setTimeout(resolve, 0));
17691
+ }
17692
+ function createTimeSliceYielder(budgetMs) {
17693
+ let sliceStart = nowMs();
17694
+ return async () => {
17695
+ if (nowMs() - sliceStart < budgetMs) return;
17696
+ await yieldToEventLoop();
17697
+ sliceStart = nowMs();
17698
+ };
17699
+ }
17700
+
17671
17701
  // src/lib/db/encryption-utils.ts
17672
17702
  init_logger();
17673
17703
  var ENCRYPTION_PREFIX = "enc:v3:";
@@ -17814,6 +17844,12 @@ async function encryptMessageFields(message, address, signMessage, embeddedWalle
17814
17844
  const encryptedChunks = msg.chunks ? await encryptJsonField(msg.chunks, address, signMessage, embeddedWalletSigner, true) : void 0;
17815
17845
  const encryptedSources = msg.sources ? await encryptJsonField(msg.sources, address, signMessage, embeddedWalletSigner, true) : void 0;
17816
17846
  const encryptedThoughtProcess = msg.thoughtProcess ? await encryptJsonField(msg.thoughtProcess, address, signMessage, embeddedWalletSigner, true) : void 0;
17847
+ const encryptedToolCallEvents = Array.isArray(msg.toolCallEvents) ? await encryptToolCallEvents(
17848
+ msg.toolCallEvents,
17849
+ address,
17850
+ signMessage,
17851
+ embeddedWalletSigner
17852
+ ) : void 0;
17817
17853
  return {
17818
17854
  ...message,
17819
17855
  ...encryptedContent !== void 0 && { content: encryptedContent },
@@ -17821,13 +17857,31 @@ async function encryptMessageFields(message, address, signMessage, embeddedWalle
17821
17857
  ...encryptedVector !== void 0 && { vector: encryptedVector },
17822
17858
  ...encryptedChunks !== void 0 && { chunks: encryptedChunks },
17823
17859
  ...encryptedSources !== void 0 && { sources: encryptedSources },
17824
- ...encryptedThoughtProcess !== void 0 && { thoughtProcess: encryptedThoughtProcess }
17860
+ ...encryptedThoughtProcess !== void 0 && { thoughtProcess: encryptedThoughtProcess },
17861
+ ...encryptedToolCallEvents !== void 0 && { toolCallEvents: encryptedToolCallEvents }
17825
17862
  };
17826
17863
  } catch (error) {
17827
17864
  getLogger().warn("Failed to encrypt message fields:", error);
17828
17865
  throw error;
17829
17866
  }
17830
17867
  }
17868
+ function mapToolCallPayloads(events, transform) {
17869
+ return Promise.all(
17870
+ events.map(async (event) => {
17871
+ if (!event || typeof event !== "object") return event;
17872
+ const mapped = { ...event };
17873
+ if (typeof event.arguments === "string") mapped.arguments = await transform(event.arguments);
17874
+ if (typeof event.output === "string") mapped.output = await transform(event.output);
17875
+ return mapped;
17876
+ })
17877
+ );
17878
+ }
17879
+ function encryptToolCallEvents(events, address, signMessage, embeddedWalletSigner) {
17880
+ return mapToolCallPayloads(
17881
+ events,
17882
+ (value) => encryptField(value, address, signMessage, embeddedWalletSigner, true)
17883
+ );
17884
+ }
17831
17885
  async function decryptMaybeJsonFieldDetailed(value, address) {
17832
17886
  if (value === void 0) return { value: void 0, status: "plaintext" };
17833
17887
  if (value === null) return { value, status: "plaintext" };
@@ -17913,6 +17967,10 @@ async function assembleDecryptedMessage(message, address) {
17913
17967
  message.thoughtProcess,
17914
17968
  address
17915
17969
  );
17970
+ const toolCallEventsResult = await decryptMaybeJsonFieldDetailed(
17971
+ message.toolCallEvents,
17972
+ address
17973
+ );
17916
17974
  let decryptionStatus;
17917
17975
  let refreshProbe;
17918
17976
  const consider = (status, probe) => {
@@ -17931,6 +17989,14 @@ async function assembleDecryptedMessage(message, address) {
17931
17989
  if (thoughtProcessResult.probe) {
17932
17990
  consider(thoughtProcessResult.status, thoughtProcessResult.probe);
17933
17991
  }
17992
+ if (toolCallEventsResult.probe) {
17993
+ consider(toolCallEventsResult.status, toolCallEventsResult.probe);
17994
+ }
17995
+ const toolCallEvents = Array.isArray(toolCallEventsResult.value) ? await mapToolCallPayloads(toolCallEventsResult.value, async (value) => {
17996
+ const detailed = await decryptFieldDetailed(value, address);
17997
+ consider(detailed.status, isEncrypted(value) ? value : void 0);
17998
+ return detailed.value;
17999
+ }) : void 0;
17934
18000
  const statusForConsumer = decryptionStatus === "ok" || decryptionStatus === "plaintext" || !decryptionStatus ? void 0 : decryptionStatus;
17935
18001
  const result = {
17936
18002
  ...message,
@@ -17939,7 +18005,8 @@ async function assembleDecryptedMessage(message, address) {
17939
18005
  vector: vectorResult.value,
17940
18006
  chunks: chunksResult.value,
17941
18007
  sources: sourcesResult.value,
17942
- thoughtProcess: thoughtProcessResult.value
18008
+ thoughtProcess: thoughtProcessResult.value,
18009
+ toolCallEvents
17943
18010
  };
17944
18011
  if (statusForConsumer) {
17945
18012
  result.decryptionStatus = statusForConsumer;
@@ -18505,7 +18572,7 @@ async function updateMessageChunksOp(ctx, uniqueId, chunks, embeddingModel) {
18505
18572
  });
18506
18573
  return messageToStored(message, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner);
18507
18574
  }
18508
- var STRIP_CHUNK_TEXT_PAGE_SIZE = 100;
18575
+ var RAW_REPAIR_PAGE_SIZE = 100;
18509
18576
  function chunksWithoutText(raw) {
18510
18577
  if (typeof raw !== "string") return null;
18511
18578
  let parsed;
@@ -18528,8 +18595,8 @@ function chunksWithoutText(raw) {
18528
18595
  async function stripLegacyChunkTextOp(ctx) {
18529
18596
  const candidateIds = await ctx.messagesCollection.query(import_watermelondb2.Q.where("chunks", import_watermelondb2.Q.like(`%${import_watermelondb2.Q.sanitizeLikeString('"text":')}%`))).fetchIds();
18530
18597
  let stripped = 0;
18531
- for (let start = 0; start < candidateIds.length; start += STRIP_CHUNK_TEXT_PAGE_SIZE) {
18532
- const pageIds = candidateIds.slice(start, start + STRIP_CHUNK_TEXT_PAGE_SIZE);
18598
+ for (let start = 0; start < candidateIds.length; start += RAW_REPAIR_PAGE_SIZE) {
18599
+ const pageIds = candidateIds.slice(start, start + RAW_REPAIR_PAGE_SIZE);
18533
18600
  const rows = await ctx.messagesCollection.query(import_watermelondb2.Q.where("id", import_watermelondb2.Q.oneOf(pageIds))).unsafeFetchRaw();
18534
18601
  const changedIds = rows.filter((row) => chunksWithoutText(row.chunks) !== null).map((row) => String(row.id));
18535
18602
  if (changedIds.length === 0) continue;
@@ -18553,6 +18620,88 @@ async function stripLegacyChunkTextOp(ctx) {
18553
18620
  }
18554
18621
  return stripped;
18555
18622
  }
18623
+ async function encryptedToolCallEventsColumn(raw, address, signMessage, embeddedWalletSigner) {
18624
+ if (typeof raw !== "string") return null;
18625
+ let parsed;
18626
+ try {
18627
+ parsed = JSON.parse(raw);
18628
+ } catch {
18629
+ return null;
18630
+ }
18631
+ if (!Array.isArray(parsed)) return null;
18632
+ const column = JSON.stringify(
18633
+ await encryptToolCallEvents(
18634
+ parsed,
18635
+ address,
18636
+ signMessage,
18637
+ embeddedWalletSigner
18638
+ )
18639
+ );
18640
+ return column === JSON.stringify(parsed) ? null : column;
18641
+ }
18642
+ async function encryptToolCallEventsOp(ctx) {
18643
+ const { walletAddress: address, signMessage, embeddedWalletSigner } = ctx;
18644
+ if (!address || !signMessage) return 0;
18645
+ if (!await requestEncryptionKey(address, signMessage, embeddedWalletSigner)) return 0;
18646
+ if (!hasEncryptionKey(address)) return 0;
18647
+ if (!await savedKeyOpensExistingData(ctx, address, signMessage, embeddedWalletSigner)) {
18648
+ return 0;
18649
+ }
18650
+ const candidateIds = await ctx.messagesCollection.query(
18651
+ import_watermelondb2.Q.where("tool_call_events", import_watermelondb2.Q.notEq(null)),
18652
+ import_watermelondb2.Q.where("tool_call_events", import_watermelondb2.Q.notIn(["", "[]"]))
18653
+ ).fetchIds();
18654
+ let encrypted = 0;
18655
+ for (let start = 0; start < candidateIds.length; start += RAW_REPAIR_PAGE_SIZE) {
18656
+ const pageIds = candidateIds.slice(start, start + RAW_REPAIR_PAGE_SIZE);
18657
+ const rows = await ctx.messagesCollection.query(import_watermelondb2.Q.where("id", import_watermelondb2.Q.oneOf(pageIds))).unsafeFetchRaw();
18658
+ const repairs = /* @__PURE__ */ new Map();
18659
+ for (const row of rows) {
18660
+ const column = await encryptedToolCallEventsColumn(
18661
+ row.tool_call_events,
18662
+ address,
18663
+ signMessage,
18664
+ embeddedWalletSigner
18665
+ );
18666
+ if (column !== null) repairs.set(String(row.id), { raw: row.tool_call_events, column });
18667
+ }
18668
+ if (repairs.size === 0) continue;
18669
+ await ctx.database.write(async () => {
18670
+ const messages = (await ctx.messagesCollection.query(import_watermelondb2.Q.where("id", import_watermelondb2.Q.oneOf([...repairs.keys()]))).fetch()).filter(
18671
+ (message) => message._raw.tool_call_events === repairs.get(message.id)?.raw
18672
+ );
18673
+ if (messages.length === 0) return;
18674
+ const conversationIds = [...new Set(messages.map((message) => message.conversationId))];
18675
+ const conversations = await ctx.conversationsCollection.query(import_watermelondb2.Q.where("conversation_id", import_watermelondb2.Q.oneOf(conversationIds))).fetch();
18676
+ await ctx.database.batch(
18677
+ ...messages.map(
18678
+ (message) => message.prepareUpdate((msg) => {
18679
+ msg._setRaw("tool_call_events", repairs.get(message.id).column);
18680
+ })
18681
+ ),
18682
+ ...conversations.map((conversation) => conversation.prepareUpdate(() => {
18683
+ }))
18684
+ );
18685
+ encrypted += messages.length;
18686
+ });
18687
+ }
18688
+ return encrypted;
18689
+ }
18690
+ async function savedKeyOpensExistingData(ctx, address, signMessage, embeddedWalletSigner) {
18691
+ const v3Probe = await findEncryptedContent(ctx, "enc:v3:%");
18692
+ if (v3Probe) {
18693
+ const { status } = await decryptFieldDetailed(v3Probe, address);
18694
+ if (status !== "auth_mismatch" && status !== "key_missing") return true;
18695
+ return refreshEncryptionKeyIfMatches(address, v3Probe, signMessage, embeddedWalletSigner);
18696
+ }
18697
+ const v2Probe = await findEncryptedContent(ctx, "enc:v2:%");
18698
+ if (!v2Probe) return true;
18699
+ return refreshEncryptionKeyIfMatches(address, v2Probe, signMessage, embeddedWalletSigner);
18700
+ }
18701
+ async function findEncryptedContent(ctx, pattern) {
18702
+ const [row] = await ctx.messagesCollection.query(import_watermelondb2.Q.where("content", import_watermelondb2.Q.like(pattern)), import_watermelondb2.Q.take(1)).unsafeFetchRaw();
18703
+ return row ? String(row.content) : null;
18704
+ }
18556
18705
  async function updateMessageErrorOp(ctx, uniqueId, error) {
18557
18706
  let message;
18558
18707
  try {
@@ -18696,6 +18845,95 @@ async function searchMessagesOp(ctx, queryVector, options) {
18696
18845
  })
18697
18846
  );
18698
18847
  }
18848
+ var CHUNK_VECTOR_BATCH_SIZE = 50;
18849
+ var CHUNK_SCAN_YIELD_BUDGET_MS = 12;
18850
+ var hasEmbeddingSql = `(coalesce("chunks", '') != '' or coalesce("vector", '') != '')`;
18851
+ function toChunkCandidateKey(raw) {
18852
+ return {
18853
+ id: raw.id,
18854
+ conversationId: String(raw.conversation_id),
18855
+ version: Number(raw.updated_at) || 0,
18856
+ embeddingModel: raw.embedding_model ?? null
18857
+ };
18858
+ }
18859
+ async function fetchRowsByIds(ctx, ids) {
18860
+ if (ids.length === 0) return [];
18861
+ return await ctx.messagesCollection.query(import_watermelondb2.Q.where("id", import_watermelondb2.Q.oneOf(ids))).unsafeFetchRaw();
18862
+ }
18863
+ async function projectedChunkRowSource(ctx, filter) {
18864
+ const clauses = [
18865
+ `"history"."_status" is not 'deleted'`,
18866
+ hasEmbeddingSql,
18867
+ `exists (select 1 from "conversations" where "conversations"."conversation_id" = "history"."conversation_id" and "conversations"."is_deleted" = 0 and "conversations"."_status" is not 'deleted')`
18868
+ ];
18869
+ const args = [];
18870
+ if (filter.conversationId !== void 0) {
18871
+ clauses.push(`"history"."conversation_id" = ?`);
18872
+ args.push(filter.conversationId);
18873
+ }
18874
+ if (filter.excludeConversationId !== void 0) {
18875
+ clauses.push(`"history"."conversation_id" != ?`);
18876
+ args.push(filter.excludeConversationId);
18877
+ }
18878
+ const keyRows = await ctx.messagesCollection.query(
18879
+ import_watermelondb2.Q.unsafeSqlQuery(
18880
+ `select "id", "conversation_id", "updated_at", "embedding_model" from "history" where ${clauses.join(" and ")}`,
18881
+ args
18882
+ )
18883
+ ).unsafeFetchRaw();
18884
+ return {
18885
+ keys: keyRows.map(toChunkCandidateKey),
18886
+ loadVectorRows: async (ids) => {
18887
+ if (ids.length === 0) return [];
18888
+ return await ctx.messagesCollection.query(
18889
+ import_watermelondb2.Q.unsafeSqlQuery(
18890
+ `select "id", "updated_at", "chunks", "vector" from "history" where "_status" is not 'deleted' and "id" in (${ids.map(() => "?").join(",")})`,
18891
+ ids
18892
+ )
18893
+ ).unsafeFetchRaw();
18894
+ },
18895
+ loadFullRows: (ids) => fetchRowsByIds(ctx, ids)
18896
+ };
18897
+ }
18898
+ async function fullScanChunkRowSource(ctx, filter) {
18899
+ const activeConversations = await ctx.conversationsCollection.query(import_watermelondb2.Q.where("is_deleted", false)).fetch();
18900
+ const activeConversationIds = new Set(
18901
+ activeConversations.map((c) => String(c._getRaw("conversation_id")))
18902
+ );
18903
+ const queryConditions = filter.conversationId !== void 0 ? [import_watermelondb2.Q.where("conversation_id", filter.conversationId)] : [];
18904
+ const rows = await ctx.messagesCollection.query(...queryConditions).unsafeFetchRaw();
18905
+ const rowsById = /* @__PURE__ */ new Map();
18906
+ for (const row of rows) {
18907
+ const conversationId = String(row.conversation_id);
18908
+ if (!activeConversationIds.has(conversationId)) continue;
18909
+ if (filter.excludeConversationId !== void 0 && conversationId === filter.excludeConversationId) {
18910
+ continue;
18911
+ }
18912
+ if (!row.chunks && !row.vector) continue;
18913
+ rowsById.set(row.id, row);
18914
+ }
18915
+ const pick = (ids) => Promise.resolve(
18916
+ ids.flatMap((id) => {
18917
+ const row = rowsById.get(id);
18918
+ return row ? [row] : [];
18919
+ })
18920
+ );
18921
+ return {
18922
+ keys: Array.from(rowsById.values(), toChunkCandidateKey),
18923
+ loadVectorRows: pick,
18924
+ loadFullRows: pick
18925
+ };
18926
+ }
18927
+ async function chunkRowSource(ctx, filter) {
18928
+ try {
18929
+ return await projectedChunkRowSource(ctx, filter);
18930
+ } catch (err) {
18931
+ getLogger().debug(
18932
+ "searchChunksOp: projected SQL unavailable, using full-load fallback: " + (err instanceof Error ? err.message : String(err))
18933
+ );
18934
+ return fullScanChunkRowSource(ctx, filter);
18935
+ }
18936
+ }
18699
18937
  async function searchChunksOp(ctx, queryVector, options) {
18700
18938
  const {
18701
18939
  limit = 10,
@@ -18703,83 +18941,111 @@ async function searchChunksOp(ctx, queryVector, options) {
18703
18941
  conversationId,
18704
18942
  excludeConversationId,
18705
18943
  embeddingModel,
18706
- chunkCache
18944
+ chunkCache,
18945
+ onStats
18707
18946
  } = options || {};
18708
- const activeConversations = await ctx.conversationsCollection.query(import_watermelondb2.Q.where("is_deleted", false)).fetch();
18709
- const activeConversationIds = new Set(
18710
- activeConversations.map((c) => String(c._getRaw("conversation_id")))
18711
- );
18712
- const queryConditions = conversationId ? [import_watermelondb2.Q.where("conversation_id", conversationId)] : [];
18713
- const messages = await ctx.messagesCollection.query(...queryConditions).unsafeFetchRaw();
18947
+ const source = await chunkRowSource(ctx, { conversationId, excludeConversationId });
18948
+ const yieldIfDue = createTimeSliceYielder(CHUNK_SCAN_YIELD_BUDGET_MS);
18714
18949
  const candidates = [];
18715
18950
  let staleSkipped = 0;
18716
18951
  let unreadableVectors = 0;
18717
- for (const message of messages) {
18718
- const msgConvId = String(message.conversation_id);
18719
- if (!activeConversationIds.has(msgConvId)) continue;
18720
- if (excludeConversationId !== void 0 && msgConvId === excludeConversationId) continue;
18721
- if (embeddingModel) {
18722
- const storedModel = message.embedding_model;
18723
- if (storedModel !== null && storedModel !== void 0 && storedModel !== embeddingModel) {
18724
- staleSkipped++;
18725
- continue;
18726
- }
18952
+ let cacheHits = 0;
18953
+ const missIds = [];
18954
+ const orderById = /* @__PURE__ */ new Map();
18955
+ for (const [order, key] of source.keys.entries()) {
18956
+ await yieldIfDue();
18957
+ orderById.set(key.id, order);
18958
+ if (embeddingModel && key.embeddingModel !== null && key.embeddingModel !== embeddingModel) {
18959
+ staleSkipped++;
18960
+ continue;
18727
18961
  }
18728
- const version = Number(message.updated_at) || 0;
18729
- const cached = chunkCache?.get(message.id);
18730
- if (cached && cached.version === version) {
18731
- if (cached.chunks.length > 0) {
18732
- for (let ci = 0; ci < cached.chunks.length; ci++) {
18733
- const vec = cached.chunks[ci];
18734
- if (vec.length === 0) continue;
18735
- const similarity = cosineSimilarity(queryVector, vec);
18736
- if (similarity >= minSimilarity) {
18737
- candidates.push({
18738
- message,
18739
- similarity,
18740
- chunkTextSource: { kind: "chunk-cached", chunkIndex: ci, version }
18741
- });
18742
- }
18743
- }
18744
- } else if (cached.fallback) {
18745
- const similarity = cosineSimilarity(queryVector, cached.fallback);
18746
- if (similarity >= minSimilarity) {
18747
- candidates.push({ message, similarity, chunkTextSource: { kind: "message" } });
18748
- }
18749
- }
18962
+ const cached = chunkCache?.get(key.id);
18963
+ if (!cached || cached.version !== key.version) {
18964
+ missIds.push(key.id);
18750
18965
  continue;
18751
18966
  }
18752
- const chunks = await readRawJsonField(message, "chunks", ctx.walletAddress);
18753
- if (chunks && chunks.length > 0) {
18754
- const vectors = chunks.map(
18755
- (chunk) => decodeChunkVector(chunk.vector, () => {
18756
- unreadableVectors++;
18757
- })
18758
- );
18759
- chunkCache?.set(message.id, { version, chunks: vectors });
18760
- for (let ci = 0; ci < chunks.length; ci++) {
18761
- const vec = vectors[ci];
18967
+ cacheHits++;
18968
+ if (cached.chunks.length > 0) {
18969
+ for (let ci = 0; ci < cached.chunks.length; ci++) {
18970
+ const vec = cached.chunks[ci];
18762
18971
  if (vec.length === 0) continue;
18763
18972
  const similarity = cosineSimilarity(queryVector, vec);
18764
18973
  if (similarity >= minSimilarity) {
18765
18974
  candidates.push({
18766
- message,
18975
+ id: key.id,
18976
+ version: key.version,
18977
+ order,
18767
18978
  similarity,
18768
- chunkTextSource: { kind: "chunk", chunk: chunks[ci], siblings: chunks }
18979
+ chunkTextSource: { kind: "chunk-cached", chunkIndex: ci, version: key.version }
18769
18980
  });
18770
18981
  }
18771
18982
  }
18772
- } else {
18773
- const messageVector = await readRawJsonField(message, "vector", ctx.walletAddress);
18774
- if (!messageVector || messageVector.length === 0) continue;
18775
- const fallback = Float32Array.from(messageVector);
18776
- chunkCache?.set(message.id, { version, chunks: [], fallback });
18777
- const similarity = cosineSimilarity(queryVector, fallback);
18983
+ } else if (cached.fallback) {
18984
+ const similarity = cosineSimilarity(queryVector, cached.fallback);
18778
18985
  if (similarity >= minSimilarity) {
18779
- candidates.push({ message, similarity, chunkTextSource: { kind: "message" } });
18986
+ candidates.push({
18987
+ id: key.id,
18988
+ version: key.version,
18989
+ order,
18990
+ similarity,
18991
+ chunkTextSource: { kind: "message" }
18992
+ });
18993
+ }
18994
+ }
18995
+ }
18996
+ let rowsFetched = 0;
18997
+ for (let offset = 0; offset < missIds.length; offset += CHUNK_VECTOR_BATCH_SIZE) {
18998
+ const rows = await source.loadVectorRows(
18999
+ missIds.slice(offset, offset + CHUNK_VECTOR_BATCH_SIZE)
19000
+ );
19001
+ rowsFetched += rows.length;
19002
+ for (const row of rows) {
19003
+ await yieldIfDue();
19004
+ const id = row.id;
19005
+ const version = Number(row.updated_at) || 0;
19006
+ const order = orderById.get(id) ?? Number.MAX_SAFE_INTEGER;
19007
+ const chunks = await readRawJsonField(row, "chunks", ctx.walletAddress);
19008
+ if (chunks && chunks.length > 0) {
19009
+ const vectors = chunks.map(
19010
+ (chunk) => decodeChunkVector(chunk.vector, () => {
19011
+ unreadableVectors++;
19012
+ })
19013
+ );
19014
+ chunkCache?.set(id, { version, chunks: vectors });
19015
+ for (let ci = 0; ci < chunks.length; ci++) {
19016
+ const vec = vectors[ci];
19017
+ if (vec.length === 0) continue;
19018
+ const similarity = cosineSimilarity(queryVector, vec);
19019
+ if (similarity >= minSimilarity) {
19020
+ candidates.push({
19021
+ id,
19022
+ version,
19023
+ order,
19024
+ similarity,
19025
+ chunkTextSource: { kind: "chunk", chunk: chunks[ci], siblings: chunks }
19026
+ });
19027
+ }
19028
+ }
19029
+ } else {
19030
+ const messageVector = await readRawJsonField(row, "vector", ctx.walletAddress);
19031
+ if (messageVector && messageVector.length > 0) {
19032
+ const fallback = Float32Array.from(messageVector);
19033
+ chunkCache?.set(id, { version, chunks: [], fallback });
19034
+ const similarity = cosineSimilarity(queryVector, fallback);
19035
+ if (similarity >= minSimilarity) {
19036
+ candidates.push({
19037
+ id,
19038
+ version,
19039
+ order,
19040
+ similarity,
19041
+ chunkTextSource: { kind: "message" }
19042
+ });
19043
+ }
19044
+ }
18780
19045
  }
18781
19046
  }
18782
19047
  }
19048
+ onStats?.({ rowsScanned: source.keys.length, rowsFetched, cacheHits });
18783
19049
  if (staleSkipped > 0) {
18784
19050
  getLogger().warn(
18785
19051
  `searchChunksOp: skipped ${staleSkipped} messages whose embedding model differs from the current model (${embeddingModel}) \u2014 re-embed via chunkAndEmbedAllMessages`
@@ -18790,12 +19056,17 @@ async function searchChunksOp(ctx, queryVector, options) {
18790
19056
  `searchChunksOp: could not read ${unreadableVectors} chunk vectors and left them out of ranking \u2014 the stored value is corrupt, so those chunks stay unsearchable until the message is re-embedded via chunkAndEmbedAllMessages`
18791
19057
  );
18792
19058
  }
18793
- const topK = candidates.sort((a, b) => b.similarity - a.similarity).slice(0, limit);
18794
- const uniqueMessages = /* @__PURE__ */ new Map();
18795
- for (const c of topK) uniqueMessages.set(c.message.id, c.message);
19059
+ const topK = candidates.sort((a, b) => b.similarity - a.similarity || a.order - b.order).slice(0, limit);
19060
+ const topIds = Array.from(new Set(topK.map((c) => c.id)));
19061
+ const fullRows = await source.loadFullRows(topIds);
19062
+ const rowById = new Map(fullRows.map((row) => [row.id, row]));
19063
+ const liveTopK = topK.filter((c) => {
19064
+ const row = rowById.get(c.id);
19065
+ return row !== void 0 && (Number(row.updated_at) || 0) === c.version;
19066
+ });
18796
19067
  const storedById = new Map(
18797
19068
  await Promise.all(
18798
- Array.from(uniqueMessages, async ([id, m]) => {
19069
+ Array.from(rowById, async ([id, m]) => {
18799
19070
  const stored = await messageRawToStored(
18800
19071
  m,
18801
19072
  ctx.walletAddress,
@@ -18808,24 +19079,24 @@ async function searchChunksOp(ctx, queryVector, options) {
18808
19079
  );
18809
19080
  const chunkStateByMsgId = /* @__PURE__ */ new Map();
18810
19081
  const needsChunkText = /* @__PURE__ */ new Set();
18811
- for (const c of topK) {
18812
- if (c.chunkTextSource.kind === "chunk-cached") needsChunkText.add(c.message.id);
19082
+ for (const c of liveTopK) {
19083
+ if (c.chunkTextSource.kind === "chunk-cached") needsChunkText.add(c.id);
18813
19084
  }
18814
19085
  await Promise.all(
18815
19086
  Array.from(needsChunkText, async (id) => {
18816
- const m = uniqueMessages.get(id);
19087
+ const m = rowById.get(id);
18817
19088
  const parsed = await readRawJsonField(m, "chunks", ctx.walletAddress);
18818
19089
  const version = Number(m.updated_at) || 0;
18819
19090
  if (parsed) chunkStateByMsgId.set(id, { version, chunks: parsed });
18820
19091
  })
18821
19092
  );
18822
- return topK.map(({ message, similarity, chunkTextSource }) => {
18823
- const stored = { ...storedById.get(message.id) };
19093
+ return liveTopK.map(({ id, similarity, chunkTextSource }) => {
19094
+ const stored = { ...storedById.get(id) };
18824
19095
  let chunkText2;
18825
19096
  if (chunkTextSource.kind === "chunk") {
18826
19097
  chunkText2 = resolveChunkText(stored.content, chunkTextSource.chunk, chunkTextSource.siblings);
18827
19098
  } else if (chunkTextSource.kind === "chunk-cached") {
18828
- const state = chunkStateByMsgId.get(message.id);
19099
+ const state = chunkStateByMsgId.get(id);
18829
19100
  const chunk = state && state.version === chunkTextSource.version ? state.chunks[chunkTextSource.chunkIndex] : void 0;
18830
19101
  chunkText2 = resolveChunkText(stored.content, chunk, state?.chunks);
18831
19102
  } else {
@@ -19927,13 +20198,35 @@ async function generateEmbedding(text12, options) {
19927
20198
  if (total === void 0) return embedOne(text12, options, void 0);
19928
20199
  return withDeadline((signal) => embedOne(text12, options, signal), total, "embedding");
19929
20200
  }
20201
+ var inFlightByCache = /* @__PURE__ */ new WeakMap();
19930
20202
  async function embedOne(text12, options, outer) {
19931
- const { baseUrl = BASE_URL, model, cache } = options;
19932
- const timeoutMs = options.timeoutMs ?? DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS;
19933
- if (cache) {
19934
- const cached = cache.get(text12);
19935
- if (cached) return Array.from(cached);
20203
+ const { cache } = options;
20204
+ if (!cache) return Array.from(await requestEmbedding(text12, options, outer));
20205
+ const cached = cache.get(text12);
20206
+ if (cached) return Array.from(cached);
20207
+ const inFlight = inFlightByCache.get(cache) ?? /* @__PURE__ */ new Map();
20208
+ inFlightByCache.set(cache, inFlight);
20209
+ const pending = inFlight.get(text12);
20210
+ if (pending) {
20211
+ try {
20212
+ return Array.from(await pending);
20213
+ } catch (err) {
20214
+ if (outer?.aborted) throw err;
20215
+ }
20216
+ }
20217
+ const request = requestEmbedding(text12, options, outer);
20218
+ inFlight.set(text12, request);
20219
+ try {
20220
+ const embedding = await request;
20221
+ cache.set(text12, embedding);
20222
+ return Array.from(embedding);
20223
+ } finally {
20224
+ if (inFlight.get(text12) === request) inFlight.delete(text12);
19936
20225
  }
20226
+ }
20227
+ async function requestEmbedding(text12, options, outer) {
20228
+ const { baseUrl = BASE_URL, model } = options;
20229
+ const timeoutMs = options.timeoutMs ?? DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS;
19937
20230
  const headers = {
19938
20231
  ...await resolveAuthHeaders(options, outer),
19939
20232
  ...taskTypeHeader(options.taskType)
@@ -19968,11 +20261,7 @@ async function embedOne(text12, options, outer) {
19968
20261
  totalTokens: response.data.usage.total_tokens ?? 0
19969
20262
  });
19970
20263
  }
19971
- const f32Embedding = Float32Array.from(embedding);
19972
- if (cache) {
19973
- cache.set(text12, f32Embedding);
19974
- }
19975
- return Array.from(f32Embedding);
20264
+ return Float32Array.from(embedding);
19976
20265
  }
19977
20266
  var DEFAULT_EMBEDDING_BATCH_SIZE = 100;
19978
20267
  var DEFAULT_EMBEDDING_BATCH_CONCURRENCY = 3;
@@ -30436,9 +30725,9 @@ function recencyMultiplier(updatedAt, opts = {}) {
30436
30725
  const updatedMs = updatedAt.getTime();
30437
30726
  if (!Number.isFinite(updatedMs)) return noDate;
30438
30727
  const now = opts.now ?? /* @__PURE__ */ new Date();
30439
- const nowMs3 = now.getTime();
30440
- if (!Number.isFinite(nowMs3)) return noDate;
30441
- const ageDays2 = Math.max(0, (nowMs3 - updatedMs) / DAY_MS2);
30728
+ const nowMs2 = now.getTime();
30729
+ if (!Number.isFinite(nowMs2)) return noDate;
30730
+ const ageDays2 = Math.max(0, (nowMs2 - updatedMs) / DAY_MS2);
30442
30731
  const perYearDecay = opts.perYearDecay ?? DEFAULT_PER_YEAR_DECAY;
30443
30732
  const floor = opts.floor ?? DEFAULT_FLOOR;
30444
30733
  const decay = perYearDecay * (ageDays2 / 365);
@@ -30950,9 +31239,6 @@ function createVaultEmbeddingCache(maxSize = DEFAULT_VAULT_CACHE_SIZE) {
30950
31239
 
30951
31240
  // src/lib/memoryVault/searchTool.ts
30952
31241
  var DEFAULT_RERANK_TOP_N = 5;
30953
- function nowMs() {
30954
- return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
30955
- }
30956
31242
  var warnedProgrammaticDecomposeIgnored = false;
30957
31243
  function normalizeEventTimeKind(kind) {
30958
31244
  return kind === "point" || kind === "range" || kind === "ongoing" ? kind : null;
@@ -33854,6 +34140,32 @@ function rankProfileCandidates(inputs, now = Date.now(), options = {}) {
33854
34140
  return ranked;
33855
34141
  }
33856
34142
 
34143
+ // src/lib/db/chat/embeddingCache.ts
34144
+ var MaskScopedEmbeddingCache = class extends Map {
34145
+ constructor(inner, masked) {
34146
+ super();
34147
+ this.inner = inner;
34148
+ this.masked = masked;
34149
+ }
34150
+ key(text12) {
34151
+ return `${this.masked ? "m" : "r"}:${text12}`;
34152
+ }
34153
+ get(text12) {
34154
+ return this.inner.get(this.key(text12));
34155
+ }
34156
+ set(text12, value) {
34157
+ this.inner.set(this.key(text12), value);
34158
+ return this;
34159
+ }
34160
+ };
34161
+ var scopedViews = /* @__PURE__ */ new WeakMap();
34162
+ function maskScopedEmbeddingCache(cache, masked) {
34163
+ const views = scopedViews.get(cache) ?? {};
34164
+ scopedViews.set(cache, views);
34165
+ const slot = masked ? "masked" : "raw";
34166
+ return views[slot] ?? (views[slot] = new MaskScopedEmbeddingCache(cache, masked));
34167
+ }
34168
+
33857
34169
  // src/lib/memory/recall.ts
33858
34170
  init_logger();
33859
34171
 
@@ -34064,9 +34376,6 @@ function scoreEventTimeOverlap(memoryStart, memoryEnd, memoryKind, window2) {
34064
34376
  var DEFAULT_LIMIT = 8;
34065
34377
  var DEFAULT_BUDGET = "low";
34066
34378
  var DEFAULT_FACT_MIN_SCORE = 0.1;
34067
- function nowMs2() {
34068
- return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
34069
- }
34070
34379
  var DEFAULT_CHUNK_MIN_SCORE = 0.5;
34071
34380
  var DEFAULT_QUERY_EMBED_TOTAL_TIMEOUT_MS = 8e3;
34072
34381
  function flagsForBudget(budget) {
@@ -34104,12 +34413,19 @@ async function recall(query, ctx, options = {}) {
34104
34413
  const subQueries = normalizedFacets.length >= 2 ? normalizedFacets : void 0;
34105
34414
  const embeddingOptions = {
34106
34415
  ...ctx.embeddingOptions,
34107
- taskType: "memory_recall_embed"
34416
+ taskType: "memory_recall_embed",
34417
+ ...options.embeddingCache && !ctx.embeddingOptions.cache && {
34418
+ cache: maskScopedEmbeddingCache(
34419
+ options.embeddingCache,
34420
+ Boolean(ctx.embeddingOptions.maskInput)
34421
+ )
34422
+ }
34108
34423
  };
34109
- const t0 = nowMs2();
34424
+ const t0 = nowMs();
34110
34425
  let prepMs = 0;
34111
34426
  let factLaneMs = 0;
34112
34427
  let chunkLaneMs = 0;
34428
+ let chunkStats;
34113
34429
  let fuseMs = 0;
34114
34430
  const factResults = [];
34115
34431
  const chunkResults = [];
@@ -34164,6 +34480,11 @@ async function recall(query, ctx, options = {}) {
34164
34480
  ...decryptLastRan !== void 0 && { decryptLast: decryptLastRan },
34165
34481
  ...vaultRowsDecrypted !== void 0 && { vaultRowsDecrypted },
34166
34482
  ...vaultRowsEmbedded !== void 0 && { vaultRowsEmbedded },
34483
+ ...chunkStats && {
34484
+ chunkRowsScanned: chunkStats.rowsScanned,
34485
+ chunkRowsFetched: chunkStats.rowsFetched,
34486
+ chunkCacheHits: chunkStats.cacheHits
34487
+ },
34167
34488
  factCount: factResults.length,
34168
34489
  chunkCount: chunkResults.length,
34169
34490
  admittedCount: admitted.length,
@@ -34175,7 +34496,7 @@ async function recall(query, ctx, options = {}) {
34175
34496
  temporalLaneCount,
34176
34497
  emptyReason: emptyReasonFor(admitted.length, laneRan),
34177
34498
  timings: {
34178
- total: nowMs2() - t0,
34499
+ total: nowMs() - t0,
34179
34500
  prep: prepMs,
34180
34501
  factLane: factLaneMs,
34181
34502
  rerank: rerankMs,
@@ -34197,14 +34518,14 @@ async function recall(query, ctx, options = {}) {
34197
34518
  const needsChunkEmbedding = types.includes("chunk") && ctx.storageCtx;
34198
34519
  const wantsTemporal = types.includes("fact") && (ctx.factSource || ctx.vaultCtx);
34199
34520
  const queryEmbedTotalTimeoutMs = options.queryEmbedTotalTimeoutMs ?? DEFAULT_QUERY_EMBED_TOTAL_TIMEOUT_MS;
34200
- const prepStart = nowMs2();
34521
+ const prepStart = nowMs();
34201
34522
  let sharedEmbedMs = 0;
34202
34523
  const [queryEmbedding, entityRanking, temporalRanking] = await Promise.all([
34203
34524
  needsChunkEmbedding ? generateEmbedding(query, {
34204
34525
  ...embeddingOptions,
34205
34526
  totalTimeoutMs: queryEmbedTotalTimeoutMs
34206
34527
  }).finally(() => {
34207
- sharedEmbedMs = nowMs2() - prepStart;
34528
+ sharedEmbedMs = nowMs() - prepStart;
34208
34529
  }).then((vec) => {
34209
34530
  if (vec.length === 0) {
34210
34531
  getLogger().warn(
@@ -34237,11 +34558,11 @@ async function recall(query, ctx, options = {}) {
34237
34558
  () => ctx.factSource ? ctx.factSource.temporalRanking(query, options.now) : buildTemporalLaneRanking(query, ctx.vaultCtx, options.now)
34238
34559
  ) : Promise.resolve([])
34239
34560
  ]);
34240
- prepMs = nowMs2() - prepStart;
34561
+ prepMs = nowMs() - prepStart;
34241
34562
  graphLaneCount = entityRanking.length;
34242
34563
  temporalLaneCount = temporalRanking.length;
34243
34564
  if (types.includes("fact") && (ctx.factSource || ctx.vaultCtx && ctx.vaultCache)) {
34244
- const factStart = nowMs2();
34565
+ const factStart = nowMs();
34245
34566
  const vaultMinScore = options.minScore ?? DEFAULT_FACT_MIN_SCORE;
34246
34567
  factFloor = vaultMinScore;
34247
34568
  const {
@@ -34317,11 +34638,11 @@ async function recall(query, ctx, options = {}) {
34317
34638
  factLaneRankedOnCosine = factRankedOnCosine;
34318
34639
  decryptLastRan = factDecryptLast;
34319
34640
  vaultRowsDecrypted = factRowsDecrypted;
34320
- factLaneMs = nowMs2() - factStart;
34641
+ factLaneMs = nowMs() - factStart;
34321
34642
  }
34322
34643
  if (chunkEmbedFailed && !factLaneRankedOnCosine) embeddingsUnavailable = true;
34323
34644
  if (types.includes("chunk") && ctx.storageCtx && queryEmbedding) {
34324
- const chunkStart = nowMs2();
34645
+ const chunkStart = nowMs();
34325
34646
  const chunkMinScore = options.minScore ?? DEFAULT_CHUNK_MIN_SCORE;
34326
34647
  chunkFloor = chunkMinScore;
34327
34648
  const results = await searchChunksOp(ctx.storageCtx, queryEmbedding, {
@@ -34332,12 +34653,15 @@ async function recall(query, ctx, options = {}) {
34332
34653
  ...options.excludeConversationId && {
34333
34654
  excludeConversationId: options.excludeConversationId
34334
34655
  },
34335
- ...ctx.chunkCache && { chunkCache: ctx.chunkCache }
34656
+ ...ctx.chunkCache && { chunkCache: ctx.chunkCache },
34657
+ onStats: (stats) => {
34658
+ chunkStats = stats;
34659
+ }
34336
34660
  });
34337
34661
  chunkResults.push(...dedupeBy(results, (r) => r.chunkText.trim()));
34338
- chunkLaneMs = nowMs2() - chunkStart;
34662
+ chunkLaneMs = nowMs() - chunkStart;
34339
34663
  }
34340
- const fuseStart = nowMs2();
34664
+ const fuseStart = nowMs();
34341
34665
  if (types.length === 1 || factResults.length === 0 || chunkResults.length === 0) {
34342
34666
  const memories2 = [
34343
34667
  ...factResults.map((r) => toFactMemory(r, options.now)),
@@ -34345,7 +34669,7 @@ async function recall(query, ctx, options = {}) {
34345
34669
  ];
34346
34670
  memories2.sort((a, b) => b.score - a.score);
34347
34671
  const candidateCount = factResults.length + chunkResults.length;
34348
- fuseMs = nowMs2() - fuseStart;
34672
+ fuseMs = nowMs() - fuseStart;
34349
34673
  const admitted = memories2.slice(0, limit);
34350
34674
  hitLimit = memories2.length > limit;
34351
34675
  emitDiagnostics(candidateCount, admitted);
@@ -34409,7 +34733,7 @@ async function recall(query, ctx, options = {}) {
34409
34733
  memories = selection.out;
34410
34734
  }
34411
34735
  hitLimit = selection.cut;
34412
- fuseMs = nowMs2() - fuseStart;
34736
+ fuseMs = nowMs() - fuseStart;
34413
34737
  emitDiagnostics(byId.size, memories);
34414
34738
  return {
34415
34739
  memories,
@@ -39427,6 +39751,7 @@ function serverPlatformStorage() {
39427
39751
  encryptDataBatch,
39428
39752
  encryptDataBytes,
39429
39753
  encryptDataWithKey,
39754
+ encryptToolCallEventsOp,
39430
39755
  estimateMessagesTokens,
39431
39756
  estimateTokens,
39432
39757
  exportPublicKey,