@statelyai/agent 2.0.0-alpha.21 → 2.0.0-alpha.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-sdk.cjs +53 -25
- package/dist/ai-sdk.d.cts +20 -12
- package/dist/ai-sdk.d.mts +20 -12
- package/dist/ai-sdk.mjs +53 -25
- package/dist/{decision-JWx6n3xR.mjs → decision-BBZ2jMO1.mjs} +542 -84
- package/dist/{decision-BnTCsuJv.cjs → decision-Cv6SfNWl.cjs} +629 -99
- package/dist/index.cjs +2225 -1032
- package/dist/index.d.cts +415 -276
- package/dist/index.d.mts +415 -276
- package/dist/index.mjs +2163 -983
- package/dist/machines.cjs +30 -2
- package/dist/machines.d.cts +151 -54
- package/dist/machines.d.mts +151 -54
- package/dist/machines.mjs +30 -2
- package/dist/openai.cjs +447 -0
- package/dist/openai.d.cts +177 -0
- package/dist/openai.d.mts +177 -0
- package/dist/openai.mjs +437 -0
- package/dist/otel.cjs +3 -6
- package/dist/otel.d.cts +1 -1
- package/dist/otel.d.mts +1 -1
- package/dist/otel.mjs +3 -6
- package/dist/run-agent-Ck7Sa3Tb.d.mts +968 -0
- package/dist/run-agent-DWqA8KiI.d.cts +968 -0
- package/dist/setup-agent-CeO2fARv.mjs +639 -0
- package/dist/setup-agent-Dl2VELLi.cjs +663 -0
- package/dist/{text-logic-Mmbgb2jy.d.cts → text-logic-C7bSnH_B.d.cts} +238 -44
- package/dist/{text-logic-BR5twXCv.d.mts → text-logic-DJBK3LbG.d.mts} +238 -44
- package/dist/{types-DSdj2tGs.d.cts → types-CjHk5OdH.d.cts} +4 -19
- package/dist/{types-CvWRGFxP.d.mts → types-DWA6X9R-.d.mts} +4 -19
- package/package.json +17 -21
- package/readme.md +32 -17
- package/dist/errors-BQRk9eiZ.d.cts +0 -19
- package/dist/errors-C9rxnWbX.d.mts +0 -19
- package/dist/errors-CeSXQx0v.mjs +0 -23
- package/dist/errors-DUBBzRLP.cjs +0 -28
- package/dist/event-log-store-Bz7HDBkE.d.cts +0 -145
- package/dist/event-log-store-DmIDosD6.mjs +0 -427
- package/dist/event-log-store-a_TKy1gk.cjs +0 -468
- package/dist/event-log-store-hrA1vqtN.d.mts +0 -145
- package/dist/run-agent-DBNI8ETM.d.mts +0 -1098
- package/dist/run-agent-Do094mGu.d.cts +0 -1098
- package/dist/setup-agent-91FSuZbB.cjs +0 -1641
- package/dist/setup-agent-DqqdcdNh.mjs +0 -1521
- package/dist/sqlite.cjs +0 -129
- package/dist/sqlite.d.cts +0 -57
- package/dist/sqlite.d.mts +0 -57
- package/dist/sqlite.mjs +0 -127
|
@@ -1,7 +1,239 @@
|
|
|
1
|
-
const require_errors = require("./errors-DUBBzRLP.cjs");
|
|
2
1
|
require("./validate.cjs");
|
|
3
2
|
let xstate = require("xstate");
|
|
3
|
+
//#region src/errors.ts
|
|
4
|
+
/**
|
|
5
|
+
* Base class for every error this package throws. Carries a stable, kebab-case
|
|
6
|
+
* `code` so hosts can branch on the failure without `instanceof` (useful across
|
|
7
|
+
* bundle/realm boundaries and after serialization).
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* catch (error) {
|
|
12
|
+
* if (error instanceof AgentError && error.code === 'agent-idle') { ... }
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
var AgentError = class extends Error {
|
|
17
|
+
code;
|
|
18
|
+
constructor(code, message, options) {
|
|
19
|
+
super(message, options);
|
|
20
|
+
this.name = "AgentError";
|
|
21
|
+
this.code = code;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* A model call ran out of output tokens before it produced anything usable.
|
|
26
|
+
* Core never throws this: it is the adapter's signal that a `'length'` finish
|
|
27
|
+
* reason left nothing to return — a structured request whose envelope never
|
|
28
|
+
* closed, say. A text request that WAS cut off still returns its text, with
|
|
29
|
+
* `finishReason: 'length'`.
|
|
30
|
+
*
|
|
31
|
+
* The code is `'truncated'`, so an invoke's `onError` can branch on it without
|
|
32
|
+
* `instanceof`:
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* onError: [
|
|
37
|
+
* { guard: ({ event }) => event.error.code === 'truncated', target: 'retryShorter' },
|
|
38
|
+
* { target: 'failed' },
|
|
39
|
+
* ]
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
var AgentTruncatedError = class extends AgentError {
|
|
43
|
+
/** The request that was cut off — `AgentTextRequest.name`. */
|
|
44
|
+
requestName;
|
|
45
|
+
/** The durable invoke id of the request, when the host knows it. */
|
|
46
|
+
requestId;
|
|
47
|
+
/** Whatever the model did produce before it hit the limit, if anything. */
|
|
48
|
+
partialOutput;
|
|
49
|
+
constructor(message, options) {
|
|
50
|
+
super("truncated", message, { cause: options.cause });
|
|
51
|
+
this.name = "AgentTruncatedError";
|
|
52
|
+
this.requestName = options.requestName;
|
|
53
|
+
if (options.requestId !== void 0) this.requestId = options.requestId;
|
|
54
|
+
if (options.partialOutput !== void 0) this.partialOutput = options.partialOutput;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/messages.ts
|
|
59
|
+
/** Ordinary machine event emitted after a request returns framework-native messages. */
|
|
60
|
+
const AGENT_MESSAGES_EVENT_TYPE = "agent.messages";
|
|
61
|
+
/** Runtime schema for the framework-neutral `agent.messages` envelope. */
|
|
62
|
+
const agentMessagesEventSchema = { "~standard": {
|
|
63
|
+
version: 1,
|
|
64
|
+
vendor: "statelyai-agent",
|
|
65
|
+
validate(value) {
|
|
66
|
+
if (!value || typeof value !== "object") return { issues: [{ message: "Expected an 'agent.messages' payload object" }] };
|
|
67
|
+
const event = value;
|
|
68
|
+
const issues = [];
|
|
69
|
+
if (typeof event.request !== "string") issues.push({
|
|
70
|
+
message: "Expected a string",
|
|
71
|
+
path: ["request"]
|
|
72
|
+
});
|
|
73
|
+
if (typeof event.actorId !== "string") issues.push({
|
|
74
|
+
message: "Expected a string",
|
|
75
|
+
path: ["actorId"]
|
|
76
|
+
});
|
|
77
|
+
if (!Array.isArray(event.messages)) issues.push({
|
|
78
|
+
message: "Expected an array",
|
|
79
|
+
path: ["messages"]
|
|
80
|
+
});
|
|
81
|
+
return issues.length > 0 ? { issues } : { value: event };
|
|
82
|
+
}
|
|
83
|
+
} };
|
|
84
|
+
function appendMessages(options) {
|
|
85
|
+
const key = options?.key ?? "messages";
|
|
86
|
+
return (({ context, event }) => {
|
|
87
|
+
const current = context[key];
|
|
88
|
+
const messages = event.messages;
|
|
89
|
+
return { context: { [key]: [...Array.isArray(current) ? current : [], ...messages ?? []] } };
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
const KNOWN_PART_TYPES = new Set([
|
|
93
|
+
"text",
|
|
94
|
+
"image",
|
|
95
|
+
"file",
|
|
96
|
+
"tool-call",
|
|
97
|
+
"tool-result"
|
|
98
|
+
]);
|
|
99
|
+
const USER_PART_TYPES = new Set([
|
|
100
|
+
"text",
|
|
101
|
+
"image",
|
|
102
|
+
"file"
|
|
103
|
+
]);
|
|
104
|
+
const ASSISTANT_PART_TYPES = new Set([
|
|
105
|
+
"text",
|
|
106
|
+
"file",
|
|
107
|
+
"tool-call",
|
|
108
|
+
"tool-result"
|
|
109
|
+
]);
|
|
110
|
+
const TOOL_PART_TYPES = new Set(["tool-result"]);
|
|
111
|
+
const TOOL_RESULT_CONTENT_PART_TYPES = new Set(["text", "image"]);
|
|
112
|
+
function isKnownPart(part) {
|
|
113
|
+
const type = part && typeof part === "object" ? part.type : void 0;
|
|
114
|
+
return typeof type === "string" && KNOWN_PART_TYPES.has(type);
|
|
115
|
+
}
|
|
116
|
+
function requireString(part, type, field) {
|
|
117
|
+
return typeof part[field] === "string" ? void 0 : `${type} part requires a string "${field}"`;
|
|
118
|
+
}
|
|
119
|
+
function isMediaData(value) {
|
|
120
|
+
return typeof value === "string" || value instanceof Uint8Array || value instanceof ArrayBuffer || value instanceof URL;
|
|
121
|
+
}
|
|
122
|
+
function requireMediaData(part, type, field) {
|
|
123
|
+
return isMediaData(part[field]) ? void 0 : `${type} part requires "${field}" to be a string, Uint8Array, ArrayBuffer, or URL`;
|
|
124
|
+
}
|
|
125
|
+
const TOOL_RESULT_OUTPUT_TYPES = new Set([
|
|
126
|
+
"text",
|
|
127
|
+
"json",
|
|
128
|
+
"error-text",
|
|
129
|
+
"error-json",
|
|
130
|
+
"content"
|
|
131
|
+
]);
|
|
132
|
+
function validateToolResultOutput(output) {
|
|
133
|
+
if (!output || typeof output !== "object" || Array.isArray(output)) return "tool-result part requires an \"output\" object";
|
|
134
|
+
const record = output;
|
|
135
|
+
const outputType = record.type;
|
|
136
|
+
if (typeof outputType !== "string" || !TOOL_RESULT_OUTPUT_TYPES.has(outputType)) return `Unknown tool-result output type: ${JSON.stringify(outputType)}`;
|
|
137
|
+
if (outputType === "text" || outputType === "error-text") {
|
|
138
|
+
if (typeof record.value !== "string") return `tool-result output of type "${outputType}" requires a string "value"`;
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (outputType === "content") {
|
|
142
|
+
if (!Array.isArray(record.value)) return "tool-result output of type \"content\" requires an array \"value\"";
|
|
143
|
+
for (const contentPart of record.value) {
|
|
144
|
+
const error = validatePart(contentPart, TOOL_RESULT_CONTENT_PART_TYPES, "tool-result output content");
|
|
145
|
+
if (error) return error;
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
return record.value !== void 0 ? void 0 : `tool-result output of type "${outputType}" requires a "value"`;
|
|
150
|
+
}
|
|
151
|
+
function validatePart(part, allowedTypes, location) {
|
|
152
|
+
if (!isKnownPart(part)) {
|
|
153
|
+
const type = part && typeof part === "object" ? part.type : void 0;
|
|
154
|
+
return `Unknown message part type: ${JSON.stringify(type)}`;
|
|
155
|
+
}
|
|
156
|
+
if (!allowedTypes.has(part.type)) return `${location} does not allow "${part.type}" parts`;
|
|
157
|
+
const record = part;
|
|
158
|
+
switch (record.type) {
|
|
159
|
+
case "text": return requireString(record, "text", "text");
|
|
160
|
+
case "image": return requireMediaData(record, "image", "image");
|
|
161
|
+
case "file": return requireMediaData(record, "file", "data") ?? requireString(record, "file", "mediaType");
|
|
162
|
+
case "tool-call": return requireString(record, "tool-call", "toolCallId") ?? requireString(record, "tool-call", "toolName") ?? ("input" in record ? void 0 : "tool-call part requires an \"input\" value");
|
|
163
|
+
case "tool-result": return requireString(record, "tool-result", "toolCallId") ?? requireString(record, "tool-result", "toolName") ?? validateToolResultOutput(record.output);
|
|
164
|
+
default: return;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function validatePartsArray(content, allowedTypes, location) {
|
|
168
|
+
if (!Array.isArray(content)) return "Expected content to be a string or an array of parts";
|
|
169
|
+
for (const part of content) {
|
|
170
|
+
const error = validatePart(part, allowedTypes, location);
|
|
171
|
+
if (error) return error;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* A {@link StandardSchemaV1} validating an `AgentMessage[]` context field —
|
|
176
|
+
* checks that every message has a known `role` (`system`/`user`/`assistant`/
|
|
177
|
+
* `tool`) and that `content` is either a string (where the role allows it) or
|
|
178
|
+
* an array of role-appropriate parts whose required fields and media payloads
|
|
179
|
+
* have the right runtime types (extra fields are allowed). Use it directly as
|
|
180
|
+
* a context schema's `messages` field when authoring with `createAgentSchemas`
|
|
181
|
+
* or `fromConfig`. A zod object cannot nest a Standard Schema, so in a zod
|
|
182
|
+
* context schema write `z.custom<AgentMessage[]>(isAgentMessages)`, which runs
|
|
183
|
+
* this same validator (see {@link isAgentMessages}).
|
|
184
|
+
*/
|
|
185
|
+
const messagesSchema = { "~standard": {
|
|
186
|
+
version: 1,
|
|
187
|
+
vendor: "statelyai-agent",
|
|
188
|
+
validate(value) {
|
|
189
|
+
if (!Array.isArray(value)) return { issues: [{ message: "Expected an array of agent messages" }] };
|
|
190
|
+
for (const message of value) {
|
|
191
|
+
if (!message || typeof message !== "object") return { issues: [{ message: "Expected an array of agent messages" }] };
|
|
192
|
+
const role = message.role;
|
|
193
|
+
const content = message.content;
|
|
194
|
+
if (role !== "system" && role !== "user" && role !== "assistant" && role !== "tool") return { issues: [{ message: `Unknown message role: ${JSON.stringify(role)}` }] };
|
|
195
|
+
if (role === "system") {
|
|
196
|
+
if (typeof content !== "string") return { issues: [{ message: "system message content must be a string" }] };
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (role === "tool") {
|
|
200
|
+
const error = validatePartsArray(content, TOOL_PART_TYPES, "tool message content");
|
|
201
|
+
if (error) return { issues: [{ message: error }] };
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (typeof content === "string") continue;
|
|
205
|
+
const error = validatePartsArray(content, role === "user" ? USER_PART_TYPES : ASSISTANT_PART_TYPES, `${role} message content`);
|
|
206
|
+
if (error) return { issues: [{ message: error }] };
|
|
207
|
+
}
|
|
208
|
+
return { value };
|
|
209
|
+
}
|
|
210
|
+
} };
|
|
211
|
+
/**
|
|
212
|
+
* Type guard over {@link messagesSchema}: `true` when `value` is a valid
|
|
213
|
+
* `AgentMessage[]`. The one-liner for zod context schemas:
|
|
214
|
+
* `messages: z.custom<AgentMessage[]>(isAgentMessages)`.
|
|
215
|
+
*/
|
|
216
|
+
function isAgentMessages(value) {
|
|
217
|
+
const result = messagesSchema["~standard"].validate(value);
|
|
218
|
+
return !(result instanceof Promise) && !("issues" in result && result.issues);
|
|
219
|
+
}
|
|
220
|
+
//#endregion
|
|
4
221
|
//#region src/utils.ts
|
|
222
|
+
/** Returns the human-readable text carried by a message, ignoring non-text parts. */
|
|
223
|
+
function getMessageText(message) {
|
|
224
|
+
if (typeof message.content === "string") return message.content;
|
|
225
|
+
const text = [];
|
|
226
|
+
for (const part of message.content) {
|
|
227
|
+
if (part.type === "text") {
|
|
228
|
+
text.push(part.text);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (part.type !== "tool-result") continue;
|
|
232
|
+
if (part.output.type === "text" || part.output.type === "error-text") text.push(part.output.value);
|
|
233
|
+
else if (part.output.type === "content") text.push(...part.output.value.flatMap((item) => item.type === "text" ? [item.text] : []));
|
|
234
|
+
}
|
|
235
|
+
return text.join("\n");
|
|
236
|
+
}
|
|
5
237
|
/**
|
|
6
238
|
* Walks a context value and returns the dot-paths of the first few values that
|
|
7
239
|
* would NOT survive a JSON persist/resume round-trip (persist with XState's
|
|
@@ -69,8 +301,7 @@ function getMachineStructuralHash(machine) {
|
|
|
69
301
|
* The version a machine is stamped with everywhere: an explicitly declared
|
|
70
302
|
* `createMachine({ version })` when present, else its
|
|
71
303
|
* {@link getMachineStructuralHash}. Single source of truth so `runAgent`,
|
|
72
|
-
* `provideExecutors`/`traceTransitions` trace envelopes
|
|
73
|
-
* helpers (`initEntry`, `createReplayEntry`, `replay`) never disagree.
|
|
304
|
+
* `provideExecutors`/`traceTransitions` trace envelopes agree.
|
|
74
305
|
* @internal
|
|
75
306
|
*/
|
|
76
307
|
function resolveMachineVersion(machine) {
|
|
@@ -175,21 +406,37 @@ function getStateMeta(snapshot) {
|
|
|
175
406
|
return Object.assign({}, ...entries.map(([, meta]) => meta));
|
|
176
407
|
}
|
|
177
408
|
/**
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
* the `(snapshot as { messages?: AgentMessage[] }).messages` cast. runAgent
|
|
181
|
-
* stamps the log as a plain enumerable `messages` property (like `agentMeta`),
|
|
182
|
-
* so it survives a JSON persist/resume round-trip; this accessor works on the
|
|
183
|
-
* live settled snapshot and on a JSON-parsed persisted one alike. Returns `[]`
|
|
184
|
-
* when no log was stamped (e.g. a default invoke-driven run).
|
|
409
|
+
* Renders a snapshot's state value as one deterministic string, so a log line,
|
|
410
|
+
* a progress field, or a test assertion reads the same on every run.
|
|
185
411
|
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
412
|
+
* - An atomic state is its own name: `'writing'`.
|
|
413
|
+
* - A nested state joins with dots: `{ review: { editing: 'draft' } }` →
|
|
414
|
+
* `'review.editing.draft'`.
|
|
415
|
+
* - A parallel state brackets its regions, sorted by region name, so key order
|
|
416
|
+
* never leaks into the string: `{ p: { left: 'x', right: { a: 'b' } } }` →
|
|
417
|
+
* `'p:{left.x,right.a.b}'`.
|
|
418
|
+
*
|
|
419
|
+
* Prefer this over `String(snapshot.value)`, which renders every non-atomic
|
|
420
|
+
* value as `[object Object]`.
|
|
421
|
+
*
|
|
422
|
+
* @example
|
|
423
|
+
* ```ts
|
|
424
|
+
* onTransition: (snapshot) => console.log(getStatePath(snapshot));
|
|
425
|
+
* ```
|
|
189
426
|
*/
|
|
190
|
-
function
|
|
191
|
-
|
|
192
|
-
|
|
427
|
+
function getStatePath(snapshot) {
|
|
428
|
+
return serializeStateValue(typeof snapshot === "object" && snapshot !== null && "value" in snapshot && ("status" in snapshot || typeof snapshot.matches === "function") ? snapshot.value : snapshot);
|
|
429
|
+
}
|
|
430
|
+
function serializeStateValue(value) {
|
|
431
|
+
if (typeof value === "string") return value;
|
|
432
|
+
const entries = Object.entries(value).map(([key, child]) => {
|
|
433
|
+
const rendered = serializeStateValue(child);
|
|
434
|
+
if (!rendered) return [key, key];
|
|
435
|
+
return [key, `${key}${rendered.startsWith("{") ? ":" : "."}${rendered}`];
|
|
436
|
+
}).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
437
|
+
if (entries.length === 0) return "";
|
|
438
|
+
if (entries.length === 1) return entries[0][1];
|
|
439
|
+
return `{${entries.map(([, rendered]) => rendered).join(",")}}`;
|
|
193
440
|
}
|
|
194
441
|
/**
|
|
195
442
|
* Structural guard for a {@link StandardSchemaV1}: `true` when `value` carries
|
|
@@ -240,7 +487,42 @@ function validateSchemaSync(schema, value) {
|
|
|
240
487
|
}
|
|
241
488
|
//#endregion
|
|
242
489
|
//#region src/internal/registry.ts
|
|
490
|
+
/** Non-enumerable hook an optional adapter can attach to a model registry. */
|
|
491
|
+
const DEFAULT_AGENT_EXECUTORS = Symbol.for("@statelyai/agent.defaultExecutors");
|
|
492
|
+
/**
|
|
493
|
+
* Registered execution options, keyed on BOTH the machine object and its root
|
|
494
|
+
* `config` object. `config` is shared by reference across `machine.provide(...)`
|
|
495
|
+
* (unlike the machine itself), so the `config` key is what lets a machine
|
|
496
|
+
* rebound by `runAgent` — or by any host `.provide(...)` — still resolve the
|
|
497
|
+
* schemas/actors `setupAgent` registered. Always write through
|
|
498
|
+
* {@link setAgentExecutionOptions} and read through
|
|
499
|
+
* {@link getAgentExecutionOptions}.
|
|
500
|
+
*/
|
|
243
501
|
const agentExecutionOptions = /* @__PURE__ */ new WeakMap();
|
|
502
|
+
/** The root `config` object a machine shares with every `.provide(...)` of itself. */
|
|
503
|
+
function machineConfigKey(machine) {
|
|
504
|
+
const config = machine?.config;
|
|
505
|
+
return config && typeof config === "object" ? config : void 0;
|
|
506
|
+
}
|
|
507
|
+
/** Registers `options` for `machine` and for every `.provide(...)` of it. */
|
|
508
|
+
function setAgentExecutionOptions(machine, options) {
|
|
509
|
+
if (!machine || typeof machine !== "object") return;
|
|
510
|
+
agentExecutionOptions.set(machine, options);
|
|
511
|
+
const config = machineConfigKey(machine);
|
|
512
|
+
if (config) agentExecutionOptions.set(config, options);
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Reads the options registered for `machine`, falling back to the root `config`
|
|
516
|
+
* key so a `.provide(...)`-rebound machine (e.g. the one behind a `runAgent`
|
|
517
|
+
* result snapshot) resolves the same pack.
|
|
518
|
+
*/
|
|
519
|
+
function getAgentExecutionOptions(machine) {
|
|
520
|
+
if (!machine || typeof machine !== "object") return void 0;
|
|
521
|
+
const direct = agentExecutionOptions.get(machine);
|
|
522
|
+
if (direct) return direct;
|
|
523
|
+
const config = machineConfigKey(machine);
|
|
524
|
+
return config ? agentExecutionOptions.get(config) : void 0;
|
|
525
|
+
}
|
|
244
526
|
/**
|
|
245
527
|
* Machine-carried wait-state predicates, keyed on the machine's root `config`
|
|
246
528
|
* object. `config` is shared by reference across `machine.provide(...)` (unlike
|
|
@@ -248,8 +530,7 @@ const agentExecutionOptions = /* @__PURE__ */ new WeakMap();
|
|
|
248
530
|
* machine through `.provide` — which is why it is keyed on `config`, not the
|
|
249
531
|
* machine. Set by `setupAgent({ isIdle })` in `createMachine` and by
|
|
250
532
|
* `setupAgent.fromConfig` (its `isIdle` option or the config's
|
|
251
|
-
* `idleTags`), read by `runAgent`
|
|
252
|
-
* override, above the timing heuristic).
|
|
533
|
+
* `idleTags`), read by `runAgent` before its structural default.
|
|
253
534
|
*/
|
|
254
535
|
const machineIdlePredicates = /* @__PURE__ */ new WeakMap();
|
|
255
536
|
/** Reads the {@link machineIdlePredicates} predicate carried by `machine` (via its root `config`), if any. */
|
|
@@ -257,24 +538,6 @@ function getMachineIdlePredicate(machine) {
|
|
|
257
538
|
const config = machine.config;
|
|
258
539
|
return config ? machineIdlePredicates.get(config) : void 0;
|
|
259
540
|
}
|
|
260
|
-
/**
|
|
261
|
-
* Lint-reachability sidecar for `fromConfig` machines.
|
|
262
|
-
*
|
|
263
|
-
* Static transition targets declared by the source config of a
|
|
264
|
-
* `setupAgent.fromConfig` machine, as `dotted state path → declared target
|
|
265
|
-
* strings` (every `on`/`always`/`after`/`onDone`/`choice` target plus each
|
|
266
|
-
* invoke's `onDone`/`onError`). The JSON layer folds a transition that carries a
|
|
267
|
-
* context patch into an opaque resolver function, erasing its target from
|
|
268
|
-
* `machine.config` — so `lintAgentMachine`'s reachability walk reads the
|
|
269
|
-
* targets from here instead. Keyed on the machine's root `config` object (like
|
|
270
|
-
* {@link machineIdlePredicates}) so it survives `machine.provide(...)`.
|
|
271
|
-
*/
|
|
272
|
-
const machineStaticTransitionTargets = /* @__PURE__ */ new WeakMap();
|
|
273
|
-
/** Reads the {@link machineStaticTransitionTargets} map carried by `machine` (via its root `config`), if any. */
|
|
274
|
-
function getMachineStaticTransitionTargets(machine) {
|
|
275
|
-
const config = machine.config;
|
|
276
|
-
return config ? machineStaticTransitionTargets.get(config) : void 0;
|
|
277
|
-
}
|
|
278
541
|
const unboundPlaceholderLogics = /* @__PURE__ */ new WeakSet();
|
|
279
542
|
/** Text/decision logics created WITH their own executor (withExecutor or the
|
|
280
543
|
* factory's second arg) — these are runnable as-is, so runAgent's bind check
|
|
@@ -292,7 +555,7 @@ function isUnboundPlaceholder(logic) {
|
|
|
292
555
|
}
|
|
293
556
|
function getRegisteredAgentExecutionOptions(machine, options) {
|
|
294
557
|
return {
|
|
295
|
-
...
|
|
558
|
+
...getAgentExecutionOptions(machine),
|
|
296
559
|
...options
|
|
297
560
|
};
|
|
298
561
|
}
|
|
@@ -302,8 +565,6 @@ const USER_INPUT_ACTOR = "agent.userInput";
|
|
|
302
565
|
const GENERATE_TEXT_ACTOR = "agent.generateText";
|
|
303
566
|
const STREAM_TEXT_ACTOR = "agent.streamText";
|
|
304
567
|
const DECIDE_ACTOR = "agent.decide";
|
|
305
|
-
/** Synthetic `src` stamped on trace requests produced by `getRequests` state interpretation — NOT a registered actor source (nothing is invoked; the pass makes the call directly). */
|
|
306
|
-
const INTERPRET_SOURCE = "agent.interpret";
|
|
307
568
|
/**
|
|
308
569
|
* Splits a portable `"provider/model-id"` model ref (the convention JSON
|
|
309
570
|
* workflows and registry-less hosts use, e.g. `"openai/gpt-5.4-mini"`) into
|
|
@@ -325,6 +586,27 @@ function parseModelRef(modelRef) {
|
|
|
325
586
|
modelId: modelRef.slice(slash + 1)
|
|
326
587
|
};
|
|
327
588
|
}
|
|
589
|
+
const AGENT_FINISH_REASONS = new Set([
|
|
590
|
+
"stop",
|
|
591
|
+
"length",
|
|
592
|
+
"tool-calls",
|
|
593
|
+
"content-filter",
|
|
594
|
+
"other"
|
|
595
|
+
]);
|
|
596
|
+
/**
|
|
597
|
+
* Reads a settled call's {@link AgentFinishReason} off a RAW executor result's
|
|
598
|
+
* `finishReason` field — the finish-reason sibling of {@link getCallUsage},
|
|
599
|
+
* applied by `runAgent` before it puts the reason on the `request.end` trace.
|
|
600
|
+
* A provider's `'error'` normalizes to `'other'`; anything else the union does
|
|
601
|
+
* not name (an un-normalized provider string, a missing field) returns
|
|
602
|
+
* `undefined`.
|
|
603
|
+
*/
|
|
604
|
+
function getCallFinishReason(raw) {
|
|
605
|
+
const value = raw?.finishReason;
|
|
606
|
+
if (typeof value !== "string") return;
|
|
607
|
+
if (value === "error") return "other";
|
|
608
|
+
return AGENT_FINISH_REASONS.has(value) ? value : void 0;
|
|
609
|
+
}
|
|
328
610
|
/** The token fields {@link AgentUsage} aggregates. @internal */
|
|
329
611
|
const AGENT_USAGE_TOKEN_FIELDS = [
|
|
330
612
|
"inputTokens",
|
|
@@ -363,6 +645,13 @@ function getCallUsage(raw) {
|
|
|
363
645
|
}
|
|
364
646
|
return out;
|
|
365
647
|
}
|
|
648
|
+
function textRequestSourceIssue(request) {
|
|
649
|
+
const hasPrompt = typeof request.prompt === "string" && request.prompt.length > 0;
|
|
650
|
+
const hasMessages = Array.isArray(request.messages) && request.messages.length > 0;
|
|
651
|
+
const label = request.name ? ` '${request.name}'` : "";
|
|
652
|
+
if (hasPrompt && hasMessages) return `Agent text request${label} has both a non-empty \`prompt\` and \`messages\` — provide exactly one so the model has a single input source.`;
|
|
653
|
+
if (!hasPrompt && !hasMessages) return `Agent text request${label} has neither a non-empty \`prompt\` nor \`messages\` — provide at least one so the model has something to respond to.`;
|
|
654
|
+
}
|
|
366
655
|
const agentTextInputSchema = { "~standard": {
|
|
367
656
|
version: 1,
|
|
368
657
|
vendor: "statelyai-agent",
|
|
@@ -370,9 +659,8 @@ const agentTextInputSchema = { "~standard": {
|
|
|
370
659
|
if (!value || typeof value !== "object") return { issues: [{ message: "Expected agent text input object" }] };
|
|
371
660
|
const request = value;
|
|
372
661
|
if (typeof request.model !== "string") return { issues: [{ message: "Expected agent text input with a string `model`" }] };
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
if (!hasPrompt && !hasMessages) return { issues: [{ message: `Agent text request${request.name ? ` '${request.name}'` : ""} has neither a non-empty \`prompt\` nor \`messages\` — provide at least one so the model has something to respond to.` }] };
|
|
662
|
+
const issue = textRequestSourceIssue(request);
|
|
663
|
+
if (issue) return { issues: [{ message: issue }] };
|
|
376
664
|
return { value: request };
|
|
377
665
|
}
|
|
378
666
|
} };
|
|
@@ -486,13 +774,24 @@ function createTextLogic(config, execute) {
|
|
|
486
774
|
output: config.schemas?.output ?? stringOutputSchema
|
|
487
775
|
};
|
|
488
776
|
const request = (input) => {
|
|
489
|
-
const
|
|
777
|
+
const parsedInput = validateSchemaSync(schemas.input, input);
|
|
778
|
+
const args = { input: parsedInput };
|
|
779
|
+
const name = resolveTextLogicValue(config.name, args);
|
|
780
|
+
const prompt = resolveTextLogicValue(config.prompt, args);
|
|
781
|
+
const messages = resolveTextLogicValue(config.messages, args);
|
|
782
|
+
const sourceIssue = textRequestSourceIssue({
|
|
783
|
+
name,
|
|
784
|
+
prompt,
|
|
785
|
+
messages
|
|
786
|
+
});
|
|
787
|
+
if (sourceIssue) throw new Error(sourceIssue);
|
|
490
788
|
return {
|
|
491
|
-
name
|
|
789
|
+
name,
|
|
790
|
+
input: parsedInput,
|
|
492
791
|
model: resolveTextLogicValue(config.model, args),
|
|
493
792
|
system: resolveTextLogicValue(config.system, args),
|
|
494
|
-
prompt
|
|
495
|
-
messages
|
|
793
|
+
prompt,
|
|
794
|
+
messages,
|
|
496
795
|
tools: resolveTextLogicValue(config.tools, args),
|
|
497
796
|
toolChoice: resolveTextLogicValue(config.toolChoice, args),
|
|
498
797
|
outputSchema: schemas.output,
|
|
@@ -509,7 +808,7 @@ function createTextLogic(config, execute) {
|
|
|
509
808
|
};
|
|
510
809
|
const logic = (0, xstate.createAsyncLogic)({ run: async ({ input, signal, system, self }, enq) => {
|
|
511
810
|
const resolvedRequest = request(input);
|
|
512
|
-
if (!execute) throw new Error("Text logic has no host execution. Pass an executor as the second argument to createTextLogic(...), provide a runtime adapter, or
|
|
811
|
+
if (!execute) throw new Error("Text logic has no host execution. Pass an executor as the second argument to createTextLogic(...), provide a runtime adapter, or bind it through runAgent/provideExecutors, or execute the XState effect in your host.");
|
|
513
812
|
const result = await execute({
|
|
514
813
|
input,
|
|
515
814
|
request: resolvedRequest,
|
|
@@ -658,11 +957,14 @@ function parseStructuredEnvelope(request, value) {
|
|
|
658
957
|
async function executeAgentTextRequest(mode, id, input, executors, tools = {}, info) {
|
|
659
958
|
const request = {
|
|
660
959
|
...input,
|
|
960
|
+
name: input.name ?? id,
|
|
661
961
|
tools: {
|
|
662
962
|
...input.tools,
|
|
663
963
|
...tools
|
|
664
964
|
}
|
|
665
965
|
};
|
|
966
|
+
const sourceIssue = textRequestSourceIssue(request);
|
|
967
|
+
if (sourceIssue) throw new Error(sourceIssue);
|
|
666
968
|
const executor = mode === "stream" ? executors.streamText : executors.generateText;
|
|
667
969
|
if (!executor) throw new Error(`No executor provided for ${mode === "stream" ? "stream" : "generate"} request '${id}'.`);
|
|
668
970
|
const raw = await executor(request, info);
|
|
@@ -732,7 +1034,31 @@ function invalidGeneratorResult(id) {
|
|
|
732
1034
|
return /* @__PURE__ */ new Error(`Executor for '${id}' returned an invalid result: executors must return { output } (an envelope with the text string or structured object as \`output\`, plus optional passthrough fields). Raw Vercel AI SDK generateText/streamText results ({ text } or { textStream }) are also accepted.`);
|
|
733
1035
|
}
|
|
734
1036
|
//#endregion
|
|
1037
|
+
//#region src/internal/is-record.ts
|
|
1038
|
+
/** True for a plain object-like value (arrays excluded). @internal */
|
|
1039
|
+
function isRecord(value) {
|
|
1040
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1041
|
+
}
|
|
1042
|
+
//#endregion
|
|
735
1043
|
//#region src/events.ts
|
|
1044
|
+
/**
|
|
1045
|
+
* Thrown by {@link parseAgentEvent} (and {@link eventFromInteraction}) when a
|
|
1046
|
+
* wire payload is not a usable event: it is not an object with a string
|
|
1047
|
+
* `type`, it names a reserved `@agent.*` type, or its fields fail the schema
|
|
1048
|
+
* the machine registered for that type. A host turns it into a 400.
|
|
1049
|
+
*
|
|
1050
|
+
* Not handling an event is NOT this error — a state machine ignores events it
|
|
1051
|
+
* has no transition for, so an event the current state does not handle parses
|
|
1052
|
+
* fine and settles the run with `result.ignored` set.
|
|
1053
|
+
*/
|
|
1054
|
+
var AgentInvalidEventPayloadError = class extends AgentError {
|
|
1055
|
+
eventType;
|
|
1056
|
+
constructor(eventType, detail) {
|
|
1057
|
+
super("invalid-event-payload", `Invalid event payload for '${eventType}': ${detail}`);
|
|
1058
|
+
this.name = "AgentInvalidEventPayloadError";
|
|
1059
|
+
this.eventType = eventType;
|
|
1060
|
+
}
|
|
1061
|
+
};
|
|
736
1062
|
/** Default prefix for the synthetic tool name generated per candidate event (e.g. `send_event_ASK`). Override per-request with {@link AgentEventToolNameResolver}. @internal */
|
|
737
1063
|
const EVENT_TOOL_PREFIX = "send_event_";
|
|
738
1064
|
function hashString(value) {
|
|
@@ -769,58 +1095,61 @@ function matchesEventPattern(eventType, pattern) {
|
|
|
769
1095
|
return eventType === pattern;
|
|
770
1096
|
}
|
|
771
1097
|
/**
|
|
772
|
-
*
|
|
773
|
-
*
|
|
1098
|
+
* Event types reserved for delivery by the library (`@agent.*` and
|
|
1099
|
+
* `agent.messages`). A machine may declare transitions on them,
|
|
774
1100
|
* but they are never model-facing: {@link getAcceptedEvents} drops them before
|
|
775
1101
|
* any `allowedEvents` matching, so they cannot be offered as a decision
|
|
776
1102
|
* candidate (not even under a `'*'` wildcard) and {@link parseAgentEvent}
|
|
777
1103
|
* rejects them — a model or a wire message must not be able to forge one.
|
|
778
|
-
* Matched as a prefix rather than a list so this module stays free of an import
|
|
779
|
-
* cycle back to `effects.ts`, where the constants live.
|
|
780
1104
|
* @internal
|
|
781
1105
|
*/
|
|
782
1106
|
const RESERVED_AGENT_EVENT_PREFIX = "@agent.";
|
|
1107
|
+
function isReservedAgentEvent(eventType) {
|
|
1108
|
+
return eventType.startsWith(RESERVED_AGENT_EVENT_PREFIX) || eventType === "agent.messages";
|
|
1109
|
+
}
|
|
783
1110
|
/** True when an `allowedEvents` entry is a wildcard pattern rather than a concrete event type. @internal */
|
|
784
1111
|
function isEventPattern(entry) {
|
|
785
1112
|
return entry === "*" || entry.endsWith(".*");
|
|
786
1113
|
}
|
|
787
1114
|
/**
|
|
788
|
-
*
|
|
789
|
-
*
|
|
790
|
-
*
|
|
791
|
-
*
|
|
792
|
-
*
|
|
793
|
-
*
|
|
794
|
-
*
|
|
1115
|
+
* Parses a wire payload (a request body, a socket frame) into an
|
|
1116
|
+
* event typed as the machine's event union, so it can go straight to
|
|
1117
|
+
* `runAgent({ event })` / `actor.send(...)` without a cast. Pass the machine
|
|
1118
|
+
* itself, or any snapshot of it.
|
|
1119
|
+
*
|
|
1120
|
+
* It checks exactly what a schema can check: the payload is an object with a
|
|
1121
|
+
* string `type`, that type is not one of the library-reserved `@agent.*` types,
|
|
1122
|
+
* and — when the machine registered a schema for it — the fields satisfy that
|
|
1123
|
+
* schema (defaults filled, transforms applied). Anything else throws
|
|
1124
|
+
* {@link AgentInvalidEventPayloadError}, which a host answers with a 400.
|
|
795
1125
|
*
|
|
796
|
-
*
|
|
797
|
-
*
|
|
798
|
-
*
|
|
799
|
-
*
|
|
800
|
-
* the live snapshot; the schemas only add payload validation.
|
|
1126
|
+
* It deliberately does NOT check whether the current state handles the event.
|
|
1127
|
+
* State machines ignore events they have no transition for; that is normal
|
|
1128
|
+
* behavior, not a validation failure. Send the parsed event and read
|
|
1129
|
+
* `result.ignored` if the host wants to tell the client nothing happened.
|
|
801
1130
|
*
|
|
802
1131
|
* @example
|
|
803
1132
|
* ```ts
|
|
804
|
-
* const event = parseAgentEvent(
|
|
805
|
-
* result = await runAgent(machine, {
|
|
1133
|
+
* const event = parseAgentEvent(machine, await request.json());
|
|
1134
|
+
* const result = await runAgent(machine, { store, threadId, event, executors });
|
|
806
1135
|
* ```
|
|
807
1136
|
*/
|
|
808
|
-
function parseAgentEvent(
|
|
809
|
-
|
|
810
|
-
const
|
|
811
|
-
if (
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
1137
|
+
function parseAgentEvent(source, payload, options = {}) {
|
|
1138
|
+
if (!isRecord(payload) || typeof payload.type !== "string") throw new AgentInvalidEventPayloadError("(unknown)", "expected an object with a string `type`.");
|
|
1139
|
+
const event = payload;
|
|
1140
|
+
if (isReservedAgentEvent(event.type)) throw new AgentInvalidEventPayloadError(event.type, "this event type is reserved for the library and cannot be sent from outside.");
|
|
1141
|
+
const registered = getAgentExecutionOptions(typeof source.provide === "function" ? source : source.machine)?.schemas;
|
|
1142
|
+
const inputSchema = (options.events ?? options.schemas?.events ?? registered?.events)?.[event.type];
|
|
1143
|
+
if (!inputSchema) return event;
|
|
1144
|
+
const { type, ...fields } = event;
|
|
1145
|
+
try {
|
|
1146
|
+
return {
|
|
1147
|
+
...validateSchemaSync(inputSchema, fields),
|
|
1148
|
+
type
|
|
1149
|
+
};
|
|
1150
|
+
} catch (error) {
|
|
1151
|
+
throw new AgentInvalidEventPayloadError(event.type, error instanceof Error ? error.message : String(error));
|
|
822
1152
|
}
|
|
823
|
-
return event;
|
|
824
1153
|
}
|
|
825
1154
|
/**
|
|
826
1155
|
* Lists the events a snapshot can currently accept, as {@link AgentEventDescriptor}s
|
|
@@ -844,7 +1173,7 @@ function getAcceptedEvents(snapshot, options = {}) {
|
|
|
844
1173
|
const usedToolNames = /* @__PURE__ */ new Set();
|
|
845
1174
|
return (0, xstate.getNextTransitions)(snapshot).flatMap((transitionDefinition) => {
|
|
846
1175
|
const eventType = transitionDefinition.eventType;
|
|
847
|
-
if (!eventType || eventType === "*" || eventType.startsWith("xstate.") || eventType
|
|
1176
|
+
if (!eventType || eventType === "*" || eventType.startsWith("xstate.") || isReservedAgentEvent(eventType) || eventTypes && !eventTypes.some((pattern) => matchesEventPattern(eventType, pattern)) || seen.has(eventType)) return [];
|
|
848
1177
|
seen.add(eventType);
|
|
849
1178
|
const defaultToolName = sanitizeEventToolName(eventType);
|
|
850
1179
|
const toolName = options.eventToolName ? options.eventToolName({
|
|
@@ -866,6 +1195,8 @@ function decideRequestFromInput(input) {
|
|
|
866
1195
|
return {
|
|
867
1196
|
kind: "decision",
|
|
868
1197
|
id: "",
|
|
1198
|
+
name: input.name,
|
|
1199
|
+
input,
|
|
869
1200
|
model: input.model,
|
|
870
1201
|
system: input.system,
|
|
871
1202
|
prompt: input.prompt,
|
|
@@ -911,18 +1242,145 @@ function resolveAllowedEventTypes(allowedEvents, input) {
|
|
|
911
1242
|
const resolved = typeof allowedEvents === "function" ? allowedEvents({ input }) : allowedEvents;
|
|
912
1243
|
return typeof resolved === "string" ? [resolved] : resolved;
|
|
913
1244
|
}
|
|
1245
|
+
/**
|
|
1246
|
+
* Creates reusable, standalone {@link DecisionLogic}: an actor that, when
|
|
1247
|
+
* run, resolves to exactly one currently-legal {@link ChosenEvent} by
|
|
1248
|
+
* calling the host `decide` executor (passed here as `execute`, or supplied
|
|
1249
|
+
* later via {@link DecisionLogic.withExecutor}, `machine.provide(...)`, or
|
|
1250
|
+
* `runAgent`'s `decide` option). Register the result under `actors:` and
|
|
1251
|
+
* invoke it by name; for a one-off, state-local decision, prefer the
|
|
1252
|
+
* `agent.decide` builtin invoke instead — it needs no separate declaration
|
|
1253
|
+
* and types `allowedEvents` against the machine's own event schemas.
|
|
1254
|
+
*
|
|
1255
|
+
* @example
|
|
1256
|
+
* ```ts
|
|
1257
|
+
* import { createDecisionLogic } from '@statelyai/agent';
|
|
1258
|
+
* import { z } from 'zod';
|
|
1259
|
+
*
|
|
1260
|
+
* export const chooseMove = createDecisionLogic({
|
|
1261
|
+
* schemas: { input: z.object({ playerHp: z.number(), enemyHp: z.number() }) },
|
|
1262
|
+
* model: 'openai/gpt-5.4-mini',
|
|
1263
|
+
* system: 'You are playing a turn-based game. Choose exactly one legal move.',
|
|
1264
|
+
* prompt: ({ input }) => `Player HP: ${input.playerHp}\nEnemy HP: ${input.enemyHp}`,
|
|
1265
|
+
* allowedEvents: ({ input }) =>
|
|
1266
|
+
* input.playerHp <= 6
|
|
1267
|
+
* ? ['ATTACK', 'DEFEND', 'HEAL', 'FLEE']
|
|
1268
|
+
* : ['ATTACK', 'DEFEND', 'FLEE'],
|
|
1269
|
+
* });
|
|
1270
|
+
* ```
|
|
1271
|
+
*/
|
|
1272
|
+
function createDecisionLogic(config, execute) {
|
|
1273
|
+
const maxRetries = config.maxRetries ?? 2;
|
|
1274
|
+
const request = (input) => {
|
|
1275
|
+
const parsedInput = config.schemas ? validateSchemaSync(config.schemas.input, input) : input;
|
|
1276
|
+
const args = { input: parsedInput };
|
|
1277
|
+
const allowedEventTypes = resolveAllowedEventTypes(config.allowedEvents, parsedInput);
|
|
1278
|
+
return {
|
|
1279
|
+
kind: "decision",
|
|
1280
|
+
id: "",
|
|
1281
|
+
name: resolveTextLogicValue(config.name, args),
|
|
1282
|
+
input: parsedInput,
|
|
1283
|
+
model: resolveTextLogicValue(config.model, args),
|
|
1284
|
+
system: resolveTextLogicValue(config.system, args),
|
|
1285
|
+
prompt: resolveTextLogicValue(config.prompt, args),
|
|
1286
|
+
messages: resolveTextLogicValue(config.messages, args),
|
|
1287
|
+
events: (allowedEventTypes ?? []).map((type) => ({
|
|
1288
|
+
type,
|
|
1289
|
+
toolName: sanitizeEventToolName(type)
|
|
1290
|
+
})),
|
|
1291
|
+
attempts: [],
|
|
1292
|
+
temperature: resolveTextLogicValue(config.temperature, args),
|
|
1293
|
+
maxOutputTokens: resolveTextLogicValue(config.maxOutputTokens, args),
|
|
1294
|
+
topP: resolveTextLogicValue(config.topP, args),
|
|
1295
|
+
topK: resolveTextLogicValue(config.topK, args),
|
|
1296
|
+
seed: resolveTextLogicValue(config.seed, args),
|
|
1297
|
+
stopSequences: resolveTextLogicValue(config.stopSequences, args),
|
|
1298
|
+
metadata: resolveTextLogicValue(config.metadata, args)
|
|
1299
|
+
};
|
|
1300
|
+
};
|
|
1301
|
+
const logic = (0, xstate.createAsyncLogic)({ run: async ({ input, signal }) => {
|
|
1302
|
+
if (!execute) throw new Error("Decision logic has no host execution. Pass an executor as the second argument to createDecisionLogic(...), provide a runtime adapter, or bind it through runAgent/provideExecutors, or call resolveDecision from your host.");
|
|
1303
|
+
const allowedEventTypes = resolveAllowedEventTypes(config.allowedEvents, input);
|
|
1304
|
+
if (allowedEventTypes === void 0) throw new Error("Decision logic has omitted `allowedEvents`, which means \"all currently-legal events\" — but that requires a snapshot-aware host (runAgent or the step path) to resolve. Under a bare createActor(...), declare `allowedEvents` explicitly on this logic to use it here.");
|
|
1305
|
+
if (allowedEventTypes.some(isEventPattern)) throw new Error("Decision logic uses wildcard `allowedEvents` patterns, which expand against the live snapshot — that requires a snapshot-aware host (runAgent or the step path). Under a bare createActor(...), list event types explicitly.");
|
|
1306
|
+
return resolveDecision(request(input), { decide: execute }, {
|
|
1307
|
+
maxRetries,
|
|
1308
|
+
signal
|
|
1309
|
+
});
|
|
1310
|
+
} });
|
|
1311
|
+
const decisionLogic = Object.assign(logic, {
|
|
1312
|
+
kind: "statelyai.decisionLogic",
|
|
1313
|
+
maxRetries,
|
|
1314
|
+
request,
|
|
1315
|
+
allowedEventTypes: (input) => resolveAllowedEventTypes(config.allowedEvents, input),
|
|
1316
|
+
withExecutor(nextExecute) {
|
|
1317
|
+
return createDecisionLogic(config, nextExecute);
|
|
1318
|
+
}
|
|
1319
|
+
});
|
|
1320
|
+
if (execute) executorBoundLogics.add(decisionLogic);
|
|
1321
|
+
return decisionLogic;
|
|
1322
|
+
}
|
|
914
1323
|
/** Type guard: true for any actor logic built by createDecisionLogic/createDecideActor (checks the `kind` marker). @internal */
|
|
915
1324
|
function isDecisionLogic(value) {
|
|
916
1325
|
return !!value && typeof value === "object" && value.kind === "statelyai.decisionLogic" && typeof value.request === "function";
|
|
917
1326
|
}
|
|
918
1327
|
/**
|
|
1328
|
+
* Builds an {@link AgentDecisionRequest} for {@link resolveDecision}, filling
|
|
1329
|
+
* in the fields a hand-written literal has to restate every time: `kind`, an
|
|
1330
|
+
* `id`, an empty `attempts` list, and each candidate event's `toolName`.
|
|
1331
|
+
*
|
|
1332
|
+
* @example
|
|
1333
|
+
* ```ts
|
|
1334
|
+
* const chosen = await resolveDecision(
|
|
1335
|
+
* createDecisionRequest({
|
|
1336
|
+
* name: 'judge',
|
|
1337
|
+
* model: 'reviewer',
|
|
1338
|
+
* prompt: `Judge this draft.\n\n${snapshot.context.draft}`,
|
|
1339
|
+
* events: getAcceptedEvents(snapshot),
|
|
1340
|
+
* }),
|
|
1341
|
+
* { decide },
|
|
1342
|
+
* { canTake: (event) => snapshot.can(event) },
|
|
1343
|
+
* );
|
|
1344
|
+
* ```
|
|
1345
|
+
*/
|
|
1346
|
+
function createDecisionRequest(options) {
|
|
1347
|
+
const { id, name, events, attempts, ...rest } = options;
|
|
1348
|
+
return {
|
|
1349
|
+
...rest,
|
|
1350
|
+
kind: "decision",
|
|
1351
|
+
id: id ?? name ?? "decision",
|
|
1352
|
+
...name === void 0 ? {} : { name },
|
|
1353
|
+
events: dedupeToolNames(events.map((event) => typeof event === "string" ? {
|
|
1354
|
+
type: event,
|
|
1355
|
+
toolName: sanitizeEventToolName(event)
|
|
1356
|
+
} : event)),
|
|
1357
|
+
attempts: attempts ?? []
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
function dedupeToolNames(candidates) {
|
|
1361
|
+
const taken = /* @__PURE__ */ new Set();
|
|
1362
|
+
return candidates.map((candidate) => {
|
|
1363
|
+
if (candidate.toolName === void 0) return candidate;
|
|
1364
|
+
let toolName = candidate.toolName;
|
|
1365
|
+
for (let n = 2; taken.has(toolName); n++) {
|
|
1366
|
+
const suffix = `_${n}`;
|
|
1367
|
+
toolName = `${candidate.toolName.slice(0, 64 - suffix.length)}${suffix}`;
|
|
1368
|
+
}
|
|
1369
|
+
taken.add(toolName);
|
|
1370
|
+
return toolName === candidate.toolName ? candidate : {
|
|
1371
|
+
...candidate,
|
|
1372
|
+
toolName
|
|
1373
|
+
};
|
|
1374
|
+
});
|
|
1375
|
+
}
|
|
1376
|
+
/**
|
|
919
1377
|
* Thrown by {@link resolveDecision} when every attempt (up to
|
|
920
1378
|
* `maxRetries + 1` of them) fails one of the three checks recorded in
|
|
921
1379
|
* {@link DecisionAttempt.failure}. Carries the full `attempts` list for
|
|
922
1380
|
* diagnostics; a machine typically routes this via the decision invoke's
|
|
923
1381
|
* `onError`.
|
|
924
1382
|
*/
|
|
925
|
-
var AgentDecisionExhaustedError = class extends
|
|
1383
|
+
var AgentDecisionExhaustedError = class extends AgentError {
|
|
926
1384
|
attempts;
|
|
927
1385
|
constructor(attempts) {
|
|
928
1386
|
super("decision-exhausted", `Decision exhausted after ${attempts.length} attempt${attempts.length === 1 ? "" : "s"}: ` + attempts.map((attempt) => attempt.reason).join("; "));
|
|
@@ -975,7 +1433,7 @@ function renderDecisionAttempts(request) {
|
|
|
975
1433
|
*/
|
|
976
1434
|
async function resolveDecision(request, executors, options = {}) {
|
|
977
1435
|
const executor = executors?.decide;
|
|
978
|
-
if (typeof executor !== "function") throw new
|
|
1436
|
+
if (typeof executor !== "function") throw new AgentError("missing-decide-executor", "resolveDecision(request, executors, ...) takes the EXECUTOR SET (the same object executeAgentRequest takes), and this one has no 'decide' function. Build one with createAiSdkExecutors({ models }), or pass { decide }.");
|
|
979
1437
|
const maxRetries = options.maxRetries ?? 2;
|
|
980
1438
|
const attempts = [];
|
|
981
1439
|
const eventsByType = new Map(request.events.map((event) => [event.type, event]));
|
|
@@ -983,8 +1441,8 @@ async function resolveDecision(request, executors, options = {}) {
|
|
|
983
1441
|
options.signal?.throwIfAborted();
|
|
984
1442
|
const result = await executor({
|
|
985
1443
|
...request,
|
|
986
|
-
|
|
987
|
-
|
|
1444
|
+
name: request.name ?? (request.id || "agent.decide"),
|
|
1445
|
+
attempts: [...attempts]
|
|
988
1446
|
}, {
|
|
989
1447
|
...options.signal !== void 0 ? { signal: options.signal } : {},
|
|
990
1448
|
...request.id ? { requestId: request.id } : {}
|
|
@@ -1030,6 +1488,12 @@ async function resolveDecision(request, executors, options = {}) {
|
|
|
1030
1488
|
throw new AgentDecisionExhaustedError(attempts);
|
|
1031
1489
|
}
|
|
1032
1490
|
//#endregion
|
|
1491
|
+
Object.defineProperty(exports, "AGENT_MESSAGES_EVENT_TYPE", {
|
|
1492
|
+
enumerable: true,
|
|
1493
|
+
get: function() {
|
|
1494
|
+
return AGENT_MESSAGES_EVENT_TYPE;
|
|
1495
|
+
}
|
|
1496
|
+
});
|
|
1033
1497
|
Object.defineProperty(exports, "AGENT_USAGE_TOKEN_FIELDS", {
|
|
1034
1498
|
enumerable: true,
|
|
1035
1499
|
get: function() {
|
|
@@ -1042,22 +1506,40 @@ Object.defineProperty(exports, "AgentDecisionExhaustedError", {
|
|
|
1042
1506
|
return AgentDecisionExhaustedError;
|
|
1043
1507
|
}
|
|
1044
1508
|
});
|
|
1509
|
+
Object.defineProperty(exports, "AgentError", {
|
|
1510
|
+
enumerable: true,
|
|
1511
|
+
get: function() {
|
|
1512
|
+
return AgentError;
|
|
1513
|
+
}
|
|
1514
|
+
});
|
|
1515
|
+
Object.defineProperty(exports, "AgentInvalidEventPayloadError", {
|
|
1516
|
+
enumerable: true,
|
|
1517
|
+
get: function() {
|
|
1518
|
+
return AgentInvalidEventPayloadError;
|
|
1519
|
+
}
|
|
1520
|
+
});
|
|
1521
|
+
Object.defineProperty(exports, "AgentTruncatedError", {
|
|
1522
|
+
enumerable: true,
|
|
1523
|
+
get: function() {
|
|
1524
|
+
return AgentTruncatedError;
|
|
1525
|
+
}
|
|
1526
|
+
});
|
|
1045
1527
|
Object.defineProperty(exports, "DECIDE_ACTOR", {
|
|
1046
1528
|
enumerable: true,
|
|
1047
1529
|
get: function() {
|
|
1048
1530
|
return DECIDE_ACTOR;
|
|
1049
1531
|
}
|
|
1050
1532
|
});
|
|
1051
|
-
Object.defineProperty(exports, "
|
|
1533
|
+
Object.defineProperty(exports, "DEFAULT_AGENT_EXECUTORS", {
|
|
1052
1534
|
enumerable: true,
|
|
1053
1535
|
get: function() {
|
|
1054
|
-
return
|
|
1536
|
+
return DEFAULT_AGENT_EXECUTORS;
|
|
1055
1537
|
}
|
|
1056
1538
|
});
|
|
1057
|
-
Object.defineProperty(exports, "
|
|
1539
|
+
Object.defineProperty(exports, "GENERATE_TEXT_ACTOR", {
|
|
1058
1540
|
enumerable: true,
|
|
1059
1541
|
get: function() {
|
|
1060
|
-
return
|
|
1542
|
+
return GENERATE_TEXT_ACTOR;
|
|
1061
1543
|
}
|
|
1062
1544
|
});
|
|
1063
1545
|
Object.defineProperty(exports, "STREAM_TEXT_ACTOR", {
|
|
@@ -1072,10 +1554,16 @@ Object.defineProperty(exports, "USER_INPUT_ACTOR", {
|
|
|
1072
1554
|
return USER_INPUT_ACTOR;
|
|
1073
1555
|
}
|
|
1074
1556
|
});
|
|
1075
|
-
Object.defineProperty(exports, "
|
|
1557
|
+
Object.defineProperty(exports, "agentMessagesEventSchema", {
|
|
1076
1558
|
enumerable: true,
|
|
1077
1559
|
get: function() {
|
|
1078
|
-
return
|
|
1560
|
+
return agentMessagesEventSchema;
|
|
1561
|
+
}
|
|
1562
|
+
});
|
|
1563
|
+
Object.defineProperty(exports, "appendMessages", {
|
|
1564
|
+
enumerable: true,
|
|
1565
|
+
get: function() {
|
|
1566
|
+
return appendMessages;
|
|
1079
1567
|
}
|
|
1080
1568
|
});
|
|
1081
1569
|
Object.defineProperty(exports, "assistantMessage", {
|
|
@@ -1108,6 +1596,18 @@ Object.defineProperty(exports, "createDecideActor", {
|
|
|
1108
1596
|
return createDecideActor;
|
|
1109
1597
|
}
|
|
1110
1598
|
});
|
|
1599
|
+
Object.defineProperty(exports, "createDecisionLogic", {
|
|
1600
|
+
enumerable: true,
|
|
1601
|
+
get: function() {
|
|
1602
|
+
return createDecisionLogic;
|
|
1603
|
+
}
|
|
1604
|
+
});
|
|
1605
|
+
Object.defineProperty(exports, "createDecisionRequest", {
|
|
1606
|
+
enumerable: true,
|
|
1607
|
+
get: function() {
|
|
1608
|
+
return createDecisionRequest;
|
|
1609
|
+
}
|
|
1610
|
+
});
|
|
1111
1611
|
Object.defineProperty(exports, "createTextLogic", {
|
|
1112
1612
|
enumerable: true,
|
|
1113
1613
|
get: function() {
|
|
@@ -1144,10 +1644,10 @@ Object.defineProperty(exports, "getAcceptedEvents", {
|
|
|
1144
1644
|
return getAcceptedEvents;
|
|
1145
1645
|
}
|
|
1146
1646
|
});
|
|
1147
|
-
Object.defineProperty(exports, "
|
|
1647
|
+
Object.defineProperty(exports, "getAgentExecutionOptions", {
|
|
1148
1648
|
enumerable: true,
|
|
1149
1649
|
get: function() {
|
|
1150
|
-
return
|
|
1650
|
+
return getAgentExecutionOptions;
|
|
1151
1651
|
}
|
|
1152
1652
|
});
|
|
1153
1653
|
Object.defineProperty(exports, "getAgentOutputMode", {
|
|
@@ -1156,6 +1656,12 @@ Object.defineProperty(exports, "getAgentOutputMode", {
|
|
|
1156
1656
|
return getAgentOutputMode;
|
|
1157
1657
|
}
|
|
1158
1658
|
});
|
|
1659
|
+
Object.defineProperty(exports, "getCallFinishReason", {
|
|
1660
|
+
enumerable: true,
|
|
1661
|
+
get: function() {
|
|
1662
|
+
return getCallFinishReason;
|
|
1663
|
+
}
|
|
1664
|
+
});
|
|
1159
1665
|
Object.defineProperty(exports, "getCallUsage", {
|
|
1160
1666
|
enumerable: true,
|
|
1161
1667
|
get: function() {
|
|
@@ -1180,16 +1686,16 @@ Object.defineProperty(exports, "getMachineIdlePredicate", {
|
|
|
1180
1686
|
return getMachineIdlePredicate;
|
|
1181
1687
|
}
|
|
1182
1688
|
});
|
|
1183
|
-
Object.defineProperty(exports, "
|
|
1689
|
+
Object.defineProperty(exports, "getMachineStructuralHash", {
|
|
1184
1690
|
enumerable: true,
|
|
1185
1691
|
get: function() {
|
|
1186
|
-
return
|
|
1692
|
+
return getMachineStructuralHash;
|
|
1187
1693
|
}
|
|
1188
1694
|
});
|
|
1189
|
-
Object.defineProperty(exports, "
|
|
1695
|
+
Object.defineProperty(exports, "getMessageText", {
|
|
1190
1696
|
enumerable: true,
|
|
1191
1697
|
get: function() {
|
|
1192
|
-
return
|
|
1698
|
+
return getMessageText;
|
|
1193
1699
|
}
|
|
1194
1700
|
});
|
|
1195
1701
|
Object.defineProperty(exports, "getRegisteredAgentExecutionOptions", {
|
|
@@ -1204,12 +1710,30 @@ Object.defineProperty(exports, "getStateMeta", {
|
|
|
1204
1710
|
return getStateMeta;
|
|
1205
1711
|
}
|
|
1206
1712
|
});
|
|
1713
|
+
Object.defineProperty(exports, "getStatePath", {
|
|
1714
|
+
enumerable: true,
|
|
1715
|
+
get: function() {
|
|
1716
|
+
return getStatePath;
|
|
1717
|
+
}
|
|
1718
|
+
});
|
|
1719
|
+
Object.defineProperty(exports, "isAgentMessages", {
|
|
1720
|
+
enumerable: true,
|
|
1721
|
+
get: function() {
|
|
1722
|
+
return isAgentMessages;
|
|
1723
|
+
}
|
|
1724
|
+
});
|
|
1207
1725
|
Object.defineProperty(exports, "isDecisionLogic", {
|
|
1208
1726
|
enumerable: true,
|
|
1209
1727
|
get: function() {
|
|
1210
1728
|
return isDecisionLogic;
|
|
1211
1729
|
}
|
|
1212
1730
|
});
|
|
1731
|
+
Object.defineProperty(exports, "isRecord", {
|
|
1732
|
+
enumerable: true,
|
|
1733
|
+
get: function() {
|
|
1734
|
+
return isRecord;
|
|
1735
|
+
}
|
|
1736
|
+
});
|
|
1213
1737
|
Object.defineProperty(exports, "isStandardSchema", {
|
|
1214
1738
|
enumerable: true,
|
|
1215
1739
|
get: function() {
|
|
@@ -1234,10 +1758,10 @@ Object.defineProperty(exports, "machineIdlePredicates", {
|
|
|
1234
1758
|
return machineIdlePredicates;
|
|
1235
1759
|
}
|
|
1236
1760
|
});
|
|
1237
|
-
Object.defineProperty(exports, "
|
|
1761
|
+
Object.defineProperty(exports, "messagesSchema", {
|
|
1238
1762
|
enumerable: true,
|
|
1239
1763
|
get: function() {
|
|
1240
|
-
return
|
|
1764
|
+
return messagesSchema;
|
|
1241
1765
|
}
|
|
1242
1766
|
});
|
|
1243
1767
|
Object.defineProperty(exports, "missingActor", {
|
|
@@ -1294,6 +1818,12 @@ Object.defineProperty(exports, "resolveMachineVersion", {
|
|
|
1294
1818
|
return resolveMachineVersion;
|
|
1295
1819
|
}
|
|
1296
1820
|
});
|
|
1821
|
+
Object.defineProperty(exports, "setAgentExecutionOptions", {
|
|
1822
|
+
enumerable: true,
|
|
1823
|
+
get: function() {
|
|
1824
|
+
return setAgentExecutionOptions;
|
|
1825
|
+
}
|
|
1826
|
+
});
|
|
1297
1827
|
Object.defineProperty(exports, "systemMessage", {
|
|
1298
1828
|
enumerable: true,
|
|
1299
1829
|
get: function() {
|