@animalabs/agent-framework 0.23.2 → 0.24.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 (338) hide show
  1. package/README.md +212 -42
  2. package/dist/src/agent.d.ts +60 -9
  3. package/dist/src/agent.d.ts.map +1 -1
  4. package/dist/src/agent.js +92 -12
  5. package/dist/src/agent.js.map +1 -1
  6. package/dist/src/api/server.d.ts.map +1 -1
  7. package/dist/src/api/server.js +14 -12
  8. package/dist/src/api/server.js.map +1 -1
  9. package/dist/src/code-execution/engine.d.ts +177 -0
  10. package/dist/src/code-execution/engine.d.ts.map +1 -0
  11. package/dist/src/code-execution/engine.js +494 -0
  12. package/dist/src/code-execution/engine.js.map +1 -0
  13. package/dist/src/code-execution/index.d.ts +5 -10
  14. package/dist/src/code-execution/index.d.ts.map +1 -1
  15. package/dist/src/code-execution/index.js +5 -9
  16. package/dist/src/code-execution/index.js.map +1 -1
  17. package/dist/src/code-execution/isolate/calls.d.ts +114 -0
  18. package/dist/src/code-execution/isolate/calls.d.ts.map +1 -0
  19. package/dist/src/code-execution/isolate/calls.js +145 -0
  20. package/dist/src/code-execution/isolate/calls.js.map +1 -0
  21. package/dist/src/code-execution/isolate/child-entry.d.ts +2 -0
  22. package/dist/src/code-execution/isolate/child-entry.d.ts.map +1 -0
  23. package/dist/src/code-execution/isolate/child-entry.js +5 -0
  24. package/dist/src/code-execution/isolate/child-entry.js.map +1 -0
  25. package/dist/src/code-execution/isolate/child-main.d.ts +26 -0
  26. package/dist/src/code-execution/isolate/child-main.d.ts.map +1 -0
  27. package/dist/src/code-execution/isolate/child-main.js +132 -0
  28. package/dist/src/code-execution/isolate/child-main.js.map +1 -0
  29. package/dist/src/code-execution/isolate/child.d.ts +44 -0
  30. package/dist/src/code-execution/isolate/child.d.ts.map +1 -0
  31. package/dist/src/code-execution/isolate/child.js +315 -0
  32. package/dist/src/code-execution/isolate/child.js.map +1 -0
  33. package/dist/src/code-execution/isolate/frames.d.ts +80 -0
  34. package/dist/src/code-execution/isolate/frames.d.ts.map +1 -0
  35. package/dist/src/code-execution/isolate/frames.js +210 -0
  36. package/dist/src/code-execution/isolate/frames.js.map +1 -0
  37. package/dist/src/code-execution/isolate/output.d.ts +68 -0
  38. package/dist/src/code-execution/isolate/output.d.ts.map +1 -0
  39. package/dist/src/code-execution/isolate/output.js +143 -0
  40. package/dist/src/code-execution/isolate/output.js.map +1 -0
  41. package/dist/src/code-execution/isolate/prelude.d.ts +109 -0
  42. package/dist/src/code-execution/isolate/prelude.d.ts.map +1 -0
  43. package/dist/src/code-execution/isolate/prelude.js +401 -0
  44. package/dist/src/code-execution/isolate/prelude.js.map +1 -0
  45. package/dist/src/code-execution/isolate/protocol.d.ts +151 -0
  46. package/dist/src/code-execution/isolate/protocol.d.ts.map +1 -0
  47. package/dist/src/code-execution/isolate/protocol.js +150 -0
  48. package/dist/src/code-execution/isolate/protocol.js.map +1 -0
  49. package/dist/src/code-execution/isolate/render.d.ts +57 -0
  50. package/dist/src/code-execution/isolate/render.d.ts.map +1 -0
  51. package/dist/src/code-execution/isolate/render.js +214 -0
  52. package/dist/src/code-execution/isolate/render.js.map +1 -0
  53. package/dist/src/code-execution/isolate/runner.d.ts +250 -0
  54. package/dist/src/code-execution/isolate/runner.d.ts.map +1 -0
  55. package/dist/src/code-execution/isolate/runner.js +854 -0
  56. package/dist/src/code-execution/isolate/runner.js.map +1 -0
  57. package/dist/src/code-execution/isolate/scripts.d.ts +175 -0
  58. package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -0
  59. package/dist/src/code-execution/isolate/scripts.js +357 -0
  60. package/dist/src/code-execution/isolate/scripts.js.map +1 -0
  61. package/dist/src/code-execution/quickjs.wasm +0 -0
  62. package/dist/src/code-execution/tool-definition.d.ts +93 -43
  63. package/dist/src/code-execution/tool-definition.d.ts.map +1 -1
  64. package/dist/src/code-execution/tool-definition.js +176 -142
  65. package/dist/src/code-execution/tool-definition.js.map +1 -1
  66. package/dist/src/deadline-timer.d.ts +3 -0
  67. package/dist/src/deadline-timer.d.ts.map +1 -1
  68. package/dist/src/deadline-timer.js +1 -1
  69. package/dist/src/deadline-timer.js.map +1 -1
  70. package/dist/src/framework.d.ts +1027 -440
  71. package/dist/src/framework.d.ts.map +1 -1
  72. package/dist/src/framework.js +4093 -2238
  73. package/dist/src/framework.js.map +1 -1
  74. package/dist/src/gate/event-gate.d.ts +28 -1
  75. package/dist/src/gate/event-gate.d.ts.map +1 -1
  76. package/dist/src/gate/event-gate.js +88 -10
  77. package/dist/src/gate/event-gate.js.map +1 -1
  78. package/dist/src/held-writes.d.ts +16 -3
  79. package/dist/src/held-writes.d.ts.map +1 -1
  80. package/dist/src/held-writes.js +16 -3
  81. package/dist/src/held-writes.js.map +1 -1
  82. package/dist/src/index.d.ts +6 -6
  83. package/dist/src/index.d.ts.map +1 -1
  84. package/dist/src/index.js +9 -8
  85. package/dist/src/index.js.map +1 -1
  86. package/dist/src/mcpl/channel-registry.d.ts +471 -75
  87. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  88. package/dist/src/mcpl/channel-registry.js +1673 -337
  89. package/dist/src/mcpl/channel-registry.js.map +1 -1
  90. package/dist/src/mcpl/conversation-router.d.ts +12 -7
  91. package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
  92. package/dist/src/mcpl/conversation-router.js +12 -7
  93. package/dist/src/mcpl/conversation-router.js.map +1 -1
  94. package/dist/src/mcpl/convert-block.d.ts.map +1 -1
  95. package/dist/src/mcpl/convert-block.js +9 -5
  96. package/dist/src/mcpl/convert-block.js.map +1 -1
  97. package/dist/src/mcpl/inbound-source.d.ts +50 -0
  98. package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
  99. package/dist/src/mcpl/inbound-source.js +130 -0
  100. package/dist/src/mcpl/inbound-source.js.map +1 -1
  101. package/dist/src/mcpl/index.d.ts +2 -1
  102. package/dist/src/mcpl/index.d.ts.map +1 -1
  103. package/dist/src/mcpl/index.js +3 -2
  104. package/dist/src/mcpl/index.js.map +1 -1
  105. package/dist/src/mcpl/log-value.d.ts +6 -0
  106. package/dist/src/mcpl/log-value.d.ts.map +1 -1
  107. package/dist/src/mcpl/log-value.js +8 -0
  108. package/dist/src/mcpl/log-value.js.map +1 -1
  109. package/dist/src/mcpl/modern-connection.d.ts +15 -6
  110. package/dist/src/mcpl/modern-connection.d.ts.map +1 -1
  111. package/dist/src/mcpl/modern-connection.js +168 -53
  112. package/dist/src/mcpl/modern-connection.js.map +1 -1
  113. package/dist/src/mcpl/prose-grammar.d.ts +4 -5
  114. package/dist/src/mcpl/prose-grammar.d.ts.map +1 -1
  115. package/dist/src/mcpl/prose-grammar.js +4 -5
  116. package/dist/src/mcpl/prose-grammar.js.map +1 -1
  117. package/dist/src/mcpl/protocol-family.d.ts +97 -10
  118. package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
  119. package/dist/src/mcpl/protocol-family.js +173 -16
  120. package/dist/src/mcpl/protocol-family.js.map +1 -1
  121. package/dist/src/mcpl/push-coalescer.d.ts +132 -14
  122. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  123. package/dist/src/mcpl/push-coalescer.js +227 -50
  124. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  125. package/dist/src/mcpl/push-handler.d.ts +6 -1
  126. package/dist/src/mcpl/push-handler.d.ts.map +1 -1
  127. package/dist/src/mcpl/push-handler.js +4 -1
  128. package/dist/src/mcpl/push-handler.js.map +1 -1
  129. package/dist/src/mcpl/reference-fetcher.d.ts +2 -2
  130. package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
  131. package/dist/src/mcpl/reference-fetcher.js +4 -4
  132. package/dist/src/mcpl/reference-fetcher.js.map +1 -1
  133. package/dist/src/mcpl/server-connection.d.ts +50 -21
  134. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  135. package/dist/src/mcpl/server-connection.js +158 -74
  136. package/dist/src/mcpl/server-connection.js.map +1 -1
  137. package/dist/src/mcpl/tags.d.ts +24 -0
  138. package/dist/src/mcpl/tags.d.ts.map +1 -1
  139. package/dist/src/mcpl/tags.js +33 -0
  140. package/dist/src/mcpl/tags.js.map +1 -1
  141. package/dist/src/mcpl/tool-result-normalize.d.ts +2 -1
  142. package/dist/src/mcpl/tool-result-normalize.d.ts.map +1 -1
  143. package/dist/src/mcpl/tool-result-normalize.js.map +1 -1
  144. package/dist/src/mcpl/transport.d.ts +55 -7
  145. package/dist/src/mcpl/transport.d.ts.map +1 -1
  146. package/dist/src/mcpl/transport.js +163 -34
  147. package/dist/src/mcpl/transport.js.map +1 -1
  148. package/dist/src/mcpl/types.d.ts +22 -6
  149. package/dist/src/mcpl/types.d.ts.map +1 -1
  150. package/dist/src/mcpl/types.js.map +1 -1
  151. package/dist/src/module-registry.d.ts +4 -2
  152. package/dist/src/module-registry.d.ts.map +1 -1
  153. package/dist/src/module-registry.js +100 -13
  154. package/dist/src/module-registry.js.map +1 -1
  155. package/dist/src/modules/discord/index.d.ts.map +1 -1
  156. package/dist/src/modules/discord/index.js +8 -13
  157. package/dist/src/modules/discord/index.js.map +1 -1
  158. package/dist/src/modules/history/index.d.ts.map +1 -1
  159. package/dist/src/modules/history/index.js +27 -4
  160. package/dist/src/modules/history/index.js.map +1 -1
  161. package/dist/src/modules/history/semantic.d.ts +15 -3
  162. package/dist/src/modules/history/semantic.d.ts.map +1 -1
  163. package/dist/src/modules/history/semantic.js +27 -5
  164. package/dist/src/modules/history/semantic.js.map +1 -1
  165. package/dist/src/modules/index.d.ts +1 -1
  166. package/dist/src/modules/index.d.ts.map +1 -1
  167. package/dist/src/modules/index.js.map +1 -1
  168. package/dist/src/modules/unknown-tool.d.ts +4 -1
  169. package/dist/src/modules/unknown-tool.d.ts.map +1 -1
  170. package/dist/src/modules/unknown-tool.js +4 -1
  171. package/dist/src/modules/unknown-tool.js.map +1 -1
  172. package/dist/src/modules/workspace/branch-intent.d.ts +3 -1
  173. package/dist/src/modules/workspace/branch-intent.d.ts.map +1 -1
  174. package/dist/src/modules/workspace/branch-intent.js +3 -1
  175. package/dist/src/modules/workspace/branch-intent.js.map +1 -1
  176. package/dist/src/modules/workspace/edit-plan.d.ts +265 -0
  177. package/dist/src/modules/workspace/edit-plan.d.ts.map +1 -0
  178. package/dist/src/modules/workspace/edit-plan.js +794 -0
  179. package/dist/src/modules/workspace/edit-plan.js.map +1 -0
  180. package/dist/src/modules/workspace/edit-text.d.ts +117 -0
  181. package/dist/src/modules/workspace/edit-text.d.ts.map +1 -0
  182. package/dist/src/modules/workspace/edit-text.js +584 -0
  183. package/dist/src/modules/workspace/edit-text.js.map +1 -0
  184. package/dist/src/modules/workspace/effects.d.ts +5 -4
  185. package/dist/src/modules/workspace/effects.d.ts.map +1 -1
  186. package/dist/src/modules/workspace/effects.js +78 -25
  187. package/dist/src/modules/workspace/effects.js.map +1 -1
  188. package/dist/src/modules/workspace/index.d.ts +484 -61
  189. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  190. package/dist/src/modules/workspace/index.js +2173 -645
  191. package/dist/src/modules/workspace/index.js.map +1 -1
  192. package/dist/src/modules/workspace/observe.d.ts +213 -38
  193. package/dist/src/modules/workspace/observe.d.ts.map +1 -1
  194. package/dist/src/modules/workspace/observe.js +416 -36
  195. package/dist/src/modules/workspace/observe.js.map +1 -1
  196. package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
  197. package/dist/src/modules/workspace/reconcile.js +15 -8
  198. package/dist/src/modules/workspace/reconcile.js.map +1 -1
  199. package/dist/src/modules/workspace/text.d.ts +32 -0
  200. package/dist/src/modules/workspace/text.d.ts.map +1 -0
  201. package/dist/src/modules/workspace/text.js +67 -0
  202. package/dist/src/modules/workspace/text.js.map +1 -0
  203. package/dist/src/modules/workspace/types.d.ts +210 -6
  204. package/dist/src/modules/workspace/types.d.ts.map +1 -1
  205. package/dist/src/modules/workspace/types.js.map +1 -1
  206. package/dist/src/modules/workspace/watcher.d.ts +3 -2
  207. package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
  208. package/dist/src/modules/workspace/watcher.js.map +1 -1
  209. package/dist/src/operator-log.d.ts +52 -24
  210. package/dist/src/operator-log.d.ts.map +1 -1
  211. package/dist/src/operator-log.js.map +1 -1
  212. package/dist/src/recovery/discord-awareness-outbox.d.ts +496 -72
  213. package/dist/src/recovery/discord-awareness-outbox.d.ts.map +1 -1
  214. package/dist/src/recovery/discord-awareness-outbox.js +1196 -289
  215. package/dist/src/recovery/discord-awareness-outbox.js.map +1 -1
  216. package/dist/src/recovery/offline-branch.d.ts +26 -1
  217. package/dist/src/recovery/offline-branch.d.ts.map +1 -1
  218. package/dist/src/recovery/offline-branch.js +164 -39
  219. package/dist/src/recovery/offline-branch.js.map +1 -1
  220. package/dist/src/recovery/recover-cli.js +23 -4
  221. package/dist/src/recovery/recover-cli.js.map +1 -1
  222. package/dist/src/send-tools.d.ts +11 -0
  223. package/dist/src/send-tools.d.ts.map +1 -0
  224. package/dist/src/send-tools.js +11 -0
  225. package/dist/src/send-tools.js.map +1 -0
  226. package/dist/src/tool-result-guard.d.ts +4 -3
  227. package/dist/src/tool-result-guard.d.ts.map +1 -1
  228. package/dist/src/tool-result-guard.js +5 -4
  229. package/dist/src/tool-result-guard.js.map +1 -1
  230. package/dist/src/tool-result-history.d.ts +1 -1
  231. package/dist/src/tool-result-history.js +1 -1
  232. package/dist/src/tune-out/coordinator.d.ts +21 -0
  233. package/dist/src/tune-out/coordinator.d.ts.map +1 -1
  234. package/dist/src/tune-out/coordinator.js +139 -37
  235. package/dist/src/tune-out/coordinator.js.map +1 -1
  236. package/dist/src/types/agent.d.ts +4 -0
  237. package/dist/src/types/agent.d.ts.map +1 -1
  238. package/dist/src/types/events.d.ts +62 -9
  239. package/dist/src/types/events.d.ts.map +1 -1
  240. package/dist/src/types/framework.d.ts +62 -58
  241. package/dist/src/types/framework.d.ts.map +1 -1
  242. package/dist/src/types/module.d.ts +40 -8
  243. package/dist/src/types/module.d.ts.map +1 -1
  244. package/dist/src/types/trace.d.ts +111 -10
  245. package/dist/src/types/trace.d.ts.map +1 -1
  246. package/dist/src/usage/types.d.ts +16 -0
  247. package/dist/src/usage/types.d.ts.map +1 -1
  248. package/dist/src/usage/usage-tracker.d.ts +14 -1
  249. package/dist/src/usage/usage-tracker.d.ts.map +1 -1
  250. package/dist/src/usage/usage-tracker.js +81 -41
  251. package/dist/src/usage/usage-tracker.js.map +1 -1
  252. package/package.json +6 -5
  253. package/src/agent.ts +93 -12
  254. package/src/api/server.ts +14 -12
  255. package/src/code-execution/engine.ts +595 -0
  256. package/src/code-execution/index.ts +5 -17
  257. package/src/code-execution/isolate/calls.ts +199 -0
  258. package/src/code-execution/isolate/child-entry.ts +5 -0
  259. package/src/code-execution/isolate/child-main.ts +132 -0
  260. package/src/code-execution/isolate/child.ts +327 -0
  261. package/src/code-execution/isolate/frames.ts +209 -0
  262. package/src/code-execution/isolate/output.ts +161 -0
  263. package/src/code-execution/isolate/prelude.ts +409 -0
  264. package/src/code-execution/isolate/protocol.ts +240 -0
  265. package/src/code-execution/isolate/render.ts +230 -0
  266. package/src/code-execution/isolate/runner.ts +911 -0
  267. package/src/code-execution/isolate/scripts.ts +479 -0
  268. package/src/code-execution/quickjs.wasm +0 -0
  269. package/src/code-execution/quickjs.wasm.LICENSES +149 -0
  270. package/src/code-execution/tool-definition.ts +236 -167
  271. package/src/deadline-timer.ts +1 -1
  272. package/src/framework.ts +4468 -2418
  273. package/src/gate/event-gate.ts +78 -11
  274. package/src/held-writes.ts +17 -3
  275. package/src/index.ts +30 -22
  276. package/src/mcpl/channel-registry.ts +1825 -368
  277. package/src/mcpl/conversation-router.ts +12 -7
  278. package/src/mcpl/convert-block.ts +9 -5
  279. package/src/mcpl/inbound-source.ts +131 -0
  280. package/src/mcpl/index.ts +4 -2
  281. package/src/mcpl/log-value.ts +9 -0
  282. package/src/mcpl/modern-connection.ts +162 -53
  283. package/src/mcpl/prose-grammar.ts +4 -5
  284. package/src/mcpl/protocol-family.ts +199 -21
  285. package/src/mcpl/push-coalescer.ts +306 -62
  286. package/src/mcpl/push-handler.ts +10 -2
  287. package/src/mcpl/reference-fetcher.ts +4 -4
  288. package/src/mcpl/server-connection.ts +180 -79
  289. package/src/mcpl/tags.ts +36 -0
  290. package/src/mcpl/tool-result-normalize.ts +2 -1
  291. package/src/mcpl/transport.ts +162 -33
  292. package/src/mcpl/types.ts +23 -6
  293. package/src/module-registry.ts +92 -13
  294. package/src/modules/discord/index.ts +8 -13
  295. package/src/modules/history/index.ts +25 -3
  296. package/src/modules/history/semantic.ts +29 -6
  297. package/src/modules/index.ts +1 -0
  298. package/src/modules/unknown-tool.ts +4 -1
  299. package/src/modules/workspace/branch-intent.ts +3 -1
  300. package/src/modules/workspace/edit-plan.ts +954 -0
  301. package/src/modules/workspace/edit-text.ts +618 -0
  302. package/src/modules/workspace/effects.ts +70 -19
  303. package/src/modules/workspace/index.ts +2247 -642
  304. package/src/modules/workspace/observe.ts +465 -56
  305. package/src/modules/workspace/reconcile.ts +14 -7
  306. package/src/modules/workspace/text.ts +68 -0
  307. package/src/modules/workspace/types.ts +211 -6
  308. package/src/modules/workspace/watcher.ts +3 -2
  309. package/src/operator-log.ts +47 -19
  310. package/src/recovery/discord-awareness-outbox.ts +1500 -356
  311. package/src/recovery/offline-branch.ts +189 -55
  312. package/src/recovery/recover-cli.ts +25 -4
  313. package/src/send-tools.ts +11 -0
  314. package/src/tool-result-guard.ts +6 -5
  315. package/src/tool-result-history.ts +1 -1
  316. package/src/tune-out/coordinator.ts +163 -42
  317. package/src/types/agent.ts +4 -0
  318. package/src/types/events.ts +62 -9
  319. package/src/types/framework.ts +62 -58
  320. package/src/types/module.ts +40 -8
  321. package/src/types/trace.ts +121 -15
  322. package/src/usage/types.ts +16 -0
  323. package/src/usage/usage-tracker.ts +86 -43
  324. package/dist/src/code-execution/py-runner.d.ts +0 -191
  325. package/dist/src/code-execution/py-runner.d.ts.map +0 -1
  326. package/dist/src/code-execution/py-runner.js +0 -655
  327. package/dist/src/code-execution/py-runner.js.map +0 -1
  328. package/dist/src/code-execution/runtime-py.d.ts +0 -50
  329. package/dist/src/code-execution/runtime-py.d.ts.map +0 -1
  330. package/dist/src/code-execution/runtime-py.js +0 -677
  331. package/dist/src/code-execution/runtime-py.js.map +0 -1
  332. package/dist/src/mcpl/prose-stream-router.d.ts +0 -88
  333. package/dist/src/mcpl/prose-stream-router.d.ts.map +0 -1
  334. package/dist/src/mcpl/prose-stream-router.js +0 -291
  335. package/dist/src/mcpl/prose-stream-router.js.map +0 -1
  336. package/src/code-execution/py-runner.ts +0 -791
  337. package/src/code-execution/runtime-py.ts +0 -677
  338. package/src/mcpl/prose-stream-router.ts +0 -268
@@ -1,104 +1,212 @@
1
1
  /**
2
- * Synthesized `code_execution` tool definition.
3
- *
4
- * The name, input shape ({code}), and description framing deliberately track
5
- * Anthropic's server-side programmatic tool calling so models' trained
6
- * priors transfer: python scripts, tools as async functions taking a single
7
- * dict and returning a string, stdout as the return channel, persistent
8
- * interpreter state, asyncio.gather for fan-out.
2
+ * `code_execution`: its configuration, the check that refuses one that still
3
+ * sets what the Python runner took, and the tool's definition, the text an
4
+ * agent reads before it writes a script. It follows the surface of Code Mode
5
+ * tools elsewhere (exact names, one result shape, top-level await), and says
6
+ * each rule once.
9
7
  */
10
8
 
11
- import type { ToolDefinition } from '../types/index.js';
12
- import { assertBooleanSetting, holdsSettings, kindOf } from '../config-checks.js';
13
- import { formatLimit, MAX_TIMER_MS } from './py-runner.js';
9
+ import type { CodeExecutionConfig, ToolDefinition } from '../types/index.js';
10
+ import { assertBooleanSetting, holdsSettings } from '../config-checks.js';
11
+ import { MAX_MEMORY_BYTES, MAX_RESULT_DEPTH, MIN_MEMORY_BYTES } from './engine.js';
12
+ import { DEFAULT_OUTPUT_BOUNDS } from './isolate/output.js';
13
+ import { MAX_ARGUMENT_BYTES, MAX_ARGUMENT_VALUES, MAX_CALLS_IN_FLIGHT, MAX_WAKE_PAYLOAD_BYTES } from './isolate/protocol.js';
14
+ import { formatMs, VALUE_CHARS } from './isolate/render.js';
15
+
16
+ const PAGE_BYTES = 64 * 1024;
14
17
 
15
18
  export const CODE_EXECUTION_TOOL_NAME = 'code_execution';
16
19
 
17
- export function buildCodeExecutionToolDefinition(opts?: {
18
- idleReclaimMs?: number;
19
- toolCallTimeoutMs?: number;
20
- scriptTimeoutMs?: number;
21
- maxScriptTimeoutMs?: number;
22
- backgroundMaxLifetimeMs?: number;
23
- }): ToolDefinition {
24
- const idleMinutes = Math.max(1, Math.round((opts?.idleReclaimMs ?? 300_000) / 60_000));
25
- const callTimeoutSeconds = Math.round((opts?.toolCallTimeoutMs ?? 270_000) / 1000);
26
- const limits = scriptTimeLimits(opts);
20
+ /** The isolate's settings. Each has a default. */
21
+ export interface IsolateSettings {
22
+ /** The bound a run or wait yields at, unless the agent asks otherwise. */
23
+ yieldMs: number;
24
+ /** The most an agent may ask for. */
25
+ maxYieldMs: number;
26
+ /** A script's limit: there it's stopped. */
27
+ scriptLifetimeMs: number;
28
+ /** Scripts one agent may have running at once, yielded ones included. */
29
+ maxRunningScripts: number;
30
+ /** Script children a host runs at once. */
31
+ maxScriptChildren: number;
32
+ /** How long a call may go unanswered before the script is told it timed out. */
33
+ toolCallTimeoutMs: number;
34
+ wakeMinIntervalMs: number;
35
+ maxWakesPerScript: number;
36
+ /**
37
+ * The WebAssembly memory's maximum for each script: its budget, a whole
38
+ * number of 64 KiB pages from 4 MiB to 2 GiB. A child stays within about its
39
+ * runtime's base plus 2.7 times this: the linear memory itself, and a result
40
+ * in transit, which takes two to five times its size and is refused past a
41
+ * third of this. Beside that come the script's calls as they go out, held
42
+ * while a result settles or not: at most 16 at once, the calls the prelude
43
+ * lets out, each with at most 4 MiB of JSON arguments, which the child holds
44
+ * as text and encodes, up to about 200 MiB under Node and 410 MiB under
45
+ * Bun. Measured on the child under Node 24.22.0 and Bun 1.4.3, whose base
46
+ * was about 85 and 55 MiB, at budgets from 32 MiB to 1 GiB.
47
+ *
48
+ * The host holds, for each script, the calls it has out and their answers:
49
+ * at most 16 calls out in the host at once, timed-out ones whose tools still
50
+ * run included, each with at most 4 MiB of JSON arguments holding 100,000
51
+ * values (about 25 MiB as text and parsed, by a probe of the costliest
52
+ * shapes under Node 24 and Bun 1.4), up to 16 more waiting for those as
53
+ * their text (at most 8 MiB each), up to 16 answers until the child takes
54
+ * them, each at most a third of this as its frame, and the one being
55
+ * encoded also as its JSON text (up to twice its bytes), the frame from the
56
+ * child being read (at most 25 MiB, and its text and its parse up to twice
57
+ * that each, since a string can take two bytes a character: 125 MiB), and
58
+ * its waiting wakes, at most 64 KiB of JSON each, up to 128 KiB as text.
59
+ * That's at most about 6 times this, plus 653 MiB,
60
+ * plus 128 KiB for each wake maxWakesPerScript allows: about 1.4 GiB a
61
+ * script at the defaults. A tool's
62
+ * own result, and the one serialization the bridge makes of it to check its
63
+ * size, are the tool's, outside this. Size a host by that and by the
64
+ * child's own memory.
65
+ *
66
+ * A result is sent only if it's at most a third of this; within that, what
67
+ * fits the script depends on its shape, since parsing builds it in this
68
+ * memory: at 128 MiB, a string or an array of numbers up to that third fits,
69
+ * and an array of small objects up to about 24 MiB, by a probe of each
70
+ * shape. A result that doesn't fit reaches the script as ResultTooLarge,
71
+ * which says the call took effect.
72
+ */
73
+ memoryMaxBytes: number;
74
+ }
75
+
76
+ export const DEFAULT_SETTINGS: IsolateSettings = {
77
+ yieldMs: 30_000,
78
+ maxYieldMs: 600_000,
79
+ scriptLifetimeMs: 86_400_000,
80
+ maxRunningScripts: 3,
81
+ maxScriptChildren: 16,
82
+ toolCallTimeoutMs: 270_000,
83
+ wakeMinIntervalMs: 60_000,
84
+ maxWakesPerScript: 100,
85
+ memoryMaxBytes: 128 * 1024 * 1024,
86
+ };
87
+
88
+ /** What the Python runner took that the isolate doesn't, and what to set instead. */
89
+ const RETIRED: Record<string, string> = {
90
+ pythonPath: 'scripts are JavaScript now, run by an engine built into agent-framework, so there is no interpreter to name; leave it out',
91
+ idleReclaimMs: 'each script starts fresh and keeps nothing after it ends, so there is nothing to reclaim; leave it out',
92
+ env: "a script's world holds only your agent's tools, with no environment; leave it out",
93
+ inheritEnv: "a script's world holds only your agent's tools, with no environment; leave it out",
94
+ scriptTimeoutMs: 'a script now yields at its bound and keeps running: set yieldMs for the bound',
95
+ maxScriptTimeoutMs: 'set maxYieldMs, the most an agent may ask a run or a wait to wait',
96
+ backgroundMaxLifetimeMs: "every script now has one limit: set scriptLifetimeMs",
97
+ maxBackgroundScripts: 'set maxRunningScripts, how many scripts an agent may have running at once, yielded ones included',
98
+ };
99
+
100
+ const MS_KEYS = ['yieldMs', 'maxYieldMs', 'scriptLifetimeMs', 'toolCallTimeoutMs', 'wakeMinIntervalMs'] as const;
101
+ const COUNT_KEYS = ['maxRunningScripts', 'maxScriptChildren', 'maxWakesPerScript', 'memoryMaxBytes'] as const;
102
+ /** Node's timer range: a timer longer than this fires at once. */
103
+ const MAX_TIMER_MS = 2_147_483_647;
104
+
105
+ /**
106
+ * The isolate's settings from a `codeExecution` configuration that's on, with each
107
+ * default filled in. Throws, naming the key and what to do, for a key the
108
+ * Python runner took, or a value that isn't a whole number in range.
109
+ */
110
+ export function isolateSettings(cfg: Record<string, unknown>): IsolateSettings {
111
+ for (const [key, why] of Object.entries(RETIRED)) {
112
+ if (cfg[key] !== undefined && cfg[key] !== null) throw new Error(`codeExecution.${key} is no longer read: ${why}`);
113
+ }
114
+ const out: IsolateSettings = { ...DEFAULT_SETTINGS };
115
+ for (const key of [...MS_KEYS, ...COUNT_KEYS]) {
116
+ const value = cfg[key];
117
+ if (value === undefined || value === null) continue;
118
+ if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0) {
119
+ throw new Error(`codeExecution.${key} must be a whole number of ${MS_KEYS.includes(key as never) ? 'milliseconds' : 'units'} of 0 or more`);
120
+ }
121
+ if (MS_KEYS.includes(key as never) && value > MAX_TIMER_MS) {
122
+ throw new Error(`codeExecution.${key} must be at most ${MAX_TIMER_MS} ms, the longest a timer can wait`);
123
+ }
124
+ (out as unknown as Record<string, number>)[key] = value;
125
+ }
126
+ if (out.maxYieldMs < out.yieldMs) {
127
+ throw new Error(`codeExecution.maxYieldMs (${out.maxYieldMs}) must be at least yieldMs (${out.yieldMs}): the default is always allowed`);
128
+ }
129
+ if (out.maxRunningScripts < 1 || out.maxScriptChildren < 1) {
130
+ throw new Error('codeExecution.maxRunningScripts and maxScriptChildren must each be at least 1');
131
+ }
132
+ if (out.memoryMaxBytes % PAGE_BYTES !== 0 || out.memoryMaxBytes < MIN_MEMORY_BYTES || out.memoryMaxBytes > MAX_MEMORY_BYTES) {
133
+ throw new Error(
134
+ `codeExecution.memoryMaxBytes must be a whole number of 64 KiB pages from ${MIN_MEMORY_BYTES} (4 MiB) to ${MAX_MEMORY_BYTES} (2 GiB), not ${out.memoryMaxBytes}`,
135
+ );
136
+ }
137
+ return out;
138
+ }
139
+
140
+ function formatBytes(bytes: number): string {
141
+ const MiB = 1024 * 1024;
142
+ if (bytes % (1024 * MiB) === 0) return `${bytes / (1024 * MiB)} GiB`;
143
+ if (bytes % MiB === 0) return `${bytes / MiB} MiB`;
144
+ if (bytes % 1024 === 0) return `${bytes / 1024} KiB`;
145
+ return `${bytes} bytes`;
146
+ }
147
+
148
+ /**
149
+ * The `code_execution` tool as an agent is offered it under these settings,
150
+ * the `codeExecution` that `create()` takes (each left out has its default).
151
+ * A setting the Python runner took is refused by name, as `create()` refuses it.
152
+ */
153
+ export function buildCodeExecutionToolDefinition(config: Partial<CodeExecutionConfig> = {}): ToolDefinition {
154
+ return describeCodeExecution(isolateSettings(config as unknown as Record<string, unknown>));
155
+ }
156
+
157
+ /** The tool's definition under settings already resolved. */
158
+ export function describeCodeExecution(s: IsolateSettings): ToolDefinition {
159
+ const { headChars, tailChars } = DEFAULT_OUTPUT_BOUNDS;
160
+ const chars = (n: number) => n.toLocaleString('en-US');
27
161
  return {
28
162
  name: CODE_EXECUTION_TOOL_NAME,
29
163
  description:
30
- 'Run Python code that can call your other tools programmatically. ' +
31
- 'Every tool you have is available inside the script as an async Python function: ' +
32
- "the function name is the tool name with '--' replaced by '__' and any other " +
33
- "non-identifier character replaced by '_' " +
34
- "(e.g. tool 'mcpl--discord--fetch_history' is the function mcpl__discord__fetch_history, " +
35
- "and tool 'mcpl--dog-events--status' is mcpl__dog_events__status). " +
36
- 'Each function takes a single dict of arguments and returns a string: the text the tool ' +
37
- 'would have returned to you directly, or "Error: ..." if the call failed. For a result ' +
38
- 'with structured content, the string is a JSON object to parse with json.loads: ' +
39
- '{"content": [...], "structuredContent": ..., "isError": false}, with "error" added when ' +
40
- `"isError" is true. "content" holds the server's content blocks. A reference to a payload ` +
41
- 'appears there as {"type": ..., "refId": ..., "savedTo": ...}, where "savedTo" is the ' +
42
- `workspace path the payload was saved to (null if it wasn't) and "refId" is fetch_reference's ` +
43
- 'ref_id. A structured result over 5 MB is saved to the workspace instead, and the string is ' +
44
- '{"isError": ..., "oversized": {"chars": ..., "savedTo": ...}}. ' +
45
- 'Each string also carries .is_error (True if the result is an error, else False; test it ' +
46
- 'rather than an "Error:" prefix) and .structured (the parsed "structuredContent" of the ' +
47
- 'JSON object above, or None). ' +
48
- "An exact-name lookup dict is also available: tools['mcpl--discord--fetch_history']({...}). " +
49
- 'Use top-level await; run independent calls in parallel with asyncio.gather. ' +
50
- 'Only what you print() (plus stderr and the exit code) comes back to you — intermediate ' +
51
- 'tool results stay in the script, so filter or aggregate large data there and print only ' +
52
- 'what you need. Interpreter state (variables, imports) persists across code_execution ' +
53
- `calls but is reclaimed after ~${idleMinutes} minutes idle. Relative paths resolve in a ` +
54
- 'scratch directory of the interpreter\'s, which goes whenever it stops or is reclaimed, so a file meant to ' +
55
- 'outlast the interpreter needs an absolute path. A tool call that receives no ' +
56
- `response within ~${callTimeoutSeconds}s raises TimeoutError inside the script. ` +
57
- `A foreground script is stopped after ${formatLimit(limits.defaultMs)}; pass time_limit_ms to set this call's limit ` +
58
- `(at most ${formatLimit(limits.maxMs)}; a background script runs up to ${formatLimit(limits.backgroundMaxMs)}). ` +
59
- 'Stopping a script does not stop tools it already called. ' +
60
- 'Use this when fanning out across many items, looping over tool calls, or when tool ' +
61
- 'results are large and you only need a slice or summary. Call tools directly (not via ' +
62
- 'code) when a single call answers the question or when you need to reason about each ' +
63
- 'result before deciding the next step. ' +
64
- 'BACKGROUND MODE: pass background=true to run the script as a detached watcher that ' +
65
- 'outlives this turn — the tool returns immediately with a script_id and you can end ' +
66
- 'your turn (e.g. sleep). Inside a background script, await wake_agent(payload) wakes ' +
67
- 'you: the payload plus provenance (script id, the line number in your script, elapsed ' +
68
- 'time) is delivered into your context and starts a turn for you. A script that ends ' +
69
- 'without calling wake_agent wakes nobody — that silence is the point (poll cheaply, ' +
70
- 'wake only on signal). If your background script CRASHES you are woken with the error. ' +
71
- 'Its print() output streams to a workspace log file you can read any time. Wakes are ' +
72
- 'rate-limited (early wakes are delayed, not dropped) and capped per script. ' +
73
- 'CAUTION: background scripts die silently if the host process restarts — for a wake ' +
74
- 'you absolutely must not miss, also arm a wake rule as backup. ' +
75
- 'Manage your scripts with {"action": "list"} and {"action": "cancel", "script_id": "..."}.',
164
+ 'Run JavaScript that calls your other tools, so that many calls, and the results between them, happen in one step and stay out of your context. ' +
165
+ 'The script is the body of an async function: await at its top level, and return a value to see it. ' +
166
+ `The value comes back as JSON, and past ${chars(VALUE_CHARS)} characters as its first and last ${chars(VALUE_CHARS / 2)}. It's made in the script's memory, so a large one needs room there for its text twice over. ` +
167
+ 'Call a tool by its exact name, as ALL_TOOLS gives it: await tools["module--tool"]({ ...its arguments }). ' +
168
+ 'Every tool answers one shape, { content, structuredContent, isError }: content is a list of blocks, what you would have read, most often one { type: "text", text }; ' +
169
+ "structuredContent is the tool's data as a value, when it gives one beside that text, otherwise null; and isError is true when the tool failed, its text saying why. " +
170
+ 'With no structuredContent, the answer is in content, most often its text: JSON.parse(result.content[0].text) reads one given as JSON. ' +
171
+ 'A tool that turns a call down itself, such as for a missing argument, answers this way, with isError. ' +
172
+ "A call that can't go out (the tool isn't yours now, or its arguments aren't an object) doesn't answer: its await throws a ToolRefused error, and nothing is sent for it. " +
173
+ 'A Promise.all holding it rejects with that error, while the calls beside it still go out. ' +
174
+ `A call's arguments may be up to ${formatBytes(MAX_ARGUMENT_BYTES)} of JSON with at most ${chars(MAX_ARGUMENT_VALUES)} values: ` +
175
+ 'a generated file of a few MB of ordinary text fits as one string (quotes, backslashes and control characters take more room once escaped), and more goes in parts, over several calls. ' +
176
+ `ALL_TOOLS lists your tools, each with its name, description and inputSchema. Run independent calls together with Promise.all: up to ${MAX_CALLS_IN_FLIGHT} are out at once, and the rest wait their turn. ` +
177
+ `A call out that gets no answer within ${formatMs(s.toolCallTimeoutMs)} throws a TimeoutError that says whether it was sent: one never sent did nothing, but one sent may still take effect, so a retry may do it twice. ` +
178
+ 'Write output with text(value) or console.log(...): only what the script writes and returns comes back to you, with how it ended and what became of its calls, ' +
179
+ 'so filter large results in the script. ' +
180
+ 'Each script starts fresh and keeps nothing after it ends: write what must last with a tool. ' +
181
+ 'A script reaches nothing but your tools: no network, files, timers or packages. ' +
182
+ `A script has ${formatBytes(s.memoryMaxBytes)} of memory. A result too large for it, or nested more than ${chars(MAX_RESULT_DEPTH)} deep, doesn't reach the script: the call's ResultTooLarge error says the tool ran. ` +
183
+ `A run or a wait takes yield_time_ms, how long to wait for the script (default ${formatMs(s.yieldMs)}, at most ${formatMs(s.maxYieldMs)}). ` +
184
+ 'If the script is still running then, you get what it has written, and it keeps running: ' +
185
+ '{"action": "wait", "script_id": ...} waits again, {"action": "stop", "script_id": ...} stops it, and {"action": "list"} lists your scripts. ' +
186
+ `Each return shows what the script wrote since the previous one: its first ${chars(headChars)} and last ${chars(tailChars)} characters, with a count of what was dropped between them. ` +
187
+ "A return longer than your inline cap for tool results (tool_result_inline_max_chars in agent_settings) has its output's first and last parts cut alike to fit; " +
188
+ "if the rest of the return leaves the output no room, the return is cut there as any long tool result is, keeping its start. " +
189
+ "You may leave a script running past your turn, unless the host names another agent as its primary agent, whose scripts alone outlive a turn: " +
190
+ 'a yield says when yours will be stopped at your turn\'s end. ' +
191
+ `A script left running can wake you with await wake_agent(payload), a payload of at most ${formatBytes(MAX_WAKE_PAYLOAD_BYTES)} of JSON, ` +
192
+ `and you are woken if it fails, crashes or reaches its limit of ${formatMs(s.scriptLifetimeMs)}; a clean finish waits for your wait or list. ` +
193
+ 'A script whose body returns while its calls are still running waits for them, and for the calls its pending work goes on to make: ' +
194
+ "it's still running until they're done. " +
195
+ 'When a script ends, the result says how, what became of its calls (any still running when it was stopped may or may not have taken effect), ' +
196
+ `what it returned, and its output. You may have ${s.maxRunningScripts} scripts running at once.`,
76
197
  inputSchema: {
77
198
  type: 'object' as const,
78
199
  properties: {
79
- code: {
80
- type: 'string',
81
- description: 'Python code to execute. Top-level await is allowed.',
82
- },
83
- background: {
84
- type: 'boolean',
85
- description: 'Run detached as a background watcher with wake_agent() available (default false).',
86
- },
200
+ code: { type: 'string', description: 'JavaScript to run, as the body of an async function.' },
87
201
  action: {
88
202
  type: 'string',
89
- enum: ['run', 'list', 'cancel'],
90
- description: 'run (default) executes `code`; list shows your background scripts; cancel stops one.',
203
+ enum: ['run', 'wait', 'stop', 'list'],
204
+ description: 'run (default) runs code; wait and stop take a script_id; list shows your scripts.',
91
205
  },
92
- script_id: {
93
- type: 'string',
94
- description: 'Background script id (for action: cancel).',
95
- },
96
- time_limit_ms: {
206
+ script_id: { type: 'string', description: 'The script to wait for or stop.' },
207
+ yield_time_ms: {
97
208
  type: 'integer',
98
- description:
99
- `Time limit for this script in milliseconds; it is stopped when the limit is reached. ` +
100
- `Default ${limits.defaultMs}, at most ${limits.maxMs}; a background script defaults to and is capped at ` +
101
- `its ${formatLimit(limits.backgroundMaxMs)} lifetime.`,
209
+ description: `For a run or a wait: how long to wait for the script, in milliseconds, before it yields. Default ${s.yieldMs}, at most ${s.maxYieldMs}.`,
102
210
  },
103
211
  },
104
212
  required: [],
@@ -106,98 +214,59 @@ export function buildCodeExecutionToolDefinition(opts?: {
106
214
  };
107
215
  }
108
216
 
217
+ /** The first Bun a script's process runs on reliably. */
218
+ export const MIN_BUN_VERSION = '1.4.0';
219
+
109
220
  /**
110
- * A script's time limits: the default, the most an agent may ask for with
111
- * `time_limit_ms`, and the background lifetime (default and ceiling at once).
221
+ * Why code execution can't run on this Bun, or null when it can (and under
222
+ * Node, where `bun` is undefined). Bun 1.3.14 loses a child's fd 3, the pipe a
223
+ * script's process answers on, at some starts: in a probe, 30 of 200 children
224
+ * started one at a time lost it before their first write, where under Bun
225
+ * 1.4.0 through 1.4.3, and Node 24, none of 200 did.
112
226
  */
113
- export function scriptTimeLimits(opts?: {
114
- scriptTimeoutMs?: number;
115
- maxScriptTimeoutMs?: number;
116
- backgroundMaxLifetimeMs?: number;
117
- }): { defaultMs: number; maxMs: number; backgroundMaxMs: number } {
118
- // Every limit fits Node's timer range (MAX_TIMER_MS, ~24.8 days).
119
- const defaultMs = Math.min(MAX_TIMER_MS, opts?.scriptTimeoutMs ?? 600_000);
120
- return {
121
- defaultMs,
122
- // A ceiling below the default is refused when a framework with code
123
- // execution on is created (validateCodeExecutionConfig); never below the
124
- // default here either.
125
- maxMs: Math.min(MAX_TIMER_MS, Math.max(defaultMs, opts?.maxScriptTimeoutMs ?? defaultMs)),
126
- backgroundMaxMs: Math.min(MAX_TIMER_MS, opts?.backgroundMaxLifetimeMs ?? 86_400_000),
127
- };
227
+ export function bunVersionProblem(bun: string | null): string | null {
228
+ if (bun === null) return null;
229
+ const parts = (v: string) => v.split(/[.-]/).slice(0, 3).map((n) => Number.parseInt(n, 10) || 0);
230
+ const [have, need] = [parts(bun), parts(MIN_BUN_VERSION)];
231
+ for (let i = 0; i < 3; i++) {
232
+ if (have[i]! > need[i]!) return null;
233
+ if (have[i]! < need[i]!) {
234
+ return `codeExecution needs Bun ${MIN_BUN_VERSION} or later, and this host runs Bun ${bun}, ` +
235
+ "which at some starts loses the pipe a script's process answers on, crashing the script: " +
236
+ `move the host to Bun ${MIN_BUN_VERSION} or later, or run it on Node`;
237
+ }
238
+ }
239
+ // The floor's own version, as a prerelease (1.4.0-canary.1), comes before it,
240
+ // and no such build was checked.
241
+ return /^\d+\.\d+\.\d+-/.test(bun)
242
+ ? `codeExecution needs Bun ${MIN_BUN_VERSION} or later, and this host runs Bun ${bun}, a build from before ${MIN_BUN_VERSION}'s release, ` +
243
+ `which hasn't been checked with it: move the host to Bun ${MIN_BUN_VERSION} or later, or run it on Node`
244
+ : null;
128
245
  }
129
246
 
130
247
  /**
131
248
  * Refuse a codeExecution that isn't an object, or whose switch is set to
132
- * anything but true or false, and, once it's on, one whose time limits
133
- * contradict each other or whose interpreter environment would be misread. A
134
- * JSON config, or a `${VAR}` substituted as a string, can carry a quoted
135
- * "false" where the type says boolean, and only `true` turns code execution
136
- * on, so the quotes would hide what the value does; a bare
137
- * `codeExecution: true` would be read as off. Likewise the runner passes the
138
- * host's environment only for an `inheritEnv` of `true`, so a quoted "true"
139
- * would quietly keep the allowlist. A null or false codeExecution, and a null
140
- * setting, count as not set, as left-out ones do. The refusal names the
141
- * value's kind, not the value. A configuration that isn't on has nothing else
249
+ * anything but true or false, and, once it's on, one that sets what the
250
+ * Python runner took or a value out of range (isolateSettings), or that a Bun
251
+ * too old to run it would run (bunVersionProblem). A JSON config,
252
+ * or a `${VAR}` substituted as a string, can carry a quoted "false" where the
253
+ * type says boolean, and only `true` turns code execution on, so the quotes
254
+ * would hide what the value does. A null or false codeExecution counts as not
255
+ * set, as a left-out one does. A configuration that isn't on has nothing else
142
256
  * read.
143
257
  */
144
- export function validateCodeExecutionConfig(cfg: {
145
- enabled?: unknown;
146
- scriptTimeoutMs?: number;
147
- maxScriptTimeoutMs?: number;
148
- env?: unknown;
149
- inheritEnv?: unknown;
150
- }): void {
258
+ /** `bun` is the Bun this runs on, null under Node: the running one's when left out. */
259
+ export function validateCodeExecutionConfig(cfg: unknown, bun: string | null = process.versions.bun ?? null): void {
151
260
  if (!holdsSettings('codeExecution', cfg, '{ enabled: true }')) return;
261
+ const settings = cfg as Record<string, unknown>;
152
262
  assertBooleanSetting(
153
263
  'codeExecution.enabled',
154
- cfg.enabled,
155
- 'true gives agents the code_execution tool, which runs their scripts in a python interpreter on the host, ' +
156
- 'and false or leaving it out doesn\'t',
157
- );
158
- if (cfg.enabled !== true) return;
159
- const defaultMs = cfg.scriptTimeoutMs ?? 600_000;
160
- if (cfg.maxScriptTimeoutMs !== undefined && !(cfg.maxScriptTimeoutMs >= defaultMs)) {
161
- throw new Error(
162
- `codeExecution.maxScriptTimeoutMs (${cfg.maxScriptTimeoutMs}) must be at least scriptTimeoutMs (${defaultMs}): ` +
163
- 'it is the most an agent may ask for, and the default is always allowed',
164
- );
165
- }
166
- // None of the environment's refusals below shows the value it refuses,
167
- // at most its kind: a substituted value can be a credential. null counts
168
- // as not set, so a generated config that writes an unset setting as null
169
- // keeps working.
170
- assertBooleanSetting(
171
- 'codeExecution.inheritEnv',
172
- cfg.inheritEnv,
173
- "true hands the interpreter the host's whole environment, and false or leaving it out keeps the allowlist",
264
+ settings.enabled,
265
+ "true gives agents the code_execution tool, which runs their scripts in a JavaScript engine of the host's, " +
266
+ "and false or leaving it out doesn't",
174
267
  );
175
- if (cfg.env !== undefined && cfg.env !== null) {
176
- if (!isPlainObject(cfg.env)) {
177
- throw new Error(`codeExecution.env must be an object of variable names and string values, not ${kindOf(cfg.env)}`);
178
- }
179
- for (const [name, value] of Object.entries(cfg.env)) {
180
- // spawn writes each variable as name=value: a name that is empty or
181
- // holds '=' doesn't reach the interpreter as written, and a NUL in a
182
- // name or a value makes spawn throw at every start.
183
- // Only what comes before an '=' or NUL is shown: what follows can be a
184
- // value pasted into the name.
185
- if (name === '' || name.includes('=') || name.includes('\0')) {
186
- const shown = name === '' ? 'that is empty' : `starting ${JSON.stringify(name.split(/[=\0]/, 1)[0])} that holds '=' or NUL`;
187
- throw new Error(`codeExecution.env has a variable name ${shown}, and a name can't be empty or hold '=' or NUL`);
188
- }
189
- if (typeof value !== 'string') {
190
- throw new Error(`codeExecution.env.${name} must be a string, not ${kindOf(value)}: give its value as a string, or leave ${name} out`);
191
- }
192
- if (value.includes('\0')) {
193
- throw new Error(`codeExecution.env.${name} holds a NUL character, which no environment variable can carry`);
194
- }
195
- }
196
- }
197
- }
198
-
199
- function isPlainObject(value: unknown): value is Record<string, unknown> {
200
- if (typeof value !== 'object' || value === null) return false;
201
- const proto = Object.getPrototypeOf(value);
202
- return proto === Object.prototype || proto === null;
268
+ if (settings.enabled !== true) return;
269
+ isolateSettings(settings);
270
+ const problem = bunVersionProblem(bun);
271
+ if (problem) throw new Error(problem);
203
272
  }
@@ -1,6 +1,6 @@
1
1
  /** The longest delay one setTimeout holds (2^31 - 1 ms, about 24.8 days).
2
2
  * Node fires a longer one almost at once, with a TimeoutOverflowWarning. */
3
- const MAX_TIMER_MS = 2_147_483_647;
3
+ export const MAX_TIMER_MS = 2_147_483_647;
4
4
 
5
5
  type Hold = (timer: ReturnType<typeof setTimeout>) => void;
6
6