@statelyai/agent 2.0.0-alpha.22 → 2.0.0-alpha.23

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