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
@@ -0,0 +1,54 @@
1
+ import type { ReactElement } from 'react'
2
+ import { beforeEach, expect, it, vi } from 'vitest'
3
+ import AgentPage from './agent/[agentId]/page'
4
+ import ChatPage from './chat/[chatId]/page'
5
+
6
+ const agentReads = vi.hoisted(() => ({
7
+ initial: { state: { agent: true }, index: 7 },
8
+ state: vi.fn<() => Promise<{ state: { agent: boolean }; index: number }>>(),
9
+ history: vi.fn<() => never>(),
10
+ }))
11
+ const reads = vi.hoisted(() => ({
12
+ initial: { state: { marker: true }, index: 42 },
13
+ state: vi.fn<() => Promise<{ state: { marker: boolean }; index: number }>>(),
14
+ history: vi.fn<() => never>(),
15
+ }))
16
+ vi.mock('./agent/server', () => ({ agentServer: { session: () => reads } }))
17
+ vi.mock('./chat/server', () => ({
18
+ chatServer: { session: () => reads },
19
+ chatAgentServer: { session: () => agentReads },
20
+ }))
21
+ vi.mock('./agent/[agentId]/agent-client', () => ({ AgentClient: () => null }))
22
+ vi.mock('./chat/[chatId]/chat-client', () => ({ ChatClient: () => null }))
23
+
24
+ beforeEach(() => {
25
+ reads.state.mockReset().mockResolvedValue(reads.initial)
26
+ agentReads.state.mockReset().mockResolvedValue(agentReads.initial)
27
+ reads.history.mockReset().mockImplementation(() => {
28
+ throw new Error('unexpected history read')
29
+ })
30
+ })
31
+
32
+ it.each(['agent', 'chat'] as const)(
33
+ 'seeds %s from one state read without fetching raw history',
34
+ async (page) => {
35
+ const element = (await (page === 'agent'
36
+ ? AgentPage({ params: Promise.resolve({ agentId: 'one' }) })
37
+ : ChatPage({
38
+ params: Promise.resolve({ chatId: 'one' }),
39
+ }))) as ReactElement<{ initial: unknown; initialEvents?: unknown }>
40
+ expect(reads.state).toHaveBeenCalledExactlyOnceWith()
41
+ expect(reads.history).not.toHaveBeenCalled()
42
+ expect(element.props.initial).toBe(reads.initial)
43
+ expect(element.props.initialEvents).toBeUndefined()
44
+ },
45
+ )
46
+
47
+ it("seeds the chat's agent session from its own state read", async () => {
48
+ const element = (await ChatPage({
49
+ params: Promise.resolve({ chatId: 'one' }),
50
+ })) as ReactElement<{ children: ReactElement<{ agentInitial: unknown }> }>
51
+ expect(agentReads.state).toHaveBeenCalledExactlyOnceWith()
52
+ expect(agentReads.history).not.toHaveBeenCalled()
53
+ expect(element.props.children.props.agentInitial).toBe(agentReads.initial)
54
+ })
@@ -1,6 +1,7 @@
1
- import type { Metadata } from 'next'
1
+ import type { Metadata, Viewport } from 'next'
2
2
  import type { ReactNode } from 'react'
3
- import Link from 'next/link'
3
+ import { GeistMono } from 'geist/font/mono'
4
+ import { SiteHeader } from './components/site-header'
4
5
  import './globals.css'
5
6
 
6
7
  export const metadata: Metadata = {
@@ -9,35 +10,18 @@ export const metadata: Metadata = {
9
10
  'A durable event log for serverless apps — counters, agents, orders, streaming, cancellation, multiplayer documents, and a shared canvas.',
10
11
  }
11
12
 
13
+ export const viewport: Viewport = { colorScheme: 'light dark' }
14
+
12
15
  export default function RootLayout({
13
16
  children,
14
17
  }: {
15
18
  children: ReactNode
16
19
  }): ReactNode {
17
20
  return (
18
- <html lang="en">
21
+ <html lang="en" className={GeistMono.variable}>
19
22
  <body>
20
- <header className="site-header">
21
- <nav>
22
- <Link href="/" className="brand">
23
- a2 <span>example</span>
24
- </Link>
25
- <Link href="/orders">Orders</Link>
26
- <Link href="/chat">Chat</Link>
27
- <Link href="/agent">Agent</Link>
28
- <Link href="/canvas">Canvas</Link>
29
- <Link href="/documents">Documents</Link>
30
- <Link href="/recovery">Recovery</Link>
31
- <Link href="/counter">Counter</Link>
32
- </nav>
33
- </header>
23
+ <SiteHeader />
34
24
  <main>{children}</main>
35
- <footer className="site-footer">
36
- <p>
37
- Everything on these pages is a fold over an append-only log — check{' '}
38
- <code>.a2/dev.db</code>.
39
- </p>
40
- </footer>
41
25
  </body>
42
26
  </html>
43
27
  )
@@ -20,8 +20,7 @@ export default async function OrderPage({
20
20
  <SessionProvider
21
21
  sessionId={decodeURIComponent(orderId)}
22
22
  api="/orders/events"
23
- initialState={state}
24
- initialIndex={index}
23
+ initial={{ state: state, index: index }}
25
24
  >
26
25
  <OrderClient sessionId={decodeURIComponent(orderId)} />
27
26
  </SessionProvider>
@@ -4,8 +4,8 @@
4
4
  * pair. Nothing in this file touches the machine module.
5
5
  */
6
6
  import { createReact } from 'experimental-a2/react'
7
- import { ordersReducer } from '../model'
7
+ import { orders } from '../model'
8
8
 
9
9
  export const { SessionProvider, useSession } = createReact({
10
- reducer: ordersReducer,
10
+ contract: orders,
11
11
  })
@@ -1,21 +1,6 @@
1
1
  import * as a2 from 'experimental-a2'
2
2
  import { z } from 'zod'
3
3
 
4
- export const orders = a2.contract({
5
- name: 'orders',
6
- events: {
7
- created: z.object({
8
- shopId: z.string(),
9
- items: z.array(z.string()).min(1),
10
- }),
11
- 'shop.notified': z.object({}),
12
- 'shop.started': z.object({}),
13
- 'driver.notified': z.object({ driverId: z.string() }),
14
- 'courier.picked_up': z.object({ etaMinutes: z.number().int().positive() }),
15
- delivered: z.object({}),
16
- },
17
- })
18
-
19
4
  const orderState = z.object({
20
5
  status: z.enum([
21
6
  'new',
@@ -34,8 +19,21 @@ const orderState = z.object({
34
19
 
35
20
  export type OrderState = z.output<typeof orderState>
36
21
 
37
- export const ordersReducer = orders
38
- .reducer({
22
+ export const orders = a2.contract({
23
+ name: 'orders',
24
+ events: {
25
+ created: z.object({
26
+ shopId: z.string(),
27
+ items: z.array(z.string()).min(1),
28
+ }),
29
+ 'shop.notified': z.object({}),
30
+ 'shop.started': z.object({}),
31
+ 'driver.notified': z.object({ driverId: z.string() }),
32
+ 'courier.picked_up': z.object({ etaMinutes: z.number().int().positive() }),
33
+ delivered: z.object({}),
34
+ },
35
+
36
+ reducer: {
39
37
  name: 'order-status',
40
38
  initialState: {
41
39
  status: 'new',
@@ -45,35 +43,37 @@ export const ordersReducer = orders
45
43
  etaMinutes: null,
46
44
  },
47
45
  stateSchema: orderState,
48
- })
49
- .fold((state, event) => {
50
- switch (event.type) {
51
- case 'created':
52
- return {
53
- ...state,
54
- status: 'created',
55
- shopId: event.payload.shopId,
56
- items: event.payload.items,
57
- }
58
- case 'shop.notified':
59
- return { ...state, status: 'notified' }
60
- case 'shop.started':
61
- return { ...state, status: 'preparing' }
62
- case 'driver.notified':
63
- return {
64
- ...state,
65
- status: 'courier-assigned',
66
- driverId: event.payload.driverId,
67
- }
68
- case 'courier.picked_up':
69
- return {
70
- ...state,
71
- status: 'delivering',
72
- etaMinutes: event.payload.etaMinutes,
73
- }
74
- case 'delivered':
75
- return { ...state, status: 'delivered', etaMinutes: null }
76
- default:
77
- return state
78
- }
79
- })
46
+ fold: (state, event): OrderState => {
47
+ switch (event.type) {
48
+ case 'created':
49
+ return {
50
+ ...state,
51
+ status: 'created',
52
+ shopId: event.payload.shopId,
53
+ items: event.payload.items,
54
+ }
55
+ case 'shop.notified':
56
+ return { ...state, status: 'notified' }
57
+ case 'shop.started':
58
+ return { ...state, status: 'preparing' }
59
+ case 'driver.notified':
60
+ return {
61
+ ...state,
62
+ status: 'courier-assigned',
63
+ driverId: event.payload.driverId,
64
+ }
65
+ case 'courier.picked_up':
66
+ return {
67
+ ...state,
68
+ status: 'delivering',
69
+ etaMinutes: event.payload.etaMinutes,
70
+ }
71
+ case 'delivered':
72
+ return { ...state, status: 'delivered', etaMinutes: null }
73
+ default:
74
+ return state
75
+ }
76
+ },
77
+ },
78
+ })
79
+ export const ordersReducer = orders.reducer
@@ -1,111 +1,22 @@
1
1
  import type { ReactNode } from 'react'
2
- import Link from 'next/link'
2
+ import { FastLink } from './components/fast-link'
3
+ import { demos } from './components/demos'
3
4
 
4
5
  export default function HomePage(): ReactNode {
5
6
  return (
6
7
  <article>
7
- <h1>A durable event log, live in your browser</h1>
8
- <p className="lede">
9
- Eight small demos, one model: append an event, let a handler react, fold
10
- the log into a view — on the server for the first paint, in the browser
11
- for every moment after.
12
- </p>
13
-
14
- <div className="card-grid">
15
- <Link className="card" href="/orders">
16
- <h2>Order tracker</h2>
17
- <p>
18
- A form appends <code>created</code>, and handlers chain the rest —
19
- shop notified, courier assigned, picked up, delivered — while the
20
- page watches over a live stream. One optimistic click, then the
21
- order runs itself to the end.
22
- </p>
23
- </Link>
24
- <Link className="card" href="/chat">
25
- <h2>Multiplayer chat + agent</h2>
26
- <p>
27
- Messages, “is typing…” presence, and an AI participant share one
28
- stream. Mention <code>@agent</code>, or enable ambient mode for the
29
- room. The model can stay silent or call a durable <code>post</code>{' '}
30
- tool; its private generation appears only as “thinking” or
31
- “writing.”
32
- </p>
33
- </Link>
34
- <Link className="card" href="/agent">
35
- <h2>Agent</h2>
36
- <p>
37
- A durable AI agent inspects a service, records tool calls, pauses
38
- for approval, runs Bash in a persistent Vercel Sandbox, and sets
39
- reminders that return as new user messages. Messages, progress,
40
- tools, timers, and the human decision are all ordinary A2 events.
41
- </p>
42
- </Link>
43
- <Link className="card" href="/canvas">
44
- <h2>Multiplayer canvas</h2>
45
- <p>
46
- Shapes are durable events you can scrub back through after the
47
- server snapshot. Cursors and drag ghosts are presence: replicated
48
- live, stored never.
49
- </p>
50
- </Link>
51
- <Link className="card" href="/documents">
52
- <h2>Multiplayer document</h2>
53
- <p>
54
- Two CodeMirror editors share one Yjs document. A2 durably orders the
55
- CRDT updates while Yjs Awareness — live cursors and selections —
56
- rides the session's presence plane, never touching the log.
57
- </p>
58
- </Link>
59
- <Link className="card" href="/recovery">
60
- <h2>Cross-invocation recovery</h2>
61
- <p>
62
- Chain four 8-second handlers under a 20-second function limit, then
63
- watch Vercel stop one mid-handler and A2 retry that pending event
64
- under a fresh claim.
65
- </p>
66
- </Link>
67
- <Link className="card" href="/vault">
68
- <h2>Vault (actor)</h2>
69
- <p>
70
- A durable actor: send messages to one vault's mailbox, watch it
71
- process them one at a time against its state — serialized
72
- read-modify-write, refusals as answers, and the whole transcript in
73
- the log. Built on <code>experimental-a2/actor</code>.
74
- </p>
75
- </Link>
76
- <Link className="card" href="/counter">
77
- <h2>Counter</h2>
78
- <p>
79
- Increment and decrement one shared value. Every click is a durable
80
- event applied optimistically in the browser, then confirmed by the
81
- stream — open two tabs and watch both folds converge.
82
- </p>
83
- </Link>
84
- </div>
85
-
86
- <section>
87
- <h2>Where things live</h2>
88
- <ul>
89
- <li>
90
- Each demo owns a <code>model.ts</code> with its contract, reducer,
91
- and shared types.
92
- </li>
93
- <li>
94
- Its <code>server.ts</code> implements handlers and configures the
95
- production store and scheduler it actually needs.
96
- </li>
97
- <li>
98
- Its <code>events/route.ts</code> exposes <code>server.fetch</code>{' '}
99
- directly, adding only feature-specific authorization or WebSocket
100
- capabilities.
101
- </li>
102
- <li>
103
- Demos with handlers or timers own a private{' '}
104
- <code>scheduler/route.ts</code> and queue topic. Counter, canvas,
105
- and documents need no scheduler.
8
+ <h1>A durable event log</h1>
9
+ <p className="lede">Every demo here is a fold over an append-only log.</p>
10
+ <ul className="app-grid" aria-label="Demos">
11
+ {demos.map((demo) => (
12
+ <li key={demo.href}>
13
+ <FastLink href={demo.href} className="app-tile">
14
+ <span className="app-icon">{demo.icon}</span>
15
+ <span>{demo.name}</span>
16
+ </FastLink>
106
17
  </li>
107
- </ul>
108
- </section>
18
+ ))}
19
+ </ul>
109
20
  </article>
110
21
  )
111
22
  }
@@ -19,8 +19,7 @@ export default async function RecoveryRunPage({
19
19
  <SessionProvider
20
20
  sessionId={sessionId}
21
21
  api="/recovery/events"
22
- initialState={state}
23
- initialIndex={index}
22
+ initial={{ state: state, index: index }}
24
23
  >
25
24
  <RecoveryClient
26
25
  sessionId={sessionId}
@@ -1,7 +1,7 @@
1
1
  'use client'
2
2
  import { createReact } from 'experimental-a2/react'
3
- import { recoveryReducer } from '../model'
3
+ import { recovery } from '../model'
4
4
 
5
5
  export const { SessionProvider, useSession } = createReact({
6
- reducer: recoveryReducer,
6
+ contract: recovery,
7
7
  })
@@ -1,6 +1,15 @@
1
1
  import * as a2 from 'experimental-a2'
2
2
  import { z } from 'zod'
3
3
 
4
+ const recoveryState = z.object({
5
+ phase: z.enum(['idle', 'running', 'completed']),
6
+ stepCount: z.number().int().positive().nullable(),
7
+ stepDurationSeconds: z.number().int().positive().nullable(),
8
+ completedSteps: z.number().int().nonnegative(),
9
+ })
10
+
11
+ export type RecoveryState = z.output<typeof recoveryState>
12
+
4
13
  export const recovery = a2.contract({
5
14
  name: 'recovery',
6
15
  events: {
@@ -14,19 +23,8 @@ export const recovery = a2.contract({
14
23
  }),
15
24
  'pipeline.completed': z.object({ stepCount: z.literal(4) }),
16
25
  },
17
- })
18
-
19
- const recoveryState = z.object({
20
- phase: z.enum(['idle', 'running', 'completed']),
21
- stepCount: z.number().int().positive().nullable(),
22
- stepDurationSeconds: z.number().int().positive().nullable(),
23
- completedSteps: z.number().int().nonnegative(),
24
- })
25
26
 
26
- export type RecoveryState = z.output<typeof recoveryState>
27
-
28
- export const recoveryReducer = recovery
29
- .reducer({
27
+ reducer: {
30
28
  name: 'recovery',
31
29
  initialState: {
32
30
  phase: 'idle',
@@ -35,21 +33,23 @@ export const recoveryReducer = recovery
35
33
  completedSteps: 0,
36
34
  },
37
35
  stateSchema: recoveryState,
38
- })
39
- .fold((state, event) => {
40
- switch (event.type) {
41
- case 'pipeline.started':
42
- return {
43
- ...state,
44
- phase: 'running',
45
- stepCount: event.payload.stepCount,
46
- stepDurationSeconds: event.payload.stepDurationSeconds,
47
- }
48
- case 'step.completed':
49
- return { ...state, completedSteps: event.payload.step }
50
- case 'pipeline.completed':
51
- return { ...state, phase: 'completed' }
52
- default:
53
- return state
54
- }
55
- })
36
+ fold: (state, event): RecoveryState => {
37
+ switch (event.type) {
38
+ case 'pipeline.started':
39
+ return {
40
+ ...state,
41
+ phase: 'running',
42
+ stepCount: event.payload.stepCount,
43
+ stepDurationSeconds: event.payload.stepDurationSeconds,
44
+ }
45
+ case 'step.completed':
46
+ return { ...state, completedSteps: event.payload.step }
47
+ case 'pipeline.completed':
48
+ return { ...state, phase: 'completed' }
49
+ default:
50
+ return state
51
+ }
52
+ },
53
+ },
54
+ })
55
+ export const recoveryReducer = recovery.reducer
@@ -0,0 +1,5 @@
1
+ import { registerOTel } from '@vercel/otel'
2
+
3
+ export function register(): void {
4
+ registerOTel({ serviceName: 'a2-playground' })
5
+ }
@@ -12,18 +12,24 @@
12
12
  "dev": "next dev",
13
13
  "build": "next build",
14
14
  "test": "vitest run",
15
- "typecheck": "tsc --noEmit"
15
+ "typecheck": "tsc --noEmit",
16
+ "a0:template": "node --env-file-if-exists=.env.local scripts/a0-template.ts"
16
17
  },
17
18
  "dependencies": {
19
+ "@ai-sdk/otel": "1.0.58",
18
20
  "@codemirror/state": "^6.7.1",
19
21
  "@codemirror/view": "^6.43.8",
22
+ "@opentelemetry/api": "^1.9.0",
20
23
  "@vercel/functions": "^3.9.3",
24
+ "@vercel/otel": "^2.1.0",
21
25
  "@vercel/queue": "^0.2.0",
22
26
  "@vercel/sandbox": "^3.0.0",
23
27
  "ai": "^7.0.58",
24
28
  "codemirror": "^6.0.2",
25
- "experimental-a2": "0.16.0",
29
+ "experimental-a2": "0.17.0",
30
+ "geist": "^1.7.2",
26
31
  "ioredis": "^5.9.0",
32
+ "lucide-react": "^1.34.0",
27
33
  "next": "^16.3.0",
28
34
  "pg": "^8.16.0",
29
35
  "react": "^19.0.0",
@@ -36,6 +42,8 @@
36
42
  "zod": "^4.4.3"
37
43
  },
38
44
  "devDependencies": {
45
+ "@opentelemetry/context-async-hooks": "^2.0.0",
46
+ "@opentelemetry/sdk-trace-base": "^2.0.0",
39
47
  "@testing-library/react": "^16.1.0",
40
48
  "jsdom": "^26.0.0",
41
49
  "@types/node": "^24.0.0",
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Builds A0's template: a sandbox with a blank Next.js app, its
3
+ * dependencies installed and its dev cache warm, snapshotted with no
4
+ * expiry. Prints the snapshot id to set as A0_TEMPLATE_SNAPSHOT_ID.
5
+ *
6
+ * pnpm --filter playground a0:template
7
+ *
8
+ * Run it again when the recipe changes, then update the variable.
9
+ */
10
+ import { Sandbox } from '@vercel/sandbox'
11
+
12
+ const appDir = '/vercel/app'
13
+ const appPort = 3000
14
+
15
+ const RECIPE = `set -e
16
+ cd /vercel
17
+ npx --yes create-next-app@latest app --yes --use-npm --disable-git
18
+ cd ${appDir}
19
+ rm -f public/*.svg
20
+ cat > next.config.ts <<'EOF'
21
+ import type { NextConfig } from 'next'
22
+
23
+ // The preview is served from the sandbox's public *.vercel.run host.
24
+ const nextConfig: NextConfig = { allowedDevOrigins: ['*.vercel.run'] }
25
+
26
+ export default nextConfig
27
+ EOF
28
+ cat > app/globals.css <<'EOF'
29
+ @import 'tailwindcss';
30
+ EOF
31
+ cat > app/layout.tsx <<'EOF'
32
+ import type { Metadata } from 'next'
33
+ import './globals.css'
34
+
35
+ export const metadata: Metadata = { title: 'App' }
36
+
37
+ export default function RootLayout({
38
+ children,
39
+ }: Readonly<{ children: React.ReactNode }>) {
40
+ return (
41
+ <html lang="en">
42
+ <body>{children}</body>
43
+ </html>
44
+ )
45
+ }
46
+ EOF
47
+ cat > app/page.tsx <<'EOF'
48
+ export default function Home() {
49
+ return <main />
50
+ }
51
+ EOF
52
+ # Warms the dev cache: next dev compiles the page once, then stops with the
53
+ # snapshot. setsid -f keeps the command from waiting on the server.
54
+ setsid -f npm run dev -- --port ${appPort} > /tmp/next.log 2>&1 < /dev/null
55
+ timeout 120 bash -c 'until curl -sf localhost:${appPort} > /dev/null; do sleep 1; done'`
56
+
57
+ // The snapshot outlives its builder, so nothing but the snapshot stays.
58
+ const builder = await Sandbox.create({
59
+ persistent: false,
60
+ timeout: 10 * 60 * 1000,
61
+ })
62
+ try {
63
+ const result = await builder.runCommand({
64
+ cmd: 'bash',
65
+ args: ['-lc', RECIPE],
66
+ })
67
+ if (result.exitCode !== 0) {
68
+ const output = await result.output('both')
69
+ throw new Error(
70
+ `The template didn't build (exit ${result.exitCode}):\n${output.slice(-2000)}`,
71
+ )
72
+ }
73
+ const { snapshotId } = await builder.snapshot({ expiration: 0 })
74
+ process.stdout.write(`A0_TEMPLATE_SNAPSHOT_ID=${snapshotId}\n`)
75
+ } finally {
76
+ await builder.delete().catch(() => {})
77
+ }
@@ -35,6 +35,24 @@
35
35
  "retryAfterSeconds": 30
36
36
  }
37
37
  ]
38
+ },
39
+ "app/a0/scheduler/route.ts": {
40
+ "experimentalTriggers": [
41
+ {
42
+ "type": "queue/v2beta",
43
+ "topic": "a2-a0",
44
+ "retryAfterSeconds": 30
45
+ }
46
+ ]
47
+ },
48
+ "app/a0/sandboxes/scheduler/route.ts": {
49
+ "experimentalTriggers": [
50
+ {
51
+ "type": "queue/v2beta",
52
+ "topic": "a2-a0-sandboxes",
53
+ "retryAfterSeconds": 30
54
+ }
55
+ ]
38
56
  }
39
57
  }
40
58
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "experimental-a2",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "Durable sync and reactions for things with a lifecycle: one event log, derived state, and live client per session.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -39,6 +39,8 @@
39
39
  "browser": "./dist/ai-server.browser.js",
40
40
  "default": "./dist/ai-server.js"
41
41
  },
42
+ "./ai/client": "./dist/ai-client.js",
43
+ "./ai/react": "./dist/ai-react.js",
42
44
  "./actor": {
43
45
  "browser": "./dist/actor.browser.js",
44
46
  "default": "./dist/actor.js"