@animalabs/agent-framework 0.23.1 → 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 +1036 -440
  71. package/dist/src/framework.d.ts.map +1 -1
  72. package/dist/src/framework.js +4131 -2228
  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 +4507 -2406
  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
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Length-prefixed frames between a script's parent and its child: four bytes
3
+ * of big-endian length, then that many bytes of body. The parent writes to the
4
+ * child's stdin and reads the child's fd 3.
5
+ *
6
+ * A body's first byte says what it holds:
7
+ * - `JSON_KIND`: a message as UTF-8 JSON. Every frame from a child is one.
8
+ * - `RESULT_KIND`: a call's result for the guest, from the parent: whether it
9
+ * resolves or rejects, the call's id, and the result's JSON as UTF-8 bytes,
10
+ * which the child hands the engine as they are, with no second parse and no
11
+ * string copy (the path the memory budget was measured on).
12
+ *
13
+ * The decoder is the only way bytes from a child become values in the parent,
14
+ * so it bounds what a child can make the parent hold: a frame longer than its
15
+ * limit, of a kind it doesn't take, not JSON, or holding more values than its
16
+ * limit (counted before it's parsed), is a protocol error, and the parent ends
17
+ * the script as crashed rather than reading on.
18
+ */
19
+
20
+ /** The largest frame from a child: a call's arguments or a batch of output. */
21
+ export const MAX_FRAME_BYTES = 64 * 1024 * 1024;
22
+
23
+ const HEADER_BYTES = 4;
24
+ export const JSON_KIND = 0;
25
+ export const RESULT_KIND = 1;
26
+ /** A result body's own header: its kind, `ok`, and the call's id. */
27
+ const RESULT_HEADER_BYTES = 1 + 1 + 4;
28
+
29
+ /** A frame that breaks the protocol: too long, of a kind not taken, not UTF-8, not JSON, holding too many values, or cut off when its stream ended. */
30
+ export class FrameError extends Error {
31
+ /** For a frame too long to encode: how long its body came to. */
32
+ readonly bytes: number | undefined;
33
+
34
+ constructor(message: string, bytes?: number) {
35
+ super(message);
36
+ this.name = 'FrameError';
37
+ this.bytes = bytes;
38
+ }
39
+ }
40
+
41
+ /**
42
+ * The values in a JSON text, counted outside its strings: the top value, and
43
+ * each value inside an object or array. Counting stops once past `stopAfter`.
44
+ */
45
+ export function countJsonValues(text: string, stopAfter = Infinity): number {
46
+ let values = 1;
47
+ let inString = false;
48
+ for (let i = 0; i < text.length; i++) {
49
+ const c = text.charCodeAt(i);
50
+ if (inString) {
51
+ if (c === 0x5c) i++;
52
+ else if (c === 0x22) inString = false;
53
+ } else if (c === 0x22) {
54
+ inString = true;
55
+ } else if (c === 0x7b || c === 0x5b) {
56
+ // An object or array holds a first value unless it closes at once.
57
+ let j = i + 1;
58
+ while (j < text.length && (text.charCodeAt(j) === 0x20 || text.charCodeAt(j) === 0x0a || text.charCodeAt(j) === 0x0d || text.charCodeAt(j) === 0x09)) j++;
59
+ const next = text.charCodeAt(j);
60
+ if (next !== 0x7d && next !== 0x5d && ++values > stopAfter) return values;
61
+ } else if (c === 0x2c) {
62
+ if (++values > stopAfter) return values;
63
+ }
64
+ }
65
+ return values;
66
+ }
67
+
68
+ /** A call's result as the child decodes it: the JSON's bytes, untouched. */
69
+ export interface ResultFrame {
70
+ t: 'result';
71
+ id: number;
72
+ ok: boolean;
73
+ json: Uint8Array;
74
+ }
75
+
76
+ function frame(body: Buffer, maxFrameBytes: number): Buffer {
77
+ if (body.length > maxFrameBytes) {
78
+ throw new FrameError(`a frame of ${body.length} bytes is longer than the ${maxFrameBytes} the protocol allows`, body.length);
79
+ }
80
+ const out = Buffer.allocUnsafe(HEADER_BYTES + body.length);
81
+ out.writeUInt32BE(body.length, 0);
82
+ body.copy(out, HEADER_BYTES);
83
+ return out;
84
+ }
85
+
86
+ /** One message as a JSON frame. One longer than `maxFrameBytes` throws a FrameError, and nothing is written. */
87
+ export function encodeFrame(message: unknown, maxFrameBytes: number = MAX_FRAME_BYTES): Buffer {
88
+ const json = JSON.stringify(message);
89
+ if (json === undefined) throw new FrameError('a frame must hold a JSON value');
90
+ const body = Buffer.allocUnsafe(1 + Buffer.byteLength(json, 'utf8'));
91
+ body[0] = JSON_KIND;
92
+ body.write(json, 1, 'utf8');
93
+ return frame(body, maxFrameBytes);
94
+ }
95
+
96
+ /**
97
+ * A call's result as a result frame: the JSON's bytes follow a header of `ok`
98
+ * and the id. Written straight into the frame, so a result's bytes are held
99
+ * once, which the host's memory figure counts.
100
+ */
101
+ export function encodeResultFrame(id: number, ok: boolean, json: string, maxFrameBytes: number): Buffer {
102
+ const bodyLength = RESULT_HEADER_BYTES + Buffer.byteLength(json, 'utf8');
103
+ if (bodyLength > maxFrameBytes) {
104
+ throw new FrameError(`a frame of ${bodyLength} bytes is longer than the ${maxFrameBytes} the protocol allows`, bodyLength);
105
+ }
106
+ const out = Buffer.allocUnsafe(HEADER_BYTES + bodyLength);
107
+ out.writeUInt32BE(bodyLength, 0);
108
+ out[HEADER_BYTES] = RESULT_KIND;
109
+ out[HEADER_BYTES + 1] = ok ? 1 : 0;
110
+ out.writeUInt32BE(id >>> 0, HEADER_BYTES + 2);
111
+ out.write(json, HEADER_BYTES + RESULT_HEADER_BYTES, 'utf8');
112
+ return out;
113
+ }
114
+
115
+ /** How much a result frame's body holds beyond its JSON. */
116
+ export const RESULT_FRAME_OVERHEAD = RESULT_HEADER_BYTES;
117
+
118
+ /**
119
+ * Bytes in, whole frames' values out. Holds at most one frame's bytes plus its
120
+ * header: a header naming more than `maxFrameBytes` throws before any of that
121
+ * frame's body is kept, and once a header names its body's length, the body
122
+ * is one buffer of that length, which each chunk is copied into. So however
123
+ * the bytes arrive, one at a time included, the decoder keeps no chunk of
124
+ * them. Takes result frames only if asked to: a parent's decoder takes JSON
125
+ * alone.
126
+ */
127
+ export class FrameDecoder {
128
+ private readonly header = Buffer.alloc(HEADER_BYTES);
129
+ private headerFilled = 0;
130
+ private body: Buffer | null = null;
131
+ private bodyFilled = 0;
132
+ private broken: FrameError | null = null;
133
+
134
+ constructor(
135
+ private readonly maxFrameBytes: number = MAX_FRAME_BYTES,
136
+ private readonly takesResults = false,
137
+ /** The most values a JSON frame may hold, counted before it's parsed. */
138
+ private readonly maxValues = Infinity,
139
+ ) {}
140
+
141
+ /** The values of every frame `chunk` completes, in order. Throws the same FrameError from then on, once one frame was bad. */
142
+ push(chunk: Buffer): unknown[] {
143
+ if (this.broken) throw this.broken;
144
+ const values: unknown[] = [];
145
+ let at = 0;
146
+ while (at < chunk.length) {
147
+ if (this.body === null) {
148
+ const n = Math.min(HEADER_BYTES - this.headerFilled, chunk.length - at);
149
+ chunk.copy(this.header, this.headerFilled, at, at + n);
150
+ this.headerFilled += n;
151
+ at += n;
152
+ if (this.headerFilled < HEADER_BYTES) break;
153
+ const length = this.header.readUInt32BE(0);
154
+ if (length > this.maxFrameBytes) {
155
+ return this.fail(`a frame announced ${length} bytes, more than the ${this.maxFrameBytes} the protocol allows`);
156
+ }
157
+ if (length === 0) return this.fail('a frame was empty');
158
+ this.body = Buffer.allocUnsafe(length);
159
+ this.bodyFilled = 0;
160
+ }
161
+ const n = Math.min(this.body.length - this.bodyFilled, chunk.length - at);
162
+ chunk.copy(this.body, this.bodyFilled, at, at + n);
163
+ this.bodyFilled += n;
164
+ at += n;
165
+ if (this.bodyFilled < this.body.length) break;
166
+ const body = this.body;
167
+ this.body = null;
168
+ this.headerFilled = 0;
169
+ values.push(this.decode(body));
170
+ }
171
+ return values;
172
+ }
173
+
174
+ /** At the stream's end: throws if a frame was begun and not finished. */
175
+ end(): void {
176
+ if (this.broken) throw this.broken;
177
+ if (this.headerFilled > 0 || this.body !== null) {
178
+ this.fail(`the stream ended inside a frame, with ${this.body === null ? this.headerFilled : this.bodyFilled} of its bytes read`);
179
+ }
180
+ }
181
+
182
+ private decode(body: Buffer): unknown {
183
+ if (body[0] === RESULT_KIND && this.takesResults) {
184
+ if (body.length < RESULT_HEADER_BYTES) return this.fail('a result frame was shorter than its header');
185
+ const result: ResultFrame = { t: 'result', id: body.readUInt32BE(2), ok: body[1] === 1, json: body.subarray(RESULT_HEADER_BYTES) };
186
+ return result;
187
+ }
188
+ if (body[0] !== JSON_KIND) return this.fail(`a frame was of kind ${body[0]}, which this side doesn't take`);
189
+ let text: string;
190
+ try {
191
+ // A byte-order mark is kept, so it fails JSON.parse as the text it is.
192
+ text = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(body.subarray(1));
193
+ } catch {
194
+ return this.fail('a frame was not UTF-8');
195
+ }
196
+ if (countJsonValues(text, this.maxValues) > this.maxValues) return this.fail(`a frame held more than ${this.maxValues} values`);
197
+ try {
198
+ return JSON.parse(text);
199
+ } catch {
200
+ return this.fail('a frame was not JSON');
201
+ }
202
+ }
203
+
204
+ private fail(message: string): never {
205
+ this.broken = new FrameError(message);
206
+ this.body = null;
207
+ throw this.broken;
208
+ }
209
+ }
@@ -0,0 +1,161 @@
1
+ /**
2
+ * A script's output, bounded once, in the parent: every return, whether a
3
+ * yield, a wait or the ending's, gives what the script wrote since the
4
+ * previous one, its first and last characters within the bound and a count of
5
+ * what was dropped between. A long stretch keeps its start, since that's
6
+ * often where its trouble began, such as the first error before a cascade.
7
+ *
8
+ * The child can't know when the parent returns, since its frames reach it only
9
+ * while its guest is idle, so the bound sits in the parent: the child holds
10
+ * one bounded batch of what the guest writes and flushes it, and the parent
11
+ * keeps the head and tail of each return's stretch, starting both afresh at
12
+ * each return. Memory is bounded by construction on both sides; the price is
13
+ * that a printing loop's whole output crosses the pipe, in batches.
14
+ */
15
+
16
+ export interface OutputBounds {
17
+ /** Characters kept from the start of each return's stretch. */
18
+ headChars: number;
19
+ /** Characters kept from its end. */
20
+ tailChars: number;
21
+ }
22
+
23
+ export const DEFAULT_OUTPUT_BOUNDS: OutputBounds = { headChars: 8_000, tailChars: 8_000 };
24
+
25
+ /** The most one output frame carries, so a single large write is sent in pieces. */
26
+ export const MAX_BATCH_CHARS = 64 * 1024;
27
+
28
+ /** Where to cut `text` near `at` without parting a surrogate pair: one earlier if `at` falls inside one. */
29
+ function safeCut(text: string, at: number): number {
30
+ if (at <= 0 || at >= text.length) return Math.max(0, Math.min(at, text.length));
31
+ const before = text.charCodeAt(at - 1);
32
+ return before >= 0xd800 && before <= 0xdbff ? at - 1 : at;
33
+ }
34
+
35
+ /** The child's side: what the guest wrote since the last flush, at most a batch of it at a time. */
36
+ export class OutputBatch {
37
+ private pending: string[] = [];
38
+ private size = 0;
39
+
40
+ /** Characters waiting to be flushed. */
41
+ get unflushed(): number {
42
+ return this.size;
43
+ }
44
+
45
+ /** True once the batch is full, so the caller flushes now. */
46
+ write(text: string): boolean {
47
+ if (text.length === 0) return this.size >= MAX_BATCH_CHARS;
48
+ this.pending.push(text);
49
+ this.size += text.length;
50
+ return this.size >= MAX_BATCH_CHARS;
51
+ }
52
+
53
+ /** The waiting text in pieces of at most a batch each, emptying it; empty when nothing waits. */
54
+ flush(): string[] {
55
+ if (this.size === 0) return [];
56
+ let text = this.pending.join('');
57
+ this.pending = [];
58
+ this.size = 0;
59
+ const pieces: string[] = [];
60
+ while (text.length > MAX_BATCH_CHARS) {
61
+ const cut = safeCut(text, MAX_BATCH_CHARS);
62
+ pieces.push(text.slice(0, cut));
63
+ text = text.slice(cut);
64
+ }
65
+ if (text.length > 0) pieces.push(text);
66
+ return pieces;
67
+ }
68
+ }
69
+
70
+ /** A return's stretch of output: its head and tail, and the count dropped between them. */
71
+ export interface OutputWindow {
72
+ head: string;
73
+ tail: string;
74
+ dropped: number;
75
+ }
76
+
77
+ /** The line naming what was dropped, at its longest, as renderOutput puts it between the head and the tail. */
78
+ const DROPPED_LINE_CHARS = `\n[… ${Number.MAX_SAFE_INTEGER} characters of output dropped here …]\n`.length;
79
+
80
+ /**
81
+ * The stretch as the model sees it: the head, a line naming what was dropped
82
+ * if anything was, and the tail. Past `max`, its start and its end are cut
83
+ * alike to fit it, each taking the room the other doesn't need, and the count
84
+ * takes in what they gave up, so a return held to its agent's inline cap
85
+ * still shows both ends. A `max` that leaves no room for that line gets the
86
+ * line alone, which passes it.
87
+ */
88
+ export function renderOutput(w: OutputWindow, max = Infinity): string {
89
+ const whole = w.dropped === 0 ? w.head + w.tail : `${w.head}\n[… ${w.dropped} characters of output dropped here …]\n${w.tail}`;
90
+ if (whole.length <= max) return whole;
91
+ const room = Math.max(0, Math.floor(max) - DROPPED_LINE_CHARS);
92
+ // With nothing dropped yet, the head and the tail are one stretch, whose end
93
+ // lies in the head while the tail is short or empty: it's cut as one.
94
+ const [first, last] = w.dropped === 0 ? [whole, whole] : [w.head, w.tail];
95
+ const lastKeep = Math.min(last.length, Math.max(Math.floor(room / 2), room - first.length));
96
+ const head = first.slice(0, safeCut(first, Math.min(first.length, room - lastKeep)));
97
+ // Kept from the end, never starting on a pair's second half.
98
+ let from = last.length - lastKeep;
99
+ const at = last.charCodeAt(from);
100
+ if (from > 0 && at >= 0xdc00 && at <= 0xdfff) from += 1;
101
+ const tail = last.slice(from);
102
+ const dropped = w.dropped === 0 ? whole.length - head.length - tail.length : w.dropped + (first.length - head.length) + from;
103
+ return `${head}\n[… ${dropped} characters of output dropped here …]\n${tail}`;
104
+ }
105
+
106
+ /** The parent's side: the head and tail of what arrived since the last return, and the count dropped between. */
107
+ export class WindowOutput {
108
+ private head = '';
109
+ private tail = '';
110
+ private dropped = 0;
111
+
112
+ constructor(private readonly bounds: OutputBounds) {}
113
+
114
+ /** Add text the child flushed. */
115
+ apply(text: string): void {
116
+ let rest = text;
117
+ if (this.tail.length === 0 && this.dropped === 0) {
118
+ const room = this.bounds.headChars - this.head.length;
119
+ if (room > 0) {
120
+ const cut = safeCut(rest, room);
121
+ this.head += rest.slice(0, cut);
122
+ rest = rest.slice(cut);
123
+ if (rest.length === 0) return;
124
+ }
125
+ }
126
+ this.tail += rest;
127
+ const over = this.tail.length - this.bounds.tailChars;
128
+ if (over > 0) {
129
+ // Drop from the front, never splitting a pair: the cut moves later by one if it would.
130
+ let cut = over;
131
+ const at = this.tail.charCodeAt(cut - 1);
132
+ if (at >= 0xd800 && at <= 0xdbff) cut += 1;
133
+ this.dropped += cut;
134
+ this.tail = this.tail.slice(cut);
135
+ }
136
+ }
137
+
138
+ /** Characters dropped from the stretch so far. */
139
+ get droppedChars(): number {
140
+ return this.dropped;
141
+ }
142
+
143
+ /** The stretch as it stands. */
144
+ window(): OutputWindow {
145
+ return { head: this.head, tail: this.tail, dropped: this.dropped };
146
+ }
147
+
148
+ /** The stretch as the model sees it, whole (renderOutput). */
149
+ render(): string {
150
+ return renderOutput(this.window());
151
+ }
152
+
153
+ /** The stretch, and a fresh one from here. */
154
+ take(): OutputWindow {
155
+ const window = this.window();
156
+ this.head = '';
157
+ this.tail = '';
158
+ this.dropped = 0;
159
+ return window;
160
+ }
161
+ }