@tangle-network/agent-app 0.45.57 → 0.45.59

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.
@@ -1,3 +1,6 @@
1
+ import {
2
+ redactErrorMessage
3
+ } from "../chunk-CDC5HFKL.js";
1
4
  import {
2
5
  createSandboxFileIndexRoute
3
6
  } from "../chunk-QY4BRKRJ.js";
@@ -2262,6 +2265,21 @@ function createUploadRoute(options) {
2262
2265
  };
2263
2266
  }
2264
2267
 
2268
+ // src/chat-routes/attachment-store.ts
2269
+ var ATTACHMENT_STORAGE_FAILURE_MESSAGE = "Attachment storage is temporarily unavailable. Please try again.";
2270
+ function immutableAttachmentPath(logicalPath, ownershipId) {
2271
+ if (!/^[A-Za-z0-9_-]{1,128}$/.test(ownershipId)) {
2272
+ throw new Error("attachment ownership id must be a path-safe identifier");
2273
+ }
2274
+ return `${logicalPath}--${ownershipId}`;
2275
+ }
2276
+ function createAtomicAttachmentWriter(input) {
2277
+ return {
2278
+ write: input.write.bind(input),
2279
+ abort: input.abort.bind(input)
2280
+ };
2281
+ }
2282
+
2265
2283
  // src/chat-routes/resolve-attachments.ts
2266
2284
  var MAX_ATTACHMENT_NAME_LENGTH = 256;
2267
2285
  function isAttachmentKind(value) {
@@ -2357,6 +2375,47 @@ async function resolveChatAttachments(value, options) {
2357
2375
  return { succeeded: true, value: inputs.map(attachmentInputToPart) };
2358
2376
  }
2359
2377
 
2378
+ // src/chat-routes/attachment-write-safety.ts
2379
+ function objectLike(value) {
2380
+ return value !== null && (typeof value === "object" || typeof value === "function");
2381
+ }
2382
+ function inspectLegacyAttachmentWriteResult(value) {
2383
+ try {
2384
+ if (!objectLike(value)) return void 0;
2385
+ const ok = value.ok;
2386
+ if (ok === true) return { ok: true };
2387
+ if (ok === false && typeof value.reason === "string") {
2388
+ return { ok: false, reason: value.reason };
2389
+ }
2390
+ } catch {
2391
+ }
2392
+ return void 0;
2393
+ }
2394
+ function inspectAtomicAttachmentWriteResult(value, ownership) {
2395
+ try {
2396
+ if (!objectLike(value)) return void 0;
2397
+ const receiptValue = value.receipt;
2398
+ if (!objectLike(receiptValue)) return void 0;
2399
+ const receiptOwnership = receiptValue.ownership;
2400
+ if (!objectLike(receiptOwnership)) return void 0;
2401
+ if (receiptOwnership.id !== ownership.id || receiptOwnership.path !== ownership.path || typeof receiptOwnership.id !== "string" || typeof receiptOwnership.path !== "string" || typeof receiptValue.rollback !== "function") {
2402
+ return void 0;
2403
+ }
2404
+ if (value.ok === true) {
2405
+ return { ok: true, receipt: receiptValue };
2406
+ }
2407
+ if (value.ok === false && typeof value.reason === "string") {
2408
+ return {
2409
+ ok: false,
2410
+ reason: value.reason,
2411
+ receipt: receiptValue
2412
+ };
2413
+ }
2414
+ } catch {
2415
+ }
2416
+ return void 0;
2417
+ }
2418
+
2360
2419
  // src/chat-routes/promote-file-part.ts
2361
2420
  var PROMOTE_MAX_FILE_BYTES = 10 * 1024 * 1024;
2362
2421
  var EXT_TO_MIME = {
@@ -2484,11 +2543,41 @@ function defaultBuildAttachmentPath(args) {
2484
2543
  const base = extension ? args.filename.slice(0, -extension.length) : args.filename;
2485
2544
  return `uploads/agent/${args.date}/${base}-${args.hash8}${extension}`;
2486
2545
  }
2546
+ function logPromotionStorageError(logger, message, fields) {
2547
+ try {
2548
+ logger.error(message, fields);
2549
+ } catch {
2550
+ }
2551
+ }
2552
+ async function compensatePromotionWrite(writer, scopeId, ownership, receipt) {
2553
+ try {
2554
+ await receipt.rollback();
2555
+ return {};
2556
+ } catch (rollbackError) {
2557
+ try {
2558
+ await writer.abort(scopeId, ownership);
2559
+ return { rollbackError };
2560
+ } catch (abortError) {
2561
+ return { rollbackError, abortError };
2562
+ }
2563
+ }
2564
+ }
2565
+ function isAtomicPromotionOptions(options) {
2566
+ return "attachmentWriter" in options;
2567
+ }
2487
2568
  async function promoteAgentFilePart(options) {
2569
+ const atomic = isAtomicPromotionOptions(options);
2488
2570
  const maxBytes = options.maxBytes ?? PROMOTE_MAX_FILE_BYTES;
2489
2571
  const sniffMime = options.sniffMime ?? sniffMimeFromName;
2490
2572
  const buildAttachmentPath = options.buildAttachmentPath ?? defaultBuildAttachmentPath;
2491
2573
  const now = options.now ?? (() => /* @__PURE__ */ new Date());
2574
+ const logger = options.logger ?? console;
2575
+ const createWriteId = atomic ? options.createWriteId ?? (() => {
2576
+ if (typeof crypto === "undefined" || typeof crypto.randomUUID !== "function") {
2577
+ throw new Error("attachment promotion requires crypto.randomUUID");
2578
+ }
2579
+ return crypto.randomUUID();
2580
+ }) : void 0;
2492
2581
  const filename = sanitizeAttachmentFileName(
2493
2582
  options.raw.filename ?? basenameFromUrl(options.raw.url) ?? "agent-file"
2494
2583
  );
@@ -2504,19 +2593,118 @@ async function promoteAgentFilePart(options) {
2504
2593
  const kind = attachmentKindForMime(mediaType);
2505
2594
  const digest = await hash8(options.raw.id ?? options.raw.url ?? filename);
2506
2595
  const date = now().toISOString().split("T")[0] ?? "";
2507
- const path = buildAttachmentPath({ filename, hash8: digest, date, mediaType, kind });
2596
+ const logicalPath = buildAttachmentPath({ filename, hash8: digest, date, mediaType, kind });
2597
+ if (!atomic) {
2598
+ let written2;
2599
+ try {
2600
+ written2 = await options.writeAttachment(options.scopeId, logicalPath, resolved.bytes, {
2601
+ mediaType,
2602
+ name: filename,
2603
+ originalName: options.raw.filename ?? filename,
2604
+ size: resolved.bytes.byteLength
2605
+ });
2606
+ } catch (error) {
2607
+ const reason = redactErrorMessage(error);
2608
+ logPromotionStorageError(logger, "[promote-file-part] legacy writer failed", {
2609
+ path: logicalPath,
2610
+ error: reason
2611
+ });
2612
+ return { succeeded: false, filename, reason };
2613
+ }
2614
+ const result2 = inspectLegacyAttachmentWriteResult(written2);
2615
+ if (!result2) {
2616
+ logPromotionStorageError(logger, "[promote-file-part] legacy writer returned an invalid result", {
2617
+ path: logicalPath
2618
+ });
2619
+ return { succeeded: false, filename, reason: ATTACHMENT_STORAGE_FAILURE_MESSAGE };
2620
+ }
2621
+ if (!result2.ok) {
2622
+ const reason = redactErrorMessage(result2.reason);
2623
+ logPromotionStorageError(logger, "[promote-file-part] legacy write rejected", {
2624
+ path: logicalPath,
2625
+ error: reason
2626
+ });
2627
+ return { succeeded: false, filename, reason };
2628
+ }
2629
+ return {
2630
+ succeeded: true,
2631
+ part: {
2632
+ type: kind,
2633
+ path: logicalPath,
2634
+ name: filename,
2635
+ size: resolved.bytes.byteLength,
2636
+ mediaType
2637
+ }
2638
+ };
2639
+ }
2640
+ let ownershipId;
2641
+ let path;
2642
+ try {
2643
+ ownershipId = createWriteId();
2644
+ path = immutableAttachmentPath(logicalPath, ownershipId);
2645
+ } catch (error) {
2646
+ logPromotionStorageError(logger, "[promote-file-part] could not allocate ownership key", {
2647
+ path: logicalPath,
2648
+ error: redactErrorMessage(error)
2649
+ });
2650
+ return { succeeded: false, filename, reason: ATTACHMENT_STORAGE_FAILURE_MESSAGE };
2651
+ }
2652
+ const ownership = { id: ownershipId, path };
2508
2653
  let written;
2509
2654
  try {
2510
- written = await options.writeAttachment(options.scopeId, path, resolved.bytes, {
2655
+ written = await options.attachmentWriter.write(options.scopeId, path, resolved.bytes, {
2511
2656
  mediaType,
2512
2657
  name: filename,
2513
2658
  originalName: options.raw.filename ?? filename,
2514
- size: resolved.bytes.byteLength
2659
+ size: resolved.bytes.byteLength,
2660
+ ownership
2515
2661
  });
2516
2662
  } catch (err) {
2517
- return { succeeded: false, filename, reason: err instanceof Error ? err.message : String(err) };
2663
+ let abortError;
2664
+ try {
2665
+ await options.attachmentWriter.abort(options.scopeId, ownership);
2666
+ } catch (error) {
2667
+ abortError = error;
2668
+ }
2669
+ logPromotionStorageError(logger, "[promote-file-part] write failed", {
2670
+ path,
2671
+ ownershipId: ownership.id,
2672
+ error: redactErrorMessage(err),
2673
+ ...abortError === void 0 ? {} : { abortError: redactErrorMessage(abortError) }
2674
+ });
2675
+ return { succeeded: false, filename, reason: ATTACHMENT_STORAGE_FAILURE_MESSAGE };
2676
+ }
2677
+ const result = inspectAtomicAttachmentWriteResult(written, ownership);
2678
+ if (!result) {
2679
+ let abortError;
2680
+ try {
2681
+ await options.attachmentWriter.abort(options.scopeId, ownership);
2682
+ } catch (error) {
2683
+ abortError = error;
2684
+ }
2685
+ logPromotionStorageError(logger, "[promote-file-part] writer returned a mismatched ownership receipt", {
2686
+ path,
2687
+ ownershipId: ownership.id,
2688
+ ...abortError === void 0 ? {} : { abortError: redactErrorMessage(abortError) }
2689
+ });
2690
+ return { succeeded: false, filename, reason: ATTACHMENT_STORAGE_FAILURE_MESSAGE };
2691
+ }
2692
+ if (!result.ok) {
2693
+ const compensation = await compensatePromotionWrite(
2694
+ options.attachmentWriter,
2695
+ options.scopeId,
2696
+ ownership,
2697
+ result.receipt
2698
+ );
2699
+ logPromotionStorageError(logger, "[promote-file-part] write rejected", {
2700
+ path,
2701
+ ownershipId: ownership.id,
2702
+ error: redactErrorMessage(result.reason),
2703
+ ...compensation.rollbackError === void 0 ? {} : { rollbackError: redactErrorMessage(compensation.rollbackError) },
2704
+ ...compensation.abortError === void 0 ? {} : { abortError: redactErrorMessage(compensation.abortError) }
2705
+ });
2706
+ return { succeeded: false, filename, reason: ATTACHMENT_STORAGE_FAILURE_MESSAGE };
2518
2707
  }
2519
- if (!written.ok) return { succeeded: false, filename, reason: written.reason };
2520
2708
  return {
2521
2709
  succeeded: true,
2522
2710
  part: {
@@ -2530,13 +2718,54 @@ async function promoteAgentFilePart(options) {
2530
2718
  }
2531
2719
 
2532
2720
  // src/chat-routes/attachment-upload.ts
2721
+ function logAttachmentUploadError(logger, message, fields) {
2722
+ try {
2723
+ logger.error(message, fields);
2724
+ } catch {
2725
+ }
2726
+ }
2533
2727
  function attachmentUploadError(status, code, message, path) {
2534
2728
  return Response.json(
2535
2729
  { error: path === void 0 ? { code, message } : { code, message, path } },
2536
2730
  { status }
2537
2731
  );
2538
2732
  }
2733
+ async function rollbackSuccessfulWrites(writes, scopeId, writer, logger) {
2734
+ let failures = 0;
2735
+ for (let index = writes.length - 1; index >= 0; index -= 1) {
2736
+ const write = writes[index];
2737
+ try {
2738
+ await write.receipt.rollback();
2739
+ } catch (error) {
2740
+ failures += 1;
2741
+ logAttachmentUploadError(logger, "[attachment-upload] cleanup failed", {
2742
+ path: write.path,
2743
+ ownershipId: write.ownership.id,
2744
+ error: redactErrorMessage(error)
2745
+ });
2746
+ failures += await abortOwnership(writer, scopeId, write.ownership, logger);
2747
+ }
2748
+ }
2749
+ return failures;
2750
+ }
2751
+ async function abortOwnership(writer, scopeId, ownership, logger) {
2752
+ try {
2753
+ await writer.abort(scopeId, ownership);
2754
+ return 0;
2755
+ } catch (error) {
2756
+ logAttachmentUploadError(logger, "[attachment-upload] ambiguous write cleanup failed", {
2757
+ path: ownership.path,
2758
+ ownershipId: ownership.id,
2759
+ error: redactErrorMessage(error)
2760
+ });
2761
+ return 1;
2762
+ }
2763
+ }
2764
+ function isAtomicRouteOptions(options) {
2765
+ return "attachmentWriter" in options;
2766
+ }
2539
2767
  function createAttachmentUploadRoute(options) {
2768
+ const atomic = isAtomicRouteOptions(options);
2540
2769
  const maxCount = options.limits?.maxCount ?? ATTACHMENT_MAX_COUNT;
2541
2770
  const maxBinaryBytes = options.limits?.maxBinaryBytes ?? MAX_BINARY_ATTACHMENT_BYTES;
2542
2771
  const maxBytesBySniffedMime = options.limits?.maxBytesBySniffedMime;
@@ -2547,10 +2776,21 @@ function createAttachmentUploadRoute(options) {
2547
2776
  const pathFor = options.pathFor ?? ((name) => name);
2548
2777
  const validatePath = options.validatePath ?? defaultValidateAttachmentPath;
2549
2778
  const sniffMime = options.sniffMime ?? sniffMimeFromName;
2779
+ const createWriteId = atomic ? options.createWriteId ?? (() => {
2780
+ if (typeof crypto === "undefined" || typeof crypto.randomUUID !== "function") {
2781
+ throw new Error("attachment upload requires crypto.randomUUID");
2782
+ }
2783
+ return crypto.randomUUID();
2784
+ }) : void 0;
2785
+ const logger = options.logger ?? console;
2550
2786
  return async function attachmentUpload(request) {
2551
2787
  const auth = await options.authorize({ request });
2552
2788
  if (!auth.ok) return auth.response;
2553
- const write = auth.writeAttachment ?? options.writeAttachment;
2789
+ const atomicWriter = atomic ? auth.attachmentWriter ?? options.attachmentWriter : void 0;
2790
+ const legacyWriter = atomic ? void 0 : auth.writeAttachment ?? options.writeAttachment;
2791
+ if (atomic && !atomicWriter) {
2792
+ return attachmentUploadError(503, "attachment_store_unavailable", ATTACHMENT_STORAGE_FAILURE_MESSAGE);
2793
+ }
2554
2794
  let form;
2555
2795
  try {
2556
2796
  form = await request.formData();
@@ -2580,7 +2820,7 @@ function createAttachmentUploadRoute(options) {
2580
2820
  );
2581
2821
  }
2582
2822
  const prepared = [];
2583
- const seenPaths = /* @__PURE__ */ new Set();
2823
+ const seenLogicalPaths = /* @__PURE__ */ new Set();
2584
2824
  let totalBytes = 0;
2585
2825
  for (const file of files) {
2586
2826
  const bytes = new Uint8Array(await file.arrayBuffer());
@@ -2613,20 +2853,40 @@ function createAttachmentUploadRoute(options) {
2613
2853
  attachmentSizeErrorMessage(name, bytes.length, limit)
2614
2854
  );
2615
2855
  }
2616
- const path = pathFor(name);
2856
+ const logicalPath = pathFor(name);
2857
+ let ownershipId;
2858
+ let path = logicalPath;
2859
+ if (atomic) {
2860
+ try {
2861
+ ownershipId = createWriteId();
2862
+ path = immutableAttachmentPath(logicalPath, ownershipId);
2863
+ } catch (error) {
2864
+ logAttachmentUploadError(logger, "[attachment-upload] could not allocate ownership key", {
2865
+ path: logicalPath,
2866
+ error: redactErrorMessage(error)
2867
+ });
2868
+ return attachmentUploadError(
2869
+ 503,
2870
+ "attachment_store_unavailable",
2871
+ ATTACHMENT_STORAGE_FAILURE_MESSAGE,
2872
+ logicalPath
2873
+ );
2874
+ }
2875
+ }
2617
2876
  const pathCheck = validatePath(path);
2618
2877
  if (!pathCheck.succeeded) {
2619
2878
  return attachmentUploadError(400, "invalid_attachment_path", pathCheck.error, path);
2620
2879
  }
2621
- if (seenPaths.has(path)) {
2880
+ const duplicatePath = atomic ? logicalPath : path;
2881
+ if (seenLogicalPaths.has(duplicatePath)) {
2622
2882
  return attachmentUploadError(
2623
2883
  400,
2624
2884
  "attachment_duplicate_path",
2625
- `attachments must not repeat a path within one upload: ${path}`,
2885
+ `attachments must not repeat a path within one upload: ${duplicatePath}`,
2626
2886
  path
2627
2887
  );
2628
2888
  }
2629
- seenPaths.add(path);
2889
+ seenLogicalPaths.add(duplicatePath);
2630
2890
  totalBytes += bytes.length;
2631
2891
  if (totalBytes > maxTotalBytes) {
2632
2892
  return attachmentUploadError(
@@ -2636,19 +2896,123 @@ function createAttachmentUploadRoute(options) {
2636
2896
  );
2637
2897
  }
2638
2898
  const mediaType = sniff.mime ?? sniffMime(name);
2639
- prepared.push({ path, name, bytes, originalName: file.name, size: bytes.length, mediaType, kind });
2899
+ prepared.push({ path, ownershipId, name, bytes, originalName: file.name, size: bytes.length, mediaType, kind });
2640
2900
  }
2641
2901
  const uploaded = [];
2902
+ const successfulWrites = [];
2642
2903
  for (const input of prepared) {
2643
- const written = await write(auth.scopeId, input.path, input.bytes, {
2644
- mediaType: input.mediaType,
2645
- name: input.name,
2646
- originalName: input.originalName,
2647
- size: input.size
2648
- });
2649
- if (!written.ok) {
2650
- return attachmentUploadError(413, "attachment_write_failed", written.reason, input.path);
2904
+ if (!atomic) {
2905
+ let written2;
2906
+ try {
2907
+ written2 = await legacyWriter(auth.scopeId, input.path, input.bytes, {
2908
+ mediaType: input.mediaType,
2909
+ name: input.name,
2910
+ originalName: input.originalName,
2911
+ size: input.size
2912
+ });
2913
+ } catch (error) {
2914
+ logAttachmentUploadError(logger, "[attachment-upload] legacy writer failed", {
2915
+ path: input.path,
2916
+ error: redactErrorMessage(error)
2917
+ });
2918
+ return attachmentUploadError(
2919
+ 503,
2920
+ "attachment_store_unavailable",
2921
+ ATTACHMENT_STORAGE_FAILURE_MESSAGE,
2922
+ input.path
2923
+ );
2924
+ }
2925
+ const result2 = inspectLegacyAttachmentWriteResult(written2);
2926
+ if (!result2) {
2927
+ logAttachmentUploadError(logger, "[attachment-upload] legacy writer returned an invalid result", {
2928
+ path: input.path
2929
+ });
2930
+ return attachmentUploadError(
2931
+ 503,
2932
+ "attachment_store_unavailable",
2933
+ ATTACHMENT_STORAGE_FAILURE_MESSAGE,
2934
+ input.path
2935
+ );
2936
+ }
2937
+ if (!result2.ok) {
2938
+ const reason = redactErrorMessage(result2.reason);
2939
+ logAttachmentUploadError(logger, "[attachment-upload] legacy write rejected", {
2940
+ path: input.path,
2941
+ error: reason
2942
+ });
2943
+ return attachmentUploadError(413, "attachment_write_failed", reason, input.path);
2944
+ }
2945
+ uploaded.push({
2946
+ path: input.path,
2947
+ name: input.name,
2948
+ size: input.size,
2949
+ mediaType: input.mediaType,
2950
+ kind: input.kind
2951
+ });
2952
+ continue;
2953
+ }
2954
+ const ownershipId = input.ownershipId;
2955
+ if (!ownershipId) {
2956
+ return attachmentUploadError(503, "attachment_store_unavailable", ATTACHMENT_STORAGE_FAILURE_MESSAGE, input.path);
2957
+ }
2958
+ const ownership = { id: ownershipId, path: input.path };
2959
+ let written;
2960
+ try {
2961
+ written = await atomicWriter.write(auth.scopeId, input.path, input.bytes, {
2962
+ mediaType: input.mediaType,
2963
+ name: input.name,
2964
+ originalName: input.originalName,
2965
+ size: input.size,
2966
+ ownership
2967
+ });
2968
+ } catch (error) {
2969
+ const cleanupFailures = await abortOwnership(atomicWriter, auth.scopeId, ownership, logger) + await rollbackSuccessfulWrites(successfulWrites, auth.scopeId, atomicWriter, logger);
2970
+ logAttachmentUploadError(logger, "[attachment-upload] write failed", {
2971
+ path: input.path,
2972
+ ownershipId: ownership.id,
2973
+ cleanupFailures,
2974
+ error: redactErrorMessage(error)
2975
+ });
2976
+ return attachmentUploadError(
2977
+ 503,
2978
+ "attachment_store_unavailable",
2979
+ ATTACHMENT_STORAGE_FAILURE_MESSAGE,
2980
+ input.path
2981
+ );
2982
+ }
2983
+ const result = inspectAtomicAttachmentWriteResult(written, ownership);
2984
+ if (!result) {
2985
+ const cleanupFailures = await abortOwnership(atomicWriter, auth.scopeId, ownership, logger) + await rollbackSuccessfulWrites(successfulWrites, auth.scopeId, atomicWriter, logger);
2986
+ logAttachmentUploadError(logger, "[attachment-upload] writer returned a mismatched ownership receipt", {
2987
+ path: input.path,
2988
+ ownershipId: ownership.id,
2989
+ cleanupFailures
2990
+ });
2991
+ return attachmentUploadError(503, "attachment_store_unavailable", ATTACHMENT_STORAGE_FAILURE_MESSAGE, input.path);
2992
+ }
2993
+ if (!result.ok) {
2994
+ const cleanupFailures = await rollbackSuccessfulWrites([
2995
+ { path: input.path, ownership, receipt: result.receipt }
2996
+ ], auth.scopeId, atomicWriter, logger) + await rollbackSuccessfulWrites(
2997
+ successfulWrites,
2998
+ auth.scopeId,
2999
+ atomicWriter,
3000
+ logger
3001
+ );
3002
+ logAttachmentUploadError(logger, "[attachment-upload] write rejected", {
3003
+ path: input.path,
3004
+ ownershipId: ownership.id,
3005
+ cleanupFailures,
3006
+ error: redactErrorMessage(result.reason)
3007
+ });
3008
+ return attachmentUploadError(
3009
+ 503,
3010
+ "attachment_store_unavailable",
3011
+ ATTACHMENT_STORAGE_FAILURE_MESSAGE,
3012
+ input.path
3013
+ );
2651
3014
  }
3015
+ successfulWrites.push({ path: input.path, ownership, receipt: result.receipt });
2652
3016
  uploaded.push({
2653
3017
  path: input.path,
2654
3018
  name: input.name,
@@ -2881,6 +3245,7 @@ export {
2881
3245
  ALLOWED_ATTACHMENT_SNIFFED_MIMES,
2882
3246
  ATTACHMENT_ACCEPT,
2883
3247
  ATTACHMENT_MAX_COUNT,
3248
+ ATTACHMENT_STORAGE_FAILURE_MESSAGE,
2884
3249
  ChatTurnInputError,
2885
3250
  DEFAULT_MODEL_FIRST_RESPONSE_TIMEOUT_MS,
2886
3251
  DEFAULT_MODEL_STREAM_OPEN_TIMEOUT_MS,
@@ -2920,6 +3285,7 @@ export {
2920
3285
  checkAttachmentType,
2921
3286
  classifyTerminalFailure,
2922
3287
  createAssistantDraftWriter,
3288
+ createAtomicAttachmentWriter,
2923
3289
  createAttachmentUploadRoute,
2924
3290
  createChatTurnRoutes,
2925
3291
  createSandboxChatProducer,
@@ -2928,6 +3294,7 @@ export {
2928
3294
  defaultValidateAttachmentPath,
2929
3295
  fileMentionsToParts,
2930
3296
  formatBytes,
3297
+ immutableAttachmentPath,
2931
3298
  isCommittingSandboxEvent,
2932
3299
  isDraftContentEvent,
2933
3300
  mediaTypeForMentionPath,