@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.
@@ -1588,6 +1588,7 @@ __export(index_exports, {
1588
1588
  createSelectPaymentWalletTool: () => createSelectPaymentWalletTool,
1589
1589
  createServerToolsFilter: () => createServerToolsFilter,
1590
1590
  createStockPricePreProcessor: () => createStockPricePreProcessor,
1591
+ createVaultAttachments: () => createVaultAttachments,
1591
1592
  createVaultChatBackend: () => createVaultChatBackend,
1592
1593
  createVaultEmbeddingCache: () => createVaultEmbeddingCache,
1593
1594
  createVaultFolderOp: () => createVaultFolderOp,
@@ -1737,11 +1738,13 @@ __export(index_exports, {
1737
1738
  linkMemoryEntitiesOp: () => linkMemoryEntitiesOp,
1738
1739
  localChatBackend: () => localChatBackend,
1739
1740
  maskScopedEmbeddingCache: () => maskScopedEmbeddingCache,
1741
+ maxVaultAttachmentBytes: () => maxVaultAttachmentBytes,
1740
1742
  mediaToStored: () => mediaToStored,
1741
1743
  memoryCipher: () => memoryCipher,
1742
1744
  migrateChatsToRemote: () => migrateChatsToRemote,
1743
1745
  migrateDisplayResult: () => migrateDisplayResult,
1744
1746
  migrateMemoriesToRemote: () => migrateMemoriesToRemote,
1747
+ migrateMessageAttachments: () => migrateMessageAttachments,
1745
1748
  migrateNotionClientRegistration: () => migrateNotionClientRegistration,
1746
1749
  migrateNotionToken: () => migrateNotionToken,
1747
1750
  moveMemoriesToFolderOp: () => moveMemoriesToFolderOp,
@@ -1850,6 +1853,7 @@ __export(index_exports, {
1850
1853
  useVoice: () => useVoice,
1851
1854
  useWalletBinding: () => useWalletBinding,
1852
1855
  userPreferencesStorageSchema: () => userPreferencesStorageSchema,
1856
+ vaultAttachmentRefs: () => vaultAttachmentRefs,
1853
1857
  vaultChatBackend: () => vaultChatBackend,
1854
1858
  verifyMemoriesForPublish: () => verifyMemoriesForPublish,
1855
1859
  walk: () => walk,
@@ -6473,7 +6477,7 @@ var mergeHeaders = (...headers) => {
6473
6477
  if (!header || typeof header !== "object") {
6474
6478
  continue;
6475
6479
  }
6476
- const iterator = Array.isArray(header) || header instanceof Headers ? headersEntries(new Headers(header)) : Object.entries(header);
6480
+ const iterator = Array.isArray(header) ? header : header instanceof Headers ? headersEntries(header) : Object.entries(header);
6477
6481
  for (const [key3, value] of iterator) {
6478
6482
  if (value === null) {
6479
6483
  mergedHeaders.delete(key3);
@@ -14291,6 +14295,360 @@ async function migrateChatsToRemote(options) {
14291
14295
  return passes(false);
14292
14296
  }
14293
14297
 
14298
+ // src/lib/chatStore/vaultAttachments.ts
14299
+ var frameVersion = 1;
14300
+ var ivBytes = 12;
14301
+ var tagBytes = 16;
14302
+ var frameOverhead = 1 + ivBytes + tagBytes;
14303
+ var maxCiphertextBytes = 50 * 1024 * 1024;
14304
+ var expirySkewMs = 3e4;
14305
+ var maxRetryAfterMs = 3e4;
14306
+ var maxRefWrites = 5;
14307
+ var uploadWindowMs = 16 * 6e4;
14308
+ var attachmentIdPattern2 = /^[0-9a-f]{32}$/;
14309
+ var maxVaultAttachmentBytes = maxCiphertextBytes - frameOverhead;
14310
+ function additionalData(attachmentId) {
14311
+ return new TextEncoder().encode(`anuma-chat-attachment-v1:${attachmentId}`);
14312
+ }
14313
+ function invalidAttachment() {
14314
+ throw new ChatStoreError("Attachment does not decrypt under this key", 0, "invalid_attachment");
14315
+ }
14316
+ function sendableHeaders(headers) {
14317
+ return Object.fromEntries(
14318
+ Object.entries(headers).filter(([name]) => name.toLowerCase() !== "content-length")
14319
+ );
14320
+ }
14321
+ function transientStatus(status) {
14322
+ return status === 429 || status >= 500;
14323
+ }
14324
+ async function discardBody(response) {
14325
+ await response.body?.cancel().catch(() => void 0);
14326
+ }
14327
+ function createVaultAttachments(options) {
14328
+ const { store, keyRing, cache } = options;
14329
+ const fetcher = options.fetch ?? globalThis.fetch.bind(globalThis);
14330
+ const retries = options.retries ?? 3;
14331
+ const retryDelayMs = options.retryDelayMs ?? 500;
14332
+ const now = options.now ?? Date.now;
14333
+ const sleep4 = (ms2, signal) => new Promise((resolve, reject) => {
14334
+ signal?.throwIfAborted();
14335
+ const timer = setTimeout(() => {
14336
+ signal?.removeEventListener("abort", onAbort);
14337
+ resolve();
14338
+ }, ms2);
14339
+ const onAbort = () => {
14340
+ clearTimeout(timer);
14341
+ reject(signal?.reason);
14342
+ };
14343
+ signal?.addEventListener("abort", onAbort, { once: true });
14344
+ });
14345
+ const pause = (attempt, signal, atLeast = 0) => sleep4(Math.max(atLeast, retryDelayMs * 2 ** attempt), signal);
14346
+ const withRetry = async (call, signal) => {
14347
+ for (let attempt = 0; ; attempt++) {
14348
+ try {
14349
+ return await call();
14350
+ } catch (error) {
14351
+ if (!(error instanceof ChatStoreError) || !transientStatus(error.status) || attempt >= retries || (error.retryAfterMs ?? 0) > maxRetryAfterMs)
14352
+ throw error;
14353
+ await pause(attempt, signal, error.retryAfterMs);
14354
+ }
14355
+ }
14356
+ };
14357
+ const expired = (url) => now() >= Date.parse(url.expiresAt) - expirySkewMs;
14358
+ const seal = async (bytes4, attachmentId) => {
14359
+ const iv = crypto.getRandomValues(new Uint8Array(ivBytes));
14360
+ const sealed2 = new Uint8Array(
14361
+ await crypto.subtle.encrypt(
14362
+ { name: "AES-GCM", iv, additionalData: additionalData(attachmentId) },
14363
+ keyRing.v3,
14364
+ bytes4
14365
+ )
14366
+ );
14367
+ const frame = new Uint8Array(1 + ivBytes + sealed2.length);
14368
+ frame[0] = frameVersion;
14369
+ frame.set(iv, 1);
14370
+ frame.set(sealed2, 1 + ivBytes);
14371
+ return frame;
14372
+ };
14373
+ const open = async (frame, attachmentId) => {
14374
+ if (frame.length < frameOverhead || frame[0] !== frameVersion) invalidAttachment();
14375
+ try {
14376
+ return new Uint8Array(
14377
+ await crypto.subtle.decrypt(
14378
+ {
14379
+ name: "AES-GCM",
14380
+ iv: frame.slice(1, 1 + ivBytes),
14381
+ additionalData: additionalData(attachmentId)
14382
+ },
14383
+ keyRing.v3,
14384
+ frame.slice(1 + ivBytes)
14385
+ )
14386
+ );
14387
+ } catch {
14388
+ return invalidAttachment();
14389
+ }
14390
+ };
14391
+ const put = async (upload, body, signal) => {
14392
+ for (let attempt = 0; ; attempt++) {
14393
+ if (expired(upload)) return "expired";
14394
+ let response;
14395
+ try {
14396
+ response = await fetcher(upload.url, {
14397
+ method: upload.method,
14398
+ headers: sendableHeaders(upload.headers),
14399
+ body,
14400
+ signal
14401
+ });
14402
+ } catch (error) {
14403
+ if (signal?.aborted || attempt >= retries) throw error;
14404
+ await pause(attempt, signal);
14405
+ continue;
14406
+ }
14407
+ await discardBody(response);
14408
+ if (response.ok) return "stored";
14409
+ if (response.status === 403) return "expired";
14410
+ if (!transientStatus(response.status) || attempt >= retries)
14411
+ throw new ChatStoreError(
14412
+ `Attachment upload failed with ${response.status}`,
14413
+ response.status,
14414
+ "attachment_upload_failed"
14415
+ );
14416
+ await pause(attempt, signal);
14417
+ }
14418
+ };
14419
+ const download = async (url, signal) => {
14420
+ for (let attempt = 0; ; attempt++) {
14421
+ if (expired(url)) return "expired";
14422
+ let response;
14423
+ try {
14424
+ response = await fetcher(url.url, {
14425
+ method: url.method,
14426
+ headers: sendableHeaders(url.headers),
14427
+ signal
14428
+ });
14429
+ if (response.ok) return new Uint8Array(await response.arrayBuffer());
14430
+ } catch (error) {
14431
+ if (signal?.aborted || attempt >= retries) throw error;
14432
+ await pause(attempt, signal);
14433
+ continue;
14434
+ }
14435
+ await discardBody(response);
14436
+ if (response.status === 403) return "expired";
14437
+ if (response.status === 404) return "missing";
14438
+ if (!transientStatus(response.status) || attempt >= retries)
14439
+ throw new ChatStoreError(
14440
+ `Attachment download failed with ${response.status}`,
14441
+ response.status,
14442
+ "attachment_download_failed"
14443
+ );
14444
+ await pause(attempt, signal);
14445
+ }
14446
+ };
14447
+ const remove = async (attachmentId, signal) => {
14448
+ try {
14449
+ await withRetry(() => store.attachments.delete(attachmentId, signal), signal);
14450
+ } catch (error) {
14451
+ if (!(error instanceof ChatStoreError) || error.status !== 404) throw error;
14452
+ }
14453
+ await cache?.delete(attachmentId).catch(() => void 0);
14454
+ };
14455
+ const discardPending = async (attachmentId) => {
14456
+ const found = await store.attachments.get(attachmentId);
14457
+ if (found?.attachment.state === "pending") await remove(attachmentId);
14458
+ };
14459
+ return {
14460
+ async upload(bytes4, uploadOptions) {
14461
+ const signal = uploadOptions?.signal;
14462
+ if (!(bytes4 instanceof Uint8Array) || bytes4.length > maxVaultAttachmentBytes)
14463
+ throw new ChatStoreError(
14464
+ "Attachment is larger than vault accepts",
14465
+ 413,
14466
+ "attachment_too_large"
14467
+ );
14468
+ const size = bytes4.length + frameOverhead;
14469
+ for (let attempt = 0; ; attempt++) {
14470
+ const { attachment: attachment2, upload } = await withRetry(
14471
+ () => store.attachments.create(size, signal),
14472
+ signal
14473
+ );
14474
+ try {
14475
+ await uploadOptions?.onReserved?.(attachment2.id);
14476
+ const frame = await seal(bytes4, attachment2.id);
14477
+ if (await put(upload, frame, signal) === "stored") return attachment2.id;
14478
+ } catch (error) {
14479
+ await discardPending(attachment2.id).catch(() => void 0);
14480
+ throw error;
14481
+ }
14482
+ await remove(attachment2.id).catch(() => void 0);
14483
+ if (attempt >= retries)
14484
+ throw new ChatStoreError(
14485
+ "Attachment storage rejected the upload URL",
14486
+ 403,
14487
+ "attachment_upload_rejected"
14488
+ );
14489
+ }
14490
+ },
14491
+ commit(attachmentId, owner, signal) {
14492
+ return withRetry(() => store.attachments.commit(attachmentId, owner, signal), signal);
14493
+ },
14494
+ async read(attachmentId, signal) {
14495
+ if (!attachmentIdPattern2.test(attachmentId)) invalidAttachment();
14496
+ const cached = await cache?.get(attachmentId).catch(() => null);
14497
+ if (cached) return cached;
14498
+ for (let attempt = 0; ; attempt++) {
14499
+ const found = await withRetry(() => store.attachments.get(attachmentId, signal), signal);
14500
+ if (!found?.download) return null;
14501
+ const frame = await download(found.download, signal);
14502
+ if (frame === "missing") return null;
14503
+ if (frame === "expired") {
14504
+ if (attempt >= retries)
14505
+ throw new ChatStoreError(
14506
+ "Attachment storage rejected the download URL",
14507
+ 403,
14508
+ "attachment_download_rejected"
14509
+ );
14510
+ continue;
14511
+ }
14512
+ if (frame.length !== found.attachment.sizeBytes) invalidAttachment();
14513
+ const bytes4 = await open(frame, attachmentId);
14514
+ await cache?.set(attachmentId, bytes4).catch(() => void 0);
14515
+ return bytes4;
14516
+ }
14517
+ },
14518
+ delete: remove
14519
+ };
14520
+ }
14521
+ function validRef(value) {
14522
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
14523
+ const ref3 = value;
14524
+ 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;
14525
+ }
14526
+ function vaultAttachmentRefs(record2) {
14527
+ const refs = record2.data.vaultAttachments;
14528
+ return Array.isArray(refs) ? refs.filter(validRef) : [];
14529
+ }
14530
+ function journalKey(messageId, fileId) {
14531
+ return JSON.stringify([messageId, fileId]);
14532
+ }
14533
+ async function migrateMessageAttachments(options) {
14534
+ const { store, attachments, journal, messageId, signal } = options;
14535
+ const now = options.now ?? Date.now;
14536
+ const owner = { kind: "message", id: messageId };
14537
+ const ownerGone = () => new ChatStoreError("Message is not in vault", 409, "attachment_owner_not_found");
14538
+ let snapshot = await store.get("message", messageId, signal);
14539
+ if (!snapshot || snapshot.record.isDeleted) throw ownerGone();
14540
+ const present = new Map(vaultAttachmentRefs(snapshot.record).map((r) => [r.fileId, r]));
14541
+ const uploadable = (attachment2) => attachment2.state === "pending" && now() < Date.parse(attachment2.createdAt) + uploadWindowMs;
14542
+ const lookup = async (attachmentId) => attachmentId && attachmentIdPattern2.test(attachmentId) ? (await store.attachments.get(attachmentId, signal))?.attachment ?? null : null;
14543
+ const bind = async (found) => {
14544
+ if (!found) return null;
14545
+ if (found.state === "committed")
14546
+ return found.ownerKind === owner.kind && found.ownerId === owner.id ? found : null;
14547
+ try {
14548
+ return await attachments.commit(found.id, owner, signal);
14549
+ } catch (error) {
14550
+ if (!(error instanceof ChatStoreError) || error.code !== "attachment_not_uploaded")
14551
+ throw error;
14552
+ if (uploadable(found)) return "busy";
14553
+ await attachments.delete(found.id, signal);
14554
+ return null;
14555
+ }
14556
+ };
14557
+ const added = [];
14558
+ const settled = [];
14559
+ const missing2 = [];
14560
+ const oversized = [];
14561
+ const deferred = [];
14562
+ const stale = /* @__PURE__ */ new Set();
14563
+ let uploaded = 0;
14564
+ for (const file of options.files) {
14565
+ signal?.throwIfAborted();
14566
+ const key3 = journalKey(messageId, file.fileId);
14567
+ const recorded = present.get(file.fileId);
14568
+ if (recorded) {
14569
+ const bound = await bind(await lookup(recorded.attachmentId));
14570
+ if (bound === "busy") {
14571
+ deferred.push(file.fileId);
14572
+ continue;
14573
+ }
14574
+ if (bound) {
14575
+ const stray = await lookup(await journal.get(key3));
14576
+ if (stray && stray.id !== recorded.attachmentId) {
14577
+ if (uploadable(stray)) continue;
14578
+ await attachments.delete(stray.id, signal);
14579
+ }
14580
+ settled.push(key3);
14581
+ continue;
14582
+ }
14583
+ stale.add(recorded.attachmentId);
14584
+ }
14585
+ const meta = { fileId: file.fileId, name: file.name, type: file.type };
14586
+ const resumed = await bind(await lookup(await journal.get(key3)));
14587
+ if (resumed === "busy") {
14588
+ deferred.push(file.fileId);
14589
+ continue;
14590
+ }
14591
+ if (resumed) {
14592
+ added.push({ attachmentId: resumed.id, ...meta, size: resumed.sizeBytes - frameOverhead });
14593
+ settled.push(key3);
14594
+ continue;
14595
+ }
14596
+ const bytes4 = await file.read();
14597
+ if (!bytes4) {
14598
+ missing2.push(file.fileId);
14599
+ continue;
14600
+ }
14601
+ if (bytes4.length > maxVaultAttachmentBytes) {
14602
+ oversized.push(file.fileId);
14603
+ continue;
14604
+ }
14605
+ const attachmentId = await attachments.upload(bytes4, {
14606
+ signal,
14607
+ onReserved: (id2) => journal.set(key3, id2)
14608
+ });
14609
+ uploaded++;
14610
+ await attachments.commit(attachmentId, owner, signal);
14611
+ added.push({ attachmentId, ...meta, size: bytes4.length });
14612
+ settled.push(key3);
14613
+ }
14614
+ const superseded = [];
14615
+ for (let attempt = 0; added.length > 0; attempt++) {
14616
+ const refs = new Map(vaultAttachmentRefs(snapshot.record).map((r) => [r.fileId, r]));
14617
+ superseded.length = 0;
14618
+ for (const ref3 of added) {
14619
+ const prior = refs.get(ref3.fileId);
14620
+ if (prior && prior.attachmentId !== ref3.attachmentId && !stale.has(prior.attachmentId))
14621
+ superseded.push(ref3.attachmentId);
14622
+ else refs.set(ref3.fileId, ref3);
14623
+ }
14624
+ if (superseded.length === added.length) break;
14625
+ try {
14626
+ [snapshot] = await store.putMany(
14627
+ [
14628
+ {
14629
+ record: {
14630
+ ...snapshot.record,
14631
+ data: { ...snapshot.record.data, ...plain({ vaultAttachments: [...refs.values()] }) }
14632
+ },
14633
+ expectedVersion: snapshot.version
14634
+ }
14635
+ ],
14636
+ signal
14637
+ );
14638
+ break;
14639
+ } catch (error) {
14640
+ if (!(error instanceof ChatStoreError) || error.code !== "version_conflict" || attempt + 1 >= maxRefWrites)
14641
+ throw error;
14642
+ const latest = await store.get("message", messageId, signal);
14643
+ if (!latest || latest.record.isDeleted) throw ownerGone();
14644
+ snapshot = latest;
14645
+ }
14646
+ }
14647
+ for (const id2 of superseded) await attachments.delete(id2, signal);
14648
+ for (const key3 of settled) await journal.set(key3, null);
14649
+ return { refs: vaultAttachmentRefs(snapshot.record), uploaded, missing: missing2, oversized, deferred };
14650
+ }
14651
+
14294
14652
  // src/lib/chatStore/vaultBackend.ts
14295
14653
  var import_uuid3 = require("uuid");
14296
14654
 
@@ -14430,6 +14788,7 @@ var resumableFields = [
14430
14788
  "imageModel",
14431
14789
  "files",
14432
14790
  "fileIds",
14791
+ "vaultAttachments",
14433
14792
  "usage",
14434
14793
  "sources",
14435
14794
  "responseDuration",
@@ -42174,6 +42533,7 @@ function useWalletBinding(options = {}) {
42174
42533
  createSelectPaymentWalletTool,
42175
42534
  createServerToolsFilter,
42176
42535
  createStockPricePreProcessor,
42536
+ createVaultAttachments,
42177
42537
  createVaultChatBackend,
42178
42538
  createVaultEmbeddingCache,
42179
42539
  createVaultFolderOp,
@@ -42323,11 +42683,13 @@ function useWalletBinding(options = {}) {
42323
42683
  linkMemoryEntitiesOp,
42324
42684
  localChatBackend,
42325
42685
  maskScopedEmbeddingCache,
42686
+ maxVaultAttachmentBytes,
42326
42687
  mediaToStored,
42327
42688
  memoryCipher,
42328
42689
  migrateChatsToRemote,
42329
42690
  migrateDisplayResult,
42330
42691
  migrateMemoriesToRemote,
42692
+ migrateMessageAttachments,
42331
42693
  migrateNotionClientRegistration,
42332
42694
  migrateNotionToken,
42333
42695
  moveMemoriesToFolderOp,
@@ -42436,6 +42798,7 @@ function useWalletBinding(options = {}) {
42436
42798
  useVoice,
42437
42799
  useWalletBinding,
42438
42800
  userPreferencesStorageSchema,
42801
+ vaultAttachmentRefs,
42439
42802
  vaultChatBackend,
42440
42803
  verifyMemoriesForPublish,
42441
42804
  walk,