experimental-a2 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +73 -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-CrLYwNEx.js +2431 -0
- package/dist/ai-CrLYwNEx.js.map +1 -0
- package/dist/{ai-Cai-lCbj.d.ts → ai-HJ9fHfYI.d.ts} +72 -13
- package/dist/ai-HJ9fHfYI.d.ts.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 +755 -290
- 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 +134 -43
- package/docs/reference/01-api.mdx +104 -27
- 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 +0 -2
- package/src/ai-context-schema.ts +296 -0
- package/src/ai-context.ts +680 -0
- package/src/ai-control-server.ts +160 -131
- package/src/ai-control-state.ts +6 -0
- package/src/ai-control.ts +6 -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 +348 -0
- package/src/ai-server.ts +232 -334
- package/src/ai-stored-state.ts +484 -0
- package/src/ai.ts +252 -418
- 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 applyControlChanges, S as initialControlState, _ as progressBatches, a as events, b as AI_EVENT_ID_PREFIX, d as advanceProjection, f as projectedMessage, g as flattenProgressBatches, h as upsertResponse, l as aiStateReducer, m as upsertMessage, p as reduceToolActivity, u as storedAIReducer, v as interruptUIMessage, w as controlCommit, x as aiEventId, y as pauseUIMessage } from "./ai-CrLYwNEx.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,7 +239,7 @@ 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.v4",
|
|
230
243
|
initialState: initialControlState()
|
|
231
244
|
}).fold((state, event) => event.type === "ai.control.committed" ? applyControlChanges({
|
|
232
245
|
state,
|
|
@@ -240,11 +253,11 @@ const createControlRuntime = (options) => {
|
|
|
240
253
|
const state = structuredClone(snapshot.state);
|
|
241
254
|
const facts = [];
|
|
242
255
|
const jobs = [];
|
|
243
|
-
const emit = ({ type, payload, id
|
|
256
|
+
const emit = async ({ type, payload, id }) => {
|
|
244
257
|
const fact = {
|
|
245
258
|
type,
|
|
246
259
|
payload,
|
|
247
|
-
id
|
|
260
|
+
id: id ?? await aiEventId("a2.ai.fact", ctx.event.id, String(facts.length))
|
|
248
261
|
};
|
|
249
262
|
facts.push(fact);
|
|
250
263
|
state.coordinator = foldCoordinator(state.coordinator, {
|
|
@@ -253,11 +266,11 @@ const createControlRuntime = (options) => {
|
|
|
253
266
|
});
|
|
254
267
|
};
|
|
255
268
|
let reason;
|
|
256
|
-
const end = ({ lastSeenIndex, reason: interruptionReason }) => {
|
|
269
|
+
const end = async ({ lastSeenIndex, reason: interruptionReason }) => {
|
|
257
270
|
const response = state.coordinator.response;
|
|
258
271
|
if (response === void 0) return;
|
|
259
272
|
const owner = response.generation === void 0 ? { requestId: response.activeRequestId } : { generationId: response.generation.generationId };
|
|
260
|
-
emit({
|
|
273
|
+
await emit({
|
|
261
274
|
type: "ai.message.interrupted",
|
|
262
275
|
payload: {
|
|
263
276
|
messageId: response.responseMessageId,
|
|
@@ -268,22 +281,13 @@ const createControlRuntime = (options) => {
|
|
|
268
281
|
});
|
|
269
282
|
state.active = null;
|
|
270
283
|
};
|
|
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
284
|
const insert = ({ message, generate = true, first = false, afterStepOf }) => {
|
|
275
285
|
const candidate = {
|
|
276
286
|
id: message.id,
|
|
277
|
-
revision: 0,
|
|
278
287
|
message,
|
|
279
288
|
generate: generate && message.role === "user",
|
|
280
289
|
...afterStepOf === void 0 ? {} : { afterStepOf }
|
|
281
290
|
};
|
|
282
|
-
state.reservedIds = {
|
|
283
|
-
...state.reservedIds,
|
|
284
|
-
[candidate.id]: true,
|
|
285
|
-
...candidate.generate ? { [`${candidate.id}:assistant`]: true } : {}
|
|
286
|
-
};
|
|
287
291
|
if (first) state.inbox.items.unshift(candidate);
|
|
288
292
|
else state.inbox.items.push(candidate);
|
|
289
293
|
};
|
|
@@ -340,42 +344,28 @@ const createControlRuntime = (options) => {
|
|
|
340
344
|
if (state.coordinator.closed) reason = "closed";
|
|
341
345
|
else switch (command.action) {
|
|
342
346
|
case "send":
|
|
343
|
-
|
|
344
|
-
message: command.message,
|
|
345
|
-
generate: command.generate !== false
|
|
346
|
-
})) reason = "duplicate-input";
|
|
347
|
-
else insert({
|
|
347
|
+
insert({
|
|
348
348
|
message: command.message,
|
|
349
349
|
generate: command.generate !== false
|
|
350
350
|
});
|
|
351
351
|
break;
|
|
352
352
|
case "edit": {
|
|
353
353
|
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
|
-
}
|
|
354
|
+
if (item === void 0) reason = "not-pending";
|
|
355
|
+
else if (item.message.role !== command.message.role) reason = "role-mismatch";
|
|
356
|
+
else item.message = command.message;
|
|
361
357
|
break;
|
|
362
358
|
}
|
|
363
359
|
case "remove": {
|
|
364
360
|
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
|
-
}
|
|
361
|
+
if (position < 0) reason = "not-pending";
|
|
362
|
+
else state.inbox.items.splice(position, 1);
|
|
373
363
|
break;
|
|
374
364
|
}
|
|
375
365
|
case "move": {
|
|
376
366
|
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-
|
|
367
|
+
if (index < 0) reason = "not-pending";
|
|
368
|
+
else if (command.beforeId !== null && !state.inbox.items.some((candidate) => candidate.id === command.beforeId)) reason = "not-pending";
|
|
379
369
|
else {
|
|
380
370
|
const destination = command.beforeId === null ? state.inbox.items.length : state.inbox.items.findIndex((item) => item.id === command.beforeId);
|
|
381
371
|
moveInput({
|
|
@@ -388,18 +378,11 @@ const createControlRuntime = (options) => {
|
|
|
388
378
|
case "send-now": {
|
|
389
379
|
const position = state.inbox.items.findIndex((item) => item.id === command.inputId);
|
|
390
380
|
const selected = state.inbox.items[position];
|
|
391
|
-
if (!selected) reason =
|
|
381
|
+
if (!selected) reason = "not-pending";
|
|
392
382
|
else if ((state.active?.turnId ?? null) !== command.turnId) reason = "stale-turn";
|
|
393
383
|
else if (selected.message.role !== "user") reason = "not-user-input";
|
|
394
|
-
else if (!selected.generate && knownInput(`${selected.id}:assistant`)) reason = "duplicate-input";
|
|
395
384
|
else {
|
|
396
|
-
|
|
397
|
-
state.reservedIds = {
|
|
398
|
-
...state.reservedIds,
|
|
399
|
-
[`${selected.id}:assistant`]: true
|
|
400
|
-
};
|
|
401
|
-
selected.generate = true;
|
|
402
|
-
}
|
|
385
|
+
selected.generate = true;
|
|
403
386
|
if (state.active) selected.afterStepOf = state.active.turnId;
|
|
404
387
|
else delete selected.afterStepOf;
|
|
405
388
|
moveInput({
|
|
@@ -411,14 +394,13 @@ const createControlRuntime = (options) => {
|
|
|
411
394
|
}
|
|
412
395
|
case "stop":
|
|
413
396
|
if (state.active?.turnId !== command.turnId) reason = "stale-turn";
|
|
414
|
-
else end({
|
|
397
|
+
else await end({
|
|
415
398
|
lastSeenIndex: command.lastSeenIndex,
|
|
416
399
|
reason: void 0
|
|
417
400
|
});
|
|
418
401
|
break;
|
|
419
402
|
case "steer":
|
|
420
|
-
|
|
421
|
-
else insert({
|
|
403
|
+
insert({
|
|
422
404
|
message: command.message,
|
|
423
405
|
generate: true,
|
|
424
406
|
first: true,
|
|
@@ -428,7 +410,7 @@ const createControlRuntime = (options) => {
|
|
|
428
410
|
case "interrupt": {
|
|
429
411
|
const response = state.coordinator.response;
|
|
430
412
|
if (!response || response.responseMessageId !== command.request.messageId || command.request.generationId !== response.generation?.generationId && command.request.requestId !== response.activeRequestId) reason = "stale-turn";
|
|
431
|
-
else end({
|
|
413
|
+
else await end({
|
|
432
414
|
lastSeenIndex: command.request.lastSeenIndex,
|
|
433
415
|
reason: command.request.reason
|
|
434
416
|
});
|
|
@@ -441,7 +423,7 @@ const createControlRuntime = (options) => {
|
|
|
441
423
|
active.suspended = true;
|
|
442
424
|
active.version += 1;
|
|
443
425
|
const generation = state.coordinator.response?.generation;
|
|
444
|
-
if (generation) emit({
|
|
426
|
+
if (generation) await emit({
|
|
445
427
|
type: "ai.generation.paused",
|
|
446
428
|
payload: {
|
|
447
429
|
generationId: generation.generationId,
|
|
@@ -455,18 +437,18 @@ const createControlRuntime = (options) => {
|
|
|
455
437
|
state.inbox.paused = false;
|
|
456
438
|
break;
|
|
457
439
|
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({
|
|
440
|
+
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";
|
|
441
|
+
else await emit({
|
|
460
442
|
type: "ai.tool.result",
|
|
461
443
|
payload: command.result
|
|
462
444
|
});
|
|
463
445
|
break;
|
|
464
446
|
case "approval": {
|
|
465
447
|
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);
|
|
448
|
+
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
449
|
if (response?.failure) reason = "stale-turn";
|
|
468
450
|
else if (!call || call.response || call.terminal) reason = "not-found";
|
|
469
|
-
else emit({
|
|
451
|
+
else await emit({
|
|
470
452
|
type: "ai.approval.responded",
|
|
471
453
|
payload: command.response
|
|
472
454
|
});
|
|
@@ -476,8 +458,8 @@ const createControlRuntime = (options) => {
|
|
|
476
458
|
case "input": {
|
|
477
459
|
const payload = command.action === "input" ? command.response : command.request;
|
|
478
460
|
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({
|
|
461
|
+
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";
|
|
462
|
+
else await emit({
|
|
481
463
|
type: command.action === "input" ? "ai.input.responded" : "ai.input.requested",
|
|
482
464
|
payload
|
|
483
465
|
});
|
|
@@ -487,7 +469,7 @@ const createControlRuntime = (options) => {
|
|
|
487
469
|
const response = state.coordinator.response;
|
|
488
470
|
if (!state.active || response?.status !== "failed" || response.rootMessageId !== command.request.messageId || response.responseMessageId !== command.request.responseMessageId) reason = "stale-turn";
|
|
489
471
|
else if (!state.inbox.paused && !state.active.suspended) {
|
|
490
|
-
emit({
|
|
472
|
+
await emit({
|
|
491
473
|
type: "ai.retry.requested",
|
|
492
474
|
payload: command.request
|
|
493
475
|
});
|
|
@@ -497,7 +479,7 @@ const createControlRuntime = (options) => {
|
|
|
497
479
|
responseMessageId: response.responseMessageId,
|
|
498
480
|
reason: "retry"
|
|
499
481
|
},
|
|
500
|
-
id:
|
|
482
|
+
id: await aiEventId("a2.ai.request.retry", options.agent.contract.name, ctx.event.sessionId, command.request.retryId)
|
|
501
483
|
});
|
|
502
484
|
} else reason = "stale-turn";
|
|
503
485
|
break;
|
|
@@ -505,6 +487,10 @@ const createControlRuntime = (options) => {
|
|
|
505
487
|
}
|
|
506
488
|
} else {
|
|
507
489
|
const report = ctx.event.payload;
|
|
490
|
+
if (report.kind === "tool" && report.settled && state.coordinator.unsettledTools) {
|
|
491
|
+
delete state.coordinator.unsettledTools[report.workId];
|
|
492
|
+
if (Object.keys(state.coordinator.unsettledTools).length === 0) delete state.coordinator.unsettledTools;
|
|
493
|
+
}
|
|
508
494
|
const active = state.active;
|
|
509
495
|
const call = state.coordinator.response?.calls.find((candidate) => candidate.work?.id === report.workId);
|
|
510
496
|
const work = report.kind === "model" ? active?.model : call?.work;
|
|
@@ -515,7 +501,7 @@ const createControlRuntime = (options) => {
|
|
|
515
501
|
for (const event of report.events) {
|
|
516
502
|
if (work.facts?.includes(event.id)) continue;
|
|
517
503
|
if (authorized || report.kind === "tool" && event.type === "ai.tool.result") {
|
|
518
|
-
emit({
|
|
504
|
+
await emit({
|
|
519
505
|
type: event.type,
|
|
520
506
|
payload: event.payload,
|
|
521
507
|
id: event.id
|
|
@@ -523,7 +509,10 @@ const createControlRuntime = (options) => {
|
|
|
523
509
|
work.facts = [...work.facts ?? [], event.id];
|
|
524
510
|
}
|
|
525
511
|
}
|
|
526
|
-
if (report.settled)
|
|
512
|
+
if (report.settled) {
|
|
513
|
+
work.settled = true;
|
|
514
|
+
delete work.facts;
|
|
515
|
+
}
|
|
527
516
|
if (!authorized) reason = "stale-work";
|
|
528
517
|
}
|
|
529
518
|
}
|
|
@@ -544,7 +533,7 @@ const createControlRuntime = (options) => {
|
|
|
544
533
|
if (!active.suspended && response.failure === void 0) for (const call of response.calls) {
|
|
545
534
|
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
535
|
if (call.response?.approved === false) {
|
|
547
|
-
emit({
|
|
536
|
+
await emit({
|
|
548
537
|
type: "ai.tool.result",
|
|
549
538
|
payload: {
|
|
550
539
|
...call.call,
|
|
@@ -554,12 +543,16 @@ const createControlRuntime = (options) => {
|
|
|
554
543
|
});
|
|
555
544
|
continue;
|
|
556
545
|
}
|
|
557
|
-
const id =
|
|
546
|
+
const id = await aiEventId("a2.ai.tool.execution", ctx.event.id, call.call.generationId, call.call.toolCallId);
|
|
558
547
|
call.work = {
|
|
559
548
|
id,
|
|
560
549
|
version: active.version,
|
|
561
550
|
settled: false
|
|
562
551
|
};
|
|
552
|
+
state.coordinator.unsettledTools = {
|
|
553
|
+
...state.coordinator.unsettledTools,
|
|
554
|
+
[id]: true
|
|
555
|
+
};
|
|
563
556
|
jobs.push({
|
|
564
557
|
type: "ai.tool.execution.requested",
|
|
565
558
|
id,
|
|
@@ -579,7 +572,7 @@ const createControlRuntime = (options) => {
|
|
|
579
572
|
const handoffReady = handoff && workSettled && !externalPending && (active.suspended || response.failure !== void 0 || response.calls.every((call) => callReadyForModel(call) || approvalBlocksExecution(call)));
|
|
580
573
|
const readyForModel = !active.suspended && response.failure === void 0 && modelDone && callsDone && response.inputs.length === 0;
|
|
581
574
|
const naturalCompletion = readyForModel && response.completion !== void 0 && response.completion.finishReason !== "tool-calls";
|
|
582
|
-
if (handoffReady && !naturalCompletion) end({
|
|
575
|
+
if (handoffReady && !naturalCompletion) await end({
|
|
583
576
|
lastSeenIndex: void 0,
|
|
584
577
|
reason: "steered after step"
|
|
585
578
|
});
|
|
@@ -588,17 +581,19 @@ const createControlRuntime = (options) => {
|
|
|
588
581
|
const input = response.inputResponse;
|
|
589
582
|
if (!handoff && (resume || continuationReady(state.coordinator) || response.completion && input)) {
|
|
590
583
|
const nextReason = resume ? "resume" : input ? "input" : "tool";
|
|
591
|
-
const
|
|
584
|
+
const sourceGenerationId = (response.generation ?? response.source?.generation)?.generationId;
|
|
585
|
+
const id = nextReason === "tool" ? await aiEventId("a2.ai.request.tool", response.generation.generationId) : await aiEventId("a2.ai.request", ctx.event.id, nextReason);
|
|
592
586
|
requestModel({
|
|
593
587
|
request: {
|
|
594
588
|
messageId: response.responseMessageId,
|
|
595
589
|
responseMessageId: response.responseMessageId,
|
|
596
|
-
reason: nextReason
|
|
590
|
+
reason: nextReason,
|
|
591
|
+
...sourceGenerationId === void 0 ? {} : { sourceGenerationId }
|
|
597
592
|
},
|
|
598
593
|
id
|
|
599
594
|
});
|
|
600
595
|
} else if (naturalCompletion) {
|
|
601
|
-
emit({
|
|
596
|
+
await emit({
|
|
602
597
|
type: "ai.message.completed",
|
|
603
598
|
payload: { messageId: response.responseMessageId }
|
|
604
599
|
});
|
|
@@ -610,13 +605,13 @@ const createControlRuntime = (options) => {
|
|
|
610
605
|
if (state.active === null && !state.inbox.paused && !state.coordinator.closed) while (state.inbox.items.length > 0) {
|
|
611
606
|
const item = state.inbox.items.shift();
|
|
612
607
|
delete item.afterStepOf;
|
|
613
|
-
emit({
|
|
608
|
+
await emit({
|
|
614
609
|
type: "ai.message.created",
|
|
615
610
|
payload: {
|
|
616
611
|
message: item.message,
|
|
617
612
|
generate: item.generate
|
|
618
613
|
},
|
|
619
|
-
id:
|
|
614
|
+
id: await aiEventId("a2.ai.admitted", options.agent.contract.name, ctx.event.sessionId, item.id)
|
|
620
615
|
});
|
|
621
616
|
if (!item.generate) continue;
|
|
622
617
|
state.active = {
|
|
@@ -627,12 +622,14 @@ const createControlRuntime = (options) => {
|
|
|
627
622
|
suspended: false,
|
|
628
623
|
model: null
|
|
629
624
|
};
|
|
625
|
+
const requestId = await aiEventId("a2.ai.request.message", options.agent.contract.name, ctx.event.sessionId, item.id);
|
|
630
626
|
requestModel({
|
|
631
627
|
request: {
|
|
632
628
|
messageId: item.id,
|
|
629
|
+
responseMessageId: requestId,
|
|
633
630
|
reason: "message"
|
|
634
631
|
},
|
|
635
|
-
id:
|
|
632
|
+
id: requestId
|
|
636
633
|
});
|
|
637
634
|
break;
|
|
638
635
|
}
|
|
@@ -646,6 +643,7 @@ const createControlRuntime = (options) => {
|
|
|
646
643
|
...facts,
|
|
647
644
|
{
|
|
648
645
|
type: "ai.control.committed",
|
|
646
|
+
id: await aiEventId("a2.ai.control.committed", ctx.event.id),
|
|
649
647
|
payload: controlCommit({
|
|
650
648
|
before,
|
|
651
649
|
after: state,
|
|
@@ -655,11 +653,12 @@ const createControlRuntime = (options) => {
|
|
|
655
653
|
...jobs,
|
|
656
654
|
{
|
|
657
655
|
type: "ai.control.decided",
|
|
656
|
+
id: await aiEventId("a2.ai.control.decided", ctx.event.id),
|
|
658
657
|
payload: receipt
|
|
659
658
|
}
|
|
660
659
|
];
|
|
661
660
|
};
|
|
662
|
-
const report = ({ ctx, events, settled, name }) => {
|
|
661
|
+
const report = async ({ ctx, events, settled, name }) => {
|
|
663
662
|
const payload = ctx.event.payload;
|
|
664
663
|
const { turnId, version } = "turnId" in payload ? payload : payload.control;
|
|
665
664
|
for (const event of events) {
|
|
@@ -669,7 +668,7 @@ const createControlRuntime = (options) => {
|
|
|
669
668
|
}
|
|
670
669
|
return {
|
|
671
670
|
type: "ai.work.reported",
|
|
672
|
-
id:
|
|
671
|
+
id: await aiEventId("a2.ai.report", ctx.event.id, String(ctx.attempt), name),
|
|
673
672
|
payload: {
|
|
674
673
|
workId: ctx.event.id,
|
|
675
674
|
attempt: ctx.attempt,
|
|
@@ -677,11 +676,11 @@ const createControlRuntime = (options) => {
|
|
|
677
676
|
version,
|
|
678
677
|
kind: ctx.event.type === "ai.generation.requested" ? "model" : "tool",
|
|
679
678
|
settled,
|
|
680
|
-
events: events.filter((event) => event.type !== "ai.message.completed" && event.type !== "ai.message.interrupted").map((event, index) => ({
|
|
679
|
+
events: await Promise.all(events.filter((event) => event.type !== "ai.message.completed" && event.type !== "ai.message.interrupted").map(async (event, index) => ({
|
|
681
680
|
type: event.type,
|
|
682
681
|
payload: event.payload,
|
|
683
|
-
id: event.id ??
|
|
684
|
-
}))
|
|
682
|
+
id: event.id ?? await aiEventId("a2.ai.work.fact", ctx.event.id, String(ctx.attempt), name, String(index))
|
|
683
|
+
})))
|
|
685
684
|
}
|
|
686
685
|
};
|
|
687
686
|
};
|
|
@@ -689,7 +688,7 @@ const createControlRuntime = (options) => {
|
|
|
689
688
|
const progress = events.filter((event) => event.type === "ai.generation.progress");
|
|
690
689
|
const lifecycle = events.filter((event) => event.type !== "ai.generation.progress");
|
|
691
690
|
if (lifecycle.length === 0) return ctx.session.append(name, ...progress);
|
|
692
|
-
const input = report({
|
|
691
|
+
const input = await report({
|
|
693
692
|
ctx,
|
|
694
693
|
events: lifecycle,
|
|
695
694
|
settled: false,
|
|
@@ -744,6 +743,532 @@ const createControlRuntime = (options) => {
|
|
|
744
743
|
};
|
|
745
744
|
};
|
|
746
745
|
//#endregion
|
|
746
|
+
//#region src/ai-context-schema.ts
|
|
747
|
+
const record = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
748
|
+
const index = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
749
|
+
const id = (value) => typeof value === "string" && value.length > 0;
|
|
750
|
+
const nullableId = (value) => value === null || id(value);
|
|
751
|
+
const matches = (schema, value) => validateSync(schema, value, "AI context checkpoint").issues === void 0;
|
|
752
|
+
const isMessage = (value) => matches(events["ai.message.created"], { message: value });
|
|
753
|
+
const indexes = (value, messages) => record(value) && Object.values(value).every(index) && messages.every((message) => Object.hasOwn(value, message.id));
|
|
754
|
+
const compaction = (value) => value === null || record(value) && Object.keys(value).every((key) => [
|
|
755
|
+
"status",
|
|
756
|
+
"generationId",
|
|
757
|
+
"throughMessageId",
|
|
758
|
+
"throughIndex",
|
|
759
|
+
"summary"
|
|
760
|
+
].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");
|
|
761
|
+
const progress = (value) => {
|
|
762
|
+
if (!record(value) || !index(value["length"]) || !Array.isArray(value["tail"]) || !Array.isArray(value["blocks"])) return false;
|
|
763
|
+
const pending = [...value["blocks"].toReversed().filter((block) => block !== null), value["tail"]].toReversed();
|
|
764
|
+
const seen = /* @__PURE__ */ new Set();
|
|
765
|
+
let count = 0;
|
|
766
|
+
let last = -1;
|
|
767
|
+
while (pending.length > 0) {
|
|
768
|
+
const block = pending.pop();
|
|
769
|
+
if (typeof block !== "object" || block === null || seen.has(block)) return false;
|
|
770
|
+
seen.add(block);
|
|
771
|
+
if (Array.isArray(block)) for (const batch of block) {
|
|
772
|
+
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;
|
|
773
|
+
count += 1;
|
|
774
|
+
last = batch["index"];
|
|
775
|
+
}
|
|
776
|
+
else if (record(block) && Object.keys(block).length === 2 && Object.hasOwn(block, "left") && Object.hasOwn(block, "right")) pending.push(block["right"], block["left"]);
|
|
777
|
+
else return false;
|
|
778
|
+
}
|
|
779
|
+
return count === value["length"];
|
|
780
|
+
};
|
|
781
|
+
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"]));
|
|
782
|
+
const projection = (value, messages) => {
|
|
783
|
+
if (!record(value) || Object.keys(value).some((key) => ![
|
|
784
|
+
"generationId",
|
|
785
|
+
"responseMessageId",
|
|
786
|
+
"promptThroughIndex",
|
|
787
|
+
"completionIndex",
|
|
788
|
+
"baseMessage",
|
|
789
|
+
"baseCompaction",
|
|
790
|
+
"batches",
|
|
791
|
+
"toolEvents",
|
|
792
|
+
"approvalEvents",
|
|
793
|
+
"cursor"
|
|
794
|
+
].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;
|
|
795
|
+
const cursor = value["cursor"];
|
|
796
|
+
if (cursor === void 0 || cursor === null) return true;
|
|
797
|
+
if (!record(cursor) || !index(cursor["throughIndex"]) || !record(cursor["stream"])) return false;
|
|
798
|
+
const stream = cursor["stream"];
|
|
799
|
+
const current = messages.find((message) => message.id === value["responseMessageId"]);
|
|
800
|
+
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;
|
|
801
|
+
return Object.values(stream["tools"]).every((tool) => record(tool) && typeof tool["text"] === "string" && typeof tool["toolName"] === "string" && typeof tool["dynamic"] === "boolean");
|
|
802
|
+
};
|
|
803
|
+
const authority = (value) => value === null || record(value) && Object.keys(value).every((key) => [
|
|
804
|
+
"requestId",
|
|
805
|
+
"responseMessageId",
|
|
806
|
+
"sourceGenerationId",
|
|
807
|
+
"requestReason",
|
|
808
|
+
"phase"
|
|
809
|
+
].includes(key)) && [
|
|
810
|
+
"requestId",
|
|
811
|
+
"responseMessageId",
|
|
812
|
+
"sourceGenerationId"
|
|
813
|
+
].every((key) => nullableId(value[key])) && (value["requestReason"] === null || [
|
|
814
|
+
"message",
|
|
815
|
+
"tool",
|
|
816
|
+
"input",
|
|
817
|
+
"resume",
|
|
818
|
+
"retry"
|
|
819
|
+
].includes(String(value["requestReason"]))) && (value["phase"] === null || [
|
|
820
|
+
"starting",
|
|
821
|
+
"generating",
|
|
822
|
+
"tools",
|
|
823
|
+
"waiting",
|
|
824
|
+
"failed",
|
|
825
|
+
"pausing",
|
|
826
|
+
"paused"
|
|
827
|
+
].includes(String(value["phase"])));
|
|
828
|
+
const contextSchema = () => ({ "~standard": {
|
|
829
|
+
version: 1,
|
|
830
|
+
vendor: "a2-context",
|
|
831
|
+
validate(value) {
|
|
832
|
+
const invalid = { issues: [{ message: "invalid AI model context" }] };
|
|
833
|
+
const keys = [
|
|
834
|
+
"messages",
|
|
835
|
+
"compaction",
|
|
836
|
+
"modelMetadata",
|
|
837
|
+
"closed",
|
|
838
|
+
"authority",
|
|
839
|
+
"generation",
|
|
840
|
+
"activeProjection",
|
|
841
|
+
"messageIndexes"
|
|
842
|
+
];
|
|
843
|
+
try {
|
|
844
|
+
if (!record(value) || keys.some((key) => !Object.hasOwn(value, key)) || Object.keys(value).some((key) => !keys.includes(key))) return invalid;
|
|
845
|
+
const messages = value["messages"];
|
|
846
|
+
const metadata = value["modelMetadata"];
|
|
847
|
+
const generation = value["generation"];
|
|
848
|
+
const current = value["activeProjection"];
|
|
849
|
+
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"], {
|
|
850
|
+
modelId,
|
|
851
|
+
limits: entry["limits"]
|
|
852
|
+
}))) || !indexes(value["messageIndexes"], messages)) return invalid;
|
|
853
|
+
if (generation !== null && (!record(generation) || !matches(events["ai.generation.started"], generation["owner"]) || ![
|
|
854
|
+
"streaming",
|
|
855
|
+
"completed",
|
|
856
|
+
"paused",
|
|
857
|
+
"failed",
|
|
858
|
+
"superseded"
|
|
859
|
+
].includes(String(generation["phase"])) || generation["responseIndex"] !== void 0 && !index(generation["responseIndex"]) || generation["phase"] === "failed" && typeof generation["error"] !== "string")) return invalid;
|
|
860
|
+
const owner = record(generation) && record(generation["owner"]) ? generation["owner"] : void 0;
|
|
861
|
+
if (current !== null && (!projection(current, messages) || !record(current) || current["generationId"] !== owner?.["generationId"] || current["responseMessageId"] !== owner?.["responseMessageId"])) return invalid;
|
|
862
|
+
if (generation !== null && generation["phase"] !== "superseded" && current === null) return invalid;
|
|
863
|
+
if (value["closed"] && (generation !== null || current !== null)) return invalid;
|
|
864
|
+
return { value };
|
|
865
|
+
} catch {
|
|
866
|
+
return invalid;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
} });
|
|
870
|
+
//#endregion
|
|
871
|
+
//#region src/ai-context.ts
|
|
872
|
+
const replaceResponse = ({ state, message, index }) => {
|
|
873
|
+
let messages = upsertResponse({
|
|
874
|
+
messages: state.messages,
|
|
875
|
+
message,
|
|
876
|
+
inputId: state.generation.owner.messageId
|
|
877
|
+
});
|
|
878
|
+
if (state.generation?.responseIndex !== void 0 && !state.messages.some((item) => item.id === message.id)) {
|
|
879
|
+
messages = messages.filter((item) => item.id !== message.id);
|
|
880
|
+
messages.splice(state.generation.responseIndex, 0, message);
|
|
881
|
+
}
|
|
882
|
+
return {
|
|
883
|
+
...state,
|
|
884
|
+
messages,
|
|
885
|
+
messageIndexes: Object.hasOwn(state.messageIndexes, message.id) ? state.messageIndexes : {
|
|
886
|
+
...state.messageIndexes,
|
|
887
|
+
[message.id]: index
|
|
888
|
+
}
|
|
889
|
+
};
|
|
890
|
+
};
|
|
891
|
+
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)]);
|
|
892
|
+
const renderProjection = ({ state, index }) => {
|
|
893
|
+
const projection = state.activeProjection;
|
|
894
|
+
let message = projectedMessage(projection);
|
|
895
|
+
if (projection.baseMessage === void 0 && projection.batches.length === 0 && message.parts.length === 0) return state;
|
|
896
|
+
if (state.generation?.phase === "paused") message = pauseUIMessage({
|
|
897
|
+
message,
|
|
898
|
+
toolCallIds: calledTools(projection)
|
|
899
|
+
});
|
|
900
|
+
if (state.generation?.phase === "failed") message = interruptUIMessage(message, `Generation failed before tool completion: ${state.generation.error}`);
|
|
901
|
+
return replaceResponse({
|
|
902
|
+
state: {
|
|
903
|
+
...state,
|
|
904
|
+
activeProjection: {
|
|
905
|
+
...projection,
|
|
906
|
+
cursor: null
|
|
907
|
+
}
|
|
908
|
+
},
|
|
909
|
+
message,
|
|
910
|
+
index
|
|
911
|
+
});
|
|
912
|
+
};
|
|
913
|
+
const generationBase = (state) => {
|
|
914
|
+
const projection = state.activeProjection;
|
|
915
|
+
if (!projection) return state;
|
|
916
|
+
return {
|
|
917
|
+
...state,
|
|
918
|
+
messages: projection.baseMessage === void 0 ? state.messages.filter((message) => message.id !== projection.responseMessageId) : upsertMessage(state.messages, projection.baseMessage),
|
|
919
|
+
compaction: projection.baseCompaction ?? null
|
|
920
|
+
};
|
|
921
|
+
};
|
|
922
|
+
const contextForAttempt = (state) => ({
|
|
923
|
+
...state.activeProjection?.completionIndex === void 0 ? generationBase(state) : state,
|
|
924
|
+
generation: state.generation && {
|
|
925
|
+
...state.generation,
|
|
926
|
+
phase: "superseded"
|
|
927
|
+
},
|
|
928
|
+
activeProjection: null
|
|
929
|
+
});
|
|
930
|
+
const endGeneration = (state) => ({
|
|
931
|
+
...state,
|
|
932
|
+
authority: null,
|
|
933
|
+
generation: null,
|
|
934
|
+
activeProjection: null
|
|
935
|
+
});
|
|
936
|
+
const ownsGeneration = (state, owner) => state.generation?.owner.generationId === owner.generationId && state.generation.owner.requestId === owner.requestId && state.generation.phase !== "superseded";
|
|
937
|
+
const createContextReducer = ({ agent }) => {
|
|
938
|
+
const reducer = agent.contract.reducer({
|
|
939
|
+
name: "a2.ai.context.v3",
|
|
940
|
+
initialState: {
|
|
941
|
+
messages: [],
|
|
942
|
+
compaction: null,
|
|
943
|
+
modelMetadata: {},
|
|
944
|
+
closed: false,
|
|
945
|
+
authority: null,
|
|
946
|
+
generation: null,
|
|
947
|
+
activeProjection: null,
|
|
948
|
+
messageIndexes: {}
|
|
949
|
+
},
|
|
950
|
+
stateSchema: contextSchema()
|
|
951
|
+
}).fold((state, event) => reduceContext(state, event));
|
|
952
|
+
return storedAIReducer(reducer);
|
|
953
|
+
};
|
|
954
|
+
const reduceContext = (state, event) => {
|
|
955
|
+
if (state.closed) return state;
|
|
956
|
+
switch (event.type) {
|
|
957
|
+
case "ai.control.committed": {
|
|
958
|
+
const { view } = event.payload;
|
|
959
|
+
const { requestId, requestReason, sourceGenerationId, responseMessageId, phase } = view;
|
|
960
|
+
const next = {
|
|
961
|
+
...state,
|
|
962
|
+
authority: {
|
|
963
|
+
requestId,
|
|
964
|
+
requestReason,
|
|
965
|
+
sourceGenerationId,
|
|
966
|
+
responseMessageId,
|
|
967
|
+
phase
|
|
968
|
+
},
|
|
969
|
+
closed: view.closed
|
|
970
|
+
};
|
|
971
|
+
return view.closed ? endGeneration(next) : next;
|
|
972
|
+
}
|
|
973
|
+
case "ai.session.closed": return endGeneration({
|
|
974
|
+
...state,
|
|
975
|
+
closed: true,
|
|
976
|
+
compaction: state.compaction?.status === "running" ? state.activeProjection?.baseCompaction ?? null : state.compaction
|
|
977
|
+
});
|
|
978
|
+
case "ai.message.created": {
|
|
979
|
+
const { message } = event.payload;
|
|
980
|
+
return {
|
|
981
|
+
...state,
|
|
982
|
+
messages: upsertMessage(state.messages, message),
|
|
983
|
+
activeProjection: state.activeProjection?.responseMessageId === message.id ? {
|
|
984
|
+
...state.activeProjection,
|
|
985
|
+
cursor: null
|
|
986
|
+
} : state.activeProjection,
|
|
987
|
+
messageIndexes: {
|
|
988
|
+
...state.messageIndexes,
|
|
989
|
+
[message.id]: event.index
|
|
990
|
+
}
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
case "ai.generation.requested": {
|
|
994
|
+
const request = event.payload;
|
|
995
|
+
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;
|
|
996
|
+
return contextForAttempt(state);
|
|
997
|
+
}
|
|
998
|
+
case "ai.generation.started": {
|
|
999
|
+
const owner = event.payload;
|
|
1000
|
+
if (state.authority?.requestId !== owner.requestId || state.authority.responseMessageId !== owner.responseMessageId || state.authority.phase === "paused" || state.authority.phase === "pausing") return state;
|
|
1001
|
+
const previous = state.generation;
|
|
1002
|
+
if (previous?.owner.requestId === owner.requestId && (previous.owner.attempt >= owner.attempt || previous.phase === "failed" || previous.phase === "completed" || state.activeProjection?.completionIndex !== void 0)) return state;
|
|
1003
|
+
const base = previous?.owner.requestId === owner.requestId ? generationBase(state) : state;
|
|
1004
|
+
const baseMessage = base.messages.find((message) => message.id === owner.responseMessageId);
|
|
1005
|
+
return {
|
|
1006
|
+
...base,
|
|
1007
|
+
generation: {
|
|
1008
|
+
owner,
|
|
1009
|
+
phase: "streaming",
|
|
1010
|
+
...previous?.owner.responseMessageId === owner.responseMessageId && previous.responseIndex !== void 0 ? { responseIndex: previous.responseIndex } : {}
|
|
1011
|
+
},
|
|
1012
|
+
activeProjection: {
|
|
1013
|
+
generationId: owner.generationId,
|
|
1014
|
+
responseMessageId: owner.responseMessageId,
|
|
1015
|
+
promptThroughIndex: owner.promptThroughIndex ?? event.index - 1,
|
|
1016
|
+
...baseMessage === void 0 ? {} : { baseMessage },
|
|
1017
|
+
baseCompaction: base.compaction,
|
|
1018
|
+
batches: progressBatches(),
|
|
1019
|
+
toolEvents: [],
|
|
1020
|
+
approvalEvents: []
|
|
1021
|
+
}
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
case "ai.generation.progress": {
|
|
1025
|
+
const payload = event.payload;
|
|
1026
|
+
if (!ownsGeneration(state, payload) || state.generation.owner.responseMessageId !== payload.responseMessageId || state.generation.phase !== "streaming" || !state.activeProjection) return state;
|
|
1027
|
+
const projection = state.activeProjection;
|
|
1028
|
+
const progress = advanceProjection({
|
|
1029
|
+
projection,
|
|
1030
|
+
base: state.messages.find((message) => message.id === projection.responseMessageId),
|
|
1031
|
+
chunks: payload.chunks,
|
|
1032
|
+
index: event.index
|
|
1033
|
+
});
|
|
1034
|
+
return replaceResponse({
|
|
1035
|
+
state: {
|
|
1036
|
+
...state,
|
|
1037
|
+
activeProjection: {
|
|
1038
|
+
...projection,
|
|
1039
|
+
batches: progress.batches,
|
|
1040
|
+
cursor: progress.cursor
|
|
1041
|
+
}
|
|
1042
|
+
},
|
|
1043
|
+
message: progress.message,
|
|
1044
|
+
index: event.index
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
case "ai.generation.completed": {
|
|
1048
|
+
const payload = event.payload;
|
|
1049
|
+
if (!ownsGeneration(state, payload) || state.generation.phase !== "streaming") return state;
|
|
1050
|
+
return {
|
|
1051
|
+
...state,
|
|
1052
|
+
generation: {
|
|
1053
|
+
...state.generation,
|
|
1054
|
+
phase: "completed"
|
|
1055
|
+
},
|
|
1056
|
+
activeProjection: {
|
|
1057
|
+
...state.activeProjection,
|
|
1058
|
+
completionIndex: event.index,
|
|
1059
|
+
cursor: null
|
|
1060
|
+
}
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
case "ai.generation.failed": {
|
|
1064
|
+
const payload = event.payload;
|
|
1065
|
+
if (!ownsGeneration(state, payload)) return state;
|
|
1066
|
+
if (state.generation.phase !== "streaming" && !(payload.stepLimit && state.authority?.requestReason === "tool" && state.authority.sourceGenerationId === payload.generationId)) return state;
|
|
1067
|
+
if (payload.superseded) return {
|
|
1068
|
+
...generationBase(state),
|
|
1069
|
+
generation: {
|
|
1070
|
+
...state.generation,
|
|
1071
|
+
phase: "superseded"
|
|
1072
|
+
},
|
|
1073
|
+
activeProjection: null
|
|
1074
|
+
};
|
|
1075
|
+
return renderProjection({
|
|
1076
|
+
state: {
|
|
1077
|
+
...state,
|
|
1078
|
+
compaction: state.activeProjection?.baseCompaction ?? null,
|
|
1079
|
+
generation: {
|
|
1080
|
+
...state.generation,
|
|
1081
|
+
phase: "failed",
|
|
1082
|
+
error: payload.error
|
|
1083
|
+
}
|
|
1084
|
+
},
|
|
1085
|
+
index: event.index
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
case "ai.message.interrupted": {
|
|
1089
|
+
const payload = event.payload;
|
|
1090
|
+
const owner = state.generation?.owner;
|
|
1091
|
+
const ownsRequest = payload.requestId !== void 0 && payload.requestId === state.authority?.requestId && payload.messageId === state.authority.responseMessageId;
|
|
1092
|
+
const ownsOwner = owner?.responseMessageId === payload.messageId && (payload.generationId === owner.generationId || payload.requestId === owner.requestId);
|
|
1093
|
+
if (!ownsRequest && !ownsOwner) return state;
|
|
1094
|
+
if (ownsOwner && state.authority?.requestId !== owner.requestId && !ownsRequest) return state;
|
|
1095
|
+
const projection = state.activeProjection;
|
|
1096
|
+
if (!projection) return endGeneration(state);
|
|
1097
|
+
const cutoff = payload.lastSeenIndex ?? Number.POSITIVE_INFINITY;
|
|
1098
|
+
const retained = {
|
|
1099
|
+
...projection,
|
|
1100
|
+
batches: progressBatches(flattenProgressBatches(projection.batches).filter((batch) => batch.index <= cutoff)),
|
|
1101
|
+
toolEvents: projection.toolEvents.filter((item) => item.index <= cutoff),
|
|
1102
|
+
approvalEvents: projection.approvalEvents.filter((item) => item.index <= cutoff)
|
|
1103
|
+
};
|
|
1104
|
+
let next = generationBase(state);
|
|
1105
|
+
if (retained.baseMessage !== void 0 || retained.batches.length > 0) next = replaceResponse({
|
|
1106
|
+
state: next,
|
|
1107
|
+
message: interruptUIMessage(projectedMessage(retained)),
|
|
1108
|
+
index: event.index
|
|
1109
|
+
});
|
|
1110
|
+
return endGeneration(next);
|
|
1111
|
+
}
|
|
1112
|
+
case "ai.message.completed": {
|
|
1113
|
+
const { messageId } = event.payload;
|
|
1114
|
+
return state.generation?.owner.responseMessageId === messageId && state.generation.phase !== "failed" && state.generation.phase !== "superseded" ? endGeneration(state) : state;
|
|
1115
|
+
}
|
|
1116
|
+
case "ai.generation.paused": {
|
|
1117
|
+
const { generationId } = event.payload;
|
|
1118
|
+
if (state.generation?.owner.generationId !== generationId || state.generation.phase === "failed" || state.generation.phase === "superseded" || !state.activeProjection) return state;
|
|
1119
|
+
const toolCallIds = calledTools(state.activeProjection);
|
|
1120
|
+
return renderProjection({
|
|
1121
|
+
state: {
|
|
1122
|
+
...state,
|
|
1123
|
+
generation: {
|
|
1124
|
+
...state.generation,
|
|
1125
|
+
phase: "paused"
|
|
1126
|
+
},
|
|
1127
|
+
compaction: state.compaction?.status === "running" ? state.activeProjection.baseCompaction ?? null : state.compaction,
|
|
1128
|
+
activeProjection: {
|
|
1129
|
+
...state.activeProjection,
|
|
1130
|
+
cursor: null,
|
|
1131
|
+
batches: progressBatches(flattenProgressBatches(state.activeProjection.batches).map((batch) => ({
|
|
1132
|
+
index: batch.index,
|
|
1133
|
+
chunks: batch.chunks.filter((chunk) => !("toolCallId" in chunk) || toolCallIds.has(chunk.toolCallId))
|
|
1134
|
+
})))
|
|
1135
|
+
}
|
|
1136
|
+
},
|
|
1137
|
+
index: event.index
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
case "ai.tool.called":
|
|
1141
|
+
case "ai.tool.result": {
|
|
1142
|
+
const payload = event.payload;
|
|
1143
|
+
if (!ownsGeneration(state, payload) || !state.activeProjection || state.generation.owner.responseMessageId !== payload.messageId) return state;
|
|
1144
|
+
if (state.generation.phase === "failed") {
|
|
1145
|
+
const result = payload;
|
|
1146
|
+
const running = state.activeProjection.toolEvents.reduce((tools, item) => reduceToolActivity(tools, item), []).some((tool) => tool.toolCallId === payload.toolCallId && tool.status === "running");
|
|
1147
|
+
if (event.type !== "ai.tool.result" || result.preliminary || !running) return state;
|
|
1148
|
+
}
|
|
1149
|
+
const toolEvent = event.type === "ai.tool.called" ? {
|
|
1150
|
+
index: event.index,
|
|
1151
|
+
type: "ai.tool.called",
|
|
1152
|
+
payload
|
|
1153
|
+
} : {
|
|
1154
|
+
index: event.index,
|
|
1155
|
+
type: "ai.tool.result",
|
|
1156
|
+
payload
|
|
1157
|
+
};
|
|
1158
|
+
const next = {
|
|
1159
|
+
...state,
|
|
1160
|
+
activeProjection: {
|
|
1161
|
+
...state.activeProjection,
|
|
1162
|
+
toolEvents: [...state.activeProjection.toolEvents, toolEvent]
|
|
1163
|
+
}
|
|
1164
|
+
};
|
|
1165
|
+
if (event.type === "ai.tool.called") return next;
|
|
1166
|
+
next.activeProjection.cursor = null;
|
|
1167
|
+
return renderProjection({
|
|
1168
|
+
state: next,
|
|
1169
|
+
index: event.index
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
case "ai.approval.requested":
|
|
1173
|
+
case "ai.approval.responded": {
|
|
1174
|
+
const payload = event.payload;
|
|
1175
|
+
if (!ownsGeneration(state, payload) || !state.activeProjection || state.generation.phase === "failed" || state.generation.owner.responseMessageId !== payload.messageId) return state;
|
|
1176
|
+
const approvalEvent = event.type === "ai.approval.requested" ? {
|
|
1177
|
+
index: event.index,
|
|
1178
|
+
type: "ai.approval.requested",
|
|
1179
|
+
payload
|
|
1180
|
+
} : {
|
|
1181
|
+
index: event.index,
|
|
1182
|
+
type: "ai.approval.responded",
|
|
1183
|
+
payload
|
|
1184
|
+
};
|
|
1185
|
+
return renderProjection({
|
|
1186
|
+
state: {
|
|
1187
|
+
...state,
|
|
1188
|
+
activeProjection: {
|
|
1189
|
+
...state.activeProjection,
|
|
1190
|
+
cursor: null,
|
|
1191
|
+
approvalEvents: [...state.activeProjection.approvalEvents, approvalEvent]
|
|
1192
|
+
}
|
|
1193
|
+
},
|
|
1194
|
+
index: event.index
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
case "ai.compaction.requested": {
|
|
1198
|
+
const payload = event.payload;
|
|
1199
|
+
if (state.generation?.owner.generationId !== payload.generationId || state.generation.phase !== "streaming") return state;
|
|
1200
|
+
return {
|
|
1201
|
+
...state,
|
|
1202
|
+
compaction: {
|
|
1203
|
+
status: "running",
|
|
1204
|
+
generationId: payload.generationId,
|
|
1205
|
+
throughMessageId: payload.throughMessageId,
|
|
1206
|
+
throughIndex: payload.throughIndex ?? state.activeProjection.promptThroughIndex
|
|
1207
|
+
}
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
case "ai.compaction.completed": {
|
|
1211
|
+
const payload = event.payload;
|
|
1212
|
+
const projection = state.activeProjection;
|
|
1213
|
+
if (state.generation?.owner.generationId !== payload.generationId || state.generation.phase !== "streaming" || state.compaction?.status !== "running" || state.compaction.generationId !== payload.generationId || !projection) return state;
|
|
1214
|
+
const throughIndex = payload.throughIndex ?? projection.promptThroughIndex;
|
|
1215
|
+
const replacements = new Set(payload.messages.map((message) => message.id));
|
|
1216
|
+
const retained = new Set(payload.retainedMessageIds ?? []);
|
|
1217
|
+
const messages = [...payload.messages, ...state.messages.filter((message) => !replacements.has(message.id) && (retained.has(message.id) || (state.messageIndexes[message.id] ?? 0) > throughIndex))];
|
|
1218
|
+
const activeProjection = {
|
|
1219
|
+
...projection,
|
|
1220
|
+
cursor: null
|
|
1221
|
+
};
|
|
1222
|
+
delete activeProjection.baseMessage;
|
|
1223
|
+
const baseMessage = messages.find((message) => message.id === projection.responseMessageId);
|
|
1224
|
+
if (baseMessage !== void 0) activeProjection.baseMessage = baseMessage;
|
|
1225
|
+
const compaction = {
|
|
1226
|
+
status: "completed",
|
|
1227
|
+
generationId: payload.generationId,
|
|
1228
|
+
throughMessageId: payload.throughMessageId,
|
|
1229
|
+
throughIndex,
|
|
1230
|
+
...payload.summary === void 0 ? {} : { summary: payload.summary }
|
|
1231
|
+
};
|
|
1232
|
+
activeProjection.baseCompaction = compaction;
|
|
1233
|
+
return {
|
|
1234
|
+
...state,
|
|
1235
|
+
messages,
|
|
1236
|
+
compaction,
|
|
1237
|
+
generation: {
|
|
1238
|
+
...state.generation,
|
|
1239
|
+
responseIndex: payload.messages.length
|
|
1240
|
+
},
|
|
1241
|
+
activeProjection,
|
|
1242
|
+
messageIndexes: Object.fromEntries(messages.map((message) => [message.id, replacements.has(message.id) ? throughIndex : state.messageIndexes[message.id]]))
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
case "ai.model.metadata.requested": {
|
|
1246
|
+
const { modelId } = event.payload;
|
|
1247
|
+
return Object.hasOwn(state.modelMetadata, modelId) ? state : {
|
|
1248
|
+
...state,
|
|
1249
|
+
modelMetadata: {
|
|
1250
|
+
...state.modelMetadata,
|
|
1251
|
+
[modelId]: { status: "pending" }
|
|
1252
|
+
}
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
case "ai.model.metadata.resolved": {
|
|
1256
|
+
const { modelId, limits } = event.payload;
|
|
1257
|
+
return {
|
|
1258
|
+
...state,
|
|
1259
|
+
modelMetadata: {
|
|
1260
|
+
...state.modelMetadata,
|
|
1261
|
+
[modelId]: limits === null ? { status: "unavailable" } : {
|
|
1262
|
+
status: "resolved",
|
|
1263
|
+
limits
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
default: return state;
|
|
1269
|
+
}
|
|
1270
|
+
};
|
|
1271
|
+
//#endregion
|
|
747
1272
|
//#region src/ai-sdk-step.ts
|
|
748
1273
|
const CONTROLLED_SETTINGS = [
|
|
749
1274
|
"_internal",
|
|
@@ -885,6 +1410,40 @@ const readModelLimits = async (options) => {
|
|
|
885
1410
|
}
|
|
886
1411
|
};
|
|
887
1412
|
//#endregion
|
|
1413
|
+
//#region src/ai-input-tokens.ts
|
|
1414
|
+
const withoutProviderOptions = (value) => {
|
|
1415
|
+
const result = { ...value };
|
|
1416
|
+
Reflect.deleteProperty(result, "providerOptions");
|
|
1417
|
+
return result;
|
|
1418
|
+
};
|
|
1419
|
+
const estimatedMessage = (message) => ({
|
|
1420
|
+
...withoutProviderOptions(message),
|
|
1421
|
+
content: typeof message.content === "string" ? message.content : message.content.map((part) => part.type === "tool-result" ? {
|
|
1422
|
+
...withoutProviderOptions(part),
|
|
1423
|
+
output: {
|
|
1424
|
+
...withoutProviderOptions(part.output),
|
|
1425
|
+
...part.output.type === "content" ? { value: part.output.value.map(withoutProviderOptions) } : {}
|
|
1426
|
+
}
|
|
1427
|
+
} : withoutProviderOptions(part))
|
|
1428
|
+
});
|
|
1429
|
+
const estimateInputTokens = async (options) => {
|
|
1430
|
+
const tools = await Promise.all(Object.entries(options.tools).map(async ([name, tool]) => ({
|
|
1431
|
+
name,
|
|
1432
|
+
description: tool.description,
|
|
1433
|
+
inputSchema: await asSchema(tool.inputSchema).jsonSchema
|
|
1434
|
+
})));
|
|
1435
|
+
const instructions = options.instructions;
|
|
1436
|
+
return Math.ceil(Buffer.byteLength(JSON.stringify({
|
|
1437
|
+
messages: options.messages.map(estimatedMessage),
|
|
1438
|
+
instructions: instructions === void 0 || typeof instructions === "string" ? instructions : Array.isArray(instructions) ? instructions.map(estimatedMessage) : estimatedMessage(instructions),
|
|
1439
|
+
tools
|
|
1440
|
+
}), "utf8") / 4);
|
|
1441
|
+
};
|
|
1442
|
+
const measuredInputTokens = (options) => {
|
|
1443
|
+
const previous = options.calibration;
|
|
1444
|
+
return previous !== void 0 && previous.model === options.model && options.estimate >= previous.estimate ? Math.ceil(previous.inputTokens + options.estimate - previous.estimate) : options.estimate;
|
|
1445
|
+
};
|
|
1446
|
+
//#endregion
|
|
888
1447
|
//#region src/ai-server.ts
|
|
889
1448
|
/**
|
|
890
1449
|
* experimental-a2/ai/server — the server-only implementation of an experimental-a2/ai definition.
|
|
@@ -1066,7 +1625,7 @@ async function* consumeGeneration(options) {
|
|
|
1066
1625
|
}
|
|
1067
1626
|
const foldAIEvents = (options) => {
|
|
1068
1627
|
let state = options.state;
|
|
1069
|
-
for (const event of options.events) state = options.
|
|
1628
|
+
for (const event of options.events) state = options.reducer.fold(state, event);
|
|
1070
1629
|
return state;
|
|
1071
1630
|
};
|
|
1072
1631
|
const summarize = async (options) => {
|
|
@@ -1107,7 +1666,7 @@ const summarize = async (options) => {
|
|
|
1107
1666
|
role: "user",
|
|
1108
1667
|
content: instruction
|
|
1109
1668
|
}],
|
|
1110
|
-
responseMessageId:
|
|
1669
|
+
responseMessageId: await aiEventId("a2.ai.summary", options.context.generationId)
|
|
1111
1670
|
},
|
|
1112
1671
|
messageMetadata: void 0
|
|
1113
1672
|
});
|
|
@@ -1133,52 +1692,11 @@ const summarize = async (options) => {
|
|
|
1133
1692
|
};
|
|
1134
1693
|
const activeCompaction = (state) => {
|
|
1135
1694
|
const compaction = state.compaction;
|
|
1136
|
-
return compaction?.status === "completed"
|
|
1695
|
+
return compaction?.status === "completed" ? compaction : null;
|
|
1137
1696
|
};
|
|
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
|
-
]);
|
|
1697
|
+
const contextMessages = ({ state, coordinator }) => {
|
|
1698
|
+
const queued = new Set(coordinator.queued.map((item) => item.messageId));
|
|
1699
|
+
return state.messages.filter((message) => !queued.has(message.id));
|
|
1182
1700
|
};
|
|
1183
1701
|
const modelContext = async (options) => {
|
|
1184
1702
|
const messages = await convertToModelMessages(options.messages, { tools: options.tools });
|
|
@@ -1188,25 +1706,9 @@ const modelContext = async (options) => {
|
|
|
1188
1706
|
content: summary
|
|
1189
1707
|
}, ...messages];
|
|
1190
1708
|
};
|
|
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) => ({
|
|
1709
|
+
const toolCalledEvent = async (payload) => ({
|
|
1208
1710
|
type: "ai.tool.called",
|
|
1209
|
-
id:
|
|
1711
|
+
id: await aiEventId("a2.ai.tool.called", payload.generationId, payload.toolCallId),
|
|
1210
1712
|
payload
|
|
1211
1713
|
});
|
|
1212
1714
|
const approvalClassification = (tools, generation, toolName) => {
|
|
@@ -1220,7 +1722,7 @@ const approvalClassification = (tools, generation, toolName) => {
|
|
|
1220
1722
|
if (typeof tool?.needsApproval === "function") return "unknown";
|
|
1221
1723
|
return tool?.needsApproval === true ? "approval" : "automatic";
|
|
1222
1724
|
};
|
|
1223
|
-
const lifecycleEvents = (options) => {
|
|
1725
|
+
const lifecycleEvents = async (options) => {
|
|
1224
1726
|
const result = [];
|
|
1225
1727
|
const pending = [...options.pending];
|
|
1226
1728
|
const take = (toolCallId) => {
|
|
@@ -1228,12 +1730,12 @@ const lifecycleEvents = (options) => {
|
|
|
1228
1730
|
if (index === -1) return void 0;
|
|
1229
1731
|
return pending.splice(index, 1)[0];
|
|
1230
1732
|
};
|
|
1231
|
-
const flush = (toolCallId) => {
|
|
1733
|
+
const flush = async (toolCallId) => {
|
|
1232
1734
|
const pendingCall = take(toolCallId);
|
|
1233
|
-
if (pendingCall) result.push(toolCalledEvent(pendingCall.call));
|
|
1735
|
+
if (pendingCall) result.push(await toolCalledEvent(pendingCall.call));
|
|
1234
1736
|
};
|
|
1235
1737
|
for (const [chunkIndex, chunk] of options.chunks.entries()) {
|
|
1236
|
-
const resultId =
|
|
1738
|
+
const resultId = () => aiEventId("a2.ai.tool.stream-result", options.generationId, String(options.sequence), String(chunkIndex));
|
|
1237
1739
|
if (chunk.type === "tool-input-available") {
|
|
1238
1740
|
const payload = {
|
|
1239
1741
|
requestId: options.requestId,
|
|
@@ -1251,7 +1753,7 @@ const lifecycleEvents = (options) => {
|
|
|
1251
1753
|
};
|
|
1252
1754
|
take(payload.toolCallId);
|
|
1253
1755
|
const classification = approvalClassification(options.tools, options.generation, chunk.toolName);
|
|
1254
|
-
if (classification === "automatic") result.push(toolCalledEvent(payload));
|
|
1756
|
+
if (classification === "automatic") result.push(await toolCalledEvent(payload));
|
|
1255
1757
|
else pending.push({
|
|
1256
1758
|
call: payload,
|
|
1257
1759
|
classification
|
|
@@ -1280,13 +1782,13 @@ const lifecycleEvents = (options) => {
|
|
|
1280
1782
|
input: chunk.input
|
|
1281
1783
|
};
|
|
1282
1784
|
take(chunk.toolCallId);
|
|
1283
|
-
result.push(toolCalledEvent(called), {
|
|
1785
|
+
result.push(await toolCalledEvent(called), {
|
|
1284
1786
|
type: "ai.tool.result",
|
|
1285
|
-
id: resultId,
|
|
1787
|
+
id: await resultId(),
|
|
1286
1788
|
payload
|
|
1287
1789
|
});
|
|
1288
1790
|
} else if (chunk.type === "tool-output-available") {
|
|
1289
|
-
flush(chunk.toolCallId);
|
|
1791
|
+
await flush(chunk.toolCallId);
|
|
1290
1792
|
const payload = {
|
|
1291
1793
|
requestId: options.requestId,
|
|
1292
1794
|
messageId: options.messageId,
|
|
@@ -1302,11 +1804,11 @@ const lifecycleEvents = (options) => {
|
|
|
1302
1804
|
};
|
|
1303
1805
|
result.push({
|
|
1304
1806
|
type: "ai.tool.result",
|
|
1305
|
-
id: resultId,
|
|
1807
|
+
id: await resultId(),
|
|
1306
1808
|
payload
|
|
1307
1809
|
});
|
|
1308
1810
|
} else if (chunk.type === "tool-output-error") {
|
|
1309
|
-
flush(chunk.toolCallId);
|
|
1811
|
+
await flush(chunk.toolCallId);
|
|
1310
1812
|
const payload = {
|
|
1311
1813
|
requestId: options.requestId,
|
|
1312
1814
|
messageId: options.messageId,
|
|
@@ -1321,11 +1823,11 @@ const lifecycleEvents = (options) => {
|
|
|
1321
1823
|
};
|
|
1322
1824
|
result.push({
|
|
1323
1825
|
type: "ai.tool.result",
|
|
1324
|
-
id: resultId,
|
|
1826
|
+
id: await resultId(),
|
|
1325
1827
|
payload
|
|
1326
1828
|
});
|
|
1327
1829
|
} else if (chunk.type === "tool-output-denied") {
|
|
1328
|
-
flush(chunk.toolCallId);
|
|
1830
|
+
await flush(chunk.toolCallId);
|
|
1329
1831
|
const payload = {
|
|
1330
1832
|
requestId: options.requestId,
|
|
1331
1833
|
messageId: options.messageId,
|
|
@@ -1336,12 +1838,13 @@ const lifecycleEvents = (options) => {
|
|
|
1336
1838
|
};
|
|
1337
1839
|
result.push({
|
|
1338
1840
|
type: "ai.tool.result",
|
|
1339
|
-
id: resultId,
|
|
1841
|
+
id: await resultId(),
|
|
1340
1842
|
payload
|
|
1341
1843
|
});
|
|
1342
1844
|
} else if (chunk.type === "tool-approval-request") {
|
|
1343
|
-
flush(chunk.toolCallId);
|
|
1845
|
+
await flush(chunk.toolCallId);
|
|
1344
1846
|
const payload = {
|
|
1847
|
+
requestId: options.requestId,
|
|
1345
1848
|
messageId: options.messageId,
|
|
1346
1849
|
generationId: options.generationId,
|
|
1347
1850
|
approvalId: chunk.approvalId,
|
|
@@ -1351,13 +1854,14 @@ const lifecycleEvents = (options) => {
|
|
|
1351
1854
|
};
|
|
1352
1855
|
result.push({
|
|
1353
1856
|
type: "ai.approval.requested",
|
|
1354
|
-
id:
|
|
1857
|
+
id: await aiEventId("a2.ai.approval.requested", options.generationId, chunk.approvalId),
|
|
1355
1858
|
payload
|
|
1356
1859
|
});
|
|
1357
1860
|
} else if (chunk.type === "tool-approval-response") result.push({
|
|
1358
1861
|
type: "ai.approval.responded",
|
|
1359
|
-
id:
|
|
1862
|
+
id: await aiEventId("a2.ai.approval.automatic-response", options.generationId, chunk.approvalId),
|
|
1360
1863
|
payload: {
|
|
1864
|
+
requestId: options.requestId,
|
|
1361
1865
|
messageId: options.messageId,
|
|
1362
1866
|
generationId: options.generationId,
|
|
1363
1867
|
approvalId: chunk.approvalId,
|
|
@@ -1371,24 +1875,6 @@ const lifecycleEvents = (options) => {
|
|
|
1371
1875
|
pending
|
|
1372
1876
|
};
|
|
1373
1877
|
};
|
|
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
1878
|
const validateCompaction = (options) => {
|
|
1393
1879
|
let compaction = options.compaction;
|
|
1394
1880
|
if (compaction !== false && (typeof compaction !== "object" || compaction === null)) throw new TypeError("compaction must resolve to false or a policy object");
|
|
@@ -1428,10 +1914,18 @@ function createHandlers(options) {
|
|
|
1428
1914
|
const maxSteps = options.maxSteps ?? Number.POSITIVE_INFINITY;
|
|
1429
1915
|
const control = createControlRuntime({ agent: options.agent });
|
|
1430
1916
|
const coordinator = control.coordinator;
|
|
1917
|
+
const contextReducer = createContextReducer({ agent: options.agent });
|
|
1918
|
+
const readContext = async (session, through) => {
|
|
1919
|
+
const snapshot = await session.state(contextReducer, { through });
|
|
1920
|
+
return {
|
|
1921
|
+
state: contextReducer.view(snapshot.state),
|
|
1922
|
+
index: snapshot.index
|
|
1923
|
+
};
|
|
1924
|
+
};
|
|
1431
1925
|
const promptCache = /* @__PURE__ */ new Map();
|
|
1432
|
-
const resultEvent = (call, suffix, result) => ({
|
|
1926
|
+
const resultEvent = async (call, suffix, result) => ({
|
|
1433
1927
|
type: "ai.tool.result",
|
|
1434
|
-
id:
|
|
1928
|
+
id: await aiEventId("a2.ai.tool.result", call.generationId, call.toolCallId, suffix),
|
|
1435
1929
|
payload: {
|
|
1436
1930
|
requestId: call.requestId,
|
|
1437
1931
|
messageId: call.messageId,
|
|
@@ -1457,28 +1951,11 @@ function createHandlers(options) {
|
|
|
1457
1951
|
const computation = (async () => {
|
|
1458
1952
|
const frontier = owner.promptThroughIndex;
|
|
1459
1953
|
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
|
-
});
|
|
1954
|
+
const snapshot = await readContext(ctx.session, ctx.event.index);
|
|
1955
|
+
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");
|
|
1956
|
+
const state = generationBase(snapshot.state);
|
|
1474
1957
|
return modelContext({
|
|
1475
|
-
messages:
|
|
1476
|
-
agent: options.agent,
|
|
1477
|
-
session: ctx.session,
|
|
1478
|
-
snapshot,
|
|
1479
|
-
coordinator: atPrompt.state,
|
|
1480
|
-
appended
|
|
1481
|
-
}),
|
|
1958
|
+
messages: state.messages.filter((message) => state.messageIndexes[message.id] <= frontier),
|
|
1482
1959
|
state,
|
|
1483
1960
|
tools
|
|
1484
1961
|
});
|
|
@@ -1489,7 +1966,7 @@ function createHandlers(options) {
|
|
|
1489
1966
|
});
|
|
1490
1967
|
return computation;
|
|
1491
1968
|
};
|
|
1492
|
-
const toolExecutionFailure = (ctx, call, error) => {
|
|
1969
|
+
const toolExecutionFailure = async (ctx, call, error) => {
|
|
1493
1970
|
const schedulerFailure = consumeSchedulerSendFailure(error);
|
|
1494
1971
|
if (checkAbort(ctx.signal)) return;
|
|
1495
1972
|
if (schedulerFailure === "retryable") throw error;
|
|
@@ -1534,7 +2011,7 @@ function createHandlers(options) {
|
|
|
1534
2011
|
await control.append({
|
|
1535
2012
|
ctx,
|
|
1536
2013
|
name: `tool:${call.toolCallId}:preliminary:${sequence}`,
|
|
1537
|
-
events: [resultEvent(call, `${ctx.event.id}:execution:${ctx.attempt}:${sequence}:preliminary`, {
|
|
2014
|
+
events: [await resultEvent(call, `${ctx.event.id}:execution:${ctx.attempt}:${sequence}:preliminary`, {
|
|
1538
2015
|
output: result.value,
|
|
1539
2016
|
preliminary: true
|
|
1540
2017
|
})]
|
|
@@ -1592,8 +2069,8 @@ function createHandlers(options) {
|
|
|
1592
2069
|
if (checkAbort(ctx.signal)) return;
|
|
1593
2070
|
const requestId = ctx.event.id;
|
|
1594
2071
|
const request = ctx.event.payload;
|
|
1595
|
-
const snapshot = await ctx.session
|
|
1596
|
-
if (snapshot.state.
|
|
2072
|
+
const snapshot = await readContext(ctx.session, "latest");
|
|
2073
|
+
if (snapshot.state.closed || snapshot.state.authority?.requestId !== requestId || snapshot.state.authority?.phase === "paused" || snapshot.state.authority?.phase === "pausing") return;
|
|
1597
2074
|
const coordinatorState = (await ctx.session.state(coordinator, { through: snapshot.index })).state;
|
|
1598
2075
|
if (coordinatorState.closed || coordinatorState.response?.activeRequestId !== requestId) return;
|
|
1599
2076
|
const response = coordinatorState.response;
|
|
@@ -1601,37 +2078,18 @@ function createHandlers(options) {
|
|
|
1601
2078
|
if (current !== void 0 && (response.completion !== void 0 || response.failure !== void 0 && response.failure.superseded !== true)) return;
|
|
1602
2079
|
if (current !== void 0 && current.attempt >= ctx.attempt) return;
|
|
1603
2080
|
const attempt = ctx.attempt;
|
|
1604
|
-
const generationId =
|
|
1605
|
-
const responseMessageId = request.responseMessageId
|
|
2081
|
+
const generationId = await aiEventId("a2.ai.generation", requestId, String(attempt));
|
|
2082
|
+
const responseMessageId = request.responseMessageId;
|
|
1606
2083
|
const responseStepCount = response.stepCount;
|
|
1607
|
-
if (request.reason === "tool" && (
|
|
2084
|
+
if (request.reason === "tool" && (request.sourceGenerationId !== response.source?.generation.generationId || response.responseMessageId !== request.messageId || response.responseMessageId !== request.responseMessageId)) return;
|
|
1608
2085
|
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
|
-
}
|
|
2086
|
+
const state = previous === void 0 ? snapshot.state : contextForAttempt(snapshot.state);
|
|
1632
2087
|
const resolverContext = {
|
|
1633
2088
|
event: ctx.event,
|
|
1634
|
-
|
|
2089
|
+
messages: contextMessages({
|
|
2090
|
+
state,
|
|
2091
|
+
coordinator: coordinatorState
|
|
2092
|
+
}),
|
|
1635
2093
|
session: { state: (reducer, readOptions) => ctx.session.state(reducer, {
|
|
1636
2094
|
...readOptions,
|
|
1637
2095
|
through: readOptions?.through ?? snapshot.index
|
|
@@ -1657,7 +2115,7 @@ function createHandlers(options) {
|
|
|
1657
2115
|
if (source === void 0) return;
|
|
1658
2116
|
return {
|
|
1659
2117
|
type: "ai.generation.failed",
|
|
1660
|
-
id:
|
|
2118
|
+
id: await aiEventId("a2.ai.step-limit", requestId),
|
|
1661
2119
|
payload: {
|
|
1662
2120
|
requestId: source.requestId,
|
|
1663
2121
|
messageId: source.messageId,
|
|
@@ -1684,7 +2142,7 @@ function createHandlers(options) {
|
|
|
1684
2142
|
const startEvents = [];
|
|
1685
2143
|
if (discoversMetadata && !Object.hasOwn(state.modelMetadata, catalogModelId)) startEvents.push({
|
|
1686
2144
|
type: "ai.model.metadata.requested",
|
|
1687
|
-
id:
|
|
2145
|
+
id: await aiEventId("a2.ai.model.metadata", options.agent.contract.name, ctx.event.sessionId, catalogModelId),
|
|
1688
2146
|
payload: { modelId: catalogModelId }
|
|
1689
2147
|
});
|
|
1690
2148
|
if (previous) {
|
|
@@ -1699,7 +2157,7 @@ function createHandlers(options) {
|
|
|
1699
2157
|
};
|
|
1700
2158
|
startEvents.push({
|
|
1701
2159
|
type: "ai.generation.failed",
|
|
1702
|
-
id:
|
|
2160
|
+
id: await aiEventId("a2.ai.superseded", payload.generationId),
|
|
1703
2161
|
payload: superseded
|
|
1704
2162
|
});
|
|
1705
2163
|
}
|
|
@@ -1716,15 +2174,9 @@ function createHandlers(options) {
|
|
|
1716
2174
|
generationStarted = true;
|
|
1717
2175
|
if (checkAbort(ctx.signal)) return;
|
|
1718
2176
|
const promptCoordinatorState = coordinatorState;
|
|
1719
|
-
const messages =
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
snapshot: {
|
|
1723
|
-
state,
|
|
1724
|
-
index: snapshot.index
|
|
1725
|
-
},
|
|
1726
|
-
coordinator: promptCoordinatorState,
|
|
1727
|
-
excluded: replacedGenerationIds
|
|
2177
|
+
const messages = contextMessages({
|
|
2178
|
+
state,
|
|
2179
|
+
coordinator: promptCoordinatorState
|
|
1728
2180
|
});
|
|
1729
2181
|
let generationMessages = messages;
|
|
1730
2182
|
let modelMessages = await modelContext({
|
|
@@ -1739,7 +2191,6 @@ function createHandlers(options) {
|
|
|
1739
2191
|
responseMessageId,
|
|
1740
2192
|
messages,
|
|
1741
2193
|
modelMessages,
|
|
1742
|
-
state,
|
|
1743
2194
|
session: resolverContext.session,
|
|
1744
2195
|
signal: ctx.signal,
|
|
1745
2196
|
model: resolvedModel,
|
|
@@ -1753,8 +2204,9 @@ function createHandlers(options) {
|
|
|
1753
2204
|
const limits = metadata?.status === "resolved" && !usesFallbackModels ? metadata.limits : void 0;
|
|
1754
2205
|
let inputTokenEstimate;
|
|
1755
2206
|
let compacted = false;
|
|
1756
|
-
|
|
1757
|
-
|
|
2207
|
+
let compactionEstimate;
|
|
2208
|
+
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)));
|
|
2209
|
+
if (policy && !hasPendingTools) {
|
|
1758
2210
|
const compactionContext = {
|
|
1759
2211
|
...resolverContext,
|
|
1760
2212
|
messages,
|
|
@@ -1776,8 +2228,18 @@ function createHandlers(options) {
|
|
|
1776
2228
|
const threshold = policy.thresholdTokens ?? (limits === void 0 ? void 0 : Math.floor(Math.min(limits.contextWindow * .75, limits.contextWindow - (generation.maxOutputTokens ?? 0))));
|
|
1777
2229
|
if (limits !== void 0 && threshold !== void 0 && threshold <= 0) throw new Error(`output allowance exhausts the ${limits.contextWindow}-token context window`);
|
|
1778
2230
|
shouldCompact = threshold !== void 0 && inputTokens >= threshold;
|
|
2231
|
+
if (threshold !== void 0 && shouldCompact) compactionEstimate = {
|
|
2232
|
+
inputTokenEstimate,
|
|
2233
|
+
inputTokens,
|
|
2234
|
+
thresholdTokens: threshold
|
|
2235
|
+
};
|
|
1779
2236
|
} else shouldCompact = false;
|
|
1780
2237
|
if (checkAbort(ctx.signal)) return;
|
|
2238
|
+
if (shouldCompact) {
|
|
2239
|
+
const latest = (await ctx.session.state(coordinator, { through: "latest" })).state;
|
|
2240
|
+
shouldCompact = Object.keys(latest.unsettledTools ?? {}).length === 0;
|
|
2241
|
+
}
|
|
2242
|
+
if (checkAbort(ctx.signal)) return;
|
|
1781
2243
|
if (shouldCompact) {
|
|
1782
2244
|
const throughMessageId = state.messages.findLast((message) => !promptCoordinatorState.queued.some((queued) => queued.messageId === message.id))?.id ?? request.messageId;
|
|
1783
2245
|
const throughIndex = snapshot.index;
|
|
@@ -1786,11 +2248,12 @@ function createHandlers(options) {
|
|
|
1786
2248
|
name: "compaction-requested",
|
|
1787
2249
|
events: [{
|
|
1788
2250
|
type: "ai.compaction.requested",
|
|
1789
|
-
id:
|
|
2251
|
+
id: await aiEventId("a2.ai.compaction.requested", generationId),
|
|
1790
2252
|
payload: {
|
|
1791
2253
|
generationId,
|
|
1792
2254
|
throughMessageId,
|
|
1793
|
-
throughIndex
|
|
2255
|
+
throughIndex,
|
|
2256
|
+
...compactionEstimate
|
|
1794
2257
|
}
|
|
1795
2258
|
}]
|
|
1796
2259
|
}));
|
|
@@ -1815,7 +2278,7 @@ function createHandlers(options) {
|
|
|
1815
2278
|
name: "compaction-completed",
|
|
1816
2279
|
events: [{
|
|
1817
2280
|
type: "ai.compaction.completed",
|
|
1818
|
-
id:
|
|
2281
|
+
id: await aiEventId("a2.ai.compaction.completed", generationId),
|
|
1819
2282
|
payload: completed
|
|
1820
2283
|
}]
|
|
1821
2284
|
}));
|
|
@@ -1825,16 +2288,13 @@ function createHandlers(options) {
|
|
|
1825
2288
|
}
|
|
1826
2289
|
}
|
|
1827
2290
|
const currentState = foldAIEvents({
|
|
1828
|
-
|
|
2291
|
+
reducer: contextReducer[aiStateReducer],
|
|
1829
2292
|
events: generationEvents,
|
|
1830
|
-
state
|
|
2293
|
+
state
|
|
1831
2294
|
});
|
|
1832
|
-
if (request.reason === "retry" || previous !== void 0) generationMessages =
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
snapshot,
|
|
1836
|
-
coordinator: promptCoordinatorState,
|
|
1837
|
-
appended: generationEvents
|
|
2295
|
+
if (request.reason === "retry" || previous !== void 0) generationMessages = contextMessages({
|
|
2296
|
+
state: currentState,
|
|
2297
|
+
coordinator: promptCoordinatorState
|
|
1838
2298
|
});
|
|
1839
2299
|
if (policy && "shouldCompact" in policy || generationMessages !== messages || activeCompaction(currentState)?.summary !== activeCompaction(state)?.summary) modelMessages = await modelContext({
|
|
1840
2300
|
messages: generationMessages,
|
|
@@ -1856,7 +2316,6 @@ function createHandlers(options) {
|
|
|
1856
2316
|
responseMessageId,
|
|
1857
2317
|
messages: generationMessages,
|
|
1858
2318
|
modelMessages,
|
|
1859
|
-
state: currentState,
|
|
1860
2319
|
session: resolverContext.session,
|
|
1861
2320
|
signal: ctx.signal,
|
|
1862
2321
|
model: resolvedModel,
|
|
@@ -1889,7 +2348,7 @@ function createHandlers(options) {
|
|
|
1889
2348
|
sequence,
|
|
1890
2349
|
chunks: update.chunks
|
|
1891
2350
|
};
|
|
1892
|
-
const lifecycle = lifecycleEvents({
|
|
2351
|
+
const lifecycle = await lifecycleEvents({
|
|
1893
2352
|
requestId,
|
|
1894
2353
|
messageId: responseMessageId,
|
|
1895
2354
|
generationId,
|
|
@@ -1906,7 +2365,7 @@ function createHandlers(options) {
|
|
|
1906
2365
|
name: `generation-progress:${sequence}`,
|
|
1907
2366
|
events: [{
|
|
1908
2367
|
type: "ai.generation.progress",
|
|
1909
|
-
id:
|
|
2368
|
+
id: await aiEventId("a2.ai.progress", generationId, String(sequence)),
|
|
1910
2369
|
payload: progress
|
|
1911
2370
|
}, ...lifecycle.events]
|
|
1912
2371
|
});
|
|
@@ -1924,19 +2383,19 @@ function createHandlers(options) {
|
|
|
1924
2383
|
};
|
|
1925
2384
|
const completionEvent = {
|
|
1926
2385
|
type: "ai.generation.completed",
|
|
1927
|
-
id:
|
|
2386
|
+
id: await aiEventId("a2.ai.completed", generationId),
|
|
1928
2387
|
payload: completed
|
|
1929
2388
|
};
|
|
1930
2389
|
const unresolved = options.generate ? pendingToolCalls.filter((pending) => pending.classification !== "automatic") : [];
|
|
1931
2390
|
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));
|
|
2391
|
+
const pendingEvents = await Promise.all(pendingToolCalls.filter((pending) => pending.classification === "automatic" || options.generate === void 0 && pending.classification === "unknown").map((pending) => toolCalledEvent(pending.call)));
|
|
1933
2392
|
if (finish.finishReason === "tool-calls") return [...pendingEvents, completionEvent];
|
|
1934
2393
|
return [
|
|
1935
2394
|
...pendingEvents,
|
|
1936
2395
|
completionEvent,
|
|
1937
2396
|
{
|
|
1938
2397
|
type: "ai.message.completed",
|
|
1939
|
-
id:
|
|
2398
|
+
id: await aiEventId("a2.ai.message.completed", generationId),
|
|
1940
2399
|
payload: { messageId: responseMessageId }
|
|
1941
2400
|
}
|
|
1942
2401
|
];
|
|
@@ -1957,7 +2416,7 @@ function createHandlers(options) {
|
|
|
1957
2416
|
};
|
|
1958
2417
|
return {
|
|
1959
2418
|
type: "ai.generation.failed",
|
|
1960
|
-
id:
|
|
2419
|
+
id: await aiEventId("a2.ai.failed", generationId),
|
|
1961
2420
|
payload: failed
|
|
1962
2421
|
};
|
|
1963
2422
|
}
|
|
@@ -1975,7 +2434,7 @@ function createHandlers(options) {
|
|
|
1975
2434
|
ctx.signal.throwIfAborted();
|
|
1976
2435
|
return {
|
|
1977
2436
|
type: "ai.model.metadata.resolved",
|
|
1978
|
-
id:
|
|
2437
|
+
id: await aiEventId("a2.ai.model.metadata.resolved", ctx.event.id),
|
|
1979
2438
|
payload: {
|
|
1980
2439
|
modelId: ctx.event.payload.modelId,
|
|
1981
2440
|
limits
|
|
@@ -1983,7 +2442,13 @@ function createHandlers(options) {
|
|
|
1983
2442
|
};
|
|
1984
2443
|
} },
|
|
1985
2444
|
"ai.control.requested": {
|
|
1986
|
-
lane:
|
|
2445
|
+
lane: ({ event }) => {
|
|
2446
|
+
const command = event.payload;
|
|
2447
|
+
if (event.id === "") throw new A2Error("INVALID_PAYLOAD", "AI command IDs must be nonempty");
|
|
2448
|
+
if (event.id?.startsWith("a2.ai:")) throw new A2Error("INVALID_PAYLOAD", `AI command IDs cannot use the reserved '${AI_EVENT_ID_PREFIX}' prefix`);
|
|
2449
|
+
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");
|
|
2450
|
+
return "a2.ai.control";
|
|
2451
|
+
},
|
|
1987
2452
|
handler: control.handler
|
|
1988
2453
|
},
|
|
1989
2454
|
"ai.work.reported": {
|