experimental-a2 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +360 -0
- package/dist/{actor-ohPC-81x.d.ts → actor-DBbe5xeE.d.ts} +21 -11
- package/dist/actor-DBbe5xeE.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-CrLYwNEx.js → ai-UrWfKeuv.js} +966 -541
- package/dist/ai-UrWfKeuv.js.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-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 +80 -36
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +466 -265
- package/dist/ai-server.js.map +1 -1
- package/dist/{ai-HJ9fHfYI.d.ts → ai-vpGtcaCB.d.ts} +115 -101
- package/dist/ai-vpGtcaCB.d.ts.map +1 -0
- package/dist/ai.d.ts +2 -2
- package/dist/ai.js +2 -2
- package/dist/{client-BYzHjkwU.js → client-COaiaDub.js} +447 -80
- package/dist/client-COaiaDub.js.map +1 -0
- package/dist/{client-CzyacQpJ.d.ts → client-qRnRUzBS.d.ts} +14 -13
- package/dist/client-qRnRUzBS.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-BrLso5x_.d.ts} +7 -3
- package/dist/initial-snapshot-BrLso5x_.d.ts.map +1 -0
- package/dist/{internal-Dq2qYxou.js → internal-CbVvEwad.js} +23 -2
- package/dist/internal-CbVvEwad.js.map +1 -0
- package/dist/otel.d.ts +1 -1
- package/dist/react-BbJ-g6Uu.d.ts +86 -0
- package/dist/react-BbJ-g6Uu.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-BYu6DT7h.d.ts} +45 -29
- package/dist/reducer-BYu6DT7h.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-DbD7IJVK.d.ts → server-CGM8WzRG.d.ts} +87 -26
- package/dist/server-CGM8WzRG.d.ts.map +1 -0
- package/dist/{server-BD5ckxZb.js → server-Rj64fNvX.js} +854 -202
- package/dist/server-Rj64fNvX.js.map +1 -0
- package/dist/server.d.ts +4 -4
- package/dist/server.js +2 -2
- package/dist/{errors-DCk6ch5n.js → session-clear-DGI4jTQ5.js} +17 -2
- package/dist/session-clear-DGI4jTQ5.js.map +1 -0
- package/dist/session-clear-Dshpuhff.d.ts +12 -0
- package/dist/session-clear-Dshpuhff.d.ts.map +1 -0
- package/dist/{store-DyZM6fS5.d.ts → store-Gpkq7WBo.d.ts} +44 -3
- package/dist/store-Gpkq7WBo.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 +182 -69
- package/dist/store-memory.js.map +1 -1
- package/dist/{store-polling-CmxUbV93.js → store-polling-4_y8lETm.js} +21 -4
- package/dist/store-polling-4_y8lETm.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 +399 -108
- package/dist/store-postgres.js.map +1 -1
- package/dist/{store-presence-polling-C7-XZyW9.js → store-presence-polling-Q1C-k2dd.js} +3 -3
- package/dist/{store-presence-polling-C7-XZyW9.js.map → store-presence-polling-Q1C-k2dd.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-tqWNnMRu.js +2374 -0
- package/dist/store-redis-notify-tqWNnMRu.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 +166 -32
- 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-cZH-cibj.d.ts} +2 -2
- package/dist/telemetry-cZH-cibj.d.ts.map +1 -0
- package/dist/{wire-BO5wWCb1.js → wire-DjfHOvdW.js} +132 -10
- package/dist/wire-DjfHOvdW.js.map +1 -0
- package/docs/01-quickstart.mdx +2 -2
- package/docs/actors/01-introduction.mdx +4 -0
- package/docs/actors/04-routes.mdx +14 -1
- package/docs/concepts/01-contracts.mdx +95 -0
- package/docs/concepts/04-state.mdx +3 -3
- package/docs/guides/01-timers.mdx +22 -14
- package/docs/guides/02-cancellation.mdx +1 -1
- package/docs/guides/03-react.mdx +268 -328
- package/docs/guides/04-local-first.mdx +32 -73
- package/docs/guides/05-production.mdx +40 -27
- package/docs/guides/06-ai-agents.mdx +680 -580
- package/docs/guides/09-presence.mdx +3 -7
- package/docs/guides/10-transports.mdx +5 -5
- package/docs/index.mdx +10 -18
- package/docs/reference/01-api.mdx +662 -195
- package/docs/reference/02-errors.mdx +2 -1
- package/examples/playground/DEPLOY.md +52 -6
- package/examples/playground/app/a0/[appId]/loading.tsx +16 -0
- package/examples/playground/app/a0/[appId]/page.tsx +48 -0
- package/examples/playground/app/a0/a0.css +818 -0
- package/examples/playground/app/a0/app-list.tsx +211 -0
- package/examples/playground/app/a0/app-view.tsx +55 -0
- package/examples/playground/app/a0/apps/[appId]/preview/route.ts +27 -0
- package/examples/playground/app/a0/apps/[appId]/route.ts +23 -0
- package/examples/playground/app/a0/apps.test.ts +116 -0
- package/examples/playground/app/a0/apps.ts +87 -0
- package/examples/playground/app/a0/chat.tsx +364 -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/layout.tsx +38 -0
- package/examples/playground/app/a0/list/route.ts +10 -0
- package/examples/playground/app/a0/model.test.ts +56 -0
- package/examples/playground/app/a0/model.ts +58 -0
- package/examples/playground/app/a0/page.tsx +14 -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 +69 -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 +158 -0
- package/examples/playground/app/a0/session.ts +47 -0
- package/examples/playground/app/a0/shell.tsx +150 -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 +98 -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 +15 -14
- package/examples/playground/app/agent/session.ts +5 -6
- package/examples/playground/app/agent/telemetry.test.ts +168 -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 +11 -11
- 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 +108 -0
- package/examples/playground/app/chat/server.ts +69 -73
- 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/package.json +10 -2
- package/examples/playground/scripts/a0-template.ts +77 -0
- package/examples/playground/vercel.json +18 -0
- package/package.json +3 -1
- 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 +2343 -0
- package/src/ai-app-state.ts +91 -0
- package/src/ai-client-state.ts +39 -2
- 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 +94 -55
- package/src/ai-control.ts +2 -0
- package/src/ai-coordinator.ts +23 -22
- package/src/ai-message-base.ts +209 -0
- package/src/ai-message-projection.ts +382 -58
- 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-server.browser.ts +2 -2
- package/src/ai-server.ts +20 -1951
- package/src/ai-stored-state.ts +48 -163
- package/src/ai.ts +343 -276
- 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 +45 -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 +92 -10
- 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 +957 -237
- 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 +220 -78
- package/src/store-polling.ts +18 -4
- package/src/store-postgres.ts +461 -114
- package/src/store-redis-core.ts +1260 -434
- package/src/store-redis-http.ts +18 -2
- package/src/store-redis-notify.ts +260 -85
- package/src/store-redis.ts +19 -9
- package/src/store-sqlite.ts +259 -45
- package/src/store.ts +46 -1
- package/src/stream-buffer.ts +43 -0
- package/src/telemetry.ts +13 -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-CrLYwNEx.js.map +0 -1
- package/dist/ai-HJ9fHfYI.d.ts.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/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
|
@@ -12,12 +12,16 @@ a2.contract(options: {
|
|
|
12
12
|
name: string
|
|
13
13
|
events: Record<string, StandardSchemaV1>
|
|
14
14
|
presence?: Record<string, StandardSchemaV1>
|
|
15
|
+
reducer?: { name: string; initialState: S; stateSchema?: StandardSchemaV1; fold: (state: S, event: Event) => S }
|
|
15
16
|
}): Contract
|
|
16
17
|
```
|
|
17
18
|
|
|
18
19
|
Defines one kind of lifecycle: a name plus the events it understands. The name
|
|
19
20
|
must be unique per app; it identifies the contract in storage and queue
|
|
20
|
-
messages. The
|
|
21
|
+
messages. The name may not contain the control character U+001F, and event
|
|
22
|
+
types may not start with `a2.`, which A2 reserves for its own events, such as
|
|
23
|
+
`a2.cleared`. The result is a plain,
|
|
24
|
+
importable, **isomorphic** value: the server
|
|
21
25
|
implements it, reducers derive from it, the browser types its pushes off it.
|
|
22
26
|
See [Contracts and sessions](/concepts/contracts).
|
|
23
27
|
|
|
@@ -37,17 +41,20 @@ Preserves the literal types in a heterogeneous handler-returned event array.
|
|
|
37
41
|
It returns the same events as a readonly tuple; A2 validates them when the
|
|
38
42
|
handler completes.
|
|
39
43
|
|
|
40
|
-
### `
|
|
44
|
+
### `reducer(options)`
|
|
41
45
|
|
|
42
46
|
```ts
|
|
43
|
-
|
|
47
|
+
reducer(options: {
|
|
48
|
+
contract: Contract
|
|
44
49
|
name: string // identity; keys cached snapshots
|
|
45
50
|
initialState: S // the seed (schema input, when stateSchema is set)
|
|
46
51
|
stateSchema?: StandardSchemaV1 // declares S, checks initialState, guards cached folds
|
|
47
52
|
}): { fold(fn: (state: S, event: Event) => S): Reducer<S> }
|
|
48
53
|
```
|
|
49
54
|
|
|
50
|
-
|
|
55
|
+
Builds a separate reducer for server reads or for the contract's optional
|
|
56
|
+
`reducer` field. The contract publishes only that selected reducer to clients.
|
|
57
|
+
Inline definitions include `fold` directly. The standalone builder uses two steps: the first call
|
|
51
58
|
fixes the state and event types, so `.fold()` receives fully concrete
|
|
52
59
|
ones. Literal unions in your state survive, with zero annotations.
|
|
53
60
|
|
|
@@ -88,6 +95,15 @@ ID generation unchanged. Changing an existing application's ID recipe changes
|
|
|
88
95
|
retry identity, so retries of previously committed requests need to retain
|
|
89
96
|
their original IDs.
|
|
90
97
|
|
|
98
|
+
### `SessionCleared`
|
|
99
|
+
|
|
100
|
+
The marker a [clear](#sessionclearoptions) leaves in `session.stream()`: an
|
|
101
|
+
`Event` whose `type` is `'a2.cleared'` and whose `payload` is `null`. It
|
|
102
|
+
never reaches handlers or `history()`. Comparing `event.type` with that string
|
|
103
|
+
narrows a stream item to `SessionCleared`. The session client handles the
|
|
104
|
+
marker on its own, so this matters only when you read the server stream
|
|
105
|
+
yourself.
|
|
106
|
+
|
|
91
107
|
## `experimental-a2/server`
|
|
92
108
|
|
|
93
109
|
### `handler(entry)`
|
|
@@ -332,6 +348,9 @@ export const POST = ordersServer.fetch
|
|
|
332
348
|
A plain GET streams one session over SSE, resumed after the `index` query
|
|
333
349
|
parameter. A GET with `gte` and `lte` returns the closed history range as JSON
|
|
334
350
|
and sets `a2-history-covered` to `true` when every requested index was returned.
|
|
351
|
+
When the range includes the latest clear marker, it also sets `a2-clear-index`,
|
|
352
|
+
and coverage counts only the requested indexes after that marker. A range
|
|
353
|
+
entirely below the marker returns `[]`.
|
|
335
354
|
POST accepts `{ sessionId, events, presence? }` and returns the appended events.
|
|
336
355
|
Presence is inferred from the contract. A contract that declares presence
|
|
337
356
|
streams it and accepts presence pushes automatically.
|
|
@@ -491,6 +510,7 @@ type StoredEvent = Event & {
|
|
|
491
510
|
lastFailedAttempt: number | null
|
|
492
511
|
lastError: string | null
|
|
493
512
|
failedAt: Date | null
|
|
513
|
+
clearIndex?: number // on claimed events, once the session has been cleared
|
|
494
514
|
}
|
|
495
515
|
|
|
496
516
|
type StoreAppendResult = {
|
|
@@ -511,8 +531,17 @@ interface A2Store {
|
|
|
511
531
|
append(
|
|
512
532
|
sessionId: string,
|
|
513
533
|
events: AppendEvent[],
|
|
534
|
+
options?: { originIndex?: number },
|
|
514
535
|
): Promise<StoreAppendResult>
|
|
515
536
|
|
|
537
|
+
// optional together; required for session.clear()
|
|
538
|
+
clear?(options: {
|
|
539
|
+
sessionId: string
|
|
540
|
+
ifLastIndex?: number
|
|
541
|
+
cause?: EventCause
|
|
542
|
+
}): Promise<{ cleared: boolean; index: number; clearIndex: number }>
|
|
543
|
+
reclaim?(sessionId: string): Promise<{ deleted: number; done: boolean }>
|
|
544
|
+
|
|
516
545
|
claimAvailable(options: {
|
|
517
546
|
sessionId: string
|
|
518
547
|
holder: string
|
|
@@ -556,6 +585,22 @@ interface A2Store {
|
|
|
556
585
|
| `renewClaims` | Extend the listed live claims still owned by the holder. `renewed` lists the claims that remain owned; `superseded` lists the claims whose event's `attemptCount` passed the listed attempt. An expired claim no successor took appears in neither: it may still complete. Renewal never revives an expired claim. |
|
|
557
586
|
| `completeAttempt` | Fence on the current attempt, mark the parent processed, store its exact ordered `returnedEventIds`, and append the returned batch in the same transaction. A same-attempt retry returns the committed children; a stale attempt returns `superseded`. |
|
|
558
587
|
| `failAttempt` | Record one current caught failure, clear its claim, and dead-letter at `maxFailures`. A repeated failure acknowledgment is idempotent; stale attempts return `superseded`. |
|
|
588
|
+
| `clear` | Serialized with `append`. Check `ifLastIndex` against the head and the latest marker, and append a settled clear marker at the next index, touching no existing rows. A `cause` is fenced like a handler append. |
|
|
589
|
+
| `reclaim` | Delete one bounded batch of rows below the latest clear marker, with their bookkeeping, event-id records, lane pointers, and cached state. Report how many rows went and `done` when nothing is left. Never wait on a lock that other methods hold. |
|
|
590
|
+
|
|
591
|
+
A store that supports clearing treats every event at or below the latest clear
|
|
592
|
+
marker as superseded, whether or not its row still exists. `claimAvailable`
|
|
593
|
+
never claims it, lane readiness at append and at completion ignores it (a
|
|
594
|
+
dead-lettered lane head stops blocking), `renewClaims` reports it in
|
|
595
|
+
`superseded`, `completeAttempt` and `failAttempt` return `superseded`, and an
|
|
596
|
+
`append` whose `cause` names it throws `SUPERSEDED_ATTEMPT`. An `append` whose
|
|
597
|
+
`originIndex` is at or below the marker throws `SESSION_CLEARED`. Reads start
|
|
598
|
+
at the marker, `readState` returns its index as `clearIndex`, and claimed
|
|
599
|
+
events carry it as `clearIndex`. In `append` and `completeAttempt`, an event id
|
|
600
|
+
whose row is below this session's marker is free to reuse. Each check belongs
|
|
601
|
+
inside the operation's existing atomic step. Without `clear` and `reclaim`,
|
|
602
|
+
`session.clear()` and `session.schedule.clear()` throw a `TypeError` before
|
|
603
|
+
writing anything.
|
|
559
604
|
|
|
560
605
|
`attemptCount` counts claims, including abandoned ones. `failureCount` counts
|
|
561
606
|
caught failures and dead-letters at ten. See
|
|
@@ -578,49 +623,61 @@ Custom scheduler adapters implement two methods:
|
|
|
578
623
|
| Method | Effect |
|
|
579
624
|
| --- | --- |
|
|
580
625
|
| `schedule(task: SchedulerTask): Promise<void>` | put one versioned task on durable delivery infrastructure |
|
|
581
|
-
| `handler(...servers)` | return the route that receives drain and
|
|
626
|
+
| `handler(...servers)` | return the route that receives drain, append, and clear tasks |
|
|
582
627
|
|
|
583
628
|
Application routes call `scheduler.handler(...servers)` on the configured
|
|
584
629
|
adapter. The handler requires at least one drainable server and rejects
|
|
585
630
|
duplicate contract names synchronously.
|
|
586
631
|
|
|
587
|
-
`SchedulerTask` is `SchedulerDrainTask | SchedulerAppendTask
|
|
588
|
-
plus `ScheduledEvent`, are exported
|
|
632
|
+
`SchedulerTask` is `SchedulerDrainTask | SchedulerAppendTask |
|
|
633
|
+
SchedulerClearTask`. These task types, plus `ScheduledEvent`, are exported
|
|
634
|
+
from `experimental-a2/server`.
|
|
589
635
|
|
|
590
636
|
| Task | Fields |
|
|
591
637
|
| --- | --- |
|
|
592
638
|
| `SchedulerDrainTask` | `version: 1`, `kind: 'drain'`, `contract`, `sessionId`, required epoch-ms `dueAt` |
|
|
593
|
-
| `SchedulerAppendTask` | `version: 1`, `kind: 'append'`, stable `id`, `contract`, `sessionId`, required epoch-ms `dueAt`, readonly `events` |
|
|
639
|
+
| `SchedulerAppendTask` | `version: 1`, `kind: 'append'`, stable `id`, `contract`, `sessionId`, required epoch-ms `dueAt`, readonly `events`, optional `originIndex` |
|
|
640
|
+
| `SchedulerClearTask` | `version: 1`, `kind: 'clear'`, stable `id`, `contract`, `sessionId`, required epoch-ms `dueAt`, `ifLastIndex` |
|
|
594
641
|
| `ScheduledEvent` | fixed `id`, `type`, and JSON `payload` |
|
|
595
642
|
|
|
596
643
|
Drain tasks wake pending work and carry no application payload. Append tasks
|
|
597
|
-
carry the events for `session.schedule
|
|
644
|
+
carry the events for `session.schedule`; `originIndex` is the scheduling
|
|
645
|
+
handler's event, absent for root schedules. Clear tasks carry the condition
|
|
646
|
+
for `session.schedule.clear`.
|
|
598
647
|
|
|
599
|
-
Custom adapters deliver an authenticated, normalized append
|
|
600
|
-
server
|
|
648
|
+
Custom adapters deliver an authenticated, normalized append or clear task
|
|
649
|
+
through the server helpers:
|
|
601
650
|
|
|
602
651
|
```ts custom-scheduler.ts
|
|
603
652
|
import {
|
|
604
653
|
deliverSchedulerAppend,
|
|
654
|
+
deliverSchedulerClear,
|
|
605
655
|
type DrainableServer,
|
|
606
656
|
type SchedulerAppendTask,
|
|
657
|
+
type SchedulerClearTask,
|
|
607
658
|
} from 'experimental-a2/server'
|
|
608
659
|
|
|
609
660
|
export async function deliver(
|
|
610
661
|
server: DrainableServer,
|
|
611
|
-
task: SchedulerAppendTask,
|
|
662
|
+
task: SchedulerAppendTask | SchedulerClearTask,
|
|
612
663
|
) {
|
|
613
|
-
await
|
|
664
|
+
if (task.kind === 'clear') await deliverSchedulerClear(server, task)
|
|
665
|
+
else await deliverSchedulerAppend(server, task)
|
|
614
666
|
}
|
|
615
667
|
```
|
|
616
668
|
|
|
617
|
-
Import
|
|
618
|
-
|
|
619
|
-
the ordinary top-level append path. The target server must have a
|
|
620
|
-
When the append leaves pending handler work, the helper resolves
|
|
621
|
-
recovery watchdog is durable; a send failure keeps the provider
|
|
622
|
-
retryable.
|
|
623
|
-
|
|
669
|
+
Import both from `experimental-a2/server`. Each verifies the task and server
|
|
670
|
+
contract match. `deliverSchedulerAppend` revalidates the carried event inputs
|
|
671
|
+
and uses the ordinary top-level append path. The target server must have a
|
|
672
|
+
scheduler. When the append leaves pending handler work, the helper resolves
|
|
673
|
+
only after a recovery watchdog is durable; a send failure keeps the provider
|
|
674
|
+
delivery retryable. A task whose `originIndex` was cleared resolves without
|
|
675
|
+
appending.
|
|
676
|
+
|
|
677
|
+
`deliverSchedulerClear` runs a conditional `clear({ ifLastIndex })` and
|
|
678
|
+
resolves after its deletion finishes. A store failure rejects, so the provider
|
|
679
|
+
redelivers and the retry finishes deleting. Authenticate the transport and
|
|
680
|
+
validate the task envelope before calling either helper.
|
|
624
681
|
|
|
625
682
|
## Session
|
|
626
683
|
|
|
@@ -708,10 +765,100 @@ ordinary top-level append validates that input, so a schema
|
|
|
708
765
|
transform produces the stored payload from the original input. The events have
|
|
709
766
|
`cause: null` and enter normal handlers, lanes, state, and streams.
|
|
710
767
|
|
|
768
|
+
A task scheduled from a handler belongs to its triggering event's lifetime. If
|
|
769
|
+
the session is cleared before delivery, the task is acknowledged without
|
|
770
|
+
appending. Once a session has been cleared, handler task ids also include the
|
|
771
|
+
clear, so a trigger id reused in the new lifetime schedules a new task. A root
|
|
772
|
+
task has no triggering event, so its events join whichever lifetime exists when
|
|
773
|
+
it is delivered, and its name identifies one task across lifetimes. Put
|
|
774
|
+
something from the new lifetime in the name when you schedule again after a
|
|
775
|
+
clear.
|
|
776
|
+
|
|
711
777
|
There is no cancellation, reschedule, cron, or timer-listing API. See
|
|
712
778
|
[Timers and delays](/guides/timers) for stale-timer guards, provider limits,
|
|
713
779
|
and deployment compatibility.
|
|
714
780
|
|
|
781
|
+
### `session.clear(options?)`
|
|
782
|
+
|
|
783
|
+
```ts
|
|
784
|
+
session.clear(options?: { ifLastIndex?: number }): Promise<{
|
|
785
|
+
cleared: boolean
|
|
786
|
+
index: number
|
|
787
|
+
}>
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
End the session's current lifetime. One atomic step checks the condition and
|
|
791
|
+
appends a reserved clear marker at the next index. The session id stays usable
|
|
792
|
+
and indexes never restart. `ifLastIndex` is a non-negative safe integer; the
|
|
793
|
+
first matching rule decides:
|
|
794
|
+
|
|
795
|
+
| When | Result |
|
|
796
|
+
| --- | --- |
|
|
797
|
+
| the latest marker is at `ifLastIndex + 1` | `{ cleared: true, index }`, a retry of that clear |
|
|
798
|
+
| `ifLastIndex` is given and is not the head | `{ cleared: false, index }`, nothing changes |
|
|
799
|
+
| the head is `0` or already a clear marker | `{ cleared: true, index }`, nothing changes |
|
|
800
|
+
| otherwise | `{ cleared: true, index }`, the new marker |
|
|
801
|
+
|
|
802
|
+
`index` is always the head after the call. Every event at or below the marker
|
|
803
|
+
is superseded: it is never dispatched again and never blocks its lane, its
|
|
804
|
+
running handler's appends throw `SUPERSEDED_ATTEMPT`, and that handler's state
|
|
805
|
+
reads never fold the new lifetime: they throw `SESSION_CLEARED`, except that
|
|
806
|
+
after a clear from another instance, a read bounded at its own event may return
|
|
807
|
+
that event's state from memory until the handler's `ctx.signal` aborts. Pushes and appends after the marker start the
|
|
808
|
+
new lifetime.
|
|
809
|
+
|
|
810
|
+
`clear()` then deletes the old events, their bookkeeping and event-id records,
|
|
811
|
+
and cached state below the marker in short batches, and resolves when they are
|
|
812
|
+
gone. Other work on the session keeps running between batches. If deletion
|
|
813
|
+
fails, the call rejects with the session already cleared; any later clear of
|
|
814
|
+
the session, including a retry, finishes the deletion. There is no background
|
|
815
|
+
sweep. Event ids from the old lifetime can be used again. Presence is not
|
|
816
|
+
cleared.
|
|
817
|
+
|
|
818
|
+
On `ctx.session`, the clear is fenced like the handler's appends and ends the
|
|
819
|
+
handler's own attempt: `ctx.signal` aborts with `SUPERSEDED_ATTEMPT` and
|
|
820
|
+
returned events are discarded. Its deletion keeps running under the platform's
|
|
821
|
+
`waitUntil`. All built-in stores support clearing. See
|
|
822
|
+
[Clearing a session](/concepts/contracts#clearing-a-session).
|
|
823
|
+
|
|
824
|
+
### `session.schedule.clear(name, options)`
|
|
825
|
+
|
|
826
|
+
```ts
|
|
827
|
+
session.schedule.clear(
|
|
828
|
+
name: string,
|
|
829
|
+
options: ({ delay: ScheduleDelay } | { at: Date }) & {
|
|
830
|
+
ifLastIndex: number
|
|
831
|
+
},
|
|
832
|
+
): Promise<void>
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
Publish a conditional clear to the configured scheduler. `name` is non-empty
|
|
836
|
+
and `ifLastIndex` is required. Timing has the same meaning and provider limits
|
|
837
|
+
as `session.schedule`. The task id derives from the contract, session, name,
|
|
838
|
+
and condition, plus the triggering event id and any earlier clear inside a
|
|
839
|
+
handler; reuse a name only for the same timing. The promise resolves after
|
|
840
|
+
provider acceptance.
|
|
841
|
+
|
|
842
|
+
Delivery runs `clear({ ifLastIndex })` and acknowledges after the deletion
|
|
843
|
+
finishes. A stale or duplicate delivery changes nothing. No event is appended.
|
|
844
|
+
Indexes never repeat, so a condition from an older lifetime never matches a
|
|
845
|
+
newer one. Clear tasks, appends, and recovery drains all use the Vercel
|
|
846
|
+
scheduler's `deploymentId` setting.
|
|
847
|
+
|
|
848
|
+
`ifLastIndex: ctx.event.index` matches only while the handler's event is still
|
|
849
|
+
the head. The handler's own appends and returned events move the head, so a
|
|
850
|
+
handler that writes should use the index of its last `ctx.session.append`
|
|
851
|
+
instead. Actor handlers always commit state, so they can't use the trigger's
|
|
852
|
+
index. A delay short enough to deliver while the handler is still running can
|
|
853
|
+
match before those writes land; the clear then ends the handler's attempt and
|
|
854
|
+
discards its returned events.
|
|
855
|
+
|
|
856
|
+
Each delivery attempt resumes the deletion where the last one stopped. Vercel
|
|
857
|
+
Queues redelivers until retention ends. QStash retries one message up to its
|
|
858
|
+
`retries` budget; a deletion that outlasts every retry leaves the message in
|
|
859
|
+
QStash's dead-letter queue with the rest of the old history still invisible,
|
|
860
|
+
and any later clear of the session finishes it.
|
|
861
|
+
|
|
715
862
|
### `session.history()`
|
|
716
863
|
|
|
717
864
|
```ts
|
|
@@ -721,6 +868,8 @@ session.history(options?: { gte?: number; lte?: number }): Promise<Event[]>
|
|
|
721
868
|
Raw events from the session, oldest first. `gte` and `lte` are inclusive event
|
|
722
869
|
indexes; omit a bound to leave that end of the log open.
|
|
723
870
|
Bounds are non-negative safe integers. `lte: 0` returns `[]`; `gte > lte` throws.
|
|
871
|
+
After a clear, history starts after the marker. The marker itself is not
|
|
872
|
+
returned.
|
|
724
873
|
|
|
725
874
|
Redis reads a fixed committed range in pages of at most 256 events, with up to
|
|
726
875
|
sixteen page reads in flight. Public history omits dispatch metadata. PostgreSQL
|
|
@@ -735,7 +884,7 @@ adapters can implement `readHistory(sessionId, options)` with the same exclusive
|
|
|
735
884
|
session.state(
|
|
736
885
|
reducer: Reducer<S>,
|
|
737
886
|
options?: { through?: number | 'latest' },
|
|
738
|
-
): Promise<{ state: S; index: number }>
|
|
887
|
+
): Promise<{ state: S; index: number; clearIndex?: number }>
|
|
739
888
|
```
|
|
740
889
|
|
|
741
890
|
The log folded through one committed prefix. `index` is its exact boundary,
|
|
@@ -750,6 +899,14 @@ Use `{ through: 'latest' }` inside a handler only when later committed events
|
|
|
750
899
|
are intentionally part of the decision. A numeric `through` is an inclusive,
|
|
751
900
|
non-negative safe integer; `through: 0` returns the reducer's initial state.
|
|
752
901
|
|
|
902
|
+
Once a session has been cleared, the result includes `clearIndex`, the latest
|
|
903
|
+
marker, and state folds from there. Forward the whole result when hydrating a
|
|
904
|
+
client. A positive `through` below `clearIndex` throws `SESSION_CLEARED`, and
|
|
905
|
+
`through: 0` returns the seed without `clearIndex`. On
|
|
906
|
+
`ctx.session`, every state read throws it once the triggering event has been
|
|
907
|
+
cleared, including `{ through: 'latest' }`. Handler `history()` and `stream()`
|
|
908
|
+
are not fenced.
|
|
909
|
+
|
|
753
910
|
The default handler read and numeric handler reads automatically retain their
|
|
754
911
|
exact folded checkpoint while the triggering event is unfinished. Completion
|
|
755
912
|
or dead-lettering releases the reference and collects the checkpoint when no
|
|
@@ -771,11 +928,11 @@ completed read or attaching snapshot contents.
|
|
|
771
928
|
### `session.stream(options?)`
|
|
772
929
|
|
|
773
930
|
```ts
|
|
774
|
-
session.stream(options?: { startAfter?: number }): AsyncIterable<Event>
|
|
931
|
+
session.stream(options?: { startAfter?: number }): AsyncIterable<Event | SessionCleared>
|
|
775
932
|
session.stream(options: {
|
|
776
933
|
startAfter?: number
|
|
777
934
|
presence: true
|
|
778
|
-
}): AsyncIterable<Event | PresencePatch | PresenceSnapshot>
|
|
935
|
+
}): AsyncIterable<Event | SessionCleared | PresencePatch | PresenceSnapshot>
|
|
779
936
|
```
|
|
780
937
|
|
|
781
938
|
A live feed of the session's events. `startAfter` is a non-negative safe integer;
|
|
@@ -783,6 +940,12 @@ A live feed of the session's events. `startAfter` is a non-negative safe integer
|
|
|
783
940
|
Server-side only; `server.fetch` exposes it over SSE and WebSocket. Subscribing
|
|
784
941
|
never dispatches handlers.
|
|
785
942
|
|
|
943
|
+
A clear arrives in order as a `SessionCleared` marker, whose `type` is
|
|
944
|
+
`'a2.cleared'`. A stream that opens below the
|
|
945
|
+
latest marker begins with it. A stream that is already running delivers the
|
|
946
|
+
log in order, so events from before the clear can arrive ahead of the marker.
|
|
947
|
+
Reset when the marker arrives. Clearing never closes a stream.
|
|
948
|
+
|
|
786
949
|
With `presence: true`, the feed yields one `PresenceSnapshot` first:
|
|
787
950
|
`{ snapshot }`, the current pruned map with each field's own `value`,
|
|
788
951
|
`seen`, and `at` stamp. Live presence patches then interleave with
|
|
@@ -865,13 +1028,12 @@ type Event = {
|
|
|
865
1028
|
|
|
866
1029
|
```ts
|
|
867
1030
|
createReact(options: {
|
|
868
|
-
client: A2Client // bind a shared client: api +
|
|
1031
|
+
client: A2Client // bind a shared client: api + contract + session identity
|
|
869
1032
|
}): { SessionProvider, useSession }
|
|
870
1033
|
|
|
871
|
-
// or,
|
|
1034
|
+
// or, bind the contract and supply api through the Provider:
|
|
872
1035
|
createReact(options: {
|
|
873
|
-
|
|
874
|
-
cache?: A2ClientCache // optional; see local-first
|
|
1036
|
+
contract: Contract // requires a public reducer
|
|
875
1037
|
}): { SessionProvider, useSession }
|
|
876
1038
|
```
|
|
877
1039
|
|
|
@@ -879,9 +1041,9 @@ A factory, like `createContext`: call it once in a `'use client'`
|
|
|
879
1041
|
module, export the bound pair. Server components import
|
|
880
1042
|
`SessionProvider` from that module as a client reference; client
|
|
881
1043
|
components import `useSession` from the same file. See
|
|
882
|
-
[Live UI](/guides/react#
|
|
1044
|
+
[Live UI](/guides/react#bind-react-once).
|
|
883
1045
|
|
|
884
|
-
`createReact({
|
|
1046
|
+
`createReact({ contract })` is also supported for provider-bound clients. Use a
|
|
885
1047
|
shared `A2Client` when code outside the provider needs to push before a route
|
|
886
1048
|
transition and the destination should adopt the same optimistic session.
|
|
887
1049
|
|
|
@@ -890,35 +1052,61 @@ transition and the destination should adopt the same optimistic session.
|
|
|
890
1052
|
| Prop | What it is |
|
|
891
1053
|
| --------------- | ------------------------------------------------------ |
|
|
892
1054
|
| `sessionId` | which session to subscribe to |
|
|
893
|
-
| `
|
|
894
|
-
| `initialIndex` | the fold's frontier, where a new session starts streaming |
|
|
1055
|
+
| `initial` | optional snapshot or Promise of a snapshot; `null` means bootstrap |
|
|
895
1056
|
| `initialEvents` | optional earlier raw events for a history UI |
|
|
896
1057
|
| `participant` | overrides the client's `participant`; one of the two is required to call `setPresence` |
|
|
897
1058
|
|
|
898
|
-
|
|
899
|
-
|
|
1059
|
+
Supplies scope and optional initial data. Mounted enabled hooks own
|
|
1060
|
+
connection leases and reconnect from the last streamed event. `participant` binds at the session's
|
|
900
1061
|
first resolution; a changed provider prop is ignored for the session's
|
|
901
1062
|
runtime lifetime.
|
|
902
1063
|
|
|
903
|
-
`
|
|
904
|
-
|
|
1064
|
+
`initial` carries the complete state/index snapshot, plus `clearIndex` when
|
|
1065
|
+
the session has been cleared.
|
|
1066
|
+
For a new session, the stream starts after `initial.index`, including events
|
|
905
1067
|
appended between the server render and the connection. `initialEvents` only preloads the raw
|
|
906
1068
|
`events` feed for a UI that needs entries from before that frontier. It does
|
|
907
1069
|
not affect state hydration or stream resumption. Later snapshots advance
|
|
908
1070
|
state while preserving the stream's resume position, so delayed events
|
|
909
1071
|
still enter the feed without folding into state again.
|
|
910
1072
|
|
|
911
|
-
|
|
1073
|
+
`initial` accepts `InitialSnapshot<S>`: `StateSnapshot<S> | null | undefined |
|
|
1074
|
+
Promise<StateSnapshot<S> | null | undefined>`. The type is exported from
|
|
1075
|
+
`experimental-a2` and `experimental-a2/client`. A cold session waits for a
|
|
1076
|
+
supplied Promise before connecting. A snapshot result seeds state; `null`
|
|
1077
|
+
or `undefined` selects subscription bootstrap. Rejection pauses cold startup
|
|
1078
|
+
and reports through `connection.error`; supply a new Promise, snapshot, or
|
|
1079
|
+
`null` to resume. A warm session keeps its current state and connection.
|
|
1080
|
+
The Provider itself does not suspend. Keep Promise identity stable.
|
|
1081
|
+
|
|
1082
|
+
### `useSession(options?)`
|
|
912
1083
|
|
|
913
1084
|
```ts
|
|
914
|
-
const { state, push, reconnect, events, index, loadHistory, history, connection
|
|
915
|
-
presence, setPresence } = useSession()
|
|
1085
|
+
const { state, hydrated, push, reconnect, events, index, loadHistory, history, connection } = useSession()
|
|
916
1086
|
```
|
|
917
1087
|
|
|
918
|
-
`state`
|
|
1088
|
+
`state` has the reducer's client state type. A supplied snapshot seeds the
|
|
1089
|
+
first render. Otherwise it displays `reducer.initialState`, through the client
|
|
1090
|
+
projection when present, until a snapshot replaces it atomically. Without a
|
|
1091
|
+
supplied snapshot, the server sends one first on the same subscription.
|
|
1092
|
+
Bootstrap errors and retries use `connection`.
|
|
1093
|
+
`hydrated` is a boolean shared with `session.getSnapshot()`. It is `false`
|
|
1094
|
+
while only the reducer seed is available and becomes `true` when an initial
|
|
1095
|
+
snapshot or bootstrap is applied, even for an empty state at index 0.
|
|
1096
|
+
It stays `true` while cached state survives reconnects, disabled hooks, or a
|
|
1097
|
+
failed replacement preload. It does not imply a live connection or fresh data.
|
|
1098
|
+
The hook accepts `sessionId`, `client`, `participant`, `initial`, `enabled`, and
|
|
1099
|
+
`suspend` options; scope defaults come from its Provider and bound client.
|
|
1100
|
+
`suspend` defaults to `false`. With `true`, React `use()` reads the supplied
|
|
1101
|
+
`initial` Promise, using the nearest Suspense and Error boundaries. Automatic
|
|
1102
|
+
subscription bootstrap never suspends, including when the Promise resolves
|
|
1103
|
+
to `null` or `undefined`. The default renders the reducer seed while a cold
|
|
1104
|
+
initial Promise is pending. `enabled` controls connection ownership independently.
|
|
1105
|
+
|
|
1106
|
+
`state` starts from that snapshot and folds live events through the
|
|
919
1107
|
shared reducer. `events` is the raw event feed: observed, explicitly
|
|
920
1108
|
seeded, or backscrolled. Unless `initialEvents` seeds earlier entries or
|
|
921
|
-
`loadHistory` fetches them, it begins after `
|
|
1109
|
+
`loadHistory` fetches them, it begins after `initial.index`. `index` is
|
|
922
1110
|
the latest server-confirmed state index, the `lastSeenIndex` for
|
|
923
1111
|
[cancellation](/guides/cancellation).
|
|
924
1112
|
|
|
@@ -933,9 +1121,15 @@ the oldest loaded event. Pass an explicit `before` to read another range.
|
|
|
933
1121
|
It never touches `state` or the optimistic overlay. Calls
|
|
934
1122
|
serialize per session and already-loaded ranges are not refetched.
|
|
935
1123
|
`history` is the progress: `{ loading, complete, oldestLoaded }`, where
|
|
936
|
-
`complete` means the feed reaches index 1
|
|
1124
|
+
`complete` means the feed reaches index 1, or the first event after the latest
|
|
1125
|
+
clear (or the log is empty). The `ws` api has no history lane; there `loadHistory`
|
|
937
1126
|
throws a `TypeError`.
|
|
938
1127
|
|
|
1128
|
+
When a clear reaches the session, `state` resets to the reducer's initial
|
|
1129
|
+
state and `events` drops everything at or below the marker. Unconfirmed pushes
|
|
1130
|
+
stay in the overlay and apply to the reset state. A push acked below the marker
|
|
1131
|
+
was cleared with it: it leaves the overlay and its `confirmed` resolves.
|
|
1132
|
+
|
|
939
1133
|
`presence` is the replicated ephemeral map,
|
|
940
1134
|
`Record<participantId, { [field]: { value, seen, at } }>`, including
|
|
941
1135
|
this client. `setPresence(values)` is fire-and-forget: validated
|
|
@@ -1015,7 +1209,9 @@ are actually flowing, not "the socket hasn't errored yet". See
|
|
|
1015
1209
|
```ts
|
|
1016
1210
|
useSession(client: A2Client, sessionId: string, options?: {
|
|
1017
1211
|
participant?: string // overrides the client's participant
|
|
1018
|
-
|
|
1212
|
+
initial?: InitialSnapshot<S>
|
|
1213
|
+
enabled?: boolean
|
|
1214
|
+
suspend?: boolean
|
|
1019
1215
|
}): UseSessionResult
|
|
1020
1216
|
```
|
|
1021
1217
|
|
|
@@ -1026,58 +1222,91 @@ handle is identity-mapped: every hook and provider mounting the same
|
|
|
1026
1222
|
session shares one runtime. The stream is refcounted: the first mount
|
|
1027
1223
|
connects, the last unmount closes (StrictMode-safe).
|
|
1028
1224
|
|
|
1029
|
-
`
|
|
1030
|
-
`
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
never-move-backward rule and the stream connects at that frontier.
|
|
1038
|
-
For a deliberate full replay, hand the fold's true starting point:
|
|
1039
|
-
`{ state: reducer.initialState, index: 0 }`. State at index 0 is the
|
|
1040
|
-
reducer's seed by definition, so the explicit replay needs no special
|
|
1041
|
-
vocabulary. See
|
|
1042
|
-
[Client-first sessions](/guides/react#client-first-sessions).
|
|
1225
|
+
`initial` accepts the result of `session.state(reducer)` or its Promise. Pass it
|
|
1226
|
+
whole: its `clearIndex` lets a cached client drop history from before a clear
|
|
1227
|
+
it never streamed. The automatic snapshot carries it too. Without it,
|
|
1228
|
+
the subscription obtains a snapshot automatically. To defer automatic I/O,
|
|
1229
|
+
pass `enabled: false`. Disabled hooks keep observing cached state; other
|
|
1230
|
+
owners continue normally. Explicit pushes can still acquire a connection.
|
|
1231
|
+
For deliberate full replay, supply `{ state: reducer.initialState, index: 0 }`.
|
|
1232
|
+
See [Live UI](/guides/react).
|
|
1043
1233
|
|
|
1044
1234
|
## `experimental-a2/ai`
|
|
1045
1235
|
|
|
1046
|
-
|
|
1236
|
+
The isomorphic AI protocol: event schemas, the `AIState` reducer, pure input
|
|
1237
|
+
builders, and `agentContract()` for extending both. It has a peer dependency on
|
|
1238
|
+
AI SDK v7, used as types only here.
|
|
1239
|
+
|
|
1240
|
+
### `agentContract(options)`
|
|
1047
1241
|
|
|
1048
1242
|
```ts
|
|
1049
|
-
|
|
1243
|
+
agentContract<M extends UIMessage = UIMessage>(options: {
|
|
1050
1244
|
name: string
|
|
1051
1245
|
events?: Record<string, StandardSchemaV1>
|
|
1052
|
-
messageSchema?: StandardSchemaV1<
|
|
1053
|
-
|
|
1054
|
-
|
|
1246
|
+
messageSchema?: StandardSchemaV1<M>
|
|
1247
|
+
reducer?: {
|
|
1248
|
+
name: string // snapshot identity: rename it when fold changes
|
|
1249
|
+
initialState: X // its keys may not shadow AIState's
|
|
1250
|
+
fold: (state: X, event: ContractEvent) => X
|
|
1251
|
+
}
|
|
1252
|
+
}): Contract<Events, {}, AgentReducer<M, Events, X>>
|
|
1055
1253
|
```
|
|
1056
1254
|
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1255
|
+
A core contract carrying the built-in AI protocol plus `events`, with a reducer
|
|
1256
|
+
that folds `AIState` and, with `reducer`, your application state beside it.
|
|
1257
|
+
The session's state is `AIState<M> & X`. Share it when a client needs
|
|
1258
|
+
application events or state: serve it with `agent({ contract })` and use it
|
|
1259
|
+
with [`useAgent({ api, id, contract })`](#useagentoptions). Otherwise
|
|
1260
|
+
`agent({ name })` builds one on the server and clients use the built-in
|
|
1261
|
+
protocol.
|
|
1262
|
+
|
|
1263
|
+
Built-in event names cannot be replaced by `options.events`. `messageSchema`
|
|
1264
|
+
validates a narrower `UIMessage` type in message payloads; the explicit type
|
|
1265
|
+
argument types messages without one. `reducer.fold` sees `X` and every event,
|
|
1266
|
+
never the transcript; it runs after the AI fold on the server and in every
|
|
1267
|
+
client, so optimistic display carries through. A key of `initialState` that
|
|
1268
|
+
`AIState` also has throws a `TypeError` and fails to typecheck. The reducer name
|
|
1269
|
+
is `AI_STATE_REDUCER` (`a2.ai.state.v26`) without `reducer`, and
|
|
1270
|
+
`${reducer.name}+a2.ai.state.v26` with one, so checkpoints advance when either
|
|
1271
|
+
fold changes. `AgentReducer` types that name, which is what lets
|
|
1272
|
+
[`createAgentClient()`](#createagentclientoptions) accept a plain
|
|
1273
|
+
`agentContract()`, and carries `X` to `agent()`'s resolvers.
|
|
1274
|
+
|
|
1275
|
+
### `TranscriptMessage<M>`
|
|
1060
1276
|
|
|
1061
|
-
|
|
1277
|
+
```ts
|
|
1278
|
+
type TranscriptMessage<M extends UIMessage> = Omit<M, 'parts'> & {
|
|
1279
|
+
parts: Array<M['parts'][number] | CompactionPart>
|
|
1280
|
+
}
|
|
1281
|
+
```
|
|
1062
1282
|
|
|
1063
|
-
|
|
1283
|
+
The message type of `AIState<M>['messages']` and `deriveUIMessages()`: your
|
|
1284
|
+
message type with the [compaction part](#iscompactionpartpart) added, so
|
|
1285
|
+
`isCompactionPart()` narrows even when `M` declares its own data parts.
|
|
1286
|
+
|
|
1287
|
+
### `AIMessageOf<Events>`
|
|
1288
|
+
|
|
1289
|
+
The `UIMessage` type a contract's `ai.message.created` schema carries, such as
|
|
1290
|
+
`AIMessageOf<typeof contract.events>`. It falls back to `UIMessage`. `agent()`
|
|
1291
|
+
uses it to type messages for any contract carrying the AI protocol.
|
|
1292
|
+
|
|
1293
|
+
### `handlerContext(contract?)`
|
|
1064
1294
|
|
|
1065
1295
|
Reads the durable A2 handler context ambient to the current tool
|
|
1066
1296
|
execution. Tools are plain AI SDK `tool()` definitions; A2 passes their
|
|
1067
1297
|
options through to the AI SDK without interpreting them and runs each
|
|
1068
1298
|
local `execute` inside an ambient context scope. Call
|
|
1069
|
-
`handlerContext(
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
`TypeError
|
|
1299
|
+
`handlerContext()` anywhere below `execute`, including awaited helpers and
|
|
1300
|
+
async iteration. A call outside any tool execution throws a `TypeError`.
|
|
1301
|
+
Without an argument, the context is typed for the built-in protocol. Pass a
|
|
1302
|
+
shared `agentContract()` to type application events; A2 verifies it against the
|
|
1303
|
+
executing agent and throws a `TypeError` on a mismatch.
|
|
1074
1304
|
|
|
1075
1305
|
The returned `AgentToolContext<D>` supplies the current A2 handler's `event`,
|
|
1076
1306
|
`attempt`, `session`, and `signal`. Local tools execute the explicit
|
|
1077
1307
|
`ai.tool.execution.requested` work event. Its payload contains the admitted
|
|
1078
|
-
`call`, `generation`, `turnId`, and execution `version`.
|
|
1079
|
-
|
|
1080
|
-
`ctx.session.schedule()` use the scheduler of the server executing the tool.
|
|
1308
|
+
`call`, `generation`, `turnId`, and execution `version`. Calls such as
|
|
1309
|
+
`ctx.session.schedule()` use the scheduler of the agent executing the tool.
|
|
1081
1310
|
|
|
1082
1311
|
The context is runtime capability, not event data. A2 creates it for the
|
|
1083
1312
|
current attempt, never persists it, and never sends it to the model or a
|
|
@@ -1093,7 +1322,8 @@ functions.
|
|
|
1093
1322
|
|
|
1094
1323
|
### `inputs`
|
|
1095
1324
|
|
|
1096
|
-
Pure typed inputs for `append()` and `push()
|
|
1325
|
+
Pure typed inputs for `append()` and `push()`. The `useAgent()` actions build
|
|
1326
|
+
the browser ones:
|
|
1097
1327
|
|
|
1098
1328
|
| Input | Events |
|
|
1099
1329
|
| --- | --- |
|
|
@@ -1122,7 +1352,8 @@ All builders emit `ai.control.requested`. Persistence and application are
|
|
|
1122
1352
|
separate: `ai.control.decided` returns `{ commandId, outcome, reason? }` through
|
|
1123
1353
|
the existing stream. Outcomes are `applied` or `rejected`. Rejection reasons
|
|
1124
1354
|
include `not-pending`, `role-mismatch`, `stale-turn`, `not-found`,
|
|
1125
|
-
`not-user-input`, and `closed`.
|
|
1355
|
+
`not-user-input`, and `closed`.
|
|
1356
|
+
`state.receipt` holds the latest user receipt.
|
|
1126
1357
|
|
|
1127
1358
|
Steering validates and prioritizes new input even if the observed turn has
|
|
1128
1359
|
finished. A matching active turn finishes its current model response and tool
|
|
@@ -1224,6 +1455,47 @@ enter later model prompts. Raw progress remains in the log. Available inputs,
|
|
|
1224
1455
|
approval-waiting calls, and completed results retain their existing terminal
|
|
1225
1456
|
handling.
|
|
1226
1457
|
|
|
1458
|
+
### `reasoningLevels`
|
|
1459
|
+
|
|
1460
|
+
```ts
|
|
1461
|
+
type ReasoningLevel = 'provider-default' | 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh'
|
|
1462
|
+
const reasoningLevels: readonly ReasoningLevel[]
|
|
1463
|
+
```
|
|
1464
|
+
|
|
1465
|
+
Every `ReasoningLevel`, in order, for pickers. A `model` option returns one in
|
|
1466
|
+
[`ModelSettings`](#modelsettings).
|
|
1467
|
+
|
|
1468
|
+
### `isCompactionPart(part)`
|
|
1469
|
+
|
|
1470
|
+
```ts
|
|
1471
|
+
isCompactionPart(part: { type: string }): part is CompactionPart
|
|
1472
|
+
|
|
1473
|
+
type CompactionPart = {
|
|
1474
|
+
type: 'data-a2-compaction'
|
|
1475
|
+
id: string // the compacting generation's ID
|
|
1476
|
+
data: {
|
|
1477
|
+
status: 'running' | 'completed' | 'failed' | 'interrupted'
|
|
1478
|
+
inputTokens?: number
|
|
1479
|
+
thresholdTokens?: number
|
|
1480
|
+
summary?: string
|
|
1481
|
+
usage?: LanguageModelUsage
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
```
|
|
1485
|
+
|
|
1486
|
+
The transcript part that marks a compaction. When `ai.compaction.requested`
|
|
1487
|
+
folds, the reducer appends it to the active response, between the steps
|
|
1488
|
+
before it and the model step it compacted for; a compaction before the first
|
|
1489
|
+
step creates the response with this part. The part counts toward that step's
|
|
1490
|
+
recovery base, so Stop cutoffs, failures, and retries of the step keep it.
|
|
1491
|
+
`ai.compaction.completed` settles it in place with the summary and usage. A
|
|
1492
|
+
running compaction whose generation ends first becomes `interrupted` (Stop,
|
|
1493
|
+
Pause now, or session close) or `failed` (failure). A pending Stop previews it
|
|
1494
|
+
as `interrupted` on the client. A superseded worker attempt's part is removed,
|
|
1495
|
+
and its replacement compacts again if it needs to. AI SDK
|
|
1496
|
+
`convertToModelMessages()` skips data parts by default, and model context never
|
|
1497
|
+
contains it.
|
|
1498
|
+
|
|
1227
1499
|
### `events` and `createEvents(options?)`
|
|
1228
1500
|
|
|
1229
1501
|
The built-in Standard Schema definitions. Use `events` for the default AI SDK
|
|
@@ -1232,9 +1504,10 @@ message type. Both are isomorphic.
|
|
|
1232
1504
|
|
|
1233
1505
|
### Optimistic AI client state
|
|
1234
1506
|
|
|
1235
|
-
|
|
1236
|
-
`state.
|
|
1237
|
-
|
|
1507
|
+
Every client folding an AI reducer exposes `state.messages`, `state.inbox`, and
|
|
1508
|
+
`state.starting` with pending send, edit, move, remove, Send now, steer, pause,
|
|
1509
|
+
resume, and Stop commands applied for display. This includes contracts with
|
|
1510
|
+
application state from `agentContract({ reducer })`. Idle, unpaused
|
|
1238
1511
|
sends appear in the conversation immediately. A generating input occupies the
|
|
1239
1512
|
optimistic turn, so subsequent sends queue. Busy or paused sessions queue new
|
|
1240
1513
|
sends. `starting` indicates a speculative turn awaiting acceptance; it does not
|
|
@@ -1245,7 +1518,12 @@ inbox. This display survives receipt and hydration, including while paused.
|
|
|
1245
1518
|
They remain pending server inputs until admission and do not enter the model
|
|
1246
1519
|
prompt early. Multiple steering inputs follow inbox admission order.
|
|
1247
1520
|
|
|
1248
|
-
|
|
1521
|
+
New active turn identities come from accepted control decisions. A pending Stop
|
|
1522
|
+
for that turn clears the displayed `state.active`, generation, and pending
|
|
1523
|
+
approvals/inputs, and previews an idle status (or paused when the inbox is paused). With
|
|
1524
|
+
`lastSeenIndex`, its retained generation projection freezes at the observed
|
|
1525
|
+
frontier. The preview lasts through the request echo until the controller's
|
|
1526
|
+
decision; rejected requests restore the confirmed active turn and output.
|
|
1249
1527
|
Server reads and reducer folds retain confirmed messages and inbox entries;
|
|
1250
1528
|
`state.starting` is false there. Custom reducers that call `reduceAIState()` or
|
|
1251
1529
|
nest the AI fold do not inherit the client display behavior. Repeat editing and
|
|
@@ -1257,30 +1535,17 @@ The pending-command ledger survives request echo and retires on the matching
|
|
|
1257
1535
|
the client's existing optimistic overlay. This display adds no I/O and does not
|
|
1258
1536
|
read raw history.
|
|
1259
1537
|
|
|
1260
|
-
### `
|
|
1538
|
+
### `AIState`
|
|
1261
1539
|
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1540
|
+
The AI reducer's `initialState`, `fold`, server `session.state()`, and client
|
|
1541
|
+
snapshots all use `AIState`. Keep state and its log index together when
|
|
1542
|
+
hydrating; do not fold already-seen history twice. `messages` holds
|
|
1543
|
+
[`TranscriptMessage<M>`](#transcriptmessagem) values. Model context has its own
|
|
1544
|
+
built-in fold and schema, which carries the contract's application state.
|
|
1265
1545
|
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
`reducer.view(storedState)` derives confirmed `AIState` for server code.
|
|
1271
|
-
`reducer.fromView(state)` converts a confirmed view into stored state, including
|
|
1272
|
-
when migrating an application-owned hydration value. Keep the stored state and
|
|
1273
|
-
its log index together when hydrating; do not fold already-seen history twice.
|
|
1274
|
-
`fold` can customize the confirmed `AIState` transition, and `stateSchema`
|
|
1275
|
-
validates that view after restoration. Both apply only to the transcript.
|
|
1276
|
-
Model context has its own built-in fold and schema. Changed custom fold semantics
|
|
1277
|
-
still require a new `name`.
|
|
1278
|
-
|
|
1279
|
-
Stored state is versioned, serializable data. Shared payload records and
|
|
1280
|
-
inverse changes replace repeated tool values and complete recovery copies.
|
|
1281
|
-
The reducer caches its live view and builds the records when the stored fields
|
|
1282
|
-
are read or serialized. Treat state and views as immutable. No event or tool
|
|
1283
|
-
payload is truncated. `AIState`
|
|
1546
|
+
Persistence encoding is internal. Tool inputs, outputs, and provider metadata
|
|
1547
|
+
live in message parts; the tool activity list retains only identity and status.
|
|
1548
|
+
No event or tool payload is truncated. `AIState`
|
|
1284
1549
|
contains `inbox`, `active`, `starting`, the latest command `receipt`, session lifecycle,
|
|
1285
1550
|
messages, generation status, pending approvals
|
|
1286
1551
|
and input, tool activity, compaction, usage, the last error, and
|
|
@@ -1313,22 +1578,23 @@ Checkpoint size and serialization cost still grow with retained progress.
|
|
|
1313
1578
|
Tool-input deltas update `input-streaming` parts with best-effort partial JSON
|
|
1314
1579
|
for display. The state stays `input-streaming` until a completed input arrives;
|
|
1315
1580
|
partial parsing does not authorize execution or change interruption handling.
|
|
1316
|
-
|
|
1581
|
+
Application events are ignored by the AI fold.
|
|
1317
1582
|
|
|
1318
1583
|
### `deriveUIMessages(history)` and `reduceAIState(state, event)`
|
|
1319
1584
|
|
|
1320
|
-
`deriveUIMessages()` folds a raw A2 event history directly into
|
|
1321
|
-
`
|
|
1585
|
+
`deriveUIMessages()` folds a raw A2 event history directly into
|
|
1586
|
+
`TranscriptMessage<M>[]`, AI SDK messages that include compaction parts. `reduceAIState()` is the event-at-a-time form used by the
|
|
1322
1587
|
standard reducer. Both synchronously project the same chunk-only protocol.
|
|
1323
1588
|
|
|
1324
1589
|
## `experimental-a2/ai/server`
|
|
1325
1590
|
|
|
1326
|
-
### `
|
|
1591
|
+
### `agent(options)`
|
|
1327
1592
|
|
|
1328
1593
|
```ts
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1594
|
+
agent({
|
|
1595
|
+
name: string, // or contract: agentContract(...), never both
|
|
1596
|
+
events?, messageSchema?, // with name: server-only application events
|
|
1597
|
+
model: LanguageModel | ModelSettings | (context => LanguageModel | ModelSettings),
|
|
1332
1598
|
tools?,
|
|
1333
1599
|
instructions?,
|
|
1334
1600
|
generation?,
|
|
@@ -1337,17 +1603,49 @@ createAgentServer({
|
|
|
1337
1603
|
generate?,
|
|
1338
1604
|
compaction?,
|
|
1339
1605
|
progress?,
|
|
1340
|
-
|
|
1341
|
-
|
|
1606
|
+
toolLane?: string, // run local tool executions one at a time on this lane
|
|
1607
|
+
handlers?, // application event handlers
|
|
1608
|
+
store?, scheduler?, telemetry?,
|
|
1609
|
+
}): Agent<Events, State, Name> // an A2Server
|
|
1342
1610
|
```
|
|
1343
1611
|
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1612
|
+
Serves an AI agent as one ordinary A2 server: the built-in protocol, its model
|
|
1613
|
+
and tool handlers, and A2's store, scheduler, and telemetry. Pass `name` for the
|
|
1614
|
+
built-in protocol (with optional server-only `events` and `messageSchema`), or a
|
|
1615
|
+
shared `agentContract()` as `contract` when clients fold application state.
|
|
1616
|
+
Any other contract throws a `TypeError` and fails to typecheck; a fully custom
|
|
1617
|
+
contract is core A2, served by `createServer()`. Messages are typed by the
|
|
1618
|
+
contract's `ai.message.created` schema through
|
|
1619
|
+
[`AIMessageOf`](#aimessageofevents).
|
|
1620
|
+
The result is an `A2Server` with `fetch`, `session(id)`, `drain()`, and
|
|
1621
|
+
`contract`. `typeof` it types `useAgent()` and
|
|
1622
|
+
`createAgentClient()` through a type-only import; `Name` is its reducer name,
|
|
1623
|
+
which decides whether the contract-free client accepts it.
|
|
1624
|
+
|
|
1625
|
+
A2 runs one AI SDK `streamText()` model step per durable generation, persists
|
|
1626
|
+
its `UIMessageChunk`s once in durable progress batches, projects messages
|
|
1627
|
+
synchronously, extracts tool and approval lifecycle events, executes local
|
|
1628
|
+
tools, and records completion, usage, interruption, and failure. `model`
|
|
1629
|
+
accepts an AI SDK model string or provider model, or
|
|
1630
|
+
[`ModelSettings`](#modelsettings) to choose its reasoning level and speed with
|
|
1631
|
+
it. `model`, `instructions`, and `generation` can be values or per-generation
|
|
1632
|
+
async resolvers. A resolver that throws, or returns an invalid value, fails the
|
|
1633
|
+
turn once with its message, like a provider error, instead of retrying; the
|
|
1634
|
+
attempt's `ai.generation.started` omits `model` when the model never resolved.
|
|
1635
|
+
`retry()` runs the step again. Store failures (`A2Error`) still retry durably.
|
|
1636
|
+
|
|
1637
|
+
`fetch` accepts reads and pushes of public AI controls:
|
|
1638
|
+
`ai.control.requested` commands other than the server-authored
|
|
1639
|
+
`request-input` and `tool-result`, with
|
|
1640
|
+
`send`, `edit`, and `steer` limited to user messages. With a `contract`, it also
|
|
1641
|
+
accepts the contract's application events, like any A2 server; `events` passed
|
|
1642
|
+
with `name` stay server-only, since no client can push them. Every other pushed
|
|
1643
|
+
event answers `400`. `fetch(request, { authorize })` adds per-operation policy on
|
|
1644
|
+
top, such as who may push which application event. `handlers` handles application events; naming an
|
|
1645
|
+
event the agent handles itself throws a `TypeError` at construction.
|
|
1646
|
+
|
|
1647
|
+
The agent's `telemetry` instruments both Core and AI operations in the same
|
|
1648
|
+
active trace. See the [span catalogue](#the-span-catalogue).
|
|
1351
1649
|
|
|
1352
1650
|
`generation` contains per-step settings such as `temperature`,
|
|
1353
1651
|
`maxOutputTokens`, `topP`, provider options, and tool approval policy. A2 owns
|
|
@@ -1361,18 +1659,30 @@ untouched. A local tool reads its durable context with `handlerContext()` when
|
|
|
1361
1659
|
its handler executes. Model-step timeouts remain available; tool-execution
|
|
1362
1660
|
timeouts are not.
|
|
1363
1661
|
|
|
1662
|
+
A `generation` resolver receives `{ event, messages, state, session, signal }`
|
|
1663
|
+
and returns `AgentGenerationSettings` or a promise. It runs once per attempt
|
|
1664
|
+
before compaction and model dispatch; its settings also govern tool approvals
|
|
1665
|
+
and automatic-compaction compatibility. Static settings are validated at
|
|
1666
|
+
construction, resolved settings before use. Failures and cancellation follow
|
|
1667
|
+
model resolution.
|
|
1668
|
+
|
|
1364
1669
|
`messageMetadata(context)` synchronously maps each default AI SDK stream part
|
|
1365
|
-
to the metadata type of the agent's `UIMessage`.
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1670
|
+
to the metadata type of the agent's `UIMessage`. It receives the same context
|
|
1671
|
+
as `generate(context)`, plus the current `TextStreamPart` as `part`; its
|
|
1672
|
+
`generation` is the settings the default generator sends, with A2's Gateway
|
|
1673
|
+
defaults applied. Metadata returned for standard `start` and `finish` parts is
|
|
1674
|
+
recorded with generation progress and shallow-merged onto the projected
|
|
1675
|
+
assistant message. Use the `finish` part's `totalUsage` to retain final token
|
|
1676
|
+
usage. A custom `generate` function owns its metadata chunks and does not use
|
|
1677
|
+
this mapper.
|
|
1371
1678
|
|
|
1372
1679
|
`generate(context)` optionally replaces the default AI SDK generation. It
|
|
1373
|
-
receives
|
|
1374
|
-
|
|
1375
|
-
|
|
1680
|
+
receives the resolver context, `{ event, messages, state, session, signal }`,
|
|
1681
|
+
plus the step's `generationId`, `modelMessages`, and the resolved `model`,
|
|
1682
|
+
`instructions`, `tools`, and `generation` settings. `messages` and
|
|
1683
|
+
`modelMessages` reflect this step's compaction. Its stream writes the assistant
|
|
1684
|
+
message `event.payload.responseMessageId`. It returns exactly one model step as
|
|
1685
|
+
a `ReadableStream<UIMessageChunk>`. A2 continues to own tool execution,
|
|
1376
1686
|
continuation, and the durable lifecycle around that stream.
|
|
1377
1687
|
|
|
1378
1688
|
Generation requests use one durable lane, so model calls for a session do not
|
|
@@ -1384,7 +1694,8 @@ failure keeps later messages queued until the failed response is explicitly
|
|
|
1384
1694
|
retried or interrupted.
|
|
1385
1695
|
|
|
1386
1696
|
Independent authorized tool handlers run concurrently without a fixed
|
|
1387
|
-
concurrency limit.
|
|
1697
|
+
concurrency limit. `toolLane` names a lane for every local tool execution, so
|
|
1698
|
+
they run one at a time in admission order; model steps keep their own lane. A private coordinator reducer tracks generation closure,
|
|
1388
1699
|
cancellation, calls, approvals, and terminal results for the active response.
|
|
1389
1700
|
Completed responses do not accumulate in its state. Its
|
|
1390
1701
|
`ctx.session.state(coordinator, { through: 'latest' })` reads the durable
|
|
@@ -1405,7 +1716,7 @@ require their own idempotency. Approved provider-executed calls may satisfy the
|
|
|
1405
1716
|
join from the approval response. Provider tools with declared deferred-result
|
|
1406
1717
|
support wait for their provider result instead of a local executor. An
|
|
1407
1718
|
authenticated provider callback appends the terminal `ai.tool.result` through
|
|
1408
|
-
the trusted server session API;
|
|
1719
|
+
the trusted server session API; `fetch` rejects it from browsers.
|
|
1409
1720
|
|
|
1410
1721
|
`compaction` defaults to automatic for supported Gateway requests. It accepts
|
|
1411
1722
|
`false` to disable compaction and discovery, `{ thresholdTokens?, instructions? }`
|
|
@@ -1416,16 +1727,15 @@ The resolver runs once per generation and returns a valid policy or `false`.
|
|
|
1416
1727
|
It can await application reducers through `context.session.state(reducer)`, whose
|
|
1417
1728
|
default boundary is the captured prompt frontier. Model, instruction, compaction,
|
|
1418
1729
|
metadata, and custom-generation contexts expose state access and omit raw history.
|
|
1419
|
-
Callbacks receive prompt messages and
|
|
1420
|
-
`execution`
|
|
1421
|
-
`{ event, messages, session, signal }
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
`context.session.state(
|
|
1427
|
-
|
|
1428
|
-
hydration.
|
|
1730
|
+
Callbacks receive prompt messages and the triggering `event`, without an
|
|
1731
|
+
`execution` argument. Model, instruction, and generation-setting resolvers receive
|
|
1732
|
+
`{ event, messages, state, session, signal }`, where `state` is the contract's
|
|
1733
|
+
application state (from `agentContract({ reducer })`, `{}` without one) as of the
|
|
1734
|
+
captured snapshot; it rides in the model-context checkpoint, so it costs no
|
|
1735
|
+
read. Compaction, metadata, and custom-generation callbacks receive the same
|
|
1736
|
+
context plus `modelMessages`, including committed summaries. The contract's reducer continues to expose the full
|
|
1737
|
+
transcript. `context.session.state()` returns its `AIState` directly, paired
|
|
1738
|
+
with its log index for hydration.
|
|
1429
1739
|
Static options are validated at construction; resolved options are validated
|
|
1430
1740
|
before that generation starts. Each session uses its own resolved value. `thresholdTokens` is an
|
|
1431
1741
|
optional positive safe integer. `instructions` adds to A2's internal summary
|
|
@@ -1464,53 +1774,149 @@ caching. It does not invoke application generation callbacks, transforms, or too
|
|
|
1464
1774
|
hooks. Both custom policy callbacks and `generate` receive `modelMessages`, the
|
|
1465
1775
|
model-ready prompt including summaries, alongside typed UI `messages`.
|
|
1466
1776
|
|
|
1467
|
-
Built-in summaries remain separate from application-typed messages.
|
|
1468
|
-
|
|
1469
|
-
`
|
|
1470
|
-
`state.
|
|
1777
|
+
Built-in summaries remain separate from application-typed messages. The
|
|
1778
|
+
transcript marks each compaction with a
|
|
1779
|
+
[`data-a2-compaction` part](#iscompactionpartpart), and
|
|
1780
|
+
`state.compaction?.status === 'running'` reports current activity. The
|
|
1781
|
+
conversation in `state.messages` remains intact. Model execution
|
|
1471
1782
|
uses a separate context checkpoint; existing compactions do not require a
|
|
1472
1783
|
transcript read. Successful compaction is permanent, including across failure,
|
|
1473
1784
|
interruption, retry, and recovery. Retry reruns the failed step from the latest
|
|
1474
1785
|
committed context. Compaction defers while tool exchanges or A2-owned tool work
|
|
1475
1786
|
remain unsettled, checked with one fresh coordinator state read on each candidate.
|
|
1476
1787
|
Unresolved dead-lettered tool work also defers compaction until repair and
|
|
1477
|
-
settlement or session closure. `progress` controls durable batching
|
|
1478
|
-
|
|
1788
|
+
settlement or session closure. `progress` controls durable batching. By
|
|
1789
|
+
default each batch is written as soon as the previous one finishes, so a slow
|
|
1790
|
+
model's chunks are written one at a time and chunks that arrive during a write
|
|
1791
|
+
share the next batch, up to `maxChunks` (128). `maxDelayMs` (default 0) lets a
|
|
1792
|
+
batch keep collecting chunks for up to that long after the previous write;
|
|
1793
|
+
boundary chunks and `maxChunks` write sooner. Raising it trades token latency
|
|
1794
|
+
for fewer progress events.
|
|
1479
1795
|
|
|
1480
1796
|
This entry point is server-only and resolves to a throwing browser stub.
|
|
1481
1797
|
|
|
1798
|
+
### `ModelSettings`
|
|
1799
|
+
|
|
1800
|
+
```ts
|
|
1801
|
+
type ModelSettings = {
|
|
1802
|
+
model: LanguageModel
|
|
1803
|
+
reasoning?: ReasoningLevel
|
|
1804
|
+
speed?: 'fast' | 'standard'
|
|
1805
|
+
}
|
|
1806
|
+
```
|
|
1807
|
+
|
|
1808
|
+
A model with its reasoning level and speed, which a `model` option or resolver
|
|
1809
|
+
returns to choose all three together. `reasoning` becomes the AI SDK
|
|
1810
|
+
`reasoning` call option. `speed: 'fast'` sets `providerOptions.gateway.speed`
|
|
1811
|
+
to prefer AI Gateway's faster tier when the model offers one, and `'standard'`
|
|
1812
|
+
removes a configured speed. Both override the same settings in `generation`.
|
|
1813
|
+
[`reasoningLevels`](#reasoninglevels) lists the levels.
|
|
1814
|
+
|
|
1482
1815
|
### `runWithHandlerContext(ctx, fn)`
|
|
1483
1816
|
|
|
1484
1817
|
The testing seam for context-reading tools: runs `fn` with `ctx`
|
|
1485
1818
|
installed as the ambient handler context, so a tool's `execute` can be
|
|
1486
1819
|
called directly in tests. Inside `fn`, `handlerContext()` returns `ctx`
|
|
1487
|
-
|
|
1820
|
+
with or without a contract argument.
|
|
1821
|
+
|
|
1822
|
+
See [Durable AI agents](/guides/ai-agents) for the protocol and complete
|
|
1823
|
+
examples.
|
|
1488
1824
|
|
|
1489
|
-
|
|
1825
|
+
## `experimental-a2/ai/client`
|
|
1490
1826
|
|
|
1491
|
-
|
|
1492
|
-
it into `createServer({ handlers })` beside application handlers when you need
|
|
1493
|
-
a custom assembly. The table handles input facts, generation requests, model
|
|
1494
|
-
step completion, tool calls, approval responses, and terminal tool results.
|
|
1495
|
-
Application handlers spread later can deliberately replace a built-in
|
|
1496
|
-
handler. Use `handler` from `experimental-a2/server` to normalize an
|
|
1497
|
-
existing entry before wrapping its `handler` and spreading its execution
|
|
1498
|
-
policy. Entries in this table's type are optional; narrow an entry before
|
|
1499
|
-
passing it to `handler`.
|
|
1827
|
+
### `createAgentClient(options)`
|
|
1500
1828
|
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1829
|
+
```ts
|
|
1830
|
+
createAgentClient<typeof agent>(options: {
|
|
1831
|
+
api: ClientApi
|
|
1832
|
+
gcTime?: number
|
|
1833
|
+
participant?: string
|
|
1834
|
+
reconnect?, fetch?, webSocket?, // as createClient
|
|
1835
|
+
}): AgentClient // an A2Client over the built-in AI protocol
|
|
1836
|
+
```
|
|
1505
1837
|
|
|
1506
|
-
|
|
1507
|
-
|
|
1838
|
+
A contract-free client for an `agent()` route. It accepts only agents whose
|
|
1839
|
+
reducer carries the built-in name (`typeof AI_STATE_REDUCER`): `agent({ name })`
|
|
1840
|
+
or `agent({ contract })` with an `agentContract()` that has no `reducer`. The AI protocol and its
|
|
1841
|
+
`AIState` reducer are library code, and the type argument, taken from a
|
|
1842
|
+
type-only import of the server module, types messages. Use it for pushes
|
|
1843
|
+
outside React, such as a first message sent before navigating, and pass it to
|
|
1844
|
+
`useAgent({ client })` so the hook shares its sessions. It pushes the built-in
|
|
1845
|
+
AI controls only. Agents built from an extended `agentContract()` use
|
|
1846
|
+
`createClient({ contract })` instead, so the client folds the same state.
|
|
1847
|
+
|
|
1848
|
+
## `experimental-a2/ai/react`
|
|
1849
|
+
|
|
1850
|
+
### `useAgent(options)`
|
|
1851
|
+
|
|
1852
|
+
```ts
|
|
1853
|
+
useAgent<typeof agent>(options: {
|
|
1854
|
+
id: string
|
|
1855
|
+
initial?: InitialSnapshot<AIState>
|
|
1856
|
+
participant?: string
|
|
1857
|
+
} & ({ api: string } | { client: AgentClient })): UseAgentResult
|
|
1858
|
+
|
|
1859
|
+
useAgent(options: {
|
|
1860
|
+
id: string
|
|
1861
|
+
initial?: InitialSnapshot<State>
|
|
1862
|
+
participant?: string
|
|
1863
|
+
} & (
|
|
1864
|
+
| { api: string; contract: Contract } // a shared agentContract(), imported for real
|
|
1865
|
+
| { client: A2Client } // createClient({ contract, api })
|
|
1866
|
+
)): UseAgentResult
|
|
1867
|
+
```
|
|
1868
|
+
|
|
1869
|
+
One agent session in React: its live optimistic `AIState` and actions that
|
|
1870
|
+
fill in protocol identities. With `api`, the hook keeps one shared client per
|
|
1871
|
+
route for the page's lifetime, reading `globalThis.fetch` on each request; with
|
|
1872
|
+
`client`, it uses yours, for a custom `fetch`, headers, or `gcTime`. Both forms
|
|
1873
|
+
accept only agents whose reducer carries the built-in name, like
|
|
1874
|
+
`createAgentClient()`. With `contract`, it folds the contract's application
|
|
1875
|
+
state too, keeping one client per contract and route; `state` is
|
|
1876
|
+
`AIState & X` and `push` accepts the contract's events. Pass that contract's
|
|
1877
|
+
`createClient({ contract, api })` as `client` instead to share it with pushes
|
|
1878
|
+
outside React, such as a first message sent before navigating. A custom
|
|
1879
|
+
contract, one not built by `agentContract()`, uses core A2's `useSession()` and
|
|
1880
|
+
`inputs` directly. Keep one options form per call site;
|
|
1881
|
+
the form must not change between renders.
|
|
1882
|
+
|
|
1883
|
+
The result is the session result (`state`, `index`, `connection`, `push`,
|
|
1884
|
+
`events`, `hydrated`, `reconnect`, `loadHistory`, `history`) plus:
|
|
1885
|
+
|
|
1886
|
+
| Member | Does |
|
|
1887
|
+
| --- | --- |
|
|
1888
|
+
| `messages` | `state.messages` |
|
|
1889
|
+
| `status` | `state.status` |
|
|
1890
|
+
| `indicator` | `'starting'`, `'thinking'`, `'compacting'`, or `null` |
|
|
1891
|
+
| `sendMessage(message, { generate? })` | queues a string (one text part) or a `UIMessage` |
|
|
1892
|
+
| `steer(message)` | steers the active turn after its current step, or sends when idle |
|
|
1893
|
+
| `stop()` | stops the active turn at the rendered frontier |
|
|
1894
|
+
| `approve(approval)`, `deny(approval, { reason? })` | answers a pending approval |
|
|
1895
|
+
| `respond(input, value)` | answers a pending application input |
|
|
1896
|
+
| `retry()` | retries the failed response with a fresh `retryId` |
|
|
1897
|
+
| `pause({ when })`, `resume()` | pauses and resumes |
|
|
1898
|
+
| `queue.edit(message)`, `queue.move({ inputId, beforeId })`, `queue.remove(inputId)`, `queue.sendNow(inputId)` | edits the inbox |
|
|
1899
|
+
|
|
1900
|
+
Every action returns a `PushResult`, like `push()`: it resolves to the
|
|
1901
|
+
persisted command events and rejects when the push fails, and `.confirmed`
|
|
1902
|
+
resolves when the stream delivers them back. Each event's `id` is the
|
|
1903
|
+
`commandId` of its `ai.control.decided` receipt. Actions read identities from
|
|
1904
|
+
the state of the render that created them, so `stop()` and `queue.sendNow()`
|
|
1905
|
+
carry the frontier the user saw. An action with nothing to address, such as
|
|
1906
|
+
`stop()` without an active turn or `retry()` without a failed one, resolves to
|
|
1907
|
+
an empty array. Actions are rebuilt on every render; their identity is not
|
|
1908
|
+
stable.
|
|
1909
|
+
|
|
1910
|
+
`indicator` is `starting` for an optimistic turn awaiting acceptance,
|
|
1911
|
+
`compacting` while a summary runs, `thinking` while the current model step has
|
|
1912
|
+
no visible part (reasoning and step boundaries do not count), and `null`
|
|
1913
|
+
otherwise.
|
|
1508
1914
|
|
|
1509
1915
|
## `experimental-a2/client`
|
|
1510
1916
|
|
|
1511
1917
|
```ts
|
|
1512
1918
|
createClient(options: {
|
|
1513
|
-
|
|
1919
|
+
contract: Contract // requires a public reducer
|
|
1514
1920
|
api: ClientApi
|
|
1515
1921
|
gcTime?: number // idle session lifetime; 5 minutes by default
|
|
1516
1922
|
participant?: string // default presence identity for every session
|
|
@@ -1550,8 +1956,13 @@ Call `session.reconnect()` to resume immediately with a fresh attempt count.
|
|
|
1550
1956
|
Releasing the last lease still closes the stream. A later first lease starts
|
|
1551
1957
|
a new connection. Push retries are independent of this policy; a push does
|
|
1552
1958
|
not resume a paused stream. An acknowledged push's `confirmed` promise waits
|
|
1553
|
-
until
|
|
1554
|
-
|
|
1959
|
+
until the stream delivers through the acknowledged index. A bootstrap
|
|
1960
|
+
snapshot, or a stream that starts after a supplied initial index, delivers
|
|
1961
|
+
through that index. Hydration does not otherwise confirm an unobserved
|
|
1962
|
+
acknowledgement. A push never waits for bootstrap. An HTTP push does not
|
|
1963
|
+
wait for a pending initial Promise either; a WebSocket push rides its
|
|
1964
|
+
connection, which opens once that Promise settles.
|
|
1965
|
+
Resumption preserves the stream's own position, even when
|
|
1555
1966
|
hydration has advanced state while paused.
|
|
1556
1967
|
|
|
1557
1968
|
A `SessionUrl` function runs for each HTTP request or WebSocket connection.
|
|
@@ -1564,7 +1975,7 @@ a string because its connection carries more than one session.
|
|
|
1564
1975
|
The framework-agnostic session client `experimental-a2/react` is built on: the SSE
|
|
1565
1976
|
subscription with frontier resume and reconnection, the optimistic push
|
|
1566
1977
|
queue with ack/rollback, and the local fold. `client.session(id, {
|
|
1567
|
-
|
|
1978
|
+
initial?, initialEvents?, participant? })` returns a
|
|
1568
1979
|
handle with `getSnapshot()`/`subscribe()` (the `useSyncExternalStore`
|
|
1569
1980
|
contract), `push()`, `loadHistory()`, `connect()`, `reconnect()`, and `close()`.
|
|
1570
1981
|
`connect()` takes a lease on the live stream and returns its release:
|
|
@@ -1591,6 +2002,16 @@ frontier. Idle handles are evicted after `gcTime`. This memory layer is
|
|
|
1591
2002
|
separate from `experimental-a2/cache-indexeddb`: memory preserves identity
|
|
1592
2003
|
across route transitions, while IndexedDB preserves the replica across reloads.
|
|
1593
2004
|
|
|
2005
|
+
`initial` accepts the same `InitialSnapshot<S>` as React. A pending Promise
|
|
2006
|
+
delays cold connection startup, including a connection acquired by `push`.
|
|
2007
|
+
An HTTP push is sent without waiting; a WebSocket push waits for that
|
|
2008
|
+
connection. It does not start another request. Replacing the source or calling `close()`
|
|
2009
|
+
invalidates its pending result and any deferred adoption behind a write.
|
|
2010
|
+
Releasing the last connection lease does not cancel the caller's Promise;
|
|
2011
|
+
it can still seed cached data without reopening transport. Idle eviction
|
|
2012
|
+
detaches A2's listener. Repeating the same Promise within that cached runtime
|
|
2013
|
+
does not restart cancelled work.
|
|
2014
|
+
|
|
1594
2015
|
## `experimental-a2/cache-indexeddb`
|
|
1595
2016
|
|
|
1596
2017
|
```ts
|
|
@@ -1617,15 +2038,31 @@ each handler → the events those handlers append.
|
|
|
1617
2038
|
|
|
1618
2039
|
### The span catalogue
|
|
1619
2040
|
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
|
1624
|
-
|
|
|
1625
|
-
| `a2.
|
|
1626
|
-
| `a2.
|
|
1627
|
-
| `a2.
|
|
1628
|
-
| `a2.
|
|
2041
|
+
All spans carry `a2.contract` and `a2.session_id`. Core spans describe the
|
|
2042
|
+
durable runtime; `a2.ai.*` spans describe the built-in AI handlers:
|
|
2043
|
+
|
|
2044
|
+
| Layer | Span | Wraps |
|
|
2045
|
+
| ----- | ---- | ----- |
|
|
2046
|
+
| Core | `a2.append` | validation + the store write (one batch) |
|
|
2047
|
+
| Core | `a2.drain` | one drain pass, including concurrent handlers |
|
|
2048
|
+
| Core | `a2.event` | one claimed dispatch, including its handler and completion |
|
|
2049
|
+
| Core | `a2.claim` | one `claimAvailable` store operation |
|
|
2050
|
+
| Core | `a2.commit` | one `completeAttempt` or `failAttempt` store operation |
|
|
2051
|
+
| Core | `a2.state` | one `state()` read: snapshot-plus-tail load + reducer fold |
|
|
2052
|
+
| Core | `a2.snapshot` | one background batch of checkpoint writes |
|
|
2053
|
+
| AI | `a2.ai.control` | applying an input/work report/session-close decision and preparing its returned events |
|
|
2054
|
+
| AI | `a2.ai.generate` | one generation handler, including Core calls and model/SDK work |
|
|
2055
|
+
| AI | `a2.ai.context` | constructing model messages, including tool prompt reconstruction when uncached |
|
|
2056
|
+
| AI | `a2.ai.compaction` | automatic summarization or the custom compaction callback |
|
|
2057
|
+
| AI | `a2.ai.tool` | one durable tool handler, including application execution |
|
|
2058
|
+
| AI | `a2.ai.ack` | waiting for a persisted work report's canonical events and control receipt |
|
|
2059
|
+
|
|
2060
|
+
Durations measure elapsed time, including nested work: `a2.event` contains its AI
|
|
2061
|
+
handler and later commit, AI handlers call Core, and `a2.state` includes reducer code.
|
|
2062
|
+
Acknowledgements overlap control processing; claims can cover several events,
|
|
2063
|
+
and snapshots run in the background. Summing spans or subtracting summed children
|
|
2064
|
+
does not yield exclusive Core/AI overhead. Model and application I/O is included;
|
|
2065
|
+
inspect its child spans for detail.
|
|
1629
2066
|
|
|
1630
2067
|
Attributes arrive in two waves: **at start** (passed to `span()`) and
|
|
1631
2068
|
**mid-span** (via `handle.setAttribute`; outcomes and counts aren't
|
|
@@ -1641,6 +2078,12 @@ alert on are all mid-span.
|
|
|
1641
2078
|
| `a2.append.armed` | `a2.append` | mid | `false` when the scheduler arm failed and this append degraded to append-driven healing |
|
|
1642
2079
|
| `a2.drain.outcome` | `a2.drain` | mid | `settled` \| `busy` \| `stalled` |
|
|
1643
2080
|
| `a2.drain.processed` | `a2.drain` | mid | events processed this pass |
|
|
2081
|
+
| `a2.claim.outcome` | `a2.claim` | mid | `claimed` \| `busy` \| `settled` |
|
|
2082
|
+
| `a2.claim.count` | `a2.claim` | mid | number of claimed events, or zero |
|
|
2083
|
+
| `a2.commit.operation` | `a2.commit` | start | `complete` \| `fail` |
|
|
2084
|
+
| `a2.commit.index` | `a2.commit` | start | event log position |
|
|
2085
|
+
| `a2.commit.attempt` | `a2.commit` | start | durable 1-based attempt ordinal |
|
|
2086
|
+
| `a2.commit.outcome` | `a2.commit` | mid | `completed` \| `failed` \| `dead_lettered` \| `superseded` |
|
|
1644
2087
|
| `a2.event.type` | `a2.event` | start | the event's type |
|
|
1645
2088
|
| `a2.event.index` | `a2.event` | start | log position |
|
|
1646
2089
|
| `a2.event.id` | `a2.event` | start | event id |
|
|
@@ -1650,12 +2093,28 @@ alert on are all mid-span.
|
|
|
1650
2093
|
| `a2.event.outcome` | `a2.event` | mid | `processed` \| `failed` \| `dead_lettered` \| `superseded` \| `surrendered` |
|
|
1651
2094
|
| `a2.event.aborted` | `a2.event` | mid | `true` when `abortOn` fired during the run |
|
|
1652
2095
|
| `a2.state.reducer` | `a2.state` | start | the reducer's name |
|
|
1653
|
-
| `a2.state.snapshot` | `a2.state` | mid | `hit` \| `miss` \| `rejected` (schema guard discarded it) |
|
|
2096
|
+
| `a2.state.snapshot` | `a2.state` | mid | `hit` \| `miss` \| `rejected` (schema guard discarded it) \| `memory` (served from the server's in-memory state) |
|
|
1654
2097
|
| `a2.state.folded` | `a2.state` | mid | events folded past the snapshot |
|
|
1655
2098
|
| `a2.state.index` | `a2.state` | mid | the frontier the returned state reflects |
|
|
1656
2099
|
| `a2.snapshot.reducer` | `a2.snapshot` | start | the reducer's name |
|
|
1657
2100
|
| `a2.snapshot.index` | `a2.snapshot` | start | greatest checkpoint index in the write batch |
|
|
1658
2101
|
| `a2.snapshot.count` | `a2.snapshot` | start | number of checkpoints in the write batch |
|
|
2102
|
+
| `a2.ai.control.type` | `a2.ai.control` | start | `ai.control.requested` \| `ai.work.reported` \| `ai.session.closed` |
|
|
2103
|
+
| `a2.ai.control.id` | `a2.ai.control` | start | triggering event id |
|
|
2104
|
+
| `a2.ai.control.attempt` | `a2.ai.control` | start | durable 1-based attempt ordinal |
|
|
2105
|
+
| `a2.ai.generate.request_id` | `a2.ai.generate` | start | generation request event id |
|
|
2106
|
+
| `a2.ai.generate.attempt` | `a2.ai.generate` | start | durable 1-based attempt ordinal |
|
|
2107
|
+
| `a2.ai.context.request_id` | `a2.ai.context` | start | generation request event id |
|
|
2108
|
+
| `a2.ai.context.phase` | `a2.ai.context` | start | `generation` \| `refresh` \| `tool` |
|
|
2109
|
+
| `a2.ai.compaction.request_id` | `a2.ai.compaction` | start | generation request event id |
|
|
2110
|
+
| `a2.ai.compaction.generation_id` | `a2.ai.compaction` | start | generation attempt id |
|
|
2111
|
+
| `a2.ai.compaction.policy` | `a2.ai.compaction` | start | `automatic` \| `custom` |
|
|
2112
|
+
| `a2.ai.tool.request_id` | `a2.ai.tool` | start | originating generation request event id |
|
|
2113
|
+
| `a2.ai.tool.call_id` | `a2.ai.tool` | start | tool call id |
|
|
2114
|
+
| `a2.ai.tool.name` | `a2.ai.tool` | start | tool name |
|
|
2115
|
+
| `a2.ai.tool.attempt` | `a2.ai.tool` | start | durable 1-based attempt ordinal |
|
|
2116
|
+
| `a2.ai.ack.request_id` | `a2.ai.ack` | start | reporting worker event id |
|
|
2117
|
+
| `a2.ai.ack.report_id` | `a2.ai.ack` | start | work report event id, matching the control span's id |
|
|
1659
2118
|
|
|
1660
2119
|
Drain outcomes: `settled` means nothing actionable is left; `busy` means live
|
|
1661
2120
|
per-event claims remain; `stalled` means a caught failure needs a later retry.
|
|
@@ -1685,24 +2144,29 @@ export const scheduler = vercelQueues()
|
|
|
1685
2144
|
| Option | Type | Default |
|
|
1686
2145
|
| --- | --- | --- |
|
|
1687
2146
|
| `topic` | `string` matching `[A-Za-z0-9_-]+` | `'a2'` |
|
|
1688
|
-
| `
|
|
2147
|
+
| `deploymentId` | `string \| null` | `VERCEL_DEPLOYMENT_ID` |
|
|
1689
2148
|
| `transport` | `QueueTransport` | lazy `@vercel/queue` client |
|
|
1690
2149
|
| `clock` | `{ now(): Date }` | system clock |
|
|
1691
2150
|
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
2151
|
+
`deploymentId` is passed to the Queue SDK unchanged and applies to every task,
|
|
2152
|
+
including recovery watchdogs. Omit it to use `VERCEL_DEPLOYMENT_ID`, pass a
|
|
2153
|
+
deployment ID to target that deployment, or pass `null` for deploymentless
|
|
2154
|
+
delivery. Publishing and consuming clients must use the same deployment scope.
|
|
2155
|
+
The same configured client handles sends, payload fetches, acknowledgements,
|
|
2156
|
+
and visibility changes. Callback contents never choose the client.
|
|
2157
|
+
|
|
2158
|
+
Pinned tasks stay with their target deployment after another deployment is
|
|
2159
|
+
promoted. Deploymentless delivery lets Vercel choose an eligible consumer; it
|
|
2160
|
+
does not select the latest production deployment. Every eligible consumer must
|
|
2161
|
+
use `deploymentId: null` and remain compatible with pending tasks and session
|
|
2162
|
+
work.
|
|
1697
2163
|
|
|
1698
2164
|
The adapter retains every task for seven days and accepts delays through six
|
|
1699
2165
|
days, leaving the final day for delivery and retries. `transport` and `clock`
|
|
1700
2166
|
are advanced injection seams for tests and custom delivery. An injected
|
|
1701
|
-
transport receives the
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
routing. The built-in transport adds its callback-routing metadata only after
|
|
1705
|
-
this injection seam.
|
|
2167
|
+
transport receives the topic, an exact `SchedulerTask`, and send options.
|
|
2168
|
+
`deploymentId` configures the built-in Queue SDK client only; an injected
|
|
2169
|
+
transport owns its provider configuration.
|
|
1706
2170
|
|
|
1707
2171
|
## `experimental-a2/scheduler-qstash`
|
|
1708
2172
|
|
|
@@ -1797,8 +2261,8 @@ scheduler work. A signed malformed envelope returns 489 with
|
|
|
1797
2261
|
`Upstash-NonRetryable-Error: true`. The same non-retryable response applies
|
|
1798
2262
|
when the declared or streamed body exceeds `maxBodyBytes`. Before an initial
|
|
1799
2263
|
publish, the adapter checks the normalized task with enough reserved room for
|
|
1800
|
-
its largest possible private store-outage lineage. Append
|
|
1801
|
-
maximum safe-integer generation. Drain tasks also reserve their actual root
|
|
2264
|
+
its largest possible private store-outage lineage. Append and clear tasks
|
|
2265
|
+
reserve a maximum safe-integer generation. Drain tasks also reserve their actual root
|
|
1802
2266
|
`dueAt`. Store-outage successors check their actual body. The adapter therefore
|
|
1803
2267
|
cannot initially accept a task that would exceed the same limit solely when
|
|
1804
2268
|
lineage is added. Raise the option to match a larger paid-plan payload limit
|
|
@@ -1830,15 +2294,16 @@ edge-access check.
|
|
|
1830
2294
|
Settled deliveries return 200. Busy deliveries await a durable successor
|
|
1831
2295
|
watchdog before returning 200. Stalled deliveries publish a fresh successor
|
|
1832
2296
|
before returning 200, which resets the per-message retry budget. A retryable
|
|
1833
|
-
store outage also hands
|
|
1834
|
-
acknowledging. Store-outage successors wait 30 seconds, then multiply the delay
|
|
2297
|
+
store outage also hands drain, append, and clear tasks to a fresh successor
|
|
2298
|
+
before acknowledging. Store-outage successors wait 30 seconds, then multiply the delay
|
|
1835
2299
|
by four for each signed generation, capped at one day. Their generation keeps
|
|
1836
2300
|
increasing after the cap so each handoff has a new deduplication id. Drain
|
|
1837
|
-
lineage includes the first failing task's `dueAt`, contract, and session.
|
|
1838
|
-
lineage includes
|
|
2301
|
+
lineage includes the first failing task's `dueAt`, contract, and session.
|
|
2302
|
+
Append and clear lineage includes the task id, contract, and session. Duplicate delivery of one
|
|
1839
2303
|
generation therefore publishes the same successor key without merging a later
|
|
1840
2304
|
drain episode. Append tasks otherwise return 200 after their ordinary fixed-id
|
|
1841
2305
|
append succeeds and any recovery watchdog for pending handler work is durable.
|
|
2306
|
+
Clear tasks return 200 after their conditional clear and its deletion finish.
|
|
1842
2307
|
Unknown contracts, missing scheduler configuration, schema failures, and
|
|
1843
2308
|
successor publish failures keep the current delivery retryable. Delivery is at
|
|
1844
2309
|
least once, so handler side effects still need idempotency keys.
|
|
@@ -1874,8 +2339,10 @@ the original error object.
|
|
|
1874
2339
|
| `experimental-a2/server` | `createServer`: implement a contract | none |
|
|
1875
2340
|
| `experimental-a2/client` | `createClient`: framework-agnostic session client | none |
|
|
1876
2341
|
| `experimental-a2/react` | `createReact` | `react` |
|
|
1877
|
-
| `experimental-a2/ai` |
|
|
1878
|
-
| `experimental-a2/ai/server` | AI SDK runner
|
|
2342
|
+
| `experimental-a2/ai` | `agentContract`, `handlerContext`, `inputs`, and the AI reducer: isomorphic | `ai` |
|
|
2343
|
+
| `experimental-a2/ai/server` | `agent`: the AI SDK runner as an A2 server | `ai` |
|
|
2344
|
+
| `experimental-a2/ai/client` | `createAgentClient`: contract-free agent client | `ai` |
|
|
2345
|
+
| `experimental-a2/ai/react` | `useAgent` | `ai`, `react` |
|
|
1879
2346
|
| `experimental-a2/store-postgres` | `postgres`: Postgres store backend | `pg` (or inject a client) |
|
|
1880
2347
|
| `experimental-a2/store-redis` | `redis`: Redis Streams store backend, push-native streaming | `ioredis` (or inject a client) |
|
|
1881
2348
|
| `experimental-a2/store-redis-http` | `redisHttp`: the same Redis store over provider REST APIs (Upstash) | none |
|