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
@@ -1,5 +1,6 @@
1
+ import { contract } from 'experimental-a2'
1
2
  import type { FinishReason, LanguageModelUsage, UIMessage } from 'ai'
2
- import { agent, type AIState, type AIStoredState } from 'experimental-a2/ai'
3
+ import { agentContract } from 'experimental-a2/ai'
3
4
  import { z } from 'zod'
4
5
 
5
6
  export type ChatAgentStep = {
@@ -147,28 +148,12 @@ const chatMessageSchema = z.custom<ChatAgentMessage>(
147
148
  'invalid chat UI message',
148
149
  )
149
150
 
150
- const chatEvents = {
151
- 'message.sent': z.object({
152
- author: z.string().min(1).max(40),
153
- text: z.string().min(1).max(2000),
154
- }),
155
- typed: z.object({ author: z.string().min(1).max(40) }),
156
- 'agent.ambient.changed': z.object({
157
- enabled: z.boolean(),
158
- author: z.string().min(1).max(40),
159
- }),
160
- 'agent.message.posted': agentPostEventSchema,
161
- }
162
-
163
- export const chatAgent = agent<ChatAgentMessage, typeof chatEvents>({
164
- name: 'chat',
165
- events: chatEvents,
151
+ /** The agent's own session: the conversation it sees, one per room. */
152
+ export const chatAgent = agentContract<ChatAgentMessage>({
153
+ name: 'chat-agent',
166
154
  messageSchema: chatMessageSchema,
167
- reducerName: 'a2.playground.chat.ai.v3',
168
155
  })
169
156
 
170
- export const chat = chatAgent.contract
171
-
172
157
  export type ChatMessage = {
173
158
  id: string
174
159
  index: number
@@ -187,7 +172,6 @@ export type AgentPost = {
187
172
  }
188
173
 
189
174
  export type ChatState = {
190
- ai: AIStoredState
191
175
  messages: ChatMessage[]
192
176
  posts: AgentPost[]
193
177
  lastTypingAt: Record<string, string>
@@ -199,124 +183,94 @@ export type ChatState = {
199
183
  }
200
184
 
201
185
  const initialChatState: ChatState = {
202
- ai: chatAgent.reducer.initialState,
203
186
  messages: [],
204
187
  posts: [],
205
188
  lastTypingAt: {},
206
189
  ambient: { enabled: false, changedBy: null, changedAt: null },
207
190
  }
208
191
 
209
- export const chatReducer = chat
210
- .reducer({
211
- name: 'a2.playground.chat.v4',
192
+ /**
193
+ * The room: an ordinary A2 session. The agent runs in its own session, and its
194
+ * `post` tool appends here, so the room's log orders its messages among
195
+ * everyone else's.
196
+ */
197
+ export const chat = contract({
198
+ name: 'chat-room',
199
+ events: {
200
+ 'message.sent': z.object({
201
+ author: z.string().min(1).max(40),
202
+ text: z.string().min(1).max(2000),
203
+ }),
204
+ typed: z.object({ author: z.string().min(1).max(40) }),
205
+ 'agent.ambient.changed': z.object({
206
+ enabled: z.boolean(),
207
+ author: z.string().min(1).max(40),
208
+ }),
209
+ 'agent.message.posted': agentPostEventSchema,
210
+ },
211
+ reducer: {
212
+ name: 'a2.playground.chat-room.v1',
212
213
  initialState: initialChatState,
213
- })
214
- .fold((state, event) => {
215
- const previousAI = chatAgent.reducer.view(state.ai)
216
- if (
217
- event.type === 'ai.tool.called' &&
218
- event.payload.toolName === 'post' &&
219
- (previousAI.activeGeneration?.generationId !==
220
- event.payload.generationId ||
221
- previousAI.activeGeneration.responseMessageId !==
222
- event.payload.messageId)
223
- ) {
224
- return state
225
- }
226
-
227
- const ai = chatAgent.reducer.fold(state.ai, event)
228
- const base = ai === state.ai ? state : { ...state, ai }
229
-
230
- switch (event.type) {
231
- case 'message.sent': {
232
- const stillTyping = { ...base.lastTypingAt }
233
- delete stillTyping[event.payload.author]
234
- return {
235
- ...base,
236
- messages: [
237
- ...base.messages,
238
- {
239
- id: event.id,
240
- index: event.index,
241
- author: event.payload.author,
242
- text: event.payload.text,
243
- at: event.createdAt.toISOString(),
244
- },
245
- ],
246
- lastTypingAt: stillTyping,
214
+ fold: (state, event) => {
215
+ switch (event.type) {
216
+ case 'message.sent': {
217
+ const stillTyping = { ...state.lastTypingAt }
218
+ delete stillTyping[event.payload.author]
219
+ return {
220
+ ...state,
221
+ messages: [
222
+ ...state.messages,
223
+ {
224
+ id: event.id,
225
+ index: event.index,
226
+ author: event.payload.author,
227
+ text: event.payload.text,
228
+ at: event.createdAt.toISOString(),
229
+ },
230
+ ],
231
+ lastTypingAt: stillTyping,
232
+ }
247
233
  }
248
- }
249
- case 'typed':
250
- return {
251
- ...base,
252
- lastTypingAt: {
253
- ...base.lastTypingAt,
254
- [event.payload.author]: event.createdAt.toISOString(),
255
- },
256
- }
257
- case 'agent.ambient.changed':
258
- return {
259
- ...base,
260
- ambient: {
261
- enabled: event.payload.enabled,
262
- changedBy: event.payload.author,
263
- changedAt: event.createdAt.toISOString(),
264
- },
265
- }
266
- case 'agent.message.posted': {
267
- const payload = event.payload
268
- const running = previousAI.tools.some(
269
- (activity) =>
270
- activity.generationId === payload.generationId &&
271
- activity.messageId === payload.responseMessageId &&
272
- activity.toolCallId === payload.toolCallId &&
273
- activity.toolName === 'post' &&
274
- activity.status === 'running',
275
- )
276
- if (!running) return base
277
- return {
278
- ...base,
279
- posts: [
280
- ...base.posts,
281
- {
282
- generationId: payload.generationId,
283
- responseMessageId: payload.responseMessageId,
284
- toolCallId: payload.toolCallId,
285
- index: event.index,
286
- text: payload.message,
287
- at: event.createdAt.toISOString(),
234
+ case 'typed':
235
+ return {
236
+ ...state,
237
+ lastTypingAt: {
238
+ ...state.lastTypingAt,
239
+ [event.payload.author]: event.createdAt.toISOString(),
288
240
  },
289
- ],
290
- }
291
- }
292
- case 'ai.message.interrupted': {
293
- const cutoff = event.payload.lastSeenIndex
294
- if (ai === state.ai || cutoff === undefined) return base
295
- return {
296
- ...base,
297
- posts: base.posts.filter(
298
- (post) =>
299
- post.responseMessageId !== event.payload.messageId ||
300
- (event.payload.generationId !== undefined &&
301
- post.generationId !== event.payload.generationId) ||
302
- post.index <= cutoff,
303
- ),
304
- }
241
+ }
242
+ case 'agent.ambient.changed':
243
+ return {
244
+ ...state,
245
+ ambient: {
246
+ enabled: event.payload.enabled,
247
+ changedBy: event.payload.author,
248
+ changedAt: event.createdAt.toISOString(),
249
+ },
250
+ }
251
+ case 'agent.message.posted':
252
+ return {
253
+ ...state,
254
+ posts: [
255
+ ...state.posts,
256
+ {
257
+ generationId: event.payload.generationId,
258
+ responseMessageId: event.payload.responseMessageId,
259
+ toolCallId: event.payload.toolCallId,
260
+ index: event.index,
261
+ text: event.payload.message,
262
+ at: event.createdAt.toISOString(),
263
+ },
264
+ ],
265
+ }
266
+ default:
267
+ return state
305
268
  }
306
- default:
307
- return base
308
- }
309
- })
269
+ },
270
+ },
271
+ })
272
+ export const chatReducer = chat.reducer
310
273
 
311
274
  const agentMention = /(?:^|\s)@agent(?![\w-])/i
312
275
 
313
276
  export const mentionsAgent = (text: string): boolean => agentMention.test(text)
314
-
315
- export type ChatViewState = Omit<ChatState, 'ai'> & {
316
- ai: AIState<ChatAgentMessage>
317
- }
318
-
319
- export const viewChatState = (state: ChatState): ChatViewState => ({
320
- ...state,
321
- ai: chatAgent.reducer.view(state.ai),
322
- })
@@ -1,5 +1,5 @@
1
- import { chatScheduler, chatServer } from '../server'
1
+ import { chatAgentServer, chatScheduler, chatServer } from '../server'
2
2
 
3
3
  export const POST =
4
- chatScheduler?.handler(chatServer) ??
4
+ chatScheduler?.handler(chatServer, chatAgentServer) ??
5
5
  (() => new Response('chat scheduler is not configured', { status: 503 }))
@@ -0,0 +1,112 @@
1
+ import type { UIMessageChunk } from 'ai'
2
+ import { expect, it, vi } from 'vitest'
3
+
4
+ vi.mock('server-only', () => ({}))
5
+ vi.mock('@/lib/store', async () => {
6
+ const { memory } = await import('experimental-a2/store-memory')
7
+ return { store: memory() }
8
+ })
9
+
10
+ // The chat's real agent, with a scripted model: two `post` calls, then done.
11
+ const { prompts, step } = vi.hoisted(() => ({
12
+ prompts: [] as string[],
13
+ step: (chunks: UIMessageChunk[]) =>
14
+ new ReadableStream<UIMessageChunk>({
15
+ start(controller) {
16
+ for (const chunk of chunks) controller.enqueue(chunk)
17
+ controller.close()
18
+ },
19
+ }),
20
+ }))
21
+ vi.mock('experimental-a2/ai/server', async (importActual) => {
22
+ const actual =
23
+ await importActual<typeof import('experimental-a2/ai/server')>()
24
+ return {
25
+ ...actual,
26
+ agent: (options: Parameters<typeof actual.agent>[0]) =>
27
+ actual.agent({
28
+ ...options,
29
+ harness: {
30
+ ...(options.harness?.type === 'ai-sdk' ? options.harness : {}),
31
+ type: 'ai-sdk',
32
+ generate: ({
33
+ event,
34
+ messages,
35
+ }: {
36
+ event: { payload: { reason: string } }
37
+ messages: { role: string; id: string }[]
38
+ }) => {
39
+ prompts.push(
40
+ messages.map((message) => `${message.role}:${message.id}`).join(),
41
+ )
42
+ return step(
43
+ event.payload.reason === 'message'
44
+ ? [
45
+ { type: 'start' },
46
+ ...['Hello', 'there'].map(
47
+ (message, call): UIMessageChunk => ({
48
+ type: 'tool-input-available',
49
+ toolCallId: `call-${call}`,
50
+ toolName: 'post',
51
+ input: { message },
52
+ }),
53
+ ),
54
+ { type: 'finish', finishReason: 'tool-calls' },
55
+ ]
56
+ : [{ type: 'start' }, { type: 'finish', finishReason: 'stop' }],
57
+ )
58
+ },
59
+ },
60
+ } as never),
61
+ }
62
+ })
63
+
64
+ it('forwards room messages to the agent, whose posts land in the room', async () => {
65
+ const { chatAgentServer, chatServer } = await import('./server')
66
+ const room = chatServer.session('room')
67
+ const agent = chatAgentServer.session('room')
68
+ await room.append({
69
+ type: 'message.sent',
70
+ payload: { author: 'alice', text: 'just chatting' },
71
+ })
72
+ await room.append({
73
+ type: 'message.sent',
74
+ payload: { author: 'bob', text: '@agent say hi' },
75
+ })
76
+
77
+ // Without a scheduler, appends run in background drains.
78
+ await vi.waitFor(
79
+ async () => {
80
+ expect((await room.state()).state.posts).toHaveLength(2)
81
+ expect((await agent.state()).state.status).toBe('idle')
82
+ },
83
+ { timeout: 5_000 },
84
+ )
85
+
86
+ const { state } = await room.state()
87
+ expect(state.messages.map((message) => message.author)).toEqual([
88
+ 'alice',
89
+ 'bob',
90
+ ])
91
+ expect(state.posts.map((post) => post.text)).toEqual(['Hello', 'there'])
92
+ expect(state.posts[0]!.index).toBeGreaterThan(state.messages[1]!.index)
93
+ // The passive message reached the agent as context without a reply of its
94
+ // own; only the mention generated, and it saw both.
95
+ expect(prompts).toHaveLength(2)
96
+ expect(
97
+ prompts[0]!.split(',').filter((id) => id.startsWith('user:')),
98
+ ).toHaveLength(2)
99
+ const messages = (await agent.state()).state.messages
100
+ expect(messages.map((message) => message.role)).toEqual([
101
+ 'user',
102
+ 'user',
103
+ 'assistant',
104
+ ])
105
+
106
+ // Replaying the post tool's append converges on the stored event.
107
+ const [post] = (await room.history()).filter(
108
+ (event) => event.type === 'agent.message.posted',
109
+ )
110
+ await room.append({ type: post!.type, id: post!.id, payload: post!.payload })
111
+ expect((await room.state()).state.posts).toHaveLength(2)
112
+ })
@@ -1,11 +1,13 @@
1
1
  import 'server-only'
2
2
  import { tool } from 'ai'
3
+ import { idempotentId } from 'experimental-a2'
3
4
  import { handlerContext, inputs } from 'experimental-a2/ai'
4
- import { createHandlers } from 'experimental-a2/ai/server'
5
+ import { agent } from 'experimental-a2/ai/server'
5
6
  import { vercelQueues } from 'experimental-a2/scheduler-vercel'
6
7
  import { createServer, type A2Operation } from 'experimental-a2/server'
7
8
  import { store } from '@/lib/store'
8
9
  import {
10
+ chat,
9
11
  chatAgent,
10
12
  chatPostInputSchema,
11
13
  mentionsAgent,
@@ -22,12 +24,16 @@ const tools = {
22
24
  execute: async ({ message }, { toolCallId }) => {
23
25
  const context = handlerContext(chatAgent)
24
26
  context.signal.throwIfAborted()
25
- await context.session.append('post', {
27
+ const { generationId, messageId } = context.event.payload.call
28
+ // The agent's session shares the room's id. The explicit id makes a
29
+ // retried tool converge on one post.
30
+ await chatServer.session(context.session.id).append({
26
31
  type: 'agent.message.posted',
32
+ id: await idempotentId('chat.post', generationId, toolCallId),
27
33
  payload: {
28
34
  message,
29
- generationId: context.event.payload.call.generationId,
30
- responseMessageId: context.event.payload.call.messageId,
35
+ generationId,
36
+ responseMessageId: messageId,
31
37
  toolCallId,
32
38
  },
33
39
  })
@@ -36,139 +42,88 @@ const tools = {
36
42
  }),
37
43
  }
38
44
 
39
- const aiHandlers = createHandlers({
40
- agent: chatAgent,
45
+ export const chatScheduler =
46
+ process.env.VERCEL || process.env.A2_QUEUES
47
+ ? vercelQueues({ topic: 'a2-chat' })
48
+ : undefined
49
+
50
+ export const chatAgentServer = agent({
51
+ ...(store ? { store } : {}),
52
+ ...(chatScheduler ? { scheduler: chatScheduler } : {}),
53
+ contract: chatAgent,
41
54
  model: 'openai/gpt-5.4',
42
55
  tools,
56
+ // Posts become chat bubbles, so one agent's posts must land in call order.
57
+ toolLane: 'a2.chat.agent-tools',
43
58
  instructions: `You are "agent", a participant in a multiplayer chat.
44
59
  The latest participant message invited you either by mentioning @agent or by enabling the room's ambient mode. Messages are formatted as "name: message"; earlier passive messages are useful conversation context.
45
60
  Sound like a sharp, relaxed person in the chat, not a customer-support bot. Match the room's language and energy. Keep messages specific and conversational; avoid canned acknowledgements, assistant-y disclaimers, and explanations of your own mechanics.
46
61
  Only contribute when you have something useful, relevant, or friendly to add. It is fine to stay silent or stop whenever you have nothing else to say.
47
62
  To speak, call post once for each visible chat bubble. Never communicate through ordinary assistant text because it is private and is not shown in the room.
48
63
  Use as many post calls as the moment naturally calls for. Several short messages are welcome when they create better rhythm, separate distinct thoughts, or fulfill a request for a sequence. A single message is fine too. Do not spam. After post results, continue only when another message genuinely improves the conversation.`,
49
- generation: {
50
- maxOutputTokens: 500,
51
- temperature: 0.4,
64
+ harness: {
65
+ type: 'ai-sdk',
66
+ generation: {
67
+ maxOutputTokens: 500,
68
+ temperature: 0.4,
69
+ },
70
+ messageMetadata: (context) => {
71
+ const previous = context.messages.find(
72
+ (message) => message.id === context.event.payload.responseMessageId,
73
+ )?.metadata
74
+ const trigger = context.messages.find(
75
+ (message) => message.id === context.event.payload.messageId,
76
+ )?.metadata
77
+ const steps = previous?.kind === 'agent' ? previous.steps : []
78
+ const mode: ChatAgentMode | undefined =
79
+ previous?.kind === 'agent'
80
+ ? previous.mode
81
+ : trigger?.kind === 'participant' && trigger.mode !== 'passive'
82
+ ? trigger.mode
83
+ : undefined
84
+ if (mode === undefined) return undefined
85
+ const metadata = {
86
+ kind: 'agent' as const,
87
+ author: 'agent' as const,
88
+ mode,
89
+ steps,
90
+ }
91
+ if (context.part.type === 'start') return metadata
92
+ if (context.part.type !== 'finish') return undefined
93
+ return {
94
+ ...metadata,
95
+ steps: [
96
+ ...steps,
97
+ {
98
+ generationId: context.generationId,
99
+ model: context.model,
100
+ finishReason: context.part.finishReason,
101
+ ...(context.part.rawFinishReason === undefined
102
+ ? {}
103
+ : { rawFinishReason: context.part.rawFinishReason }),
104
+ usage: context.part.totalUsage,
105
+ completedAt: new Date().toISOString(),
106
+ },
107
+ ],
108
+ }
109
+ },
52
110
  },
53
111
  progress: {
54
112
  maxChunks: 4,
55
113
  maxDelayMs: 30,
56
114
  },
57
- messageMetadata: (context) => {
58
- const previous = context.messages.find(
59
- (message) => message.id === context.responseMessageId,
60
- )?.metadata
61
- const trigger = context.messages.find(
62
- (message) => message.id === context.request.payload.messageId,
63
- )?.metadata
64
- const steps = previous?.kind === 'agent' ? previous.steps : []
65
- const mode: ChatAgentMode | undefined =
66
- previous?.kind === 'agent'
67
- ? previous.mode
68
- : trigger?.kind === 'participant' && trigger.mode !== 'passive'
69
- ? trigger.mode
70
- : undefined
71
- if (mode === undefined) return undefined
72
- const metadata = {
73
- kind: 'agent' as const,
74
- author: 'agent' as const,
75
- mode,
76
- steps,
77
- }
78
- if (context.part.type === 'start') return metadata
79
- if (context.part.type !== 'finish') return undefined
80
- return {
81
- ...metadata,
82
- steps: [
83
- ...steps,
84
- {
85
- generationId: context.generationId,
86
- model:
87
- typeof context.model === 'string'
88
- ? context.model
89
- : context.model.modelId,
90
- finishReason: context.part.finishReason,
91
- ...(context.part.rawFinishReason === undefined
92
- ? {}
93
- : { rawFinishReason: context.part.rawFinishReason }),
94
- usage: context.part.totalUsage,
95
- completedAt: new Date().toISOString(),
96
- },
97
- ],
98
- }
99
- },
100
115
  })
101
116
 
102
- export const authorizeChatOperation = (
103
- operation: A2Operation<(typeof chatAgent.contract)['events']>,
104
- ): boolean => {
105
- if (operation.type !== 'push') return true
106
- const serverOnly = operation.events.find(
107
- (event) =>
108
- event.type === 'ai.message.created' ||
109
- event.type === 'agent.message.posted',
110
- )
111
- if (serverOnly !== undefined) return false
112
- const rejected = operation.events.find((event) => {
113
- if (
114
- event.type === 'message.sent' ||
115
- event.type === 'typed' ||
116
- event.type === 'agent.ambient.changed'
117
- ) {
118
- return false
119
- }
120
- return !(
121
- event.type === 'ai.control.requested' &&
122
- typeof event.payload === 'object' &&
123
- event.payload !== null &&
124
- 'action' in event.payload &&
125
- typeof event.payload.action === 'string' &&
126
- [
127
- 'approval',
128
- 'input',
129
- 'interrupt',
130
- 'stop',
131
- 'retry',
132
- 'pause',
133
- 'resume',
134
- ].includes(event.payload.action)
135
- )
136
- })
137
- return rejected === undefined
138
- }
139
-
140
- const toolExecutionHandler = aiHandlers['ai.tool.execution.requested']
141
- if (toolExecutionHandler === undefined) {
142
- throw new Error('A2 AI handlers must include ai.tool.execution.requested')
143
- }
144
- const serializedToolExecutionHandler =
145
- typeof toolExecutionHandler === 'function'
146
- ? { handler: toolExecutionHandler, lane: 'a2.chat.agent-tools' }
147
- : { ...toolExecutionHandler, lane: 'a2.chat.agent-tools' }
148
-
149
- export const chatScheduler =
150
- process.env.VERCEL || process.env.A2_QUEUES
151
- ? vercelQueues({ topic: 'a2-chat' })
152
- : undefined
153
-
117
+ /** The room. Each message goes on to the agent's session, marked for whether it should answer. */
154
118
  export const chatServer = createServer({
155
119
  ...(store ? { store } : {}),
156
120
  ...(chatScheduler ? { scheduler: chatScheduler } : {}),
157
- contract: chatAgent.contract,
121
+ contract: chat,
158
122
  handlers: {
159
- ...aiHandlers,
160
- 'ai.tool.execution.requested': serializedToolExecutionHandler,
161
123
  'message.sent': {
162
124
  lane: 'a2.chat.ingress',
163
125
  handler: async (context) => {
164
- let ambient = false
165
- for (const event of await context.session.history()) {
166
- if (event.index > context.event.index) break
167
- if (event.type === 'agent.ambient.changed') {
168
- ambient = event.payload.enabled
169
- }
170
- }
171
-
126
+ const ambient = (await context.session.state()).state.ambient.enabled
172
127
  const { author, text } = context.event.payload
173
128
  const mode: ChatParticipantMode = ambient
174
129
  ? 'ambient'
@@ -176,7 +131,9 @@ export const chatServer = createServer({
176
131
  ? 'mention'
177
132
  : 'passive'
178
133
  const message: ChatAgentMessage = {
179
- id: context.event.id,
134
+ // Event ids are unique across a store's sessions, so the forward
135
+ // derives its own; a retried forward converges on the same id.
136
+ id: await idempotentId('chat.message', context.event.id),
180
137
  role: 'user',
181
138
  metadata: {
182
139
  kind: 'participant',
@@ -187,8 +144,47 @@ export const chatServer = createServer({
187
144
  },
188
145
  parts: [{ type: 'text', text: `${author}: ${text}` }],
189
146
  }
190
- return inputs.message(message, { generate: mode !== 'passive' })
147
+ await chatAgentServer
148
+ .session(context.session.id)
149
+ .append(...inputs.message(message, { generate: mode !== 'passive' }))
191
150
  },
192
151
  },
193
152
  },
194
153
  })
154
+
155
+ /** Room pushes: people's messages, typing, and ambient mode. Posts are the agent's. */
156
+ export const authorizeChatOperation = (
157
+ operation: A2Operation<(typeof chat)['events']>,
158
+ ): boolean =>
159
+ operation.type !== 'push' ||
160
+ operation.events.every(
161
+ (event) =>
162
+ event.type === 'message.sent' ||
163
+ event.type === 'typed' ||
164
+ event.type === 'agent.ambient.changed',
165
+ )
166
+
167
+ const agentControls = new Set([
168
+ 'approval',
169
+ 'input',
170
+ 'interrupt',
171
+ 'stop',
172
+ 'retry',
173
+ 'pause',
174
+ 'resume',
175
+ ])
176
+
177
+ /** Agent pushes: controls only. Messages reach the agent through the room. */
178
+ export const authorizeChatAgentOperation = (
179
+ operation: A2Operation<(typeof chatAgent)['events']>,
180
+ ): boolean =>
181
+ operation.type !== 'push' ||
182
+ operation.events.every(
183
+ (event) =>
184
+ event.type === 'ai.control.requested' &&
185
+ typeof event.payload === 'object' &&
186
+ event.payload !== null &&
187
+ 'action' in event.payload &&
188
+ typeof event.payload.action === 'string' &&
189
+ agentControls.has(event.payload.action),
190
+ )