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,251 +1,135 @@
1
- import { n as validateSync, t as assertSyncSchema } from "./validate-XKT4FSNn.js";
2
- import { t as clientStateProjection } from "./reducer-DEMjEY_O.js";
3
- import { t as contract } from "./contract-CKRg_E4q.js";
1
+ import { a as reducer, o as assertSyncSchema, s as validateSync } from "./reducer-BAXc8JyF.js";
2
+ import { t as contract } from "./contract-BYKASlCV.js";
4
3
  import { t as idempotentId } from "./idempotent-id-BRJVeylj.js";
5
- import { l as ambientToolScopeStorage } from "./internal-Dq2qYxou.js";
6
- //#region src/ai-client-state.ts
7
- const isQueueCommand = (command) => command.action === "send" || command.action === "edit" || command.action === "move" || command.action === "remove" || command.action === "send-now" || command.action === "steer" || command.action === "pause" || command.action === "resume";
8
- const moveItem = ({ items, inputId, beforeId }) => {
9
- const from = items.findIndex((item) => item.id === inputId);
10
- if (from < 0 || inputId === beforeId || beforeId !== null && !items.some((item) => item.id === beforeId)) return items;
11
- const next = [...items];
12
- const [item] = next.splice(from, 1);
13
- const to = beforeId === null ? next.length : next.findIndex((entry) => entry.id === beforeId);
14
- next.splice(to, 0, item);
15
- return next;
4
+ import { i as withReducerRuntime } from "./reducer-runtime-DQwz_J59.js";
5
+ import { u as ambientToolScopeStorage } from "./internal-CbVvEwad.js";
6
+ //#region src/equal-json.ts
7
+ const sameValue = (left, right) => {
8
+ if (Object.is(left, right)) return true;
9
+ if (typeof left !== "object" || left === null || typeof right !== "object" || right === null) return false;
10
+ const prototype = Object.getPrototypeOf(left);
11
+ if (prototype !== Object.getPrototypeOf(right) || prototype !== Object.prototype && prototype !== Array.prototype && prototype !== null) return false;
12
+ if (Array.isArray(left) && (!Array.isArray(right) || left.length !== right.length)) return false;
13
+ const keys = Reflect.ownKeys(left);
14
+ return keys.length === Reflect.ownKeys(right).length && keys.every((key) => Object.hasOwn(right, key) && sameValue(Reflect.get(left, key), Reflect.get(right, key)));
16
15
  };
17
- function projectAIClientState(state) {
18
- const pending = state.pendingQueueCommands ?? [];
19
- if (state.status === "closed") return state;
20
- if (pending.length === 0 && !state.inbox.items.some((item) => item.afterStepOf !== void 0)) return state;
21
- let messages = state.messages;
22
- let { items, paused } = state.inbox;
23
- let turnId = state.active?.turnId ?? null;
24
- let starting = false;
25
- let suspended = state.active?.phase === "paused" || state.active?.phase === "pausing";
26
- let messageIds;
27
- const show = (message) => {
28
- messageIds ??= new Set(messages.map((item) => item.id));
29
- if (messageIds.has(message.id)) return;
30
- if (messages === state.messages) messages = state.messages.slice();
31
- messages.push(message);
32
- messageIds.add(message.id);
33
- };
34
- const admit = () => {
35
- if (turnId !== null || paused || items.length === 0) return;
36
- let count = 0;
37
- for (const item of items) {
38
- count += 1;
39
- show(item.message);
40
- if (item.generate) {
41
- turnId = item.id;
42
- starting = true;
43
- suspended = false;
44
- break;
45
- }
46
- }
47
- if (count > 0) items = items.slice(count);
48
- };
49
- for (const { command } of pending) {
50
- switch (command.action) {
51
- case "send":
52
- case "steer": {
53
- if (items.some((item) => item.id === command.message.id) || turnId === command.message.id) break;
54
- const item = {
55
- ...command.action === "steer" ? { afterStepOf: command.turnId } : {},
56
- id: command.message.id,
57
- message: command.message,
58
- generate: command.message.role === "user" && (command.action === "steer" || command.generate !== false)
59
- };
60
- items = command.action === "steer" ? [item, ...items] : [...items, item];
61
- break;
62
- }
63
- case "edit":
64
- items = items.map((item) => item.id === command.message.id && item.message.role === command.message.role ? {
65
- ...item,
66
- message: command.message
67
- } : item);
68
- break;
69
- case "move":
70
- items = moveItem({
71
- items,
72
- inputId: command.inputId,
73
- beforeId: command.beforeId
74
- });
75
- break;
76
- case "remove":
77
- items = items.filter((item) => item.id !== command.inputId);
78
- break;
79
- case "send-now": {
80
- if (turnId !== command.turnId) break;
81
- const selected = items.find((item) => item.id === command.inputId);
82
- if (!selected || selected.message.role !== "user") break;
83
- items = items.map((item) => item.id === selected.id ? {
84
- ...item,
85
- generate: true,
86
- ...turnId === null ? {} : { afterStepOf: turnId }
87
- } : item);
88
- items = moveItem({
89
- items,
90
- inputId: command.inputId,
91
- beforeId: items[0]?.id ?? null
92
- });
93
- break;
94
- }
95
- case "pause":
96
- paused = true;
97
- if (command.when === "now") suspended = true;
98
- break;
99
- case "resume":
100
- paused = false;
101
- suspended = false;
102
- }
103
- admit();
16
+ //#endregion
17
+ //#region src/ai-message-base.ts
18
+ const record = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
19
+ const assertFields = (fields) => {
20
+ if (fields === void 0) return;
21
+ if (!record(fields)) throw new TypeError("invalid AI message base fields");
22
+ for (const [key, undo] of Object.entries(fields)) if (!record(undo) || Object.keys(undo).length > 1 || !(Object.keys(undo).length === 0 || "value" in undo || undo["remove"] === true || key === "text" && Number.isSafeInteger(undo["length"]) && undo["length"] >= 0)) throw new TypeError("invalid AI message base field");
23
+ };
24
+ /** Checks decoded recovery data; `restoreMessageBase` trusts this shape. */
25
+ function assertMessageBase(value) {
26
+ if (!record(value) || !Number.isSafeInteger(value["partCount"]) || value["partCount"] < 0 || value["partCount"] > 4294967294 || Object.keys(value).some((key) => ![
27
+ "partCount",
28
+ "fields",
29
+ "parts"
30
+ ].includes(key)) || value["parts"] !== void 0 && !record(value["parts"])) throw new TypeError("invalid AI message base");
31
+ assertFields(value["fields"]);
32
+ const partCount = value["partCount"];
33
+ for (const [key, undo] of Object.entries(value["parts"] ?? {})) {
34
+ const index = Number(key);
35
+ if (!Number.isSafeInteger(index) || String(index) !== key || index < 0 || index >= partCount || !record(undo) || Object.keys(undo).length !== 1 || !("value" in undo && record(undo["value"]) || "fields" in undo)) throw new TypeError("invalid AI message base part");
36
+ if ("fields" in undo) assertFields(undo["fields"]);
104
37
  }
105
- items = items.filter((item) => {
106
- if (item.afterStepOf === void 0) return true;
107
- show(item.message);
108
- return false;
109
- });
110
- return {
111
- ...state,
112
- messages,
113
- inbox: {
114
- items,
115
- paused
116
- },
117
- starting: starting && !suspended
118
- };
119
38
  }
120
- //#endregion
121
- //#region src/ai-control-state.ts
122
- const container = (value) => typeof value === "object" && value !== null;
123
- const same = (left, right) => JSON.stringify(left) === JSON.stringify(right);
124
- const controlChanges = ({ before, after }) => {
125
- const changes = [];
126
- const visit = (left, right, path) => {
127
- if (same(left, right)) return;
128
- if (Array.isArray(left) && Array.isArray(right)) {
129
- let prefix = 0;
130
- while (prefix < Math.min(left.length, right.length) && same(left[prefix], right[prefix])) prefix += 1;
131
- let suffix = 0;
132
- while (suffix < Math.min(left.length, right.length) - prefix && same(left[left.length - 1 - suffix], right[right.length - 1 - suffix])) suffix += 1;
133
- if (left.length === right.length) for (let index = prefix; index < left.length - suffix; index += 1) visit(left[index], right[index], [...path, index]);
134
- else changes.push({
135
- path,
136
- splice: {
137
- index: prefix,
138
- deleteCount: left.length - prefix - suffix,
139
- items: right.slice(prefix, right.length - suffix)
140
- }
141
- });
142
- return;
143
- }
144
- if (container(left) && container(right) && !Array.isArray(left) && !Array.isArray(right)) {
145
- if (Object.keys(right).length === 0) {
146
- changes.push({
147
- path,
148
- value: right
149
- });
150
- return;
151
- }
152
- for (const key of Object.keys(left)) if (!Object.hasOwn(right, key)) changes.push({
153
- path: [...path, key],
154
- remove: true
155
- });
156
- for (const key of Object.keys(right)) visit(Object.hasOwn(left, key) ? left[key] : void 0, right[key], [...path, key]);
157
- return;
39
+ const restoreFields = (current, fields) => {
40
+ if (fields === void 0) return current;
41
+ const restored = { ...current };
42
+ for (const [key, undo] of Object.entries(fields)) if ("remove" in undo) delete restored[key];
43
+ else if ("length" in undo) {
44
+ const text = current[key];
45
+ if (typeof text !== "string" || undo.length > text.length) return void 0;
46
+ restored[key] = text.slice(0, undo.length);
47
+ } else Object.defineProperty(restored, key, {
48
+ value: undo.value,
49
+ enumerable: true,
50
+ writable: true,
51
+ configurable: true
52
+ });
53
+ return restored;
54
+ };
55
+ /** `undefined` when `base` no longer describes `current`, including a removed message. */
56
+ const restoreMessageBase = (options) => {
57
+ const { current, base } = options;
58
+ if (base === void 0 || current === void 0) return void 0;
59
+ const parts = current.parts.slice(0, base.partCount);
60
+ for (const [key, undo] of Object.entries(base.parts ?? {})) {
61
+ const index = Number(key);
62
+ if ("value" in undo) {
63
+ parts[index] = undo.value;
64
+ continue;
158
65
  }
159
- changes.push({
160
- path,
161
- value: right
162
- });
66
+ const part = parts[index];
67
+ const restored = part === void 0 ? void 0 : restoreFields(part, undo.fields);
68
+ if (!restored) return void 0;
69
+ parts[index] = restored;
70
+ }
71
+ if (parts.length !== base.partCount || Object.keys(parts).length !== parts.length) return void 0;
72
+ const { parts: _parts, ...fields } = current;
73
+ const restored = restoreFields(fields, base.fields);
74
+ return restored && {
75
+ ...restored,
76
+ parts
163
77
  };
164
- visit(before, after, []);
165
- return changes;
166
78
  };
167
- const applyControlChanges = ({ state, changes }) => {
168
- if (changes.length === 0) return state;
169
- let next = structuredClone(state);
170
- for (const change of changes) {
171
- const path = change.path;
172
- if (path.length === 0) {
173
- next = structuredClone(change.value);
79
+ const updateFields = (options) => {
80
+ const { before, after, undo } = options;
81
+ const original = restoreFields(before, undo);
82
+ if (!original) return void 0;
83
+ return Object.fromEntries([.../* @__PURE__ */ new Set([...Object.keys(original), ...Object.keys(after)])].flatMap((key) => {
84
+ if (Object.hasOwn(original, key) === Object.hasOwn(after, key) && sameValue(original[key], after[key])) return [];
85
+ const value = original[key];
86
+ const next = after[key];
87
+ return [[key, !Object.hasOwn(original, key) ? { remove: true } : key === "text" && typeof value === "string" && typeof next === "string" && next.startsWith(value) ? { length: value.length } : value === void 0 ? {} : { value }]];
88
+ }));
89
+ };
90
+ /** `undefined` when `before` no longer matches `base`, so the recovery must be dropped. */
91
+ const updateMessageBase = (options) => {
92
+ const { base, before, after } = options;
93
+ if (before === after) return base;
94
+ const { parts: beforeParts, ...beforeFields } = before;
95
+ const { parts: afterParts, ...afterFields } = after;
96
+ const fields = updateFields({
97
+ before: beforeFields,
98
+ after: afterFields,
99
+ ...base.fields && { undo: base.fields }
100
+ });
101
+ if (!fields) return void 0;
102
+ const parts = [];
103
+ for (let index = 0; index < base.partCount; index += 1) {
104
+ const previous = beforeParts[index];
105
+ const current = afterParts[index];
106
+ const undo = base.parts?.[index];
107
+ const original = undo && "value" in undo ? undo.value : previous;
108
+ if (!original) return void 0;
109
+ if (previous === current && undo === void 0) continue;
110
+ const originalFields = original;
111
+ const undoFields = undo && "fields" in undo ? undo.fields : void 0;
112
+ if (!current) {
113
+ const value = restoreFields(originalFields, undoFields);
114
+ if (!value) return void 0;
115
+ parts.push([String(index), { value }]);
174
116
  continue;
175
117
  }
176
- let target = next;
177
- for (const key of path.slice(0, -1)) {
178
- if (!container(target) || !Object.hasOwn(target, key)) throw new TypeError("Invalid AI control change path");
179
- target = target[key];
180
- }
181
- if (!container(target)) throw new TypeError("Invalid AI control change target");
182
- const parent = target;
183
- const key = path.at(-1);
184
- if (change.remove) delete parent[key];
185
- else if (change.splice) {
186
- const items = parent[key];
187
- Object.defineProperty(parent, key, {
188
- value: [
189
- ...items.slice(0, change.splice.index),
190
- ...structuredClone(change.splice.items),
191
- ...items.slice(change.splice.index + change.splice.deleteCount)
192
- ],
193
- enumerable: true,
194
- configurable: true,
195
- writable: true
196
- });
197
- } else if (change.move) {
198
- const items = parent[key];
199
- const [item] = items.splice(change.move.from, 1);
200
- items.splice(change.move.to, 0, item);
201
- } else Object.defineProperty(parent, key, {
202
- value: structuredClone(change.value),
203
- enumerable: true,
204
- configurable: true,
205
- writable: true
118
+ const changed = updateFields({
119
+ before: originalFields,
120
+ after: current,
121
+ ...undoFields && { undo: undoFields }
206
122
  });
123
+ if (!changed) return void 0;
124
+ if (Object.keys(changed).length > 0) parts.push([String(index), { fields: changed }]);
207
125
  }
208
- return next;
209
- };
210
- const controlCommit = (options) => {
211
- const active = options.after.active;
212
- const response = options.after.coordinator.response;
213
126
  return {
214
- changes: [...options.moves, ...controlChanges({
215
- before: options.before,
216
- after: options.after
217
- })],
218
- view: {
219
- turnId: active?.turnId ?? null,
220
- version: active?.version ?? null,
221
- phase: active?.phase ?? null,
222
- paused: options.after.inbox.paused,
223
- closed: options.after.coordinator.closed,
224
- requestId: response?.activeRequestId ?? null,
225
- requestReason: response?.requestReason ?? null,
226
- sourceGenerationId: response?.sourceGenerationId ?? null,
227
- responseMessageId: response?.responseMessageId ?? null
228
- }
127
+ partCount: base.partCount,
128
+ ...Object.keys(fields).length > 0 && { fields },
129
+ ...parts.length > 0 && { parts: Object.fromEntries(parts) }
229
130
  };
230
131
  };
231
132
  //#endregion
232
- //#region src/ai-control.ts
233
- const initialControlState = () => ({
234
- inbox: {
235
- paused: false,
236
- items: []
237
- },
238
- active: null,
239
- coordinator: {
240
- closed: false,
241
- queued: []
242
- }
243
- });
244
- //#endregion
245
- //#region src/ai-id.ts
246
- const AI_EVENT_ID_PREFIX = "a2.ai:";
247
- const aiEventId = async (...parts) => `${AI_EVENT_ID_PREFIX}${await idempotentId(...parts)}`;
248
- //#endregion
249
133
  //#region src/parse-partial-json.ts
250
134
  /*!
251
135
  * Adapted from AI SDK 7.0.58, packages/ai/src/util/fix-json.ts.
@@ -527,7 +411,13 @@ const cloneMessage = (messageId, base) => base === void 0 ? {
527
411
  } : {
528
412
  ...base,
529
413
  id: messageId,
530
- parts: base.parts.map((part) => ({ ...part }))
414
+ parts: [...base.parts]
415
+ };
416
+ const editPart = ({ parts, part }) => {
417
+ const index = parts.indexOf(part);
418
+ const next = { ...part };
419
+ parts[index] = next;
420
+ return next;
531
421
  };
532
422
  const currentStepParts = (parts) => {
533
423
  let start = parts.length - 1;
@@ -540,6 +430,10 @@ const findToolPart = (parts, toolCallId) => {
540
430
  if (current) return current;
541
431
  return parts.findLast((part) => isToolPart(part) && part["toolCallId"] === toolCallId);
542
432
  };
433
+ const hasToolCall = ({ message, call }) => {
434
+ const part = findToolPart(message.parts, call.toolCallId);
435
+ return part !== void 0 && part["state"] !== "input-streaming" && sameValue(part["input"], call.input) && (part["callProviderMetadata"] !== void 0 || call.providerMetadata === void 0) && (part["toolMetadata"] !== void 0 || call.toolMetadata === void 0) && (call.providerExecuted === void 0 || part["providerExecuted"] === call.providerExecuted) && (call.title === void 0 || part["title"] === call.title);
436
+ };
543
437
  const findApprovalPart = (parts, approvalId) => parts.find((part) => {
544
438
  const approval = part["approval"];
545
439
  return isToolPart(part) && isRecord$3(approval) && approval["id"] === approvalId;
@@ -556,7 +450,10 @@ const setToolPart = (options) => {
556
450
  toolCallId: options.toolCallId
557
451
  };
558
452
  options.parts.push(part);
559
- }
453
+ } else part = editPart({
454
+ parts: options.parts,
455
+ part
456
+ });
560
457
  part["state"] = options.state;
561
458
  part["input"] = options.input;
562
459
  delete part["output"];
@@ -614,13 +511,22 @@ function projectUIMessageStream(options) {
614
511
  activeText.set(chunk.id, part);
615
512
  parts.push(part);
616
513
  }
514
+ part = editPart({
515
+ parts,
516
+ part
517
+ });
518
+ activeText.set(chunk.id, part);
617
519
  part["text"] = `${typeof part["text"] === "string" ? part["text"] : ""}${chunk.delta}`;
618
520
  setOptional(part, "providerMetadata", chunk.providerMetadata);
619
521
  break;
620
522
  }
621
523
  case "text-end": {
622
- const part = activeText.get(chunk.id);
623
- if (part) {
524
+ const current = activeText.get(chunk.id);
525
+ if (current) {
526
+ const part = editPart({
527
+ parts,
528
+ part: current
529
+ });
624
530
  part["state"] = "done";
625
531
  setOptional(part, "providerMetadata", chunk.providerMetadata);
626
532
  activeText.delete(chunk.id);
@@ -649,13 +555,22 @@ function projectUIMessageStream(options) {
649
555
  activeReasoning.set(chunk.id, part);
650
556
  parts.push(part);
651
557
  }
652
- part["text"] = `${typeof part["text"] === "string" ? part["text"] : ""}${chunk.delta}`;
558
+ part = editPart({
559
+ parts,
560
+ part
561
+ });
562
+ activeReasoning.set(chunk.id, part);
563
+ part["text"] = `${typeof part["text"] === "string" ? part["text"] : ""}${chunk.delta}`;
653
564
  setOptional(part, "providerMetadata", chunk.providerMetadata);
654
565
  break;
655
566
  }
656
567
  case "reasoning-end": {
657
- const part = activeReasoning.get(chunk.id);
658
- if (part) {
568
+ const current = activeReasoning.get(chunk.id);
569
+ if (current) {
570
+ const part = editPart({
571
+ parts,
572
+ part: current
573
+ });
659
574
  part["state"] = "done";
660
575
  setOptional(part, "providerMetadata", chunk.providerMetadata);
661
576
  activeReasoning.delete(chunk.id);
@@ -737,6 +652,35 @@ function projectUIMessageStream(options) {
737
652
  });
738
653
  break;
739
654
  }
655
+ case "a2-tool-called": {
656
+ const call = chunk.call;
657
+ const current = findToolPart(parts, call.toolCallId);
658
+ if (current) {
659
+ const existing = editPart({
660
+ parts,
661
+ part: current
662
+ });
663
+ existing["input"] = call.input;
664
+ if (existing["state"] === "input-streaming") existing["state"] = "input-available";
665
+ if (existing["callProviderMetadata"] === void 0) setOptional(existing, "callProviderMetadata", call.providerMetadata);
666
+ if (existing["toolMetadata"] === void 0) setOptional(existing, "toolMetadata", call.toolMetadata);
667
+ setOptional(existing, "providerExecuted", call.providerExecuted);
668
+ setOptional(existing, "title", call.title);
669
+ } else setToolPart({
670
+ parts,
671
+ toolCallId: call.toolCallId,
672
+ toolName: call.toolName,
673
+ dynamic: call.dynamic === true,
674
+ state: "input-available",
675
+ input: call.input,
676
+ providerExecuted: call.providerExecuted,
677
+ providerMetadata: call.providerMetadata,
678
+ toolMetadata: call.toolMetadata,
679
+ title: call.title
680
+ });
681
+ partialTools.delete(call.toolCallId);
682
+ break;
683
+ }
740
684
  case "tool-input-available":
741
685
  setToolPart({
742
686
  parts,
@@ -772,8 +716,12 @@ function projectUIMessageStream(options) {
772
716
  break;
773
717
  }
774
718
  case "tool-approval-request": {
775
- const part = findToolPart(parts, chunk.toolCallId);
776
- if (!part) break;
719
+ const current = findToolPart(parts, chunk.toolCallId);
720
+ if (!current) break;
721
+ const part = editPart({
722
+ parts,
723
+ part: current
724
+ });
777
725
  part["state"] = "approval-requested";
778
726
  part["approval"] = {
779
727
  id: chunk.approvalId,
@@ -783,8 +731,12 @@ function projectUIMessageStream(options) {
783
731
  break;
784
732
  }
785
733
  case "tool-approval-response": {
786
- const part = findApprovalPart(parts, chunk.approvalId);
787
- if (!part) break;
734
+ const current = findApprovalPart(parts, chunk.approvalId);
735
+ if (!current) break;
736
+ const part = editPart({
737
+ parts,
738
+ part: current
739
+ });
788
740
  const approval = isRecord$3(part["approval"]) ? part["approval"] : {};
789
741
  part["state"] = "approval-responded";
790
742
  part["approval"] = {
@@ -838,7 +790,10 @@ function projectUIMessageStream(options) {
838
790
  }
839
791
  case "tool-output-denied": {
840
792
  const part = findToolPart(parts, chunk.toolCallId);
841
- if (part) part["state"] = "output-denied";
793
+ if (part) editPart({
794
+ parts,
795
+ part
796
+ })["state"] = "output-denied";
842
797
  break;
843
798
  }
844
799
  case "start-step":
@@ -862,7 +817,10 @@ function projectUIMessageStream(options) {
862
817
  default: {
863
818
  if (!chunk.type.startsWith("data-") || chunk.transient) break;
864
819
  const existing = chunk.id === void 0 ? void 0 : parts.find((part) => part["type"] === chunk.type && part["id"] === chunk.id);
865
- if (existing) existing["data"] = chunk.data;
820
+ if (existing) editPart({
821
+ parts,
822
+ part: existing
823
+ })["data"] = chunk.data;
866
824
  else parts.push({
867
825
  type: chunk.type,
868
826
  ...chunk.id === void 0 ? {} : { id: chunk.id },
@@ -923,6 +881,7 @@ function interruptUIMessage(message, toolError = "Tool execution was interrupted
923
881
  }
924
882
  //#endregion
925
883
  //#region src/ai-progress-batches.ts
884
+ const progressOwnedBy = ({ payload, owner, startIndex }) => "startIndex" in payload ? payload.startIndex === startIndex : payload.requestId === owner.requestId && payload.generationId === owner.generationId && payload.responseMessageId === owner.responseMessageId;
926
885
  const PAGE_SIZE = 64;
927
886
  const appendProgressBatch = ({ batches, batch }) => {
928
887
  if (batches.tail.length < PAGE_SIZE) return {
@@ -981,22 +940,22 @@ const flattenProgressBatches = (batches) => {
981
940
  //#endregion
982
941
  //#region src/ai-message-projection.ts
983
942
  const isRecord$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
943
+ const positionOf = (messages, id) => messages.findLastIndex((message) => message.id === id);
944
+ const findMessage = (messages, id) => messages.findLast((message) => message.id === id);
984
945
  const upsertMessage = (messages, message) => {
985
- const index = messages.findIndex((candidate) => candidate.id === message.id);
946
+ const index = positionOf(messages, message.id);
986
947
  if (index === -1) return [...messages, message];
987
- const next = [...messages];
988
- next[index] = message;
989
- return next;
948
+ return messages[index] === message ? messages : messages.with(index, message);
990
949
  };
991
950
  const upsertResponse = (options) => {
992
951
  const { messages, message, inputId } = options;
993
- if (messages.some((item) => item.id === message.id)) return upsertMessage(messages, message);
994
- const inputIndex = messages.findIndex((item) => item.id === inputId);
995
- if (inputIndex === -1) return [...messages, message];
952
+ if (positionOf(messages, message.id) !== -1) return upsertMessage(messages, message);
953
+ const inputIndex = positionOf(messages, inputId);
954
+ const position = inputIndex === -1 ? messages.length : inputIndex + 1;
996
955
  return [
997
- ...messages.slice(0, inputIndex + 1),
956
+ ...messages.slice(0, position),
998
957
  message,
999
- ...messages.slice(inputIndex + 1)
958
+ ...messages.slice(position)
1000
959
  ];
1001
960
  };
1002
961
  const answerApproval = (messages, response) => messages.map((message) => {
@@ -1034,6 +993,10 @@ const approvalRespondedChunk = (payload) => ({
1034
993
  approved: payload.approved,
1035
994
  ...payload.reason === void 0 ? {} : { reason: payload.reason }
1036
995
  });
996
+ const toolCalledChunk = (payload) => ({
997
+ type: "a2-tool-called",
998
+ call: payload
999
+ });
1037
1000
  const toolResultChunk = (payload) => {
1038
1001
  if (payload.phase === "input") return {
1039
1002
  type: "tool-input-error",
@@ -1070,97 +1033,525 @@ const toolResultChunk = (payload) => {
1070
1033
  ...payload.toolMetadata === void 0 ? {} : { toolMetadata: payload.toolMetadata }
1071
1034
  };
1072
1035
  };
1073
- const projectedMessageStream = (projection) => {
1074
- const projectionEvents = flattenProgressBatches(projection.batches);
1075
- for (const event of projection.toolEvents) if (event.type === "ai.tool.result") projectionEvents.push({
1076
- index: event.index,
1077
- chunks: [toolResultChunk(event.payload)]
1078
- });
1079
- for (const event of projection.approvalEvents) projectionEvents.push({
1080
- index: event.index,
1081
- chunks: [event.type === "ai.approval.requested" ? approvalRequestedChunk(event.payload) : approvalRespondedChunk(event.payload)]
1082
- });
1083
- return projectUIMessageStream({
1084
- messageId: projection.responseMessageId,
1085
- ...projection.baseMessage === void 0 ? {} : { base: projection.baseMessage },
1086
- chunks: projectionEvents.toSorted((left, right) => left.index - right.index).flatMap((event) => event.chunks)
1087
- });
1088
- };
1089
- const projectedMessage = (projection) => projectedMessageStream(projection).message;
1090
- const reduceToolActivity = (tools, event) => {
1091
- if (event.type === "ai.tool.called") {
1092
- const payload = event.payload;
1093
- const { providerMetadata, ...called } = payload;
1094
- const activity = {
1095
- ...called,
1096
- ...providerMetadata === void 0 ? {} : { callProviderMetadata: providerMetadata },
1097
- status: "running"
1098
- };
1099
- return [...tools.filter((candidate) => candidate.generationId !== payload.generationId || candidate.toolCallId !== payload.toolCallId), activity];
1036
+ const projectedMessageStream = ({ projection, current }) => {
1037
+ const base = restoreMessageBase({
1038
+ current,
1039
+ base: projection.base
1040
+ });
1041
+ const projectionEvents = flattenProgressBatches(projection.batches);
1042
+ for (const event of projection.toolEvents) projectionEvents.push({
1043
+ index: event.index,
1044
+ chunks: [event.type === "ai.tool.called" ? toolCalledChunk(event.payload) : toolResultChunk(event.payload)]
1045
+ });
1046
+ for (const event of projection.approvalEvents) projectionEvents.push({
1047
+ index: event.index,
1048
+ chunks: [event.type === "ai.approval.requested" ? approvalRequestedChunk(event.payload) : approvalRespondedChunk(event.payload)]
1049
+ });
1050
+ const ordered = projectionEvents.toSorted((left, right) => left.index - right.index);
1051
+ return {
1052
+ ...projectUIMessageStream({
1053
+ messageId: projection.responseMessageId,
1054
+ ...base === void 0 ? {} : { base },
1055
+ chunks: ordered.flatMap((event) => event.chunks)
1056
+ }),
1057
+ throughIndex: ordered.at(-1)?.index ?? 0
1058
+ };
1059
+ };
1060
+ const projectedMessage = (options) => projectedMessageStream(options).message;
1061
+ const reduceToolActivity = (tools, event) => {
1062
+ if (event.type === "ai.tool.called") {
1063
+ const payload = event.payload;
1064
+ const activity = {
1065
+ requestId: payload.requestId,
1066
+ messageId: payload.messageId,
1067
+ generationId: payload.generationId,
1068
+ toolCallId: payload.toolCallId,
1069
+ toolName: payload.toolName,
1070
+ ...payload.dynamic === void 0 ? {} : { dynamic: payload.dynamic },
1071
+ ...payload.providerExecuted === void 0 ? {} : { providerExecuted: payload.providerExecuted },
1072
+ ...payload.title === void 0 ? {} : { title: payload.title },
1073
+ status: "running"
1074
+ };
1075
+ return [...tools.filter((candidate) => candidate.generationId !== payload.generationId || candidate.toolCallId !== payload.toolCallId), activity];
1076
+ }
1077
+ const payload = event.payload;
1078
+ const existing = tools.find((candidate) => candidate.generationId === payload.generationId && candidate.toolCallId === payload.toolCallId);
1079
+ const toolName = payload.toolName ?? existing?.toolName;
1080
+ const dynamic = payload.dynamic ?? existing?.dynamic;
1081
+ const providerExecuted = payload.providerExecuted ?? existing?.providerExecuted;
1082
+ const activity = {
1083
+ requestId: payload.requestId,
1084
+ messageId: payload.messageId,
1085
+ generationId: payload.generationId,
1086
+ toolCallId: payload.toolCallId,
1087
+ ...toolName === void 0 ? {} : { toolName },
1088
+ ...payload.error === void 0 ? {} : { error: payload.error },
1089
+ ...payload.preliminary === void 0 ? {} : { preliminary: payload.preliminary },
1090
+ ...payload.phase === void 0 ? {} : { phase: payload.phase },
1091
+ ...dynamic === void 0 ? {} : { dynamic },
1092
+ ...providerExecuted === void 0 ? {} : { providerExecuted },
1093
+ ...existing?.title === void 0 ? {} : { title: existing.title },
1094
+ status: payload.denied ? "denied" : payload.error ? "failed" : payload.preliminary ? "running" : "completed"
1095
+ };
1096
+ return [...tools.filter((candidate) => candidate.generationId !== payload.generationId || candidate.toolCallId !== payload.toolCallId), activity];
1097
+ };
1098
+ const advanceProjection = ({ projection, base, chunks, index }) => {
1099
+ const batches = index > (projection.batches.tail.at(-1)?.index ?? -1) ? appendProgressBatch({
1100
+ batches: projection.batches,
1101
+ batch: {
1102
+ index,
1103
+ chunks
1104
+ }
1105
+ }) : progressBatches([...flattenProgressBatches(projection.batches).filter((batch) => batch.index !== index), {
1106
+ index,
1107
+ chunks
1108
+ }].toSorted((left, right) => left.index - right.index));
1109
+ const incremental = base !== void 0 && projection.cursor != null && index > projection.cursor.throughIndex;
1110
+ const projected = incremental ? projectUIMessageStream({
1111
+ messageId: projection.responseMessageId,
1112
+ base,
1113
+ cursor: projection.cursor.stream,
1114
+ chunks
1115
+ }) : projectedMessageStream({
1116
+ projection: {
1117
+ ...projection,
1118
+ batches
1119
+ },
1120
+ current: base
1121
+ });
1122
+ return {
1123
+ message: projected.message,
1124
+ batches,
1125
+ cursor: {
1126
+ throughIndex: incremental ? index : Math.max(index, batches.tail.at(-1)?.index ?? 0, projection.toolEvents.reduce((latest, event) => Math.max(latest, event.index), 0), projection.approvalEvents.reduce((latest, event) => Math.max(latest, event.index), 0)),
1127
+ stream: projected.cursor
1128
+ }
1129
+ };
1130
+ };
1131
+ const projectedTools = (projection) => projection.toolEvents.reduce((tools, event) => reduceToolActivity(tools, event), projection.baseTools);
1132
+ const restoreGenerationBase = (state, generationId, responseMessageId, reason) => {
1133
+ const projection = state.activeProjection?.generationId === generationId ? state.activeProjection : null;
1134
+ const restored = restoreMessageBase({
1135
+ current: findMessage(state.messages, responseMessageId),
1136
+ base: projection?.base
1137
+ });
1138
+ const base = restored !== void 0 && reason === "superseded" ? withoutRunningCompactionPart(restored, generationId) : restored;
1139
+ return {
1140
+ ...state,
1141
+ messages: base === void 0 ? state.messages.filter((message) => message.id !== responseMessageId) : upsertMessage(state.messages, base),
1142
+ tools: projection === null ? state.tools.filter((activity) => activity.generationId !== generationId) : projection.baseTools,
1143
+ pendingApprovals: state.pendingApprovals.filter((approval) => approval.generationId !== generationId),
1144
+ pendingInputs: state.pendingInputs.filter((input) => input.generationId !== generationId),
1145
+ activeGeneration: null,
1146
+ activeProjection: null,
1147
+ terminalGenerations: {
1148
+ ...state.terminalGenerations,
1149
+ [generationId]: reason
1150
+ },
1151
+ compaction: projection === null ? state.compaction?.generationId === generationId ? null : state.compaction : projection.baseCompaction ?? null
1152
+ };
1153
+ };
1154
+ const stopGenerationTools = (tools, generationId, responseMessageId, error) => tools.map((activity) => activity.status === "running" && (generationId === void 0 ? activity.messageId === responseMessageId : activity.generationId === generationId) ? {
1155
+ ...activity,
1156
+ status: "failed",
1157
+ error,
1158
+ preliminary: false
1159
+ } : activity);
1160
+ const interruptGenerationProjection = (state, generationId, responseMessageId, cutoff) => {
1161
+ const projection = state.activeProjection?.generationId === generationId ? state.activeProjection : null;
1162
+ if (projection === null) return null;
1163
+ const retained = {
1164
+ ...projection,
1165
+ batches: progressBatches(flattenProgressBatches(projection.batches).filter((batch) => batch.index <= cutoff)),
1166
+ toolEvents: projection.toolEvents.filter((toolEvent) => toolEvent.index <= cutoff),
1167
+ approvalEvents: projection.approvalEvents.filter((approvalEvent) => approvalEvent.index <= cutoff)
1168
+ };
1169
+ const shouldProject = retained.base !== void 0 || retained.batches.length > 0;
1170
+ const restored = restoreGenerationBase(state, generationId, responseMessageId, "interrupted");
1171
+ return {
1172
+ ...restored,
1173
+ messages: shouldProject ? upsertMessage(state.messages, interruptUIMessage(projectedMessage({
1174
+ projection: retained,
1175
+ current: findMessage(state.messages, responseMessageId)
1176
+ }))) : restored.messages,
1177
+ tools: stopGenerationTools(projectedTools(retained), generationId, responseMessageId, "Tool execution was interrupted."),
1178
+ usage: projection.completionIndex === void 0 || projection.completionIndex <= cutoff ? restored.usage : restored.usage.filter((entry) => entry.generationId !== generationId)
1179
+ };
1180
+ };
1181
+ const retainMessageBase = (options) => {
1182
+ const { before, after } = options;
1183
+ const projection = after.activeProjection;
1184
+ if (before.messages === after.messages || !projection?.base || projection.base !== before.activeProjection?.base) return after;
1185
+ const previous = findMessage(before.messages, projection.responseMessageId);
1186
+ const current = findMessage(after.messages, projection.responseMessageId);
1187
+ if (previous !== void 0 && previous === current) return after;
1188
+ const base = previous && current && updateMessageBase({
1189
+ base: projection.base,
1190
+ before: previous,
1191
+ after: current
1192
+ });
1193
+ const { base: _stale, ...unrecoverable } = projection;
1194
+ return {
1195
+ ...after,
1196
+ activeProjection: base ? {
1197
+ ...unrecoverable,
1198
+ base
1199
+ } : unrecoverable
1200
+ };
1201
+ };
1202
+ const isCompactionPart = (part) => part.type === "data-a2-compaction";
1203
+ const compactionPartOf = (message, generationId) => message.parts.findIndex((part) => isCompactionPart(part) && part.id === generationId);
1204
+ const withoutRunningCompactionPart = (message, generationId) => {
1205
+ const index = compactionPartOf(message, generationId);
1206
+ const part = message.parts[index];
1207
+ return part !== void 0 && isCompactionPart(part) && part.data.status === "running" ? {
1208
+ ...message,
1209
+ parts: message.parts.toSpliced(index, 1)
1210
+ } : message;
1211
+ };
1212
+ /**
1213
+ * Compaction runs before its model step streams, so the part lands between
1214
+ * the response's earlier steps and this one. Counting it in the step's base
1215
+ * keeps it through Stop cutoffs, failures, and retries of that step.
1216
+ */
1217
+ const addCompactionPart = (state, payload) => {
1218
+ const projection = state.activeProjection;
1219
+ if (projection?.generationId !== payload.generationId) return state;
1220
+ const current = findMessage(state.messages, projection.responseMessageId);
1221
+ if (current === void 0 ? projection.base !== void 0 : projection.base?.partCount !== current.parts.length) return state;
1222
+ const inputTokens = payload.inputTokens ?? payload.inputTokenEstimate;
1223
+ const part = {
1224
+ type: "data-a2-compaction",
1225
+ id: payload.generationId,
1226
+ data: {
1227
+ status: "running",
1228
+ ...inputTokens === void 0 ? {} : { inputTokens },
1229
+ ...payload.thresholdTokens === void 0 ? {} : { thresholdTokens: payload.thresholdTokens }
1230
+ }
1231
+ };
1232
+ const message = current === void 0 ? {
1233
+ id: projection.responseMessageId,
1234
+ role: "assistant",
1235
+ parts: [part]
1236
+ } : {
1237
+ ...current,
1238
+ parts: [...current.parts, part]
1239
+ };
1240
+ return {
1241
+ ...state,
1242
+ messages: upsertResponse({
1243
+ messages: state.messages,
1244
+ message,
1245
+ inputId: state.activeGeneration?.messageId ?? message.id
1246
+ }),
1247
+ activeProjection: {
1248
+ ...projection,
1249
+ cursor: null,
1250
+ base: projection.base === void 0 ? { partCount: 1 } : {
1251
+ ...projection.base,
1252
+ partCount: projection.base.partCount + 1
1253
+ }
1254
+ }
1255
+ };
1256
+ };
1257
+ const settleCompactionPart = (options) => {
1258
+ const { state, generationId, data } = options;
1259
+ const position = state.messages.findLastIndex((message) => compactionPartOf(message, generationId) !== -1);
1260
+ if (position === -1) return state;
1261
+ const message = state.messages[position];
1262
+ const index = compactionPartOf(message, generationId);
1263
+ const part = message.parts[index];
1264
+ return {
1265
+ ...state,
1266
+ messages: state.messages.with(position, {
1267
+ ...message,
1268
+ parts: message.parts.with(index, {
1269
+ ...part,
1270
+ data: {
1271
+ ...part.data,
1272
+ ...data
1273
+ }
1274
+ })
1275
+ })
1276
+ };
1277
+ };
1278
+ /** A running compaction whose state reverted without completing was abandoned. */
1279
+ const settleAbandonedCompaction = (before, after) => {
1280
+ const running = before.compaction;
1281
+ if (running?.status !== "running" || after.compaction === running || after.compaction?.status === "completed" && after.compaction.generationId === running.generationId) return after;
1282
+ const interrupted = after.status === "closed" || after.pausedGenerationIds?.[running.generationId] === true || after.terminalGenerations?.[running.generationId] === "interrupted";
1283
+ return settleCompactionPart({
1284
+ state: after,
1285
+ generationId: running.generationId,
1286
+ data: { status: interrupted ? "interrupted" : "failed" }
1287
+ });
1288
+ };
1289
+ //#endregion
1290
+ //#region src/ai-client-state.ts
1291
+ const isQueueCommand = (command) => command.action === "send" || command.action === "edit" || command.action === "move" || command.action === "remove" || command.action === "send-now" || command.action === "steer" || command.action === "pause" || command.action === "resume" || command.action === "stop";
1292
+ const moveItem = ({ items, inputId, beforeId }) => {
1293
+ const from = items.findIndex((item) => item.id === inputId);
1294
+ if (from < 0 || inputId === beforeId || beforeId !== null && !items.some((item) => item.id === beforeId)) return items;
1295
+ const next = [...items];
1296
+ const [item] = next.splice(from, 1);
1297
+ const to = beforeId === null ? next.length : next.findIndex((entry) => entry.id === beforeId);
1298
+ next.splice(to, 0, item);
1299
+ return next;
1300
+ };
1301
+ function projectAIClientState(state) {
1302
+ const pending = state.pendingQueueCommands ?? [];
1303
+ if (state.status === "closed") return state;
1304
+ if (pending.length === 0 && !state.inbox.items.some((item) => item.afterStepOf !== void 0)) return state;
1305
+ let messages = state.messages;
1306
+ let { items, paused } = state.inbox;
1307
+ let turnId = state.active?.turnId ?? null;
1308
+ let starting = false;
1309
+ let suspended = state.active?.phase === "paused" || state.active?.phase === "pausing";
1310
+ let messageIds;
1311
+ const show = (message) => {
1312
+ messageIds ??= new Set(messages.map((item) => item.id));
1313
+ if (messageIds.has(message.id)) return;
1314
+ if (messages === state.messages) messages = state.messages.slice();
1315
+ messages.push(message);
1316
+ messageIds.add(message.id);
1317
+ };
1318
+ const admit = () => {
1319
+ if (turnId !== null || paused || items.length === 0) return;
1320
+ let count = 0;
1321
+ for (const item of items) {
1322
+ count += 1;
1323
+ show(item.message);
1324
+ if (item.generate) {
1325
+ turnId = item.id;
1326
+ starting = true;
1327
+ suspended = false;
1328
+ break;
1329
+ }
1330
+ }
1331
+ if (count > 0) items = items.slice(count);
1332
+ };
1333
+ for (const { command } of pending) {
1334
+ switch (command.action) {
1335
+ case "send":
1336
+ case "steer": {
1337
+ if (items.some((item) => item.id === command.message.id) || turnId === command.message.id) break;
1338
+ const item = {
1339
+ ...command.action === "steer" ? { afterStepOf: command.turnId } : {},
1340
+ id: command.message.id,
1341
+ message: command.message,
1342
+ generate: command.message.role === "user" && (command.action === "steer" || command.generate !== false)
1343
+ };
1344
+ items = command.action === "steer" ? [item, ...items] : [...items, item];
1345
+ break;
1346
+ }
1347
+ case "edit":
1348
+ items = items.map((item) => item.id === command.message.id && item.message.role === command.message.role ? {
1349
+ ...item,
1350
+ message: command.message
1351
+ } : item);
1352
+ break;
1353
+ case "move":
1354
+ items = moveItem({
1355
+ items,
1356
+ inputId: command.inputId,
1357
+ beforeId: command.beforeId
1358
+ });
1359
+ break;
1360
+ case "remove":
1361
+ items = items.filter((item) => item.id !== command.inputId);
1362
+ break;
1363
+ case "send-now": {
1364
+ if (turnId !== command.turnId) break;
1365
+ const selected = items.find((item) => item.id === command.inputId);
1366
+ if (!selected || selected.message.role !== "user") break;
1367
+ items = items.map((item) => item.id === selected.id ? {
1368
+ ...item,
1369
+ generate: true,
1370
+ ...turnId === null ? {} : { afterStepOf: turnId }
1371
+ } : item);
1372
+ items = moveItem({
1373
+ items,
1374
+ inputId: command.inputId,
1375
+ beforeId: items[0]?.id ?? null
1376
+ });
1377
+ break;
1378
+ }
1379
+ case "pause":
1380
+ paused = true;
1381
+ if (command.when === "now") suspended = true;
1382
+ break;
1383
+ case "resume":
1384
+ paused = false;
1385
+ suspended = false;
1386
+ break;
1387
+ case "stop": continue;
1388
+ }
1389
+ admit();
1390
+ }
1391
+ items = items.filter((item) => {
1392
+ if (item.afterStepOf === void 0) return true;
1393
+ show(item.message);
1394
+ return false;
1395
+ });
1396
+ const displayed = {
1397
+ ...state,
1398
+ messages,
1399
+ inbox: {
1400
+ items,
1401
+ paused
1402
+ },
1403
+ starting: starting && !suspended
1404
+ };
1405
+ const stop = pending.find(({ command }) => command.action === "stop" && command.turnId === state.active?.turnId)?.command;
1406
+ if (stop?.action !== "stop") return displayed;
1407
+ const projection = state.activeProjection;
1408
+ const stopped = projection === null ? displayed : interruptGenerationProjection(displayed, projection.generationId, projection.responseMessageId, stop.lastSeenIndex ?? Number.POSITIVE_INFINITY) ?? displayed;
1409
+ return settleAbandonedCompaction(displayed, {
1410
+ ...stopped,
1411
+ active: null,
1412
+ status: paused ? "paused" : "idle",
1413
+ starting: false,
1414
+ activeGeneration: null,
1415
+ activeRequestId: null,
1416
+ activeRequestReason: null,
1417
+ activeRequestSourceGenerationId: null,
1418
+ activeResponseMessageId: null,
1419
+ pendingApprovals: [],
1420
+ pendingInputs: [],
1421
+ error: null
1422
+ });
1423
+ }
1424
+ //#endregion
1425
+ //#region src/ai-control-state.ts
1426
+ const container = (value) => typeof value === "object" && value !== null;
1427
+ const same = (left, right) => JSON.stringify(left) === JSON.stringify(right);
1428
+ const controlChanges = ({ before, after }) => {
1429
+ const changes = [];
1430
+ const visit = (left, right, path) => {
1431
+ if (same(left, right)) return;
1432
+ if (Array.isArray(left) && Array.isArray(right)) {
1433
+ let prefix = 0;
1434
+ while (prefix < Math.min(left.length, right.length) && same(left[prefix], right[prefix])) prefix += 1;
1435
+ let suffix = 0;
1436
+ while (suffix < Math.min(left.length, right.length) - prefix && same(left[left.length - 1 - suffix], right[right.length - 1 - suffix])) suffix += 1;
1437
+ if (left.length === right.length) for (let index = prefix; index < left.length - suffix; index += 1) visit(left[index], right[index], [...path, index]);
1438
+ else changes.push({
1439
+ path,
1440
+ splice: {
1441
+ index: prefix,
1442
+ deleteCount: left.length - prefix - suffix,
1443
+ items: right.slice(prefix, right.length - suffix)
1444
+ }
1445
+ });
1446
+ return;
1447
+ }
1448
+ if (container(left) && container(right) && !Array.isArray(left) && !Array.isArray(right)) {
1449
+ if (Object.keys(right).length === 0) {
1450
+ changes.push({
1451
+ path,
1452
+ value: right
1453
+ });
1454
+ return;
1455
+ }
1456
+ for (const key of Object.keys(left)) if (!Object.hasOwn(right, key)) changes.push({
1457
+ path: [...path, key],
1458
+ remove: true
1459
+ });
1460
+ for (const key of Object.keys(right)) visit(Object.hasOwn(left, key) ? left[key] : void 0, right[key], [...path, key]);
1461
+ return;
1462
+ }
1463
+ changes.push({
1464
+ path,
1465
+ value: right
1466
+ });
1467
+ };
1468
+ visit(before, after, []);
1469
+ return changes;
1470
+ };
1471
+ const applyControlChanges = ({ state, changes }) => {
1472
+ if (changes.length === 0) return state;
1473
+ let next = structuredClone(state);
1474
+ for (const change of changes) {
1475
+ const path = change.path;
1476
+ if (path.length === 0) {
1477
+ next = structuredClone(change.value);
1478
+ continue;
1479
+ }
1480
+ let target = next;
1481
+ for (const key of path.slice(0, -1)) {
1482
+ if (!container(target) || !Object.hasOwn(target, key)) throw new TypeError("Invalid AI control change path");
1483
+ target = target[key];
1484
+ }
1485
+ if (!container(target)) throw new TypeError("Invalid AI control change target");
1486
+ const parent = target;
1487
+ const key = path.at(-1);
1488
+ if (change.remove) delete parent[key];
1489
+ else if (change.splice) {
1490
+ const items = parent[key];
1491
+ Object.defineProperty(parent, key, {
1492
+ value: [
1493
+ ...items.slice(0, change.splice.index),
1494
+ ...structuredClone(change.splice.items),
1495
+ ...items.slice(change.splice.index + change.splice.deleteCount)
1496
+ ],
1497
+ enumerable: true,
1498
+ configurable: true,
1499
+ writable: true
1500
+ });
1501
+ } else if (change.move) {
1502
+ const items = parent[key];
1503
+ const [item] = items.splice(change.move.from, 1);
1504
+ items.splice(change.move.to, 0, item);
1505
+ } else Object.defineProperty(parent, key, {
1506
+ value: structuredClone(change.value),
1507
+ enumerable: true,
1508
+ configurable: true,
1509
+ writable: true
1510
+ });
1100
1511
  }
1101
- const payload = event.payload;
1102
- const existing = tools.find((candidate) => candidate.generationId === payload.generationId && candidate.toolCallId === payload.toolCallId);
1103
- const toolName = payload.toolName ?? existing?.toolName;
1104
- const input = payload.input === void 0 ? existing?.input : payload.input;
1105
- const dynamic = payload.dynamic ?? existing?.dynamic;
1106
- const providerExecuted = payload.providerExecuted ?? existing?.providerExecuted;
1107
- const toolMetadata = payload.toolMetadata ?? existing?.toolMetadata;
1108
- const resultProviderMetadata = payload.providerMetadata ?? existing?.resultProviderMetadata;
1109
- const activity = {
1110
- requestId: payload.requestId,
1111
- messageId: payload.messageId,
1112
- generationId: payload.generationId,
1113
- toolCallId: payload.toolCallId,
1114
- ...toolName === void 0 ? {} : { toolName },
1115
- ...input === void 0 ? {} : { input },
1116
- ...payload.rawInput === void 0 ? {} : { rawInput: payload.rawInput },
1117
- ...payload.output === void 0 ? {} : { output: payload.output },
1118
- ...payload.error === void 0 ? {} : { error: payload.error },
1119
- ...payload.preliminary === void 0 ? {} : { preliminary: payload.preliminary },
1120
- ...payload.phase === void 0 ? {} : { phase: payload.phase },
1121
- ...dynamic === void 0 ? {} : { dynamic },
1122
- ...providerExecuted === void 0 ? {} : { providerExecuted },
1123
- ...existing?.callProviderMetadata === void 0 ? {} : { callProviderMetadata: existing.callProviderMetadata },
1124
- ...resultProviderMetadata === void 0 ? {} : { resultProviderMetadata },
1125
- ...toolMetadata === void 0 ? {} : { toolMetadata },
1126
- ...existing?.title === void 0 ? {} : { title: existing.title },
1127
- status: payload.denied ? "denied" : payload.error ? "failed" : payload.preliminary ? "running" : "completed"
1128
- };
1129
- return [...tools.filter((candidate) => candidate.generationId !== payload.generationId || candidate.toolCallId !== payload.toolCallId), activity];
1512
+ return next;
1130
1513
  };
1131
- const advanceProjection = ({ projection, base, chunks, index }) => {
1132
- const batches = index > (projection.batches.tail.at(-1)?.index ?? -1) ? appendProgressBatch({
1133
- batches: projection.batches,
1134
- batch: {
1135
- index,
1136
- chunks
1137
- }
1138
- }) : progressBatches([...flattenProgressBatches(projection.batches).filter((batch) => batch.index !== index), {
1139
- index,
1140
- chunks
1141
- }].toSorted((left, right) => left.index - right.index));
1142
- const incremental = base !== void 0 && projection.cursor != null && index > projection.cursor.throughIndex;
1143
- const projected = incremental ? projectUIMessageStream({
1144
- messageId: projection.responseMessageId,
1145
- base,
1146
- cursor: projection.cursor.stream,
1147
- chunks
1148
- }) : projectedMessageStream({
1149
- ...projection,
1150
- batches
1151
- });
1514
+ const controlCommit = (options) => {
1515
+ const active = options.after.active;
1516
+ const response = options.after.coordinator.response;
1152
1517
  return {
1153
- message: projected.message,
1154
- batches,
1155
- cursor: {
1156
- throughIndex: incremental ? index : Math.max(index, batches.tail.at(-1)?.index ?? 0, projection.toolEvents.reduce((latest, event) => Math.max(latest, event.index), 0), projection.approvalEvents.reduce((latest, event) => Math.max(latest, event.index), 0)),
1157
- stream: projected.cursor
1518
+ changes: [...options.moves, ...controlChanges({
1519
+ before: options.before,
1520
+ after: options.after
1521
+ })],
1522
+ view: {
1523
+ turnId: active?.turnId ?? null,
1524
+ version: active?.version ?? null,
1525
+ phase: active?.phase ?? null,
1526
+ paused: options.after.inbox.paused,
1527
+ closed: options.after.coordinator.closed,
1528
+ requestId: response?.activeRequestId ?? null,
1529
+ requestReason: response?.requestReason ?? null,
1530
+ sourceGenerationId: response?.sourceGenerationId ?? null,
1531
+ responseMessageId: response?.responseMessageId ?? null
1158
1532
  }
1159
1533
  };
1160
1534
  };
1161
1535
  //#endregion
1536
+ //#region src/ai-control.ts
1537
+ const initialControlState = () => ({
1538
+ pendingStops: {},
1539
+ inbox: {
1540
+ paused: false,
1541
+ items: []
1542
+ },
1543
+ active: null,
1544
+ coordinator: {
1545
+ closed: false,
1546
+ queued: []
1547
+ }
1548
+ });
1549
+ //#endregion
1550
+ //#region src/ai-id.ts
1551
+ const AI_EVENT_ID_PREFIX = "a2.ai:";
1552
+ const aiEventId = async (...parts) => `${AI_EVENT_ID_PREFIX}${await idempotentId(...parts)}`;
1553
+ //#endregion
1162
1554
  //#region src/ai-stored-state.ts
1163
- const aiStateReducer = Symbol("a2.ai.stateReducer");
1164
1555
  const isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1165
1556
  const hasKeys = (value, { required, optional = [] }) => isRecord$1(value) && required.every((key) => Object.hasOwn(value, key)) && Object.keys(value).every((key) => required.includes(key) || optional.includes(key));
1166
1557
  const reference = (value) => {
@@ -1181,15 +1572,6 @@ const payloadFields = /* @__PURE__ */ new Set([
1181
1572
  "data"
1182
1573
  ]);
1183
1574
  const keyFor = (value) => value === void 0 ? "undefined" : JSON.stringify(value);
1184
- const sameValue = (left, right) => {
1185
- if (Object.is(left, right)) return true;
1186
- if (typeof left !== "object" || left === null || typeof right !== "object" || right === null) return false;
1187
- const prototype = Object.getPrototypeOf(left);
1188
- if (prototype !== Object.getPrototypeOf(right) || prototype !== Object.prototype && prototype !== Array.prototype && prototype !== null) return false;
1189
- if (Array.isArray(left) && (!Array.isArray(right) || left.length !== right.length)) return false;
1190
- const keys = Reflect.ownKeys(left);
1191
- return keys.length === Reflect.ownKeys(right).length && keys.every((key) => Object.hasOwn(right, key) && sameValue(Reflect.get(left, key), Reflect.get(right, key)));
1192
- };
1193
1575
  const restore = (current, undo) => {
1194
1576
  if (undo === void 0) return current;
1195
1577
  if (!isRecord$1(undo)) throw new TypeError("invalid AI recovery change");
@@ -1288,22 +1670,18 @@ const storeState = (state) => {
1288
1670
  packed.set(value, result);
1289
1671
  return result;
1290
1672
  };
1291
- const saveProjection = (projection, messages, tools) => {
1292
- const { baseMessage, baseTools, ...fields } = projection;
1293
- const current = messages.find((message) => message.id === projection.responseMessageId);
1294
- const message = baseMessage === void 0 ? void 0 : difference(pack(current), pack(baseMessage));
1673
+ const saveProjection = (projection, tools) => {
1674
+ const { baseTools, ...fields } = projection;
1295
1675
  const toolPatch = baseTools === void 0 ? void 0 : difference(pack(tools), pack(baseTools));
1296
1676
  return {
1297
1677
  fields: pack(fields),
1298
- hasMessage: baseMessage !== void 0,
1299
- ...message === void 0 ? {} : { message },
1300
1678
  ...toolPatch === void 0 ? {} : { tools: toolPatch }
1301
1679
  };
1302
1680
  };
1303
1681
  const { activeProjection, ...fields } = state;
1304
- const projection = activeProjection === null ? null : saveProjection(activeProjection, state.messages, state.tools);
1682
+ const projection = activeProjection === null ? null : saveProjection(activeProjection, state.tools);
1305
1683
  return {
1306
- version: 2,
1684
+ version: 3,
1307
1685
  fields: pack(fields),
1308
1686
  projection,
1309
1687
  values
@@ -1326,99 +1704,114 @@ const loadState = (stored) => {
1326
1704
  };
1327
1705
  const fields = stored.fields;
1328
1706
  const state = unpack(stored.fields);
1329
- const loadProjection = (saved, messages, tools) => {
1707
+ const loadProjection = (saved, tools) => {
1330
1708
  if (!hasKeys(saved, {
1331
- required: ["fields", "hasMessage"],
1332
- optional: ["message", "tools"]
1333
- }) || typeof saved.hasMessage !== "boolean" || !isRecord$1(saved.fields) || "message" in saved && (!saved.hasMessage || saved.message === void 0) || "tools" in saved && saved.tools === void 0) throw new TypeError("invalid AI projection recovery");
1709
+ required: ["fields"],
1710
+ optional: ["tools"]
1711
+ }) || !isRecord$1(saved.fields) || "tools" in saved && saved.tools === void 0) throw new TypeError("invalid AI projection recovery");
1334
1712
  const projection = unpack(saved.fields);
1335
- const index = unpack(messages).findIndex((message) => message.id === projection.responseMessageId);
1336
- if (saved.hasMessage) {
1337
- const current = index === -1 ? null : messages[index];
1338
- projection.baseMessage = unpack(restore(current, saved.message));
1713
+ if (projection.base !== void 0) {
1714
+ assertMessageBase(projection.base);
1715
+ const current = state.messages.find((message) => message.id === projection.responseMessageId);
1716
+ if (current !== void 0 && restoreMessageBase({
1717
+ current,
1718
+ base: projection.base
1719
+ }) === void 0) throw new TypeError("AI message base does not match its message");
1339
1720
  }
1340
1721
  if (tools !== void 0) projection.baseTools = unpack(restore(tools, saved.tools));
1341
1722
  else if (saved.tools !== void 0) throw new TypeError("missing AI recovery tools");
1342
1723
  return projection;
1343
1724
  };
1344
- state.activeProjection = stored.projection === null ? null : loadProjection(stored.projection, fields["messages"], fields["tools"]);
1725
+ state.activeProjection = stored.projection === null ? null : loadProjection(stored.projection, fields["tools"]);
1345
1726
  return state;
1346
1727
  };
1347
- const storedAIReducer = (source) => {
1348
- const views = /* @__PURE__ */ new WeakMap();
1349
- const view = (state) => {
1350
- let result = views.get(state);
1351
- if (result === void 0) {
1352
- result = loadState(state);
1353
- views.set(state, result);
1354
- }
1355
- return result;
1356
- };
1357
- const defer = (next) => {
1358
- let stored;
1359
- const read = () => stored ??= storeState(next);
1360
- const state = {
1361
- version: 2,
1362
- get fields() {
1363
- return read().fields;
1364
- },
1365
- get projection() {
1366
- return read().projection;
1367
- },
1368
- get values() {
1369
- return read().values;
1370
- }
1371
- };
1372
- views.set(state, next);
1373
- return state;
1374
- };
1375
- return {
1376
- ...source,
1377
- name: `${source.name}:records-v2`,
1378
- initialState: storeState(source.initialState),
1379
- [aiStateReducer]: source,
1380
- view,
1381
- fromView: (state) => storeState(state),
1382
- [clientStateProjection]: (state) => source[clientStateProjection]?.(view(state)) ?? view(state),
1383
- stateSchema: { "~standard": {
1384
- version: 1,
1385
- vendor: "a2-ai",
1386
- validate(value) {
1387
- try {
1388
- if (!hasKeys(value, { required: [
1389
- "version",
1390
- "fields",
1391
- "projection",
1392
- "values"
1393
- ] }) || value["version"] !== 2 || !Array.isArray(value["values"]) || !isRecord$1(value["fields"]) || value["projection"] !== null && !isRecord$1(value["projection"])) return { issues: [{ message: "invalid AI stored state" }] };
1394
- const state = value;
1395
- if (Object.keys(state.values).length !== state.values.length) return { issues: [{ message: "invalid AI payload table" }] };
1396
- for (const payload of state.values) if (!hasKeys(payload, {
1397
- required: [],
1398
- optional: ["value"]
1399
- })) return { issues: [{ message: "invalid AI payload table" }] };
1400
- const decoded = loadState(state);
1401
- if (source.stateSchema) {
1402
- const result = validateSync(source.stateSchema, decoded, "AI view stateSchema");
1403
- if (result.issues) return result;
1404
- return { value: storeState(result.value) };
1405
- }
1406
- return { value: state };
1407
- } catch {
1408
- return { issues: [{ message: "invalid AI stored state" }] };
1409
- }
1410
- }
1411
- } },
1412
- fold(state, event) {
1413
- const current = view(state);
1414
- const next = source.fold(current, event);
1415
- if (next === current) return state;
1416
- return defer(next);
1728
+ const withAIStorage = (source) => withReducerRuntime({
1729
+ reducer: source,
1730
+ storage: {
1731
+ encode: (state) => storeState(state),
1732
+ decode(value) {
1733
+ if (!hasKeys(value, { required: [
1734
+ "version",
1735
+ "fields",
1736
+ "projection",
1737
+ "values"
1738
+ ] }) || value["version"] !== 3 || !Array.isArray(value["values"]) || !isRecord$1(value["fields"]) || value["projection"] !== null && !isRecord$1(value["projection"])) throw new TypeError("invalid AI stored state");
1739
+ const state = value;
1740
+ if (Object.keys(state.values).length !== state.values.length) throw new TypeError("invalid AI payload table");
1741
+ for (const payload of state.values) if (!hasKeys(payload, {
1742
+ required: [],
1743
+ optional: ["value"]
1744
+ })) throw new TypeError("invalid AI payload table");
1745
+ return loadState(state);
1417
1746
  }
1418
- };
1747
+ }
1748
+ });
1749
+ //#endregion
1750
+ //#region src/ai-app-state.ts
1751
+ const aiStateKeys = {
1752
+ inbox: true,
1753
+ pendingQueueCommands: true,
1754
+ active: true,
1755
+ starting: true,
1756
+ receipt: true,
1757
+ pausedGenerationIds: true,
1758
+ session: true,
1759
+ messages: true,
1760
+ status: true,
1761
+ activeGeneration: true,
1762
+ activeRequestId: true,
1763
+ activeRequestReason: true,
1764
+ activeRequestSourceGenerationId: true,
1765
+ activeResponseMessageId: true,
1766
+ activeProjection: true,
1767
+ responseOwners: true,
1768
+ terminalRequestIds: true,
1769
+ terminalGenerations: true,
1770
+ pendingApprovals: true,
1771
+ pendingInputs: true,
1772
+ tools: true,
1773
+ compaction: true,
1774
+ modelMetadata: true,
1775
+ usage: true,
1776
+ error: true
1777
+ };
1778
+ const isAIStateKey = (key) => Object.hasOwn(aiStateKeys, key);
1779
+ const keep = (state, ai) => {
1780
+ const kept = {};
1781
+ for (const [key, value] of Object.entries(state)) if (isAIStateKey(key) === ai) kept[key] = value;
1782
+ return kept;
1783
+ };
1784
+ /** The application part of a merged state: every key `AIState` does not own. */
1785
+ const appSlice = (state) => keep(state, false);
1786
+ /** A merged state with its application part replaced. */
1787
+ const withAppSlice = (state, app) => ({
1788
+ ...keep(state, true),
1789
+ ...keep(app, false)
1790
+ });
1791
+ const agentReducers = /* @__PURE__ */ new WeakMap();
1792
+ const registerAgentReducer = (reducer, app) => {
1793
+ agentReducers.set(reducer, app ?? null);
1794
+ };
1795
+ const reducerOf = (contract) => typeof contract.reducer === "object" && contract.reducer !== null ? contract.reducer : void 0;
1796
+ const isAgentContract = (contract) => {
1797
+ const reducer = reducerOf(contract);
1798
+ return reducer !== void 0 && agentReducers.has(reducer);
1799
+ };
1800
+ const appReducerOf = (contract) => {
1801
+ const reducer = reducerOf(contract);
1802
+ return reducer === void 0 ? void 0 : agentReducers.get(reducer) ?? void 0;
1419
1803
  };
1420
1804
  //#endregion
1421
1805
  //#region src/ai.ts
1806
+ const reasoningLevels = [
1807
+ "provider-default",
1808
+ "none",
1809
+ "minimal",
1810
+ "low",
1811
+ "medium",
1812
+ "high",
1813
+ "xhigh"
1814
+ ];
1422
1815
  const issue = (message) => ({ issues: [{ message }] });
1423
1816
  const schema = (label, parse) => ({ "~standard": {
1424
1817
  version: 1,
@@ -1468,7 +1861,7 @@ const messagePayloadSchema = (messageSchema) => schema("ai.message.created", (va
1468
1861
  } };
1469
1862
  });
1470
1863
  const progressPayloadSchema = schema("ai.generation.progress", (value) => {
1471
- if (!isRecord(value) || !isJSONCompatible(value) || !stringField(value, "requestId") || !stringField(value, "messageId") || !stringField(value, "generationId") || !stringField(value, "responseMessageId") || typeof value["sequence"] !== "number" || !Number.isInteger(value["sequence"]) || value["sequence"] < 0 || !Array.isArray(value["chunks"]) || !value["chunks"].every((chunk) => isRecord(chunk) && typeof chunk["type"] === "string")) return issue("invalid ai.generation.progress payload");
1864
+ if (!isRecord(value) || !isJSONCompatible(value) || typeof value["startIndex"] !== "number" || !optionalIndexField(value, "startIndex") || !Array.isArray(value["chunks"]) || !value["chunks"].every((chunk) => isRecord(chunk) && typeof chunk["type"] === "string")) return issue("invalid ai.generation.progress payload");
1472
1865
  return { value };
1473
1866
  });
1474
1867
  const completedPayloadSchema = schema("ai.generation.completed", (value) => {
@@ -1571,7 +1964,7 @@ function createEvents(options) {
1571
1964
  "ai.message.interrupted": jsonObject("ai.message.interrupted", (value) => stringField(value, "messageId") && (stringField(value, "generationId") && value["requestId"] === void 0 || stringField(value, "requestId") && value["generationId"] === void 0) && optionalStringField(value, "reason") && (value["lastSeenIndex"] === void 0 || typeof value["lastSeenIndex"] === "number" && Number.isInteger(value["lastSeenIndex"]) && value["lastSeenIndex"] >= 0)),
1572
1965
  "ai.generation.requested": jsonObject("ai.generation.requested", (value) => stringField(value, "messageId") && stringField(value, "responseMessageId") && optionalStringField(value, "sourceGenerationId") && (value["reason"] !== "tool" || stringField(value, "sourceGenerationId")) && (value["reason"] === "message" || value["reason"] === "tool" || value["reason"] === "input" || value["reason"] === "retry" || value["reason"] === "resume")),
1573
1966
  "ai.retry.requested": jsonObject("ai.retry.requested", (value) => stringField(value, "messageId") && stringField(value, "responseMessageId") && stringField(value, "retryId")),
1574
- "ai.generation.started": jsonObject("ai.generation.started", (value) => stringField(value, "requestId") && stringField(value, "messageId") && stringField(value, "generationId") && stringField(value, "responseMessageId") && typeof value["attempt"] === "number" && Number.isInteger(value["attempt"]) && value["attempt"] > 0 && stringField(value, "model") && optionalIndexField(value, "promptThroughIndex")),
1967
+ "ai.generation.started": jsonObject("ai.generation.started", (value) => stringField(value, "requestId") && stringField(value, "messageId") && stringField(value, "generationId") && stringField(value, "responseMessageId") && typeof value["attempt"] === "number" && Number.isInteger(value["attempt"]) && value["attempt"] > 0 && (value["model"] === void 0 || stringField(value, "model")) && optionalIndexField(value, "promptThroughIndex")),
1575
1968
  "ai.generation.progress": progressPayloadSchema,
1576
1969
  "ai.generation.completed": completedPayloadSchema,
1577
1970
  "ai.generation.failed": jsonObject("ai.generation.failed", (value) => stringField(value, "requestId") && stringField(value, "messageId") && stringField(value, "generationId") && stringField(value, "responseMessageId") && stringField(value, "error") && (value["superseded"] === void 0 || typeof value["superseded"] === "boolean") && (value["stepLimit"] === void 0 || typeof value["stepLimit"] === "boolean")),
@@ -1613,11 +2006,14 @@ const initialState = () => ({
1613
2006
  usage: [],
1614
2007
  error: null
1615
2008
  });
1616
- const projectMessageChunks = (messages, messageId, chunks) => messages.map((message) => message.id === messageId ? projectUIMessage({
1617
- messageId,
1618
- base: message,
1619
- chunks
1620
- }) : message);
2009
+ const projectMessageChunks = (messages, messageId, chunks) => {
2010
+ const base = findMessage(messages, messageId);
2011
+ return upsertMessage(messages, projectUIMessage({
2012
+ messageId,
2013
+ ...base === void 0 ? {} : { base },
2014
+ chunks
2015
+ }));
2016
+ };
1621
2017
  const waitingStatus = (state) => {
1622
2018
  if (state.pendingApprovals.length > 0 || state.pendingInputs.length > 0) return "waiting";
1623
2019
  return state.activeGeneration !== null || state.tools.some((activity) => activity.status === "running") ? "generating" : "idle";
@@ -1628,54 +2024,15 @@ const isStaleGeneration = (state, messageId, owner) => {
1628
2024
  return current !== void 0 && (current.generationId !== owner.generationId || current.requestId !== owner.requestId);
1629
2025
  };
1630
2026
  const projectedResponse = (options) => {
1631
- const message = projectedMessage(options.projection);
2027
+ const message = projectedMessage({
2028
+ projection: options.projection,
2029
+ current: findMessage(options.state.messages, options.projection.responseMessageId)
2030
+ });
1632
2031
  return options.state.pausedGenerationIds?.[options.projection.generationId] ? pauseUIMessage({
1633
2032
  message,
1634
2033
  toolCallIds: new Set(options.state.tools.map((tool) => tool.toolCallId))
1635
2034
  }) : message;
1636
2035
  };
1637
- const projectedTools = (projection) => projection.toolEvents.reduce((tools, event) => reduceToolActivity(tools, event), projection.baseTools);
1638
- const restoreGenerationBase = (state, generationId, responseMessageId, reason) => {
1639
- const projection = state.activeProjection?.generationId === generationId ? state.activeProjection : null;
1640
- return {
1641
- ...state,
1642
- messages: projection?.baseMessage === void 0 ? state.messages.filter((message) => message.id !== responseMessageId) : upsertMessage(state.messages, projection.baseMessage),
1643
- tools: projection === null ? state.tools.filter((activity) => activity.generationId !== generationId) : projection.baseTools,
1644
- pendingApprovals: state.pendingApprovals.filter((approval) => approval.generationId !== generationId),
1645
- pendingInputs: state.pendingInputs.filter((input) => input.generationId !== generationId),
1646
- activeGeneration: null,
1647
- activeProjection: null,
1648
- terminalGenerations: {
1649
- ...state.terminalGenerations,
1650
- [generationId]: reason
1651
- },
1652
- compaction: projection === null ? state.compaction?.generationId === generationId ? null : state.compaction : projection.baseCompaction ?? null
1653
- };
1654
- };
1655
- const stopGenerationTools = (tools, generationId, responseMessageId, error) => tools.map((activity) => activity.status === "running" && (generationId === void 0 ? activity.messageId === responseMessageId : activity.generationId === generationId) ? {
1656
- ...activity,
1657
- status: "failed",
1658
- error,
1659
- preliminary: false
1660
- } : activity);
1661
- const interruptGenerationProjection = (state, generationId, responseMessageId, cutoff) => {
1662
- const projection = state.activeProjection?.generationId === generationId ? state.activeProjection : null;
1663
- if (projection === null) return null;
1664
- const retained = {
1665
- ...projection,
1666
- batches: progressBatches(flattenProgressBatches(projection.batches).filter((batch) => batch.index <= cutoff)),
1667
- toolEvents: projection.toolEvents.filter((toolEvent) => toolEvent.index <= cutoff),
1668
- approvalEvents: projection.approvalEvents.filter((approvalEvent) => approvalEvent.index <= cutoff)
1669
- };
1670
- const shouldProject = retained.baseMessage !== void 0 || retained.batches.length > 0;
1671
- const restored = restoreGenerationBase(state, generationId, responseMessageId, "interrupted");
1672
- return {
1673
- ...restored,
1674
- messages: shouldProject ? upsertMessage(state.messages, interruptUIMessage(projectedMessage(retained))) : restored.messages,
1675
- tools: stopGenerationTools(projectedTools(retained), generationId, responseMessageId, "Tool execution was interrupted."),
1676
- usage: projection.completionIndex === void 0 || projection.completionIndex <= cutoff ? restored.usage : restored.usage.filter((entry) => entry.generationId !== generationId)
1677
- };
1678
- };
1679
2036
  const terminalGenerationReason = (state, generationId, requestId) => {
1680
2037
  const terminal = state.terminalGenerations?.[generationId];
1681
2038
  if (terminal !== void 0) return terminal;
@@ -1683,6 +2040,12 @@ const terminalGenerationReason = (state, generationId, requestId) => {
1683
2040
  };
1684
2041
  /** Pure projection of the built-in AI protocol. Unknown extension events are ignored. */
1685
2042
  function reduceAIState(state, event) {
2043
+ return settleAbandonedCompaction(state, retainMessageBase({
2044
+ before: state,
2045
+ after: reduceAIEvent(state, event)
2046
+ }));
2047
+ }
2048
+ function reduceAIEvent(state, event) {
1686
2049
  if (state.status === "closed") return state;
1687
2050
  switch (event.type) {
1688
2051
  case "ai.control.requested": {
@@ -1873,11 +2236,14 @@ function reduceAIState(state, event) {
1873
2236
  if (currentOwner?.requestId === payload.requestId && currentOwner.attempt >= payload.attempt) return state;
1874
2237
  const activeGeneration = state.activeGeneration;
1875
2238
  const baseState = activeGeneration !== null && activeGeneration.requestId === payload.requestId && activeGeneration.responseMessageId === payload.responseMessageId && activeGeneration.attempt < payload.attempt && state.activeProjection?.generationId === activeGeneration.generationId ? restoreGenerationBase(state, activeGeneration.generationId, activeGeneration.responseMessageId, "superseded") : state;
1876
- const baseMessage = baseState.messages.find((message) => message.id === payload.responseMessageId);
2239
+ const baseMessage = findMessage(baseState.messages, payload.responseMessageId);
1877
2240
  return {
1878
2241
  ...baseState,
1879
2242
  status: "generating",
1880
- activeGeneration: payload,
2243
+ activeGeneration: {
2244
+ ...payload,
2245
+ startIndex: event.index
2246
+ },
1881
2247
  activeResponseMessageId: null,
1882
2248
  responseOwners: {
1883
2249
  ...baseState.responseOwners,
@@ -1891,7 +2257,7 @@ function reduceAIState(state, event) {
1891
2257
  generationId: payload.generationId,
1892
2258
  responseMessageId: payload.responseMessageId,
1893
2259
  promptThroughIndex: payload.promptThroughIndex ?? event.index - 1,
1894
- ...baseMessage === void 0 ? {} : { baseMessage },
2260
+ ...baseMessage === void 0 ? {} : { base: { partCount: baseMessage.parts.length } },
1895
2261
  baseCompaction: baseState.compaction,
1896
2262
  batches: progressBatches(),
1897
2263
  baseTools: baseState.tools,
@@ -1903,13 +2269,16 @@ function reduceAIState(state, event) {
1903
2269
  }
1904
2270
  case "ai.generation.progress": {
1905
2271
  const payload = event.payload;
1906
- if (state.pausedGenerationIds?.[payload.generationId]) return state;
1907
- if (terminalGenerationReason(state, payload.generationId, payload.requestId) !== void 0) return state;
2272
+ const generation = state.activeGeneration;
1908
2273
  const activeProjection = state.activeProjection;
1909
- if (state.activeGeneration?.generationId !== payload.generationId || state.activeGeneration.requestId !== payload.requestId || activeProjection?.generationId !== payload.generationId) return state;
2274
+ if (generation === null || !progressOwnedBy({
2275
+ payload,
2276
+ owner: generation,
2277
+ startIndex: generation.startIndex
2278
+ }) || state.pausedGenerationIds?.[generation.generationId] || terminalGenerationReason(state, generation.generationId, generation.requestId) !== void 0 || activeProjection?.generationId !== generation.generationId) return state;
1910
2279
  const progress = advanceProjection({
1911
2280
  projection: activeProjection,
1912
- base: state.messages.find((message) => message.id === activeProjection.responseMessageId),
2281
+ base: findMessage(state.messages, activeProjection.responseMessageId),
1913
2282
  chunks: payload.chunks,
1914
2283
  index: event.index
1915
2284
  });
@@ -1918,7 +2287,7 @@ function reduceAIState(state, event) {
1918
2287
  messages: upsertResponse({
1919
2288
  messages: state.messages,
1920
2289
  message: progress.message,
1921
- inputId: state.activeGeneration.messageId
2290
+ inputId: generation.messageId
1922
2291
  }),
1923
2292
  activeProjection: {
1924
2293
  ...activeProjection,
@@ -1956,10 +2325,13 @@ function reduceAIState(state, event) {
1956
2325
  if ((state.activeGeneration?.generationId !== payload.generationId || state.activeGeneration.requestId !== payload.requestId) && !(payload.stepLimit === true && state.activeRequestReason === "tool" && state.responseOwners[payload.responseMessageId]?.requestId === payload.requestId && state.responseOwners[payload.responseMessageId]?.generationId === payload.generationId && state.activeRequestSourceGenerationId === payload.generationId && state.activeResponseMessageId === payload.responseMessageId)) return state;
1957
2326
  if (payload.superseded === true) return restoreGenerationBase(state, payload.generationId, payload.responseMessageId, "superseded");
1958
2327
  const projection = state.activeProjection?.generationId === payload.generationId ? state.activeProjection : null;
1959
- const shouldProject = projection !== null && (projection.baseMessage !== void 0 || projection.batches.length > 0);
2328
+ const shouldProject = projection !== null && (projection.base !== void 0 || projection.batches.length > 0);
1960
2329
  return {
1961
2330
  ...state,
1962
- messages: shouldProject ? upsertMessage(state.messages, interruptUIMessage(projectedMessage(projection), `Generation failed before tool completion: ${payload.error}`)) : state.messages,
2331
+ messages: shouldProject ? upsertMessage(state.messages, interruptUIMessage(projectedMessage({
2332
+ projection,
2333
+ current: findMessage(state.messages, projection.responseMessageId)
2334
+ }), `Generation failed before tool completion: ${payload.error}`)) : state.messages,
1963
2335
  status: "failed",
1964
2336
  activeGeneration: null,
1965
2337
  activeResponseMessageId: null,
@@ -2078,6 +2450,7 @@ function reduceAIState(state, event) {
2078
2450
  if (projection?.generationId !== payload.generationId) {
2079
2451
  const next = {
2080
2452
  ...state,
2453
+ messages: projectMessageChunks(state.messages, payload.messageId, [toolCalledChunk(payload)]),
2081
2454
  tools: reduceToolActivity(state.tools, {
2082
2455
  type: "ai.tool.called",
2083
2456
  payload
@@ -2090,15 +2463,49 @@ function reduceAIState(state, event) {
2090
2463
  }
2091
2464
  const activeProjection = {
2092
2465
  ...projection,
2466
+ cursor: null,
2093
2467
  toolEvents: [...projection.toolEvents, {
2094
2468
  index: event.index,
2095
2469
  type: "ai.tool.called",
2096
2470
  payload
2097
2471
  }]
2098
2472
  };
2473
+ const base = findMessage(state.messages, payload.messageId);
2474
+ const paused = state.pausedGenerationIds?.[payload.generationId];
2475
+ const cursor = projection.cursor;
2476
+ const projected = base && cursor && !paused && event.index > cursor.throughIndex ? {
2477
+ ...hasToolCall({
2478
+ message: base,
2479
+ call: payload
2480
+ }) ? {
2481
+ message: base,
2482
+ cursor: cursor.stream
2483
+ } : projectUIMessageStream({
2484
+ messageId: payload.messageId,
2485
+ base,
2486
+ cursor: cursor.stream,
2487
+ chunks: [toolCalledChunk(payload)]
2488
+ }),
2489
+ throughIndex: event.index
2490
+ } : projectedMessageStream({
2491
+ projection: activeProjection,
2492
+ current: base
2493
+ });
2494
+ activeProjection.cursor = paused ? null : {
2495
+ throughIndex: projected.throughIndex,
2496
+ stream: projected.cursor
2497
+ };
2099
2498
  const next = {
2100
2499
  ...state,
2101
2500
  activeProjection,
2501
+ messages: upsertResponse({
2502
+ messages: state.messages,
2503
+ message: paused ? projectedResponse({
2504
+ state,
2505
+ projection: activeProjection
2506
+ }) : projected.message,
2507
+ inputId: state.activeGeneration?.messageId ?? state.active?.input.id ?? payload.messageId
2508
+ }),
2102
2509
  tools: projectedTools(activeProjection)
2103
2510
  };
2104
2511
  return {
@@ -2241,30 +2648,41 @@ function reduceAIState(state, event) {
2241
2648
  case "ai.compaction.requested": {
2242
2649
  const payload = event.payload;
2243
2650
  if (state.activeGeneration?.generationId !== payload.generationId) return state;
2244
- return {
2651
+ return addCompactionPart({
2245
2652
  ...state,
2246
2653
  compaction: {
2247
2654
  status: "running",
2248
2655
  ...payload
2249
2656
  }
2250
- };
2657
+ }, payload);
2251
2658
  }
2252
2659
  case "ai.compaction.completed": {
2253
2660
  const payload = event.payload;
2254
2661
  if (state.activeGeneration?.generationId !== payload.generationId || state.compaction?.status !== "running" || state.compaction.generationId !== payload.generationId) return state;
2662
+ const { messages: _messages, retainedMessageIds: _retained, ...summary } = payload;
2255
2663
  const compaction = {
2256
2664
  status: "completed",
2257
- ...payload,
2665
+ ...summary,
2258
2666
  throughIndex: payload.throughIndex ?? state.activeProjection.promptThroughIndex
2259
2667
  };
2260
- return {
2261
- ...state,
2262
- compaction,
2263
- activeProjection: state.activeProjection && {
2264
- ...state.activeProjection,
2265
- baseCompaction: compaction
2668
+ const projection = state.activeProjection;
2669
+ return settleCompactionPart({
2670
+ state: {
2671
+ ...state,
2672
+ compaction,
2673
+ activeProjection: projection && {
2674
+ ...projection,
2675
+ baseCompaction: compaction,
2676
+ ...projection.base === void 0 ? {} : { base: { ...projection.base } }
2677
+ }
2678
+ },
2679
+ generationId: payload.generationId,
2680
+ data: {
2681
+ status: "completed",
2682
+ ...summary.summary === void 0 ? {} : { summary: summary.summary },
2683
+ ...summary.usage === void 0 ? {} : { usage: summary.usage }
2266
2684
  }
2267
- };
2685
+ });
2268
2686
  }
2269
2687
  default: return state;
2270
2688
  }
@@ -2275,18 +2693,6 @@ function deriveUIMessages(history) {
2275
2693
  for (const event of history) state = reduceAIState(state, event);
2276
2694
  return state.messages;
2277
2695
  }
2278
- /** Build the standard AI state projection for any contract containing the protocol. */
2279
- function createReducer(options) {
2280
- const reducer = options.contract.reducer({
2281
- name: options.name ?? "a2.ai.state.v21",
2282
- initialState: initialState(),
2283
- ...options.stateSchema === void 0 ? {} : { stateSchema: options.stateSchema }
2284
- }).fold(options.fold ?? ((state, event) => reduceAIState(state, event)));
2285
- return storedAIReducer({
2286
- ...reducer,
2287
- [clientStateProjection]: projectAIClientState
2288
- });
2289
- }
2290
2696
  const commandInput = (payload) => [{
2291
2697
  type: "ai.control.requested",
2292
2698
  payload
@@ -2387,45 +2793,64 @@ const inputs = {
2387
2793
  * Read the durable handler context ambient to the current AI tool
2388
2794
  * execution. A2 runs each local tool's `execute` inside this scope, so
2389
2795
  * plain AI SDK `tool()` definitions reach their `event`, `attempt`,
2390
- * `session`, and `signal` without an A2-specific wrapper. The agent (or
2391
- * contract) argument carries the event types and is verified against
2392
- * the executing server's contract.
2796
+ * `session`, and `signal` without an A2-specific wrapper. Pass the
2797
+ * agent's contract to type application events; it is verified against
2798
+ * the executing agent.
2393
2799
  */
2394
- function handlerContext(agentOrContract) {
2395
- const contract = "contract" in agentOrContract ? agentOrContract.contract : agentOrContract;
2800
+ function handlerContext(contract) {
2396
2801
  const scope = ambientToolScopeStorage()?.getStore();
2397
- if (scope === void 0) throw new TypeError(`handlerContext('${contract.name}') requires an ambient A2 tool execution: call it inside a local tool's execute, or wrap direct tool tests with runWithHandlerContext`);
2398
- if (scope.contract !== void 0 && scope.contract.name !== contract.name) throw new TypeError(`handlerContext('${contract.name}') was called during a tool execution for agent '${scope.contract.name}'`);
2802
+ if (scope === void 0) throw new TypeError("handlerContext() requires an ambient A2 tool execution: call it inside a local tool's execute, or wrap direct tool tests with runWithHandlerContext");
2803
+ if (contract !== void 0 && scope.contract !== void 0 && scope.contract.name !== contract.name) throw new TypeError(`handlerContext('${contract.name}') was called during a tool execution for agent '${scope.contract.name}'`);
2399
2804
  return scope.context;
2400
2805
  }
2806
+ /** The built-in AI reducer's name, and so its checkpoint identity. */
2807
+ const AI_STATE_REDUCER = "a2.ai.state.v26";
2401
2808
  /**
2402
- * Define an agent: built-in AI protocol + application events and one state
2403
- * reducer. The result is isomorphic.
2809
+ * The built-in AI protocol plus application events, as a core contract whose
2810
+ * reducer folds `AIState`. Isomorphic: share it when a client needs
2811
+ * application events or extended state; otherwise `agent({ name })` on the
2812
+ * server builds one and clients use the built-in protocol.
2404
2813
  */
2405
- function agent(options) {
2814
+ function agentContract(options) {
2406
2815
  const builtIns = createEvents(options.messageSchema === void 0 ? void 0 : { messageSchema: options.messageSchema });
2407
2816
  const extensions = options.events ?? {};
2408
2817
  for (const key of Object.keys(extensions)) if (Object.hasOwn(builtIns, key)) throw new TypeError(`agent event '${key}' conflicts with a built-in event`);
2409
- const agentContract = contract({
2818
+ const vocabulary = contract({
2410
2819
  name: options.name,
2411
2820
  events: {
2412
2821
  ...builtIns,
2413
2822
  ...extensions
2414
2823
  }
2415
2824
  });
2416
- const reducer = agentContract.reducer({
2417
- name: options.reducerName ?? "a2.ai.state.v21",
2418
- initialState: initialState()
2419
- }).fold((state, event) => reduceAIState(state, event));
2420
- return {
2421
- contract: agentContract,
2422
- reducer: storedAIReducer({
2423
- ...reducer,
2424
- [clientStateProjection]: projectAIClientState
2425
- })
2426
- };
2825
+ const app = options.reducer;
2826
+ const aiState = initialState();
2827
+ for (const key of Object.keys(app?.initialState ?? {})) if (isAIStateKey(key)) throw new TypeError(`agent state '${key}' conflicts with AIState`);
2828
+ const reducer$1 = reducer({
2829
+ contract: vocabulary,
2830
+ name: app === void 0 ? AI_STATE_REDUCER : `${app.name}+${AI_STATE_REDUCER}`,
2831
+ initialState: {
2832
+ ...aiState,
2833
+ ...app?.initialState
2834
+ }
2835
+ }).fold((state, event) => {
2836
+ const next = reduceAIState(state, event);
2837
+ if (app === void 0) return next;
2838
+ const before = appSlice(state);
2839
+ const after = app.fold(before, event);
2840
+ return after === before ? next : withAppSlice(next, after);
2841
+ });
2842
+ const shared = contract({
2843
+ name: options.name,
2844
+ events: vocabulary.events,
2845
+ reducer: withAIStorage(withReducerRuntime({
2846
+ reducer: reducer$1,
2847
+ project: projectAIClientState
2848
+ }))
2849
+ });
2850
+ registerAgentReducer(shared.reducer, app);
2851
+ return shared;
2427
2852
  }
2428
2853
  //#endregion
2429
- export { applyControlChanges as C, initialControlState as S, progressBatches as _, events as a, AI_EVENT_ID_PREFIX as b, reduceAIState as c, advanceProjection as d, projectedMessage as f, flattenProgressBatches as g, upsertResponse as h, deriveUIMessages as i, aiStateReducer as l, upsertMessage as m, createEvents as n, handlerContext as o, reduceToolActivity as p, createReducer as r, inputs as s, agent as t, storedAIReducer as u, interruptUIMessage as v, controlCommit as w, aiEventId as x, pauseUIMessage as y };
2854
+ export { interruptUIMessage as A, reduceToolActivity as C, flattenProgressBatches as D, upsertResponse as E, assertMessageBase as M, restoreMessageBase as N, progressBatches as O, projectedMessage as S, upsertMessage as T, applyControlChanges as _, events as a, findMessage as b, reasoningLevels as c, appSlice as d, isAgentContract as f, initialControlState as g, aiEventId as h, deriveUIMessages as i, pauseUIMessage as j, progressOwnedBy as k, reduceAIState as l, AI_EVENT_ID_PREFIX as m, agentContract as n, handlerContext as o, withAIStorage as p, createEvents as r, inputs as s, AI_STATE_REDUCER as t, appReducerOf as u, controlCommit as v, retainMessageBase as w, isCompactionPart as x, advanceProjection as y };
2430
2855
 
2431
- //# sourceMappingURL=ai-CrLYwNEx.js.map
2856
+ //# sourceMappingURL=ai-UrWfKeuv.js.map