experimental-a2 0.12.0 → 0.14.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 +47 -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 +16 -8
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +1322 -475
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.d.ts +2 -334
- package/dist/ai.js +838 -85
- 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 +361 -76
- package/docs/reference/01-api.mdx +159 -27
- package/examples/playground/app/agent/[agentId]/agent-client.tsx +145 -33
- package/examples/playground/app/agent/[agentId]/agent-queue.tsx +289 -0
- package/examples/playground/app/agent/[agentId]/compaction/route.ts +14 -0
- package/examples/playground/app/agent/[agentId]/compaction-event.tsx +38 -0
- package/examples/playground/app/agent/[agentId]/compaction-panel.tsx +294 -0
- package/examples/playground/app/agent/compaction-settings.test.ts +144 -0
- package/examples/playground/app/agent/compaction-settings.ts +49 -0
- package/examples/playground/app/agent/compaction-timeline.test.ts +337 -0
- package/examples/playground/app/agent/compaction-timeline.ts +198 -0
- package/examples/playground/app/agent/model.ts +56 -1
- package/examples/playground/app/agent/server.ts +9 -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 +333 -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-model-metadata.ts +108 -0
- package/src/ai-progress-batches.ts +68 -0
- package/src/ai-projector.ts +76 -15
- package/src/ai-sdk-step.ts +5 -2
- package/src/ai-server.ts +920 -638
- package/src/ai.ts +650 -110
- 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
package/dist/ai-server.js
CHANGED
|
@@ -1,90 +1,10 @@
|
|
|
1
|
+
import { n as validateSync } from "./validate-XKT4FSNn.js";
|
|
1
2
|
import { t as A2Error } from "./errors-DCk6ch5n.js";
|
|
2
3
|
import { f as consumeSchedulerSendFailure, p as installAmbientToolScopeStorage } from "./internal-Dq2qYxou.js";
|
|
3
|
-
import { i as setServerFetchHooks, t as createServer } from "./server-
|
|
4
|
+
import { i as setServerFetchHooks, t as createServer } from "./server-CjJSGcF7.js";
|
|
5
|
+
import { n as applyControlChanges, r as controlCommit, t as initialControlState } from "./ai-control-CcD4hh3y.js";
|
|
4
6
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
5
|
-
import { convertToModelMessages, stepCountIs, streamText, toUIMessageStream } from "ai";
|
|
6
|
-
//#region src/ai-sdk-step.ts
|
|
7
|
-
const CONTROLLED_SETTINGS = [
|
|
8
|
-
"_internal",
|
|
9
|
-
"abortSignal",
|
|
10
|
-
"instructions",
|
|
11
|
-
"messages",
|
|
12
|
-
"model",
|
|
13
|
-
"onToolExecutionEnd",
|
|
14
|
-
"onToolExecutionStart",
|
|
15
|
-
"experimental_onToolCallFinish",
|
|
16
|
-
"experimental_onToolCallStart",
|
|
17
|
-
"experimental_sandbox",
|
|
18
|
-
"experimental_toolCallers",
|
|
19
|
-
"experimental_toolApprovalSecret",
|
|
20
|
-
"prompt",
|
|
21
|
-
"prepareStep",
|
|
22
|
-
"stopWhen",
|
|
23
|
-
"system",
|
|
24
|
-
"tools"
|
|
25
|
-
];
|
|
26
|
-
const safeSettings = (settings) => {
|
|
27
|
-
const result = { ...settings };
|
|
28
|
-
for (const key of CONTROLLED_SETTINGS) Reflect.deleteProperty(result, key);
|
|
29
|
-
if (typeof result.timeout === "object" && result.timeout !== null) {
|
|
30
|
-
const timeout = { ...result.timeout };
|
|
31
|
-
Reflect.deleteProperty(timeout, "toolMs");
|
|
32
|
-
Reflect.deleteProperty(timeout, "tools");
|
|
33
|
-
result.timeout = timeout;
|
|
34
|
-
}
|
|
35
|
-
return result;
|
|
36
|
-
};
|
|
37
|
-
const modelToolSet = (tools) => Object.fromEntries(Object.entries(tools).map(([name, tool]) => {
|
|
38
|
-
if (tool.type === "provider" && tool.isProviderExecuted === true) return [name, tool];
|
|
39
|
-
const definition = { ...tool };
|
|
40
|
-
Reflect.deleteProperty(definition, "execute");
|
|
41
|
-
return [name, definition];
|
|
42
|
-
}));
|
|
43
|
-
const errorMessage$1 = (error) => error instanceof Error ? error.message : String(error);
|
|
44
|
-
/** Run exactly one AI SDK model step while leaving local tool execution to A2. */
|
|
45
|
-
async function generateAISDKStep(input) {
|
|
46
|
-
const settings = safeSettings(input.settings);
|
|
47
|
-
const tools = modelToolSet(input.tools);
|
|
48
|
-
const messages = await convertToModelMessages(input.messages, { tools });
|
|
49
|
-
const streamOptions = {
|
|
50
|
-
...settings,
|
|
51
|
-
model: input.model,
|
|
52
|
-
tools,
|
|
53
|
-
messages,
|
|
54
|
-
abortSignal: input.abortSignal,
|
|
55
|
-
...input.instructions === void 0 ? {} : { instructions: input.instructions },
|
|
56
|
-
stopWhen: stepCountIs(1),
|
|
57
|
-
onError: settings.onError ?? (() => {})
|
|
58
|
-
};
|
|
59
|
-
const result = streamText(streamOptions);
|
|
60
|
-
let completion;
|
|
61
|
-
let fatalError;
|
|
62
|
-
const observed = result.stream.pipeThrough(new TransformStream({ transform(part, controller) {
|
|
63
|
-
if (part.type === "error" && fatalError === void 0) fatalError = { value: part.error };
|
|
64
|
-
else if (part.type === "finish") completion = {
|
|
65
|
-
finishReason: part.finishReason,
|
|
66
|
-
usage: part.totalUsage
|
|
67
|
-
};
|
|
68
|
-
controller.enqueue(part);
|
|
69
|
-
} }));
|
|
70
|
-
return {
|
|
71
|
-
stream: toUIMessageStream({
|
|
72
|
-
stream: observed,
|
|
73
|
-
tools,
|
|
74
|
-
originalMessages: input.messages,
|
|
75
|
-
generateMessageId: () => input.responseMessageId,
|
|
76
|
-
onError: errorMessage$1,
|
|
77
|
-
...input.messageMetadata === void 0 ? {} : { messageMetadata: input.messageMetadata }
|
|
78
|
-
}),
|
|
79
|
-
completion() {
|
|
80
|
-
if (fatalError !== void 0) throw fatalError.value;
|
|
81
|
-
if (completion === void 0) throw new Error("AI SDK step ended without a finish part");
|
|
82
|
-
if (completion.finishReason === "error") throw new Error("AI SDK step finished with an error");
|
|
83
|
-
return completion;
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
//#endregion
|
|
7
|
+
import { asSchema, convertToModelMessages, stepCountIs, streamText, toUIMessageStream } from "ai";
|
|
88
8
|
//#region src/ai-coordinator.ts
|
|
89
9
|
const updateResponse = (state, update) => state.response === void 0 ? state : {
|
|
90
10
|
...state,
|
|
@@ -96,6 +16,11 @@ const foldCoordinator = (state, event) => {
|
|
|
96
16
|
closed: true,
|
|
97
17
|
queued: []
|
|
98
18
|
};
|
|
19
|
+
case "ai.retry.requested":
|
|
20
|
+
case "ai.compaction.completed": {
|
|
21
|
+
const { calibration: _, ...rest } = state;
|
|
22
|
+
return rest;
|
|
23
|
+
}
|
|
99
24
|
case "ai.message.created": {
|
|
100
25
|
const payload = event.payload;
|
|
101
26
|
const message = payload.message;
|
|
@@ -103,24 +28,24 @@ const foldCoordinator = (state, event) => {
|
|
|
103
28
|
return {
|
|
104
29
|
...state,
|
|
105
30
|
queued: [...state.queued.filter((item) => item.messageId !== message.id), {
|
|
106
|
-
index: event.index,
|
|
107
31
|
messageId: message.id,
|
|
108
32
|
generate: payload.generate !== false
|
|
109
|
-
}]
|
|
33
|
+
}]
|
|
110
34
|
};
|
|
111
35
|
}
|
|
112
36
|
case "ai.generation.requested": {
|
|
113
37
|
const request = event.payload;
|
|
114
38
|
if (request.reason === "message") {
|
|
115
39
|
const responseMessageId = request.responseMessageId ?? `${request.messageId}:assistant`;
|
|
116
|
-
const requested = state.queued.
|
|
40
|
+
const requested = state.queued.findIndex((item) => item.messageId === request.messageId);
|
|
117
41
|
return {
|
|
118
42
|
...state,
|
|
119
|
-
queued: requested
|
|
43
|
+
queued: requested < 0 ? state.queued.filter((item) => item.messageId !== request.messageId) : state.queued.slice(requested + 1),
|
|
120
44
|
response: {
|
|
121
45
|
rootMessageId: request.messageId,
|
|
122
46
|
responseMessageId,
|
|
123
47
|
status: "requested",
|
|
48
|
+
stepCount: 0,
|
|
124
49
|
activeRequestId: event.id,
|
|
125
50
|
calls: [],
|
|
126
51
|
inputs: []
|
|
@@ -129,14 +54,25 @@ const foldCoordinator = (state, event) => {
|
|
|
129
54
|
}
|
|
130
55
|
if (state.response === void 0 || request.responseMessageId !== state.response.responseMessageId) return state;
|
|
131
56
|
if (request.reason === "tool" && (!continuationReady(state) || event.id !== `ai.generate:tools:${state.response.generation?.generationId}`) || request.reason === "retry" && state.response.status !== "failed" || request.reason === "input" && state.response.inputResponse === void 0) return state;
|
|
132
|
-
return updateResponse(state, (response) =>
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
57
|
+
return updateResponse(state, (response) => {
|
|
58
|
+
const generation = response.generation ?? response.source?.generation;
|
|
59
|
+
const promptThroughIndex = response.promptThroughIndex ?? response.source?.promptThroughIndex;
|
|
60
|
+
return {
|
|
61
|
+
rootMessageId: response.rootMessageId,
|
|
62
|
+
responseMessageId: response.responseMessageId,
|
|
63
|
+
status: "requested",
|
|
64
|
+
stepCount: response.stepCount,
|
|
65
|
+
activeRequestId: event.id,
|
|
66
|
+
...generation === void 0 || promptThroughIndex === void 0 ? {} : { source: {
|
|
67
|
+
generation,
|
|
68
|
+
promptThroughIndex,
|
|
69
|
+
failed: response.failure?.generationId === generation.generationId,
|
|
70
|
+
canCompact: response.calls.every((call) => call.terminal)
|
|
71
|
+
} },
|
|
72
|
+
calls: [],
|
|
73
|
+
inputs: []
|
|
74
|
+
};
|
|
75
|
+
});
|
|
140
76
|
}
|
|
141
77
|
case "ai.generation.started": {
|
|
142
78
|
const generation = event.payload;
|
|
@@ -145,7 +81,10 @@ const foldCoordinator = (state, event) => {
|
|
|
145
81
|
rootMessageId: response.rootMessageId,
|
|
146
82
|
responseMessageId: response.responseMessageId,
|
|
147
83
|
status: "generating",
|
|
84
|
+
stepCount: response.stepCount,
|
|
148
85
|
generation,
|
|
86
|
+
...generation.promptThroughIndex === void 0 ? {} : { promptThroughIndex: generation.promptThroughIndex },
|
|
87
|
+
...response.source === void 0 ? {} : { source: response.source },
|
|
149
88
|
activeRequestId: generation.requestId,
|
|
150
89
|
calls: [],
|
|
151
90
|
inputs: []
|
|
@@ -154,11 +93,22 @@ const foldCoordinator = (state, event) => {
|
|
|
154
93
|
case "ai.generation.completed": {
|
|
155
94
|
const completion = event.payload;
|
|
156
95
|
if (state.response?.generation?.generationId !== completion.generationId) return state;
|
|
157
|
-
|
|
96
|
+
const { calibration: _, ...uncalibrated } = updateResponse(state, (response) => ({
|
|
158
97
|
...response,
|
|
159
98
|
status: "waiting",
|
|
160
|
-
completion
|
|
99
|
+
completion,
|
|
100
|
+
stepCount: response.stepCount + (response.completion === void 0 ? 1 : 0)
|
|
161
101
|
}));
|
|
102
|
+
const inputTokens = completion.usage?.inputTokens;
|
|
103
|
+
const estimate = completion.inputTokenEstimate;
|
|
104
|
+
return inputTokens !== void 0 && Number.isFinite(inputTokens) && inputTokens >= 0 && estimate !== void 0 ? {
|
|
105
|
+
...uncalibrated,
|
|
106
|
+
calibration: {
|
|
107
|
+
model: state.response.generation.model,
|
|
108
|
+
inputTokens,
|
|
109
|
+
estimate
|
|
110
|
+
}
|
|
111
|
+
} : uncalibrated;
|
|
162
112
|
}
|
|
163
113
|
case "ai.generation.failed": {
|
|
164
114
|
const failure = event.payload;
|
|
@@ -174,18 +124,16 @@ const foldCoordinator = (state, event) => {
|
|
|
174
124
|
}
|
|
175
125
|
case "ai.message.completed": {
|
|
176
126
|
const messageId = event.payload.messageId;
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
} : state;
|
|
127
|
+
if (state.response?.responseMessageId !== messageId) return state;
|
|
128
|
+
const { response: _, ...rest } = state;
|
|
129
|
+
return rest;
|
|
181
130
|
}
|
|
182
131
|
case "ai.message.interrupted": {
|
|
183
132
|
const interruption = event.payload;
|
|
184
133
|
const response = state.response;
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
} : state;
|
|
134
|
+
if (response?.responseMessageId !== interruption.messageId || interruption.generationId !== response.generation?.generationId && interruption.requestId !== response.activeRequestId) return state;
|
|
135
|
+
const { response: _, ...rest } = state;
|
|
136
|
+
return rest;
|
|
189
137
|
}
|
|
190
138
|
case "ai.tool.called": {
|
|
191
139
|
const call = event.payload;
|
|
@@ -193,7 +141,6 @@ const foldCoordinator = (state, event) => {
|
|
|
193
141
|
return updateResponse(state, (response) => ({
|
|
194
142
|
...response,
|
|
195
143
|
calls: [...response.calls.filter((candidate) => candidate.call.toolCallId !== call.toolCallId), {
|
|
196
|
-
index: event.index,
|
|
197
144
|
call,
|
|
198
145
|
terminal: false
|
|
199
146
|
}]
|
|
@@ -217,8 +164,7 @@ const foldCoordinator = (state, event) => {
|
|
|
217
164
|
...response,
|
|
218
165
|
calls: response.calls.map((candidate) => candidate.approval?.approvalId === approval.approvalId ? {
|
|
219
166
|
...candidate,
|
|
220
|
-
response: approval
|
|
221
|
-
responseIndex: event.index
|
|
167
|
+
response: approval
|
|
222
168
|
} : candidate)
|
|
223
169
|
}));
|
|
224
170
|
}
|
|
@@ -249,7 +195,6 @@ const foldCoordinator = (state, event) => {
|
|
|
249
195
|
...response,
|
|
250
196
|
inputs: response.inputs.filter((candidate) => candidate.inputId !== input.inputId),
|
|
251
197
|
inputResponse: {
|
|
252
|
-
index: event.index,
|
|
253
198
|
generationId: input.generationId,
|
|
254
199
|
inputId: input.inputId,
|
|
255
200
|
name: requested.name
|
|
@@ -260,17 +205,685 @@ const foldCoordinator = (state, event) => {
|
|
|
260
205
|
}
|
|
261
206
|
};
|
|
262
207
|
const aiCoordinatorReducer = (contract) => contract.reducer({
|
|
263
|
-
name: "a2.ai.coordinator.
|
|
208
|
+
name: "a2.ai.coordinator.v5",
|
|
264
209
|
initialState: {
|
|
265
210
|
closed: false,
|
|
266
211
|
queued: []
|
|
267
212
|
}
|
|
268
|
-
}).fold((state, event) =>
|
|
213
|
+
}).fold((state, event) => event.type === "ai.control.committed" ? applyControlChanges({
|
|
214
|
+
state,
|
|
215
|
+
changes: event.payload.changes.filter((change) => change.path[0] === "coordinator").map((change) => Object.assign({}, change, { path: change.path.slice(1) }))
|
|
216
|
+
}) : state);
|
|
269
217
|
const continuationReady = (state) => {
|
|
270
218
|
const response = state.response;
|
|
271
219
|
return !state.closed && response?.completion?.finishReason === "tool-calls" && response.generation?.generationId === response.completion.generationId && response.failure === void 0 && response.calls.length > 0 && response.calls.every((call) => call.terminal || call.call.providerExecuted === true && call.call.supportsDeferredResults !== true && call.approval !== void 0 && call.response !== void 0);
|
|
272
220
|
};
|
|
273
221
|
//#endregion
|
|
222
|
+
//#region src/ai-control-server.ts
|
|
223
|
+
var ControlCancelled = class extends Error {};
|
|
224
|
+
const callReadyForModel = (call) => call.terminal || call.call.providerExecuted === true && !call.call.supportsDeferredResults && call.response !== void 0;
|
|
225
|
+
const approvalBlocksExecution = (call) => call.approval !== void 0 && call.response?.approved !== true;
|
|
226
|
+
const createControlRuntime = (options) => {
|
|
227
|
+
const reducer = options.agent.contract.reducer({
|
|
228
|
+
name: "a2.ai.control.v1",
|
|
229
|
+
initialState: initialControlState()
|
|
230
|
+
}).fold((state, event) => event.type === "ai.control.committed" ? applyControlChanges({
|
|
231
|
+
state,
|
|
232
|
+
changes: event.payload.changes
|
|
233
|
+
}) : state);
|
|
234
|
+
const coordinator = aiCoordinatorReducer(options.agent.contract);
|
|
235
|
+
const handler = async (ctx) => {
|
|
236
|
+
const snapshot = await ctx.session.state(reducer, { through: "latest" });
|
|
237
|
+
const before = structuredClone(snapshot.state);
|
|
238
|
+
const moves = [];
|
|
239
|
+
const state = structuredClone(snapshot.state);
|
|
240
|
+
const facts = [];
|
|
241
|
+
const jobs = [];
|
|
242
|
+
const emit = ({ type, payload, id = `${ctx.event.id}:fact:${facts.length}` }) => {
|
|
243
|
+
const fact = {
|
|
244
|
+
type,
|
|
245
|
+
payload,
|
|
246
|
+
id
|
|
247
|
+
};
|
|
248
|
+
facts.push(fact);
|
|
249
|
+
state.coordinator = foldCoordinator(state.coordinator, {
|
|
250
|
+
...ctx.event,
|
|
251
|
+
...fact
|
|
252
|
+
});
|
|
253
|
+
};
|
|
254
|
+
let reason;
|
|
255
|
+
const end = ({ lastSeenIndex, reason: interruptionReason }) => {
|
|
256
|
+
const response = state.coordinator.response;
|
|
257
|
+
if (response === void 0) return;
|
|
258
|
+
const owner = response.generation === void 0 ? { requestId: response.activeRequestId } : { generationId: response.generation.generationId };
|
|
259
|
+
emit({
|
|
260
|
+
type: "ai.message.interrupted",
|
|
261
|
+
payload: {
|
|
262
|
+
messageId: response.responseMessageId,
|
|
263
|
+
...owner,
|
|
264
|
+
...interruptionReason === void 0 ? {} : { reason: interruptionReason },
|
|
265
|
+
...lastSeenIndex === void 0 ? {} : { lastSeenIndex }
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
state.active = null;
|
|
269
|
+
};
|
|
270
|
+
const knownInput = (id) => state.inbox.items.some((candidate) => candidate.id === id) || Object.hasOwn(state.reservedIds, id);
|
|
271
|
+
const missingInputReason = (id) => state.reservedIds[id] === "removed" || !knownInput(id) ? "not-found" : "already-active";
|
|
272
|
+
const inputTaken = ({ message, generate = true }) => knownInput(message.id) || generate && message.role === "user" && knownInput(`${message.id}:assistant`);
|
|
273
|
+
const insert = ({ message, generate = true, first = false, afterStepOf }) => {
|
|
274
|
+
const candidate = {
|
|
275
|
+
id: message.id,
|
|
276
|
+
revision: 0,
|
|
277
|
+
message,
|
|
278
|
+
generate: generate && message.role === "user",
|
|
279
|
+
...afterStepOf === void 0 ? {} : { afterStepOf }
|
|
280
|
+
};
|
|
281
|
+
state.reservedIds = {
|
|
282
|
+
...state.reservedIds,
|
|
283
|
+
[candidate.id]: true,
|
|
284
|
+
...candidate.generate ? { [`${candidate.id}:assistant`]: true } : {}
|
|
285
|
+
};
|
|
286
|
+
if (first) state.inbox.items.unshift(candidate);
|
|
287
|
+
else state.inbox.items.push(candidate);
|
|
288
|
+
};
|
|
289
|
+
const moveInput = ({ from, to }) => {
|
|
290
|
+
if (from === to) return;
|
|
291
|
+
const [moved] = state.inbox.items.splice(from, 1);
|
|
292
|
+
state.inbox.items.splice(to, 0, moved);
|
|
293
|
+
const [previous] = before.inbox.items.splice(from, 1);
|
|
294
|
+
before.inbox.items.splice(to, 0, previous);
|
|
295
|
+
moves.push({
|
|
296
|
+
path: ["inbox", "items"],
|
|
297
|
+
move: {
|
|
298
|
+
from,
|
|
299
|
+
to
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
};
|
|
303
|
+
const requestModel = ({ request, id }) => {
|
|
304
|
+
const active = state.active;
|
|
305
|
+
const payload = {
|
|
306
|
+
...request,
|
|
307
|
+
control: {
|
|
308
|
+
turnId: active.turnId,
|
|
309
|
+
version: active.version
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
state.coordinator = foldCoordinator(state.coordinator, {
|
|
313
|
+
...ctx.event,
|
|
314
|
+
type: "ai.generation.requested",
|
|
315
|
+
id,
|
|
316
|
+
payload
|
|
317
|
+
});
|
|
318
|
+
active.model = {
|
|
319
|
+
id,
|
|
320
|
+
version: active.version,
|
|
321
|
+
settled: false
|
|
322
|
+
};
|
|
323
|
+
active.phase = "starting";
|
|
324
|
+
jobs.push({
|
|
325
|
+
type: "ai.generation.requested",
|
|
326
|
+
id,
|
|
327
|
+
payload
|
|
328
|
+
});
|
|
329
|
+
};
|
|
330
|
+
if (ctx.event.type === "ai.session.closed") {
|
|
331
|
+
state.coordinator = {
|
|
332
|
+
closed: true,
|
|
333
|
+
queued: []
|
|
334
|
+
};
|
|
335
|
+
state.active = null;
|
|
336
|
+
state.inbox.items = [];
|
|
337
|
+
} else if (ctx.event.type === "ai.control.requested") {
|
|
338
|
+
const command = ctx.event.payload;
|
|
339
|
+
if (state.coordinator.closed) reason = "closed";
|
|
340
|
+
else switch (command.action) {
|
|
341
|
+
case "send":
|
|
342
|
+
if (inputTaken({
|
|
343
|
+
message: command.message,
|
|
344
|
+
generate: command.generate !== false
|
|
345
|
+
})) reason = "duplicate-input";
|
|
346
|
+
else insert({
|
|
347
|
+
message: command.message,
|
|
348
|
+
generate: command.generate !== false
|
|
349
|
+
});
|
|
350
|
+
break;
|
|
351
|
+
case "edit": {
|
|
352
|
+
const item = state.inbox.items.find((candidate) => candidate.id === command.message.id);
|
|
353
|
+
if (item === void 0) reason = missingInputReason(command.message.id);
|
|
354
|
+
else if (item.revision !== command.expectedRevision) reason = "revision-conflict";
|
|
355
|
+
else if (item.message.role !== command.message.role) reason = "revision-conflict";
|
|
356
|
+
else {
|
|
357
|
+
item.message = command.message;
|
|
358
|
+
item.revision += 1;
|
|
359
|
+
}
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
case "remove": {
|
|
363
|
+
const position = state.inbox.items.findIndex((item) => item.id === command.inputId);
|
|
364
|
+
if (position < 0) reason = missingInputReason(command.inputId);
|
|
365
|
+
else {
|
|
366
|
+
state.inbox.items.splice(position, 1);
|
|
367
|
+
state.reservedIds = {
|
|
368
|
+
...state.reservedIds,
|
|
369
|
+
[command.inputId]: "removed"
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
case "move": {
|
|
375
|
+
const index = state.inbox.items.findIndex((candidate) => candidate.id === command.inputId);
|
|
376
|
+
if (index < 0) reason = missingInputReason(command.inputId);
|
|
377
|
+
else if (command.beforeId !== null && !state.inbox.items.some((candidate) => candidate.id === command.beforeId)) reason = "not-found";
|
|
378
|
+
else {
|
|
379
|
+
const destination = command.beforeId === null ? state.inbox.items.length : state.inbox.items.findIndex((item) => item.id === command.beforeId);
|
|
380
|
+
moveInput({
|
|
381
|
+
from: index,
|
|
382
|
+
to: destination > index ? destination - 1 : destination
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
case "send-now": {
|
|
388
|
+
const position = state.inbox.items.findIndex((item) => item.id === command.inputId);
|
|
389
|
+
const selected = state.inbox.items[position];
|
|
390
|
+
if (!selected) reason = missingInputReason(command.inputId);
|
|
391
|
+
else if ((state.active?.turnId ?? null) !== command.turnId) reason = "stale-turn";
|
|
392
|
+
else if (selected.message.role !== "user") reason = "not-user-input";
|
|
393
|
+
else if (!selected.generate && knownInput(`${selected.id}:assistant`)) reason = "duplicate-input";
|
|
394
|
+
else {
|
|
395
|
+
if (!selected.generate) {
|
|
396
|
+
state.reservedIds = {
|
|
397
|
+
...state.reservedIds,
|
|
398
|
+
[`${selected.id}:assistant`]: true
|
|
399
|
+
};
|
|
400
|
+
selected.generate = true;
|
|
401
|
+
}
|
|
402
|
+
if (state.active) selected.afterStepOf = state.active.turnId;
|
|
403
|
+
else delete selected.afterStepOf;
|
|
404
|
+
moveInput({
|
|
405
|
+
from: position,
|
|
406
|
+
to: 0
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
break;
|
|
410
|
+
}
|
|
411
|
+
case "stop":
|
|
412
|
+
if (state.active?.turnId !== command.turnId) reason = "stale-turn";
|
|
413
|
+
else end({
|
|
414
|
+
lastSeenIndex: command.lastSeenIndex,
|
|
415
|
+
reason: void 0
|
|
416
|
+
});
|
|
417
|
+
break;
|
|
418
|
+
case "steer":
|
|
419
|
+
if (inputTaken({ message: command.message })) reason = "duplicate-input";
|
|
420
|
+
else insert({
|
|
421
|
+
message: command.message,
|
|
422
|
+
generate: true,
|
|
423
|
+
first: true,
|
|
424
|
+
afterStepOf: command.turnId
|
|
425
|
+
});
|
|
426
|
+
break;
|
|
427
|
+
case "interrupt": {
|
|
428
|
+
const response = state.coordinator.response;
|
|
429
|
+
if (!response || response.responseMessageId !== command.request.messageId || command.request.generationId !== response.generation?.generationId && command.request.requestId !== response.activeRequestId) reason = "stale-turn";
|
|
430
|
+
else end({
|
|
431
|
+
lastSeenIndex: command.request.lastSeenIndex,
|
|
432
|
+
reason: command.request.reason
|
|
433
|
+
});
|
|
434
|
+
break;
|
|
435
|
+
}
|
|
436
|
+
case "pause": {
|
|
437
|
+
state.inbox.paused = true;
|
|
438
|
+
const active = state.active;
|
|
439
|
+
if (command.when === "now" && active !== null && !active.suspended) {
|
|
440
|
+
active.suspended = true;
|
|
441
|
+
active.version += 1;
|
|
442
|
+
const generation = state.coordinator.response?.generation;
|
|
443
|
+
if (generation) emit({
|
|
444
|
+
type: "ai.generation.paused",
|
|
445
|
+
payload: {
|
|
446
|
+
generationId: generation.generationId,
|
|
447
|
+
messageId: generation.responseMessageId
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
case "resume":
|
|
454
|
+
state.inbox.paused = false;
|
|
455
|
+
break;
|
|
456
|
+
case "tool-result":
|
|
457
|
+
if (!state.coordinator.response?.calls.find((candidate) => candidate.call.generationId === command.result.generationId && candidate.call.messageId === command.result.messageId && candidate.call.toolCallId === command.result.toolCallId && candidate.call.providerExecuted === true && !candidate.terminal)) reason = "not-found";
|
|
458
|
+
else emit({
|
|
459
|
+
type: "ai.tool.result",
|
|
460
|
+
payload: command.result
|
|
461
|
+
});
|
|
462
|
+
break;
|
|
463
|
+
case "approval": {
|
|
464
|
+
const response = state.coordinator.response;
|
|
465
|
+
const call = response?.calls.find((candidate) => candidate.approval?.approvalId === command.response.approvalId && candidate.approval.generationId === command.response.generationId && candidate.approval.messageId === command.response.messageId);
|
|
466
|
+
if (response?.failure) reason = "stale-turn";
|
|
467
|
+
else if (!call || call.response || call.terminal) reason = "not-found";
|
|
468
|
+
else emit({
|
|
469
|
+
type: "ai.approval.responded",
|
|
470
|
+
payload: command.response
|
|
471
|
+
});
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
case "request-input":
|
|
475
|
+
case "input": {
|
|
476
|
+
const payload = command.action === "input" ? command.response : command.request;
|
|
477
|
+
const response = state.coordinator.response;
|
|
478
|
+
if (response?.failure !== void 0 || response?.generation?.generationId !== payload.generationId || response.responseMessageId !== payload.messageId || command.action === "input" && !response.inputs.some((input) => input.inputId === payload.inputId && input.name === payload.name)) reason = "not-found";
|
|
479
|
+
else emit({
|
|
480
|
+
type: command.action === "input" ? "ai.input.responded" : "ai.input.requested",
|
|
481
|
+
payload
|
|
482
|
+
});
|
|
483
|
+
break;
|
|
484
|
+
}
|
|
485
|
+
case "retry": {
|
|
486
|
+
const response = state.coordinator.response;
|
|
487
|
+
if (!state.active || response?.status !== "failed" || response.rootMessageId !== command.request.messageId || response.responseMessageId !== command.request.responseMessageId) reason = "stale-turn";
|
|
488
|
+
else if (!state.inbox.paused && !state.active.suspended) {
|
|
489
|
+
emit({
|
|
490
|
+
type: "ai.retry.requested",
|
|
491
|
+
payload: command.request
|
|
492
|
+
});
|
|
493
|
+
requestModel({
|
|
494
|
+
request: {
|
|
495
|
+
messageId: response.rootMessageId,
|
|
496
|
+
responseMessageId: response.responseMessageId,
|
|
497
|
+
reason: "retry"
|
|
498
|
+
},
|
|
499
|
+
id: `ai.generate:retry:${command.request.retryId}`
|
|
500
|
+
});
|
|
501
|
+
} else reason = "stale-turn";
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
} else {
|
|
506
|
+
const report = ctx.event.payload;
|
|
507
|
+
const active = state.active;
|
|
508
|
+
const call = state.coordinator.response?.calls.find((candidate) => candidate.work?.id === report.workId);
|
|
509
|
+
const work = report.kind === "model" ? active?.model : call?.work;
|
|
510
|
+
if (!(active?.turnId === report.turnId && work?.id === report.workId && work.version === report.version && !work.settled && report.attempt >= (work.attempt ?? 0))) reason = "stale-work";
|
|
511
|
+
else {
|
|
512
|
+
work.attempt = report.attempt;
|
|
513
|
+
const authorized = !active.suspended && active.version === report.version;
|
|
514
|
+
for (const event of report.events) {
|
|
515
|
+
if (work.facts?.includes(event.id)) continue;
|
|
516
|
+
if (authorized || report.kind === "tool" && event.type === "ai.tool.result") {
|
|
517
|
+
emit({
|
|
518
|
+
type: event.type,
|
|
519
|
+
payload: event.payload,
|
|
520
|
+
id: event.id
|
|
521
|
+
});
|
|
522
|
+
work.facts = [...work.facts ?? [], event.id];
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
if (report.settled) work.settled = true;
|
|
526
|
+
if (!authorized) reason = "stale-work";
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
let active = state.active;
|
|
530
|
+
let response = state.coordinator.response;
|
|
531
|
+
if (active && response === void 0) {
|
|
532
|
+
state.active = null;
|
|
533
|
+
active = null;
|
|
534
|
+
}
|
|
535
|
+
if (active && response) {
|
|
536
|
+
const handoff = state.inbox.items.some((item) => item.afterStepOf === active.turnId);
|
|
537
|
+
const outstanding = active.model !== null && !active.model.settled || response.calls.some((candidate) => candidate.work !== void 0 && !candidate.work.settled);
|
|
538
|
+
if (active.suspended) {
|
|
539
|
+
active.phase = outstanding ? "pausing" : "paused";
|
|
540
|
+
if (!state.inbox.paused && !outstanding && !handoff) active.suspended = false;
|
|
541
|
+
}
|
|
542
|
+
if (!active.suspended && response.failure) active.phase = "failed";
|
|
543
|
+
if (!active.suspended && response.failure === void 0) for (const call of response.calls) {
|
|
544
|
+
if (call.terminal || call.work !== void 0 && (!call.work.settled || call.work.version === active.version) || call.call.providerExecuted === true || call.approval && !call.response) continue;
|
|
545
|
+
if (call.response?.approved === false) {
|
|
546
|
+
emit({
|
|
547
|
+
type: "ai.tool.result",
|
|
548
|
+
payload: {
|
|
549
|
+
...call.call,
|
|
550
|
+
denied: true,
|
|
551
|
+
phase: "execution"
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
const id = `${ctx.event.id}:tool:${call.call.toolCallId}`;
|
|
557
|
+
call.work = {
|
|
558
|
+
id,
|
|
559
|
+
version: active.version,
|
|
560
|
+
settled: false
|
|
561
|
+
};
|
|
562
|
+
jobs.push({
|
|
563
|
+
type: "ai.tool.execution.requested",
|
|
564
|
+
id,
|
|
565
|
+
payload: {
|
|
566
|
+
turnId: active.turnId,
|
|
567
|
+
version: active.version,
|
|
568
|
+
call: call.call,
|
|
569
|
+
generation: response.generation
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
response = state.coordinator.response;
|
|
574
|
+
const callsDone = response.calls.every(callReadyForModel);
|
|
575
|
+
const modelDone = active.model === null || active.model.settled;
|
|
576
|
+
const workSettled = modelDone && response.calls.every((call) => call.work === void 0 || call.work.settled);
|
|
577
|
+
const externalPending = response.calls.some((call) => call.call.providerExecuted && call.call.supportsDeferredResults && !call.terminal && !approvalBlocksExecution(call));
|
|
578
|
+
const handoffReady = handoff && workSettled && !externalPending && (active.suspended || response.failure !== void 0 || response.calls.every((call) => callReadyForModel(call) || approvalBlocksExecution(call)));
|
|
579
|
+
const readyForModel = !active.suspended && response.failure === void 0 && modelDone && callsDone && response.inputs.length === 0;
|
|
580
|
+
const naturalCompletion = readyForModel && response.completion !== void 0 && response.completion.finishReason !== "tool-calls";
|
|
581
|
+
if (handoffReady && !naturalCompletion) end({
|
|
582
|
+
lastSeenIndex: void 0,
|
|
583
|
+
reason: "steered after step"
|
|
584
|
+
});
|
|
585
|
+
else if (readyForModel && (!handoff || handoffReady)) {
|
|
586
|
+
const resume = active.model !== null && active.model.version !== active.version;
|
|
587
|
+
const input = response.inputResponse;
|
|
588
|
+
if (!handoff && (resume || continuationReady(state.coordinator) || response.completion && input)) {
|
|
589
|
+
const nextReason = resume ? "resume" : input ? "input" : "tool";
|
|
590
|
+
const id = nextReason === "tool" ? `ai.generate:tools:${response.generation.generationId}` : `${ctx.event.id}:generate:${nextReason}`;
|
|
591
|
+
requestModel({
|
|
592
|
+
request: {
|
|
593
|
+
messageId: response.responseMessageId,
|
|
594
|
+
responseMessageId: response.responseMessageId,
|
|
595
|
+
reason: nextReason
|
|
596
|
+
},
|
|
597
|
+
id
|
|
598
|
+
});
|
|
599
|
+
} else if (naturalCompletion) {
|
|
600
|
+
emit({
|
|
601
|
+
type: "ai.message.completed",
|
|
602
|
+
payload: { messageId: response.responseMessageId }
|
|
603
|
+
});
|
|
604
|
+
state.active = null;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
if (!active.suspended && state.active && jobs.every((job) => job.type !== "ai.generation.requested")) state.active.phase = response.failure ? "failed" : !modelDone ? "generating" : !callsDone ? "tools" : "waiting";
|
|
608
|
+
}
|
|
609
|
+
if (state.active === null && !state.inbox.paused && !state.coordinator.closed) while (state.inbox.items.length > 0) {
|
|
610
|
+
const item = state.inbox.items.shift();
|
|
611
|
+
delete item.afterStepOf;
|
|
612
|
+
emit({
|
|
613
|
+
type: "ai.message.created",
|
|
614
|
+
payload: {
|
|
615
|
+
message: item.message,
|
|
616
|
+
generate: item.generate
|
|
617
|
+
},
|
|
618
|
+
id: `ai.admitted:${item.id}`
|
|
619
|
+
});
|
|
620
|
+
if (!item.generate) continue;
|
|
621
|
+
state.active = {
|
|
622
|
+
turnId: item.id,
|
|
623
|
+
input: item,
|
|
624
|
+
phase: "starting",
|
|
625
|
+
version: 0,
|
|
626
|
+
suspended: false,
|
|
627
|
+
model: null
|
|
628
|
+
};
|
|
629
|
+
requestModel({
|
|
630
|
+
request: {
|
|
631
|
+
messageId: item.id,
|
|
632
|
+
reason: "message"
|
|
633
|
+
},
|
|
634
|
+
id: `ai.generate:message:${item.id}`
|
|
635
|
+
});
|
|
636
|
+
break;
|
|
637
|
+
}
|
|
638
|
+
const receipt = {
|
|
639
|
+
commandId: ctx.event.id,
|
|
640
|
+
...ctx.event.type === "ai.work.reported" ? { work: true } : {},
|
|
641
|
+
outcome: reason === void 0 ? "applied" : "rejected",
|
|
642
|
+
...reason === void 0 ? {} : { reason }
|
|
643
|
+
};
|
|
644
|
+
return [
|
|
645
|
+
...facts,
|
|
646
|
+
{
|
|
647
|
+
type: "ai.control.committed",
|
|
648
|
+
payload: controlCommit({
|
|
649
|
+
before,
|
|
650
|
+
after: state,
|
|
651
|
+
moves
|
|
652
|
+
})
|
|
653
|
+
},
|
|
654
|
+
...jobs,
|
|
655
|
+
{
|
|
656
|
+
type: "ai.control.decided",
|
|
657
|
+
payload: receipt
|
|
658
|
+
}
|
|
659
|
+
];
|
|
660
|
+
};
|
|
661
|
+
const report = ({ ctx, events, settled, name }) => {
|
|
662
|
+
const payload = ctx.event.payload;
|
|
663
|
+
const { turnId, version } = "turnId" in payload ? payload : payload.control;
|
|
664
|
+
for (const event of events) {
|
|
665
|
+
const schema = options.agent.contract.events[event.type];
|
|
666
|
+
const result = validateSync(schema, event.payload, `AI worker ${event.type}`);
|
|
667
|
+
if (result.issues) throw new TypeError(result.issues.map((issue) => issue.message).join("; "));
|
|
668
|
+
}
|
|
669
|
+
return {
|
|
670
|
+
type: "ai.work.reported",
|
|
671
|
+
id: `${ctx.event.id}:report:${ctx.attempt}:${name}`,
|
|
672
|
+
payload: {
|
|
673
|
+
workId: ctx.event.id,
|
|
674
|
+
attempt: ctx.attempt,
|
|
675
|
+
turnId,
|
|
676
|
+
version,
|
|
677
|
+
kind: ctx.event.type === "ai.generation.requested" ? "model" : "tool",
|
|
678
|
+
settled,
|
|
679
|
+
events: events.filter((event) => event.type !== "ai.message.completed" && event.type !== "ai.message.interrupted").map((event, index) => ({
|
|
680
|
+
type: event.type,
|
|
681
|
+
payload: event.payload,
|
|
682
|
+
id: event.id ?? `${ctx.event.id}:fact:${ctx.attempt}:${name}:${index}`
|
|
683
|
+
}))
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
};
|
|
687
|
+
const append = async ({ ctx, name, events }) => {
|
|
688
|
+
const progress = events.filter((event) => event.type === "ai.generation.progress");
|
|
689
|
+
const lifecycle = events.filter((event) => event.type !== "ai.generation.progress");
|
|
690
|
+
if (lifecycle.length === 0) return ctx.session.append(name, ...progress);
|
|
691
|
+
const input = report({
|
|
692
|
+
ctx,
|
|
693
|
+
events: lifecycle,
|
|
694
|
+
settled: false,
|
|
695
|
+
name
|
|
696
|
+
});
|
|
697
|
+
const persisted = await ctx.session.append(name, ...progress, input);
|
|
698
|
+
const index = persisted.at(-1).index;
|
|
699
|
+
const stream = ctx.session.stream({ startAfter: index })[Symbol.asyncIterator]();
|
|
700
|
+
const canonical = [];
|
|
701
|
+
const wanted = new Set(lifecycle.map((event) => event.id));
|
|
702
|
+
let rejectAbort;
|
|
703
|
+
const aborted = new Promise((_, reject) => {
|
|
704
|
+
rejectAbort = reject;
|
|
705
|
+
});
|
|
706
|
+
const onAbort = () => {
|
|
707
|
+
rejectAbort(ctx.signal.reason);
|
|
708
|
+
};
|
|
709
|
+
ctx.signal.addEventListener("abort", onAbort, { once: true });
|
|
710
|
+
try {
|
|
711
|
+
ctx.signal.throwIfAborted();
|
|
712
|
+
for (;;) {
|
|
713
|
+
const item = await Promise.race([stream.next(), aborted]);
|
|
714
|
+
if (item.done) throw new Error("AI control acknowledgement stream ended");
|
|
715
|
+
if (wanted.has(item.value.id)) canonical.push(item.value);
|
|
716
|
+
if (item.value.type === "ai.control.decided" && item.value.payload.commandId === input.id) {
|
|
717
|
+
if (item.value.payload.outcome === "rejected") throw new ControlCancelled("stale AI work");
|
|
718
|
+
return [...persisted.filter((event) => event.type !== "ai.work.reported"), ...canonical];
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
} finally {
|
|
722
|
+
ctx.signal.removeEventListener("abort", onAbort);
|
|
723
|
+
await stream.return?.();
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
const settled = ({ ctx, events }) => report({
|
|
727
|
+
ctx,
|
|
728
|
+
events: events === void 0 ? [] : Array.isArray(events) ? events : [events],
|
|
729
|
+
settled: true,
|
|
730
|
+
name: "settled"
|
|
731
|
+
});
|
|
732
|
+
const cancelled = ({ error, signal }) => {
|
|
733
|
+
if (signal.reason instanceof A2Error) throw signal.reason;
|
|
734
|
+
return error instanceof ControlCancelled || signal.aborted;
|
|
735
|
+
};
|
|
736
|
+
return {
|
|
737
|
+
handler,
|
|
738
|
+
append,
|
|
739
|
+
settled,
|
|
740
|
+
cancelled,
|
|
741
|
+
reducer,
|
|
742
|
+
coordinator
|
|
743
|
+
};
|
|
744
|
+
};
|
|
745
|
+
//#endregion
|
|
746
|
+
//#region src/ai-sdk-step.ts
|
|
747
|
+
const CONTROLLED_SETTINGS = [
|
|
748
|
+
"_internal",
|
|
749
|
+
"abortSignal",
|
|
750
|
+
"instructions",
|
|
751
|
+
"messages",
|
|
752
|
+
"model",
|
|
753
|
+
"onToolExecutionEnd",
|
|
754
|
+
"onToolExecutionStart",
|
|
755
|
+
"experimental_onToolCallFinish",
|
|
756
|
+
"experimental_onToolCallStart",
|
|
757
|
+
"experimental_sandbox",
|
|
758
|
+
"experimental_toolCallers",
|
|
759
|
+
"experimental_toolApprovalSecret",
|
|
760
|
+
"prompt",
|
|
761
|
+
"prepareStep",
|
|
762
|
+
"stopWhen",
|
|
763
|
+
"system",
|
|
764
|
+
"tools"
|
|
765
|
+
];
|
|
766
|
+
const safeSettings = (settings) => {
|
|
767
|
+
const result = { ...settings };
|
|
768
|
+
for (const key of CONTROLLED_SETTINGS) Reflect.deleteProperty(result, key);
|
|
769
|
+
if (typeof result.timeout === "object" && result.timeout !== null) {
|
|
770
|
+
const timeout = { ...result.timeout };
|
|
771
|
+
Reflect.deleteProperty(timeout, "toolMs");
|
|
772
|
+
Reflect.deleteProperty(timeout, "tools");
|
|
773
|
+
result.timeout = timeout;
|
|
774
|
+
}
|
|
775
|
+
return result;
|
|
776
|
+
};
|
|
777
|
+
const modelToolSet = (tools) => Object.fromEntries(Object.entries(tools).map(([name, tool]) => {
|
|
778
|
+
if (tool.type === "provider" && tool.isProviderExecuted === true) return [name, tool];
|
|
779
|
+
const definition = { ...tool };
|
|
780
|
+
Reflect.deleteProperty(definition, "execute");
|
|
781
|
+
return [name, definition];
|
|
782
|
+
}));
|
|
783
|
+
const errorMessage$1 = (error) => error instanceof Error ? error.message : String(error);
|
|
784
|
+
/** Run exactly one AI SDK model step while leaving local tool execution to A2. */
|
|
785
|
+
async function generateAISDKStep(input) {
|
|
786
|
+
const settings = safeSettings(input.settings);
|
|
787
|
+
const tools = modelToolSet(input.tools);
|
|
788
|
+
const messages = input.modelMessages ?? await convertToModelMessages(input.messages, { tools });
|
|
789
|
+
const streamOptions = {
|
|
790
|
+
...settings,
|
|
791
|
+
model: input.model,
|
|
792
|
+
tools,
|
|
793
|
+
messages,
|
|
794
|
+
abortSignal: input.abortSignal,
|
|
795
|
+
...input.instructions === void 0 ? {} : { instructions: input.instructions },
|
|
796
|
+
stopWhen: stepCountIs(1),
|
|
797
|
+
onError: settings.onError ?? (() => {})
|
|
798
|
+
};
|
|
799
|
+
const result = streamText(streamOptions);
|
|
800
|
+
let completion;
|
|
801
|
+
let fatalError;
|
|
802
|
+
const observed = result.stream.pipeThrough(new TransformStream({ transform(part, controller) {
|
|
803
|
+
if (part.type === "error" && fatalError === void 0) fatalError = { value: part.error };
|
|
804
|
+
else if (part.type === "finish") completion = {
|
|
805
|
+
finishReason: part.finishReason,
|
|
806
|
+
usage: part.totalUsage
|
|
807
|
+
};
|
|
808
|
+
controller.enqueue(part);
|
|
809
|
+
} }));
|
|
810
|
+
return {
|
|
811
|
+
stream: toUIMessageStream({
|
|
812
|
+
stream: observed,
|
|
813
|
+
tools,
|
|
814
|
+
generateMessageId: () => input.responseMessageId,
|
|
815
|
+
onError: errorMessage$1,
|
|
816
|
+
...input.messageMetadata === void 0 ? {} : { messageMetadata: input.messageMetadata }
|
|
817
|
+
}),
|
|
818
|
+
completion() {
|
|
819
|
+
if (fatalError !== void 0) throw fatalError.value;
|
|
820
|
+
if (completion === void 0) throw new Error("AI SDK step ended without a finish part");
|
|
821
|
+
if (completion.finishReason === "error") throw new Error("AI SDK step finished with an error");
|
|
822
|
+
return completion;
|
|
823
|
+
}
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
//#endregion
|
|
827
|
+
//#region src/ai-model-metadata.ts
|
|
828
|
+
const catalogUrl = "https://ai-gateway.vercel.sh/v1/models";
|
|
829
|
+
const catalogLifetimeMs = 36e5;
|
|
830
|
+
const catalogTimeoutMs = 5e3;
|
|
831
|
+
let catalog;
|
|
832
|
+
const gatewayModelId = (model) => {
|
|
833
|
+
if (typeof model === "string") return globalThis.AI_SDK_DEFAULT_PROVIDER === void 0 ? model : void 0;
|
|
834
|
+
return model.provider === "gateway" ? model.modelId : void 0;
|
|
835
|
+
};
|
|
836
|
+
const readCatalog = () => {
|
|
837
|
+
if (catalog && catalog.expiresAt > Date.now()) return catalog.promise;
|
|
838
|
+
const entry = {
|
|
839
|
+
expiresAt: Number.POSITIVE_INFINITY,
|
|
840
|
+
promise: (async () => {
|
|
841
|
+
const controller = new AbortController();
|
|
842
|
+
const timeout = setTimeout(() => controller.abort(/* @__PURE__ */ new Error("model catalog request timed out")), catalogTimeoutMs);
|
|
843
|
+
timeout.unref?.();
|
|
844
|
+
try {
|
|
845
|
+
const response = await fetch(catalogUrl, { signal: controller.signal });
|
|
846
|
+
if (!response.ok) throw new Error(`model catalog request failed: HTTP ${response.status}`);
|
|
847
|
+
const body = await response.json();
|
|
848
|
+
if (typeof body !== "object" || body === null || !("data" in body) || !Array.isArray(body.data)) throw new Error("model catalog response must contain a data array");
|
|
849
|
+
const models = /* @__PURE__ */ new Map();
|
|
850
|
+
for (const row of body.data) {
|
|
851
|
+
if (typeof row !== "object" || row === null || !("type" in row) || !("id" in row) || !("context_window" in row) || !("max_tokens" in row) || row.type !== "language" || typeof row.id !== "string" || typeof row.context_window !== "number" || typeof row.max_tokens !== "number" || !Number.isSafeInteger(row.context_window) || row.context_window <= 0 || !Number.isSafeInteger(row.max_tokens) || row.max_tokens <= 0) continue;
|
|
852
|
+
models.set(row.id, {
|
|
853
|
+
contextWindow: row.context_window,
|
|
854
|
+
maxOutputTokens: row.max_tokens
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
return models;
|
|
858
|
+
} finally {
|
|
859
|
+
clearTimeout(timeout);
|
|
860
|
+
}
|
|
861
|
+
})()
|
|
862
|
+
};
|
|
863
|
+
catalog = entry;
|
|
864
|
+
entry.promise = entry.promise.then((models) => {
|
|
865
|
+
entry.expiresAt = Date.now() + catalogLifetimeMs;
|
|
866
|
+
return models;
|
|
867
|
+
}, (error) => {
|
|
868
|
+
if (catalog === entry) catalog = void 0;
|
|
869
|
+
throw error;
|
|
870
|
+
});
|
|
871
|
+
return entry.promise;
|
|
872
|
+
};
|
|
873
|
+
const readModelLimits = async (options) => {
|
|
874
|
+
options.signal.throwIfAborted();
|
|
875
|
+
let onAbort;
|
|
876
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
877
|
+
onAbort = () => reject(options.signal.reason);
|
|
878
|
+
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
879
|
+
});
|
|
880
|
+
try {
|
|
881
|
+
return (await Promise.race([readCatalog(), aborted])).get(options.modelId) ?? null;
|
|
882
|
+
} finally {
|
|
883
|
+
options.signal.removeEventListener("abort", onAbort);
|
|
884
|
+
}
|
|
885
|
+
};
|
|
886
|
+
//#endregion
|
|
274
887
|
//#region src/ai-server.ts
|
|
275
888
|
/**
|
|
276
889
|
* experimental-a2/ai/server — the server-only implementation of an experimental-a2/ai definition.
|
|
@@ -280,13 +893,31 @@ const continuationReady = (state) => {
|
|
|
280
893
|
*/
|
|
281
894
|
function validateAgentIngress(context) {
|
|
282
895
|
const rejected = context.events.find((event) => {
|
|
283
|
-
if (event.type !== "ai.
|
|
284
|
-
const
|
|
285
|
-
|
|
896
|
+
if (event.type !== "ai.control.requested") return true;
|
|
897
|
+
const command = event.payload;
|
|
898
|
+
if (command["action"] === "request-input" || command["action"] === "tool-result") return true;
|
|
899
|
+
if (command["action"] === "send" || command["action"] === "edit" || command["action"] === "steer") return command["message"]?.role !== "user";
|
|
900
|
+
return false;
|
|
286
901
|
});
|
|
287
902
|
if (rejected !== void 0) throw new A2Error("INVALID_PAYLOAD", `event '${rejected.type}' is server-only for an AI agent`);
|
|
288
903
|
}
|
|
289
|
-
const
|
|
904
|
+
const checkAbort = (signal) => {
|
|
905
|
+
if (!signal.aborted) return false;
|
|
906
|
+
const reason = signal.reason;
|
|
907
|
+
if (reason instanceof A2Error && (reason.code === "CLAIM_EXPIRED" || reason.code === "SUPERSEDED_ATTEMPT")) throw reason;
|
|
908
|
+
return true;
|
|
909
|
+
};
|
|
910
|
+
const resolve = async (value, context) => {
|
|
911
|
+
let result;
|
|
912
|
+
try {
|
|
913
|
+
result = typeof value === "function" ? await value(context) : value;
|
|
914
|
+
} catch (error) {
|
|
915
|
+
checkAbort(context.signal);
|
|
916
|
+
throw error;
|
|
917
|
+
}
|
|
918
|
+
checkAbort(context.signal);
|
|
919
|
+
return result;
|
|
920
|
+
};
|
|
290
921
|
const modelName = (model) => {
|
|
291
922
|
if (typeof model === "string") return model;
|
|
292
923
|
if ("modelId" in model && typeof model.modelId === "string") return model.modelId;
|
|
@@ -325,6 +956,7 @@ async function generateWithAISDK(context, messageMetadata) {
|
|
|
325
956
|
model: context.model,
|
|
326
957
|
tools: context.tools,
|
|
327
958
|
messages: context.messages,
|
|
959
|
+
modelMessages: context.modelMessages,
|
|
328
960
|
responseMessageId: context.responseMessageId,
|
|
329
961
|
abortSignal: context.signal,
|
|
330
962
|
...context.instructions === void 0 ? {} : { instructions: context.instructions },
|
|
@@ -346,8 +978,8 @@ async function* consumeGeneration(options) {
|
|
|
346
978
|
const maxChunks = options.progress?.maxChunks ?? 16;
|
|
347
979
|
const maxDelayMs = options.progress?.maxDelayMs ?? 30;
|
|
348
980
|
let lastFlush = Date.now();
|
|
349
|
-
const
|
|
350
|
-
let pendingChunk =
|
|
981
|
+
const reader = options.source.stream.getReader();
|
|
982
|
+
let pendingChunk = reader.read();
|
|
351
983
|
try {
|
|
352
984
|
for (;;) {
|
|
353
985
|
let result;
|
|
@@ -369,7 +1001,7 @@ async function* consumeGeneration(options) {
|
|
|
369
1001
|
pending.push(chunk);
|
|
370
1002
|
if (chunk.type === "finish") streamedFinishReason = chunk.finishReason;
|
|
371
1003
|
if (chunk.type === "error") streamedError = chunk.errorText;
|
|
372
|
-
pendingChunk =
|
|
1004
|
+
pendingChunk = reader.read();
|
|
373
1005
|
if (pending.length >= maxChunks || isBoundaryChunk(pending.at(-1))) {
|
|
374
1006
|
yield {
|
|
375
1007
|
type: "progress",
|
|
@@ -384,6 +1016,13 @@ async function* consumeGeneration(options) {
|
|
|
384
1016
|
chunks: pending.splice(0)
|
|
385
1017
|
};
|
|
386
1018
|
throw error;
|
|
1019
|
+
} finally {
|
|
1020
|
+
pendingChunk.catch(() => {});
|
|
1021
|
+
try {
|
|
1022
|
+
await reader.cancel();
|
|
1023
|
+
} catch {} finally {
|
|
1024
|
+
reader.releaseLock();
|
|
1025
|
+
}
|
|
387
1026
|
}
|
|
388
1027
|
let completion;
|
|
389
1028
|
try {
|
|
@@ -407,29 +1046,141 @@ async function* consumeGeneration(options) {
|
|
|
407
1046
|
...completion?.usage === void 0 ? {} : { usage: completion.usage }
|
|
408
1047
|
};
|
|
409
1048
|
}
|
|
410
|
-
const
|
|
411
|
-
let state =
|
|
412
|
-
for (const event of
|
|
1049
|
+
const foldAIEvents = (options) => {
|
|
1050
|
+
let state = options.state;
|
|
1051
|
+
for (const event of options.events) state = options.agent.reducer.fold(state, event);
|
|
413
1052
|
return state;
|
|
414
1053
|
};
|
|
415
|
-
const
|
|
1054
|
+
const summarize = async (options) => {
|
|
1055
|
+
const instruction = [
|
|
1056
|
+
"Summarize this conversation so you can continue the task after the earlier context is replaced by your summary.",
|
|
1057
|
+
"Preserve the objective, constraints, decisions, exact identifiers, completed actions and their results, unresolved questions, and next steps. Distinguish observations from assumptions.",
|
|
1058
|
+
"Respond only with the summary as text. Do not call any tools or continue working on the task.",
|
|
1059
|
+
options.instructions
|
|
1060
|
+
].filter(Boolean).join("\n");
|
|
1061
|
+
const generation = { ...options.context.generation };
|
|
1062
|
+
for (const key of Object.keys(generation)) if (key.startsWith("on") || key.startsWith("experimental_on") || [
|
|
1063
|
+
"repairToolCall",
|
|
1064
|
+
"experimental_repairToolCall",
|
|
1065
|
+
"experimental_refineToolInput",
|
|
1066
|
+
"experimental_transform",
|
|
1067
|
+
"toolApproval"
|
|
1068
|
+
].includes(key)) Reflect.deleteProperty(generation, key);
|
|
1069
|
+
const tools = Object.fromEntries(Object.entries(options.context.tools).map(([name, tool]) => {
|
|
1070
|
+
const definition = {
|
|
1071
|
+
...tool,
|
|
1072
|
+
needsApproval: false
|
|
1073
|
+
};
|
|
1074
|
+
for (const key of [
|
|
1075
|
+
"execute",
|
|
1076
|
+
"onInputStart",
|
|
1077
|
+
"onInputDelta",
|
|
1078
|
+
"onInputAvailable"
|
|
1079
|
+
]) Reflect.deleteProperty(definition, key);
|
|
1080
|
+
return [name, definition];
|
|
1081
|
+
}));
|
|
1082
|
+
const source = await generateWithAISDK({
|
|
1083
|
+
...options.context,
|
|
1084
|
+
generation,
|
|
1085
|
+
tools,
|
|
1086
|
+
modelMessages: [...options.context.modelMessages, {
|
|
1087
|
+
role: "user",
|
|
1088
|
+
content: instruction
|
|
1089
|
+
}],
|
|
1090
|
+
responseMessageId: `${options.context.generationId}:summary`
|
|
1091
|
+
}, void 0);
|
|
1092
|
+
let summary = "";
|
|
1093
|
+
let calledTool = false;
|
|
1094
|
+
let finish;
|
|
1095
|
+
for await (const update of consumeGeneration({ source })) {
|
|
1096
|
+
options.context.signal.throwIfAborted();
|
|
1097
|
+
if (update.type === "finish") {
|
|
1098
|
+
finish = update;
|
|
1099
|
+
continue;
|
|
1100
|
+
}
|
|
1101
|
+
for (const chunk of update.chunks) {
|
|
1102
|
+
if (chunk.type === "text-delta") summary += chunk.delta;
|
|
1103
|
+
if (chunk.type.startsWith("tool-")) calledTool = true;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
if (calledTool || summary.trim().length === 0 || finish?.finishReason !== "stop") throw new Error("compaction must produce a complete text summary without calling tools");
|
|
1107
|
+
return {
|
|
1108
|
+
summary,
|
|
1109
|
+
...finish.usage === void 0 ? {} : { usage: finish.usage }
|
|
1110
|
+
};
|
|
1111
|
+
};
|
|
1112
|
+
const activeCompaction = (state) => {
|
|
416
1113
|
const compaction = state.compaction;
|
|
417
|
-
|
|
418
|
-
|
|
1114
|
+
return compaction?.status === "completed" && compaction.messages !== void 0 && state.messages.some((message) => message.id === compaction.throughMessageId) ? compaction : null;
|
|
1115
|
+
};
|
|
1116
|
+
const contextMessages = async (options) => {
|
|
1117
|
+
const { agent, session, snapshot } = options;
|
|
1118
|
+
const appended = options.appended ?? [];
|
|
1119
|
+
const state = foldAIEvents({
|
|
1120
|
+
agent,
|
|
1121
|
+
events: appended,
|
|
1122
|
+
state: snapshot.state
|
|
1123
|
+
});
|
|
1124
|
+
const compaction = activeCompaction(state);
|
|
1125
|
+
const queued = new Set(options.coordinator.queued.map((item) => item.messageId));
|
|
1126
|
+
const visible = (messages) => messages.filter((message) => !queued.has(message.id));
|
|
1127
|
+
if (compaction === null) return visible(state.messages);
|
|
419
1128
|
const retained = new Set(compaction.retainedMessageIds ?? []);
|
|
420
|
-
|
|
1129
|
+
const throughIndex = compaction.throughIndex;
|
|
1130
|
+
if (throughIndex !== void 0) {
|
|
1131
|
+
const [prefix, priorCoordinator, tail] = await Promise.all([
|
|
1132
|
+
throughIndex === snapshot.index ? Promise.resolve(snapshot) : session.state(agent.reducer, { through: throughIndex }),
|
|
1133
|
+
session.state(aiCoordinatorReducer(agent.contract), { through: throughIndex }),
|
|
1134
|
+
throughIndex >= snapshot.index ? Promise.resolve([]) : session.history({
|
|
1135
|
+
gte: throughIndex + 1,
|
|
1136
|
+
lte: snapshot.index
|
|
1137
|
+
})
|
|
1138
|
+
]);
|
|
1139
|
+
for (const item of priorCoordinator.state.queued) retained.add(item.messageId);
|
|
1140
|
+
const events = options.excluded === void 0 ? tail : withoutGenerationLifecycle(tail, options.excluded);
|
|
1141
|
+
const context = foldAIEvents({
|
|
1142
|
+
agent,
|
|
1143
|
+
events: [...events, ...appended],
|
|
1144
|
+
state: {
|
|
1145
|
+
...prefix.state,
|
|
1146
|
+
messages: [...compaction.messages, ...prefix.state.messages.filter((message) => retained.has(message.id))],
|
|
1147
|
+
activeProjection: null
|
|
1148
|
+
}
|
|
1149
|
+
});
|
|
1150
|
+
const positions = /* @__PURE__ */ new Map();
|
|
1151
|
+
for (const message of [...compaction.messages, ...state.messages]) if (!positions.has(message.id)) positions.set(message.id, positions.size);
|
|
1152
|
+
return visible(context.messages.toSorted((left, right) => (positions.get(left.id) ?? positions.size) - (positions.get(right.id) ?? positions.size)));
|
|
1153
|
+
}
|
|
1154
|
+
const boundary = state.messages.findIndex((message) => message.id === compaction.throughMessageId);
|
|
1155
|
+
return visible([
|
|
421
1156
|
...compaction.messages,
|
|
422
1157
|
...state.messages.slice(0, boundary + 1).filter((message) => retained.has(message.id)),
|
|
423
1158
|
...state.messages.slice(boundary + 1)
|
|
424
|
-
];
|
|
1159
|
+
]);
|
|
1160
|
+
};
|
|
1161
|
+
const modelContext = async (options) => {
|
|
1162
|
+
const messages = await convertToModelMessages(options.messages, { tools: options.tools });
|
|
1163
|
+
const summary = activeCompaction(options.state)?.summary;
|
|
1164
|
+
return summary === void 0 ? messages : [{
|
|
1165
|
+
role: "user",
|
|
1166
|
+
content: summary
|
|
1167
|
+
}, ...messages];
|
|
425
1168
|
};
|
|
426
|
-
const
|
|
427
|
-
const
|
|
428
|
-
|
|
1169
|
+
const estimateInputTokens = async (options) => {
|
|
1170
|
+
const tools = await Promise.all(Object.entries(options.tools).map(async ([name, tool]) => ({
|
|
1171
|
+
name,
|
|
1172
|
+
description: tool.description,
|
|
1173
|
+
inputSchema: await asSchema(tool.inputSchema).jsonSchema
|
|
1174
|
+
})));
|
|
1175
|
+
return Math.ceil(new TextEncoder().encode(JSON.stringify({
|
|
1176
|
+
messages: options.messages,
|
|
1177
|
+
instructions: options.instructions,
|
|
1178
|
+
tools
|
|
1179
|
+
})).byteLength / 4);
|
|
429
1180
|
};
|
|
430
|
-
const
|
|
431
|
-
const
|
|
432
|
-
return
|
|
1181
|
+
const measuredInputTokens = (options) => {
|
|
1182
|
+
const previous = options.calibration;
|
|
1183
|
+
return previous !== void 0 && previous.model === options.model && options.estimate >= previous.estimate ? Math.max(options.estimate, Math.ceil(previous.inputTokens + options.estimate - previous.estimate)) : options.estimate;
|
|
433
1184
|
};
|
|
434
1185
|
const toolCalledEvent = (payload) => ({
|
|
435
1186
|
type: "ai.tool.called",
|
|
@@ -598,29 +1349,6 @@ const lifecycleEvents = (options) => {
|
|
|
598
1349
|
pending
|
|
599
1350
|
};
|
|
600
1351
|
};
|
|
601
|
-
function queuedMessagesAt(history) {
|
|
602
|
-
const queued = [];
|
|
603
|
-
for (const event of history) {
|
|
604
|
-
if (event.type === "ai.message.created") {
|
|
605
|
-
const payload = event.payload;
|
|
606
|
-
if (payload.message.role !== "user") continue;
|
|
607
|
-
const duplicate = queued.findIndex((item) => item.messageId === payload.message.id);
|
|
608
|
-
if (duplicate !== -1) queued.splice(duplicate, 1);
|
|
609
|
-
queued.push({
|
|
610
|
-
index: event.index,
|
|
611
|
-
messageId: payload.message.id,
|
|
612
|
-
generate: payload.generate !== false
|
|
613
|
-
});
|
|
614
|
-
continue;
|
|
615
|
-
}
|
|
616
|
-
if (event.type !== "ai.generation.requested") continue;
|
|
617
|
-
const request = event.payload;
|
|
618
|
-
if (request.reason !== "message") continue;
|
|
619
|
-
const requested = queued.findIndex((item) => item.messageId === request.messageId);
|
|
620
|
-
if (requested !== -1) queued.splice(0, requested + 1);
|
|
621
|
-
}
|
|
622
|
-
return queued;
|
|
623
|
-
}
|
|
624
1352
|
function withoutGenerationLifecycle(history, generationIds) {
|
|
625
1353
|
if (generationIds.size === 0) return history;
|
|
626
1354
|
const approvalKeys = /* @__PURE__ */ new Set();
|
|
@@ -639,6 +1367,24 @@ function withoutGenerationLifecycle(history, generationIds) {
|
|
|
639
1367
|
return true;
|
|
640
1368
|
});
|
|
641
1369
|
}
|
|
1370
|
+
const validateCompaction = (options) => {
|
|
1371
|
+
let compaction = options.compaction;
|
|
1372
|
+
if (compaction !== false && (typeof compaction !== "object" || compaction === null)) throw new TypeError("compaction must resolve to false or a policy object");
|
|
1373
|
+
if (compaction !== false && "then" in compaction) {
|
|
1374
|
+
Promise.resolve(compaction).catch(() => {});
|
|
1375
|
+
throw new TypeError("compaction options must be a policy or a resolver");
|
|
1376
|
+
}
|
|
1377
|
+
if (compaction !== false && !("shouldCompact" in compaction)) {
|
|
1378
|
+
if (compaction.thresholdTokens !== void 0 && (!Number.isSafeInteger(compaction.thresholdTokens) || compaction.thresholdTokens < 1)) throw new TypeError("compaction.thresholdTokens must be a positive safe integer");
|
|
1379
|
+
const choice = options.generation?.toolChoice;
|
|
1380
|
+
const unsupported = options.generation?.output !== void 0 ? "structured output" : choice !== void 0 && choice !== "auto" && choice !== "none" ? "forced tool choice" : Object.values(options.tools ?? {}).some((tool) => tool.type === "provider" && tool.isProviderExecuted === true) ? "provider-executed tools" : void 0;
|
|
1381
|
+
if (unsupported !== void 0) {
|
|
1382
|
+
if (options.explicit) throw new TypeError(`automatic compaction does not support ${unsupported}; use a custom policy or compaction: false`);
|
|
1383
|
+
compaction = false;
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
return compaction;
|
|
1387
|
+
};
|
|
642
1388
|
/**
|
|
643
1389
|
* Build the ordinary A2 handler table for the built-in agent protocol.
|
|
644
1390
|
* Application handlers can be spread beside this table.
|
|
@@ -648,61 +1394,19 @@ function createHandlers(options) {
|
|
|
648
1394
|
if (options.progress?.maxChunks !== void 0 && (!Number.isInteger(options.progress.maxChunks) || options.progress.maxChunks < 1)) throw new TypeError("progress.maxChunks must be a positive integer");
|
|
649
1395
|
if (options.progress?.maxDelayMs !== void 0 && (!Number.isFinite(options.progress.maxDelayMs) || options.progress.maxDelayMs < 0)) throw new TypeError("progress.maxDelayMs must be a non-negative number");
|
|
650
1396
|
if (options.maxSteps !== void 0 && options.maxSteps !== Number.POSITIVE_INFINITY && (!Number.isInteger(options.maxSteps) || options.maxSteps < 1)) throw new TypeError("maxSteps must be a positive integer or Infinity");
|
|
1397
|
+
const configuredCompaction = options.compaction ?? (options.generate === void 0 ? {} : false);
|
|
1398
|
+
const staticCompaction = typeof configuredCompaction === "function" ? void 0 : validateCompaction({
|
|
1399
|
+
compaction: configuredCompaction,
|
|
1400
|
+
explicit: options.compaction !== void 0,
|
|
1401
|
+
generation: options.generation,
|
|
1402
|
+
tools: options.tools
|
|
1403
|
+
});
|
|
651
1404
|
const tools = options.tools ?? {};
|
|
652
1405
|
const generation = options.generation ?? {};
|
|
653
1406
|
const maxSteps = options.maxSteps ?? Number.POSITIVE_INFINITY;
|
|
654
|
-
const
|
|
1407
|
+
const control = createControlRuntime({ agent: options.agent });
|
|
1408
|
+
const coordinator = control.coordinator;
|
|
655
1409
|
const promptCache = /* @__PURE__ */ new Map();
|
|
656
|
-
const coordinatorStateAt = (history, frontier = Number.POSITIVE_INFINITY) => {
|
|
657
|
-
let state = coordinator.initialState;
|
|
658
|
-
for (const event of history) {
|
|
659
|
-
if (event.index >= frontier) break;
|
|
660
|
-
state = coordinator.fold(state, event);
|
|
661
|
-
}
|
|
662
|
-
return state;
|
|
663
|
-
};
|
|
664
|
-
const requestForNextMessage = (state) => {
|
|
665
|
-
if (state.closed || state.response !== void 0) return void 0;
|
|
666
|
-
const next = state.queued.find((item) => item.generate !== false);
|
|
667
|
-
if (next === void 0) return void 0;
|
|
668
|
-
return {
|
|
669
|
-
type: "ai.generation.requested",
|
|
670
|
-
id: `ai.generate:message:${next.messageId}`,
|
|
671
|
-
payload: {
|
|
672
|
-
messageId: next.messageId,
|
|
673
|
-
reason: "message"
|
|
674
|
-
}
|
|
675
|
-
};
|
|
676
|
-
};
|
|
677
|
-
const scheduleNext = async (ctx) => requestForNextMessage((await ctx.session.state(coordinator, { through: "latest" })).state);
|
|
678
|
-
const continueIfReady = async (ctx, generationId) => {
|
|
679
|
-
const state = (await ctx.session.state(coordinator, { through: "latest" })).state;
|
|
680
|
-
const response = state.response;
|
|
681
|
-
if (response?.generation?.generationId !== generationId) return;
|
|
682
|
-
const input = response.inputResponse;
|
|
683
|
-
if (response.completion?.generationId === generationId && response.failure === void 0 && input !== void 0 && response.inputs.length === 0 && response.calls.every((call) => call.terminal || call.call.providerExecuted === true && call.call.supportsDeferredResults !== true && call.approval !== void 0 && call.response !== void 0)) {
|
|
684
|
-
await ctx.session.append("continue-after-input", {
|
|
685
|
-
type: "ai.generation.requested",
|
|
686
|
-
id: `ai.generate:input:${encodeURIComponent(response.responseMessageId)}:${encodeURIComponent(input.generationId)}:${encodeURIComponent(input.inputId)}`,
|
|
687
|
-
payload: {
|
|
688
|
-
messageId: response.responseMessageId,
|
|
689
|
-
responseMessageId: response.responseMessageId,
|
|
690
|
-
reason: "input"
|
|
691
|
-
}
|
|
692
|
-
});
|
|
693
|
-
return;
|
|
694
|
-
}
|
|
695
|
-
if (!continuationReady(state)) return;
|
|
696
|
-
await ctx.session.append("continue-after-tools", {
|
|
697
|
-
type: "ai.generation.requested",
|
|
698
|
-
id: `ai.generate:tools:${generationId}`,
|
|
699
|
-
payload: {
|
|
700
|
-
messageId: response.responseMessageId,
|
|
701
|
-
responseMessageId: response.responseMessageId,
|
|
702
|
-
reason: "tool"
|
|
703
|
-
}
|
|
704
|
-
});
|
|
705
|
-
};
|
|
706
1410
|
const resultEvent = (call, suffix, result) => ({
|
|
707
1411
|
type: "ai.tool.result",
|
|
708
1412
|
id: `${call.generationId}:tool:${call.toolCallId}:${suffix}`,
|
|
@@ -723,21 +1427,39 @@ function createHandlers(options) {
|
|
|
723
1427
|
...call.toolMetadata === void 0 ? {} : { toolMetadata: call.toolMetadata }
|
|
724
1428
|
}
|
|
725
1429
|
});
|
|
726
|
-
const promptMessages = (
|
|
727
|
-
const
|
|
1430
|
+
const promptMessages = (input) => {
|
|
1431
|
+
const { ctx, call, generation: owner } = input;
|
|
1432
|
+
const key = promptCacheKey(ctx.event.sessionId, call.generationId);
|
|
728
1433
|
const cached = promptCache.get(key);
|
|
729
1434
|
if (cached) return cached;
|
|
730
1435
|
const computation = (async () => {
|
|
731
|
-
const
|
|
732
|
-
|
|
733
|
-
const
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
1436
|
+
const frontier = owner.promptThroughIndex;
|
|
1437
|
+
if (frontier === void 0) throw new TypeError("AI work requires a prompt checkpoint");
|
|
1438
|
+
const [snapshot, atPrompt, tail] = await Promise.all([
|
|
1439
|
+
ctx.session.state(options.agent.reducer, { through: frontier }),
|
|
1440
|
+
ctx.session.state(coordinator, { through: frontier }),
|
|
1441
|
+
ctx.session.history({
|
|
1442
|
+
gte: frontier + 1,
|
|
1443
|
+
lte: ctx.event.index
|
|
1444
|
+
})
|
|
1445
|
+
]);
|
|
1446
|
+
const appended = tail.filter((event) => (event.type === "ai.generation.started" || event.type === "ai.compaction.requested" || event.type === "ai.compaction.completed") && event.payload.generationId === call.generationId);
|
|
1447
|
+
const state = foldAIEvents({
|
|
1448
|
+
agent: options.agent,
|
|
1449
|
+
events: appended,
|
|
1450
|
+
state: snapshot.state
|
|
1451
|
+
});
|
|
1452
|
+
return modelContext({
|
|
1453
|
+
messages: await contextMessages({
|
|
1454
|
+
agent: options.agent,
|
|
1455
|
+
session: ctx.session,
|
|
1456
|
+
snapshot,
|
|
1457
|
+
coordinator: atPrompt.state,
|
|
1458
|
+
appended
|
|
1459
|
+
}),
|
|
1460
|
+
state,
|
|
1461
|
+
tools
|
|
1462
|
+
});
|
|
741
1463
|
})();
|
|
742
1464
|
promptCache.set(key, computation);
|
|
743
1465
|
computation.catch(() => {
|
|
@@ -747,9 +1469,9 @@ function createHandlers(options) {
|
|
|
747
1469
|
};
|
|
748
1470
|
const toolExecutionFailure = (ctx, call, error) => {
|
|
749
1471
|
const schedulerFailure = consumeSchedulerSendFailure(error);
|
|
750
|
-
if (ctx.signal
|
|
1472
|
+
if (checkAbort(ctx.signal)) return;
|
|
751
1473
|
if (schedulerFailure === "retryable") throw error;
|
|
752
|
-
return resultEvent(call,
|
|
1474
|
+
return resultEvent(call, `${ctx.event.id}:execution:error`, { error: errorMessage(error) });
|
|
753
1475
|
};
|
|
754
1476
|
const runToolExecution = async (ctx, call, execute, messages) => {
|
|
755
1477
|
let output;
|
|
@@ -768,217 +1490,351 @@ function createHandlers(options) {
|
|
|
768
1490
|
} catch (error) {
|
|
769
1491
|
return toolExecutionFailure(ctx, call, error);
|
|
770
1492
|
}
|
|
771
|
-
if (iterator === void 0) return resultEvent(call,
|
|
1493
|
+
if (iterator === void 0) return resultEvent(call, `${ctx.event.id}:execution:0`, { output });
|
|
772
1494
|
let last;
|
|
773
1495
|
let sequence = 0;
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
result
|
|
778
|
-
} catch (error) {
|
|
779
|
-
return toolExecutionFailure(ctx, call, error);
|
|
780
|
-
}
|
|
781
|
-
if (result.done) break;
|
|
782
|
-
if (ctx.signal.aborted) {
|
|
783
|
-
try {
|
|
784
|
-
await iterator.return?.();
|
|
785
|
-
} catch {}
|
|
786
|
-
return;
|
|
787
|
-
}
|
|
788
|
-
last = result.value;
|
|
789
|
-
try {
|
|
790
|
-
await ctx.session.append(`tool:${call.toolCallId}:preliminary:${sequence}`, resultEvent(call, `execution:${sequence}:preliminary`, {
|
|
791
|
-
output: result.value,
|
|
792
|
-
preliminary: true
|
|
793
|
-
}));
|
|
794
|
-
} catch (error) {
|
|
1496
|
+
let done = false;
|
|
1497
|
+
try {
|
|
1498
|
+
for (;;) {
|
|
1499
|
+
let result;
|
|
795
1500
|
try {
|
|
796
|
-
await iterator.
|
|
797
|
-
} catch {
|
|
798
|
-
|
|
1501
|
+
result = await iterator.next();
|
|
1502
|
+
} catch (error) {
|
|
1503
|
+
return toolExecutionFailure(ctx, call, error);
|
|
1504
|
+
}
|
|
1505
|
+
if (result.done) {
|
|
1506
|
+
if (ctx.signal.reason instanceof A2Error) throw ctx.signal.reason;
|
|
1507
|
+
done = true;
|
|
1508
|
+
break;
|
|
1509
|
+
}
|
|
1510
|
+
if (checkAbort(ctx.signal)) return;
|
|
1511
|
+
last = result.value;
|
|
1512
|
+
await control.append({
|
|
1513
|
+
ctx,
|
|
1514
|
+
name: `tool:${call.toolCallId}:preliminary:${sequence}`,
|
|
1515
|
+
events: [resultEvent(call, `${ctx.event.id}:execution:${ctx.attempt}:${sequence}:preliminary`, {
|
|
1516
|
+
output: result.value,
|
|
1517
|
+
preliminary: true
|
|
1518
|
+
})]
|
|
1519
|
+
});
|
|
1520
|
+
sequence += 1;
|
|
799
1521
|
}
|
|
800
|
-
|
|
1522
|
+
} finally {
|
|
1523
|
+
if (!done) try {
|
|
1524
|
+
await iterator.return?.();
|
|
1525
|
+
} catch {}
|
|
801
1526
|
}
|
|
802
|
-
return resultEvent(call,
|
|
1527
|
+
return resultEvent(call, `${ctx.event.id}:execution:${sequence}:final`, sequence === 0 ? {} : { output: last });
|
|
803
1528
|
};
|
|
804
|
-
const executeTool = async (
|
|
1529
|
+
const executeTool = async (input) => {
|
|
1530
|
+
const { ctx, call } = input;
|
|
805
1531
|
const execute = tools[call.toolName]?.execute;
|
|
806
|
-
if (execute === void 0) return resultEvent(call,
|
|
807
|
-
const messages = await promptMessages(
|
|
808
|
-
if (ctx.signal
|
|
1532
|
+
if (execute === void 0) return resultEvent(call, `${ctx.event.id}:execution:error`, { error: `Tool '${call.toolName}' has no server executor` });
|
|
1533
|
+
const messages = await promptMessages(input);
|
|
1534
|
+
if (checkAbort(ctx.signal)) return;
|
|
809
1535
|
const scope = {
|
|
810
1536
|
contract: options.agent.contract,
|
|
811
1537
|
context: ctx
|
|
812
1538
|
};
|
|
813
1539
|
return toolScopeStorage.run(scope, () => runToolExecution(ctx, call, execute, messages));
|
|
814
1540
|
};
|
|
815
|
-
const
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
1541
|
+
const handleToolExecution = async (ctx) => {
|
|
1542
|
+
try {
|
|
1543
|
+
const state = (await ctx.session.state(control.reducer, { through: "latest" })).state;
|
|
1544
|
+
const request = ctx.event.payload;
|
|
1545
|
+
const call = state.coordinator.response?.calls.find((candidate) => candidate.work?.id === ctx.event.id);
|
|
1546
|
+
if (state.active?.turnId !== request.turnId || state.active.suspended || state.active.version !== request.version || state.coordinator.response?.failure !== void 0 || !call || call.terminal || call.work?.settled) return control.settled({
|
|
1547
|
+
ctx,
|
|
1548
|
+
events: void 0
|
|
1549
|
+
});
|
|
1550
|
+
return control.settled({
|
|
1551
|
+
ctx,
|
|
1552
|
+
events: await executeTool({
|
|
1553
|
+
ctx,
|
|
1554
|
+
call: request.call,
|
|
1555
|
+
generation: request.generation
|
|
1556
|
+
})
|
|
1557
|
+
});
|
|
1558
|
+
} catch (error) {
|
|
1559
|
+
if (control.cancelled({
|
|
1560
|
+
error,
|
|
1561
|
+
signal: ctx.signal
|
|
1562
|
+
})) return control.settled({
|
|
1563
|
+
ctx,
|
|
1564
|
+
events: void 0
|
|
1565
|
+
});
|
|
1566
|
+
throw error;
|
|
832
1567
|
}
|
|
833
|
-
if (!ctx.event.payload.approved) return resultEvent(current.call, "execution:denied", { denied: true });
|
|
834
|
-
return executeTool(ctx, current.call);
|
|
835
1568
|
};
|
|
836
1569
|
const generationHandler = async (ctx) => {
|
|
837
|
-
|
|
1570
|
+
if (checkAbort(ctx.signal)) return;
|
|
838
1571
|
const requestId = ctx.event.id;
|
|
839
1572
|
const request = ctx.event.payload;
|
|
840
|
-
const
|
|
1573
|
+
const snapshot = await ctx.session.state(options.agent.reducer, { through: "latest" });
|
|
1574
|
+
if (snapshot.state.active?.phase === "paused" || snapshot.state.active?.phase === "pausing") return;
|
|
1575
|
+
const coordinatorState = (await ctx.session.state(coordinator, { through: snapshot.index })).state;
|
|
841
1576
|
if (coordinatorState.closed || coordinatorState.response?.activeRequestId !== requestId) return;
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
return payload.requestId === requestId && payload.superseded !== true;
|
|
847
|
-
})) return;
|
|
848
|
-
const previousStarts = history.filter((event) => event.type === "ai.generation.started" && event.payload.requestId === requestId);
|
|
849
|
-
if (previousStarts.map((event) => event.payload.attempt).some((priorAttempt) => priorAttempt >= ctx.attempt)) return;
|
|
1577
|
+
const response = coordinatorState.response;
|
|
1578
|
+
const current = response.generation?.requestId === requestId ? response.generation : void 0;
|
|
1579
|
+
if (current !== void 0 && (response.completion !== void 0 || response.failure !== void 0 && response.failure.superseded !== true)) return;
|
|
1580
|
+
if (current !== void 0 && current.attempt >= ctx.attempt) return;
|
|
850
1581
|
const attempt = ctx.attempt;
|
|
851
1582
|
const generationId = `${requestId}:generation:${attempt}`;
|
|
852
1583
|
const responseMessageId = request.responseMessageId ?? (request.reason === "message" ? `${request.messageId}:assistant` : request.messageId);
|
|
853
|
-
const responseStepCount =
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
1584
|
+
const responseStepCount = response.stepCount;
|
|
1585
|
+
if (request.reason === "tool" && (requestId !== `ai.generate:tools:${response.source?.generation.generationId}` || response.responseMessageId !== request.messageId || response.responseMessageId !== request.responseMessageId)) return;
|
|
1586
|
+
const previous = current;
|
|
1587
|
+
const replaced = previous === void 0 ? [] : [{
|
|
1588
|
+
generation: previous,
|
|
1589
|
+
frontier: response.promptThroughIndex
|
|
1590
|
+
}];
|
|
1591
|
+
if (request.reason === "retry" && response.source?.failed) replaced.push({
|
|
1592
|
+
generation: response.source.generation,
|
|
1593
|
+
frontier: response.source.promptThroughIndex
|
|
1594
|
+
});
|
|
1595
|
+
const replacedGenerationIds = new Set(replaced.map(({ generation: owner }) => owner.generationId));
|
|
1596
|
+
let state = snapshot.state;
|
|
1597
|
+
if (replaced.length > 0) {
|
|
1598
|
+
const through = Math.min(...replaced.map(({ frontier }) => frontier));
|
|
1599
|
+
const baseline = await ctx.session.state(options.agent.reducer, { through });
|
|
1600
|
+
const tail = await ctx.session.history({
|
|
1601
|
+
gte: through + 1,
|
|
1602
|
+
lte: snapshot.index
|
|
1603
|
+
});
|
|
1604
|
+
state = foldAIEvents({
|
|
1605
|
+
agent: options.agent,
|
|
1606
|
+
events: withoutGenerationLifecycle(tail, replacedGenerationIds),
|
|
1607
|
+
state: baseline.state
|
|
1608
|
+
});
|
|
859
1609
|
}
|
|
860
|
-
const previous = previousStarts.filter((event) => event.payload.attempt < attempt).toSorted((left, right) => right.payload.attempt - left.payload.attempt)[0];
|
|
861
|
-
const incompleteId = previous ? previous.payload.generationId : void 0;
|
|
862
|
-
const replacedGenerationIds = /* @__PURE__ */ new Set();
|
|
863
|
-
if (incompleteId !== void 0) replacedGenerationIds.add(incompleteId);
|
|
864
|
-
if (request.reason === "retry" && sourceCoordinatorState.response?.failure?.generationId !== void 0) replacedGenerationIds.add(sourceCoordinatorState.response.failure.generationId);
|
|
865
|
-
const promptHistory = withoutGenerationLifecycle(history, replacedGenerationIds);
|
|
866
|
-
let state = replay(options.agent, promptHistory);
|
|
867
1610
|
const resolverContext = {
|
|
868
1611
|
event: ctx.event,
|
|
869
1612
|
state,
|
|
870
|
-
|
|
1613
|
+
session: { state: (reducer, readOptions) => ctx.session.state(reducer, {
|
|
1614
|
+
...readOptions,
|
|
1615
|
+
through: readOptions?.through ?? snapshot.index
|
|
1616
|
+
}) },
|
|
871
1617
|
signal: ctx.signal
|
|
872
1618
|
};
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
return
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
const
|
|
1619
|
+
let generationStarted = false;
|
|
1620
|
+
try {
|
|
1621
|
+
const resolvedModel = await resolve(options.model, resolverContext);
|
|
1622
|
+
if (resolvedModel === void 0) throw new TypeError("the model resolver returned undefined");
|
|
1623
|
+
if (checkAbort(ctx.signal)) return;
|
|
1624
|
+
const resolvedInstructions = options.instructions === void 0 ? void 0 : await resolve(options.instructions, resolverContext);
|
|
1625
|
+
if (checkAbort(ctx.signal)) return;
|
|
1626
|
+
const compaction = typeof configuredCompaction === "function" ? validateCompaction({
|
|
1627
|
+
compaction: await resolve(configuredCompaction, resolverContext),
|
|
1628
|
+
explicit: true,
|
|
1629
|
+
generation: options.generation,
|
|
1630
|
+
tools: options.tools
|
|
1631
|
+
}) : staticCompaction;
|
|
1632
|
+
if (checkAbort(ctx.signal)) return;
|
|
1633
|
+
if (request.reason === "tool" && responseStepCount >= maxSteps) {
|
|
1634
|
+
const source = response.source?.generation;
|
|
1635
|
+
if (source === void 0) return;
|
|
1636
|
+
return {
|
|
1637
|
+
type: "ai.generation.failed",
|
|
1638
|
+
id: `${requestId}:step-limit`,
|
|
1639
|
+
payload: {
|
|
1640
|
+
requestId: source.requestId,
|
|
1641
|
+
messageId: source.messageId,
|
|
1642
|
+
generationId: source.generationId,
|
|
1643
|
+
responseMessageId,
|
|
1644
|
+
error: `agent exceeded the ${maxSteps}-step limit`,
|
|
1645
|
+
stepLimit: true
|
|
1646
|
+
}
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
const started = {
|
|
904
1650
|
requestId,
|
|
905
|
-
messageId:
|
|
906
|
-
generationId
|
|
907
|
-
responseMessageId
|
|
908
|
-
|
|
909
|
-
|
|
1651
|
+
messageId: request.messageId,
|
|
1652
|
+
generationId,
|
|
1653
|
+
responseMessageId,
|
|
1654
|
+
attempt,
|
|
1655
|
+
model: modelName(resolvedModel),
|
|
1656
|
+
promptThroughIndex: snapshot.index
|
|
910
1657
|
};
|
|
1658
|
+
const catalogModelId = gatewayModelId(resolvedModel);
|
|
1659
|
+
const gatewayOptions = options.generation?.providerOptions?.["gateway"];
|
|
1660
|
+
const usesFallbackModels = Array.isArray(gatewayOptions?.["models"]) && gatewayOptions["models"].length > 0;
|
|
1661
|
+
const discoversMetadata = compaction !== false && !("shouldCompact" in compaction) && compaction.thresholdTokens === void 0 && !usesFallbackModels && catalogModelId !== void 0;
|
|
1662
|
+
const startEvents = [];
|
|
1663
|
+
if (discoversMetadata && !Object.hasOwn(state.modelMetadata, catalogModelId)) startEvents.push({
|
|
1664
|
+
type: "ai.model.metadata.requested",
|
|
1665
|
+
id: `ai.model.metadata:${encodeURIComponent(options.agent.contract.name)}:${encodeURIComponent(ctx.event.sessionId)}:${encodeURIComponent(catalogModelId)}`,
|
|
1666
|
+
payload: { modelId: catalogModelId }
|
|
1667
|
+
});
|
|
1668
|
+
if (previous) {
|
|
1669
|
+
const payload = previous;
|
|
1670
|
+
const superseded = {
|
|
1671
|
+
requestId,
|
|
1672
|
+
messageId: payload.messageId,
|
|
1673
|
+
generationId: payload.generationId,
|
|
1674
|
+
responseMessageId: payload.responseMessageId,
|
|
1675
|
+
error: "generation attempt was superseded after an incomplete run",
|
|
1676
|
+
superseded: true
|
|
1677
|
+
};
|
|
1678
|
+
startEvents.push({
|
|
1679
|
+
type: "ai.generation.failed",
|
|
1680
|
+
id: `${payload.generationId}:superseded`,
|
|
1681
|
+
payload: superseded
|
|
1682
|
+
});
|
|
1683
|
+
}
|
|
911
1684
|
startEvents.push({
|
|
912
|
-
type: "ai.generation.
|
|
913
|
-
id:
|
|
914
|
-
payload:
|
|
1685
|
+
type: "ai.generation.started",
|
|
1686
|
+
id: generationId,
|
|
1687
|
+
payload: started
|
|
915
1688
|
});
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
1689
|
+
const generationEvents = await control.append({
|
|
1690
|
+
ctx,
|
|
1691
|
+
name: "generation-start",
|
|
1692
|
+
events: startEvents
|
|
1693
|
+
});
|
|
1694
|
+
generationStarted = true;
|
|
1695
|
+
if (checkAbort(ctx.signal)) return;
|
|
1696
|
+
const promptCoordinatorState = coordinatorState;
|
|
1697
|
+
const messages = await contextMessages({
|
|
1698
|
+
agent: options.agent,
|
|
1699
|
+
session: ctx.session,
|
|
1700
|
+
snapshot: {
|
|
1701
|
+
state,
|
|
1702
|
+
index: snapshot.index
|
|
1703
|
+
},
|
|
1704
|
+
coordinator: promptCoordinatorState,
|
|
1705
|
+
excluded: replacedGenerationIds
|
|
1706
|
+
});
|
|
1707
|
+
let generationMessages = messages;
|
|
1708
|
+
let modelMessages = await modelContext({
|
|
1709
|
+
messages,
|
|
1710
|
+
state,
|
|
1711
|
+
tools
|
|
1712
|
+
});
|
|
1713
|
+
const baseContext = {
|
|
1714
|
+
request: ctx.event,
|
|
1715
|
+
requestId,
|
|
1716
|
+
generationId,
|
|
1717
|
+
responseMessageId,
|
|
1718
|
+
messages,
|
|
1719
|
+
modelMessages,
|
|
1720
|
+
state,
|
|
1721
|
+
session: resolverContext.session,
|
|
1722
|
+
signal: ctx.signal,
|
|
1723
|
+
model: resolvedModel,
|
|
1724
|
+
tools,
|
|
1725
|
+
...resolvedInstructions === void 0 ? {} : { instructions: resolvedInstructions },
|
|
1726
|
+
generation
|
|
934
1727
|
};
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
messages = await options.compaction.compact(compactionContext);
|
|
946
|
-
const completed = {
|
|
947
|
-
generationId,
|
|
948
|
-
throughMessageId,
|
|
1728
|
+
const policy = compaction;
|
|
1729
|
+
const tracksInput = policy !== false && !("shouldCompact" in policy) && (policy.thresholdTokens !== void 0 || discoversMetadata);
|
|
1730
|
+
const metadata = catalogModelId === void 0 ? void 0 : state.modelMetadata[catalogModelId];
|
|
1731
|
+
const limits = metadata?.status === "resolved" && !usesFallbackModels ? metadata.limits : void 0;
|
|
1732
|
+
let inputTokenEstimate;
|
|
1733
|
+
let compacted = false;
|
|
1734
|
+
const canCompact = response.source?.canCompact ?? true;
|
|
1735
|
+
if (policy && canCompact) {
|
|
1736
|
+
const compactionContext = {
|
|
1737
|
+
...resolverContext,
|
|
949
1738
|
messages,
|
|
950
|
-
|
|
951
|
-
};
|
|
952
|
-
await ctx.session.append("compaction-completed", {
|
|
953
|
-
type: "ai.compaction.completed",
|
|
954
|
-
id: `${generationId}:compaction:completed`,
|
|
955
|
-
payload: completed
|
|
956
|
-
});
|
|
957
|
-
state = {
|
|
958
|
-
...state,
|
|
959
|
-
compaction: {
|
|
960
|
-
status: "completed",
|
|
961
|
-
...completed
|
|
962
|
-
}
|
|
1739
|
+
modelMessages
|
|
963
1740
|
};
|
|
1741
|
+
let shouldCompact;
|
|
1742
|
+
if ("shouldCompact" in policy) shouldCompact = await policy.shouldCompact(compactionContext);
|
|
1743
|
+
else if (tracksInput) {
|
|
1744
|
+
inputTokenEstimate = await estimateInputTokens({
|
|
1745
|
+
messages: modelMessages,
|
|
1746
|
+
instructions: resolvedInstructions,
|
|
1747
|
+
tools
|
|
1748
|
+
});
|
|
1749
|
+
const inputTokens = measuredInputTokens({
|
|
1750
|
+
estimate: inputTokenEstimate,
|
|
1751
|
+
model: modelName(resolvedModel),
|
|
1752
|
+
calibration: coordinatorState.calibration
|
|
1753
|
+
});
|
|
1754
|
+
const threshold = policy.thresholdTokens ?? (limits === void 0 ? void 0 : Math.floor(Math.min(limits.contextWindow * .75, limits.contextWindow - (generation.maxOutputTokens ?? 0))));
|
|
1755
|
+
if (limits !== void 0 && threshold !== void 0 && threshold <= 0) throw new Error(`output allowance exhausts the ${limits.contextWindow}-token context window`);
|
|
1756
|
+
shouldCompact = threshold !== void 0 && inputTokens >= threshold;
|
|
1757
|
+
} else shouldCompact = false;
|
|
1758
|
+
if (checkAbort(ctx.signal)) return;
|
|
1759
|
+
if (shouldCompact) {
|
|
1760
|
+
const throughMessageId = state.messages.findLast((message) => !promptCoordinatorState.queued.some((queued) => queued.messageId === message.id))?.id ?? request.messageId;
|
|
1761
|
+
const throughIndex = snapshot.index;
|
|
1762
|
+
generationEvents.push(...await control.append({
|
|
1763
|
+
ctx,
|
|
1764
|
+
name: "compaction-requested",
|
|
1765
|
+
events: [{
|
|
1766
|
+
type: "ai.compaction.requested",
|
|
1767
|
+
id: `${generationId}:compaction:requested`,
|
|
1768
|
+
payload: {
|
|
1769
|
+
generationId,
|
|
1770
|
+
throughMessageId,
|
|
1771
|
+
throughIndex
|
|
1772
|
+
}
|
|
1773
|
+
}]
|
|
1774
|
+
}));
|
|
1775
|
+
const result = !("shouldCompact" in policy) ? {
|
|
1776
|
+
messages: [],
|
|
1777
|
+
...await summarize({
|
|
1778
|
+
context: baseContext,
|
|
1779
|
+
instructions: policy.instructions
|
|
1780
|
+
})
|
|
1781
|
+
} : { messages: await policy.compact(compactionContext) };
|
|
1782
|
+
if (checkAbort(ctx.signal)) return;
|
|
1783
|
+
const completed = {
|
|
1784
|
+
generationId,
|
|
1785
|
+
throughMessageId,
|
|
1786
|
+
throughIndex,
|
|
1787
|
+
...result,
|
|
1788
|
+
...promptCoordinatorState.queued.length === 0 ? {} : { retainedMessageIds: promptCoordinatorState.queued.map((item) => item.messageId) }
|
|
1789
|
+
};
|
|
1790
|
+
generationEvents.push(...await control.append({
|
|
1791
|
+
ctx,
|
|
1792
|
+
name: "compaction-completed",
|
|
1793
|
+
events: [{
|
|
1794
|
+
type: "ai.compaction.completed",
|
|
1795
|
+
id: `${generationId}:compaction:completed`,
|
|
1796
|
+
payload: completed
|
|
1797
|
+
}]
|
|
1798
|
+
}));
|
|
1799
|
+
const queued = new Set(promptCoordinatorState.queued.map((item) => item.messageId));
|
|
1800
|
+
generationMessages = result.messages.filter((message) => !queued.has(message.id));
|
|
1801
|
+
compacted = true;
|
|
1802
|
+
}
|
|
964
1803
|
}
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1804
|
+
const currentState = foldAIEvents({
|
|
1805
|
+
agent: options.agent,
|
|
1806
|
+
events: generationEvents,
|
|
1807
|
+
state: snapshot.state
|
|
1808
|
+
});
|
|
1809
|
+
if (request.reason === "retry" || previous !== void 0) generationMessages = await contextMessages({
|
|
1810
|
+
agent: options.agent,
|
|
1811
|
+
session: ctx.session,
|
|
1812
|
+
snapshot,
|
|
1813
|
+
coordinator: promptCoordinatorState,
|
|
1814
|
+
appended: generationEvents
|
|
1815
|
+
});
|
|
1816
|
+
if (policy && "shouldCompact" in policy || generationMessages !== messages || activeCompaction(currentState)?.summary !== activeCompaction(state)?.summary) modelMessages = await modelContext({
|
|
1817
|
+
messages: generationMessages,
|
|
1818
|
+
state: currentState,
|
|
1819
|
+
tools
|
|
1820
|
+
});
|
|
1821
|
+
promptCache.set(promptCacheKey(ctx.event.sessionId, generationId), Promise.resolve(modelMessages));
|
|
1822
|
+
if (tracksInput && (inputTokenEstimate === void 0 || compacted)) inputTokenEstimate = await estimateInputTokens({
|
|
1823
|
+
messages: modelMessages,
|
|
1824
|
+
instructions: resolvedInstructions,
|
|
1825
|
+
tools
|
|
1826
|
+
});
|
|
1827
|
+
let pendingToolCalls = [];
|
|
1828
|
+
if (checkAbort(ctx.signal)) return;
|
|
974
1829
|
const generateContext = {
|
|
975
1830
|
request: ctx.event,
|
|
976
1831
|
requestId,
|
|
977
1832
|
generationId,
|
|
978
1833
|
responseMessageId,
|
|
979
1834
|
messages: generationMessages,
|
|
1835
|
+
modelMessages,
|
|
980
1836
|
state: currentState,
|
|
981
|
-
|
|
1837
|
+
session: resolverContext.session,
|
|
982
1838
|
signal: ctx.signal,
|
|
983
1839
|
model: resolvedModel,
|
|
984
1840
|
tools,
|
|
@@ -1018,11 +1874,15 @@ function createHandlers(options) {
|
|
|
1018
1874
|
custom
|
|
1019
1875
|
});
|
|
1020
1876
|
pendingToolCalls = lifecycle.pending;
|
|
1021
|
-
await
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1877
|
+
await control.append({
|
|
1878
|
+
ctx,
|
|
1879
|
+
name: `generation-progress:${sequence}`,
|
|
1880
|
+
events: [{
|
|
1881
|
+
type: "ai.generation.progress",
|
|
1882
|
+
id: `${generationId}:progress:${sequence}`,
|
|
1883
|
+
payload: progress
|
|
1884
|
+
}, ...lifecycle.events]
|
|
1885
|
+
});
|
|
1026
1886
|
sequence += 1;
|
|
1027
1887
|
}
|
|
1028
1888
|
if (!finish) throw new Error("agent generation finished without output");
|
|
@@ -1031,6 +1891,7 @@ function createHandlers(options) {
|
|
|
1031
1891
|
messageId: request.messageId,
|
|
1032
1892
|
generationId,
|
|
1033
1893
|
responseMessageId,
|
|
1894
|
+
...inputTokenEstimate === void 0 ? {} : { inputTokenEstimate },
|
|
1034
1895
|
...finish.finishReason === void 0 ? {} : { finishReason: finish.finishReason },
|
|
1035
1896
|
...finish.usage === void 0 ? {} : { usage: finish.usage }
|
|
1036
1897
|
};
|
|
@@ -1053,19 +1914,13 @@ function createHandlers(options) {
|
|
|
1053
1914
|
}
|
|
1054
1915
|
];
|
|
1055
1916
|
} catch (error) {
|
|
1056
|
-
if (error instanceof
|
|
1057
|
-
if (
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
generationId,
|
|
1061
|
-
reason: "aborted"
|
|
1062
|
-
};
|
|
1063
|
-
return {
|
|
1064
|
-
type: "ai.message.interrupted",
|
|
1065
|
-
id: `${generationId}:interrupted`,
|
|
1066
|
-
payload: interrupted
|
|
1067
|
-
};
|
|
1917
|
+
if (error instanceof ControlCancelled) throw error;
|
|
1918
|
+
if (error instanceof A2Error) {
|
|
1919
|
+
checkAbort(ctx.signal);
|
|
1920
|
+
throw error;
|
|
1068
1921
|
}
|
|
1922
|
+
if (checkAbort(ctx.signal)) return;
|
|
1923
|
+
if (!generationStarted) throw error;
|
|
1069
1924
|
const failed = {
|
|
1070
1925
|
requestId,
|
|
1071
1926
|
messageId: request.messageId,
|
|
@@ -1080,97 +1935,89 @@ function createHandlers(options) {
|
|
|
1080
1935
|
};
|
|
1081
1936
|
}
|
|
1082
1937
|
};
|
|
1083
|
-
const handleMessageCreated = async (ctx) => {
|
|
1084
|
-
if (ctx.event.payload.message.role !== "user" || ctx.event.payload.generate === false) return;
|
|
1085
|
-
return scheduleNext(ctx);
|
|
1086
|
-
};
|
|
1087
|
-
const handleRetry = async (ctx) => {
|
|
1088
|
-
const response = (await ctx.session.state(coordinator, { through: "latest" })).state.response;
|
|
1089
|
-
if (response?.status !== "failed" || response.rootMessageId !== ctx.event.payload.messageId || response.responseMessageId !== ctx.event.payload.responseMessageId) return;
|
|
1090
|
-
return {
|
|
1091
|
-
type: "ai.generation.requested",
|
|
1092
|
-
id: `ai.generate:retry:${ctx.event.payload.retryId}`,
|
|
1093
|
-
payload: {
|
|
1094
|
-
messageId: response.rootMessageId,
|
|
1095
|
-
responseMessageId: response.responseMessageId,
|
|
1096
|
-
reason: "retry"
|
|
1097
|
-
}
|
|
1098
|
-
};
|
|
1099
|
-
};
|
|
1100
|
-
const handleInputResponse = async (ctx) => {
|
|
1101
|
-
const response = (await ctx.session.state(coordinator, { through: "latest" })).state.response;
|
|
1102
|
-
if (response?.responseMessageId !== ctx.event.payload.messageId || response.inputResponse?.index !== ctx.event.index || response.inputResponse.generationId !== ctx.event.payload.generationId || response.inputResponse.inputId !== ctx.event.payload.inputId) return;
|
|
1103
|
-
await continueIfReady(ctx, ctx.event.payload.generationId);
|
|
1104
|
-
};
|
|
1105
1938
|
const clearPromptCache = (sessionId) => {
|
|
1106
1939
|
const prefix = `${sessionId}\u001f`;
|
|
1107
1940
|
for (const key of promptCache.keys()) if (key.startsWith(prefix)) promptCache.delete(key);
|
|
1108
1941
|
};
|
|
1109
|
-
const handleResponseEnded = async (ctx) => {
|
|
1110
|
-
clearPromptCache(ctx.event.sessionId);
|
|
1111
|
-
return scheduleNext(ctx);
|
|
1112
|
-
};
|
|
1113
1942
|
return {
|
|
1114
|
-
"ai.
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1943
|
+
"ai.model.metadata.requested": { handler: async (ctx) => {
|
|
1944
|
+
const limits = await readModelLimits({
|
|
1945
|
+
modelId: ctx.event.payload.modelId,
|
|
1946
|
+
signal: ctx.signal
|
|
1947
|
+
});
|
|
1948
|
+
ctx.signal.throwIfAborted();
|
|
1949
|
+
return {
|
|
1950
|
+
type: "ai.model.metadata.resolved",
|
|
1951
|
+
id: `${ctx.event.id}:resolved`,
|
|
1952
|
+
payload: {
|
|
1953
|
+
modelId: ctx.event.payload.modelId,
|
|
1954
|
+
limits
|
|
1955
|
+
}
|
|
1956
|
+
};
|
|
1957
|
+
} },
|
|
1958
|
+
"ai.control.requested": {
|
|
1959
|
+
lane: "a2.ai.control",
|
|
1960
|
+
handler: control.handler
|
|
1129
1961
|
},
|
|
1130
|
-
"ai.
|
|
1131
|
-
lane: "a2.ai.
|
|
1132
|
-
handler:
|
|
1962
|
+
"ai.work.reported": {
|
|
1963
|
+
lane: "a2.ai.control",
|
|
1964
|
+
handler: control.handler
|
|
1133
1965
|
},
|
|
1134
|
-
"ai.
|
|
1966
|
+
"ai.message.completed": { handler: async (ctx) => {
|
|
1135
1967
|
clearPromptCache(ctx.event.sessionId);
|
|
1136
|
-
return Promise.resolve();
|
|
1137
1968
|
} },
|
|
1969
|
+
"ai.message.interrupted": { handler: async (ctx) => {
|
|
1970
|
+
clearPromptCache(ctx.event.sessionId);
|
|
1971
|
+
} },
|
|
1972
|
+
"ai.session.closed": {
|
|
1973
|
+
lane: "a2.ai.control",
|
|
1974
|
+
handler: async (ctx) => {
|
|
1975
|
+
clearPromptCache(ctx.event.sessionId);
|
|
1976
|
+
return control.handler(ctx);
|
|
1977
|
+
}
|
|
1978
|
+
},
|
|
1138
1979
|
"ai.generation.failed": { handler: (ctx) => {
|
|
1139
1980
|
promptCache.delete(promptCacheKey(ctx.event.sessionId, ctx.event.payload.generationId));
|
|
1140
1981
|
return Promise.resolve();
|
|
1141
1982
|
} },
|
|
1142
1983
|
"ai.generation.requested": {
|
|
1143
|
-
lane: "a2.ai.
|
|
1984
|
+
lane: "a2.ai.model",
|
|
1144
1985
|
abortOn: {
|
|
1145
|
-
"ai.
|
|
1146
|
-
const
|
|
1147
|
-
return
|
|
1986
|
+
"ai.control.committed": (event, trigger) => {
|
|
1987
|
+
const active = event.payload.view;
|
|
1988
|
+
return active?.turnId !== trigger.payload.control?.turnId || active?.version !== trigger.payload.control?.version;
|
|
1148
1989
|
},
|
|
1149
1990
|
"ai.session.closed": true
|
|
1150
1991
|
},
|
|
1151
|
-
handler:
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1992
|
+
handler: async (ctx) => {
|
|
1993
|
+
try {
|
|
1994
|
+
return control.settled({
|
|
1995
|
+
ctx,
|
|
1996
|
+
events: await generationHandler(ctx)
|
|
1997
|
+
});
|
|
1998
|
+
} catch (error) {
|
|
1999
|
+
if (control.cancelled({
|
|
2000
|
+
error,
|
|
2001
|
+
signal: ctx.signal
|
|
2002
|
+
})) return control.settled({
|
|
2003
|
+
ctx,
|
|
2004
|
+
events: void 0
|
|
2005
|
+
});
|
|
2006
|
+
throw error;
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
1161
2009
|
},
|
|
1162
|
-
"ai.
|
|
2010
|
+
"ai.tool.execution.requested": {
|
|
1163
2011
|
abortOn: {
|
|
1164
|
-
"ai.generation.failed": (event, trigger) => event.payload.
|
|
1165
|
-
"ai.
|
|
2012
|
+
"ai.generation.failed": (event, trigger) => event.payload.generationId === trigger.payload.call.generationId,
|
|
2013
|
+
"ai.control.committed": (event, trigger) => {
|
|
2014
|
+
const active = event.payload.view;
|
|
2015
|
+
return active?.turnId !== trigger.payload.turnId || active?.version !== trigger.payload.version;
|
|
2016
|
+
},
|
|
1166
2017
|
"ai.session.closed": true
|
|
1167
2018
|
},
|
|
1168
|
-
handler:
|
|
1169
|
-
}
|
|
1170
|
-
"ai.tool.result": { handler: async (ctx) => {
|
|
1171
|
-
if (ctx.event.payload.preliminary === true) return;
|
|
1172
|
-
await continueIfReady(ctx, ctx.event.payload.generationId);
|
|
1173
|
-
} }
|
|
2019
|
+
handler: handleToolExecution
|
|
2020
|
+
}
|
|
1174
2021
|
};
|
|
1175
2022
|
}
|
|
1176
2023
|
/** Assemble an A2 server with the built-in agent handlers and app extensions. */
|