experimental-a2 0.13.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 +34 -0
- package/dist/{actor-DJi3RsNu.d.ts → actor-BfQSE0KC.d.ts} +4 -4
- package/dist/{actor-DJi3RsNu.d.ts.map → actor-BfQSE0KC.d.ts.map} +1 -1
- package/dist/actor-client.d.ts +1 -1
- package/dist/actor-client.js +1 -1
- package/dist/actor-react.d.ts +3 -3
- package/dist/actor-react.js +2 -2
- package/dist/{actor-shared-DI7J5upy.js → actor-shared-B5tJfzt-.js} +2 -2
- package/dist/{actor-shared-DI7J5upy.js.map → actor-shared-B5tJfzt-.js.map} +1 -1
- package/dist/actor.d.ts +1 -1
- package/dist/actor.js +3 -3
- package/dist/ai-Cai-lCbj.d.ts +580 -0
- package/dist/ai-Cai-lCbj.d.ts.map +1 -0
- package/dist/ai-control-CcD4hh3y.js +119 -0
- package/dist/ai-control-CcD4hh3y.js.map +1 -0
- package/dist/ai-server.d.ts +6 -6
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +1014 -501
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.d.ts +2 -365
- package/dist/ai.js +801 -80
- package/dist/ai.js.map +1 -1
- package/dist/{client-P_NNNRM-.d.ts → client-BAEABRZB.d.ts} +2 -2
- package/dist/{client-P_NNNRM-.d.ts.map → client-BAEABRZB.d.ts.map} +1 -1
- package/dist/{client-Bf6uSEAk.js → client-BYzHjkwU.js} +21 -6
- package/dist/client-BYzHjkwU.js.map +1 -0
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/{contract-48bUMgcL.js → contract-CKRg_E4q.js} +3 -26
- package/dist/contract-CKRg_E4q.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/react.d.ts +2 -2
- package/dist/react.js +1 -1
- package/dist/{reducer-DJKWm3cp.d.ts → reducer-BcS9VDKC.d.ts} +4 -1
- package/dist/{reducer-DJKWm3cp.d.ts.map → reducer-BcS9VDKC.d.ts.map} +1 -1
- package/dist/reducer-DEMjEY_O.js +29 -0
- package/dist/reducer-DEMjEY_O.js.map +1 -0
- package/dist/scheduler-qstash.d.ts +2 -2
- package/dist/scheduler-qstash.js +1 -1
- package/dist/scheduler-vercel.d.ts +2 -2
- package/dist/scheduler-vercel.js +1 -1
- package/dist/{server-DjZZa1wr.d.ts → server-Bp5Nd1pF.d.ts} +3 -3
- package/dist/{server-DjZZa1wr.d.ts.map → server-Bp5Nd1pF.d.ts.map} +1 -1
- package/dist/{server-BeNADlCI.js → server-CjJSGcF7.js} +4 -3
- package/dist/server-CjJSGcF7.js.map +1 -0
- package/dist/server.d.ts +3 -3
- package/dist/server.js +1 -1
- package/dist/{store-DtDOWLSn.d.ts → store-D_yhNdPz.d.ts} +7 -2
- package/dist/{store-DtDOWLSn.d.ts.map → store-D_yhNdPz.d.ts.map} +1 -1
- package/dist/store-N8PXxDAS.js.map +1 -1
- package/dist/store-memory.d.ts +1 -1
- package/dist/store-postgres.d.ts +1 -1
- package/dist/store-postgres.js +19 -0
- package/dist/store-postgres.js.map +1 -1
- package/dist/store-redis-http.d.ts +1 -1
- package/dist/store-redis-http.js +1 -1
- package/dist/{store-redis-notify-BUCyXOn0.js → store-redis-notify-D2EI6gwX.js} +27 -2
- package/dist/store-redis-notify-D2EI6gwX.js.map +1 -0
- package/dist/store-redis.d.ts +1 -1
- package/dist/store-redis.js +1 -1
- package/dist/store-sqlite.d.ts +1 -1
- package/docs/guides/06-ai-agents.mdx +265 -62
- package/docs/reference/01-api.mdx +122 -27
- package/examples/playground/app/agent/[agentId]/agent-client.tsx +118 -21
- package/examples/playground/app/agent/[agentId]/agent-queue.tsx +289 -0
- package/examples/playground/app/agent/compaction-settings.test.ts +22 -6
- package/examples/playground/app/agent/model.ts +11 -2
- package/examples/playground/app/agent/server.ts +8 -2
- package/examples/playground/app/chat/[chatId]/chat-client.tsx +3 -13
- package/examples/playground/app/chat/model.ts +2 -2
- package/examples/playground/app/chat/server.ts +24 -17
- package/examples/playground/app/globals.css +179 -0
- package/examples/playground/package.json +1 -1
- package/package.json +1 -1
- package/src/ai-client-state.ts +185 -0
- package/src/ai-control-server.ts +829 -0
- package/src/ai-control-state.ts +152 -0
- package/src/ai-control.ts +139 -0
- package/src/ai-coordinator.ts +99 -32
- package/src/ai-progress-batches.ts +68 -0
- package/src/ai-projector.ts +76 -15
- package/src/ai-sdk-step.ts +0 -1
- package/src/ai-server.ts +381 -608
- package/src/ai.ts +553 -108
- package/src/client.ts +31 -9
- package/src/licenses/Apache-2.0.txt +55 -0
- package/src/parse-partial-json.ts +441 -0
- package/src/reducer.ts +6 -0
- package/src/server.ts +8 -4
- package/src/store-postgres.ts +27 -0
- package/src/store-redis-core.ts +53 -1
- package/src/store-redis-notify.ts +1 -0
- package/src/store.ts +6 -0
- package/dist/ai.d.ts.map +0 -1
- package/dist/client-Bf6uSEAk.js.map +0 -1
- package/dist/contract-48bUMgcL.js.map +0 -1
- package/dist/server-BeNADlCI.js.map +0 -1
- package/dist/store-redis-notify-BUCyXOn0.js.map +0 -1
package/dist/ai-server.js
CHANGED
|
@@ -1,150 +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
7
|
import { asSchema, 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 = input.modelMessages ?? 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
|
|
88
|
-
//#region src/ai-model-metadata.ts
|
|
89
|
-
const catalogUrl = "https://ai-gateway.vercel.sh/v1/models";
|
|
90
|
-
const catalogLifetimeMs = 36e5;
|
|
91
|
-
const catalogTimeoutMs = 5e3;
|
|
92
|
-
let catalog;
|
|
93
|
-
const gatewayModelId = (model) => {
|
|
94
|
-
if (typeof model === "string") return globalThis.AI_SDK_DEFAULT_PROVIDER === void 0 ? model : void 0;
|
|
95
|
-
return model.provider === "gateway" ? model.modelId : void 0;
|
|
96
|
-
};
|
|
97
|
-
const readCatalog = () => {
|
|
98
|
-
if (catalog && catalog.expiresAt > Date.now()) return catalog.promise;
|
|
99
|
-
const entry = {
|
|
100
|
-
expiresAt: Number.POSITIVE_INFINITY,
|
|
101
|
-
promise: (async () => {
|
|
102
|
-
const controller = new AbortController();
|
|
103
|
-
const timeout = setTimeout(() => controller.abort(/* @__PURE__ */ new Error("model catalog request timed out")), catalogTimeoutMs);
|
|
104
|
-
timeout.unref?.();
|
|
105
|
-
try {
|
|
106
|
-
const response = await fetch(catalogUrl, { signal: controller.signal });
|
|
107
|
-
if (!response.ok) throw new Error(`model catalog request failed: HTTP ${response.status}`);
|
|
108
|
-
const body = await response.json();
|
|
109
|
-
if (typeof body !== "object" || body === null || !("data" in body) || !Array.isArray(body.data)) throw new Error("model catalog response must contain a data array");
|
|
110
|
-
const models = /* @__PURE__ */ new Map();
|
|
111
|
-
for (const row of body.data) {
|
|
112
|
-
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;
|
|
113
|
-
models.set(row.id, {
|
|
114
|
-
contextWindow: row.context_window,
|
|
115
|
-
maxOutputTokens: row.max_tokens
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
return models;
|
|
119
|
-
} finally {
|
|
120
|
-
clearTimeout(timeout);
|
|
121
|
-
}
|
|
122
|
-
})()
|
|
123
|
-
};
|
|
124
|
-
catalog = entry;
|
|
125
|
-
entry.promise = entry.promise.then((models) => {
|
|
126
|
-
entry.expiresAt = Date.now() + catalogLifetimeMs;
|
|
127
|
-
return models;
|
|
128
|
-
}, (error) => {
|
|
129
|
-
if (catalog === entry) catalog = void 0;
|
|
130
|
-
throw error;
|
|
131
|
-
});
|
|
132
|
-
return entry.promise;
|
|
133
|
-
};
|
|
134
|
-
const readModelLimits = async (options) => {
|
|
135
|
-
options.signal.throwIfAborted();
|
|
136
|
-
let onAbort;
|
|
137
|
-
const aborted = new Promise((_resolve, reject) => {
|
|
138
|
-
onAbort = () => reject(options.signal.reason);
|
|
139
|
-
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
140
|
-
});
|
|
141
|
-
try {
|
|
142
|
-
return (await Promise.race([readCatalog(), aborted])).get(options.modelId) ?? null;
|
|
143
|
-
} finally {
|
|
144
|
-
options.signal.removeEventListener("abort", onAbort);
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
|
-
//#endregion
|
|
148
8
|
//#region src/ai-coordinator.ts
|
|
149
9
|
const updateResponse = (state, update) => state.response === void 0 ? state : {
|
|
150
10
|
...state,
|
|
@@ -156,6 +16,11 @@ const foldCoordinator = (state, event) => {
|
|
|
156
16
|
closed: true,
|
|
157
17
|
queued: []
|
|
158
18
|
};
|
|
19
|
+
case "ai.retry.requested":
|
|
20
|
+
case "ai.compaction.completed": {
|
|
21
|
+
const { calibration: _, ...rest } = state;
|
|
22
|
+
return rest;
|
|
23
|
+
}
|
|
159
24
|
case "ai.message.created": {
|
|
160
25
|
const payload = event.payload;
|
|
161
26
|
const message = payload.message;
|
|
@@ -163,24 +28,24 @@ const foldCoordinator = (state, event) => {
|
|
|
163
28
|
return {
|
|
164
29
|
...state,
|
|
165
30
|
queued: [...state.queued.filter((item) => item.messageId !== message.id), {
|
|
166
|
-
index: event.index,
|
|
167
31
|
messageId: message.id,
|
|
168
32
|
generate: payload.generate !== false
|
|
169
|
-
}]
|
|
33
|
+
}]
|
|
170
34
|
};
|
|
171
35
|
}
|
|
172
36
|
case "ai.generation.requested": {
|
|
173
37
|
const request = event.payload;
|
|
174
38
|
if (request.reason === "message") {
|
|
175
39
|
const responseMessageId = request.responseMessageId ?? `${request.messageId}:assistant`;
|
|
176
|
-
const requested = state.queued.
|
|
40
|
+
const requested = state.queued.findIndex((item) => item.messageId === request.messageId);
|
|
177
41
|
return {
|
|
178
42
|
...state,
|
|
179
|
-
queued: requested
|
|
43
|
+
queued: requested < 0 ? state.queued.filter((item) => item.messageId !== request.messageId) : state.queued.slice(requested + 1),
|
|
180
44
|
response: {
|
|
181
45
|
rootMessageId: request.messageId,
|
|
182
46
|
responseMessageId,
|
|
183
47
|
status: "requested",
|
|
48
|
+
stepCount: 0,
|
|
184
49
|
activeRequestId: event.id,
|
|
185
50
|
calls: [],
|
|
186
51
|
inputs: []
|
|
@@ -189,14 +54,25 @@ const foldCoordinator = (state, event) => {
|
|
|
189
54
|
}
|
|
190
55
|
if (state.response === void 0 || request.responseMessageId !== state.response.responseMessageId) return state;
|
|
191
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;
|
|
192
|
-
return updateResponse(state, (response) =>
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
+
});
|
|
200
76
|
}
|
|
201
77
|
case "ai.generation.started": {
|
|
202
78
|
const generation = event.payload;
|
|
@@ -205,7 +81,10 @@ const foldCoordinator = (state, event) => {
|
|
|
205
81
|
rootMessageId: response.rootMessageId,
|
|
206
82
|
responseMessageId: response.responseMessageId,
|
|
207
83
|
status: "generating",
|
|
84
|
+
stepCount: response.stepCount,
|
|
208
85
|
generation,
|
|
86
|
+
...generation.promptThroughIndex === void 0 ? {} : { promptThroughIndex: generation.promptThroughIndex },
|
|
87
|
+
...response.source === void 0 ? {} : { source: response.source },
|
|
209
88
|
activeRequestId: generation.requestId,
|
|
210
89
|
calls: [],
|
|
211
90
|
inputs: []
|
|
@@ -214,11 +93,22 @@ const foldCoordinator = (state, event) => {
|
|
|
214
93
|
case "ai.generation.completed": {
|
|
215
94
|
const completion = event.payload;
|
|
216
95
|
if (state.response?.generation?.generationId !== completion.generationId) return state;
|
|
217
|
-
|
|
96
|
+
const { calibration: _, ...uncalibrated } = updateResponse(state, (response) => ({
|
|
218
97
|
...response,
|
|
219
98
|
status: "waiting",
|
|
220
|
-
completion
|
|
99
|
+
completion,
|
|
100
|
+
stepCount: response.stepCount + (response.completion === void 0 ? 1 : 0)
|
|
221
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;
|
|
222
112
|
}
|
|
223
113
|
case "ai.generation.failed": {
|
|
224
114
|
const failure = event.payload;
|
|
@@ -234,18 +124,16 @@ const foldCoordinator = (state, event) => {
|
|
|
234
124
|
}
|
|
235
125
|
case "ai.message.completed": {
|
|
236
126
|
const messageId = event.payload.messageId;
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
} : state;
|
|
127
|
+
if (state.response?.responseMessageId !== messageId) return state;
|
|
128
|
+
const { response: _, ...rest } = state;
|
|
129
|
+
return rest;
|
|
241
130
|
}
|
|
242
131
|
case "ai.message.interrupted": {
|
|
243
132
|
const interruption = event.payload;
|
|
244
133
|
const response = state.response;
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
} : 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;
|
|
249
137
|
}
|
|
250
138
|
case "ai.tool.called": {
|
|
251
139
|
const call = event.payload;
|
|
@@ -253,7 +141,6 @@ const foldCoordinator = (state, event) => {
|
|
|
253
141
|
return updateResponse(state, (response) => ({
|
|
254
142
|
...response,
|
|
255
143
|
calls: [...response.calls.filter((candidate) => candidate.call.toolCallId !== call.toolCallId), {
|
|
256
|
-
index: event.index,
|
|
257
144
|
call,
|
|
258
145
|
terminal: false
|
|
259
146
|
}]
|
|
@@ -277,8 +164,7 @@ const foldCoordinator = (state, event) => {
|
|
|
277
164
|
...response,
|
|
278
165
|
calls: response.calls.map((candidate) => candidate.approval?.approvalId === approval.approvalId ? {
|
|
279
166
|
...candidate,
|
|
280
|
-
response: approval
|
|
281
|
-
responseIndex: event.index
|
|
167
|
+
response: approval
|
|
282
168
|
} : candidate)
|
|
283
169
|
}));
|
|
284
170
|
}
|
|
@@ -301,34 +187,701 @@ const foldCoordinator = (state, event) => {
|
|
|
301
187
|
inputs: [...response.inputs.filter((candidate) => candidate.messageId !== input.messageId || candidate.generationId !== input.generationId || candidate.inputId !== input.inputId), input]
|
|
302
188
|
}));
|
|
303
189
|
}
|
|
304
|
-
case "ai.input.responded": {
|
|
305
|
-
const input = event.payload;
|
|
306
|
-
const requested = state.response?.inputs.find((candidate) => candidate.messageId === input.messageId && candidate.generationId === input.generationId && candidate.inputId === input.inputId && candidate.name === input.name);
|
|
307
|
-
if (state.response?.responseMessageId !== input.messageId || state.response.generation?.generationId !== input.generationId || requested === void 0) return state;
|
|
308
|
-
return updateResponse(state, (response) => ({
|
|
309
|
-
...response,
|
|
310
|
-
inputs: response.inputs.filter((candidate) => candidate.inputId !== input.inputId),
|
|
311
|
-
inputResponse: {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
190
|
+
case "ai.input.responded": {
|
|
191
|
+
const input = event.payload;
|
|
192
|
+
const requested = state.response?.inputs.find((candidate) => candidate.messageId === input.messageId && candidate.generationId === input.generationId && candidate.inputId === input.inputId && candidate.name === input.name);
|
|
193
|
+
if (state.response?.responseMessageId !== input.messageId || state.response.generation?.generationId !== input.generationId || requested === void 0) return state;
|
|
194
|
+
return updateResponse(state, (response) => ({
|
|
195
|
+
...response,
|
|
196
|
+
inputs: response.inputs.filter((candidate) => candidate.inputId !== input.inputId),
|
|
197
|
+
inputResponse: {
|
|
198
|
+
generationId: input.generationId,
|
|
199
|
+
inputId: input.inputId,
|
|
200
|
+
name: requested.name
|
|
201
|
+
}
|
|
202
|
+
}));
|
|
203
|
+
}
|
|
204
|
+
default: return state;
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
const aiCoordinatorReducer = (contract) => contract.reducer({
|
|
208
|
+
name: "a2.ai.coordinator.v5",
|
|
209
|
+
initialState: {
|
|
210
|
+
closed: false,
|
|
211
|
+
queued: []
|
|
212
|
+
}
|
|
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);
|
|
217
|
+
const continuationReady = (state) => {
|
|
218
|
+
const response = state.response;
|
|
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);
|
|
220
|
+
};
|
|
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
|
+
});
|
|
316
856
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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;
|
|
321
872
|
};
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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);
|
|
327
884
|
}
|
|
328
|
-
}).fold((state, event) => foldCoordinator(state, event));
|
|
329
|
-
const continuationReady = (state) => {
|
|
330
|
-
const response = state.response;
|
|
331
|
-
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);
|
|
332
885
|
};
|
|
333
886
|
//#endregion
|
|
334
887
|
//#region src/ai-server.ts
|
|
@@ -340,9 +893,11 @@ const continuationReady = (state) => {
|
|
|
340
893
|
*/
|
|
341
894
|
function validateAgentIngress(context) {
|
|
342
895
|
const rejected = context.events.find((event) => {
|
|
343
|
-
if (event.type !== "ai.
|
|
344
|
-
const
|
|
345
|
-
|
|
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;
|
|
346
901
|
});
|
|
347
902
|
if (rejected !== void 0) throw new A2Error("INVALID_PAYLOAD", `event '${rejected.type}' is server-only for an AI agent`);
|
|
348
903
|
}
|
|
@@ -491,9 +1046,9 @@ async function* consumeGeneration(options) {
|
|
|
491
1046
|
...completion?.usage === void 0 ? {} : { usage: completion.usage }
|
|
492
1047
|
};
|
|
493
1048
|
}
|
|
494
|
-
const
|
|
495
|
-
let state =
|
|
496
|
-
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);
|
|
497
1052
|
return state;
|
|
498
1053
|
};
|
|
499
1054
|
const summarize = async (options) => {
|
|
@@ -558,35 +1113,50 @@ const activeCompaction = (state) => {
|
|
|
558
1113
|
const compaction = state.compaction;
|
|
559
1114
|
return compaction?.status === "completed" && compaction.messages !== void 0 && state.messages.some((message) => message.id === compaction.throughMessageId) ? compaction : null;
|
|
560
1115
|
};
|
|
561
|
-
const contextMessages = (options) => {
|
|
562
|
-
const { agent,
|
|
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
|
+
});
|
|
563
1124
|
const compaction = activeCompaction(state);
|
|
564
|
-
|
|
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);
|
|
565
1128
|
const retained = new Set(compaction.retainedMessageIds ?? []);
|
|
566
|
-
const
|
|
567
|
-
const throughIndex = compaction.throughIndex ?? (startedIndex === void 0 ? void 0 : startedIndex - 1);
|
|
1129
|
+
const throughIndex = compaction.throughIndex;
|
|
568
1130
|
if (throughIndex !== void 0) {
|
|
569
|
-
const prefix =
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
for (const
|
|
578
|
-
|
|
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)));
|
|
579
1153
|
}
|
|
580
1154
|
const boundary = state.messages.findIndex((message) => message.id === compaction.throughMessageId);
|
|
581
|
-
return [
|
|
1155
|
+
return visible([
|
|
582
1156
|
...compaction.messages,
|
|
583
1157
|
...state.messages.slice(0, boundary + 1).filter((message) => retained.has(message.id)),
|
|
584
1158
|
...state.messages.slice(boundary + 1)
|
|
585
|
-
];
|
|
586
|
-
};
|
|
587
|
-
const activeContextMessages = (options) => {
|
|
588
|
-
const queued = new Set(options.coordinator.queued.map((item) => item.messageId));
|
|
589
|
-
return contextMessages(options).filter((message) => !queued.has(message.id));
|
|
1159
|
+
]);
|
|
590
1160
|
};
|
|
591
1161
|
const modelContext = async (options) => {
|
|
592
1162
|
const messages = await convertToModelMessages(options.messages, { tools: options.tools });
|
|
@@ -609,22 +1179,8 @@ const estimateInputTokens = async (options) => {
|
|
|
609
1179
|
})).byteLength / 4);
|
|
610
1180
|
};
|
|
611
1181
|
const measuredInputTokens = (options) => {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
if (event.type !== "ai.generation.completed") continue;
|
|
615
|
-
const completed = event.payload;
|
|
616
|
-
const started = options.history.find((candidate) => candidate.type === "ai.generation.started" && candidate.payload.generationId === completed.generationId);
|
|
617
|
-
const input = completed.usage?.inputTokens;
|
|
618
|
-
const estimate = completed.inputTokenEstimate;
|
|
619
|
-
if ((started?.payload)?.model !== options.model) break;
|
|
620
|
-
if (input === void 0 || estimate === void 0 || !Number.isFinite(input) || input < 0 || options.estimate < estimate) break;
|
|
621
|
-
return Math.max(options.estimate, Math.ceil(input + options.estimate - estimate));
|
|
622
|
-
}
|
|
623
|
-
return options.estimate;
|
|
624
|
-
};
|
|
625
|
-
const generationRequestId = (generationId) => {
|
|
626
|
-
const markerIndex = generationId.lastIndexOf(":generation:");
|
|
627
|
-
return markerIndex === -1 ? void 0 : generationId.slice(0, markerIndex);
|
|
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;
|
|
628
1184
|
};
|
|
629
1185
|
const toolCalledEvent = (payload) => ({
|
|
630
1186
|
type: "ai.tool.called",
|
|
@@ -793,29 +1349,6 @@ const lifecycleEvents = (options) => {
|
|
|
793
1349
|
pending
|
|
794
1350
|
};
|
|
795
1351
|
};
|
|
796
|
-
function queuedMessagesAt(history) {
|
|
797
|
-
const queued = [];
|
|
798
|
-
for (const event of history) {
|
|
799
|
-
if (event.type === "ai.message.created") {
|
|
800
|
-
const payload = event.payload;
|
|
801
|
-
if (payload.message.role !== "user") continue;
|
|
802
|
-
const duplicate = queued.findIndex((item) => item.messageId === payload.message.id);
|
|
803
|
-
if (duplicate !== -1) queued.splice(duplicate, 1);
|
|
804
|
-
queued.push({
|
|
805
|
-
index: event.index,
|
|
806
|
-
messageId: payload.message.id,
|
|
807
|
-
generate: payload.generate !== false
|
|
808
|
-
});
|
|
809
|
-
continue;
|
|
810
|
-
}
|
|
811
|
-
if (event.type !== "ai.generation.requested") continue;
|
|
812
|
-
const request = event.payload;
|
|
813
|
-
if (request.reason !== "message") continue;
|
|
814
|
-
const requested = queued.findIndex((item) => item.messageId === request.messageId);
|
|
815
|
-
if (requested !== -1) queued.splice(0, requested + 1);
|
|
816
|
-
}
|
|
817
|
-
return queued;
|
|
818
|
-
}
|
|
819
1352
|
function withoutGenerationLifecycle(history, generationIds) {
|
|
820
1353
|
if (generationIds.size === 0) return history;
|
|
821
1354
|
const approvalKeys = /* @__PURE__ */ new Set();
|
|
@@ -839,7 +1372,7 @@ const validateCompaction = (options) => {
|
|
|
839
1372
|
if (compaction !== false && (typeof compaction !== "object" || compaction === null)) throw new TypeError("compaction must resolve to false or a policy object");
|
|
840
1373
|
if (compaction !== false && "then" in compaction) {
|
|
841
1374
|
Promise.resolve(compaction).catch(() => {});
|
|
842
|
-
throw new TypeError("compaction options must
|
|
1375
|
+
throw new TypeError("compaction options must be a policy or a resolver");
|
|
843
1376
|
}
|
|
844
1377
|
if (compaction !== false && !("shouldCompact" in compaction)) {
|
|
845
1378
|
if (compaction.thresholdTokens !== void 0 && (!Number.isSafeInteger(compaction.thresholdTokens) || compaction.thresholdTokens < 1)) throw new TypeError("compaction.thresholdTokens must be a positive safe integer");
|
|
@@ -871,58 +1404,9 @@ function createHandlers(options) {
|
|
|
871
1404
|
const tools = options.tools ?? {};
|
|
872
1405
|
const generation = options.generation ?? {};
|
|
873
1406
|
const maxSteps = options.maxSteps ?? Number.POSITIVE_INFINITY;
|
|
874
|
-
const
|
|
1407
|
+
const control = createControlRuntime({ agent: options.agent });
|
|
1408
|
+
const coordinator = control.coordinator;
|
|
875
1409
|
const promptCache = /* @__PURE__ */ new Map();
|
|
876
|
-
const coordinatorStateAt = (history, frontier = Number.POSITIVE_INFINITY) => {
|
|
877
|
-
let state = coordinator.initialState;
|
|
878
|
-
for (const event of history) {
|
|
879
|
-
if (event.index >= frontier) break;
|
|
880
|
-
state = coordinator.fold(state, event);
|
|
881
|
-
}
|
|
882
|
-
return state;
|
|
883
|
-
};
|
|
884
|
-
const requestForNextMessage = (state) => {
|
|
885
|
-
if (state.closed || state.response !== void 0) return void 0;
|
|
886
|
-
const next = state.queued.find((item) => item.generate !== false);
|
|
887
|
-
if (next === void 0) return void 0;
|
|
888
|
-
return {
|
|
889
|
-
type: "ai.generation.requested",
|
|
890
|
-
id: `ai.generate:message:${next.messageId}`,
|
|
891
|
-
payload: {
|
|
892
|
-
messageId: next.messageId,
|
|
893
|
-
reason: "message"
|
|
894
|
-
}
|
|
895
|
-
};
|
|
896
|
-
};
|
|
897
|
-
const scheduleNext = async (ctx) => requestForNextMessage((await ctx.session.state(coordinator, { through: "latest" })).state);
|
|
898
|
-
const continueIfReady = async (ctx, generationId) => {
|
|
899
|
-
const state = (await ctx.session.state(coordinator, { through: "latest" })).state;
|
|
900
|
-
const response = state.response;
|
|
901
|
-
if (response?.generation?.generationId !== generationId) return;
|
|
902
|
-
const input = response.inputResponse;
|
|
903
|
-
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)) {
|
|
904
|
-
await ctx.session.append("continue-after-input", {
|
|
905
|
-
type: "ai.generation.requested",
|
|
906
|
-
id: `ai.generate:input:${encodeURIComponent(response.responseMessageId)}:${encodeURIComponent(input.generationId)}:${encodeURIComponent(input.inputId)}`,
|
|
907
|
-
payload: {
|
|
908
|
-
messageId: response.responseMessageId,
|
|
909
|
-
responseMessageId: response.responseMessageId,
|
|
910
|
-
reason: "input"
|
|
911
|
-
}
|
|
912
|
-
});
|
|
913
|
-
return;
|
|
914
|
-
}
|
|
915
|
-
if (!continuationReady(state)) return;
|
|
916
|
-
await ctx.session.append("continue-after-tools", {
|
|
917
|
-
type: "ai.generation.requested",
|
|
918
|
-
id: `ai.generate:tools:${generationId}`,
|
|
919
|
-
payload: {
|
|
920
|
-
messageId: response.responseMessageId,
|
|
921
|
-
responseMessageId: response.responseMessageId,
|
|
922
|
-
reason: "tool"
|
|
923
|
-
}
|
|
924
|
-
});
|
|
925
|
-
};
|
|
926
1410
|
const resultEvent = (call, suffix, result) => ({
|
|
927
1411
|
type: "ai.tool.result",
|
|
928
1412
|
id: `${call.generationId}:tool:${call.toolCallId}:${suffix}`,
|
|
@@ -943,22 +1427,35 @@ function createHandlers(options) {
|
|
|
943
1427
|
...call.toolMetadata === void 0 ? {} : { toolMetadata: call.toolMetadata }
|
|
944
1428
|
}
|
|
945
1429
|
});
|
|
946
|
-
const promptMessages = (
|
|
947
|
-
const
|
|
1430
|
+
const promptMessages = (input) => {
|
|
1431
|
+
const { ctx, call, generation: owner } = input;
|
|
1432
|
+
const key = promptCacheKey(ctx.event.sessionId, call.generationId);
|
|
948
1433
|
const cached = promptCache.get(key);
|
|
949
1434
|
if (cached) return cached;
|
|
950
1435
|
const computation = (async () => {
|
|
951
|
-
const
|
|
952
|
-
|
|
953
|
-
const
|
|
954
|
-
|
|
955
|
-
|
|
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
|
+
});
|
|
956
1452
|
return modelContext({
|
|
957
|
-
messages:
|
|
1453
|
+
messages: await contextMessages({
|
|
958
1454
|
agent: options.agent,
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
coordinator:
|
|
1455
|
+
session: ctx.session,
|
|
1456
|
+
snapshot,
|
|
1457
|
+
coordinator: atPrompt.state,
|
|
1458
|
+
appended
|
|
962
1459
|
}),
|
|
963
1460
|
state,
|
|
964
1461
|
tools
|
|
@@ -974,7 +1471,7 @@ function createHandlers(options) {
|
|
|
974
1471
|
const schedulerFailure = consumeSchedulerSendFailure(error);
|
|
975
1472
|
if (checkAbort(ctx.signal)) return;
|
|
976
1473
|
if (schedulerFailure === "retryable") throw error;
|
|
977
|
-
return resultEvent(call,
|
|
1474
|
+
return resultEvent(call, `${ctx.event.id}:execution:error`, { error: errorMessage(error) });
|
|
978
1475
|
};
|
|
979
1476
|
const runToolExecution = async (ctx, call, execute, messages) => {
|
|
980
1477
|
let output;
|
|
@@ -993,7 +1490,7 @@ function createHandlers(options) {
|
|
|
993
1490
|
} catch (error) {
|
|
994
1491
|
return toolExecutionFailure(ctx, call, error);
|
|
995
1492
|
}
|
|
996
|
-
if (iterator === void 0) return resultEvent(call,
|
|
1493
|
+
if (iterator === void 0) return resultEvent(call, `${ctx.event.id}:execution:0`, { output });
|
|
997
1494
|
let last;
|
|
998
1495
|
let sequence = 0;
|
|
999
1496
|
let done = false;
|
|
@@ -1005,16 +1502,21 @@ function createHandlers(options) {
|
|
|
1005
1502
|
} catch (error) {
|
|
1006
1503
|
return toolExecutionFailure(ctx, call, error);
|
|
1007
1504
|
}
|
|
1008
|
-
if (checkAbort(ctx.signal)) return;
|
|
1009
1505
|
if (result.done) {
|
|
1506
|
+
if (ctx.signal.reason instanceof A2Error) throw ctx.signal.reason;
|
|
1010
1507
|
done = true;
|
|
1011
1508
|
break;
|
|
1012
1509
|
}
|
|
1510
|
+
if (checkAbort(ctx.signal)) return;
|
|
1013
1511
|
last = result.value;
|
|
1014
|
-
await
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
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
|
+
});
|
|
1018
1520
|
sequence += 1;
|
|
1019
1521
|
}
|
|
1020
1522
|
} finally {
|
|
@@ -1022,12 +1524,13 @@ function createHandlers(options) {
|
|
|
1022
1524
|
await iterator.return?.();
|
|
1023
1525
|
} catch {}
|
|
1024
1526
|
}
|
|
1025
|
-
return resultEvent(call,
|
|
1527
|
+
return resultEvent(call, `${ctx.event.id}:execution:${sequence}:final`, sequence === 0 ? {} : { output: last });
|
|
1026
1528
|
};
|
|
1027
|
-
const executeTool = async (
|
|
1529
|
+
const executeTool = async (input) => {
|
|
1530
|
+
const { ctx, call } = input;
|
|
1028
1531
|
const execute = tools[call.toolName]?.execute;
|
|
1029
|
-
if (execute === void 0) return resultEvent(call,
|
|
1030
|
-
const messages = await promptMessages(
|
|
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);
|
|
1031
1534
|
if (checkAbort(ctx.signal)) return;
|
|
1032
1535
|
const scope = {
|
|
1033
1536
|
contract: options.agent.contract,
|
|
@@ -1035,62 +1538,82 @@ function createHandlers(options) {
|
|
|
1035
1538
|
};
|
|
1036
1539
|
return toolScopeStorage.run(scope, () => runToolExecution(ctx, call, execute, messages));
|
|
1037
1540
|
};
|
|
1038
|
-
const
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
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;
|
|
1055
1567
|
}
|
|
1056
|
-
if (!ctx.event.payload.approved) return resultEvent(current.call, "execution:denied", { denied: true });
|
|
1057
|
-
return executeTool(ctx, current.call);
|
|
1058
1568
|
};
|
|
1059
1569
|
const generationHandler = async (ctx) => {
|
|
1060
|
-
|
|
1570
|
+
if (checkAbort(ctx.signal)) return;
|
|
1061
1571
|
const requestId = ctx.event.id;
|
|
1062
1572
|
const request = ctx.event.payload;
|
|
1063
|
-
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;
|
|
1064
1576
|
if (coordinatorState.closed || coordinatorState.response?.activeRequestId !== requestId) return;
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
return payload.requestId === requestId && payload.superseded !== true;
|
|
1070
|
-
})) return;
|
|
1071
|
-
const previousStarts = history.filter((event) => event.type === "ai.generation.started" && event.payload.requestId === requestId);
|
|
1072
|
-
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;
|
|
1073
1581
|
const attempt = ctx.attempt;
|
|
1074
1582
|
const generationId = `${requestId}:generation:${attempt}`;
|
|
1075
1583
|
const responseMessageId = request.responseMessageId ?? (request.reason === "message" ? `${request.messageId}:assistant` : request.messageId);
|
|
1076
|
-
const responseStepCount =
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
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
|
+
});
|
|
1082
1609
|
}
|
|
1083
|
-
const previous = previousStarts.filter((event) => event.payload.attempt < attempt).toSorted((left, right) => right.payload.attempt - left.payload.attempt)[0];
|
|
1084
|
-
const incompleteId = previous ? previous.payload.generationId : void 0;
|
|
1085
|
-
const replacedGenerationIds = /* @__PURE__ */ new Set();
|
|
1086
|
-
if (incompleteId !== void 0) replacedGenerationIds.add(incompleteId);
|
|
1087
|
-
if (request.reason === "retry" && sourceCoordinatorState.response?.failure?.generationId !== void 0) replacedGenerationIds.add(sourceCoordinatorState.response.failure.generationId);
|
|
1088
|
-
const promptHistory = withoutGenerationLifecycle(history, replacedGenerationIds);
|
|
1089
|
-
const state = replay(options.agent, promptHistory);
|
|
1090
1610
|
const resolverContext = {
|
|
1091
1611
|
event: ctx.event,
|
|
1092
1612
|
state,
|
|
1093
|
-
|
|
1613
|
+
session: { state: (reducer, readOptions) => ctx.session.state(reducer, {
|
|
1614
|
+
...readOptions,
|
|
1615
|
+
through: readOptions?.through ?? snapshot.index
|
|
1616
|
+
}) },
|
|
1094
1617
|
signal: ctx.signal
|
|
1095
1618
|
};
|
|
1096
1619
|
let generationStarted = false;
|
|
@@ -1101,14 +1624,14 @@ function createHandlers(options) {
|
|
|
1101
1624
|
const resolvedInstructions = options.instructions === void 0 ? void 0 : await resolve(options.instructions, resolverContext);
|
|
1102
1625
|
if (checkAbort(ctx.signal)) return;
|
|
1103
1626
|
const compaction = typeof configuredCompaction === "function" ? validateCompaction({
|
|
1104
|
-
compaction: configuredCompaction
|
|
1627
|
+
compaction: await resolve(configuredCompaction, resolverContext),
|
|
1105
1628
|
explicit: true,
|
|
1106
1629
|
generation: options.generation,
|
|
1107
1630
|
tools: options.tools
|
|
1108
1631
|
}) : staticCompaction;
|
|
1109
1632
|
if (checkAbort(ctx.signal)) return;
|
|
1110
1633
|
if (request.reason === "tool" && responseStepCount >= maxSteps) {
|
|
1111
|
-
const source =
|
|
1634
|
+
const source = response.source?.generation;
|
|
1112
1635
|
if (source === void 0) return;
|
|
1113
1636
|
return {
|
|
1114
1637
|
type: "ai.generation.failed",
|
|
@@ -1130,7 +1653,7 @@ function createHandlers(options) {
|
|
|
1130
1653
|
responseMessageId,
|
|
1131
1654
|
attempt,
|
|
1132
1655
|
model: modelName(resolvedModel),
|
|
1133
|
-
promptThroughIndex:
|
|
1656
|
+
promptThroughIndex: snapshot.index
|
|
1134
1657
|
};
|
|
1135
1658
|
const catalogModelId = gatewayModelId(resolvedModel);
|
|
1136
1659
|
const gatewayOptions = options.generation?.providerOptions?.["gateway"];
|
|
@@ -1143,7 +1666,7 @@ function createHandlers(options) {
|
|
|
1143
1666
|
payload: { modelId: catalogModelId }
|
|
1144
1667
|
});
|
|
1145
1668
|
if (previous) {
|
|
1146
|
-
const payload = previous
|
|
1669
|
+
const payload = previous;
|
|
1147
1670
|
const superseded = {
|
|
1148
1671
|
requestId,
|
|
1149
1672
|
messageId: payload.messageId,
|
|
@@ -1163,20 +1686,25 @@ function createHandlers(options) {
|
|
|
1163
1686
|
id: generationId,
|
|
1164
1687
|
payload: started
|
|
1165
1688
|
});
|
|
1166
|
-
const
|
|
1689
|
+
const generationEvents = await control.append({
|
|
1690
|
+
ctx,
|
|
1691
|
+
name: "generation-start",
|
|
1692
|
+
events: startEvents
|
|
1693
|
+
});
|
|
1167
1694
|
generationStarted = true;
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
const
|
|
1171
|
-
...coordinatorStateAt(history),
|
|
1172
|
-
queued: queuedMessagesAt(history)
|
|
1173
|
-
};
|
|
1174
|
-
const messages = activeContextMessages({
|
|
1695
|
+
if (checkAbort(ctx.signal)) return;
|
|
1696
|
+
const promptCoordinatorState = coordinatorState;
|
|
1697
|
+
const messages = await contextMessages({
|
|
1175
1698
|
agent: options.agent,
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1699
|
+
session: ctx.session,
|
|
1700
|
+
snapshot: {
|
|
1701
|
+
state,
|
|
1702
|
+
index: snapshot.index
|
|
1703
|
+
},
|
|
1704
|
+
coordinator: promptCoordinatorState,
|
|
1705
|
+
excluded: replacedGenerationIds
|
|
1179
1706
|
});
|
|
1707
|
+
let generationMessages = messages;
|
|
1180
1708
|
let modelMessages = await modelContext({
|
|
1181
1709
|
messages,
|
|
1182
1710
|
state,
|
|
@@ -1190,7 +1718,7 @@ function createHandlers(options) {
|
|
|
1190
1718
|
messages,
|
|
1191
1719
|
modelMessages,
|
|
1192
1720
|
state,
|
|
1193
|
-
|
|
1721
|
+
session: resolverContext.session,
|
|
1194
1722
|
signal: ctx.signal,
|
|
1195
1723
|
model: resolvedModel,
|
|
1196
1724
|
tools,
|
|
@@ -1203,7 +1731,7 @@ function createHandlers(options) {
|
|
|
1203
1731
|
const limits = metadata?.status === "resolved" && !usesFallbackModels ? metadata.limits : void 0;
|
|
1204
1732
|
let inputTokenEstimate;
|
|
1205
1733
|
let compacted = false;
|
|
1206
|
-
const canCompact =
|
|
1734
|
+
const canCompact = response.source?.canCompact ?? true;
|
|
1207
1735
|
if (policy && canCompact) {
|
|
1208
1736
|
const compactionContext = {
|
|
1209
1737
|
...resolverContext,
|
|
@@ -1221,7 +1749,7 @@ function createHandlers(options) {
|
|
|
1221
1749
|
const inputTokens = measuredInputTokens({
|
|
1222
1750
|
estimate: inputTokenEstimate,
|
|
1223
1751
|
model: modelName(resolvedModel),
|
|
1224
|
-
|
|
1752
|
+
calibration: coordinatorState.calibration
|
|
1225
1753
|
});
|
|
1226
1754
|
const threshold = policy.thresholdTokens ?? (limits === void 0 ? void 0 : Math.floor(Math.min(limits.contextWindow * .75, limits.contextWindow - (generation.maxOutputTokens ?? 0))));
|
|
1227
1755
|
if (limits !== void 0 && threshold !== void 0 && threshold <= 0) throw new Error(`output allowance exhausts the ${limits.contextWindow}-token context window`);
|
|
@@ -1230,15 +1758,19 @@ function createHandlers(options) {
|
|
|
1230
1758
|
if (checkAbort(ctx.signal)) return;
|
|
1231
1759
|
if (shouldCompact) {
|
|
1232
1760
|
const throughMessageId = state.messages.findLast((message) => !promptCoordinatorState.queued.some((queued) => queued.messageId === message.id))?.id ?? request.messageId;
|
|
1233
|
-
const throughIndex =
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
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
|
+
}]
|
|
1242
1774
|
}));
|
|
1243
1775
|
const result = !("shouldCompact" in policy) ? {
|
|
1244
1776
|
messages: [],
|
|
@@ -1255,23 +1787,33 @@ function createHandlers(options) {
|
|
|
1255
1787
|
...result,
|
|
1256
1788
|
...promptCoordinatorState.queued.length === 0 ? {} : { retainedMessageIds: promptCoordinatorState.queued.map((item) => item.messageId) }
|
|
1257
1789
|
};
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
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
|
+
}]
|
|
1262
1798
|
}));
|
|
1799
|
+
const queued = new Set(promptCoordinatorState.queued.map((item) => item.messageId));
|
|
1800
|
+
generationMessages = result.messages.filter((message) => !queued.has(message.id));
|
|
1263
1801
|
compacted = true;
|
|
1264
1802
|
}
|
|
1265
1803
|
}
|
|
1266
|
-
const currentState =
|
|
1267
|
-
const currentCoordinatorState = coordinatorStateAt(generationHistory);
|
|
1268
|
-
const generationMessages = activeContextMessages({
|
|
1804
|
+
const currentState = foldAIEvents({
|
|
1269
1805
|
agent: options.agent,
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
coordinator: currentCoordinatorState
|
|
1806
|
+
events: generationEvents,
|
|
1807
|
+
state: snapshot.state
|
|
1273
1808
|
});
|
|
1274
|
-
|
|
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({
|
|
1275
1817
|
messages: generationMessages,
|
|
1276
1818
|
state: currentState,
|
|
1277
1819
|
tools
|
|
@@ -1292,7 +1834,7 @@ function createHandlers(options) {
|
|
|
1292
1834
|
messages: generationMessages,
|
|
1293
1835
|
modelMessages,
|
|
1294
1836
|
state: currentState,
|
|
1295
|
-
|
|
1837
|
+
session: resolverContext.session,
|
|
1296
1838
|
signal: ctx.signal,
|
|
1297
1839
|
model: resolvedModel,
|
|
1298
1840
|
tools,
|
|
@@ -1332,11 +1874,15 @@ function createHandlers(options) {
|
|
|
1332
1874
|
custom
|
|
1333
1875
|
});
|
|
1334
1876
|
pendingToolCalls = lifecycle.pending;
|
|
1335
|
-
await
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
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
|
+
});
|
|
1340
1886
|
sequence += 1;
|
|
1341
1887
|
}
|
|
1342
1888
|
if (!finish) throw new Error("agent generation finished without output");
|
|
@@ -1368,22 +1914,12 @@ function createHandlers(options) {
|
|
|
1368
1914
|
}
|
|
1369
1915
|
];
|
|
1370
1916
|
} catch (error) {
|
|
1917
|
+
if (error instanceof ControlCancelled) throw error;
|
|
1371
1918
|
if (error instanceof A2Error) {
|
|
1372
1919
|
checkAbort(ctx.signal);
|
|
1373
1920
|
throw error;
|
|
1374
1921
|
}
|
|
1375
|
-
if (checkAbort(ctx.signal))
|
|
1376
|
-
const interrupted = {
|
|
1377
|
-
messageId: responseMessageId,
|
|
1378
|
-
generationId,
|
|
1379
|
-
reason: "aborted"
|
|
1380
|
-
};
|
|
1381
|
-
return {
|
|
1382
|
-
type: "ai.message.interrupted",
|
|
1383
|
-
id: `${generationId}:interrupted`,
|
|
1384
|
-
payload: interrupted
|
|
1385
|
-
};
|
|
1386
|
-
}
|
|
1922
|
+
if (checkAbort(ctx.signal)) return;
|
|
1387
1923
|
if (!generationStarted) throw error;
|
|
1388
1924
|
const failed = {
|
|
1389
1925
|
requestId,
|
|
@@ -1399,36 +1935,10 @@ function createHandlers(options) {
|
|
|
1399
1935
|
};
|
|
1400
1936
|
}
|
|
1401
1937
|
};
|
|
1402
|
-
const handleMessageCreated = async (ctx) => {
|
|
1403
|
-
if (ctx.event.payload.message.role !== "user" || ctx.event.payload.generate === false) return;
|
|
1404
|
-
return scheduleNext(ctx);
|
|
1405
|
-
};
|
|
1406
|
-
const handleRetry = async (ctx) => {
|
|
1407
|
-
const response = (await ctx.session.state(coordinator, { through: "latest" })).state.response;
|
|
1408
|
-
if (response?.status !== "failed" || response.rootMessageId !== ctx.event.payload.messageId || response.responseMessageId !== ctx.event.payload.responseMessageId) return;
|
|
1409
|
-
return {
|
|
1410
|
-
type: "ai.generation.requested",
|
|
1411
|
-
id: `ai.generate:retry:${ctx.event.payload.retryId}`,
|
|
1412
|
-
payload: {
|
|
1413
|
-
messageId: response.rootMessageId,
|
|
1414
|
-
responseMessageId: response.responseMessageId,
|
|
1415
|
-
reason: "retry"
|
|
1416
|
-
}
|
|
1417
|
-
};
|
|
1418
|
-
};
|
|
1419
|
-
const handleInputResponse = async (ctx) => {
|
|
1420
|
-
const response = (await ctx.session.state(coordinator, { through: "latest" })).state.response;
|
|
1421
|
-
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;
|
|
1422
|
-
await continueIfReady(ctx, ctx.event.payload.generationId);
|
|
1423
|
-
};
|
|
1424
1938
|
const clearPromptCache = (sessionId) => {
|
|
1425
1939
|
const prefix = `${sessionId}\u001f`;
|
|
1426
1940
|
for (const key of promptCache.keys()) if (key.startsWith(prefix)) promptCache.delete(key);
|
|
1427
1941
|
};
|
|
1428
|
-
const handleResponseEnded = async (ctx) => {
|
|
1429
|
-
clearPromptCache(ctx.event.sessionId);
|
|
1430
|
-
return scheduleNext(ctx);
|
|
1431
|
-
};
|
|
1432
1942
|
return {
|
|
1433
1943
|
"ai.model.metadata.requested": { handler: async (ctx) => {
|
|
1434
1944
|
const limits = await readModelLimits({
|
|
@@ -1445,66 +1955,69 @@ function createHandlers(options) {
|
|
|
1445
1955
|
}
|
|
1446
1956
|
};
|
|
1447
1957
|
} },
|
|
1448
|
-
"ai.
|
|
1449
|
-
lane: "a2.ai.
|
|
1450
|
-
handler:
|
|
1451
|
-
},
|
|
1452
|
-
"ai.retry.requested": {
|
|
1453
|
-
lane: "a2.ai.turn",
|
|
1454
|
-
handler: handleRetry
|
|
1958
|
+
"ai.control.requested": {
|
|
1959
|
+
lane: "a2.ai.control",
|
|
1960
|
+
handler: control.handler
|
|
1455
1961
|
},
|
|
1456
|
-
"ai.
|
|
1457
|
-
lane: "a2.ai.
|
|
1458
|
-
handler:
|
|
1962
|
+
"ai.work.reported": {
|
|
1963
|
+
lane: "a2.ai.control",
|
|
1964
|
+
handler: control.handler
|
|
1459
1965
|
},
|
|
1460
|
-
"ai.message.completed": {
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
"ai.message.interrupted": {
|
|
1465
|
-
lane: "a2.ai.turn",
|
|
1466
|
-
handler: handleResponseEnded
|
|
1467
|
-
},
|
|
1468
|
-
"ai.session.closed": { handler: (ctx) => {
|
|
1966
|
+
"ai.message.completed": { handler: async (ctx) => {
|
|
1967
|
+
clearPromptCache(ctx.event.sessionId);
|
|
1968
|
+
} },
|
|
1969
|
+
"ai.message.interrupted": { handler: async (ctx) => {
|
|
1469
1970
|
clearPromptCache(ctx.event.sessionId);
|
|
1470
|
-
return Promise.resolve();
|
|
1471
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
|
+
},
|
|
1472
1979
|
"ai.generation.failed": { handler: (ctx) => {
|
|
1473
1980
|
promptCache.delete(promptCacheKey(ctx.event.sessionId, ctx.event.payload.generationId));
|
|
1474
1981
|
return Promise.resolve();
|
|
1475
1982
|
} },
|
|
1476
1983
|
"ai.generation.requested": {
|
|
1477
|
-
lane: "a2.ai.
|
|
1984
|
+
lane: "a2.ai.model",
|
|
1478
1985
|
abortOn: {
|
|
1479
|
-
"ai.
|
|
1480
|
-
const
|
|
1481
|
-
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;
|
|
1482
1989
|
},
|
|
1483
1990
|
"ai.session.closed": true
|
|
1484
1991
|
},
|
|
1485
|
-
handler:
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
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
|
+
}
|
|
1495
2009
|
},
|
|
1496
|
-
"ai.
|
|
2010
|
+
"ai.tool.execution.requested": {
|
|
1497
2011
|
abortOn: {
|
|
1498
|
-
"ai.generation.failed": (event, trigger) => event.payload.
|
|
1499
|
-
"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
|
+
},
|
|
1500
2017
|
"ai.session.closed": true
|
|
1501
2018
|
},
|
|
1502
|
-
handler:
|
|
1503
|
-
}
|
|
1504
|
-
"ai.tool.result": { handler: async (ctx) => {
|
|
1505
|
-
if (ctx.event.payload.preliminary === true) return;
|
|
1506
|
-
await continueIfReady(ctx, ctx.event.payload.generationId);
|
|
1507
|
-
} }
|
|
2019
|
+
handler: handleToolExecution
|
|
2020
|
+
}
|
|
1508
2021
|
};
|
|
1509
2022
|
}
|
|
1510
2023
|
/** Assemble an A2 server with the built-in agent handlers and app extensions. */
|