@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
package/dist/index.cjs
CHANGED
|
@@ -1,571 +1,14 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
const
|
|
2
|
+
const require_errors = require("./errors-DUBBzRLP.cjs");
|
|
3
|
+
const require_setup_agent = require("./setup-agent-DP95MFrI.cjs");
|
|
4
|
+
const require_decision = require("./decision-dWGhBh0P.cjs");
|
|
5
|
+
const require_event_log_store = require("./event-log-store-CNT_7F0V.cjs");
|
|
5
6
|
let xstate = require("xstate");
|
|
6
|
-
//#region src/messages.ts
|
|
7
|
-
function addMessages(resolve) {
|
|
8
|
-
return (args) => {
|
|
9
|
-
const resolved = typeof resolve === "function" ? resolve(args) : resolve;
|
|
10
|
-
return [...args.context.messages, ...Array.isArray(resolved) ? resolved : [resolved]];
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Builds a transition-function result that appends one or more
|
|
15
|
-
* {@link AgentMessage}s to a context's `messages` array. `resolve` is either
|
|
16
|
-
* a message (or array of messages) or a function of `{ context, event }`
|
|
17
|
-
* returning them; the returned function is meant to be used directly as (or
|
|
18
|
-
* composed into) a transition's result, e.g. `on: { USER_REPLIED:
|
|
19
|
-
* agent.appendMessages(({ event }) => userMessage(event.text)) }`. Requires
|
|
20
|
-
* `messages: AgentMessage[]` on context — see {@link messagesSchema} for a
|
|
21
|
-
* ready-made schema for that field.
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* ```ts
|
|
25
|
-
* on: {
|
|
26
|
-
* USER_REPLIED: appendMessages(({ event }) => userMessage(event.text)),
|
|
27
|
-
* }
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
function appendMessages(resolve) {
|
|
31
|
-
return (args) => ({ context: { messages: addMessages(resolve)(args) } });
|
|
32
|
-
}
|
|
33
|
-
const KNOWN_PART_TYPES = new Set([
|
|
34
|
-
"text",
|
|
35
|
-
"image",
|
|
36
|
-
"file",
|
|
37
|
-
"tool-call",
|
|
38
|
-
"tool-result"
|
|
39
|
-
]);
|
|
40
|
-
function isKnownPart(part) {
|
|
41
|
-
return !!part && typeof part === "object" && KNOWN_PART_TYPES.has(part.type);
|
|
42
|
-
}
|
|
43
|
-
function validatePartsArray(content) {
|
|
44
|
-
if (!Array.isArray(content)) return "Expected content to be a string or an array of parts";
|
|
45
|
-
for (const part of content) if (!isKnownPart(part)) {
|
|
46
|
-
const type = part && typeof part === "object" ? part.type : void 0;
|
|
47
|
-
return `Unknown message part type: ${JSON.stringify(type)}`;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* A {@link StandardSchemaV1} validating an `AgentMessage[]` context field —
|
|
52
|
-
* checks that every message has a known `role` (`system`/`user`/`assistant`/
|
|
53
|
-
* `tool`) and that `content` is either a string (where the role allows it) or
|
|
54
|
-
* an array of parts with a known `type`. Use it directly as a context
|
|
55
|
-
* schema's `messages` field when authoring with `createAgentSchemas`.
|
|
56
|
-
*/
|
|
57
|
-
const messagesSchema = { "~standard": {
|
|
58
|
-
version: 1,
|
|
59
|
-
vendor: "statelyai-agent",
|
|
60
|
-
validate(value) {
|
|
61
|
-
if (!Array.isArray(value)) return { issues: [{ message: "Expected an array of agent messages" }] };
|
|
62
|
-
for (const message of value) {
|
|
63
|
-
if (!message || typeof message !== "object") return { issues: [{ message: "Expected an array of agent messages" }] };
|
|
64
|
-
const role = message.role;
|
|
65
|
-
const content = message.content;
|
|
66
|
-
if (role !== "system" && role !== "user" && role !== "assistant" && role !== "tool") return { issues: [{ message: `Unknown message role: ${JSON.stringify(role)}` }] };
|
|
67
|
-
if (role === "system") {
|
|
68
|
-
if (typeof content !== "string") return { issues: [{ message: "system message content must be a string" }] };
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
if (role === "tool") {
|
|
72
|
-
const error = validatePartsArray(content) ?? (content.some((part) => part.type !== "tool-result") ? "tool message content must contain only tool-result parts" : void 0);
|
|
73
|
-
if (error) return { issues: [{ message: error }] };
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
if (typeof content === "string") continue;
|
|
77
|
-
const error = validatePartsArray(content);
|
|
78
|
-
if (error) return { issues: [{ message: error }] };
|
|
79
|
-
}
|
|
80
|
-
return { value };
|
|
81
|
-
}
|
|
82
|
-
} };
|
|
83
|
-
//#endregion
|
|
84
|
-
//#region src/workflow-config.ts
|
|
85
|
-
const DECIDE_SRC$1 = "agent.decide";
|
|
86
|
-
const workflowConfigWholeExpressionPattern = /^\{\{\s*([\s\S]*?)\s*\}\}$/;
|
|
87
|
-
const workflowConfigTemplateExpressionPattern = /\{\{\s*([\s\S]*?)\s*\}\}/g;
|
|
88
|
-
function evaluateWorkflowConfigPath(expression, scope) {
|
|
89
|
-
const parts = expression.trim().split(".").filter(Boolean);
|
|
90
|
-
let current = scope;
|
|
91
|
-
for (const part of parts) {
|
|
92
|
-
if (!current || typeof current !== "object") return;
|
|
93
|
-
current = current[part];
|
|
94
|
-
}
|
|
95
|
-
return current;
|
|
96
|
-
}
|
|
97
|
-
function evaluateWorkflowConfigValue(value, scope) {
|
|
98
|
-
if (typeof value === "string") {
|
|
99
|
-
const wholeMatch = value.match(workflowConfigWholeExpressionPattern);
|
|
100
|
-
if (wholeMatch?.[1]) return evaluateWorkflowConfigPath(wholeMatch[1], scope);
|
|
101
|
-
return value.replace(workflowConfigTemplateExpressionPattern, (_match, expression) => {
|
|
102
|
-
const resolved = evaluateWorkflowConfigPath(expression, scope);
|
|
103
|
-
return resolved === void 0 || resolved === null ? "" : String(resolved);
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
if (Array.isArray(value)) return value.map((item) => evaluateWorkflowConfigValue(item, scope));
|
|
107
|
-
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, evaluateWorkflowConfigValue(item, scope)]));
|
|
108
|
-
return value;
|
|
109
|
-
}
|
|
110
|
-
function createSchemasFromWorkflowConfig(config, compileSchema) {
|
|
111
|
-
return createAgentSchemas({
|
|
112
|
-
context: compileSchema(config.schemas?.context ?? { type: "object" }, "context"),
|
|
113
|
-
events: Object.fromEntries(Object.entries(config.schemas?.events ?? {}).map(([key, schema]) => [key, compileSchema(schema, `event.${key}`)])),
|
|
114
|
-
emitted: Object.fromEntries(Object.entries(config.schemas?.emitted ?? {}).map(([key, schema]) => [key, compileSchema(schema, `emitted.${key}`)])),
|
|
115
|
-
input: compileSchema(config.schemas?.input ?? {}, "input"),
|
|
116
|
-
output: compileSchema(config.schemas?.output ?? {}, "output"),
|
|
117
|
-
meta: compileSchema(config.schemas?.meta ?? {}, "meta")
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
function createRequestsFromWorkflowConfig(config, compileSchema) {
|
|
121
|
-
return Object.fromEntries(Object.entries(config.requests ?? {}).map(([key, request]) => [key, {
|
|
122
|
-
mode: request.mode,
|
|
123
|
-
description: request.description,
|
|
124
|
-
schemas: {
|
|
125
|
-
input: compileSchema(request.input, `${key}.input`),
|
|
126
|
-
output: compileSchema(request.output, `${key}.output`)
|
|
127
|
-
},
|
|
128
|
-
model: ({ input }) => String(evaluateWorkflowConfigValue(request.model, { input }) ?? ""),
|
|
129
|
-
system: request.system === void 0 ? void 0 : ({ input }) => evaluateWorkflowConfigValue(request.system, { input }),
|
|
130
|
-
prompt: request.prompt === void 0 ? void 0 : ({ input }) => evaluateWorkflowConfigValue(request.prompt, { input }),
|
|
131
|
-
messages: request.messages === void 0 ? void 0 : ({ input }) => evaluateWorkflowConfigValue(request.messages, { input }),
|
|
132
|
-
tools: request.tools,
|
|
133
|
-
toolChoice: request.toolChoice,
|
|
134
|
-
reasoning: request.reasoning,
|
|
135
|
-
temperature: request.temperature === void 0 ? void 0 : ({ input }) => evaluateWorkflowConfigValue(request.temperature, { input }),
|
|
136
|
-
maxOutputTokens: request.maxOutputTokens === void 0 ? void 0 : ({ input }) => evaluateWorkflowConfigValue(request.maxOutputTokens, { input }),
|
|
137
|
-
topP: request.topP === void 0 ? void 0 : ({ input }) => evaluateWorkflowConfigValue(request.topP, { input }),
|
|
138
|
-
topK: request.topK === void 0 ? void 0 : ({ input }) => evaluateWorkflowConfigValue(request.topK, { input }),
|
|
139
|
-
seed: request.seed === void 0 ? void 0 : ({ input }) => evaluateWorkflowConfigValue(request.seed, { input }),
|
|
140
|
-
stopSequences: request.stopSequences === void 0 ? void 0 : ({ input }) => evaluateWorkflowConfigValue(request.stopSequences, { input }),
|
|
141
|
-
metadata: request.metadata === void 0 ? void 0 : ({ input }) => evaluateWorkflowConfigValue(request.metadata, { input })
|
|
142
|
-
}]));
|
|
143
|
-
}
|
|
144
|
-
function createActorPlaceholdersFromWorkflowConfig(config) {
|
|
145
|
-
return Object.fromEntries(Object.keys(config.actors ?? {}).map((key) => [key, require_events.missingActor(key)]));
|
|
146
|
-
}
|
|
147
|
-
function createAssignAction(assignConfig) {
|
|
148
|
-
return ({ context, event }) => ({ context: Object.fromEntries(Object.entries(assignConfig).map(([key, value]) => [key, evaluateWorkflowConfigValue(value, {
|
|
149
|
-
context,
|
|
150
|
-
event
|
|
151
|
-
})])) });
|
|
152
|
-
}
|
|
153
|
-
function lowerWorkflowActions(actionConfig) {
|
|
154
|
-
if (!actionConfig) return;
|
|
155
|
-
const lowered = (Array.isArray(actionConfig) ? actionConfig : [actionConfig]).map((action) => {
|
|
156
|
-
if (action.assign !== void 0) return createAssignAction(action.assign);
|
|
157
|
-
if (action.emit !== void 0) return ({ context, event }, enq) => {
|
|
158
|
-
enq.emit(evaluateWorkflowConfigValue(action.emit, {
|
|
159
|
-
context,
|
|
160
|
-
event
|
|
161
|
-
}));
|
|
162
|
-
};
|
|
163
|
-
if (!action.type) throw new Error("setupAgent.fromConfig: action must declare 'type', 'assign', or 'emit'.");
|
|
164
|
-
return {
|
|
165
|
-
type: action.type,
|
|
166
|
-
params: ({ context, event }) => evaluateWorkflowConfigValue(action.params, {
|
|
167
|
-
context,
|
|
168
|
-
event
|
|
169
|
-
})
|
|
170
|
-
};
|
|
171
|
-
});
|
|
172
|
-
return Array.isArray(actionConfig) ? lowered : lowered[0];
|
|
173
|
-
}
|
|
174
|
-
function workflowTransitionMatches(transitionConfig, scope) {
|
|
175
|
-
if (transitionConfig.guard === void 0) return true;
|
|
176
|
-
if (typeof transitionConfig.guard === "string") return Boolean(evaluateWorkflowConfigValue(transitionConfig.guard, scope));
|
|
177
|
-
return typeof transitionConfig.guard === "function" ? transitionConfig.guard(scope) : false;
|
|
178
|
-
}
|
|
179
|
-
function applyWorkflowTransitionActions(actionConfig, scope, enq) {
|
|
180
|
-
if (!actionConfig) return;
|
|
181
|
-
const actions = Array.isArray(actionConfig) ? actionConfig : [actionConfig];
|
|
182
|
-
let assigned;
|
|
183
|
-
for (const action of actions) if (action.assign !== void 0) assigned = {
|
|
184
|
-
...assigned ?? {},
|
|
185
|
-
...Object.fromEntries(Object.entries(action.assign).map(([key, value]) => [key, evaluateWorkflowConfigValue(value, scope)]))
|
|
186
|
-
};
|
|
187
|
-
else if (action.emit !== void 0) enq.emit(evaluateWorkflowConfigValue(action.emit, scope));
|
|
188
|
-
else if (action.type) throw new Error(`setupAgent.fromConfig: named action type '${action.type}' is not supported in a transition 'actions' entry (config lowering wires no action implementations). Use 'emit' or 'assign', or move it to a state 'entry'/'exit'.`);
|
|
189
|
-
else throw new Error("setupAgent.fromConfig: a transition action must declare 'emit', 'assign', or 'type'.");
|
|
190
|
-
return assigned;
|
|
191
|
-
}
|
|
192
|
-
function lowerWorkflowTransitionResult(transitionConfig, scope, enq) {
|
|
193
|
-
const actionAssign = applyWorkflowTransitionActions(transitionConfig.actions, scope, enq);
|
|
194
|
-
const configAssign = transitionConfig.assign ? Object.fromEntries(Object.entries(transitionConfig.assign).map(([key, value]) => [key, evaluateWorkflowConfigValue(value, scope)])) : void 0;
|
|
195
|
-
const context = configAssign || actionAssign ? {
|
|
196
|
-
...configAssign ?? {},
|
|
197
|
-
...actionAssign ?? {}
|
|
198
|
-
} : void 0;
|
|
199
|
-
return {
|
|
200
|
-
...transitionConfig.target !== void 0 ? { target: transitionConfig.target } : {},
|
|
201
|
-
...context ? { context } : {},
|
|
202
|
-
...transitionConfig.description !== void 0 ? { description: transitionConfig.description } : {},
|
|
203
|
-
...transitionConfig.reenter !== void 0 ? { reenter: transitionConfig.reenter } : {},
|
|
204
|
-
...transitionConfig.meta !== void 0 ? { meta: transitionConfig.meta } : {}
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
function lowerWorkflowTransition(transitionConfig) {
|
|
208
|
-
return ({ context, event }, enq) => {
|
|
209
|
-
const scope = {
|
|
210
|
-
context,
|
|
211
|
-
event
|
|
212
|
-
};
|
|
213
|
-
if (!workflowTransitionMatches(transitionConfig, scope)) return;
|
|
214
|
-
return lowerWorkflowTransitionResult(transitionConfig, scope, enq);
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
function lowerWorkflowTransitionOrArray(transitionConfig) {
|
|
218
|
-
if (!transitionConfig) return;
|
|
219
|
-
return Array.isArray(transitionConfig) ? ({ context, event }, enq) => {
|
|
220
|
-
const scope = {
|
|
221
|
-
context,
|
|
222
|
-
event
|
|
223
|
-
};
|
|
224
|
-
const transition = transitionConfig.find((candidate) => workflowTransitionMatches(candidate, scope));
|
|
225
|
-
if (!transition) return;
|
|
226
|
-
return lowerWorkflowTransitionResult(transition, scope, enq);
|
|
227
|
-
} : lowerWorkflowTransition(transitionConfig);
|
|
228
|
-
}
|
|
229
|
-
function lowerWorkflowInvoke(invokeConfig, stateKey) {
|
|
230
|
-
if (invokeConfig.src === DECIDE_SRC$1 && invokeConfig.onDone !== void 0) throw new Error(`setupAgent.fromConfig: state '${stateKey}' declares an 'onDone' on its 'agent.decide' invoke, which is always a config error. A decision has no output of its own — the chosen event is delivered automatically and handled by the state's 'on' transitions. Remove the 'onDone' (use 'on' for the chosen event; 'onError' still handles retries exhausted).`);
|
|
231
|
-
return {
|
|
232
|
-
...invokeConfig.id !== void 0 ? { id: invokeConfig.id } : {},
|
|
233
|
-
src: invokeConfig.src,
|
|
234
|
-
...invokeConfig.input !== void 0 ? { input: ({ context, event }) => evaluateWorkflowConfigValue(invokeConfig.input, {
|
|
235
|
-
context,
|
|
236
|
-
event
|
|
237
|
-
}) } : {},
|
|
238
|
-
...invokeConfig.onDone !== void 0 ? { onDone: lowerWorkflowTransitionOrArray(invokeConfig.onDone) } : {},
|
|
239
|
-
...invokeConfig.onError !== void 0 ? { onError: lowerWorkflowTransitionOrArray(invokeConfig.onError) } : {},
|
|
240
|
-
...invokeConfig.meta !== void 0 ? { meta: invokeConfig.meta } : {}
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
function assertValidTransitionTargets(stateKey, location, transitionConfig, siblingKeys) {
|
|
244
|
-
if (!transitionConfig) return;
|
|
245
|
-
const transitions = Array.isArray(transitionConfig) ? transitionConfig : [transitionConfig];
|
|
246
|
-
for (const transition of transitions) {
|
|
247
|
-
const targets = transition.target === void 0 ? [] : Array.isArray(transition.target) ? transition.target : [transition.target];
|
|
248
|
-
for (const target of targets) {
|
|
249
|
-
if (typeof target !== "string" || target.startsWith("#") || target.startsWith(".")) continue;
|
|
250
|
-
const head = target.split(".")[0];
|
|
251
|
-
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)"}.`);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
function assertStateTransitionTargets(stateKey, stateConfig, siblingKeys) {
|
|
256
|
-
for (const [eventType, transition] of Object.entries(stateConfig.on ?? {})) assertValidTransitionTargets(stateKey, `'on.${eventType}'`, transition, siblingKeys);
|
|
257
|
-
for (const [delay, transition] of Object.entries(stateConfig.after ?? {})) assertValidTransitionTargets(stateKey, `'after.${delay}'`, transition, siblingKeys);
|
|
258
|
-
assertValidTransitionTargets(stateKey, "'always'", stateConfig.always, siblingKeys);
|
|
259
|
-
assertValidTransitionTargets(stateKey, "'onDone'", stateConfig.onDone, siblingKeys);
|
|
260
|
-
assertValidTransitionTargets(stateKey, "'choice'", stateConfig.choice, siblingKeys);
|
|
261
|
-
const invokes = stateConfig.invoke ? Array.isArray(stateConfig.invoke) ? stateConfig.invoke : [stateConfig.invoke] : [];
|
|
262
|
-
for (const invoke of invokes) {
|
|
263
|
-
assertValidTransitionTargets(stateKey, "invoke 'onDone'", invoke.onDone, siblingKeys);
|
|
264
|
-
assertValidTransitionTargets(stateKey, "invoke 'onError'", invoke.onError, siblingKeys);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
function lowerWorkflowState(stateKey, stateConfig, siblingKeys) {
|
|
268
|
-
assertStateTransitionTargets(stateKey, stateConfig, siblingKeys);
|
|
269
|
-
const childKeys = new Set(Object.keys(stateConfig.states ?? {}));
|
|
270
|
-
return {
|
|
271
|
-
...stateConfig.description !== void 0 ? { description: stateConfig.description } : {},
|
|
272
|
-
...stateConfig.type !== void 0 ? { type: stateConfig.type } : {},
|
|
273
|
-
...stateConfig.initial !== void 0 ? { initial: stateConfig.initial } : {},
|
|
274
|
-
...stateConfig.states !== void 0 ? { states: Object.fromEntries(Object.entries(stateConfig.states).map(([key, child]) => [key, lowerWorkflowState(key, child, childKeys)])) } : {},
|
|
275
|
-
...stateConfig.choice !== void 0 ? { choice: lowerWorkflowTransitionOrArray(stateConfig.choice) } : {},
|
|
276
|
-
...stateConfig.invoke !== void 0 ? { invoke: Array.isArray(stateConfig.invoke) ? stateConfig.invoke.map((invoke) => lowerWorkflowInvoke(invoke, stateKey)) : lowerWorkflowInvoke(stateConfig.invoke, stateKey) } : {},
|
|
277
|
-
...stateConfig.on !== void 0 ? { on: Object.fromEntries(Object.entries(stateConfig.on).map(([eventType, transitionConfig]) => [eventType, lowerWorkflowTransitionOrArray(transitionConfig)])) } : {},
|
|
278
|
-
...stateConfig.always !== void 0 ? { always: lowerWorkflowTransitionOrArray(stateConfig.always) } : {},
|
|
279
|
-
...stateConfig.onDone !== void 0 ? { onDone: lowerWorkflowTransitionOrArray(stateConfig.onDone) } : {},
|
|
280
|
-
...stateConfig.after !== void 0 ? { after: Object.fromEntries(Object.entries(stateConfig.after).map(([delay, transitionConfig]) => [delay, lowerWorkflowTransitionOrArray(transitionConfig)])) } : {},
|
|
281
|
-
...stateConfig.entry !== void 0 ? { entry: lowerWorkflowActions(stateConfig.entry) } : {},
|
|
282
|
-
...stateConfig.exit !== void 0 ? { exit: lowerWorkflowActions(stateConfig.exit) } : {},
|
|
283
|
-
...stateConfig.tags !== void 0 ? { tags: stateConfig.tags } : {},
|
|
284
|
-
...stateConfig.output !== void 0 ? { output: ({ context, event }) => evaluateWorkflowConfigValue(stateConfig.output, {
|
|
285
|
-
context,
|
|
286
|
-
event
|
|
287
|
-
}) } : {},
|
|
288
|
-
...stateConfig.meta !== void 0 ? { meta: stateConfig.meta } : {}
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
function setupAgentFromConfig(config, options) {
|
|
292
|
-
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.");
|
|
293
|
-
const { compileSchema } = options;
|
|
294
|
-
const schemas = createSchemasFromWorkflowConfig(config, compileSchema);
|
|
295
|
-
const requestActors = createRequestActors(createRequestsFromWorkflowConfig(config, compileSchema));
|
|
296
|
-
return setupAgent({
|
|
297
|
-
schemas,
|
|
298
|
-
actorSources: {
|
|
299
|
-
...createActorPlaceholdersFromWorkflowConfig(config),
|
|
300
|
-
...requestActors
|
|
301
|
-
}
|
|
302
|
-
}).createMachine({
|
|
303
|
-
...config.id !== void 0 ? { id: config.id } : {},
|
|
304
|
-
...config.description !== void 0 ? { description: config.description } : {},
|
|
305
|
-
...config.context !== void 0 ? { context: ({ input }) => require_events.validateSchemaSync(schemas.context, evaluateWorkflowConfigValue(config.context, { input })) } : {},
|
|
306
|
-
initial: config.initial,
|
|
307
|
-
states: (() => {
|
|
308
|
-
const rootKeys = new Set(Object.keys(config.states));
|
|
309
|
-
return Object.fromEntries(Object.entries(config.states).map(([key, state]) => [key, lowerWorkflowState(key, state, rootKeys)]));
|
|
310
|
-
})(),
|
|
311
|
-
...config.meta !== void 0 ? { meta: config.meta } : {}
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
//#endregion
|
|
315
|
-
//#region src/setup-agent.ts
|
|
316
|
-
const emptyEventSchema = { "~standard": {
|
|
317
|
-
version: 1,
|
|
318
|
-
vendor: "statelyai-agent",
|
|
319
|
-
validate(value) {
|
|
320
|
-
return value !== null && typeof value === "object" && Object.keys(value).length === 0 ? { value: {} } : { issues: [{ message: "Expected an empty event payload" }] };
|
|
321
|
-
}
|
|
322
|
-
} };
|
|
323
|
-
function normalizeEventSchemas(events) {
|
|
324
|
-
return Object.fromEntries(Object.entries(events).map(([type, schema]) => [type, schema && typeof schema === "object" && "~standard" in schema ? schema : emptyEventSchema]));
|
|
325
|
-
}
|
|
326
|
-
/**
|
|
327
|
-
* Builds a machine's {@link AgentSchemaPack} from a partial schema
|
|
328
|
-
* declaration — only `context` is required; `events`/`input`/`output`/`meta`
|
|
329
|
-
* default to empty/unknown schemas when omitted. Pass the result as
|
|
330
|
-
* `setupAgent({ schemas })`'s `schemas` (or spread the individual fields
|
|
331
|
-
* directly into `setupAgent({ context, events, ... })` — both forms are
|
|
332
|
-
* accepted).
|
|
333
|
-
*/
|
|
334
|
-
function createAgentSchemas(schemas) {
|
|
335
|
-
return {
|
|
336
|
-
context: schemas.context,
|
|
337
|
-
events: normalizeEventSchemas(schemas.events ?? {}),
|
|
338
|
-
input: schemas.input,
|
|
339
|
-
output: schemas.output,
|
|
340
|
-
meta: schemas.meta,
|
|
341
|
-
emitted: schemas.emitted
|
|
342
|
-
};
|
|
343
|
-
}
|
|
344
|
-
function mergeContextSchema(base, fields) {
|
|
345
|
-
return { "~standard": {
|
|
346
|
-
version: 1,
|
|
347
|
-
vendor: "statelyai-agent",
|
|
348
|
-
validate(value) {
|
|
349
|
-
const baseResult = base["~standard"].validate(value);
|
|
350
|
-
if (baseResult instanceof Promise) throw new Error("setupAgent: async context schemas are not supported.");
|
|
351
|
-
if (baseResult.issues) return baseResult;
|
|
352
|
-
const merged = { ...baseResult.value };
|
|
353
|
-
const issues = [];
|
|
354
|
-
for (const [key, fieldSchema] of Object.entries(fields)) {
|
|
355
|
-
const fieldResult = fieldSchema["~standard"].validate(value[key]);
|
|
356
|
-
if (fieldResult instanceof Promise) throw new Error("setupAgent: async context schemas are not supported.");
|
|
357
|
-
if (fieldResult.issues) issues.push(...fieldResult.issues.map((issue) => ({
|
|
358
|
-
...issue,
|
|
359
|
-
path: [key, ...issue.path ?? []]
|
|
360
|
-
})));
|
|
361
|
-
else merged[key] = fieldResult.value;
|
|
362
|
-
}
|
|
363
|
-
return issues.length > 0 ? { issues } : { value: merged };
|
|
364
|
-
}
|
|
365
|
-
} };
|
|
366
|
-
}
|
|
367
|
-
function resolveAgentStateSchemas(contextSchema, states) {
|
|
368
|
-
return Object.fromEntries(Object.entries(states).map(([key, state]) => {
|
|
369
|
-
if (!state || typeof state !== "object") return [key, state];
|
|
370
|
-
const children = "states" in state && state.states ? resolveAgentStateSchemas(contextSchema, state.states) : void 0;
|
|
371
|
-
if ("context" in state && state.context) return [key, {
|
|
372
|
-
schemas: { context: mergeContextSchema(contextSchema, state.context) },
|
|
373
|
-
...children ? { states: children } : {}
|
|
374
|
-
}];
|
|
375
|
-
return [key, children ? {
|
|
376
|
-
...state,
|
|
377
|
-
states: children
|
|
378
|
-
} : state];
|
|
379
|
-
}));
|
|
380
|
-
}
|
|
381
|
-
/**
|
|
382
|
-
* Schema-first `setup(...)` for agent machines — the standard entry point
|
|
383
|
-
* for authoring a machine (the blueprint) that this library then runs (via
|
|
384
|
-
* {@link runAgent} or the step helpers) against host-supplied model/decision
|
|
385
|
-
* executors. Context, events, machine input, machine output, and
|
|
386
|
-
* state/transition meta are all standard schemas — no `{} as Type` casts —
|
|
387
|
-
* and are retained on `result.schemas` for runtime validation. Also
|
|
388
|
-
* registers the `agent.generateText`/`agent.streamText`/`agent.userInput`/
|
|
389
|
-
* `agent.decide`/`agent.plan` builtin actors and lowers `requests`/`actorSources` into the
|
|
390
|
-
* machine's actor sources. The result is the xstate `setup(...)` object with
|
|
391
|
-
* a wrapped `result.createMachine(...)` plus `result.schemas`/`models`/
|
|
392
|
-
* `requests`/`appendMessages` attached. Also has a
|
|
393
|
-
* `setupAgent.fromConfig(...)` namespace member for building a machine from
|
|
394
|
-
* a serializable {@link AgentWorkflowConfig} instead of this TS API.
|
|
395
|
-
*
|
|
396
|
-
* @example
|
|
397
|
-
* ```ts
|
|
398
|
-
* const schemas = createAgentSchemas({
|
|
399
|
-
* context: z.object({ topic: z.string(), joke: z.string().nullable() }),
|
|
400
|
-
* input: z.object({ topic: z.string() }),
|
|
401
|
-
* output: z.object({ joke: z.string() }),
|
|
402
|
-
* });
|
|
403
|
-
*
|
|
404
|
-
* const agent = setupAgent({
|
|
405
|
-
* schemas,
|
|
406
|
-
* actorSources: { tellJoke },
|
|
407
|
-
* });
|
|
408
|
-
*
|
|
409
|
-
* const jokeMachine = agent.createMachine({
|
|
410
|
-
* context: ({ input }) => ({ topic: input.topic, joke: null }),
|
|
411
|
-
* initial: 'telling',
|
|
412
|
-
* states: {
|
|
413
|
-
* telling: {
|
|
414
|
-
* invoke: {
|
|
415
|
-
* id: 'joke',
|
|
416
|
-
* src: 'tellJoke',
|
|
417
|
-
* input: ({ context }) => ({ topic: context.topic }),
|
|
418
|
-
* onDone: ({ output }) => ({ target: 'done', context: { joke: output } }),
|
|
419
|
-
* },
|
|
420
|
-
* },
|
|
421
|
-
* done: { type: 'final', output: ({ context }) => ({ joke: context.joke ?? '' }) },
|
|
422
|
-
* },
|
|
423
|
-
* });
|
|
424
|
-
* ```
|
|
425
|
-
*/
|
|
426
|
-
function setupAgent(config) {
|
|
427
|
-
return createSetupAgent(config);
|
|
428
|
-
}
|
|
429
|
-
function collectFinalStateOutputs(states, outputs = []) {
|
|
430
|
-
for (const state of Object.values(states ?? {})) {
|
|
431
|
-
if (state?.type === "final" && state.output !== void 0) outputs.push(state.output);
|
|
432
|
-
collectFinalStateOutputs(state?.states, outputs);
|
|
433
|
-
}
|
|
434
|
-
return outputs;
|
|
435
|
-
}
|
|
436
|
-
function withRootOutputFromSingleFinal(config) {
|
|
437
|
-
if (!config || typeof config !== "object" || "output" in config || !("states" in config)) return config;
|
|
438
|
-
const outputs = collectFinalStateOutputs(config.states);
|
|
439
|
-
return outputs.length === 1 ? {
|
|
440
|
-
...config,
|
|
441
|
-
output: outputs[0]
|
|
442
|
-
} : config;
|
|
443
|
-
}
|
|
444
|
-
(function(_setupAgent) {
|
|
445
|
-
function fromConfig(config, options) {
|
|
446
|
-
return setupAgentFromConfig(config, options);
|
|
447
|
-
}
|
|
448
|
-
_setupAgent.fromConfig = fromConfig;
|
|
449
|
-
})(setupAgent || (setupAgent = {}));
|
|
450
|
-
/** Builds one TextLogic actor per `setupAgent({ requests })` entry. @internal */
|
|
451
|
-
function createRequestActors(requests) {
|
|
452
|
-
return Object.fromEntries(Object.entries(requests).map(([key, request]) => {
|
|
453
|
-
return [key, require_events.createTextLogic({
|
|
454
|
-
...request,
|
|
455
|
-
name: key,
|
|
456
|
-
mode: request.mode ?? "generate"
|
|
457
|
-
})];
|
|
458
|
-
}));
|
|
459
|
-
}
|
|
460
|
-
function normalizeAgentSchemas(config) {
|
|
461
|
-
if ("schemas" in config && config.schemas) return config.schemas;
|
|
462
|
-
const loose = config;
|
|
463
|
-
return createAgentSchemas({
|
|
464
|
-
...loose,
|
|
465
|
-
context: loose.context
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
|
-
function normalizeAgentRequestInput(requests) {
|
|
469
|
-
return requests ?? {};
|
|
470
|
-
}
|
|
471
|
-
/**
|
|
472
|
-
* Runtime guard: a key appearing in both `actorSources`/`requests` is almost
|
|
473
|
-
* certainly a mistake (whichever spread applies last would silently win) —
|
|
474
|
-
* fail fast with a clear message rather than let one implementation shadow
|
|
475
|
-
* another.
|
|
476
|
-
*/
|
|
477
|
-
function assertNoActorKeyCollisions(actorSources, requests) {
|
|
478
|
-
const seenIn = /* @__PURE__ */ new Map();
|
|
479
|
-
const groups = [["actorSources", actorSources], ["requests", requests]];
|
|
480
|
-
for (const [groupName, group] of groups) for (const key of Object.keys(group ?? {})) {
|
|
481
|
-
const existingGroup = seenIn.get(key);
|
|
482
|
-
if (existingGroup) throw new Error(`setupAgent: key '${key}' is defined in both '${existingGroup}' and '${groupName}'. Each actor source key must be unique across 'actorSources' and 'requests'.`);
|
|
483
|
-
seenIn.set(key, groupName);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
const RESERVED_AGENT_ACTOR_KEYS = [
|
|
487
|
-
...Object.keys(require_events.builtinTextActors),
|
|
488
|
-
require_events.USER_INPUT_ACTOR,
|
|
489
|
-
require_events.DECIDE_ACTOR,
|
|
490
|
-
require_events.PLAN_ACTOR
|
|
491
|
-
];
|
|
492
|
-
/**
|
|
493
|
-
* Rejects a user-supplied `actorSources`/`requests` key in the reserved
|
|
494
|
-
* `agent.*` builtin namespace. Without this, the builtins-first spread in
|
|
495
|
-
* {@link createAgentActorSources} lets such a key overwrite the builtin
|
|
496
|
-
* (`agent.decide`, `agent.plan`, …) silently. Deliberate override of a builtin
|
|
497
|
-
* is still possible after the machine is created, via
|
|
498
|
-
* `machine.provide({ actorSources: { 'agent.decide': ... } })`.
|
|
499
|
-
*/
|
|
500
|
-
function assertNoReservedAgentKeys(actorSources, requests) {
|
|
501
|
-
const groups = [["actorSources", actorSources], ["requests", requests]];
|
|
502
|
-
for (const [groupName, group] of groups) for (const key of Object.keys(group ?? {})) if (RESERVED_AGENT_ACTOR_KEYS.includes(key)) throw new Error(`setupAgent: '${groupName}' key '${key}' is a reserved builtin agent actor and cannot be redefined here (it would silently clobber the builtin). Reserved keys: ${RESERVED_AGENT_ACTOR_KEYS.join(", ")}. To deliberately override a builtin, do it on the created machine instead: machine.provide({ actorSources: { '${key}': ... } }).`);
|
|
503
|
-
}
|
|
504
|
-
function createAgentActorSources(actorSources, requestActors) {
|
|
505
|
-
assertNoActorKeyCollisions(actorSources, requestActors);
|
|
506
|
-
assertNoReservedAgentKeys(actorSources, requestActors);
|
|
507
|
-
return {
|
|
508
|
-
...require_events.builtinTextActors,
|
|
509
|
-
[require_events.USER_INPUT_ACTOR]: require_events.userInputActor,
|
|
510
|
-
[require_events.DECIDE_ACTOR]: require_decision.createDecideActor(),
|
|
511
|
-
[require_events.PLAN_ACTOR]: require_decision.createPlanActor(),
|
|
512
|
-
...actorSources,
|
|
513
|
-
...requestActors
|
|
514
|
-
};
|
|
515
|
-
}
|
|
516
|
-
function createAgentSetupConfig(schemas, actorSources, config) {
|
|
517
|
-
return {
|
|
518
|
-
schemas: {
|
|
519
|
-
context: schemas.context,
|
|
520
|
-
events: schemas.events,
|
|
521
|
-
input: schemas.input,
|
|
522
|
-
output: schemas.output,
|
|
523
|
-
meta: schemas.meta,
|
|
524
|
-
...schemas.emitted && Object.keys(schemas.emitted).length > 0 ? { emitted: schemas.emitted } : {}
|
|
525
|
-
},
|
|
526
|
-
...config.states ? { states: resolveAgentStateSchemas(schemas.context, config.states) } : {},
|
|
527
|
-
actorSources,
|
|
528
|
-
actions: config.actions,
|
|
529
|
-
guards: config.guards,
|
|
530
|
-
delays: config.delays
|
|
531
|
-
};
|
|
532
|
-
}
|
|
533
|
-
function createSetupAgent(config) {
|
|
534
|
-
const schemas = normalizeAgentSchemas(config);
|
|
535
|
-
const requestActors = createRequestActors(normalizeAgentRequestInput(config.requests));
|
|
536
|
-
const actorSources = createAgentActorSources(config.actorSources, requestActors);
|
|
537
|
-
const base = (0, xstate.setup)(createAgentSetupConfig(schemas, actorSources, config));
|
|
538
|
-
const createBaseMachine = base.createMachine.bind(base);
|
|
539
|
-
const models = config.models ?? {};
|
|
540
|
-
const machineOptions = {
|
|
541
|
-
schemas,
|
|
542
|
-
actorSources,
|
|
543
|
-
models
|
|
544
|
-
};
|
|
545
|
-
return Object.assign(base, {
|
|
546
|
-
createMachine(machineConfig) {
|
|
547
|
-
const machine = createBaseMachine(withRootOutputFromSingleFinal(machineConfig));
|
|
548
|
-
require_events.agentExecutionOptions.set(machine, machineOptions);
|
|
549
|
-
if (config.isSuspended) {
|
|
550
|
-
const rootConfig = machine.config;
|
|
551
|
-
if (rootConfig) require_events.machineSuspensionPredicates.set(rootConfig, config.isSuspended);
|
|
552
|
-
}
|
|
553
|
-
return machine;
|
|
554
|
-
},
|
|
555
|
-
schemas,
|
|
556
|
-
models,
|
|
557
|
-
requests: requestActors,
|
|
558
|
-
appendMessages(resolve) {
|
|
559
|
-
return appendMessages(resolve);
|
|
560
|
-
}
|
|
561
|
-
});
|
|
562
|
-
}
|
|
563
|
-
//#endregion
|
|
564
7
|
//#region src/internal/state-request-pass.ts
|
|
565
8
|
async function runTextPhase(stateRequest, baseMessages, deps) {
|
|
566
9
|
const { model, system } = stateRequest;
|
|
567
10
|
const id = stateRequest.id ?? deps.nextRequestId();
|
|
568
|
-
const promptMessage =
|
|
11
|
+
const promptMessage = require_decision.userMessage(stateRequest.prompt);
|
|
569
12
|
if (stateRequest.kind === "decision") return {
|
|
570
13
|
stateRequest,
|
|
571
14
|
id,
|
|
@@ -582,7 +25,7 @@ async function runTextPhase(stateRequest, baseMessages, deps) {
|
|
|
582
25
|
const agentRequest = {
|
|
583
26
|
kind: "text",
|
|
584
27
|
id,
|
|
585
|
-
src:
|
|
28
|
+
src: require_decision.INTERPRET_SOURCE,
|
|
586
29
|
mode: "generate",
|
|
587
30
|
input: request,
|
|
588
31
|
tools: {},
|
|
@@ -596,9 +39,11 @@ async function runTextPhase(stateRequest, baseMessages, deps) {
|
|
|
596
39
|
let output;
|
|
597
40
|
try {
|
|
598
41
|
const raw = await deps.generateText(request, { signal: deps.signal });
|
|
599
|
-
output = await
|
|
42
|
+
output = await require_decision.normalizeGeneratorResult(raw, id, { request });
|
|
600
43
|
const rawReasoning = raw?.reasoning;
|
|
601
44
|
const reasoning = typeof rawReasoning === "string" ? rawReasoning : void 0;
|
|
45
|
+
const usage = require_decision.extractCallUsage(raw);
|
|
46
|
+
if (usage) deps.recordUsage?.(usage);
|
|
602
47
|
deps.onResult?.(agentRequest, {
|
|
603
48
|
output,
|
|
604
49
|
raw
|
|
@@ -608,7 +53,8 @@ async function runTextPhase(stateRequest, baseMessages, deps) {
|
|
|
608
53
|
request: agentRequest,
|
|
609
54
|
output,
|
|
610
55
|
raw,
|
|
611
|
-
...reasoning !== void 0 ? { reasoning } : {}
|
|
56
|
+
...reasoning !== void 0 ? { reasoning } : {},
|
|
57
|
+
...usage !== void 0 ? { usage } : {}
|
|
612
58
|
});
|
|
613
59
|
} catch (error) {
|
|
614
60
|
deps.onTrace?.({
|
|
@@ -622,7 +68,7 @@ async function runTextPhase(stateRequest, baseMessages, deps) {
|
|
|
622
68
|
stateRequest,
|
|
623
69
|
id,
|
|
624
70
|
output,
|
|
625
|
-
appended: [promptMessage,
|
|
71
|
+
appended: [promptMessage, require_decision.assistantMessage(typeof output === "string" ? output : JSON.stringify(output))]
|
|
626
72
|
};
|
|
627
73
|
}
|
|
628
74
|
async function runAdvancePhase(plan, deps) {
|
|
@@ -637,13 +83,13 @@ async function runAdvancePhase(plan, deps) {
|
|
|
637
83
|
messages: deps.messages
|
|
638
84
|
}) : stateRequest.onDone;
|
|
639
85
|
if (!resolved) return false;
|
|
640
|
-
const acceptedTypes =
|
|
86
|
+
const acceptedTypes = require_decision.getAcceptedEvents(snapshot, { schemas: deps.schemas }).map((descriptor) => descriptor.type);
|
|
641
87
|
if (!acceptedTypes.includes(resolved.type)) throw new Error(`runAgent: getRequests request '${id}' resolved onDone to event '${resolved.type}', which the current state does not accept. Accepted: ${acceptedTypes.join(", ") || "(none)"}.`);
|
|
642
88
|
if (!snapshot.can(resolved)) return false;
|
|
643
89
|
deps.send(resolved);
|
|
644
90
|
return true;
|
|
645
91
|
}
|
|
646
|
-
const events =
|
|
92
|
+
const events = require_decision.getAcceptedEvents(deps.getSnapshot(), {
|
|
647
93
|
schemas: deps.schemas,
|
|
648
94
|
...stateRequest.allowedEvents ? { eventTypes: stateRequest.allowedEvents } : {}
|
|
649
95
|
});
|
|
@@ -662,7 +108,7 @@ async function runAdvancePhase(plan, deps) {
|
|
|
662
108
|
canTake: (event) => deps.getSnapshot().can(event)
|
|
663
109
|
});
|
|
664
110
|
if (deps.isSettled()) return false;
|
|
665
|
-
deps.appendToLog(
|
|
111
|
+
deps.appendToLog(require_decision.assistantMessage(`[chose: ${chosen.type}]`));
|
|
666
112
|
deps.send(chosen);
|
|
667
113
|
return true;
|
|
668
114
|
}
|
|
@@ -696,12 +142,12 @@ async function runStateRequestPass(requests, deps) {
|
|
|
696
142
|
* machine simply takes no transition). Opt out with
|
|
697
143
|
* {@link RunAgentOptions.onIllegalResumeEvent} `'ignore'`.
|
|
698
144
|
*/
|
|
699
|
-
var
|
|
145
|
+
var AgentIllegalResumeEventError = class extends require_errors.AgentError {
|
|
700
146
|
eventType;
|
|
701
147
|
acceptedTypes;
|
|
702
148
|
constructor(eventType, acceptedTypes) {
|
|
703
|
-
super(`runAgent: cannot resume with event '${eventType}' — the restored state does not accept it. Accepted event types: ${acceptedTypes.length > 0 ? acceptedTypes.join(", ") : "(none)"}.`);
|
|
704
|
-
this.name = "
|
|
149
|
+
super("illegal-resume-event", `runAgent: cannot resume with event '${eventType}' — the restored state does not accept it. Accepted event types: ${acceptedTypes.length > 0 ? acceptedTypes.join(", ") : "(none)"}.`);
|
|
150
|
+
this.name = "AgentIllegalResumeEventError";
|
|
705
151
|
this.eventType = eventType;
|
|
706
152
|
this.acceptedTypes = acceptedTypes;
|
|
707
153
|
}
|
|
@@ -715,30 +161,30 @@ var IllegalResumeEventError = class extends Error {
|
|
|
715
161
|
* the snapshot may no longer resume cleanly. `from` is the snapshot's version,
|
|
716
162
|
* `to` the current machine's.
|
|
717
163
|
*/
|
|
718
|
-
var
|
|
164
|
+
var AgentSnapshotVersionMismatchError = class extends require_errors.AgentError {
|
|
719
165
|
from;
|
|
720
166
|
to;
|
|
721
167
|
machineId;
|
|
722
168
|
constructor(from, to, machineId) {
|
|
723
|
-
super(`runAgent: cannot resume snapshot stamped with machine version '${from}' against machine '${machineId}' at version '${to}' — the machine's structure changed since the snapshot was persisted. Provide options.migrateSnapshot to adapt it, or set options.onVersionMismatch to 'warn'/'ignore' to proceed anyway.`);
|
|
724
|
-
this.name = "
|
|
169
|
+
super("snapshot-version-mismatch", `runAgent: cannot resume snapshot stamped with machine version '${from}' against machine '${machineId}' at version '${to}' — the machine's structure changed since the snapshot was persisted. Provide options.migrateSnapshot to adapt it, or set options.onVersionMismatch to 'warn'/'ignore' to proceed anyway.`);
|
|
170
|
+
this.name = "AgentSnapshotVersionMismatchError";
|
|
725
171
|
this.from = from;
|
|
726
172
|
this.to = to;
|
|
727
173
|
this.machineId = machineId;
|
|
728
174
|
}
|
|
729
175
|
};
|
|
730
176
|
/**
|
|
731
|
-
* Thrown by {@link
|
|
177
|
+
* Thrown by {@link generateResult} when the run settles `idle` instead of
|
|
732
178
|
* `done`: the machine paused for external input. Carries the idle `snapshot`
|
|
733
179
|
* and `acceptedTypes` (the event types that could resume it, via
|
|
734
180
|
* {@link getAcceptedEvents}). Use {@link runAgent} directly when idle is an
|
|
735
181
|
* expected outcome you handle.
|
|
736
182
|
*/
|
|
737
|
-
var AgentIdleError = class extends
|
|
183
|
+
var AgentIdleError = class extends require_errors.AgentError {
|
|
738
184
|
snapshot;
|
|
739
185
|
acceptedTypes;
|
|
740
186
|
constructor(snapshot, acceptedTypes) {
|
|
741
|
-
super(`
|
|
187
|
+
super("agent-idle", `generateResult: the machine paused (idle) instead of completing. Resume it by calling runAgent with one of these events: ${acceptedTypes.length > 0 ? acceptedTypes.join(", ") : "(none)"}.`);
|
|
742
188
|
this.name = "AgentIdleError";
|
|
743
189
|
this.snapshot = snapshot;
|
|
744
190
|
this.acceptedTypes = acceptedTypes;
|
|
@@ -752,17 +198,104 @@ var AgentIdleError = class extends Error {
|
|
|
752
198
|
* {@link traceTransitions}.
|
|
753
199
|
*/
|
|
754
200
|
const AGENT_TRACE_SCHEMA_VERSION = 1;
|
|
201
|
+
const TRACE_ENVELOPE_KEYS = [
|
|
202
|
+
"schemaVersion",
|
|
203
|
+
"runId",
|
|
204
|
+
"seq",
|
|
205
|
+
"timestamp",
|
|
206
|
+
"machineId",
|
|
207
|
+
"machineVersion",
|
|
208
|
+
"type",
|
|
209
|
+
"status",
|
|
210
|
+
"cause",
|
|
211
|
+
"eventId",
|
|
212
|
+
"reasoning",
|
|
213
|
+
"chunk"
|
|
214
|
+
];
|
|
215
|
+
/**
|
|
216
|
+
* Best-effort JSON projection of an arbitrary value. Never throws: functions,
|
|
217
|
+
* symbols, `undefined`, and cyclic back-references are DROPPED (array holes
|
|
218
|
+
* become `null`), non-finite numbers become `null`, `bigint`s become strings,
|
|
219
|
+
* `Error`s become `{ name, message, stack?, code? }`, and anything with a
|
|
220
|
+
* `toJSON()` (e.g. `Date`) is projected through it — the same losses a
|
|
221
|
+
* `JSON.parse(JSON.stringify(...))` round-trip incurs, minus the throws.
|
|
222
|
+
*/
|
|
223
|
+
function toJsonValue(value, ancestors) {
|
|
224
|
+
if (value === null) return null;
|
|
225
|
+
const type = typeof value;
|
|
226
|
+
if (type === "string" || type === "boolean") return value;
|
|
227
|
+
if (type === "number") return Number.isFinite(value) ? value : null;
|
|
228
|
+
if (type === "bigint") return value.toString();
|
|
229
|
+
if (type !== "object") return;
|
|
230
|
+
const object = value;
|
|
231
|
+
if (ancestors.includes(object)) return;
|
|
232
|
+
const nextAncestors = [...ancestors, object];
|
|
233
|
+
if (object instanceof Error) {
|
|
234
|
+
const serialized = {
|
|
235
|
+
name: object.name,
|
|
236
|
+
message: object.message
|
|
237
|
+
};
|
|
238
|
+
if (typeof object.stack === "string") serialized.stack = object.stack;
|
|
239
|
+
const code = object.code;
|
|
240
|
+
if (typeof code === "string") serialized.code = code;
|
|
241
|
+
const cause = toJsonValue(object.cause, nextAncestors);
|
|
242
|
+
if (cause !== void 0) serialized.cause = cause;
|
|
243
|
+
return serialized;
|
|
244
|
+
}
|
|
245
|
+
const toJSON = object.toJSON;
|
|
246
|
+
if (typeof toJSON === "function") return toJsonValue(toJSON.call(object), nextAncestors);
|
|
247
|
+
if (Array.isArray(object)) return object.map((item) => toJsonValue(item, nextAncestors) ?? null);
|
|
248
|
+
const out = {};
|
|
249
|
+
for (const [key, item] of Object.entries(object)) {
|
|
250
|
+
const serializedItem = toJsonValue(item, nextAncestors);
|
|
251
|
+
if (serializedItem !== void 0) out[key] = serializedItem;
|
|
252
|
+
}
|
|
253
|
+
return out;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Projects an {@link AgentTraceEvent} into a guaranteed JSON-safe envelope —
|
|
257
|
+
* the form the trace stream is actually sold for (one `JSON.stringify` per line
|
|
258
|
+
* in a JSONL file). Live values are sanitized rather than trusted:
|
|
259
|
+
*
|
|
260
|
+
* - Snapshots (`run.start`, `machine.transition`, `run.end`) go through the
|
|
261
|
+
* same JSON round-trip as {@link persistSnapshot}, so what lands on disk is
|
|
262
|
+
* what a resume would see.
|
|
263
|
+
* - `request.end`'s `raw` (a provider SDK object, frequently cyclic) is DROPPED
|
|
264
|
+
* unless `includeRaw` is set, in which case it is sanitized like everything
|
|
265
|
+
* else.
|
|
266
|
+
* - Non-serializable values anywhere (functions, symbols, `undefined`, cyclic
|
|
267
|
+
* back-references) are dropped; `Error`s become `{ name, message, stack?,
|
|
268
|
+
* code? }` instead of `{}`. Nothing throws.
|
|
269
|
+
*
|
|
270
|
+
* @example
|
|
271
|
+
* ```ts
|
|
272
|
+
* await appendFile('trace.jsonl', JSON.stringify(serializeTraceEvent(event)) + '\n');
|
|
273
|
+
* ```
|
|
274
|
+
*/
|
|
275
|
+
function serializeTraceEvent(event, options = {}) {
|
|
276
|
+
const out = {};
|
|
277
|
+
for (const [key, value] of Object.entries(event)) {
|
|
278
|
+
if (key === "raw" && !options.includeRaw) continue;
|
|
279
|
+
if (TRACE_ENVELOPE_KEYS.includes(key)) {
|
|
280
|
+
if (value !== void 0) out[key] = value;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
const serialized = toJsonValue(value, []);
|
|
284
|
+
if (serialized !== void 0) out[key] = serialized;
|
|
285
|
+
}
|
|
286
|
+
return out;
|
|
287
|
+
}
|
|
755
288
|
let nextRunAgentTraceId = 1;
|
|
756
|
-
var
|
|
289
|
+
var AgentMaxModelCallsExceededError = class extends require_errors.AgentError {
|
|
757
290
|
constructor() {
|
|
758
|
-
super("runAgent exceeded maxModelCalls.");
|
|
759
|
-
this.name = "
|
|
291
|
+
super("max-model-calls-exceeded", "runAgent exceeded maxModelCalls.");
|
|
292
|
+
this.name = "AgentMaxModelCallsExceededError";
|
|
760
293
|
}
|
|
761
294
|
};
|
|
762
295
|
function wrapsDecisionExhausted(error) {
|
|
763
296
|
let current = error;
|
|
764
297
|
for (let depth = 0; depth < 10 && current != null; depth++) {
|
|
765
|
-
if (current instanceof require_decision.
|
|
298
|
+
if (current instanceof require_decision.AgentDecisionExhaustedError) return true;
|
|
766
299
|
current = current.cause;
|
|
767
300
|
}
|
|
768
301
|
return false;
|
|
@@ -790,14 +323,14 @@ function collectConfiguredInvokeSrcs(stateConfig, stateName, out) {
|
|
|
790
323
|
/**
|
|
791
324
|
* Duck-types a state machine actor logic (an invoked child machine) vs. any
|
|
792
325
|
* other actor logic. xstate's `StateMachine` carries `.config`, `.root`, and
|
|
793
|
-
* a `.provide(...)` method plus
|
|
326
|
+
* a `.provide(...)` method plus a `sources.actors` map — this
|
|
794
327
|
* combination is unique to machines and survives the dual-package/version
|
|
795
328
|
* boundary an `instanceof` check would not. Used to descend the bind-time
|
|
796
329
|
* walk into invoked child machines (their internal agent requests are opaque
|
|
797
330
|
* to the parent-level source walk otherwise).
|
|
798
331
|
*/
|
|
799
332
|
function isStateMachine(logic) {
|
|
800
|
-
return !!logic && typeof logic === "object" && "config" in logic && "root" in logic && typeof logic.provide === "function" && typeof logic.
|
|
333
|
+
return !!logic && typeof logic === "object" && "config" in logic && "root" in logic && typeof logic.provide === "function" && typeof logic.sources === "object" && !!logic.sources?.actors;
|
|
801
334
|
}
|
|
802
335
|
/**
|
|
803
336
|
* Fails fast (throws) at bind time — before any actor runs — when the
|
|
@@ -832,18 +365,18 @@ function assertMachineBindable(machine, effectiveSources, options, ctx) {
|
|
|
832
365
|
assertChildMachineBindable(src, src, stateName, options, ctx);
|
|
833
366
|
continue;
|
|
834
367
|
}
|
|
835
|
-
if ((
|
|
368
|
+
if ((require_decision.isTextLogic(src) || require_decision.isDecisionLogic(src) || require_decision.isPlanLogic(src)) && !require_decision.executorBoundLogics.has(src)) throw new Error(`runAgent: ${where} '${stateName}' invokes a direct-object actor logic (kind: '${src.kind}'). Direct-object invoke srcs cannot be rebound by runAgent — either call '.withExecutor(...)' on the logic before invoking it, or register it as a string-keyed actor source instead (machine.provide({ actors: { name: logic } })) and invoke it by name.`);
|
|
836
369
|
continue;
|
|
837
370
|
}
|
|
838
371
|
const logic = effectiveSources[src];
|
|
839
|
-
if (logic === void 0) throw new Error(`runAgent: ${where} '${stateName}' invokes unregistered actor source '${src}'. Provide it via machine.provide({
|
|
372
|
+
if (logic === void 0) throw new Error(`runAgent: ${where} '${stateName}' invokes unregistered actor source '${src}'. Provide it via machine.provide({ actors: { '${src}': ... } }) or runAgent(machine, { actors: { '${src}': ... } }).`);
|
|
840
373
|
if (isStateMachine(logic)) {
|
|
841
374
|
assertChildMachineBindable(logic, src, stateName, options, ctx);
|
|
842
375
|
continue;
|
|
843
376
|
}
|
|
844
377
|
if (src === "agent.userInput") continue;
|
|
845
378
|
if (require_decision.isDecisionLogic(logic)) {
|
|
846
|
-
if (
|
|
379
|
+
if (require_decision.executorBoundLogics.has(logic)) continue;
|
|
847
380
|
if (!ctx.rebindable) throw unrebindableChildRequestError(ctx.childPath, stateName, src, "decision");
|
|
848
381
|
if (!options.hasDecide) throw new Error(`runAgent: ${where} '${stateName}' invokes decision source '${src}' but no 'decide' executor was provided to runAgent(...).`);
|
|
849
382
|
continue;
|
|
@@ -853,14 +386,14 @@ function assertMachineBindable(machine, effectiveSources, options, ctx) {
|
|
|
853
386
|
if (!options.hasDecide) throw new Error(`runAgent: ${where} '${stateName}' invokes plan source '${src}' but no 'decide' executor was provided to runAgent(...).`);
|
|
854
387
|
continue;
|
|
855
388
|
}
|
|
856
|
-
if (
|
|
857
|
-
if (
|
|
389
|
+
if (require_decision.isTextLogic(logic)) {
|
|
390
|
+
if (require_decision.executorBoundLogics.has(logic)) continue;
|
|
858
391
|
if (!ctx.rebindable) throw unrebindableChildRequestError(ctx.childPath, stateName, src, logic.mode === "stream" ? "streaming text" : "text");
|
|
859
392
|
if (logic.mode === "stream" && !options.hasStreamText) throw new Error(`runAgent: ${where} '${stateName}' invokes streaming text source '${src}' but no 'streamText' executor was provided to runAgent(...).`);
|
|
860
393
|
if (logic.mode !== "stream" && !options.hasGenerateText) throw new Error(`runAgent: ${where} '${stateName}' invokes text source '${src}' but no 'generateText' executor was provided to runAgent(...).`);
|
|
861
394
|
continue;
|
|
862
395
|
}
|
|
863
|
-
if (
|
|
396
|
+
if (require_decision.isUnboundPlaceholder(logic)) throw new Error(`runAgent: ${where} '${stateName}' invokes actor source '${src}', which has no host execution. Provide it via machine.provide({ actors: { '${src}': ... } }) or runAgent(machine, { actors: { '${src}': ... } }).`);
|
|
864
397
|
}
|
|
865
398
|
}
|
|
866
399
|
/** Descends the bind-time walk into an invoked child state machine, guarding
|
|
@@ -869,7 +402,7 @@ function assertChildMachineBindable(childMachine, childSrc, stateName, options,
|
|
|
869
402
|
if (ctx.visited.has(childMachine)) return;
|
|
870
403
|
const childName = typeof childSrc === "string" ? childSrc : childMachine.config.id ?? "(child machine)";
|
|
871
404
|
const childPath = ctx.childPath ? `${ctx.childPath} > ${childName}` : childName;
|
|
872
|
-
const childSources = childMachine.
|
|
405
|
+
const childSources = childMachine.sources.actors;
|
|
873
406
|
assertMachineBindable(childMachine, childSources, options, {
|
|
874
407
|
isChild: true,
|
|
875
408
|
childPath,
|
|
@@ -885,7 +418,21 @@ function assertChildMachineBindable(childMachine, childSrc, stateName, options,
|
|
|
885
418
|
* runAgent's executors — this error is only for the unrebindable direct-object
|
|
886
419
|
* case. */
|
|
887
420
|
function unrebindableChildRequestError(childPath, stateName, requestSrc, kind) {
|
|
888
|
-
return /* @__PURE__ */ new Error(`runAgent: child machine '${childPath}' (state '${stateName}') invokes ${kind} source '${requestSrc}', which has no host execution and is reached through a direct-object invoke src that runAgent cannot rebind. Requests reached through string-keyed actor sources inherit runAgent's generateText/streamText/decide executors automatically; a direct-object child machine does not. Either bind the request with its own executor (requestLogic.withExecutor(...)), or register the child as a string-keyed actor source (machine.provide({
|
|
421
|
+
return /* @__PURE__ */ new Error(`runAgent: child machine '${childPath}' (state '${stateName}') invokes ${kind} source '${requestSrc}', which has no host execution and is reached through a direct-object invoke src that runAgent cannot rebind. Requests reached through string-keyed actor sources inherit runAgent's generateText/streamText/decide executors automatically; a direct-object child machine does not. Either bind the request with its own executor (requestLogic.withExecutor(...)), or register the child as a string-keyed actor source (machine.provide({ actors: { <child>: childMachine } })) and invoke it by name.`);
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* True when the snapshot's active states declare a transition for the reserved
|
|
425
|
+
* `'@agent.usage'` type EXPLICITLY. A catch-all `on: { '*': … }` deliberately
|
|
426
|
+
* does not count: a wildcard is a machine's own event vocabulary, not an
|
|
427
|
+
* opt-in to a library-reserved event, and `snapshot.can(event)` alone cannot
|
|
428
|
+
* tell the two apart (it answers "would this event be taken?", which a
|
|
429
|
+
* wildcard makes true for everything). Gating delivery on the explicit
|
|
430
|
+
* declaration is what keeps `@agent.usage` opt-in by construction — and keeps
|
|
431
|
+
* a wildcard machine's context and event log byte-identical to a run without
|
|
432
|
+
* the feature. @internal
|
|
433
|
+
*/
|
|
434
|
+
function declaresUsageTransition(snapshot) {
|
|
435
|
+
return (0, xstate.getNextTransitions)(snapshot).some((transition) => transition.eventType === require_setup_agent.AGENT_USAGE_EVENT_TYPE);
|
|
889
436
|
}
|
|
890
437
|
/** Reads the durable invoke id/src off the async actor's own ref (`self`). */
|
|
891
438
|
function selfIdAndSrc(self) {
|
|
@@ -947,9 +494,11 @@ function wrapTextLogicForRunAgent(logic, runCtx) {
|
|
|
947
494
|
}, self);
|
|
948
495
|
runCtx.onChunk?.(chunk, { request: agentRequest });
|
|
949
496
|
},
|
|
950
|
-
signal
|
|
497
|
+
signal,
|
|
498
|
+
...runCtx.runId !== void 0 ? { runId: runCtx.runId } : {},
|
|
499
|
+
...id !== "" ? { requestId: id } : {}
|
|
951
500
|
});
|
|
952
|
-
const output = await
|
|
501
|
+
const output = await require_decision.normalizeGeneratorResult(raw, id, {
|
|
953
502
|
request,
|
|
954
503
|
onChunk: (chunk) => {
|
|
955
504
|
runCtx.onTrace?.({
|
|
@@ -962,6 +511,14 @@ function wrapTextLogicForRunAgent(logic, runCtx) {
|
|
|
962
511
|
});
|
|
963
512
|
const rawReasoning = raw?.reasoning;
|
|
964
513
|
const reasoning = typeof rawReasoning === "string" ? rawReasoning : void 0;
|
|
514
|
+
const usage = require_decision.extractCallUsage(raw);
|
|
515
|
+
if (usage) runCtx.recordUsage?.(usage, {
|
|
516
|
+
kind: "text",
|
|
517
|
+
...id !== "" ? { id } : {},
|
|
518
|
+
...src !== "" ? { src } : {},
|
|
519
|
+
model: request.model,
|
|
520
|
+
...request.name !== void 0 ? { name: request.name } : {}
|
|
521
|
+
}, self);
|
|
965
522
|
runCtx.onResult?.(agentRequest, {
|
|
966
523
|
output,
|
|
967
524
|
raw
|
|
@@ -971,7 +528,8 @@ function wrapTextLogicForRunAgent(logic, runCtx) {
|
|
|
971
528
|
request: agentRequest,
|
|
972
529
|
output,
|
|
973
530
|
raw,
|
|
974
|
-
...reasoning !== void 0 ? { reasoning } : {}
|
|
531
|
+
...reasoning !== void 0 ? { reasoning } : {},
|
|
532
|
+
...usage !== void 0 ? { usage } : {}
|
|
975
533
|
}, self);
|
|
976
534
|
return { output };
|
|
977
535
|
} catch (error) {
|
|
@@ -984,7 +542,7 @@ function wrapTextLogicForRunAgent(logic, runCtx) {
|
|
|
984
542
|
}
|
|
985
543
|
});
|
|
986
544
|
}
|
|
987
|
-
function createCountingDecide(runCtx, self) {
|
|
545
|
+
function createCountingDecide(runCtx, self, kind = "decision") {
|
|
988
546
|
return async (attemptRequest) => {
|
|
989
547
|
runCtx.consumeModelCall();
|
|
990
548
|
runCtx.onTrace?.({
|
|
@@ -992,7 +550,20 @@ function createCountingDecide(runCtx, self) {
|
|
|
992
550
|
request: attemptRequest
|
|
993
551
|
}, self);
|
|
994
552
|
try {
|
|
995
|
-
const result = await runCtx.decide(
|
|
553
|
+
const result = await runCtx.decide(runCtx.runId !== void 0 ? {
|
|
554
|
+
...attemptRequest,
|
|
555
|
+
runId: runCtx.runId
|
|
556
|
+
} : attemptRequest);
|
|
557
|
+
const usage = require_decision.extractCallUsage(result);
|
|
558
|
+
if (usage) {
|
|
559
|
+
const { src } = selfIdAndSrc(self);
|
|
560
|
+
runCtx.recordUsage?.(usage, {
|
|
561
|
+
kind,
|
|
562
|
+
...attemptRequest.id ? { id: attemptRequest.id } : {},
|
|
563
|
+
...src !== "" ? { src } : {},
|
|
564
|
+
model: attemptRequest.model
|
|
565
|
+
}, self);
|
|
566
|
+
}
|
|
996
567
|
runCtx.onResult?.(attemptRequest, {
|
|
997
568
|
output: result.event,
|
|
998
569
|
raw: result
|
|
@@ -1001,7 +572,8 @@ function createCountingDecide(runCtx, self) {
|
|
|
1001
572
|
type: "request.end",
|
|
1002
573
|
request: attemptRequest,
|
|
1003
574
|
output: result.event,
|
|
1004
|
-
raw: result
|
|
575
|
+
raw: result,
|
|
576
|
+
...usage !== void 0 ? { usage } : {}
|
|
1005
577
|
}, self);
|
|
1006
578
|
return result;
|
|
1007
579
|
} catch (error) {
|
|
@@ -1036,7 +608,7 @@ function createRunAgentDecisionLogic(logic, runCtx) {
|
|
|
1036
608
|
const declaredEventTypes = logic.allowedEventTypes?.(input);
|
|
1037
609
|
await Promise.resolve();
|
|
1038
610
|
const actorRef = invokingActorOf(self, runCtx);
|
|
1039
|
-
const events = actorRef ?
|
|
611
|
+
const events = actorRef ? require_decision.getAcceptedEvents(actorRef.getSnapshot(), {
|
|
1040
612
|
schemas: runCtx.schemas,
|
|
1041
613
|
eventTypes: declaredEventTypes
|
|
1042
614
|
}) : [];
|
|
@@ -1079,7 +651,7 @@ function createRunAgentPlanLogic(logic, runCtx) {
|
|
|
1079
651
|
if (!runCtx.decide) throw new Error("runAgent: no 'decide' executor provided.");
|
|
1080
652
|
const { id } = selfIdAndSrc(self);
|
|
1081
653
|
const stopOn = new Set(input.stopOn ?? []);
|
|
1082
|
-
const countingDecide = createCountingDecide(runCtx, self);
|
|
654
|
+
const countingDecide = createCountingDecide(runCtx, self, "plan");
|
|
1083
655
|
const base = logic.request(input);
|
|
1084
656
|
let ledger = require_decision.initialPlanLedger(logic, input);
|
|
1085
657
|
const end = (stopped) => {
|
|
@@ -1094,14 +666,14 @@ function createRunAgentPlanLogic(logic, runCtx) {
|
|
|
1094
666
|
while (ledger.context.stepsRemaining > 0) {
|
|
1095
667
|
const actorRef = invokingActor;
|
|
1096
668
|
if (!actorRef || signal.aborted) break;
|
|
1097
|
-
const machineEvents =
|
|
669
|
+
const machineEvents = require_decision.getAcceptedEvents(actorRef.getSnapshot(), {
|
|
1098
670
|
schemas: runCtx.schemas,
|
|
1099
671
|
eventTypes: logic.allowedEventTypes(input)
|
|
1100
672
|
});
|
|
1101
673
|
if (machineEvents.length === 0) return end("no-legal-events");
|
|
1102
674
|
const doneDescriptor = {
|
|
1103
675
|
type: require_decision.PLAN_DONE_EVENT_TYPE,
|
|
1104
|
-
toolName:
|
|
676
|
+
toolName: require_decision.sanitizeEventToolName(require_decision.PLAN_DONE_EVENT_TYPE)
|
|
1105
677
|
};
|
|
1106
678
|
const events = machineEvents.some((event) => event.type === "agent.plan.done") ? machineEvents : [...machineEvents, doneDescriptor];
|
|
1107
679
|
const applied = ledger.context.applied;
|
|
@@ -1169,7 +741,7 @@ function rootTraceState(root) {
|
|
|
1169
741
|
if (!state) {
|
|
1170
742
|
const logic = root.logic;
|
|
1171
743
|
const machineId = (logic?.config)?.id ?? logic?.id ?? "(machine)";
|
|
1172
|
-
const machineVersion = logic ?
|
|
744
|
+
const machineVersion = logic ? require_decision.getMachineStructuralHash(logic) : "";
|
|
1173
745
|
state = {
|
|
1174
746
|
runId: `run_${nextProvideRunId++}`,
|
|
1175
747
|
seq: 0,
|
|
@@ -1209,6 +781,10 @@ function provideTraceSink(onTrace) {
|
|
|
1209
781
|
* actor off `self._parent`, always present under a live `createActor` tree.
|
|
1210
782
|
* `onTrace` (when given) mints a per-root-actor envelope. `schemas` come from
|
|
1211
783
|
* the machine's registered `setupAgent` execution options.
|
|
784
|
+
*
|
|
785
|
+
* `recordUsage` has no run-level aggregate to fold into here (there is no
|
|
786
|
+
* run), so it does one thing: deliver the reserved `@agent.usage` event, gated
|
|
787
|
+
* exactly like runAgent's — see {@link deliverUsageToInvokingActor}.
|
|
1212
788
|
*/
|
|
1213
789
|
function provideBindContext(machine, executors, options) {
|
|
1214
790
|
return {
|
|
@@ -1218,9 +794,43 @@ function provideBindContext(machine, executors, options) {
|
|
|
1218
794
|
onChunk: options.onChunk ? (chunk) => options.onChunk(chunk) : void 0,
|
|
1219
795
|
onTrace: provideTraceSink(options.onTrace),
|
|
1220
796
|
consumeModelCall: () => {},
|
|
797
|
+
recordUsage: (usage, source, self) => {
|
|
798
|
+
deliverUsageToInvokingActor(usage, source ?? {}, self);
|
|
799
|
+
},
|
|
1221
800
|
actorHolder: { actorRef: void 0 },
|
|
1222
|
-
schemas:
|
|
801
|
+
schemas: require_decision.getRegisteredAgentExecutionOptions(machine).schemas
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* `provideExecutors`' counterpart to runAgent's `deliverUsageEvent`: after a
|
|
806
|
+
* bound call settles with reported usage, send the reserved
|
|
807
|
+
* `@agent.usage` event to the machine actor that INVOKED the request — read
|
|
808
|
+
* off the settling request actor's `self._parent`, which under a live
|
|
809
|
+
* `createActor` tree is always the invoking machine (there is no run-scoped
|
|
810
|
+
* root actor on this path).
|
|
811
|
+
*
|
|
812
|
+
* Gated identically to runAgent: the invoking snapshot must be active, must
|
|
813
|
+
* declare an `'@agent.usage'` transition EXPLICITLY (see
|
|
814
|
+
* {@link declaresUsageTransition} — a catch-all `on: { '*' }` is not an opt-in),
|
|
815
|
+
* and must be able to take the event. There is no cycle to settle in
|
|
816
|
+
* uncontrolled mode, so there are no dropped stragglers.
|
|
817
|
+
*
|
|
818
|
+
* Delivery follows `provideExecutors`' binding boundary: only sources IT bound
|
|
819
|
+
* report here, so an invoked child machine that was not itself passed through
|
|
820
|
+
* `provideExecutors` reports nothing. @internal
|
|
821
|
+
*/
|
|
822
|
+
function deliverUsageToInvokingActor(usage, source, self) {
|
|
823
|
+
const actorRef = self?._parent;
|
|
824
|
+
if (!actorRef) return;
|
|
825
|
+
const snapshot = actorRef.getSnapshot();
|
|
826
|
+
if (snapshot?.status !== "active" || !declaresUsageTransition(snapshot)) return;
|
|
827
|
+
const event = {
|
|
828
|
+
type: require_setup_agent.AGENT_USAGE_EVENT_TYPE,
|
|
829
|
+
...source,
|
|
830
|
+
usage
|
|
1223
831
|
};
|
|
832
|
+
if (!snapshot.can(event)) return;
|
|
833
|
+
actorRef.send(event);
|
|
1224
834
|
}
|
|
1225
835
|
/**
|
|
1226
836
|
* Host-binds one text/stream source for {@link provideExecutors} using the SAME
|
|
@@ -1265,19 +875,19 @@ function bindPlanForProvide(machine, logic, executors, options) {
|
|
|
1265
875
|
function rebindChildMachine(childMachine, runCtx, visited) {
|
|
1266
876
|
if (visited.has(childMachine)) return childMachine;
|
|
1267
877
|
const childVisited = new Set([...visited, childMachine]);
|
|
1268
|
-
const sources = childMachine.
|
|
878
|
+
const sources = childMachine.sources.actors;
|
|
1269
879
|
const wrapped = {};
|
|
1270
880
|
for (const [key, logic] of Object.entries(sources)) {
|
|
1271
881
|
if (require_decision.isDecisionLogic(logic)) {
|
|
1272
|
-
if (!
|
|
882
|
+
if (!require_decision.executorBoundLogics.has(logic)) wrapped[key] = createRunAgentDecisionLogic(logic, runCtx);
|
|
1273
883
|
continue;
|
|
1274
884
|
}
|
|
1275
885
|
if (require_decision.isPlanLogic(logic)) {
|
|
1276
886
|
wrapped[key] = createRunAgentPlanLogic(logic, runCtx);
|
|
1277
887
|
continue;
|
|
1278
888
|
}
|
|
1279
|
-
if (
|
|
1280
|
-
if (!
|
|
889
|
+
if (require_decision.isTextLogic(logic)) {
|
|
890
|
+
if (!require_decision.executorBoundLogics.has(logic)) wrapped[key] = wrapTextLogicForRunAgent(logic, runCtx);
|
|
1281
891
|
continue;
|
|
1282
892
|
}
|
|
1283
893
|
if (isStateMachine(logic)) {
|
|
@@ -1286,7 +896,7 @@ function rebindChildMachine(childMachine, runCtx, visited) {
|
|
|
1286
896
|
continue;
|
|
1287
897
|
}
|
|
1288
898
|
}
|
|
1289
|
-
return Object.keys(wrapped).length > 0 ? childMachine.provide({
|
|
899
|
+
return Object.keys(wrapped).length > 0 ? childMachine.provide({ actors: wrapped }) : childMachine;
|
|
1290
900
|
}
|
|
1291
901
|
/**
|
|
1292
902
|
* Runs an agent machine to completion or idle: a `createActor` host that
|
|
@@ -1303,7 +913,7 @@ function rebindChildMachine(childMachine, runCtx, visited) {
|
|
|
1303
913
|
*
|
|
1304
914
|
* Binding happens **before** the actor starts: every invoke the machine
|
|
1305
915
|
* could reach is walked and checked against the effective actor sources
|
|
1306
|
-
* (`options.
|
|
916
|
+
* (`options.actors` merged onto the machine), so a missing
|
|
1307
917
|
* `streamText`/`decide` executor or any other unbound actor source throws
|
|
1308
918
|
* immediately — a bind-time error, not a mid-run failure. The one exception
|
|
1309
919
|
* is `agent.userInput`: unhandled, it binds as a pending placeholder that
|
|
@@ -1328,14 +938,42 @@ function rebindChildMachine(childMachine, runCtx, visited) {
|
|
|
1328
938
|
* lives in an adapter — use `createAiSdkExecutors` from '@statelyai/agent/ai-sdk'.
|
|
1329
939
|
*/
|
|
1330
940
|
async function runAgent(machine, options) {
|
|
941
|
+
return createAgentSession(machine, options, { oneShot: true }).settled();
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* Session mode: {@link runAgent}'s engine with a long-lived actor. Use it when
|
|
945
|
+
* the agent is a *session* fed by external events (chat turns, device or
|
|
946
|
+
* timer events, a socket) rather than a one-shot job — you keep the log,
|
|
947
|
+
* budget, traces, and idle semantics that bare `provideExecutors` +
|
|
948
|
+
* `createActor` would forfeit.
|
|
949
|
+
*
|
|
950
|
+
* ```ts
|
|
951
|
+
* const session = createAgentActor(machine, { input, executors });
|
|
952
|
+
* let result = await session.settled(); // first quiescence
|
|
953
|
+
* while (result.status === "idle") {
|
|
954
|
+
* session.actor.send(await nextUserEvent(result.snapshot));
|
|
955
|
+
* result = await session.settled(); // next quiescence, same log
|
|
956
|
+
* }
|
|
957
|
+
* session.stop();
|
|
958
|
+
* ```
|
|
959
|
+
*
|
|
960
|
+
* Accepts the same options as {@link runAgent} (including `snapshot`/`events`
|
|
961
|
+
* resume). Not yet supported in session mode: `getRequests` re-interpretation
|
|
962
|
+
* across cycles behaves per-cycle exactly as in `runAgent`.
|
|
963
|
+
*/
|
|
964
|
+
function createAgentActor(machine, options) {
|
|
965
|
+
return createAgentSession(machine, options, { oneShot: false });
|
|
966
|
+
}
|
|
967
|
+
function createAgentSession(machine, options, lifecycle) {
|
|
1331
968
|
const maxModelCalls = options.maxModelCalls ?? 100;
|
|
1332
969
|
let modelCallCount = 0;
|
|
1333
970
|
let budgetExceeded = false;
|
|
1334
971
|
let warnedNonSerializable = false;
|
|
972
|
+
let warnedHeuristicIdle = false;
|
|
1335
973
|
const runId = `run_${nextRunAgentTraceId++}`;
|
|
1336
974
|
let traceSeq = 0;
|
|
1337
975
|
const machineId = machine.config.id ?? machine.id ?? "(machine)";
|
|
1338
|
-
const machineVersion = options.machineVersion ??
|
|
976
|
+
const machineVersion = options.machineVersion ?? machine.version ?? require_decision.getMachineStructuralHash(machine);
|
|
1339
977
|
const agentMeta = {
|
|
1340
978
|
machineId,
|
|
1341
979
|
version: machineVersion
|
|
@@ -1355,18 +993,52 @@ async function runAgent(machine, options) {
|
|
|
1355
993
|
});
|
|
1356
994
|
};
|
|
1357
995
|
const consumeModelCall = () => {
|
|
1358
|
-
if (budgetExceeded) throw new
|
|
1359
|
-
modelCallCount
|
|
1360
|
-
if (modelCallCount > maxModelCalls) {
|
|
996
|
+
if (budgetExceeded) throw new AgentMaxModelCallsExceededError();
|
|
997
|
+
if (modelCallCount + 1 > maxModelCalls) {
|
|
1361
998
|
budgetExceeded = true;
|
|
1362
|
-
throw new
|
|
999
|
+
throw new AgentMaxModelCallsExceededError();
|
|
1363
1000
|
}
|
|
1001
|
+
modelCallCount += 1;
|
|
1002
|
+
};
|
|
1003
|
+
const tokenTotals = {};
|
|
1004
|
+
const cycleGate = { isResolved: () => false };
|
|
1005
|
+
const deliverUsageEvent = (usage, source) => {
|
|
1006
|
+
const actorRef = actorHolder.actorRef;
|
|
1007
|
+
if (!actorRef) return;
|
|
1008
|
+
const event = {
|
|
1009
|
+
type: require_setup_agent.AGENT_USAGE_EVENT_TYPE,
|
|
1010
|
+
...source,
|
|
1011
|
+
usage
|
|
1012
|
+
};
|
|
1013
|
+
const snapshot = actorRef.getSnapshot();
|
|
1014
|
+
if (snapshot?.status !== "active" || !declaresUsageTransition(snapshot)) return;
|
|
1015
|
+
if (cycleGate.isResolved()) {
|
|
1016
|
+
onTrace({
|
|
1017
|
+
type: "usage.dropped",
|
|
1018
|
+
event,
|
|
1019
|
+
reason: "settled"
|
|
1020
|
+
});
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
if (!snapshot.can(event)) return;
|
|
1024
|
+
actorRef.send(event);
|
|
1364
1025
|
};
|
|
1026
|
+
const recordUsage = (usage, source = {}) => {
|
|
1027
|
+
for (const field of require_decision.AGENT_USAGE_TOKEN_FIELDS) {
|
|
1028
|
+
const value = usage[field];
|
|
1029
|
+
if (typeof value === "number" && Number.isFinite(value)) tokenTotals[field] = (tokenTotals[field] ?? 0) + value;
|
|
1030
|
+
}
|
|
1031
|
+
deliverUsageEvent(usage, source);
|
|
1032
|
+
};
|
|
1033
|
+
const runUsage = () => ({
|
|
1034
|
+
...tokenTotals,
|
|
1035
|
+
modelCalls: modelCallCount
|
|
1036
|
+
});
|
|
1365
1037
|
const warnNonSerializableContext = (snapshot) => {
|
|
1366
1038
|
if (warnedNonSerializable || process.env.NODE_ENV === "production") return;
|
|
1367
1039
|
let offending = [];
|
|
1368
1040
|
try {
|
|
1369
|
-
offending =
|
|
1041
|
+
offending = require_decision.findNonSerializableContextPaths(snapshot.context);
|
|
1370
1042
|
} catch {
|
|
1371
1043
|
return;
|
|
1372
1044
|
}
|
|
@@ -1374,8 +1046,8 @@ async function runAgent(machine, options) {
|
|
|
1374
1046
|
warnedNonSerializable = true;
|
|
1375
1047
|
console.warn(`runAgent: context holds value(s) that will not survive snapshot persist/resume (JSON round-trip): ${offending.join(", ")}. Persist only JSON-serializable context, or convert these before the run settles.`);
|
|
1376
1048
|
};
|
|
1377
|
-
const provided = machine.provide({
|
|
1378
|
-
const effectiveSources = provided.
|
|
1049
|
+
const provided = machine.provide({ actors: options.actors });
|
|
1050
|
+
const effectiveSources = provided.sources.actors;
|
|
1379
1051
|
assertBindable(provided, effectiveSources, {
|
|
1380
1052
|
hasGenerateText: !!options.executors?.generateText,
|
|
1381
1053
|
hasDecide: !!options.executors?.decide,
|
|
@@ -1391,8 +1063,10 @@ async function runAgent(machine, options) {
|
|
|
1391
1063
|
onResult: options.onResult,
|
|
1392
1064
|
onTrace,
|
|
1393
1065
|
consumeModelCall,
|
|
1066
|
+
recordUsage,
|
|
1394
1067
|
actorHolder,
|
|
1395
|
-
|
|
1068
|
+
runId,
|
|
1069
|
+
schemas: require_decision.getRegisteredAgentExecutionOptions(machine).schemas
|
|
1396
1070
|
};
|
|
1397
1071
|
let userInputIsPlaceholder = false;
|
|
1398
1072
|
const wrappedSources = {};
|
|
@@ -1401,7 +1075,7 @@ async function runAgent(machine, options) {
|
|
|
1401
1075
|
if (options.userInput) {
|
|
1402
1076
|
const userInput = options.userInput;
|
|
1403
1077
|
wrappedSources[key] = (0, xstate.createAsyncLogic)({ run: async ({ input }) => await userInput(input) });
|
|
1404
|
-
} else if (
|
|
1078
|
+
} else if (require_decision.isUnboundPlaceholder(logic)) {
|
|
1405
1079
|
userInputIsPlaceholder = true;
|
|
1406
1080
|
wrappedSources[key] = (0, xstate.createAsyncLogic)({ run: () => new Promise(() => {}) });
|
|
1407
1081
|
}
|
|
@@ -1415,8 +1089,8 @@ async function runAgent(machine, options) {
|
|
|
1415
1089
|
wrappedSources[key] = createRunAgentPlanLogic(logic, runCtx);
|
|
1416
1090
|
continue;
|
|
1417
1091
|
}
|
|
1418
|
-
if (
|
|
1419
|
-
if (!
|
|
1092
|
+
if (require_decision.isTextLogic(logic)) {
|
|
1093
|
+
if (!require_decision.executorBoundLogics.has(logic)) wrappedSources[key] = wrapTextLogicForRunAgent(logic, runCtx);
|
|
1420
1094
|
continue;
|
|
1421
1095
|
}
|
|
1422
1096
|
if (isStateMachine(logic)) {
|
|
@@ -1425,11 +1099,12 @@ async function runAgent(machine, options) {
|
|
|
1425
1099
|
continue;
|
|
1426
1100
|
}
|
|
1427
1101
|
}
|
|
1428
|
-
const boundMachine = provided.provide({
|
|
1429
|
-
const
|
|
1102
|
+
const boundMachine = provided.provide({ actors: wrappedSources });
|
|
1103
|
+
const declaredSuspensionPredicate = options.isSuspended ?? require_decision.getMachineSuspensionPredicate(machine);
|
|
1104
|
+
const isSuspended = declaredSuspensionPredicate ?? (() => false);
|
|
1430
1105
|
let effectiveSnapshot = options.snapshot;
|
|
1431
1106
|
if (effectiveSnapshot !== void 0) {
|
|
1432
|
-
const from = effectiveSnapshot.agentMeta?.version;
|
|
1107
|
+
const from = effectiveSnapshot.agentMeta?.version ?? effectiveSnapshot.version;
|
|
1433
1108
|
if (from !== void 0 && from !== machineVersion) {
|
|
1434
1109
|
const info = {
|
|
1435
1110
|
from,
|
|
@@ -1438,32 +1113,86 @@ async function runAgent(machine, options) {
|
|
|
1438
1113
|
if (options.migrateSnapshot) effectiveSnapshot = options.migrateSnapshot(effectiveSnapshot, info);
|
|
1439
1114
|
else {
|
|
1440
1115
|
const mode = options.onVersionMismatch ?? "throw";
|
|
1441
|
-
if (mode === "throw") throw new
|
|
1116
|
+
if (mode === "throw") throw new AgentSnapshotVersionMismatchError(from, machineVersion, machineId);
|
|
1442
1117
|
if (mode === "warn") console.warn(`runAgent: resuming a snapshot stamped with machine version '${from}' against machine '${machineId}' at version '${machineVersion}'. Structural changes may not resume cleanly.`);
|
|
1443
1118
|
}
|
|
1444
1119
|
}
|
|
1445
1120
|
}
|
|
1446
|
-
const
|
|
1121
|
+
const resumeEvents = options.events;
|
|
1122
|
+
if (effectiveSnapshot === void 0 && options.events !== void 0 && options.events[0]?.event.type === "@agent.init") {
|
|
1123
|
+
const { snapshot: replayedSnapshot } = require_setup_agent.replay(machine, resumeEvents, { machineVersion });
|
|
1124
|
+
effectiveSnapshot = machine.getPersistedSnapshot(replayedSnapshot);
|
|
1125
|
+
}
|
|
1126
|
+
const machineOwnVersion = machine.version;
|
|
1127
|
+
if (effectiveSnapshot !== void 0 && effectiveSnapshot.version !== machineOwnVersion) {
|
|
1128
|
+
const aligned = Object.assign(Object.create(Object.getPrototypeOf(effectiveSnapshot)), effectiveSnapshot);
|
|
1129
|
+
if (machineOwnVersion === void 0) delete aligned.version;
|
|
1130
|
+
else aligned.version = machineOwnVersion;
|
|
1131
|
+
effectiveSnapshot = aligned;
|
|
1132
|
+
}
|
|
1133
|
+
const priorMessages = require_decision.getAgentMessages(effectiveSnapshot);
|
|
1447
1134
|
const messages = typeof options.messages === "function" ? [...options.messages([...priorMessages])] : [...priorMessages, ...options.messages ?? []];
|
|
1448
1135
|
const stampMessages = (snapshot) => {
|
|
1449
1136
|
if (!options.getRequests && !options.messages && messages.length === 0) return;
|
|
1450
1137
|
if (snapshot && typeof snapshot === "object") snapshot.messages = [...messages];
|
|
1451
1138
|
};
|
|
1452
1139
|
if (effectiveSnapshot !== void 0 && options.event !== void 0 && (options.onIllegalResumeEvent ?? "throw") === "throw") {
|
|
1453
|
-
const acceptedTypes =
|
|
1140
|
+
const acceptedTypes = require_decision.getAcceptedEvents((0, xstate.createActor)(boundMachine, { snapshot: effectiveSnapshot }).getSnapshot(), { schemas: runCtx.schemas }).map((descriptor) => descriptor.type);
|
|
1454
1141
|
const eventType = options.event.type;
|
|
1455
|
-
if (!acceptedTypes.includes(eventType)) throw new
|
|
1142
|
+
if (!acceptedTypes.includes(eventType)) throw new AgentIllegalResumeEventError(eventType, acceptedTypes);
|
|
1143
|
+
}
|
|
1144
|
+
const replayEvents = [...resumeEvents ?? []];
|
|
1145
|
+
const replayEventIds = /* @__PURE__ */ new Set();
|
|
1146
|
+
for (let index = 0; index < replayEvents.length; index++) {
|
|
1147
|
+
const entry = replayEvents[index];
|
|
1148
|
+
require_event_log_store.assertAgentLogEntry(entry);
|
|
1149
|
+
if (entry.index !== index) throw new Error(`runAgent events must be contiguous from index 0; found entry.index ${entry.index} at position ${index}.`);
|
|
1150
|
+
if (entry.machineId !== machineId || entry.machineVersion !== machineVersion) throw new require_setup_agent.AgentReplayMachineMismatchError(entry.id, entry.index, {
|
|
1151
|
+
machineId,
|
|
1152
|
+
machineVersion
|
|
1153
|
+
}, {
|
|
1154
|
+
machineId: entry.machineId,
|
|
1155
|
+
machineVersion: entry.machineVersion
|
|
1156
|
+
});
|
|
1157
|
+
if (replayEventIds.has(entry.id)) throw new Error(`runAgent events contain duplicate event id '${entry.id}'.`);
|
|
1158
|
+
replayEventIds.add(entry.id);
|
|
1456
1159
|
}
|
|
1457
|
-
|
|
1160
|
+
const hasCompleteReplayHistory = replayEvents[0]?.event.type === "@agent.init" || effectiveSnapshot === void 0 && replayEvents.length === 0;
|
|
1161
|
+
const appendReplayEvent = (event) => {
|
|
1162
|
+
const entry = require_setup_agent.createReplayEntry(machine, replayEvents, event, {
|
|
1163
|
+
machineVersion,
|
|
1164
|
+
verification: hasCompleteReplayHistory
|
|
1165
|
+
});
|
|
1166
|
+
if (replayEventIds.has(entry.id)) throw new Error(`runAgent generated duplicate event id '${entry.id}'.`);
|
|
1167
|
+
replayEventIds.add(entry.id);
|
|
1168
|
+
replayEvents.push(entry);
|
|
1169
|
+
options.onEvent?.(entry);
|
|
1170
|
+
return entry;
|
|
1171
|
+
};
|
|
1172
|
+
if (replayEvents.length === 0 && effectiveSnapshot === void 0) {
|
|
1173
|
+
const entry = require_setup_agent.initEntry(machine, options.input, { machineVersion });
|
|
1174
|
+
replayEventIds.add(entry.id);
|
|
1175
|
+
replayEvents.push(entry);
|
|
1176
|
+
options.onEvent?.(entry);
|
|
1177
|
+
}
|
|
1178
|
+
return (() => {
|
|
1458
1179
|
let settled = false;
|
|
1180
|
+
let finalized = false;
|
|
1181
|
+
cycleGate.isResolved = () => settled;
|
|
1182
|
+
let lastResult;
|
|
1183
|
+
const waiters = [];
|
|
1459
1184
|
let idleTimer;
|
|
1460
1185
|
let actor;
|
|
1461
1186
|
let deliveringResumeEvent = options.event !== void 0;
|
|
1462
|
-
const settle = (
|
|
1187
|
+
const settle = (outcome) => {
|
|
1463
1188
|
if (settled) return;
|
|
1464
1189
|
settled = true;
|
|
1190
|
+
const result = {
|
|
1191
|
+
...outcome,
|
|
1192
|
+
events: [...replayEvents],
|
|
1193
|
+
usage: runUsage()
|
|
1194
|
+
};
|
|
1465
1195
|
if (idleTimer !== void 0) clearTimeout(idleTimer);
|
|
1466
|
-
if (options.signal) options.signal.removeEventListener("abort", onAbort);
|
|
1467
1196
|
stampAgentMeta(result.snapshot);
|
|
1468
1197
|
stampMessages(result.snapshot);
|
|
1469
1198
|
if ("persistedSnapshot" in result) {
|
|
@@ -1472,10 +1201,15 @@ async function runAgent(machine, options) {
|
|
|
1472
1201
|
}
|
|
1473
1202
|
onTrace({
|
|
1474
1203
|
type: "run.end",
|
|
1475
|
-
...
|
|
1204
|
+
...outcome
|
|
1476
1205
|
});
|
|
1477
|
-
|
|
1478
|
-
|
|
1206
|
+
if (lifecycle.oneShot || result.status !== "idle") {
|
|
1207
|
+
finalized = true;
|
|
1208
|
+
if (options.signal) options.signal.removeEventListener("abort", onAbort);
|
|
1209
|
+
actor.stop();
|
|
1210
|
+
}
|
|
1211
|
+
lastResult = result;
|
|
1212
|
+
for (const resolve of waiters.splice(0)) resolve(result);
|
|
1479
1213
|
};
|
|
1480
1214
|
const onAbort = () => {
|
|
1481
1215
|
settle({
|
|
@@ -1528,6 +1262,7 @@ async function runAgent(machine, options) {
|
|
|
1528
1262
|
generateText: runCtx.generateText,
|
|
1529
1263
|
decide: runCtx.decide ? createCountingDecide(runCtx, void 0) : void 0,
|
|
1530
1264
|
consumeModelCall,
|
|
1265
|
+
recordUsage,
|
|
1531
1266
|
nextRequestId: () => `interpret_${++interpretSeq}`,
|
|
1532
1267
|
onTrace,
|
|
1533
1268
|
onResult: runCtx.onResult,
|
|
@@ -1564,7 +1299,13 @@ async function runAgent(machine, options) {
|
|
|
1564
1299
|
if (settled) return;
|
|
1565
1300
|
const current = actor.getSnapshot();
|
|
1566
1301
|
if (isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) {
|
|
1567
|
-
if (!maybeInterpret(current))
|
|
1302
|
+
if (!maybeInterpret(current)) {
|
|
1303
|
+
if (!declaredSuspensionPredicate && current.status === "active" && !warnedHeuristicIdle && process.env.NODE_ENV !== "production") {
|
|
1304
|
+
warnedHeuristicIdle = true;
|
|
1305
|
+
console.warn("[@statelyai/agent] runAgent settled idle via the timing heuristic (no suspension predicate declared). This is best-effort; for deterministic idle detection, declare setupAgent({ isSuspended }) or pass runAgent(machine, { isSuspended }), e.g. (s) => s.hasTag('waiting').");
|
|
1306
|
+
}
|
|
1307
|
+
settleIdle(current);
|
|
1308
|
+
}
|
|
1568
1309
|
}
|
|
1569
1310
|
}, 0);
|
|
1570
1311
|
};
|
|
@@ -1574,12 +1315,21 @@ async function runAgent(machine, options) {
|
|
|
1574
1315
|
inspect: (event) => {
|
|
1575
1316
|
if (typeof options.inspect === "function") options.inspect(event);
|
|
1576
1317
|
else options.inspect?.next?.(event);
|
|
1577
|
-
if (
|
|
1318
|
+
if (event.type !== "@xstate.transition" || event.actorRef !== actor.ref) return;
|
|
1319
|
+
if (settled) {
|
|
1320
|
+
if (finalized) return;
|
|
1321
|
+
settled = false;
|
|
1322
|
+
lastResult = void 0;
|
|
1323
|
+
}
|
|
1578
1324
|
const snapshot = event.snapshot;
|
|
1325
|
+
let eventId;
|
|
1326
|
+
if (event.event.type !== "@xstate.init" && (event.sourceRef !== event.actorRef || event.event.type === "xstate.timer")) eventId = appendReplayEvent(event.event).id;
|
|
1327
|
+
else if (event.event.type === "@xstate.init") eventId = replayEvents[0]?.event.type === "@agent.init" ? replayEvents[0].id : void 0;
|
|
1579
1328
|
onTrace({
|
|
1580
1329
|
type: "machine.transition",
|
|
1581
1330
|
snapshot,
|
|
1582
|
-
event: event.event
|
|
1331
|
+
event: event.event,
|
|
1332
|
+
...eventId !== void 0 ? { eventId } : {}
|
|
1583
1333
|
});
|
|
1584
1334
|
options.onTransition?.(snapshot, event.event);
|
|
1585
1335
|
if (snapshot.status === "done") {
|
|
@@ -1624,6 +1374,19 @@ async function runAgent(machine, options) {
|
|
|
1624
1374
|
});
|
|
1625
1375
|
});
|
|
1626
1376
|
for (const [type, handler] of Object.entries(options.on ?? {})) if (typeof handler === "function") actor.on(type, handler);
|
|
1377
|
+
const sessionApi = {
|
|
1378
|
+
actor,
|
|
1379
|
+
get events() {
|
|
1380
|
+
return replayEvents;
|
|
1381
|
+
},
|
|
1382
|
+
usage: runUsage,
|
|
1383
|
+
settled: () => settled && lastResult !== void 0 ? Promise.resolve(lastResult) : new Promise((resolve) => {
|
|
1384
|
+
waiters.push(resolve);
|
|
1385
|
+
}),
|
|
1386
|
+
stop: () => {
|
|
1387
|
+
actor.stop();
|
|
1388
|
+
}
|
|
1389
|
+
};
|
|
1627
1390
|
if (options.signal) {
|
|
1628
1391
|
if (options.signal.aborted) {
|
|
1629
1392
|
settle({
|
|
@@ -1632,7 +1395,7 @@ async function runAgent(machine, options) {
|
|
|
1632
1395
|
error: options.signal.reason ?? /* @__PURE__ */ new Error("Aborted"),
|
|
1633
1396
|
snapshot: actor.getSnapshot()
|
|
1634
1397
|
});
|
|
1635
|
-
return;
|
|
1398
|
+
return sessionApi;
|
|
1636
1399
|
}
|
|
1637
1400
|
options.signal.addEventListener("abort", onAbort);
|
|
1638
1401
|
}
|
|
@@ -1647,13 +1410,17 @@ async function runAgent(machine, options) {
|
|
|
1647
1410
|
deliveringResumeEvent = false;
|
|
1648
1411
|
actor.send(options.event);
|
|
1649
1412
|
}
|
|
1650
|
-
|
|
1413
|
+
return sessionApi;
|
|
1414
|
+
})();
|
|
1651
1415
|
}
|
|
1652
1416
|
/**
|
|
1653
|
-
* Runs an agent machine to a **final state
|
|
1654
|
-
*
|
|
1417
|
+
* Runs an agent machine to a **final state**, for run-to-done flows where an
|
|
1418
|
+
* idle pause is unexpected. Wraps {@link runAgent}:
|
|
1655
1419
|
*
|
|
1656
|
-
* - `done` → resolves with `result.output` (the machine's
|
|
1420
|
+
* - `done` → resolves with the done result: `result.output` (the machine's
|
|
1421
|
+
* `OutputFrom`) plus metadata — `result.snapshot`, the replayable
|
|
1422
|
+
* `result.events`, and the aggregated `result.usage` — the same shape
|
|
1423
|
+
* `generateText` users expect (`text` + call metadata).
|
|
1657
1424
|
* - `idle` → throws {@link AgentIdleError} carrying the idle snapshot and the
|
|
1658
1425
|
* event types that could resume it.
|
|
1659
1426
|
* - `error` → throws `result.error` when it is an `Error`; otherwise wraps it
|
|
@@ -1661,18 +1428,18 @@ async function runAgent(machine, options) {
|
|
|
1661
1428
|
* `.error` is the raw thrown value.
|
|
1662
1429
|
*
|
|
1663
1430
|
* Use {@link runAgent} directly when idle is an expected outcome you handle
|
|
1664
|
-
* (human-in-the-loop, resumable flows); use `
|
|
1431
|
+
* (human-in-the-loop, resumable flows); use `generateResult` when the
|
|
1665
1432
|
* machine is meant to run straight through to a final state.
|
|
1666
1433
|
*/
|
|
1667
|
-
async function
|
|
1434
|
+
async function generateResult(machine, options) {
|
|
1668
1435
|
const result = await runAgent(machine, options);
|
|
1669
|
-
if (result.status === "done") return result
|
|
1436
|
+
if (result.status === "done") return result;
|
|
1670
1437
|
if (result.status === "idle") {
|
|
1671
|
-
const acceptedTypes =
|
|
1438
|
+
const acceptedTypes = require_decision.getAcceptedEvents(result.snapshot, { schemas: require_decision.getRegisteredAgentExecutionOptions(machine).schemas }).map((descriptor) => descriptor.type);
|
|
1672
1439
|
throw new AgentIdleError(result.snapshot, acceptedTypes);
|
|
1673
1440
|
}
|
|
1674
1441
|
if (result.error instanceof Error) throw result.error;
|
|
1675
|
-
const wrapped = /* @__PURE__ */ new Error(`
|
|
1442
|
+
const wrapped = /* @__PURE__ */ new Error(`generateResult: run failed with cause '${result.cause}'.`);
|
|
1676
1443
|
wrapped.cause = result.cause;
|
|
1677
1444
|
wrapped.error = result.error;
|
|
1678
1445
|
throw wrapped;
|
|
@@ -1751,6 +1518,105 @@ function collectPendingUserInputs(snapshot) {
|
|
|
1751
1518
|
return pending;
|
|
1752
1519
|
}
|
|
1753
1520
|
//#endregion
|
|
1521
|
+
//#region src/agent-run.ts
|
|
1522
|
+
/**
|
|
1523
|
+
* Wraps {@link runAgent} in a canonical run-stream handle: the run's trace
|
|
1524
|
+
* events as a pull-based {@link AgentRun.events} async iterator, alongside the
|
|
1525
|
+
* unchanged {@link AgentRun.result} promise.
|
|
1526
|
+
*
|
|
1527
|
+
* Semantics:
|
|
1528
|
+
*
|
|
1529
|
+
* 1. **Starts immediately.** The underlying `runAgent` is invoked on this call,
|
|
1530
|
+
* not on the first `events` iteration — work is already in flight when
|
|
1531
|
+
* `createAgentRun` returns.
|
|
1532
|
+
* 2. **Buffer, don't stall.** Events are queued in an unbounded in-memory
|
|
1533
|
+
* buffer as the run emits them; a slow or absent consumer never applies
|
|
1534
|
+
* backpressure to the run. Once `run.end` is delivered, the iterator
|
|
1535
|
+
* completes.
|
|
1536
|
+
* 3. **Composes `onTrace`.** If `options.onTrace` is passed, it still fires for
|
|
1537
|
+
* every event — the wrapper adds queue delivery, it does not replace the
|
|
1538
|
+
* caller's sink.
|
|
1539
|
+
* 4. **`result` mirrors `runAgent`.** Same resolution and rejection behavior
|
|
1540
|
+
* (see {@link AgentRun.result}). A no-op rejection handler is attached
|
|
1541
|
+
* internally so a caller who reads only `events` never trips an unhandled
|
|
1542
|
+
* rejection on a bind-time throw; the returned promise still rejects for a
|
|
1543
|
+
* caller who awaits it.
|
|
1544
|
+
* 5. **Resume is identical.** Options pass straight through, so starting from a
|
|
1545
|
+
* persisted `snapshot` (+ resume `event`) streams that run's events from its
|
|
1546
|
+
* own `run.start`, exactly as `runAgent` would run it.
|
|
1547
|
+
* 6. **Early termination does not cancel the run.** Breaking out of a
|
|
1548
|
+
* `for await` (or calling `events.return()`) stops delivery, but the run
|
|
1549
|
+
* keeps going and `result` still settles. Cancelling the run itself is
|
|
1550
|
+
* future work — pass `options.signal` to abort it.
|
|
1551
|
+
* 7. **`events` is single-consumer.** See {@link AgentRun.events}.
|
|
1552
|
+
*/
|
|
1553
|
+
function createAgentRun(machine, options) {
|
|
1554
|
+
const buffer = [];
|
|
1555
|
+
const resolvers = [];
|
|
1556
|
+
let closed = false;
|
|
1557
|
+
let stopped = false;
|
|
1558
|
+
const push = (event) => {
|
|
1559
|
+
if (closed || stopped) return;
|
|
1560
|
+
const resolve = resolvers.shift();
|
|
1561
|
+
if (resolve) resolve({
|
|
1562
|
+
value: event,
|
|
1563
|
+
done: false
|
|
1564
|
+
});
|
|
1565
|
+
else buffer.push(event);
|
|
1566
|
+
};
|
|
1567
|
+
const close = () => {
|
|
1568
|
+
if (closed) return;
|
|
1569
|
+
closed = true;
|
|
1570
|
+
while (resolvers.length > 0) resolvers.shift()({
|
|
1571
|
+
value: void 0,
|
|
1572
|
+
done: true
|
|
1573
|
+
});
|
|
1574
|
+
};
|
|
1575
|
+
const userOnTrace = options.onTrace;
|
|
1576
|
+
const result = runAgent(machine, {
|
|
1577
|
+
...options,
|
|
1578
|
+
onTrace: (event) => {
|
|
1579
|
+
push(event);
|
|
1580
|
+
userOnTrace?.(event);
|
|
1581
|
+
if (event.type === "run.end") close();
|
|
1582
|
+
}
|
|
1583
|
+
});
|
|
1584
|
+
result.then(close, close);
|
|
1585
|
+
return {
|
|
1586
|
+
events: {
|
|
1587
|
+
next() {
|
|
1588
|
+
if (buffer.length > 0) return Promise.resolve({
|
|
1589
|
+
value: buffer.shift(),
|
|
1590
|
+
done: false
|
|
1591
|
+
});
|
|
1592
|
+
if (closed || stopped) return Promise.resolve({
|
|
1593
|
+
value: void 0,
|
|
1594
|
+
done: true
|
|
1595
|
+
});
|
|
1596
|
+
return new Promise((resolve) => {
|
|
1597
|
+
resolvers.push(resolve);
|
|
1598
|
+
});
|
|
1599
|
+
},
|
|
1600
|
+
return() {
|
|
1601
|
+
stopped = true;
|
|
1602
|
+
buffer.length = 0;
|
|
1603
|
+
while (resolvers.length > 0) resolvers.shift()({
|
|
1604
|
+
value: void 0,
|
|
1605
|
+
done: true
|
|
1606
|
+
});
|
|
1607
|
+
return Promise.resolve({
|
|
1608
|
+
value: void 0,
|
|
1609
|
+
done: true
|
|
1610
|
+
});
|
|
1611
|
+
},
|
|
1612
|
+
[Symbol.asyncIterator]() {
|
|
1613
|
+
return this;
|
|
1614
|
+
}
|
|
1615
|
+
},
|
|
1616
|
+
result
|
|
1617
|
+
};
|
|
1618
|
+
}
|
|
1619
|
+
//#endregion
|
|
1754
1620
|
//#region src/provide-executors.ts
|
|
1755
1621
|
/**
|
|
1756
1622
|
* Binds a machine's agent actor sources to a set of host `executors` in one
|
|
@@ -1765,8 +1631,8 @@ function collectPendingUserInputs(snapshot) {
|
|
|
1765
1631
|
*
|
|
1766
1632
|
* behaves like a normal XState actor whose agent invokes now reach real models.
|
|
1767
1633
|
*
|
|
1768
|
-
* Binding pass over `machine.
|
|
1769
|
-
* `options.
|
|
1634
|
+
* Binding pass over `machine.sources.actors` (after merging
|
|
1635
|
+
* `options.actors`):
|
|
1770
1636
|
* - `mode: 'generate'` text source → `executors.generateText`
|
|
1771
1637
|
* - `mode: 'stream'` text source → `executors.streamText`
|
|
1772
1638
|
* - decision / `agent.decide` source → `executors.decide` (snapshot-driven
|
|
@@ -1780,7 +1646,7 @@ function collectPendingUserInputs(snapshot) {
|
|
|
1780
1646
|
*
|
|
1781
1647
|
* A source that already carries its own executor (`.withExecutor(...)`) is left
|
|
1782
1648
|
* as-is. `agent.userInput` is left UNBOUND — an uncontrolled host handles idle
|
|
1783
|
-
* itself, so supply a handler via `options.
|
|
1649
|
+
* itself, so supply a handler via `options.actors` if the machine uses it.
|
|
1784
1650
|
* Non-agent actors are untouched.
|
|
1785
1651
|
*
|
|
1786
1652
|
* Throws at bind time if a source needs an executor kind that `executors` does
|
|
@@ -1795,14 +1661,14 @@ function provideExecutors(machine, executors, options = {}) {
|
|
|
1795
1661
|
onChunk: options.onChunk,
|
|
1796
1662
|
onTrace: options.onTrace
|
|
1797
1663
|
};
|
|
1798
|
-
const provided = options.
|
|
1799
|
-
const effectiveSources = provided.
|
|
1664
|
+
const provided = options.actors ? machine.provide({ actors: options.actors }) : machine;
|
|
1665
|
+
const effectiveSources = provided.sources.actors;
|
|
1800
1666
|
const wrappedSources = {};
|
|
1801
1667
|
const invokedSrcs = getConfiguredInvokeSrcs(provided);
|
|
1802
1668
|
for (const [key, logic] of Object.entries(effectiveSources)) {
|
|
1803
1669
|
if (key === "agent.userInput") continue;
|
|
1804
1670
|
if (require_decision.isDecisionLogic(logic)) {
|
|
1805
|
-
if (
|
|
1671
|
+
if (require_decision.executorBoundLogics.has(logic)) continue;
|
|
1806
1672
|
if (!executors.decide) {
|
|
1807
1673
|
if (invokedSrcs.has(key)) throw missingExecutorError(key, "decision", "decide");
|
|
1808
1674
|
continue;
|
|
@@ -1818,8 +1684,8 @@ function provideExecutors(machine, executors, options = {}) {
|
|
|
1818
1684
|
wrappedSources[key] = bindPlanForProvide(provided, logic, executors, bindOptions);
|
|
1819
1685
|
continue;
|
|
1820
1686
|
}
|
|
1821
|
-
if (
|
|
1822
|
-
if (
|
|
1687
|
+
if (require_decision.isTextLogic(logic)) {
|
|
1688
|
+
if (require_decision.executorBoundLogics.has(logic)) continue;
|
|
1823
1689
|
if (!(logic.mode === "stream" ? executors.streamText : executors.generateText)) {
|
|
1824
1690
|
if (invokedSrcs.has(key)) throw logic.mode === "stream" ? missingExecutorError(key, "streaming text", "streamText") : missingExecutorError(key, "text", "generateText");
|
|
1825
1691
|
continue;
|
|
@@ -1828,7 +1694,7 @@ function provideExecutors(machine, executors, options = {}) {
|
|
|
1828
1694
|
continue;
|
|
1829
1695
|
}
|
|
1830
1696
|
}
|
|
1831
|
-
return provided.provide({
|
|
1697
|
+
return provided.provide({ actors: wrappedSources });
|
|
1832
1698
|
}
|
|
1833
1699
|
function missingExecutorError(src, kind, executor) {
|
|
1834
1700
|
return /* @__PURE__ */ new Error(`provideExecutors: actor source '${src}' is a ${kind} source but no '${executor}' executor was provided. Add it to the executors object, or bind the source with its own executor (logic.withExecutor(...)) before calling provideExecutors.`);
|
|
@@ -1884,8 +1750,15 @@ function collectTransitionTargets(value, fromNode, index, out) {
|
|
|
1884
1750
|
return;
|
|
1885
1751
|
}
|
|
1886
1752
|
if (typeof value === "object") {
|
|
1887
|
-
const target = value
|
|
1888
|
-
if (target !== void 0)
|
|
1753
|
+
const { target, to } = value;
|
|
1754
|
+
if (target !== void 0) {
|
|
1755
|
+
collectTransitionTargets(target, fromNode, index, out);
|
|
1756
|
+
return;
|
|
1757
|
+
}
|
|
1758
|
+
if (to !== void 0) {
|
|
1759
|
+
collectTransitionTargets(to, fromNode, index, out);
|
|
1760
|
+
return;
|
|
1761
|
+
}
|
|
1889
1762
|
}
|
|
1890
1763
|
}
|
|
1891
1764
|
function resolveTargetString(target, fromNode, index, out) {
|
|
@@ -1897,12 +1770,16 @@ function resolveTargetString(target, fromNode, index, out) {
|
|
|
1897
1770
|
if (index.has(resolved)) out.targets.push(resolved);
|
|
1898
1771
|
else out.opaque = true;
|
|
1899
1772
|
}
|
|
1900
|
-
function outgoingTargets(node, index) {
|
|
1773
|
+
function outgoingTargets(node, index, staticTargets) {
|
|
1901
1774
|
const out = {
|
|
1902
1775
|
targets: [],
|
|
1903
1776
|
opaque: false
|
|
1904
1777
|
};
|
|
1905
1778
|
const { config } = node;
|
|
1779
|
+
if (staticTargets) {
|
|
1780
|
+
for (const target of staticTargets[node.path] ?? []) resolveTargetString(target, node, index, out);
|
|
1781
|
+
return out;
|
|
1782
|
+
}
|
|
1906
1783
|
for (const value of Object.values(config.on ?? {})) collectTransitionTargets(value, node, index, out);
|
|
1907
1784
|
collectTransitionTargets(config.always, node, index, out);
|
|
1908
1785
|
collectTransitionTargets(config.choice, node, index, out);
|
|
@@ -1914,7 +1791,7 @@ function outgoingTargets(node, index) {
|
|
|
1914
1791
|
}
|
|
1915
1792
|
return out;
|
|
1916
1793
|
}
|
|
1917
|
-
function computeReachable(rootConfig, index) {
|
|
1794
|
+
function computeReachable(rootConfig, index, staticTargets) {
|
|
1918
1795
|
const reachable = /* @__PURE__ */ new Set();
|
|
1919
1796
|
const queue = [];
|
|
1920
1797
|
const markAncestors = (path) => {
|
|
@@ -1939,7 +1816,7 @@ function computeReachable(rootConfig, index) {
|
|
|
1939
1816
|
while (queue.length > 0) {
|
|
1940
1817
|
const node = index.get(queue.shift());
|
|
1941
1818
|
if (!node) continue;
|
|
1942
|
-
const { targets, opaque } = outgoingTargets(node, index);
|
|
1819
|
+
const { targets, opaque } = outgoingTargets(node, index, staticTargets);
|
|
1943
1820
|
for (const target of targets) enter(target);
|
|
1944
1821
|
if (opaque) for (const sibling of childrenOf(index, node.parentPath)) enter(sibling.path);
|
|
1945
1822
|
}
|
|
@@ -1959,11 +1836,11 @@ function ancestorChain(node, index) {
|
|
|
1959
1836
|
function hasNonEmptyOn(config) {
|
|
1960
1837
|
return !!config.on && Object.keys(config.on).length > 0;
|
|
1961
1838
|
}
|
|
1962
|
-
function decisionKindOf(src,
|
|
1839
|
+
function decisionKindOf(src, actors) {
|
|
1963
1840
|
if (typeof src === "string") {
|
|
1964
1841
|
if (src === DECIDE_SRC) return "decision";
|
|
1965
1842
|
if (src === PLAN_SRC) return "plan";
|
|
1966
|
-
const logic =
|
|
1843
|
+
const logic = actors[src];
|
|
1967
1844
|
if (require_decision.isDecisionLogic(logic)) return "decision";
|
|
1968
1845
|
if (require_decision.isPlanLogic(logic)) return "plan";
|
|
1969
1846
|
return;
|
|
@@ -1972,12 +1849,12 @@ function decisionKindOf(src, actorSources) {
|
|
|
1972
1849
|
if (require_decision.isPlanLogic(src)) return "plan";
|
|
1973
1850
|
}
|
|
1974
1851
|
function isAgentLogicNeedingBinding(src) {
|
|
1975
|
-
return (
|
|
1852
|
+
return (require_decision.isTextLogic(src) || require_decision.isDecisionLogic(src) || require_decision.isPlanLogic(src)) && !require_decision.executorBoundLogics.has(src);
|
|
1976
1853
|
}
|
|
1977
1854
|
function schemaExposesJson(schema) {
|
|
1978
1855
|
if (!schema) return false;
|
|
1979
1856
|
try {
|
|
1980
|
-
return
|
|
1857
|
+
return require_decision.getJsonSchemaSync(schema) !== void 0;
|
|
1981
1858
|
} catch {
|
|
1982
1859
|
return false;
|
|
1983
1860
|
}
|
|
@@ -1986,7 +1863,7 @@ function isDeclaredOutputSchema(schema) {
|
|
|
1986
1863
|
if (!schema) return false;
|
|
1987
1864
|
let json;
|
|
1988
1865
|
try {
|
|
1989
|
-
json =
|
|
1866
|
+
json = require_decision.getJsonSchemaSync(schema);
|
|
1990
1867
|
} catch {
|
|
1991
1868
|
return false;
|
|
1992
1869
|
}
|
|
@@ -2008,7 +1885,7 @@ function checkUnreachableStates(ctx) {
|
|
|
2008
1885
|
function checkDecideWithoutEvents(ctx) {
|
|
2009
1886
|
const out = [];
|
|
2010
1887
|
for (const node of ctx.index.values()) for (const invoke of node.invokes) {
|
|
2011
|
-
const kind = decisionKindOf(invoke.src, ctx.
|
|
1888
|
+
const kind = decisionKindOf(invoke.src, ctx.actors);
|
|
2012
1889
|
if (!kind) continue;
|
|
2013
1890
|
const selfHandles = hasNonEmptyOn(node.config);
|
|
2014
1891
|
const ancestorHandles = ancestorChain(node, ctx.index).some((ancestor) => hasNonEmptyOn(ancestor.config));
|
|
@@ -2045,7 +1922,7 @@ function checkDirectObjectSrc(ctx) {
|
|
|
2045
1922
|
code: "direct-object-src",
|
|
2046
1923
|
severity: "warning",
|
|
2047
1924
|
path: node.path,
|
|
2048
|
-
message: `State '${node.path}' invokes a direct-object agent logic. Direct-object invoke srcs cannot be rebound by runAgent, so they inherit no host executors — call '.withExecutor(...)' on the logic, or register it as a string-keyed actor source (machine.provide({
|
|
1925
|
+
message: `State '${node.path}' invokes a direct-object agent logic. Direct-object invoke srcs cannot be rebound by runAgent, so they inherit no host executors — call '.withExecutor(...)' on the logic, or register it as a string-keyed actor source (machine.provide({ actors: { name: logic } })) and invoke it by name.`
|
|
2049
1926
|
});
|
|
2050
1927
|
}
|
|
2051
1928
|
return out;
|
|
@@ -2089,8 +1966,8 @@ function isBuiltinOrWildcardEvent(eventType) {
|
|
|
2089
1966
|
}
|
|
2090
1967
|
function checkUndeclaredEvents(ctx) {
|
|
2091
1968
|
const declared = ctx.schemas?.events;
|
|
2092
|
-
|
|
2093
|
-
|
|
1969
|
+
const declaredTypes = new Set(Object.keys(declared ?? {}));
|
|
1970
|
+
if ([...declaredTypes].every((type) => type.startsWith("@agent."))) return [];
|
|
2094
1971
|
const out = [];
|
|
2095
1972
|
for (const node of ctx.index.values()) for (const eventType of Object.keys(node.config.on ?? {})) {
|
|
2096
1973
|
if (declaredTypes.has(eventType) || isBuiltinOrWildcardEvent(eventType)) continue;
|
|
@@ -2142,15 +2019,15 @@ const LINT_CHECKS = [
|
|
|
2142
2019
|
function lintAgentMachine(machine, options = {}) {
|
|
2143
2020
|
const config = machine.config ?? {};
|
|
2144
2021
|
const index = buildStateIndex(config);
|
|
2145
|
-
const reachable = computeReachable(config, index);
|
|
2146
|
-
const registered =
|
|
2022
|
+
const reachable = computeReachable(config, index, require_decision.getMachineStaticTransitionTargets(machine));
|
|
2023
|
+
const registered = require_decision.getRegisteredAgentExecutionOptions(machine);
|
|
2147
2024
|
const ctx = {
|
|
2148
2025
|
machine,
|
|
2149
2026
|
config,
|
|
2150
2027
|
index,
|
|
2151
2028
|
reachable,
|
|
2152
2029
|
schemas: registered.schemas,
|
|
2153
|
-
|
|
2030
|
+
actors: registered.actors ?? machine.sources?.actors ?? {}
|
|
2154
2031
|
};
|
|
2155
2032
|
const disabled = new Set(options.disable ?? []);
|
|
2156
2033
|
return LINT_CHECKS.flatMap((check) => check(ctx)).filter((d) => !disabled.has(d.code));
|
|
@@ -2160,11 +2037,11 @@ function lintAgentMachine(machine, options = {}) {
|
|
|
2160
2037
|
* `diagnostics` holds the findings; the message lists them one per finding,
|
|
2161
2038
|
* so a test runner's failure output reads like the CLI's lint report.
|
|
2162
2039
|
*/
|
|
2163
|
-
var AgentLintError = class extends
|
|
2040
|
+
var AgentLintError = class extends require_errors.AgentError {
|
|
2164
2041
|
diagnostics;
|
|
2165
2042
|
constructor(machineId, diagnostics) {
|
|
2166
2043
|
const lines = diagnostics.map((d) => ` ${d.severity === "error" ? "error" : "warn "} ${d.code} ${d.path}\n ${d.message}`);
|
|
2167
|
-
super(`Agent machine '${machineId}' failed lint (${diagnostics.length} finding(s)):\n${lines.join("\n")}`);
|
|
2044
|
+
super("lint-failed", `Agent machine '${machineId}' failed lint (${diagnostics.length} finding(s)):\n${lines.join("\n")}`);
|
|
2168
2045
|
this.name = "AgentLintError";
|
|
2169
2046
|
this.diagnostics = diagnostics;
|
|
2170
2047
|
}
|
|
@@ -2190,7 +2067,7 @@ function assertAgentMachine(machine, options = {}) {
|
|
|
2190
2067
|
function pendingInvokes(step) {
|
|
2191
2068
|
const out = [];
|
|
2192
2069
|
for (const action of step.actions) {
|
|
2193
|
-
const metadata =
|
|
2070
|
+
const metadata = require_setup_agent.getInvokeEffectMetadata(action);
|
|
2194
2071
|
if (typeof metadata?.src === "string" && typeof metadata.id === "string") out.push({
|
|
2195
2072
|
id: metadata.id,
|
|
2196
2073
|
src: metadata.src
|
|
@@ -2232,7 +2109,7 @@ async function simulateAgent(machine, options) {
|
|
|
2232
2109
|
decisions: mapValues(options.script.decisions ?? {}, (arr) => [...arr]),
|
|
2233
2110
|
invokes: mapValues(options.script.invokes ?? {}, (arr) => [...arr])
|
|
2234
2111
|
};
|
|
2235
|
-
let step =
|
|
2112
|
+
let step = require_setup_agent.initialAgentStep(machine, options.input);
|
|
2236
2113
|
const trail = [];
|
|
2237
2114
|
for (let i = 0; i < maxSteps; i++) {
|
|
2238
2115
|
if (step.done) return {
|
|
@@ -2246,7 +2123,7 @@ async function simulateAgent(machine, options) {
|
|
|
2246
2123
|
const decisionSrc = new Map(pendingInvokes(step).map((invoke) => [invoke.id, invoke.src])).get(request.id) ?? request.id;
|
|
2247
2124
|
const taken = takeFromQueue(script.decisions, decisionSrc);
|
|
2248
2125
|
if (!taken.found) throw scriptDryError("decision", decisionSrc, request.id, request);
|
|
2249
|
-
step =
|
|
2126
|
+
step = require_setup_agent.transitionAgentStep(machine, step, taken.value);
|
|
2250
2127
|
trail.push({
|
|
2251
2128
|
state: step.snapshot.value,
|
|
2252
2129
|
appliedEvent: taken.value
|
|
@@ -2257,7 +2134,7 @@ async function simulateAgent(machine, options) {
|
|
|
2257
2134
|
const taken = takeFromQueue(script.decisions, request.src);
|
|
2258
2135
|
if (!taken.found) throw scriptDryError("decision", request.src, request.id, request);
|
|
2259
2136
|
const event = taken.value;
|
|
2260
|
-
step = await
|
|
2137
|
+
step = await require_setup_agent.resolveAgentRequests(machine, step, { decide: async () => ({ event }) });
|
|
2261
2138
|
trail.push({
|
|
2262
2139
|
state: step.snapshot.value,
|
|
2263
2140
|
appliedEvent: taken.value
|
|
@@ -2266,7 +2143,7 @@ async function simulateAgent(machine, options) {
|
|
|
2266
2143
|
}
|
|
2267
2144
|
const taken = takeFromQueue(script.text, request.src);
|
|
2268
2145
|
if (!taken.found) throw scriptDryError("text", request.src, request.id);
|
|
2269
|
-
step =
|
|
2146
|
+
step = require_setup_agent.resolveAgentStep(machine, step, request, taken.value);
|
|
2270
2147
|
trail.push({
|
|
2271
2148
|
state: step.snapshot.value,
|
|
2272
2149
|
resolvedRequest: {
|
|
@@ -2281,7 +2158,7 @@ async function simulateAgent(machine, options) {
|
|
|
2281
2158
|
if (invoke) {
|
|
2282
2159
|
const taken = takeFromQueue(script.invokes, invoke.src);
|
|
2283
2160
|
if (!taken.found) throw scriptDryError("userInput", invoke.src, invoke.id);
|
|
2284
|
-
step =
|
|
2161
|
+
step = require_setup_agent.resolveAgentStep(machine, step, invoke.id, taken.value);
|
|
2285
2162
|
trail.push({
|
|
2286
2163
|
state: step.snapshot.value,
|
|
2287
2164
|
resolvedRequest: {
|
|
@@ -2330,7 +2207,7 @@ async function explore(machine, options, stopWhen) {
|
|
|
2330
2207
|
reachedValues.push(snapshot.value);
|
|
2331
2208
|
}
|
|
2332
2209
|
};
|
|
2333
|
-
const initial =
|
|
2210
|
+
const initial = require_setup_agent.initialAgentStep(machine, options.input);
|
|
2334
2211
|
recordState(initial.snapshot);
|
|
2335
2212
|
if (stopWhen?.(initial.snapshot)) witness = [];
|
|
2336
2213
|
const advance = (step) => {
|
|
@@ -2343,7 +2220,7 @@ async function explore(machine, options, stopWhen) {
|
|
|
2343
2220
|
step: current,
|
|
2344
2221
|
blockedSrc: request.src
|
|
2345
2222
|
};
|
|
2346
|
-
current =
|
|
2223
|
+
current = require_setup_agent.resolveAgentStep(machine, current, request, textOutputs[request.src]);
|
|
2347
2224
|
recordState(current.snapshot);
|
|
2348
2225
|
continue;
|
|
2349
2226
|
}
|
|
@@ -2354,7 +2231,7 @@ async function explore(machine, options, stopWhen) {
|
|
|
2354
2231
|
step: current,
|
|
2355
2232
|
blockedSrc: invoke.src
|
|
2356
2233
|
};
|
|
2357
|
-
current =
|
|
2234
|
+
current = require_setup_agent.resolveAgentStep(machine, current, invoke.id, textOutputs[invoke.src]);
|
|
2358
2235
|
recordState(current.snapshot);
|
|
2359
2236
|
continue;
|
|
2360
2237
|
}
|
|
@@ -2395,7 +2272,7 @@ async function explore(machine, options, stopWhen) {
|
|
|
2395
2272
|
}
|
|
2396
2273
|
const request = settled.requests[0];
|
|
2397
2274
|
const isPlan = request?.kind === "plan";
|
|
2398
|
-
const branchEvents = request?.kind === "decision" || request?.kind === "plan" ? request.events.map((descriptor) => ({ type: descriptor.type })) :
|
|
2275
|
+
const branchEvents = request?.kind === "decision" || request?.kind === "plan" ? request.events.map((descriptor) => ({ type: descriptor.type })) : require_decision.getAcceptedEvents(settled.snapshot).map((descriptor) => ({ type: descriptor.type }));
|
|
2399
2276
|
if (branchEvents.length === 0) {
|
|
2400
2277
|
pathsExplored++;
|
|
2401
2278
|
terminals.push({
|
|
@@ -2424,7 +2301,7 @@ async function explore(machine, options, stopWhen) {
|
|
|
2424
2301
|
prunedByGuard++;
|
|
2425
2302
|
continue;
|
|
2426
2303
|
}
|
|
2427
|
-
const next = isPlan ? await
|
|
2304
|
+
const next = isPlan ? await require_setup_agent.resolveAgentRequests(machine, settled, { decide: async () => ({ event }) }) : require_setup_agent.transitionAgentStep(machine, settled, event);
|
|
2428
2305
|
recordState(next.snapshot);
|
|
2429
2306
|
await visit(next, [...path, event], depth + 1);
|
|
2430
2307
|
}
|
|
@@ -2491,38 +2368,506 @@ async function canReach(machine, statePath, options = {}) {
|
|
|
2491
2368
|
} : { canReach: false };
|
|
2492
2369
|
}
|
|
2493
2370
|
//#endregion
|
|
2371
|
+
//#region src/trajectory.ts
|
|
2372
|
+
function isRecord$2(value) {
|
|
2373
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2374
|
+
}
|
|
2375
|
+
/**
|
|
2376
|
+
* Classifies one item. Event-shaped things (log entries, events) become events;
|
|
2377
|
+
* everything else is a state value. A snapshot is unwrapped to its `value`.
|
|
2378
|
+
* @internal
|
|
2379
|
+
*/
|
|
2380
|
+
function normalize(item) {
|
|
2381
|
+
if (isRecord$2(item)) {
|
|
2382
|
+
const nested = item["event"];
|
|
2383
|
+
if (isRecord$2(nested) && typeof nested["type"] === "string") return {
|
|
2384
|
+
kind: "event",
|
|
2385
|
+
event: nested
|
|
2386
|
+
};
|
|
2387
|
+
if (typeof item["type"] === "string") return {
|
|
2388
|
+
kind: "event",
|
|
2389
|
+
event: item
|
|
2390
|
+
};
|
|
2391
|
+
if ("value" in item && "status" in item) return {
|
|
2392
|
+
kind: "state",
|
|
2393
|
+
value: item["value"]
|
|
2394
|
+
};
|
|
2395
|
+
}
|
|
2396
|
+
return {
|
|
2397
|
+
kind: "state",
|
|
2398
|
+
value: item
|
|
2399
|
+
};
|
|
2400
|
+
}
|
|
2401
|
+
/** Every leaf of a state value as a dot path (`{ review: 'editing' }` → `review.editing`). @internal */
|
|
2402
|
+
function statePaths(value) {
|
|
2403
|
+
if (typeof value === "string") return [value];
|
|
2404
|
+
if (!isRecord$2(value)) return [];
|
|
2405
|
+
return Object.entries(value).flatMap(([key, child]) => statePaths(child).map((path) => `${key}.${path}`));
|
|
2406
|
+
}
|
|
2407
|
+
/**
|
|
2408
|
+
* A string expectation matches a state value when it names one of its leaves or
|
|
2409
|
+
* an ancestor of one, so `'review'` matches `{ review: 'editing' }`. @internal
|
|
2410
|
+
*/
|
|
2411
|
+
function stateMatchesPath(path, value) {
|
|
2412
|
+
return statePaths(value).some((leaf) => leaf === path || leaf.startsWith(`${path}.`));
|
|
2413
|
+
}
|
|
2414
|
+
function deepEqual(a, b) {
|
|
2415
|
+
if (Object.is(a, b)) return true;
|
|
2416
|
+
if (Array.isArray(a) || Array.isArray(b)) return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((item, index) => deepEqual(item, b[index]));
|
|
2417
|
+
if (!isRecord$2(a) || !isRecord$2(b)) return false;
|
|
2418
|
+
const keys = Object.keys(a);
|
|
2419
|
+
return keys.length === Object.keys(b).length && keys.every((key) => deepEqual(a[key], b[key]));
|
|
2420
|
+
}
|
|
2421
|
+
/**
|
|
2422
|
+
* Does one actual item satisfy one expected item?
|
|
2423
|
+
*
|
|
2424
|
+
* - A string expectation matches an event's `type`, or a state value it names.
|
|
2425
|
+
* - An event expectation matches an event with the same `type` whose other
|
|
2426
|
+
* declared keys deep-equal, so payload details are opt-in.
|
|
2427
|
+
* - A state-value expectation matches structurally.
|
|
2428
|
+
* @internal
|
|
2429
|
+
*/
|
|
2430
|
+
function matchesItem(expected, actual) {
|
|
2431
|
+
const actualItem = normalize(actual);
|
|
2432
|
+
if (typeof expected === "string") return actualItem.kind === "event" ? actualItem.event.type === expected : stateMatchesPath(expected, actualItem.value);
|
|
2433
|
+
const expectedItem = normalize(expected);
|
|
2434
|
+
if (expectedItem.kind !== actualItem.kind) return false;
|
|
2435
|
+
if (expectedItem.kind === "event" && actualItem.kind === "event") return Object.keys(expectedItem.event).every((key) => deepEqual(expectedItem.event[key], actualItem.event[key]));
|
|
2436
|
+
return deepEqual(expectedItem.value, actualItem.value);
|
|
2437
|
+
}
|
|
2438
|
+
/**
|
|
2439
|
+
* Matches a run's trajectory against an expected one.
|
|
2440
|
+
*
|
|
2441
|
+
* Both trajectories may be state values (collected from `onTransition`) or
|
|
2442
|
+
* events (`result.events`, whose {@link AgentLogEntry} envelopes are unwrapped);
|
|
2443
|
+
* items are compared by shape, so `['prompting', 'drafting']` scores a state
|
|
2444
|
+
* path and `['PROMPT_SUBMITTED', 'SEND']` scores an event log with the same
|
|
2445
|
+
* call.
|
|
2446
|
+
*
|
|
2447
|
+
* Semantics are an ordered subsequence: every expected item must appear in
|
|
2448
|
+
* `actual`, in order, with gaps allowed. Pass `{ exact: true }` to require
|
|
2449
|
+
* equality instead.
|
|
2450
|
+
*
|
|
2451
|
+
* @example Trajectory assertions in a test
|
|
2452
|
+
* ```ts
|
|
2453
|
+
* const statePath: unknown[] = [];
|
|
2454
|
+
* const result = await runAgent(machine, {
|
|
2455
|
+
* input,
|
|
2456
|
+
* executors,
|
|
2457
|
+
* onTransition: (snapshot) => statePath.push(snapshot.value),
|
|
2458
|
+
* });
|
|
2459
|
+
*
|
|
2460
|
+
* const path = matchesTrajectory(statePath, ['prompting', 'drafting', 'sent']);
|
|
2461
|
+
* expect(path.matched, JSON.stringify(path.firstMiss)).toBe(true);
|
|
2462
|
+
* expect(matchesTrajectory(result.events, ['PROMPT_SUBMITTED', 'SEND']).matched).toBe(true);
|
|
2463
|
+
* ```
|
|
2464
|
+
*
|
|
2465
|
+
* @example Partial credit in an eval scorer
|
|
2466
|
+
* ```ts
|
|
2467
|
+
* const match = matchesTrajectory(output.statePath, expected.statePath);
|
|
2468
|
+
* return { name: 'state_path', score: match.score, metadata: { ...match.firstMiss } };
|
|
2469
|
+
* ```
|
|
2470
|
+
*/
|
|
2471
|
+
function matchesTrajectory(actual, expected, options = {}) {
|
|
2472
|
+
const expectedCount = expected.length;
|
|
2473
|
+
if (options.exact) {
|
|
2474
|
+
let matchedCount = 0;
|
|
2475
|
+
let firstMiss;
|
|
2476
|
+
for (let index = 0; index < expectedCount; index++) {
|
|
2477
|
+
if (index < actual.length && matchesItem(expected[index], actual[index])) {
|
|
2478
|
+
matchedCount++;
|
|
2479
|
+
continue;
|
|
2480
|
+
}
|
|
2481
|
+
firstMiss = {
|
|
2482
|
+
index,
|
|
2483
|
+
expected: expected[index],
|
|
2484
|
+
searchedFrom: index
|
|
2485
|
+
};
|
|
2486
|
+
break;
|
|
2487
|
+
}
|
|
2488
|
+
const matched = !firstMiss && actual.length === expectedCount;
|
|
2489
|
+
const denominator = Math.max(expectedCount, actual.length);
|
|
2490
|
+
return {
|
|
2491
|
+
matched,
|
|
2492
|
+
matchedCount,
|
|
2493
|
+
expectedCount,
|
|
2494
|
+
score: matched ? 1 : denominator === 0 ? 1 : matchedCount / denominator,
|
|
2495
|
+
...firstMiss ? { firstMiss } : {}
|
|
2496
|
+
};
|
|
2497
|
+
}
|
|
2498
|
+
let cursor = 0;
|
|
2499
|
+
let matchedCount = 0;
|
|
2500
|
+
for (let index = 0; index < expectedCount; index++) {
|
|
2501
|
+
const want = expected[index];
|
|
2502
|
+
let found = -1;
|
|
2503
|
+
for (let at = cursor; at < actual.length; at++) if (matchesItem(want, actual[at])) {
|
|
2504
|
+
found = at;
|
|
2505
|
+
break;
|
|
2506
|
+
}
|
|
2507
|
+
if (found === -1) return {
|
|
2508
|
+
matched: false,
|
|
2509
|
+
matchedCount,
|
|
2510
|
+
expectedCount,
|
|
2511
|
+
score: matchedCount / expectedCount,
|
|
2512
|
+
firstMiss: {
|
|
2513
|
+
index,
|
|
2514
|
+
expected: want,
|
|
2515
|
+
searchedFrom: cursor
|
|
2516
|
+
}
|
|
2517
|
+
};
|
|
2518
|
+
matchedCount++;
|
|
2519
|
+
cursor = found + 1;
|
|
2520
|
+
}
|
|
2521
|
+
return {
|
|
2522
|
+
matched: true,
|
|
2523
|
+
matchedCount,
|
|
2524
|
+
expectedCount,
|
|
2525
|
+
score: 1
|
|
2526
|
+
};
|
|
2527
|
+
}
|
|
2528
|
+
//#endregion
|
|
2529
|
+
//#region src/scripted-executors.ts
|
|
2530
|
+
/**
|
|
2531
|
+
* Scripted executors — a keyless, deterministic stand-in for a model host.
|
|
2532
|
+
*
|
|
2533
|
+
* `createScriptedExecutors` builds a full `{ generateText, streamText, decide }`
|
|
2534
|
+
* set that plays back canned answers from FIFO queues, so `runAgent` (or
|
|
2535
|
+
* `provideExecutors`, or a bare `TextLogic.execute`) runs with no API key and no
|
|
2536
|
+
* network. It is the fastest way to see a machine run, and the least ceremonial
|
|
2537
|
+
* way to test one: same machine, same executor contract, scripted answers.
|
|
2538
|
+
*/
|
|
2539
|
+
/** Thrown when a scripted queue runs dry on a pending request. */
|
|
2540
|
+
var ScriptedExecutorsError = class extends require_errors.AgentError {
|
|
2541
|
+
constructor(message) {
|
|
2542
|
+
super("scripted-executors-exhausted", message);
|
|
2543
|
+
this.name = "ScriptedExecutorsError";
|
|
2544
|
+
}
|
|
2545
|
+
};
|
|
2546
|
+
function isRecord$1(value) {
|
|
2547
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2548
|
+
}
|
|
2549
|
+
/** The only own keys an executor-result envelope carries. @internal */
|
|
2550
|
+
const TEXT_ENVELOPE_KEYS = new Set([
|
|
2551
|
+
"output",
|
|
2552
|
+
"usage",
|
|
2553
|
+
"raw"
|
|
2554
|
+
]);
|
|
2555
|
+
/**
|
|
2556
|
+
* True when a scripted entry is the executor envelope rather than the output
|
|
2557
|
+
* value: it owns an `output` key and owns NOTHING outside the envelope's own
|
|
2558
|
+
* vocabulary. Bare `'output' in value` would swallow an output object's
|
|
2559
|
+
* siblings (`{ output: 'draft', confidence: 0.9 }` would lose `confidence`)
|
|
2560
|
+
* and would also match an inherited `output`. @internal
|
|
2561
|
+
*/
|
|
2562
|
+
function isTextEnvelope(value) {
|
|
2563
|
+
return Object.hasOwn(value, "output") && Object.keys(value).every((key) => TEXT_ENVELOPE_KEYS.has(key));
|
|
2564
|
+
}
|
|
2565
|
+
/**
|
|
2566
|
+
* Resolves ONE scripted text entry to an executor result: a function entry is
|
|
2567
|
+
* called with the request, and the value is taken as the raw envelope only when
|
|
2568
|
+
* it is one (see {@link isTextEnvelope}). Shared with `runSeam`, whose routed
|
|
2569
|
+
* queues follow the same entry conventions. @internal
|
|
2570
|
+
*/
|
|
2571
|
+
async function resolveScriptedTextEntry(entry, request, info) {
|
|
2572
|
+
const value = typeof entry === "function" ? await entry(request, info) : entry;
|
|
2573
|
+
return isRecord$1(value) && isTextEnvelope(value) ? value : { output: value };
|
|
2574
|
+
}
|
|
2575
|
+
/**
|
|
2576
|
+
* Keyless executors that replay a script instead of calling a model. Every
|
|
2577
|
+
* slot is provided, so any machine binds; a request with no entry left throws a
|
|
2578
|
+
* descriptive error naming what was pending.
|
|
2579
|
+
*
|
|
2580
|
+
* Queues are consumed FIFO and are copied on creation, so the caller's arrays
|
|
2581
|
+
* are never mutated and each call builds a fresh, independent playthrough.
|
|
2582
|
+
* Entries may be plain values or functions of the request, which is how one
|
|
2583
|
+
* script serves a machine that loops or branches: route on `request.name` (the
|
|
2584
|
+
* `setupAgent({ requests })` key) or on the decision's candidate `events`.
|
|
2585
|
+
*
|
|
2586
|
+
* @example
|
|
2587
|
+
* ```ts
|
|
2588
|
+
* const result = await runAgent(moderationMachine, {
|
|
2589
|
+
* input: { comment: 'honestly this update is terrible', trust: 20 },
|
|
2590
|
+
* executors: createScriptedExecutors({
|
|
2591
|
+
* decisions: [{ type: 'FLAG', reason: 'Borderline tone.' }],
|
|
2592
|
+
* }),
|
|
2593
|
+
* });
|
|
2594
|
+
* ```
|
|
2595
|
+
*
|
|
2596
|
+
* @example Dynamic entries
|
|
2597
|
+
* ```ts
|
|
2598
|
+
* createScriptedExecutors({
|
|
2599
|
+
* text: [(request) => `Draft about ${request.prompt}`],
|
|
2600
|
+
* decisions: [(request) => ({ type: request.events[0]!.type })],
|
|
2601
|
+
* });
|
|
2602
|
+
* ```
|
|
2603
|
+
*/
|
|
2604
|
+
function createScriptedExecutors(script = {}) {
|
|
2605
|
+
const decisions = [...script.decisions ?? []];
|
|
2606
|
+
const text = [...script.text ?? []];
|
|
2607
|
+
const nextText = async (request, info) => {
|
|
2608
|
+
if (text.length === 0) throw new ScriptedExecutorsError(`createScriptedExecutors: script ran dry on a pending text request ${describeText(request)}. Add another entry to the script's \`text\` queue.`);
|
|
2609
|
+
return resolveScriptedTextEntry(text.shift(), request, info);
|
|
2610
|
+
};
|
|
2611
|
+
return {
|
|
2612
|
+
generateText: nextText,
|
|
2613
|
+
streamText: async (request, info) => {
|
|
2614
|
+
const result = await nextText(request, info);
|
|
2615
|
+
if (typeof result.output === "string") info?.onChunk?.(result.output);
|
|
2616
|
+
return result;
|
|
2617
|
+
},
|
|
2618
|
+
decide: async (request) => {
|
|
2619
|
+
if (decisions.length === 0) throw new ScriptedExecutorsError(`createScriptedExecutors: script ran dry on a pending decision request (id '${request.id}'). Add another entry to the script's \`decisions\` queue. Candidate events: ${request.events.map((event) => event.type).join(", ") || "(none)"}.`);
|
|
2620
|
+
const entry = decisions.shift();
|
|
2621
|
+
const value = typeof entry === "function" ? await entry(request) : entry;
|
|
2622
|
+
return typeof value.type === "string" ? { event: value } : value;
|
|
2623
|
+
}
|
|
2624
|
+
};
|
|
2625
|
+
}
|
|
2626
|
+
function describeText(request) {
|
|
2627
|
+
return request.name ? `'${request.name}'` : `(model '${request.model}')`;
|
|
2628
|
+
}
|
|
2629
|
+
//#endregion
|
|
2630
|
+
//#region src/seam.ts
|
|
2631
|
+
/** Thrown when the call plan runs dry on a request that is not the live seam. */
|
|
2632
|
+
var SeamScriptError = class extends require_errors.AgentError {
|
|
2633
|
+
constructor(message) {
|
|
2634
|
+
super("seam-script-exhausted", message);
|
|
2635
|
+
this.name = "SeamScriptError";
|
|
2636
|
+
}
|
|
2637
|
+
};
|
|
2638
|
+
function seamKeyOf(seam) {
|
|
2639
|
+
return seam.request ?? seam.model;
|
|
2640
|
+
}
|
|
2641
|
+
function describeRequest(request) {
|
|
2642
|
+
return request.name ? `'${request.name}' (model '${request.model}')` : `(model '${request.model}')`;
|
|
2643
|
+
}
|
|
2644
|
+
function isRecord(value) {
|
|
2645
|
+
return typeof value === "object" && value !== null;
|
|
2646
|
+
}
|
|
2647
|
+
/**
|
|
2648
|
+
* The seam's own answer, for scoring. Our `{ output }` envelope is read
|
|
2649
|
+
* directly; a raw AI SDK `generateText` result contributes its `text`. A raw
|
|
2650
|
+
* STREAM result is left alone — the machine consumes that stream, and reading
|
|
2651
|
+
* it here would steal the chunks — so `seamOutput` is `undefined` for a
|
|
2652
|
+
* streaming candidate that returns one. Score its trajectory instead.
|
|
2653
|
+
* @internal
|
|
2654
|
+
*/
|
|
2655
|
+
async function seamOutputOf(result, request) {
|
|
2656
|
+
if (!isRecord(result)) return;
|
|
2657
|
+
if ("output" in result) return await result["output"];
|
|
2658
|
+
if ("textStream" in result) return;
|
|
2659
|
+
return await require_decision.normalizeGeneratorResult(result, `seam '${request.name ?? request.model}'`, { request });
|
|
2660
|
+
}
|
|
2661
|
+
/**
|
|
2662
|
+
* Runs a machine end to end with one model call under test, and slices the run
|
|
2663
|
+
* at that call.
|
|
2664
|
+
*
|
|
2665
|
+
* The `before`/`after` slices are the point: `after.statePath` is the branch
|
|
2666
|
+
* the seam chose and `after.events` is the same question against the durable
|
|
2667
|
+
* log, both ready for `matchesTrajectory`. The state slice splits where the
|
|
2668
|
+
* state path stood when the seam answered; the event slice splits at the
|
|
2669
|
+
* seam's own effect completion (the first `xstate.done.*`/`xstate.error.*`
|
|
2670
|
+
* entry appended after the call was made).
|
|
2671
|
+
*
|
|
2672
|
+
* @example Keyless: the seam is scripted too, so the whole thing runs offline.
|
|
2673
|
+
* ```ts
|
|
2674
|
+
* const run = await runSeam(emailDrafter, {
|
|
2675
|
+
* scripts: { promptEvaluator: [vague, complete], emailDrafter: [draft] },
|
|
2676
|
+
* seam: { request: 'evaluatePrompt' },
|
|
2677
|
+
* respond: ({ state }) => (state === 'prompting' ? { type: 'PROMPT_SUBMITTED', prompt } : null),
|
|
2678
|
+
* });
|
|
2679
|
+
*
|
|
2680
|
+
* matchesTrajectory(run.after.statePath, ['needsMoreInfo', 'drafting']);
|
|
2681
|
+
* ```
|
|
2682
|
+
*
|
|
2683
|
+
* @example A candidate prompt at the seam, scored against the same rows.
|
|
2684
|
+
* ```ts
|
|
2685
|
+
* const { generateText } = createAiSdkExecutors({ models });
|
|
2686
|
+
* const run = await runSeam(emailDrafter, { ...row, candidate: generateText });
|
|
2687
|
+
* ```
|
|
2688
|
+
*/
|
|
2689
|
+
async function runSeam(machine, options) {
|
|
2690
|
+
const { seam, candidate } = options;
|
|
2691
|
+
const queues = new Map(Object.entries(options.scripts ?? {}).map(([key, answers]) => [key, [...answers]]));
|
|
2692
|
+
const statePath = [];
|
|
2693
|
+
let calls = 0;
|
|
2694
|
+
let seamMatches = 0;
|
|
2695
|
+
let seamOutput;
|
|
2696
|
+
let seamReached = false;
|
|
2697
|
+
let callsBeforeSeam = -1;
|
|
2698
|
+
let seamStateAt = 0;
|
|
2699
|
+
let seamEventAt = 0;
|
|
2700
|
+
let liveEvents = 0;
|
|
2701
|
+
const scriptedAnswer = async (request, info, optional) => {
|
|
2702
|
+
const key = request.name !== void 0 && queues.has(request.name) ? request.name : request.model;
|
|
2703
|
+
const queue = queues.get(key);
|
|
2704
|
+
if (!queue?.length) {
|
|
2705
|
+
if (optional) return;
|
|
2706
|
+
throw new SeamScriptError(`runSeam: no scripted answer left for request ${describeRequest(request)}. Add an entry to \`scripts.${key}\` — its last entry repeats, so one extra answer covers a longer branch.`);
|
|
2707
|
+
}
|
|
2708
|
+
return resolveScriptedTextEntry(queue.length === 1 ? queue[0] : queue.shift(), request, info);
|
|
2709
|
+
};
|
|
2710
|
+
const route = async (request, info) => {
|
|
2711
|
+
const callIndex = calls++;
|
|
2712
|
+
const isSeam = (seam.request !== void 0 ? request.name === seam.request : request.model === seam.model) && seamMatches++ === (seam.occurrence ?? 0);
|
|
2713
|
+
const scripted = await scriptedAnswer(request, info, isSeam && candidate !== void 0);
|
|
2714
|
+
if (!isSeam) return scripted;
|
|
2715
|
+
seamReached = true;
|
|
2716
|
+
callsBeforeSeam = callIndex;
|
|
2717
|
+
seamStateAt = statePath.length;
|
|
2718
|
+
seamEventAt = liveEvents;
|
|
2719
|
+
if (!candidate) {
|
|
2720
|
+
if (!scripted) throw new SeamScriptError(`runSeam: the seam '${seamKeyOf(seam)}' has no candidate and no scripted answer. Pass \`candidate\`, or script the seam's key.`);
|
|
2721
|
+
seamOutput = scripted.output;
|
|
2722
|
+
return scripted;
|
|
2723
|
+
}
|
|
2724
|
+
const result = await candidate(request, info);
|
|
2725
|
+
seamOutput = await seamOutputOf(result, request);
|
|
2726
|
+
return result;
|
|
2727
|
+
};
|
|
2728
|
+
const executors = {
|
|
2729
|
+
...options.executors,
|
|
2730
|
+
generateText: route,
|
|
2731
|
+
streamText: async (request, info) => {
|
|
2732
|
+
const result = await route(request, info);
|
|
2733
|
+
const output = isRecord(result) ? result["output"] : void 0;
|
|
2734
|
+
if (typeof output === "string") info?.onChunk?.(output);
|
|
2735
|
+
return result;
|
|
2736
|
+
}
|
|
2737
|
+
};
|
|
2738
|
+
let snapshot;
|
|
2739
|
+
let event;
|
|
2740
|
+
let events = [];
|
|
2741
|
+
let result;
|
|
2742
|
+
const maxTurns = options.maxTurns ?? 12;
|
|
2743
|
+
for (let turn = 0; turn <= maxTurns; turn++) {
|
|
2744
|
+
result = await runAgent(machine, {
|
|
2745
|
+
...snapshot ? { snapshot } : { input: options.input },
|
|
2746
|
+
...event ? { event } : {},
|
|
2747
|
+
...options.isSuspended ? { isSuspended: options.isSuspended } : {},
|
|
2748
|
+
...options.actors ? { actors: options.actors } : {},
|
|
2749
|
+
events,
|
|
2750
|
+
executors,
|
|
2751
|
+
onEvent: () => {
|
|
2752
|
+
liveEvents++;
|
|
2753
|
+
},
|
|
2754
|
+
onTransition: (next, causedBy) => {
|
|
2755
|
+
if (turn > 0 && causedBy.type === "@xstate.init") return;
|
|
2756
|
+
statePath.push(next.value);
|
|
2757
|
+
}
|
|
2758
|
+
});
|
|
2759
|
+
events = result.events;
|
|
2760
|
+
liveEvents = events.length;
|
|
2761
|
+
if (result.status !== "idle" || turn === maxTurns) break;
|
|
2762
|
+
const next = options.respond?.({
|
|
2763
|
+
snapshot: result.snapshot,
|
|
2764
|
+
state: result.snapshot.value,
|
|
2765
|
+
meta: require_decision.getStateMeta(result.snapshot),
|
|
2766
|
+
turn,
|
|
2767
|
+
result
|
|
2768
|
+
});
|
|
2769
|
+
if (!next) break;
|
|
2770
|
+
event = next;
|
|
2771
|
+
snapshot = result.snapshot;
|
|
2772
|
+
}
|
|
2773
|
+
let splitAt = events.length;
|
|
2774
|
+
if (seamReached) {
|
|
2775
|
+
splitAt = seamEventAt;
|
|
2776
|
+
for (let index = seamEventAt; index < events.length; index++) {
|
|
2777
|
+
const type = events[index].event.type;
|
|
2778
|
+
if (type.startsWith("xstate.done") || type.startsWith("xstate.error")) {
|
|
2779
|
+
splitAt = index;
|
|
2780
|
+
break;
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
const splitStateAt = seamReached ? seamStateAt : statePath.length;
|
|
2785
|
+
return {
|
|
2786
|
+
result,
|
|
2787
|
+
seamOutput,
|
|
2788
|
+
callsBeforeSeam,
|
|
2789
|
+
before: {
|
|
2790
|
+
statePath: statePath.slice(0, splitStateAt),
|
|
2791
|
+
events: events.slice(0, splitAt)
|
|
2792
|
+
},
|
|
2793
|
+
after: {
|
|
2794
|
+
statePath: statePath.slice(splitStateAt),
|
|
2795
|
+
events: events.slice(splitAt)
|
|
2796
|
+
}
|
|
2797
|
+
};
|
|
2798
|
+
}
|
|
2799
|
+
//#endregion
|
|
2800
|
+
exports.AGENT_EVENT_SCHEMA_VERSION = require_event_log_store.AGENT_EVENT_SCHEMA_VERSION;
|
|
2801
|
+
exports.AGENT_INIT_EVENT_TYPE = require_setup_agent.AGENT_INIT_EVENT_TYPE;
|
|
2494
2802
|
exports.AGENT_TRACE_SCHEMA_VERSION = AGENT_TRACE_SCHEMA_VERSION;
|
|
2803
|
+
exports.AGENT_USAGE_EVENT_TYPE = require_setup_agent.AGENT_USAGE_EVENT_TYPE;
|
|
2804
|
+
exports.AgentDecisionExhaustedError = require_decision.AgentDecisionExhaustedError;
|
|
2805
|
+
exports.AgentError = require_errors.AgentError;
|
|
2806
|
+
exports.AgentEventLogConflictError = require_event_log_store.AgentEventLogConflictError;
|
|
2495
2807
|
exports.AgentIdleError = AgentIdleError;
|
|
2808
|
+
exports.AgentIllegalResumeEventError = AgentIllegalResumeEventError;
|
|
2496
2809
|
exports.AgentLintError = AgentLintError;
|
|
2497
|
-
exports.
|
|
2498
|
-
exports.
|
|
2499
|
-
exports.
|
|
2500
|
-
exports.
|
|
2810
|
+
exports.AgentReplayDivergenceError = require_setup_agent.AgentReplayDivergenceError;
|
|
2811
|
+
exports.AgentReplayMachineMismatchError = require_setup_agent.AgentReplayMachineMismatchError;
|
|
2812
|
+
exports.AgentSnapshotVersionMismatchError = AgentSnapshotVersionMismatchError;
|
|
2813
|
+
exports.NonSerializableAgentEventError = require_event_log_store.NonSerializableAgentEventError;
|
|
2814
|
+
exports.PLAN_DONE_EVENT_TYPE = require_decision.PLAN_DONE_EVENT_TYPE;
|
|
2815
|
+
exports.appendMessages = require_setup_agent.appendMessages;
|
|
2816
|
+
exports.assertAgentLogEntry = require_event_log_store.assertAgentLogEntry;
|
|
2501
2817
|
exports.assertAgentMachine = assertAgentMachine;
|
|
2502
|
-
exports.
|
|
2818
|
+
exports.assertEventLogStoreConformance = require_event_log_store.assertEventLogStoreConformance;
|
|
2819
|
+
exports.assertJsonSerializable = require_event_log_store.assertJsonSerializable;
|
|
2820
|
+
exports.assistantMessage = require_decision.assistantMessage;
|
|
2821
|
+
exports.bindRequestExecutor = require_decision.bindRequestExecutor;
|
|
2822
|
+
exports.buildEnvelopeSchema = require_decision.buildEnvelopeSchema;
|
|
2503
2823
|
exports.canReach = canReach;
|
|
2504
|
-
exports.
|
|
2505
|
-
exports.
|
|
2824
|
+
exports.createAgentActor = createAgentActor;
|
|
2825
|
+
exports.createAgentRun = createAgentRun;
|
|
2826
|
+
exports.createAgentSchemas = require_setup_agent.createAgentSchemas;
|
|
2827
|
+
exports.createInMemoryEventLogStore = require_event_log_store.createInMemoryEventLogStore;
|
|
2828
|
+
exports.createReplayEntry = require_setup_agent.createReplayEntry;
|
|
2829
|
+
exports.createScriptedExecutors = createScriptedExecutors;
|
|
2830
|
+
exports.createTextLogic = require_decision.createTextLogic;
|
|
2831
|
+
exports.diffEventLogs = require_setup_agent.diffEventLogs;
|
|
2832
|
+
exports.executeAgentRequest = require_setup_agent.executeAgentRequest;
|
|
2506
2833
|
exports.explorePaths = explorePaths;
|
|
2507
|
-
exports.
|
|
2508
|
-
exports.
|
|
2509
|
-
exports.
|
|
2834
|
+
exports.generateResult = generateResult;
|
|
2835
|
+
exports.getAcceptedEvents = require_decision.getAcceptedEvents;
|
|
2836
|
+
exports.getAgentEffects = require_setup_agent.getAgentEffects;
|
|
2837
|
+
exports.getAgentMessages = require_decision.getAgentMessages;
|
|
2838
|
+
exports.getAgentOutputMode = require_decision.getAgentOutputMode;
|
|
2839
|
+
exports.getCallUsage = require_setup_agent.getCallUsage;
|
|
2840
|
+
exports.getJsonSchema = require_decision.getJsonSchema;
|
|
2841
|
+
exports.getJsonSchemaSync = require_decision.getJsonSchemaSync;
|
|
2842
|
+
exports.getMachineStructuralHash = require_decision.getMachineStructuralHash;
|
|
2843
|
+
exports.getStateMeta = require_decision.getStateMeta;
|
|
2844
|
+
exports.initEntry = require_setup_agent.initEntry;
|
|
2510
2845
|
exports.inspectTransitions = inspectTransitions;
|
|
2846
|
+
exports.isStandardSchema = require_decision.isStandardSchema;
|
|
2511
2847
|
exports.lintAgentMachine = lintAgentMachine;
|
|
2512
|
-
exports.
|
|
2513
|
-
exports.
|
|
2514
|
-
exports.
|
|
2848
|
+
exports.matchesTrajectory = matchesTrajectory;
|
|
2849
|
+
exports.messagesSchema = require_setup_agent.messagesSchema;
|
|
2850
|
+
exports.parseAgentEvent = require_decision.parseAgentEvent;
|
|
2851
|
+
exports.parseModelRef = require_decision.parseModelRef;
|
|
2852
|
+
exports.parseOutput = require_decision.parseOutput;
|
|
2853
|
+
exports.parseStructuredEnvelope = require_decision.parseStructuredEnvelope;
|
|
2854
|
+
exports.persistSnapshot = require_decision.persistSnapshot;
|
|
2515
2855
|
exports.provideExecutors = provideExecutors;
|
|
2856
|
+
exports.renderDecisionAttempts = require_decision.renderDecisionAttempts;
|
|
2857
|
+
exports.replay = require_setup_agent.replay;
|
|
2858
|
+
exports.resolveDecision = require_decision.resolveDecision;
|
|
2516
2859
|
exports.runAgent = runAgent;
|
|
2517
|
-
exports.
|
|
2860
|
+
exports.runSeam = runSeam;
|
|
2861
|
+
exports.serializeTraceEvent = serializeTraceEvent;
|
|
2518
2862
|
Object.defineProperty(exports, "setupAgent", {
|
|
2519
2863
|
enumerable: true,
|
|
2520
2864
|
get: function() {
|
|
2521
|
-
return setupAgent;
|
|
2865
|
+
return require_setup_agent.setupAgent;
|
|
2522
2866
|
}
|
|
2523
2867
|
});
|
|
2524
2868
|
exports.simulateAgent = simulateAgent;
|
|
2525
|
-
exports.systemMessage =
|
|
2526
|
-
exports.toolMessage =
|
|
2869
|
+
exports.systemMessage = require_decision.systemMessage;
|
|
2870
|
+
exports.toolMessage = require_decision.toolMessage;
|
|
2527
2871
|
exports.traceTransitions = traceTransitions;
|
|
2528
|
-
exports.userMessage =
|
|
2872
|
+
exports.userMessage = require_decision.userMessage;
|
|
2873
|
+
exports.verifyReplay = require_setup_agent.verifyReplay;
|