@statelyai/agent 1.1.6 → 2.0.0-alpha.5

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 (84) hide show
  1. package/LICENSE +21 -0
  2. package/dist/ai-sdk.cjs +249 -0
  3. package/dist/ai-sdk.d.cts +168 -0
  4. package/dist/ai-sdk.d.mts +168 -0
  5. package/dist/ai-sdk.mjs +241 -0
  6. package/dist/cli.cjs +63 -0
  7. package/dist/cli.d.cts +1 -0
  8. package/dist/cli.d.mts +1 -0
  9. package/dist/cli.mjs +64 -0
  10. package/dist/decision-FTmbqSEe.mjs +938 -0
  11. package/dist/decision-pC-bY2DE.cjs +1231 -0
  12. package/dist/index.cjs +54 -0
  13. package/dist/index.d.cts +1217 -0
  14. package/dist/index.d.mts +1194 -405
  15. package/dist/index.mjs +3 -588
  16. package/dist/openai-compat.cjs +319 -0
  17. package/dist/openai-compat.d.cts +98 -0
  18. package/dist/openai-compat.d.mts +98 -0
  19. package/dist/openai-compat.mjs +312 -0
  20. package/dist/src-CjpHDU8F.mjs +2445 -0
  21. package/dist/src-DcRsWPfV.cjs +2564 -0
  22. package/dist/text-logic-1ZQkO3zr.d.cts +682 -0
  23. package/dist/text-logic-2EMJIS-n.d.mts +682 -0
  24. package/dist/types-BHjeDdch.d.cts +208 -0
  25. package/dist/types-Cq1YlAQ6.d.mts +208 -0
  26. package/dist/utils-CWUCa3pF.d.mts +108 -0
  27. package/dist/utils-lK1wnL2i.d.cts +108 -0
  28. package/dist/zod.cjs +31 -0
  29. package/dist/zod.d.cts +30 -0
  30. package/dist/zod.d.mts +30 -0
  31. package/dist/zod.mjs +30 -0
  32. package/package.json +109 -28
  33. package/readme.md +144 -6
  34. package/schemas/agent-workflow.json +527 -0
  35. package/.changeset/README.md +0 -8
  36. package/.changeset/config.json +0 -11
  37. package/.env.template +0 -3
  38. package/.github/actions/ci-setup/action.yml +0 -24
  39. package/.github/workflows/release.yml +0 -46
  40. package/.vscode/launch.json +0 -28
  41. package/CHANGELOG.md +0 -222
  42. package/dist/index.d.ts +0 -428
  43. package/dist/index.js +0 -621
  44. package/examples/chatbot.ts +0 -71
  45. package/examples/cot.ts +0 -89
  46. package/examples/email.ts +0 -118
  47. package/examples/example.ts +0 -81
  48. package/examples/goal.ts +0 -94
  49. package/examples/helpers/helpers.ts +0 -17
  50. package/examples/helpers/loader.ts +0 -32
  51. package/examples/helpers/runner.ts +0 -27
  52. package/examples/joke.ts +0 -225
  53. package/examples/multi.ts +0 -103
  54. package/examples/newspaper.ts +0 -324
  55. package/examples/number.ts +0 -102
  56. package/examples/raffle.ts +0 -105
  57. package/examples/sandbox.ts +0 -28
  58. package/examples/simple.ts +0 -39
  59. package/examples/support.ts +0 -147
  60. package/examples/ticTacToe.ts +0 -224
  61. package/examples/todo.ts +0 -137
  62. package/examples/tutor.ts +0 -100
  63. package/examples/verify.ts +0 -120
  64. package/examples/weather.ts +0 -178
  65. package/examples/wiki.ts +0 -30
  66. package/examples/word.ts +0 -171
  67. package/src/adapters/vercel.ts +0 -7
  68. package/src/agent-experimental.ts +0 -221
  69. package/src/agent.test.ts +0 -506
  70. package/src/agent.ts +0 -300
  71. package/src/decision.test.ts +0 -179
  72. package/src/decision.ts +0 -84
  73. package/src/index.ts +0 -4
  74. package/src/memory.ts +0 -25
  75. package/src/planners/shortestPathPlanner.ts +0 -22
  76. package/src/planners/simplePlanner.ts +0 -139
  77. package/src/schemas.ts +0 -11
  78. package/src/strategies/chain-of-note.ts +0 -155
  79. package/src/templates/defaultText.ts +0 -18
  80. package/src/text.ts +0 -241
  81. package/src/types.ts +0 -499
  82. package/src/utils.ts +0 -72
  83. package/tsconfig.json +0 -109
  84. package/vitest.config.ts +0 -9
@@ -0,0 +1,2445 @@
1
+ import { A as agentExecutionOptions, C as executeAgentTextRequest, D as normalizeGeneratorResult, E as isTextLogic, F as machineSuspensionPredicates, H as getMachineStructuralHash, I as missingActor, J as userMessage, L as assistantMessage, M as getMachineSuspensionPredicate, N as getRegisteredAgentExecutionOptions, P as isUnboundPlaceholder, R as findNonSerializableContextPaths, S as createTextLogic, V as getJsonSchemaSync, Y as validateSchemaSync, _ as PLAN_ACTOR, a as createPlanActor, c as isPlanLogic, f as getAcceptedEvents, g as DECIDE_ACTOR, h as sanitizeEventToolName, i as createDecideActor, j as executorBoundLogics, k as userInputActor, n as PLAN_DONE_EVENT_TYPE, o as initialPlanLedger, r as advancePlanLedger, s as isDecisionLogic, t as DecisionExhaustedError, u as resolveDecision, v as USER_INPUT_ACTOR, x as builtinTextActors } from "./decision-FTmbqSEe.mjs";
2
+ import { createActor, createAsyncLogic, getNextTransitions, initialTransition, setup, transition } from "xstate";
3
+ //#region src/messages.ts
4
+ function addMessages(resolve) {
5
+ return (args) => {
6
+ const resolved = typeof resolve === "function" ? resolve(args) : resolve;
7
+ return [...args.context.messages, ...Array.isArray(resolved) ? resolved : [resolved]];
8
+ };
9
+ }
10
+ /**
11
+ * Builds a transition-function result that appends one or more
12
+ * {@link AgentMessage}s to a context's `messages` array. `resolve` is either
13
+ * a message (or array of messages) or a function of `{ context, event }`
14
+ * returning them; the returned function is meant to be used directly as (or
15
+ * composed into) a transition's result, e.g. `on: { USER_REPLIED:
16
+ * agent.appendMessages(({ event }) => userMessage(event.text)) }`. Requires
17
+ * `messages: AgentMessage[]` on context — see {@link messagesSchema} for a
18
+ * ready-made schema for that field.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * on: {
23
+ * USER_REPLIED: appendMessages(({ event }) => userMessage(event.text)),
24
+ * }
25
+ * ```
26
+ */
27
+ function appendMessages(resolve) {
28
+ return (args) => ({ context: { messages: addMessages(resolve)(args) } });
29
+ }
30
+ const KNOWN_PART_TYPES = new Set([
31
+ "text",
32
+ "image",
33
+ "file",
34
+ "tool-call",
35
+ "tool-result"
36
+ ]);
37
+ function isKnownPart(part) {
38
+ return !!part && typeof part === "object" && KNOWN_PART_TYPES.has(part.type);
39
+ }
40
+ function validatePartsArray(content) {
41
+ if (!Array.isArray(content)) return "Expected content to be a string or an array of parts";
42
+ for (const part of content) if (!isKnownPart(part)) {
43
+ const type = part && typeof part === "object" ? part.type : void 0;
44
+ return `Unknown message part type: ${JSON.stringify(type)}`;
45
+ }
46
+ }
47
+ /**
48
+ * A {@link StandardSchemaV1} validating an `AgentMessage[]` context field —
49
+ * checks that every message has a known `role` (`system`/`user`/`assistant`/
50
+ * `tool`) and that `content` is either a string (where the role allows it) or
51
+ * an array of parts with a known `type`. Use it directly as a context
52
+ * schema's `messages` field when authoring with `createAgentSchemas`.
53
+ */
54
+ const messagesSchema = { "~standard": {
55
+ version: 1,
56
+ vendor: "statelyai-agent",
57
+ validate(value) {
58
+ if (!Array.isArray(value)) return { issues: [{ message: "Expected an array of agent messages" }] };
59
+ for (const message of value) {
60
+ if (!message || typeof message !== "object") return { issues: [{ message: "Expected an array of agent messages" }] };
61
+ const role = message.role;
62
+ const content = message.content;
63
+ if (role !== "system" && role !== "user" && role !== "assistant" && role !== "tool") return { issues: [{ message: `Unknown message role: ${JSON.stringify(role)}` }] };
64
+ if (role === "system") {
65
+ if (typeof content !== "string") return { issues: [{ message: "system message content must be a string" }] };
66
+ continue;
67
+ }
68
+ if (role === "tool") {
69
+ const error = validatePartsArray(content) ?? (content.some((part) => part.type !== "tool-result") ? "tool message content must contain only tool-result parts" : void 0);
70
+ if (error) return { issues: [{ message: error }] };
71
+ continue;
72
+ }
73
+ if (typeof content === "string") continue;
74
+ const error = validatePartsArray(content);
75
+ if (error) return { issues: [{ message: error }] };
76
+ }
77
+ return { value };
78
+ }
79
+ } };
80
+ //#endregion
81
+ //#region src/workflow-config.ts
82
+ const workflowConfigWholeExpressionPattern = /^\{\{\s*([\s\S]*?)\s*\}\}$/;
83
+ const workflowConfigTemplateExpressionPattern = /\{\{\s*([\s\S]*?)\s*\}\}/g;
84
+ function evaluateWorkflowConfigPath(expression, scope) {
85
+ const parts = expression.trim().split(".").filter(Boolean);
86
+ let current = scope;
87
+ for (const part of parts) {
88
+ if (!current || typeof current !== "object") return;
89
+ current = current[part];
90
+ }
91
+ return current;
92
+ }
93
+ function evaluateWorkflowConfigValue(value, scope) {
94
+ if (typeof value === "string") {
95
+ const wholeMatch = value.match(workflowConfigWholeExpressionPattern);
96
+ if (wholeMatch?.[1]) return evaluateWorkflowConfigPath(wholeMatch[1], scope);
97
+ return value.replace(workflowConfigTemplateExpressionPattern, (_match, expression) => {
98
+ const resolved = evaluateWorkflowConfigPath(expression, scope);
99
+ return resolved === void 0 || resolved === null ? "" : String(resolved);
100
+ });
101
+ }
102
+ if (Array.isArray(value)) return value.map((item) => evaluateWorkflowConfigValue(item, scope));
103
+ if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, evaluateWorkflowConfigValue(item, scope)]));
104
+ return value;
105
+ }
106
+ function createSchemasFromWorkflowConfig(config, compileSchema) {
107
+ return createAgentSchemas({
108
+ context: compileSchema(config.schemas?.context ?? { type: "object" }, "context"),
109
+ events: Object.fromEntries(Object.entries(config.schemas?.events ?? {}).map(([key, schema]) => [key, compileSchema(schema, `event.${key}`)])),
110
+ emitted: Object.fromEntries(Object.entries(config.schemas?.emitted ?? {}).map(([key, schema]) => [key, compileSchema(schema, `emitted.${key}`)])),
111
+ input: compileSchema(config.schemas?.input ?? {}, "input"),
112
+ output: compileSchema(config.schemas?.output ?? {}, "output"),
113
+ meta: compileSchema(config.schemas?.meta ?? {}, "meta")
114
+ });
115
+ }
116
+ function createRequestsFromWorkflowConfig(config, compileSchema) {
117
+ return Object.fromEntries(Object.entries(config.requests ?? {}).map(([key, request]) => [key, {
118
+ mode: request.mode,
119
+ description: request.description,
120
+ schemas: {
121
+ input: compileSchema(request.input, `${key}.input`),
122
+ output: compileSchema(request.output, `${key}.output`)
123
+ },
124
+ model: ({ input }) => String(evaluateWorkflowConfigValue(request.model, { input }) ?? ""),
125
+ system: request.system === void 0 ? void 0 : ({ input }) => evaluateWorkflowConfigValue(request.system, { input }),
126
+ prompt: request.prompt === void 0 ? void 0 : ({ input }) => evaluateWorkflowConfigValue(request.prompt, { input }),
127
+ messages: request.messages === void 0 ? void 0 : ({ input }) => evaluateWorkflowConfigValue(request.messages, { input }),
128
+ tools: request.tools,
129
+ toolChoice: request.toolChoice,
130
+ reasoning: request.reasoning,
131
+ temperature: request.temperature === void 0 ? void 0 : ({ input }) => evaluateWorkflowConfigValue(request.temperature, { input }),
132
+ maxOutputTokens: request.maxOutputTokens === void 0 ? void 0 : ({ input }) => evaluateWorkflowConfigValue(request.maxOutputTokens, { input }),
133
+ topP: request.topP === void 0 ? void 0 : ({ input }) => evaluateWorkflowConfigValue(request.topP, { input }),
134
+ topK: request.topK === void 0 ? void 0 : ({ input }) => evaluateWorkflowConfigValue(request.topK, { input }),
135
+ seed: request.seed === void 0 ? void 0 : ({ input }) => evaluateWorkflowConfigValue(request.seed, { input }),
136
+ stopSequences: request.stopSequences === void 0 ? void 0 : ({ input }) => evaluateWorkflowConfigValue(request.stopSequences, { input }),
137
+ metadata: request.metadata === void 0 ? void 0 : ({ input }) => evaluateWorkflowConfigValue(request.metadata, { input })
138
+ }]));
139
+ }
140
+ function createActorPlaceholdersFromWorkflowConfig(config) {
141
+ return Object.fromEntries(Object.keys(config.actors ?? {}).map((key) => [key, missingActor(key)]));
142
+ }
143
+ function createAssignAction(assignConfig) {
144
+ return ({ context, event }) => ({ context: Object.fromEntries(Object.entries(assignConfig).map(([key, value]) => [key, evaluateWorkflowConfigValue(value, {
145
+ context,
146
+ event
147
+ })])) });
148
+ }
149
+ function lowerWorkflowActions(actionConfig) {
150
+ if (!actionConfig) return;
151
+ const lowered = (Array.isArray(actionConfig) ? actionConfig : [actionConfig]).map((action) => {
152
+ if (action.assign !== void 0) return createAssignAction(action.assign);
153
+ if (action.emit !== void 0) return ({ context, event }, enq) => {
154
+ enq.emit(evaluateWorkflowConfigValue(action.emit, {
155
+ context,
156
+ event
157
+ }));
158
+ };
159
+ if (!action.type) throw new Error("setupAgent.fromConfig: action must declare 'type', 'assign', or 'emit'.");
160
+ return {
161
+ type: action.type,
162
+ params: ({ context, event }) => evaluateWorkflowConfigValue(action.params, {
163
+ context,
164
+ event
165
+ })
166
+ };
167
+ });
168
+ return Array.isArray(actionConfig) ? lowered : lowered[0];
169
+ }
170
+ function workflowTransitionMatches(transitionConfig, scope) {
171
+ if (transitionConfig.guard === void 0) return true;
172
+ if (typeof transitionConfig.guard === "string") return Boolean(evaluateWorkflowConfigValue(transitionConfig.guard, scope));
173
+ return typeof transitionConfig.guard === "function" ? transitionConfig.guard(scope) : false;
174
+ }
175
+ function lowerWorkflowTransitionResult(transitionConfig, scope) {
176
+ return {
177
+ ...transitionConfig.target !== void 0 ? { target: transitionConfig.target } : {},
178
+ ...transitionConfig.assign ? { context: Object.fromEntries(Object.entries(transitionConfig.assign).map(([key, value]) => [key, evaluateWorkflowConfigValue(value, scope)])) } : {},
179
+ ...transitionConfig.description !== void 0 ? { description: transitionConfig.description } : {},
180
+ ...transitionConfig.reenter !== void 0 ? { reenter: transitionConfig.reenter } : {},
181
+ ...transitionConfig.meta !== void 0 ? { meta: transitionConfig.meta } : {}
182
+ };
183
+ }
184
+ function lowerWorkflowTransition(transitionConfig) {
185
+ return ({ context, event }) => {
186
+ const scope = {
187
+ context,
188
+ event
189
+ };
190
+ if (!workflowTransitionMatches(transitionConfig, scope)) return;
191
+ return lowerWorkflowTransitionResult(transitionConfig, scope);
192
+ };
193
+ }
194
+ function lowerWorkflowTransitionOrArray(transitionConfig) {
195
+ if (!transitionConfig) return;
196
+ return Array.isArray(transitionConfig) ? ({ context, event }) => {
197
+ const scope = {
198
+ context,
199
+ event
200
+ };
201
+ const transition = transitionConfig.find((candidate) => workflowTransitionMatches(candidate, scope));
202
+ if (!transition) return;
203
+ return lowerWorkflowTransitionResult(transition, scope);
204
+ } : lowerWorkflowTransition(transitionConfig);
205
+ }
206
+ function lowerWorkflowInvoke(invokeConfig) {
207
+ return {
208
+ ...invokeConfig.id !== void 0 ? { id: invokeConfig.id } : {},
209
+ src: invokeConfig.src,
210
+ ...invokeConfig.input !== void 0 ? { input: ({ context, event }) => evaluateWorkflowConfigValue(invokeConfig.input, {
211
+ context,
212
+ event
213
+ }) } : {},
214
+ ...invokeConfig.onDone !== void 0 ? { onDone: lowerWorkflowTransitionOrArray(invokeConfig.onDone) } : {},
215
+ ...invokeConfig.onError !== void 0 ? { onError: lowerWorkflowTransitionOrArray(invokeConfig.onError) } : {},
216
+ ...invokeConfig.meta !== void 0 ? { meta: invokeConfig.meta } : {}
217
+ };
218
+ }
219
+ function lowerWorkflowState(stateConfig) {
220
+ return {
221
+ ...stateConfig.description !== void 0 ? { description: stateConfig.description } : {},
222
+ ...stateConfig.type !== void 0 ? { type: stateConfig.type } : {},
223
+ ...stateConfig.initial !== void 0 ? { initial: stateConfig.initial } : {},
224
+ ...stateConfig.states !== void 0 ? { states: Object.fromEntries(Object.entries(stateConfig.states).map(([key, child]) => [key, lowerWorkflowState(child)])) } : {},
225
+ ...stateConfig.choice !== void 0 ? { choice: lowerWorkflowTransitionOrArray(stateConfig.choice) } : {},
226
+ ...stateConfig.invoke !== void 0 ? { invoke: Array.isArray(stateConfig.invoke) ? stateConfig.invoke.map(lowerWorkflowInvoke) : lowerWorkflowInvoke(stateConfig.invoke) } : {},
227
+ ...stateConfig.on !== void 0 ? { on: Object.fromEntries(Object.entries(stateConfig.on).map(([eventType, transitionConfig]) => [eventType, lowerWorkflowTransitionOrArray(transitionConfig)])) } : {},
228
+ ...stateConfig.always !== void 0 ? { always: lowerWorkflowTransitionOrArray(stateConfig.always) } : {},
229
+ ...stateConfig.onDone !== void 0 ? { onDone: lowerWorkflowTransitionOrArray(stateConfig.onDone) } : {},
230
+ ...stateConfig.after !== void 0 ? { after: Object.fromEntries(Object.entries(stateConfig.after).map(([delay, transitionConfig]) => [delay, lowerWorkflowTransitionOrArray(transitionConfig)])) } : {},
231
+ ...stateConfig.entry !== void 0 ? { entry: lowerWorkflowActions(stateConfig.entry) } : {},
232
+ ...stateConfig.exit !== void 0 ? { exit: lowerWorkflowActions(stateConfig.exit) } : {},
233
+ ...stateConfig.tags !== void 0 ? { tags: stateConfig.tags } : {},
234
+ ...stateConfig.output !== void 0 ? { output: ({ context, event }) => evaluateWorkflowConfigValue(stateConfig.output, {
235
+ context,
236
+ event
237
+ }) } : {},
238
+ ...stateConfig.meta !== void 0 ? { meta: stateConfig.meta } : {}
239
+ };
240
+ }
241
+ function setupAgentFromConfig(config, options) {
242
+ if (!options || typeof options.compileSchema !== "function") throw new Error("setupAgent.fromConfig(...) requires a 'compileSchema' option: { compileSchema: (jsonSchema, name) => StandardSchemaV1 }. Bring your own JSON Schema engine (Ajv, @cfworker/json-schema, a compiled-Zod-from-JSON-Schema pipeline, ...). Core intentionally ships no JSON Schema engine.");
243
+ const { compileSchema } = options;
244
+ const schemas = createSchemasFromWorkflowConfig(config, compileSchema);
245
+ const requestActors = createRequestActors(createRequestsFromWorkflowConfig(config, compileSchema));
246
+ return setupAgent({
247
+ schemas,
248
+ actorSources: {
249
+ ...createActorPlaceholdersFromWorkflowConfig(config),
250
+ ...requestActors
251
+ }
252
+ }).createMachine({
253
+ ...config.id !== void 0 ? { id: config.id } : {},
254
+ ...config.description !== void 0 ? { description: config.description } : {},
255
+ ...config.context !== void 0 ? { context: ({ input }) => validateSchemaSync(schemas.context, evaluateWorkflowConfigValue(config.context, { input })) } : {},
256
+ initial: config.initial,
257
+ states: Object.fromEntries(Object.entries(config.states).map(([key, state]) => [key, lowerWorkflowState(state)])),
258
+ ...config.meta !== void 0 ? { meta: config.meta } : {}
259
+ });
260
+ }
261
+ //#endregion
262
+ //#region src/setup-agent.ts
263
+ /**
264
+ * Builds a machine's {@link AgentSchemaPack} from a partial schema
265
+ * declaration — only `context` is required; `events`/`input`/`output`/`meta`
266
+ * default to empty/unknown schemas when omitted. Pass the result as
267
+ * `setupAgent({ schemas })`'s `schemas` (or spread the individual fields
268
+ * directly into `setupAgent({ context, events, ... })` — both forms are
269
+ * accepted).
270
+ */
271
+ function createAgentSchemas(schemas) {
272
+ return {
273
+ context: schemas.context,
274
+ events: schemas.events ?? {},
275
+ input: schemas.input,
276
+ output: schemas.output,
277
+ meta: schemas.meta,
278
+ emitted: schemas.emitted
279
+ };
280
+ }
281
+ /**
282
+ * Schema-first `setup(...)` for agent machines — the standard entry point
283
+ * for authoring a machine (the blueprint) that this library then runs (via
284
+ * {@link runAgent} or the step helpers) against host-supplied model/decision
285
+ * executors. Context, events, machine input, machine output, and
286
+ * state/transition meta are all standard schemas — no `{} as Type` casts —
287
+ * and are retained on `result.schemas` for runtime validation. Also
288
+ * registers the `agent.generateText`/`agent.streamText`/`agent.userInput`/
289
+ * `agent.decide` builtin actors and lowers `requests`/`actorSources` into the
290
+ * machine's actor sources. The result is the xstate `setup(...)` object with
291
+ * a wrapped `result.createMachine(...)` plus `result.schemas`/`models`/
292
+ * `requests`/`appendMessages` attached. Also has a
293
+ * `setupAgent.fromConfig(...)` namespace member for building a machine from
294
+ * a serializable {@link AgentWorkflowConfig} instead of this TS API.
295
+ *
296
+ * @example
297
+ * ```ts
298
+ * const schemas = createAgentSchemas({
299
+ * context: z.object({ topic: z.string(), joke: z.string().nullable() }),
300
+ * input: z.object({ topic: z.string() }),
301
+ * output: z.object({ joke: z.string() }),
302
+ * });
303
+ *
304
+ * const agent = setupAgent({
305
+ * schemas,
306
+ * actorSources: { tellJoke },
307
+ * });
308
+ *
309
+ * const jokeMachine = agent.createMachine({
310
+ * context: ({ input }) => ({ topic: input.topic, joke: null }),
311
+ * initial: 'telling',
312
+ * states: {
313
+ * telling: {
314
+ * invoke: {
315
+ * id: 'joke',
316
+ * src: 'tellJoke',
317
+ * input: ({ context }) => ({ topic: context.topic }),
318
+ * onDone: ({ output }) => ({ target: 'done', context: { joke: output } }),
319
+ * },
320
+ * },
321
+ * done: { type: 'final', output: ({ context }) => ({ joke: context.joke ?? '' }) },
322
+ * },
323
+ * });
324
+ * ```
325
+ */
326
+ function setupAgent(config) {
327
+ return createSetupAgent(config);
328
+ }
329
+ function collectFinalStateOutputs(states, outputs = []) {
330
+ for (const state of Object.values(states ?? {})) {
331
+ if (state?.type === "final" && state.output !== void 0) outputs.push(state.output);
332
+ collectFinalStateOutputs(state?.states, outputs);
333
+ }
334
+ return outputs;
335
+ }
336
+ function withRootOutputFromSingleFinal(config) {
337
+ if (!config || typeof config !== "object" || "output" in config || !("states" in config)) return config;
338
+ const outputs = collectFinalStateOutputs(config.states);
339
+ return outputs.length === 1 ? {
340
+ ...config,
341
+ output: outputs[0]
342
+ } : config;
343
+ }
344
+ (function(_setupAgent) {
345
+ function fromConfig(config, options) {
346
+ return setupAgentFromConfig(config, options);
347
+ }
348
+ _setupAgent.fromConfig = fromConfig;
349
+ })(setupAgent || (setupAgent = {}));
350
+ /** Builds one TextLogic actor per `setupAgent({ requests })` entry. @internal */
351
+ function createRequestActors(requests) {
352
+ return Object.fromEntries(Object.entries(requests).map(([key, request]) => {
353
+ return [key, createTextLogic({
354
+ ...request,
355
+ name: key,
356
+ mode: request.mode ?? "generate"
357
+ })];
358
+ }));
359
+ }
360
+ function normalizeAgentSchemas(config) {
361
+ return "schemas" in config ? config.schemas : createAgentSchemas(config);
362
+ }
363
+ function normalizeAgentRequestInput(requests) {
364
+ return requests ?? {};
365
+ }
366
+ /**
367
+ * Runtime guard: a key appearing in both `actorSources`/`requests` is almost
368
+ * certainly a mistake (whichever spread applies last would silently win) —
369
+ * fail fast with a clear message rather than let one implementation shadow
370
+ * another.
371
+ */
372
+ function assertNoActorKeyCollisions(actorSources, requests) {
373
+ const seenIn = /* @__PURE__ */ new Map();
374
+ const groups = [["actorSources", actorSources], ["requests", requests]];
375
+ for (const [groupName, group] of groups) for (const key of Object.keys(group ?? {})) {
376
+ const existingGroup = seenIn.get(key);
377
+ if (existingGroup) throw new Error(`setupAgent: key '${key}' is defined in both '${existingGroup}' and '${groupName}'. Each actor source key must be unique across 'actorSources' and 'requests'.`);
378
+ seenIn.set(key, groupName);
379
+ }
380
+ }
381
+ const RESERVED_AGENT_ACTOR_KEYS = [
382
+ ...Object.keys(builtinTextActors),
383
+ USER_INPUT_ACTOR,
384
+ DECIDE_ACTOR,
385
+ PLAN_ACTOR
386
+ ];
387
+ /**
388
+ * Rejects a user-supplied `actorSources`/`requests` key in the reserved
389
+ * `agent.*` builtin namespace. Without this, the builtins-first spread in
390
+ * {@link createAgentActorSources} lets such a key overwrite the builtin
391
+ * (`agent.decide`, `agent.plan`, …) silently. Deliberate override of a builtin
392
+ * is still possible after the machine is created, via
393
+ * `machine.provide({ actorSources: { 'agent.decide': ... } })`.
394
+ */
395
+ function assertNoReservedAgentKeys(actorSources, requests) {
396
+ const groups = [["actorSources", actorSources], ["requests", requests]];
397
+ for (const [groupName, group] of groups) for (const key of Object.keys(group ?? {})) if (RESERVED_AGENT_ACTOR_KEYS.includes(key)) throw new Error(`setupAgent: '${groupName}' key '${key}' is a reserved builtin agent actor and cannot be redefined here (it would silently clobber the builtin). Reserved keys: ${RESERVED_AGENT_ACTOR_KEYS.join(", ")}. To deliberately override a builtin, do it on the created machine instead: machine.provide({ actorSources: { '${key}': ... } }).`);
398
+ }
399
+ function createAgentActorSources(actorSources, requestActors) {
400
+ assertNoActorKeyCollisions(actorSources, requestActors);
401
+ assertNoReservedAgentKeys(actorSources, requestActors);
402
+ return {
403
+ ...builtinTextActors,
404
+ [USER_INPUT_ACTOR]: userInputActor,
405
+ [DECIDE_ACTOR]: createDecideActor(),
406
+ [PLAN_ACTOR]: createPlanActor(),
407
+ ...actorSources,
408
+ ...requestActors
409
+ };
410
+ }
411
+ function createAgentSetupConfig(schemas, actorSources, config) {
412
+ return {
413
+ schemas: {
414
+ context: schemas.context,
415
+ events: schemas.events,
416
+ input: schemas.input,
417
+ output: schemas.output,
418
+ meta: schemas.meta,
419
+ ...schemas.emitted && Object.keys(schemas.emitted).length > 0 ? { emitted: schemas.emitted } : {}
420
+ },
421
+ ...config.states ? { states: config.states } : {},
422
+ actorSources,
423
+ actions: config.actions,
424
+ guards: config.guards,
425
+ delays: config.delays
426
+ };
427
+ }
428
+ function createSetupAgent(config) {
429
+ const schemas = normalizeAgentSchemas(config);
430
+ const requestActors = createRequestActors(normalizeAgentRequestInput(config.requests));
431
+ const actorSources = createAgentActorSources(config.actorSources, requestActors);
432
+ const base = setup(createAgentSetupConfig(schemas, actorSources, config));
433
+ const createBaseMachine = base.createMachine.bind(base);
434
+ const machineOptions = {
435
+ schemas,
436
+ actorSources
437
+ };
438
+ const models = config.models ?? {};
439
+ return Object.assign(base, {
440
+ createMachine(machineConfig) {
441
+ const machine = createBaseMachine(withRootOutputFromSingleFinal(machineConfig));
442
+ agentExecutionOptions.set(machine, machineOptions);
443
+ if (config.isSuspended) {
444
+ const rootConfig = machine.config;
445
+ if (rootConfig) machineSuspensionPredicates.set(rootConfig, config.isSuspended);
446
+ }
447
+ return machine;
448
+ },
449
+ schemas,
450
+ models,
451
+ requests: requestActors,
452
+ appendMessages(resolve) {
453
+ return appendMessages(resolve);
454
+ }
455
+ });
456
+ }
457
+ //#endregion
458
+ //#region src/steps.ts
459
+ /**
460
+ * The step path: durable, per-model-call-checkpoint hosting of an agent
461
+ * machine. Public vocabulary — `initialAgentStep`, `transitionAgentStep`,
462
+ * `resolveAgentStep`, `getAgentRequests`, `executeAgentRequest`,
463
+ * `resolveAgentRequests`.
464
+ * @module
465
+ */
466
+ /**
467
+ * Scans a set of executable actions (as returned by xstate's `transition`/
468
+ * `initialTransition`) for spawned `TextLogic`/`DecisionLogic` invokes and
469
+ * lowers each into an {@link AgentStepRequest}. The hand-passed-schemas
470
+ * implementation detail behind the public {@link getAgentRequests} — it needs
471
+ * `schemas`/`actorSources` passed explicitly, whereas `getAgentRequests`
472
+ * pre-fills them from the machine's registered `setupAgent` options.
473
+ * `options.snapshot` is required to resolve a decision's candidate events
474
+ * (intersecting declared `allowedEvents` with what's currently legal) — omit
475
+ * it and decision requests report an empty `events` list.
476
+ *
477
+ * @internal
478
+ */
479
+ function getAgentRequestsWith(actions, options = {}) {
480
+ return [...actions.flatMap((action) => {
481
+ if (action.type !== "xstate.spawnChild" && action.type !== "@xstate.start") return [];
482
+ const params = action.type === "@xstate.start" ? action : action.params;
483
+ if (!params || typeof params.src !== "string") return [];
484
+ if (typeof params.id !== "string" || params.id.length === 0) throw new Error(`Agent invoke '${params.src}' must define a durable string id.`);
485
+ const registeredLogic = isTextLogic(action.logic) || isDecisionLogic(action.logic) ? action.logic : options.actorSources?.[params.src];
486
+ if (isDecisionLogic(registeredLogic)) {
487
+ const decisionRequest = registeredLogic.request(params.input);
488
+ const allowedEventTypes = registeredLogic.allowedEventTypes?.(params.input);
489
+ const events = options.snapshot ? getAcceptedEvents(options.snapshot, {
490
+ events: options.events,
491
+ schemas: options.schemas,
492
+ eventTypes: allowedEventTypes,
493
+ eventToolName: options.eventToolName
494
+ }) : [];
495
+ return [{
496
+ ...decisionRequest,
497
+ id: params.id,
498
+ events
499
+ }];
500
+ }
501
+ const textLogic = isTextLogic(registeredLogic) ? registeredLogic : void 0;
502
+ const input = textLogic ? textLogic.request(params.input) : void 0;
503
+ if (!input) return [];
504
+ return [{
505
+ kind: "text",
506
+ id: params.id,
507
+ src: params.src,
508
+ ...textLogic ? { mode: textLogic.mode } : {},
509
+ input,
510
+ tools: input.tools ?? {},
511
+ events: []
512
+ }];
513
+ }), ...getActivePlanRequests(options)];
514
+ }
515
+ /**
516
+ * Scans the live snapshot's children for active `agent.plan` (plan-logic)
517
+ * invokes and lowers each into an {@link AgentPlanRequest} — the re-surfacing
518
+ * half of plan discovery. Reads the applied-event trail and remaining budget
519
+ * from the child's own ledger `context` ({@link PlanLedgerContext}), recomputes
520
+ * the currently-legal candidates (∩ declared `allowedEvents`) plus the reserved
521
+ * `agent.plan.done` move, and takes `stepsRemaining` from the ledger (falling
522
+ * back to `maxSteps - applied.length` for a snapshot with no context yet).
523
+ * Returns `[]` when no snapshot is available (candidates need a live snapshot).
524
+ *
525
+ * @internal
526
+ */
527
+ function getActivePlanRequests(options) {
528
+ const snapshot = options.snapshot;
529
+ if (!snapshot) return [];
530
+ const children = snapshot.children;
531
+ if (!children) return [];
532
+ const requests = [];
533
+ for (const [id, child] of Object.entries(children)) {
534
+ const ref = child;
535
+ if (typeof ref?.getSnapshot !== "function") continue;
536
+ const src = typeof ref.src === "string" ? ref.src : void 0;
537
+ const logic = (src ? options.actorSources?.[src] : void 0) ?? ref.logic;
538
+ if (!isPlanLogic(logic)) continue;
539
+ const childSnapshot = ref.getSnapshot();
540
+ if (childSnapshot?.status !== "active") continue;
541
+ const input = childSnapshot.input ?? {};
542
+ const maxSteps = input.maxSteps ?? 8;
543
+ const ledger = childSnapshot.context ?? {};
544
+ const applied = ledger.applied ?? [];
545
+ const stepsRemaining = ledger.stepsRemaining ?? maxSteps - applied.length;
546
+ const machineEvents = getAcceptedEvents(snapshot, {
547
+ events: options.events,
548
+ schemas: options.schemas,
549
+ eventTypes: logic.allowedEventTypes(input) ?? void 0,
550
+ eventToolName: options.eventToolName
551
+ });
552
+ const events = machineEvents.some((event) => event.type === "agent.plan.done") ? machineEvents : [...machineEvents, {
553
+ type: PLAN_DONE_EVENT_TYPE,
554
+ toolName: sanitizeEventToolName(PLAN_DONE_EVENT_TYPE)
555
+ }];
556
+ requests.push({
557
+ kind: "plan",
558
+ id,
559
+ src: src ?? "",
560
+ input,
561
+ events,
562
+ applied,
563
+ stepsRemaining: Math.max(0, stepsRemaining)
564
+ });
565
+ }
566
+ return requests;
567
+ }
568
+ /**
569
+ * Builds the synthetic `xstate.done.actor.<id>` event xstate's `transition()`
570
+ * expects to resolve a spawned invoke — the event {@link resolveAgentStep}
571
+ * applies internally.
572
+ *
573
+ * @internal
574
+ */
575
+ function doneEvent(request, output) {
576
+ return {
577
+ type: `xstate.done.actor.${typeof request === "string" ? request : request.id}`,
578
+ output
579
+ };
580
+ }
581
+ /**
582
+ * Applies a request's `output` as a done event via `transition(...)`,
583
+ * returning the raw `[snapshot, actions]` tuple. Lower-level than
584
+ * {@link resolveAgentStep} — that helper wraps this and also runs
585
+ * {@link getAgentRequests} to produce the next {@link AgentStep}.
586
+ *
587
+ * @internal
588
+ */
589
+ function transitionResult(logic, snapshot, request, output) {
590
+ const event = doneEvent(request, output);
591
+ const result = transition(logic, snapshot, event);
592
+ applyFinalStateOutput(logic, result[0], event);
593
+ return result;
594
+ }
595
+ /**
596
+ * Starts a machine and returns its first {@link AgentStep} — the step-path
597
+ * equivalent of `initialTransition` plus request discovery. Begins the
598
+ * durable/per-model-call-checkpoint loop: resolve each `step.requests` entry
599
+ * (via {@link executeAgentRequest} for `kind: 'text'`, or
600
+ * {@link resolveDecision} for `kind: 'decision'`), then advance with
601
+ * {@link resolveAgentStep} or {@link transitionAgentStep}.
602
+ */
603
+ function initialAgentStep(machine, input, options) {
604
+ const [snapshot, actions] = initialTransition(machine, input);
605
+ return createAgentStep(machine, snapshot, actions, getRegisteredAgentExecutionOptions(machine, options));
606
+ }
607
+ /**
608
+ * Applies an externally-sent event (e.g. a decision's chosen event, or a
609
+ * human's reply) and returns the next {@link AgentStep}. Accepts **either**
610
+ * a raw snapshot **or** a prior `AgentStep` as the second argument —
611
+ * `.snapshot` is unwrapped automatically, so callers can thread the whole
612
+ * step object through without manually plucking the snapshot out.
613
+ */
614
+ function transitionAgentStep(machine, snapshotOrStep, event, options) {
615
+ const [nextSnapshot, actions] = transition(machine, isAgentStep(snapshotOrStep) ? snapshotOrStep.snapshot : snapshotOrStep, event);
616
+ return createAgentStep(machine, nextSnapshot, actions, getRegisteredAgentExecutionOptions(machine, options));
617
+ }
618
+ /**
619
+ * Applies a resolved text request's output (a `kind: 'text'`
620
+ * {@link AgentRequest} — not a decision) as a done event and returns the
621
+ * next {@link AgentStep}. For decisions, resolve with `resolveDecision`
622
+ * (which returns a {@link ChosenEvent}) and apply it with
623
+ * {@link transitionAgentStep} instead — a decision has no output value of
624
+ * its own to feed here.
625
+ */
626
+ function resolveAgentStep(machine, step, request, output, options) {
627
+ const [snapshot, actions] = transitionResult(machine, step.snapshot, request, output);
628
+ return createAgentStep(machine, snapshot, actions, getRegisteredAgentExecutionOptions(machine, options));
629
+ }
630
+ /**
631
+ * Snapshot in, requests out: scans executable actions for spawned agent
632
+ * invokes and lowers each into an {@link AgentStepRequest}, pre-filled with
633
+ * the machine's registered `setupAgent` schemas/actorSources (so callers
634
+ * don't pass them by hand each call) — merged with any `options` passed here,
635
+ * which take precedence. The step path's public discovery primitive;
636
+ * `initialAgentStep`/`transitionAgentStep`/`resolveAgentStep` call it
637
+ * internally to populate `AgentStep.requests`.
638
+ */
639
+ function getAgentRequests(machine, actions, snapshot, options = {}) {
640
+ return getAgentRequestsWith(actions, {
641
+ ...getRegisteredAgentExecutionOptions(machine, options),
642
+ ...options,
643
+ snapshot
644
+ });
645
+ }
646
+ async function executeAgentRequest(request, executors, options) {
647
+ if (request.kind === "decision") throw new Error("executeAgentRequest(...) is text-only. Resolve a 'decision' request with resolveDecision(request, executors.decide, ...) instead.");
648
+ assertTextExecutor(request, executors);
649
+ const { output, raw } = await executeAgentTextRequest(request.mode ?? "generate", request.id, request.input, executors, request.tools);
650
+ const normalizedOutput = request.input.outputSchema ? validateSchemaSync(request.input.outputSchema, output) : output;
651
+ return options?.verbose ? {
652
+ output: normalizedOutput,
653
+ raw
654
+ } : normalizedOutput;
655
+ }
656
+ /**
657
+ * Resolves the current step's pending requests and returns the next
658
+ * {@link AgentStep} — one iteration of the durable step loop, collapsing the
659
+ * manual `request.kind` dispatch a host would otherwise write by hand.
660
+ *
661
+ * For each pending request, in order: a `kind: 'text'` request is run with
662
+ * {@link executeAgentRequest} then fed back via {@link resolveAgentStep}; a
663
+ * `kind: 'decision'` request is resolved with `resolveDecision` (wiring
664
+ * `canTake` to `step.snapshot.can` so guard-rejected choices retry) then
665
+ * applied with {@link transitionAgentStep}. The **current** step is re-read
666
+ * after each application — the machine may advance and its `requests` change —
667
+ * so this always resolves against the live step, never a stale list.
668
+ *
669
+ * A `kind: 'plan'` request (`agent.plan`) is resolved natively too: one plan
670
+ * step per call. It resolves a single decision from `request.events` (wiring
671
+ * `canTake` to `step.snapshot.can`, exempting the reserved `agent.plan.done`
672
+ * move and `stopOn` events), then either applies the chosen machine event and
673
+ * lets the next step re-surface the plan, or completes the plan (feeding its
674
+ * `{ steps, stopped }` output back) on the done move / a `stopOn` event / an
675
+ * exhausted budget / no legal events. The plan's applied trail is carried in
676
+ * the invoke child's snapshot, so persisting the step between calls resumes the
677
+ * plan identically.
678
+ *
679
+ * Missing the executor a request needs throws a clear error
680
+ * (`generateText`/`streamText` for text, `decide` for decisions and plans).
681
+ *
682
+ * A complete durable host is two lines:
683
+ *
684
+ * ```ts
685
+ * let step = initialAgentStep(machine, input);
686
+ * while (!step.done) step = await resolveAgentRequests(machine, step, executors);
687
+ * ```
688
+ *
689
+ * All pending **text** requests of a step are resolved in parallel
690
+ * (`Promise.all`) — parallel statechart regions are genuinely concurrent, so
691
+ * their model calls run concurrently — then their outputs apply in
692
+ * **request-array order** (deterministic for durable replay regardless of which
693
+ * call finishes first). Decisions and plans stay **one at a time**: applying
694
+ * either changes the set of legal candidates for what follows, so they cannot be
695
+ * resolved against a stale snapshot. A host that instead wants strictly
696
+ * sequential text resolution loops the manual per-request helpers
697
+ * ({@link executeAgentRequest} + {@link resolveAgentStep}) one at a time.
698
+ */
699
+ async function resolveAgentRequests(machine, step, executors, options) {
700
+ const [request] = step.requests;
701
+ if (!request) return step;
702
+ if (request.kind === "decision") {
703
+ if (!executors.decide) throw new Error(`this step's decision request '${request.id}' needs a 'decide' executor but none was provided.`);
704
+ return transitionAgentStep(machine, step, await resolveDecision(request, executors.decide, {
705
+ canTake: (event) => step.snapshot.can(event),
706
+ maxRetries: options?.maxRetries
707
+ }), options);
708
+ }
709
+ if (request.kind === "plan") return resolvePlanRequest(machine, step, request, executors, options);
710
+ const textRequests = step.requests.filter((candidate) => candidate.kind === "text");
711
+ for (const textRequest of textRequests) assertTextExecutor(textRequest, executors);
712
+ const outputs = await Promise.all(textRequests.map((textRequest) => executeAgentRequest(textRequest, executors)));
713
+ let next = step;
714
+ for (let index = 0; index < textRequests.length; index++) next = resolveAgentStep(machine, next, textRequests[index], outputs[index], options);
715
+ return next;
716
+ }
717
+ function assertTextExecutor(request, executors) {
718
+ const mode = request.mode ?? "generate";
719
+ const kind = mode === "stream" ? "streamText" : "generateText";
720
+ 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.`);
721
+ }
722
+ async function resolvePlanRequest(machine, step, request, executors, options) {
723
+ if (!executors.decide) throw new Error(`this step's plan request '${request.src}' needs a 'decide' executor but none was provided.`);
724
+ const stopOn = new Set(request.input.stopOn ?? []);
725
+ if (request.stepsRemaining <= 0) return completePlan(machine, step, request.id, request.applied, "max-steps", options);
726
+ if (request.events.filter((event) => event.type !== "agent.plan.done").length === 0) return completePlan(machine, step, request.id, request.applied, "no-legal-events", options);
727
+ const chosen = await resolveDecision(planStepDecisionRequest(request), executors.decide, {
728
+ maxRetries: options?.maxRetries,
729
+ canTake: (event) => {
730
+ if (event.type === "agent.plan.done" || stopOn.has(event.type)) return true;
731
+ return step.snapshot.can(event);
732
+ }
733
+ });
734
+ if (chosen.type === "agent.plan.done") return completePlan(machine, step, request.id, request.applied, "done", options);
735
+ const applied = [...request.applied, chosen];
736
+ advancePlanChildLedger(step.snapshot, request.id, {
737
+ type: "plan.applied",
738
+ event: chosen
739
+ });
740
+ const next = transitionAgentStep(machine, step, chosen, options);
741
+ if (stopOn.has(chosen.type)) {
742
+ if (isPlanActive(next.snapshot, request.id)) return completePlan(machine, next, request.id, applied, "stop-event", options);
743
+ }
744
+ return next;
745
+ }
746
+ function completePlan(machine, step, id, steps, stopped, options) {
747
+ return resolveAgentStep(machine, step, { id }, {
748
+ steps,
749
+ stopped
750
+ }, options);
751
+ }
752
+ function planStepDecisionRequest(request) {
753
+ const { input, applied, events, id } = request;
754
+ const trail = applied.length === 0 ? "" : `\n\nEvents already applied in this plan, in order:\n${applied.map((step) => JSON.stringify(step)).join("\n")}\nContinue from here; do not repeat applied events.`;
755
+ const doneHint = `\n\nWhen the request is fully handled (or no action is needed), choose '${PLAN_DONE_EVENT_TYPE}'.`;
756
+ return {
757
+ kind: "decision",
758
+ id: `${id}[${applied.length}]`,
759
+ model: input.model,
760
+ system: input.system,
761
+ prompt: `${input.prompt ?? ""}${trail}${doneHint}`,
762
+ messages: input.messages,
763
+ events,
764
+ attempts: [],
765
+ temperature: input.temperature,
766
+ maxOutputTokens: input.maxOutputTokens,
767
+ topP: input.topP,
768
+ topK: input.topK,
769
+ seed: input.seed,
770
+ stopSequences: input.stopSequences,
771
+ metadata: input.metadata
772
+ };
773
+ }
774
+ function advancePlanChildLedger(snapshot, id, event) {
775
+ const child = snapshot.children?.[id];
776
+ const childSnapshot = child?.getSnapshot?.();
777
+ if (!isPlanLogic(child?.logic) || !childSnapshot || typeof childSnapshot !== "object") return;
778
+ Object.assign(childSnapshot, advancePlanLedger(child.logic, childSnapshot, event));
779
+ }
780
+ function isPlanActive(snapshot, id) {
781
+ return (snapshot.children?.[id])?.getSnapshot?.()?.status === "active";
782
+ }
783
+ function createAgentStep(machine, snapshot, actions, options) {
784
+ applyFinalStateOutput(machine, snapshot);
785
+ return {
786
+ snapshot,
787
+ actions,
788
+ requests: getAgentRequestsWith(actions, {
789
+ ...options,
790
+ snapshot
791
+ }),
792
+ done: snapshot.status === "done"
793
+ };
794
+ }
795
+ function resolveStateValueConfig(config, value) {
796
+ if (typeof value === "string") return config.states?.[value];
797
+ if (!value || typeof value !== "object") return;
798
+ for (const [key, childValue] of Object.entries(value)) {
799
+ const childConfig = config.states?.[key];
800
+ if (!childConfig) continue;
801
+ if (childConfig.type === "final") return childConfig;
802
+ const nested = resolveStateValueConfig(childConfig, childValue);
803
+ if (nested) return nested;
804
+ }
805
+ }
806
+ function applyFinalStateOutput(logic, snapshot, event) {
807
+ const machineSnapshot = snapshot;
808
+ if (machineSnapshot.status !== "done" || machineSnapshot.output !== void 0 || !("config" in logic)) return;
809
+ const config = logic.config;
810
+ if (!config) return;
811
+ const output = resolveStateValueConfig(config, machineSnapshot.value)?.output;
812
+ if (output === void 0) return;
813
+ machineSnapshot.output = typeof output === "function" ? output({
814
+ context: machineSnapshot.context,
815
+ event
816
+ }) : output;
817
+ }
818
+ function isAgentStep(value) {
819
+ return !!value && typeof value === "object" && "snapshot" in value && "actions" in value && "requests" in value;
820
+ }
821
+ //#endregion
822
+ //#region src/internal/state-request-pass.ts
823
+ async function runTextPhase(stateRequest, baseMessages, deps) {
824
+ const { model, system } = stateRequest;
825
+ const id = stateRequest.id ?? deps.nextRequestId();
826
+ const promptMessage = userMessage(stateRequest.prompt);
827
+ if (stateRequest.kind === "decision") return {
828
+ stateRequest,
829
+ id,
830
+ output: void 0,
831
+ appended: [promptMessage]
832
+ };
833
+ if (!deps.generateText) throw new Error("runAgent: a getRequests request needs a 'generateText' executor (or use kind: 'decision').");
834
+ const request = {
835
+ model,
836
+ ...system !== void 0 ? { system } : {},
837
+ messages: [...baseMessages, promptMessage],
838
+ tools: {}
839
+ };
840
+ const agentRequest = {
841
+ kind: "text",
842
+ id,
843
+ src: "agent.interpret",
844
+ mode: "generate",
845
+ input: request,
846
+ tools: {},
847
+ events: []
848
+ };
849
+ deps.consumeModelCall();
850
+ deps.onTrace?.({
851
+ type: "request.start",
852
+ request: agentRequest
853
+ });
854
+ let output;
855
+ try {
856
+ const raw = await deps.generateText(request, { signal: deps.signal });
857
+ output = await normalizeGeneratorResult(raw, id, { request });
858
+ deps.onResult?.(agentRequest, {
859
+ output,
860
+ raw
861
+ });
862
+ deps.onTrace?.({
863
+ type: "request.end",
864
+ request: agentRequest,
865
+ output,
866
+ raw
867
+ });
868
+ } catch (error) {
869
+ deps.onTrace?.({
870
+ type: "request.error",
871
+ request: agentRequest,
872
+ error
873
+ });
874
+ throw error;
875
+ }
876
+ return {
877
+ stateRequest,
878
+ id,
879
+ output,
880
+ appended: [promptMessage, assistantMessage(typeof output === "string" ? output : JSON.stringify(output))]
881
+ };
882
+ }
883
+ async function runAdvancePhase(plan, deps) {
884
+ const { stateRequest, id, output } = plan;
885
+ const { model, system } = stateRequest;
886
+ deps.appendToLog(...plan.appended);
887
+ if (stateRequest.kind !== "decision" && stateRequest.onDone !== void 0) {
888
+ const snapshot = deps.getSnapshot();
889
+ const resolved = typeof stateRequest.onDone === "function" ? stateRequest.onDone({
890
+ output,
891
+ snapshot,
892
+ messages: deps.messages
893
+ }) : stateRequest.onDone;
894
+ if (!resolved) return false;
895
+ const acceptedTypes = getAcceptedEvents(snapshot, { schemas: deps.schemas }).map((descriptor) => descriptor.type);
896
+ if (!acceptedTypes.includes(resolved.type)) throw new Error(`runAgent: getRequests request '${id}' resolved onDone to event '${resolved.type}', which the current state does not accept. Accepted: ${acceptedTypes.join(", ") || "(none)"}.`);
897
+ if (!snapshot.can(resolved)) return false;
898
+ deps.send(resolved);
899
+ return true;
900
+ }
901
+ const events = getAcceptedEvents(deps.getSnapshot(), {
902
+ schemas: deps.schemas,
903
+ ...stateRequest.allowedEvents ? { eventTypes: stateRequest.allowedEvents } : {}
904
+ });
905
+ if (events.length === 0) return false;
906
+ if (!deps.decide) throw new Error(`runAgent: a getRequests request without 'onDone' needs a 'decide' executor to choose between events ${events.map((descriptor) => `'${descriptor.type}'`).join(", ")}. Provide request.onDone for deterministic advancement, or a 'decide' executor.`);
907
+ const chosen = await resolveDecision({
908
+ kind: "decision",
909
+ id,
910
+ model,
911
+ ...system !== void 0 ? { system } : {},
912
+ messages: [...deps.messages],
913
+ events,
914
+ attempts: []
915
+ }, deps.decide, {
916
+ signal: deps.signal,
917
+ canTake: (event) => deps.getSnapshot().can(event)
918
+ });
919
+ deps.appendToLog(assistantMessage(`[chose: ${chosen.type}]`));
920
+ if (deps.isSettled()) return false;
921
+ deps.send(chosen);
922
+ return true;
923
+ }
924
+ /**
925
+ * Executes one getRequests pass: all text calls concurrently against the
926
+ * pass-start log, then per-request advancement sequentially in request
927
+ * order. Returns whether ANY request sent an event — the host settles idle
928
+ * when none did (otherwise an unchanged snapshot would re-produce the same
929
+ * pass forever). Stops early (skipping remaining appends/sends) once
930
+ * {@link StateRequestPassDeps.isSettled} reports the run is over. Throws on
931
+ * executor/`onDone` errors — the host maps them to its error settle.
932
+ */
933
+ async function runStateRequestPass(requests, deps) {
934
+ const baseMessages = [...deps.messages];
935
+ const plans = await Promise.all(requests.map((stateRequest) => runTextPhase(stateRequest, baseMessages, deps)));
936
+ let sentAny = false;
937
+ for (const plan of plans) {
938
+ if (deps.isSettled()) break;
939
+ sentAny = await runAdvancePhase(plan, deps) || sentAny;
940
+ }
941
+ return { sentAny };
942
+ }
943
+ //#endregion
944
+ //#region src/run-agent.ts
945
+ /**
946
+ * Thrown by {@link runAgent} when resuming with a `snapshot` + `event` whose
947
+ * `type` the restored state cannot accept (a type-level check via
948
+ * {@link getAcceptedEvents}). A programmer/integration error, in the same
949
+ * class as runAgent's bind-time throws — it throws rather than settling an
950
+ * `error` result. A type-legal event a guard rejects is NOT this error (the
951
+ * machine simply takes no transition). Opt out with
952
+ * {@link RunAgentOptions.onIllegalResumeEvent} `'ignore'`.
953
+ */
954
+ var IllegalResumeEventError = class extends Error {
955
+ eventType;
956
+ acceptedTypes;
957
+ constructor(eventType, acceptedTypes) {
958
+ super(`runAgent: cannot resume with event '${eventType}' — the restored state does not accept it. Accepted event types: ${acceptedTypes.length > 0 ? acceptedTypes.join(", ") : "(none)"}.`);
959
+ this.name = "IllegalResumeEventError";
960
+ this.eventType = eventType;
961
+ this.acceptedTypes = acceptedTypes;
962
+ }
963
+ };
964
+ /**
965
+ * Thrown by {@link runAgent} when resuming from a `snapshot` whose stamped
966
+ * `agentMeta.version` differs from the current machine's version, under the
967
+ * default `onVersionMismatch: 'throw'` and with no `migrateSnapshot` hook. The
968
+ * structural fingerprint of the machine changed since the snapshot was
969
+ * persisted (a state/transition/invoke was added, removed, or retargeted), so
970
+ * the snapshot may no longer resume cleanly. `from` is the snapshot's version,
971
+ * `to` the current machine's.
972
+ */
973
+ var SnapshotVersionMismatchError = class extends Error {
974
+ from;
975
+ to;
976
+ machineId;
977
+ constructor(from, to, machineId) {
978
+ super(`runAgent: cannot resume snapshot stamped with machine version '${from}' against machine '${machineId}' at version '${to}' — the machine's structure changed since the snapshot was persisted. Provide options.migrateSnapshot to adapt it, or set options.onVersionMismatch to 'warn'/'ignore' to proceed anyway.`);
979
+ this.name = "SnapshotVersionMismatchError";
980
+ this.from = from;
981
+ this.to = to;
982
+ this.machineId = machineId;
983
+ }
984
+ };
985
+ /**
986
+ * Thrown by {@link runAgentToCompletion} when the run settles `idle` instead of
987
+ * `done`: the machine paused for external input. Carries the idle `snapshot`
988
+ * and `acceptedTypes` (the event types that could resume it, via
989
+ * {@link getAcceptedEvents}). Use {@link runAgent} directly when idle is an
990
+ * expected outcome you handle.
991
+ */
992
+ var AgentIdleError = class extends Error {
993
+ snapshot;
994
+ acceptedTypes;
995
+ constructor(snapshot, acceptedTypes) {
996
+ super(`runAgentToCompletion: the machine paused (idle) instead of completing. Resume it by calling runAgent with one of these events: ${acceptedTypes.length > 0 ? acceptedTypes.join(", ") : "(none)"}.`);
997
+ this.name = "AgentIdleError";
998
+ this.snapshot = snapshot;
999
+ this.acceptedTypes = acceptedTypes;
1000
+ }
1001
+ };
1002
+ let nextRunAgentTraceId = 1;
1003
+ var MaxModelCallsExceededError = class extends Error {
1004
+ constructor() {
1005
+ super("runAgent exceeded maxModelCalls.");
1006
+ this.name = "MaxModelCallsExceededError";
1007
+ }
1008
+ };
1009
+ function wrapsDecisionExhausted(error) {
1010
+ let current = error;
1011
+ for (let depth = 0; depth < 10 && current != null; depth++) {
1012
+ if (current instanceof DecisionExhaustedError) return true;
1013
+ current = current.cause;
1014
+ }
1015
+ return false;
1016
+ }
1017
+ /**
1018
+ * Recursively collects every invoke's `src` from raw machine config (spike
1019
+ * S6: `machine.config` preserves authored srcs; the built `machine.root`
1020
+ * normalizes object srcs to synthetic string ids and loses the distinction
1021
+ * this walk needs). Function-valued `src` resolvers are dynamic and are not
1022
+ * statically analyzable, so they are skipped (pass-through, like any other
1023
+ * non-agent actor).
1024
+ */
1025
+ function collectConfiguredInvokeSrcs(stateConfig, stateName, out) {
1026
+ if (!stateConfig) return;
1027
+ const invokes = stateConfig.invoke === void 0 ? [] : Array.isArray(stateConfig.invoke) ? stateConfig.invoke : [stateConfig.invoke];
1028
+ for (const invokeConfig of invokes) {
1029
+ const src = invokeConfig?.src;
1030
+ if (typeof src === "string" || src && typeof src === "object") out.push({
1031
+ stateName,
1032
+ src
1033
+ });
1034
+ }
1035
+ for (const [childName, childConfig] of Object.entries(stateConfig.states ?? {})) collectConfiguredInvokeSrcs(childConfig, `${stateName}.${childName}`, out);
1036
+ }
1037
+ /**
1038
+ * Duck-types a state machine actor logic (an invoked child machine) vs. any
1039
+ * other actor logic. xstate's `StateMachine` carries `.config`, `.root`, and
1040
+ * a `.provide(...)` method plus an `implementations.actorSources` map — this
1041
+ * combination is unique to machines and survives the dual-package/version
1042
+ * boundary an `instanceof` check would not. Used to descend the bind-time
1043
+ * walk into invoked child machines (their internal agent requests are opaque
1044
+ * to the parent-level source walk otherwise).
1045
+ */
1046
+ function isStateMachine(logic) {
1047
+ return !!logic && typeof logic === "object" && "config" in logic && "root" in logic && typeof logic.provide === "function" && typeof logic.implementations === "object" && !!logic.implementations?.actorSources;
1048
+ }
1049
+ /**
1050
+ * Fails fast (throws) at bind time — before any actor runs — when the
1051
+ * machine invokes an agent actor `runAgent` cannot execute. See §3.2 point 2.
1052
+ *
1053
+ * Recurses into invoked child state machines (arbitrarily deep). A child
1054
+ * machine's agent requests reached through string-keyed invoke srcs DO inherit
1055
+ * the parent runAgent's `generateText`/`streamText`/`decide` executors —
1056
+ * runAgent rebinds them with the same host-backed wrappers (see
1057
+ * {@link rebindChildMachine}) — so the only remaining bind-time errors are: a
1058
+ * required executor kind missing entirely (naming the invoke chain and src),
1059
+ * and an unbound request reached through a direct-object invoke src that can't
1060
+ * be rebound ({@link unrebindableChildRequestError}). A request that carries
1061
+ * its own executor (`.withExecutor(...)`, tracked in `executorBoundLogics`)
1062
+ * always runs itself; explicit binding shadows inheritance.
1063
+ */
1064
+ function assertBindable(machine, effectiveSources, options) {
1065
+ assertMachineBindable(machine, effectiveSources, options, {
1066
+ isChild: false,
1067
+ childPath: "",
1068
+ rebindable: true,
1069
+ visited: new Set([machine])
1070
+ });
1071
+ }
1072
+ function assertMachineBindable(machine, effectiveSources, options, ctx) {
1073
+ const invokes = [];
1074
+ collectConfiguredInvokeSrcs(machine.config, machine.config.id ?? "(root)", invokes);
1075
+ const where = ctx.isChild ? `child machine '${ctx.childPath}' state` : "state";
1076
+ for (const { stateName, src } of invokes) {
1077
+ if (typeof src !== "string") {
1078
+ if (isStateMachine(src)) {
1079
+ assertChildMachineBindable(src, src, stateName, options, ctx);
1080
+ continue;
1081
+ }
1082
+ if ((isTextLogic(src) || isDecisionLogic(src) || isPlanLogic(src)) && !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({ actorSources: { name: logic } })) and invoke it by name.`);
1083
+ continue;
1084
+ }
1085
+ const logic = effectiveSources[src];
1086
+ if (logic === void 0) throw new Error(`runAgent: ${where} '${stateName}' invokes unregistered actor source '${src}'. Provide it via machine.provide({ actorSources: { '${src}': ... } }) or runAgent(machine, { actorSources: { '${src}': ... } }).`);
1087
+ if (isStateMachine(logic)) {
1088
+ assertChildMachineBindable(logic, src, stateName, options, ctx);
1089
+ continue;
1090
+ }
1091
+ if (src === "agent.userInput") continue;
1092
+ if (isDecisionLogic(logic)) {
1093
+ if (executorBoundLogics.has(logic)) continue;
1094
+ if (!ctx.rebindable) throw unrebindableChildRequestError(ctx.childPath, stateName, src, "decision");
1095
+ if (!options.hasDecide) throw new Error(`runAgent: ${where} '${stateName}' invokes decision source '${src}' but no 'decide' executor was provided to runAgent(...).`);
1096
+ continue;
1097
+ }
1098
+ if (isPlanLogic(logic)) {
1099
+ if (!ctx.rebindable) throw unrebindableChildRequestError(ctx.childPath, stateName, src, "plan");
1100
+ if (!options.hasDecide) throw new Error(`runAgent: ${where} '${stateName}' invokes plan source '${src}' but no 'decide' executor was provided to runAgent(...).`);
1101
+ continue;
1102
+ }
1103
+ if (isTextLogic(logic)) {
1104
+ if (executorBoundLogics.has(logic)) continue;
1105
+ if (!ctx.rebindable) throw unrebindableChildRequestError(ctx.childPath, stateName, src, logic.mode === "stream" ? "streaming text" : "text");
1106
+ if (logic.mode === "stream" && !options.hasStreamText) throw new Error(`runAgent: ${where} '${stateName}' invokes streaming text source '${src}' but no 'streamText' executor was provided to runAgent(...).`);
1107
+ if (logic.mode !== "stream" && !options.hasGenerateText) throw new Error(`runAgent: ${where} '${stateName}' invokes text source '${src}' but no 'generateText' executor was provided to runAgent(...).`);
1108
+ continue;
1109
+ }
1110
+ if (isUnboundPlaceholder(logic)) throw new Error(`runAgent: ${where} '${stateName}' invokes actor source '${src}', which has no host execution. Provide it via machine.provide({ actorSources: { '${src}': ... } }) or runAgent(machine, { actorSources: { '${src}': ... } }).`);
1111
+ }
1112
+ }
1113
+ /** Descends the bind-time walk into an invoked child state machine, guarding
1114
+ * against a machine that (transitively) invokes itself. */
1115
+ function assertChildMachineBindable(childMachine, childSrc, stateName, options, ctx) {
1116
+ if (ctx.visited.has(childMachine)) return;
1117
+ const childName = typeof childSrc === "string" ? childSrc : childMachine.config.id ?? "(child machine)";
1118
+ const childPath = ctx.childPath ? `${ctx.childPath} > ${childName}` : childName;
1119
+ const childSources = childMachine.implementations.actorSources;
1120
+ assertMachineBindable(childMachine, childSources, options, {
1121
+ isChild: true,
1122
+ childPath,
1123
+ rebindable: ctx.rebindable && typeof childSrc === "string",
1124
+ visited: new Set([...ctx.visited, childMachine])
1125
+ });
1126
+ }
1127
+ /** The loud bind-time error for an unbound agent request reached under a
1128
+ * direct-object invoke src, which runAgent cannot rebind (only string-keyed
1129
+ * sources can be swapped via `.provide`). Names the invoke chain AND the
1130
+ * request src, and spells out the `.withExecutor`/string-keyed remedy. Note:
1131
+ * requests reachable through string-keyed srcs at any depth DO inherit
1132
+ * runAgent's executors — this error is only for the unrebindable direct-object
1133
+ * case. */
1134
+ function unrebindableChildRequestError(childPath, stateName, requestSrc, kind) {
1135
+ 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({ actorSources: { <child>: childMachine } })) and invoke it by name.`);
1136
+ }
1137
+ /** Reads the durable invoke id/src off the async actor's own ref (`self`). */
1138
+ function selfIdAndSrc(self) {
1139
+ const ref = self;
1140
+ return {
1141
+ id: typeof ref?.id === "string" ? ref.id : "",
1142
+ src: typeof ref?.src === "string" ? ref.src : ""
1143
+ };
1144
+ }
1145
+ /**
1146
+ * The machine actor that INVOKED a decision/plan request — the actor whose
1147
+ * live snapshot supplies the candidate events and drives `canTake`/`send`.
1148
+ * For a top-level request this is the root actor (identity-equal to
1149
+ * `runCtx.actorHolder.actorRef`); for a request inside an invoked child
1150
+ * machine it is that child's actor, so a child decision/plan reads and drives
1151
+ * the CHILD's snapshot — not the root's. Read off `self._parent`, with the
1152
+ * root actor as a fallback.
1153
+ */
1154
+ function invokingActorOf(self, runCtx) {
1155
+ return self?._parent ?? runCtx.actorHolder.actorRef;
1156
+ }
1157
+ function wrapTextLogicForRunAgent(logic, runCtx) {
1158
+ return logic.withExecutor(async ({ request, self, signal }) => {
1159
+ const { id, src } = selfIdAndSrc(self);
1160
+ const executor = logic.mode === "stream" ? runCtx.streamText : runCtx.generateText;
1161
+ if (!executor) throw new Error(`runAgent: no '${logic.mode === "stream" ? "streamText" : "generateText"}' executor provided.`);
1162
+ const requestWithTools = {
1163
+ ...request,
1164
+ tools: request.tools ?? {}
1165
+ };
1166
+ const agentRequest = {
1167
+ kind: "text",
1168
+ id,
1169
+ src,
1170
+ mode: logic.mode,
1171
+ input: request,
1172
+ tools: requestWithTools.tools,
1173
+ events: []
1174
+ };
1175
+ runCtx.consumeModelCall();
1176
+ runCtx.onTrace?.({
1177
+ type: "request.start",
1178
+ request: agentRequest
1179
+ });
1180
+ try {
1181
+ const raw = await executor(requestWithTools, {
1182
+ onChunk: (chunk) => {
1183
+ runCtx.onTrace?.({
1184
+ type: "stream.chunk",
1185
+ request: agentRequest,
1186
+ chunk
1187
+ });
1188
+ runCtx.onChunk?.(chunk, { request: agentRequest });
1189
+ },
1190
+ signal
1191
+ });
1192
+ const output = await normalizeGeneratorResult(raw, id, {
1193
+ request,
1194
+ onChunk: (chunk) => {
1195
+ runCtx.onTrace?.({
1196
+ type: "stream.chunk",
1197
+ request: agentRequest,
1198
+ chunk
1199
+ });
1200
+ runCtx.onChunk?.(chunk, { request: agentRequest });
1201
+ }
1202
+ });
1203
+ const rawReasoning = raw?.reasoning;
1204
+ const reasoning = typeof rawReasoning === "string" ? rawReasoning : void 0;
1205
+ runCtx.onResult?.(agentRequest, {
1206
+ output,
1207
+ raw
1208
+ });
1209
+ runCtx.onTrace?.({
1210
+ type: "request.end",
1211
+ request: agentRequest,
1212
+ output,
1213
+ raw,
1214
+ ...reasoning !== void 0 ? { reasoning } : {}
1215
+ });
1216
+ return { output };
1217
+ } catch (error) {
1218
+ runCtx.onTrace?.({
1219
+ type: "request.error",
1220
+ request: agentRequest,
1221
+ error
1222
+ });
1223
+ throw error;
1224
+ }
1225
+ });
1226
+ }
1227
+ function createCountingDecide(runCtx) {
1228
+ return async (attemptRequest) => {
1229
+ runCtx.consumeModelCall();
1230
+ runCtx.onTrace?.({
1231
+ type: "request.start",
1232
+ request: attemptRequest
1233
+ });
1234
+ try {
1235
+ const result = await runCtx.decide(attemptRequest);
1236
+ runCtx.onResult?.(attemptRequest, {
1237
+ output: result.event,
1238
+ raw: result
1239
+ });
1240
+ runCtx.onTrace?.({
1241
+ type: "request.end",
1242
+ request: attemptRequest,
1243
+ output: result.event,
1244
+ raw: result
1245
+ });
1246
+ return result;
1247
+ } catch (error) {
1248
+ runCtx.onTrace?.({
1249
+ type: "request.error",
1250
+ request: attemptRequest,
1251
+ error
1252
+ });
1253
+ throw error;
1254
+ }
1255
+ };
1256
+ }
1257
+ /**
1258
+ * Builds the decision actor logic runAgent installs in place of a
1259
+ * `DecisionLogic`/`agent.decide` source. `DecisionLogic.withExecutor(...)`
1260
+ * can only swap the innermost per-attempt executor — the `resolveDecision(...)`
1261
+ * call (and its `canTake`) is hardwired inside the original logic's `run`.
1262
+ * To supply `canTake` (mode-3, §2.6), runAgent instead builds a fresh async
1263
+ * logic here that calls `resolveDecision` itself, reusing `logic.request(...)`
1264
+ * to build the request the same way the original logic would have.
1265
+ *
1266
+ * On success it SENDS the chosen event to the invoking actor (auto-delivery,
1267
+ * mirroring {@link createRunAgentPlanLogic}) and then completes with that event
1268
+ * as its output — so callers never wire an `onDone` to deliver it. See the
1269
+ * send-then-complete note inside `run` for how exit-cancels-invoke interacts
1270
+ * with `onDone`.
1271
+ */
1272
+ function createRunAgentDecisionLogic(logic, runCtx) {
1273
+ const decisionLogic = createAsyncLogic({ run: async ({ input, signal, self }) => {
1274
+ if (!runCtx.decide) throw new Error("runAgent: no 'decide' executor provided.");
1275
+ const { id } = selfIdAndSrc(self);
1276
+ const declaredEventTypes = logic.allowedEventTypes?.(input);
1277
+ await Promise.resolve();
1278
+ const actorRef = invokingActorOf(self, runCtx);
1279
+ const events = actorRef ? getAcceptedEvents(actorRef.getSnapshot(), {
1280
+ schemas: runCtx.schemas,
1281
+ eventTypes: declaredEventTypes
1282
+ }) : [];
1283
+ const chosen = await resolveDecision({
1284
+ ...logic.request(input),
1285
+ id,
1286
+ events
1287
+ }, createCountingDecide(runCtx), {
1288
+ maxRetries: logic.maxRetries,
1289
+ signal,
1290
+ canTake: (event) => actorRef ? actorRef.getSnapshot().can(event) : true
1291
+ });
1292
+ actorRef?.send(chosen);
1293
+ await Promise.resolve();
1294
+ return chosen;
1295
+ } });
1296
+ return Object.assign(decisionLogic, {
1297
+ kind: "statelyai.decisionLogic",
1298
+ maxRetries: logic.maxRetries,
1299
+ request: logic.request,
1300
+ withExecutor: (nextExecute) => createRunAgentDecisionLogic(logic.withExecutor(nextExecute), runCtx)
1301
+ });
1302
+ }
1303
+ /**
1304
+ * Builds the plan actor logic runAgent installs in place of the `agent.plan`
1305
+ * builtin: iterated {@link resolveDecision}. Each step re-reads the live
1306
+ * snapshot (so the candidate set reflects everything applied so far), asks
1307
+ * the `decide` executor for one legal event with the same validation/retry
1308
+ * loop a decision gets, and sends it to the machine. The loop ends on a
1309
+ * `stopOn` event, at `maxSteps`, when no legal candidate remains, or when an
1310
+ * applied event exits the invoking state (xstate cancels this invoke — the
1311
+ * machine simply moves on and the pending output is discarded).
1312
+ *
1313
+ * The invoke's `input` is resolved once, so the prompt cannot re-render
1314
+ * context between steps; instead the applied trail is appended to the prompt
1315
+ * each step so the model can see plan progress.
1316
+ */
1317
+ function createRunAgentPlanLogic(logic, runCtx) {
1318
+ const planLogic = createAsyncLogic({ run: async ({ input, signal, self }) => {
1319
+ if (!runCtx.decide) throw new Error("runAgent: no 'decide' executor provided.");
1320
+ const { id } = selfIdAndSrc(self);
1321
+ const stopOn = new Set(input.stopOn ?? []);
1322
+ const countingDecide = createCountingDecide(runCtx);
1323
+ const base = logic.request(input);
1324
+ let ledger = initialPlanLedger(logic, input);
1325
+ const end = (stopped) => {
1326
+ ledger = advancePlanLedger(logic, ledger, {
1327
+ type: "plan.ended",
1328
+ stopped
1329
+ });
1330
+ return ledger.output;
1331
+ };
1332
+ await Promise.resolve();
1333
+ const invokingActor = invokingActorOf(self, runCtx);
1334
+ while (ledger.context.stepsRemaining > 0) {
1335
+ const actorRef = invokingActor;
1336
+ if (!actorRef || signal.aborted) break;
1337
+ const machineEvents = getAcceptedEvents(actorRef.getSnapshot(), {
1338
+ schemas: runCtx.schemas,
1339
+ eventTypes: logic.allowedEventTypes(input)
1340
+ });
1341
+ if (machineEvents.length === 0) return end("no-legal-events");
1342
+ const doneDescriptor = {
1343
+ type: PLAN_DONE_EVENT_TYPE,
1344
+ toolName: sanitizeEventToolName(PLAN_DONE_EVENT_TYPE)
1345
+ };
1346
+ const events = machineEvents.some((event) => event.type === "agent.plan.done") ? machineEvents : [...machineEvents, doneDescriptor];
1347
+ const applied = ledger.context.applied;
1348
+ const trail = applied.length === 0 ? "" : `\n\nEvents already applied in this plan, in order:\n${applied.map((step) => JSON.stringify(step)).join("\n")}\nContinue from here; do not repeat applied events.`;
1349
+ const doneHint = `\n\nWhen the request is fully handled (or no action is needed), choose '${PLAN_DONE_EVENT_TYPE}'.`;
1350
+ const chosen = await resolveDecision({
1351
+ ...base,
1352
+ id: `${id}[${applied.length}]`,
1353
+ events,
1354
+ prompt: `${base.prompt ?? ""}${trail}${doneHint}`,
1355
+ attempts: []
1356
+ }, countingDecide, {
1357
+ maxRetries: input.maxRetries ?? logic.maxRetries,
1358
+ signal,
1359
+ canTake: (event) => {
1360
+ if (event.type === "agent.plan.done" || stopOn.has(event.type)) return true;
1361
+ return invokingActor ? invokingActor.getSnapshot().can(event) : true;
1362
+ }
1363
+ });
1364
+ if (chosen.type === "agent.plan.done") return end("done");
1365
+ actorRef.send(chosen);
1366
+ ledger = advancePlanLedger(logic, ledger, {
1367
+ type: "plan.applied",
1368
+ event: chosen
1369
+ });
1370
+ await Promise.resolve();
1371
+ if (stopOn.has(chosen.type)) return end("stop-event");
1372
+ }
1373
+ return end("max-steps");
1374
+ } });
1375
+ return Object.assign(planLogic, {
1376
+ kind: "statelyai.planLogic",
1377
+ maxRetries: logic.maxRetries,
1378
+ request: logic.request,
1379
+ allowedEventTypes: logic.allowedEventTypes
1380
+ });
1381
+ }
1382
+ /**
1383
+ * Recursively rebinds an invoked child machine's own agent sources with the
1384
+ * SAME host-backed wrappers runAgent applies to the top-level machine, so a
1385
+ * child's text/stream/decision/plan requests inherit runAgent's executors and
1386
+ * participate in maxModelCalls counting, onTrace/onChunk/onResult exactly like
1387
+ * parent requests. Returns the child machine to invoke: a `.provide`-rebound
1388
+ * copy when any inner source needed wrapping, else the original untouched.
1389
+ *
1390
+ * Only string-keyed sources are visited — a direct-object invoke src cannot be
1391
+ * swapped via `.provide` (assertBindable already rejected an unbound request
1392
+ * under one). A source that already carries its own executor
1393
+ * (`executorBoundLogics`) is left as-is: explicit binding shadows inheritance.
1394
+ * Cycle-safe via `visited` (a machine that invokes itself is returned as-is).
1395
+ */
1396
+ function rebindChildMachine(childMachine, runCtx, visited) {
1397
+ if (visited.has(childMachine)) return childMachine;
1398
+ const childVisited = new Set([...visited, childMachine]);
1399
+ const sources = childMachine.implementations.actorSources;
1400
+ const wrapped = {};
1401
+ for (const [key, logic] of Object.entries(sources)) {
1402
+ if (isDecisionLogic(logic)) {
1403
+ if (!executorBoundLogics.has(logic)) wrapped[key] = createRunAgentDecisionLogic(logic, runCtx);
1404
+ continue;
1405
+ }
1406
+ if (isPlanLogic(logic)) {
1407
+ wrapped[key] = createRunAgentPlanLogic(logic, runCtx);
1408
+ continue;
1409
+ }
1410
+ if (isTextLogic(logic)) {
1411
+ if (!executorBoundLogics.has(logic)) wrapped[key] = wrapTextLogicForRunAgent(logic, runCtx);
1412
+ continue;
1413
+ }
1414
+ if (isStateMachine(logic)) {
1415
+ const rebound = rebindChildMachine(logic, runCtx, childVisited);
1416
+ if (rebound !== logic) wrapped[key] = rebound;
1417
+ continue;
1418
+ }
1419
+ }
1420
+ return Object.keys(wrapped).length > 0 ? childMachine.provide({ actorSources: wrapped }) : childMachine;
1421
+ }
1422
+ /**
1423
+ * Runs an agent machine to completion or idle: a `createActor` host that
1424
+ * binds `options`' host executors onto the machine's `agent.*`/`TextLogic`/
1425
+ * `DecisionLogic` actor sources, starts (or resumes) the actor, and drives
1426
+ * it until it settles — {@link RunAgentResult} `done | idle | error`. Unlike
1427
+ * the step helpers ({@link initialAgentStep} etc — a pure
1428
+ * transition-at-a-time path for durable hosts), `runAgent` owns a live actor
1429
+ * internally; there is no continuation callback, so **idle always settles**
1430
+ * and the caller resumes explicitly by passing the settled `{ snapshot,
1431
+ * event }` back in. The actor is stopped on every settle path (`done`,
1432
+ * `idle`, and `error` alike) — resume is always by snapshot, never by
1433
+ * holding a reference to a live actor.
1434
+ *
1435
+ * Binding happens **before** the actor starts: every invoke the machine
1436
+ * could reach is walked and checked against the effective actor sources
1437
+ * (`options.actorSources` merged onto the machine), so a missing
1438
+ * `streamText`/`decide` executor or any other unbound actor source throws
1439
+ * immediately — a bind-time error, not a mid-run failure. The one exception
1440
+ * is `agent.userInput`: unhandled, it binds as a pending placeholder that
1441
+ * settles the run idle (with `pendingUserInputs`) instead of erroring.
1442
+ *
1443
+ * @example
1444
+ * ```ts
1445
+ * const executors = createAiSdkExecutors({ models });
1446
+ * let r = await runAgent(machine, { input, executors });
1447
+ * while (r.status === 'idle') {
1448
+ * const event = await promptUser(getAcceptedEvents(r.snapshot));
1449
+ * r = await runAgent(machine, { snapshot: r.snapshot, event, executors });
1450
+ * }
1451
+ * if (r.status !== 'done') throw new Error(`Run did not complete: ${r.status}`);
1452
+ * console.log(r.output);
1453
+ * ```
1454
+ *
1455
+ * The `executors`' `generateText`/`streamText` accept the raw Vercel AI SDK
1456
+ * functions directly (`executors: { generateText, streamText }` with them
1457
+ * imported from `ai`) — their `{ text }`/`{ textStream }` results are unwrapped
1458
+ * natively. `decide` cannot be a raw AI SDK function: the tool-per-event mapping
1459
+ * lives in an adapter — use `createAiSdkExecutors` from '@statelyai/agent/ai-sdk'.
1460
+ */
1461
+ async function runAgent(machine, options) {
1462
+ const maxModelCalls = options.maxModelCalls ?? 100;
1463
+ let modelCallCount = 0;
1464
+ let budgetExceeded = false;
1465
+ let warnedNonSerializable = false;
1466
+ const runId = `run_${nextRunAgentTraceId++}`;
1467
+ let traceSeq = 0;
1468
+ const machineId = machine.config.id ?? machine.id ?? "(machine)";
1469
+ const machineVersion = options.machineVersion ?? getMachineStructuralHash(machine);
1470
+ const agentMeta = {
1471
+ machineId,
1472
+ version: machineVersion
1473
+ };
1474
+ const stampAgentMeta = (snapshot) => {
1475
+ if (snapshot && typeof snapshot === "object") snapshot.agentMeta = agentMeta;
1476
+ };
1477
+ const onTrace = (event) => {
1478
+ options.onTrace?.({
1479
+ runId,
1480
+ seq: ++traceSeq,
1481
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1482
+ ...event
1483
+ });
1484
+ };
1485
+ const consumeModelCall = () => {
1486
+ if (budgetExceeded) throw new MaxModelCallsExceededError();
1487
+ modelCallCount += 1;
1488
+ if (modelCallCount > maxModelCalls) {
1489
+ budgetExceeded = true;
1490
+ throw new MaxModelCallsExceededError();
1491
+ }
1492
+ };
1493
+ const warnNonSerializableContext = (snapshot) => {
1494
+ if (warnedNonSerializable || process.env.NODE_ENV === "production") return;
1495
+ let offending = [];
1496
+ try {
1497
+ offending = findNonSerializableContextPaths(snapshot.context);
1498
+ } catch {
1499
+ return;
1500
+ }
1501
+ if (offending.length === 0) return;
1502
+ warnedNonSerializable = true;
1503
+ 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.`);
1504
+ };
1505
+ const provided = machine.provide({ actorSources: options.actorSources });
1506
+ const effectiveSources = provided.implementations.actorSources;
1507
+ assertBindable(provided, effectiveSources, {
1508
+ hasGenerateText: !!options.executors?.generateText,
1509
+ hasDecide: !!options.executors?.decide,
1510
+ hasStreamText: !!options.executors?.streamText
1511
+ });
1512
+ if (options.getRequests && !options.executors?.generateText && !options.executors?.decide) throw new Error("runAgent: 'getRequests' requires a 'generateText' and/or 'decide' executor — the returned requests run through them.");
1513
+ const actorHolder = { actorRef: void 0 };
1514
+ const runCtx = {
1515
+ generateText: options.executors?.generateText,
1516
+ streamText: options.executors?.streamText,
1517
+ decide: options.executors?.decide,
1518
+ onChunk: options.onChunk,
1519
+ onResult: options.onResult,
1520
+ onTrace,
1521
+ consumeModelCall,
1522
+ actorHolder,
1523
+ schemas: getRegisteredAgentExecutionOptions(machine).schemas
1524
+ };
1525
+ let userInputIsPlaceholder = false;
1526
+ const wrappedSources = {};
1527
+ for (const [key, logic] of Object.entries(effectiveSources)) {
1528
+ if (key === "agent.userInput") {
1529
+ if (options.userInput) {
1530
+ const userInput = options.userInput;
1531
+ wrappedSources[key] = createAsyncLogic({ run: async ({ input }) => await userInput(input) });
1532
+ } else if (isUnboundPlaceholder(logic)) {
1533
+ userInputIsPlaceholder = true;
1534
+ wrappedSources[key] = createAsyncLogic({ run: () => new Promise(() => {}) });
1535
+ }
1536
+ continue;
1537
+ }
1538
+ if (isDecisionLogic(logic)) {
1539
+ wrappedSources[key] = createRunAgentDecisionLogic(logic, runCtx);
1540
+ continue;
1541
+ }
1542
+ if (isPlanLogic(logic)) {
1543
+ wrappedSources[key] = createRunAgentPlanLogic(logic, runCtx);
1544
+ continue;
1545
+ }
1546
+ if (isTextLogic(logic)) {
1547
+ if (!executorBoundLogics.has(logic)) wrappedSources[key] = wrapTextLogicForRunAgent(logic, runCtx);
1548
+ continue;
1549
+ }
1550
+ if (isStateMachine(logic)) {
1551
+ const rebound = rebindChildMachine(logic, runCtx, new Set([machine]));
1552
+ if (rebound !== logic) wrappedSources[key] = rebound;
1553
+ continue;
1554
+ }
1555
+ }
1556
+ const boundMachine = provided.provide({ actorSources: wrappedSources });
1557
+ const isSuspended = options.isSuspended ?? getMachineSuspensionPredicate(machine) ?? (() => false);
1558
+ let effectiveSnapshot = options.snapshot;
1559
+ if (effectiveSnapshot !== void 0) {
1560
+ const from = effectiveSnapshot.agentMeta?.version;
1561
+ if (from !== void 0 && from !== machineVersion) {
1562
+ const info = {
1563
+ from,
1564
+ to: machineVersion
1565
+ };
1566
+ if (options.migrateSnapshot) effectiveSnapshot = options.migrateSnapshot(effectiveSnapshot, info);
1567
+ else {
1568
+ const mode = options.onVersionMismatch ?? "throw";
1569
+ if (mode === "throw") throw new SnapshotVersionMismatchError(from, machineVersion, machineId);
1570
+ if (mode === "warn") console.warn(`runAgent: resuming a snapshot stamped with machine version '${from}' against machine '${machineId}' at version '${machineVersion}'. Structural changes may not resume cleanly.`);
1571
+ }
1572
+ }
1573
+ }
1574
+ const priorMessages = effectiveSnapshot?.messages ?? [];
1575
+ const messages = typeof options.messages === "function" ? [...options.messages([...priorMessages])] : [...priorMessages, ...options.messages ?? []];
1576
+ const stampMessages = (snapshot) => {
1577
+ if (!options.getRequests && !options.messages) return;
1578
+ if (snapshot && typeof snapshot === "object") snapshot.messages = [...messages];
1579
+ };
1580
+ if (effectiveSnapshot !== void 0 && options.event !== void 0 && (options.onIllegalResumeEvent ?? "throw") === "throw") {
1581
+ const acceptedTypes = getAcceptedEvents(createActor(boundMachine, { snapshot: effectiveSnapshot }).getSnapshot(), { schemas: runCtx.schemas }).map((descriptor) => descriptor.type);
1582
+ const eventType = options.event.type;
1583
+ if (!acceptedTypes.includes(eventType)) throw new IllegalResumeEventError(eventType, acceptedTypes);
1584
+ }
1585
+ return new Promise((resolvePromise) => {
1586
+ let settled = false;
1587
+ let idleTimer;
1588
+ let actor;
1589
+ let deliveringResumeEvent = options.event !== void 0;
1590
+ const settle = (result) => {
1591
+ if (settled) return;
1592
+ settled = true;
1593
+ if (idleTimer !== void 0) clearTimeout(idleTimer);
1594
+ if (options.signal) options.signal.removeEventListener("abort", onAbort);
1595
+ stampAgentMeta(result.snapshot);
1596
+ stampMessages(result.snapshot);
1597
+ if ("persistedSnapshot" in result) {
1598
+ stampAgentMeta(result.persistedSnapshot);
1599
+ stampMessages(result.persistedSnapshot);
1600
+ }
1601
+ onTrace({
1602
+ type: "run.end",
1603
+ ...result
1604
+ });
1605
+ actor.stop();
1606
+ resolvePromise(result);
1607
+ };
1608
+ const onAbort = () => {
1609
+ settle({
1610
+ status: "error",
1611
+ cause: "aborted",
1612
+ error: options.signal?.reason ?? /* @__PURE__ */ new Error("Aborted"),
1613
+ snapshot: actor.getSnapshot()
1614
+ });
1615
+ };
1616
+ const settleIdle = (current) => {
1617
+ warnNonSerializableContext(current);
1618
+ const pendingUserInputs = userInputIsPlaceholder ? collectPendingUserInputs(current) : [];
1619
+ settle({
1620
+ status: "idle",
1621
+ snapshot: current,
1622
+ ...pendingUserInputs.length > 0 ? {
1623
+ pendingUserInputs,
1624
+ persistedSnapshot: actor.getPersistedSnapshot()
1625
+ } : {}
1626
+ });
1627
+ };
1628
+ let interpreting = false;
1629
+ let interpretSeq = 0;
1630
+ const appendToLog = (...items) => {
1631
+ messages.push(...items);
1632
+ if (options.onMessage) for (const item of items) options.onMessage(item);
1633
+ };
1634
+ const settleInterpretError = (error) => {
1635
+ settle({
1636
+ status: "error",
1637
+ cause: budgetExceeded ? "max-model-calls" : wrapsDecisionExhausted(error) ? "decision-exhausted" : "machine",
1638
+ error,
1639
+ snapshot: actor.getSnapshot()
1640
+ });
1641
+ };
1642
+ const passDeps = {
1643
+ getSnapshot: () => actor.getSnapshot(),
1644
+ send: (event) => actor.send(event),
1645
+ isSettled: () => settled,
1646
+ messages,
1647
+ appendToLog,
1648
+ generateText: runCtx.generateText,
1649
+ decide: runCtx.decide ? createCountingDecide(runCtx) : void 0,
1650
+ consumeModelCall,
1651
+ nextRequestId: () => `interpret_${++interpretSeq}`,
1652
+ onTrace,
1653
+ onResult: runCtx.onResult,
1654
+ schemas: runCtx.schemas,
1655
+ signal: options.signal
1656
+ };
1657
+ const maybeInterpret = (snapshot) => {
1658
+ if (!options.getRequests || settled) return false;
1659
+ if (interpreting) return true;
1660
+ let requested;
1661
+ try {
1662
+ requested = options.getRequests(snapshot, { messages });
1663
+ } catch (error) {
1664
+ settleInterpretError(error);
1665
+ return true;
1666
+ }
1667
+ const requests = (Array.isArray(requested) ? requested : requested ? [requested] : []).filter(Boolean);
1668
+ if (requests.length === 0) return false;
1669
+ interpreting = true;
1670
+ runStateRequestPass(requests, passDeps).then(({ sentAny }) => {
1671
+ if (settled || sentAny) return;
1672
+ const current = actor.getSnapshot();
1673
+ if (isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) settleIdle(current);
1674
+ }).catch((error) => settleInterpretError(error)).finally(() => {
1675
+ interpreting = false;
1676
+ if (!settled) scheduleIdleCheck();
1677
+ });
1678
+ return true;
1679
+ };
1680
+ const scheduleIdleCheck = () => {
1681
+ if (idleTimer !== void 0) clearTimeout(idleTimer);
1682
+ idleTimer = setTimeout(() => {
1683
+ idleTimer = void 0;
1684
+ if (settled) return;
1685
+ const current = actor.getSnapshot();
1686
+ if (isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) {
1687
+ if (!maybeInterpret(current)) settleIdle(current);
1688
+ }
1689
+ }, 0);
1690
+ };
1691
+ actor = createActor(boundMachine, {
1692
+ input: options.input,
1693
+ snapshot: effectiveSnapshot,
1694
+ inspect: (event) => {
1695
+ options.inspect?.(event);
1696
+ if (settled || event.type !== "@xstate.transition" || event.actorRef !== actor.ref) return;
1697
+ const snapshot = event.snapshot;
1698
+ onTrace({
1699
+ type: "machine.transition",
1700
+ snapshot,
1701
+ event: event.event
1702
+ });
1703
+ options.onTransition?.(snapshot, event.event);
1704
+ if (snapshot.status === "done") {
1705
+ settle({
1706
+ status: "done",
1707
+ output: snapshot.output,
1708
+ snapshot
1709
+ });
1710
+ return;
1711
+ }
1712
+ if (snapshot.status === "error") {
1713
+ settle({
1714
+ status: "error",
1715
+ cause: budgetExceeded ? "max-model-calls" : wrapsDecisionExhausted(snapshot.error) ? "decision-exhausted" : "machine",
1716
+ error: snapshot.error,
1717
+ snapshot
1718
+ });
1719
+ return;
1720
+ }
1721
+ if (snapshot.status === "stopped") {
1722
+ settle({
1723
+ status: "error",
1724
+ cause: "stopped",
1725
+ error: /* @__PURE__ */ new Error("Actor stopped externally."),
1726
+ snapshot
1727
+ });
1728
+ return;
1729
+ }
1730
+ if (!deliveringResumeEvent && isSuspended(snapshot) && isIdleSnapshot(snapshot, { ignoreUserInputChildren: userInputIsPlaceholder })) {
1731
+ if (!maybeInterpret(snapshot)) settleIdle(snapshot);
1732
+ return;
1733
+ }
1734
+ scheduleIdleCheck();
1735
+ }
1736
+ });
1737
+ actorHolder.actorRef = actor;
1738
+ actor.subscribe({ error: () => {} });
1739
+ actor.on("*", (event) => {
1740
+ onTrace({
1741
+ type: "emit",
1742
+ event
1743
+ });
1744
+ });
1745
+ for (const [type, handler] of Object.entries(options.on ?? {})) if (typeof handler === "function") actor.on(type, handler);
1746
+ if (options.signal) {
1747
+ if (options.signal.aborted) {
1748
+ settle({
1749
+ status: "error",
1750
+ cause: "aborted",
1751
+ error: options.signal.reason ?? /* @__PURE__ */ new Error("Aborted"),
1752
+ snapshot: actor.getSnapshot()
1753
+ });
1754
+ return;
1755
+ }
1756
+ options.signal.addEventListener("abort", onAbort);
1757
+ }
1758
+ onTrace({
1759
+ type: "run.start",
1760
+ ...options.input !== void 0 ? { input: options.input } : {},
1761
+ ...effectiveSnapshot !== void 0 ? { snapshot: effectiveSnapshot } : {},
1762
+ ...options.event !== void 0 ? { event: options.event } : {}
1763
+ });
1764
+ actor.start();
1765
+ if (options.event) {
1766
+ deliveringResumeEvent = false;
1767
+ actor.send(options.event);
1768
+ }
1769
+ });
1770
+ }
1771
+ /**
1772
+ * Runs an agent machine to a **final state** and returns its output, for
1773
+ * run-to-done flows where an idle pause is unexpected. Wraps {@link runAgent}:
1774
+ *
1775
+ * - `done` → resolves with `result.output` (the machine's `OutputFrom`).
1776
+ * - `idle` → throws {@link AgentIdleError} carrying the idle snapshot and the
1777
+ * event types that could resume it.
1778
+ * - `error` → throws `result.error` when it is an `Error`; otherwise wraps it
1779
+ * in an `Error` whose `.cause` is the {@link RunAgentErrorCause} and whose
1780
+ * `.error` is the raw thrown value.
1781
+ *
1782
+ * Use {@link runAgent} directly when idle is an expected outcome you handle
1783
+ * (human-in-the-loop, resumable flows); use `runAgentToCompletion` when the
1784
+ * machine is meant to run straight through to a final state.
1785
+ */
1786
+ async function runAgentToCompletion(machine, options) {
1787
+ const result = await runAgent(machine, options);
1788
+ if (result.status === "done") return result.output;
1789
+ if (result.status === "idle") {
1790
+ const acceptedTypes = getAcceptedEvents(result.snapshot, { schemas: getRegisteredAgentExecutionOptions(machine).schemas }).map((descriptor) => descriptor.type);
1791
+ throw new AgentIdleError(result.snapshot, acceptedTypes);
1792
+ }
1793
+ if (result.error instanceof Error) throw result.error;
1794
+ const wrapped = /* @__PURE__ */ new Error(`runAgentToCompletion: run failed with cause '${result.cause}'.`);
1795
+ wrapped.cause = result.cause;
1796
+ wrapped.error = result.error;
1797
+ throw wrapped;
1798
+ }
1799
+ /**
1800
+ * Wraps a `(snapshot, actorRef) => void` handler into a function usable as
1801
+ * {@link RunAgentOptions.inspect}: it filters the raw inspection stream to
1802
+ * `@xstate.transition` events and hands the handler the typed
1803
+ * {@link AnyMachineSnapshot} and the {@link InspectedActorRef} that
1804
+ * transitioned. Attribute a child actor via `actorRef.id`/`actorRef.src`. Saves
1805
+ * the manual `event.type === '@xstate.transition'` filtering and the snapshot/
1806
+ * actorRef casts.
1807
+ */
1808
+ function inspectTransitions(handler) {
1809
+ return (inspectionEvent) => {
1810
+ if (inspectionEvent.type !== "@xstate.transition") return;
1811
+ handler(inspectionEvent.snapshot, inspectionEvent.actorRef);
1812
+ };
1813
+ }
1814
+ function isIdleSnapshot(snapshot, { ignoreUserInputChildren }) {
1815
+ if (snapshot.status !== "active") return false;
1816
+ if (Object.values(snapshot.children ?? {}).some((child) => {
1817
+ const ref = child;
1818
+ if (ignoreUserInputChildren && ref?.src === "agent.userInput") return false;
1819
+ return ref?.getSnapshot?.()?.status === "active";
1820
+ })) return false;
1821
+ return !getNextTransitions(snapshot).some((transitionDef) => transitionDef.eventType === "" || transitionDef.eventType.startsWith("xstate.after"));
1822
+ }
1823
+ function collectPendingUserInputs(snapshot) {
1824
+ const pending = [];
1825
+ for (const [id, child] of Object.entries(snapshot.children ?? {})) {
1826
+ const ref = child;
1827
+ if (ref?.src !== "agent.userInput") continue;
1828
+ const childSnapshot = ref.getSnapshot?.();
1829
+ if (childSnapshot?.status !== "active") continue;
1830
+ pending.push({
1831
+ id,
1832
+ input: childSnapshot.input
1833
+ });
1834
+ }
1835
+ return pending;
1836
+ }
1837
+ //#endregion
1838
+ //#region src/verify.ts
1839
+ const DECIDE_SRC = "agent.decide";
1840
+ const PLAN_SRC = "agent.plan";
1841
+ function normalizeInvokes(invoke) {
1842
+ if (invoke === void 0 || invoke === null) return [];
1843
+ return Array.isArray(invoke) ? invoke : [invoke];
1844
+ }
1845
+ function buildStateIndex(rootConfig) {
1846
+ const index = /* @__PURE__ */ new Map();
1847
+ const walk = (states, parentPath) => {
1848
+ for (const [name, config] of Object.entries(states ?? {})) {
1849
+ const path = parentPath ? `${parentPath}.${name}` : name;
1850
+ const hasChildren = !!config.states && Object.keys(config.states).length > 0;
1851
+ index.set(path, {
1852
+ path,
1853
+ name,
1854
+ config,
1855
+ parentPath,
1856
+ type: config.type,
1857
+ isFinal: config.type === "final",
1858
+ isParallel: config.type === "parallel",
1859
+ isCompound: hasChildren && config.type !== "parallel",
1860
+ invokes: normalizeInvokes(config.invoke)
1861
+ });
1862
+ if (hasChildren) walk(config.states, path);
1863
+ }
1864
+ };
1865
+ walk(rootConfig.states, "");
1866
+ return index;
1867
+ }
1868
+ function childrenOf(index, parentPath) {
1869
+ const out = [];
1870
+ for (const node of index.values()) if (node.parentPath === parentPath) out.push(node);
1871
+ return out;
1872
+ }
1873
+ function collectTransitionTargets(value, fromNode, index, out) {
1874
+ if (value === void 0 || value === null) return;
1875
+ if (Array.isArray(value)) {
1876
+ for (const item of value) collectTransitionTargets(item, fromNode, index, out);
1877
+ return;
1878
+ }
1879
+ if (typeof value === "function") {
1880
+ out.opaque = true;
1881
+ return;
1882
+ }
1883
+ if (typeof value === "string") {
1884
+ resolveTargetString(value, fromNode, index, out);
1885
+ return;
1886
+ }
1887
+ if (typeof value === "object") {
1888
+ const target = value.target;
1889
+ if (target !== void 0) collectTransitionTargets(target, fromNode, index, out);
1890
+ }
1891
+ }
1892
+ function resolveTargetString(target, fromNode, index, out) {
1893
+ if (target.startsWith("#")) {
1894
+ out.opaque = true;
1895
+ return;
1896
+ }
1897
+ const resolved = target.startsWith(".") ? `${fromNode.path}.${target.slice(1)}` : fromNode.parentPath ? `${fromNode.parentPath}.${target}` : target;
1898
+ if (index.has(resolved)) out.targets.push(resolved);
1899
+ else out.opaque = true;
1900
+ }
1901
+ function outgoingTargets(node, index) {
1902
+ const out = {
1903
+ targets: [],
1904
+ opaque: false
1905
+ };
1906
+ const { config } = node;
1907
+ for (const value of Object.values(config.on ?? {})) collectTransitionTargets(value, node, index, out);
1908
+ collectTransitionTargets(config.always, node, index, out);
1909
+ collectTransitionTargets(config.choice, node, index, out);
1910
+ for (const value of Object.values(config.after ?? {})) collectTransitionTargets(value, node, index, out);
1911
+ collectTransitionTargets(config.onDone, node, index, out);
1912
+ for (const invoke of node.invokes) {
1913
+ collectTransitionTargets(invoke.onDone, node, index, out);
1914
+ collectTransitionTargets(invoke.onError, node, index, out);
1915
+ }
1916
+ return out;
1917
+ }
1918
+ function computeReachable(rootConfig, index) {
1919
+ const reachable = /* @__PURE__ */ new Set();
1920
+ const queue = [];
1921
+ const markAncestors = (path) => {
1922
+ let parent = index.get(path)?.parentPath ?? "";
1923
+ while (parent) {
1924
+ reachable.add(parent);
1925
+ parent = index.get(parent)?.parentPath ?? "";
1926
+ }
1927
+ };
1928
+ const enter = (path) => {
1929
+ if (reachable.has(path)) return;
1930
+ const node = index.get(path);
1931
+ if (!node) return;
1932
+ reachable.add(path);
1933
+ queue.push(path);
1934
+ markAncestors(path);
1935
+ if (node.isParallel) for (const child of childrenOf(index, path)) enter(child.path);
1936
+ else if (node.isCompound && node.config.initial) enter(`${path}.${node.config.initial}`);
1937
+ };
1938
+ if (rootConfig.type === "parallel") for (const child of childrenOf(index, "")) enter(child.path);
1939
+ else if (rootConfig.initial) enter(rootConfig.initial);
1940
+ while (queue.length > 0) {
1941
+ const node = index.get(queue.shift());
1942
+ if (!node) continue;
1943
+ const { targets, opaque } = outgoingTargets(node, index);
1944
+ for (const target of targets) enter(target);
1945
+ if (opaque) for (const sibling of childrenOf(index, node.parentPath)) enter(sibling.path);
1946
+ }
1947
+ return reachable;
1948
+ }
1949
+ function ancestorChain(node, index) {
1950
+ const chain = [];
1951
+ let parent = node.parentPath;
1952
+ while (parent) {
1953
+ const parentNode = index.get(parent);
1954
+ if (!parentNode) break;
1955
+ chain.push(parentNode);
1956
+ parent = parentNode.parentPath;
1957
+ }
1958
+ return chain;
1959
+ }
1960
+ function hasNonEmptyOn(config) {
1961
+ return !!config.on && Object.keys(config.on).length > 0;
1962
+ }
1963
+ function decisionKindOf(src, actorSources) {
1964
+ if (typeof src === "string") {
1965
+ if (src === DECIDE_SRC) return "decision";
1966
+ if (src === PLAN_SRC) return "plan";
1967
+ const logic = actorSources[src];
1968
+ if (isDecisionLogic(logic)) return "decision";
1969
+ if (isPlanLogic(logic)) return "plan";
1970
+ return;
1971
+ }
1972
+ if (isDecisionLogic(src)) return "decision";
1973
+ if (isPlanLogic(src)) return "plan";
1974
+ }
1975
+ function isAgentLogicNeedingBinding(src) {
1976
+ return (isTextLogic(src) || isDecisionLogic(src) || isPlanLogic(src)) && !executorBoundLogics.has(src);
1977
+ }
1978
+ function schemaExposesJson(schema) {
1979
+ if (!schema) return false;
1980
+ try {
1981
+ return getJsonSchemaSync(schema) !== void 0;
1982
+ } catch {
1983
+ return false;
1984
+ }
1985
+ }
1986
+ function isDeclaredOutputSchema(schema) {
1987
+ if (!schema) return false;
1988
+ let json;
1989
+ try {
1990
+ json = getJsonSchemaSync(schema);
1991
+ } catch {
1992
+ return false;
1993
+ }
1994
+ if (!json) return false;
1995
+ const properties = json.properties;
1996
+ const required = json.required;
1997
+ return json.type === "object" && !!properties && Object.keys(properties).length > 0 || Array.isArray(required) && required.length > 0;
1998
+ }
1999
+ function checkUnreachableStates(ctx) {
2000
+ const out = [];
2001
+ for (const node of ctx.index.values()) if (!ctx.reachable.has(node.path)) out.push({
2002
+ code: "unreachable-state",
2003
+ severity: "error",
2004
+ path: node.path,
2005
+ message: `State '${node.path}' is unreachable: no transition, always, onDone, or onError target (from any reachable state) leads to it. Remove it or add a transition.`
2006
+ });
2007
+ return out;
2008
+ }
2009
+ function checkDecideWithoutEvents(ctx) {
2010
+ const out = [];
2011
+ for (const node of ctx.index.values()) for (const invoke of node.invokes) {
2012
+ const kind = decisionKindOf(invoke.src, ctx.actorSources);
2013
+ if (!kind) continue;
2014
+ const selfHandles = hasNonEmptyOn(node.config);
2015
+ const ancestorHandles = ancestorChain(node, ctx.index).some((ancestor) => hasNonEmptyOn(ancestor.config));
2016
+ const invokeObserves = invoke.onDone !== void 0;
2017
+ if (selfHandles || ancestorHandles || invokeObserves) continue;
2018
+ const srcName = typeof invoke.src === "string" ? invoke.src : "(inline logic)";
2019
+ out.push({
2020
+ code: "decide-without-events",
2021
+ severity: "error",
2022
+ path: node.path,
2023
+ message: `State '${node.path}' invokes ${kind} source '${srcName}', but neither it nor any ancestor handles any event (no 'on:'), so the ${kind}'s chosen event can never be delivered. Add an 'on:' handler for the candidate events.`
2024
+ });
2025
+ }
2026
+ return out;
2027
+ }
2028
+ function checkUnserializableContext(ctx) {
2029
+ const contextSchema = ctx.schemas?.context;
2030
+ if (!contextSchema) return [];
2031
+ if (schemaExposesJson(contextSchema)) return [];
2032
+ return [{
2033
+ code: "unserializable-context",
2034
+ severity: "warning",
2035
+ path: "context",
2036
+ message: "The context schema does not expose a JSON schema (e.g. a `z.custom` without a `jsonSchema` extension, such as a messages array), so its fields cannot be statically checked for JSON persist/resume round-tripping. This is expected for message transcripts; verify any other custom-typed context is JSON-serializable."
2037
+ }];
2038
+ }
2039
+ function checkDirectObjectSrc(ctx) {
2040
+ const out = [];
2041
+ for (const node of ctx.index.values()) for (const invoke of node.invokes) {
2042
+ const src = invoke.src;
2043
+ if (typeof src === "string" || !src || typeof src !== "object") continue;
2044
+ if (!isAgentLogicNeedingBinding(src)) continue;
2045
+ out.push({
2046
+ code: "direct-object-src",
2047
+ severity: "warning",
2048
+ path: node.path,
2049
+ 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({ actorSources: { name: logic } })) and invoke it by name.`
2050
+ });
2051
+ }
2052
+ return out;
2053
+ }
2054
+ function checkFinalWithoutOutput(ctx) {
2055
+ if (!isDeclaredOutputSchema(ctx.schemas?.output)) return [];
2056
+ if (ctx.config.output !== void 0) return [];
2057
+ const out = [];
2058
+ for (const node of ctx.index.values()) {
2059
+ if (node.parentPath !== "" || !node.isFinal) continue;
2060
+ if (node.config.output === void 0) out.push({
2061
+ code: "final-without-output",
2062
+ severity: "error",
2063
+ path: node.path,
2064
+ message: `The machine declares an output schema, but top-level final state '${node.path}' has no 'output'. Its snapshot output will be undefined and fail the schema. Add an 'output' to this final state (or a root 'output').`
2065
+ });
2066
+ }
2067
+ return out;
2068
+ }
2069
+ function outputFnReadsEvent(fn) {
2070
+ return /\bevent\s*(?:\.|\?\.|\[)/.test(fn.toString());
2071
+ }
2072
+ function checkFinalOutputReadsEvent(ctx) {
2073
+ const out = [];
2074
+ for (const node of ctx.index.values()) {
2075
+ if (node.parentPath !== "" || !node.isFinal) continue;
2076
+ const output = node.config.output;
2077
+ if (typeof output !== "function") continue;
2078
+ if (!outputFnReadsEvent(output)) continue;
2079
+ out.push({
2080
+ code: "final-output-reads-event",
2081
+ severity: "warning",
2082
+ path: node.path,
2083
+ message: `Final state '${node.path}' has an 'output' function that reads 'event'. Final-state 'output' functions are evaluated more than once with different events (the entering event, then the machine-done computation) under current xstate behavior, so 'event' is unreliable here. Read 'context' only; capture what you need from the entering event into context in the transition that targets this state. (This guard can relax if xstate guarantees a stable entering event across evaluations.)`
2084
+ });
2085
+ }
2086
+ return out;
2087
+ }
2088
+ function checkMissingFinal(ctx) {
2089
+ for (const node of ctx.index.values()) if (node.isFinal && ctx.reachable.has(node.path)) return [];
2090
+ return [{
2091
+ code: "missing-final",
2092
+ severity: "warning",
2093
+ path: "(root)",
2094
+ message: "The machine has no reachable final state, so a run can never settle 'done' (only idle/looping). This is legal for agents that only idle, but verify it is intended."
2095
+ }];
2096
+ }
2097
+ const LINT_CHECKS = [
2098
+ checkUnreachableStates,
2099
+ checkDecideWithoutEvents,
2100
+ checkUnserializableContext,
2101
+ checkDirectObjectSrc,
2102
+ checkFinalWithoutOutput,
2103
+ checkFinalOutputReadsEvent,
2104
+ checkMissingFinal
2105
+ ];
2106
+ /**
2107
+ * Runs static structural checks over a built agent machine and returns the
2108
+ * findings ({@link AgentLintDiagnostic}[], empty when clean). Works for
2109
+ * TS-authored (`setupAgent(...).createMachine(...)`) and
2110
+ * `setupAgent.fromConfig(...)`-compiled machines alike, reading `machine.config`
2111
+ * plus the schemas/actor sources the library already retains per machine.
2112
+ *
2113
+ * No model calls, no API keys — a coding agent that emits an agent machine can
2114
+ * call this to catch dead states, undeliverable decisions, un-rebindable
2115
+ * invoke srcs, and output-contract gaps before ever running it.
2116
+ *
2117
+ * @example
2118
+ * ```ts
2119
+ * const errors = lintAgentMachine(machine).filter((d) => d.severity === 'error');
2120
+ * if (errors.length) throw new Error(errors.map((e) => `${e.path}: ${e.message}`).join('\n'));
2121
+ * ```
2122
+ */
2123
+ function lintAgentMachine(machine, options = {}) {
2124
+ const config = machine.config ?? {};
2125
+ const index = buildStateIndex(config);
2126
+ const reachable = computeReachable(config, index);
2127
+ const registered = getRegisteredAgentExecutionOptions(machine);
2128
+ const ctx = {
2129
+ machine,
2130
+ config,
2131
+ index,
2132
+ reachable,
2133
+ schemas: registered.schemas,
2134
+ actorSources: registered.actorSources ?? machine.implementations?.actorSources ?? {}
2135
+ };
2136
+ const disabled = new Set(options.disable ?? []);
2137
+ return LINT_CHECKS.flatMap((check) => check(ctx)).filter((d) => !disabled.has(d.code));
2138
+ }
2139
+ function pendingInvokes(step) {
2140
+ const out = [];
2141
+ for (const action of step.actions) {
2142
+ const type = action.type;
2143
+ if (type !== "xstate.spawnChild" && type !== "@xstate.start") continue;
2144
+ const params = type === "@xstate.start" ? action : action.params ?? {};
2145
+ if (typeof params.src === "string" && typeof params.id === "string") out.push({
2146
+ id: params.id,
2147
+ src: params.src
2148
+ });
2149
+ }
2150
+ return out;
2151
+ }
2152
+ function takeFromQueue(channel, src) {
2153
+ const queue = channel?.[src];
2154
+ if (queue && queue.length > 0) return {
2155
+ found: true,
2156
+ value: queue.shift()
2157
+ };
2158
+ return { found: false };
2159
+ }
2160
+ /**
2161
+ * Deterministically plays a machine through, resolving each request from a
2162
+ * {@link SimulationScript} instead of a model — no API keys, no model calls.
2163
+ * Runs on the pure step path ({@link initialAgentStep} etc.), so it exercises
2164
+ * the real transition logic. Returns the terminal `status`, final `snapshot`,
2165
+ * and a `trail` of every step taken.
2166
+ *
2167
+ * Throws a descriptive error when the script runs dry mid-request, naming the
2168
+ * pending request's kind, src, and id so the missing scripted response is
2169
+ * obvious.
2170
+ *
2171
+ * @example
2172
+ * ```ts
2173
+ * const { status, snapshot } = simulateAgent(machine, {
2174
+ * input: { topic: 'state machines' },
2175
+ * script: { decisions: { 'agent.decide': [{ type: 'END' }] } },
2176
+ * });
2177
+ * ```
2178
+ */
2179
+ async function simulateAgent(machine, options) {
2180
+ const maxSteps = options.maxSteps ?? 100;
2181
+ const script = {
2182
+ text: { ...options.script.text },
2183
+ decisions: mapValues(options.script.decisions ?? {}, (arr) => [...arr]),
2184
+ userInput: mapValues(options.script.userInput ?? {}, (arr) => [...arr])
2185
+ };
2186
+ let step = initialAgentStep(machine, options.input);
2187
+ const trail = [];
2188
+ for (let i = 0; i < maxSteps; i++) {
2189
+ if (step.done) return {
2190
+ status: "done",
2191
+ snapshot: step.snapshot,
2192
+ trail
2193
+ };
2194
+ const request = step.requests[0];
2195
+ if (request) {
2196
+ if (request.kind === "decision") {
2197
+ const decisionSrc = new Map(pendingInvokes(step).map((invoke) => [invoke.id, invoke.src])).get(request.id) ?? request.id;
2198
+ const taken = takeFromQueue(script.decisions, decisionSrc);
2199
+ if (!taken.found) throw scriptDryError("decision", decisionSrc, request.id, request);
2200
+ step = transitionAgentStep(machine, step, taken.value);
2201
+ trail.push({
2202
+ state: step.snapshot.value,
2203
+ appliedEvent: taken.value
2204
+ });
2205
+ continue;
2206
+ }
2207
+ if (request.kind === "plan") {
2208
+ const taken = takeFromQueue(script.decisions, request.src);
2209
+ if (!taken.found) throw scriptDryError("decision", request.src, request.id, request);
2210
+ const event = taken.value;
2211
+ step = await resolveAgentRequests(machine, step, { decide: async () => ({ event }) });
2212
+ trail.push({
2213
+ state: step.snapshot.value,
2214
+ appliedEvent: taken.value
2215
+ });
2216
+ continue;
2217
+ }
2218
+ const taken = takeFromQueue(script.text, request.src);
2219
+ if (!taken.found) throw scriptDryError("text", request.src, request.id);
2220
+ step = resolveAgentStep(machine, step, request, taken.value);
2221
+ trail.push({
2222
+ state: step.snapshot.value,
2223
+ resolvedRequest: {
2224
+ kind: "text",
2225
+ src: request.src,
2226
+ id: request.id
2227
+ }
2228
+ });
2229
+ continue;
2230
+ }
2231
+ const [invoke] = pendingInvokes(step);
2232
+ if (invoke) {
2233
+ const taken = takeFromQueue(script.userInput, invoke.src);
2234
+ if (!taken.found) throw scriptDryError("userInput", invoke.src, invoke.id);
2235
+ step = resolveAgentStep(machine, step, invoke.id, taken.value);
2236
+ trail.push({
2237
+ state: step.snapshot.value,
2238
+ resolvedRequest: {
2239
+ kind: "userInput",
2240
+ src: invoke.src,
2241
+ id: invoke.id
2242
+ }
2243
+ });
2244
+ continue;
2245
+ }
2246
+ return {
2247
+ status: "idle",
2248
+ snapshot: step.snapshot,
2249
+ trail
2250
+ };
2251
+ }
2252
+ return {
2253
+ status: "exhausted",
2254
+ snapshot: step.snapshot,
2255
+ trail
2256
+ };
2257
+ }
2258
+ function mapValues(obj, fn) {
2259
+ return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, fn(value)]));
2260
+ }
2261
+ function scriptDryError(kind, src, id, request) {
2262
+ const events = request?.kind === "decision" ? ` Candidate events: ${request.events.map((e) => e.type).join(", ") || "(none)"}.` : "";
2263
+ return /* @__PURE__ */ new Error(`simulateAgent: script ran dry on a pending ${kind} request for src '${src}' (id '${id}'). Add a '${kind}' entry for '${src}' to the script.${events}`);
2264
+ }
2265
+ async function explore(machine, options, stopWhen) {
2266
+ const maxDepth = options.maxDepth ?? 8;
2267
+ const maxPaths = options.maxPaths ?? 200;
2268
+ const textOutputs = options.textOutputs ?? {};
2269
+ const reachedStates = /* @__PURE__ */ new Set();
2270
+ const reachedValues = [];
2271
+ const terminals = [];
2272
+ const unexplored = [];
2273
+ let prunedByGuard = 0;
2274
+ let pathsExplored = 0;
2275
+ let hitPathCap = false;
2276
+ let witness;
2277
+ const recordState = (snapshot) => {
2278
+ const key = JSON.stringify(snapshot.value);
2279
+ if (!reachedStates.has(key)) {
2280
+ reachedStates.add(key);
2281
+ reachedValues.push(snapshot.value);
2282
+ }
2283
+ };
2284
+ const initial = initialAgentStep(machine, options.input);
2285
+ recordState(initial.snapshot);
2286
+ if (stopWhen?.(initial.snapshot)) witness = [];
2287
+ const advance = (step) => {
2288
+ let current = step;
2289
+ for (let i = 0; i < 1e3; i++) {
2290
+ if (current.done) return { step: current };
2291
+ const request = current.requests[0];
2292
+ if (request && request.kind === "text") {
2293
+ if (!(request.src in textOutputs)) return {
2294
+ step: current,
2295
+ blockedSrc: request.src
2296
+ };
2297
+ current = resolveAgentStep(machine, current, request, textOutputs[request.src]);
2298
+ recordState(current.snapshot);
2299
+ continue;
2300
+ }
2301
+ if (request && (request.kind === "decision" || request.kind === "plan")) return { step: current };
2302
+ const [invoke] = pendingInvokes(current);
2303
+ if (invoke) {
2304
+ if (!(invoke.src in textOutputs)) return {
2305
+ step: current,
2306
+ blockedSrc: invoke.src
2307
+ };
2308
+ current = resolveAgentStep(machine, current, invoke.id, textOutputs[invoke.src]);
2309
+ recordState(current.snapshot);
2310
+ continue;
2311
+ }
2312
+ return { step: current };
2313
+ }
2314
+ return { step: current };
2315
+ };
2316
+ const visit = async (step, path, depth) => {
2317
+ if (witness !== void 0) return;
2318
+ if (pathsExplored >= maxPaths) {
2319
+ hitPathCap = true;
2320
+ return;
2321
+ }
2322
+ const { step: settled, blockedSrc } = advance(step);
2323
+ if (stopWhen?.(settled.snapshot)) {
2324
+ witness = path;
2325
+ return;
2326
+ }
2327
+ if (blockedSrc) {
2328
+ pathsExplored++;
2329
+ terminals.push({
2330
+ status: "needs-output",
2331
+ path,
2332
+ state: settled.snapshot.value,
2333
+ missingSrc: blockedSrc
2334
+ });
2335
+ unexplored.push(`needs-output: no canned output for src '${blockedSrc}' at path [${path.map((e) => e.type).join(", ")}]`);
2336
+ return;
2337
+ }
2338
+ if (settled.done) {
2339
+ pathsExplored++;
2340
+ terminals.push({
2341
+ status: "done",
2342
+ path,
2343
+ state: settled.snapshot.value
2344
+ });
2345
+ return;
2346
+ }
2347
+ const request = settled.requests[0];
2348
+ const isPlan = request?.kind === "plan";
2349
+ const branchEvents = request?.kind === "decision" || request?.kind === "plan" ? request.events.map((descriptor) => ({ type: descriptor.type })) : getAcceptedEvents(settled.snapshot).map((descriptor) => ({ type: descriptor.type }));
2350
+ if (branchEvents.length === 0) {
2351
+ pathsExplored++;
2352
+ terminals.push({
2353
+ status: "idle",
2354
+ path,
2355
+ state: settled.snapshot.value
2356
+ });
2357
+ return;
2358
+ }
2359
+ if (depth >= maxDepth) {
2360
+ pathsExplored++;
2361
+ terminals.push({
2362
+ status: "max-depth",
2363
+ path,
2364
+ state: settled.snapshot.value
2365
+ });
2366
+ unexplored.push(`max-depth: stopped at path [${path.map((e) => e.type).join(", ")}]`);
2367
+ return;
2368
+ }
2369
+ for (const event of branchEvents) {
2370
+ if (witness !== void 0 || pathsExplored >= maxPaths) {
2371
+ if (pathsExplored >= maxPaths) hitPathCap = true;
2372
+ return;
2373
+ }
2374
+ if (!(isPlan && event.type === "agent.plan.done") && !settled.snapshot.can(event)) {
2375
+ prunedByGuard++;
2376
+ continue;
2377
+ }
2378
+ const next = isPlan ? await resolveAgentRequests(machine, settled, { decide: async () => ({ event }) }) : transitionAgentStep(machine, settled, event);
2379
+ recordState(next.snapshot);
2380
+ await visit(next, [...path, event], depth + 1);
2381
+ }
2382
+ };
2383
+ if (witness === void 0) await visit(initial, [], 0);
2384
+ return {
2385
+ report: {
2386
+ reachedStates: reachedValues,
2387
+ terminals,
2388
+ prunedByGuard,
2389
+ unexplored,
2390
+ pathsExplored,
2391
+ hitPathCap
2392
+ },
2393
+ witness
2394
+ };
2395
+ }
2396
+ /**
2397
+ * Enumerates a machine's decision and external-event branches to a bounded
2398
+ * depth, model-free, and reports which states are reached and how each path
2399
+ * terminates. At each decision request it forks one branch per candidate event
2400
+ * (guard-rejected candidates are counted in `prunedByGuard`, not explored); at
2401
+ * an idle wait it forks per externally-accepted event. A `agent.plan` request
2402
+ * forks the same way — one branch per candidate, including the reserved
2403
+ * `agent.plan.done` move — advancing each branch through the real plan protocol
2404
+ * (`resolveAgentRequests`), so a plan can consume several depth units. Text/`userInput` invokes
2405
+ * are resolved from `textOutputs` (a by-src canned-output map) — a missing src
2406
+ * halts that branch with a `needs-output` terminal rather than throwing.
2407
+ *
2408
+ * Combinatorics are bounded by `maxDepth` (default 8) and `maxPaths` (default
2409
+ * 200, reported via `hitPathCap`).
2410
+ *
2411
+ * @example
2412
+ * ```ts
2413
+ * const report = await explorePaths(refundMachine, { input: { request: 'x', amount: 5000 } });
2414
+ * // report.terminals → both 'refunded' and 'denied'; report.prunedByGuard → 1
2415
+ * ```
2416
+ */
2417
+ async function explorePaths(machine, options = {}) {
2418
+ return (await explore(machine, options)).report;
2419
+ }
2420
+ /**
2421
+ * Answers "can the machine reach `statePath`?" by exploring its branches (a
2422
+ * thin wrapper over {@link explorePaths}). Returns `{ canReach: true, witness }`
2423
+ * with the event sequence that reaches it, or `{ canReach: false }`.
2424
+ *
2425
+ * @example
2426
+ * ```ts
2427
+ * const { canReach, witness } = await canReach(refundMachine, 'denied', { input: { request: 'x', amount: 5000 } });
2428
+ * // canReach → true; witness → [{ type: 'NEEDS_REVIEW' }, { type: 'DENY' }]
2429
+ * ```
2430
+ */
2431
+ async function canReach(machine, statePath, options = {}) {
2432
+ const { witness } = await explore(machine, options, (snapshot) => {
2433
+ try {
2434
+ return snapshot.matches(statePath);
2435
+ } catch {
2436
+ return false;
2437
+ }
2438
+ });
2439
+ return witness !== void 0 ? {
2440
+ canReach: true,
2441
+ witness
2442
+ } : { canReach: false };
2443
+ }
2444
+ //#endregion
2445
+ export { createAgentSchemas as _, AgentIdleError as a, messagesSchema as b, inspectTransitions as c, executeAgentRequest as d, getAgentRequests as f, transitionAgentStep as g, resolveAgentStep as h, simulateAgent as i, runAgent as l, resolveAgentRequests as m, explorePaths as n, IllegalResumeEventError as o, initialAgentStep as p, lintAgentMachine as r, SnapshotVersionMismatchError as s, canReach as t, runAgentToCompletion as u, setupAgent as v, appendMessages as y };