@statelyai/agent 2.0.0-alpha.23 → 2.0.0-alpha.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-sdk.cjs +30 -34
- package/dist/ai-sdk.d.cts +27 -31
- package/dist/ai-sdk.d.mts +27 -31
- package/dist/ai-sdk.mjs +27 -31
- package/dist/decision-CM9CYZ6t.cjs +519 -0
- package/dist/decision-DzSv1NpD.mjs +453 -0
- package/dist/event-log-store-Ba2fsc4E.mjs +576 -0
- package/dist/event-log-store-DQmImLYk.cjs +702 -0
- package/dist/event-log-store-DvOcs10J.d.cts +338 -0
- package/dist/event-log-store-DxyVgnas.d.mts +338 -0
- package/dist/index.cjs +429 -4272
- package/dist/index.d.cts +104 -1005
- package/dist/index.d.mts +104 -1005
- package/dist/index.mjs +389 -4202
- package/dist/log.cjs +392 -0
- package/dist/log.d.cts +32 -0
- package/dist/log.d.mts +32 -0
- package/dist/log.mjs +371 -0
- package/dist/machines.cjs +27 -16
- package/dist/machines.d.cts +1 -1
- package/dist/machines.d.mts +1 -1
- package/dist/machines.mjs +27 -16
- package/dist/openai.cjs +16 -15
- package/dist/openai.d.cts +9 -9
- package/dist/openai.d.mts +9 -9
- package/dist/openai.mjs +10 -9
- package/dist/otel.d.cts +1 -1
- package/dist/otel.d.mts +1 -1
- package/dist/{run-agent-Ck7Sa3Tb.d.mts → run-agent-BOfoJg5B.d.cts} +86 -387
- package/dist/{run-agent-DWqA8KiI.d.cts → run-agent-CG343kFs.d.mts} +86 -387
- package/dist/run-agent-Cb1-LS5P.cjs +1783 -0
- package/dist/run-agent-DvDQd4If.mjs +1669 -0
- package/dist/{setup-agent-CeO2fARv.mjs → setup-agent-CFW-jPI8.mjs} +9 -20
- package/dist/{setup-agent-Dl2VELLi.cjs → setup-agent-CPf7MxnG.cjs} +20 -37
- package/dist/testing.cjs +1222 -0
- package/dist/testing.d.cts +602 -0
- package/dist/testing.d.mts +602 -0
- package/dist/testing.mjs +1212 -0
- package/dist/{text-logic-DJBK3LbG.d.mts → text-logic-BFEOxyxv.d.cts} +71 -147
- package/dist/text-logic-Bl_ZRxDi.mjs +833 -0
- package/dist/text-logic-C8xyr6Ag.cjs +1091 -0
- package/dist/{text-logic-C7bSnH_B.d.cts → text-logic-hZSiRUo1.d.mts} +71 -147
- package/dist/{types-CjHk5OdH.d.cts → types-B1Qs2q3q.d.cts} +2 -1
- package/dist/{types-DWA6X9R-.d.mts → types-tA3SdczG.d.mts} +2 -1
- package/dist/usage-BdEjFuIi.mjs +5 -0
- package/dist/usage-efOQPdf_.cjs +10 -0
- package/dist/utils-C1p37X_c.d.mts +209 -0
- package/dist/utils-RalRn9sk.d.cts +209 -0
- package/package.json +24 -4
- package/readme.md +2 -2
- package/skills/generate-machine/SKILL.md +4 -6
- package/dist/decision-BBZ2jMO1.mjs +0 -1490
- package/dist/decision-Cv6SfNWl.cjs +0 -1856
|
@@ -0,0 +1,1783 @@
|
|
|
1
|
+
const require_text_logic = require("./text-logic-C8xyr6Ag.cjs");
|
|
2
|
+
const require_decision = require("./decision-CM9CYZ6t.cjs");
|
|
3
|
+
const require_usage = require("./usage-efOQPdf_.cjs");
|
|
4
|
+
const require_event_log_store = require("./event-log-store-DQmImLYk.cjs");
|
|
5
|
+
require("./validate.cjs");
|
|
6
|
+
let xstate = require("xstate");
|
|
7
|
+
//#region src/steps.ts
|
|
8
|
+
/**
|
|
9
|
+
* The step API: the pure `(state, event) => (nextState, requests)` view of an
|
|
10
|
+
* agent machine. An {@link AgentStep} is a snapshot plus the model requests
|
|
11
|
+
* the machine is now waiting on; nothing here executes anything. A host
|
|
12
|
+
* resolves each request however it likes ({@link executeAgentRequest},
|
|
13
|
+
* `resolveDecision`, a queue, a durable workflow, a replayed log) and feeds
|
|
14
|
+
* the result back with {@link resolveAgentStep} or {@link rejectAgentStep}.
|
|
15
|
+
* `runAgent` is the in-process host built for the common case.
|
|
16
|
+
* @module
|
|
17
|
+
*/
|
|
18
|
+
/** @internal Normalizes current and legacy XState invoke effect shapes. */
|
|
19
|
+
function getInvokeEffectMetadata(action) {
|
|
20
|
+
if (action.type === "@xstate.spawn") return action;
|
|
21
|
+
if (action.type === "xstate.spawnChild") {
|
|
22
|
+
const params = action.params;
|
|
23
|
+
return params ? {
|
|
24
|
+
...params,
|
|
25
|
+
logic: action.logic
|
|
26
|
+
} : void 0;
|
|
27
|
+
}
|
|
28
|
+
if (action.type === "@xstate.start" && typeof action.src === "string") return action;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The invokes a snapshot is still waiting on, lowered to the same spawn-effect
|
|
32
|
+
* shape {@link getAgentRequests} reads. Derived from the snapshot's live
|
|
33
|
+
* children rather than the last transition's actions, so a state that invokes
|
|
34
|
+
* several actors at once (an `invoke: [...]` array, or parallel regions each
|
|
35
|
+
* invoking) keeps reporting the siblings after the first one settles —
|
|
36
|
+
* including when the settled invoke's `onDone` is targetless and starts
|
|
37
|
+
* nothing new.
|
|
38
|
+
*
|
|
39
|
+
* Entries are ordered by invoke id, so a walker that resolves one invoke per
|
|
40
|
+
* step is deterministic.
|
|
41
|
+
*
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
function getPendingInvokes(snapshot) {
|
|
45
|
+
const children = snapshot.children ?? {};
|
|
46
|
+
return Object.keys(children).sort().flatMap((id) => {
|
|
47
|
+
const child = children[id];
|
|
48
|
+
const childSnapshot = child?.getSnapshot?.();
|
|
49
|
+
if (!child || childSnapshot?.status !== "active") return [];
|
|
50
|
+
return [{
|
|
51
|
+
type: "@xstate.spawn",
|
|
52
|
+
id,
|
|
53
|
+
src: child.src,
|
|
54
|
+
input: childSnapshot.input,
|
|
55
|
+
logic: child.logic
|
|
56
|
+
}];
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Scans a set of executable actions (as returned by xstate's `transition`/
|
|
61
|
+
* `initialTransition`) for spawned `TextLogic`/`DecisionLogic` invokes and
|
|
62
|
+
* lowers each into an {@link AgentStepRequest}. Pass `options.machine` to
|
|
63
|
+
* pre-fill `schemas`/`actors` from the machine's registered `setupAgent`
|
|
64
|
+
* options instead of passing them by hand.
|
|
65
|
+
* `options.snapshot` is required to resolve a decision's candidate events
|
|
66
|
+
* (intersecting declared `allowedEvents` with what's currently legal) — omit
|
|
67
|
+
* it and decision requests report an empty `events` list.
|
|
68
|
+
*
|
|
69
|
+
* @internal
|
|
70
|
+
*/
|
|
71
|
+
function getAgentRequests(actions, { machine, ...options } = {}) {
|
|
72
|
+
if (machine) options = {
|
|
73
|
+
...require_text_logic.getRegisteredAgentExecutionOptions(machine),
|
|
74
|
+
...options
|
|
75
|
+
};
|
|
76
|
+
return actions.flatMap((action) => {
|
|
77
|
+
const params = getInvokeEffectMetadata(action);
|
|
78
|
+
if (!params || typeof params.src !== "string") return [];
|
|
79
|
+
if (typeof params.id !== "string" || params.id.length === 0) throw new Error(`Agent invoke '${params.src}' must define a durable string id.`);
|
|
80
|
+
const registeredLogic = require_text_logic.isTextLogic(params.logic) || require_decision.isDecisionLogic(params.logic) ? params.logic : options.actors?.[params.src];
|
|
81
|
+
if (require_decision.isDecisionLogic(registeredLogic)) {
|
|
82
|
+
const decisionRequest = registeredLogic.request(params.input);
|
|
83
|
+
const allowedEventTypes = registeredLogic.allowedEventTypes?.(params.input);
|
|
84
|
+
const events = options.snapshot ? require_decision.getAcceptedEvents(options.snapshot, {
|
|
85
|
+
events: options.events,
|
|
86
|
+
schemas: options.schemas,
|
|
87
|
+
eventTypes: allowedEventTypes
|
|
88
|
+
}) : [];
|
|
89
|
+
return [{
|
|
90
|
+
...decisionRequest,
|
|
91
|
+
id: params.id,
|
|
92
|
+
events
|
|
93
|
+
}];
|
|
94
|
+
}
|
|
95
|
+
const textLogic = require_text_logic.isTextLogic(registeredLogic) ? registeredLogic : void 0;
|
|
96
|
+
const input = textLogic ? textLogic.request(params.input) : void 0;
|
|
97
|
+
if (!input) return [];
|
|
98
|
+
return [{
|
|
99
|
+
kind: "text",
|
|
100
|
+
id: params.id,
|
|
101
|
+
src: params.src,
|
|
102
|
+
...textLogic ? { mode: textLogic.mode } : {},
|
|
103
|
+
input,
|
|
104
|
+
tools: input.tools ?? {},
|
|
105
|
+
events: []
|
|
106
|
+
}];
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Builds the canonical `xstate.done.actor` event xstate's `transition()`
|
|
111
|
+
* expects to resolve a spawned invoke — including the child session identity
|
|
112
|
+
* that rejects a stale completion from an earlier incarnation.
|
|
113
|
+
*
|
|
114
|
+
* @internal
|
|
115
|
+
*/
|
|
116
|
+
function doneEvent(snapshot, request, output) {
|
|
117
|
+
const id = typeof request === "string" ? request : request.id;
|
|
118
|
+
const child = snapshot.children[id];
|
|
119
|
+
return {
|
|
120
|
+
type: "xstate.done.actor",
|
|
121
|
+
actorId: id,
|
|
122
|
+
...typeof child?.sessionId === "string" ? { sessionId: child.sessionId } : {},
|
|
123
|
+
output
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* The `xstate.error.actor` counterpart of {@link doneEvent} — the event a
|
|
128
|
+
* failed invoke delivers, which `onError` transitions observe. Carries the
|
|
129
|
+
* same child session identity, so xstate does not discard it as stale.
|
|
130
|
+
*
|
|
131
|
+
* @internal
|
|
132
|
+
*/
|
|
133
|
+
function errorEvent(snapshot, request, error) {
|
|
134
|
+
const id = typeof request === "string" ? request : request.id;
|
|
135
|
+
const child = snapshot.children[id];
|
|
136
|
+
return {
|
|
137
|
+
type: "xstate.error.actor",
|
|
138
|
+
actorId: id,
|
|
139
|
+
...typeof child?.sessionId === "string" ? { sessionId: child.sessionId } : {},
|
|
140
|
+
error
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Applies a request's `output` as a done event (or, with `outcome: 'error'`,
|
|
145
|
+
* its `output` as the invoke's error) via `transition(...)`, returning the raw
|
|
146
|
+
* `[snapshot, actions]` tuple. Lower-level than {@link resolveAgentStep} /
|
|
147
|
+
* {@link rejectAgentStep} — those helpers wrap this and also run
|
|
148
|
+
* {@link getAgentRequests} to produce the next {@link AgentStep}.
|
|
149
|
+
*
|
|
150
|
+
* @internal
|
|
151
|
+
*/
|
|
152
|
+
function transitionResult(logic, snapshot, request, output, outcome = "output") {
|
|
153
|
+
const event = outcome === "error" ? errorEvent(snapshot, request, output) : doneEvent(snapshot, request, output);
|
|
154
|
+
const result = (0, xstate.transition)(logic, snapshot, event);
|
|
155
|
+
applyFinalStateOutput(logic, result[0], event);
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Starts a machine and returns its first {@link AgentStep} — the step-path
|
|
160
|
+
* equivalent of `initialTransition` plus request discovery. Begins the
|
|
161
|
+
* durable/per-model-call-checkpoint loop: resolve each `step.requests` entry
|
|
162
|
+
* (via {@link executeAgentRequest} for `kind: 'text'`, or
|
|
163
|
+
* {@link resolveDecision} for `kind: 'decision'`), then advance with
|
|
164
|
+
* {@link resolveAgentStep} or {@link transitionAgentStep}.
|
|
165
|
+
*/
|
|
166
|
+
function initialAgentStep(machine, input, options) {
|
|
167
|
+
const [snapshot, actions] = (0, xstate.initialTransition)(machine, input);
|
|
168
|
+
return createAgentStep(machine, snapshot, actions, require_text_logic.getRegisteredAgentExecutionOptions(machine, options));
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Applies an externally-sent event (e.g. a decision's chosen event, or a
|
|
172
|
+
* human's reply) and returns the next {@link AgentStep}. Accepts **either**
|
|
173
|
+
* a raw snapshot **or** a prior `AgentStep` as the second argument —
|
|
174
|
+
* `.snapshot` is unwrapped automatically, so callers can thread the whole
|
|
175
|
+
* step object through without manually plucking the snapshot out.
|
|
176
|
+
*/
|
|
177
|
+
function transitionAgentStep(machine, snapshotOrStep, event, options) {
|
|
178
|
+
const [nextSnapshot, actions] = (0, xstate.transition)(machine, isAgentStep(snapshotOrStep) ? snapshotOrStep.snapshot : snapshotOrStep, event);
|
|
179
|
+
return createAgentStep(machine, nextSnapshot, actions, require_text_logic.getRegisteredAgentExecutionOptions(machine, options));
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Applies a resolved text request's output (a `kind: 'text'`
|
|
183
|
+
* {@link AgentRequest} — not a decision) as a done event and returns the
|
|
184
|
+
* next {@link AgentStep}. For decisions, resolve with `resolveDecision`
|
|
185
|
+
* (which returns a {@link ChosenEvent}) and apply it with
|
|
186
|
+
* {@link transitionAgentStep} instead — a decision has no output value of
|
|
187
|
+
* its own to feed here.
|
|
188
|
+
*/
|
|
189
|
+
function resolveAgentStep(machine, step, request, output, options) {
|
|
190
|
+
const [snapshot, actions] = transitionResult(machine, step.snapshot, request, output);
|
|
191
|
+
return createAgentStep(machine, snapshot, actions, require_text_logic.getRegisteredAgentExecutionOptions(machine, options));
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Rejects a pending invoke — the failure counterpart of
|
|
195
|
+
* {@link resolveAgentStep}. Delivers `error` as the invoke's
|
|
196
|
+
* `xstate.error.actor` event, so the machine takes that invoke's `onError`
|
|
197
|
+
* transition and returns the next {@link AgentStep}. With no `onError` in
|
|
198
|
+
* scope the returned step's snapshot has `status: 'error'`, exactly as a live
|
|
199
|
+
* run's failed invoke does.
|
|
200
|
+
*
|
|
201
|
+
* A durable host replays a failed call with this: the log says the call
|
|
202
|
+
* failed, so the replay rejects the invoke instead of resolving it. Scripted
|
|
203
|
+
* playthroughs use it too — see `simulateAgent`'s `script.errors`.
|
|
204
|
+
*
|
|
205
|
+
* `error` can be any value. An `Error` instance is the usual one, but a plain
|
|
206
|
+
* object (`{ code: 'truncated' }`) reaches the `onError` transition as
|
|
207
|
+
* `event.error` untouched, so a machine that branches on a failure code is
|
|
208
|
+
* testable without constructing a provider's error class.
|
|
209
|
+
*/
|
|
210
|
+
function rejectAgentStep(machine, step, request, error, options) {
|
|
211
|
+
const [snapshot, actions] = transitionResult(machine, step.snapshot, request, error, "error");
|
|
212
|
+
return createAgentStep(machine, snapshot, actions, require_text_logic.getRegisteredAgentExecutionOptions(machine, options));
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Resolves one **text** request ({@link AgentRequest}, from
|
|
216
|
+
* {@link AgentStep.requests}) against a host's {@link AgentRequestExecutors}
|
|
217
|
+
* — merges the request's tools, dispatches to `generateText`/`streamText`
|
|
218
|
+
* per `mode`, and validates the result against the request's
|
|
219
|
+
* `outputSchema` if present. **Text-only**: passing a `kind: 'decision'`
|
|
220
|
+
* request throws, directing the caller to `resolveDecision(request,
|
|
221
|
+
* executors, ...)` instead. Returns the invoke's output — `{ result,
|
|
222
|
+
* messages }`, ready for {@link resolveAgentStep} — plus the `raw` executor
|
|
223
|
+
* result (tool calls, usage, finish reason).
|
|
224
|
+
*/
|
|
225
|
+
async function executeAgentRequest(request, executors) {
|
|
226
|
+
if (request.kind === "decision") throw new Error("executeAgentRequest(...) is text-only. Resolve a 'decision' request with resolveDecision(request, executors, ...) instead.");
|
|
227
|
+
assertTextExecutor(request, executors);
|
|
228
|
+
const { result, raw } = await require_text_logic.executeAgentTextRequest(request.mode ?? "generate", request.id, request.input, executors, request.tools);
|
|
229
|
+
return {
|
|
230
|
+
result: request.input.outputSchema ? require_text_logic.validateSchemaSync(request.input.outputSchema, result) : result,
|
|
231
|
+
messages: require_text_logic.responseMessagesOf(raw),
|
|
232
|
+
raw
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
function assertTextExecutor(request, executors) {
|
|
236
|
+
const mode = request.mode ?? "generate";
|
|
237
|
+
const kind = mode === "stream" ? "streamText" : "generateText";
|
|
238
|
+
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.`);
|
|
239
|
+
}
|
|
240
|
+
function createAgentStep(machine, snapshot, actions, options) {
|
|
241
|
+
applyFinalStateOutput(machine, snapshot);
|
|
242
|
+
return {
|
|
243
|
+
snapshot,
|
|
244
|
+
actions,
|
|
245
|
+
requests: getAgentRequests(getPendingInvokes(snapshot), {
|
|
246
|
+
...options,
|
|
247
|
+
snapshot
|
|
248
|
+
}),
|
|
249
|
+
done: snapshot.status === "done"
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function resolveStateValueConfig(config, value) {
|
|
253
|
+
if (typeof value === "string") return config.states?.[value];
|
|
254
|
+
if (!value || typeof value !== "object") return;
|
|
255
|
+
for (const [key, childValue] of Object.entries(value)) {
|
|
256
|
+
const childConfig = config.states?.[key];
|
|
257
|
+
if (!childConfig) continue;
|
|
258
|
+
if (childConfig.type === "final") return childConfig;
|
|
259
|
+
const nested = resolveStateValueConfig(childConfig, childValue);
|
|
260
|
+
if (nested) return nested;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
function applyFinalStateOutput(logic, snapshot, event) {
|
|
264
|
+
const machineSnapshot = snapshot;
|
|
265
|
+
if (machineSnapshot.status !== "done" || machineSnapshot.output !== void 0 || !("config" in logic)) return;
|
|
266
|
+
const config = logic.config;
|
|
267
|
+
if (!config) return;
|
|
268
|
+
const output = resolveStateValueConfig(config, machineSnapshot.value)?.output;
|
|
269
|
+
if (output === void 0) return;
|
|
270
|
+
machineSnapshot.output = typeof output === "function" ? output({
|
|
271
|
+
context: machineSnapshot.context,
|
|
272
|
+
event
|
|
273
|
+
}) : output;
|
|
274
|
+
}
|
|
275
|
+
function isAgentStep(value) {
|
|
276
|
+
return !!value && typeof value === "object" && "snapshot" in value && "actions" in value && "requests" in value;
|
|
277
|
+
}
|
|
278
|
+
//#endregion
|
|
279
|
+
//#region src/run-agent.ts
|
|
280
|
+
/**
|
|
281
|
+
* Thrown by {@link runAgent} when a resume is given BOTH an `events` log and a
|
|
282
|
+
* `snapshot` that claims a position in that log (`agentMeta.logIndex`), and the
|
|
283
|
+
* two disagree about the state at that position. The log is the source of
|
|
284
|
+
* truth, so this is a host bug (a snapshot cached from a different lineage, or
|
|
285
|
+
* a snapshot mutated out of band), not a recoverable resume: pass the log
|
|
286
|
+
* alone, or the matching snapshot.
|
|
287
|
+
*/
|
|
288
|
+
var AgentSnapshotDivergedError = class extends require_text_logic.AgentError {
|
|
289
|
+
constructor(expected, actual, logIndex) {
|
|
290
|
+
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}'.`);
|
|
291
|
+
this.expected = expected;
|
|
292
|
+
this.actual = actual;
|
|
293
|
+
this.logIndex = logIndex;
|
|
294
|
+
this.name = "AgentSnapshotDivergedError";
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
function hashResumeSnapshot(snapshot) {
|
|
298
|
+
if (snapshot !== null && typeof snapshot === "object" && "agentMeta" in snapshot) {
|
|
299
|
+
const rest = {};
|
|
300
|
+
for (const [key, value] of Object.entries(snapshot)) if (key !== "agentMeta") rest[key] = value;
|
|
301
|
+
return require_event_log_store.getSnapshotStateHash(rest);
|
|
302
|
+
}
|
|
303
|
+
return require_event_log_store.getSnapshotStateHash(snapshot);
|
|
304
|
+
}
|
|
305
|
+
function readAgentMeta(snapshot) {
|
|
306
|
+
const meta = snapshot?.agentMeta;
|
|
307
|
+
return meta !== null && typeof meta === "object" ? meta : void 0;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* The version of the {@link AgentTraceEvent} envelope every trace event carries
|
|
311
|
+
* as `schemaVersion`. Bumped only on a breaking change to the envelope or any
|
|
312
|
+
* payload shape, so a consumer can gate on it. Emitted identically by
|
|
313
|
+
* {@link runAgent}, {@link provideExecutors}' `onTrace`, and
|
|
314
|
+
* {@link traceTransitions}.
|
|
315
|
+
*/
|
|
316
|
+
const AGENT_TRACE_SCHEMA_VERSION = 1;
|
|
317
|
+
const TRACE_ENVELOPE_KEYS = [
|
|
318
|
+
"schemaVersion",
|
|
319
|
+
"runId",
|
|
320
|
+
"seq",
|
|
321
|
+
"timestamp",
|
|
322
|
+
"machineId",
|
|
323
|
+
"machineVersion",
|
|
324
|
+
"type",
|
|
325
|
+
"status",
|
|
326
|
+
"cause",
|
|
327
|
+
"reasoning",
|
|
328
|
+
"finishReason",
|
|
329
|
+
"chunk"
|
|
330
|
+
];
|
|
331
|
+
/**
|
|
332
|
+
* Best-effort JSON projection of an arbitrary value. Never throws: functions,
|
|
333
|
+
* symbols, `undefined`, and cyclic back-references are DROPPED (array holes
|
|
334
|
+
* become `null`), non-finite numbers become `null`, `bigint`s become strings,
|
|
335
|
+
* `Error`s become `{ name, message, stack?, code? }`, and anything with a
|
|
336
|
+
* `toJSON()` (e.g. `Date`) is projected through it — the same losses a
|
|
337
|
+
* `JSON.parse(JSON.stringify(...))` round-trip incurs, minus the throws.
|
|
338
|
+
*/
|
|
339
|
+
function toJsonValue(value, ancestors) {
|
|
340
|
+
if (value === null) return null;
|
|
341
|
+
const type = typeof value;
|
|
342
|
+
if (type === "string" || type === "boolean") return value;
|
|
343
|
+
if (type === "number") return Number.isFinite(value) ? value : null;
|
|
344
|
+
if (type === "bigint") return value.toString();
|
|
345
|
+
if (type !== "object") return;
|
|
346
|
+
const object = value;
|
|
347
|
+
if (ancestors.includes(object)) return;
|
|
348
|
+
const nextAncestors = [...ancestors, object];
|
|
349
|
+
if (object instanceof Error) {
|
|
350
|
+
const serialized = {
|
|
351
|
+
name: object.name,
|
|
352
|
+
message: object.message
|
|
353
|
+
};
|
|
354
|
+
if (typeof object.stack === "string") serialized.stack = object.stack;
|
|
355
|
+
const code = object.code;
|
|
356
|
+
if (typeof code === "string") serialized.code = code;
|
|
357
|
+
const cause = toJsonValue(object.cause, nextAncestors);
|
|
358
|
+
if (cause !== void 0) serialized.cause = cause;
|
|
359
|
+
return serialized;
|
|
360
|
+
}
|
|
361
|
+
const toJSON = object.toJSON;
|
|
362
|
+
if (typeof toJSON === "function") return toJsonValue(toJSON.call(object), nextAncestors);
|
|
363
|
+
if (Array.isArray(object)) return object.map((item) => toJsonValue(item, nextAncestors) ?? null);
|
|
364
|
+
const out = {};
|
|
365
|
+
for (const [key, item] of Object.entries(object)) {
|
|
366
|
+
const serializedItem = toJsonValue(item, nextAncestors);
|
|
367
|
+
if (serializedItem !== void 0) out[key] = serializedItem;
|
|
368
|
+
}
|
|
369
|
+
return out;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Projects an {@link AgentTraceEvent} into a guaranteed JSON-safe envelope —
|
|
373
|
+
* the form the trace stream is actually sold for (one `JSON.stringify` per line
|
|
374
|
+
* in a JSONL file). Live values are sanitized rather than trusted:
|
|
375
|
+
*
|
|
376
|
+
* - Snapshots (`run.start`, `machine.transition`, `run.end`) go through the
|
|
377
|
+
* same JSON round-trip as `machine.getPersistedSnapshot(...)`, so what lands on disk is
|
|
378
|
+
* what a resume would see.
|
|
379
|
+
* - `request.end`'s `raw` (a provider SDK object, frequently cyclic) is DROPPED
|
|
380
|
+
* unless `includeRaw` is set, in which case it is sanitized like everything
|
|
381
|
+
* else.
|
|
382
|
+
* - Non-serializable values anywhere (functions, symbols, `undefined`, cyclic
|
|
383
|
+
* back-references) are dropped; `Error`s become `{ name, message, stack?,
|
|
384
|
+
* code? }` instead of `{}`. Nothing throws.
|
|
385
|
+
*
|
|
386
|
+
* @example
|
|
387
|
+
* ```ts
|
|
388
|
+
* await appendFile('trace.jsonl', JSON.stringify(serializeTraceEvent(event)) + '\n');
|
|
389
|
+
* ```
|
|
390
|
+
*/
|
|
391
|
+
function serializeTraceEvent(event, options = {}) {
|
|
392
|
+
const out = {};
|
|
393
|
+
for (const [key, value] of Object.entries(event)) {
|
|
394
|
+
if (key === "raw" && !options.includeRaw) continue;
|
|
395
|
+
if (TRACE_ENVELOPE_KEYS.includes(key)) {
|
|
396
|
+
if (value !== void 0) out[key] = value;
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
const serialized = toJsonValue(value, []);
|
|
400
|
+
if (serialized !== void 0) out[key] = serialized;
|
|
401
|
+
}
|
|
402
|
+
return out;
|
|
403
|
+
}
|
|
404
|
+
let nextRunAgentTraceId = 1;
|
|
405
|
+
/**
|
|
406
|
+
* Thrown into the invoke that would have made the call once
|
|
407
|
+
* {@link RunAgentOptions.maxModelCalls} is spent. It reaches the machine
|
|
408
|
+
* through the normal error channel, so an invoke's `onError` can branch on it
|
|
409
|
+
* (`error.code === 'max-model-calls'`, the same string the settled result's
|
|
410
|
+
* `cause` uses) and route to a degraded/finish state instead of failing the
|
|
411
|
+
* run. Unhandled, it settles `{ status: 'error', cause: 'max-model-calls' }`.
|
|
412
|
+
*
|
|
413
|
+
* ```ts
|
|
414
|
+
* onError: [
|
|
415
|
+
* { guard: ({ event }) => event.error?.code === 'max-model-calls', target: 'budgetSpent' },
|
|
416
|
+
* { target: 'failed' },
|
|
417
|
+
* ]
|
|
418
|
+
* ```
|
|
419
|
+
*/
|
|
420
|
+
var AgentMaxModelCallsExceededError = class extends require_text_logic.AgentError {
|
|
421
|
+
/** The budget that was exceeded (`options.maxModelCalls`). */
|
|
422
|
+
maxModelCalls;
|
|
423
|
+
constructor(maxModelCalls) {
|
|
424
|
+
super("max-model-calls", `runAgent exceeded maxModelCalls (${maxModelCalls}). Raise the budget, or handle it in the invoke's onError (error.code === 'max-model-calls').`);
|
|
425
|
+
this.name = "AgentMaxModelCallsExceededError";
|
|
426
|
+
this.maxModelCalls = maxModelCalls;
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
function wrapsDecisionExhausted(error) {
|
|
430
|
+
let current = error;
|
|
431
|
+
for (let depth = 0; depth < 10 && current != null; depth++) {
|
|
432
|
+
if (current instanceof require_decision.AgentDecisionExhaustedError) return true;
|
|
433
|
+
current = current.cause;
|
|
434
|
+
}
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Recursively collects every invoke's `src` from raw machine config (spike
|
|
439
|
+
* S6: `machine.config` preserves authored srcs; the built `machine.root`
|
|
440
|
+
* normalizes object srcs to synthetic string ids and loses the distinction
|
|
441
|
+
* this walk needs). Function-valued `src` resolvers are dynamic and are not
|
|
442
|
+
* statically analyzable, so they are skipped (pass-through, like any other
|
|
443
|
+
* non-agent actor).
|
|
444
|
+
*/
|
|
445
|
+
function collectConfiguredInvokeSrcs(stateConfig, stateName, out) {
|
|
446
|
+
if (!stateConfig) return;
|
|
447
|
+
const invokes = stateConfig.invoke === void 0 ? [] : Array.isArray(stateConfig.invoke) ? stateConfig.invoke : [stateConfig.invoke];
|
|
448
|
+
for (const invokeConfig of invokes) {
|
|
449
|
+
const src = invokeConfig?.src;
|
|
450
|
+
if (typeof src === "string" || src && typeof src === "object") out.push({
|
|
451
|
+
stateName,
|
|
452
|
+
src
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
for (const [childName, childConfig] of Object.entries(stateConfig.states ?? {})) collectConfiguredInvokeSrcs(childConfig, `${stateName}.${childName}`, out);
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Duck-types a state machine actor logic (an invoked child machine) vs. any
|
|
459
|
+
* other actor logic. xstate's `StateMachine` carries `.config`, `.root`, and
|
|
460
|
+
* a `.provide(...)` method plus a `sources.actors` map — this
|
|
461
|
+
* combination is unique to machines and survives the dual-package/version
|
|
462
|
+
* boundary an `instanceof` check would not. Used to descend the bind-time
|
|
463
|
+
* walk into invoked child machines (their internal agent requests are opaque
|
|
464
|
+
* to the parent-level source walk otherwise).
|
|
465
|
+
*/
|
|
466
|
+
function isStateMachineLogic(logic) {
|
|
467
|
+
return !!logic && typeof logic === "object" && "config" in logic && "root" in logic && typeof logic.provide === "function" && typeof logic.sources === "object" && !!logic.sources?.actors;
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Fails fast (throws) at bind time — before any actor runs — when the
|
|
471
|
+
* machine invokes an agent actor `runAgent` cannot execute. See §3.2 point 2.
|
|
472
|
+
*
|
|
473
|
+
* Recurses into invoked child state machines (arbitrarily deep). A child
|
|
474
|
+
* machine's agent requests reached through string-keyed invoke srcs DO inherit
|
|
475
|
+
* the parent runAgent's `generateText`/`streamText`/`decide` executors —
|
|
476
|
+
* runAgent rebinds them with the same host-backed wrappers (see
|
|
477
|
+
* {@link rebindChildMachine}) — so the only remaining bind-time errors are: a
|
|
478
|
+
* required executor kind missing entirely (naming the invoke chain and src),
|
|
479
|
+
* and an unbound request reached through a direct-object invoke src that can't
|
|
480
|
+
* be rebound ({@link unrebindableChildRequestError}). A request that carries
|
|
481
|
+
* its own executor (`.withExecutor(...)`, tracked in `executorBoundLogics`)
|
|
482
|
+
* always runs itself; explicit binding shadows inheritance.
|
|
483
|
+
*/
|
|
484
|
+
function assertBindable(machine, effectiveSources, executors) {
|
|
485
|
+
assertMachineBindable(machine, effectiveSources, executors, {
|
|
486
|
+
isChild: false,
|
|
487
|
+
childPath: "",
|
|
488
|
+
rebindable: true,
|
|
489
|
+
visited: new Set([machine])
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
function assertMachineBindable(machine, effectiveSources, executors, ctx) {
|
|
493
|
+
const invokes = [];
|
|
494
|
+
collectConfiguredInvokeSrcs(machine.config, machine.config.id ?? "(root)", invokes);
|
|
495
|
+
const where = ctx.isChild ? `child machine '${ctx.childPath}' state` : "state";
|
|
496
|
+
for (const { stateName, src } of invokes) {
|
|
497
|
+
if (typeof src !== "string") {
|
|
498
|
+
if (isStateMachineLogic(src)) {
|
|
499
|
+
assertChildMachineBindable(src, src, stateName, executors, ctx);
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
if ((require_text_logic.isTextLogic(src) || require_decision.isDecisionLogic(src)) && !require_text_logic.executorBoundLogics.has(src)) throw new Error(`runAgent: ${where} '${stateName}' invokes a direct-object actor logic (kind: '${src.kind}'). Direct-object invoke srcs cannot be rebound by runAgent — either call '.withExecutor(...)' on the logic before invoking it, or register it as a string-keyed actor source instead (machine.provide({ actors: { name: logic } })) and invoke it by name.`);
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
const logic = effectiveSources[src];
|
|
506
|
+
if (logic === void 0) throw new Error(`runAgent: ${where} '${stateName}' invokes unregistered actor source '${src}'. Provide it via machine.provide({ actors: { '${src}': ... } }) or runAgent(machine, { actors: { '${src}': ... } }).`);
|
|
507
|
+
if (isStateMachineLogic(logic)) {
|
|
508
|
+
assertChildMachineBindable(logic, src, stateName, executors, ctx);
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
if (require_decision.isDecisionLogic(logic)) {
|
|
512
|
+
if (require_text_logic.executorBoundLogics.has(logic)) continue;
|
|
513
|
+
if (!ctx.rebindable) throw unrebindableChildRequestError(ctx.childPath, stateName, src, "decision");
|
|
514
|
+
if (!executors.decide) throw new Error(`runAgent: ${where} '${stateName}' invokes decision source '${src}' but no 'decide' executor was provided to runAgent(...).`);
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
if (require_text_logic.isTextLogic(logic)) {
|
|
518
|
+
if (require_text_logic.executorBoundLogics.has(logic)) continue;
|
|
519
|
+
if (!ctx.rebindable) throw unrebindableChildRequestError(ctx.childPath, stateName, src, logic.mode === "stream" ? "streaming text" : "text");
|
|
520
|
+
if (logic.mode === "stream" && !executors.streamText) throw new Error(`runAgent: ${where} '${stateName}' invokes streaming text source '${src}' but no 'streamText' executor was provided to runAgent(...).`);
|
|
521
|
+
if (logic.mode !== "stream" && !executors.generateText) throw new Error(`runAgent: ${where} '${stateName}' invokes text source '${src}' but no 'generateText' executor was provided to runAgent(...).`);
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
if (require_text_logic.isUnboundPlaceholder(logic)) throw new Error(`runAgent: ${where} '${stateName}' invokes actor source '${src}', which has no host execution. Provide it via machine.provide({ actors: { '${src}': ... } }) or runAgent(machine, { actors: { '${src}': ... } }).`);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
/** Descends the bind-time walk into an invoked child state machine, guarding
|
|
528
|
+
* against a machine that (transitively) invokes itself. */
|
|
529
|
+
function assertChildMachineBindable(childMachine, childSrc, stateName, executors, ctx) {
|
|
530
|
+
if (ctx.visited.has(childMachine)) return;
|
|
531
|
+
const childName = typeof childSrc === "string" ? childSrc : childMachine.config.id ?? "(child machine)";
|
|
532
|
+
const childPath = ctx.childPath ? `${ctx.childPath} > ${childName}` : childName;
|
|
533
|
+
const childSources = childMachine.sources.actors;
|
|
534
|
+
assertMachineBindable(childMachine, childSources, executors, {
|
|
535
|
+
isChild: true,
|
|
536
|
+
childPath,
|
|
537
|
+
rebindable: ctx.rebindable && typeof childSrc === "string",
|
|
538
|
+
visited: new Set([...ctx.visited, childMachine])
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
/** The loud bind-time error for an unbound agent request reached under a
|
|
542
|
+
* direct-object invoke src, which runAgent cannot rebind (only string-keyed
|
|
543
|
+
* sources can be swapped via `.provide`). Names the invoke chain AND the
|
|
544
|
+
* request src, and spells out the `.withExecutor`/string-keyed remedy. Note:
|
|
545
|
+
* requests reachable through string-keyed srcs at any depth DO inherit
|
|
546
|
+
* runAgent's executors — this error is only for the unrebindable direct-object
|
|
547
|
+
* case. */
|
|
548
|
+
function unrebindableChildRequestError(childPath, stateName, requestSrc, kind) {
|
|
549
|
+
return /* @__PURE__ */ new Error(`runAgent: child machine '${childPath}' (state '${stateName}') invokes ${kind} source '${requestSrc}', which has no host execution and is reached through a direct-object invoke src that runAgent cannot rebind. Requests reached through string-keyed actor sources inherit runAgent's generateText/streamText/decide executors automatically; a direct-object child machine does not. Either bind the request with its own executor (requestLogic.withExecutor(...)), or register the child as a string-keyed actor source (machine.provide({ actors: { <child>: childMachine } })) and invoke it by name.`);
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* True when the snapshot's active states declare a transition that would
|
|
553
|
+
* receive the reserved `'@agent.usage'` type — an explicit `on: { '@agent.usage'
|
|
554
|
+
* }` OR a catch-all `on: { '*': … }`. Plain XState semantics apply unmodified:
|
|
555
|
+
* a wildcard matches every event delivered to the machine, reserved ones
|
|
556
|
+
* included. (The MODEL-facing side stays closed: `getAcceptedEvents` drops
|
|
557
|
+
* `@agent.*` before any `allowedEvents` matching, so a wildcard never offers
|
|
558
|
+
* the reserved event as a decision candidate.) @internal
|
|
559
|
+
*/
|
|
560
|
+
function declaresUsageTransition(snapshot) {
|
|
561
|
+
return (0, xstate.getNextTransitions)(snapshot).some((transition) => transition.eventType === "@agent.usage" || transition.eventType === "*");
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Builds the ONE place a trace payload is emitted: it hands the payload to the
|
|
565
|
+
* envelope-stamping trace sink and, from that same payload, invokes the sugar
|
|
566
|
+
* callbacks that are projections of it — {@link RunAgentOptions.onChunk},
|
|
567
|
+
* {@link RunAgentOptions.onResult}, {@link RunAgentOptions.onTransition}. Each
|
|
568
|
+
* keeps its historical position relative to the trace: `onResult` fires just
|
|
569
|
+
* BEFORE its `request.end`, `onChunk`/`onTransition` just AFTER their
|
|
570
|
+
* `stream.chunk`/`machine.transition`. Sugar dispatch never depends on whether
|
|
571
|
+
* a trace sink is present, and the trace sink is never called when it is
|
|
572
|
+
* absent — so an `onTrace`-less run still mints no envelope (and advances no
|
|
573
|
+
* `seq`). @internal
|
|
574
|
+
*/
|
|
575
|
+
function createTraceDispatch(sinks) {
|
|
576
|
+
return (payload, self) => {
|
|
577
|
+
switch (payload.type) {
|
|
578
|
+
case "stream.chunk":
|
|
579
|
+
sinks.onTrace?.(payload, self);
|
|
580
|
+
sinks.onChunk?.(payload.chunk, { request: payload.request });
|
|
581
|
+
return;
|
|
582
|
+
case "request.end":
|
|
583
|
+
sinks.onResult?.(payload.request, {
|
|
584
|
+
result: payload.output,
|
|
585
|
+
raw: payload.raw
|
|
586
|
+
});
|
|
587
|
+
sinks.onTrace?.(payload, self);
|
|
588
|
+
return;
|
|
589
|
+
case "machine.transition":
|
|
590
|
+
sinks.onTrace?.(payload, self);
|
|
591
|
+
sinks.onTransition?.(payload.snapshot, payload.event);
|
|
592
|
+
return;
|
|
593
|
+
default: sinks.onTrace?.(payload, self);
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
/** Reads the durable invoke id/src off the async actor's own ref (`self`). */
|
|
598
|
+
function selfIdAndSrc(self) {
|
|
599
|
+
return {
|
|
600
|
+
id: typeof self?.id === "string" ? self.id : "",
|
|
601
|
+
src: typeof self?.src === "string" ? self.src : ""
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* The machine actor that INVOKED a decision request — the actor whose
|
|
606
|
+
* live snapshot supplies the candidate events and drives `canTake`/`send`.
|
|
607
|
+
* For a top-level request this is the root actor (identity-equal to
|
|
608
|
+
* `runCtx.actorHolder.actorRef`); for a request inside an invoked child
|
|
609
|
+
* machine it is that child's actor, so a child decision reads and drives
|
|
610
|
+
* the CHILD's snapshot — not the root's. Read off `self._parent`, with the
|
|
611
|
+
* root actor as a fallback.
|
|
612
|
+
*/
|
|
613
|
+
function invokingActorOf(self, runCtx) {
|
|
614
|
+
return self?._parent ?? runCtx.actorHolder.actorRef;
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* The shared text/stream emission helper: binds a {@link TextLogic} to
|
|
618
|
+
* `runCtx`'s executor and constructs the `request.start` / `stream.chunk` /
|
|
619
|
+
* `request.end` (incl. the lifted `reasoning`) / `request.error` trace payloads.
|
|
620
|
+
* Used by both `runAgent` and `provideExecutors` so the two paths produce
|
|
621
|
+
* identical event shapes by construction. @internal
|
|
622
|
+
*/
|
|
623
|
+
function bindTextLogic(logic, runCtx) {
|
|
624
|
+
return logic.withExecutor(async ({ request: rawRequest, self: selfArg, signal }) => {
|
|
625
|
+
const self = selfArg;
|
|
626
|
+
const { id, src } = selfIdAndSrc(self);
|
|
627
|
+
const request = {
|
|
628
|
+
...rawRequest,
|
|
629
|
+
name: rawRequest.name ?? (src !== "" && src !== "agent.generateText" && src !== "agent.streamText" ? src : id || src)
|
|
630
|
+
};
|
|
631
|
+
const executor = logic.mode === "stream" ? runCtx.streamText : runCtx.generateText;
|
|
632
|
+
if (!executor) throw new Error(`No '${logic.mode === "stream" ? "streamText" : "generateText"}' executor provided.`);
|
|
633
|
+
const requestWithTools = {
|
|
634
|
+
...request,
|
|
635
|
+
tools: request.tools ?? {}
|
|
636
|
+
};
|
|
637
|
+
const agentRequest = {
|
|
638
|
+
kind: "text",
|
|
639
|
+
id,
|
|
640
|
+
src,
|
|
641
|
+
mode: logic.mode,
|
|
642
|
+
input: request,
|
|
643
|
+
tools: requestWithTools.tools,
|
|
644
|
+
events: []
|
|
645
|
+
};
|
|
646
|
+
runCtx.consumeModelCall();
|
|
647
|
+
runCtx.emitTrace?.({
|
|
648
|
+
type: "request.start",
|
|
649
|
+
request: agentRequest
|
|
650
|
+
}, self);
|
|
651
|
+
try {
|
|
652
|
+
const callKey = id !== "" ? runCtx.callKey?.(id, self) : void 0;
|
|
653
|
+
await runCtx.awaitJournal?.();
|
|
654
|
+
const raw = await executor(requestWithTools, {
|
|
655
|
+
onChunk: (chunk) => {
|
|
656
|
+
runCtx.emitTrace?.({
|
|
657
|
+
type: "stream.chunk",
|
|
658
|
+
request: agentRequest,
|
|
659
|
+
chunk
|
|
660
|
+
}, self);
|
|
661
|
+
},
|
|
662
|
+
signal,
|
|
663
|
+
...runCtx.runId !== void 0 ? { runId: runCtx.runId } : {},
|
|
664
|
+
...id !== "" ? { requestId: id } : {},
|
|
665
|
+
...callKey !== void 0 ? { callKey } : {}
|
|
666
|
+
});
|
|
667
|
+
const output = await require_text_logic.normalizeGeneratorResult(raw, id);
|
|
668
|
+
const rawReasoning = raw?.reasoning;
|
|
669
|
+
const reasoning = typeof rawReasoning === "string" ? rawReasoning : void 0;
|
|
670
|
+
const usage = require_text_logic.getCallUsage(raw);
|
|
671
|
+
const finishReason = require_text_logic.getCallFinishReason(raw);
|
|
672
|
+
if (usage) runCtx.recordUsage?.(usage, {
|
|
673
|
+
kind: "text",
|
|
674
|
+
...id !== "" ? { id } : {},
|
|
675
|
+
...src !== "" ? { src } : {},
|
|
676
|
+
model: request.model,
|
|
677
|
+
...request.name !== void 0 ? { name: request.name } : {}
|
|
678
|
+
}, self);
|
|
679
|
+
runCtx.emitTrace?.({
|
|
680
|
+
type: "request.end",
|
|
681
|
+
request: agentRequest,
|
|
682
|
+
output,
|
|
683
|
+
raw,
|
|
684
|
+
...reasoning !== void 0 ? { reasoning } : {},
|
|
685
|
+
...usage !== void 0 ? { usage } : {},
|
|
686
|
+
...finishReason !== void 0 ? { finishReason } : {}
|
|
687
|
+
}, self);
|
|
688
|
+
return {
|
|
689
|
+
result: output,
|
|
690
|
+
messages: require_text_logic.responseMessagesOf(raw)
|
|
691
|
+
};
|
|
692
|
+
} catch (error) {
|
|
693
|
+
runCtx.emitTrace?.({
|
|
694
|
+
type: "request.error",
|
|
695
|
+
request: agentRequest,
|
|
696
|
+
error
|
|
697
|
+
}, self);
|
|
698
|
+
throw error;
|
|
699
|
+
}
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
function createCountingDecide(runCtx, self) {
|
|
703
|
+
return async (attemptRequest, info) => {
|
|
704
|
+
runCtx.consumeModelCall();
|
|
705
|
+
runCtx.emitTrace?.({
|
|
706
|
+
type: "request.start",
|
|
707
|
+
request: attemptRequest
|
|
708
|
+
}, self);
|
|
709
|
+
try {
|
|
710
|
+
const { id } = selfIdAndSrc(self);
|
|
711
|
+
const invokeCallKey = id !== "" ? runCtx.callKey?.(id, self) : void 0;
|
|
712
|
+
const callKey = invokeCallKey === void 0 ? void 0 : `${invokeCallKey}.${attemptRequest.attempts?.length ?? 0}`;
|
|
713
|
+
await runCtx.awaitJournal?.();
|
|
714
|
+
const result = await runCtx.decide(runCtx.runId !== void 0 ? {
|
|
715
|
+
...attemptRequest,
|
|
716
|
+
runId: runCtx.runId
|
|
717
|
+
} : attemptRequest, {
|
|
718
|
+
...info,
|
|
719
|
+
...runCtx.runId !== void 0 ? { runId: runCtx.runId } : {},
|
|
720
|
+
...info?.requestId === void 0 && id !== "" ? { requestId: id } : {},
|
|
721
|
+
...info?.callKey === void 0 && callKey !== void 0 ? { callKey } : {}
|
|
722
|
+
});
|
|
723
|
+
const usage = require_text_logic.getCallUsage(result);
|
|
724
|
+
const finishReason = require_text_logic.getCallFinishReason(result);
|
|
725
|
+
if (usage) {
|
|
726
|
+
const { src } = selfIdAndSrc(self);
|
|
727
|
+
runCtx.recordUsage?.(usage, {
|
|
728
|
+
kind: "decision",
|
|
729
|
+
...attemptRequest.id ? { id: attemptRequest.id } : {},
|
|
730
|
+
...src !== "" ? { src } : {},
|
|
731
|
+
model: attemptRequest.model
|
|
732
|
+
}, self);
|
|
733
|
+
}
|
|
734
|
+
runCtx.emitTrace?.({
|
|
735
|
+
type: "request.end",
|
|
736
|
+
request: attemptRequest,
|
|
737
|
+
output: result.event,
|
|
738
|
+
raw: result,
|
|
739
|
+
...usage !== void 0 ? { usage } : {},
|
|
740
|
+
...finishReason !== void 0 ? { finishReason } : {}
|
|
741
|
+
}, self);
|
|
742
|
+
return result;
|
|
743
|
+
} catch (error) {
|
|
744
|
+
runCtx.emitTrace?.({
|
|
745
|
+
type: "request.error",
|
|
746
|
+
request: attemptRequest,
|
|
747
|
+
error
|
|
748
|
+
}, self);
|
|
749
|
+
throw error;
|
|
750
|
+
}
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Builds the decision actor logic runAgent installs in place of a
|
|
755
|
+
* `DecisionLogic`/`agent.decide` source. `DecisionLogic.withExecutor(...)`
|
|
756
|
+
* can only swap the innermost per-attempt executor — the `resolveDecision(...)`
|
|
757
|
+
* call (and its `canTake`) is hardwired inside the original logic's `run`.
|
|
758
|
+
* To supply `canTake` (mode-3, §2.6), runAgent instead builds a fresh async
|
|
759
|
+
* logic here that calls `resolveDecision` itself, reusing `logic.request(...)`
|
|
760
|
+
* to build the request the same way the original logic would have.
|
|
761
|
+
*
|
|
762
|
+
* On success it SENDS the chosen event to the invoking actor (auto-delivery)
|
|
763
|
+
* and then completes with that event
|
|
764
|
+
* as its output — so callers never wire an `onDone` to deliver it. See the
|
|
765
|
+
* send-then-complete note inside `run` for how exit-cancels-invoke interacts
|
|
766
|
+
* with `onDone`.
|
|
767
|
+
*/
|
|
768
|
+
function bindDecisionLogic(logic, runCtx) {
|
|
769
|
+
const decisionLogic = (0, xstate.createAsyncLogic)({ run: async ({ input, signal, self: selfArg }) => {
|
|
770
|
+
if (!runCtx.decide) throw new Error("No 'decide' executor provided.");
|
|
771
|
+
const self = selfArg;
|
|
772
|
+
const { id } = selfIdAndSrc(self);
|
|
773
|
+
const declaredEventTypes = logic.allowedEventTypes?.(input);
|
|
774
|
+
await Promise.resolve();
|
|
775
|
+
const actorRef = invokingActorOf(self, runCtx);
|
|
776
|
+
const events = actorRef ? require_decision.getAcceptedEvents(actorRef.getSnapshot(), {
|
|
777
|
+
schemas: runCtx.schemas,
|
|
778
|
+
eventTypes: declaredEventTypes
|
|
779
|
+
}) : [];
|
|
780
|
+
const lowered = logic.request(input);
|
|
781
|
+
const chosen = await require_decision.resolveDecision({
|
|
782
|
+
...lowered,
|
|
783
|
+
id,
|
|
784
|
+
name: lowered.name ?? id,
|
|
785
|
+
input,
|
|
786
|
+
events
|
|
787
|
+
}, { decide: createCountingDecide(runCtx, self) }, {
|
|
788
|
+
maxRetries: logic.maxRetries,
|
|
789
|
+
signal,
|
|
790
|
+
canTake: (event) => actorRef ? actorRef.getSnapshot().can(event) : true
|
|
791
|
+
});
|
|
792
|
+
actorRef?.send(chosen);
|
|
793
|
+
await Promise.resolve();
|
|
794
|
+
return chosen;
|
|
795
|
+
} });
|
|
796
|
+
return Object.assign(decisionLogic, {
|
|
797
|
+
kind: "statelyai.decisionLogic",
|
|
798
|
+
maxRetries: logic.maxRetries,
|
|
799
|
+
request: logic.request,
|
|
800
|
+
withExecutor: (nextExecute) => bindDecisionLogic(logic.withExecutor(nextExecute), runCtx)
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* The set of string-keyed actor `src`s the machine's own config invokes
|
|
805
|
+
* (top-level, recursing into child STATES but not into invoked child
|
|
806
|
+
* machines). {@link provideExecutors} uses it to require an executor only for a
|
|
807
|
+
* source the machine actually invokes — the always-registered `agent.*`
|
|
808
|
+
* builtins that go unused must not force their executors to be supplied.
|
|
809
|
+
* @internal
|
|
810
|
+
*/
|
|
811
|
+
function getConfiguredInvokeSrcs(machine) {
|
|
812
|
+
const invokes = [];
|
|
813
|
+
collectConfiguredInvokeSrcs(machine.config, machine.config.id ?? "(root)", invokes);
|
|
814
|
+
const srcs = /* @__PURE__ */ new Set();
|
|
815
|
+
for (const { src } of invokes) if (typeof src === "string") srcs.add(src);
|
|
816
|
+
return srcs;
|
|
817
|
+
}
|
|
818
|
+
const rootTraceRegistry = /* @__PURE__ */ new WeakMap();
|
|
819
|
+
let nextProvideRunId = 1;
|
|
820
|
+
/** Walks `self._parent` from an invoked async leaf actor up to its root actor. */
|
|
821
|
+
function rootActorOf(self) {
|
|
822
|
+
let ref = self;
|
|
823
|
+
while (ref?._parent) ref = ref._parent;
|
|
824
|
+
return ref;
|
|
825
|
+
}
|
|
826
|
+
/** The per-root envelope state, minted on first use (runId `run_<n>`, matching runAgent). */
|
|
827
|
+
function rootTraceState(root) {
|
|
828
|
+
let state = rootTraceRegistry.get(root);
|
|
829
|
+
if (!state) {
|
|
830
|
+
const logic = root.logic;
|
|
831
|
+
const machineId = (logic?.config)?.id ?? logic?.id ?? "(machine)";
|
|
832
|
+
const machineVersion = logic ? require_text_logic.resolveMachineVersion(logic) : "";
|
|
833
|
+
state = {
|
|
834
|
+
runId: `run_${nextProvideRunId++}`,
|
|
835
|
+
seq: 0,
|
|
836
|
+
machineId,
|
|
837
|
+
machineVersion
|
|
838
|
+
};
|
|
839
|
+
rootTraceRegistry.set(root, state);
|
|
840
|
+
}
|
|
841
|
+
return state;
|
|
842
|
+
}
|
|
843
|
+
/** Stamps a per-root-actor envelope onto a bare trace payload. */
|
|
844
|
+
function stampRootTrace(root, payload) {
|
|
845
|
+
const state = rootTraceState(root);
|
|
846
|
+
return {
|
|
847
|
+
schemaVersion: 1,
|
|
848
|
+
runId: state.runId,
|
|
849
|
+
seq: ++state.seq,
|
|
850
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
851
|
+
machineId: state.machineId,
|
|
852
|
+
machineVersion: state.machineVersion,
|
|
853
|
+
...payload
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
/** Adapts a public `onTrace` into the payload-level trace sink a {@link TraceDispatch} fans out to. */
|
|
857
|
+
function provideTraceSink(onTrace) {
|
|
858
|
+
if (!onTrace) return;
|
|
859
|
+
return (payload, self) => {
|
|
860
|
+
const root = rootActorOf(self);
|
|
861
|
+
if (root) onTrace(stampRootTrace(root, payload));
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* A minimal {@link RunAgentBindContext} for `provideExecutors` (uncontrolled
|
|
866
|
+
* `createActor`): the same wrappers runAgent installs, MINUS the run-scoped
|
|
867
|
+
* model-call counter. `consumeModelCall` is a no-op (no budget), and
|
|
868
|
+
* `actorHolder.actorRef` is left undefined — the wrappers read the invoking
|
|
869
|
+
* actor off `self._parent`, always present under a live `createActor` tree.
|
|
870
|
+
* `onTrace` (when given) mints a per-root-actor envelope. `schemas` come from
|
|
871
|
+
* the machine's registered `setupAgent` execution options.
|
|
872
|
+
*
|
|
873
|
+
* `recordUsage` has no run-level aggregate to fold into here (there is no
|
|
874
|
+
* run), so it does one thing: deliver the reserved `@agent.usage` event, gated
|
|
875
|
+
* exactly like runAgent's — see {@link deliverUsageEvent}.
|
|
876
|
+
*/
|
|
877
|
+
function provideBindContext(machine, executors, options) {
|
|
878
|
+
const traceSink = provideTraceSink(options.onTrace);
|
|
879
|
+
const onChunk = options.onChunk;
|
|
880
|
+
return {
|
|
881
|
+
generateText: executors.generateText,
|
|
882
|
+
streamText: executors.streamText,
|
|
883
|
+
decide: executors.decide,
|
|
884
|
+
emitTrace: traceSink || onChunk ? createTraceDispatch({
|
|
885
|
+
onTrace: traceSink,
|
|
886
|
+
onChunk: onChunk ? (chunk) => onChunk(chunk) : void 0
|
|
887
|
+
}) : void 0,
|
|
888
|
+
consumeModelCall: () => {},
|
|
889
|
+
recordUsage: (usage, source, self) => {
|
|
890
|
+
deliverUsageEvent({
|
|
891
|
+
type: require_usage.AGENT_USAGE_EVENT_TYPE,
|
|
892
|
+
...source,
|
|
893
|
+
usage
|
|
894
|
+
}, () => self?._parent);
|
|
895
|
+
},
|
|
896
|
+
actorHolder: { actorRef: void 0 },
|
|
897
|
+
schemas: require_text_logic.getRegisteredAgentExecutionOptions(machine).schemas
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* The single reserved-`@agent.usage` DELIVERY seam, shared by both bind paths:
|
|
902
|
+
* after a bound call settles with reported usage, send the prebuilt `event` to
|
|
903
|
+
* the machine actor `resolveActorRef` names — the run's root actor on the `runAgent` path,
|
|
904
|
+
* the settling request actor's `self._parent` (always the invoking machine
|
|
905
|
+
* under a live `createActor` tree) on the `provideExecutors` path.
|
|
906
|
+
*
|
|
907
|
+
* Journaling is separate and unconditional (see `recordUsage`): a call's tokens
|
|
908
|
+
* are recorded in the event log whether or not the machine reacts to them.
|
|
909
|
+
*
|
|
910
|
+
* Gating is identical on both: the target snapshot must be active, must declare
|
|
911
|
+
* an `'@agent.usage'` transition — explicitly, or through a catch-all
|
|
912
|
+
* `on: { '*' }` (see {@link declaresUsageTransition}) — and must be able to
|
|
913
|
+
* take the event. `onDropped` is the run path's straggler gate: it returns `true` for a
|
|
914
|
+
* call that settled after the cycle resolved, which drops the event (traced as
|
|
915
|
+
* `usage.dropped`) rather than delivering it. Uncontrolled mode has no cycle to
|
|
916
|
+
* settle, so it passes no gate and has no dropped stragglers.
|
|
917
|
+
*
|
|
918
|
+
* Delivery follows each path's binding boundary: only sources IT bound report
|
|
919
|
+
* here, so an invoked child machine that was not itself passed through
|
|
920
|
+
* `provideExecutors` reports nothing. @internal
|
|
921
|
+
*/
|
|
922
|
+
function deliverUsageEvent(event, resolveActorRef, onDropped) {
|
|
923
|
+
const actorRef = resolveActorRef();
|
|
924
|
+
if (!actorRef) return;
|
|
925
|
+
const snapshot = actorRef.getSnapshot();
|
|
926
|
+
if (snapshot?.status !== "active" || !declaresUsageTransition(snapshot)) return;
|
|
927
|
+
if (onDropped?.(event)) return;
|
|
928
|
+
if (!snapshot.can(event)) return;
|
|
929
|
+
actorRef.send(event);
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* Host-binds one text/stream source for {@link provideExecutors} using the SAME
|
|
933
|
+
* emission helper as `runAgent` ({@link bindTextLogic}), so a bound
|
|
934
|
+
* text request emits request.start/stream.chunk/request.end/request.error with
|
|
935
|
+
* identical shapes. @internal
|
|
936
|
+
*/
|
|
937
|
+
function bindTextForProvide(machine, logic, executors, options) {
|
|
938
|
+
return bindTextLogic(logic, provideBindContext(machine, executors, options));
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Host-binds one `DecisionLogic`/`agent.decide` source for
|
|
942
|
+
* {@link provideExecutors}: runAgent's decision wrapper (snapshot-driven
|
|
943
|
+
* candidate events, `canTake`, auto-delivery of the chosen event) with the same
|
|
944
|
+
* request-level tracing runAgent emits, minus run-scoped counting. @internal
|
|
945
|
+
*/
|
|
946
|
+
function bindDecisionForProvide(machine, logic, executors, options) {
|
|
947
|
+
return bindDecisionLogic(logic, provideBindContext(machine, executors, options));
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* Recursively binds an invoked child state machine for {@link provideExecutors},
|
|
951
|
+
* with the same semantics `runAgent` applies ({@link rebindChildMachine}):
|
|
952
|
+
* string-keyed text/decision sources at any depth inherit the host executors,
|
|
953
|
+
* a source that carries its own executor is left alone, and a cycle is
|
|
954
|
+
* returned as-is. Each machine in the tree is bound with its own registered
|
|
955
|
+
* `setupAgent` schemas. Returns the original machine when nothing needed
|
|
956
|
+
* wrapping. @internal
|
|
957
|
+
*/
|
|
958
|
+
function bindChildMachineForProvide(childMachine, executors, options, visited) {
|
|
959
|
+
const ctxFor = (target) => provideBindContext(target, executors, options);
|
|
960
|
+
return rebindChildMachine(childMachine, ctxFor(childMachine), visited, ctxFor);
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
963
|
+
* Validates `input` against the machine's registered input schema, returning
|
|
964
|
+
* the schema's output — so defaults are filled and transforms applied before
|
|
965
|
+
* the value reaches `createActor` or the replayable event log.
|
|
966
|
+
*
|
|
967
|
+
* Standard Schema only (no validation library is referenced), so this works for
|
|
968
|
+
* whatever the machine was declared with. Omitted input stays omitted rather
|
|
969
|
+
* than being validated as `{}`: "started with no input" keeps meaning what it
|
|
970
|
+
* has always meant, instead of newly failing schemas with required fields.
|
|
971
|
+
*/
|
|
972
|
+
function resolveMachineInput(machine, input) {
|
|
973
|
+
if (input === void 0) return input;
|
|
974
|
+
const schema = require_text_logic.getRegisteredAgentExecutionOptions(machine).schemas?.input;
|
|
975
|
+
if (!require_text_logic.isStandardSchema(schema)) return input;
|
|
976
|
+
try {
|
|
977
|
+
return require_text_logic.validateSchemaSync(schema, input);
|
|
978
|
+
} catch (error) {
|
|
979
|
+
throw new require_text_logic.AgentError("invalid-machine-input", `runAgent: machine input failed validation against the declared input schema: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* Recursively rebinds an invoked child machine's own agent sources with the
|
|
984
|
+
* SAME host-backed wrappers runAgent applies to the top-level machine, so a
|
|
985
|
+
* child's text/stream/decision requests inherit runAgent's executors and
|
|
986
|
+
* participate in maxModelCalls counting, onTrace/onChunk/onResult exactly like
|
|
987
|
+
* parent requests. Returns the child machine to invoke: a `.provide`-rebound
|
|
988
|
+
* copy when any inner source needed wrapping, else the original untouched.
|
|
989
|
+
*
|
|
990
|
+
* Only string-keyed sources are visited — a direct-object invoke src cannot be
|
|
991
|
+
* swapped via `.provide` (assertBindable already rejected an unbound request
|
|
992
|
+
* under one). A source that already carries its own executor
|
|
993
|
+
* (`executorBoundLogics`) is left as-is: explicit binding shadows inheritance.
|
|
994
|
+
* Cycle-safe via `visited` (a machine that invokes itself is returned as-is).
|
|
995
|
+
*/
|
|
996
|
+
function rebindChildMachine(childMachine, runCtx, visited, ctxFor) {
|
|
997
|
+
if (visited.has(childMachine)) return childMachine;
|
|
998
|
+
const childVisited = new Set([...visited, childMachine]);
|
|
999
|
+
runCtx = ctxFor ? ctxFor(childMachine) : runCtx;
|
|
1000
|
+
const sources = childMachine.sources.actors;
|
|
1001
|
+
const wrapped = {};
|
|
1002
|
+
for (const [key, logic] of Object.entries(sources)) {
|
|
1003
|
+
if (require_decision.isDecisionLogic(logic)) {
|
|
1004
|
+
if (!require_text_logic.executorBoundLogics.has(logic)) wrapped[key] = bindDecisionLogic(logic, runCtx);
|
|
1005
|
+
continue;
|
|
1006
|
+
}
|
|
1007
|
+
if (require_text_logic.isTextLogic(logic)) {
|
|
1008
|
+
if (!require_text_logic.executorBoundLogics.has(logic)) wrapped[key] = bindTextLogic(logic, runCtx);
|
|
1009
|
+
continue;
|
|
1010
|
+
}
|
|
1011
|
+
if (isStateMachineLogic(logic)) {
|
|
1012
|
+
const rebound = rebindChildMachine(logic, runCtx, childVisited, ctxFor);
|
|
1013
|
+
if (rebound !== logic) wrapped[key] = rebound;
|
|
1014
|
+
continue;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
return Object.keys(wrapped).length > 0 ? childMachine.provide({ actors: wrapped }) : childMachine;
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Runs an agent machine to completion or idle: a `createActor` host that
|
|
1021
|
+
* binds `options`' host executors onto the machine's `agent.*`/`TextLogic`/
|
|
1022
|
+
* `DecisionLogic` actor sources, starts (or resumes) the actor, and drives
|
|
1023
|
+
* it until it settles — {@link RunAgentResult} `done | idle | error`. Unlike
|
|
1024
|
+
* the step helpers ({@link initialAgentStep} etc — a pure
|
|
1025
|
+
* transition-at-a-time path for durable hosts), `runAgent` owns a live actor
|
|
1026
|
+
* internally; there is no continuation callback, so **idle always settles**
|
|
1027
|
+
* and the caller resumes explicitly by passing the settled `{ snapshot,
|
|
1028
|
+
* event }` back in. The actor is stopped on every settle path (`done`,
|
|
1029
|
+
* `idle`, and `error` alike) — resume is always by snapshot, never by
|
|
1030
|
+
* holding a reference to a live actor.
|
|
1031
|
+
*
|
|
1032
|
+
* Binding happens **before** the actor starts: every invoke the machine
|
|
1033
|
+
* could reach is walked and checked against the effective actor sources
|
|
1034
|
+
* (`options.actors` merged onto the machine), so a missing
|
|
1035
|
+
* `streamText`/`decide` executor or any other unbound actor source throws
|
|
1036
|
+
* immediately — a bind-time error, not a mid-run failure.
|
|
1037
|
+
*
|
|
1038
|
+
* @example
|
|
1039
|
+
* ```ts
|
|
1040
|
+
* const executors = createAiSdkExecutors({ models });
|
|
1041
|
+
* let r = await runAgent(machine, { input, executors });
|
|
1042
|
+
* while (r.status === 'idle') {
|
|
1043
|
+
* const event = await promptUser(getAcceptedEvents(r.snapshot));
|
|
1044
|
+
* r = await runAgent(machine, { snapshot: r.snapshot, event, executors });
|
|
1045
|
+
* }
|
|
1046
|
+
* if (r.status !== 'done') throw new Error(`Run did not complete: ${r.status}`);
|
|
1047
|
+
* console.log(r.output);
|
|
1048
|
+
* ```
|
|
1049
|
+
*
|
|
1050
|
+
* Each executor is a plain function returning `{ result }` (plus optional `messages`/`usage`), or
|
|
1051
|
+
* an adapter's set: `createAiSdkExecutors` from '@statelyai/agent/ai-sdk' or
|
|
1052
|
+
* `createOpenAiExecutors` from '@statelyai/agent/openai' supply all three.
|
|
1053
|
+
*/
|
|
1054
|
+
async function runAgent(machine, options) {
|
|
1055
|
+
const store = options.store;
|
|
1056
|
+
if (store === void 0) return createAgentSession(machine, options).settled();
|
|
1057
|
+
const threadId = options.threadId;
|
|
1058
|
+
if (threadId === void 0 || threadId === "") throw new require_text_logic.AgentError("missing-thread-id", "runAgent: `threadId` is required when `store` is given — it names the log thread to read and append to.");
|
|
1059
|
+
if (options.events !== void 0) {
|
|
1060
|
+
const storedThread = await store.read(threadId);
|
|
1061
|
+
if (storedThread.length !== options.events.length) throw new require_event_log_store.AgentEventLogConflictError(threadId, options.events.length, storedThread.length);
|
|
1062
|
+
assertThreadMatchesEvents(threadId, storedThread, options.events);
|
|
1063
|
+
return createAgentSession(machine, options).settled();
|
|
1064
|
+
}
|
|
1065
|
+
const stored = await store.read(threadId);
|
|
1066
|
+
return createAgentSession(machine, stored.length > 0 ? {
|
|
1067
|
+
...options,
|
|
1068
|
+
events: stored
|
|
1069
|
+
} : options).settled();
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Entry-by-entry check that the caller's `events` IS the store's thread. Equal
|
|
1073
|
+
* lengths prove nothing: a fork, a rolled-back replay, or a concurrent writer
|
|
1074
|
+
* that appended and truncated all produce a divergent log of the same size,
|
|
1075
|
+
* and appending this run's entries onto it would splice two lineages together.
|
|
1076
|
+
*/
|
|
1077
|
+
function assertThreadMatchesEvents(threadId, stored, events) {
|
|
1078
|
+
for (let index = 0; index < stored.length; index++) {
|
|
1079
|
+
const storedEntry = stored[index];
|
|
1080
|
+
const givenEntry = events[index];
|
|
1081
|
+
const storedHash = storedEntry.verification?.stateHash;
|
|
1082
|
+
const givenHash = givenEntry.verification?.stateHash;
|
|
1083
|
+
if (!(storedEntry.id === givenEntry.id && storedEntry.index === givenEntry.index && require_event_log_store.canonicalEventJson(storedEntry.event) === require_event_log_store.canonicalEventJson(givenEntry.event) && (storedHash === void 0 || givenHash === void 0 || storedHash === givenHash))) throw new require_text_logic.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.`);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
function createAgentSession(machine, options) {
|
|
1087
|
+
const maxModelCalls = options.maxModelCalls ?? 100;
|
|
1088
|
+
let modelCallCount = 0;
|
|
1089
|
+
let budgetExceeded = false;
|
|
1090
|
+
let warnedNonSerializable = false;
|
|
1091
|
+
const runId = `run_${nextRunAgentTraceId++}`;
|
|
1092
|
+
let traceSeq = 0;
|
|
1093
|
+
const resolvedInput = resolveMachineInput(machine, options.input);
|
|
1094
|
+
const machineId = machine.config.id ?? machine.id ?? "(machine)";
|
|
1095
|
+
const machineVersion = require_text_logic.resolveMachineVersion(machine);
|
|
1096
|
+
const onTrace = createTraceDispatch({
|
|
1097
|
+
onTrace: (payload) => {
|
|
1098
|
+
options.onTrace?.({
|
|
1099
|
+
schemaVersion: 1,
|
|
1100
|
+
runId,
|
|
1101
|
+
seq: ++traceSeq,
|
|
1102
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1103
|
+
machineId,
|
|
1104
|
+
machineVersion,
|
|
1105
|
+
...payload
|
|
1106
|
+
});
|
|
1107
|
+
},
|
|
1108
|
+
onChunk: options.onChunk,
|
|
1109
|
+
onResult: options.onResult,
|
|
1110
|
+
onTransition: options.onTransition
|
|
1111
|
+
});
|
|
1112
|
+
const consumeModelCall = () => {
|
|
1113
|
+
if (budgetExceeded) throw new AgentMaxModelCallsExceededError(maxModelCalls);
|
|
1114
|
+
if (modelCallCount + 1 > maxModelCalls) {
|
|
1115
|
+
budgetExceeded = true;
|
|
1116
|
+
throw new AgentMaxModelCallsExceededError(maxModelCalls);
|
|
1117
|
+
}
|
|
1118
|
+
modelCallCount += 1;
|
|
1119
|
+
};
|
|
1120
|
+
const cycleGate = { isResolved: () => false };
|
|
1121
|
+
const logEntries = [];
|
|
1122
|
+
const store = options.store;
|
|
1123
|
+
const threadId = options.threadId;
|
|
1124
|
+
if (store !== void 0 && (threadId === void 0 || threadId === "")) throw new require_text_logic.AgentError("missing-thread-id", "runAgent: `threadId` is required when `store` is given — it names the log thread to read and append to.");
|
|
1125
|
+
let journalChain = Promise.resolve();
|
|
1126
|
+
let journalPending = 0;
|
|
1127
|
+
let journalError;
|
|
1128
|
+
const journalEntry = (entry) => {
|
|
1129
|
+
if (store === void 0) return;
|
|
1130
|
+
journalPending++;
|
|
1131
|
+
journalChain = journalChain.then(async () => {
|
|
1132
|
+
if (journalError !== void 0) return;
|
|
1133
|
+
await store.append({
|
|
1134
|
+
threadId,
|
|
1135
|
+
expectedIndex: entry.index,
|
|
1136
|
+
entries: [entry]
|
|
1137
|
+
});
|
|
1138
|
+
}).catch((error) => {
|
|
1139
|
+
journalError ??= error;
|
|
1140
|
+
failRunOnJournalError(error);
|
|
1141
|
+
}).finally(() => {
|
|
1142
|
+
journalPending--;
|
|
1143
|
+
});
|
|
1144
|
+
};
|
|
1145
|
+
/**
|
|
1146
|
+
* Every journal write ISSUED SO FAR, including the stragglers appended after
|
|
1147
|
+
* the run settled. Re-reads the chain tail until it stops moving, so a write
|
|
1148
|
+
* queued while an earlier one was in flight is covered too. Never rejects: a
|
|
1149
|
+
* failed write already settled the run with `cause: 'journal'`.
|
|
1150
|
+
*/
|
|
1151
|
+
const drainJournal = async () => {
|
|
1152
|
+
if (store === void 0) return;
|
|
1153
|
+
let tail = journalChain;
|
|
1154
|
+
for (;;) {
|
|
1155
|
+
await tail;
|
|
1156
|
+
if (journalPending === 0 && journalChain === tail) return;
|
|
1157
|
+
tail = journalChain;
|
|
1158
|
+
}
|
|
1159
|
+
};
|
|
1160
|
+
/** The barrier: settle the pending chain, then surface any failure. */
|
|
1161
|
+
const awaitJournal = async () => {
|
|
1162
|
+
if (store === void 0) return;
|
|
1163
|
+
while (journalPending > 0) await journalChain;
|
|
1164
|
+
if (journalError !== void 0) throw journalError;
|
|
1165
|
+
};
|
|
1166
|
+
const failRunOnJournalError = (error) => {
|
|
1167
|
+
if (settled) return;
|
|
1168
|
+
settle({
|
|
1169
|
+
status: "error",
|
|
1170
|
+
cause: "journal",
|
|
1171
|
+
error,
|
|
1172
|
+
snapshot: actor.getSnapshot()
|
|
1173
|
+
});
|
|
1174
|
+
};
|
|
1175
|
+
let resumedLogLength = 0;
|
|
1176
|
+
let logExecutionId;
|
|
1177
|
+
const verificationEnabled = options.verification !== false;
|
|
1178
|
+
let lastRootSnapshot;
|
|
1179
|
+
let logMachine = machine;
|
|
1180
|
+
let loggingEnabled = true;
|
|
1181
|
+
const appendLogEntry = (event, snapshot) => {
|
|
1182
|
+
if (!loggingEnabled) return;
|
|
1183
|
+
const entry = require_event_log_store.createReplayEntry(logMachine, logEntries, event, {
|
|
1184
|
+
machineVersion,
|
|
1185
|
+
verification: verificationEnabled,
|
|
1186
|
+
...verificationEnabled && snapshot !== void 0 ? { snapshot } : {}
|
|
1187
|
+
});
|
|
1188
|
+
logEntries.push(entry);
|
|
1189
|
+
journalEntry(entry);
|
|
1190
|
+
options.onEvent?.(entry);
|
|
1191
|
+
return entry;
|
|
1192
|
+
};
|
|
1193
|
+
const usageEntrySnapshot = () => {
|
|
1194
|
+
const current = lastRootSnapshot;
|
|
1195
|
+
if (current && (current.status !== "active" || !declaresUsageTransition(current))) return current;
|
|
1196
|
+
};
|
|
1197
|
+
const recordUsage = (usage, source = {}) => {
|
|
1198
|
+
const event = {
|
|
1199
|
+
type: require_usage.AGENT_USAGE_EVENT_TYPE,
|
|
1200
|
+
...source,
|
|
1201
|
+
usage
|
|
1202
|
+
};
|
|
1203
|
+
const journal = { event };
|
|
1204
|
+
pendingUsageJournal = journal;
|
|
1205
|
+
try {
|
|
1206
|
+
deliverUsageEvent(event, () => actorHolder.actorRef, (dropped) => {
|
|
1207
|
+
if (!cycleGate.isResolved()) return false;
|
|
1208
|
+
onTrace({
|
|
1209
|
+
type: "usage.dropped",
|
|
1210
|
+
event: dropped,
|
|
1211
|
+
reason: "settled"
|
|
1212
|
+
});
|
|
1213
|
+
return true;
|
|
1214
|
+
});
|
|
1215
|
+
} finally {
|
|
1216
|
+
pendingUsageJournal = void 0;
|
|
1217
|
+
}
|
|
1218
|
+
if (journal.entry === void 0) appendLogEntry(event, usageEntrySnapshot());
|
|
1219
|
+
};
|
|
1220
|
+
let pendingUsageJournal;
|
|
1221
|
+
const runUsage = () => ({
|
|
1222
|
+
...require_event_log_store.getUsageFromEvents(logEntries.slice(resumedLogLength)),
|
|
1223
|
+
modelCalls: modelCallCount
|
|
1224
|
+
});
|
|
1225
|
+
const warnNonSerializableContext = (snapshot) => {
|
|
1226
|
+
if (warnedNonSerializable || process.env.NODE_ENV === "production") return;
|
|
1227
|
+
let offending = [];
|
|
1228
|
+
try {
|
|
1229
|
+
offending = require_text_logic.findNonSerializableContextPaths(snapshot.context);
|
|
1230
|
+
} catch {
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
if (offending.length === 0) return;
|
|
1234
|
+
warnedNonSerializable = true;
|
|
1235
|
+
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.`);
|
|
1236
|
+
};
|
|
1237
|
+
const provided = machine.provide({ actors: options.actors });
|
|
1238
|
+
logMachine = provided;
|
|
1239
|
+
const effectiveSources = provided.sources.actors;
|
|
1240
|
+
const executors = { ...options.executors };
|
|
1241
|
+
assertBindable(provided, effectiveSources, executors);
|
|
1242
|
+
const actorHolder = { actorRef: void 0 };
|
|
1243
|
+
const runCtx = {
|
|
1244
|
+
generateText: executors.generateText,
|
|
1245
|
+
streamText: executors.streamText,
|
|
1246
|
+
decide: executors.decide,
|
|
1247
|
+
emitTrace: onTrace,
|
|
1248
|
+
consumeModelCall,
|
|
1249
|
+
recordUsage,
|
|
1250
|
+
actorHolder,
|
|
1251
|
+
awaitJournal,
|
|
1252
|
+
runId,
|
|
1253
|
+
schemas: require_text_logic.getRegisteredAgentExecutionOptions(machine).schemas
|
|
1254
|
+
};
|
|
1255
|
+
const wrappedSources = {};
|
|
1256
|
+
for (const [key, logic] of Object.entries(effectiveSources)) {
|
|
1257
|
+
if (require_decision.isDecisionLogic(logic)) {
|
|
1258
|
+
wrappedSources[key] = bindDecisionLogic(logic, runCtx);
|
|
1259
|
+
continue;
|
|
1260
|
+
}
|
|
1261
|
+
if (require_text_logic.isTextLogic(logic)) {
|
|
1262
|
+
if (!require_text_logic.executorBoundLogics.has(logic)) wrappedSources[key] = bindTextLogic(logic, runCtx);
|
|
1263
|
+
continue;
|
|
1264
|
+
}
|
|
1265
|
+
if (isStateMachineLogic(logic)) {
|
|
1266
|
+
const rebound = rebindChildMachine(logic, runCtx, new Set([machine]));
|
|
1267
|
+
if (rebound !== logic) wrappedSources[key] = rebound;
|
|
1268
|
+
continue;
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
const boundMachine = provided.provide({ actors: wrappedSources });
|
|
1272
|
+
const isIdle = require_text_logic.getMachineIdlePredicate(machine) ?? isAgentIdle;
|
|
1273
|
+
const isIntentionalIdle = (snapshot) => isIdle(snapshot);
|
|
1274
|
+
const resumeEvents = options.events;
|
|
1275
|
+
let effectiveSnapshot = options.snapshot;
|
|
1276
|
+
/** Deferred until after the illegal-resume check, so a rejected resume emits no entries. */
|
|
1277
|
+
let seedInitEntry;
|
|
1278
|
+
const restoredPersistedSnapshot = () => {
|
|
1279
|
+
try {
|
|
1280
|
+
const restored = logMachine.restoreSnapshot(options.snapshot);
|
|
1281
|
+
return logMachine.getPersistedSnapshot(restored);
|
|
1282
|
+
} catch {
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
};
|
|
1286
|
+
/**
|
|
1287
|
+
* Appends the log's reserved first entry, trying each candidate `init` in
|
|
1288
|
+
* turn and, for each, an unverified entry if the hash cannot be computed.
|
|
1289
|
+
* A machine or snapshot that defeats every attempt leaves the run WITHOUT a
|
|
1290
|
+
* log rather than failing it: `runAgent` predates the log, and an exotic
|
|
1291
|
+
* machine that cannot be folded must still run.
|
|
1292
|
+
*/
|
|
1293
|
+
const appendInitEntry = (candidates, metadata) => {
|
|
1294
|
+
for (const init of candidates) for (const verification of verificationEnabled ? [true, false] : [false]) try {
|
|
1295
|
+
const entry = require_event_log_store.initEntry(logMachine, init, {
|
|
1296
|
+
machineVersion,
|
|
1297
|
+
verification,
|
|
1298
|
+
metadata
|
|
1299
|
+
});
|
|
1300
|
+
logEntries.push(entry);
|
|
1301
|
+
journalEntry(entry);
|
|
1302
|
+
options.onEvent?.(entry);
|
|
1303
|
+
return;
|
|
1304
|
+
} catch {}
|
|
1305
|
+
loggingEnabled = false;
|
|
1306
|
+
};
|
|
1307
|
+
/** Init forms for a snapshot resume, most faithful first. */
|
|
1308
|
+
const snapshotInitCandidates = () => {
|
|
1309
|
+
const restored = restoredPersistedSnapshot();
|
|
1310
|
+
return [...restored !== void 0 ? [{ snapshot: restored }] : [], { snapshot: options.snapshot }];
|
|
1311
|
+
};
|
|
1312
|
+
if (resumeEvents !== void 0 && resumeEvents.length > 0) {
|
|
1313
|
+
require_event_log_store.validateReplayEntries(resumeEvents);
|
|
1314
|
+
const tail = resumeEvents[resumeEvents.length - 1];
|
|
1315
|
+
const inheritedExecutionId = require_event_log_store.getLogExecutionId(resumeEvents);
|
|
1316
|
+
if (resumeEvents[0].machineId !== machineId) throw new require_event_log_store.AgentMachineVersionMismatchError(tail.id, tail.index, {
|
|
1317
|
+
machineId,
|
|
1318
|
+
machineVersion
|
|
1319
|
+
}, {
|
|
1320
|
+
machineId: resumeEvents[0].machineId,
|
|
1321
|
+
machineVersion: tail.machineVersion
|
|
1322
|
+
});
|
|
1323
|
+
if (tail.machineVersion === machineVersion) {
|
|
1324
|
+
logEntries.push(...resumeEvents);
|
|
1325
|
+
resumedLogLength = logEntries.length;
|
|
1326
|
+
logExecutionId = inheritedExecutionId;
|
|
1327
|
+
const cachedMeta = readAgentMeta(options.snapshot);
|
|
1328
|
+
const tailStateHash = tail.verification?.stateHash;
|
|
1329
|
+
if (!(options.snapshot !== void 0 && inheritedExecutionId !== void 0 && cachedMeta?.logId === inheritedExecutionId && cachedMeta.logIndex === resumeEvents.length && tailStateHash !== void 0 && hashResumeSnapshot(options.snapshot) === tailStateHash)) {
|
|
1330
|
+
const cachedIndex = cachedMeta?.logIndex;
|
|
1331
|
+
if (options.snapshot !== void 0 && typeof cachedIndex === "number" && cachedIndex >= 1 && cachedIndex <= resumeEvents.length && resumeEvents[cachedIndex - 1].verification?.stateHash !== void 0) {
|
|
1332
|
+
const expected = require_event_log_store.getSnapshotStateHash(require_event_log_store.replay(logMachine, resumeEvents.slice(0, cachedIndex), {
|
|
1333
|
+
machineVersion,
|
|
1334
|
+
verify: false
|
|
1335
|
+
}).persistedSnapshot);
|
|
1336
|
+
const actual = hashResumeSnapshot(options.snapshot);
|
|
1337
|
+
if (expected !== actual) throw new AgentSnapshotDivergedError(expected, actual, cachedIndex);
|
|
1338
|
+
}
|
|
1339
|
+
effectiveSnapshot = require_event_log_store.replay(logMachine, resumeEvents, {
|
|
1340
|
+
machineVersion,
|
|
1341
|
+
verify: true
|
|
1342
|
+
}).persistedSnapshot;
|
|
1343
|
+
}
|
|
1344
|
+
} else {
|
|
1345
|
+
if (options.snapshot === void 0) throw new require_event_log_store.AgentMachineVersionMismatchError(tail.id, tail.index, {
|
|
1346
|
+
machineId,
|
|
1347
|
+
machineVersion
|
|
1348
|
+
}, {
|
|
1349
|
+
machineId: tail.machineId,
|
|
1350
|
+
machineVersion: tail.machineVersion
|
|
1351
|
+
});
|
|
1352
|
+
const bridgeMeta = readAgentMeta(options.snapshot);
|
|
1353
|
+
if (inheritedExecutionId === void 0 || bridgeMeta?.logId !== inheritedExecutionId) throw new require_event_log_store.AgentMachineVersionMismatchError(tail.id, tail.index, {
|
|
1354
|
+
machineId,
|
|
1355
|
+
machineVersion
|
|
1356
|
+
}, {
|
|
1357
|
+
machineId: tail.machineId,
|
|
1358
|
+
machineVersion: tail.machineVersion
|
|
1359
|
+
});
|
|
1360
|
+
const bridgeTailHash = tail.verification?.stateHash;
|
|
1361
|
+
const bridgeSnapshotHash = hashResumeSnapshot(options.snapshot);
|
|
1362
|
+
if (bridgeMeta.logIndex !== resumeEvents.length) throw new AgentSnapshotDivergedError(bridgeTailHash ?? "(unrecorded)", bridgeSnapshotHash, resumeEvents.length);
|
|
1363
|
+
if (bridgeTailHash !== void 0 && bridgeSnapshotHash !== bridgeTailHash) throw new AgentSnapshotDivergedError(bridgeTailHash, bridgeSnapshotHash, resumeEvents.length);
|
|
1364
|
+
logExecutionId = inheritedExecutionId;
|
|
1365
|
+
const migratedFrom = {
|
|
1366
|
+
machineVersion: tail.machineVersion,
|
|
1367
|
+
logIndex: resumeEvents.length
|
|
1368
|
+
};
|
|
1369
|
+
seedInitEntry = () => appendInitEntry(snapshotInitCandidates(), {
|
|
1370
|
+
...logExecutionId !== void 0 ? { executionId: logExecutionId } : {},
|
|
1371
|
+
migratedFrom
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
} else if (options.snapshot !== void 0) {
|
|
1375
|
+
logExecutionId = crypto.randomUUID();
|
|
1376
|
+
seedInitEntry = () => appendInitEntry(snapshotInitCandidates(), { executionId: logExecutionId });
|
|
1377
|
+
} else {
|
|
1378
|
+
logExecutionId = crypto.randomUUID();
|
|
1379
|
+
seedInitEntry = () => appendInitEntry([resolvedInput !== void 0 ? { input: resolvedInput } : {}], { executionId: logExecutionId });
|
|
1380
|
+
}
|
|
1381
|
+
const resumedHistory = [...logEntries];
|
|
1382
|
+
const siteCallCounts = /* @__PURE__ */ new Map();
|
|
1383
|
+
const mintedCallKeys = /* @__PURE__ */ new WeakMap();
|
|
1384
|
+
runCtx.callKey = (siteId, self) => {
|
|
1385
|
+
if (logExecutionId === void 0) return;
|
|
1386
|
+
const memoized = self === void 0 ? void 0 : mintedCallKeys.get(self);
|
|
1387
|
+
if (memoized !== void 0) return memoized;
|
|
1388
|
+
const startedHere = siteCallCounts.get(siteId) ?? 0;
|
|
1389
|
+
siteCallCounts.set(siteId, startedHere + 1);
|
|
1390
|
+
const key = `${logExecutionId}:${siteId}#${require_event_log_store.agentCallOccurrence(resumedHistory, siteId) + startedHere}`;
|
|
1391
|
+
if (self !== void 0) mintedCallKeys.set(self, key);
|
|
1392
|
+
return key;
|
|
1393
|
+
};
|
|
1394
|
+
const resumeEventToSend = options.event;
|
|
1395
|
+
let ignoredEvent;
|
|
1396
|
+
seedInitEntry?.();
|
|
1397
|
+
let settled = false;
|
|
1398
|
+
let journalAtSettle;
|
|
1399
|
+
cycleGate.isResolved = () => settled;
|
|
1400
|
+
let lastResult;
|
|
1401
|
+
const waiters = [];
|
|
1402
|
+
let idleTimer;
|
|
1403
|
+
let actor;
|
|
1404
|
+
let actorStarted = false;
|
|
1405
|
+
let deliveringResumeEvent = resumeEventToSend !== void 0;
|
|
1406
|
+
const settle = (outcome) => {
|
|
1407
|
+
if (settled) return;
|
|
1408
|
+
settled = true;
|
|
1409
|
+
let persistedSnapshot;
|
|
1410
|
+
let persistenceError;
|
|
1411
|
+
if (actorStarted && outcome.status !== "done") try {
|
|
1412
|
+
persistedSnapshot = actor.getPersistedSnapshot();
|
|
1413
|
+
} catch (error) {
|
|
1414
|
+
persistenceError = error;
|
|
1415
|
+
}
|
|
1416
|
+
const logIndexAtSettle = logEntries.length;
|
|
1417
|
+
journalAtSettle = journalChain;
|
|
1418
|
+
const persist = () => {
|
|
1419
|
+
if (persistenceError !== void 0) throw persistenceError;
|
|
1420
|
+
persistedSnapshot ??= actor.getPersistedSnapshot();
|
|
1421
|
+
persistedSnapshot.agentMeta = {
|
|
1422
|
+
machineId,
|
|
1423
|
+
version: machineVersion,
|
|
1424
|
+
...logExecutionId !== void 0 ? { logId: logExecutionId } : {},
|
|
1425
|
+
logIndex: logIndexAtSettle
|
|
1426
|
+
};
|
|
1427
|
+
return persistedSnapshot;
|
|
1428
|
+
};
|
|
1429
|
+
const result = {
|
|
1430
|
+
...outcome,
|
|
1431
|
+
events: [...logEntries],
|
|
1432
|
+
persist,
|
|
1433
|
+
drain: drainJournal,
|
|
1434
|
+
usage: runUsage(),
|
|
1435
|
+
...ignoredEvent !== void 0 ? { ignored: ignoredEvent } : {}
|
|
1436
|
+
};
|
|
1437
|
+
if (idleTimer !== void 0) clearTimeout(idleTimer);
|
|
1438
|
+
onTrace({
|
|
1439
|
+
type: "run.end",
|
|
1440
|
+
...outcome
|
|
1441
|
+
});
|
|
1442
|
+
if (options.signal) options.signal.removeEventListener("abort", onAbort);
|
|
1443
|
+
actor.stop();
|
|
1444
|
+
lastResult = result;
|
|
1445
|
+
for (const resolve of waiters.splice(0)) resolve(result);
|
|
1446
|
+
};
|
|
1447
|
+
const onAbort = () => {
|
|
1448
|
+
settle({
|
|
1449
|
+
status: "error",
|
|
1450
|
+
cause: "aborted",
|
|
1451
|
+
error: options.signal?.reason ?? /* @__PURE__ */ new Error("Aborted"),
|
|
1452
|
+
snapshot: actor.getSnapshot()
|
|
1453
|
+
});
|
|
1454
|
+
};
|
|
1455
|
+
const settleIdle = (current) => {
|
|
1456
|
+
warnNonSerializableContext(current);
|
|
1457
|
+
settle({
|
|
1458
|
+
status: "idle",
|
|
1459
|
+
snapshot: current
|
|
1460
|
+
});
|
|
1461
|
+
};
|
|
1462
|
+
const runErrorCause = (error) => budgetExceeded ? "max-model-calls" : wrapsDecisionExhausted(error) ? "decision-exhausted" : "machine";
|
|
1463
|
+
const scheduleIdleCheck = () => {
|
|
1464
|
+
if (idleTimer !== void 0) clearTimeout(idleTimer);
|
|
1465
|
+
idleTimer = setTimeout(() => {
|
|
1466
|
+
idleTimer = void 0;
|
|
1467
|
+
if (settled) return;
|
|
1468
|
+
const current = actor.getSnapshot();
|
|
1469
|
+
if (isIntentionalIdle(current) && isIdleSnapshot(current)) settleIdle(current);
|
|
1470
|
+
}, 0);
|
|
1471
|
+
};
|
|
1472
|
+
actor = (0, xstate.createActor)(boundMachine, {
|
|
1473
|
+
input: resolvedInput,
|
|
1474
|
+
snapshot: effectiveSnapshot,
|
|
1475
|
+
inspect: (event) => {
|
|
1476
|
+
if (typeof options.inspect === "function") options.inspect(event);
|
|
1477
|
+
else options.inspect?.next?.(event);
|
|
1478
|
+
if (event.type !== "@xstate.transition" || event.actorRef !== actor.ref) return;
|
|
1479
|
+
if (settled) return;
|
|
1480
|
+
const snapshot = event.snapshot;
|
|
1481
|
+
lastRootSnapshot = snapshot;
|
|
1482
|
+
if (resumeEventToSend !== void 0 && event.event === resumeEventToSend && event.microsteps.length === 0 && event.actions.length === 0 && event.sent.length === 0) ignoredEvent = resumeEventToSend;
|
|
1483
|
+
if (pendingUsageJournal !== void 0 && event.event === pendingUsageJournal.event) pendingUsageJournal.entry = appendLogEntry(event.event, snapshot);
|
|
1484
|
+
else if (event.event.type !== "@xstate.init" && (event.sourceRef !== event.actorRef || event.event.type === "xstate.timer")) appendLogEntry(event.event, snapshot);
|
|
1485
|
+
onTrace({
|
|
1486
|
+
type: "machine.transition",
|
|
1487
|
+
snapshot,
|
|
1488
|
+
event: event.event
|
|
1489
|
+
});
|
|
1490
|
+
if (snapshot.status === "done") {
|
|
1491
|
+
settle({
|
|
1492
|
+
status: "done",
|
|
1493
|
+
output: snapshot.output,
|
|
1494
|
+
snapshot
|
|
1495
|
+
});
|
|
1496
|
+
return;
|
|
1497
|
+
}
|
|
1498
|
+
if (snapshot.status === "error") {
|
|
1499
|
+
settle({
|
|
1500
|
+
status: "error",
|
|
1501
|
+
cause: runErrorCause(snapshot.error),
|
|
1502
|
+
error: snapshot.error,
|
|
1503
|
+
snapshot
|
|
1504
|
+
});
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
if (snapshot.status === "stopped") {
|
|
1508
|
+
settle({
|
|
1509
|
+
status: "error",
|
|
1510
|
+
cause: "stopped",
|
|
1511
|
+
error: /* @__PURE__ */ new Error("Actor stopped externally."),
|
|
1512
|
+
snapshot
|
|
1513
|
+
});
|
|
1514
|
+
return;
|
|
1515
|
+
}
|
|
1516
|
+
if (!deliveringResumeEvent && isIntentionalIdle(snapshot) && isIdleSnapshot(snapshot)) {
|
|
1517
|
+
queueMicrotask(() => {
|
|
1518
|
+
if (settled) return;
|
|
1519
|
+
const current = actor.getSnapshot();
|
|
1520
|
+
if (isIntentionalIdle(current) && isIdleSnapshot(current)) settleIdle(current);
|
|
1521
|
+
else scheduleIdleCheck();
|
|
1522
|
+
});
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
scheduleIdleCheck();
|
|
1526
|
+
}
|
|
1527
|
+
});
|
|
1528
|
+
actorHolder.actorRef = actor;
|
|
1529
|
+
actor.subscribe({ error: () => {} });
|
|
1530
|
+
actor.on("*", (event) => {
|
|
1531
|
+
onTrace({
|
|
1532
|
+
type: "emit",
|
|
1533
|
+
event
|
|
1534
|
+
});
|
|
1535
|
+
});
|
|
1536
|
+
for (const [type, handler] of Object.entries(options.on ?? {})) if (typeof handler === "function") actor.on(type, handler);
|
|
1537
|
+
const sessionApi = { settled: async () => {
|
|
1538
|
+
const result = settled && lastResult !== void 0 ? lastResult : await new Promise((resolve) => {
|
|
1539
|
+
waiters.push(resolve);
|
|
1540
|
+
});
|
|
1541
|
+
await journalAtSettle;
|
|
1542
|
+
return result;
|
|
1543
|
+
} };
|
|
1544
|
+
if (options.signal) {
|
|
1545
|
+
if (options.signal.aborted) {
|
|
1546
|
+
settle({
|
|
1547
|
+
status: "error",
|
|
1548
|
+
cause: "aborted",
|
|
1549
|
+
error: options.signal.reason ?? /* @__PURE__ */ new Error("Aborted"),
|
|
1550
|
+
snapshot: actor.getSnapshot()
|
|
1551
|
+
});
|
|
1552
|
+
return sessionApi;
|
|
1553
|
+
}
|
|
1554
|
+
options.signal.addEventListener("abort", onAbort);
|
|
1555
|
+
}
|
|
1556
|
+
onTrace({
|
|
1557
|
+
type: "run.start",
|
|
1558
|
+
...resolvedInput !== void 0 ? { input: resolvedInput } : {},
|
|
1559
|
+
...effectiveSnapshot !== void 0 ? { snapshot: effectiveSnapshot } : {},
|
|
1560
|
+
...resumeEventToSend !== void 0 ? { event: resumeEventToSend } : {}
|
|
1561
|
+
});
|
|
1562
|
+
actorStarted = true;
|
|
1563
|
+
actor.start();
|
|
1564
|
+
if (!settled) {
|
|
1565
|
+
const startedSnapshot = actor.getSnapshot();
|
|
1566
|
+
lastRootSnapshot ??= startedSnapshot;
|
|
1567
|
+
if (startedSnapshot.status === "done") settle({
|
|
1568
|
+
status: "done",
|
|
1569
|
+
output: startedSnapshot.output,
|
|
1570
|
+
snapshot: startedSnapshot
|
|
1571
|
+
});
|
|
1572
|
+
else if (startedSnapshot.status === "error") settle({
|
|
1573
|
+
status: "error",
|
|
1574
|
+
cause: runErrorCause(startedSnapshot.error),
|
|
1575
|
+
error: startedSnapshot.error,
|
|
1576
|
+
snapshot: startedSnapshot
|
|
1577
|
+
});
|
|
1578
|
+
else if (startedSnapshot.status === "stopped") settle({
|
|
1579
|
+
status: "error",
|
|
1580
|
+
cause: "stopped",
|
|
1581
|
+
error: /* @__PURE__ */ new Error("Actor stopped externally."),
|
|
1582
|
+
snapshot: startedSnapshot
|
|
1583
|
+
});
|
|
1584
|
+
}
|
|
1585
|
+
if (resumeEventToSend && !settled) {
|
|
1586
|
+
deliveringResumeEvent = false;
|
|
1587
|
+
actor.send(resumeEventToSend);
|
|
1588
|
+
}
|
|
1589
|
+
return sessionApi;
|
|
1590
|
+
}
|
|
1591
|
+
/**
|
|
1592
|
+
* Wraps a `(snapshot, actorRef) => void` handler into a function usable as
|
|
1593
|
+
* {@link RunAgentOptions.inspect}: it filters the raw inspection stream to
|
|
1594
|
+
* `@xstate.transition` events and hands the handler the typed
|
|
1595
|
+
* {@link AnyMachineSnapshot} and the {@link InspectedActorRef} that
|
|
1596
|
+
* transitioned. Attribute a child actor via `actorRef.id`/`actorRef.src`. Saves
|
|
1597
|
+
* the manual `event.type === '@xstate.transition'` filtering and the snapshot/
|
|
1598
|
+
* actorRef casts.
|
|
1599
|
+
*/
|
|
1600
|
+
function inspectTransitions(handler) {
|
|
1601
|
+
return (inspectionEvent) => {
|
|
1602
|
+
if (inspectionEvent.type !== "@xstate.transition") return;
|
|
1603
|
+
handler(inspectionEvent.snapshot, inspectionEvent.actorRef);
|
|
1604
|
+
};
|
|
1605
|
+
}
|
|
1606
|
+
/**
|
|
1607
|
+
* An xstate `inspect` handler that emits `machine.transition` trace events onto
|
|
1608
|
+
* `onTrace`, sharing the SAME versioned envelope and per-root-actor `seq`
|
|
1609
|
+
* registry as {@link provideExecutors}' `onTrace`. Pair the two on one actor to
|
|
1610
|
+
* get a single ordered trace stream (request + transition events) for the
|
|
1611
|
+
* uncontrolled path:
|
|
1612
|
+
*
|
|
1613
|
+
* ```ts
|
|
1614
|
+
* const bound = provideExecutors(machine, executors, { onTrace });
|
|
1615
|
+
* const actor = createActor(bound, { inspect: traceTransitions(onTrace) });
|
|
1616
|
+
* ```
|
|
1617
|
+
*
|
|
1618
|
+
* Only ROOT-actor transitions are traced (matching `runAgent`'s
|
|
1619
|
+
* `machine.transition`); child-actor transitions are ignored. Attribute the
|
|
1620
|
+
* event via its envelope `runId`.
|
|
1621
|
+
*
|
|
1622
|
+
* By design this path has NO `run.start`/`run.end` events: `createActor` has no
|
|
1623
|
+
* run boundary the way `runAgent` does, so the stream starts at the actor's
|
|
1624
|
+
* first transition. It also does NOT emit `emit` trace events: in this xstate
|
|
1625
|
+
* build emitted events are delivered through `actor.on(...)`, not the inspection
|
|
1626
|
+
* protocol, so they are not observable from an `inspect` handler — subscribe
|
|
1627
|
+
* with `actor.on('*', ...)` if you need them.
|
|
1628
|
+
*/
|
|
1629
|
+
function traceTransitions(onTrace) {
|
|
1630
|
+
return (inspectionEvent) => {
|
|
1631
|
+
if (inspectionEvent.type !== "@xstate.transition") return;
|
|
1632
|
+
const actorRef = inspectionEvent.actorRef;
|
|
1633
|
+
if (actorRef?._parent) return;
|
|
1634
|
+
onTrace(stampRootTrace(actorRef, {
|
|
1635
|
+
type: "machine.transition",
|
|
1636
|
+
snapshot: inspectionEvent.snapshot,
|
|
1637
|
+
event: inspectionEvent.event
|
|
1638
|
+
}));
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
function isIdleSnapshot(snapshot) {
|
|
1642
|
+
if (snapshot.status !== "active") return false;
|
|
1643
|
+
if (Object.values(snapshot.children ?? {}).some((child) => {
|
|
1644
|
+
const childSnapshot = child?.getSnapshot?.();
|
|
1645
|
+
if (childSnapshot?.status !== "active") return false;
|
|
1646
|
+
if ((0, xstate.isMachineSnapshot)(childSnapshot)) return !isIdleSnapshot(childSnapshot);
|
|
1647
|
+
return true;
|
|
1648
|
+
})) return false;
|
|
1649
|
+
return !(0, xstate.getNextTransitions)(snapshot).some((transitionDef) => transitionDef.eventType === "" || transitionDef.eventType.startsWith("xstate.after"));
|
|
1650
|
+
}
|
|
1651
|
+
/**
|
|
1652
|
+
* Default machine-owned idle signal used by {@link runAgent}: an active
|
|
1653
|
+
* snapshot that accepts an external event anywhere in its active hierarchy,
|
|
1654
|
+
* or declares interaction metadata on an active state.
|
|
1655
|
+
*
|
|
1656
|
+
* Compose this in `setupAgent({ isIdle })` when the application has additional
|
|
1657
|
+
* wait states: `isIdle: (snapshot) => isAgentIdle(snapshot) || snapshot.hasTag('waiting')`.
|
|
1658
|
+
* Pending work and active invoked children are checked separately by the
|
|
1659
|
+
* runner, so this function only answers whether the state is an intentional
|
|
1660
|
+
* external wait.
|
|
1661
|
+
*/
|
|
1662
|
+
function isAgentIdle(snapshot) {
|
|
1663
|
+
if (snapshot.status !== "active") return false;
|
|
1664
|
+
return snapshot.nodes.some((node) => {
|
|
1665
|
+
const meta = node.meta;
|
|
1666
|
+
return typeof meta === "object" && meta !== null && "interaction" in meta;
|
|
1667
|
+
}) || require_decision.getAcceptedEvents(snapshot).length > 0;
|
|
1668
|
+
}
|
|
1669
|
+
//#endregion
|
|
1670
|
+
Object.defineProperty(exports, "AGENT_TRACE_SCHEMA_VERSION", {
|
|
1671
|
+
enumerable: true,
|
|
1672
|
+
get: function() {
|
|
1673
|
+
return AGENT_TRACE_SCHEMA_VERSION;
|
|
1674
|
+
}
|
|
1675
|
+
});
|
|
1676
|
+
Object.defineProperty(exports, "AgentMaxModelCallsExceededError", {
|
|
1677
|
+
enumerable: true,
|
|
1678
|
+
get: function() {
|
|
1679
|
+
return AgentMaxModelCallsExceededError;
|
|
1680
|
+
}
|
|
1681
|
+
});
|
|
1682
|
+
Object.defineProperty(exports, "AgentSnapshotDivergedError", {
|
|
1683
|
+
enumerable: true,
|
|
1684
|
+
get: function() {
|
|
1685
|
+
return AgentSnapshotDivergedError;
|
|
1686
|
+
}
|
|
1687
|
+
});
|
|
1688
|
+
Object.defineProperty(exports, "bindChildMachineForProvide", {
|
|
1689
|
+
enumerable: true,
|
|
1690
|
+
get: function() {
|
|
1691
|
+
return bindChildMachineForProvide;
|
|
1692
|
+
}
|
|
1693
|
+
});
|
|
1694
|
+
Object.defineProperty(exports, "bindDecisionForProvide", {
|
|
1695
|
+
enumerable: true,
|
|
1696
|
+
get: function() {
|
|
1697
|
+
return bindDecisionForProvide;
|
|
1698
|
+
}
|
|
1699
|
+
});
|
|
1700
|
+
Object.defineProperty(exports, "bindTextForProvide", {
|
|
1701
|
+
enumerable: true,
|
|
1702
|
+
get: function() {
|
|
1703
|
+
return bindTextForProvide;
|
|
1704
|
+
}
|
|
1705
|
+
});
|
|
1706
|
+
Object.defineProperty(exports, "executeAgentRequest", {
|
|
1707
|
+
enumerable: true,
|
|
1708
|
+
get: function() {
|
|
1709
|
+
return executeAgentRequest;
|
|
1710
|
+
}
|
|
1711
|
+
});
|
|
1712
|
+
Object.defineProperty(exports, "getConfiguredInvokeSrcs", {
|
|
1713
|
+
enumerable: true,
|
|
1714
|
+
get: function() {
|
|
1715
|
+
return getConfiguredInvokeSrcs;
|
|
1716
|
+
}
|
|
1717
|
+
});
|
|
1718
|
+
Object.defineProperty(exports, "getPendingInvokes", {
|
|
1719
|
+
enumerable: true,
|
|
1720
|
+
get: function() {
|
|
1721
|
+
return getPendingInvokes;
|
|
1722
|
+
}
|
|
1723
|
+
});
|
|
1724
|
+
Object.defineProperty(exports, "initialAgentStep", {
|
|
1725
|
+
enumerable: true,
|
|
1726
|
+
get: function() {
|
|
1727
|
+
return initialAgentStep;
|
|
1728
|
+
}
|
|
1729
|
+
});
|
|
1730
|
+
Object.defineProperty(exports, "inspectTransitions", {
|
|
1731
|
+
enumerable: true,
|
|
1732
|
+
get: function() {
|
|
1733
|
+
return inspectTransitions;
|
|
1734
|
+
}
|
|
1735
|
+
});
|
|
1736
|
+
Object.defineProperty(exports, "isAgentIdle", {
|
|
1737
|
+
enumerable: true,
|
|
1738
|
+
get: function() {
|
|
1739
|
+
return isAgentIdle;
|
|
1740
|
+
}
|
|
1741
|
+
});
|
|
1742
|
+
Object.defineProperty(exports, "isStateMachineLogic", {
|
|
1743
|
+
enumerable: true,
|
|
1744
|
+
get: function() {
|
|
1745
|
+
return isStateMachineLogic;
|
|
1746
|
+
}
|
|
1747
|
+
});
|
|
1748
|
+
Object.defineProperty(exports, "rejectAgentStep", {
|
|
1749
|
+
enumerable: true,
|
|
1750
|
+
get: function() {
|
|
1751
|
+
return rejectAgentStep;
|
|
1752
|
+
}
|
|
1753
|
+
});
|
|
1754
|
+
Object.defineProperty(exports, "resolveAgentStep", {
|
|
1755
|
+
enumerable: true,
|
|
1756
|
+
get: function() {
|
|
1757
|
+
return resolveAgentStep;
|
|
1758
|
+
}
|
|
1759
|
+
});
|
|
1760
|
+
Object.defineProperty(exports, "runAgent", {
|
|
1761
|
+
enumerable: true,
|
|
1762
|
+
get: function() {
|
|
1763
|
+
return runAgent;
|
|
1764
|
+
}
|
|
1765
|
+
});
|
|
1766
|
+
Object.defineProperty(exports, "serializeTraceEvent", {
|
|
1767
|
+
enumerable: true,
|
|
1768
|
+
get: function() {
|
|
1769
|
+
return serializeTraceEvent;
|
|
1770
|
+
}
|
|
1771
|
+
});
|
|
1772
|
+
Object.defineProperty(exports, "traceTransitions", {
|
|
1773
|
+
enumerable: true,
|
|
1774
|
+
get: function() {
|
|
1775
|
+
return traceTransitions;
|
|
1776
|
+
}
|
|
1777
|
+
});
|
|
1778
|
+
Object.defineProperty(exports, "transitionAgentStep", {
|
|
1779
|
+
enumerable: true,
|
|
1780
|
+
get: function() {
|
|
1781
|
+
return transitionAgentStep;
|
|
1782
|
+
}
|
|
1783
|
+
});
|