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,209 @@
1
+ import type { UIMessage } from 'ai'
2
+ import { sameValue } from './equal-json.ts'
3
+
4
+ type FieldUndo = { value?: unknown } | { remove: true } | { length: number }
5
+ type FieldsUndo = Record<string, FieldUndo>
6
+ type PartUndo = { value: UIMessage['parts'][number] } | { fields: FieldsUndo }
7
+
8
+ export type MessageBase = {
9
+ partCount: number
10
+ fields?: FieldsUndo
11
+ parts?: Record<string, PartUndo>
12
+ }
13
+
14
+ const record = (value: unknown): value is Record<string, unknown> =>
15
+ typeof value === 'object' && value !== null && !Array.isArray(value)
16
+
17
+ const assertFields = (fields: unknown): void => {
18
+ if (fields === undefined) return
19
+ if (!record(fields)) throw new TypeError('invalid AI message base fields')
20
+ for (const [key, undo] of Object.entries(fields)) {
21
+ if (
22
+ !record(undo) ||
23
+ Object.keys(undo).length > 1 ||
24
+ !(
25
+ Object.keys(undo).length === 0 ||
26
+ 'value' in undo ||
27
+ undo['remove'] === true ||
28
+ (key === 'text' &&
29
+ Number.isSafeInteger(undo['length']) &&
30
+ (undo['length'] as number) >= 0)
31
+ )
32
+ )
33
+ throw new TypeError('invalid AI message base field')
34
+ }
35
+ }
36
+
37
+ /** Checks decoded recovery data; `restoreMessageBase` trusts this shape. */
38
+ export function assertMessageBase(
39
+ value: unknown,
40
+ ): asserts value is MessageBase {
41
+ if (
42
+ !record(value) ||
43
+ !Number.isSafeInteger(value['partCount']) ||
44
+ (value['partCount'] as number) < 0 ||
45
+ (value['partCount'] as number) > 0xffffffff - 1 ||
46
+ Object.keys(value).some(
47
+ (key) => !['partCount', 'fields', 'parts'].includes(key),
48
+ ) ||
49
+ (value['parts'] !== undefined && !record(value['parts']))
50
+ )
51
+ throw new TypeError('invalid AI message base')
52
+ assertFields(value['fields'])
53
+ const partCount = value['partCount'] as number
54
+ for (const [key, undo] of Object.entries(value['parts'] ?? {})) {
55
+ const index = Number(key)
56
+ if (
57
+ !Number.isSafeInteger(index) ||
58
+ String(index) !== key ||
59
+ index < 0 ||
60
+ index >= partCount ||
61
+ !record(undo) ||
62
+ Object.keys(undo).length !== 1 ||
63
+ !(('value' in undo && record(undo['value'])) || 'fields' in undo)
64
+ )
65
+ throw new TypeError('invalid AI message base part')
66
+ if ('fields' in undo) assertFields(undo['fields'])
67
+ }
68
+ }
69
+
70
+ const restoreFields = (
71
+ current: Record<string, unknown>,
72
+ fields: FieldsUndo | undefined,
73
+ ): Record<string, unknown> | undefined => {
74
+ if (fields === undefined) return current
75
+ const restored = { ...current }
76
+ for (const [key, undo] of Object.entries(fields)) {
77
+ if ('remove' in undo) delete restored[key]
78
+ else if ('length' in undo) {
79
+ const text = current[key]
80
+ if (typeof text !== 'string' || undo.length > text.length)
81
+ return undefined
82
+ restored[key] = text.slice(0, undo.length)
83
+ } else
84
+ Object.defineProperty(restored, key, {
85
+ value: undo.value,
86
+ enumerable: true,
87
+ writable: true,
88
+ configurable: true,
89
+ })
90
+ }
91
+ return restored
92
+ }
93
+
94
+ /** `undefined` when `base` no longer describes `current`, including a removed message. */
95
+ export const restoreMessageBase = <M extends UIMessage>(options: {
96
+ current: M | undefined
97
+ base: MessageBase | undefined
98
+ }): M | undefined => {
99
+ const { current, base } = options
100
+ if (base === undefined || current === undefined) return undefined
101
+ const parts = current.parts.slice(0, base.partCount)
102
+ for (const [key, undo] of Object.entries(base.parts ?? {})) {
103
+ const index = Number(key)
104
+ if ('value' in undo) {
105
+ parts[index] = undo.value as M['parts'][number]
106
+ continue
107
+ }
108
+ const part = parts[index]
109
+ const restored =
110
+ part === undefined
111
+ ? undefined
112
+ : restoreFields(part as unknown as Record<string, unknown>, undo.fields)
113
+ if (!restored) return undefined
114
+ parts[index] = restored as M['parts'][number]
115
+ }
116
+ if (
117
+ parts.length !== base.partCount ||
118
+ Object.keys(parts).length !== parts.length
119
+ )
120
+ return undefined
121
+ const { parts: _parts, ...fields } = current
122
+ const restored = restoreFields(fields, base.fields)
123
+ return restored && ({ ...restored, parts } as M)
124
+ }
125
+
126
+ const updateFields = (options: {
127
+ before: Record<string, unknown>
128
+ after: Record<string, unknown>
129
+ undo?: FieldsUndo
130
+ }): FieldsUndo | undefined => {
131
+ const { before, after, undo } = options
132
+ const original = restoreFields(before, undo)
133
+ if (!original) return undefined
134
+ return Object.fromEntries(
135
+ [...new Set([...Object.keys(original), ...Object.keys(after)])].flatMap(
136
+ (key) => {
137
+ if (
138
+ Object.hasOwn(original, key) === Object.hasOwn(after, key) &&
139
+ sameValue(original[key], after[key])
140
+ )
141
+ return []
142
+ const value = original[key]
143
+ const next = after[key]
144
+ const change: FieldUndo = !Object.hasOwn(original, key)
145
+ ? { remove: true }
146
+ : key === 'text' &&
147
+ typeof value === 'string' &&
148
+ typeof next === 'string' &&
149
+ next.startsWith(value)
150
+ ? { length: value.length }
151
+ : value === undefined
152
+ ? {}
153
+ : { value }
154
+ return [[key, change]]
155
+ },
156
+ ),
157
+ )
158
+ }
159
+
160
+ /** `undefined` when `before` no longer matches `base`, so the recovery must be dropped. */
161
+ export const updateMessageBase = (options: {
162
+ base: MessageBase
163
+ before: UIMessage
164
+ after: UIMessage
165
+ }): MessageBase | undefined => {
166
+ const { base, before, after } = options
167
+ if (before === after) return base
168
+ const { parts: beforeParts, ...beforeFields } = before
169
+ const { parts: afterParts, ...afterFields } = after
170
+ const fields = updateFields({
171
+ before: beforeFields,
172
+ after: afterFields,
173
+ ...(base.fields && { undo: base.fields }),
174
+ })
175
+ if (!fields) return undefined
176
+ const parts: Array<[string, PartUndo]> = []
177
+ for (let index = 0; index < base.partCount; index += 1) {
178
+ const previous = beforeParts[index]
179
+ const current = afterParts[index]
180
+ const undo = base.parts?.[index]
181
+ const original = undo && 'value' in undo ? undo.value : previous
182
+ if (!original) return undefined
183
+ if (previous === current && undo === undefined) continue
184
+ const originalFields = original as unknown as Record<string, unknown>
185
+ const undoFields = undo && 'fields' in undo ? undo.fields : undefined
186
+ if (!current) {
187
+ const value = restoreFields(originalFields, undoFields)
188
+ if (!value) return undefined
189
+ parts.push([
190
+ String(index),
191
+ { value: value as UIMessage['parts'][number] },
192
+ ])
193
+ continue
194
+ }
195
+ const changed = updateFields({
196
+ before: originalFields,
197
+ after: current as unknown as Record<string, unknown>,
198
+ ...(undoFields && { undo: undoFields }),
199
+ })
200
+ if (!changed) return undefined
201
+ if (Object.keys(changed).length > 0)
202
+ parts.push([String(index), { fields: changed }])
203
+ }
204
+ return {
205
+ partCount: base.partCount,
206
+ ...(Object.keys(fields).length > 0 && { fields }),
207
+ ...(parts.length > 0 && { parts: Object.fromEntries(parts) }),
208
+ }
209
+ }
@@ -1,7 +1,15 @@
1
+ import {
2
+ restoreMessageBase,
3
+ updateMessageBase,
4
+ type MessageBase,
5
+ } from './ai-message-base.ts'
1
6
  import type { UIMessage, UIMessageChunk } from 'ai'
2
7
  import type {
3
8
  ActiveGenerationProjection,
4
9
  AIState,
10
+ CompactionPart,
11
+ CompactionPartData,
12
+ CompactionRequestedPayload,
5
13
  ApprovalRequestedPayload,
6
14
  ApprovalRespondedPayload,
7
15
  ToolCalledPayload,
@@ -10,6 +18,7 @@ import type {
10
18
  } from './ai.ts'
11
19
  import {
12
20
  type UIMessageCursor,
21
+ type ProjectionChunk,
13
22
  projectUIMessageStream,
14
23
  interruptUIMessage,
15
24
  } from './ai-projector.ts'
@@ -20,23 +29,26 @@ import {
20
29
  type ProgressBatches,
21
30
  } from './ai-progress-batches.ts'
22
31
 
23
- export type MessageProjection<M extends UIMessage> = Omit<
24
- ActiveGenerationProjection<M>,
25
- 'baseTools'
26
- >
32
+ export type MessageProjection = Omit<ActiveGenerationProjection, 'baseTools'>
27
33
 
28
34
  const isRecord = (value: unknown): value is Record<string, unknown> =>
29
35
  typeof value === 'object' && value !== null && !Array.isArray(value)
30
36
 
37
+ const positionOf = (messages: readonly UIMessage[], id: string): number =>
38
+ messages.findLastIndex((message) => message.id === id)
39
+
40
+ export const findMessage = <M extends UIMessage>(
41
+ messages: M[],
42
+ id: string,
43
+ ): M | undefined => messages.findLast((message) => message.id === id)
44
+
31
45
  export const upsertMessage = <M extends UIMessage>(
32
46
  messages: M[],
33
47
  message: M,
34
48
  ): M[] => {
35
- const index = messages.findIndex((candidate) => candidate.id === message.id)
49
+ const index = positionOf(messages, message.id)
36
50
  if (index === -1) return [...messages, message]
37
- const next = [...messages]
38
- next[index] = message
39
- return next
51
+ return messages[index] === message ? messages : messages.with(index, message)
40
52
  }
41
53
 
42
54
  export const upsertResponse = <M extends UIMessage>(options: {
@@ -45,15 +57,11 @@ export const upsertResponse = <M extends UIMessage>(options: {
45
57
  inputId: string
46
58
  }): M[] => {
47
59
  const { messages, message, inputId } = options
48
- if (messages.some((item) => item.id === message.id))
60
+ if (positionOf(messages, message.id) !== -1)
49
61
  return upsertMessage(messages, message)
50
- const inputIndex = messages.findIndex((item) => item.id === inputId)
51
- if (inputIndex === -1) return [...messages, message]
52
- return [
53
- ...messages.slice(0, inputIndex + 1),
54
- message,
55
- ...messages.slice(inputIndex + 1),
56
- ]
62
+ const inputIndex = positionOf(messages, inputId)
63
+ const position = inputIndex === -1 ? messages.length : inputIndex + 1
64
+ return [...messages.slice(0, position), message, ...messages.slice(position)]
57
65
  }
58
66
 
59
67
  export const answerApproval = <M extends UIMessage>(
@@ -108,6 +116,10 @@ export const approvalRespondedChunk = (
108
116
  ...(payload.reason === undefined ? {} : { reason: payload.reason }),
109
117
  })
110
118
 
119
+ export const toolCalledChunk = (
120
+ payload: ToolCalledPayload,
121
+ ): ProjectionChunk => ({ type: 'a2-tool-called', call: payload })
122
+
111
123
  export const toolResultChunk = (payload: ToolResultPayload): UIMessageChunk => {
112
124
  if (payload.phase === 'input') {
113
125
  return {
@@ -171,17 +183,27 @@ export const toolResultChunk = (payload: ToolResultPayload): UIMessageChunk => {
171
183
  } as UIMessageChunk
172
184
  }
173
185
 
174
- export const projectedMessageStream = <M extends UIMessage>(
175
- projection: MessageProjection<M>,
176
- ): { message: M; cursor: UIMessageCursor } => {
177
- const projectionEvents = flattenProgressBatches(projection.batches)
186
+ export const projectedMessageStream = <M extends UIMessage>({
187
+ projection,
188
+ current,
189
+ }: {
190
+ projection: MessageProjection
191
+ current: M | undefined
192
+ }): { message: M; cursor: UIMessageCursor; throughIndex: number } => {
193
+ const base = restoreMessageBase({ current, base: projection.base })
194
+ const projectionEvents: Array<{
195
+ index: number
196
+ chunks: readonly ProjectionChunk[]
197
+ }> = flattenProgressBatches(projection.batches)
178
198
  for (const event of projection.toolEvents) {
179
- if (event.type === 'ai.tool.result') {
180
- projectionEvents.push({
181
- index: event.index,
182
- chunks: [toolResultChunk(event.payload)],
183
- })
184
- }
199
+ projectionEvents.push({
200
+ index: event.index,
201
+ chunks: [
202
+ event.type === 'ai.tool.called'
203
+ ? toolCalledChunk(event.payload)
204
+ : toolResultChunk(event.payload),
205
+ ],
206
+ })
185
207
  }
186
208
  for (const event of projection.approvalEvents) {
187
209
  projectionEvents.push({
@@ -193,20 +215,23 @@ export const projectedMessageStream = <M extends UIMessage>(
193
215
  ],
194
216
  })
195
217
  }
196
- return projectUIMessageStream({
197
- messageId: projection.responseMessageId,
198
- ...(projection.baseMessage === undefined
199
- ? {}
200
- : { base: projection.baseMessage }),
201
- chunks: projectionEvents
202
- .toSorted((left, right) => left.index - right.index)
203
- .flatMap((event) => event.chunks),
204
- })
218
+ const ordered = projectionEvents.toSorted(
219
+ (left, right) => left.index - right.index,
220
+ )
221
+ return {
222
+ ...projectUIMessageStream<M>({
223
+ messageId: projection.responseMessageId,
224
+ ...(base === undefined ? {} : { base }),
225
+ chunks: ordered.flatMap((event) => event.chunks),
226
+ }),
227
+ throughIndex: ordered.at(-1)?.index ?? 0,
228
+ }
205
229
  }
206
230
 
207
- export const projectedMessage = <M extends UIMessage>(
208
- projection: MessageProjection<M>,
209
- ): M => projectedMessageStream(projection).message
231
+ export const projectedMessage = <M extends UIMessage>(options: {
232
+ projection: MessageProjection
233
+ current: M | undefined
234
+ }): M => projectedMessageStream(options).message
210
235
 
211
236
  export const reduceToolActivity = (
212
237
  tools: ToolActivity[],
@@ -216,12 +241,17 @@ export const reduceToolActivity = (
216
241
  ): ToolActivity[] => {
217
242
  if (event.type === 'ai.tool.called') {
218
243
  const payload = event.payload
219
- const { providerMetadata, ...called } = payload
220
244
  const activity: ToolActivity = {
221
- ...called,
222
- ...(providerMetadata === undefined
245
+ requestId: payload.requestId,
246
+ messageId: payload.messageId,
247
+ generationId: payload.generationId,
248
+ toolCallId: payload.toolCallId,
249
+ toolName: payload.toolName,
250
+ ...(payload.dynamic === undefined ? {} : { dynamic: payload.dynamic }),
251
+ ...(payload.providerExecuted === undefined
223
252
  ? {}
224
- : { callProviderMetadata: providerMetadata }),
253
+ : { providerExecuted: payload.providerExecuted }),
254
+ ...(payload.title === undefined ? {} : { title: payload.title }),
225
255
  status: 'running',
226
256
  }
227
257
  return [
@@ -241,22 +271,15 @@ export const reduceToolActivity = (
241
271
  candidate.toolCallId === payload.toolCallId,
242
272
  )
243
273
  const toolName = payload.toolName ?? existing?.toolName
244
- const input = payload.input === undefined ? existing?.input : payload.input
245
274
  const dynamic = payload.dynamic ?? existing?.dynamic
246
275
  const providerExecuted =
247
276
  payload.providerExecuted ?? existing?.providerExecuted
248
- const toolMetadata = payload.toolMetadata ?? existing?.toolMetadata
249
- const resultProviderMetadata =
250
- payload.providerMetadata ?? existing?.resultProviderMetadata
251
277
  const activity: ToolActivity = {
252
278
  requestId: payload.requestId,
253
279
  messageId: payload.messageId,
254
280
  generationId: payload.generationId,
255
281
  toolCallId: payload.toolCallId,
256
282
  ...(toolName === undefined ? {} : { toolName }),
257
- ...(input === undefined ? {} : { input }),
258
- ...(payload.rawInput === undefined ? {} : { rawInput: payload.rawInput }),
259
- ...(payload.output === undefined ? {} : { output: payload.output }),
260
283
  ...(payload.error === undefined ? {} : { error: payload.error }),
261
284
  ...(payload.preliminary === undefined
262
285
  ? {}
@@ -264,11 +287,6 @@ export const reduceToolActivity = (
264
287
  ...(payload.phase === undefined ? {} : { phase: payload.phase }),
265
288
  ...(dynamic === undefined ? {} : { dynamic }),
266
289
  ...(providerExecuted === undefined ? {} : { providerExecuted }),
267
- ...(existing?.callProviderMetadata === undefined
268
- ? {}
269
- : { callProviderMetadata: existing.callProviderMetadata }),
270
- ...(resultProviderMetadata === undefined ? {} : { resultProviderMetadata }),
271
- ...(toolMetadata === undefined ? {} : { toolMetadata }),
272
290
  ...(existing?.title === undefined ? {} : { title: existing.title }),
273
291
  status: payload.denied
274
292
  ? 'denied'
@@ -294,14 +312,14 @@ export const advanceProjection = <M extends UIMessage>({
294
312
  chunks,
295
313
  index,
296
314
  }: {
297
- projection: MessageProjection<M>
315
+ projection: MessageProjection
298
316
  base: M | undefined
299
317
  chunks: UIMessageChunk[]
300
318
  index: number
301
319
  }): {
302
320
  message: M
303
321
  batches: ProgressBatches
304
- cursor: NonNullable<MessageProjection<M>['cursor']>
322
+ cursor: NonNullable<MessageProjection['cursor']>
305
323
  } => {
306
324
  const ordered = index > (projection.batches.tail.at(-1)?.index ?? -1)
307
325
  const batches = ordered
@@ -328,7 +346,10 @@ export const advanceProjection = <M extends UIMessage>({
328
346
  cursor: projection.cursor!.stream,
329
347
  chunks,
330
348
  })
331
- : projectedMessageStream({ ...projection, batches })
349
+ : projectedMessageStream({
350
+ projection: { ...projection, batches },
351
+ current: base,
352
+ })
332
353
  return {
333
354
  message: projected.message,
334
355
  batches,
@@ -352,8 +373,8 @@ export const advanceProjection = <M extends UIMessage>({
352
373
  }
353
374
  }
354
375
 
355
- export const projectedTools = <M extends UIMessage>(
356
- projection: ActiveGenerationProjection<M>,
376
+ export const projectedTools = (
377
+ projection: ActiveGenerationProjection,
357
378
  ): ToolActivity[] =>
358
379
  projection.toolEvents.reduce(
359
380
  (tools, event) => reduceToolActivity(tools, event),
@@ -370,12 +391,22 @@ export const restoreGenerationBase = <M extends UIMessage>(
370
391
  state.activeProjection?.generationId === generationId
371
392
  ? state.activeProjection
372
393
  : null
394
+ const restored = restoreMessageBase({
395
+ current: findMessage(state.messages, responseMessageId),
396
+ base: projection?.base,
397
+ })
398
+ // A superseded attempt's unfinished compaction reruns in its replacement;
399
+ // a completed one stays in effect, and so does its part.
400
+ const base =
401
+ restored !== undefined && reason === 'superseded'
402
+ ? withoutRunningCompactionPart(restored, generationId)
403
+ : restored
373
404
  return {
374
405
  ...state,
375
406
  messages:
376
- projection?.baseMessage === undefined
407
+ base === undefined
377
408
  ? state.messages.filter((message) => message.id !== responseMessageId)
378
- : upsertMessage(state.messages, projection.baseMessage),
409
+ : upsertMessage(state.messages, base),
379
410
  tools:
380
411
  projection === null
381
412
  ? state.tools.filter(
@@ -429,7 +460,7 @@ export const interruptGenerationProjection = <M extends UIMessage>(
429
460
  ? state.activeProjection
430
461
  : null
431
462
  if (projection === null) return null
432
- const retained: ActiveGenerationProjection<M> = {
463
+ const retained: ActiveGenerationProjection = {
433
464
  ...projection,
434
465
  batches: progressBatches(
435
466
  flattenProgressBatches(projection.batches).filter(
@@ -444,7 +475,7 @@ export const interruptGenerationProjection = <M extends UIMessage>(
444
475
  ),
445
476
  }
446
477
  const shouldProject =
447
- retained.baseMessage !== undefined || retained.batches.length > 0
478
+ retained.base !== undefined || retained.batches.length > 0
448
479
  const restored = restoreGenerationBase(
449
480
  state,
450
481
  generationId,
@@ -456,7 +487,12 @@ export const interruptGenerationProjection = <M extends UIMessage>(
456
487
  messages: shouldProject
457
488
  ? upsertMessage(
458
489
  state.messages,
459
- interruptUIMessage(projectedMessage(retained)),
490
+ interruptUIMessage(
491
+ projectedMessage({
492
+ projection: retained,
493
+ current: findMessage(state.messages, responseMessageId),
494
+ }),
495
+ ),
460
496
  )
461
497
  : restored.messages,
462
498
  tools: stopGenerationTools(
@@ -472,3 +508,165 @@ export const interruptGenerationProjection = <M extends UIMessage>(
472
508
  : restored.usage.filter((entry) => entry.generationId !== generationId),
473
509
  }
474
510
  }
511
+
512
+ export const retainMessageBase = <
513
+ S extends {
514
+ messages: UIMessage[]
515
+ activeProjection: { responseMessageId: string; base?: MessageBase } | null
516
+ },
517
+ >(options: {
518
+ before: S
519
+ after: S
520
+ }): S => {
521
+ const { before, after } = options
522
+ const projection = after.activeProjection
523
+ if (
524
+ before.messages === after.messages ||
525
+ !projection?.base ||
526
+ projection.base !== before.activeProjection?.base
527
+ )
528
+ return after
529
+ const previous = findMessage(before.messages, projection.responseMessageId)
530
+ const current = findMessage(after.messages, projection.responseMessageId)
531
+ if (previous !== undefined && previous === current) return after
532
+ // A response removed or rewritten outside this tracking (for example, by an
533
+ // extension's own transition) cannot be restored, so a later re-created
534
+ // message must not inherit the stale recovery.
535
+ const base =
536
+ previous &&
537
+ current &&
538
+ updateMessageBase({
539
+ base: projection.base,
540
+ before: previous,
541
+ after: current,
542
+ })
543
+ const { base: _stale, ...unrecoverable } = projection
544
+ return {
545
+ ...after,
546
+ activeProjection: base ? { ...unrecoverable, base } : unrecoverable,
547
+ }
548
+ }
549
+
550
+ export const isCompactionPart = (part: {
551
+ type: string
552
+ }): part is CompactionPart => part.type === 'data-a2-compaction'
553
+
554
+ const compactionPartOf = (message: UIMessage, generationId: string): number =>
555
+ message.parts.findIndex(
556
+ (part) => isCompactionPart(part) && part.id === generationId,
557
+ )
558
+
559
+ const withoutRunningCompactionPart = <M extends UIMessage>(
560
+ message: M,
561
+ generationId: string,
562
+ ): M => {
563
+ const index = compactionPartOf(message, generationId)
564
+ const part = message.parts[index]
565
+ return part !== undefined &&
566
+ isCompactionPart(part) &&
567
+ part.data.status === 'running'
568
+ ? { ...message, parts: message.parts.toSpliced(index, 1) }
569
+ : message
570
+ }
571
+
572
+ /**
573
+ * Compaction runs before its model step streams, so the part lands between
574
+ * the response's earlier steps and this one. Counting it in the step's base
575
+ * keeps it through Stop cutoffs, failures, and retries of that step.
576
+ */
577
+ export const addCompactionPart = <M extends UIMessage>(
578
+ state: AIState<M>,
579
+ payload: CompactionRequestedPayload,
580
+ ): AIState<M> => {
581
+ const projection = state.activeProjection
582
+ if (projection?.generationId !== payload.generationId) return state
583
+ const current = findMessage(state.messages, projection.responseMessageId)
584
+ if (
585
+ current === undefined
586
+ ? projection.base !== undefined
587
+ : projection.base?.partCount !== current.parts.length
588
+ )
589
+ return state
590
+ const inputTokens = payload.inputTokens ?? payload.inputTokenEstimate
591
+ const part: CompactionPart = {
592
+ type: 'data-a2-compaction',
593
+ id: payload.generationId,
594
+ data: {
595
+ status: 'running',
596
+ ...(inputTokens === undefined ? {} : { inputTokens }),
597
+ ...(payload.thresholdTokens === undefined
598
+ ? {}
599
+ : { thresholdTokens: payload.thresholdTokens }),
600
+ },
601
+ }
602
+ const message = (
603
+ current === undefined
604
+ ? { id: projection.responseMessageId, role: 'assistant', parts: [part] }
605
+ : { ...current, parts: [...current.parts, part] }
606
+ ) as M
607
+ return {
608
+ ...state,
609
+ messages: upsertResponse({
610
+ messages: state.messages,
611
+ message,
612
+ inputId: state.activeGeneration?.messageId ?? message.id,
613
+ }),
614
+ activeProjection: {
615
+ ...projection,
616
+ cursor: null,
617
+ base:
618
+ projection.base === undefined
619
+ ? { partCount: 1 }
620
+ : { ...projection.base, partCount: projection.base.partCount + 1 },
621
+ },
622
+ }
623
+ }
624
+
625
+ export const settleCompactionPart = <M extends UIMessage>(options: {
626
+ state: AIState<M>
627
+ generationId: string
628
+ data: Partial<CompactionPartData>
629
+ }): AIState<M> => {
630
+ const { state, generationId, data } = options
631
+ const position = state.messages.findLastIndex(
632
+ (message) => compactionPartOf(message, generationId) !== -1,
633
+ )
634
+ if (position === -1) return state
635
+ const message = state.messages[position]!
636
+ const index = compactionPartOf(message, generationId)
637
+ const part = message.parts[index] as unknown as CompactionPart
638
+ return {
639
+ ...state,
640
+ messages: state.messages.with(position, {
641
+ ...message,
642
+ parts: message.parts.with(index, {
643
+ ...part,
644
+ data: { ...part.data, ...data },
645
+ } as unknown as M['parts'][number]),
646
+ }),
647
+ }
648
+ }
649
+
650
+ /** A running compaction whose state reverted without completing was abandoned. */
651
+ export const settleAbandonedCompaction = <M extends UIMessage>(
652
+ before: AIState<M>,
653
+ after: AIState<M>,
654
+ ): AIState<M> => {
655
+ const running = before.compaction
656
+ if (
657
+ running?.status !== 'running' ||
658
+ after.compaction === running ||
659
+ (after.compaction?.status === 'completed' &&
660
+ after.compaction.generationId === running.generationId)
661
+ )
662
+ return after
663
+ const interrupted =
664
+ after.status === 'closed' ||
665
+ after.pausedGenerationIds?.[running.generationId] === true ||
666
+ after.terminalGenerations?.[running.generationId] === 'interrupted'
667
+ return settleCompactionPart({
668
+ state: after,
669
+ generationId: running.generationId,
670
+ data: { status: interrupted ? 'interrupted' : 'failed' },
671
+ })
672
+ }