@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.
@@ -755,7 +755,7 @@ var mergeHeaders = (...headers) => {
755
755
  if (!header || typeof header !== "object") {
756
756
  continue;
757
757
  }
758
- const iterator = Array.isArray(header) || header instanceof Headers ? headersEntries(new Headers(header)) : Object.entries(header);
758
+ const iterator = Array.isArray(header) ? header : header instanceof Headers ? headersEntries(header) : Object.entries(header);
759
759
  for (const [key, value] of iterator) {
760
760
  if (value === null) {
761
761
  mergedHeaders.delete(key);
@@ -529,7 +529,7 @@ var mergeHeaders = (...headers) => {
529
529
  if (!header || typeof header !== "object") {
530
530
  continue;
531
531
  }
532
- const iterator = Array.isArray(header) || header instanceof Headers ? headersEntries(new Headers(header)) : Object.entries(header);
532
+ const iterator = Array.isArray(header) ? header : header instanceof Headers ? headersEntries(header) : Object.entries(header);
533
533
  for (const [key, value] of iterator) {
534
534
  if (value === null) {
535
535
  mergedHeaders.delete(key);
@@ -1534,6 +1534,7 @@ __export(index_exports, {
1534
1534
  createSelectPaymentWalletTool: () => createSelectPaymentWalletTool,
1535
1535
  createStockPricePreProcessor: () => createStockPricePreProcessor,
1536
1536
  createStreamingDeAnonymizer: () => createStreamingDeAnonymizer,
1537
+ createVaultAttachments: () => createVaultAttachments,
1537
1538
  createVaultChatBackend: () => createVaultChatBackend,
1538
1539
  createVaultEmbeddingCache: () => createVaultEmbeddingCache,
1539
1540
  createVaultFolderOp: () => createVaultFolderOp,
@@ -1598,9 +1599,11 @@ __export(index_exports, {
1598
1599
  isRerankerAvailable: () => isRerankerAvailable,
1599
1600
  localChatBackend: () => localChatBackend,
1600
1601
  maskScopedEmbeddingCache: () => maskScopedEmbeddingCache,
1602
+ maxVaultAttachmentBytes: () => maxVaultAttachmentBytes,
1601
1603
  memoryCipher: () => memoryCipher,
1602
1604
  migrateChatsToRemote: () => migrateChatsToRemote,
1603
1605
  migrateMemoriesToRemote: () => migrateMemoriesToRemote,
1606
+ migrateMessageAttachments: () => migrateMessageAttachments,
1604
1607
  moveMemoriesToFolderOp: () => moveMemoriesToFolderOp,
1605
1608
  noopLogger: () => noopLogger,
1606
1609
  onKeyAvailable: () => onKeyAvailable,
@@ -1654,6 +1657,7 @@ __export(index_exports, {
1654
1657
  useCredits: () => useCredits,
1655
1658
  useEncryption: () => useEncryption,
1656
1659
  useModels: () => useModels,
1660
+ vaultAttachmentRefs: () => vaultAttachmentRefs,
1657
1661
  vaultChatBackend: () => vaultChatBackend,
1658
1662
  verifyMemoriesForPublish: () => verifyMemoriesForPublish,
1659
1663
  xhrTransport: () => xhrTransport
@@ -5733,7 +5737,7 @@ var mergeHeaders = (...headers) => {
5733
5737
  if (!header || typeof header !== "object") {
5734
5738
  continue;
5735
5739
  }
5736
- const iterator = Array.isArray(header) || header instanceof Headers ? headersEntries(new Headers(header)) : Object.entries(header);
5740
+ const iterator = Array.isArray(header) ? header : header instanceof Headers ? headersEntries(header) : Object.entries(header);
5737
5741
  for (const [key3, value] of iterator) {
5738
5742
  if (value === null) {
5739
5743
  mergedHeaders.delete(key3);
@@ -13521,6 +13525,360 @@ async function migrateChatsToRemote(options) {
13521
13525
  return passes(false);
13522
13526
  }
13523
13527
 
13528
+ // src/lib/chatStore/vaultAttachments.ts
13529
+ var frameVersion = 1;
13530
+ var ivBytes = 12;
13531
+ var tagBytes = 16;
13532
+ var frameOverhead = 1 + ivBytes + tagBytes;
13533
+ var maxCiphertextBytes = 50 * 1024 * 1024;
13534
+ var expirySkewMs = 3e4;
13535
+ var maxRetryAfterMs = 3e4;
13536
+ var maxRefWrites = 5;
13537
+ var uploadWindowMs = 16 * 6e4;
13538
+ var attachmentIdPattern2 = /^[0-9a-f]{32}$/;
13539
+ var maxVaultAttachmentBytes = maxCiphertextBytes - frameOverhead;
13540
+ function additionalData(attachmentId) {
13541
+ return new TextEncoder().encode(`anuma-chat-attachment-v1:${attachmentId}`);
13542
+ }
13543
+ function invalidAttachment() {
13544
+ throw new ChatStoreError("Attachment does not decrypt under this key", 0, "invalid_attachment");
13545
+ }
13546
+ function sendableHeaders(headers) {
13547
+ return Object.fromEntries(
13548
+ Object.entries(headers).filter(([name]) => name.toLowerCase() !== "content-length")
13549
+ );
13550
+ }
13551
+ function transientStatus(status) {
13552
+ return status === 429 || status >= 500;
13553
+ }
13554
+ async function discardBody(response) {
13555
+ await response.body?.cancel().catch(() => void 0);
13556
+ }
13557
+ function createVaultAttachments(options) {
13558
+ const { store, keyRing, cache } = options;
13559
+ const fetcher = options.fetch ?? globalThis.fetch.bind(globalThis);
13560
+ const retries = options.retries ?? 3;
13561
+ const retryDelayMs = options.retryDelayMs ?? 500;
13562
+ const now = options.now ?? Date.now;
13563
+ const sleep4 = (ms2, signal) => new Promise((resolve, reject) => {
13564
+ signal?.throwIfAborted();
13565
+ const timer = setTimeout(() => {
13566
+ signal?.removeEventListener("abort", onAbort);
13567
+ resolve();
13568
+ }, ms2);
13569
+ const onAbort = () => {
13570
+ clearTimeout(timer);
13571
+ reject(signal?.reason);
13572
+ };
13573
+ signal?.addEventListener("abort", onAbort, { once: true });
13574
+ });
13575
+ const pause = (attempt, signal, atLeast = 0) => sleep4(Math.max(atLeast, retryDelayMs * 2 ** attempt), signal);
13576
+ const withRetry = async (call, signal) => {
13577
+ for (let attempt = 0; ; attempt++) {
13578
+ try {
13579
+ return await call();
13580
+ } catch (error) {
13581
+ if (!(error instanceof ChatStoreError) || !transientStatus(error.status) || attempt >= retries || (error.retryAfterMs ?? 0) > maxRetryAfterMs)
13582
+ throw error;
13583
+ await pause(attempt, signal, error.retryAfterMs);
13584
+ }
13585
+ }
13586
+ };
13587
+ const expired = (url) => now() >= Date.parse(url.expiresAt) - expirySkewMs;
13588
+ const seal = async (bytes4, attachmentId) => {
13589
+ const iv = crypto.getRandomValues(new Uint8Array(ivBytes));
13590
+ const sealed2 = new Uint8Array(
13591
+ await crypto.subtle.encrypt(
13592
+ { name: "AES-GCM", iv, additionalData: additionalData(attachmentId) },
13593
+ keyRing.v3,
13594
+ bytes4
13595
+ )
13596
+ );
13597
+ const frame = new Uint8Array(1 + ivBytes + sealed2.length);
13598
+ frame[0] = frameVersion;
13599
+ frame.set(iv, 1);
13600
+ frame.set(sealed2, 1 + ivBytes);
13601
+ return frame;
13602
+ };
13603
+ const open = async (frame, attachmentId) => {
13604
+ if (frame.length < frameOverhead || frame[0] !== frameVersion) invalidAttachment();
13605
+ try {
13606
+ return new Uint8Array(
13607
+ await crypto.subtle.decrypt(
13608
+ {
13609
+ name: "AES-GCM",
13610
+ iv: frame.slice(1, 1 + ivBytes),
13611
+ additionalData: additionalData(attachmentId)
13612
+ },
13613
+ keyRing.v3,
13614
+ frame.slice(1 + ivBytes)
13615
+ )
13616
+ );
13617
+ } catch {
13618
+ return invalidAttachment();
13619
+ }
13620
+ };
13621
+ const put = async (upload, body, signal) => {
13622
+ for (let attempt = 0; ; attempt++) {
13623
+ if (expired(upload)) return "expired";
13624
+ let response;
13625
+ try {
13626
+ response = await fetcher(upload.url, {
13627
+ method: upload.method,
13628
+ headers: sendableHeaders(upload.headers),
13629
+ body,
13630
+ signal
13631
+ });
13632
+ } catch (error) {
13633
+ if (signal?.aborted || attempt >= retries) throw error;
13634
+ await pause(attempt, signal);
13635
+ continue;
13636
+ }
13637
+ await discardBody(response);
13638
+ if (response.ok) return "stored";
13639
+ if (response.status === 403) return "expired";
13640
+ if (!transientStatus(response.status) || attempt >= retries)
13641
+ throw new ChatStoreError(
13642
+ `Attachment upload failed with ${response.status}`,
13643
+ response.status,
13644
+ "attachment_upload_failed"
13645
+ );
13646
+ await pause(attempt, signal);
13647
+ }
13648
+ };
13649
+ const download = async (url, signal) => {
13650
+ for (let attempt = 0; ; attempt++) {
13651
+ if (expired(url)) return "expired";
13652
+ let response;
13653
+ try {
13654
+ response = await fetcher(url.url, {
13655
+ method: url.method,
13656
+ headers: sendableHeaders(url.headers),
13657
+ signal
13658
+ });
13659
+ if (response.ok) return new Uint8Array(await response.arrayBuffer());
13660
+ } catch (error) {
13661
+ if (signal?.aborted || attempt >= retries) throw error;
13662
+ await pause(attempt, signal);
13663
+ continue;
13664
+ }
13665
+ await discardBody(response);
13666
+ if (response.status === 403) return "expired";
13667
+ if (response.status === 404) return "missing";
13668
+ if (!transientStatus(response.status) || attempt >= retries)
13669
+ throw new ChatStoreError(
13670
+ `Attachment download failed with ${response.status}`,
13671
+ response.status,
13672
+ "attachment_download_failed"
13673
+ );
13674
+ await pause(attempt, signal);
13675
+ }
13676
+ };
13677
+ const remove = async (attachmentId, signal) => {
13678
+ try {
13679
+ await withRetry(() => store.attachments.delete(attachmentId, signal), signal);
13680
+ } catch (error) {
13681
+ if (!(error instanceof ChatStoreError) || error.status !== 404) throw error;
13682
+ }
13683
+ await cache?.delete(attachmentId).catch(() => void 0);
13684
+ };
13685
+ const discardPending = async (attachmentId) => {
13686
+ const found = await store.attachments.get(attachmentId);
13687
+ if (found?.attachment.state === "pending") await remove(attachmentId);
13688
+ };
13689
+ return {
13690
+ async upload(bytes4, uploadOptions) {
13691
+ const signal = uploadOptions?.signal;
13692
+ if (!(bytes4 instanceof Uint8Array) || bytes4.length > maxVaultAttachmentBytes)
13693
+ throw new ChatStoreError(
13694
+ "Attachment is larger than vault accepts",
13695
+ 413,
13696
+ "attachment_too_large"
13697
+ );
13698
+ const size = bytes4.length + frameOverhead;
13699
+ for (let attempt = 0; ; attempt++) {
13700
+ const { attachment: attachment2, upload } = await withRetry(
13701
+ () => store.attachments.create(size, signal),
13702
+ signal
13703
+ );
13704
+ try {
13705
+ await uploadOptions?.onReserved?.(attachment2.id);
13706
+ const frame = await seal(bytes4, attachment2.id);
13707
+ if (await put(upload, frame, signal) === "stored") return attachment2.id;
13708
+ } catch (error) {
13709
+ await discardPending(attachment2.id).catch(() => void 0);
13710
+ throw error;
13711
+ }
13712
+ await remove(attachment2.id).catch(() => void 0);
13713
+ if (attempt >= retries)
13714
+ throw new ChatStoreError(
13715
+ "Attachment storage rejected the upload URL",
13716
+ 403,
13717
+ "attachment_upload_rejected"
13718
+ );
13719
+ }
13720
+ },
13721
+ commit(attachmentId, owner, signal) {
13722
+ return withRetry(() => store.attachments.commit(attachmentId, owner, signal), signal);
13723
+ },
13724
+ async read(attachmentId, signal) {
13725
+ if (!attachmentIdPattern2.test(attachmentId)) invalidAttachment();
13726
+ const cached = await cache?.get(attachmentId).catch(() => null);
13727
+ if (cached) return cached;
13728
+ for (let attempt = 0; ; attempt++) {
13729
+ const found = await withRetry(() => store.attachments.get(attachmentId, signal), signal);
13730
+ if (!found?.download) return null;
13731
+ const frame = await download(found.download, signal);
13732
+ if (frame === "missing") return null;
13733
+ if (frame === "expired") {
13734
+ if (attempt >= retries)
13735
+ throw new ChatStoreError(
13736
+ "Attachment storage rejected the download URL",
13737
+ 403,
13738
+ "attachment_download_rejected"
13739
+ );
13740
+ continue;
13741
+ }
13742
+ if (frame.length !== found.attachment.sizeBytes) invalidAttachment();
13743
+ const bytes4 = await open(frame, attachmentId);
13744
+ await cache?.set(attachmentId, bytes4).catch(() => void 0);
13745
+ return bytes4;
13746
+ }
13747
+ },
13748
+ delete: remove
13749
+ };
13750
+ }
13751
+ function validRef(value) {
13752
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
13753
+ const ref3 = value;
13754
+ 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;
13755
+ }
13756
+ function vaultAttachmentRefs(record2) {
13757
+ const refs = record2.data.vaultAttachments;
13758
+ return Array.isArray(refs) ? refs.filter(validRef) : [];
13759
+ }
13760
+ function journalKey(messageId, fileId) {
13761
+ return JSON.stringify([messageId, fileId]);
13762
+ }
13763
+ async function migrateMessageAttachments(options) {
13764
+ const { store, attachments, journal, messageId, signal } = options;
13765
+ const now = options.now ?? Date.now;
13766
+ const owner = { kind: "message", id: messageId };
13767
+ const ownerGone = () => new ChatStoreError("Message is not in vault", 409, "attachment_owner_not_found");
13768
+ let snapshot = await store.get("message", messageId, signal);
13769
+ if (!snapshot || snapshot.record.isDeleted) throw ownerGone();
13770
+ const present = new Map(vaultAttachmentRefs(snapshot.record).map((r) => [r.fileId, r]));
13771
+ const uploadable = (attachment2) => attachment2.state === "pending" && now() < Date.parse(attachment2.createdAt) + uploadWindowMs;
13772
+ const lookup = async (attachmentId) => attachmentId && attachmentIdPattern2.test(attachmentId) ? (await store.attachments.get(attachmentId, signal))?.attachment ?? null : null;
13773
+ const bind = async (found) => {
13774
+ if (!found) return null;
13775
+ if (found.state === "committed")
13776
+ return found.ownerKind === owner.kind && found.ownerId === owner.id ? found : null;
13777
+ try {
13778
+ return await attachments.commit(found.id, owner, signal);
13779
+ } catch (error) {
13780
+ if (!(error instanceof ChatStoreError) || error.code !== "attachment_not_uploaded")
13781
+ throw error;
13782
+ if (uploadable(found)) return "busy";
13783
+ await attachments.delete(found.id, signal);
13784
+ return null;
13785
+ }
13786
+ };
13787
+ const added = [];
13788
+ const settled = [];
13789
+ const missing2 = [];
13790
+ const oversized = [];
13791
+ const deferred = [];
13792
+ const stale = /* @__PURE__ */ new Set();
13793
+ let uploaded = 0;
13794
+ for (const file of options.files) {
13795
+ signal?.throwIfAborted();
13796
+ const key3 = journalKey(messageId, file.fileId);
13797
+ const recorded = present.get(file.fileId);
13798
+ if (recorded) {
13799
+ const bound = await bind(await lookup(recorded.attachmentId));
13800
+ if (bound === "busy") {
13801
+ deferred.push(file.fileId);
13802
+ continue;
13803
+ }
13804
+ if (bound) {
13805
+ const stray = await lookup(await journal.get(key3));
13806
+ if (stray && stray.id !== recorded.attachmentId) {
13807
+ if (uploadable(stray)) continue;
13808
+ await attachments.delete(stray.id, signal);
13809
+ }
13810
+ settled.push(key3);
13811
+ continue;
13812
+ }
13813
+ stale.add(recorded.attachmentId);
13814
+ }
13815
+ const meta = { fileId: file.fileId, name: file.name, type: file.type };
13816
+ const resumed = await bind(await lookup(await journal.get(key3)));
13817
+ if (resumed === "busy") {
13818
+ deferred.push(file.fileId);
13819
+ continue;
13820
+ }
13821
+ if (resumed) {
13822
+ added.push({ attachmentId: resumed.id, ...meta, size: resumed.sizeBytes - frameOverhead });
13823
+ settled.push(key3);
13824
+ continue;
13825
+ }
13826
+ const bytes4 = await file.read();
13827
+ if (!bytes4) {
13828
+ missing2.push(file.fileId);
13829
+ continue;
13830
+ }
13831
+ if (bytes4.length > maxVaultAttachmentBytes) {
13832
+ oversized.push(file.fileId);
13833
+ continue;
13834
+ }
13835
+ const attachmentId = await attachments.upload(bytes4, {
13836
+ signal,
13837
+ onReserved: (id2) => journal.set(key3, id2)
13838
+ });
13839
+ uploaded++;
13840
+ await attachments.commit(attachmentId, owner, signal);
13841
+ added.push({ attachmentId, ...meta, size: bytes4.length });
13842
+ settled.push(key3);
13843
+ }
13844
+ const superseded = [];
13845
+ for (let attempt = 0; added.length > 0; attempt++) {
13846
+ const refs = new Map(vaultAttachmentRefs(snapshot.record).map((r) => [r.fileId, r]));
13847
+ superseded.length = 0;
13848
+ for (const ref3 of added) {
13849
+ const prior = refs.get(ref3.fileId);
13850
+ if (prior && prior.attachmentId !== ref3.attachmentId && !stale.has(prior.attachmentId))
13851
+ superseded.push(ref3.attachmentId);
13852
+ else refs.set(ref3.fileId, ref3);
13853
+ }
13854
+ if (superseded.length === added.length) break;
13855
+ try {
13856
+ [snapshot] = await store.putMany(
13857
+ [
13858
+ {
13859
+ record: {
13860
+ ...snapshot.record,
13861
+ data: { ...snapshot.record.data, ...plain({ vaultAttachments: [...refs.values()] }) }
13862
+ },
13863
+ expectedVersion: snapshot.version
13864
+ }
13865
+ ],
13866
+ signal
13867
+ );
13868
+ break;
13869
+ } catch (error) {
13870
+ if (!(error instanceof ChatStoreError) || error.code !== "version_conflict" || attempt + 1 >= maxRefWrites)
13871
+ throw error;
13872
+ const latest = await store.get("message", messageId, signal);
13873
+ if (!latest || latest.record.isDeleted) throw ownerGone();
13874
+ snapshot = latest;
13875
+ }
13876
+ }
13877
+ for (const id2 of superseded) await attachments.delete(id2, signal);
13878
+ for (const key3 of settled) await journal.set(key3, null);
13879
+ return { refs: vaultAttachmentRefs(snapshot.record), uploaded, missing: missing2, oversized, deferred };
13880
+ }
13881
+
13524
13882
  // src/lib/chatStore/vaultBackend.ts
13525
13883
  var import_uuid3 = require("uuid");
13526
13884
 
@@ -13660,6 +14018,7 @@ var resumableFields = [
13660
14018
  "imageModel",
13661
14019
  "files",
13662
14020
  "fileIds",
14021
+ "vaultAttachments",
13663
14022
  "usage",
13664
14023
  "sources",
13665
14024
  "responseDuration",
@@ -34114,6 +34473,7 @@ function useChatStorage(options) {
34114
34473
  createSelectPaymentWalletTool,
34115
34474
  createStockPricePreProcessor,
34116
34475
  createStreamingDeAnonymizer,
34476
+ createVaultAttachments,
34117
34477
  createVaultChatBackend,
34118
34478
  createVaultEmbeddingCache,
34119
34479
  createVaultFolderOp,
@@ -34178,9 +34538,11 @@ function useChatStorage(options) {
34178
34538
  isRerankerAvailable,
34179
34539
  localChatBackend,
34180
34540
  maskScopedEmbeddingCache,
34541
+ maxVaultAttachmentBytes,
34181
34542
  memoryCipher,
34182
34543
  migrateChatsToRemote,
34183
34544
  migrateMemoriesToRemote,
34545
+ migrateMessageAttachments,
34184
34546
  moveMemoriesToFolderOp,
34185
34547
  noopLogger,
34186
34548
  onKeyAvailable,
@@ -34234,6 +34596,7 @@ function useChatStorage(options) {
34234
34596
  useCredits,
34235
34597
  useEncryption,
34236
34598
  useModels,
34599
+ vaultAttachmentRefs,
34237
34600
  vaultChatBackend,
34238
34601
  verifyMemoriesForPublish,
34239
34602
  xhrTransport