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.
- package/CHANGELOG.md +373 -0
- package/README.md +1 -1
- package/dist/{actor-ohPC-81x.d.ts → actor-Bjce9_5U.d.ts} +21 -11
- package/dist/actor-Bjce9_5U.d.ts.map +1 -0
- package/dist/actor-client.d.ts +2 -5
- package/dist/actor-client.d.ts.map +1 -1
- package/dist/actor-client.js +13 -4
- package/dist/actor-client.js.map +1 -1
- package/dist/actor-react.d.ts +5 -3
- package/dist/actor-react.d.ts.map +1 -1
- package/dist/actor-react.js +20 -10
- package/dist/actor-react.js.map +1 -1
- package/dist/{actor-shared-B5tJfzt-.js → actor-shared-t7w8xIKP.js} +12 -7
- package/dist/actor-shared-t7w8xIKP.js.map +1 -0
- package/dist/actor.d.ts +1 -1
- package/dist/actor.js +42 -23
- package/dist/actor.js.map +1 -1
- package/dist/{ai-DgOBltJ_.js → ai-BpOGm-zB.js} +621 -216
- package/dist/ai-BpOGm-zB.js.map +1 -0
- package/dist/{ai-CrEf6p_W.d.ts → ai-DrUaMyRt.d.ts} +114 -100
- package/dist/ai-DrUaMyRt.d.ts.map +1 -0
- package/dist/ai-client.d.ts +27 -0
- package/dist/ai-client.d.ts.map +1 -0
- package/dist/ai-client.js +29 -0
- package/dist/ai-client.js.map +1 -0
- package/dist/ai-fx.browser.d.ts +1 -0
- package/dist/ai-fx.browser.js +6 -0
- package/dist/ai-fx.browser.js.map +1 -0
- package/dist/ai-fx.d.ts +10 -0
- package/dist/ai-fx.d.ts.map +1 -0
- package/dist/ai-fx.js +634 -0
- package/dist/ai-fx.js.map +1 -0
- package/dist/ai-harness-BuLh_EYi.js +273 -0
- package/dist/ai-harness-BuLh_EYi.js.map +1 -0
- package/dist/ai-harness-DvsPeJeB.d.ts +26 -0
- package/dist/ai-harness-DvsPeJeB.d.ts.map +1 -0
- package/dist/ai-pi.browser.d.ts +1 -0
- package/dist/ai-pi.browser.js +6 -0
- package/dist/ai-pi.browser.js.map +1 -0
- package/dist/ai-pi.d.ts +17 -0
- package/dist/ai-pi.d.ts.map +1 -0
- package/dist/ai-pi.js +904 -0
- package/dist/ai-pi.js.map +1 -0
- package/dist/ai-react.d.ts +96 -0
- package/dist/ai-react.d.ts.map +1 -0
- package/dist/ai-react.js +142 -0
- package/dist/ai-react.js.map +1 -0
- package/dist/ai-server.browser.js +2 -2
- package/dist/ai-server.browser.js.map +1 -1
- package/dist/ai-server.d.ts +109 -43
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +526 -283
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.d.ts +2 -2
- package/dist/ai.js +2 -2
- package/dist/bounded-cache-CPHWF-8h.js +41 -0
- package/dist/bounded-cache-CPHWF-8h.js.map +1 -0
- package/dist/{client-BYzHjkwU.js → client-BReKqwZC.js} +447 -80
- package/dist/client-BReKqwZC.js.map +1 -0
- package/dist/{client-CzyacQpJ.d.ts → client-h_DKFAjj.d.ts} +14 -13
- package/dist/client-h_DKFAjj.d.ts.map +1 -0
- package/dist/client.d.ts +4 -2
- package/dist/client.js +1 -1
- package/dist/{contract-CKRg_E4q.js → contract-BYKASlCV.js} +12 -21
- package/dist/contract-BYKASlCV.js.map +1 -0
- package/dist/{idempotent-replay-DVOlyYbx.js → idempotent-replay-JdgMQRRm.js} +2 -2
- package/dist/{idempotent-replay-DVOlyYbx.js.map → idempotent-replay-JdgMQRRm.js.map} +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/{errors-DvhSXnxk.d.ts → initial-snapshot-Bx-c0pDP.d.ts} +7 -3
- package/dist/initial-snapshot-Bx-c0pDP.d.ts.map +1 -0
- package/dist/{internal-Dq2qYxou.js → internal-B2T3U4Rq.js} +29 -2
- package/dist/internal-B2T3U4Rq.js.map +1 -0
- package/dist/otel.d.ts +1 -1
- package/dist/react-BBXgHWFc.d.ts +86 -0
- package/dist/react-BBXgHWFc.d.ts.map +1 -0
- package/dist/react.d.ts +2 -122
- package/dist/react.js +107 -100
- package/dist/react.js.map +1 -1
- package/dist/reducer-BAXc8JyF.js +87 -0
- package/dist/reducer-BAXc8JyF.js.map +1 -0
- package/dist/{reducer-otzHjuJj.d.ts → reducer-CqA59Q6U.d.ts} +46 -30
- package/dist/reducer-CqA59Q6U.d.ts.map +1 -0
- package/dist/reducer-runtime-DQwz_J59.js +28 -0
- package/dist/reducer-runtime-DQwz_J59.js.map +1 -0
- package/dist/scheduler-qstash.d.ts +3 -3
- package/dist/scheduler-qstash.d.ts.map +1 -1
- package/dist/scheduler-qstash.js +33 -10
- package/dist/scheduler-qstash.js.map +1 -1
- package/dist/{scheduler-task-BpzhPnRS.js → scheduler-task-X1SToDjP.js} +18 -3
- package/dist/scheduler-task-X1SToDjP.js.map +1 -0
- package/dist/scheduler-vercel.d.ts +4 -9
- package/dist/scheduler-vercel.d.ts.map +1 -1
- package/dist/scheduler-vercel.js +25 -44
- package/dist/scheduler-vercel.js.map +1 -1
- package/dist/{server-BD5ckxZb.js → server-Dw_k_7kO.js} +904 -219
- package/dist/server-Dw_k_7kO.js.map +1 -0
- package/dist/{server-DbD7IJVK.d.ts → server-En7cbi3v.d.ts} +116 -26
- package/dist/server-En7cbi3v.d.ts.map +1 -0
- package/dist/server.d.ts +4 -4
- package/dist/server.js +2 -2
- package/dist/session-clear-Bo-XTFEy.d.ts +12 -0
- package/dist/session-clear-Bo-XTFEy.d.ts.map +1 -0
- package/dist/{errors-DCk6ch5n.js → session-clear-DGI4jTQ5.js} +17 -2
- package/dist/session-clear-DGI4jTQ5.js.map +1 -0
- package/dist/{store-DyZM6fS5.d.ts → store-Djdv0bnZ.d.ts} +73 -3
- package/dist/store-Djdv0bnZ.d.ts.map +1 -0
- package/dist/store-N8PXxDAS.js.map +1 -1
- package/dist/store-memory.d.ts +1 -1
- package/dist/store-memory.d.ts.map +1 -1
- package/dist/store-memory.js +205 -66
- package/dist/store-memory.js.map +1 -1
- package/dist/{store-polling-CmxUbV93.js → store-polling-B5FcwJfo.js} +21 -4
- package/dist/store-polling-B5FcwJfo.js.map +1 -0
- package/dist/store-postgres.d.ts +1 -1
- package/dist/store-postgres.d.ts.map +1 -1
- package/dist/store-postgres.js +450 -108
- package/dist/store-postgres.js.map +1 -1
- package/dist/{store-presence-polling-C7-XZyW9.js → store-presence-polling-BAwS8RWn.js} +3 -3
- package/dist/{store-presence-polling-C7-XZyW9.js.map → store-presence-polling-BAwS8RWn.js.map} +1 -1
- package/dist/store-redis-http.d.ts +1 -1
- package/dist/store-redis-http.d.ts.map +1 -1
- package/dist/store-redis-http.js +293 -11
- package/dist/store-redis-http.js.map +1 -1
- package/dist/store-redis-notify-BRITti_o.js +2493 -0
- package/dist/store-redis-notify-BRITti_o.js.map +1 -0
- package/dist/store-redis.d.ts +1 -1
- package/dist/store-redis.d.ts.map +1 -1
- package/dist/store-redis.js +11 -11
- package/dist/store-redis.js.map +1 -1
- package/dist/store-sqlite.d.ts +1 -1
- package/dist/store-sqlite.d.ts.map +1 -1
- package/dist/store-sqlite.js +197 -33
- package/dist/store-sqlite.js.map +1 -1
- package/dist/stream-buffer-Cx7AAPGq.js +23 -0
- package/dist/stream-buffer-Cx7AAPGq.js.map +1 -0
- package/dist/{telemetry-B5jzpy6y.d.ts → telemetry-Cyfqo6Qt.d.ts} +2 -2
- package/dist/telemetry-Cyfqo6Qt.d.ts.map +1 -0
- package/dist/{wire-BO5wWCb1.js → wire-rSUglYo8.js} +132 -10
- package/dist/wire-rSUglYo8.js.map +1 -0
- package/docs/01-quickstart.mdx +50 -84
- package/docs/{guides/07-examples.mdx → 02-examples.mdx} +10 -20
- package/docs/actors/01-introduction.mdx +48 -82
- package/docs/actors/02-concurrency.mdx +43 -70
- package/docs/actors/03-timers.mdx +28 -42
- package/docs/actors/04-routes.mdx +110 -137
- package/docs/actors/meta.ts +1 -1
- package/docs/agents/01-introduction.mdx +257 -0
- package/docs/agents/02-tools.mdx +247 -0
- package/docs/agents/03-conversations.mdx +538 -0
- package/docs/agents/04-harnesses.mdx +136 -0
- package/docs/agents/meta.ts +1 -0
- package/docs/client/01-react.mdx +294 -0
- package/docs/client/02-transports.mdx +86 -0
- package/docs/client/03-presence.mdx +224 -0
- package/docs/client/meta.ts +1 -0
- package/docs/contract/01-events.mdx +57 -0
- package/docs/contract/02-state.mdx +142 -0
- package/docs/contract/03-sessions.mdx +175 -0
- package/docs/index.mdx +112 -156
- package/docs/reference/01-core.mdx +139 -0
- package/docs/reference/02-server.mdx +855 -0
- package/docs/reference/03-client.mdx +301 -0
- package/docs/reference/04-ai.mdx +722 -0
- package/docs/reference/05-adapters.mdx +351 -0
- package/docs/reference/06-errors.mdx +138 -0
- package/docs/reference/meta.ts +1 -1
- package/docs/server/01-handlers.mdx +200 -0
- package/docs/server/02-timers.mdx +130 -0
- package/docs/server/03-cancellation.mdx +141 -0
- package/docs/server/04-routes.mdx +243 -0
- package/docs/server/05-production.mdx +322 -0
- package/docs/server/06-guarantees.mdx +123 -0
- package/examples/playground/DEPLOY.md +53 -7
- package/examples/playground/app/a0/[appId]/loading.tsx +16 -0
- package/examples/playground/app/a0/[appId]/page.tsx +53 -0
- package/examples/playground/app/a0/a0.css +818 -0
- package/examples/playground/app/a0/app-list.tsx +218 -0
- package/examples/playground/app/a0/app-view.tsx +63 -0
- package/examples/playground/app/a0/apps/[appId]/preview/route.ts +27 -0
- package/examples/playground/app/a0/apps/[appId]/route.ts +28 -0
- package/examples/playground/app/a0/apps.test.ts +128 -0
- package/examples/playground/app/a0/apps.ts +102 -0
- package/examples/playground/app/a0/chat.tsx +410 -0
- package/examples/playground/app/a0/columns.tsx +109 -0
- package/examples/playground/app/a0/context-menu.tsx +116 -0
- package/examples/playground/app/a0/cookies.ts +46 -0
- package/examples/playground/app/a0/crash-on-request.ts +72 -0
- package/examples/playground/app/a0/crashes/route.ts +3 -0
- package/examples/playground/app/a0/crashes.test.ts +33 -0
- package/examples/playground/app/a0/crashes.ts +28 -0
- package/examples/playground/app/a0/devtools.tsx +164 -0
- package/examples/playground/app/a0/events/route.ts +6 -0
- package/examples/playground/app/a0/flags.ts +30 -0
- package/examples/playground/app/a0/fx/events/route.ts +9 -0
- package/examples/playground/app/a0/fx/scheduler/route.ts +7 -0
- package/examples/playground/app/a0/fx/short/events/route.ts +11 -0
- package/examples/playground/app/a0/layout.tsx +38 -0
- package/examples/playground/app/a0/list/route.ts +10 -0
- package/examples/playground/app/a0/model.test.ts +73 -0
- package/examples/playground/app/a0/model.ts +95 -0
- package/examples/playground/app/a0/no-photon.cjs +4 -0
- package/examples/playground/app/a0/no-quickjs.cjs +3 -0
- package/examples/playground/app/a0/page.tsx +14 -0
- package/examples/playground/app/a0/pi/events/route.ts +9 -0
- package/examples/playground/app/a0/pi/scheduler/route.ts +7 -0
- package/examples/playground/app/a0/pi/short/events/route.ts +11 -0
- package/examples/playground/app/a0/preview-pane.tsx +145 -0
- package/examples/playground/app/a0/preview.test.ts +53 -0
- package/examples/playground/app/a0/preview.ts +26 -0
- package/examples/playground/app/a0/sandbox-log.ts +75 -0
- package/examples/playground/app/a0/sandbox-session.ts +152 -0
- package/examples/playground/app/a0/sandbox.ts +191 -0
- package/examples/playground/app/a0/sandboxes/scheduler/route.ts +6 -0
- package/examples/playground/app/a0/scheduler/route.ts +7 -0
- package/examples/playground/app/a0/server.ts +213 -0
- package/examples/playground/app/a0/session.ts +59 -0
- package/examples/playground/app/a0/shell.tsx +158 -0
- package/examples/playground/app/a0/short/events/route.ts +9 -0
- package/examples/playground/app/a0/splitter.tsx +120 -0
- package/examples/playground/app/agent/[agentId]/agent-client.tsx +144 -174
- package/examples/playground/app/agent/[agentId]/command-receipt.test.tsx +65 -0
- package/examples/playground/app/agent/[agentId]/command-receipt.tsx +27 -0
- package/examples/playground/app/agent/[agentId]/compaction-event.tsx +25 -24
- package/examples/playground/app/agent/[agentId]/compaction-panel.test.tsx +119 -0
- package/examples/playground/app/agent/[agentId]/compaction-panel.tsx +208 -165
- package/examples/playground/app/agent/[agentId]/page.tsx +2 -16
- package/examples/playground/app/agent/events/route.ts +5 -2
- package/examples/playground/app/agent/ingress.test.ts +101 -0
- package/examples/playground/app/agent/model-input.test.tsx +58 -0
- package/examples/playground/app/agent/model-input.tsx +115 -0
- package/examples/playground/app/agent/model.test.ts +73 -0
- package/examples/playground/app/agent/model.ts +58 -26
- package/examples/playground/app/agent/new-agent-session.tsx +37 -19
- package/examples/playground/app/agent/page.tsx +4 -4
- package/examples/playground/app/agent/server.ts +19 -15
- package/examples/playground/app/agent/session.ts +5 -6
- package/examples/playground/app/agent/telemetry.test.ts +189 -0
- package/examples/playground/app/agent/telemetry.ts +30 -0
- package/examples/playground/app/agent/trace.test.ts +23 -0
- package/examples/playground/app/agent/trace.ts +18 -0
- package/examples/playground/app/canvas/[canvasId]/canvas-client.tsx +12 -12
- package/examples/playground/app/canvas/[canvasId]/page.tsx +1 -2
- package/examples/playground/app/canvas/[canvasId]/session.ts +2 -2
- package/examples/playground/app/canvas/model.ts +53 -53
- package/examples/playground/app/chat/[chatId]/agent-state.test.ts +176 -0
- package/examples/playground/app/chat/[chatId]/agent-stream-drawer.tsx +16 -1
- package/examples/playground/app/chat/[chatId]/chat-client.tsx +55 -77
- package/examples/playground/app/chat/[chatId]/chat-view.ts +37 -3
- package/examples/playground/app/chat/[chatId]/page.tsx +7 -15
- package/examples/playground/app/chat/[chatId]/session.ts +2 -19
- package/examples/playground/app/chat/agent/route.ts +9 -0
- package/examples/playground/app/chat/model.test.ts +46 -119
- package/examples/playground/app/chat/model.ts +82 -128
- package/examples/playground/app/chat/scheduler/route.ts +2 -2
- package/examples/playground/app/chat/server.test.ts +112 -0
- package/examples/playground/app/chat/server.ts +113 -117
- package/examples/playground/app/components/activity-feed.tsx +30 -1
- package/examples/playground/app/components/activity-history.test.tsx +35 -0
- package/examples/playground/app/components/demo-icons.tsx +89 -0
- package/examples/playground/app/components/demos.tsx +33 -0
- package/examples/playground/app/components/fast-link.test.tsx +58 -0
- package/examples/playground/app/components/fast-link.tsx +55 -0
- package/examples/playground/app/components/site-header.tsx +96 -0
- package/examples/playground/app/counter/counter-client.tsx +3 -3
- package/examples/playground/app/counter/model.test.ts +3 -3
- package/examples/playground/app/counter/model.ts +11 -21
- package/examples/playground/app/counter/page.tsx +2 -15
- package/examples/playground/app/counter/session.ts +2 -2
- package/examples/playground/app/documents/[documentId]/code-editor.tsx +12 -1
- package/examples/playground/app/documents/[documentId]/document-client.tsx +10 -11
- package/examples/playground/app/documents/[documentId]/page.tsx +1 -2
- package/examples/playground/app/documents/[documentId]/session.ts +2 -2
- package/examples/playground/app/documents/model.ts +27 -27
- package/examples/playground/app/globals.css +420 -207
- package/examples/playground/app/initial-state.test.tsx +54 -0
- package/examples/playground/app/layout.tsx +7 -23
- package/examples/playground/app/orders/[orderId]/page.tsx +1 -2
- package/examples/playground/app/orders/[orderId]/session.ts +2 -2
- package/examples/playground/app/orders/model.ts +49 -49
- package/examples/playground/app/page.tsx +13 -102
- package/examples/playground/app/recovery/[recoveryId]/page.tsx +1 -2
- package/examples/playground/app/recovery/[recoveryId]/session.ts +2 -2
- package/examples/playground/app/recovery/model.ts +30 -30
- package/examples/playground/instrumentation.ts +5 -0
- package/examples/playground/next.config.ts +17 -5
- package/examples/playground/package.json +13 -2
- package/examples/playground/scripts/a0-template.ts +77 -0
- package/examples/playground/vercel.json +36 -0
- package/package.json +29 -3
- package/src/actor-client.ts +16 -4
- package/src/actor-react.ts +25 -11
- package/src/actor-shared.ts +21 -6
- package/src/actor.ts +76 -31
- package/src/ai-agent-actions.ts +219 -0
- package/src/ai-agent.ts +2517 -0
- package/src/ai-app-state.ts +91 -0
- package/src/ai-client-state.ts +6 -3
- package/src/ai-client.ts +62 -0
- package/src/ai-context-schema.ts +25 -5
- package/src/ai-context.ts +90 -40
- package/src/ai-control-server.ts +92 -72
- package/src/ai-coordinator.ts +23 -22
- package/src/ai-fx.browser.ts +5 -0
- package/src/ai-fx.ts +833 -0
- package/src/ai-harness.ts +518 -0
- package/src/ai-message-base.ts +209 -0
- package/src/ai-message-projection.ts +262 -64
- package/src/ai-model-metadata.ts +11 -6
- package/src/ai-pi.browser.ts +5 -0
- package/src/ai-pi.ts +1263 -0
- package/src/ai-progress-batches.ts +26 -0
- package/src/ai-projector.ts +95 -14
- package/src/ai-react.ts +146 -0
- package/src/ai-sdk-step.ts +2 -2
- package/src/ai-server.browser.ts +2 -2
- package/src/ai-server.ts +23 -1965
- package/src/ai-stored-state.ts +48 -163
- package/src/ai.ts +339 -155
- package/src/bootstrap-stream.ts +72 -0
- package/src/bounded-cache.ts +55 -0
- package/src/client-render-session.ts +49 -0
- package/src/client.ts +688 -151
- package/src/contract.ts +92 -42
- package/src/deterministic-id.ts +37 -0
- package/src/equal-json.ts +32 -0
- package/src/errors.ts +13 -1
- package/src/index.ts +5 -1
- package/src/initial-snapshot.ts +39 -0
- package/src/internal.ts +52 -0
- package/src/libfx.d.ts +34 -0
- package/src/postgres-notification-scope.ts +14 -3
- package/src/react.ts +171 -206
- package/src/redis-http-reply.ts +256 -0
- package/src/redis-http-stream.ts +73 -0
- package/src/redis-http-subscriptions.ts +13 -5
- package/src/reducer-runtime.ts +52 -0
- package/src/reducer.ts +93 -11
- package/src/scheduler-qstash.ts +49 -20
- package/src/scheduler-task.ts +34 -2
- package/src/scheduler-vercel.ts +32 -84
- package/src/server-fetch.ts +139 -35
- package/src/server.ts +1133 -255
- package/src/session-clear.ts +23 -0
- package/src/session-socket.ts +154 -49
- package/src/sse.ts +71 -4
- package/src/store-memory.ts +252 -75
- package/src/store-polling.ts +18 -4
- package/src/store-postgres.ts +541 -114
- package/src/store-redis-core.ts +1450 -493
- package/src/store-redis-http.ts +18 -2
- package/src/store-redis-notify.ts +262 -85
- package/src/store-redis.ts +19 -9
- package/src/store-sqlite.ts +319 -45
- package/src/store.ts +79 -1
- package/src/stream-buffer.ts +43 -0
- package/src/telemetry.ts +14 -1
- package/src/wire.ts +241 -13
- package/dist/actor-ohPC-81x.d.ts.map +0 -1
- package/dist/actor-shared-B5tJfzt-.js.map +0 -1
- package/dist/ai-CrEf6p_W.d.ts.map +0 -1
- package/dist/ai-DgOBltJ_.js.map +0 -1
- package/dist/client-BYzHjkwU.js.map +0 -1
- package/dist/client-CzyacQpJ.d.ts.map +0 -1
- package/dist/contract-CKRg_E4q.js.map +0 -1
- package/dist/errors-DCk6ch5n.js.map +0 -1
- package/dist/errors-DvhSXnxk.d.ts.map +0 -1
- package/dist/internal-Dq2qYxou.js.map +0 -1
- package/dist/react.d.ts.map +0 -1
- package/dist/reducer-DEMjEY_O.js +0 -29
- package/dist/reducer-DEMjEY_O.js.map +0 -1
- package/dist/reducer-otzHjuJj.d.ts.map +0 -1
- package/dist/scheduler-task-BpzhPnRS.js.map +0 -1
- package/dist/server-BD5ckxZb.js.map +0 -1
- package/dist/server-DbD7IJVK.d.ts.map +0 -1
- package/dist/store-DyZM6fS5.d.ts.map +0 -1
- package/dist/store-polling-CmxUbV93.js.map +0 -1
- package/dist/store-redis-notify-BVLUfI3j.js +0 -1583
- package/dist/store-redis-notify-BVLUfI3j.js.map +0 -1
- package/dist/telemetry-B5jzpy6y.d.ts.map +0 -1
- package/dist/validate-XKT4FSNn.js +0 -30
- package/dist/validate-XKT4FSNn.js.map +0 -1
- package/dist/wire-BO5wWCb1.js.map +0 -1
- package/docs/concepts/01-contracts.mdx +0 -142
- package/docs/concepts/02-handlers.mdx +0 -284
- package/docs/concepts/03-durability.mdx +0 -314
- package/docs/concepts/04-state.mdx +0 -209
- package/docs/guides/01-timers.mdx +0 -185
- package/docs/guides/02-cancellation.mdx +0 -161
- package/docs/guides/03-react.mdx +0 -384
- package/docs/guides/04-local-first.mdx +0 -88
- package/docs/guides/05-production.mdx +0 -521
- package/docs/guides/06-ai-agents.mdx +0 -1318
- package/docs/guides/08-application-data.mdx +0 -113
- package/docs/guides/09-presence.mdx +0 -266
- package/docs/guides/10-transports.mdx +0 -221
- package/docs/reference/01-api.mdx +0 -1896
- package/docs/reference/02-errors.mdx +0 -149
- package/examples/playground/app/agent/[agentId]/compaction/route.ts +0 -14
- package/examples/playground/app/agent/compaction-settings.test.ts +0 -144
- package/examples/playground/app/agent/compaction-settings.ts +0 -49
- package/examples/playground/app/agent/compaction-timeline.test.ts +0 -377
- package/examples/playground/app/agent/compaction-timeline.ts +0 -208
- /package/docs/{concepts → contract}/meta.ts +0 -0
- /package/docs/{guides → server}/meta.ts +0 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import 'server-only'
|
|
2
|
+
import type { AIState } from 'experimental-a2/ai'
|
|
3
|
+
import { type AppMessage, appAgent } from './model'
|
|
4
|
+
import type { agentServer, crashServer } from './server'
|
|
5
|
+
|
|
6
|
+
type AppSession = ReturnType<typeof agentServer.session>
|
|
7
|
+
type CrashLog = ReturnType<typeof crashServer.session>
|
|
8
|
+
|
|
9
|
+
const working = (state: AIState<AppMessage>): boolean =>
|
|
10
|
+
state.status === 'generating' ||
|
|
11
|
+
state.status === 'waiting' ||
|
|
12
|
+
!!state.pendingQueueCommands?.length
|
|
13
|
+
|
|
14
|
+
/** Resolves once the app's agent stops working. */
|
|
15
|
+
async function workDone(app: AppSession): Promise<void> {
|
|
16
|
+
const { state: initial, index } = await app.state()
|
|
17
|
+
let state = initial
|
|
18
|
+
if (!working(state)) return
|
|
19
|
+
for await (const event of app.stream({ startAfter: index })) {
|
|
20
|
+
// A cleared app has nothing left to work on.
|
|
21
|
+
if (event.type === 'a2.cleared') return
|
|
22
|
+
state = appAgent.reducer.fold(state, event)
|
|
23
|
+
if (!working(state)) return
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function listen({
|
|
28
|
+
app,
|
|
29
|
+
log,
|
|
30
|
+
}: {
|
|
31
|
+
app: AppSession
|
|
32
|
+
log: CrashLog
|
|
33
|
+
}): Promise<void> {
|
|
34
|
+
const crash = async (): Promise<never> => {
|
|
35
|
+
// Recorded first, so the resumed work doesn't crash again.
|
|
36
|
+
await log.append({ type: 'crashed', payload: {} })
|
|
37
|
+
process.exit(1)
|
|
38
|
+
}
|
|
39
|
+
const { state: pending, index } = await log.state()
|
|
40
|
+
if (pending) return crash()
|
|
41
|
+
const requests = log.stream({ startAfter: index })[Symbol.asyncIterator]()
|
|
42
|
+
// When the work ends, closing the stream ends the loop below.
|
|
43
|
+
const close = (): void => void requests.return?.()
|
|
44
|
+
void workDone(app).then(close, close)
|
|
45
|
+
for await (const event of { [Symbol.asyncIterator]: () => requests })
|
|
46
|
+
if (event.type === 'crash.requested') return crash()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// One listener per app in this process, while its agent works here.
|
|
50
|
+
const listening = new Map<string, Promise<void>>()
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The dev tools' crash: while the app's agent works in this process, a
|
|
54
|
+
* request in its crash log ends the process at once, mid-stream or
|
|
55
|
+
* mid-command, and the scheduler resumes the turn in a fresh invocation.
|
|
56
|
+
* Each step calls this, so work resumed elsewhere gets a listener too.
|
|
57
|
+
*/
|
|
58
|
+
export function crashOnRequest({
|
|
59
|
+
appId,
|
|
60
|
+
app,
|
|
61
|
+
log,
|
|
62
|
+
}: {
|
|
63
|
+
appId: string
|
|
64
|
+
app: AppSession
|
|
65
|
+
log: CrashLog
|
|
66
|
+
}): Promise<void> {
|
|
67
|
+
const current = listening.get(appId)
|
|
68
|
+
if (current) return current
|
|
69
|
+
const started = listen({ app, log }).finally(() => listening.delete(appId))
|
|
70
|
+
listening.set(appId, started)
|
|
71
|
+
return started
|
|
72
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ContractEvent } from 'experimental-a2'
|
|
2
|
+
import { expect, it } from 'vitest'
|
|
3
|
+
import { crashLog, crashLogId, isCrashLogId } from './crashes'
|
|
4
|
+
|
|
5
|
+
type CrashEvents = typeof crashLog.events
|
|
6
|
+
|
|
7
|
+
const pending = (...types: (keyof CrashEvents & string)[]): boolean =>
|
|
8
|
+
types.reduce(
|
|
9
|
+
(state, type, index) =>
|
|
10
|
+
crashLog.reducer.fold(state, {
|
|
11
|
+
id: `event-${index}`,
|
|
12
|
+
type,
|
|
13
|
+
payload: {},
|
|
14
|
+
index: index + 1,
|
|
15
|
+
sessionId: 'log-1',
|
|
16
|
+
createdAt: new Date(index * 1000),
|
|
17
|
+
} as ContractEvent<CrashEvents>),
|
|
18
|
+
crashLog.reducer.initialState,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
it('holds a crash from its request until it happens', () => {
|
|
22
|
+
expect(pending()).toBe(false)
|
|
23
|
+
expect(pending('crash.requested')).toBe(true)
|
|
24
|
+
expect(pending('crash.requested', 'crashed')).toBe(false)
|
|
25
|
+
expect(pending('crash.requested', 'crashed', 'crash.requested')).toBe(true)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it("names one crash log per app, and only an app's", () => {
|
|
29
|
+
const appId = '6f1c1c3e-2f7a-4f0e-9d4e-5b8a9f1d2c3b'
|
|
30
|
+
expect(isCrashLogId(crashLogId(appId))).toBe(true)
|
|
31
|
+
expect(isCrashLogId(appId)).toBe(false)
|
|
32
|
+
expect(isCrashLogId('not-a-uuid:crashes')).toBe(false)
|
|
33
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { contract } from 'experimental-a2'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { isAppId } from './apps'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The dev tools' crashes for one app, in a log of their own so the app's
|
|
7
|
+
* log stays its conversation: a request, then the crash that carried it
|
|
8
|
+
* out. The state is whether one is still pending.
|
|
9
|
+
*/
|
|
10
|
+
export const crashLog = contract({
|
|
11
|
+
name: 'a0-crashes',
|
|
12
|
+
events: {
|
|
13
|
+
'crash.requested': z.object({}),
|
|
14
|
+
crashed: z.object({}),
|
|
15
|
+
},
|
|
16
|
+
reducer: {
|
|
17
|
+
name: 'a0-crashes.v1',
|
|
18
|
+
initialState: false as boolean,
|
|
19
|
+
fold: (_pending, event) => event.type === 'crash.requested',
|
|
20
|
+
},
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const suffix = ':crashes'
|
|
24
|
+
|
|
25
|
+
export const crashLogId = (appId: string): string => `${appId}${suffix}`
|
|
26
|
+
|
|
27
|
+
export const isCrashLogId = (id: string): boolean =>
|
|
28
|
+
id.endsWith(suffix) && isAppId(id.slice(0, -suffix.length))
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import type { ReactNode } from 'react'
|
|
3
|
+
import { memo, useEffect, useState } from 'react'
|
|
4
|
+
import { useSearchParams } from 'next/navigation'
|
|
5
|
+
import { X, Zap } from 'lucide-react'
|
|
6
|
+
import { crashLogId } from './crashes'
|
|
7
|
+
import { setFlag, toggleDevtools } from './flags'
|
|
8
|
+
import { logPage } from './model'
|
|
9
|
+
import { useCrashLog, useSession } from './session'
|
|
10
|
+
|
|
11
|
+
type LogEvent = ReturnType<typeof useSession>['events'][number]
|
|
12
|
+
|
|
13
|
+
const gap = (ms: number): string =>
|
|
14
|
+
ms < 1000 ? `+${Math.round(ms)}ms` : `+${(ms / 1000).toFixed(2)}s`
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The app's raw A2 log, and ways to break the function running its agent:
|
|
18
|
+
* a crash, or a 5-second time limit, so the recovery is there to watch.
|
|
19
|
+
*/
|
|
20
|
+
export function DevTools({
|
|
21
|
+
appId,
|
|
22
|
+
deployed,
|
|
23
|
+
crashesAllowed,
|
|
24
|
+
}: {
|
|
25
|
+
appId: string
|
|
26
|
+
/** Function limits and crashes only mean something on Vercel. */
|
|
27
|
+
deployed: boolean
|
|
28
|
+
crashesAllowed: boolean
|
|
29
|
+
}): ReactNode {
|
|
30
|
+
const { state, events, index, history, hydrated, loadHistory } = useSession()
|
|
31
|
+
const short = useSearchParams().get('limit') === '5s'
|
|
32
|
+
const crashes = useCrashLog({ sessionId: crashLogId(appId) })
|
|
33
|
+
const [error, setError] = useState<string | null>(null)
|
|
34
|
+
|
|
35
|
+
const load = (options: { before?: number }): void => {
|
|
36
|
+
loadHistory({ ...options, limit: logPage }).catch((cause: unknown) =>
|
|
37
|
+
setError(cause instanceof Error ? cause.message : String(cause)),
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
// Opening reads the latest page. The client skips what it already holds,
|
|
41
|
+
// such as the server's seed, so this reads nothing when there's one.
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (hydrated && !history.complete) load({ before: index + 1 })
|
|
44
|
+
}, [hydrated])
|
|
45
|
+
|
|
46
|
+
const crash = (): void => {
|
|
47
|
+
setError(null)
|
|
48
|
+
crashes
|
|
49
|
+
.push({ type: 'crash.requested', payload: {} })
|
|
50
|
+
.catch((cause: unknown) =>
|
|
51
|
+
setError(cause instanceof Error ? cause.message : String(cause)),
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const localOnly = deployed ? undefined : "next dev doesn't limit functions"
|
|
56
|
+
return (
|
|
57
|
+
<section aria-label="Dev tools" className="a0-devtools">
|
|
58
|
+
<header className="a0-devtools-bar">
|
|
59
|
+
<span className="a0-devtools-title">
|
|
60
|
+
Log <span>{events.length} events</span>
|
|
61
|
+
</span>
|
|
62
|
+
{error && (
|
|
63
|
+
<span role="alert" className="a0-error">
|
|
64
|
+
{error}
|
|
65
|
+
</span>
|
|
66
|
+
)}
|
|
67
|
+
<label className="a0-devtools-switch" title={localOnly}>
|
|
68
|
+
<input
|
|
69
|
+
type="checkbox"
|
|
70
|
+
checked={short}
|
|
71
|
+
disabled={!deployed}
|
|
72
|
+
onChange={(event) =>
|
|
73
|
+
setFlag({
|
|
74
|
+
name: 'limit',
|
|
75
|
+
value: event.currentTarget.checked ? '5s' : null,
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
/>
|
|
79
|
+
5-second functions
|
|
80
|
+
</label>
|
|
81
|
+
{/* A crash only means something while the agent works. */}
|
|
82
|
+
{state.active && (
|
|
83
|
+
<button
|
|
84
|
+
type="button"
|
|
85
|
+
className="a0-ghost a0-devtools-crash"
|
|
86
|
+
disabled={!crashesAllowed || crashes.state}
|
|
87
|
+
title={
|
|
88
|
+
crashesAllowed
|
|
89
|
+
? 'Ends the process running the agent, right now'
|
|
90
|
+
: 'Crashes need a preview deployment, or A0_CRASHES=on'
|
|
91
|
+
}
|
|
92
|
+
onClick={crash}
|
|
93
|
+
>
|
|
94
|
+
<Zap size={13} aria-hidden />
|
|
95
|
+
{crashes.state ? 'Crashing…' : 'Crash'}
|
|
96
|
+
</button>
|
|
97
|
+
)}
|
|
98
|
+
<button
|
|
99
|
+
type="button"
|
|
100
|
+
className="a0-icon-button"
|
|
101
|
+
aria-label="Close dev tools"
|
|
102
|
+
title="Close (⌘J)"
|
|
103
|
+
onClick={toggleDevtools}
|
|
104
|
+
>
|
|
105
|
+
<X size={14} />
|
|
106
|
+
</button>
|
|
107
|
+
</header>
|
|
108
|
+
{/* Reversed, so the view stays pinned to the newest event. */}
|
|
109
|
+
<div className="a0-log-scroll">
|
|
110
|
+
<ol className="a0-log">
|
|
111
|
+
{!history.complete && (
|
|
112
|
+
<li>
|
|
113
|
+
<button
|
|
114
|
+
type="button"
|
|
115
|
+
className="a0-ghost a0-log-older"
|
|
116
|
+
disabled={history.loading}
|
|
117
|
+
onClick={() => load({})}
|
|
118
|
+
>
|
|
119
|
+
{history.loading ? 'Loading…' : 'Load older'}
|
|
120
|
+
</button>
|
|
121
|
+
</li>
|
|
122
|
+
)}
|
|
123
|
+
{events.map((event, position) => (
|
|
124
|
+
<LogRow
|
|
125
|
+
key={event.id}
|
|
126
|
+
event={event}
|
|
127
|
+
previous={events[position - 1]}
|
|
128
|
+
/>
|
|
129
|
+
))}
|
|
130
|
+
</ol>
|
|
131
|
+
</div>
|
|
132
|
+
</section>
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Rows keep their events' identity, so a new event renders only its own row.
|
|
137
|
+
const LogRow = memo(function LogRow({
|
|
138
|
+
event,
|
|
139
|
+
previous,
|
|
140
|
+
}: {
|
|
141
|
+
event: LogEvent
|
|
142
|
+
previous: LogEvent | undefined
|
|
143
|
+
}): ReactNode {
|
|
144
|
+
const payload = JSON.stringify(event.payload)
|
|
145
|
+
return (
|
|
146
|
+
<li>
|
|
147
|
+
<details className="a0-log-row">
|
|
148
|
+
<summary title={event.createdAt.toISOString()}>
|
|
149
|
+
<span className="a0-log-index">{event.index}</span>
|
|
150
|
+
<span className="a0-log-gap">
|
|
151
|
+
{previous
|
|
152
|
+
? gap(event.createdAt.getTime() - previous.createdAt.getTime())
|
|
153
|
+
: ''}
|
|
154
|
+
</span>
|
|
155
|
+
<span className="a0-log-type">{event.type}</span>
|
|
156
|
+
<code>
|
|
157
|
+
{payload.length > 240 ? `${payload.slice(0, 240)}…` : payload}
|
|
158
|
+
</code>
|
|
159
|
+
</summary>
|
|
160
|
+
<pre>{JSON.stringify(event.payload, null, 2)}</pre>
|
|
161
|
+
</details>
|
|
162
|
+
</li>
|
|
163
|
+
)
|
|
164
|
+
})
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dev tools' state lives in the query: `?devtools` while they're open,
|
|
3
|
+
* and `limit=5s` for the 5-second switch. Setting a flag is browser-only.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** A query string for a URL, with bare flags as `?devtools`, not `?devtools=`. */
|
|
7
|
+
export function queryString(params: URLSearchParams): string {
|
|
8
|
+
const query = params.toString().replace(/=(?=&|$)/g, '')
|
|
9
|
+
return query && `?${query}`
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Sets or clears one flag, in place, without a navigation. */
|
|
13
|
+
export function setFlag({
|
|
14
|
+
name,
|
|
15
|
+
value,
|
|
16
|
+
}: {
|
|
17
|
+
name: 'devtools' | 'limit'
|
|
18
|
+
value: string | null
|
|
19
|
+
}): void {
|
|
20
|
+
const params = new URLSearchParams(location.search)
|
|
21
|
+
if (value === null) params.delete(name)
|
|
22
|
+
else params.set(name, value)
|
|
23
|
+
history.replaceState(null, '', `${location.pathname}${queryString(params)}`)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const toggleDevtools = (): void =>
|
|
27
|
+
setFlag({
|
|
28
|
+
name: 'devtools',
|
|
29
|
+
value: new URLSearchParams(location.search).has('devtools') ? null : '',
|
|
30
|
+
})
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { harnessEvents } from '../../server'
|
|
2
|
+
|
|
3
|
+
/** The events of apps whose agent runs FX's loop. */
|
|
4
|
+
const events = harnessEvents('fx')
|
|
5
|
+
|
|
6
|
+
export { events as GET, events as POST }
|
|
7
|
+
|
|
8
|
+
// Agent work started by a push runs after the response, up to this long.
|
|
9
|
+
export const maxDuration = 300
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { harnessEvents } from '../../../server'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pushes from the dev tools' 5-second switch. Agent work they start stops
|
|
5
|
+
* with the function, and the scheduler resumes it in a full-length one.
|
|
6
|
+
*/
|
|
7
|
+
const events = harnessEvents('fx')
|
|
8
|
+
|
|
9
|
+
export { events as POST }
|
|
10
|
+
|
|
11
|
+
export const maxDuration = 5
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Metadata } from 'next'
|
|
2
|
+
import type { ReactNode } from 'react'
|
|
3
|
+
import { cookies } from 'next/headers'
|
|
4
|
+
import { appList, isAppId } from './apps'
|
|
5
|
+
import { listCookie, parseWidths, widthsCookie } from './cookies'
|
|
6
|
+
import { listServer } from './server'
|
|
7
|
+
import { A0Shell } from './shell'
|
|
8
|
+
import './a0.css'
|
|
9
|
+
|
|
10
|
+
export const metadata: Metadata = { title: 'A0 · a2 example' }
|
|
11
|
+
|
|
12
|
+
/** The apps list and the columns stay put while the app beside them changes. */
|
|
13
|
+
export default async function A0Layout({
|
|
14
|
+
children,
|
|
15
|
+
}: {
|
|
16
|
+
children: ReactNode
|
|
17
|
+
}): Promise<ReactNode> {
|
|
18
|
+
const jar = await cookies()
|
|
19
|
+
const saved = jar.get(listCookie)?.value
|
|
20
|
+
const listId = saved && isAppId(saved) ? saved : null
|
|
21
|
+
const list = listId
|
|
22
|
+
? { id: listId, initial: await listServer.session(listId).state() }
|
|
23
|
+
: {
|
|
24
|
+
// Becomes this browser's list with its first write.
|
|
25
|
+
id: crypto.randomUUID(),
|
|
26
|
+
initial: { state: appList.reducer.initialState, index: 0 },
|
|
27
|
+
}
|
|
28
|
+
return (
|
|
29
|
+
<A0Shell
|
|
30
|
+
list={list}
|
|
31
|
+
widths={parseWidths(jar.get(widthsCookie)?.value)}
|
|
32
|
+
renderedAt={Date.now()}
|
|
33
|
+
newAppId={crypto.randomUUID()}
|
|
34
|
+
>
|
|
35
|
+
{children}
|
|
36
|
+
</A0Shell>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { isAppId } from '../apps'
|
|
2
|
+
import { listServer } from '../server'
|
|
3
|
+
|
|
4
|
+
function list(request: Request): Promise<Response> {
|
|
5
|
+
return listServer.fetch(request, {
|
|
6
|
+
authorize: (operation) => isAppId(operation.sessionId),
|
|
7
|
+
})
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export { list as GET, list as POST }
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { ContractEvent } from 'experimental-a2'
|
|
2
|
+
import { expect, it } from 'vitest'
|
|
3
|
+
import {
|
|
4
|
+
appAgent,
|
|
5
|
+
defaultModelChoice,
|
|
6
|
+
harnessAgents,
|
|
7
|
+
harnessFrom,
|
|
8
|
+
} from './model'
|
|
9
|
+
|
|
10
|
+
const chosen = (payload: unknown): ContractEvent<typeof appAgent.events> =>
|
|
11
|
+
({
|
|
12
|
+
id: 'choice',
|
|
13
|
+
index: 1,
|
|
14
|
+
sessionId: 'app',
|
|
15
|
+
createdAt: new Date(0),
|
|
16
|
+
type: 'model.chosen',
|
|
17
|
+
payload,
|
|
18
|
+
}) as ContractEvent<typeof appAgent.events>
|
|
19
|
+
|
|
20
|
+
const validate = (payload: unknown) =>
|
|
21
|
+
appAgent.events['model.chosen']['~standard'].validate(payload)
|
|
22
|
+
|
|
23
|
+
it('defaults to Opus 5.5 in fast mode', () => {
|
|
24
|
+
expect(appAgent.reducer.initialState.modelChoice).toEqual({
|
|
25
|
+
model: 'anthropic/claude-opus-5.5',
|
|
26
|
+
reasoning: 'provider-default',
|
|
27
|
+
fast: true,
|
|
28
|
+
})
|
|
29
|
+
expect(defaultModelChoice).toEqual(appAgent.reducer.initialState.modelChoice)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it("keeps the latest choice in the app's state", () => {
|
|
33
|
+
const choice = {
|
|
34
|
+
model: 'openai/gpt-5.6-luna',
|
|
35
|
+
reasoning: 'high',
|
|
36
|
+
fast: false,
|
|
37
|
+
}
|
|
38
|
+
const state = appAgent.reducer.fold(
|
|
39
|
+
appAgent.reducer.initialState,
|
|
40
|
+
chosen(choice),
|
|
41
|
+
)
|
|
42
|
+
expect(state.modelChoice).toEqual(choice)
|
|
43
|
+
expect(state.messages).toEqual([])
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('fills in defaults and rejects choices a browser could write badly', () => {
|
|
47
|
+
expect(validate({ model: ' anthropic/claude-sonnet-5 ' })).toEqual({
|
|
48
|
+
value: {
|
|
49
|
+
model: 'anthropic/claude-sonnet-5',
|
|
50
|
+
reasoning: 'provider-default',
|
|
51
|
+
fast: false,
|
|
52
|
+
},
|
|
53
|
+
})
|
|
54
|
+
for (const payload of [
|
|
55
|
+
{},
|
|
56
|
+
{ model: ' ' },
|
|
57
|
+
{ model: 'openai/gpt-5.6-luna', reasoning: 'maximum' },
|
|
58
|
+
{ model: 'openai/gpt-5.6-luna', fast: 'yes' },
|
|
59
|
+
])
|
|
60
|
+
expect(validate(payload)).toHaveProperty('issues')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it("reads the app's harness from its URL, the AI SDK's by default", () => {
|
|
64
|
+
expect(harnessFrom('fx')).toBe('fx')
|
|
65
|
+
expect(harnessFrom(null)).toBe('ai-sdk')
|
|
66
|
+
expect(harnessFrom('other')).toBe('ai-sdk')
|
|
67
|
+
// Each harness's agent keeps its own logs.
|
|
68
|
+
expect(Object.values(harnessAgents).map((agent) => agent.name)).toEqual([
|
|
69
|
+
'a0',
|
|
70
|
+
'a0-fx',
|
|
71
|
+
'a0-pi',
|
|
72
|
+
])
|
|
73
|
+
})
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { UIMessage } from 'ai'
|
|
2
|
+
import { agentContract } from 'experimental-a2/ai'
|
|
3
|
+
import { z } from 'zod'
|
|
4
|
+
|
|
5
|
+
/** Log events the dev tools read at a time. */
|
|
6
|
+
export const logPage = 200
|
|
7
|
+
|
|
8
|
+
/** The port the agent serves its Next.js app on, and the preview shows. */
|
|
9
|
+
export const appPort = 3000
|
|
10
|
+
|
|
11
|
+
export const reasoningSchema = z.enum([
|
|
12
|
+
'provider-default',
|
|
13
|
+
'none',
|
|
14
|
+
'minimal',
|
|
15
|
+
'low',
|
|
16
|
+
'medium',
|
|
17
|
+
'high',
|
|
18
|
+
'xhigh',
|
|
19
|
+
])
|
|
20
|
+
|
|
21
|
+
/** What the app's next request runs with. The browser picks it and the log keeps it. */
|
|
22
|
+
const modelChoiceSchema = z.object({
|
|
23
|
+
model: z.string().trim().min(1),
|
|
24
|
+
reasoning: reasoningSchema.default('provider-default'),
|
|
25
|
+
fast: z.boolean().default(false),
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
export type ModelChoice = z.output<typeof modelChoiceSchema>
|
|
29
|
+
|
|
30
|
+
export const defaultModelChoice: ModelChoice = {
|
|
31
|
+
model: 'anthropic/claude-opus-5.5',
|
|
32
|
+
reasoning: 'provider-default',
|
|
33
|
+
fast: true,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type AppMessage = UIMessage
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The loop an app's agent runs: the AI SDK's, FX's, or Pi's. An
|
|
40
|
+
* app picks it before its first message and keeps it.
|
|
41
|
+
*/
|
|
42
|
+
export const harnessSchema = z.enum(['ai-sdk', 'fx', 'pi'])
|
|
43
|
+
|
|
44
|
+
export type Harness = z.output<typeof harnessSchema>
|
|
45
|
+
|
|
46
|
+
export const harnessLabels: Record<Harness, string> = {
|
|
47
|
+
'ai-sdk': 'AI SDK',
|
|
48
|
+
fx: 'FX',
|
|
49
|
+
pi: 'Pi',
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The harness an app's URL names, `?harness=fx`; the AI SDK's without one. */
|
|
53
|
+
export const harnessFrom = (value: unknown): Harness =>
|
|
54
|
+
harnessSchema.safeParse(value).data ?? 'ai-sdk'
|
|
55
|
+
|
|
56
|
+
const appContract = (name: string) =>
|
|
57
|
+
agentContract({
|
|
58
|
+
name,
|
|
59
|
+
events: { 'model.chosen': modelChoiceSchema },
|
|
60
|
+
reducer: {
|
|
61
|
+
name: 'a0.model-choice.v1',
|
|
62
|
+
initialState: { modelChoice: defaultModelChoice },
|
|
63
|
+
fold: (state, event) =>
|
|
64
|
+
event.type === 'model.chosen' ? { modelChoice: event.payload } : state,
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
/** The coding agent's contract, shared by the server and the browser. */
|
|
69
|
+
export const appAgent = appContract('a0')
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Each harness's agent keeps its own logs. They differ only in name, so the
|
|
73
|
+
* browser follows any of them with `appAgent`.
|
|
74
|
+
*/
|
|
75
|
+
export const harnessAgents = {
|
|
76
|
+
'ai-sdk': appAgent,
|
|
77
|
+
fx: appContract('a0-fx'),
|
|
78
|
+
pi: appContract('a0-pi'),
|
|
79
|
+
} satisfies Record<Harness, typeof appAgent>
|
|
80
|
+
|
|
81
|
+
export const toolLabels: Record<string, string> = {
|
|
82
|
+
bash: 'Running a command',
|
|
83
|
+
show_preview: 'Showing the app',
|
|
84
|
+
// Pi's own tools.
|
|
85
|
+
read: 'Reading a file',
|
|
86
|
+
write: 'Writing a file',
|
|
87
|
+
edit: 'Editing a file',
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** While the model is still writing a call's input. */
|
|
91
|
+
export const writingLabels: Record<string, string> = {
|
|
92
|
+
bash: 'Writing a command',
|
|
93
|
+
write: 'Writing a file',
|
|
94
|
+
edit: 'Writing an edit',
|
|
95
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { redirect } from 'next/navigation'
|
|
2
|
+
import { queryString } from './flags'
|
|
3
|
+
|
|
4
|
+
/** A new app is just an id with no log yet; the query keeps the dev tools. */
|
|
5
|
+
export default async function NewApp({
|
|
6
|
+
searchParams,
|
|
7
|
+
}: {
|
|
8
|
+
searchParams: Promise<Record<string, string | string[] | undefined>>
|
|
9
|
+
}): Promise<never> {
|
|
10
|
+
const params = new URLSearchParams()
|
|
11
|
+
for (const [name, value] of Object.entries(await searchParams))
|
|
12
|
+
for (const one of [value ?? []].flat()) params.append(name, one)
|
|
13
|
+
redirect(`/a0/${crypto.randomUUID()}${queryString(params)}`)
|
|
14
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { harnessEvents } from '../../server'
|
|
2
|
+
|
|
3
|
+
/** The events of apps whose agent runs Pi's loop. */
|
|
4
|
+
const events = harnessEvents('pi')
|
|
5
|
+
|
|
6
|
+
export { events as GET, events as POST }
|
|
7
|
+
|
|
8
|
+
// Agent work started by a push runs after the response, up to this long.
|
|
9
|
+
export const maxDuration = 300
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { harnessEvents } from '../../../server'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pushes from the dev tools' 5-second switch. Agent work they start stops
|
|
5
|
+
* with the function, and the scheduler resumes it in a full-length one.
|
|
6
|
+
*/
|
|
7
|
+
const events = harnessEvents('pi')
|
|
8
|
+
|
|
9
|
+
export { events as POST }
|
|
10
|
+
|
|
11
|
+
export const maxDuration = 5
|