experimental-a2 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (334) hide show
  1. package/CHANGELOG.md +360 -0
  2. package/dist/{actor-ohPC-81x.d.ts → actor-DBbe5xeE.d.ts} +21 -11
  3. package/dist/actor-DBbe5xeE.d.ts.map +1 -0
  4. package/dist/actor-client.d.ts +2 -5
  5. package/dist/actor-client.d.ts.map +1 -1
  6. package/dist/actor-client.js +13 -4
  7. package/dist/actor-client.js.map +1 -1
  8. package/dist/actor-react.d.ts +5 -3
  9. package/dist/actor-react.d.ts.map +1 -1
  10. package/dist/actor-react.js +20 -10
  11. package/dist/actor-react.js.map +1 -1
  12. package/dist/{actor-shared-B5tJfzt-.js → actor-shared-t7w8xIKP.js} +12 -7
  13. package/dist/actor-shared-t7w8xIKP.js.map +1 -0
  14. package/dist/actor.d.ts +1 -1
  15. package/dist/actor.js +42 -23
  16. package/dist/actor.js.map +1 -1
  17. package/dist/{ai-CrLYwNEx.js → ai-UrWfKeuv.js} +966 -541
  18. package/dist/ai-UrWfKeuv.js.map +1 -0
  19. package/dist/ai-client.d.ts +27 -0
  20. package/dist/ai-client.d.ts.map +1 -0
  21. package/dist/ai-client.js +29 -0
  22. package/dist/ai-client.js.map +1 -0
  23. package/dist/ai-react.d.ts +96 -0
  24. package/dist/ai-react.d.ts.map +1 -0
  25. package/dist/ai-react.js +142 -0
  26. package/dist/ai-react.js.map +1 -0
  27. package/dist/ai-server.browser.js +2 -2
  28. package/dist/ai-server.browser.js.map +1 -1
  29. package/dist/ai-server.d.ts +80 -36
  30. package/dist/ai-server.d.ts.map +1 -1
  31. package/dist/ai-server.js +466 -265
  32. package/dist/ai-server.js.map +1 -1
  33. package/dist/{ai-HJ9fHfYI.d.ts → ai-vpGtcaCB.d.ts} +115 -101
  34. package/dist/ai-vpGtcaCB.d.ts.map +1 -0
  35. package/dist/ai.d.ts +2 -2
  36. package/dist/ai.js +2 -2
  37. package/dist/{client-BYzHjkwU.js → client-COaiaDub.js} +447 -80
  38. package/dist/client-COaiaDub.js.map +1 -0
  39. package/dist/{client-CzyacQpJ.d.ts → client-qRnRUzBS.d.ts} +14 -13
  40. package/dist/client-qRnRUzBS.d.ts.map +1 -0
  41. package/dist/client.d.ts +4 -2
  42. package/dist/client.js +1 -1
  43. package/dist/{contract-CKRg_E4q.js → contract-BYKASlCV.js} +12 -21
  44. package/dist/contract-BYKASlCV.js.map +1 -0
  45. package/dist/{idempotent-replay-DVOlyYbx.js → idempotent-replay-JdgMQRRm.js} +2 -2
  46. package/dist/{idempotent-replay-DVOlyYbx.js.map → idempotent-replay-JdgMQRRm.js.map} +1 -1
  47. package/dist/index.d.ts +6 -5
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +4 -3
  50. package/dist/{errors-DvhSXnxk.d.ts → initial-snapshot-BrLso5x_.d.ts} +7 -3
  51. package/dist/initial-snapshot-BrLso5x_.d.ts.map +1 -0
  52. package/dist/{internal-Dq2qYxou.js → internal-CbVvEwad.js} +23 -2
  53. package/dist/internal-CbVvEwad.js.map +1 -0
  54. package/dist/otel.d.ts +1 -1
  55. package/dist/react-BbJ-g6Uu.d.ts +86 -0
  56. package/dist/react-BbJ-g6Uu.d.ts.map +1 -0
  57. package/dist/react.d.ts +2 -122
  58. package/dist/react.js +107 -100
  59. package/dist/react.js.map +1 -1
  60. package/dist/reducer-BAXc8JyF.js +87 -0
  61. package/dist/reducer-BAXc8JyF.js.map +1 -0
  62. package/dist/{reducer-otzHjuJj.d.ts → reducer-BYu6DT7h.d.ts} +45 -29
  63. package/dist/reducer-BYu6DT7h.d.ts.map +1 -0
  64. package/dist/reducer-runtime-DQwz_J59.js +28 -0
  65. package/dist/reducer-runtime-DQwz_J59.js.map +1 -0
  66. package/dist/scheduler-qstash.d.ts +3 -3
  67. package/dist/scheduler-qstash.d.ts.map +1 -1
  68. package/dist/scheduler-qstash.js +33 -10
  69. package/dist/scheduler-qstash.js.map +1 -1
  70. package/dist/{scheduler-task-BpzhPnRS.js → scheduler-task-X1SToDjP.js} +18 -3
  71. package/dist/scheduler-task-X1SToDjP.js.map +1 -0
  72. package/dist/scheduler-vercel.d.ts +4 -9
  73. package/dist/scheduler-vercel.d.ts.map +1 -1
  74. package/dist/scheduler-vercel.js +25 -44
  75. package/dist/scheduler-vercel.js.map +1 -1
  76. package/dist/{server-DbD7IJVK.d.ts → server-CGM8WzRG.d.ts} +87 -26
  77. package/dist/server-CGM8WzRG.d.ts.map +1 -0
  78. package/dist/{server-BD5ckxZb.js → server-Rj64fNvX.js} +854 -202
  79. package/dist/server-Rj64fNvX.js.map +1 -0
  80. package/dist/server.d.ts +4 -4
  81. package/dist/server.js +2 -2
  82. package/dist/{errors-DCk6ch5n.js → session-clear-DGI4jTQ5.js} +17 -2
  83. package/dist/session-clear-DGI4jTQ5.js.map +1 -0
  84. package/dist/session-clear-Dshpuhff.d.ts +12 -0
  85. package/dist/session-clear-Dshpuhff.d.ts.map +1 -0
  86. package/dist/{store-DyZM6fS5.d.ts → store-Gpkq7WBo.d.ts} +44 -3
  87. package/dist/store-Gpkq7WBo.d.ts.map +1 -0
  88. package/dist/store-N8PXxDAS.js.map +1 -1
  89. package/dist/store-memory.d.ts +1 -1
  90. package/dist/store-memory.d.ts.map +1 -1
  91. package/dist/store-memory.js +182 -69
  92. package/dist/store-memory.js.map +1 -1
  93. package/dist/{store-polling-CmxUbV93.js → store-polling-4_y8lETm.js} +21 -4
  94. package/dist/store-polling-4_y8lETm.js.map +1 -0
  95. package/dist/store-postgres.d.ts +1 -1
  96. package/dist/store-postgres.d.ts.map +1 -1
  97. package/dist/store-postgres.js +399 -108
  98. package/dist/store-postgres.js.map +1 -1
  99. package/dist/{store-presence-polling-C7-XZyW9.js → store-presence-polling-Q1C-k2dd.js} +3 -3
  100. package/dist/{store-presence-polling-C7-XZyW9.js.map → store-presence-polling-Q1C-k2dd.js.map} +1 -1
  101. package/dist/store-redis-http.d.ts +1 -1
  102. package/dist/store-redis-http.d.ts.map +1 -1
  103. package/dist/store-redis-http.js +293 -11
  104. package/dist/store-redis-http.js.map +1 -1
  105. package/dist/store-redis-notify-tqWNnMRu.js +2374 -0
  106. package/dist/store-redis-notify-tqWNnMRu.js.map +1 -0
  107. package/dist/store-redis.d.ts +1 -1
  108. package/dist/store-redis.d.ts.map +1 -1
  109. package/dist/store-redis.js +11 -11
  110. package/dist/store-redis.js.map +1 -1
  111. package/dist/store-sqlite.d.ts +1 -1
  112. package/dist/store-sqlite.d.ts.map +1 -1
  113. package/dist/store-sqlite.js +166 -32
  114. package/dist/store-sqlite.js.map +1 -1
  115. package/dist/stream-buffer-Cx7AAPGq.js +23 -0
  116. package/dist/stream-buffer-Cx7AAPGq.js.map +1 -0
  117. package/dist/{telemetry-B5jzpy6y.d.ts → telemetry-cZH-cibj.d.ts} +2 -2
  118. package/dist/telemetry-cZH-cibj.d.ts.map +1 -0
  119. package/dist/{wire-BO5wWCb1.js → wire-DjfHOvdW.js} +132 -10
  120. package/dist/wire-DjfHOvdW.js.map +1 -0
  121. package/docs/01-quickstart.mdx +2 -2
  122. package/docs/actors/01-introduction.mdx +4 -0
  123. package/docs/actors/04-routes.mdx +14 -1
  124. package/docs/concepts/01-contracts.mdx +95 -0
  125. package/docs/concepts/04-state.mdx +3 -3
  126. package/docs/guides/01-timers.mdx +22 -14
  127. package/docs/guides/02-cancellation.mdx +1 -1
  128. package/docs/guides/03-react.mdx +268 -328
  129. package/docs/guides/04-local-first.mdx +32 -73
  130. package/docs/guides/05-production.mdx +40 -27
  131. package/docs/guides/06-ai-agents.mdx +680 -580
  132. package/docs/guides/09-presence.mdx +3 -7
  133. package/docs/guides/10-transports.mdx +5 -5
  134. package/docs/index.mdx +10 -18
  135. package/docs/reference/01-api.mdx +662 -195
  136. package/docs/reference/02-errors.mdx +2 -1
  137. package/examples/playground/DEPLOY.md +52 -6
  138. package/examples/playground/app/a0/[appId]/loading.tsx +16 -0
  139. package/examples/playground/app/a0/[appId]/page.tsx +48 -0
  140. package/examples/playground/app/a0/a0.css +818 -0
  141. package/examples/playground/app/a0/app-list.tsx +211 -0
  142. package/examples/playground/app/a0/app-view.tsx +55 -0
  143. package/examples/playground/app/a0/apps/[appId]/preview/route.ts +27 -0
  144. package/examples/playground/app/a0/apps/[appId]/route.ts +23 -0
  145. package/examples/playground/app/a0/apps.test.ts +116 -0
  146. package/examples/playground/app/a0/apps.ts +87 -0
  147. package/examples/playground/app/a0/chat.tsx +364 -0
  148. package/examples/playground/app/a0/columns.tsx +109 -0
  149. package/examples/playground/app/a0/context-menu.tsx +116 -0
  150. package/examples/playground/app/a0/cookies.ts +46 -0
  151. package/examples/playground/app/a0/crash-on-request.ts +72 -0
  152. package/examples/playground/app/a0/crashes/route.ts +3 -0
  153. package/examples/playground/app/a0/crashes.test.ts +33 -0
  154. package/examples/playground/app/a0/crashes.ts +28 -0
  155. package/examples/playground/app/a0/devtools.tsx +164 -0
  156. package/examples/playground/app/a0/events/route.ts +6 -0
  157. package/examples/playground/app/a0/flags.ts +30 -0
  158. package/examples/playground/app/a0/layout.tsx +38 -0
  159. package/examples/playground/app/a0/list/route.ts +10 -0
  160. package/examples/playground/app/a0/model.test.ts +56 -0
  161. package/examples/playground/app/a0/model.ts +58 -0
  162. package/examples/playground/app/a0/page.tsx +14 -0
  163. package/examples/playground/app/a0/preview-pane.tsx +145 -0
  164. package/examples/playground/app/a0/preview.test.ts +53 -0
  165. package/examples/playground/app/a0/preview.ts +26 -0
  166. package/examples/playground/app/a0/sandbox-log.ts +69 -0
  167. package/examples/playground/app/a0/sandbox.ts +191 -0
  168. package/examples/playground/app/a0/sandboxes/scheduler/route.ts +6 -0
  169. package/examples/playground/app/a0/scheduler/route.ts +7 -0
  170. package/examples/playground/app/a0/server.ts +158 -0
  171. package/examples/playground/app/a0/session.ts +47 -0
  172. package/examples/playground/app/a0/shell.tsx +150 -0
  173. package/examples/playground/app/a0/short/events/route.ts +9 -0
  174. package/examples/playground/app/a0/splitter.tsx +120 -0
  175. package/examples/playground/app/agent/[agentId]/agent-client.tsx +144 -174
  176. package/examples/playground/app/agent/[agentId]/command-receipt.test.tsx +65 -0
  177. package/examples/playground/app/agent/[agentId]/command-receipt.tsx +27 -0
  178. package/examples/playground/app/agent/[agentId]/compaction-event.tsx +25 -24
  179. package/examples/playground/app/agent/[agentId]/compaction-panel.test.tsx +119 -0
  180. package/examples/playground/app/agent/[agentId]/compaction-panel.tsx +208 -165
  181. package/examples/playground/app/agent/[agentId]/page.tsx +2 -16
  182. package/examples/playground/app/agent/events/route.ts +5 -2
  183. package/examples/playground/app/agent/ingress.test.ts +98 -0
  184. package/examples/playground/app/agent/model-input.test.tsx +58 -0
  185. package/examples/playground/app/agent/model-input.tsx +115 -0
  186. package/examples/playground/app/agent/model.test.ts +73 -0
  187. package/examples/playground/app/agent/model.ts +58 -26
  188. package/examples/playground/app/agent/new-agent-session.tsx +37 -19
  189. package/examples/playground/app/agent/page.tsx +4 -4
  190. package/examples/playground/app/agent/server.ts +15 -14
  191. package/examples/playground/app/agent/session.ts +5 -6
  192. package/examples/playground/app/agent/telemetry.test.ts +168 -0
  193. package/examples/playground/app/agent/telemetry.ts +30 -0
  194. package/examples/playground/app/agent/trace.test.ts +23 -0
  195. package/examples/playground/app/agent/trace.ts +18 -0
  196. package/examples/playground/app/canvas/[canvasId]/canvas-client.tsx +11 -11
  197. package/examples/playground/app/canvas/[canvasId]/page.tsx +1 -2
  198. package/examples/playground/app/canvas/[canvasId]/session.ts +2 -2
  199. package/examples/playground/app/canvas/model.ts +53 -53
  200. package/examples/playground/app/chat/[chatId]/agent-state.test.ts +176 -0
  201. package/examples/playground/app/chat/[chatId]/agent-stream-drawer.tsx +16 -1
  202. package/examples/playground/app/chat/[chatId]/chat-client.tsx +55 -77
  203. package/examples/playground/app/chat/[chatId]/chat-view.ts +37 -3
  204. package/examples/playground/app/chat/[chatId]/page.tsx +7 -15
  205. package/examples/playground/app/chat/[chatId]/session.ts +2 -19
  206. package/examples/playground/app/chat/agent/route.ts +9 -0
  207. package/examples/playground/app/chat/model.test.ts +46 -119
  208. package/examples/playground/app/chat/model.ts +82 -128
  209. package/examples/playground/app/chat/scheduler/route.ts +2 -2
  210. package/examples/playground/app/chat/server.test.ts +108 -0
  211. package/examples/playground/app/chat/server.ts +69 -73
  212. package/examples/playground/app/components/activity-feed.tsx +30 -1
  213. package/examples/playground/app/components/activity-history.test.tsx +35 -0
  214. package/examples/playground/app/components/demo-icons.tsx +89 -0
  215. package/examples/playground/app/components/demos.tsx +33 -0
  216. package/examples/playground/app/components/fast-link.test.tsx +58 -0
  217. package/examples/playground/app/components/fast-link.tsx +55 -0
  218. package/examples/playground/app/components/site-header.tsx +96 -0
  219. package/examples/playground/app/counter/counter-client.tsx +3 -3
  220. package/examples/playground/app/counter/model.test.ts +3 -3
  221. package/examples/playground/app/counter/model.ts +11 -21
  222. package/examples/playground/app/counter/page.tsx +2 -15
  223. package/examples/playground/app/counter/session.ts +2 -2
  224. package/examples/playground/app/documents/[documentId]/code-editor.tsx +12 -1
  225. package/examples/playground/app/documents/[documentId]/document-client.tsx +10 -11
  226. package/examples/playground/app/documents/[documentId]/page.tsx +1 -2
  227. package/examples/playground/app/documents/[documentId]/session.ts +2 -2
  228. package/examples/playground/app/documents/model.ts +27 -27
  229. package/examples/playground/app/globals.css +420 -207
  230. package/examples/playground/app/initial-state.test.tsx +54 -0
  231. package/examples/playground/app/layout.tsx +7 -23
  232. package/examples/playground/app/orders/[orderId]/page.tsx +1 -2
  233. package/examples/playground/app/orders/[orderId]/session.ts +2 -2
  234. package/examples/playground/app/orders/model.ts +49 -49
  235. package/examples/playground/app/page.tsx +13 -102
  236. package/examples/playground/app/recovery/[recoveryId]/page.tsx +1 -2
  237. package/examples/playground/app/recovery/[recoveryId]/session.ts +2 -2
  238. package/examples/playground/app/recovery/model.ts +30 -30
  239. package/examples/playground/instrumentation.ts +5 -0
  240. package/examples/playground/package.json +10 -2
  241. package/examples/playground/scripts/a0-template.ts +77 -0
  242. package/examples/playground/vercel.json +18 -0
  243. package/package.json +3 -1
  244. package/src/actor-client.ts +16 -4
  245. package/src/actor-react.ts +25 -11
  246. package/src/actor-shared.ts +21 -6
  247. package/src/actor.ts +76 -31
  248. package/src/ai-agent-actions.ts +219 -0
  249. package/src/ai-agent.ts +2343 -0
  250. package/src/ai-app-state.ts +91 -0
  251. package/src/ai-client-state.ts +39 -2
  252. package/src/ai-client.ts +62 -0
  253. package/src/ai-context-schema.ts +25 -5
  254. package/src/ai-context.ts +90 -40
  255. package/src/ai-control-server.ts +94 -55
  256. package/src/ai-control.ts +2 -0
  257. package/src/ai-coordinator.ts +23 -22
  258. package/src/ai-message-base.ts +209 -0
  259. package/src/ai-message-projection.ts +382 -58
  260. package/src/ai-progress-batches.ts +26 -0
  261. package/src/ai-projector.ts +95 -14
  262. package/src/ai-react.ts +146 -0
  263. package/src/ai-server.browser.ts +2 -2
  264. package/src/ai-server.ts +20 -1951
  265. package/src/ai-stored-state.ts +48 -163
  266. package/src/ai.ts +343 -276
  267. package/src/bootstrap-stream.ts +72 -0
  268. package/src/bounded-cache.ts +55 -0
  269. package/src/client-render-session.ts +49 -0
  270. package/src/client.ts +688 -151
  271. package/src/contract.ts +92 -42
  272. package/src/deterministic-id.ts +37 -0
  273. package/src/equal-json.ts +32 -0
  274. package/src/errors.ts +13 -1
  275. package/src/index.ts +5 -1
  276. package/src/initial-snapshot.ts +39 -0
  277. package/src/internal.ts +45 -0
  278. package/src/postgres-notification-scope.ts +14 -3
  279. package/src/react.ts +171 -206
  280. package/src/redis-http-reply.ts +256 -0
  281. package/src/redis-http-stream.ts +73 -0
  282. package/src/redis-http-subscriptions.ts +13 -5
  283. package/src/reducer-runtime.ts +52 -0
  284. package/src/reducer.ts +92 -10
  285. package/src/scheduler-qstash.ts +49 -20
  286. package/src/scheduler-task.ts +34 -2
  287. package/src/scheduler-vercel.ts +32 -84
  288. package/src/server-fetch.ts +139 -35
  289. package/src/server.ts +957 -237
  290. package/src/session-clear.ts +23 -0
  291. package/src/session-socket.ts +154 -49
  292. package/src/sse.ts +71 -4
  293. package/src/store-memory.ts +220 -78
  294. package/src/store-polling.ts +18 -4
  295. package/src/store-postgres.ts +461 -114
  296. package/src/store-redis-core.ts +1260 -434
  297. package/src/store-redis-http.ts +18 -2
  298. package/src/store-redis-notify.ts +260 -85
  299. package/src/store-redis.ts +19 -9
  300. package/src/store-sqlite.ts +259 -45
  301. package/src/store.ts +46 -1
  302. package/src/stream-buffer.ts +43 -0
  303. package/src/telemetry.ts +13 -1
  304. package/src/wire.ts +241 -13
  305. package/dist/actor-ohPC-81x.d.ts.map +0 -1
  306. package/dist/actor-shared-B5tJfzt-.js.map +0 -1
  307. package/dist/ai-CrLYwNEx.js.map +0 -1
  308. package/dist/ai-HJ9fHfYI.d.ts.map +0 -1
  309. package/dist/client-BYzHjkwU.js.map +0 -1
  310. package/dist/client-CzyacQpJ.d.ts.map +0 -1
  311. package/dist/contract-CKRg_E4q.js.map +0 -1
  312. package/dist/errors-DCk6ch5n.js.map +0 -1
  313. package/dist/errors-DvhSXnxk.d.ts.map +0 -1
  314. package/dist/internal-Dq2qYxou.js.map +0 -1
  315. package/dist/react.d.ts.map +0 -1
  316. package/dist/reducer-DEMjEY_O.js +0 -29
  317. package/dist/reducer-DEMjEY_O.js.map +0 -1
  318. package/dist/reducer-otzHjuJj.d.ts.map +0 -1
  319. package/dist/scheduler-task-BpzhPnRS.js.map +0 -1
  320. package/dist/server-BD5ckxZb.js.map +0 -1
  321. package/dist/server-DbD7IJVK.d.ts.map +0 -1
  322. package/dist/store-DyZM6fS5.d.ts.map +0 -1
  323. package/dist/store-polling-CmxUbV93.js.map +0 -1
  324. package/dist/store-redis-notify-BVLUfI3j.js +0 -1583
  325. package/dist/store-redis-notify-BVLUfI3j.js.map +0 -1
  326. package/dist/telemetry-B5jzpy6y.d.ts.map +0 -1
  327. package/dist/validate-XKT4FSNn.js +0 -30
  328. package/dist/validate-XKT4FSNn.js.map +0 -1
  329. package/dist/wire-BO5wWCb1.js.map +0 -1
  330. package/examples/playground/app/agent/[agentId]/compaction/route.ts +0 -14
  331. package/examples/playground/app/agent/compaction-settings.test.ts +0 -144
  332. package/examples/playground/app/agent/compaction-settings.ts +0 -49
  333. package/examples/playground/app/agent/compaction-timeline.test.ts +0 -377
  334. package/examples/playground/app/agent/compaction-timeline.ts +0 -208
@@ -1,13 +1,27 @@
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,
9
+ AIState,
10
+ CompactionPart,
11
+ CompactionPartData,
12
+ CompactionRequestedPayload,
4
13
  ApprovalRequestedPayload,
5
14
  ApprovalRespondedPayload,
6
15
  ToolCalledPayload,
7
16
  ToolResultPayload,
8
17
  ToolActivity,
9
18
  } from './ai.ts'
10
- import { type UIMessageCursor, projectUIMessageStream } from './ai-projector.ts'
19
+ import {
20
+ type UIMessageCursor,
21
+ type ProjectionChunk,
22
+ projectUIMessageStream,
23
+ interruptUIMessage,
24
+ } from './ai-projector.ts'
11
25
  import {
12
26
  flattenProgressBatches,
13
27
  appendProgressBatch,
@@ -15,23 +29,26 @@ import {
15
29
  type ProgressBatches,
16
30
  } from './ai-progress-batches.ts'
17
31
 
18
- export type MessageProjection<M extends UIMessage> = Omit<
19
- ActiveGenerationProjection<M>,
20
- 'baseTools'
21
- >
32
+ export type MessageProjection = Omit<ActiveGenerationProjection, 'baseTools'>
22
33
 
23
34
  const isRecord = (value: unknown): value is Record<string, unknown> =>
24
35
  typeof value === 'object' && value !== null && !Array.isArray(value)
25
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
+
26
45
  export const upsertMessage = <M extends UIMessage>(
27
46
  messages: M[],
28
47
  message: M,
29
48
  ): M[] => {
30
- const index = messages.findIndex((candidate) => candidate.id === message.id)
49
+ const index = positionOf(messages, message.id)
31
50
  if (index === -1) return [...messages, message]
32
- const next = [...messages]
33
- next[index] = message
34
- return next
51
+ return messages[index] === message ? messages : messages.with(index, message)
35
52
  }
36
53
 
37
54
  export const upsertResponse = <M extends UIMessage>(options: {
@@ -40,15 +57,11 @@ export const upsertResponse = <M extends UIMessage>(options: {
40
57
  inputId: string
41
58
  }): M[] => {
42
59
  const { messages, message, inputId } = options
43
- if (messages.some((item) => item.id === message.id))
60
+ if (positionOf(messages, message.id) !== -1)
44
61
  return upsertMessage(messages, message)
45
- const inputIndex = messages.findIndex((item) => item.id === inputId)
46
- if (inputIndex === -1) return [...messages, message]
47
- return [
48
- ...messages.slice(0, inputIndex + 1),
49
- message,
50
- ...messages.slice(inputIndex + 1),
51
- ]
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)]
52
65
  }
53
66
 
54
67
  export const answerApproval = <M extends UIMessage>(
@@ -103,6 +116,10 @@ export const approvalRespondedChunk = (
103
116
  ...(payload.reason === undefined ? {} : { reason: payload.reason }),
104
117
  })
105
118
 
119
+ export const toolCalledChunk = (
120
+ payload: ToolCalledPayload,
121
+ ): ProjectionChunk => ({ type: 'a2-tool-called', call: payload })
122
+
106
123
  export const toolResultChunk = (payload: ToolResultPayload): UIMessageChunk => {
107
124
  if (payload.phase === 'input') {
108
125
  return {
@@ -166,17 +183,27 @@ export const toolResultChunk = (payload: ToolResultPayload): UIMessageChunk => {
166
183
  } as UIMessageChunk
167
184
  }
168
185
 
169
- export const projectedMessageStream = <M extends UIMessage>(
170
- projection: MessageProjection<M>,
171
- ): { message: M; cursor: UIMessageCursor } => {
172
- 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)
173
198
  for (const event of projection.toolEvents) {
174
- if (event.type === 'ai.tool.result') {
175
- projectionEvents.push({
176
- index: event.index,
177
- chunks: [toolResultChunk(event.payload)],
178
- })
179
- }
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
+ })
180
207
  }
181
208
  for (const event of projection.approvalEvents) {
182
209
  projectionEvents.push({
@@ -188,20 +215,23 @@ export const projectedMessageStream = <M extends UIMessage>(
188
215
  ],
189
216
  })
190
217
  }
191
- return projectUIMessageStream({
192
- messageId: projection.responseMessageId,
193
- ...(projection.baseMessage === undefined
194
- ? {}
195
- : { base: projection.baseMessage }),
196
- chunks: projectionEvents
197
- .toSorted((left, right) => left.index - right.index)
198
- .flatMap((event) => event.chunks),
199
- })
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
+ }
200
229
  }
201
230
 
202
- export const projectedMessage = <M extends UIMessage>(
203
- projection: MessageProjection<M>,
204
- ): M => projectedMessageStream(projection).message
231
+ export const projectedMessage = <M extends UIMessage>(options: {
232
+ projection: MessageProjection
233
+ current: M | undefined
234
+ }): M => projectedMessageStream(options).message
205
235
 
206
236
  export const reduceToolActivity = (
207
237
  tools: ToolActivity[],
@@ -211,12 +241,17 @@ export const reduceToolActivity = (
211
241
  ): ToolActivity[] => {
212
242
  if (event.type === 'ai.tool.called') {
213
243
  const payload = event.payload
214
- const { providerMetadata, ...called } = payload
215
244
  const activity: ToolActivity = {
216
- ...called,
217
- ...(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
218
252
  ? {}
219
- : { callProviderMetadata: providerMetadata }),
253
+ : { providerExecuted: payload.providerExecuted }),
254
+ ...(payload.title === undefined ? {} : { title: payload.title }),
220
255
  status: 'running',
221
256
  }
222
257
  return [
@@ -236,22 +271,15 @@ export const reduceToolActivity = (
236
271
  candidate.toolCallId === payload.toolCallId,
237
272
  )
238
273
  const toolName = payload.toolName ?? existing?.toolName
239
- const input = payload.input === undefined ? existing?.input : payload.input
240
274
  const dynamic = payload.dynamic ?? existing?.dynamic
241
275
  const providerExecuted =
242
276
  payload.providerExecuted ?? existing?.providerExecuted
243
- const toolMetadata = payload.toolMetadata ?? existing?.toolMetadata
244
- const resultProviderMetadata =
245
- payload.providerMetadata ?? existing?.resultProviderMetadata
246
277
  const activity: ToolActivity = {
247
278
  requestId: payload.requestId,
248
279
  messageId: payload.messageId,
249
280
  generationId: payload.generationId,
250
281
  toolCallId: payload.toolCallId,
251
282
  ...(toolName === undefined ? {} : { toolName }),
252
- ...(input === undefined ? {} : { input }),
253
- ...(payload.rawInput === undefined ? {} : { rawInput: payload.rawInput }),
254
- ...(payload.output === undefined ? {} : { output: payload.output }),
255
283
  ...(payload.error === undefined ? {} : { error: payload.error }),
256
284
  ...(payload.preliminary === undefined
257
285
  ? {}
@@ -259,11 +287,6 @@ export const reduceToolActivity = (
259
287
  ...(payload.phase === undefined ? {} : { phase: payload.phase }),
260
288
  ...(dynamic === undefined ? {} : { dynamic }),
261
289
  ...(providerExecuted === undefined ? {} : { providerExecuted }),
262
- ...(existing?.callProviderMetadata === undefined
263
- ? {}
264
- : { callProviderMetadata: existing.callProviderMetadata }),
265
- ...(resultProviderMetadata === undefined ? {} : { resultProviderMetadata }),
266
- ...(toolMetadata === undefined ? {} : { toolMetadata }),
267
290
  ...(existing?.title === undefined ? {} : { title: existing.title }),
268
291
  status: payload.denied
269
292
  ? 'denied'
@@ -289,14 +312,14 @@ export const advanceProjection = <M extends UIMessage>({
289
312
  chunks,
290
313
  index,
291
314
  }: {
292
- projection: MessageProjection<M>
315
+ projection: MessageProjection
293
316
  base: M | undefined
294
317
  chunks: UIMessageChunk[]
295
318
  index: number
296
319
  }): {
297
320
  message: M
298
321
  batches: ProgressBatches
299
- cursor: NonNullable<MessageProjection<M>['cursor']>
322
+ cursor: NonNullable<MessageProjection['cursor']>
300
323
  } => {
301
324
  const ordered = index > (projection.batches.tail.at(-1)?.index ?? -1)
302
325
  const batches = ordered
@@ -323,7 +346,10 @@ export const advanceProjection = <M extends UIMessage>({
323
346
  cursor: projection.cursor!.stream,
324
347
  chunks,
325
348
  })
326
- : projectedMessageStream({ ...projection, batches })
349
+ : projectedMessageStream({
350
+ projection: { ...projection, batches },
351
+ current: base,
352
+ })
327
353
  return {
328
354
  message: projected.message,
329
355
  batches,
@@ -346,3 +372,301 @@ export const advanceProjection = <M extends UIMessage>({
346
372
  },
347
373
  }
348
374
  }
375
+
376
+ export const projectedTools = (
377
+ projection: ActiveGenerationProjection,
378
+ ): ToolActivity[] =>
379
+ projection.toolEvents.reduce(
380
+ (tools, event) => reduceToolActivity(tools, event),
381
+ projection.baseTools,
382
+ )
383
+
384
+ export const restoreGenerationBase = <M extends UIMessage>(
385
+ state: AIState<M>,
386
+ generationId: string,
387
+ responseMessageId: string,
388
+ reason: 'interrupted' | 'superseded' | 'failed',
389
+ ): AIState<M> => {
390
+ const projection =
391
+ state.activeProjection?.generationId === generationId
392
+ ? state.activeProjection
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
404
+ return {
405
+ ...state,
406
+ messages:
407
+ base === undefined
408
+ ? state.messages.filter((message) => message.id !== responseMessageId)
409
+ : upsertMessage(state.messages, base),
410
+ tools:
411
+ projection === null
412
+ ? state.tools.filter(
413
+ (activity) => activity.generationId !== generationId,
414
+ )
415
+ : projection.baseTools,
416
+ pendingApprovals: state.pendingApprovals.filter(
417
+ (approval) => approval.generationId !== generationId,
418
+ ),
419
+ pendingInputs: state.pendingInputs.filter(
420
+ (input) => input.generationId !== generationId,
421
+ ),
422
+ activeGeneration: null,
423
+ activeProjection: null,
424
+ terminalGenerations: {
425
+ ...state.terminalGenerations,
426
+ [generationId]: reason,
427
+ },
428
+ compaction:
429
+ projection === null
430
+ ? state.compaction?.generationId === generationId
431
+ ? null
432
+ : state.compaction
433
+ : (projection.baseCompaction ?? null),
434
+ }
435
+ }
436
+
437
+ export const stopGenerationTools = (
438
+ tools: ToolActivity[],
439
+ generationId: string | undefined,
440
+ responseMessageId: string,
441
+ error: string,
442
+ ): ToolActivity[] =>
443
+ tools.map((activity) =>
444
+ activity.status === 'running' &&
445
+ (generationId === undefined
446
+ ? activity.messageId === responseMessageId
447
+ : activity.generationId === generationId)
448
+ ? { ...activity, status: 'failed', error, preliminary: false }
449
+ : activity,
450
+ )
451
+
452
+ export const interruptGenerationProjection = <M extends UIMessage>(
453
+ state: AIState<M>,
454
+ generationId: string,
455
+ responseMessageId: string,
456
+ cutoff: number,
457
+ ): AIState<M> | null => {
458
+ const projection =
459
+ state.activeProjection?.generationId === generationId
460
+ ? state.activeProjection
461
+ : null
462
+ if (projection === null) return null
463
+ const retained: ActiveGenerationProjection = {
464
+ ...projection,
465
+ batches: progressBatches(
466
+ flattenProgressBatches(projection.batches).filter(
467
+ (batch) => batch.index <= cutoff,
468
+ ),
469
+ ),
470
+ toolEvents: projection.toolEvents.filter(
471
+ (toolEvent) => toolEvent.index <= cutoff,
472
+ ),
473
+ approvalEvents: projection.approvalEvents.filter(
474
+ (approvalEvent) => approvalEvent.index <= cutoff,
475
+ ),
476
+ }
477
+ const shouldProject =
478
+ retained.base !== undefined || retained.batches.length > 0
479
+ const restored = restoreGenerationBase(
480
+ state,
481
+ generationId,
482
+ responseMessageId,
483
+ 'interrupted',
484
+ )
485
+ return {
486
+ ...restored,
487
+ messages: shouldProject
488
+ ? upsertMessage(
489
+ state.messages,
490
+ interruptUIMessage(
491
+ projectedMessage({
492
+ projection: retained,
493
+ current: findMessage(state.messages, responseMessageId),
494
+ }),
495
+ ),
496
+ )
497
+ : restored.messages,
498
+ tools: stopGenerationTools(
499
+ projectedTools(retained),
500
+ generationId,
501
+ responseMessageId,
502
+ 'Tool execution was interrupted.',
503
+ ),
504
+ usage:
505
+ projection.completionIndex === undefined ||
506
+ projection.completionIndex <= cutoff
507
+ ? restored.usage
508
+ : restored.usage.filter((entry) => entry.generationId !== generationId),
509
+ }
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
+ }
@@ -1,4 +1,30 @@
1
1
  import type { UIMessageChunk } from 'ai'
2
+ import type { GenerationProgressPayload } from './ai.ts'
3
+
4
+ /** Progress already in a log may name its generation by ids instead of `startIndex`. */
5
+ export type StoredProgressPayload =
6
+ | GenerationProgressPayload
7
+ | {
8
+ requestId: string
9
+ generationId: string
10
+ responseMessageId: string
11
+ chunks: UIMessageChunk[]
12
+ }
13
+
14
+ export const progressOwnedBy = ({
15
+ payload,
16
+ owner,
17
+ startIndex,
18
+ }: {
19
+ payload: StoredProgressPayload
20
+ owner: { requestId: string; generationId: string; responseMessageId: string }
21
+ startIndex: number
22
+ }): boolean =>
23
+ 'startIndex' in payload
24
+ ? payload.startIndex === startIndex
25
+ : payload.requestId === owner.requestId &&
26
+ payload.generationId === owner.generationId &&
27
+ payload.responseMessageId === owner.responseMessageId
2
28
 
3
29
  export type ProgressBatch = { index: number; chunks: UIMessageChunk[] }
4
30
  type BatchBlock = ProgressBatch[] | { left: BatchBlock; right: BatchBlock }