@openai/agents-core 0.8.4 → 0.9.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 (462) hide show
  1. package/dist/agentToolRunConfig.js +3 -0
  2. package/dist/agentToolRunConfig.js.map +1 -1
  3. package/dist/agentToolRunConfig.mjs +3 -0
  4. package/dist/agentToolRunConfig.mjs.map +1 -1
  5. package/dist/errors.d.ts +10 -0
  6. package/dist/errors.js +15 -1
  7. package/dist/errors.js.map +1 -1
  8. package/dist/errors.mjs +13 -0
  9. package/dist/errors.mjs.map +1 -1
  10. package/dist/handoff.js +1 -1
  11. package/dist/handoff.js.map +1 -1
  12. package/dist/handoff.mjs +1 -1
  13. package/dist/handoff.mjs.map +1 -1
  14. package/dist/index.d.ts +5 -4
  15. package/dist/index.js +6 -2
  16. package/dist/index.js.map +1 -1
  17. package/dist/index.mjs +3 -2
  18. package/dist/index.mjs.map +1 -1
  19. package/dist/items.d.ts +13 -0
  20. package/dist/items.js +15 -0
  21. package/dist/items.js.map +1 -1
  22. package/dist/items.mjs +15 -0
  23. package/dist/items.mjs.map +1 -1
  24. package/dist/memory/historyMutations.d.ts +6 -0
  25. package/dist/memory/historyMutations.js +32 -0
  26. package/dist/memory/historyMutations.js.map +1 -0
  27. package/dist/memory/historyMutations.mjs +29 -0
  28. package/dist/memory/historyMutations.mjs.map +1 -0
  29. package/dist/memory/memorySession.d.ts +3 -2
  30. package/dist/memory/memorySession.js +7 -0
  31. package/dist/memory/memorySession.js.map +1 -1
  32. package/dist/memory/memorySession.mjs +7 -0
  33. package/dist/memory/memorySession.mjs.map +1 -1
  34. package/dist/memory/session.d.ts +15 -0
  35. package/dist/memory/session.js +6 -0
  36. package/dist/memory/session.js.map +1 -1
  37. package/dist/memory/session.mjs +5 -0
  38. package/dist/memory/session.mjs.map +1 -1
  39. package/dist/metadata.js +2 -2
  40. package/dist/metadata.mjs +2 -2
  41. package/dist/model.d.ts +21 -0
  42. package/dist/run.d.ts +7 -1
  43. package/dist/run.js +116 -57
  44. package/dist/run.js.map +1 -1
  45. package/dist/run.mjs +117 -58
  46. package/dist/run.mjs.map +1 -1
  47. package/dist/runState.d.ts +83 -1
  48. package/dist/runState.js +96 -11
  49. package/dist/runState.js.map +1 -1
  50. package/dist/runState.mjs +95 -11
  51. package/dist/runState.mjs.map +1 -1
  52. package/dist/runner/errorHandlers.d.ts +13 -4
  53. package/dist/runner/errorHandlers.js +22 -4
  54. package/dist/runner/errorHandlers.js.map +1 -1
  55. package/dist/runner/errorHandlers.mjs +21 -4
  56. package/dist/runner/errorHandlers.mjs.map +1 -1
  57. package/dist/runner/items.js +11 -1
  58. package/dist/runner/items.js.map +1 -1
  59. package/dist/runner/items.mjs +11 -1
  60. package/dist/runner/items.mjs.map +1 -1
  61. package/dist/runner/modelPreparation.d.ts +1 -1
  62. package/dist/runner/modelPreparation.js +7 -7
  63. package/dist/runner/modelPreparation.js.map +1 -1
  64. package/dist/runner/modelPreparation.mjs +7 -7
  65. package/dist/runner/modelPreparation.mjs.map +1 -1
  66. package/dist/runner/runLoop.d.ts +2 -1
  67. package/dist/runner/runLoop.js +2 -2
  68. package/dist/runner/runLoop.js.map +1 -1
  69. package/dist/runner/runLoop.mjs +2 -2
  70. package/dist/runner/runLoop.mjs.map +1 -1
  71. package/dist/runner/sandbox.d.ts +33 -0
  72. package/dist/runner/sandbox.js +92 -0
  73. package/dist/runner/sandbox.js.map +1 -0
  74. package/dist/runner/sandbox.mjs +83 -0
  75. package/dist/runner/sandbox.mjs.map +1 -0
  76. package/dist/runner/toolExecution.js +25 -13
  77. package/dist/runner/toolExecution.js.map +1 -1
  78. package/dist/runner/toolExecution.mjs +25 -13
  79. package/dist/runner/toolExecution.mjs.map +1 -1
  80. package/dist/runner/tracing.js +1 -0
  81. package/dist/runner/tracing.js.map +1 -1
  82. package/dist/runner/tracing.mjs +1 -0
  83. package/dist/runner/tracing.mjs.map +1 -1
  84. package/dist/runner/turnPreparation.d.ts +2 -4
  85. package/dist/runner/turnPreparation.js +7 -3
  86. package/dist/runner/turnPreparation.js.map +1 -1
  87. package/dist/runner/turnPreparation.mjs +7 -3
  88. package/dist/runner/turnPreparation.mjs.map +1 -1
  89. package/dist/runner/turnResolution.js +158 -31
  90. package/dist/runner/turnResolution.js.map +1 -1
  91. package/dist/runner/turnResolution.mjs +160 -33
  92. package/dist/runner/turnResolution.mjs.map +1 -1
  93. package/dist/runner/types.d.ts +8 -8
  94. package/dist/sandbox/agent.d.ts +24 -0
  95. package/dist/sandbox/agent.js +68 -0
  96. package/dist/sandbox/agent.js.map +1 -0
  97. package/dist/sandbox/agent.mjs +64 -0
  98. package/dist/sandbox/agent.mjs.map +1 -0
  99. package/dist/sandbox/brand.d.ts +1 -0
  100. package/dist/sandbox/brand.js +5 -0
  101. package/dist/sandbox/brand.js.map +1 -0
  102. package/dist/sandbox/brand.mjs +2 -0
  103. package/dist/sandbox/brand.mjs.map +1 -0
  104. package/dist/sandbox/capabilities/base.d.ts +25 -0
  105. package/dist/sandbox/capabilities/base.js +89 -0
  106. package/dist/sandbox/capabilities/base.js.map +1 -0
  107. package/dist/sandbox/capabilities/base.mjs +84 -0
  108. package/dist/sandbox/capabilities/base.mjs.map +1 -0
  109. package/dist/sandbox/capabilities/compaction.d.ts +33 -0
  110. package/dist/sandbox/capabilities/compaction.js +172 -0
  111. package/dist/sandbox/capabilities/compaction.js.map +1 -0
  112. package/dist/sandbox/capabilities/compaction.mjs +164 -0
  113. package/dist/sandbox/capabilities/compaction.mjs.map +1 -0
  114. package/dist/sandbox/capabilities/filesystem.d.ts +14 -0
  115. package/dist/sandbox/capabilities/filesystem.js +447 -0
  116. package/dist/sandbox/capabilities/filesystem.js.map +1 -0
  117. package/dist/sandbox/capabilities/filesystem.mjs +444 -0
  118. package/dist/sandbox/capabilities/filesystem.mjs.map +1 -0
  119. package/dist/sandbox/capabilities/index.d.ts +19 -0
  120. package/dist/sandbox/capabilities/index.js +31 -0
  121. package/dist/sandbox/capabilities/index.js.map +1 -0
  122. package/dist/sandbox/capabilities/index.mjs +17 -0
  123. package/dist/sandbox/capabilities/index.mjs.map +1 -0
  124. package/dist/sandbox/capabilities/memory.d.ts +52 -0
  125. package/dist/sandbox/capabilities/memory.js +290 -0
  126. package/dist/sandbox/capabilities/memory.js.map +1 -0
  127. package/dist/sandbox/capabilities/memory.mjs +286 -0
  128. package/dist/sandbox/capabilities/memory.mjs.map +1 -0
  129. package/dist/sandbox/capabilities/shell.d.ts +15 -0
  130. package/dist/sandbox/capabilities/shell.js +130 -0
  131. package/dist/sandbox/capabilities/shell.js.map +1 -0
  132. package/dist/sandbox/capabilities/shell.mjs +127 -0
  133. package/dist/sandbox/capabilities/shell.mjs.map +1 -0
  134. package/dist/sandbox/capabilities/skills.d.ts +47 -0
  135. package/dist/sandbox/capabilities/skills.js +453 -0
  136. package/dist/sandbox/capabilities/skills.js.map +1 -0
  137. package/dist/sandbox/capabilities/skills.mjs +449 -0
  138. package/dist/sandbox/capabilities/skills.mjs.map +1 -0
  139. package/dist/sandbox/capabilities/transport.d.ts +3 -0
  140. package/dist/sandbox/capabilities/transport.js +33 -0
  141. package/dist/sandbox/capabilities/transport.js.map +1 -0
  142. package/dist/sandbox/capabilities/transport.mjs +28 -0
  143. package/dist/sandbox/capabilities/transport.mjs.map +1 -0
  144. package/dist/sandbox/client.d.ts +53 -0
  145. package/dist/sandbox/client.js +34 -0
  146. package/dist/sandbox/client.js.map +1 -0
  147. package/dist/sandbox/client.mjs +31 -0
  148. package/dist/sandbox/client.mjs.map +1 -0
  149. package/dist/sandbox/entries/factories.d.ts +17 -0
  150. package/dist/sandbox/entries/factories.js +112 -0
  151. package/dist/sandbox/entries/factories.js.map +1 -0
  152. package/dist/sandbox/entries/factories.mjs +94 -0
  153. package/dist/sandbox/entries/factories.mjs.map +1 -0
  154. package/dist/sandbox/entries/guards.d.ts +5 -0
  155. package/dist/sandbox/entries/guards.js +19 -0
  156. package/dist/sandbox/entries/guards.js.map +1 -0
  157. package/dist/sandbox/entries/guards.mjs +13 -0
  158. package/dist/sandbox/entries/guards.mjs.map +1 -0
  159. package/dist/sandbox/entries/index.d.ts +3 -0
  160. package/dist/sandbox/entries/index.js +26 -0
  161. package/dist/sandbox/entries/index.js.map +1 -0
  162. package/dist/sandbox/entries/index.mjs +3 -0
  163. package/dist/sandbox/entries/index.mjs.map +1 -0
  164. package/dist/sandbox/entries/types.d.ts +177 -0
  165. package/dist/sandbox/entries/types.js +3 -0
  166. package/dist/sandbox/entries/types.js.map +1 -0
  167. package/dist/sandbox/entries/types.mjs +2 -0
  168. package/dist/sandbox/entries/types.mjs.map +1 -0
  169. package/dist/sandbox/errors.d.ts +151 -0
  170. package/dist/sandbox/errors.js +303 -0
  171. package/dist/sandbox/errors.js.map +1 -0
  172. package/dist/sandbox/errors.mjs +251 -0
  173. package/dist/sandbox/errors.mjs.map +1 -0
  174. package/dist/sandbox/events.d.ts +51 -0
  175. package/dist/sandbox/events.js +104 -0
  176. package/dist/sandbox/events.js.map +1 -0
  177. package/dist/sandbox/events.mjs +95 -0
  178. package/dist/sandbox/events.mjs.map +1 -0
  179. package/dist/sandbox/index.d.ts +14 -0
  180. package/dist/sandbox/index.js +31 -0
  181. package/dist/sandbox/index.js.map +1 -0
  182. package/dist/sandbox/index.mjs +15 -0
  183. package/dist/sandbox/index.mjs.map +1 -0
  184. package/dist/sandbox/internal.d.ts +7 -0
  185. package/dist/sandbox/internal.js +46 -0
  186. package/dist/sandbox/internal.js.map +1 -0
  187. package/dist/sandbox/internal.mjs +8 -0
  188. package/dist/sandbox/internal.mjs.map +1 -0
  189. package/dist/sandbox/local.d.ts +3 -0
  190. package/dist/sandbox/local.js +20 -0
  191. package/dist/sandbox/local.js.map +1 -0
  192. package/dist/sandbox/local.mjs +4 -0
  193. package/dist/sandbox/local.mjs.map +1 -0
  194. package/dist/sandbox/localSkills.d.ts +13 -0
  195. package/dist/sandbox/localSkills.js +62 -0
  196. package/dist/sandbox/localSkills.js.map +1 -0
  197. package/dist/sandbox/localSkills.mjs +59 -0
  198. package/dist/sandbox/localSkills.mjs.map +1 -0
  199. package/dist/sandbox/manifest.d.ts +86 -0
  200. package/dist/sandbox/manifest.js +553 -0
  201. package/dist/sandbox/manifest.js.map +1 -0
  202. package/dist/sandbox/manifest.mjs +545 -0
  203. package/dist/sandbox/manifest.mjs.map +1 -0
  204. package/dist/sandbox/memory/generation.d.ts +56 -0
  205. package/dist/sandbox/memory/generation.js +426 -0
  206. package/dist/sandbox/memory/generation.js.map +1 -0
  207. package/dist/sandbox/memory/generation.mjs +385 -0
  208. package/dist/sandbox/memory/generation.mjs.map +1 -0
  209. package/dist/sandbox/memory/prompts.d.ts +16 -0
  210. package/dist/sandbox/memory/prompts.js +1685 -0
  211. package/dist/sandbox/memory/prompts.js.map +1 -0
  212. package/dist/sandbox/memory/prompts.mjs +1679 -0
  213. package/dist/sandbox/memory/prompts.mjs.map +1 -0
  214. package/dist/sandbox/memory/rollouts.d.ts +33 -0
  215. package/dist/sandbox/memory/rollouts.js +228 -0
  216. package/dist/sandbox/memory/rollouts.js.map +1 -0
  217. package/dist/sandbox/memory/rollouts.mjs +221 -0
  218. package/dist/sandbox/memory/rollouts.mjs.map +1 -0
  219. package/dist/sandbox/memory/storage.d.ts +70 -0
  220. package/dist/sandbox/memory/storage.js +543 -0
  221. package/dist/sandbox/memory/storage.js.map +1 -0
  222. package/dist/sandbox/memory/storage.mjs +537 -0
  223. package/dist/sandbox/memory/storage.mjs.map +1 -0
  224. package/dist/sandbox/pathGrants.d.ts +11 -0
  225. package/dist/sandbox/pathGrants.js +28 -0
  226. package/dist/sandbox/pathGrants.js.map +1 -0
  227. package/dist/sandbox/pathGrants.mjs +25 -0
  228. package/dist/sandbox/pathGrants.mjs.map +1 -0
  229. package/dist/sandbox/permissions.d.ts +29 -0
  230. package/dist/sandbox/permissions.js +140 -0
  231. package/dist/sandbox/permissions.js.map +1 -0
  232. package/dist/sandbox/permissions.mjs +134 -0
  233. package/dist/sandbox/permissions.mjs.map +1 -0
  234. package/dist/sandbox/runtime/agentKeys.d.ts +7 -0
  235. package/dist/sandbox/runtime/agentKeys.js +76 -0
  236. package/dist/sandbox/runtime/agentKeys.js.map +1 -0
  237. package/dist/sandbox/runtime/agentKeys.mjs +69 -0
  238. package/dist/sandbox/runtime/agentKeys.mjs.map +1 -0
  239. package/dist/sandbox/runtime/agentPreparation.d.ts +20 -0
  240. package/dist/sandbox/runtime/agentPreparation.js +178 -0
  241. package/dist/sandbox/runtime/agentPreparation.js.map +1 -0
  242. package/dist/sandbox/runtime/agentPreparation.mjs +172 -0
  243. package/dist/sandbox/runtime/agentPreparation.mjs.map +1 -0
  244. package/dist/sandbox/runtime/index.d.ts +5 -0
  245. package/dist/sandbox/runtime/index.js +22 -0
  246. package/dist/sandbox/runtime/index.js.map +1 -0
  247. package/dist/sandbox/runtime/index.mjs +6 -0
  248. package/dist/sandbox/runtime/index.mjs.map +1 -0
  249. package/dist/sandbox/runtime/livePreservedSessions.d.ts +25 -0
  250. package/dist/sandbox/runtime/livePreservedSessions.js +58 -0
  251. package/dist/sandbox/runtime/livePreservedSessions.js.map +1 -0
  252. package/dist/sandbox/runtime/livePreservedSessions.mjs +51 -0
  253. package/dist/sandbox/runtime/livePreservedSessions.mjs.map +1 -0
  254. package/dist/sandbox/runtime/manager.d.ts +68 -0
  255. package/dist/sandbox/runtime/manager.js +704 -0
  256. package/dist/sandbox/runtime/manager.js.map +1 -0
  257. package/dist/sandbox/runtime/manager.mjs +697 -0
  258. package/dist/sandbox/runtime/manager.mjs.map +1 -0
  259. package/dist/sandbox/runtime/prompts.d.ts +6 -0
  260. package/dist/sandbox/runtime/prompts.js +108 -0
  261. package/dist/sandbox/runtime/prompts.js.map +1 -0
  262. package/dist/sandbox/runtime/prompts.mjs +101 -0
  263. package/dist/sandbox/runtime/prompts.mjs.map +1 -0
  264. package/dist/sandbox/runtime/providedSessionManifest.d.ts +3 -0
  265. package/dist/sandbox/runtime/providedSessionManifest.js +175 -0
  266. package/dist/sandbox/runtime/providedSessionManifest.js.map +1 -0
  267. package/dist/sandbox/runtime/providedSessionManifest.mjs +172 -0
  268. package/dist/sandbox/runtime/providedSessionManifest.mjs.map +1 -0
  269. package/dist/sandbox/runtime/runAsManifest.d.ts +4 -0
  270. package/dist/sandbox/runtime/runAsManifest.js +40 -0
  271. package/dist/sandbox/runtime/runAsManifest.js.map +1 -0
  272. package/dist/sandbox/runtime/runAsManifest.mjs +36 -0
  273. package/dist/sandbox/runtime/runAsManifest.mjs.map +1 -0
  274. package/dist/sandbox/runtime/sessionLifecycle.d.ts +6 -0
  275. package/dist/sandbox/runtime/sessionLifecycle.js +222 -0
  276. package/dist/sandbox/runtime/sessionLifecycle.js.map +1 -0
  277. package/dist/sandbox/runtime/sessionLifecycle.mjs +215 -0
  278. package/dist/sandbox/runtime/sessionLifecycle.mjs.map +1 -0
  279. package/dist/sandbox/runtime/sessionSerialization.d.ts +12 -0
  280. package/dist/sandbox/runtime/sessionSerialization.js +74 -0
  281. package/dist/sandbox/runtime/sessionSerialization.js.map +1 -0
  282. package/dist/sandbox/runtime/sessionSerialization.mjs +71 -0
  283. package/dist/sandbox/runtime/sessionSerialization.mjs.map +1 -0
  284. package/dist/sandbox/runtime/sessionState.d.ts +26 -0
  285. package/dist/sandbox/runtime/sessionState.js +113 -0
  286. package/dist/sandbox/runtime/sessionState.js.map +1 -0
  287. package/dist/sandbox/runtime/sessionState.mjs +104 -0
  288. package/dist/sandbox/runtime/sessionState.mjs.map +1 -0
  289. package/dist/sandbox/runtime/spans.d.ts +1 -0
  290. package/dist/sandbox/runtime/spans.js +51 -0
  291. package/dist/sandbox/runtime/spans.js.map +1 -0
  292. package/dist/sandbox/runtime/spans.mjs +48 -0
  293. package/dist/sandbox/runtime/spans.mjs.map +1 -0
  294. package/dist/sandbox/runtime/toolRehydration.d.ts +34 -0
  295. package/dist/sandbox/runtime/toolRehydration.js +207 -0
  296. package/dist/sandbox/runtime/toolRehydration.js.map +1 -0
  297. package/dist/sandbox/runtime/toolRehydration.mjs +200 -0
  298. package/dist/sandbox/runtime/toolRehydration.mjs.map +1 -0
  299. package/dist/sandbox/sandboxes/docker.d.ts +75 -0
  300. package/dist/sandbox/sandboxes/docker.js +2015 -0
  301. package/dist/sandbox/sandboxes/docker.js.map +1 -0
  302. package/dist/sandbox/sandboxes/docker.mjs +2010 -0
  303. package/dist/sandbox/sandboxes/docker.mjs.map +1 -0
  304. package/dist/sandbox/sandboxes/index.d.ts +3 -0
  305. package/dist/sandbox/sandboxes/index.js +20 -0
  306. package/dist/sandbox/sandboxes/index.js.map +1 -0
  307. package/dist/sandbox/sandboxes/index.mjs +4 -0
  308. package/dist/sandbox/sandboxes/index.mjs.map +1 -0
  309. package/dist/sandbox/sandboxes/shared/localSnapshotPaths.d.ts +1 -0
  310. package/dist/sandbox/sandboxes/shared/localSnapshotPaths.js +22 -0
  311. package/dist/sandbox/sandboxes/shared/localSnapshotPaths.js.map +1 -0
  312. package/dist/sandbox/sandboxes/shared/localSnapshotPaths.mjs +19 -0
  313. package/dist/sandbox/sandboxes/shared/localSnapshotPaths.mjs.map +1 -0
  314. package/dist/sandbox/sandboxes/shared/localSnapshots.d.ts +34 -0
  315. package/dist/sandbox/sandboxes/shared/localSnapshots.js +525 -0
  316. package/dist/sandbox/sandboxes/shared/localSnapshots.js.map +1 -0
  317. package/dist/sandbox/sandboxes/shared/localSnapshots.mjs +508 -0
  318. package/dist/sandbox/sandboxes/shared/localSnapshots.mjs.map +1 -0
  319. package/dist/sandbox/sandboxes/shared/localWorkspace.d.ts +27 -0
  320. package/dist/sandbox/sandboxes/shared/localWorkspace.js +693 -0
  321. package/dist/sandbox/sandboxes/shared/localWorkspace.js.map +1 -0
  322. package/dist/sandbox/sandboxes/shared/localWorkspace.mjs +684 -0
  323. package/dist/sandbox/sandboxes/shared/localWorkspace.mjs.map +1 -0
  324. package/dist/sandbox/sandboxes/shared/manifestPersistence.d.ts +15 -0
  325. package/dist/sandbox/sandboxes/shared/manifestPersistence.js +191 -0
  326. package/dist/sandbox/sandboxes/shared/manifestPersistence.js.map +1 -0
  327. package/dist/sandbox/sandboxes/shared/manifestPersistence.mjs +182 -0
  328. package/dist/sandbox/sandboxes/shared/manifestPersistence.mjs.map +1 -0
  329. package/dist/sandbox/sandboxes/shared/pty.d.ts +9 -0
  330. package/dist/sandbox/sandboxes/shared/pty.js +151 -0
  331. package/dist/sandbox/sandboxes/shared/pty.js.map +1 -0
  332. package/dist/sandbox/sandboxes/shared/pty.mjs +148 -0
  333. package/dist/sandbox/sandboxes/shared/pty.mjs.map +1 -0
  334. package/dist/sandbox/sandboxes/shared/runProcess.d.ts +16 -0
  335. package/dist/sandbox/sandboxes/shared/runProcess.js +90 -0
  336. package/dist/sandbox/sandboxes/shared/runProcess.js.map +1 -0
  337. package/dist/sandbox/sandboxes/shared/runProcess.mjs +86 -0
  338. package/dist/sandbox/sandboxes/shared/runProcess.mjs.map +1 -0
  339. package/dist/sandbox/sandboxes/shared/sessionStateValues.d.ts +18 -0
  340. package/dist/sandbox/sandboxes/shared/sessionStateValues.js +40 -0
  341. package/dist/sandbox/sandboxes/shared/sessionStateValues.js.map +1 -0
  342. package/dist/sandbox/sandboxes/shared/sessionStateValues.mjs +35 -0
  343. package/dist/sandbox/sandboxes/shared/sessionStateValues.mjs.map +1 -0
  344. package/dist/sandbox/sandboxes/shared/shellCommand.d.ts +17 -0
  345. package/dist/sandbox/sandboxes/shared/shellCommand.js +38 -0
  346. package/dist/sandbox/sandboxes/shared/shellCommand.js.map +1 -0
  347. package/dist/sandbox/sandboxes/shared/shellCommand.mjs +34 -0
  348. package/dist/sandbox/sandboxes/shared/shellCommand.mjs.map +1 -0
  349. package/dist/sandbox/sandboxes/types.d.ts +11 -0
  350. package/dist/sandbox/sandboxes/types.js +3 -0
  351. package/dist/sandbox/sandboxes/types.js.map +1 -0
  352. package/dist/sandbox/sandboxes/types.mjs +2 -0
  353. package/dist/sandbox/sandboxes/types.mjs.map +1 -0
  354. package/dist/sandbox/sandboxes/unixLocal.d.ts +95 -0
  355. package/dist/sandbox/sandboxes/unixLocal.js +863 -0
  356. package/dist/sandbox/sandboxes/unixLocal.js.map +1 -0
  357. package/dist/sandbox/sandboxes/unixLocal.mjs +858 -0
  358. package/dist/sandbox/sandboxes/unixLocal.mjs.map +1 -0
  359. package/dist/sandbox/session.d.ts +123 -0
  360. package/dist/sandbox/session.js +58 -0
  361. package/dist/sandbox/session.js.map +1 -0
  362. package/dist/sandbox/session.mjs +50 -0
  363. package/dist/sandbox/session.mjs.map +1 -0
  364. package/dist/sandbox/shared/compare.d.ts +2 -0
  365. package/dist/sandbox/shared/compare.js +13 -0
  366. package/dist/sandbox/shared/compare.js.map +1 -0
  367. package/dist/sandbox/shared/compare.mjs +9 -0
  368. package/dist/sandbox/shared/compare.mjs.map +1 -0
  369. package/dist/sandbox/shared/environment.d.ts +14 -0
  370. package/dist/sandbox/shared/environment.js +69 -0
  371. package/dist/sandbox/shared/environment.js.map +1 -0
  372. package/dist/sandbox/shared/environment.mjs +59 -0
  373. package/dist/sandbox/shared/environment.mjs.map +1 -0
  374. package/dist/sandbox/shared/hostPath.d.ts +4 -0
  375. package/dist/sandbox/shared/hostPath.js +22 -0
  376. package/dist/sandbox/shared/hostPath.js.map +1 -0
  377. package/dist/sandbox/shared/hostPath.mjs +16 -0
  378. package/dist/sandbox/shared/hostPath.mjs.map +1 -0
  379. package/dist/sandbox/shared/manifestCollections.d.ts +12 -0
  380. package/dist/sandbox/shared/manifestCollections.js +40 -0
  381. package/dist/sandbox/shared/manifestCollections.js.map +1 -0
  382. package/dist/sandbox/shared/manifestCollections.mjs +34 -0
  383. package/dist/sandbox/shared/manifestCollections.mjs.map +1 -0
  384. package/dist/sandbox/shared/media.d.ts +6 -0
  385. package/dist/sandbox/shared/media.js +126 -0
  386. package/dist/sandbox/shared/media.js.map +1 -0
  387. package/dist/sandbox/shared/media.mjs +119 -0
  388. package/dist/sandbox/shared/media.mjs.map +1 -0
  389. package/dist/sandbox/shared/output.d.ts +12 -0
  390. package/dist/sandbox/shared/output.js +108 -0
  391. package/dist/sandbox/shared/output.js.map +1 -0
  392. package/dist/sandbox/shared/output.mjs +103 -0
  393. package/dist/sandbox/shared/output.mjs.map +1 -0
  394. package/dist/sandbox/shared/posixPath.d.ts +7 -0
  395. package/dist/sandbox/shared/posixPath.js +90 -0
  396. package/dist/sandbox/shared/posixPath.js.map +1 -0
  397. package/dist/sandbox/shared/posixPath.mjs +81 -0
  398. package/dist/sandbox/shared/posixPath.mjs.map +1 -0
  399. package/dist/sandbox/shared/remoteMountCommandAllowlist.d.ts +3 -0
  400. package/dist/sandbox/shared/remoteMountCommandAllowlist.js +33 -0
  401. package/dist/sandbox/shared/remoteMountCommandAllowlist.js.map +1 -0
  402. package/dist/sandbox/shared/remoteMountCommandAllowlist.mjs +28 -0
  403. package/dist/sandbox/shared/remoteMountCommandAllowlist.mjs.map +1 -0
  404. package/dist/sandbox/shared/shell.d.ts +1 -0
  405. package/dist/sandbox/shared/shell.js +7 -0
  406. package/dist/sandbox/shared/shell.js.map +1 -0
  407. package/dist/sandbox/shared/shell.mjs +4 -0
  408. package/dist/sandbox/shared/shell.mjs.map +1 -0
  409. package/dist/sandbox/shared/stableJson.d.ts +12 -0
  410. package/dist/sandbox/shared/stableJson.js +40 -0
  411. package/dist/sandbox/shared/stableJson.js.map +1 -0
  412. package/dist/sandbox/shared/stableJson.mjs +35 -0
  413. package/dist/sandbox/shared/stableJson.mjs.map +1 -0
  414. package/dist/sandbox/shared/typeGuards.d.ts +6 -0
  415. package/dist/sandbox/shared/typeGuards.js +34 -0
  416. package/dist/sandbox/shared/typeGuards.js.map +1 -0
  417. package/dist/sandbox/shared/typeGuards.mjs +26 -0
  418. package/dist/sandbox/shared/typeGuards.mjs.map +1 -0
  419. package/dist/sandbox/snapshot.d.ts +60 -0
  420. package/dist/sandbox/snapshot.js +45 -0
  421. package/dist/sandbox/snapshot.js.map +1 -0
  422. package/dist/sandbox/snapshot.mjs +39 -0
  423. package/dist/sandbox/snapshot.mjs.map +1 -0
  424. package/dist/sandbox/users.d.ts +11 -0
  425. package/dist/sandbox/users.js +31 -0
  426. package/dist/sandbox/users.js.map +1 -0
  427. package/dist/sandbox/users.mjs +26 -0
  428. package/dist/sandbox/users.mjs.map +1 -0
  429. package/dist/sandbox/workspacePaths.d.ts +20 -0
  430. package/dist/sandbox/workspacePaths.js +73 -0
  431. package/dist/sandbox/workspacePaths.js.map +1 -0
  432. package/dist/sandbox/workspacePaths.mjs +69 -0
  433. package/dist/sandbox/workspacePaths.mjs.map +1 -0
  434. package/dist/tool.js +1 -1
  435. package/dist/tool.js.map +1 -1
  436. package/dist/tool.mjs +1 -1
  437. package/dist/tool.mjs.map +1 -1
  438. package/dist/types/protocol.d.ts +8 -0
  439. package/dist/types/protocol.js +1 -0
  440. package/dist/types/protocol.js.map +1 -1
  441. package/dist/types/protocol.mjs +1 -0
  442. package/dist/types/protocol.mjs.map +1 -1
  443. package/dist/utils/messages.d.ts +6 -0
  444. package/dist/utils/messages.js +21 -0
  445. package/dist/utils/messages.js.map +1 -1
  446. package/dist/utils/messages.mjs +20 -0
  447. package/dist/utils/messages.mjs.map +1 -1
  448. package/dist/utils/strictToolSchema.d.ts +4 -0
  449. package/dist/utils/strictToolSchema.js +358 -0
  450. package/dist/utils/strictToolSchema.js.map +1 -0
  451. package/dist/utils/strictToolSchema.mjs +353 -0
  452. package/dist/utils/strictToolSchema.mjs.map +1 -0
  453. package/dist/utils/tools.d.ts +3 -1
  454. package/dist/utils/tools.js +18 -7
  455. package/dist/utils/tools.js.map +1 -1
  456. package/dist/utils/tools.mjs +18 -7
  457. package/dist/utils/tools.mjs.map +1 -1
  458. package/dist/utils/zodJsonSchemaCompat.js +18 -16
  459. package/dist/utils/zodJsonSchemaCompat.js.map +1 -1
  460. package/dist/utils/zodJsonSchemaCompat.mjs +18 -16
  461. package/dist/utils/zodJsonSchemaCompat.mjs.map +1 -1
  462. package/package.json +25 -1
@@ -0,0 +1,2010 @@
1
+ import { UserError } from "../../errors.mjs";
2
+ import { applyDiff } from "../../utils/applyDiff.mjs";
3
+ import { SandboxConfigurationError, SandboxMountError, SandboxUnsupportedFeatureError, } from "../errors.mjs";
4
+ import { chmod, mkdir, mkdtemp, rm } from 'node:fs/promises';
5
+ import { createHash, randomUUID } from 'node:crypto';
6
+ import { spawn } from 'node:child_process';
7
+ import { isAbsolute, join, resolve } from 'node:path';
8
+ import { tmpdir } from 'node:os';
9
+ import { isMount, } from "../entries/index.mjs";
10
+ import { normalizeSandboxClientCreateArgs } from "../client.mjs";
11
+ import { Manifest } from "../manifest.mjs";
12
+ import { WorkspacePathPolicy, } from "../workspacePaths.mjs";
13
+ import { getRecordedExposedPortEndpoint, normalizeExposedPort, recordExposedPortEndpoint, } from "../session.mjs";
14
+ import { UnixLocalSandboxSession, } from "./unixLocal.mjs";
15
+ import { assertLocalWorkspaceManifestMetadataSupported, joinSandboxLogicalPath, materializeLocalWorkspaceManifest, materializeLocalWorkspaceManifestEntry, materializeLocalWorkspaceManifestMounts, pathExists, } from "./shared/localWorkspace.mjs";
16
+ import { mergeManifestEntryDelta, mergeManifestDelta, sanitizeEnvironmentForPersistence, serializeManifest, } from "./shared/manifestPersistence.mjs";
17
+ import { imageOutputFromBytes } from "../shared/media.mjs";
18
+ import { canReuseLocalSnapshotWorkspace, localSnapshotIsRestorable, persistLocalSnapshot, restoreLocalSnapshotToWorkspace, serializeLocalSnapshotSpec, } from "./shared/localSnapshots.mjs";
19
+ import { spawnInPseudoTerminal } from "./shared/pty.mjs";
20
+ import { formatSandboxProcessError, runSandboxProcess, } from "./shared/runProcess.mjs";
21
+ import { resolveFallbackShellCommand } from "./shared/shellCommand.mjs";
22
+ import { shellQuote } from "../shared/shell.mjs";
23
+ import { deserializeLocalSandboxSessionStateValues, normalizeExposedPorts, } from "./shared/sessionStateValues.mjs";
24
+ import { readOptionalString, readString, readStringArray, } from "../shared/typeGuards.mjs";
25
+ const DEFAULT_DOCKER_IMAGE = 'python:3.14-slim';
26
+ const DEFAULT_CONTAINER_COMMAND = 'trap "exit 0" TERM INT; while true; do sleep 3600; done';
27
+ const DOCKER_FAST_COMMAND_TIMEOUT_MS = 10_000;
28
+ const DOCKER_CONTAINER_START_TIMEOUT_MS = 2 * 60_000;
29
+ const DOCKER_CONTAINER_REMOVE_TIMEOUT_MS = 30_000;
30
+ export class DockerSandboxSession extends UnixLocalSandboxSession {
31
+ containerClosed = false;
32
+ async resolveFilesystemRunAs(runAs) {
33
+ if (runAs && runAs.trim().length > 0) {
34
+ throw new UserError('DockerSandboxClient does not support runAs for filesystem operations.');
35
+ }
36
+ return undefined;
37
+ }
38
+ createEditor(runAs) {
39
+ if (!runAs) {
40
+ return super.createEditor();
41
+ }
42
+ return new DockerSandboxEditor(this, runAs);
43
+ }
44
+ async viewImage(args) {
45
+ if (!args.runAs) {
46
+ return await super.viewImage(args);
47
+ }
48
+ const bytes = await this.readDockerFileAs(this.resolveContainerFilesystemPath(args.path), args.runAs);
49
+ return imageOutputFromBytes(args.path, bytes);
50
+ }
51
+ async pathExists(path, runAs) {
52
+ if (!runAs && !this.pathRequiresDockerFilesystem(path)) {
53
+ return await super.pathExists(path);
54
+ }
55
+ const result = await this.runDockerFilesystemCommand(`test -e ${shellQuote(this.resolveContainerFilesystemPath(path))}`, { runAs });
56
+ return result.status === 0;
57
+ }
58
+ async readFile(args) {
59
+ if (!args.runAs && !this.pathRequiresDockerFilesystem(args.path)) {
60
+ return await super.readFile(args);
61
+ }
62
+ const bytes = await this.readDockerFileAs(this.resolveContainerFilesystemPath(args.path), args.runAs);
63
+ if (typeof args.maxBytes === 'number' && bytes.byteLength > args.maxBytes) {
64
+ return bytes.subarray(0, args.maxBytes);
65
+ }
66
+ return bytes;
67
+ }
68
+ async listDir(args) {
69
+ if (!args.runAs && !this.pathRequiresDockerFilesystem(args.path)) {
70
+ return await super.listDir(args);
71
+ }
72
+ const absolutePath = this.resolveContainerFilesystemPath(args.path);
73
+ const output = await this.runCheckedDockerFilesystemCommand([
74
+ `find ${shellQuote(absolutePath)} -mindepth 1 -maxdepth 1 -printf '%y\\t%f\\n'`,
75
+ ].join(' && '), { runAs: args.runAs }, `list directory ${absolutePath}`);
76
+ const logicalPath = this.resolveLogicalPath(args.path);
77
+ return output
78
+ .split(/\r?\n/u)
79
+ .filter((line) => line.trim().length > 0)
80
+ .map((line) => {
81
+ const separator = line.indexOf('\t');
82
+ const kind = separator >= 0 ? line.slice(0, separator) : '';
83
+ const name = separator >= 0 ? line.slice(separator + 1) : line;
84
+ return {
85
+ name,
86
+ path: logicalPath ? `${logicalPath}/${name}` : name,
87
+ type: kind === 'd' ? 'dir' : kind === 'f' ? 'file' : 'other',
88
+ };
89
+ });
90
+ }
91
+ pathRequiresDockerFilesystem(path) {
92
+ return Boolean(dockerInContainerMountContainingPath(this.state.manifest, path));
93
+ }
94
+ async materializeEntry(args) {
95
+ if (isMount(args.entry) && isDockerInContainerMount(args.entry)) {
96
+ const logicalPath = this.resolveLogicalPath(args.path);
97
+ assertDockerCanApplyInContainerMounts(this.state.manifest, new Manifest({
98
+ entries: {
99
+ [logicalPath]: args.entry,
100
+ },
101
+ }));
102
+ assertLocalWorkspaceManifestMetadataSupported('DockerSandboxClient', new Manifest({
103
+ entries: {
104
+ [logicalPath]: args.entry,
105
+ },
106
+ }), {
107
+ allowLocalBindMounts: false,
108
+ allowIdentityMetadata: true,
109
+ supportsMount: isSupportedDockerApplyMount,
110
+ });
111
+ await materializeDockerMountPoint(this.state.workspaceRootPath, this.state.manifest.root, logicalPath, args.entry);
112
+ if (args.runAs) {
113
+ const mountPath = resolveDockerMountPath(this.state.manifest.root, logicalPath, args.entry);
114
+ await this.mkdirDockerPathAs(mountPath, 'root');
115
+ await this.chownContainerPath(mountPath, args.runAs);
116
+ }
117
+ await applyDockerInContainerMount(this, logicalPath, args.entry);
118
+ this.state.manifest = mergeManifestEntryDelta(this.state.manifest, logicalPath, args.entry);
119
+ return;
120
+ }
121
+ if (!args.runAs) {
122
+ await super.materializeEntry(args);
123
+ return;
124
+ }
125
+ const logicalPath = this.resolveLogicalPath(args.path);
126
+ assertLocalWorkspaceManifestMetadataSupported('DockerSandboxClient', new Manifest({
127
+ entries: {
128
+ [logicalPath]: args.entry,
129
+ },
130
+ }), {
131
+ allowLocalBindMounts: false,
132
+ allowIdentityMetadata: true,
133
+ supportsMount: isSupportedDockerApplyMount,
134
+ });
135
+ await materializeLocalWorkspaceManifestEntry(this.state.workspaceRootPath, logicalPath, args.entry);
136
+ await this.chownContainerPath(this.resolveContainerFilesystemPath(args.path), args.runAs);
137
+ this.state.manifest = mergeManifestEntryDelta(this.state.manifest, logicalPath, args.entry);
138
+ }
139
+ async applyManifest(manifest, runAs) {
140
+ assertDockerManifestDeltaSupported(manifest);
141
+ assertDockerCanApplyInContainerMounts(this.state.manifest, manifest);
142
+ const environment = await manifest.resolveEnvironment();
143
+ const previousEnvironment = this.state.environment;
144
+ const nextEnvironment = {
145
+ ...this.state.environment,
146
+ ...environment,
147
+ };
148
+ this.state.environment = nextEnvironment;
149
+ try {
150
+ await provisionDockerAccounts(this.state.containerId, manifest);
151
+ const materializedManifest = stripDockerIdentityMetadata(manifest);
152
+ await materializeLocalWorkspaceManifest(materializedManifest, this.state.workspaceRootPath, {
153
+ allowLocalBindMounts: false,
154
+ allowIdentityMetadata: true,
155
+ supportsMount: isSupportedDockerApplyMount,
156
+ materializeMount: async ({ logicalPath, entry }) => {
157
+ await materializeDockerMountPoint(this.state.workspaceRootPath, this.state.manifest.root, logicalPath, entry);
158
+ },
159
+ });
160
+ if (runAs) {
161
+ for (const [path, entry] of Object.entries(manifest.entries)) {
162
+ if (isMount(entry)) {
163
+ const mountPath = resolveDockerMountPath(this.state.manifest.root, path, entry);
164
+ await this.mkdirDockerPathAs(mountPath, 'root');
165
+ await this.chownContainerPath(mountPath, runAs);
166
+ }
167
+ else {
168
+ await this.chownContainerPath(this.resolveContainerFilesystemPath(path), runAs);
169
+ }
170
+ }
171
+ }
172
+ await applyDockerInContainerMounts(this, manifest);
173
+ this.state.manifest = mergeDockerIdentityMetadata(mergeManifestDelta(this.state.manifest, materializedManifest), manifest);
174
+ }
175
+ catch (error) {
176
+ this.state.environment = previousEnvironment;
177
+ throw error;
178
+ }
179
+ }
180
+ async resolveExposedPort(port) {
181
+ const containerPort = normalizeExposedPort(port);
182
+ const configuredPorts = this.state.configuredExposedPorts ?? [];
183
+ if (configuredPorts.length > 0 &&
184
+ !configuredPorts.includes(containerPort)) {
185
+ throw new SandboxConfigurationError(`DockerSandboxClient was not configured to expose port ${containerPort}.`, {
186
+ provider: 'DockerSandboxClient',
187
+ port: containerPort,
188
+ configuredPorts,
189
+ });
190
+ }
191
+ const recorded = getRecordedExposedPortEndpoint(this.state, containerPort);
192
+ if (recorded) {
193
+ return recorded;
194
+ }
195
+ const result = await runSandboxProcess('docker', ['port', this.state.containerId, `${containerPort}/tcp`], {
196
+ timeoutMs: DOCKER_FAST_COMMAND_TIMEOUT_MS,
197
+ });
198
+ if (result.status !== 0) {
199
+ throw new UserError(`Failed to resolve Docker exposed port ${containerPort}: ${formatSandboxProcessError(result)}`);
200
+ }
201
+ return recordExposedPortEndpoint(this.state, {
202
+ ...parseDockerPortBinding(result.stdout, containerPort),
203
+ tls: false,
204
+ }, containerPort);
205
+ }
206
+ resolveCommandWorkdir(path) {
207
+ const logicalPath = this.resolveLogicalPath(path);
208
+ return joinSandboxLogicalPath(this.state.manifest.root, logicalPath);
209
+ }
210
+ async spawnShellCommand(command, args) {
211
+ const { shellPath, flag } = resolveFallbackShellCommand({
212
+ shell: args.shell,
213
+ defaultShell: this.defaultShell,
214
+ login: args.login,
215
+ });
216
+ const dockerArgs = ['exec', '-i', '-w', args.cwd];
217
+ if (args.tty) {
218
+ dockerArgs.splice(2, 0, '-t');
219
+ }
220
+ for (const [key, value] of Object.entries(this.state.environment)) {
221
+ dockerArgs.push('-e', `${key}=${value}`);
222
+ }
223
+ const runAs = args.runAs ?? this.state.defaultUser;
224
+ if (runAs) {
225
+ dockerArgs.push('-u', runAs);
226
+ }
227
+ dockerArgs.push(this.state.containerId, shellPath, flag, command);
228
+ if (args.tty) {
229
+ return spawnInPseudoTerminal('docker', dockerArgs);
230
+ }
231
+ return spawn('docker', dockerArgs, {
232
+ stdio: 'pipe',
233
+ });
234
+ }
235
+ translateCommandInput(command) {
236
+ return command;
237
+ }
238
+ translateCommandOutput(output) {
239
+ return output;
240
+ }
241
+ async materializeRestoredWorkspaceMounts() {
242
+ await prepareDockerWorkspaceRoot(this.state.workspaceRootPath, this.state.manifest);
243
+ await materializeLocalWorkspaceManifestMounts(this.state.manifest, this.state.workspaceRootPath, {
244
+ allowLocalBindMounts: false,
245
+ allowIdentityMetadata: true,
246
+ supportsMount: isSupportedDockerCreateMount,
247
+ materializeMount: async ({ logicalPath, entry }) => {
248
+ await materializeDockerMountPoint(this.state.workspaceRootPath, this.state.manifest.root, logicalPath, entry);
249
+ },
250
+ });
251
+ await applyDockerInContainerMounts(this, this.state.manifest);
252
+ }
253
+ resolveSandboxPath(path, options = {}) {
254
+ const mountPath = dockerVolumeMountContainingPath(this.state.manifest, path);
255
+ if (mountPath) {
256
+ throw new UserError(`DockerSandboxClient filesystem operations cannot access Docker volume mount path "${path ?? mountPath}". Use execCommand for container-visible paths under "${mountPath}".`);
257
+ }
258
+ const inContainerMountPath = dockerInContainerMountContainingPath(this.state.manifest, path);
259
+ if (inContainerMountPath) {
260
+ throw new UserError(`DockerSandboxClient host filesystem operations cannot access in-container mount path "${path ?? inContainerMountPath}". Use execCommand for container-visible paths under "${inContainerMountPath}".`);
261
+ }
262
+ return super.resolveSandboxPath(path, options);
263
+ }
264
+ resolveContainerFilesystemPath(path, options = {}) {
265
+ const resolved = new WorkspacePathPolicy({
266
+ root: this.state.manifest.root,
267
+ extraPathGrants: this.state.manifest.extraPathGrants,
268
+ }).resolve(path, options);
269
+ return resolved.path;
270
+ }
271
+ async readDockerFileAs(path, runAs) {
272
+ const output = await this.runCheckedDockerFilesystemCommand(`base64 -- ${shellQuote(path)}`, { runAs }, `read file ${path}`);
273
+ return Buffer.from(output.replace(/\s+/gu, ''), 'base64');
274
+ }
275
+ async writeDockerTextFileAs(path, content, runAs) {
276
+ const parent = dockerPosixDirname(path);
277
+ await this.runCheckedDockerFilesystemCommand(parent === '/' || parent === '.'
278
+ ? `cat > ${shellQuote(path)}`
279
+ : `mkdir -p -- ${shellQuote(parent)} && cat > ${shellQuote(path)}`, { runAs, input: content }, `write file ${path}`);
280
+ }
281
+ async deleteDockerPathAs(path, runAs) {
282
+ await this.runCheckedDockerFilesystemCommand(`rm -f -- ${shellQuote(path)}`, { runAs }, `delete path ${path}`);
283
+ }
284
+ async mkdirDockerPathAs(path, runAs) {
285
+ await this.runCheckedDockerFilesystemCommand(`mkdir -p -- ${shellQuote(path)}`, { runAs }, `create directory ${path}`);
286
+ }
287
+ async runDockerMountCommand(command, action, options = {}) {
288
+ return await this.runCheckedDockerFilesystemCommand(command, { runAs: 'root', input: options.input }, action);
289
+ }
290
+ async chownContainerPath(path, runAs) {
291
+ await this.runCheckedDockerFilesystemCommand(`chown -R ${shellQuote(runAs)}:${shellQuote(runAs)} -- ${shellQuote(path)}`, { runAs: 'root' }, `set ownership on ${path}`);
292
+ }
293
+ async runCheckedDockerFilesystemCommand(command, options = {}, action) {
294
+ const result = await this.runDockerFilesystemCommand(command, options);
295
+ if (result.status !== 0) {
296
+ throw new UserError(`DockerSandboxClient failed to ${action}: ${formatSandboxProcessError(result)}`);
297
+ }
298
+ return result.stdout;
299
+ }
300
+ async runDockerFilesystemCommand(command, options = {}) {
301
+ const dockerArgs = ['exec', '-i', '-w', '/'];
302
+ for (const [key, value] of Object.entries(this.state.environment)) {
303
+ dockerArgs.push('-e', `${key}=${value}`);
304
+ }
305
+ const runAs = options.runAs ?? this.state.defaultUser;
306
+ if (runAs) {
307
+ dockerArgs.push('-u', runAs);
308
+ }
309
+ dockerArgs.push(this.state.containerId, '/bin/sh', '-lc', command);
310
+ return await runDockerProcess(dockerArgs, options.input);
311
+ }
312
+ async close() {
313
+ let cleanupError;
314
+ if (!this.containerClosed) {
315
+ try {
316
+ await removeDockerContainer(this.state.containerId, {
317
+ ignoreMissing: true,
318
+ });
319
+ this.containerClosed = true;
320
+ }
321
+ catch (error) {
322
+ cleanupError = error;
323
+ }
324
+ }
325
+ try {
326
+ await removeDockerVolumes(this.state.dockerVolumeNames ?? []);
327
+ }
328
+ catch (error) {
329
+ cleanupError ??= error;
330
+ }
331
+ try {
332
+ await super.close();
333
+ }
334
+ catch (error) {
335
+ cleanupError ??= error;
336
+ }
337
+ if (cleanupError) {
338
+ throw cleanupError;
339
+ }
340
+ }
341
+ }
342
+ class DockerSandboxEditor {
343
+ session;
344
+ runAs;
345
+ constructor(session, runAs) {
346
+ this.session = session;
347
+ this.runAs = runAs;
348
+ }
349
+ async createFile(operation) {
350
+ const path = this.session.resolveContainerFilesystemPath(operation.path, {
351
+ forWrite: true,
352
+ });
353
+ if (await this.session.pathExists(operation.path, this.runAs)) {
354
+ throw new UserError(`Cannot create file because it already exists: ${path}`);
355
+ }
356
+ const content = applyDiff('', operation.diff, 'create');
357
+ const parent = dockerPosixDirname(path);
358
+ if (parent !== '.' && parent !== '/') {
359
+ await this.session.mkdirDockerPathAs(parent, this.runAs);
360
+ }
361
+ await this.session.writeDockerTextFileAs(path, content, this.runAs);
362
+ return {};
363
+ }
364
+ async updateFile(operation) {
365
+ const path = this.session.resolveContainerFilesystemPath(operation.path, {
366
+ forWrite: true,
367
+ });
368
+ const destination = operation.moveTo
369
+ ? this.session.resolveContainerFilesystemPath(operation.moveTo, {
370
+ forWrite: true,
371
+ })
372
+ : path;
373
+ const current = new TextDecoder().decode(await this.session.readDockerFileAs(path, this.runAs));
374
+ const next = applyDiff(current, operation.diff);
375
+ const parent = dockerPosixDirname(destination);
376
+ if (parent !== '.' && parent !== '/') {
377
+ await this.session.mkdirDockerPathAs(parent, this.runAs);
378
+ }
379
+ await this.session.writeDockerTextFileAs(destination, next, this.runAs);
380
+ if (operation.moveTo && destination !== path) {
381
+ await this.session.deleteDockerPathAs(path, this.runAs);
382
+ }
383
+ return {};
384
+ }
385
+ async deleteFile(operation) {
386
+ await this.session.deleteDockerPathAs(this.session.resolveContainerFilesystemPath(operation.path, {
387
+ forWrite: true,
388
+ }), this.runAs);
389
+ return {};
390
+ }
391
+ }
392
+ export class DockerSandboxClient {
393
+ backendId = 'docker';
394
+ supportsDefaultOptions = true;
395
+ options;
396
+ constructor(options = {}) {
397
+ this.options = options;
398
+ }
399
+ async create(args, manifestOptions) {
400
+ const createArgs = normalizeSandboxClientCreateArgs(args, manifestOptions);
401
+ const manifest = createArgs.manifest;
402
+ assertDockerManifestSupported(manifest);
403
+ await ensureDockerAvailable();
404
+ const resolvedOptions = {
405
+ ...this.options,
406
+ ...createArgs.options,
407
+ ...(createArgs.snapshot
408
+ ? { snapshot: createArgs.snapshot }
409
+ : {}),
410
+ ...(createArgs.concurrencyLimits
411
+ ? { concurrencyLimits: createArgs.concurrencyLimits }
412
+ : {}),
413
+ };
414
+ const workspaceRootPath = await mkdtemp(join(resolvedOptions.workspaceBaseDir ?? tmpdir(), 'openai-agents-docker-sandbox-'));
415
+ await materializeLocalWorkspaceManifest(manifest, workspaceRootPath, {
416
+ concurrencyLimits: resolvedOptions.concurrencyLimits,
417
+ allowLocalBindMounts: false,
418
+ allowIdentityMetadata: true,
419
+ supportsMount: isSupportedDockerCreateMount,
420
+ materializeMount: async ({ logicalPath, entry }) => {
421
+ await materializeDockerMountPoint(workspaceRootPath, manifest.root, logicalPath, entry);
422
+ },
423
+ });
424
+ await prepareDockerWorkspaceRoot(workspaceRootPath, manifest);
425
+ const image = resolvedOptions.image ?? DEFAULT_DOCKER_IMAGE;
426
+ const environment = await manifest.resolveEnvironment();
427
+ const defaultUser = getHostDockerUser();
428
+ const configuredExposedPorts = normalizeExposedPorts(resolvedOptions.exposedPorts);
429
+ const container = await startDockerContainer({
430
+ image,
431
+ manifest,
432
+ manifestRoot: manifest.root,
433
+ workspaceRootPath,
434
+ environment,
435
+ defaultUser,
436
+ exposedPorts: configuredExposedPorts,
437
+ });
438
+ const session = new DockerSandboxSession({
439
+ state: {
440
+ manifest,
441
+ workspaceRootPath,
442
+ workspaceRootOwned: true,
443
+ environment,
444
+ snapshotSpec: resolvedOptions.snapshot ?? null,
445
+ snapshot: null,
446
+ image,
447
+ containerId: container.containerId,
448
+ defaultUser,
449
+ configuredExposedPorts,
450
+ dockerVolumeNames: container.volumeNames,
451
+ },
452
+ });
453
+ try {
454
+ await provisionDockerAccounts(container.containerId, manifest);
455
+ await applyDockerInContainerMounts(session, manifest);
456
+ }
457
+ catch (error) {
458
+ await cleanupStartedDockerContainer({
459
+ containerId: container.containerId,
460
+ volumeNames: container.volumeNames,
461
+ workspaceRootPath,
462
+ removeWorkspace: true,
463
+ });
464
+ throw error;
465
+ }
466
+ return session;
467
+ }
468
+ async resume(state) {
469
+ assertDockerManifestSupported(state.manifest);
470
+ await ensureDockerAvailable();
471
+ const restoredState = await this.restoreIfNeeded(state);
472
+ return new DockerSandboxSession({
473
+ state: restoredState,
474
+ });
475
+ }
476
+ async serializeSessionState(state) {
477
+ const snapshotSpec = state.snapshotSpec ?? this.options.snapshot ?? null;
478
+ attachDockerSnapshotExcludedPaths(state);
479
+ const snapshot = await persistLocalSnapshot('DockerSandboxClient', state, snapshotSpec);
480
+ state.snapshotSpec = snapshotSpec;
481
+ return {
482
+ manifest: serializeManifest(state.manifest),
483
+ workspaceRootPath: state.workspaceRootPath,
484
+ workspaceRootOwned: state.workspaceRootOwned,
485
+ environment: sanitizeEnvironmentForPersistence(state),
486
+ snapshotSpec: serializeLocalSnapshotSpec(snapshotSpec),
487
+ snapshot,
488
+ snapshotFingerprint: state.snapshotFingerprint ?? null,
489
+ snapshotFingerprintVersion: state.snapshotFingerprintVersion ?? null,
490
+ image: state.image,
491
+ containerId: state.containerId,
492
+ defaultUser: state.defaultUser,
493
+ configuredExposedPorts: state.configuredExposedPorts ?? [],
494
+ dockerVolumeNames: state.dockerVolumeNames ?? [],
495
+ exposedPorts: state.exposedPorts ?? null,
496
+ };
497
+ }
498
+ async deserializeSessionState(state) {
499
+ const baseState = deserializeLocalSandboxSessionStateValues(state, this.options.snapshot);
500
+ return {
501
+ ...baseState,
502
+ image: readString(state, 'image'),
503
+ containerId: readString(state, 'containerId'),
504
+ defaultUser: readOptionalString(state, 'defaultUser') ?? getHostDockerUser(),
505
+ dockerVolumeNames: readStringArray(state.dockerVolumeNames),
506
+ };
507
+ }
508
+ async restoreIfNeeded(state) {
509
+ attachDockerSnapshotExcludedPaths(state);
510
+ const containerRunning = await inspectContainerRunning(state.containerId);
511
+ const workspaceExists = await pathExists(state.workspaceRootPath);
512
+ if (workspaceExists) {
513
+ if (containerRunning) {
514
+ return state;
515
+ }
516
+ if (await canReuseLocalSnapshotWorkspace(state)) {
517
+ await this.cleanupDockerResources(state);
518
+ return await this.restartContainer(state, state.workspaceRootPath);
519
+ }
520
+ if (await localSnapshotIsRestorable(state)) {
521
+ const restoredState = await restoreLocalSnapshotToWorkspace(state, state.workspaceRootPath);
522
+ await this.cleanupDockerResources(state);
523
+ return await this.restartContainer(restoredState, restoredState.workspaceRootPath);
524
+ }
525
+ }
526
+ if (!(await localSnapshotIsRestorable(state))) {
527
+ throw new UserError('Docker sandbox resources are unavailable and no local snapshot could be restored.');
528
+ }
529
+ await this.cleanupDockerResources(state);
530
+ const workspaceRootPath = await mkdtemp(join(this.options.workspaceBaseDir ?? tmpdir(), 'openai-agents-docker-sandbox-'));
531
+ const restoredState = await restoreLocalSnapshotToWorkspace({
532
+ ...state,
533
+ workspaceRootPath,
534
+ workspaceRootOwned: true,
535
+ }, workspaceRootPath);
536
+ return await this.restartContainer(restoredState, workspaceRootPath);
537
+ }
538
+ async cleanupDockerResources(state) {
539
+ await removeDockerContainer(state.containerId, { ignoreMissing: true });
540
+ await removeDockerVolumes(state.dockerVolumeNames ?? []);
541
+ }
542
+ async restartContainer(state, workspaceRootPath) {
543
+ await materializeLocalWorkspaceManifestMounts(state.manifest, workspaceRootPath, {
544
+ allowLocalBindMounts: false,
545
+ allowIdentityMetadata: true,
546
+ supportsMount: isSupportedDockerCreateMount,
547
+ materializeMount: async ({ logicalPath, entry }) => {
548
+ await materializeDockerMountPoint(workspaceRootPath, state.manifest.root, logicalPath, entry);
549
+ },
550
+ });
551
+ await prepareDockerWorkspaceRoot(workspaceRootPath, state.manifest);
552
+ const container = await startDockerContainer({
553
+ image: state.image,
554
+ manifest: state.manifest,
555
+ manifestRoot: state.manifest.root,
556
+ workspaceRootPath,
557
+ environment: state.environment,
558
+ defaultUser: state.defaultUser,
559
+ exposedPorts: state.configuredExposedPorts,
560
+ });
561
+ const nextState = {
562
+ ...state,
563
+ workspaceRootPath,
564
+ containerId: container.containerId,
565
+ dockerVolumeNames: container.volumeNames,
566
+ exposedPorts: undefined,
567
+ };
568
+ const session = new DockerSandboxSession({ state: nextState });
569
+ try {
570
+ await provisionDockerAccounts(container.containerId, state.manifest);
571
+ await applyDockerInContainerMounts(session, state.manifest);
572
+ }
573
+ catch (error) {
574
+ await cleanupStartedDockerContainer({
575
+ containerId: container.containerId,
576
+ volumeNames: container.volumeNames,
577
+ workspaceRootPath,
578
+ removeWorkspace: false,
579
+ });
580
+ throw error;
581
+ }
582
+ return nextState;
583
+ }
584
+ }
585
+ async function cleanupStartedDockerContainer(args) {
586
+ await removeDockerContainer(args.containerId, { ignoreMissing: true }).catch(() => undefined);
587
+ await removeDockerVolumes(args.volumeNames);
588
+ if (args.removeWorkspace) {
589
+ await rm(args.workspaceRootPath, { recursive: true, force: true }).catch(() => undefined);
590
+ }
591
+ }
592
+ function assertDockerManifestSupported(manifest) {
593
+ assertDockerManifestRootSupported(manifest);
594
+ assertLocalWorkspaceManifestMetadataSupported('DockerSandboxClient', manifest, {
595
+ allowLocalBindMounts: false,
596
+ allowIdentityMetadata: true,
597
+ supportsMount: isSupportedDockerCreateMount,
598
+ });
599
+ }
600
+ function assertDockerManifestDeltaSupported(manifest) {
601
+ assertLocalWorkspaceManifestMetadataSupported('DockerSandboxClient', manifest, {
602
+ allowLocalBindMounts: false,
603
+ allowIdentityMetadata: true,
604
+ supportsMount: isSupportedDockerApplyMount,
605
+ });
606
+ }
607
+ function assertDockerManifestRootSupported(manifest) {
608
+ // Docker maps the host workspace as a bind mount at manifest.root; mounting it
609
+ // over "/" would hide the image filesystem rather than emulate root confinement.
610
+ if (manifest.root === '/') {
611
+ throw new UserError('DockerSandboxClient does not support manifest root "/".');
612
+ }
613
+ }
614
+ async function prepareDockerWorkspaceRoot(workspaceRootPath, manifest) {
615
+ if (manifest.users.length === 0 && manifest.groups.length === 0) {
616
+ return;
617
+ }
618
+ await chmod(workspaceRootPath, 0o755);
619
+ }
620
+ async function provisionDockerAccounts(containerId, manifest) {
621
+ for (const command of dockerAccountProvisionCommands(manifest)) {
622
+ const result = await runSandboxProcess('docker', ['exec', '-u', 'root', containerId, '/bin/sh', '-c', command], {
623
+ timeoutMs: DOCKER_FAST_COMMAND_TIMEOUT_MS,
624
+ });
625
+ if (result.status !== 0) {
626
+ throw new UserError(`Failed to provision Docker sandbox manifest accounts: ${formatSandboxProcessError(result)}`);
627
+ }
628
+ }
629
+ }
630
+ function dockerAccountProvisionCommands(manifest) {
631
+ const commands = [];
632
+ const users = new Set(manifest.users.map((user) => user.name));
633
+ for (const group of manifest.groups) {
634
+ commands.push(`getent group ${shellQuote(group.name)} >/dev/null 2>&1 || groupadd ${shellQuote(group.name)}`);
635
+ for (const user of group.users ?? []) {
636
+ users.add(user.name);
637
+ }
638
+ }
639
+ for (const user of users) {
640
+ const quotedUser = shellQuote(user);
641
+ commands.push([
642
+ `if id -u ${quotedUser} >/dev/null 2>&1; then exit 0; fi`,
643
+ `if getent group ${quotedUser} >/dev/null 2>&1; then useradd -M -s /usr/sbin/nologin -g ${quotedUser} ${quotedUser}; else useradd -U -M -s /usr/sbin/nologin ${quotedUser}; fi`,
644
+ ].join('; '));
645
+ }
646
+ for (const group of manifest.groups) {
647
+ for (const user of group.users ?? []) {
648
+ commands.push(`usermod -aG ${shellQuote(group.name)} ${shellQuote(user.name)}`);
649
+ }
650
+ }
651
+ return commands;
652
+ }
653
+ function stripDockerIdentityMetadata(manifest) {
654
+ return new Manifest({
655
+ version: manifest.version,
656
+ root: manifest.root,
657
+ entries: manifest.entries,
658
+ environment: Object.fromEntries(Object.entries(manifest.environment).map(([key, value]) => [
659
+ key,
660
+ value.init(),
661
+ ])),
662
+ extraPathGrants: manifest.extraPathGrants,
663
+ remoteMountCommandAllowlist: manifest.remoteMountCommandAllowlist,
664
+ });
665
+ }
666
+ function mergeDockerIdentityMetadata(current, delta) {
667
+ return mergeManifestDelta(current, new Manifest({
668
+ users: delta.users,
669
+ groups: delta.groups,
670
+ }));
671
+ }
672
+ async function ensureDockerAvailable() {
673
+ const result = await runSandboxProcess('docker', ['version'], {
674
+ timeoutMs: DOCKER_FAST_COMMAND_TIMEOUT_MS,
675
+ });
676
+ if (result.status !== 0) {
677
+ throw new UserError('Docker sandbox execution requires a working Docker CLI and daemon.');
678
+ }
679
+ }
680
+ async function inspectContainerRunning(containerId) {
681
+ const result = await runSandboxProcess('docker', [
682
+ 'inspect',
683
+ '--type',
684
+ 'container',
685
+ '--format',
686
+ '{{.State.Running}}',
687
+ containerId,
688
+ ], {
689
+ timeoutMs: DOCKER_FAST_COMMAND_TIMEOUT_MS,
690
+ });
691
+ return result.status === 0 && result.stdout.trim() === 'true';
692
+ }
693
+ async function runDockerProcess(args, input) {
694
+ const child = spawn('docker', args, {
695
+ stdio: 'pipe',
696
+ });
697
+ const stdoutChunks = [];
698
+ const stderrChunks = [];
699
+ child.stdout.on('data', (chunk) => {
700
+ stdoutChunks.push(chunk);
701
+ });
702
+ child.stderr.on('data', (chunk) => {
703
+ stderrChunks.push(chunk);
704
+ });
705
+ const closed = new Promise((resolve) => {
706
+ child.on('close', (code) => {
707
+ resolve(code ?? 1);
708
+ });
709
+ child.on('error', (error) => {
710
+ stderrChunks.push(Buffer.from(error.message));
711
+ resolve(1);
712
+ });
713
+ });
714
+ if (input !== undefined) {
715
+ child.stdin.write(input);
716
+ }
717
+ child.stdin.end();
718
+ return {
719
+ status: await closed,
720
+ signal: null,
721
+ timedOut: false,
722
+ stdout: Buffer.concat(stdoutChunks).toString('utf8'),
723
+ stderr: Buffer.concat(stderrChunks).toString('utf8'),
724
+ };
725
+ }
726
+ async function removeDockerContainer(containerId, options = {}) {
727
+ const result = await runSandboxProcess('docker', ['rm', '-f', containerId], {
728
+ timeoutMs: DOCKER_CONTAINER_REMOVE_TIMEOUT_MS,
729
+ });
730
+ if (result.status !== 0) {
731
+ if (options.ignoreMissing && isMissingDockerContainerError(result)) {
732
+ return;
733
+ }
734
+ throw new UserError(`Failed to remove Docker sandbox container: ${formatSandboxProcessError(result)}`);
735
+ }
736
+ }
737
+ function isMissingDockerContainerError(result) {
738
+ const message = formatSandboxProcessError(result).toLowerCase();
739
+ return (message.includes('no such container') || message.includes('no such object'));
740
+ }
741
+ async function startDockerContainer(args) {
742
+ const envArgs = Object.entries(args.environment).flatMap(([key, value]) => [
743
+ '-e',
744
+ `${key}=${value}`,
745
+ ]);
746
+ const userArgs = args.defaultUser ? ['--user', args.defaultUser] : [];
747
+ const portArgs = normalizeExposedPorts(args.exposedPorts).flatMap((port) => [
748
+ '-p',
749
+ `127.0.0.1::${port}`,
750
+ ]);
751
+ const containerName = `openai-agents-sandbox-${randomUUID().slice(0, 8)}`;
752
+ const { mountArgs, volumeNames } = dockerMountArgsForManifest(args.manifest, containerName);
753
+ const privilegeArgs = dockerInContainerMountPrivilegeArgs(args.manifest);
754
+ const result = await runSandboxProcess('docker', [
755
+ 'run',
756
+ '-d',
757
+ '--name',
758
+ containerName,
759
+ '--label',
760
+ 'openai-agents-sandbox=true',
761
+ '-v',
762
+ `${args.workspaceRootPath}:${args.manifestRoot}`,
763
+ ...dockerExtraPathGrantMountArgs(args.manifest),
764
+ ...mountArgs,
765
+ ...privilegeArgs,
766
+ '-w',
767
+ args.manifestRoot,
768
+ ...portArgs,
769
+ ...userArgs,
770
+ ...envArgs,
771
+ args.image,
772
+ '/bin/sh',
773
+ '-c',
774
+ DEFAULT_CONTAINER_COMMAND,
775
+ ], {
776
+ timeoutMs: DOCKER_CONTAINER_START_TIMEOUT_MS,
777
+ });
778
+ if (result.status !== 0) {
779
+ throw new UserError(`Failed to start Docker sandbox container: ${formatSandboxProcessError(result)}`);
780
+ }
781
+ return {
782
+ containerId: result.stdout.trim(),
783
+ volumeNames,
784
+ };
785
+ }
786
+ async function materializeDockerMountPoint(workspaceRootPath, manifestRoot, logicalPath, entry) {
787
+ const relativePath = resolveDockerMountWorkspaceRelativePath(manifestRoot, logicalPath, entry);
788
+ if (!relativePath) {
789
+ return;
790
+ }
791
+ await mkdir(resolve(workspaceRootPath, relativePath), { recursive: true });
792
+ }
793
+ function isSupportedDockerCreateMount(entry) {
794
+ return (isDockerBindMount(entry) ||
795
+ isDockerVolumeMount(entry) ||
796
+ isSupportedDockerInContainerMount(entry));
797
+ }
798
+ function isSupportedDockerApplyMount(entry) {
799
+ return isSupportedDockerInContainerMount(entry);
800
+ }
801
+ function isDockerBindMount(entry) {
802
+ return (entry.type === 'mount' &&
803
+ typeof entry.source === 'string' &&
804
+ isAbsolute(entry.source) &&
805
+ (entry.mountStrategy === undefined ||
806
+ entry.mountStrategy.type === 'local_bind'));
807
+ }
808
+ function isDockerVolumeMount(entry) {
809
+ return Boolean(dockerVolumeDriverConfig(entry));
810
+ }
811
+ function isDockerInContainerMount(entry) {
812
+ return entry.mountStrategy?.type === 'in_container';
813
+ }
814
+ function isSupportedDockerInContainerMount(entry) {
815
+ if (!isDockerInContainerMount(entry)) {
816
+ return false;
817
+ }
818
+ let pattern;
819
+ try {
820
+ pattern = dockerInContainerMountPattern(entry);
821
+ }
822
+ catch {
823
+ return false;
824
+ }
825
+ switch (pattern.type) {
826
+ case 'rclone':
827
+ return dockerRcloneMountTypes.has(entry.type);
828
+ case 'mountpoint':
829
+ return entry.type === 's3_mount' || entry.type === 'gcs_mount';
830
+ case 's3files':
831
+ return entry.type === 's3_files_mount';
832
+ case 'fuse':
833
+ return entry.type === 'azure_blob_mount' || Boolean(pattern.command);
834
+ default:
835
+ return false;
836
+ }
837
+ }
838
+ function assertDockerCanApplyInContainerMounts(currentManifest, deltaManifest) {
839
+ const currentPrivilege = dockerInContainerMountPrivilege(currentManifest);
840
+ const requiredPrivilege = dockerInContainerMountPrivilege(deltaManifest);
841
+ if (dockerPrivilegeSatisfies(currentPrivilege, requiredPrivilege)) {
842
+ return;
843
+ }
844
+ throw new SandboxUnsupportedFeatureError('DockerSandboxClient cannot add this in-container mount to an already-running container because it requires Docker privileges that were not granted at container start.', {
845
+ provider: 'DockerSandboxClient',
846
+ currentPrivilege,
847
+ requiredPrivilege,
848
+ });
849
+ }
850
+ function dockerPrivilegeSatisfies(current, required) {
851
+ if (required === 'none' || current === required) {
852
+ return true;
853
+ }
854
+ return current === 'fuse' && required === 'sys_admin';
855
+ }
856
+ function dockerVolumeMountContainingPath(manifest, path) {
857
+ return dockerMountContainingPath(manifest, path, isDockerVolumeMount);
858
+ }
859
+ function dockerInContainerMountContainingPath(manifest, path) {
860
+ return dockerMountContainingPath(manifest, path, isDockerInContainerMount);
861
+ }
862
+ function dockerMountContainingPath(manifest, path, predicate) {
863
+ const resolved = new WorkspacePathPolicy({
864
+ root: manifest.root,
865
+ extraPathGrants: manifest.extraPathGrants,
866
+ }).resolve(path);
867
+ for (const { entry, mountPath } of manifest.mountTargets()) {
868
+ if (!predicate(entry)) {
869
+ continue;
870
+ }
871
+ if (pathWithinDockerMount(resolved.path, mountPath)) {
872
+ return mountPath;
873
+ }
874
+ }
875
+ return undefined;
876
+ }
877
+ function pathWithinDockerMount(path, mountPath) {
878
+ if (mountPath === '/') {
879
+ return true;
880
+ }
881
+ return path === mountPath || path.startsWith(`${mountPath}/`);
882
+ }
883
+ async function applyDockerInContainerMounts(session, manifest) {
884
+ const appliedMountPaths = [];
885
+ for (const { logicalPath, entry, } of manifest.mountTargetsForMaterialization()) {
886
+ if (!isDockerInContainerMount(entry)) {
887
+ continue;
888
+ }
889
+ try {
890
+ appliedMountPaths.push(await applyDockerInContainerMount(session, logicalPath, entry));
891
+ }
892
+ catch (error) {
893
+ await cleanupDockerAppliedMounts(session, appliedMountPaths);
894
+ throw error;
895
+ }
896
+ }
897
+ }
898
+ async function applyDockerInContainerMount(session, logicalPath, entry) {
899
+ const mountPath = resolveDockerMountPath(session.state.manifest.root, logicalPath, entry);
900
+ const pattern = dockerInContainerMountPattern(entry);
901
+ try {
902
+ switch (pattern.type) {
903
+ case 'rclone':
904
+ await applyDockerRcloneMount(session, entry, mountPath, pattern);
905
+ return mountPath;
906
+ case 'mountpoint':
907
+ await applyDockerMountpointMount(session, entry, mountPath, pattern);
908
+ return mountPath;
909
+ case 's3files':
910
+ await applyDockerS3FilesMount(session, entry, mountPath, pattern);
911
+ return mountPath;
912
+ case 'fuse':
913
+ await applyDockerFuseMount(session, entry, mountPath, pattern);
914
+ return mountPath;
915
+ default:
916
+ throw new SandboxUnsupportedFeatureError('DockerSandboxClient does not support this in-container mount pattern.', {
917
+ provider: 'DockerSandboxClient',
918
+ feature: 'entry.mountStrategy.pattern',
919
+ mountType: entry.type,
920
+ patternType: pattern.type,
921
+ });
922
+ }
923
+ }
924
+ catch (error) {
925
+ await cleanupDockerAppliedMounts(session, [mountPath]);
926
+ throw error;
927
+ }
928
+ }
929
+ async function cleanupDockerAppliedMounts(session, mountPaths) {
930
+ for (const mountPath of [...mountPaths].reverse()) {
931
+ await session
932
+ .runDockerMountCommand([
933
+ `fusermount3 -u ${shellQuote(mountPath)} >/dev/null 2>&1 || true`,
934
+ `fusermount -u ${shellQuote(mountPath)} >/dev/null 2>&1 || true`,
935
+ `umount ${shellQuote(mountPath)} >/dev/null 2>&1 || true`,
936
+ `umount -l ${shellQuote(mountPath)} >/dev/null 2>&1 || true`,
937
+ dockerSafeKillRcloneNfsPidFileCommand(dockerRcloneNfsPidPath(session, mountPath)),
938
+ ].join(' ; '), `cleanup Docker mount ${mountPath}`)
939
+ .catch(() => undefined);
940
+ }
941
+ }
942
+ function dockerInContainerMountPattern(entry) {
943
+ const pattern = entry.mountStrategy?.pattern;
944
+ if (pattern) {
945
+ return pattern;
946
+ }
947
+ if (entry.type === 's3_files_mount') {
948
+ return { type: 's3files' };
949
+ }
950
+ if (dockerRcloneMountTypes.has(entry.type)) {
951
+ return { type: 'rclone', mode: 'fuse' };
952
+ }
953
+ throw new SandboxUnsupportedFeatureError('DockerSandboxClient in-container mounts require a mount strategy pattern for this mount type.', {
954
+ provider: 'DockerSandboxClient',
955
+ feature: 'entry.mountStrategy.pattern',
956
+ mountType: entry.type,
957
+ });
958
+ }
959
+ function attachDockerSnapshotExcludedPaths(state) {
960
+ state.snapshotExcludedPaths = dockerInternalSnapshotExcludedPaths(state.manifest);
961
+ }
962
+ function dockerInternalSnapshotExcludedPaths(manifest) {
963
+ const paths = new Set();
964
+ for (const { entry } of manifest.mountTargetsForMaterialization()) {
965
+ if (!isDockerInContainerMount(entry)) {
966
+ continue;
967
+ }
968
+ const pattern = dockerInContainerMountPattern(entry);
969
+ if (entry.type === 'azure_blob_mount' && pattern.type === 'fuse') {
970
+ paths.add('.sandbox-blobfuse-config');
971
+ paths.add('.sandbox-blobfuse-cache');
972
+ const cachePath = dockerBlobfuseWorkspaceCachePath(pattern);
973
+ if (cachePath) {
974
+ paths.add(cachePath);
975
+ }
976
+ }
977
+ }
978
+ return [...paths];
979
+ }
980
+ async function applyDockerRcloneMount(session, entry, mountPath, pattern) {
981
+ const mode = pattern.mode ?? 'fuse';
982
+ if (mode !== 'fuse' && mode !== 'nfs') {
983
+ throw new SandboxUnsupportedFeatureError('DockerSandboxClient rclone mounts support fuse and nfs modes only.', {
984
+ provider: 'DockerSandboxClient',
985
+ feature: 'entry.mountStrategy.pattern.mode',
986
+ mode,
987
+ });
988
+ }
989
+ const config = await dockerRcloneMountConfig(session, entry, pattern, mountPath);
990
+ const configDir = `/tmp/openai-agents-docker-mounts-${dockerPathHash(`${session.state.containerId}:${mountPath}`)}`;
991
+ const configPath = `${configDir}/${config.remoteName}.conf`;
992
+ const baseCommand = [
993
+ `mkdir -p -- ${shellQuote(mountPath)}`,
994
+ `rm -rf -- ${shellQuote(configDir)}`,
995
+ `trap ${shellQuote(`rm -rf -- ${shellQuote(configDir)}`)} EXIT`,
996
+ `install -d -m 700 -- ${shellQuote(configDir)}`,
997
+ `(umask 077 && cat > ${shellQuote(configPath)})`,
998
+ ];
999
+ if (mode === 'nfs') {
1000
+ const nfsAddr = rclonePatternString(pattern, 'nfsAddr') ?? '127.0.0.1:2049';
1001
+ const pidPath = dockerRcloneNfsPidPath(session, mountPath);
1002
+ const [host, port] = splitDockerNfsAddr(nfsAddr);
1003
+ const serverArgs = [
1004
+ 'rclone',
1005
+ 'serve',
1006
+ 'nfs',
1007
+ `${config.remoteName}:${config.remotePath}`,
1008
+ '--addr',
1009
+ nfsAddr,
1010
+ '--config',
1011
+ configPath,
1012
+ ...(config.readOnly ? ['--read-only'] : []),
1013
+ ...dockerRclonePatternArgs(pattern),
1014
+ ];
1015
+ const mountOptions = rclonePatternStringArray(pattern, 'nfsMountOptions') ?? ['vers=4.1', 'tcp', `port=${port}`, 'soft', 'timeo=50', 'retrans=1'];
1016
+ const mountCommand = [
1017
+ '{ mounted=0',
1018
+ `for i in 1 2 3; do if mount -v -t nfs -o ${shellQuote(mountOptions.join(','))} ${shellQuote(`${host}:/`)} ${shellQuote(mountPath)}; then mounted=1; break; fi; sleep 1; done`,
1019
+ `if [ "$mounted" = 1 ]; then rm -rf -- ${shellQuote(configDir)}; exit 0; fi`,
1020
+ dockerSafeKillRcloneNfsPidFileCommand(pidPath),
1021
+ `rm -rf -- ${shellQuote(configDir)}`,
1022
+ 'exit 1',
1023
+ '}',
1024
+ ].join('; ');
1025
+ await session.runDockerMountCommand([
1026
+ ...baseCommand,
1027
+ `(${shellCommand(serverArgs)} & printf %s "$!" > ${shellQuote(pidPath)}) && ${mountCommand}`,
1028
+ ].join(' && '), `mount rclone ${entry.type}`, { input: config.configText });
1029
+ return;
1030
+ }
1031
+ const mountArgs = [
1032
+ 'rclone',
1033
+ 'mount',
1034
+ `${config.remoteName}:${config.remotePath}`,
1035
+ mountPath,
1036
+ ...(config.readOnly ? ['--read-only'] : []),
1037
+ ...dockerRcloneFuseAccessArgs(session),
1038
+ '--config',
1039
+ configPath,
1040
+ '--daemon',
1041
+ ...dockerRclonePatternArgs(pattern),
1042
+ ];
1043
+ await session.runDockerMountCommand([
1044
+ ...baseCommand,
1045
+ dockerEnableFuseAllowOtherCommand(),
1046
+ shellCommand(mountArgs),
1047
+ `rm -rf -- ${shellQuote(configDir)}`,
1048
+ ].join(' && '), `mount rclone ${entry.type}`, { input: config.configText });
1049
+ }
1050
+ function dockerRcloneNfsPidPath(session, mountPath) {
1051
+ return `/tmp/openai-agents-rclone-nfs-${dockerPathHash(`${session.state.containerId}:${mountPath}`)}.pid`;
1052
+ }
1053
+ function dockerSafeKillRcloneNfsPidFileCommand(pidPath) {
1054
+ return [
1055
+ `pid=$(cat ${shellQuote(pidPath)} 2>/dev/null || true)`,
1056
+ `case "$pid" in ''|0|*[!0-9]*) ;; *) cmdline=$(tr '\\000' ' ' < "/proc/$pid/cmdline" 2>/dev/null || true); case "$cmdline" in *rclone*serve\\ nfs*) kill "$pid" >/dev/null 2>&1 || true ;; esac ;; esac`,
1057
+ `rm -f -- ${shellQuote(pidPath)}`,
1058
+ ].join('; ');
1059
+ }
1060
+ function dockerEnableFuseAllowOtherCommand() {
1061
+ return "touch /etc/fuse.conf && (grep -qxF user_allow_other /etc/fuse.conf || printf '\\nuser_allow_other\\n' >> /etc/fuse.conf)";
1062
+ }
1063
+ function dockerRcloneFuseAccessArgs(session) {
1064
+ const user = session.state.defaultUser;
1065
+ const match = /^(\d+):(\d+)$/u.exec(user ?? '');
1066
+ return [
1067
+ '--allow-other',
1068
+ ...(match ? ['--uid', match[1], '--gid', match[2]] : []),
1069
+ ];
1070
+ }
1071
+ async function applyDockerMountpointMount(session, entry, mountPath, pattern) {
1072
+ if (entry.type !== 's3_mount' && entry.type !== 'gcs_mount') {
1073
+ throw new SandboxUnsupportedFeatureError('DockerSandboxClient mountpoint mounts support S3 and GCS mount entries only.', {
1074
+ provider: 'DockerSandboxClient',
1075
+ mountType: entry.type,
1076
+ });
1077
+ }
1078
+ const options = dockerMountpointPatternOptions(pattern);
1079
+ const endpointUrl = entry.endpointUrl ??
1080
+ options.endpointUrl ??
1081
+ (entry.type === 'gcs_mount' ? 'https://storage.googleapis.com' : undefined);
1082
+ const mountArgs = [
1083
+ 'mount-s3',
1084
+ ...((entry.readOnly ?? true)
1085
+ ? ['--read-only']
1086
+ : ['--allow-overwrite', '--allow-delete']),
1087
+ ...((entry.region ?? options.region)
1088
+ ? ['--region', entry.region ?? options.region]
1089
+ : []),
1090
+ ...(endpointUrl ? ['--endpoint-url', endpointUrl] : []),
1091
+ ...(entry.type === 'gcs_mount' ? ['--upload-checksums', 'off'] : []),
1092
+ ...((entry.prefix ?? options.prefix)
1093
+ ? ['--prefix', entry.prefix ?? options.prefix]
1094
+ : []),
1095
+ entry.bucket,
1096
+ mountPath,
1097
+ ];
1098
+ const envText = entry.type === 's3_mount'
1099
+ ? dockerMountpointAwsEnv(entry.accessKeyId, entry.secretAccessKey, entry.sessionToken)
1100
+ : dockerMountpointAwsEnv(readOptionalString(entry, 'accessId'), readOptionalString(entry, 'secretAccessKey'));
1101
+ const envDir = `/tmp/openai-agents-mountpoint-env-${dockerPathHash(`${session.state.containerId}:${mountPath}`)}`;
1102
+ const envPath = `${envDir}/credentials.env`;
1103
+ const command = envText
1104
+ ? [
1105
+ `rm -rf -- ${shellQuote(envDir)}`,
1106
+ `install -d -m 700 -- ${shellQuote(envDir)}`,
1107
+ `(umask 077 && cat > ${shellQuote(envPath)})`,
1108
+ `set -a && . ${shellQuote(envPath)} && set +a`,
1109
+ `rm -rf -- ${shellQuote(envDir)}`,
1110
+ shellCommand(mountArgs),
1111
+ ].join(' && ')
1112
+ : shellCommand(mountArgs);
1113
+ await session.runDockerMountCommand([`mkdir -p -- ${shellQuote(mountPath)}`, command].join(' && '), `mount mountpoint ${entry.type}`, envText ? { input: envText } : {});
1114
+ }
1115
+ async function applyDockerS3FilesMount(session, entry, mountPath, pattern) {
1116
+ if (entry.type !== 's3_files_mount') {
1117
+ throw new SandboxUnsupportedFeatureError('DockerSandboxClient s3files mounts require an s3FilesMount() entry.', {
1118
+ provider: 'DockerSandboxClient',
1119
+ mountType: entry.type,
1120
+ });
1121
+ }
1122
+ const s3Files = entry;
1123
+ if (!s3Files.fileSystemId) {
1124
+ throw new SandboxMountError('s3FilesMount() requires fileSystemId for Docker in-container mounts.', { mountType: entry.type }, 'mount_config_invalid');
1125
+ }
1126
+ const patternOptions = dockerS3FilesPatternOptions(pattern);
1127
+ const options = {
1128
+ ...readStringNullRecord(patternOptions.extraOptions),
1129
+ ...readStringNullRecord(s3Files.extraOptions),
1130
+ };
1131
+ if (entry.readOnly ?? true) {
1132
+ options.ro = null;
1133
+ }
1134
+ const mountTargetIp = s3Files.mountTargetIp ?? patternOptions.mountTargetIp;
1135
+ const accessPoint = s3Files.accessPoint ?? patternOptions.accessPoint;
1136
+ const region = s3Files.region ?? patternOptions.region;
1137
+ if (mountTargetIp) {
1138
+ options.mounttargetip = mountTargetIp;
1139
+ }
1140
+ if (accessPoint) {
1141
+ options.accesspoint = accessPoint;
1142
+ }
1143
+ if (region) {
1144
+ options.region = region;
1145
+ }
1146
+ const device = s3Files.subpath
1147
+ ? `${s3Files.fileSystemId}:${s3Files.subpath}`
1148
+ : s3Files.fileSystemId;
1149
+ const mountArgs = [
1150
+ 'mount',
1151
+ '-t',
1152
+ 's3files',
1153
+ ...(Object.keys(options).length > 0
1154
+ ? ['-o', dockerMountOptions(options)]
1155
+ : []),
1156
+ device,
1157
+ mountPath,
1158
+ ];
1159
+ await session.runDockerMountCommand([`mkdir -p -- ${shellQuote(mountPath)}`, shellCommand(mountArgs)].join(' && '), `mount s3files ${entry.type}`);
1160
+ }
1161
+ async function applyDockerFuseMount(session, entry, mountPath, pattern) {
1162
+ if (entry.type === 'azure_blob_mount') {
1163
+ await applyDockerAzureBlobFuseMount(session, entry, mountPath, pattern);
1164
+ return;
1165
+ }
1166
+ if (!pattern.command) {
1167
+ throw new SandboxUnsupportedFeatureError('DockerSandboxClient fuse command mounts require pattern.command.', {
1168
+ provider: 'DockerSandboxClient',
1169
+ mountType: entry.type,
1170
+ });
1171
+ }
1172
+ const command = Array.isArray(pattern.command)
1173
+ ? shellCommand(pattern.command)
1174
+ : pattern.command;
1175
+ await session.runDockerMountCommand([
1176
+ `mkdir -p -- ${shellQuote(mountPath)}`,
1177
+ [
1178
+ `export OPENAI_AGENTS_MOUNT_PATH=${shellQuote(mountPath)}`,
1179
+ ...(entry.source
1180
+ ? [`export OPENAI_AGENTS_MOUNT_SOURCE=${shellQuote(entry.source)}`]
1181
+ : []),
1182
+ command,
1183
+ ].join('; '),
1184
+ ].join(' && '), `mount fuse command ${entry.type}`);
1185
+ }
1186
+ async function applyDockerAzureBlobFuseMount(session, entry, mountPath, pattern) {
1187
+ const account = entry.account ?? entry.accountName;
1188
+ if (!account) {
1189
+ throw new SandboxMountError('azureBlobMount() requires account or accountName for Docker fuse mounts.', { mountType: entry.type }, 'mount_config_invalid');
1190
+ }
1191
+ if (entry.prefix) {
1192
+ throw new SandboxUnsupportedFeatureError('DockerSandboxClient blobfuse mounts do not support azureBlobMount().prefix. Use an rclone mount pattern for prefix-scoped Azure Blob mounts.', {
1193
+ provider: 'DockerSandboxClient',
1194
+ mountType: entry.type,
1195
+ });
1196
+ }
1197
+ const cacheType = dockerFusePatternString(pattern, 'cacheType', 'block_cache');
1198
+ if (cacheType !== 'block_cache' && cacheType !== 'file_cache') {
1199
+ throw new SandboxMountError('blobfuse cacheType must be "block_cache" or "file_cache".', { mountType: entry.type, cacheType }, 'mount_config_invalid');
1200
+ }
1201
+ const workspaceRoot = session.state.manifest.root;
1202
+ const cacheDir = resolveDockerBlobfuseCacheDir(workspaceRoot, pattern, session.state.containerId, account, entry.container);
1203
+ if (pathWithinDockerMount(cacheDir, mountPath)) {
1204
+ throw new SandboxMountError('blobfuse cachePath must be outside the mount path.', {
1205
+ mountPath,
1206
+ cachePath: cacheDir,
1207
+ }, 'mount_config_invalid');
1208
+ }
1209
+ const configDir = `/tmp/openai-agents-blobfuse-config-${dockerPathHash(`${session.state.containerId}:${mountPath}`)}`;
1210
+ const configName = `${sanitizeDockerMountName(account)}_${sanitizeDockerMountName(entry.container)}.yaml`;
1211
+ const configPath = `${configDir}/${configName}`;
1212
+ const configText = dockerBlobfuseConfigText({
1213
+ account,
1214
+ container: entry.container,
1215
+ endpoint: azureBlobEndpoint(entry) ?? `https://${account}.blob.core.windows.net`,
1216
+ cacheType,
1217
+ cacheSizeMb: dockerFusePatternNumber(pattern, 'cacheSizeMb') ??
1218
+ (cacheType === 'block_cache' ? 50_000 : 4_096),
1219
+ blockCacheBlockSizeMb: dockerFusePatternNumber(pattern, 'blockCacheBlockSizeMb') ?? 16,
1220
+ blockCacheDiskTimeoutSec: dockerFusePatternNumber(pattern, 'blockCacheDiskTimeoutSec') ?? 3600,
1221
+ fileCacheTimeoutSec: dockerFusePatternNumber(pattern, 'fileCacheTimeoutSec') ?? 120,
1222
+ fileCacheMaxSizeMb: dockerFusePatternNumber(pattern, 'fileCacheMaxSizeMb'),
1223
+ cacheDir,
1224
+ allowOther: dockerFusePatternBoolean(pattern, 'allowOther', true),
1225
+ logType: dockerFusePatternString(pattern, 'logType', 'syslog'),
1226
+ logLevel: dockerFusePatternString(pattern, 'logLevel', 'log_debug'),
1227
+ entryCacheTimeoutSec: dockerFusePatternNumber(pattern, 'entryCacheTimeoutSec'),
1228
+ negativeEntryCacheTimeoutSec: dockerFusePatternNumber(pattern, 'negativeEntryCacheTimeoutSec'),
1229
+ attrCacheTimeoutSec: dockerFusePatternNumber(pattern, 'attrCacheTimeoutSec'),
1230
+ identityClientId: entry.identityClientId,
1231
+ accountKey: entry.accountKey,
1232
+ });
1233
+ const mountArgs = [
1234
+ 'blobfuse2',
1235
+ 'mount',
1236
+ ...((entry.readOnly ?? true) ? ['--read-only'] : []),
1237
+ '--config-file',
1238
+ configPath,
1239
+ mountPath,
1240
+ ];
1241
+ await session.runDockerMountCommand([
1242
+ 'command -v blobfuse2 >/dev/null 2>&1',
1243
+ `mkdir -p -- ${shellQuote(mountPath)} ${shellQuote(cacheDir)}`,
1244
+ `rm -rf -- ${shellQuote(configDir)}`,
1245
+ `trap ${shellQuote(`rm -rf -- ${shellQuote(configDir)}`)} EXIT`,
1246
+ `install -d -m 700 -- ${shellQuote(configDir)}`,
1247
+ `(umask 077 && cat > ${shellQuote(configPath)})`,
1248
+ dockerEnableFuseAllowOtherCommand(),
1249
+ shellCommand(mountArgs),
1250
+ `rm -rf -- ${shellQuote(configDir)}`,
1251
+ ].join(' && '), `mount blobfuse ${entry.type}`, { input: configText });
1252
+ }
1253
+ function resolveDockerBlobfuseCacheDir(workspaceRoot, pattern, containerId, account, container) {
1254
+ const configuredCachePath = dockerFusePatternString(pattern, 'cachePath');
1255
+ if (configuredCachePath) {
1256
+ return joinSandboxLogicalPath(workspaceRoot, normalizeDockerBlobfuseCachePath(configuredCachePath));
1257
+ }
1258
+ return joinSandboxLogicalPath(workspaceRoot, [
1259
+ '.sandbox-blobfuse-cache',
1260
+ dockerPathHash(containerId),
1261
+ sanitizeDockerMountName(account),
1262
+ sanitizeDockerMountName(container),
1263
+ ].join('/'));
1264
+ }
1265
+ function dockerBlobfuseWorkspaceCachePath(pattern) {
1266
+ const configuredCachePath = dockerFusePatternString(pattern, 'cachePath');
1267
+ return configuredCachePath
1268
+ ? normalizeDockerBlobfuseCachePath(configuredCachePath)
1269
+ : undefined;
1270
+ }
1271
+ function normalizeDockerBlobfuseCachePath(cachePath) {
1272
+ const normalized = cachePath.replace(/\\/gu, '/');
1273
+ const parts = normalized.split('/').filter((part) => part.length > 0);
1274
+ if (normalized.startsWith('/') ||
1275
+ /^[A-Za-z]:\//u.test(normalized) ||
1276
+ normalized === '.' ||
1277
+ parts.includes('..')) {
1278
+ throw new SandboxMountError('blobfuse cachePath must be relative to the workspace root.', { cachePath }, 'mount_config_invalid');
1279
+ }
1280
+ return normalized.replace(/^\.\/+/u, '');
1281
+ }
1282
+ function dockerBlobfuseConfigText(args) {
1283
+ const lines = [];
1284
+ if (args.allowOther) {
1285
+ lines.push('allow-other: true', '');
1286
+ }
1287
+ lines.push('logging:', ` type: ${args.logType}`, ` level: ${args.logLevel}`, '', 'components:', ' - libfuse', ` - ${args.cacheType}`, ' - attr_cache', ' - azstorage', '');
1288
+ const libfuseLines = [];
1289
+ if (args.entryCacheTimeoutSec !== undefined) {
1290
+ libfuseLines.push(` entry-expiration-sec: ${args.entryCacheTimeoutSec}`);
1291
+ }
1292
+ if (args.negativeEntryCacheTimeoutSec !== undefined) {
1293
+ libfuseLines.push(` negative-entry-expiration-sec: ${args.negativeEntryCacheTimeoutSec}`);
1294
+ }
1295
+ if (libfuseLines.length > 0) {
1296
+ lines.push('libfuse:', ...libfuseLines, '');
1297
+ }
1298
+ if (args.cacheType === 'block_cache') {
1299
+ lines.push('block_cache:', ` block-size-mb: ${args.blockCacheBlockSizeMb}`, ` mem-size-mb: ${args.cacheSizeMb}`, ` path: ${args.cacheDir}`, ` disk-size-mb: ${args.cacheSizeMb}`, ` disk-timeout-sec: ${args.blockCacheDiskTimeoutSec}`, '');
1300
+ }
1301
+ else {
1302
+ lines.push('file_cache:', ` path: ${args.cacheDir}`, ` timeout-sec: ${args.fileCacheTimeoutSec}`, ` max-size-mb: ${args.fileCacheMaxSizeMb ?? args.cacheSizeMb}`, '');
1303
+ }
1304
+ lines.push('attr_cache:', ` timeout-sec: ${args.attrCacheTimeoutSec ?? 7200}`, '', 'azstorage:', ' type: block', ` account-name: ${args.account}`, ` container: ${args.container}`, ` endpoint: ${args.endpoint}`);
1305
+ if (args.accountKey) {
1306
+ lines.push(' auth-type: key', ` account-key: ${args.accountKey}`);
1307
+ }
1308
+ else {
1309
+ lines.push(' mode: msi');
1310
+ }
1311
+ if (args.identityClientId) {
1312
+ lines.push(` identity-client-id: ${args.identityClientId}`);
1313
+ }
1314
+ lines.push('');
1315
+ return lines.join('\n');
1316
+ }
1317
+ function dockerFusePatternString(pattern, key, fallback) {
1318
+ const value = pattern[key];
1319
+ return typeof value === 'string' ? value : (fallback ?? '');
1320
+ }
1321
+ function dockerFusePatternNumber(pattern, key, fallback) {
1322
+ const value = pattern[key];
1323
+ return typeof value === 'number' ? value : fallback;
1324
+ }
1325
+ function dockerFusePatternBoolean(pattern, key, fallback) {
1326
+ const value = pattern[key];
1327
+ return typeof value === 'boolean' ? value : fallback;
1328
+ }
1329
+ function dockerMountArgsForManifest(manifest, containerName) {
1330
+ const mountArgs = [];
1331
+ const volumeNames = [];
1332
+ for (const { mountPath, entry, } of manifest.mountTargetsForMaterialization()) {
1333
+ if (isDockerBindMount(entry)) {
1334
+ mountArgs.push('--mount', dockerMountArg({
1335
+ type: 'bind',
1336
+ source: entry.source,
1337
+ target: mountPath,
1338
+ readOnly: entry.readOnly ?? true,
1339
+ }));
1340
+ continue;
1341
+ }
1342
+ const volumeConfig = dockerVolumeDriverConfig(entry);
1343
+ if (!volumeConfig) {
1344
+ continue;
1345
+ }
1346
+ const volumeName = dockerVolumeName(containerName, mountPath);
1347
+ volumeNames.push(volumeName);
1348
+ mountArgs.push('--mount', dockerMountArg({
1349
+ type: 'volume',
1350
+ source: volumeName,
1351
+ target: mountPath,
1352
+ readOnly: volumeConfig.readOnly,
1353
+ volumeDriver: volumeConfig.driver,
1354
+ volumeOptions: volumeConfig.options,
1355
+ }));
1356
+ }
1357
+ return { mountArgs, volumeNames };
1358
+ }
1359
+ function dockerExtraPathGrantMountArgs(manifest) {
1360
+ return manifest.extraPathGrants.flatMap((grant) => [
1361
+ '--mount',
1362
+ dockerMountArg({
1363
+ type: 'bind',
1364
+ source: grant.path,
1365
+ target: grant.path,
1366
+ readOnly: grant.readOnly,
1367
+ }),
1368
+ ]);
1369
+ }
1370
+ function dockerVolumeDriverConfig(entry) {
1371
+ if (entry.mountStrategy?.type !== 'docker_volume') {
1372
+ return undefined;
1373
+ }
1374
+ const driver = entry.mountStrategy.driver ?? 'local';
1375
+ const driverOptions = entry.mountStrategy.driverOptions ?? {};
1376
+ const readOnly = entry.readOnly ?? true;
1377
+ switch (entry.type) {
1378
+ case 's3_mount':
1379
+ if (driver !== 'rclone' && driver !== 'mountpoint') {
1380
+ return undefined;
1381
+ }
1382
+ return {
1383
+ driver,
1384
+ options: {
1385
+ ...(driver === 'rclone'
1386
+ ? dockerRcloneS3Options(entry)
1387
+ : dockerMountpointS3Options(entry)),
1388
+ ...driverOptions,
1389
+ },
1390
+ readOnly,
1391
+ };
1392
+ case 'gcs_mount':
1393
+ if (driver !== 'rclone' && driver !== 'mountpoint') {
1394
+ return undefined;
1395
+ }
1396
+ return {
1397
+ driver,
1398
+ options: {
1399
+ ...(driver === 'rclone'
1400
+ ? dockerRcloneGcsOptions(entry)
1401
+ : dockerMountpointGcsOptions(entry)),
1402
+ ...driverOptions,
1403
+ },
1404
+ readOnly,
1405
+ };
1406
+ case 'r2_mount':
1407
+ if (driver !== 'rclone') {
1408
+ return undefined;
1409
+ }
1410
+ return {
1411
+ driver,
1412
+ options: {
1413
+ ...dockerRcloneR2Options(entry),
1414
+ ...driverOptions,
1415
+ },
1416
+ readOnly,
1417
+ };
1418
+ case 'azure_blob_mount':
1419
+ if (driver !== 'rclone') {
1420
+ return undefined;
1421
+ }
1422
+ return {
1423
+ driver,
1424
+ options: {
1425
+ ...dockerRcloneAzureBlobOptions(entry),
1426
+ ...driverOptions,
1427
+ },
1428
+ readOnly,
1429
+ };
1430
+ case 'box_mount':
1431
+ if (driver !== 'rclone') {
1432
+ return undefined;
1433
+ }
1434
+ return {
1435
+ driver,
1436
+ options: {
1437
+ ...dockerRcloneBoxOptions(entry),
1438
+ ...driverOptions,
1439
+ },
1440
+ readOnly,
1441
+ };
1442
+ default:
1443
+ return undefined;
1444
+ }
1445
+ }
1446
+ const dockerRcloneMountTypes = new Set([
1447
+ 's3_mount',
1448
+ 'r2_mount',
1449
+ 'gcs_mount',
1450
+ 'azure_blob_mount',
1451
+ 'box_mount',
1452
+ ]);
1453
+ async function dockerRcloneMountConfig(session, entry, pattern, mountPath) {
1454
+ const remoteName = dockerRcloneRemoteName(entry, pattern, mountPath);
1455
+ const withConfigText = async (config) => {
1456
+ const configFilePath = rclonePatternString(pattern, 'configFilePath');
1457
+ if (!configFilePath) {
1458
+ return config;
1459
+ }
1460
+ const sourcePath = resolveDockerRcloneConfigPath(session.state.manifest, configFilePath);
1461
+ const encodedConfig = await session.runDockerMountCommand(`base64 -- ${shellQuote(sourcePath)}`, `read rclone config ${sourcePath}`);
1462
+ const sourceConfigText = Buffer.from(encodedConfig.replace(/\s+/gu, ''), 'base64').toString('utf8');
1463
+ return {
1464
+ ...config,
1465
+ configText: supplementDockerRcloneConfigText(sourceConfigText, remoteName, config.configText, entry.type),
1466
+ };
1467
+ };
1468
+ switch (entry.type) {
1469
+ case 's3_mount':
1470
+ return await withConfigText({
1471
+ remoteName,
1472
+ remotePath: joinRemotePath(entry.bucket, entry.prefix),
1473
+ configText: [
1474
+ `[${remoteName}]`,
1475
+ 'type = s3',
1476
+ `provider = ${entry.s3Provider ?? 'AWS'}`,
1477
+ ...(entry.endpointUrl ? [`endpoint = ${entry.endpointUrl}`] : []),
1478
+ ...(entry.region ? [`region = ${entry.region}`] : []),
1479
+ ...s3CredentialLines(entry),
1480
+ '',
1481
+ ].join('\n'),
1482
+ readOnly: entry.readOnly ?? true,
1483
+ });
1484
+ case 'r2_mount':
1485
+ validateCredentialPair('R2', entry.type, entry.accessKeyId, entry.secretAccessKey);
1486
+ if (!entry.accountId) {
1487
+ throw new SandboxMountError('R2 mounts require accountId.', { mountType: entry.type }, 'mount_config_invalid');
1488
+ }
1489
+ return await withConfigText({
1490
+ remoteName,
1491
+ remotePath: joinRemotePath(entry.bucket, entry.prefix),
1492
+ configText: [
1493
+ `[${remoteName}]`,
1494
+ 'type = s3',
1495
+ 'provider = Cloudflare',
1496
+ `endpoint = ${entry.customDomain ?? `https://${entry.accountId}.r2.cloudflarestorage.com`}`,
1497
+ 'acl = private',
1498
+ ...(entry.accessKeyId && entry.secretAccessKey
1499
+ ? [
1500
+ 'env_auth = false',
1501
+ `access_key_id = ${entry.accessKeyId}`,
1502
+ `secret_access_key = ${entry.secretAccessKey}`,
1503
+ ]
1504
+ : ['env_auth = true']),
1505
+ '',
1506
+ ].join('\n'),
1507
+ readOnly: entry.readOnly ?? true,
1508
+ });
1509
+ case 'gcs_mount':
1510
+ return await withConfigText(dockerGcsRcloneMountConfig(entry, remoteName));
1511
+ case 'azure_blob_mount':
1512
+ return await withConfigText(dockerAzureBlobRcloneMountConfig(entry, remoteName));
1513
+ case 'box_mount':
1514
+ return await withConfigText({
1515
+ remoteName,
1516
+ remotePath: normalizeBoxRemotePath(entry.path),
1517
+ configText: [
1518
+ `[${remoteName}]`,
1519
+ 'type = box',
1520
+ ...(entry.clientId ? [`client_id = ${entry.clientId}`] : []),
1521
+ ...(entry.clientSecret
1522
+ ? [`client_secret = ${entry.clientSecret}`]
1523
+ : []),
1524
+ ...(entry.accessToken ? [`access_token = ${entry.accessToken}`] : []),
1525
+ ...(entry.token ? [`token = ${entry.token}`] : []),
1526
+ ...(entry.boxConfigFile
1527
+ ? [`box_config_file = ${entry.boxConfigFile}`]
1528
+ : []),
1529
+ ...(entry.configCredentials
1530
+ ? [`config_credentials = ${entry.configCredentials}`]
1531
+ : []),
1532
+ ...(entry.boxSubType && entry.boxSubType !== 'user'
1533
+ ? [`box_sub_type = ${entry.boxSubType}`]
1534
+ : []),
1535
+ ...(entry.rootFolderId
1536
+ ? [`root_folder_id = ${entry.rootFolderId}`]
1537
+ : []),
1538
+ ...(entry.impersonate ? [`impersonate = ${entry.impersonate}`] : []),
1539
+ ...(entry.ownedBy ? [`owned_by = ${entry.ownedBy}`] : []),
1540
+ '',
1541
+ ].join('\n'),
1542
+ readOnly: entry.readOnly ?? true,
1543
+ });
1544
+ default:
1545
+ throw new SandboxUnsupportedFeatureError('DockerSandboxClient rclone mounts do not support this mount entry.', {
1546
+ provider: 'DockerSandboxClient',
1547
+ mountType: entry.type,
1548
+ });
1549
+ }
1550
+ }
1551
+ function dockerAzureBlobRcloneMountConfig(entry, remoteName) {
1552
+ const account = entry.account ?? entry.accountName;
1553
+ if (!account) {
1554
+ throw new SandboxMountError('Azure Blob mounts require account or accountName.', { mountType: entry.type }, 'mount_config_invalid');
1555
+ }
1556
+ return {
1557
+ remoteName,
1558
+ remotePath: joinRemotePath(entry.container, entry.prefix),
1559
+ configText: [
1560
+ `[${remoteName}]`,
1561
+ 'type = azureblob',
1562
+ `account = ${account}`,
1563
+ ...(azureBlobEndpoint(entry)
1564
+ ? [`endpoint = ${azureBlobEndpoint(entry)}`]
1565
+ : []),
1566
+ ...(entry.accountKey
1567
+ ? [`key = ${entry.accountKey}`]
1568
+ : [
1569
+ 'use_msi = true',
1570
+ ...(entry.identityClientId
1571
+ ? [`msi_client_id = ${entry.identityClientId}`]
1572
+ : []),
1573
+ ]),
1574
+ '',
1575
+ ].join('\n'),
1576
+ readOnly: entry.readOnly ?? true,
1577
+ };
1578
+ }
1579
+ function azureBlobEndpoint(entry) {
1580
+ return entry.endpointUrl ?? entry.endpoint;
1581
+ }
1582
+ function dockerGcsRcloneMountConfig(entry, remoteName) {
1583
+ const accessId = readOptionalString(entry, 'accessId');
1584
+ const secretAccessKey = readOptionalString(entry, 'secretAccessKey');
1585
+ if (accessId && secretAccessKey) {
1586
+ return {
1587
+ remoteName,
1588
+ remotePath: joinRemotePath(entry.bucket, entry.prefix),
1589
+ configText: [
1590
+ `[${remoteName}]`,
1591
+ 'type = s3',
1592
+ 'provider = GCS',
1593
+ 'env_auth = false',
1594
+ `access_key_id = ${accessId}`,
1595
+ `secret_access_key = ${secretAccessKey}`,
1596
+ `endpoint = ${entry.endpointUrl ?? 'https://storage.googleapis.com'}`,
1597
+ ...(entry.region ? [`region = ${entry.region}`] : []),
1598
+ '',
1599
+ ].join('\n'),
1600
+ readOnly: entry.readOnly ?? true,
1601
+ };
1602
+ }
1603
+ return {
1604
+ remoteName,
1605
+ remotePath: joinRemotePath(entry.bucket, entry.prefix),
1606
+ configText: [
1607
+ `[${remoteName}]`,
1608
+ 'type = google cloud storage',
1609
+ ...(entry.serviceAccountFile
1610
+ ? [`service_account_file = ${entry.serviceAccountFile}`]
1611
+ : []),
1612
+ ...(entry.serviceAccountCredentials
1613
+ ? [`service_account_credentials = ${entry.serviceAccountCredentials}`]
1614
+ : []),
1615
+ ...(entry.accessToken ? [`access_token = ${entry.accessToken}`] : []),
1616
+ entry.serviceAccountFile ||
1617
+ entry.serviceAccountCredentials ||
1618
+ entry.accessToken
1619
+ ? 'env_auth = false'
1620
+ : 'env_auth = true',
1621
+ '',
1622
+ ].join('\n'),
1623
+ readOnly: entry.readOnly ?? true,
1624
+ };
1625
+ }
1626
+ function dockerInContainerMountPrivilegeArgs(manifest) {
1627
+ const privilege = dockerInContainerMountPrivilege(manifest);
1628
+ if (privilege === 'fuse') {
1629
+ return [
1630
+ '--device',
1631
+ '/dev/fuse',
1632
+ '--cap-add',
1633
+ 'SYS_ADMIN',
1634
+ '--security-opt',
1635
+ 'apparmor:unconfined',
1636
+ ];
1637
+ }
1638
+ if (privilege === 'sys_admin') {
1639
+ return ['--cap-add', 'SYS_ADMIN', '--security-opt', 'apparmor:unconfined'];
1640
+ }
1641
+ return [];
1642
+ }
1643
+ function dockerInContainerMountPrivilege(manifest) {
1644
+ let needsSysAdmin = false;
1645
+ for (const { entry } of manifest.mountTargetsForMaterialization()) {
1646
+ if (!isDockerInContainerMount(entry)) {
1647
+ continue;
1648
+ }
1649
+ const pattern = dockerInContainerMountPattern(entry);
1650
+ if (pattern.type === 'fuse' ||
1651
+ pattern.type === 'mountpoint' ||
1652
+ (pattern.type === 'rclone' && (pattern.mode ?? 'fuse') === 'fuse')) {
1653
+ return 'fuse';
1654
+ }
1655
+ if (pattern.type === 's3files' ||
1656
+ (pattern.type === 'rclone' && pattern.mode === 'nfs')) {
1657
+ needsSysAdmin = true;
1658
+ }
1659
+ }
1660
+ return needsSysAdmin ? 'sys_admin' : 'none';
1661
+ }
1662
+ function s3CredentialLines(entry) {
1663
+ const lines = [];
1664
+ if (entry.accessKeyId && entry.secretAccessKey) {
1665
+ lines.push('env_auth = false');
1666
+ lines.push(`access_key_id = ${entry.accessKeyId}`);
1667
+ lines.push(`secret_access_key = ${entry.secretAccessKey}`);
1668
+ if (entry.sessionToken) {
1669
+ lines.push(`session_token = ${entry.sessionToken}`);
1670
+ }
1671
+ }
1672
+ else {
1673
+ lines.push('env_auth = true');
1674
+ }
1675
+ return lines;
1676
+ }
1677
+ function validateCredentialPair(provider, mountType, accessKeyId, secretAccessKey) {
1678
+ if (Boolean(accessKeyId) !== Boolean(secretAccessKey)) {
1679
+ throw new SandboxMountError(`${provider} mounts require both accessKeyId and secretAccessKey when either is provided.`, { mountType }, 'mount_config_invalid');
1680
+ }
1681
+ }
1682
+ function dockerMountpointAwsEnv(accessKeyId, secretAccessKey, sessionToken) {
1683
+ if (!accessKeyId || !secretAccessKey) {
1684
+ return '';
1685
+ }
1686
+ return [
1687
+ `AWS_ACCESS_KEY_ID=${shellQuote(accessKeyId)}`,
1688
+ `AWS_SECRET_ACCESS_KEY=${shellQuote(secretAccessKey)}`,
1689
+ ...(sessionToken ? [`AWS_SESSION_TOKEN=${shellQuote(sessionToken)}`] : []),
1690
+ '',
1691
+ ].join('\n');
1692
+ }
1693
+ function splitDockerNfsAddr(value) {
1694
+ const index = value.lastIndexOf(':');
1695
+ if (index < 0) {
1696
+ return [
1697
+ value === '0.0.0.0' || value === '::' ? '127.0.0.1' : value,
1698
+ '2049',
1699
+ ];
1700
+ }
1701
+ const host = value.slice(0, index);
1702
+ return [
1703
+ host === '0.0.0.0' || host === '::' ? '127.0.0.1' : host,
1704
+ value.slice(index + 1),
1705
+ ];
1706
+ }
1707
+ function dockerMountpointPatternOptions(pattern) {
1708
+ const options = readRecord(pattern.options);
1709
+ return {
1710
+ prefix: readOptionalString(options, 'prefix'),
1711
+ region: readOptionalString(options, 'region'),
1712
+ endpointUrl: readOptionalString(options, 'endpointUrl'),
1713
+ };
1714
+ }
1715
+ function dockerS3FilesPatternOptions(pattern) {
1716
+ const options = readRecord(pattern.options);
1717
+ return {
1718
+ mountTargetIp: readOptionalString(options, 'mountTargetIp'),
1719
+ accessPoint: readOptionalString(options, 'accessPoint'),
1720
+ region: readOptionalString(options, 'region'),
1721
+ extraOptions: readStringNullRecord(options.extraOptions),
1722
+ };
1723
+ }
1724
+ function dockerRcloneRemoteName(entry, pattern, mountPath) {
1725
+ const remoteName = rclonePatternString(pattern, 'remoteName') ??
1726
+ rclonePatternString(pattern, 'remote');
1727
+ if (!remoteName) {
1728
+ return `sandbox_${sanitizeDockerMountName(entry.type)}_${dockerPathHash(mountPath)}`;
1729
+ }
1730
+ if (!/^[A-Za-z0-9_-]+$/u.test(remoteName)) {
1731
+ throw new SandboxMountError('DockerSandboxClient rclone mounts require remoteName to contain only letters, numbers, underscores, and hyphens.', {
1732
+ mountType: entry.type,
1733
+ remoteName,
1734
+ }, 'mount_config_invalid');
1735
+ }
1736
+ return remoteName;
1737
+ }
1738
+ function resolveDockerRcloneConfigPath(manifest, configFilePath) {
1739
+ return new WorkspacePathPolicy({
1740
+ root: manifest.root,
1741
+ extraPathGrants: manifest.extraPathGrants,
1742
+ }).resolve(configFilePath).path;
1743
+ }
1744
+ function supplementDockerRcloneConfigText(configText, remoteName, requiredConfigText, mountType) {
1745
+ const escapedRemote = remoteName.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
1746
+ const sectionPattern = new RegExp(`^\\s*\\[${escapedRemote}\\]\\s*$`, 'mu');
1747
+ const match = sectionPattern.exec(configText);
1748
+ if (!match) {
1749
+ throw new SandboxMountError('DockerSandboxClient rclone config file is missing the required remote section.', {
1750
+ mountType,
1751
+ remoteName,
1752
+ }, 'mount_config_invalid');
1753
+ }
1754
+ const sectionStart = match.index;
1755
+ const sectionEnd = match.index + match[0].length;
1756
+ const nextSection = /^\s*\[.+\]\s*$/mu.exec(configText.slice(sectionEnd));
1757
+ const sectionBodyEnd = nextSection
1758
+ ? sectionEnd + nextSection.index
1759
+ : configText.length;
1760
+ const before = configText.slice(0, sectionStart);
1761
+ const sectionBody = configText.slice(sectionStart, sectionBodyEnd).trimEnd();
1762
+ const after = configText.slice(sectionBodyEnd);
1763
+ const requiredLines = requiredConfigText.trimEnd().split('\n').slice(1);
1764
+ const supplement = requiredLines.length > 0 ? `\n${requiredLines.join('\n')}` : '';
1765
+ return `${before}${sectionBody}${supplement}\n${after}`;
1766
+ }
1767
+ function dockerRclonePatternArgs(pattern) {
1768
+ return [
1769
+ ...(rclonePatternStringArray(pattern, 'args') ?? []),
1770
+ ...(rclonePatternStringArray(pattern, 'extraArgs') ?? []),
1771
+ ];
1772
+ }
1773
+ function rclonePatternString(pattern, key) {
1774
+ return readOptionalString(pattern, key);
1775
+ }
1776
+ function rclonePatternStringArray(pattern, key) {
1777
+ const value = readStringArray(pattern[key]);
1778
+ return value.length > 0 ? value : undefined;
1779
+ }
1780
+ function readRecord(value) {
1781
+ return value && typeof value === 'object' && !Array.isArray(value)
1782
+ ? value
1783
+ : {};
1784
+ }
1785
+ function readStringNullRecord(value) {
1786
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
1787
+ return {};
1788
+ }
1789
+ return Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === 'string' || entry[1] === null));
1790
+ }
1791
+ function dockerMountOptions(options) {
1792
+ return Object.entries(options)
1793
+ .map(([key, value]) => (value === null ? key : `${key}=${value}`))
1794
+ .join(',');
1795
+ }
1796
+ function shellCommand(parts) {
1797
+ return parts.map((part) => shellQuote(part)).join(' ');
1798
+ }
1799
+ function sanitizeDockerMountName(value) {
1800
+ return value.replace(/[^A-Za-z0-9_-]/gu, '_');
1801
+ }
1802
+ function dockerPathHash(path) {
1803
+ return createHash('sha256').update(path).digest('hex').slice(0, 12);
1804
+ }
1805
+ function dockerPosixDirname(path) {
1806
+ const index = path.lastIndexOf('/');
1807
+ if (index <= 0) {
1808
+ return index === 0 ? '/' : '.';
1809
+ }
1810
+ return path.slice(0, index);
1811
+ }
1812
+ function resolveDockerMountPath(manifestRoot, logicalPath, entry) {
1813
+ if (entry.mountPath?.startsWith('/')) {
1814
+ return entry.mountPath;
1815
+ }
1816
+ const mountPath = entry.mountPath ?? logicalPath;
1817
+ return joinSandboxLogicalPath(manifestRoot, mountPath);
1818
+ }
1819
+ function resolveDockerMountWorkspaceRelativePath(manifestRoot, logicalPath, entry) {
1820
+ const target = resolveDockerMountPath(manifestRoot, logicalPath, entry);
1821
+ if (target === '/') {
1822
+ throw new SandboxUnsupportedFeatureError('DockerSandboxClient does not support mounting over the container root.');
1823
+ }
1824
+ try {
1825
+ const resolved = new WorkspacePathPolicy({ root: manifestRoot }).resolve(target, { forWrite: true });
1826
+ return resolved.workspaceRelativePath ?? null;
1827
+ }
1828
+ catch {
1829
+ return null;
1830
+ }
1831
+ }
1832
+ function dockerMountArg(args) {
1833
+ return [
1834
+ dockerMountField('type', args.type),
1835
+ dockerMountField('source', args.source),
1836
+ dockerMountField('target', args.target),
1837
+ ...(args.readOnly ? ['readonly'] : []),
1838
+ ...(args.volumeDriver
1839
+ ? [dockerMountField('volume-driver', args.volumeDriver)]
1840
+ : []),
1841
+ ...Object.entries(args.volumeOptions ?? {}).map(([key, value]) => dockerMountField('volume-opt', `${key}=${value}`)),
1842
+ ].join(',');
1843
+ }
1844
+ function dockerMountField(key, value) {
1845
+ return dockerMountCsvField(`${key}=${value}`);
1846
+ }
1847
+ function dockerMountCsvField(field) {
1848
+ if (!/[",\n\r]/u.test(field)) {
1849
+ return field;
1850
+ }
1851
+ return `"${field.replace(/"/gu, '""')}"`;
1852
+ }
1853
+ function dockerVolumeName(containerName, mountPath) {
1854
+ const pathHash = createHash('sha256')
1855
+ .update(mountPath)
1856
+ .digest('hex')
1857
+ .slice(0, 12);
1858
+ const safePath = mountPath.replace(/[^A-Za-z0-9_.-]+/gu, '_').replace(/^_+|_+$/gu, '') ||
1859
+ 'workspace';
1860
+ return `${containerName}-${pathHash}-${safePath.slice(0, 80)}`;
1861
+ }
1862
+ async function removeDockerVolumes(volumeNames) {
1863
+ await Promise.all(volumeNames.map(async (volumeName) => {
1864
+ await runSandboxProcess('docker', ['volume', 'rm', '-f', volumeName], {
1865
+ timeoutMs: DOCKER_FAST_COMMAND_TIMEOUT_MS,
1866
+ }).catch(() => undefined);
1867
+ }));
1868
+ }
1869
+ function dockerRcloneS3Options(entry) {
1870
+ return withDefinedStringValues({
1871
+ type: 's3',
1872
+ 's3-provider': entry.s3Provider ?? 'AWS',
1873
+ path: joinRemotePath(entry.bucket, entry.prefix),
1874
+ 's3-access-key-id': entry.accessKeyId,
1875
+ 's3-secret-access-key': entry.secretAccessKey,
1876
+ 's3-session-token': entry.sessionToken,
1877
+ 's3-endpoint': entry.endpointUrl,
1878
+ 's3-region': entry.region,
1879
+ });
1880
+ }
1881
+ function dockerMountpointS3Options(entry) {
1882
+ return withDefinedStringValues({
1883
+ bucket: entry.bucket,
1884
+ access_key_id: entry.accessKeyId,
1885
+ secret_access_key: entry.secretAccessKey,
1886
+ session_token: entry.sessionToken,
1887
+ endpoint_url: entry.endpointUrl,
1888
+ region: entry.region,
1889
+ prefix: entry.prefix,
1890
+ });
1891
+ }
1892
+ function dockerRcloneGcsOptions(entry) {
1893
+ if (entry.accessId && entry.secretAccessKey) {
1894
+ return withDefinedStringValues({
1895
+ type: 's3',
1896
+ path: joinRemotePath(entry.bucket, entry.prefix),
1897
+ 's3-provider': 'GCS',
1898
+ 's3-access-key-id': entry.accessId,
1899
+ 's3-secret-access-key': entry.secretAccessKey,
1900
+ 's3-endpoint': entry.endpointUrl ?? 'https://storage.googleapis.com',
1901
+ 's3-region': entry.region,
1902
+ });
1903
+ }
1904
+ return withDefinedStringValues({
1905
+ type: 'google cloud storage',
1906
+ path: joinRemotePath(entry.bucket, entry.prefix),
1907
+ 'gcs-service-account-file': entry.serviceAccountFile,
1908
+ 'gcs-service-account-credentials': entry.serviceAccountCredentials,
1909
+ 'gcs-access-token': entry.accessToken,
1910
+ });
1911
+ }
1912
+ function dockerMountpointGcsOptions(entry) {
1913
+ return withDefinedStringValues({
1914
+ bucket: entry.bucket,
1915
+ endpoint_url: entry.endpointUrl ?? 'https://storage.googleapis.com',
1916
+ access_key_id: entry.accessId,
1917
+ secret_access_key: entry.secretAccessKey,
1918
+ region: entry.region,
1919
+ prefix: entry.prefix,
1920
+ });
1921
+ }
1922
+ function dockerRcloneR2Options(entry) {
1923
+ validateCredentialPair('R2', entry.type, entry.accessKeyId, entry.secretAccessKey);
1924
+ if (!entry.accountId) {
1925
+ throw new SandboxMountError('R2 Docker volume mounts require accountId.', { mountType: entry.type }, 'mount_config_invalid');
1926
+ }
1927
+ return withDefinedStringValues({
1928
+ type: 's3',
1929
+ path: joinRemotePath(entry.bucket, entry.prefix),
1930
+ 's3-provider': 'Cloudflare',
1931
+ 's3-endpoint': entry.customDomain ??
1932
+ `https://${entry.accountId}.r2.cloudflarestorage.com`,
1933
+ 's3-access-key-id': entry.accessKeyId,
1934
+ 's3-secret-access-key': entry.secretAccessKey,
1935
+ });
1936
+ }
1937
+ function dockerRcloneAzureBlobOptions(entry) {
1938
+ return withDefinedStringValues({
1939
+ type: 'azureblob',
1940
+ path: joinRemotePath(entry.container, entry.prefix),
1941
+ 'azureblob-account': entry.account ?? entry.accountName,
1942
+ 'azureblob-endpoint': azureBlobEndpoint(entry),
1943
+ 'azureblob-msi-client-id': entry.identityClientId,
1944
+ 'azureblob-key': entry.accountKey,
1945
+ });
1946
+ }
1947
+ function dockerRcloneBoxOptions(entry) {
1948
+ return withDefinedStringValues({
1949
+ type: 'box',
1950
+ path: normalizeBoxRemotePath(entry.path),
1951
+ 'box-client-id': entry.clientId,
1952
+ 'box-client-secret': entry.clientSecret,
1953
+ 'box-access-token': entry.accessToken,
1954
+ 'box-token': entry.token,
1955
+ 'box-box-config-file': entry.boxConfigFile,
1956
+ 'box-config-credentials': entry.configCredentials,
1957
+ 'box-box-sub-type': entry.boxSubType && entry.boxSubType !== 'user'
1958
+ ? entry.boxSubType
1959
+ : undefined,
1960
+ 'box-root-folder-id': entry.rootFolderId,
1961
+ 'box-impersonate': entry.impersonate,
1962
+ 'box-owned-by': entry.ownedBy,
1963
+ });
1964
+ }
1965
+ function joinRemotePath(base, prefix) {
1966
+ const normalizedPrefix = prefix?.replace(/^\/+|\/+$/gu, '');
1967
+ return normalizedPrefix ? `${base}/${normalizedPrefix}` : base;
1968
+ }
1969
+ function normalizeBoxRemotePath(path) {
1970
+ return path?.replace(/^\/+/gu, '') ?? '';
1971
+ }
1972
+ function withDefinedStringValues(values) {
1973
+ return Object.fromEntries(Object.entries(values).filter((entry) => typeof entry[1] === 'string'));
1974
+ }
1975
+ function parseDockerPortBinding(stdout, containerPort) {
1976
+ const line = stdout
1977
+ .split(/\r?\n/u)
1978
+ .map((value) => value.trim())
1979
+ .find(Boolean);
1980
+ if (!line) {
1981
+ throw new UserError(`Docker did not report a host binding for exposed port ${containerPort}.`);
1982
+ }
1983
+ const bracketMatch = line.match(/^\[([^\]]+)\]:(\d+)$/u);
1984
+ const match = bracketMatch ?? line.match(/^(.+):(\d+)$/u);
1985
+ if (!match) {
1986
+ throw new UserError(`Docker reported an unrecognized host binding for exposed port ${containerPort}: ${line}`);
1987
+ }
1988
+ const rawHost = match[1];
1989
+ return {
1990
+ host: normalizeDockerBindingHost(rawHost),
1991
+ port: normalizeExposedPort(Number(match[2])),
1992
+ };
1993
+ }
1994
+ function normalizeDockerBindingHost(host) {
1995
+ if (host === '0.0.0.0') {
1996
+ return '127.0.0.1';
1997
+ }
1998
+ if (host === '::' || host === '') {
1999
+ return '::1';
2000
+ }
2001
+ return host;
2002
+ }
2003
+ function getHostDockerUser() {
2004
+ if (typeof process.getuid !== 'function' ||
2005
+ typeof process.getgid !== 'function') {
2006
+ return undefined;
2007
+ }
2008
+ return `${process.getuid()}:${process.getgid()}`;
2009
+ }
2010
+ //# sourceMappingURL=docker.mjs.map