@statelyai/agent 2.0.0-alpha.24 → 2.0.0-alpha.25
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/dist/ai-sdk.cjs +30 -34
- package/dist/ai-sdk.d.cts +27 -31
- package/dist/ai-sdk.d.mts +27 -31
- package/dist/ai-sdk.mjs +27 -31
- package/dist/decision-CM9CYZ6t.cjs +519 -0
- package/dist/decision-DzSv1NpD.mjs +453 -0
- package/dist/event-log-store-Ba2fsc4E.mjs +576 -0
- package/dist/event-log-store-DQmImLYk.cjs +702 -0
- package/dist/event-log-store-DvOcs10J.d.cts +338 -0
- package/dist/event-log-store-DxyVgnas.d.mts +338 -0
- package/dist/index.cjs +429 -4272
- package/dist/index.d.cts +104 -1005
- package/dist/index.d.mts +104 -1005
- package/dist/index.mjs +389 -4202
- package/dist/log.cjs +392 -0
- package/dist/log.d.cts +32 -0
- package/dist/log.d.mts +32 -0
- package/dist/log.mjs +371 -0
- package/dist/machines.cjs +27 -16
- package/dist/machines.d.cts +1 -1
- package/dist/machines.d.mts +1 -1
- package/dist/machines.mjs +27 -16
- package/dist/openai.cjs +16 -15
- package/dist/openai.d.cts +9 -9
- package/dist/openai.d.mts +9 -9
- package/dist/openai.mjs +10 -9
- package/dist/otel.d.cts +1 -1
- package/dist/otel.d.mts +1 -1
- package/dist/{run-agent-Ck7Sa3Tb.d.mts → run-agent-BOfoJg5B.d.cts} +86 -387
- package/dist/{run-agent-DWqA8KiI.d.cts → run-agent-CG343kFs.d.mts} +86 -387
- package/dist/run-agent-Cb1-LS5P.cjs +1783 -0
- package/dist/run-agent-DvDQd4If.mjs +1669 -0
- package/dist/{setup-agent-CeO2fARv.mjs → setup-agent-CFW-jPI8.mjs} +9 -20
- package/dist/{setup-agent-Dl2VELLi.cjs → setup-agent-CPf7MxnG.cjs} +20 -37
- package/dist/testing.cjs +1222 -0
- package/dist/testing.d.cts +602 -0
- package/dist/testing.d.mts +602 -0
- package/dist/testing.mjs +1212 -0
- package/dist/{text-logic-DJBK3LbG.d.mts → text-logic-BFEOxyxv.d.cts} +71 -147
- package/dist/text-logic-Bl_ZRxDi.mjs +833 -0
- package/dist/text-logic-C8xyr6Ag.cjs +1091 -0
- package/dist/{text-logic-C7bSnH_B.d.cts → text-logic-hZSiRUo1.d.mts} +71 -147
- package/dist/{types-CjHk5OdH.d.cts → types-B1Qs2q3q.d.cts} +2 -1
- package/dist/{types-DWA6X9R-.d.mts → types-tA3SdczG.d.mts} +2 -1
- package/dist/usage-BdEjFuIi.mjs +5 -0
- package/dist/usage-efOQPdf_.cjs +10 -0
- package/dist/utils-C1p37X_c.d.mts +209 -0
- package/dist/utils-RalRn9sk.d.cts +209 -0
- package/package.json +24 -4
- package/readme.md +2 -2
- package/skills/generate-machine/SKILL.md +4 -6
- package/dist/decision-BBZ2jMO1.mjs +0 -1490
- package/dist/decision-Cv6SfNWl.cjs +0 -1856
package/dist/index.cjs
CHANGED
|
@@ -1,8 +1,140 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
require("./
|
|
5
|
-
|
|
2
|
+
const require_text_logic = require("./text-logic-C8xyr6Ag.cjs");
|
|
3
|
+
const require_decision = require("./decision-CM9CYZ6t.cjs");
|
|
4
|
+
const require_usage = require("./usage-efOQPdf_.cjs");
|
|
5
|
+
const require_setup_agent = require("./setup-agent-CPf7MxnG.cjs");
|
|
6
|
+
const require_run_agent = require("./run-agent-Cb1-LS5P.cjs");
|
|
7
|
+
const require_event_log_store = require("./event-log-store-DQmImLYk.cjs");
|
|
8
|
+
//#region src/messages.ts
|
|
9
|
+
const KNOWN_PART_TYPES = new Set([
|
|
10
|
+
"text",
|
|
11
|
+
"image",
|
|
12
|
+
"file",
|
|
13
|
+
"tool-call",
|
|
14
|
+
"tool-result"
|
|
15
|
+
]);
|
|
16
|
+
const USER_PART_TYPES = new Set([
|
|
17
|
+
"text",
|
|
18
|
+
"image",
|
|
19
|
+
"file"
|
|
20
|
+
]);
|
|
21
|
+
const ASSISTANT_PART_TYPES = new Set([
|
|
22
|
+
"text",
|
|
23
|
+
"file",
|
|
24
|
+
"tool-call",
|
|
25
|
+
"tool-result"
|
|
26
|
+
]);
|
|
27
|
+
const TOOL_PART_TYPES = new Set(["tool-result"]);
|
|
28
|
+
const TOOL_RESULT_CONTENT_PART_TYPES = new Set(["text", "image"]);
|
|
29
|
+
function isKnownPart(part) {
|
|
30
|
+
const type = part && typeof part === "object" ? part.type : void 0;
|
|
31
|
+
return typeof type === "string" && KNOWN_PART_TYPES.has(type);
|
|
32
|
+
}
|
|
33
|
+
function requireString(part, type, field) {
|
|
34
|
+
return typeof part[field] === "string" ? void 0 : `${type} part requires a string "${field}"`;
|
|
35
|
+
}
|
|
36
|
+
function isMediaData(value) {
|
|
37
|
+
return typeof value === "string" || value instanceof Uint8Array || value instanceof ArrayBuffer || value instanceof URL;
|
|
38
|
+
}
|
|
39
|
+
function requireMediaData(part, type, field) {
|
|
40
|
+
return isMediaData(part[field]) ? void 0 : `${type} part requires "${field}" to be a string, Uint8Array, ArrayBuffer, or URL`;
|
|
41
|
+
}
|
|
42
|
+
const TOOL_RESULT_OUTPUT_TYPES = new Set([
|
|
43
|
+
"text",
|
|
44
|
+
"json",
|
|
45
|
+
"error-text",
|
|
46
|
+
"error-json",
|
|
47
|
+
"content"
|
|
48
|
+
]);
|
|
49
|
+
function validateToolResultOutput(output) {
|
|
50
|
+
if (!output || typeof output !== "object" || Array.isArray(output)) return "tool-result part requires an \"output\" object";
|
|
51
|
+
const record = output;
|
|
52
|
+
const outputType = record.type;
|
|
53
|
+
if (typeof outputType !== "string" || !TOOL_RESULT_OUTPUT_TYPES.has(outputType)) return `Unknown tool-result output type: ${JSON.stringify(outputType)}`;
|
|
54
|
+
if (outputType === "text" || outputType === "error-text") {
|
|
55
|
+
if (typeof record.value !== "string") return `tool-result output of type "${outputType}" requires a string "value"`;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (outputType === "content") {
|
|
59
|
+
if (!Array.isArray(record.value)) return "tool-result output of type \"content\" requires an array \"value\"";
|
|
60
|
+
for (const contentPart of record.value) {
|
|
61
|
+
const error = validatePart(contentPart, TOOL_RESULT_CONTENT_PART_TYPES, "tool-result output content");
|
|
62
|
+
if (error) return error;
|
|
63
|
+
}
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
return record.value !== void 0 ? void 0 : `tool-result output of type "${outputType}" requires a "value"`;
|
|
67
|
+
}
|
|
68
|
+
function validatePart(part, allowedTypes, location) {
|
|
69
|
+
if (!isKnownPart(part)) {
|
|
70
|
+
const type = part && typeof part === "object" ? part.type : void 0;
|
|
71
|
+
return `Unknown message part type: ${JSON.stringify(type)}`;
|
|
72
|
+
}
|
|
73
|
+
if (!allowedTypes.has(part.type)) return `${location} does not allow "${part.type}" parts`;
|
|
74
|
+
const record = part;
|
|
75
|
+
switch (record.type) {
|
|
76
|
+
case "text": return requireString(record, "text", "text");
|
|
77
|
+
case "image": return requireMediaData(record, "image", "image");
|
|
78
|
+
case "file": return requireMediaData(record, "file", "data") ?? requireString(record, "file", "mediaType");
|
|
79
|
+
case "tool-call": return requireString(record, "tool-call", "toolCallId") ?? requireString(record, "tool-call", "toolName") ?? ("input" in record ? void 0 : "tool-call part requires an \"input\" value");
|
|
80
|
+
case "tool-result": return requireString(record, "tool-result", "toolCallId") ?? requireString(record, "tool-result", "toolName") ?? validateToolResultOutput(record.output);
|
|
81
|
+
default: return;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function validatePartsArray(content, allowedTypes, location) {
|
|
85
|
+
if (!Array.isArray(content)) return "Expected content to be a string or an array of parts";
|
|
86
|
+
for (const part of content) {
|
|
87
|
+
const error = validatePart(part, allowedTypes, location);
|
|
88
|
+
if (error) return error;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* A {@link StandardSchemaV1} validating an `AgentMessage[]` context field —
|
|
93
|
+
* checks that every message has a known `role` (`system`/`user`/`assistant`/
|
|
94
|
+
* `tool`) and that `content` is either a string (where the role allows it) or
|
|
95
|
+
* an array of role-appropriate parts whose required fields and media payloads
|
|
96
|
+
* have the right runtime types (extra fields are allowed). Use it directly as
|
|
97
|
+
* a context schema's `messages` field when authoring with `createAgentSchemas`
|
|
98
|
+
* or `fromConfig`. A zod object cannot nest a Standard Schema, so in a zod
|
|
99
|
+
* context schema write `z.custom<AgentMessage[]>(isAgentMessages)`, which runs
|
|
100
|
+
* this same validator (see {@link isAgentMessages}).
|
|
101
|
+
*/
|
|
102
|
+
const messagesSchema = { "~standard": {
|
|
103
|
+
version: 1,
|
|
104
|
+
vendor: "statelyai-agent",
|
|
105
|
+
validate(value) {
|
|
106
|
+
if (!Array.isArray(value)) return { issues: [{ message: "Expected an array of agent messages" }] };
|
|
107
|
+
for (const message of value) {
|
|
108
|
+
if (!message || typeof message !== "object") return { issues: [{ message: "Expected an array of agent messages" }] };
|
|
109
|
+
const role = message.role;
|
|
110
|
+
const content = message.content;
|
|
111
|
+
if (role !== "system" && role !== "user" && role !== "assistant" && role !== "tool") return { issues: [{ message: `Unknown message role: ${JSON.stringify(role)}` }] };
|
|
112
|
+
if (role === "system") {
|
|
113
|
+
if (typeof content !== "string") return { issues: [{ message: "system message content must be a string" }] };
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (role === "tool") {
|
|
117
|
+
const error = validatePartsArray(content, TOOL_PART_TYPES, "tool message content");
|
|
118
|
+
if (error) return { issues: [{ message: error }] };
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (typeof content === "string") continue;
|
|
122
|
+
const error = validatePartsArray(content, role === "user" ? USER_PART_TYPES : ASSISTANT_PART_TYPES, `${role} message content`);
|
|
123
|
+
if (error) return { issues: [{ message: error }] };
|
|
124
|
+
}
|
|
125
|
+
return { value };
|
|
126
|
+
}
|
|
127
|
+
} };
|
|
128
|
+
/**
|
|
129
|
+
* Type guard over {@link messagesSchema}: `true` when `value` is a valid
|
|
130
|
+
* `AgentMessage[]`. The one-liner for zod context schemas:
|
|
131
|
+
* `messages: z.custom<AgentMessage[]>(isAgentMessages)`.
|
|
132
|
+
*/
|
|
133
|
+
function isAgentMessages(value) {
|
|
134
|
+
const result = messagesSchema["~standard"].validate(value);
|
|
135
|
+
return !(result instanceof Promise) && !("issues" in result && result.issues);
|
|
136
|
+
}
|
|
137
|
+
//#endregion
|
|
6
138
|
//#region src/interaction.ts
|
|
7
139
|
function issue(message, path) {
|
|
8
140
|
return {
|
|
@@ -25,9 +157,10 @@ function validateEventMeta(value, path) {
|
|
|
25
157
|
* A {@link StandardSchemaV1} for the state metadata {@link getInteraction}
|
|
26
158
|
* reads: an object whose optional `interaction` declares a `label` (a string
|
|
27
159
|
* with `{context.path}` interpolation, or a function of the context), an
|
|
28
|
-
* `events` map of choices, and a `textEvent` for free-text answers.
|
|
29
|
-
*
|
|
30
|
-
*
|
|
160
|
+
* `events` map of choices, and a `textEvent` for free-text answers. This is
|
|
161
|
+
* the default `meta` type `setupAgent` gives a machine; pass it to
|
|
162
|
+
* `createAgentSchemas({ meta: interactionMetaSchema })` only when building a
|
|
163
|
+
* schema pack by hand.
|
|
31
164
|
*/
|
|
32
165
|
const interactionMetaSchema = { "~standard": {
|
|
33
166
|
version: 1,
|
|
@@ -68,13 +201,13 @@ function interpolate(label, context, preserveWhitespace) {
|
|
|
68
201
|
/** Reads the active state's interaction metadata and filters it through the
|
|
69
202
|
* events XState currently accepts. Returns `undefined` when none is declared. */
|
|
70
203
|
function getInteraction(snapshot, options = {}) {
|
|
71
|
-
const interaction =
|
|
204
|
+
const interaction = require_text_logic.getStateMeta(snapshot).interaction;
|
|
72
205
|
if (!interaction) return void 0;
|
|
73
206
|
const preserveWhitespace = options.preserveWhitespace ?? false;
|
|
74
|
-
const schemas =
|
|
207
|
+
const schemas = require_text_logic.getAgentExecutionOptions(snapshot.machine)?.schemas;
|
|
75
208
|
const accepted = new Set(require_decision.getAcceptedEvents(snapshot).map((event) => event.type));
|
|
76
209
|
const label = typeof interaction.label === "function" ? interaction.label({ context: snapshot.context }) : interaction.label ?? "";
|
|
77
|
-
const events = Object.entries(interaction.events ?? {}).filter((
|
|
210
|
+
const events = Object.entries(interaction.events ?? {}).filter((entry) => entry[1] !== void 0 && accepted.has(entry[0])).map(([type, config]) => {
|
|
78
211
|
return [type, typeof config === "string" ? { label: config } : config];
|
|
79
212
|
}).filter(([type, descriptor]) => {
|
|
80
213
|
try {
|
|
@@ -124,4329 +257,353 @@ function eventFromInteraction(snapshot, choice) {
|
|
|
124
257
|
return require_decision.parseAgentEvent(snapshot, event);
|
|
125
258
|
}
|
|
126
259
|
//#endregion
|
|
127
|
-
//#region src/
|
|
128
|
-
/**
|
|
129
|
-
* The step envelope: an INTERNAL, per-model-call-checkpoint view of an agent
|
|
130
|
-
* machine (`AgentStep` = snapshot + actions + discovered requests + done). It
|
|
131
|
-
* once backed a public step API; the public step path is now the thin
|
|
132
|
-
* effect/replay loop in `./effects.ts` (re-exported from `@statelyai/agent`).
|
|
133
|
-
* These helpers survive as internals that `runAgent`, `verify.ts`, and the
|
|
134
|
-
* effect path build on — the only symbol still on the public surface is
|
|
135
|
-
* {@link executeAgentRequest} (the `text`-effect resolver).
|
|
136
|
-
* @module
|
|
137
|
-
*/
|
|
138
|
-
/** @internal Normalizes current and legacy XState invoke effect shapes. */
|
|
139
|
-
function getInvokeEffectMetadata(action) {
|
|
140
|
-
if (action.type === "@xstate.spawn") return action;
|
|
141
|
-
if (action.type === "xstate.spawnChild") {
|
|
142
|
-
const params = action.params;
|
|
143
|
-
return params ? {
|
|
144
|
-
...params,
|
|
145
|
-
logic: action.logic
|
|
146
|
-
} : void 0;
|
|
147
|
-
}
|
|
148
|
-
if (action.type === "@xstate.start" && typeof action.src === "string") return action;
|
|
149
|
-
}
|
|
260
|
+
//#region src/agent-run.ts
|
|
150
261
|
/**
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
* several actors at once (an `invoke: [...]` array, or parallel regions each
|
|
155
|
-
* invoking) keeps reporting the siblings after the first one settles —
|
|
156
|
-
* including when the settled invoke's `onDone` is targetless and starts
|
|
157
|
-
* nothing new.
|
|
158
|
-
*
|
|
159
|
-
* Entries are ordered by invoke id, so a walker that resolves one invoke per
|
|
160
|
-
* step is deterministic.
|
|
262
|
+
* Wraps {@link runAgent} in a canonical run-stream handle: the run's trace
|
|
263
|
+
* events as a pull-based {@link AgentRun.events} async iterator, alongside the
|
|
264
|
+
* unchanged {@link AgentRun.result} promise.
|
|
161
265
|
*
|
|
162
|
-
*
|
|
163
|
-
*/
|
|
164
|
-
function getPendingInvokes(snapshot) {
|
|
165
|
-
const children = snapshot.children ?? {};
|
|
166
|
-
return Object.keys(children).sort().flatMap((id) => {
|
|
167
|
-
const child = children[id];
|
|
168
|
-
const childSnapshot = child?.getSnapshot?.();
|
|
169
|
-
if (!child || childSnapshot?.status !== "active") return [];
|
|
170
|
-
return [{
|
|
171
|
-
type: "@xstate.spawn",
|
|
172
|
-
id,
|
|
173
|
-
src: child.src,
|
|
174
|
-
input: childSnapshot.input,
|
|
175
|
-
logic: child.logic
|
|
176
|
-
}];
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Scans a set of executable actions (as returned by xstate's `transition`/
|
|
181
|
-
* `initialTransition`) for spawned `TextLogic`/`DecisionLogic` invokes and
|
|
182
|
-
* lowers each into an {@link AgentStepRequest}. Pass `options.machine` to
|
|
183
|
-
* pre-fill `schemas`/`actors` from the machine's registered `setupAgent`
|
|
184
|
-
* options instead of passing them by hand.
|
|
185
|
-
* `options.snapshot` is required to resolve a decision's candidate events
|
|
186
|
-
* (intersecting declared `allowedEvents` with what's currently legal) — omit
|
|
187
|
-
* it and decision requests report an empty `events` list.
|
|
266
|
+
* Semantics:
|
|
188
267
|
*
|
|
189
|
-
*
|
|
268
|
+
* 1. **Starts immediately.** The underlying `runAgent` is invoked on this call,
|
|
269
|
+
* not on the first `events` iteration — work is already in flight when
|
|
270
|
+
* `createAgentRun` returns.
|
|
271
|
+
* 2. **Buffer, don't stall.** Events are queued in an unbounded in-memory
|
|
272
|
+
* buffer as the run emits them; a slow or absent consumer never applies
|
|
273
|
+
* backpressure to the run. Once `run.end` is delivered, the iterator
|
|
274
|
+
* completes.
|
|
275
|
+
* 3. **Composes `onTrace`.** If `options.onTrace` is passed, it still fires for
|
|
276
|
+
* every event — the wrapper adds queue delivery, it does not replace the
|
|
277
|
+
* caller's sink.
|
|
278
|
+
* 4. **`result` mirrors `runAgent`.** Same resolution and rejection behavior
|
|
279
|
+
* (see {@link AgentRun.result}). A no-op rejection handler is attached
|
|
280
|
+
* internally so a caller who reads only `events` never trips an unhandled
|
|
281
|
+
* rejection on a bind-time throw; the returned promise still rejects for a
|
|
282
|
+
* caller who awaits it.
|
|
283
|
+
* 5. **Resume is identical.** Options pass straight through, so starting from a
|
|
284
|
+
* persisted `snapshot` (+ resume `event`) streams that run's events from its
|
|
285
|
+
* own `run.start`, exactly as `runAgent` would run it.
|
|
286
|
+
* 6. **Early termination does not cancel the run.** Breaking out of a
|
|
287
|
+
* `for await` (or calling `events.return()`) stops delivery, but the run
|
|
288
|
+
* keeps going and `result` still settles. Cancelling the run itself is
|
|
289
|
+
* future work — pass `options.signal` to abort it.
|
|
290
|
+
* 7. **`events` is single-consumer.** See {@link AgentRun.events}.
|
|
190
291
|
*/
|
|
191
|
-
function
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
292
|
+
function createAgentRun(machine, options) {
|
|
293
|
+
const buffer = [];
|
|
294
|
+
const resolvers = [];
|
|
295
|
+
let closed = false;
|
|
296
|
+
let stopped = false;
|
|
297
|
+
const push = (event) => {
|
|
298
|
+
if (closed || stopped) return;
|
|
299
|
+
const resolve = resolvers.shift();
|
|
300
|
+
if (resolve) resolve({
|
|
301
|
+
value: event,
|
|
302
|
+
done: false
|
|
303
|
+
});
|
|
304
|
+
else buffer.push(event);
|
|
305
|
+
};
|
|
306
|
+
const drain = () => {
|
|
307
|
+
while (resolvers.length > 0) resolvers.shift()({
|
|
308
|
+
value: void 0,
|
|
309
|
+
done: true
|
|
310
|
+
});
|
|
311
|
+
};
|
|
312
|
+
const close = () => {
|
|
313
|
+
if (closed) return;
|
|
314
|
+
closed = true;
|
|
315
|
+
drain();
|
|
195
316
|
};
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const allowedEventTypes = registeredLogic.allowedEventTypes?.(params.input);
|
|
204
|
-
const events = options.snapshot ? require_decision.getAcceptedEvents(options.snapshot, {
|
|
205
|
-
events: options.events,
|
|
206
|
-
schemas: options.schemas,
|
|
207
|
-
eventTypes: allowedEventTypes,
|
|
208
|
-
eventToolName: options.eventToolName
|
|
209
|
-
}) : [];
|
|
210
|
-
return [{
|
|
211
|
-
...decisionRequest,
|
|
212
|
-
id: params.id,
|
|
213
|
-
events
|
|
214
|
-
}];
|
|
317
|
+
const userOnTrace = options.onTrace;
|
|
318
|
+
const result = require_run_agent.runAgent(machine, {
|
|
319
|
+
...options,
|
|
320
|
+
onTrace: (event) => {
|
|
321
|
+
push(event);
|
|
322
|
+
userOnTrace?.(event);
|
|
323
|
+
if (event.type === "run.end") close();
|
|
215
324
|
}
|
|
216
|
-
const textLogic = require_decision.isTextLogic(registeredLogic) ? registeredLogic : void 0;
|
|
217
|
-
const input = textLogic ? textLogic.request(params.input) : void 0;
|
|
218
|
-
if (!input) return [];
|
|
219
|
-
return [{
|
|
220
|
-
kind: "text",
|
|
221
|
-
id: params.id,
|
|
222
|
-
src: params.src,
|
|
223
|
-
...textLogic ? { mode: textLogic.mode } : {},
|
|
224
|
-
input,
|
|
225
|
-
tools: input.tools ?? {},
|
|
226
|
-
events: []
|
|
227
|
-
}];
|
|
228
325
|
});
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Builds the canonical `xstate.done.actor` event xstate's `transition()`
|
|
232
|
-
* expects to resolve a spawned invoke — including the child session identity
|
|
233
|
-
* that rejects a stale completion from an earlier incarnation.
|
|
234
|
-
*
|
|
235
|
-
* @internal
|
|
236
|
-
*/
|
|
237
|
-
function doneEvent(snapshot, request, output) {
|
|
238
|
-
const id = typeof request === "string" ? request : request.id;
|
|
239
|
-
const child = snapshot.children[id];
|
|
326
|
+
result.then(close, close);
|
|
240
327
|
return {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
328
|
+
events: {
|
|
329
|
+
next() {
|
|
330
|
+
if (buffer.length > 0) return Promise.resolve({
|
|
331
|
+
value: buffer.shift(),
|
|
332
|
+
done: false
|
|
333
|
+
});
|
|
334
|
+
if (closed || stopped) return Promise.resolve({
|
|
335
|
+
value: void 0,
|
|
336
|
+
done: true
|
|
337
|
+
});
|
|
338
|
+
return new Promise((resolve) => {
|
|
339
|
+
resolvers.push(resolve);
|
|
340
|
+
});
|
|
341
|
+
},
|
|
342
|
+
return() {
|
|
343
|
+
stopped = true;
|
|
344
|
+
buffer.length = 0;
|
|
345
|
+
drain();
|
|
346
|
+
return Promise.resolve({
|
|
347
|
+
value: void 0,
|
|
348
|
+
done: true
|
|
349
|
+
});
|
|
350
|
+
},
|
|
351
|
+
[Symbol.asyncIterator]() {
|
|
352
|
+
return this;
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
result
|
|
245
356
|
};
|
|
246
357
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
* same child session identity, so xstate does not discard it as stale.
|
|
251
|
-
*
|
|
252
|
-
* @internal
|
|
253
|
-
*/
|
|
254
|
-
function errorEvent(snapshot, request, error) {
|
|
255
|
-
const id = typeof request === "string" ? request : request.id;
|
|
256
|
-
const child = snapshot.children[id];
|
|
358
|
+
function requestIdentity(request) {
|
|
359
|
+
const candidate = request;
|
|
360
|
+
const id = candidate.id ?? candidate.name ?? "agent.request";
|
|
257
361
|
return {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
...typeof child?.sessionId === "string" ? { sessionId: child.sessionId } : {},
|
|
261
|
-
error
|
|
362
|
+
name: candidate.kind === "text" ? candidate.input?.name ?? id : candidate.name ?? id,
|
|
363
|
+
id
|
|
262
364
|
};
|
|
263
365
|
}
|
|
264
366
|
/**
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
* {@link rejectAgentStep} — those helpers wrap this and also run
|
|
269
|
-
* {@link getAgentRequests} to produce the next {@link AgentStep}.
|
|
270
|
-
*
|
|
271
|
-
* @internal
|
|
272
|
-
*/
|
|
273
|
-
function transitionResult(logic, snapshot, request, output, outcome = "output") {
|
|
274
|
-
const event = outcome === "error" ? errorEvent(snapshot, request, output) : doneEvent(snapshot, request, output);
|
|
275
|
-
const result = (0, xstate.transition)(logic, snapshot, event);
|
|
276
|
-
applyFinalStateOutput(logic, result[0], event);
|
|
277
|
-
return result;
|
|
278
|
-
}
|
|
279
|
-
/**
|
|
280
|
-
* Starts a machine and returns its first {@link AgentStep} — the step-path
|
|
281
|
-
* equivalent of `initialTransition` plus request discovery. Begins the
|
|
282
|
-
* durable/per-model-call-checkpoint loop: resolve each `step.requests` entry
|
|
283
|
-
* (via {@link executeAgentRequest} for `kind: 'text'`, or
|
|
284
|
-
* {@link resolveDecision} for `kind: 'decision'`), then advance with
|
|
285
|
-
* {@link resolveAgentStep} or {@link transitionAgentStep}.
|
|
286
|
-
*/
|
|
287
|
-
function initialAgentStep(machine, input, options) {
|
|
288
|
-
const [snapshot, actions] = (0, xstate.initialTransition)(machine, input);
|
|
289
|
-
return createAgentStep(machine, snapshot, actions, require_decision.getRegisteredAgentExecutionOptions(machine, options));
|
|
290
|
-
}
|
|
291
|
-
/**
|
|
292
|
-
* Applies an externally-sent event (e.g. a decision's chosen event, or a
|
|
293
|
-
* human's reply) and returns the next {@link AgentStep}. Accepts **either**
|
|
294
|
-
* a raw snapshot **or** a prior `AgentStep` as the second argument —
|
|
295
|
-
* `.snapshot` is unwrapped automatically, so callers can thread the whole
|
|
296
|
-
* step object through without manually plucking the snapshot out.
|
|
297
|
-
*/
|
|
298
|
-
function transitionAgentStep(machine, snapshotOrStep, event, options) {
|
|
299
|
-
const [nextSnapshot, actions] = (0, xstate.transition)(machine, isAgentStep(snapshotOrStep) ? snapshotOrStep.snapshot : snapshotOrStep, event);
|
|
300
|
-
return createAgentStep(machine, nextSnapshot, actions, require_decision.getRegisteredAgentExecutionOptions(machine, options));
|
|
301
|
-
}
|
|
302
|
-
/**
|
|
303
|
-
* Applies a resolved text request's output (a `kind: 'text'`
|
|
304
|
-
* {@link AgentRequest} — not a decision) as a done event and returns the
|
|
305
|
-
* next {@link AgentStep}. For decisions, resolve with `resolveDecision`
|
|
306
|
-
* (which returns a {@link ChosenEvent}) and apply it with
|
|
307
|
-
* {@link transitionAgentStep} instead — a decision has no output value of
|
|
308
|
-
* its own to feed here.
|
|
309
|
-
*/
|
|
310
|
-
function resolveAgentStep(machine, step, request, output, options) {
|
|
311
|
-
const [snapshot, actions] = transitionResult(machine, step.snapshot, request, output);
|
|
312
|
-
return createAgentStep(machine, snapshot, actions, require_decision.getRegisteredAgentExecutionOptions(machine, options));
|
|
313
|
-
}
|
|
314
|
-
/**
|
|
315
|
-
* Rejects a pending invoke — the failure counterpart of
|
|
316
|
-
* {@link resolveAgentStep}. Delivers `error` as the invoke's
|
|
317
|
-
* `xstate.error.actor` event, so the machine takes that invoke's `onError`
|
|
318
|
-
* transition and returns the next {@link AgentStep}. With no `onError` in
|
|
319
|
-
* scope the returned step's snapshot has `status: 'error'`, exactly as a live
|
|
320
|
-
* run's failed invoke does.
|
|
321
|
-
*
|
|
322
|
-
* A durable host replays a failed call with this: the log says the call
|
|
323
|
-
* failed, so the replay rejects the invoke instead of resolving it. Scripted
|
|
324
|
-
* playthroughs use it too — see `simulateAgent`'s `script.errors`.
|
|
325
|
-
*
|
|
326
|
-
* `error` can be any value. An `Error` instance is the usual one, but a plain
|
|
327
|
-
* object (`{ code: 'truncated' }`) reaches the `onError` transition as
|
|
328
|
-
* `event.error` untouched, so a machine that branches on a failure code is
|
|
329
|
-
* testable without constructing a provider's error class.
|
|
367
|
+
* Streams one {@link runAgent} call as agent-level events. The machine remains
|
|
368
|
+
* the single artifact; this function only projects its requests, chunks,
|
|
369
|
+
* transitions, emitted events, and terminal result into an async iterable.
|
|
330
370
|
*/
|
|
331
|
-
function
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
371
|
+
async function* runAgentStream(machine, options) {
|
|
372
|
+
const run = createAgentRun(machine, options);
|
|
373
|
+
for await (const event of run.events) switch (event.type) {
|
|
374
|
+
case "request.start":
|
|
375
|
+
yield {
|
|
376
|
+
kind: "request",
|
|
377
|
+
phase: "start",
|
|
378
|
+
...requestIdentity(event.request),
|
|
379
|
+
request: event.request
|
|
380
|
+
};
|
|
381
|
+
break;
|
|
382
|
+
case "request.end":
|
|
383
|
+
yield {
|
|
384
|
+
kind: "request",
|
|
385
|
+
phase: "end",
|
|
386
|
+
...requestIdentity(event.request),
|
|
387
|
+
request: event.request,
|
|
388
|
+
output: event.output,
|
|
389
|
+
raw: event.raw
|
|
390
|
+
};
|
|
391
|
+
break;
|
|
392
|
+
case "request.error":
|
|
393
|
+
yield {
|
|
394
|
+
kind: "request",
|
|
395
|
+
phase: "error",
|
|
396
|
+
...requestIdentity(event.request),
|
|
397
|
+
request: event.request,
|
|
398
|
+
error: event.error
|
|
399
|
+
};
|
|
400
|
+
break;
|
|
401
|
+
case "stream.chunk":
|
|
402
|
+
yield {
|
|
403
|
+
kind: "chunk",
|
|
404
|
+
...requestIdentity(event.request),
|
|
405
|
+
delta: event.chunk
|
|
406
|
+
};
|
|
407
|
+
break;
|
|
408
|
+
case "machine.transition":
|
|
409
|
+
yield {
|
|
410
|
+
kind: "transition",
|
|
411
|
+
value: event.snapshot.value,
|
|
412
|
+
event: event.event
|
|
413
|
+
};
|
|
414
|
+
break;
|
|
415
|
+
case "emit":
|
|
416
|
+
yield {
|
|
417
|
+
kind: "emit",
|
|
418
|
+
event: event.event
|
|
419
|
+
};
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
const result = await run.result;
|
|
423
|
+
if (result.status === "done") yield {
|
|
424
|
+
kind: "done",
|
|
425
|
+
result
|
|
351
426
|
};
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
applyFinalStateOutput(machine, snapshot);
|
|
360
|
-
return {
|
|
361
|
-
snapshot,
|
|
362
|
-
actions,
|
|
363
|
-
requests: getAgentRequests(getPendingInvokes(snapshot), {
|
|
364
|
-
...options,
|
|
365
|
-
snapshot
|
|
366
|
-
}),
|
|
367
|
-
done: snapshot.status === "done"
|
|
427
|
+
else if (result.status === "idle") yield {
|
|
428
|
+
kind: "idle",
|
|
429
|
+
result
|
|
430
|
+
};
|
|
431
|
+
else yield {
|
|
432
|
+
kind: "error",
|
|
433
|
+
result
|
|
368
434
|
};
|
|
369
|
-
}
|
|
370
|
-
function resolveStateValueConfig(config, value) {
|
|
371
|
-
if (typeof value === "string") return config.states?.[value];
|
|
372
|
-
if (!value || typeof value !== "object") return;
|
|
373
|
-
for (const [key, childValue] of Object.entries(value)) {
|
|
374
|
-
const childConfig = config.states?.[key];
|
|
375
|
-
if (!childConfig) continue;
|
|
376
|
-
if (childConfig.type === "final") return childConfig;
|
|
377
|
-
const nested = resolveStateValueConfig(childConfig, childValue);
|
|
378
|
-
if (nested) return nested;
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
function applyFinalStateOutput(logic, snapshot, event) {
|
|
382
|
-
const machineSnapshot = snapshot;
|
|
383
|
-
if (machineSnapshot.status !== "done" || machineSnapshot.output !== void 0 || !("config" in logic)) return;
|
|
384
|
-
const config = logic.config;
|
|
385
|
-
if (!config) return;
|
|
386
|
-
const output = resolveStateValueConfig(config, machineSnapshot.value)?.output;
|
|
387
|
-
if (output === void 0) return;
|
|
388
|
-
machineSnapshot.output = typeof output === "function" ? output({
|
|
389
|
-
context: machineSnapshot.context,
|
|
390
|
-
event
|
|
391
|
-
}) : output;
|
|
392
|
-
}
|
|
393
|
-
function isAgentStep(value) {
|
|
394
|
-
return !!value && typeof value === "object" && "snapshot" in value && "actions" in value && "requests" in value;
|
|
395
435
|
}
|
|
396
436
|
//#endregion
|
|
397
|
-
//#region src/
|
|
437
|
+
//#region src/provide-executors.ts
|
|
398
438
|
/**
|
|
399
|
-
*
|
|
400
|
-
*
|
|
401
|
-
*
|
|
439
|
+
* Binds a machine's agent actor sources to a set of host `executors` in one
|
|
440
|
+
* call, returning a `machine.provide(...)`-ed copy ready for a plain
|
|
441
|
+
* `createActor(...)` — the uncontrolled-mode counterpart to {@link runAgent}.
|
|
442
|
+
* No run loop, no idle settling: the returned machine drives itself, so
|
|
402
443
|
*
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
*
|
|
406
|
-
*
|
|
407
|
-
* so journaling them would double-apply them.
|
|
444
|
+
* ```ts
|
|
445
|
+
* const actor = createActor(provideExecutors(machine, { generateText, decide }), { input });
|
|
446
|
+
* actor.start();
|
|
447
|
+
* ```
|
|
408
448
|
*
|
|
409
|
-
*
|
|
410
|
-
* either the machine `input` (a fresh start) or a persisted snapshot (a
|
|
411
|
-
* continuation across a machine version, or from a log-less legacy snapshot),
|
|
412
|
-
* so {@link replay} needs no side channel.
|
|
413
|
-
* @module
|
|
414
|
-
*/
|
|
415
|
-
/** The durable entry envelope version. */
|
|
416
|
-
const AGENT_EVENT_SCHEMA_VERSION = 1;
|
|
417
|
-
/**
|
|
418
|
-
* The reserved event type of the first entry of every log. It carries the
|
|
419
|
-
* machine `input` (fresh start) or a persisted `snapshot` (continuation), so a
|
|
420
|
-
* log replays with no side channel. Lives in the reserved `@agent.*` namespace
|
|
421
|
-
* so it can never collide with a machine's own vocabulary; it is consumed by
|
|
422
|
-
* {@link replay} and never fed to `transition`.
|
|
423
|
-
*/
|
|
424
|
-
const AGENT_INIT_EVENT_TYPE = "@agent.init";
|
|
425
|
-
/** A malformed or internally inconsistent log. */
|
|
426
|
-
var AgentEventLogError = class extends require_decision.AgentError {
|
|
427
|
-
constructor(message) {
|
|
428
|
-
super("invalid-event-log", message);
|
|
429
|
-
this.name = "AgentEventLogError";
|
|
430
|
-
}
|
|
431
|
-
};
|
|
432
|
-
/** A precise failure when an entry would not survive a JSON round-trip. */
|
|
433
|
-
var NonSerializableAgentEventError = class extends require_decision.AgentError {
|
|
434
|
-
path;
|
|
435
|
-
valueType;
|
|
436
|
-
constructor(path, valueType) {
|
|
437
|
-
super("non-serializable-event", `Agent event field '${path}' is not JSON-serializable (${valueType}).`);
|
|
438
|
-
this.name = "NonSerializableAgentEventError";
|
|
439
|
-
this.path = path;
|
|
440
|
-
this.valueType = valueType;
|
|
441
|
-
}
|
|
442
|
-
};
|
|
443
|
-
/** Replay reached a state that does not match the entry's recorded hash. */
|
|
444
|
-
var AgentReplayDivergenceError = class extends require_decision.AgentError {
|
|
445
|
-
constructor(eventId, index, kind, expected, actual) {
|
|
446
|
-
super("replay-diverged", kind === "missing-verification" ? `Replay entry '${eventId}' at index ${index} has no recorded verification hash.` : `Replay diverged after '${eventId}' at index ${index}: expected state hash '${expected}', got '${actual}'.`);
|
|
447
|
-
this.eventId = eventId;
|
|
448
|
-
this.index = index;
|
|
449
|
-
this.kind = kind;
|
|
450
|
-
this.expected = expected;
|
|
451
|
-
this.actual = actual;
|
|
452
|
-
this.name = "AgentReplayDivergenceError";
|
|
453
|
-
}
|
|
454
|
-
};
|
|
455
|
-
/**
|
|
456
|
-
* An entry was recorded against a different machine (id) or a different
|
|
457
|
-
* machine version than the one replaying it. An init-with-snapshot entry is
|
|
458
|
-
* exempt: that entry IS the bridge from the old version to the new one.
|
|
459
|
-
*/
|
|
460
|
-
var AgentMachineVersionMismatchError = class extends require_decision.AgentError {
|
|
461
|
-
constructor(eventId, index, expected, actual) {
|
|
462
|
-
super("machine-version-mismatch", `Event log entry '${eventId}' at index ${index} targets machine '${actual.machineId}'@'${actual.machineVersion}', expected '${expected.machineId}'@'${expected.machineVersion}'.`);
|
|
463
|
-
this.eventId = eventId;
|
|
464
|
-
this.index = index;
|
|
465
|
-
this.expected = expected;
|
|
466
|
-
this.actual = actual;
|
|
467
|
-
this.name = "AgentMachineVersionMismatchError";
|
|
468
|
-
}
|
|
469
|
-
};
|
|
470
|
-
function assertNoHiddenKeys(value, path, isOwnKey) {
|
|
471
|
-
const symbols = Object.getOwnPropertySymbols(value);
|
|
472
|
-
if (symbols.length > 0) throw new NonSerializableAgentEventError(`${path}.[${String(symbols[0])}]`, "symbol key");
|
|
473
|
-
const hiddenKey = Object.getOwnPropertyNames(value).find((key) => !isOwnKey?.(key) && !Object.getOwnPropertyDescriptor(value, key)?.enumerable);
|
|
474
|
-
if (hiddenKey !== void 0) throw new NonSerializableAgentEventError(`${path}.${hiddenKey}`, "non-enumerable property");
|
|
475
|
-
}
|
|
476
|
-
const arrayIndexPattern = /^(?:0|[1-9]\d*)$/;
|
|
477
|
-
/**
|
|
478
|
-
* Rejects values JSON would drop or coerce. Unlike `JSON.stringify`, this does
|
|
479
|
-
* not silently erase `undefined`/functions or turn non-finite numbers into
|
|
480
|
-
* `null`; durable entries contain only plain JSON values.
|
|
481
|
-
*/
|
|
482
|
-
function assertJsonSerializable(value, path = "entry", ancestors = /* @__PURE__ */ new WeakSet()) {
|
|
483
|
-
if (value === null || typeof value === "string" || typeof value === "boolean") return;
|
|
484
|
-
if (typeof value === "number") {
|
|
485
|
-
if (!Number.isFinite(value) || Object.is(value, -0)) throw new NonSerializableAgentEventError(path, Object.is(value, -0) ? "-0" : String(value));
|
|
486
|
-
return;
|
|
487
|
-
}
|
|
488
|
-
if (typeof value !== "object") throw new NonSerializableAgentEventError(path, typeof value);
|
|
489
|
-
if (ancestors.has(value)) throw new NonSerializableAgentEventError(path, "circular reference");
|
|
490
|
-
ancestors.add(value);
|
|
491
|
-
try {
|
|
492
|
-
if (Array.isArray(value)) {
|
|
493
|
-
for (let index = 0; index < value.length; index++) {
|
|
494
|
-
if (!Object.hasOwn(value, index)) throw new NonSerializableAgentEventError(`${path}[${index}]`, "array hole");
|
|
495
|
-
assertJsonSerializable(value[index], `${path}[${index}]`, ancestors);
|
|
496
|
-
}
|
|
497
|
-
const extraKey = Object.keys(value).find((key) => !arrayIndexPattern.test(key) || Number(key) >= value.length);
|
|
498
|
-
if (extraKey !== void 0) throw new NonSerializableAgentEventError(`${path}.${extraKey}`, "array property");
|
|
499
|
-
assertNoHiddenKeys(value, path, (key) => key === "length" || arrayIndexPattern.test(key));
|
|
500
|
-
return;
|
|
501
|
-
}
|
|
502
|
-
const prototype = Object.getPrototypeOf(value);
|
|
503
|
-
if (prototype !== Object.prototype && prototype !== null) throw new NonSerializableAgentEventError(path, value.constructor?.name ?? "object");
|
|
504
|
-
assertNoHiddenKeys(value, path);
|
|
505
|
-
for (const [key, child] of Object.entries(value)) assertJsonSerializable(child, `${path}.${key}`, ancestors);
|
|
506
|
-
} finally {
|
|
507
|
-
ancestors.delete(value);
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
function requireNonEmptyString(value, label, qualifier = "") {
|
|
511
|
-
if (typeof value !== "string" || !value) throw new AgentEventLogError(`Agent event ${label} must be a non-empty string${qualifier}.`);
|
|
512
|
-
}
|
|
513
|
-
/** Validates the complete durable envelope before append/export/replay. */
|
|
514
|
-
function assertAgentLogEntry(entry) {
|
|
515
|
-
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) throw new AgentEventLogError("Agent event entry must be an object.");
|
|
516
|
-
assertJsonSerializable(entry);
|
|
517
|
-
const candidate = entry;
|
|
518
|
-
if (candidate.schemaVersion !== 1) throw new AgentEventLogError(`Unsupported agent event schema version '${String(candidate.schemaVersion)}'; expected '1'.`);
|
|
519
|
-
if (!Number.isInteger(candidate.index) || candidate.index < 0) throw new AgentEventLogError(`Agent event entry.index must be a non-negative integer; got ${String(candidate.index)}.`);
|
|
520
|
-
for (const field of [
|
|
521
|
-
"id",
|
|
522
|
-
"machineId",
|
|
523
|
-
"machineVersion"
|
|
524
|
-
]) requireNonEmptyString(candidate[field], `entry.${field}`);
|
|
525
|
-
if (typeof candidate.recordedAt !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(candidate.recordedAt) || Number.isNaN(Date.parse(candidate.recordedAt))) throw new AgentEventLogError(`Agent event entry.recordedAt is not RFC 3339: '${String(candidate.recordedAt)}'.`);
|
|
526
|
-
if (candidate.event === null || typeof candidate.event !== "object" || Array.isArray(candidate.event) || typeof candidate.event.type !== "string" || !candidate.event.type) throw new AgentEventLogError("Agent event entry.event requires a non-empty string type.");
|
|
527
|
-
if (candidate.causationId !== void 0) requireNonEmptyString(candidate.causationId, "entry.causationId", " when present");
|
|
528
|
-
if (candidate.metadata !== void 0 && (candidate.metadata === null || typeof candidate.metadata !== "object" || Array.isArray(candidate.metadata))) throw new AgentEventLogError("Agent event entry.metadata must be an object when present.");
|
|
529
|
-
if (candidate.verification !== void 0 && (candidate.verification === null || typeof candidate.verification !== "object" || typeof candidate.verification.stateHash !== "string" || !candidate.verification.stateHash)) throw new AgentEventLogError("Agent event entry.verification requires a non-empty stateHash string.");
|
|
530
|
-
}
|
|
531
|
-
/**
|
|
532
|
-
* Replaces `Error` instances with plain `{ name, message, cause? }` objects so
|
|
533
|
-
* an `xstate.error.actor` payload survives the log's JSON round-trip. Cycles
|
|
534
|
-
* are preserved by identity; non-plain objects are left alone (and are then
|
|
535
|
-
* rejected by {@link assertJsonSerializable}).
|
|
536
|
-
*/
|
|
537
|
-
function normalizeEventErrors(value, seen = /* @__PURE__ */ new WeakMap()) {
|
|
538
|
-
if (value instanceof Error) {
|
|
539
|
-
const normalized = {
|
|
540
|
-
name: value.name,
|
|
541
|
-
message: value.message
|
|
542
|
-
};
|
|
543
|
-
seen.set(value, normalized);
|
|
544
|
-
if (value.cause !== void 0) normalized.cause = normalizeEventErrors(value.cause, seen);
|
|
545
|
-
return normalized;
|
|
546
|
-
}
|
|
547
|
-
if (value === null || typeof value !== "object") return value;
|
|
548
|
-
const existing = seen.get(value);
|
|
549
|
-
if (existing !== void 0) return existing;
|
|
550
|
-
if (Array.isArray(value)) {
|
|
551
|
-
const result = [];
|
|
552
|
-
seen.set(value, result);
|
|
553
|
-
for (const item of value) result.push(normalizeEventErrors(item, seen));
|
|
554
|
-
return result;
|
|
555
|
-
}
|
|
556
|
-
const prototype = Object.getPrototypeOf(value);
|
|
557
|
-
if (prototype !== Object.prototype && prototype !== null) return value;
|
|
558
|
-
const result = {};
|
|
559
|
-
seen.set(value, result);
|
|
560
|
-
for (const [key, item] of Object.entries(value)) result[key] = normalizeEventErrors(item, seen);
|
|
561
|
-
return result;
|
|
562
|
-
}
|
|
563
|
-
const SNAPSHOT_VOLATILE_KEYS = new Set([
|
|
564
|
-
"sessionId",
|
|
565
|
-
"_nextTimerId",
|
|
566
|
-
"_nextActorIds"
|
|
567
|
-
]);
|
|
568
|
-
const CHILD_VOLATILE_KEYS = new Set(["sessionId"]);
|
|
569
|
-
function positionOf(parent, key) {
|
|
570
|
-
if (parent === "snapshot") return key === "children" ? "children" : "data";
|
|
571
|
-
if (parent === "children") return "child";
|
|
572
|
-
if (parent === "child") return key === "snapshot" ? "snapshot" : "data";
|
|
573
|
-
return "data";
|
|
574
|
-
}
|
|
575
|
-
function canonicalizeForHash(value, seen = /* @__PURE__ */ new WeakSet(), position = "snapshot") {
|
|
576
|
-
if (value === void 0) return "[undefined]";
|
|
577
|
-
if (typeof value === "function") return "[function]";
|
|
578
|
-
if (typeof value === "bigint") return `[bigint:${String(value)}]`;
|
|
579
|
-
if (typeof value === "symbol") return `[symbol:${String(value)}]`;
|
|
580
|
-
if (value === null || typeof value !== "object") return value;
|
|
581
|
-
if (value instanceof Date) return `[Date:${value.toISOString()}]`;
|
|
582
|
-
if (value instanceof Error) {
|
|
583
|
-
if (seen.has(value)) return "[circular]";
|
|
584
|
-
seen.add(value);
|
|
585
|
-
try {
|
|
586
|
-
return {
|
|
587
|
-
errorName: value.name,
|
|
588
|
-
message: value.message,
|
|
589
|
-
...value.cause !== void 0 ? { cause: canonicalizeForHash(value.cause, seen, "data") } : {}
|
|
590
|
-
};
|
|
591
|
-
} finally {
|
|
592
|
-
seen.delete(value);
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
if (seen.has(value)) return "[circular]";
|
|
596
|
-
seen.add(value);
|
|
597
|
-
try {
|
|
598
|
-
if (value instanceof Set) return [...value].map((item) => canonicalizeForHash(item, seen, "data")).sort((a, b) => stableJson(a).localeCompare(stableJson(b)));
|
|
599
|
-
if (value instanceof Map) return [...value.entries()].map(([key, item]) => [canonicalizeForHash(key, seen, "data"), canonicalizeForHash(item, seen, "data")]).sort(([a], [b]) => stableJson(a).localeCompare(stableJson(b)));
|
|
600
|
-
if (Array.isArray(value)) return value.map((item) => canonicalizeForHash(item, seen, "data"));
|
|
601
|
-
const volatileKeys = position === "snapshot" ? SNAPSHOT_VOLATILE_KEYS : position === "child" ? CHILD_VOLATILE_KEYS : void 0;
|
|
602
|
-
const result = {};
|
|
603
|
-
for (const key of Object.keys(value).sort()) {
|
|
604
|
-
if (volatileKeys?.has(key)) continue;
|
|
605
|
-
const child = value[key];
|
|
606
|
-
if (child === void 0) continue;
|
|
607
|
-
result[key] = canonicalizeForHash(child, seen, positionOf(position, key));
|
|
608
|
-
}
|
|
609
|
-
return result;
|
|
610
|
-
} finally {
|
|
611
|
-
seen.delete(value);
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
/**
|
|
615
|
-
* Key-sorted JSON for comparing two events by content: key insertion order
|
|
616
|
-
* does not count, so an entry read back from storage equals the one that was
|
|
617
|
-
* appended.
|
|
618
|
-
* @internal
|
|
619
|
-
*/
|
|
620
|
-
function canonicalEventJson(event) {
|
|
621
|
-
return stableJson(canonicalizeForHash(event, /* @__PURE__ */ new Set(), "data"));
|
|
622
|
-
}
|
|
623
|
-
function stableJson(value) {
|
|
624
|
-
return JSON.stringify(sortJson(value));
|
|
625
|
-
}
|
|
626
|
-
function sortJson(value) {
|
|
627
|
-
if (Array.isArray(value)) return value.map(sortJson);
|
|
628
|
-
if (value && typeof value === "object") return Object.fromEntries(Object.keys(value).sort().map((key) => [key, sortJson(value[key])]));
|
|
629
|
-
return value;
|
|
630
|
-
}
|
|
631
|
-
/**
|
|
632
|
-
* The canonical hash of a PERSISTED snapshot (`machine.getPersistedSnapshot()`,
|
|
633
|
-
* or {@link replay}'s `persistedSnapshot`) — the value recorded in an entry's
|
|
634
|
-
* `verification.stateHash`.
|
|
635
|
-
*
|
|
636
|
-
* Stripped before hashing, because they are re-minted on every fold and would
|
|
637
|
-
* otherwise make two identical runs hash differently: `sessionId`,
|
|
638
|
-
* `_nextTimerId` and `_nextActorIds` at the snapshot root, `sessionId` on each
|
|
639
|
-
* `children[*]` entry (recursively, through nested child snapshots), and
|
|
640
|
-
* `undefined`-valued properties (so a snapshot hashes the same before and
|
|
641
|
-
* after a JSON round-trip). Stripping is confined to those structural
|
|
642
|
-
* positions: a `sessionId` inside `context`, `output`, `error` or an event
|
|
643
|
-
* payload is application data and is hashed. Object keys are sorted, so key
|
|
644
|
-
* order is irrelevant.
|
|
645
|
-
*
|
|
646
|
-
* A change detector, not a cryptographic digest.
|
|
647
|
-
*/
|
|
648
|
-
function getSnapshotStateHash(snapshot) {
|
|
649
|
-
return require_decision.djb2Hex(stableJson(canonicalizeForHash(snapshot)));
|
|
650
|
-
}
|
|
651
|
-
function machineIdOf(machine) {
|
|
652
|
-
return machine.config.id ?? machine.id ?? "(machine)";
|
|
653
|
-
}
|
|
654
|
-
function toPersisted(machine, snapshot) {
|
|
655
|
-
return typeof snapshot.matches === "function" ? machine.getPersistedSnapshot(snapshot) : snapshot;
|
|
656
|
-
}
|
|
657
|
-
/**
|
|
658
|
-
* Creates a JSON-safe, self-describing entry for `event` appended after
|
|
659
|
-
* `entries` (which must be the complete prefix, so `index` and the recorded
|
|
660
|
-
* state hash line up).
|
|
661
|
-
*/
|
|
662
|
-
function createReplayEntry(machine, entries, event, options = {}) {
|
|
663
|
-
const index = entries.length;
|
|
664
|
-
const machineVersion = options.machineVersion ?? require_decision.resolveMachineVersion(machine);
|
|
665
|
-
const entry = {
|
|
666
|
-
schemaVersion: 1,
|
|
667
|
-
id: options.id ?? `evt_${String(index).padStart(8, "0")}`,
|
|
668
|
-
index,
|
|
669
|
-
recordedAt: options.recordedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
670
|
-
machineId: machineIdOf(machine),
|
|
671
|
-
machineVersion,
|
|
672
|
-
event: normalizeEventErrors(event),
|
|
673
|
-
...options.causationId !== void 0 ? { causationId: options.causationId } : {},
|
|
674
|
-
...options.metadata !== void 0 ? { metadata: options.metadata } : {}
|
|
675
|
-
};
|
|
676
|
-
assertAgentLogEntry(entry);
|
|
677
|
-
if (options.verification !== false) entry.verification = { stateHash: getSnapshotStateHash(options.snapshot ? toPersisted(machine, options.snapshot) : replay(machine, [...entries, entry], {
|
|
678
|
-
machineVersion,
|
|
679
|
-
verify: false
|
|
680
|
-
}).persistedSnapshot) };
|
|
681
|
-
return entry;
|
|
682
|
-
}
|
|
683
|
-
function pruneUndefined(value) {
|
|
684
|
-
if (Array.isArray(value)) return value.map(pruneUndefined);
|
|
685
|
-
if (value === null || typeof value !== "object") return value;
|
|
686
|
-
if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) return value;
|
|
687
|
-
const result = {};
|
|
688
|
-
for (const [key, child] of Object.entries(value)) {
|
|
689
|
-
if (child === void 0) continue;
|
|
690
|
-
result[key] = pruneUndefined(child);
|
|
691
|
-
}
|
|
692
|
-
return result;
|
|
693
|
-
}
|
|
694
|
-
/**
|
|
695
|
-
* The reserved first entry of a log. Pass EXACTLY one of:
|
|
449
|
+
* behaves like a normal XState actor whose agent invokes now reach real models.
|
|
696
450
|
*
|
|
697
|
-
*
|
|
698
|
-
*
|
|
699
|
-
*
|
|
700
|
-
*
|
|
451
|
+
* Binding pass over `machine.sources.actors` (after merging
|
|
452
|
+
* `options.actors`):
|
|
453
|
+
* - `mode: 'generate'` text source → `executors.generateText`
|
|
454
|
+
* - `mode: 'stream'` text source → `executors.streamText`
|
|
455
|
+
* - decision / `agent.decide` source → `executors.decide` (snapshot-driven
|
|
456
|
+
* candidate events, guard `canTake`, and auto-delivery of the chosen event,
|
|
457
|
+
* mirroring `runAgent` but without its model-call counting)
|
|
701
458
|
*
|
|
702
|
-
* `options.
|
|
703
|
-
*
|
|
704
|
-
|
|
705
|
-
function initEntry(machine, init = {}, options = {}) {
|
|
706
|
-
const hasSnapshot = init.snapshot !== void 0;
|
|
707
|
-
if (hasSnapshot && init.input !== void 0) throw new AgentEventLogError("initEntry accepts exactly one of `input` (fresh start) or `snapshot` (continuation).");
|
|
708
|
-
return createReplayEntry(machine, [], {
|
|
709
|
-
type: AGENT_INIT_EVENT_TYPE,
|
|
710
|
-
...hasSnapshot ? { snapshot: pruneUndefined(init.snapshot) } : init.input !== void 0 ? { input: init.input } : {}
|
|
711
|
-
}, options);
|
|
712
|
-
}
|
|
713
|
-
/** Whether `entry` is the reserved init entry. */
|
|
714
|
-
function isInitEntry(entry) {
|
|
715
|
-
return entry?.event?.type === AGENT_INIT_EVENT_TYPE;
|
|
716
|
-
}
|
|
717
|
-
/** Whether `entry` is the version-bridging init-with-snapshot entry. */
|
|
718
|
-
function isSnapshotInitEntry(entry) {
|
|
719
|
-
return isInitEntry(entry) && entry.event.snapshot !== void 0;
|
|
720
|
-
}
|
|
721
|
-
/**
|
|
722
|
-
* The log's execution id: the lineage identity pinned in the init entry's
|
|
723
|
-
* `metadata.executionId`.
|
|
459
|
+
* Pass `options.onTrace` to observe request-level trace events (identical in
|
|
460
|
+
* shape to `runAgent`'s); pair it with {@link traceTransitions} on the actor's
|
|
461
|
+
* `inspect` to also capture `machine.transition` events in the same stream.
|
|
724
462
|
*
|
|
725
|
-
*
|
|
726
|
-
*
|
|
727
|
-
*
|
|
728
|
-
*
|
|
729
|
-
* execution id by construction.
|
|
463
|
+
* A source that already carries its own executor (`.withExecutor(...)`) is left
|
|
464
|
+
* as-is.
|
|
465
|
+
* itself, so supply a handler via `options.actors` if the machine uses it.
|
|
466
|
+
* Non-agent actors are untouched.
|
|
730
467
|
*
|
|
731
|
-
*
|
|
732
|
-
*
|
|
733
|
-
*/
|
|
734
|
-
function getLogExecutionId(entries) {
|
|
735
|
-
const executionId = (entries.find((entry) => isInitEntry(entry))?.metadata)?.executionId;
|
|
736
|
-
return typeof executionId === "string" ? executionId : void 0;
|
|
737
|
-
}
|
|
738
|
-
/**
|
|
739
|
-
* Validates a log before replay or append: every envelope well-formed, indices
|
|
740
|
-
* contiguous from 0, ids unique, the first entry the reserved init entry, and
|
|
741
|
-
* one consistent machine identity throughout.
|
|
468
|
+
* Throws at bind time if a source needs an executor kind that `executors` does
|
|
469
|
+
* not provide.
|
|
742
470
|
*
|
|
743
|
-
*
|
|
744
|
-
*
|
|
745
|
-
*
|
|
746
|
-
*
|
|
471
|
+
* Executor inheritance is RECURSIVE, exactly as in `runAgent`: a string-keyed
|
|
472
|
+
* invoked child machine is rebound too, so its own text/decision requests — at
|
|
473
|
+
* any depth — reach the same host executors. A direct-object invoke `src`
|
|
474
|
+
* cannot be swapped via `.provide`, so nothing under one inherits; bind those
|
|
475
|
+
* with `.withExecutor(...)` or register the child as a string-keyed source. A
|
|
476
|
+
* source that already carries its own executor is never overwritten.
|
|
747
477
|
*/
|
|
748
|
-
function
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
machineId: machineIdOf(options.machine),
|
|
753
|
-
machineVersion: options.machineVersion ?? require_decision.resolveMachineVersion(options.machine)
|
|
754
|
-
} : {
|
|
755
|
-
machineId: reference.machineId,
|
|
756
|
-
machineVersion: options.machineVersion ?? reference.machineVersion
|
|
478
|
+
function provideExecutors(machine, executors, options = {}) {
|
|
479
|
+
const bindOptions = {
|
|
480
|
+
onChunk: options.onChunk,
|
|
481
|
+
onTrace: options.onTrace
|
|
757
482
|
};
|
|
758
|
-
const
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
machineId: entry.machineId,
|
|
770
|
-
machineVersion: entry.machineVersion
|
|
771
|
-
});
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
/**
|
|
775
|
-
* The prefix `[0, upToIndex)` of a log, as a new log. `upToIndex` is exclusive
|
|
776
|
-
* and must leave the init entry in place, so the fork is still self-contained.
|
|
777
|
-
*/
|
|
778
|
-
function forkEventLog(entries, upToIndex) {
|
|
779
|
-
if (!Number.isInteger(upToIndex) || upToIndex < 1 || upToIndex > entries.length) throw new AgentEventLogError(`forkEventLog: upToIndex must be an integer in [1, ${entries.length}]; got ${String(upToIndex)}.`);
|
|
780
|
-
const forked = entries.slice(0, upToIndex);
|
|
781
|
-
if (!isInitEntry(forked[0])) throw new AgentEventLogError(`forkEventLog: the forked prefix must start with the reserved '${AGENT_INIT_EVENT_TYPE}' entry.`);
|
|
782
|
-
return forked;
|
|
783
|
-
}
|
|
784
|
-
const DONE_ACTOR_EVENT_TYPE = "xstate.done.actor";
|
|
785
|
-
const ERROR_ACTOR_EVENT_TYPE = "xstate.error.actor";
|
|
786
|
-
/** Normalizes a mixed `EventObject | AgentLogEntry` history into bare events. */
|
|
787
|
-
function toEvents(history) {
|
|
788
|
-
if (!history) return [];
|
|
789
|
-
return history.map((item) => {
|
|
790
|
-
const candidate = item;
|
|
791
|
-
return candidate?.schemaVersion === 1 && typeof candidate.index === "number" && candidate.event !== null && typeof candidate.event === "object" ? candidate.event : item;
|
|
792
|
-
});
|
|
793
|
-
}
|
|
794
|
-
/**
|
|
795
|
-
* The 1-based occurrence `n` for the NEXT call at invoke site `siteId`:
|
|
796
|
-
* `1 + completions`, where a completion is a journaled `xstate.done.actor` OR
|
|
797
|
-
* `xstate.error.actor` for that `actorId` (an error is a semantic completion).
|
|
798
|
-
* The one counting rule behind every `${siteId}#${n}` call key, so a live run
|
|
799
|
-
* and a replay derive the same `n` by construction.
|
|
800
|
-
*/
|
|
801
|
-
function agentCallOccurrence(history, siteId) {
|
|
802
|
-
let count = 0;
|
|
803
|
-
for (const event of toEvents(history)) if ((event.type === DONE_ACTOR_EVENT_TYPE || event.type === ERROR_ACTOR_EVENT_TYPE) && event.actorId === siteId) count++;
|
|
804
|
-
return count + 1;
|
|
805
|
-
}
|
|
806
|
-
/**
|
|
807
|
-
* Folds the reserved `@agent.usage` entries of a log into one cumulative usage
|
|
808
|
-
* total. The log is the source of truth, so the totals are a pure projection of
|
|
809
|
-
* it: replay a log, fold it, and you get the numbers the run reported with no
|
|
810
|
-
* host-side accumulator in between.
|
|
811
|
-
*
|
|
812
|
-
* Accepts entries or bare events. Token fields are partial sums: a field stays
|
|
813
|
-
* `undefined` until some entry reports it, and non-finite values are ignored.
|
|
814
|
-
*/
|
|
815
|
-
function getUsageFromEvents(entries) {
|
|
816
|
-
const totals = {};
|
|
817
|
-
for (const event of toEvents(entries)) {
|
|
818
|
-
if (event.type !== "@agent.usage") continue;
|
|
819
|
-
const usage = event.usage;
|
|
820
|
-
if (!usage || typeof usage !== "object") continue;
|
|
821
|
-
for (const field of require_decision.AGENT_USAGE_TOKEN_FIELDS) {
|
|
822
|
-
const value = usage[field];
|
|
823
|
-
if (typeof value === "number" && Number.isFinite(value)) totals[field] = (totals[field] ?? 0) + value;
|
|
483
|
+
const withActors = (target, actors) => target.provide({ actors });
|
|
484
|
+
const provided = options.actors ? withActors(machine, options.actors) : machine;
|
|
485
|
+
const effectiveSources = provided.sources.actors;
|
|
486
|
+
const wrappedSources = {};
|
|
487
|
+
const invokedSrcs = require_run_agent.getConfiguredInvokeSrcs(provided);
|
|
488
|
+
for (const [key, logic] of Object.entries(effectiveSources)) {
|
|
489
|
+
if (require_run_agent.isStateMachineLogic(logic)) {
|
|
490
|
+
if (invokedSrcs.has(key)) assertChildBindable(logic, executors, key, new Set([provided]));
|
|
491
|
+
const rebound = require_run_agent.bindChildMachineForProvide(logic, executors, bindOptions, new Set([provided]));
|
|
492
|
+
if (rebound !== logic) wrappedSources[key] = rebound;
|
|
493
|
+
continue;
|
|
824
494
|
}
|
|
495
|
+
const requirement = executorRequirementOf(logic);
|
|
496
|
+
if (!requirement) continue;
|
|
497
|
+
const binding = {
|
|
498
|
+
...requirement,
|
|
499
|
+
bind: () => require_decision.isDecisionLogic(logic) ? require_run_agent.bindDecisionForProvide(provided, logic, executors, bindOptions) : require_run_agent.bindTextForProvide(provided, logic, executors, bindOptions)
|
|
500
|
+
};
|
|
501
|
+
if (require_text_logic.executorBoundLogics.has(logic)) continue;
|
|
502
|
+
if (!executors[binding.executorKey]) {
|
|
503
|
+
if (invokedSrcs.has(key)) throw missingExecutorError(key, binding.kind, binding.executorKey);
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
wrappedSources[key] = binding.bind();
|
|
825
507
|
}
|
|
826
|
-
return
|
|
827
|
-
}
|
|
828
|
-
/**
|
|
829
|
-
* Rewrites a journaled `xstate.done.actor`/`xstate.error.actor` event's
|
|
830
|
-
* `sessionId` to the session the CURRENT fold minted for that `actorId`.
|
|
831
|
-
*
|
|
832
|
-
* Session ids are re-minted on every fold, and XState drops a completion event
|
|
833
|
-
* whose `sessionId` does not match the live child — silently, as a no-op
|
|
834
|
-
* transition. Rebinding is what makes a journaled completion replayable at all.
|
|
835
|
-
* `sessions` caches the mapping per fold, so a later event referring to the
|
|
836
|
-
* recorded session of an already-stopped child still resolves.
|
|
837
|
-
*/
|
|
838
|
-
function rebindActorSession(event, snapshot, sessions) {
|
|
839
|
-
const actorEvent = event;
|
|
840
|
-
if (typeof actorEvent.actorId !== "string" || typeof actorEvent.sessionId !== "string") return event;
|
|
841
|
-
const key = `${actorEvent.actorId}\0${actorEvent.sessionId}`;
|
|
842
|
-
let sessionId = sessions.get(key);
|
|
843
|
-
if (!sessionId) {
|
|
844
|
-
const child = snapshot.children[actorEvent.actorId];
|
|
845
|
-
if (typeof child?.sessionId !== "string") return event;
|
|
846
|
-
sessionId = child.sessionId;
|
|
847
|
-
sessions.set(key, sessionId);
|
|
848
|
-
}
|
|
849
|
-
return {
|
|
850
|
-
...event,
|
|
851
|
-
sessionId
|
|
852
|
-
};
|
|
853
|
-
}
|
|
854
|
-
function verifyEntry(entry, persisted, mode) {
|
|
855
|
-
if (mode === false) return;
|
|
856
|
-
if (!entry.verification) {
|
|
857
|
-
if (mode === "strict") throw new AgentReplayDivergenceError(entry.id, entry.index, "missing-verification");
|
|
858
|
-
return;
|
|
859
|
-
}
|
|
860
|
-
const actual = getSnapshotStateHash(persisted);
|
|
861
|
-
if (actual !== entry.verification.stateHash) throw new AgentReplayDivergenceError(entry.id, entry.index, "state", entry.verification.stateHash, actual);
|
|
862
|
-
}
|
|
863
|
-
/**
|
|
864
|
-
* Folds a log through XState's pure `initialTransition`/`transition` WITHOUT
|
|
865
|
-
* executing anything: no actor is started, no action runs, no model is called.
|
|
866
|
-
* A journaled `xstate.done.actor` entry carries the completed actor's output
|
|
867
|
-
* inline, so the invoke it belongs to is never re-run. Crash recovery, fork
|
|
868
|
-
* resume, and time travel in one call.
|
|
869
|
-
*
|
|
870
|
-
* The first entry is the reserved {@link initEntry}: `input` folds from
|
|
871
|
-
* `initialTransition`, `snapshot` restores the persisted snapshot purely
|
|
872
|
-
* (`machine.restoreSnapshot`) and folds the rest of the log onto it.
|
|
873
|
-
*/
|
|
874
|
-
function replay(machine, entries, options = {}) {
|
|
875
|
-
validateReplayEntries(entries, {
|
|
876
|
-
machine,
|
|
877
|
-
machineVersion: options.machineVersion ?? require_decision.resolveMachineVersion(machine)
|
|
878
|
-
});
|
|
879
|
-
if (entries.length === 0) throw new AgentEventLogError(`Cannot replay an empty event log; it must start with a '${AGENT_INIT_EVENT_TYPE}' entry.`);
|
|
880
|
-
const initEvent = entries[0].event;
|
|
881
|
-
let snapshot = initEvent.snapshot !== void 0 ? machine.restoreSnapshot(initEvent.snapshot) : (0, xstate.initialTransition)(machine, initEvent.input)[0];
|
|
882
|
-
let persisted = machine.getPersistedSnapshot(snapshot);
|
|
883
|
-
verifyEntry(entries[0], persisted, options.verify);
|
|
884
|
-
const sessions = /* @__PURE__ */ new Map();
|
|
885
|
-
for (let index = 1; index < entries.length; index++) {
|
|
886
|
-
const entry = entries[index];
|
|
887
|
-
const event = rebindActorSession(entry.event, snapshot, sessions);
|
|
888
|
-
[snapshot] = (0, xstate.transition)(machine, snapshot, event);
|
|
889
|
-
persisted = machine.getPersistedSnapshot(snapshot);
|
|
890
|
-
verifyEntry(entry, persisted, options.verify);
|
|
891
|
-
}
|
|
892
|
-
return {
|
|
893
|
-
snapshot,
|
|
894
|
-
persistedSnapshot: persisted
|
|
895
|
-
};
|
|
508
|
+
return withActors(provided, wrappedSources);
|
|
896
509
|
}
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
* suite is written against.
|
|
903
|
-
* @module
|
|
904
|
-
*/
|
|
905
|
-
/**
|
|
906
|
-
* Rejection from {@link AgentEventLogStore.append} when the thread's stored
|
|
907
|
-
* length is not the `expectedIndex` the writer held — a concurrent writer
|
|
908
|
-
* appended first. `actualIndex` is the next index the store would accept.
|
|
909
|
-
*/
|
|
910
|
-
var AgentEventLogConflictError = class extends require_decision.AgentError {
|
|
911
|
-
threadId;
|
|
912
|
-
expectedIndex;
|
|
913
|
-
actualIndex;
|
|
914
|
-
constructor(threadId, expectedIndex, actualIndex) {
|
|
915
|
-
super("event-log-conflict", `AgentEventLogStore.append: index conflict on thread "${threadId}": expected index ${expectedIndex} but found ${actualIndex} — a concurrent writer won.`);
|
|
916
|
-
this.name = "AgentEventLogConflictError";
|
|
917
|
-
this.threadId = threadId;
|
|
918
|
-
this.expectedIndex = expectedIndex;
|
|
919
|
-
this.actualIndex = actualIndex;
|
|
920
|
-
}
|
|
921
|
-
};
|
|
922
|
-
/**
|
|
923
|
-
* In-memory reference store, and the baseline the conformance suite runs
|
|
924
|
-
* against. Per-thread entries are held in a contiguous, index-ordered list;
|
|
925
|
-
* append's check-and-push runs synchronously (no `await` between reading the
|
|
926
|
-
* length and writing), so two appends racing on the same `expectedIndex`
|
|
927
|
-
* resolve to exactly one winner. Every stored and returned entry is
|
|
928
|
-
* `structuredClone`d, so a caller can neither mutate stored state through a
|
|
929
|
-
* value it appended nor through a value it read.
|
|
930
|
-
*/
|
|
931
|
-
function createInMemoryEventLogStore() {
|
|
932
|
-
const threads = /* @__PURE__ */ new Map();
|
|
933
|
-
const clone = (value) => structuredClone(value);
|
|
934
|
-
return {
|
|
935
|
-
async append({ threadId, expectedIndex, entries }) {
|
|
936
|
-
for (const entry of entries) assertAgentLogEntry(entry);
|
|
937
|
-
for (let i = 0; i < entries.length; i++) if (entries[i].index !== expectedIndex + i) throw new Error(`AgentEventLogStore.append: entry.index (${entries[i].index}) must be contiguous from expectedIndex (${expectedIndex}); expected ${expectedIndex + i} at position ${i} on thread "${threadId}".`);
|
|
938
|
-
const list = threads.get(threadId);
|
|
939
|
-
const length = list ? list.length : 0;
|
|
940
|
-
if (length !== expectedIndex) throw new AgentEventLogConflictError(threadId, expectedIndex, length);
|
|
941
|
-
const ids = new Set((list ?? []).map((entry) => entry.id));
|
|
942
|
-
for (const entry of entries) {
|
|
943
|
-
if (ids.has(entry.id)) throw new Error(`AgentEventLogStore.append: duplicate event id "${entry.id}" in thread "${threadId}".`);
|
|
944
|
-
ids.add(entry.id);
|
|
945
|
-
}
|
|
946
|
-
const cloned = entries.map((entry) => clone(entry));
|
|
947
|
-
if (list) for (const entry of cloned) list.push(entry);
|
|
948
|
-
else threads.set(threadId, cloned);
|
|
949
|
-
},
|
|
950
|
-
async read(threadId, options) {
|
|
951
|
-
const list = threads.get(threadId) ?? [];
|
|
952
|
-
const from = options?.from ?? 0;
|
|
953
|
-
return list.slice(from).map((entry) => clone(entry));
|
|
954
|
-
},
|
|
955
|
-
async length(threadId) {
|
|
956
|
-
return threads.get(threadId)?.length ?? 0;
|
|
957
|
-
},
|
|
958
|
-
async fork({ threadId, newThreadId, upToIndex }) {
|
|
959
|
-
if ((threads.get(newThreadId)?.length ?? 0) > 0) throw new Error(`AgentEventLogStore.fork: newThreadId "${newThreadId}" already has entries.`);
|
|
960
|
-
const source = threads.get(threadId);
|
|
961
|
-
if (!source) throw new Error(`AgentEventLogStore.fork: unknown source thread "${threadId}".`);
|
|
962
|
-
const upTo = upToIndex ?? source.length;
|
|
963
|
-
if (!Number.isInteger(upTo) || upTo < 1 || upTo > source.length) throw new Error(`AgentEventLogStore.fork: upToIndex must be an integer in [1, ${source.length}] on thread "${threadId}"; got ${String(upTo)}.`);
|
|
964
|
-
threads.set(newThreadId, source.slice(0, upTo).map((entry) => clone(entry)));
|
|
965
|
-
}
|
|
510
|
+
/** The executor slot + label an agent logic needs, or `undefined` for a non-agent actor. */
|
|
511
|
+
function executorRequirementOf(logic) {
|
|
512
|
+
if (require_decision.isDecisionLogic(logic)) return {
|
|
513
|
+
executorKey: "decide",
|
|
514
|
+
kind: "decision"
|
|
966
515
|
};
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
* the reference implementation stay readable.
|
|
974
|
-
*
|
|
975
|
-
* Every case throws a descriptive `Error` on the first violation, so any test
|
|
976
|
-
* runner (or a plain script) can drive it against its own store. Pass a
|
|
977
|
-
* `{ describe, it }` harness to register one test per case instead.
|
|
978
|
-
* @module
|
|
979
|
-
*/
|
|
980
|
-
function fail(message) {
|
|
981
|
-
throw new Error(`event-log-store conformance: ${message}`);
|
|
982
|
-
}
|
|
983
|
-
function entry(index, type, metadata) {
|
|
984
|
-
return {
|
|
985
|
-
schemaVersion: 1,
|
|
986
|
-
id: `evt_${index}`,
|
|
987
|
-
index,
|
|
988
|
-
recordedAt: "2026-01-01T00:00:00.000Z",
|
|
989
|
-
machineId: "conformance",
|
|
990
|
-
machineVersion: "v1",
|
|
991
|
-
event: {
|
|
992
|
-
type,
|
|
993
|
-
seq: index
|
|
994
|
-
},
|
|
995
|
-
...metadata !== void 0 ? { metadata } : {}
|
|
516
|
+
if (require_text_logic.isTextLogic(logic)) return logic.mode === "stream" ? {
|
|
517
|
+
executorKey: "streamText",
|
|
518
|
+
kind: "streaming text"
|
|
519
|
+
} : {
|
|
520
|
+
executorKey: "generateText",
|
|
521
|
+
kind: "text"
|
|
996
522
|
};
|
|
997
523
|
}
|
|
998
|
-
function entriesFrom(start, count) {
|
|
999
|
-
return Array.from({ length: count }, (_unused, i) => entry(start + i, `e${start + i}`));
|
|
1000
|
-
}
|
|
1001
|
-
function assertJsonEqual(actual, expected, message) {
|
|
1002
|
-
const a = JSON.stringify(actual);
|
|
1003
|
-
const b = JSON.stringify(expected);
|
|
1004
|
-
if (a !== b) fail(`${message} (expected ${b}, got ${a})`);
|
|
1005
|
-
}
|
|
1006
|
-
const cases = [
|
|
1007
|
-
{
|
|
1008
|
-
name: "an unknown thread reads empty and has length 0",
|
|
1009
|
-
async run(create) {
|
|
1010
|
-
const store = await create();
|
|
1011
|
-
assertJsonEqual(await store.read("missing"), [], "read of an unknown thread must be empty");
|
|
1012
|
-
if (await store.length("missing") !== 0) fail("length of an unknown thread must be 0");
|
|
1013
|
-
}
|
|
1014
|
-
},
|
|
1015
|
-
{
|
|
1016
|
-
name: "a single append reads back verbatim",
|
|
1017
|
-
async run(create) {
|
|
1018
|
-
const store = await create();
|
|
1019
|
-
const e = entry(0, "start", { label: "first" });
|
|
1020
|
-
await store.append({
|
|
1021
|
-
threadId: "t",
|
|
1022
|
-
expectedIndex: 0,
|
|
1023
|
-
entries: [e]
|
|
1024
|
-
});
|
|
1025
|
-
assertJsonEqual(await store.read("t"), [e], "read must return the appended entry");
|
|
1026
|
-
if (await store.length("t") !== 1) fail("length after a single append must be 1");
|
|
1027
|
-
}
|
|
1028
|
-
},
|
|
1029
|
-
{
|
|
1030
|
-
name: "a second append extends the log in order",
|
|
1031
|
-
async run(create) {
|
|
1032
|
-
const store = await create();
|
|
1033
|
-
await store.append({
|
|
1034
|
-
threadId: "t",
|
|
1035
|
-
expectedIndex: 0,
|
|
1036
|
-
entries: entriesFrom(0, 3)
|
|
1037
|
-
});
|
|
1038
|
-
await store.append({
|
|
1039
|
-
threadId: "t",
|
|
1040
|
-
expectedIndex: 3,
|
|
1041
|
-
entries: entriesFrom(3, 2)
|
|
1042
|
-
});
|
|
1043
|
-
if (await store.length("t") !== 5) fail("length after appending 3 then 2 entries must be 5");
|
|
1044
|
-
assertJsonEqual((await store.read("t")).map((e) => e.index), [
|
|
1045
|
-
0,
|
|
1046
|
-
1,
|
|
1047
|
-
2,
|
|
1048
|
-
3,
|
|
1049
|
-
4
|
|
1050
|
-
], "reads must return entries in contiguous log order");
|
|
1051
|
-
}
|
|
1052
|
-
},
|
|
1053
|
-
{
|
|
1054
|
-
name: "a non-contiguous entry.index is misuse, not a race",
|
|
1055
|
-
async run(create) {
|
|
1056
|
-
const store = await create();
|
|
1057
|
-
let caught;
|
|
1058
|
-
try {
|
|
1059
|
-
await store.append({
|
|
1060
|
-
threadId: "t",
|
|
1061
|
-
expectedIndex: 0,
|
|
1062
|
-
entries: [entry(1, "gap")]
|
|
1063
|
-
});
|
|
1064
|
-
} catch (error) {
|
|
1065
|
-
caught = error;
|
|
1066
|
-
}
|
|
1067
|
-
if (!(caught instanceof Error) || caught instanceof AgentEventLogConflictError) fail("a non-contiguous entry.index must throw a plain Error, not a conflict");
|
|
1068
|
-
}
|
|
1069
|
-
},
|
|
1070
|
-
{
|
|
1071
|
-
name: "a stale expectedIndex conflicts with correct fields",
|
|
1072
|
-
async run(create) {
|
|
1073
|
-
const store = await create();
|
|
1074
|
-
await store.append({
|
|
1075
|
-
threadId: "t",
|
|
1076
|
-
expectedIndex: 0,
|
|
1077
|
-
entries: [entry(0, "a")]
|
|
1078
|
-
});
|
|
1079
|
-
let caught;
|
|
1080
|
-
try {
|
|
1081
|
-
await store.append({
|
|
1082
|
-
threadId: "t",
|
|
1083
|
-
expectedIndex: 0,
|
|
1084
|
-
entries: [entry(0, "b")]
|
|
1085
|
-
});
|
|
1086
|
-
} catch (error) {
|
|
1087
|
-
caught = error;
|
|
1088
|
-
}
|
|
1089
|
-
if (!(caught instanceof AgentEventLogConflictError)) fail("a stale expectedIndex must throw AgentEventLogConflictError");
|
|
1090
|
-
if (caught.threadId !== "t" || caught.expectedIndex !== 0 || caught.actualIndex !== 1) fail("conflict error must carry threadId, expectedIndex, and the actual index");
|
|
1091
|
-
}
|
|
1092
|
-
},
|
|
1093
|
-
{
|
|
1094
|
-
name: "entry ids are unique within a thread",
|
|
1095
|
-
async run(create) {
|
|
1096
|
-
const store = await create();
|
|
1097
|
-
await store.append({
|
|
1098
|
-
threadId: "t",
|
|
1099
|
-
expectedIndex: 0,
|
|
1100
|
-
entries: [entry(0, "a")]
|
|
1101
|
-
});
|
|
1102
|
-
let caught;
|
|
1103
|
-
try {
|
|
1104
|
-
await store.append({
|
|
1105
|
-
threadId: "t",
|
|
1106
|
-
expectedIndex: 1,
|
|
1107
|
-
entries: [{
|
|
1108
|
-
...entry(1, "b"),
|
|
1109
|
-
id: "evt_0"
|
|
1110
|
-
}]
|
|
1111
|
-
});
|
|
1112
|
-
} catch (error) {
|
|
1113
|
-
caught = error;
|
|
1114
|
-
}
|
|
1115
|
-
if (!(caught instanceof Error) || caught instanceof AgentEventLogConflictError) fail("a duplicate event id within a thread must throw a plain Error");
|
|
1116
|
-
}
|
|
1117
|
-
},
|
|
1118
|
-
{
|
|
1119
|
-
name: "concurrent appends at the same index have exactly one winner",
|
|
1120
|
-
async run(create) {
|
|
1121
|
-
const store = await create();
|
|
1122
|
-
await store.append({
|
|
1123
|
-
threadId: "t",
|
|
1124
|
-
expectedIndex: 0,
|
|
1125
|
-
entries: [entry(0, "a")]
|
|
1126
|
-
});
|
|
1127
|
-
const results = await Promise.allSettled([store.append({
|
|
1128
|
-
threadId: "t",
|
|
1129
|
-
expectedIndex: 1,
|
|
1130
|
-
entries: [entry(1, "w-a")]
|
|
1131
|
-
}), store.append({
|
|
1132
|
-
threadId: "t",
|
|
1133
|
-
expectedIndex: 1,
|
|
1134
|
-
entries: [entry(1, "w-b")]
|
|
1135
|
-
})]);
|
|
1136
|
-
const winners = results.filter((r) => r.status === "fulfilled");
|
|
1137
|
-
const conflicts = results.filter((r) => r.status === "rejected" && r.reason instanceof AgentEventLogConflictError);
|
|
1138
|
-
if (winners.length !== 1) fail(`exactly one racing append must win, got ${winners.length}`);
|
|
1139
|
-
if (conflicts.length !== 1) fail(`exactly one racing append must reject with a conflict, got ${conflicts.length}`);
|
|
1140
|
-
if (await store.length("t") !== 2) fail("after the race the log length must be 2");
|
|
1141
|
-
}
|
|
1142
|
-
},
|
|
1143
|
-
{
|
|
1144
|
-
name: "read({ from }) is incrementally correct",
|
|
1145
|
-
async run(create) {
|
|
1146
|
-
const store = await create();
|
|
1147
|
-
await store.append({
|
|
1148
|
-
threadId: "t",
|
|
1149
|
-
expectedIndex: 0,
|
|
1150
|
-
entries: entriesFrom(0, 5)
|
|
1151
|
-
});
|
|
1152
|
-
assertJsonEqual((await store.read("t", { from: 2 })).map((e) => e.index), [
|
|
1153
|
-
2,
|
|
1154
|
-
3,
|
|
1155
|
-
4
|
|
1156
|
-
], "read({ from }) must skip entries below `from`");
|
|
1157
|
-
assertJsonEqual(await store.read("t", { from: 5 }), [], "read({ from }) at the log length must be empty");
|
|
1158
|
-
}
|
|
1159
|
-
},
|
|
1160
|
-
{
|
|
1161
|
-
name: "threads are isolated and grow independently",
|
|
1162
|
-
async run(create) {
|
|
1163
|
-
const store = await create();
|
|
1164
|
-
await store.append({
|
|
1165
|
-
threadId: "a",
|
|
1166
|
-
expectedIndex: 0,
|
|
1167
|
-
entries: [entry(0, "x")]
|
|
1168
|
-
});
|
|
1169
|
-
if (await store.length("b") !== 0) fail("an append to one thread must not create another");
|
|
1170
|
-
await store.append({
|
|
1171
|
-
threadId: "b",
|
|
1172
|
-
expectedIndex: 0,
|
|
1173
|
-
entries: entriesFrom(0, 2)
|
|
1174
|
-
});
|
|
1175
|
-
if (await store.length("a") !== 1 || await store.length("b") !== 2) fail("threads must grow independently");
|
|
1176
|
-
}
|
|
1177
|
-
},
|
|
1178
|
-
{
|
|
1179
|
-
name: "metadata round-trips verbatim",
|
|
1180
|
-
async run(create) {
|
|
1181
|
-
const store = await create();
|
|
1182
|
-
const metadata = {
|
|
1183
|
-
source: "user",
|
|
1184
|
-
nested: { attempt: 2 },
|
|
1185
|
-
tags: ["x", "y"]
|
|
1186
|
-
};
|
|
1187
|
-
await store.append({
|
|
1188
|
-
threadId: "t",
|
|
1189
|
-
expectedIndex: 0,
|
|
1190
|
-
entries: [entry(0, "e", metadata)]
|
|
1191
|
-
});
|
|
1192
|
-
assertJsonEqual((await store.read("t"))[0].metadata, metadata, "metadata must round-trip verbatim");
|
|
1193
|
-
}
|
|
1194
|
-
},
|
|
1195
|
-
{
|
|
1196
|
-
name: "stored entries are isolated from caller mutation",
|
|
1197
|
-
async run(create) {
|
|
1198
|
-
const store = await create();
|
|
1199
|
-
const e = entry(0, "e", { tags: ["a"] });
|
|
1200
|
-
await store.append({
|
|
1201
|
-
threadId: "t",
|
|
1202
|
-
expectedIndex: 0,
|
|
1203
|
-
entries: [e]
|
|
1204
|
-
});
|
|
1205
|
-
e.metadata.tags.push("mutated-after-append");
|
|
1206
|
-
e.event.seq = 999;
|
|
1207
|
-
const read = await store.read("t");
|
|
1208
|
-
read[0].metadata.tags.push("mutated-after-read");
|
|
1209
|
-
read[0].event.seq = 888;
|
|
1210
|
-
const reread = await store.read("t");
|
|
1211
|
-
assertJsonEqual(reread[0].metadata, { tags: ["a"] }, "stored entry must be isolated from post-append and post-read mutation");
|
|
1212
|
-
assertJsonEqual(reread[0].event, {
|
|
1213
|
-
type: "e",
|
|
1214
|
-
seq: 0
|
|
1215
|
-
}, "stored event must be isolated from mutation of an appended or read copy");
|
|
1216
|
-
}
|
|
1217
|
-
},
|
|
1218
|
-
{
|
|
1219
|
-
name: "fork copies a prefix and then diverges independently",
|
|
1220
|
-
async run(create) {
|
|
1221
|
-
const store = await create();
|
|
1222
|
-
await store.append({
|
|
1223
|
-
threadId: "src",
|
|
1224
|
-
expectedIndex: 0,
|
|
1225
|
-
entries: entriesFrom(0, 3)
|
|
1226
|
-
});
|
|
1227
|
-
await store.fork({
|
|
1228
|
-
threadId: "src",
|
|
1229
|
-
newThreadId: "fork-full"
|
|
1230
|
-
});
|
|
1231
|
-
if (await store.length("fork-full") !== 3) fail("fork with the default upToIndex must copy the full log");
|
|
1232
|
-
await store.fork({
|
|
1233
|
-
threadId: "src",
|
|
1234
|
-
newThreadId: "fork-1",
|
|
1235
|
-
upToIndex: 1
|
|
1236
|
-
});
|
|
1237
|
-
assertJsonEqual((await store.read("fork-1")).map((e) => e.index), [0], "fork with upToIndex 1 must copy only entry 0");
|
|
1238
|
-
await store.fork({
|
|
1239
|
-
threadId: "src",
|
|
1240
|
-
newThreadId: "fork-2",
|
|
1241
|
-
upToIndex: 2
|
|
1242
|
-
});
|
|
1243
|
-
assertJsonEqual((await store.read("fork-2")).map((e) => e.id), ["evt_0", "evt_1"], "fork with upToIndex 2 must copy entries 0 and 1 only");
|
|
1244
|
-
await store.append({
|
|
1245
|
-
threadId: "fork-1",
|
|
1246
|
-
expectedIndex: 1,
|
|
1247
|
-
entries: [entry(1, "branch")]
|
|
1248
|
-
});
|
|
1249
|
-
assertJsonEqual((await store.read("fork-1")).map((e) => e.event.type), ["e0", "branch"], "a forked thread must append independently from its copied length");
|
|
1250
|
-
if (await store.length("src") !== 3) fail("appending to a fork must not touch the source thread");
|
|
1251
|
-
}
|
|
1252
|
-
},
|
|
1253
|
-
{
|
|
1254
|
-
name: "fork rejects a non-empty target, an unknown source, and an out-of-range cutoff (0 included)",
|
|
1255
|
-
async run(create) {
|
|
1256
|
-
const store = await create();
|
|
1257
|
-
await store.append({
|
|
1258
|
-
threadId: "src",
|
|
1259
|
-
expectedIndex: 0,
|
|
1260
|
-
entries: entriesFrom(0, 3)
|
|
1261
|
-
});
|
|
1262
|
-
await store.fork({
|
|
1263
|
-
threadId: "src",
|
|
1264
|
-
newThreadId: "fork-full"
|
|
1265
|
-
});
|
|
1266
|
-
let caughtExisting;
|
|
1267
|
-
try {
|
|
1268
|
-
await store.fork({
|
|
1269
|
-
threadId: "src",
|
|
1270
|
-
newThreadId: "fork-full"
|
|
1271
|
-
});
|
|
1272
|
-
} catch (error) {
|
|
1273
|
-
caughtExisting = error;
|
|
1274
|
-
}
|
|
1275
|
-
if (!(caughtExisting instanceof Error) || caughtExisting instanceof AgentEventLogConflictError) fail("forking onto a non-empty thread must reject with a plain Error");
|
|
1276
|
-
let caughtUnknown;
|
|
1277
|
-
try {
|
|
1278
|
-
await store.fork({
|
|
1279
|
-
threadId: "nope",
|
|
1280
|
-
newThreadId: "fork-nope"
|
|
1281
|
-
});
|
|
1282
|
-
} catch (error) {
|
|
1283
|
-
caughtUnknown = error;
|
|
1284
|
-
}
|
|
1285
|
-
if (!(caughtUnknown instanceof Error) || caughtUnknown instanceof AgentEventLogConflictError) fail("forking an unknown source thread must reject with a plain Error");
|
|
1286
|
-
let caughtRange;
|
|
1287
|
-
try {
|
|
1288
|
-
await store.fork({
|
|
1289
|
-
threadId: "src",
|
|
1290
|
-
newThreadId: "fork-range",
|
|
1291
|
-
upToIndex: 99
|
|
1292
|
-
});
|
|
1293
|
-
} catch (error) {
|
|
1294
|
-
caughtRange = error;
|
|
1295
|
-
}
|
|
1296
|
-
if (!(caughtRange instanceof Error) || caughtRange instanceof AgentEventLogConflictError) fail("forking past the source length must reject with a plain Error");
|
|
1297
|
-
let caughtZero;
|
|
1298
|
-
try {
|
|
1299
|
-
await store.fork({
|
|
1300
|
-
threadId: "src",
|
|
1301
|
-
newThreadId: "fork-zero",
|
|
1302
|
-
upToIndex: 0
|
|
1303
|
-
});
|
|
1304
|
-
} catch (error) {
|
|
1305
|
-
caughtZero = error;
|
|
1306
|
-
}
|
|
1307
|
-
if (!(caughtZero instanceof Error) || caughtZero instanceof AgentEventLogConflictError) fail("forking with upToIndex 0 must reject with a plain Error");
|
|
1308
|
-
if (await store.length("fork-zero") !== 0) fail("a rejected fork must not create the target thread");
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
];
|
|
1312
|
-
/**
|
|
1313
|
-
* Validates a store against the reference's semantics: empty read + zero length
|
|
1314
|
-
* for unknown threads; single and multi-entry append; contiguity misuse guard;
|
|
1315
|
-
* stale-`expectedIndex` conflict with correct fields; an interleaved concurrent
|
|
1316
|
-
* append race (exactly one winner); entry-id uniqueness; `read({ from })`
|
|
1317
|
-
* incremental correctness; thread isolation; metadata round-trip; deep-copy
|
|
1318
|
-
* isolation on append and read; and the full fork contract.
|
|
1319
|
-
*
|
|
1320
|
-
* Without a `harness`, every case runs in sequence and the first violation
|
|
1321
|
-
* throws. With one, each case is registered as its own test:
|
|
1322
|
-
*
|
|
1323
|
-
* ```ts
|
|
1324
|
-
* import { describe, it } from 'vitest';
|
|
1325
|
-
* assertEventLogStoreConformance(createMyStore, { describe, it });
|
|
1326
|
-
* ```
|
|
1327
|
-
*/
|
|
1328
|
-
async function assertEventLogStoreConformance(create, harness) {
|
|
1329
|
-
if (harness) {
|
|
1330
|
-
harness.describe("AgentEventLogStore conformance", () => {
|
|
1331
|
-
for (const testCase of cases) harness.it(testCase.name, () => testCase.run(create));
|
|
1332
|
-
});
|
|
1333
|
-
return;
|
|
1334
|
-
}
|
|
1335
|
-
for (const testCase of cases) await testCase.run(create);
|
|
1336
|
-
}
|
|
1337
|
-
//#endregion
|
|
1338
|
-
//#region src/run-agent.ts
|
|
1339
|
-
/**
|
|
1340
|
-
* Thrown by {@link runAgent} when a resume is given BOTH an `events` log and a
|
|
1341
|
-
* `snapshot` that claims a position in that log (`agentMeta.logIndex`), and the
|
|
1342
|
-
* two disagree about the state at that position. The log is the source of
|
|
1343
|
-
* truth, so this is a host bug (a snapshot cached from a different lineage, or
|
|
1344
|
-
* a snapshot mutated out of band), not a recoverable resume: pass the log
|
|
1345
|
-
* alone, or the matching snapshot.
|
|
1346
|
-
*/
|
|
1347
|
-
var AgentSnapshotDivergedError = class extends require_decision.AgentError {
|
|
1348
|
-
constructor(expected, actual, logIndex) {
|
|
1349
|
-
super("snapshot-diverged", `runAgent: the resume snapshot disagrees with the event log at index ${logIndex}: the log replays to state hash '${expected}', the snapshot hashes '${actual}'.`);
|
|
1350
|
-
this.expected = expected;
|
|
1351
|
-
this.actual = actual;
|
|
1352
|
-
this.logIndex = logIndex;
|
|
1353
|
-
this.name = "AgentSnapshotDivergedError";
|
|
1354
|
-
}
|
|
1355
|
-
};
|
|
1356
|
-
function hashResumeSnapshot(snapshot) {
|
|
1357
|
-
if (snapshot !== null && typeof snapshot === "object" && "agentMeta" in snapshot) {
|
|
1358
|
-
const rest = {};
|
|
1359
|
-
for (const [key, value] of Object.entries(snapshot)) if (key !== "agentMeta") rest[key] = value;
|
|
1360
|
-
return getSnapshotStateHash(rest);
|
|
1361
|
-
}
|
|
1362
|
-
return getSnapshotStateHash(snapshot);
|
|
1363
|
-
}
|
|
1364
|
-
function readAgentMeta(snapshot) {
|
|
1365
|
-
const meta = snapshot?.agentMeta;
|
|
1366
|
-
return meta !== null && typeof meta === "object" ? meta : void 0;
|
|
1367
|
-
}
|
|
1368
|
-
/**
|
|
1369
|
-
* The version of the {@link AgentTraceEvent} envelope every trace event carries
|
|
1370
|
-
* as `schemaVersion`. Bumped only on a breaking change to the envelope or any
|
|
1371
|
-
* payload shape, so a consumer can gate on it. Emitted identically by
|
|
1372
|
-
* {@link runAgent}, {@link provideExecutors}' `onTrace`, and
|
|
1373
|
-
* {@link traceTransitions}.
|
|
1374
|
-
*/
|
|
1375
|
-
const AGENT_TRACE_SCHEMA_VERSION = 1;
|
|
1376
|
-
const TRACE_ENVELOPE_KEYS = [
|
|
1377
|
-
"schemaVersion",
|
|
1378
|
-
"runId",
|
|
1379
|
-
"seq",
|
|
1380
|
-
"timestamp",
|
|
1381
|
-
"machineId",
|
|
1382
|
-
"machineVersion",
|
|
1383
|
-
"type",
|
|
1384
|
-
"status",
|
|
1385
|
-
"cause",
|
|
1386
|
-
"reasoning",
|
|
1387
|
-
"finishReason",
|
|
1388
|
-
"chunk"
|
|
1389
|
-
];
|
|
1390
|
-
/**
|
|
1391
|
-
* Best-effort JSON projection of an arbitrary value. Never throws: functions,
|
|
1392
|
-
* symbols, `undefined`, and cyclic back-references are DROPPED (array holes
|
|
1393
|
-
* become `null`), non-finite numbers become `null`, `bigint`s become strings,
|
|
1394
|
-
* `Error`s become `{ name, message, stack?, code? }`, and anything with a
|
|
1395
|
-
* `toJSON()` (e.g. `Date`) is projected through it — the same losses a
|
|
1396
|
-
* `JSON.parse(JSON.stringify(...))` round-trip incurs, minus the throws.
|
|
1397
|
-
*/
|
|
1398
|
-
function toJsonValue(value, ancestors) {
|
|
1399
|
-
if (value === null) return null;
|
|
1400
|
-
const type = typeof value;
|
|
1401
|
-
if (type === "string" || type === "boolean") return value;
|
|
1402
|
-
if (type === "number") return Number.isFinite(value) ? value : null;
|
|
1403
|
-
if (type === "bigint") return value.toString();
|
|
1404
|
-
if (type !== "object") return;
|
|
1405
|
-
const object = value;
|
|
1406
|
-
if (ancestors.includes(object)) return;
|
|
1407
|
-
const nextAncestors = [...ancestors, object];
|
|
1408
|
-
if (object instanceof Error) {
|
|
1409
|
-
const serialized = {
|
|
1410
|
-
name: object.name,
|
|
1411
|
-
message: object.message
|
|
1412
|
-
};
|
|
1413
|
-
if (typeof object.stack === "string") serialized.stack = object.stack;
|
|
1414
|
-
const code = object.code;
|
|
1415
|
-
if (typeof code === "string") serialized.code = code;
|
|
1416
|
-
const cause = toJsonValue(object.cause, nextAncestors);
|
|
1417
|
-
if (cause !== void 0) serialized.cause = cause;
|
|
1418
|
-
return serialized;
|
|
1419
|
-
}
|
|
1420
|
-
const toJSON = object.toJSON;
|
|
1421
|
-
if (typeof toJSON === "function") return toJsonValue(toJSON.call(object), nextAncestors);
|
|
1422
|
-
if (Array.isArray(object)) return object.map((item) => toJsonValue(item, nextAncestors) ?? null);
|
|
1423
|
-
const out = {};
|
|
1424
|
-
for (const [key, item] of Object.entries(object)) {
|
|
1425
|
-
const serializedItem = toJsonValue(item, nextAncestors);
|
|
1426
|
-
if (serializedItem !== void 0) out[key] = serializedItem;
|
|
1427
|
-
}
|
|
1428
|
-
return out;
|
|
1429
|
-
}
|
|
1430
524
|
/**
|
|
1431
|
-
*
|
|
1432
|
-
*
|
|
1433
|
-
*
|
|
1434
|
-
*
|
|
1435
|
-
* - Snapshots (`run.start`, `machine.transition`, `run.end`) go through the
|
|
1436
|
-
* same JSON round-trip as `machine.getPersistedSnapshot(...)`, so what lands on disk is
|
|
1437
|
-
* what a resume would see.
|
|
1438
|
-
* - `request.end`'s `raw` (a provider SDK object, frequently cyclic) is DROPPED
|
|
1439
|
-
* unless `includeRaw` is set, in which case it is sanitized like everything
|
|
1440
|
-
* else.
|
|
1441
|
-
* - Non-serializable values anywhere (functions, symbols, `undefined`, cyclic
|
|
1442
|
-
* back-references) are dropped; `Error`s become `{ name, message, stack?,
|
|
1443
|
-
* code? }` instead of `{}`. Nothing throws.
|
|
1444
|
-
*
|
|
1445
|
-
* @example
|
|
1446
|
-
* ```ts
|
|
1447
|
-
* await appendFile('trace.jsonl', JSON.stringify(serializeTraceEvent(event)) + '\n');
|
|
1448
|
-
* ```
|
|
525
|
+
* Walks an invoked child machine's own invoked sources (recursively, at any
|
|
526
|
+
* depth) and throws the same missing-executor error `provideExecutors` throws
|
|
527
|
+
* for the top-level machine — before any actor starts. Mirrors runAgent's
|
|
528
|
+
* `assertBindable` for the uncontrolled path. Cycle-safe via `visited`.
|
|
1449
529
|
*/
|
|
1450
|
-
function
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
530
|
+
function assertChildBindable(childMachine, executors, path, visited) {
|
|
531
|
+
if (visited.has(childMachine)) return;
|
|
532
|
+
const nextVisited = new Set([...visited, childMachine]);
|
|
533
|
+
const sources = childMachine.sources.actors;
|
|
534
|
+
for (const src of require_run_agent.getConfiguredInvokeSrcs(childMachine)) {
|
|
535
|
+
const logic = sources[src];
|
|
536
|
+
if (!logic) continue;
|
|
537
|
+
if (require_run_agent.isStateMachineLogic(logic)) {
|
|
538
|
+
assertChildBindable(logic, executors, `${path} > ${src}`, nextVisited);
|
|
1456
539
|
continue;
|
|
1457
540
|
}
|
|
1458
|
-
const
|
|
1459
|
-
if (
|
|
1460
|
-
|
|
1461
|
-
return out;
|
|
1462
|
-
}
|
|
1463
|
-
let nextRunAgentTraceId = 1;
|
|
1464
|
-
/**
|
|
1465
|
-
* Thrown into the invoke that would have made the call once
|
|
1466
|
-
* {@link RunAgentOptions.maxModelCalls} is spent. It reaches the machine
|
|
1467
|
-
* through the normal error channel, so an invoke's `onError` can branch on it
|
|
1468
|
-
* (`error.code === 'max-model-calls'`, the same string the settled result's
|
|
1469
|
-
* `cause` uses) and route to a degraded/finish state instead of failing the
|
|
1470
|
-
* run. Unhandled, it settles `{ status: 'error', cause: 'max-model-calls' }`.
|
|
1471
|
-
*
|
|
1472
|
-
* ```ts
|
|
1473
|
-
* onError: [
|
|
1474
|
-
* { guard: ({ event }) => event.error?.code === 'max-model-calls', target: 'budgetSpent' },
|
|
1475
|
-
* { target: 'failed' },
|
|
1476
|
-
* ]
|
|
1477
|
-
* ```
|
|
1478
|
-
*/
|
|
1479
|
-
var AgentMaxModelCallsExceededError = class extends require_decision.AgentError {
|
|
1480
|
-
/** The budget that was exceeded (`options.maxModelCalls`). */
|
|
1481
|
-
maxModelCalls;
|
|
1482
|
-
constructor(maxModelCalls) {
|
|
1483
|
-
super("max-model-calls", `runAgent exceeded maxModelCalls (${maxModelCalls}). Raise the budget, or handle it in the invoke's onError (error.code === 'max-model-calls').`);
|
|
1484
|
-
this.name = "AgentMaxModelCallsExceededError";
|
|
1485
|
-
this.maxModelCalls = maxModelCalls;
|
|
1486
|
-
}
|
|
1487
|
-
};
|
|
1488
|
-
function wrapsDecisionExhausted(error) {
|
|
1489
|
-
let current = error;
|
|
1490
|
-
for (let depth = 0; depth < 10 && current != null; depth++) {
|
|
1491
|
-
if (current instanceof require_decision.AgentDecisionExhaustedError) return true;
|
|
1492
|
-
current = current.cause;
|
|
1493
|
-
}
|
|
1494
|
-
return false;
|
|
1495
|
-
}
|
|
1496
|
-
/**
|
|
1497
|
-
* Recursively collects every invoke's `src` from raw machine config (spike
|
|
1498
|
-
* S6: `machine.config` preserves authored srcs; the built `machine.root`
|
|
1499
|
-
* normalizes object srcs to synthetic string ids and loses the distinction
|
|
1500
|
-
* this walk needs). Function-valued `src` resolvers are dynamic and are not
|
|
1501
|
-
* statically analyzable, so they are skipped (pass-through, like any other
|
|
1502
|
-
* non-agent actor).
|
|
1503
|
-
*/
|
|
1504
|
-
function collectConfiguredInvokeSrcs(stateConfig, stateName, out) {
|
|
1505
|
-
if (!stateConfig) return;
|
|
1506
|
-
const invokes = stateConfig.invoke === void 0 ? [] : Array.isArray(stateConfig.invoke) ? stateConfig.invoke : [stateConfig.invoke];
|
|
1507
|
-
for (const invokeConfig of invokes) {
|
|
1508
|
-
const src = invokeConfig?.src;
|
|
1509
|
-
if (typeof src === "string" || src && typeof src === "object") out.push({
|
|
1510
|
-
stateName,
|
|
1511
|
-
src
|
|
1512
|
-
});
|
|
541
|
+
const requirement = executorRequirementOf(logic);
|
|
542
|
+
if (!requirement || require_text_logic.executorBoundLogics.has(logic)) continue;
|
|
543
|
+
if (!executors[requirement.executorKey]) throw missingExecutorError(`${path} > ${src}`, requirement.kind, requirement.executorKey);
|
|
1513
544
|
}
|
|
1514
|
-
for (const [childName, childConfig] of Object.entries(stateConfig.states ?? {})) collectConfiguredInvokeSrcs(childConfig, `${stateName}.${childName}`, out);
|
|
1515
545
|
}
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
* other actor logic. xstate's `StateMachine` carries `.config`, `.root`, and
|
|
1519
|
-
* a `.provide(...)` method plus a `sources.actors` map — this
|
|
1520
|
-
* combination is unique to machines and survives the dual-package/version
|
|
1521
|
-
* boundary an `instanceof` check would not. Used to descend the bind-time
|
|
1522
|
-
* walk into invoked child machines (their internal agent requests are opaque
|
|
1523
|
-
* to the parent-level source walk otherwise).
|
|
1524
|
-
*/
|
|
1525
|
-
function isStateMachineLogic(logic) {
|
|
1526
|
-
return !!logic && typeof logic === "object" && "config" in logic && "root" in logic && typeof logic.provide === "function" && typeof logic.sources === "object" && !!logic.sources?.actors;
|
|
1527
|
-
}
|
|
1528
|
-
/**
|
|
1529
|
-
* Fails fast (throws) at bind time — before any actor runs — when the
|
|
1530
|
-
* machine invokes an agent actor `runAgent` cannot execute. See §3.2 point 2.
|
|
1531
|
-
*
|
|
1532
|
-
* Recurses into invoked child state machines (arbitrarily deep). A child
|
|
1533
|
-
* machine's agent requests reached through string-keyed invoke srcs DO inherit
|
|
1534
|
-
* the parent runAgent's `generateText`/`streamText`/`decide` executors —
|
|
1535
|
-
* runAgent rebinds them with the same host-backed wrappers (see
|
|
1536
|
-
* {@link rebindChildMachine}) — so the only remaining bind-time errors are: a
|
|
1537
|
-
* required executor kind missing entirely (naming the invoke chain and src),
|
|
1538
|
-
* and an unbound request reached through a direct-object invoke src that can't
|
|
1539
|
-
* be rebound ({@link unrebindableChildRequestError}). A request that carries
|
|
1540
|
-
* its own executor (`.withExecutor(...)`, tracked in `executorBoundLogics`)
|
|
1541
|
-
* always runs itself; explicit binding shadows inheritance.
|
|
1542
|
-
*/
|
|
1543
|
-
function assertBindable(machine, effectiveSources, executors) {
|
|
1544
|
-
assertMachineBindable(machine, effectiveSources, executors, {
|
|
1545
|
-
isChild: false,
|
|
1546
|
-
childPath: "",
|
|
1547
|
-
rebindable: true,
|
|
1548
|
-
visited: new Set([machine])
|
|
1549
|
-
});
|
|
1550
|
-
}
|
|
1551
|
-
function assertMachineBindable(machine, effectiveSources, executors, ctx) {
|
|
1552
|
-
const invokes = [];
|
|
1553
|
-
collectConfiguredInvokeSrcs(machine.config, machine.config.id ?? "(root)", invokes);
|
|
1554
|
-
const where = ctx.isChild ? `child machine '${ctx.childPath}' state` : "state";
|
|
1555
|
-
for (const { stateName, src } of invokes) {
|
|
1556
|
-
if (typeof src !== "string") {
|
|
1557
|
-
if (isStateMachineLogic(src)) {
|
|
1558
|
-
assertChildMachineBindable(src, src, stateName, executors, ctx);
|
|
1559
|
-
continue;
|
|
1560
|
-
}
|
|
1561
|
-
if ((require_decision.isTextLogic(src) || require_decision.isDecisionLogic(src)) && !require_decision.executorBoundLogics.has(src)) throw new Error(`runAgent: ${where} '${stateName}' invokes a direct-object actor logic (kind: '${src.kind}'). Direct-object invoke srcs cannot be rebound by runAgent — either call '.withExecutor(...)' on the logic before invoking it, or register it as a string-keyed actor source instead (machine.provide({ actors: { name: logic } })) and invoke it by name.`);
|
|
1562
|
-
continue;
|
|
1563
|
-
}
|
|
1564
|
-
const logic = effectiveSources[src];
|
|
1565
|
-
if (logic === void 0) throw new Error(`runAgent: ${where} '${stateName}' invokes unregistered actor source '${src}'. Provide it via machine.provide({ actors: { '${src}': ... } }) or runAgent(machine, { actors: { '${src}': ... } }).`);
|
|
1566
|
-
if (isStateMachineLogic(logic)) {
|
|
1567
|
-
assertChildMachineBindable(logic, src, stateName, executors, ctx);
|
|
1568
|
-
continue;
|
|
1569
|
-
}
|
|
1570
|
-
if (src === "agent.userInput") continue;
|
|
1571
|
-
if (require_decision.isDecisionLogic(logic)) {
|
|
1572
|
-
if (require_decision.executorBoundLogics.has(logic)) continue;
|
|
1573
|
-
if (!ctx.rebindable) throw unrebindableChildRequestError(ctx.childPath, stateName, src, "decision");
|
|
1574
|
-
if (!executors.decide) throw new Error(`runAgent: ${where} '${stateName}' invokes decision source '${src}' but no 'decide' executor was provided to runAgent(...).`);
|
|
1575
|
-
continue;
|
|
1576
|
-
}
|
|
1577
|
-
if (require_decision.isTextLogic(logic)) {
|
|
1578
|
-
if (require_decision.executorBoundLogics.has(logic)) continue;
|
|
1579
|
-
if (!ctx.rebindable) throw unrebindableChildRequestError(ctx.childPath, stateName, src, logic.mode === "stream" ? "streaming text" : "text");
|
|
1580
|
-
if (logic.mode === "stream" && !executors.streamText) throw new Error(`runAgent: ${where} '${stateName}' invokes streaming text source '${src}' but no 'streamText' executor was provided to runAgent(...).`);
|
|
1581
|
-
if (logic.mode !== "stream" && !executors.generateText) throw new Error(`runAgent: ${where} '${stateName}' invokes text source '${src}' but no 'generateText' executor was provided to runAgent(...).`);
|
|
1582
|
-
continue;
|
|
1583
|
-
}
|
|
1584
|
-
if (require_decision.isUnboundPlaceholder(logic)) throw new Error(`runAgent: ${where} '${stateName}' invokes actor source '${src}', which has no host execution. Provide it via machine.provide({ actors: { '${src}': ... } }) or runAgent(machine, { actors: { '${src}': ... } }).`);
|
|
1585
|
-
}
|
|
1586
|
-
}
|
|
1587
|
-
/** Descends the bind-time walk into an invoked child state machine, guarding
|
|
1588
|
-
* against a machine that (transitively) invokes itself. */
|
|
1589
|
-
function assertChildMachineBindable(childMachine, childSrc, stateName, executors, ctx) {
|
|
1590
|
-
if (ctx.visited.has(childMachine)) return;
|
|
1591
|
-
const childName = typeof childSrc === "string" ? childSrc : childMachine.config.id ?? "(child machine)";
|
|
1592
|
-
const childPath = ctx.childPath ? `${ctx.childPath} > ${childName}` : childName;
|
|
1593
|
-
const childSources = childMachine.sources.actors;
|
|
1594
|
-
assertMachineBindable(childMachine, childSources, executors, {
|
|
1595
|
-
isChild: true,
|
|
1596
|
-
childPath,
|
|
1597
|
-
rebindable: ctx.rebindable && typeof childSrc === "string",
|
|
1598
|
-
visited: new Set([...ctx.visited, childMachine])
|
|
1599
|
-
});
|
|
1600
|
-
}
|
|
1601
|
-
/** The loud bind-time error for an unbound agent request reached under a
|
|
1602
|
-
* direct-object invoke src, which runAgent cannot rebind (only string-keyed
|
|
1603
|
-
* sources can be swapped via `.provide`). Names the invoke chain AND the
|
|
1604
|
-
* request src, and spells out the `.withExecutor`/string-keyed remedy. Note:
|
|
1605
|
-
* requests reachable through string-keyed srcs at any depth DO inherit
|
|
1606
|
-
* runAgent's executors — this error is only for the unrebindable direct-object
|
|
1607
|
-
* case. */
|
|
1608
|
-
function unrebindableChildRequestError(childPath, stateName, requestSrc, kind) {
|
|
1609
|
-
return /* @__PURE__ */ new Error(`runAgent: child machine '${childPath}' (state '${stateName}') invokes ${kind} source '${requestSrc}', which has no host execution and is reached through a direct-object invoke src that runAgent cannot rebind. Requests reached through string-keyed actor sources inherit runAgent's generateText/streamText/decide executors automatically; a direct-object child machine does not. Either bind the request with its own executor (requestLogic.withExecutor(...)), or register the child as a string-keyed actor source (machine.provide({ actors: { <child>: childMachine } })) and invoke it by name.`);
|
|
1610
|
-
}
|
|
1611
|
-
/**
|
|
1612
|
-
* True when the snapshot's active states declare a transition that would
|
|
1613
|
-
* receive the reserved `'@agent.usage'` type — an explicit `on: { '@agent.usage'
|
|
1614
|
-
* }` OR a catch-all `on: { '*': … }`. Plain XState semantics apply unmodified:
|
|
1615
|
-
* a wildcard matches every event delivered to the machine, reserved ones
|
|
1616
|
-
* included. (The MODEL-facing side stays closed: `getAcceptedEvents` drops
|
|
1617
|
-
* `@agent.*` before any `allowedEvents` matching, so a wildcard never offers
|
|
1618
|
-
* the reserved event as a decision candidate.) @internal
|
|
1619
|
-
*/
|
|
1620
|
-
function declaresUsageTransition(snapshot) {
|
|
1621
|
-
return (0, xstate.getNextTransitions)(snapshot).some((transition) => transition.eventType === "@agent.usage" || transition.eventType === "*");
|
|
1622
|
-
}
|
|
1623
|
-
/**
|
|
1624
|
-
* Builds the ONE place a trace payload is emitted: it hands the payload to the
|
|
1625
|
-
* envelope-stamping trace sink and, from that same payload, invokes the sugar
|
|
1626
|
-
* callbacks that are projections of it — {@link RunAgentOptions.onChunk},
|
|
1627
|
-
* {@link RunAgentOptions.onResult}, {@link RunAgentOptions.onTransition}. Each
|
|
1628
|
-
* keeps its historical position relative to the trace: `onResult` fires just
|
|
1629
|
-
* BEFORE its `request.end`, `onChunk`/`onTransition` just AFTER their
|
|
1630
|
-
* `stream.chunk`/`machine.transition`. Sugar dispatch never depends on whether
|
|
1631
|
-
* a trace sink is present, and the trace sink is never called when it is
|
|
1632
|
-
* absent — so an `onTrace`-less run still mints no envelope (and advances no
|
|
1633
|
-
* `seq`). @internal
|
|
1634
|
-
*/
|
|
1635
|
-
function createTraceDispatch(sinks) {
|
|
1636
|
-
return (payload, self) => {
|
|
1637
|
-
switch (payload.type) {
|
|
1638
|
-
case "stream.chunk":
|
|
1639
|
-
sinks.onTrace?.(payload, self);
|
|
1640
|
-
sinks.onChunk?.(payload.chunk, { request: payload.request });
|
|
1641
|
-
return;
|
|
1642
|
-
case "request.end":
|
|
1643
|
-
sinks.onResult?.(payload.request, {
|
|
1644
|
-
output: payload.output,
|
|
1645
|
-
raw: payload.raw
|
|
1646
|
-
});
|
|
1647
|
-
sinks.onTrace?.(payload, self);
|
|
1648
|
-
return;
|
|
1649
|
-
case "machine.transition":
|
|
1650
|
-
sinks.onTrace?.(payload, self);
|
|
1651
|
-
sinks.onTransition?.(payload.snapshot, payload.event);
|
|
1652
|
-
return;
|
|
1653
|
-
default: sinks.onTrace?.(payload, self);
|
|
1654
|
-
}
|
|
1655
|
-
};
|
|
1656
|
-
}
|
|
1657
|
-
/** Reads the durable invoke id/src off the async actor's own ref (`self`). */
|
|
1658
|
-
function selfIdAndSrc(self) {
|
|
1659
|
-
return {
|
|
1660
|
-
id: typeof self?.id === "string" ? self.id : "",
|
|
1661
|
-
src: typeof self?.src === "string" ? self.src : ""
|
|
1662
|
-
};
|
|
1663
|
-
}
|
|
1664
|
-
/**
|
|
1665
|
-
* The machine actor that INVOKED a decision request — the actor whose
|
|
1666
|
-
* live snapshot supplies the candidate events and drives `canTake`/`send`.
|
|
1667
|
-
* For a top-level request this is the root actor (identity-equal to
|
|
1668
|
-
* `runCtx.actorHolder.actorRef`); for a request inside an invoked child
|
|
1669
|
-
* machine it is that child's actor, so a child decision reads and drives
|
|
1670
|
-
* the CHILD's snapshot — not the root's. Read off `self._parent`, with the
|
|
1671
|
-
* root actor as a fallback.
|
|
1672
|
-
*/
|
|
1673
|
-
function invokingActorOf(self, runCtx) {
|
|
1674
|
-
return self?._parent ?? runCtx.actorHolder.actorRef;
|
|
1675
|
-
}
|
|
1676
|
-
/**
|
|
1677
|
-
* The shared text/stream emission helper: binds a {@link TextLogic} to
|
|
1678
|
-
* `runCtx`'s executor and constructs the `request.start` / `stream.chunk` /
|
|
1679
|
-
* `request.end` (incl. the lifted `reasoning`) / `request.error` trace payloads.
|
|
1680
|
-
* Used by both `runAgent` and `provideExecutors` so the two paths produce
|
|
1681
|
-
* identical event shapes by construction. @internal
|
|
1682
|
-
*/
|
|
1683
|
-
function bindTextLogic(logic, runCtx) {
|
|
1684
|
-
return logic.withExecutor(async ({ request: rawRequest, self: selfArg, signal }) => {
|
|
1685
|
-
const self = selfArg;
|
|
1686
|
-
const { id, src } = selfIdAndSrc(self);
|
|
1687
|
-
const request = {
|
|
1688
|
-
...rawRequest,
|
|
1689
|
-
name: rawRequest.name ?? (src !== "" && src !== "agent.generateText" && src !== "agent.streamText" ? src : id || src)
|
|
1690
|
-
};
|
|
1691
|
-
const executor = logic.mode === "stream" ? runCtx.streamText : runCtx.generateText;
|
|
1692
|
-
if (!executor) throw new Error(`No '${logic.mode === "stream" ? "streamText" : "generateText"}' executor provided.`);
|
|
1693
|
-
const requestWithTools = {
|
|
1694
|
-
...request,
|
|
1695
|
-
tools: request.tools ?? {}
|
|
1696
|
-
};
|
|
1697
|
-
const agentRequest = {
|
|
1698
|
-
kind: "text",
|
|
1699
|
-
id,
|
|
1700
|
-
src,
|
|
1701
|
-
mode: logic.mode,
|
|
1702
|
-
input: request,
|
|
1703
|
-
tools: requestWithTools.tools,
|
|
1704
|
-
events: []
|
|
1705
|
-
};
|
|
1706
|
-
runCtx.consumeModelCall();
|
|
1707
|
-
runCtx.emitTrace?.({
|
|
1708
|
-
type: "request.start",
|
|
1709
|
-
request: agentRequest
|
|
1710
|
-
}, self);
|
|
1711
|
-
try {
|
|
1712
|
-
const callKey = id !== "" ? runCtx.callKey?.(id, self) : void 0;
|
|
1713
|
-
await runCtx.awaitJournal?.();
|
|
1714
|
-
const raw = await executor(requestWithTools, {
|
|
1715
|
-
onChunk: (chunk) => {
|
|
1716
|
-
runCtx.emitTrace?.({
|
|
1717
|
-
type: "stream.chunk",
|
|
1718
|
-
request: agentRequest,
|
|
1719
|
-
chunk
|
|
1720
|
-
}, self);
|
|
1721
|
-
},
|
|
1722
|
-
signal,
|
|
1723
|
-
...runCtx.runId !== void 0 ? { runId: runCtx.runId } : {},
|
|
1724
|
-
...id !== "" ? { requestId: id } : {},
|
|
1725
|
-
...callKey !== void 0 ? { callKey } : {}
|
|
1726
|
-
});
|
|
1727
|
-
const output = await require_decision.normalizeGeneratorResult(raw, id, {
|
|
1728
|
-
request,
|
|
1729
|
-
onChunk: (chunk) => {
|
|
1730
|
-
runCtx.emitTrace?.({
|
|
1731
|
-
type: "stream.chunk",
|
|
1732
|
-
request: agentRequest,
|
|
1733
|
-
chunk
|
|
1734
|
-
}, self);
|
|
1735
|
-
}
|
|
1736
|
-
});
|
|
1737
|
-
const responseMessages = raw?.messages;
|
|
1738
|
-
if (Array.isArray(responseMessages) && responseMessages.length > 0) {
|
|
1739
|
-
const parent = invokingActorOf(self, runCtx);
|
|
1740
|
-
const event = {
|
|
1741
|
-
type: require_decision.AGENT_MESSAGES_EVENT_TYPE,
|
|
1742
|
-
request: request.name,
|
|
1743
|
-
actorId: id,
|
|
1744
|
-
messages: responseMessages
|
|
1745
|
-
};
|
|
1746
|
-
if ((parent?.getSnapshot())?.can(event)) parent?.send(event);
|
|
1747
|
-
}
|
|
1748
|
-
const rawReasoning = raw?.reasoning;
|
|
1749
|
-
const reasoning = typeof rawReasoning === "string" ? rawReasoning : void 0;
|
|
1750
|
-
const usage = require_decision.getCallUsage(raw);
|
|
1751
|
-
const finishReason = require_decision.getCallFinishReason(raw);
|
|
1752
|
-
if (usage) runCtx.recordUsage?.(usage, {
|
|
1753
|
-
kind: "text",
|
|
1754
|
-
...id !== "" ? { id } : {},
|
|
1755
|
-
...src !== "" ? { src } : {},
|
|
1756
|
-
model: request.model,
|
|
1757
|
-
...request.name !== void 0 ? { name: request.name } : {}
|
|
1758
|
-
}, self);
|
|
1759
|
-
runCtx.emitTrace?.({
|
|
1760
|
-
type: "request.end",
|
|
1761
|
-
request: agentRequest,
|
|
1762
|
-
output,
|
|
1763
|
-
raw,
|
|
1764
|
-
...reasoning !== void 0 ? { reasoning } : {},
|
|
1765
|
-
...usage !== void 0 ? { usage } : {},
|
|
1766
|
-
...finishReason !== void 0 ? { finishReason } : {}
|
|
1767
|
-
}, self);
|
|
1768
|
-
return { output };
|
|
1769
|
-
} catch (error) {
|
|
1770
|
-
runCtx.emitTrace?.({
|
|
1771
|
-
type: "request.error",
|
|
1772
|
-
request: agentRequest,
|
|
1773
|
-
error
|
|
1774
|
-
}, self);
|
|
1775
|
-
throw error;
|
|
1776
|
-
}
|
|
1777
|
-
});
|
|
1778
|
-
}
|
|
1779
|
-
function createCountingDecide(runCtx, self) {
|
|
1780
|
-
return async (attemptRequest, info) => {
|
|
1781
|
-
runCtx.consumeModelCall();
|
|
1782
|
-
runCtx.emitTrace?.({
|
|
1783
|
-
type: "request.start",
|
|
1784
|
-
request: attemptRequest
|
|
1785
|
-
}, self);
|
|
1786
|
-
try {
|
|
1787
|
-
const { id } = selfIdAndSrc(self);
|
|
1788
|
-
const invokeCallKey = id !== "" ? runCtx.callKey?.(id, self) : void 0;
|
|
1789
|
-
const callKey = invokeCallKey === void 0 ? void 0 : `${invokeCallKey}.${attemptRequest.attempts?.length ?? 0}`;
|
|
1790
|
-
await runCtx.awaitJournal?.();
|
|
1791
|
-
const result = await runCtx.decide(runCtx.runId !== void 0 ? {
|
|
1792
|
-
...attemptRequest,
|
|
1793
|
-
runId: runCtx.runId
|
|
1794
|
-
} : attemptRequest, {
|
|
1795
|
-
...info,
|
|
1796
|
-
...runCtx.runId !== void 0 ? { runId: runCtx.runId } : {},
|
|
1797
|
-
...info?.requestId === void 0 && id !== "" ? { requestId: id } : {},
|
|
1798
|
-
...info?.callKey === void 0 && callKey !== void 0 ? { callKey } : {}
|
|
1799
|
-
});
|
|
1800
|
-
const usage = require_decision.getCallUsage(result);
|
|
1801
|
-
const finishReason = require_decision.getCallFinishReason(result);
|
|
1802
|
-
if (usage) {
|
|
1803
|
-
const { src } = selfIdAndSrc(self);
|
|
1804
|
-
runCtx.recordUsage?.(usage, {
|
|
1805
|
-
kind: "decision",
|
|
1806
|
-
...attemptRequest.id ? { id: attemptRequest.id } : {},
|
|
1807
|
-
...src !== "" ? { src } : {},
|
|
1808
|
-
model: attemptRequest.model
|
|
1809
|
-
}, self);
|
|
1810
|
-
}
|
|
1811
|
-
runCtx.emitTrace?.({
|
|
1812
|
-
type: "request.end",
|
|
1813
|
-
request: attemptRequest,
|
|
1814
|
-
output: result.event,
|
|
1815
|
-
raw: result,
|
|
1816
|
-
...usage !== void 0 ? { usage } : {},
|
|
1817
|
-
...finishReason !== void 0 ? { finishReason } : {}
|
|
1818
|
-
}, self);
|
|
1819
|
-
return result;
|
|
1820
|
-
} catch (error) {
|
|
1821
|
-
runCtx.emitTrace?.({
|
|
1822
|
-
type: "request.error",
|
|
1823
|
-
request: attemptRequest,
|
|
1824
|
-
error
|
|
1825
|
-
}, self);
|
|
1826
|
-
throw error;
|
|
1827
|
-
}
|
|
1828
|
-
};
|
|
1829
|
-
}
|
|
1830
|
-
/**
|
|
1831
|
-
* Builds the decision actor logic runAgent installs in place of a
|
|
1832
|
-
* `DecisionLogic`/`agent.decide` source. `DecisionLogic.withExecutor(...)`
|
|
1833
|
-
* can only swap the innermost per-attempt executor — the `resolveDecision(...)`
|
|
1834
|
-
* call (and its `canTake`) is hardwired inside the original logic's `run`.
|
|
1835
|
-
* To supply `canTake` (mode-3, §2.6), runAgent instead builds a fresh async
|
|
1836
|
-
* logic here that calls `resolveDecision` itself, reusing `logic.request(...)`
|
|
1837
|
-
* to build the request the same way the original logic would have.
|
|
1838
|
-
*
|
|
1839
|
-
* On success it SENDS the chosen event to the invoking actor (auto-delivery)
|
|
1840
|
-
* and then completes with that event
|
|
1841
|
-
* as its output — so callers never wire an `onDone` to deliver it. See the
|
|
1842
|
-
* send-then-complete note inside `run` for how exit-cancels-invoke interacts
|
|
1843
|
-
* with `onDone`.
|
|
1844
|
-
*/
|
|
1845
|
-
function bindDecisionLogic(logic, runCtx) {
|
|
1846
|
-
const decisionLogic = (0, xstate.createAsyncLogic)({ run: async ({ input, signal, self: selfArg }) => {
|
|
1847
|
-
if (!runCtx.decide) throw new Error("No 'decide' executor provided.");
|
|
1848
|
-
const self = selfArg;
|
|
1849
|
-
const { id } = selfIdAndSrc(self);
|
|
1850
|
-
const declaredEventTypes = logic.allowedEventTypes?.(input);
|
|
1851
|
-
await Promise.resolve();
|
|
1852
|
-
const actorRef = invokingActorOf(self, runCtx);
|
|
1853
|
-
const events = actorRef ? require_decision.getAcceptedEvents(actorRef.getSnapshot(), {
|
|
1854
|
-
schemas: runCtx.schemas,
|
|
1855
|
-
eventTypes: declaredEventTypes
|
|
1856
|
-
}) : [];
|
|
1857
|
-
const lowered = logic.request(input);
|
|
1858
|
-
const chosen = await require_decision.resolveDecision({
|
|
1859
|
-
...lowered,
|
|
1860
|
-
id,
|
|
1861
|
-
name: lowered.name ?? id,
|
|
1862
|
-
input,
|
|
1863
|
-
events
|
|
1864
|
-
}, { decide: createCountingDecide(runCtx, self) }, {
|
|
1865
|
-
maxRetries: logic.maxRetries,
|
|
1866
|
-
signal,
|
|
1867
|
-
canTake: (event) => actorRef ? actorRef.getSnapshot().can(event) : true
|
|
1868
|
-
});
|
|
1869
|
-
actorRef?.send(chosen);
|
|
1870
|
-
await Promise.resolve();
|
|
1871
|
-
return chosen;
|
|
1872
|
-
} });
|
|
1873
|
-
return Object.assign(decisionLogic, {
|
|
1874
|
-
kind: "statelyai.decisionLogic",
|
|
1875
|
-
maxRetries: logic.maxRetries,
|
|
1876
|
-
request: logic.request,
|
|
1877
|
-
withExecutor: (nextExecute) => bindDecisionLogic(logic.withExecutor(nextExecute), runCtx)
|
|
1878
|
-
});
|
|
1879
|
-
}
|
|
1880
|
-
/**
|
|
1881
|
-
* The set of string-keyed actor `src`s the machine's own config invokes
|
|
1882
|
-
* (top-level, recursing into child STATES but not into invoked child
|
|
1883
|
-
* machines). {@link provideExecutors} uses it to require an executor only for a
|
|
1884
|
-
* source the machine actually invokes — the always-registered `agent.*`
|
|
1885
|
-
* builtins that go unused must not force their executors to be supplied.
|
|
1886
|
-
* @internal
|
|
1887
|
-
*/
|
|
1888
|
-
function getConfiguredInvokeSrcs(machine) {
|
|
1889
|
-
const invokes = [];
|
|
1890
|
-
collectConfiguredInvokeSrcs(machine.config, machine.config.id ?? "(root)", invokes);
|
|
1891
|
-
const srcs = /* @__PURE__ */ new Set();
|
|
1892
|
-
for (const { src } of invokes) if (typeof src === "string") srcs.add(src);
|
|
1893
|
-
return srcs;
|
|
1894
|
-
}
|
|
1895
|
-
const rootTraceRegistry = /* @__PURE__ */ new WeakMap();
|
|
1896
|
-
let nextProvideRunId = 1;
|
|
1897
|
-
/** Walks `self._parent` from an invoked async leaf actor up to its root actor. */
|
|
1898
|
-
function rootActorOf(self) {
|
|
1899
|
-
let ref = self;
|
|
1900
|
-
while (ref?._parent) ref = ref._parent;
|
|
1901
|
-
return ref;
|
|
1902
|
-
}
|
|
1903
|
-
/** The per-root envelope state, minted on first use (runId `run_<n>`, matching runAgent). */
|
|
1904
|
-
function rootTraceState(root) {
|
|
1905
|
-
let state = rootTraceRegistry.get(root);
|
|
1906
|
-
if (!state) {
|
|
1907
|
-
const logic = root.logic;
|
|
1908
|
-
const machineId = (logic?.config)?.id ?? logic?.id ?? "(machine)";
|
|
1909
|
-
const machineVersion = logic ? require_decision.resolveMachineVersion(logic) : "";
|
|
1910
|
-
state = {
|
|
1911
|
-
runId: `run_${nextProvideRunId++}`,
|
|
1912
|
-
seq: 0,
|
|
1913
|
-
machineId,
|
|
1914
|
-
machineVersion
|
|
1915
|
-
};
|
|
1916
|
-
rootTraceRegistry.set(root, state);
|
|
1917
|
-
}
|
|
1918
|
-
return state;
|
|
1919
|
-
}
|
|
1920
|
-
/** Stamps a per-root-actor envelope onto a bare trace payload. */
|
|
1921
|
-
function stampRootTrace(root, payload) {
|
|
1922
|
-
const state = rootTraceState(root);
|
|
1923
|
-
return {
|
|
1924
|
-
schemaVersion: 1,
|
|
1925
|
-
runId: state.runId,
|
|
1926
|
-
seq: ++state.seq,
|
|
1927
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1928
|
-
machineId: state.machineId,
|
|
1929
|
-
machineVersion: state.machineVersion,
|
|
1930
|
-
...payload
|
|
1931
|
-
};
|
|
1932
|
-
}
|
|
1933
|
-
/** Adapts a public `onTrace` into the payload-level trace sink a {@link TraceDispatch} fans out to. */
|
|
1934
|
-
function provideTraceSink(onTrace) {
|
|
1935
|
-
if (!onTrace) return;
|
|
1936
|
-
return (payload, self) => {
|
|
1937
|
-
const root = rootActorOf(self);
|
|
1938
|
-
if (root) onTrace(stampRootTrace(root, payload));
|
|
1939
|
-
};
|
|
1940
|
-
}
|
|
1941
|
-
/**
|
|
1942
|
-
* A minimal {@link RunAgentBindContext} for `provideExecutors` (uncontrolled
|
|
1943
|
-
* `createActor`): the same wrappers runAgent installs, MINUS the run-scoped
|
|
1944
|
-
* model-call counter. `consumeModelCall` is a no-op (no budget), and
|
|
1945
|
-
* `actorHolder.actorRef` is left undefined — the wrappers read the invoking
|
|
1946
|
-
* actor off `self._parent`, always present under a live `createActor` tree.
|
|
1947
|
-
* `onTrace` (when given) mints a per-root-actor envelope. `schemas` come from
|
|
1948
|
-
* the machine's registered `setupAgent` execution options.
|
|
1949
|
-
*
|
|
1950
|
-
* `recordUsage` has no run-level aggregate to fold into here (there is no
|
|
1951
|
-
* run), so it does one thing: deliver the reserved `@agent.usage` event, gated
|
|
1952
|
-
* exactly like runAgent's — see {@link deliverUsageEvent}.
|
|
1953
|
-
*/
|
|
1954
|
-
function provideBindContext(machine, executors, options) {
|
|
1955
|
-
const traceSink = provideTraceSink(options.onTrace);
|
|
1956
|
-
const onChunk = options.onChunk;
|
|
1957
|
-
return {
|
|
1958
|
-
generateText: executors.generateText,
|
|
1959
|
-
streamText: executors.streamText,
|
|
1960
|
-
decide: executors.decide,
|
|
1961
|
-
emitTrace: traceSink || onChunk ? createTraceDispatch({
|
|
1962
|
-
onTrace: traceSink,
|
|
1963
|
-
onChunk: onChunk ? (chunk) => onChunk(chunk) : void 0
|
|
1964
|
-
}) : void 0,
|
|
1965
|
-
consumeModelCall: () => {},
|
|
1966
|
-
recordUsage: (usage, source, self) => {
|
|
1967
|
-
deliverUsageEvent({
|
|
1968
|
-
type: require_setup_agent.AGENT_USAGE_EVENT_TYPE,
|
|
1969
|
-
...source,
|
|
1970
|
-
usage
|
|
1971
|
-
}, () => self?._parent);
|
|
1972
|
-
},
|
|
1973
|
-
actorHolder: { actorRef: void 0 },
|
|
1974
|
-
schemas: require_decision.getRegisteredAgentExecutionOptions(machine).schemas
|
|
1975
|
-
};
|
|
1976
|
-
}
|
|
1977
|
-
/**
|
|
1978
|
-
* The single reserved-`@agent.usage` DELIVERY seam, shared by both bind paths:
|
|
1979
|
-
* after a bound call settles with reported usage, send the prebuilt `event` to
|
|
1980
|
-
* the machine actor `resolveActorRef` names — the run's root actor on the `runAgent` path,
|
|
1981
|
-
* the settling request actor's `self._parent` (always the invoking machine
|
|
1982
|
-
* under a live `createActor` tree) on the `provideExecutors` path.
|
|
1983
|
-
*
|
|
1984
|
-
* Journaling is separate and unconditional (see `recordUsage`): a call's tokens
|
|
1985
|
-
* are recorded in the event log whether or not the machine reacts to them.
|
|
1986
|
-
*
|
|
1987
|
-
* Gating is identical on both: the target snapshot must be active, must declare
|
|
1988
|
-
* an `'@agent.usage'` transition — explicitly, or through a catch-all
|
|
1989
|
-
* `on: { '*' }` (see {@link declaresUsageTransition}) — and must be able to
|
|
1990
|
-
* take the event. `onDropped` is the run path's straggler gate: it returns `true` for a
|
|
1991
|
-
* call that settled after the cycle resolved, which drops the event (traced as
|
|
1992
|
-
* `usage.dropped`) rather than delivering it. Uncontrolled mode has no cycle to
|
|
1993
|
-
* settle, so it passes no gate and has no dropped stragglers.
|
|
1994
|
-
*
|
|
1995
|
-
* Delivery follows each path's binding boundary: only sources IT bound report
|
|
1996
|
-
* here, so an invoked child machine that was not itself passed through
|
|
1997
|
-
* `provideExecutors` reports nothing. @internal
|
|
1998
|
-
*/
|
|
1999
|
-
function deliverUsageEvent(event, resolveActorRef, onDropped) {
|
|
2000
|
-
const actorRef = resolveActorRef();
|
|
2001
|
-
if (!actorRef) return;
|
|
2002
|
-
const snapshot = actorRef.getSnapshot();
|
|
2003
|
-
if (snapshot?.status !== "active" || !declaresUsageTransition(snapshot)) return;
|
|
2004
|
-
if (onDropped?.(event)) return;
|
|
2005
|
-
if (!snapshot.can(event)) return;
|
|
2006
|
-
actorRef.send(event);
|
|
2007
|
-
}
|
|
2008
|
-
/**
|
|
2009
|
-
* Host-binds one text/stream source for {@link provideExecutors} using the SAME
|
|
2010
|
-
* emission helper as `runAgent` ({@link bindTextLogic}), so a bound
|
|
2011
|
-
* text request emits request.start/stream.chunk/request.end/request.error with
|
|
2012
|
-
* identical shapes. @internal
|
|
2013
|
-
*/
|
|
2014
|
-
function bindTextForProvide(machine, logic, executors, options) {
|
|
2015
|
-
return bindTextLogic(logic, provideBindContext(machine, executors, options));
|
|
2016
|
-
}
|
|
2017
|
-
/**
|
|
2018
|
-
* Host-binds one `DecisionLogic`/`agent.decide` source for
|
|
2019
|
-
* {@link provideExecutors}: runAgent's decision wrapper (snapshot-driven
|
|
2020
|
-
* candidate events, `canTake`, auto-delivery of the chosen event) with the same
|
|
2021
|
-
* request-level tracing runAgent emits, minus run-scoped counting. @internal
|
|
2022
|
-
*/
|
|
2023
|
-
function bindDecisionForProvide(machine, logic, executors, options) {
|
|
2024
|
-
return bindDecisionLogic(logic, provideBindContext(machine, executors, options));
|
|
2025
|
-
}
|
|
2026
|
-
/**
|
|
2027
|
-
* Recursively binds an invoked child state machine for {@link provideExecutors},
|
|
2028
|
-
* with the same semantics `runAgent` applies ({@link rebindChildMachine}):
|
|
2029
|
-
* string-keyed text/decision sources at any depth inherit the host executors,
|
|
2030
|
-
* a source that carries its own executor is left alone, and a cycle is
|
|
2031
|
-
* returned as-is. Each machine in the tree is bound with its own registered
|
|
2032
|
-
* `setupAgent` schemas. Returns the original machine when nothing needed
|
|
2033
|
-
* wrapping. @internal
|
|
2034
|
-
*/
|
|
2035
|
-
function bindChildMachineForProvide(childMachine, executors, options, visited) {
|
|
2036
|
-
const ctxFor = (target) => provideBindContext(target, executors, options);
|
|
2037
|
-
return rebindChildMachine(childMachine, ctxFor(childMachine), visited, ctxFor);
|
|
2038
|
-
}
|
|
2039
|
-
/**
|
|
2040
|
-
* Validates `input` against the machine's registered input schema, returning
|
|
2041
|
-
* the schema's output — so defaults are filled and transforms applied before
|
|
2042
|
-
* the value reaches `createActor` or the replayable event log.
|
|
2043
|
-
*
|
|
2044
|
-
* Standard Schema only (no validation library is referenced), so this works for
|
|
2045
|
-
* whatever the machine was declared with. Omitted input stays omitted rather
|
|
2046
|
-
* than being validated as `{}`: "started with no input" keeps meaning what it
|
|
2047
|
-
* has always meant, instead of newly failing schemas with required fields.
|
|
2048
|
-
*/
|
|
2049
|
-
function resolveMachineInput(machine, input) {
|
|
2050
|
-
if (input === void 0) return input;
|
|
2051
|
-
const schema = require_decision.getRegisteredAgentExecutionOptions(machine).schemas?.input;
|
|
2052
|
-
if (!require_decision.isStandardSchema(schema)) return input;
|
|
2053
|
-
try {
|
|
2054
|
-
return require_decision.validateSchemaSync(schema, input);
|
|
2055
|
-
} catch (error) {
|
|
2056
|
-
throw new require_decision.AgentError("invalid-machine-input", `runAgent: machine input failed validation against the declared input schema: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
2057
|
-
}
|
|
2058
|
-
}
|
|
2059
|
-
/**
|
|
2060
|
-
* Recursively rebinds an invoked child machine's own agent sources with the
|
|
2061
|
-
* SAME host-backed wrappers runAgent applies to the top-level machine, so a
|
|
2062
|
-
* child's text/stream/decision requests inherit runAgent's executors and
|
|
2063
|
-
* participate in maxModelCalls counting, onTrace/onChunk/onResult exactly like
|
|
2064
|
-
* parent requests. Returns the child machine to invoke: a `.provide`-rebound
|
|
2065
|
-
* copy when any inner source needed wrapping, else the original untouched.
|
|
2066
|
-
*
|
|
2067
|
-
* Only string-keyed sources are visited — a direct-object invoke src cannot be
|
|
2068
|
-
* swapped via `.provide` (assertBindable already rejected an unbound request
|
|
2069
|
-
* under one). A source that already carries its own executor
|
|
2070
|
-
* (`executorBoundLogics`) is left as-is: explicit binding shadows inheritance.
|
|
2071
|
-
* Cycle-safe via `visited` (a machine that invokes itself is returned as-is).
|
|
2072
|
-
*/
|
|
2073
|
-
function rebindChildMachine(childMachine, runCtx, visited, ctxFor) {
|
|
2074
|
-
if (visited.has(childMachine)) return childMachine;
|
|
2075
|
-
const childVisited = new Set([...visited, childMachine]);
|
|
2076
|
-
runCtx = ctxFor ? ctxFor(childMachine) : runCtx;
|
|
2077
|
-
const sources = childMachine.sources.actors;
|
|
2078
|
-
const wrapped = {};
|
|
2079
|
-
for (const [key, logic] of Object.entries(sources)) {
|
|
2080
|
-
if (require_decision.isDecisionLogic(logic)) {
|
|
2081
|
-
if (!require_decision.executorBoundLogics.has(logic)) wrapped[key] = bindDecisionLogic(logic, runCtx);
|
|
2082
|
-
continue;
|
|
2083
|
-
}
|
|
2084
|
-
if (require_decision.isTextLogic(logic)) {
|
|
2085
|
-
if (!require_decision.executorBoundLogics.has(logic)) wrapped[key] = bindTextLogic(logic, runCtx);
|
|
2086
|
-
continue;
|
|
2087
|
-
}
|
|
2088
|
-
if (isStateMachineLogic(logic)) {
|
|
2089
|
-
const rebound = rebindChildMachine(logic, runCtx, childVisited, ctxFor);
|
|
2090
|
-
if (rebound !== logic) wrapped[key] = rebound;
|
|
2091
|
-
continue;
|
|
2092
|
-
}
|
|
2093
|
-
}
|
|
2094
|
-
return Object.keys(wrapped).length > 0 ? childMachine.provide({ actors: wrapped }) : childMachine;
|
|
2095
|
-
}
|
|
2096
|
-
/**
|
|
2097
|
-
* Runs an agent machine to completion or idle: a `createActor` host that
|
|
2098
|
-
* binds `options`' host executors onto the machine's `agent.*`/`TextLogic`/
|
|
2099
|
-
* `DecisionLogic` actor sources, starts (or resumes) the actor, and drives
|
|
2100
|
-
* it until it settles — {@link RunAgentResult} `done | idle | error`. Unlike
|
|
2101
|
-
* the step helpers ({@link initialAgentStep} etc — a pure
|
|
2102
|
-
* transition-at-a-time path for durable hosts), `runAgent` owns a live actor
|
|
2103
|
-
* internally; there is no continuation callback, so **idle always settles**
|
|
2104
|
-
* and the caller resumes explicitly by passing the settled `{ snapshot,
|
|
2105
|
-
* event }` back in. The actor is stopped on every settle path (`done`,
|
|
2106
|
-
* `idle`, and `error` alike) — resume is always by snapshot, never by
|
|
2107
|
-
* holding a reference to a live actor.
|
|
2108
|
-
*
|
|
2109
|
-
* Binding happens **before** the actor starts: every invoke the machine
|
|
2110
|
-
* could reach is walked and checked against the effective actor sources
|
|
2111
|
-
* (`options.actors` merged onto the machine), so a missing
|
|
2112
|
-
* `streamText`/`decide` executor or any other unbound actor source throws
|
|
2113
|
-
* immediately — a bind-time error, not a mid-run failure. The one exception
|
|
2114
|
-
* is `agent.userInput`: unhandled, it binds as a pending placeholder that
|
|
2115
|
-
* settles the run idle (with `pendingUserInputs`) instead of erroring.
|
|
2116
|
-
*
|
|
2117
|
-
* @example
|
|
2118
|
-
* ```ts
|
|
2119
|
-
* const executors = createAiSdkExecutors({ models });
|
|
2120
|
-
* let r = await runAgent(machine, { input, executors });
|
|
2121
|
-
* while (r.status === 'idle') {
|
|
2122
|
-
* const event = await promptUser(getAcceptedEvents(r.snapshot));
|
|
2123
|
-
* r = await runAgent(machine, { snapshot: r.snapshot, event, executors });
|
|
2124
|
-
* }
|
|
2125
|
-
* if (r.status !== 'done') throw new Error(`Run did not complete: ${r.status}`);
|
|
2126
|
-
* console.log(r.output);
|
|
2127
|
-
* ```
|
|
2128
|
-
*
|
|
2129
|
-
* The `executors`' `generateText`/`streamText` accept the raw Vercel AI SDK
|
|
2130
|
-
* functions directly (`executors: { generateText, streamText }` with them
|
|
2131
|
-
* imported from `ai`) — their `{ text }`/`{ textStream }` results are unwrapped
|
|
2132
|
-
* natively. `decide` cannot be a raw AI SDK function: the tool-per-event mapping
|
|
2133
|
-
* lives in an adapter — use `createAiSdkExecutors` from '@statelyai/agent/ai-sdk'.
|
|
2134
|
-
*/
|
|
2135
|
-
async function runAgent(machine, options) {
|
|
2136
|
-
const store = options.store;
|
|
2137
|
-
if (store === void 0) return createAgentSession(machine, options).settled();
|
|
2138
|
-
const threadId = options.threadId;
|
|
2139
|
-
if (threadId === void 0 || threadId === "") throw new require_decision.AgentError("missing-thread-id", "runAgent: `threadId` is required when `store` is given — it names the log thread to read and append to.");
|
|
2140
|
-
if (options.events !== void 0) {
|
|
2141
|
-
const storedThread = await store.read(threadId);
|
|
2142
|
-
if (storedThread.length !== options.events.length) throw new AgentEventLogConflictError(threadId, options.events.length, storedThread.length);
|
|
2143
|
-
assertThreadMatchesEvents(threadId, storedThread, options.events);
|
|
2144
|
-
return createAgentSession(machine, options).settled();
|
|
2145
|
-
}
|
|
2146
|
-
const stored = await store.read(threadId);
|
|
2147
|
-
return createAgentSession(machine, stored.length > 0 ? {
|
|
2148
|
-
...options,
|
|
2149
|
-
events: stored
|
|
2150
|
-
} : options).settled();
|
|
2151
|
-
}
|
|
2152
|
-
/**
|
|
2153
|
-
* Entry-by-entry check that the caller's `events` IS the store's thread. Equal
|
|
2154
|
-
* lengths prove nothing: a fork, a rolled-back replay, or a concurrent writer
|
|
2155
|
-
* that appended and truncated all produce a divergent log of the same size,
|
|
2156
|
-
* and appending this run's entries onto it would splice two lineages together.
|
|
2157
|
-
*/
|
|
2158
|
-
function assertThreadMatchesEvents(threadId, stored, events) {
|
|
2159
|
-
for (let index = 0; index < stored.length; index++) {
|
|
2160
|
-
const storedEntry = stored[index];
|
|
2161
|
-
const givenEntry = events[index];
|
|
2162
|
-
const storedHash = storedEntry.verification?.stateHash;
|
|
2163
|
-
const givenHash = givenEntry.verification?.stateHash;
|
|
2164
|
-
if (!(storedEntry.id === givenEntry.id && storedEntry.index === givenEntry.index && canonicalEventJson(storedEntry.event) === canonicalEventJson(givenEntry.event) && (storedHash === void 0 || givenHash === void 0 || storedHash === givenHash))) throw new require_decision.AgentError("event-log-conflict", `runAgent: the given \`events\` diverge from thread "${threadId}" at index ${index} (stored entry '${storedEntry.id}', given '${givenEntry.id}') — the log passed in is not this thread's log.`);
|
|
2165
|
-
}
|
|
2166
|
-
}
|
|
2167
|
-
function createAgentSession(machine, options) {
|
|
2168
|
-
const maxModelCalls = options.maxModelCalls ?? 100;
|
|
2169
|
-
let modelCallCount = 0;
|
|
2170
|
-
let budgetExceeded = false;
|
|
2171
|
-
let warnedNonSerializable = false;
|
|
2172
|
-
const runId = `run_${nextRunAgentTraceId++}`;
|
|
2173
|
-
let traceSeq = 0;
|
|
2174
|
-
const resolvedInput = resolveMachineInput(machine, options.input);
|
|
2175
|
-
const machineId = machine.config.id ?? machine.id ?? "(machine)";
|
|
2176
|
-
const machineVersion = require_decision.resolveMachineVersion(machine);
|
|
2177
|
-
const onTrace = createTraceDispatch({
|
|
2178
|
-
onTrace: (payload) => {
|
|
2179
|
-
options.onTrace?.({
|
|
2180
|
-
schemaVersion: 1,
|
|
2181
|
-
runId,
|
|
2182
|
-
seq: ++traceSeq,
|
|
2183
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2184
|
-
machineId,
|
|
2185
|
-
machineVersion,
|
|
2186
|
-
...payload
|
|
2187
|
-
});
|
|
2188
|
-
},
|
|
2189
|
-
onChunk: options.onChunk,
|
|
2190
|
-
onResult: options.onResult,
|
|
2191
|
-
onTransition: options.onTransition
|
|
2192
|
-
});
|
|
2193
|
-
const consumeModelCall = () => {
|
|
2194
|
-
if (budgetExceeded) throw new AgentMaxModelCallsExceededError(maxModelCalls);
|
|
2195
|
-
if (modelCallCount + 1 > maxModelCalls) {
|
|
2196
|
-
budgetExceeded = true;
|
|
2197
|
-
throw new AgentMaxModelCallsExceededError(maxModelCalls);
|
|
2198
|
-
}
|
|
2199
|
-
modelCallCount += 1;
|
|
2200
|
-
};
|
|
2201
|
-
const cycleGate = { isResolved: () => false };
|
|
2202
|
-
const logEntries = [];
|
|
2203
|
-
const store = options.store;
|
|
2204
|
-
const threadId = options.threadId;
|
|
2205
|
-
if (store !== void 0 && (threadId === void 0 || threadId === "")) throw new require_decision.AgentError("missing-thread-id", "runAgent: `threadId` is required when `store` is given — it names the log thread to read and append to.");
|
|
2206
|
-
let journalChain = Promise.resolve();
|
|
2207
|
-
let journalPending = 0;
|
|
2208
|
-
let journalError;
|
|
2209
|
-
const journalEntry = (entry) => {
|
|
2210
|
-
if (store === void 0) return;
|
|
2211
|
-
journalPending++;
|
|
2212
|
-
journalChain = journalChain.then(async () => {
|
|
2213
|
-
if (journalError !== void 0) return;
|
|
2214
|
-
await store.append({
|
|
2215
|
-
threadId,
|
|
2216
|
-
expectedIndex: entry.index,
|
|
2217
|
-
entries: [entry]
|
|
2218
|
-
});
|
|
2219
|
-
}).catch((error) => {
|
|
2220
|
-
journalError ??= error;
|
|
2221
|
-
failRunOnJournalError(error);
|
|
2222
|
-
}).finally(() => {
|
|
2223
|
-
journalPending--;
|
|
2224
|
-
});
|
|
2225
|
-
};
|
|
2226
|
-
/**
|
|
2227
|
-
* Every journal write ISSUED SO FAR, including the stragglers appended after
|
|
2228
|
-
* the run settled. Re-reads the chain tail until it stops moving, so a write
|
|
2229
|
-
* queued while an earlier one was in flight is covered too. Never rejects: a
|
|
2230
|
-
* failed write already settled the run with `cause: 'journal'`.
|
|
2231
|
-
*/
|
|
2232
|
-
const drainJournal = async () => {
|
|
2233
|
-
if (store === void 0) return;
|
|
2234
|
-
let tail = journalChain;
|
|
2235
|
-
for (;;) {
|
|
2236
|
-
await tail;
|
|
2237
|
-
if (journalPending === 0 && journalChain === tail) return;
|
|
2238
|
-
tail = journalChain;
|
|
2239
|
-
}
|
|
2240
|
-
};
|
|
2241
|
-
/** The barrier: settle the pending chain, then surface any failure. */
|
|
2242
|
-
const awaitJournal = async () => {
|
|
2243
|
-
if (store === void 0) return;
|
|
2244
|
-
while (journalPending > 0) await journalChain;
|
|
2245
|
-
if (journalError !== void 0) throw journalError;
|
|
2246
|
-
};
|
|
2247
|
-
const failRunOnJournalError = (error) => {
|
|
2248
|
-
if (settled) return;
|
|
2249
|
-
settle({
|
|
2250
|
-
status: "error",
|
|
2251
|
-
cause: "journal",
|
|
2252
|
-
error,
|
|
2253
|
-
snapshot: actor.getSnapshot()
|
|
2254
|
-
});
|
|
2255
|
-
};
|
|
2256
|
-
let resumedLogLength = 0;
|
|
2257
|
-
let logExecutionId;
|
|
2258
|
-
const verificationEnabled = options.verification !== false;
|
|
2259
|
-
let lastRootSnapshot;
|
|
2260
|
-
let logMachine = machine;
|
|
2261
|
-
let loggingEnabled = true;
|
|
2262
|
-
const appendLogEntry = (event, snapshot) => {
|
|
2263
|
-
if (!loggingEnabled) return;
|
|
2264
|
-
const entry = createReplayEntry(logMachine, logEntries, event, {
|
|
2265
|
-
machineVersion,
|
|
2266
|
-
verification: verificationEnabled,
|
|
2267
|
-
...verificationEnabled && snapshot !== void 0 ? { snapshot } : {}
|
|
2268
|
-
});
|
|
2269
|
-
logEntries.push(entry);
|
|
2270
|
-
journalEntry(entry);
|
|
2271
|
-
options.onEvent?.(entry);
|
|
2272
|
-
return entry;
|
|
2273
|
-
};
|
|
2274
|
-
const usageEntrySnapshot = () => {
|
|
2275
|
-
const current = lastRootSnapshot;
|
|
2276
|
-
if (current && (current.status !== "active" || !declaresUsageTransition(current))) return current;
|
|
2277
|
-
};
|
|
2278
|
-
const recordUsage = (usage, source = {}) => {
|
|
2279
|
-
const event = {
|
|
2280
|
-
type: require_setup_agent.AGENT_USAGE_EVENT_TYPE,
|
|
2281
|
-
...source,
|
|
2282
|
-
usage
|
|
2283
|
-
};
|
|
2284
|
-
const journal = { event };
|
|
2285
|
-
pendingUsageJournal = journal;
|
|
2286
|
-
try {
|
|
2287
|
-
deliverUsageEvent(event, () => actorHolder.actorRef, (dropped) => {
|
|
2288
|
-
if (!cycleGate.isResolved()) return false;
|
|
2289
|
-
onTrace({
|
|
2290
|
-
type: "usage.dropped",
|
|
2291
|
-
event: dropped,
|
|
2292
|
-
reason: "settled"
|
|
2293
|
-
});
|
|
2294
|
-
return true;
|
|
2295
|
-
});
|
|
2296
|
-
} finally {
|
|
2297
|
-
pendingUsageJournal = void 0;
|
|
2298
|
-
}
|
|
2299
|
-
if (journal.entry === void 0) appendLogEntry(event, usageEntrySnapshot());
|
|
2300
|
-
};
|
|
2301
|
-
let pendingUsageJournal;
|
|
2302
|
-
const runUsage = () => ({
|
|
2303
|
-
...getUsageFromEvents(logEntries.slice(resumedLogLength)),
|
|
2304
|
-
modelCalls: modelCallCount
|
|
2305
|
-
});
|
|
2306
|
-
const warnNonSerializableContext = (snapshot) => {
|
|
2307
|
-
if (warnedNonSerializable || process.env.NODE_ENV === "production") return;
|
|
2308
|
-
let offending = [];
|
|
2309
|
-
try {
|
|
2310
|
-
offending = require_decision.findNonSerializableContextPaths(snapshot.context);
|
|
2311
|
-
} catch {
|
|
2312
|
-
return;
|
|
2313
|
-
}
|
|
2314
|
-
if (offending.length === 0) return;
|
|
2315
|
-
warnedNonSerializable = true;
|
|
2316
|
-
console.warn(`runAgent: context holds value(s) that will not survive snapshot persist/resume (JSON round-trip): ${offending.join(", ")}. Persist only JSON-serializable context, or convert these before the run settles.`);
|
|
2317
|
-
};
|
|
2318
|
-
const provided = machine.provide({ actors: options.actors });
|
|
2319
|
-
logMachine = provided;
|
|
2320
|
-
const effectiveSources = provided.sources.actors;
|
|
2321
|
-
const executors = {
|
|
2322
|
-
...require_decision.getRegisteredAgentExecutionOptions(machine).models?.[require_decision.DEFAULT_AGENT_EXECUTORS]?.(),
|
|
2323
|
-
...options.executors
|
|
2324
|
-
};
|
|
2325
|
-
assertBindable(provided, effectiveSources, executors);
|
|
2326
|
-
const actorHolder = { actorRef: void 0 };
|
|
2327
|
-
const runCtx = {
|
|
2328
|
-
generateText: executors.generateText,
|
|
2329
|
-
streamText: executors.streamText,
|
|
2330
|
-
decide: executors.decide,
|
|
2331
|
-
emitTrace: onTrace,
|
|
2332
|
-
consumeModelCall,
|
|
2333
|
-
recordUsage,
|
|
2334
|
-
actorHolder,
|
|
2335
|
-
awaitJournal,
|
|
2336
|
-
runId,
|
|
2337
|
-
schemas: require_decision.getRegisteredAgentExecutionOptions(machine).schemas
|
|
2338
|
-
};
|
|
2339
|
-
let userInputIsPlaceholder = false;
|
|
2340
|
-
const wrappedSources = {};
|
|
2341
|
-
for (const [key, logic] of Object.entries(effectiveSources)) {
|
|
2342
|
-
if (key === "agent.userInput") {
|
|
2343
|
-
if (options.userInput) {
|
|
2344
|
-
const userInput = options.userInput;
|
|
2345
|
-
wrappedSources[key] = (0, xstate.createAsyncLogic)({ run: async ({ input }) => await userInput(input) });
|
|
2346
|
-
} else if (require_decision.isUnboundPlaceholder(logic)) {
|
|
2347
|
-
userInputIsPlaceholder = true;
|
|
2348
|
-
wrappedSources[key] = (0, xstate.createAsyncLogic)({ run: () => new Promise(() => {}) });
|
|
2349
|
-
}
|
|
2350
|
-
continue;
|
|
2351
|
-
}
|
|
2352
|
-
if (require_decision.isDecisionLogic(logic)) {
|
|
2353
|
-
wrappedSources[key] = bindDecisionLogic(logic, runCtx);
|
|
2354
|
-
continue;
|
|
2355
|
-
}
|
|
2356
|
-
if (require_decision.isTextLogic(logic)) {
|
|
2357
|
-
if (!require_decision.executorBoundLogics.has(logic)) wrappedSources[key] = bindTextLogic(logic, runCtx);
|
|
2358
|
-
continue;
|
|
2359
|
-
}
|
|
2360
|
-
if (isStateMachineLogic(logic)) {
|
|
2361
|
-
const rebound = rebindChildMachine(logic, runCtx, new Set([machine]));
|
|
2362
|
-
if (rebound !== logic) wrappedSources[key] = rebound;
|
|
2363
|
-
continue;
|
|
2364
|
-
}
|
|
2365
|
-
}
|
|
2366
|
-
const boundMachine = provided.provide({ actors: wrappedSources });
|
|
2367
|
-
const isIdle = require_decision.getMachineIdlePredicate(machine) ?? isAgentIdle;
|
|
2368
|
-
const isIntentionalIdle = (snapshot) => isIdle(snapshot) || collectPendingUserInputs(snapshot).length > 0;
|
|
2369
|
-
const resumeEvents = options.events;
|
|
2370
|
-
let effectiveSnapshot = options.snapshot;
|
|
2371
|
-
/** Deferred until after the illegal-resume check, so a rejected resume emits no entries. */
|
|
2372
|
-
let seedInitEntry;
|
|
2373
|
-
const restoredPersistedSnapshot = () => {
|
|
2374
|
-
try {
|
|
2375
|
-
const restored = logMachine.restoreSnapshot(options.snapshot);
|
|
2376
|
-
return logMachine.getPersistedSnapshot(restored);
|
|
2377
|
-
} catch {
|
|
2378
|
-
return;
|
|
2379
|
-
}
|
|
2380
|
-
};
|
|
2381
|
-
/**
|
|
2382
|
-
* Appends the log's reserved first entry, trying each candidate `init` in
|
|
2383
|
-
* turn and, for each, an unverified entry if the hash cannot be computed.
|
|
2384
|
-
* A machine or snapshot that defeats every attempt leaves the run WITHOUT a
|
|
2385
|
-
* log rather than failing it: `runAgent` predates the log, and an exotic
|
|
2386
|
-
* machine that cannot be folded must still run.
|
|
2387
|
-
*/
|
|
2388
|
-
const appendInitEntry = (candidates, metadata) => {
|
|
2389
|
-
for (const init of candidates) for (const verification of verificationEnabled ? [true, false] : [false]) try {
|
|
2390
|
-
const entry = initEntry(logMachine, init, {
|
|
2391
|
-
machineVersion,
|
|
2392
|
-
verification,
|
|
2393
|
-
metadata
|
|
2394
|
-
});
|
|
2395
|
-
logEntries.push(entry);
|
|
2396
|
-
journalEntry(entry);
|
|
2397
|
-
options.onEvent?.(entry);
|
|
2398
|
-
return;
|
|
2399
|
-
} catch {}
|
|
2400
|
-
loggingEnabled = false;
|
|
2401
|
-
};
|
|
2402
|
-
/** Init forms for a snapshot resume, most faithful first. */
|
|
2403
|
-
const snapshotInitCandidates = () => {
|
|
2404
|
-
const restored = restoredPersistedSnapshot();
|
|
2405
|
-
return [...restored !== void 0 ? [{ snapshot: restored }] : [], { snapshot: options.snapshot }];
|
|
2406
|
-
};
|
|
2407
|
-
if (resumeEvents !== void 0 && resumeEvents.length > 0) {
|
|
2408
|
-
validateReplayEntries(resumeEvents);
|
|
2409
|
-
const tail = resumeEvents[resumeEvents.length - 1];
|
|
2410
|
-
const inheritedExecutionId = getLogExecutionId(resumeEvents);
|
|
2411
|
-
if (resumeEvents[0].machineId !== machineId) throw new AgentMachineVersionMismatchError(tail.id, tail.index, {
|
|
2412
|
-
machineId,
|
|
2413
|
-
machineVersion
|
|
2414
|
-
}, {
|
|
2415
|
-
machineId: resumeEvents[0].machineId,
|
|
2416
|
-
machineVersion: tail.machineVersion
|
|
2417
|
-
});
|
|
2418
|
-
if (tail.machineVersion === machineVersion) {
|
|
2419
|
-
logEntries.push(...resumeEvents);
|
|
2420
|
-
resumedLogLength = logEntries.length;
|
|
2421
|
-
logExecutionId = inheritedExecutionId;
|
|
2422
|
-
const cachedMeta = readAgentMeta(options.snapshot);
|
|
2423
|
-
const tailStateHash = tail.verification?.stateHash;
|
|
2424
|
-
if (!(options.snapshot !== void 0 && inheritedExecutionId !== void 0 && cachedMeta?.logId === inheritedExecutionId && cachedMeta.logIndex === resumeEvents.length && tailStateHash !== void 0 && hashResumeSnapshot(options.snapshot) === tailStateHash)) {
|
|
2425
|
-
const cachedIndex = cachedMeta?.logIndex;
|
|
2426
|
-
if (options.snapshot !== void 0 && typeof cachedIndex === "number" && cachedIndex >= 1 && cachedIndex <= resumeEvents.length && resumeEvents[cachedIndex - 1].verification?.stateHash !== void 0) {
|
|
2427
|
-
const expected = getSnapshotStateHash(replay(logMachine, resumeEvents.slice(0, cachedIndex), {
|
|
2428
|
-
machineVersion,
|
|
2429
|
-
verify: false
|
|
2430
|
-
}).persistedSnapshot);
|
|
2431
|
-
const actual = hashResumeSnapshot(options.snapshot);
|
|
2432
|
-
if (expected !== actual) throw new AgentSnapshotDivergedError(expected, actual, cachedIndex);
|
|
2433
|
-
}
|
|
2434
|
-
effectiveSnapshot = replay(logMachine, resumeEvents, {
|
|
2435
|
-
machineVersion,
|
|
2436
|
-
verify: true
|
|
2437
|
-
}).persistedSnapshot;
|
|
2438
|
-
}
|
|
2439
|
-
} else {
|
|
2440
|
-
if (options.snapshot === void 0) throw new AgentMachineVersionMismatchError(tail.id, tail.index, {
|
|
2441
|
-
machineId,
|
|
2442
|
-
machineVersion
|
|
2443
|
-
}, {
|
|
2444
|
-
machineId: tail.machineId,
|
|
2445
|
-
machineVersion: tail.machineVersion
|
|
2446
|
-
});
|
|
2447
|
-
const bridgeMeta = readAgentMeta(options.snapshot);
|
|
2448
|
-
if (inheritedExecutionId === void 0 || bridgeMeta?.logId !== inheritedExecutionId) throw new AgentMachineVersionMismatchError(tail.id, tail.index, {
|
|
2449
|
-
machineId,
|
|
2450
|
-
machineVersion
|
|
2451
|
-
}, {
|
|
2452
|
-
machineId: tail.machineId,
|
|
2453
|
-
machineVersion: tail.machineVersion
|
|
2454
|
-
});
|
|
2455
|
-
const bridgeTailHash = tail.verification?.stateHash;
|
|
2456
|
-
const bridgeSnapshotHash = hashResumeSnapshot(options.snapshot);
|
|
2457
|
-
if (bridgeMeta.logIndex !== resumeEvents.length) throw new AgentSnapshotDivergedError(bridgeTailHash ?? "(unrecorded)", bridgeSnapshotHash, resumeEvents.length);
|
|
2458
|
-
if (bridgeTailHash !== void 0 && bridgeSnapshotHash !== bridgeTailHash) throw new AgentSnapshotDivergedError(bridgeTailHash, bridgeSnapshotHash, resumeEvents.length);
|
|
2459
|
-
logExecutionId = inheritedExecutionId;
|
|
2460
|
-
const migratedFrom = {
|
|
2461
|
-
machineVersion: tail.machineVersion,
|
|
2462
|
-
logIndex: resumeEvents.length
|
|
2463
|
-
};
|
|
2464
|
-
seedInitEntry = () => appendInitEntry(snapshotInitCandidates(), {
|
|
2465
|
-
...logExecutionId !== void 0 ? { executionId: logExecutionId } : {},
|
|
2466
|
-
migratedFrom
|
|
2467
|
-
});
|
|
2468
|
-
}
|
|
2469
|
-
} else if (options.snapshot !== void 0) {
|
|
2470
|
-
logExecutionId = crypto.randomUUID();
|
|
2471
|
-
seedInitEntry = () => appendInitEntry(snapshotInitCandidates(), { executionId: logExecutionId });
|
|
2472
|
-
} else {
|
|
2473
|
-
logExecutionId = crypto.randomUUID();
|
|
2474
|
-
seedInitEntry = () => appendInitEntry([resolvedInput !== void 0 ? { input: resolvedInput } : {}], { executionId: logExecutionId });
|
|
2475
|
-
}
|
|
2476
|
-
const resumedHistory = [...logEntries];
|
|
2477
|
-
const siteCallCounts = /* @__PURE__ */ new Map();
|
|
2478
|
-
const mintedCallKeys = /* @__PURE__ */ new WeakMap();
|
|
2479
|
-
runCtx.callKey = (siteId, self) => {
|
|
2480
|
-
if (logExecutionId === void 0) return;
|
|
2481
|
-
const memoized = self === void 0 ? void 0 : mintedCallKeys.get(self);
|
|
2482
|
-
if (memoized !== void 0) return memoized;
|
|
2483
|
-
const startedHere = siteCallCounts.get(siteId) ?? 0;
|
|
2484
|
-
siteCallCounts.set(siteId, startedHere + 1);
|
|
2485
|
-
const key = `${logExecutionId}:${siteId}#${agentCallOccurrence(resumedHistory, siteId) + startedHere}`;
|
|
2486
|
-
if (self !== void 0) mintedCallKeys.set(self, key);
|
|
2487
|
-
return key;
|
|
2488
|
-
};
|
|
2489
|
-
const resumeEventToSend = options.event;
|
|
2490
|
-
let ignoredEvent;
|
|
2491
|
-
seedInitEntry?.();
|
|
2492
|
-
let settled = false;
|
|
2493
|
-
let journalAtSettle;
|
|
2494
|
-
cycleGate.isResolved = () => settled;
|
|
2495
|
-
let lastResult;
|
|
2496
|
-
const waiters = [];
|
|
2497
|
-
let idleTimer;
|
|
2498
|
-
let actor;
|
|
2499
|
-
let actorStarted = false;
|
|
2500
|
-
let deliveringResumeEvent = resumeEventToSend !== void 0;
|
|
2501
|
-
const settle = (outcome) => {
|
|
2502
|
-
if (settled) return;
|
|
2503
|
-
settled = true;
|
|
2504
|
-
let persistedSnapshot;
|
|
2505
|
-
let persistenceError;
|
|
2506
|
-
if (actorStarted && outcome.status !== "done") try {
|
|
2507
|
-
persistedSnapshot = actor.getPersistedSnapshot();
|
|
2508
|
-
} catch (error) {
|
|
2509
|
-
persistenceError = error;
|
|
2510
|
-
}
|
|
2511
|
-
const logIndexAtSettle = logEntries.length;
|
|
2512
|
-
journalAtSettle = journalChain;
|
|
2513
|
-
const persist = () => {
|
|
2514
|
-
if (persistenceError !== void 0) throw persistenceError;
|
|
2515
|
-
persistedSnapshot ??= actor.getPersistedSnapshot();
|
|
2516
|
-
persistedSnapshot.agentMeta = {
|
|
2517
|
-
machineId,
|
|
2518
|
-
version: machineVersion,
|
|
2519
|
-
...logExecutionId !== void 0 ? { logId: logExecutionId } : {},
|
|
2520
|
-
logIndex: logIndexAtSettle
|
|
2521
|
-
};
|
|
2522
|
-
return persistedSnapshot;
|
|
2523
|
-
};
|
|
2524
|
-
const result = {
|
|
2525
|
-
...outcome,
|
|
2526
|
-
events: [...logEntries],
|
|
2527
|
-
persist,
|
|
2528
|
-
drain: drainJournal,
|
|
2529
|
-
usage: runUsage(),
|
|
2530
|
-
...ignoredEvent !== void 0 ? { ignored: ignoredEvent } : {}
|
|
2531
|
-
};
|
|
2532
|
-
if (idleTimer !== void 0) clearTimeout(idleTimer);
|
|
2533
|
-
onTrace({
|
|
2534
|
-
type: "run.end",
|
|
2535
|
-
...outcome
|
|
2536
|
-
});
|
|
2537
|
-
if (options.signal) options.signal.removeEventListener("abort", onAbort);
|
|
2538
|
-
actor.stop();
|
|
2539
|
-
lastResult = result;
|
|
2540
|
-
for (const resolve of waiters.splice(0)) resolve(result);
|
|
2541
|
-
};
|
|
2542
|
-
const onAbort = () => {
|
|
2543
|
-
settle({
|
|
2544
|
-
status: "error",
|
|
2545
|
-
cause: "aborted",
|
|
2546
|
-
error: options.signal?.reason ?? /* @__PURE__ */ new Error("Aborted"),
|
|
2547
|
-
snapshot: actor.getSnapshot()
|
|
2548
|
-
});
|
|
2549
|
-
};
|
|
2550
|
-
const settleIdle = (current) => {
|
|
2551
|
-
warnNonSerializableContext(current);
|
|
2552
|
-
const pendingUserInputs = userInputIsPlaceholder ? collectPendingUserInputs(current) : [];
|
|
2553
|
-
settle({
|
|
2554
|
-
status: "idle",
|
|
2555
|
-
snapshot: current,
|
|
2556
|
-
...pendingUserInputs.length > 0 ? { pendingUserInputs } : {}
|
|
2557
|
-
});
|
|
2558
|
-
};
|
|
2559
|
-
const runErrorCause = (error) => budgetExceeded ? "max-model-calls" : wrapsDecisionExhausted(error) ? "decision-exhausted" : "machine";
|
|
2560
|
-
const scheduleIdleCheck = () => {
|
|
2561
|
-
if (idleTimer !== void 0) clearTimeout(idleTimer);
|
|
2562
|
-
idleTimer = setTimeout(() => {
|
|
2563
|
-
idleTimer = void 0;
|
|
2564
|
-
if (settled) return;
|
|
2565
|
-
const current = actor.getSnapshot();
|
|
2566
|
-
if (isIntentionalIdle(current) && isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) settleIdle(current);
|
|
2567
|
-
}, 0);
|
|
2568
|
-
};
|
|
2569
|
-
actor = (0, xstate.createActor)(boundMachine, {
|
|
2570
|
-
input: resolvedInput,
|
|
2571
|
-
snapshot: effectiveSnapshot,
|
|
2572
|
-
inspect: (event) => {
|
|
2573
|
-
if (typeof options.inspect === "function") options.inspect(event);
|
|
2574
|
-
else options.inspect?.next?.(event);
|
|
2575
|
-
if (event.type !== "@xstate.transition" || event.actorRef !== actor.ref) return;
|
|
2576
|
-
if (settled) return;
|
|
2577
|
-
const snapshot = event.snapshot;
|
|
2578
|
-
lastRootSnapshot = snapshot;
|
|
2579
|
-
if (resumeEventToSend !== void 0 && event.event === resumeEventToSend && event.microsteps.length === 0 && event.actions.length === 0 && event.sent.length === 0) ignoredEvent = resumeEventToSend;
|
|
2580
|
-
if (pendingUsageJournal !== void 0 && event.event === pendingUsageJournal.event) pendingUsageJournal.entry = appendLogEntry(event.event, snapshot);
|
|
2581
|
-
else if (event.event.type !== "@xstate.init" && (event.sourceRef !== event.actorRef || event.event.type === "xstate.timer")) appendLogEntry(event.event, snapshot);
|
|
2582
|
-
onTrace({
|
|
2583
|
-
type: "machine.transition",
|
|
2584
|
-
snapshot,
|
|
2585
|
-
event: event.event
|
|
2586
|
-
});
|
|
2587
|
-
if (snapshot.status === "done") {
|
|
2588
|
-
settle({
|
|
2589
|
-
status: "done",
|
|
2590
|
-
output: snapshot.output,
|
|
2591
|
-
snapshot
|
|
2592
|
-
});
|
|
2593
|
-
return;
|
|
2594
|
-
}
|
|
2595
|
-
if (snapshot.status === "error") {
|
|
2596
|
-
settle({
|
|
2597
|
-
status: "error",
|
|
2598
|
-
cause: runErrorCause(snapshot.error),
|
|
2599
|
-
error: snapshot.error,
|
|
2600
|
-
snapshot
|
|
2601
|
-
});
|
|
2602
|
-
return;
|
|
2603
|
-
}
|
|
2604
|
-
if (snapshot.status === "stopped") {
|
|
2605
|
-
settle({
|
|
2606
|
-
status: "error",
|
|
2607
|
-
cause: "stopped",
|
|
2608
|
-
error: /* @__PURE__ */ new Error("Actor stopped externally."),
|
|
2609
|
-
snapshot
|
|
2610
|
-
});
|
|
2611
|
-
return;
|
|
2612
|
-
}
|
|
2613
|
-
if (!deliveringResumeEvent && isIntentionalIdle(snapshot) && isIdleSnapshot(snapshot, { ignoreUserInputChildren: userInputIsPlaceholder })) {
|
|
2614
|
-
queueMicrotask(() => {
|
|
2615
|
-
if (settled) return;
|
|
2616
|
-
const current = actor.getSnapshot();
|
|
2617
|
-
if (isIntentionalIdle(current) && isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) settleIdle(current);
|
|
2618
|
-
else scheduleIdleCheck();
|
|
2619
|
-
});
|
|
2620
|
-
return;
|
|
2621
|
-
}
|
|
2622
|
-
scheduleIdleCheck();
|
|
2623
|
-
}
|
|
2624
|
-
});
|
|
2625
|
-
actorHolder.actorRef = actor;
|
|
2626
|
-
actor.subscribe({ error: () => {} });
|
|
2627
|
-
actor.on("*", (event) => {
|
|
2628
|
-
onTrace({
|
|
2629
|
-
type: "emit",
|
|
2630
|
-
event
|
|
2631
|
-
});
|
|
2632
|
-
});
|
|
2633
|
-
for (const [type, handler] of Object.entries(options.on ?? {})) if (typeof handler === "function") actor.on(type, handler);
|
|
2634
|
-
const sessionApi = { settled: async () => {
|
|
2635
|
-
const result = settled && lastResult !== void 0 ? lastResult : await new Promise((resolve) => {
|
|
2636
|
-
waiters.push(resolve);
|
|
2637
|
-
});
|
|
2638
|
-
await journalAtSettle;
|
|
2639
|
-
return result;
|
|
2640
|
-
} };
|
|
2641
|
-
if (options.signal) {
|
|
2642
|
-
if (options.signal.aborted) {
|
|
2643
|
-
settle({
|
|
2644
|
-
status: "error",
|
|
2645
|
-
cause: "aborted",
|
|
2646
|
-
error: options.signal.reason ?? /* @__PURE__ */ new Error("Aborted"),
|
|
2647
|
-
snapshot: actor.getSnapshot()
|
|
2648
|
-
});
|
|
2649
|
-
return sessionApi;
|
|
2650
|
-
}
|
|
2651
|
-
options.signal.addEventListener("abort", onAbort);
|
|
2652
|
-
}
|
|
2653
|
-
onTrace({
|
|
2654
|
-
type: "run.start",
|
|
2655
|
-
...resolvedInput !== void 0 ? { input: resolvedInput } : {},
|
|
2656
|
-
...effectiveSnapshot !== void 0 ? { snapshot: effectiveSnapshot } : {},
|
|
2657
|
-
...resumeEventToSend !== void 0 ? { event: resumeEventToSend } : {}
|
|
2658
|
-
});
|
|
2659
|
-
actorStarted = true;
|
|
2660
|
-
actor.start();
|
|
2661
|
-
if (!settled) {
|
|
2662
|
-
const startedSnapshot = actor.getSnapshot();
|
|
2663
|
-
lastRootSnapshot ??= startedSnapshot;
|
|
2664
|
-
if (startedSnapshot.status === "done") settle({
|
|
2665
|
-
status: "done",
|
|
2666
|
-
output: startedSnapshot.output,
|
|
2667
|
-
snapshot: startedSnapshot
|
|
2668
|
-
});
|
|
2669
|
-
else if (startedSnapshot.status === "error") settle({
|
|
2670
|
-
status: "error",
|
|
2671
|
-
cause: runErrorCause(startedSnapshot.error),
|
|
2672
|
-
error: startedSnapshot.error,
|
|
2673
|
-
snapshot: startedSnapshot
|
|
2674
|
-
});
|
|
2675
|
-
else if (startedSnapshot.status === "stopped") settle({
|
|
2676
|
-
status: "error",
|
|
2677
|
-
cause: "stopped",
|
|
2678
|
-
error: /* @__PURE__ */ new Error("Actor stopped externally."),
|
|
2679
|
-
snapshot: startedSnapshot
|
|
2680
|
-
});
|
|
2681
|
-
}
|
|
2682
|
-
if (resumeEventToSend && !settled) {
|
|
2683
|
-
deliveringResumeEvent = false;
|
|
2684
|
-
actor.send(resumeEventToSend);
|
|
2685
|
-
}
|
|
2686
|
-
return sessionApi;
|
|
2687
|
-
}
|
|
2688
|
-
/**
|
|
2689
|
-
* Wraps a `(snapshot, actorRef) => void` handler into a function usable as
|
|
2690
|
-
* {@link RunAgentOptions.inspect}: it filters the raw inspection stream to
|
|
2691
|
-
* `@xstate.transition` events and hands the handler the typed
|
|
2692
|
-
* {@link AnyMachineSnapshot} and the {@link InspectedActorRef} that
|
|
2693
|
-
* transitioned. Attribute a child actor via `actorRef.id`/`actorRef.src`. Saves
|
|
2694
|
-
* the manual `event.type === '@xstate.transition'` filtering and the snapshot/
|
|
2695
|
-
* actorRef casts.
|
|
2696
|
-
*/
|
|
2697
|
-
function inspectTransitions(handler) {
|
|
2698
|
-
return (inspectionEvent) => {
|
|
2699
|
-
if (inspectionEvent.type !== "@xstate.transition") return;
|
|
2700
|
-
handler(inspectionEvent.snapshot, inspectionEvent.actorRef);
|
|
2701
|
-
};
|
|
2702
|
-
}
|
|
2703
|
-
/**
|
|
2704
|
-
* An xstate `inspect` handler that emits `machine.transition` trace events onto
|
|
2705
|
-
* `onTrace`, sharing the SAME versioned envelope and per-root-actor `seq`
|
|
2706
|
-
* registry as {@link provideExecutors}' `onTrace`. Pair the two on one actor to
|
|
2707
|
-
* get a single ordered trace stream (request + transition events) for the
|
|
2708
|
-
* uncontrolled path:
|
|
2709
|
-
*
|
|
2710
|
-
* ```ts
|
|
2711
|
-
* const bound = provideExecutors(machine, executors, { onTrace });
|
|
2712
|
-
* const actor = createActor(bound, { inspect: traceTransitions(onTrace) });
|
|
2713
|
-
* ```
|
|
2714
|
-
*
|
|
2715
|
-
* Only ROOT-actor transitions are traced (matching `runAgent`'s
|
|
2716
|
-
* `machine.transition`); child-actor transitions are ignored. Attribute the
|
|
2717
|
-
* event via its envelope `runId`.
|
|
2718
|
-
*
|
|
2719
|
-
* By design this path has NO `run.start`/`run.end` events: `createActor` has no
|
|
2720
|
-
* run boundary the way `runAgent` does, so the stream starts at the actor's
|
|
2721
|
-
* first transition. It also does NOT emit `emit` trace events: in this xstate
|
|
2722
|
-
* build emitted events are delivered through `actor.on(...)`, not the inspection
|
|
2723
|
-
* protocol, so they are not observable from an `inspect` handler — subscribe
|
|
2724
|
-
* with `actor.on('*', ...)` if you need them.
|
|
2725
|
-
*/
|
|
2726
|
-
function traceTransitions(onTrace) {
|
|
2727
|
-
return (inspectionEvent) => {
|
|
2728
|
-
if (inspectionEvent.type !== "@xstate.transition") return;
|
|
2729
|
-
const actorRef = inspectionEvent.actorRef;
|
|
2730
|
-
if (actorRef?._parent) return;
|
|
2731
|
-
onTrace(stampRootTrace(actorRef, {
|
|
2732
|
-
type: "machine.transition",
|
|
2733
|
-
snapshot: inspectionEvent.snapshot,
|
|
2734
|
-
event: inspectionEvent.event
|
|
2735
|
-
}));
|
|
2736
|
-
};
|
|
2737
|
-
}
|
|
2738
|
-
function isIdleSnapshot(snapshot, { ignoreUserInputChildren }) {
|
|
2739
|
-
if (snapshot.status !== "active") return false;
|
|
2740
|
-
if (Object.values(snapshot.children ?? {}).some((child) => {
|
|
2741
|
-
const ref = child;
|
|
2742
|
-
if (ignoreUserInputChildren && ref?.src === "agent.userInput") return false;
|
|
2743
|
-
const childSnapshot = ref?.getSnapshot?.();
|
|
2744
|
-
if (childSnapshot?.status !== "active") return false;
|
|
2745
|
-
if ((0, xstate.isMachineSnapshot)(childSnapshot)) return !isIdleSnapshot(childSnapshot, { ignoreUserInputChildren });
|
|
2746
|
-
return true;
|
|
2747
|
-
})) return false;
|
|
2748
|
-
return !(0, xstate.getNextTransitions)(snapshot).some((transitionDef) => transitionDef.eventType === "" || transitionDef.eventType.startsWith("xstate.after"));
|
|
2749
|
-
}
|
|
2750
|
-
/**
|
|
2751
|
-
* Default machine-owned idle signal used by {@link runAgent}: an active
|
|
2752
|
-
* snapshot that accepts an external event anywhere in its active hierarchy,
|
|
2753
|
-
* or declares interaction metadata on an active state.
|
|
2754
|
-
*
|
|
2755
|
-
* Compose this in `setupAgent({ isIdle })` when the application has additional
|
|
2756
|
-
* wait states: `isIdle: (snapshot) => isAgentIdle(snapshot) || snapshot.hasTag('waiting')`.
|
|
2757
|
-
* Pending work and active invoked children are checked separately by the
|
|
2758
|
-
* runner, so this function only answers whether the state is an intentional
|
|
2759
|
-
* external wait.
|
|
2760
|
-
*/
|
|
2761
|
-
function isAgentIdle(snapshot) {
|
|
2762
|
-
if (snapshot.status !== "active") return false;
|
|
2763
|
-
return snapshot.nodes.some((node) => {
|
|
2764
|
-
const meta = node.meta;
|
|
2765
|
-
return typeof meta === "object" && meta !== null && "interaction" in meta;
|
|
2766
|
-
}) || require_decision.getAcceptedEvents(snapshot).length > 0;
|
|
2767
|
-
}
|
|
2768
|
-
function collectPendingUserInputs(snapshot) {
|
|
2769
|
-
const pending = [];
|
|
2770
|
-
for (const [id, child] of Object.entries(snapshot.children ?? {})) {
|
|
2771
|
-
const ref = child;
|
|
2772
|
-
if (ref?.src !== "agent.userInput") continue;
|
|
2773
|
-
const childSnapshot = ref.getSnapshot?.();
|
|
2774
|
-
if (childSnapshot?.status !== "active") continue;
|
|
2775
|
-
pending.push({
|
|
2776
|
-
id,
|
|
2777
|
-
input: childSnapshot.input
|
|
2778
|
-
});
|
|
2779
|
-
}
|
|
2780
|
-
return pending;
|
|
2781
|
-
}
|
|
2782
|
-
//#endregion
|
|
2783
|
-
//#region src/agent-run.ts
|
|
2784
|
-
/**
|
|
2785
|
-
* Wraps {@link runAgent} in a canonical run-stream handle: the run's trace
|
|
2786
|
-
* events as a pull-based {@link AgentRun.events} async iterator, alongside the
|
|
2787
|
-
* unchanged {@link AgentRun.result} promise.
|
|
2788
|
-
*
|
|
2789
|
-
* Semantics:
|
|
2790
|
-
*
|
|
2791
|
-
* 1. **Starts immediately.** The underlying `runAgent` is invoked on this call,
|
|
2792
|
-
* not on the first `events` iteration — work is already in flight when
|
|
2793
|
-
* `createAgentRun` returns.
|
|
2794
|
-
* 2. **Buffer, don't stall.** Events are queued in an unbounded in-memory
|
|
2795
|
-
* buffer as the run emits them; a slow or absent consumer never applies
|
|
2796
|
-
* backpressure to the run. Once `run.end` is delivered, the iterator
|
|
2797
|
-
* completes.
|
|
2798
|
-
* 3. **Composes `onTrace`.** If `options.onTrace` is passed, it still fires for
|
|
2799
|
-
* every event — the wrapper adds queue delivery, it does not replace the
|
|
2800
|
-
* caller's sink.
|
|
2801
|
-
* 4. **`result` mirrors `runAgent`.** Same resolution and rejection behavior
|
|
2802
|
-
* (see {@link AgentRun.result}). A no-op rejection handler is attached
|
|
2803
|
-
* internally so a caller who reads only `events` never trips an unhandled
|
|
2804
|
-
* rejection on a bind-time throw; the returned promise still rejects for a
|
|
2805
|
-
* caller who awaits it.
|
|
2806
|
-
* 5. **Resume is identical.** Options pass straight through, so starting from a
|
|
2807
|
-
* persisted `snapshot` (+ resume `event`) streams that run's events from its
|
|
2808
|
-
* own `run.start`, exactly as `runAgent` would run it.
|
|
2809
|
-
* 6. **Early termination does not cancel the run.** Breaking out of a
|
|
2810
|
-
* `for await` (or calling `events.return()`) stops delivery, but the run
|
|
2811
|
-
* keeps going and `result` still settles. Cancelling the run itself is
|
|
2812
|
-
* future work — pass `options.signal` to abort it.
|
|
2813
|
-
* 7. **`events` is single-consumer.** See {@link AgentRun.events}.
|
|
2814
|
-
*/
|
|
2815
|
-
function createAgentRun(machine, options) {
|
|
2816
|
-
const buffer = [];
|
|
2817
|
-
const resolvers = [];
|
|
2818
|
-
let closed = false;
|
|
2819
|
-
let stopped = false;
|
|
2820
|
-
const push = (event) => {
|
|
2821
|
-
if (closed || stopped) return;
|
|
2822
|
-
const resolve = resolvers.shift();
|
|
2823
|
-
if (resolve) resolve({
|
|
2824
|
-
value: event,
|
|
2825
|
-
done: false
|
|
2826
|
-
});
|
|
2827
|
-
else buffer.push(event);
|
|
2828
|
-
};
|
|
2829
|
-
const drain = () => {
|
|
2830
|
-
while (resolvers.length > 0) resolvers.shift()({
|
|
2831
|
-
value: void 0,
|
|
2832
|
-
done: true
|
|
2833
|
-
});
|
|
2834
|
-
};
|
|
2835
|
-
const close = () => {
|
|
2836
|
-
if (closed) return;
|
|
2837
|
-
closed = true;
|
|
2838
|
-
drain();
|
|
2839
|
-
};
|
|
2840
|
-
const userOnTrace = options.onTrace;
|
|
2841
|
-
const result = runAgent(machine, {
|
|
2842
|
-
...options,
|
|
2843
|
-
onTrace: (event) => {
|
|
2844
|
-
push(event);
|
|
2845
|
-
userOnTrace?.(event);
|
|
2846
|
-
if (event.type === "run.end") close();
|
|
2847
|
-
}
|
|
2848
|
-
});
|
|
2849
|
-
result.then(close, close);
|
|
2850
|
-
return {
|
|
2851
|
-
events: {
|
|
2852
|
-
next() {
|
|
2853
|
-
if (buffer.length > 0) return Promise.resolve({
|
|
2854
|
-
value: buffer.shift(),
|
|
2855
|
-
done: false
|
|
2856
|
-
});
|
|
2857
|
-
if (closed || stopped) return Promise.resolve({
|
|
2858
|
-
value: void 0,
|
|
2859
|
-
done: true
|
|
2860
|
-
});
|
|
2861
|
-
return new Promise((resolve) => {
|
|
2862
|
-
resolvers.push(resolve);
|
|
2863
|
-
});
|
|
2864
|
-
},
|
|
2865
|
-
return() {
|
|
2866
|
-
stopped = true;
|
|
2867
|
-
buffer.length = 0;
|
|
2868
|
-
drain();
|
|
2869
|
-
return Promise.resolve({
|
|
2870
|
-
value: void 0,
|
|
2871
|
-
done: true
|
|
2872
|
-
});
|
|
2873
|
-
},
|
|
2874
|
-
[Symbol.asyncIterator]() {
|
|
2875
|
-
return this;
|
|
2876
|
-
}
|
|
2877
|
-
},
|
|
2878
|
-
result
|
|
2879
|
-
};
|
|
2880
|
-
}
|
|
2881
|
-
function requestIdentity(request) {
|
|
2882
|
-
const candidate = request;
|
|
2883
|
-
const id = candidate.id ?? candidate.name ?? "agent.request";
|
|
2884
|
-
return {
|
|
2885
|
-
name: candidate.kind === "text" ? candidate.input?.name ?? id : candidate.name ?? id,
|
|
2886
|
-
id
|
|
2887
|
-
};
|
|
2888
|
-
}
|
|
2889
|
-
/**
|
|
2890
|
-
* Streams one {@link runAgent} call as agent-level events. The machine remains
|
|
2891
|
-
* the single artifact; this function only projects its requests, chunks,
|
|
2892
|
-
* transitions, emitted events, and terminal result into an async iterable.
|
|
2893
|
-
*/
|
|
2894
|
-
async function* runAgentStream(machine, options) {
|
|
2895
|
-
const run = createAgentRun(machine, options);
|
|
2896
|
-
for await (const event of run.events) switch (event.type) {
|
|
2897
|
-
case "request.start":
|
|
2898
|
-
yield {
|
|
2899
|
-
kind: "request",
|
|
2900
|
-
phase: "start",
|
|
2901
|
-
...requestIdentity(event.request),
|
|
2902
|
-
request: event.request
|
|
2903
|
-
};
|
|
2904
|
-
break;
|
|
2905
|
-
case "request.end":
|
|
2906
|
-
yield {
|
|
2907
|
-
kind: "request",
|
|
2908
|
-
phase: "end",
|
|
2909
|
-
...requestIdentity(event.request),
|
|
2910
|
-
request: event.request,
|
|
2911
|
-
output: event.output,
|
|
2912
|
-
raw: event.raw
|
|
2913
|
-
};
|
|
2914
|
-
break;
|
|
2915
|
-
case "request.error":
|
|
2916
|
-
yield {
|
|
2917
|
-
kind: "request",
|
|
2918
|
-
phase: "error",
|
|
2919
|
-
...requestIdentity(event.request),
|
|
2920
|
-
request: event.request,
|
|
2921
|
-
error: event.error
|
|
2922
|
-
};
|
|
2923
|
-
break;
|
|
2924
|
-
case "stream.chunk":
|
|
2925
|
-
yield {
|
|
2926
|
-
kind: "chunk",
|
|
2927
|
-
...requestIdentity(event.request),
|
|
2928
|
-
delta: event.chunk
|
|
2929
|
-
};
|
|
2930
|
-
break;
|
|
2931
|
-
case "machine.transition":
|
|
2932
|
-
yield {
|
|
2933
|
-
kind: "transition",
|
|
2934
|
-
value: event.snapshot.value,
|
|
2935
|
-
event: event.event
|
|
2936
|
-
};
|
|
2937
|
-
break;
|
|
2938
|
-
case "emit":
|
|
2939
|
-
yield {
|
|
2940
|
-
kind: "emit",
|
|
2941
|
-
event: event.event
|
|
2942
|
-
};
|
|
2943
|
-
break;
|
|
2944
|
-
}
|
|
2945
|
-
const result = await run.result;
|
|
2946
|
-
if (result.status === "done") yield {
|
|
2947
|
-
kind: "done",
|
|
2948
|
-
result
|
|
2949
|
-
};
|
|
2950
|
-
else if (result.status === "idle") yield {
|
|
2951
|
-
kind: "idle",
|
|
2952
|
-
result
|
|
2953
|
-
};
|
|
2954
|
-
else yield {
|
|
2955
|
-
kind: "error",
|
|
2956
|
-
result
|
|
2957
|
-
};
|
|
2958
|
-
}
|
|
2959
|
-
//#endregion
|
|
2960
|
-
//#region src/run-loop.ts
|
|
2961
|
-
function addUsage(total, next) {
|
|
2962
|
-
const merged = { ...total };
|
|
2963
|
-
for (const [key, value] of Object.entries(next)) if (typeof value === "number") merged[key] = (merged[key] ?? 0) + value;
|
|
2964
|
-
return merged;
|
|
2965
|
-
}
|
|
2966
|
-
/** Drives one machine artifact across as many idle/resume turns as the host
|
|
2967
|
-
* supplies. XState snapshots remain the continuation; this helper owns no
|
|
2968
|
-
* persistence, retry, tool-loop, or storage policy. */
|
|
2969
|
-
async function runAgentLoop(machine, options) {
|
|
2970
|
-
const { onIdle, persist, maxTurns = 100, events: initialEvents, ...runOptions } = options;
|
|
2971
|
-
if (!Number.isInteger(maxTurns) || maxTurns < 0) throw new Error("runAgentLoop: maxTurns must be a non-negative integer.");
|
|
2972
|
-
let usage = { modelCalls: 0 };
|
|
2973
|
-
let snapshot;
|
|
2974
|
-
let event;
|
|
2975
|
-
const stored = options.store !== void 0;
|
|
2976
|
-
let events = initialEvents;
|
|
2977
|
-
for (let turn = 0;; turn++) {
|
|
2978
|
-
const result = await runAgent(machine, {
|
|
2979
|
-
...runOptions,
|
|
2980
|
-
...events ? { events } : {},
|
|
2981
|
-
...snapshot ? {
|
|
2982
|
-
snapshot,
|
|
2983
|
-
event
|
|
2984
|
-
} : {}
|
|
2985
|
-
});
|
|
2986
|
-
events = stored ? void 0 : result.events;
|
|
2987
|
-
usage = addUsage(usage, result.usage);
|
|
2988
|
-
const cumulative = {
|
|
2989
|
-
...result,
|
|
2990
|
-
usage
|
|
2991
|
-
};
|
|
2992
|
-
if (result.status !== "idle" || !onIdle || turn >= maxTurns) return cumulative;
|
|
2993
|
-
snapshot = result.persist();
|
|
2994
|
-
await persist?.(snapshot, result);
|
|
2995
|
-
event = await onIdle(result) ?? void 0;
|
|
2996
|
-
if (!event) return cumulative;
|
|
2997
|
-
}
|
|
2998
|
-
}
|
|
2999
|
-
//#endregion
|
|
3000
|
-
//#region src/provide-executors.ts
|
|
3001
|
-
/**
|
|
3002
|
-
* Binds a machine's agent actor sources to a set of host `executors` in one
|
|
3003
|
-
* call, returning a `machine.provide(...)`-ed copy ready for a plain
|
|
3004
|
-
* `createActor(...)` — the uncontrolled-mode counterpart to {@link runAgent}.
|
|
3005
|
-
* No run loop, no idle settling: the returned machine drives itself, so
|
|
3006
|
-
*
|
|
3007
|
-
* ```ts
|
|
3008
|
-
* const actor = createActor(provideExecutors(machine, { generateText, decide }), { input });
|
|
3009
|
-
* actor.start();
|
|
3010
|
-
* ```
|
|
3011
|
-
*
|
|
3012
|
-
* behaves like a normal XState actor whose agent invokes now reach real models.
|
|
3013
|
-
*
|
|
3014
|
-
* Binding pass over `machine.sources.actors` (after merging
|
|
3015
|
-
* `options.actors`):
|
|
3016
|
-
* - `mode: 'generate'` text source → `executors.generateText`
|
|
3017
|
-
* - `mode: 'stream'` text source → `executors.streamText`
|
|
3018
|
-
* - decision / `agent.decide` source → `executors.decide` (snapshot-driven
|
|
3019
|
-
* candidate events, guard `canTake`, and auto-delivery of the chosen event,
|
|
3020
|
-
* mirroring `runAgent` but without its model-call counting)
|
|
3021
|
-
*
|
|
3022
|
-
* Pass `options.onTrace` to observe request-level trace events (identical in
|
|
3023
|
-
* shape to `runAgent`'s); pair it with {@link traceTransitions} on the actor's
|
|
3024
|
-
* `inspect` to also capture `machine.transition` events in the same stream.
|
|
3025
|
-
*
|
|
3026
|
-
* A source that already carries its own executor (`.withExecutor(...)`) is left
|
|
3027
|
-
* as-is. `agent.userInput` is left UNBOUND — an uncontrolled host handles idle
|
|
3028
|
-
* itself, so supply a handler via `options.actors` if the machine uses it.
|
|
3029
|
-
* Non-agent actors are untouched.
|
|
3030
|
-
*
|
|
3031
|
-
* Throws at bind time if a source needs an executor kind that `executors` does
|
|
3032
|
-
* not provide.
|
|
3033
|
-
*
|
|
3034
|
-
* Executor inheritance is RECURSIVE, exactly as in `runAgent`: a string-keyed
|
|
3035
|
-
* invoked child machine is rebound too, so its own text/decision requests — at
|
|
3036
|
-
* any depth — reach the same host executors. A direct-object invoke `src`
|
|
3037
|
-
* cannot be swapped via `.provide`, so nothing under one inherits; bind those
|
|
3038
|
-
* with `.withExecutor(...)` or register the child as a string-keyed source. A
|
|
3039
|
-
* source that already carries its own executor is never overwritten.
|
|
3040
|
-
*/
|
|
3041
|
-
function provideExecutors(machine, executors, options = {}) {
|
|
3042
|
-
const bindOptions = {
|
|
3043
|
-
onChunk: options.onChunk,
|
|
3044
|
-
onTrace: options.onTrace
|
|
3045
|
-
};
|
|
3046
|
-
const withActors = (target, actors) => target.provide({ actors });
|
|
3047
|
-
const provided = options.actors ? withActors(machine, options.actors) : machine;
|
|
3048
|
-
const effectiveSources = provided.sources.actors;
|
|
3049
|
-
const wrappedSources = {};
|
|
3050
|
-
const invokedSrcs = getConfiguredInvokeSrcs(provided);
|
|
3051
|
-
for (const [key, logic] of Object.entries(effectiveSources)) {
|
|
3052
|
-
if (key === "agent.userInput") continue;
|
|
3053
|
-
if (isStateMachineLogic(logic)) {
|
|
3054
|
-
if (invokedSrcs.has(key)) assertChildBindable(logic, executors, key, new Set([provided]));
|
|
3055
|
-
const rebound = bindChildMachineForProvide(logic, executors, bindOptions, new Set([provided]));
|
|
3056
|
-
if (rebound !== logic) wrappedSources[key] = rebound;
|
|
3057
|
-
continue;
|
|
3058
|
-
}
|
|
3059
|
-
const requirement = executorRequirementOf(logic);
|
|
3060
|
-
if (!requirement) continue;
|
|
3061
|
-
const binding = {
|
|
3062
|
-
...requirement,
|
|
3063
|
-
bind: () => require_decision.isDecisionLogic(logic) ? bindDecisionForProvide(provided, logic, executors, bindOptions) : bindTextForProvide(provided, logic, executors, bindOptions)
|
|
3064
|
-
};
|
|
3065
|
-
if (require_decision.executorBoundLogics.has(logic)) continue;
|
|
3066
|
-
if (!executors[binding.executorKey]) {
|
|
3067
|
-
if (invokedSrcs.has(key)) throw missingExecutorError(key, binding.kind, binding.executorKey);
|
|
3068
|
-
continue;
|
|
3069
|
-
}
|
|
3070
|
-
wrappedSources[key] = binding.bind();
|
|
3071
|
-
}
|
|
3072
|
-
return withActors(provided, wrappedSources);
|
|
3073
|
-
}
|
|
3074
|
-
/** The executor slot + label an agent logic needs, or `undefined` for a non-agent actor. */
|
|
3075
|
-
function executorRequirementOf(logic) {
|
|
3076
|
-
if (require_decision.isDecisionLogic(logic)) return {
|
|
3077
|
-
executorKey: "decide",
|
|
3078
|
-
kind: "decision"
|
|
3079
|
-
};
|
|
3080
|
-
if (require_decision.isTextLogic(logic)) return logic.mode === "stream" ? {
|
|
3081
|
-
executorKey: "streamText",
|
|
3082
|
-
kind: "streaming text"
|
|
3083
|
-
} : {
|
|
3084
|
-
executorKey: "generateText",
|
|
3085
|
-
kind: "text"
|
|
3086
|
-
};
|
|
3087
|
-
}
|
|
3088
|
-
/**
|
|
3089
|
-
* Walks an invoked child machine's own invoked sources (recursively, at any
|
|
3090
|
-
* depth) and throws the same missing-executor error `provideExecutors` throws
|
|
3091
|
-
* for the top-level machine — before any actor starts. Mirrors runAgent's
|
|
3092
|
-
* `assertBindable` for the uncontrolled path. Cycle-safe via `visited`.
|
|
3093
|
-
*/
|
|
3094
|
-
function assertChildBindable(childMachine, executors, path, visited) {
|
|
3095
|
-
if (visited.has(childMachine)) return;
|
|
3096
|
-
const nextVisited = new Set([...visited, childMachine]);
|
|
3097
|
-
const sources = childMachine.sources.actors;
|
|
3098
|
-
for (const src of getConfiguredInvokeSrcs(childMachine)) {
|
|
3099
|
-
if (src === "agent.userInput") continue;
|
|
3100
|
-
const logic = sources[src];
|
|
3101
|
-
if (!logic) continue;
|
|
3102
|
-
if (isStateMachineLogic(logic)) {
|
|
3103
|
-
assertChildBindable(logic, executors, `${path} > ${src}`, nextVisited);
|
|
3104
|
-
continue;
|
|
3105
|
-
}
|
|
3106
|
-
const requirement = executorRequirementOf(logic);
|
|
3107
|
-
if (!requirement || require_decision.executorBoundLogics.has(logic)) continue;
|
|
3108
|
-
if (!executors[requirement.executorKey]) throw missingExecutorError(`${path} > ${src}`, requirement.kind, requirement.executorKey);
|
|
3109
|
-
}
|
|
3110
|
-
}
|
|
3111
|
-
function missingExecutorError(src, kind, executor) {
|
|
3112
|
-
return /* @__PURE__ */ new Error(`provideExecutors: actor source '${src}' is a ${kind} source but no '${executor}' executor was provided. Add it to the executors object, or bind the source with its own executor (logic.withExecutor(...)) before calling provideExecutors.`);
|
|
3113
|
-
}
|
|
3114
|
-
//#endregion
|
|
3115
|
-
//#region src/verify.ts
|
|
3116
|
-
const DECIDE_SRC = "agent.decide";
|
|
3117
|
-
const USER_INPUT_SRC = "agent.userInput";
|
|
3118
|
-
function normalizeInvokes(invoke) {
|
|
3119
|
-
if (invoke === void 0 || invoke === null) return [];
|
|
3120
|
-
return Array.isArray(invoke) ? invoke : [invoke];
|
|
3121
|
-
}
|
|
3122
|
-
function buildStateIndex(rootConfig) {
|
|
3123
|
-
const index = /* @__PURE__ */ new Map();
|
|
3124
|
-
const walk = (states, parentPath) => {
|
|
3125
|
-
for (const [name, config] of Object.entries(states ?? {})) {
|
|
3126
|
-
const path = parentPath ? `${parentPath}.${name}` : name;
|
|
3127
|
-
const hasChildren = !!config.states && Object.keys(config.states).length > 0;
|
|
3128
|
-
index.set(path, {
|
|
3129
|
-
path,
|
|
3130
|
-
config,
|
|
3131
|
-
parentPath,
|
|
3132
|
-
isFinal: config.type === "final",
|
|
3133
|
-
isParallel: config.type === "parallel",
|
|
3134
|
-
isCompound: hasChildren && config.type !== "parallel",
|
|
3135
|
-
invokes: normalizeInvokes(config.invoke)
|
|
3136
|
-
});
|
|
3137
|
-
if (hasChildren) walk(config.states, path);
|
|
3138
|
-
}
|
|
3139
|
-
};
|
|
3140
|
-
walk(rootConfig.states, "");
|
|
3141
|
-
return index;
|
|
3142
|
-
}
|
|
3143
|
-
function ancestorChain(node, index) {
|
|
3144
|
-
const chain = [];
|
|
3145
|
-
let parent = node.parentPath;
|
|
3146
|
-
while (parent) {
|
|
3147
|
-
const parentNode = index.get(parent);
|
|
3148
|
-
if (!parentNode) break;
|
|
3149
|
-
chain.push(parentNode);
|
|
3150
|
-
parent = parentNode.parentPath;
|
|
3151
|
-
}
|
|
3152
|
-
return chain;
|
|
3153
|
-
}
|
|
3154
|
-
function hasNonEmptyOn(config) {
|
|
3155
|
-
return !!config.on && Object.keys(config.on).length > 0;
|
|
3156
|
-
}
|
|
3157
|
-
function isDecisionInvoke(src, actors) {
|
|
3158
|
-
if (typeof src === "string") return src === DECIDE_SRC || require_decision.isDecisionLogic(actors[src]);
|
|
3159
|
-
return require_decision.isDecisionLogic(src);
|
|
3160
|
-
}
|
|
3161
|
-
function isAgentLogicNeedingBinding(src) {
|
|
3162
|
-
return (require_decision.isTextLogic(src) || require_decision.isDecisionLogic(src)) && !require_decision.executorBoundLogics.has(src);
|
|
3163
|
-
}
|
|
3164
|
-
function checkDecideWithoutEvents(ctx) {
|
|
3165
|
-
const out = [];
|
|
3166
|
-
for (const node of ctx.index.values()) for (const invoke of node.invokes) {
|
|
3167
|
-
if (!isDecisionInvoke(invoke.src, ctx.actors)) continue;
|
|
3168
|
-
const selfHandles = hasNonEmptyOn(node.config);
|
|
3169
|
-
const ancestorHandles = ancestorChain(node, ctx.index).some((ancestor) => hasNonEmptyOn(ancestor.config));
|
|
3170
|
-
const invokeObserves = invoke.onDone !== void 0;
|
|
3171
|
-
if (selfHandles || ancestorHandles || invokeObserves) continue;
|
|
3172
|
-
const srcName = typeof invoke.src === "string" ? invoke.src : "(inline logic)";
|
|
3173
|
-
out.push({
|
|
3174
|
-
code: "decide-without-events",
|
|
3175
|
-
severity: "error",
|
|
3176
|
-
path: node.path,
|
|
3177
|
-
message: `State '${node.path}' invokes decision source '${srcName}', but neither it nor any ancestor handles any event (no 'on:'), so the decision's chosen event can never be delivered. Add an 'on:' handler for the candidate events.`
|
|
3178
|
-
});
|
|
3179
|
-
}
|
|
3180
|
-
return out;
|
|
3181
|
-
}
|
|
3182
|
-
function checkDirectObjectSrc(ctx) {
|
|
3183
|
-
const out = [];
|
|
3184
|
-
for (const node of ctx.index.values()) for (const invoke of node.invokes) {
|
|
3185
|
-
const src = invoke.src;
|
|
3186
|
-
if (typeof src === "string" || !src || typeof src !== "object") continue;
|
|
3187
|
-
if (!isAgentLogicNeedingBinding(src)) continue;
|
|
3188
|
-
out.push({
|
|
3189
|
-
code: "direct-object-src",
|
|
3190
|
-
severity: "warning",
|
|
3191
|
-
path: node.path,
|
|
3192
|
-
message: `State '${node.path}' invokes a direct-object agent logic. Direct-object invoke srcs cannot be rebound by runAgent, so they inherit no host executors — call '.withExecutor(...)' on the logic, or register it as a string-keyed actor source (machine.provide({ actors: { name: logic } })) and invoke it by name.`
|
|
3193
|
-
});
|
|
3194
|
-
}
|
|
3195
|
-
return out;
|
|
3196
|
-
}
|
|
3197
|
-
function handlesActorError(config) {
|
|
3198
|
-
if (config["onError"] !== void 0) return true;
|
|
3199
|
-
return Object.keys(config.on ?? {}).some((type) => type === "*" || type.startsWith("xstate.error"));
|
|
3200
|
-
}
|
|
3201
|
-
function checkInvokeWithoutOnError(ctx) {
|
|
3202
|
-
const out = [];
|
|
3203
|
-
for (const node of ctx.index.values()) for (const invoke of node.invokes) {
|
|
3204
|
-
if (invoke.onError !== void 0 || handlesActorError(node.config)) continue;
|
|
3205
|
-
if (handlesActorError(ctx.config) || ancestorChain(node, ctx.index).some((ancestor) => handlesActorError(ancestor.config))) continue;
|
|
3206
|
-
const srcName = typeof invoke.src === "string" ? invoke.src : "(inline logic)";
|
|
3207
|
-
out.push({
|
|
3208
|
-
code: "invoke-without-on-error",
|
|
3209
|
-
severity: "warning",
|
|
3210
|
-
path: node.path,
|
|
3211
|
-
message: `State '${node.path}' invokes '${srcName}' with no 'onError' transition, and no ancestor handles an actor error. A rejected request or actor puts the machine in an error state with no modeled recovery. Add 'onError' (a 'failed' final state, or a bounded retry).`
|
|
3212
|
-
});
|
|
3213
|
-
}
|
|
3214
|
-
return out;
|
|
3215
|
-
}
|
|
3216
|
-
function checkUnhandledAgentMessages(ctx) {
|
|
3217
|
-
const handlesMessages = (config) => config.on?.["agent.messages"] !== void 0 || config.on?.["*"] !== void 0;
|
|
3218
|
-
if (handlesMessages(ctx.config) || [...ctx.index.values()].some((node) => handlesMessages(node.config))) return [];
|
|
3219
|
-
let contextJsonSchema;
|
|
3220
|
-
try {
|
|
3221
|
-
contextJsonSchema = require_decision.getJsonSchemaSync(ctx.schemas?.context);
|
|
3222
|
-
} catch {}
|
|
3223
|
-
if (!contextJsonSchema?.properties?.messages) return [];
|
|
3224
|
-
return [...ctx.index.values()].some((node) => node.invokes.some((invoke) => {
|
|
3225
|
-
if (typeof invoke.src !== "string") return false;
|
|
3226
|
-
return invoke.src === "agent.generateText" || invoke.src === "agent.streamText" || require_decision.isTextLogic(ctx.actors[invoke.src]);
|
|
3227
|
-
})) ? [{
|
|
3228
|
-
code: "unhandled-agent-messages",
|
|
3229
|
-
severity: "warning",
|
|
3230
|
-
path: "(root)",
|
|
3231
|
-
message: `Text requests may return framework messages, but the machine does not handle '${require_decision.AGENT_MESSAGES_EVENT_TYPE}'. Add on: { '${require_decision.AGENT_MESSAGES_EVENT_TYPE}': appendMessages() } when transcript retention is intended, or disable this warning when messages are intentionally ignored.`
|
|
3232
|
-
}] : [];
|
|
3233
|
-
}
|
|
3234
|
-
const LINT_CHECKS = [
|
|
3235
|
-
checkDecideWithoutEvents,
|
|
3236
|
-
checkDirectObjectSrc,
|
|
3237
|
-
checkInvokeWithoutOnError,
|
|
3238
|
-
checkUnhandledAgentMessages
|
|
3239
|
-
];
|
|
3240
|
-
/**
|
|
3241
|
-
* Runs static structural checks over a built agent machine and returns the
|
|
3242
|
-
* findings ({@link AgentLintDiagnostic}[], empty when clean). Works for
|
|
3243
|
-
* TS-authored (`setupAgent(...).createMachine(...)`) and
|
|
3244
|
-
* `setupAgent.fromConfig(...)`-compiled machines alike, reading `machine.config`
|
|
3245
|
-
* plus the schemas/actor sources the library already retains per machine.
|
|
3246
|
-
*
|
|
3247
|
-
* No model calls, no API keys — a coding agent that emits an agent machine can
|
|
3248
|
-
* call this to catch agent-specific mistakes such as undeliverable decisions,
|
|
3249
|
-
* un-rebindable request sources, and an unhandled transcript event. General
|
|
3250
|
-
* state-machine lint belongs to XState tooling.
|
|
3251
|
-
*
|
|
3252
|
-
* Pass `{ throw: true }` for the one-liner form used in tests and generation
|
|
3253
|
-
* loops: it returns silently when the machine is clean and throws
|
|
3254
|
-
* {@link AgentLintError} (findings on `.diagnostics`) on error-severity
|
|
3255
|
-
* findings, or on warnings too with `{ throw: true, warnings: true }`.
|
|
3256
|
-
*
|
|
3257
|
-
* @example
|
|
3258
|
-
* ```ts
|
|
3259
|
-
* const errors = lintAgentMachine(machine).filter((d) => d.severity === 'error');
|
|
3260
|
-
* if (errors.length) throw new Error(errors.map((e) => `${e.path}: ${e.message}`).join('\n'));
|
|
3261
|
-
* ```
|
|
3262
|
-
*
|
|
3263
|
-
* @example Throwing form
|
|
3264
|
-
* ```ts
|
|
3265
|
-
* test('agent machine is structurally sound', () => {
|
|
3266
|
-
* lintAgentMachine(machine, { throw: true });
|
|
3267
|
-
* });
|
|
3268
|
-
* ```
|
|
3269
|
-
*/
|
|
3270
|
-
function lintAgentMachine(machine, options = {}) {
|
|
3271
|
-
const config = machine.config ?? {};
|
|
3272
|
-
const index = buildStateIndex(config);
|
|
3273
|
-
const registered = require_decision.getRegisteredAgentExecutionOptions(machine);
|
|
3274
|
-
const ctx = {
|
|
3275
|
-
config,
|
|
3276
|
-
index,
|
|
3277
|
-
schemas: registered.schemas,
|
|
3278
|
-
actors: registered.actors ?? machine.sources?.actors ?? {}
|
|
3279
|
-
};
|
|
3280
|
-
const disabled = new Set(options.disable ?? []);
|
|
3281
|
-
const diagnostics = LINT_CHECKS.flatMap((check) => check(ctx)).filter((d) => !disabled.has(d.code));
|
|
3282
|
-
if (options.throw) {
|
|
3283
|
-
const failing = options.warnings ? diagnostics : diagnostics.filter((d) => d.severity === "error");
|
|
3284
|
-
if (failing.length > 0) throw new AgentLintError(machine.id ?? "(machine)", failing);
|
|
3285
|
-
}
|
|
3286
|
-
return diagnostics;
|
|
3287
|
-
}
|
|
3288
|
-
/**
|
|
3289
|
-
* Thrown by `lintAgentMachine(machine, { throw: true })` (and its
|
|
3290
|
-
* {@link assertAgentMachine} alias) when lint finds failing diagnostics.
|
|
3291
|
-
* `diagnostics` holds the findings; the message lists them one per finding,
|
|
3292
|
-
* so a test runner's failure output reads like the CLI's lint report.
|
|
3293
|
-
*/
|
|
3294
|
-
var AgentLintError = class extends require_decision.AgentError {
|
|
3295
|
-
diagnostics;
|
|
3296
|
-
constructor(machineId, diagnostics) {
|
|
3297
|
-
const lines = diagnostics.map((d) => ` ${d.severity === "error" ? "error" : "warn "} ${d.code} ${d.path}\n ${d.message}`);
|
|
3298
|
-
super("lint-failed", `Agent machine '${machineId}' failed lint (${diagnostics.length} finding(s)):\n${lines.join("\n")}`);
|
|
3299
|
-
this.name = "AgentLintError";
|
|
3300
|
-
this.diagnostics = diagnostics;
|
|
3301
|
-
}
|
|
3302
|
-
};
|
|
3303
|
-
/**
|
|
3304
|
-
* Asserts a machine passes {@link lintAgentMachine}: returns silently when
|
|
3305
|
-
* clean, throws {@link AgentLintError} (with the findings on `.diagnostics`)
|
|
3306
|
-
* otherwise — sugar for `lintAgentMachine(machine, { ...options, throw: true })`.
|
|
3307
|
-
* Fails on error-severity findings; set `warnings: true` to fail on warnings
|
|
3308
|
-
* too. The one-liner for tests and generation loops:
|
|
3309
|
-
*
|
|
3310
|
-
* @example
|
|
3311
|
-
* ```ts
|
|
3312
|
-
* test('agent machine is structurally sound', () => {
|
|
3313
|
-
* assertAgentMachine(machine);
|
|
3314
|
-
* });
|
|
3315
|
-
* ```
|
|
3316
|
-
*/
|
|
3317
|
-
function assertAgentMachine(machine, options = {}) {
|
|
3318
|
-
lintAgentMachine(machine, {
|
|
3319
|
-
...options,
|
|
3320
|
-
throw: true
|
|
3321
|
-
});
|
|
3322
|
-
}
|
|
3323
|
-
function pendingInvokes(step) {
|
|
3324
|
-
const out = [];
|
|
3325
|
-
for (const metadata of getPendingInvokes(step.snapshot)) if (typeof metadata.src === "string") out.push({
|
|
3326
|
-
id: metadata.id,
|
|
3327
|
-
src: metadata.src
|
|
3328
|
-
});
|
|
3329
|
-
return out;
|
|
3330
|
-
}
|
|
3331
|
-
function takeFromQueue(channel, src) {
|
|
3332
|
-
const queue = channel?.[src];
|
|
3333
|
-
if (queue && queue.length > 0) return {
|
|
3334
|
-
found: true,
|
|
3335
|
-
value: queue.shift()
|
|
3336
|
-
};
|
|
3337
|
-
return { found: false };
|
|
3338
|
-
}
|
|
3339
|
-
/**
|
|
3340
|
-
* Deterministically plays a machine through, resolving each request from a
|
|
3341
|
-
* {@link SimulationScript} instead of a model — no API keys, no model calls.
|
|
3342
|
-
* Runs on the pure step path ({@link initialAgentStep} etc.), so it exercises
|
|
3343
|
-
* the real transition logic. Returns the terminal `status`, final `snapshot`,
|
|
3344
|
-
* and a `trail` of every step taken.
|
|
3345
|
-
*
|
|
3346
|
-
* Decisions run through the live run's own validation/retry core,
|
|
3347
|
-
* {@link resolveDecision}, with the script standing in for the model: each
|
|
3348
|
-
* attempt consumes the next queued {@link ChosenEvent} for that src, so an
|
|
3349
|
-
* unknown, payload-invalid, or guard-rejected event is NOT silently swallowed
|
|
3350
|
-
* — the next queued decision is tried, exactly as a live run re-asks the
|
|
3351
|
-
* model. The decision logic's `maxRetries` caps attempts as it would live;
|
|
3352
|
-
* when retries continue past the end of the queue, the last queued decision
|
|
3353
|
-
* repeats (a scripted model that insists). The repeat applies only within one
|
|
3354
|
-
* decision request's retries — each new decision request must have its own
|
|
3355
|
-
* queued entry, or the dry-script error throws as usual. Exhausting all
|
|
3356
|
-
* attempts delivers
|
|
3357
|
-
* the resulting {@link AgentDecisionExhaustedError} to the machine as the
|
|
3358
|
-
* decision invoke's error (so an `onError` transition observes it, as it
|
|
3359
|
-
* would live); with no `onError` to catch it, it is thrown.
|
|
3360
|
-
*
|
|
3361
|
-
* Throws a descriptive error when the script runs dry mid-request, naming the
|
|
3362
|
-
* pending request's kind, src, and id so the missing scripted response is
|
|
3363
|
-
* obvious.
|
|
3364
|
-
*
|
|
3365
|
-
* @example
|
|
3366
|
-
* ```ts
|
|
3367
|
-
* const { status, snapshot } = simulateAgent(machine, {
|
|
3368
|
-
* input: { topic: 'state machines' },
|
|
3369
|
-
* script: {
|
|
3370
|
-
* decisions: { 'agent.decide': [{ type: 'ESCALATE' }] },
|
|
3371
|
-
* events: [{ type: 'APPROVE' }], // crosses the human gate
|
|
3372
|
-
* },
|
|
3373
|
-
* });
|
|
3374
|
-
* ```
|
|
3375
|
-
*/
|
|
3376
|
-
async function simulateAgent(machine, options) {
|
|
3377
|
-
const maxSteps = options.maxSteps ?? 100;
|
|
3378
|
-
const script = {
|
|
3379
|
-
text: mapValues(options.script.text ?? {}, (arr) => [...arr]),
|
|
3380
|
-
decisions: mapValues(options.script.decisions ?? {}, (arr) => [...arr]),
|
|
3381
|
-
invokes: mapValues(options.script.invokes ?? {}, (arr) => [...arr]),
|
|
3382
|
-
errors: mapValues(options.script.errors ?? {}, (arr) => [...arr]),
|
|
3383
|
-
events: [...options.script.events ?? []]
|
|
3384
|
-
};
|
|
3385
|
-
if (options.script.userInput?.length) script.invokes[USER_INPUT_SRC] = [...options.script.userInput, ...script.invokes[USER_INPUT_SRC] ?? []];
|
|
3386
|
-
let step = initialAgentStep(machine, options.input);
|
|
3387
|
-
const trail = [{ state: step.snapshot.value }];
|
|
3388
|
-
for (let i = 0; i < maxSteps; i++) {
|
|
3389
|
-
if (step.done) return {
|
|
3390
|
-
status: "done",
|
|
3391
|
-
snapshot: step.snapshot,
|
|
3392
|
-
trail
|
|
3393
|
-
};
|
|
3394
|
-
const request = step.requests[0];
|
|
3395
|
-
if (request) {
|
|
3396
|
-
if (request.kind === "decision") {
|
|
3397
|
-
const invokeMeta = findInvokeMetadata(step, request.id);
|
|
3398
|
-
const src = invokeMeta?.src ?? request.id;
|
|
3399
|
-
const decisionSrc = src in (script.decisions ?? {}) ? src : request.id in (script.decisions ?? {}) ? request.id : src;
|
|
3400
|
-
const errorSrc = queueKey(script.errors, src, request.id);
|
|
3401
|
-
const failure = takeFromQueue(script.errors, errorSrc);
|
|
3402
|
-
if (failure.found) {
|
|
3403
|
-
step = rejectScriptedRequest(machine, step, request.id, failure.value, {
|
|
3404
|
-
kind: "decision",
|
|
3405
|
-
src: errorSrc,
|
|
3406
|
-
trail
|
|
3407
|
-
});
|
|
3408
|
-
continue;
|
|
3409
|
-
}
|
|
3410
|
-
step = await applyScriptedDecision(machine, step, request, decisionSrc, invokeMeta?.logic, script, trail);
|
|
3411
|
-
continue;
|
|
3412
|
-
}
|
|
3413
|
-
const failure = takeFromQueue(script.errors, request.src);
|
|
3414
|
-
if (failure.found) {
|
|
3415
|
-
step = rejectScriptedRequest(machine, step, request, failure.value, {
|
|
3416
|
-
kind: "text",
|
|
3417
|
-
src: request.src,
|
|
3418
|
-
trail
|
|
3419
|
-
});
|
|
3420
|
-
continue;
|
|
3421
|
-
}
|
|
3422
|
-
const taken = takeFromQueue(script.text, request.src);
|
|
3423
|
-
if (!taken.found) throw scriptDryError("text", request.src, request.id);
|
|
3424
|
-
step = resolveAgentStep(machine, step, request, taken.value);
|
|
3425
|
-
trail.push({
|
|
3426
|
-
state: step.snapshot.value,
|
|
3427
|
-
resolvedRequest: {
|
|
3428
|
-
kind: "text",
|
|
3429
|
-
src: request.src,
|
|
3430
|
-
id: request.id,
|
|
3431
|
-
outcome: "output"
|
|
3432
|
-
}
|
|
3433
|
-
});
|
|
3434
|
-
continue;
|
|
3435
|
-
}
|
|
3436
|
-
const [invoke] = pendingInvokes(step);
|
|
3437
|
-
if (invoke) {
|
|
3438
|
-
const failure = takeFromQueue(script.errors, invoke.src);
|
|
3439
|
-
if (failure.found) {
|
|
3440
|
-
step = rejectScriptedRequest(machine, step, invoke.id, failure.value, {
|
|
3441
|
-
kind: "userInput",
|
|
3442
|
-
src: invoke.src,
|
|
3443
|
-
trail
|
|
3444
|
-
});
|
|
3445
|
-
continue;
|
|
3446
|
-
}
|
|
3447
|
-
const taken = takeFromQueue(script.invokes, invoke.src);
|
|
3448
|
-
if (!taken.found) throw scriptDryError("userInput", invoke.src, invoke.id);
|
|
3449
|
-
step = resolveAgentStep(machine, step, invoke.id, taken.value);
|
|
3450
|
-
trail.push({
|
|
3451
|
-
state: step.snapshot.value,
|
|
3452
|
-
resolvedRequest: {
|
|
3453
|
-
kind: "userInput",
|
|
3454
|
-
src: invoke.src,
|
|
3455
|
-
id: invoke.id,
|
|
3456
|
-
outcome: "output"
|
|
3457
|
-
}
|
|
3458
|
-
});
|
|
3459
|
-
continue;
|
|
3460
|
-
}
|
|
3461
|
-
const external = script.events;
|
|
3462
|
-
if (external.length > 0) {
|
|
3463
|
-
const event = external.shift();
|
|
3464
|
-
if (!step.snapshot.can(event)) throw new Error(`simulateAgent: scripted external event '${event.type}' cannot be taken in state ${JSON.stringify(step.snapshot.value)} (no handler, or its guard rejected it). A live run's send would be silently dropped here; fix the script's \`events\` queue.`);
|
|
3465
|
-
step = transitionAgentStep(machine, step, event);
|
|
3466
|
-
trail.push({
|
|
3467
|
-
state: step.snapshot.value,
|
|
3468
|
-
appliedEvent: event,
|
|
3469
|
-
external: true
|
|
3470
|
-
});
|
|
3471
|
-
continue;
|
|
3472
|
-
}
|
|
3473
|
-
return {
|
|
3474
|
-
status: "idle",
|
|
3475
|
-
snapshot: step.snapshot,
|
|
3476
|
-
trail
|
|
3477
|
-
};
|
|
3478
|
-
}
|
|
3479
|
-
return {
|
|
3480
|
-
status: "exhausted",
|
|
3481
|
-
snapshot: step.snapshot,
|
|
3482
|
-
trail
|
|
3483
|
-
};
|
|
3484
|
-
}
|
|
3485
|
-
function queueKey(channel, src, id) {
|
|
3486
|
-
return channel && !(src in channel) && id in channel ? id : src;
|
|
3487
|
-
}
|
|
3488
|
-
function rejectScriptedRequest(machine, step, request, error, entry) {
|
|
3489
|
-
const id = typeof request === "string" ? request : request.id;
|
|
3490
|
-
const next = rejectAgentStep(machine, step, request, error);
|
|
3491
|
-
if (next.snapshot.status === "error") throw unhandledScriptedError(error, entry.kind, entry.src, id);
|
|
3492
|
-
entry.trail.push({
|
|
3493
|
-
state: next.snapshot.value,
|
|
3494
|
-
resolvedRequest: {
|
|
3495
|
-
kind: entry.kind,
|
|
3496
|
-
src: entry.src,
|
|
3497
|
-
id,
|
|
3498
|
-
outcome: "error",
|
|
3499
|
-
error
|
|
3500
|
-
}
|
|
3501
|
-
});
|
|
3502
|
-
return next;
|
|
3503
|
-
}
|
|
3504
|
-
function unhandledScriptedError(error, kind, src, id) {
|
|
3505
|
-
if (error instanceof Error) return error;
|
|
3506
|
-
return new Error(`simulateAgent: the scripted error for the ${kind} request '${src}' (id '${id}') reached no onError transition, so the machine errored. Add an onError to that invoke, or drop the errors['${src}'] entry.`, { cause: error });
|
|
3507
|
-
}
|
|
3508
|
-
async function applyScriptedDecision(machine, step, request, decisionSrc, invokeLogic, script, trail) {
|
|
3509
|
-
const dequeued = [];
|
|
3510
|
-
const decide = async () => {
|
|
3511
|
-
const taken = takeFromQueue(script.decisions, decisionSrc);
|
|
3512
|
-
if (taken.found) {
|
|
3513
|
-
dequeued.push(taken.value);
|
|
3514
|
-
return { event: taken.value };
|
|
3515
|
-
}
|
|
3516
|
-
if (dequeued.length === 0) throw scriptDryError("decision", decisionSrc, request.id, request);
|
|
3517
|
-
return { event: dequeued[dequeued.length - 1] };
|
|
3518
|
-
};
|
|
3519
|
-
const maxRetries = (require_decision.isDecisionLogic(invokeLogic) ? invokeLogic : resolveRegisteredDecisionLogic(machine, decisionSrc))?.maxRetries;
|
|
3520
|
-
let chosen;
|
|
3521
|
-
try {
|
|
3522
|
-
chosen = await require_decision.resolveDecision(request, { decide }, {
|
|
3523
|
-
maxRetries,
|
|
3524
|
-
canTake: (event) => step.snapshot.can(event)
|
|
3525
|
-
});
|
|
3526
|
-
} catch (error) {
|
|
3527
|
-
if (!(error instanceof require_decision.AgentDecisionExhaustedError)) throw error;
|
|
3528
|
-
const next = rejectAgentStep(machine, step, request.id, error);
|
|
3529
|
-
if (next.snapshot.status === "error") throw error;
|
|
3530
|
-
trail.push({
|
|
3531
|
-
state: next.snapshot.value,
|
|
3532
|
-
rejectedEvents: dequeued
|
|
3533
|
-
});
|
|
3534
|
-
return next;
|
|
3535
|
-
}
|
|
3536
|
-
const next = transitionAgentStep(machine, step, chosen);
|
|
3537
|
-
const rejected = dequeued.slice(0, -1);
|
|
3538
|
-
trail.push({
|
|
3539
|
-
state: next.snapshot.value,
|
|
3540
|
-
appliedEvent: chosen,
|
|
3541
|
-
...rejected.length > 0 ? { rejectedEvents: rejected } : {}
|
|
3542
|
-
});
|
|
3543
|
-
return next;
|
|
3544
|
-
}
|
|
3545
|
-
function findInvokeMetadata(step, id) {
|
|
3546
|
-
for (const metadata of getPendingInvokes(step.snapshot)) {
|
|
3547
|
-
if (metadata.id !== id) continue;
|
|
3548
|
-
return {
|
|
3549
|
-
...typeof metadata.src === "string" ? { src: metadata.src } : {},
|
|
3550
|
-
logic: metadata.logic ?? (typeof metadata.src === "object" ? metadata.src : void 0)
|
|
3551
|
-
};
|
|
3552
|
-
}
|
|
3553
|
-
}
|
|
3554
|
-
function resolveRegisteredDecisionLogic(machine, src) {
|
|
3555
|
-
const candidate = require_decision.getRegisteredAgentExecutionOptions(machine).actors?.[src] ?? machine.sources?.actors?.[src];
|
|
3556
|
-
return require_decision.isDecisionLogic(candidate) ? candidate : void 0;
|
|
3557
|
-
}
|
|
3558
|
-
function mapValues(obj, fn) {
|
|
3559
|
-
return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, fn(value)]));
|
|
3560
|
-
}
|
|
3561
|
-
function scriptDryError(kind, src, id, request) {
|
|
3562
|
-
const events = request?.kind === "decision" ? ` Candidate events: ${request.events.map((e) => e.type).join(", ") || "(none)"}.` : "";
|
|
3563
|
-
const key = kind === "text" ? `text['${src}']` : kind === "decision" ? `decisions['${src}']` : src === USER_INPUT_SRC ? "userInput" : `invokes['${src}']`;
|
|
3564
|
-
return /* @__PURE__ */ new Error(`simulateAgent: script ran dry on a pending ${kind} request for src '${src}' (id '${id}'). Add an entry to the script's \`${key}\` queue.${events}`);
|
|
3565
|
-
}
|
|
3566
|
-
const MAX_ADVANCE_STEPS = 1e3;
|
|
3567
|
-
function invokeFork(id, src, outputs) {
|
|
3568
|
-
return src in outputs ? {
|
|
3569
|
-
id,
|
|
3570
|
-
src,
|
|
3571
|
-
hasOutput: true,
|
|
3572
|
-
output: outputs[src]
|
|
3573
|
-
} : {
|
|
3574
|
-
id,
|
|
3575
|
-
src,
|
|
3576
|
-
hasOutput: false
|
|
3577
|
-
};
|
|
3578
|
-
}
|
|
3579
|
-
async function explore(machine, options, stopWhen) {
|
|
3580
|
-
const maxDepth = options.maxDepth ?? 8;
|
|
3581
|
-
const maxPaths = options.maxPaths ?? 200;
|
|
3582
|
-
const textScript = options.text ?? {};
|
|
3583
|
-
const invokeOutputs = { ...options.invokes ?? {} };
|
|
3584
|
-
if ("userInput" in options) invokeOutputs[USER_INPUT_SRC] = options.userInput;
|
|
3585
|
-
const errorScript = options.errors ?? {};
|
|
3586
|
-
const reachedStates = /* @__PURE__ */ new Set();
|
|
3587
|
-
const reachedValues = [];
|
|
3588
|
-
const terminals = [];
|
|
3589
|
-
const unexplored = [];
|
|
3590
|
-
let prunedByGuard = 0;
|
|
3591
|
-
let pathsExplored = 0;
|
|
3592
|
-
let hitPathCap = false;
|
|
3593
|
-
let witness;
|
|
3594
|
-
const recordState = (snapshot) => {
|
|
3595
|
-
const key = JSON.stringify(snapshot.value);
|
|
3596
|
-
if (!reachedStates.has(key)) {
|
|
3597
|
-
reachedStates.add(key);
|
|
3598
|
-
reachedValues.push(snapshot.value);
|
|
3599
|
-
}
|
|
3600
|
-
};
|
|
3601
|
-
const initial = initialAgentStep(machine, options.input);
|
|
3602
|
-
recordState(initial.snapshot);
|
|
3603
|
-
if (stopWhen?.(initial.snapshot)) witness = [];
|
|
3604
|
-
const advance = (step) => {
|
|
3605
|
-
let current = step;
|
|
3606
|
-
for (let i = 0; i < MAX_ADVANCE_STEPS; i++) {
|
|
3607
|
-
if (stopWhen?.(current.snapshot)) return {
|
|
3608
|
-
step: current,
|
|
3609
|
-
hit: true
|
|
3610
|
-
};
|
|
3611
|
-
if (current.done) return { step: current };
|
|
3612
|
-
const request = current.requests[0];
|
|
3613
|
-
if (request && request.kind === "text") {
|
|
3614
|
-
if (request.src in errorScript) return {
|
|
3615
|
-
step: current,
|
|
3616
|
-
fork: invokeFork(request.id, request.src, textScript)
|
|
3617
|
-
};
|
|
3618
|
-
if (!(request.src in textScript)) return {
|
|
3619
|
-
step: current,
|
|
3620
|
-
blockedSrc: request.src
|
|
3621
|
-
};
|
|
3622
|
-
current = resolveAgentStep(machine, current, request, textScript[request.src]);
|
|
3623
|
-
recordState(current.snapshot);
|
|
3624
|
-
continue;
|
|
3625
|
-
}
|
|
3626
|
-
if (request && request.kind === "decision") {
|
|
3627
|
-
const src = findInvokeMetadata(current, request.id)?.src;
|
|
3628
|
-
const errorSrc = src !== void 0 && src in errorScript ? src : request.id in errorScript ? request.id : void 0;
|
|
3629
|
-
return errorSrc !== void 0 ? {
|
|
3630
|
-
step: current,
|
|
3631
|
-
fork: {
|
|
3632
|
-
id: request.id,
|
|
3633
|
-
src: errorSrc,
|
|
3634
|
-
hasOutput: false,
|
|
3635
|
-
isDecision: true
|
|
3636
|
-
}
|
|
3637
|
-
} : { step: current };
|
|
3638
|
-
}
|
|
3639
|
-
const [invoke] = pendingInvokes(current);
|
|
3640
|
-
if (invoke) {
|
|
3641
|
-
if (invoke.src in errorScript) return {
|
|
3642
|
-
step: current,
|
|
3643
|
-
fork: invokeFork(invoke.id, invoke.src, invokeOutputs)
|
|
3644
|
-
};
|
|
3645
|
-
if (!(invoke.src in invokeOutputs)) return {
|
|
3646
|
-
step: current,
|
|
3647
|
-
blockedSrc: invoke.src
|
|
3648
|
-
};
|
|
3649
|
-
current = resolveAgentStep(machine, current, invoke.id, invokeOutputs[invoke.src]);
|
|
3650
|
-
recordState(current.snapshot);
|
|
3651
|
-
continue;
|
|
3652
|
-
}
|
|
3653
|
-
return { step: current };
|
|
3654
|
-
}
|
|
3655
|
-
return { step: current };
|
|
3656
|
-
};
|
|
3657
|
-
const visit = async (step, path, startDepth) => {
|
|
3658
|
-
let depth = startDepth;
|
|
3659
|
-
if (witness !== void 0) return;
|
|
3660
|
-
if (pathsExplored >= maxPaths) {
|
|
3661
|
-
hitPathCap = true;
|
|
3662
|
-
return;
|
|
3663
|
-
}
|
|
3664
|
-
const { step: settled, blockedSrc, hit, fork } = advance(step);
|
|
3665
|
-
if (hit) {
|
|
3666
|
-
witness = path;
|
|
3667
|
-
return;
|
|
3668
|
-
}
|
|
3669
|
-
if (fork) {
|
|
3670
|
-
if (depth >= maxDepth) {
|
|
3671
|
-
pathsExplored++;
|
|
3672
|
-
terminals.push({
|
|
3673
|
-
status: "max-depth",
|
|
3674
|
-
path,
|
|
3675
|
-
state: settled.snapshot.value
|
|
3676
|
-
});
|
|
3677
|
-
unexplored.push(`max-depth: stopped at path [${path.map((e) => e.type).join(", ")}]`);
|
|
3678
|
-
return;
|
|
3679
|
-
}
|
|
3680
|
-
const failed = rejectAgentStep(machine, settled, fork.id, errorScript[fork.src]);
|
|
3681
|
-
recordState(failed.snapshot);
|
|
3682
|
-
await visit(failed, [...path, { type: `xstate.error.actor.${fork.id}` }], depth + 1);
|
|
3683
|
-
if (!fork.isDecision) {
|
|
3684
|
-
if (fork.hasOutput) {
|
|
3685
|
-
const resolved = resolveAgentStep(machine, settled, fork.id, fork.output);
|
|
3686
|
-
recordState(resolved.snapshot);
|
|
3687
|
-
await visit(resolved, path, depth + 1);
|
|
3688
|
-
}
|
|
3689
|
-
return;
|
|
3690
|
-
}
|
|
3691
|
-
depth += 1;
|
|
3692
|
-
if (witness !== void 0) return;
|
|
3693
|
-
}
|
|
3694
|
-
if (blockedSrc) {
|
|
3695
|
-
pathsExplored++;
|
|
3696
|
-
terminals.push({
|
|
3697
|
-
status: "needs-output",
|
|
3698
|
-
path,
|
|
3699
|
-
state: settled.snapshot.value,
|
|
3700
|
-
missingSrc: blockedSrc
|
|
3701
|
-
});
|
|
3702
|
-
unexplored.push(`needs-output: no canned output for src '${blockedSrc}' at path [${path.map((e) => e.type).join(", ")}]`);
|
|
3703
|
-
return;
|
|
3704
|
-
}
|
|
3705
|
-
if (settled.done) {
|
|
3706
|
-
pathsExplored++;
|
|
3707
|
-
terminals.push({
|
|
3708
|
-
status: "done",
|
|
3709
|
-
path,
|
|
3710
|
-
state: settled.snapshot.value
|
|
3711
|
-
});
|
|
3712
|
-
return;
|
|
3713
|
-
}
|
|
3714
|
-
if (settled.snapshot.status === "error") {
|
|
3715
|
-
pathsExplored++;
|
|
3716
|
-
terminals.push({
|
|
3717
|
-
status: "error",
|
|
3718
|
-
path,
|
|
3719
|
-
state: settled.snapshot.value,
|
|
3720
|
-
error: settled.snapshot.error
|
|
3721
|
-
});
|
|
3722
|
-
return;
|
|
3723
|
-
}
|
|
3724
|
-
const request = settled.requests[0];
|
|
3725
|
-
const branchEvents = request?.kind === "decision" ? request.events.map((descriptor) => ({ type: descriptor.type })) : require_decision.getAcceptedEvents(settled.snapshot).map((descriptor) => ({ type: descriptor.type }));
|
|
3726
|
-
if (branchEvents.length === 0) {
|
|
3727
|
-
pathsExplored++;
|
|
3728
|
-
terminals.push({
|
|
3729
|
-
status: "idle",
|
|
3730
|
-
path,
|
|
3731
|
-
state: settled.snapshot.value
|
|
3732
|
-
});
|
|
3733
|
-
return;
|
|
3734
|
-
}
|
|
3735
|
-
if (depth >= maxDepth) {
|
|
3736
|
-
pathsExplored++;
|
|
3737
|
-
terminals.push({
|
|
3738
|
-
status: "max-depth",
|
|
3739
|
-
path,
|
|
3740
|
-
state: settled.snapshot.value
|
|
3741
|
-
});
|
|
3742
|
-
unexplored.push(`max-depth: stopped at path [${path.map((e) => e.type).join(", ")}]`);
|
|
3743
|
-
return;
|
|
3744
|
-
}
|
|
3745
|
-
for (const event of branchEvents) {
|
|
3746
|
-
if (witness !== void 0 || pathsExplored >= maxPaths) {
|
|
3747
|
-
if (pathsExplored >= maxPaths) hitPathCap = true;
|
|
3748
|
-
return;
|
|
3749
|
-
}
|
|
3750
|
-
if (!settled.snapshot.can(event)) {
|
|
3751
|
-
prunedByGuard++;
|
|
3752
|
-
continue;
|
|
3753
|
-
}
|
|
3754
|
-
const next = transitionAgentStep(machine, settled, event);
|
|
3755
|
-
recordState(next.snapshot);
|
|
3756
|
-
await visit(next, [...path, event], depth + 1);
|
|
3757
|
-
}
|
|
3758
|
-
};
|
|
3759
|
-
if (witness === void 0) await visit(initial, [], 0);
|
|
3760
|
-
return {
|
|
3761
|
-
report: {
|
|
3762
|
-
reachedStates: reachedValues,
|
|
3763
|
-
terminals,
|
|
3764
|
-
prunedByGuard,
|
|
3765
|
-
unexplored,
|
|
3766
|
-
pathsExplored,
|
|
3767
|
-
hitPathCap
|
|
3768
|
-
},
|
|
3769
|
-
witness
|
|
3770
|
-
};
|
|
3771
|
-
}
|
|
3772
|
-
/**
|
|
3773
|
-
* Enumerates a machine's decision and external-event branches to a bounded
|
|
3774
|
-
* depth, model-free, and reports which states are reached and how each path
|
|
3775
|
-
* terminates. At each decision request it forks one branch per candidate event
|
|
3776
|
-
* (guard-rejected candidates are counted in `prunedByGuard`, not explored); at
|
|
3777
|
-
* an idle wait it forks per externally-accepted event. Text requests resolve
|
|
3778
|
-
* from `text`, other invokes from `invokes` (or `userInput` for
|
|
3779
|
-
* `agent.userInput`) — all by-src canned-output maps, and a missing src halts
|
|
3780
|
-
* that branch with a `needs-output` terminal rather than throwing. A src in
|
|
3781
|
-
* `errors` forks an extra branch where that invoke is rejected, so states
|
|
3782
|
-
* behind an `onError` are explored too.
|
|
3783
|
-
*
|
|
3784
|
-
* Combinatorics are bounded by `maxDepth` (default 8) and `maxPaths` (default
|
|
3785
|
-
* 200, reported via `hitPathCap`).
|
|
3786
|
-
*
|
|
3787
|
-
* @example
|
|
3788
|
-
* ```ts
|
|
3789
|
-
* const report = await explorePaths(refundMachine, { input: { request: 'x', amount: 5000 } });
|
|
3790
|
-
* // report.terminals → both 'refunded' and 'denied'; report.prunedByGuard → 1
|
|
3791
|
-
* ```
|
|
3792
|
-
*/
|
|
3793
|
-
async function explorePaths(machine, options = {}) {
|
|
3794
|
-
return (await explore(machine, options)).report;
|
|
3795
|
-
}
|
|
3796
|
-
/** Thrown when a string reachability target is not a state in the machine. */
|
|
3797
|
-
var AgentUnknownStateError = class extends require_decision.AgentError {
|
|
3798
|
-
target;
|
|
3799
|
-
constructor(machineId, target) {
|
|
3800
|
-
super("unknown-state", `canReach: machine '${machineId}' has no state matching '${target}'.`);
|
|
3801
|
-
this.name = "AgentUnknownStateError";
|
|
3802
|
-
this.target = target;
|
|
3803
|
-
}
|
|
3804
|
-
};
|
|
3805
|
-
function resolveStateTarget(machine, target) {
|
|
3806
|
-
const stack = [machine.root];
|
|
3807
|
-
while (stack.length > 0) {
|
|
3808
|
-
const node = stack.pop();
|
|
3809
|
-
if (node.id === target.replace(/^#/, "") || node.path.join(".") === target) return node.id;
|
|
3810
|
-
stack.push(...Object.values(node.states));
|
|
3811
|
-
}
|
|
3812
|
-
throw new AgentUnknownStateError(machine.id, target);
|
|
3813
|
-
}
|
|
3814
|
-
/**
|
|
3815
|
-
* Answers "can the machine reach this?" by exploring its branches (a thin
|
|
3816
|
-
* wrapper over {@link explorePaths}). The target is either a state path string
|
|
3817
|
-
* (`snapshot.matches(...)` semantics) or a snapshot predicate — the predicate
|
|
3818
|
-
* form checks any property (a context invariant, a tag, a state+context
|
|
3819
|
-
* combination) without reifying a sentinel state for it. Returns
|
|
3820
|
-
* `{ reachable: true, witness }` with the event sequence that reaches it, or
|
|
3821
|
-
* `{ reachable: false }`.
|
|
3822
|
-
*
|
|
3823
|
-
* @example
|
|
3824
|
-
* ```ts
|
|
3825
|
-
* const { reachable, witness } = await canReach(refundMachine, 'denied', { input: { request: 'x', amount: 5000 } });
|
|
3826
|
-
* // reachable → true; witness → [{ type: 'NEEDS_REVIEW' }, { type: 'DENY' }]
|
|
3827
|
-
* ```
|
|
3828
|
-
*
|
|
3829
|
-
* @example Predicate target — a violation property, no sentinel state needed
|
|
3830
|
-
* ```ts
|
|
3831
|
-
* const violation = await canReach(
|
|
3832
|
-
* refundMachine,
|
|
3833
|
-
* (snapshot) => snapshot.matches('issued') && !snapshot.context.approved,
|
|
3834
|
-
* { input: { amount: 5000 } },
|
|
3835
|
-
* );
|
|
3836
|
-
* // violation.reachable → false is the safety proof
|
|
3837
|
-
* ```
|
|
3838
|
-
*/
|
|
3839
|
-
async function canReach(machine, target, options = {}) {
|
|
3840
|
-
const resolvedTarget = typeof target === "string" ? resolveStateTarget(machine, target) : void 0;
|
|
3841
|
-
const { witness } = await explore(machine, options, typeof target === "function" ? target : (snapshot) => snapshot.nodes.some((node) => node.id === resolvedTarget));
|
|
3842
|
-
return witness !== void 0 ? {
|
|
3843
|
-
reachable: true,
|
|
3844
|
-
witness,
|
|
3845
|
-
...resolvedTarget ? { target: resolvedTarget } : {}
|
|
3846
|
-
} : {
|
|
3847
|
-
reachable: false,
|
|
3848
|
-
...resolvedTarget ? { target: resolvedTarget } : {}
|
|
3849
|
-
};
|
|
3850
|
-
}
|
|
3851
|
-
//#endregion
|
|
3852
|
-
//#region src/trajectory.ts
|
|
3853
|
-
/**
|
|
3854
|
-
* Classifies one item. Event-shaped things (log entries, events) become events;
|
|
3855
|
-
* everything else is a state value. A snapshot is unwrapped to its `value`.
|
|
3856
|
-
* @internal
|
|
3857
|
-
*/
|
|
3858
|
-
function normalize(item) {
|
|
3859
|
-
if (require_decision.isRecord(item)) {
|
|
3860
|
-
const nested = item["event"];
|
|
3861
|
-
if (require_decision.isRecord(nested) && typeof nested["type"] === "string") return {
|
|
3862
|
-
kind: "event",
|
|
3863
|
-
event: nested
|
|
3864
|
-
};
|
|
3865
|
-
if (typeof item["type"] === "string") return {
|
|
3866
|
-
kind: "event",
|
|
3867
|
-
event: item
|
|
3868
|
-
};
|
|
3869
|
-
if ("value" in item) return {
|
|
3870
|
-
kind: "state",
|
|
3871
|
-
value: item["value"]
|
|
3872
|
-
};
|
|
3873
|
-
}
|
|
3874
|
-
return {
|
|
3875
|
-
kind: "state",
|
|
3876
|
-
value: item
|
|
3877
|
-
};
|
|
3878
|
-
}
|
|
3879
|
-
/** Every leaf of a state value as a dot path (`{ review: 'editing' }` → `review.editing`). @internal */
|
|
3880
|
-
function statePaths(value) {
|
|
3881
|
-
if (typeof value === "string") return [value];
|
|
3882
|
-
if (!require_decision.isRecord(value)) return [];
|
|
3883
|
-
return Object.entries(value).flatMap(([key, child]) => statePaths(child).map((path) => `${key}.${path}`));
|
|
3884
|
-
}
|
|
3885
|
-
/**
|
|
3886
|
-
* A string expectation matches a state value when it names one of its leaves or
|
|
3887
|
-
* an ancestor of one, so `'review'` matches `{ review: 'editing' }`. @internal
|
|
3888
|
-
*/
|
|
3889
|
-
function stateMatchesPath(path, value) {
|
|
3890
|
-
return statePaths(value).some((leaf) => leaf === path || leaf.startsWith(`${path}.`));
|
|
3891
|
-
}
|
|
3892
|
-
function deepEqual(a, b) {
|
|
3893
|
-
if (Object.is(a, b)) return true;
|
|
3894
|
-
if (Array.isArray(a) || Array.isArray(b)) return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((item, index) => deepEqual(item, b[index]));
|
|
3895
|
-
if (!require_decision.isRecord(a) || !require_decision.isRecord(b)) return false;
|
|
3896
|
-
const keys = Object.keys(a);
|
|
3897
|
-
return keys.length === Object.keys(b).length && keys.every((key) => deepEqual(a[key], b[key]));
|
|
3898
|
-
}
|
|
3899
|
-
/**
|
|
3900
|
-
* Normalizes ONE expected item into a predicate over actual items, so the
|
|
3901
|
-
* expectation is classified once instead of once per candidate:
|
|
3902
|
-
*
|
|
3903
|
-
* - A string expectation matches an event's `type`, or a state value it names.
|
|
3904
|
-
* - An event expectation matches an event with the same `type` whose other
|
|
3905
|
-
* declared keys deep-equal, so payload details are opt-in.
|
|
3906
|
-
* - A state-value expectation matches structurally.
|
|
3907
|
-
* @internal
|
|
3908
|
-
*/
|
|
3909
|
-
function matcherFor(expected) {
|
|
3910
|
-
if (typeof expected === "string") return (actual) => {
|
|
3911
|
-
const actualItem = normalize(actual);
|
|
3912
|
-
return actualItem.kind === "event" ? actualItem.event.type === expected : stateMatchesPath(expected, actualItem.value);
|
|
3913
|
-
};
|
|
3914
|
-
const expectedItem = normalize(expected);
|
|
3915
|
-
switch (expectedItem.kind) {
|
|
3916
|
-
case "event": return (actual) => {
|
|
3917
|
-
const actualItem = normalize(actual);
|
|
3918
|
-
return actualItem.kind === "event" && Object.keys(expectedItem.event).every((key) => deepEqual(expectedItem.event[key], actualItem.event[key]));
|
|
3919
|
-
};
|
|
3920
|
-
case "state": return (actual) => {
|
|
3921
|
-
const actualItem = normalize(actual);
|
|
3922
|
-
return actualItem.kind === "state" && deepEqual(expectedItem.value, actualItem.value);
|
|
3923
|
-
};
|
|
3924
|
-
}
|
|
3925
|
-
}
|
|
3926
|
-
/**
|
|
3927
|
-
* Matches a run's trajectory against an expected one.
|
|
3928
|
-
*
|
|
3929
|
-
* Both trajectories may be state values or events collected from
|
|
3930
|
-
* `onTransition`;
|
|
3931
|
-
* items are compared by shape, so `['prompting', 'drafting']` scores a state
|
|
3932
|
-
* path and `['PROMPT_SUBMITTED', 'SEND']` scores an event log with the same
|
|
3933
|
-
* call.
|
|
3934
|
-
*
|
|
3935
|
-
* Semantics are an ordered subsequence: every expected item must appear in
|
|
3936
|
-
* `actual`, in order, with gaps allowed. Pass `{ exact: true }` to require
|
|
3937
|
-
* equality instead.
|
|
3938
|
-
*
|
|
3939
|
-
* @example Trajectory assertions in a test
|
|
3940
|
-
* ```ts
|
|
3941
|
-
* const statePath: unknown[] = [];
|
|
3942
|
-
* const result = await runAgent(machine, {
|
|
3943
|
-
* input,
|
|
3944
|
-
* executors,
|
|
3945
|
-
* onTransition: (snapshot) => statePath.push(snapshot.value),
|
|
3946
|
-
* });
|
|
3947
|
-
*
|
|
3948
|
-
* const path = matchesTrajectory(statePath, ['prompting', 'drafting', 'sent']);
|
|
3949
|
-
* expect(path.matched, JSON.stringify(path.firstMiss)).toBe(true);
|
|
3950
|
-
* expect(matchesTrajectory(events, ['PROMPT_SUBMITTED', 'SEND']).matched).toBe(true);
|
|
3951
|
-
* ```
|
|
3952
|
-
*
|
|
3953
|
-
* @example Partial credit in an eval scorer
|
|
3954
|
-
* ```ts
|
|
3955
|
-
* const match = matchesTrajectory(output.statePath, expected.statePath);
|
|
3956
|
-
* return { name: 'state_path', score: match.score, metadata: { ...match.firstMiss } };
|
|
3957
|
-
* ```
|
|
3958
|
-
*/
|
|
3959
|
-
function matchesTrajectory(actual, expected, options = {}) {
|
|
3960
|
-
const expectedCount = expected.length;
|
|
3961
|
-
if (expectedCount === 0) throw new require_decision.AgentError("empty-trajectory", "matchesTrajectory: expected trajectory must contain at least one item.");
|
|
3962
|
-
const exact = options.exact === true;
|
|
3963
|
-
let cursor = 0;
|
|
3964
|
-
let matchedCount = 0;
|
|
3965
|
-
let firstMiss;
|
|
3966
|
-
for (let index = 0; index < expectedCount; index++) {
|
|
3967
|
-
const want = expected[index];
|
|
3968
|
-
const matches = matcherFor(want);
|
|
3969
|
-
const searchedFrom = exact ? index : cursor;
|
|
3970
|
-
let found = -1;
|
|
3971
|
-
for (let at = searchedFrom; at < actual.length; at++) {
|
|
3972
|
-
if (matches(actual[at])) {
|
|
3973
|
-
found = at;
|
|
3974
|
-
break;
|
|
3975
|
-
}
|
|
3976
|
-
if (exact) break;
|
|
3977
|
-
}
|
|
3978
|
-
if (found === -1) {
|
|
3979
|
-
firstMiss = {
|
|
3980
|
-
index,
|
|
3981
|
-
expected: want,
|
|
3982
|
-
searchedFrom
|
|
3983
|
-
};
|
|
3984
|
-
break;
|
|
3985
|
-
}
|
|
3986
|
-
matchedCount++;
|
|
3987
|
-
cursor = found + 1;
|
|
3988
|
-
}
|
|
3989
|
-
const matched = !firstMiss && (!exact || actual.length === expectedCount);
|
|
3990
|
-
const denominator = exact ? Math.max(expectedCount, actual.length) : expectedCount;
|
|
3991
|
-
return {
|
|
3992
|
-
matched,
|
|
3993
|
-
matchedCount,
|
|
3994
|
-
expectedCount,
|
|
3995
|
-
score: matched ? 1 : denominator === 0 ? 1 : matchedCount / denominator,
|
|
3996
|
-
...firstMiss ? { firstMiss } : {}
|
|
3997
|
-
};
|
|
3998
|
-
}
|
|
3999
|
-
//#endregion
|
|
4000
|
-
//#region src/scripted-executors.ts
|
|
4001
|
-
/**
|
|
4002
|
-
* Scripted executors — a deterministic stand-in for a model host.
|
|
4003
|
-
*
|
|
4004
|
-
* `createScriptedExecutors` builds a full `{ generateText, streamText, decide }`
|
|
4005
|
-
* set (plus a `userInput` handler) that plays back canned answers keyed by
|
|
4006
|
-
* request name, so `runAgent` (or `provideExecutors`, or a bare
|
|
4007
|
-
* `TextLogic.execute`) runs with no API key and no network.
|
|
4008
|
-
*
|
|
4009
|
-
* One rule: entries for a name are consumed in order, and the last entry
|
|
4010
|
-
* repeats forever.
|
|
4011
|
-
*
|
|
4012
|
-
* @module
|
|
4013
|
-
*/
|
|
4014
|
-
function toQueues(value) {
|
|
4015
|
-
return new Map(Object.entries(value ?? {}).map(([name, entries]) => [name, {
|
|
4016
|
-
entries: Array.isArray(entries) ? [...entries] : [entries],
|
|
4017
|
-
index: 0
|
|
4018
|
-
}]));
|
|
4019
|
-
}
|
|
4020
|
-
/**
|
|
4021
|
-
* Takes the next entry for `name`, holding on the last entry once the list is
|
|
4022
|
-
* spent. `undefined` means the script has no route for this name at all.
|
|
4023
|
-
* @internal
|
|
4024
|
-
*/
|
|
4025
|
-
function takeEntry(queues, name) {
|
|
4026
|
-
const queue = queues.get(name) ?? queues.get("*");
|
|
4027
|
-
if (!queue || queue.entries.length === 0) return;
|
|
4028
|
-
const entry = queue.entries[Math.min(queue.index, queue.entries.length - 1)];
|
|
4029
|
-
queue.index++;
|
|
4030
|
-
return entry;
|
|
4031
|
-
}
|
|
4032
|
-
/** @internal */
|
|
4033
|
-
function noAnswer(name, queues) {
|
|
4034
|
-
const known = [...queues.keys()].join(", ") || "(none)";
|
|
4035
|
-
return /* @__PURE__ */ new Error(`No scripted answer for request '${name}'. Known: ${known}`);
|
|
4036
|
-
}
|
|
4037
|
-
/** The only own keys an executor-result envelope carries. @internal */
|
|
4038
|
-
const TEXT_ENVELOPE_KEYS = new Set([
|
|
4039
|
-
"output",
|
|
4040
|
-
"usage",
|
|
4041
|
-
"raw"
|
|
4042
|
-
]);
|
|
4043
|
-
/**
|
|
4044
|
-
* True when a scripted entry is the executor envelope rather than the output
|
|
4045
|
-
* value: it owns an `output` key and owns NOTHING outside the envelope's own
|
|
4046
|
-
* vocabulary. Bare `'output' in value` would swallow an output object's
|
|
4047
|
-
* siblings (`{ output: 'draft', confidence: 0.9 }` would lose `confidence`)
|
|
4048
|
-
* and would also match an inherited `output`. @internal
|
|
4049
|
-
*/
|
|
4050
|
-
function isTextEnvelope(value) {
|
|
4051
|
-
return Object.hasOwn(value, "output") && Object.keys(value).every((key) => TEXT_ENVELOPE_KEYS.has(key));
|
|
4052
|
-
}
|
|
4053
|
-
/**
|
|
4054
|
-
* Resolves ONE scripted text entry to an executor result: a function entry is
|
|
4055
|
-
* called with the request, and the value is taken as the raw envelope only when
|
|
4056
|
-
* it is one (see {@link isTextEnvelope}). Shared with `runSeam`, whose routed
|
|
4057
|
-
* queues follow the same entry conventions. @internal
|
|
4058
|
-
*/
|
|
4059
|
-
async function resolveScriptedTextEntry(entry, request, info) {
|
|
4060
|
-
const value = typeof entry === "function" ? await entry(request, info) : entry;
|
|
4061
|
-
return require_decision.isRecord(value) && isTextEnvelope(value) ? value : { output: value };
|
|
4062
|
-
}
|
|
4063
|
-
/**
|
|
4064
|
-
* Names a pending text request in an error message. Shared with `runSeam`.
|
|
4065
|
-
* @internal
|
|
4066
|
-
*/
|
|
4067
|
-
function describeText(request) {
|
|
4068
|
-
return request.name ? `'${request.name}' (model '${request.model}')` : `(model '${request.model}')`;
|
|
4069
|
-
}
|
|
4070
|
-
/**
|
|
4071
|
-
* Stream semantics with no model: the whole text lands as one chunk. Shared
|
|
4072
|
-
* with `runSeam`, whose scripted answers stream the same way. @internal
|
|
4073
|
-
*/
|
|
4074
|
-
function emitScriptedChunk(result, info) {
|
|
4075
|
-
const output = require_decision.isRecord(result) ? result["output"] : void 0;
|
|
4076
|
-
if (typeof output === "string") info?.onChunk?.(output);
|
|
4077
|
-
}
|
|
4078
|
-
/**
|
|
4079
|
-
* Executors that replay a script instead of calling a model. Every slot is
|
|
4080
|
-
* provided, so any machine binds.
|
|
4081
|
-
*
|
|
4082
|
-
* Answers are keyed by the request's resolved name — its authored `name`, else
|
|
4083
|
-
* the invoke `id` — and `"*"` serves anything unmatched.
|
|
4084
|
-
* Entries for a name are consumed in order and the last one repeats forever, so
|
|
4085
|
-
* a looping machine needs no extra option. A request the script has no route
|
|
4086
|
-
* for throws an ordinary `Error` from inside the executor, which reaches the
|
|
4087
|
-
* machine as an actor error.
|
|
4088
|
-
*
|
|
4089
|
-
* Queues are copied on creation, so the caller's arrays are never mutated and
|
|
4090
|
-
* each call builds a fresh, independent playthrough. Entries may be plain
|
|
4091
|
-
* values or functions of the request.
|
|
4092
|
-
*
|
|
4093
|
-
* @example
|
|
4094
|
-
* ```ts
|
|
4095
|
-
* const result = await runAgent(moderationMachine, {
|
|
4096
|
-
* input: { comment: 'honestly this update is terrible', trust: 20 },
|
|
4097
|
-
* executors: createScriptedExecutors({
|
|
4098
|
-
* decisions: { moderate: [{ type: 'FLAG', reason: 'Borderline tone.' }] },
|
|
4099
|
-
* }),
|
|
4100
|
-
* });
|
|
4101
|
-
* ```
|
|
4102
|
-
*
|
|
4103
|
-
* @example Dynamic entries
|
|
4104
|
-
* ```ts
|
|
4105
|
-
* createScriptedExecutors({
|
|
4106
|
-
* text: { draft: (request) => `Draft about ${request.prompt}` },
|
|
4107
|
-
* decisions: { route: (request) => ({ type: request.events[0]!.type }) },
|
|
4108
|
-
* });
|
|
4109
|
-
* ```
|
|
4110
|
-
*
|
|
4111
|
-
* @example Scripted human input
|
|
4112
|
-
* ```ts
|
|
4113
|
-
* const scripted = createScriptedExecutors({ userInput: ['ship it'] });
|
|
4114
|
-
* await runAgent(machine, { executors: scripted, userInput: scripted.userInput });
|
|
4115
|
-
* ```
|
|
4116
|
-
*/
|
|
4117
|
-
function createScriptedExecutors(script = {}) {
|
|
4118
|
-
const decisions = toQueues(script.decisions);
|
|
4119
|
-
const text = toQueues(script.text);
|
|
4120
|
-
const userInput = [...script.userInput ?? []];
|
|
4121
|
-
let userInputIndex = 0;
|
|
4122
|
-
const calls = [];
|
|
4123
|
-
const withDefaultUsage = (result) => result.usage || !script.usage ? result : {
|
|
4124
|
-
...result,
|
|
4125
|
-
usage: script.usage
|
|
4126
|
-
};
|
|
4127
|
-
const nextText = async (kind, request, info) => {
|
|
4128
|
-
const name = request.name ?? "*";
|
|
4129
|
-
calls.push({
|
|
4130
|
-
kind,
|
|
4131
|
-
name,
|
|
4132
|
-
input: request.input,
|
|
4133
|
-
request
|
|
4134
|
-
});
|
|
4135
|
-
const entry = takeEntry(text, name);
|
|
4136
|
-
if (entry === void 0) throw noAnswer(name, text);
|
|
4137
|
-
return withDefaultUsage(await resolveScriptedTextEntry(entry, request, info));
|
|
4138
|
-
};
|
|
4139
|
-
return {
|
|
4140
|
-
calls,
|
|
4141
|
-
userInput: async (input) => {
|
|
4142
|
-
calls.push({
|
|
4143
|
-
kind: "userInput",
|
|
4144
|
-
name: "agent.userInput",
|
|
4145
|
-
input,
|
|
4146
|
-
request: input
|
|
4147
|
-
});
|
|
4148
|
-
if (userInput.length === 0) throw new Error("No scripted answer for request 'agent.userInput'. Add an entry to the script's `userInput` array.");
|
|
4149
|
-
const entry = userInput[Math.min(userInputIndex++, userInput.length - 1)];
|
|
4150
|
-
return typeof entry === "function" ? await entry(input) : entry;
|
|
4151
|
-
},
|
|
4152
|
-
generateText: (request, info) => nextText("generateText", request, info),
|
|
4153
|
-
streamText: async (request, info) => {
|
|
4154
|
-
const name = request.name ?? "*";
|
|
4155
|
-
const streamEntry = script.stream?.[name] ?? script.stream?.["*"];
|
|
4156
|
-
if (streamEntry !== void 0) {
|
|
4157
|
-
calls.push({
|
|
4158
|
-
kind: "streamText",
|
|
4159
|
-
name,
|
|
4160
|
-
input: request.input,
|
|
4161
|
-
request
|
|
4162
|
-
});
|
|
4163
|
-
const resolved = typeof streamEntry === "function" ? await streamEntry(request, info) : streamEntry;
|
|
4164
|
-
const chunks = typeof resolved === "string" ? [resolved] : [...resolved];
|
|
4165
|
-
for (const chunk of chunks) info?.onChunk?.(chunk);
|
|
4166
|
-
return withDefaultUsage({ output: chunks.join("") });
|
|
4167
|
-
}
|
|
4168
|
-
const result = await nextText("streamText", request, info);
|
|
4169
|
-
emitScriptedChunk(result, info);
|
|
4170
|
-
return result;
|
|
4171
|
-
},
|
|
4172
|
-
decide: async (request) => {
|
|
4173
|
-
const name = request.name ?? request.id;
|
|
4174
|
-
calls.push({
|
|
4175
|
-
kind: "decide",
|
|
4176
|
-
name,
|
|
4177
|
-
input: request.input,
|
|
4178
|
-
request
|
|
4179
|
-
});
|
|
4180
|
-
const entry = takeEntry(decisions, name);
|
|
4181
|
-
if (entry === void 0) throw noAnswer(name, decisions);
|
|
4182
|
-
const value = typeof entry === "function" ? await entry(request) : entry;
|
|
4183
|
-
return withDefaultUsage(require_decision.isRecord(value) && typeof value["type"] !== "string" && "event" in value ? value : { event: value });
|
|
4184
|
-
}
|
|
4185
|
-
};
|
|
4186
|
-
}
|
|
4187
|
-
//#endregion
|
|
4188
|
-
//#region src/seam.ts
|
|
4189
|
-
/**
|
|
4190
|
-
* The seam's own answer, for scoring. Our `{ output }` envelope is read
|
|
4191
|
-
* directly; a raw AI SDK `generateText` result contributes its `text`. A raw
|
|
4192
|
-
* STREAM result is left alone — the machine consumes that stream, and reading
|
|
4193
|
-
* it here would steal the chunks — so `seamOutput` is `undefined` for a
|
|
4194
|
-
* streaming candidate that returns one. Score its trajectory instead.
|
|
4195
|
-
* @internal
|
|
4196
|
-
*/
|
|
4197
|
-
async function seamOutputOf(result, request) {
|
|
4198
|
-
if (!require_decision.isRecord(result)) return;
|
|
4199
|
-
if ("output" in result) return await result["output"];
|
|
4200
|
-
if ("textStream" in result) return;
|
|
4201
|
-
return await require_decision.normalizeGeneratorResult(result, `seam '${request.name ?? request.model}'`, { request });
|
|
4202
|
-
}
|
|
4203
|
-
/**
|
|
4204
|
-
* Runs a machine end to end with one model call under test, and slices the run
|
|
4205
|
-
* at that call.
|
|
4206
|
-
*
|
|
4207
|
-
* The `before`/`after` slices are the point: `after.statePath` is the branch
|
|
4208
|
-
* the seam chose and `after.events` is the same question against the durable
|
|
4209
|
-
* log, both ready for `matchesTrajectory`. The state slice splits where the
|
|
4210
|
-
* state path stood when the seam answered; the event slice splits at the
|
|
4211
|
-
* seam's own effect completion (the first `xstate.done.*`/`xstate.error.*`
|
|
4212
|
-
* entry appended after the call was made).
|
|
4213
|
-
*
|
|
4214
|
-
* @example No API key: the seam is scripted too, so the whole thing runs offline.
|
|
4215
|
-
* ```ts
|
|
4216
|
-
* const run = await runSeam(emailDrafter, {
|
|
4217
|
-
* scripts: { promptEvaluator: [vague, complete], emailDrafter: [draft] },
|
|
4218
|
-
* seam: { request: 'evaluatePrompt' },
|
|
4219
|
-
* respond: ({ state }) => (state === 'prompting' ? { type: 'PROMPT_SUBMITTED', prompt } : null),
|
|
4220
|
-
* });
|
|
4221
|
-
*
|
|
4222
|
-
* matchesTrajectory(run.after.statePath, ['needsMoreInfo', 'drafting']);
|
|
4223
|
-
* ```
|
|
4224
|
-
*
|
|
4225
|
-
* @example A candidate prompt at the seam, scored against the same rows.
|
|
4226
|
-
* ```ts
|
|
4227
|
-
* const { generateText } = createAiSdkExecutors({ models });
|
|
4228
|
-
* const run = await runSeam(emailDrafter, { ...row, candidate: generateText });
|
|
4229
|
-
* ```
|
|
4230
|
-
*/
|
|
4231
|
-
async function runSeam(machine, options) {
|
|
4232
|
-
const { seam, candidate } = options;
|
|
4233
|
-
const queues = new Map(Object.entries(options.scripts ?? {}).map(([key, answers]) => [key, [...answers]]));
|
|
4234
|
-
const statePath = [];
|
|
4235
|
-
let calls = 0;
|
|
4236
|
-
let seamMatches = 0;
|
|
4237
|
-
let seamOutput;
|
|
4238
|
-
let seamUsage;
|
|
4239
|
-
const callLog = [];
|
|
4240
|
-
let seamReached = false;
|
|
4241
|
-
let callsBeforeSeam = -1;
|
|
4242
|
-
let seamStateAt = 0;
|
|
4243
|
-
let seamEventAt = 0;
|
|
4244
|
-
let liveEvents = 0;
|
|
4245
|
-
const queueKeyOf = (request) => request.name !== void 0 && queues.has(request.name) ? request.name : request.model;
|
|
4246
|
-
/**
|
|
4247
|
-
* Consumes this request's slot in the call plan, or resolves `undefined` when
|
|
4248
|
-
* its queue is dry. With `repeatLast`, the last entry is replayed instead of
|
|
4249
|
-
* running dry.
|
|
4250
|
-
*/
|
|
4251
|
-
const takeScriptedSlot = async (request, info) => {
|
|
4252
|
-
const queue = queues.get(queueKeyOf(request));
|
|
4253
|
-
if (!queue?.length) return;
|
|
4254
|
-
return resolveScriptedTextEntry(options.repeatLast && queue.length === 1 ? queue[0] : queue.shift(), request, info);
|
|
4255
|
-
};
|
|
4256
|
-
const scriptedAnswer = async (request, info) => {
|
|
4257
|
-
const scripted = await takeScriptedSlot(request, info);
|
|
4258
|
-
if (!scripted) throw new require_decision.AgentError("seam-script-exhausted", `runSeam: no scripted answer left for request ${describeText(request)}. Add an entry to \`scripts.${queueKeyOf(request)}\`, or pass \`repeatLast: true\` to replay its last entry down a longer branch.`);
|
|
4259
|
-
return scripted;
|
|
4260
|
-
};
|
|
4261
|
-
const route = async (request, info) => {
|
|
4262
|
-
const callIndex = calls++;
|
|
4263
|
-
const isSeam = request.name === seam.request && seamMatches++ === (seam.occurrence ?? 0);
|
|
4264
|
-
const ledger = (source) => {
|
|
4265
|
-
callLog.push({
|
|
4266
|
-
...request.name !== void 0 ? { name: request.name } : {},
|
|
4267
|
-
model: request.model,
|
|
4268
|
-
key: queueKeyOf(request),
|
|
4269
|
-
source,
|
|
4270
|
-
seam: isSeam
|
|
4271
|
-
});
|
|
4272
|
-
};
|
|
4273
|
-
if (isSeam && candidate) {
|
|
4274
|
-
await takeScriptedSlot(request, info);
|
|
4275
|
-
seamReached = true;
|
|
4276
|
-
callsBeforeSeam = callIndex;
|
|
4277
|
-
seamStateAt = statePath.length;
|
|
4278
|
-
seamEventAt = liveEvents;
|
|
4279
|
-
const result = await candidate(request, info);
|
|
4280
|
-
seamOutput = await seamOutputOf(result, request);
|
|
4281
|
-
seamUsage = require_decision.getCallUsage(result);
|
|
4282
|
-
ledger("candidate");
|
|
4283
|
-
return result;
|
|
4284
|
-
}
|
|
4285
|
-
const scripted = await scriptedAnswer(request, info);
|
|
4286
|
-
ledger("script");
|
|
4287
|
-
if (!isSeam) return scripted;
|
|
4288
|
-
seamReached = true;
|
|
4289
|
-
callsBeforeSeam = callIndex;
|
|
4290
|
-
seamStateAt = statePath.length;
|
|
4291
|
-
seamEventAt = liveEvents;
|
|
4292
|
-
seamOutput = scripted.output;
|
|
4293
|
-
seamUsage = scripted.usage;
|
|
4294
|
-
return scripted;
|
|
4295
|
-
};
|
|
4296
|
-
const executors = {
|
|
4297
|
-
...options.executors,
|
|
4298
|
-
generateText: route,
|
|
4299
|
-
streamText: async (request, info) => {
|
|
4300
|
-
const result = await route(request, info);
|
|
4301
|
-
emitScriptedChunk(result, info);
|
|
4302
|
-
return result;
|
|
4303
|
-
}
|
|
4304
|
-
};
|
|
4305
|
-
let snapshot;
|
|
4306
|
-
let event;
|
|
4307
|
-
const events = [];
|
|
4308
|
-
let result;
|
|
4309
|
-
const maxTurns = options.maxTurns ?? 12;
|
|
4310
|
-
for (let turn = 0; turn <= maxTurns; turn++) {
|
|
4311
|
-
result = await runAgent(machine, {
|
|
4312
|
-
...snapshot ? { snapshot } : { input: options.input },
|
|
4313
|
-
...event ? { event } : {},
|
|
4314
|
-
...options.actors ? { actors: options.actors } : {},
|
|
4315
|
-
executors,
|
|
4316
|
-
onTransition: (next, causedBy) => {
|
|
4317
|
-
if (turn > 0 && causedBy.type === "@xstate.init") return;
|
|
4318
|
-
events.push(causedBy);
|
|
4319
|
-
liveEvents = events.length;
|
|
4320
|
-
statePath.push(next.value);
|
|
4321
|
-
}
|
|
4322
|
-
});
|
|
4323
|
-
if (result.status !== "idle" || turn === maxTurns) break;
|
|
4324
|
-
const next = options.respond?.({
|
|
4325
|
-
snapshot: result.snapshot,
|
|
4326
|
-
state: result.snapshot.value,
|
|
4327
|
-
meta: require_decision.getStateMeta(result.snapshot),
|
|
4328
|
-
turn,
|
|
4329
|
-
result
|
|
4330
|
-
});
|
|
4331
|
-
if (!next) break;
|
|
4332
|
-
event = next;
|
|
4333
|
-
snapshot = result.persist();
|
|
4334
|
-
}
|
|
4335
|
-
let splitAt = events.length;
|
|
4336
|
-
if (seamReached) {
|
|
4337
|
-
splitAt = seamEventAt;
|
|
4338
|
-
for (let index = seamEventAt; index < events.length; index++) {
|
|
4339
|
-
const type = events[index].type;
|
|
4340
|
-
if (type.startsWith("xstate.done") || type.startsWith("xstate.error")) {
|
|
4341
|
-
splitAt = index;
|
|
4342
|
-
break;
|
|
4343
|
-
}
|
|
4344
|
-
}
|
|
4345
|
-
}
|
|
4346
|
-
const splitStateAt = seamReached ? seamStateAt : statePath.length;
|
|
4347
|
-
return {
|
|
4348
|
-
result,
|
|
4349
|
-
seamOutput,
|
|
4350
|
-
...seamUsage !== void 0 ? { seamUsage } : {},
|
|
4351
|
-
callsBeforeSeam,
|
|
4352
|
-
calls: callLog,
|
|
4353
|
-
before: {
|
|
4354
|
-
statePath: statePath.slice(0, splitStateAt),
|
|
4355
|
-
events: events.slice(0, splitAt)
|
|
4356
|
-
},
|
|
4357
|
-
after: {
|
|
4358
|
-
statePath: statePath.slice(splitStateAt),
|
|
4359
|
-
events: events.slice(splitAt)
|
|
4360
|
-
}
|
|
4361
|
-
};
|
|
546
|
+
function missingExecutorError(src, kind, executor) {
|
|
547
|
+
return /* @__PURE__ */ new Error(`provideExecutors: actor source '${src}' is a ${kind} source but no '${executor}' executor was provided. Add it to the executors object, or bind the source with its own executor (logic.withExecutor(...)) before calling provideExecutors.`);
|
|
4362
548
|
}
|
|
4363
549
|
//#endregion
|
|
4364
|
-
exports.
|
|
4365
|
-
exports.
|
|
4366
|
-
exports.AGENT_MESSAGES_EVENT_TYPE = require_decision.AGENT_MESSAGES_EVENT_TYPE;
|
|
4367
|
-
exports.AGENT_TRACE_SCHEMA_VERSION = AGENT_TRACE_SCHEMA_VERSION;
|
|
4368
|
-
exports.AGENT_USAGE_EVENT_TYPE = require_setup_agent.AGENT_USAGE_EVENT_TYPE;
|
|
550
|
+
exports.AGENT_TRACE_SCHEMA_VERSION = require_run_agent.AGENT_TRACE_SCHEMA_VERSION;
|
|
551
|
+
exports.AGENT_USAGE_EVENT_TYPE = require_usage.AGENT_USAGE_EVENT_TYPE;
|
|
4369
552
|
exports.AgentDecisionExhaustedError = require_decision.AgentDecisionExhaustedError;
|
|
4370
|
-
exports.AgentError =
|
|
4371
|
-
exports.AgentEventLogConflictError = AgentEventLogConflictError;
|
|
4372
|
-
exports.AgentEventLogError = AgentEventLogError;
|
|
553
|
+
exports.AgentError = require_text_logic.AgentError;
|
|
554
|
+
exports.AgentEventLogConflictError = require_event_log_store.AgentEventLogConflictError;
|
|
4373
555
|
exports.AgentInvalidEventPayloadError = require_decision.AgentInvalidEventPayloadError;
|
|
4374
|
-
exports.
|
|
4375
|
-
exports.
|
|
4376
|
-
exports.
|
|
4377
|
-
exports.
|
|
4378
|
-
exports.
|
|
4379
|
-
exports.
|
|
4380
|
-
exports.AgentUnknownStateError = AgentUnknownStateError;
|
|
4381
|
-
exports.NonSerializableAgentEventError = NonSerializableAgentEventError;
|
|
4382
|
-
exports.agentCallOccurrence = agentCallOccurrence;
|
|
4383
|
-
exports.appendMessages = require_decision.appendMessages;
|
|
4384
|
-
exports.assertAgentLogEntry = assertAgentLogEntry;
|
|
4385
|
-
exports.assertAgentMachine = assertAgentMachine;
|
|
4386
|
-
exports.assertEventLogStoreConformance = assertEventLogStoreConformance;
|
|
4387
|
-
exports.assertJsonSerializable = assertJsonSerializable;
|
|
4388
|
-
exports.assistantMessage = require_decision.assistantMessage;
|
|
4389
|
-
exports.bindRequestExecutor = require_decision.bindRequestExecutor;
|
|
4390
|
-
exports.buildEnvelopeSchema = require_decision.buildEnvelopeSchema;
|
|
4391
|
-
exports.canReach = canReach;
|
|
556
|
+
exports.AgentMachineVersionMismatchError = require_event_log_store.AgentMachineVersionMismatchError;
|
|
557
|
+
exports.AgentMaxModelCallsExceededError = require_run_agent.AgentMaxModelCallsExceededError;
|
|
558
|
+
exports.AgentSnapshotDivergedError = require_run_agent.AgentSnapshotDivergedError;
|
|
559
|
+
exports.AgentTruncatedError = require_text_logic.AgentTruncatedError;
|
|
560
|
+
exports.assistantMessage = require_text_logic.assistantMessage;
|
|
561
|
+
exports.bindRequestExecutor = require_text_logic.bindRequestExecutor;
|
|
4392
562
|
exports.createAgentSchemas = require_setup_agent.createAgentSchemas;
|
|
4393
563
|
exports.createDecisionLogic = require_decision.createDecisionLogic;
|
|
4394
564
|
exports.createDecisionRequest = require_decision.createDecisionRequest;
|
|
4395
|
-
exports.
|
|
4396
|
-
exports.createReplayEntry = createReplayEntry;
|
|
4397
|
-
exports.createScriptedExecutors = createScriptedExecutors;
|
|
4398
|
-
exports.createTextLogic = require_decision.createTextLogic;
|
|
565
|
+
exports.createTextLogic = require_text_logic.createTextLogic;
|
|
4399
566
|
exports.eventFromInteraction = eventFromInteraction;
|
|
4400
|
-
exports.executeAgentRequest = executeAgentRequest;
|
|
4401
|
-
exports.explorePaths = explorePaths;
|
|
4402
|
-
exports.forkEventLog = forkEventLog;
|
|
567
|
+
exports.executeAgentRequest = require_run_agent.executeAgentRequest;
|
|
4403
568
|
exports.getAcceptedEvents = require_decision.getAcceptedEvents;
|
|
4404
|
-
exports.getAgentOutputMode =
|
|
569
|
+
exports.getAgentOutputMode = require_text_logic.getAgentOutputMode;
|
|
4405
570
|
exports.getAgentSchemas = require_setup_agent.getAgentSchemas;
|
|
4406
|
-
exports.getCallFinishReason =
|
|
4407
|
-
exports.getCallUsage =
|
|
571
|
+
exports.getCallFinishReason = require_text_logic.getCallFinishReason;
|
|
572
|
+
exports.getCallUsage = require_text_logic.getCallUsage;
|
|
4408
573
|
exports.getInteraction = getInteraction;
|
|
4409
|
-
exports.getJsonSchema =
|
|
4410
|
-
exports.getJsonSchemaSync =
|
|
4411
|
-
exports.
|
|
4412
|
-
exports.
|
|
4413
|
-
exports.
|
|
4414
|
-
exports.
|
|
4415
|
-
exports.
|
|
4416
|
-
exports.
|
|
4417
|
-
exports.getUsageFromEvents = getUsageFromEvents;
|
|
4418
|
-
exports.initEntry = initEntry;
|
|
4419
|
-
exports.inspectTransitions = inspectTransitions;
|
|
574
|
+
exports.getJsonSchema = require_text_logic.getJsonSchema;
|
|
575
|
+
exports.getJsonSchemaSync = require_text_logic.getJsonSchemaSync;
|
|
576
|
+
exports.getMachineStructuralHash = require_text_logic.getMachineStructuralHash;
|
|
577
|
+
exports.getMessageText = require_text_logic.getMessageText;
|
|
578
|
+
exports.getStateMeta = require_text_logic.getStateMeta;
|
|
579
|
+
exports.getStatePath = require_text_logic.getStatePath;
|
|
580
|
+
exports.initialAgentStep = require_run_agent.initialAgentStep;
|
|
581
|
+
exports.inspectTransitions = require_run_agent.inspectTransitions;
|
|
4420
582
|
exports.interactionMetaSchema = interactionMetaSchema;
|
|
4421
|
-
exports.isAgentIdle = isAgentIdle;
|
|
4422
|
-
exports.isAgentMessages =
|
|
4423
|
-
exports.isStandardSchema =
|
|
4424
|
-
exports.
|
|
4425
|
-
exports.matchesTrajectory = matchesTrajectory;
|
|
4426
|
-
exports.messagesSchema = require_decision.messagesSchema;
|
|
583
|
+
exports.isAgentIdle = require_run_agent.isAgentIdle;
|
|
584
|
+
exports.isAgentMessages = isAgentMessages;
|
|
585
|
+
exports.isStandardSchema = require_text_logic.isStandardSchema;
|
|
586
|
+
exports.messagesSchema = messagesSchema;
|
|
4427
587
|
exports.parseAgentEvent = require_decision.parseAgentEvent;
|
|
4428
|
-
exports.
|
|
4429
|
-
exports.
|
|
4430
|
-
exports.parseStructuredEnvelope = require_decision.parseStructuredEnvelope;
|
|
588
|
+
exports.parseOutput = require_text_logic.parseOutput;
|
|
589
|
+
exports.parseProviderOutput = require_text_logic.parseProviderOutput;
|
|
4431
590
|
exports.provideExecutors = provideExecutors;
|
|
4432
|
-
exports.
|
|
591
|
+
exports.providerOutputSchema = require_text_logic.providerOutputSchema;
|
|
592
|
+
exports.rejectAgentStep = require_run_agent.rejectAgentStep;
|
|
4433
593
|
exports.renderDecisionAttempts = require_decision.renderDecisionAttempts;
|
|
4434
|
-
exports.
|
|
594
|
+
exports.resolveAgentStep = require_run_agent.resolveAgentStep;
|
|
4435
595
|
exports.resolveDecision = require_decision.resolveDecision;
|
|
4436
|
-
exports.runAgent = runAgent;
|
|
4437
|
-
exports.runAgentLoop = runAgentLoop;
|
|
596
|
+
exports.runAgent = require_run_agent.runAgent;
|
|
4438
597
|
exports.runAgentStream = runAgentStream;
|
|
4439
|
-
exports.
|
|
4440
|
-
exports.serializeTraceEvent = serializeTraceEvent;
|
|
598
|
+
exports.serializeTraceEvent = require_run_agent.serializeTraceEvent;
|
|
4441
599
|
Object.defineProperty(exports, "setupAgent", {
|
|
4442
600
|
enumerable: true,
|
|
4443
601
|
get: function() {
|
|
4444
602
|
return require_setup_agent.setupAgent;
|
|
4445
603
|
}
|
|
4446
604
|
});
|
|
4447
|
-
exports.
|
|
4448
|
-
exports.
|
|
4449
|
-
exports.
|
|
4450
|
-
exports.
|
|
4451
|
-
exports.userMessage =
|
|
4452
|
-
exports.validateReplayEntries = validateReplayEntries;
|
|
605
|
+
exports.systemMessage = require_text_logic.systemMessage;
|
|
606
|
+
exports.toolMessage = require_text_logic.toolMessage;
|
|
607
|
+
exports.traceTransitions = require_run_agent.traceTransitions;
|
|
608
|
+
exports.transitionAgentStep = require_run_agent.transitionAgentStep;
|
|
609
|
+
exports.userMessage = require_text_logic.userMessage;
|