@anuma/sdk 10.3.1 → 10.4.1

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.
@@ -4091,7 +4091,7 @@ var mergeHeaders = (...headers) => {
4091
4091
  if (!header || typeof header !== "object") {
4092
4092
  continue;
4093
4093
  }
4094
- const iterator = Array.isArray(header) || header instanceof Headers ? headersEntries(new Headers(header)) : Object.entries(header);
4094
+ const iterator = Array.isArray(header) ? header : header instanceof Headers ? headersEntries(header) : Object.entries(header);
4095
4095
  for (const [key3, value] of iterator) {
4096
4096
  if (value === null) {
4097
4097
  mergedHeaders.delete(key3);
@@ -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",
@@ -32374,6 +32729,7 @@ export {
32374
32729
  createSelectPaymentWalletTool,
32375
32730
  createStockPricePreProcessor,
32376
32731
  createStreamingDeAnonymizer,
32732
+ createVaultAttachments,
32377
32733
  createVaultChatBackend,
32378
32734
  createVaultEmbeddingCache,
32379
32735
  createVaultFolderOp,
@@ -32438,9 +32794,11 @@ export {
32438
32794
  isRerankerAvailable,
32439
32795
  localChatBackend,
32440
32796
  maskScopedEmbeddingCache,
32797
+ maxVaultAttachmentBytes,
32441
32798
  memoryCipher,
32442
32799
  migrateChatsToRemote,
32443
32800
  migrateMemoriesToRemote,
32801
+ migrateMessageAttachments,
32444
32802
  moveMemoriesToFolderOp,
32445
32803
  noopLogger,
32446
32804
  onKeyAvailable,
@@ -32494,6 +32852,7 @@ export {
32494
32852
  useCredits,
32495
32853
  useEncryption,
32496
32854
  useModels,
32855
+ vaultAttachmentRefs,
32497
32856
  vaultChatBackend,
32498
32857
  verifyMemoriesForPublish,
32499
32858
  xhrTransport
package/dist/index.cjs CHANGED
@@ -713,7 +713,7 @@ var mergeHeaders = (...headers) => {
713
713
  if (!header || typeof header !== "object") {
714
714
  continue;
715
715
  }
716
- const iterator = Array.isArray(header) || header instanceof Headers ? headersEntries(new Headers(header)) : Object.entries(header);
716
+ const iterator = Array.isArray(header) ? header : header instanceof Headers ? headersEntries(header) : Object.entries(header);
717
717
  for (const [key, value] of iterator) {
718
718
  if (value === null) {
719
719
  mergedHeaders.delete(key);