experimental-a2 0.13.0 → 0.14.1
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 +43 -0
- package/dist/{actor-DJi3RsNu.d.ts → actor-BfQSE0KC.d.ts} +4 -4
- package/dist/{actor-DJi3RsNu.d.ts.map → actor-BfQSE0KC.d.ts.map} +1 -1
- package/dist/actor-client.d.ts +1 -1
- package/dist/actor-client.js +1 -1
- package/dist/actor-react.d.ts +3 -3
- package/dist/actor-react.js +2 -2
- package/dist/{actor-shared-DI7J5upy.js → actor-shared-B5tJfzt-.js} +2 -2
- package/dist/{actor-shared-DI7J5upy.js.map → actor-shared-B5tJfzt-.js.map} +1 -1
- package/dist/actor.d.ts +1 -1
- package/dist/actor.js +3 -3
- package/dist/ai-Cai-lCbj.d.ts +580 -0
- package/dist/ai-Cai-lCbj.d.ts.map +1 -0
- package/dist/ai-control-CcD4hh3y.js +119 -0
- package/dist/ai-control-CcD4hh3y.js.map +1 -0
- package/dist/ai-server.d.ts +6 -6
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +1053 -513
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.d.ts +2 -365
- package/dist/ai.js +801 -80
- package/dist/ai.js.map +1 -1
- package/dist/{client-P_NNNRM-.d.ts → client-BAEABRZB.d.ts} +2 -2
- package/dist/{client-P_NNNRM-.d.ts.map → client-BAEABRZB.d.ts.map} +1 -1
- package/dist/{client-Bf6uSEAk.js → client-BYzHjkwU.js} +21 -6
- package/dist/client-BYzHjkwU.js.map +1 -0
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/{contract-48bUMgcL.js → contract-CKRg_E4q.js} +3 -26
- package/dist/contract-CKRg_E4q.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/react.d.ts +2 -2
- package/dist/react.js +1 -1
- package/dist/{reducer-DJKWm3cp.d.ts → reducer-BcS9VDKC.d.ts} +4 -1
- package/dist/{reducer-DJKWm3cp.d.ts.map → reducer-BcS9VDKC.d.ts.map} +1 -1
- package/dist/reducer-DEMjEY_O.js +29 -0
- package/dist/reducer-DEMjEY_O.js.map +1 -0
- package/dist/scheduler-qstash.d.ts +2 -2
- package/dist/scheduler-qstash.js +1 -1
- package/dist/scheduler-vercel.d.ts +2 -2
- package/dist/scheduler-vercel.js +1 -1
- package/dist/{server-DjZZa1wr.d.ts → server-Bp5Nd1pF.d.ts} +3 -3
- package/dist/{server-DjZZa1wr.d.ts.map → server-Bp5Nd1pF.d.ts.map} +1 -1
- package/dist/{server-BeNADlCI.js → server-CjJSGcF7.js} +4 -3
- package/dist/server-CjJSGcF7.js.map +1 -0
- package/dist/server.d.ts +3 -3
- package/dist/server.js +1 -1
- package/dist/{store-DtDOWLSn.d.ts → store-D_yhNdPz.d.ts} +7 -2
- package/dist/{store-DtDOWLSn.d.ts.map → store-D_yhNdPz.d.ts.map} +1 -1
- package/dist/store-N8PXxDAS.js.map +1 -1
- package/dist/store-memory.d.ts +1 -1
- package/dist/store-postgres.d.ts +1 -1
- package/dist/store-postgres.js +19 -0
- package/dist/store-postgres.js.map +1 -1
- package/dist/store-redis-http.d.ts +1 -1
- package/dist/store-redis-http.js +1 -1
- package/dist/{store-redis-notify-BUCyXOn0.js → store-redis-notify-D2EI6gwX.js} +27 -2
- package/dist/store-redis-notify-D2EI6gwX.js.map +1 -0
- package/dist/store-redis.d.ts +1 -1
- package/dist/store-redis.js +1 -1
- package/dist/store-sqlite.d.ts +1 -1
- package/docs/guides/06-ai-agents.mdx +287 -65
- package/docs/reference/01-api.mdx +122 -27
- package/examples/playground/app/agent/[agentId]/agent-client.tsx +118 -21
- package/examples/playground/app/agent/[agentId]/agent-queue.tsx +289 -0
- package/examples/playground/app/agent/compaction-settings.test.ts +22 -6
- package/examples/playground/app/agent/model.ts +11 -2
- package/examples/playground/app/agent/server.ts +8 -2
- package/examples/playground/app/chat/[chatId]/chat-client.tsx +3 -13
- package/examples/playground/app/chat/model.ts +2 -2
- package/examples/playground/app/chat/server.ts +24 -17
- package/examples/playground/app/globals.css +179 -0
- package/examples/playground/package.json +1 -1
- package/package.json +1 -1
- package/src/ai-client-state.ts +185 -0
- package/src/ai-control-server.ts +829 -0
- package/src/ai-control-state.ts +152 -0
- package/src/ai-control.ts +139 -0
- package/src/ai-coordinator.ts +99 -32
- package/src/ai-progress-batches.ts +68 -0
- package/src/ai-projector.ts +76 -15
- package/src/ai-sdk-step.ts +0 -1
- package/src/ai-server.ts +429 -619
- package/src/ai.ts +553 -108
- package/src/client.ts +31 -9
- package/src/licenses/Apache-2.0.txt +55 -0
- package/src/parse-partial-json.ts +441 -0
- package/src/reducer.ts +6 -0
- package/src/server.ts +8 -4
- package/src/store-postgres.ts +27 -0
- package/src/store-redis-core.ts +53 -1
- package/src/store-redis-notify.ts +1 -0
- package/src/store.ts +6 -0
- package/dist/ai.d.ts.map +0 -1
- package/dist/client-Bf6uSEAk.js.map +0 -1
- package/dist/contract-48bUMgcL.js.map +0 -1
- package/dist/server-BeNADlCI.js.map +0 -1
- package/dist/store-redis-notify-BUCyXOn0.js.map +0 -1
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AIControlState,
|
|
3
|
+
ControlCommit,
|
|
4
|
+
ControlChange,
|
|
5
|
+
} from './ai-control.ts'
|
|
6
|
+
import type { UIMessage } from 'ai'
|
|
7
|
+
|
|
8
|
+
type RecordValue = Record<string, unknown> | unknown[]
|
|
9
|
+
const container = (value: unknown): value is RecordValue =>
|
|
10
|
+
typeof value === 'object' && value !== null
|
|
11
|
+
const same = (left: unknown, right: unknown): boolean =>
|
|
12
|
+
JSON.stringify(left) === JSON.stringify(right)
|
|
13
|
+
|
|
14
|
+
export const controlChanges = ({
|
|
15
|
+
before,
|
|
16
|
+
after,
|
|
17
|
+
}: {
|
|
18
|
+
before: unknown
|
|
19
|
+
after: unknown
|
|
20
|
+
}): ControlChange[] => {
|
|
21
|
+
const changes: ControlChange[] = []
|
|
22
|
+
const visit = (
|
|
23
|
+
left: unknown,
|
|
24
|
+
right: unknown,
|
|
25
|
+
path: (string | number)[],
|
|
26
|
+
): void => {
|
|
27
|
+
if (same(left, right)) return
|
|
28
|
+
if (Array.isArray(left) && Array.isArray(right)) {
|
|
29
|
+
let prefix = 0
|
|
30
|
+
while (
|
|
31
|
+
prefix < Math.min(left.length, right.length) &&
|
|
32
|
+
same(left[prefix], right[prefix])
|
|
33
|
+
)
|
|
34
|
+
prefix += 1
|
|
35
|
+
let suffix = 0
|
|
36
|
+
while (
|
|
37
|
+
suffix < Math.min(left.length, right.length) - prefix &&
|
|
38
|
+
same(left[left.length - 1 - suffix], right[right.length - 1 - suffix])
|
|
39
|
+
)
|
|
40
|
+
suffix += 1
|
|
41
|
+
if (left.length === right.length) {
|
|
42
|
+
for (let index = prefix; index < left.length - suffix; index += 1)
|
|
43
|
+
visit(left[index], right[index], [...path, index])
|
|
44
|
+
} else
|
|
45
|
+
changes.push({
|
|
46
|
+
path,
|
|
47
|
+
splice: {
|
|
48
|
+
index: prefix,
|
|
49
|
+
deleteCount: left.length - prefix - suffix,
|
|
50
|
+
items: right.slice(prefix, right.length - suffix),
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
if (
|
|
56
|
+
container(left) &&
|
|
57
|
+
container(right) &&
|
|
58
|
+
!Array.isArray(left) &&
|
|
59
|
+
!Array.isArray(right)
|
|
60
|
+
) {
|
|
61
|
+
for (const key of Object.keys(left))
|
|
62
|
+
if (!Object.hasOwn(right, key))
|
|
63
|
+
changes.push({ path: [...path, key], remove: true })
|
|
64
|
+
for (const key of Object.keys(right))
|
|
65
|
+
visit(Object.hasOwn(left, key) ? left[key] : undefined, right[key], [
|
|
66
|
+
...path,
|
|
67
|
+
key,
|
|
68
|
+
])
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
changes.push({ path, value: right })
|
|
72
|
+
}
|
|
73
|
+
visit(before, after, [])
|
|
74
|
+
return changes
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const applyControlChanges = <T>({
|
|
78
|
+
state,
|
|
79
|
+
changes,
|
|
80
|
+
}: {
|
|
81
|
+
state: T
|
|
82
|
+
changes: readonly ControlChange[]
|
|
83
|
+
}): T => {
|
|
84
|
+
if (changes.length === 0) return state
|
|
85
|
+
let next = structuredClone(state)
|
|
86
|
+
for (const change of changes) {
|
|
87
|
+
const path = change.path
|
|
88
|
+
if (path.length === 0) {
|
|
89
|
+
next = structuredClone(change.value) as T
|
|
90
|
+
continue
|
|
91
|
+
}
|
|
92
|
+
let target: unknown = next
|
|
93
|
+
for (const key of path.slice(0, -1)) {
|
|
94
|
+
if (!container(target) || !Object.hasOwn(target, key))
|
|
95
|
+
throw new TypeError('Invalid AI control change path')
|
|
96
|
+
target = (target as Record<string | number, unknown>)[key]
|
|
97
|
+
}
|
|
98
|
+
if (!container(target))
|
|
99
|
+
throw new TypeError('Invalid AI control change target')
|
|
100
|
+
const parent = target as Record<string | number, unknown>
|
|
101
|
+
const key = path.at(-1)!
|
|
102
|
+
if (change.remove) delete parent[key]
|
|
103
|
+
else if (change.splice) {
|
|
104
|
+
const items = parent[key] as unknown[]
|
|
105
|
+
Object.defineProperty(parent, key, {
|
|
106
|
+
value: [
|
|
107
|
+
...items.slice(0, change.splice.index),
|
|
108
|
+
...structuredClone(change.splice.items),
|
|
109
|
+
...items.slice(change.splice.index + change.splice.deleteCount),
|
|
110
|
+
],
|
|
111
|
+
enumerable: true,
|
|
112
|
+
configurable: true,
|
|
113
|
+
writable: true,
|
|
114
|
+
})
|
|
115
|
+
} else if (change.move) {
|
|
116
|
+
const items = parent[key] as unknown[]
|
|
117
|
+
const [item] = items.splice(change.move.from, 1)
|
|
118
|
+
items.splice(change.move.to, 0, item)
|
|
119
|
+
} else
|
|
120
|
+
Object.defineProperty(parent, key, {
|
|
121
|
+
value: structuredClone(change.value),
|
|
122
|
+
enumerable: true,
|
|
123
|
+
configurable: true,
|
|
124
|
+
writable: true,
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
return next
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export const controlCommit = <M extends UIMessage>(options: {
|
|
131
|
+
before: AIControlState<M>
|
|
132
|
+
after: AIControlState<M>
|
|
133
|
+
moves: ControlChange[]
|
|
134
|
+
}): ControlCommit => {
|
|
135
|
+
const active = options.after.active
|
|
136
|
+
const response = options.after.coordinator.response
|
|
137
|
+
return {
|
|
138
|
+
changes: [
|
|
139
|
+
...options.moves,
|
|
140
|
+
...controlChanges({ before: options.before, after: options.after }),
|
|
141
|
+
],
|
|
142
|
+
view: {
|
|
143
|
+
turnId: active?.turnId ?? null,
|
|
144
|
+
version: active?.version ?? null,
|
|
145
|
+
phase: active?.phase ?? null,
|
|
146
|
+
paused: options.after.inbox.paused,
|
|
147
|
+
closed: options.after.coordinator.closed,
|
|
148
|
+
requestId: response?.activeRequestId ?? null,
|
|
149
|
+
responseMessageId: response?.responseMessageId ?? null,
|
|
150
|
+
},
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { UIMessage } from 'ai'
|
|
2
|
+
import type { AICoordinatorState } from './ai-coordinator.ts'
|
|
3
|
+
import type {
|
|
4
|
+
ApprovalRespondedPayload,
|
|
5
|
+
GenerationStartedPayload,
|
|
6
|
+
InputRequestedPayload,
|
|
7
|
+
InputRespondedPayload,
|
|
8
|
+
MessageInterruptedPayload,
|
|
9
|
+
RetryRequestedPayload,
|
|
10
|
+
ToolCalledPayload,
|
|
11
|
+
ToolResultPayload,
|
|
12
|
+
} from './ai.ts'
|
|
13
|
+
|
|
14
|
+
export type InboxItem<M extends UIMessage = UIMessage> = {
|
|
15
|
+
id: string
|
|
16
|
+
revision: number
|
|
17
|
+
message: M
|
|
18
|
+
generate: boolean
|
|
19
|
+
afterStepOf?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type ControlCommand<M extends UIMessage = UIMessage> =
|
|
23
|
+
| { action: 'send'; message: M; generate?: boolean }
|
|
24
|
+
| { action: 'edit'; message: M; expectedRevision: number }
|
|
25
|
+
| { action: 'move'; inputId: string; beforeId: string | null }
|
|
26
|
+
| { action: 'remove'; inputId: string }
|
|
27
|
+
| {
|
|
28
|
+
action: 'send-now'
|
|
29
|
+
inputId: string
|
|
30
|
+
turnId: string | null
|
|
31
|
+
lastSeenIndex?: number
|
|
32
|
+
}
|
|
33
|
+
| { action: 'stop'; turnId: string; lastSeenIndex?: number }
|
|
34
|
+
| { action: 'steer'; turnId: string; message: M; lastSeenIndex?: number }
|
|
35
|
+
| { action: 'pause'; when: 'now' | 'after-turn' }
|
|
36
|
+
| { action: 'resume' }
|
|
37
|
+
| { action: 'tool-result'; result: ToolResultPayload }
|
|
38
|
+
| { action: 'approval'; response: ApprovalRespondedPayload }
|
|
39
|
+
| { action: 'input'; response: InputRespondedPayload }
|
|
40
|
+
| { action: 'request-input'; request: InputRequestedPayload }
|
|
41
|
+
| { action: 'retry'; request: RetryRequestedPayload }
|
|
42
|
+
| { action: 'interrupt'; request: MessageInterruptedPayload }
|
|
43
|
+
|
|
44
|
+
export type ControlReceipt = {
|
|
45
|
+
commandId: string
|
|
46
|
+
work?: true
|
|
47
|
+
outcome: 'applied' | 'rejected'
|
|
48
|
+
reason?:
|
|
49
|
+
| 'already-active'
|
|
50
|
+
| 'revision-conflict'
|
|
51
|
+
| 'stale-turn'
|
|
52
|
+
| 'not-found'
|
|
53
|
+
| 'duplicate-input'
|
|
54
|
+
| 'closed'
|
|
55
|
+
| 'stale-work'
|
|
56
|
+
| 'not-user-input'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type ActiveTurn<M extends UIMessage = UIMessage> = {
|
|
60
|
+
turnId: string
|
|
61
|
+
input: InboxItem<M>
|
|
62
|
+
phase:
|
|
63
|
+
| 'starting'
|
|
64
|
+
| 'generating'
|
|
65
|
+
| 'tools'
|
|
66
|
+
| 'waiting'
|
|
67
|
+
| 'failed'
|
|
68
|
+
| 'pausing'
|
|
69
|
+
| 'paused'
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type ControlWork = {
|
|
73
|
+
id: string
|
|
74
|
+
version: number
|
|
75
|
+
settled: boolean
|
|
76
|
+
attempt?: number
|
|
77
|
+
facts?: string[]
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export type AIControlState<M extends UIMessage = UIMessage> = {
|
|
81
|
+
reservedIds: Record<string, true | 'removed'>
|
|
82
|
+
inbox: { paused: boolean; items: InboxItem<M>[] }
|
|
83
|
+
active:
|
|
84
|
+
| (ActiveTurn<M> & {
|
|
85
|
+
version: number
|
|
86
|
+
suspended: boolean
|
|
87
|
+
model: ControlWork | null
|
|
88
|
+
})
|
|
89
|
+
| null
|
|
90
|
+
coordinator: AICoordinatorState
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type WorkFact = { type: string; id: string; payload: unknown }
|
|
94
|
+
export type WorkReport = {
|
|
95
|
+
attempt: number
|
|
96
|
+
workId: string
|
|
97
|
+
turnId: string
|
|
98
|
+
version: number
|
|
99
|
+
kind: 'model' | 'tool'
|
|
100
|
+
settled: boolean
|
|
101
|
+
events: WorkFact[]
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export type ToolExecutionRequest = {
|
|
105
|
+
turnId: string
|
|
106
|
+
version: number
|
|
107
|
+
call: ToolCalledPayload
|
|
108
|
+
generation: GenerationStartedPayload
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export const initialControlState = <
|
|
112
|
+
M extends UIMessage,
|
|
113
|
+
>(): AIControlState<M> => ({
|
|
114
|
+
reservedIds: {},
|
|
115
|
+
inbox: { paused: false, items: [] },
|
|
116
|
+
active: null,
|
|
117
|
+
coordinator: { closed: false, queued: [] },
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
export type ControlChange = {
|
|
121
|
+
path: (string | number)[]
|
|
122
|
+
value?: unknown
|
|
123
|
+
remove?: true
|
|
124
|
+
splice?: { index: number; deleteCount: number; items: unknown[] }
|
|
125
|
+
move?: { from: number; to: number }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export type ControlCommit = {
|
|
129
|
+
changes: ControlChange[]
|
|
130
|
+
view: {
|
|
131
|
+
turnId: string | null
|
|
132
|
+
version: number | null
|
|
133
|
+
phase: ActiveTurn['phase'] | null
|
|
134
|
+
paused: boolean
|
|
135
|
+
closed: boolean
|
|
136
|
+
requestId: string | null
|
|
137
|
+
responseMessageId: string | null
|
|
138
|
+
}
|
|
139
|
+
}
|
package/src/ai-coordinator.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { UIMessage } from 'ai'
|
|
2
|
+
import { applyControlChanges } from './ai-control-state.ts'
|
|
3
|
+
import type { ControlCommit, ControlWork } from './ai-control.ts'
|
|
2
4
|
import type {
|
|
3
5
|
AIEventDefs,
|
|
4
6
|
ApprovalRequestedPayload,
|
|
@@ -17,26 +19,32 @@ import type { Contract, ContractEvent, EventDefs } from './contract.ts'
|
|
|
17
19
|
import type { Reducer } from './reducer.ts'
|
|
18
20
|
|
|
19
21
|
export type CoordinatorCall = {
|
|
20
|
-
index: number
|
|
21
22
|
call: ToolCalledPayload
|
|
22
23
|
approval?: ApprovalRequestedPayload
|
|
23
24
|
response?: ApprovalRespondedPayload
|
|
24
|
-
responseIndex?: number
|
|
25
25
|
terminal: boolean
|
|
26
|
+
work?: ControlWork
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
export type CoordinatorResponse = {
|
|
29
30
|
rootMessageId: string
|
|
30
31
|
responseMessageId: string
|
|
31
32
|
status: 'requested' | 'generating' | 'waiting' | 'failed'
|
|
33
|
+
stepCount: number
|
|
32
34
|
activeRequestId?: string
|
|
33
35
|
generation?: GenerationStartedPayload
|
|
36
|
+
promptThroughIndex?: number
|
|
37
|
+
source?: {
|
|
38
|
+
generation: GenerationStartedPayload
|
|
39
|
+
promptThroughIndex: number
|
|
40
|
+
failed: boolean
|
|
41
|
+
canCompact: boolean
|
|
42
|
+
}
|
|
34
43
|
completion?: GenerationCompletedPayload
|
|
35
44
|
failure?: GenerationFailedPayload
|
|
36
45
|
calls: CoordinatorCall[]
|
|
37
46
|
inputs: InputRequestedPayload[]
|
|
38
47
|
inputResponse?: {
|
|
39
|
-
index: number
|
|
40
48
|
generationId: string
|
|
41
49
|
inputId: string
|
|
42
50
|
name: string
|
|
@@ -45,8 +53,9 @@ export type CoordinatorResponse = {
|
|
|
45
53
|
|
|
46
54
|
export type AICoordinatorState = {
|
|
47
55
|
closed: boolean
|
|
48
|
-
queued: Array<{
|
|
56
|
+
queued: Array<{ messageId: string; generate: boolean }>
|
|
49
57
|
response?: CoordinatorResponse
|
|
58
|
+
calibration?: { model: string; inputTokens: number; estimate: number }
|
|
50
59
|
}
|
|
51
60
|
|
|
52
61
|
const updateResponse = (
|
|
@@ -57,13 +66,18 @@ const updateResponse = (
|
|
|
57
66
|
? state
|
|
58
67
|
: { ...state, response: update(state.response) }
|
|
59
68
|
|
|
60
|
-
const foldCoordinator = <M extends UIMessage, D extends EventDefs>(
|
|
69
|
+
export const foldCoordinator = <M extends UIMessage, D extends EventDefs>(
|
|
61
70
|
state: AICoordinatorState,
|
|
62
71
|
event: ContractEvent<D>,
|
|
63
72
|
): AICoordinatorState => {
|
|
64
73
|
switch (event.type) {
|
|
65
74
|
case 'ai.session.closed':
|
|
66
75
|
return { closed: true, queued: [] }
|
|
76
|
+
case 'ai.retry.requested':
|
|
77
|
+
case 'ai.compaction.completed': {
|
|
78
|
+
const { calibration: _, ...rest } = state
|
|
79
|
+
return rest
|
|
80
|
+
}
|
|
67
81
|
case 'ai.message.created': {
|
|
68
82
|
const payload = event.payload as MessageCreatedPayload<M>
|
|
69
83
|
const message = payload.message
|
|
@@ -73,11 +87,10 @@ const foldCoordinator = <M extends UIMessage, D extends EventDefs>(
|
|
|
73
87
|
queued: [
|
|
74
88
|
...state.queued.filter((item) => item.messageId !== message.id),
|
|
75
89
|
{
|
|
76
|
-
index: event.index,
|
|
77
90
|
messageId: message.id,
|
|
78
91
|
generate: payload.generate !== false,
|
|
79
92
|
},
|
|
80
|
-
]
|
|
93
|
+
],
|
|
81
94
|
}
|
|
82
95
|
}
|
|
83
96
|
case 'ai.generation.requested': {
|
|
@@ -85,21 +98,22 @@ const foldCoordinator = <M extends UIMessage, D extends EventDefs>(
|
|
|
85
98
|
if (request.reason === 'message') {
|
|
86
99
|
const responseMessageId =
|
|
87
100
|
request.responseMessageId ?? `${request.messageId}:assistant`
|
|
88
|
-
const requested = state.queued.
|
|
101
|
+
const requested = state.queued.findIndex(
|
|
89
102
|
(item) => item.messageId === request.messageId,
|
|
90
103
|
)
|
|
91
104
|
return {
|
|
92
105
|
...state,
|
|
93
106
|
queued:
|
|
94
|
-
requested
|
|
107
|
+
requested < 0
|
|
95
108
|
? state.queued.filter(
|
|
96
109
|
(item) => item.messageId !== request.messageId,
|
|
97
110
|
)
|
|
98
|
-
: state.queued.
|
|
111
|
+
: state.queued.slice(requested + 1),
|
|
99
112
|
response: {
|
|
100
113
|
rootMessageId: request.messageId,
|
|
101
114
|
responseMessageId,
|
|
102
115
|
status: 'requested',
|
|
116
|
+
stepCount: 0,
|
|
103
117
|
activeRequestId: event.id,
|
|
104
118
|
calls: [],
|
|
105
119
|
inputs: [],
|
|
@@ -123,14 +137,31 @@ const foldCoordinator = <M extends UIMessage, D extends EventDefs>(
|
|
|
123
137
|
) {
|
|
124
138
|
return state
|
|
125
139
|
}
|
|
126
|
-
return updateResponse(state, (response) =>
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
140
|
+
return updateResponse(state, (response) => {
|
|
141
|
+
const generation = response.generation ?? response.source?.generation
|
|
142
|
+
const promptThroughIndex =
|
|
143
|
+
response.promptThroughIndex ?? response.source?.promptThroughIndex
|
|
144
|
+
return {
|
|
145
|
+
rootMessageId: response.rootMessageId,
|
|
146
|
+
responseMessageId: response.responseMessageId,
|
|
147
|
+
status: 'requested',
|
|
148
|
+
stepCount: response.stepCount,
|
|
149
|
+
activeRequestId: event.id,
|
|
150
|
+
...(generation === undefined || promptThroughIndex === undefined
|
|
151
|
+
? {}
|
|
152
|
+
: {
|
|
153
|
+
source: {
|
|
154
|
+
generation,
|
|
155
|
+
promptThroughIndex,
|
|
156
|
+
failed:
|
|
157
|
+
response.failure?.generationId === generation.generationId,
|
|
158
|
+
canCompact: response.calls.every((call) => call.terminal),
|
|
159
|
+
},
|
|
160
|
+
}),
|
|
161
|
+
calls: [],
|
|
162
|
+
inputs: [],
|
|
163
|
+
}
|
|
164
|
+
})
|
|
134
165
|
}
|
|
135
166
|
case 'ai.generation.started': {
|
|
136
167
|
const generation = event.payload as GenerationStartedPayload
|
|
@@ -146,7 +177,12 @@ const foldCoordinator = <M extends UIMessage, D extends EventDefs>(
|
|
|
146
177
|
rootMessageId: response.rootMessageId,
|
|
147
178
|
responseMessageId: response.responseMessageId,
|
|
148
179
|
status: 'generating',
|
|
180
|
+
stepCount: response.stepCount,
|
|
149
181
|
generation,
|
|
182
|
+
...(generation.promptThroughIndex === undefined
|
|
183
|
+
? {}
|
|
184
|
+
: { promptThroughIndex: generation.promptThroughIndex }),
|
|
185
|
+
...(response.source === undefined ? {} : { source: response.source }),
|
|
150
186
|
activeRequestId: generation.requestId,
|
|
151
187
|
calls: [],
|
|
152
188
|
inputs: [],
|
|
@@ -159,11 +195,29 @@ const foldCoordinator = <M extends UIMessage, D extends EventDefs>(
|
|
|
159
195
|
) {
|
|
160
196
|
return state
|
|
161
197
|
}
|
|
162
|
-
|
|
198
|
+
const next = updateResponse(state, (response) => ({
|
|
163
199
|
...response,
|
|
164
200
|
status: 'waiting',
|
|
165
201
|
completion,
|
|
202
|
+
stepCount:
|
|
203
|
+
response.stepCount + (response.completion === undefined ? 1 : 0),
|
|
166
204
|
}))
|
|
205
|
+
const { calibration: _, ...uncalibrated } = next
|
|
206
|
+
const inputTokens = completion.usage?.inputTokens
|
|
207
|
+
const estimate = completion.inputTokenEstimate
|
|
208
|
+
return inputTokens !== undefined &&
|
|
209
|
+
Number.isFinite(inputTokens) &&
|
|
210
|
+
inputTokens >= 0 &&
|
|
211
|
+
estimate !== undefined
|
|
212
|
+
? {
|
|
213
|
+
...uncalibrated,
|
|
214
|
+
calibration: {
|
|
215
|
+
model: state.response.generation.model,
|
|
216
|
+
inputTokens,
|
|
217
|
+
estimate,
|
|
218
|
+
},
|
|
219
|
+
}
|
|
220
|
+
: uncalibrated
|
|
167
221
|
}
|
|
168
222
|
case 'ai.generation.failed': {
|
|
169
223
|
const failure = event.payload as GenerationFailedPayload
|
|
@@ -186,18 +240,21 @@ const foldCoordinator = <M extends UIMessage, D extends EventDefs>(
|
|
|
186
240
|
}
|
|
187
241
|
case 'ai.message.completed': {
|
|
188
242
|
const messageId = (event.payload as { messageId: string }).messageId
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
243
|
+
if (state.response?.responseMessageId !== messageId) return state
|
|
244
|
+
const { response: _, ...rest } = state
|
|
245
|
+
return rest
|
|
192
246
|
}
|
|
193
247
|
case 'ai.message.interrupted': {
|
|
194
248
|
const interruption = event.payload as MessageInterruptedPayload
|
|
195
249
|
const response = state.response
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
250
|
+
if (
|
|
251
|
+
response?.responseMessageId !== interruption.messageId ||
|
|
252
|
+
(interruption.generationId !== response.generation?.generationId &&
|
|
253
|
+
interruption.requestId !== response.activeRequestId)
|
|
254
|
+
)
|
|
255
|
+
return state
|
|
256
|
+
const { response: _, ...rest } = state
|
|
257
|
+
return rest
|
|
201
258
|
}
|
|
202
259
|
case 'ai.tool.called': {
|
|
203
260
|
const call = event.payload as ToolCalledPayload
|
|
@@ -210,7 +267,7 @@ const foldCoordinator = <M extends UIMessage, D extends EventDefs>(
|
|
|
210
267
|
...response.calls.filter(
|
|
211
268
|
(candidate) => candidate.call.toolCallId !== call.toolCallId,
|
|
212
269
|
),
|
|
213
|
-
{
|
|
270
|
+
{ call, terminal: false },
|
|
214
271
|
],
|
|
215
272
|
}))
|
|
216
273
|
}
|
|
@@ -240,7 +297,7 @@ const foldCoordinator = <M extends UIMessage, D extends EventDefs>(
|
|
|
240
297
|
...response,
|
|
241
298
|
calls: response.calls.map((candidate) =>
|
|
242
299
|
candidate.approval?.approvalId === approval.approvalId
|
|
243
|
-
? { ...candidate, response: approval
|
|
300
|
+
? { ...candidate, response: approval }
|
|
244
301
|
: candidate,
|
|
245
302
|
),
|
|
246
303
|
}))
|
|
@@ -310,7 +367,6 @@ const foldCoordinator = <M extends UIMessage, D extends EventDefs>(
|
|
|
310
367
|
(candidate) => candidate.inputId !== input.inputId,
|
|
311
368
|
),
|
|
312
369
|
inputResponse: {
|
|
313
|
-
index: event.index,
|
|
314
370
|
generationId: input.generationId,
|
|
315
371
|
inputId: input.inputId,
|
|
316
372
|
name: requested.name,
|
|
@@ -330,13 +386,24 @@ export const aiCoordinatorReducer = <
|
|
|
330
386
|
): Reducer<D, AICoordinatorState> =>
|
|
331
387
|
contract
|
|
332
388
|
.reducer({
|
|
333
|
-
name: 'a2.ai.coordinator.
|
|
389
|
+
name: 'a2.ai.coordinator.v5',
|
|
334
390
|
initialState: {
|
|
335
391
|
closed: false,
|
|
336
392
|
queued: [],
|
|
337
393
|
} as AICoordinatorState,
|
|
338
394
|
})
|
|
339
|
-
.fold((state, event) =>
|
|
395
|
+
.fold((state, event) =>
|
|
396
|
+
event.type === 'ai.control.committed'
|
|
397
|
+
? applyControlChanges({
|
|
398
|
+
state,
|
|
399
|
+
changes: (event.payload as ControlCommit).changes
|
|
400
|
+
.filter((change) => change.path[0] === 'coordinator')
|
|
401
|
+
.map((change) =>
|
|
402
|
+
Object.assign({}, change, { path: change.path.slice(1) }),
|
|
403
|
+
),
|
|
404
|
+
})
|
|
405
|
+
: state,
|
|
406
|
+
)
|
|
340
407
|
|
|
341
408
|
export const continuationReady = (state: AICoordinatorState): boolean => {
|
|
342
409
|
const response = state.response
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { UIMessageChunk } from 'ai'
|
|
2
|
+
|
|
3
|
+
export type ProgressBatch = { index: number; chunks: UIMessageChunk[] }
|
|
4
|
+
type BatchBlock = ProgressBatch[] | { left: BatchBlock; right: BatchBlock }
|
|
5
|
+
|
|
6
|
+
export type ProgressBatches = {
|
|
7
|
+
length: number
|
|
8
|
+
tail: ProgressBatch[]
|
|
9
|
+
blocks: Array<BatchBlock | null>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const PAGE_SIZE = 64
|
|
13
|
+
|
|
14
|
+
export const appendProgressBatch = ({
|
|
15
|
+
batches,
|
|
16
|
+
batch,
|
|
17
|
+
}: {
|
|
18
|
+
batches: ProgressBatches
|
|
19
|
+
batch: ProgressBatch
|
|
20
|
+
}): ProgressBatches => {
|
|
21
|
+
if (batches.tail.length < PAGE_SIZE)
|
|
22
|
+
return {
|
|
23
|
+
...batches,
|
|
24
|
+
length: batches.length + 1,
|
|
25
|
+
tail: [...batches.tail, batch],
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const blocks = [...batches.blocks]
|
|
29
|
+
let block: BatchBlock = batches.tail
|
|
30
|
+
let level = 0
|
|
31
|
+
for (;;) {
|
|
32
|
+
const left = blocks[level]
|
|
33
|
+
if (left == null) break
|
|
34
|
+
block = { left, right: block }
|
|
35
|
+
blocks[level] = null
|
|
36
|
+
level += 1
|
|
37
|
+
}
|
|
38
|
+
blocks[level] = block
|
|
39
|
+
return { length: batches.length + 1, tail: [batch], blocks }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const progressBatches = (
|
|
43
|
+
items: readonly ProgressBatch[] = [],
|
|
44
|
+
): ProgressBatches => {
|
|
45
|
+
let batches: ProgressBatches = { length: 0, tail: [], blocks: [] }
|
|
46
|
+
for (const batch of items) batches = appendProgressBatch({ batches, batch })
|
|
47
|
+
return batches
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const flattenProgressBatches = (
|
|
51
|
+
batches: ProgressBatches,
|
|
52
|
+
): ProgressBatch[] => {
|
|
53
|
+
const items: ProgressBatch[] = []
|
|
54
|
+
const visit = (block: BatchBlock): void => {
|
|
55
|
+
if (Array.isArray(block)) items.push(...block)
|
|
56
|
+
else {
|
|
57
|
+
visit(block.left)
|
|
58
|
+
visit(block.right)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Level n holds 2^n completed pages; higher levels contain older batches.
|
|
62
|
+
for (let level = batches.blocks.length - 1; level >= 0; level -= 1) {
|
|
63
|
+
const block = batches.blocks[level]
|
|
64
|
+
if (block) visit(block)
|
|
65
|
+
}
|
|
66
|
+
items.push(...batches.tail)
|
|
67
|
+
return items
|
|
68
|
+
}
|