experimental-a2 0.16.0 → 0.17.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 (334) hide show
  1. package/CHANGELOG.md +360 -0
  2. package/dist/{actor-ohPC-81x.d.ts → actor-DBbe5xeE.d.ts} +21 -11
  3. package/dist/actor-DBbe5xeE.d.ts.map +1 -0
  4. package/dist/actor-client.d.ts +2 -5
  5. package/dist/actor-client.d.ts.map +1 -1
  6. package/dist/actor-client.js +13 -4
  7. package/dist/actor-client.js.map +1 -1
  8. package/dist/actor-react.d.ts +5 -3
  9. package/dist/actor-react.d.ts.map +1 -1
  10. package/dist/actor-react.js +20 -10
  11. package/dist/actor-react.js.map +1 -1
  12. package/dist/{actor-shared-B5tJfzt-.js → actor-shared-t7w8xIKP.js} +12 -7
  13. package/dist/actor-shared-t7w8xIKP.js.map +1 -0
  14. package/dist/actor.d.ts +1 -1
  15. package/dist/actor.js +42 -23
  16. package/dist/actor.js.map +1 -1
  17. package/dist/{ai-CrLYwNEx.js → ai-UrWfKeuv.js} +966 -541
  18. package/dist/ai-UrWfKeuv.js.map +1 -0
  19. package/dist/ai-client.d.ts +27 -0
  20. package/dist/ai-client.d.ts.map +1 -0
  21. package/dist/ai-client.js +29 -0
  22. package/dist/ai-client.js.map +1 -0
  23. package/dist/ai-react.d.ts +96 -0
  24. package/dist/ai-react.d.ts.map +1 -0
  25. package/dist/ai-react.js +142 -0
  26. package/dist/ai-react.js.map +1 -0
  27. package/dist/ai-server.browser.js +2 -2
  28. package/dist/ai-server.browser.js.map +1 -1
  29. package/dist/ai-server.d.ts +80 -36
  30. package/dist/ai-server.d.ts.map +1 -1
  31. package/dist/ai-server.js +466 -265
  32. package/dist/ai-server.js.map +1 -1
  33. package/dist/{ai-HJ9fHfYI.d.ts → ai-vpGtcaCB.d.ts} +115 -101
  34. package/dist/ai-vpGtcaCB.d.ts.map +1 -0
  35. package/dist/ai.d.ts +2 -2
  36. package/dist/ai.js +2 -2
  37. package/dist/{client-BYzHjkwU.js → client-COaiaDub.js} +447 -80
  38. package/dist/client-COaiaDub.js.map +1 -0
  39. package/dist/{client-CzyacQpJ.d.ts → client-qRnRUzBS.d.ts} +14 -13
  40. package/dist/client-qRnRUzBS.d.ts.map +1 -0
  41. package/dist/client.d.ts +4 -2
  42. package/dist/client.js +1 -1
  43. package/dist/{contract-CKRg_E4q.js → contract-BYKASlCV.js} +12 -21
  44. package/dist/contract-BYKASlCV.js.map +1 -0
  45. package/dist/{idempotent-replay-DVOlyYbx.js → idempotent-replay-JdgMQRRm.js} +2 -2
  46. package/dist/{idempotent-replay-DVOlyYbx.js.map → idempotent-replay-JdgMQRRm.js.map} +1 -1
  47. package/dist/index.d.ts +6 -5
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +4 -3
  50. package/dist/{errors-DvhSXnxk.d.ts → initial-snapshot-BrLso5x_.d.ts} +7 -3
  51. package/dist/initial-snapshot-BrLso5x_.d.ts.map +1 -0
  52. package/dist/{internal-Dq2qYxou.js → internal-CbVvEwad.js} +23 -2
  53. package/dist/internal-CbVvEwad.js.map +1 -0
  54. package/dist/otel.d.ts +1 -1
  55. package/dist/react-BbJ-g6Uu.d.ts +86 -0
  56. package/dist/react-BbJ-g6Uu.d.ts.map +1 -0
  57. package/dist/react.d.ts +2 -122
  58. package/dist/react.js +107 -100
  59. package/dist/react.js.map +1 -1
  60. package/dist/reducer-BAXc8JyF.js +87 -0
  61. package/dist/reducer-BAXc8JyF.js.map +1 -0
  62. package/dist/{reducer-otzHjuJj.d.ts → reducer-BYu6DT7h.d.ts} +45 -29
  63. package/dist/reducer-BYu6DT7h.d.ts.map +1 -0
  64. package/dist/reducer-runtime-DQwz_J59.js +28 -0
  65. package/dist/reducer-runtime-DQwz_J59.js.map +1 -0
  66. package/dist/scheduler-qstash.d.ts +3 -3
  67. package/dist/scheduler-qstash.d.ts.map +1 -1
  68. package/dist/scheduler-qstash.js +33 -10
  69. package/dist/scheduler-qstash.js.map +1 -1
  70. package/dist/{scheduler-task-BpzhPnRS.js → scheduler-task-X1SToDjP.js} +18 -3
  71. package/dist/scheduler-task-X1SToDjP.js.map +1 -0
  72. package/dist/scheduler-vercel.d.ts +4 -9
  73. package/dist/scheduler-vercel.d.ts.map +1 -1
  74. package/dist/scheduler-vercel.js +25 -44
  75. package/dist/scheduler-vercel.js.map +1 -1
  76. package/dist/{server-DbD7IJVK.d.ts → server-CGM8WzRG.d.ts} +87 -26
  77. package/dist/server-CGM8WzRG.d.ts.map +1 -0
  78. package/dist/{server-BD5ckxZb.js → server-Rj64fNvX.js} +854 -202
  79. package/dist/server-Rj64fNvX.js.map +1 -0
  80. package/dist/server.d.ts +4 -4
  81. package/dist/server.js +2 -2
  82. package/dist/{errors-DCk6ch5n.js → session-clear-DGI4jTQ5.js} +17 -2
  83. package/dist/session-clear-DGI4jTQ5.js.map +1 -0
  84. package/dist/session-clear-Dshpuhff.d.ts +12 -0
  85. package/dist/session-clear-Dshpuhff.d.ts.map +1 -0
  86. package/dist/{store-DyZM6fS5.d.ts → store-Gpkq7WBo.d.ts} +44 -3
  87. package/dist/store-Gpkq7WBo.d.ts.map +1 -0
  88. package/dist/store-N8PXxDAS.js.map +1 -1
  89. package/dist/store-memory.d.ts +1 -1
  90. package/dist/store-memory.d.ts.map +1 -1
  91. package/dist/store-memory.js +182 -69
  92. package/dist/store-memory.js.map +1 -1
  93. package/dist/{store-polling-CmxUbV93.js → store-polling-4_y8lETm.js} +21 -4
  94. package/dist/store-polling-4_y8lETm.js.map +1 -0
  95. package/dist/store-postgres.d.ts +1 -1
  96. package/dist/store-postgres.d.ts.map +1 -1
  97. package/dist/store-postgres.js +399 -108
  98. package/dist/store-postgres.js.map +1 -1
  99. package/dist/{store-presence-polling-C7-XZyW9.js → store-presence-polling-Q1C-k2dd.js} +3 -3
  100. package/dist/{store-presence-polling-C7-XZyW9.js.map → store-presence-polling-Q1C-k2dd.js.map} +1 -1
  101. package/dist/store-redis-http.d.ts +1 -1
  102. package/dist/store-redis-http.d.ts.map +1 -1
  103. package/dist/store-redis-http.js +293 -11
  104. package/dist/store-redis-http.js.map +1 -1
  105. package/dist/store-redis-notify-tqWNnMRu.js +2374 -0
  106. package/dist/store-redis-notify-tqWNnMRu.js.map +1 -0
  107. package/dist/store-redis.d.ts +1 -1
  108. package/dist/store-redis.d.ts.map +1 -1
  109. package/dist/store-redis.js +11 -11
  110. package/dist/store-redis.js.map +1 -1
  111. package/dist/store-sqlite.d.ts +1 -1
  112. package/dist/store-sqlite.d.ts.map +1 -1
  113. package/dist/store-sqlite.js +166 -32
  114. package/dist/store-sqlite.js.map +1 -1
  115. package/dist/stream-buffer-Cx7AAPGq.js +23 -0
  116. package/dist/stream-buffer-Cx7AAPGq.js.map +1 -0
  117. package/dist/{telemetry-B5jzpy6y.d.ts → telemetry-cZH-cibj.d.ts} +2 -2
  118. package/dist/telemetry-cZH-cibj.d.ts.map +1 -0
  119. package/dist/{wire-BO5wWCb1.js → wire-DjfHOvdW.js} +132 -10
  120. package/dist/wire-DjfHOvdW.js.map +1 -0
  121. package/docs/01-quickstart.mdx +2 -2
  122. package/docs/actors/01-introduction.mdx +4 -0
  123. package/docs/actors/04-routes.mdx +14 -1
  124. package/docs/concepts/01-contracts.mdx +95 -0
  125. package/docs/concepts/04-state.mdx +3 -3
  126. package/docs/guides/01-timers.mdx +22 -14
  127. package/docs/guides/02-cancellation.mdx +1 -1
  128. package/docs/guides/03-react.mdx +268 -328
  129. package/docs/guides/04-local-first.mdx +32 -73
  130. package/docs/guides/05-production.mdx +40 -27
  131. package/docs/guides/06-ai-agents.mdx +680 -580
  132. package/docs/guides/09-presence.mdx +3 -7
  133. package/docs/guides/10-transports.mdx +5 -5
  134. package/docs/index.mdx +10 -18
  135. package/docs/reference/01-api.mdx +662 -195
  136. package/docs/reference/02-errors.mdx +2 -1
  137. package/examples/playground/DEPLOY.md +52 -6
  138. package/examples/playground/app/a0/[appId]/loading.tsx +16 -0
  139. package/examples/playground/app/a0/[appId]/page.tsx +48 -0
  140. package/examples/playground/app/a0/a0.css +818 -0
  141. package/examples/playground/app/a0/app-list.tsx +211 -0
  142. package/examples/playground/app/a0/app-view.tsx +55 -0
  143. package/examples/playground/app/a0/apps/[appId]/preview/route.ts +27 -0
  144. package/examples/playground/app/a0/apps/[appId]/route.ts +23 -0
  145. package/examples/playground/app/a0/apps.test.ts +116 -0
  146. package/examples/playground/app/a0/apps.ts +87 -0
  147. package/examples/playground/app/a0/chat.tsx +364 -0
  148. package/examples/playground/app/a0/columns.tsx +109 -0
  149. package/examples/playground/app/a0/context-menu.tsx +116 -0
  150. package/examples/playground/app/a0/cookies.ts +46 -0
  151. package/examples/playground/app/a0/crash-on-request.ts +72 -0
  152. package/examples/playground/app/a0/crashes/route.ts +3 -0
  153. package/examples/playground/app/a0/crashes.test.ts +33 -0
  154. package/examples/playground/app/a0/crashes.ts +28 -0
  155. package/examples/playground/app/a0/devtools.tsx +164 -0
  156. package/examples/playground/app/a0/events/route.ts +6 -0
  157. package/examples/playground/app/a0/flags.ts +30 -0
  158. package/examples/playground/app/a0/layout.tsx +38 -0
  159. package/examples/playground/app/a0/list/route.ts +10 -0
  160. package/examples/playground/app/a0/model.test.ts +56 -0
  161. package/examples/playground/app/a0/model.ts +58 -0
  162. package/examples/playground/app/a0/page.tsx +14 -0
  163. package/examples/playground/app/a0/preview-pane.tsx +145 -0
  164. package/examples/playground/app/a0/preview.test.ts +53 -0
  165. package/examples/playground/app/a0/preview.ts +26 -0
  166. package/examples/playground/app/a0/sandbox-log.ts +69 -0
  167. package/examples/playground/app/a0/sandbox.ts +191 -0
  168. package/examples/playground/app/a0/sandboxes/scheduler/route.ts +6 -0
  169. package/examples/playground/app/a0/scheduler/route.ts +7 -0
  170. package/examples/playground/app/a0/server.ts +158 -0
  171. package/examples/playground/app/a0/session.ts +47 -0
  172. package/examples/playground/app/a0/shell.tsx +150 -0
  173. package/examples/playground/app/a0/short/events/route.ts +9 -0
  174. package/examples/playground/app/a0/splitter.tsx +120 -0
  175. package/examples/playground/app/agent/[agentId]/agent-client.tsx +144 -174
  176. package/examples/playground/app/agent/[agentId]/command-receipt.test.tsx +65 -0
  177. package/examples/playground/app/agent/[agentId]/command-receipt.tsx +27 -0
  178. package/examples/playground/app/agent/[agentId]/compaction-event.tsx +25 -24
  179. package/examples/playground/app/agent/[agentId]/compaction-panel.test.tsx +119 -0
  180. package/examples/playground/app/agent/[agentId]/compaction-panel.tsx +208 -165
  181. package/examples/playground/app/agent/[agentId]/page.tsx +2 -16
  182. package/examples/playground/app/agent/events/route.ts +5 -2
  183. package/examples/playground/app/agent/ingress.test.ts +98 -0
  184. package/examples/playground/app/agent/model-input.test.tsx +58 -0
  185. package/examples/playground/app/agent/model-input.tsx +115 -0
  186. package/examples/playground/app/agent/model.test.ts +73 -0
  187. package/examples/playground/app/agent/model.ts +58 -26
  188. package/examples/playground/app/agent/new-agent-session.tsx +37 -19
  189. package/examples/playground/app/agent/page.tsx +4 -4
  190. package/examples/playground/app/agent/server.ts +15 -14
  191. package/examples/playground/app/agent/session.ts +5 -6
  192. package/examples/playground/app/agent/telemetry.test.ts +168 -0
  193. package/examples/playground/app/agent/telemetry.ts +30 -0
  194. package/examples/playground/app/agent/trace.test.ts +23 -0
  195. package/examples/playground/app/agent/trace.ts +18 -0
  196. package/examples/playground/app/canvas/[canvasId]/canvas-client.tsx +11 -11
  197. package/examples/playground/app/canvas/[canvasId]/page.tsx +1 -2
  198. package/examples/playground/app/canvas/[canvasId]/session.ts +2 -2
  199. package/examples/playground/app/canvas/model.ts +53 -53
  200. package/examples/playground/app/chat/[chatId]/agent-state.test.ts +176 -0
  201. package/examples/playground/app/chat/[chatId]/agent-stream-drawer.tsx +16 -1
  202. package/examples/playground/app/chat/[chatId]/chat-client.tsx +55 -77
  203. package/examples/playground/app/chat/[chatId]/chat-view.ts +37 -3
  204. package/examples/playground/app/chat/[chatId]/page.tsx +7 -15
  205. package/examples/playground/app/chat/[chatId]/session.ts +2 -19
  206. package/examples/playground/app/chat/agent/route.ts +9 -0
  207. package/examples/playground/app/chat/model.test.ts +46 -119
  208. package/examples/playground/app/chat/model.ts +82 -128
  209. package/examples/playground/app/chat/scheduler/route.ts +2 -2
  210. package/examples/playground/app/chat/server.test.ts +108 -0
  211. package/examples/playground/app/chat/server.ts +69 -73
  212. package/examples/playground/app/components/activity-feed.tsx +30 -1
  213. package/examples/playground/app/components/activity-history.test.tsx +35 -0
  214. package/examples/playground/app/components/demo-icons.tsx +89 -0
  215. package/examples/playground/app/components/demos.tsx +33 -0
  216. package/examples/playground/app/components/fast-link.test.tsx +58 -0
  217. package/examples/playground/app/components/fast-link.tsx +55 -0
  218. package/examples/playground/app/components/site-header.tsx +96 -0
  219. package/examples/playground/app/counter/counter-client.tsx +3 -3
  220. package/examples/playground/app/counter/model.test.ts +3 -3
  221. package/examples/playground/app/counter/model.ts +11 -21
  222. package/examples/playground/app/counter/page.tsx +2 -15
  223. package/examples/playground/app/counter/session.ts +2 -2
  224. package/examples/playground/app/documents/[documentId]/code-editor.tsx +12 -1
  225. package/examples/playground/app/documents/[documentId]/document-client.tsx +10 -11
  226. package/examples/playground/app/documents/[documentId]/page.tsx +1 -2
  227. package/examples/playground/app/documents/[documentId]/session.ts +2 -2
  228. package/examples/playground/app/documents/model.ts +27 -27
  229. package/examples/playground/app/globals.css +420 -207
  230. package/examples/playground/app/initial-state.test.tsx +54 -0
  231. package/examples/playground/app/layout.tsx +7 -23
  232. package/examples/playground/app/orders/[orderId]/page.tsx +1 -2
  233. package/examples/playground/app/orders/[orderId]/session.ts +2 -2
  234. package/examples/playground/app/orders/model.ts +49 -49
  235. package/examples/playground/app/page.tsx +13 -102
  236. package/examples/playground/app/recovery/[recoveryId]/page.tsx +1 -2
  237. package/examples/playground/app/recovery/[recoveryId]/session.ts +2 -2
  238. package/examples/playground/app/recovery/model.ts +30 -30
  239. package/examples/playground/instrumentation.ts +5 -0
  240. package/examples/playground/package.json +10 -2
  241. package/examples/playground/scripts/a0-template.ts +77 -0
  242. package/examples/playground/vercel.json +18 -0
  243. package/package.json +3 -1
  244. package/src/actor-client.ts +16 -4
  245. package/src/actor-react.ts +25 -11
  246. package/src/actor-shared.ts +21 -6
  247. package/src/actor.ts +76 -31
  248. package/src/ai-agent-actions.ts +219 -0
  249. package/src/ai-agent.ts +2343 -0
  250. package/src/ai-app-state.ts +91 -0
  251. package/src/ai-client-state.ts +39 -2
  252. package/src/ai-client.ts +62 -0
  253. package/src/ai-context-schema.ts +25 -5
  254. package/src/ai-context.ts +90 -40
  255. package/src/ai-control-server.ts +94 -55
  256. package/src/ai-control.ts +2 -0
  257. package/src/ai-coordinator.ts +23 -22
  258. package/src/ai-message-base.ts +209 -0
  259. package/src/ai-message-projection.ts +382 -58
  260. package/src/ai-progress-batches.ts +26 -0
  261. package/src/ai-projector.ts +95 -14
  262. package/src/ai-react.ts +146 -0
  263. package/src/ai-server.browser.ts +2 -2
  264. package/src/ai-server.ts +20 -1951
  265. package/src/ai-stored-state.ts +48 -163
  266. package/src/ai.ts +343 -276
  267. package/src/bootstrap-stream.ts +72 -0
  268. package/src/bounded-cache.ts +55 -0
  269. package/src/client-render-session.ts +49 -0
  270. package/src/client.ts +688 -151
  271. package/src/contract.ts +92 -42
  272. package/src/deterministic-id.ts +37 -0
  273. package/src/equal-json.ts +32 -0
  274. package/src/errors.ts +13 -1
  275. package/src/index.ts +5 -1
  276. package/src/initial-snapshot.ts +39 -0
  277. package/src/internal.ts +45 -0
  278. package/src/postgres-notification-scope.ts +14 -3
  279. package/src/react.ts +171 -206
  280. package/src/redis-http-reply.ts +256 -0
  281. package/src/redis-http-stream.ts +73 -0
  282. package/src/redis-http-subscriptions.ts +13 -5
  283. package/src/reducer-runtime.ts +52 -0
  284. package/src/reducer.ts +92 -10
  285. package/src/scheduler-qstash.ts +49 -20
  286. package/src/scheduler-task.ts +34 -2
  287. package/src/scheduler-vercel.ts +32 -84
  288. package/src/server-fetch.ts +139 -35
  289. package/src/server.ts +957 -237
  290. package/src/session-clear.ts +23 -0
  291. package/src/session-socket.ts +154 -49
  292. package/src/sse.ts +71 -4
  293. package/src/store-memory.ts +220 -78
  294. package/src/store-polling.ts +18 -4
  295. package/src/store-postgres.ts +461 -114
  296. package/src/store-redis-core.ts +1260 -434
  297. package/src/store-redis-http.ts +18 -2
  298. package/src/store-redis-notify.ts +260 -85
  299. package/src/store-redis.ts +19 -9
  300. package/src/store-sqlite.ts +259 -45
  301. package/src/store.ts +46 -1
  302. package/src/stream-buffer.ts +43 -0
  303. package/src/telemetry.ts +13 -1
  304. package/src/wire.ts +241 -13
  305. package/dist/actor-ohPC-81x.d.ts.map +0 -1
  306. package/dist/actor-shared-B5tJfzt-.js.map +0 -1
  307. package/dist/ai-CrLYwNEx.js.map +0 -1
  308. package/dist/ai-HJ9fHfYI.d.ts.map +0 -1
  309. package/dist/client-BYzHjkwU.js.map +0 -1
  310. package/dist/client-CzyacQpJ.d.ts.map +0 -1
  311. package/dist/contract-CKRg_E4q.js.map +0 -1
  312. package/dist/errors-DCk6ch5n.js.map +0 -1
  313. package/dist/errors-DvhSXnxk.d.ts.map +0 -1
  314. package/dist/internal-Dq2qYxou.js.map +0 -1
  315. package/dist/react.d.ts.map +0 -1
  316. package/dist/reducer-DEMjEY_O.js +0 -29
  317. package/dist/reducer-DEMjEY_O.js.map +0 -1
  318. package/dist/reducer-otzHjuJj.d.ts.map +0 -1
  319. package/dist/scheduler-task-BpzhPnRS.js.map +0 -1
  320. package/dist/server-BD5ckxZb.js.map +0 -1
  321. package/dist/server-DbD7IJVK.d.ts.map +0 -1
  322. package/dist/store-DyZM6fS5.d.ts.map +0 -1
  323. package/dist/store-polling-CmxUbV93.js.map +0 -1
  324. package/dist/store-redis-notify-BVLUfI3j.js +0 -1583
  325. package/dist/store-redis-notify-BVLUfI3j.js.map +0 -1
  326. package/dist/telemetry-B5jzpy6y.d.ts.map +0 -1
  327. package/dist/validate-XKT4FSNn.js +0 -30
  328. package/dist/validate-XKT4FSNn.js.map +0 -1
  329. package/dist/wire-BO5wWCb1.js.map +0 -1
  330. package/examples/playground/app/agent/[agentId]/compaction/route.ts +0 -14
  331. package/examples/playground/app/agent/compaction-settings.test.ts +0 -144
  332. package/examples/playground/app/agent/compaction-settings.ts +0 -49
  333. package/examples/playground/app/agent/compaction-timeline.test.ts +0 -377
  334. package/examples/playground/app/agent/compaction-timeline.ts +0 -208
@@ -1,10 +1,10 @@
1
+ import { reducer as makeReducer } from './reducer.ts'
1
2
  // oxlint-disable no-await-in-loop -- work acknowledgements follow the durable stream.
2
3
  import { applyControlChanges, controlCommit } from './ai-control-state.ts'
3
4
  import type { ControlChange, ControlCommit } from './ai-control.ts'
4
5
  import type { UIMessage } from 'ai'
5
6
  import type {
6
7
  AIEventDefs,
7
- AgentDefinition,
8
8
  GenerationRequestedPayload,
9
9
  MessageInterruptedPayload,
10
10
  } from './ai.ts'
@@ -21,7 +21,12 @@ import {
21
21
  type WorkFact,
22
22
  type WorkReport,
23
23
  } from './ai-control.ts'
24
- import type { AppendInput, ContractEvent, EventDefs } from './contract.ts'
24
+ import type {
25
+ AppendInput,
26
+ Contract,
27
+ ContractEvent,
28
+ EventDefs,
29
+ } from './contract.ts'
25
30
  import type { HandlerContext } from './server.ts'
26
31
  import type { Reducer } from './reducer.ts'
27
32
  import type { AICoordinatorState, CoordinatorCall } from './ai-coordinator.ts'
@@ -29,6 +34,7 @@ import { validateSync } from './validate.ts'
29
34
  import type { StandardSchemaV1 } from './standard-schema.ts'
30
35
  import { A2Error } from './errors.ts'
31
36
  import { aiEventId } from './ai-id.ts'
37
+ import { CONSOLE_TELEMETRY, type A2Telemetry } from './telemetry.ts'
32
38
 
33
39
  export class ControlCancelled extends Error {}
34
40
 
@@ -69,22 +75,42 @@ export const createControlRuntime = <
69
75
  M extends UIMessage,
70
76
  D extends AIEventDefs<M> & EventDefs,
71
77
  >(options: {
72
- agent: AgentDefinition<M, D>
78
+ contract: Contract<D, Record<never, never>, unknown>
79
+ telemetry?: A2Telemetry
73
80
  }): AIControlRuntime<M, D> => {
74
- const reducer = options.agent.contract
75
- .reducer({
76
- name: 'a2.ai.control.v4',
77
- initialState: initialControlState<M>(),
78
- })
79
- .fold((state, event) =>
80
- event.type === 'ai.control.committed'
81
- ? applyControlChanges({
82
- state,
83
- changes: (event.payload as ControlCommit).changes,
84
- })
85
- : state,
86
- )
87
- const coordinator = aiCoordinatorReducer(options.agent.contract)
81
+ const telemetry = options.telemetry ?? CONSOLE_TELEMETRY
82
+ const reducer = makeReducer({
83
+ contract: options.contract,
84
+ name: 'a2.ai.control.v5',
85
+ initialState: initialControlState<M>(),
86
+ }).fold((state, event) => {
87
+ if (event.type === 'ai.control.requested') {
88
+ const command = event.payload as ControlCommand<M>
89
+ if (command.action === 'stop')
90
+ return {
91
+ ...state,
92
+ pendingStops: {
93
+ ...state.pendingStops,
94
+ [event.id]: command.turnId,
95
+ },
96
+ }
97
+ }
98
+ if (event.type === 'ai.control.decided') {
99
+ const receipt = event.payload as ControlReceipt
100
+ if (Object.hasOwn(state.pendingStops, receipt.commandId)) {
101
+ const pendingStops = { ...state.pendingStops }
102
+ delete pendingStops[receipt.commandId]
103
+ return { ...state, pendingStops }
104
+ }
105
+ }
106
+ return event.type === 'ai.control.committed'
107
+ ? applyControlChanges({
108
+ state,
109
+ changes: (event.payload as ControlCommit).changes,
110
+ })
111
+ : state
112
+ })
113
+ const coordinator = aiCoordinatorReducer(options.contract)
88
114
 
89
115
  const handler = async (
90
116
  ctx: HandlerContext<
@@ -419,7 +445,7 @@ export const createControlRuntime = <
419
445
  },
420
446
  id: await aiEventId(
421
447
  'a2.ai.request.retry',
422
- options.agent.contract.name,
448
+ options.contract.name,
423
449
  ctx.event.sessionId,
424
450
  command.request.retryId,
425
451
  ),
@@ -649,7 +675,7 @@ export const createControlRuntime = <
649
675
  payload: { message: item.message, generate: item.generate },
650
676
  id: await aiEventId(
651
677
  'a2.ai.admitted',
652
- options.agent.contract.name,
678
+ options.contract.name,
653
679
  ctx.event.sessionId,
654
680
  item.id,
655
681
  ),
@@ -665,7 +691,7 @@ export const createControlRuntime = <
665
691
  }
666
692
  const requestId = await aiEventId(
667
693
  'a2.ai.request.message',
668
- options.agent.contract.name,
694
+ options.contract.name,
669
695
  ctx.event.sessionId,
670
696
  item.id,
671
697
  )
@@ -717,7 +743,7 @@ export const createControlRuntime = <
717
743
  GenerationRequestedPayload | { turnId: string; version: number }
718
744
  const { turnId, version } = 'turnId' in payload ? payload : payload.control!
719
745
  for (const event of events) {
720
- const schema = options.agent.contract.events[
746
+ const schema = options.contract.events[
721
747
  event.type
722
748
  ] as StandardSchemaV1<unknown>
723
749
  const result = validateSync(
@@ -789,41 +815,54 @@ export const createControlRuntime = <
789
815
  const input = await report({ ctx, events: lifecycle, settled: false, name })
790
816
  const persisted = await ctx.session.append(name, ...progress, input)
791
817
  const index = persisted.at(-1)!.index
792
- const feed = ctx.session.stream({ startAfter: index })
793
- const stream = feed[Symbol.asyncIterator]()
794
- const canonical: ContractEvent<D>[] = []
795
- const wanted = new Set(lifecycle.map((event) => event.id))
796
- let rejectAbort!: (error: unknown) => void
797
- const aborted = new Promise<never>((_, reject) => {
798
- rejectAbort = reject
799
- })
800
- const onAbort = (): void => {
801
- rejectAbort(ctx.signal.reason)
802
- }
803
- ctx.signal.addEventListener('abort', onAbort, { once: true })
804
- try {
805
- ctx.signal.throwIfAborted()
806
- for (;;) {
807
- const item = await Promise.race([stream.next(), aborted])
808
- if (item.done)
809
- throw new Error('AI control acknowledgement stream ended')
810
- if (wanted.has(item.value.id)) canonical.push(item.value)
811
- if (
812
- item.value.type === 'ai.control.decided' &&
813
- (item.value.payload as ControlReceipt).commandId === input.id
814
- ) {
815
- if ((item.value.payload as ControlReceipt).outcome === 'rejected')
816
- throw new ControlCancelled('stale AI work')
817
- return [
818
- ...persisted.filter((event) => event.type !== 'ai.work.reported'),
819
- ...canonical,
820
- ]
818
+ return telemetry.span(
819
+ 'a2.ai.ack',
820
+ {
821
+ 'a2.contract': options.contract.name,
822
+ 'a2.session_id': ctx.event.sessionId,
823
+ 'a2.ai.ack.request_id': ctx.event.id,
824
+ 'a2.ai.ack.report_id': input.id!,
825
+ },
826
+ async () => {
827
+ const feed = ctx.session.stream({ startAfter: index })
828
+ const stream = feed[Symbol.asyncIterator]()
829
+ const canonical: ContractEvent<D>[] = []
830
+ const wanted = new Set(lifecycle.map((event) => event.id))
831
+ let rejectAbort!: (error: unknown) => void
832
+ const aborted = new Promise<never>((_, reject) => {
833
+ rejectAbort = reject
834
+ })
835
+ const onAbort = (): void => {
836
+ rejectAbort(ctx.signal.reason)
821
837
  }
822
- }
823
- } finally {
824
- ctx.signal.removeEventListener('abort', onAbort)
825
- await stream.return?.()
826
- }
838
+ ctx.signal.addEventListener('abort', onAbort, { once: true })
839
+ try {
840
+ ctx.signal.throwIfAborted()
841
+ for (;;) {
842
+ const item = await Promise.race([stream.next(), aborted])
843
+ if (item.done)
844
+ throw new Error('AI control acknowledgement stream ended')
845
+ if (wanted.has(item.value.id)) canonical.push(item.value)
846
+ if (
847
+ item.value.type === 'ai.control.decided' &&
848
+ (item.value.payload as ControlReceipt).commandId === input.id
849
+ ) {
850
+ if ((item.value.payload as ControlReceipt).outcome === 'rejected')
851
+ throw new ControlCancelled('stale AI work')
852
+ return [
853
+ ...persisted.filter(
854
+ (event) => event.type !== 'ai.work.reported',
855
+ ),
856
+ ...canonical,
857
+ ]
858
+ }
859
+ }
860
+ } finally {
861
+ ctx.signal.removeEventListener('abort', onAbort)
862
+ await stream.return?.()
863
+ }
864
+ },
865
+ )
827
866
  }
828
867
 
829
868
  const settled = ({
package/src/ai-control.ts CHANGED
@@ -77,6 +77,7 @@ export type ControlWork = {
77
77
  }
78
78
 
79
79
  export type AIControlState<M extends UIMessage = UIMessage> = {
80
+ pendingStops: Record<string, string>
80
81
  inbox: { paused: boolean; items: InboxItem<M>[] }
81
82
  active:
82
83
  | (ActiveTurn<M> & {
@@ -109,6 +110,7 @@ export type ToolExecutionRequest = {
109
110
  export const initialControlState = <
110
111
  M extends UIMessage,
111
112
  >(): AIControlState<M> => ({
113
+ pendingStops: {},
112
114
  inbox: { paused: false, items: [] },
113
115
  active: null,
114
116
  coordinator: { closed: false, queued: [] },
@@ -1,3 +1,4 @@
1
+ import { reducer } from './reducer.ts'
1
2
  import type { UIMessage } from 'ai'
2
3
  import { applyControlChanges } from './ai-control-state.ts'
3
4
  import type { ControlCommit, ControlWork } from './ai-control.ts'
@@ -255,7 +256,8 @@ export const foldCoordinator = <M extends UIMessage, D extends EventDefs>(
255
256
  return inputTokens !== undefined &&
256
257
  Number.isFinite(inputTokens) &&
257
258
  inputTokens >= 0 &&
258
- estimate !== undefined
259
+ estimate !== undefined &&
260
+ state.response.generation.model !== undefined
259
261
  ? {
260
262
  ...uncalibrated,
261
263
  calibration: {
@@ -436,28 +438,27 @@ export const aiCoordinatorReducer = <
436
438
  M extends UIMessage,
437
439
  D extends AIEventDefs<M> & EventDefs,
438
440
  >(
439
- contract: Contract<D>,
441
+ contract: Contract<D, Record<never, never>, unknown>,
440
442
  ): Reducer<D, AICoordinatorState> =>
441
- contract
442
- .reducer({
443
- name: 'a2.ai.coordinator.v6',
444
- initialState: {
445
- closed: false,
446
- queued: [],
447
- } as AICoordinatorState,
448
- })
449
- .fold((state, event) =>
450
- event.type === 'ai.control.committed'
451
- ? applyControlChanges({
452
- state,
453
- changes: (event.payload as ControlCommit).changes
454
- .filter((change) => change.path[0] === 'coordinator')
455
- .map((change) =>
456
- Object.assign({}, change, { path: change.path.slice(1) }),
457
- ),
458
- })
459
- : state,
460
- )
443
+ reducer({
444
+ contract: contract,
445
+ name: 'a2.ai.coordinator.v6',
446
+ initialState: {
447
+ closed: false,
448
+ queued: [],
449
+ } as AICoordinatorState,
450
+ }).fold((state, event) =>
451
+ event.type === 'ai.control.committed'
452
+ ? applyControlChanges({
453
+ state,
454
+ changes: (event.payload as ControlCommit).changes
455
+ .filter((change) => change.path[0] === 'coordinator')
456
+ .map((change) =>
457
+ Object.assign({}, change, { path: change.path.slice(1) }),
458
+ ),
459
+ })
460
+ : state,
461
+ )
461
462
 
462
463
  export const continuationReady = (state: AICoordinatorState): boolean => {
463
464
  const response = state.response
@@ -0,0 +1,209 @@
1
+ import type { UIMessage } from 'ai'
2
+ import { sameValue } from './equal-json.ts'
3
+
4
+ type FieldUndo = { value?: unknown } | { remove: true } | { length: number }
5
+ type FieldsUndo = Record<string, FieldUndo>
6
+ type PartUndo = { value: UIMessage['parts'][number] } | { fields: FieldsUndo }
7
+
8
+ export type MessageBase = {
9
+ partCount: number
10
+ fields?: FieldsUndo
11
+ parts?: Record<string, PartUndo>
12
+ }
13
+
14
+ const record = (value: unknown): value is Record<string, unknown> =>
15
+ typeof value === 'object' && value !== null && !Array.isArray(value)
16
+
17
+ const assertFields = (fields: unknown): void => {
18
+ if (fields === undefined) return
19
+ if (!record(fields)) throw new TypeError('invalid AI message base fields')
20
+ for (const [key, undo] of Object.entries(fields)) {
21
+ if (
22
+ !record(undo) ||
23
+ Object.keys(undo).length > 1 ||
24
+ !(
25
+ Object.keys(undo).length === 0 ||
26
+ 'value' in undo ||
27
+ undo['remove'] === true ||
28
+ (key === 'text' &&
29
+ Number.isSafeInteger(undo['length']) &&
30
+ (undo['length'] as number) >= 0)
31
+ )
32
+ )
33
+ throw new TypeError('invalid AI message base field')
34
+ }
35
+ }
36
+
37
+ /** Checks decoded recovery data; `restoreMessageBase` trusts this shape. */
38
+ export function assertMessageBase(
39
+ value: unknown,
40
+ ): asserts value is MessageBase {
41
+ if (
42
+ !record(value) ||
43
+ !Number.isSafeInteger(value['partCount']) ||
44
+ (value['partCount'] as number) < 0 ||
45
+ (value['partCount'] as number) > 0xffffffff - 1 ||
46
+ Object.keys(value).some(
47
+ (key) => !['partCount', 'fields', 'parts'].includes(key),
48
+ ) ||
49
+ (value['parts'] !== undefined && !record(value['parts']))
50
+ )
51
+ throw new TypeError('invalid AI message base')
52
+ assertFields(value['fields'])
53
+ const partCount = value['partCount'] as number
54
+ for (const [key, undo] of Object.entries(value['parts'] ?? {})) {
55
+ const index = Number(key)
56
+ if (
57
+ !Number.isSafeInteger(index) ||
58
+ String(index) !== key ||
59
+ index < 0 ||
60
+ index >= partCount ||
61
+ !record(undo) ||
62
+ Object.keys(undo).length !== 1 ||
63
+ !(('value' in undo && record(undo['value'])) || 'fields' in undo)
64
+ )
65
+ throw new TypeError('invalid AI message base part')
66
+ if ('fields' in undo) assertFields(undo['fields'])
67
+ }
68
+ }
69
+
70
+ const restoreFields = (
71
+ current: Record<string, unknown>,
72
+ fields: FieldsUndo | undefined,
73
+ ): Record<string, unknown> | undefined => {
74
+ if (fields === undefined) return current
75
+ const restored = { ...current }
76
+ for (const [key, undo] of Object.entries(fields)) {
77
+ if ('remove' in undo) delete restored[key]
78
+ else if ('length' in undo) {
79
+ const text = current[key]
80
+ if (typeof text !== 'string' || undo.length > text.length)
81
+ return undefined
82
+ restored[key] = text.slice(0, undo.length)
83
+ } else
84
+ Object.defineProperty(restored, key, {
85
+ value: undo.value,
86
+ enumerable: true,
87
+ writable: true,
88
+ configurable: true,
89
+ })
90
+ }
91
+ return restored
92
+ }
93
+
94
+ /** `undefined` when `base` no longer describes `current`, including a removed message. */
95
+ export const restoreMessageBase = <M extends UIMessage>(options: {
96
+ current: M | undefined
97
+ base: MessageBase | undefined
98
+ }): M | undefined => {
99
+ const { current, base } = options
100
+ if (base === undefined || current === undefined) return undefined
101
+ const parts = current.parts.slice(0, base.partCount)
102
+ for (const [key, undo] of Object.entries(base.parts ?? {})) {
103
+ const index = Number(key)
104
+ if ('value' in undo) {
105
+ parts[index] = undo.value as M['parts'][number]
106
+ continue
107
+ }
108
+ const part = parts[index]
109
+ const restored =
110
+ part === undefined
111
+ ? undefined
112
+ : restoreFields(part as unknown as Record<string, unknown>, undo.fields)
113
+ if (!restored) return undefined
114
+ parts[index] = restored as M['parts'][number]
115
+ }
116
+ if (
117
+ parts.length !== base.partCount ||
118
+ Object.keys(parts).length !== parts.length
119
+ )
120
+ return undefined
121
+ const { parts: _parts, ...fields } = current
122
+ const restored = restoreFields(fields, base.fields)
123
+ return restored && ({ ...restored, parts } as M)
124
+ }
125
+
126
+ const updateFields = (options: {
127
+ before: Record<string, unknown>
128
+ after: Record<string, unknown>
129
+ undo?: FieldsUndo
130
+ }): FieldsUndo | undefined => {
131
+ const { before, after, undo } = options
132
+ const original = restoreFields(before, undo)
133
+ if (!original) return undefined
134
+ return Object.fromEntries(
135
+ [...new Set([...Object.keys(original), ...Object.keys(after)])].flatMap(
136
+ (key) => {
137
+ if (
138
+ Object.hasOwn(original, key) === Object.hasOwn(after, key) &&
139
+ sameValue(original[key], after[key])
140
+ )
141
+ return []
142
+ const value = original[key]
143
+ const next = after[key]
144
+ const change: FieldUndo = !Object.hasOwn(original, key)
145
+ ? { remove: true }
146
+ : key === 'text' &&
147
+ typeof value === 'string' &&
148
+ typeof next === 'string' &&
149
+ next.startsWith(value)
150
+ ? { length: value.length }
151
+ : value === undefined
152
+ ? {}
153
+ : { value }
154
+ return [[key, change]]
155
+ },
156
+ ),
157
+ )
158
+ }
159
+
160
+ /** `undefined` when `before` no longer matches `base`, so the recovery must be dropped. */
161
+ export const updateMessageBase = (options: {
162
+ base: MessageBase
163
+ before: UIMessage
164
+ after: UIMessage
165
+ }): MessageBase | undefined => {
166
+ const { base, before, after } = options
167
+ if (before === after) return base
168
+ const { parts: beforeParts, ...beforeFields } = before
169
+ const { parts: afterParts, ...afterFields } = after
170
+ const fields = updateFields({
171
+ before: beforeFields,
172
+ after: afterFields,
173
+ ...(base.fields && { undo: base.fields }),
174
+ })
175
+ if (!fields) return undefined
176
+ const parts: Array<[string, PartUndo]> = []
177
+ for (let index = 0; index < base.partCount; index += 1) {
178
+ const previous = beforeParts[index]
179
+ const current = afterParts[index]
180
+ const undo = base.parts?.[index]
181
+ const original = undo && 'value' in undo ? undo.value : previous
182
+ if (!original) return undefined
183
+ if (previous === current && undo === undefined) continue
184
+ const originalFields = original as unknown as Record<string, unknown>
185
+ const undoFields = undo && 'fields' in undo ? undo.fields : undefined
186
+ if (!current) {
187
+ const value = restoreFields(originalFields, undoFields)
188
+ if (!value) return undefined
189
+ parts.push([
190
+ String(index),
191
+ { value: value as UIMessage['parts'][number] },
192
+ ])
193
+ continue
194
+ }
195
+ const changed = updateFields({
196
+ before: originalFields,
197
+ after: current as unknown as Record<string, unknown>,
198
+ ...(undoFields && { undo: undoFields }),
199
+ })
200
+ if (!changed) return undefined
201
+ if (Object.keys(changed).length > 0)
202
+ parts.push([String(index), { fields: changed }])
203
+ }
204
+ return {
205
+ partCount: base.partCount,
206
+ ...(Object.keys(fields).length > 0 && { fields }),
207
+ ...(parts.length > 0 && { parts: Object.fromEntries(parts) }),
208
+ }
209
+ }