@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.
@@ -1566,6 +1566,7 @@ __export(index_exports, {
1566
1566
  encryptDataBatch: () => encryptDataBatch,
1567
1567
  encryptDataBytes: () => encryptDataBytes,
1568
1568
  encryptDataWithKey: () => encryptDataWithKey,
1569
+ encryptToolCallEventsOp: () => encryptToolCallEventsOp,
1569
1570
  ensureDefaultFoldersOp: () => ensureDefaultFoldersOp,
1570
1571
  expandToolSetsAdditive: () => expandToolSetsAdditive,
1571
1572
  exportElementToPdf: () => exportElementToPdf,
@@ -3584,6 +3585,35 @@ function decodeChunkVector(vector, onMalformed) {
3584
3585
  }
3585
3586
  }
3586
3587
 
3588
+ // src/lib/scheduling.ts
3589
+ function nowMs() {
3590
+ return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
3591
+ }
3592
+ function yieldToEventLoop() {
3593
+ const scheduler = globalThis.scheduler;
3594
+ if (typeof scheduler?.yield === "function") return scheduler.yield();
3595
+ if (typeof MessageChannel === "function") {
3596
+ return new Promise((resolve) => {
3597
+ const channel = new MessageChannel();
3598
+ channel.port1.onmessage = () => {
3599
+ channel.port1.close();
3600
+ channel.port2.close();
3601
+ resolve();
3602
+ };
3603
+ channel.port2.postMessage(null);
3604
+ });
3605
+ }
3606
+ return new Promise((resolve) => setTimeout(resolve, 0));
3607
+ }
3608
+ function createTimeSliceYielder(budgetMs) {
3609
+ let sliceStart = nowMs();
3610
+ return async () => {
3611
+ if (nowMs() - sliceStart < budgetMs) return;
3612
+ await yieldToEventLoop();
3613
+ sliceStart = nowMs();
3614
+ };
3615
+ }
3616
+
3587
3617
  // src/lib/db/encryption-utils.ts
3588
3618
  init_logger();
3589
3619
  var ENCRYPTION_PREFIX = "enc:v3:";
@@ -3593,6 +3623,11 @@ function isEncrypted(value) {
3593
3623
  const payload = value.slice(prefix.length);
3594
3624
  return payload.length >= 56 && /^[0-9a-f]+$/i.test(payload);
3595
3625
  }
3626
+ function hasEncryptedToolPayload(event) {
3627
+ return [event.arguments, event.output].some(
3628
+ (value) => typeof value === "string" && isEncrypted(value)
3629
+ );
3630
+ }
3596
3631
  function detectEncryptionVersion(value) {
3597
3632
  if (value.startsWith("enc:v3:")) {
3598
3633
  return { version: "v3", encryptedData: value.slice("enc:v3:".length) };
@@ -3730,6 +3765,12 @@ async function encryptMessageFields(message, address, signMessage, embeddedWalle
3730
3765
  const encryptedChunks = msg.chunks ? await encryptJsonField(msg.chunks, address, signMessage, embeddedWalletSigner, true) : void 0;
3731
3766
  const encryptedSources = msg.sources ? await encryptJsonField(msg.sources, address, signMessage, embeddedWalletSigner, true) : void 0;
3732
3767
  const encryptedThoughtProcess = msg.thoughtProcess ? await encryptJsonField(msg.thoughtProcess, address, signMessage, embeddedWalletSigner, true) : void 0;
3768
+ const encryptedToolCallEvents = Array.isArray(msg.toolCallEvents) ? await encryptToolCallEvents(
3769
+ msg.toolCallEvents,
3770
+ address,
3771
+ signMessage,
3772
+ embeddedWalletSigner
3773
+ ) : void 0;
3733
3774
  return {
3734
3775
  ...message,
3735
3776
  ...encryptedContent !== void 0 && { content: encryptedContent },
@@ -3737,13 +3778,31 @@ async function encryptMessageFields(message, address, signMessage, embeddedWalle
3737
3778
  ...encryptedVector !== void 0 && { vector: encryptedVector },
3738
3779
  ...encryptedChunks !== void 0 && { chunks: encryptedChunks },
3739
3780
  ...encryptedSources !== void 0 && { sources: encryptedSources },
3740
- ...encryptedThoughtProcess !== void 0 && { thoughtProcess: encryptedThoughtProcess }
3781
+ ...encryptedThoughtProcess !== void 0 && { thoughtProcess: encryptedThoughtProcess },
3782
+ ...encryptedToolCallEvents !== void 0 && { toolCallEvents: encryptedToolCallEvents }
3741
3783
  };
3742
3784
  } catch (error) {
3743
3785
  getLogger().warn("Failed to encrypt message fields:", error);
3744
3786
  throw error;
3745
3787
  }
3746
3788
  }
3789
+ function mapToolCallPayloads(events, transform) {
3790
+ return Promise.all(
3791
+ events.map(async (event) => {
3792
+ if (!event || typeof event !== "object") return event;
3793
+ const mapped = { ...event };
3794
+ if (typeof event.arguments === "string") mapped.arguments = await transform(event.arguments);
3795
+ if (typeof event.output === "string") mapped.output = await transform(event.output);
3796
+ return mapped;
3797
+ })
3798
+ );
3799
+ }
3800
+ function encryptToolCallEvents(events, address, signMessage, embeddedWalletSigner) {
3801
+ return mapToolCallPayloads(
3802
+ events,
3803
+ (value) => encryptField(value, address, signMessage, embeddedWalletSigner, true)
3804
+ );
3805
+ }
3747
3806
  async function decryptMaybeJsonFieldDetailed(value, address) {
3748
3807
  if (value === void 0) return { value: void 0, status: "plaintext" };
3749
3808
  if (value === null) return { value, status: "plaintext" };
@@ -3829,6 +3888,10 @@ async function assembleDecryptedMessage(message, address) {
3829
3888
  message.thoughtProcess,
3830
3889
  address
3831
3890
  );
3891
+ const toolCallEventsResult = await decryptMaybeJsonFieldDetailed(
3892
+ message.toolCallEvents,
3893
+ address
3894
+ );
3832
3895
  let decryptionStatus;
3833
3896
  let refreshProbe;
3834
3897
  const consider = (status, probe) => {
@@ -3847,6 +3910,14 @@ async function assembleDecryptedMessage(message, address) {
3847
3910
  if (thoughtProcessResult.probe) {
3848
3911
  consider(thoughtProcessResult.status, thoughtProcessResult.probe);
3849
3912
  }
3913
+ if (toolCallEventsResult.probe) {
3914
+ consider(toolCallEventsResult.status, toolCallEventsResult.probe);
3915
+ }
3916
+ const toolCallEvents = Array.isArray(toolCallEventsResult.value) ? await mapToolCallPayloads(toolCallEventsResult.value, async (value) => {
3917
+ const detailed = await decryptFieldDetailed(value, address);
3918
+ consider(detailed.status, isEncrypted(value) ? value : void 0);
3919
+ return detailed.value;
3920
+ }) : void 0;
3850
3921
  const statusForConsumer = decryptionStatus === "ok" || decryptionStatus === "plaintext" || !decryptionStatus ? void 0 : decryptionStatus;
3851
3922
  const result = {
3852
3923
  ...message,
@@ -3855,7 +3926,8 @@ async function assembleDecryptedMessage(message, address) {
3855
3926
  vector: vectorResult.value,
3856
3927
  chunks: chunksResult.value,
3857
3928
  sources: sourcesResult.value,
3858
- thoughtProcess: thoughtProcessResult.value
3929
+ thoughtProcess: thoughtProcessResult.value,
3930
+ toolCallEvents
3859
3931
  };
3860
3932
  if (statusForConsumer) {
3861
3933
  result.decryptionStatus = statusForConsumer;
@@ -4649,7 +4721,7 @@ async function updateMessageChunksOp(ctx, uniqueId, chunks3, embeddingModel) {
4649
4721
  });
4650
4722
  return messageToStored(message, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner);
4651
4723
  }
4652
- var STRIP_CHUNK_TEXT_PAGE_SIZE = 100;
4724
+ var RAW_REPAIR_PAGE_SIZE = 100;
4653
4725
  function chunksWithoutText(raw) {
4654
4726
  if (typeof raw !== "string") return null;
4655
4727
  let parsed;
@@ -4672,8 +4744,8 @@ function chunksWithoutText(raw) {
4672
4744
  async function stripLegacyChunkTextOp(ctx) {
4673
4745
  const candidateIds = await ctx.messagesCollection.query(import_watermelondb2.Q.where("chunks", import_watermelondb2.Q.like(`%${import_watermelondb2.Q.sanitizeLikeString('"text":')}%`))).fetchIds();
4674
4746
  let stripped = 0;
4675
- for (let start = 0; start < candidateIds.length; start += STRIP_CHUNK_TEXT_PAGE_SIZE) {
4676
- const pageIds = candidateIds.slice(start, start + STRIP_CHUNK_TEXT_PAGE_SIZE);
4747
+ for (let start = 0; start < candidateIds.length; start += RAW_REPAIR_PAGE_SIZE) {
4748
+ const pageIds = candidateIds.slice(start, start + RAW_REPAIR_PAGE_SIZE);
4677
4749
  const rows = await ctx.messagesCollection.query(import_watermelondb2.Q.where("id", import_watermelondb2.Q.oneOf(pageIds))).unsafeFetchRaw();
4678
4750
  const changedIds = rows.filter((row) => chunksWithoutText(row.chunks) !== null).map((row) => String(row.id));
4679
4751
  if (changedIds.length === 0) continue;
@@ -4697,6 +4769,88 @@ async function stripLegacyChunkTextOp(ctx) {
4697
4769
  }
4698
4770
  return stripped;
4699
4771
  }
4772
+ async function encryptedToolCallEventsColumn(raw, address, signMessage, embeddedWalletSigner) {
4773
+ if (typeof raw !== "string") return null;
4774
+ let parsed;
4775
+ try {
4776
+ parsed = JSON.parse(raw);
4777
+ } catch {
4778
+ return null;
4779
+ }
4780
+ if (!Array.isArray(parsed)) return null;
4781
+ const column = JSON.stringify(
4782
+ await encryptToolCallEvents(
4783
+ parsed,
4784
+ address,
4785
+ signMessage,
4786
+ embeddedWalletSigner
4787
+ )
4788
+ );
4789
+ return column === JSON.stringify(parsed) ? null : column;
4790
+ }
4791
+ async function encryptToolCallEventsOp(ctx) {
4792
+ const { walletAddress: address, signMessage, embeddedWalletSigner } = ctx;
4793
+ if (!address || !signMessage) return 0;
4794
+ if (!await requestEncryptionKey(address, signMessage, embeddedWalletSigner)) return 0;
4795
+ if (!hasEncryptionKey(address)) return 0;
4796
+ if (!await savedKeyOpensExistingData(ctx, address, signMessage, embeddedWalletSigner)) {
4797
+ return 0;
4798
+ }
4799
+ const candidateIds = await ctx.messagesCollection.query(
4800
+ import_watermelondb2.Q.where("tool_call_events", import_watermelondb2.Q.notEq(null)),
4801
+ import_watermelondb2.Q.where("tool_call_events", import_watermelondb2.Q.notIn(["", "[]"]))
4802
+ ).fetchIds();
4803
+ let encrypted = 0;
4804
+ for (let start = 0; start < candidateIds.length; start += RAW_REPAIR_PAGE_SIZE) {
4805
+ const pageIds = candidateIds.slice(start, start + RAW_REPAIR_PAGE_SIZE);
4806
+ const rows = await ctx.messagesCollection.query(import_watermelondb2.Q.where("id", import_watermelondb2.Q.oneOf(pageIds))).unsafeFetchRaw();
4807
+ const repairs = /* @__PURE__ */ new Map();
4808
+ for (const row of rows) {
4809
+ const column = await encryptedToolCallEventsColumn(
4810
+ row.tool_call_events,
4811
+ address,
4812
+ signMessage,
4813
+ embeddedWalletSigner
4814
+ );
4815
+ if (column !== null) repairs.set(String(row.id), { raw: row.tool_call_events, column });
4816
+ }
4817
+ if (repairs.size === 0) continue;
4818
+ await ctx.database.write(async () => {
4819
+ const messages = (await ctx.messagesCollection.query(import_watermelondb2.Q.where("id", import_watermelondb2.Q.oneOf([...repairs.keys()]))).fetch()).filter(
4820
+ (message) => message._raw.tool_call_events === repairs.get(message.id)?.raw
4821
+ );
4822
+ if (messages.length === 0) return;
4823
+ const conversationIds = [...new Set(messages.map((message) => message.conversationId))];
4824
+ const conversations = await ctx.conversationsCollection.query(import_watermelondb2.Q.where("conversation_id", import_watermelondb2.Q.oneOf(conversationIds))).fetch();
4825
+ await ctx.database.batch(
4826
+ ...messages.map(
4827
+ (message) => message.prepareUpdate((msg) => {
4828
+ msg._setRaw("tool_call_events", repairs.get(message.id).column);
4829
+ })
4830
+ ),
4831
+ ...conversations.map((conversation) => conversation.prepareUpdate(() => {
4832
+ }))
4833
+ );
4834
+ encrypted += messages.length;
4835
+ });
4836
+ }
4837
+ return encrypted;
4838
+ }
4839
+ async function savedKeyOpensExistingData(ctx, address, signMessage, embeddedWalletSigner) {
4840
+ const v3Probe = await findEncryptedContent(ctx, "enc:v3:%");
4841
+ if (v3Probe) {
4842
+ const { status } = await decryptFieldDetailed(v3Probe, address);
4843
+ if (status !== "auth_mismatch" && status !== "key_missing") return true;
4844
+ return refreshEncryptionKeyIfMatches(address, v3Probe, signMessage, embeddedWalletSigner);
4845
+ }
4846
+ const v2Probe = await findEncryptedContent(ctx, "enc:v2:%");
4847
+ if (!v2Probe) return true;
4848
+ return refreshEncryptionKeyIfMatches(address, v2Probe, signMessage, embeddedWalletSigner);
4849
+ }
4850
+ async function findEncryptedContent(ctx, pattern) {
4851
+ const [row] = await ctx.messagesCollection.query(import_watermelondb2.Q.where("content", import_watermelondb2.Q.like(pattern)), import_watermelondb2.Q.take(1)).unsafeFetchRaw();
4852
+ return row ? String(row.content) : null;
4853
+ }
4700
4854
  async function updateMessageErrorOp(ctx, uniqueId, error) {
4701
4855
  let message;
4702
4856
  try {
@@ -4843,6 +4997,95 @@ async function searchMessagesOp(ctx, queryVector, options) {
4843
4997
  })
4844
4998
  );
4845
4999
  }
5000
+ var CHUNK_VECTOR_BATCH_SIZE = 50;
5001
+ var CHUNK_SCAN_YIELD_BUDGET_MS = 12;
5002
+ var hasEmbeddingSql = `(coalesce("chunks", '') != '' or coalesce("vector", '') != '')`;
5003
+ function toChunkCandidateKey(raw) {
5004
+ return {
5005
+ id: raw.id,
5006
+ conversationId: String(raw.conversation_id),
5007
+ version: Number(raw.updated_at) || 0,
5008
+ embeddingModel: raw.embedding_model ?? null
5009
+ };
5010
+ }
5011
+ async function fetchRowsByIds(ctx, ids) {
5012
+ if (ids.length === 0) return [];
5013
+ return await ctx.messagesCollection.query(import_watermelondb2.Q.where("id", import_watermelondb2.Q.oneOf(ids))).unsafeFetchRaw();
5014
+ }
5015
+ async function projectedChunkRowSource(ctx, filter) {
5016
+ const clauses = [
5017
+ `"history"."_status" is not 'deleted'`,
5018
+ hasEmbeddingSql,
5019
+ `exists (select 1 from "conversations" where "conversations"."conversation_id" = "history"."conversation_id" and "conversations"."is_deleted" = 0 and "conversations"."_status" is not 'deleted')`
5020
+ ];
5021
+ const args = [];
5022
+ if (filter.conversationId !== void 0) {
5023
+ clauses.push(`"history"."conversation_id" = ?`);
5024
+ args.push(filter.conversationId);
5025
+ }
5026
+ if (filter.excludeConversationId !== void 0) {
5027
+ clauses.push(`"history"."conversation_id" != ?`);
5028
+ args.push(filter.excludeConversationId);
5029
+ }
5030
+ const keyRows = await ctx.messagesCollection.query(
5031
+ import_watermelondb2.Q.unsafeSqlQuery(
5032
+ `select "id", "conversation_id", "updated_at", "embedding_model" from "history" where ${clauses.join(" and ")}`,
5033
+ args
5034
+ )
5035
+ ).unsafeFetchRaw();
5036
+ return {
5037
+ keys: keyRows.map(toChunkCandidateKey),
5038
+ loadVectorRows: async (ids) => {
5039
+ if (ids.length === 0) return [];
5040
+ return await ctx.messagesCollection.query(
5041
+ import_watermelondb2.Q.unsafeSqlQuery(
5042
+ `select "id", "updated_at", "chunks", "vector" from "history" where "_status" is not 'deleted' and "id" in (${ids.map(() => "?").join(",")})`,
5043
+ ids
5044
+ )
5045
+ ).unsafeFetchRaw();
5046
+ },
5047
+ loadFullRows: (ids) => fetchRowsByIds(ctx, ids)
5048
+ };
5049
+ }
5050
+ async function fullScanChunkRowSource(ctx, filter) {
5051
+ const activeConversations = await ctx.conversationsCollection.query(import_watermelondb2.Q.where("is_deleted", false)).fetch();
5052
+ const activeConversationIds = new Set(
5053
+ activeConversations.map((c) => String(c._getRaw("conversation_id")))
5054
+ );
5055
+ const queryConditions = filter.conversationId !== void 0 ? [import_watermelondb2.Q.where("conversation_id", filter.conversationId)] : [];
5056
+ const rows = await ctx.messagesCollection.query(...queryConditions).unsafeFetchRaw();
5057
+ const rowsById = /* @__PURE__ */ new Map();
5058
+ for (const row of rows) {
5059
+ const conversationId = String(row.conversation_id);
5060
+ if (!activeConversationIds.has(conversationId)) continue;
5061
+ if (filter.excludeConversationId !== void 0 && conversationId === filter.excludeConversationId) {
5062
+ continue;
5063
+ }
5064
+ if (!row.chunks && !row.vector) continue;
5065
+ rowsById.set(row.id, row);
5066
+ }
5067
+ const pick = (ids) => Promise.resolve(
5068
+ ids.flatMap((id) => {
5069
+ const row = rowsById.get(id);
5070
+ return row ? [row] : [];
5071
+ })
5072
+ );
5073
+ return {
5074
+ keys: Array.from(rowsById.values(), toChunkCandidateKey),
5075
+ loadVectorRows: pick,
5076
+ loadFullRows: pick
5077
+ };
5078
+ }
5079
+ async function chunkRowSource(ctx, filter) {
5080
+ try {
5081
+ return await projectedChunkRowSource(ctx, filter);
5082
+ } catch (err) {
5083
+ getLogger().debug(
5084
+ "searchChunksOp: projected SQL unavailable, using full-load fallback: " + (err instanceof Error ? err.message : String(err))
5085
+ );
5086
+ return fullScanChunkRowSource(ctx, filter);
5087
+ }
5088
+ }
4846
5089
  async function searchChunksOp(ctx, queryVector, options) {
4847
5090
  const {
4848
5091
  limit = 10,
@@ -4850,83 +5093,111 @@ async function searchChunksOp(ctx, queryVector, options) {
4850
5093
  conversationId,
4851
5094
  excludeConversationId,
4852
5095
  embeddingModel,
4853
- chunkCache
5096
+ chunkCache,
5097
+ onStats
4854
5098
  } = options || {};
4855
- const activeConversations = await ctx.conversationsCollection.query(import_watermelondb2.Q.where("is_deleted", false)).fetch();
4856
- const activeConversationIds = new Set(
4857
- activeConversations.map((c) => String(c._getRaw("conversation_id")))
4858
- );
4859
- const queryConditions = conversationId ? [import_watermelondb2.Q.where("conversation_id", conversationId)] : [];
4860
- const messages = await ctx.messagesCollection.query(...queryConditions).unsafeFetchRaw();
5099
+ const source = await chunkRowSource(ctx, { conversationId, excludeConversationId });
5100
+ const yieldIfDue = createTimeSliceYielder(CHUNK_SCAN_YIELD_BUDGET_MS);
4861
5101
  const candidates = [];
4862
5102
  let staleSkipped = 0;
4863
5103
  let unreadableVectors = 0;
4864
- for (const message of messages) {
4865
- const msgConvId = String(message.conversation_id);
4866
- if (!activeConversationIds.has(msgConvId)) continue;
4867
- if (excludeConversationId !== void 0 && msgConvId === excludeConversationId) continue;
4868
- if (embeddingModel) {
4869
- const storedModel = message.embedding_model;
4870
- if (storedModel !== null && storedModel !== void 0 && storedModel !== embeddingModel) {
4871
- staleSkipped++;
4872
- continue;
4873
- }
5104
+ let cacheHits = 0;
5105
+ const missIds = [];
5106
+ const orderById = /* @__PURE__ */ new Map();
5107
+ for (const [order, key] of source.keys.entries()) {
5108
+ await yieldIfDue();
5109
+ orderById.set(key.id, order);
5110
+ if (embeddingModel && key.embeddingModel !== null && key.embeddingModel !== embeddingModel) {
5111
+ staleSkipped++;
5112
+ continue;
4874
5113
  }
4875
- const version = Number(message.updated_at) || 0;
4876
- const cached = chunkCache?.get(message.id);
4877
- if (cached && cached.version === version) {
4878
- if (cached.chunks.length > 0) {
4879
- for (let ci = 0; ci < cached.chunks.length; ci++) {
4880
- const vec = cached.chunks[ci];
4881
- if (vec.length === 0) continue;
4882
- const similarity = cosineSimilarity(queryVector, vec);
4883
- if (similarity >= minSimilarity) {
4884
- candidates.push({
4885
- message,
4886
- similarity,
4887
- chunkTextSource: { kind: "chunk-cached", chunkIndex: ci, version }
4888
- });
4889
- }
4890
- }
4891
- } else if (cached.fallback) {
4892
- const similarity = cosineSimilarity(queryVector, cached.fallback);
4893
- if (similarity >= minSimilarity) {
4894
- candidates.push({ message, similarity, chunkTextSource: { kind: "message" } });
4895
- }
4896
- }
5114
+ const cached = chunkCache?.get(key.id);
5115
+ if (!cached || cached.version !== key.version) {
5116
+ missIds.push(key.id);
4897
5117
  continue;
4898
5118
  }
4899
- const chunks3 = await readRawJsonField(message, "chunks", ctx.walletAddress);
4900
- if (chunks3 && chunks3.length > 0) {
4901
- const vectors = chunks3.map(
4902
- (chunk) => decodeChunkVector(chunk.vector, () => {
4903
- unreadableVectors++;
4904
- })
4905
- );
4906
- chunkCache?.set(message.id, { version, chunks: vectors });
4907
- for (let ci = 0; ci < chunks3.length; ci++) {
4908
- const vec = vectors[ci];
5119
+ cacheHits++;
5120
+ if (cached.chunks.length > 0) {
5121
+ for (let ci = 0; ci < cached.chunks.length; ci++) {
5122
+ const vec = cached.chunks[ci];
4909
5123
  if (vec.length === 0) continue;
4910
5124
  const similarity = cosineSimilarity(queryVector, vec);
4911
5125
  if (similarity >= minSimilarity) {
4912
5126
  candidates.push({
4913
- message,
5127
+ id: key.id,
5128
+ version: key.version,
5129
+ order,
4914
5130
  similarity,
4915
- chunkTextSource: { kind: "chunk", chunk: chunks3[ci], siblings: chunks3 }
5131
+ chunkTextSource: { kind: "chunk-cached", chunkIndex: ci, version: key.version }
4916
5132
  });
4917
5133
  }
4918
5134
  }
4919
- } else {
4920
- const messageVector = await readRawJsonField(message, "vector", ctx.walletAddress);
4921
- if (!messageVector || messageVector.length === 0) continue;
4922
- const fallback = Float32Array.from(messageVector);
4923
- chunkCache?.set(message.id, { version, chunks: [], fallback });
4924
- const similarity = cosineSimilarity(queryVector, fallback);
5135
+ } else if (cached.fallback) {
5136
+ const similarity = cosineSimilarity(queryVector, cached.fallback);
4925
5137
  if (similarity >= minSimilarity) {
4926
- candidates.push({ message, similarity, chunkTextSource: { kind: "message" } });
5138
+ candidates.push({
5139
+ id: key.id,
5140
+ version: key.version,
5141
+ order,
5142
+ similarity,
5143
+ chunkTextSource: { kind: "message" }
5144
+ });
5145
+ }
5146
+ }
5147
+ }
5148
+ let rowsFetched = 0;
5149
+ for (let offset = 0; offset < missIds.length; offset += CHUNK_VECTOR_BATCH_SIZE) {
5150
+ const rows = await source.loadVectorRows(
5151
+ missIds.slice(offset, offset + CHUNK_VECTOR_BATCH_SIZE)
5152
+ );
5153
+ rowsFetched += rows.length;
5154
+ for (const row of rows) {
5155
+ await yieldIfDue();
5156
+ const id = row.id;
5157
+ const version = Number(row.updated_at) || 0;
5158
+ const order = orderById.get(id) ?? Number.MAX_SAFE_INTEGER;
5159
+ const chunks3 = await readRawJsonField(row, "chunks", ctx.walletAddress);
5160
+ if (chunks3 && chunks3.length > 0) {
5161
+ const vectors = chunks3.map(
5162
+ (chunk) => decodeChunkVector(chunk.vector, () => {
5163
+ unreadableVectors++;
5164
+ })
5165
+ );
5166
+ chunkCache?.set(id, { version, chunks: vectors });
5167
+ for (let ci = 0; ci < chunks3.length; ci++) {
5168
+ const vec = vectors[ci];
5169
+ if (vec.length === 0) continue;
5170
+ const similarity = cosineSimilarity(queryVector, vec);
5171
+ if (similarity >= minSimilarity) {
5172
+ candidates.push({
5173
+ id,
5174
+ version,
5175
+ order,
5176
+ similarity,
5177
+ chunkTextSource: { kind: "chunk", chunk: chunks3[ci], siblings: chunks3 }
5178
+ });
5179
+ }
5180
+ }
5181
+ } else {
5182
+ const messageVector = await readRawJsonField(row, "vector", ctx.walletAddress);
5183
+ if (messageVector && messageVector.length > 0) {
5184
+ const fallback = Float32Array.from(messageVector);
5185
+ chunkCache?.set(id, { version, chunks: [], fallback });
5186
+ const similarity = cosineSimilarity(queryVector, fallback);
5187
+ if (similarity >= minSimilarity) {
5188
+ candidates.push({
5189
+ id,
5190
+ version,
5191
+ order,
5192
+ similarity,
5193
+ chunkTextSource: { kind: "message" }
5194
+ });
5195
+ }
5196
+ }
4927
5197
  }
4928
5198
  }
4929
5199
  }
5200
+ onStats?.({ rowsScanned: source.keys.length, rowsFetched, cacheHits });
4930
5201
  if (staleSkipped > 0) {
4931
5202
  getLogger().warn(
4932
5203
  `searchChunksOp: skipped ${staleSkipped} messages whose embedding model differs from the current model (${embeddingModel}) \u2014 re-embed via chunkAndEmbedAllMessages`
@@ -4937,12 +5208,17 @@ async function searchChunksOp(ctx, queryVector, options) {
4937
5208
  `searchChunksOp: could not read ${unreadableVectors} chunk vectors and left them out of ranking \u2014 the stored value is corrupt, so those chunks stay unsearchable until the message is re-embedded via chunkAndEmbedAllMessages`
4938
5209
  );
4939
5210
  }
4940
- const topK = candidates.sort((a, b) => b.similarity - a.similarity).slice(0, limit);
4941
- const uniqueMessages = /* @__PURE__ */ new Map();
4942
- for (const c of topK) uniqueMessages.set(c.message.id, c.message);
5211
+ const topK = candidates.sort((a, b) => b.similarity - a.similarity || a.order - b.order).slice(0, limit);
5212
+ const topIds = Array.from(new Set(topK.map((c) => c.id)));
5213
+ const fullRows = await source.loadFullRows(topIds);
5214
+ const rowById = new Map(fullRows.map((row) => [row.id, row]));
5215
+ const liveTopK = topK.filter((c) => {
5216
+ const row = rowById.get(c.id);
5217
+ return row !== void 0 && (Number(row.updated_at) || 0) === c.version;
5218
+ });
4943
5219
  const storedById = new Map(
4944
5220
  await Promise.all(
4945
- Array.from(uniqueMessages, async ([id, m]) => {
5221
+ Array.from(rowById, async ([id, m]) => {
4946
5222
  const stored2 = await messageRawToStored(
4947
5223
  m,
4948
5224
  ctx.walletAddress,
@@ -4955,24 +5231,24 @@ async function searchChunksOp(ctx, queryVector, options) {
4955
5231
  );
4956
5232
  const chunkStateByMsgId = /* @__PURE__ */ new Map();
4957
5233
  const needsChunkText = /* @__PURE__ */ new Set();
4958
- for (const c of topK) {
4959
- if (c.chunkTextSource.kind === "chunk-cached") needsChunkText.add(c.message.id);
5234
+ for (const c of liveTopK) {
5235
+ if (c.chunkTextSource.kind === "chunk-cached") needsChunkText.add(c.id);
4960
5236
  }
4961
5237
  await Promise.all(
4962
5238
  Array.from(needsChunkText, async (id) => {
4963
- const m = uniqueMessages.get(id);
5239
+ const m = rowById.get(id);
4964
5240
  const parsed = await readRawJsonField(m, "chunks", ctx.walletAddress);
4965
5241
  const version = Number(m.updated_at) || 0;
4966
5242
  if (parsed) chunkStateByMsgId.set(id, { version, chunks: parsed });
4967
5243
  })
4968
5244
  );
4969
- return topK.map(({ message, similarity, chunkTextSource }) => {
4970
- const stored2 = { ...storedById.get(message.id) };
5245
+ return liveTopK.map(({ id, similarity, chunkTextSource }) => {
5246
+ const stored2 = { ...storedById.get(id) };
4971
5247
  let chunkText2;
4972
5248
  if (chunkTextSource.kind === "chunk") {
4973
5249
  chunkText2 = resolveChunkText(stored2.content, chunkTextSource.chunk, chunkTextSource.siblings);
4974
5250
  } else if (chunkTextSource.kind === "chunk-cached") {
4975
- const state = chunkStateByMsgId.get(message.id);
5251
+ const state = chunkStateByMsgId.get(id);
4976
5252
  const chunk = state && state.version === chunkTextSource.version ? state.chunks[chunkTextSource.chunkIndex] : void 0;
4977
5253
  chunkText2 = resolveChunkText(stored2.content, chunk, state?.chunks);
4978
5254
  } else {
@@ -6132,13 +6408,35 @@ async function generateEmbedding(text14, options) {
6132
6408
  if (total === void 0) return embedOne(text14, options, void 0);
6133
6409
  return withDeadline((signal) => embedOne(text14, options, signal), total, "embedding");
6134
6410
  }
6411
+ var inFlightByCache = /* @__PURE__ */ new WeakMap();
6135
6412
  async function embedOne(text14, options, outer) {
6136
- const { baseUrl = BASE_URL, model, cache } = options;
6137
- const timeoutMs = options.timeoutMs ?? DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS;
6138
- if (cache) {
6139
- const cached = cache.get(text14);
6140
- if (cached) return Array.from(cached);
6413
+ const { cache } = options;
6414
+ if (!cache) return Array.from(await requestEmbedding(text14, options, outer));
6415
+ const cached = cache.get(text14);
6416
+ if (cached) return Array.from(cached);
6417
+ const inFlight = inFlightByCache.get(cache) ?? /* @__PURE__ */ new Map();
6418
+ inFlightByCache.set(cache, inFlight);
6419
+ const pending = inFlight.get(text14);
6420
+ if (pending) {
6421
+ try {
6422
+ return Array.from(await pending);
6423
+ } catch (err) {
6424
+ if (outer?.aborted) throw err;
6425
+ }
6141
6426
  }
6427
+ const request = requestEmbedding(text14, options, outer);
6428
+ inFlight.set(text14, request);
6429
+ try {
6430
+ const embedding2 = await request;
6431
+ cache.set(text14, embedding2);
6432
+ return Array.from(embedding2);
6433
+ } finally {
6434
+ if (inFlight.get(text14) === request) inFlight.delete(text14);
6435
+ }
6436
+ }
6437
+ async function requestEmbedding(text14, options, outer) {
6438
+ const { baseUrl = BASE_URL, model } = options;
6439
+ const timeoutMs = options.timeoutMs ?? DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS;
6142
6440
  const headers = {
6143
6441
  ...await resolveAuthHeaders(options, outer),
6144
6442
  ...taskTypeHeader(options.taskType)
@@ -6173,11 +6471,7 @@ async function embedOne(text14, options, outer) {
6173
6471
  totalTokens: response.data.usage.total_tokens ?? 0
6174
6472
  });
6175
6473
  }
6176
- const f32Embedding = Float32Array.from(embedding2);
6177
- if (cache) {
6178
- cache.set(text14, f32Embedding);
6179
- }
6180
- return Array.from(f32Embedding);
6474
+ return Float32Array.from(embedding2);
6181
6475
  }
6182
6476
  var DEFAULT_EMBEDDING_BATCH_SIZE = 100;
6183
6477
  var DEFAULT_EMBEDDING_BATCH_CONCURRENCY = 3;
@@ -6575,7 +6869,8 @@ function extractReservationReceipts(toolCallEvents) {
6575
6869
  for (const event of toolCallEvents ?? []) {
6576
6870
  const action = ACTIONS.find((a) => event.name?.endsWith(a.tool));
6577
6871
  const output = event.output ?? "";
6578
- if (!action || !output || NOT_AN_ATTEMPT.test(output)) continue;
6872
+ if (!action || !output || hasEncryptedToolPayload(event) || NOT_AN_ATTEMPT.test(output))
6873
+ continue;
6579
6874
  const kept = latest.get(action.kind)?.status;
6580
6875
  if (kept === "made") continue;
6581
6876
  const receipt = toReceipt(action, event.arguments ?? "", output);
@@ -17493,9 +17788,9 @@ function recencyMultiplier(updatedAt, opts = {}) {
17493
17788
  const updatedMs = updatedAt.getTime();
17494
17789
  if (!Number.isFinite(updatedMs)) return noDate;
17495
17790
  const now = opts.now ?? /* @__PURE__ */ new Date();
17496
- const nowMs3 = now.getTime();
17497
- if (!Number.isFinite(nowMs3)) return noDate;
17498
- const ageDays2 = Math.max(0, (nowMs3 - updatedMs) / DAY_MS2);
17791
+ const nowMs2 = now.getTime();
17792
+ if (!Number.isFinite(nowMs2)) return noDate;
17793
+ const ageDays2 = Math.max(0, (nowMs2 - updatedMs) / DAY_MS2);
17499
17794
  const perYearDecay = opts.perYearDecay ?? DEFAULT_PER_YEAR_DECAY;
17500
17795
  const floor = opts.floor ?? DEFAULT_FLOOR;
17501
17796
  const decay = perYearDecay * (ageDays2 / 365);
@@ -18007,9 +18302,6 @@ function createVaultEmbeddingCache(maxSize = DEFAULT_VAULT_CACHE_SIZE) {
18007
18302
 
18008
18303
  // src/lib/memoryVault/searchTool.ts
18009
18304
  var DEFAULT_RERANK_TOP_N = 5;
18010
- function nowMs() {
18011
- return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
18012
- }
18013
18305
  var warnedProgrammaticDecomposeIgnored = false;
18014
18306
  function normalizeEventTimeKind(kind) {
18015
18307
  return kind === "point" || kind === "range" || kind === "ongoing" ? kind : null;
@@ -20911,6 +21203,32 @@ function rankProfileCandidates(inputs, now = Date.now(), options = {}) {
20911
21203
  return ranked;
20912
21204
  }
20913
21205
 
21206
+ // src/lib/db/chat/embeddingCache.ts
21207
+ var MaskScopedEmbeddingCache = class extends Map {
21208
+ constructor(inner, masked) {
21209
+ super();
21210
+ this.inner = inner;
21211
+ this.masked = masked;
21212
+ }
21213
+ key(text14) {
21214
+ return `${this.masked ? "m" : "r"}:${text14}`;
21215
+ }
21216
+ get(text14) {
21217
+ return this.inner.get(this.key(text14));
21218
+ }
21219
+ set(text14, value) {
21220
+ this.inner.set(this.key(text14), value);
21221
+ return this;
21222
+ }
21223
+ };
21224
+ var scopedViews = /* @__PURE__ */ new WeakMap();
21225
+ function maskScopedEmbeddingCache(cache, masked) {
21226
+ const views = scopedViews.get(cache) ?? {};
21227
+ scopedViews.set(cache, views);
21228
+ const slot = masked ? "masked" : "raw";
21229
+ return views[slot] ?? (views[slot] = new MaskScopedEmbeddingCache(cache, masked));
21230
+ }
21231
+
20914
21232
  // src/lib/memory/recall.ts
20915
21233
  init_logger();
20916
21234
 
@@ -21121,9 +21439,6 @@ function scoreEventTimeOverlap(memoryStart, memoryEnd, memoryKind, window2) {
21121
21439
  var DEFAULT_LIMIT = 8;
21122
21440
  var DEFAULT_BUDGET = "low";
21123
21441
  var DEFAULT_FACT_MIN_SCORE = 0.1;
21124
- function nowMs2() {
21125
- return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
21126
- }
21127
21442
  var DEFAULT_CHUNK_MIN_SCORE = 0.5;
21128
21443
  var DEFAULT_QUERY_EMBED_TOTAL_TIMEOUT_MS = 8e3;
21129
21444
  function flagsForBudget(budget) {
@@ -21161,12 +21476,19 @@ async function recall(query, ctx, options = {}) {
21161
21476
  const subQueries = normalizedFacets.length >= 2 ? normalizedFacets : void 0;
21162
21477
  const embeddingOptions = {
21163
21478
  ...ctx.embeddingOptions,
21164
- taskType: "memory_recall_embed"
21479
+ taskType: "memory_recall_embed",
21480
+ ...options.embeddingCache && !ctx.embeddingOptions.cache && {
21481
+ cache: maskScopedEmbeddingCache(
21482
+ options.embeddingCache,
21483
+ Boolean(ctx.embeddingOptions.maskInput)
21484
+ )
21485
+ }
21165
21486
  };
21166
- const t0 = nowMs2();
21487
+ const t0 = nowMs();
21167
21488
  let prepMs = 0;
21168
21489
  let factLaneMs = 0;
21169
21490
  let chunkLaneMs = 0;
21491
+ let chunkStats;
21170
21492
  let fuseMs = 0;
21171
21493
  const factResults = [];
21172
21494
  const chunkResults = [];
@@ -21221,6 +21543,11 @@ async function recall(query, ctx, options = {}) {
21221
21543
  ...decryptLastRan !== void 0 && { decryptLast: decryptLastRan },
21222
21544
  ...vaultRowsDecrypted !== void 0 && { vaultRowsDecrypted },
21223
21545
  ...vaultRowsEmbedded !== void 0 && { vaultRowsEmbedded },
21546
+ ...chunkStats && {
21547
+ chunkRowsScanned: chunkStats.rowsScanned,
21548
+ chunkRowsFetched: chunkStats.rowsFetched,
21549
+ chunkCacheHits: chunkStats.cacheHits
21550
+ },
21224
21551
  factCount: factResults.length,
21225
21552
  chunkCount: chunkResults.length,
21226
21553
  admittedCount: admitted.length,
@@ -21232,7 +21559,7 @@ async function recall(query, ctx, options = {}) {
21232
21559
  temporalLaneCount,
21233
21560
  emptyReason: emptyReasonFor(admitted.length, laneRan),
21234
21561
  timings: {
21235
- total: nowMs2() - t0,
21562
+ total: nowMs() - t0,
21236
21563
  prep: prepMs,
21237
21564
  factLane: factLaneMs,
21238
21565
  rerank: rerankMs,
@@ -21254,14 +21581,14 @@ async function recall(query, ctx, options = {}) {
21254
21581
  const needsChunkEmbedding = types.includes("chunk") && ctx.storageCtx;
21255
21582
  const wantsTemporal = types.includes("fact") && (ctx.factSource || ctx.vaultCtx);
21256
21583
  const queryEmbedTotalTimeoutMs = options.queryEmbedTotalTimeoutMs ?? DEFAULT_QUERY_EMBED_TOTAL_TIMEOUT_MS;
21257
- const prepStart = nowMs2();
21584
+ const prepStart = nowMs();
21258
21585
  let sharedEmbedMs = 0;
21259
21586
  const [queryEmbedding, entityRanking, temporalRanking] = await Promise.all([
21260
21587
  needsChunkEmbedding ? generateEmbedding(query, {
21261
21588
  ...embeddingOptions,
21262
21589
  totalTimeoutMs: queryEmbedTotalTimeoutMs
21263
21590
  }).finally(() => {
21264
- sharedEmbedMs = nowMs2() - prepStart;
21591
+ sharedEmbedMs = nowMs() - prepStart;
21265
21592
  }).then((vec) => {
21266
21593
  if (vec.length === 0) {
21267
21594
  getLogger().warn(
@@ -21294,11 +21621,11 @@ async function recall(query, ctx, options = {}) {
21294
21621
  () => ctx.factSource ? ctx.factSource.temporalRanking(query, options.now) : buildTemporalLaneRanking(query, ctx.vaultCtx, options.now)
21295
21622
  ) : Promise.resolve([])
21296
21623
  ]);
21297
- prepMs = nowMs2() - prepStart;
21624
+ prepMs = nowMs() - prepStart;
21298
21625
  graphLaneCount = entityRanking.length;
21299
21626
  temporalLaneCount = temporalRanking.length;
21300
21627
  if (types.includes("fact") && (ctx.factSource || ctx.vaultCtx && ctx.vaultCache)) {
21301
- const factStart = nowMs2();
21628
+ const factStart = nowMs();
21302
21629
  const vaultMinScore = options.minScore ?? DEFAULT_FACT_MIN_SCORE;
21303
21630
  factFloor = vaultMinScore;
21304
21631
  const {
@@ -21374,11 +21701,11 @@ async function recall(query, ctx, options = {}) {
21374
21701
  factLaneRankedOnCosine = factRankedOnCosine;
21375
21702
  decryptLastRan = factDecryptLast;
21376
21703
  vaultRowsDecrypted = factRowsDecrypted;
21377
- factLaneMs = nowMs2() - factStart;
21704
+ factLaneMs = nowMs() - factStart;
21378
21705
  }
21379
21706
  if (chunkEmbedFailed && !factLaneRankedOnCosine) embeddingsUnavailable = true;
21380
21707
  if (types.includes("chunk") && ctx.storageCtx && queryEmbedding) {
21381
- const chunkStart = nowMs2();
21708
+ const chunkStart = nowMs();
21382
21709
  const chunkMinScore = options.minScore ?? DEFAULT_CHUNK_MIN_SCORE;
21383
21710
  chunkFloor = chunkMinScore;
21384
21711
  const results = await searchChunksOp(ctx.storageCtx, queryEmbedding, {
@@ -21389,12 +21716,15 @@ async function recall(query, ctx, options = {}) {
21389
21716
  ...options.excludeConversationId && {
21390
21717
  excludeConversationId: options.excludeConversationId
21391
21718
  },
21392
- ...ctx.chunkCache && { chunkCache: ctx.chunkCache }
21719
+ ...ctx.chunkCache && { chunkCache: ctx.chunkCache },
21720
+ onStats: (stats) => {
21721
+ chunkStats = stats;
21722
+ }
21393
21723
  });
21394
21724
  chunkResults.push(...dedupeBy(results, (r) => r.chunkText.trim()));
21395
- chunkLaneMs = nowMs2() - chunkStart;
21725
+ chunkLaneMs = nowMs() - chunkStart;
21396
21726
  }
21397
- const fuseStart = nowMs2();
21727
+ const fuseStart = nowMs();
21398
21728
  if (types.length === 1 || factResults.length === 0 || chunkResults.length === 0) {
21399
21729
  const memories2 = [
21400
21730
  ...factResults.map((r) => toFactMemory(r, options.now)),
@@ -21402,7 +21732,7 @@ async function recall(query, ctx, options = {}) {
21402
21732
  ];
21403
21733
  memories2.sort((a, b) => b.score - a.score);
21404
21734
  const candidateCount = factResults.length + chunkResults.length;
21405
- fuseMs = nowMs2() - fuseStart;
21735
+ fuseMs = nowMs() - fuseStart;
21406
21736
  const admitted = memories2.slice(0, limit);
21407
21737
  hitLimit = memories2.length > limit;
21408
21738
  emitDiagnostics(candidateCount, admitted);
@@ -21466,7 +21796,7 @@ async function recall(query, ctx, options = {}) {
21466
21796
  memories = selection.out;
21467
21797
  }
21468
21798
  hitLimit = selection.cut;
21469
- fuseMs = nowMs2() - fuseStart;
21799
+ fuseMs = nowMs() - fuseStart;
21470
21800
  emitDiagnostics(byId.size, memories);
21471
21801
  return {
21472
21802
  memories,
@@ -31486,28 +31816,6 @@ async function cleanupConversationSummary(database, conversationId) {
31486
31816
  }
31487
31817
  }
31488
31818
 
31489
- // src/lib/db/chat/embeddingCache.ts
31490
- var MaskScopedEmbeddingCache = class extends Map {
31491
- constructor(inner, masked) {
31492
- super();
31493
- this.inner = inner;
31494
- this.masked = masked;
31495
- }
31496
- key(text14) {
31497
- return `${this.masked ? "m" : "r"}:${text14}`;
31498
- }
31499
- get(text14) {
31500
- return this.inner.get(this.key(text14));
31501
- }
31502
- set(text14, value) {
31503
- this.inner.set(this.key(text14), value);
31504
- return this;
31505
- }
31506
- };
31507
- function maskScopedEmbeddingCache(cache, masked) {
31508
- return new MaskScopedEmbeddingCache(cache, masked);
31509
- }
31510
-
31511
31819
  // src/react/useChatStorage.ts
31512
31820
  init_logger();
31513
31821
  init_redactor();
@@ -32041,11 +32349,12 @@ async function storedToLlmapiMessage(stored2, encryptionKey, resolveMediaByIds)
32041
32349
  textContent = textContent.replace(/\n{3,}/g, "\n\n").trim();
32042
32350
  const content = [{ type: "text", text: textContent }, ...imageParts];
32043
32351
  const messages = [];
32044
- if (stored2.role === "assistant" && stored2.toolCallEvents?.length) {
32352
+ const toolCallEvents = Array.isArray(stored2.toolCallEvents) ? stored2.toolCallEvents.filter((event) => !hasEncryptedToolPayload(event)) : [];
32353
+ if (stored2.role === "assistant" && toolCallEvents.length) {
32045
32354
  messages.push({
32046
32355
  role: stored2.role,
32047
32356
  content: void 0,
32048
- tool_calls: stored2.toolCallEvents.map((event) => ({
32357
+ tool_calls: toolCallEvents.map((event) => ({
32049
32358
  id: event.id,
32050
32359
  type: "function",
32051
32360
  function: {
@@ -32054,7 +32363,7 @@ async function storedToLlmapiMessage(stored2, encryptionKey, resolveMediaByIds)
32054
32363
  }
32055
32364
  }))
32056
32365
  });
32057
- for (const event of stored2.toolCallEvents) {
32366
+ for (const event of toolCallEvents) {
32058
32367
  if (event.id && event.output !== void 0 && event.output !== null) {
32059
32368
  messages.push({
32060
32369
  role: "tool",
@@ -37042,6 +37351,7 @@ function useWalletBinding(options = {}) {
37042
37351
  encryptDataBatch,
37043
37352
  encryptDataBytes,
37044
37353
  encryptDataWithKey,
37354
+ encryptToolCallEventsOp,
37045
37355
  ensureDefaultFoldersOp,
37046
37356
  expandToolSetsAdditive,
37047
37357
  exportElementToPdf,