experimental-a2 0.16.1 → 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 (333) hide show
  1. package/CHANGELOG.md +352 -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-DgOBltJ_.js → ai-UrWfKeuv.js} +621 -216
  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 +432 -258
  32. package/dist/ai-server.js.map +1 -1
  33. package/dist/{ai-CrEf6p_W.d.ts → ai-vpGtcaCB.d.ts} +114 -100
  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 +671 -581
  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 +656 -194
  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 +6 -3
  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 +92 -72
  256. package/src/ai-coordinator.ts +23 -22
  257. package/src/ai-message-base.ts +209 -0
  258. package/src/ai-message-projection.ts +262 -64
  259. package/src/ai-progress-batches.ts +26 -0
  260. package/src/ai-projector.ts +95 -14
  261. package/src/ai-react.ts +146 -0
  262. package/src/ai-server.browser.ts +2 -2
  263. package/src/ai-server.ts +20 -1964
  264. package/src/ai-stored-state.ts +48 -163
  265. package/src/ai.ts +339 -155
  266. package/src/bootstrap-stream.ts +72 -0
  267. package/src/bounded-cache.ts +55 -0
  268. package/src/client-render-session.ts +49 -0
  269. package/src/client.ts +688 -151
  270. package/src/contract.ts +92 -42
  271. package/src/deterministic-id.ts +37 -0
  272. package/src/equal-json.ts +32 -0
  273. package/src/errors.ts +13 -1
  274. package/src/index.ts +5 -1
  275. package/src/initial-snapshot.ts +39 -0
  276. package/src/internal.ts +45 -0
  277. package/src/postgres-notification-scope.ts +14 -3
  278. package/src/react.ts +171 -206
  279. package/src/redis-http-reply.ts +256 -0
  280. package/src/redis-http-stream.ts +73 -0
  281. package/src/redis-http-subscriptions.ts +13 -5
  282. package/src/reducer-runtime.ts +52 -0
  283. package/src/reducer.ts +92 -10
  284. package/src/scheduler-qstash.ts +49 -20
  285. package/src/scheduler-task.ts +34 -2
  286. package/src/scheduler-vercel.ts +32 -84
  287. package/src/server-fetch.ts +139 -35
  288. package/src/server.ts +957 -237
  289. package/src/session-clear.ts +23 -0
  290. package/src/session-socket.ts +154 -49
  291. package/src/sse.ts +71 -4
  292. package/src/store-memory.ts +220 -78
  293. package/src/store-polling.ts +18 -4
  294. package/src/store-postgres.ts +461 -114
  295. package/src/store-redis-core.ts +1260 -434
  296. package/src/store-redis-http.ts +18 -2
  297. package/src/store-redis-notify.ts +260 -85
  298. package/src/store-redis.ts +19 -9
  299. package/src/store-sqlite.ts +259 -45
  300. package/src/store.ts +46 -1
  301. package/src/stream-buffer.ts +43 -0
  302. package/src/telemetry.ts +13 -1
  303. package/src/wire.ts +241 -13
  304. package/dist/actor-ohPC-81x.d.ts.map +0 -1
  305. package/dist/actor-shared-B5tJfzt-.js.map +0 -1
  306. package/dist/ai-CrEf6p_W.d.ts.map +0 -1
  307. package/dist/ai-DgOBltJ_.js.map +0 -1
  308. package/dist/client-BYzHjkwU.js.map +0 -1
  309. package/dist/client-CzyacQpJ.d.ts.map +0 -1
  310. package/dist/contract-CKRg_E4q.js.map +0 -1
  311. package/dist/errors-DCk6ch5n.js.map +0 -1
  312. package/dist/errors-DvhSXnxk.d.ts.map +0 -1
  313. package/dist/internal-Dq2qYxou.js.map +0 -1
  314. package/dist/react.d.ts.map +0 -1
  315. package/dist/reducer-DEMjEY_O.js +0 -29
  316. package/dist/reducer-DEMjEY_O.js.map +0 -1
  317. package/dist/reducer-otzHjuJj.d.ts.map +0 -1
  318. package/dist/scheduler-task-BpzhPnRS.js.map +0 -1
  319. package/dist/server-BD5ckxZb.js.map +0 -1
  320. package/dist/server-DbD7IJVK.d.ts.map +0 -1
  321. package/dist/store-DyZM6fS5.d.ts.map +0 -1
  322. package/dist/store-polling-CmxUbV93.js.map +0 -1
  323. package/dist/store-redis-notify-BVLUfI3j.js +0 -1583
  324. package/dist/store-redis-notify-BVLUfI3j.js.map +0 -1
  325. package/dist/telemetry-B5jzpy6y.d.ts.map +0 -1
  326. package/dist/validate-XKT4FSNn.js +0 -30
  327. package/dist/validate-XKT4FSNn.js.map +0 -1
  328. package/dist/wire-BO5wWCb1.js.map +0 -1
  329. package/examples/playground/app/agent/[agentId]/compaction/route.ts +0 -14
  330. package/examples/playground/app/agent/compaction-settings.test.ts +0 -144
  331. package/examples/playground/app/agent/compaction-settings.ts +0 -49
  332. package/examples/playground/app/agent/compaction-timeline.test.ts +0 -377
  333. package/examples/playground/app/agent/compaction-timeline.ts +0 -208
@@ -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
+ }
@@ -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 }