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,9 +1,11 @@
1
+ import { assertMessageBase, restoreMessageBase } from './ai-message-base.ts'
2
+ import { sameValue } from './equal-json.ts'
1
3
  import type { UIMessage } from 'ai'
2
4
  import type { AIState } from './ai.ts'
3
5
  import type { MessageProjection } from './ai-message-projection.ts'
4
6
  import type { EventDefs } from './contract.ts'
5
- import { clientStateProjection, type Reducer } from './reducer.ts'
6
- import { validateSync } from './validate.ts'
7
+ import type { Reducer } from './reducer.ts'
8
+ import { withReducerRuntime } from './reducer-runtime.ts'
7
9
 
8
10
  type Value =
9
11
  null | boolean | number | string | Value[] | { [key: string]: Value }
@@ -15,13 +17,11 @@ type Undo =
15
17
 
16
18
  type Projection = {
17
19
  fields: Value
18
- hasMessage: boolean
19
- message?: Undo
20
20
  tools?: Undo
21
21
  }
22
22
 
23
23
  export type AIStoredState = {
24
- version: 2
24
+ version: 3
25
25
  fields: Value
26
26
  projection: Projection | null
27
27
  values: Payload[]
@@ -30,22 +30,11 @@ export type AIStoredState = {
30
30
  export type AIStoredView = {
31
31
  messages: UIMessage[]
32
32
  activeProjection:
33
- (MessageProjection<UIMessage> & { baseTools?: AIState['tools'] }) | null
33
+ (MessageProjection & { baseTools?: AIState['tools'] }) | null
34
34
  tools?: AIState['tools']
35
35
  usage?: AIState['usage']
36
36
  }
37
37
 
38
- export const aiStateReducer: unique symbol = Symbol('a2.ai.stateReducer')
39
-
40
- export type AIStoredReducer<
41
- D extends EventDefs,
42
- S extends AIStoredView,
43
- > = Reducer<D, AIStoredState, Record<never, never>, S> & {
44
- readonly [aiStateReducer]: Reducer<D, S>
45
- view(state: AIStoredState): S
46
- fromView(state: S): AIStoredState
47
- }
48
-
49
38
  const isRecord = (value: unknown): value is Record<string, unknown> =>
50
39
  typeof value === 'object' && value !== null && !Array.isArray(value)
51
40
 
@@ -87,39 +76,6 @@ const payloadFields = new Set([
87
76
  const keyFor = (value: unknown): string =>
88
77
  value === undefined ? 'undefined' : JSON.stringify(value)
89
78
 
90
- const sameValue = (left: unknown, right: unknown): boolean => {
91
- if (Object.is(left, right)) return true
92
- if (
93
- typeof left !== 'object' ||
94
- left === null ||
95
- typeof right !== 'object' ||
96
- right === null
97
- )
98
- return false
99
- const prototype = Object.getPrototypeOf(left)
100
- if (
101
- prototype !== Object.getPrototypeOf(right) ||
102
- (prototype !== Object.prototype &&
103
- prototype !== Array.prototype &&
104
- prototype !== null)
105
- )
106
- return false
107
- if (
108
- Array.isArray(left) &&
109
- (!Array.isArray(right) || left.length !== right.length)
110
- )
111
- return false
112
- const keys = Reflect.ownKeys(left)
113
- return (
114
- keys.length === Reflect.ownKeys(right).length &&
115
- keys.every(
116
- (key) =>
117
- Object.hasOwn(right, key) &&
118
- sameValue(Reflect.get(left, key), Reflect.get(right, key)),
119
- )
120
- )
121
- }
122
-
123
79
  const restore = (current: Value, undo: Undo | undefined): Value => {
124
80
  if (undo === undefined) return current
125
81
  if (!isRecord(undo)) throw new TypeError('invalid AI recovery change')
@@ -284,25 +240,15 @@ const storeState = <S extends AIStoredView>(state: S): AIStoredState => {
284
240
  }
285
241
  const saveProjection = (
286
242
  projection: NonNullable<AIStoredView['activeProjection']>,
287
- messages: UIMessage[],
288
243
  tools: AIStoredView['tools'],
289
244
  ): Projection => {
290
- const { baseMessage, baseTools, ...fields } = projection
291
- const current = messages.find(
292
- (message) => message.id === projection.responseMessageId,
293
- )
294
- const message =
295
- baseMessage === undefined
296
- ? undefined
297
- : difference(pack(current), pack(baseMessage))
245
+ const { baseTools, ...fields } = projection
298
246
  const toolPatch =
299
247
  baseTools === undefined
300
248
  ? undefined
301
249
  : difference(pack(tools), pack(baseTools))
302
250
  return {
303
251
  fields: pack(fields),
304
- hasMessage: baseMessage !== undefined,
305
- ...(message === undefined ? {} : { message }),
306
252
  ...(toolPatch === undefined ? {} : { tools: toolPatch }),
307
253
  }
308
254
  }
@@ -310,9 +256,9 @@ const storeState = <S extends AIStoredView>(state: S): AIStoredState => {
310
256
  const projection =
311
257
  activeProjection === null
312
258
  ? null
313
- : saveProjection(activeProjection, state.messages, state.tools)
259
+ : saveProjection(activeProjection, state.tools)
314
260
  return {
315
- version: 2,
261
+ version: 3,
316
262
  fields: pack(fields),
317
263
  projection,
318
264
  values,
@@ -352,33 +298,30 @@ const loadState = <S extends AIStoredView>(stored: AIStoredState): S => {
352
298
  const state = unpack(stored.fields) as S
353
299
  const loadProjection = (
354
300
  saved: Projection,
355
- messages: Value,
356
301
  tools: Value | undefined,
357
302
  ): AIStoredView['activeProjection'] => {
358
303
  if (
359
304
  !hasKeys(saved, {
360
- required: ['fields', 'hasMessage'],
361
- optional: ['message', 'tools'],
305
+ required: ['fields'],
306
+ optional: ['tools'],
362
307
  }) ||
363
- typeof saved.hasMessage !== 'boolean' ||
364
308
  !isRecord(saved.fields) ||
365
- ('message' in saved &&
366
- (!saved.hasMessage || saved.message === undefined)) ||
367
309
  ('tools' in saved && saved.tools === undefined)
368
310
  )
369
311
  throw new TypeError('invalid AI projection recovery')
370
312
  const projection = unpack(saved.fields) as NonNullable<
371
313
  AIStoredView['activeProjection']
372
314
  >
373
- const currentMessages = unpack(messages) as UIMessage[]
374
- const index = currentMessages.findIndex(
375
- (message) => message.id === projection.responseMessageId,
376
- )
377
- if (saved.hasMessage) {
378
- const current = index === -1 ? null : (messages as Value[])[index]!
379
- projection.baseMessage = unpack(
380
- restore(current, saved.message),
381
- ) as UIMessage
315
+ if (projection.base !== undefined) {
316
+ assertMessageBase(projection.base)
317
+ const current = state.messages.find(
318
+ (message) => message.id === projection.responseMessageId,
319
+ )
320
+ if (
321
+ current !== undefined &&
322
+ restoreMessageBase({ current, base: projection.base }) === undefined
323
+ )
324
+ throw new TypeError('AI message base does not match its message')
382
325
  }
383
326
  if (tools !== undefined)
384
327
  projection.baseTools = unpack(
@@ -391,94 +334,36 @@ const loadState = <S extends AIStoredView>(stored: AIStoredState): S => {
391
334
  state.activeProjection =
392
335
  stored.projection === null
393
336
  ? null
394
- : loadProjection(stored.projection, fields['messages']!, fields['tools']!)
337
+ : loadProjection(stored.projection, fields['tools']!)
395
338
  return state
396
339
  }
397
340
 
398
- export const storedAIReducer = <D extends EventDefs, S extends AIStoredView>(
341
+ export const withAIStorage = <D extends EventDefs, S extends AIStoredView>(
399
342
  source: Reducer<D, S>,
400
- ): AIStoredReducer<D, S> => {
401
- const views = new WeakMap<AIStoredState, S>()
402
- const view = (state: AIStoredState): S => {
403
- let result = views.get(state)
404
- if (result === undefined) {
405
- result = loadState<S>(state)
406
- views.set(state, result)
407
- }
408
- return result
409
- }
410
- const defer = (next: S): AIStoredState => {
411
- let stored: AIStoredState | undefined
412
- const read = (): AIStoredState => (stored ??= storeState(next))
413
- const state: AIStoredState = {
414
- version: 2,
415
- get fields() {
416
- return read().fields
417
- },
418
- get projection() {
419
- return read().projection
420
- },
421
- get values() {
422
- return read().values
423
- },
424
- }
425
- views.set(state, next)
426
- return state
427
- }
428
- return {
429
- ...source,
430
- name: `${source.name}:records-v2`,
431
- initialState: storeState(source.initialState),
432
- [aiStateReducer]: source,
433
- view,
434
- fromView: (state) => storeState(state),
435
- [clientStateProjection]: (state) =>
436
- source[clientStateProjection]?.(view(state)) ?? view(state),
437
- stateSchema: {
438
- '~standard': {
439
- version: 1,
440
- vendor: 'a2-ai',
441
- validate(value) {
442
- try {
443
- if (
444
- !hasKeys(value, {
445
- required: ['version', 'fields', 'projection', 'values'],
446
- }) ||
447
- value['version'] !== 2 ||
448
- !Array.isArray(value['values']) ||
449
- !isRecord(value['fields']) ||
450
- (value['projection'] !== null && !isRecord(value['projection']))
451
- )
452
- return { issues: [{ message: 'invalid AI stored state' }] }
453
- const state = value as AIStoredState
454
- if (Object.keys(state.values).length !== state.values.length)
455
- return { issues: [{ message: 'invalid AI payload table' }] }
456
- for (const payload of state.values) {
457
- if (!hasKeys(payload, { required: [], optional: ['value'] }))
458
- return { issues: [{ message: 'invalid AI payload table' }] }
459
- }
460
- const decoded = loadState<S>(state)
461
- if (source.stateSchema) {
462
- const result = validateSync(
463
- source.stateSchema,
464
- decoded,
465
- 'AI view stateSchema',
466
- )
467
- if (result.issues) return result
468
- return { value: storeState(result.value) }
469
- }
470
- return { value: state }
471
- } catch {
472
- return { issues: [{ message: 'invalid AI stored state' }] }
473
- }
474
- },
343
+ ): Reducer<D, S> =>
344
+ withReducerRuntime({
345
+ reducer: source,
346
+ storage: {
347
+ encode: (state: S) => storeState(state),
348
+ decode(value: unknown): S {
349
+ if (
350
+ !hasKeys(value, {
351
+ required: ['version', 'fields', 'projection', 'values'],
352
+ }) ||
353
+ value['version'] !== 3 ||
354
+ !Array.isArray(value['values']) ||
355
+ !isRecord(value['fields']) ||
356
+ (value['projection'] !== null && !isRecord(value['projection']))
357
+ )
358
+ throw new TypeError('invalid AI stored state')
359
+ const state = value as AIStoredState
360
+ if (Object.keys(state.values).length !== state.values.length)
361
+ throw new TypeError('invalid AI payload table')
362
+ for (const payload of state.values) {
363
+ if (!hasKeys(payload, { required: [], optional: ['value'] }))
364
+ throw new TypeError('invalid AI payload table')
365
+ }
366
+ return loadState<S>(state)
475
367
  },
476
368
  },
477
- fold(state, event) {
478
- const current = view(state)
479
- const next = source.fold(current, event)
480
- if (next === current) return state
481
- return defer(next)
482
- },
483
- }
484
- }
369
+ })