@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,741 @@
1
+ import { n as setupAgent } from "./setup-agent-DAZZSjDS.mjs";
2
+ //#region src/machines/internal.ts
3
+ /** The builtin inline text request every preset lowers a request entry to. */
4
+ const GENERATE_TEXT_SRC = "agent.generateText";
5
+ /** The builtin decision actor the router and supervisor presets invoke. */
6
+ const DECIDE_SRC = "agent.decide";
7
+ /**
8
+ * A permissive Standard Schema that carries a JSON Schema. Presets build their
9
+ * own context/input/output schemas this way so the module stays dependency-free
10
+ * (no Zod in `src/`) while `lintAgentMachine` and JSON tooling still see a
11
+ * serializable shape.
12
+ *
13
+ * @internal
14
+ */
15
+ function objectSchema(properties, required = []) {
16
+ const json = {
17
+ type: "object",
18
+ properties,
19
+ required: [...required]
20
+ };
21
+ return { "~standard": {
22
+ version: 1,
23
+ vendor: "statelyai-agent-machines",
24
+ validate: (value) => value !== null && typeof value === "object" ? { value } : { issues: [{ message: "Expected an object" }] },
25
+ jsonSchema: { input: () => json }
26
+ } };
27
+ }
28
+ /** A payload-less event schema (`{}` shorthand equivalent) with a JSON Schema. @internal */
29
+ const emptyPayload = objectSchema({});
30
+ /** JSON Schema fragments reused across preset context schemas. @internal */
31
+ const jsonString = { type: "string" };
32
+ /** @internal */
33
+ const jsonNumber = { type: "number" };
34
+ /** @internal */
35
+ const jsonRecord = {
36
+ type: "object",
37
+ additionalProperties: true
38
+ };
39
+ /** @internal */
40
+ const jsonArray = { type: "array" };
41
+ /** @internal */
42
+ const jsonAny = {};
43
+ /** True when `entry` delegates to a child machine rather than an inline request. @internal */
44
+ function isMachineEntry(entry) {
45
+ return "machine" in entry && !!entry.machine;
46
+ }
47
+ /** The actor sources a preset must register: one per child-machine entry. @internal */
48
+ function machineActors(entries) {
49
+ return Object.fromEntries(Object.entries(entries).filter(([, entry]) => isMachineEntry(entry)).map(([name, entry]) => [name, entry.machine]));
50
+ }
51
+ /** The `src` an entry invokes: its own actor key (child machine) or the inline text builtin. @internal */
52
+ function entrySrc(name, entry) {
53
+ return isMachineEntry(entry) ? name : GENERATE_TEXT_SRC;
54
+ }
55
+ /** Builds an entry's invoke `input` from the current prompt. @internal */
56
+ function entryInput(name, entry, defaultModel, prompt) {
57
+ if (isMachineEntry(entry)) return entry.input ? entry.input({ prompt }) : { prompt };
58
+ return requestInput(name, entry, defaultModel, prompt);
59
+ }
60
+ /** Builds the inline `agent.generateText` input for a request entry. @internal */
61
+ function requestInput(name, entry, defaultModel, prompt) {
62
+ const model = entry.model ?? defaultModel;
63
+ if (!model) throw new Error(`Preset request '${name}' has no model. Set 'model' on the entry or on the factory config.`);
64
+ return {
65
+ name,
66
+ model,
67
+ ...entry.instructions ? { system: entry.instructions } : {},
68
+ prompt,
69
+ ...entry.tools ? { tools: entry.tools } : {},
70
+ ...entry.outputSchema ? { outputSchema: entry.outputSchema } : {},
71
+ ...entry.maxTurns !== void 0 ? { metadata: { maxSteps: entry.maxTurns } } : {}
72
+ };
73
+ }
74
+ /**
75
+ * Rejects an entry name that would collide with a preset's own state names or
76
+ * with the reserved `agent.*` actor namespace. A collision is otherwise a
77
+ * confusing machine-build failure far from its cause.
78
+ *
79
+ * @internal
80
+ */
81
+ function assertEntryNames(kind, names, reserved) {
82
+ if (names.length === 0) throw new Error(`Preset requires at least one ${kind}.`);
83
+ for (const name of names) {
84
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) throw new Error(`Invalid ${kind} name '${name}'. Names must be identifier-like (letters, digits, underscore; not starting with a digit) — they become state names and event types.`);
85
+ if (reserved.includes(name)) throw new Error(`Invalid ${kind} name '${name}': it collides with a state this preset already declares (${reserved.join(", ")}). Rename it.`);
86
+ }
87
+ }
88
+ /** Renders the `name: description` list a routing/supervising model chooses from. @internal */
89
+ function renderEntryList(entries) {
90
+ return Object.entries(entries).map(([name, entry]) => `- ${name}: ${entry.description ?? "(no description)"}`).join("\n");
91
+ }
92
+ //#endregion
93
+ //#region src/machines/tool-loop.ts
94
+ const contextSchema$6 = objectSchema({
95
+ prompt: jsonString,
96
+ result: jsonAny
97
+ }, ["prompt"]);
98
+ const inputSchema$6 = objectSchema({ prompt: jsonString }, ["prompt"]);
99
+ const outputSchema$5 = objectSchema({ result: jsonAny }, ["result"]);
100
+ /**
101
+ * The single-state tool loop: one text request carries the `tools`, and the
102
+ * host runs the tool loop inside it (`maxTurns` bounds it). Selecting and
103
+ * executing tools is the model + host's business, not machine states.
104
+ *
105
+ * States: `answering` → `done`.
106
+ *
107
+ * ```ts
108
+ * const machine = createToolLoopMachine({
109
+ * model: "quick",
110
+ * instructions: "Answer using the tools.",
111
+ * tools: { calculate },
112
+ * maxTurns: 5,
113
+ * });
114
+ *
115
+ * const result = await runAgent(machine, {
116
+ * input: { prompt: "What is 42 * 17?" },
117
+ * executors,
118
+ * });
119
+ * // Snapshots and log entries carry machine.version ("1") automatically.
120
+ * ```
121
+ */
122
+ function createToolLoopMachine(config) {
123
+ const { model, instructions, tools, outputSchema: resultSchema, maxTurns, interruptOn } = config;
124
+ const metadata = {
125
+ ...maxTurns !== void 0 ? { maxSteps: maxTurns } : {},
126
+ ...interruptOn && interruptOn.length > 0 ? { interruptOn: [...interruptOn] } : {}
127
+ };
128
+ return setupAgent({
129
+ context: contextSchema$6,
130
+ input: inputSchema$6,
131
+ output: outputSchema$5
132
+ }).createMachine({
133
+ id: "tool-loop",
134
+ version: "1",
135
+ context: ({ input }) => ({
136
+ prompt: input.prompt,
137
+ result: null
138
+ }),
139
+ initial: "answering",
140
+ states: {
141
+ answering: { invoke: {
142
+ id: "answer",
143
+ src: GENERATE_TEXT_SRC,
144
+ input: ({ context }) => ({
145
+ name: "answer",
146
+ model,
147
+ ...instructions ? { system: instructions } : {},
148
+ prompt: context.prompt,
149
+ ...tools ? { tools } : {},
150
+ ...resultSchema ? { outputSchema: resultSchema } : {},
151
+ ...Object.keys(metadata).length > 0 ? { metadata } : {}
152
+ }),
153
+ onDone: ({ output }) => ({
154
+ target: "done",
155
+ context: { result: output }
156
+ })
157
+ } },
158
+ done: {
159
+ type: "final",
160
+ output: ({ context }) => ({ result: context.result })
161
+ }
162
+ }
163
+ });
164
+ }
165
+ //#endregion
166
+ //#region src/machines/sequential.ts
167
+ const contextSchema$5 = objectSchema({
168
+ prompt: jsonString,
169
+ results: jsonRecord,
170
+ previous: jsonAny
171
+ }, ["prompt", "results"]);
172
+ const inputSchema$5 = objectSchema({ prompt: jsonString }, ["prompt"]);
173
+ const outputSchema$4 = objectSchema({
174
+ results: jsonRecord,
175
+ output: jsonAny
176
+ }, ["results"]);
177
+ /**
178
+ * A prompt chain: each step is one state, and each step's output feeds the
179
+ * next. The default prompt for a step is the previous step's output, so a
180
+ * chain needs no `prompt` functions at all.
181
+ *
182
+ * States: one per step, in order → `done`.
183
+ *
184
+ * ```ts
185
+ * const machine = createSequentialMachine({
186
+ * model: "quick",
187
+ * steps: [
188
+ * { name: "outline", instructions: "Outline the post." },
189
+ * { name: "draft", instructions: "Write the post from the outline." },
190
+ * { name: "polish", instructions: "Tighten the prose." },
191
+ * ],
192
+ * });
193
+ * ```
194
+ */
195
+ function createSequentialMachine(config) {
196
+ const { model, steps } = config;
197
+ assertEntryNames("step", steps.map((step) => step.name), ["done"]);
198
+ const agentSetup = setupAgent({
199
+ context: contextSchema$5,
200
+ input: inputSchema$5,
201
+ output: outputSchema$4
202
+ });
203
+ const states = {};
204
+ steps.forEach((step, index) => {
205
+ const next = steps[index + 1]?.name ?? "done";
206
+ states[step.name] = { invoke: {
207
+ id: step.name,
208
+ src: GENERATE_TEXT_SRC,
209
+ input: ({ context }) => requestInput(step.name, {
210
+ instructions: step.instructions,
211
+ model: step.model,
212
+ outputSchema: step.outputSchema,
213
+ tools: step.tools,
214
+ maxTurns: step.maxTurns
215
+ }, model, step.prompt ? step.prompt({
216
+ prompt: context.prompt,
217
+ results: context.results,
218
+ previous: context.previous
219
+ }) : context.previous === null || context.previous === void 0 ? context.prompt : String(context.previous)),
220
+ onDone: ({ context, output }) => ({
221
+ target: next,
222
+ context: {
223
+ results: {
224
+ ...context.results,
225
+ [step.name]: output
226
+ },
227
+ previous: output
228
+ }
229
+ })
230
+ } };
231
+ });
232
+ states.done = {
233
+ type: "final",
234
+ output: ({ context }) => ({
235
+ results: context.results,
236
+ output: context.previous
237
+ })
238
+ };
239
+ const machineConfig = {
240
+ id: "sequential",
241
+ version: "1",
242
+ context: ({ input }) => ({
243
+ prompt: input.prompt,
244
+ results: {},
245
+ previous: null
246
+ }),
247
+ initial: steps[0].name,
248
+ states
249
+ };
250
+ return agentSetup.createMachine(machineConfig);
251
+ }
252
+ //#endregion
253
+ //#region src/machines/router.ts
254
+ const contextSchema$4 = objectSchema({
255
+ prompt: jsonString,
256
+ route: { type: ["string", "null"] },
257
+ result: jsonAny
258
+ }, ["prompt"]);
259
+ const inputSchema$4 = objectSchema({ prompt: jsonString }, ["prompt"]);
260
+ const outputSchema$3 = objectSchema({
261
+ route: { type: ["string", "null"] },
262
+ result: jsonAny
263
+ }, ["route"]);
264
+ /** The event a route decision chooses: `ROUTE_<name>`. */
265
+ function routeEventType(route) {
266
+ return `ROUTE_${route}`;
267
+ }
268
+ /**
269
+ * One `agent.decide` picks exactly one declared route, then the machine runs
270
+ * it. Only the declared routes have events and transitions, so a model naming
271
+ * anything else is rejected before any work happens — illegal routes are
272
+ * impossible, not discouraged.
273
+ *
274
+ * States: `routing` → one state per route → `done`.
275
+ *
276
+ * ```ts
277
+ * const machine = createRouterMachine({
278
+ * model: "quick",
279
+ * routes: {
280
+ * billing: { description: "Payments and invoices", instructions: "Answer the billing question." },
281
+ * technical: { description: "Bugs and outages", machine: technicalMachine },
282
+ * },
283
+ * fallback: "technical",
284
+ * });
285
+ * ```
286
+ */
287
+ function createRouterMachine(config) {
288
+ const { model, instructions, routes, fallback } = config;
289
+ const names = Object.keys(routes);
290
+ assertEntryNames("route", names, ["routing", "done"]);
291
+ if (fallback !== void 0 && !names.includes(fallback)) throw new Error(`createRouterMachine: fallback '${fallback}' is not a declared route (${names.join(", ")}).`);
292
+ const agentSetup = setupAgent({
293
+ context: contextSchema$4,
294
+ input: inputSchema$4,
295
+ output: outputSchema$3,
296
+ events: Object.fromEntries(names.map((name) => [routeEventType(name), emptyPayload])),
297
+ actors: machineActors(routes)
298
+ });
299
+ const routeList = renderEntryList(routes);
300
+ const states = {
301
+ routing: {
302
+ invoke: {
303
+ id: "route",
304
+ src: DECIDE_SRC,
305
+ input: ({ context }) => ({
306
+ model,
307
+ system: instructions ?? "Route the request to exactly one destination. Choose the single best fit.",
308
+ prompt: `Request:\n${context.prompt}\n\nDestinations:\n${routeList}`,
309
+ allowedEvents: names.map(routeEventType)
310
+ }),
311
+ ...fallback ? { onError: {
312
+ target: fallback,
313
+ context: { route: fallback }
314
+ } } : {}
315
+ },
316
+ on: Object.fromEntries(names.map((name) => [routeEventType(name), {
317
+ target: name,
318
+ context: { route: name }
319
+ }]))
320
+ },
321
+ done: {
322
+ type: "final",
323
+ output: ({ context }) => ({
324
+ route: context.route,
325
+ result: context.result
326
+ })
327
+ }
328
+ };
329
+ for (const [name, entry] of Object.entries(routes)) states[name] = { invoke: {
330
+ id: name,
331
+ src: entrySrc(name, entry),
332
+ input: ({ context }) => entryInput(name, entry, model, context.prompt),
333
+ onDone: ({ output }) => ({
334
+ target: "done",
335
+ context: { result: output }
336
+ })
337
+ } };
338
+ const machineConfig = {
339
+ id: "router",
340
+ version: "1",
341
+ context: ({ input }) => ({
342
+ prompt: input.prompt,
343
+ route: null,
344
+ result: null
345
+ }),
346
+ initial: "routing",
347
+ states
348
+ };
349
+ return agentSetup.createMachine(machineConfig);
350
+ }
351
+ //#endregion
352
+ //#region src/machines/parallel.ts
353
+ const contextSchema$3 = objectSchema({
354
+ prompt: jsonString,
355
+ results: jsonRecord
356
+ }, ["prompt", "results"]);
357
+ const inputSchema$3 = objectSchema({ prompt: jsonString }, ["prompt"]);
358
+ const outputSchema$2 = objectSchema({ results: jsonRecord }, ["results"]);
359
+ /**
360
+ * Static fan-out: every branch runs concurrently as its own region of one
361
+ * parallel state, and the run joins when all of them finish. Results are keyed
362
+ * by branch name.
363
+ *
364
+ * Branch count is fixed at author time. For an N decided at run time (a planner
365
+ * choosing subtopics), eject to `examples/fan-out`, which spawns branches
366
+ * dynamically.
367
+ *
368
+ * States: `running` (one region per branch) → `done`.
369
+ *
370
+ * ```ts
371
+ * const machine = createParallelMachine({
372
+ * model: "quick",
373
+ * branches: {
374
+ * security: { instructions: "Review for security issues." },
375
+ * performance: { instructions: "Review for performance issues." },
376
+ * },
377
+ * });
378
+ * ```
379
+ */
380
+ function createParallelMachine(config) {
381
+ const { model, branches } = config;
382
+ assertEntryNames("branch", Object.keys(branches), ["running", "done"]);
383
+ const agentSetup = setupAgent({
384
+ context: contextSchema$3,
385
+ input: inputSchema$3,
386
+ output: outputSchema$2,
387
+ actors: machineActors(branches)
388
+ });
389
+ const regions = {};
390
+ for (const [name, entry] of Object.entries(branches)) regions[name] = {
391
+ initial: "running",
392
+ states: {
393
+ running: { invoke: {
394
+ id: name,
395
+ src: entrySrc(name, entry),
396
+ input: ({ context }) => entryInput(name, entry, model, context.prompt),
397
+ onDone: ({ context, output }) => ({
398
+ target: "done",
399
+ context: { results: {
400
+ ...context.results,
401
+ [name]: output
402
+ } }
403
+ })
404
+ } },
405
+ done: { type: "final" }
406
+ }
407
+ };
408
+ const machineConfig = {
409
+ id: "parallel",
410
+ version: "1",
411
+ context: ({ input }) => ({
412
+ prompt: input.prompt,
413
+ results: {}
414
+ }),
415
+ initial: "running",
416
+ states: {
417
+ running: {
418
+ type: "parallel",
419
+ states: regions,
420
+ onDone: { target: "done" }
421
+ },
422
+ done: {
423
+ type: "final",
424
+ output: ({ context }) => ({ results: context.results })
425
+ }
426
+ }
427
+ };
428
+ return agentSetup.createMachine(machineConfig);
429
+ }
430
+ //#endregion
431
+ //#region src/machines/loop.ts
432
+ const contextSchema$2 = objectSchema({
433
+ prompt: jsonString,
434
+ iterations: jsonNumber,
435
+ results: jsonArray,
436
+ last: jsonAny
437
+ }, [
438
+ "prompt",
439
+ "iterations",
440
+ "results"
441
+ ]);
442
+ const inputSchema$2 = objectSchema({ prompt: jsonString }, ["prompt"]);
443
+ const outputSchema$1 = objectSchema({
444
+ iterations: jsonNumber,
445
+ results: jsonArray,
446
+ last: jsonAny
447
+ }, ["iterations", "results"]);
448
+ /**
449
+ * A bounded repeat: run the body, check `until` over the accumulated state,
450
+ * and either stop or go again. `maxIterations` is a guard, so the loop cannot
451
+ * run away even if `until` never returns `true`.
452
+ *
453
+ * States: `running` → `checking` → (`running` | `done`).
454
+ *
455
+ * ```ts
456
+ * const machine = createLoopMachine({
457
+ * model: "quick",
458
+ * body: { instructions: "Improve the draft. Return only the draft." },
459
+ * until: ({ last }) => String(last).length > 500,
460
+ * maxIterations: 4,
461
+ * });
462
+ * ```
463
+ */
464
+ function createLoopMachine(config) {
465
+ const { model, body, until, maxIterations } = config;
466
+ if (!Number.isInteger(maxIterations) || maxIterations < 1) throw new Error("createLoopMachine: maxIterations must be an integer >= 1.");
467
+ const agentSetup = setupAgent({
468
+ context: contextSchema$2,
469
+ input: inputSchema$2,
470
+ output: outputSchema$1,
471
+ actors: isMachineEntry(body) ? { body: body.machine } : {}
472
+ });
473
+ const loopState = (context) => ({
474
+ prompt: context.prompt,
475
+ iterations: context.iterations,
476
+ results: context.results,
477
+ last: context.last
478
+ });
479
+ const machineConfig = {
480
+ id: "loop",
481
+ version: "1",
482
+ context: ({ input }) => ({
483
+ prompt: input.prompt,
484
+ iterations: 0,
485
+ results: [],
486
+ last: null
487
+ }),
488
+ initial: "running",
489
+ states: {
490
+ running: { invoke: {
491
+ id: "body",
492
+ src: entrySrc("body", body),
493
+ input: ({ context }) => entryInput("body", body, model, body.prompt ? body.prompt(loopState(context)) : context.prompt),
494
+ onDone: ({ context, output }) => ({
495
+ target: "checking",
496
+ context: {
497
+ iterations: context.iterations + 1,
498
+ results: [...context.results, output],
499
+ last: output
500
+ }
501
+ })
502
+ } },
503
+ checking: {
504
+ type: "choice",
505
+ choice: ({ context }) => context.iterations >= maxIterations || until(loopState(context)) ? { target: "done" } : { target: "running" }
506
+ },
507
+ done: {
508
+ type: "final",
509
+ output: ({ context }) => ({
510
+ iterations: context.iterations,
511
+ results: context.results,
512
+ last: context.last
513
+ })
514
+ }
515
+ }
516
+ };
517
+ return agentSetup.createMachine(machineConfig);
518
+ }
519
+ //#endregion
520
+ //#region src/machines/supervisor.ts
521
+ const contextSchema$1 = objectSchema({
522
+ task: jsonString,
523
+ results: jsonRecord,
524
+ turns: jsonNumber,
525
+ worker: { type: ["string", "null"] }
526
+ }, [
527
+ "task",
528
+ "results",
529
+ "turns"
530
+ ]);
531
+ const inputSchema$1 = objectSchema({ task: jsonString }, ["task"]);
532
+ const outputSchema = objectSchema({
533
+ results: jsonRecord,
534
+ turns: jsonNumber
535
+ }, ["results", "turns"]);
536
+ /** The event a supervising decision chooses to delegate to a worker: `DELEGATE_<name>`. */
537
+ function delegateEventType(worker) {
538
+ return `DELEGATE_${worker}`;
539
+ }
540
+ /** The event a supervising decision chooses to stop. */
541
+ const FINISH_EVENT_TYPE = "FINISH";
542
+ /**
543
+ * A supervisor delegating to typed workers: each turn, one `agent.decide`
544
+ * picks a worker or `FINISH`. Worker results accumulate in context and are fed
545
+ * back into the next decision.
546
+ *
547
+ * Control always returns to the supervisor after a worker finishes — that is
548
+ * what separates this from {@link createHandoffMachine}, where control
549
+ * transfers and does not come back.
550
+ *
551
+ * `maxTurns` bounds the delegations twice over: a spent budget removes every
552
+ * `DELEGATE_*` from the decision's candidate events, and a guard on each
553
+ * delegate transition rejects one anyway. `FINISH` is all that is left.
554
+ *
555
+ * States: `supervising` → one state per worker → `supervising` → … → `done`.
556
+ *
557
+ * ```ts
558
+ * const machine = createSupervisorMachine({
559
+ * model: "quick",
560
+ * workers: {
561
+ * researcher: { description: "Facts and background", instructions: "Research it." },
562
+ * writer: { description: "Prose and summaries", instructions: "Write it up." },
563
+ * },
564
+ * maxTurns: 4,
565
+ * });
566
+ * ```
567
+ */
568
+ function createSupervisorMachine(config) {
569
+ const { model, instructions, workers, maxTurns = 6 } = config;
570
+ const names = Object.keys(workers);
571
+ assertEntryNames("worker", names, ["supervising", "done"]);
572
+ if (!Number.isInteger(maxTurns) || maxTurns < 1) throw new Error("createSupervisorMachine: maxTurns must be an integer >= 1.");
573
+ const agentSetup = setupAgent({
574
+ context: contextSchema$1,
575
+ input: inputSchema$1,
576
+ output: outputSchema,
577
+ events: {
578
+ ...Object.fromEntries(names.map((name) => [delegateEventType(name), emptyPayload])),
579
+ [FINISH_EVENT_TYPE]: emptyPayload
580
+ },
581
+ actors: machineActors(workers)
582
+ });
583
+ const workerList = renderEntryList(workers);
584
+ const states = {
585
+ supervising: {
586
+ invoke: {
587
+ id: "supervise",
588
+ src: DECIDE_SRC,
589
+ input: ({ context }) => ({
590
+ model,
591
+ system: instructions ?? "You are a supervisor. Delegate the task to one worker at a time, then finish once the accumulated results answer it.",
592
+ prompt: `Task:\n${context.task}\n\nWorkers:\n${workerList}\n\nResults so far:\n${renderResults(context.results)}\n\nTurns used: ${context.turns} of ${maxTurns}.`,
593
+ allowedEvents: context.turns < maxTurns ? [...names.map(delegateEventType), FINISH_EVENT_TYPE] : [FINISH_EVENT_TYPE]
594
+ })
595
+ },
596
+ on: {
597
+ ...Object.fromEntries(names.map((name) => [delegateEventType(name), ({ context }) => context.turns < maxTurns ? {
598
+ target: name,
599
+ context: { worker: name }
600
+ } : void 0])),
601
+ [FINISH_EVENT_TYPE]: { target: "done" }
602
+ }
603
+ },
604
+ done: {
605
+ type: "final",
606
+ output: ({ context }) => ({
607
+ results: context.results,
608
+ turns: context.turns
609
+ })
610
+ }
611
+ };
612
+ for (const [name, entry] of Object.entries(workers)) states[name] = { invoke: {
613
+ id: name,
614
+ src: entrySrc(name, entry),
615
+ input: ({ context }) => entryInput(name, entry, model, context.task),
616
+ onDone: ({ context, output }) => ({
617
+ target: "supervising",
618
+ context: {
619
+ results: {
620
+ ...context.results,
621
+ [name]: output
622
+ },
623
+ turns: context.turns + 1
624
+ }
625
+ })
626
+ } };
627
+ const machineConfig = {
628
+ id: "supervisor",
629
+ version: "1",
630
+ context: ({ input }) => ({
631
+ task: input.task,
632
+ results: {},
633
+ turns: 0,
634
+ worker: null
635
+ }),
636
+ initial: "supervising",
637
+ states
638
+ };
639
+ return agentSetup.createMachine(machineConfig);
640
+ }
641
+ function renderResults(results) {
642
+ const entries = Object.entries(results);
643
+ if (entries.length === 0) return "(none yet)";
644
+ return entries.map(([name, value]) => `- ${name}: ${typeof value === "string" ? value : JSON.stringify(value)}`).join("\n");
645
+ }
646
+ //#endregion
647
+ //#region src/machines/handoff.ts
648
+ const contextSchema = objectSchema({
649
+ message: jsonString,
650
+ activeAgent: jsonString,
651
+ reply: jsonAny
652
+ }, ["message", "activeAgent"]);
653
+ const inputSchema = objectSchema({
654
+ message: jsonString,
655
+ activeAgent: jsonString
656
+ }, ["message"]);
657
+ const transferPayload = objectSchema({ message: jsonString });
658
+ /** The event that hands the mic to a peer: `transfer_to_<name>`. */
659
+ function transferEventType(agent) {
660
+ return `transfer_to_${agent}`;
661
+ }
662
+ /**
663
+ * Peer handoff (the swarm shape): `context.activeAgent` holds the mic, runs one
664
+ * turn, then the machine settles idle in `waiting`. A `transfer_to_<name>`
665
+ * event moves the mic to a peer and re-routes.
666
+ *
667
+ * Control TRANSFERS and does not return — the opposite of
668
+ * {@link createSupervisorMachine}, where every worker hands control back.
669
+ *
670
+ * There is no final state: a conversation ends when the host stops resuming it.
671
+ * Persist the idle snapshot between turns; `activeAgent` round-trips with it.
672
+ *
673
+ * States: `routing` → one turn state per agent → `waiting` → `routing` → …
674
+ *
675
+ * ```ts
676
+ * const machine = createHandoffMachine({
677
+ * model: "quick",
678
+ * defaultActiveAgent: "travel",
679
+ * agents: {
680
+ * travel: { description: "Destinations and itineraries", instructions: "You are a travel concierge." },
681
+ * food: { description: "Restaurants and dishes", instructions: "You are a food concierge." },
682
+ * },
683
+ * });
684
+ *
685
+ * const next = await runAgent(machine, {
686
+ * snapshot,
687
+ * event: { type: "transfer_to_food", message: "What should I eat there?" },
688
+ * executors,
689
+ * });
690
+ * ```
691
+ */
692
+ function createHandoffMachine(config) {
693
+ const { agents, defaultActiveAgent, model } = config;
694
+ const names = Object.keys(agents);
695
+ assertEntryNames("agent", names, ["routing", "waiting"]);
696
+ if (!names.includes(defaultActiveAgent)) throw new Error(`createHandoffMachine: defaultActiveAgent '${defaultActiveAgent}' is not a declared agent (${names.join(", ")}).`);
697
+ const agentSetup = setupAgent({
698
+ context: contextSchema,
699
+ input: inputSchema,
700
+ events: Object.fromEntries(names.map((name) => [transferEventType(name), transferPayload])),
701
+ actors: machineActors(agents),
702
+ isSuspended: (snapshot) => snapshot.matches("waiting")
703
+ });
704
+ const turnState = (name) => `${name}Turn`;
705
+ const states = {
706
+ routing: {
707
+ type: "choice",
708
+ choice: ({ context }) => ({ target: names.includes(context.activeAgent) ? turnState(context.activeAgent) : turnState(defaultActiveAgent) })
709
+ },
710
+ waiting: { on: Object.fromEntries(names.map((name) => [transferEventType(name), ({ context, event }) => ({
711
+ target: "routing",
712
+ context: {
713
+ activeAgent: name,
714
+ message: event.message ?? context.message
715
+ }
716
+ })])) }
717
+ };
718
+ for (const [name, entry] of Object.entries(agents)) states[turnState(name)] = { invoke: {
719
+ id: name,
720
+ src: entrySrc(name, entry),
721
+ input: ({ context }) => entryInput(name, entry, model, context.message),
722
+ onDone: ({ output }) => ({
723
+ target: "waiting",
724
+ context: { reply: output }
725
+ })
726
+ } };
727
+ const machineConfig = {
728
+ id: "handoff",
729
+ version: "1",
730
+ context: ({ input }) => ({
731
+ message: input.message,
732
+ activeAgent: input.activeAgent ?? defaultActiveAgent,
733
+ reply: null
734
+ }),
735
+ initial: "routing",
736
+ states
737
+ };
738
+ return agentSetup.createMachine(machineConfig);
739
+ }
740
+ //#endregion
741
+ export { FINISH_EVENT_TYPE, createHandoffMachine, createLoopMachine, createParallelMachine, createRouterMachine, createSequentialMachine, createSupervisorMachine, createToolLoopMachine, delegateEventType, routeEventType, transferEventType };