@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
@@ -0,0 +1,1711 @@
1
+ import { t as AgentError } from "./errors-CeSXQx0v.mjs";
2
+ import { $ as validateSchemaSync, B as missingActor, C as executeAgentTextRequest, E as isTextLogic, I as getRegisteredAgentExecutionOptions, K as getMachineStructuralHash, M as agentExecutionOptions, R as machineStaticTransitionTargets, S as createTextLogic, _ as PLAN_ACTOR, a as createPlanActor, c as isPlanLogic, d as getAcceptedEvents, h as DECIDE_ACTOR, i as createDecideActor, j as userInputActor, n as PLAN_DONE_EVENT_TYPE, p as sanitizeEventToolName, r as advancePlanLedger, s as isDecisionLogic, u as resolveDecision, v as USER_INPUT_ACTOR, w as extractCallUsage, x as builtinTextActors, z as machineSuspensionPredicates } from "./decision-BezSD_YC.mjs";
3
+ import { i as assertAgentLogEntry, o as assertJsonSerializable } from "./event-log-store-D7pWtIhb.mjs";
4
+ import { createMachineFromConfig, initialTransition, setup, transition } 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/steps.ts
84
+ /**
85
+ * The step envelope: an INTERNAL, per-model-call-checkpoint view of an agent
86
+ * machine (`AgentStep` = snapshot + actions + discovered requests + done). It
87
+ * once backed a public step API; the public step path is now the thin
88
+ * effect/replay loop in `./effects.ts` (re-exported from `@statelyai/agent/steps`).
89
+ * These helpers survive as internals that `runAgent`, `verify.ts`, and the
90
+ * effect path build on — the only symbol still on the public surface is
91
+ * {@link executeAgentRequest} (the `text`-effect resolver).
92
+ * @module
93
+ */
94
+ /** @internal Normalizes current and legacy XState invoke effect shapes. */
95
+ function getInvokeEffectMetadata(action) {
96
+ if (action.type === "@xstate.spawn") return action;
97
+ if (action.type === "xstate.spawnChild") {
98
+ const params = action.params;
99
+ return params ? {
100
+ ...params,
101
+ logic: action.logic
102
+ } : void 0;
103
+ }
104
+ if (action.type === "@xstate.start" && typeof action.src === "string") return action;
105
+ }
106
+ /**
107
+ * Scans a set of executable actions (as returned by xstate's `transition`/
108
+ * `initialTransition`) for spawned `TextLogic`/`DecisionLogic` invokes and
109
+ * lowers each into an {@link AgentStepRequest}. The hand-passed-schemas
110
+ * implementation detail behind the public {@link getAgentRequests} — it needs
111
+ * `schemas`/`actors` passed explicitly, whereas `getAgentRequests`
112
+ * pre-fills them from the machine's registered `setupAgent` options.
113
+ * `options.snapshot` is required to resolve a decision's candidate events
114
+ * (intersecting declared `allowedEvents` with what's currently legal) — omit
115
+ * it and decision requests report an empty `events` list.
116
+ *
117
+ * @internal
118
+ */
119
+ function getAgentRequestsWith(actions, options = {}) {
120
+ return [...actions.flatMap((action) => {
121
+ const params = getInvokeEffectMetadata(action);
122
+ if (!params || typeof params.src !== "string") return [];
123
+ if (typeof params.id !== "string" || params.id.length === 0) throw new Error(`Agent invoke '${params.src}' must define a durable string id.`);
124
+ const registeredLogic = isTextLogic(params.logic) || isDecisionLogic(params.logic) ? params.logic : options.actors?.[params.src];
125
+ if (isDecisionLogic(registeredLogic)) {
126
+ const decisionRequest = registeredLogic.request(params.input);
127
+ const allowedEventTypes = registeredLogic.allowedEventTypes?.(params.input);
128
+ const events = options.snapshot ? getAcceptedEvents(options.snapshot, {
129
+ events: options.events,
130
+ schemas: options.schemas,
131
+ eventTypes: allowedEventTypes,
132
+ eventToolName: options.eventToolName
133
+ }) : [];
134
+ return [{
135
+ ...decisionRequest,
136
+ id: params.id,
137
+ events
138
+ }];
139
+ }
140
+ const textLogic = isTextLogic(registeredLogic) ? registeredLogic : void 0;
141
+ const input = textLogic ? textLogic.request(params.input) : void 0;
142
+ if (!input) return [];
143
+ return [{
144
+ kind: "text",
145
+ id: params.id,
146
+ src: params.src,
147
+ ...textLogic ? { mode: textLogic.mode } : {},
148
+ input,
149
+ tools: input.tools ?? {},
150
+ events: []
151
+ }];
152
+ }), ...getActivePlanRequests(options)];
153
+ }
154
+ /**
155
+ * Scans the live snapshot's children for active `agent.plan` (plan-logic)
156
+ * invokes and lowers each into an {@link AgentPlanRequest} — the re-surfacing
157
+ * half of plan discovery. Reads the applied-event trail and remaining budget
158
+ * from the child's own ledger `context` ({@link PlanLedgerContext}), recomputes
159
+ * the currently-legal candidates (∩ declared `allowedEvents`) plus the reserved
160
+ * `agent.plan.done` move, and takes `stepsRemaining` from the ledger (falling
161
+ * back to `maxSteps - applied.length` for a snapshot with no context yet).
162
+ * Returns `[]` when no snapshot is available (candidates need a live snapshot).
163
+ *
164
+ * @internal
165
+ */
166
+ function getActivePlanRequests(options) {
167
+ const snapshot = options.snapshot;
168
+ if (!snapshot) return [];
169
+ const children = snapshot.children;
170
+ if (!children) return [];
171
+ const requests = [];
172
+ for (const [id, child] of Object.entries(children)) {
173
+ const ref = child;
174
+ if (typeof ref?.getSnapshot !== "function") continue;
175
+ const src = typeof ref.src === "string" ? ref.src : void 0;
176
+ const logic = (src ? options.actors?.[src] : void 0) ?? ref.logic;
177
+ if (!isPlanLogic(logic)) continue;
178
+ const childSnapshot = ref.getSnapshot();
179
+ if (childSnapshot?.status !== "active") continue;
180
+ const input = childSnapshot.input ?? {};
181
+ const maxSteps = input.maxSteps ?? 8;
182
+ const ledger = childSnapshot.context ?? {};
183
+ const applied = ledger.applied ?? [];
184
+ const stepsRemaining = ledger.stepsRemaining ?? maxSteps - applied.length;
185
+ const machineEvents = getAcceptedEvents(snapshot, {
186
+ events: options.events,
187
+ schemas: options.schemas,
188
+ eventTypes: logic.allowedEventTypes(input) ?? void 0,
189
+ eventToolName: options.eventToolName
190
+ });
191
+ const events = machineEvents.some((event) => event.type === "agent.plan.done") ? machineEvents : [...machineEvents, {
192
+ type: PLAN_DONE_EVENT_TYPE,
193
+ toolName: sanitizeEventToolName(PLAN_DONE_EVENT_TYPE)
194
+ }];
195
+ requests.push({
196
+ kind: "plan",
197
+ id,
198
+ src: src ?? "",
199
+ input,
200
+ events,
201
+ applied,
202
+ stepsRemaining: Math.max(0, stepsRemaining)
203
+ });
204
+ }
205
+ return requests;
206
+ }
207
+ /**
208
+ * Builds the canonical `xstate.done.actor` event xstate's `transition()`
209
+ * expects to resolve a spawned invoke — including the child session identity
210
+ * that rejects a stale completion from an earlier incarnation.
211
+ *
212
+ * @internal
213
+ */
214
+ function doneEvent(snapshot, request, output) {
215
+ const id = typeof request === "string" ? request : request.id;
216
+ const child = snapshot.children[id];
217
+ return {
218
+ type: "xstate.done.actor",
219
+ actorId: id,
220
+ ...typeof child?.sessionId === "string" ? { sessionId: child.sessionId } : {},
221
+ output
222
+ };
223
+ }
224
+ /**
225
+ * Applies a request's `output` as a done event via `transition(...)`,
226
+ * returning the raw `[snapshot, actions]` tuple. Lower-level than
227
+ * {@link resolveAgentStep} — that helper wraps this and also runs
228
+ * {@link getAgentRequests} to produce the next {@link AgentStep}.
229
+ *
230
+ * @internal
231
+ */
232
+ function transitionResult(logic, snapshot, request, output) {
233
+ const event = doneEvent(snapshot, request, output);
234
+ const result = transition(logic, snapshot, event);
235
+ applyFinalStateOutput(logic, result[0], event);
236
+ return result;
237
+ }
238
+ /**
239
+ * Starts a machine and returns its first {@link AgentStep} — the step-path
240
+ * equivalent of `initialTransition` plus request discovery. Begins the
241
+ * durable/per-model-call-checkpoint loop: resolve each `step.requests` entry
242
+ * (via {@link executeAgentRequest} for `kind: 'text'`, or
243
+ * {@link resolveDecision} for `kind: 'decision'`), then advance with
244
+ * {@link resolveAgentStep} or {@link transitionAgentStep}.
245
+ */
246
+ function initialAgentStep(machine, input, options) {
247
+ const [snapshot, actions] = initialTransition(machine, input);
248
+ return createAgentStep(machine, snapshot, actions, getRegisteredAgentExecutionOptions(machine, options));
249
+ }
250
+ /**
251
+ * Applies an externally-sent event (e.g. a decision's chosen event, or a
252
+ * human's reply) and returns the next {@link AgentStep}. Accepts **either**
253
+ * a raw snapshot **or** a prior `AgentStep` as the second argument —
254
+ * `.snapshot` is unwrapped automatically, so callers can thread the whole
255
+ * step object through without manually plucking the snapshot out.
256
+ */
257
+ function transitionAgentStep(machine, snapshotOrStep, event, options) {
258
+ const [nextSnapshot, actions] = transition(machine, isAgentStep(snapshotOrStep) ? snapshotOrStep.snapshot : snapshotOrStep, event);
259
+ return createAgentStep(machine, nextSnapshot, actions, getRegisteredAgentExecutionOptions(machine, options));
260
+ }
261
+ /**
262
+ * Applies a resolved text request's output (a `kind: 'text'`
263
+ * {@link AgentRequest} — not a decision) as a done event and returns the
264
+ * next {@link AgentStep}. For decisions, resolve with `resolveDecision`
265
+ * (which returns a {@link ChosenEvent}) and apply it with
266
+ * {@link transitionAgentStep} instead — a decision has no output value of
267
+ * its own to feed here.
268
+ */
269
+ function resolveAgentStep(machine, step, request, output, options) {
270
+ const [snapshot, actions] = transitionResult(machine, step.snapshot, request, output);
271
+ return createAgentStep(machine, snapshot, actions, getRegisteredAgentExecutionOptions(machine, options));
272
+ }
273
+ async function executeAgentRequest(requestOrEffect, executors, options) {
274
+ if (requestOrEffect.kind === "decision") throw new Error("executeAgentRequest(...) is text-only. Resolve a 'decision' request with resolveDecision(request, executors.decide, ...) instead.");
275
+ const request = "requestId" in requestOrEffect ? {
276
+ kind: "text",
277
+ id: requestOrEffect.requestId,
278
+ src: "",
279
+ mode: requestOrEffect.mode,
280
+ input: requestOrEffect.request,
281
+ tools: requestOrEffect.request.tools ?? {},
282
+ events: []
283
+ } : requestOrEffect;
284
+ assertTextExecutor(request, executors);
285
+ const { output, raw } = await executeAgentTextRequest(request.mode ?? "generate", request.id, request.input, executors, request.tools);
286
+ const normalizedOutput = request.input.outputSchema ? validateSchemaSync(request.input.outputSchema, output) : output;
287
+ return options?.verbose ? {
288
+ output: normalizedOutput,
289
+ raw
290
+ } : normalizedOutput;
291
+ }
292
+ /**
293
+ * Resolves the current step's pending requests and returns the next
294
+ * {@link AgentStep} — one iteration of the durable step loop, collapsing the
295
+ * manual `request.kind` dispatch a host would otherwise write by hand.
296
+ *
297
+ * For each pending request, in order: a `kind: 'text'` request is run with
298
+ * {@link executeAgentRequest} then fed back via {@link resolveAgentStep}; a
299
+ * `kind: 'decision'` request is resolved with `resolveDecision` (wiring
300
+ * `canTake` to `step.snapshot.can` so guard-rejected choices retry) then
301
+ * applied with {@link transitionAgentStep}. The **current** step is re-read
302
+ * after each application — the machine may advance and its `requests` change —
303
+ * so this always resolves against the live step, never a stale list.
304
+ *
305
+ * A `kind: 'plan'` request (`agent.plan`) is resolved natively too: one plan
306
+ * step per call. It resolves a single decision from `request.events` (wiring
307
+ * `canTake` to `step.snapshot.can`, exempting the reserved `agent.plan.done`
308
+ * move and `stopOn` events), then either applies the chosen machine event and
309
+ * lets the next step re-surface the plan, or completes the plan (feeding its
310
+ * `{ steps, stopped }` output back) on the done move / a `stopOn` event / an
311
+ * exhausted budget / no legal events. The plan's applied trail is carried in
312
+ * the invoke child's snapshot, so persisting the step between calls resumes the
313
+ * plan identically.
314
+ *
315
+ * Missing the executor a request needs throws a clear error
316
+ * (`generateText`/`streamText` for text, `decide` for decisions and plans).
317
+ *
318
+ * A complete durable host is two lines:
319
+ *
320
+ * ```ts
321
+ * let step = initialAgentStep(machine, input);
322
+ * while (!step.done) step = await resolveAgentRequests(machine, step, executors);
323
+ * ```
324
+ *
325
+ * All pending **text** requests of a step are resolved in parallel
326
+ * (`Promise.all`) — parallel statechart regions are genuinely concurrent, so
327
+ * their model calls run concurrently — then their outputs apply in
328
+ * **request-array order** (deterministic for durable replay regardless of which
329
+ * call finishes first). Decisions and plans stay **one at a time**: applying
330
+ * either changes the set of legal candidates for what follows, so they cannot be
331
+ * resolved against a stale snapshot. A host that instead wants strictly
332
+ * sequential text resolution loops the manual per-request helpers
333
+ * ({@link executeAgentRequest} + {@link resolveAgentStep}) one at a time.
334
+ */
335
+ async function resolveAgentRequests(machine, step, executors, options) {
336
+ const [request] = step.requests;
337
+ if (!request) return step;
338
+ if (request.kind === "decision") {
339
+ if (!executors.decide) throw new Error(`this step's decision request '${request.id}' needs a 'decide' executor but none was provided.`);
340
+ return transitionAgentStep(machine, step, await resolveDecision(request, executors.decide, {
341
+ canTake: (event) => step.snapshot.can(event),
342
+ maxRetries: options?.maxRetries
343
+ }), options);
344
+ }
345
+ if (request.kind === "plan") return resolvePlanRequest(machine, step, request, executors, options);
346
+ const textRequests = step.requests.filter((candidate) => candidate.kind === "text");
347
+ for (const textRequest of textRequests) assertTextExecutor(textRequest, executors);
348
+ const outputs = await Promise.all(textRequests.map((textRequest) => executeAgentRequest(textRequest, executors)));
349
+ let next = step;
350
+ for (let index = 0; index < textRequests.length; index++) next = resolveAgentStep(machine, next, textRequests[index], outputs[index], options);
351
+ return next;
352
+ }
353
+ function assertTextExecutor(request, executors) {
354
+ const mode = request.mode ?? "generate";
355
+ const kind = mode === "stream" ? "streamText" : "generateText";
356
+ if (!(mode === "stream" ? executors.streamText : executors.generateText)) throw new Error(`this step's text request '${request.src}' needs a '${kind}' executor but none was provided.`);
357
+ }
358
+ async function resolvePlanRequest(machine, step, request, executors, options) {
359
+ if (!executors.decide) throw new Error(`this step's plan request '${request.src}' needs a 'decide' executor but none was provided.`);
360
+ const stopOn = new Set(request.input.stopOn ?? []);
361
+ if (request.stepsRemaining <= 0) return completePlan(machine, step, request.id, request.applied, "max-steps", options);
362
+ if (request.events.filter((event) => event.type !== "agent.plan.done").length === 0) return completePlan(machine, step, request.id, request.applied, "no-legal-events", options);
363
+ const chosen = await resolveDecision(planStepDecisionRequest(request), executors.decide, {
364
+ maxRetries: options?.maxRetries,
365
+ canTake: (event) => {
366
+ if (event.type === "agent.plan.done" || stopOn.has(event.type)) return true;
367
+ return step.snapshot.can(event);
368
+ }
369
+ });
370
+ if (chosen.type === "agent.plan.done") return completePlan(machine, step, request.id, request.applied, "done", options);
371
+ const applied = [...request.applied, chosen];
372
+ advancePlanChildLedger(step.snapshot, request.id, {
373
+ type: "plan.applied",
374
+ event: chosen
375
+ });
376
+ const next = transitionAgentStep(machine, step, chosen, options);
377
+ if (stopOn.has(chosen.type)) {
378
+ if (isPlanActive(next.snapshot, request.id)) return completePlan(machine, next, request.id, applied, "stop-event", options);
379
+ }
380
+ return next;
381
+ }
382
+ function completePlan(machine, step, id, steps, stopped, options) {
383
+ return resolveAgentStep(machine, step, { id }, {
384
+ steps,
385
+ stopped
386
+ }, options);
387
+ }
388
+ function planStepDecisionRequest(request) {
389
+ const { input, applied, events, id } = request;
390
+ const trail = applied.length === 0 ? "" : `\n\nEvents already applied in this plan, in order:\n${applied.map((step) => JSON.stringify(step)).join("\n")}\nContinue from here; do not repeat applied events.`;
391
+ const doneHint = `\n\nWhen the request is fully handled (or no action is needed), choose '${PLAN_DONE_EVENT_TYPE}'.`;
392
+ return {
393
+ kind: "decision",
394
+ id: `${id}[${applied.length}]`,
395
+ model: input.model,
396
+ system: input.system,
397
+ prompt: `${input.prompt ?? ""}${trail}${doneHint}`,
398
+ messages: input.messages,
399
+ events,
400
+ attempts: [],
401
+ temperature: input.temperature,
402
+ maxOutputTokens: input.maxOutputTokens,
403
+ topP: input.topP,
404
+ topK: input.topK,
405
+ seed: input.seed,
406
+ stopSequences: input.stopSequences,
407
+ metadata: input.metadata
408
+ };
409
+ }
410
+ function advancePlanChildLedger(snapshot, id, event) {
411
+ const child = snapshot.children?.[id];
412
+ const childSnapshot = child?.getSnapshot?.();
413
+ if (!isPlanLogic(child?.logic) || !childSnapshot || typeof childSnapshot !== "object") return;
414
+ Object.assign(childSnapshot, advancePlanLedger(child.logic, childSnapshot, event));
415
+ }
416
+ function isPlanActive(snapshot, id) {
417
+ return (snapshot.children?.[id])?.getSnapshot?.()?.status === "active";
418
+ }
419
+ function createAgentStep(machine, snapshot, actions, options) {
420
+ applyFinalStateOutput(machine, snapshot);
421
+ return {
422
+ snapshot,
423
+ actions,
424
+ requests: getAgentRequestsWith(actions, {
425
+ ...options,
426
+ snapshot
427
+ }),
428
+ done: snapshot.status === "done"
429
+ };
430
+ }
431
+ function resolveStateValueConfig(config, value) {
432
+ if (typeof value === "string") return config.states?.[value];
433
+ if (!value || typeof value !== "object") return;
434
+ for (const [key, childValue] of Object.entries(value)) {
435
+ const childConfig = config.states?.[key];
436
+ if (!childConfig) continue;
437
+ if (childConfig.type === "final") return childConfig;
438
+ const nested = resolveStateValueConfig(childConfig, childValue);
439
+ if (nested) return nested;
440
+ }
441
+ }
442
+ function applyFinalStateOutput(logic, snapshot, event) {
443
+ const machineSnapshot = snapshot;
444
+ if (machineSnapshot.status !== "done" || machineSnapshot.output !== void 0 || !("config" in logic)) return;
445
+ const config = logic.config;
446
+ if (!config) return;
447
+ const output = resolveStateValueConfig(config, machineSnapshot.value)?.output;
448
+ if (output === void 0) return;
449
+ machineSnapshot.output = typeof output === "function" ? output({
450
+ context: machineSnapshot.context,
451
+ event
452
+ }) : output;
453
+ }
454
+ function isAgentStep(value) {
455
+ return !!value && typeof value === "object" && "snapshot" in value && "actions" in value && "requests" in value;
456
+ }
457
+ //#endregion
458
+ //#region src/effects.ts
459
+ /**
460
+ * The effect/replay path: the library's new append-only-log core. Two
461
+ * primitives sit under every host loop:
462
+ *
463
+ * - {@link getAgentEffects} maps a transition's ordered executable actions
464
+ * (reconciled with the still-owed effects visible only on the snapshot) into
465
+ * an ordered {@link AgentEffect} list a host starts at the frontier.
466
+ * - {@link replay} folds a journal of EXTERNAL inputs through xstate's pure
467
+ * `initialTransition`/`transition` WITHOUT executing anything, returning the
468
+ * final snapshot plus the effects still owed — crash recovery, fork resume,
469
+ * and time travel in one function.
470
+ *
471
+ * The journal of external inputs (effect completions with outputs inline, user
472
+ * events, timer firings) is the authoritative artifact; raised/internal events
473
+ * are never journaled (replay re-derives them). A reserved first entry
474
+ * {@link initEntry} carries the machine input so a log is self-contained.
475
+ * @module
476
+ */
477
+ /**
478
+ * The reserved journal event type of the {@link initEntry} first entry: it
479
+ * carries the machine `input` so a log replays with no side-channel. Named in
480
+ * the `agent.*` builtin-actor namespace so it never collides with a machine's
481
+ * own event vocabulary. Consumed by {@link replay}; never fed to `transition`.
482
+ */
483
+ const AGENT_INIT_EVENT_TYPE = "@agent.init";
484
+ /**
485
+ * The reserved event type `runAgent` delivers to the running machine after
486
+ * every settled model call that reported usage — the one seam that puts a
487
+ * call's tokens in reach of ordinary `context` and guards, so a token budget
488
+ * is a plain machine transition instead of host bookkeeping.
489
+ *
490
+ * Like {@link AGENT_INIT_EVENT_TYPE} it lives in the reserved `@agent.*`
491
+ * namespace: it can never collide with a machine's own vocabulary, and
492
+ * `getAcceptedEvents`/`parseAgentEvent` never offer it — a model can neither
493
+ * be shown it as a decision candidate nor forge one.
494
+ *
495
+ * Delivery is opt-in BY CONSTRUCTION: the event is sent only when the live
496
+ * snapshot can currently take it (i.e. the machine declares an
497
+ * `'@agent.usage'` transition, usually machine-level `on`). A machine without
498
+ * one sees no extra transition, no extra trace event, and no extra log entry.
499
+ * When it IS taken it rides the event log like any other external input, so
500
+ * events-only recovery (`runAgent({ events })`) replays the folded tokens
501
+ * without re-calling a model.
502
+ */
503
+ const AGENT_USAGE_EVENT_TYPE = "@agent.usage";
504
+ /**
505
+ * Reads a settled call's token usage off a RAW executor result — the same
506
+ * normalization `runAgent` applies before it delivers
507
+ * {@link AGENT_USAGE_EVENT_TYPE}. Returns `undefined` when the executor
508
+ * reported none.
509
+ *
510
+ * The seam for the step-loop path, where the host holds the raw result itself:
511
+ *
512
+ * ```ts
513
+ * const { output, raw } = await executeAgentRequest(effect, executors, { verbose: true });
514
+ * const usage = getCallUsage(raw);
515
+ * if (usage) append({ type: AGENT_USAGE_EVENT_TYPE, usage }); // journal + transition, like any event
516
+ * append(effect.toDoneEvent(output));
517
+ * ```
518
+ *
519
+ * See "Token usage on this path" in docs/steps.md for the full loop.
520
+ */
521
+ function getCallUsage(raw) {
522
+ return extractCallUsage(raw);
523
+ }
524
+ /**
525
+ * Creates a JSON-safe, self-describing entry and records the state/effect
526
+ * hashes produced after replaying it. `entries` must be the complete prefix.
527
+ */
528
+ function createReplayEntry(machine, entries, event, options = {}) {
529
+ const index = entries.length;
530
+ const machineId = machine.config.id ?? machine.id ?? "(machine)";
531
+ const machineVersion = options.machineVersion ?? getMachineStructuralHash(machine);
532
+ const entry = {
533
+ schemaVersion: 1,
534
+ id: options.id ?? `evt_${String(index).padStart(8, "0")}`,
535
+ index,
536
+ recordedAt: options.recordedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
537
+ machineId,
538
+ machineVersion,
539
+ event: normalizeEventErrors(event),
540
+ ...options.causationId !== void 0 ? { causationId: options.causationId } : {},
541
+ ...options.correlationId !== void 0 ? { correlationId: options.correlationId } : {},
542
+ ...options.metadata !== void 0 ? { metadata: options.metadata } : {}
543
+ };
544
+ assertAgentLogEntry(entry);
545
+ if (options.verification !== false) {
546
+ const result = replay(machine, [...entries, entry], {
547
+ machineVersion,
548
+ verify: false
549
+ });
550
+ entry.verification = replayVerification(result.snapshot, result.effects);
551
+ }
552
+ assertAgentLogEntry(entry);
553
+ return entry;
554
+ }
555
+ /** Reserved first replay entry carrying machine input and verification hashes. */
556
+ function initEntry(machine, input, options = {}) {
557
+ return createReplayEntry(machine, [], {
558
+ type: AGENT_INIT_EVENT_TYPE,
559
+ ...input !== void 0 ? { input } : {}
560
+ }, options);
561
+ }
562
+ /** Normalizes a mixed `EventObject | AgentLogEntry` history into bare events. */
563
+ function toEvents(history) {
564
+ if (!history) return [];
565
+ return history.map((entry) => {
566
+ const candidate = entry;
567
+ return candidate && typeof candidate === "object" && "event" in candidate && candidate.event ? candidate.event : entry;
568
+ });
569
+ }
570
+ function rebindActorSession(event, snapshot, sessions) {
571
+ const actorEvent = event;
572
+ if (typeof actorEvent.actorId !== "string" || typeof actorEvent.sessionId !== "string") return event;
573
+ const key = `${actorEvent.actorId}\0${actorEvent.sessionId}`;
574
+ let sessionId = sessions.get(key);
575
+ if (!sessionId) {
576
+ const child = snapshot.children[actorEvent.actorId];
577
+ if (typeof child?.sessionId !== "string") return event;
578
+ sessionId = child.sessionId;
579
+ sessions.set(key, sessionId);
580
+ }
581
+ return {
582
+ ...event,
583
+ sessionId
584
+ };
585
+ }
586
+ const DONE_ACTOR_EVENT_TYPE = "xstate.done.actor";
587
+ const ERROR_ACTOR_EVENT_TYPE = "xstate.error.actor";
588
+ function isInvokeCompletion(event, id) {
589
+ return (event.type === DONE_ACTOR_EVENT_TYPE || event.type === ERROR_ACTOR_EVENT_TYPE) && event.actorId === id;
590
+ }
591
+ /**
592
+ * 1-based occurrence for an invoke/spawn site: `1 + completions` (done AND
593
+ * error both count — an error is a semantic completion) for `id` in `events`.
594
+ */
595
+ function invokeOccurrence(events, id) {
596
+ let count = 0;
597
+ for (const event of events) if (isInvokeCompletion(event, id)) count++;
598
+ return count + 1;
599
+ }
600
+ /** The exact canonical done/error events xstate's actor system delivers for an invoke `id`. */
601
+ function invokeEventMinters(id, snapshot) {
602
+ const child = snapshot.children[id];
603
+ const identity = {
604
+ actorId: id,
605
+ ...typeof child?.sessionId === "string" ? { sessionId: child.sessionId } : {}
606
+ };
607
+ return {
608
+ toDoneEvent: (output) => ({
609
+ type: DONE_ACTOR_EVENT_TYPE,
610
+ output,
611
+ ...identity
612
+ }),
613
+ toErrorEvent: (error) => ({
614
+ type: ERROR_ACTOR_EVENT_TYPE,
615
+ error,
616
+ ...identity
617
+ })
618
+ };
619
+ }
620
+ /** Splits an `xstate.after.<delayKey>.<statePath>` id into its parts. */
621
+ function parseAfterId(afterId) {
622
+ if (!afterId.startsWith("xstate.after.")) return;
623
+ const rest = afterId.slice(13);
624
+ const dot = rest.indexOf(".");
625
+ if (dot === -1) return;
626
+ return {
627
+ delayKey: rest.slice(0, dot),
628
+ statePath: rest.slice(dot + 1)
629
+ };
630
+ }
631
+ function buildInvokeEffect(meta, mapped, events, snapshot, options) {
632
+ const id = typeof meta.id === "string" ? meta.id : void 0;
633
+ if (!id) return;
634
+ const requestId = `${id}#${invokeOccurrence(events, id)}`;
635
+ if (mapped?.kind === "text") return {
636
+ kind: "text",
637
+ requestId,
638
+ request: mapped.input,
639
+ mode: mapped.mode,
640
+ ...invokeEventMinters(id, snapshot)
641
+ };
642
+ if (mapped?.kind === "decision") return {
643
+ kind: "decision",
644
+ requestId,
645
+ request: mapped
646
+ };
647
+ if (mapped?.kind === "plan") return {
648
+ kind: "plan",
649
+ requestId,
650
+ request: mapped
651
+ };
652
+ const logic = isTextLogic(meta.logic) || isDecisionLogic(meta.logic) || isPlanLogic(meta.logic) ? meta.logic : typeof meta.src === "string" ? options.actors?.[meta.src] : void 0;
653
+ if (isTextLogic(logic)) return {
654
+ kind: "text",
655
+ requestId,
656
+ request: logic.request(meta.input),
657
+ mode: logic.mode,
658
+ ...invokeEventMinters(id, snapshot)
659
+ };
660
+ if (isDecisionLogic(logic)) {
661
+ const base = logic.request(meta.input);
662
+ const eventTypes = logic.allowedEventTypes?.(meta.input);
663
+ const candidateEvents = getAcceptedEvents(snapshot, {
664
+ events: options.schemas?.events,
665
+ schemas: options.schemas,
666
+ eventTypes
667
+ });
668
+ return {
669
+ kind: "decision",
670
+ requestId,
671
+ request: {
672
+ ...base,
673
+ id,
674
+ events: candidateEvents
675
+ }
676
+ };
677
+ }
678
+ return {
679
+ kind: "task",
680
+ requestId,
681
+ id,
682
+ src: typeof meta.src === "string" ? meta.src : id,
683
+ input: meta.input,
684
+ ...invokeEventMinters(id, snapshot)
685
+ };
686
+ }
687
+ /**
688
+ * Maps a transition's ORDERED executable actions (plus the still-owed effects
689
+ * visible only on the snapshot) into an ordered {@link AgentEffect} list the
690
+ * host starts at the frontier.
691
+ *
692
+ * Ordering is load-bearing: a single transition's actions are emitted in
693
+ * document order (a custom entry action, a spawn, and a `sendTo` in that order
694
+ * yield `execute`, then `task`/agent effect, then `execute` — never a
695
+ * reordered set). Effects visible only on the snapshot (an `agent.plan` that
696
+ * re-surfaces every step, and children spawned by an EARLIER transition that
697
+ * have not completed yet — the fan-out / crash-resume case) are appended after
698
+ * the action-derived effects, deduped by site id.
699
+ *
700
+ * Every `requestId` is `${siteId}#${n}`, `n` the 1-based occurrence derived
701
+ * from `options.history` — so the same log yields identical requestIds on every
702
+ * replay. XState alpha.24 prunes a child when its matching completion is
703
+ * folded; the history check also prevents older/restored snapshots from
704
+ * re-surfacing completed work. A re-entered invoke site instead re-derives
705
+ * from the action list each fresh entry (so `#2`, `#3`, … stay correct).
706
+ */
707
+ function getAgentEffects(machine, snapshot, actions, options = {}) {
708
+ const resolved = getRegisteredAgentExecutionOptions(machine, options);
709
+ const events = toEvents(options.history);
710
+ const requests = getAgentRequestsWith(actions, {
711
+ ...resolved,
712
+ snapshot
713
+ });
714
+ const requestById = new Map(requests.map((request) => [request.id, request]));
715
+ const effects = [];
716
+ const emitted = /* @__PURE__ */ new Set();
717
+ for (const rawAction of actions) {
718
+ const meta = getInvokeEffectMetadata(rawAction);
719
+ if (meta) {
720
+ const effect = buildInvokeEffect(meta, typeof meta.id === "string" ? requestById.get(meta.id) : void 0, events, snapshot, resolved);
721
+ if (effect && typeof meta.id === "string") {
722
+ effects.push(effect);
723
+ emitted.add(meta.id);
724
+ }
725
+ continue;
726
+ }
727
+ if (rawAction.type === "@xstate.raise" && typeof rawAction.delay === "number") {
728
+ const afterId = typeof rawAction.id === "string" ? rawAction.id : "";
729
+ const parsed = parseAfterId(afterId);
730
+ const siteId = parsed ? `${parsed.statePath}#${parsed.delayKey}` : afterId;
731
+ const event = {
732
+ type: "xstate.timer",
733
+ id: afterId
734
+ };
735
+ const firings = events.filter((candidate) => candidate.type === event.type && candidate.id === afterId).length;
736
+ effects.push({
737
+ kind: "delay",
738
+ requestId: `${siteId}#${firings + 1}`,
739
+ id: afterId,
740
+ delayMs: rawAction.delay,
741
+ event
742
+ });
743
+ continue;
744
+ }
745
+ if (rawAction.type === "@xstate.start" || rawAction.type === "@xstate.stop" || rawAction.type === "@xstate.terminate" || rawAction.type === "@xstate.raise" || rawAction.type === "registerChild") continue;
746
+ if (typeof rawAction.exec === "function") {
747
+ const action = rawAction;
748
+ effects.push({
749
+ kind: "execute",
750
+ action: rawAction,
751
+ exec: () => {
752
+ action.exec();
753
+ }
754
+ });
755
+ }
756
+ }
757
+ for (const request of requests) if (request.kind === "plan" && !emitted.has(request.id)) {
758
+ effects.push({
759
+ kind: "plan",
760
+ requestId: `${request.id}#${invokeOccurrence(events, request.id)}`,
761
+ request
762
+ });
763
+ emitted.add(request.id);
764
+ }
765
+ const children = snapshot.children;
766
+ for (const [id, child] of Object.entries(children ?? {})) {
767
+ if (emitted.has(id) || invokeOccurrence(events, id) > 1) continue;
768
+ const ref = child;
769
+ if (typeof ref?.getSnapshot !== "function") continue;
770
+ const effect = buildInvokeEffect({
771
+ id,
772
+ src: ref.src,
773
+ input: ref.getSnapshot().input,
774
+ logic: ref.logic
775
+ }, requestById.get(id), events, snapshot, resolved);
776
+ if (effect) {
777
+ effects.push(effect);
778
+ emitted.add(id);
779
+ }
780
+ }
781
+ return effects;
782
+ }
783
+ var AgentReplayMachineMismatchError = class extends AgentError {
784
+ constructor(eventId, index, expected, actual) {
785
+ super("replay-machine-mismatch", `Replay entry '${eventId}' at index ${index} targets machine '${actual.machineId}'@'${actual.machineVersion}', expected '${expected.machineId}'@'${expected.machineVersion}'.`);
786
+ this.eventId = eventId;
787
+ this.index = index;
788
+ this.expected = expected;
789
+ this.actual = actual;
790
+ this.name = "AgentReplayMachineMismatchError";
791
+ }
792
+ };
793
+ var AgentReplayDivergenceError = class extends AgentError {
794
+ constructor(eventId, index, kind, expected, actual) {
795
+ super("replay-divergence", kind === "missing-verification" ? `Replay entry '${eventId}' at index ${index} has no verification hashes.` : `Replay diverged after '${eventId}' at index ${index} (${kind}): expected '${expected}', got '${actual}'.`);
796
+ this.eventId = eventId;
797
+ this.index = index;
798
+ this.kind = kind;
799
+ this.expected = expected;
800
+ this.actual = actual;
801
+ this.name = "AgentReplayDivergenceError";
802
+ }
803
+ };
804
+ /**
805
+ * Folds a journal through `initialTransition`/`transition` WITHOUT executing
806
+ * anything, then returns the final snapshot plus the still-owed effects
807
+ * ({@link getAgentEffects} of the final frontier, occurrence counts taken from
808
+ * the whole log). Crash recovery, fork resume, and time travel in one call.
809
+ *
810
+ * `entries` is a versioned {@link AgentLogEntry} array. A reserved
811
+ * {@link initEntry} first envelope carries `{ type: '@agent.init', input }`,
812
+ * so a complete log replays with no side-channel; when absent,
813
+ * `options.input` is used instead.
814
+ * Raised/internal events are never in the journal — replay re-derives them
815
+ * deterministically from the machine's own logic.
816
+ */
817
+ function replay(machine, entries, options = {}) {
818
+ const machineId = machine.config.id ?? machine.id ?? "(machine)";
819
+ const machineVersion = options.machineVersion ?? getMachineStructuralHash(machine);
820
+ const eventIds = /* @__PURE__ */ new Set();
821
+ for (let index = 0; index < entries.length; index++) {
822
+ const entry = entries[index];
823
+ assertAgentLogEntry(entry);
824
+ if (entry.index !== index) throw new Error(`Replay entries must be contiguous from index 0; found entry.index ${entry.index} at position ${index}.`);
825
+ if (eventIds.has(entry.id)) throw new Error(`Replay entries contain duplicate event id '${entry.id}'.`);
826
+ eventIds.add(entry.id);
827
+ if (entry.machineId !== machineId || entry.machineVersion !== machineVersion) throw new AgentReplayMachineMismatchError(entry.id, entry.index, {
828
+ machineId,
829
+ machineVersion
830
+ }, {
831
+ machineId: entry.machineId,
832
+ machineVersion: entry.machineVersion
833
+ });
834
+ }
835
+ const events = toEvents(entries);
836
+ let input = options.input;
837
+ let journal = events;
838
+ let journalEntries = entries;
839
+ if (events[0]?.type === "@agent.init") {
840
+ input = events[0].input;
841
+ journal = events.slice(1);
842
+ journalEntries = entries.slice(1);
843
+ }
844
+ let [snapshot, actions] = initialTransition(machine, input);
845
+ let effects = getAgentEffects(machine, snapshot, actions, {
846
+ ...options,
847
+ history: entries.slice(0, events[0]?.type === "@agent.init" ? 1 : 0)
848
+ });
849
+ if (events[0]?.type === "@agent.init") verifyEntry(entries[0], snapshot, effects, options.verify);
850
+ const sessions = /* @__PURE__ */ new Map();
851
+ for (let index = 0; index < journal.length; index++) {
852
+ const event = journal[index];
853
+ const reboundEvent = rebindActorSession(event, snapshot, sessions);
854
+ [snapshot, actions] = transition(machine, snapshot, reboundEvent);
855
+ const consumed = entries.slice(0, entries.length - journalEntries.length + index + 1);
856
+ effects = getAgentEffects(machine, snapshot, actions, {
857
+ ...options,
858
+ history: consumed
859
+ });
860
+ verifyEntry(journalEntries[index], snapshot, effects, options.verify);
861
+ }
862
+ return {
863
+ snapshot,
864
+ effects
865
+ };
866
+ }
867
+ /** Requires and checks every entry's recorded state/effect hashes. */
868
+ function verifyReplay(machine, entries, options = {}) {
869
+ return replay(machine, entries, {
870
+ ...options,
871
+ verify: "strict"
872
+ });
873
+ }
874
+ /** Structural event-tail, logical-state, and owed-effect comparison. */
875
+ function diffEventLogs(machine, parentEntries, forkEntries, options = {}) {
876
+ let commonLength = 0;
877
+ while (commonLength < parentEntries.length && commonLength < forkEntries.length && stableJson(parentEntries[commonLength]) === stableJson(forkEntries[commonLength])) commonLength++;
878
+ const parent = replay(machine, parentEntries, options);
879
+ const fork = replay(machine, forkEntries, options);
880
+ const parentState = logicalReplayState(parent.snapshot);
881
+ const forkState = logicalReplayState(fork.snapshot);
882
+ return {
883
+ commonPrefix: {
884
+ length: commonLength,
885
+ ...commonLength > 0 ? { throughEventId: parentEntries[commonLength - 1].id } : {}
886
+ },
887
+ parentOnly: parentEntries.slice(commonLength),
888
+ forkOnly: forkEntries.slice(commonLength),
889
+ parent,
890
+ fork,
891
+ stateChanges: diffJson(parentState, forkState),
892
+ effectChanges: diffEffects(parent.effects, fork.effects)
893
+ };
894
+ }
895
+ function verifyEntry(entry, snapshot, effects, mode) {
896
+ if (mode === false) return;
897
+ if (!entry.verification) {
898
+ if (mode === "strict") throw new AgentReplayDivergenceError(entry.id, entry.index, "missing-verification");
899
+ return;
900
+ }
901
+ const actual = replayVerification(snapshot, effects);
902
+ if (actual.stateHash !== entry.verification.stateHash) throw new AgentReplayDivergenceError(entry.id, entry.index, "state", entry.verification.stateHash, actual.stateHash);
903
+ if (actual.effectsHash !== entry.verification.effectsHash) throw new AgentReplayDivergenceError(entry.id, entry.index, "effects", entry.verification.effectsHash, actual.effectsHash);
904
+ }
905
+ function replayVerification(snapshot, effects) {
906
+ return {
907
+ stateHash: hashStableJson(logicalReplayState(snapshot)),
908
+ effectsHash: hashStableJson(serializableEffects(effects))
909
+ };
910
+ }
911
+ function logicalReplayState(snapshot) {
912
+ const state = {
913
+ status: snapshot.status,
914
+ value: snapshot.value,
915
+ context: snapshot.context
916
+ };
917
+ if (snapshot.output !== void 0) state.output = snapshot.output;
918
+ if (snapshot.error !== void 0) state.error = snapshot.error;
919
+ const canonical = canonicalizeForHash(state);
920
+ assertJsonSerializable(canonical, "snapshot");
921
+ return canonical;
922
+ }
923
+ function serializableEffects(effects) {
924
+ return effects.map((effect) => canonicalizeEffect(effect));
925
+ }
926
+ function canonicalizeEffect(effect) {
927
+ if (effect.kind === "execute") return {
928
+ kind: effect.kind,
929
+ type: String(effect.action.type)
930
+ };
931
+ const value = { ...effect };
932
+ delete value.toDoneEvent;
933
+ delete value.toErrorEvent;
934
+ delete value.exec;
935
+ delete value.action;
936
+ return canonicalizeForHash(value);
937
+ }
938
+ function canonicalizeForHash(value, seen = /* @__PURE__ */ new WeakSet()) {
939
+ if (value === void 0) return "[undefined]";
940
+ if (typeof value === "function") return "[function]";
941
+ if (typeof value === "bigint") return `[bigint:${String(value)}]`;
942
+ if (typeof value === "symbol") return `[symbol:${String(value)}]`;
943
+ if (value === null || typeof value !== "object") return value;
944
+ if (value instanceof Date) return `[Date:${value.toISOString()}]`;
945
+ if (value instanceof Error) return {
946
+ errorName: value.name,
947
+ message: value.message,
948
+ ...value.cause !== void 0 ? { cause: canonicalizeForHash(value.cause, seen) } : {}
949
+ };
950
+ if (value instanceof Set) return [...value].map((item) => canonicalizeForHash(item, seen)).sort((a, b) => stableJson(a).localeCompare(stableJson(b)));
951
+ if (value instanceof Map) return [...value.entries()].map(([key, item]) => [canonicalizeForHash(key, seen), canonicalizeForHash(item, seen)]).sort(([a], [b]) => stableJson(a).localeCompare(stableJson(b)));
952
+ if (seen.has(value)) return "[circular]";
953
+ seen.add(value);
954
+ if (Array.isArray(value)) {
955
+ const result = value.map((item) => canonicalizeForHash(item, seen));
956
+ seen.delete(value);
957
+ return result;
958
+ }
959
+ const standard = value["~standard"];
960
+ if (standard) {
961
+ seen.delete(value);
962
+ return {
963
+ schemaVendor: standard.vendor,
964
+ schemaVersion: standard.version
965
+ };
966
+ }
967
+ const result = {};
968
+ for (const key of Object.keys(value).sort()) result[key] = canonicalizeForHash(value[key], seen);
969
+ seen.delete(value);
970
+ return result;
971
+ }
972
+ function normalizeEventErrors(value, seen = /* @__PURE__ */ new WeakMap()) {
973
+ if (value instanceof Error) {
974
+ const normalized = {
975
+ name: value.name,
976
+ message: value.message
977
+ };
978
+ seen.set(value, normalized);
979
+ if (value.cause !== void 0) normalized.cause = normalizeEventErrors(value.cause, seen);
980
+ return normalized;
981
+ }
982
+ if (value === null || typeof value !== "object") return value;
983
+ const existing = seen.get(value);
984
+ if (existing !== void 0) return existing;
985
+ if (Array.isArray(value)) {
986
+ const result = [];
987
+ seen.set(value, result);
988
+ for (const item of value) result.push(normalizeEventErrors(item, seen));
989
+ return result;
990
+ }
991
+ const prototype = Object.getPrototypeOf(value);
992
+ if (prototype !== Object.prototype && prototype !== null) return value;
993
+ const result = {};
994
+ seen.set(value, result);
995
+ for (const [key, item] of Object.entries(value)) result[key] = normalizeEventErrors(item, seen);
996
+ return result;
997
+ }
998
+ function stableJson(value) {
999
+ return JSON.stringify(sortJson(value));
1000
+ }
1001
+ function sortJson(value) {
1002
+ if (Array.isArray(value)) return value.map(sortJson);
1003
+ if (value && typeof value === "object") return Object.fromEntries(Object.keys(value).sort().map((key) => [key, sortJson(value[key])]));
1004
+ return value;
1005
+ }
1006
+ function hashStableJson(value) {
1007
+ const input = stableJson(value);
1008
+ let hash = 5381;
1009
+ for (let index = 0; index < input.length; index++) hash = (hash << 5) + hash + input.charCodeAt(index) | 0;
1010
+ return (hash >>> 0).toString(16).padStart(8, "0");
1011
+ }
1012
+ function diffJson(before, after, path = "") {
1013
+ if (stableJson(before) === stableJson(after)) return [];
1014
+ if (before === null || after === null || typeof before !== "object" || typeof after !== "object" || Array.isArray(before) || Array.isArray(after)) return [{
1015
+ op: "replace",
1016
+ path,
1017
+ value: after
1018
+ }];
1019
+ const changes = [];
1020
+ const beforeObject = before;
1021
+ const afterObject = after;
1022
+ const keys = new Set([...Object.keys(beforeObject), ...Object.keys(afterObject)]);
1023
+ for (const key of [...keys].sort()) {
1024
+ const childPath = `${path}/${key.replaceAll("~", "~0").replaceAll("/", "~1")}`;
1025
+ if (!(key in afterObject)) changes.push({
1026
+ op: "remove",
1027
+ path: childPath
1028
+ });
1029
+ else if (!(key in beforeObject)) changes.push({
1030
+ op: "add",
1031
+ path: childPath,
1032
+ value: afterObject[key]
1033
+ });
1034
+ else changes.push(...diffJson(beforeObject[key], afterObject[key], childPath));
1035
+ }
1036
+ return changes;
1037
+ }
1038
+ function diffEffects(before, after) {
1039
+ const beforeValues = serializableEffects(before);
1040
+ const afterValues = serializableEffects(after);
1041
+ const key = (value, index) => {
1042
+ const record = value;
1043
+ return typeof record.requestId === "string" ? record.requestId : `${String(record.kind ?? "effect")}:${index}`;
1044
+ };
1045
+ const beforeMap = new Map(beforeValues.map((value, index) => [key(value, index), value]));
1046
+ const afterMap = new Map(afterValues.map((value, index) => [key(value, index), value]));
1047
+ const added = [];
1048
+ const removed = [];
1049
+ const changed = [];
1050
+ for (const [id, value] of beforeMap) {
1051
+ const next = afterMap.get(id);
1052
+ if (next === void 0) removed.push(value);
1053
+ else if (stableJson(value) !== stableJson(next)) changed.push({
1054
+ before: value,
1055
+ after: next
1056
+ });
1057
+ }
1058
+ for (const [id, value] of afterMap) if (!beforeMap.has(id)) added.push(value);
1059
+ return {
1060
+ added,
1061
+ removed,
1062
+ changed
1063
+ };
1064
+ }
1065
+ //#endregion
1066
+ //#region src/workflow-config.ts
1067
+ /**
1068
+ * The machines-as-data lowering: `setupAgent(...).fromConfig(config, options)`.
1069
+ *
1070
+ * Takes a serializable `AgentWorkflowConfig` (the JSON form of an agent
1071
+ * machine — published schema at `schemas/agent-workflow.json`, docs at
1072
+ * docs/machines-as-data.md), compiles its embedded JSON Schemas with the
1073
+ * host-provided `compileSchema`, resolves named guards/actions/actors from
1074
+ * `options`, and lowers the whole thing to an ordinary XState machine plus its
1075
+ * schema pack. This is what makes LLM-generated and stored-as-JSON machines
1076
+ * first-class: the config validates against the shipped schema before any
1077
+ * model call, and the resulting machine lints, simulates, and runs like a
1078
+ * hand-authored one.
1079
+ */
1080
+ const DECIDE_SRC = "agent.decide";
1081
+ const workflowConfigWholeExpressionPattern = /^\{\{\s*([\s\S]*?)\s*\}\}$/;
1082
+ const workflowConfigTemplateExpressionPattern = /\{\{\s*([\s\S]*?)\s*\}\}/g;
1083
+ const workflowConfigHasTemplatePattern = /\{\{[\s\S]*?\}\}/;
1084
+ function evaluateWorkflowConfigPath(expression, scope) {
1085
+ const parts = expression.trim().split(".").filter(Boolean);
1086
+ let current = scope;
1087
+ for (const part of parts) {
1088
+ if (!current || typeof current !== "object") return;
1089
+ current = current[part];
1090
+ }
1091
+ return current;
1092
+ }
1093
+ function evaluateWorkflowConfigValue(value, scope) {
1094
+ if (typeof value === "string") {
1095
+ const wholeMatch = value.match(workflowConfigWholeExpressionPattern);
1096
+ if (wholeMatch?.[1]) return evaluateWorkflowConfigPath(wholeMatch[1], scope);
1097
+ return value.replace(workflowConfigTemplateExpressionPattern, (_match, expression) => {
1098
+ const resolved = evaluateWorkflowConfigPath(expression, scope);
1099
+ return resolved === void 0 || resolved === null ? "" : String(resolved);
1100
+ });
1101
+ }
1102
+ if (Array.isArray(value)) return value.map((item) => evaluateWorkflowConfigValue(item, scope));
1103
+ if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, evaluateWorkflowConfigValue(item, scope)]));
1104
+ return value;
1105
+ }
1106
+ function createSchemasFromWorkflowConfig(config, compileSchema) {
1107
+ return createAgentSchemas({
1108
+ context: compileSchema(config.schemas?.context ?? { type: "object" }, "context"),
1109
+ events: Object.fromEntries(Object.entries(config.schemas?.events ?? {}).map(([key, schema]) => [key, compileSchema(schema, `event.${key}`)])),
1110
+ emitted: Object.fromEntries(Object.entries(config.schemas?.emitted ?? {}).map(([key, schema]) => [key, compileSchema(schema, `emitted.${key}`)])),
1111
+ input: compileSchema(config.schemas?.input ?? {}, "input"),
1112
+ output: compileSchema(config.schemas?.output ?? {}, "output"),
1113
+ meta: compileSchema(config.schemas?.meta ?? {}, "meta")
1114
+ });
1115
+ }
1116
+ function createRequestsFromWorkflowConfig(config, compileSchema) {
1117
+ return Object.fromEntries(Object.entries(config.requests ?? {}).map(([key, request]) => [key, {
1118
+ mode: request.mode,
1119
+ description: request.description,
1120
+ schemas: {
1121
+ input: compileSchema(request.input, `${key}.input`),
1122
+ output: compileSchema(request.output, `${key}.output`)
1123
+ },
1124
+ model: ({ input }) => String(evaluateWorkflowConfigValue(request.model, { input }) ?? ""),
1125
+ system: request.system === void 0 ? void 0 : ({ input }) => evaluateWorkflowConfigValue(request.system, { input }),
1126
+ prompt: request.prompt === void 0 ? void 0 : ({ input }) => evaluateWorkflowConfigValue(request.prompt, { input }),
1127
+ messages: request.messages === void 0 ? void 0 : ({ input }) => evaluateWorkflowConfigValue(request.messages, { input }),
1128
+ tools: request.tools,
1129
+ toolChoice: request.toolChoice,
1130
+ reasoning: request.reasoning,
1131
+ temperature: request.temperature === void 0 ? void 0 : ({ input }) => evaluateWorkflowConfigValue(request.temperature, { input }),
1132
+ maxOutputTokens: request.maxOutputTokens === void 0 ? void 0 : ({ input }) => evaluateWorkflowConfigValue(request.maxOutputTokens, { input }),
1133
+ topP: request.topP === void 0 ? void 0 : ({ input }) => evaluateWorkflowConfigValue(request.topP, { input }),
1134
+ topK: request.topK === void 0 ? void 0 : ({ input }) => evaluateWorkflowConfigValue(request.topK, { input }),
1135
+ seed: request.seed === void 0 ? void 0 : ({ input }) => evaluateWorkflowConfigValue(request.seed, { input }),
1136
+ stopSequences: request.stopSequences === void 0 ? void 0 : ({ input }) => evaluateWorkflowConfigValue(request.stopSequences, { input }),
1137
+ metadata: request.metadata === void 0 ? void 0 : ({ input }) => evaluateWorkflowConfigValue(request.metadata, { input })
1138
+ }]));
1139
+ }
1140
+ function createActorPlaceholdersFromWorkflowConfig(config) {
1141
+ return Object.fromEntries(Object.keys(config.actors ?? {}).map((key) => [key, missingActor(key)]));
1142
+ }
1143
+ const AGENT_EXPRESSION_LANG = "agent-template";
1144
+ function containsTemplateExpression(value) {
1145
+ if (typeof value === "string") return workflowConfigHasTemplatePattern.test(value);
1146
+ if (Array.isArray(value)) return value.some(containsTemplateExpression);
1147
+ if (value && typeof value === "object") return Object.values(value).some(containsTemplateExpression);
1148
+ return false;
1149
+ }
1150
+ function toWorkflowExpression(value) {
1151
+ if (!containsTemplateExpression(value)) return value;
1152
+ return { "@expr": JSON.stringify(value) };
1153
+ }
1154
+ function createWorkflowConfigEvaluator(contextSchema) {
1155
+ const decoded = /* @__PURE__ */ new Map();
1156
+ const decode = (source) => {
1157
+ if (!decoded.has(source)) decoded.set(source, JSON.parse(source));
1158
+ return decoded.get(source);
1159
+ };
1160
+ return ({ source, slot, scope }) => {
1161
+ const value = decode(source);
1162
+ if (slot === "context") return validateSchemaSync(contextSchema, evaluateWorkflowConfigValue(value, { input: scope.input }));
1163
+ return evaluateWorkflowConfigValue(value, {
1164
+ context: scope.context,
1165
+ event: scope.event,
1166
+ input: scope.input
1167
+ });
1168
+ };
1169
+ }
1170
+ function translateWorkflowGuard(guard, translation, location) {
1171
+ if (guard === void 0) return;
1172
+ if (typeof guard === "string") {
1173
+ if (workflowConfigHasTemplatePattern.test(guard)) return { "@expr": JSON.stringify(guard) };
1174
+ if (!(guard in translation.guards)) throw new Error(`setupAgent.fromConfig: ${location} references guard '${guard}', which has no implementation. Provide one via fromConfig options: setupAgent.fromConfig(config, { guards: { '${guard}': ({ context, event }) => ... } }).`);
1175
+ return { type: guard };
1176
+ }
1177
+ if (typeof guard === "function") {
1178
+ const name = `__fromConfigGuard${Object.keys(translation.syntheticGuards).length}`;
1179
+ translation.syntheticGuards[name] = guard;
1180
+ return { type: name };
1181
+ }
1182
+ throw new Error(`setupAgent.fromConfig: ${location} has an unsupported guard value. Use a '{{ ... }}' template string, a named guard reference (string), or a function.`);
1183
+ }
1184
+ function translateWorkflowAction(action, translation, location) {
1185
+ if (action.assign !== void 0) return {
1186
+ type: "@xstate.assign",
1187
+ context: toWorkflowExpression(action.assign)
1188
+ };
1189
+ if (action.emit !== void 0) return {
1190
+ type: "@xstate.emit",
1191
+ event: toWorkflowExpression(action.emit)
1192
+ };
1193
+ if (action.type) {
1194
+ if (!(action.type in translation.actions)) throw new Error(`setupAgent.fromConfig: ${location} uses named action type '${action.type}', which has no implementation. Provide one via fromConfig options: setupAgent.fromConfig(config, { actions: { '${action.type}': (params) => ... } }).`);
1195
+ return {
1196
+ type: action.type,
1197
+ ...action.params !== void 0 ? { params: toWorkflowExpression(action.params) } : {}
1198
+ };
1199
+ }
1200
+ throw new Error(`setupAgent.fromConfig: ${location} action must declare 'assign', 'emit', or 'type'.`);
1201
+ }
1202
+ function translateWorkflowTransition(transitionConfig, translation, location) {
1203
+ const actionConfigs = transitionConfig.actions ? Array.isArray(transitionConfig.actions) ? transitionConfig.actions : [transitionConfig.actions] : [];
1204
+ const assignConfigs = [...transitionConfig.assign !== void 0 ? [transitionConfig.assign] : [], ...actionConfigs.filter((action) => action.assign !== void 0).map((action) => action.assign)];
1205
+ const contextPatch = assignConfigs.length ? toWorkflowExpression(Object.assign({}, ...assignConfigs)) : void 0;
1206
+ const actions = actionConfigs.filter((action) => action.assign === void 0).map((action) => translateWorkflowAction(action, translation, location));
1207
+ const guard = translateWorkflowGuard(transitionConfig.guard, translation, location);
1208
+ return {
1209
+ ...transitionConfig.target !== void 0 ? { target: transitionConfig.target } : {},
1210
+ ...guard ? { guard } : {},
1211
+ ...contextPatch !== void 0 ? { context: contextPatch } : {},
1212
+ ...actions.length ? { actions } : {},
1213
+ ...transitionConfig.description !== void 0 ? { description: transitionConfig.description } : {},
1214
+ ...transitionConfig.reenter !== void 0 ? { reenter: transitionConfig.reenter } : {},
1215
+ ...transitionConfig.meta !== void 0 ? { meta: transitionConfig.meta } : {}
1216
+ };
1217
+ }
1218
+ function translateWorkflowTransitions(transitionConfig, translation, location) {
1219
+ if (!transitionConfig) return;
1220
+ return Array.isArray(transitionConfig) ? transitionConfig.map((candidate) => translateWorkflowTransition(candidate, translation, location)) : translateWorkflowTransition(transitionConfig, translation, location);
1221
+ }
1222
+ function addTransitionMatch(transition, matches) {
1223
+ if (Array.isArray(transition)) return transition.map((candidate) => addTransitionMatch(candidate, matches));
1224
+ return transition ? {
1225
+ ...transition,
1226
+ matches: {
1227
+ ...transition.matches,
1228
+ ...matches
1229
+ }
1230
+ } : void 0;
1231
+ }
1232
+ function translateWorkflowInvoke(invokeConfig, translation, stateKey) {
1233
+ if (invokeConfig.src === DECIDE_SRC && 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).`);
1234
+ const location = `state '${stateKey}'`;
1235
+ return {
1236
+ ...invokeConfig.id !== void 0 ? { id: invokeConfig.id } : {},
1237
+ src: invokeConfig.src,
1238
+ ...invokeConfig.input !== void 0 ? { input: toWorkflowExpression(invokeConfig.input) } : {},
1239
+ ...invokeConfig.onDone !== void 0 ? { onDone: translateWorkflowTransitions(invokeConfig.onDone, translation, location) } : {},
1240
+ ...invokeConfig.onError !== void 0 ? { onError: translateWorkflowTransitions(invokeConfig.onError, translation, location) } : {}
1241
+ };
1242
+ }
1243
+ function assertValidTransitionTargets(stateKey, location, transitionConfig, siblingKeys, declaredTargets) {
1244
+ if (!transitionConfig) return;
1245
+ const transitions = Array.isArray(transitionConfig) ? transitionConfig : [transitionConfig];
1246
+ for (const transition of transitions) {
1247
+ const targets = transition.target === void 0 ? [] : Array.isArray(transition.target) ? transition.target : [transition.target];
1248
+ for (const target of targets) {
1249
+ if (typeof target !== "string") continue;
1250
+ declaredTargets.push(target);
1251
+ if (target.startsWith("#") || target.startsWith(".")) continue;
1252
+ const head = target.split(".")[0];
1253
+ 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)"}.`);
1254
+ }
1255
+ }
1256
+ }
1257
+ function assertStateTransitionTargets(stateKey, stateConfig, siblingKeys) {
1258
+ const declaredTargets = [];
1259
+ for (const [eventType, transition] of Object.entries(stateConfig.on ?? {})) assertValidTransitionTargets(stateKey, `'on.${eventType}'`, transition, siblingKeys, declaredTargets);
1260
+ for (const [delay, transition] of Object.entries(stateConfig.after ?? {})) assertValidTransitionTargets(stateKey, `'after.${delay}'`, transition, siblingKeys, declaredTargets);
1261
+ assertValidTransitionTargets(stateKey, "'always'", stateConfig.always, siblingKeys, declaredTargets);
1262
+ assertValidTransitionTargets(stateKey, "'onDone'", stateConfig.onDone, siblingKeys, declaredTargets);
1263
+ assertValidTransitionTargets(stateKey, "'choice'", stateConfig.choice, siblingKeys, declaredTargets);
1264
+ const invokes = stateConfig.invoke ? Array.isArray(stateConfig.invoke) ? stateConfig.invoke : [stateConfig.invoke] : [];
1265
+ for (const invoke of invokes) {
1266
+ assertValidTransitionTargets(stateKey, "invoke 'onDone'", invoke.onDone, siblingKeys, declaredTargets);
1267
+ assertValidTransitionTargets(stateKey, "invoke 'onError'", invoke.onError, siblingKeys, declaredTargets);
1268
+ }
1269
+ return declaredTargets;
1270
+ }
1271
+ function translateWorkflowState(stateKey, stateConfig, siblingKeys, path, translation) {
1272
+ const location = `state '${stateKey}'`;
1273
+ if (stateKey.includes(".")) throw new Error(`setupAgent.fromConfig: state key '${stateKey}' contains a '.', which is reserved as the state-path separator. Rename the state (e.g. '${stateKey.replace(/\./g, "_")}').`);
1274
+ const childKeys = new Set(Object.keys(stateConfig.states ?? {}));
1275
+ const childPath = [...path, stateKey];
1276
+ translation.transitionTargets[childPath.join(".")] = assertStateTransitionTargets(stateKey, stateConfig, siblingKeys);
1277
+ const doneStateId = stateConfig.onDone !== void 0 ? `@state.${childPath.join(".")}` : void 0;
1278
+ const translatedOn = {
1279
+ ...Object.fromEntries(Object.entries(stateConfig.on ?? {}).map(([eventType, transitionConfig]) => [eventType, translateWorkflowTransitions(transitionConfig, translation, location)])),
1280
+ ...doneStateId ? { "xstate.done.state": addTransitionMatch(translateWorkflowTransitions(stateConfig.onDone, translation, location), { stateId: doneStateId }) } : {}
1281
+ };
1282
+ return {
1283
+ ...stateConfig.description !== void 0 ? { description: stateConfig.description } : {},
1284
+ ...stateConfig.type !== void 0 ? { type: stateConfig.type } : {},
1285
+ ...doneStateId ? { id: doneStateId } : {},
1286
+ ...stateConfig.initial !== void 0 ? { initial: stateConfig.initial } : {},
1287
+ ...stateConfig.states !== void 0 ? { states: Object.fromEntries(Object.entries(stateConfig.states).map(([key, child]) => [key, translateWorkflowState(key, child, childKeys, childPath, translation)])) } : {},
1288
+ ...stateConfig.choice !== void 0 ? { choice: (Array.isArray(stateConfig.choice) ? stateConfig.choice : [stateConfig.choice]).map((branch) => {
1289
+ if (branch.actions !== void 0) throw new Error(`setupAgent.fromConfig: state '${stateKey}' has a 'choice' branch with 'actions', which is not supported. Use 'assign' on the branch, or move the actions to the target state's 'entry'.`);
1290
+ const when = translateWorkflowGuard(branch.guard, translation, location);
1291
+ return {
1292
+ ...when ? { when } : {},
1293
+ target: branch.target,
1294
+ ...branch.assign !== void 0 ? { context: toWorkflowExpression(branch.assign) } : {},
1295
+ ...branch.description !== void 0 ? { description: branch.description } : {},
1296
+ ...branch.reenter !== void 0 ? { reenter: branch.reenter } : {},
1297
+ ...branch.meta !== void 0 ? { meta: branch.meta } : {}
1298
+ };
1299
+ }) } : {},
1300
+ ...stateConfig.invoke !== void 0 ? { invoke: Array.isArray(stateConfig.invoke) ? stateConfig.invoke.map((invoke) => translateWorkflowInvoke(invoke, translation, stateKey)) : translateWorkflowInvoke(stateConfig.invoke, translation, stateKey) } : {},
1301
+ ...Object.keys(translatedOn).length ? { on: translatedOn } : {},
1302
+ ...stateConfig.always !== void 0 ? { always: translateWorkflowTransitions(stateConfig.always, translation, location) } : {},
1303
+ ...stateConfig.after !== void 0 ? { after: Object.fromEntries(Object.entries(stateConfig.after).map(([delay, transitionConfig]) => [delay, translateWorkflowTransitions(transitionConfig, translation, location)])) } : {},
1304
+ ...stateConfig.entry !== void 0 ? { entry: (Array.isArray(stateConfig.entry) ? stateConfig.entry : [stateConfig.entry]).map((action) => translateWorkflowAction(action, translation, location)) } : {},
1305
+ ...stateConfig.exit !== void 0 ? { exit: (Array.isArray(stateConfig.exit) ? stateConfig.exit : [stateConfig.exit]).map((action) => translateWorkflowAction(action, translation, location)) } : {},
1306
+ ...stateConfig.tags !== void 0 ? { tags: stateConfig.tags } : {},
1307
+ ...stateConfig.output !== void 0 ? { output: toWorkflowExpression(stateConfig.output) } : {},
1308
+ ...stateConfig.meta !== void 0 ? { meta: stateConfig.meta } : {}
1309
+ };
1310
+ }
1311
+ function collectTranslatedFinalOutputs(states, outputs = []) {
1312
+ for (const state of Object.values(states ?? {})) {
1313
+ if (state?.type === "final" && state.output !== void 0) outputs.push(state.output);
1314
+ collectTranslatedFinalOutputs(state?.states, outputs);
1315
+ }
1316
+ return outputs;
1317
+ }
1318
+ function translateWorkflowConfig(config, translation) {
1319
+ const rootKeys = new Set(Object.keys(config.states));
1320
+ const states = Object.fromEntries(Object.entries(config.states).map(([key, state]) => [key, translateWorkflowState(key, state, rootKeys, [], translation)]));
1321
+ const json = {
1322
+ "@exprLang": AGENT_EXPRESSION_LANG,
1323
+ ...config.id !== void 0 ? { id: config.id } : {},
1324
+ ...config.description !== void 0 ? { description: config.description } : {},
1325
+ ...config.context !== void 0 ? { context: { "@expr": JSON.stringify(config.context) } } : {},
1326
+ initial: config.initial,
1327
+ states,
1328
+ ...config.meta !== void 0 ? { meta: config.meta } : {}
1329
+ };
1330
+ const finalOutputs = collectTranslatedFinalOutputs(states);
1331
+ if (json.output === void 0 && finalOutputs.length === 1) json.output = finalOutputs[0];
1332
+ return json;
1333
+ }
1334
+ function setupAgentFromConfig(config, options) {
1335
+ 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.");
1336
+ const { compileSchema } = options;
1337
+ const schemas = createSchemasFromWorkflowConfig(config, compileSchema);
1338
+ const requestActors = createRequestActors(createRequestsFromWorkflowConfig(config, compileSchema));
1339
+ const actors = createAgentActors(createActorPlaceholdersFromWorkflowConfig(config), requestActors);
1340
+ const translation = {
1341
+ guards: options.guards ?? {},
1342
+ actions: options.actions ?? {},
1343
+ syntheticGuards: {},
1344
+ transitionTargets: {}
1345
+ };
1346
+ const machine = createMachineFromConfig(translateWorkflowConfig(config, translation), {
1347
+ guards: Object.fromEntries(Object.entries({
1348
+ ...translation.guards,
1349
+ ...translation.syntheticGuards
1350
+ }).map(([name, implementation]) => [name, (args) => Boolean(implementation({
1351
+ context: args.context,
1352
+ event: args.event
1353
+ }))])),
1354
+ ...options.actions ? { actions: options.actions } : {},
1355
+ actors: Object.fromEntries(Object.keys(actors).map((key) => [key, key])),
1356
+ evaluators: { [AGENT_EXPRESSION_LANG]: createWorkflowConfigEvaluator(schemas.context) }
1357
+ }).provide({ actors });
1358
+ agentExecutionOptions.set(machine, {
1359
+ schemas,
1360
+ actors,
1361
+ models: {}
1362
+ });
1363
+ if (machine.config) machineStaticTransitionTargets.set(machine.config, translation.transitionTargets);
1364
+ return {
1365
+ machine,
1366
+ schemas
1367
+ };
1368
+ }
1369
+ //#endregion
1370
+ //#region src/setup-agent.ts
1371
+ const emptyEventSchema = { "~standard": {
1372
+ version: 1,
1373
+ vendor: "statelyai-agent",
1374
+ validate(value) {
1375
+ return value !== null && typeof value === "object" && Object.keys(value).length === 0 ? { value: {} } : { issues: [{ message: "Expected an empty event payload" }] };
1376
+ }
1377
+ } };
1378
+ const USAGE_TOKEN_FIELDS = [
1379
+ "inputTokens",
1380
+ "outputTokens",
1381
+ "totalTokens",
1382
+ "reasoningTokens",
1383
+ "cachedInputTokens"
1384
+ ];
1385
+ const USAGE_ATTRIBUTION_FIELDS = [
1386
+ "id",
1387
+ "src",
1388
+ "model",
1389
+ "name"
1390
+ ];
1391
+ const USAGE_KINDS = [
1392
+ "text",
1393
+ "decision",
1394
+ "plan"
1395
+ ];
1396
+ /**
1397
+ * Standard Schema for the reserved `'@agent.usage'` payload. Hand-rolled (no
1398
+ * validation-library dependency in core) and deliberately permissive about
1399
+ * unknown fields, so a newer runtime adding an attribution field cannot fail
1400
+ * an older machine's validation.
1401
+ */
1402
+ const agentUsageEventSchema = { "~standard": {
1403
+ version: 1,
1404
+ vendor: "statelyai-agent",
1405
+ validate(value) {
1406
+ const issues = [];
1407
+ if (!value || typeof value !== "object") return { issues: [{ message: "Expected an '@agent.usage' payload object" }] };
1408
+ const event = value;
1409
+ const usage = event.usage;
1410
+ if (!usage || typeof usage !== "object") issues.push({
1411
+ message: "Expected 'usage' to be an object",
1412
+ path: ["usage"]
1413
+ });
1414
+ else for (const field of USAGE_TOKEN_FIELDS) {
1415
+ const token = usage[field];
1416
+ if (token !== void 0 && (typeof token !== "number" || !Number.isFinite(token))) issues.push({
1417
+ message: `Expected a finite number`,
1418
+ path: ["usage", field]
1419
+ });
1420
+ }
1421
+ if (event.kind !== void 0 && !USAGE_KINDS.includes(event.kind)) issues.push({
1422
+ message: `Expected one of ${USAGE_KINDS.join(", ")}`,
1423
+ path: ["kind"]
1424
+ });
1425
+ for (const field of USAGE_ATTRIBUTION_FIELDS) if (event[field] !== void 0 && typeof event[field] !== "string") issues.push({
1426
+ message: "Expected a string",
1427
+ path: [field]
1428
+ });
1429
+ return issues.length > 0 ? { issues } : { value: event };
1430
+ }
1431
+ } };
1432
+ /**
1433
+ * Adds the reserved `'@agent.usage'` schema to an authored event map. A
1434
+ * user-declared entry under that key is rejected: the `@agent.*` namespace
1435
+ * belongs to the library (same rule as the reserved `agent.*` actor keys), and
1436
+ * a custom payload schema would silently disagree with what `runAgent`
1437
+ * delivers.
1438
+ */
1439
+ function withAgentUsageEventSchema(events) {
1440
+ const declared = events?.[AGENT_USAGE_EVENT_TYPE];
1441
+ if (declared !== void 0 && declared !== agentUsageEventSchema) throw new Error(`setupAgent: event type '${AGENT_USAGE_EVENT_TYPE}' is in the reserved '@agent.' namespace and cannot be declared in 'events' — setupAgent registers it for you with the usage payload schema. Remove it from 'events'; to receive it, declare a transition instead: on: { '${AGENT_USAGE_EVENT_TYPE}': … }.`);
1442
+ return {
1443
+ ...events,
1444
+ [AGENT_USAGE_EVENT_TYPE]: agentUsageEventSchema
1445
+ };
1446
+ }
1447
+ function normalizeEventSchemas(events) {
1448
+ return Object.fromEntries(Object.entries(events).map(([type, schema]) => [type, schema && typeof schema === "object" && "~standard" in schema ? schema : emptyEventSchema]));
1449
+ }
1450
+ /**
1451
+ * Builds a machine's {@link AgentSchemaPack} from a partial schema
1452
+ * declaration — only `context` is required; `events`/`input`/`output`/`meta`
1453
+ * default to empty/unknown schemas when omitted. Pass the result as
1454
+ * `setupAgent({ schemas })`'s `schemas` (or spread the individual fields
1455
+ * directly into `setupAgent({ context, events, ... })` — both forms are
1456
+ * accepted).
1457
+ */
1458
+ function createAgentSchemas(schemas) {
1459
+ return {
1460
+ context: schemas.context,
1461
+ events: normalizeEventSchemas(withAgentUsageEventSchema(schemas.events)),
1462
+ input: schemas.input,
1463
+ output: schemas.output,
1464
+ meta: schemas.meta,
1465
+ emitted: schemas.emitted
1466
+ };
1467
+ }
1468
+ function mergeContextSchema(base, fields) {
1469
+ return { "~standard": {
1470
+ version: 1,
1471
+ vendor: "statelyai-agent",
1472
+ validate(value) {
1473
+ const baseResult = base["~standard"].validate(value);
1474
+ if (baseResult instanceof Promise) throw new Error("setupAgent: async context schemas are not supported.");
1475
+ if (baseResult.issues) return baseResult;
1476
+ const merged = { ...baseResult.value };
1477
+ const issues = [];
1478
+ for (const [key, fieldSchema] of Object.entries(fields)) {
1479
+ const fieldResult = fieldSchema["~standard"].validate(value[key]);
1480
+ if (fieldResult instanceof Promise) throw new Error("setupAgent: async context schemas are not supported.");
1481
+ if (fieldResult.issues) issues.push(...fieldResult.issues.map((issue) => ({
1482
+ ...issue,
1483
+ path: [key, ...issue.path ?? []]
1484
+ })));
1485
+ else merged[key] = fieldResult.value;
1486
+ }
1487
+ return issues.length > 0 ? { issues } : { value: merged };
1488
+ }
1489
+ } };
1490
+ }
1491
+ function resolveAgentStateSchemas(contextSchema, states) {
1492
+ return Object.fromEntries(Object.entries(states).map(([key, state]) => {
1493
+ if (!state || typeof state !== "object") return [key, state];
1494
+ const children = "states" in state && state.states ? resolveAgentStateSchemas(contextSchema, state.states) : void 0;
1495
+ if ("context" in state && state.context) return [key, {
1496
+ schemas: { context: mergeContextSchema(contextSchema, state.context) },
1497
+ ...children ? { states: children } : {}
1498
+ }];
1499
+ return [key, children ? {
1500
+ ...state,
1501
+ states: children
1502
+ } : state];
1503
+ }));
1504
+ }
1505
+ /**
1506
+ * Schema-first `setup(...)` for agent machines — the standard entry point
1507
+ * for authoring a machine (the blueprint) that this library then runs (via
1508
+ * {@link runAgent} or the step helpers) against host-supplied model/decision
1509
+ * executors. Context, events, machine input, machine output, and
1510
+ * state/transition meta are all standard schemas — no `{} as Type` casts —
1511
+ * and are retained on `result.schemas` for runtime validation. Also
1512
+ * registers the `agent.generateText`/`agent.streamText`/`agent.userInput`/
1513
+ * `agent.decide`/`agent.plan` builtin actors and lowers `requests`/`actors` into the
1514
+ * machine's actor sources. The result is the xstate `setup(...)` object with
1515
+ * a wrapped `result.createMachine(...)` plus `result.schemas`/`models`/
1516
+ * `requests`/`appendMessages` attached. Also has a
1517
+ * `setupAgent.fromConfig(...)` namespace member for building a machine from
1518
+ * a serializable {@link AgentWorkflowConfig} instead of this TS API.
1519
+ *
1520
+ * @example
1521
+ * ```ts
1522
+ * const schemas = createAgentSchemas({
1523
+ * context: z.object({ topic: z.string(), joke: z.string().nullable() }),
1524
+ * input: z.object({ topic: z.string() }),
1525
+ * output: z.object({ joke: z.string() }),
1526
+ * });
1527
+ *
1528
+ * const agent = setupAgent({
1529
+ * schemas,
1530
+ * actors: { tellJoke },
1531
+ * });
1532
+ *
1533
+ * const jokeMachine = agent.createMachine({
1534
+ * context: ({ input }) => ({ topic: input.topic, joke: null }),
1535
+ * initial: 'telling',
1536
+ * states: {
1537
+ * telling: {
1538
+ * invoke: {
1539
+ * id: 'joke',
1540
+ * src: 'tellJoke',
1541
+ * input: ({ context }) => ({ topic: context.topic }),
1542
+ * onDone: ({ output }) => ({ target: 'done', context: { joke: output } }),
1543
+ * },
1544
+ * },
1545
+ * done: { type: 'final', output: ({ context }) => ({ joke: context.joke ?? '' }) },
1546
+ * },
1547
+ * });
1548
+ * ```
1549
+ */
1550
+ function setupAgent(config) {
1551
+ return createSetupAgent(config);
1552
+ }
1553
+ function collectFinalStateOutputs(states, outputs = []) {
1554
+ for (const state of Object.values(states ?? {})) {
1555
+ if (state?.type === "final" && state.output !== void 0) outputs.push(state.output);
1556
+ collectFinalStateOutputs(state?.states, outputs);
1557
+ }
1558
+ return outputs;
1559
+ }
1560
+ function withRootOutputFromSingleFinal(config) {
1561
+ if (!config || typeof config !== "object" || "output" in config || !("states" in config)) return config;
1562
+ const outputs = collectFinalStateOutputs(config.states);
1563
+ return outputs.length === 1 ? {
1564
+ ...config,
1565
+ output: outputs[0]
1566
+ } : config;
1567
+ }
1568
+ (function(_setupAgent) {
1569
+ function fromConfig(config, options) {
1570
+ return setupAgentFromConfig(config, options);
1571
+ }
1572
+ _setupAgent.fromConfig = fromConfig;
1573
+ })(setupAgent || (setupAgent = {}));
1574
+ /** Builds one TextLogic actor per `setupAgent({ requests })` entry. @internal */
1575
+ function createRequestActors(requests) {
1576
+ return Object.fromEntries(Object.entries(requests).map(([key, request]) => {
1577
+ return [key, createTextLogic({
1578
+ ...request,
1579
+ name: key,
1580
+ mode: request.mode ?? "generate"
1581
+ })];
1582
+ }));
1583
+ }
1584
+ function normalizeAgentSchemas(config) {
1585
+ if ("schemas" in config && config.schemas) return {
1586
+ ...config.schemas,
1587
+ events: normalizeEventSchemas(withAgentUsageEventSchema(config.schemas.events))
1588
+ };
1589
+ const loose = config;
1590
+ return createAgentSchemas({
1591
+ ...loose,
1592
+ context: loose.context
1593
+ });
1594
+ }
1595
+ function normalizeAgentRequestInput(requests) {
1596
+ return requests ?? {};
1597
+ }
1598
+ /**
1599
+ * Runtime guard: a key appearing in both `actors`/`requests` is almost
1600
+ * certainly a mistake (whichever spread applies last would silently win) —
1601
+ * fail fast with a clear message rather than let one implementation shadow
1602
+ * another.
1603
+ */
1604
+ function assertNoActorKeyCollisions(actors, requests) {
1605
+ const seenIn = /* @__PURE__ */ new Map();
1606
+ const groups = [["actors", actors], ["requests", requests]];
1607
+ for (const [groupName, group] of groups) for (const key of Object.keys(group ?? {})) {
1608
+ const existingGroup = seenIn.get(key);
1609
+ if (existingGroup) throw new Error(`setupAgent: key '${key}' is defined in both '${existingGroup}' and '${groupName}'. Each actor source key must be unique across 'actors' and 'requests'.`);
1610
+ seenIn.set(key, groupName);
1611
+ }
1612
+ }
1613
+ /**
1614
+ * Runtime guard: every key of the `setupAgent({ states })` narrowing map must
1615
+ * name a real state node in the machine config. A typo'd key is otherwise a
1616
+ * silent no-op — the narrowing simply never applies — so fail fast at
1617
+ * `createMachine` time. Walks nested `states` maps in parallel with the
1618
+ * machine config, since narrowing entries nest the same way.
1619
+ */
1620
+ function assertStateSchemaKeysExist(stateSchemas, machineStates, path = []) {
1621
+ if (!stateSchemas) return;
1622
+ for (const [key, stateSchema] of Object.entries(stateSchemas)) {
1623
+ const machineState = machineStates?.[key];
1624
+ if (!machineState || typeof machineState !== "object") {
1625
+ const validKeys = Object.keys(machineStates ?? {});
1626
+ const parent = path.length > 0 ? ` of state '${path.join(".")}'` : "";
1627
+ throw new Error(`setupAgent: 'states' key '${[...path, key].join(".")}' does not name a state in the machine config, so its context narrowing would silently never apply. Valid child states${parent}: ${validKeys.join(", ") || "(none)"}. Fix the key to match a state in createMachine({ states }), or remove it from setupAgent({ states }).`);
1628
+ }
1629
+ if (stateSchema && typeof stateSchema === "object" && "states" in stateSchema && stateSchema.states) assertStateSchemaKeysExist(stateSchema.states, machineState.states, [...path, key]);
1630
+ }
1631
+ }
1632
+ const RESERVED_AGENT_ACTOR_KEYS = [
1633
+ ...Object.keys(builtinTextActors),
1634
+ USER_INPUT_ACTOR,
1635
+ DECIDE_ACTOR,
1636
+ PLAN_ACTOR
1637
+ ];
1638
+ /**
1639
+ * Rejects a user-supplied `actors`/`requests` key in the reserved
1640
+ * `agent.*` builtin namespace. Without this, the builtins-first spread in
1641
+ * {@link createAgentActors} lets such a key overwrite the builtin
1642
+ * (`agent.decide`, `agent.plan`, …) silently. Deliberate override of a builtin
1643
+ * is still possible after the machine is created, via
1644
+ * `machine.provide({ actors: { 'agent.decide': ... } })`.
1645
+ */
1646
+ function assertNoReservedAgentKeys(actors, requests) {
1647
+ const groups = [["actors", actors], ["requests", requests]];
1648
+ 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({ actors: { '${key}': ... } }).`);
1649
+ }
1650
+ function createAgentActors(actors, requestActors) {
1651
+ assertNoActorKeyCollisions(actors, requestActors);
1652
+ assertNoReservedAgentKeys(actors, requestActors);
1653
+ return {
1654
+ ...builtinTextActors,
1655
+ [USER_INPUT_ACTOR]: userInputActor,
1656
+ [DECIDE_ACTOR]: createDecideActor(),
1657
+ [PLAN_ACTOR]: createPlanActor(),
1658
+ ...actors,
1659
+ ...requestActors
1660
+ };
1661
+ }
1662
+ function createAgentSetupConfig(schemas, actors, config) {
1663
+ return {
1664
+ schemas: {
1665
+ context: schemas.context,
1666
+ events: schemas.events,
1667
+ input: schemas.input,
1668
+ output: schemas.output,
1669
+ meta: schemas.meta,
1670
+ ...schemas.emitted && Object.keys(schemas.emitted).length > 0 ? { emitted: schemas.emitted } : {}
1671
+ },
1672
+ ...config.states ? { states: resolveAgentStateSchemas(schemas.context, config.states) } : {},
1673
+ actors,
1674
+ actions: config.actions,
1675
+ guards: config.guards,
1676
+ delays: config.delays
1677
+ };
1678
+ }
1679
+ function createSetupAgent(config) {
1680
+ const schemas = normalizeAgentSchemas(config);
1681
+ const requestActors = createRequestActors(normalizeAgentRequestInput(config.requests));
1682
+ const actors = createAgentActors(config.actors, requestActors);
1683
+ const base = setup(createAgentSetupConfig(schemas, actors, config));
1684
+ const createBaseMachine = base.createMachine.bind(base);
1685
+ const models = config.models ?? {};
1686
+ const machineOptions = {
1687
+ schemas,
1688
+ actors,
1689
+ models
1690
+ };
1691
+ return Object.assign(base, {
1692
+ createMachine(machineConfig) {
1693
+ assertStateSchemaKeysExist(config.states, machineConfig?.states);
1694
+ const machine = createBaseMachine(withRootOutputFromSingleFinal(machineConfig));
1695
+ agentExecutionOptions.set(machine, machineOptions);
1696
+ if (config.isSuspended) {
1697
+ const rootConfig = machine.config;
1698
+ if (rootConfig) machineSuspensionPredicates.set(rootConfig, config.isSuspended);
1699
+ }
1700
+ return machine;
1701
+ },
1702
+ schemas,
1703
+ models,
1704
+ requests: requestActors,
1705
+ appendMessages(resolve) {
1706
+ return appendMessages(resolve);
1707
+ }
1708
+ });
1709
+ }
1710
+ //#endregion
1711
+ export { resolveAgentRequests as _, AgentReplayDivergenceError as a, appendMessages as b, diffEventLogs as c, initEntry as d, replay as f, initialAgentStep as g, getInvokeEffectMetadata as h, AGENT_USAGE_EVENT_TYPE as i, getAgentEffects as l, executeAgentRequest as m, setupAgent as n, AgentReplayMachineMismatchError as o, verifyReplay as p, AGENT_INIT_EVENT_TYPE as r, createReplayEntry as s, createAgentSchemas as t, getCallUsage as u, resolveAgentStep as v, messagesSchema as x, transitionAgentStep as y };