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