experimental-a2 0.0.0 → 0.2.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 +43 -0
- package/dist/ai-server.browser.js +2 -2
- package/dist/ai-server.d.ts +19 -7
- package/dist/ai-server.js +730 -96
- package/dist/ai.d.ts +32 -11
- package/dist/ai.js +253 -75
- package/dist/client.d.ts +1 -1
- package/dist/client.js +4 -4
- package/dist/{contract-B0kAXoaL.js → contract-CG_adnu_.js} +2 -1
- package/dist/{contract-DL8btVd9.d.ts → contract-C_3dIIEU.d.ts} +4 -1
- package/dist/devtools-server.browser.js +2 -2
- package/dist/devtools-server.js +1 -1
- package/dist/http.d.ts +1 -1
- package/dist/http.js +4 -3
- package/dist/idempotent-replay-BMyHrP0L.js +19 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1 -1
- package/dist/{internal-Dm8Ejnud.js → internal-D6wNxTck.js} +3 -3
- package/dist/{log-Dg1I8NRr.d.ts → log-ldf5g8Cx.d.ts} +74 -56
- package/dist/log-memory.d.ts +1 -1
- package/dist/log-memory.js +173 -96
- package/dist/{log-polling-RO7kclzR.js → log-polling-6COoN60V.js} +1 -1
- package/dist/log-postgres.d.ts +1 -1
- package/dist/log-postgres.js +235 -192
- package/dist/log-redis.d.ts +1 -1
- package/dist/log-redis.js +453 -263
- package/dist/log-sqlite.d.ts +1 -1
- package/dist/log-sqlite.js +216 -127
- package/dist/otel.d.ts +1 -1
- package/dist/otel.js +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/recovery-vercel.d.ts +2 -2
- package/dist/recovery-vercel.js +9 -10
- package/dist/server-DJgD2YWP.js +877 -0
- package/dist/server.browser.js +4 -4
- package/dist/server.d.ts +46 -27
- package/dist/server.js +1 -1
- package/dist/{telemetry-C78al20p.d.ts → telemetry-Cso0qyHQ.d.ts} +1 -1
- package/dist/{wire-2QpU1EtJ.js → wire-BVsgR8o9.js} +1 -1
- package/docs/01-quickstart.mdx +7 -7
- package/docs/concepts/01-contracts.mdx +22 -22
- package/docs/concepts/02-handlers.mdx +223 -89
- package/docs/concepts/03-durability.mdx +199 -112
- package/docs/concepts/04-state.mdx +27 -1
- package/docs/guides/01-timers.mdx +4 -4
- package/docs/guides/02-cancellation.mdx +32 -4
- package/docs/guides/05-production.mdx +61 -27
- package/docs/guides/06-ai-agents.mdx +151 -70
- package/docs/guides/07-devtools.mdx +6 -3
- package/docs/guides/08-application-data.mdx +5 -6
- package/docs/index.mdx +30 -14
- package/docs/reference/01-api.mdx +305 -70
- package/package.json +31 -31
- package/dist/server-DYsnKTTy.js +0 -780
package/dist/ai-server.js
CHANGED
|
@@ -1,12 +1,286 @@
|
|
|
1
|
-
import { t as
|
|
2
|
-
import {
|
|
1
|
+
import { t as A2Error } from "./errors-BJRMd-h6.js";
|
|
2
|
+
import { t as createServer } from "./server-DJgD2YWP.js";
|
|
3
|
+
import { convertToModelMessages, stepCountIs, streamText, toUIMessageStream } from "ai";
|
|
4
|
+
//#region src/ai-sdk-step.ts
|
|
5
|
+
const CONTROLLED_SETTINGS = [
|
|
6
|
+
"_internal",
|
|
7
|
+
"abortSignal",
|
|
8
|
+
"instructions",
|
|
9
|
+
"messages",
|
|
10
|
+
"model",
|
|
11
|
+
"onToolExecutionEnd",
|
|
12
|
+
"onToolExecutionStart",
|
|
13
|
+
"experimental_onToolCallFinish",
|
|
14
|
+
"experimental_onToolCallStart",
|
|
15
|
+
"experimental_sandbox",
|
|
16
|
+
"experimental_toolCallers",
|
|
17
|
+
"experimental_toolApprovalSecret",
|
|
18
|
+
"prompt",
|
|
19
|
+
"prepareStep",
|
|
20
|
+
"stopWhen",
|
|
21
|
+
"system",
|
|
22
|
+
"tools",
|
|
23
|
+
"toolsContext"
|
|
24
|
+
];
|
|
25
|
+
const safeSettings = (settings) => {
|
|
26
|
+
const result = { ...settings };
|
|
27
|
+
for (const key of CONTROLLED_SETTINGS) Reflect.deleteProperty(result, key);
|
|
28
|
+
if (typeof result.timeout === "object" && result.timeout !== null) {
|
|
29
|
+
const timeout = { ...result.timeout };
|
|
30
|
+
Reflect.deleteProperty(timeout, "toolMs");
|
|
31
|
+
Reflect.deleteProperty(timeout, "tools");
|
|
32
|
+
result.timeout = timeout;
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
};
|
|
36
|
+
const modelToolSet = (tools) => Object.fromEntries(Object.entries(tools).map(([name, tool]) => {
|
|
37
|
+
if (tool.type === "provider" && tool.isProviderExecuted === true) return [name, tool];
|
|
38
|
+
const definition = { ...tool };
|
|
39
|
+
Reflect.deleteProperty(definition, "execute");
|
|
40
|
+
return [name, definition];
|
|
41
|
+
}));
|
|
42
|
+
const errorMessage$1 = (error) => error instanceof Error ? error.message : String(error);
|
|
43
|
+
/** Run exactly one AI SDK model step while leaving local tool execution to A2. */
|
|
44
|
+
async function generateAISDKStep(input) {
|
|
45
|
+
const settings = safeSettings(input.settings);
|
|
46
|
+
const tools = modelToolSet(input.tools);
|
|
47
|
+
const messages = await convertToModelMessages(input.messages, { tools });
|
|
48
|
+
const streamOptions = {
|
|
49
|
+
...settings,
|
|
50
|
+
model: input.model,
|
|
51
|
+
tools,
|
|
52
|
+
messages,
|
|
53
|
+
abortSignal: input.abortSignal,
|
|
54
|
+
...input.instructions === void 0 ? {} : { instructions: input.instructions },
|
|
55
|
+
stopWhen: stepCountIs(1),
|
|
56
|
+
onError: settings.onError ?? (() => {})
|
|
57
|
+
};
|
|
58
|
+
const result = streamText(streamOptions);
|
|
59
|
+
let completion;
|
|
60
|
+
let fatalError;
|
|
61
|
+
const observed = result.stream.pipeThrough(new TransformStream({ transform(part, controller) {
|
|
62
|
+
if (part.type === "error" && fatalError === void 0) fatalError = { value: part.error };
|
|
63
|
+
else if (part.type === "finish") completion = {
|
|
64
|
+
finishReason: part.finishReason,
|
|
65
|
+
usage: part.totalUsage
|
|
66
|
+
};
|
|
67
|
+
controller.enqueue(part);
|
|
68
|
+
} }));
|
|
69
|
+
return {
|
|
70
|
+
stream: toUIMessageStream({
|
|
71
|
+
stream: observed,
|
|
72
|
+
tools,
|
|
73
|
+
originalMessages: input.messages,
|
|
74
|
+
generateMessageId: () => input.responseMessageId,
|
|
75
|
+
onError: errorMessage$1
|
|
76
|
+
}),
|
|
77
|
+
completion() {
|
|
78
|
+
if (fatalError !== void 0) throw fatalError.value;
|
|
79
|
+
if (completion === void 0) throw new Error("AI SDK step ended without a finish part");
|
|
80
|
+
if (completion.finishReason === "error") throw new Error("AI SDK step finished with an error");
|
|
81
|
+
return completion;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region src/ai-coordinator.ts
|
|
87
|
+
const updateResponse = (state, update) => state.response === void 0 ? state : {
|
|
88
|
+
...state,
|
|
89
|
+
response: update(state.response)
|
|
90
|
+
};
|
|
91
|
+
const foldCoordinator = (state, event) => {
|
|
92
|
+
switch (event.type) {
|
|
93
|
+
case "ai.session.closed": return {
|
|
94
|
+
closed: true,
|
|
95
|
+
queued: []
|
|
96
|
+
};
|
|
97
|
+
case "ai.message.created": {
|
|
98
|
+
const message = event.payload.message;
|
|
99
|
+
if (message.role !== "user") return state;
|
|
100
|
+
return {
|
|
101
|
+
...state,
|
|
102
|
+
queued: [...state.queued.filter((item) => item.messageId !== message.id), {
|
|
103
|
+
index: event.index,
|
|
104
|
+
messageId: message.id
|
|
105
|
+
}].toSorted((left, right) => left.index - right.index)
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
case "ai.generation.requested": {
|
|
109
|
+
const request = event.payload;
|
|
110
|
+
if (request.reason === "message") {
|
|
111
|
+
const responseMessageId = request.responseMessageId ?? `${request.messageId}:assistant`;
|
|
112
|
+
return {
|
|
113
|
+
...state,
|
|
114
|
+
queued: state.queued.filter((item) => item.messageId !== request.messageId),
|
|
115
|
+
response: {
|
|
116
|
+
rootMessageId: request.messageId,
|
|
117
|
+
responseMessageId,
|
|
118
|
+
status: "requested",
|
|
119
|
+
activeRequestId: event.id,
|
|
120
|
+
calls: [],
|
|
121
|
+
inputs: []
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (state.response === void 0 || request.responseMessageId !== state.response.responseMessageId) return state;
|
|
126
|
+
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;
|
|
127
|
+
return updateResponse(state, (response) => ({
|
|
128
|
+
rootMessageId: response.rootMessageId,
|
|
129
|
+
responseMessageId: response.responseMessageId,
|
|
130
|
+
status: "requested",
|
|
131
|
+
activeRequestId: event.id,
|
|
132
|
+
calls: [],
|
|
133
|
+
inputs: []
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
case "ai.generation.started": {
|
|
137
|
+
const generation = event.payload;
|
|
138
|
+
if (state.response?.responseMessageId !== generation.responseMessageId || state.response.activeRequestId !== generation.requestId || state.response.generation?.requestId === generation.requestId && state.response.generation.attempt >= generation.attempt) return state;
|
|
139
|
+
return updateResponse(state, (response) => ({
|
|
140
|
+
rootMessageId: response.rootMessageId,
|
|
141
|
+
responseMessageId: response.responseMessageId,
|
|
142
|
+
status: "generating",
|
|
143
|
+
generation,
|
|
144
|
+
activeRequestId: generation.requestId,
|
|
145
|
+
calls: [],
|
|
146
|
+
inputs: []
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
149
|
+
case "ai.generation.completed": {
|
|
150
|
+
const completion = event.payload;
|
|
151
|
+
if (state.response?.generation?.generationId !== completion.generationId) return state;
|
|
152
|
+
return updateResponse(state, (response) => ({
|
|
153
|
+
...response,
|
|
154
|
+
status: "waiting",
|
|
155
|
+
completion
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
case "ai.generation.failed": {
|
|
159
|
+
const failure = event.payload;
|
|
160
|
+
const response = state.response;
|
|
161
|
+
const ownsActiveGeneration = response?.generation?.generationId === failure.generationId;
|
|
162
|
+
const ownsActiveStepLimit = failure.stepLimit === true && response?.activeRequestId === `ai.generate:tools:${failure.generationId}` && response.responseMessageId === failure.responseMessageId;
|
|
163
|
+
if (!ownsActiveGeneration && !ownsActiveStepLimit) return state;
|
|
164
|
+
return updateResponse(state, (current) => ({
|
|
165
|
+
...current,
|
|
166
|
+
status: "failed",
|
|
167
|
+
failure
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
170
|
+
case "ai.message.completed": {
|
|
171
|
+
const messageId = event.payload.messageId;
|
|
172
|
+
return state.response?.responseMessageId === messageId ? {
|
|
173
|
+
closed: state.closed,
|
|
174
|
+
queued: state.queued
|
|
175
|
+
} : state;
|
|
176
|
+
}
|
|
177
|
+
case "ai.message.interrupted": {
|
|
178
|
+
const interruption = event.payload;
|
|
179
|
+
const response = state.response;
|
|
180
|
+
return response?.responseMessageId === interruption.messageId && (interruption.generationId === void 0 || interruption.generationId === response.generation?.generationId) ? {
|
|
181
|
+
closed: state.closed,
|
|
182
|
+
queued: state.queued
|
|
183
|
+
} : state;
|
|
184
|
+
}
|
|
185
|
+
case "ai.tool.called": {
|
|
186
|
+
const call = event.payload;
|
|
187
|
+
if (state.response?.generation?.generationId !== call.generationId) return state;
|
|
188
|
+
return updateResponse(state, (response) => ({
|
|
189
|
+
...response,
|
|
190
|
+
calls: [...response.calls.filter((candidate) => candidate.call.toolCallId !== call.toolCallId), {
|
|
191
|
+
index: event.index,
|
|
192
|
+
call,
|
|
193
|
+
terminal: false
|
|
194
|
+
}]
|
|
195
|
+
}));
|
|
196
|
+
}
|
|
197
|
+
case "ai.approval.requested": {
|
|
198
|
+
const approval = event.payload;
|
|
199
|
+
if (state.response?.generation?.generationId !== approval.generationId) return state;
|
|
200
|
+
return updateResponse(state, (response) => ({
|
|
201
|
+
...response,
|
|
202
|
+
calls: response.calls.map((candidate) => candidate.call.toolCallId === approval.toolCallId ? {
|
|
203
|
+
...candidate,
|
|
204
|
+
approval
|
|
205
|
+
} : candidate)
|
|
206
|
+
}));
|
|
207
|
+
}
|
|
208
|
+
case "ai.approval.responded": {
|
|
209
|
+
const approval = event.payload;
|
|
210
|
+
if (state.response?.responseMessageId !== approval.messageId || state.response.generation?.generationId !== approval.generationId) return state;
|
|
211
|
+
return updateResponse(state, (response) => ({
|
|
212
|
+
...response,
|
|
213
|
+
calls: response.calls.map((candidate) => candidate.approval?.approvalId === approval.approvalId ? {
|
|
214
|
+
...candidate,
|
|
215
|
+
response: approval,
|
|
216
|
+
responseIndex: event.index
|
|
217
|
+
} : candidate)
|
|
218
|
+
}));
|
|
219
|
+
}
|
|
220
|
+
case "ai.tool.result": {
|
|
221
|
+
const result = event.payload;
|
|
222
|
+
if (result.preliminary === true || state.response?.generation?.generationId !== result.generationId) return state;
|
|
223
|
+
return updateResponse(state, (response) => ({
|
|
224
|
+
...response,
|
|
225
|
+
calls: response.calls.map((candidate) => candidate.call.toolCallId === result.toolCallId ? {
|
|
226
|
+
...candidate,
|
|
227
|
+
terminal: true
|
|
228
|
+
} : candidate)
|
|
229
|
+
}));
|
|
230
|
+
}
|
|
231
|
+
case "ai.input.requested": {
|
|
232
|
+
const input = event.payload;
|
|
233
|
+
if (state.response?.responseMessageId !== input.messageId || state.response.generation?.generationId !== input.generationId) return state;
|
|
234
|
+
return updateResponse(state, (response) => ({
|
|
235
|
+
...response,
|
|
236
|
+
inputs: [...response.inputs.filter((candidate) => candidate.messageId !== input.messageId || candidate.generationId !== input.generationId || candidate.inputId !== input.inputId), input]
|
|
237
|
+
}));
|
|
238
|
+
}
|
|
239
|
+
case "ai.input.responded": {
|
|
240
|
+
const input = event.payload;
|
|
241
|
+
const requested = state.response?.inputs.find((candidate) => candidate.messageId === input.messageId && candidate.generationId === input.generationId && candidate.inputId === input.inputId && candidate.name === input.name);
|
|
242
|
+
if (state.response?.responseMessageId !== input.messageId || state.response.generation?.generationId !== input.generationId || requested === void 0) return state;
|
|
243
|
+
return updateResponse(state, (response) => ({
|
|
244
|
+
...response,
|
|
245
|
+
inputs: response.inputs.filter((candidate) => candidate.inputId !== input.inputId),
|
|
246
|
+
inputResponse: {
|
|
247
|
+
index: event.index,
|
|
248
|
+
generationId: input.generationId,
|
|
249
|
+
inputId: input.inputId,
|
|
250
|
+
name: requested.name
|
|
251
|
+
}
|
|
252
|
+
}));
|
|
253
|
+
}
|
|
254
|
+
default: return state;
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
const aiCoordinatorReducer = (contract) => contract.reducer({
|
|
258
|
+
name: "a2.ai.coordinator.v1",
|
|
259
|
+
initialState: {
|
|
260
|
+
closed: false,
|
|
261
|
+
queued: []
|
|
262
|
+
}
|
|
263
|
+
}).fold((state, event) => foldCoordinator(state, event));
|
|
264
|
+
const continuationReady = (state) => {
|
|
265
|
+
const response = state.response;
|
|
266
|
+
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);
|
|
267
|
+
};
|
|
268
|
+
//#endregion
|
|
3
269
|
//#region src/ai-server.ts
|
|
4
270
|
/**
|
|
5
|
-
* a2/ai/server — the server-only implementation of an a2/ai definition.
|
|
271
|
+
* experimental-a2/ai/server — the server-only implementation of an experimental-a2/ai definition.
|
|
6
272
|
*
|
|
7
273
|
* `createHandlers()` exposes the ordinary A2 handler table;
|
|
8
274
|
* `createAgentServer()` is its batteries-included assembly with createServer.
|
|
9
275
|
*/
|
|
276
|
+
function validateAgentPush(context) {
|
|
277
|
+
const rejected = context.events.find((event) => {
|
|
278
|
+
if (event.type !== "ai.message.created") return !(event.type === "ai.approval.responded" || event.type === "ai.input.responded" || event.type === "ai.message.interrupted" || event.type === "ai.retry.requested");
|
|
279
|
+
const payload = event.payload;
|
|
280
|
+
return typeof payload !== "object" || payload === null || !("message" in payload) || typeof payload.message !== "object" || payload.message === null || !("role" in payload.message) || payload.message.role !== "user";
|
|
281
|
+
});
|
|
282
|
+
if (rejected !== void 0) throw new A2Error("INVALID_PAYLOAD", `event '${rejected.type}' is server-only for an AI agent`);
|
|
283
|
+
}
|
|
10
284
|
const resolve = async (value, context) => typeof value === "function" ? await value(context) : value;
|
|
11
285
|
const modelName = (model) => {
|
|
12
286
|
if (typeof model === "string") return model;
|
|
@@ -14,23 +288,8 @@ const modelName = (model) => {
|
|
|
14
288
|
return "custom";
|
|
15
289
|
};
|
|
16
290
|
const errorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
return {
|
|
20
|
-
inputTokens: addTokenCounts(left?.inputTokens, right.inputTokens),
|
|
21
|
-
inputTokenDetails: {
|
|
22
|
-
noCacheTokens: addTokenCounts(left?.inputTokenDetails.noCacheTokens, right.inputTokenDetails.noCacheTokens),
|
|
23
|
-
cacheReadTokens: addTokenCounts(left?.inputTokenDetails.cacheReadTokens, right.inputTokenDetails.cacheReadTokens),
|
|
24
|
-
cacheWriteTokens: addTokenCounts(left?.inputTokenDetails.cacheWriteTokens, right.inputTokenDetails.cacheWriteTokens)
|
|
25
|
-
},
|
|
26
|
-
outputTokens: addTokenCounts(left?.outputTokens, right.outputTokens),
|
|
27
|
-
outputTokenDetails: {
|
|
28
|
-
textTokens: addTokenCounts(left?.outputTokenDetails.textTokens, right.outputTokenDetails.textTokens),
|
|
29
|
-
reasoningTokens: addTokenCounts(left?.outputTokenDetails.reasoningTokens, right.outputTokenDetails.reasoningTokens)
|
|
30
|
-
},
|
|
31
|
-
totalTokens: addTokenCounts(left?.totalTokens, right.totalTokens)
|
|
32
|
-
};
|
|
33
|
-
};
|
|
291
|
+
const promptCacheKey = (sessionId, generationId) => `${sessionId}\u001f${generationId}`;
|
|
292
|
+
const isAsyncIterable = (value) => typeof value === "object" && value !== null && Symbol.asyncIterator in value && typeof value[Symbol.asyncIterator] === "function";
|
|
34
293
|
const isBoundaryChunk = (chunk) => chunk !== void 0 && (chunk.type === "text-end" || chunk.type === "reasoning-end" || chunk.type === "tool-input-available" || chunk.type === "tool-input-error" || chunk.type === "tool-output-available" || chunk.type === "tool-output-error" || chunk.type === "tool-output-denied" || chunk.type === "tool-approval-request" || chunk.type === "finish" || chunk.type === "abort" || chunk.type === "error");
|
|
35
294
|
const nextOrTimer = async (next, delayMs) => {
|
|
36
295
|
let timer;
|
|
@@ -47,41 +306,24 @@ const nextOrTimer = async (next, delayMs) => {
|
|
|
47
306
|
}
|
|
48
307
|
};
|
|
49
308
|
async function generateWithAISDK(context) {
|
|
50
|
-
const
|
|
51
|
-
const userOnStepEnd = onStepEnd ?? onStepFinish;
|
|
52
|
-
const agentSettings = {
|
|
53
|
-
...settings,
|
|
54
|
-
id: context.generationId,
|
|
309
|
+
const step = await generateAISDKStep({
|
|
55
310
|
model: context.model,
|
|
56
311
|
tools: context.tools,
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
312
|
+
messages: context.messages,
|
|
313
|
+
responseMessageId: context.responseMessageId,
|
|
314
|
+
abortSignal: context.signal,
|
|
315
|
+
...context.instructions === void 0 ? {} : { instructions: context.instructions },
|
|
316
|
+
settings: context.generation
|
|
317
|
+
});
|
|
62
318
|
return {
|
|
63
|
-
stream:
|
|
64
|
-
|
|
65
|
-
uiMessages: context.messages,
|
|
66
|
-
generateMessageId: () => context.responseMessageId,
|
|
67
|
-
abortSignal: context.signal,
|
|
68
|
-
async onStepEnd(step) {
|
|
69
|
-
usage = addUsage(usage, step.usage);
|
|
70
|
-
await userOnStepEnd?.(step);
|
|
71
|
-
},
|
|
72
|
-
onEnd(event) {
|
|
73
|
-
finishReason = event.finishReason;
|
|
74
|
-
}
|
|
75
|
-
}),
|
|
76
|
-
completion: () => ({
|
|
77
|
-
...finishReason === void 0 ? {} : { finishReason },
|
|
78
|
-
...usage === void 0 ? {} : { usage }
|
|
79
|
-
})
|
|
319
|
+
stream: step.stream,
|
|
320
|
+
completion: step.completion
|
|
80
321
|
};
|
|
81
322
|
}
|
|
82
323
|
async function* consumeGeneration(options) {
|
|
83
324
|
const pending = [];
|
|
84
325
|
let streamedFinishReason;
|
|
326
|
+
let streamedError;
|
|
85
327
|
const maxChunks = options.progress?.maxChunks ?? 16;
|
|
86
328
|
const maxDelayMs = options.progress?.maxDelayMs ?? 30;
|
|
87
329
|
let lastFlush = Date.now();
|
|
@@ -107,6 +349,7 @@ async function* consumeGeneration(options) {
|
|
|
107
349
|
const chunk = result.value;
|
|
108
350
|
pending.push(chunk);
|
|
109
351
|
if (chunk.type === "finish") streamedFinishReason = chunk.finishReason;
|
|
352
|
+
if (chunk.type === "error") streamedError = chunk.errorText;
|
|
110
353
|
pendingChunk = chunks.next();
|
|
111
354
|
if (pending.length >= maxChunks || isBoundaryChunk(pending.at(-1))) {
|
|
112
355
|
yield {
|
|
@@ -123,11 +366,21 @@ async function* consumeGeneration(options) {
|
|
|
123
366
|
};
|
|
124
367
|
throw error;
|
|
125
368
|
}
|
|
126
|
-
|
|
369
|
+
let completion;
|
|
370
|
+
try {
|
|
371
|
+
completion = options.source.completion?.();
|
|
372
|
+
} catch (error) {
|
|
373
|
+
if (pending.length > 0) yield {
|
|
374
|
+
type: "progress",
|
|
375
|
+
chunks: pending.splice(0)
|
|
376
|
+
};
|
|
377
|
+
throw error;
|
|
378
|
+
}
|
|
127
379
|
if (pending.length > 0) yield {
|
|
128
380
|
type: "progress",
|
|
129
381
|
chunks: pending.splice(0)
|
|
130
382
|
};
|
|
383
|
+
if (streamedError !== void 0) throw new Error(streamedError);
|
|
131
384
|
const finishReason = completion?.finishReason ?? streamedFinishReason;
|
|
132
385
|
yield {
|
|
133
386
|
type: "finish",
|
|
@@ -144,10 +397,45 @@ const contextMessages = (state) => {
|
|
|
144
397
|
const compaction = state.compaction;
|
|
145
398
|
if (compaction?.status !== "completed" || !compaction.messages) return state.messages;
|
|
146
399
|
const boundary = state.messages.findIndex((message) => message.id === compaction.throughMessageId);
|
|
147
|
-
|
|
400
|
+
const retained = new Set(compaction.retainedMessageIds ?? []);
|
|
401
|
+
return boundary === -1 ? state.messages : [
|
|
402
|
+
...compaction.messages,
|
|
403
|
+
...state.messages.slice(0, boundary + 1).filter((message) => retained.has(message.id)),
|
|
404
|
+
...state.messages.slice(boundary + 1)
|
|
405
|
+
];
|
|
406
|
+
};
|
|
407
|
+
const activeContextMessages = (state, coordinator) => {
|
|
408
|
+
const queued = new Set(coordinator.queued.map((item) => item.messageId));
|
|
409
|
+
return contextMessages(state).filter((message) => !queued.has(message.id));
|
|
410
|
+
};
|
|
411
|
+
const toolCalledEvent = (payload) => ({
|
|
412
|
+
type: "ai.tool.called",
|
|
413
|
+
id: `${payload.generationId}:tool:${payload.toolCallId}:called`,
|
|
414
|
+
payload
|
|
415
|
+
});
|
|
416
|
+
const approvalClassification = (tools, generation, toolName) => {
|
|
417
|
+
const configured = generation.toolApproval;
|
|
418
|
+
if (typeof configured === "function") return "unknown";
|
|
419
|
+
const policy = configured?.[toolName];
|
|
420
|
+
if (typeof policy === "function") return "unknown";
|
|
421
|
+
if (policy === "user-approval" || typeof policy === "object" && policy?.type === "user-approval") return "approval";
|
|
422
|
+
if (policy === "denied" || typeof policy === "object" && policy?.type === "denied") return "approval";
|
|
423
|
+
const tool = tools[toolName];
|
|
424
|
+
if (typeof tool?.needsApproval === "function") return "unknown";
|
|
425
|
+
return tool?.needsApproval === true ? "approval" : "automatic";
|
|
148
426
|
};
|
|
149
427
|
const lifecycleEvents = (options) => {
|
|
150
428
|
const result = [];
|
|
429
|
+
const pending = [...options.pending];
|
|
430
|
+
const take = (toolCallId) => {
|
|
431
|
+
const index = pending.findIndex((candidate) => candidate.call.toolCallId === toolCallId);
|
|
432
|
+
if (index === -1) return void 0;
|
|
433
|
+
return pending.splice(index, 1)[0];
|
|
434
|
+
};
|
|
435
|
+
const flush = (toolCallId) => {
|
|
436
|
+
const pendingCall = take(toolCallId);
|
|
437
|
+
if (pendingCall) result.push(toolCalledEvent(pendingCall.call));
|
|
438
|
+
};
|
|
151
439
|
for (const [chunkIndex, chunk] of options.chunks.entries()) {
|
|
152
440
|
const resultId = `${options.generationId}:tool-result:${options.sequence}:${chunkIndex}`;
|
|
153
441
|
if (chunk.type === "tool-input-available") {
|
|
@@ -160,14 +448,17 @@ const lifecycleEvents = (options) => {
|
|
|
160
448
|
input: chunk.input,
|
|
161
449
|
...chunk.dynamic === void 0 ? {} : { dynamic: chunk.dynamic },
|
|
162
450
|
...chunk.providerExecuted === void 0 ? {} : { providerExecuted: chunk.providerExecuted },
|
|
451
|
+
...options.tools[chunk.toolName]?.supportsDeferredResults === true ? { supportsDeferredResults: true } : {},
|
|
163
452
|
...chunk.providerMetadata === void 0 ? {} : { providerMetadata: chunk.providerMetadata },
|
|
164
453
|
...chunk.toolMetadata === void 0 ? {} : { toolMetadata: chunk.toolMetadata },
|
|
165
454
|
...chunk.title === void 0 ? {} : { title: chunk.title }
|
|
166
455
|
};
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
456
|
+
take(payload.toolCallId);
|
|
457
|
+
const classification = approvalClassification(options.tools, options.generation, chunk.toolName);
|
|
458
|
+
if (classification === "automatic") result.push(toolCalledEvent(payload));
|
|
459
|
+
else pending.push({
|
|
460
|
+
call: payload,
|
|
461
|
+
classification
|
|
171
462
|
});
|
|
172
463
|
} else if (chunk.type === "tool-input-error") {
|
|
173
464
|
const payload = {
|
|
@@ -184,12 +475,22 @@ const lifecycleEvents = (options) => {
|
|
|
184
475
|
...chunk.providerMetadata === void 0 ? {} : { providerMetadata: chunk.providerMetadata },
|
|
185
476
|
...chunk.toolMetadata === void 0 ? {} : { toolMetadata: chunk.toolMetadata }
|
|
186
477
|
};
|
|
187
|
-
|
|
478
|
+
const called = {
|
|
479
|
+
requestId: options.requestId,
|
|
480
|
+
messageId: options.messageId,
|
|
481
|
+
generationId: options.generationId,
|
|
482
|
+
toolCallId: chunk.toolCallId,
|
|
483
|
+
toolName: chunk.toolName,
|
|
484
|
+
input: chunk.input
|
|
485
|
+
};
|
|
486
|
+
take(chunk.toolCallId);
|
|
487
|
+
result.push(toolCalledEvent(called), {
|
|
188
488
|
type: "ai.tool.result",
|
|
189
489
|
id: resultId,
|
|
190
490
|
payload
|
|
191
491
|
});
|
|
192
492
|
} else if (chunk.type === "tool-output-available") {
|
|
493
|
+
flush(chunk.toolCallId);
|
|
193
494
|
const payload = {
|
|
194
495
|
requestId: options.requestId,
|
|
195
496
|
messageId: options.messageId,
|
|
@@ -209,6 +510,7 @@ const lifecycleEvents = (options) => {
|
|
|
209
510
|
payload
|
|
210
511
|
});
|
|
211
512
|
} else if (chunk.type === "tool-output-error") {
|
|
513
|
+
flush(chunk.toolCallId);
|
|
212
514
|
const payload = {
|
|
213
515
|
requestId: options.requestId,
|
|
214
516
|
messageId: options.messageId,
|
|
@@ -227,6 +529,7 @@ const lifecycleEvents = (options) => {
|
|
|
227
529
|
payload
|
|
228
530
|
});
|
|
229
531
|
} else if (chunk.type === "tool-output-denied") {
|
|
532
|
+
flush(chunk.toolCallId);
|
|
230
533
|
const payload = {
|
|
231
534
|
requestId: options.requestId,
|
|
232
535
|
messageId: options.messageId,
|
|
@@ -241,6 +544,7 @@ const lifecycleEvents = (options) => {
|
|
|
241
544
|
payload
|
|
242
545
|
});
|
|
243
546
|
} else if (chunk.type === "tool-approval-request") {
|
|
547
|
+
flush(chunk.toolCallId);
|
|
244
548
|
const payload = {
|
|
245
549
|
messageId: options.messageId,
|
|
246
550
|
generationId: options.generationId,
|
|
@@ -254,10 +558,52 @@ const lifecycleEvents = (options) => {
|
|
|
254
558
|
id: `${options.generationId}:approval:${chunk.approvalId}`,
|
|
255
559
|
payload
|
|
256
560
|
});
|
|
257
|
-
}
|
|
561
|
+
} else if (chunk.type === "tool-approval-response") result.push({
|
|
562
|
+
type: "ai.approval.responded",
|
|
563
|
+
id: `${options.generationId}:approval:${chunk.approvalId}:automatic-response`,
|
|
564
|
+
payload: {
|
|
565
|
+
messageId: options.messageId,
|
|
566
|
+
generationId: options.generationId,
|
|
567
|
+
approvalId: chunk.approvalId,
|
|
568
|
+
approved: chunk.approved,
|
|
569
|
+
...chunk.reason === void 0 ? {} : { reason: chunk.reason }
|
|
570
|
+
}
|
|
571
|
+
});
|
|
258
572
|
}
|
|
259
|
-
return
|
|
573
|
+
return {
|
|
574
|
+
events: result,
|
|
575
|
+
pending
|
|
576
|
+
};
|
|
260
577
|
};
|
|
578
|
+
function queuedMessagesAt(history) {
|
|
579
|
+
const started = new Set(history.filter((event) => event.type === "ai.generation.requested").map((event) => event.payload).filter((request) => request.reason === "message").map((request) => request.messageId));
|
|
580
|
+
return history.flatMap((event) => {
|
|
581
|
+
if (event.type !== "ai.message.created") return [];
|
|
582
|
+
const message = event.payload.message;
|
|
583
|
+
return message.role === "user" && !started.has(message.id) ? [{
|
|
584
|
+
index: event.index,
|
|
585
|
+
messageId: message.id
|
|
586
|
+
}] : [];
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
function withoutGenerationLifecycle(history, generationIds) {
|
|
590
|
+
if (generationIds.size === 0) return history;
|
|
591
|
+
const approvalKeys = /* @__PURE__ */ new Set();
|
|
592
|
+
const inputKeys = /* @__PURE__ */ new Set();
|
|
593
|
+
for (const event of history) {
|
|
594
|
+
const payload = event.payload;
|
|
595
|
+
if (typeof payload.generationId !== "string" || !generationIds.has(payload.generationId) || typeof payload.messageId !== "string") continue;
|
|
596
|
+
if (event.type === "ai.approval.requested" && typeof payload.approvalId === "string") approvalKeys.add(`${payload.messageId}\u001f${payload.approvalId}`);
|
|
597
|
+
if (event.type === "ai.input.requested" && typeof payload.inputId === "string") inputKeys.add(`${payload.messageId}\u001f${payload.inputId}`);
|
|
598
|
+
}
|
|
599
|
+
return history.filter((event) => {
|
|
600
|
+
const payload = event.payload;
|
|
601
|
+
if (typeof payload.generationId === "string" && generationIds.has(payload.generationId)) return false;
|
|
602
|
+
if (event.type === "ai.approval.responded" && typeof payload.messageId === "string" && typeof payload.approvalId === "string") return !approvalKeys.has(`${payload.messageId}\u001f${payload.approvalId}`);
|
|
603
|
+
if (event.type === "ai.input.responded" && typeof payload.messageId === "string" && typeof payload.inputId === "string") return !inputKeys.has(`${payload.messageId}\u001f${payload.inputId}`);
|
|
604
|
+
return true;
|
|
605
|
+
});
|
|
606
|
+
}
|
|
261
607
|
/**
|
|
262
608
|
* Build the ordinary A2 handler table for the built-in agent protocol.
|
|
263
609
|
* Application handlers can be spread beside this table.
|
|
@@ -266,11 +612,161 @@ function createHandlers(options) {
|
|
|
266
612
|
if (options.model === void 0) throw new TypeError("createHandlers requires model");
|
|
267
613
|
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");
|
|
268
614
|
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");
|
|
615
|
+
if (options.maxSteps !== void 0 && (!Number.isInteger(options.maxSteps) || options.maxSteps < 1)) throw new TypeError("maxSteps must be a positive integer");
|
|
616
|
+
const tools = options.tools ?? {};
|
|
617
|
+
const generation = options.generation ?? {};
|
|
618
|
+
const maxSteps = options.maxSteps ?? 20;
|
|
619
|
+
const coordinator = aiCoordinatorReducer(options.agent.contract);
|
|
620
|
+
const promptCache = /* @__PURE__ */ new Map();
|
|
621
|
+
const coordinatorStateAt = (history, frontier = Number.POSITIVE_INFINITY) => {
|
|
622
|
+
let state = coordinator.initialState;
|
|
623
|
+
for (const event of history) {
|
|
624
|
+
if (event.index >= frontier) break;
|
|
625
|
+
state = coordinator.fold(state, event);
|
|
626
|
+
}
|
|
627
|
+
return state;
|
|
628
|
+
};
|
|
629
|
+
const requestForNextMessage = (state) => {
|
|
630
|
+
if (state.closed || state.response !== void 0) return void 0;
|
|
631
|
+
const next = state.queued[0];
|
|
632
|
+
if (next === void 0) return void 0;
|
|
633
|
+
return {
|
|
634
|
+
type: "ai.generation.requested",
|
|
635
|
+
id: `ai.generate:message:${next.messageId}`,
|
|
636
|
+
payload: {
|
|
637
|
+
messageId: next.messageId,
|
|
638
|
+
reason: "message"
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
};
|
|
642
|
+
const scheduleNext = async (ctx) => requestForNextMessage((await ctx.session.state(coordinator)).state);
|
|
643
|
+
const continueIfReady = async (ctx, generationId) => {
|
|
644
|
+
const state = (await ctx.session.state(coordinator)).state;
|
|
645
|
+
const response = state.response;
|
|
646
|
+
if (response?.generation?.generationId !== generationId) return;
|
|
647
|
+
const input = response.inputResponse;
|
|
648
|
+
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)) {
|
|
649
|
+
await ctx.session.append("continue-after-input", {
|
|
650
|
+
type: "ai.generation.requested",
|
|
651
|
+
id: `ai.generate:input:${encodeURIComponent(response.responseMessageId)}:${encodeURIComponent(input.generationId)}:${encodeURIComponent(input.inputId)}`,
|
|
652
|
+
payload: {
|
|
653
|
+
messageId: response.responseMessageId,
|
|
654
|
+
responseMessageId: response.responseMessageId,
|
|
655
|
+
reason: "input"
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
if (!continuationReady(state)) return;
|
|
661
|
+
await ctx.session.append("continue-after-tools", {
|
|
662
|
+
type: "ai.generation.requested",
|
|
663
|
+
id: `ai.generate:tools:${generationId}`,
|
|
664
|
+
payload: {
|
|
665
|
+
messageId: response.responseMessageId,
|
|
666
|
+
responseMessageId: response.responseMessageId,
|
|
667
|
+
reason: "tool"
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
};
|
|
671
|
+
const resultEvent = (call, suffix, result) => ({
|
|
672
|
+
type: "ai.tool.result",
|
|
673
|
+
id: `${call.generationId}:tool:${call.toolCallId}:${suffix}`,
|
|
674
|
+
payload: {
|
|
675
|
+
requestId: call.requestId,
|
|
676
|
+
messageId: call.messageId,
|
|
677
|
+
generationId: call.generationId,
|
|
678
|
+
toolCallId: call.toolCallId,
|
|
679
|
+
toolName: call.toolName,
|
|
680
|
+
input: call.input,
|
|
681
|
+
phase: "execution",
|
|
682
|
+
...result.output === void 0 ? {} : { output: result.output },
|
|
683
|
+
...result.error === void 0 ? {} : { error: result.error },
|
|
684
|
+
...result.denied === void 0 ? {} : { denied: result.denied },
|
|
685
|
+
...result.preliminary === void 0 ? {} : { preliminary: result.preliminary },
|
|
686
|
+
...call.dynamic === void 0 ? {} : { dynamic: call.dynamic },
|
|
687
|
+
...call.providerExecuted === void 0 ? {} : { providerExecuted: call.providerExecuted },
|
|
688
|
+
...call.toolMetadata === void 0 ? {} : { toolMetadata: call.toolMetadata }
|
|
689
|
+
}
|
|
690
|
+
});
|
|
691
|
+
const promptMessages = (sessionId, call, readHistory) => {
|
|
692
|
+
const key = promptCacheKey(sessionId, call.generationId);
|
|
693
|
+
const cached = promptCache.get(key);
|
|
694
|
+
if (cached) return cached;
|
|
695
|
+
const computation = (async () => {
|
|
696
|
+
const history = await readHistory();
|
|
697
|
+
const coordinatorState = coordinatorStateAt(history);
|
|
698
|
+
const compaction = history.find((event) => event.type === "ai.compaction.completed" && event.payload.generationId === call.generationId);
|
|
699
|
+
if (compaction !== void 0) {
|
|
700
|
+
const messages = compaction.payload.messages.filter((message) => !coordinatorState.queued.some((queued) => queued.messageId === message.id));
|
|
701
|
+
return convertToModelMessages(messages, { tools });
|
|
702
|
+
}
|
|
703
|
+
const frontier = history.find((event) => event.type === "ai.generation.started" && event.payload.generationId === call.generationId)?.index ?? Number.POSITIVE_INFINITY;
|
|
704
|
+
const state = replay(options.agent, history.filter((event) => event.index < frontier));
|
|
705
|
+
return convertToModelMessages(activeContextMessages(state, coordinatorState), { tools });
|
|
706
|
+
})();
|
|
707
|
+
promptCache.set(key, computation);
|
|
708
|
+
computation.catch(() => {
|
|
709
|
+
if (promptCache.get(key) === computation) promptCache.delete(key);
|
|
710
|
+
});
|
|
711
|
+
return computation;
|
|
712
|
+
};
|
|
713
|
+
const executeTool = async (ctx, call) => {
|
|
714
|
+
const tool = tools[call.toolName];
|
|
715
|
+
if (tool?.execute === void 0) return resultEvent(call, "execution:error", { error: `Tool '${call.toolName}' has no server executor` });
|
|
716
|
+
const messages = await promptMessages(ctx.event.sessionId, call, ctx.session.history);
|
|
717
|
+
let output;
|
|
718
|
+
try {
|
|
719
|
+
output = await tool.execute(call.input, {
|
|
720
|
+
toolCallId: call.toolCallId,
|
|
721
|
+
messages,
|
|
722
|
+
abortSignal: ctx.signal,
|
|
723
|
+
context: void 0
|
|
724
|
+
});
|
|
725
|
+
} catch (error) {
|
|
726
|
+
if (ctx.signal.aborted) return;
|
|
727
|
+
return resultEvent(call, "execution:error", { error: errorMessage(error) });
|
|
728
|
+
}
|
|
729
|
+
if (!isAsyncIterable(output)) return resultEvent(call, "execution:0", { output });
|
|
730
|
+
let last;
|
|
731
|
+
let sequence = 0;
|
|
732
|
+
for await (const value of output) {
|
|
733
|
+
if (ctx.signal.aborted) return;
|
|
734
|
+
last = value;
|
|
735
|
+
await ctx.session.append(`tool:${call.toolCallId}:preliminary:${sequence}`, resultEvent(call, `execution:${sequence}:preliminary`, {
|
|
736
|
+
output: value,
|
|
737
|
+
preliminary: true
|
|
738
|
+
}));
|
|
739
|
+
sequence += 1;
|
|
740
|
+
}
|
|
741
|
+
return resultEvent(call, `execution:${sequence}:final`, sequence === 0 ? {} : { output: last });
|
|
742
|
+
};
|
|
743
|
+
const handleToolCall = async (ctx) => {
|
|
744
|
+
const response = (await ctx.session.state(coordinator)).state.response;
|
|
745
|
+
const current = response?.calls.find((candidate) => candidate.index === ctx.event.index);
|
|
746
|
+
if (current === void 0 || response?.generation?.generationId !== ctx.event.payload.generationId || response.failure !== void 0 || current.terminal || current.approval !== void 0) return;
|
|
747
|
+
if (current.call.providerExecuted === true) {
|
|
748
|
+
await continueIfReady(ctx, current.call.generationId);
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
return executeTool(ctx, current.call);
|
|
752
|
+
};
|
|
753
|
+
const handleApproval = async (ctx) => {
|
|
754
|
+
const response = (await ctx.session.state(coordinator)).state.response;
|
|
755
|
+
const current = response?.calls.find((candidate) => candidate.approval?.approvalId === ctx.event.payload.approvalId && candidate.approval.messageId === ctx.event.payload.messageId && candidate.approval.generationId === ctx.event.payload.generationId && candidate.responseIndex === ctx.event.index);
|
|
756
|
+
if (current === void 0 || response?.generation?.generationId !== current.call.generationId || response.failure !== void 0 || current.terminal) return;
|
|
757
|
+
if (current.call.providerExecuted === true) {
|
|
758
|
+
await continueIfReady(ctx, current.call.generationId);
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
if (!ctx.event.payload.approved) return resultEvent(current.call, "execution:denied", { denied: true });
|
|
762
|
+
return executeTool(ctx, current.call);
|
|
763
|
+
};
|
|
269
764
|
const generationHandler = async (ctx) => {
|
|
270
|
-
const history = await ctx.history();
|
|
765
|
+
const history = await ctx.session.history();
|
|
271
766
|
const requestId = ctx.event.id;
|
|
272
767
|
const request = ctx.event.payload;
|
|
273
|
-
|
|
768
|
+
const coordinatorState = (await ctx.session.state(coordinator)).state;
|
|
769
|
+
if (coordinatorState.closed || coordinatorState.response?.activeRequestId !== requestId) return;
|
|
274
770
|
if (history.some((event) => {
|
|
275
771
|
if (event.type === "ai.generation.completed") return event.payload.requestId === requestId;
|
|
276
772
|
if (event.type !== "ai.generation.failed") return false;
|
|
@@ -278,12 +774,23 @@ function createHandlers(options) {
|
|
|
278
774
|
return payload.requestId === requestId && payload.superseded !== true;
|
|
279
775
|
})) return;
|
|
280
776
|
const previousStarts = history.filter((event) => event.type === "ai.generation.started" && event.payload.requestId === requestId);
|
|
281
|
-
|
|
777
|
+
if (previousStarts.map((event) => event.payload.attempt).some((priorAttempt) => priorAttempt >= ctx.attempt)) return;
|
|
778
|
+
const attempt = ctx.attempt;
|
|
282
779
|
const generationId = `${requestId}:generation:${attempt}`;
|
|
283
780
|
const responseMessageId = request.responseMessageId ?? (request.reason === "message" ? `${request.messageId}:assistant` : request.messageId);
|
|
284
|
-
const
|
|
781
|
+
const responseStepCount = history.filter((event) => event.type === "ai.generation.completed" && event.payload.responseMessageId === responseMessageId).length;
|
|
782
|
+
const sourceCoordinatorState = coordinatorStateAt(history, ctx.event.index);
|
|
783
|
+
if (request.reason === "tool") {
|
|
784
|
+
if (!requestId.startsWith("ai.generate:tools:")) return;
|
|
785
|
+
const sourceGenerationId = requestId.slice(18);
|
|
786
|
+
if (!continuationReady(sourceCoordinatorState) || sourceCoordinatorState.response?.generation?.generationId !== sourceGenerationId || sourceCoordinatorState.response.responseMessageId !== request.messageId || sourceCoordinatorState.response.responseMessageId !== request.responseMessageId) return;
|
|
787
|
+
}
|
|
788
|
+
const previous = previousStarts.filter((event) => event.payload.attempt < attempt).toSorted((left, right) => right.payload.attempt - left.payload.attempt)[0];
|
|
285
789
|
const incompleteId = previous ? previous.payload.generationId : void 0;
|
|
286
|
-
const
|
|
790
|
+
const replacedGenerationIds = /* @__PURE__ */ new Set();
|
|
791
|
+
if (incompleteId !== void 0) replacedGenerationIds.add(incompleteId);
|
|
792
|
+
if (request.reason === "retry" && sourceCoordinatorState.response?.failure?.generationId !== void 0) replacedGenerationIds.add(sourceCoordinatorState.response.failure.generationId);
|
|
793
|
+
const promptHistory = withoutGenerationLifecycle(history, replacedGenerationIds);
|
|
287
794
|
let state = replay(options.agent, promptHistory);
|
|
288
795
|
const resolverContext = {
|
|
289
796
|
event: ctx.event,
|
|
@@ -293,6 +800,23 @@ function createHandlers(options) {
|
|
|
293
800
|
};
|
|
294
801
|
const resolvedModel = await resolve(options.model, resolverContext);
|
|
295
802
|
if (resolvedModel === void 0) throw new TypeError("the model resolver returned undefined");
|
|
803
|
+
if (ctx.signal.aborted) return;
|
|
804
|
+
if (request.reason === "tool" && responseStepCount >= maxSteps) {
|
|
805
|
+
const source = sourceCoordinatorState.response?.generation;
|
|
806
|
+
if (source === void 0) return;
|
|
807
|
+
return {
|
|
808
|
+
type: "ai.generation.failed",
|
|
809
|
+
id: `${requestId}:step-limit`,
|
|
810
|
+
payload: {
|
|
811
|
+
requestId: source.requestId,
|
|
812
|
+
messageId: source.messageId,
|
|
813
|
+
generationId: source.generationId,
|
|
814
|
+
responseMessageId,
|
|
815
|
+
error: `agent exceeded the ${maxSteps}-step limit`,
|
|
816
|
+
stepLimit: true
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
}
|
|
296
820
|
const started = {
|
|
297
821
|
requestId,
|
|
298
822
|
messageId: request.messageId,
|
|
@@ -323,8 +847,14 @@ function createHandlers(options) {
|
|
|
323
847
|
id: generationId,
|
|
324
848
|
payload: started
|
|
325
849
|
});
|
|
326
|
-
await ctx.append(...startEvents);
|
|
327
|
-
|
|
850
|
+
await ctx.session.append("generation-start", ...startEvents);
|
|
851
|
+
const startedCoordinatorState = (await ctx.session.state(coordinator)).state;
|
|
852
|
+
if (startedCoordinatorState.response?.activeRequestId !== requestId || startedCoordinatorState.response.generation?.generationId !== generationId) return;
|
|
853
|
+
const promptCoordinatorState = {
|
|
854
|
+
...coordinatorStateAt(history),
|
|
855
|
+
queued: queuedMessagesAt(history)
|
|
856
|
+
};
|
|
857
|
+
let messages = activeContextMessages(state, promptCoordinatorState);
|
|
328
858
|
if (options.compaction) {
|
|
329
859
|
const compactionContext = {
|
|
330
860
|
...resolverContext,
|
|
@@ -332,7 +862,7 @@ function createHandlers(options) {
|
|
|
332
862
|
};
|
|
333
863
|
if (await options.compaction.shouldCompact(compactionContext)) {
|
|
334
864
|
const throughMessageId = messages.at(-1)?.id ?? request.messageId;
|
|
335
|
-
await ctx.append({
|
|
865
|
+
await ctx.session.append("compaction-requested", {
|
|
336
866
|
type: "ai.compaction.requested",
|
|
337
867
|
id: `${generationId}:compaction:requested`,
|
|
338
868
|
payload: {
|
|
@@ -344,9 +874,10 @@ function createHandlers(options) {
|
|
|
344
874
|
const completed = {
|
|
345
875
|
generationId,
|
|
346
876
|
throughMessageId,
|
|
347
|
-
messages
|
|
877
|
+
messages,
|
|
878
|
+
...promptCoordinatorState.queued.length === 0 ? {} : { retainedMessageIds: promptCoordinatorState.queued.map((item) => item.messageId) }
|
|
348
879
|
};
|
|
349
|
-
await ctx.append({
|
|
880
|
+
await ctx.session.append("compaction-completed", {
|
|
350
881
|
type: "ai.compaction.completed",
|
|
351
882
|
id: `${generationId}:compaction:completed`,
|
|
352
883
|
payload: completed
|
|
@@ -360,6 +891,11 @@ function createHandlers(options) {
|
|
|
360
891
|
};
|
|
361
892
|
}
|
|
362
893
|
}
|
|
894
|
+
const currentState = (await ctx.session.state(options.agent.reducer)).state;
|
|
895
|
+
const currentCoordinatorState = (await ctx.session.state(coordinator)).state;
|
|
896
|
+
const generationMessages = activeContextMessages(currentState, currentCoordinatorState);
|
|
897
|
+
promptCache.set(promptCacheKey(ctx.event.sessionId, generationId), convertToModelMessages(generationMessages, { tools }));
|
|
898
|
+
let pendingToolCalls = [];
|
|
363
899
|
try {
|
|
364
900
|
const resolvedInstructions = options.instructions === void 0 ? void 0 : await resolve(options.instructions, resolverContext);
|
|
365
901
|
const generateContext = {
|
|
@@ -367,15 +903,16 @@ function createHandlers(options) {
|
|
|
367
903
|
requestId,
|
|
368
904
|
generationId,
|
|
369
905
|
responseMessageId,
|
|
370
|
-
messages,
|
|
371
|
-
state,
|
|
906
|
+
messages: generationMessages,
|
|
907
|
+
state: currentState,
|
|
372
908
|
history,
|
|
373
909
|
signal: ctx.signal,
|
|
374
910
|
model: resolvedModel,
|
|
375
|
-
tools
|
|
911
|
+
tools,
|
|
376
912
|
...resolvedInstructions === void 0 ? {} : { instructions: resolvedInstructions },
|
|
377
913
|
generation: options.generation ?? {}
|
|
378
914
|
};
|
|
915
|
+
const custom = options.generate !== void 0;
|
|
379
916
|
const updates = consumeGeneration({
|
|
380
917
|
source: options.generate === void 0 ? await generateWithAISDK(generateContext) : { stream: await options.generate(generateContext) },
|
|
381
918
|
...options.progress === void 0 ? {} : { progress: options.progress }
|
|
@@ -396,18 +933,23 @@ function createHandlers(options) {
|
|
|
396
933
|
sequence,
|
|
397
934
|
chunks: update.chunks
|
|
398
935
|
};
|
|
399
|
-
const
|
|
936
|
+
const lifecycle = lifecycleEvents({
|
|
400
937
|
requestId,
|
|
401
938
|
messageId: responseMessageId,
|
|
402
939
|
generationId,
|
|
403
940
|
sequence,
|
|
404
|
-
chunks: update.chunks
|
|
941
|
+
chunks: update.chunks,
|
|
942
|
+
pending: pendingToolCalls,
|
|
943
|
+
tools,
|
|
944
|
+
generation,
|
|
945
|
+
custom
|
|
405
946
|
});
|
|
406
|
-
|
|
947
|
+
pendingToolCalls = lifecycle.pending;
|
|
948
|
+
await ctx.session.append(`generation-progress:${sequence}`, {
|
|
407
949
|
type: "ai.generation.progress",
|
|
408
950
|
id: `${generationId}:progress:${sequence}`,
|
|
409
951
|
payload: progress
|
|
410
|
-
}, ...
|
|
952
|
+
}, ...lifecycle.events);
|
|
411
953
|
sequence += 1;
|
|
412
954
|
}
|
|
413
955
|
if (!finish) throw new Error("agent generation finished without output");
|
|
@@ -419,29 +961,37 @@ function createHandlers(options) {
|
|
|
419
961
|
...finish.finishReason === void 0 ? {} : { finishReason: finish.finishReason },
|
|
420
962
|
...finish.usage === void 0 ? {} : { usage: finish.usage }
|
|
421
963
|
};
|
|
422
|
-
const
|
|
423
|
-
await ctx.append({
|
|
964
|
+
const completionEvent = {
|
|
424
965
|
type: "ai.generation.completed",
|
|
425
966
|
id: `${generationId}:completed`,
|
|
426
967
|
payload: completed
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
968
|
+
};
|
|
969
|
+
const unresolved = options.generate ? pendingToolCalls.filter((pending) => pending.classification !== "automatic") : [];
|
|
970
|
+
if (unresolved.length > 0) throw new Error(`generation ended with unresolved tool authorization for '${unresolved[0].call.toolName}'`);
|
|
971
|
+
const pendingEvents = pendingToolCalls.filter((pending) => pending.classification === "automatic" || options.generate === void 0 && pending.classification === "unknown").map((pending) => toolCalledEvent(pending.call));
|
|
972
|
+
if (finish.finishReason === "tool-calls") return [...pendingEvents, completionEvent];
|
|
973
|
+
return [
|
|
974
|
+
...pendingEvents,
|
|
975
|
+
completionEvent,
|
|
976
|
+
{
|
|
977
|
+
type: "ai.message.completed",
|
|
978
|
+
id: `${generationId}:message:completed`,
|
|
979
|
+
payload: { messageId: responseMessageId }
|
|
980
|
+
}
|
|
981
|
+
];
|
|
432
982
|
} catch (error) {
|
|
983
|
+
if (error instanceof A2Error) throw error;
|
|
433
984
|
if (ctx.signal.aborted) {
|
|
434
985
|
const interrupted = {
|
|
435
986
|
messageId: responseMessageId,
|
|
436
987
|
generationId,
|
|
437
988
|
reason: "aborted"
|
|
438
989
|
};
|
|
439
|
-
|
|
990
|
+
return {
|
|
440
991
|
type: "ai.message.interrupted",
|
|
441
992
|
id: `${generationId}:interrupted`,
|
|
442
993
|
payload: interrupted
|
|
443
|
-
}
|
|
444
|
-
return;
|
|
994
|
+
};
|
|
445
995
|
}
|
|
446
996
|
const failed = {
|
|
447
997
|
requestId,
|
|
@@ -450,33 +1000,116 @@ function createHandlers(options) {
|
|
|
450
1000
|
responseMessageId,
|
|
451
1001
|
error: errorMessage(error)
|
|
452
1002
|
};
|
|
453
|
-
|
|
1003
|
+
return {
|
|
454
1004
|
type: "ai.generation.failed",
|
|
455
1005
|
id: `${generationId}:failed`,
|
|
456
1006
|
payload: failed
|
|
457
|
-
}
|
|
1007
|
+
};
|
|
458
1008
|
}
|
|
459
1009
|
};
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
1010
|
+
const handleMessageCreated = async (ctx) => {
|
|
1011
|
+
if (ctx.event.payload.message.role !== "user") return;
|
|
1012
|
+
return scheduleNext(ctx);
|
|
1013
|
+
};
|
|
1014
|
+
const handleRetry = async (ctx) => {
|
|
1015
|
+
const response = (await ctx.session.state(coordinator)).state.response;
|
|
1016
|
+
if (response?.status !== "failed" || response.rootMessageId !== ctx.event.payload.messageId || response.responseMessageId !== ctx.event.payload.responseMessageId) return;
|
|
1017
|
+
return {
|
|
1018
|
+
type: "ai.generation.requested",
|
|
1019
|
+
id: `ai.generate:retry:${ctx.event.payload.retryId}`,
|
|
1020
|
+
payload: {
|
|
1021
|
+
messageId: response.rootMessageId,
|
|
1022
|
+
responseMessageId: response.responseMessageId,
|
|
1023
|
+
reason: "retry"
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
};
|
|
1027
|
+
const handleInputResponse = async (ctx) => {
|
|
1028
|
+
const response = (await ctx.session.state(coordinator)).state.response;
|
|
1029
|
+
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;
|
|
1030
|
+
await continueIfReady(ctx, ctx.event.payload.generationId);
|
|
1031
|
+
};
|
|
1032
|
+
const clearPromptCache = (sessionId) => {
|
|
1033
|
+
const prefix = `${sessionId}\u001f`;
|
|
1034
|
+
for (const key of promptCache.keys()) if (key.startsWith(prefix)) promptCache.delete(key);
|
|
1035
|
+
};
|
|
1036
|
+
const handleResponseEnded = async (ctx) => {
|
|
1037
|
+
clearPromptCache(ctx.event.sessionId);
|
|
1038
|
+
return scheduleNext(ctx);
|
|
1039
|
+
};
|
|
1040
|
+
return {
|
|
1041
|
+
"ai.message.created": {
|
|
1042
|
+
lane: "a2.ai.turn",
|
|
1043
|
+
handler: handleMessageCreated
|
|
1044
|
+
},
|
|
1045
|
+
"ai.retry.requested": {
|
|
1046
|
+
lane: "a2.ai.turn",
|
|
1047
|
+
handler: handleRetry
|
|
1048
|
+
},
|
|
1049
|
+
"ai.input.responded": {
|
|
1050
|
+
lane: "a2.ai.turn",
|
|
1051
|
+
handler: handleInputResponse
|
|
1052
|
+
},
|
|
1053
|
+
"ai.message.completed": {
|
|
1054
|
+
lane: "a2.ai.turn",
|
|
1055
|
+
handler: handleResponseEnded
|
|
1056
|
+
},
|
|
1057
|
+
"ai.message.interrupted": {
|
|
1058
|
+
lane: "a2.ai.turn",
|
|
1059
|
+
handler: handleResponseEnded
|
|
1060
|
+
},
|
|
1061
|
+
"ai.session.closed": { handler: (ctx) => {
|
|
1062
|
+
clearPromptCache(ctx.event.sessionId);
|
|
1063
|
+
return Promise.resolve();
|
|
1064
|
+
} },
|
|
1065
|
+
"ai.generation.failed": { handler: (ctx) => {
|
|
1066
|
+
promptCache.delete(promptCacheKey(ctx.event.sessionId, ctx.event.payload.generationId));
|
|
1067
|
+
return Promise.resolve();
|
|
1068
|
+
} },
|
|
1069
|
+
"ai.generation.requested": {
|
|
1070
|
+
lane: "a2.ai.turn",
|
|
1071
|
+
abortOn: {
|
|
1072
|
+
"ai.message.interrupted": (event, trigger, context) => {
|
|
1073
|
+
const responseMessageId = trigger.payload.responseMessageId ?? (trigger.payload.reason === "message" ? `${trigger.payload.messageId}:assistant` : trigger.payload.messageId);
|
|
1074
|
+
return event.payload.messageId === responseMessageId && (event.payload.generationId === void 0 || event.payload.generationId === `${trigger.id}:generation:${context.attempt}`);
|
|
1075
|
+
},
|
|
1076
|
+
"ai.session.closed": true
|
|
465
1077
|
},
|
|
466
|
-
|
|
1078
|
+
handler: generationHandler
|
|
467
1079
|
},
|
|
468
|
-
handler:
|
|
469
|
-
|
|
1080
|
+
"ai.generation.completed": { handler: async (ctx) => continueIfReady(ctx, ctx.event.payload.generationId) },
|
|
1081
|
+
"ai.tool.called": {
|
|
1082
|
+
abortOn: {
|
|
1083
|
+
"ai.generation.failed": (event, trigger) => event.payload.generationId === trigger.payload.generationId,
|
|
1084
|
+
"ai.message.interrupted": (event, trigger) => event.payload.messageId === trigger.payload.messageId && (event.payload.generationId === void 0 || event.payload.generationId === trigger.payload.generationId),
|
|
1085
|
+
"ai.session.closed": true
|
|
1086
|
+
},
|
|
1087
|
+
handler: handleToolCall
|
|
1088
|
+
},
|
|
1089
|
+
"ai.approval.responded": {
|
|
1090
|
+
abortOn: {
|
|
1091
|
+
"ai.generation.failed": (event, trigger) => event.payload.responseMessageId === trigger.payload.messageId && event.payload.generationId === trigger.payload.generationId,
|
|
1092
|
+
"ai.message.interrupted": (event, trigger) => event.payload.messageId === trigger.payload.messageId && (event.payload.generationId === void 0 || event.payload.generationId === trigger.payload.generationId),
|
|
1093
|
+
"ai.session.closed": true
|
|
1094
|
+
},
|
|
1095
|
+
handler: handleApproval
|
|
1096
|
+
},
|
|
1097
|
+
"ai.tool.result": { handler: async (ctx) => {
|
|
1098
|
+
if (ctx.event.payload.preliminary === true) return;
|
|
1099
|
+
await continueIfReady(ctx, ctx.event.payload.generationId);
|
|
1100
|
+
} }
|
|
1101
|
+
};
|
|
470
1102
|
}
|
|
471
1103
|
/** Assemble an A2 server with the built-in agent handlers and app extensions. */
|
|
472
1104
|
function createAgentServer(options) {
|
|
473
|
-
const { agent: definition, model, tools, instructions, generation, generate, compaction, progress, handlers, ...serverOptions } = options;
|
|
1105
|
+
const { agent: definition, model, tools, instructions, generation, maxSteps, generate, compaction, progress, handlers, ...serverOptions } = options;
|
|
474
1106
|
const builtIns = createHandlers({
|
|
475
1107
|
agent: definition,
|
|
476
1108
|
model,
|
|
477
1109
|
...tools === void 0 ? {} : { tools },
|
|
478
1110
|
...instructions === void 0 ? {} : { instructions },
|
|
479
1111
|
...generation === void 0 ? {} : { generation },
|
|
1112
|
+
...maxSteps === void 0 ? {} : { maxSteps },
|
|
480
1113
|
...generate === void 0 ? {} : { generate },
|
|
481
1114
|
...compaction === void 0 ? {} : { compaction },
|
|
482
1115
|
...progress === void 0 ? {} : { progress }
|
|
@@ -487,8 +1120,9 @@ function createAgentServer(options) {
|
|
|
487
1120
|
handlers: {
|
|
488
1121
|
...builtIns,
|
|
489
1122
|
...handlers
|
|
490
|
-
}
|
|
1123
|
+
},
|
|
1124
|
+
validatePush: validateAgentPush
|
|
491
1125
|
});
|
|
492
1126
|
}
|
|
493
1127
|
//#endregion
|
|
494
|
-
export { createAgentServer, createHandlers };
|
|
1128
|
+
export { createAgentServer, createHandlers, validateAgentPush };
|