@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.
@@ -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) {
@@ -16162,6 +16191,12 @@ async function encryptMessageFields(message, address, signMessage, embeddedWalle
16162
16191
  const encryptedChunks = msg.chunks ? await encryptJsonField(msg.chunks, address, signMessage, embeddedWalletSigner, true) : void 0;
16163
16192
  const encryptedSources = msg.sources ? await encryptJsonField(msg.sources, address, signMessage, embeddedWalletSigner, true) : void 0;
16164
16193
  const encryptedThoughtProcess = msg.thoughtProcess ? await encryptJsonField(msg.thoughtProcess, address, signMessage, embeddedWalletSigner, true) : void 0;
16194
+ const encryptedToolCallEvents = Array.isArray(msg.toolCallEvents) ? await encryptToolCallEvents(
16195
+ msg.toolCallEvents,
16196
+ address,
16197
+ signMessage,
16198
+ embeddedWalletSigner
16199
+ ) : void 0;
16165
16200
  return {
16166
16201
  ...message,
16167
16202
  ...encryptedContent !== void 0 && { content: encryptedContent },
@@ -16169,13 +16204,31 @@ async function encryptMessageFields(message, address, signMessage, embeddedWalle
16169
16204
  ...encryptedVector !== void 0 && { vector: encryptedVector },
16170
16205
  ...encryptedChunks !== void 0 && { chunks: encryptedChunks },
16171
16206
  ...encryptedSources !== void 0 && { sources: encryptedSources },
16172
- ...encryptedThoughtProcess !== void 0 && { thoughtProcess: encryptedThoughtProcess }
16207
+ ...encryptedThoughtProcess !== void 0 && { thoughtProcess: encryptedThoughtProcess },
16208
+ ...encryptedToolCallEvents !== void 0 && { toolCallEvents: encryptedToolCallEvents }
16173
16209
  };
16174
16210
  } catch (error) {
16175
16211
  getLogger().warn("Failed to encrypt message fields:", error);
16176
16212
  throw error;
16177
16213
  }
16178
16214
  }
16215
+ function mapToolCallPayloads(events, transform) {
16216
+ return Promise.all(
16217
+ events.map(async (event) => {
16218
+ if (!event || typeof event !== "object") return event;
16219
+ const mapped = { ...event };
16220
+ if (typeof event.arguments === "string") mapped.arguments = await transform(event.arguments);
16221
+ if (typeof event.output === "string") mapped.output = await transform(event.output);
16222
+ return mapped;
16223
+ })
16224
+ );
16225
+ }
16226
+ function encryptToolCallEvents(events, address, signMessage, embeddedWalletSigner) {
16227
+ return mapToolCallPayloads(
16228
+ events,
16229
+ (value) => encryptField(value, address, signMessage, embeddedWalletSigner, true)
16230
+ );
16231
+ }
16179
16232
  async function decryptMaybeJsonFieldDetailed(value, address) {
16180
16233
  if (value === void 0) return { value: void 0, status: "plaintext" };
16181
16234
  if (value === null) return { value, status: "plaintext" };
@@ -16261,6 +16314,10 @@ async function assembleDecryptedMessage(message, address) {
16261
16314
  message.thoughtProcess,
16262
16315
  address
16263
16316
  );
16317
+ const toolCallEventsResult = await decryptMaybeJsonFieldDetailed(
16318
+ message.toolCallEvents,
16319
+ address
16320
+ );
16264
16321
  let decryptionStatus;
16265
16322
  let refreshProbe;
16266
16323
  const consider = (status, probe) => {
@@ -16279,6 +16336,14 @@ async function assembleDecryptedMessage(message, address) {
16279
16336
  if (thoughtProcessResult.probe) {
16280
16337
  consider(thoughtProcessResult.status, thoughtProcessResult.probe);
16281
16338
  }
16339
+ if (toolCallEventsResult.probe) {
16340
+ consider(toolCallEventsResult.status, toolCallEventsResult.probe);
16341
+ }
16342
+ const toolCallEvents = Array.isArray(toolCallEventsResult.value) ? await mapToolCallPayloads(toolCallEventsResult.value, async (value) => {
16343
+ const detailed = await decryptFieldDetailed(value, address);
16344
+ consider(detailed.status, isEncrypted(value) ? value : void 0);
16345
+ return detailed.value;
16346
+ }) : void 0;
16282
16347
  const statusForConsumer = decryptionStatus === "ok" || decryptionStatus === "plaintext" || !decryptionStatus ? void 0 : decryptionStatus;
16283
16348
  const result = {
16284
16349
  ...message,
@@ -16287,7 +16352,8 @@ async function assembleDecryptedMessage(message, address) {
16287
16352
  vector: vectorResult.value,
16288
16353
  chunks: chunksResult.value,
16289
16354
  sources: sourcesResult.value,
16290
- thoughtProcess: thoughtProcessResult.value
16355
+ thoughtProcess: thoughtProcessResult.value,
16356
+ toolCallEvents
16291
16357
  };
16292
16358
  if (statusForConsumer) {
16293
16359
  result.decryptionStatus = statusForConsumer;
@@ -16853,7 +16919,7 @@ async function updateMessageChunksOp(ctx, uniqueId, chunks, embeddingModel) {
16853
16919
  });
16854
16920
  return messageToStored(message, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner);
16855
16921
  }
16856
- var STRIP_CHUNK_TEXT_PAGE_SIZE = 100;
16922
+ var RAW_REPAIR_PAGE_SIZE = 100;
16857
16923
  function chunksWithoutText(raw) {
16858
16924
  if (typeof raw !== "string") return null;
16859
16925
  let parsed;
@@ -16876,8 +16942,8 @@ function chunksWithoutText(raw) {
16876
16942
  async function stripLegacyChunkTextOp(ctx) {
16877
16943
  const candidateIds = await ctx.messagesCollection.query(import_watermelondb2.Q.where("chunks", import_watermelondb2.Q.like(`%${import_watermelondb2.Q.sanitizeLikeString('"text":')}%`))).fetchIds();
16878
16944
  let stripped = 0;
16879
- for (let start = 0; start < candidateIds.length; start += STRIP_CHUNK_TEXT_PAGE_SIZE) {
16880
- const pageIds = candidateIds.slice(start, start + STRIP_CHUNK_TEXT_PAGE_SIZE);
16945
+ for (let start = 0; start < candidateIds.length; start += RAW_REPAIR_PAGE_SIZE) {
16946
+ const pageIds = candidateIds.slice(start, start + RAW_REPAIR_PAGE_SIZE);
16881
16947
  const rows = await ctx.messagesCollection.query(import_watermelondb2.Q.where("id", import_watermelondb2.Q.oneOf(pageIds))).unsafeFetchRaw();
16882
16948
  const changedIds = rows.filter((row) => chunksWithoutText(row.chunks) !== null).map((row) => String(row.id));
16883
16949
  if (changedIds.length === 0) continue;
@@ -16901,6 +16967,88 @@ async function stripLegacyChunkTextOp(ctx) {
16901
16967
  }
16902
16968
  return stripped;
16903
16969
  }
16970
+ async function encryptedToolCallEventsColumn(raw, address, signMessage, embeddedWalletSigner) {
16971
+ if (typeof raw !== "string") return null;
16972
+ let parsed;
16973
+ try {
16974
+ parsed = JSON.parse(raw);
16975
+ } catch {
16976
+ return null;
16977
+ }
16978
+ if (!Array.isArray(parsed)) return null;
16979
+ const column = JSON.stringify(
16980
+ await encryptToolCallEvents(
16981
+ parsed,
16982
+ address,
16983
+ signMessage,
16984
+ embeddedWalletSigner
16985
+ )
16986
+ );
16987
+ return column === JSON.stringify(parsed) ? null : column;
16988
+ }
16989
+ async function encryptToolCallEventsOp(ctx) {
16990
+ const { walletAddress: address, signMessage, embeddedWalletSigner } = ctx;
16991
+ if (!address || !signMessage) return 0;
16992
+ if (!await requestEncryptionKey(address, signMessage, embeddedWalletSigner)) return 0;
16993
+ if (!hasEncryptionKey(address)) return 0;
16994
+ if (!await savedKeyOpensExistingData(ctx, address, signMessage, embeddedWalletSigner)) {
16995
+ return 0;
16996
+ }
16997
+ const candidateIds = await ctx.messagesCollection.query(
16998
+ import_watermelondb2.Q.where("tool_call_events", import_watermelondb2.Q.notEq(null)),
16999
+ import_watermelondb2.Q.where("tool_call_events", import_watermelondb2.Q.notIn(["", "[]"]))
17000
+ ).fetchIds();
17001
+ let encrypted = 0;
17002
+ for (let start = 0; start < candidateIds.length; start += RAW_REPAIR_PAGE_SIZE) {
17003
+ const pageIds = candidateIds.slice(start, start + RAW_REPAIR_PAGE_SIZE);
17004
+ const rows = await ctx.messagesCollection.query(import_watermelondb2.Q.where("id", import_watermelondb2.Q.oneOf(pageIds))).unsafeFetchRaw();
17005
+ const repairs = /* @__PURE__ */ new Map();
17006
+ for (const row of rows) {
17007
+ const column = await encryptedToolCallEventsColumn(
17008
+ row.tool_call_events,
17009
+ address,
17010
+ signMessage,
17011
+ embeddedWalletSigner
17012
+ );
17013
+ if (column !== null) repairs.set(String(row.id), { raw: row.tool_call_events, column });
17014
+ }
17015
+ if (repairs.size === 0) continue;
17016
+ await ctx.database.write(async () => {
17017
+ const messages = (await ctx.messagesCollection.query(import_watermelondb2.Q.where("id", import_watermelondb2.Q.oneOf([...repairs.keys()]))).fetch()).filter(
17018
+ (message) => message._raw.tool_call_events === repairs.get(message.id)?.raw
17019
+ );
17020
+ if (messages.length === 0) return;
17021
+ const conversationIds = [...new Set(messages.map((message) => message.conversationId))];
17022
+ const conversations = await ctx.conversationsCollection.query(import_watermelondb2.Q.where("conversation_id", import_watermelondb2.Q.oneOf(conversationIds))).fetch();
17023
+ await ctx.database.batch(
17024
+ ...messages.map(
17025
+ (message) => message.prepareUpdate((msg) => {
17026
+ msg._setRaw("tool_call_events", repairs.get(message.id).column);
17027
+ })
17028
+ ),
17029
+ ...conversations.map((conversation) => conversation.prepareUpdate(() => {
17030
+ }))
17031
+ );
17032
+ encrypted += messages.length;
17033
+ });
17034
+ }
17035
+ return encrypted;
17036
+ }
17037
+ async function savedKeyOpensExistingData(ctx, address, signMessage, embeddedWalletSigner) {
17038
+ const v3Probe = await findEncryptedContent(ctx, "enc:v3:%");
17039
+ if (v3Probe) {
17040
+ const { status } = await decryptFieldDetailed(v3Probe, address);
17041
+ if (status !== "auth_mismatch" && status !== "key_missing") return true;
17042
+ return refreshEncryptionKeyIfMatches(address, v3Probe, signMessage, embeddedWalletSigner);
17043
+ }
17044
+ const v2Probe = await findEncryptedContent(ctx, "enc:v2:%");
17045
+ if (!v2Probe) return true;
17046
+ return refreshEncryptionKeyIfMatches(address, v2Probe, signMessage, embeddedWalletSigner);
17047
+ }
17048
+ async function findEncryptedContent(ctx, pattern) {
17049
+ const [row] = await ctx.messagesCollection.query(import_watermelondb2.Q.where("content", import_watermelondb2.Q.like(pattern)), import_watermelondb2.Q.take(1)).unsafeFetchRaw();
17050
+ return row ? String(row.content) : null;
17051
+ }
16904
17052
  async function updateMessageErrorOp(ctx, uniqueId, error) {
16905
17053
  let message;
16906
17054
  try {
@@ -17044,6 +17192,95 @@ async function searchMessagesOp(ctx, queryVector, options) {
17044
17192
  })
17045
17193
  );
17046
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
+ }
17047
17284
  async function searchChunksOp(ctx, queryVector, options) {
17048
17285
  const {
17049
17286
  limit = 10,
@@ -17051,83 +17288,111 @@ async function searchChunksOp(ctx, queryVector, options) {
17051
17288
  conversationId,
17052
17289
  excludeConversationId,
17053
17290
  embeddingModel,
17054
- chunkCache
17291
+ chunkCache,
17292
+ onStats
17055
17293
  } = options || {};
17056
- const activeConversations = await ctx.conversationsCollection.query(import_watermelondb2.Q.where("is_deleted", false)).fetch();
17057
- const activeConversationIds = new Set(
17058
- activeConversations.map((c) => String(c._getRaw("conversation_id")))
17059
- );
17060
- const queryConditions = conversationId ? [import_watermelondb2.Q.where("conversation_id", conversationId)] : [];
17061
- 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);
17062
17296
  const candidates = [];
17063
17297
  let staleSkipped = 0;
17064
17298
  let unreadableVectors = 0;
17065
- for (const message of messages) {
17066
- const msgConvId = String(message.conversation_id);
17067
- if (!activeConversationIds.has(msgConvId)) continue;
17068
- if (excludeConversationId !== void 0 && msgConvId === excludeConversationId) continue;
17069
- if (embeddingModel) {
17070
- const storedModel = message.embedding_model;
17071
- if (storedModel !== null && storedModel !== void 0 && storedModel !== embeddingModel) {
17072
- staleSkipped++;
17073
- continue;
17074
- }
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;
17075
17308
  }
17076
- const version = Number(message.updated_at) || 0;
17077
- const cached = chunkCache?.get(message.id);
17078
- if (cached && cached.version === version) {
17079
- if (cached.chunks.length > 0) {
17080
- for (let ci = 0; ci < cached.chunks.length; ci++) {
17081
- const vec = cached.chunks[ci];
17082
- if (vec.length === 0) continue;
17083
- const similarity = cosineSimilarity(queryVector, vec);
17084
- if (similarity >= minSimilarity) {
17085
- candidates.push({
17086
- message,
17087
- similarity,
17088
- chunkTextSource: { kind: "chunk-cached", chunkIndex: ci, version }
17089
- });
17090
- }
17091
- }
17092
- } else if (cached.fallback) {
17093
- const similarity = cosineSimilarity(queryVector, cached.fallback);
17094
- if (similarity >= minSimilarity) {
17095
- candidates.push({ message, similarity, chunkTextSource: { kind: "message" } });
17096
- }
17097
- }
17309
+ const cached = chunkCache?.get(key.id);
17310
+ if (!cached || cached.version !== key.version) {
17311
+ missIds.push(key.id);
17098
17312
  continue;
17099
17313
  }
17100
- const chunks = await readRawJsonField(message, "chunks", ctx.walletAddress);
17101
- if (chunks && chunks.length > 0) {
17102
- const vectors = chunks.map(
17103
- (chunk) => decodeChunkVector(chunk.vector, () => {
17104
- unreadableVectors++;
17105
- })
17106
- );
17107
- chunkCache?.set(message.id, { version, chunks: vectors });
17108
- for (let ci = 0; ci < chunks.length; ci++) {
17109
- 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];
17110
17318
  if (vec.length === 0) continue;
17111
17319
  const similarity = cosineSimilarity(queryVector, vec);
17112
17320
  if (similarity >= minSimilarity) {
17113
17321
  candidates.push({
17114
- message,
17322
+ id: key.id,
17323
+ version: key.version,
17324
+ order,
17115
17325
  similarity,
17116
- chunkTextSource: { kind: "chunk", chunk: chunks[ci], siblings: chunks }
17326
+ chunkTextSource: { kind: "chunk-cached", chunkIndex: ci, version: key.version }
17117
17327
  });
17118
17328
  }
17119
17329
  }
17120
- } else {
17121
- const messageVector = await readRawJsonField(message, "vector", ctx.walletAddress);
17122
- if (!messageVector || messageVector.length === 0) continue;
17123
- const fallback = Float32Array.from(messageVector);
17124
- chunkCache?.set(message.id, { version, chunks: [], fallback });
17125
- const similarity = cosineSimilarity(queryVector, fallback);
17330
+ } else if (cached.fallback) {
17331
+ const similarity = cosineSimilarity(queryVector, cached.fallback);
17126
17332
  if (similarity >= minSimilarity) {
17127
- 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
+ });
17340
+ }
17341
+ }
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
+ }
17128
17392
  }
17129
17393
  }
17130
17394
  }
17395
+ onStats?.({ rowsScanned: source.keys.length, rowsFetched, cacheHits });
17131
17396
  if (staleSkipped > 0) {
17132
17397
  getLogger().warn(
17133
17398
  `searchChunksOp: skipped ${staleSkipped} messages whose embedding model differs from the current model (${embeddingModel}) \u2014 re-embed via chunkAndEmbedAllMessages`
@@ -17138,12 +17403,17 @@ async function searchChunksOp(ctx, queryVector, options) {
17138
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`
17139
17404
  );
17140
17405
  }
17141
- const topK = candidates.sort((a, b) => b.similarity - a.similarity).slice(0, limit);
17142
- const uniqueMessages = /* @__PURE__ */ new Map();
17143
- 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
+ });
17144
17414
  const storedById = new Map(
17145
17415
  await Promise.all(
17146
- Array.from(uniqueMessages, async ([id, m]) => {
17416
+ Array.from(rowById, async ([id, m]) => {
17147
17417
  const stored = await messageRawToStored(
17148
17418
  m,
17149
17419
  ctx.walletAddress,
@@ -17156,24 +17426,24 @@ async function searchChunksOp(ctx, queryVector, options) {
17156
17426
  );
17157
17427
  const chunkStateByMsgId = /* @__PURE__ */ new Map();
17158
17428
  const needsChunkText = /* @__PURE__ */ new Set();
17159
- for (const c of topK) {
17160
- 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);
17161
17431
  }
17162
17432
  await Promise.all(
17163
17433
  Array.from(needsChunkText, async (id) => {
17164
- const m = uniqueMessages.get(id);
17434
+ const m = rowById.get(id);
17165
17435
  const parsed = await readRawJsonField(m, "chunks", ctx.walletAddress);
17166
17436
  const version = Number(m.updated_at) || 0;
17167
17437
  if (parsed) chunkStateByMsgId.set(id, { version, chunks: parsed });
17168
17438
  })
17169
17439
  );
17170
- return topK.map(({ message, similarity, chunkTextSource }) => {
17171
- const stored = { ...storedById.get(message.id) };
17440
+ return liveTopK.map(({ id, similarity, chunkTextSource }) => {
17441
+ const stored = { ...storedById.get(id) };
17172
17442
  let chunkText2;
17173
17443
  if (chunkTextSource.kind === "chunk") {
17174
17444
  chunkText2 = resolveChunkText(stored.content, chunkTextSource.chunk, chunkTextSource.siblings);
17175
17445
  } else if (chunkTextSource.kind === "chunk-cached") {
17176
- const state = chunkStateByMsgId.get(message.id);
17446
+ const state = chunkStateByMsgId.get(id);
17177
17447
  const chunk = state && state.version === chunkTextSource.version ? state.chunks[chunkTextSource.chunkIndex] : void 0;
17178
17448
  chunkText2 = resolveChunkText(stored.content, chunk, state?.chunks);
17179
17449
  } else {
@@ -18269,13 +18539,35 @@ async function generateEmbedding(text12, options) {
18269
18539
  if (total === void 0) return embedOne(text12, options, void 0);
18270
18540
  return withDeadline((signal) => embedOne(text12, options, signal), total, "embedding");
18271
18541
  }
18542
+ var inFlightByCache = /* @__PURE__ */ new WeakMap();
18272
18543
  async function embedOne(text12, options, outer) {
18273
- const { baseUrl = BASE_URL, model, cache } = options;
18274
- const timeoutMs = options.timeoutMs ?? DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS;
18275
- if (cache) {
18276
- const cached = cache.get(text12);
18277
- 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);
18278
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;
18279
18571
  const headers = {
18280
18572
  ...await resolveAuthHeaders(options, outer),
18281
18573
  ...taskTypeHeader(options.taskType)
@@ -18310,11 +18602,7 @@ async function embedOne(text12, options, outer) {
18310
18602
  totalTokens: response.data.usage.total_tokens ?? 0
18311
18603
  });
18312
18604
  }
18313
- const f32Embedding = Float32Array.from(embedding);
18314
- if (cache) {
18315
- cache.set(text12, f32Embedding);
18316
- }
18317
- return Array.from(f32Embedding);
18605
+ return Float32Array.from(embedding);
18318
18606
  }
18319
18607
  var DEFAULT_EMBEDDING_BATCH_SIZE = 100;
18320
18608
  var DEFAULT_EMBEDDING_BATCH_CONCURRENCY = 3;
@@ -28737,9 +29025,9 @@ function recencyMultiplier(updatedAt, opts = {}) {
28737
29025
  const updatedMs = updatedAt.getTime();
28738
29026
  if (!Number.isFinite(updatedMs)) return noDate;
28739
29027
  const now = opts.now ?? /* @__PURE__ */ new Date();
28740
- const nowMs3 = now.getTime();
28741
- if (!Number.isFinite(nowMs3)) return noDate;
28742
- 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);
28743
29031
  const perYearDecay = opts.perYearDecay ?? DEFAULT_PER_YEAR_DECAY;
28744
29032
  const floor = opts.floor ?? DEFAULT_FLOOR;
28745
29033
  const decay = perYearDecay * (ageDays2 / 365);
@@ -29248,9 +29536,6 @@ function createVaultEmbeddingCache(maxSize = DEFAULT_VAULT_CACHE_SIZE) {
29248
29536
 
29249
29537
  // src/lib/memoryVault/searchTool.ts
29250
29538
  var DEFAULT_RERANK_TOP_N = 5;
29251
- function nowMs() {
29252
- return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
29253
- }
29254
29539
  var warnedProgrammaticDecomposeIgnored = false;
29255
29540
  function normalizeEventTimeKind(kind) {
29256
29541
  return kind === "point" || kind === "range" || kind === "ongoing" ? kind : null;
@@ -32141,6 +32426,32 @@ function rankProfileCandidates(inputs, now = Date.now(), options = {}) {
32141
32426
  return ranked;
32142
32427
  }
32143
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
+
32144
32455
  // src/lib/memory/queryTemporal.ts
32145
32456
  var RELATIVE_DAY_ENTRIES = [
32146
32457
  [/\btoday\b/, "today", 0],
@@ -32348,9 +32659,6 @@ function scoreEventTimeOverlap(memoryStart, memoryEnd, memoryKind, window2) {
32348
32659
  var DEFAULT_LIMIT = 8;
32349
32660
  var DEFAULT_BUDGET = "low";
32350
32661
  var DEFAULT_FACT_MIN_SCORE = 0.1;
32351
- function nowMs2() {
32352
- return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
32353
- }
32354
32662
  var DEFAULT_CHUNK_MIN_SCORE = 0.5;
32355
32663
  var DEFAULT_QUERY_EMBED_TOTAL_TIMEOUT_MS = 8e3;
32356
32664
  function flagsForBudget(budget) {
@@ -32388,12 +32696,19 @@ async function recall(query, ctx, options = {}) {
32388
32696
  const subQueries = normalizedFacets.length >= 2 ? normalizedFacets : void 0;
32389
32697
  const embeddingOptions = {
32390
32698
  ...ctx.embeddingOptions,
32391
- 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
+ }
32392
32706
  };
32393
- const t0 = nowMs2();
32707
+ const t0 = nowMs();
32394
32708
  let prepMs = 0;
32395
32709
  let factLaneMs = 0;
32396
32710
  let chunkLaneMs = 0;
32711
+ let chunkStats;
32397
32712
  let fuseMs = 0;
32398
32713
  const factResults = [];
32399
32714
  const chunkResults = [];
@@ -32448,6 +32763,11 @@ async function recall(query, ctx, options = {}) {
32448
32763
  ...decryptLastRan !== void 0 && { decryptLast: decryptLastRan },
32449
32764
  ...vaultRowsDecrypted !== void 0 && { vaultRowsDecrypted },
32450
32765
  ...vaultRowsEmbedded !== void 0 && { vaultRowsEmbedded },
32766
+ ...chunkStats && {
32767
+ chunkRowsScanned: chunkStats.rowsScanned,
32768
+ chunkRowsFetched: chunkStats.rowsFetched,
32769
+ chunkCacheHits: chunkStats.cacheHits
32770
+ },
32451
32771
  factCount: factResults.length,
32452
32772
  chunkCount: chunkResults.length,
32453
32773
  admittedCount: admitted.length,
@@ -32459,7 +32779,7 @@ async function recall(query, ctx, options = {}) {
32459
32779
  temporalLaneCount,
32460
32780
  emptyReason: emptyReasonFor(admitted.length, laneRan),
32461
32781
  timings: {
32462
- total: nowMs2() - t0,
32782
+ total: nowMs() - t0,
32463
32783
  prep: prepMs,
32464
32784
  factLane: factLaneMs,
32465
32785
  rerank: rerankMs,
@@ -32481,14 +32801,14 @@ async function recall(query, ctx, options = {}) {
32481
32801
  const needsChunkEmbedding = types.includes("chunk") && ctx.storageCtx;
32482
32802
  const wantsTemporal = types.includes("fact") && (ctx.factSource || ctx.vaultCtx);
32483
32803
  const queryEmbedTotalTimeoutMs = options.queryEmbedTotalTimeoutMs ?? DEFAULT_QUERY_EMBED_TOTAL_TIMEOUT_MS;
32484
- const prepStart = nowMs2();
32804
+ const prepStart = nowMs();
32485
32805
  let sharedEmbedMs = 0;
32486
32806
  const [queryEmbedding, entityRanking, temporalRanking] = await Promise.all([
32487
32807
  needsChunkEmbedding ? generateEmbedding(query, {
32488
32808
  ...embeddingOptions,
32489
32809
  totalTimeoutMs: queryEmbedTotalTimeoutMs
32490
32810
  }).finally(() => {
32491
- sharedEmbedMs = nowMs2() - prepStart;
32811
+ sharedEmbedMs = nowMs() - prepStart;
32492
32812
  }).then((vec) => {
32493
32813
  if (vec.length === 0) {
32494
32814
  getLogger().warn(
@@ -32521,11 +32841,11 @@ async function recall(query, ctx, options = {}) {
32521
32841
  () => ctx.factSource ? ctx.factSource.temporalRanking(query, options.now) : buildTemporalLaneRanking(query, ctx.vaultCtx, options.now)
32522
32842
  ) : Promise.resolve([])
32523
32843
  ]);
32524
- prepMs = nowMs2() - prepStart;
32844
+ prepMs = nowMs() - prepStart;
32525
32845
  graphLaneCount = entityRanking.length;
32526
32846
  temporalLaneCount = temporalRanking.length;
32527
32847
  if (types.includes("fact") && (ctx.factSource || ctx.vaultCtx && ctx.vaultCache)) {
32528
- const factStart = nowMs2();
32848
+ const factStart = nowMs();
32529
32849
  const vaultMinScore = options.minScore ?? DEFAULT_FACT_MIN_SCORE;
32530
32850
  factFloor = vaultMinScore;
32531
32851
  const {
@@ -32601,11 +32921,11 @@ async function recall(query, ctx, options = {}) {
32601
32921
  factLaneRankedOnCosine = factRankedOnCosine;
32602
32922
  decryptLastRan = factDecryptLast;
32603
32923
  vaultRowsDecrypted = factRowsDecrypted;
32604
- factLaneMs = nowMs2() - factStart;
32924
+ factLaneMs = nowMs() - factStart;
32605
32925
  }
32606
32926
  if (chunkEmbedFailed && !factLaneRankedOnCosine) embeddingsUnavailable = true;
32607
32927
  if (types.includes("chunk") && ctx.storageCtx && queryEmbedding) {
32608
- const chunkStart = nowMs2();
32928
+ const chunkStart = nowMs();
32609
32929
  const chunkMinScore = options.minScore ?? DEFAULT_CHUNK_MIN_SCORE;
32610
32930
  chunkFloor = chunkMinScore;
32611
32931
  const results = await searchChunksOp(ctx.storageCtx, queryEmbedding, {
@@ -32616,12 +32936,15 @@ async function recall(query, ctx, options = {}) {
32616
32936
  ...options.excludeConversationId && {
32617
32937
  excludeConversationId: options.excludeConversationId
32618
32938
  },
32619
- ...ctx.chunkCache && { chunkCache: ctx.chunkCache }
32939
+ ...ctx.chunkCache && { chunkCache: ctx.chunkCache },
32940
+ onStats: (stats) => {
32941
+ chunkStats = stats;
32942
+ }
32620
32943
  });
32621
32944
  chunkResults.push(...dedupeBy(results, (r) => r.chunkText.trim()));
32622
- chunkLaneMs = nowMs2() - chunkStart;
32945
+ chunkLaneMs = nowMs() - chunkStart;
32623
32946
  }
32624
- const fuseStart = nowMs2();
32947
+ const fuseStart = nowMs();
32625
32948
  if (types.length === 1 || factResults.length === 0 || chunkResults.length === 0) {
32626
32949
  const memories2 = [
32627
32950
  ...factResults.map((r) => toFactMemory(r, options.now)),
@@ -32629,7 +32952,7 @@ async function recall(query, ctx, options = {}) {
32629
32952
  ];
32630
32953
  memories2.sort((a, b) => b.score - a.score);
32631
32954
  const candidateCount = factResults.length + chunkResults.length;
32632
- fuseMs = nowMs2() - fuseStart;
32955
+ fuseMs = nowMs() - fuseStart;
32633
32956
  const admitted = memories2.slice(0, limit);
32634
32957
  hitLimit = memories2.length > limit;
32635
32958
  emitDiagnostics(candidateCount, admitted);
@@ -32693,7 +33016,7 @@ async function recall(query, ctx, options = {}) {
32693
33016
  memories = selection.out;
32694
33017
  }
32695
33018
  hitLimit = selection.cut;
32696
- fuseMs = nowMs2() - fuseStart;
33019
+ fuseMs = nowMs() - fuseStart;
32697
33020
  emitDiagnostics(byId.size, memories);
32698
33021
  return {
32699
33022
  memories,
@@ -37687,6 +38010,7 @@ export {
37687
38010
  encryptDataBatch,
37688
38011
  encryptDataBytes,
37689
38012
  encryptDataWithKey,
38013
+ encryptToolCallEventsOp,
37690
38014
  estimateMessagesTokens,
37691
38015
  estimateTokens,
37692
38016
  exportPublicKey,