experimental-a2 0.3.0 → 0.5.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 +207 -0
- package/dist/ai-CFNeCrRl.d.ts +334 -0
- package/dist/ai-CFNeCrRl.d.ts.map +1 -0
- package/dist/ai-server.browser.js +2 -0
- package/dist/ai-server.browser.js.map +1 -0
- package/dist/ai-server.d.ts +49 -9
- package/dist/ai-server.d.ts.map +1 -0
- package/dist/ai-server.js +123 -49
- package/dist/ai-server.js.map +1 -0
- package/dist/ai.d.ts +2 -303
- package/dist/ai.js +233 -86
- package/dist/ai.js.map +1 -0
- package/dist/cli-B3VuxoDe.js +599 -0
- package/dist/cli-B3VuxoDe.js.map +1 -0
- package/dist/cli-bin.d.ts +1 -0
- package/dist/cli-bin.js +7 -0
- package/dist/cli-bin.js.map +1 -0
- package/dist/cli.d.ts +20 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +2 -0
- package/dist/client-BKlyLiOU.js +1008 -0
- package/dist/client-BKlyLiOU.js.map +1 -0
- package/dist/client-D7mvIXrF.d.ts +191 -0
- package/dist/client-D7mvIXrF.d.ts.map +1 -0
- package/dist/client.d.ts +2 -90
- package/dist/client.js +1 -409
- package/dist/{contract-CG_adnu_.js → contract-48bUMgcL.js} +12 -2
- package/dist/contract-48bUMgcL.js.map +1 -0
- package/dist/{contract-C_3dIIEU.d.ts → contract-jIfaR085.d.ts} +63 -8
- package/dist/contract-jIfaR085.d.ts.map +1 -0
- package/dist/devtools-J_jZ2vQf.d.ts +152 -0
- package/dist/devtools-J_jZ2vQf.d.ts.map +1 -0
- package/dist/devtools-kJJaORn-.js +340 -0
- package/dist/devtools-kJJaORn-.js.map +1 -0
- package/dist/devtools-server.browser.js +3 -1
- package/dist/devtools-server.browser.js.map +1 -0
- package/dist/devtools-server.d.ts +4 -3
- package/dist/devtools-server.d.ts.map +1 -0
- package/dist/devtools-server.js +226 -43
- package/dist/devtools-server.js.map +1 -0
- package/dist/devtools.d.ts +2 -0
- package/dist/devtools.js +2 -0
- package/dist/{errors-BJRMd-h6.js → errors-BQuJpe82.js} +6 -4
- package/dist/errors-BQuJpe82.js.map +1 -0
- package/dist/{errors-xL_JTXsY.d.ts → errors-W6nwJ-fm.d.ts} +3 -2
- package/dist/errors-W6nwJ-fm.d.ts.map +1 -0
- package/dist/http.d.ts +135 -28
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +647 -61
- package/dist/http.js.map +1 -0
- package/dist/{idempotent-replay-BMyHrP0L.js → idempotent-replay-DuqEkYA7.js} +4 -2
- package/dist/idempotent-replay-DuqEkYA7.js.map +1 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +2 -2
- package/dist/{inspection-E7qbD0Xj.js → inspection-DaxB5jM2.js} +4 -1
- package/dist/inspection-DaxB5jM2.js.map +1 -0
- package/dist/internal-DstsI6Re.js +211 -0
- package/dist/internal-DstsI6Re.js.map +1 -0
- package/dist/otel.d.ts +3 -2
- package/dist/otel.d.ts.map +1 -0
- package/dist/otel.js +2 -0
- package/dist/otel.js.map +1 -0
- package/dist/platform-B4TnJtWu.js +34 -0
- package/dist/platform-B4TnJtWu.js.map +1 -0
- package/dist/react.d.ts +47 -15
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +30 -15
- package/dist/react.js.map +1 -0
- package/dist/retryable-lazy-DZWmHpii.js +2 -0
- package/dist/retryable-lazy-DZWmHpii.js.map +1 -0
- package/dist/scheduler-qstash.d.ts +79 -0
- package/dist/scheduler-qstash.d.ts.map +1 -0
- package/dist/scheduler-qstash.js +501 -0
- package/dist/scheduler-qstash.js.map +1 -0
- package/dist/scheduler-task-BpzhPnRS.js +56 -0
- package/dist/scheduler-task-BpzhPnRS.js.map +1 -0
- package/dist/{recovery-vercel.d.ts → scheduler-vercel.d.ts} +18 -24
- package/dist/scheduler-vercel.d.ts.map +1 -0
- package/dist/scheduler-vercel.js +228 -0
- package/dist/scheduler-vercel.js.map +1 -0
- package/dist/server-C72KOw51.js +1457 -0
- package/dist/server-C72KOw51.js.map +1 -0
- package/dist/server-DUF9pjsx.d.ts +261 -0
- package/dist/server-DUF9pjsx.d.ts.map +1 -0
- package/dist/server.browser.js +2 -0
- package/dist/server.browser.js.map +1 -0
- package/dist/server.d.ts +4 -155
- package/dist/server.js +2 -2
- package/dist/{log-ldf5g8Cx.d.ts → store-DysUkTH3.d.ts} +120 -35
- package/dist/store-DysUkTH3.d.ts.map +1 -0
- package/dist/{log-yJbXUf72.js → store-N8PXxDAS.js} +3 -1
- package/dist/store-N8PXxDAS.js.map +1 -0
- package/dist/store-codec-DTG0Ftek.js +10 -0
- package/dist/store-codec-DTG0Ftek.js.map +1 -0
- package/dist/store-memory.d.ts +12 -0
- package/dist/store-memory.d.ts.map +1 -0
- package/dist/{log-memory.js → store-memory.js} +145 -34
- package/dist/store-memory.js.map +1 -0
- package/dist/{log-polling-DZ1MiKLg.js → store-polling-dSeLxzfb.js} +7 -5
- package/dist/store-polling-dSeLxzfb.js.map +1 -0
- package/dist/{log-postgres.d.ts → store-postgres.d.ts} +7 -6
- package/dist/store-postgres.d.ts.map +1 -0
- package/dist/{log-postgres.js → store-postgres.js} +214 -24
- package/dist/store-postgres.js.map +1 -0
- package/dist/{log-redis-core-CyJ5L8yR.js → store-redis-core-BFLwz0Wj.js} +271 -66
- package/dist/store-redis-core-BFLwz0Wj.js.map +1 -0
- package/dist/{log-redis-http.d.ts → store-redis-http.d.ts} +8 -7
- package/dist/store-redis-http.d.ts.map +1 -0
- package/dist/{log-redis-http.js → store-redis-http.js} +22 -12
- package/dist/store-redis-http.js.map +1 -0
- package/dist/{log-redis.d.ts → store-redis.d.ts} +7 -6
- package/dist/store-redis.d.ts.map +1 -0
- package/dist/{log-redis.js → store-redis.js} +101 -39
- package/dist/store-redis.js.map +1 -0
- package/dist/{log-sqlite.d.ts → store-sqlite.d.ts} +7 -6
- package/dist/store-sqlite.d.ts.map +1 -0
- package/dist/{log-sqlite.js → store-sqlite.js} +118 -22
- package/dist/store-sqlite.js.map +1 -0
- package/dist/{telemetry-Cso0qyHQ.d.ts → telemetry-BjYHTfh2.d.ts} +3 -2
- package/dist/telemetry-BjYHTfh2.d.ts.map +1 -0
- package/dist/testing.browser.d.ts +1 -0
- package/dist/testing.browser.js +6 -0
- package/dist/testing.browser.js.map +1 -0
- package/dist/testing.d.ts +32 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +103 -0
- package/dist/testing.js.map +1 -0
- package/dist/validate-XKT4FSNn.js +2 -0
- package/dist/validate-XKT4FSNn.js.map +1 -0
- package/dist/wire-BFQmSJ-9.js +284 -0
- package/dist/wire-BFQmSJ-9.js.map +1 -0
- package/docs/01-quickstart.mdx +3 -3
- package/docs/concepts/01-contracts.mdx +18 -13
- package/docs/concepts/02-handlers.mdx +7 -7
- package/docs/concepts/03-durability.mdx +21 -21
- package/docs/concepts/04-state.mdx +18 -21
- package/docs/guides/01-timers.mdx +154 -49
- package/docs/guides/02-cancellation.mdx +30 -4
- package/docs/guides/03-react.mdx +76 -57
- package/docs/guides/04-local-first.mdx +1 -1
- package/docs/guides/05-production.mdx +310 -61
- package/docs/guides/06-ai-agents.mdx +252 -74
- package/docs/guides/07-devtools.mdx +137 -12
- package/docs/guides/08-application-data.mdx +1 -1
- package/docs/guides/09-presence.mdx +263 -0
- package/docs/guides/10-transports.mdx +140 -0
- package/docs/index.mdx +18 -14
- package/docs/reference/01-api.mdx +839 -110
- package/docs/reference/02-errors.mdx +13 -6
- package/package.json +24 -7
- package/src/ai-coordinator.ts +358 -0
- package/src/ai-projector.ts +524 -0
- package/src/ai-sdk-step.ts +261 -0
- package/src/ai-server.browser.ts +5 -0
- package/src/ai-server.ts +1719 -0
- package/src/ai.ts +2155 -0
- package/src/cache-indexeddb.ts +10 -0
- package/src/cli-bin.ts +5 -0
- package/src/cli.ts +1046 -0
- package/src/client.ts +1826 -0
- package/src/contract.ts +206 -0
- package/src/deterministic-id.ts +72 -0
- package/src/devtools-app.ts +989 -0
- package/src/devtools-server.browser.ts +5 -0
- package/src/devtools-server.ts +604 -0
- package/src/devtools.ts +716 -0
- package/src/errors.ts +50 -0
- package/src/http.ts +394 -0
- package/src/idempotent-replay.ts +53 -0
- package/src/index.ts +37 -0
- package/src/inspection.ts +39 -0
- package/src/internal.ts +426 -0
- package/src/otel.ts +59 -0
- package/src/platform.ts +60 -0
- package/src/push-envelope.ts +137 -0
- package/src/react.ts +284 -0
- package/src/reducer.ts +108 -0
- package/src/retryable-lazy.ts +27 -0
- package/src/scheduler-qstash.ts +915 -0
- package/src/scheduler-task.ts +106 -0
- package/src/scheduler-vercel.ts +437 -0
- package/src/server.browser.ts +12 -0
- package/src/server.ts +2700 -0
- package/src/session-socket.ts +548 -0
- package/src/sse.ts +141 -0
- package/src/standard-schema.ts +77 -0
- package/src/store-codec.ts +10 -0
- package/src/store-memory.ts +788 -0
- package/src/store-polling.ts +102 -0
- package/src/store-postgres.ts +1212 -0
- package/src/store-redis-core.ts +1494 -0
- package/src/store-redis-http.ts +116 -0
- package/src/store-redis.ts +458 -0
- package/src/store-sqlite.ts +1108 -0
- package/src/store.ts +385 -0
- package/src/telemetry.ts +47 -0
- package/src/testing.browser.ts +5 -0
- package/src/testing.ts +185 -0
- package/src/validate.ts +39 -0
- package/src/wire.ts +454 -0
- package/dist/internal-gCd5qMry.js +0 -44
- package/dist/log-memory.d.ts +0 -11
- package/dist/recovery-vercel.js +0 -119
- package/dist/server-BcLa4RFL.js +0 -877
- package/dist/wire-BVsgR8o9.js +0 -62
|
@@ -36,11 +36,15 @@ export async function POST(req: Request) {
|
|
|
36
36
|
| ------------------------- | ------------------------------------------------------------------ | --------- |
|
|
37
37
|
| `INVALID_PAYLOAD` | a payload fails its schema; nothing was written, and `details` carries the issues ([Standard Schema](https://standardschema.dev) format) | no |
|
|
38
38
|
| `UNKNOWN_EVENT_TYPE` | an event type isn't in the contract's `events` map | no |
|
|
39
|
+
| `UNKNOWN_PRESENCE_FIELD` | a presence field isn't in the contract's `presence` map | no |
|
|
40
|
+
| `PRESENCE_NOT_SUPPORTED` | the contract declares `presence` but the configured store has no presence capability (thrown at construction; for environment-resolved stores, deferred to the first store use) | no |
|
|
39
41
|
| `PARTIAL_DUPLICATE_BATCH` | a batch mixed already-appended and fresh events | no |
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
42
|
+
| `SUPERSEDED_ATTEMPT` | a handler append from an attempt a recovery claim has replaced; nothing was written, the successor owns the event. Also the abort reason on that handler's `ctx.signal` | no |
|
|
43
|
+
| `CLAIM_EXPIRED` | never thrown; only the `ctx.signal` abort reason when the attempt's lease lapsed without renewal (assumed loss, decided locally, not proof) | no |
|
|
44
|
+
| `STORE_UNAVAILABLE` | the store backend failed; nothing was written, original error as `cause` | **yes** |
|
|
45
|
+
| `STORE_NOT_CONFIGURED` | production boot with no `store` configured (during `next build` page collection, deferred to first use) | no |
|
|
42
46
|
|
|
43
|
-
Retryability is derivable from the code. `
|
|
47
|
+
Retryability is derivable from the code. `STORE_UNAVAILABLE` is the only one
|
|
44
48
|
worth retrying; the rest are deterministic caller bugs, and retrying them
|
|
45
49
|
is wasted work.
|
|
46
50
|
|
|
@@ -69,9 +73,12 @@ The push route serializes an `A2Error` as:
|
|
|
69
73
|
```
|
|
70
74
|
|
|
71
75
|
with a mapped status: `INVALID_PAYLOAD`, `UNKNOWN_EVENT_TYPE`, and
|
|
72
|
-
`PARTIAL_DUPLICATE_BATCH` are 400; `
|
|
76
|
+
`PARTIAL_DUPLICATE_BATCH` are 400; `SUPERSEDED_ATTEMPT` is 409;
|
|
77
|
+
`STORE_UNAVAILABLE` is 503.
|
|
73
78
|
|
|
74
79
|
The client's `push` deserializes the body back into an `A2Error`, so client
|
|
75
80
|
and server code branch on identical codes. `push` auto-retries only
|
|
76
|
-
`
|
|
77
|
-
`
|
|
81
|
+
`STORE_UNAVAILABLE`. The serializer pair, `errorResponse` and
|
|
82
|
+
`deserializeError`, ships in `experimental-a2/http` alongside `handle`;
|
|
83
|
+
a `before` hook that wants the wire's own error shapes returns
|
|
84
|
+
`errorResponse(new A2Error(...))`.
|
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "experimental-a2",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Durable sync and reactions for things with a lifecycle: one event log, derived state, and live client per session.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"a2": "./dist/cli-bin.js"
|
|
9
|
+
},
|
|
7
10
|
"sideEffects": false,
|
|
8
11
|
"engines": {
|
|
9
12
|
"node": ">=22.13"
|
|
@@ -20,6 +23,7 @@
|
|
|
20
23
|
"files": [
|
|
21
24
|
"dist",
|
|
22
25
|
"docs",
|
|
26
|
+
"src",
|
|
23
27
|
"CHANGELOG.md"
|
|
24
28
|
],
|
|
25
29
|
"exports": {
|
|
@@ -36,22 +40,29 @@
|
|
|
36
40
|
"default": "./dist/ai-server.js"
|
|
37
41
|
},
|
|
38
42
|
"./http": "./dist/http.js",
|
|
39
|
-
"./
|
|
40
|
-
"./
|
|
41
|
-
"./
|
|
42
|
-
"./
|
|
43
|
-
"./
|
|
44
|
-
"./
|
|
43
|
+
"./store-memory": "./dist/store-memory.js",
|
|
44
|
+
"./store-sqlite": "./dist/store-sqlite.js",
|
|
45
|
+
"./store-postgres": "./dist/store-postgres.js",
|
|
46
|
+
"./store-redis": "./dist/store-redis.js",
|
|
47
|
+
"./store-redis-http": "./dist/store-redis-http.js",
|
|
48
|
+
"./scheduler-qstash": "./dist/scheduler-qstash.js",
|
|
49
|
+
"./scheduler-vercel": "./dist/scheduler-vercel.js",
|
|
45
50
|
"./cache-indexeddb": "./dist/cache-indexeddb.js",
|
|
46
51
|
"./otel": "./dist/otel.js",
|
|
52
|
+
"./devtools": "./dist/devtools.js",
|
|
47
53
|
"./devtools/server": {
|
|
48
54
|
"browser": "./dist/devtools-server.browser.js",
|
|
49
55
|
"default": "./dist/devtools-server.js"
|
|
50
56
|
},
|
|
57
|
+
"./testing": {
|
|
58
|
+
"browser": "./dist/testing.browser.js",
|
|
59
|
+
"default": "./dist/testing.js"
|
|
60
|
+
},
|
|
51
61
|
"./package.json": "./package.json"
|
|
52
62
|
},
|
|
53
63
|
"peerDependencies": {
|
|
54
64
|
"@opentelemetry/api": "^1.9.0",
|
|
65
|
+
"@upstash/qstash": "^2.11.0",
|
|
55
66
|
"@vercel/queue": "*",
|
|
56
67
|
"ai": "^7.0.0",
|
|
57
68
|
"ioredis": "^5.4.0",
|
|
@@ -62,6 +73,9 @@
|
|
|
62
73
|
"@opentelemetry/api": {
|
|
63
74
|
"optional": true
|
|
64
75
|
},
|
|
76
|
+
"@upstash/qstash": {
|
|
77
|
+
"optional": true
|
|
78
|
+
},
|
|
65
79
|
"@vercel/queue": {
|
|
66
80
|
"optional": true
|
|
67
81
|
},
|
|
@@ -80,12 +94,15 @@
|
|
|
80
94
|
},
|
|
81
95
|
"devDependencies": {
|
|
82
96
|
"@electric-sql/pglite": "^0.3.14",
|
|
97
|
+
"@upstash/qstash": "^2.11.3",
|
|
83
98
|
"ai": "^7.0.58",
|
|
84
99
|
"ioredis": "^5.9.0",
|
|
85
100
|
"@opentelemetry/api": "^1.9.0",
|
|
86
101
|
"@types/pg": "^8.15.0",
|
|
102
|
+
"@types/ws": "^8.18.1",
|
|
87
103
|
"@vercel/queue": "^0.2.0",
|
|
88
104
|
"pg": "^8.16.0",
|
|
105
|
+
"ws": "^8.21.3",
|
|
89
106
|
"@testing-library/react": "^16.1.0",
|
|
90
107
|
"@types/react": "^19.0.0",
|
|
91
108
|
"esbuild": "^0.25.0",
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import type { UIMessage } from 'ai'
|
|
2
|
+
import type {
|
|
3
|
+
AIEventDefs,
|
|
4
|
+
ApprovalRequestedPayload,
|
|
5
|
+
ApprovalRespondedPayload,
|
|
6
|
+
GenerationCompletedPayload,
|
|
7
|
+
GenerationFailedPayload,
|
|
8
|
+
GenerationRequestedPayload,
|
|
9
|
+
GenerationStartedPayload,
|
|
10
|
+
InputRequestedPayload,
|
|
11
|
+
MessageCreatedPayload,
|
|
12
|
+
MessageInterruptedPayload,
|
|
13
|
+
ToolCalledPayload,
|
|
14
|
+
ToolResultPayload,
|
|
15
|
+
} from './ai.ts'
|
|
16
|
+
import type { Contract, ContractEvent, EventDefs } from './contract.ts'
|
|
17
|
+
import type { Reducer } from './reducer.ts'
|
|
18
|
+
|
|
19
|
+
export type CoordinatorCall = {
|
|
20
|
+
index: number
|
|
21
|
+
call: ToolCalledPayload
|
|
22
|
+
approval?: ApprovalRequestedPayload
|
|
23
|
+
response?: ApprovalRespondedPayload
|
|
24
|
+
responseIndex?: number
|
|
25
|
+
terminal: boolean
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type CoordinatorResponse = {
|
|
29
|
+
rootMessageId: string
|
|
30
|
+
responseMessageId: string
|
|
31
|
+
status: 'requested' | 'generating' | 'waiting' | 'failed'
|
|
32
|
+
activeRequestId?: string
|
|
33
|
+
generation?: GenerationStartedPayload
|
|
34
|
+
completion?: GenerationCompletedPayload
|
|
35
|
+
failure?: GenerationFailedPayload
|
|
36
|
+
calls: CoordinatorCall[]
|
|
37
|
+
inputs: InputRequestedPayload[]
|
|
38
|
+
inputResponse?: {
|
|
39
|
+
index: number
|
|
40
|
+
generationId: string
|
|
41
|
+
inputId: string
|
|
42
|
+
name: string
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type AICoordinatorState = {
|
|
47
|
+
closed: boolean
|
|
48
|
+
queued: Array<{ index: number; messageId: string; generate: boolean }>
|
|
49
|
+
response?: CoordinatorResponse
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const updateResponse = (
|
|
53
|
+
state: AICoordinatorState,
|
|
54
|
+
update: (response: CoordinatorResponse) => CoordinatorResponse,
|
|
55
|
+
): AICoordinatorState =>
|
|
56
|
+
state.response === undefined
|
|
57
|
+
? state
|
|
58
|
+
: { ...state, response: update(state.response) }
|
|
59
|
+
|
|
60
|
+
const foldCoordinator = <M extends UIMessage, D extends EventDefs>(
|
|
61
|
+
state: AICoordinatorState,
|
|
62
|
+
event: ContractEvent<D>,
|
|
63
|
+
): AICoordinatorState => {
|
|
64
|
+
switch (event.type) {
|
|
65
|
+
case 'ai.session.closed':
|
|
66
|
+
return { closed: true, queued: [] }
|
|
67
|
+
case 'ai.message.created': {
|
|
68
|
+
const payload = event.payload as MessageCreatedPayload<M>
|
|
69
|
+
const message = payload.message
|
|
70
|
+
if (message.role !== 'user') return state
|
|
71
|
+
return {
|
|
72
|
+
...state,
|
|
73
|
+
queued: [
|
|
74
|
+
...state.queued.filter((item) => item.messageId !== message.id),
|
|
75
|
+
{
|
|
76
|
+
index: event.index,
|
|
77
|
+
messageId: message.id,
|
|
78
|
+
generate: payload.generate !== false,
|
|
79
|
+
},
|
|
80
|
+
].toSorted((left, right) => left.index - right.index),
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
case 'ai.generation.requested': {
|
|
84
|
+
const request = event.payload as GenerationRequestedPayload
|
|
85
|
+
if (request.reason === 'message') {
|
|
86
|
+
const responseMessageId =
|
|
87
|
+
request.responseMessageId ?? `${request.messageId}:assistant`
|
|
88
|
+
const requested = state.queued.find(
|
|
89
|
+
(item) => item.messageId === request.messageId,
|
|
90
|
+
)
|
|
91
|
+
return {
|
|
92
|
+
...state,
|
|
93
|
+
queued:
|
|
94
|
+
requested === undefined
|
|
95
|
+
? state.queued.filter(
|
|
96
|
+
(item) => item.messageId !== request.messageId,
|
|
97
|
+
)
|
|
98
|
+
: state.queued.filter((item) => item.index > requested.index),
|
|
99
|
+
response: {
|
|
100
|
+
rootMessageId: request.messageId,
|
|
101
|
+
responseMessageId,
|
|
102
|
+
status: 'requested',
|
|
103
|
+
activeRequestId: event.id,
|
|
104
|
+
calls: [],
|
|
105
|
+
inputs: [],
|
|
106
|
+
},
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (
|
|
110
|
+
state.response === undefined ||
|
|
111
|
+
request.responseMessageId !== state.response.responseMessageId
|
|
112
|
+
) {
|
|
113
|
+
return state
|
|
114
|
+
}
|
|
115
|
+
if (
|
|
116
|
+
(request.reason === 'tool' &&
|
|
117
|
+
(!continuationReady(state) ||
|
|
118
|
+
event.id !==
|
|
119
|
+
`ai.generate:tools:${state.response.generation?.generationId}`)) ||
|
|
120
|
+
(request.reason === 'retry' && state.response.status !== 'failed') ||
|
|
121
|
+
(request.reason === 'input' &&
|
|
122
|
+
state.response.inputResponse === undefined)
|
|
123
|
+
) {
|
|
124
|
+
return state
|
|
125
|
+
}
|
|
126
|
+
return updateResponse(state, (response) => ({
|
|
127
|
+
rootMessageId: response.rootMessageId,
|
|
128
|
+
responseMessageId: response.responseMessageId,
|
|
129
|
+
status: 'requested',
|
|
130
|
+
activeRequestId: event.id,
|
|
131
|
+
calls: [],
|
|
132
|
+
inputs: [],
|
|
133
|
+
}))
|
|
134
|
+
}
|
|
135
|
+
case 'ai.generation.started': {
|
|
136
|
+
const generation = event.payload as GenerationStartedPayload
|
|
137
|
+
if (
|
|
138
|
+
state.response?.responseMessageId !== generation.responseMessageId ||
|
|
139
|
+
state.response.activeRequestId !== generation.requestId ||
|
|
140
|
+
(state.response.generation?.requestId === generation.requestId &&
|
|
141
|
+
state.response.generation.attempt >= generation.attempt)
|
|
142
|
+
) {
|
|
143
|
+
return state
|
|
144
|
+
}
|
|
145
|
+
return updateResponse(state, (response) => ({
|
|
146
|
+
rootMessageId: response.rootMessageId,
|
|
147
|
+
responseMessageId: response.responseMessageId,
|
|
148
|
+
status: 'generating',
|
|
149
|
+
generation,
|
|
150
|
+
activeRequestId: generation.requestId,
|
|
151
|
+
calls: [],
|
|
152
|
+
inputs: [],
|
|
153
|
+
}))
|
|
154
|
+
}
|
|
155
|
+
case 'ai.generation.completed': {
|
|
156
|
+
const completion = event.payload as GenerationCompletedPayload
|
|
157
|
+
if (
|
|
158
|
+
state.response?.generation?.generationId !== completion.generationId
|
|
159
|
+
) {
|
|
160
|
+
return state
|
|
161
|
+
}
|
|
162
|
+
return updateResponse(state, (response) => ({
|
|
163
|
+
...response,
|
|
164
|
+
status: 'waiting',
|
|
165
|
+
completion,
|
|
166
|
+
}))
|
|
167
|
+
}
|
|
168
|
+
case 'ai.generation.failed': {
|
|
169
|
+
const failure = event.payload as GenerationFailedPayload
|
|
170
|
+
const response = state.response
|
|
171
|
+
const ownsActiveGeneration =
|
|
172
|
+
response?.generation?.generationId === failure.generationId
|
|
173
|
+
const ownsActiveStepLimit =
|
|
174
|
+
failure.stepLimit === true &&
|
|
175
|
+
response?.activeRequestId ===
|
|
176
|
+
`ai.generate:tools:${failure.generationId}` &&
|
|
177
|
+
response.responseMessageId === failure.responseMessageId
|
|
178
|
+
if (!ownsActiveGeneration && !ownsActiveStepLimit) {
|
|
179
|
+
return state
|
|
180
|
+
}
|
|
181
|
+
return updateResponse(state, (current) => ({
|
|
182
|
+
...current,
|
|
183
|
+
status: 'failed',
|
|
184
|
+
failure,
|
|
185
|
+
}))
|
|
186
|
+
}
|
|
187
|
+
case 'ai.message.completed': {
|
|
188
|
+
const messageId = (event.payload as { messageId: string }).messageId
|
|
189
|
+
return state.response?.responseMessageId === messageId
|
|
190
|
+
? { closed: state.closed, queued: state.queued }
|
|
191
|
+
: state
|
|
192
|
+
}
|
|
193
|
+
case 'ai.message.interrupted': {
|
|
194
|
+
const interruption = event.payload as MessageInterruptedPayload
|
|
195
|
+
const response = state.response
|
|
196
|
+
return response?.responseMessageId === interruption.messageId &&
|
|
197
|
+
(interruption.generationId === response.generation?.generationId ||
|
|
198
|
+
interruption.requestId === response.activeRequestId)
|
|
199
|
+
? { closed: state.closed, queued: state.queued }
|
|
200
|
+
: state
|
|
201
|
+
}
|
|
202
|
+
case 'ai.tool.called': {
|
|
203
|
+
const call = event.payload as ToolCalledPayload
|
|
204
|
+
if (state.response?.generation?.generationId !== call.generationId) {
|
|
205
|
+
return state
|
|
206
|
+
}
|
|
207
|
+
return updateResponse(state, (response) => ({
|
|
208
|
+
...response,
|
|
209
|
+
calls: [
|
|
210
|
+
...response.calls.filter(
|
|
211
|
+
(candidate) => candidate.call.toolCallId !== call.toolCallId,
|
|
212
|
+
),
|
|
213
|
+
{ index: event.index, call, terminal: false },
|
|
214
|
+
],
|
|
215
|
+
}))
|
|
216
|
+
}
|
|
217
|
+
case 'ai.approval.requested': {
|
|
218
|
+
const approval = event.payload as ApprovalRequestedPayload
|
|
219
|
+
if (state.response?.generation?.generationId !== approval.generationId) {
|
|
220
|
+
return state
|
|
221
|
+
}
|
|
222
|
+
return updateResponse(state, (response) => ({
|
|
223
|
+
...response,
|
|
224
|
+
calls: response.calls.map((candidate) =>
|
|
225
|
+
candidate.call.toolCallId === approval.toolCallId
|
|
226
|
+
? { ...candidate, approval }
|
|
227
|
+
: candidate,
|
|
228
|
+
),
|
|
229
|
+
}))
|
|
230
|
+
}
|
|
231
|
+
case 'ai.approval.responded': {
|
|
232
|
+
const approval = event.payload as ApprovalRespondedPayload
|
|
233
|
+
if (
|
|
234
|
+
state.response?.responseMessageId !== approval.messageId ||
|
|
235
|
+
state.response.generation?.generationId !== approval.generationId
|
|
236
|
+
) {
|
|
237
|
+
return state
|
|
238
|
+
}
|
|
239
|
+
return updateResponse(state, (response) => ({
|
|
240
|
+
...response,
|
|
241
|
+
calls: response.calls.map((candidate) =>
|
|
242
|
+
candidate.approval?.approvalId === approval.approvalId
|
|
243
|
+
? { ...candidate, response: approval, responseIndex: event.index }
|
|
244
|
+
: candidate,
|
|
245
|
+
),
|
|
246
|
+
}))
|
|
247
|
+
}
|
|
248
|
+
case 'ai.tool.result': {
|
|
249
|
+
const result = event.payload as ToolResultPayload
|
|
250
|
+
if (
|
|
251
|
+
result.preliminary === true ||
|
|
252
|
+
state.response?.generation?.generationId !== result.generationId
|
|
253
|
+
) {
|
|
254
|
+
return state
|
|
255
|
+
}
|
|
256
|
+
return updateResponse(state, (response) => ({
|
|
257
|
+
...response,
|
|
258
|
+
calls: response.calls.map((candidate) =>
|
|
259
|
+
candidate.call.toolCallId === result.toolCallId
|
|
260
|
+
? { ...candidate, terminal: true }
|
|
261
|
+
: candidate,
|
|
262
|
+
),
|
|
263
|
+
}))
|
|
264
|
+
}
|
|
265
|
+
case 'ai.input.requested': {
|
|
266
|
+
const input = event.payload as InputRequestedPayload
|
|
267
|
+
if (
|
|
268
|
+
state.response?.responseMessageId !== input.messageId ||
|
|
269
|
+
state.response.generation?.generationId !== input.generationId
|
|
270
|
+
) {
|
|
271
|
+
return state
|
|
272
|
+
}
|
|
273
|
+
return updateResponse(state, (response) => ({
|
|
274
|
+
...response,
|
|
275
|
+
inputs: [
|
|
276
|
+
...response.inputs.filter(
|
|
277
|
+
(candidate) =>
|
|
278
|
+
candidate.messageId !== input.messageId ||
|
|
279
|
+
candidate.generationId !== input.generationId ||
|
|
280
|
+
candidate.inputId !== input.inputId,
|
|
281
|
+
),
|
|
282
|
+
input,
|
|
283
|
+
],
|
|
284
|
+
}))
|
|
285
|
+
}
|
|
286
|
+
case 'ai.input.responded': {
|
|
287
|
+
const input = event.payload as {
|
|
288
|
+
messageId: string
|
|
289
|
+
generationId: string
|
|
290
|
+
inputId: string
|
|
291
|
+
name: string
|
|
292
|
+
}
|
|
293
|
+
const requested = state.response?.inputs.find(
|
|
294
|
+
(candidate) =>
|
|
295
|
+
candidate.messageId === input.messageId &&
|
|
296
|
+
candidate.generationId === input.generationId &&
|
|
297
|
+
candidate.inputId === input.inputId &&
|
|
298
|
+
candidate.name === input.name,
|
|
299
|
+
)
|
|
300
|
+
if (
|
|
301
|
+
state.response?.responseMessageId !== input.messageId ||
|
|
302
|
+
state.response.generation?.generationId !== input.generationId ||
|
|
303
|
+
requested === undefined
|
|
304
|
+
) {
|
|
305
|
+
return state
|
|
306
|
+
}
|
|
307
|
+
return updateResponse(state, (response) => ({
|
|
308
|
+
...response,
|
|
309
|
+
inputs: response.inputs.filter(
|
|
310
|
+
(candidate) => candidate.inputId !== input.inputId,
|
|
311
|
+
),
|
|
312
|
+
inputResponse: {
|
|
313
|
+
index: event.index,
|
|
314
|
+
generationId: input.generationId,
|
|
315
|
+
inputId: input.inputId,
|
|
316
|
+
name: requested.name,
|
|
317
|
+
},
|
|
318
|
+
}))
|
|
319
|
+
}
|
|
320
|
+
default:
|
|
321
|
+
return state
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export const aiCoordinatorReducer = <
|
|
326
|
+
M extends UIMessage,
|
|
327
|
+
D extends AIEventDefs<M> & EventDefs,
|
|
328
|
+
>(
|
|
329
|
+
contract: Contract<D>,
|
|
330
|
+
): Reducer<D, AICoordinatorState> =>
|
|
331
|
+
contract
|
|
332
|
+
.reducer({
|
|
333
|
+
name: 'a2.ai.coordinator.v3',
|
|
334
|
+
initialState: {
|
|
335
|
+
closed: false,
|
|
336
|
+
queued: [],
|
|
337
|
+
} as AICoordinatorState,
|
|
338
|
+
})
|
|
339
|
+
.fold((state, event) => foldCoordinator<M, D>(state, event))
|
|
340
|
+
|
|
341
|
+
export const continuationReady = (state: AICoordinatorState): boolean => {
|
|
342
|
+
const response = state.response
|
|
343
|
+
return (
|
|
344
|
+
!state.closed &&
|
|
345
|
+
response?.completion?.finishReason === 'tool-calls' &&
|
|
346
|
+
response.generation?.generationId === response.completion.generationId &&
|
|
347
|
+
response.failure === undefined &&
|
|
348
|
+
response.calls.length > 0 &&
|
|
349
|
+
response.calls.every(
|
|
350
|
+
(call) =>
|
|
351
|
+
call.terminal ||
|
|
352
|
+
(call.call.providerExecuted === true &&
|
|
353
|
+
call.call.supportsDeferredResults !== true &&
|
|
354
|
+
call.approval !== undefined &&
|
|
355
|
+
call.response !== undefined),
|
|
356
|
+
)
|
|
357
|
+
)
|
|
358
|
+
}
|