@tangle-network/agent-app 0.43.47 → 0.43.49

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.
Files changed (57) hide show
  1. package/dist/assistant/index.d.ts +3 -2
  2. package/dist/assistant/index.js +4 -3
  3. package/dist/assistant/index.js.map +1 -1
  4. package/dist/chat-routes/index.d.ts +394 -4
  5. package/dist/chat-routes/index.js +510 -12
  6. package/dist/chat-routes/index.js.map +1 -1
  7. package/dist/chat-store/index.d.ts +3 -2
  8. package/dist/chat-store/index.js +19 -1
  9. package/dist/chat-store/index.js.map +1 -1
  10. package/dist/chunk-23LUHHHN.js +282 -0
  11. package/dist/chunk-23LUHHHN.js.map +1 -0
  12. package/dist/{chunk-UHXQ3KNX.js → chunk-54GSK2KK.js} +4 -4
  13. package/dist/{chunk-4AUQIAYU.js → chunk-5MG74GVQ.js} +2 -281
  14. package/dist/chunk-5MG74GVQ.js.map +1 -0
  15. package/dist/{chunk-5RJNEEO2.js → chunk-HRJJH3RX.js} +2 -2
  16. package/dist/{chunk-6E2XJSCT.js → chunk-JGYOYY5D.js} +65 -2
  17. package/dist/chunk-JGYOYY5D.js.map +1 -0
  18. package/dist/{chunk-E7QYOOON.js → chunk-MCJSS6SM.js} +2 -1
  19. package/dist/chunk-MCJSS6SM.js.map +1 -0
  20. package/dist/chunk-NBSBRZ6F.js +337 -0
  21. package/dist/chunk-NBSBRZ6F.js.map +1 -0
  22. package/dist/{chunk-7VMUOD3G.js → chunk-O6H2WD3I.js} +2 -2
  23. package/dist/chunk-O6H2WD3I.js.map +1 -0
  24. package/dist/{file-index-Bn6sitKb.d.ts → file-index-b26ee-_R.d.ts} +1 -1
  25. package/dist/harness/index.d.ts +1 -1
  26. package/dist/harness/index.js +1 -1
  27. package/dist/index.d.ts +3 -2
  28. package/dist/index.js +119 -103
  29. package/dist/{parts-1_3y2JmR.d.ts → parts-IB-Kbb7z.d.ts} +146 -2
  30. package/dist/preset-cloudflare/index.js +2 -2
  31. package/dist/profile/index.d.ts +13 -2
  32. package/dist/profile/index.js +25 -4
  33. package/dist/profile/index.js.map +1 -1
  34. package/dist/prompt/index.d.ts +2 -1
  35. package/dist/prompt/index.js.map +1 -1
  36. package/dist/run/index.js +1 -1
  37. package/dist/sandbox/index.d.ts +1 -1
  38. package/dist/sandbox/index.js +2 -2
  39. package/dist/skills/index.d.ts +183 -8
  40. package/dist/skills/index.js +21 -3
  41. package/dist/skills-placement/index.d.ts +52 -0
  42. package/dist/skills-placement/index.js +44 -0
  43. package/dist/skills-placement/index.js.map +1 -0
  44. package/dist/stream/index.d.ts +4 -47
  45. package/dist/stream/index.js +14 -12
  46. package/dist/stream-normalizer-DWvtmY6F.d.ts +48 -0
  47. package/dist/web-react/index.d.ts +4 -3
  48. package/dist/web-react/index.js +25 -3
  49. package/package.json +12 -2
  50. package/dist/chunk-4AUQIAYU.js.map +0 -1
  51. package/dist/chunk-6E2XJSCT.js.map +0 -1
  52. package/dist/chunk-7VMUOD3G.js.map +0 -1
  53. package/dist/chunk-E7QYOOON.js.map +0 -1
  54. package/dist/chunk-KOG473C4.js +0 -132
  55. package/dist/chunk-KOG473C4.js.map +0 -1
  56. /package/dist/{chunk-UHXQ3KNX.js.map → chunk-54GSK2KK.js.map} +0 -0
  57. /package/dist/{chunk-5RJNEEO2.js.map → chunk-HRJJH3RX.js.map} +0 -0
@@ -7,33 +7,34 @@ import {
7
7
  reconcileStaleTurnLock
8
8
  } from "../chunk-BQV42AFK.js";
9
9
  import {
10
- asRecord,
11
- asString,
10
+ parseJsonObjectBody
11
+ } from "../chunk-HFC4BTWJ.js";
12
+ import {
12
13
  coalesceDeltas,
13
14
  createBufferedTurnTap,
14
- finalizeAssistantParts,
15
- getPartKey,
16
- mergePersistedPart,
17
15
  normalizeClientTurnId,
18
- normalizePersistedPart,
19
- normalizeToolEvent,
20
16
  replayTurnEvents,
21
17
  resolveChatTurn
22
- } from "../chunk-4AUQIAYU.js";
18
+ } from "../chunk-23LUHHHN.js";
23
19
  import {
24
20
  createInteractionAnswerRoute
25
21
  } from "../chunk-6KVH5SP5.js";
26
- import {
27
- parseJsonObjectBody
28
- } from "../chunk-HFC4BTWJ.js";
29
22
  import {
30
23
  ChatTurnInputError,
24
+ DISPATCH_MAX_MEDIA_PARTS,
25
+ DISPATCH_MAX_PARTS,
26
+ DISPATCH_REQUEST_MAX_BYTES,
27
+ DISPATCH_STRUCTURAL_RESERVE_BYTES,
31
28
  INLINE_PARTS_MAX_BYTES,
32
29
  MENTION_MAX_COUNT,
33
30
  assertPromptPartsWithinCap,
31
+ attachmentInputToPart,
32
+ attachmentKindForMime,
33
+ base64WireLen,
34
34
  buildMentionPromptBlock,
35
35
  chatTurnRequestInit,
36
36
  fileMentionsToParts,
37
+ formatBytes,
37
38
  mediaTypeForMentionPath,
38
39
  mentionInputToPart,
39
40
  mentionKindForPath,
@@ -42,7 +43,16 @@ import {
42
43
  promptPartsByteSize,
43
44
  toChatMessageParts,
44
45
  validateSandboxMentionPath
45
- } from "../chunk-6E2XJSCT.js";
46
+ } from "../chunk-JGYOYY5D.js";
47
+ import {
48
+ asRecord,
49
+ asString,
50
+ finalizeAssistantParts,
51
+ getPartKey,
52
+ mergePersistedPart,
53
+ normalizePersistedPart,
54
+ normalizeToolEvent
55
+ } from "../chunk-5MG74GVQ.js";
46
56
  import {
47
57
  cancelStatusFor,
48
58
  interactionPartKey,
@@ -55,6 +65,17 @@ import {
55
65
  parsePlanSubmittedEvent,
56
66
  planToPersistedPart
57
67
  } from "../chunk-SIXYZ2FB.js";
68
+ import {
69
+ flattenHistory,
70
+ readSandboxBinaryBytes,
71
+ statSandboxFileSize
72
+ } from "../chunk-HRJJH3RX.js";
73
+ import "../chunk-MCJSS6SM.js";
74
+ import "../chunk-PJC4NXPA.js";
75
+ import "../chunk-7EVZUIHW.js";
76
+ import "../chunk-S5SRJJQG.js";
77
+ import "../chunk-7W5XSTUF.js";
78
+ import "../chunk-MFRCM32T.js";
58
79
 
59
80
  // src/chat-routes/turn-routes.ts
60
81
  import { deriveExecutionId, handleChatTurn } from "@tangle-network/agent-runtime";
@@ -552,6 +573,7 @@ function createSandboxChatProducer(options) {
552
573
  const announcedTools = /* @__PURE__ */ new Set();
553
574
  const settledTools = /* @__PURE__ */ new Set();
554
575
  let stepCounter = 0;
576
+ const promotedFileParts = /* @__PURE__ */ new Map();
555
577
  function recordPersistedPart(part, delta, keyOverride) {
556
578
  const persisted = normalizePersistedPart(part);
557
579
  if (!persisted) return;
@@ -623,6 +645,33 @@ function createSandboxChatProducer(options) {
623
645
  recordPersistedPart(part, void 0, `step-start:#${stepCounter}`);
624
646
  continue;
625
647
  }
648
+ if (partType === "file" && options.promoteFilePart) {
649
+ const promote = options.promoteFilePart;
650
+ const rawId = asString(part.id);
651
+ const rawUrl = asString(part.url);
652
+ const memoKey = rawId ? `id:${rawId}` : rawUrl ? `url:${rawUrl}` : void 0;
653
+ const attempt = () => Promise.resolve().then(() => promote(part)).catch((err) => {
654
+ const reason = err instanceof Error ? err.message : String(err);
655
+ log("[chat-routes] file part promotion threw", { key: memoKey ?? "(keyless)", error: reason });
656
+ return { succeeded: false, reason };
657
+ });
658
+ let pending;
659
+ if (memoKey) {
660
+ pending = promotedFileParts.get(memoKey) ?? attempt();
661
+ promotedFileParts.set(memoKey, pending);
662
+ } else {
663
+ pending = attempt();
664
+ }
665
+ const outcome = await pending;
666
+ if (outcome.succeeded) {
667
+ recordPersistedPart(outcome.part, void 0, outcome.key);
668
+ } else if (outcome.part) {
669
+ recordPersistedPart(outcome.part, void 0, outcome.key);
670
+ } else {
671
+ recordPersistedPart(part, void 0);
672
+ }
673
+ continue;
674
+ }
626
675
  recordPersistedPart(part, void 0);
627
676
  continue;
628
677
  }
@@ -845,15 +894,459 @@ function createUploadRoute(options) {
845
894
  return Response.json({ files: uploaded });
846
895
  };
847
896
  }
897
+
898
+ // src/chat-routes/resolve-attachments.ts
899
+ var ATTACHMENT_MAX_COUNT = 10;
900
+ var MAX_ATTACHMENT_TOTAL_BYTES = 25 * 1024 * 1024;
901
+ var MAX_ATTACHMENT_NAME_LENGTH = 256;
902
+ function attachmentTotalSizeErrorMessage(totalBytes, limitBytes) {
903
+ return `Attachments total ${formatBytes(totalBytes)}; each message is limited to ${formatBytes(limitBytes)}`;
904
+ }
905
+ function isAttachmentKind(value) {
906
+ return value === "image" || value === "file";
907
+ }
908
+ var CONTROL_CHARS = /[\x00-\x1F\x7F]/;
909
+ function defaultValidateAttachmentPath(path) {
910
+ if (path.includes("\0")) return { succeeded: false, error: "attachment path must not contain null bytes" };
911
+ if (path.includes("\\")) return { succeeded: false, error: "attachment path must not contain backslashes" };
912
+ if (CONTROL_CHARS.test(path)) return { succeeded: false, error: "attachment path must not contain control characters" };
913
+ if (path.startsWith("/")) return { succeeded: false, error: "attachment path must be store-relative, not absolute" };
914
+ const segments = path.split("/");
915
+ if (segments.some((segment) => segment === "..")) {
916
+ return { succeeded: false, error: 'attachment path must not contain ".." segments' };
917
+ }
918
+ if (segments.some((segment) => segment.startsWith("."))) {
919
+ return { succeeded: false, error: "attachment path must not contain a hidden (dotfile) segment" };
920
+ }
921
+ return { succeeded: true };
922
+ }
923
+ function parseAttachmentInput(value, index, validatePath) {
924
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
925
+ return { succeeded: false, error: `attachments[${index}] must be an object` };
926
+ }
927
+ const record = value;
928
+ const path = record.path;
929
+ if (typeof path !== "string" || !path) {
930
+ return { succeeded: false, error: `attachments[${index}].path must be a non-empty string` };
931
+ }
932
+ const name = record.name;
933
+ if (typeof name !== "string" || !name.trim()) {
934
+ return { succeeded: false, error: `attachments[${index}].name must be a non-empty string` };
935
+ }
936
+ if (name.length > MAX_ATTACHMENT_NAME_LENGTH) {
937
+ return { succeeded: false, error: `attachments[${index}].name must not exceed ${MAX_ATTACHMENT_NAME_LENGTH} characters` };
938
+ }
939
+ if (CONTROL_CHARS.test(name)) {
940
+ return { succeeded: false, error: `attachments[${index}].name must not contain control characters` };
941
+ }
942
+ const size = record.size;
943
+ if (typeof size !== "number" || !Number.isFinite(size)) {
944
+ return { succeeded: false, error: `attachments[${index}].size must be a finite number` };
945
+ }
946
+ if (size < 0) {
947
+ return { succeeded: false, error: `attachments[${index}].size must not be negative` };
948
+ }
949
+ const mediaType = record.mediaType;
950
+ if (typeof mediaType !== "string") {
951
+ return { succeeded: false, error: `attachments[${index}].mediaType must be a string` };
952
+ }
953
+ const kind = record.kind;
954
+ if (!isAttachmentKind(kind)) {
955
+ return { succeeded: false, error: `attachments[${index}].kind must be "image" or "file"` };
956
+ }
957
+ const pathCheck = validatePath(path);
958
+ if (!pathCheck.succeeded) return { succeeded: false, error: pathCheck.error };
959
+ return { succeeded: true, value: { path, name, size, mediaType, kind } };
960
+ }
961
+ async function resolveChatAttachments(value, options) {
962
+ const maxCount = options.maxCount ?? ATTACHMENT_MAX_COUNT;
963
+ const maxTotalBytes = options.maxTotalBytes ?? MAX_ATTACHMENT_TOTAL_BYTES;
964
+ const validatePath = options.validatePath ?? defaultValidateAttachmentPath;
965
+ if (value === void 0 || value === null) return { succeeded: true, value: [] };
966
+ if (!Array.isArray(value)) return { succeeded: false, error: "attachments must be an array" };
967
+ if (value.length > maxCount) {
968
+ return { succeeded: false, error: `attachments must not exceed ${maxCount} entries` };
969
+ }
970
+ const inputs = [];
971
+ const seenPaths = /* @__PURE__ */ new Set();
972
+ for (let index = 0; index < value.length; index += 1) {
973
+ const parsed = parseAttachmentInput(value[index], index, validatePath);
974
+ if (!parsed.succeeded) return parsed;
975
+ if (seenPaths.has(parsed.value.path)) {
976
+ return { succeeded: false, error: `attachments must not repeat a path: ${parsed.value.path}` };
977
+ }
978
+ seenPaths.add(parsed.value.path);
979
+ inputs.push(parsed.value);
980
+ }
981
+ const advisoryTotal = inputs.reduce((sum, input) => sum + input.size, 0);
982
+ if (advisoryTotal > maxTotalBytes) {
983
+ return { succeeded: false, error: attachmentTotalSizeErrorMessage(advisoryTotal, maxTotalBytes) };
984
+ }
985
+ let totalStoredBytes = 0;
986
+ for (const input of inputs) {
987
+ const read = await options.readAttachment(options.scopeId, input.path);
988
+ if (!read.ok) return { succeeded: false, error: read.reason };
989
+ totalStoredBytes += read.size;
990
+ if (totalStoredBytes > maxTotalBytes) {
991
+ return { succeeded: false, error: attachmentTotalSizeErrorMessage(totalStoredBytes, maxTotalBytes) };
992
+ }
993
+ input.size = read.size;
994
+ }
995
+ return { succeeded: true, value: inputs.map(attachmentInputToPart) };
996
+ }
997
+
998
+ // src/chat-routes/dispatch-parts.ts
999
+ function byteLen(value) {
1000
+ return new TextEncoder().encode(value).length;
1001
+ }
1002
+ async function readSandboxMention(box, absolutePath, options) {
1003
+ const stat = await statSandboxFileSize(box, absolutePath);
1004
+ if (!stat.succeeded) {
1005
+ return { succeeded: false, error: `mentioned sandbox file missing or unreadable: ${absolutePath} \u2014 ${stat.error}` };
1006
+ }
1007
+ if (!options.readBytes) return { succeeded: true, value: { size: stat.value } };
1008
+ const read = await readSandboxBinaryBytes(box, absolutePath, stat.value);
1009
+ if (!read.succeeded) {
1010
+ return { succeeded: false, error: `mentioned sandbox file read failed: ${absolutePath} \u2014 ${read.error}` };
1011
+ }
1012
+ return { succeeded: true, value: { size: stat.value, base64: bytesToBase64(read.value.bytes) } };
1013
+ }
1014
+ function violatesUrlPathXor(part) {
1015
+ if (part.type === "text") return false;
1016
+ const hasUrl = typeof part.url === "string" && part.url.startsWith("data:");
1017
+ const hasPath = typeof part.path === "string" && part.path.startsWith("/");
1018
+ return hasUrl === hasPath;
1019
+ }
1020
+ function readResultToBase64(read) {
1021
+ if (typeof read.base64 === "string") return read.base64;
1022
+ if (read.bytes) return bytesToBase64(read.bytes);
1023
+ return void 0;
1024
+ }
1025
+ async function buildDispatchParts(input) {
1026
+ const readMention = input.readSandboxMention ?? readSandboxMention;
1027
+ const resolveMentionPath = input.resolveMentionPath ?? input.resolveAttachmentPath;
1028
+ const forcePath = input.forcePath ?? false;
1029
+ const mentions = input.mentions ?? [];
1030
+ const requestMaxBytes = input.requestMaxBytes ?? DISPATCH_REQUEST_MAX_BYTES;
1031
+ const structuralReserveBytes = input.structuralReserveBytes ?? DISPATCH_STRUCTURAL_RESERVE_BYTES;
1032
+ const maxParts = input.maxParts ?? DISPATCH_MAX_PARTS;
1033
+ const parts = [{ type: "text", text: input.text }];
1034
+ const emittedAbsPaths = /* @__PURE__ */ new Set();
1035
+ const flattenedForSizing = flattenHistory(input.text, input.history);
1036
+ const inlineBudget = requestMaxBytes - base64WireLen(byteLen(flattenedForSizing)) - byteLen(JSON.stringify(input.systemPrompt)) - input.profileWireBytes - structuralReserveBytes;
1037
+ let runningInline = 0;
1038
+ for (const attachment of input.attachments) {
1039
+ if (!attachment.path) {
1040
+ return { succeeded: false, error: `attachment path must be non-empty: ${attachment.name}` };
1041
+ }
1042
+ let read;
1043
+ try {
1044
+ read = await input.readAttachment(input.scopeId, attachment.path);
1045
+ } catch (err) {
1046
+ return {
1047
+ succeeded: false,
1048
+ error: `attachment store read failed: ${attachment.path} \u2014 ${err instanceof Error ? err.message : String(err)}`
1049
+ };
1050
+ }
1051
+ if (!read.ok) return { succeeded: false, error: read.reason };
1052
+ const base64 = readResultToBase64(read);
1053
+ if (base64 === void 0) {
1054
+ return { succeeded: false, error: `attachment store read produced no content: ${attachment.path}` };
1055
+ }
1056
+ const mediaType = attachment.mediaType ?? read.mediaType;
1057
+ if (attachment.type === "image" && !mediaType) {
1058
+ return { succeeded: false, error: `attachment is missing a mediaType required for an image data URI: ${attachment.path}` };
1059
+ }
1060
+ const absPath = input.resolveAttachmentPath(attachment.path);
1061
+ emittedAbsPaths.add(absPath);
1062
+ if (attachment.type === "image") {
1063
+ const inlinePart2 = {
1064
+ type: "image",
1065
+ filename: attachment.name,
1066
+ mediaType,
1067
+ url: `data:${mediaType};base64,${base64}`
1068
+ };
1069
+ const cost2 = byteLen(JSON.stringify(inlinePart2));
1070
+ if (!forcePath && runningInline + cost2 <= inlineBudget) {
1071
+ parts.push(inlinePart2);
1072
+ runningInline += cost2;
1073
+ } else {
1074
+ parts.push({ type: "image", filename: attachment.name, mediaType, path: absPath });
1075
+ }
1076
+ continue;
1077
+ }
1078
+ const fileMediaType = mediaType ?? "application/octet-stream";
1079
+ const inlinePart = {
1080
+ type: "file",
1081
+ filename: attachment.name,
1082
+ mediaType: fileMediaType,
1083
+ url: `data:${fileMediaType};base64,${base64}`
1084
+ };
1085
+ const cost = byteLen(JSON.stringify(inlinePart));
1086
+ if (!forcePath && runningInline + cost <= inlineBudget) {
1087
+ parts.push(inlinePart);
1088
+ runningInline += cost;
1089
+ } else {
1090
+ parts.push({ type: "file", path: absPath });
1091
+ }
1092
+ }
1093
+ if (mentions.length > 0 && !input.box) {
1094
+ return { succeeded: false, error: "internal error: sandbox mentions require a box to read from" };
1095
+ }
1096
+ for (const mention of mentions) {
1097
+ if (!mention.path) {
1098
+ return { succeeded: false, error: `mention path must be non-empty: ${mention.name}` };
1099
+ }
1100
+ const absPath = resolveMentionPath(mention.path);
1101
+ if (emittedAbsPaths.has(absPath)) continue;
1102
+ emittedAbsPaths.add(absPath);
1103
+ const isImage = mention.mentionKind === "image";
1104
+ const mediaType = isImage ? mediaTypeForMentionPath(mention.path) : void 0;
1105
+ let stat;
1106
+ try {
1107
+ stat = await readMention(input.box, absPath, { readBytes: false });
1108
+ } catch (err) {
1109
+ return { succeeded: false, error: `mention read failed: ${absPath} \u2014 ${err instanceof Error ? err.message : String(err)}` };
1110
+ }
1111
+ if (!stat.succeeded) return { succeeded: false, error: stat.error };
1112
+ const projectedInlineCost = base64WireLen(stat.value.size) + byteLen(JSON.stringify({ type: "image", filename: mention.name, mediaType: mediaType ?? "", url: "" }));
1113
+ if (isImage && mediaType && !forcePath && runningInline + projectedInlineCost <= inlineBudget) {
1114
+ let read;
1115
+ try {
1116
+ read = await readMention(input.box, absPath, { readBytes: true });
1117
+ } catch (err) {
1118
+ return { succeeded: false, error: `mention read failed: ${absPath} \u2014 ${err instanceof Error ? err.message : String(err)}` };
1119
+ }
1120
+ if (!read.succeeded) return { succeeded: false, error: read.error };
1121
+ if (!read.value.base64) return { succeeded: false, error: `mentioned image produced no bytes: ${absPath}` };
1122
+ const inlinePart = {
1123
+ type: "image",
1124
+ filename: mention.name,
1125
+ mediaType,
1126
+ url: `data:${mediaType};base64,${read.value.base64}`
1127
+ };
1128
+ const cost = byteLen(JSON.stringify(inlinePart));
1129
+ if (runningInline + cost <= inlineBudget) {
1130
+ parts.push(inlinePart);
1131
+ runningInline += cost;
1132
+ continue;
1133
+ }
1134
+ }
1135
+ parts.push(
1136
+ isImage && mediaType ? { type: "image", filename: mention.name, mediaType, path: absPath } : { type: "file", path: absPath }
1137
+ );
1138
+ }
1139
+ for (const part of parts) {
1140
+ if (violatesUrlPathXor(part)) {
1141
+ return { succeeded: false, error: "internal error: emitted media part violates the url/path exclusivity invariant" };
1142
+ }
1143
+ }
1144
+ const textPartSize = base64WireLen(byteLen(flattenedForSizing));
1145
+ const mediaPartsSize = parts.slice(1).reduce((total, part) => total + byteLen(JSON.stringify(part)), 0);
1146
+ const systemPromptSize = byteLen(JSON.stringify(input.systemPrompt));
1147
+ if (textPartSize + mediaPartsSize + systemPromptSize + input.profileWireBytes + structuralReserveBytes > requestMaxBytes) {
1148
+ return { succeeded: false, error: "dispatch parts exceed the sandbox proxy request cap even after path demotion" };
1149
+ }
1150
+ if (parts.length > maxParts) {
1151
+ return { succeeded: false, error: `dispatch parts exceed the sidecar per-request cap of ${maxParts}` };
1152
+ }
1153
+ return { succeeded: true, value: parts };
1154
+ }
1155
+
1156
+ // src/chat-routes/promote-file-part.ts
1157
+ var PROMOTE_MAX_FILE_BYTES = 10 * 1024 * 1024;
1158
+ var EXT_TO_MIME = {
1159
+ md: "text/markdown",
1160
+ markdown: "text/markdown",
1161
+ txt: "text/plain",
1162
+ log: "text/plain",
1163
+ csv: "text/csv",
1164
+ tsv: "text/tab-separated-values",
1165
+ json: "application/json",
1166
+ yaml: "text/yaml",
1167
+ yml: "text/yaml",
1168
+ xml: "application/xml",
1169
+ html: "text/html",
1170
+ htm: "text/html",
1171
+ pdf: "application/pdf",
1172
+ png: "image/png",
1173
+ jpg: "image/jpeg",
1174
+ jpeg: "image/jpeg",
1175
+ gif: "image/gif",
1176
+ webp: "image/webp",
1177
+ avif: "image/avif",
1178
+ heic: "image/heic",
1179
+ heif: "image/heif",
1180
+ svg: "image/svg+xml",
1181
+ mp4: "video/mp4",
1182
+ webm: "video/webm",
1183
+ mov: "video/quicktime",
1184
+ mp3: "audio/mpeg",
1185
+ wav: "audio/wav",
1186
+ ogg: "audio/ogg",
1187
+ m4a: "audio/mp4",
1188
+ aac: "audio/aac"
1189
+ };
1190
+ function sniffMimeFromName(filename) {
1191
+ const ext = filename.split(".").pop()?.toLowerCase();
1192
+ if (!ext) return "text/plain";
1193
+ return EXT_TO_MIME[ext] ?? "text/plain";
1194
+ }
1195
+ function sanitizeAttachmentFileName(name) {
1196
+ const sanitized = name.trim().replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^[.-]+/, "");
1197
+ return sanitized || "file";
1198
+ }
1199
+ function base64ToBytes(base64) {
1200
+ const binary = atob(base64);
1201
+ const bytes = new Uint8Array(binary.length);
1202
+ for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);
1203
+ return bytes;
1204
+ }
1205
+ function parseDataUrl(url) {
1206
+ const match = /^data:[^,]*,([\s\S]*)$/.exec(url);
1207
+ if (!match) return null;
1208
+ return { base64: /;base64,/i.test(url), data: match[1] ?? "" };
1209
+ }
1210
+ function dataUrlMime(url) {
1211
+ if (!url) return void 0;
1212
+ const match = /^data:([^;,]+)[;,]/.exec(url);
1213
+ return match ? match[1] : void 0;
1214
+ }
1215
+ function basenameFromUrl(url) {
1216
+ if (!url || url.startsWith("data:")) return void 0;
1217
+ const withoutQuery = url.split(/[?#]/)[0] ?? url;
1218
+ const segments = withoutQuery.split("/").filter(Boolean);
1219
+ return segments[segments.length - 1] || void 0;
1220
+ }
1221
+ function resolveFileUrlPath(url) {
1222
+ const withoutScheme = url.startsWith("file://") ? url.slice("file://".length) : url;
1223
+ try {
1224
+ return { succeeded: true, path: decodeURIComponent(withoutScheme) };
1225
+ } catch (err) {
1226
+ return { succeeded: false, reason: `malformed file path: ${err instanceof Error ? err.message : String(err)}` };
1227
+ }
1228
+ }
1229
+ function oversizeReason(filename, actual, limit) {
1230
+ return `${filename} is ${formatBytes(actual)}; attachments are limited to ${formatBytes(limit)}`;
1231
+ }
1232
+ function resolveDataUrlBytes(url, filename, maxBytes) {
1233
+ const parsed = parseDataUrl(url);
1234
+ if (!parsed) return { succeeded: false, reason: "malformed data URI" };
1235
+ let bytes;
1236
+ try {
1237
+ bytes = parsed.base64 ? base64ToBytes(parsed.data) : new TextEncoder().encode(decodeURIComponent(parsed.data));
1238
+ } catch (err) {
1239
+ return { succeeded: false, reason: `failed to decode data URI: ${err instanceof Error ? err.message : String(err)}` };
1240
+ }
1241
+ if (bytes.byteLength > maxBytes) {
1242
+ return { succeeded: false, reason: oversizeReason(filename, bytes.byteLength, maxBytes) };
1243
+ }
1244
+ return { succeeded: true, bytes };
1245
+ }
1246
+ async function resolveSandboxFileBytes(input) {
1247
+ const stat = await statSandboxFileSize(input.box, input.path, { sessionId: input.sessionId });
1248
+ if (!stat.succeeded) {
1249
+ return { succeeded: false, reason: `could not stat agent file: ${stat.error}` };
1250
+ }
1251
+ if (stat.value > input.maxBytes) {
1252
+ return { succeeded: false, reason: oversizeReason(input.filename, stat.value, input.maxBytes) };
1253
+ }
1254
+ const read = await readSandboxBinaryBytes(input.box, input.path, stat.value, { sessionId: input.sessionId });
1255
+ if (!read.succeeded) {
1256
+ return { succeeded: false, reason: `could not read agent file: ${read.error}` };
1257
+ }
1258
+ return { succeeded: true, bytes: read.value.bytes };
1259
+ }
1260
+ async function resolveBytes(input) {
1261
+ const url = input.raw.url;
1262
+ if (!url) return { succeeded: false, reason: "the file part carries no url" };
1263
+ if (url.startsWith("data:")) return resolveDataUrlBytes(url, input.filename, input.maxBytes);
1264
+ const isSandboxPath = url.startsWith("file://") || url.startsWith("/");
1265
+ if (!isSandboxPath) return { succeeded: false, reason: `unsupported file URL scheme: ${url}` };
1266
+ if (!input.box) return { succeeded: false, reason: "no sandbox to read agent file" };
1267
+ const resolvedPath = resolveFileUrlPath(url);
1268
+ if (!resolvedPath.succeeded) return resolvedPath;
1269
+ return resolveSandboxFileBytes({
1270
+ path: resolvedPath.path,
1271
+ box: input.box,
1272
+ sessionId: input.sessionId,
1273
+ filename: input.filename,
1274
+ maxBytes: input.maxBytes
1275
+ });
1276
+ }
1277
+ async function hash8(seed) {
1278
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(seed));
1279
+ return Array.from(new Uint8Array(digest).slice(0, 4)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
1280
+ }
1281
+ function defaultBuildAttachmentPath(args) {
1282
+ const extensionMatch = /\.[A-Za-z0-9]+$/.exec(args.filename);
1283
+ const extension = extensionMatch ? extensionMatch[0] : "";
1284
+ const base = extension ? args.filename.slice(0, -extension.length) : args.filename;
1285
+ return `uploads/agent/${args.date}/${base}-${args.hash8}${extension}`;
1286
+ }
1287
+ async function promoteAgentFilePart(options) {
1288
+ const maxBytes = options.maxBytes ?? PROMOTE_MAX_FILE_BYTES;
1289
+ const sniffMime = options.sniffMime ?? sniffMimeFromName;
1290
+ const buildAttachmentPath = options.buildAttachmentPath ?? defaultBuildAttachmentPath;
1291
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
1292
+ const filename = sanitizeAttachmentFileName(
1293
+ options.raw.filename ?? basenameFromUrl(options.raw.url) ?? "agent-file"
1294
+ );
1295
+ const resolved = await resolveBytes({
1296
+ raw: options.raw,
1297
+ box: options.box,
1298
+ sessionId: options.sessionId,
1299
+ filename,
1300
+ maxBytes
1301
+ });
1302
+ if (!resolved.succeeded) return { succeeded: false, filename, reason: resolved.reason };
1303
+ const mediaType = options.raw.mediaType ?? options.raw.mime ?? dataUrlMime(options.raw.url) ?? sniffMime(filename);
1304
+ const kind = attachmentKindForMime(mediaType);
1305
+ const digest = await hash8(options.raw.id ?? options.raw.url ?? filename);
1306
+ const date = now().toISOString().split("T")[0] ?? "";
1307
+ const path = buildAttachmentPath({ filename, hash8: digest, date, mediaType, kind });
1308
+ let written;
1309
+ try {
1310
+ written = await options.writeAttachment(options.scopeId, path, resolved.bytes, {
1311
+ mediaType,
1312
+ name: filename,
1313
+ originalName: options.raw.filename ?? filename,
1314
+ size: resolved.bytes.byteLength
1315
+ });
1316
+ } catch (err) {
1317
+ return { succeeded: false, filename, reason: err instanceof Error ? err.message : String(err) };
1318
+ }
1319
+ if (!written.ok) return { succeeded: false, filename, reason: written.reason };
1320
+ return {
1321
+ succeeded: true,
1322
+ part: {
1323
+ type: kind,
1324
+ path,
1325
+ name: filename,
1326
+ size: resolved.bytes.byteLength,
1327
+ mediaType
1328
+ }
1329
+ };
1330
+ }
848
1331
  export {
1332
+ ATTACHMENT_MAX_COUNT,
849
1333
  ChatTurnInputError,
850
1334
  DEFAULT_STALE_TURN_LOCK_GRACE_MS,
851
1335
  DEFAULT_TERMINAL_TURN_LOCK_GRACE_MS,
1336
+ DISPATCH_MAX_MEDIA_PARTS,
1337
+ DISPATCH_MAX_PARTS,
1338
+ DISPATCH_REQUEST_MAX_BYTES,
1339
+ DISPATCH_STRUCTURAL_RESERVE_BYTES,
852
1340
  INLINE_PARTS_MAX_BYTES,
1341
+ MAX_ATTACHMENT_TOTAL_BYTES,
853
1342
  MENTION_MAX_COUNT,
1343
+ PROMOTE_MAX_FILE_BYTES,
854
1344
  UPLOAD_INLINE_MAX_BYTES,
855
1345
  UPLOAD_MAX_FILE_BYTES,
856
1346
  assertPromptPartsWithinCap,
1347
+ attachmentTotalSizeErrorMessage,
1348
+ base64WireLen,
1349
+ buildDispatchParts,
857
1350
  buildMentionPromptBlock,
858
1351
  bytesToBase64,
859
1352
  chatTurnRequestInit,
@@ -861,14 +1354,19 @@ export {
861
1354
  createSandboxChatProducer,
862
1355
  createSandboxFileIndexRoute,
863
1356
  createUploadRoute,
1357
+ defaultValidateAttachmentPath,
864
1358
  fileMentionsToParts,
1359
+ formatBytes,
865
1360
  mediaTypeForMentionPath,
866
1361
  mentionKindForPath,
867
1362
  parseChatTurnParts,
868
1363
  parseFileMentions,
1364
+ promoteAgentFilePart,
869
1365
  promptPartsByteSize,
870
1366
  reconcileStaleTurnLock,
1367
+ resolveChatAttachments,
871
1368
  sanitizeUploadFilename,
1369
+ sniffMimeFromName,
872
1370
  validateSandboxMentionPath,
873
1371
  withDurableChatProjection
874
1372
  };