experimental-a2 0.16.1 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (406) hide show
  1. package/CHANGELOG.md +373 -0
  2. package/README.md +1 -1
  3. package/dist/{actor-ohPC-81x.d.ts → actor-Bjce9_5U.d.ts} +21 -11
  4. package/dist/actor-Bjce9_5U.d.ts.map +1 -0
  5. package/dist/actor-client.d.ts +2 -5
  6. package/dist/actor-client.d.ts.map +1 -1
  7. package/dist/actor-client.js +13 -4
  8. package/dist/actor-client.js.map +1 -1
  9. package/dist/actor-react.d.ts +5 -3
  10. package/dist/actor-react.d.ts.map +1 -1
  11. package/dist/actor-react.js +20 -10
  12. package/dist/actor-react.js.map +1 -1
  13. package/dist/{actor-shared-B5tJfzt-.js → actor-shared-t7w8xIKP.js} +12 -7
  14. package/dist/actor-shared-t7w8xIKP.js.map +1 -0
  15. package/dist/actor.d.ts +1 -1
  16. package/dist/actor.js +42 -23
  17. package/dist/actor.js.map +1 -1
  18. package/dist/{ai-DgOBltJ_.js → ai-BpOGm-zB.js} +621 -216
  19. package/dist/ai-BpOGm-zB.js.map +1 -0
  20. package/dist/{ai-CrEf6p_W.d.ts → ai-DrUaMyRt.d.ts} +114 -100
  21. package/dist/ai-DrUaMyRt.d.ts.map +1 -0
  22. package/dist/ai-client.d.ts +27 -0
  23. package/dist/ai-client.d.ts.map +1 -0
  24. package/dist/ai-client.js +29 -0
  25. package/dist/ai-client.js.map +1 -0
  26. package/dist/ai-fx.browser.d.ts +1 -0
  27. package/dist/ai-fx.browser.js +6 -0
  28. package/dist/ai-fx.browser.js.map +1 -0
  29. package/dist/ai-fx.d.ts +10 -0
  30. package/dist/ai-fx.d.ts.map +1 -0
  31. package/dist/ai-fx.js +634 -0
  32. package/dist/ai-fx.js.map +1 -0
  33. package/dist/ai-harness-BuLh_EYi.js +273 -0
  34. package/dist/ai-harness-BuLh_EYi.js.map +1 -0
  35. package/dist/ai-harness-DvsPeJeB.d.ts +26 -0
  36. package/dist/ai-harness-DvsPeJeB.d.ts.map +1 -0
  37. package/dist/ai-pi.browser.d.ts +1 -0
  38. package/dist/ai-pi.browser.js +6 -0
  39. package/dist/ai-pi.browser.js.map +1 -0
  40. package/dist/ai-pi.d.ts +17 -0
  41. package/dist/ai-pi.d.ts.map +1 -0
  42. package/dist/ai-pi.js +904 -0
  43. package/dist/ai-pi.js.map +1 -0
  44. package/dist/ai-react.d.ts +96 -0
  45. package/dist/ai-react.d.ts.map +1 -0
  46. package/dist/ai-react.js +142 -0
  47. package/dist/ai-react.js.map +1 -0
  48. package/dist/ai-server.browser.js +2 -2
  49. package/dist/ai-server.browser.js.map +1 -1
  50. package/dist/ai-server.d.ts +109 -43
  51. package/dist/ai-server.d.ts.map +1 -1
  52. package/dist/ai-server.js +526 -283
  53. package/dist/ai-server.js.map +1 -1
  54. package/dist/ai.d.ts +2 -2
  55. package/dist/ai.js +2 -2
  56. package/dist/bounded-cache-CPHWF-8h.js +41 -0
  57. package/dist/bounded-cache-CPHWF-8h.js.map +1 -0
  58. package/dist/{client-BYzHjkwU.js → client-BReKqwZC.js} +447 -80
  59. package/dist/client-BReKqwZC.js.map +1 -0
  60. package/dist/{client-CzyacQpJ.d.ts → client-h_DKFAjj.d.ts} +14 -13
  61. package/dist/client-h_DKFAjj.d.ts.map +1 -0
  62. package/dist/client.d.ts +4 -2
  63. package/dist/client.js +1 -1
  64. package/dist/{contract-CKRg_E4q.js → contract-BYKASlCV.js} +12 -21
  65. package/dist/contract-BYKASlCV.js.map +1 -0
  66. package/dist/{idempotent-replay-DVOlyYbx.js → idempotent-replay-JdgMQRRm.js} +2 -2
  67. package/dist/{idempotent-replay-DVOlyYbx.js.map → idempotent-replay-JdgMQRRm.js.map} +1 -1
  68. package/dist/index.d.ts +6 -5
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +4 -3
  71. package/dist/{errors-DvhSXnxk.d.ts → initial-snapshot-Bx-c0pDP.d.ts} +7 -3
  72. package/dist/initial-snapshot-Bx-c0pDP.d.ts.map +1 -0
  73. package/dist/{internal-Dq2qYxou.js → internal-B2T3U4Rq.js} +29 -2
  74. package/dist/internal-B2T3U4Rq.js.map +1 -0
  75. package/dist/otel.d.ts +1 -1
  76. package/dist/react-BBXgHWFc.d.ts +86 -0
  77. package/dist/react-BBXgHWFc.d.ts.map +1 -0
  78. package/dist/react.d.ts +2 -122
  79. package/dist/react.js +107 -100
  80. package/dist/react.js.map +1 -1
  81. package/dist/reducer-BAXc8JyF.js +87 -0
  82. package/dist/reducer-BAXc8JyF.js.map +1 -0
  83. package/dist/{reducer-otzHjuJj.d.ts → reducer-CqA59Q6U.d.ts} +46 -30
  84. package/dist/reducer-CqA59Q6U.d.ts.map +1 -0
  85. package/dist/reducer-runtime-DQwz_J59.js +28 -0
  86. package/dist/reducer-runtime-DQwz_J59.js.map +1 -0
  87. package/dist/scheduler-qstash.d.ts +3 -3
  88. package/dist/scheduler-qstash.d.ts.map +1 -1
  89. package/dist/scheduler-qstash.js +33 -10
  90. package/dist/scheduler-qstash.js.map +1 -1
  91. package/dist/{scheduler-task-BpzhPnRS.js → scheduler-task-X1SToDjP.js} +18 -3
  92. package/dist/scheduler-task-X1SToDjP.js.map +1 -0
  93. package/dist/scheduler-vercel.d.ts +4 -9
  94. package/dist/scheduler-vercel.d.ts.map +1 -1
  95. package/dist/scheduler-vercel.js +25 -44
  96. package/dist/scheduler-vercel.js.map +1 -1
  97. package/dist/{server-BD5ckxZb.js → server-Dw_k_7kO.js} +904 -219
  98. package/dist/server-Dw_k_7kO.js.map +1 -0
  99. package/dist/{server-DbD7IJVK.d.ts → server-En7cbi3v.d.ts} +116 -26
  100. package/dist/server-En7cbi3v.d.ts.map +1 -0
  101. package/dist/server.d.ts +4 -4
  102. package/dist/server.js +2 -2
  103. package/dist/session-clear-Bo-XTFEy.d.ts +12 -0
  104. package/dist/session-clear-Bo-XTFEy.d.ts.map +1 -0
  105. package/dist/{errors-DCk6ch5n.js → session-clear-DGI4jTQ5.js} +17 -2
  106. package/dist/session-clear-DGI4jTQ5.js.map +1 -0
  107. package/dist/{store-DyZM6fS5.d.ts → store-Djdv0bnZ.d.ts} +73 -3
  108. package/dist/store-Djdv0bnZ.d.ts.map +1 -0
  109. package/dist/store-N8PXxDAS.js.map +1 -1
  110. package/dist/store-memory.d.ts +1 -1
  111. package/dist/store-memory.d.ts.map +1 -1
  112. package/dist/store-memory.js +205 -66
  113. package/dist/store-memory.js.map +1 -1
  114. package/dist/{store-polling-CmxUbV93.js → store-polling-B5FcwJfo.js} +21 -4
  115. package/dist/store-polling-B5FcwJfo.js.map +1 -0
  116. package/dist/store-postgres.d.ts +1 -1
  117. package/dist/store-postgres.d.ts.map +1 -1
  118. package/dist/store-postgres.js +450 -108
  119. package/dist/store-postgres.js.map +1 -1
  120. package/dist/{store-presence-polling-C7-XZyW9.js → store-presence-polling-BAwS8RWn.js} +3 -3
  121. package/dist/{store-presence-polling-C7-XZyW9.js.map → store-presence-polling-BAwS8RWn.js.map} +1 -1
  122. package/dist/store-redis-http.d.ts +1 -1
  123. package/dist/store-redis-http.d.ts.map +1 -1
  124. package/dist/store-redis-http.js +293 -11
  125. package/dist/store-redis-http.js.map +1 -1
  126. package/dist/store-redis-notify-BRITti_o.js +2493 -0
  127. package/dist/store-redis-notify-BRITti_o.js.map +1 -0
  128. package/dist/store-redis.d.ts +1 -1
  129. package/dist/store-redis.d.ts.map +1 -1
  130. package/dist/store-redis.js +11 -11
  131. package/dist/store-redis.js.map +1 -1
  132. package/dist/store-sqlite.d.ts +1 -1
  133. package/dist/store-sqlite.d.ts.map +1 -1
  134. package/dist/store-sqlite.js +197 -33
  135. package/dist/store-sqlite.js.map +1 -1
  136. package/dist/stream-buffer-Cx7AAPGq.js +23 -0
  137. package/dist/stream-buffer-Cx7AAPGq.js.map +1 -0
  138. package/dist/{telemetry-B5jzpy6y.d.ts → telemetry-Cyfqo6Qt.d.ts} +2 -2
  139. package/dist/telemetry-Cyfqo6Qt.d.ts.map +1 -0
  140. package/dist/{wire-BO5wWCb1.js → wire-rSUglYo8.js} +132 -10
  141. package/dist/wire-rSUglYo8.js.map +1 -0
  142. package/docs/01-quickstart.mdx +50 -84
  143. package/docs/{guides/07-examples.mdx → 02-examples.mdx} +10 -20
  144. package/docs/actors/01-introduction.mdx +48 -82
  145. package/docs/actors/02-concurrency.mdx +43 -70
  146. package/docs/actors/03-timers.mdx +28 -42
  147. package/docs/actors/04-routes.mdx +110 -137
  148. package/docs/actors/meta.ts +1 -1
  149. package/docs/agents/01-introduction.mdx +257 -0
  150. package/docs/agents/02-tools.mdx +247 -0
  151. package/docs/agents/03-conversations.mdx +538 -0
  152. package/docs/agents/04-harnesses.mdx +136 -0
  153. package/docs/agents/meta.ts +1 -0
  154. package/docs/client/01-react.mdx +294 -0
  155. package/docs/client/02-transports.mdx +86 -0
  156. package/docs/client/03-presence.mdx +224 -0
  157. package/docs/client/meta.ts +1 -0
  158. package/docs/contract/01-events.mdx +57 -0
  159. package/docs/contract/02-state.mdx +142 -0
  160. package/docs/contract/03-sessions.mdx +175 -0
  161. package/docs/index.mdx +112 -156
  162. package/docs/reference/01-core.mdx +139 -0
  163. package/docs/reference/02-server.mdx +855 -0
  164. package/docs/reference/03-client.mdx +301 -0
  165. package/docs/reference/04-ai.mdx +722 -0
  166. package/docs/reference/05-adapters.mdx +351 -0
  167. package/docs/reference/06-errors.mdx +138 -0
  168. package/docs/reference/meta.ts +1 -1
  169. package/docs/server/01-handlers.mdx +200 -0
  170. package/docs/server/02-timers.mdx +130 -0
  171. package/docs/server/03-cancellation.mdx +141 -0
  172. package/docs/server/04-routes.mdx +243 -0
  173. package/docs/server/05-production.mdx +322 -0
  174. package/docs/server/06-guarantees.mdx +123 -0
  175. package/examples/playground/DEPLOY.md +53 -7
  176. package/examples/playground/app/a0/[appId]/loading.tsx +16 -0
  177. package/examples/playground/app/a0/[appId]/page.tsx +53 -0
  178. package/examples/playground/app/a0/a0.css +818 -0
  179. package/examples/playground/app/a0/app-list.tsx +218 -0
  180. package/examples/playground/app/a0/app-view.tsx +63 -0
  181. package/examples/playground/app/a0/apps/[appId]/preview/route.ts +27 -0
  182. package/examples/playground/app/a0/apps/[appId]/route.ts +28 -0
  183. package/examples/playground/app/a0/apps.test.ts +128 -0
  184. package/examples/playground/app/a0/apps.ts +102 -0
  185. package/examples/playground/app/a0/chat.tsx +410 -0
  186. package/examples/playground/app/a0/columns.tsx +109 -0
  187. package/examples/playground/app/a0/context-menu.tsx +116 -0
  188. package/examples/playground/app/a0/cookies.ts +46 -0
  189. package/examples/playground/app/a0/crash-on-request.ts +72 -0
  190. package/examples/playground/app/a0/crashes/route.ts +3 -0
  191. package/examples/playground/app/a0/crashes.test.ts +33 -0
  192. package/examples/playground/app/a0/crashes.ts +28 -0
  193. package/examples/playground/app/a0/devtools.tsx +164 -0
  194. package/examples/playground/app/a0/events/route.ts +6 -0
  195. package/examples/playground/app/a0/flags.ts +30 -0
  196. package/examples/playground/app/a0/fx/events/route.ts +9 -0
  197. package/examples/playground/app/a0/fx/scheduler/route.ts +7 -0
  198. package/examples/playground/app/a0/fx/short/events/route.ts +11 -0
  199. package/examples/playground/app/a0/layout.tsx +38 -0
  200. package/examples/playground/app/a0/list/route.ts +10 -0
  201. package/examples/playground/app/a0/model.test.ts +73 -0
  202. package/examples/playground/app/a0/model.ts +95 -0
  203. package/examples/playground/app/a0/no-photon.cjs +4 -0
  204. package/examples/playground/app/a0/no-quickjs.cjs +3 -0
  205. package/examples/playground/app/a0/page.tsx +14 -0
  206. package/examples/playground/app/a0/pi/events/route.ts +9 -0
  207. package/examples/playground/app/a0/pi/scheduler/route.ts +7 -0
  208. package/examples/playground/app/a0/pi/short/events/route.ts +11 -0
  209. package/examples/playground/app/a0/preview-pane.tsx +145 -0
  210. package/examples/playground/app/a0/preview.test.ts +53 -0
  211. package/examples/playground/app/a0/preview.ts +26 -0
  212. package/examples/playground/app/a0/sandbox-log.ts +75 -0
  213. package/examples/playground/app/a0/sandbox-session.ts +152 -0
  214. package/examples/playground/app/a0/sandbox.ts +191 -0
  215. package/examples/playground/app/a0/sandboxes/scheduler/route.ts +6 -0
  216. package/examples/playground/app/a0/scheduler/route.ts +7 -0
  217. package/examples/playground/app/a0/server.ts +213 -0
  218. package/examples/playground/app/a0/session.ts +59 -0
  219. package/examples/playground/app/a0/shell.tsx +158 -0
  220. package/examples/playground/app/a0/short/events/route.ts +9 -0
  221. package/examples/playground/app/a0/splitter.tsx +120 -0
  222. package/examples/playground/app/agent/[agentId]/agent-client.tsx +144 -174
  223. package/examples/playground/app/agent/[agentId]/command-receipt.test.tsx +65 -0
  224. package/examples/playground/app/agent/[agentId]/command-receipt.tsx +27 -0
  225. package/examples/playground/app/agent/[agentId]/compaction-event.tsx +25 -24
  226. package/examples/playground/app/agent/[agentId]/compaction-panel.test.tsx +119 -0
  227. package/examples/playground/app/agent/[agentId]/compaction-panel.tsx +208 -165
  228. package/examples/playground/app/agent/[agentId]/page.tsx +2 -16
  229. package/examples/playground/app/agent/events/route.ts +5 -2
  230. package/examples/playground/app/agent/ingress.test.ts +101 -0
  231. package/examples/playground/app/agent/model-input.test.tsx +58 -0
  232. package/examples/playground/app/agent/model-input.tsx +115 -0
  233. package/examples/playground/app/agent/model.test.ts +73 -0
  234. package/examples/playground/app/agent/model.ts +58 -26
  235. package/examples/playground/app/agent/new-agent-session.tsx +37 -19
  236. package/examples/playground/app/agent/page.tsx +4 -4
  237. package/examples/playground/app/agent/server.ts +19 -15
  238. package/examples/playground/app/agent/session.ts +5 -6
  239. package/examples/playground/app/agent/telemetry.test.ts +189 -0
  240. package/examples/playground/app/agent/telemetry.ts +30 -0
  241. package/examples/playground/app/agent/trace.test.ts +23 -0
  242. package/examples/playground/app/agent/trace.ts +18 -0
  243. package/examples/playground/app/canvas/[canvasId]/canvas-client.tsx +12 -12
  244. package/examples/playground/app/canvas/[canvasId]/page.tsx +1 -2
  245. package/examples/playground/app/canvas/[canvasId]/session.ts +2 -2
  246. package/examples/playground/app/canvas/model.ts +53 -53
  247. package/examples/playground/app/chat/[chatId]/agent-state.test.ts +176 -0
  248. package/examples/playground/app/chat/[chatId]/agent-stream-drawer.tsx +16 -1
  249. package/examples/playground/app/chat/[chatId]/chat-client.tsx +55 -77
  250. package/examples/playground/app/chat/[chatId]/chat-view.ts +37 -3
  251. package/examples/playground/app/chat/[chatId]/page.tsx +7 -15
  252. package/examples/playground/app/chat/[chatId]/session.ts +2 -19
  253. package/examples/playground/app/chat/agent/route.ts +9 -0
  254. package/examples/playground/app/chat/model.test.ts +46 -119
  255. package/examples/playground/app/chat/model.ts +82 -128
  256. package/examples/playground/app/chat/scheduler/route.ts +2 -2
  257. package/examples/playground/app/chat/server.test.ts +112 -0
  258. package/examples/playground/app/chat/server.ts +113 -117
  259. package/examples/playground/app/components/activity-feed.tsx +30 -1
  260. package/examples/playground/app/components/activity-history.test.tsx +35 -0
  261. package/examples/playground/app/components/demo-icons.tsx +89 -0
  262. package/examples/playground/app/components/demos.tsx +33 -0
  263. package/examples/playground/app/components/fast-link.test.tsx +58 -0
  264. package/examples/playground/app/components/fast-link.tsx +55 -0
  265. package/examples/playground/app/components/site-header.tsx +96 -0
  266. package/examples/playground/app/counter/counter-client.tsx +3 -3
  267. package/examples/playground/app/counter/model.test.ts +3 -3
  268. package/examples/playground/app/counter/model.ts +11 -21
  269. package/examples/playground/app/counter/page.tsx +2 -15
  270. package/examples/playground/app/counter/session.ts +2 -2
  271. package/examples/playground/app/documents/[documentId]/code-editor.tsx +12 -1
  272. package/examples/playground/app/documents/[documentId]/document-client.tsx +10 -11
  273. package/examples/playground/app/documents/[documentId]/page.tsx +1 -2
  274. package/examples/playground/app/documents/[documentId]/session.ts +2 -2
  275. package/examples/playground/app/documents/model.ts +27 -27
  276. package/examples/playground/app/globals.css +420 -207
  277. package/examples/playground/app/initial-state.test.tsx +54 -0
  278. package/examples/playground/app/layout.tsx +7 -23
  279. package/examples/playground/app/orders/[orderId]/page.tsx +1 -2
  280. package/examples/playground/app/orders/[orderId]/session.ts +2 -2
  281. package/examples/playground/app/orders/model.ts +49 -49
  282. package/examples/playground/app/page.tsx +13 -102
  283. package/examples/playground/app/recovery/[recoveryId]/page.tsx +1 -2
  284. package/examples/playground/app/recovery/[recoveryId]/session.ts +2 -2
  285. package/examples/playground/app/recovery/model.ts +30 -30
  286. package/examples/playground/instrumentation.ts +5 -0
  287. package/examples/playground/next.config.ts +17 -5
  288. package/examples/playground/package.json +13 -2
  289. package/examples/playground/scripts/a0-template.ts +77 -0
  290. package/examples/playground/vercel.json +36 -0
  291. package/package.json +29 -3
  292. package/src/actor-client.ts +16 -4
  293. package/src/actor-react.ts +25 -11
  294. package/src/actor-shared.ts +21 -6
  295. package/src/actor.ts +76 -31
  296. package/src/ai-agent-actions.ts +219 -0
  297. package/src/ai-agent.ts +2517 -0
  298. package/src/ai-app-state.ts +91 -0
  299. package/src/ai-client-state.ts +6 -3
  300. package/src/ai-client.ts +62 -0
  301. package/src/ai-context-schema.ts +25 -5
  302. package/src/ai-context.ts +90 -40
  303. package/src/ai-control-server.ts +92 -72
  304. package/src/ai-coordinator.ts +23 -22
  305. package/src/ai-fx.browser.ts +5 -0
  306. package/src/ai-fx.ts +833 -0
  307. package/src/ai-harness.ts +518 -0
  308. package/src/ai-message-base.ts +209 -0
  309. package/src/ai-message-projection.ts +262 -64
  310. package/src/ai-model-metadata.ts +11 -6
  311. package/src/ai-pi.browser.ts +5 -0
  312. package/src/ai-pi.ts +1263 -0
  313. package/src/ai-progress-batches.ts +26 -0
  314. package/src/ai-projector.ts +95 -14
  315. package/src/ai-react.ts +146 -0
  316. package/src/ai-sdk-step.ts +2 -2
  317. package/src/ai-server.browser.ts +2 -2
  318. package/src/ai-server.ts +23 -1965
  319. package/src/ai-stored-state.ts +48 -163
  320. package/src/ai.ts +339 -155
  321. package/src/bootstrap-stream.ts +72 -0
  322. package/src/bounded-cache.ts +55 -0
  323. package/src/client-render-session.ts +49 -0
  324. package/src/client.ts +688 -151
  325. package/src/contract.ts +92 -42
  326. package/src/deterministic-id.ts +37 -0
  327. package/src/equal-json.ts +32 -0
  328. package/src/errors.ts +13 -1
  329. package/src/index.ts +5 -1
  330. package/src/initial-snapshot.ts +39 -0
  331. package/src/internal.ts +52 -0
  332. package/src/libfx.d.ts +34 -0
  333. package/src/postgres-notification-scope.ts +14 -3
  334. package/src/react.ts +171 -206
  335. package/src/redis-http-reply.ts +256 -0
  336. package/src/redis-http-stream.ts +73 -0
  337. package/src/redis-http-subscriptions.ts +13 -5
  338. package/src/reducer-runtime.ts +52 -0
  339. package/src/reducer.ts +93 -11
  340. package/src/scheduler-qstash.ts +49 -20
  341. package/src/scheduler-task.ts +34 -2
  342. package/src/scheduler-vercel.ts +32 -84
  343. package/src/server-fetch.ts +139 -35
  344. package/src/server.ts +1133 -255
  345. package/src/session-clear.ts +23 -0
  346. package/src/session-socket.ts +154 -49
  347. package/src/sse.ts +71 -4
  348. package/src/store-memory.ts +252 -75
  349. package/src/store-polling.ts +18 -4
  350. package/src/store-postgres.ts +541 -114
  351. package/src/store-redis-core.ts +1450 -493
  352. package/src/store-redis-http.ts +18 -2
  353. package/src/store-redis-notify.ts +262 -85
  354. package/src/store-redis.ts +19 -9
  355. package/src/store-sqlite.ts +319 -45
  356. package/src/store.ts +79 -1
  357. package/src/stream-buffer.ts +43 -0
  358. package/src/telemetry.ts +14 -1
  359. package/src/wire.ts +241 -13
  360. package/dist/actor-ohPC-81x.d.ts.map +0 -1
  361. package/dist/actor-shared-B5tJfzt-.js.map +0 -1
  362. package/dist/ai-CrEf6p_W.d.ts.map +0 -1
  363. package/dist/ai-DgOBltJ_.js.map +0 -1
  364. package/dist/client-BYzHjkwU.js.map +0 -1
  365. package/dist/client-CzyacQpJ.d.ts.map +0 -1
  366. package/dist/contract-CKRg_E4q.js.map +0 -1
  367. package/dist/errors-DCk6ch5n.js.map +0 -1
  368. package/dist/errors-DvhSXnxk.d.ts.map +0 -1
  369. package/dist/internal-Dq2qYxou.js.map +0 -1
  370. package/dist/react.d.ts.map +0 -1
  371. package/dist/reducer-DEMjEY_O.js +0 -29
  372. package/dist/reducer-DEMjEY_O.js.map +0 -1
  373. package/dist/reducer-otzHjuJj.d.ts.map +0 -1
  374. package/dist/scheduler-task-BpzhPnRS.js.map +0 -1
  375. package/dist/server-BD5ckxZb.js.map +0 -1
  376. package/dist/server-DbD7IJVK.d.ts.map +0 -1
  377. package/dist/store-DyZM6fS5.d.ts.map +0 -1
  378. package/dist/store-polling-CmxUbV93.js.map +0 -1
  379. package/dist/store-redis-notify-BVLUfI3j.js +0 -1583
  380. package/dist/store-redis-notify-BVLUfI3j.js.map +0 -1
  381. package/dist/telemetry-B5jzpy6y.d.ts.map +0 -1
  382. package/dist/validate-XKT4FSNn.js +0 -30
  383. package/dist/validate-XKT4FSNn.js.map +0 -1
  384. package/dist/wire-BO5wWCb1.js.map +0 -1
  385. package/docs/concepts/01-contracts.mdx +0 -142
  386. package/docs/concepts/02-handlers.mdx +0 -284
  387. package/docs/concepts/03-durability.mdx +0 -314
  388. package/docs/concepts/04-state.mdx +0 -209
  389. package/docs/guides/01-timers.mdx +0 -185
  390. package/docs/guides/02-cancellation.mdx +0 -161
  391. package/docs/guides/03-react.mdx +0 -384
  392. package/docs/guides/04-local-first.mdx +0 -88
  393. package/docs/guides/05-production.mdx +0 -521
  394. package/docs/guides/06-ai-agents.mdx +0 -1318
  395. package/docs/guides/08-application-data.mdx +0 -113
  396. package/docs/guides/09-presence.mdx +0 -266
  397. package/docs/guides/10-transports.mdx +0 -221
  398. package/docs/reference/01-api.mdx +0 -1896
  399. package/docs/reference/02-errors.mdx +0 -149
  400. package/examples/playground/app/agent/[agentId]/compaction/route.ts +0 -14
  401. package/examples/playground/app/agent/compaction-settings.test.ts +0 -144
  402. package/examples/playground/app/agent/compaction-settings.ts +0 -49
  403. package/examples/playground/app/agent/compaction-timeline.test.ts +0 -377
  404. package/examples/playground/app/agent/compaction-timeline.ts +0 -208
  405. /package/docs/{concepts → contract}/meta.ts +0 -0
  406. /package/docs/{guides → server}/meta.ts +0 -0
@@ -0,0 +1,410 @@
1
+ 'use client'
2
+ import type { ReactNode } from 'react'
3
+ import { useEffect, useRef, useState } from 'react'
4
+ import { useRouter, useSearchParams } from 'next/navigation'
5
+ import { Asterisk, LoaderCircle, Zap } from 'lucide-react'
6
+ import { inputs } from 'experimental-a2/ai'
7
+ import { queryString } from './flags'
8
+ import {
9
+ type AppMessage,
10
+ type Harness,
11
+ harnessLabels,
12
+ harnessSchema,
13
+ type ModelChoice,
14
+ reasoningSchema,
15
+ toolLabels,
16
+ writingLabels,
17
+ } from './model'
18
+ import { useSession } from './session'
19
+
20
+ type Part = AppMessage['parts'][number]
21
+
22
+ const suggestions = [
23
+ 'A pomodoro timer with start, pause and reset',
24
+ 'A landing page for a coffee shop called Ember',
25
+ 'A tip calculator that splits the bill',
26
+ ]
27
+
28
+ /** The coding agent's conversation: its words, and one line per tool step. */
29
+ export function Chat({
30
+ harness,
31
+ onPrompt,
32
+ autoFocus,
33
+ }: {
34
+ harness: Harness
35
+ onPrompt: (prompt: string) => void
36
+ autoFocus: boolean
37
+ }): ReactNode {
38
+ const { state, push } = useSession()
39
+ const router = useRouter()
40
+ const params = useSearchParams()
41
+ // Each harness's agent has its own log, so an app picks one before its
42
+ // first message: the URL names it, and the page follows that agent.
43
+ const pickHarness = (next: Harness): void => {
44
+ const query = new URLSearchParams(params)
45
+ if (next === 'ai-sdk') query.delete('harness')
46
+ else query.set('harness', next)
47
+ router.replace(`${location.pathname}${queryString(query)}`)
48
+ }
49
+ const [error, setError] = useState<string | null>(null)
50
+ // A change here applies when the next message goes out with it, so typing a
51
+ // model id doesn't record a choice per keystroke. Until then, the composer
52
+ // shows the app's choice, which every client sees.
53
+ const [chosen, setChosen] = useState<ModelChoice | null>(null)
54
+ const settings = chosen ?? state.modelChoice
55
+
56
+ const last = state.messages.at(-1)
57
+ // A send is optimistic, and so is the turn it starts, so this holds from
58
+ // the moment it's pushed rather than once the server accepts it.
59
+ const working =
60
+ state.starting || state.status === 'generating' || state.active !== null
61
+ const waiting =
62
+ working &&
63
+ (last?.role !== 'assistant' || !last.parts.some((part) => isVisible(part)))
64
+
65
+ const send = async (text: string): Promise<void> => {
66
+ setError(null)
67
+ onPrompt(text)
68
+ try {
69
+ await push(
70
+ ...(chosen === null
71
+ ? []
72
+ : [
73
+ {
74
+ type: 'model.chosen' as const,
75
+ payload: { ...chosen, model: chosen.model.trim() },
76
+ },
77
+ ]),
78
+ ...inputs.message({
79
+ id: crypto.randomUUID(),
80
+ role: 'user',
81
+ parts: [{ type: 'text', text }],
82
+ }),
83
+ )
84
+ setChosen(null)
85
+ } catch (cause) {
86
+ setError(cause instanceof Error ? cause.message : String(cause))
87
+ throw cause
88
+ }
89
+ }
90
+ const stop = (): void => {
91
+ if (!state.active) return
92
+ push(...inputs.stop({ turnId: state.active.turnId })).catch((cause) =>
93
+ setError(cause instanceof Error ? cause.message : String(cause)),
94
+ )
95
+ }
96
+
97
+ return (
98
+ <>
99
+ {/* Reversed, so it opens at the newest message and stays there. */}
100
+ <div className="a0-transcript-scroll">
101
+ <ol className="a0-transcript" aria-label="Conversation">
102
+ {!state.messages.length && (
103
+ <li className="a0-suggestions">
104
+ {suggestions.map((suggestion) => (
105
+ <button
106
+ key={suggestion}
107
+ type="button"
108
+ className="a0-suggestion"
109
+ disabled={!settings.model.trim()}
110
+ onClick={() => void send(suggestion).catch(() => {})}
111
+ >
112
+ <Asterisk size={12} aria-hidden />
113
+ {suggestion}
114
+ </button>
115
+ ))}
116
+ </li>
117
+ )}
118
+ {state.messages.flatMap((message) =>
119
+ message.parts.map((part, index) => (
120
+ <MessagePart
121
+ key={`${message.id}:${index}`}
122
+ message={message}
123
+ part={part}
124
+ />
125
+ )),
126
+ )}
127
+ {waiting && (
128
+ <li className="a0-step">
129
+ <LoaderCircle size={12} className="a0-spin" aria-hidden />
130
+ Thinking
131
+ </li>
132
+ )}
133
+ {state.error && (
134
+ <li role="alert" className="a0-error">
135
+ {state.error}
136
+ </li>
137
+ )}
138
+ </ol>
139
+ </div>
140
+ {error && (
141
+ <p role="alert" className="a0-error a0-send-error">
142
+ {error}
143
+ </p>
144
+ )}
145
+ <Composer
146
+ onSend={send}
147
+ working={working}
148
+ // A turn can be stopped once it's accepted and has an id.
149
+ onStop={state.active ? stop : null}
150
+ queued={state.inbox.items.length}
151
+ settings={settings}
152
+ onSettings={setChosen}
153
+ harness={harness}
154
+ onHarness={state.messages.length ? null : pickHarness}
155
+ autoFocus={autoFocus}
156
+ />
157
+ </>
158
+ )
159
+ }
160
+
161
+ const isVisible = (part: Part): boolean =>
162
+ ((part.type === 'text' || part.type === 'reasoning') && !!part.text.trim()) ||
163
+ part.type.startsWith('tool-')
164
+
165
+ function MessagePart({
166
+ message,
167
+ part,
168
+ }: {
169
+ message: AppMessage
170
+ part: Part
171
+ }): ReactNode {
172
+ if (part.type === 'text' && part.text.trim())
173
+ return (
174
+ <li className="a0-text" data-role={message.role}>
175
+ {part.text}
176
+ </li>
177
+ )
178
+ if (part.type === 'reasoning' && part.text.trim()) {
179
+ const thinking = part.state === 'streaming'
180
+ return (
181
+ <li className="a0-reasoning">
182
+ {/* Open while the model thinks, folded once it's done. */}
183
+ <details open={thinking}>
184
+ <summary>
185
+ {thinking ? (
186
+ <LoaderCircle size={12} className="a0-spin" aria-hidden />
187
+ ) : (
188
+ <Asterisk size={12} aria-hidden />
189
+ )}
190
+ {thinking ? 'Thinking' : 'Thought'}
191
+ </summary>
192
+ <p>{part.text}</p>
193
+ </details>
194
+ </li>
195
+ )
196
+ }
197
+ if (!part.type.startsWith('tool-') || !('state' in part)) return null
198
+ const name = part.type.slice('tool-'.length)
199
+ const done = part.state === 'output-available'
200
+ const failed = part.state === 'output-error'
201
+ // A command, or the file one of Pi's tools works on.
202
+ const input = (part as { input?: { command?: unknown; path?: unknown } })
203
+ .input
204
+ const detail = input?.command ?? input?.path
205
+ const took = (part as { output?: { durationMs?: unknown } }).output
206
+ ?.durationMs
207
+ const label =
208
+ (part.state === 'input-streaming' && writingLabels[name]) ||
209
+ toolLabels[name] ||
210
+ name
211
+ return (
212
+ <li className="a0-step" data-tool={name} data-failed={failed || undefined}>
213
+ {done || failed ? (
214
+ <Asterisk size={12} className="a0-step-done" aria-hidden />
215
+ ) : (
216
+ <LoaderCircle size={12} className="a0-spin" aria-hidden />
217
+ )}
218
+ <span className="a0-step-label">{label}</span>
219
+ {typeof detail === 'string' && <code>{detail}</code>}
220
+ {done && typeof took === 'number' && (
221
+ <span className="a0-step-took">{duration(took)}</span>
222
+ )}
223
+ </li>
224
+ )
225
+ }
226
+
227
+ const duration = (ms: number): string =>
228
+ ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(1)}s`
229
+
230
+ const reasoningLabel = (level: ModelChoice['reasoning']): string =>
231
+ level === 'provider-default'
232
+ ? 'default'
233
+ : level === 'xhigh'
234
+ ? 'extra high'
235
+ : level
236
+
237
+ /** The message box: a dotted frame, then the model settings it sends with. */
238
+ function Composer({
239
+ onSend,
240
+ working,
241
+ onStop,
242
+ queued,
243
+ settings,
244
+ onSettings,
245
+ harness,
246
+ onHarness,
247
+ autoFocus,
248
+ }: {
249
+ onSend: (text: string) => Promise<void>
250
+ working: boolean
251
+ onStop: (() => void) | null
252
+ queued: number
253
+ settings: ModelChoice
254
+ onSettings: (settings: ModelChoice) => void
255
+ harness: Harness
256
+ /** Until the app's first message; it keeps its harness after that. */
257
+ onHarness: ((harness: Harness) => void) | null
258
+ autoFocus: boolean
259
+ }): ReactNode {
260
+ const [text, setText] = useState('')
261
+ const input = useRef<HTMLTextAreaElement>(null)
262
+ // Typing anywhere else starts a message. Focus moves on keydown, so the
263
+ // key itself lands in the box.
264
+ useEffect(() => {
265
+ const type = (event: KeyboardEvent): void => {
266
+ const { target } = event
267
+ if (
268
+ event.defaultPrevented ||
269
+ event.isComposing ||
270
+ event.metaKey ||
271
+ event.ctrlKey ||
272
+ event.altKey ||
273
+ event.key.length !== 1 ||
274
+ (event.key === ' ' && target !== document.body) ||
275
+ (target instanceof HTMLElement &&
276
+ (target.isContentEditable ||
277
+ target.closest('input, textarea, select')))
278
+ )
279
+ return
280
+ input.current?.focus()
281
+ }
282
+ document.addEventListener('keydown', type)
283
+ return () => document.removeEventListener('keydown', type)
284
+ }, [])
285
+ // Grows with its text, up to a point, then scrolls.
286
+ useEffect(() => {
287
+ const textarea = input.current
288
+ if (!textarea) return
289
+ textarea.style.height = 'auto'
290
+ textarea.style.height = `${Math.min(textarea.scrollHeight, 208)}px`
291
+ }, [text])
292
+ const ready = !!text.trim() && !!settings.model.trim()
293
+ return (
294
+ <form
295
+ className="a0-composer"
296
+ onSubmit={async (event) => {
297
+ event.preventDefault()
298
+ const message = text.trim()
299
+ if (!ready) return
300
+ setText('')
301
+ await onSend(message).catch(() =>
302
+ setText((current) => current || message),
303
+ )
304
+ }}
305
+ >
306
+ <textarea
307
+ ref={input}
308
+ aria-label="Message"
309
+ value={text}
310
+ rows={2}
311
+ autoFocus={autoFocus}
312
+ onChange={(event) => setText(event.target.value)}
313
+ onKeyDown={(event) => {
314
+ if (
315
+ event.key === 'Enter' &&
316
+ !event.shiftKey &&
317
+ !event.nativeEvent.isComposing
318
+ ) {
319
+ event.preventDefault()
320
+ event.currentTarget.form?.requestSubmit()
321
+ }
322
+ }}
323
+ placeholder="Ask the agent to build something"
324
+ />
325
+ <div className="a0-composer-bar">
326
+ <div className="a0-composer-model">
327
+ <select
328
+ aria-label="Harness"
329
+ title={
330
+ onHarness
331
+ ? "The agent's loop: the AI SDK's, FX's, or Pi's"
332
+ : 'The agent loop this app runs'
333
+ }
334
+ value={harness}
335
+ disabled={!onHarness}
336
+ onChange={(event) =>
337
+ onHarness?.(harnessSchema.parse(event.target.value))
338
+ }
339
+ >
340
+ {harnessSchema.options.map((option) => (
341
+ <option key={option} value={option}>
342
+ {harnessLabels[option]}
343
+ </option>
344
+ ))}
345
+ </select>
346
+ <input
347
+ aria-label="Model"
348
+ title="The Gateway model the next request runs with"
349
+ value={settings.model}
350
+ size={Math.max(settings.model.length, 12)}
351
+ onChange={(event) =>
352
+ onSettings({ ...settings, model: event.target.value })
353
+ }
354
+ autoComplete="off"
355
+ autoCorrect="off"
356
+ autoCapitalize="none"
357
+ spellCheck={false}
358
+ />
359
+ <select
360
+ aria-label="Reasoning"
361
+ title="Reasoning effort"
362
+ value={settings.reasoning}
363
+ onChange={(event) =>
364
+ onSettings({
365
+ ...settings,
366
+ reasoning: reasoningSchema.parse(event.target.value),
367
+ })
368
+ }
369
+ >
370
+ {reasoningSchema.options.map((level) => (
371
+ <option key={level} value={level}>
372
+ {reasoningLabel(level)}
373
+ </option>
374
+ ))}
375
+ </select>
376
+ <button
377
+ type="button"
378
+ className="a0-fast"
379
+ aria-label="Fast"
380
+ aria-pressed={settings.fast}
381
+ title="Fast: the faster tier when the model has one. May cost more."
382
+ onClick={() => onSettings({ ...settings, fast: !settings.fast })}
383
+ >
384
+ <Zap
385
+ size={13}
386
+ fill={settings.fast ? 'currentColor' : 'none'}
387
+ aria-hidden
388
+ />
389
+ </button>
390
+ </div>
391
+ <div className="a0-composer-actions">
392
+ {!!queued && <span className="a0-queued">{queued} queued</span>}
393
+ {working && (
394
+ <button
395
+ type="button"
396
+ className="a0-ghost"
397
+ disabled={!onStop}
398
+ onClick={onStop ?? undefined}
399
+ >
400
+ Stop
401
+ </button>
402
+ )}
403
+ <button type="submit" disabled={!ready}>
404
+ Send
405
+ </button>
406
+ </div>
407
+ </div>
408
+ </form>
409
+ )
410
+ }
@@ -0,0 +1,109 @@
1
+ 'use client'
2
+ import type { CSSProperties, ReactNode } from 'react'
3
+ import { createContext, useContext, useRef, useState } from 'react'
4
+ import {
5
+ appsWidth,
6
+ chatMin,
7
+ previewMin,
8
+ serializeWidths,
9
+ type Widths,
10
+ widthsCookie,
11
+ writeCookie,
12
+ } from './cookies'
13
+ import { Splitter } from './splitter'
14
+
15
+ type Column = keyof Widths
16
+
17
+ type Resize = (column: Column, width: number, done: boolean) => void
18
+
19
+ const edges: Record<
20
+ Column,
21
+ { label: string; min: number; max: (widths: Widths) => number }
22
+ > = {
23
+ apps: {
24
+ label: 'Resize the apps list',
25
+ min: appsWidth.min,
26
+ max: () => appsWidth.max,
27
+ },
28
+ chat: {
29
+ label: 'Resize the chat',
30
+ min: chatMin,
31
+ max: ({ apps }) => window.innerWidth - apps - previewMin,
32
+ },
33
+ }
34
+
35
+ const ColumnsContext = createContext<{
36
+ widths: Widths
37
+ resize: Resize
38
+ } | null>(null)
39
+
40
+ // A saved width still leaves the preview its minimum in a smaller window.
41
+ const chatTrack = (width: number): string =>
42
+ `min(${width}px, 100% - var(--a0-apps-width) - ${previewMin}px)`
43
+
44
+ const track = (column: Column, width: number): string =>
45
+ column === 'chat' ? chatTrack(width) : `${width}px`
46
+
47
+ /**
48
+ * A0's columns, apps, chat and preview, with the widths someone dragged the
49
+ * first two to, kept in a cookie so the server renders them. `panel` keeps
50
+ * a row under the chat and preview for the dev tools, even while the page
51
+ * beside it loads.
52
+ */
53
+ export function Columns({
54
+ saved,
55
+ panel,
56
+ children,
57
+ }: {
58
+ saved: Widths
59
+ panel: boolean
60
+ children: ReactNode
61
+ }): ReactNode {
62
+ const grid = useRef<HTMLDivElement>(null)
63
+ const [widths, setWidths] = useState(saved)
64
+ const resize: Resize = (column, width, done) => {
65
+ // Dragging sets the width directly, without rendering the page.
66
+ grid.current?.style.setProperty(
67
+ `--a0-${column}-width`,
68
+ track(column, width),
69
+ )
70
+ if (!done) return
71
+ const next = { ...widths, [column]: width }
72
+ setWidths(next)
73
+ writeCookie({ name: widthsCookie, value: serializeWidths(next) })
74
+ }
75
+ return (
76
+ <ColumnsContext value={{ widths, resize }}>
77
+ <div
78
+ ref={grid}
79
+ className="a0-page"
80
+ data-panel={panel || undefined}
81
+ style={
82
+ {
83
+ '--a0-apps-width': track('apps', widths.apps),
84
+ ...(widths.chat === null
85
+ ? {}
86
+ : { '--a0-chat-width': track('chat', widths.chat) }),
87
+ } as CSSProperties
88
+ }
89
+ >
90
+ {children}
91
+ </div>
92
+ </ColumnsContext>
93
+ )
94
+ }
95
+
96
+ /** The right edge of the column it sits in, which resizes that column. */
97
+ export function ColumnEdge({ column }: { column: Column }): ReactNode {
98
+ const columns = useContext(ColumnsContext)
99
+ if (!columns) throw new Error('ColumnEdge needs Columns around it.')
100
+ const { label, min, max } = edges[column]
101
+ return (
102
+ <Splitter
103
+ label={label}
104
+ min={min}
105
+ max={() => max(columns.widths)}
106
+ onResize={({ width, done }) => columns.resize(column, width, done)}
107
+ />
108
+ )
109
+ }
@@ -0,0 +1,116 @@
1
+ 'use client'
2
+ import type { KeyboardEvent, ReactNode } from 'react'
3
+ import {
4
+ useEffect,
5
+ useEffectEvent,
6
+ useLayoutEffect,
7
+ useRef,
8
+ useState,
9
+ } from 'react'
10
+
11
+ export type MenuItem = {
12
+ label: string
13
+ onSelect: () => void
14
+ danger?: boolean
15
+ }
16
+
17
+ // Keeps the menu this far inside the window's edges.
18
+ const MARGIN = 8
19
+
20
+ /**
21
+ * A menu at a point, as a right-click opens one. It takes focus, moves with
22
+ * the arrow keys, and closes on Escape, a press outside, or a pick; Escape
23
+ * and a pick hand focus back through `onClose`.
24
+ */
25
+ export function ContextMenu({
26
+ at,
27
+ items,
28
+ onClose,
29
+ }: {
30
+ at: { x: number; y: number }
31
+ items: MenuItem[]
32
+ onClose: (closed: { returnFocus: boolean }) => void
33
+ }): ReactNode {
34
+ const menu = useRef<HTMLDivElement>(null)
35
+ const [position, setPosition] = useState(at)
36
+
37
+ // Measured once it's drawn, so it can stay inside the window.
38
+ useLayoutEffect(() => {
39
+ const element = menu.current
40
+ if (!element) return
41
+ const { width, height } = element.getBoundingClientRect()
42
+ setPosition({
43
+ x: Math.max(MARGIN, Math.min(at.x, innerWidth - width - MARGIN)),
44
+ y: Math.max(MARGIN, Math.min(at.y, innerHeight - height - MARGIN)),
45
+ })
46
+ element.querySelector<HTMLElement>('[role="menuitem"]')?.focus()
47
+ }, [at])
48
+
49
+ const dismiss = useEffectEvent((returnFocus: boolean) =>
50
+ onClose({ returnFocus }),
51
+ )
52
+ useEffect(() => {
53
+ const outside = (event: PointerEvent): void => {
54
+ if (!menu.current?.contains(event.target as Node)) dismiss(false)
55
+ }
56
+ const away = (): void => dismiss(false)
57
+ document.addEventListener('pointerdown', outside, true)
58
+ window.addEventListener('blur', away)
59
+ window.addEventListener('resize', away)
60
+ return () => {
61
+ document.removeEventListener('pointerdown', outside, true)
62
+ window.removeEventListener('blur', away)
63
+ window.removeEventListener('resize', away)
64
+ }
65
+ }, [])
66
+
67
+ const move = (event: KeyboardEvent<HTMLDivElement>): void => {
68
+ const buttons = [
69
+ ...event.currentTarget.querySelectorAll<HTMLElement>('[role="menuitem"]'),
70
+ ]
71
+ const focused = buttons.indexOf(document.activeElement as HTMLElement)
72
+ const next =
73
+ event.key === 'ArrowDown'
74
+ ? (focused + 1) % buttons.length
75
+ : event.key === 'ArrowUp'
76
+ ? (focused - 1 + buttons.length) % buttons.length
77
+ : event.key === 'Home'
78
+ ? 0
79
+ : event.key === 'End'
80
+ ? buttons.length - 1
81
+ : null
82
+ if (event.key === 'Escape' || event.key === 'Tab') {
83
+ event.preventDefault()
84
+ onClose({ returnFocus: true })
85
+ } else if (next !== null) {
86
+ event.preventDefault()
87
+ buttons[next]?.focus()
88
+ }
89
+ }
90
+
91
+ return (
92
+ <div
93
+ ref={menu}
94
+ role="menu"
95
+ className="a0-menu"
96
+ style={{ left: position.x, top: position.y }}
97
+ onKeyDown={move}
98
+ onContextMenu={(event) => event.preventDefault()}
99
+ >
100
+ {items.map((item) => (
101
+ <button
102
+ key={item.label}
103
+ type="button"
104
+ role="menuitem"
105
+ data-danger={item.danger || undefined}
106
+ onClick={() => {
107
+ onClose({ returnFocus: true })
108
+ item.onSelect()
109
+ }}
110
+ >
111
+ {item.label}
112
+ </button>
113
+ ))}
114
+ </div>
115
+ )
116
+ }
@@ -0,0 +1,46 @@
1
+ /** The id of this browser's app list session. */
2
+ export const listCookie = 'a0_list'
3
+
4
+ /** The sidebar and chat widths someone dragged them to, in pixels. */
5
+ export const widthsCookie = 'a0_widths'
6
+
7
+ export type Widths = { apps: number; chat: number | null }
8
+
9
+ export const defaultWidths: Widths = { apps: 216, chat: null }
10
+
11
+ export const appsWidth = { min: 160, max: 400 }
12
+ export const chatMin = 320
13
+ export const previewMin = 320
14
+
15
+ export function parseWidths(value: string | undefined): Widths {
16
+ const [apps, chat] = (value ?? '').split(',').map(Number)
17
+ return {
18
+ apps:
19
+ apps && apps >= appsWidth.min && apps <= appsWidth.max
20
+ ? apps
21
+ : defaultWidths.apps,
22
+ chat: chat && chat >= chatMin ? chat : null,
23
+ }
24
+ }
25
+
26
+ export const serializeWidths = ({ apps, chat }: Widths): string =>
27
+ chat === null ? String(apps) : `${apps},${chat}`
28
+
29
+ /** Browser-only. */
30
+ export function readCookie(name: string): string | undefined {
31
+ return document.cookie
32
+ .split('; ')
33
+ .find((entry) => entry.startsWith(`${name}=`))
34
+ ?.slice(name.length + 1)
35
+ }
36
+
37
+ /** Browser-only. */
38
+ export function writeCookie({
39
+ name,
40
+ value,
41
+ }: {
42
+ name: string
43
+ value: string
44
+ }): void {
45
+ document.cookie = `${name}=${value}; path=/; max-age=31536000; samesite=lax`
46
+ }