stitchkit 0.56.1 → 0.56.2
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/README.md +12 -3
- package/dist/agent-runtime/compaction.d.ts +33 -0
- package/dist/agent-runtime/compaction.d.ts.map +1 -0
- package/dist/agent-runtime/coordinator.d.ts +24 -0
- package/dist/agent-runtime/coordinator.d.ts.map +1 -0
- package/dist/agent-runtime/events.d.ts +454 -0
- package/dist/agent-runtime/events.d.ts.map +1 -0
- package/dist/agent-runtime/history.d.ts +11 -0
- package/dist/agent-runtime/history.d.ts.map +1 -0
- package/dist/agent-runtime/managed-tools.d.ts +18 -0
- package/dist/agent-runtime/managed-tools.d.ts.map +1 -0
- package/dist/agent-runtime/models.d.ts +51 -0
- package/dist/agent-runtime/models.d.ts.map +1 -0
- package/dist/agent-runtime/observability.d.ts +134 -0
- package/dist/agent-runtime/observability.d.ts.map +1 -0
- package/dist/agent-runtime/prompt.d.ts +50 -0
- package/dist/agent-runtime/prompt.d.ts.map +1 -0
- package/dist/agent-runtime/protocol.d.ts +16 -0
- package/dist/agent-runtime/protocol.d.ts.map +1 -0
- package/dist/agent-runtime/runtime.d.ts +120 -0
- package/dist/agent-runtime/runtime.d.ts.map +1 -0
- package/dist/agent-runtime/schemas.d.ts +470 -0
- package/dist/agent-runtime/schemas.d.ts.map +1 -0
- package/dist/agent-runtime/store.d.ts +905 -0
- package/dist/agent-runtime/store.d.ts.map +1 -0
- package/dist/agent-runtime/testing.d.ts +17 -0
- package/dist/agent-runtime/testing.d.ts.map +1 -0
- package/dist/agent-runtime-openrouter.d.ts +6 -0
- package/dist/agent-runtime-openrouter.d.ts.map +1 -0
- package/dist/agent-runtime-openrouter.js +46 -0
- package/dist/agent-runtime.d.ts +13 -0
- package/dist/agent-runtime.d.ts.map +1 -0
- package/dist/agent-runtime.js +1931 -0
- package/dist/cli.js +8 -5
- package/dist/index-0nc0cddp.js +178 -0
- package/dist/{index-6y759j86.js → index-1tkyng3g.js} +9 -57
- package/dist/{index-sm2tjx06.js → index-41wm56v0.js} +2 -2
- package/dist/index-6djpbnda.js +56 -0
- package/dist/{index-j3dem06f.js → index-7c0gkyvj.js} +5 -3
- package/dist/{index-75njxz7p.js → index-9zn9fb4e.js} +8 -6
- package/dist/{index-xy8fmh6w.js → index-f6pymtqe.js} +3 -3
- package/dist/{index-jcc611vh.js → index-gnvzyw0k.js} +2 -2
- package/dist/{index-qh4xevf5.js → index-mr617n62.js} +4 -2
- package/dist/{index-5r13htq1.js → index-ps5pxp3h.js} +1 -1
- package/dist/index-sa2mbwa7.js +336 -0
- package/dist/index-smpbdg6k.js +27 -0
- package/dist/{index-7eq9va6e.js → index-y2rb7dwx.js} +1 -27
- package/dist/{index-escqg10p.js → index-zk5zn2nh.js} +32 -334
- package/dist/internal/observability-sink.d.ts +26 -0
- package/dist/internal/observability-sink.d.ts.map +1 -0
- package/dist/node.js +6 -5
- package/dist/observability/audit.d.ts.map +1 -1
- package/dist/observability/index.js +15 -178
- package/dist/remote.js +4 -3
- package/dist/server/error-hook.d.ts +9 -7
- package/dist/server/error-hook.d.ts.map +1 -1
- package/dist/server/index.js +9 -7
- package/dist/testing.js +5 -3
- package/dist/tools/agent.d.ts.map +1 -1
- package/dist/tools/execute.d.ts +7 -0
- package/dist/tools/execute.d.ts.map +1 -1
- package/dist/tools.d.ts +2 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +23 -12
- package/llms-full.txt +365 -10
- package/llms.txt +1 -0
- package/package.json +16 -3
|
@@ -0,0 +1,1931 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ToolExecutionControlError,
|
|
3
|
+
isToolExecutionControlError
|
|
4
|
+
} from "./index-sa2mbwa7.js";
|
|
5
|
+
import"./index-6djpbnda.js";
|
|
6
|
+
import"./index-cby4ar3v.js";
|
|
7
|
+
import {
|
|
8
|
+
createBoundedSinkManager
|
|
9
|
+
} from "./index-0nc0cddp.js";
|
|
10
|
+
import"./index-7c0gkyvj.js";
|
|
11
|
+
import"./index-ps5pxp3h.js";
|
|
12
|
+
import"./index-6k1937bx.js";
|
|
13
|
+
import {
|
|
14
|
+
childSpan,
|
|
15
|
+
createTraceContext
|
|
16
|
+
} from "./index-y2rb7dwx.js";
|
|
17
|
+
import"./index-smpbdg6k.js";
|
|
18
|
+
import"./index-1bx83sw4.js";
|
|
19
|
+
|
|
20
|
+
// src/agent-runtime/compaction.ts
|
|
21
|
+
function providerValidTurn(messages) {
|
|
22
|
+
if (messages[0]?.role !== "user")
|
|
23
|
+
return false;
|
|
24
|
+
const assistant = messages.find((message) => message.role === "assistant");
|
|
25
|
+
if (!assistant || assistant.status === "streaming" || assistant.status === "failed") {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
const calls = new Set(assistant.parts.filter((part) => part.type === "tool-call").map((part) => part.callId));
|
|
29
|
+
const results = new Set(assistant.parts.filter((part) => part.type === "tool-result").map((part) => part.callId));
|
|
30
|
+
return [...calls].every((callId) => results.has(callId)) && [...results].every((callId) => calls.has(callId));
|
|
31
|
+
}
|
|
32
|
+
function groupProviderTurns(messages) {
|
|
33
|
+
const turns = [];
|
|
34
|
+
let current = [];
|
|
35
|
+
for (const message of messages) {
|
|
36
|
+
if (message.role === "user" && current.length > 0) {
|
|
37
|
+
turns.push({
|
|
38
|
+
messages: current,
|
|
39
|
+
complete: providerValidTurn(current)
|
|
40
|
+
});
|
|
41
|
+
current = [];
|
|
42
|
+
}
|
|
43
|
+
current.push(message);
|
|
44
|
+
}
|
|
45
|
+
if (current.length > 0) {
|
|
46
|
+
turns.push({
|
|
47
|
+
messages: current,
|
|
48
|
+
complete: providerValidTurn(current)
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return turns;
|
|
52
|
+
}
|
|
53
|
+
function eligibleForCompaction(messages, keepRecentTurns) {
|
|
54
|
+
const withoutLeadingSummary = messages[0]?.role === "summary" ? messages.slice(1) : messages;
|
|
55
|
+
const turns = groupProviderTurns(withoutLeadingSummary);
|
|
56
|
+
const firstIncomplete = turns.findIndex((turn) => !turn.complete);
|
|
57
|
+
const completeTurns = firstIncomplete === -1 ? turns : turns.slice(0, firstIncomplete);
|
|
58
|
+
const eligibleCount = Math.max(0, completeTurns.length - keepRecentTurns);
|
|
59
|
+
return completeTurns.slice(0, eligibleCount).flatMap((turn) => turn.messages);
|
|
60
|
+
}
|
|
61
|
+
function mutationSnapshot(result, fallback) {
|
|
62
|
+
if (result.outcome === "applied" || result.outcome === "duplicate") {
|
|
63
|
+
return { outcome: "applied", snapshot: result.snapshot };
|
|
64
|
+
}
|
|
65
|
+
return { outcome: result.outcome, snapshot: fallback };
|
|
66
|
+
}
|
|
67
|
+
function structuredCompaction(config) {
|
|
68
|
+
if (!Number.isSafeInteger(config.keepRecentTurns) || config.keepRecentTurns < 1) {
|
|
69
|
+
throw new TypeError("keepRecentTurns must be a positive safe integer");
|
|
70
|
+
}
|
|
71
|
+
return async (input) => {
|
|
72
|
+
const snapshot = await input.store.loadSnapshot(input.conversationId);
|
|
73
|
+
if (!await config.threshold(snapshot))
|
|
74
|
+
return { outcome: "not_needed", snapshot };
|
|
75
|
+
const eligibleMessages = eligibleForCompaction(snapshot.messages, config.keepRecentTurns);
|
|
76
|
+
if (eligibleMessages.length === 0)
|
|
77
|
+
return { outcome: "nothing_eligible", snapshot };
|
|
78
|
+
const leadingSummary = snapshot.messages[0]?.role === "summary" ? snapshot.messages[0] : undefined;
|
|
79
|
+
const previousSummary = input.previousSummary ?? (leadingSummary && config.readPreviousSummary ? config.schema.parse(config.readPreviousSummary(leadingSummary)) : undefined);
|
|
80
|
+
const rawSummary = await config.summarize({
|
|
81
|
+
conversationId: input.conversationId,
|
|
82
|
+
snapshot,
|
|
83
|
+
eligibleMessages,
|
|
84
|
+
...previousSummary !== undefined && { previousSummary },
|
|
85
|
+
signal: input.signal
|
|
86
|
+
});
|
|
87
|
+
const summary = config.schema.parse(rawSummary);
|
|
88
|
+
if (input.signal.aborted)
|
|
89
|
+
throw input.signal.reason;
|
|
90
|
+
const summaryMessage = config.createSummaryMessage({
|
|
91
|
+
conversationId: input.conversationId,
|
|
92
|
+
summary,
|
|
93
|
+
compactedMessages: eligibleMessages
|
|
94
|
+
});
|
|
95
|
+
if (summaryMessage.role !== "summary" || summaryMessage.status !== "committed") {
|
|
96
|
+
throw new TypeError("Compaction summary must be one committed summary message");
|
|
97
|
+
}
|
|
98
|
+
const previousSummaryMessage = previousSummary !== undefined && leadingSummary ? leadingSummary : undefined;
|
|
99
|
+
const applied = await input.store.replaceCompactedRange({
|
|
100
|
+
conversationId: input.conversationId,
|
|
101
|
+
expectedVersion: snapshot.version,
|
|
102
|
+
replacedMessageIds: [
|
|
103
|
+
...previousSummaryMessage ? [previousSummaryMessage.id] : [],
|
|
104
|
+
...eligibleMessages.map((message) => message.id)
|
|
105
|
+
],
|
|
106
|
+
summary: summaryMessage
|
|
107
|
+
});
|
|
108
|
+
return mutationSnapshot(applied, snapshot);
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
// src/agent-runtime/coordinator.ts
|
|
112
|
+
function createAgentSessionCoordinator() {
|
|
113
|
+
const lanes = new Map;
|
|
114
|
+
let closed = false;
|
|
115
|
+
const laneFor = (key) => {
|
|
116
|
+
const existing = lanes.get(key);
|
|
117
|
+
if (existing)
|
|
118
|
+
return existing;
|
|
119
|
+
const created = { queue: [] };
|
|
120
|
+
lanes.set(key, created);
|
|
121
|
+
return created;
|
|
122
|
+
};
|
|
123
|
+
const startNext = (key, lane) => {
|
|
124
|
+
if (lane.active)
|
|
125
|
+
return;
|
|
126
|
+
const next = lane.queue.shift();
|
|
127
|
+
if (!next) {
|
|
128
|
+
lanes.delete(key);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
next.start();
|
|
132
|
+
};
|
|
133
|
+
return {
|
|
134
|
+
submit(input) {
|
|
135
|
+
const accepted = Promise.withResolvers();
|
|
136
|
+
const result = Promise.withResolvers();
|
|
137
|
+
if (closed) {
|
|
138
|
+
const error = new Error("Agent session coordinator is closed");
|
|
139
|
+
accepted.reject(error);
|
|
140
|
+
result.reject(error);
|
|
141
|
+
return { accepted: accepted.promise, result: result.promise };
|
|
142
|
+
}
|
|
143
|
+
const lane = laneFor(input.key);
|
|
144
|
+
const pending = {
|
|
145
|
+
reject(reason) {
|
|
146
|
+
accepted.reject(reason);
|
|
147
|
+
result.reject(reason);
|
|
148
|
+
},
|
|
149
|
+
async start() {
|
|
150
|
+
const controller = new AbortController;
|
|
151
|
+
const settlement = Promise.withResolvers();
|
|
152
|
+
lane.active = { controller, settled: settlement.promise };
|
|
153
|
+
try {
|
|
154
|
+
const coordinated = await input.create(controller.signal);
|
|
155
|
+
accepted.resolve();
|
|
156
|
+
result.resolve(await coordinated.execute());
|
|
157
|
+
} catch (error) {
|
|
158
|
+
accepted.reject(error);
|
|
159
|
+
result.reject(error);
|
|
160
|
+
} finally {
|
|
161
|
+
settlement.resolve();
|
|
162
|
+
lane.active = undefined;
|
|
163
|
+
startNext(input.key, lane);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
if (input.policy === "interrupt")
|
|
168
|
+
lane.active?.controller.abort("user-interrupt");
|
|
169
|
+
lane.queue.push(pending);
|
|
170
|
+
startNext(input.key, lane);
|
|
171
|
+
return { accepted: accepted.promise, result: result.promise };
|
|
172
|
+
},
|
|
173
|
+
stop(key, reason = "user-interrupt") {
|
|
174
|
+
const lane = lanes.get(key);
|
|
175
|
+
if (!lane?.active)
|
|
176
|
+
return false;
|
|
177
|
+
lane.active.controller.abort(reason);
|
|
178
|
+
return true;
|
|
179
|
+
},
|
|
180
|
+
async close(options = {}) {
|
|
181
|
+
closed = true;
|
|
182
|
+
const error = new Error("Agent session coordinator is closed");
|
|
183
|
+
for (const lane of lanes.values()) {
|
|
184
|
+
lane.active?.controller.abort("shutdown");
|
|
185
|
+
for (const pending of lane.queue.splice(0))
|
|
186
|
+
pending.reject(error);
|
|
187
|
+
}
|
|
188
|
+
const settlements = [...lanes.values()].map((lane) => lane.active?.settled).filter((settled) => settled !== undefined);
|
|
189
|
+
const drain = Promise.all(settlements).then(() => {
|
|
190
|
+
return;
|
|
191
|
+
});
|
|
192
|
+
if (options.drainTimeoutMs === undefined)
|
|
193
|
+
return drain;
|
|
194
|
+
await Promise.race([
|
|
195
|
+
drain,
|
|
196
|
+
new Promise((resolve) => setTimeout(resolve, options.drainTimeoutMs))
|
|
197
|
+
]);
|
|
198
|
+
},
|
|
199
|
+
isRunning(key) {
|
|
200
|
+
return lanes.get(key)?.active !== undefined;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
// src/agent-runtime/events.ts
|
|
205
|
+
import { z as z2 } from "zod";
|
|
206
|
+
|
|
207
|
+
// src/agent-runtime/schemas.ts
|
|
208
|
+
import { z } from "zod";
|
|
209
|
+
var AgentRecordIdSchema = z.string().min(1);
|
|
210
|
+
var AgentRecordVersionSchema = z.int().nonnegative();
|
|
211
|
+
var AgentTimestampSchema = z.iso.datetime({ offset: true });
|
|
212
|
+
var AgentJsonObjectSchema = z.record(z.string(), z.json());
|
|
213
|
+
var AgentProviderEnvelopeSchema = z.object({
|
|
214
|
+
schemaVersion: z.int().positive(),
|
|
215
|
+
provider: z.string().min(1),
|
|
216
|
+
data: AgentJsonObjectSchema
|
|
217
|
+
});
|
|
218
|
+
var AgentTextPartSchema = z.object({
|
|
219
|
+
type: z.literal("text"),
|
|
220
|
+
text: z.string()
|
|
221
|
+
});
|
|
222
|
+
var AgentReasoningPartSchema = z.object({
|
|
223
|
+
type: z.literal("reasoning"),
|
|
224
|
+
text: z.string(),
|
|
225
|
+
provider: AgentProviderEnvelopeSchema.optional()
|
|
226
|
+
});
|
|
227
|
+
var AgentFilePartSchema = z.object({
|
|
228
|
+
type: z.literal("file"),
|
|
229
|
+
mediaType: z.string().min(1),
|
|
230
|
+
reference: z.string().min(1),
|
|
231
|
+
filename: z.string().min(1).optional()
|
|
232
|
+
});
|
|
233
|
+
var AgentSourcePartSchema = z.object({
|
|
234
|
+
type: z.literal("source"),
|
|
235
|
+
sourceId: z.string().min(1),
|
|
236
|
+
url: z.url().optional(),
|
|
237
|
+
title: z.string().optional()
|
|
238
|
+
});
|
|
239
|
+
var AgentToolCallPartSchema = z.object({
|
|
240
|
+
type: z.literal("tool-call"),
|
|
241
|
+
callId: z.string().min(1),
|
|
242
|
+
toolName: z.string().min(1),
|
|
243
|
+
input: z.json(),
|
|
244
|
+
provider: AgentProviderEnvelopeSchema.optional()
|
|
245
|
+
});
|
|
246
|
+
var AgentToolResultPartSchema = z.object({
|
|
247
|
+
type: z.literal("tool-result"),
|
|
248
|
+
callId: z.string().min(1),
|
|
249
|
+
toolName: z.string().min(1),
|
|
250
|
+
outcome: z.enum(["success", "error", "interrupted"]),
|
|
251
|
+
output: z.json().optional()
|
|
252
|
+
});
|
|
253
|
+
var AgentOpaquePartSchema = z.object({
|
|
254
|
+
type: z.literal("provider"),
|
|
255
|
+
envelope: AgentProviderEnvelopeSchema
|
|
256
|
+
});
|
|
257
|
+
var AgentControlPartSchema = z.object({
|
|
258
|
+
type: z.literal("control"),
|
|
259
|
+
reason: z.enum(["run-interrupted", "stale-run"])
|
|
260
|
+
});
|
|
261
|
+
var AgentMessagePartSchema = z.discriminatedUnion("type", [
|
|
262
|
+
AgentTextPartSchema,
|
|
263
|
+
AgentReasoningPartSchema,
|
|
264
|
+
AgentFilePartSchema,
|
|
265
|
+
AgentSourcePartSchema,
|
|
266
|
+
AgentToolCallPartSchema,
|
|
267
|
+
AgentToolResultPartSchema,
|
|
268
|
+
AgentOpaquePartSchema,
|
|
269
|
+
AgentControlPartSchema
|
|
270
|
+
]);
|
|
271
|
+
var AgentMessageRoleSchema = z.enum(["user", "assistant", "system", "summary"]);
|
|
272
|
+
var AgentMessageStatusSchema = z.enum([
|
|
273
|
+
"committed",
|
|
274
|
+
"streaming",
|
|
275
|
+
"completed",
|
|
276
|
+
"interrupted",
|
|
277
|
+
"failed"
|
|
278
|
+
]);
|
|
279
|
+
var AgentMessageSchema = z.object({
|
|
280
|
+
schemaVersion: z.literal(1),
|
|
281
|
+
id: AgentRecordIdSchema,
|
|
282
|
+
conversationId: AgentRecordIdSchema,
|
|
283
|
+
runId: AgentRecordIdSchema.optional(),
|
|
284
|
+
role: AgentMessageRoleSchema,
|
|
285
|
+
status: AgentMessageStatusSchema,
|
|
286
|
+
parts: z.array(AgentMessagePartSchema),
|
|
287
|
+
metadata: AgentJsonObjectSchema.optional(),
|
|
288
|
+
createdAt: AgentTimestampSchema,
|
|
289
|
+
updatedAt: AgentTimestampSchema
|
|
290
|
+
});
|
|
291
|
+
var AgentRunStateSchema = z.enum([
|
|
292
|
+
"queued",
|
|
293
|
+
"running",
|
|
294
|
+
"interrupt_requested",
|
|
295
|
+
"completed",
|
|
296
|
+
"interrupted",
|
|
297
|
+
"failed",
|
|
298
|
+
"cancelled",
|
|
299
|
+
"abandoned"
|
|
300
|
+
]);
|
|
301
|
+
var AgentTerminalReasonSchema = z.enum([
|
|
302
|
+
"success",
|
|
303
|
+
"policy_stop",
|
|
304
|
+
"interrupted",
|
|
305
|
+
"cancelled",
|
|
306
|
+
"timeout",
|
|
307
|
+
"shutdown",
|
|
308
|
+
"provider_failure",
|
|
309
|
+
"tool_failure",
|
|
310
|
+
"abandoned"
|
|
311
|
+
]);
|
|
312
|
+
var AgentRunSchema = z.object({
|
|
313
|
+
schemaVersion: z.literal(1),
|
|
314
|
+
id: AgentRecordIdSchema,
|
|
315
|
+
conversationId: AgentRecordIdSchema,
|
|
316
|
+
inputMessageIds: z.array(AgentRecordIdSchema).min(1),
|
|
317
|
+
assistantMessageId: AgentRecordIdSchema,
|
|
318
|
+
state: AgentRunStateSchema,
|
|
319
|
+
revision: AgentRecordVersionSchema,
|
|
320
|
+
ownerId: z.string().min(1).optional(),
|
|
321
|
+
terminalReason: AgentTerminalReasonSchema.optional(),
|
|
322
|
+
terminalPolicyName: z.string().min(1).optional(),
|
|
323
|
+
createdAt: AgentTimestampSchema,
|
|
324
|
+
updatedAt: AgentTimestampSchema
|
|
325
|
+
});
|
|
326
|
+
var AgentSnapshotSchema = z.object({
|
|
327
|
+
schemaVersion: z.literal(1),
|
|
328
|
+
conversationId: AgentRecordIdSchema,
|
|
329
|
+
version: AgentRecordVersionSchema,
|
|
330
|
+
messages: z.array(AgentMessageSchema),
|
|
331
|
+
runs: z.array(AgentRunSchema)
|
|
332
|
+
});
|
|
333
|
+
var AgentUsageValueSchema = z.object({
|
|
334
|
+
value: z.number().nonnegative().optional(),
|
|
335
|
+
provenance: z.enum(["provider-reported", "computed", "estimated", "unavailable"])
|
|
336
|
+
});
|
|
337
|
+
var AgentCostValueSchema = z.object({
|
|
338
|
+
value: z.number().nonnegative().optional(),
|
|
339
|
+
currency: z.string().length(3).optional(),
|
|
340
|
+
provenance: z.enum(["provider-reported", "computed", "estimated", "unavailable"])
|
|
341
|
+
});
|
|
342
|
+
var AgentUsageSchema = z.object({
|
|
343
|
+
inputTokens: AgentUsageValueSchema,
|
|
344
|
+
outputTokens: AgentUsageValueSchema,
|
|
345
|
+
reasoningTokens: AgentUsageValueSchema.optional(),
|
|
346
|
+
cacheReadTokens: AgentUsageValueSchema.optional(),
|
|
347
|
+
cacheWriteTokens: AgentUsageValueSchema.optional(),
|
|
348
|
+
cost: AgentCostValueSchema.optional()
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// src/agent-runtime/events.ts
|
|
352
|
+
var EventIdentitySchema = z2.object({
|
|
353
|
+
conversationId: AgentRecordIdSchema,
|
|
354
|
+
runId: AgentRecordIdSchema,
|
|
355
|
+
emittedAt: AgentTimestampSchema
|
|
356
|
+
});
|
|
357
|
+
var AgentTransientDeltaEventSchema = EventIdentitySchema.extend({
|
|
358
|
+
type: z2.literal("assistant-delta"),
|
|
359
|
+
runtimeEpoch: z2.string().min(1),
|
|
360
|
+
sequence: z2.int().nonnegative(),
|
|
361
|
+
textDelta: z2.string()
|
|
362
|
+
});
|
|
363
|
+
var AgentCheckpointEventSchema = EventIdentitySchema.extend({
|
|
364
|
+
type: z2.literal("assistant-checkpoint"),
|
|
365
|
+
eventId: AgentRecordIdSchema,
|
|
366
|
+
snapshotVersion: AgentRecordVersionSchema,
|
|
367
|
+
message: AgentMessageSchema
|
|
368
|
+
});
|
|
369
|
+
var AgentRunStateEventSchema = EventIdentitySchema.extend({
|
|
370
|
+
type: z2.literal("run-state"),
|
|
371
|
+
eventId: AgentRecordIdSchema,
|
|
372
|
+
snapshotVersion: AgentRecordVersionSchema,
|
|
373
|
+
state: AgentRunStateSchema
|
|
374
|
+
});
|
|
375
|
+
var AgentToolStatusEventSchema = EventIdentitySchema.extend({
|
|
376
|
+
type: z2.literal("tool-status"),
|
|
377
|
+
runtimeEpoch: z2.string().min(1),
|
|
378
|
+
sequence: z2.int().nonnegative(),
|
|
379
|
+
callId: AgentRecordIdSchema,
|
|
380
|
+
toolName: z2.string().min(1),
|
|
381
|
+
status: z2.enum(["started", "completed", "failed", "interrupted"]),
|
|
382
|
+
input: z2.json().optional(),
|
|
383
|
+
output: z2.json().optional()
|
|
384
|
+
});
|
|
385
|
+
var AgentTerminalEventSchema = EventIdentitySchema.extend({
|
|
386
|
+
type: z2.literal("terminal"),
|
|
387
|
+
eventId: AgentRecordIdSchema,
|
|
388
|
+
snapshotVersion: AgentRecordVersionSchema,
|
|
389
|
+
reason: AgentTerminalReasonSchema,
|
|
390
|
+
policyName: z2.string().min(1).optional(),
|
|
391
|
+
message: AgentMessageSchema
|
|
392
|
+
});
|
|
393
|
+
var AgentRuntimeEventSchema = z2.discriminatedUnion("type", [
|
|
394
|
+
AgentTransientDeltaEventSchema,
|
|
395
|
+
AgentCheckpointEventSchema,
|
|
396
|
+
AgentRunStateEventSchema,
|
|
397
|
+
AgentToolStatusEventSchema,
|
|
398
|
+
AgentTerminalEventSchema
|
|
399
|
+
]);
|
|
400
|
+
// src/agent-runtime/history.ts
|
|
401
|
+
import { modelMessageSchema } from "ai";
|
|
402
|
+
function providerOptions(envelope) {
|
|
403
|
+
if (!envelope)
|
|
404
|
+
return;
|
|
405
|
+
return envelope.provider === "ai-sdk" ? envelope.data : { [envelope.provider]: envelope.data };
|
|
406
|
+
}
|
|
407
|
+
function textContent(parts) {
|
|
408
|
+
return parts.filter((part) => part.type === "text").map((part) => part.text).join(`
|
|
409
|
+
`);
|
|
410
|
+
}
|
|
411
|
+
async function userMessage(message, options) {
|
|
412
|
+
const text = textContent(message.parts);
|
|
413
|
+
const content = [];
|
|
414
|
+
if (text)
|
|
415
|
+
content.push({ type: "text", text });
|
|
416
|
+
for (const part of message.parts) {
|
|
417
|
+
if (part.type !== "file")
|
|
418
|
+
continue;
|
|
419
|
+
if (options.resolveFile) {
|
|
420
|
+
content.push({
|
|
421
|
+
type: "file",
|
|
422
|
+
data: await options.resolveFile(part, message),
|
|
423
|
+
mediaType: part.mediaType,
|
|
424
|
+
...part.filename && { filename: part.filename }
|
|
425
|
+
});
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
const fallback = options.unresolvedFile ?? "text";
|
|
429
|
+
if (fallback === "error") {
|
|
430
|
+
throw new Error(`No history file resolver configured for ${part.reference}`);
|
|
431
|
+
}
|
|
432
|
+
if (fallback === "text") {
|
|
433
|
+
content.push({ type: "text", text: `[attachment: ${part.reference}]` });
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
if (content.length === 0)
|
|
437
|
+
return;
|
|
438
|
+
return modelMessageSchema.parse({ role: "user", content });
|
|
439
|
+
}
|
|
440
|
+
function assistantMessages(message) {
|
|
441
|
+
const assistantContent = [];
|
|
442
|
+
const toolContent = [];
|
|
443
|
+
for (const part of message.parts) {
|
|
444
|
+
if (part.type === "text")
|
|
445
|
+
assistantContent.push({ type: "text", text: part.text });
|
|
446
|
+
if (part.type === "reasoning") {
|
|
447
|
+
assistantContent.push({
|
|
448
|
+
type: "reasoning",
|
|
449
|
+
text: part.text,
|
|
450
|
+
...part.provider && { providerOptions: providerOptions(part.provider) }
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
if (part.type === "tool-call") {
|
|
454
|
+
assistantContent.push({
|
|
455
|
+
type: "tool-call",
|
|
456
|
+
toolCallId: part.callId,
|
|
457
|
+
toolName: part.toolName,
|
|
458
|
+
input: part.input,
|
|
459
|
+
...part.provider && { providerOptions: providerOptions(part.provider) }
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
if (part.type === "tool-result") {
|
|
463
|
+
const output = part.outcome === "success" ? { type: "json", value: part.output ?? null } : { type: "error-json", value: part.output ?? { message: part.outcome } };
|
|
464
|
+
toolContent.push({
|
|
465
|
+
type: "tool-result",
|
|
466
|
+
toolCallId: part.callId,
|
|
467
|
+
toolName: part.toolName,
|
|
468
|
+
output
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
const messages = [];
|
|
473
|
+
if (assistantContent.length > 0) {
|
|
474
|
+
messages.push(modelMessageSchema.parse({ role: "assistant", content: assistantContent }));
|
|
475
|
+
}
|
|
476
|
+
if (toolContent.length > 0) {
|
|
477
|
+
messages.push(modelMessageSchema.parse({ role: "tool", content: toolContent }));
|
|
478
|
+
}
|
|
479
|
+
return messages;
|
|
480
|
+
}
|
|
481
|
+
async function projectAgentHistory(messages, options = {}) {
|
|
482
|
+
const projected = [];
|
|
483
|
+
for (const message of messages) {
|
|
484
|
+
if (message.status === "streaming" || message.status === "failed")
|
|
485
|
+
continue;
|
|
486
|
+
if (message.role === "user") {
|
|
487
|
+
const user = await userMessage(message, options);
|
|
488
|
+
if (user)
|
|
489
|
+
projected.push(user);
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
if (message.role === "system" || message.role === "summary") {
|
|
493
|
+
const content = textContent(message.parts);
|
|
494
|
+
if (content)
|
|
495
|
+
projected.push(modelMessageSchema.parse({ role: "system", content }));
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
projected.push(...assistantMessages(message));
|
|
499
|
+
}
|
|
500
|
+
return projected;
|
|
501
|
+
}
|
|
502
|
+
// src/agent-runtime/managed-tools.ts
|
|
503
|
+
async function assertFence(config, input) {
|
|
504
|
+
const rejected = await config.assertCurrent(input);
|
|
505
|
+
if (rejected)
|
|
506
|
+
throw new ToolExecutionControlError(rejected);
|
|
507
|
+
}
|
|
508
|
+
function createAgentToolFenceLifecycle(config) {
|
|
509
|
+
const callContext = (ctx, endpoint) => ({
|
|
510
|
+
runId: config.runId,
|
|
511
|
+
...config.context?.(ctx, endpoint)
|
|
512
|
+
});
|
|
513
|
+
return {
|
|
514
|
+
async beforeHandle(ctx, endpoint) {
|
|
515
|
+
await assertFence(config, callContext(ctx, endpoint));
|
|
516
|
+
},
|
|
517
|
+
async afterHandle(ctx, result, endpoint) {
|
|
518
|
+
const current = callContext(ctx, endpoint);
|
|
519
|
+
await assertFence(config, current);
|
|
520
|
+
await config.onSettled?.(current);
|
|
521
|
+
return result;
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
// src/agent-runtime/models.ts
|
|
526
|
+
import { z as z3 } from "zod";
|
|
527
|
+
var AgentModelCapabilitySchema = z3.enum(["tools", "vision", "reasoning", "files"]);
|
|
528
|
+
var AgentModelDescriptorSchema = z3.object({
|
|
529
|
+
provider: z3.string().min(1),
|
|
530
|
+
modelId: z3.string().min(1),
|
|
531
|
+
contextWindow: z3.int().positive(),
|
|
532
|
+
capabilities: z3.array(AgentModelCapabilitySchema),
|
|
533
|
+
observedAt: z3.iso.datetime({ offset: true }).optional(),
|
|
534
|
+
source: z3.string().min(1).optional()
|
|
535
|
+
});
|
|
536
|
+
function defineModelRegistry(config) {
|
|
537
|
+
const descriptors = new Map;
|
|
538
|
+
for (const key in config.models) {
|
|
539
|
+
const declaration = config.models[key];
|
|
540
|
+
if (declaration)
|
|
541
|
+
descriptors.set(key, AgentModelDescriptorSchema.parse(declaration));
|
|
542
|
+
}
|
|
543
|
+
const descriptor = (key) => {
|
|
544
|
+
const found = descriptors.get(key);
|
|
545
|
+
if (!found)
|
|
546
|
+
throw new Error(`Unknown agent model: ${key}`);
|
|
547
|
+
return found;
|
|
548
|
+
};
|
|
549
|
+
const supports = (key, capabilities) => {
|
|
550
|
+
const available = new Set(descriptor(key).capabilities);
|
|
551
|
+
return capabilities.every((capability) => available.has(capability));
|
|
552
|
+
};
|
|
553
|
+
return {
|
|
554
|
+
keys: () => [...descriptors.keys()],
|
|
555
|
+
descriptor,
|
|
556
|
+
supports,
|
|
557
|
+
resolve(key, required = []) {
|
|
558
|
+
const selected = descriptor(key);
|
|
559
|
+
if (!supports(key, required)) {
|
|
560
|
+
throw new Error(`Agent model ${key} does not satisfy required capabilities`);
|
|
561
|
+
}
|
|
562
|
+
const provider = config.providers[selected.provider];
|
|
563
|
+
if (!provider)
|
|
564
|
+
throw new Error(`Unknown agent model provider: ${selected.provider}`);
|
|
565
|
+
return {
|
|
566
|
+
descriptor: selected,
|
|
567
|
+
model: provider.create(selected.modelId),
|
|
568
|
+
...provider.normalizeUsage && { normalizeUsage: provider.normalizeUsage }
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
// src/agent-runtime/observability.ts
|
|
574
|
+
import { z as z4 } from "zod";
|
|
575
|
+
var AgentRunEventSchema = z4.object({
|
|
576
|
+
schemaVersion: z4.literal(1),
|
|
577
|
+
eventId: AgentRecordIdSchema,
|
|
578
|
+
type: z4.enum(["run-started", "step-finished", "run-terminal"]),
|
|
579
|
+
conversationId: AgentRecordIdSchema,
|
|
580
|
+
runId: AgentRecordIdSchema,
|
|
581
|
+
traceId: z4.string().min(1),
|
|
582
|
+
spanId: z4.string().min(1),
|
|
583
|
+
parentSpanId: z4.string().min(1).optional(),
|
|
584
|
+
state: AgentRunStateSchema,
|
|
585
|
+
terminalReason: AgentTerminalReasonSchema.optional(),
|
|
586
|
+
modelId: z4.string().min(1).optional(),
|
|
587
|
+
step: z4.int().nonnegative().optional(),
|
|
588
|
+
queueWaitMs: z4.number().nonnegative().optional(),
|
|
589
|
+
durationMs: z4.number().nonnegative().optional(),
|
|
590
|
+
ttftMs: z4.number().nonnegative().optional(),
|
|
591
|
+
usage: AgentUsageSchema.optional(),
|
|
592
|
+
internalCause: z4.unknown().optional(),
|
|
593
|
+
emittedAt: AgentTimestampSchema
|
|
594
|
+
});
|
|
595
|
+
function createAgentObservability(config) {
|
|
596
|
+
const manager = createBoundedSinkManager({
|
|
597
|
+
write: config.write,
|
|
598
|
+
...config.filter && { filter: config.filter },
|
|
599
|
+
...config.maxPending !== undefined && { maxPending: config.maxPending },
|
|
600
|
+
...config.onSinkError && { onSinkError: config.onSinkError },
|
|
601
|
+
...config.onDrop && { onDrop: config.onDrop }
|
|
602
|
+
});
|
|
603
|
+
return {
|
|
604
|
+
rootTrace(parent) {
|
|
605
|
+
const trace = parent ? childSpan(parent) : createTraceContext();
|
|
606
|
+
return trace;
|
|
607
|
+
},
|
|
608
|
+
emit(rawEvent) {
|
|
609
|
+
manager.submit(() => AgentRunEventSchema.parse(rawEvent));
|
|
610
|
+
},
|
|
611
|
+
flush: () => manager.flush(),
|
|
612
|
+
getStatus: () => manager.getStatus(),
|
|
613
|
+
close: () => manager.close()
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
// src/agent-runtime/prompt.ts
|
|
617
|
+
import { z as z5 } from "zod";
|
|
618
|
+
var AgentTokenCountSchema = z5.object({
|
|
619
|
+
value: z5.int().nonnegative().optional(),
|
|
620
|
+
provenance: z5.enum(["measured", "estimated", "unavailable"])
|
|
621
|
+
});
|
|
622
|
+
function knownValue(value) {
|
|
623
|
+
return value.provenance === "unavailable" ? undefined : value.value;
|
|
624
|
+
}
|
|
625
|
+
function composeAgentPrompt(sections) {
|
|
626
|
+
return async (options) => {
|
|
627
|
+
const rendered = [];
|
|
628
|
+
let total = 0;
|
|
629
|
+
let unavailable = false;
|
|
630
|
+
let estimated = false;
|
|
631
|
+
for (const section of sections) {
|
|
632
|
+
const text = await section.render({ context: options.context, signal: options.signal });
|
|
633
|
+
rendered.push({ name: section.name, stability: section.stability, text });
|
|
634
|
+
let count;
|
|
635
|
+
if (section.estimateTokens)
|
|
636
|
+
count = await section.estimateTokens(text);
|
|
637
|
+
else if (options.estimateFallback)
|
|
638
|
+
count = await options.estimateFallback(text);
|
|
639
|
+
else
|
|
640
|
+
count = { provenance: "unavailable" };
|
|
641
|
+
const value = knownValue(count);
|
|
642
|
+
if (value === undefined)
|
|
643
|
+
unavailable = true;
|
|
644
|
+
else
|
|
645
|
+
total += value;
|
|
646
|
+
if (count.provenance === "estimated")
|
|
647
|
+
estimated = true;
|
|
648
|
+
}
|
|
649
|
+
const instructionTokens = unavailable ? { provenance: "unavailable" } : { value: total, provenance: estimated ? "estimated" : "measured" };
|
|
650
|
+
let availableHistoryTokens;
|
|
651
|
+
let contextDecision = "unavailable";
|
|
652
|
+
if (options.budget) {
|
|
653
|
+
const reserveValues = [
|
|
654
|
+
options.budget.reservedOutput,
|
|
655
|
+
knownValue(options.budget.toolSchemas),
|
|
656
|
+
knownValue(options.budget.attachments),
|
|
657
|
+
knownValue(options.budget.providerOverhead),
|
|
658
|
+
knownValue(instructionTokens)
|
|
659
|
+
];
|
|
660
|
+
if (reserveValues.every((value) => value !== undefined)) {
|
|
661
|
+
availableHistoryTokens = Math.max(0, options.budget.contextWindow - reserveValues.reduce((sum, value) => sum + (value ?? 0), 0));
|
|
662
|
+
const historyTokens = options.historyTokens ? knownValue(options.historyTokens) : undefined;
|
|
663
|
+
if (historyTokens !== undefined) {
|
|
664
|
+
if (historyTokens <= availableHistoryTokens)
|
|
665
|
+
contextDecision = "fits";
|
|
666
|
+
else
|
|
667
|
+
contextDecision = options.oversizePolicy === "compact" ? "requires-compaction" : "oversized";
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
const instructionText = rendered.map((section) => section.text).join(`
|
|
672
|
+
|
|
673
|
+
`);
|
|
674
|
+
return {
|
|
675
|
+
instructions: options.adaptInstructions ? await options.adaptInstructions(instructionText) : instructionText,
|
|
676
|
+
sections: rendered,
|
|
677
|
+
instructionTokens,
|
|
678
|
+
contextDecision,
|
|
679
|
+
...availableHistoryTokens !== undefined && { availableHistoryTokens }
|
|
680
|
+
};
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
// src/agent-runtime/protocol.ts
|
|
684
|
+
function defineAgentProtocol(config) {
|
|
685
|
+
return {
|
|
686
|
+
contextSchema: config.context,
|
|
687
|
+
inputMetadataSchema: config.inputMetadata,
|
|
688
|
+
parseContext: (input) => config.context.parse(input),
|
|
689
|
+
parseInputMetadata: (input) => AgentJsonObjectSchema.parse(config.inputMetadata.parse(input)),
|
|
690
|
+
parseMessage: (input) => AgentMessageSchema.parse(input),
|
|
691
|
+
parsePart: (input) => AgentMessageSchema.shape.parts.element.parse(input)
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
// src/agent-runtime/runtime.ts
|
|
695
|
+
import {
|
|
696
|
+
stepCountIs,
|
|
697
|
+
streamText
|
|
698
|
+
} from "ai";
|
|
699
|
+
import { z as z6 } from "zod";
|
|
700
|
+
class AgentRuntimeConflictError extends Error {
|
|
701
|
+
constructor(operation) {
|
|
702
|
+
super(`Agent runtime store conflict during ${operation}`);
|
|
703
|
+
this.name = "AgentRuntimeConflictError";
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
function appliedSnapshot(result, operation) {
|
|
707
|
+
if (result.outcome === "applied" || result.outcome === "duplicate")
|
|
708
|
+
return result.snapshot;
|
|
709
|
+
throw new AgentRuntimeConflictError(operation);
|
|
710
|
+
}
|
|
711
|
+
function findRun(runs, runId) {
|
|
712
|
+
const run = runs.find((candidate) => candidate.id === runId);
|
|
713
|
+
if (!run)
|
|
714
|
+
throw new AgentRuntimeConflictError("run lookup");
|
|
715
|
+
return run;
|
|
716
|
+
}
|
|
717
|
+
function jsonValue(value) {
|
|
718
|
+
const parsed = z6.json().safeParse(value);
|
|
719
|
+
return parsed.success ? parsed.data : { message: "Non-JSON tool output omitted" };
|
|
720
|
+
}
|
|
721
|
+
function providerEnvelope(value) {
|
|
722
|
+
const parsed = AgentJsonObjectSchema.safeParse(value);
|
|
723
|
+
if (!parsed.success)
|
|
724
|
+
return;
|
|
725
|
+
return { schemaVersion: 1, provider: "ai-sdk", data: parsed.data };
|
|
726
|
+
}
|
|
727
|
+
function appendText(parts, text) {
|
|
728
|
+
const previous = parts.at(-1);
|
|
729
|
+
if (previous?.type === "text") {
|
|
730
|
+
const next = AgentMessagePartSchema.parse({ ...previous, text: previous.text + text });
|
|
731
|
+
parts.splice(parts.length - 1, 1, next);
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
parts.push(AgentMessagePartSchema.parse({ type: "text", text }));
|
|
735
|
+
}
|
|
736
|
+
function assistantStatus(reason) {
|
|
737
|
+
if (reason === "success" || reason === "policy_stop")
|
|
738
|
+
return "completed";
|
|
739
|
+
if (reason === "interrupted" || reason === "cancelled" || reason === "shutdown") {
|
|
740
|
+
return "interrupted";
|
|
741
|
+
}
|
|
742
|
+
return "failed";
|
|
743
|
+
}
|
|
744
|
+
function abortTerminalReason(signal) {
|
|
745
|
+
if (signal.reason === "shutdown")
|
|
746
|
+
return "shutdown";
|
|
747
|
+
if (signal.reason === "timeout")
|
|
748
|
+
return "timeout";
|
|
749
|
+
return "interrupted";
|
|
750
|
+
}
|
|
751
|
+
function normalizeSdkUsage(value) {
|
|
752
|
+
const reported = (tokens) => tokens === undefined ? { provenance: "unavailable" } : { value: tokens, provenance: "provider-reported" };
|
|
753
|
+
return {
|
|
754
|
+
inputTokens: reported(value.inputTokens),
|
|
755
|
+
outputTokens: reported(value.outputTokens),
|
|
756
|
+
reasoningTokens: reported(value.outputTokenDetails.reasoningTokens),
|
|
757
|
+
cacheReadTokens: reported(value.inputTokenDetails.cacheReadTokens),
|
|
758
|
+
cacheWriteTokens: reported(value.inputTokenDetails.cacheWriteTokens)
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
function createIdleDeadline(parent, timeoutMs) {
|
|
762
|
+
if (timeoutMs === undefined) {
|
|
763
|
+
const noop = () => {
|
|
764
|
+
return;
|
|
765
|
+
};
|
|
766
|
+
return { signal: parent, touch: noop, dispose: noop };
|
|
767
|
+
}
|
|
768
|
+
const controller = new AbortController;
|
|
769
|
+
let timer;
|
|
770
|
+
const touch = () => {
|
|
771
|
+
if (timer !== undefined)
|
|
772
|
+
clearTimeout(timer);
|
|
773
|
+
timer = setTimeout(() => controller.abort("timeout"), timeoutMs);
|
|
774
|
+
};
|
|
775
|
+
touch();
|
|
776
|
+
return {
|
|
777
|
+
signal: AbortSignal.any([parent, controller.signal]),
|
|
778
|
+
touch,
|
|
779
|
+
dispose() {
|
|
780
|
+
if (timer !== undefined)
|
|
781
|
+
clearTimeout(timer);
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
function createAgentRuntime(config) {
|
|
786
|
+
const coordinator = createAgentSessionCoordinator();
|
|
787
|
+
const tickets = new Map;
|
|
788
|
+
const generateId = config.generateId ?? (() => crypto.randomUUID());
|
|
789
|
+
const now = config.now ?? (() => new Date);
|
|
790
|
+
const runtimeEpoch = generateId();
|
|
791
|
+
const admissionLanes = new Map;
|
|
792
|
+
const reserveAdmission = (key, runId) => {
|
|
793
|
+
const existing = admissionLanes.get(key);
|
|
794
|
+
const lane = existing ?? { acceptanceTail: Promise.resolve() };
|
|
795
|
+
if (!existing)
|
|
796
|
+
admissionLanes.set(key, lane);
|
|
797
|
+
if (!lane.head) {
|
|
798
|
+
const admission = { runId, completion: Promise.withResolvers() };
|
|
799
|
+
admission.completion.promise.catch(() => {
|
|
800
|
+
return;
|
|
801
|
+
});
|
|
802
|
+
lane.head = admission;
|
|
803
|
+
return { lane, admission, shouldSchedule: true };
|
|
804
|
+
}
|
|
805
|
+
if (!lane.pending) {
|
|
806
|
+
const admission = { runId, completion: Promise.withResolvers() };
|
|
807
|
+
admission.completion.promise.catch(() => {
|
|
808
|
+
return;
|
|
809
|
+
});
|
|
810
|
+
lane.pending = admission;
|
|
811
|
+
return { lane, admission, shouldSchedule: true };
|
|
812
|
+
}
|
|
813
|
+
return { lane, admission: lane.pending, shouldSchedule: false };
|
|
814
|
+
};
|
|
815
|
+
const settleAdmission = (key, admission) => {
|
|
816
|
+
const lane = admissionLanes.get(key);
|
|
817
|
+
if (!lane)
|
|
818
|
+
return;
|
|
819
|
+
if (lane.head?.runId === admission.runId) {
|
|
820
|
+
lane.head = lane.pending;
|
|
821
|
+
lane.pending = undefined;
|
|
822
|
+
} else if (lane.pending?.runId === admission.runId) {
|
|
823
|
+
lane.pending = undefined;
|
|
824
|
+
}
|
|
825
|
+
if (!lane.head && !lane.pending)
|
|
826
|
+
admissionLanes.delete(key);
|
|
827
|
+
};
|
|
828
|
+
const waitForAdmissionAcceptances = async (lane) => {
|
|
829
|
+
while (true) {
|
|
830
|
+
const tail = lane.acceptanceTail;
|
|
831
|
+
await tail.catch(() => {
|
|
832
|
+
return;
|
|
833
|
+
});
|
|
834
|
+
if (tail === lane.acceptanceTail)
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
};
|
|
838
|
+
const checkpointEveryEvents = config.loop?.checkpointEveryEvents ?? 20;
|
|
839
|
+
const maxSteps = config.loop?.maxSteps ?? 50;
|
|
840
|
+
const idleTimeoutMs = config.loop?.idleTimeoutMs;
|
|
841
|
+
if (!Number.isSafeInteger(checkpointEveryEvents) || checkpointEveryEvents < 1) {
|
|
842
|
+
throw new TypeError("checkpointEveryEvents must be a positive safe integer");
|
|
843
|
+
}
|
|
844
|
+
if (!Number.isSafeInteger(maxSteps) || maxSteps < 1) {
|
|
845
|
+
throw new TypeError("maxSteps must be a positive safe integer");
|
|
846
|
+
}
|
|
847
|
+
if (idleTimeoutMs !== undefined && (!Number.isSafeInteger(idleTimeoutMs) || idleTimeoutMs < 1)) {
|
|
848
|
+
throw new TypeError("idleTimeoutMs must be a positive safe integer");
|
|
849
|
+
}
|
|
850
|
+
const policyNames = new Set(["max-steps"]);
|
|
851
|
+
for (const policy of config.loop?.stopPolicies ?? []) {
|
|
852
|
+
if (!policy.name || policyNames.has(policy.name)) {
|
|
853
|
+
throw new TypeError("Agent stop policy names must be non-empty and unique");
|
|
854
|
+
}
|
|
855
|
+
policyNames.add(policy.name);
|
|
856
|
+
}
|
|
857
|
+
const publish = async (event) => {
|
|
858
|
+
try {
|
|
859
|
+
await config.publish?.(event);
|
|
860
|
+
} catch {}
|
|
861
|
+
};
|
|
862
|
+
const executeRun = async (input) => {
|
|
863
|
+
const queuedSnapshot = await config.store.loadSnapshot(input.acceptedRun.conversationId);
|
|
864
|
+
const queuedRun = findRun(queuedSnapshot.runs, input.acceptedRun.id);
|
|
865
|
+
const acquired = appliedSnapshot(await config.store.acquireRun({
|
|
866
|
+
conversationId: queuedRun.conversationId,
|
|
867
|
+
runId: queuedRun.id,
|
|
868
|
+
expectedRevision: queuedRun.revision,
|
|
869
|
+
ownerId: runtimeEpoch
|
|
870
|
+
}), "run acquisition");
|
|
871
|
+
let run = findRun(acquired.runs, input.acceptedRun.id);
|
|
872
|
+
await publish({
|
|
873
|
+
type: "run-state",
|
|
874
|
+
eventId: generateId(),
|
|
875
|
+
conversationId: run.conversationId,
|
|
876
|
+
runId: run.id,
|
|
877
|
+
snapshotVersion: acquired.version,
|
|
878
|
+
state: run.state,
|
|
879
|
+
emittedAt: now().toISOString()
|
|
880
|
+
});
|
|
881
|
+
const trace = config.observe?.rootTrace();
|
|
882
|
+
const runStartedAt = performance.now();
|
|
883
|
+
config.observe?.emit({
|
|
884
|
+
schemaVersion: 1,
|
|
885
|
+
eventId: generateId(),
|
|
886
|
+
type: "run-started",
|
|
887
|
+
conversationId: run.conversationId,
|
|
888
|
+
runId: run.id,
|
|
889
|
+
traceId: trace?.traceId ?? generateId(),
|
|
890
|
+
spanId: trace?.spanId ?? generateId(),
|
|
891
|
+
...trace?.parentSpanId && { parentSpanId: trace.parentSpanId },
|
|
892
|
+
state: run.state,
|
|
893
|
+
queueWaitMs: Math.max(0, now().getTime() - new Date(run.createdAt).getTime()),
|
|
894
|
+
emittedAt: now().toISOString()
|
|
895
|
+
});
|
|
896
|
+
let assistant = AgentMessageSchema.parse({
|
|
897
|
+
schemaVersion: 1,
|
|
898
|
+
id: run.assistantMessageId,
|
|
899
|
+
conversationId: run.conversationId,
|
|
900
|
+
runId: run.id,
|
|
901
|
+
role: "assistant",
|
|
902
|
+
status: "streaming",
|
|
903
|
+
parts: [],
|
|
904
|
+
createdAt: now().toISOString(),
|
|
905
|
+
updatedAt: now().toISOString()
|
|
906
|
+
});
|
|
907
|
+
let snapshot = appliedSnapshot(await config.store.checkpointRunAssistant({
|
|
908
|
+
conversationId: run.conversationId,
|
|
909
|
+
runId: run.id,
|
|
910
|
+
expectedRevision: run.revision,
|
|
911
|
+
ownerId: runtimeEpoch,
|
|
912
|
+
assistant
|
|
913
|
+
}), "assistant draft");
|
|
914
|
+
run = findRun(snapshot.runs, run.id);
|
|
915
|
+
const parts = [];
|
|
916
|
+
let eventCount = 0;
|
|
917
|
+
let sequence = 0;
|
|
918
|
+
let terminalReason = "success";
|
|
919
|
+
let usage;
|
|
920
|
+
let step = 0;
|
|
921
|
+
let selectedModel;
|
|
922
|
+
let internalCause;
|
|
923
|
+
let reasoningPartIndex;
|
|
924
|
+
let firstOutputAt;
|
|
925
|
+
let terminalPolicyName;
|
|
926
|
+
const idleDeadline = createIdleDeadline(input.signal, idleTimeoutMs);
|
|
927
|
+
const executionSignal = idleDeadline.signal;
|
|
928
|
+
const updateReasoning = (text, metadata) => {
|
|
929
|
+
const provider = providerEnvelope(metadata);
|
|
930
|
+
if (reasoningPartIndex === undefined) {
|
|
931
|
+
reasoningPartIndex = parts.length;
|
|
932
|
+
parts.push(AgentMessagePartSchema.parse({
|
|
933
|
+
type: "reasoning",
|
|
934
|
+
text,
|
|
935
|
+
...provider && { provider }
|
|
936
|
+
}));
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
const current = parts[reasoningPartIndex];
|
|
940
|
+
if (current?.type !== "reasoning") {
|
|
941
|
+
throw new AgentRuntimeConflictError("reasoning accumulator");
|
|
942
|
+
}
|
|
943
|
+
parts.splice(reasoningPartIndex, 1, AgentMessagePartSchema.parse({
|
|
944
|
+
...current,
|
|
945
|
+
text: current.text + text,
|
|
946
|
+
...provider && { provider }
|
|
947
|
+
}));
|
|
948
|
+
};
|
|
949
|
+
const checkpoint = async () => {
|
|
950
|
+
assistant = AgentMessageSchema.parse({
|
|
951
|
+
...assistant,
|
|
952
|
+
parts,
|
|
953
|
+
updatedAt: now().toISOString()
|
|
954
|
+
});
|
|
955
|
+
snapshot = appliedSnapshot(await config.store.checkpointRunAssistant({
|
|
956
|
+
conversationId: run.conversationId,
|
|
957
|
+
runId: run.id,
|
|
958
|
+
expectedRevision: run.revision,
|
|
959
|
+
ownerId: runtimeEpoch,
|
|
960
|
+
assistant
|
|
961
|
+
}), "assistant checkpoint");
|
|
962
|
+
run = findRun(snapshot.runs, run.id);
|
|
963
|
+
await publish({
|
|
964
|
+
type: "assistant-checkpoint",
|
|
965
|
+
eventId: generateId(),
|
|
966
|
+
conversationId: run.conversationId,
|
|
967
|
+
runId: run.id,
|
|
968
|
+
snapshotVersion: snapshot.version,
|
|
969
|
+
message: assistant,
|
|
970
|
+
emittedAt: now().toISOString()
|
|
971
|
+
});
|
|
972
|
+
};
|
|
973
|
+
try {
|
|
974
|
+
if (config.history?.compact) {
|
|
975
|
+
const compacted = await config.history.compact({
|
|
976
|
+
conversationId: run.conversationId,
|
|
977
|
+
store: config.store,
|
|
978
|
+
signal: executionSignal
|
|
979
|
+
});
|
|
980
|
+
snapshot = compacted.snapshot;
|
|
981
|
+
run = findRun(snapshot.runs, run.id);
|
|
982
|
+
}
|
|
983
|
+
const assertCurrent = async () => {
|
|
984
|
+
if (executionSignal.aborted)
|
|
985
|
+
return "run_interrupted";
|
|
986
|
+
const current = await config.store.loadSnapshot(run.conversationId);
|
|
987
|
+
const currentRun = current.runs.find((candidate) => candidate.id === run.id);
|
|
988
|
+
if (!currentRun || currentRun.ownerId !== runtimeEpoch)
|
|
989
|
+
return "stale_run";
|
|
990
|
+
if (currentRun.state === "interrupt_requested")
|
|
991
|
+
return "run_interrupted";
|
|
992
|
+
if (currentRun.state !== "running")
|
|
993
|
+
return "stale_run";
|
|
994
|
+
return;
|
|
995
|
+
};
|
|
996
|
+
const toolFenceLifecycle = createAgentToolFenceLifecycle({
|
|
997
|
+
runId: run.id,
|
|
998
|
+
assertCurrent
|
|
999
|
+
});
|
|
1000
|
+
const runtimeContext = {
|
|
1001
|
+
context: input.context,
|
|
1002
|
+
run,
|
|
1003
|
+
signal: executionSignal,
|
|
1004
|
+
toolFenceLifecycle
|
|
1005
|
+
};
|
|
1006
|
+
selectedModel = await config.models.resolve({
|
|
1007
|
+
context: input.context,
|
|
1008
|
+
conversationId: run.conversationId
|
|
1009
|
+
});
|
|
1010
|
+
const [prompt, tools] = await Promise.all([
|
|
1011
|
+
config.prompt({
|
|
1012
|
+
context: input.context,
|
|
1013
|
+
signal: executionSignal,
|
|
1014
|
+
model: selectedModel,
|
|
1015
|
+
snapshot
|
|
1016
|
+
}),
|
|
1017
|
+
config.tools(runtimeContext)
|
|
1018
|
+
]);
|
|
1019
|
+
if (prompt.contextDecision === "oversized") {
|
|
1020
|
+
throw new Error("Agent context exceeds the configured model budget");
|
|
1021
|
+
}
|
|
1022
|
+
if (prompt.contextDecision === "requires-compaction") {
|
|
1023
|
+
throw new Error("Agent context still exceeds the model budget after compaction");
|
|
1024
|
+
}
|
|
1025
|
+
const history = await (config.history?.project ? config.history.project(snapshot.messages) : projectAgentHistory(snapshot.messages, {
|
|
1026
|
+
...config.history?.resolveFile && { resolveFile: config.history.resolveFile },
|
|
1027
|
+
...config.history?.unresolvedFile && {
|
|
1028
|
+
unresolvedFile: config.history.unresolvedFile
|
|
1029
|
+
}
|
|
1030
|
+
}));
|
|
1031
|
+
const maxStepCondition = stepCountIs(maxSteps);
|
|
1032
|
+
const stopConditions = [
|
|
1033
|
+
async (options) => {
|
|
1034
|
+
const stopped = await maxStepCondition(options);
|
|
1035
|
+
if (stopped && terminalPolicyName === undefined)
|
|
1036
|
+
terminalPolicyName = "max-steps";
|
|
1037
|
+
return stopped;
|
|
1038
|
+
}
|
|
1039
|
+
];
|
|
1040
|
+
for (const policy of config.loop?.stopPolicies ?? []) {
|
|
1041
|
+
stopConditions.push(async (options) => {
|
|
1042
|
+
const stopped = await policy.when(options);
|
|
1043
|
+
if (stopped && terminalPolicyName === undefined)
|
|
1044
|
+
terminalPolicyName = policy.name;
|
|
1045
|
+
return stopped;
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
const result = streamText({
|
|
1049
|
+
model: selectedModel.model,
|
|
1050
|
+
tools,
|
|
1051
|
+
instructions: prompt.instructions,
|
|
1052
|
+
messages: history,
|
|
1053
|
+
abortSignal: executionSignal,
|
|
1054
|
+
maxRetries: 0,
|
|
1055
|
+
stopWhen: stopConditions,
|
|
1056
|
+
...config.loop?.prepareStep && {
|
|
1057
|
+
prepareStep: (options) => config.loop?.prepareStep?.({ ...options, ...runtimeContext })
|
|
1058
|
+
}
|
|
1059
|
+
});
|
|
1060
|
+
for await (const part of result.stream) {
|
|
1061
|
+
idleDeadline.touch();
|
|
1062
|
+
eventCount += 1;
|
|
1063
|
+
sequence += 1;
|
|
1064
|
+
if (firstOutputAt === undefined && ["text-delta", "reasoning-delta", "tool-call", "file", "source"].includes(part.type)) {
|
|
1065
|
+
firstOutputAt = performance.now();
|
|
1066
|
+
}
|
|
1067
|
+
if (part.type === "text-delta") {
|
|
1068
|
+
appendText(parts, part.text);
|
|
1069
|
+
await publish({
|
|
1070
|
+
type: "assistant-delta",
|
|
1071
|
+
conversationId: run.conversationId,
|
|
1072
|
+
runId: run.id,
|
|
1073
|
+
runtimeEpoch,
|
|
1074
|
+
sequence,
|
|
1075
|
+
textDelta: part.text,
|
|
1076
|
+
emittedAt: now().toISOString()
|
|
1077
|
+
});
|
|
1078
|
+
} else if (part.type === "reasoning-start") {
|
|
1079
|
+
reasoningPartIndex = undefined;
|
|
1080
|
+
updateReasoning("", part.providerMetadata);
|
|
1081
|
+
} else if (part.type === "reasoning-delta") {
|
|
1082
|
+
updateReasoning(part.text, part.providerMetadata);
|
|
1083
|
+
} else if (part.type === "reasoning-end") {
|
|
1084
|
+
updateReasoning("", part.providerMetadata);
|
|
1085
|
+
reasoningPartIndex = undefined;
|
|
1086
|
+
} else if (part.type === "tool-call") {
|
|
1087
|
+
const provider = providerEnvelope(part.providerMetadata);
|
|
1088
|
+
parts.push(AgentMessagePartSchema.parse({
|
|
1089
|
+
type: "tool-call",
|
|
1090
|
+
callId: part.toolCallId,
|
|
1091
|
+
toolName: part.toolName,
|
|
1092
|
+
input: jsonValue(part.input),
|
|
1093
|
+
...provider && { provider }
|
|
1094
|
+
}));
|
|
1095
|
+
await publish({
|
|
1096
|
+
type: "tool-status",
|
|
1097
|
+
conversationId: run.conversationId,
|
|
1098
|
+
runId: run.id,
|
|
1099
|
+
runtimeEpoch,
|
|
1100
|
+
sequence,
|
|
1101
|
+
callId: part.toolCallId,
|
|
1102
|
+
toolName: part.toolName,
|
|
1103
|
+
status: "started",
|
|
1104
|
+
input: jsonValue(part.input),
|
|
1105
|
+
emittedAt: now().toISOString()
|
|
1106
|
+
});
|
|
1107
|
+
} else if (part.type === "tool-result") {
|
|
1108
|
+
parts.push(AgentMessagePartSchema.parse({
|
|
1109
|
+
type: "tool-result",
|
|
1110
|
+
callId: part.toolCallId,
|
|
1111
|
+
toolName: part.toolName,
|
|
1112
|
+
outcome: "success",
|
|
1113
|
+
output: jsonValue(part.output)
|
|
1114
|
+
}));
|
|
1115
|
+
await publish({
|
|
1116
|
+
type: "tool-status",
|
|
1117
|
+
conversationId: run.conversationId,
|
|
1118
|
+
runId: run.id,
|
|
1119
|
+
runtimeEpoch,
|
|
1120
|
+
sequence,
|
|
1121
|
+
callId: part.toolCallId,
|
|
1122
|
+
toolName: part.toolName,
|
|
1123
|
+
status: "completed",
|
|
1124
|
+
output: jsonValue(part.output),
|
|
1125
|
+
emittedAt: now().toISOString()
|
|
1126
|
+
});
|
|
1127
|
+
} else if (part.type === "tool-error") {
|
|
1128
|
+
internalCause = part.error;
|
|
1129
|
+
if (isToolExecutionControlError(part.error)) {
|
|
1130
|
+
await publish({
|
|
1131
|
+
type: "tool-status",
|
|
1132
|
+
conversationId: run.conversationId,
|
|
1133
|
+
runId: run.id,
|
|
1134
|
+
runtimeEpoch,
|
|
1135
|
+
sequence,
|
|
1136
|
+
callId: part.toolCallId,
|
|
1137
|
+
toolName: part.toolName,
|
|
1138
|
+
status: "interrupted",
|
|
1139
|
+
emittedAt: now().toISOString()
|
|
1140
|
+
});
|
|
1141
|
+
throw part.error;
|
|
1142
|
+
}
|
|
1143
|
+
parts.push(AgentMessagePartSchema.parse({
|
|
1144
|
+
type: "tool-result",
|
|
1145
|
+
callId: part.toolCallId,
|
|
1146
|
+
toolName: part.toolName,
|
|
1147
|
+
outcome: "error",
|
|
1148
|
+
output: { message: "Tool execution failed" }
|
|
1149
|
+
}));
|
|
1150
|
+
await publish({
|
|
1151
|
+
type: "tool-status",
|
|
1152
|
+
conversationId: run.conversationId,
|
|
1153
|
+
runId: run.id,
|
|
1154
|
+
runtimeEpoch,
|
|
1155
|
+
sequence,
|
|
1156
|
+
callId: part.toolCallId,
|
|
1157
|
+
toolName: part.toolName,
|
|
1158
|
+
status: "failed",
|
|
1159
|
+
output: { message: "Tool execution failed" },
|
|
1160
|
+
emittedAt: now().toISOString()
|
|
1161
|
+
});
|
|
1162
|
+
} else if (part.type === "tool-output-denied") {
|
|
1163
|
+
parts.push(AgentMessagePartSchema.parse({
|
|
1164
|
+
type: "tool-result",
|
|
1165
|
+
callId: part.toolCallId,
|
|
1166
|
+
toolName: part.toolName,
|
|
1167
|
+
outcome: "error",
|
|
1168
|
+
output: { message: "Tool output denied" }
|
|
1169
|
+
}));
|
|
1170
|
+
await publish({
|
|
1171
|
+
type: "tool-status",
|
|
1172
|
+
conversationId: run.conversationId,
|
|
1173
|
+
runId: run.id,
|
|
1174
|
+
runtimeEpoch,
|
|
1175
|
+
sequence,
|
|
1176
|
+
callId: part.toolCallId,
|
|
1177
|
+
toolName: part.toolName,
|
|
1178
|
+
status: "failed",
|
|
1179
|
+
output: { message: "Tool output denied" },
|
|
1180
|
+
emittedAt: now().toISOString()
|
|
1181
|
+
});
|
|
1182
|
+
} else if (part.type === "source") {
|
|
1183
|
+
parts.push(AgentMessagePartSchema.parse({
|
|
1184
|
+
type: "source",
|
|
1185
|
+
sourceId: part.id,
|
|
1186
|
+
...part.sourceType === "url" && { url: part.url },
|
|
1187
|
+
...part.title && { title: part.title }
|
|
1188
|
+
}));
|
|
1189
|
+
} else if (part.type === "file" && config.persistGeneratedFile) {
|
|
1190
|
+
const persisted = await config.persistGeneratedFile(part.file);
|
|
1191
|
+
parts.push(AgentMessagePartSchema.parse({
|
|
1192
|
+
type: "file",
|
|
1193
|
+
mediaType: part.file.mediaType,
|
|
1194
|
+
reference: persisted.reference,
|
|
1195
|
+
...persisted.filename && { filename: persisted.filename }
|
|
1196
|
+
}));
|
|
1197
|
+
} else if (part.type === "file") {
|
|
1198
|
+
throw new Error("persistGeneratedFile is required for generated file output");
|
|
1199
|
+
} else if (part.type === "reasoning-file" && config.persistGeneratedFile) {
|
|
1200
|
+
const persisted = await config.persistGeneratedFile(part.file);
|
|
1201
|
+
parts.push(AgentMessagePartSchema.parse({
|
|
1202
|
+
type: "file",
|
|
1203
|
+
mediaType: part.file.mediaType,
|
|
1204
|
+
reference: persisted.reference,
|
|
1205
|
+
...persisted.filename && { filename: persisted.filename }
|
|
1206
|
+
}));
|
|
1207
|
+
} else if (part.type === "reasoning-file") {
|
|
1208
|
+
throw new Error("persistGeneratedFile is required for generated reasoning files");
|
|
1209
|
+
} else if (part.type === "tool-approval-request" || part.type === "tool-approval-response") {
|
|
1210
|
+
throw new Error("Durable tool approval/resume is outside this runtime version");
|
|
1211
|
+
} else if (part.type === "custom") {
|
|
1212
|
+
const provider = providerEnvelope(part.providerMetadata);
|
|
1213
|
+
parts.push(AgentMessagePartSchema.parse({
|
|
1214
|
+
type: "provider",
|
|
1215
|
+
envelope: {
|
|
1216
|
+
schemaVersion: 1,
|
|
1217
|
+
provider: "ai-sdk-custom",
|
|
1218
|
+
data: { kind: part.kind, ...provider && { provider: provider.data } }
|
|
1219
|
+
}
|
|
1220
|
+
}));
|
|
1221
|
+
} else if (part.type === "raw") {
|
|
1222
|
+
parts.push(AgentMessagePartSchema.parse({
|
|
1223
|
+
type: "provider",
|
|
1224
|
+
envelope: {
|
|
1225
|
+
schemaVersion: 1,
|
|
1226
|
+
provider: "ai-sdk-raw",
|
|
1227
|
+
data: { value: jsonValue(part.rawValue) }
|
|
1228
|
+
}
|
|
1229
|
+
}));
|
|
1230
|
+
} else if (part.type === "abort") {
|
|
1231
|
+
terminalReason = "interrupted";
|
|
1232
|
+
} else if (part.type === "error") {
|
|
1233
|
+
terminalReason = "provider_failure";
|
|
1234
|
+
internalCause = part.error;
|
|
1235
|
+
} else if (part.type === "finish-step") {
|
|
1236
|
+
const stepTrace = trace ? config.observe?.rootTrace(trace) : undefined;
|
|
1237
|
+
const stepUsage = selectedModel.normalizeUsage?.({
|
|
1238
|
+
usage: part.usage,
|
|
1239
|
+
providerMetadata: part.providerMetadata
|
|
1240
|
+
}) ?? normalizeSdkUsage(part.usage);
|
|
1241
|
+
usage = stepUsage;
|
|
1242
|
+
config.observe?.emit({
|
|
1243
|
+
schemaVersion: 1,
|
|
1244
|
+
eventId: generateId(),
|
|
1245
|
+
type: "step-finished",
|
|
1246
|
+
conversationId: run.conversationId,
|
|
1247
|
+
runId: run.id,
|
|
1248
|
+
traceId: stepTrace?.traceId ?? trace?.traceId ?? generateId(),
|
|
1249
|
+
spanId: stepTrace?.spanId ?? generateId(),
|
|
1250
|
+
...stepTrace?.parentSpanId && { parentSpanId: stepTrace.parentSpanId },
|
|
1251
|
+
state: run.state,
|
|
1252
|
+
modelId: selectedModel.descriptor.modelId,
|
|
1253
|
+
step,
|
|
1254
|
+
usage: stepUsage,
|
|
1255
|
+
emittedAt: now().toISOString()
|
|
1256
|
+
});
|
|
1257
|
+
step += 1;
|
|
1258
|
+
} else if (part.type === "finish" && part.finishReason !== "stop") {
|
|
1259
|
+
terminalReason = "policy_stop";
|
|
1260
|
+
}
|
|
1261
|
+
if (part.type === "finish") {
|
|
1262
|
+
const aggregate = normalizeSdkUsage(part.totalUsage);
|
|
1263
|
+
usage = { ...aggregate, ...usage?.cost && { cost: usage.cost } };
|
|
1264
|
+
}
|
|
1265
|
+
if (eventCount % checkpointEveryEvents === 0)
|
|
1266
|
+
await checkpoint();
|
|
1267
|
+
}
|
|
1268
|
+
if (terminalPolicyName !== undefined)
|
|
1269
|
+
terminalReason = "policy_stop";
|
|
1270
|
+
if (executionSignal.aborted)
|
|
1271
|
+
terminalReason = abortTerminalReason(executionSignal);
|
|
1272
|
+
} catch (error) {
|
|
1273
|
+
internalCause = error;
|
|
1274
|
+
const latest = await config.store.loadSnapshot(run.conversationId);
|
|
1275
|
+
const latestRun = latest.runs.find((candidate) => candidate.id === run.id);
|
|
1276
|
+
const durableInterrupt = latestRun?.ownerId === runtimeEpoch && latestRun.state === "interrupt_requested";
|
|
1277
|
+
if (durableInterrupt && latestRun) {
|
|
1278
|
+
snapshot = latest;
|
|
1279
|
+
run = latestRun;
|
|
1280
|
+
}
|
|
1281
|
+
if (isToolExecutionControlError(error) || executionSignal.aborted || durableInterrupt) {
|
|
1282
|
+
terminalReason = executionSignal.aborted ? abortTerminalReason(executionSignal) : "interrupted";
|
|
1283
|
+
parts.push(AgentMessagePartSchema.parse({
|
|
1284
|
+
type: "control",
|
|
1285
|
+
reason: isToolExecutionControlError(error) && error.reason === "stale_run" ? "stale-run" : "run-interrupted"
|
|
1286
|
+
}));
|
|
1287
|
+
} else {
|
|
1288
|
+
terminalReason = "provider_failure";
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
idleDeadline.dispose();
|
|
1292
|
+
assistant = AgentMessageSchema.parse({
|
|
1293
|
+
...assistant,
|
|
1294
|
+
status: assistantStatus(terminalReason),
|
|
1295
|
+
parts,
|
|
1296
|
+
updatedAt: now().toISOString()
|
|
1297
|
+
});
|
|
1298
|
+
snapshot = appliedSnapshot(await config.store.commitRunTerminal({
|
|
1299
|
+
conversationId: run.conversationId,
|
|
1300
|
+
runId: run.id,
|
|
1301
|
+
expectedRevision: run.revision,
|
|
1302
|
+
ownerId: runtimeEpoch,
|
|
1303
|
+
assistant,
|
|
1304
|
+
reason: terminalReason,
|
|
1305
|
+
...terminalPolicyName && { policyName: terminalPolicyName }
|
|
1306
|
+
}), "terminal commit");
|
|
1307
|
+
run = findRun(snapshot.runs, run.id);
|
|
1308
|
+
config.observe?.emit({
|
|
1309
|
+
schemaVersion: 1,
|
|
1310
|
+
eventId: generateId(),
|
|
1311
|
+
type: "run-terminal",
|
|
1312
|
+
conversationId: run.conversationId,
|
|
1313
|
+
runId: run.id,
|
|
1314
|
+
traceId: trace?.traceId ?? generateId(),
|
|
1315
|
+
spanId: trace?.spanId ?? generateId(),
|
|
1316
|
+
...trace?.parentSpanId && { parentSpanId: trace.parentSpanId },
|
|
1317
|
+
state: run.state,
|
|
1318
|
+
terminalReason,
|
|
1319
|
+
...selectedModel && { modelId: selectedModel.descriptor.modelId },
|
|
1320
|
+
durationMs: performance.now() - runStartedAt,
|
|
1321
|
+
...usage && { usage },
|
|
1322
|
+
...internalCause !== undefined && { internalCause },
|
|
1323
|
+
...firstOutputAt !== undefined && { ttftMs: firstOutputAt - runStartedAt },
|
|
1324
|
+
emittedAt: now().toISOString()
|
|
1325
|
+
});
|
|
1326
|
+
await publish({
|
|
1327
|
+
type: "terminal",
|
|
1328
|
+
eventId: generateId(),
|
|
1329
|
+
conversationId: run.conversationId,
|
|
1330
|
+
runId: run.id,
|
|
1331
|
+
snapshotVersion: snapshot.version,
|
|
1332
|
+
reason: terminalReason,
|
|
1333
|
+
...terminalPolicyName && { policyName: terminalPolicyName },
|
|
1334
|
+
message: assistant,
|
|
1335
|
+
emittedAt: now().toISOString()
|
|
1336
|
+
});
|
|
1337
|
+
return {
|
|
1338
|
+
run,
|
|
1339
|
+
message: assistant,
|
|
1340
|
+
reason: terminalReason,
|
|
1341
|
+
snapshotVersion: snapshot.version,
|
|
1342
|
+
...terminalPolicyName && { policyName: terminalPolicyName }
|
|
1343
|
+
};
|
|
1344
|
+
};
|
|
1345
|
+
return {
|
|
1346
|
+
submit(rawInput) {
|
|
1347
|
+
const metadata = rawInput.metadata === undefined ? undefined : config.protocol.parseInputMetadata(rawInput.metadata);
|
|
1348
|
+
const input = {
|
|
1349
|
+
conversationId: rawInput.conversationId,
|
|
1350
|
+
idempotencyKey: rawInput.idempotencyKey,
|
|
1351
|
+
context: rawInput.context,
|
|
1352
|
+
parts: rawInput.parts.map((part) => config.protocol.parsePart(part)),
|
|
1353
|
+
...metadata !== undefined && { metadata }
|
|
1354
|
+
};
|
|
1355
|
+
const context = config.protocol.parseContext(input.context);
|
|
1356
|
+
const ticketKey = `${input.conversationId}\x00${input.idempotencyKey}`;
|
|
1357
|
+
const existingTicket = tickets.get(ticketKey);
|
|
1358
|
+
if (existingTicket)
|
|
1359
|
+
return existingTicket;
|
|
1360
|
+
const key = config.runs?.key?.(input) ?? input.conversationId;
|
|
1361
|
+
const policy = typeof config.runs?.inputPolicy === "function" ? config.runs.inputPolicy(input) : config.runs?.inputPolicy ?? "queue";
|
|
1362
|
+
const nowIso = now().toISOString();
|
|
1363
|
+
const inputMessageId = generateId();
|
|
1364
|
+
const runId = generateId();
|
|
1365
|
+
const assistantMessageId = generateId();
|
|
1366
|
+
const userMessage2 = AgentMessageSchema.parse({
|
|
1367
|
+
schemaVersion: 1,
|
|
1368
|
+
id: inputMessageId,
|
|
1369
|
+
conversationId: input.conversationId,
|
|
1370
|
+
role: "user",
|
|
1371
|
+
status: "committed",
|
|
1372
|
+
parts: input.parts,
|
|
1373
|
+
...input.metadata && { metadata: input.metadata },
|
|
1374
|
+
createdAt: nowIso,
|
|
1375
|
+
updatedAt: nowIso
|
|
1376
|
+
});
|
|
1377
|
+
const queuedRun = AgentRunSchema.parse({
|
|
1378
|
+
schemaVersion: 1,
|
|
1379
|
+
id: runId,
|
|
1380
|
+
conversationId: input.conversationId,
|
|
1381
|
+
inputMessageIds: [inputMessageId],
|
|
1382
|
+
assistantMessageId,
|
|
1383
|
+
state: "queued",
|
|
1384
|
+
revision: 0,
|
|
1385
|
+
createdAt: nowIso,
|
|
1386
|
+
updatedAt: nowIso
|
|
1387
|
+
});
|
|
1388
|
+
const outerAccepted = Promise.withResolvers();
|
|
1389
|
+
const outerResult = Promise.withResolvers();
|
|
1390
|
+
const reservation = config.runs?.coalescePending ? reserveAdmission(key, runId) : undefined;
|
|
1391
|
+
const previousAcceptance = reservation?.lane.acceptanceTail ?? Promise.resolve();
|
|
1392
|
+
const acceptanceDone = Promise.withResolvers();
|
|
1393
|
+
if (reservation) {
|
|
1394
|
+
reservation.lane.acceptanceTail = previousAcceptance.catch(() => {
|
|
1395
|
+
return;
|
|
1396
|
+
}).then(() => acceptanceDone.promise);
|
|
1397
|
+
}
|
|
1398
|
+
const publicTicket = { accepted: outerAccepted.promise, result: outerResult.promise };
|
|
1399
|
+
tickets.set(ticketKey, publicTicket);
|
|
1400
|
+
outerResult.promise.then(() => tickets.delete(ticketKey), () => tickets.delete(ticketKey));
|
|
1401
|
+
(async () => {
|
|
1402
|
+
try {
|
|
1403
|
+
await previousAcceptance.catch(() => {
|
|
1404
|
+
return;
|
|
1405
|
+
});
|
|
1406
|
+
const acceptance = await config.store.acceptInputAndAssignRun({
|
|
1407
|
+
idempotencyKey: input.idempotencyKey,
|
|
1408
|
+
input: userMessage2,
|
|
1409
|
+
run: queuedRun,
|
|
1410
|
+
...reservation && !reservation.shouldSchedule && {
|
|
1411
|
+
coalesceIntoRunId: reservation.admission.runId
|
|
1412
|
+
}
|
|
1413
|
+
});
|
|
1414
|
+
const acceptedSnapshot = appliedSnapshot(acceptance, "input acceptance");
|
|
1415
|
+
const assignedRunId = acceptance.outcome === "duplicate" ? acceptance.runId : reservation?.admission.runId ?? runId;
|
|
1416
|
+
const acceptedRun = findRun(acceptedSnapshot.runs, assignedRunId);
|
|
1417
|
+
await publish({
|
|
1418
|
+
type: "run-state",
|
|
1419
|
+
eventId: generateId(),
|
|
1420
|
+
conversationId: acceptedRun.conversationId,
|
|
1421
|
+
runId: acceptedRun.id,
|
|
1422
|
+
snapshotVersion: acceptedSnapshot.version,
|
|
1423
|
+
state: acceptedRun.state,
|
|
1424
|
+
emittedAt: now().toISOString()
|
|
1425
|
+
});
|
|
1426
|
+
outerAccepted.resolve();
|
|
1427
|
+
if (acceptance.outcome === "duplicate") {
|
|
1428
|
+
if (!acceptedRun.terminalReason) {
|
|
1429
|
+
const error = new Error("Duplicate input is already owned by another runtime execution");
|
|
1430
|
+
outerResult.reject(error);
|
|
1431
|
+
if (reservation?.shouldSchedule) {
|
|
1432
|
+
reservation.admission.completion.reject(error);
|
|
1433
|
+
settleAdmission(key, reservation.admission);
|
|
1434
|
+
}
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
1437
|
+
const message = acceptedSnapshot.messages.find((candidate) => candidate.id === acceptedRun.assistantMessageId);
|
|
1438
|
+
if (!message) {
|
|
1439
|
+
const error = new Error("Duplicate terminal run has no assistant message");
|
|
1440
|
+
outerResult.reject(error);
|
|
1441
|
+
if (reservation?.shouldSchedule) {
|
|
1442
|
+
reservation.admission.completion.reject(error);
|
|
1443
|
+
settleAdmission(key, reservation.admission);
|
|
1444
|
+
}
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
outerResult.resolve({
|
|
1448
|
+
run: acceptedRun,
|
|
1449
|
+
message,
|
|
1450
|
+
reason: acceptedRun.terminalReason,
|
|
1451
|
+
snapshotVersion: acceptedSnapshot.version,
|
|
1452
|
+
...acceptedRun.terminalPolicyName && {
|
|
1453
|
+
policyName: acceptedRun.terminalPolicyName
|
|
1454
|
+
}
|
|
1455
|
+
});
|
|
1456
|
+
if (reservation?.shouldSchedule) {
|
|
1457
|
+
reservation.admission.completion.reject(new Error("Reserved run resolved to a duplicate durable input"));
|
|
1458
|
+
settleAdmission(key, reservation.admission);
|
|
1459
|
+
}
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1462
|
+
if (reservation && !reservation.shouldSchedule) {
|
|
1463
|
+
reservation.admission.completion.promise.then(outerResult.resolve, outerResult.reject);
|
|
1464
|
+
return;
|
|
1465
|
+
}
|
|
1466
|
+
const ticket = coordinator.submit({
|
|
1467
|
+
key,
|
|
1468
|
+
policy,
|
|
1469
|
+
create: async (signal) => {
|
|
1470
|
+
if (reservation)
|
|
1471
|
+
await waitForAdmissionAcceptances(reservation.lane);
|
|
1472
|
+
return {
|
|
1473
|
+
runId: acceptedRun.id,
|
|
1474
|
+
execute: () => executeRun({ acceptedRun, context, signal })
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
});
|
|
1478
|
+
if (reservation) {
|
|
1479
|
+
ticket.result.then(reservation.admission.completion.resolve, reservation.admission.completion.reject);
|
|
1480
|
+
reservation.admission.completion.promise.then((value) => {
|
|
1481
|
+
settleAdmission(key, reservation.admission);
|
|
1482
|
+
outerResult.resolve(value);
|
|
1483
|
+
}, (error) => {
|
|
1484
|
+
settleAdmission(key, reservation.admission);
|
|
1485
|
+
outerResult.reject(error);
|
|
1486
|
+
});
|
|
1487
|
+
} else {
|
|
1488
|
+
ticket.result.then(outerResult.resolve, outerResult.reject);
|
|
1489
|
+
}
|
|
1490
|
+
} catch (error) {
|
|
1491
|
+
outerAccepted.reject(error);
|
|
1492
|
+
outerResult.reject(error);
|
|
1493
|
+
if (reservation?.shouldSchedule) {
|
|
1494
|
+
reservation.admission.completion.reject(error);
|
|
1495
|
+
settleAdmission(key, reservation.admission);
|
|
1496
|
+
}
|
|
1497
|
+
} finally {
|
|
1498
|
+
acceptanceDone.resolve();
|
|
1499
|
+
}
|
|
1500
|
+
})();
|
|
1501
|
+
return publicTicket;
|
|
1502
|
+
},
|
|
1503
|
+
resume(rawInput) {
|
|
1504
|
+
const context = config.protocol.parseContext(rawInput.context);
|
|
1505
|
+
const accepted = Promise.withResolvers();
|
|
1506
|
+
const result = Promise.withResolvers();
|
|
1507
|
+
(async () => {
|
|
1508
|
+
try {
|
|
1509
|
+
const snapshot = await config.store.loadSnapshot(rawInput.conversationId);
|
|
1510
|
+
const recoveredRun = findRun(snapshot.runs, rawInput.runId);
|
|
1511
|
+
if (recoveredRun.state !== "queued") {
|
|
1512
|
+
throw new Error("Only a queued recovered agent run can be resumed");
|
|
1513
|
+
}
|
|
1514
|
+
accepted.resolve();
|
|
1515
|
+
const ticket = coordinator.submit({
|
|
1516
|
+
key: rawInput.conversationKey ?? rawInput.conversationId,
|
|
1517
|
+
policy: "queue",
|
|
1518
|
+
create: (signal) => ({
|
|
1519
|
+
runId: recoveredRun.id,
|
|
1520
|
+
execute: () => executeRun({ acceptedRun: recoveredRun, context, signal })
|
|
1521
|
+
})
|
|
1522
|
+
});
|
|
1523
|
+
ticket.result.then(result.resolve, result.reject);
|
|
1524
|
+
} catch (error) {
|
|
1525
|
+
accepted.reject(error);
|
|
1526
|
+
result.reject(error);
|
|
1527
|
+
}
|
|
1528
|
+
})();
|
|
1529
|
+
return { accepted: accepted.promise, result: result.promise };
|
|
1530
|
+
},
|
|
1531
|
+
async interrupt(input) {
|
|
1532
|
+
const snapshot = await config.store.loadSnapshot(input.conversationId);
|
|
1533
|
+
const run = findRun(snapshot.runs, input.runId);
|
|
1534
|
+
const requested = await config.store.requestRunInterrupt({
|
|
1535
|
+
conversationId: input.conversationId,
|
|
1536
|
+
runId: input.runId,
|
|
1537
|
+
expectedRevision: run.revision
|
|
1538
|
+
});
|
|
1539
|
+
if (requested.outcome === "applied") {
|
|
1540
|
+
const interruptedRun = findRun(requested.snapshot.runs, input.runId);
|
|
1541
|
+
await publish({
|
|
1542
|
+
type: "run-state",
|
|
1543
|
+
eventId: generateId(),
|
|
1544
|
+
conversationId: interruptedRun.conversationId,
|
|
1545
|
+
runId: interruptedRun.id,
|
|
1546
|
+
snapshotVersion: requested.snapshot.version,
|
|
1547
|
+
state: interruptedRun.state,
|
|
1548
|
+
emittedAt: now().toISOString()
|
|
1549
|
+
});
|
|
1550
|
+
coordinator.stop(input.conversationKey ?? input.conversationId, "user-interrupt");
|
|
1551
|
+
}
|
|
1552
|
+
return requested;
|
|
1553
|
+
},
|
|
1554
|
+
stop: (conversationKey, reason) => coordinator.stop(conversationKey, reason),
|
|
1555
|
+
close: (options) => coordinator.close(options)
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
// src/agent-runtime/store.ts
|
|
1559
|
+
import { z as z7 } from "zod";
|
|
1560
|
+
var AgentStoreConflictSchema = z7.object({
|
|
1561
|
+
outcome: z7.literal("conflict"),
|
|
1562
|
+
actualVersion: AgentRecordVersionSchema
|
|
1563
|
+
});
|
|
1564
|
+
var AgentStoreNotFoundSchema = z7.object({
|
|
1565
|
+
outcome: z7.literal("not_found")
|
|
1566
|
+
});
|
|
1567
|
+
var AgentStoreAppliedSchema = z7.object({
|
|
1568
|
+
outcome: z7.literal("applied"),
|
|
1569
|
+
snapshot: AgentSnapshotSchema
|
|
1570
|
+
});
|
|
1571
|
+
var AgentStoreDuplicateSchema = z7.object({
|
|
1572
|
+
outcome: z7.literal("duplicate"),
|
|
1573
|
+
runId: AgentRecordIdSchema,
|
|
1574
|
+
snapshot: AgentSnapshotSchema
|
|
1575
|
+
});
|
|
1576
|
+
var AgentStoreMutationResultSchema = z7.discriminatedUnion("outcome", [
|
|
1577
|
+
AgentStoreAppliedSchema,
|
|
1578
|
+
AgentStoreDuplicateSchema,
|
|
1579
|
+
AgentStoreConflictSchema,
|
|
1580
|
+
AgentStoreNotFoundSchema
|
|
1581
|
+
]);
|
|
1582
|
+
var AcceptInputAndAssignRunSchema = z7.object({
|
|
1583
|
+
idempotencyKey: z7.string().min(1),
|
|
1584
|
+
expectedVersion: AgentRecordVersionSchema.optional(),
|
|
1585
|
+
input: AgentMessageSchema,
|
|
1586
|
+
run: AgentRunSchema,
|
|
1587
|
+
coalesceIntoRunId: AgentRecordIdSchema.optional()
|
|
1588
|
+
});
|
|
1589
|
+
var AcquireAgentRunSchema = z7.object({
|
|
1590
|
+
conversationId: AgentRecordIdSchema,
|
|
1591
|
+
runId: AgentRecordIdSchema,
|
|
1592
|
+
expectedRevision: AgentRecordVersionSchema,
|
|
1593
|
+
ownerId: z7.string().min(1)
|
|
1594
|
+
});
|
|
1595
|
+
var CheckpointRunAssistantSchema = z7.object({
|
|
1596
|
+
conversationId: AgentRecordIdSchema,
|
|
1597
|
+
runId: AgentRecordIdSchema,
|
|
1598
|
+
expectedRevision: AgentRecordVersionSchema,
|
|
1599
|
+
ownerId: z7.string().min(1),
|
|
1600
|
+
assistant: AgentMessageSchema
|
|
1601
|
+
});
|
|
1602
|
+
var CommitRunTerminalSchema = z7.object({
|
|
1603
|
+
conversationId: AgentRecordIdSchema,
|
|
1604
|
+
runId: AgentRecordIdSchema,
|
|
1605
|
+
expectedRevision: AgentRecordVersionSchema,
|
|
1606
|
+
ownerId: z7.string().min(1),
|
|
1607
|
+
assistant: AgentMessageSchema,
|
|
1608
|
+
reason: AgentTerminalReasonSchema,
|
|
1609
|
+
policyName: z7.string().min(1).optional()
|
|
1610
|
+
});
|
|
1611
|
+
var RequestRunInterruptSchema = z7.object({
|
|
1612
|
+
conversationId: AgentRecordIdSchema,
|
|
1613
|
+
runId: AgentRecordIdSchema,
|
|
1614
|
+
expectedRevision: AgentRecordVersionSchema
|
|
1615
|
+
});
|
|
1616
|
+
var RecoverAgentRunSchema = z7.object({
|
|
1617
|
+
conversationId: AgentRecordIdSchema,
|
|
1618
|
+
runId: AgentRecordIdSchema,
|
|
1619
|
+
expectedRevision: AgentRecordVersionSchema,
|
|
1620
|
+
action: z7.enum(["requeue", "abandon"]),
|
|
1621
|
+
replaySafe: z7.boolean().optional()
|
|
1622
|
+
});
|
|
1623
|
+
var ReplaceCompactedRangeSchema = z7.object({
|
|
1624
|
+
conversationId: AgentRecordIdSchema,
|
|
1625
|
+
expectedVersion: AgentRecordVersionSchema,
|
|
1626
|
+
replacedMessageIds: z7.array(AgentRecordIdSchema).min(1),
|
|
1627
|
+
summary: AgentMessageSchema
|
|
1628
|
+
});
|
|
1629
|
+
function emptySnapshot(conversationId) {
|
|
1630
|
+
return AgentSnapshotSchema.parse({
|
|
1631
|
+
schemaVersion: 1,
|
|
1632
|
+
conversationId,
|
|
1633
|
+
version: 0,
|
|
1634
|
+
messages: [],
|
|
1635
|
+
runs: []
|
|
1636
|
+
});
|
|
1637
|
+
}
|
|
1638
|
+
function cloneSnapshot(snapshot) {
|
|
1639
|
+
return AgentSnapshotSchema.parse(structuredClone(snapshot));
|
|
1640
|
+
}
|
|
1641
|
+
function replaceRun(runs, next) {
|
|
1642
|
+
return runs.map((run) => run.id === next.id ? next : run);
|
|
1643
|
+
}
|
|
1644
|
+
function replaceMessage(messages, next) {
|
|
1645
|
+
const exists = messages.some((message) => message.id === next.id);
|
|
1646
|
+
if (!exists)
|
|
1647
|
+
return [...messages, next];
|
|
1648
|
+
return messages.map((message) => message.id === next.id ? next : message);
|
|
1649
|
+
}
|
|
1650
|
+
function terminalState(reason) {
|
|
1651
|
+
if (reason === "success" || reason === "policy_stop")
|
|
1652
|
+
return "completed";
|
|
1653
|
+
if (reason === "interrupted")
|
|
1654
|
+
return "interrupted";
|
|
1655
|
+
if (reason === "cancelled" || reason === "shutdown" || reason === "timeout") {
|
|
1656
|
+
return "cancelled";
|
|
1657
|
+
}
|
|
1658
|
+
if (reason === "abandoned")
|
|
1659
|
+
return "abandoned";
|
|
1660
|
+
return "failed";
|
|
1661
|
+
}
|
|
1662
|
+
function terminalMessageStatus(reason) {
|
|
1663
|
+
if (reason === "success" || reason === "policy_stop")
|
|
1664
|
+
return "completed";
|
|
1665
|
+
if (reason === "interrupted" || reason === "cancelled" || reason === "shutdown") {
|
|
1666
|
+
return "interrupted";
|
|
1667
|
+
}
|
|
1668
|
+
return "failed";
|
|
1669
|
+
}
|
|
1670
|
+
function createMemoryAgentRuntimeStore() {
|
|
1671
|
+
const conversations = new Map;
|
|
1672
|
+
const get = (conversationId) => {
|
|
1673
|
+
const existing = conversations.get(conversationId);
|
|
1674
|
+
if (existing)
|
|
1675
|
+
return existing;
|
|
1676
|
+
const created = { snapshot: emptySnapshot(conversationId), idempotency: new Map };
|
|
1677
|
+
conversations.set(conversationId, created);
|
|
1678
|
+
return created;
|
|
1679
|
+
};
|
|
1680
|
+
const conflict = (actualVersion) => ({
|
|
1681
|
+
outcome: "conflict",
|
|
1682
|
+
actualVersion
|
|
1683
|
+
});
|
|
1684
|
+
const apply = (entry, snapshot) => {
|
|
1685
|
+
entry.snapshot = AgentSnapshotSchema.parse(snapshot);
|
|
1686
|
+
return { outcome: "applied", snapshot: cloneSnapshot(entry.snapshot) };
|
|
1687
|
+
};
|
|
1688
|
+
return {
|
|
1689
|
+
async loadSnapshot(conversationId) {
|
|
1690
|
+
return cloneSnapshot(get(conversationId).snapshot);
|
|
1691
|
+
},
|
|
1692
|
+
async acceptInputAndAssignRun(rawInput) {
|
|
1693
|
+
const input = AcceptInputAndAssignRunSchema.parse(rawInput);
|
|
1694
|
+
const entry = get(input.input.conversationId);
|
|
1695
|
+
const duplicateRunId = entry.idempotency.get(input.idempotencyKey);
|
|
1696
|
+
if (duplicateRunId !== undefined) {
|
|
1697
|
+
return {
|
|
1698
|
+
outcome: "duplicate",
|
|
1699
|
+
runId: duplicateRunId,
|
|
1700
|
+
snapshot: cloneSnapshot(entry.snapshot)
|
|
1701
|
+
};
|
|
1702
|
+
}
|
|
1703
|
+
if (input.run.conversationId !== input.input.conversationId || input.run.inputMessageIds.length !== 1 || input.run.inputMessageIds[0] !== input.input.id || input.run.state !== "queued" || input.run.revision !== 0 || input.run.ownerId !== undefined || input.run.terminalReason !== undefined || input.run.terminalPolicyName !== undefined || input.input.role !== "user" || input.input.status !== "committed" || input.input.runId !== undefined || entry.snapshot.messages.some((message) => message.id === input.input.id) || entry.snapshot.runs.some((candidate) => candidate.id === input.run.id)) {
|
|
1704
|
+
throw new TypeError("Input and queued run do not form one valid assignment");
|
|
1705
|
+
}
|
|
1706
|
+
if (input.expectedVersion !== undefined && input.expectedVersion !== entry.snapshot.version) {
|
|
1707
|
+
return conflict(entry.snapshot.version);
|
|
1708
|
+
}
|
|
1709
|
+
const coalescedRun = input.coalesceIntoRunId ? entry.snapshot.runs.find((candidate) => candidate.id === input.coalesceIntoRunId) : undefined;
|
|
1710
|
+
if (input.coalesceIntoRunId !== undefined && (!coalescedRun || coalescedRun.conversationId !== input.input.conversationId || coalescedRun.state !== "queued" || coalescedRun.ownerId !== undefined || coalescedRun.terminalReason !== undefined)) {
|
|
1711
|
+
return coalescedRun ? conflict(coalescedRun.revision) : { outcome: "not_found" };
|
|
1712
|
+
}
|
|
1713
|
+
const assignedRun = coalescedRun ? AgentRunSchema.parse({
|
|
1714
|
+
...coalescedRun,
|
|
1715
|
+
inputMessageIds: [...coalescedRun.inputMessageIds, input.input.id],
|
|
1716
|
+
revision: coalescedRun.revision + 1,
|
|
1717
|
+
updatedAt: new Date().toISOString()
|
|
1718
|
+
}) : input.run;
|
|
1719
|
+
const next = AgentSnapshotSchema.parse({
|
|
1720
|
+
...entry.snapshot,
|
|
1721
|
+
version: entry.snapshot.version + 1,
|
|
1722
|
+
messages: [...entry.snapshot.messages, input.input],
|
|
1723
|
+
runs: coalescedRun ? replaceRun(entry.snapshot.runs, assignedRun) : [...entry.snapshot.runs, assignedRun]
|
|
1724
|
+
});
|
|
1725
|
+
entry.idempotency.set(input.idempotencyKey, assignedRun.id);
|
|
1726
|
+
return apply(entry, next);
|
|
1727
|
+
},
|
|
1728
|
+
async acquireRun(rawInput) {
|
|
1729
|
+
const input = AcquireAgentRunSchema.parse(rawInput);
|
|
1730
|
+
const entry = get(input.conversationId);
|
|
1731
|
+
const run = entry.snapshot.runs.find((candidate) => candidate.id === input.runId);
|
|
1732
|
+
if (!run)
|
|
1733
|
+
return { outcome: "not_found" };
|
|
1734
|
+
if (run.revision !== input.expectedRevision || run.state !== "queued") {
|
|
1735
|
+
return conflict(run.revision);
|
|
1736
|
+
}
|
|
1737
|
+
const nextRun = AgentRunSchema.parse({
|
|
1738
|
+
...run,
|
|
1739
|
+
state: "running",
|
|
1740
|
+
ownerId: input.ownerId,
|
|
1741
|
+
revision: run.revision + 1,
|
|
1742
|
+
updatedAt: new Date().toISOString()
|
|
1743
|
+
});
|
|
1744
|
+
return apply(entry, AgentSnapshotSchema.parse({
|
|
1745
|
+
...entry.snapshot,
|
|
1746
|
+
version: entry.snapshot.version + 1,
|
|
1747
|
+
runs: replaceRun(entry.snapshot.runs, nextRun)
|
|
1748
|
+
}));
|
|
1749
|
+
},
|
|
1750
|
+
async checkpointRunAssistant(rawInput) {
|
|
1751
|
+
const input = CheckpointRunAssistantSchema.parse(rawInput);
|
|
1752
|
+
const entry = get(input.conversationId);
|
|
1753
|
+
const run = entry.snapshot.runs.find((candidate) => candidate.id === input.runId);
|
|
1754
|
+
if (!run)
|
|
1755
|
+
return { outcome: "not_found" };
|
|
1756
|
+
if (run.revision !== input.expectedRevision || run.state !== "running" || run.ownerId !== input.ownerId || input.assistant.runId !== run.id || input.assistant.id !== run.assistantMessageId || input.assistant.conversationId !== run.conversationId || input.assistant.role !== "assistant" || input.assistant.status !== "streaming") {
|
|
1757
|
+
return conflict(run.revision);
|
|
1758
|
+
}
|
|
1759
|
+
const nextRun = AgentRunSchema.parse({
|
|
1760
|
+
...run,
|
|
1761
|
+
revision: run.revision + 1,
|
|
1762
|
+
updatedAt: new Date().toISOString()
|
|
1763
|
+
});
|
|
1764
|
+
return apply(entry, AgentSnapshotSchema.parse({
|
|
1765
|
+
...entry.snapshot,
|
|
1766
|
+
version: entry.snapshot.version + 1,
|
|
1767
|
+
messages: replaceMessage(entry.snapshot.messages, input.assistant),
|
|
1768
|
+
runs: replaceRun(entry.snapshot.runs, nextRun)
|
|
1769
|
+
}));
|
|
1770
|
+
},
|
|
1771
|
+
async requestRunInterrupt(rawInput) {
|
|
1772
|
+
const input = RequestRunInterruptSchema.parse(rawInput);
|
|
1773
|
+
const entry = get(input.conversationId);
|
|
1774
|
+
const run = entry.snapshot.runs.find((candidate) => candidate.id === input.runId);
|
|
1775
|
+
if (!run)
|
|
1776
|
+
return { outcome: "not_found" };
|
|
1777
|
+
if (run.revision !== input.expectedRevision || run.state !== "running") {
|
|
1778
|
+
return conflict(run.revision);
|
|
1779
|
+
}
|
|
1780
|
+
const nextRun = AgentRunSchema.parse({
|
|
1781
|
+
...run,
|
|
1782
|
+
state: "interrupt_requested",
|
|
1783
|
+
revision: run.revision + 1,
|
|
1784
|
+
updatedAt: new Date().toISOString()
|
|
1785
|
+
});
|
|
1786
|
+
return apply(entry, AgentSnapshotSchema.parse({
|
|
1787
|
+
...entry.snapshot,
|
|
1788
|
+
version: entry.snapshot.version + 1,
|
|
1789
|
+
runs: replaceRun(entry.snapshot.runs, nextRun)
|
|
1790
|
+
}));
|
|
1791
|
+
},
|
|
1792
|
+
async recoverRun(rawInput) {
|
|
1793
|
+
const input = RecoverAgentRunSchema.parse(rawInput);
|
|
1794
|
+
const entry = get(input.conversationId);
|
|
1795
|
+
const run = entry.snapshot.runs.find((candidate) => candidate.id === input.runId);
|
|
1796
|
+
if (!run)
|
|
1797
|
+
return { outcome: "not_found" };
|
|
1798
|
+
if (run.revision !== input.expectedRevision || !["queued", "running", "interrupt_requested"].includes(run.state)) {
|
|
1799
|
+
return conflict(run.revision);
|
|
1800
|
+
}
|
|
1801
|
+
if (input.action === "requeue" && run.state !== "queued" && input.replaySafe !== true) {
|
|
1802
|
+
throw new TypeError("Recovering an acquired run requires explicit replaySafe evidence");
|
|
1803
|
+
}
|
|
1804
|
+
const nextRun = AgentRunSchema.parse({
|
|
1805
|
+
schemaVersion: 1,
|
|
1806
|
+
id: run.id,
|
|
1807
|
+
conversationId: run.conversationId,
|
|
1808
|
+
inputMessageIds: run.inputMessageIds,
|
|
1809
|
+
assistantMessageId: run.assistantMessageId,
|
|
1810
|
+
state: input.action === "requeue" ? "queued" : "abandoned",
|
|
1811
|
+
revision: run.revision + 1,
|
|
1812
|
+
...input.action === "abandon" && { terminalReason: "abandoned" },
|
|
1813
|
+
createdAt: run.createdAt,
|
|
1814
|
+
updatedAt: new Date().toISOString()
|
|
1815
|
+
});
|
|
1816
|
+
return apply(entry, AgentSnapshotSchema.parse({
|
|
1817
|
+
...entry.snapshot,
|
|
1818
|
+
version: entry.snapshot.version + 1,
|
|
1819
|
+
runs: replaceRun(entry.snapshot.runs, nextRun)
|
|
1820
|
+
}));
|
|
1821
|
+
},
|
|
1822
|
+
async commitRunTerminal(rawInput) {
|
|
1823
|
+
const input = CommitRunTerminalSchema.parse(rawInput);
|
|
1824
|
+
const entry = get(input.conversationId);
|
|
1825
|
+
const run = entry.snapshot.runs.find((candidate) => candidate.id === input.runId);
|
|
1826
|
+
if (!run)
|
|
1827
|
+
return { outcome: "not_found" };
|
|
1828
|
+
if (run.revision !== input.expectedRevision || run.state !== "running" && run.state !== "interrupt_requested" || run.ownerId !== input.ownerId || input.assistant.runId !== run.id || input.assistant.id !== run.assistantMessageId || input.assistant.conversationId !== run.conversationId || input.assistant.role !== "assistant" || input.assistant.status !== terminalMessageStatus(input.reason)) {
|
|
1829
|
+
return conflict(run.revision);
|
|
1830
|
+
}
|
|
1831
|
+
const nextRun = AgentRunSchema.parse({
|
|
1832
|
+
...run,
|
|
1833
|
+
state: terminalState(input.reason),
|
|
1834
|
+
terminalReason: input.reason,
|
|
1835
|
+
...input.policyName && { terminalPolicyName: input.policyName },
|
|
1836
|
+
revision: run.revision + 1,
|
|
1837
|
+
updatedAt: new Date().toISOString()
|
|
1838
|
+
});
|
|
1839
|
+
return apply(entry, AgentSnapshotSchema.parse({
|
|
1840
|
+
...entry.snapshot,
|
|
1841
|
+
version: entry.snapshot.version + 1,
|
|
1842
|
+
messages: replaceMessage(entry.snapshot.messages, input.assistant),
|
|
1843
|
+
runs: replaceRun(entry.snapshot.runs, nextRun)
|
|
1844
|
+
}));
|
|
1845
|
+
},
|
|
1846
|
+
async replaceCompactedRange(rawInput) {
|
|
1847
|
+
const input = ReplaceCompactedRangeSchema.parse(rawInput);
|
|
1848
|
+
const entry = get(input.conversationId);
|
|
1849
|
+
if (entry.snapshot.version !== input.expectedVersion) {
|
|
1850
|
+
return conflict(entry.snapshot.version);
|
|
1851
|
+
}
|
|
1852
|
+
const replaced = new Set(input.replacedMessageIds);
|
|
1853
|
+
if (!input.replacedMessageIds.every((id) => entry.snapshot.messages.some((m) => m.id === id))) {
|
|
1854
|
+
return { outcome: "not_found" };
|
|
1855
|
+
}
|
|
1856
|
+
const positions = entry.snapshot.messages.map((message, index) => replaced.has(message.id) ? index : undefined).filter((index) => index !== undefined);
|
|
1857
|
+
const first = positions[0];
|
|
1858
|
+
if (first === undefined || positions.some((position, offset) => position !== first + offset) || input.summary.conversationId !== input.conversationId || input.summary.runId !== undefined || input.summary.role !== "summary" || input.summary.status !== "committed") {
|
|
1859
|
+
throw new TypeError("Compaction replacement must be one valid contiguous history range");
|
|
1860
|
+
}
|
|
1861
|
+
const before = entry.snapshot.messages.slice(0, first);
|
|
1862
|
+
const after = entry.snapshot.messages.slice(first + positions.length);
|
|
1863
|
+
return apply(entry, AgentSnapshotSchema.parse({
|
|
1864
|
+
...entry.snapshot,
|
|
1865
|
+
version: entry.snapshot.version + 1,
|
|
1866
|
+
messages: [...before, input.summary, ...after]
|
|
1867
|
+
}));
|
|
1868
|
+
},
|
|
1869
|
+
async scanRecoverable() {
|
|
1870
|
+
return [...conversations.values()].filter((entry) => entry.snapshot.runs.some((run) => ["queued", "running", "interrupt_requested"].includes(run.state))).map((entry) => cloneSnapshot(entry.snapshot));
|
|
1871
|
+
}
|
|
1872
|
+
};
|
|
1873
|
+
}
|
|
1874
|
+
export {
|
|
1875
|
+
AcceptInputAndAssignRunSchema,
|
|
1876
|
+
AcquireAgentRunSchema,
|
|
1877
|
+
AgentCheckpointEventSchema,
|
|
1878
|
+
AgentControlPartSchema,
|
|
1879
|
+
AgentCostValueSchema,
|
|
1880
|
+
AgentFilePartSchema,
|
|
1881
|
+
AgentJsonObjectSchema,
|
|
1882
|
+
AgentMessagePartSchema,
|
|
1883
|
+
AgentMessageRoleSchema,
|
|
1884
|
+
AgentMessageSchema,
|
|
1885
|
+
AgentMessageStatusSchema,
|
|
1886
|
+
AgentModelCapabilitySchema,
|
|
1887
|
+
AgentModelDescriptorSchema,
|
|
1888
|
+
AgentOpaquePartSchema,
|
|
1889
|
+
AgentProviderEnvelopeSchema,
|
|
1890
|
+
AgentReasoningPartSchema,
|
|
1891
|
+
AgentRecordIdSchema,
|
|
1892
|
+
AgentRecordVersionSchema,
|
|
1893
|
+
AgentRunEventSchema,
|
|
1894
|
+
AgentRunSchema,
|
|
1895
|
+
AgentRunStateEventSchema,
|
|
1896
|
+
AgentRunStateSchema,
|
|
1897
|
+
AgentRuntimeEventSchema,
|
|
1898
|
+
AgentSnapshotSchema,
|
|
1899
|
+
AgentSourcePartSchema,
|
|
1900
|
+
AgentStoreAppliedSchema,
|
|
1901
|
+
AgentStoreConflictSchema,
|
|
1902
|
+
AgentStoreDuplicateSchema,
|
|
1903
|
+
AgentStoreMutationResultSchema,
|
|
1904
|
+
AgentStoreNotFoundSchema,
|
|
1905
|
+
AgentTerminalEventSchema,
|
|
1906
|
+
AgentTerminalReasonSchema,
|
|
1907
|
+
AgentTextPartSchema,
|
|
1908
|
+
AgentTimestampSchema,
|
|
1909
|
+
AgentTokenCountSchema,
|
|
1910
|
+
AgentToolCallPartSchema,
|
|
1911
|
+
AgentToolResultPartSchema,
|
|
1912
|
+
AgentToolStatusEventSchema,
|
|
1913
|
+
AgentTransientDeltaEventSchema,
|
|
1914
|
+
AgentUsageSchema,
|
|
1915
|
+
AgentUsageValueSchema,
|
|
1916
|
+
CheckpointRunAssistantSchema,
|
|
1917
|
+
CommitRunTerminalSchema,
|
|
1918
|
+
RecoverAgentRunSchema,
|
|
1919
|
+
ReplaceCompactedRangeSchema,
|
|
1920
|
+
RequestRunInterruptSchema,
|
|
1921
|
+
composeAgentPrompt,
|
|
1922
|
+
createAgentObservability,
|
|
1923
|
+
createAgentRuntime,
|
|
1924
|
+
createAgentSessionCoordinator,
|
|
1925
|
+
createAgentToolFenceLifecycle,
|
|
1926
|
+
createMemoryAgentRuntimeStore,
|
|
1927
|
+
defineAgentProtocol,
|
|
1928
|
+
defineModelRegistry,
|
|
1929
|
+
projectAgentHistory,
|
|
1930
|
+
structuredCompaction
|
|
1931
|
+
};
|