@assemblyline-agents/runtime 0.1.0

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 (331) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +19 -0
  3. package/dist/agent-harness.d.ts +108 -0
  4. package/dist/agent-harness.d.ts.map +1 -0
  5. package/dist/agent-harness.js +2 -0
  6. package/dist/agent-harness.js.map +1 -0
  7. package/dist/attachments-legacy.d.ts +17 -0
  8. package/dist/attachments-legacy.d.ts.map +1 -0
  9. package/dist/attachments-legacy.js +273 -0
  10. package/dist/attachments-legacy.js.map +1 -0
  11. package/dist/attachments.d.ts +49 -0
  12. package/dist/attachments.d.ts.map +1 -0
  13. package/dist/attachments.js +832 -0
  14. package/dist/attachments.js.map +1 -0
  15. package/dist/builtin-tool-executors.d.ts +96 -0
  16. package/dist/builtin-tool-executors.d.ts.map +1 -0
  17. package/dist/builtin-tool-executors.js +501 -0
  18. package/dist/builtin-tool-executors.js.map +1 -0
  19. package/dist/builtin-tools.d.ts +30 -0
  20. package/dist/builtin-tools.d.ts.map +1 -0
  21. package/dist/builtin-tools.js +690 -0
  22. package/dist/builtin-tools.js.map +1 -0
  23. package/dist/capability-catalog.d.ts +27 -0
  24. package/dist/capability-catalog.d.ts.map +1 -0
  25. package/dist/capability-catalog.js +193 -0
  26. package/dist/capability-catalog.js.map +1 -0
  27. package/dist/channel-http-util.d.ts +34 -0
  28. package/dist/channel-http-util.d.ts.map +1 -0
  29. package/dist/channel-http-util.js +93 -0
  30. package/dist/channel-http-util.js.map +1 -0
  31. package/dist/channel-ingress.d.ts +56 -0
  32. package/dist/channel-ingress.d.ts.map +1 -0
  33. package/dist/channel-ingress.js +273 -0
  34. package/dist/channel-ingress.js.map +1 -0
  35. package/dist/channel.d.ts +146 -0
  36. package/dist/channel.d.ts.map +1 -0
  37. package/dist/channel.js +2 -0
  38. package/dist/channel.js.map +1 -0
  39. package/dist/checkpoints.d.ts +34 -0
  40. package/dist/checkpoints.d.ts.map +1 -0
  41. package/dist/checkpoints.js +86 -0
  42. package/dist/checkpoints.js.map +1 -0
  43. package/dist/code-mode.d.ts +20 -0
  44. package/dist/code-mode.d.ts.map +1 -0
  45. package/dist/code-mode.js +658 -0
  46. package/dist/code-mode.js.map +1 -0
  47. package/dist/computer-relay-protocol.d.ts +36 -0
  48. package/dist/computer-relay-protocol.d.ts.map +1 -0
  49. package/dist/computer-relay-protocol.js +127 -0
  50. package/dist/computer-relay-protocol.js.map +1 -0
  51. package/dist/concurrency.d.ts +68 -0
  52. package/dist/concurrency.d.ts.map +1 -0
  53. package/dist/concurrency.js +193 -0
  54. package/dist/concurrency.js.map +1 -0
  55. package/dist/connection-auth-util.d.ts +56 -0
  56. package/dist/connection-auth-util.d.ts.map +1 -0
  57. package/dist/connection-auth-util.js +154 -0
  58. package/dist/connection-auth-util.js.map +1 -0
  59. package/dist/connection-clients.d.ts +50 -0
  60. package/dist/connection-clients.d.ts.map +1 -0
  61. package/dist/connection-clients.js +402 -0
  62. package/dist/connection-clients.js.map +1 -0
  63. package/dist/connection-stores.d.ts +108 -0
  64. package/dist/connection-stores.d.ts.map +1 -0
  65. package/dist/connection-stores.js +127 -0
  66. package/dist/connection-stores.js.map +1 -0
  67. package/dist/connection-util.d.ts +27 -0
  68. package/dist/connection-util.d.ts.map +1 -0
  69. package/dist/connection-util.js +118 -0
  70. package/dist/connection-util.js.map +1 -0
  71. package/dist/connections.d.ts +152 -0
  72. package/dist/connections.d.ts.map +1 -0
  73. package/dist/connections.js +838 -0
  74. package/dist/connections.js.map +1 -0
  75. package/dist/content-capture.d.ts +32 -0
  76. package/dist/content-capture.d.ts.map +1 -0
  77. package/dist/content-capture.js +77 -0
  78. package/dist/content-capture.js.map +1 -0
  79. package/dist/context-policy-util.d.ts +45 -0
  80. package/dist/context-policy-util.d.ts.map +1 -0
  81. package/dist/context-policy-util.js +120 -0
  82. package/dist/context-policy-util.js.map +1 -0
  83. package/dist/conversation-context.d.ts +55 -0
  84. package/dist/conversation-context.d.ts.map +1 -0
  85. package/dist/conversation-context.js +179 -0
  86. package/dist/conversation-context.js.map +1 -0
  87. package/dist/conversation-transcript.d.ts +49 -0
  88. package/dist/conversation-transcript.d.ts.map +1 -0
  89. package/dist/conversation-transcript.js +77 -0
  90. package/dist/conversation-transcript.js.map +1 -0
  91. package/dist/conversation-turn-dispatch.d.ts +46 -0
  92. package/dist/conversation-turn-dispatch.d.ts.map +1 -0
  93. package/dist/conversation-turn-dispatch.js +201 -0
  94. package/dist/conversation-turn-dispatch.js.map +1 -0
  95. package/dist/delivery-queue.d.ts +89 -0
  96. package/dist/delivery-queue.d.ts.map +1 -0
  97. package/dist/delivery-queue.js +244 -0
  98. package/dist/delivery-queue.js.map +1 -0
  99. package/dist/delivery-send.d.ts +44 -0
  100. package/dist/delivery-send.d.ts.map +1 -0
  101. package/dist/delivery-send.js +494 -0
  102. package/dist/delivery-send.js.map +1 -0
  103. package/dist/env-config.d.ts +6 -0
  104. package/dist/env-config.d.ts.map +1 -0
  105. package/dist/env-config.js +27 -0
  106. package/dist/env-config.js.map +1 -0
  107. package/dist/eval-run-options.d.ts +37 -0
  108. package/dist/eval-run-options.d.ts.map +1 -0
  109. package/dist/eval-run-options.js +106 -0
  110. package/dist/eval-run-options.js.map +1 -0
  111. package/dist/harness-continuation.d.ts +54 -0
  112. package/dist/harness-continuation.d.ts.map +1 -0
  113. package/dist/harness-continuation.js +58 -0
  114. package/dist/harness-continuation.js.map +1 -0
  115. package/dist/harness-loop.d.ts +110 -0
  116. package/dist/harness-loop.d.ts.map +1 -0
  117. package/dist/harness-loop.js +575 -0
  118. package/dist/harness-loop.js.map +1 -0
  119. package/dist/hook-dispatch.d.ts +19 -0
  120. package/dist/hook-dispatch.d.ts.map +1 -0
  121. package/dist/hook-dispatch.js +37 -0
  122. package/dist/hook-dispatch.js.map +1 -0
  123. package/dist/http.d.ts +26 -0
  124. package/dist/http.d.ts.map +1 -0
  125. package/dist/http.js +114 -0
  126. package/dist/http.js.map +1 -0
  127. package/dist/in-memory-conversation-turn-store.d.ts +29 -0
  128. package/dist/in-memory-conversation-turn-store.d.ts.map +1 -0
  129. package/dist/in-memory-conversation-turn-store.js +166 -0
  130. package/dist/in-memory-conversation-turn-store.js.map +1 -0
  131. package/dist/in-memory-run-store.d.ts +39 -0
  132. package/dist/in-memory-run-store.d.ts.map +1 -0
  133. package/dist/in-memory-run-store.js +332 -0
  134. package/dist/in-memory-run-store.js.map +1 -0
  135. package/dist/in-memory-sandbox-session-store.d.ts +33 -0
  136. package/dist/in-memory-sandbox-session-store.d.ts.map +1 -0
  137. package/dist/in-memory-sandbox-session-store.js +186 -0
  138. package/dist/in-memory-sandbox-session-store.js.map +1 -0
  139. package/dist/in-memory-small-stores.d.ts +117 -0
  140. package/dist/in-memory-small-stores.d.ts.map +1 -0
  141. package/dist/in-memory-small-stores.js +340 -0
  142. package/dist/in-memory-small-stores.js.map +1 -0
  143. package/dist/in-memory-usage-store.d.ts +15 -0
  144. package/dist/in-memory-usage-store.d.ts.map +1 -0
  145. package/dist/in-memory-usage-store.js +49 -0
  146. package/dist/in-memory-usage-store.js.map +1 -0
  147. package/dist/index.d.ts +467 -0
  148. package/dist/index.d.ts.map +1 -0
  149. package/dist/index.js +2046 -0
  150. package/dist/index.js.map +1 -0
  151. package/dist/json-util.d.ts +19 -0
  152. package/dist/json-util.d.ts.map +1 -0
  153. package/dist/json-util.js +59 -0
  154. package/dist/json-util.js.map +1 -0
  155. package/dist/local-sandbox.d.ts +16 -0
  156. package/dist/local-sandbox.d.ts.map +1 -0
  157. package/dist/local-sandbox.js +327 -0
  158. package/dist/local-sandbox.js.map +1 -0
  159. package/dist/logging.d.ts +16 -0
  160. package/dist/logging.d.ts.map +1 -0
  161. package/dist/logging.js +41 -0
  162. package/dist/logging.js.map +1 -0
  163. package/dist/mcp-client.d.ts +27 -0
  164. package/dist/mcp-client.d.ts.map +1 -0
  165. package/dist/mcp-client.js +169 -0
  166. package/dist/mcp-client.js.map +1 -0
  167. package/dist/model-harnesses.d.ts +4 -0
  168. package/dist/model-harnesses.d.ts.map +1 -0
  169. package/dist/model-harnesses.js +6 -0
  170. package/dist/model-harnesses.js.map +1 -0
  171. package/dist/model-media.d.ts +36 -0
  172. package/dist/model-media.d.ts.map +1 -0
  173. package/dist/model-media.js +234 -0
  174. package/dist/model-media.js.map +1 -0
  175. package/dist/operator-controls.d.ts +31 -0
  176. package/dist/operator-controls.d.ts.map +1 -0
  177. package/dist/operator-controls.js +71 -0
  178. package/dist/operator-controls.js.map +1 -0
  179. package/dist/pi-agent-harness.d.ts +40 -0
  180. package/dist/pi-agent-harness.d.ts.map +1 -0
  181. package/dist/pi-agent-harness.js +222 -0
  182. package/dist/pi-agent-harness.js.map +1 -0
  183. package/dist/pi-harness.d.ts +42 -0
  184. package/dist/pi-harness.d.ts.map +1 -0
  185. package/dist/pi-harness.js +142 -0
  186. package/dist/pi-harness.js.map +1 -0
  187. package/dist/relay-mcp-client.d.ts +17 -0
  188. package/dist/relay-mcp-client.d.ts.map +1 -0
  189. package/dist/relay-mcp-client.js +119 -0
  190. package/dist/relay-mcp-client.js.map +1 -0
  191. package/dist/replay-resume.d.ts +107 -0
  192. package/dist/replay-resume.d.ts.map +1 -0
  193. package/dist/replay-resume.js +495 -0
  194. package/dist/replay-resume.js.map +1 -0
  195. package/dist/resource-projection-util.d.ts +58 -0
  196. package/dist/resource-projection-util.d.ts.map +1 -0
  197. package/dist/resource-projection-util.js +298 -0
  198. package/dist/resource-projection-util.js.map +1 -0
  199. package/dist/resource-projection.d.ts +45 -0
  200. package/dist/resource-projection.d.ts.map +1 -0
  201. package/dist/resource-projection.js +481 -0
  202. package/dist/resource-projection.js.map +1 -0
  203. package/dist/run-heartbeat.d.ts +34 -0
  204. package/dist/run-heartbeat.d.ts.map +1 -0
  205. package/dist/run-heartbeat.js +62 -0
  206. package/dist/run-heartbeat.js.map +1 -0
  207. package/dist/run-outcome.d.ts +47 -0
  208. package/dist/run-outcome.d.ts.map +1 -0
  209. package/dist/run-outcome.js +92 -0
  210. package/dist/run-outcome.js.map +1 -0
  211. package/dist/run-recovery.d.ts +117 -0
  212. package/dist/run-recovery.d.ts.map +1 -0
  213. package/dist/run-recovery.js +466 -0
  214. package/dist/run-recovery.js.map +1 -0
  215. package/dist/run-resolver.d.ts +35 -0
  216. package/dist/run-resolver.d.ts.map +1 -0
  217. package/dist/run-resolver.js +84 -0
  218. package/dist/run-resolver.js.map +1 -0
  219. package/dist/run-stream.d.ts +37 -0
  220. package/dist/run-stream.d.ts.map +1 -0
  221. package/dist/run-stream.js +78 -0
  222. package/dist/run-stream.js.map +1 -0
  223. package/dist/runs-query.d.ts +29 -0
  224. package/dist/runs-query.d.ts.map +1 -0
  225. package/dist/runs-query.js +142 -0
  226. package/dist/runs-query.js.map +1 -0
  227. package/dist/runtime-bundle.d.ts +3 -0
  228. package/dist/runtime-bundle.d.ts.map +1 -0
  229. package/dist/runtime-bundle.js +18 -0
  230. package/dist/runtime-bundle.js.map +1 -0
  231. package/dist/runtime-types.d.ts +397 -0
  232. package/dist/runtime-types.d.ts.map +1 -0
  233. package/dist/runtime-types.js +2 -0
  234. package/dist/runtime-types.js.map +1 -0
  235. package/dist/runtime-util.d.ts +45 -0
  236. package/dist/runtime-util.d.ts.map +1 -0
  237. package/dist/runtime-util.js +116 -0
  238. package/dist/runtime-util.js.map +1 -0
  239. package/dist/sandbox-cli-client.d.ts +20 -0
  240. package/dist/sandbox-cli-client.d.ts.map +1 -0
  241. package/dist/sandbox-cli-client.js +97 -0
  242. package/dist/sandbox-cli-client.js.map +1 -0
  243. package/dist/sandbox-fs.d.ts +56 -0
  244. package/dist/sandbox-fs.d.ts.map +1 -0
  245. package/dist/sandbox-fs.js +276 -0
  246. package/dist/sandbox-fs.js.map +1 -0
  247. package/dist/sandbox-session-key.d.ts +8 -0
  248. package/dist/sandbox-session-key.d.ts.map +1 -0
  249. package/dist/sandbox-session-key.js +32 -0
  250. package/dist/sandbox-session-key.js.map +1 -0
  251. package/dist/sandbox-snapshot-policy.d.ts +27 -0
  252. package/dist/sandbox-snapshot-policy.d.ts.map +1 -0
  253. package/dist/sandbox-snapshot-policy.js +94 -0
  254. package/dist/sandbox-snapshot-policy.js.map +1 -0
  255. package/dist/sandbox-sync-worker.d.ts +63 -0
  256. package/dist/sandbox-sync-worker.d.ts.map +1 -0
  257. package/dist/sandbox-sync-worker.js +448 -0
  258. package/dist/sandbox-sync-worker.js.map +1 -0
  259. package/dist/schedule-execution.d.ts +59 -0
  260. package/dist/schedule-execution.d.ts.map +1 -0
  261. package/dist/schedule-execution.js +951 -0
  262. package/dist/schedule-execution.js.map +1 -0
  263. package/dist/scheduler.d.ts +19 -0
  264. package/dist/scheduler.d.ts.map +1 -0
  265. package/dist/scheduler.js +38 -0
  266. package/dist/scheduler.js.map +1 -0
  267. package/dist/security.d.ts +20 -0
  268. package/dist/security.d.ts.map +1 -0
  269. package/dist/security.js +145 -0
  270. package/dist/security.js.map +1 -0
  271. package/dist/self-improvement.d.ts +245 -0
  272. package/dist/self-improvement.d.ts.map +1 -0
  273. package/dist/self-improvement.js +554 -0
  274. package/dist/self-improvement.js.map +1 -0
  275. package/dist/source-loader.d.ts +19 -0
  276. package/dist/source-loader.d.ts.map +1 -0
  277. package/dist/source-loader.js +160 -0
  278. package/dist/source-loader.js.map +1 -0
  279. package/dist/state-store-helpers.d.ts +28 -0
  280. package/dist/state-store-helpers.d.ts.map +1 -0
  281. package/dist/state-store-helpers.js +164 -0
  282. package/dist/state-store-helpers.js.map +1 -0
  283. package/dist/state-stores.d.ts +25 -0
  284. package/dist/state-stores.d.ts.map +1 -0
  285. package/dist/state-stores.js +157 -0
  286. package/dist/state-stores.js.map +1 -0
  287. package/dist/state.d.ts +736 -0
  288. package/dist/state.d.ts.map +1 -0
  289. package/dist/state.js +46 -0
  290. package/dist/state.js.map +1 -0
  291. package/dist/storage.d.ts +188 -0
  292. package/dist/storage.d.ts.map +1 -0
  293. package/dist/storage.js +752 -0
  294. package/dist/storage.js.map +1 -0
  295. package/dist/subagent-execution.d.ts +80 -0
  296. package/dist/subagent-execution.d.ts.map +1 -0
  297. package/dist/subagent-execution.js +135 -0
  298. package/dist/subagent-execution.js.map +1 -0
  299. package/dist/telemetry.d.ts +49 -0
  300. package/dist/telemetry.d.ts.map +1 -0
  301. package/dist/telemetry.js +60 -0
  302. package/dist/telemetry.js.map +1 -0
  303. package/dist/tool-apis.d.ts +22 -0
  304. package/dist/tool-apis.d.ts.map +1 -0
  305. package/dist/tool-apis.js +344 -0
  306. package/dist/tool-apis.js.map +1 -0
  307. package/dist/tool-execution.d.ts +143 -0
  308. package/dist/tool-execution.d.ts.map +1 -0
  309. package/dist/tool-execution.js +573 -0
  310. package/dist/tool-execution.js.map +1 -0
  311. package/dist/trusted-attachments.d.ts +20 -0
  312. package/dist/trusted-attachments.d.ts.map +1 -0
  313. package/dist/trusted-attachments.js +276 -0
  314. package/dist/trusted-attachments.js.map +1 -0
  315. package/dist/usage-accounting.d.ts +37 -0
  316. package/dist/usage-accounting.d.ts.map +1 -0
  317. package/dist/usage-accounting.js +181 -0
  318. package/dist/usage-accounting.js.map +1 -0
  319. package/dist/usage-budget.d.ts +29 -0
  320. package/dist/usage-budget.d.ts.map +1 -0
  321. package/dist/usage-budget.js +145 -0
  322. package/dist/usage-budget.js.map +1 -0
  323. package/dist/usage-reconciliation.d.ts +76 -0
  324. package/dist/usage-reconciliation.d.ts.map +1 -0
  325. package/dist/usage-reconciliation.js +463 -0
  326. package/dist/usage-reconciliation.js.map +1 -0
  327. package/dist/usage-records.d.ts +24 -0
  328. package/dist/usage-records.d.ts.map +1 -0
  329. package/dist/usage-records.js +445 -0
  330. package/dist/usage-records.js.map +1 -0
  331. package/package.json +51 -0
@@ -0,0 +1,832 @@
1
+ import { createHash } from "node:crypto";
2
+ import { inflateRawSync } from "node:zlib";
3
+ import { trustedProviderDownloadRequest } from "./trusted-attachments.js";
4
+ const DEFAULT_ATTACHMENT_MAX_COUNT = 20;
5
+ const DEFAULT_ATTACHMENT_MAX_BYTES = 50 * 1024 * 1024;
6
+ const DEFAULT_ATTACHMENT_FETCH_TIMEOUT_MS = 30_000;
7
+ const DEFAULT_ZIP_MAX_FILES = 500;
8
+ const DEFAULT_ZIP_MAX_TOTAL_BYTES = 100 * 1024 * 1024;
9
+ export async function materializeTurnAttachments(input) {
10
+ const maxCount = boundedPositiveIntEnv(input.env.OPENEVE_ATTACHMENT_MAX_COUNT, DEFAULT_ATTACHMENT_MAX_COUNT, 1, 100);
11
+ const limited = input.attachments.slice(0, maxCount);
12
+ const output = [];
13
+ const usedPaths = new Set();
14
+ if (input.attachments.length > limited.length) {
15
+ await input.state.appendEvent(input.run.id, "attachment.intake_truncated", {
16
+ received: input.attachments.length,
17
+ stored: limited.length,
18
+ maxCount
19
+ });
20
+ }
21
+ for (const [index, attachment] of limited.entries()) {
22
+ const path = uniqueAttachmentPath(attachment, index, usedPaths);
23
+ const base = sanitizeAttachmentForContext({
24
+ ...attachment,
25
+ path,
26
+ status: stringValue(attachment.status) || "pending"
27
+ });
28
+ const inline = inlineAttachmentContents(attachment);
29
+ if (inline !== undefined) {
30
+ try {
31
+ const maxBytes = boundedPositiveIntEnv(input.env.OPENEVE_ATTACHMENT_MAX_BYTES, DEFAULT_ATTACHMENT_MAX_BYTES, 1, 1024 * 1024 * 1024);
32
+ const contentType = attachmentContentType(attachment);
33
+ const bytes = typeof inline === "string" ? Buffer.from(inline) : Buffer.from(inline);
34
+ if (bytes.byteLength > maxBytes)
35
+ throw new Error(`Attachment exceeds max bytes (${maxBytes}).`);
36
+ const filename = attachmentFilename(attachment, index);
37
+ const blob = await input.blob.put(`attachments/${input.run.id}/${path.slice("/files/original/".length)}`, bytes, contentType);
38
+ const extraction = await maybeExtractZipAttachment({
39
+ ...input,
40
+ attachment,
41
+ archivePath: path,
42
+ archiveFilename: filename,
43
+ bytes,
44
+ contentType
45
+ });
46
+ await input.state.recordFileIndex({
47
+ runId: input.run.id,
48
+ path,
49
+ kind: "file",
50
+ blobKey: blob.key,
51
+ sha256: blob.sha256,
52
+ sizeBytes: blob.size,
53
+ metadata: {
54
+ channel: input.channel,
55
+ source: "turn-attachment",
56
+ provider: stringValue(attachment.provider) || null,
57
+ filename,
58
+ contentType
59
+ }
60
+ });
61
+ await input.state.appendEvent(input.run.id, "attachment.stored", {
62
+ path,
63
+ channel: input.channel,
64
+ provider: stringValue(attachment.provider) || null,
65
+ sizeBytes: blob.size,
66
+ contentType,
67
+ inline: true
68
+ });
69
+ output.push({
70
+ ...sanitizeAttachmentForContext({
71
+ ...base,
72
+ id: stringValue(attachment.id) || `attachment-${index + 1}`,
73
+ channel: input.channel,
74
+ path,
75
+ filename,
76
+ name: filename,
77
+ status: extraction.extractedFiles.length > 0 ? "extracted" : "stored",
78
+ contentType,
79
+ mimeType: contentType,
80
+ size: blob.size,
81
+ sizeBytes: blob.size,
82
+ sha256: blob.sha256
83
+ }),
84
+ blobKey: blob.key,
85
+ ...blobUriForContext(blob),
86
+ ...(extraction.extractedPath ? { extractedPath: extraction.extractedPath } : {}),
87
+ ...(extraction.extractedFiles.length > 0 ? { extractedFiles: extraction.extractedFiles } : {}),
88
+ ...(extraction.errorMessage ? { extractionError: extraction.errorMessage } : {})
89
+ });
90
+ }
91
+ catch (error) {
92
+ const message = error instanceof Error ? error.message : String(error);
93
+ await input.state.appendEvent(input.run.id, "attachment.store_failed", {
94
+ path,
95
+ channel: input.channel,
96
+ provider: stringValue(attachment.provider) || null,
97
+ error: message
98
+ });
99
+ output.push(sanitizeAttachmentForContext({
100
+ ...base,
101
+ status: "failed",
102
+ errorMessage: message
103
+ }));
104
+ }
105
+ continue;
106
+ }
107
+ try {
108
+ const download = await attachmentDownloadRequest(attachment, input);
109
+ if (!download) {
110
+ output.push(sanitizeAttachmentForContext({
111
+ ...base,
112
+ status: "metadata_only"
113
+ }));
114
+ continue;
115
+ }
116
+ const downloaded = await fetchAttachmentBytes(input.fetch, download, input.env);
117
+ const bytes = downloaded.bytes;
118
+ const hintedContentType = attachmentContentType(attachment);
119
+ const contentType = responseContentType(downloaded.contentType, hintedContentType);
120
+ const filename = attachmentFilename(attachment, index);
121
+ const blob = await input.blob.put(`attachments/${input.run.id}/${path.slice("/files/original/".length)}`, bytes, contentType);
122
+ const extraction = await maybeExtractZipAttachment({
123
+ ...input,
124
+ attachment,
125
+ archivePath: path,
126
+ archiveFilename: filename,
127
+ bytes,
128
+ contentType
129
+ });
130
+ const stored = {
131
+ ...sanitizeAttachmentForContext({
132
+ ...base,
133
+ id: stringValue(attachment.id) || `attachment-${index + 1}`,
134
+ channel: input.channel,
135
+ status: extraction.extractedFiles.length > 0 ? "extracted" : "stored",
136
+ path,
137
+ filename,
138
+ name: filename,
139
+ contentType,
140
+ mimeType: contentType,
141
+ size: blob.size,
142
+ sizeBytes: blob.size,
143
+ sha256: blob.sha256
144
+ }),
145
+ blobKey: blob.key,
146
+ ...blobUriForContext(blob),
147
+ ...(extraction.extractedPath ? { extractedPath: extraction.extractedPath } : {}),
148
+ ...(extraction.extractedFiles.length > 0 ? { extractedFiles: extraction.extractedFiles } : {}),
149
+ ...(extraction.errorMessage ? { extractionError: extraction.errorMessage } : {})
150
+ };
151
+ await input.state.recordFileIndex({
152
+ runId: input.run.id,
153
+ path,
154
+ kind: "file",
155
+ blobKey: blob.key,
156
+ sha256: blob.sha256,
157
+ sizeBytes: blob.size,
158
+ metadata: {
159
+ channel: input.channel,
160
+ source: "turn-attachment",
161
+ provider: stringValue(attachment.provider) || null,
162
+ filename,
163
+ contentType
164
+ }
165
+ });
166
+ await input.state.appendEvent(input.run.id, "attachment.stored", {
167
+ path,
168
+ channel: input.channel,
169
+ provider: stringValue(attachment.provider) || null,
170
+ sizeBytes: blob.size,
171
+ contentType
172
+ });
173
+ output.push(stored);
174
+ }
175
+ catch (error) {
176
+ const message = error instanceof Error ? error.message : String(error);
177
+ await input.state.appendEvent(input.run.id, "attachment.store_failed", {
178
+ path,
179
+ channel: input.channel,
180
+ provider: stringValue(attachment.provider) || null,
181
+ error: message
182
+ });
183
+ output.push(sanitizeAttachmentForContext({
184
+ ...base,
185
+ status: "failed",
186
+ errorMessage: message
187
+ }));
188
+ }
189
+ }
190
+ return output;
191
+ }
192
+ export function fileManifestEntries(attachments) {
193
+ return attachments.flatMap((attachment, index) => {
194
+ const path = attachmentResourcePath(attachment);
195
+ const displayName = stringValue(attachment.displayName) ||
196
+ stringValue(attachment.filename) ||
197
+ stringValue(attachment.name) ||
198
+ path.split("/").at(-1) ||
199
+ `attachment-${index + 1}`;
200
+ const contentType = attachmentContentType(attachment);
201
+ const size = numberValue(attachment.sizeBytes) ?? numberValue(attachment.size) ?? inlineAttachmentSize(attachment);
202
+ const entry = {
203
+ fileId: stringValue(attachment.id) || `attachment-${index + 1}`,
204
+ displayName,
205
+ mimeType: contentType,
206
+ contentType,
207
+ source: stringValue(attachment.provider) || stringValue(attachment.source) || "turn-attachment",
208
+ status: stringValue(attachment.status) || (hasAttachmentContents(attachment) ? "stored" : "metadata_only"),
209
+ originalPath: path,
210
+ path
211
+ };
212
+ if (size !== undefined) {
213
+ entry.sizeBytes = size;
214
+ entry.size = size;
215
+ }
216
+ const revision = stringValue(attachment.sha256);
217
+ if (revision)
218
+ entry.sha256 = revision;
219
+ const extractedPath = stringValue(attachment.extractedPath);
220
+ if (extractedPath)
221
+ entry.extractedPath = extractedPath;
222
+ const errorMessage = stringValue(attachment.errorMessage);
223
+ if (errorMessage)
224
+ entry.errorMessage = errorMessage;
225
+ const extracted = Array.isArray(attachment.extractedFiles)
226
+ ? attachment.extractedFiles.flatMap((value) => {
227
+ const record = recordValue(value);
228
+ return record ? [record] : [];
229
+ })
230
+ : [];
231
+ return [entry, ...extracted];
232
+ });
233
+ }
234
+ export async function attachmentContentsFromRecord(attachment, blob) {
235
+ const inline = inlineAttachmentContents(attachment);
236
+ if (inline !== undefined)
237
+ return inline;
238
+ const blobKey = stringValue(attachment.blobKey);
239
+ if (!blobKey || !blob)
240
+ return undefined;
241
+ const bytes = await blob.get(blobKey);
242
+ if (!bytes)
243
+ return undefined;
244
+ return isTextualContentType(attachmentContentType(attachment)) ? Buffer.from(bytes).toString("utf8") : bytes;
245
+ }
246
+ export function attachmentResourcePath(attachment) {
247
+ const explicitPath = stringValue(attachment.path);
248
+ if (explicitPath) {
249
+ const normalized = normalizeResourcePath(explicitPath);
250
+ return normalized.startsWith("/files") ? normalized : `/files/original/${sanitizeResourcePath(normalized)}`;
251
+ }
252
+ const name = stringValue(attachment.filename) || stringValue(attachment.name) || stringValue(attachment.id);
253
+ if (name)
254
+ return `/files/original/${sanitizeResourcePath(name)}`;
255
+ return `/files/original/attachment-${hashJson(attachment).slice(0, 12)}.txt`;
256
+ }
257
+ export function attachmentContentType(attachment) {
258
+ return stringValue(attachment.contentType) ||
259
+ stringValue(attachment.mimeType) ||
260
+ stringValue(attachment.mimetype) ||
261
+ "application/octet-stream";
262
+ }
263
+ function uniqueAttachmentPath(attachment, index, usedPaths) {
264
+ const explicit = stringValue(attachment.path);
265
+ const basePath = explicit
266
+ ? attachmentResourcePath({ path: explicit })
267
+ : `/files/original/${sanitizeResourcePath(attachmentFilename(attachment, index))}`;
268
+ const normalized = normalizeResourcePath(basePath);
269
+ if (!usedPaths.has(normalized)) {
270
+ usedPaths.add(normalized);
271
+ return normalized;
272
+ }
273
+ const dot = normalized.lastIndexOf(".");
274
+ const suffix = `-${index + 1}`;
275
+ const candidate = dot > "/files/original/".length
276
+ ? `${normalized.slice(0, dot)}${suffix}${normalized.slice(dot)}`
277
+ : `${normalized}${suffix}`;
278
+ usedPaths.add(candidate);
279
+ return candidate;
280
+ }
281
+ function attachmentFilename(attachment, index) {
282
+ const name = stringValue(attachment.filename) ||
283
+ stringValue(attachment.name) ||
284
+ stringValue(attachment.displayName) ||
285
+ stringValue(attachment.title) ||
286
+ stringValue(attachment.id);
287
+ if (name)
288
+ return name;
289
+ const kind = stringValue(attachment.kind) || "file";
290
+ return `attachment-${index + 1}${extensionForContentType(attachmentContentType(attachment), kind)}`;
291
+ }
292
+ function extensionForContentType(contentType, kind) {
293
+ if (contentType === "text/markdown")
294
+ return ".md";
295
+ if (contentType === "text/plain")
296
+ return ".txt";
297
+ if (contentType === "application/json")
298
+ return ".json";
299
+ if (contentType === "application/pdf")
300
+ return ".pdf";
301
+ if (contentType === "application/zip")
302
+ return ".zip";
303
+ if (contentType === "image/png")
304
+ return ".png";
305
+ if (contentType === "image/jpeg")
306
+ return ".jpg";
307
+ if (contentType === "image/heic" || contentType === "image/heif")
308
+ return ".heic";
309
+ if (kind === "image")
310
+ return ".img";
311
+ if (kind === "audio")
312
+ return ".audio";
313
+ if (kind === "video")
314
+ return ".video";
315
+ return "";
316
+ }
317
+ function responseContentType(responseType, hintedType) {
318
+ const contentType = responseType?.split(";")[0]?.trim();
319
+ return contentType || hintedType || "application/octet-stream";
320
+ }
321
+ async function maybeExtractZipAttachment(input) {
322
+ if (!isZipAttachment(input.contentType, input.archiveFilename, input.bytes))
323
+ return { extractedFiles: [] };
324
+ const zipRoot = `/files/extracted/${zipRootName(input.archiveFilename)}`;
325
+ try {
326
+ const entries = extractZipEntries(input.bytes, input.env);
327
+ const extractedFiles = [];
328
+ let skipped = 0;
329
+ for (const entry of entries) {
330
+ if (entry.skipped) {
331
+ skipped += 1;
332
+ continue;
333
+ }
334
+ const contentType = contentTypeFromFilename(entry.path);
335
+ const path = `${zipRoot}/${entry.path}`;
336
+ const blob = await input.blob.put(`attachments/${input.run.id}/extracted/${zipRoot.slice("/files/extracted/".length)}/${entry.path}`, entry.bytes, contentType);
337
+ await input.state.recordFileIndex({
338
+ runId: input.run.id,
339
+ path,
340
+ kind: "file",
341
+ blobKey: blob.key,
342
+ sha256: blob.sha256,
343
+ sizeBytes: blob.size,
344
+ metadata: {
345
+ channel: input.channel,
346
+ source: "zip-extraction",
347
+ provider: stringValue(input.attachment.provider) || null,
348
+ archivePath: input.archivePath,
349
+ archiveFilename: input.archiveFilename,
350
+ contentType
351
+ }
352
+ });
353
+ extractedFiles.push({
354
+ path,
355
+ displayName: entry.path,
356
+ contentType,
357
+ sizeBytes: blob.size,
358
+ sha256: blob.sha256,
359
+ status: "extracted"
360
+ });
361
+ }
362
+ await input.state.appendEvent(input.run.id, "attachment.zip_extracted", {
363
+ archivePath: input.archivePath,
364
+ extractedPath: zipRoot,
365
+ extractedFiles: extractedFiles.length,
366
+ skippedEntries: skipped,
367
+ sizeBytes: input.bytes.byteLength
368
+ });
369
+ return { extractedPath: zipRoot, extractedFiles };
370
+ }
371
+ catch (error) {
372
+ const message = error instanceof Error ? error.message : String(error);
373
+ await input.state.appendEvent(input.run.id, "attachment.zip_extract_failed", {
374
+ archivePath: input.archivePath,
375
+ error: message
376
+ });
377
+ return { extractedPath: zipRoot, extractedFiles: [], errorMessage: message };
378
+ }
379
+ }
380
+ function isZipAttachment(contentType, filename, bytes) {
381
+ const normalized = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
382
+ return normalized === "application/zip" ||
383
+ normalized === "application/x-zip-compressed" ||
384
+ filename.toLowerCase().endsWith(".zip") ||
385
+ (bytes.byteLength >= 4 && bytes[0] === 0x50 && bytes[1] === 0x4b && bytes[2] === 0x03 && bytes[3] === 0x04);
386
+ }
387
+ function extractZipEntries(bytes, env) {
388
+ const buffer = Buffer.from(bytes);
389
+ const eocdOffset = findEndOfCentralDirectory(buffer);
390
+ if (eocdOffset < 0)
391
+ throw new Error("ZIP central directory was not found.");
392
+ const entryCount = buffer.readUInt16LE(eocdOffset + 10);
393
+ const centralDirectorySize = buffer.readUInt32LE(eocdOffset + 12);
394
+ const centralDirectoryOffset = buffer.readUInt32LE(eocdOffset + 16);
395
+ if (entryCount === 0xffff || centralDirectorySize === 0xffffffff || centralDirectoryOffset === 0xffffffff) {
396
+ throw new Error("ZIP64 archives are not supported by built-in intake extraction.");
397
+ }
398
+ const maxFiles = boundedPositiveIntEnv(env.OPENEVE_ZIP_MAX_FILES, DEFAULT_ZIP_MAX_FILES, 1, 10_000);
399
+ const maxTotalBytes = boundedPositiveIntEnv(env.OPENEVE_ZIP_MAX_TOTAL_BYTES, DEFAULT_ZIP_MAX_TOTAL_BYTES, 1, 2 * 1024 * 1024 * 1024);
400
+ const entries = [];
401
+ let offset = centralDirectoryOffset;
402
+ let extractedFiles = 0;
403
+ let totalBytes = 0;
404
+ for (let index = 0; index < entryCount; index += 1) {
405
+ if (offset + 46 > buffer.byteLength || buffer.readUInt32LE(offset) !== 0x02014b50) {
406
+ throw new Error("ZIP central directory is malformed.");
407
+ }
408
+ const flags = buffer.readUInt16LE(offset + 8);
409
+ const method = buffer.readUInt16LE(offset + 10);
410
+ const compressedSize = buffer.readUInt32LE(offset + 20);
411
+ const uncompressedSize = buffer.readUInt32LE(offset + 24);
412
+ const nameLength = buffer.readUInt16LE(offset + 28);
413
+ const extraLength = buffer.readUInt16LE(offset + 30);
414
+ const commentLength = buffer.readUInt16LE(offset + 32);
415
+ const localHeaderOffset = buffer.readUInt32LE(offset + 42);
416
+ const rawName = buffer.subarray(offset + 46, offset + 46 + nameLength).toString((flags & 0x0800) !== 0 ? "utf8" : "utf8");
417
+ offset += 46 + nameLength + extraLength + commentLength;
418
+ const safePath = safeZipEntryPath(rawName);
419
+ if (!safePath || rawName.endsWith("/") || (flags & 0x0001) !== 0 || (method !== 0 && method !== 8)) {
420
+ entries.push({ skipped: true });
421
+ continue;
422
+ }
423
+ if (compressedSize === 0xffffffff || uncompressedSize === 0xffffffff || localHeaderOffset === 0xffffffff) {
424
+ entries.push({ skipped: true });
425
+ continue;
426
+ }
427
+ extractedFiles += 1;
428
+ if (extractedFiles > maxFiles)
429
+ throw new Error(`ZIP extraction exceeds max files (${maxFiles}).`);
430
+ totalBytes += uncompressedSize;
431
+ if (totalBytes > maxTotalBytes)
432
+ throw new Error(`ZIP extraction exceeds max bytes (${maxTotalBytes}).`);
433
+ const entryBytes = readZipEntry(buffer, localHeaderOffset, compressedSize, uncompressedSize, method);
434
+ entries.push({ path: safePath, bytes: entryBytes });
435
+ }
436
+ return entries;
437
+ }
438
+ function findEndOfCentralDirectory(buffer) {
439
+ const min = Math.max(0, buffer.byteLength - 65_557);
440
+ for (let offset = buffer.byteLength - 22; offset >= min; offset -= 1) {
441
+ if (buffer.readUInt32LE(offset) === 0x06054b50)
442
+ return offset;
443
+ }
444
+ return -1;
445
+ }
446
+ function readZipEntry(buffer, localHeaderOffset, compressedSize, uncompressedSize, method) {
447
+ if (localHeaderOffset + 30 > buffer.byteLength || buffer.readUInt32LE(localHeaderOffset) !== 0x04034b50) {
448
+ throw new Error("ZIP local file header is malformed.");
449
+ }
450
+ const localNameLength = buffer.readUInt16LE(localHeaderOffset + 26);
451
+ const localExtraLength = buffer.readUInt16LE(localHeaderOffset + 28);
452
+ const dataStart = localHeaderOffset + 30 + localNameLength + localExtraLength;
453
+ const dataEnd = dataStart + compressedSize;
454
+ if (dataEnd > buffer.byteLength)
455
+ throw new Error("ZIP entry data exceeds archive size.");
456
+ const compressed = buffer.subarray(dataStart, dataEnd);
457
+ const output = method === 0 ? Buffer.from(compressed) : inflateRawSync(compressed);
458
+ if (output.byteLength !== uncompressedSize) {
459
+ throw new Error("ZIP entry expanded to an unexpected size.");
460
+ }
461
+ return output;
462
+ }
463
+ function safeZipEntryPath(name) {
464
+ const normalized = name.replace(/\\/g, "/").replace(/\0/g, "");
465
+ if (!normalized || /^[A-Za-z]:/u.test(normalized))
466
+ return undefined;
467
+ const parts = normalized
468
+ .split("/")
469
+ .filter((part) => part && part !== ".")
470
+ .map((part) => part.trim());
471
+ if (parts.length === 0 || parts.some((part) => part === ".."))
472
+ return undefined;
473
+ return parts.map((part) => part.replace(/[^A-Za-z0-9._ -]/gu, "_")).join("/");
474
+ }
475
+ function zipRootName(filename) {
476
+ const clean = filename.replace(/\.zip$/iu, "") || "archive";
477
+ return sanitizeResourcePath(clean);
478
+ }
479
+ function contentTypeFromFilename(filename) {
480
+ const lower = filename.toLowerCase();
481
+ if (lower.endsWith(".md") || lower.endsWith(".markdown"))
482
+ return "text/markdown";
483
+ if (lower.endsWith(".txt") || lower.endsWith(".log"))
484
+ return "text/plain";
485
+ if (lower.endsWith(".json"))
486
+ return "application/json";
487
+ if (lower.endsWith(".csv"))
488
+ return "text/csv";
489
+ if (lower.endsWith(".tsv"))
490
+ return "text/tab-separated-values";
491
+ if (lower.endsWith(".xml"))
492
+ return "application/xml";
493
+ if (lower.endsWith(".html") || lower.endsWith(".htm"))
494
+ return "text/html";
495
+ if (lower.endsWith(".css"))
496
+ return "text/css";
497
+ if (lower.endsWith(".js") || lower.endsWith(".mjs") || lower.endsWith(".cjs"))
498
+ return "application/javascript";
499
+ if (lower.endsWith(".ts") || lower.endsWith(".tsx"))
500
+ return "application/typescript";
501
+ if (lower.endsWith(".zip"))
502
+ return "application/zip";
503
+ if (lower.endsWith(".png"))
504
+ return "image/png";
505
+ if (lower.endsWith(".jpg") || lower.endsWith(".jpeg"))
506
+ return "image/jpeg";
507
+ if (lower.endsWith(".gif"))
508
+ return "image/gif";
509
+ if (lower.endsWith(".webp"))
510
+ return "image/webp";
511
+ if (lower.endsWith(".pdf"))
512
+ return "application/pdf";
513
+ return "application/octet-stream";
514
+ }
515
+ function inlineAttachmentContents(attachment) {
516
+ const encoding = stringValue(attachment.encoding).toLowerCase();
517
+ for (const key of ["content", "text", "body", "data"]) {
518
+ const value = attachment[key];
519
+ if (typeof value !== "string")
520
+ continue;
521
+ if (encoding === "base64")
522
+ return Buffer.from(value, "base64");
523
+ return value;
524
+ }
525
+ const dataBase64 = stringValue(attachment.dataBase64) || stringValue(attachment.base64);
526
+ if (dataBase64)
527
+ return Buffer.from(dataBase64, "base64");
528
+ return undefined;
529
+ }
530
+ function inlineAttachmentSize(attachment) {
531
+ const contents = inlineAttachmentContents(attachment);
532
+ if (contents === undefined)
533
+ return undefined;
534
+ return typeof contents === "string" ? Buffer.byteLength(contents) : contents.byteLength;
535
+ }
536
+ function hasAttachmentContents(attachment) {
537
+ return inlineAttachmentContents(attachment) !== undefined || typeof attachment.blobKey === "string";
538
+ }
539
+ export function isTextualContentType(contentType) {
540
+ const normalized = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
541
+ return normalized.startsWith("text/") ||
542
+ normalized === "application/json" ||
543
+ normalized === "application/xml" ||
544
+ normalized === "application/javascript" ||
545
+ normalized === "application/typescript" ||
546
+ normalized.endsWith("+json") ||
547
+ normalized.endsWith("+xml");
548
+ }
549
+ async function attachmentDownloadRequest(attachment, input) {
550
+ if (input.resolveAttachment) {
551
+ // The turn's own channel module resolves provider-specific downloads
552
+ // (auth lookups, host allowlists, credential headers).
553
+ const resolved = await input.resolveAttachment(attachment);
554
+ if (resolved) {
555
+ const request = { url: resolved.url, headers: resolved.headers };
556
+ if (input.trustedProvider)
557
+ request.provider = input.trustedProvider;
558
+ const filename = resolved.filename || stringValue(attachment.filename) || stringValue(attachment.name);
559
+ if (filename)
560
+ request.filename = filename;
561
+ return request;
562
+ }
563
+ // The channel declined the attachment: fall back to a generic,
564
+ // unauthenticated URL download so plain public links keep working.
565
+ const url = attachmentRemoteUrl(attachment);
566
+ if (!url)
567
+ return undefined;
568
+ const request = { url, headers: {} };
569
+ const provider = stringValue(attachment.provider);
570
+ if (provider)
571
+ request.provider = provider;
572
+ const filename = stringValue(attachment.filename) || stringValue(attachment.name);
573
+ if (filename)
574
+ request.filename = filename;
575
+ return request;
576
+ }
577
+ // Direct `run({ trustedAttachmentProvider })` callers get the in-runtime
578
+ // provider resolvers (files.info lookups, host allowlists, credential
579
+ // headers). Anything else falls back to a generic, unauthenticated
580
+ // public-URL download — provider credentials always require either a
581
+ // channel resolver or an explicit trusted provider.
582
+ if (input.trustedProvider) {
583
+ return trustedProviderDownloadRequest(attachment, input);
584
+ }
585
+ const url = attachmentRemoteUrl(attachment);
586
+ if (!url)
587
+ return undefined;
588
+ const request = { url, headers: {} };
589
+ const provider = stringValue(attachment.provider);
590
+ if (provider)
591
+ request.provider = provider;
592
+ const filename = stringValue(attachment.filename) || stringValue(attachment.name);
593
+ if (filename)
594
+ request.filename = filename;
595
+ return request;
596
+ }
597
+ /**
598
+ * Extract the first downloadable http(s) URL a channel attachment advertises.
599
+ * Exported for channel modules implementing `resolveAttachment`.
600
+ */
601
+ export function attachmentRemoteUrl(attachment) {
602
+ return attachmentDownloadUrl(attachment);
603
+ }
604
+ /**
605
+ * True when the attachment's own trust markers (`remote.trusted` plus a
606
+ * matching provider/auth hint) claim the given provider. Channel modules use
607
+ * this in `resolveAttachment` before attaching credentials, so forged
608
+ * attachments never receive another provider's tokens.
609
+ */
610
+ export function attachmentTrustedForProvider(attachment, provider) {
611
+ return trustedProviderForAttachment(attachment, provider) !== undefined;
612
+ }
613
+ function attachmentDownloadUrl(attachment) {
614
+ const remote = recordValue(attachment.remote) ?? recordValue(attachment.download);
615
+ const candidates = [
616
+ remote?.downloadUrl,
617
+ remote?.contentUrl,
618
+ attachment.downloadUrl,
619
+ attachment.url_private_download,
620
+ remote?.url_private_download,
621
+ attachment.url_private,
622
+ remote?.url_private,
623
+ attachment.contentUrl,
624
+ attachment.mediaUrl,
625
+ attachment.fileUrl,
626
+ attachment.url,
627
+ remote?.url,
628
+ attachment.href
629
+ ];
630
+ return firstDownloadableUrl(candidates);
631
+ }
632
+ /** Exported for channel modules and the transitional legacy resolver module. */
633
+ export function firstDownloadableUrl(candidates) {
634
+ for (const candidate of candidates) {
635
+ const url = stringValue(candidate);
636
+ if (isDownloadableHttpUrl(url))
637
+ return url;
638
+ }
639
+ return undefined;
640
+ }
641
+ function isDownloadableHttpUrl(value) {
642
+ if (!value)
643
+ return false;
644
+ try {
645
+ const url = new URL(value);
646
+ return url.protocol === "https:" || url.protocol === "http:";
647
+ }
648
+ catch {
649
+ return false;
650
+ }
651
+ }
652
+ export function trustedProviderForAttachment(attachment, trustedProvider) {
653
+ const normalizedTrustedProvider = normalizeAttachmentProvider(trustedProvider);
654
+ if (!normalizedTrustedProvider)
655
+ return undefined;
656
+ const remote = recordValue(attachment.remote) ?? recordValue(attachment.download);
657
+ if (!remoteTrusted(remote))
658
+ return undefined;
659
+ const providers = [
660
+ attachment.provider,
661
+ attachment.remoteProvider,
662
+ remote?.provider,
663
+ remote?.auth,
664
+ remote?.authProvider,
665
+ attachment.authProvider
666
+ ].map((value) => normalizeAttachmentProvider(stringValue(value)));
667
+ return providers.includes(normalizedTrustedProvider) ? normalizedTrustedProvider : undefined;
668
+ }
669
+ function remoteTrusted(remote) {
670
+ return remote?.trusted === true || stringValue(remote?.trusted) === "true";
671
+ }
672
+ export function normalizeAttachmentProvider(value) {
673
+ if (!value)
674
+ return undefined;
675
+ const provider = value.toLowerCase();
676
+ if (provider === "photon-spectrum")
677
+ return "photon";
678
+ if (provider === "microsoft-teams")
679
+ return "teams";
680
+ if (provider === "slack" || provider === "telegram" || provider === "photon" || provider === "teams")
681
+ return provider;
682
+ return provider;
683
+ }
684
+ function blobUriForContext(blob) {
685
+ return blob.visibility === "public" ? { blobUri: blob.uri } : {};
686
+ }
687
+ async function fetchAttachmentBytes(fetchImpl, request, env) {
688
+ const maxBytes = boundedPositiveIntEnv(env.OPENEVE_ATTACHMENT_MAX_BYTES, DEFAULT_ATTACHMENT_MAX_BYTES, 1, 1024 * 1024 * 1024);
689
+ const init = { signal: timeoutSignal(attachmentFetchTimeoutMs(env)) };
690
+ if (request.headers)
691
+ init.headers = request.headers;
692
+ const response = await fetchImpl(request.url, init);
693
+ if (!response.ok)
694
+ throw new Error(`Attachment download failed: ${response.status}`);
695
+ const bytes = await readResponseBytesLimited(response, maxBytes);
696
+ const contentType = response.headers.get("content-type") ?? undefined;
697
+ if (isSlackHtmlDownloadPage(request, contentType, bytes)) {
698
+ throw new Error("Slack returned an HTML file page instead of the private file bytes. Pass Slack url_private_download or a Slack file id with SLACK_BOT_TOKEN/SLACK_USER_TOKEN.");
699
+ }
700
+ const out = { bytes };
701
+ if (contentType)
702
+ out.contentType = contentType;
703
+ return out;
704
+ }
705
+ function isSlackHtmlDownloadPage(request, contentType, bytes) {
706
+ if (request.provider !== "slack")
707
+ return false;
708
+ const normalized = contentType?.toLowerCase().split(";")[0]?.trim() ?? "";
709
+ if (normalized !== "text/html")
710
+ return false;
711
+ const filename = request.filename?.toLowerCase() ?? "";
712
+ if (!filename.endsWith(".zip"))
713
+ return false;
714
+ return Buffer.from(bytes.subarray(0, 512)).toString("utf8").toLowerCase().includes("<html");
715
+ }
716
+ async function readResponseBytesLimited(response, maxBytes) {
717
+ const reader = response.body?.getReader();
718
+ if (!reader) {
719
+ const buffer = Buffer.from(await response.arrayBuffer());
720
+ if (buffer.byteLength > maxBytes)
721
+ throw new Error(`Attachment exceeds max bytes (${maxBytes}).`);
722
+ return buffer;
723
+ }
724
+ const chunks = [];
725
+ let total = 0;
726
+ while (true) {
727
+ const { done, value } = await reader.read();
728
+ if (done)
729
+ break;
730
+ if (!value)
731
+ continue;
732
+ total += value.byteLength;
733
+ if (total > maxBytes) {
734
+ await reader.cancel().catch(() => undefined);
735
+ throw new Error(`Attachment exceeds max bytes (${maxBytes}).`);
736
+ }
737
+ chunks.push(value);
738
+ }
739
+ const bytes = new Uint8Array(total);
740
+ let offset = 0;
741
+ for (const chunk of chunks) {
742
+ bytes.set(chunk, offset);
743
+ offset += chunk.byteLength;
744
+ }
745
+ return bytes;
746
+ }
747
+ function sanitizeAttachmentForContext(attachment) {
748
+ const blocked = new Set([
749
+ "authorization",
750
+ "headers",
751
+ "token",
752
+ "remote",
753
+ "download",
754
+ "downloadUrl",
755
+ "contentUrl",
756
+ "mediaUrl",
757
+ "fileUrl",
758
+ "dataBase64",
759
+ "base64",
760
+ "url",
761
+ "url_private_download",
762
+ "url_private",
763
+ "href",
764
+ "authProvider",
765
+ "remoteProvider",
766
+ // Blob pointers must always be runtime-issued: a forged inbound blobKey
767
+ // (or a nested extractedFiles entry carrying one) would otherwise let a
768
+ // crafted payload read arbitrary blobs in the shared storage root.
769
+ // Callers re-attach the runtime's own pointers after sanitization.
770
+ "blobKey",
771
+ "blobUri",
772
+ "extractedFiles"
773
+ ]);
774
+ const out = {};
775
+ for (const [key, value] of Object.entries(attachment)) {
776
+ if (blocked.has(key))
777
+ continue;
778
+ out[key] = safeJson(value);
779
+ }
780
+ return out;
781
+ }
782
+ function timeoutSignal(timeoutMs) {
783
+ return AbortSignal.timeout(timeoutMs);
784
+ }
785
+ /** Attachment download timeout (`OPENEVE_ATTACHMENT_FETCH_TIMEOUT_MS`). Exported for channel `resolveAttachment` lookups. */
786
+ export function attachmentFetchTimeoutMs(env) {
787
+ return boundedPositiveIntEnv(env.OPENEVE_ATTACHMENT_FETCH_TIMEOUT_MS, DEFAULT_ATTACHMENT_FETCH_TIMEOUT_MS, 1_000, 120_000);
788
+ }
789
+ function boundedPositiveIntEnv(value, fallback, min, max) {
790
+ const parsed = Number.parseInt(value ?? "", 10);
791
+ if (!Number.isFinite(parsed))
792
+ return fallback;
793
+ return Math.min(max, Math.max(min, parsed));
794
+ }
795
+ function normalizeResourcePath(path) {
796
+ const normalized = path.replace(/\\/g, "/").replace(/\/+/g, "/").trim();
797
+ const absolute = normalized.startsWith("/") ? normalized : `/${normalized}`;
798
+ if (absolute.split("/").some((part) => part === "..")) {
799
+ throw new Error(`Invalid resource path: ${path}`);
800
+ }
801
+ return absolute === "/" ? "/" : absolute.replace(/\/$/u, "");
802
+ }
803
+ function sanitizeResourcePath(path) {
804
+ return path
805
+ .replace(/\\/g, "/")
806
+ .replace(/^\/+/u, "")
807
+ .split("/")
808
+ .filter((part) => part && part !== "." && part !== "..")
809
+ .map((part) => part.replace(/[^A-Za-z0-9._-]/gu, "_"))
810
+ .join("/") || "resource";
811
+ }
812
+ function stringValue(value) {
813
+ if (typeof value === "number" && Number.isFinite(value))
814
+ return String(value);
815
+ return typeof value === "string" ? value.trim() : "";
816
+ }
817
+ function numberValue(value) {
818
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
819
+ }
820
+ function recordValue(value) {
821
+ return Boolean(value && typeof value === "object" && !Array.isArray(value)) ? value : undefined;
822
+ }
823
+ function safeJson(value) {
824
+ return JSON.parse(JSON.stringify(value ?? null));
825
+ }
826
+ function hashJson(value) {
827
+ return createHash("sha256").update(JSON.stringify(value)).digest("hex");
828
+ }
829
+ function sha256(contents) {
830
+ return createHash("sha256").update(contents).digest("hex");
831
+ }
832
+ //# sourceMappingURL=attachments.js.map