experimental-a2 0.16.1 → 0.18.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 (406) hide show
  1. package/CHANGELOG.md +373 -0
  2. package/README.md +1 -1
  3. package/dist/{actor-ohPC-81x.d.ts → actor-Bjce9_5U.d.ts} +21 -11
  4. package/dist/actor-Bjce9_5U.d.ts.map +1 -0
  5. package/dist/actor-client.d.ts +2 -5
  6. package/dist/actor-client.d.ts.map +1 -1
  7. package/dist/actor-client.js +13 -4
  8. package/dist/actor-client.js.map +1 -1
  9. package/dist/actor-react.d.ts +5 -3
  10. package/dist/actor-react.d.ts.map +1 -1
  11. package/dist/actor-react.js +20 -10
  12. package/dist/actor-react.js.map +1 -1
  13. package/dist/{actor-shared-B5tJfzt-.js → actor-shared-t7w8xIKP.js} +12 -7
  14. package/dist/actor-shared-t7w8xIKP.js.map +1 -0
  15. package/dist/actor.d.ts +1 -1
  16. package/dist/actor.js +42 -23
  17. package/dist/actor.js.map +1 -1
  18. package/dist/{ai-DgOBltJ_.js → ai-BpOGm-zB.js} +621 -216
  19. package/dist/ai-BpOGm-zB.js.map +1 -0
  20. package/dist/{ai-CrEf6p_W.d.ts → ai-DrUaMyRt.d.ts} +114 -100
  21. package/dist/ai-DrUaMyRt.d.ts.map +1 -0
  22. package/dist/ai-client.d.ts +27 -0
  23. package/dist/ai-client.d.ts.map +1 -0
  24. package/dist/ai-client.js +29 -0
  25. package/dist/ai-client.js.map +1 -0
  26. package/dist/ai-fx.browser.d.ts +1 -0
  27. package/dist/ai-fx.browser.js +6 -0
  28. package/dist/ai-fx.browser.js.map +1 -0
  29. package/dist/ai-fx.d.ts +10 -0
  30. package/dist/ai-fx.d.ts.map +1 -0
  31. package/dist/ai-fx.js +634 -0
  32. package/dist/ai-fx.js.map +1 -0
  33. package/dist/ai-harness-BuLh_EYi.js +273 -0
  34. package/dist/ai-harness-BuLh_EYi.js.map +1 -0
  35. package/dist/ai-harness-DvsPeJeB.d.ts +26 -0
  36. package/dist/ai-harness-DvsPeJeB.d.ts.map +1 -0
  37. package/dist/ai-pi.browser.d.ts +1 -0
  38. package/dist/ai-pi.browser.js +6 -0
  39. package/dist/ai-pi.browser.js.map +1 -0
  40. package/dist/ai-pi.d.ts +17 -0
  41. package/dist/ai-pi.d.ts.map +1 -0
  42. package/dist/ai-pi.js +904 -0
  43. package/dist/ai-pi.js.map +1 -0
  44. package/dist/ai-react.d.ts +96 -0
  45. package/dist/ai-react.d.ts.map +1 -0
  46. package/dist/ai-react.js +142 -0
  47. package/dist/ai-react.js.map +1 -0
  48. package/dist/ai-server.browser.js +2 -2
  49. package/dist/ai-server.browser.js.map +1 -1
  50. package/dist/ai-server.d.ts +109 -43
  51. package/dist/ai-server.d.ts.map +1 -1
  52. package/dist/ai-server.js +526 -283
  53. package/dist/ai-server.js.map +1 -1
  54. package/dist/ai.d.ts +2 -2
  55. package/dist/ai.js +2 -2
  56. package/dist/bounded-cache-CPHWF-8h.js +41 -0
  57. package/dist/bounded-cache-CPHWF-8h.js.map +1 -0
  58. package/dist/{client-BYzHjkwU.js → client-BReKqwZC.js} +447 -80
  59. package/dist/client-BReKqwZC.js.map +1 -0
  60. package/dist/{client-CzyacQpJ.d.ts → client-h_DKFAjj.d.ts} +14 -13
  61. package/dist/client-h_DKFAjj.d.ts.map +1 -0
  62. package/dist/client.d.ts +4 -2
  63. package/dist/client.js +1 -1
  64. package/dist/{contract-CKRg_E4q.js → contract-BYKASlCV.js} +12 -21
  65. package/dist/contract-BYKASlCV.js.map +1 -0
  66. package/dist/{idempotent-replay-DVOlyYbx.js → idempotent-replay-JdgMQRRm.js} +2 -2
  67. package/dist/{idempotent-replay-DVOlyYbx.js.map → idempotent-replay-JdgMQRRm.js.map} +1 -1
  68. package/dist/index.d.ts +6 -5
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +4 -3
  71. package/dist/{errors-DvhSXnxk.d.ts → initial-snapshot-Bx-c0pDP.d.ts} +7 -3
  72. package/dist/initial-snapshot-Bx-c0pDP.d.ts.map +1 -0
  73. package/dist/{internal-Dq2qYxou.js → internal-B2T3U4Rq.js} +29 -2
  74. package/dist/internal-B2T3U4Rq.js.map +1 -0
  75. package/dist/otel.d.ts +1 -1
  76. package/dist/react-BBXgHWFc.d.ts +86 -0
  77. package/dist/react-BBXgHWFc.d.ts.map +1 -0
  78. package/dist/react.d.ts +2 -122
  79. package/dist/react.js +107 -100
  80. package/dist/react.js.map +1 -1
  81. package/dist/reducer-BAXc8JyF.js +87 -0
  82. package/dist/reducer-BAXc8JyF.js.map +1 -0
  83. package/dist/{reducer-otzHjuJj.d.ts → reducer-CqA59Q6U.d.ts} +46 -30
  84. package/dist/reducer-CqA59Q6U.d.ts.map +1 -0
  85. package/dist/reducer-runtime-DQwz_J59.js +28 -0
  86. package/dist/reducer-runtime-DQwz_J59.js.map +1 -0
  87. package/dist/scheduler-qstash.d.ts +3 -3
  88. package/dist/scheduler-qstash.d.ts.map +1 -1
  89. package/dist/scheduler-qstash.js +33 -10
  90. package/dist/scheduler-qstash.js.map +1 -1
  91. package/dist/{scheduler-task-BpzhPnRS.js → scheduler-task-X1SToDjP.js} +18 -3
  92. package/dist/scheduler-task-X1SToDjP.js.map +1 -0
  93. package/dist/scheduler-vercel.d.ts +4 -9
  94. package/dist/scheduler-vercel.d.ts.map +1 -1
  95. package/dist/scheduler-vercel.js +25 -44
  96. package/dist/scheduler-vercel.js.map +1 -1
  97. package/dist/{server-BD5ckxZb.js → server-Dw_k_7kO.js} +904 -219
  98. package/dist/server-Dw_k_7kO.js.map +1 -0
  99. package/dist/{server-DbD7IJVK.d.ts → server-En7cbi3v.d.ts} +116 -26
  100. package/dist/server-En7cbi3v.d.ts.map +1 -0
  101. package/dist/server.d.ts +4 -4
  102. package/dist/server.js +2 -2
  103. package/dist/session-clear-Bo-XTFEy.d.ts +12 -0
  104. package/dist/session-clear-Bo-XTFEy.d.ts.map +1 -0
  105. package/dist/{errors-DCk6ch5n.js → session-clear-DGI4jTQ5.js} +17 -2
  106. package/dist/session-clear-DGI4jTQ5.js.map +1 -0
  107. package/dist/{store-DyZM6fS5.d.ts → store-Djdv0bnZ.d.ts} +73 -3
  108. package/dist/store-Djdv0bnZ.d.ts.map +1 -0
  109. package/dist/store-N8PXxDAS.js.map +1 -1
  110. package/dist/store-memory.d.ts +1 -1
  111. package/dist/store-memory.d.ts.map +1 -1
  112. package/dist/store-memory.js +205 -66
  113. package/dist/store-memory.js.map +1 -1
  114. package/dist/{store-polling-CmxUbV93.js → store-polling-B5FcwJfo.js} +21 -4
  115. package/dist/store-polling-B5FcwJfo.js.map +1 -0
  116. package/dist/store-postgres.d.ts +1 -1
  117. package/dist/store-postgres.d.ts.map +1 -1
  118. package/dist/store-postgres.js +450 -108
  119. package/dist/store-postgres.js.map +1 -1
  120. package/dist/{store-presence-polling-C7-XZyW9.js → store-presence-polling-BAwS8RWn.js} +3 -3
  121. package/dist/{store-presence-polling-C7-XZyW9.js.map → store-presence-polling-BAwS8RWn.js.map} +1 -1
  122. package/dist/store-redis-http.d.ts +1 -1
  123. package/dist/store-redis-http.d.ts.map +1 -1
  124. package/dist/store-redis-http.js +293 -11
  125. package/dist/store-redis-http.js.map +1 -1
  126. package/dist/store-redis-notify-BRITti_o.js +2493 -0
  127. package/dist/store-redis-notify-BRITti_o.js.map +1 -0
  128. package/dist/store-redis.d.ts +1 -1
  129. package/dist/store-redis.d.ts.map +1 -1
  130. package/dist/store-redis.js +11 -11
  131. package/dist/store-redis.js.map +1 -1
  132. package/dist/store-sqlite.d.ts +1 -1
  133. package/dist/store-sqlite.d.ts.map +1 -1
  134. package/dist/store-sqlite.js +197 -33
  135. package/dist/store-sqlite.js.map +1 -1
  136. package/dist/stream-buffer-Cx7AAPGq.js +23 -0
  137. package/dist/stream-buffer-Cx7AAPGq.js.map +1 -0
  138. package/dist/{telemetry-B5jzpy6y.d.ts → telemetry-Cyfqo6Qt.d.ts} +2 -2
  139. package/dist/telemetry-Cyfqo6Qt.d.ts.map +1 -0
  140. package/dist/{wire-BO5wWCb1.js → wire-rSUglYo8.js} +132 -10
  141. package/dist/wire-rSUglYo8.js.map +1 -0
  142. package/docs/01-quickstart.mdx +50 -84
  143. package/docs/{guides/07-examples.mdx → 02-examples.mdx} +10 -20
  144. package/docs/actors/01-introduction.mdx +48 -82
  145. package/docs/actors/02-concurrency.mdx +43 -70
  146. package/docs/actors/03-timers.mdx +28 -42
  147. package/docs/actors/04-routes.mdx +110 -137
  148. package/docs/actors/meta.ts +1 -1
  149. package/docs/agents/01-introduction.mdx +257 -0
  150. package/docs/agents/02-tools.mdx +247 -0
  151. package/docs/agents/03-conversations.mdx +538 -0
  152. package/docs/agents/04-harnesses.mdx +136 -0
  153. package/docs/agents/meta.ts +1 -0
  154. package/docs/client/01-react.mdx +294 -0
  155. package/docs/client/02-transports.mdx +86 -0
  156. package/docs/client/03-presence.mdx +224 -0
  157. package/docs/client/meta.ts +1 -0
  158. package/docs/contract/01-events.mdx +57 -0
  159. package/docs/contract/02-state.mdx +142 -0
  160. package/docs/contract/03-sessions.mdx +175 -0
  161. package/docs/index.mdx +112 -156
  162. package/docs/reference/01-core.mdx +139 -0
  163. package/docs/reference/02-server.mdx +855 -0
  164. package/docs/reference/03-client.mdx +301 -0
  165. package/docs/reference/04-ai.mdx +722 -0
  166. package/docs/reference/05-adapters.mdx +351 -0
  167. package/docs/reference/06-errors.mdx +138 -0
  168. package/docs/reference/meta.ts +1 -1
  169. package/docs/server/01-handlers.mdx +200 -0
  170. package/docs/server/02-timers.mdx +130 -0
  171. package/docs/server/03-cancellation.mdx +141 -0
  172. package/docs/server/04-routes.mdx +243 -0
  173. package/docs/server/05-production.mdx +322 -0
  174. package/docs/server/06-guarantees.mdx +123 -0
  175. package/examples/playground/DEPLOY.md +53 -7
  176. package/examples/playground/app/a0/[appId]/loading.tsx +16 -0
  177. package/examples/playground/app/a0/[appId]/page.tsx +53 -0
  178. package/examples/playground/app/a0/a0.css +818 -0
  179. package/examples/playground/app/a0/app-list.tsx +218 -0
  180. package/examples/playground/app/a0/app-view.tsx +63 -0
  181. package/examples/playground/app/a0/apps/[appId]/preview/route.ts +27 -0
  182. package/examples/playground/app/a0/apps/[appId]/route.ts +28 -0
  183. package/examples/playground/app/a0/apps.test.ts +128 -0
  184. package/examples/playground/app/a0/apps.ts +102 -0
  185. package/examples/playground/app/a0/chat.tsx +410 -0
  186. package/examples/playground/app/a0/columns.tsx +109 -0
  187. package/examples/playground/app/a0/context-menu.tsx +116 -0
  188. package/examples/playground/app/a0/cookies.ts +46 -0
  189. package/examples/playground/app/a0/crash-on-request.ts +72 -0
  190. package/examples/playground/app/a0/crashes/route.ts +3 -0
  191. package/examples/playground/app/a0/crashes.test.ts +33 -0
  192. package/examples/playground/app/a0/crashes.ts +28 -0
  193. package/examples/playground/app/a0/devtools.tsx +164 -0
  194. package/examples/playground/app/a0/events/route.ts +6 -0
  195. package/examples/playground/app/a0/flags.ts +30 -0
  196. package/examples/playground/app/a0/fx/events/route.ts +9 -0
  197. package/examples/playground/app/a0/fx/scheduler/route.ts +7 -0
  198. package/examples/playground/app/a0/fx/short/events/route.ts +11 -0
  199. package/examples/playground/app/a0/layout.tsx +38 -0
  200. package/examples/playground/app/a0/list/route.ts +10 -0
  201. package/examples/playground/app/a0/model.test.ts +73 -0
  202. package/examples/playground/app/a0/model.ts +95 -0
  203. package/examples/playground/app/a0/no-photon.cjs +4 -0
  204. package/examples/playground/app/a0/no-quickjs.cjs +3 -0
  205. package/examples/playground/app/a0/page.tsx +14 -0
  206. package/examples/playground/app/a0/pi/events/route.ts +9 -0
  207. package/examples/playground/app/a0/pi/scheduler/route.ts +7 -0
  208. package/examples/playground/app/a0/pi/short/events/route.ts +11 -0
  209. package/examples/playground/app/a0/preview-pane.tsx +145 -0
  210. package/examples/playground/app/a0/preview.test.ts +53 -0
  211. package/examples/playground/app/a0/preview.ts +26 -0
  212. package/examples/playground/app/a0/sandbox-log.ts +75 -0
  213. package/examples/playground/app/a0/sandbox-session.ts +152 -0
  214. package/examples/playground/app/a0/sandbox.ts +191 -0
  215. package/examples/playground/app/a0/sandboxes/scheduler/route.ts +6 -0
  216. package/examples/playground/app/a0/scheduler/route.ts +7 -0
  217. package/examples/playground/app/a0/server.ts +213 -0
  218. package/examples/playground/app/a0/session.ts +59 -0
  219. package/examples/playground/app/a0/shell.tsx +158 -0
  220. package/examples/playground/app/a0/short/events/route.ts +9 -0
  221. package/examples/playground/app/a0/splitter.tsx +120 -0
  222. package/examples/playground/app/agent/[agentId]/agent-client.tsx +144 -174
  223. package/examples/playground/app/agent/[agentId]/command-receipt.test.tsx +65 -0
  224. package/examples/playground/app/agent/[agentId]/command-receipt.tsx +27 -0
  225. package/examples/playground/app/agent/[agentId]/compaction-event.tsx +25 -24
  226. package/examples/playground/app/agent/[agentId]/compaction-panel.test.tsx +119 -0
  227. package/examples/playground/app/agent/[agentId]/compaction-panel.tsx +208 -165
  228. package/examples/playground/app/agent/[agentId]/page.tsx +2 -16
  229. package/examples/playground/app/agent/events/route.ts +5 -2
  230. package/examples/playground/app/agent/ingress.test.ts +101 -0
  231. package/examples/playground/app/agent/model-input.test.tsx +58 -0
  232. package/examples/playground/app/agent/model-input.tsx +115 -0
  233. package/examples/playground/app/agent/model.test.ts +73 -0
  234. package/examples/playground/app/agent/model.ts +58 -26
  235. package/examples/playground/app/agent/new-agent-session.tsx +37 -19
  236. package/examples/playground/app/agent/page.tsx +4 -4
  237. package/examples/playground/app/agent/server.ts +19 -15
  238. package/examples/playground/app/agent/session.ts +5 -6
  239. package/examples/playground/app/agent/telemetry.test.ts +189 -0
  240. package/examples/playground/app/agent/telemetry.ts +30 -0
  241. package/examples/playground/app/agent/trace.test.ts +23 -0
  242. package/examples/playground/app/agent/trace.ts +18 -0
  243. package/examples/playground/app/canvas/[canvasId]/canvas-client.tsx +12 -12
  244. package/examples/playground/app/canvas/[canvasId]/page.tsx +1 -2
  245. package/examples/playground/app/canvas/[canvasId]/session.ts +2 -2
  246. package/examples/playground/app/canvas/model.ts +53 -53
  247. package/examples/playground/app/chat/[chatId]/agent-state.test.ts +176 -0
  248. package/examples/playground/app/chat/[chatId]/agent-stream-drawer.tsx +16 -1
  249. package/examples/playground/app/chat/[chatId]/chat-client.tsx +55 -77
  250. package/examples/playground/app/chat/[chatId]/chat-view.ts +37 -3
  251. package/examples/playground/app/chat/[chatId]/page.tsx +7 -15
  252. package/examples/playground/app/chat/[chatId]/session.ts +2 -19
  253. package/examples/playground/app/chat/agent/route.ts +9 -0
  254. package/examples/playground/app/chat/model.test.ts +46 -119
  255. package/examples/playground/app/chat/model.ts +82 -128
  256. package/examples/playground/app/chat/scheduler/route.ts +2 -2
  257. package/examples/playground/app/chat/server.test.ts +112 -0
  258. package/examples/playground/app/chat/server.ts +113 -117
  259. package/examples/playground/app/components/activity-feed.tsx +30 -1
  260. package/examples/playground/app/components/activity-history.test.tsx +35 -0
  261. package/examples/playground/app/components/demo-icons.tsx +89 -0
  262. package/examples/playground/app/components/demos.tsx +33 -0
  263. package/examples/playground/app/components/fast-link.test.tsx +58 -0
  264. package/examples/playground/app/components/fast-link.tsx +55 -0
  265. package/examples/playground/app/components/site-header.tsx +96 -0
  266. package/examples/playground/app/counter/counter-client.tsx +3 -3
  267. package/examples/playground/app/counter/model.test.ts +3 -3
  268. package/examples/playground/app/counter/model.ts +11 -21
  269. package/examples/playground/app/counter/page.tsx +2 -15
  270. package/examples/playground/app/counter/session.ts +2 -2
  271. package/examples/playground/app/documents/[documentId]/code-editor.tsx +12 -1
  272. package/examples/playground/app/documents/[documentId]/document-client.tsx +10 -11
  273. package/examples/playground/app/documents/[documentId]/page.tsx +1 -2
  274. package/examples/playground/app/documents/[documentId]/session.ts +2 -2
  275. package/examples/playground/app/documents/model.ts +27 -27
  276. package/examples/playground/app/globals.css +420 -207
  277. package/examples/playground/app/initial-state.test.tsx +54 -0
  278. package/examples/playground/app/layout.tsx +7 -23
  279. package/examples/playground/app/orders/[orderId]/page.tsx +1 -2
  280. package/examples/playground/app/orders/[orderId]/session.ts +2 -2
  281. package/examples/playground/app/orders/model.ts +49 -49
  282. package/examples/playground/app/page.tsx +13 -102
  283. package/examples/playground/app/recovery/[recoveryId]/page.tsx +1 -2
  284. package/examples/playground/app/recovery/[recoveryId]/session.ts +2 -2
  285. package/examples/playground/app/recovery/model.ts +30 -30
  286. package/examples/playground/instrumentation.ts +5 -0
  287. package/examples/playground/next.config.ts +17 -5
  288. package/examples/playground/package.json +13 -2
  289. package/examples/playground/scripts/a0-template.ts +77 -0
  290. package/examples/playground/vercel.json +36 -0
  291. package/package.json +29 -3
  292. package/src/actor-client.ts +16 -4
  293. package/src/actor-react.ts +25 -11
  294. package/src/actor-shared.ts +21 -6
  295. package/src/actor.ts +76 -31
  296. package/src/ai-agent-actions.ts +219 -0
  297. package/src/ai-agent.ts +2517 -0
  298. package/src/ai-app-state.ts +91 -0
  299. package/src/ai-client-state.ts +6 -3
  300. package/src/ai-client.ts +62 -0
  301. package/src/ai-context-schema.ts +25 -5
  302. package/src/ai-context.ts +90 -40
  303. package/src/ai-control-server.ts +92 -72
  304. package/src/ai-coordinator.ts +23 -22
  305. package/src/ai-fx.browser.ts +5 -0
  306. package/src/ai-fx.ts +833 -0
  307. package/src/ai-harness.ts +518 -0
  308. package/src/ai-message-base.ts +209 -0
  309. package/src/ai-message-projection.ts +262 -64
  310. package/src/ai-model-metadata.ts +11 -6
  311. package/src/ai-pi.browser.ts +5 -0
  312. package/src/ai-pi.ts +1263 -0
  313. package/src/ai-progress-batches.ts +26 -0
  314. package/src/ai-projector.ts +95 -14
  315. package/src/ai-react.ts +146 -0
  316. package/src/ai-sdk-step.ts +2 -2
  317. package/src/ai-server.browser.ts +2 -2
  318. package/src/ai-server.ts +23 -1965
  319. package/src/ai-stored-state.ts +48 -163
  320. package/src/ai.ts +339 -155
  321. package/src/bootstrap-stream.ts +72 -0
  322. package/src/bounded-cache.ts +55 -0
  323. package/src/client-render-session.ts +49 -0
  324. package/src/client.ts +688 -151
  325. package/src/contract.ts +92 -42
  326. package/src/deterministic-id.ts +37 -0
  327. package/src/equal-json.ts +32 -0
  328. package/src/errors.ts +13 -1
  329. package/src/index.ts +5 -1
  330. package/src/initial-snapshot.ts +39 -0
  331. package/src/internal.ts +52 -0
  332. package/src/libfx.d.ts +34 -0
  333. package/src/postgres-notification-scope.ts +14 -3
  334. package/src/react.ts +171 -206
  335. package/src/redis-http-reply.ts +256 -0
  336. package/src/redis-http-stream.ts +73 -0
  337. package/src/redis-http-subscriptions.ts +13 -5
  338. package/src/reducer-runtime.ts +52 -0
  339. package/src/reducer.ts +93 -11
  340. package/src/scheduler-qstash.ts +49 -20
  341. package/src/scheduler-task.ts +34 -2
  342. package/src/scheduler-vercel.ts +32 -84
  343. package/src/server-fetch.ts +139 -35
  344. package/src/server.ts +1133 -255
  345. package/src/session-clear.ts +23 -0
  346. package/src/session-socket.ts +154 -49
  347. package/src/sse.ts +71 -4
  348. package/src/store-memory.ts +252 -75
  349. package/src/store-polling.ts +18 -4
  350. package/src/store-postgres.ts +541 -114
  351. package/src/store-redis-core.ts +1450 -493
  352. package/src/store-redis-http.ts +18 -2
  353. package/src/store-redis-notify.ts +262 -85
  354. package/src/store-redis.ts +19 -9
  355. package/src/store-sqlite.ts +319 -45
  356. package/src/store.ts +79 -1
  357. package/src/stream-buffer.ts +43 -0
  358. package/src/telemetry.ts +14 -1
  359. package/src/wire.ts +241 -13
  360. package/dist/actor-ohPC-81x.d.ts.map +0 -1
  361. package/dist/actor-shared-B5tJfzt-.js.map +0 -1
  362. package/dist/ai-CrEf6p_W.d.ts.map +0 -1
  363. package/dist/ai-DgOBltJ_.js.map +0 -1
  364. package/dist/client-BYzHjkwU.js.map +0 -1
  365. package/dist/client-CzyacQpJ.d.ts.map +0 -1
  366. package/dist/contract-CKRg_E4q.js.map +0 -1
  367. package/dist/errors-DCk6ch5n.js.map +0 -1
  368. package/dist/errors-DvhSXnxk.d.ts.map +0 -1
  369. package/dist/internal-Dq2qYxou.js.map +0 -1
  370. package/dist/react.d.ts.map +0 -1
  371. package/dist/reducer-DEMjEY_O.js +0 -29
  372. package/dist/reducer-DEMjEY_O.js.map +0 -1
  373. package/dist/reducer-otzHjuJj.d.ts.map +0 -1
  374. package/dist/scheduler-task-BpzhPnRS.js.map +0 -1
  375. package/dist/server-BD5ckxZb.js.map +0 -1
  376. package/dist/server-DbD7IJVK.d.ts.map +0 -1
  377. package/dist/store-DyZM6fS5.d.ts.map +0 -1
  378. package/dist/store-polling-CmxUbV93.js.map +0 -1
  379. package/dist/store-redis-notify-BVLUfI3j.js +0 -1583
  380. package/dist/store-redis-notify-BVLUfI3j.js.map +0 -1
  381. package/dist/telemetry-B5jzpy6y.d.ts.map +0 -1
  382. package/dist/validate-XKT4FSNn.js +0 -30
  383. package/dist/validate-XKT4FSNn.js.map +0 -1
  384. package/dist/wire-BO5wWCb1.js.map +0 -1
  385. package/docs/concepts/01-contracts.mdx +0 -142
  386. package/docs/concepts/02-handlers.mdx +0 -284
  387. package/docs/concepts/03-durability.mdx +0 -314
  388. package/docs/concepts/04-state.mdx +0 -209
  389. package/docs/guides/01-timers.mdx +0 -185
  390. package/docs/guides/02-cancellation.mdx +0 -161
  391. package/docs/guides/03-react.mdx +0 -384
  392. package/docs/guides/04-local-first.mdx +0 -88
  393. package/docs/guides/05-production.mdx +0 -521
  394. package/docs/guides/06-ai-agents.mdx +0 -1318
  395. package/docs/guides/08-application-data.mdx +0 -113
  396. package/docs/guides/09-presence.mdx +0 -266
  397. package/docs/guides/10-transports.mdx +0 -221
  398. package/docs/reference/01-api.mdx +0 -1896
  399. package/docs/reference/02-errors.mdx +0 -149
  400. package/examples/playground/app/agent/[agentId]/compaction/route.ts +0 -14
  401. package/examples/playground/app/agent/compaction-settings.test.ts +0 -144
  402. package/examples/playground/app/agent/compaction-settings.ts +0 -49
  403. package/examples/playground/app/agent/compaction-timeline.test.ts +0 -377
  404. package/examples/playground/app/agent/compaction-timeline.ts +0 -208
  405. /package/docs/{concepts → contract}/meta.ts +0 -0
  406. /package/docs/{guides → server}/meta.ts +0 -0
@@ -0,0 +1,189 @@
1
+ import { context, trace } from '@opentelemetry/api'
2
+ import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks'
3
+ import {
4
+ BasicTracerProvider,
5
+ InMemorySpanExporter,
6
+ SimpleSpanProcessor,
7
+ } from '@opentelemetry/sdk-trace-base'
8
+ import { simulateReadableStream } from 'ai'
9
+ import { MockLanguageModelV4 } from 'ai/test'
10
+ import { inputs } from 'experimental-a2/ai'
11
+ import { agent } from 'experimental-a2/ai/server'
12
+ import { memory } from 'experimental-a2/store-memory'
13
+ import { otel } from 'experimental-a2/otel'
14
+ import {
15
+ afterAll,
16
+ beforeAll,
17
+ beforeEach,
18
+ expect,
19
+ it,
20
+ onTestFinished,
21
+ vi,
22
+ } from 'vitest'
23
+ import { demoAgent } from './model'
24
+ import { modelTelemetry, traceMetadata } from './telemetry'
25
+ import { traceUrl } from './trace'
26
+
27
+ const exporter = new InMemorySpanExporter()
28
+ const openSpans = new Set<string>()
29
+ const provider = new BasicTracerProvider({
30
+ spanProcessors: [
31
+ new SimpleSpanProcessor(exporter),
32
+ {
33
+ onStart: (span) => {
34
+ openSpans.add(span.spanContext().spanId)
35
+ },
36
+ onEnd: (span) => {
37
+ openSpans.delete(span.spanContext().spanId)
38
+ },
39
+ forceFlush: async () => {},
40
+ shutdown: async () => {},
41
+ },
42
+ ],
43
+ })
44
+ beforeAll(() => {
45
+ context.setGlobalContextManager(
46
+ new AsyncLocalStorageContextManager().enable(),
47
+ )
48
+ trace.setGlobalTracerProvider(provider)
49
+ })
50
+ beforeEach(() => {
51
+ exporter.reset()
52
+ })
53
+ afterAll(async () => {
54
+ await provider.shutdown()
55
+ trace.disable()
56
+ context.disable()
57
+ })
58
+
59
+ it.each(['complete', 'stop'] as const)(
60
+ 'correlates standard SDK traces and releases spans when generation ends with %s',
61
+ async (outcome) => {
62
+ const model = new MockLanguageModelV4({
63
+ modelId: 'test-model',
64
+ doStream: async ({ abortSignal }) => ({
65
+ stream:
66
+ outcome === 'complete'
67
+ ? simulateReadableStream({
68
+ chunks: [
69
+ { type: 'stream-start', warnings: [] },
70
+ { type: 'text-start', id: 'text' },
71
+ { type: 'text-delta', id: 'text', delta: 'PRIVATE_OUTPUT' },
72
+ { type: 'text-end', id: 'text' },
73
+ {
74
+ type: 'finish',
75
+ finishReason: { unified: 'stop', raw: 'stop' },
76
+ usage: {
77
+ inputTokens: {
78
+ total: 5,
79
+ noCache: 5,
80
+ cacheRead: 0,
81
+ cacheWrite: 0,
82
+ },
83
+ outputTokens: { total: 2, text: 2, reasoning: 0 },
84
+ },
85
+ providerMetadata: {
86
+ test: { secret: 'PRIVATE_METADATA' },
87
+ },
88
+ },
89
+ ],
90
+ })
91
+ : new ReadableStream({
92
+ start(controller) {
93
+ controller.enqueue({ type: 'stream-start', warnings: [] })
94
+ controller.enqueue({ type: 'text-start', id: 'text' })
95
+ controller.enqueue({
96
+ type: 'text-delta',
97
+ id: 'text',
98
+ delta: 'PRIVATE_PARTIAL',
99
+ })
100
+ abortSignal?.addEventListener(
101
+ 'abort',
102
+ () => controller.close(),
103
+ { once: true },
104
+ )
105
+ },
106
+ }),
107
+ }),
108
+ })
109
+ // Agents take AI Gateway model IDs; the AI SDK's global provider serves
110
+ // this one from the mock.
111
+ vi.stubGlobal('AI_SDK_DEFAULT_PROVIDER', {
112
+ specificationVersion: 'v4',
113
+ languageModel: () => model,
114
+ })
115
+ onTestFinished(() => {
116
+ vi.unstubAllGlobals()
117
+ })
118
+ const server = agent({
119
+ contract: demoAgent,
120
+ store: memory(),
121
+ telemetry: otel(),
122
+ harness: {
123
+ type: 'ai-sdk',
124
+ generation: { telemetry: modelTelemetry() },
125
+ messageMetadata: traceMetadata,
126
+ },
127
+ model: 'test-model',
128
+ })
129
+ const session = server.session(`trace-${outcome}`)
130
+ await session.append(
131
+ ...inputs.message({
132
+ id: 'user',
133
+ role: 'user',
134
+ parts: [{ type: 'text', text: 'PRIVATE_PROMPT' }],
135
+ }),
136
+ )
137
+ if (outcome === 'stop') {
138
+ await vi.waitUntil(async () => {
139
+ const { state } = await session.state(demoAgent.reducer)
140
+ return state.messages
141
+ .at(-1)
142
+ ?.parts.some(
143
+ (part) => part.type === 'text' && part.text === 'PRIVATE_PARTIAL',
144
+ )
145
+ })
146
+ const { state } = await session.state(demoAgent.reducer)
147
+ await session.append(
148
+ ...inputs.stop({
149
+ turnId: state.active!.turnId,
150
+ }),
151
+ )
152
+ }
153
+ await vi.waitFor(async () => {
154
+ const { state } = await session.state(demoAgent.reducer)
155
+ const view = state
156
+ expect(view.status).toBe('idle')
157
+ expect(view.messages.at(-1)?.role).toBe('assistant')
158
+ expect(openSpans.size).toBe(0)
159
+ })
160
+ const { state } = await session.state(demoAgent.reducer)
161
+ const metadata = state.messages.at(-1)?.metadata
162
+ const spans = exporter.getFinishedSpans()
163
+ const generation = spans.find((span) => span.name === 'a2.ai.generate')!
164
+ expect(metadata).toEqual({ traceId: generation.spanContext().traceId })
165
+ expect(traceUrl(metadata)).toContain(generation.spanContext().traceId)
166
+ const models = spans.filter(
167
+ (span) => span.instrumentationScope.name === 'gen_ai',
168
+ )
169
+ expect(models.length).toBeGreaterThan(0)
170
+ expect(
171
+ models.every(
172
+ (span) =>
173
+ span.spanContext().traceId === generation.spanContext().traceId,
174
+ ),
175
+ ).toBe(true)
176
+ expect(spans.some((span) => span.name.startsWith('a2.model'))).toBe(false)
177
+ expect(
178
+ JSON.stringify(
179
+ spans.map(({ attributes, events }) => ({ attributes, events })),
180
+ ),
181
+ ).not.toContain('PRIVATE_')
182
+ },
183
+ )
184
+
185
+ it('clears an earlier trace when no active trace is available', () => {
186
+ expect(
187
+ traceMetadata({ generationId: 'next', part: { type: 'start' } }),
188
+ ).toEqual({ traceId: null })
189
+ })
@@ -0,0 +1,30 @@
1
+ import { OpenTelemetry } from '@ai-sdk/otel'
2
+ import { isSpanContextValid, trace } from '@opentelemetry/api'
3
+ import type { TelemetryOptions, TextStreamPart, ToolSet } from 'ai'
4
+
5
+ export function modelTelemetry(): TelemetryOptions {
6
+ return {
7
+ recordInputs: false,
8
+ recordOutputs: false,
9
+ integrations: [new OpenTelemetry()],
10
+ }
11
+ }
12
+
13
+ export function traceMetadata({
14
+ generationId,
15
+ part,
16
+ }: {
17
+ generationId: string
18
+ part: TextStreamPart<ToolSet>
19
+ }): { traceId: string | null } | undefined {
20
+ if (part.type !== 'start') return
21
+ const span = trace.getActiveSpan()
22
+ span?.setAttribute('a2.generation_id', generationId)
23
+ const spanContext = span?.spanContext()
24
+ return {
25
+ traceId:
26
+ spanContext && isSpanContextValid(spanContext)
27
+ ? spanContext.traceId
28
+ : null,
29
+ }
30
+ }
@@ -0,0 +1,23 @@
1
+ import { expect, it } from 'vitest'
2
+ import { traceUrl } from './trace'
3
+
4
+ const traceId = '1234567890abcdef1234567890abcdef'
5
+
6
+ it('links current and previously recorded trace metadata to Vercel', () => {
7
+ const current = traceUrl({ traceId })!
8
+ expect(traceUrl({ timing: { traceId } })).toBe(current)
9
+ const url = new URL(current)
10
+ expect(url.origin).toBe('https://vercel.com')
11
+ expect(url.searchParams.get('search')).toBe(`traceId:${traceId}`)
12
+ })
13
+
14
+ it('does not reuse an older trace after the latest generation clears it', () => {
15
+ expect(traceUrl({ traceId: null, timing: { traceId } })).toBeUndefined()
16
+ })
17
+
18
+ it.each([undefined, null, {}, { traceId: 'https://example.com' }])(
19
+ 'omits the link for invalid metadata %j',
20
+ (metadata) => {
21
+ expect(traceUrl(metadata)).toBeUndefined()
22
+ },
23
+ )
@@ -0,0 +1,18 @@
1
+ import { z } from 'zod'
2
+
3
+ const traceId = z.string().regex(/^[0-9a-f]{32}$/)
4
+ const metadataSchema = z.object({
5
+ traceId: traceId.nullable().optional(),
6
+ timing: z.object({ traceId: traceId.optional() }).optional(),
7
+ })
8
+
9
+ export function traceUrl(metadata: unknown): string | undefined {
10
+ const parsed = metadataSchema.safeParse(metadata)
11
+ if (!parsed.success) return
12
+ const id =
13
+ parsed.data.traceId === undefined
14
+ ? parsed.data.timing?.traceId
15
+ : parsed.data.traceId
16
+ if (id)
17
+ return `https://vercel.com/vercel-labs/a2-playground/logs?${new URLSearchParams({ search: `traceId:${id}`, timeline: 'past2Weeks' })}`
18
+ }
@@ -1,4 +1,5 @@
1
1
  'use client'
2
+ import type { StateSnapshot } from 'experimental-a2'
2
3
  import type {
3
4
  CSSProperties,
4
5
  PointerEvent as ReactPointerEvent,
@@ -30,11 +31,11 @@ const ADJECTIVES = ['Bright', 'Calm', 'Curious', 'Kind', 'Swift'] as const
30
31
  const ANIMALS = ['Fox', 'Heron', 'Otter', 'Panda', 'Wren'] as const
31
32
  const COLORS = ['teal', 'amber', 'blue', 'violet', 'rose'] as const
32
33
  const COLOR_HEX: Record<PaletteColor, string> = {
33
- teal: '#16836a',
34
- amber: '#bd7917',
35
- blue: '#3874c8',
36
- violet: '#7956b6',
37
- rose: '#bd5269',
34
+ teal: '#3ccf9f',
35
+ amber: '#f2b54b',
36
+ blue: '#6ea8fe',
37
+ violet: '#a98bff',
38
+ rose: '#ff7a93',
38
39
  }
39
40
 
40
41
  /** The tab's identity: an opaque participant id, plus the name and
@@ -191,13 +192,12 @@ function Smoothed({
191
192
 
192
193
  export function CanvasSession({
193
194
  canvasId,
194
- initialState,
195
- initialIndex,
195
+ initial,
196
196
  }: {
197
197
  canvasId: string
198
- initialState: CanvasState
199
- initialIndex: number
198
+ initial: StateSnapshot<CanvasState>
200
199
  }): ReactNode {
200
+ const { state: initialState, index: initialIndex } = initial
201
201
  // The server snapshot is null so hydration matches exactly. The
202
202
  // browser snapshot then mints one stable tab identity before presence
203
203
  // becomes interactive.
@@ -205,8 +205,8 @@ export function CanvasSession({
205
205
  return (
206
206
  <SessionProvider
207
207
  sessionId={canvasId}
208
- initialState={initialState}
209
- initialIndex={initialIndex}
208
+ initial={initial}
209
+
210
210
  {...(identity ? { participant: identity.id } : {})}
211
211
  >
212
212
  <CanvasClient
@@ -330,7 +330,7 @@ function CanvasClient({
330
330
 
331
331
  // A ghost is dead once the log has moved past it: show `dragging`
332
332
  // only while its `seen` frontier reaches the shape's durable
333
- // `movedAt` (/guides/presence#reconciling-the-planes-seen).
333
+ // `movedAt` (/client/presence#reconciling-the-planes-seen).
334
334
  const ghosts = new Map<
335
335
  string,
336
336
  { x: number; y: number; color: PaletteColor }
@@ -15,8 +15,7 @@ export default async function CanvasSessionPage({
15
15
  return (
16
16
  <CanvasSession
17
17
  canvasId={canvasId}
18
- initialState={state}
19
- initialIndex={index}
18
+ initial={{ state: state, index: index }}
20
19
  />
21
20
  )
22
21
  }
@@ -1,14 +1,14 @@
1
1
  'use client'
2
2
  import { createClient } from 'experimental-a2/client'
3
3
  import { createReact } from 'experimental-a2/react'
4
- import { canvasReducer } from '../model'
4
+ import { canvas } from '../model'
5
5
 
6
6
  // One route, two wires: on Vercel (production and previews), pushes
7
7
  // and presence ride one WebSocket. Anywhere else, including `next
8
8
  // dev` (which drops the upgrade handshake before the route runs) and
9
9
  // a local `next start`, the same route serves the SSE + POST pair.
10
10
  export const canvasClient = createClient({
11
- reducer: canvasReducer,
11
+ contract: canvas,
12
12
  api: process.env.NEXT_PUBLIC_VERCEL_ENV
13
13
  ? { type: 'ws', url: '/canvas/events' }
14
14
  : '/canvas/events',
@@ -5,6 +5,24 @@ const paletteSchema = z.enum(['teal', 'amber', 'blue', 'violet', 'rose'])
5
5
 
6
6
  export type PaletteColor = z.output<typeof paletteSchema>
7
7
 
8
+ const canvasShapeSchema = z.object({
9
+ id: z.string(),
10
+ kind: z.enum(['rect', 'ellipse']),
11
+ x: z.number(),
12
+ y: z.number(),
13
+ w: z.number(),
14
+ h: z.number(),
15
+ color: paletteSchema,
16
+ movedAt: z.number(),
17
+ })
18
+
19
+ const canvasState = z.object({
20
+ shapes: z.record(z.string(), canvasShapeSchema),
21
+ })
22
+
23
+ export type CanvasState = z.output<typeof canvasState>
24
+ export type CanvasShape = CanvasState['shapes'][string]
25
+
8
26
  export const canvas = a2.contract({
9
27
  name: 'canvas',
10
28
  events: {
@@ -32,63 +50,45 @@ export const canvas = a2.contract({
32
50
  cursor: z.object({ x: z.number(), y: z.number() }),
33
51
  dragging: z.object({ shapeId: z.string(), x: z.number(), y: z.number() }),
34
52
  },
35
- })
36
53
 
37
- const canvasShapeSchema = z.object({
38
- id: z.string(),
39
- kind: z.enum(['rect', 'ellipse']),
40
- x: z.number(),
41
- y: z.number(),
42
- w: z.number(),
43
- h: z.number(),
44
- color: paletteSchema,
45
- movedAt: z.number(),
46
- })
47
-
48
- const canvasState = z.object({
49
- shapes: z.record(z.string(), canvasShapeSchema),
50
- })
51
-
52
- export type CanvasState = z.output<typeof canvasState>
53
- export type CanvasShape = CanvasState['shapes'][string]
54
-
55
- export const canvasReducer = canvas
56
- .reducer({
54
+ reducer: {
57
55
  name: 'canvas-scene',
58
56
  initialState: { shapes: {} },
59
57
  stateSchema: canvasState,
60
- })
61
- .fold((state, event) => {
62
- switch (event.type) {
63
- case 'shape.created':
64
- return {
65
- shapes: {
66
- ...state.shapes,
67
- [event.payload.id]: { ...event.payload, movedAt: event.index },
68
- },
69
- }
70
- case 'shape.moved': {
71
- const shape = state.shapes[event.payload.id]
72
- if (!shape) return state
73
- return {
74
- shapes: {
75
- ...state.shapes,
76
- [shape.id]: {
77
- ...shape,
78
- x: event.payload.x,
79
- y: event.payload.y,
80
- movedAt: event.index,
58
+ fold: (state, event) => {
59
+ switch (event.type) {
60
+ case 'shape.created':
61
+ return {
62
+ shapes: {
63
+ ...state.shapes,
64
+ [event.payload.id]: { ...event.payload, movedAt: event.index },
81
65
  },
82
- },
66
+ }
67
+ case 'shape.moved': {
68
+ const shape = state.shapes[event.payload.id]
69
+ if (!shape) return state
70
+ return {
71
+ shapes: {
72
+ ...state.shapes,
73
+ [shape.id]: {
74
+ ...shape,
75
+ x: event.payload.x,
76
+ y: event.payload.y,
77
+ movedAt: event.index,
78
+ },
79
+ },
80
+ }
83
81
  }
82
+ case 'shape.deleted': {
83
+ if (!state.shapes[event.payload.id]) return state
84
+ const shapes = { ...state.shapes }
85
+ delete shapes[event.payload.id]
86
+ return { shapes }
87
+ }
88
+ default:
89
+ return state
84
90
  }
85
- case 'shape.deleted': {
86
- if (!state.shapes[event.payload.id]) return state
87
- const shapes = { ...state.shapes }
88
- delete shapes[event.payload.id]
89
- return { shapes }
90
- }
91
- default:
92
- return state
93
- }
94
- })
91
+ },
92
+ },
93
+ })
94
+ export const canvasReducer = canvas.reducer
@@ -0,0 +1,176 @@
1
+ import { expect, it } from 'vitest'
2
+ import { reduceAIState, type AIState } from 'experimental-a2/ai'
3
+ import { chatAgent, type ChatAgentMessage } from '../model'
4
+ import { agentRetry, agentStreamStatus } from './chat-view'
5
+
6
+ const generation = {
7
+ generationId: 'generation',
8
+ requestId: 'request',
9
+ responseMessageId: 'response',
10
+ messageId: 'input',
11
+ model: 'test/model',
12
+ attempt: 1,
13
+ }
14
+
15
+ function running() {
16
+ let index = 0
17
+ let state: AIState<ChatAgentMessage> = {
18
+ ...chatAgent.reducer.initialState,
19
+ active: {
20
+ turnId: 'turn',
21
+ phase: 'generating',
22
+ input: {
23
+ id: 'input',
24
+ generate: true,
25
+ message: { id: 'input', role: 'user', parts: [] },
26
+ },
27
+ },
28
+ }
29
+ const append = (event: { type: string; payload: unknown; id?: string }) => {
30
+ state = reduceAIState(state, { ...event, index: ++index })
31
+ }
32
+ append({
33
+ id: 'request',
34
+ type: 'ai.generation.requested',
35
+ payload: { ...generation, reason: 'message' },
36
+ })
37
+ append({ type: 'ai.generation.started', payload: generation })
38
+ const snapshot = () => state
39
+ return { append, snapshot }
40
+ }
41
+
42
+ it.each([false, true])(
43
+ 'can retry from a restored snapshot before and after the control commit (step limit: %s)',
44
+ (stepLimit) => {
45
+ const log = running()
46
+ if (stepLimit) {
47
+ log.append({
48
+ type: 'ai.generation.completed',
49
+ payload: { ...generation, finishReason: 'tool-calls' },
50
+ })
51
+ log.append({
52
+ id: 'continuation',
53
+ type: 'ai.generation.requested',
54
+ payload: {
55
+ messageId: 'response',
56
+ responseMessageId: 'response',
57
+ reason: 'tool',
58
+ sourceGenerationId: 'generation',
59
+ },
60
+ })
61
+ }
62
+ log.append({
63
+ type: 'ai.generation.failed',
64
+ payload: {
65
+ ...generation,
66
+ error: 'failed',
67
+ ...(stepLimit ? { stepLimit: true } : {}),
68
+ },
69
+ })
70
+ expect(log.snapshot().status).toBe('failed')
71
+ expect(log.snapshot().activeResponseMessageId).toBeNull()
72
+ expect(agentRetry(log.snapshot())).toEqual({
73
+ messageId: 'input',
74
+ responseMessageId: 'response',
75
+ })
76
+ expect(
77
+ agentStreamStatus({
78
+ state: log.snapshot(),
79
+ responseMessageId: 'response',
80
+ streaming: false,
81
+ }),
82
+ ).toBe('failed')
83
+
84
+ log.append({
85
+ type: 'ai.control.committed',
86
+ payload: {
87
+ changes: [],
88
+ view: {
89
+ turnId: 'turn',
90
+ version: 1,
91
+ phase: 'failed',
92
+ paused: false,
93
+ closed: false,
94
+ requestId: null,
95
+ requestReason: null,
96
+ sourceGenerationId: null,
97
+ responseMessageId: 'response',
98
+ },
99
+ },
100
+ })
101
+ expect(log.snapshot().activeResponseMessageId).toBe('response')
102
+ expect(agentRetry(log.snapshot())).toEqual({
103
+ messageId: 'input',
104
+ responseMessageId: 'response',
105
+ })
106
+ },
107
+ )
108
+
109
+ it('uses the current response owner after retry rather than an older failure', () => {
110
+ const log = running()
111
+ log.append({
112
+ type: 'ai.generation.failed',
113
+ payload: { ...generation, error: 'failed' },
114
+ })
115
+ const retryGeneration = {
116
+ ...generation,
117
+ requestId: 'retry-request',
118
+ generationId: 'retry-generation',
119
+ }
120
+ log.append({
121
+ id: retryGeneration.requestId,
122
+ type: 'ai.generation.requested',
123
+ payload: { ...retryGeneration, reason: 'retry' },
124
+ })
125
+ log.append({ type: 'ai.generation.started', payload: retryGeneration })
126
+ expect(agentRetry(log.snapshot())).toBeNull()
127
+ expect(
128
+ agentStreamStatus({
129
+ state: log.snapshot(),
130
+ responseMessageId: 'response',
131
+ streaming: true,
132
+ }),
133
+ ).toBe('streaming')
134
+ log.append({ type: 'ai.generation.completed', payload: retryGeneration })
135
+ log.append({
136
+ type: 'ai.message.completed',
137
+ payload: { messageId: 'response' },
138
+ })
139
+ log.append({
140
+ type: 'ai.generation.failed',
141
+ payload: { ...generation, error: 'stale failure', superseded: true },
142
+ })
143
+ expect(
144
+ agentStreamStatus({
145
+ state: log.snapshot(),
146
+ responseMessageId: 'response',
147
+ streaming: false,
148
+ }),
149
+ ).toBe('completed')
150
+ })
151
+
152
+ it('shows interruption from the snapshot and does not invent missing retry identifiers', () => {
153
+ const log = running()
154
+ log.append({
155
+ type: 'ai.message.interrupted',
156
+ payload: { ...generation, messageId: 'response' },
157
+ })
158
+ expect(
159
+ agentStreamStatus({
160
+ state: log.snapshot(),
161
+ responseMessageId: 'response',
162
+ streaming: false,
163
+ }),
164
+ ).toBe('interrupted')
165
+ expect(
166
+ agentRetry({
167
+ ...log.snapshot(),
168
+ status: 'failed',
169
+ activeProjection: null,
170
+ activeResponseMessageId: null,
171
+ }),
172
+ ).toBeNull()
173
+ expect(
174
+ agentRetry({ ...log.snapshot(), status: 'failed', active: null }),
175
+ ).toBeNull()
176
+ })
@@ -2,6 +2,7 @@
2
2
 
3
3
  import type { ReactNode } from 'react'
4
4
  import { useEffect, useRef, useState } from 'react'
5
+ import { isCompactionPart, type TranscriptMessage } from 'experimental-a2/ai'
5
6
  import type { ChatAgentMessage, ChatAgentStep } from '../model'
6
7
  import type { AgentStreamSelection } from './chat-view'
7
8
 
@@ -36,9 +37,23 @@ function StreamPart({
36
37
  part,
37
38
  position,
38
39
  }: {
39
- part: ChatAgentMessage['parts'][number]
40
+ part: TranscriptMessage<ChatAgentMessage>['parts'][number]
40
41
  position: number
41
42
  }): ReactNode {
43
+ if (isCompactionPart(part)) {
44
+ return (
45
+ <div className="agent-stream-step" role="separator">
46
+ <span>
47
+ {part.data.status === 'completed'
48
+ ? 'Context compacted'
49
+ : part.data.status === 'running'
50
+ ? 'Compacting…'
51
+ : 'Compaction did not complete'}
52
+ </span>
53
+ </div>
54
+ )
55
+ }
56
+
42
57
  if (part.type === 'step-start') {
43
58
  return (
44
59
  <div className="agent-stream-step" role="separator">