@anuma/sdk 10.3.0 → 10.4.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.
@@ -11855,6 +11855,360 @@ async function migrateChatsToRemote(options) {
11855
11855
  return passes(false);
11856
11856
  }
11857
11857
 
11858
+ // src/lib/chatStore/vaultAttachments.ts
11859
+ var frameVersion = 1;
11860
+ var ivBytes = 12;
11861
+ var tagBytes = 16;
11862
+ var frameOverhead = 1 + ivBytes + tagBytes;
11863
+ var maxCiphertextBytes = 50 * 1024 * 1024;
11864
+ var expirySkewMs = 3e4;
11865
+ var maxRetryAfterMs = 3e4;
11866
+ var maxRefWrites = 5;
11867
+ var uploadWindowMs = 16 * 6e4;
11868
+ var attachmentIdPattern2 = /^[0-9a-f]{32}$/;
11869
+ var maxVaultAttachmentBytes = maxCiphertextBytes - frameOverhead;
11870
+ function additionalData(attachmentId) {
11871
+ return new TextEncoder().encode(`anuma-chat-attachment-v1:${attachmentId}`);
11872
+ }
11873
+ function invalidAttachment() {
11874
+ throw new ChatStoreError("Attachment does not decrypt under this key", 0, "invalid_attachment");
11875
+ }
11876
+ function sendableHeaders(headers) {
11877
+ return Object.fromEntries(
11878
+ Object.entries(headers).filter(([name]) => name.toLowerCase() !== "content-length")
11879
+ );
11880
+ }
11881
+ function transientStatus(status) {
11882
+ return status === 429 || status >= 500;
11883
+ }
11884
+ async function discardBody(response) {
11885
+ await response.body?.cancel().catch(() => void 0);
11886
+ }
11887
+ function createVaultAttachments(options) {
11888
+ const { store, keyRing, cache } = options;
11889
+ const fetcher = options.fetch ?? globalThis.fetch.bind(globalThis);
11890
+ const retries = options.retries ?? 3;
11891
+ const retryDelayMs = options.retryDelayMs ?? 500;
11892
+ const now = options.now ?? Date.now;
11893
+ const sleep3 = (ms2, signal) => new Promise((resolve, reject) => {
11894
+ signal?.throwIfAborted();
11895
+ const timer = setTimeout(() => {
11896
+ signal?.removeEventListener("abort", onAbort);
11897
+ resolve();
11898
+ }, ms2);
11899
+ const onAbort = () => {
11900
+ clearTimeout(timer);
11901
+ reject(signal?.reason);
11902
+ };
11903
+ signal?.addEventListener("abort", onAbort, { once: true });
11904
+ });
11905
+ const pause = (attempt, signal, atLeast = 0) => sleep3(Math.max(atLeast, retryDelayMs * 2 ** attempt), signal);
11906
+ const withRetry = async (call, signal) => {
11907
+ for (let attempt = 0; ; attempt++) {
11908
+ try {
11909
+ return await call();
11910
+ } catch (error) {
11911
+ if (!(error instanceof ChatStoreError) || !transientStatus(error.status) || attempt >= retries || (error.retryAfterMs ?? 0) > maxRetryAfterMs)
11912
+ throw error;
11913
+ await pause(attempt, signal, error.retryAfterMs);
11914
+ }
11915
+ }
11916
+ };
11917
+ const expired = (url) => now() >= Date.parse(url.expiresAt) - expirySkewMs;
11918
+ const seal = async (bytes4, attachmentId) => {
11919
+ const iv = crypto.getRandomValues(new Uint8Array(ivBytes));
11920
+ const sealed2 = new Uint8Array(
11921
+ await crypto.subtle.encrypt(
11922
+ { name: "AES-GCM", iv, additionalData: additionalData(attachmentId) },
11923
+ keyRing.v3,
11924
+ bytes4
11925
+ )
11926
+ );
11927
+ const frame = new Uint8Array(1 + ivBytes + sealed2.length);
11928
+ frame[0] = frameVersion;
11929
+ frame.set(iv, 1);
11930
+ frame.set(sealed2, 1 + ivBytes);
11931
+ return frame;
11932
+ };
11933
+ const open = async (frame, attachmentId) => {
11934
+ if (frame.length < frameOverhead || frame[0] !== frameVersion) invalidAttachment();
11935
+ try {
11936
+ return new Uint8Array(
11937
+ await crypto.subtle.decrypt(
11938
+ {
11939
+ name: "AES-GCM",
11940
+ iv: frame.slice(1, 1 + ivBytes),
11941
+ additionalData: additionalData(attachmentId)
11942
+ },
11943
+ keyRing.v3,
11944
+ frame.slice(1 + ivBytes)
11945
+ )
11946
+ );
11947
+ } catch {
11948
+ return invalidAttachment();
11949
+ }
11950
+ };
11951
+ const put = async (upload, body, signal) => {
11952
+ for (let attempt = 0; ; attempt++) {
11953
+ if (expired(upload)) return "expired";
11954
+ let response;
11955
+ try {
11956
+ response = await fetcher(upload.url, {
11957
+ method: upload.method,
11958
+ headers: sendableHeaders(upload.headers),
11959
+ body,
11960
+ signal
11961
+ });
11962
+ } catch (error) {
11963
+ if (signal?.aborted || attempt >= retries) throw error;
11964
+ await pause(attempt, signal);
11965
+ continue;
11966
+ }
11967
+ await discardBody(response);
11968
+ if (response.ok) return "stored";
11969
+ if (response.status === 403) return "expired";
11970
+ if (!transientStatus(response.status) || attempt >= retries)
11971
+ throw new ChatStoreError(
11972
+ `Attachment upload failed with ${response.status}`,
11973
+ response.status,
11974
+ "attachment_upload_failed"
11975
+ );
11976
+ await pause(attempt, signal);
11977
+ }
11978
+ };
11979
+ const download = async (url, signal) => {
11980
+ for (let attempt = 0; ; attempt++) {
11981
+ if (expired(url)) return "expired";
11982
+ let response;
11983
+ try {
11984
+ response = await fetcher(url.url, {
11985
+ method: url.method,
11986
+ headers: sendableHeaders(url.headers),
11987
+ signal
11988
+ });
11989
+ if (response.ok) return new Uint8Array(await response.arrayBuffer());
11990
+ } catch (error) {
11991
+ if (signal?.aborted || attempt >= retries) throw error;
11992
+ await pause(attempt, signal);
11993
+ continue;
11994
+ }
11995
+ await discardBody(response);
11996
+ if (response.status === 403) return "expired";
11997
+ if (response.status === 404) return "missing";
11998
+ if (!transientStatus(response.status) || attempt >= retries)
11999
+ throw new ChatStoreError(
12000
+ `Attachment download failed with ${response.status}`,
12001
+ response.status,
12002
+ "attachment_download_failed"
12003
+ );
12004
+ await pause(attempt, signal);
12005
+ }
12006
+ };
12007
+ const remove = async (attachmentId, signal) => {
12008
+ try {
12009
+ await withRetry(() => store.attachments.delete(attachmentId, signal), signal);
12010
+ } catch (error) {
12011
+ if (!(error instanceof ChatStoreError) || error.status !== 404) throw error;
12012
+ }
12013
+ await cache?.delete(attachmentId).catch(() => void 0);
12014
+ };
12015
+ const discardPending = async (attachmentId) => {
12016
+ const found = await store.attachments.get(attachmentId);
12017
+ if (found?.attachment.state === "pending") await remove(attachmentId);
12018
+ };
12019
+ return {
12020
+ async upload(bytes4, uploadOptions) {
12021
+ const signal = uploadOptions?.signal;
12022
+ if (!(bytes4 instanceof Uint8Array) || bytes4.length > maxVaultAttachmentBytes)
12023
+ throw new ChatStoreError(
12024
+ "Attachment is larger than vault accepts",
12025
+ 413,
12026
+ "attachment_too_large"
12027
+ );
12028
+ const size = bytes4.length + frameOverhead;
12029
+ for (let attempt = 0; ; attempt++) {
12030
+ const { attachment: attachment2, upload } = await withRetry(
12031
+ () => store.attachments.create(size, signal),
12032
+ signal
12033
+ );
12034
+ try {
12035
+ await uploadOptions?.onReserved?.(attachment2.id);
12036
+ const frame = await seal(bytes4, attachment2.id);
12037
+ if (await put(upload, frame, signal) === "stored") return attachment2.id;
12038
+ } catch (error) {
12039
+ await discardPending(attachment2.id).catch(() => void 0);
12040
+ throw error;
12041
+ }
12042
+ await remove(attachment2.id).catch(() => void 0);
12043
+ if (attempt >= retries)
12044
+ throw new ChatStoreError(
12045
+ "Attachment storage rejected the upload URL",
12046
+ 403,
12047
+ "attachment_upload_rejected"
12048
+ );
12049
+ }
12050
+ },
12051
+ commit(attachmentId, owner, signal) {
12052
+ return withRetry(() => store.attachments.commit(attachmentId, owner, signal), signal);
12053
+ },
12054
+ async read(attachmentId, signal) {
12055
+ if (!attachmentIdPattern2.test(attachmentId)) invalidAttachment();
12056
+ const cached = await cache?.get(attachmentId).catch(() => null);
12057
+ if (cached) return cached;
12058
+ for (let attempt = 0; ; attempt++) {
12059
+ const found = await withRetry(() => store.attachments.get(attachmentId, signal), signal);
12060
+ if (!found?.download) return null;
12061
+ const frame = await download(found.download, signal);
12062
+ if (frame === "missing") return null;
12063
+ if (frame === "expired") {
12064
+ if (attempt >= retries)
12065
+ throw new ChatStoreError(
12066
+ "Attachment storage rejected the download URL",
12067
+ 403,
12068
+ "attachment_download_rejected"
12069
+ );
12070
+ continue;
12071
+ }
12072
+ if (frame.length !== found.attachment.sizeBytes) invalidAttachment();
12073
+ const bytes4 = await open(frame, attachmentId);
12074
+ await cache?.set(attachmentId, bytes4).catch(() => void 0);
12075
+ return bytes4;
12076
+ }
12077
+ },
12078
+ delete: remove
12079
+ };
12080
+ }
12081
+ function validRef(value) {
12082
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
12083
+ const ref3 = value;
12084
+ return typeof ref3.attachmentId === "string" && attachmentIdPattern2.test(ref3.attachmentId) && typeof ref3.fileId === "string" && ref3.fileId.length > 0 && typeof ref3.name === "string" && typeof ref3.type === "string" && Number.isSafeInteger(ref3.size) && ref3.size >= 0;
12085
+ }
12086
+ function vaultAttachmentRefs(record2) {
12087
+ const refs = record2.data.vaultAttachments;
12088
+ return Array.isArray(refs) ? refs.filter(validRef) : [];
12089
+ }
12090
+ function journalKey(messageId, fileId) {
12091
+ return JSON.stringify([messageId, fileId]);
12092
+ }
12093
+ async function migrateMessageAttachments(options) {
12094
+ const { store, attachments, journal, messageId, signal } = options;
12095
+ const now = options.now ?? Date.now;
12096
+ const owner = { kind: "message", id: messageId };
12097
+ const ownerGone = () => new ChatStoreError("Message is not in vault", 409, "attachment_owner_not_found");
12098
+ let snapshot = await store.get("message", messageId, signal);
12099
+ if (!snapshot || snapshot.record.isDeleted) throw ownerGone();
12100
+ const present = new Map(vaultAttachmentRefs(snapshot.record).map((r) => [r.fileId, r]));
12101
+ const uploadable = (attachment2) => attachment2.state === "pending" && now() < Date.parse(attachment2.createdAt) + uploadWindowMs;
12102
+ const lookup = async (attachmentId) => attachmentId && attachmentIdPattern2.test(attachmentId) ? (await store.attachments.get(attachmentId, signal))?.attachment ?? null : null;
12103
+ const bind = async (found) => {
12104
+ if (!found) return null;
12105
+ if (found.state === "committed")
12106
+ return found.ownerKind === owner.kind && found.ownerId === owner.id ? found : null;
12107
+ try {
12108
+ return await attachments.commit(found.id, owner, signal);
12109
+ } catch (error) {
12110
+ if (!(error instanceof ChatStoreError) || error.code !== "attachment_not_uploaded")
12111
+ throw error;
12112
+ if (uploadable(found)) return "busy";
12113
+ await attachments.delete(found.id, signal);
12114
+ return null;
12115
+ }
12116
+ };
12117
+ const added = [];
12118
+ const settled = [];
12119
+ const missing2 = [];
12120
+ const oversized = [];
12121
+ const deferred = [];
12122
+ const stale = /* @__PURE__ */ new Set();
12123
+ let uploaded = 0;
12124
+ for (const file of options.files) {
12125
+ signal?.throwIfAborted();
12126
+ const key3 = journalKey(messageId, file.fileId);
12127
+ const recorded = present.get(file.fileId);
12128
+ if (recorded) {
12129
+ const bound = await bind(await lookup(recorded.attachmentId));
12130
+ if (bound === "busy") {
12131
+ deferred.push(file.fileId);
12132
+ continue;
12133
+ }
12134
+ if (bound) {
12135
+ const stray = await lookup(await journal.get(key3));
12136
+ if (stray && stray.id !== recorded.attachmentId) {
12137
+ if (uploadable(stray)) continue;
12138
+ await attachments.delete(stray.id, signal);
12139
+ }
12140
+ settled.push(key3);
12141
+ continue;
12142
+ }
12143
+ stale.add(recorded.attachmentId);
12144
+ }
12145
+ const meta = { fileId: file.fileId, name: file.name, type: file.type };
12146
+ const resumed = await bind(await lookup(await journal.get(key3)));
12147
+ if (resumed === "busy") {
12148
+ deferred.push(file.fileId);
12149
+ continue;
12150
+ }
12151
+ if (resumed) {
12152
+ added.push({ attachmentId: resumed.id, ...meta, size: resumed.sizeBytes - frameOverhead });
12153
+ settled.push(key3);
12154
+ continue;
12155
+ }
12156
+ const bytes4 = await file.read();
12157
+ if (!bytes4) {
12158
+ missing2.push(file.fileId);
12159
+ continue;
12160
+ }
12161
+ if (bytes4.length > maxVaultAttachmentBytes) {
12162
+ oversized.push(file.fileId);
12163
+ continue;
12164
+ }
12165
+ const attachmentId = await attachments.upload(bytes4, {
12166
+ signal,
12167
+ onReserved: (id2) => journal.set(key3, id2)
12168
+ });
12169
+ uploaded++;
12170
+ await attachments.commit(attachmentId, owner, signal);
12171
+ added.push({ attachmentId, ...meta, size: bytes4.length });
12172
+ settled.push(key3);
12173
+ }
12174
+ const superseded = [];
12175
+ for (let attempt = 0; added.length > 0; attempt++) {
12176
+ const refs = new Map(vaultAttachmentRefs(snapshot.record).map((r) => [r.fileId, r]));
12177
+ superseded.length = 0;
12178
+ for (const ref3 of added) {
12179
+ const prior = refs.get(ref3.fileId);
12180
+ if (prior && prior.attachmentId !== ref3.attachmentId && !stale.has(prior.attachmentId))
12181
+ superseded.push(ref3.attachmentId);
12182
+ else refs.set(ref3.fileId, ref3);
12183
+ }
12184
+ if (superseded.length === added.length) break;
12185
+ try {
12186
+ [snapshot] = await store.putMany(
12187
+ [
12188
+ {
12189
+ record: {
12190
+ ...snapshot.record,
12191
+ data: { ...snapshot.record.data, ...plain({ vaultAttachments: [...refs.values()] }) }
12192
+ },
12193
+ expectedVersion: snapshot.version
12194
+ }
12195
+ ],
12196
+ signal
12197
+ );
12198
+ break;
12199
+ } catch (error) {
12200
+ if (!(error instanceof ChatStoreError) || error.code !== "version_conflict" || attempt + 1 >= maxRefWrites)
12201
+ throw error;
12202
+ const latest = await store.get("message", messageId, signal);
12203
+ if (!latest || latest.record.isDeleted) throw ownerGone();
12204
+ snapshot = latest;
12205
+ }
12206
+ }
12207
+ for (const id2 of superseded) await attachments.delete(id2, signal);
12208
+ for (const key3 of settled) await journal.set(key3, null);
12209
+ return { refs: vaultAttachmentRefs(snapshot.record), uploaded, missing: missing2, oversized, deferred };
12210
+ }
12211
+
11858
12212
  // src/lib/chatStore/vaultBackend.ts
11859
12213
  import { v7 as uuidv73 } from "uuid";
11860
12214
 
@@ -11994,6 +12348,7 @@ var resumableFields = [
11994
12348
  "imageModel",
11995
12349
  "files",
11996
12350
  "fileIds",
12351
+ "vaultAttachments",
11997
12352
  "usage",
11998
12353
  "sources",
11999
12354
  "responseDuration",
@@ -24231,7 +24586,7 @@ function createRemoteMemoryStore(options) {
24231
24586
  };
24232
24587
  const mutate = (id2, change, edit, writeOptions) => serialized(id2, () => mutateLocked(id2, change, edit, writeOptions));
24233
24588
  const serializedAll = (ids, task) => [...new Set(ids)].sort().reduceRight((inner, id2) => () => serialized(id2, inner), task)();
24234
- const mutateMany = async (ids, change, edit, leaseToken, signal) => {
24589
+ const mutateMany = async (ids, change, edit, leaseToken, signal, includeEmbeddings = false) => {
24235
24590
  const saved = /* @__PURE__ */ new Map();
24236
24591
  const failed = /* @__PURE__ */ new Map();
24237
24592
  for (const slice of chunks2([...new Set(ids)], MAX_BATCH)) {
@@ -24240,7 +24595,7 @@ function createRemoteMemoryStore(options) {
24240
24595
  const writes = [];
24241
24596
  let read;
24242
24597
  try {
24243
- read = await readEveryState(slice, signal);
24598
+ read = await readEveryState(slice, signal, includeEmbeddings);
24244
24599
  } catch (error) {
24245
24600
  signal?.throwIfAborted();
24246
24601
  for (const id2 of slice) failed.set(id2, error);
@@ -24299,21 +24654,55 @@ function createRemoteMemoryStore(options) {
24299
24654
  }
24300
24655
  return { saved, failed };
24301
24656
  };
24302
- const embedInBackground = ({ memory: { memory_id: id2, content } }) => {
24303
- generateEmbedding(content, embeddingOptions).then(
24304
- (vector2) => serialized(id2, async () => {
24305
- const current = await live(id2);
24306
- if (!current || current.memory.content !== content || current.memory.embedding?.length)
24307
- return;
24308
- const saved = await persistence.put(
24309
- { ...current.memory, embedding: storedVector(vector2), embedding_model: model },
24310
- current
24311
- );
24312
- notify("embedding", [saved]);
24313
- })
24314
- ).catch((error) => {
24315
- getLogger().warn("[memory/remote-store] Failed to embed memory:", error);
24316
- });
24657
+ const pendingEmbeddings = /* @__PURE__ */ new Map();
24658
+ let embeddingActive = false;
24659
+ const drainEmbeddings = async () => {
24660
+ try {
24661
+ while (pendingEmbeddings.size) {
24662
+ const batch = /* @__PURE__ */ new Map();
24663
+ for (const [id2, content] of pendingEmbeddings) {
24664
+ pendingEmbeddings.delete(id2);
24665
+ batch.set(id2, content);
24666
+ if (batch.size === MAX_BATCH) break;
24667
+ }
24668
+ try {
24669
+ const vectors = await generateEmbeddings([...batch.values()], embeddingOptions);
24670
+ const embeddings2 = /* @__PURE__ */ new Map();
24671
+ [...batch.keys()].forEach((id2, i) => {
24672
+ try {
24673
+ embeddings2.set(id2, storedVector(vectors[i]));
24674
+ } catch (error) {
24675
+ getLogger().warn("[memory/remote-store] Failed to embed memory:", error);
24676
+ }
24677
+ });
24678
+ if (!embeddings2.size) continue;
24679
+ const { failed } = await mutateMany(
24680
+ [...embeddings2.keys()],
24681
+ () => "embedding",
24682
+ (memory) => memory.content !== batch.get(memory.memory_id) || memory.embedding?.length ? null : {
24683
+ ...memory,
24684
+ embedding: embeddings2.get(memory.memory_id),
24685
+ embedding_model: model
24686
+ },
24687
+ void 0,
24688
+ void 0,
24689
+ true
24690
+ );
24691
+ for (const error of failed.values())
24692
+ getLogger().warn("[memory/remote-store] Failed to embed memory:", error);
24693
+ } catch (error) {
24694
+ getLogger().warn("[memory/remote-store] Failed to embed memories:", error);
24695
+ }
24696
+ }
24697
+ } finally {
24698
+ embeddingActive = false;
24699
+ }
24700
+ };
24701
+ const embedInBackground = (records) => {
24702
+ for (const { memory } of records) pendingEmbeddings.set(memory.memory_id, memory.content);
24703
+ if (embeddingActive || !pendingEmbeddings.size) return;
24704
+ embeddingActive = true;
24705
+ void drainEmbeddings();
24317
24706
  };
24318
24707
  const newRow = (input) => {
24319
24708
  const now = Date.now();
@@ -24347,9 +24736,7 @@ function createRemoteMemoryStore(options) {
24347
24736
  };
24348
24737
  };
24349
24738
  const created = (records, inputs) => {
24350
- records.forEach((record2, i) => {
24351
- if (inputs[i].embedding === void 0) embedInBackground(record2);
24352
- });
24739
+ embedInBackground(records.filter((_, i) => inputs[i].embedding === void 0));
24353
24740
  notify("membership", records);
24354
24741
  return records.map((record2) => stored(record2.memory));
24355
24742
  };
@@ -24379,7 +24766,7 @@ function createRemoteMemoryStore(options) {
24379
24766
  };
24380
24767
  };
24381
24768
  const withKnownKinds = async (topics) => topics.map(await knownKinds(topics));
24382
- const readEveryState = async (ids, signal) => {
24769
+ const readEveryState = async (ids, signal, includeEmbeddings = false) => {
24383
24770
  const records = /* @__PURE__ */ new Map();
24384
24771
  const unreadable = /* @__PURE__ */ new Set();
24385
24772
  for (const memory_ids of chunks2(ids, MAX_QUERY_IDS)) {
@@ -24391,6 +24778,7 @@ function createRemoteMemoryStore(options) {
24391
24778
  include_archived: true,
24392
24779
  include_quarantined: true,
24393
24780
  include_superseded: true,
24781
+ ...includeEmbeddings && { include_embeddings: true },
24394
24782
  limit: 200,
24395
24783
  ...signal && { signal },
24396
24784
  ...cursor2 && { cursor: cursor2 }
@@ -25053,7 +25441,7 @@ function createRemoteMemoryStore(options) {
25053
25441
  }
25054
25442
  return memory;
25055
25443
  });
25056
- if (saved && patch.embedding === void 0) embedInBackground(saved);
25444
+ if (saved && patch.embedding === void 0) embedInBackground([saved]);
25057
25445
  return asStored(saved);
25058
25446
  },
25059
25447
  delete: async (id2) => !!await mutate(id2, "membership", (memory) => ({ ...memory, is_deleted: true })),
@@ -32341,6 +32729,7 @@ export {
32341
32729
  createSelectPaymentWalletTool,
32342
32730
  createStockPricePreProcessor,
32343
32731
  createStreamingDeAnonymizer,
32732
+ createVaultAttachments,
32344
32733
  createVaultChatBackend,
32345
32734
  createVaultEmbeddingCache,
32346
32735
  createVaultFolderOp,
@@ -32405,9 +32794,11 @@ export {
32405
32794
  isRerankerAvailable,
32406
32795
  localChatBackend,
32407
32796
  maskScopedEmbeddingCache,
32797
+ maxVaultAttachmentBytes,
32408
32798
  memoryCipher,
32409
32799
  migrateChatsToRemote,
32410
32800
  migrateMemoriesToRemote,
32801
+ migrateMessageAttachments,
32411
32802
  moveMemoriesToFolderOp,
32412
32803
  noopLogger,
32413
32804
  onKeyAvailable,
@@ -32461,6 +32852,7 @@ export {
32461
32852
  useCredits,
32462
32853
  useEncryption,
32463
32854
  useModels,
32855
+ vaultAttachmentRefs,
32464
32856
  vaultChatBackend,
32465
32857
  verifyMemoriesForPublish,
32466
32858
  xhrTransport