@anuma/sdk 10.5.1 → 10.5.3

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.
@@ -5619,6 +5619,63 @@ async function chunkRowSource(ctx, filter) {
5619
5619
  return fullScanChunkRowSource(ctx, filter);
5620
5620
  }
5621
5621
  }
5622
+ async function readChunkVectorRow(ctx, row, onUnreadable) {
5623
+ const version = Number(row.updated_at) || 0;
5624
+ const chunks3 = await readRawJsonField(row, "chunks", ctx.walletAddress);
5625
+ if (chunks3 && chunks3.length > 0) {
5626
+ return {
5627
+ entry: {
5628
+ version,
5629
+ chunks: chunks3.map((chunk) => decodeChunkVector(chunk.vector, onUnreadable))
5630
+ },
5631
+ chunks: chunks3
5632
+ };
5633
+ }
5634
+ const messageVector = await readRawJsonField(row, "vector", ctx.walletAddress);
5635
+ if (!messageVector || messageVector.length === 0) return null;
5636
+ return { entry: { version, chunks: [], fallback: Float32Array.from(messageVector) }, chunks: [] };
5637
+ }
5638
+ async function warmChunkVectorCacheOp(ctx, options) {
5639
+ const { chunkCache, embeddingModel, limit, signal } = options;
5640
+ const source = await chunkRowSource(ctx, {});
5641
+ const yieldIfDue = createTimeSliceYielder(CHUNK_SCAN_YIELD_BUDGET_MS);
5642
+ let keys = source.keys.filter(
5643
+ (key3) => !embeddingModel || key3.embeddingModel === null || key3.embeddingModel === embeddingModel
5644
+ );
5645
+ if (limit !== void 0 && keys.length > limit) {
5646
+ keys = keys.sort((a, b) => b.version - a.version).slice(0, limit);
5647
+ }
5648
+ const missIds = [];
5649
+ for (const key3 of keys) {
5650
+ await yieldIfDue();
5651
+ if (chunkCache.get(key3.id)?.version !== key3.version) missIds.push(key3.id);
5652
+ }
5653
+ let warmed = 0;
5654
+ let unreadableVectors = 0;
5655
+ for await (const row of loadVectorRowsInBatches(source, missIds)) {
5656
+ await yieldIfDue();
5657
+ const read = await readChunkVectorRow(ctx, row, () => {
5658
+ unreadableVectors++;
5659
+ });
5660
+ if (signal?.aborted) break;
5661
+ if (!read) continue;
5662
+ chunkCache.set(row.id, read.entry);
5663
+ warmed++;
5664
+ }
5665
+ warnUnreadableChunkVectors("warmChunkVectorCacheOp", unreadableVectors);
5666
+ return warmed;
5667
+ }
5668
+ async function* loadVectorRowsInBatches(source, ids) {
5669
+ for (let offset = 0; offset < ids.length; offset += CHUNK_VECTOR_BATCH_SIZE) {
5670
+ yield* await source.loadVectorRows(ids.slice(offset, offset + CHUNK_VECTOR_BATCH_SIZE));
5671
+ }
5672
+ }
5673
+ function warnUnreadableChunkVectors(operation, count2) {
5674
+ if (count2 === 0) return;
5675
+ getLogger().warn(
5676
+ `${operation}: could not read ${count2} 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`
5677
+ );
5678
+ }
5622
5679
  async function searchChunksOp(ctx, queryVector, options) {
5623
5680
  const {
5624
5681
  limit = 10,
@@ -5687,45 +5744,39 @@ async function searchChunksOp(ctx, queryVector, options) {
5687
5744
  for (const row of rows) {
5688
5745
  await yieldIfDue();
5689
5746
  const id2 = row.id;
5690
- const version = Number(row.updated_at) || 0;
5691
5747
  const order = orderById.get(id2) ?? Number.MAX_SAFE_INTEGER;
5692
- const chunks3 = await readRawJsonField(row, "chunks", ctx.walletAddress);
5693
- if (chunks3 && chunks3.length > 0) {
5694
- const vectors = chunks3.map(
5695
- (chunk) => decodeChunkVector(chunk.vector, () => {
5696
- unreadableVectors++;
5697
- })
5698
- );
5699
- chunkCache?.set(id2, { version, chunks: vectors });
5700
- for (let ci = 0; ci < chunks3.length; ci++) {
5701
- const vec = vectors[ci];
5702
- if (vec.length === 0) continue;
5703
- const similarity = cosineSimilarity(queryVector, vec);
5704
- if (similarity >= minSimilarity) {
5705
- candidates.push({
5706
- id: id2,
5707
- version,
5708
- order,
5709
- similarity,
5710
- chunkTextSource: { kind: "chunk", chunk: chunks3[ci], siblings: chunks3 }
5711
- });
5712
- }
5748
+ const read = await readChunkVectorRow(ctx, row, () => {
5749
+ unreadableVectors++;
5750
+ });
5751
+ if (!read) continue;
5752
+ const { entry, chunks: chunks3 } = read;
5753
+ const { version } = entry;
5754
+ chunkCache?.set(id2, entry);
5755
+ if (entry.fallback) {
5756
+ const similarity = cosineSimilarity(queryVector, entry.fallback);
5757
+ if (similarity >= minSimilarity) {
5758
+ candidates.push({
5759
+ id: id2,
5760
+ version,
5761
+ order,
5762
+ similarity,
5763
+ chunkTextSource: { kind: "message" }
5764
+ });
5713
5765
  }
5714
- } else {
5715
- const messageVector = await readRawJsonField(row, "vector", ctx.walletAddress);
5716
- if (messageVector && messageVector.length > 0) {
5717
- const fallback = Float32Array.from(messageVector);
5718
- chunkCache?.set(id2, { version, chunks: [], fallback });
5719
- const similarity = cosineSimilarity(queryVector, fallback);
5720
- if (similarity >= minSimilarity) {
5721
- candidates.push({
5722
- id: id2,
5723
- version,
5724
- order,
5725
- similarity,
5726
- chunkTextSource: { kind: "message" }
5727
- });
5728
- }
5766
+ continue;
5767
+ }
5768
+ for (let ci = 0; ci < chunks3.length; ci++) {
5769
+ const vec = entry.chunks[ci];
5770
+ if (vec.length === 0) continue;
5771
+ const similarity = cosineSimilarity(queryVector, vec);
5772
+ if (similarity >= minSimilarity) {
5773
+ candidates.push({
5774
+ id: id2,
5775
+ version,
5776
+ order,
5777
+ similarity,
5778
+ chunkTextSource: { kind: "chunk", chunk: chunks3[ci], siblings: chunks3 }
5779
+ });
5729
5780
  }
5730
5781
  }
5731
5782
  }
@@ -5736,11 +5787,7 @@ async function searchChunksOp(ctx, queryVector, options) {
5736
5787
  `searchChunksOp: skipped ${staleSkipped} messages whose embedding model differs from the current model (${embeddingModel}) \u2014 re-embed via chunkAndEmbedAllMessages`
5737
5788
  );
5738
5789
  }
5739
- if (unreadableVectors > 0) {
5740
- getLogger().warn(
5741
- `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`
5742
- );
5743
- }
5790
+ warnUnreadableChunkVectors("searchChunksOp", unreadableVectors);
5744
5791
  const topK = candidates.sort((a, b) => b.similarity - a.similarity || a.order - b.order).slice(0, limit);
5745
5792
  const topIds = Array.from(new Set(topK.map((c) => c.id)));
5746
5793
  const fullRows = await source.loadFullRows(topIds);
@@ -18392,6 +18439,9 @@ var WatermelonChatStorageAdapter = class {
18392
18439
  searchChunks(queryVector, options) {
18393
18440
  return searchChunksOp(this.ctx, queryVector, options);
18394
18441
  }
18442
+ warmChunkCache(options) {
18443
+ return warmChunkVectorCacheOp(this.ctx, options);
18444
+ }
18395
18445
  getSummary(conversationId) {
18396
18446
  return getConversationSummaryOp(
18397
18447
  createSummaryContext(this.ctx.database, this.ctx),
@@ -23709,12 +23759,16 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
23709
23759
  const currentModel = embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
23710
23760
  const uncachedTexts = [];
23711
23761
  const uncachedIndices = [];
23762
+ const resolvedVectors = /* @__PURE__ */ new Map();
23712
23763
  let staleReembedCount = 0;
23713
23764
  for (let i = 0; queryEmbedding.length > 0 && i < memories.length; i++) {
23714
23765
  const content = memories[i].content;
23715
23766
  const memoryId = memories[i].uniqueId;
23716
23767
  const cached = cachedRowVector(cache, memoryId, memories[i].updatedAt, content);
23717
- if (cached && cached.length === queryEmbedding.length) continue;
23768
+ if (cached && cached.length === queryEmbedding.length) {
23769
+ resolvedVectors.set(memoryId, cached);
23770
+ continue;
23771
+ }
23718
23772
  if (cached) cache.delete(memoryId);
23719
23773
  const storedModel = memories[i].embeddingModel;
23720
23774
  const modelCompatible = (storedModel ?? currentModel) === currentModel;
@@ -23722,13 +23776,9 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
23722
23776
  try {
23723
23777
  const parsed = JSON.parse(memories[i].embedding);
23724
23778
  if (Array.isArray(parsed) && parsed.length === queryEmbedding.length) {
23725
- cacheRowVector(
23726
- cache,
23727
- memoryId,
23728
- Float32Array.from(parsed),
23729
- memories[i].updatedAt,
23730
- content
23731
- );
23779
+ const vector2 = Float32Array.from(parsed);
23780
+ resolvedVectors.set(memoryId, vector2);
23781
+ cacheRowVector(cache, memoryId, vector2, memories[i].updatedAt, content);
23732
23782
  continue;
23733
23783
  }
23734
23784
  } catch {
@@ -23761,20 +23811,17 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
23761
23811
  }
23762
23812
  if (newEmbeddings) {
23763
23813
  for (let j = 0; j < uncachedTexts.length; j++) {
23764
- cacheRowVector(
23765
- cache,
23766
- memories[uncachedIndices[j]].uniqueId,
23767
- Float32Array.from(newEmbeddings[j]),
23768
- memories[uncachedIndices[j]].updatedAt,
23769
- memories[uncachedIndices[j]].content
23770
- );
23814
+ const memory = memories[uncachedIndices[j]];
23815
+ const vector2 = Float32Array.from(newEmbeddings[j]);
23816
+ resolvedVectors.set(memory.uniqueId, vector2);
23817
+ cacheRowVector(cache, memory.uniqueId, vector2, memory.updatedAt, memory.content);
23771
23818
  }
23772
23819
  }
23773
23820
  }
23774
23821
  embeddedItems = memories.map((m) => ({
23775
23822
  id: m.uniqueId,
23776
23823
  content: m.content,
23777
- embedding: cache.get(m.uniqueId) ?? [],
23824
+ embedding: resolvedVectors.get(m.uniqueId) ?? [],
23778
23825
  createdAt: m.createdAt,
23779
23826
  updatedAt: m.updatedAt,
23780
23827
  proofCount: m.proofCount,
@@ -38286,6 +38333,7 @@ function useChatStorage(options) {
38286
38333
  [vaultCtx, vaultEmbeddingCache, memoryStore]
38287
38334
  );
38288
38335
  const chunkVectorCacheRef = (0, import_react7.useRef)(createChunkVectorCache());
38336
+ const recallWarmsRef = (0, import_react7.useRef)(/* @__PURE__ */ new Set());
38289
38337
  const clientToolEmbeddingsCacheRef = (0, import_react7.useRef)(/* @__PURE__ */ new Map());
38290
38338
  (0, import_react7.useEffect)(() => {
38291
38339
  if (!getToken || memoryStore !== void 0) return;
@@ -38299,6 +38347,8 @@ function useChatStorage(options) {
38299
38347
  (0, import_react7.useEffect)(() => {
38300
38348
  return onClearAllEncryptionState(() => {
38301
38349
  vaultEmbeddingCache.clear();
38350
+ for (const warm of recallWarmsRef.current) warm.abort();
38351
+ recallWarmsRef.current.clear();
38302
38352
  chunkVectorCacheRef.current.clear();
38303
38353
  });
38304
38354
  }, [vaultEmbeddingCache]);
@@ -38417,6 +38467,28 @@ function useChatStorage(options) {
38417
38467
  storeMaskMissing
38418
38468
  ]
38419
38469
  );
38470
+ const warmRecallCache = (0, import_react7.useCallback)(
38471
+ async (signal) => {
38472
+ if (!backend.warmChunkCache) return 0;
38473
+ const warm = new AbortController();
38474
+ const stop2 = () => warm.abort(signal?.reason);
38475
+ if (signal?.aborted) stop2();
38476
+ signal?.addEventListener("abort", stop2, { once: true });
38477
+ recallWarmsRef.current.add(warm);
38478
+ try {
38479
+ return await backend.warmChunkCache({
38480
+ chunkCache: chunkVectorCacheRef.current,
38481
+ embeddingModel,
38482
+ limit: DEFAULT_CHUNK_CACHE_SIZE,
38483
+ signal: warm.signal
38484
+ });
38485
+ } finally {
38486
+ recallWarmsRef.current.delete(warm);
38487
+ signal?.removeEventListener("abort", stop2);
38488
+ }
38489
+ },
38490
+ [backend, embeddingModel]
38491
+ );
38420
38492
  const {
38421
38493
  isLoading,
38422
38494
  sendMessage: baseSendMessage,
@@ -39599,6 +39671,7 @@ function useChatStorage(options) {
39599
39671
  createMemoryVaultSearchTool: createMemoryVaultSearchTool2,
39600
39672
  createRecallTool: createRecallTool2,
39601
39673
  recall: recallFn,
39674
+ warmRecallCache,
39602
39675
  searchVaultMemories: searchVaultMemoriesFn,
39603
39676
  vaultEmbeddingCache,
39604
39677
  getVaultMemories,
@@ -4899,6 +4899,13 @@ interface ChatBackend extends ChatStorageAdapter {
4899
4899
  uniqueId: string;
4900
4900
  }): Promise<StoredMessage>;
4901
4901
  searchChunks(queryVector: number[], options?: ChatChunkSearchOptions): Promise<ChunkSearchResult[]>;
4902
+ /** Fills a chunk vector cache ahead of the first search, for backends that score on the device. */
4903
+ warmChunkCache?(options: {
4904
+ chunkCache: ChunkVectorCache;
4905
+ embeddingModel?: string;
4906
+ limit?: number;
4907
+ signal?: AbortSignal;
4908
+ }): Promise<number>;
4902
4909
  getSummary(conversationId: string): Promise<StoredConversationSummary | null>;
4903
4910
  upsertSummary(conversationId: string, summary: string, summarizedUpTo: string, tokenCount: number): Promise<StoredConversationSummary>;
4904
4911
  deleteSummary(conversationId: string): Promise<void>;
@@ -13709,6 +13716,7 @@ declare class WatermelonChatStorageAdapter implements ChatBackend {
13709
13716
  uniqueId: string;
13710
13717
  }): Promise<StoredMessage>;
13711
13718
  searchChunks(queryVector: number[], options?: ChatChunkSearchOptions): Promise<ChunkSearchResult[]>;
13719
+ warmChunkCache(options: Parameters<NonNullable<ChatBackend["warmChunkCache"]>>[0]): Promise<number>;
13712
13720
  getSummary(conversationId: string): Promise<StoredConversationSummary | null>;
13713
13721
  upsertSummary(conversationId: string, summary: string, summarizedUpTo: string, tokenCount: number): Promise<StoredConversationSummary>;
13714
13722
  deleteSummary(conversationId: string): Promise<void>;
@@ -15057,6 +15065,11 @@ interface UseChatStorageResult extends BaseUseChatStorageResult {
15057
15065
  * @param options - Optional recall configuration (types, budget, limit, scopes, …)
15058
15066
  */
15059
15067
  recall: (query: string, options?: RecallOptions) => Promise<RecallResult>;
15068
+ /**
15069
+ * Loads the most recent past messages' vectors, up to the recall cache's size, so the first recall does not read them all.
15070
+ * @returns How many it added. It resolves rather than rejects when aborted or signed out, with how many it added before stopping.
15071
+ */
15072
+ warmRecallCache: (signal?: AbortSignal) => Promise<number>;
15060
15073
  /**
15061
15074
  * Search vault memories programmatically using semantic similarity.
15062
15075
  * Returns structured results sorted by descending similarity.
@@ -4899,6 +4899,13 @@ interface ChatBackend extends ChatStorageAdapter {
4899
4899
  uniqueId: string;
4900
4900
  }): Promise<StoredMessage>;
4901
4901
  searchChunks(queryVector: number[], options?: ChatChunkSearchOptions): Promise<ChunkSearchResult[]>;
4902
+ /** Fills a chunk vector cache ahead of the first search, for backends that score on the device. */
4903
+ warmChunkCache?(options: {
4904
+ chunkCache: ChunkVectorCache;
4905
+ embeddingModel?: string;
4906
+ limit?: number;
4907
+ signal?: AbortSignal;
4908
+ }): Promise<number>;
4902
4909
  getSummary(conversationId: string): Promise<StoredConversationSummary | null>;
4903
4910
  upsertSummary(conversationId: string, summary: string, summarizedUpTo: string, tokenCount: number): Promise<StoredConversationSummary>;
4904
4911
  deleteSummary(conversationId: string): Promise<void>;
@@ -13709,6 +13716,7 @@ declare class WatermelonChatStorageAdapter implements ChatBackend {
13709
13716
  uniqueId: string;
13710
13717
  }): Promise<StoredMessage>;
13711
13718
  searchChunks(queryVector: number[], options?: ChatChunkSearchOptions): Promise<ChunkSearchResult[]>;
13719
+ warmChunkCache(options: Parameters<NonNullable<ChatBackend["warmChunkCache"]>>[0]): Promise<number>;
13712
13720
  getSummary(conversationId: string): Promise<StoredConversationSummary | null>;
13713
13721
  upsertSummary(conversationId: string, summary: string, summarizedUpTo: string, tokenCount: number): Promise<StoredConversationSummary>;
13714
13722
  deleteSummary(conversationId: string): Promise<void>;
@@ -15057,6 +15065,11 @@ interface UseChatStorageResult extends BaseUseChatStorageResult {
15057
15065
  * @param options - Optional recall configuration (types, budget, limit, scopes, …)
15058
15066
  */
15059
15067
  recall: (query: string, options?: RecallOptions) => Promise<RecallResult>;
15068
+ /**
15069
+ * Loads the most recent past messages' vectors, up to the recall cache's size, so the first recall does not read them all.
15070
+ * @returns How many it added. It resolves rather than rejects when aborted or signed out, with how many it added before stopping.
15071
+ */
15072
+ warmRecallCache: (signal?: AbortSignal) => Promise<number>;
15060
15073
  /**
15061
15074
  * Search vault memories programmatically using semantic similarity.
15062
15075
  * Returns structured results sorted by descending similarity.
@@ -3773,6 +3773,63 @@ async function chunkRowSource(ctx, filter) {
3773
3773
  return fullScanChunkRowSource(ctx, filter);
3774
3774
  }
3775
3775
  }
3776
+ async function readChunkVectorRow(ctx, row, onUnreadable) {
3777
+ const version = Number(row.updated_at) || 0;
3778
+ const chunks3 = await readRawJsonField(row, "chunks", ctx.walletAddress);
3779
+ if (chunks3 && chunks3.length > 0) {
3780
+ return {
3781
+ entry: {
3782
+ version,
3783
+ chunks: chunks3.map((chunk) => decodeChunkVector(chunk.vector, onUnreadable))
3784
+ },
3785
+ chunks: chunks3
3786
+ };
3787
+ }
3788
+ const messageVector = await readRawJsonField(row, "vector", ctx.walletAddress);
3789
+ if (!messageVector || messageVector.length === 0) return null;
3790
+ return { entry: { version, chunks: [], fallback: Float32Array.from(messageVector) }, chunks: [] };
3791
+ }
3792
+ async function warmChunkVectorCacheOp(ctx, options) {
3793
+ const { chunkCache, embeddingModel, limit, signal } = options;
3794
+ const source = await chunkRowSource(ctx, {});
3795
+ const yieldIfDue = createTimeSliceYielder(CHUNK_SCAN_YIELD_BUDGET_MS);
3796
+ let keys = source.keys.filter(
3797
+ (key3) => !embeddingModel || key3.embeddingModel === null || key3.embeddingModel === embeddingModel
3798
+ );
3799
+ if (limit !== void 0 && keys.length > limit) {
3800
+ keys = keys.sort((a, b) => b.version - a.version).slice(0, limit);
3801
+ }
3802
+ const missIds = [];
3803
+ for (const key3 of keys) {
3804
+ await yieldIfDue();
3805
+ if (chunkCache.get(key3.id)?.version !== key3.version) missIds.push(key3.id);
3806
+ }
3807
+ let warmed = 0;
3808
+ let unreadableVectors = 0;
3809
+ for await (const row of loadVectorRowsInBatches(source, missIds)) {
3810
+ await yieldIfDue();
3811
+ const read = await readChunkVectorRow(ctx, row, () => {
3812
+ unreadableVectors++;
3813
+ });
3814
+ if (signal?.aborted) break;
3815
+ if (!read) continue;
3816
+ chunkCache.set(row.id, read.entry);
3817
+ warmed++;
3818
+ }
3819
+ warnUnreadableChunkVectors("warmChunkVectorCacheOp", unreadableVectors);
3820
+ return warmed;
3821
+ }
3822
+ async function* loadVectorRowsInBatches(source, ids) {
3823
+ for (let offset = 0; offset < ids.length; offset += CHUNK_VECTOR_BATCH_SIZE) {
3824
+ yield* await source.loadVectorRows(ids.slice(offset, offset + CHUNK_VECTOR_BATCH_SIZE));
3825
+ }
3826
+ }
3827
+ function warnUnreadableChunkVectors(operation, count2) {
3828
+ if (count2 === 0) return;
3829
+ getLogger().warn(
3830
+ `${operation}: could not read ${count2} 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`
3831
+ );
3832
+ }
3776
3833
  async function searchChunksOp(ctx, queryVector, options) {
3777
3834
  const {
3778
3835
  limit = 10,
@@ -3841,45 +3898,39 @@ async function searchChunksOp(ctx, queryVector, options) {
3841
3898
  for (const row of rows) {
3842
3899
  await yieldIfDue();
3843
3900
  const id2 = row.id;
3844
- const version = Number(row.updated_at) || 0;
3845
3901
  const order = orderById.get(id2) ?? Number.MAX_SAFE_INTEGER;
3846
- const chunks3 = await readRawJsonField(row, "chunks", ctx.walletAddress);
3847
- if (chunks3 && chunks3.length > 0) {
3848
- const vectors = chunks3.map(
3849
- (chunk) => decodeChunkVector(chunk.vector, () => {
3850
- unreadableVectors++;
3851
- })
3852
- );
3853
- chunkCache?.set(id2, { version, chunks: vectors });
3854
- for (let ci = 0; ci < chunks3.length; ci++) {
3855
- const vec = vectors[ci];
3856
- if (vec.length === 0) continue;
3857
- const similarity = cosineSimilarity(queryVector, vec);
3858
- if (similarity >= minSimilarity) {
3859
- candidates.push({
3860
- id: id2,
3861
- version,
3862
- order,
3863
- similarity,
3864
- chunkTextSource: { kind: "chunk", chunk: chunks3[ci], siblings: chunks3 }
3865
- });
3866
- }
3902
+ const read = await readChunkVectorRow(ctx, row, () => {
3903
+ unreadableVectors++;
3904
+ });
3905
+ if (!read) continue;
3906
+ const { entry, chunks: chunks3 } = read;
3907
+ const { version } = entry;
3908
+ chunkCache?.set(id2, entry);
3909
+ if (entry.fallback) {
3910
+ const similarity = cosineSimilarity(queryVector, entry.fallback);
3911
+ if (similarity >= minSimilarity) {
3912
+ candidates.push({
3913
+ id: id2,
3914
+ version,
3915
+ order,
3916
+ similarity,
3917
+ chunkTextSource: { kind: "message" }
3918
+ });
3867
3919
  }
3868
- } else {
3869
- const messageVector = await readRawJsonField(row, "vector", ctx.walletAddress);
3870
- if (messageVector && messageVector.length > 0) {
3871
- const fallback = Float32Array.from(messageVector);
3872
- chunkCache?.set(id2, { version, chunks: [], fallback });
3873
- const similarity = cosineSimilarity(queryVector, fallback);
3874
- if (similarity >= minSimilarity) {
3875
- candidates.push({
3876
- id: id2,
3877
- version,
3878
- order,
3879
- similarity,
3880
- chunkTextSource: { kind: "message" }
3881
- });
3882
- }
3920
+ continue;
3921
+ }
3922
+ for (let ci = 0; ci < chunks3.length; ci++) {
3923
+ const vec = entry.chunks[ci];
3924
+ if (vec.length === 0) continue;
3925
+ const similarity = cosineSimilarity(queryVector, vec);
3926
+ if (similarity >= minSimilarity) {
3927
+ candidates.push({
3928
+ id: id2,
3929
+ version,
3930
+ order,
3931
+ similarity,
3932
+ chunkTextSource: { kind: "chunk", chunk: chunks3[ci], siblings: chunks3 }
3933
+ });
3883
3934
  }
3884
3935
  }
3885
3936
  }
@@ -3890,11 +3941,7 @@ async function searchChunksOp(ctx, queryVector, options) {
3890
3941
  `searchChunksOp: skipped ${staleSkipped} messages whose embedding model differs from the current model (${embeddingModel}) \u2014 re-embed via chunkAndEmbedAllMessages`
3891
3942
  );
3892
3943
  }
3893
- if (unreadableVectors > 0) {
3894
- getLogger().warn(
3895
- `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`
3896
- );
3897
- }
3944
+ warnUnreadableChunkVectors("searchChunksOp", unreadableVectors);
3898
3945
  const topK = candidates.sort((a, b) => b.similarity - a.similarity || a.order - b.order).slice(0, limit);
3899
3946
  const topIds = Array.from(new Set(topK.map((c) => c.id)));
3900
3947
  const fullRows = await source.loadFullRows(topIds);
@@ -16522,6 +16569,9 @@ var WatermelonChatStorageAdapter = class {
16522
16569
  searchChunks(queryVector, options) {
16523
16570
  return searchChunksOp(this.ctx, queryVector, options);
16524
16571
  }
16572
+ warmChunkCache(options) {
16573
+ return warmChunkVectorCacheOp(this.ctx, options);
16574
+ }
16525
16575
  getSummary(conversationId) {
16526
16576
  return getConversationSummaryOp(
16527
16577
  createSummaryContext(this.ctx.database, this.ctx),
@@ -21809,12 +21859,16 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
21809
21859
  const currentModel = embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
21810
21860
  const uncachedTexts = [];
21811
21861
  const uncachedIndices = [];
21862
+ const resolvedVectors = /* @__PURE__ */ new Map();
21812
21863
  let staleReembedCount = 0;
21813
21864
  for (let i = 0; queryEmbedding.length > 0 && i < memories.length; i++) {
21814
21865
  const content = memories[i].content;
21815
21866
  const memoryId = memories[i].uniqueId;
21816
21867
  const cached = cachedRowVector(cache, memoryId, memories[i].updatedAt, content);
21817
- if (cached && cached.length === queryEmbedding.length) continue;
21868
+ if (cached && cached.length === queryEmbedding.length) {
21869
+ resolvedVectors.set(memoryId, cached);
21870
+ continue;
21871
+ }
21818
21872
  if (cached) cache.delete(memoryId);
21819
21873
  const storedModel = memories[i].embeddingModel;
21820
21874
  const modelCompatible = (storedModel ?? currentModel) === currentModel;
@@ -21822,13 +21876,9 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
21822
21876
  try {
21823
21877
  const parsed = JSON.parse(memories[i].embedding);
21824
21878
  if (Array.isArray(parsed) && parsed.length === queryEmbedding.length) {
21825
- cacheRowVector(
21826
- cache,
21827
- memoryId,
21828
- Float32Array.from(parsed),
21829
- memories[i].updatedAt,
21830
- content
21831
- );
21879
+ const vector2 = Float32Array.from(parsed);
21880
+ resolvedVectors.set(memoryId, vector2);
21881
+ cacheRowVector(cache, memoryId, vector2, memories[i].updatedAt, content);
21832
21882
  continue;
21833
21883
  }
21834
21884
  } catch {
@@ -21861,20 +21911,17 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
21861
21911
  }
21862
21912
  if (newEmbeddings) {
21863
21913
  for (let j = 0; j < uncachedTexts.length; j++) {
21864
- cacheRowVector(
21865
- cache,
21866
- memories[uncachedIndices[j]].uniqueId,
21867
- Float32Array.from(newEmbeddings[j]),
21868
- memories[uncachedIndices[j]].updatedAt,
21869
- memories[uncachedIndices[j]].content
21870
- );
21914
+ const memory = memories[uncachedIndices[j]];
21915
+ const vector2 = Float32Array.from(newEmbeddings[j]);
21916
+ resolvedVectors.set(memory.uniqueId, vector2);
21917
+ cacheRowVector(cache, memory.uniqueId, vector2, memory.updatedAt, memory.content);
21871
21918
  }
21872
21919
  }
21873
21920
  }
21874
21921
  embeddedItems = memories.map((m) => ({
21875
21922
  id: m.uniqueId,
21876
21923
  content: m.content,
21877
- embedding: cache.get(m.uniqueId) ?? [],
21924
+ embedding: resolvedVectors.get(m.uniqueId) ?? [],
21878
21925
  createdAt: m.createdAt,
21879
21926
  updatedAt: m.updatedAt,
21880
21927
  proofCount: m.proofCount,
@@ -36347,6 +36394,7 @@ function useChatStorage(options) {
36347
36394
  [vaultCtx, vaultEmbeddingCache, memoryStore]
36348
36395
  );
36349
36396
  const chunkVectorCacheRef = useRef4(createChunkVectorCache());
36397
+ const recallWarmsRef = useRef4(/* @__PURE__ */ new Set());
36350
36398
  const clientToolEmbeddingsCacheRef = useRef4(/* @__PURE__ */ new Map());
36351
36399
  useEffect5(() => {
36352
36400
  if (!getToken || memoryStore !== void 0) return;
@@ -36360,6 +36408,8 @@ function useChatStorage(options) {
36360
36408
  useEffect5(() => {
36361
36409
  return onClearAllEncryptionState(() => {
36362
36410
  vaultEmbeddingCache.clear();
36411
+ for (const warm of recallWarmsRef.current) warm.abort();
36412
+ recallWarmsRef.current.clear();
36363
36413
  chunkVectorCacheRef.current.clear();
36364
36414
  });
36365
36415
  }, [vaultEmbeddingCache]);
@@ -36478,6 +36528,28 @@ function useChatStorage(options) {
36478
36528
  storeMaskMissing
36479
36529
  ]
36480
36530
  );
36531
+ const warmRecallCache = useCallback6(
36532
+ async (signal) => {
36533
+ if (!backend.warmChunkCache) return 0;
36534
+ const warm = new AbortController();
36535
+ const stop2 = () => warm.abort(signal?.reason);
36536
+ if (signal?.aborted) stop2();
36537
+ signal?.addEventListener("abort", stop2, { once: true });
36538
+ recallWarmsRef.current.add(warm);
36539
+ try {
36540
+ return await backend.warmChunkCache({
36541
+ chunkCache: chunkVectorCacheRef.current,
36542
+ embeddingModel,
36543
+ limit: DEFAULT_CHUNK_CACHE_SIZE,
36544
+ signal: warm.signal
36545
+ });
36546
+ } finally {
36547
+ recallWarmsRef.current.delete(warm);
36548
+ signal?.removeEventListener("abort", stop2);
36549
+ }
36550
+ },
36551
+ [backend, embeddingModel]
36552
+ );
36481
36553
  const {
36482
36554
  isLoading,
36483
36555
  sendMessage: baseSendMessage,
@@ -37660,6 +37732,7 @@ function useChatStorage(options) {
37660
37732
  createMemoryVaultSearchTool: createMemoryVaultSearchTool2,
37661
37733
  createRecallTool: createRecallTool2,
37662
37734
  recall: recallFn,
37735
+ warmRecallCache,
37663
37736
  searchVaultMemories: searchVaultMemoriesFn,
37664
37737
  vaultEmbeddingCache,
37665
37738
  getVaultMemories,