@statelyai/agent 2.0.0-alpha.22 → 2.0.0-alpha.24
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 +53 -25
- package/dist/ai-sdk.d.cts +20 -12
- package/dist/ai-sdk.d.mts +20 -12
- package/dist/ai-sdk.mjs +53 -25
- package/dist/{decision-DxSTqzgZ.mjs → decision-BBZ2jMO1.mjs} +444 -79
- package/dist/{decision-1o45_ZrS.cjs → decision-Cv6SfNWl.cjs} +525 -94
- package/dist/index.cjs +2224 -1032
- package/dist/index.d.cts +413 -275
- package/dist/index.d.mts +413 -275
- package/dist/index.mjs +2163 -983
- package/dist/machines.cjs +30 -2
- package/dist/machines.d.cts +1 -1
- package/dist/machines.d.mts +1 -1
- package/dist/machines.mjs +30 -2
- package/dist/openai.cjs +447 -0
- package/dist/openai.d.cts +177 -0
- package/dist/openai.d.mts +177 -0
- package/dist/openai.mjs +437 -0
- package/dist/otel.cjs +3 -6
- package/dist/otel.d.cts +1 -1
- package/dist/otel.d.mts +1 -1
- package/dist/otel.mjs +3 -6
- package/dist/run-agent-Ck7Sa3Tb.d.mts +968 -0
- package/dist/run-agent-DWqA8KiI.d.cts +968 -0
- package/dist/setup-agent-CeO2fARv.mjs +639 -0
- package/dist/setup-agent-Dl2VELLi.cjs +663 -0
- package/dist/{text-logic-BaxPrcLk.d.cts → text-logic-C7bSnH_B.d.cts} +195 -44
- package/dist/{text-logic-DhfFWzu9.d.mts → text-logic-DJBK3LbG.d.mts} +195 -44
- package/dist/{types-DSdj2tGs.d.cts → types-CjHk5OdH.d.cts} +4 -19
- package/dist/{types-CvWRGFxP.d.mts → types-DWA6X9R-.d.mts} +4 -19
- package/package.json +17 -21
- package/readme.md +32 -17
- package/dist/errors-BQRk9eiZ.d.cts +0 -19
- package/dist/errors-C9rxnWbX.d.mts +0 -19
- package/dist/errors-CeSXQx0v.mjs +0 -23
- package/dist/errors-DUBBzRLP.cjs +0 -28
- package/dist/event-log-store-Bz7HDBkE.d.cts +0 -145
- package/dist/event-log-store-DmIDosD6.mjs +0 -427
- package/dist/event-log-store-a_TKy1gk.cjs +0 -468
- package/dist/event-log-store-hrA1vqtN.d.mts +0 -145
- package/dist/run-agent-0fpEZ1wJ.d.mts +0 -1098
- package/dist/run-agent-Zbkx7XP8.d.cts +0 -1098
- package/dist/setup-agent-DkCTy5Eu.cjs +0 -1707
- package/dist/setup-agent-nSFYrNTP.mjs +0 -1587
- package/dist/sqlite.cjs +0 -129
- package/dist/sqlite.d.cts +0 -57
- package/dist/sqlite.d.mts +0 -57
- package/dist/sqlite.mjs +0 -127
package/dist/index.cjs
CHANGED
|
@@ -1,196 +1,1370 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const
|
|
3
|
-
const require_setup_agent = require("./setup-agent-
|
|
4
|
-
const require_decision = require("./decision-1o45_ZrS.cjs");
|
|
5
|
-
const require_event_log_store = require("./event-log-store-a_TKy1gk.cjs");
|
|
2
|
+
const require_decision = require("./decision-Cv6SfNWl.cjs");
|
|
3
|
+
const require_setup_agent = require("./setup-agent-Dl2VELLi.cjs");
|
|
6
4
|
require("./validate.cjs");
|
|
7
5
|
let xstate = require("xstate");
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
6
|
+
//#region src/interaction.ts
|
|
7
|
+
function issue(message, path) {
|
|
8
|
+
return {
|
|
9
|
+
message,
|
|
10
|
+
path
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function validateEventMeta(value, path) {
|
|
14
|
+
if (typeof value === "string") return [];
|
|
15
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return [issue("Expected a label string or an interaction event descriptor", path)];
|
|
16
|
+
const descriptor = value;
|
|
17
|
+
const issues = [];
|
|
18
|
+
if (descriptor["label"] !== void 0 && typeof descriptor["label"] !== "string") issues.push(issue("Expected a string", [...path, "label"]));
|
|
19
|
+
if (descriptor["style"] !== void 0 && typeof descriptor["style"] !== "string") issues.push(issue("Expected a string", [...path, "style"]));
|
|
20
|
+
const event = descriptor["event"];
|
|
21
|
+
if (event !== void 0 && (!event || typeof event !== "object" || Array.isArray(event))) issues.push(issue("Expected an object of fixed event fields", [...path, "event"]));
|
|
22
|
+
return issues;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A {@link StandardSchemaV1} for the state metadata {@link getInteraction}
|
|
26
|
+
* reads: an object whose optional `interaction` declares a `label` (a string
|
|
27
|
+
* with `{context.path}` interpolation, or a function of the context), an
|
|
28
|
+
* `events` map of choices, and a `textEvent` for free-text answers. Pass it
|
|
29
|
+
* straight to `createAgentSchemas({ meta: interactionMetaSchema })` instead of
|
|
30
|
+
* restating the shape per machine.
|
|
31
|
+
*/
|
|
32
|
+
const interactionMetaSchema = { "~standard": {
|
|
33
|
+
version: 1,
|
|
34
|
+
vendor: "statelyai-agent",
|
|
35
|
+
validate(value) {
|
|
36
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return { issues: [{ message: "Expected a state meta object" }] };
|
|
37
|
+
const interaction = value["interaction"];
|
|
38
|
+
if (interaction === void 0) return { value };
|
|
39
|
+
if (!interaction || typeof interaction !== "object" || Array.isArray(interaction)) return { issues: [issue("Expected an interaction descriptor object", ["interaction"])] };
|
|
40
|
+
const descriptor = interaction;
|
|
41
|
+
const issues = [];
|
|
42
|
+
const label = descriptor["label"];
|
|
43
|
+
if (label !== void 0 && typeof label !== "string" && typeof label !== "function") issues.push(issue("Expected a string or a (context) => string", ["interaction", "label"]));
|
|
44
|
+
const events = descriptor["events"];
|
|
45
|
+
if (events !== void 0) if (!events || typeof events !== "object" || Array.isArray(events)) issues.push(issue("Expected an object keyed by event type", ["interaction", "events"]));
|
|
46
|
+
else for (const [type, config] of Object.entries(events)) issues.push(...validateEventMeta(config, [
|
|
47
|
+
"interaction",
|
|
48
|
+
"events",
|
|
49
|
+
type
|
|
50
|
+
]));
|
|
51
|
+
if (descriptor["textEvent"] !== void 0 && typeof descriptor["textEvent"] !== "string") issues.push(issue("Expected a string", ["interaction", "textEvent"]));
|
|
52
|
+
return issues.length > 0 ? { issues } : { value };
|
|
53
|
+
}
|
|
54
|
+
} };
|
|
55
|
+
function readPath(value, path) {
|
|
56
|
+
return path.split(".").reduce((current, key) => {
|
|
57
|
+
if (!current || typeof current !== "object") return void 0;
|
|
58
|
+
return current[key];
|
|
59
|
+
}, value);
|
|
60
|
+
}
|
|
61
|
+
function interpolate(label, context, preserveWhitespace) {
|
|
62
|
+
const interpolated = label.replace(/\{([^{}]+)\}/g, (_, path) => {
|
|
63
|
+
const value = readPath(context, path.trim());
|
|
64
|
+
return value === void 0 || value === null ? "" : String(value);
|
|
65
|
+
});
|
|
66
|
+
return preserveWhitespace ? interpolated : interpolated.replace(/\s+/g, " ").trim();
|
|
67
|
+
}
|
|
68
|
+
/** Reads the active state's interaction metadata and filters it through the
|
|
69
|
+
* events XState currently accepts. Returns `undefined` when none is declared. */
|
|
70
|
+
function getInteraction(snapshot, options = {}) {
|
|
71
|
+
const interaction = require_decision.getStateMeta(snapshot).interaction;
|
|
72
|
+
if (!interaction) return void 0;
|
|
73
|
+
const preserveWhitespace = options.preserveWhitespace ?? false;
|
|
74
|
+
const schemas = require_decision.getAgentExecutionOptions(snapshot.machine)?.schemas;
|
|
75
|
+
const accepted = new Set(require_decision.getAcceptedEvents(snapshot).map((event) => event.type));
|
|
76
|
+
const label = typeof interaction.label === "function" ? interaction.label({ context: snapshot.context }) : interaction.label ?? "";
|
|
77
|
+
const events = Object.entries(interaction.events ?? {}).filter(([type]) => accepted.has(type)).map(([type, config]) => {
|
|
78
|
+
return [type, typeof config === "string" ? { label: config } : config];
|
|
79
|
+
}).filter(([type, descriptor]) => {
|
|
80
|
+
try {
|
|
81
|
+
const complete = require_decision.parseAgentEvent(snapshot, {
|
|
82
|
+
...descriptor.event,
|
|
83
|
+
type
|
|
84
|
+
}, { schemas });
|
|
85
|
+
return snapshot.can(complete);
|
|
86
|
+
} catch {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
}).map(([type, descriptor]) => {
|
|
90
|
+
return {
|
|
91
|
+
type,
|
|
92
|
+
label: interpolate(descriptor.label ?? type, snapshot.context, preserveWhitespace),
|
|
93
|
+
...descriptor.style ? { style: descriptor.style } : {},
|
|
94
|
+
...descriptor.event ? { event: descriptor.event } : {}
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
return {
|
|
98
|
+
label: interpolate(label, snapshot.context, preserveWhitespace),
|
|
99
|
+
events,
|
|
100
|
+
...interaction.textEvent && accepted.has(interaction.textEvent) ? { textEvent: interaction.textEvent } : {}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/** Converts a rendered interaction choice back into a schema-validated machine
|
|
104
|
+
* event. Fixed fields declared in metadata are merged before user fields. */
|
|
105
|
+
function eventFromInteraction(snapshot, choice) {
|
|
106
|
+
const interaction = getInteraction(snapshot);
|
|
107
|
+
if (!interaction) throw new require_decision.AgentInvalidEventPayloadError("(interaction)", "this state renders no interaction, so there is no choice to convert.");
|
|
108
|
+
let event;
|
|
109
|
+
if ("text" in choice) {
|
|
110
|
+
if (!interaction.textEvent) throw new require_decision.AgentInvalidEventPayloadError("(text)", `this interaction takes no free text. Choices: ${interaction.events.map(({ type }) => type).join(", ") || "(none)"}.`);
|
|
111
|
+
event = {
|
|
112
|
+
type: interaction.textEvent,
|
|
113
|
+
text: choice.text
|
|
114
|
+
};
|
|
115
|
+
} else {
|
|
116
|
+
const descriptor = interaction.events.find(({ type }) => type === choice.type);
|
|
117
|
+
if (!descriptor) throw new require_decision.AgentInvalidEventPayloadError(choice.type, `this interaction does not offer that choice. Choices: ${interaction.events.map(({ type }) => type).join(", ") || "(none)"}.`);
|
|
118
|
+
event = {
|
|
119
|
+
...choice,
|
|
120
|
+
...descriptor.event,
|
|
121
|
+
type: choice.type
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return require_decision.parseAgentEvent(snapshot, event);
|
|
125
|
+
}
|
|
126
|
+
//#endregion
|
|
127
|
+
//#region src/steps.ts
|
|
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
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The invokes a snapshot is still waiting on, lowered to the same spawn-effect
|
|
152
|
+
* shape {@link getAgentRequests} reads. Derived from the snapshot's live
|
|
153
|
+
* children rather than the last transition's actions, so a state that invokes
|
|
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.
|
|
161
|
+
*
|
|
162
|
+
* @internal
|
|
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.
|
|
188
|
+
*
|
|
189
|
+
* @internal
|
|
190
|
+
*/
|
|
191
|
+
function getAgentRequests(actions, { machine, ...options } = {}) {
|
|
192
|
+
if (machine) options = {
|
|
193
|
+
...require_decision.getRegisteredAgentExecutionOptions(machine),
|
|
194
|
+
...options
|
|
195
|
+
};
|
|
196
|
+
return actions.flatMap((action) => {
|
|
197
|
+
const params = getInvokeEffectMetadata(action);
|
|
198
|
+
if (!params || typeof params.src !== "string") return [];
|
|
199
|
+
if (typeof params.id !== "string" || params.id.length === 0) throw new Error(`Agent invoke '${params.src}' must define a durable string id.`);
|
|
200
|
+
const registeredLogic = require_decision.isTextLogic(params.logic) || require_decision.isDecisionLogic(params.logic) ? params.logic : options.actors?.[params.src];
|
|
201
|
+
if (require_decision.isDecisionLogic(registeredLogic)) {
|
|
202
|
+
const decisionRequest = registeredLogic.request(params.input);
|
|
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
|
+
}];
|
|
215
|
+
}
|
|
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
|
+
});
|
|
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];
|
|
240
|
+
return {
|
|
241
|
+
type: "xstate.done.actor",
|
|
242
|
+
actorId: id,
|
|
243
|
+
...typeof child?.sessionId === "string" ? { sessionId: child.sessionId } : {},
|
|
244
|
+
output
|
|
19
245
|
};
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* The `xstate.error.actor` counterpart of {@link doneEvent} — the event a
|
|
249
|
+
* failed invoke delivers, which `onError` transitions observe. Carries the
|
|
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];
|
|
257
|
+
return {
|
|
258
|
+
type: "xstate.error.actor",
|
|
259
|
+
actorId: id,
|
|
260
|
+
...typeof child?.sessionId === "string" ? { sessionId: child.sessionId } : {},
|
|
261
|
+
error
|
|
26
262
|
};
|
|
27
|
-
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Applies a request's `output` as a done event (or, with `outcome: 'error'`,
|
|
266
|
+
* its `output` as the invoke's error) via `transition(...)`, returning the raw
|
|
267
|
+
* `[snapshot, actions]` tuple. Lower-level than {@link resolveAgentStep} /
|
|
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.
|
|
330
|
+
*/
|
|
331
|
+
function rejectAgentStep(machine, step, request, error, options) {
|
|
332
|
+
const [snapshot, actions] = transitionResult(machine, step.snapshot, request, error, "error");
|
|
333
|
+
return createAgentStep(machine, snapshot, actions, require_decision.getRegisteredAgentExecutionOptions(machine, options));
|
|
334
|
+
}
|
|
335
|
+
async function executeAgentRequest(requestOrEffect, executors) {
|
|
336
|
+
if (requestOrEffect.kind === "decision") throw new Error("executeAgentRequest(...) is text-only. Resolve a 'decision' request with resolveDecision(request, executors, ...) instead.");
|
|
337
|
+
const request = "requestId" in requestOrEffect ? {
|
|
28
338
|
kind: "text",
|
|
29
|
-
id,
|
|
30
|
-
src:
|
|
31
|
-
mode:
|
|
32
|
-
input: request,
|
|
33
|
-
tools: {},
|
|
339
|
+
id: requestOrEffect.requestId,
|
|
340
|
+
src: "",
|
|
341
|
+
mode: requestOrEffect.mode,
|
|
342
|
+
input: requestOrEffect.request,
|
|
343
|
+
tools: requestOrEffect.request.tools ?? {},
|
|
34
344
|
events: []
|
|
345
|
+
} : requestOrEffect;
|
|
346
|
+
assertTextExecutor(request, executors);
|
|
347
|
+
const { output, raw } = await require_decision.executeAgentTextRequest(request.mode ?? "generate", request.id, request.input, executors, request.tools);
|
|
348
|
+
return {
|
|
349
|
+
output: request.input.outputSchema ? require_decision.validateSchemaSync(request.input.outputSchema, output) : output,
|
|
350
|
+
raw
|
|
35
351
|
};
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
});
|
|
41
|
-
|
|
352
|
+
}
|
|
353
|
+
function assertTextExecutor(request, executors) {
|
|
354
|
+
const mode = request.mode ?? "generate";
|
|
355
|
+
const kind = mode === "stream" ? "streamText" : "generateText";
|
|
356
|
+
if (!(mode === "stream" ? executors.streamText : executors.generateText)) throw new Error(`this step's text request '${request.src}' needs a '${kind}' executor but none was provided.`);
|
|
357
|
+
}
|
|
358
|
+
function createAgentStep(machine, snapshot, actions, options) {
|
|
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"
|
|
368
|
+
};
|
|
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
|
+
}
|
|
396
|
+
//#endregion
|
|
397
|
+
//#region src/event-log.ts
|
|
398
|
+
/**
|
|
399
|
+
* The event log: an append-only journal of the EXTERNAL inputs a machine
|
|
400
|
+
* consumed, and the pure fold ({@link replay}) that turns that journal back
|
|
401
|
+
* into a snapshot without executing anything.
|
|
402
|
+
*
|
|
403
|
+
* A journaled entry is always an external input — an invoke completion
|
|
404
|
+
* (`xstate.done.actor`/`xstate.error.actor` carrying its output inline), a
|
|
405
|
+
* user-sent event, a timer firing, a reserved `@agent.usage` record. Never a
|
|
406
|
+
* raised/internal event: replay re-derives those from the machine's own logic,
|
|
407
|
+
* so journaling them would double-apply them.
|
|
408
|
+
*
|
|
409
|
+
* A log is self-contained: its reserved first entry ({@link initEntry}) carries
|
|
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);
|
|
42
491
|
try {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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:
|
|
696
|
+
*
|
|
697
|
+
* - `{ input }` — a fresh start; replay folds from `initialTransition`.
|
|
698
|
+
* - `{ snapshot }` — a continuation from a persisted snapshot: a version
|
|
699
|
+
* bridge, or a legacy snapshot that predates the log. Replay restores it
|
|
700
|
+
* purely (no actors started) and folds the rest of the log onto it.
|
|
701
|
+
*
|
|
702
|
+
* `options.metadata.executionId` names the lineage; this library always sets
|
|
703
|
+
* one, and readers must tolerate its absence in logs written elsewhere.
|
|
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`.
|
|
724
|
+
*
|
|
725
|
+
* Minted once per run and inherited verbatim by every resume of that log, so
|
|
726
|
+
* it names one lineage rather than one process. Entry ids cannot serve this
|
|
727
|
+
* role — the built-in ids are index-derived, so every log's init entry is
|
|
728
|
+
* `evt_00000000`. A fork copies the init entry, so it shares its parent's
|
|
729
|
+
* execution id by construction.
|
|
730
|
+
*
|
|
731
|
+
* Returns `undefined` for a log with no init entry and for one written without
|
|
732
|
+
* the field — callers must treat the id as optional.
|
|
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.
|
|
742
|
+
*
|
|
743
|
+
* With `machine`, entries are also checked against that machine's id and
|
|
744
|
+
* resolved version — a mismatch throws {@link AgentMachineVersionMismatchError}.
|
|
745
|
+
* An init-with-snapshot entry is exempt from the version check: that entry is
|
|
746
|
+
* itself the bridge from whatever version produced the snapshot.
|
|
747
|
+
*/
|
|
748
|
+
function validateReplayEntries(entries, options = {}) {
|
|
749
|
+
if (entries.length === 0) return;
|
|
750
|
+
const reference = entries.find((entry) => !isSnapshotInitEntry(entry)) ?? entries[0];
|
|
751
|
+
const expected = options.machine ? {
|
|
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
|
|
757
|
+
};
|
|
758
|
+
const ids = /* @__PURE__ */ new Set();
|
|
759
|
+
for (let index = 0; index < entries.length; index++) {
|
|
760
|
+
const entry = entries[index];
|
|
761
|
+
assertAgentLogEntry(entry);
|
|
762
|
+
if (entry.index !== index) throw new AgentEventLogError(`Event log must be contiguous from index 0; found entry.index ${entry.index} at position ${index}.`);
|
|
763
|
+
if (ids.has(entry.id)) throw new AgentEventLogError(`Event log contains duplicate entry id '${entry.id}'.`);
|
|
764
|
+
ids.add(entry.id);
|
|
765
|
+
if (index === 0 && !isInitEntry(entry)) throw new AgentEventLogError(`Event log must start with the reserved '${AGENT_INIT_EVENT_TYPE}' entry; found '${entry.event.type}'.`);
|
|
766
|
+
if (index > 0 && isInitEntry(entry)) throw new AgentEventLogError(`Event log has a second '${AGENT_INIT_EVENT_TYPE}' entry at index ${index}.`);
|
|
767
|
+
const versionMatches = entry.machineVersion === expected.machineVersion;
|
|
768
|
+
if (entry.machineId !== expected.machineId || !versionMatches && !isSnapshotInitEntry(entry)) throw new AgentMachineVersionMismatchError(entry.id, entry.index, expected, {
|
|
769
|
+
machineId: entry.machineId,
|
|
770
|
+
machineVersion: entry.machineVersion
|
|
66
771
|
});
|
|
67
|
-
|
|
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;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
return totals;
|
|
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);
|
|
68
848
|
}
|
|
69
849
|
return {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
deps.appendToLog(...plan.appended);
|
|
80
|
-
if (stateRequest.kind !== "decision" && stateRequest.onDone !== void 0) {
|
|
81
|
-
const snapshot = deps.getSnapshot();
|
|
82
|
-
const resolved = typeof stateRequest.onDone === "function" ? stateRequest.onDone({
|
|
83
|
-
output,
|
|
84
|
-
snapshot,
|
|
85
|
-
messages: deps.messages
|
|
86
|
-
}) : stateRequest.onDone;
|
|
87
|
-
if (!resolved) return false;
|
|
88
|
-
const acceptedTypes = require_decision.getAcceptedEvents(snapshot, { schemas: deps.schemas }).map((descriptor) => descriptor.type);
|
|
89
|
-
if (!acceptedTypes.includes(resolved.type)) throw new Error(`runAgent: getRequests request '${id}' resolved onDone to event '${resolved.type}', which the current state does not accept. Accepted: ${acceptedTypes.join(", ") || "(none)"}.`);
|
|
90
|
-
if (!snapshot.can(resolved)) return false;
|
|
91
|
-
deps.send(resolved);
|
|
92
|
-
return true;
|
|
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;
|
|
93
859
|
}
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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)
|
|
111
878
|
});
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
let sentAny = false;
|
|
130
|
-
for (const plan of plans) {
|
|
131
|
-
if (deps.isSettled()) break;
|
|
132
|
-
sentAny = await runAdvancePhase(plan, deps) || sentAny;
|
|
133
|
-
}
|
|
134
|
-
return { sentAny };
|
|
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
|
+
};
|
|
135
896
|
}
|
|
136
897
|
//#endregion
|
|
137
|
-
//#region src/
|
|
898
|
+
//#region src/event-log-store.ts
|
|
138
899
|
/**
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
900
|
+
* Durable storage for {@link AgentLogEntry} logs: the one interface a host
|
|
901
|
+
* implements, plus the in-memory reference implementation the conformance
|
|
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;
|
|
154
920
|
}
|
|
155
921
|
};
|
|
156
922
|
/**
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*/
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
+
}
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
//#endregion
|
|
969
|
+
//#region src/event-log-store-conformance.ts
|
|
970
|
+
/**
|
|
971
|
+
* The runner-agnostic conformance suite for {@link AgentEventLogStore}
|
|
972
|
+
* implementations. Split out of `event-log-store.ts` so the store contract and
|
|
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 } : {}
|
|
996
|
+
};
|
|
997
|
+
}
|
|
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;
|
|
175
1334
|
}
|
|
176
|
-
|
|
1335
|
+
for (const testCase of cases) await testCase.run(create);
|
|
1336
|
+
}
|
|
1337
|
+
//#endregion
|
|
1338
|
+
//#region src/run-agent.ts
|
|
177
1339
|
/**
|
|
178
|
-
* Thrown by {@link
|
|
179
|
-
* `
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
this.
|
|
190
|
-
this.
|
|
191
|
-
this.
|
|
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";
|
|
192
1354
|
}
|
|
193
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
|
+
}
|
|
194
1368
|
/**
|
|
195
1369
|
* The version of the {@link AgentTraceEvent} envelope every trace event carries
|
|
196
1370
|
* as `schemaVersion`. Bumped only on a breaking change to the envelope or any
|
|
@@ -209,8 +1383,8 @@ const TRACE_ENVELOPE_KEYS = [
|
|
|
209
1383
|
"type",
|
|
210
1384
|
"status",
|
|
211
1385
|
"cause",
|
|
212
|
-
"eventId",
|
|
213
1386
|
"reasoning",
|
|
1387
|
+
"finishReason",
|
|
214
1388
|
"chunk"
|
|
215
1389
|
];
|
|
216
1390
|
/**
|
|
@@ -286,69 +1460,6 @@ function serializeTraceEvent(event, options = {}) {
|
|
|
286
1460
|
}
|
|
287
1461
|
return out;
|
|
288
1462
|
}
|
|
289
|
-
function snapshotNodes(snapshot) {
|
|
290
|
-
return (snapshot.nodes ?? []).map((raw) => {
|
|
291
|
-
const node = raw;
|
|
292
|
-
return {
|
|
293
|
-
id: node.id ?? "",
|
|
294
|
-
key: node.key ?? "",
|
|
295
|
-
...node.description !== void 0 ? { description: node.description } : {},
|
|
296
|
-
tags: [...node.tags ?? []],
|
|
297
|
-
...node.meta !== void 0 ? { meta: node.meta } : {},
|
|
298
|
-
ownEvents: [...node.ownEvents ?? []],
|
|
299
|
-
leaf: Object.keys(node.states ?? {}).length === 0
|
|
300
|
-
};
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
/**
|
|
304
|
-
* The active state nodes of a snapshot, as plain {@link AgentSnapshotNode}
|
|
305
|
-
* descriptors. The escape hatch under {@link getSnapshotRequests}: use it when
|
|
306
|
-
* your `getRequests` hook needs to build requests some other way, so host code
|
|
307
|
-
* never touches xstate's private `snapshot._nodes`.
|
|
308
|
-
*/
|
|
309
|
-
function getSnapshotNodes(snapshot) {
|
|
310
|
-
return snapshotNodes(snapshot);
|
|
311
|
-
}
|
|
312
|
-
/**
|
|
313
|
-
* Builds the {@link AgentStateRequest}s for a snapshot straight from its active
|
|
314
|
-
* state nodes — the prompts-in-descriptions recipe as a function, so a
|
|
315
|
-
* {@link RunAgentOptions.getRequests} hook is one line and never reaches into
|
|
316
|
-
* xstate's private `snapshot._nodes`:
|
|
317
|
-
*
|
|
318
|
-
* ```ts
|
|
319
|
-
* runAgent(machine, {
|
|
320
|
-
* executors,
|
|
321
|
-
* getRequests: (snapshot) => getSnapshotRequests(snapshot, { model: 'writer' }),
|
|
322
|
-
* });
|
|
323
|
-
* ```
|
|
324
|
-
*
|
|
325
|
-
* Each described active node becomes one request: `prompt` from the node's
|
|
326
|
-
* `description`, `kind: 'decision'` when it is tagged `'decision'`, `system`
|
|
327
|
-
* from `meta.role` when present, `allowedEvents` scoped to the node's own
|
|
328
|
-
* events, and an explicit `onDone` when the node has exactly one own event
|
|
329
|
-
* (single-outcome states advance deterministically; anything else falls
|
|
330
|
-
* through to a `decide` call). Nodes tagged `'waiting'` produce nothing, so the
|
|
331
|
-
* run settles idle for a human. Override any of it with `filter`/`map`.
|
|
332
|
-
*/
|
|
333
|
-
function getSnapshotRequests(snapshot, options) {
|
|
334
|
-
const filter = options.filter ?? ((node) => !!node.description && !node.tags.includes("waiting"));
|
|
335
|
-
const requests = [];
|
|
336
|
-
for (const node of snapshotNodes(snapshot)) {
|
|
337
|
-
if (!filter(node)) continue;
|
|
338
|
-
const system = node.meta?.role;
|
|
339
|
-
const request = {
|
|
340
|
-
model: options.model,
|
|
341
|
-
prompt: node.description ?? "",
|
|
342
|
-
kind: node.tags.includes("decision") ? "decision" : "text",
|
|
343
|
-
...system !== void 0 ? { system } : {},
|
|
344
|
-
...node.ownEvents.length > 0 ? { allowedEvents: node.ownEvents } : {},
|
|
345
|
-
...node.ownEvents.length === 1 ? { onDone: { type: node.ownEvents[0] } } : {}
|
|
346
|
-
};
|
|
347
|
-
const mapped = options.map ? options.map(request, node) : request;
|
|
348
|
-
if (mapped) requests.push(mapped);
|
|
349
|
-
}
|
|
350
|
-
return requests;
|
|
351
|
-
}
|
|
352
1463
|
let nextRunAgentTraceId = 1;
|
|
353
1464
|
/**
|
|
354
1465
|
* Thrown into the invoke that would have made the call once
|
|
@@ -365,7 +1476,7 @@ let nextRunAgentTraceId = 1;
|
|
|
365
1476
|
* ]
|
|
366
1477
|
* ```
|
|
367
1478
|
*/
|
|
368
|
-
var AgentMaxModelCallsExceededError = class extends
|
|
1479
|
+
var AgentMaxModelCallsExceededError = class extends require_decision.AgentError {
|
|
369
1480
|
/** The budget that was exceeded (`options.maxModelCalls`). */
|
|
370
1481
|
maxModelCalls;
|
|
371
1482
|
constructor(maxModelCalls) {
|
|
@@ -573,10 +1684,10 @@ function bindTextLogic(logic, runCtx) {
|
|
|
573
1684
|
return logic.withExecutor(async ({ request: rawRequest, self: selfArg, signal }) => {
|
|
574
1685
|
const self = selfArg;
|
|
575
1686
|
const { id, src } = selfIdAndSrc(self);
|
|
576
|
-
const request =
|
|
1687
|
+
const request = {
|
|
577
1688
|
...rawRequest,
|
|
578
|
-
name: src
|
|
579
|
-
}
|
|
1689
|
+
name: rawRequest.name ?? (src !== "" && src !== "agent.generateText" && src !== "agent.streamText" ? src : id || src)
|
|
1690
|
+
};
|
|
580
1691
|
const executor = logic.mode === "stream" ? runCtx.streamText : runCtx.generateText;
|
|
581
1692
|
if (!executor) throw new Error(`No '${logic.mode === "stream" ? "streamText" : "generateText"}' executor provided.`);
|
|
582
1693
|
const requestWithTools = {
|
|
@@ -598,6 +1709,8 @@ function bindTextLogic(logic, runCtx) {
|
|
|
598
1709
|
request: agentRequest
|
|
599
1710
|
}, self);
|
|
600
1711
|
try {
|
|
1712
|
+
const callKey = id !== "" ? runCtx.callKey?.(id, self) : void 0;
|
|
1713
|
+
await runCtx.awaitJournal?.();
|
|
601
1714
|
const raw = await executor(requestWithTools, {
|
|
602
1715
|
onChunk: (chunk) => {
|
|
603
1716
|
runCtx.emitTrace?.({
|
|
@@ -608,7 +1721,8 @@ function bindTextLogic(logic, runCtx) {
|
|
|
608
1721
|
},
|
|
609
1722
|
signal,
|
|
610
1723
|
...runCtx.runId !== void 0 ? { runId: runCtx.runId } : {},
|
|
611
|
-
...id !== "" ? { requestId: id } : {}
|
|
1724
|
+
...id !== "" ? { requestId: id } : {},
|
|
1725
|
+
...callKey !== void 0 ? { callKey } : {}
|
|
612
1726
|
});
|
|
613
1727
|
const output = await require_decision.normalizeGeneratorResult(raw, id, {
|
|
614
1728
|
request,
|
|
@@ -620,9 +1734,21 @@ function bindTextLogic(logic, runCtx) {
|
|
|
620
1734
|
}, self);
|
|
621
1735
|
}
|
|
622
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
|
+
}
|
|
623
1748
|
const rawReasoning = raw?.reasoning;
|
|
624
1749
|
const reasoning = typeof rawReasoning === "string" ? rawReasoning : void 0;
|
|
625
1750
|
const usage = require_decision.getCallUsage(raw);
|
|
1751
|
+
const finishReason = require_decision.getCallFinishReason(raw);
|
|
626
1752
|
if (usage) runCtx.recordUsage?.(usage, {
|
|
627
1753
|
kind: "text",
|
|
628
1754
|
...id !== "" ? { id } : {},
|
|
@@ -636,7 +1762,8 @@ function bindTextLogic(logic, runCtx) {
|
|
|
636
1762
|
output,
|
|
637
1763
|
raw,
|
|
638
1764
|
...reasoning !== void 0 ? { reasoning } : {},
|
|
639
|
-
...usage !== void 0 ? { usage } : {}
|
|
1765
|
+
...usage !== void 0 ? { usage } : {},
|
|
1766
|
+
...finishReason !== void 0 ? { finishReason } : {}
|
|
640
1767
|
}, self);
|
|
641
1768
|
return { output };
|
|
642
1769
|
} catch (error) {
|
|
@@ -658,15 +1785,20 @@ function createCountingDecide(runCtx, self) {
|
|
|
658
1785
|
}, self);
|
|
659
1786
|
try {
|
|
660
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?.();
|
|
661
1791
|
const result = await runCtx.decide(runCtx.runId !== void 0 ? {
|
|
662
1792
|
...attemptRequest,
|
|
663
1793
|
runId: runCtx.runId
|
|
664
1794
|
} : attemptRequest, {
|
|
665
1795
|
...info,
|
|
666
1796
|
...runCtx.runId !== void 0 ? { runId: runCtx.runId } : {},
|
|
667
|
-
...info?.requestId === void 0 && id !== "" ? { requestId: id } : {}
|
|
1797
|
+
...info?.requestId === void 0 && id !== "" ? { requestId: id } : {},
|
|
1798
|
+
...info?.callKey === void 0 && callKey !== void 0 ? { callKey } : {}
|
|
668
1799
|
});
|
|
669
1800
|
const usage = require_decision.getCallUsage(result);
|
|
1801
|
+
const finishReason = require_decision.getCallFinishReason(result);
|
|
670
1802
|
if (usage) {
|
|
671
1803
|
const { src } = selfIdAndSrc(self);
|
|
672
1804
|
runCtx.recordUsage?.(usage, {
|
|
@@ -681,7 +1813,8 @@ function createCountingDecide(runCtx, self) {
|
|
|
681
1813
|
request: attemptRequest,
|
|
682
1814
|
output: result.event,
|
|
683
1815
|
raw: result,
|
|
684
|
-
...usage !== void 0 ? { usage } : {}
|
|
1816
|
+
...usage !== void 0 ? { usage } : {},
|
|
1817
|
+
...finishReason !== void 0 ? { finishReason } : {}
|
|
685
1818
|
}, self);
|
|
686
1819
|
return result;
|
|
687
1820
|
} catch (error) {
|
|
@@ -721,9 +1854,12 @@ function bindDecisionLogic(logic, runCtx) {
|
|
|
721
1854
|
schemas: runCtx.schemas,
|
|
722
1855
|
eventTypes: declaredEventTypes
|
|
723
1856
|
}) : [];
|
|
1857
|
+
const lowered = logic.request(input);
|
|
724
1858
|
const chosen = await require_decision.resolveDecision({
|
|
725
|
-
...
|
|
1859
|
+
...lowered,
|
|
726
1860
|
id,
|
|
1861
|
+
name: lowered.name ?? id,
|
|
1862
|
+
input,
|
|
727
1863
|
events
|
|
728
1864
|
}, { decide: createCountingDecide(runCtx, self) }, {
|
|
729
1865
|
maxRetries: logic.maxRetries,
|
|
@@ -828,19 +1964,26 @@ function provideBindContext(machine, executors, options) {
|
|
|
828
1964
|
}) : void 0,
|
|
829
1965
|
consumeModelCall: () => {},
|
|
830
1966
|
recordUsage: (usage, source, self) => {
|
|
831
|
-
deliverUsageEvent(
|
|
1967
|
+
deliverUsageEvent({
|
|
1968
|
+
type: require_setup_agent.AGENT_USAGE_EVENT_TYPE,
|
|
1969
|
+
...source,
|
|
1970
|
+
usage
|
|
1971
|
+
}, () => self?._parent);
|
|
832
1972
|
},
|
|
833
1973
|
actorHolder: { actorRef: void 0 },
|
|
834
1974
|
schemas: require_decision.getRegisteredAgentExecutionOptions(machine).schemas
|
|
835
1975
|
};
|
|
836
1976
|
}
|
|
837
1977
|
/**
|
|
838
|
-
* The single reserved-`@agent.usage`
|
|
839
|
-
* after a bound call settles with reported usage, send the event to
|
|
840
|
-
* actor `resolveActorRef` names — the run's root actor on the `runAgent` path,
|
|
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,
|
|
841
1981
|
* the settling request actor's `self._parent` (always the invoking machine
|
|
842
1982
|
* under a live `createActor` tree) on the `provideExecutors` path.
|
|
843
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
|
+
*
|
|
844
1987
|
* Gating is identical on both: the target snapshot must be active, must declare
|
|
845
1988
|
* an `'@agent.usage'` transition — explicitly, or through a catch-all
|
|
846
1989
|
* `on: { '*' }` (see {@link declaresUsageTransition}) — and must be able to
|
|
@@ -853,16 +1996,11 @@ function provideBindContext(machine, executors, options) {
|
|
|
853
1996
|
* here, so an invoked child machine that was not itself passed through
|
|
854
1997
|
* `provideExecutors` reports nothing. @internal
|
|
855
1998
|
*/
|
|
856
|
-
function deliverUsageEvent(
|
|
1999
|
+
function deliverUsageEvent(event, resolveActorRef, onDropped) {
|
|
857
2000
|
const actorRef = resolveActorRef();
|
|
858
2001
|
if (!actorRef) return;
|
|
859
2002
|
const snapshot = actorRef.getSnapshot();
|
|
860
2003
|
if (snapshot?.status !== "active" || !declaresUsageTransition(snapshot)) return;
|
|
861
|
-
const event = {
|
|
862
|
-
type: require_setup_agent.AGENT_USAGE_EVENT_TYPE,
|
|
863
|
-
...source,
|
|
864
|
-
usage
|
|
865
|
-
};
|
|
866
2004
|
if (onDropped?.(event)) return;
|
|
867
2005
|
if (!snapshot.can(event)) return;
|
|
868
2006
|
actorRef.send(event);
|
|
@@ -915,7 +2053,7 @@ function resolveMachineInput(machine, input) {
|
|
|
915
2053
|
try {
|
|
916
2054
|
return require_decision.validateSchemaSync(schema, input);
|
|
917
2055
|
} catch (error) {
|
|
918
|
-
throw new
|
|
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 });
|
|
919
2057
|
}
|
|
920
2058
|
}
|
|
921
2059
|
/**
|
|
@@ -995,47 +2133,47 @@ function rebindChildMachine(childMachine, runCtx, visited, ctxFor) {
|
|
|
995
2133
|
* lives in an adapter — use `createAiSdkExecutors` from '@statelyai/agent/ai-sdk'.
|
|
996
2134
|
*/
|
|
997
2135
|
async function runAgent(machine, options) {
|
|
998
|
-
|
|
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();
|
|
999
2151
|
}
|
|
1000
2152
|
/**
|
|
1001
|
-
*
|
|
1002
|
-
*
|
|
1003
|
-
*
|
|
1004
|
-
*
|
|
1005
|
-
* `createActor` would forfeit.
|
|
1006
|
-
*
|
|
1007
|
-
* ```ts
|
|
1008
|
-
* const session = createAgentActor(machine, { input, executors });
|
|
1009
|
-
* let result = await session.settled(); // first quiescence
|
|
1010
|
-
* while (result.status === "idle") {
|
|
1011
|
-
* session.actor.send(await nextUserEvent(result.snapshot));
|
|
1012
|
-
* result = await session.settled(); // next quiescence, same log
|
|
1013
|
-
* }
|
|
1014
|
-
* session.stop();
|
|
1015
|
-
* ```
|
|
1016
|
-
*
|
|
1017
|
-
* Accepts the same options as {@link runAgent} (including `snapshot`/`events`
|
|
1018
|
-
* resume). Not yet supported in session mode: `getRequests` re-interpretation
|
|
1019
|
-
* across cycles behaves per-cycle exactly as in `runAgent`.
|
|
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.
|
|
1020
2157
|
*/
|
|
1021
|
-
function
|
|
1022
|
-
|
|
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
|
+
}
|
|
1023
2166
|
}
|
|
1024
|
-
function createAgentSession(machine, options
|
|
2167
|
+
function createAgentSession(machine, options) {
|
|
1025
2168
|
const maxModelCalls = options.maxModelCalls ?? 100;
|
|
1026
2169
|
let modelCallCount = 0;
|
|
1027
2170
|
let budgetExceeded = false;
|
|
1028
2171
|
let warnedNonSerializable = false;
|
|
1029
|
-
let warnedHeuristicIdle = false;
|
|
1030
2172
|
const runId = `run_${nextRunAgentTraceId++}`;
|
|
1031
2173
|
let traceSeq = 0;
|
|
1032
2174
|
const resolvedInput = resolveMachineInput(machine, options.input);
|
|
1033
2175
|
const machineId = machine.config.id ?? machine.id ?? "(machine)";
|
|
1034
2176
|
const machineVersion = require_decision.resolveMachineVersion(machine);
|
|
1035
|
-
const agentMeta = {
|
|
1036
|
-
machineId,
|
|
1037
|
-
version: machineVersion
|
|
1038
|
-
};
|
|
1039
2177
|
const onTrace = createTraceDispatch({
|
|
1040
2178
|
onTrace: (payload) => {
|
|
1041
2179
|
options.onTrace?.({
|
|
@@ -1060,25 +2198,109 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1060
2198
|
}
|
|
1061
2199
|
modelCallCount += 1;
|
|
1062
2200
|
};
|
|
1063
|
-
const tokenTotals = {};
|
|
1064
2201
|
const cycleGate = { isResolved: () => false };
|
|
1065
|
-
const
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
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]
|
|
1076
2218
|
});
|
|
1077
|
-
|
|
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()
|
|
1078
2254
|
});
|
|
1079
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;
|
|
1080
2302
|
const runUsage = () => ({
|
|
1081
|
-
...
|
|
2303
|
+
...getUsageFromEvents(logEntries.slice(resumedLogLength)),
|
|
1082
2304
|
modelCalls: modelCallCount
|
|
1083
2305
|
});
|
|
1084
2306
|
const warnNonSerializableContext = (snapshot) => {
|
|
@@ -1094,18 +2316,23 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1094
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.`);
|
|
1095
2317
|
};
|
|
1096
2318
|
const provided = machine.provide({ actors: options.actors });
|
|
2319
|
+
logMachine = provided;
|
|
1097
2320
|
const effectiveSources = provided.sources.actors;
|
|
1098
|
-
|
|
1099
|
-
|
|
2321
|
+
const executors = {
|
|
2322
|
+
...require_decision.getRegisteredAgentExecutionOptions(machine).models?.[require_decision.DEFAULT_AGENT_EXECUTORS]?.(),
|
|
2323
|
+
...options.executors
|
|
2324
|
+
};
|
|
2325
|
+
assertBindable(provided, effectiveSources, executors);
|
|
1100
2326
|
const actorHolder = { actorRef: void 0 };
|
|
1101
2327
|
const runCtx = {
|
|
1102
|
-
generateText:
|
|
1103
|
-
streamText:
|
|
1104
|
-
decide:
|
|
2328
|
+
generateText: executors.generateText,
|
|
2329
|
+
streamText: executors.streamText,
|
|
2330
|
+
decide: executors.decide,
|
|
1105
2331
|
emitTrace: onTrace,
|
|
1106
2332
|
consumeModelCall,
|
|
1107
2333
|
recordUsage,
|
|
1108
2334
|
actorHolder,
|
|
2335
|
+
awaitJournal,
|
|
1109
2336
|
runId,
|
|
1110
2337
|
schemas: require_decision.getRegisteredAgentExecutionOptions(machine).schemas
|
|
1111
2338
|
};
|
|
@@ -1137,103 +2364,178 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1137
2364
|
}
|
|
1138
2365
|
}
|
|
1139
2366
|
const boundMachine = provided.provide({ actors: wrappedSources });
|
|
1140
|
-
const
|
|
1141
|
-
const
|
|
1142
|
-
const
|
|
2367
|
+
const isIdle = require_decision.getMachineIdlePredicate(machine) ?? isAgentIdle;
|
|
2368
|
+
const isIntentionalIdle = (snapshot) => isIdle(snapshot) || collectPendingUserInputs(snapshot).length > 0;
|
|
2369
|
+
const resumeEvents = options.events;
|
|
1143
2370
|
let effectiveSnapshot = options.snapshot;
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
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;
|
|
1156
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
|
+
});
|
|
1157
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 });
|
|
1158
2475
|
}
|
|
1159
|
-
const
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
const
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
}
|
|
1172
|
-
const priorMessages = require_decision.getAgentMessages(effectiveSnapshot);
|
|
1173
|
-
const messages = typeof options.messages === "function" ? [...options.messages([...priorMessages])] : [...priorMessages, ...options.messages ?? []];
|
|
1174
|
-
const stampSettledSnapshot = (snapshot) => {
|
|
1175
|
-
if (!snapshot || typeof snapshot !== "object") return;
|
|
1176
|
-
snapshot.agentMeta = agentMeta;
|
|
1177
|
-
if (!options.getRequests && !options.messages && messages.length === 0) return;
|
|
1178
|
-
snapshot.messages = [...messages];
|
|
1179
|
-
};
|
|
1180
|
-
if (effectiveSnapshot !== void 0 && options.event !== void 0) {
|
|
1181
|
-
const acceptedTypes = require_decision.getAcceptedEvents((0, xstate.createActor)(boundMachine, { snapshot: effectiveSnapshot }).getSnapshot(), { schemas: runCtx.schemas }).map((descriptor) => descriptor.type);
|
|
1182
|
-
const eventType = options.event.type;
|
|
1183
|
-
if (!acceptedTypes.includes(eventType)) throw new AgentIllegalResumeEventError(eventType, acceptedTypes);
|
|
1184
|
-
}
|
|
1185
|
-
const replayEvents = [...resumeEvents ?? []];
|
|
1186
|
-
require_setup_agent.validateReplayEntries(replayEvents, {
|
|
1187
|
-
machineId,
|
|
1188
|
-
machineVersion
|
|
1189
|
-
}, "runAgent events");
|
|
1190
|
-
const replayEventIds = new Set(replayEvents.map((entry) => entry.id));
|
|
1191
|
-
const hasCompleteReplayHistory = replayEvents[0]?.event.type === "@agent.init" || effectiveSnapshot === void 0 && replayEvents.length === 0;
|
|
1192
|
-
const appendReplayEvent = (event) => {
|
|
1193
|
-
const entry = require_setup_agent.createReplayEntry(machine, replayEvents, event, {
|
|
1194
|
-
machineVersion,
|
|
1195
|
-
verification: hasCompleteReplayHistory
|
|
1196
|
-
});
|
|
1197
|
-
if (replayEventIds.has(entry.id)) throw new Error(`runAgent generated duplicate event id '${entry.id}'.`);
|
|
1198
|
-
replayEventIds.add(entry.id);
|
|
1199
|
-
replayEvents.push(entry);
|
|
1200
|
-
options.onEvent?.(entry);
|
|
1201
|
-
return entry;
|
|
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;
|
|
1202
2488
|
};
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
replayEvents.push(entry);
|
|
1207
|
-
options.onEvent?.(entry);
|
|
1208
|
-
}
|
|
2489
|
+
const resumeEventToSend = options.event;
|
|
2490
|
+
let ignoredEvent;
|
|
2491
|
+
seedInitEntry?.();
|
|
1209
2492
|
let settled = false;
|
|
1210
|
-
let
|
|
2493
|
+
let journalAtSettle;
|
|
1211
2494
|
cycleGate.isResolved = () => settled;
|
|
1212
2495
|
let lastResult;
|
|
1213
2496
|
const waiters = [];
|
|
1214
2497
|
let idleTimer;
|
|
1215
2498
|
let actor;
|
|
1216
|
-
let
|
|
2499
|
+
let actorStarted = false;
|
|
2500
|
+
let deliveringResumeEvent = resumeEventToSend !== void 0;
|
|
1217
2501
|
const settle = (outcome) => {
|
|
1218
2502
|
if (settled) return;
|
|
1219
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
|
+
};
|
|
1220
2524
|
const result = {
|
|
1221
2525
|
...outcome,
|
|
1222
|
-
events: [...
|
|
1223
|
-
|
|
2526
|
+
events: [...logEntries],
|
|
2527
|
+
persist,
|
|
2528
|
+
drain: drainJournal,
|
|
2529
|
+
usage: runUsage(),
|
|
2530
|
+
...ignoredEvent !== void 0 ? { ignored: ignoredEvent } : {}
|
|
1224
2531
|
};
|
|
1225
2532
|
if (idleTimer !== void 0) clearTimeout(idleTimer);
|
|
1226
|
-
stampSettledSnapshot(result.snapshot);
|
|
1227
|
-
if ("persistedSnapshot" in result) stampSettledSnapshot(result.persistedSnapshot);
|
|
1228
2533
|
onTrace({
|
|
1229
2534
|
type: "run.end",
|
|
1230
2535
|
...outcome
|
|
1231
2536
|
});
|
|
1232
|
-
if (
|
|
1233
|
-
|
|
1234
|
-
if (options.signal) options.signal.removeEventListener("abort", onAbort);
|
|
1235
|
-
actor.stop();
|
|
1236
|
-
}
|
|
2537
|
+
if (options.signal) options.signal.removeEventListener("abort", onAbort);
|
|
2538
|
+
actor.stop();
|
|
1237
2539
|
lastResult = result;
|
|
1238
2540
|
for (const resolve of waiters.splice(0)) resolve(result);
|
|
1239
2541
|
};
|
|
@@ -1246,90 +2548,22 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1246
2548
|
});
|
|
1247
2549
|
};
|
|
1248
2550
|
const settleIdle = (current) => {
|
|
1249
|
-
warnNonSerializableContext(current);
|
|
1250
|
-
const pendingUserInputs = userInputIsPlaceholder ? collectPendingUserInputs(current) : [];
|
|
1251
|
-
settle({
|
|
1252
|
-
status: "idle",
|
|
1253
|
-
snapshot: current,
|
|
1254
|
-
|
|
1255
|
-
...pendingUserInputs.length > 0 ? { pendingUserInputs } : {}
|
|
1256
|
-
});
|
|
1257
|
-
};
|
|
1258
|
-
let interpreting = false;
|
|
1259
|
-
let interpretSeq = 0;
|
|
1260
|
-
const appendToLog = (...items) => {
|
|
1261
|
-
messages.push(...items);
|
|
1262
|
-
if (options.onMessage) {
|
|
1263
|
-
const info = {
|
|
1264
|
-
runId,
|
|
1265
|
-
machineId,
|
|
1266
|
-
machineVersion
|
|
1267
|
-
};
|
|
1268
|
-
for (const item of items) options.onMessage(item, info);
|
|
1269
|
-
}
|
|
1270
|
-
};
|
|
1271
|
-
const runErrorCause = (error) => budgetExceeded ? "max-model-calls" : wrapsDecisionExhausted(error) ? "decision-exhausted" : "machine";
|
|
1272
|
-
const settleInterpretError = (error) => {
|
|
1273
|
-
settle({
|
|
1274
|
-
status: "error",
|
|
1275
|
-
cause: runErrorCause(error),
|
|
1276
|
-
error,
|
|
1277
|
-
snapshot: actor.getSnapshot()
|
|
1278
|
-
});
|
|
1279
|
-
};
|
|
1280
|
-
const passDeps = {
|
|
1281
|
-
getSnapshot: () => actor.getSnapshot(),
|
|
1282
|
-
send: (event) => actor.send(event),
|
|
1283
|
-
isSettled: () => settled,
|
|
1284
|
-
messages,
|
|
1285
|
-
appendToLog,
|
|
1286
|
-
generateText: runCtx.generateText,
|
|
1287
|
-
decide: runCtx.decide ? createCountingDecide(runCtx, void 0) : void 0,
|
|
1288
|
-
consumeModelCall,
|
|
1289
|
-
recordUsage,
|
|
1290
|
-
nextRequestId: () => `interpret_${++interpretSeq}`,
|
|
1291
|
-
onTrace,
|
|
1292
|
-
schemas: runCtx.schemas,
|
|
1293
|
-
signal: options.signal
|
|
1294
|
-
};
|
|
1295
|
-
const maybeInterpret = (snapshot) => {
|
|
1296
|
-
if (!options.getRequests || settled) return false;
|
|
1297
|
-
if (interpreting) return true;
|
|
1298
|
-
let requested;
|
|
1299
|
-
try {
|
|
1300
|
-
requested = options.getRequests(snapshot, { messages });
|
|
1301
|
-
} catch (error) {
|
|
1302
|
-
settleInterpretError(error);
|
|
1303
|
-
return true;
|
|
1304
|
-
}
|
|
1305
|
-
const requests = (Array.isArray(requested) ? requested : requested ? [requested] : []).filter((stateRequest) => Boolean(stateRequest));
|
|
1306
|
-
if (requests.length === 0) return false;
|
|
1307
|
-
interpreting = true;
|
|
1308
|
-
runStateRequestPass(requests, passDeps).then(({ sentAny }) => {
|
|
1309
|
-
if (settled || sentAny) return;
|
|
1310
|
-
const current = actor.getSnapshot();
|
|
1311
|
-
if (isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) settleIdle(current);
|
|
1312
|
-
}).catch((error) => settleInterpretError(error)).finally(() => {
|
|
1313
|
-
interpreting = false;
|
|
1314
|
-
if (!settled) scheduleIdleCheck();
|
|
2551
|
+
warnNonSerializableContext(current);
|
|
2552
|
+
const pendingUserInputs = userInputIsPlaceholder ? collectPendingUserInputs(current) : [];
|
|
2553
|
+
settle({
|
|
2554
|
+
status: "idle",
|
|
2555
|
+
snapshot: current,
|
|
2556
|
+
...pendingUserInputs.length > 0 ? { pendingUserInputs } : {}
|
|
1315
2557
|
});
|
|
1316
|
-
return true;
|
|
1317
2558
|
};
|
|
2559
|
+
const runErrorCause = (error) => budgetExceeded ? "max-model-calls" : wrapsDecisionExhausted(error) ? "decision-exhausted" : "machine";
|
|
1318
2560
|
const scheduleIdleCheck = () => {
|
|
1319
2561
|
if (idleTimer !== void 0) clearTimeout(idleTimer);
|
|
1320
2562
|
idleTimer = setTimeout(() => {
|
|
1321
2563
|
idleTimer = void 0;
|
|
1322
2564
|
if (settled) return;
|
|
1323
2565
|
const current = actor.getSnapshot();
|
|
1324
|
-
if (isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder }))
|
|
1325
|
-
if (!maybeInterpret(current)) {
|
|
1326
|
-
if (!declaredIdlePredicate && current.status === "active" && !warnedHeuristicIdle && process.env.NODE_ENV !== "production") {
|
|
1327
|
-
warnedHeuristicIdle = true;
|
|
1328
|
-
console.warn("[@statelyai/agent] runAgent settled idle via the timing heuristic (no idle predicate declared). This is best-effort; for deterministic idle detection, declare setupAgent({ isIdle }) or pass runAgent(machine, { isIdle }), e.g. (s) => s.hasTag('waiting').");
|
|
1329
|
-
}
|
|
1330
|
-
settleIdle(current);
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
2566
|
+
if (isIntentionalIdle(current) && isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) settleIdle(current);
|
|
1333
2567
|
}, 0);
|
|
1334
2568
|
};
|
|
1335
2569
|
actor = (0, xstate.createActor)(boundMachine, {
|
|
@@ -1339,20 +2573,16 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1339
2573
|
if (typeof options.inspect === "function") options.inspect(event);
|
|
1340
2574
|
else options.inspect?.next?.(event);
|
|
1341
2575
|
if (event.type !== "@xstate.transition" || event.actorRef !== actor.ref) return;
|
|
1342
|
-
if (settled)
|
|
1343
|
-
if (finalized) return;
|
|
1344
|
-
settled = false;
|
|
1345
|
-
lastResult = void 0;
|
|
1346
|
-
}
|
|
2576
|
+
if (settled) return;
|
|
1347
2577
|
const snapshot = event.snapshot;
|
|
1348
|
-
|
|
1349
|
-
if (event.event
|
|
1350
|
-
|
|
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);
|
|
1351
2582
|
onTrace({
|
|
1352
2583
|
type: "machine.transition",
|
|
1353
2584
|
snapshot,
|
|
1354
|
-
event: event.event
|
|
1355
|
-
...eventId !== void 0 ? { eventId } : {}
|
|
2585
|
+
event: event.event
|
|
1356
2586
|
});
|
|
1357
2587
|
if (snapshot.status === "done") {
|
|
1358
2588
|
settle({
|
|
@@ -1380,13 +2610,12 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1380
2610
|
});
|
|
1381
2611
|
return;
|
|
1382
2612
|
}
|
|
1383
|
-
if (!deliveringResumeEvent &&
|
|
2613
|
+
if (!deliveringResumeEvent && isIntentionalIdle(snapshot) && isIdleSnapshot(snapshot, { ignoreUserInputChildren: userInputIsPlaceholder })) {
|
|
1384
2614
|
queueMicrotask(() => {
|
|
1385
2615
|
if (settled) return;
|
|
1386
2616
|
const current = actor.getSnapshot();
|
|
1387
|
-
if (
|
|
1388
|
-
|
|
1389
|
-
} else scheduleIdleCheck();
|
|
2617
|
+
if (isIntentionalIdle(current) && isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) settleIdle(current);
|
|
2618
|
+
else scheduleIdleCheck();
|
|
1390
2619
|
});
|
|
1391
2620
|
return;
|
|
1392
2621
|
}
|
|
@@ -1402,19 +2631,13 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1402
2631
|
});
|
|
1403
2632
|
});
|
|
1404
2633
|
for (const [type, handler] of Object.entries(options.on ?? {})) if (typeof handler === "function") actor.on(type, handler);
|
|
1405
|
-
const sessionApi = {
|
|
1406
|
-
|
|
1407
|
-
get events() {
|
|
1408
|
-
return replayEvents;
|
|
1409
|
-
},
|
|
1410
|
-
usage: runUsage,
|
|
1411
|
-
settled: () => settled && lastResult !== void 0 ? Promise.resolve(lastResult) : new Promise((resolve) => {
|
|
2634
|
+
const sessionApi = { settled: async () => {
|
|
2635
|
+
const result = settled && lastResult !== void 0 ? lastResult : await new Promise((resolve) => {
|
|
1412
2636
|
waiters.push(resolve);
|
|
1413
|
-
})
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
};
|
|
2637
|
+
});
|
|
2638
|
+
await journalAtSettle;
|
|
2639
|
+
return result;
|
|
2640
|
+
} };
|
|
1418
2641
|
if (options.signal) {
|
|
1419
2642
|
if (options.signal.aborted) {
|
|
1420
2643
|
settle({
|
|
@@ -1431,47 +2654,38 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1431
2654
|
type: "run.start",
|
|
1432
2655
|
...resolvedInput !== void 0 ? { input: resolvedInput } : {},
|
|
1433
2656
|
...effectiveSnapshot !== void 0 ? { snapshot: effectiveSnapshot } : {},
|
|
1434
|
-
...
|
|
2657
|
+
...resumeEventToSend !== void 0 ? { event: resumeEventToSend } : {}
|
|
1435
2658
|
});
|
|
2659
|
+
actorStarted = true;
|
|
1436
2660
|
actor.start();
|
|
1437
|
-
if (
|
|
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) {
|
|
1438
2683
|
deliveringResumeEvent = false;
|
|
1439
|
-
actor.send(
|
|
2684
|
+
actor.send(resumeEventToSend);
|
|
1440
2685
|
}
|
|
1441
2686
|
return sessionApi;
|
|
1442
2687
|
}
|
|
1443
2688
|
/**
|
|
1444
|
-
* Runs an agent machine to a **final state**, for run-to-done flows where an
|
|
1445
|
-
* idle pause is unexpected. Wraps {@link runAgent}:
|
|
1446
|
-
*
|
|
1447
|
-
* - `done` → resolves with the done result: `result.output` (the machine's
|
|
1448
|
-
* `OutputFrom`) plus metadata — `result.snapshot`, the replayable
|
|
1449
|
-
* `result.events`, and the aggregated `result.usage` — the same shape
|
|
1450
|
-
* `generateText` users expect (`text` + call metadata).
|
|
1451
|
-
* - `idle` → throws {@link AgentIdleError} carrying the idle snapshot and the
|
|
1452
|
-
* event types that could resume it.
|
|
1453
|
-
* - `error` → throws `result.error` when it is an `Error`; otherwise wraps it
|
|
1454
|
-
* in an `Error` whose `.cause` is the {@link RunAgentErrorCause} and whose
|
|
1455
|
-
* `.error` is the raw thrown value.
|
|
1456
|
-
*
|
|
1457
|
-
* Use {@link runAgent} directly when idle is an expected outcome you handle
|
|
1458
|
-
* (human-in-the-loop, resumable flows); use `generateResult` when the
|
|
1459
|
-
* machine is meant to run straight through to a final state.
|
|
1460
|
-
*/
|
|
1461
|
-
async function generateResult(machine, options) {
|
|
1462
|
-
const result = await runAgent(machine, options);
|
|
1463
|
-
if (result.status === "done") return result;
|
|
1464
|
-
if (result.status === "idle") {
|
|
1465
|
-
const acceptedTypes = require_decision.getAcceptedEvents(result.snapshot, { schemas: require_decision.getRegisteredAgentExecutionOptions(machine).schemas }).map((descriptor) => descriptor.type);
|
|
1466
|
-
throw new AgentIdleError(result.snapshot, acceptedTypes);
|
|
1467
|
-
}
|
|
1468
|
-
if (result.error instanceof Error) throw result.error;
|
|
1469
|
-
const wrapped = /* @__PURE__ */ new Error(`generateResult: run failed with cause '${result.cause}'.`);
|
|
1470
|
-
wrapped.cause = result.cause;
|
|
1471
|
-
wrapped.error = result.error;
|
|
1472
|
-
throw wrapped;
|
|
1473
|
-
}
|
|
1474
|
-
/**
|
|
1475
2689
|
* Wraps a `(snapshot, actorRef) => void` handler into a function usable as
|
|
1476
2690
|
* {@link RunAgentOptions.inspect}: it filters the raw inspection stream to
|
|
1477
2691
|
* `@xstate.transition` events and hands the handler the typed
|
|
@@ -1533,6 +2747,24 @@ function isIdleSnapshot(snapshot, { ignoreUserInputChildren }) {
|
|
|
1533
2747
|
})) return false;
|
|
1534
2748
|
return !(0, xstate.getNextTransitions)(snapshot).some((transitionDef) => transitionDef.eventType === "" || transitionDef.eventType.startsWith("xstate.after"));
|
|
1535
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
|
+
}
|
|
1536
2768
|
function collectPendingUserInputs(snapshot) {
|
|
1537
2769
|
const pending = [];
|
|
1538
2770
|
for (const [id, child] of Object.entries(snapshot.children ?? {})) {
|
|
@@ -1646,6 +2878,124 @@ function createAgentRun(machine, options) {
|
|
|
1646
2878
|
result
|
|
1647
2879
|
};
|
|
1648
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
|
+
}
|
|
1649
2999
|
//#endregion
|
|
1650
3000
|
//#region src/provide-executors.ts
|
|
1651
3001
|
/**
|
|
@@ -1790,98 +3140,6 @@ function buildStateIndex(rootConfig) {
|
|
|
1790
3140
|
walk(rootConfig.states, "");
|
|
1791
3141
|
return index;
|
|
1792
3142
|
}
|
|
1793
|
-
function childrenOf(index, parentPath) {
|
|
1794
|
-
const out = [];
|
|
1795
|
-
for (const node of index.values()) if (node.parentPath === parentPath) out.push(node);
|
|
1796
|
-
return out;
|
|
1797
|
-
}
|
|
1798
|
-
function collectTransitionTargets(value, fromNode, index, out) {
|
|
1799
|
-
if (value === void 0 || value === null) return;
|
|
1800
|
-
if (Array.isArray(value)) {
|
|
1801
|
-
for (const item of value) collectTransitionTargets(item, fromNode, index, out);
|
|
1802
|
-
return;
|
|
1803
|
-
}
|
|
1804
|
-
if (typeof value === "function") {
|
|
1805
|
-
out.opaque = true;
|
|
1806
|
-
return;
|
|
1807
|
-
}
|
|
1808
|
-
if (typeof value === "string") {
|
|
1809
|
-
resolveTargetString(value, fromNode, index, out);
|
|
1810
|
-
return;
|
|
1811
|
-
}
|
|
1812
|
-
if (typeof value === "object") {
|
|
1813
|
-
const { target, to } = value;
|
|
1814
|
-
if (target !== void 0) {
|
|
1815
|
-
collectTransitionTargets(target, fromNode, index, out);
|
|
1816
|
-
return;
|
|
1817
|
-
}
|
|
1818
|
-
if (to !== void 0) {
|
|
1819
|
-
collectTransitionTargets(to, fromNode, index, out);
|
|
1820
|
-
return;
|
|
1821
|
-
}
|
|
1822
|
-
}
|
|
1823
|
-
}
|
|
1824
|
-
function resolveTargetString(target, fromNode, index, out) {
|
|
1825
|
-
if (target.startsWith("#")) {
|
|
1826
|
-
out.opaque = true;
|
|
1827
|
-
return;
|
|
1828
|
-
}
|
|
1829
|
-
const resolved = target.startsWith(".") ? `${fromNode.path}.${target.slice(1)}` : fromNode.parentPath ? `${fromNode.parentPath}.${target}` : target;
|
|
1830
|
-
if (index.has(resolved)) out.targets.push(resolved);
|
|
1831
|
-
else out.opaque = true;
|
|
1832
|
-
}
|
|
1833
|
-
function outgoingTargets(node, index, staticTargets) {
|
|
1834
|
-
const out = {
|
|
1835
|
-
targets: [],
|
|
1836
|
-
opaque: false
|
|
1837
|
-
};
|
|
1838
|
-
const { config } = node;
|
|
1839
|
-
if (staticTargets) {
|
|
1840
|
-
for (const target of staticTargets[node.path] ?? []) resolveTargetString(target, node, index, out);
|
|
1841
|
-
return out;
|
|
1842
|
-
}
|
|
1843
|
-
for (const value of Object.values(config.on ?? {})) collectTransitionTargets(value, node, index, out);
|
|
1844
|
-
collectTransitionTargets(config.always, node, index, out);
|
|
1845
|
-
collectTransitionTargets(config.choice, node, index, out);
|
|
1846
|
-
for (const value of Object.values(config.after ?? {})) collectTransitionTargets(value, node, index, out);
|
|
1847
|
-
collectTransitionTargets(config.onDone, node, index, out);
|
|
1848
|
-
for (const invoke of node.invokes) {
|
|
1849
|
-
collectTransitionTargets(invoke.onDone, node, index, out);
|
|
1850
|
-
collectTransitionTargets(invoke.onError, node, index, out);
|
|
1851
|
-
}
|
|
1852
|
-
return out;
|
|
1853
|
-
}
|
|
1854
|
-
function computeReachable(rootConfig, index, staticTargets) {
|
|
1855
|
-
const reachable = /* @__PURE__ */ new Set();
|
|
1856
|
-
const queue = [];
|
|
1857
|
-
const markAncestors = (path) => {
|
|
1858
|
-
let parent = index.get(path)?.parentPath ?? "";
|
|
1859
|
-
while (parent) {
|
|
1860
|
-
reachable.add(parent);
|
|
1861
|
-
parent = index.get(parent)?.parentPath ?? "";
|
|
1862
|
-
}
|
|
1863
|
-
};
|
|
1864
|
-
const enter = (path) => {
|
|
1865
|
-
if (reachable.has(path)) return;
|
|
1866
|
-
const node = index.get(path);
|
|
1867
|
-
if (!node) return;
|
|
1868
|
-
reachable.add(path);
|
|
1869
|
-
queue.push(path);
|
|
1870
|
-
markAncestors(path);
|
|
1871
|
-
if (node.isParallel) for (const child of childrenOf(index, path)) enter(child.path);
|
|
1872
|
-
else if (node.isCompound && node.config.initial) enter(`${path}.${node.config.initial}`);
|
|
1873
|
-
};
|
|
1874
|
-
if (rootConfig.type === "parallel") for (const child of childrenOf(index, "")) enter(child.path);
|
|
1875
|
-
else if (rootConfig.initial) enter(rootConfig.initial);
|
|
1876
|
-
while (queue.length > 0) {
|
|
1877
|
-
const node = index.get(queue.shift());
|
|
1878
|
-
if (!node) continue;
|
|
1879
|
-
const { targets, opaque } = outgoingTargets(node, index, staticTargets);
|
|
1880
|
-
for (const target of targets) enter(target);
|
|
1881
|
-
if (opaque) for (const sibling of childrenOf(index, node.parentPath)) enter(sibling.path);
|
|
1882
|
-
}
|
|
1883
|
-
return reachable;
|
|
1884
|
-
}
|
|
1885
3143
|
function ancestorChain(node, index) {
|
|
1886
3144
|
const chain = [];
|
|
1887
3145
|
let parent = node.parentPath;
|
|
@@ -1903,24 +3161,6 @@ function isDecisionInvoke(src, actors) {
|
|
|
1903
3161
|
function isAgentLogicNeedingBinding(src) {
|
|
1904
3162
|
return (require_decision.isTextLogic(src) || require_decision.isDecisionLogic(src)) && !require_decision.executorBoundLogics.has(src);
|
|
1905
3163
|
}
|
|
1906
|
-
function readJsonSchema(schema) {
|
|
1907
|
-
if (!schema) return;
|
|
1908
|
-
try {
|
|
1909
|
-
return require_decision.getJsonSchemaSync(schema);
|
|
1910
|
-
} catch {
|
|
1911
|
-
return;
|
|
1912
|
-
}
|
|
1913
|
-
}
|
|
1914
|
-
function checkUnreachableStates(ctx) {
|
|
1915
|
-
const out = [];
|
|
1916
|
-
for (const node of ctx.index.values()) if (!ctx.reachable.has(node.path)) out.push({
|
|
1917
|
-
code: "unreachable-state",
|
|
1918
|
-
severity: "error",
|
|
1919
|
-
path: node.path,
|
|
1920
|
-
message: `State '${node.path}' is unreachable: no transition, always, onDone, or onError target (from any reachable state) leads to it. Remove it or add a transition.`
|
|
1921
|
-
});
|
|
1922
|
-
return out;
|
|
1923
|
-
}
|
|
1924
3164
|
function checkDecideWithoutEvents(ctx) {
|
|
1925
3165
|
const out = [];
|
|
1926
3166
|
for (const node of ctx.index.values()) for (const invoke of node.invokes) {
|
|
@@ -1939,17 +3179,6 @@ function checkDecideWithoutEvents(ctx) {
|
|
|
1939
3179
|
}
|
|
1940
3180
|
return out;
|
|
1941
3181
|
}
|
|
1942
|
-
function checkUnserializableContext(ctx) {
|
|
1943
|
-
const contextSchema = ctx.schemas?.context;
|
|
1944
|
-
if (!contextSchema) return [];
|
|
1945
|
-
if (readJsonSchema(contextSchema)) return [];
|
|
1946
|
-
return [{
|
|
1947
|
-
code: "unserializable-context",
|
|
1948
|
-
severity: "warning",
|
|
1949
|
-
path: "context",
|
|
1950
|
-
message: "The context schema does not expose a JSON schema (e.g. a `z.custom` without a `jsonSchema` extension, such as a messages array), so its fields cannot be statically checked for JSON persist/resume round-tripping. This is expected for message transcripts; verify any other custom-typed context is JSON-serializable."
|
|
1951
|
-
}];
|
|
1952
|
-
}
|
|
1953
3182
|
function checkDirectObjectSrc(ctx) {
|
|
1954
3183
|
const out = [];
|
|
1955
3184
|
for (const node of ctx.index.values()) for (const invoke of node.invokes) {
|
|
@@ -1965,80 +3194,48 @@ function checkDirectObjectSrc(ctx) {
|
|
|
1965
3194
|
}
|
|
1966
3195
|
return out;
|
|
1967
3196
|
}
|
|
1968
|
-
function
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
const required = outputJson?.required;
|
|
1972
|
-
if (!(outputJson?.type === "object" && !!properties && Object.keys(properties).length > 0 || Array.isArray(required) && required.length > 0)) return [];
|
|
1973
|
-
if (ctx.config.output !== void 0) return [];
|
|
1974
|
-
const out = [];
|
|
1975
|
-
for (const node of ctx.index.values()) {
|
|
1976
|
-
if (node.parentPath !== "" || !node.isFinal) continue;
|
|
1977
|
-
if (node.config.output === void 0) out.push({
|
|
1978
|
-
code: "final-without-output",
|
|
1979
|
-
severity: "error",
|
|
1980
|
-
path: node.path,
|
|
1981
|
-
message: `The machine declares an output schema, but top-level final state '${node.path}' has no 'output'. Its snapshot output will be undefined and fail the schema. Add an 'output' to this final state (or a root 'output').`
|
|
1982
|
-
});
|
|
1983
|
-
}
|
|
1984
|
-
return out;
|
|
1985
|
-
}
|
|
1986
|
-
function outputFnReadsEvent(fn) {
|
|
1987
|
-
return /\bevent\s*(?:\.|\?\.|\[)/.test(fn.toString());
|
|
1988
|
-
}
|
|
1989
|
-
function checkFinalOutputReadsEvent(ctx) {
|
|
1990
|
-
const out = [];
|
|
1991
|
-
for (const node of ctx.index.values()) {
|
|
1992
|
-
if (node.parentPath !== "" || !node.isFinal) continue;
|
|
1993
|
-
const output = node.config.output;
|
|
1994
|
-
if (typeof output !== "function") continue;
|
|
1995
|
-
if (!outputFnReadsEvent(output)) continue;
|
|
1996
|
-
out.push({
|
|
1997
|
-
code: "final-output-reads-event",
|
|
1998
|
-
severity: "warning",
|
|
1999
|
-
path: node.path,
|
|
2000
|
-
message: `Final state '${node.path}' has an 'output' function that reads 'event'. Final-state 'output' functions are evaluated more than once with different events (the entering event, then the machine-done computation) under current xstate behavior, so 'event' is unreliable here. Read 'context' only; capture what you need from the entering event into context in the transition that targets this state. (This guard can relax if xstate guarantees a stable entering event across evaluations.)`
|
|
2001
|
-
});
|
|
2002
|
-
}
|
|
2003
|
-
return out;
|
|
2004
|
-
}
|
|
2005
|
-
function isBuiltinOrWildcardEvent(eventType) {
|
|
2006
|
-
return eventType === "*" || eventType.includes("*") || eventType.startsWith("xstate.") || eventType.startsWith("done.") || eventType.startsWith("error.");
|
|
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"));
|
|
2007
3200
|
}
|
|
2008
|
-
function
|
|
2009
|
-
const declared = ctx.schemas?.events;
|
|
2010
|
-
const declaredTypes = new Set(Object.keys(declared ?? {}));
|
|
2011
|
-
if ([...declaredTypes].every((type) => type.startsWith("@agent."))) return [];
|
|
3201
|
+
function checkInvokeWithoutOnError(ctx) {
|
|
2012
3202
|
const out = [];
|
|
2013
|
-
for (const node of ctx.index.values()) for (const
|
|
2014
|
-
if (
|
|
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)";
|
|
2015
3207
|
out.push({
|
|
2016
|
-
code: "
|
|
3208
|
+
code: "invoke-without-on-error",
|
|
2017
3209
|
severity: "warning",
|
|
2018
3210
|
path: node.path,
|
|
2019
|
-
message: `State '${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).`
|
|
2020
3212
|
});
|
|
2021
3213
|
}
|
|
2022
3214
|
return out;
|
|
2023
3215
|
}
|
|
2024
|
-
function
|
|
2025
|
-
|
|
2026
|
-
return [
|
|
2027
|
-
|
|
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",
|
|
2028
3229
|
severity: "warning",
|
|
2029
3230
|
path: "(root)",
|
|
2030
|
-
message:
|
|
2031
|
-
}];
|
|
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
|
+
}] : [];
|
|
2032
3233
|
}
|
|
2033
3234
|
const LINT_CHECKS = [
|
|
2034
|
-
checkUnreachableStates,
|
|
2035
3235
|
checkDecideWithoutEvents,
|
|
2036
|
-
checkUnserializableContext,
|
|
2037
3236
|
checkDirectObjectSrc,
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
checkUndeclaredEvents,
|
|
2041
|
-
checkMissingFinal
|
|
3237
|
+
checkInvokeWithoutOnError,
|
|
3238
|
+
checkUnhandledAgentMessages
|
|
2042
3239
|
];
|
|
2043
3240
|
/**
|
|
2044
3241
|
* Runs static structural checks over a built agent machine and returns the
|
|
@@ -2048,8 +3245,9 @@ const LINT_CHECKS = [
|
|
|
2048
3245
|
* plus the schemas/actor sources the library already retains per machine.
|
|
2049
3246
|
*
|
|
2050
3247
|
* No model calls, no API keys — a coding agent that emits an agent machine can
|
|
2051
|
-
* call this to catch
|
|
2052
|
-
*
|
|
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.
|
|
2053
3251
|
*
|
|
2054
3252
|
* Pass `{ throw: true }` for the one-liner form used in tests and generation
|
|
2055
3253
|
* loops: it returns silently when the machine is clean and throws
|
|
@@ -2072,12 +3270,10 @@ const LINT_CHECKS = [
|
|
|
2072
3270
|
function lintAgentMachine(machine, options = {}) {
|
|
2073
3271
|
const config = machine.config ?? {};
|
|
2074
3272
|
const index = buildStateIndex(config);
|
|
2075
|
-
const reachable = computeReachable(config, index, require_decision.getMachineStaticTransitionTargets(machine));
|
|
2076
3273
|
const registered = require_decision.getRegisteredAgentExecutionOptions(machine);
|
|
2077
3274
|
const ctx = {
|
|
2078
3275
|
config,
|
|
2079
3276
|
index,
|
|
2080
|
-
reachable,
|
|
2081
3277
|
schemas: registered.schemas,
|
|
2082
3278
|
actors: registered.actors ?? machine.sources?.actors ?? {}
|
|
2083
3279
|
};
|
|
@@ -2095,7 +3291,7 @@ function lintAgentMachine(machine, options = {}) {
|
|
|
2095
3291
|
* `diagnostics` holds the findings; the message lists them one per finding,
|
|
2096
3292
|
* so a test runner's failure output reads like the CLI's lint report.
|
|
2097
3293
|
*/
|
|
2098
|
-
var AgentLintError = class extends
|
|
3294
|
+
var AgentLintError = class extends require_decision.AgentError {
|
|
2099
3295
|
diagnostics;
|
|
2100
3296
|
constructor(machineId, diagnostics) {
|
|
2101
3297
|
const lines = diagnostics.map((d) => ` ${d.severity === "error" ? "error" : "warn "} ${d.code} ${d.path}\n ${d.message}`);
|
|
@@ -2126,13 +3322,10 @@ function assertAgentMachine(machine, options = {}) {
|
|
|
2126
3322
|
}
|
|
2127
3323
|
function pendingInvokes(step) {
|
|
2128
3324
|
const out = [];
|
|
2129
|
-
for (const
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
src: metadata.src
|
|
2134
|
-
});
|
|
2135
|
-
}
|
|
3325
|
+
for (const metadata of getPendingInvokes(step.snapshot)) if (typeof metadata.src === "string") out.push({
|
|
3326
|
+
id: metadata.id,
|
|
3327
|
+
src: metadata.src
|
|
3328
|
+
});
|
|
2136
3329
|
return out;
|
|
2137
3330
|
}
|
|
2138
3331
|
function takeFromQueue(channel, src) {
|
|
@@ -2186,10 +3379,11 @@ async function simulateAgent(machine, options) {
|
|
|
2186
3379
|
text: mapValues(options.script.text ?? {}, (arr) => [...arr]),
|
|
2187
3380
|
decisions: mapValues(options.script.decisions ?? {}, (arr) => [...arr]),
|
|
2188
3381
|
invokes: mapValues(options.script.invokes ?? {}, (arr) => [...arr]),
|
|
3382
|
+
errors: mapValues(options.script.errors ?? {}, (arr) => [...arr]),
|
|
2189
3383
|
events: [...options.script.events ?? []]
|
|
2190
3384
|
};
|
|
2191
3385
|
if (options.script.userInput?.length) script.invokes[USER_INPUT_SRC] = [...options.script.userInput, ...script.invokes[USER_INPUT_SRC] ?? []];
|
|
2192
|
-
let step =
|
|
3386
|
+
let step = initialAgentStep(machine, options.input);
|
|
2193
3387
|
const trail = [{ state: step.snapshot.value }];
|
|
2194
3388
|
for (let i = 0; i < maxSteps; i++) {
|
|
2195
3389
|
if (step.done) return {
|
|
@@ -2203,33 +3397,63 @@ async function simulateAgent(machine, options) {
|
|
|
2203
3397
|
const invokeMeta = findInvokeMetadata(step, request.id);
|
|
2204
3398
|
const src = invokeMeta?.src ?? request.id;
|
|
2205
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
|
+
}
|
|
2206
3410
|
step = await applyScriptedDecision(machine, step, request, decisionSrc, invokeMeta?.logic, script, trail);
|
|
2207
3411
|
continue;
|
|
2208
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
|
+
}
|
|
2209
3422
|
const taken = takeFromQueue(script.text, request.src);
|
|
2210
3423
|
if (!taken.found) throw scriptDryError("text", request.src, request.id);
|
|
2211
|
-
step =
|
|
3424
|
+
step = resolveAgentStep(machine, step, request, taken.value);
|
|
2212
3425
|
trail.push({
|
|
2213
3426
|
state: step.snapshot.value,
|
|
2214
3427
|
resolvedRequest: {
|
|
2215
3428
|
kind: "text",
|
|
2216
3429
|
src: request.src,
|
|
2217
|
-
id: request.id
|
|
3430
|
+
id: request.id,
|
|
3431
|
+
outcome: "output"
|
|
2218
3432
|
}
|
|
2219
3433
|
});
|
|
2220
3434
|
continue;
|
|
2221
3435
|
}
|
|
2222
3436
|
const [invoke] = pendingInvokes(step);
|
|
2223
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
|
+
}
|
|
2224
3447
|
const taken = takeFromQueue(script.invokes, invoke.src);
|
|
2225
3448
|
if (!taken.found) throw scriptDryError("userInput", invoke.src, invoke.id);
|
|
2226
|
-
step =
|
|
3449
|
+
step = resolveAgentStep(machine, step, invoke.id, taken.value);
|
|
2227
3450
|
trail.push({
|
|
2228
3451
|
state: step.snapshot.value,
|
|
2229
3452
|
resolvedRequest: {
|
|
2230
3453
|
kind: "userInput",
|
|
2231
3454
|
src: invoke.src,
|
|
2232
|
-
id: invoke.id
|
|
3455
|
+
id: invoke.id,
|
|
3456
|
+
outcome: "output"
|
|
2233
3457
|
}
|
|
2234
3458
|
});
|
|
2235
3459
|
continue;
|
|
@@ -2238,7 +3462,7 @@ async function simulateAgent(machine, options) {
|
|
|
2238
3462
|
if (external.length > 0) {
|
|
2239
3463
|
const event = external.shift();
|
|
2240
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.`);
|
|
2241
|
-
step =
|
|
3465
|
+
step = transitionAgentStep(machine, step, event);
|
|
2242
3466
|
trail.push({
|
|
2243
3467
|
state: step.snapshot.value,
|
|
2244
3468
|
appliedEvent: event,
|
|
@@ -2258,6 +3482,29 @@ async function simulateAgent(machine, options) {
|
|
|
2258
3482
|
trail
|
|
2259
3483
|
};
|
|
2260
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
|
+
}
|
|
2261
3508
|
async function applyScriptedDecision(machine, step, request, decisionSrc, invokeLogic, script, trail) {
|
|
2262
3509
|
const dequeued = [];
|
|
2263
3510
|
const decide = async () => {
|
|
@@ -2278,12 +3525,7 @@ async function applyScriptedDecision(machine, step, request, decisionSrc, invoke
|
|
|
2278
3525
|
});
|
|
2279
3526
|
} catch (error) {
|
|
2280
3527
|
if (!(error instanceof require_decision.AgentDecisionExhaustedError)) throw error;
|
|
2281
|
-
const next =
|
|
2282
|
-
type: "xstate.error.actor",
|
|
2283
|
-
actorId: request.id,
|
|
2284
|
-
...sessionIdOf(step.snapshot, request.id),
|
|
2285
|
-
error
|
|
2286
|
-
});
|
|
3528
|
+
const next = rejectAgentStep(machine, step, request.id, error);
|
|
2287
3529
|
if (next.snapshot.status === "error") throw error;
|
|
2288
3530
|
trail.push({
|
|
2289
3531
|
state: next.snapshot.value,
|
|
@@ -2291,7 +3533,7 @@ async function applyScriptedDecision(machine, step, request, decisionSrc, invoke
|
|
|
2291
3533
|
});
|
|
2292
3534
|
return next;
|
|
2293
3535
|
}
|
|
2294
|
-
const next =
|
|
3536
|
+
const next = transitionAgentStep(machine, step, chosen);
|
|
2295
3537
|
const rejected = dequeued.slice(0, -1);
|
|
2296
3538
|
trail.push({
|
|
2297
3539
|
state: next.snapshot.value,
|
|
@@ -2301,9 +3543,8 @@ async function applyScriptedDecision(machine, step, request, decisionSrc, invoke
|
|
|
2301
3543
|
return next;
|
|
2302
3544
|
}
|
|
2303
3545
|
function findInvokeMetadata(step, id) {
|
|
2304
|
-
for (const
|
|
2305
|
-
|
|
2306
|
-
if (metadata?.id !== id) continue;
|
|
3546
|
+
for (const metadata of getPendingInvokes(step.snapshot)) {
|
|
3547
|
+
if (metadata.id !== id) continue;
|
|
2307
3548
|
return {
|
|
2308
3549
|
...typeof metadata.src === "string" ? { src: metadata.src } : {},
|
|
2309
3550
|
logic: metadata.logic ?? (typeof metadata.src === "object" ? metadata.src : void 0)
|
|
@@ -2314,10 +3555,6 @@ function resolveRegisteredDecisionLogic(machine, src) {
|
|
|
2314
3555
|
const candidate = require_decision.getRegisteredAgentExecutionOptions(machine).actors?.[src] ?? machine.sources?.actors?.[src];
|
|
2315
3556
|
return require_decision.isDecisionLogic(candidate) ? candidate : void 0;
|
|
2316
3557
|
}
|
|
2317
|
-
function sessionIdOf(snapshot, id) {
|
|
2318
|
-
const child = snapshot.children[id];
|
|
2319
|
-
return typeof child?.sessionId === "string" ? { sessionId: child.sessionId } : {};
|
|
2320
|
-
}
|
|
2321
3558
|
function mapValues(obj, fn) {
|
|
2322
3559
|
return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, fn(value)]));
|
|
2323
3560
|
}
|
|
@@ -2327,12 +3564,25 @@ function scriptDryError(kind, src, id, request) {
|
|
|
2327
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}`);
|
|
2328
3565
|
}
|
|
2329
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
|
+
}
|
|
2330
3579
|
async function explore(machine, options, stopWhen) {
|
|
2331
3580
|
const maxDepth = options.maxDepth ?? 8;
|
|
2332
3581
|
const maxPaths = options.maxPaths ?? 200;
|
|
2333
3582
|
const textScript = options.text ?? {};
|
|
2334
3583
|
const invokeOutputs = { ...options.invokes ?? {} };
|
|
2335
3584
|
if ("userInput" in options) invokeOutputs[USER_INPUT_SRC] = options.userInput;
|
|
3585
|
+
const errorScript = options.errors ?? {};
|
|
2336
3586
|
const reachedStates = /* @__PURE__ */ new Set();
|
|
2337
3587
|
const reachedValues = [];
|
|
2338
3588
|
const terminals = [];
|
|
@@ -2348,7 +3598,7 @@ async function explore(machine, options, stopWhen) {
|
|
|
2348
3598
|
reachedValues.push(snapshot.value);
|
|
2349
3599
|
}
|
|
2350
3600
|
};
|
|
2351
|
-
const initial =
|
|
3601
|
+
const initial = initialAgentStep(machine, options.input);
|
|
2352
3602
|
recordState(initial.snapshot);
|
|
2353
3603
|
if (stopWhen?.(initial.snapshot)) witness = [];
|
|
2354
3604
|
const advance = (step) => {
|
|
@@ -2361,22 +3611,42 @@ async function explore(machine, options, stopWhen) {
|
|
|
2361
3611
|
if (current.done) return { step: current };
|
|
2362
3612
|
const request = current.requests[0];
|
|
2363
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
|
+
};
|
|
2364
3618
|
if (!(request.src in textScript)) return {
|
|
2365
3619
|
step: current,
|
|
2366
3620
|
blockedSrc: request.src
|
|
2367
3621
|
};
|
|
2368
|
-
current =
|
|
3622
|
+
current = resolveAgentStep(machine, current, request, textScript[request.src]);
|
|
2369
3623
|
recordState(current.snapshot);
|
|
2370
3624
|
continue;
|
|
2371
3625
|
}
|
|
2372
|
-
if (request && request.kind === "decision")
|
|
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
|
+
}
|
|
2373
3639
|
const [invoke] = pendingInvokes(current);
|
|
2374
3640
|
if (invoke) {
|
|
3641
|
+
if (invoke.src in errorScript) return {
|
|
3642
|
+
step: current,
|
|
3643
|
+
fork: invokeFork(invoke.id, invoke.src, invokeOutputs)
|
|
3644
|
+
};
|
|
2375
3645
|
if (!(invoke.src in invokeOutputs)) return {
|
|
2376
3646
|
step: current,
|
|
2377
3647
|
blockedSrc: invoke.src
|
|
2378
3648
|
};
|
|
2379
|
-
current =
|
|
3649
|
+
current = resolveAgentStep(machine, current, invoke.id, invokeOutputs[invoke.src]);
|
|
2380
3650
|
recordState(current.snapshot);
|
|
2381
3651
|
continue;
|
|
2382
3652
|
}
|
|
@@ -2384,17 +3654,43 @@ async function explore(machine, options, stopWhen) {
|
|
|
2384
3654
|
}
|
|
2385
3655
|
return { step: current };
|
|
2386
3656
|
};
|
|
2387
|
-
const visit = async (step, path,
|
|
3657
|
+
const visit = async (step, path, startDepth) => {
|
|
3658
|
+
let depth = startDepth;
|
|
2388
3659
|
if (witness !== void 0) return;
|
|
2389
3660
|
if (pathsExplored >= maxPaths) {
|
|
2390
3661
|
hitPathCap = true;
|
|
2391
3662
|
return;
|
|
2392
3663
|
}
|
|
2393
|
-
const { step: settled, blockedSrc, hit } = advance(step);
|
|
3664
|
+
const { step: settled, blockedSrc, hit, fork } = advance(step);
|
|
2394
3665
|
if (hit) {
|
|
2395
3666
|
witness = path;
|
|
2396
3667
|
return;
|
|
2397
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
|
+
}
|
|
2398
3694
|
if (blockedSrc) {
|
|
2399
3695
|
pathsExplored++;
|
|
2400
3696
|
terminals.push({
|
|
@@ -2415,6 +3711,16 @@ async function explore(machine, options, stopWhen) {
|
|
|
2415
3711
|
});
|
|
2416
3712
|
return;
|
|
2417
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
|
+
}
|
|
2418
3724
|
const request = settled.requests[0];
|
|
2419
3725
|
const branchEvents = request?.kind === "decision" ? request.events.map((descriptor) => ({ type: descriptor.type })) : require_decision.getAcceptedEvents(settled.snapshot).map((descriptor) => ({ type: descriptor.type }));
|
|
2420
3726
|
if (branchEvents.length === 0) {
|
|
@@ -2445,7 +3751,7 @@ async function explore(machine, options, stopWhen) {
|
|
|
2445
3751
|
prunedByGuard++;
|
|
2446
3752
|
continue;
|
|
2447
3753
|
}
|
|
2448
|
-
const next =
|
|
3754
|
+
const next = transitionAgentStep(machine, settled, event);
|
|
2449
3755
|
recordState(next.snapshot);
|
|
2450
3756
|
await visit(next, [...path, event], depth + 1);
|
|
2451
3757
|
}
|
|
@@ -2471,7 +3777,9 @@ async function explore(machine, options, stopWhen) {
|
|
|
2471
3777
|
* an idle wait it forks per externally-accepted event. Text requests resolve
|
|
2472
3778
|
* from `text`, other invokes from `invokes` (or `userInput` for
|
|
2473
3779
|
* `agent.userInput`) — all by-src canned-output maps, and a missing src halts
|
|
2474
|
-
* that branch with a `needs-output` terminal rather than throwing.
|
|
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.
|
|
2475
3783
|
*
|
|
2476
3784
|
* Combinatorics are bounded by `maxDepth` (default 8) and `maxPaths` (default
|
|
2477
3785
|
* 200, reported via `hitPathCap`).
|
|
@@ -2485,6 +3793,24 @@ async function explore(machine, options, stopWhen) {
|
|
|
2485
3793
|
async function explorePaths(machine, options = {}) {
|
|
2486
3794
|
return (await explore(machine, options)).report;
|
|
2487
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
|
+
}
|
|
2488
3814
|
/**
|
|
2489
3815
|
* Answers "can the machine reach this?" by exploring its branches (a thin
|
|
2490
3816
|
* wrapper over {@link explorePaths}). The target is either a state path string
|
|
@@ -2511,23 +3837,16 @@ async function explorePaths(machine, options = {}) {
|
|
|
2511
3837
|
* ```
|
|
2512
3838
|
*/
|
|
2513
3839
|
async function canReach(machine, target, options = {}) {
|
|
2514
|
-
const
|
|
2515
|
-
|
|
2516
|
-
return snapshot.matches(target);
|
|
2517
|
-
} catch {
|
|
2518
|
-
return false;
|
|
2519
|
-
}
|
|
2520
|
-
});
|
|
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));
|
|
2521
3842
|
return witness !== void 0 ? {
|
|
2522
3843
|
reachable: true,
|
|
2523
|
-
witness
|
|
2524
|
-
|
|
2525
|
-
}
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
function isRecord(value) {
|
|
2530
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3844
|
+
witness,
|
|
3845
|
+
...resolvedTarget ? { target: resolvedTarget } : {}
|
|
3846
|
+
} : {
|
|
3847
|
+
reachable: false,
|
|
3848
|
+
...resolvedTarget ? { target: resolvedTarget } : {}
|
|
3849
|
+
};
|
|
2531
3850
|
}
|
|
2532
3851
|
//#endregion
|
|
2533
3852
|
//#region src/trajectory.ts
|
|
@@ -2537,9 +3856,9 @@ function isRecord(value) {
|
|
|
2537
3856
|
* @internal
|
|
2538
3857
|
*/
|
|
2539
3858
|
function normalize(item) {
|
|
2540
|
-
if (isRecord(item)) {
|
|
3859
|
+
if (require_decision.isRecord(item)) {
|
|
2541
3860
|
const nested = item["event"];
|
|
2542
|
-
if (isRecord(nested) && typeof nested["type"] === "string") return {
|
|
3861
|
+
if (require_decision.isRecord(nested) && typeof nested["type"] === "string") return {
|
|
2543
3862
|
kind: "event",
|
|
2544
3863
|
event: nested
|
|
2545
3864
|
};
|
|
@@ -2547,7 +3866,7 @@ function normalize(item) {
|
|
|
2547
3866
|
kind: "event",
|
|
2548
3867
|
event: item
|
|
2549
3868
|
};
|
|
2550
|
-
if ("value" in item
|
|
3869
|
+
if ("value" in item) return {
|
|
2551
3870
|
kind: "state",
|
|
2552
3871
|
value: item["value"]
|
|
2553
3872
|
};
|
|
@@ -2560,7 +3879,7 @@ function normalize(item) {
|
|
|
2560
3879
|
/** Every leaf of a state value as a dot path (`{ review: 'editing' }` → `review.editing`). @internal */
|
|
2561
3880
|
function statePaths(value) {
|
|
2562
3881
|
if (typeof value === "string") return [value];
|
|
2563
|
-
if (!isRecord(value)) return [];
|
|
3882
|
+
if (!require_decision.isRecord(value)) return [];
|
|
2564
3883
|
return Object.entries(value).flatMap(([key, child]) => statePaths(child).map((path) => `${key}.${path}`));
|
|
2565
3884
|
}
|
|
2566
3885
|
/**
|
|
@@ -2573,7 +3892,7 @@ function stateMatchesPath(path, value) {
|
|
|
2573
3892
|
function deepEqual(a, b) {
|
|
2574
3893
|
if (Object.is(a, b)) return true;
|
|
2575
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]));
|
|
2576
|
-
if (!isRecord(a) || !isRecord(b)) return false;
|
|
3895
|
+
if (!require_decision.isRecord(a) || !require_decision.isRecord(b)) return false;
|
|
2577
3896
|
const keys = Object.keys(a);
|
|
2578
3897
|
return keys.length === Object.keys(b).length && keys.every((key) => deepEqual(a[key], b[key]));
|
|
2579
3898
|
}
|
|
@@ -2607,8 +3926,8 @@ function matcherFor(expected) {
|
|
|
2607
3926
|
/**
|
|
2608
3927
|
* Matches a run's trajectory against an expected one.
|
|
2609
3928
|
*
|
|
2610
|
-
* Both trajectories may be state values
|
|
2611
|
-
*
|
|
3929
|
+
* Both trajectories may be state values or events collected from
|
|
3930
|
+
* `onTransition`;
|
|
2612
3931
|
* items are compared by shape, so `['prompting', 'drafting']` scores a state
|
|
2613
3932
|
* path and `['PROMPT_SUBMITTED', 'SEND']` scores an event log with the same
|
|
2614
3933
|
* call.
|
|
@@ -2628,7 +3947,7 @@ function matcherFor(expected) {
|
|
|
2628
3947
|
*
|
|
2629
3948
|
* const path = matchesTrajectory(statePath, ['prompting', 'drafting', 'sent']);
|
|
2630
3949
|
* expect(path.matched, JSON.stringify(path.firstMiss)).toBe(true);
|
|
2631
|
-
* expect(matchesTrajectory(
|
|
3950
|
+
* expect(matchesTrajectory(events, ['PROMPT_SUBMITTED', 'SEND']).matched).toBe(true);
|
|
2632
3951
|
* ```
|
|
2633
3952
|
*
|
|
2634
3953
|
* @example Partial credit in an eval scorer
|
|
@@ -2639,6 +3958,7 @@ function matcherFor(expected) {
|
|
|
2639
3958
|
*/
|
|
2640
3959
|
function matchesTrajectory(actual, expected, options = {}) {
|
|
2641
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.");
|
|
2642
3962
|
const exact = options.exact === true;
|
|
2643
3963
|
let cursor = 0;
|
|
2644
3964
|
let matchedCount = 0;
|
|
@@ -2679,17 +3999,41 @@ function matchesTrajectory(actual, expected, options = {}) {
|
|
|
2679
3999
|
//#endregion
|
|
2680
4000
|
//#region src/scripted-executors.ts
|
|
2681
4001
|
/**
|
|
2682
|
-
* Scripted executors — a
|
|
4002
|
+
* Scripted executors — a deterministic stand-in for a model host.
|
|
2683
4003
|
*
|
|
2684
4004
|
* `createScriptedExecutors` builds a full `{ generateText, streamText, decide }`
|
|
2685
|
-
* set (plus a `userInput` handler) that plays back canned answers
|
|
2686
|
-
*
|
|
2687
|
-
* `
|
|
2688
|
-
*
|
|
2689
|
-
*
|
|
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.
|
|
2690
4011
|
*
|
|
2691
4012
|
* @module
|
|
2692
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
|
+
}
|
|
2693
4037
|
/** The only own keys an executor-result envelope carries. @internal */
|
|
2694
4038
|
const TEXT_ENVELOPE_KEYS = new Set([
|
|
2695
4039
|
"output",
|
|
@@ -2714,7 +4058,7 @@ function isTextEnvelope(value) {
|
|
|
2714
4058
|
*/
|
|
2715
4059
|
async function resolveScriptedTextEntry(entry, request, info) {
|
|
2716
4060
|
const value = typeof entry === "function" ? await entry(request, info) : entry;
|
|
2717
|
-
return isRecord(value) && isTextEnvelope(value) ? value : { output: value };
|
|
4061
|
+
return require_decision.isRecord(value) && isTextEnvelope(value) ? value : { output: value };
|
|
2718
4062
|
}
|
|
2719
4063
|
/**
|
|
2720
4064
|
* Names a pending text request in an error message. Shared with `runSeam`.
|
|
@@ -2728,26 +4072,30 @@ function describeText(request) {
|
|
|
2728
4072
|
* with `runSeam`, whose scripted answers stream the same way. @internal
|
|
2729
4073
|
*/
|
|
2730
4074
|
function emitScriptedChunk(result, info) {
|
|
2731
|
-
const output = isRecord(result) ? result["output"] : void 0;
|
|
4075
|
+
const output = require_decision.isRecord(result) ? result["output"] : void 0;
|
|
2732
4076
|
if (typeof output === "string") info?.onChunk?.(output);
|
|
2733
4077
|
}
|
|
2734
4078
|
/**
|
|
2735
|
-
*
|
|
2736
|
-
*
|
|
2737
|
-
*
|
|
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.
|
|
2738
4088
|
*
|
|
2739
|
-
* Queues are
|
|
2740
|
-
*
|
|
2741
|
-
*
|
|
2742
|
-
* script serves a machine that loops or branches: route on `request.name` (the
|
|
2743
|
-
* `setupAgent({ requests })` key) or on the decision's candidate `events`.
|
|
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.
|
|
2744
4092
|
*
|
|
2745
4093
|
* @example
|
|
2746
4094
|
* ```ts
|
|
2747
4095
|
* const result = await runAgent(moderationMachine, {
|
|
2748
4096
|
* input: { comment: 'honestly this update is terrible', trust: 20 },
|
|
2749
4097
|
* executors: createScriptedExecutors({
|
|
2750
|
-
* decisions: [{ type: 'FLAG', reason: 'Borderline tone.' }],
|
|
4098
|
+
* decisions: { moderate: [{ type: 'FLAG', reason: 'Borderline tone.' }] },
|
|
2751
4099
|
* }),
|
|
2752
4100
|
* });
|
|
2753
4101
|
* ```
|
|
@@ -2755,8 +4103,8 @@ function emitScriptedChunk(result, info) {
|
|
|
2755
4103
|
* @example Dynamic entries
|
|
2756
4104
|
* ```ts
|
|
2757
4105
|
* createScriptedExecutors({
|
|
2758
|
-
* text:
|
|
2759
|
-
* decisions:
|
|
4106
|
+
* text: { draft: (request) => `Draft about ${request.prompt}` },
|
|
4107
|
+
* decisions: { route: (request) => ({ type: request.events[0]!.type }) },
|
|
2760
4108
|
* });
|
|
2761
4109
|
* ```
|
|
2762
4110
|
*
|
|
@@ -2767,30 +4115,72 @@ function emitScriptedChunk(result, info) {
|
|
|
2767
4115
|
* ```
|
|
2768
4116
|
*/
|
|
2769
4117
|
function createScriptedExecutors(script = {}) {
|
|
2770
|
-
const decisions =
|
|
2771
|
-
const text =
|
|
4118
|
+
const decisions = toQueues(script.decisions);
|
|
4119
|
+
const text = toQueues(script.text);
|
|
2772
4120
|
const userInput = [...script.userInput ?? []];
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
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));
|
|
2776
4138
|
};
|
|
2777
4139
|
return {
|
|
4140
|
+
calls,
|
|
2778
4141
|
userInput: async (input) => {
|
|
2779
|
-
|
|
2780
|
-
|
|
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)];
|
|
2781
4150
|
return typeof entry === "function" ? await entry(input) : entry;
|
|
2782
4151
|
},
|
|
2783
|
-
generateText: nextText,
|
|
4152
|
+
generateText: (request, info) => nextText("generateText", request, info),
|
|
2784
4153
|
streamText: async (request, info) => {
|
|
2785
|
-
const
|
|
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);
|
|
2786
4169
|
emitScriptedChunk(result, info);
|
|
2787
4170
|
return result;
|
|
2788
4171
|
},
|
|
2789
4172
|
decide: async (request) => {
|
|
2790
|
-
|
|
2791
|
-
|
|
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);
|
|
2792
4182
|
const value = typeof entry === "function" ? await entry(request) : entry;
|
|
2793
|
-
return isRecord(value) && typeof value["type"] !== "string" && "event" in value ? value : { event: value };
|
|
4183
|
+
return withDefaultUsage(require_decision.isRecord(value) && typeof value["type"] !== "string" && "event" in value ? value : { event: value });
|
|
2794
4184
|
}
|
|
2795
4185
|
};
|
|
2796
4186
|
}
|
|
@@ -2805,7 +4195,7 @@ function createScriptedExecutors(script = {}) {
|
|
|
2805
4195
|
* @internal
|
|
2806
4196
|
*/
|
|
2807
4197
|
async function seamOutputOf(result, request) {
|
|
2808
|
-
if (!isRecord(result)) return;
|
|
4198
|
+
if (!require_decision.isRecord(result)) return;
|
|
2809
4199
|
if ("output" in result) return await result["output"];
|
|
2810
4200
|
if ("textStream" in result) return;
|
|
2811
4201
|
return await require_decision.normalizeGeneratorResult(result, `seam '${request.name ?? request.model}'`, { request });
|
|
@@ -2821,7 +4211,7 @@ async function seamOutputOf(result, request) {
|
|
|
2821
4211
|
* seam's own effect completion (the first `xstate.done.*`/`xstate.error.*`
|
|
2822
4212
|
* entry appended after the call was made).
|
|
2823
4213
|
*
|
|
2824
|
-
* @example
|
|
4214
|
+
* @example No API key: the seam is scripted too, so the whole thing runs offline.
|
|
2825
4215
|
* ```ts
|
|
2826
4216
|
* const run = await runSeam(emailDrafter, {
|
|
2827
4217
|
* scripts: { promptEvaluator: [vague, complete], emailDrafter: [draft] },
|
|
@@ -2865,7 +4255,7 @@ async function runSeam(machine, options) {
|
|
|
2865
4255
|
};
|
|
2866
4256
|
const scriptedAnswer = async (request, info) => {
|
|
2867
4257
|
const scripted = await takeScriptedSlot(request, info);
|
|
2868
|
-
if (!scripted) throw new
|
|
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.`);
|
|
2869
4259
|
return scripted;
|
|
2870
4260
|
};
|
|
2871
4261
|
const route = async (request, info) => {
|
|
@@ -2914,27 +4304,22 @@ async function runSeam(machine, options) {
|
|
|
2914
4304
|
};
|
|
2915
4305
|
let snapshot;
|
|
2916
4306
|
let event;
|
|
2917
|
-
|
|
4307
|
+
const events = [];
|
|
2918
4308
|
let result;
|
|
2919
4309
|
const maxTurns = options.maxTurns ?? 12;
|
|
2920
4310
|
for (let turn = 0; turn <= maxTurns; turn++) {
|
|
2921
4311
|
result = await runAgent(machine, {
|
|
2922
4312
|
...snapshot ? { snapshot } : { input: options.input },
|
|
2923
4313
|
...event ? { event } : {},
|
|
2924
|
-
...options.isIdle ? { isIdle: options.isIdle } : {},
|
|
2925
4314
|
...options.actors ? { actors: options.actors } : {},
|
|
2926
|
-
events,
|
|
2927
4315
|
executors,
|
|
2928
|
-
onEvent: () => {
|
|
2929
|
-
liveEvents++;
|
|
2930
|
-
},
|
|
2931
4316
|
onTransition: (next, causedBy) => {
|
|
2932
4317
|
if (turn > 0 && causedBy.type === "@xstate.init") return;
|
|
4318
|
+
events.push(causedBy);
|
|
4319
|
+
liveEvents = events.length;
|
|
2933
4320
|
statePath.push(next.value);
|
|
2934
4321
|
}
|
|
2935
4322
|
});
|
|
2936
|
-
events = result.events;
|
|
2937
|
-
liveEvents = events.length;
|
|
2938
4323
|
if (result.status !== "idle" || turn === maxTurns) break;
|
|
2939
4324
|
const next = options.respond?.({
|
|
2940
4325
|
snapshot: result.snapshot,
|
|
@@ -2945,13 +4330,13 @@ async function runSeam(machine, options) {
|
|
|
2945
4330
|
});
|
|
2946
4331
|
if (!next) break;
|
|
2947
4332
|
event = next;
|
|
2948
|
-
snapshot = result.
|
|
4333
|
+
snapshot = result.persist();
|
|
2949
4334
|
}
|
|
2950
4335
|
let splitAt = events.length;
|
|
2951
4336
|
if (seamReached) {
|
|
2952
4337
|
splitAt = seamEventAt;
|
|
2953
4338
|
for (let index = seamEventAt; index < events.length; index++) {
|
|
2954
|
-
const type = events[index].
|
|
4339
|
+
const type = events[index].type;
|
|
2955
4340
|
if (type.startsWith("xstate.done") || type.startsWith("xstate.error")) {
|
|
2956
4341
|
splitAt = index;
|
|
2957
4342
|
break;
|
|
@@ -2976,275 +4361,81 @@ async function runSeam(machine, options) {
|
|
|
2976
4361
|
};
|
|
2977
4362
|
}
|
|
2978
4363
|
//#endregion
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
* agent machine on xstate's `createDurable` execution (`xstate/durable`),
|
|
2983
|
-
* with the agent event log as the journal.
|
|
2984
|
-
*
|
|
2985
|
-
* Where {@link replay} + `getAgentEffects` hand a host an effect list to run
|
|
2986
|
-
* itself, `runDurableAgent` owns the whole loop on the durable runtime:
|
|
2987
|
-
* invoked actors execute live through xstate's own runtime, every EXTERNAL
|
|
2988
|
-
* event (invoke completions included) is appended to the log, and a resume
|
|
2989
|
-
* folds the log back through pure transitions — an invoke whose completion is
|
|
2990
|
-
* already journaled is never re-started, so recorded model calls are never
|
|
2991
|
-
* re-executed. Crash recovery re-runs only the work that was still in flight.
|
|
2992
|
-
*
|
|
2993
|
-
* @module
|
|
2994
|
-
*/
|
|
2995
|
-
const DONE_ACTOR_EVENT_TYPE = "xstate.done.actor";
|
|
2996
|
-
const ERROR_ACTOR_EVENT_TYPE = "xstate.error.actor";
|
|
2997
|
-
function completionActorId(event) {
|
|
2998
|
-
if (event.type !== DONE_ACTOR_EVENT_TYPE && event.type !== ERROR_ACTOR_EVENT_TYPE) return;
|
|
2999
|
-
const actorId = event.actorId;
|
|
3000
|
-
return typeof actorId === "string" ? actorId : void 0;
|
|
3001
|
-
}
|
|
3002
|
-
/** Thrown by the adapter's `waitForEvent` when nothing can produce an event. */
|
|
3003
|
-
const IDLE = Symbol("agent.durable.idle");
|
|
3004
|
-
function createMailbox() {
|
|
3005
|
-
const queue = [];
|
|
3006
|
-
const waiters = [];
|
|
3007
|
-
return {
|
|
3008
|
-
push(event) {
|
|
3009
|
-
const waiter = waiters.shift();
|
|
3010
|
-
if (waiter) waiter(event);
|
|
3011
|
-
else queue.push(event);
|
|
3012
|
-
},
|
|
3013
|
-
take() {
|
|
3014
|
-
const next = queue.shift();
|
|
3015
|
-
if (next !== void 0) return Promise.resolve(next);
|
|
3016
|
-
return new Promise((resolve) => waiters.push(resolve));
|
|
3017
|
-
},
|
|
3018
|
-
size: () => queue.length
|
|
3019
|
-
};
|
|
3020
|
-
}
|
|
3021
|
-
/**
|
|
3022
|
-
* Runs an agent machine as a durable execution: journal in, journal out.
|
|
3023
|
-
*
|
|
3024
|
-
* A fresh call starts from `input` and appends a reserved init entry; a
|
|
3025
|
-
* resume call folds `entries` through pure transitions first — invokes whose
|
|
3026
|
-
* completions are journaled are suppressed (their recorded results replay
|
|
3027
|
-
* instead of re-executing), while work that was in flight at the crash
|
|
3028
|
-
* re-executes live. After the journal, an optional `options.event` is
|
|
3029
|
-
* delivered. The call settles:
|
|
3030
|
-
*
|
|
3031
|
-
* - `done` when the machine reaches a final state, with `output`;
|
|
3032
|
-
* - `idle` when the frontier needs an external event the host has not
|
|
3033
|
-
* supplied (no live work pending, or `isIdle` says the pending work is a
|
|
3034
|
-
* human wait). Persist `entries` and call again with them later.
|
|
3035
|
-
*
|
|
3036
|
-
* ```ts
|
|
3037
|
-
* const first = await runDurableAgent(machine, { input, executors });
|
|
3038
|
-
* // ... persist first.entries; later, in a new process:
|
|
3039
|
-
* const next = await runDurableAgent(machine, {
|
|
3040
|
-
* entries: first.entries,
|
|
3041
|
-
* event: { type: "APPROVE" },
|
|
3042
|
-
* executors,
|
|
3043
|
-
* });
|
|
3044
|
-
* ```
|
|
3045
|
-
*
|
|
3046
|
-
* @experimental Built on xstate's experimental `xstate/durable` entrypoint.
|
|
3047
|
-
*/
|
|
3048
|
-
async function runDurableAgent(machine, options = {}) {
|
|
3049
|
-
const bound = options.executors ? provideExecutors(machine, options.executors, {
|
|
3050
|
-
actors: options.actors,
|
|
3051
|
-
onChunk: options.onChunk,
|
|
3052
|
-
onTrace: options.onTrace
|
|
3053
|
-
}) : options.actors ? machine.provide({ actors: options.actors }) : machine;
|
|
3054
|
-
const machineId = machine.config.id ?? machine.id ?? "(machine)";
|
|
3055
|
-
const machineVersion = options.machineVersion ?? require_decision.resolveMachineVersion(machine);
|
|
3056
|
-
const priorEntries = options.entries ?? [];
|
|
3057
|
-
if (priorEntries.length > 0) require_setup_agent.validateReplayEntries(priorEntries, {
|
|
3058
|
-
machineId,
|
|
3059
|
-
machineVersion
|
|
3060
|
-
}, "Durable journal entries");
|
|
3061
|
-
const hasInit = priorEntries[0]?.event.type === require_setup_agent.AGENT_INIT_EVENT_TYPE;
|
|
3062
|
-
const input = hasInit ? priorEntries[0].event.input : options.input;
|
|
3063
|
-
const journal = priorEntries.slice(hasInit ? 1 : 0).map((entry) => entry.event);
|
|
3064
|
-
const journaledCompletions = /* @__PURE__ */ new Map();
|
|
3065
|
-
for (const event of journal) {
|
|
3066
|
-
const actorId = completionActorId(event);
|
|
3067
|
-
if (actorId !== void 0) journaledCompletions.set(actorId, (journaledCompletions.get(actorId) ?? 0) + 1);
|
|
3068
|
-
}
|
|
3069
|
-
const storedExecutionId = hasInit ? priorEntries[0].metadata?.executionId : void 0;
|
|
3070
|
-
const executionId = typeof storedExecutionId === "string" ? storedExecutionId : hasInit ? priorEntries[0].id : crypto.randomUUID();
|
|
3071
|
-
const mailbox = createMailbox();
|
|
3072
|
-
const rootAddress = machineId;
|
|
3073
|
-
const suppressedChildren = /* @__PURE__ */ new WeakSet();
|
|
3074
|
-
const startsSeen = /* @__PURE__ */ new Map();
|
|
3075
|
-
const liveInFlight = /* @__PURE__ */ new Set();
|
|
3076
|
-
const findChildRef = (effect) => {
|
|
3077
|
-
const raw = effect;
|
|
3078
|
-
const candidates = [raw.actor, ...Array.isArray(raw.args) ? raw.args : []];
|
|
3079
|
-
for (const candidate of candidates) {
|
|
3080
|
-
const ref = candidate;
|
|
3081
|
-
if (ref && typeof ref.sessionId === "string" && typeof ref.id === "string") return ref;
|
|
3082
|
-
}
|
|
3083
|
-
};
|
|
3084
|
-
let replaying = journal.length > 0;
|
|
3085
|
-
let liveEventConsumed = false;
|
|
3086
|
-
let latestSnapshot;
|
|
3087
|
-
const execution = (0, xstate_durable.createDurable)(bound, {
|
|
3088
|
-
executionId,
|
|
3089
|
-
sendEvent(source, target, event) {
|
|
3090
|
-
if (target.address === rootAddress) {
|
|
3091
|
-
mailbox.push(event);
|
|
3092
|
-
return;
|
|
3093
|
-
}
|
|
3094
|
-
(0, xstate.deliverEvent)(source, target, event);
|
|
3095
|
-
},
|
|
3096
|
-
runtime(_metadata, effect) {
|
|
3097
|
-
const type = effect.type;
|
|
3098
|
-
if (type === "@xstate.spawn" || type === "@xstate.start") {
|
|
3099
|
-
const child = findChildRef(effect);
|
|
3100
|
-
if (!child) return {};
|
|
3101
|
-
if (type === "@xstate.spawn") {
|
|
3102
|
-
const seen = (startsSeen.get(child.id) ?? 0) + 1;
|
|
3103
|
-
startsSeen.set(child.id, seen);
|
|
3104
|
-
if (seen <= (journaledCompletions.get(child.id) ?? 0)) suppressedChildren.add(child);
|
|
3105
|
-
else liveInFlight.add(child.id);
|
|
3106
|
-
}
|
|
3107
|
-
if (suppressedChildren.has(child)) return {
|
|
3108
|
-
spawnActor() {},
|
|
3109
|
-
startActor() {}
|
|
3110
|
-
};
|
|
3111
|
-
}
|
|
3112
|
-
return {};
|
|
3113
|
-
},
|
|
3114
|
-
executeAction(action) {
|
|
3115
|
-
if (replaying) return;
|
|
3116
|
-
action.exec?.();
|
|
3117
|
-
},
|
|
3118
|
-
async waitForEvent() {
|
|
3119
|
-
if (mailbox.size() > 0) return await mailbox.take();
|
|
3120
|
-
if (liveInFlight.size > 0 && !(options.isIdle?.(latestSnapshot) ?? false)) return await mailbox.take();
|
|
3121
|
-
if (!liveEventConsumed && options.event !== void 0) {
|
|
3122
|
-
liveEventConsumed = true;
|
|
3123
|
-
return options.event;
|
|
3124
|
-
}
|
|
3125
|
-
throw IDLE;
|
|
3126
|
-
}
|
|
3127
|
-
});
|
|
3128
|
-
const entries = [...priorEntries];
|
|
3129
|
-
const entryOptions = {
|
|
3130
|
-
machineVersion,
|
|
3131
|
-
verification: options.verification ?? false
|
|
3132
|
-
};
|
|
3133
|
-
const appendEntry = (event) => {
|
|
3134
|
-
const entry = require_setup_agent.createReplayEntry(machine, entries, event, entryOptions);
|
|
3135
|
-
entries.push(entry);
|
|
3136
|
-
options.onEntry?.(entry);
|
|
3137
|
-
};
|
|
3138
|
-
if (!hasInit) {
|
|
3139
|
-
const entry = require_setup_agent.initEntry(machine, input, {
|
|
3140
|
-
...entryOptions,
|
|
3141
|
-
metadata: { executionId }
|
|
3142
|
-
});
|
|
3143
|
-
entries.push(entry);
|
|
3144
|
-
options.onEntry?.(entry);
|
|
3145
|
-
}
|
|
3146
|
-
let journalIndex = 0;
|
|
3147
|
-
let [snapshot, effects] = execution.initialTransition(input);
|
|
3148
|
-
latestSnapshot = snapshot;
|
|
3149
|
-
for (;;) {
|
|
3150
|
-
await execution.executeEffects(effects);
|
|
3151
|
-
const machineSnapshot = snapshot;
|
|
3152
|
-
if (machineSnapshot.status === "done") return {
|
|
3153
|
-
status: "done",
|
|
3154
|
-
output: machineSnapshot.output,
|
|
3155
|
-
snapshot,
|
|
3156
|
-
entries
|
|
3157
|
-
};
|
|
3158
|
-
if (machineSnapshot.status === "error") throw machineSnapshot.error;
|
|
3159
|
-
let event;
|
|
3160
|
-
let fromJournal = false;
|
|
3161
|
-
if (journalIndex < journal.length) {
|
|
3162
|
-
event = journal[journalIndex];
|
|
3163
|
-
journalIndex++;
|
|
3164
|
-
fromJournal = true;
|
|
3165
|
-
replaying = journalIndex < journal.length;
|
|
3166
|
-
} else try {
|
|
3167
|
-
event = await execution.waitForEvent();
|
|
3168
|
-
} catch (error) {
|
|
3169
|
-
if (error === IDLE) return {
|
|
3170
|
-
status: "idle",
|
|
3171
|
-
snapshot,
|
|
3172
|
-
entries
|
|
3173
|
-
};
|
|
3174
|
-
throw error;
|
|
3175
|
-
}
|
|
3176
|
-
const completedId = completionActorId(event);
|
|
3177
|
-
if (completedId !== void 0) liveInFlight.delete(completedId);
|
|
3178
|
-
if (!fromJournal) appendEntry(event);
|
|
3179
|
-
[snapshot, effects] = execution.transition(snapshot, event);
|
|
3180
|
-
latestSnapshot = snapshot;
|
|
3181
|
-
}
|
|
3182
|
-
}
|
|
3183
|
-
//#endregion
|
|
3184
|
-
exports.AGENT_EVENT_SCHEMA_VERSION = require_event_log_store.AGENT_EVENT_SCHEMA_VERSION;
|
|
3185
|
-
exports.AGENT_INIT_EVENT_TYPE = require_setup_agent.AGENT_INIT_EVENT_TYPE;
|
|
4364
|
+
exports.AGENT_EVENT_SCHEMA_VERSION = AGENT_EVENT_SCHEMA_VERSION;
|
|
4365
|
+
exports.AGENT_INIT_EVENT_TYPE = AGENT_INIT_EVENT_TYPE;
|
|
4366
|
+
exports.AGENT_MESSAGES_EVENT_TYPE = require_decision.AGENT_MESSAGES_EVENT_TYPE;
|
|
3186
4367
|
exports.AGENT_TRACE_SCHEMA_VERSION = AGENT_TRACE_SCHEMA_VERSION;
|
|
3187
4368
|
exports.AGENT_USAGE_EVENT_TYPE = require_setup_agent.AGENT_USAGE_EVENT_TYPE;
|
|
3188
4369
|
exports.AgentDecisionExhaustedError = require_decision.AgentDecisionExhaustedError;
|
|
3189
|
-
exports.AgentError =
|
|
3190
|
-
exports.AgentEventLogConflictError =
|
|
3191
|
-
exports.
|
|
3192
|
-
exports.
|
|
4370
|
+
exports.AgentError = require_decision.AgentError;
|
|
4371
|
+
exports.AgentEventLogConflictError = AgentEventLogConflictError;
|
|
4372
|
+
exports.AgentEventLogError = AgentEventLogError;
|
|
4373
|
+
exports.AgentInvalidEventPayloadError = require_decision.AgentInvalidEventPayloadError;
|
|
3193
4374
|
exports.AgentLintError = AgentLintError;
|
|
4375
|
+
exports.AgentMachineVersionMismatchError = AgentMachineVersionMismatchError;
|
|
3194
4376
|
exports.AgentMaxModelCallsExceededError = AgentMaxModelCallsExceededError;
|
|
3195
|
-
exports.AgentReplayDivergenceError =
|
|
3196
|
-
exports.
|
|
3197
|
-
exports.
|
|
3198
|
-
exports.
|
|
3199
|
-
exports.
|
|
3200
|
-
exports.
|
|
4377
|
+
exports.AgentReplayDivergenceError = AgentReplayDivergenceError;
|
|
4378
|
+
exports.AgentSnapshotDivergedError = AgentSnapshotDivergedError;
|
|
4379
|
+
exports.AgentTruncatedError = require_decision.AgentTruncatedError;
|
|
4380
|
+
exports.AgentUnknownStateError = AgentUnknownStateError;
|
|
4381
|
+
exports.NonSerializableAgentEventError = NonSerializableAgentEventError;
|
|
4382
|
+
exports.agentCallOccurrence = agentCallOccurrence;
|
|
4383
|
+
exports.appendMessages = require_decision.appendMessages;
|
|
4384
|
+
exports.assertAgentLogEntry = assertAgentLogEntry;
|
|
3201
4385
|
exports.assertAgentMachine = assertAgentMachine;
|
|
3202
|
-
exports.assertEventLogStoreConformance =
|
|
3203
|
-
exports.assertJsonSerializable =
|
|
4386
|
+
exports.assertEventLogStoreConformance = assertEventLogStoreConformance;
|
|
4387
|
+
exports.assertJsonSerializable = assertJsonSerializable;
|
|
3204
4388
|
exports.assistantMessage = require_decision.assistantMessage;
|
|
3205
4389
|
exports.bindRequestExecutor = require_decision.bindRequestExecutor;
|
|
3206
4390
|
exports.buildEnvelopeSchema = require_decision.buildEnvelopeSchema;
|
|
3207
4391
|
exports.canReach = canReach;
|
|
3208
|
-
exports.createAgentActor = createAgentActor;
|
|
3209
|
-
exports.createAgentRun = createAgentRun;
|
|
3210
4392
|
exports.createAgentSchemas = require_setup_agent.createAgentSchemas;
|
|
3211
4393
|
exports.createDecisionLogic = require_decision.createDecisionLogic;
|
|
3212
|
-
exports.
|
|
3213
|
-
exports.
|
|
4394
|
+
exports.createDecisionRequest = require_decision.createDecisionRequest;
|
|
4395
|
+
exports.createInMemoryEventLogStore = createInMemoryEventLogStore;
|
|
4396
|
+
exports.createReplayEntry = createReplayEntry;
|
|
3214
4397
|
exports.createScriptedExecutors = createScriptedExecutors;
|
|
3215
4398
|
exports.createTextLogic = require_decision.createTextLogic;
|
|
3216
|
-
exports.
|
|
3217
|
-
exports.executeAgentRequest =
|
|
4399
|
+
exports.eventFromInteraction = eventFromInteraction;
|
|
4400
|
+
exports.executeAgentRequest = executeAgentRequest;
|
|
3218
4401
|
exports.explorePaths = explorePaths;
|
|
3219
|
-
exports.
|
|
4402
|
+
exports.forkEventLog = forkEventLog;
|
|
3220
4403
|
exports.getAcceptedEvents = require_decision.getAcceptedEvents;
|
|
3221
|
-
exports.getAgentEffects = require_setup_agent.getAgentEffects;
|
|
3222
|
-
exports.getAgentMessages = require_decision.getAgentMessages;
|
|
3223
4404
|
exports.getAgentOutputMode = require_decision.getAgentOutputMode;
|
|
3224
4405
|
exports.getAgentSchemas = require_setup_agent.getAgentSchemas;
|
|
4406
|
+
exports.getCallFinishReason = require_decision.getCallFinishReason;
|
|
3225
4407
|
exports.getCallUsage = require_decision.getCallUsage;
|
|
4408
|
+
exports.getInteraction = getInteraction;
|
|
3226
4409
|
exports.getJsonSchema = require_decision.getJsonSchema;
|
|
3227
4410
|
exports.getJsonSchemaSync = require_decision.getJsonSchemaSync;
|
|
4411
|
+
exports.getLogExecutionId = getLogExecutionId;
|
|
3228
4412
|
exports.getMachineStructuralHash = require_decision.getMachineStructuralHash;
|
|
3229
|
-
exports.
|
|
3230
|
-
exports.
|
|
4413
|
+
exports.getMessageText = require_decision.getMessageText;
|
|
4414
|
+
exports.getSnapshotStateHash = getSnapshotStateHash;
|
|
3231
4415
|
exports.getStateMeta = require_decision.getStateMeta;
|
|
3232
|
-
exports.
|
|
4416
|
+
exports.getStatePath = require_decision.getStatePath;
|
|
4417
|
+
exports.getUsageFromEvents = getUsageFromEvents;
|
|
4418
|
+
exports.initEntry = initEntry;
|
|
3233
4419
|
exports.inspectTransitions = inspectTransitions;
|
|
4420
|
+
exports.interactionMetaSchema = interactionMetaSchema;
|
|
4421
|
+
exports.isAgentIdle = isAgentIdle;
|
|
4422
|
+
exports.isAgentMessages = require_decision.isAgentMessages;
|
|
3234
4423
|
exports.isStandardSchema = require_decision.isStandardSchema;
|
|
3235
4424
|
exports.lintAgentMachine = lintAgentMachine;
|
|
3236
4425
|
exports.matchesTrajectory = matchesTrajectory;
|
|
3237
|
-
exports.messagesSchema =
|
|
4426
|
+
exports.messagesSchema = require_decision.messagesSchema;
|
|
3238
4427
|
exports.parseAgentEvent = require_decision.parseAgentEvent;
|
|
3239
4428
|
exports.parseModelRef = require_decision.parseModelRef;
|
|
3240
4429
|
exports.parseOutput = require_decision.parseOutput;
|
|
3241
4430
|
exports.parseStructuredEnvelope = require_decision.parseStructuredEnvelope;
|
|
3242
4431
|
exports.provideExecutors = provideExecutors;
|
|
4432
|
+
exports.rebindActorSession = rebindActorSession;
|
|
3243
4433
|
exports.renderDecisionAttempts = require_decision.renderDecisionAttempts;
|
|
3244
|
-
exports.replay =
|
|
4434
|
+
exports.replay = replay;
|
|
3245
4435
|
exports.resolveDecision = require_decision.resolveDecision;
|
|
3246
4436
|
exports.runAgent = runAgent;
|
|
3247
|
-
exports.
|
|
4437
|
+
exports.runAgentLoop = runAgentLoop;
|
|
4438
|
+
exports.runAgentStream = runAgentStream;
|
|
3248
4439
|
exports.runSeam = runSeam;
|
|
3249
4440
|
exports.serializeTraceEvent = serializeTraceEvent;
|
|
3250
4441
|
Object.defineProperty(exports, "setupAgent", {
|
|
@@ -3258,3 +4449,4 @@ exports.systemMessage = require_decision.systemMessage;
|
|
|
3258
4449
|
exports.toolMessage = require_decision.toolMessage;
|
|
3259
4450
|
exports.traceTransitions = traceTransitions;
|
|
3260
4451
|
exports.userMessage = require_decision.userMessage;
|
|
4452
|
+
exports.validateReplayEntries = validateReplayEntries;
|