experimental-a2 0.16.1 → 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 (333) hide show
  1. package/CHANGELOG.md +352 -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-DgOBltJ_.js → ai-UrWfKeuv.js} +621 -216
  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 +432 -258
  32. package/dist/ai-server.js.map +1 -1
  33. package/dist/{ai-CrEf6p_W.d.ts → ai-vpGtcaCB.d.ts} +114 -100
  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 +671 -581
  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 +656 -194
  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 +6 -3
  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 +92 -72
  256. package/src/ai-coordinator.ts +23 -22
  257. package/src/ai-message-base.ts +209 -0
  258. package/src/ai-message-projection.ts +262 -64
  259. package/src/ai-progress-batches.ts +26 -0
  260. package/src/ai-projector.ts +95 -14
  261. package/src/ai-react.ts +146 -0
  262. package/src/ai-server.browser.ts +2 -2
  263. package/src/ai-server.ts +20 -1964
  264. package/src/ai-stored-state.ts +48 -163
  265. package/src/ai.ts +339 -155
  266. package/src/bootstrap-stream.ts +72 -0
  267. package/src/bounded-cache.ts +55 -0
  268. package/src/client-render-session.ts +49 -0
  269. package/src/client.ts +688 -151
  270. package/src/contract.ts +92 -42
  271. package/src/deterministic-id.ts +37 -0
  272. package/src/equal-json.ts +32 -0
  273. package/src/errors.ts +13 -1
  274. package/src/index.ts +5 -1
  275. package/src/initial-snapshot.ts +39 -0
  276. package/src/internal.ts +45 -0
  277. package/src/postgres-notification-scope.ts +14 -3
  278. package/src/react.ts +171 -206
  279. package/src/redis-http-reply.ts +256 -0
  280. package/src/redis-http-stream.ts +73 -0
  281. package/src/redis-http-subscriptions.ts +13 -5
  282. package/src/reducer-runtime.ts +52 -0
  283. package/src/reducer.ts +92 -10
  284. package/src/scheduler-qstash.ts +49 -20
  285. package/src/scheduler-task.ts +34 -2
  286. package/src/scheduler-vercel.ts +32 -84
  287. package/src/server-fetch.ts +139 -35
  288. package/src/server.ts +957 -237
  289. package/src/session-clear.ts +23 -0
  290. package/src/session-socket.ts +154 -49
  291. package/src/sse.ts +71 -4
  292. package/src/store-memory.ts +220 -78
  293. package/src/store-polling.ts +18 -4
  294. package/src/store-postgres.ts +461 -114
  295. package/src/store-redis-core.ts +1260 -434
  296. package/src/store-redis-http.ts +18 -2
  297. package/src/store-redis-notify.ts +260 -85
  298. package/src/store-redis.ts +19 -9
  299. package/src/store-sqlite.ts +259 -45
  300. package/src/store.ts +46 -1
  301. package/src/stream-buffer.ts +43 -0
  302. package/src/telemetry.ts +13 -1
  303. package/src/wire.ts +241 -13
  304. package/dist/actor-ohPC-81x.d.ts.map +0 -1
  305. package/dist/actor-shared-B5tJfzt-.js.map +0 -1
  306. package/dist/ai-CrEf6p_W.d.ts.map +0 -1
  307. package/dist/ai-DgOBltJ_.js.map +0 -1
  308. package/dist/client-BYzHjkwU.js.map +0 -1
  309. package/dist/client-CzyacQpJ.d.ts.map +0 -1
  310. package/dist/contract-CKRg_E4q.js.map +0 -1
  311. package/dist/errors-DCk6ch5n.js.map +0 -1
  312. package/dist/errors-DvhSXnxk.d.ts.map +0 -1
  313. package/dist/internal-Dq2qYxou.js.map +0 -1
  314. package/dist/react.d.ts.map +0 -1
  315. package/dist/reducer-DEMjEY_O.js +0 -29
  316. package/dist/reducer-DEMjEY_O.js.map +0 -1
  317. package/dist/reducer-otzHjuJj.d.ts.map +0 -1
  318. package/dist/scheduler-task-BpzhPnRS.js.map +0 -1
  319. package/dist/server-BD5ckxZb.js.map +0 -1
  320. package/dist/server-DbD7IJVK.d.ts.map +0 -1
  321. package/dist/store-DyZM6fS5.d.ts.map +0 -1
  322. package/dist/store-polling-CmxUbV93.js.map +0 -1
  323. package/dist/store-redis-notify-BVLUfI3j.js +0 -1583
  324. package/dist/store-redis-notify-BVLUfI3j.js.map +0 -1
  325. package/dist/telemetry-B5jzpy6y.d.ts.map +0 -1
  326. package/dist/validate-XKT4FSNn.js +0 -30
  327. package/dist/validate-XKT4FSNn.js.map +0 -1
  328. package/dist/wire-BO5wWCb1.js.map +0 -1
  329. package/examples/playground/app/agent/[agentId]/compaction/route.ts +0 -14
  330. package/examples/playground/app/agent/compaction-settings.test.ts +0 -144
  331. package/examples/playground/app/agent/compaction-settings.ts +0 -49
  332. package/examples/playground/app/agent/compaction-timeline.test.ts +0 -377
  333. package/examples/playground/app/agent/compaction-timeline.ts +0 -208
@@ -1,6 +1,11 @@
1
+ import { sameValue } from './equal-json.ts'
2
+ import type { ToolCalledPayload } from './ai.ts'
1
3
  import type { UIMessage, UIMessageChunk } from 'ai'
2
4
  import { parsePartialJSON } from './parse-partial-json.ts'
3
5
 
6
+ export type ProjectionChunk =
7
+ UIMessageChunk | { type: 'a2-tool-called'; call: ToolCalledPayload }
8
+
4
9
  type MutablePart = Record<string, unknown>
5
10
 
6
11
  type PartialToolCall = {
@@ -22,9 +27,22 @@ const cloneMessage = <M extends UIMessage>(messageId: string, base?: M): M =>
22
27
  : {
23
28
  ...base,
24
29
  id: messageId,
25
- parts: base.parts.map((part) => ({ ...part })),
30
+ parts: [...base.parts],
26
31
  }) as M
27
32
 
33
+ const editPart = ({
34
+ parts,
35
+ part,
36
+ }: {
37
+ parts: MutablePart[]
38
+ part: MutablePart
39
+ }): MutablePart => {
40
+ const index = parts.indexOf(part)
41
+ const next = { ...part }
42
+ parts[index] = next
43
+ return next
44
+ }
45
+
28
46
  const currentStepParts = (parts: MutablePart[]): MutablePart[] => {
29
47
  let start = parts.length - 1
30
48
  while (start >= 0 && parts[start]?.['type'] !== 'step-start') start -= 1
@@ -49,6 +67,30 @@ const findToolPart = (
49
67
  )
50
68
  }
51
69
 
70
+ export const hasToolCall = ({
71
+ message,
72
+ call,
73
+ }: {
74
+ message: UIMessage
75
+ call: ToolCalledPayload
76
+ }): boolean => {
77
+ const part = findToolPart(
78
+ message.parts as unknown as MutablePart[],
79
+ call.toolCallId,
80
+ )
81
+ return (
82
+ part !== undefined &&
83
+ part['state'] !== 'input-streaming' &&
84
+ sameValue(part['input'], call.input) &&
85
+ (part['callProviderMetadata'] !== undefined ||
86
+ call.providerMetadata === undefined) &&
87
+ (part['toolMetadata'] !== undefined || call.toolMetadata === undefined) &&
88
+ (call.providerExecuted === undefined ||
89
+ part['providerExecuted'] === call.providerExecuted) &&
90
+ (call.title === undefined || part['title'] === call.title)
91
+ )
92
+ }
93
+
52
94
  const findApprovalPart = (
53
95
  parts: MutablePart[],
54
96
  approvalId: string,
@@ -92,7 +134,7 @@ const setToolPart = (options: {
92
134
  toolCallId: options.toolCallId,
93
135
  }
94
136
  options.parts.push(part)
95
- }
137
+ } else part = editPart({ parts: options.parts, part })
96
138
 
97
139
  part['state'] = options.state
98
140
  part['input'] = options.input
@@ -134,7 +176,7 @@ export type UIMessageCursor = {
134
176
  export function projectUIMessage<M extends UIMessage>(options: {
135
177
  messageId: string
136
178
  base?: M
137
- chunks: readonly UIMessageChunk[]
179
+ chunks: readonly ProjectionChunk[]
138
180
  }): M {
139
181
  return projectUIMessageStream(options).message
140
182
  }
@@ -143,7 +185,7 @@ export function projectUIMessageStream<M extends UIMessage>(options: {
143
185
  messageId: string
144
186
  base?: M
145
187
  cursor?: UIMessageCursor
146
- chunks: readonly UIMessageChunk[]
188
+ chunks: readonly ProjectionChunk[]
147
189
  }): { message: M; cursor: UIMessageCursor } {
148
190
  const message = cloneMessage(options.messageId, options.base)
149
191
  const record = message as unknown as Record<string, unknown>
@@ -189,14 +231,17 @@ export function projectUIMessageStream<M extends UIMessage>(options: {
189
231
  activeText.set(chunk.id, part)
190
232
  parts.push(part)
191
233
  }
234
+ part = editPart({ parts, part })
235
+ activeText.set(chunk.id, part)
192
236
  part['text'] =
193
237
  `${typeof part['text'] === 'string' ? part['text'] : ''}${chunk.delta}`
194
238
  setOptional(part, 'providerMetadata', chunk.providerMetadata)
195
239
  break
196
240
  }
197
241
  case 'text-end': {
198
- const part = activeText.get(chunk.id)
199
- if (part) {
242
+ const current = activeText.get(chunk.id)
243
+ if (current) {
244
+ const part = editPart({ parts, part: current })
200
245
  part['state'] = 'done'
201
246
  setOptional(part, 'providerMetadata', chunk.providerMetadata)
202
247
  activeText.delete(chunk.id)
@@ -223,14 +268,17 @@ export function projectUIMessageStream<M extends UIMessage>(options: {
223
268
  activeReasoning.set(chunk.id, part)
224
269
  parts.push(part)
225
270
  }
271
+ part = editPart({ parts, part })
272
+ activeReasoning.set(chunk.id, part)
226
273
  part['text'] =
227
274
  `${typeof part['text'] === 'string' ? part['text'] : ''}${chunk.delta}`
228
275
  setOptional(part, 'providerMetadata', chunk.providerMetadata)
229
276
  break
230
277
  }
231
278
  case 'reasoning-end': {
232
- const part = activeReasoning.get(chunk.id)
233
- if (part) {
279
+ const current = activeReasoning.get(chunk.id)
280
+ if (current) {
281
+ const part = editPart({ parts, part: current })
234
282
  part['state'] = 'done'
235
283
  setOptional(part, 'providerMetadata', chunk.providerMetadata)
236
284
  activeReasoning.delete(chunk.id)
@@ -327,6 +375,37 @@ export function projectUIMessageStream<M extends UIMessage>(options: {
327
375
  })
328
376
  break
329
377
  }
378
+ case 'a2-tool-called': {
379
+ const call = chunk.call
380
+ const current = findToolPart(parts, call.toolCallId)
381
+ if (current) {
382
+ const existing = editPart({ parts, part: current })
383
+ existing['input'] = call.input
384
+ if (existing['state'] === 'input-streaming')
385
+ existing['state'] = 'input-available'
386
+ if (existing['callProviderMetadata'] === undefined)
387
+ setOptional(existing, 'callProviderMetadata', call.providerMetadata)
388
+ if (existing['toolMetadata'] === undefined)
389
+ setOptional(existing, 'toolMetadata', call.toolMetadata)
390
+ setOptional(existing, 'providerExecuted', call.providerExecuted)
391
+ setOptional(existing, 'title', call.title)
392
+ } else {
393
+ setToolPart({
394
+ parts,
395
+ toolCallId: call.toolCallId,
396
+ toolName: call.toolName,
397
+ dynamic: call.dynamic === true,
398
+ state: 'input-available',
399
+ input: call.input,
400
+ providerExecuted: call.providerExecuted,
401
+ providerMetadata: call.providerMetadata,
402
+ toolMetadata: call.toolMetadata,
403
+ title: call.title,
404
+ })
405
+ }
406
+ partialTools.delete(call.toolCallId)
407
+ break
408
+ }
330
409
  case 'tool-input-available':
331
410
  setToolPart({
332
411
  parts,
@@ -363,8 +442,9 @@ export function projectUIMessageStream<M extends UIMessage>(options: {
363
442
  break
364
443
  }
365
444
  case 'tool-approval-request': {
366
- const part = findToolPart(parts, chunk.toolCallId)
367
- if (!part) break
445
+ const current = findToolPart(parts, chunk.toolCallId)
446
+ if (!current) break
447
+ const part = editPart({ parts, part: current })
368
448
  part['state'] = 'approval-requested'
369
449
  part['approval'] = {
370
450
  id: chunk.approvalId,
@@ -376,8 +456,9 @@ export function projectUIMessageStream<M extends UIMessage>(options: {
376
456
  break
377
457
  }
378
458
  case 'tool-approval-response': {
379
- const part = findApprovalPart(parts, chunk.approvalId)
380
- if (!part) break
459
+ const current = findApprovalPart(parts, chunk.approvalId)
460
+ if (!current) break
461
+ const part = editPart({ parts, part: current })
381
462
  const approval = isRecord(part['approval']) ? part['approval'] : {}
382
463
  part['state'] = 'approval-responded'
383
464
  part['approval'] = {
@@ -441,7 +522,7 @@ export function projectUIMessageStream<M extends UIMessage>(options: {
441
522
  }
442
523
  case 'tool-output-denied': {
443
524
  const part = findToolPart(parts, chunk.toolCallId)
444
- if (part) part['state'] = 'output-denied'
525
+ if (part) editPart({ parts, part })['state'] = 'output-denied'
445
526
  break
446
527
  }
447
528
  case 'start-step':
@@ -496,7 +577,7 @@ export function projectUIMessageStream<M extends UIMessage>(options: {
496
577
  (part) =>
497
578
  part['type'] === chunk.type && part['id'] === chunk.id,
498
579
  )
499
- if (existing) existing['data'] = chunk.data
580
+ if (existing) editPart({ parts, part: existing })['data'] = chunk.data
500
581
  else
501
582
  parts.push({
502
583
  type: chunk.type,
@@ -0,0 +1,146 @@
1
+ 'use client'
2
+ /**
3
+ * experimental-a2/ai/react — one hook for an agent session: its live
4
+ * optimistic `AIState`, and actions that fill in protocol identities.
5
+ *
6
+ * ```tsx
7
+ * import type { assistant } from '@/server/assistant' // erased at build
8
+ * const { messages, indicator, sendMessage, stop } =
9
+ * useAgent<typeof assistant>({ api: '/api/agent', id, initial })
10
+ * ```
11
+ *
12
+ * With a shared `agentContract()`, pass it as `contract` to fold its
13
+ * application state in the browser too.
14
+ */
15
+
16
+ import type { AIEventDefs, AIState } from './ai.ts'
17
+ import { withAgentActions, type UseAgentResult } from './ai-agent-actions.ts'
18
+ import {
19
+ createAgentClient,
20
+ type AgentClient,
21
+ type AgentMessage,
22
+ type AnyAgent,
23
+ } from './ai-client.ts'
24
+ import type { Contract, EventDefs } from './contract.ts'
25
+ import type { InitialSnapshot } from './initial-snapshot.ts'
26
+ import { createClient, type A2Client } from './client.ts'
27
+ import type { Reducer } from './reducer.ts'
28
+ import { useSession, type UseSessionResult } from './react.ts'
29
+
30
+ export type UseAgentOptions<A extends AnyAgent> = {
31
+ /** The session id. */
32
+ id: string
33
+ /** The server-rendered `assistant.session(id).state()`. */
34
+ initial?: InitialSnapshot<AIState<AgentMessage<A>>>
35
+ participant?: string
36
+ } & (
37
+ | {
38
+ /** The route serving the agent's `fetch`; its client is shared. */
39
+ api: string
40
+ contract?: never
41
+ }
42
+ | { client: AgentClient<A> }
43
+ )
44
+
45
+ /**
46
+ * Use a session of an agent served from a shared `agentContract()`, folding
47
+ * its application state in the browser. The contract is a real import here.
48
+ */
49
+ export type UseContractAgentOptions<D extends EventDefs, S extends AIState> = {
50
+ /** The session id. */
51
+ id: string
52
+ /** The server-rendered `agent.session(id).state()`. */
53
+ initial?: InitialSnapshot<S>
54
+ participant?: string
55
+ } & (
56
+ | {
57
+ /** The route serving the agent's `fetch`; its client is shared. */
58
+ api: string
59
+ contract: Contract<D, Record<never, never>, Reducer<D, S>>
60
+ }
61
+ | {
62
+ /** `createClient({ contract, api })`, shared with pushes outside React. */
63
+ client: A2Client<D, S>
64
+ }
65
+ )
66
+
67
+ const clients = new Map<string, AgentClient>()
68
+ const contractClients = new WeakMap<object, Map<string, object>>()
69
+
70
+ /**
71
+ * One client per route for the page's lifetime; its idle sessions expire
72
+ * after the default `gcTime`. It reads `globalThis.fetch` per request, so
73
+ * instrumentation installed later still applies.
74
+ */
75
+ const clientFor = (api: string): AgentClient => {
76
+ let client = clients.get(api)
77
+ if (client === undefined) {
78
+ client = createAgentClient({
79
+ api,
80
+ fetch: (input, init) => globalThis.fetch(input, init),
81
+ })
82
+ clients.set(api, client)
83
+ }
84
+ return client
85
+ }
86
+
87
+ /** One client per contract and route, like `clientFor`. */
88
+ const contractClientFor = <D extends EventDefs, S>(
89
+ contract: Contract<D, Record<never, never>, Reducer<D, S>>,
90
+ api: string,
91
+ ): A2Client<D, S> => {
92
+ let byApi = contractClients.get(contract)
93
+ if (byApi === undefined) {
94
+ byApi = new Map()
95
+ contractClients.set(contract, byApi)
96
+ }
97
+ let client = byApi.get(api) as A2Client<D, S> | undefined
98
+ if (client === undefined) {
99
+ client = createClient({
100
+ contract,
101
+ api,
102
+ fetch: (input, init) => globalThis.fetch(input, init),
103
+ })
104
+ byApi.set(api, client)
105
+ }
106
+ return client
107
+ }
108
+
109
+ /** Use a session of an agent served from a shared `agentContract()`. */
110
+ export function useAgent<D extends EventDefs, S extends AIState>(
111
+ options: UseContractAgentOptions<D, S>,
112
+ ): UseAgentResult<D, S>
113
+ /**
114
+ * Use one agent session. Pass `typeof` the server's `agent()` for its
115
+ * message types; the import is type-only and erased.
116
+ */
117
+ export function useAgent<A extends AnyAgent = AnyAgent>(
118
+ options: UseAgentOptions<A>,
119
+ ): UseAgentResult<AIEventDefs<AgentMessage<A>>, AIState<AgentMessage<A>>>
120
+ export function useAgent(
121
+ options:
122
+ UseAgentOptions<AnyAgent> | UseContractAgentOptions<AIEventDefs, AIState>,
123
+ ): object {
124
+ const session: UseSessionResult<AIEventDefs, AIState> = useSession(
125
+ 'client' in options
126
+ ? options.client
127
+ : options.contract !== undefined
128
+ ? contractClientFor(options.contract, options.api)
129
+ : clientFor(options.api),
130
+ options.id,
131
+ {
132
+ ...(options.initial === undefined ? {} : { initial: options.initial }),
133
+ ...(options.participant === undefined
134
+ ? {}
135
+ : { participant: options.participant }),
136
+ },
137
+ )
138
+ return withAgentActions(session)
139
+ }
140
+
141
+ export type { AgentClient, AgentMessage, AnyAgent }
142
+ export type {
143
+ AgentActions,
144
+ AgentIndicator,
145
+ UseAgentResult,
146
+ } from './ai-agent-actions.ts'
@@ -1,5 +1,5 @@
1
- /** experimental-a2/ai/server is server-only; agent definitions live in experimental-a2/ai. */
1
+ /** experimental-a2/ai/server is server-only; browsers use experimental-a2/ai/react or /ai/client. */
2
2
  throw new Error(
3
- 'experimental-a2/ai/server is server-only — a client bundle imported it. Import agent from experimental-a2/ai in browser code instead.',
3
+ "experimental-a2/ai/server is server-only — a client bundle imported it. Import its agent's type with `import type`, and use experimental-a2/ai/react or experimental-a2/ai/client in browser code.",
4
4
  )
5
5
  export {}