@anuma/sdk 4.2.0 → 5.0.0

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.
@@ -13,8 +13,21 @@ import { v7 as uuidv72 } from "uuid";
13
13
 
14
14
  // src/react/useEncryption.ts
15
15
  var SIGN_MESSAGE = "The app is asking you to sign this message to generate a key, which will be used to encrypt data.";
16
+ var EncryptionKeyMissingError = class extends Error {
17
+ constructor(address, version) {
18
+ super(
19
+ `Encryption key (${version}) not found for ${address}. Please sign a message to generate your encryption key.`
20
+ );
21
+ this.name = "EncryptionKeyMissingError";
22
+ this.address = address;
23
+ this.version = version;
24
+ }
25
+ };
16
26
  var encryptionKeyStore = /* @__PURE__ */ new Map();
17
27
  var pendingKeyRequests = /* @__PURE__ */ new Map();
28
+ var pendingCandidateDerives = /* @__PURE__ */ new Map();
29
+ var refreshCandidatesByAddress = /* @__PURE__ */ new Map();
30
+ var divergentDeriveAddresses = /* @__PURE__ */ new Set();
18
31
  var sessionEpoch = 0;
19
32
  var cryptoKeyCache = /* @__PURE__ */ new Map();
20
33
  var keyAvailableCallbacks = /* @__PURE__ */ new Map();
@@ -57,14 +70,11 @@ function notifyKeyAvailable(address) {
57
70
  }
58
71
  }
59
72
  var keyPairStore = /* @__PURE__ */ new Map();
60
- function getStoredKey(address) {
61
- const keys = encryptionKeyStore.get(address);
62
- return keys?.current ?? null;
63
- }
64
73
  function getStoredKeyByVersion(address, version) {
65
74
  const keys = encryptionKeyStore.get(address);
66
75
  if (!keys) return null;
67
- return version === "v2" ? keys.legacy : keys.current;
76
+ const value = version === "v2" ? keys.legacy : keys.current;
77
+ return value ?? null;
68
78
  }
69
79
  function setStoredKey(address, keys) {
70
80
  encryptionKeyStore.set(address, keys);
@@ -73,6 +83,8 @@ function clearEncryptionKey(address) {
73
83
  encryptionKeyStore.delete(address);
74
84
  cryptoKeyCache.delete(`${address}:v2`);
75
85
  cryptoKeyCache.delete(`${address}:v3`);
86
+ refreshCandidatesByAddress.delete(address);
87
+ divergentDeriveAddresses.delete(address);
76
88
  }
77
89
  function clearAllEncryptionState() {
78
90
  sessionEpoch += 1;
@@ -80,6 +92,9 @@ function clearAllEncryptionState() {
80
92
  cryptoKeyCache.clear();
81
93
  keyAvailableCallbacks.clear();
82
94
  pendingKeyRequests.clear();
95
+ pendingCandidateDerives.clear();
96
+ refreshCandidatesByAddress.clear();
97
+ divergentDeriveAddresses.clear();
83
98
  clearAllKeyPairs();
84
99
  for (const cb of clearAllEncryptionStateCallbacks) {
85
100
  try {
@@ -265,9 +280,7 @@ async function getEncryptionKey(address, version = "v3") {
265
280
  }
266
281
  const keyHex = getStoredKeyByVersion(address, version);
267
282
  if (!keyHex) {
268
- throw new Error(
269
- "Encryption key not found. Please sign a message to generate your encryption key."
270
- );
283
+ throw new EncryptionKeyMissingError(address, version);
271
284
  }
272
285
  const keyBytes = hexToBytes(keyHex);
273
286
  const cryptoKey = await crypto.subtle.importKey(
@@ -331,8 +344,43 @@ async function decryptDataBytesFromBytes(encrypted, address, version = "v3") {
331
344
  );
332
345
  return new Uint8Array(decryptedData);
333
346
  }
334
- function hasEncryptionKey(address) {
335
- return getStoredKey(address) !== null;
347
+ function hasEncryptionKey(address, version) {
348
+ return getStoredKeyByVersion(address, version ?? "v3") !== null;
349
+ }
350
+ function seedEncryptionKeys(address, keys) {
351
+ if (!isValidWalletAddress(address)) {
352
+ throw new Error(
353
+ `Invalid wallet address: ${address}. Address must start with 0x and be 42 characters (0x + 40 hex characters).`
354
+ );
355
+ }
356
+ if (!keys.legacy && !keys.current) {
357
+ throw new Error("seedEncryptionKeys: at least one of legacy or current is required");
358
+ }
359
+ if (keys.legacy !== void 0) {
360
+ assertAes256KeyHex("legacy", keys.legacy);
361
+ }
362
+ if (keys.current !== void 0) {
363
+ assertAes256KeyHex("current", keys.current);
364
+ }
365
+ const existing = encryptionKeyStore.get(address);
366
+ const next = {
367
+ legacy: keys.legacy ?? existing?.legacy,
368
+ current: keys.current ?? existing?.current
369
+ };
370
+ setStoredKey(address, next);
371
+ if (keys.legacy !== void 0) cryptoKeyCache.delete(`${address}:v2`);
372
+ if (keys.current !== void 0) cryptoKeyCache.delete(`${address}:v3`);
373
+ refreshCandidatesByAddress.delete(address);
374
+ divergentDeriveAddresses.delete(address);
375
+ notifyKeyAvailable(address);
376
+ }
377
+ var AES_256_KEY_HEX = /^[0-9a-fA-F]{64}$/;
378
+ function assertAes256KeyHex(field, value) {
379
+ if (!AES_256_KEY_HEX.test(value)) {
380
+ throw new Error(
381
+ `seedEncryptionKeys: ${field} must be 64 hex characters (32-byte AES key), got length ${value.length}`
382
+ );
383
+ }
336
384
  }
337
385
  async function encryptBytesWithKey(plaintext, key) {
338
386
  const plaintextBytes = typeof plaintext === "string" ? SHARED_TEXT_ENCODER.encode(plaintext) : plaintext;
@@ -381,20 +429,23 @@ async function decryptDataBatch(encryptedValues, address) {
381
429
  const key = await getEncryptionKey(address);
382
430
  return Promise.all(encryptedValues.map((value) => decryptDataWithKey(value, key)));
383
431
  }
384
- async function requestEncryptionKey(walletAddress, signMessage, embeddedWalletSigner) {
432
+ async function requestEncryptionKey(walletAddress, signMessage, embeddedWalletSigner, options) {
385
433
  if (!isValidWalletAddress(walletAddress)) {
386
434
  throw new Error(
387
435
  `Invalid wallet address: ${walletAddress}. Address must start with 0x and be 42 characters (0x + 40 hex characters).`
388
436
  );
389
437
  }
390
- const existingKey = getStoredKey(walletAddress);
391
- if (existingKey) {
392
- return;
438
+ if (!options?.force && getStoredKeyByVersion(walletAddress, "v3") && getStoredKeyByVersion(walletAddress, "v2")) {
439
+ return true;
393
440
  }
394
- const pending = pendingKeyRequests.get(walletAddress);
395
- if (pending) {
396
- await pending;
397
- return;
441
+ if (!options?.force && divergentDeriveAddresses.has(walletAddress)) {
442
+ return false;
443
+ }
444
+ if (!options?.force) {
445
+ const pending = pendingKeyRequests.get(walletAddress);
446
+ if (pending) {
447
+ return pending;
448
+ }
398
449
  }
399
450
  const startEpoch = sessionEpoch;
400
451
  const promise = (async () => {
@@ -422,20 +473,143 @@ async function requestEncryptionKey(walletAddress, signMessage, embeddedWalletSi
422
473
  deriveKeyFromSignatureV3(signature)
423
474
  ]);
424
475
  if (sessionEpoch !== startEpoch) {
425
- return;
476
+ return false;
477
+ }
478
+ const existing = encryptionKeyStore.get(walletAddress);
479
+ if (!options?.force && existing && (existing.legacy || existing.current)) {
480
+ const matchesCurrent = Boolean(existing.current && existing.current === currentKey);
481
+ const matchesLegacy = Boolean(existing.legacy && existing.legacy === legacyKey);
482
+ if (!matchesCurrent && !matchesLegacy) {
483
+ getLogger().warn(
484
+ "requestEncryptionKey: derived keys do not match existing store; leaving unchanged"
485
+ );
486
+ divergentDeriveAddresses.add(walletAddress);
487
+ return false;
488
+ }
489
+ setStoredKey(walletAddress, {
490
+ legacy: existing.legacy ?? legacyKey,
491
+ current: existing.current ?? currentKey
492
+ });
493
+ } else {
494
+ setStoredKey(walletAddress, { legacy: legacyKey, current: currentKey });
426
495
  }
427
- setStoredKey(walletAddress, { legacy: legacyKey, current: currentKey });
496
+ cryptoKeyCache.delete(`${walletAddress}:v2`);
497
+ cryptoKeyCache.delete(`${walletAddress}:v3`);
498
+ refreshCandidatesByAddress.delete(walletAddress);
499
+ divergentDeriveAddresses.delete(walletAddress);
428
500
  notifyKeyAvailable(walletAddress);
501
+ return true;
429
502
  })();
430
503
  pendingKeyRequests.set(walletAddress, promise);
431
504
  try {
432
- await promise;
505
+ return await promise;
433
506
  } finally {
434
507
  if (pendingKeyRequests.get(walletAddress) === promise) {
435
508
  pendingKeyRequests.delete(walletAddress);
436
509
  }
437
510
  }
438
511
  }
512
+ async function refreshEncryptionKeyIfMatches(walletAddress, probeCiphertext, signMessage, embeddedWalletSigner) {
513
+ if (!isValidWalletAddress(walletAddress)) {
514
+ throw new Error(
515
+ `Invalid wallet address: ${walletAddress}. Address must start with 0x and be 42 characters (0x + 40 hex characters).`
516
+ );
517
+ }
518
+ const parsed = parsePrefixedCiphertext(probeCiphertext);
519
+ if (!parsed) {
520
+ return false;
521
+ }
522
+ const cached = refreshCandidatesByAddress.get(walletAddress);
523
+ if (cached) {
524
+ return tryCommitCandidatesIfProbeMatches(walletAddress, parsed, cached);
525
+ }
526
+ let derivePromise = pendingCandidateDerives.get(walletAddress);
527
+ if (!derivePromise) {
528
+ derivePromise = (async () => {
529
+ const candidates2 = await deriveRefreshCandidates(signMessage, embeddedWalletSigner);
530
+ if (candidates2) {
531
+ refreshCandidatesByAddress.set(walletAddress, candidates2);
532
+ }
533
+ return candidates2;
534
+ })().finally(() => {
535
+ if (pendingCandidateDerives.get(walletAddress) === derivePromise) {
536
+ pendingCandidateDerives.delete(walletAddress);
537
+ }
538
+ });
539
+ pendingCandidateDerives.set(walletAddress, derivePromise);
540
+ }
541
+ const candidates = await derivePromise;
542
+ if (!candidates) {
543
+ return false;
544
+ }
545
+ return tryCommitCandidatesIfProbeMatches(walletAddress, parsed, candidates);
546
+ }
547
+ function parsePrefixedCiphertext(probeCiphertext) {
548
+ const version = probeCiphertext.startsWith("enc:v3:") ? "v3" : probeCiphertext.startsWith("enc:v2:") ? "v2" : null;
549
+ if (!version) return null;
550
+ const encryptedData = probeCiphertext.slice(
551
+ version === "v3" ? "enc:v3:".length : "enc:v2:".length
552
+ );
553
+ if (encryptedData.length < 56 || !/^[0-9a-f]+$/i.test(encryptedData)) {
554
+ return null;
555
+ }
556
+ return { version, encryptedData };
557
+ }
558
+ async function deriveRefreshCandidates(signMessage, embeddedWalletSigner) {
559
+ const startEpoch = sessionEpoch;
560
+ const signOptions = { showWalletUIs: false };
561
+ let signature;
562
+ try {
563
+ if (embeddedWalletSigner) {
564
+ signature = await embeddedWalletSigner(SIGN_MESSAGE, signOptions);
565
+ } else {
566
+ signature = await signMessage(SIGN_MESSAGE, signOptions);
567
+ }
568
+ } catch (error) {
569
+ if (embeddedWalletSigner && error instanceof Error) {
570
+ getLogger().warn(
571
+ "Embedded wallet signing failed during key refresh, falling back to standard signMessage:",
572
+ error.message
573
+ );
574
+ signature = await signMessage(SIGN_MESSAGE, signOptions);
575
+ } else {
576
+ throw error;
577
+ }
578
+ }
579
+ const [legacyKey, currentKey] = await Promise.all([
580
+ deriveKeyFromSignature(signature),
581
+ deriveKeyFromSignatureV3(signature)
582
+ ]);
583
+ if (sessionEpoch !== startEpoch) {
584
+ return null;
585
+ }
586
+ return { legacy: legacyKey, current: currentKey };
587
+ }
588
+ async function tryCommitCandidatesIfProbeMatches(walletAddress, parsed, candidates) {
589
+ const candidateHex = parsed.version === "v2" ? candidates.legacy : candidates.current;
590
+ const keyBytes = hexToBytes(candidateHex);
591
+ const candidateCryptoKey = await crypto.subtle.importKey(
592
+ "raw",
593
+ keyBytes.buffer,
594
+ { name: "AES-GCM" },
595
+ false,
596
+ ["encrypt", "decrypt"]
597
+ );
598
+ try {
599
+ await decryptDataWithKey(parsed.encryptedData, candidateCryptoKey);
600
+ } catch {
601
+ getLogger().warn(
602
+ "refreshEncryptionKeyIfMatches: candidate keys failed to decrypt probe; leaving store unchanged"
603
+ );
604
+ return false;
605
+ }
606
+ setStoredKey(walletAddress, { legacy: candidates.legacy, current: candidates.current });
607
+ cryptoKeyCache.delete(`${walletAddress}:v2`);
608
+ cryptoKeyCache.delete(`${walletAddress}:v3`);
609
+ divergentDeriveAddresses.delete(walletAddress);
610
+ notifyKeyAvailable(walletAddress);
611
+ return true;
612
+ }
439
613
  var KEYPAIR_STORAGE_PREFIX = "ecdh_keypair_";
440
614
  async function persistKeyPair(address, startEpoch) {
441
615
  if (typeof window === "undefined") {
@@ -715,20 +889,29 @@ async function encryptField(value, address, signMessage, embeddedWalletSigner, s
715
889
  const encrypted = await encryptData(value, address);
716
890
  return `${ENCRYPTION_PREFIX}${encrypted}`;
717
891
  }
718
- async function decryptField(value, address) {
719
- if (!value) return value;
892
+ async function decryptFieldDetailed(value, address) {
893
+ if (!value) return { status: "plaintext", value };
720
894
  const detected = detectEncryptionVersion(value);
721
- if (!detected) return value;
895
+ if (!detected) return { status: "plaintext", value };
722
896
  if (detected.encryptedData.length < 56 || !/^[0-9a-f]+$/i.test(detected.encryptedData)) {
723
- return value;
897
+ return { status: "invalid_payload", value, version: detected.version };
724
898
  }
725
899
  try {
726
- return await decryptData(detected.encryptedData, address, detected.version);
900
+ const plaintext = await decryptData(detected.encryptedData, address, detected.version);
901
+ return { status: "ok", value: plaintext, version: detected.version };
727
902
  } catch (error) {
728
- getLogger().warn("Failed to decrypt field, returning as-is:", error);
729
- return value;
903
+ if (error instanceof EncryptionKeyMissingError) {
904
+ getLogger().warn("Failed to decrypt field (key missing for version):", error.message);
905
+ return { status: "key_missing", value, version: detected.version };
906
+ }
907
+ getLogger().warn("Failed to decrypt field (auth mismatch or crypto error):", error);
908
+ return { status: "auth_mismatch", value, version: detected.version };
730
909
  }
731
910
  }
911
+ async function decryptField(value, address) {
912
+ const result = await decryptFieldDetailed(value, address);
913
+ return result.value;
914
+ }
732
915
  async function encryptJsonField(value, address, signMessage, embeddedWalletSigner, skipKeyRequest) {
733
916
  if (!value) return void 0;
734
917
  const jsonString = JSON.stringify(value);
@@ -828,20 +1011,40 @@ async function encryptMessageFields(message, address, signMessage, embeddedWalle
828
1011
  throw error;
829
1012
  }
830
1013
  }
831
- async function decryptMaybeJsonField(value, address) {
832
- if (value === void 0) return void 0;
833
- if (value === null) return value;
1014
+ async function decryptMaybeJsonFieldDetailed(value, address) {
1015
+ if (value === void 0) return { value: void 0, status: "plaintext" };
1016
+ if (value === null) return { value, status: "plaintext" };
834
1017
  if (typeof value === "string") {
835
1018
  if (isEncrypted(value)) {
836
- return await decryptJsonField(value, address);
1019
+ const detailed = await decryptFieldDetailed(value, address);
1020
+ if (detailed.status !== "ok") {
1021
+ return { value: void 0, status: detailed.status, probe: value };
1022
+ }
1023
+ try {
1024
+ return { value: JSON.parse(detailed.value), status: "ok" };
1025
+ } catch (error) {
1026
+ getLogger().warn("Failed to parse decrypted JSON field:", error);
1027
+ return { value: void 0, status: "invalid_payload", probe: value };
1028
+ }
837
1029
  }
838
1030
  try {
839
- return JSON.parse(value);
1031
+ return { value: JSON.parse(value), status: "plaintext" };
840
1032
  } catch {
841
- return void 0;
1033
+ return { value: void 0, status: "plaintext" };
842
1034
  }
843
1035
  }
844
- return value;
1036
+ return { value, status: "plaintext" };
1037
+ }
1038
+ function worseDecryptStatus(a, b) {
1039
+ if (b === "ok" || b === "plaintext") return a;
1040
+ if (!a || a === "ok" || a === "plaintext") return b;
1041
+ const rank = (s) => {
1042
+ if (s === "auth_mismatch") return 3;
1043
+ if (s === "key_missing") return 2;
1044
+ if (s === "invalid_payload") return 1;
1045
+ return 0;
1046
+ };
1047
+ return rank(b) >= rank(a) ? b : a;
845
1048
  }
846
1049
  async function decryptMessageFields(message, address, signMessage, embeddedWalletSigner) {
847
1050
  if (!address) {
@@ -854,33 +1057,82 @@ async function decryptMessageFields(message, address, signMessage, embeddedWalle
854
1057
  getLogger().warn("Failed to request encryption key for decryption:", error);
855
1058
  }
856
1059
  }
857
- const decryptedContent = await decryptField(message.content, address);
858
- const decryptedThinking = message.thinking ? await decryptField(message.thinking, address) : message.thinking;
859
- const decryptedVector = await decryptMaybeJsonField(
1060
+ let assembled = await assembleDecryptedMessage(message, address);
1061
+ const needsRefresh = assembled.decryptionStatus === "auth_mismatch" || assembled.decryptionStatus === "key_missing";
1062
+ if (needsRefresh && signMessage && assembled.refreshProbe) {
1063
+ try {
1064
+ const refreshed = await refreshEncryptionKeyIfMatches(
1065
+ address,
1066
+ assembled.refreshProbe,
1067
+ signMessage,
1068
+ embeddedWalletSigner
1069
+ );
1070
+ if (refreshed) {
1071
+ assembled = await assembleDecryptedMessage(message, address);
1072
+ }
1073
+ } catch (error) {
1074
+ getLogger().warn("Failed to refresh encryption key after decrypt failure:", error);
1075
+ }
1076
+ }
1077
+ const { refreshProbe: _probe, ...result } = assembled;
1078
+ return result;
1079
+ }
1080
+ async function assembleDecryptedMessage(message, address) {
1081
+ const contentResult = await decryptFieldDetailed(message.content, address);
1082
+ const thinkingResult = message.thinking ? await decryptFieldDetailed(message.thinking, address) : null;
1083
+ const vectorResult = await decryptMaybeJsonFieldDetailed(
860
1084
  message.vector,
861
1085
  address
862
1086
  );
863
- const decryptedChunks = await decryptMaybeJsonField(
1087
+ const chunksResult = await decryptMaybeJsonFieldDetailed(
864
1088
  message.chunks,
865
1089
  address
866
1090
  );
867
- const decryptedSources = await decryptMaybeJsonField(
1091
+ const sourcesResult = await decryptMaybeJsonFieldDetailed(
868
1092
  message.sources,
869
1093
  address
870
1094
  );
871
- const decryptedThoughtProcess = await decryptMaybeJsonField(
1095
+ const thoughtProcessResult = await decryptMaybeJsonFieldDetailed(
872
1096
  message.thoughtProcess,
873
1097
  address
874
1098
  );
875
- return {
1099
+ let decryptionStatus;
1100
+ let refreshProbe;
1101
+ const consider = (status, probe) => {
1102
+ decryptionStatus = worseDecryptStatus(decryptionStatus, status);
1103
+ if (!refreshProbe && probe && (status === "auth_mismatch" || status === "key_missing")) {
1104
+ refreshProbe = probe;
1105
+ }
1106
+ };
1107
+ consider(contentResult.status, isEncrypted(message.content) ? message.content : void 0);
1108
+ if (thinkingResult && message.thinking && typeof message.thinking === "string") {
1109
+ consider(thinkingResult.status, isEncrypted(message.thinking) ? message.thinking : void 0);
1110
+ }
1111
+ if (vectorResult.probe) consider(vectorResult.status, vectorResult.probe);
1112
+ if (chunksResult.probe) consider(chunksResult.status, chunksResult.probe);
1113
+ if (sourcesResult.probe) consider(sourcesResult.status, sourcesResult.probe);
1114
+ if (thoughtProcessResult.probe) {
1115
+ consider(thoughtProcessResult.status, thoughtProcessResult.probe);
1116
+ }
1117
+ const statusForConsumer = decryptionStatus === "ok" || decryptionStatus === "plaintext" || !decryptionStatus ? void 0 : decryptionStatus;
1118
+ const result = {
876
1119
  ...message,
877
- content: decryptedContent,
878
- thinking: decryptedThinking,
879
- vector: decryptedVector,
880
- chunks: decryptedChunks,
881
- sources: decryptedSources,
882
- thoughtProcess: decryptedThoughtProcess
1120
+ content: contentResult.value,
1121
+ thinking: thinkingResult ? thinkingResult.value : message.thinking,
1122
+ vector: vectorResult.value,
1123
+ chunks: chunksResult.value,
1124
+ sources: sourcesResult.value,
1125
+ thoughtProcess: thoughtProcessResult.value
883
1126
  };
1127
+ if (statusForConsumer) {
1128
+ result.decryptionStatus = statusForConsumer;
1129
+ } else {
1130
+ delete result.decryptionStatus;
1131
+ }
1132
+ if (refreshProbe) {
1133
+ result.refreshProbe = refreshProbe;
1134
+ }
1135
+ return result;
884
1136
  }
885
1137
 
886
1138
  // src/lib/db/chat/types.ts
@@ -4779,6 +5031,107 @@ async function chunkAndEmbedAllMessages(ctx, options, filter) {
4779
5031
  return embeddedCount;
4780
5032
  }
4781
5033
 
5034
+ // src/lib/memoryVault/decomposeQuery.ts
5035
+ var DEFAULT_MODEL = "inclusionai/ling-2.6-flash";
5036
+ var MAX_SUB_QUERIES = 5;
5037
+ var SYSTEM_PROMPT = `You classify a memory query and, if needed, decompose it into concrete sub-queries.
5038
+
5039
+ CLASSIFICATION RULES \u2014 be strict:
5040
+ - "specific": asks for one fact OR a list of items in ONE category (one dimension).
5041
+ Examples: a name, a date, a preference, a location, an ID, a value, "what hobbies",
5042
+ "which conferences", "what open source projects", "what does X do for exercise".
5043
+ Even when the answer is a list, if all items belong to one dimension, it is specific.
5044
+ - "composite": asks about a person, system, or setup MULTI-DIMENSIONALLY \u2014 a summary,
5045
+ an overview, a profile, "tell me about X", "what is my X setup", "describe the team's
5046
+ X". The answer must span multiple distinct fact dimensions (e.g. for a "tech stack":
5047
+ language + database + framework + tools \u2014 four different dimensions).
5048
+
5049
+ When in doubt, prefer "specific". Mis-classifying a single-dimension list as composite
5050
+ hurts retrieval more than the reverse.
5051
+
5052
+ SUB-QUERY RULES (composite only, 3\u20135 questions):
5053
+ - Each sub-query targets a DIFFERENT fact dimension.
5054
+ - Use SHORT parenthetical examples \u2014 2\u20134 concrete options that anchor the
5055
+ embedding to likely memory content. Examples must be terms a user would
5056
+ actually have in their notes (e.g., "Postgres" not "PostgreSQL 15.2 with
5057
+ read replicas"). No verbose qualifiers like "and versions" or "and any
5058
+ relevant configuration details".
5059
+ - 8\u201314 words including the parenthetical. Like a human asking a follow-up.
5060
+
5061
+ Examples:
5062
+ "What is my dog's name?" \u2192 {"mode":"specific","subQueries":["What is my dog's name?"]}
5063
+ "What does the user do for exercise?" \u2192 {"mode":"specific","subQueries":["What does the user do for exercise?"]}
5064
+ "What conferences has the user attended?" \u2192 {"mode":"specific","subQueries":["What conferences has the user attended?"]}
5065
+ "What did I do last Tuesday?" \u2192 {"mode":"specific","subQueries":["What did I do last Tuesday?"]}
5066
+ "Tell me about the user as a person" \u2192 {"mode":"composite","subQueries":["What is the user's name?","Where does the user live?","What does the user do for work?","Any health conditions or allergies?","What are the user's hobbies (sports, music, games)?"]}
5067
+ "What is the user's tech stack?" \u2192 {"mode":"composite","subQueries":["What programming languages does the user use (Python, Go, TypeScript)?","What database does the user use (Postgres, MySQL, Mongo)?","What framework or ORM (React, Django, Prisma)?","What infrastructure (AWS, Docker, K8s)?","What testing tools (Jest, pytest, Cypress)?"]}
5068
+ "Describe the user's development environment" \u2192 {"mode":"composite","subQueries":["What operating system does the user use (macOS, Linux, Windows)?","What editor or IDE (VS Code, Vim, IntelliJ)?","What shell or terminal setup (zsh, bash, tmux)?","What keyboard or display setup (mechanical, vertical monitor)?","Any UI preferences (dark mode, font, vim keybindings)?"]}
5069
+
5070
+ Output strict JSON. No prose.`;
5071
+ async function decomposeQuery(query, options) {
5072
+ const fallback = { mode: "specific", subQueries: [query] };
5073
+ const trimmed = query.trim();
5074
+ if (trimmed.length === 0) return fallback;
5075
+ let parsed;
5076
+ try {
5077
+ parsed = await callPortalJsonCompletion({
5078
+ ...options.apiKey !== void 0 && { apiKey: options.apiKey },
5079
+ ...options.getToken !== void 0 && { getToken: options.getToken },
5080
+ ...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
5081
+ model: options.model ?? DEFAULT_MODEL,
5082
+ systemPrompt: SYSTEM_PROMPT,
5083
+ // Wrap the bare query in a task framing so the model treats it as
5084
+ // input to classify, not as a question to answer conversationally.
5085
+ // Bare-query inputs caused Anthropic models to respond with prose
5086
+ // like "Do you mean...?" before this wrap.
5087
+ userMessage: `Classify the following memory query and decompose if composite. Respond with JSON only \u2014 do not answer the question, do not ask for clarification.
5088
+
5089
+ Query: ${trimmed}`,
5090
+ // Decompose runs ahead of recall in the tool layer — tighter than the
5091
+ // portalLlm default (consolidate/extract can wait longer).
5092
+ timeoutMs: 2e4,
5093
+ // No internal retry: a failure degrades to the safe `{specific,[query]}`
5094
+ // fallback below. The agent/tool loop is the retry surface (719/B4).
5095
+ maxAttempts: 1,
5096
+ tag: "memory/decompose",
5097
+ ...options.fetchFn && { fetchFn: options.fetchFn }
5098
+ });
5099
+ } catch (err) {
5100
+ getLogger().warn("memoryVault/decompose: portal call failed, falling back to specific", err);
5101
+ return fallback;
5102
+ }
5103
+ if (parsed === null) return fallback;
5104
+ return validate(parsed, trimmed) ?? fallback;
5105
+ }
5106
+ function normalizeSubQueries(raw) {
5107
+ if (!raw || raw.length === 0) return [];
5108
+ const seen = /* @__PURE__ */ new Set();
5109
+ const out = [];
5110
+ for (const s of raw) {
5111
+ if (typeof s !== "string") continue;
5112
+ const t = s.trim();
5113
+ if (t.length === 0) continue;
5114
+ const key = t.toLowerCase();
5115
+ if (seen.has(key)) continue;
5116
+ seen.add(key);
5117
+ out.push(t);
5118
+ if (out.length >= MAX_SUB_QUERIES) break;
5119
+ }
5120
+ return out;
5121
+ }
5122
+ function validate(parsed, originalQuery) {
5123
+ if (typeof parsed !== "object" || parsed === null) return null;
5124
+ const obj = parsed;
5125
+ const mode = obj.mode === "composite" ? "composite" : "specific";
5126
+ if (!Array.isArray(obj.subQueries)) return null;
5127
+ const subQueries = normalizeSubQueries(obj.subQueries);
5128
+ if (subQueries.length === 0) return null;
5129
+ if (mode === "specific") {
5130
+ return { mode: "specific", subQueries: [originalQuery] };
5131
+ }
5132
+ return { mode, subQueries };
5133
+ }
5134
+
4782
5135
  // src/lib/memory/mmr.ts
4783
5136
  function applyMMR(candidates, k, lambda = 0.5) {
4784
5137
  if (candidates.length === 0 || k <= 0) return [];
@@ -5156,91 +5509,6 @@ function scoreBM25(query, items) {
5156
5509
  return scoreBM25Prepared(query, prepareBM25Corpus(items));
5157
5510
  }
5158
5511
 
5159
- // src/lib/memoryVault/decomposeQuery.ts
5160
- var DEFAULT_MODEL = "inclusionai/ling-2.6-flash";
5161
- var MAX_SUB_QUERIES = 5;
5162
- var SYSTEM_PROMPT = `You classify a memory query and, if needed, decompose it into concrete sub-queries.
5163
-
5164
- CLASSIFICATION RULES \u2014 be strict:
5165
- - "specific": asks for one fact OR a list of items in ONE category (one dimension).
5166
- Examples: a name, a date, a preference, a location, an ID, a value, "what hobbies",
5167
- "which conferences", "what open source projects", "what does X do for exercise".
5168
- Even when the answer is a list, if all items belong to one dimension, it is specific.
5169
- - "composite": asks about a person, system, or setup MULTI-DIMENSIONALLY \u2014 a summary,
5170
- an overview, a profile, "tell me about X", "what is my X setup", "describe the team's
5171
- X". The answer must span multiple distinct fact dimensions (e.g. for a "tech stack":
5172
- language + database + framework + tools \u2014 four different dimensions).
5173
-
5174
- When in doubt, prefer "specific". Mis-classifying a single-dimension list as composite
5175
- hurts retrieval more than the reverse.
5176
-
5177
- SUB-QUERY RULES (composite only, 3\u20135 questions):
5178
- - Each sub-query targets a DIFFERENT fact dimension.
5179
- - Use SHORT parenthetical examples \u2014 2\u20134 concrete options that anchor the
5180
- embedding to likely memory content. Examples must be terms a user would
5181
- actually have in their notes (e.g., "Postgres" not "PostgreSQL 15.2 with
5182
- read replicas"). No verbose qualifiers like "and versions" or "and any
5183
- relevant configuration details".
5184
- - 8\u201314 words including the parenthetical. Like a human asking a follow-up.
5185
-
5186
- Examples:
5187
- "What is my dog's name?" \u2192 {"mode":"specific","subQueries":["What is my dog's name?"]}
5188
- "What does the user do for exercise?" \u2192 {"mode":"specific","subQueries":["What does the user do for exercise?"]}
5189
- "What conferences has the user attended?" \u2192 {"mode":"specific","subQueries":["What conferences has the user attended?"]}
5190
- "What did I do last Tuesday?" \u2192 {"mode":"specific","subQueries":["What did I do last Tuesday?"]}
5191
- "Tell me about the user as a person" \u2192 {"mode":"composite","subQueries":["What is the user's name?","Where does the user live?","What does the user do for work?","Any health conditions or allergies?","What are the user's hobbies (sports, music, games)?"]}
5192
- "What is the user's tech stack?" \u2192 {"mode":"composite","subQueries":["What programming languages does the user use (Python, Go, TypeScript)?","What database does the user use (Postgres, MySQL, Mongo)?","What framework or ORM (React, Django, Prisma)?","What infrastructure (AWS, Docker, K8s)?","What testing tools (Jest, pytest, Cypress)?"]}
5193
- "Describe the user's development environment" \u2192 {"mode":"composite","subQueries":["What operating system does the user use (macOS, Linux, Windows)?","What editor or IDE (VS Code, Vim, IntelliJ)?","What shell or terminal setup (zsh, bash, tmux)?","What keyboard or display setup (mechanical, vertical monitor)?","Any UI preferences (dark mode, font, vim keybindings)?"]}
5194
-
5195
- Output strict JSON. No prose.`;
5196
- async function decomposeQuery(query, options) {
5197
- const fallback = { mode: "specific", subQueries: [query] };
5198
- const trimmed = query.trim();
5199
- if (trimmed.length === 0) return fallback;
5200
- let parsed;
5201
- try {
5202
- parsed = await callPortalJsonCompletion({
5203
- ...options.apiKey !== void 0 && { apiKey: options.apiKey },
5204
- ...options.getToken !== void 0 && { getToken: options.getToken },
5205
- ...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
5206
- model: options.model ?? DEFAULT_MODEL,
5207
- systemPrompt: SYSTEM_PROMPT,
5208
- // Wrap the bare query in a task framing so the model treats it as
5209
- // input to classify, not as a question to answer conversationally.
5210
- // Bare-query inputs caused Anthropic models to respond with prose
5211
- // like "Do you mean...?" before this wrap.
5212
- userMessage: `Classify the following memory query and decompose if composite. Respond with JSON only \u2014 do not answer the question, do not ask for clarification.
5213
-
5214
- Query: ${trimmed}`,
5215
- // Decompose runs on the recall hot path — tighter than the
5216
- // portalLlm default (consolidate/extract can wait longer).
5217
- timeoutMs: 2e4,
5218
- // No internal retry: a failure degrades to the safe `{specific,[query]}`
5219
- // fallback below, so retrying would only add latency on the hot path.
5220
- maxAttempts: 1,
5221
- tag: "memory/decompose",
5222
- ...options.fetchFn && { fetchFn: options.fetchFn }
5223
- });
5224
- } catch (err) {
5225
- getLogger().warn("memoryVault/decompose: portal call failed, falling back to specific", err);
5226
- return fallback;
5227
- }
5228
- if (parsed === null) return fallback;
5229
- return validate(parsed, trimmed) ?? fallback;
5230
- }
5231
- function validate(parsed, originalQuery) {
5232
- if (typeof parsed !== "object" || parsed === null) return null;
5233
- const obj = parsed;
5234
- const mode = obj.mode === "composite" ? "composite" : "specific";
5235
- if (!Array.isArray(obj.subQueries)) return null;
5236
- const subQueries = obj.subQueries.filter((s) => typeof s === "string").map((s) => s.trim()).filter((s) => s.length > 0).slice(0, MAX_SUB_QUERIES);
5237
- if (subQueries.length === 0) return null;
5238
- if (mode === "specific") {
5239
- return { mode: "specific", subQueries: [originalQuery] };
5240
- }
5241
- return { mode, subQueries };
5242
- }
5243
-
5244
5512
  // src/lib/memoryVault/lruCache.ts
5245
5513
  var DEFAULT_VAULT_CACHE_SIZE = 5e3;
5246
5514
  var LRUCache = class extends Map {
@@ -5274,6 +5542,7 @@ function createVaultEmbeddingCache(maxSize = DEFAULT_VAULT_CACHE_SIZE) {
5274
5542
  }
5275
5543
 
5276
5544
  // src/lib/memoryVault/searchTool.ts
5545
+ var warnedProgrammaticDecomposeIgnored = false;
5277
5546
  function normalizeEventTimeKind(kind) {
5278
5547
  return kind === "point" || kind === "range" || kind === "ongoing" ? kind : null;
5279
5548
  }
@@ -6208,52 +6477,51 @@ async function rankPreparedVaultCandidates(query, prepared, embeddingOptions, se
6208
6477
  ...searchOptions?.rrfK !== void 0 && { rrfK: searchOptions.rrfK },
6209
6478
  ...searchOptions?.factTypeWeights && { factTypeWeights: searchOptions.factTypeWeights }
6210
6479
  };
6211
- if (useFusion && !embeddingsUnavailable && searchOptions?.decompose === "llm" && searchOptions.decomposeOptions) {
6212
- const decomp = await decomposeQuery(query, searchOptions.decomposeOptions);
6213
- if (decomp.mode === "composite") {
6214
- let subEmbeddings;
6215
- try {
6216
- subEmbeddings = await generateEmbeddings(decomp.subQueries, embeddingOptions);
6217
- } catch (err) {
6218
- getLogger().warn(
6219
- "memoryVault: sub-query embedding failed \u2014 falling back to single-query ranking: " + (err instanceof Error ? err.message : String(err))
6220
- );
6221
- subEmbeddings = [];
6222
- }
6223
- const subEmbeddingsUsable = decomp.subQueries.length > 0 && subEmbeddings.length === decomp.subQueries.length && subEmbeddings.every((v) => v.length > 0 && v.length === queryEmbedding.length);
6224
- if (subEmbeddings.length > 0 && !subEmbeddingsUsable) {
6225
- getLogger().warn(
6226
- `memoryVault: sub-query embedding returned ${subEmbeddings.length} vectors (dims ${subEmbeddings.map((v) => v.length).join(",")}) for ${decomp.subQueries.length} sub-queries against a ${queryEmbedding.length}-dim query \u2014 falling back to single-query ranking`
6227
- );
6228
- }
6229
- if (subEmbeddingsUsable) {
6230
- const subQueries = decomp.subQueries.map((sq, i) => ({
6231
- query: sq,
6232
- embedding: subEmbeddings[i]
6233
- }));
6234
- const v2HeadStats = { hadResults: false };
6235
- const composite = await rankComposite(query, queryEmbedding, subQueries, embeddedItems, {
6236
- limit,
6237
- minSimilarity,
6238
- rerank: !!searchOptions.rerank,
6239
- ...searchOptions.rerankTopN !== void 0 && {
6240
- rerankTopN: searchOptions.rerankTopN
6241
- },
6242
- ...searchOptions.ceWeight !== void 0 && { ceWeight: searchOptions.ceWeight },
6243
- ...searchOptions.mmr !== void 0 && { mmr: searchOptions.mmr },
6244
- ...tuning,
6245
- ...searchOptions.entityRanking && { entityRanking: searchOptions.entityRanking },
6246
- ...searchOptions.temporalRanking && { temporalRanking: searchOptions.temporalRanking },
6247
- rerankStats,
6248
- v2HeadStats
6249
- });
6250
- return stampTimestamps({
6251
- results: composite,
6252
- vaultSize,
6253
- reranked: rerankStats.applied,
6254
- hadV2Head: v2HeadStats.hadResults
6255
- });
6256
- }
6480
+ const normalizedFacets = normalizeSubQueries(searchOptions?.subQueries);
6481
+ const facetQueries = normalizedFacets.length >= 2 ? normalizedFacets : void 0;
6482
+ if (useFusion && !embeddingsUnavailable && facetQueries) {
6483
+ let subEmbeddings;
6484
+ try {
6485
+ subEmbeddings = await generateEmbeddings(facetQueries, embeddingOptions);
6486
+ } catch (err) {
6487
+ getLogger().warn(
6488
+ "memoryVault: sub-query embedding failed \u2014 falling back to single-query ranking: " + (err instanceof Error ? err.message : String(err))
6489
+ );
6490
+ subEmbeddings = [];
6491
+ }
6492
+ const subEmbeddingsUsable = facetQueries.length > 0 && subEmbeddings.length === facetQueries.length && subEmbeddings.every((v) => v.length > 0 && v.length === queryEmbedding.length);
6493
+ if (subEmbeddings.length > 0 && !subEmbeddingsUsable) {
6494
+ getLogger().warn(
6495
+ `memoryVault: sub-query embedding returned ${subEmbeddings.length} vectors (dims ${subEmbeddings.map((v) => v.length).join(",")}) for ${facetQueries.length} sub-queries against a ${queryEmbedding.length}-dim query \u2014 falling back to single-query ranking`
6496
+ );
6497
+ }
6498
+ if (subEmbeddingsUsable) {
6499
+ const subQueries = facetQueries.map((sq, i) => ({
6500
+ query: sq,
6501
+ embedding: subEmbeddings[i]
6502
+ }));
6503
+ const v2HeadStats = { hadResults: false };
6504
+ const composite = await rankComposite(query, queryEmbedding, subQueries, embeddedItems, {
6505
+ limit,
6506
+ minSimilarity,
6507
+ rerank: !!searchOptions?.rerank,
6508
+ ...searchOptions?.rerankTopN !== void 0 && {
6509
+ rerankTopN: searchOptions.rerankTopN
6510
+ },
6511
+ ...searchOptions?.ceWeight !== void 0 && { ceWeight: searchOptions.ceWeight },
6512
+ ...searchOptions?.mmr !== void 0 && { mmr: searchOptions.mmr },
6513
+ ...tuning,
6514
+ ...searchOptions?.entityRanking && { entityRanking: searchOptions.entityRanking },
6515
+ ...searchOptions?.temporalRanking && { temporalRanking: searchOptions.temporalRanking },
6516
+ rerankStats,
6517
+ v2HeadStats
6518
+ });
6519
+ return stampTimestamps({
6520
+ results: composite,
6521
+ vaultSize,
6522
+ reranked: rerankStats.applied,
6523
+ hadV2Head: v2HeadStats.hadResults
6524
+ });
6257
6525
  }
6258
6526
  }
6259
6527
  if (useFusion && searchOptions?.rerank) {
@@ -6313,6 +6581,12 @@ async function searchVaultMemoriesWithSize(query, vaultCtx, embeddingOptions, ca
6313
6581
  rankedOnCosine: false
6314
6582
  };
6315
6583
  }
6584
+ if (searchOptions?.decompose === "llm" && normalizeSubQueries(searchOptions.subQueries).length < 2 && !warnedProgrammaticDecomposeIgnored) {
6585
+ warnedProgrammaticDecomposeIgnored = true;
6586
+ getLogger().warn(
6587
+ 'memoryVault: decompose:"llm" is ignored by searchVaultMemories \u2014 pass subQueries or use createMemoryVaultSearchTool / createRecallTool (719/B4)'
6588
+ );
6589
+ }
6316
6590
  const prepared = await prepareVaultCandidates(
6317
6591
  query,
6318
6592
  vaultCtx,
@@ -6418,7 +6692,8 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
6418
6692
  return "Error: A search query is required.";
6419
6693
  }
6420
6694
  try {
6421
- const budget = searchOptions?.decompose === "llm" && searchOptions.decomposeOptions ? "high" : searchOptions?.rerank ? "mid" : "low";
6695
+ const wantsDecompose = searchOptions?.decompose === "llm" && !!searchOptions.decomposeOptions;
6696
+ const budget = wantsDecompose ? "high" : searchOptions?.rerank ? "mid" : "low";
6422
6697
  const folderId = searchOptions?.folderId ?? argFolderId;
6423
6698
  if (searchOptions?.useFusion === false) {
6424
6699
  const { results: legacy, embeddingsUnavailable } = await searchVaultMemoriesWithSize(
@@ -6442,7 +6717,14 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
6442
6717
  legacy.map((r) => ({ id: r.uniqueId, content: r.content, score: r.similarity }))
6443
6718
  );
6444
6719
  }
6445
- const { recall: recall2 } = await import("./recall-WXCZHXHZ.mjs");
6720
+ let subQueries;
6721
+ if (wantsDecompose && searchOptions?.decomposeOptions) {
6722
+ const decomp = await decomposeQuery(query, searchOptions.decomposeOptions);
6723
+ if (decomp.mode === "composite" && decomp.subQueries.length >= 2) {
6724
+ subQueries = decomp.subQueries;
6725
+ }
6726
+ }
6727
+ const { recall: recall2 } = await import("./recall-DVX4W2UD.mjs");
6446
6728
  let recallDegraded = [];
6447
6729
  const result = await recall2(
6448
6730
  query,
@@ -6458,9 +6740,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
6458
6740
  ...tuningForward,
6459
6741
  ...folderId !== void 0 && { folderId },
6460
6742
  ...searchOptions?.scopes && { scopes: searchOptions.scopes },
6461
- ...searchOptions?.decompose === "llm" && searchOptions.decomposeOptions && {
6462
- decomposeOptions: searchOptions.decomposeOptions
6463
- }
6743
+ ...subQueries && { subQueries }
6464
6744
  }
6465
6745
  );
6466
6746
  if (result.vaultSize === 0) {
@@ -6988,12 +7268,12 @@ var DEFAULT_CHUNK_MIN_SCORE = 0.5;
6988
7268
  function flagsForBudget(budget) {
6989
7269
  switch (budget) {
6990
7270
  case "high":
6991
- return { rerank: true, decompose: true, traverse: true };
7271
+ return { rerank: true, traverse: true };
6992
7272
  case "mid":
6993
- return { rerank: true, decompose: false, traverse: false };
7273
+ return { rerank: true, traverse: false };
6994
7274
  case "low":
6995
7275
  default:
6996
- return { rerank: false, decompose: false, traverse: false };
7276
+ return { rerank: false, traverse: false };
6997
7277
  }
6998
7278
  }
6999
7279
  function dedupeBy(items, ...keys) {
@@ -7012,10 +7292,10 @@ function dedupeBy(items, ...keys) {
7012
7292
  async function recall(query, ctx, options = {}) {
7013
7293
  const types = options.types ?? ["fact"];
7014
7294
  const limit = options.limit ?? DEFAULT_LIMIT;
7015
- const requestedBudget = options.budget ?? DEFAULT_BUDGET;
7016
- const flags = flagsForBudget(requestedBudget);
7017
- const decomposeAvailable = flags.decompose && !!options.decomposeOptions;
7018
- const usedBudget = requestedBudget === "high" && !decomposeAvailable ? "mid" : requestedBudget;
7295
+ const usedBudget = options.budget ?? DEFAULT_BUDGET;
7296
+ const flags = flagsForBudget(usedBudget);
7297
+ const normalizedFacets = normalizeSubQueries(options.subQueries);
7298
+ const subQueries = normalizedFacets.length >= 2 ? normalizedFacets : void 0;
7019
7299
  const t0 = nowMs();
7020
7300
  let prepMs = 0;
7021
7301
  let factLaneMs = 0;
@@ -7036,7 +7316,9 @@ async function recall(query, ctx, options = {}) {
7036
7316
  if (flags.rerank && factResults.length > 0 && hadV2Head && !didRerank) {
7037
7317
  degraded.push("rerank-unavailable");
7038
7318
  }
7039
- if (flags.decompose && !decomposeAvailable) degraded.push("decompose-unavailable");
7319
+ if (usedBudget === "high" && options.decomposeOptions && !subQueries && !options.graphRefine) {
7320
+ degraded.push("decompose-moved");
7321
+ }
7040
7322
  if (embeddingsUnavailable) degraded.push("embeddings-unavailable");
7041
7323
  const diagnostics = {
7042
7324
  usedBudget,
@@ -7149,10 +7431,8 @@ async function recall(query, ctx, options = {}) {
7149
7431
  },
7150
7432
  ...options.rrfK !== void 0 && { rrfK: options.rrfK },
7151
7433
  ...options.decryptLast !== void 0 && { decryptLast: options.decryptLast },
7152
- ...decomposeAvailable && {
7153
- decompose: "llm",
7154
- decomposeOptions: options.decomposeOptions
7155
- },
7434
+ // 719/B4 — composite facets arrive pre-built; no LLM call here.
7435
+ ...subQueries && { subQueries },
7156
7436
  ...options.scopes && { scopes: options.scopes },
7157
7437
  ...options.folderId !== void 0 && { folderId: options.folderId },
7158
7438
  ...options.factTypes?.length && { factTypes: options.factTypes },
@@ -7382,6 +7662,7 @@ function toChunkMemory(r) {
7382
7662
 
7383
7663
  export {
7384
7664
  createSseClient,
7665
+ EncryptionKeyMissingError,
7385
7666
  onClearAllEncryptionState,
7386
7667
  onKeyAvailable,
7387
7668
  clearEncryptionKey,
@@ -7394,11 +7675,13 @@ export {
7394
7675
  decryptDataBytes,
7395
7676
  decryptDataBytesFromBytes,
7396
7677
  hasEncryptionKey,
7678
+ seedEncryptionKeys,
7397
7679
  encryptDataWithKey,
7398
7680
  decryptDataWithKey,
7399
7681
  encryptDataBatch,
7400
7682
  decryptDataBatch,
7401
7683
  requestEncryptionKey,
7684
+ refreshEncryptionKeyIfMatches,
7402
7685
  requestKeyPair,
7403
7686
  exportPublicKey,
7404
7687
  hasKeyPair,
@@ -7522,6 +7805,7 @@ export {
7522
7805
  EMBEDDINGS_DEGRADED_EMPTY,
7523
7806
  RerankerUnavailableError,
7524
7807
  isRerankerAvailable,
7808
+ decomposeQuery,
7525
7809
  DEFAULT_VAULT_CACHE_SIZE,
7526
7810
  LRUCache,
7527
7811
  createVaultEmbeddingCache,