@statelyai/agent 1.1.6 → 2.0.0-alpha.11

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