experimental-a2 0.15.0 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +81 -0
- package/dist/actor-client.d.ts +1 -1
- package/dist/{actor-BfQSE0KC.d.ts → actor-ohPC-81x.d.ts} +5 -5
- package/dist/{actor-BfQSE0KC.d.ts.map → actor-ohPC-81x.d.ts.map} +1 -1
- package/dist/actor-react.d.ts +3 -3
- package/dist/actor.d.ts +1 -1
- package/dist/actor.js +1 -1
- package/dist/{ai-Cai-lCbj.d.ts → ai-CrEf6p_W.d.ts} +73 -14
- package/dist/ai-CrEf6p_W.d.ts.map +1 -0
- package/dist/ai-DgOBltJ_.js +2451 -0
- package/dist/ai-DgOBltJ_.js.map +1 -0
- package/dist/ai-server.d.ts +4 -5
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +788 -296
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.d.ts +2 -2
- package/dist/ai.js +1 -1999
- package/dist/client-BYzHjkwU.js.map +1 -1
- package/dist/{client-BAEABRZB.d.ts → client-CzyacQpJ.d.ts} +9 -9
- package/dist/client-CzyacQpJ.d.ts.map +1 -0
- package/dist/client.d.ts +1 -1
- package/dist/idempotent-id-BRJVeylj.js +13 -0
- package/dist/idempotent-id-BRJVeylj.js.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -12
- package/dist/otel.d.ts +1 -1
- package/dist/react.d.ts +13 -13
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js.map +1 -1
- package/dist/reducer-DEMjEY_O.js.map +1 -1
- package/dist/{reducer-BcS9VDKC.d.ts → reducer-otzHjuJj.d.ts} +3 -3
- package/dist/{reducer-BcS9VDKC.d.ts.map → reducer-otzHjuJj.d.ts.map} +1 -1
- 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-CjJSGcF7.js → server-BD5ckxZb.js} +39 -26
- package/dist/server-BD5ckxZb.js.map +1 -0
- package/dist/{server-Bp5Nd1pF.d.ts → server-DbD7IJVK.d.ts} +5 -5
- package/dist/{server-Bp5Nd1pF.d.ts.map → server-DbD7IJVK.d.ts.map} +1 -1
- package/dist/server.d.ts +3 -3
- package/dist/server.js +1 -1
- package/dist/{store-D_yhNdPz.d.ts → store-DyZM6fS5.d.ts} +3 -2
- package/dist/{store-D_yhNdPz.d.ts.map → store-DyZM6fS5.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-redis-http.d.ts +1 -1
- package/dist/store-redis-http.js +1 -1
- package/dist/{store-redis-notify-D2EI6gwX.js → store-redis-notify-BVLUfI3j.js} +14 -13
- package/dist/store-redis-notify-BVLUfI3j.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/dist/{telemetry-CpeclqB2.d.ts → telemetry-B5jzpy6y.d.ts} +2 -2
- package/dist/telemetry-B5jzpy6y.d.ts.map +1 -0
- package/docs/concepts/04-state.mdx +9 -4
- package/docs/guides/06-ai-agents.mdx +149 -48
- package/docs/reference/01-api.mdx +110 -28
- package/examples/playground/app/agent/[agentId]/agent-client.tsx +20 -28
- package/examples/playground/app/agent/[agentId]/agent-queue.test.tsx +123 -0
- package/examples/playground/app/agent/[agentId]/agent-queue.tsx +37 -43
- package/examples/playground/app/agent/[agentId]/compaction-panel.tsx +44 -24
- package/examples/playground/app/agent/compaction-timeline.test.ts +47 -7
- package/examples/playground/app/agent/compaction-timeline.ts +13 -3
- package/examples/playground/app/chat/[chatId]/chat-client.tsx +1 -5
- package/examples/playground/app/chat/[chatId]/session.ts +17 -2
- package/examples/playground/app/chat/model.test.ts +4 -4
- package/examples/playground/app/chat/model.ts +18 -6
- package/examples/playground/app/chat/server.ts +6 -3
- package/examples/playground/package.json +3 -1
- package/package.json +1 -1
- package/src/ai-client-state.ts +36 -4
- package/src/ai-context-schema.ts +296 -0
- package/src/ai-context.ts +680 -0
- package/src/ai-control-server.ts +183 -135
- package/src/ai-control-state.ts +6 -0
- package/src/ai-control.ts +8 -7
- package/src/ai-coordinator.ts +92 -38
- package/src/ai-id.ts +6 -0
- package/src/ai-input-tokens.ts +75 -0
- package/src/ai-message-projection.ts +474 -0
- package/src/ai-server.ts +247 -336
- package/src/ai-stored-state.ts +484 -0
- package/src/ai.ts +255 -538
- package/src/client.ts +21 -15
- package/src/react.ts +25 -19
- package/src/reducer.ts +2 -1
- package/src/server.ts +73 -56
- package/src/store-redis-core.ts +15 -17
- package/src/store.ts +1 -0
- package/src/telemetry.ts +2 -1
- package/dist/ai-Cai-lCbj.d.ts.map +0 -1
- package/dist/ai-control-CcD4hh3y.js +0 -119
- package/dist/ai-control-CcD4hh3y.js.map +0 -1
- package/dist/ai.js.map +0 -1
- package/dist/client-BAEABRZB.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/server-CjJSGcF7.js.map +0 -1
- package/dist/store-redis-notify-D2EI6gwX.js.map +0 -1
- package/dist/telemetry-CpeclqB2.d.ts.map +0 -1
package/dist/ai-server.js
CHANGED
|
@@ -1,12 +1,29 @@
|
|
|
1
1
|
import { n as validateSync } from "./validate-XKT4FSNn.js";
|
|
2
2
|
import { t as A2Error } from "./errors-DCk6ch5n.js";
|
|
3
3
|
import { f as consumeSchedulerSendFailure, p as installAmbientToolScopeStorage } from "./internal-Dq2qYxou.js";
|
|
4
|
-
import { i as setServerFetchHooks, t as createServer } from "./server-
|
|
5
|
-
import {
|
|
4
|
+
import { i as setServerFetchHooks, t as createServer } from "./server-BD5ckxZb.js";
|
|
5
|
+
import { C as interruptUIMessage, S as progressBatches, _ as projectedMessage, a as events, b as upsertResponse, d as AI_EVENT_ID_PREFIX, f as aiEventId, g as advanceProjection, h as controlCommit, l as aiStateReducer, m as applyControlChanges, p as initialControlState, u as storedAIReducer, v as reduceToolActivity, w as pauseUIMessage, x as flattenProgressBatches, y as upsertMessage } from "./ai-DgOBltJ_.js";
|
|
6
6
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
|
-
import { asSchema, convertToModelMessages, stepCountIs, streamText, toUIMessageStream } from "ai";
|
|
8
|
+
import { asSchema, convertToModelMessages, isToolUIPart, stepCountIs, streamText, toUIMessageStream } from "ai";
|
|
9
|
+
import { Buffer } from "node:buffer";
|
|
9
10
|
//#region src/ai-coordinator.ts
|
|
11
|
+
const completeCall = (candidate) => {
|
|
12
|
+
const { requestId, generationId, messageId, toolCallId, toolName, providerExecuted, supportsDeferredResults } = candidate.call;
|
|
13
|
+
return {
|
|
14
|
+
terminal: true,
|
|
15
|
+
call: {
|
|
16
|
+
requestId,
|
|
17
|
+
generationId,
|
|
18
|
+
messageId,
|
|
19
|
+
toolCallId,
|
|
20
|
+
toolName,
|
|
21
|
+
...providerExecuted === void 0 ? {} : { providerExecuted },
|
|
22
|
+
...supportsDeferredResults === void 0 ? {} : { supportsDeferredResults }
|
|
23
|
+
},
|
|
24
|
+
...candidate.work === void 0 ? {} : { work: candidate.work }
|
|
25
|
+
};
|
|
26
|
+
};
|
|
10
27
|
const updateResponse = (state, update) => state.response === void 0 ? state : {
|
|
11
28
|
...state,
|
|
12
29
|
response: update(state.response)
|
|
@@ -37,7 +54,7 @@ const foldCoordinator = (state, event) => {
|
|
|
37
54
|
case "ai.generation.requested": {
|
|
38
55
|
const request = event.payload;
|
|
39
56
|
if (request.reason === "message") {
|
|
40
|
-
const responseMessageId = request.responseMessageId
|
|
57
|
+
const responseMessageId = request.responseMessageId;
|
|
41
58
|
const requested = state.queued.findIndex((item) => item.messageId === request.messageId);
|
|
42
59
|
return {
|
|
43
60
|
...state,
|
|
@@ -48,28 +65,26 @@ const foldCoordinator = (state, event) => {
|
|
|
48
65
|
status: "requested",
|
|
49
66
|
stepCount: 0,
|
|
50
67
|
activeRequestId: event.id,
|
|
68
|
+
requestReason: request.reason,
|
|
69
|
+
...request.sourceGenerationId === void 0 ? {} : { sourceGenerationId: request.sourceGenerationId },
|
|
51
70
|
calls: [],
|
|
52
71
|
inputs: []
|
|
53
72
|
}
|
|
54
73
|
};
|
|
55
74
|
}
|
|
56
75
|
if (state.response === void 0 || request.responseMessageId !== state.response.responseMessageId) return state;
|
|
57
|
-
if (request.reason === "tool" && (!continuationReady(state) ||
|
|
76
|
+
if (request.reason === "tool" && (!continuationReady(state) || request.sourceGenerationId !== state.response.generation?.generationId) || request.sourceGenerationId !== void 0 && request.sourceGenerationId !== (state.response.generation ?? state.response.source?.generation)?.generationId || request.reason === "retry" && state.response.status !== "failed" || request.reason === "input" && state.response.inputResponse === void 0) return state;
|
|
58
77
|
return updateResponse(state, (response) => {
|
|
59
78
|
const generation = response.generation ?? response.source?.generation;
|
|
60
|
-
const promptThroughIndex = response.promptThroughIndex ?? response.source?.promptThroughIndex;
|
|
61
79
|
return {
|
|
62
80
|
rootMessageId: response.rootMessageId,
|
|
63
81
|
responseMessageId: response.responseMessageId,
|
|
64
82
|
status: "requested",
|
|
65
83
|
stepCount: response.stepCount,
|
|
66
84
|
activeRequestId: event.id,
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
failed: response.failure?.generationId === generation.generationId,
|
|
71
|
-
canCompact: response.calls.every((call) => call.terminal)
|
|
72
|
-
} },
|
|
85
|
+
requestReason: request.reason,
|
|
86
|
+
...request.sourceGenerationId === void 0 ? {} : { sourceGenerationId: request.sourceGenerationId },
|
|
87
|
+
...generation === void 0 ? {} : { source: { generation } },
|
|
73
88
|
calls: [],
|
|
74
89
|
inputs: []
|
|
75
90
|
};
|
|
@@ -84,16 +99,17 @@ const foldCoordinator = (state, event) => {
|
|
|
84
99
|
status: "generating",
|
|
85
100
|
stepCount: response.stepCount,
|
|
86
101
|
generation,
|
|
87
|
-
...generation.promptThroughIndex === void 0 ? {} : { promptThroughIndex: generation.promptThroughIndex },
|
|
88
102
|
...response.source === void 0 ? {} : { source: response.source },
|
|
89
103
|
activeRequestId: generation.requestId,
|
|
104
|
+
requestReason: response.requestReason,
|
|
105
|
+
...response.sourceGenerationId === void 0 ? {} : { sourceGenerationId: response.sourceGenerationId },
|
|
90
106
|
calls: [],
|
|
91
107
|
inputs: []
|
|
92
108
|
}));
|
|
93
109
|
}
|
|
94
110
|
case "ai.generation.completed": {
|
|
95
111
|
const completion = event.payload;
|
|
96
|
-
if (state.response?.generation?.generationId !== completion.generationId) return state;
|
|
112
|
+
if (state.response?.generation?.requestId !== completion.requestId || state.response.generation.generationId !== completion.generationId) return state;
|
|
97
113
|
const { calibration: _, ...uncalibrated } = updateResponse(state, (response) => ({
|
|
98
114
|
...response,
|
|
99
115
|
status: "waiting",
|
|
@@ -114,8 +130,8 @@ const foldCoordinator = (state, event) => {
|
|
|
114
130
|
case "ai.generation.failed": {
|
|
115
131
|
const failure = event.payload;
|
|
116
132
|
const response = state.response;
|
|
117
|
-
const ownsActiveGeneration = response?.generation?.generationId === failure.generationId;
|
|
118
|
-
const ownsActiveStepLimit = failure.stepLimit === true && response?.
|
|
133
|
+
const ownsActiveGeneration = response?.generation?.generationId === failure.generationId && response.generation.requestId === failure.requestId;
|
|
134
|
+
const ownsActiveStepLimit = failure.stepLimit === true && response?.requestReason === "tool" && response.sourceGenerationId === failure.generationId && response.source?.generation.requestId === failure.requestId && response.responseMessageId === failure.responseMessageId;
|
|
119
135
|
if (!ownsActiveGeneration && !ownsActiveStepLimit) return state;
|
|
120
136
|
return updateResponse(state, (current) => ({
|
|
121
137
|
...current,
|
|
@@ -138,7 +154,7 @@ const foldCoordinator = (state, event) => {
|
|
|
138
154
|
}
|
|
139
155
|
case "ai.tool.called": {
|
|
140
156
|
const call = event.payload;
|
|
141
|
-
if (state.response?.generation?.generationId !== call.generationId) return state;
|
|
157
|
+
if (state.response?.generation?.requestId !== call.requestId || state.response.generation.generationId !== call.generationId) return state;
|
|
142
158
|
return updateResponse(state, (response) => ({
|
|
143
159
|
...response,
|
|
144
160
|
calls: [...response.calls.filter((candidate) => candidate.call.toolCallId !== call.toolCallId), {
|
|
@@ -149,7 +165,7 @@ const foldCoordinator = (state, event) => {
|
|
|
149
165
|
}
|
|
150
166
|
case "ai.approval.requested": {
|
|
151
167
|
const approval = event.payload;
|
|
152
|
-
if (state.response?.generation?.generationId !== approval.generationId) return state;
|
|
168
|
+
if (state.response?.generation?.requestId !== approval.requestId || state.response.generation.generationId !== approval.generationId) return state;
|
|
153
169
|
return updateResponse(state, (response) => ({
|
|
154
170
|
...response,
|
|
155
171
|
calls: response.calls.map((candidate) => candidate.call.toolCallId === approval.toolCallId ? {
|
|
@@ -160,7 +176,7 @@ const foldCoordinator = (state, event) => {
|
|
|
160
176
|
}
|
|
161
177
|
case "ai.approval.responded": {
|
|
162
178
|
const approval = event.payload;
|
|
163
|
-
if (state.response?.responseMessageId !== approval.messageId || state.response.generation?.generationId !== approval.generationId) return state;
|
|
179
|
+
if (state.response?.responseMessageId !== approval.messageId || state.response.generation?.requestId !== approval.requestId || state.response.generation.generationId !== approval.generationId) return state;
|
|
164
180
|
return updateResponse(state, (response) => ({
|
|
165
181
|
...response,
|
|
166
182
|
calls: response.calls.map((candidate) => candidate.approval?.approvalId === approval.approvalId ? {
|
|
@@ -171,18 +187,15 @@ const foldCoordinator = (state, event) => {
|
|
|
171
187
|
}
|
|
172
188
|
case "ai.tool.result": {
|
|
173
189
|
const result = event.payload;
|
|
174
|
-
if (result.preliminary === true || state.response?.generation?.generationId !== result.generationId) return state;
|
|
190
|
+
if (result.preliminary === true || state.response?.generation?.requestId !== result.requestId || state.response.generation.generationId !== result.generationId) return state;
|
|
175
191
|
return updateResponse(state, (response) => ({
|
|
176
192
|
...response,
|
|
177
|
-
calls: response.calls.map((candidate) => candidate.call.toolCallId === result.toolCallId ?
|
|
178
|
-
...candidate,
|
|
179
|
-
terminal: true
|
|
180
|
-
} : candidate)
|
|
193
|
+
calls: response.calls.map((candidate) => candidate.call.toolCallId === result.toolCallId ? completeCall(candidate) : candidate)
|
|
181
194
|
}));
|
|
182
195
|
}
|
|
183
196
|
case "ai.input.requested": {
|
|
184
197
|
const input = event.payload;
|
|
185
|
-
if (state.response?.responseMessageId !== input.messageId || state.response.generation?.generationId !== input.generationId) return state;
|
|
198
|
+
if (state.response?.responseMessageId !== input.messageId || state.response.generation?.requestId !== input.requestId || state.response.generation.generationId !== input.generationId) return state;
|
|
186
199
|
return updateResponse(state, (response) => ({
|
|
187
200
|
...response,
|
|
188
201
|
inputs: [...response.inputs.filter((candidate) => candidate.messageId !== input.messageId || candidate.generationId !== input.generationId || candidate.inputId !== input.inputId), input]
|
|
@@ -191,7 +204,7 @@ const foldCoordinator = (state, event) => {
|
|
|
191
204
|
case "ai.input.responded": {
|
|
192
205
|
const input = event.payload;
|
|
193
206
|
const requested = state.response?.inputs.find((candidate) => candidate.messageId === input.messageId && candidate.generationId === input.generationId && candidate.inputId === input.inputId && candidate.name === input.name);
|
|
194
|
-
if (state.response?.responseMessageId !== input.messageId || state.response.generation?.generationId !== input.generationId || requested === void 0) return state;
|
|
207
|
+
if (state.response?.responseMessageId !== input.messageId || state.response.generation?.requestId !== input.requestId || state.response.generation.generationId !== input.generationId || requested === void 0) return state;
|
|
195
208
|
return updateResponse(state, (response) => ({
|
|
196
209
|
...response,
|
|
197
210
|
inputs: response.inputs.filter((candidate) => candidate.inputId !== input.inputId),
|
|
@@ -206,7 +219,7 @@ const foldCoordinator = (state, event) => {
|
|
|
206
219
|
}
|
|
207
220
|
};
|
|
208
221
|
const aiCoordinatorReducer = (contract) => contract.reducer({
|
|
209
|
-
name: "a2.ai.coordinator.
|
|
222
|
+
name: "a2.ai.coordinator.v6",
|
|
210
223
|
initialState: {
|
|
211
224
|
closed: false,
|
|
212
225
|
queued: []
|
|
@@ -226,12 +239,35 @@ const callReadyForModel = (call) => call.terminal || call.call.providerExecuted
|
|
|
226
239
|
const approvalBlocksExecution = (call) => call.approval !== void 0 && call.response?.approved !== true;
|
|
227
240
|
const createControlRuntime = (options) => {
|
|
228
241
|
const reducer = options.agent.contract.reducer({
|
|
229
|
-
name: "a2.ai.control.
|
|
242
|
+
name: "a2.ai.control.v5",
|
|
230
243
|
initialState: initialControlState()
|
|
231
|
-
}).fold((state, event) =>
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
244
|
+
}).fold((state, event) => {
|
|
245
|
+
if (event.type === "ai.control.requested") {
|
|
246
|
+
const command = event.payload;
|
|
247
|
+
if (command.action === "stop") return {
|
|
248
|
+
...state,
|
|
249
|
+
pendingStops: {
|
|
250
|
+
...state.pendingStops,
|
|
251
|
+
[event.id]: command.turnId
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
if (event.type === "ai.control.decided") {
|
|
256
|
+
const receipt = event.payload;
|
|
257
|
+
if (Object.hasOwn(state.pendingStops, receipt.commandId)) {
|
|
258
|
+
const pendingStops = { ...state.pendingStops };
|
|
259
|
+
delete pendingStops[receipt.commandId];
|
|
260
|
+
return {
|
|
261
|
+
...state,
|
|
262
|
+
pendingStops
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return event.type === "ai.control.committed" ? applyControlChanges({
|
|
267
|
+
state,
|
|
268
|
+
changes: event.payload.changes
|
|
269
|
+
}) : state;
|
|
270
|
+
});
|
|
235
271
|
const coordinator = aiCoordinatorReducer(options.agent.contract);
|
|
236
272
|
const handler = async (ctx) => {
|
|
237
273
|
const snapshot = await ctx.session.state(reducer, { through: "latest" });
|
|
@@ -240,11 +276,11 @@ const createControlRuntime = (options) => {
|
|
|
240
276
|
const state = structuredClone(snapshot.state);
|
|
241
277
|
const facts = [];
|
|
242
278
|
const jobs = [];
|
|
243
|
-
const emit = ({ type, payload, id
|
|
279
|
+
const emit = async ({ type, payload, id }) => {
|
|
244
280
|
const fact = {
|
|
245
281
|
type,
|
|
246
282
|
payload,
|
|
247
|
-
id
|
|
283
|
+
id: id ?? await aiEventId("a2.ai.fact", ctx.event.id, String(facts.length))
|
|
248
284
|
};
|
|
249
285
|
facts.push(fact);
|
|
250
286
|
state.coordinator = foldCoordinator(state.coordinator, {
|
|
@@ -253,11 +289,11 @@ const createControlRuntime = (options) => {
|
|
|
253
289
|
});
|
|
254
290
|
};
|
|
255
291
|
let reason;
|
|
256
|
-
const end = ({ lastSeenIndex, reason: interruptionReason }) => {
|
|
292
|
+
const end = async ({ lastSeenIndex, reason: interruptionReason }) => {
|
|
257
293
|
const response = state.coordinator.response;
|
|
258
294
|
if (response === void 0) return;
|
|
259
295
|
const owner = response.generation === void 0 ? { requestId: response.activeRequestId } : { generationId: response.generation.generationId };
|
|
260
|
-
emit({
|
|
296
|
+
await emit({
|
|
261
297
|
type: "ai.message.interrupted",
|
|
262
298
|
payload: {
|
|
263
299
|
messageId: response.responseMessageId,
|
|
@@ -268,22 +304,13 @@ const createControlRuntime = (options) => {
|
|
|
268
304
|
});
|
|
269
305
|
state.active = null;
|
|
270
306
|
};
|
|
271
|
-
const knownInput = (id) => state.inbox.items.some((candidate) => candidate.id === id) || Object.hasOwn(state.reservedIds, id);
|
|
272
|
-
const missingInputReason = (id) => state.reservedIds[id] === "removed" || !knownInput(id) ? "not-found" : "already-active";
|
|
273
|
-
const inputTaken = ({ message, generate = true }) => knownInput(message.id) || generate && message.role === "user" && knownInput(`${message.id}:assistant`);
|
|
274
307
|
const insert = ({ message, generate = true, first = false, afterStepOf }) => {
|
|
275
308
|
const candidate = {
|
|
276
309
|
id: message.id,
|
|
277
|
-
revision: 0,
|
|
278
310
|
message,
|
|
279
311
|
generate: generate && message.role === "user",
|
|
280
312
|
...afterStepOf === void 0 ? {} : { afterStepOf }
|
|
281
313
|
};
|
|
282
|
-
state.reservedIds = {
|
|
283
|
-
...state.reservedIds,
|
|
284
|
-
[candidate.id]: true,
|
|
285
|
-
...candidate.generate ? { [`${candidate.id}:assistant`]: true } : {}
|
|
286
|
-
};
|
|
287
314
|
if (first) state.inbox.items.unshift(candidate);
|
|
288
315
|
else state.inbox.items.push(candidate);
|
|
289
316
|
};
|
|
@@ -340,42 +367,28 @@ const createControlRuntime = (options) => {
|
|
|
340
367
|
if (state.coordinator.closed) reason = "closed";
|
|
341
368
|
else switch (command.action) {
|
|
342
369
|
case "send":
|
|
343
|
-
|
|
344
|
-
message: command.message,
|
|
345
|
-
generate: command.generate !== false
|
|
346
|
-
})) reason = "duplicate-input";
|
|
347
|
-
else insert({
|
|
370
|
+
insert({
|
|
348
371
|
message: command.message,
|
|
349
372
|
generate: command.generate !== false
|
|
350
373
|
});
|
|
351
374
|
break;
|
|
352
375
|
case "edit": {
|
|
353
376
|
const item = state.inbox.items.find((candidate) => candidate.id === command.message.id);
|
|
354
|
-
if (item === void 0) reason =
|
|
355
|
-
else if (item.
|
|
356
|
-
else
|
|
357
|
-
else {
|
|
358
|
-
item.message = command.message;
|
|
359
|
-
item.revision += 1;
|
|
360
|
-
}
|
|
377
|
+
if (item === void 0) reason = "not-pending";
|
|
378
|
+
else if (item.message.role !== command.message.role) reason = "role-mismatch";
|
|
379
|
+
else item.message = command.message;
|
|
361
380
|
break;
|
|
362
381
|
}
|
|
363
382
|
case "remove": {
|
|
364
383
|
const position = state.inbox.items.findIndex((item) => item.id === command.inputId);
|
|
365
|
-
if (position < 0) reason =
|
|
366
|
-
else
|
|
367
|
-
state.inbox.items.splice(position, 1);
|
|
368
|
-
state.reservedIds = {
|
|
369
|
-
...state.reservedIds,
|
|
370
|
-
[command.inputId]: "removed"
|
|
371
|
-
};
|
|
372
|
-
}
|
|
384
|
+
if (position < 0) reason = "not-pending";
|
|
385
|
+
else state.inbox.items.splice(position, 1);
|
|
373
386
|
break;
|
|
374
387
|
}
|
|
375
388
|
case "move": {
|
|
376
389
|
const index = state.inbox.items.findIndex((candidate) => candidate.id === command.inputId);
|
|
377
|
-
if (index < 0) reason =
|
|
378
|
-
else if (command.beforeId !== null && !state.inbox.items.some((candidate) => candidate.id === command.beforeId)) reason = "not-
|
|
390
|
+
if (index < 0) reason = "not-pending";
|
|
391
|
+
else if (command.beforeId !== null && !state.inbox.items.some((candidate) => candidate.id === command.beforeId)) reason = "not-pending";
|
|
379
392
|
else {
|
|
380
393
|
const destination = command.beforeId === null ? state.inbox.items.length : state.inbox.items.findIndex((item) => item.id === command.beforeId);
|
|
381
394
|
moveInput({
|
|
@@ -388,18 +401,11 @@ const createControlRuntime = (options) => {
|
|
|
388
401
|
case "send-now": {
|
|
389
402
|
const position = state.inbox.items.findIndex((item) => item.id === command.inputId);
|
|
390
403
|
const selected = state.inbox.items[position];
|
|
391
|
-
if (!selected) reason =
|
|
404
|
+
if (!selected) reason = "not-pending";
|
|
392
405
|
else if ((state.active?.turnId ?? null) !== command.turnId) reason = "stale-turn";
|
|
393
406
|
else if (selected.message.role !== "user") reason = "not-user-input";
|
|
394
|
-
else if (!selected.generate && knownInput(`${selected.id}:assistant`)) reason = "duplicate-input";
|
|
395
407
|
else {
|
|
396
|
-
|
|
397
|
-
state.reservedIds = {
|
|
398
|
-
...state.reservedIds,
|
|
399
|
-
[`${selected.id}:assistant`]: true
|
|
400
|
-
};
|
|
401
|
-
selected.generate = true;
|
|
402
|
-
}
|
|
408
|
+
selected.generate = true;
|
|
403
409
|
if (state.active) selected.afterStepOf = state.active.turnId;
|
|
404
410
|
else delete selected.afterStepOf;
|
|
405
411
|
moveInput({
|
|
@@ -411,14 +417,13 @@ const createControlRuntime = (options) => {
|
|
|
411
417
|
}
|
|
412
418
|
case "stop":
|
|
413
419
|
if (state.active?.turnId !== command.turnId) reason = "stale-turn";
|
|
414
|
-
else end({
|
|
420
|
+
else await end({
|
|
415
421
|
lastSeenIndex: command.lastSeenIndex,
|
|
416
422
|
reason: void 0
|
|
417
423
|
});
|
|
418
424
|
break;
|
|
419
425
|
case "steer":
|
|
420
|
-
|
|
421
|
-
else insert({
|
|
426
|
+
insert({
|
|
422
427
|
message: command.message,
|
|
423
428
|
generate: true,
|
|
424
429
|
first: true,
|
|
@@ -428,7 +433,7 @@ const createControlRuntime = (options) => {
|
|
|
428
433
|
case "interrupt": {
|
|
429
434
|
const response = state.coordinator.response;
|
|
430
435
|
if (!response || response.responseMessageId !== command.request.messageId || command.request.generationId !== response.generation?.generationId && command.request.requestId !== response.activeRequestId) reason = "stale-turn";
|
|
431
|
-
else end({
|
|
436
|
+
else await end({
|
|
432
437
|
lastSeenIndex: command.request.lastSeenIndex,
|
|
433
438
|
reason: command.request.reason
|
|
434
439
|
});
|
|
@@ -441,7 +446,7 @@ const createControlRuntime = (options) => {
|
|
|
441
446
|
active.suspended = true;
|
|
442
447
|
active.version += 1;
|
|
443
448
|
const generation = state.coordinator.response?.generation;
|
|
444
|
-
if (generation) emit({
|
|
449
|
+
if (generation) await emit({
|
|
445
450
|
type: "ai.generation.paused",
|
|
446
451
|
payload: {
|
|
447
452
|
generationId: generation.generationId,
|
|
@@ -455,18 +460,18 @@ const createControlRuntime = (options) => {
|
|
|
455
460
|
state.inbox.paused = false;
|
|
456
461
|
break;
|
|
457
462
|
case "tool-result":
|
|
458
|
-
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";
|
|
459
|
-
else emit({
|
|
463
|
+
if (!state.coordinator.response?.calls.find((candidate) => candidate.call.requestId === command.result.requestId && 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";
|
|
464
|
+
else await emit({
|
|
460
465
|
type: "ai.tool.result",
|
|
461
466
|
payload: command.result
|
|
462
467
|
});
|
|
463
468
|
break;
|
|
464
469
|
case "approval": {
|
|
465
470
|
const response = state.coordinator.response;
|
|
466
|
-
const call = response?.calls.find((candidate) => candidate.approval?.approvalId === command.response.approvalId && candidate.approval.generationId === command.response.generationId && candidate.approval.messageId === command.response.messageId);
|
|
471
|
+
const call = response?.calls.find((candidate) => candidate.approval?.requestId === command.response.requestId && candidate.approval.approvalId === command.response.approvalId && candidate.approval.generationId === command.response.generationId && candidate.approval.messageId === command.response.messageId);
|
|
467
472
|
if (response?.failure) reason = "stale-turn";
|
|
468
473
|
else if (!call || call.response || call.terminal) reason = "not-found";
|
|
469
|
-
else emit({
|
|
474
|
+
else await emit({
|
|
470
475
|
type: "ai.approval.responded",
|
|
471
476
|
payload: command.response
|
|
472
477
|
});
|
|
@@ -476,8 +481,8 @@ const createControlRuntime = (options) => {
|
|
|
476
481
|
case "input": {
|
|
477
482
|
const payload = command.action === "input" ? command.response : command.request;
|
|
478
483
|
const response = state.coordinator.response;
|
|
479
|
-
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";
|
|
480
|
-
else emit({
|
|
484
|
+
if (response?.failure !== void 0 || response?.generation?.requestId !== payload.requestId || 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";
|
|
485
|
+
else await emit({
|
|
481
486
|
type: command.action === "input" ? "ai.input.responded" : "ai.input.requested",
|
|
482
487
|
payload
|
|
483
488
|
});
|
|
@@ -487,7 +492,7 @@ const createControlRuntime = (options) => {
|
|
|
487
492
|
const response = state.coordinator.response;
|
|
488
493
|
if (!state.active || response?.status !== "failed" || response.rootMessageId !== command.request.messageId || response.responseMessageId !== command.request.responseMessageId) reason = "stale-turn";
|
|
489
494
|
else if (!state.inbox.paused && !state.active.suspended) {
|
|
490
|
-
emit({
|
|
495
|
+
await emit({
|
|
491
496
|
type: "ai.retry.requested",
|
|
492
497
|
payload: command.request
|
|
493
498
|
});
|
|
@@ -497,7 +502,7 @@ const createControlRuntime = (options) => {
|
|
|
497
502
|
responseMessageId: response.responseMessageId,
|
|
498
503
|
reason: "retry"
|
|
499
504
|
},
|
|
500
|
-
id:
|
|
505
|
+
id: await aiEventId("a2.ai.request.retry", options.agent.contract.name, ctx.event.sessionId, command.request.retryId)
|
|
501
506
|
});
|
|
502
507
|
} else reason = "stale-turn";
|
|
503
508
|
break;
|
|
@@ -505,6 +510,10 @@ const createControlRuntime = (options) => {
|
|
|
505
510
|
}
|
|
506
511
|
} else {
|
|
507
512
|
const report = ctx.event.payload;
|
|
513
|
+
if (report.kind === "tool" && report.settled && state.coordinator.unsettledTools) {
|
|
514
|
+
delete state.coordinator.unsettledTools[report.workId];
|
|
515
|
+
if (Object.keys(state.coordinator.unsettledTools).length === 0) delete state.coordinator.unsettledTools;
|
|
516
|
+
}
|
|
508
517
|
const active = state.active;
|
|
509
518
|
const call = state.coordinator.response?.calls.find((candidate) => candidate.work?.id === report.workId);
|
|
510
519
|
const work = report.kind === "model" ? active?.model : call?.work;
|
|
@@ -515,7 +524,7 @@ const createControlRuntime = (options) => {
|
|
|
515
524
|
for (const event of report.events) {
|
|
516
525
|
if (work.facts?.includes(event.id)) continue;
|
|
517
526
|
if (authorized || report.kind === "tool" && event.type === "ai.tool.result") {
|
|
518
|
-
emit({
|
|
527
|
+
await emit({
|
|
519
528
|
type: event.type,
|
|
520
529
|
payload: event.payload,
|
|
521
530
|
id: event.id
|
|
@@ -523,7 +532,10 @@ const createControlRuntime = (options) => {
|
|
|
523
532
|
work.facts = [...work.facts ?? [], event.id];
|
|
524
533
|
}
|
|
525
534
|
}
|
|
526
|
-
if (report.settled)
|
|
535
|
+
if (report.settled) {
|
|
536
|
+
work.settled = true;
|
|
537
|
+
delete work.facts;
|
|
538
|
+
}
|
|
527
539
|
if (!authorized) reason = "stale-work";
|
|
528
540
|
}
|
|
529
541
|
}
|
|
@@ -544,7 +556,7 @@ const createControlRuntime = (options) => {
|
|
|
544
556
|
if (!active.suspended && response.failure === void 0) for (const call of response.calls) {
|
|
545
557
|
if (call.terminal || call.work !== void 0 && (!call.work.settled || call.work.version === active.version) || call.call.providerExecuted === true || call.approval && !call.response) continue;
|
|
546
558
|
if (call.response?.approved === false) {
|
|
547
|
-
emit({
|
|
559
|
+
await emit({
|
|
548
560
|
type: "ai.tool.result",
|
|
549
561
|
payload: {
|
|
550
562
|
...call.call,
|
|
@@ -554,12 +566,16 @@ const createControlRuntime = (options) => {
|
|
|
554
566
|
});
|
|
555
567
|
continue;
|
|
556
568
|
}
|
|
557
|
-
const id =
|
|
569
|
+
const id = await aiEventId("a2.ai.tool.execution", ctx.event.id, call.call.generationId, call.call.toolCallId);
|
|
558
570
|
call.work = {
|
|
559
571
|
id,
|
|
560
572
|
version: active.version,
|
|
561
573
|
settled: false
|
|
562
574
|
};
|
|
575
|
+
state.coordinator.unsettledTools = {
|
|
576
|
+
...state.coordinator.unsettledTools,
|
|
577
|
+
[id]: true
|
|
578
|
+
};
|
|
563
579
|
jobs.push({
|
|
564
580
|
type: "ai.tool.execution.requested",
|
|
565
581
|
id,
|
|
@@ -579,7 +595,7 @@ const createControlRuntime = (options) => {
|
|
|
579
595
|
const handoffReady = handoff && workSettled && !externalPending && (active.suspended || response.failure !== void 0 || response.calls.every((call) => callReadyForModel(call) || approvalBlocksExecution(call)));
|
|
580
596
|
const readyForModel = !active.suspended && response.failure === void 0 && modelDone && callsDone && response.inputs.length === 0;
|
|
581
597
|
const naturalCompletion = readyForModel && response.completion !== void 0 && response.completion.finishReason !== "tool-calls";
|
|
582
|
-
if (handoffReady && !naturalCompletion) end({
|
|
598
|
+
if (handoffReady && !naturalCompletion) await end({
|
|
583
599
|
lastSeenIndex: void 0,
|
|
584
600
|
reason: "steered after step"
|
|
585
601
|
});
|
|
@@ -588,17 +604,19 @@ const createControlRuntime = (options) => {
|
|
|
588
604
|
const input = response.inputResponse;
|
|
589
605
|
if (!handoff && (resume || continuationReady(state.coordinator) || response.completion && input)) {
|
|
590
606
|
const nextReason = resume ? "resume" : input ? "input" : "tool";
|
|
591
|
-
const
|
|
607
|
+
const sourceGenerationId = (response.generation ?? response.source?.generation)?.generationId;
|
|
608
|
+
const id = nextReason === "tool" ? await aiEventId("a2.ai.request.tool", response.generation.generationId) : await aiEventId("a2.ai.request", ctx.event.id, nextReason);
|
|
592
609
|
requestModel({
|
|
593
610
|
request: {
|
|
594
611
|
messageId: response.responseMessageId,
|
|
595
612
|
responseMessageId: response.responseMessageId,
|
|
596
|
-
reason: nextReason
|
|
613
|
+
reason: nextReason,
|
|
614
|
+
...sourceGenerationId === void 0 ? {} : { sourceGenerationId }
|
|
597
615
|
},
|
|
598
616
|
id
|
|
599
617
|
});
|
|
600
618
|
} else if (naturalCompletion) {
|
|
601
|
-
emit({
|
|
619
|
+
await emit({
|
|
602
620
|
type: "ai.message.completed",
|
|
603
621
|
payload: { messageId: response.responseMessageId }
|
|
604
622
|
});
|
|
@@ -610,13 +628,13 @@ const createControlRuntime = (options) => {
|
|
|
610
628
|
if (state.active === null && !state.inbox.paused && !state.coordinator.closed) while (state.inbox.items.length > 0) {
|
|
611
629
|
const item = state.inbox.items.shift();
|
|
612
630
|
delete item.afterStepOf;
|
|
613
|
-
emit({
|
|
631
|
+
await emit({
|
|
614
632
|
type: "ai.message.created",
|
|
615
633
|
payload: {
|
|
616
634
|
message: item.message,
|
|
617
635
|
generate: item.generate
|
|
618
636
|
},
|
|
619
|
-
id:
|
|
637
|
+
id: await aiEventId("a2.ai.admitted", options.agent.contract.name, ctx.event.sessionId, item.id)
|
|
620
638
|
});
|
|
621
639
|
if (!item.generate) continue;
|
|
622
640
|
state.active = {
|
|
@@ -627,12 +645,14 @@ const createControlRuntime = (options) => {
|
|
|
627
645
|
suspended: false,
|
|
628
646
|
model: null
|
|
629
647
|
};
|
|
648
|
+
const requestId = await aiEventId("a2.ai.request.message", options.agent.contract.name, ctx.event.sessionId, item.id);
|
|
630
649
|
requestModel({
|
|
631
650
|
request: {
|
|
632
651
|
messageId: item.id,
|
|
652
|
+
responseMessageId: requestId,
|
|
633
653
|
reason: "message"
|
|
634
654
|
},
|
|
635
|
-
id:
|
|
655
|
+
id: requestId
|
|
636
656
|
});
|
|
637
657
|
break;
|
|
638
658
|
}
|
|
@@ -646,6 +666,7 @@ const createControlRuntime = (options) => {
|
|
|
646
666
|
...facts,
|
|
647
667
|
{
|
|
648
668
|
type: "ai.control.committed",
|
|
669
|
+
id: await aiEventId("a2.ai.control.committed", ctx.event.id),
|
|
649
670
|
payload: controlCommit({
|
|
650
671
|
before,
|
|
651
672
|
after: state,
|
|
@@ -655,11 +676,12 @@ const createControlRuntime = (options) => {
|
|
|
655
676
|
...jobs,
|
|
656
677
|
{
|
|
657
678
|
type: "ai.control.decided",
|
|
679
|
+
id: await aiEventId("a2.ai.control.decided", ctx.event.id),
|
|
658
680
|
payload: receipt
|
|
659
681
|
}
|
|
660
682
|
];
|
|
661
683
|
};
|
|
662
|
-
const report = ({ ctx, events, settled, name }) => {
|
|
684
|
+
const report = async ({ ctx, events, settled, name }) => {
|
|
663
685
|
const payload = ctx.event.payload;
|
|
664
686
|
const { turnId, version } = "turnId" in payload ? payload : payload.control;
|
|
665
687
|
for (const event of events) {
|
|
@@ -669,7 +691,7 @@ const createControlRuntime = (options) => {
|
|
|
669
691
|
}
|
|
670
692
|
return {
|
|
671
693
|
type: "ai.work.reported",
|
|
672
|
-
id:
|
|
694
|
+
id: await aiEventId("a2.ai.report", ctx.event.id, String(ctx.attempt), name),
|
|
673
695
|
payload: {
|
|
674
696
|
workId: ctx.event.id,
|
|
675
697
|
attempt: ctx.attempt,
|
|
@@ -677,11 +699,11 @@ const createControlRuntime = (options) => {
|
|
|
677
699
|
version,
|
|
678
700
|
kind: ctx.event.type === "ai.generation.requested" ? "model" : "tool",
|
|
679
701
|
settled,
|
|
680
|
-
events: events.filter((event) => event.type !== "ai.message.completed" && event.type !== "ai.message.interrupted").map((event, index) => ({
|
|
702
|
+
events: await Promise.all(events.filter((event) => event.type !== "ai.message.completed" && event.type !== "ai.message.interrupted").map(async (event, index) => ({
|
|
681
703
|
type: event.type,
|
|
682
704
|
payload: event.payload,
|
|
683
|
-
id: event.id ??
|
|
684
|
-
}))
|
|
705
|
+
id: event.id ?? await aiEventId("a2.ai.work.fact", ctx.event.id, String(ctx.attempt), name, String(index))
|
|
706
|
+
})))
|
|
685
707
|
}
|
|
686
708
|
};
|
|
687
709
|
};
|
|
@@ -689,7 +711,7 @@ const createControlRuntime = (options) => {
|
|
|
689
711
|
const progress = events.filter((event) => event.type === "ai.generation.progress");
|
|
690
712
|
const lifecycle = events.filter((event) => event.type !== "ai.generation.progress");
|
|
691
713
|
if (lifecycle.length === 0) return ctx.session.append(name, ...progress);
|
|
692
|
-
const input = report({
|
|
714
|
+
const input = await report({
|
|
693
715
|
ctx,
|
|
694
716
|
events: lifecycle,
|
|
695
717
|
settled: false,
|
|
@@ -744,6 +766,532 @@ const createControlRuntime = (options) => {
|
|
|
744
766
|
};
|
|
745
767
|
};
|
|
746
768
|
//#endregion
|
|
769
|
+
//#region src/ai-context-schema.ts
|
|
770
|
+
const record = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
771
|
+
const index = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
772
|
+
const id = (value) => typeof value === "string" && value.length > 0;
|
|
773
|
+
const nullableId = (value) => value === null || id(value);
|
|
774
|
+
const matches = (schema, value) => validateSync(schema, value, "AI context checkpoint").issues === void 0;
|
|
775
|
+
const isMessage = (value) => matches(events["ai.message.created"], { message: value });
|
|
776
|
+
const indexes = (value, messages) => record(value) && Object.values(value).every(index) && messages.every((message) => Object.hasOwn(value, message.id));
|
|
777
|
+
const compaction = (value) => value === null || record(value) && Object.keys(value).every((key) => [
|
|
778
|
+
"status",
|
|
779
|
+
"generationId",
|
|
780
|
+
"throughMessageId",
|
|
781
|
+
"throughIndex",
|
|
782
|
+
"summary"
|
|
783
|
+
].includes(key)) && ["running", "completed"].includes(String(value["status"])) && id(value["generationId"]) && id(value["throughMessageId"]) && index(value["throughIndex"]) && (value["summary"] === void 0 || typeof value["summary"] === "string");
|
|
784
|
+
const progress = (value) => {
|
|
785
|
+
if (!record(value) || !index(value["length"]) || !Array.isArray(value["tail"]) || !Array.isArray(value["blocks"])) return false;
|
|
786
|
+
const pending = [...value["blocks"].toReversed().filter((block) => block !== null), value["tail"]].toReversed();
|
|
787
|
+
const seen = /* @__PURE__ */ new Set();
|
|
788
|
+
let count = 0;
|
|
789
|
+
let last = -1;
|
|
790
|
+
while (pending.length > 0) {
|
|
791
|
+
const block = pending.pop();
|
|
792
|
+
if (typeof block !== "object" || block === null || seen.has(block)) return false;
|
|
793
|
+
seen.add(block);
|
|
794
|
+
if (Array.isArray(block)) for (const batch of block) {
|
|
795
|
+
if (!record(batch) || !index(batch["index"]) || batch["index"] <= last || !Array.isArray(batch["chunks"]) || !batch["chunks"].every((chunk) => record(chunk) && typeof chunk["type"] === "string")) return false;
|
|
796
|
+
count += 1;
|
|
797
|
+
last = batch["index"];
|
|
798
|
+
}
|
|
799
|
+
else if (record(block) && Object.keys(block).length === 2 && Object.hasOwn(block, "left") && Object.hasOwn(block, "right")) pending.push(block["right"], block["left"]);
|
|
800
|
+
else return false;
|
|
801
|
+
}
|
|
802
|
+
return count === value["length"];
|
|
803
|
+
};
|
|
804
|
+
const indexedEvents = (value, types) => Array.isArray(value) && value.every((event) => record(event) && index(event["index"]) && types.includes(event["type"]) && matches(events[event["type"]], event["payload"]));
|
|
805
|
+
const projection = (value, messages) => {
|
|
806
|
+
if (!record(value) || Object.keys(value).some((key) => ![
|
|
807
|
+
"generationId",
|
|
808
|
+
"responseMessageId",
|
|
809
|
+
"promptThroughIndex",
|
|
810
|
+
"completionIndex",
|
|
811
|
+
"baseMessage",
|
|
812
|
+
"baseCompaction",
|
|
813
|
+
"batches",
|
|
814
|
+
"toolEvents",
|
|
815
|
+
"approvalEvents",
|
|
816
|
+
"cursor"
|
|
817
|
+
].includes(key)) || !id(value["generationId"]) || !id(value["responseMessageId"]) || !index(value["promptThroughIndex"]) || value["completionIndex"] !== void 0 && !index(value["completionIndex"]) || value["baseMessage"] !== void 0 && !isMessage(value["baseMessage"]) || value["baseCompaction"] !== void 0 && !compaction(value["baseCompaction"]) || !progress(value["batches"]) || !indexedEvents(value["toolEvents"], ["ai.tool.called", "ai.tool.result"]) || !indexedEvents(value["approvalEvents"], ["ai.approval.requested", "ai.approval.responded"])) return false;
|
|
818
|
+
const cursor = value["cursor"];
|
|
819
|
+
if (cursor === void 0 || cursor === null) return true;
|
|
820
|
+
if (!record(cursor) || !index(cursor["throughIndex"]) || !record(cursor["stream"])) return false;
|
|
821
|
+
const stream = cursor["stream"];
|
|
822
|
+
const current = messages.find((message) => message.id === value["responseMessageId"]);
|
|
823
|
+
if (!current || !["text", "reasoning"].every((key) => record(stream[key]) && Object.values(stream[key]).every((part) => index(part) && part < current.parts.length)) || !record(stream["tools"])) return false;
|
|
824
|
+
return Object.values(stream["tools"]).every((tool) => record(tool) && typeof tool["text"] === "string" && typeof tool["toolName"] === "string" && typeof tool["dynamic"] === "boolean");
|
|
825
|
+
};
|
|
826
|
+
const authority = (value) => value === null || record(value) && Object.keys(value).every((key) => [
|
|
827
|
+
"requestId",
|
|
828
|
+
"responseMessageId",
|
|
829
|
+
"sourceGenerationId",
|
|
830
|
+
"requestReason",
|
|
831
|
+
"phase"
|
|
832
|
+
].includes(key)) && [
|
|
833
|
+
"requestId",
|
|
834
|
+
"responseMessageId",
|
|
835
|
+
"sourceGenerationId"
|
|
836
|
+
].every((key) => nullableId(value[key])) && (value["requestReason"] === null || [
|
|
837
|
+
"message",
|
|
838
|
+
"tool",
|
|
839
|
+
"input",
|
|
840
|
+
"resume",
|
|
841
|
+
"retry"
|
|
842
|
+
].includes(String(value["requestReason"]))) && (value["phase"] === null || [
|
|
843
|
+
"starting",
|
|
844
|
+
"generating",
|
|
845
|
+
"tools",
|
|
846
|
+
"waiting",
|
|
847
|
+
"failed",
|
|
848
|
+
"pausing",
|
|
849
|
+
"paused"
|
|
850
|
+
].includes(String(value["phase"])));
|
|
851
|
+
const contextSchema = () => ({ "~standard": {
|
|
852
|
+
version: 1,
|
|
853
|
+
vendor: "a2-context",
|
|
854
|
+
validate(value) {
|
|
855
|
+
const invalid = { issues: [{ message: "invalid AI model context" }] };
|
|
856
|
+
const keys = [
|
|
857
|
+
"messages",
|
|
858
|
+
"compaction",
|
|
859
|
+
"modelMetadata",
|
|
860
|
+
"closed",
|
|
861
|
+
"authority",
|
|
862
|
+
"generation",
|
|
863
|
+
"activeProjection",
|
|
864
|
+
"messageIndexes"
|
|
865
|
+
];
|
|
866
|
+
try {
|
|
867
|
+
if (!record(value) || keys.some((key) => !Object.hasOwn(value, key)) || Object.keys(value).some((key) => !keys.includes(key))) return invalid;
|
|
868
|
+
const messages = value["messages"];
|
|
869
|
+
const metadata = value["modelMetadata"];
|
|
870
|
+
const generation = value["generation"];
|
|
871
|
+
const current = value["activeProjection"];
|
|
872
|
+
if (!Array.isArray(messages) || !messages.every(isMessage) || !compaction(value["compaction"]) || typeof value["closed"] !== "boolean" || !authority(value["authority"]) || !record(metadata) || !Object.entries(metadata).every(([modelId, entry]) => record(entry) && (entry["status"] === "pending" || entry["status"] === "unavailable" || entry["status"] === "resolved" && matches(events["ai.model.metadata.resolved"], {
|
|
873
|
+
modelId,
|
|
874
|
+
limits: entry["limits"]
|
|
875
|
+
}))) || !indexes(value["messageIndexes"], messages)) return invalid;
|
|
876
|
+
if (generation !== null && (!record(generation) || !matches(events["ai.generation.started"], generation["owner"]) || ![
|
|
877
|
+
"streaming",
|
|
878
|
+
"completed",
|
|
879
|
+
"paused",
|
|
880
|
+
"failed",
|
|
881
|
+
"superseded"
|
|
882
|
+
].includes(String(generation["phase"])) || generation["responseIndex"] !== void 0 && !index(generation["responseIndex"]) || generation["phase"] === "failed" && typeof generation["error"] !== "string")) return invalid;
|
|
883
|
+
const owner = record(generation) && record(generation["owner"]) ? generation["owner"] : void 0;
|
|
884
|
+
if (current !== null && (!projection(current, messages) || !record(current) || current["generationId"] !== owner?.["generationId"] || current["responseMessageId"] !== owner?.["responseMessageId"])) return invalid;
|
|
885
|
+
if (generation !== null && generation["phase"] !== "superseded" && current === null) return invalid;
|
|
886
|
+
if (value["closed"] && (generation !== null || current !== null)) return invalid;
|
|
887
|
+
return { value };
|
|
888
|
+
} catch {
|
|
889
|
+
return invalid;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
} });
|
|
893
|
+
//#endregion
|
|
894
|
+
//#region src/ai-context.ts
|
|
895
|
+
const replaceResponse = ({ state, message, index }) => {
|
|
896
|
+
let messages = upsertResponse({
|
|
897
|
+
messages: state.messages,
|
|
898
|
+
message,
|
|
899
|
+
inputId: state.generation.owner.messageId
|
|
900
|
+
});
|
|
901
|
+
if (state.generation?.responseIndex !== void 0 && !state.messages.some((item) => item.id === message.id)) {
|
|
902
|
+
messages = messages.filter((item) => item.id !== message.id);
|
|
903
|
+
messages.splice(state.generation.responseIndex, 0, message);
|
|
904
|
+
}
|
|
905
|
+
return {
|
|
906
|
+
...state,
|
|
907
|
+
messages,
|
|
908
|
+
messageIndexes: Object.hasOwn(state.messageIndexes, message.id) ? state.messageIndexes : {
|
|
909
|
+
...state.messageIndexes,
|
|
910
|
+
[message.id]: index
|
|
911
|
+
}
|
|
912
|
+
};
|
|
913
|
+
};
|
|
914
|
+
const calledTools = (projection) => /* @__PURE__ */ new Set([...projection.baseMessage?.parts.flatMap((part) => "toolCallId" in part ? [part.toolCallId] : []) ?? [], ...projection.toolEvents.filter((event) => event.type === "ai.tool.called").map((event) => event.payload.toolCallId)]);
|
|
915
|
+
const renderProjection = ({ state, index }) => {
|
|
916
|
+
const projection = state.activeProjection;
|
|
917
|
+
let message = projectedMessage(projection);
|
|
918
|
+
if (projection.baseMessage === void 0 && projection.batches.length === 0 && message.parts.length === 0) return state;
|
|
919
|
+
if (state.generation?.phase === "paused") message = pauseUIMessage({
|
|
920
|
+
message,
|
|
921
|
+
toolCallIds: calledTools(projection)
|
|
922
|
+
});
|
|
923
|
+
if (state.generation?.phase === "failed") message = interruptUIMessage(message, `Generation failed before tool completion: ${state.generation.error}`);
|
|
924
|
+
return replaceResponse({
|
|
925
|
+
state: {
|
|
926
|
+
...state,
|
|
927
|
+
activeProjection: {
|
|
928
|
+
...projection,
|
|
929
|
+
cursor: null
|
|
930
|
+
}
|
|
931
|
+
},
|
|
932
|
+
message,
|
|
933
|
+
index
|
|
934
|
+
});
|
|
935
|
+
};
|
|
936
|
+
const generationBase = (state) => {
|
|
937
|
+
const projection = state.activeProjection;
|
|
938
|
+
if (!projection) return state;
|
|
939
|
+
return {
|
|
940
|
+
...state,
|
|
941
|
+
messages: projection.baseMessage === void 0 ? state.messages.filter((message) => message.id !== projection.responseMessageId) : upsertMessage(state.messages, projection.baseMessage),
|
|
942
|
+
compaction: projection.baseCompaction ?? null
|
|
943
|
+
};
|
|
944
|
+
};
|
|
945
|
+
const contextForAttempt = (state) => ({
|
|
946
|
+
...state.activeProjection?.completionIndex === void 0 ? generationBase(state) : state,
|
|
947
|
+
generation: state.generation && {
|
|
948
|
+
...state.generation,
|
|
949
|
+
phase: "superseded"
|
|
950
|
+
},
|
|
951
|
+
activeProjection: null
|
|
952
|
+
});
|
|
953
|
+
const endGeneration = (state) => ({
|
|
954
|
+
...state,
|
|
955
|
+
authority: null,
|
|
956
|
+
generation: null,
|
|
957
|
+
activeProjection: null
|
|
958
|
+
});
|
|
959
|
+
const ownsGeneration = (state, owner) => state.generation?.owner.generationId === owner.generationId && state.generation.owner.requestId === owner.requestId && state.generation.phase !== "superseded";
|
|
960
|
+
const createContextReducer = ({ agent }) => {
|
|
961
|
+
const reducer = agent.contract.reducer({
|
|
962
|
+
name: "a2.ai.context.v3",
|
|
963
|
+
initialState: {
|
|
964
|
+
messages: [],
|
|
965
|
+
compaction: null,
|
|
966
|
+
modelMetadata: {},
|
|
967
|
+
closed: false,
|
|
968
|
+
authority: null,
|
|
969
|
+
generation: null,
|
|
970
|
+
activeProjection: null,
|
|
971
|
+
messageIndexes: {}
|
|
972
|
+
},
|
|
973
|
+
stateSchema: contextSchema()
|
|
974
|
+
}).fold((state, event) => reduceContext(state, event));
|
|
975
|
+
return storedAIReducer(reducer);
|
|
976
|
+
};
|
|
977
|
+
const reduceContext = (state, event) => {
|
|
978
|
+
if (state.closed) return state;
|
|
979
|
+
switch (event.type) {
|
|
980
|
+
case "ai.control.committed": {
|
|
981
|
+
const { view } = event.payload;
|
|
982
|
+
const { requestId, requestReason, sourceGenerationId, responseMessageId, phase } = view;
|
|
983
|
+
const next = {
|
|
984
|
+
...state,
|
|
985
|
+
authority: {
|
|
986
|
+
requestId,
|
|
987
|
+
requestReason,
|
|
988
|
+
sourceGenerationId,
|
|
989
|
+
responseMessageId,
|
|
990
|
+
phase
|
|
991
|
+
},
|
|
992
|
+
closed: view.closed
|
|
993
|
+
};
|
|
994
|
+
return view.closed ? endGeneration(next) : next;
|
|
995
|
+
}
|
|
996
|
+
case "ai.session.closed": return endGeneration({
|
|
997
|
+
...state,
|
|
998
|
+
closed: true,
|
|
999
|
+
compaction: state.compaction?.status === "running" ? state.activeProjection?.baseCompaction ?? null : state.compaction
|
|
1000
|
+
});
|
|
1001
|
+
case "ai.message.created": {
|
|
1002
|
+
const { message } = event.payload;
|
|
1003
|
+
return {
|
|
1004
|
+
...state,
|
|
1005
|
+
messages: upsertMessage(state.messages, message),
|
|
1006
|
+
activeProjection: state.activeProjection?.responseMessageId === message.id ? {
|
|
1007
|
+
...state.activeProjection,
|
|
1008
|
+
cursor: null
|
|
1009
|
+
} : state.activeProjection,
|
|
1010
|
+
messageIndexes: {
|
|
1011
|
+
...state.messageIndexes,
|
|
1012
|
+
[message.id]: event.index
|
|
1013
|
+
}
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
case "ai.generation.requested": {
|
|
1017
|
+
const request = event.payload;
|
|
1018
|
+
if (state.authority?.requestId !== event.id || request.responseMessageId !== state.authority.responseMessageId || request.reason !== state.authority.requestReason || (request.sourceGenerationId ?? null) !== state.authority.sourceGenerationId || request.reason !== "retry") return state;
|
|
1019
|
+
return contextForAttempt(state);
|
|
1020
|
+
}
|
|
1021
|
+
case "ai.generation.started": {
|
|
1022
|
+
const owner = event.payload;
|
|
1023
|
+
if (state.authority?.requestId !== owner.requestId || state.authority.responseMessageId !== owner.responseMessageId || state.authority.phase === "paused" || state.authority.phase === "pausing") return state;
|
|
1024
|
+
const previous = state.generation;
|
|
1025
|
+
if (previous?.owner.requestId === owner.requestId && (previous.owner.attempt >= owner.attempt || previous.phase === "failed" || previous.phase === "completed" || state.activeProjection?.completionIndex !== void 0)) return state;
|
|
1026
|
+
const base = previous?.owner.requestId === owner.requestId ? generationBase(state) : state;
|
|
1027
|
+
const baseMessage = base.messages.find((message) => message.id === owner.responseMessageId);
|
|
1028
|
+
return {
|
|
1029
|
+
...base,
|
|
1030
|
+
generation: {
|
|
1031
|
+
owner,
|
|
1032
|
+
phase: "streaming",
|
|
1033
|
+
...previous?.owner.responseMessageId === owner.responseMessageId && previous.responseIndex !== void 0 ? { responseIndex: previous.responseIndex } : {}
|
|
1034
|
+
},
|
|
1035
|
+
activeProjection: {
|
|
1036
|
+
generationId: owner.generationId,
|
|
1037
|
+
responseMessageId: owner.responseMessageId,
|
|
1038
|
+
promptThroughIndex: owner.promptThroughIndex ?? event.index - 1,
|
|
1039
|
+
...baseMessage === void 0 ? {} : { baseMessage },
|
|
1040
|
+
baseCompaction: base.compaction,
|
|
1041
|
+
batches: progressBatches(),
|
|
1042
|
+
toolEvents: [],
|
|
1043
|
+
approvalEvents: []
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
case "ai.generation.progress": {
|
|
1048
|
+
const payload = event.payload;
|
|
1049
|
+
if (!ownsGeneration(state, payload) || state.generation.owner.responseMessageId !== payload.responseMessageId || state.generation.phase !== "streaming" || !state.activeProjection) return state;
|
|
1050
|
+
const projection = state.activeProjection;
|
|
1051
|
+
const progress = advanceProjection({
|
|
1052
|
+
projection,
|
|
1053
|
+
base: state.messages.find((message) => message.id === projection.responseMessageId),
|
|
1054
|
+
chunks: payload.chunks,
|
|
1055
|
+
index: event.index
|
|
1056
|
+
});
|
|
1057
|
+
return replaceResponse({
|
|
1058
|
+
state: {
|
|
1059
|
+
...state,
|
|
1060
|
+
activeProjection: {
|
|
1061
|
+
...projection,
|
|
1062
|
+
batches: progress.batches,
|
|
1063
|
+
cursor: progress.cursor
|
|
1064
|
+
}
|
|
1065
|
+
},
|
|
1066
|
+
message: progress.message,
|
|
1067
|
+
index: event.index
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
case "ai.generation.completed": {
|
|
1071
|
+
const payload = event.payload;
|
|
1072
|
+
if (!ownsGeneration(state, payload) || state.generation.phase !== "streaming") return state;
|
|
1073
|
+
return {
|
|
1074
|
+
...state,
|
|
1075
|
+
generation: {
|
|
1076
|
+
...state.generation,
|
|
1077
|
+
phase: "completed"
|
|
1078
|
+
},
|
|
1079
|
+
activeProjection: {
|
|
1080
|
+
...state.activeProjection,
|
|
1081
|
+
completionIndex: event.index,
|
|
1082
|
+
cursor: null
|
|
1083
|
+
}
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
case "ai.generation.failed": {
|
|
1087
|
+
const payload = event.payload;
|
|
1088
|
+
if (!ownsGeneration(state, payload)) return state;
|
|
1089
|
+
if (state.generation.phase !== "streaming" && !(payload.stepLimit && state.authority?.requestReason === "tool" && state.authority.sourceGenerationId === payload.generationId)) return state;
|
|
1090
|
+
if (payload.superseded) return {
|
|
1091
|
+
...generationBase(state),
|
|
1092
|
+
generation: {
|
|
1093
|
+
...state.generation,
|
|
1094
|
+
phase: "superseded"
|
|
1095
|
+
},
|
|
1096
|
+
activeProjection: null
|
|
1097
|
+
};
|
|
1098
|
+
return renderProjection({
|
|
1099
|
+
state: {
|
|
1100
|
+
...state,
|
|
1101
|
+
compaction: state.activeProjection?.baseCompaction ?? null,
|
|
1102
|
+
generation: {
|
|
1103
|
+
...state.generation,
|
|
1104
|
+
phase: "failed",
|
|
1105
|
+
error: payload.error
|
|
1106
|
+
}
|
|
1107
|
+
},
|
|
1108
|
+
index: event.index
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
case "ai.message.interrupted": {
|
|
1112
|
+
const payload = event.payload;
|
|
1113
|
+
const owner = state.generation?.owner;
|
|
1114
|
+
const ownsRequest = payload.requestId !== void 0 && payload.requestId === state.authority?.requestId && payload.messageId === state.authority.responseMessageId;
|
|
1115
|
+
const ownsOwner = owner?.responseMessageId === payload.messageId && (payload.generationId === owner.generationId || payload.requestId === owner.requestId);
|
|
1116
|
+
if (!ownsRequest && !ownsOwner) return state;
|
|
1117
|
+
if (ownsOwner && state.authority?.requestId !== owner.requestId && !ownsRequest) return state;
|
|
1118
|
+
const projection = state.activeProjection;
|
|
1119
|
+
if (!projection) return endGeneration(state);
|
|
1120
|
+
const cutoff = payload.lastSeenIndex ?? Number.POSITIVE_INFINITY;
|
|
1121
|
+
const retained = {
|
|
1122
|
+
...projection,
|
|
1123
|
+
batches: progressBatches(flattenProgressBatches(projection.batches).filter((batch) => batch.index <= cutoff)),
|
|
1124
|
+
toolEvents: projection.toolEvents.filter((item) => item.index <= cutoff),
|
|
1125
|
+
approvalEvents: projection.approvalEvents.filter((item) => item.index <= cutoff)
|
|
1126
|
+
};
|
|
1127
|
+
let next = generationBase(state);
|
|
1128
|
+
if (retained.baseMessage !== void 0 || retained.batches.length > 0) next = replaceResponse({
|
|
1129
|
+
state: next,
|
|
1130
|
+
message: interruptUIMessage(projectedMessage(retained)),
|
|
1131
|
+
index: event.index
|
|
1132
|
+
});
|
|
1133
|
+
return endGeneration(next);
|
|
1134
|
+
}
|
|
1135
|
+
case "ai.message.completed": {
|
|
1136
|
+
const { messageId } = event.payload;
|
|
1137
|
+
return state.generation?.owner.responseMessageId === messageId && state.generation.phase !== "failed" && state.generation.phase !== "superseded" ? endGeneration(state) : state;
|
|
1138
|
+
}
|
|
1139
|
+
case "ai.generation.paused": {
|
|
1140
|
+
const { generationId } = event.payload;
|
|
1141
|
+
if (state.generation?.owner.generationId !== generationId || state.generation.phase === "failed" || state.generation.phase === "superseded" || !state.activeProjection) return state;
|
|
1142
|
+
const toolCallIds = calledTools(state.activeProjection);
|
|
1143
|
+
return renderProjection({
|
|
1144
|
+
state: {
|
|
1145
|
+
...state,
|
|
1146
|
+
generation: {
|
|
1147
|
+
...state.generation,
|
|
1148
|
+
phase: "paused"
|
|
1149
|
+
},
|
|
1150
|
+
compaction: state.compaction?.status === "running" ? state.activeProjection.baseCompaction ?? null : state.compaction,
|
|
1151
|
+
activeProjection: {
|
|
1152
|
+
...state.activeProjection,
|
|
1153
|
+
cursor: null,
|
|
1154
|
+
batches: progressBatches(flattenProgressBatches(state.activeProjection.batches).map((batch) => ({
|
|
1155
|
+
index: batch.index,
|
|
1156
|
+
chunks: batch.chunks.filter((chunk) => !("toolCallId" in chunk) || toolCallIds.has(chunk.toolCallId))
|
|
1157
|
+
})))
|
|
1158
|
+
}
|
|
1159
|
+
},
|
|
1160
|
+
index: event.index
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
case "ai.tool.called":
|
|
1164
|
+
case "ai.tool.result": {
|
|
1165
|
+
const payload = event.payload;
|
|
1166
|
+
if (!ownsGeneration(state, payload) || !state.activeProjection || state.generation.owner.responseMessageId !== payload.messageId) return state;
|
|
1167
|
+
if (state.generation.phase === "failed") {
|
|
1168
|
+
const result = payload;
|
|
1169
|
+
const running = state.activeProjection.toolEvents.reduce((tools, item) => reduceToolActivity(tools, item), []).some((tool) => tool.toolCallId === payload.toolCallId && tool.status === "running");
|
|
1170
|
+
if (event.type !== "ai.tool.result" || result.preliminary || !running) return state;
|
|
1171
|
+
}
|
|
1172
|
+
const toolEvent = event.type === "ai.tool.called" ? {
|
|
1173
|
+
index: event.index,
|
|
1174
|
+
type: "ai.tool.called",
|
|
1175
|
+
payload
|
|
1176
|
+
} : {
|
|
1177
|
+
index: event.index,
|
|
1178
|
+
type: "ai.tool.result",
|
|
1179
|
+
payload
|
|
1180
|
+
};
|
|
1181
|
+
const next = {
|
|
1182
|
+
...state,
|
|
1183
|
+
activeProjection: {
|
|
1184
|
+
...state.activeProjection,
|
|
1185
|
+
toolEvents: [...state.activeProjection.toolEvents, toolEvent]
|
|
1186
|
+
}
|
|
1187
|
+
};
|
|
1188
|
+
if (event.type === "ai.tool.called") return next;
|
|
1189
|
+
next.activeProjection.cursor = null;
|
|
1190
|
+
return renderProjection({
|
|
1191
|
+
state: next,
|
|
1192
|
+
index: event.index
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
case "ai.approval.requested":
|
|
1196
|
+
case "ai.approval.responded": {
|
|
1197
|
+
const payload = event.payload;
|
|
1198
|
+
if (!ownsGeneration(state, payload) || !state.activeProjection || state.generation.phase === "failed" || state.generation.owner.responseMessageId !== payload.messageId) return state;
|
|
1199
|
+
const approvalEvent = event.type === "ai.approval.requested" ? {
|
|
1200
|
+
index: event.index,
|
|
1201
|
+
type: "ai.approval.requested",
|
|
1202
|
+
payload
|
|
1203
|
+
} : {
|
|
1204
|
+
index: event.index,
|
|
1205
|
+
type: "ai.approval.responded",
|
|
1206
|
+
payload
|
|
1207
|
+
};
|
|
1208
|
+
return renderProjection({
|
|
1209
|
+
state: {
|
|
1210
|
+
...state,
|
|
1211
|
+
activeProjection: {
|
|
1212
|
+
...state.activeProjection,
|
|
1213
|
+
cursor: null,
|
|
1214
|
+
approvalEvents: [...state.activeProjection.approvalEvents, approvalEvent]
|
|
1215
|
+
}
|
|
1216
|
+
},
|
|
1217
|
+
index: event.index
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
case "ai.compaction.requested": {
|
|
1221
|
+
const payload = event.payload;
|
|
1222
|
+
if (state.generation?.owner.generationId !== payload.generationId || state.generation.phase !== "streaming") return state;
|
|
1223
|
+
return {
|
|
1224
|
+
...state,
|
|
1225
|
+
compaction: {
|
|
1226
|
+
status: "running",
|
|
1227
|
+
generationId: payload.generationId,
|
|
1228
|
+
throughMessageId: payload.throughMessageId,
|
|
1229
|
+
throughIndex: payload.throughIndex ?? state.activeProjection.promptThroughIndex
|
|
1230
|
+
}
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
case "ai.compaction.completed": {
|
|
1234
|
+
const payload = event.payload;
|
|
1235
|
+
const projection = state.activeProjection;
|
|
1236
|
+
if (state.generation?.owner.generationId !== payload.generationId || state.generation.phase !== "streaming" || state.compaction?.status !== "running" || state.compaction.generationId !== payload.generationId || !projection) return state;
|
|
1237
|
+
const throughIndex = payload.throughIndex ?? projection.promptThroughIndex;
|
|
1238
|
+
const replacements = new Set(payload.messages.map((message) => message.id));
|
|
1239
|
+
const retained = new Set(payload.retainedMessageIds ?? []);
|
|
1240
|
+
const messages = [...payload.messages, ...state.messages.filter((message) => !replacements.has(message.id) && (retained.has(message.id) || (state.messageIndexes[message.id] ?? 0) > throughIndex))];
|
|
1241
|
+
const activeProjection = {
|
|
1242
|
+
...projection,
|
|
1243
|
+
cursor: null
|
|
1244
|
+
};
|
|
1245
|
+
delete activeProjection.baseMessage;
|
|
1246
|
+
const baseMessage = messages.find((message) => message.id === projection.responseMessageId);
|
|
1247
|
+
if (baseMessage !== void 0) activeProjection.baseMessage = baseMessage;
|
|
1248
|
+
const compaction = {
|
|
1249
|
+
status: "completed",
|
|
1250
|
+
generationId: payload.generationId,
|
|
1251
|
+
throughMessageId: payload.throughMessageId,
|
|
1252
|
+
throughIndex,
|
|
1253
|
+
...payload.summary === void 0 ? {} : { summary: payload.summary }
|
|
1254
|
+
};
|
|
1255
|
+
activeProjection.baseCompaction = compaction;
|
|
1256
|
+
return {
|
|
1257
|
+
...state,
|
|
1258
|
+
messages,
|
|
1259
|
+
compaction,
|
|
1260
|
+
generation: {
|
|
1261
|
+
...state.generation,
|
|
1262
|
+
responseIndex: payload.messages.length
|
|
1263
|
+
},
|
|
1264
|
+
activeProjection,
|
|
1265
|
+
messageIndexes: Object.fromEntries(messages.map((message) => [message.id, replacements.has(message.id) ? throughIndex : state.messageIndexes[message.id]]))
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
case "ai.model.metadata.requested": {
|
|
1269
|
+
const { modelId } = event.payload;
|
|
1270
|
+
return Object.hasOwn(state.modelMetadata, modelId) ? state : {
|
|
1271
|
+
...state,
|
|
1272
|
+
modelMetadata: {
|
|
1273
|
+
...state.modelMetadata,
|
|
1274
|
+
[modelId]: { status: "pending" }
|
|
1275
|
+
}
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
case "ai.model.metadata.resolved": {
|
|
1279
|
+
const { modelId, limits } = event.payload;
|
|
1280
|
+
return {
|
|
1281
|
+
...state,
|
|
1282
|
+
modelMetadata: {
|
|
1283
|
+
...state.modelMetadata,
|
|
1284
|
+
[modelId]: limits === null ? { status: "unavailable" } : {
|
|
1285
|
+
status: "resolved",
|
|
1286
|
+
limits
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
default: return state;
|
|
1292
|
+
}
|
|
1293
|
+
};
|
|
1294
|
+
//#endregion
|
|
747
1295
|
//#region src/ai-sdk-step.ts
|
|
748
1296
|
const CONTROLLED_SETTINGS = [
|
|
749
1297
|
"_internal",
|
|
@@ -885,6 +1433,40 @@ const readModelLimits = async (options) => {
|
|
|
885
1433
|
}
|
|
886
1434
|
};
|
|
887
1435
|
//#endregion
|
|
1436
|
+
//#region src/ai-input-tokens.ts
|
|
1437
|
+
const withoutProviderOptions = (value) => {
|
|
1438
|
+
const result = { ...value };
|
|
1439
|
+
Reflect.deleteProperty(result, "providerOptions");
|
|
1440
|
+
return result;
|
|
1441
|
+
};
|
|
1442
|
+
const estimatedMessage = (message) => ({
|
|
1443
|
+
...withoutProviderOptions(message),
|
|
1444
|
+
content: typeof message.content === "string" ? message.content : message.content.map((part) => part.type === "tool-result" ? {
|
|
1445
|
+
...withoutProviderOptions(part),
|
|
1446
|
+
output: {
|
|
1447
|
+
...withoutProviderOptions(part.output),
|
|
1448
|
+
...part.output.type === "content" ? { value: part.output.value.map(withoutProviderOptions) } : {}
|
|
1449
|
+
}
|
|
1450
|
+
} : withoutProviderOptions(part))
|
|
1451
|
+
});
|
|
1452
|
+
const estimateInputTokens = async (options) => {
|
|
1453
|
+
const tools = await Promise.all(Object.entries(options.tools).map(async ([name, tool]) => ({
|
|
1454
|
+
name,
|
|
1455
|
+
description: tool.description,
|
|
1456
|
+
inputSchema: await asSchema(tool.inputSchema).jsonSchema
|
|
1457
|
+
})));
|
|
1458
|
+
const instructions = options.instructions;
|
|
1459
|
+
return Math.ceil(Buffer.byteLength(JSON.stringify({
|
|
1460
|
+
messages: options.messages.map(estimatedMessage),
|
|
1461
|
+
instructions: instructions === void 0 || typeof instructions === "string" ? instructions : Array.isArray(instructions) ? instructions.map(estimatedMessage) : estimatedMessage(instructions),
|
|
1462
|
+
tools
|
|
1463
|
+
}), "utf8") / 4);
|
|
1464
|
+
};
|
|
1465
|
+
const measuredInputTokens = (options) => {
|
|
1466
|
+
const previous = options.calibration;
|
|
1467
|
+
return previous !== void 0 && previous.model === options.model && options.estimate >= previous.estimate ? Math.ceil(previous.inputTokens + options.estimate - previous.estimate) : options.estimate;
|
|
1468
|
+
};
|
|
1469
|
+
//#endregion
|
|
888
1470
|
//#region src/ai-server.ts
|
|
889
1471
|
/**
|
|
890
1472
|
* experimental-a2/ai/server — the server-only implementation of an experimental-a2/ai definition.
|
|
@@ -1066,7 +1648,7 @@ async function* consumeGeneration(options) {
|
|
|
1066
1648
|
}
|
|
1067
1649
|
const foldAIEvents = (options) => {
|
|
1068
1650
|
let state = options.state;
|
|
1069
|
-
for (const event of options.events) state = options.
|
|
1651
|
+
for (const event of options.events) state = options.reducer.fold(state, event);
|
|
1070
1652
|
return state;
|
|
1071
1653
|
};
|
|
1072
1654
|
const summarize = async (options) => {
|
|
@@ -1107,7 +1689,7 @@ const summarize = async (options) => {
|
|
|
1107
1689
|
role: "user",
|
|
1108
1690
|
content: instruction
|
|
1109
1691
|
}],
|
|
1110
|
-
responseMessageId:
|
|
1692
|
+
responseMessageId: await aiEventId("a2.ai.summary", options.context.generationId)
|
|
1111
1693
|
},
|
|
1112
1694
|
messageMetadata: void 0
|
|
1113
1695
|
});
|
|
@@ -1133,52 +1715,11 @@ const summarize = async (options) => {
|
|
|
1133
1715
|
};
|
|
1134
1716
|
const activeCompaction = (state) => {
|
|
1135
1717
|
const compaction = state.compaction;
|
|
1136
|
-
return compaction?.status === "completed"
|
|
1718
|
+
return compaction?.status === "completed" ? compaction : null;
|
|
1137
1719
|
};
|
|
1138
|
-
const contextMessages =
|
|
1139
|
-
const
|
|
1140
|
-
|
|
1141
|
-
const state = foldAIEvents({
|
|
1142
|
-
agent,
|
|
1143
|
-
events: appended,
|
|
1144
|
-
state: snapshot.state
|
|
1145
|
-
});
|
|
1146
|
-
const compaction = activeCompaction(state);
|
|
1147
|
-
const queued = new Set(options.coordinator.queued.map((item) => item.messageId));
|
|
1148
|
-
const visible = (messages) => messages.filter((message) => !queued.has(message.id));
|
|
1149
|
-
if (compaction === null) return visible(state.messages);
|
|
1150
|
-
const retained = new Set(compaction.retainedMessageIds ?? []);
|
|
1151
|
-
const throughIndex = compaction.throughIndex;
|
|
1152
|
-
if (throughIndex !== void 0) {
|
|
1153
|
-
const [prefix, priorCoordinator, tail] = await Promise.all([
|
|
1154
|
-
throughIndex === snapshot.index ? Promise.resolve(snapshot) : session.state(agent.reducer, { through: throughIndex }),
|
|
1155
|
-
session.state(aiCoordinatorReducer(agent.contract), { through: throughIndex }),
|
|
1156
|
-
throughIndex >= snapshot.index ? Promise.resolve([]) : session.history({
|
|
1157
|
-
gte: throughIndex + 1,
|
|
1158
|
-
lte: snapshot.index
|
|
1159
|
-
})
|
|
1160
|
-
]);
|
|
1161
|
-
for (const item of priorCoordinator.state.queued) retained.add(item.messageId);
|
|
1162
|
-
const events = options.excluded === void 0 ? tail : withoutGenerationLifecycle(tail, options.excluded);
|
|
1163
|
-
const context = foldAIEvents({
|
|
1164
|
-
agent,
|
|
1165
|
-
events: [...events, ...appended],
|
|
1166
|
-
state: {
|
|
1167
|
-
...prefix.state,
|
|
1168
|
-
messages: [...compaction.messages, ...prefix.state.messages.filter((message) => retained.has(message.id))],
|
|
1169
|
-
activeProjection: null
|
|
1170
|
-
}
|
|
1171
|
-
});
|
|
1172
|
-
const positions = /* @__PURE__ */ new Map();
|
|
1173
|
-
for (const message of [...compaction.messages, ...state.messages]) if (!positions.has(message.id)) positions.set(message.id, positions.size);
|
|
1174
|
-
return visible(context.messages.toSorted((left, right) => (positions.get(left.id) ?? positions.size) - (positions.get(right.id) ?? positions.size)));
|
|
1175
|
-
}
|
|
1176
|
-
const boundary = state.messages.findIndex((message) => message.id === compaction.throughMessageId);
|
|
1177
|
-
return visible([
|
|
1178
|
-
...compaction.messages,
|
|
1179
|
-
...state.messages.slice(0, boundary + 1).filter((message) => retained.has(message.id)),
|
|
1180
|
-
...state.messages.slice(boundary + 1)
|
|
1181
|
-
]);
|
|
1720
|
+
const contextMessages = ({ state, coordinator }) => {
|
|
1721
|
+
const queued = new Set(coordinator.queued.map((item) => item.messageId));
|
|
1722
|
+
return state.messages.filter((message) => !queued.has(message.id));
|
|
1182
1723
|
};
|
|
1183
1724
|
const modelContext = async (options) => {
|
|
1184
1725
|
const messages = await convertToModelMessages(options.messages, { tools: options.tools });
|
|
@@ -1188,25 +1729,9 @@ const modelContext = async (options) => {
|
|
|
1188
1729
|
content: summary
|
|
1189
1730
|
}, ...messages];
|
|
1190
1731
|
};
|
|
1191
|
-
const
|
|
1192
|
-
const tools = await Promise.all(Object.entries(options.tools).map(async ([name, tool]) => ({
|
|
1193
|
-
name,
|
|
1194
|
-
description: tool.description,
|
|
1195
|
-
inputSchema: await asSchema(tool.inputSchema).jsonSchema
|
|
1196
|
-
})));
|
|
1197
|
-
return Math.ceil(new TextEncoder().encode(JSON.stringify({
|
|
1198
|
-
messages: options.messages,
|
|
1199
|
-
instructions: options.instructions,
|
|
1200
|
-
tools
|
|
1201
|
-
})).byteLength / 4);
|
|
1202
|
-
};
|
|
1203
|
-
const measuredInputTokens = (options) => {
|
|
1204
|
-
const previous = options.calibration;
|
|
1205
|
-
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;
|
|
1206
|
-
};
|
|
1207
|
-
const toolCalledEvent = (payload) => ({
|
|
1732
|
+
const toolCalledEvent = async (payload) => ({
|
|
1208
1733
|
type: "ai.tool.called",
|
|
1209
|
-
id:
|
|
1734
|
+
id: await aiEventId("a2.ai.tool.called", payload.generationId, payload.toolCallId),
|
|
1210
1735
|
payload
|
|
1211
1736
|
});
|
|
1212
1737
|
const approvalClassification = (tools, generation, toolName) => {
|
|
@@ -1220,7 +1745,7 @@ const approvalClassification = (tools, generation, toolName) => {
|
|
|
1220
1745
|
if (typeof tool?.needsApproval === "function") return "unknown";
|
|
1221
1746
|
return tool?.needsApproval === true ? "approval" : "automatic";
|
|
1222
1747
|
};
|
|
1223
|
-
const lifecycleEvents = (options) => {
|
|
1748
|
+
const lifecycleEvents = async (options) => {
|
|
1224
1749
|
const result = [];
|
|
1225
1750
|
const pending = [...options.pending];
|
|
1226
1751
|
const take = (toolCallId) => {
|
|
@@ -1228,12 +1753,12 @@ const lifecycleEvents = (options) => {
|
|
|
1228
1753
|
if (index === -1) return void 0;
|
|
1229
1754
|
return pending.splice(index, 1)[0];
|
|
1230
1755
|
};
|
|
1231
|
-
const flush = (toolCallId) => {
|
|
1756
|
+
const flush = async (toolCallId) => {
|
|
1232
1757
|
const pendingCall = take(toolCallId);
|
|
1233
|
-
if (pendingCall) result.push(toolCalledEvent(pendingCall.call));
|
|
1758
|
+
if (pendingCall) result.push(await toolCalledEvent(pendingCall.call));
|
|
1234
1759
|
};
|
|
1235
1760
|
for (const [chunkIndex, chunk] of options.chunks.entries()) {
|
|
1236
|
-
const resultId =
|
|
1761
|
+
const resultId = () => aiEventId("a2.ai.tool.stream-result", options.generationId, String(options.sequence), String(chunkIndex));
|
|
1237
1762
|
if (chunk.type === "tool-input-available") {
|
|
1238
1763
|
const payload = {
|
|
1239
1764
|
requestId: options.requestId,
|
|
@@ -1251,7 +1776,7 @@ const lifecycleEvents = (options) => {
|
|
|
1251
1776
|
};
|
|
1252
1777
|
take(payload.toolCallId);
|
|
1253
1778
|
const classification = approvalClassification(options.tools, options.generation, chunk.toolName);
|
|
1254
|
-
if (classification === "automatic") result.push(toolCalledEvent(payload));
|
|
1779
|
+
if (classification === "automatic") result.push(await toolCalledEvent(payload));
|
|
1255
1780
|
else pending.push({
|
|
1256
1781
|
call: payload,
|
|
1257
1782
|
classification
|
|
@@ -1280,13 +1805,13 @@ const lifecycleEvents = (options) => {
|
|
|
1280
1805
|
input: chunk.input
|
|
1281
1806
|
};
|
|
1282
1807
|
take(chunk.toolCallId);
|
|
1283
|
-
result.push(toolCalledEvent(called), {
|
|
1808
|
+
result.push(await toolCalledEvent(called), {
|
|
1284
1809
|
type: "ai.tool.result",
|
|
1285
|
-
id: resultId,
|
|
1810
|
+
id: await resultId(),
|
|
1286
1811
|
payload
|
|
1287
1812
|
});
|
|
1288
1813
|
} else if (chunk.type === "tool-output-available") {
|
|
1289
|
-
flush(chunk.toolCallId);
|
|
1814
|
+
await flush(chunk.toolCallId);
|
|
1290
1815
|
const payload = {
|
|
1291
1816
|
requestId: options.requestId,
|
|
1292
1817
|
messageId: options.messageId,
|
|
@@ -1302,11 +1827,11 @@ const lifecycleEvents = (options) => {
|
|
|
1302
1827
|
};
|
|
1303
1828
|
result.push({
|
|
1304
1829
|
type: "ai.tool.result",
|
|
1305
|
-
id: resultId,
|
|
1830
|
+
id: await resultId(),
|
|
1306
1831
|
payload
|
|
1307
1832
|
});
|
|
1308
1833
|
} else if (chunk.type === "tool-output-error") {
|
|
1309
|
-
flush(chunk.toolCallId);
|
|
1834
|
+
await flush(chunk.toolCallId);
|
|
1310
1835
|
const payload = {
|
|
1311
1836
|
requestId: options.requestId,
|
|
1312
1837
|
messageId: options.messageId,
|
|
@@ -1321,11 +1846,11 @@ const lifecycleEvents = (options) => {
|
|
|
1321
1846
|
};
|
|
1322
1847
|
result.push({
|
|
1323
1848
|
type: "ai.tool.result",
|
|
1324
|
-
id: resultId,
|
|
1849
|
+
id: await resultId(),
|
|
1325
1850
|
payload
|
|
1326
1851
|
});
|
|
1327
1852
|
} else if (chunk.type === "tool-output-denied") {
|
|
1328
|
-
flush(chunk.toolCallId);
|
|
1853
|
+
await flush(chunk.toolCallId);
|
|
1329
1854
|
const payload = {
|
|
1330
1855
|
requestId: options.requestId,
|
|
1331
1856
|
messageId: options.messageId,
|
|
@@ -1336,12 +1861,13 @@ const lifecycleEvents = (options) => {
|
|
|
1336
1861
|
};
|
|
1337
1862
|
result.push({
|
|
1338
1863
|
type: "ai.tool.result",
|
|
1339
|
-
id: resultId,
|
|
1864
|
+
id: await resultId(),
|
|
1340
1865
|
payload
|
|
1341
1866
|
});
|
|
1342
1867
|
} else if (chunk.type === "tool-approval-request") {
|
|
1343
|
-
flush(chunk.toolCallId);
|
|
1868
|
+
await flush(chunk.toolCallId);
|
|
1344
1869
|
const payload = {
|
|
1870
|
+
requestId: options.requestId,
|
|
1345
1871
|
messageId: options.messageId,
|
|
1346
1872
|
generationId: options.generationId,
|
|
1347
1873
|
approvalId: chunk.approvalId,
|
|
@@ -1351,13 +1877,14 @@ const lifecycleEvents = (options) => {
|
|
|
1351
1877
|
};
|
|
1352
1878
|
result.push({
|
|
1353
1879
|
type: "ai.approval.requested",
|
|
1354
|
-
id:
|
|
1880
|
+
id: await aiEventId("a2.ai.approval.requested", options.generationId, chunk.approvalId),
|
|
1355
1881
|
payload
|
|
1356
1882
|
});
|
|
1357
1883
|
} else if (chunk.type === "tool-approval-response") result.push({
|
|
1358
1884
|
type: "ai.approval.responded",
|
|
1359
|
-
id:
|
|
1885
|
+
id: await aiEventId("a2.ai.approval.automatic-response", options.generationId, chunk.approvalId),
|
|
1360
1886
|
payload: {
|
|
1887
|
+
requestId: options.requestId,
|
|
1361
1888
|
messageId: options.messageId,
|
|
1362
1889
|
generationId: options.generationId,
|
|
1363
1890
|
approvalId: chunk.approvalId,
|
|
@@ -1371,24 +1898,6 @@ const lifecycleEvents = (options) => {
|
|
|
1371
1898
|
pending
|
|
1372
1899
|
};
|
|
1373
1900
|
};
|
|
1374
|
-
function withoutGenerationLifecycle(history, generationIds) {
|
|
1375
|
-
if (generationIds.size === 0) return history;
|
|
1376
|
-
const approvalKeys = /* @__PURE__ */ new Set();
|
|
1377
|
-
const inputKeys = /* @__PURE__ */ new Set();
|
|
1378
|
-
for (const event of history) {
|
|
1379
|
-
const payload = event.payload;
|
|
1380
|
-
if (typeof payload.generationId !== "string" || !generationIds.has(payload.generationId) || typeof payload.messageId !== "string") continue;
|
|
1381
|
-
if (event.type === "ai.approval.requested" && typeof payload.approvalId === "string") approvalKeys.add(`${payload.messageId}\u001f${payload.approvalId}`);
|
|
1382
|
-
if (event.type === "ai.input.requested" && typeof payload.inputId === "string") inputKeys.add(`${payload.messageId}\u001f${payload.inputId}`);
|
|
1383
|
-
}
|
|
1384
|
-
return history.filter((event) => {
|
|
1385
|
-
const payload = event.payload;
|
|
1386
|
-
if (typeof payload.generationId === "string" && generationIds.has(payload.generationId)) return false;
|
|
1387
|
-
if (event.type === "ai.approval.responded" && typeof payload.messageId === "string" && typeof payload.approvalId === "string") return !approvalKeys.has(`${payload.messageId}\u001f${payload.approvalId}`);
|
|
1388
|
-
if (event.type === "ai.input.responded" && typeof payload.messageId === "string" && typeof payload.inputId === "string") return !inputKeys.has(`${payload.messageId}\u001f${payload.inputId}`);
|
|
1389
|
-
return true;
|
|
1390
|
-
});
|
|
1391
|
-
}
|
|
1392
1901
|
const validateCompaction = (options) => {
|
|
1393
1902
|
let compaction = options.compaction;
|
|
1394
1903
|
if (compaction !== false && (typeof compaction !== "object" || compaction === null)) throw new TypeError("compaction must resolve to false or a policy object");
|
|
@@ -1428,10 +1937,18 @@ function createHandlers(options) {
|
|
|
1428
1937
|
const maxSteps = options.maxSteps ?? Number.POSITIVE_INFINITY;
|
|
1429
1938
|
const control = createControlRuntime({ agent: options.agent });
|
|
1430
1939
|
const coordinator = control.coordinator;
|
|
1940
|
+
const contextReducer = createContextReducer({ agent: options.agent });
|
|
1941
|
+
const readContext = async (session, through) => {
|
|
1942
|
+
const snapshot = await session.state(contextReducer, { through });
|
|
1943
|
+
return {
|
|
1944
|
+
state: contextReducer.view(snapshot.state),
|
|
1945
|
+
index: snapshot.index
|
|
1946
|
+
};
|
|
1947
|
+
};
|
|
1431
1948
|
const promptCache = /* @__PURE__ */ new Map();
|
|
1432
|
-
const resultEvent = (call, suffix, result) => ({
|
|
1949
|
+
const resultEvent = async (call, suffix, result) => ({
|
|
1433
1950
|
type: "ai.tool.result",
|
|
1434
|
-
id:
|
|
1951
|
+
id: await aiEventId("a2.ai.tool.result", call.generationId, call.toolCallId, suffix),
|
|
1435
1952
|
payload: {
|
|
1436
1953
|
requestId: call.requestId,
|
|
1437
1954
|
messageId: call.messageId,
|
|
@@ -1457,28 +1974,11 @@ function createHandlers(options) {
|
|
|
1457
1974
|
const computation = (async () => {
|
|
1458
1975
|
const frontier = owner.promptThroughIndex;
|
|
1459
1976
|
if (frontier === void 0) throw new TypeError("AI work requires a prompt checkpoint");
|
|
1460
|
-
const
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
ctx.session.history({
|
|
1464
|
-
gte: frontier + 1,
|
|
1465
|
-
lte: ctx.event.index
|
|
1466
|
-
})
|
|
1467
|
-
]);
|
|
1468
|
-
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);
|
|
1469
|
-
const state = foldAIEvents({
|
|
1470
|
-
agent: options.agent,
|
|
1471
|
-
events: appended,
|
|
1472
|
-
state: snapshot.state
|
|
1473
|
-
});
|
|
1977
|
+
const snapshot = await readContext(ctx.session, ctx.event.index);
|
|
1978
|
+
if (snapshot.state.generation?.owner.generationId !== owner.generationId || snapshot.state.generation.owner.requestId !== owner.requestId || snapshot.state.activeProjection?.generationId !== owner.generationId) throw new TypeError("Tool prompt checkpoint does not own its generation");
|
|
1979
|
+
const state = generationBase(snapshot.state);
|
|
1474
1980
|
return modelContext({
|
|
1475
|
-
messages:
|
|
1476
|
-
agent: options.agent,
|
|
1477
|
-
session: ctx.session,
|
|
1478
|
-
snapshot,
|
|
1479
|
-
coordinator: atPrompt.state,
|
|
1480
|
-
appended
|
|
1481
|
-
}),
|
|
1981
|
+
messages: state.messages.filter((message) => state.messageIndexes[message.id] <= frontier),
|
|
1482
1982
|
state,
|
|
1483
1983
|
tools
|
|
1484
1984
|
});
|
|
@@ -1489,7 +1989,7 @@ function createHandlers(options) {
|
|
|
1489
1989
|
});
|
|
1490
1990
|
return computation;
|
|
1491
1991
|
};
|
|
1492
|
-
const toolExecutionFailure = (ctx, call, error) => {
|
|
1992
|
+
const toolExecutionFailure = async (ctx, call, error) => {
|
|
1493
1993
|
const schedulerFailure = consumeSchedulerSendFailure(error);
|
|
1494
1994
|
if (checkAbort(ctx.signal)) return;
|
|
1495
1995
|
if (schedulerFailure === "retryable") throw error;
|
|
@@ -1534,7 +2034,7 @@ function createHandlers(options) {
|
|
|
1534
2034
|
await control.append({
|
|
1535
2035
|
ctx,
|
|
1536
2036
|
name: `tool:${call.toolCallId}:preliminary:${sequence}`,
|
|
1537
|
-
events: [resultEvent(call, `${ctx.event.id}:execution:${ctx.attempt}:${sequence}:preliminary`, {
|
|
2037
|
+
events: [await resultEvent(call, `${ctx.event.id}:execution:${ctx.attempt}:${sequence}:preliminary`, {
|
|
1538
2038
|
output: result.value,
|
|
1539
2039
|
preliminary: true
|
|
1540
2040
|
})]
|
|
@@ -1565,7 +2065,7 @@ function createHandlers(options) {
|
|
|
1565
2065
|
const state = (await ctx.session.state(control.reducer, { through: "latest" })).state;
|
|
1566
2066
|
const request = ctx.event.payload;
|
|
1567
2067
|
const call = state.coordinator.response?.calls.find((candidate) => candidate.work?.id === ctx.event.id);
|
|
1568
|
-
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({
|
|
2068
|
+
if (state.active?.turnId !== request.turnId || Object.values(state.pendingStops).includes(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({
|
|
1569
2069
|
ctx,
|
|
1570
2070
|
events: void 0
|
|
1571
2071
|
});
|
|
@@ -1592,46 +2092,29 @@ function createHandlers(options) {
|
|
|
1592
2092
|
if (checkAbort(ctx.signal)) return;
|
|
1593
2093
|
const requestId = ctx.event.id;
|
|
1594
2094
|
const request = ctx.event.payload;
|
|
1595
|
-
const snapshot = await ctx.session
|
|
1596
|
-
if (snapshot.state.
|
|
1597
|
-
const
|
|
2095
|
+
const snapshot = await readContext(ctx.session, "latest");
|
|
2096
|
+
if (snapshot.state.closed || snapshot.state.authority?.requestId !== requestId || snapshot.state.authority?.phase === "paused" || snapshot.state.authority?.phase === "pausing") return;
|
|
2097
|
+
const controlState = (await ctx.session.state(control.reducer, { through: snapshot.index })).state;
|
|
2098
|
+
if (request.control !== void 0 && Object.values(controlState.pendingStops).includes(request.control.turnId)) return;
|
|
2099
|
+
const coordinatorState = controlState.coordinator;
|
|
1598
2100
|
if (coordinatorState.closed || coordinatorState.response?.activeRequestId !== requestId) return;
|
|
1599
2101
|
const response = coordinatorState.response;
|
|
1600
2102
|
const current = response.generation?.requestId === requestId ? response.generation : void 0;
|
|
1601
2103
|
if (current !== void 0 && (response.completion !== void 0 || response.failure !== void 0 && response.failure.superseded !== true)) return;
|
|
1602
2104
|
if (current !== void 0 && current.attempt >= ctx.attempt) return;
|
|
1603
2105
|
const attempt = ctx.attempt;
|
|
1604
|
-
const generationId =
|
|
1605
|
-
const responseMessageId = request.responseMessageId
|
|
2106
|
+
const generationId = await aiEventId("a2.ai.generation", requestId, String(attempt));
|
|
2107
|
+
const responseMessageId = request.responseMessageId;
|
|
1606
2108
|
const responseStepCount = response.stepCount;
|
|
1607
|
-
if (request.reason === "tool" && (
|
|
2109
|
+
if (request.reason === "tool" && (request.sourceGenerationId !== response.source?.generation.generationId || response.responseMessageId !== request.messageId || response.responseMessageId !== request.responseMessageId)) return;
|
|
1608
2110
|
const previous = current;
|
|
1609
|
-
const
|
|
1610
|
-
generation: previous,
|
|
1611
|
-
frontier: response.promptThroughIndex
|
|
1612
|
-
}];
|
|
1613
|
-
if (request.reason === "retry" && response.source?.failed) replaced.push({
|
|
1614
|
-
generation: response.source.generation,
|
|
1615
|
-
frontier: response.source.promptThroughIndex
|
|
1616
|
-
});
|
|
1617
|
-
const replacedGenerationIds = new Set(replaced.map(({ generation: owner }) => owner.generationId));
|
|
1618
|
-
let state = snapshot.state;
|
|
1619
|
-
if (replaced.length > 0) {
|
|
1620
|
-
const through = Math.min(...replaced.map(({ frontier }) => frontier));
|
|
1621
|
-
const baseline = await ctx.session.state(options.agent.reducer, { through });
|
|
1622
|
-
const tail = await ctx.session.history({
|
|
1623
|
-
gte: through + 1,
|
|
1624
|
-
lte: snapshot.index
|
|
1625
|
-
});
|
|
1626
|
-
state = foldAIEvents({
|
|
1627
|
-
agent: options.agent,
|
|
1628
|
-
events: withoutGenerationLifecycle(tail, replacedGenerationIds),
|
|
1629
|
-
state: baseline.state
|
|
1630
|
-
});
|
|
1631
|
-
}
|
|
2111
|
+
const state = previous === void 0 ? snapshot.state : contextForAttempt(snapshot.state);
|
|
1632
2112
|
const resolverContext = {
|
|
1633
2113
|
event: ctx.event,
|
|
1634
|
-
|
|
2114
|
+
messages: contextMessages({
|
|
2115
|
+
state,
|
|
2116
|
+
coordinator: coordinatorState
|
|
2117
|
+
}),
|
|
1635
2118
|
session: { state: (reducer, readOptions) => ctx.session.state(reducer, {
|
|
1636
2119
|
...readOptions,
|
|
1637
2120
|
through: readOptions?.through ?? snapshot.index
|
|
@@ -1657,7 +2140,7 @@ function createHandlers(options) {
|
|
|
1657
2140
|
if (source === void 0) return;
|
|
1658
2141
|
return {
|
|
1659
2142
|
type: "ai.generation.failed",
|
|
1660
|
-
id:
|
|
2143
|
+
id: await aiEventId("a2.ai.step-limit", requestId),
|
|
1661
2144
|
payload: {
|
|
1662
2145
|
requestId: source.requestId,
|
|
1663
2146
|
messageId: source.messageId,
|
|
@@ -1684,7 +2167,7 @@ function createHandlers(options) {
|
|
|
1684
2167
|
const startEvents = [];
|
|
1685
2168
|
if (discoversMetadata && !Object.hasOwn(state.modelMetadata, catalogModelId)) startEvents.push({
|
|
1686
2169
|
type: "ai.model.metadata.requested",
|
|
1687
|
-
id:
|
|
2170
|
+
id: await aiEventId("a2.ai.model.metadata", options.agent.contract.name, ctx.event.sessionId, catalogModelId),
|
|
1688
2171
|
payload: { modelId: catalogModelId }
|
|
1689
2172
|
});
|
|
1690
2173
|
if (previous) {
|
|
@@ -1699,7 +2182,7 @@ function createHandlers(options) {
|
|
|
1699
2182
|
};
|
|
1700
2183
|
startEvents.push({
|
|
1701
2184
|
type: "ai.generation.failed",
|
|
1702
|
-
id:
|
|
2185
|
+
id: await aiEventId("a2.ai.superseded", payload.generationId),
|
|
1703
2186
|
payload: superseded
|
|
1704
2187
|
});
|
|
1705
2188
|
}
|
|
@@ -1716,15 +2199,9 @@ function createHandlers(options) {
|
|
|
1716
2199
|
generationStarted = true;
|
|
1717
2200
|
if (checkAbort(ctx.signal)) return;
|
|
1718
2201
|
const promptCoordinatorState = coordinatorState;
|
|
1719
|
-
const messages =
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
snapshot: {
|
|
1723
|
-
state,
|
|
1724
|
-
index: snapshot.index
|
|
1725
|
-
},
|
|
1726
|
-
coordinator: promptCoordinatorState,
|
|
1727
|
-
excluded: replacedGenerationIds
|
|
2202
|
+
const messages = contextMessages({
|
|
2203
|
+
state,
|
|
2204
|
+
coordinator: promptCoordinatorState
|
|
1728
2205
|
});
|
|
1729
2206
|
let generationMessages = messages;
|
|
1730
2207
|
let modelMessages = await modelContext({
|
|
@@ -1739,7 +2216,6 @@ function createHandlers(options) {
|
|
|
1739
2216
|
responseMessageId,
|
|
1740
2217
|
messages,
|
|
1741
2218
|
modelMessages,
|
|
1742
|
-
state,
|
|
1743
2219
|
session: resolverContext.session,
|
|
1744
2220
|
signal: ctx.signal,
|
|
1745
2221
|
model: resolvedModel,
|
|
@@ -1753,8 +2229,9 @@ function createHandlers(options) {
|
|
|
1753
2229
|
const limits = metadata?.status === "resolved" && !usesFallbackModels ? metadata.limits : void 0;
|
|
1754
2230
|
let inputTokenEstimate;
|
|
1755
2231
|
let compacted = false;
|
|
1756
|
-
|
|
1757
|
-
|
|
2232
|
+
let compactionEstimate;
|
|
2233
|
+
const hasPendingTools = messages.some((message) => message.parts.some((part) => isToolUIPart(part) && !(part.state === "output-error" || part.state === "output-denied" || part.state === "output-available" && part.preliminary !== true)));
|
|
2234
|
+
if (policy && !hasPendingTools) {
|
|
1758
2235
|
const compactionContext = {
|
|
1759
2236
|
...resolverContext,
|
|
1760
2237
|
messages,
|
|
@@ -1776,8 +2253,18 @@ function createHandlers(options) {
|
|
|
1776
2253
|
const threshold = policy.thresholdTokens ?? (limits === void 0 ? void 0 : Math.floor(Math.min(limits.contextWindow * .75, limits.contextWindow - (generation.maxOutputTokens ?? 0))));
|
|
1777
2254
|
if (limits !== void 0 && threshold !== void 0 && threshold <= 0) throw new Error(`output allowance exhausts the ${limits.contextWindow}-token context window`);
|
|
1778
2255
|
shouldCompact = threshold !== void 0 && inputTokens >= threshold;
|
|
2256
|
+
if (threshold !== void 0 && shouldCompact) compactionEstimate = {
|
|
2257
|
+
inputTokenEstimate,
|
|
2258
|
+
inputTokens,
|
|
2259
|
+
thresholdTokens: threshold
|
|
2260
|
+
};
|
|
1779
2261
|
} else shouldCompact = false;
|
|
1780
2262
|
if (checkAbort(ctx.signal)) return;
|
|
2263
|
+
if (shouldCompact) {
|
|
2264
|
+
const latest = (await ctx.session.state(coordinator, { through: "latest" })).state;
|
|
2265
|
+
shouldCompact = Object.keys(latest.unsettledTools ?? {}).length === 0;
|
|
2266
|
+
}
|
|
2267
|
+
if (checkAbort(ctx.signal)) return;
|
|
1781
2268
|
if (shouldCompact) {
|
|
1782
2269
|
const throughMessageId = state.messages.findLast((message) => !promptCoordinatorState.queued.some((queued) => queued.messageId === message.id))?.id ?? request.messageId;
|
|
1783
2270
|
const throughIndex = snapshot.index;
|
|
@@ -1786,11 +2273,12 @@ function createHandlers(options) {
|
|
|
1786
2273
|
name: "compaction-requested",
|
|
1787
2274
|
events: [{
|
|
1788
2275
|
type: "ai.compaction.requested",
|
|
1789
|
-
id:
|
|
2276
|
+
id: await aiEventId("a2.ai.compaction.requested", generationId),
|
|
1790
2277
|
payload: {
|
|
1791
2278
|
generationId,
|
|
1792
2279
|
throughMessageId,
|
|
1793
|
-
throughIndex
|
|
2280
|
+
throughIndex,
|
|
2281
|
+
...compactionEstimate
|
|
1794
2282
|
}
|
|
1795
2283
|
}]
|
|
1796
2284
|
}));
|
|
@@ -1815,7 +2303,7 @@ function createHandlers(options) {
|
|
|
1815
2303
|
name: "compaction-completed",
|
|
1816
2304
|
events: [{
|
|
1817
2305
|
type: "ai.compaction.completed",
|
|
1818
|
-
id:
|
|
2306
|
+
id: await aiEventId("a2.ai.compaction.completed", generationId),
|
|
1819
2307
|
payload: completed
|
|
1820
2308
|
}]
|
|
1821
2309
|
}));
|
|
@@ -1825,16 +2313,13 @@ function createHandlers(options) {
|
|
|
1825
2313
|
}
|
|
1826
2314
|
}
|
|
1827
2315
|
const currentState = foldAIEvents({
|
|
1828
|
-
|
|
2316
|
+
reducer: contextReducer[aiStateReducer],
|
|
1829
2317
|
events: generationEvents,
|
|
1830
|
-
state
|
|
2318
|
+
state
|
|
1831
2319
|
});
|
|
1832
|
-
if (request.reason === "retry" || previous !== void 0) generationMessages =
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
snapshot,
|
|
1836
|
-
coordinator: promptCoordinatorState,
|
|
1837
|
-
appended: generationEvents
|
|
2320
|
+
if (request.reason === "retry" || previous !== void 0) generationMessages = contextMessages({
|
|
2321
|
+
state: currentState,
|
|
2322
|
+
coordinator: promptCoordinatorState
|
|
1838
2323
|
});
|
|
1839
2324
|
if (policy && "shouldCompact" in policy || generationMessages !== messages || activeCompaction(currentState)?.summary !== activeCompaction(state)?.summary) modelMessages = await modelContext({
|
|
1840
2325
|
messages: generationMessages,
|
|
@@ -1856,7 +2341,6 @@ function createHandlers(options) {
|
|
|
1856
2341
|
responseMessageId,
|
|
1857
2342
|
messages: generationMessages,
|
|
1858
2343
|
modelMessages,
|
|
1859
|
-
state: currentState,
|
|
1860
2344
|
session: resolverContext.session,
|
|
1861
2345
|
signal: ctx.signal,
|
|
1862
2346
|
model: resolvedModel,
|
|
@@ -1889,7 +2373,7 @@ function createHandlers(options) {
|
|
|
1889
2373
|
sequence,
|
|
1890
2374
|
chunks: update.chunks
|
|
1891
2375
|
};
|
|
1892
|
-
const lifecycle = lifecycleEvents({
|
|
2376
|
+
const lifecycle = await lifecycleEvents({
|
|
1893
2377
|
requestId,
|
|
1894
2378
|
messageId: responseMessageId,
|
|
1895
2379
|
generationId,
|
|
@@ -1906,7 +2390,7 @@ function createHandlers(options) {
|
|
|
1906
2390
|
name: `generation-progress:${sequence}`,
|
|
1907
2391
|
events: [{
|
|
1908
2392
|
type: "ai.generation.progress",
|
|
1909
|
-
id:
|
|
2393
|
+
id: await aiEventId("a2.ai.progress", generationId, String(sequence)),
|
|
1910
2394
|
payload: progress
|
|
1911
2395
|
}, ...lifecycle.events]
|
|
1912
2396
|
});
|
|
@@ -1924,19 +2408,19 @@ function createHandlers(options) {
|
|
|
1924
2408
|
};
|
|
1925
2409
|
const completionEvent = {
|
|
1926
2410
|
type: "ai.generation.completed",
|
|
1927
|
-
id:
|
|
2411
|
+
id: await aiEventId("a2.ai.completed", generationId),
|
|
1928
2412
|
payload: completed
|
|
1929
2413
|
};
|
|
1930
2414
|
const unresolved = options.generate ? pendingToolCalls.filter((pending) => pending.classification !== "automatic") : [];
|
|
1931
2415
|
if (unresolved.length > 0) throw new Error(`generation ended with unresolved tool authorization for '${unresolved[0].call.toolName}'`);
|
|
1932
|
-
const pendingEvents = pendingToolCalls.filter((pending) => pending.classification === "automatic" || options.generate === void 0 && pending.classification === "unknown").map((pending) => toolCalledEvent(pending.call));
|
|
2416
|
+
const pendingEvents = await Promise.all(pendingToolCalls.filter((pending) => pending.classification === "automatic" || options.generate === void 0 && pending.classification === "unknown").map((pending) => toolCalledEvent(pending.call)));
|
|
1933
2417
|
if (finish.finishReason === "tool-calls") return [...pendingEvents, completionEvent];
|
|
1934
2418
|
return [
|
|
1935
2419
|
...pendingEvents,
|
|
1936
2420
|
completionEvent,
|
|
1937
2421
|
{
|
|
1938
2422
|
type: "ai.message.completed",
|
|
1939
|
-
id:
|
|
2423
|
+
id: await aiEventId("a2.ai.message.completed", generationId),
|
|
1940
2424
|
payload: { messageId: responseMessageId }
|
|
1941
2425
|
}
|
|
1942
2426
|
];
|
|
@@ -1957,7 +2441,7 @@ function createHandlers(options) {
|
|
|
1957
2441
|
};
|
|
1958
2442
|
return {
|
|
1959
2443
|
type: "ai.generation.failed",
|
|
1960
|
-
id:
|
|
2444
|
+
id: await aiEventId("a2.ai.failed", generationId),
|
|
1961
2445
|
payload: failed
|
|
1962
2446
|
};
|
|
1963
2447
|
}
|
|
@@ -1975,7 +2459,7 @@ function createHandlers(options) {
|
|
|
1975
2459
|
ctx.signal.throwIfAborted();
|
|
1976
2460
|
return {
|
|
1977
2461
|
type: "ai.model.metadata.resolved",
|
|
1978
|
-
id:
|
|
2462
|
+
id: await aiEventId("a2.ai.model.metadata.resolved", ctx.event.id),
|
|
1979
2463
|
payload: {
|
|
1980
2464
|
modelId: ctx.event.payload.modelId,
|
|
1981
2465
|
limits
|
|
@@ -1983,7 +2467,13 @@ function createHandlers(options) {
|
|
|
1983
2467
|
};
|
|
1984
2468
|
} },
|
|
1985
2469
|
"ai.control.requested": {
|
|
1986
|
-
lane:
|
|
2470
|
+
lane: ({ event }) => {
|
|
2471
|
+
const command = event.payload;
|
|
2472
|
+
if (event.id === "") throw new A2Error("INVALID_PAYLOAD", "AI command IDs must be nonempty");
|
|
2473
|
+
if (event.id?.startsWith("a2.ai:")) throw new A2Error("INVALID_PAYLOAD", `AI command IDs cannot use the reserved '${AI_EVENT_ID_PREFIX}' prefix`);
|
|
2474
|
+
if ((command.action === "send" || command.action === "steer") && event.id !== command.message.id) throw new A2Error("INVALID_PAYLOAD", "AI input event ID must equal message.id");
|
|
2475
|
+
return "a2.ai.control";
|
|
2476
|
+
},
|
|
1987
2477
|
handler: control.handler
|
|
1988
2478
|
},
|
|
1989
2479
|
"ai.work.reported": {
|
|
@@ -2010,6 +2500,7 @@ function createHandlers(options) {
|
|
|
2010
2500
|
"ai.generation.requested": {
|
|
2011
2501
|
lane: "a2.ai.model",
|
|
2012
2502
|
abortOn: {
|
|
2503
|
+
"ai.control.requested": (event, trigger) => event.payload.action === "stop" && event.payload.turnId === trigger.payload.control?.turnId,
|
|
2013
2504
|
"ai.control.committed": (event, trigger) => {
|
|
2014
2505
|
const active = event.payload.view;
|
|
2015
2506
|
return active?.turnId !== trigger.payload.control?.turnId || active?.version !== trigger.payload.control?.version;
|
|
@@ -2036,6 +2527,7 @@ function createHandlers(options) {
|
|
|
2036
2527
|
},
|
|
2037
2528
|
"ai.tool.execution.requested": {
|
|
2038
2529
|
abortOn: {
|
|
2530
|
+
"ai.control.requested": (event, trigger) => event.payload.action === "stop" && event.payload.turnId === trigger.payload.turnId,
|
|
2039
2531
|
"ai.generation.failed": (event, trigger) => event.payload.generationId === trigger.payload.call.generationId,
|
|
2040
2532
|
"ai.control.committed": (event, trigger) => {
|
|
2041
2533
|
const active = event.payload.view;
|