experimental-a2 0.16.1 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +352 -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-DgOBltJ_.js → ai-UrWfKeuv.js} +621 -216
- 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 +432 -258
- package/dist/ai-server.js.map +1 -1
- package/dist/{ai-CrEf6p_W.d.ts → ai-vpGtcaCB.d.ts} +114 -100
- 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 +671 -581
- 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 +656 -194
- 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 +6 -3
- package/src/ai-client.ts +62 -0
- package/src/ai-context-schema.ts +25 -5
- package/src/ai-context.ts +90 -40
- package/src/ai-control-server.ts +92 -72
- package/src/ai-coordinator.ts +23 -22
- package/src/ai-message-base.ts +209 -0
- package/src/ai-message-projection.ts +262 -64
- 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 -1964
- package/src/ai-stored-state.ts +48 -163
- package/src/ai.ts +339 -155
- package/src/bootstrap-stream.ts +72 -0
- package/src/bounded-cache.ts +55 -0
- package/src/client-render-session.ts +49 -0
- package/src/client.ts +688 -151
- package/src/contract.ts +92 -42
- package/src/deterministic-id.ts +37 -0
- package/src/equal-json.ts +32 -0
- package/src/errors.ts +13 -1
- package/src/index.ts +5 -1
- package/src/initial-snapshot.ts +39 -0
- package/src/internal.ts +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-CrEf6p_W.d.ts.map +0 -1
- package/dist/ai-DgOBltJ_.js.map +0 -1
- package/dist/client-BYzHjkwU.js.map +0 -1
- package/dist/client-CzyacQpJ.d.ts.map +0 -1
- package/dist/contract-CKRg_E4q.js.map +0 -1
- package/dist/errors-DCk6ch5n.js.map +0 -1
- package/dist/errors-DvhSXnxk.d.ts.map +0 -1
- package/dist/internal-Dq2qYxou.js.map +0 -1
- package/dist/react.d.ts.map +0 -1
- package/dist/reducer-DEMjEY_O.js +0 -29
- package/dist/reducer-DEMjEY_O.js.map +0 -1
- package/dist/reducer-otzHjuJj.d.ts.map +0 -1
- package/dist/scheduler-task-BpzhPnRS.js.map +0 -1
- package/dist/server-BD5ckxZb.js.map +0 -1
- package/dist/server-DbD7IJVK.d.ts.map +0 -1
- package/dist/store-DyZM6fS5.d.ts.map +0 -1
- package/dist/store-polling-CmxUbV93.js.map +0 -1
- package/dist/store-redis-notify-BVLUfI3j.js +0 -1583
- package/dist/store-redis-notify-BVLUfI3j.js.map +0 -1
- package/dist/telemetry-B5jzpy6y.d.ts.map +0 -1
- package/dist/validate-XKT4FSNn.js +0 -30
- package/dist/validate-XKT4FSNn.js.map +0 -1
- package/dist/wire-BO5wWCb1.js.map +0 -1
- package/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
|
|
@@ -1262,30 +1535,17 @@ The pending-command ledger survives request echo and retires on the matching
|
|
|
1262
1535
|
the client's existing optimistic overlay. This display adds no I/O and does not
|
|
1263
1536
|
read raw history.
|
|
1264
1537
|
|
|
1265
|
-
### `
|
|
1538
|
+
### `AIState`
|
|
1266
1539
|
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
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.
|
|
1270
1545
|
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
`reducer.view(storedState)` derives confirmed `AIState` for server code.
|
|
1276
|
-
`reducer.fromView(state)` converts a confirmed view into stored state, including
|
|
1277
|
-
when migrating an application-owned hydration value. Keep the stored state and
|
|
1278
|
-
its log index together when hydrating; do not fold already-seen history twice.
|
|
1279
|
-
`fold` can customize the confirmed `AIState` transition, and `stateSchema`
|
|
1280
|
-
validates that view after restoration. Both apply only to the transcript.
|
|
1281
|
-
Model context has its own built-in fold and schema. Changed custom fold semantics
|
|
1282
|
-
still require a new `name`.
|
|
1283
|
-
|
|
1284
|
-
Stored state is versioned, serializable data. Shared payload records and
|
|
1285
|
-
inverse changes replace repeated tool values and complete recovery copies.
|
|
1286
|
-
The reducer caches its live view and builds the records when the stored fields
|
|
1287
|
-
are read or serialized. Treat state and views as immutable. No event or tool
|
|
1288
|
-
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`
|
|
1289
1549
|
contains `inbox`, `active`, `starting`, the latest command `receipt`, session lifecycle,
|
|
1290
1550
|
messages, generation status, pending approvals
|
|
1291
1551
|
and input, tool activity, compaction, usage, the last error, and
|
|
@@ -1318,22 +1578,23 @@ Checkpoint size and serialization cost still grow with retained progress.
|
|
|
1318
1578
|
Tool-input deltas update `input-streaming` parts with best-effort partial JSON
|
|
1319
1579
|
for display. The state stays `input-streaming` until a completed input arrives;
|
|
1320
1580
|
partial parsing does not authorize execution or change interruption handling.
|
|
1321
|
-
|
|
1581
|
+
Application events are ignored by the AI fold.
|
|
1322
1582
|
|
|
1323
1583
|
### `deriveUIMessages(history)` and `reduceAIState(state, event)`
|
|
1324
1584
|
|
|
1325
|
-
`deriveUIMessages()` folds a raw A2 event history directly into
|
|
1326
|
-
`
|
|
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
|
|
1327
1587
|
standard reducer. Both synchronously project the same chunk-only protocol.
|
|
1328
1588
|
|
|
1329
1589
|
## `experimental-a2/ai/server`
|
|
1330
1590
|
|
|
1331
|
-
### `
|
|
1591
|
+
### `agent(options)`
|
|
1332
1592
|
|
|
1333
1593
|
```ts
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
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),
|
|
1337
1598
|
tools?,
|
|
1338
1599
|
instructions?,
|
|
1339
1600
|
generation?,
|
|
@@ -1342,17 +1603,49 @@ createAgentServer({
|
|
|
1342
1603
|
generate?,
|
|
1343
1604
|
compaction?,
|
|
1344
1605
|
progress?,
|
|
1345
|
-
|
|
1346
|
-
|
|
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
|
|
1347
1610
|
```
|
|
1348
1611
|
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
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).
|
|
1356
1649
|
|
|
1357
1650
|
`generation` contains per-step settings such as `temperature`,
|
|
1358
1651
|
`maxOutputTokens`, `topP`, provider options, and tool approval policy. A2 owns
|
|
@@ -1366,18 +1659,30 @@ untouched. A local tool reads its durable context with `handlerContext()` when
|
|
|
1366
1659
|
its handler executes. Model-step timeouts remain available; tool-execution
|
|
1367
1660
|
timeouts are not.
|
|
1368
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
|
+
|
|
1369
1669
|
`messageMetadata(context)` synchronously maps each default AI SDK stream part
|
|
1370
|
-
to the metadata type of the agent's `UIMessage`.
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
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.
|
|
1376
1678
|
|
|
1377
1679
|
`generate(context)` optionally replaces the default AI SDK generation. It
|
|
1378
|
-
receives
|
|
1379
|
-
|
|
1380
|
-
|
|
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,
|
|
1381
1686
|
continuation, and the durable lifecycle around that stream.
|
|
1382
1687
|
|
|
1383
1688
|
Generation requests use one durable lane, so model calls for a session do not
|
|
@@ -1389,7 +1694,8 @@ failure keeps later messages queued until the failed response is explicitly
|
|
|
1389
1694
|
retried or interrupted.
|
|
1390
1695
|
|
|
1391
1696
|
Independent authorized tool handlers run concurrently without a fixed
|
|
1392
|
-
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,
|
|
1393
1699
|
cancellation, calls, approvals, and terminal results for the active response.
|
|
1394
1700
|
Completed responses do not accumulate in its state. Its
|
|
1395
1701
|
`ctx.session.state(coordinator, { through: 'latest' })` reads the durable
|
|
@@ -1410,7 +1716,7 @@ require their own idempotency. Approved provider-executed calls may satisfy the
|
|
|
1410
1716
|
join from the approval response. Provider tools with declared deferred-result
|
|
1411
1717
|
support wait for their provider result instead of a local executor. An
|
|
1412
1718
|
authenticated provider callback appends the terminal `ai.tool.result` through
|
|
1413
|
-
the trusted server session API;
|
|
1719
|
+
the trusted server session API; `fetch` rejects it from browsers.
|
|
1414
1720
|
|
|
1415
1721
|
`compaction` defaults to automatic for supported Gateway requests. It accepts
|
|
1416
1722
|
`false` to disable compaction and discovery, `{ thresholdTokens?, instructions? }`
|
|
@@ -1421,16 +1727,15 @@ The resolver runs once per generation and returns a valid policy or `false`.
|
|
|
1421
1727
|
It can await application reducers through `context.session.state(reducer)`, whose
|
|
1422
1728
|
default boundary is the captured prompt frontier. Model, instruction, compaction,
|
|
1423
1729
|
metadata, and custom-generation contexts expose state access and omit raw history.
|
|
1424
|
-
Callbacks receive prompt messages and
|
|
1425
|
-
`execution`
|
|
1426
|
-
`{ event, messages, session, signal }
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
`context.session.state(
|
|
1432
|
-
|
|
1433
|
-
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.
|
|
1434
1739
|
Static options are validated at construction; resolved options are validated
|
|
1435
1740
|
before that generation starts. Each session uses its own resolved value. `thresholdTokens` is an
|
|
1436
1741
|
optional positive safe integer. `instructions` adds to A2's internal summary
|
|
@@ -1469,53 +1774,149 @@ caching. It does not invoke application generation callbacks, transforms, or too
|
|
|
1469
1774
|
hooks. Both custom policy callbacks and `generate` receive `modelMessages`, the
|
|
1470
1775
|
model-ready prompt including summaries, alongside typed UI `messages`.
|
|
1471
1776
|
|
|
1472
|
-
Built-in summaries remain separate from application-typed messages.
|
|
1473
|
-
|
|
1474
|
-
`
|
|
1475
|
-
`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
|
|
1476
1782
|
uses a separate context checkpoint; existing compactions do not require a
|
|
1477
1783
|
transcript read. Successful compaction is permanent, including across failure,
|
|
1478
1784
|
interruption, retry, and recovery. Retry reruns the failed step from the latest
|
|
1479
1785
|
committed context. Compaction defers while tool exchanges or A2-owned tool work
|
|
1480
1786
|
remain unsettled, checked with one fresh coordinator state read on each candidate.
|
|
1481
1787
|
Unresolved dead-lettered tool work also defers compaction until repair and
|
|
1482
|
-
settlement or session closure. `progress` controls durable batching
|
|
1483
|
-
|
|
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.
|
|
1484
1795
|
|
|
1485
1796
|
This entry point is server-only and resolves to a throwing browser stub.
|
|
1486
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
|
+
|
|
1487
1815
|
### `runWithHandlerContext(ctx, fn)`
|
|
1488
1816
|
|
|
1489
1817
|
The testing seam for context-reading tools: runs `fn` with `ctx`
|
|
1490
1818
|
installed as the ambient handler context, so a tool's `execute` can be
|
|
1491
1819
|
called directly in tests. Inside `fn`, `handlerContext()` returns `ctx`
|
|
1492
|
-
|
|
1820
|
+
with or without a contract argument.
|
|
1821
|
+
|
|
1822
|
+
See [Durable AI agents](/guides/ai-agents) for the protocol and complete
|
|
1823
|
+
examples.
|
|
1493
1824
|
|
|
1494
|
-
|
|
1825
|
+
## `experimental-a2/ai/client`
|
|
1495
1826
|
|
|
1496
|
-
|
|
1497
|
-
it into `createServer({ handlers })` beside application handlers when you need
|
|
1498
|
-
a custom assembly. The table handles input facts, generation requests, model
|
|
1499
|
-
step completion, tool calls, approval responses, and terminal tool results.
|
|
1500
|
-
Application handlers spread later can deliberately replace a built-in
|
|
1501
|
-
handler. Use `handler` from `experimental-a2/server` to normalize an
|
|
1502
|
-
existing entry before wrapping its `handler` and spreading its execution
|
|
1503
|
-
policy. Entries in this table's type are optional; narrow an entry before
|
|
1504
|
-
passing it to `handler`.
|
|
1827
|
+
### `createAgentClient(options)`
|
|
1505
1828
|
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
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
|
+
```
|
|
1510
1837
|
|
|
1511
|
-
|
|
1512
|
-
|
|
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.
|
|
1513
1914
|
|
|
1514
1915
|
## `experimental-a2/client`
|
|
1515
1916
|
|
|
1516
1917
|
```ts
|
|
1517
1918
|
createClient(options: {
|
|
1518
|
-
|
|
1919
|
+
contract: Contract // requires a public reducer
|
|
1519
1920
|
api: ClientApi
|
|
1520
1921
|
gcTime?: number // idle session lifetime; 5 minutes by default
|
|
1521
1922
|
participant?: string // default presence identity for every session
|
|
@@ -1555,8 +1956,13 @@ Call `session.reconnect()` to resume immediately with a fresh attempt count.
|
|
|
1555
1956
|
Releasing the last lease still closes the stream. A later first lease starts
|
|
1556
1957
|
a new connection. Push retries are independent of this policy; a push does
|
|
1557
1958
|
not resume a paused stream. An acknowledged push's `confirmed` promise waits
|
|
1558
|
-
until
|
|
1559
|
-
|
|
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
|
|
1560
1966
|
hydration has advanced state while paused.
|
|
1561
1967
|
|
|
1562
1968
|
A `SessionUrl` function runs for each HTTP request or WebSocket connection.
|
|
@@ -1569,7 +1975,7 @@ a string because its connection carries more than one session.
|
|
|
1569
1975
|
The framework-agnostic session client `experimental-a2/react` is built on: the SSE
|
|
1570
1976
|
subscription with frontier resume and reconnection, the optimistic push
|
|
1571
1977
|
queue with ack/rollback, and the local fold. `client.session(id, {
|
|
1572
|
-
|
|
1978
|
+
initial?, initialEvents?, participant? })` returns a
|
|
1573
1979
|
handle with `getSnapshot()`/`subscribe()` (the `useSyncExternalStore`
|
|
1574
1980
|
contract), `push()`, `loadHistory()`, `connect()`, `reconnect()`, and `close()`.
|
|
1575
1981
|
`connect()` takes a lease on the live stream and returns its release:
|
|
@@ -1596,6 +2002,16 @@ frontier. Idle handles are evicted after `gcTime`. This memory layer is
|
|
|
1596
2002
|
separate from `experimental-a2/cache-indexeddb`: memory preserves identity
|
|
1597
2003
|
across route transitions, while IndexedDB preserves the replica across reloads.
|
|
1598
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
|
+
|
|
1599
2015
|
## `experimental-a2/cache-indexeddb`
|
|
1600
2016
|
|
|
1601
2017
|
```ts
|
|
@@ -1622,15 +2038,31 @@ each handler → the events those handlers append.
|
|
|
1622
2038
|
|
|
1623
2039
|
### The span catalogue
|
|
1624
2040
|
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
|
1629
|
-
|
|
|
1630
|
-
| `a2.
|
|
1631
|
-
| `a2.
|
|
1632
|
-
| `a2.
|
|
1633
|
-
| `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.
|
|
1634
2066
|
|
|
1635
2067
|
Attributes arrive in two waves: **at start** (passed to `span()`) and
|
|
1636
2068
|
**mid-span** (via `handle.setAttribute`; outcomes and counts aren't
|
|
@@ -1646,6 +2078,12 @@ alert on are all mid-span.
|
|
|
1646
2078
|
| `a2.append.armed` | `a2.append` | mid | `false` when the scheduler arm failed and this append degraded to append-driven healing |
|
|
1647
2079
|
| `a2.drain.outcome` | `a2.drain` | mid | `settled` \| `busy` \| `stalled` |
|
|
1648
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` |
|
|
1649
2087
|
| `a2.event.type` | `a2.event` | start | the event's type |
|
|
1650
2088
|
| `a2.event.index` | `a2.event` | start | log position |
|
|
1651
2089
|
| `a2.event.id` | `a2.event` | start | event id |
|
|
@@ -1655,12 +2093,28 @@ alert on are all mid-span.
|
|
|
1655
2093
|
| `a2.event.outcome` | `a2.event` | mid | `processed` \| `failed` \| `dead_lettered` \| `superseded` \| `surrendered` |
|
|
1656
2094
|
| `a2.event.aborted` | `a2.event` | mid | `true` when `abortOn` fired during the run |
|
|
1657
2095
|
| `a2.state.reducer` | `a2.state` | start | the reducer's name |
|
|
1658
|
-
| `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) |
|
|
1659
2097
|
| `a2.state.folded` | `a2.state` | mid | events folded past the snapshot |
|
|
1660
2098
|
| `a2.state.index` | `a2.state` | mid | the frontier the returned state reflects |
|
|
1661
2099
|
| `a2.snapshot.reducer` | `a2.snapshot` | start | the reducer's name |
|
|
1662
2100
|
| `a2.snapshot.index` | `a2.snapshot` | start | greatest checkpoint index in the write batch |
|
|
1663
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 |
|
|
1664
2118
|
|
|
1665
2119
|
Drain outcomes: `settled` means nothing actionable is left; `busy` means live
|
|
1666
2120
|
per-event claims remain; `stalled` means a caught failure needs a later retry.
|
|
@@ -1690,24 +2144,29 @@ export const scheduler = vercelQueues()
|
|
|
1690
2144
|
| Option | Type | Default |
|
|
1691
2145
|
| --- | --- | --- |
|
|
1692
2146
|
| `topic` | `string` matching `[A-Za-z0-9_-]+` | `'a2'` |
|
|
1693
|
-
| `
|
|
2147
|
+
| `deploymentId` | `string \| null` | `VERCEL_DEPLOYMENT_ID` |
|
|
1694
2148
|
| `transport` | `QueueTransport` | lazy `@vercel/queue` client |
|
|
1695
2149
|
| `clock` | `{ now(): Date }` | system clock |
|
|
1696
2150
|
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
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.
|
|
1702
2163
|
|
|
1703
2164
|
The adapter retains every task for seven days and accepts delays through six
|
|
1704
2165
|
days, leaving the final day for delivery and retries. `transport` and `clock`
|
|
1705
2166
|
are advanced injection seams for tests and custom delivery. An injected
|
|
1706
|
-
transport receives the
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
routing. The built-in transport adds its callback-routing metadata only after
|
|
1710
|
-
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.
|
|
1711
2170
|
|
|
1712
2171
|
## `experimental-a2/scheduler-qstash`
|
|
1713
2172
|
|
|
@@ -1802,8 +2261,8 @@ scheduler work. A signed malformed envelope returns 489 with
|
|
|
1802
2261
|
`Upstash-NonRetryable-Error: true`. The same non-retryable response applies
|
|
1803
2262
|
when the declared or streamed body exceeds `maxBodyBytes`. Before an initial
|
|
1804
2263
|
publish, the adapter checks the normalized task with enough reserved room for
|
|
1805
|
-
its largest possible private store-outage lineage. Append
|
|
1806
|
-
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
|
|
1807
2266
|
`dueAt`. Store-outage successors check their actual body. The adapter therefore
|
|
1808
2267
|
cannot initially accept a task that would exceed the same limit solely when
|
|
1809
2268
|
lineage is added. Raise the option to match a larger paid-plan payload limit
|
|
@@ -1835,15 +2294,16 @@ edge-access check.
|
|
|
1835
2294
|
Settled deliveries return 200. Busy deliveries await a durable successor
|
|
1836
2295
|
watchdog before returning 200. Stalled deliveries publish a fresh successor
|
|
1837
2296
|
before returning 200, which resets the per-message retry budget. A retryable
|
|
1838
|
-
store outage also hands
|
|
1839
|
-
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
|
|
1840
2299
|
by four for each signed generation, capped at one day. Their generation keeps
|
|
1841
2300
|
increasing after the cap so each handoff has a new deduplication id. Drain
|
|
1842
|
-
lineage includes the first failing task's `dueAt`, contract, and session.
|
|
1843
|
-
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
|
|
1844
2303
|
generation therefore publishes the same successor key without merging a later
|
|
1845
2304
|
drain episode. Append tasks otherwise return 200 after their ordinary fixed-id
|
|
1846
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.
|
|
1847
2307
|
Unknown contracts, missing scheduler configuration, schema failures, and
|
|
1848
2308
|
successor publish failures keep the current delivery retryable. Delivery is at
|
|
1849
2309
|
least once, so handler side effects still need idempotency keys.
|
|
@@ -1879,8 +2339,10 @@ the original error object.
|
|
|
1879
2339
|
| `experimental-a2/server` | `createServer`: implement a contract | none |
|
|
1880
2340
|
| `experimental-a2/client` | `createClient`: framework-agnostic session client | none |
|
|
1881
2341
|
| `experimental-a2/react` | `createReact` | `react` |
|
|
1882
|
-
| `experimental-a2/ai` |
|
|
1883
|
-
| `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` |
|
|
1884
2346
|
| `experimental-a2/store-postgres` | `postgres`: Postgres store backend | `pg` (or inject a client) |
|
|
1885
2347
|
| `experimental-a2/store-redis` | `redis`: Redis Streams store backend, push-native streaming | `ioredis` (or inject a client) |
|
|
1886
2348
|
| `experimental-a2/store-redis-http` | `redisHttp`: the same Redis store over provider REST APIs (Upstash) | none |
|