@statelyai/agent 2.0.0-alpha.11 → 2.0.0-alpha.13

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 (66) hide show
  1. package/dist/ai-sdk.cjs +4 -5
  2. package/dist/ai-sdk.d.cts +7 -4
  3. package/dist/ai-sdk.d.mts +7 -4
  4. package/dist/ai-sdk.mjs +1 -2
  5. package/dist/{events-JiVPYrct.mjs → decision-BezSD_YC.mjs} +327 -20
  6. package/dist/{events-CRQj3VtP.cjs → decision-dWGhBh0P.cjs} +401 -28
  7. package/dist/errors-BQRk9eiZ.d.cts +19 -0
  8. package/dist/errors-C9rxnWbX.d.mts +19 -0
  9. package/dist/errors-CeSXQx0v.mjs +23 -0
  10. package/dist/errors-DUBBzRLP.cjs +28 -0
  11. package/dist/event-log-store-CNT_7F0V.cjs +452 -0
  12. package/dist/event-log-store-CriMgX1D.d.mts +144 -0
  13. package/dist/event-log-store-D7pWtIhb.mjs +411 -0
  14. package/dist/event-log-store-Ruq18mGp.d.cts +144 -0
  15. package/dist/index.cjs +1050 -705
  16. package/dist/index.d.cts +538 -565
  17. package/dist/index.d.mts +538 -565
  18. package/dist/index.mjs +950 -644
  19. package/dist/machines.cjs +752 -0
  20. package/dist/machines.d.cts +372 -0
  21. package/dist/machines.d.mts +372 -0
  22. package/dist/machines.mjs +741 -0
  23. package/dist/otel.cjs +268 -0
  24. package/dist/otel.d.cts +67 -0
  25. package/dist/otel.d.mts +67 -0
  26. package/dist/otel.mjs +267 -0
  27. package/dist/run-agent-C3mFDGTf.d.mts +1111 -0
  28. package/dist/run-agent-DnvtcnTZ.d.cts +1111 -0
  29. package/dist/setup-agent-DAZZSjDS.mjs +1711 -0
  30. package/dist/setup-agent-DP95MFrI.cjs +1836 -0
  31. package/dist/sqlite.cjs +135 -0
  32. package/dist/sqlite.d.cts +57 -0
  33. package/dist/sqlite.d.mts +57 -0
  34. package/dist/sqlite.mjs +133 -0
  35. package/dist/{text-logic-CaKqgX4Y.d.mts → text-logic-BDxwQNsD.d.cts} +155 -72
  36. package/dist/{text-logic-Ckhr2kKC.d.cts → text-logic-TkKPw8Aq.d.mts} +155 -72
  37. package/dist/{types-qm00QF91.d.mts → types-QbEfCVny.d.cts} +1 -1
  38. package/dist/{types-C9QiMjre.d.cts → types-_FXoFBGO.d.mts} +1 -1
  39. package/package.json +47 -39
  40. package/readme.md +49 -12
  41. package/schemas/agent-workflow.json +40 -21
  42. package/skills/generate-machine/SKILL.md +267 -0
  43. package/dist/adapter.cjs +0 -15
  44. package/dist/adapter.d.cts +0 -4
  45. package/dist/adapter.d.mts +0 -4
  46. package/dist/adapter.mjs +0 -2
  47. package/dist/decision-C3k4ve51.mjs +0 -227
  48. package/dist/decision-D8wJrM8W.cjs +0 -286
  49. package/dist/openai-compat.cjs +0 -309
  50. package/dist/openai-compat.d.cts +0 -59
  51. package/dist/openai-compat.d.mts +0 -59
  52. package/dist/openai-compat.mjs +0 -308
  53. package/dist/steps-BALp1eZo.d.mts +0 -198
  54. package/dist/steps-CVe54GPP.cjs +0 -420
  55. package/dist/steps-CkyyyuHd.mjs +0 -379
  56. package/dist/steps-MjnQI4aB.d.cts +0 -198
  57. package/dist/steps.cjs +0 -12
  58. package/dist/steps.d.cts +0 -3
  59. package/dist/steps.d.mts +0 -3
  60. package/dist/steps.mjs +0 -3
  61. package/dist/utils-BYqT_Dyv.d.cts +0 -108
  62. package/dist/utils-Do5wIJrh.d.mts +0 -108
  63. package/dist/zod.cjs +0 -31
  64. package/dist/zod.d.cts +0 -30
  65. package/dist/zod.d.mts +0 -30
  66. package/dist/zod.mjs +0 -30
package/dist/index.mjs CHANGED
@@ -1,565 +1,8 @@
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
1
+ import { t as AgentError } from "./errors-CeSXQx0v.mjs";
2
+ import { _ as resolveAgentRequests, a as AgentReplayDivergenceError, b as appendMessages, c as diffEventLogs, d as initEntry, f as replay, g as initialAgentStep, h as getInvokeEffectMetadata, i as AGENT_USAGE_EVENT_TYPE, l as getAgentEffects, m as executeAgentRequest, n as setupAgent, o as AgentReplayMachineMismatchError, p as verifyReplay, r as AGENT_INIT_EVENT_TYPE, s as createReplayEntry, t as createAgentSchemas, u as getCallUsage, v as resolveAgentStep, x as messagesSchema, y as transitionAgentStep } from "./setup-agent-DAZZSjDS.mjs";
3
+ import { A as parseStructuredEnvelope, D as normalizeGeneratorResult, E as isTextLogic, F as getMachineSuspensionPredicate, G as getJsonSchemaSync, H as findNonSerializableContextPaths, I as getRegisteredAgentExecutionOptions, J as isStandardSchema, K as getMachineStructuralHash, L as isUnboundPlaceholder, N as executorBoundLogics, O as parseModelRef, P as getMachineStaticTransitionTargets, Q as userMessage, S as createTextLogic, T as getAgentOutputMode, U as getAgentMessages, V as assistantMessage, W as getJsonSchema, X as systemMessage, Y as persistSnapshot, Z as toolMessage, b as buildEnvelopeSchema, c as isPlanLogic, d as getAcceptedEvents, f as parseAgentEvent, g as INTERPRET_SOURCE, k as parseOutput, l as renderDecisionAttempts, m as AGENT_USAGE_TOKEN_FIELDS, n as PLAN_DONE_EVENT_TYPE, o as initialPlanLedger, p as sanitizeEventToolName, q as getStateMeta, r as advancePlanLedger, s as isDecisionLogic, t as AgentDecisionExhaustedError, u as resolveDecision, w as extractCallUsage, y as bindRequestExecutor } from "./decision-BezSD_YC.mjs";
4
+ import { a as assertEventLogStoreConformance, i as assertAgentLogEntry, n as AgentEventLogConflictError, o as assertJsonSerializable, r as NonSerializableAgentEventError, s as createInMemoryEventLogStore, t as AGENT_EVENT_SCHEMA_VERSION } from "./event-log-store-D7pWtIhb.mjs";
5
+ import { createActor, createAsyncLogic, getNextTransitions } from "xstate";
563
6
  //#region src/internal/state-request-pass.ts
564
7
  async function runTextPhase(stateRequest, baseMessages, deps) {
565
8
  const { model, system } = stateRequest;
@@ -598,6 +41,8 @@ async function runTextPhase(stateRequest, baseMessages, deps) {
598
41
  output = await normalizeGeneratorResult(raw, id, { request });
599
42
  const rawReasoning = raw?.reasoning;
600
43
  const reasoning = typeof rawReasoning === "string" ? rawReasoning : void 0;
44
+ const usage = extractCallUsage(raw);
45
+ if (usage) deps.recordUsage?.(usage);
601
46
  deps.onResult?.(agentRequest, {
602
47
  output,
603
48
  raw
@@ -607,7 +52,8 @@ async function runTextPhase(stateRequest, baseMessages, deps) {
607
52
  request: agentRequest,
608
53
  output,
609
54
  raw,
610
- ...reasoning !== void 0 ? { reasoning } : {}
55
+ ...reasoning !== void 0 ? { reasoning } : {},
56
+ ...usage !== void 0 ? { usage } : {}
611
57
  });
612
58
  } catch (error) {
613
59
  deps.onTrace?.({
@@ -695,12 +141,12 @@ async function runStateRequestPass(requests, deps) {
695
141
  * machine simply takes no transition). Opt out with
696
142
  * {@link RunAgentOptions.onIllegalResumeEvent} `'ignore'`.
697
143
  */
698
- var IllegalResumeEventError = class extends Error {
144
+ var AgentIllegalResumeEventError = class extends AgentError {
699
145
  eventType;
700
146
  acceptedTypes;
701
147
  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";
148
+ super("illegal-resume-event", `runAgent: cannot resume with event '${eventType}' — the restored state does not accept it. Accepted event types: ${acceptedTypes.length > 0 ? acceptedTypes.join(", ") : "(none)"}.`);
149
+ this.name = "AgentIllegalResumeEventError";
704
150
  this.eventType = eventType;
705
151
  this.acceptedTypes = acceptedTypes;
706
152
  }
@@ -714,30 +160,30 @@ var IllegalResumeEventError = class extends Error {
714
160
  * the snapshot may no longer resume cleanly. `from` is the snapshot's version,
715
161
  * `to` the current machine's.
716
162
  */
717
- var SnapshotVersionMismatchError = class extends Error {
163
+ var AgentSnapshotVersionMismatchError = class extends AgentError {
718
164
  from;
719
165
  to;
720
166
  machineId;
721
167
  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";
168
+ super("snapshot-version-mismatch", `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.`);
169
+ this.name = "AgentSnapshotVersionMismatchError";
724
170
  this.from = from;
725
171
  this.to = to;
726
172
  this.machineId = machineId;
727
173
  }
728
174
  };
729
175
  /**
730
- * Thrown by {@link runAgentToCompletion} when the run settles `idle` instead of
176
+ * Thrown by {@link generateResult} when the run settles `idle` instead of
731
177
  * `done`: the machine paused for external input. Carries the idle `snapshot`
732
178
  * and `acceptedTypes` (the event types that could resume it, via
733
179
  * {@link getAcceptedEvents}). Use {@link runAgent} directly when idle is an
734
180
  * expected outcome you handle.
735
181
  */
736
- var AgentIdleError = class extends Error {
182
+ var AgentIdleError = class extends AgentError {
737
183
  snapshot;
738
184
  acceptedTypes;
739
185
  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)"}.`);
186
+ super("agent-idle", `generateResult: the machine paused (idle) instead of completing. Resume it by calling runAgent with one of these events: ${acceptedTypes.length > 0 ? acceptedTypes.join(", ") : "(none)"}.`);
741
187
  this.name = "AgentIdleError";
742
188
  this.snapshot = snapshot;
743
189
  this.acceptedTypes = acceptedTypes;
@@ -751,17 +197,104 @@ var AgentIdleError = class extends Error {
751
197
  * {@link traceTransitions}.
752
198
  */
753
199
  const AGENT_TRACE_SCHEMA_VERSION = 1;
200
+ const TRACE_ENVELOPE_KEYS = [
201
+ "schemaVersion",
202
+ "runId",
203
+ "seq",
204
+ "timestamp",
205
+ "machineId",
206
+ "machineVersion",
207
+ "type",
208
+ "status",
209
+ "cause",
210
+ "eventId",
211
+ "reasoning",
212
+ "chunk"
213
+ ];
214
+ /**
215
+ * Best-effort JSON projection of an arbitrary value. Never throws: functions,
216
+ * symbols, `undefined`, and cyclic back-references are DROPPED (array holes
217
+ * become `null`), non-finite numbers become `null`, `bigint`s become strings,
218
+ * `Error`s become `{ name, message, stack?, code? }`, and anything with a
219
+ * `toJSON()` (e.g. `Date`) is projected through it — the same losses a
220
+ * `JSON.parse(JSON.stringify(...))` round-trip incurs, minus the throws.
221
+ */
222
+ function toJsonValue(value, ancestors) {
223
+ if (value === null) return null;
224
+ const type = typeof value;
225
+ if (type === "string" || type === "boolean") return value;
226
+ if (type === "number") return Number.isFinite(value) ? value : null;
227
+ if (type === "bigint") return value.toString();
228
+ if (type !== "object") return;
229
+ const object = value;
230
+ if (ancestors.includes(object)) return;
231
+ const nextAncestors = [...ancestors, object];
232
+ if (object instanceof Error) {
233
+ const serialized = {
234
+ name: object.name,
235
+ message: object.message
236
+ };
237
+ if (typeof object.stack === "string") serialized.stack = object.stack;
238
+ const code = object.code;
239
+ if (typeof code === "string") serialized.code = code;
240
+ const cause = toJsonValue(object.cause, nextAncestors);
241
+ if (cause !== void 0) serialized.cause = cause;
242
+ return serialized;
243
+ }
244
+ const toJSON = object.toJSON;
245
+ if (typeof toJSON === "function") return toJsonValue(toJSON.call(object), nextAncestors);
246
+ if (Array.isArray(object)) return object.map((item) => toJsonValue(item, nextAncestors) ?? null);
247
+ const out = {};
248
+ for (const [key, item] of Object.entries(object)) {
249
+ const serializedItem = toJsonValue(item, nextAncestors);
250
+ if (serializedItem !== void 0) out[key] = serializedItem;
251
+ }
252
+ return out;
253
+ }
254
+ /**
255
+ * Projects an {@link AgentTraceEvent} into a guaranteed JSON-safe envelope —
256
+ * the form the trace stream is actually sold for (one `JSON.stringify` per line
257
+ * in a JSONL file). Live values are sanitized rather than trusted:
258
+ *
259
+ * - Snapshots (`run.start`, `machine.transition`, `run.end`) go through the
260
+ * same JSON round-trip as {@link persistSnapshot}, so what lands on disk is
261
+ * what a resume would see.
262
+ * - `request.end`'s `raw` (a provider SDK object, frequently cyclic) is DROPPED
263
+ * unless `includeRaw` is set, in which case it is sanitized like everything
264
+ * else.
265
+ * - Non-serializable values anywhere (functions, symbols, `undefined`, cyclic
266
+ * back-references) are dropped; `Error`s become `{ name, message, stack?,
267
+ * code? }` instead of `{}`. Nothing throws.
268
+ *
269
+ * @example
270
+ * ```ts
271
+ * await appendFile('trace.jsonl', JSON.stringify(serializeTraceEvent(event)) + '\n');
272
+ * ```
273
+ */
274
+ function serializeTraceEvent(event, options = {}) {
275
+ const out = {};
276
+ for (const [key, value] of Object.entries(event)) {
277
+ if (key === "raw" && !options.includeRaw) continue;
278
+ if (TRACE_ENVELOPE_KEYS.includes(key)) {
279
+ if (value !== void 0) out[key] = value;
280
+ continue;
281
+ }
282
+ const serialized = toJsonValue(value, []);
283
+ if (serialized !== void 0) out[key] = serialized;
284
+ }
285
+ return out;
286
+ }
754
287
  let nextRunAgentTraceId = 1;
755
- var MaxModelCallsExceededError = class extends Error {
288
+ var AgentMaxModelCallsExceededError = class extends AgentError {
756
289
  constructor() {
757
- super("runAgent exceeded maxModelCalls.");
758
- this.name = "MaxModelCallsExceededError";
290
+ super("max-model-calls-exceeded", "runAgent exceeded maxModelCalls.");
291
+ this.name = "AgentMaxModelCallsExceededError";
759
292
  }
760
293
  };
761
294
  function wrapsDecisionExhausted(error) {
762
295
  let current = error;
763
296
  for (let depth = 0; depth < 10 && current != null; depth++) {
764
- if (current instanceof DecisionExhaustedError) return true;
297
+ if (current instanceof AgentDecisionExhaustedError) return true;
765
298
  current = current.cause;
766
299
  }
767
300
  return false;
@@ -789,14 +322,14 @@ function collectConfiguredInvokeSrcs(stateConfig, stateName, out) {
789
322
  /**
790
323
  * Duck-types a state machine actor logic (an invoked child machine) vs. any
791
324
  * other actor logic. xstate's `StateMachine` carries `.config`, `.root`, and
792
- * a `.provide(...)` method plus an `implementations.actorSources` map — this
325
+ * a `.provide(...)` method plus a `sources.actors` map — this
793
326
  * combination is unique to machines and survives the dual-package/version
794
327
  * boundary an `instanceof` check would not. Used to descend the bind-time
795
328
  * walk into invoked child machines (their internal agent requests are opaque
796
329
  * to the parent-level source walk otherwise).
797
330
  */
798
331
  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;
332
+ return !!logic && typeof logic === "object" && "config" in logic && "root" in logic && typeof logic.provide === "function" && typeof logic.sources === "object" && !!logic.sources?.actors;
800
333
  }
801
334
  /**
802
335
  * Fails fast (throws) at bind time — before any actor runs — when the
@@ -831,11 +364,11 @@ function assertMachineBindable(machine, effectiveSources, options, ctx) {
831
364
  assertChildMachineBindable(src, src, stateName, options, ctx);
832
365
  continue;
833
366
  }
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.`);
367
+ 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({ actors: { name: logic } })) and invoke it by name.`);
835
368
  continue;
836
369
  }
837
370
  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}': ... } }).`);
371
+ if (logic === void 0) throw new Error(`runAgent: ${where} '${stateName}' invokes unregistered actor source '${src}'. Provide it via machine.provide({ actors: { '${src}': ... } }) or runAgent(machine, { actors: { '${src}': ... } }).`);
839
372
  if (isStateMachine(logic)) {
840
373
  assertChildMachineBindable(logic, src, stateName, options, ctx);
841
374
  continue;
@@ -859,7 +392,7 @@ function assertMachineBindable(machine, effectiveSources, options, ctx) {
859
392
  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
393
  continue;
861
394
  }
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}': ... } }).`);
395
+ if (isUnboundPlaceholder(logic)) throw new Error(`runAgent: ${where} '${stateName}' invokes actor source '${src}', which has no host execution. Provide it via machine.provide({ actors: { '${src}': ... } }) or runAgent(machine, { actors: { '${src}': ... } }).`);
863
396
  }
864
397
  }
865
398
  /** Descends the bind-time walk into an invoked child state machine, guarding
@@ -868,7 +401,7 @@ function assertChildMachineBindable(childMachine, childSrc, stateName, options,
868
401
  if (ctx.visited.has(childMachine)) return;
869
402
  const childName = typeof childSrc === "string" ? childSrc : childMachine.config.id ?? "(child machine)";
870
403
  const childPath = ctx.childPath ? `${ctx.childPath} > ${childName}` : childName;
871
- const childSources = childMachine.implementations.actorSources;
404
+ const childSources = childMachine.sources.actors;
872
405
  assertMachineBindable(childMachine, childSources, options, {
873
406
  isChild: true,
874
407
  childPath,
@@ -884,7 +417,21 @@ function assertChildMachineBindable(childMachine, childSrc, stateName, options,
884
417
  * runAgent's executors — this error is only for the unrebindable direct-object
885
418
  * case. */
886
419
  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.`);
420
+ return /* @__PURE__ */ new Error(`runAgent: child machine '${childPath}' (state '${stateName}') invokes ${kind} source '${requestSrc}', which has no host execution and is reached through a direct-object invoke src that runAgent cannot rebind. Requests reached through string-keyed actor sources inherit runAgent's generateText/streamText/decide executors automatically; a direct-object child machine does not. Either bind the request with its own executor (requestLogic.withExecutor(...)), or register the child as a string-keyed actor source (machine.provide({ actors: { <child>: childMachine } })) and invoke it by name.`);
421
+ }
422
+ /**
423
+ * True when the snapshot's active states declare a transition for the reserved
424
+ * `'@agent.usage'` type EXPLICITLY. A catch-all `on: { '*': … }` deliberately
425
+ * does not count: a wildcard is a machine's own event vocabulary, not an
426
+ * opt-in to a library-reserved event, and `snapshot.can(event)` alone cannot
427
+ * tell the two apart (it answers "would this event be taken?", which a
428
+ * wildcard makes true for everything). Gating delivery on the explicit
429
+ * declaration is what keeps `@agent.usage` opt-in by construction — and keeps
430
+ * a wildcard machine's context and event log byte-identical to a run without
431
+ * the feature. @internal
432
+ */
433
+ function declaresUsageTransition(snapshot) {
434
+ return getNextTransitions(snapshot).some((transition) => transition.eventType === AGENT_USAGE_EVENT_TYPE);
888
435
  }
889
436
  /** Reads the durable invoke id/src off the async actor's own ref (`self`). */
890
437
  function selfIdAndSrc(self) {
@@ -946,7 +493,9 @@ function wrapTextLogicForRunAgent(logic, runCtx) {
946
493
  }, self);
947
494
  runCtx.onChunk?.(chunk, { request: agentRequest });
948
495
  },
949
- signal
496
+ signal,
497
+ ...runCtx.runId !== void 0 ? { runId: runCtx.runId } : {},
498
+ ...id !== "" ? { requestId: id } : {}
950
499
  });
951
500
  const output = await normalizeGeneratorResult(raw, id, {
952
501
  request,
@@ -961,6 +510,14 @@ function wrapTextLogicForRunAgent(logic, runCtx) {
961
510
  });
962
511
  const rawReasoning = raw?.reasoning;
963
512
  const reasoning = typeof rawReasoning === "string" ? rawReasoning : void 0;
513
+ const usage = extractCallUsage(raw);
514
+ if (usage) runCtx.recordUsage?.(usage, {
515
+ kind: "text",
516
+ ...id !== "" ? { id } : {},
517
+ ...src !== "" ? { src } : {},
518
+ model: request.model,
519
+ ...request.name !== void 0 ? { name: request.name } : {}
520
+ }, self);
964
521
  runCtx.onResult?.(agentRequest, {
965
522
  output,
966
523
  raw
@@ -970,7 +527,8 @@ function wrapTextLogicForRunAgent(logic, runCtx) {
970
527
  request: agentRequest,
971
528
  output,
972
529
  raw,
973
- ...reasoning !== void 0 ? { reasoning } : {}
530
+ ...reasoning !== void 0 ? { reasoning } : {},
531
+ ...usage !== void 0 ? { usage } : {}
974
532
  }, self);
975
533
  return { output };
976
534
  } catch (error) {
@@ -983,7 +541,7 @@ function wrapTextLogicForRunAgent(logic, runCtx) {
983
541
  }
984
542
  });
985
543
  }
986
- function createCountingDecide(runCtx, self) {
544
+ function createCountingDecide(runCtx, self, kind = "decision") {
987
545
  return async (attemptRequest) => {
988
546
  runCtx.consumeModelCall();
989
547
  runCtx.onTrace?.({
@@ -991,7 +549,20 @@ function createCountingDecide(runCtx, self) {
991
549
  request: attemptRequest
992
550
  }, self);
993
551
  try {
994
- const result = await runCtx.decide(attemptRequest);
552
+ const result = await runCtx.decide(runCtx.runId !== void 0 ? {
553
+ ...attemptRequest,
554
+ runId: runCtx.runId
555
+ } : attemptRequest);
556
+ const usage = extractCallUsage(result);
557
+ if (usage) {
558
+ const { src } = selfIdAndSrc(self);
559
+ runCtx.recordUsage?.(usage, {
560
+ kind,
561
+ ...attemptRequest.id ? { id: attemptRequest.id } : {},
562
+ ...src !== "" ? { src } : {},
563
+ model: attemptRequest.model
564
+ }, self);
565
+ }
995
566
  runCtx.onResult?.(attemptRequest, {
996
567
  output: result.event,
997
568
  raw: result
@@ -1000,7 +571,8 @@ function createCountingDecide(runCtx, self) {
1000
571
  type: "request.end",
1001
572
  request: attemptRequest,
1002
573
  output: result.event,
1003
- raw: result
574
+ raw: result,
575
+ ...usage !== void 0 ? { usage } : {}
1004
576
  }, self);
1005
577
  return result;
1006
578
  } catch (error) {
@@ -1078,7 +650,7 @@ function createRunAgentPlanLogic(logic, runCtx) {
1078
650
  if (!runCtx.decide) throw new Error("runAgent: no 'decide' executor provided.");
1079
651
  const { id } = selfIdAndSrc(self);
1080
652
  const stopOn = new Set(input.stopOn ?? []);
1081
- const countingDecide = createCountingDecide(runCtx, self);
653
+ const countingDecide = createCountingDecide(runCtx, self, "plan");
1082
654
  const base = logic.request(input);
1083
655
  let ledger = initialPlanLedger(logic, input);
1084
656
  const end = (stopped) => {
@@ -1208,6 +780,10 @@ function provideTraceSink(onTrace) {
1208
780
  * actor off `self._parent`, always present under a live `createActor` tree.
1209
781
  * `onTrace` (when given) mints a per-root-actor envelope. `schemas` come from
1210
782
  * the machine's registered `setupAgent` execution options.
783
+ *
784
+ * `recordUsage` has no run-level aggregate to fold into here (there is no
785
+ * run), so it does one thing: deliver the reserved `@agent.usage` event, gated
786
+ * exactly like runAgent's — see {@link deliverUsageToInvokingActor}.
1211
787
  */
1212
788
  function provideBindContext(machine, executors, options) {
1213
789
  return {
@@ -1217,11 +793,45 @@ function provideBindContext(machine, executors, options) {
1217
793
  onChunk: options.onChunk ? (chunk) => options.onChunk(chunk) : void 0,
1218
794
  onTrace: provideTraceSink(options.onTrace),
1219
795
  consumeModelCall: () => {},
796
+ recordUsage: (usage, source, self) => {
797
+ deliverUsageToInvokingActor(usage, source ?? {}, self);
798
+ },
1220
799
  actorHolder: { actorRef: void 0 },
1221
800
  schemas: getRegisteredAgentExecutionOptions(machine).schemas
1222
801
  };
1223
802
  }
1224
803
  /**
804
+ * `provideExecutors`' counterpart to runAgent's `deliverUsageEvent`: after a
805
+ * bound call settles with reported usage, send the reserved
806
+ * `@agent.usage` event to the machine actor that INVOKED the request — read
807
+ * off the settling request actor's `self._parent`, which under a live
808
+ * `createActor` tree is always the invoking machine (there is no run-scoped
809
+ * root actor on this path).
810
+ *
811
+ * Gated identically to runAgent: the invoking snapshot must be active, must
812
+ * declare an `'@agent.usage'` transition EXPLICITLY (see
813
+ * {@link declaresUsageTransition} — a catch-all `on: { '*' }` is not an opt-in),
814
+ * and must be able to take the event. There is no cycle to settle in
815
+ * uncontrolled mode, so there are no dropped stragglers.
816
+ *
817
+ * Delivery follows `provideExecutors`' binding boundary: only sources IT bound
818
+ * report here, so an invoked child machine that was not itself passed through
819
+ * `provideExecutors` reports nothing. @internal
820
+ */
821
+ function deliverUsageToInvokingActor(usage, source, self) {
822
+ const actorRef = self?._parent;
823
+ if (!actorRef) return;
824
+ const snapshot = actorRef.getSnapshot();
825
+ if (snapshot?.status !== "active" || !declaresUsageTransition(snapshot)) return;
826
+ const event = {
827
+ type: AGENT_USAGE_EVENT_TYPE,
828
+ ...source,
829
+ usage
830
+ };
831
+ if (!snapshot.can(event)) return;
832
+ actorRef.send(event);
833
+ }
834
+ /**
1225
835
  * Host-binds one text/stream source for {@link provideExecutors} using the SAME
1226
836
  * emission helper as `runAgent` ({@link wrapTextLogicForRunAgent}), so a bound
1227
837
  * text request emits request.start/stream.chunk/request.end/request.error with
@@ -1264,7 +874,7 @@ function bindPlanForProvide(machine, logic, executors, options) {
1264
874
  function rebindChildMachine(childMachine, runCtx, visited) {
1265
875
  if (visited.has(childMachine)) return childMachine;
1266
876
  const childVisited = new Set([...visited, childMachine]);
1267
- const sources = childMachine.implementations.actorSources;
877
+ const sources = childMachine.sources.actors;
1268
878
  const wrapped = {};
1269
879
  for (const [key, logic] of Object.entries(sources)) {
1270
880
  if (isDecisionLogic(logic)) {
@@ -1285,7 +895,7 @@ function rebindChildMachine(childMachine, runCtx, visited) {
1285
895
  continue;
1286
896
  }
1287
897
  }
1288
- return Object.keys(wrapped).length > 0 ? childMachine.provide({ actorSources: wrapped }) : childMachine;
898
+ return Object.keys(wrapped).length > 0 ? childMachine.provide({ actors: wrapped }) : childMachine;
1289
899
  }
1290
900
  /**
1291
901
  * Runs an agent machine to completion or idle: a `createActor` host that
@@ -1302,7 +912,7 @@ function rebindChildMachine(childMachine, runCtx, visited) {
1302
912
  *
1303
913
  * Binding happens **before** the actor starts: every invoke the machine
1304
914
  * could reach is walked and checked against the effective actor sources
1305
- * (`options.actorSources` merged onto the machine), so a missing
915
+ * (`options.actors` merged onto the machine), so a missing
1306
916
  * `streamText`/`decide` executor or any other unbound actor source throws
1307
917
  * immediately — a bind-time error, not a mid-run failure. The one exception
1308
918
  * is `agent.userInput`: unhandled, it binds as a pending placeholder that
@@ -1327,14 +937,42 @@ function rebindChildMachine(childMachine, runCtx, visited) {
1327
937
  * lives in an adapter — use `createAiSdkExecutors` from '@statelyai/agent/ai-sdk'.
1328
938
  */
1329
939
  async function runAgent(machine, options) {
940
+ return createAgentSession(machine, options, { oneShot: true }).settled();
941
+ }
942
+ /**
943
+ * Session mode: {@link runAgent}'s engine with a long-lived actor. Use it when
944
+ * the agent is a *session* fed by external events (chat turns, device or
945
+ * timer events, a socket) rather than a one-shot job — you keep the log,
946
+ * budget, traces, and idle semantics that bare `provideExecutors` +
947
+ * `createActor` would forfeit.
948
+ *
949
+ * ```ts
950
+ * const session = createAgentActor(machine, { input, executors });
951
+ * let result = await session.settled(); // first quiescence
952
+ * while (result.status === "idle") {
953
+ * session.actor.send(await nextUserEvent(result.snapshot));
954
+ * result = await session.settled(); // next quiescence, same log
955
+ * }
956
+ * session.stop();
957
+ * ```
958
+ *
959
+ * Accepts the same options as {@link runAgent} (including `snapshot`/`events`
960
+ * resume). Not yet supported in session mode: `getRequests` re-interpretation
961
+ * across cycles behaves per-cycle exactly as in `runAgent`.
962
+ */
963
+ function createAgentActor(machine, options) {
964
+ return createAgentSession(machine, options, { oneShot: false });
965
+ }
966
+ function createAgentSession(machine, options, lifecycle) {
1330
967
  const maxModelCalls = options.maxModelCalls ?? 100;
1331
968
  let modelCallCount = 0;
1332
969
  let budgetExceeded = false;
1333
970
  let warnedNonSerializable = false;
971
+ let warnedHeuristicIdle = false;
1334
972
  const runId = `run_${nextRunAgentTraceId++}`;
1335
973
  let traceSeq = 0;
1336
974
  const machineId = machine.config.id ?? machine.id ?? "(machine)";
1337
- const machineVersion = options.machineVersion ?? getMachineStructuralHash(machine);
975
+ const machineVersion = options.machineVersion ?? machine.version ?? getMachineStructuralHash(machine);
1338
976
  const agentMeta = {
1339
977
  machineId,
1340
978
  version: machineVersion
@@ -1354,13 +992,47 @@ async function runAgent(machine, options) {
1354
992
  });
1355
993
  };
1356
994
  const consumeModelCall = () => {
1357
- if (budgetExceeded) throw new MaxModelCallsExceededError();
1358
- modelCallCount += 1;
1359
- if (modelCallCount > maxModelCalls) {
995
+ if (budgetExceeded) throw new AgentMaxModelCallsExceededError();
996
+ if (modelCallCount + 1 > maxModelCalls) {
1360
997
  budgetExceeded = true;
1361
- throw new MaxModelCallsExceededError();
998
+ throw new AgentMaxModelCallsExceededError();
1362
999
  }
1000
+ modelCallCount += 1;
1001
+ };
1002
+ const tokenTotals = {};
1003
+ const cycleGate = { isResolved: () => false };
1004
+ const deliverUsageEvent = (usage, source) => {
1005
+ const actorRef = actorHolder.actorRef;
1006
+ if (!actorRef) return;
1007
+ const event = {
1008
+ type: AGENT_USAGE_EVENT_TYPE,
1009
+ ...source,
1010
+ usage
1011
+ };
1012
+ const snapshot = actorRef.getSnapshot();
1013
+ if (snapshot?.status !== "active" || !declaresUsageTransition(snapshot)) return;
1014
+ if (cycleGate.isResolved()) {
1015
+ onTrace({
1016
+ type: "usage.dropped",
1017
+ event,
1018
+ reason: "settled"
1019
+ });
1020
+ return;
1021
+ }
1022
+ if (!snapshot.can(event)) return;
1023
+ actorRef.send(event);
1363
1024
  };
1025
+ const recordUsage = (usage, source = {}) => {
1026
+ for (const field of AGENT_USAGE_TOKEN_FIELDS) {
1027
+ const value = usage[field];
1028
+ if (typeof value === "number" && Number.isFinite(value)) tokenTotals[field] = (tokenTotals[field] ?? 0) + value;
1029
+ }
1030
+ deliverUsageEvent(usage, source);
1031
+ };
1032
+ const runUsage = () => ({
1033
+ ...tokenTotals,
1034
+ modelCalls: modelCallCount
1035
+ });
1364
1036
  const warnNonSerializableContext = (snapshot) => {
1365
1037
  if (warnedNonSerializable || process.env.NODE_ENV === "production") return;
1366
1038
  let offending = [];
@@ -1373,8 +1045,8 @@ async function runAgent(machine, options) {
1373
1045
  warnedNonSerializable = true;
1374
1046
  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
1047
  };
1376
- const provided = machine.provide({ actorSources: options.actorSources });
1377
- const effectiveSources = provided.implementations.actorSources;
1048
+ const provided = machine.provide({ actors: options.actors });
1049
+ const effectiveSources = provided.sources.actors;
1378
1050
  assertBindable(provided, effectiveSources, {
1379
1051
  hasGenerateText: !!options.executors?.generateText,
1380
1052
  hasDecide: !!options.executors?.decide,
@@ -1390,7 +1062,9 @@ async function runAgent(machine, options) {
1390
1062
  onResult: options.onResult,
1391
1063
  onTrace,
1392
1064
  consumeModelCall,
1065
+ recordUsage,
1393
1066
  actorHolder,
1067
+ runId,
1394
1068
  schemas: getRegisteredAgentExecutionOptions(machine).schemas
1395
1069
  };
1396
1070
  let userInputIsPlaceholder = false;
@@ -1424,11 +1098,12 @@ async function runAgent(machine, options) {
1424
1098
  continue;
1425
1099
  }
1426
1100
  }
1427
- const boundMachine = provided.provide({ actorSources: wrappedSources });
1428
- const isSuspended = options.isSuspended ?? getMachineSuspensionPredicate(machine) ?? (() => false);
1101
+ const boundMachine = provided.provide({ actors: wrappedSources });
1102
+ const declaredSuspensionPredicate = options.isSuspended ?? getMachineSuspensionPredicate(machine);
1103
+ const isSuspended = declaredSuspensionPredicate ?? (() => false);
1429
1104
  let effectiveSnapshot = options.snapshot;
1430
1105
  if (effectiveSnapshot !== void 0) {
1431
- const from = effectiveSnapshot.agentMeta?.version;
1106
+ const from = effectiveSnapshot.agentMeta?.version ?? effectiveSnapshot.version;
1432
1107
  if (from !== void 0 && from !== machineVersion) {
1433
1108
  const info = {
1434
1109
  from,
@@ -1437,11 +1112,23 @@ async function runAgent(machine, options) {
1437
1112
  if (options.migrateSnapshot) effectiveSnapshot = options.migrateSnapshot(effectiveSnapshot, info);
1438
1113
  else {
1439
1114
  const mode = options.onVersionMismatch ?? "throw";
1440
- if (mode === "throw") throw new SnapshotVersionMismatchError(from, machineVersion, machineId);
1115
+ if (mode === "throw") throw new AgentSnapshotVersionMismatchError(from, machineVersion, machineId);
1441
1116
  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
1117
  }
1443
1118
  }
1444
1119
  }
1120
+ const resumeEvents = options.events;
1121
+ if (effectiveSnapshot === void 0 && options.events !== void 0 && options.events[0]?.event.type === "@agent.init") {
1122
+ const { snapshot: replayedSnapshot } = replay(machine, resumeEvents, { machineVersion });
1123
+ effectiveSnapshot = machine.getPersistedSnapshot(replayedSnapshot);
1124
+ }
1125
+ const machineOwnVersion = machine.version;
1126
+ if (effectiveSnapshot !== void 0 && effectiveSnapshot.version !== machineOwnVersion) {
1127
+ const aligned = Object.assign(Object.create(Object.getPrototypeOf(effectiveSnapshot)), effectiveSnapshot);
1128
+ if (machineOwnVersion === void 0) delete aligned.version;
1129
+ else aligned.version = machineOwnVersion;
1130
+ effectiveSnapshot = aligned;
1131
+ }
1445
1132
  const priorMessages = getAgentMessages(effectiveSnapshot);
1446
1133
  const messages = typeof options.messages === "function" ? [...options.messages([...priorMessages])] : [...priorMessages, ...options.messages ?? []];
1447
1134
  const stampMessages = (snapshot) => {
@@ -1451,18 +1138,60 @@ async function runAgent(machine, options) {
1451
1138
  if (effectiveSnapshot !== void 0 && options.event !== void 0 && (options.onIllegalResumeEvent ?? "throw") === "throw") {
1452
1139
  const acceptedTypes = getAcceptedEvents(createActor(boundMachine, { snapshot: effectiveSnapshot }).getSnapshot(), { schemas: runCtx.schemas }).map((descriptor) => descriptor.type);
1453
1140
  const eventType = options.event.type;
1454
- if (!acceptedTypes.includes(eventType)) throw new IllegalResumeEventError(eventType, acceptedTypes);
1141
+ if (!acceptedTypes.includes(eventType)) throw new AgentIllegalResumeEventError(eventType, acceptedTypes);
1142
+ }
1143
+ const replayEvents = [...resumeEvents ?? []];
1144
+ const replayEventIds = /* @__PURE__ */ new Set();
1145
+ for (let index = 0; index < replayEvents.length; index++) {
1146
+ const entry = replayEvents[index];
1147
+ assertAgentLogEntry(entry);
1148
+ if (entry.index !== index) throw new Error(`runAgent events must be contiguous from index 0; found entry.index ${entry.index} at position ${index}.`);
1149
+ if (entry.machineId !== machineId || entry.machineVersion !== machineVersion) throw new AgentReplayMachineMismatchError(entry.id, entry.index, {
1150
+ machineId,
1151
+ machineVersion
1152
+ }, {
1153
+ machineId: entry.machineId,
1154
+ machineVersion: entry.machineVersion
1155
+ });
1156
+ if (replayEventIds.has(entry.id)) throw new Error(`runAgent events contain duplicate event id '${entry.id}'.`);
1157
+ replayEventIds.add(entry.id);
1455
1158
  }
1456
- return new Promise((resolvePromise) => {
1159
+ const hasCompleteReplayHistory = replayEvents[0]?.event.type === "@agent.init" || effectiveSnapshot === void 0 && replayEvents.length === 0;
1160
+ const appendReplayEvent = (event) => {
1161
+ const entry = createReplayEntry(machine, replayEvents, event, {
1162
+ machineVersion,
1163
+ verification: hasCompleteReplayHistory
1164
+ });
1165
+ if (replayEventIds.has(entry.id)) throw new Error(`runAgent generated duplicate event id '${entry.id}'.`);
1166
+ replayEventIds.add(entry.id);
1167
+ replayEvents.push(entry);
1168
+ options.onEvent?.(entry);
1169
+ return entry;
1170
+ };
1171
+ if (replayEvents.length === 0 && effectiveSnapshot === void 0) {
1172
+ const entry = initEntry(machine, options.input, { machineVersion });
1173
+ replayEventIds.add(entry.id);
1174
+ replayEvents.push(entry);
1175
+ options.onEvent?.(entry);
1176
+ }
1177
+ return (() => {
1457
1178
  let settled = false;
1179
+ let finalized = false;
1180
+ cycleGate.isResolved = () => settled;
1181
+ let lastResult;
1182
+ const waiters = [];
1458
1183
  let idleTimer;
1459
1184
  let actor;
1460
1185
  let deliveringResumeEvent = options.event !== void 0;
1461
- const settle = (result) => {
1186
+ const settle = (outcome) => {
1462
1187
  if (settled) return;
1463
1188
  settled = true;
1189
+ const result = {
1190
+ ...outcome,
1191
+ events: [...replayEvents],
1192
+ usage: runUsage()
1193
+ };
1464
1194
  if (idleTimer !== void 0) clearTimeout(idleTimer);
1465
- if (options.signal) options.signal.removeEventListener("abort", onAbort);
1466
1195
  stampAgentMeta(result.snapshot);
1467
1196
  stampMessages(result.snapshot);
1468
1197
  if ("persistedSnapshot" in result) {
@@ -1471,10 +1200,15 @@ async function runAgent(machine, options) {
1471
1200
  }
1472
1201
  onTrace({
1473
1202
  type: "run.end",
1474
- ...result
1203
+ ...outcome
1475
1204
  });
1476
- actor.stop();
1477
- resolvePromise(result);
1205
+ if (lifecycle.oneShot || result.status !== "idle") {
1206
+ finalized = true;
1207
+ if (options.signal) options.signal.removeEventListener("abort", onAbort);
1208
+ actor.stop();
1209
+ }
1210
+ lastResult = result;
1211
+ for (const resolve of waiters.splice(0)) resolve(result);
1478
1212
  };
1479
1213
  const onAbort = () => {
1480
1214
  settle({
@@ -1527,6 +1261,7 @@ async function runAgent(machine, options) {
1527
1261
  generateText: runCtx.generateText,
1528
1262
  decide: runCtx.decide ? createCountingDecide(runCtx, void 0) : void 0,
1529
1263
  consumeModelCall,
1264
+ recordUsage,
1530
1265
  nextRequestId: () => `interpret_${++interpretSeq}`,
1531
1266
  onTrace,
1532
1267
  onResult: runCtx.onResult,
@@ -1563,7 +1298,13 @@ async function runAgent(machine, options) {
1563
1298
  if (settled) return;
1564
1299
  const current = actor.getSnapshot();
1565
1300
  if (isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) {
1566
- if (!maybeInterpret(current)) settleIdle(current);
1301
+ if (!maybeInterpret(current)) {
1302
+ if (!declaredSuspensionPredicate && current.status === "active" && !warnedHeuristicIdle && process.env.NODE_ENV !== "production") {
1303
+ warnedHeuristicIdle = true;
1304
+ console.warn("[@statelyai/agent] runAgent settled idle via the timing heuristic (no suspension predicate declared). This is best-effort; for deterministic idle detection, declare setupAgent({ isSuspended }) or pass runAgent(machine, { isSuspended }), e.g. (s) => s.hasTag('waiting').");
1305
+ }
1306
+ settleIdle(current);
1307
+ }
1567
1308
  }
1568
1309
  }, 0);
1569
1310
  };
@@ -1573,12 +1314,21 @@ async function runAgent(machine, options) {
1573
1314
  inspect: (event) => {
1574
1315
  if (typeof options.inspect === "function") options.inspect(event);
1575
1316
  else options.inspect?.next?.(event);
1576
- if (settled || event.type !== "@xstate.transition" || event.actorRef !== actor.ref) return;
1317
+ if (event.type !== "@xstate.transition" || event.actorRef !== actor.ref) return;
1318
+ if (settled) {
1319
+ if (finalized) return;
1320
+ settled = false;
1321
+ lastResult = void 0;
1322
+ }
1577
1323
  const snapshot = event.snapshot;
1324
+ let eventId;
1325
+ if (event.event.type !== "@xstate.init" && (event.sourceRef !== event.actorRef || event.event.type === "xstate.timer")) eventId = appendReplayEvent(event.event).id;
1326
+ else if (event.event.type === "@xstate.init") eventId = replayEvents[0]?.event.type === "@agent.init" ? replayEvents[0].id : void 0;
1578
1327
  onTrace({
1579
1328
  type: "machine.transition",
1580
1329
  snapshot,
1581
- event: event.event
1330
+ event: event.event,
1331
+ ...eventId !== void 0 ? { eventId } : {}
1582
1332
  });
1583
1333
  options.onTransition?.(snapshot, event.event);
1584
1334
  if (snapshot.status === "done") {
@@ -1623,6 +1373,19 @@ async function runAgent(machine, options) {
1623
1373
  });
1624
1374
  });
1625
1375
  for (const [type, handler] of Object.entries(options.on ?? {})) if (typeof handler === "function") actor.on(type, handler);
1376
+ const sessionApi = {
1377
+ actor,
1378
+ get events() {
1379
+ return replayEvents;
1380
+ },
1381
+ usage: runUsage,
1382
+ settled: () => settled && lastResult !== void 0 ? Promise.resolve(lastResult) : new Promise((resolve) => {
1383
+ waiters.push(resolve);
1384
+ }),
1385
+ stop: () => {
1386
+ actor.stop();
1387
+ }
1388
+ };
1626
1389
  if (options.signal) {
1627
1390
  if (options.signal.aborted) {
1628
1391
  settle({
@@ -1631,7 +1394,7 @@ async function runAgent(machine, options) {
1631
1394
  error: options.signal.reason ?? /* @__PURE__ */ new Error("Aborted"),
1632
1395
  snapshot: actor.getSnapshot()
1633
1396
  });
1634
- return;
1397
+ return sessionApi;
1635
1398
  }
1636
1399
  options.signal.addEventListener("abort", onAbort);
1637
1400
  }
@@ -1646,13 +1409,17 @@ async function runAgent(machine, options) {
1646
1409
  deliveringResumeEvent = false;
1647
1410
  actor.send(options.event);
1648
1411
  }
1649
- });
1412
+ return sessionApi;
1413
+ })();
1650
1414
  }
1651
1415
  /**
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}:
1416
+ * Runs an agent machine to a **final state**, for run-to-done flows where an
1417
+ * idle pause is unexpected. Wraps {@link runAgent}:
1654
1418
  *
1655
- * - `done` → resolves with `result.output` (the machine's `OutputFrom`).
1419
+ * - `done` → resolves with the done result: `result.output` (the machine's
1420
+ * `OutputFrom`) plus metadata — `result.snapshot`, the replayable
1421
+ * `result.events`, and the aggregated `result.usage` — the same shape
1422
+ * `generateText` users expect (`text` + call metadata).
1656
1423
  * - `idle` → throws {@link AgentIdleError} carrying the idle snapshot and the
1657
1424
  * event types that could resume it.
1658
1425
  * - `error` → throws `result.error` when it is an `Error`; otherwise wraps it
@@ -1660,18 +1427,18 @@ async function runAgent(machine, options) {
1660
1427
  * `.error` is the raw thrown value.
1661
1428
  *
1662
1429
  * Use {@link runAgent} directly when idle is an expected outcome you handle
1663
- * (human-in-the-loop, resumable flows); use `runAgentToCompletion` when the
1430
+ * (human-in-the-loop, resumable flows); use `generateResult` when the
1664
1431
  * machine is meant to run straight through to a final state.
1665
1432
  */
1666
- async function runAgentToCompletion(machine, options) {
1433
+ async function generateResult(machine, options) {
1667
1434
  const result = await runAgent(machine, options);
1668
- if (result.status === "done") return result.output;
1435
+ if (result.status === "done") return result;
1669
1436
  if (result.status === "idle") {
1670
1437
  const acceptedTypes = getAcceptedEvents(result.snapshot, { schemas: getRegisteredAgentExecutionOptions(machine).schemas }).map((descriptor) => descriptor.type);
1671
1438
  throw new AgentIdleError(result.snapshot, acceptedTypes);
1672
1439
  }
1673
1440
  if (result.error instanceof Error) throw result.error;
1674
- const wrapped = /* @__PURE__ */ new Error(`runAgentToCompletion: run failed with cause '${result.cause}'.`);
1441
+ const wrapped = /* @__PURE__ */ new Error(`generateResult: run failed with cause '${result.cause}'.`);
1675
1442
  wrapped.cause = result.cause;
1676
1443
  wrapped.error = result.error;
1677
1444
  throw wrapped;
@@ -1750,6 +1517,105 @@ function collectPendingUserInputs(snapshot) {
1750
1517
  return pending;
1751
1518
  }
1752
1519
  //#endregion
1520
+ //#region src/agent-run.ts
1521
+ /**
1522
+ * Wraps {@link runAgent} in a canonical run-stream handle: the run's trace
1523
+ * events as a pull-based {@link AgentRun.events} async iterator, alongside the
1524
+ * unchanged {@link AgentRun.result} promise.
1525
+ *
1526
+ * Semantics:
1527
+ *
1528
+ * 1. **Starts immediately.** The underlying `runAgent` is invoked on this call,
1529
+ * not on the first `events` iteration — work is already in flight when
1530
+ * `createAgentRun` returns.
1531
+ * 2. **Buffer, don't stall.** Events are queued in an unbounded in-memory
1532
+ * buffer as the run emits them; a slow or absent consumer never applies
1533
+ * backpressure to the run. Once `run.end` is delivered, the iterator
1534
+ * completes.
1535
+ * 3. **Composes `onTrace`.** If `options.onTrace` is passed, it still fires for
1536
+ * every event — the wrapper adds queue delivery, it does not replace the
1537
+ * caller's sink.
1538
+ * 4. **`result` mirrors `runAgent`.** Same resolution and rejection behavior
1539
+ * (see {@link AgentRun.result}). A no-op rejection handler is attached
1540
+ * internally so a caller who reads only `events` never trips an unhandled
1541
+ * rejection on a bind-time throw; the returned promise still rejects for a
1542
+ * caller who awaits it.
1543
+ * 5. **Resume is identical.** Options pass straight through, so starting from a
1544
+ * persisted `snapshot` (+ resume `event`) streams that run's events from its
1545
+ * own `run.start`, exactly as `runAgent` would run it.
1546
+ * 6. **Early termination does not cancel the run.** Breaking out of a
1547
+ * `for await` (or calling `events.return()`) stops delivery, but the run
1548
+ * keeps going and `result` still settles. Cancelling the run itself is
1549
+ * future work — pass `options.signal` to abort it.
1550
+ * 7. **`events` is single-consumer.** See {@link AgentRun.events}.
1551
+ */
1552
+ function createAgentRun(machine, options) {
1553
+ const buffer = [];
1554
+ const resolvers = [];
1555
+ let closed = false;
1556
+ let stopped = false;
1557
+ const push = (event) => {
1558
+ if (closed || stopped) return;
1559
+ const resolve = resolvers.shift();
1560
+ if (resolve) resolve({
1561
+ value: event,
1562
+ done: false
1563
+ });
1564
+ else buffer.push(event);
1565
+ };
1566
+ const close = () => {
1567
+ if (closed) return;
1568
+ closed = true;
1569
+ while (resolvers.length > 0) resolvers.shift()({
1570
+ value: void 0,
1571
+ done: true
1572
+ });
1573
+ };
1574
+ const userOnTrace = options.onTrace;
1575
+ const result = runAgent(machine, {
1576
+ ...options,
1577
+ onTrace: (event) => {
1578
+ push(event);
1579
+ userOnTrace?.(event);
1580
+ if (event.type === "run.end") close();
1581
+ }
1582
+ });
1583
+ result.then(close, close);
1584
+ return {
1585
+ events: {
1586
+ next() {
1587
+ if (buffer.length > 0) return Promise.resolve({
1588
+ value: buffer.shift(),
1589
+ done: false
1590
+ });
1591
+ if (closed || stopped) return Promise.resolve({
1592
+ value: void 0,
1593
+ done: true
1594
+ });
1595
+ return new Promise((resolve) => {
1596
+ resolvers.push(resolve);
1597
+ });
1598
+ },
1599
+ return() {
1600
+ stopped = true;
1601
+ buffer.length = 0;
1602
+ while (resolvers.length > 0) resolvers.shift()({
1603
+ value: void 0,
1604
+ done: true
1605
+ });
1606
+ return Promise.resolve({
1607
+ value: void 0,
1608
+ done: true
1609
+ });
1610
+ },
1611
+ [Symbol.asyncIterator]() {
1612
+ return this;
1613
+ }
1614
+ },
1615
+ result
1616
+ };
1617
+ }
1618
+ //#endregion
1753
1619
  //#region src/provide-executors.ts
1754
1620
  /**
1755
1621
  * Binds a machine's agent actor sources to a set of host `executors` in one
@@ -1764,8 +1630,8 @@ function collectPendingUserInputs(snapshot) {
1764
1630
  *
1765
1631
  * behaves like a normal XState actor whose agent invokes now reach real models.
1766
1632
  *
1767
- * Binding pass over `machine.implementations.actorSources` (after merging
1768
- * `options.actorSources`):
1633
+ * Binding pass over `machine.sources.actors` (after merging
1634
+ * `options.actors`):
1769
1635
  * - `mode: 'generate'` text source → `executors.generateText`
1770
1636
  * - `mode: 'stream'` text source → `executors.streamText`
1771
1637
  * - decision / `agent.decide` source → `executors.decide` (snapshot-driven
@@ -1779,7 +1645,7 @@ function collectPendingUserInputs(snapshot) {
1779
1645
  *
1780
1646
  * A source that already carries its own executor (`.withExecutor(...)`) is left
1781
1647
  * 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.
1648
+ * itself, so supply a handler via `options.actors` if the machine uses it.
1783
1649
  * Non-agent actors are untouched.
1784
1650
  *
1785
1651
  * Throws at bind time if a source needs an executor kind that `executors` does
@@ -1794,8 +1660,8 @@ function provideExecutors(machine, executors, options = {}) {
1794
1660
  onChunk: options.onChunk,
1795
1661
  onTrace: options.onTrace
1796
1662
  };
1797
- const provided = options.actorSources ? machine.provide({ actorSources: options.actorSources }) : machine;
1798
- const effectiveSources = provided.implementations.actorSources;
1663
+ const provided = options.actors ? machine.provide({ actors: options.actors }) : machine;
1664
+ const effectiveSources = provided.sources.actors;
1799
1665
  const wrappedSources = {};
1800
1666
  const invokedSrcs = getConfiguredInvokeSrcs(provided);
1801
1667
  for (const [key, logic] of Object.entries(effectiveSources)) {
@@ -1827,7 +1693,7 @@ function provideExecutors(machine, executors, options = {}) {
1827
1693
  continue;
1828
1694
  }
1829
1695
  }
1830
- return provided.provide({ actorSources: wrappedSources });
1696
+ return provided.provide({ actors: wrappedSources });
1831
1697
  }
1832
1698
  function missingExecutorError(src, kind, executor) {
1833
1699
  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.`);
@@ -1883,8 +1749,15 @@ function collectTransitionTargets(value, fromNode, index, out) {
1883
1749
  return;
1884
1750
  }
1885
1751
  if (typeof value === "object") {
1886
- const target = value.target;
1887
- if (target !== void 0) collectTransitionTargets(target, fromNode, index, out);
1752
+ const { target, to } = value;
1753
+ if (target !== void 0) {
1754
+ collectTransitionTargets(target, fromNode, index, out);
1755
+ return;
1756
+ }
1757
+ if (to !== void 0) {
1758
+ collectTransitionTargets(to, fromNode, index, out);
1759
+ return;
1760
+ }
1888
1761
  }
1889
1762
  }
1890
1763
  function resolveTargetString(target, fromNode, index, out) {
@@ -1896,12 +1769,16 @@ function resolveTargetString(target, fromNode, index, out) {
1896
1769
  if (index.has(resolved)) out.targets.push(resolved);
1897
1770
  else out.opaque = true;
1898
1771
  }
1899
- function outgoingTargets(node, index) {
1772
+ function outgoingTargets(node, index, staticTargets) {
1900
1773
  const out = {
1901
1774
  targets: [],
1902
1775
  opaque: false
1903
1776
  };
1904
1777
  const { config } = node;
1778
+ if (staticTargets) {
1779
+ for (const target of staticTargets[node.path] ?? []) resolveTargetString(target, node, index, out);
1780
+ return out;
1781
+ }
1905
1782
  for (const value of Object.values(config.on ?? {})) collectTransitionTargets(value, node, index, out);
1906
1783
  collectTransitionTargets(config.always, node, index, out);
1907
1784
  collectTransitionTargets(config.choice, node, index, out);
@@ -1913,7 +1790,7 @@ function outgoingTargets(node, index) {
1913
1790
  }
1914
1791
  return out;
1915
1792
  }
1916
- function computeReachable(rootConfig, index) {
1793
+ function computeReachable(rootConfig, index, staticTargets) {
1917
1794
  const reachable = /* @__PURE__ */ new Set();
1918
1795
  const queue = [];
1919
1796
  const markAncestors = (path) => {
@@ -1938,7 +1815,7 @@ function computeReachable(rootConfig, index) {
1938
1815
  while (queue.length > 0) {
1939
1816
  const node = index.get(queue.shift());
1940
1817
  if (!node) continue;
1941
- const { targets, opaque } = outgoingTargets(node, index);
1818
+ const { targets, opaque } = outgoingTargets(node, index, staticTargets);
1942
1819
  for (const target of targets) enter(target);
1943
1820
  if (opaque) for (const sibling of childrenOf(index, node.parentPath)) enter(sibling.path);
1944
1821
  }
@@ -1958,11 +1835,11 @@ function ancestorChain(node, index) {
1958
1835
  function hasNonEmptyOn(config) {
1959
1836
  return !!config.on && Object.keys(config.on).length > 0;
1960
1837
  }
1961
- function decisionKindOf(src, actorSources) {
1838
+ function decisionKindOf(src, actors) {
1962
1839
  if (typeof src === "string") {
1963
1840
  if (src === DECIDE_SRC) return "decision";
1964
1841
  if (src === PLAN_SRC) return "plan";
1965
- const logic = actorSources[src];
1842
+ const logic = actors[src];
1966
1843
  if (isDecisionLogic(logic)) return "decision";
1967
1844
  if (isPlanLogic(logic)) return "plan";
1968
1845
  return;
@@ -2007,7 +1884,7 @@ function checkUnreachableStates(ctx) {
2007
1884
  function checkDecideWithoutEvents(ctx) {
2008
1885
  const out = [];
2009
1886
  for (const node of ctx.index.values()) for (const invoke of node.invokes) {
2010
- const kind = decisionKindOf(invoke.src, ctx.actorSources);
1887
+ const kind = decisionKindOf(invoke.src, ctx.actors);
2011
1888
  if (!kind) continue;
2012
1889
  const selfHandles = hasNonEmptyOn(node.config);
2013
1890
  const ancestorHandles = ancestorChain(node, ctx.index).some((ancestor) => hasNonEmptyOn(ancestor.config));
@@ -2044,7 +1921,7 @@ function checkDirectObjectSrc(ctx) {
2044
1921
  code: "direct-object-src",
2045
1922
  severity: "warning",
2046
1923
  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.`
1924
+ message: `State '${node.path}' invokes a direct-object agent logic. Direct-object invoke srcs cannot be rebound by runAgent, so they inherit no host executors — call '.withExecutor(...)' on the logic, or register it as a string-keyed actor source (machine.provide({ actors: { name: logic } })) and invoke it by name.`
2048
1925
  });
2049
1926
  }
2050
1927
  return out;
@@ -2088,8 +1965,8 @@ function isBuiltinOrWildcardEvent(eventType) {
2088
1965
  }
2089
1966
  function checkUndeclaredEvents(ctx) {
2090
1967
  const declared = ctx.schemas?.events;
2091
- if (!declared || Object.keys(declared).length === 0) return [];
2092
- const declaredTypes = new Set(Object.keys(declared));
1968
+ const declaredTypes = new Set(Object.keys(declared ?? {}));
1969
+ if ([...declaredTypes].every((type) => type.startsWith("@agent."))) return [];
2093
1970
  const out = [];
2094
1971
  for (const node of ctx.index.values()) for (const eventType of Object.keys(node.config.on ?? {})) {
2095
1972
  if (declaredTypes.has(eventType) || isBuiltinOrWildcardEvent(eventType)) continue;
@@ -2141,7 +2018,7 @@ const LINT_CHECKS = [
2141
2018
  function lintAgentMachine(machine, options = {}) {
2142
2019
  const config = machine.config ?? {};
2143
2020
  const index = buildStateIndex(config);
2144
- const reachable = computeReachable(config, index);
2021
+ const reachable = computeReachable(config, index, getMachineStaticTransitionTargets(machine));
2145
2022
  const registered = getRegisteredAgentExecutionOptions(machine);
2146
2023
  const ctx = {
2147
2024
  machine,
@@ -2149,7 +2026,7 @@ function lintAgentMachine(machine, options = {}) {
2149
2026
  index,
2150
2027
  reachable,
2151
2028
  schemas: registered.schemas,
2152
- actorSources: registered.actorSources ?? machine.implementations?.actorSources ?? {}
2029
+ actors: registered.actors ?? machine.sources?.actors ?? {}
2153
2030
  };
2154
2031
  const disabled = new Set(options.disable ?? []);
2155
2032
  return LINT_CHECKS.flatMap((check) => check(ctx)).filter((d) => !disabled.has(d.code));
@@ -2159,11 +2036,11 @@ function lintAgentMachine(machine, options = {}) {
2159
2036
  * `diagnostics` holds the findings; the message lists them one per finding,
2160
2037
  * so a test runner's failure output reads like the CLI's lint report.
2161
2038
  */
2162
- var AgentLintError = class extends Error {
2039
+ var AgentLintError = class extends AgentError {
2163
2040
  diagnostics;
2164
2041
  constructor(machineId, diagnostics) {
2165
2042
  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")}`);
2043
+ super("lint-failed", `Agent machine '${machineId}' failed lint (${diagnostics.length} finding(s)):\n${lines.join("\n")}`);
2167
2044
  this.name = "AgentLintError";
2168
2045
  this.diagnostics = diagnostics;
2169
2046
  }
@@ -2490,4 +2367,433 @@ async function canReach(machine, statePath, options = {}) {
2490
2367
  } : { canReach: false };
2491
2368
  }
2492
2369
  //#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 };
2370
+ //#region src/trajectory.ts
2371
+ function isRecord$2(value) {
2372
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2373
+ }
2374
+ /**
2375
+ * Classifies one item. Event-shaped things (log entries, events) become events;
2376
+ * everything else is a state value. A snapshot is unwrapped to its `value`.
2377
+ * @internal
2378
+ */
2379
+ function normalize(item) {
2380
+ if (isRecord$2(item)) {
2381
+ const nested = item["event"];
2382
+ if (isRecord$2(nested) && typeof nested["type"] === "string") return {
2383
+ kind: "event",
2384
+ event: nested
2385
+ };
2386
+ if (typeof item["type"] === "string") return {
2387
+ kind: "event",
2388
+ event: item
2389
+ };
2390
+ if ("value" in item && "status" in item) return {
2391
+ kind: "state",
2392
+ value: item["value"]
2393
+ };
2394
+ }
2395
+ return {
2396
+ kind: "state",
2397
+ value: item
2398
+ };
2399
+ }
2400
+ /** Every leaf of a state value as a dot path (`{ review: 'editing' }` → `review.editing`). @internal */
2401
+ function statePaths(value) {
2402
+ if (typeof value === "string") return [value];
2403
+ if (!isRecord$2(value)) return [];
2404
+ return Object.entries(value).flatMap(([key, child]) => statePaths(child).map((path) => `${key}.${path}`));
2405
+ }
2406
+ /**
2407
+ * A string expectation matches a state value when it names one of its leaves or
2408
+ * an ancestor of one, so `'review'` matches `{ review: 'editing' }`. @internal
2409
+ */
2410
+ function stateMatchesPath(path, value) {
2411
+ return statePaths(value).some((leaf) => leaf === path || leaf.startsWith(`${path}.`));
2412
+ }
2413
+ function deepEqual(a, b) {
2414
+ if (Object.is(a, b)) return true;
2415
+ if (Array.isArray(a) || Array.isArray(b)) return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((item, index) => deepEqual(item, b[index]));
2416
+ if (!isRecord$2(a) || !isRecord$2(b)) return false;
2417
+ const keys = Object.keys(a);
2418
+ return keys.length === Object.keys(b).length && keys.every((key) => deepEqual(a[key], b[key]));
2419
+ }
2420
+ /**
2421
+ * Does one actual item satisfy one expected item?
2422
+ *
2423
+ * - A string expectation matches an event's `type`, or a state value it names.
2424
+ * - An event expectation matches an event with the same `type` whose other
2425
+ * declared keys deep-equal, so payload details are opt-in.
2426
+ * - A state-value expectation matches structurally.
2427
+ * @internal
2428
+ */
2429
+ function matchesItem(expected, actual) {
2430
+ const actualItem = normalize(actual);
2431
+ if (typeof expected === "string") return actualItem.kind === "event" ? actualItem.event.type === expected : stateMatchesPath(expected, actualItem.value);
2432
+ const expectedItem = normalize(expected);
2433
+ if (expectedItem.kind !== actualItem.kind) return false;
2434
+ if (expectedItem.kind === "event" && actualItem.kind === "event") return Object.keys(expectedItem.event).every((key) => deepEqual(expectedItem.event[key], actualItem.event[key]));
2435
+ return deepEqual(expectedItem.value, actualItem.value);
2436
+ }
2437
+ /**
2438
+ * Matches a run's trajectory against an expected one.
2439
+ *
2440
+ * Both trajectories may be state values (collected from `onTransition`) or
2441
+ * events (`result.events`, whose {@link AgentLogEntry} envelopes are unwrapped);
2442
+ * items are compared by shape, so `['prompting', 'drafting']` scores a state
2443
+ * path and `['PROMPT_SUBMITTED', 'SEND']` scores an event log with the same
2444
+ * call.
2445
+ *
2446
+ * Semantics are an ordered subsequence: every expected item must appear in
2447
+ * `actual`, in order, with gaps allowed. Pass `{ exact: true }` to require
2448
+ * equality instead.
2449
+ *
2450
+ * @example Trajectory assertions in a test
2451
+ * ```ts
2452
+ * const statePath: unknown[] = [];
2453
+ * const result = await runAgent(machine, {
2454
+ * input,
2455
+ * executors,
2456
+ * onTransition: (snapshot) => statePath.push(snapshot.value),
2457
+ * });
2458
+ *
2459
+ * const path = matchesTrajectory(statePath, ['prompting', 'drafting', 'sent']);
2460
+ * expect(path.matched, JSON.stringify(path.firstMiss)).toBe(true);
2461
+ * expect(matchesTrajectory(result.events, ['PROMPT_SUBMITTED', 'SEND']).matched).toBe(true);
2462
+ * ```
2463
+ *
2464
+ * @example Partial credit in an eval scorer
2465
+ * ```ts
2466
+ * const match = matchesTrajectory(output.statePath, expected.statePath);
2467
+ * return { name: 'state_path', score: match.score, metadata: { ...match.firstMiss } };
2468
+ * ```
2469
+ */
2470
+ function matchesTrajectory(actual, expected, options = {}) {
2471
+ const expectedCount = expected.length;
2472
+ if (options.exact) {
2473
+ let matchedCount = 0;
2474
+ let firstMiss;
2475
+ for (let index = 0; index < expectedCount; index++) {
2476
+ if (index < actual.length && matchesItem(expected[index], actual[index])) {
2477
+ matchedCount++;
2478
+ continue;
2479
+ }
2480
+ firstMiss = {
2481
+ index,
2482
+ expected: expected[index],
2483
+ searchedFrom: index
2484
+ };
2485
+ break;
2486
+ }
2487
+ const matched = !firstMiss && actual.length === expectedCount;
2488
+ const denominator = Math.max(expectedCount, actual.length);
2489
+ return {
2490
+ matched,
2491
+ matchedCount,
2492
+ expectedCount,
2493
+ score: matched ? 1 : denominator === 0 ? 1 : matchedCount / denominator,
2494
+ ...firstMiss ? { firstMiss } : {}
2495
+ };
2496
+ }
2497
+ let cursor = 0;
2498
+ let matchedCount = 0;
2499
+ for (let index = 0; index < expectedCount; index++) {
2500
+ const want = expected[index];
2501
+ let found = -1;
2502
+ for (let at = cursor; at < actual.length; at++) if (matchesItem(want, actual[at])) {
2503
+ found = at;
2504
+ break;
2505
+ }
2506
+ if (found === -1) return {
2507
+ matched: false,
2508
+ matchedCount,
2509
+ expectedCount,
2510
+ score: matchedCount / expectedCount,
2511
+ firstMiss: {
2512
+ index,
2513
+ expected: want,
2514
+ searchedFrom: cursor
2515
+ }
2516
+ };
2517
+ matchedCount++;
2518
+ cursor = found + 1;
2519
+ }
2520
+ return {
2521
+ matched: true,
2522
+ matchedCount,
2523
+ expectedCount,
2524
+ score: 1
2525
+ };
2526
+ }
2527
+ //#endregion
2528
+ //#region src/scripted-executors.ts
2529
+ /**
2530
+ * Scripted executors — a keyless, deterministic stand-in for a model host.
2531
+ *
2532
+ * `createScriptedExecutors` builds a full `{ generateText, streamText, decide }`
2533
+ * set that plays back canned answers from FIFO queues, so `runAgent` (or
2534
+ * `provideExecutors`, or a bare `TextLogic.execute`) runs with no API key and no
2535
+ * network. It is the fastest way to see a machine run, and the least ceremonial
2536
+ * way to test one: same machine, same executor contract, scripted answers.
2537
+ */
2538
+ /** Thrown when a scripted queue runs dry on a pending request. */
2539
+ var ScriptedExecutorsError = class extends AgentError {
2540
+ constructor(message) {
2541
+ super("scripted-executors-exhausted", message);
2542
+ this.name = "ScriptedExecutorsError";
2543
+ }
2544
+ };
2545
+ function isRecord$1(value) {
2546
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2547
+ }
2548
+ /** The only own keys an executor-result envelope carries. @internal */
2549
+ const TEXT_ENVELOPE_KEYS = new Set([
2550
+ "output",
2551
+ "usage",
2552
+ "raw"
2553
+ ]);
2554
+ /**
2555
+ * True when a scripted entry is the executor envelope rather than the output
2556
+ * value: it owns an `output` key and owns NOTHING outside the envelope's own
2557
+ * vocabulary. Bare `'output' in value` would swallow an output object's
2558
+ * siblings (`{ output: 'draft', confidence: 0.9 }` would lose `confidence`)
2559
+ * and would also match an inherited `output`. @internal
2560
+ */
2561
+ function isTextEnvelope(value) {
2562
+ return Object.hasOwn(value, "output") && Object.keys(value).every((key) => TEXT_ENVELOPE_KEYS.has(key));
2563
+ }
2564
+ /**
2565
+ * Resolves ONE scripted text entry to an executor result: a function entry is
2566
+ * called with the request, and the value is taken as the raw envelope only when
2567
+ * it is one (see {@link isTextEnvelope}). Shared with `runSeam`, whose routed
2568
+ * queues follow the same entry conventions. @internal
2569
+ */
2570
+ async function resolveScriptedTextEntry(entry, request, info) {
2571
+ const value = typeof entry === "function" ? await entry(request, info) : entry;
2572
+ return isRecord$1(value) && isTextEnvelope(value) ? value : { output: value };
2573
+ }
2574
+ /**
2575
+ * Keyless executors that replay a script instead of calling a model. Every
2576
+ * slot is provided, so any machine binds; a request with no entry left throws a
2577
+ * descriptive error naming what was pending.
2578
+ *
2579
+ * Queues are consumed FIFO and are copied on creation, so the caller's arrays
2580
+ * are never mutated and each call builds a fresh, independent playthrough.
2581
+ * Entries may be plain values or functions of the request, which is how one
2582
+ * script serves a machine that loops or branches: route on `request.name` (the
2583
+ * `setupAgent({ requests })` key) or on the decision's candidate `events`.
2584
+ *
2585
+ * @example
2586
+ * ```ts
2587
+ * const result = await runAgent(moderationMachine, {
2588
+ * input: { comment: 'honestly this update is terrible', trust: 20 },
2589
+ * executors: createScriptedExecutors({
2590
+ * decisions: [{ type: 'FLAG', reason: 'Borderline tone.' }],
2591
+ * }),
2592
+ * });
2593
+ * ```
2594
+ *
2595
+ * @example Dynamic entries
2596
+ * ```ts
2597
+ * createScriptedExecutors({
2598
+ * text: [(request) => `Draft about ${request.prompt}`],
2599
+ * decisions: [(request) => ({ type: request.events[0]!.type })],
2600
+ * });
2601
+ * ```
2602
+ */
2603
+ function createScriptedExecutors(script = {}) {
2604
+ const decisions = [...script.decisions ?? []];
2605
+ const text = [...script.text ?? []];
2606
+ const nextText = async (request, info) => {
2607
+ if (text.length === 0) throw new ScriptedExecutorsError(`createScriptedExecutors: script ran dry on a pending text request ${describeText(request)}. Add another entry to the script's \`text\` queue.`);
2608
+ return resolveScriptedTextEntry(text.shift(), request, info);
2609
+ };
2610
+ return {
2611
+ generateText: nextText,
2612
+ streamText: async (request, info) => {
2613
+ const result = await nextText(request, info);
2614
+ if (typeof result.output === "string") info?.onChunk?.(result.output);
2615
+ return result;
2616
+ },
2617
+ decide: async (request) => {
2618
+ if (decisions.length === 0) throw new ScriptedExecutorsError(`createScriptedExecutors: script ran dry on a pending decision request (id '${request.id}'). Add another entry to the script's \`decisions\` queue. Candidate events: ${request.events.map((event) => event.type).join(", ") || "(none)"}.`);
2619
+ const entry = decisions.shift();
2620
+ const value = typeof entry === "function" ? await entry(request) : entry;
2621
+ return typeof value.type === "string" ? { event: value } : value;
2622
+ }
2623
+ };
2624
+ }
2625
+ function describeText(request) {
2626
+ return request.name ? `'${request.name}'` : `(model '${request.model}')`;
2627
+ }
2628
+ //#endregion
2629
+ //#region src/seam.ts
2630
+ /** Thrown when the call plan runs dry on a request that is not the live seam. */
2631
+ var SeamScriptError = class extends AgentError {
2632
+ constructor(message) {
2633
+ super("seam-script-exhausted", message);
2634
+ this.name = "SeamScriptError";
2635
+ }
2636
+ };
2637
+ function seamKeyOf(seam) {
2638
+ return seam.request ?? seam.model;
2639
+ }
2640
+ function describeRequest(request) {
2641
+ return request.name ? `'${request.name}' (model '${request.model}')` : `(model '${request.model}')`;
2642
+ }
2643
+ function isRecord(value) {
2644
+ return typeof value === "object" && value !== null;
2645
+ }
2646
+ /**
2647
+ * The seam's own answer, for scoring. Our `{ output }` envelope is read
2648
+ * directly; a raw AI SDK `generateText` result contributes its `text`. A raw
2649
+ * STREAM result is left alone — the machine consumes that stream, and reading
2650
+ * it here would steal the chunks — so `seamOutput` is `undefined` for a
2651
+ * streaming candidate that returns one. Score its trajectory instead.
2652
+ * @internal
2653
+ */
2654
+ async function seamOutputOf(result, request) {
2655
+ if (!isRecord(result)) return;
2656
+ if ("output" in result) return await result["output"];
2657
+ if ("textStream" in result) return;
2658
+ return await normalizeGeneratorResult(result, `seam '${request.name ?? request.model}'`, { request });
2659
+ }
2660
+ /**
2661
+ * Runs a machine end to end with one model call under test, and slices the run
2662
+ * at that call.
2663
+ *
2664
+ * The `before`/`after` slices are the point: `after.statePath` is the branch
2665
+ * the seam chose and `after.events` is the same question against the durable
2666
+ * log, both ready for `matchesTrajectory`. The state slice splits where the
2667
+ * state path stood when the seam answered; the event slice splits at the
2668
+ * seam's own effect completion (the first `xstate.done.*`/`xstate.error.*`
2669
+ * entry appended after the call was made).
2670
+ *
2671
+ * @example Keyless: the seam is scripted too, so the whole thing runs offline.
2672
+ * ```ts
2673
+ * const run = await runSeam(emailDrafter, {
2674
+ * scripts: { promptEvaluator: [vague, complete], emailDrafter: [draft] },
2675
+ * seam: { request: 'evaluatePrompt' },
2676
+ * respond: ({ state }) => (state === 'prompting' ? { type: 'PROMPT_SUBMITTED', prompt } : null),
2677
+ * });
2678
+ *
2679
+ * matchesTrajectory(run.after.statePath, ['needsMoreInfo', 'drafting']);
2680
+ * ```
2681
+ *
2682
+ * @example A candidate prompt at the seam, scored against the same rows.
2683
+ * ```ts
2684
+ * const { generateText } = createAiSdkExecutors({ models });
2685
+ * const run = await runSeam(emailDrafter, { ...row, candidate: generateText });
2686
+ * ```
2687
+ */
2688
+ async function runSeam(machine, options) {
2689
+ const { seam, candidate } = options;
2690
+ const queues = new Map(Object.entries(options.scripts ?? {}).map(([key, answers]) => [key, [...answers]]));
2691
+ const statePath = [];
2692
+ let calls = 0;
2693
+ let seamMatches = 0;
2694
+ let seamOutput;
2695
+ let seamReached = false;
2696
+ let callsBeforeSeam = -1;
2697
+ let seamStateAt = 0;
2698
+ let seamEventAt = 0;
2699
+ let liveEvents = 0;
2700
+ const scriptedAnswer = async (request, info, optional) => {
2701
+ const key = request.name !== void 0 && queues.has(request.name) ? request.name : request.model;
2702
+ const queue = queues.get(key);
2703
+ if (!queue?.length) {
2704
+ if (optional) return;
2705
+ throw new SeamScriptError(`runSeam: no scripted answer left for request ${describeRequest(request)}. Add an entry to \`scripts.${key}\` — its last entry repeats, so one extra answer covers a longer branch.`);
2706
+ }
2707
+ return resolveScriptedTextEntry(queue.length === 1 ? queue[0] : queue.shift(), request, info);
2708
+ };
2709
+ const route = async (request, info) => {
2710
+ const callIndex = calls++;
2711
+ const isSeam = (seam.request !== void 0 ? request.name === seam.request : request.model === seam.model) && seamMatches++ === (seam.occurrence ?? 0);
2712
+ const scripted = await scriptedAnswer(request, info, isSeam && candidate !== void 0);
2713
+ if (!isSeam) return scripted;
2714
+ seamReached = true;
2715
+ callsBeforeSeam = callIndex;
2716
+ seamStateAt = statePath.length;
2717
+ seamEventAt = liveEvents;
2718
+ if (!candidate) {
2719
+ if (!scripted) throw new SeamScriptError(`runSeam: the seam '${seamKeyOf(seam)}' has no candidate and no scripted answer. Pass \`candidate\`, or script the seam's key.`);
2720
+ seamOutput = scripted.output;
2721
+ return scripted;
2722
+ }
2723
+ const result = await candidate(request, info);
2724
+ seamOutput = await seamOutputOf(result, request);
2725
+ return result;
2726
+ };
2727
+ const executors = {
2728
+ ...options.executors,
2729
+ generateText: route,
2730
+ streamText: async (request, info) => {
2731
+ const result = await route(request, info);
2732
+ const output = isRecord(result) ? result["output"] : void 0;
2733
+ if (typeof output === "string") info?.onChunk?.(output);
2734
+ return result;
2735
+ }
2736
+ };
2737
+ let snapshot;
2738
+ let event;
2739
+ let events = [];
2740
+ let result;
2741
+ const maxTurns = options.maxTurns ?? 12;
2742
+ for (let turn = 0; turn <= maxTurns; turn++) {
2743
+ result = await runAgent(machine, {
2744
+ ...snapshot ? { snapshot } : { input: options.input },
2745
+ ...event ? { event } : {},
2746
+ ...options.isSuspended ? { isSuspended: options.isSuspended } : {},
2747
+ ...options.actors ? { actors: options.actors } : {},
2748
+ events,
2749
+ executors,
2750
+ onEvent: () => {
2751
+ liveEvents++;
2752
+ },
2753
+ onTransition: (next, causedBy) => {
2754
+ if (turn > 0 && causedBy.type === "@xstate.init") return;
2755
+ statePath.push(next.value);
2756
+ }
2757
+ });
2758
+ events = result.events;
2759
+ liveEvents = events.length;
2760
+ if (result.status !== "idle" || turn === maxTurns) break;
2761
+ const next = options.respond?.({
2762
+ snapshot: result.snapshot,
2763
+ state: result.snapshot.value,
2764
+ meta: getStateMeta(result.snapshot),
2765
+ turn,
2766
+ result
2767
+ });
2768
+ if (!next) break;
2769
+ event = next;
2770
+ snapshot = result.snapshot;
2771
+ }
2772
+ let splitAt = events.length;
2773
+ if (seamReached) {
2774
+ splitAt = seamEventAt;
2775
+ for (let index = seamEventAt; index < events.length; index++) {
2776
+ const type = events[index].event.type;
2777
+ if (type.startsWith("xstate.done") || type.startsWith("xstate.error")) {
2778
+ splitAt = index;
2779
+ break;
2780
+ }
2781
+ }
2782
+ }
2783
+ const splitStateAt = seamReached ? seamStateAt : statePath.length;
2784
+ return {
2785
+ result,
2786
+ seamOutput,
2787
+ callsBeforeSeam,
2788
+ before: {
2789
+ statePath: statePath.slice(0, splitStateAt),
2790
+ events: events.slice(0, splitAt)
2791
+ },
2792
+ after: {
2793
+ statePath: statePath.slice(splitStateAt),
2794
+ events: events.slice(splitAt)
2795
+ }
2796
+ };
2797
+ }
2798
+ //#endregion
2799
+ export { AGENT_EVENT_SCHEMA_VERSION, AGENT_INIT_EVENT_TYPE, AGENT_TRACE_SCHEMA_VERSION, AGENT_USAGE_EVENT_TYPE, AgentDecisionExhaustedError, AgentError, AgentEventLogConflictError, AgentIdleError, AgentIllegalResumeEventError, AgentLintError, AgentReplayDivergenceError, AgentReplayMachineMismatchError, AgentSnapshotVersionMismatchError, NonSerializableAgentEventError, PLAN_DONE_EVENT_TYPE, appendMessages, assertAgentLogEntry, assertAgentMachine, assertEventLogStoreConformance, assertJsonSerializable, assistantMessage, bindRequestExecutor, buildEnvelopeSchema, canReach, createAgentActor, createAgentRun, createAgentSchemas, createInMemoryEventLogStore, createReplayEntry, createScriptedExecutors, createTextLogic, diffEventLogs, executeAgentRequest, explorePaths, generateResult, getAcceptedEvents, getAgentEffects, getAgentMessages, getAgentOutputMode, getCallUsage, getJsonSchema, getJsonSchemaSync, getMachineStructuralHash, getStateMeta, initEntry, inspectTransitions, isStandardSchema, lintAgentMachine, matchesTrajectory, messagesSchema, parseAgentEvent, parseModelRef, parseOutput, parseStructuredEnvelope, persistSnapshot, provideExecutors, renderDecisionAttempts, replay, resolveDecision, runAgent, runSeam, serializeTraceEvent, setupAgent, simulateAgent, systemMessage, toolMessage, traceTransitions, userMessage, verifyReplay };