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