@statelyai/agent 2.0.0-alpha.25 → 2.0.0-alpha.27
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 -4
- package/dist/ai-sdk.d.cts +6 -6
- package/dist/ai-sdk.d.mts +6 -6
- package/dist/ai-sdk.mjs +4 -4
- package/dist/{decision-CM9CYZ6t.cjs → decision-CmM8-HHz.cjs} +10 -9
- package/dist/{decision-DzSv1NpD.mjs → decision-RQ0iv0oZ.mjs} +10 -9
- package/dist/{event-log-store-Ba2fsc4E.mjs → event-log-store-2pAFiWuc.mjs} +1 -1
- package/dist/{event-log-store-DvOcs10J.d.cts → event-log-store-2xzFmgr2.d.cts} +1 -1
- package/dist/{event-log-store-DxyVgnas.d.mts → event-log-store-BIPOz-di.d.mts} +1 -1
- package/dist/{event-log-store-DQmImLYk.cjs → event-log-store-C-COhIXP.cjs} +1 -1
- package/dist/index.cjs +13 -195
- package/dist/index.d.cts +25 -105
- package/dist/index.d.mts +25 -105
- package/dist/index.mjs +12 -189
- package/dist/log.cjs +1 -1
- package/dist/log.d.cts +1 -1
- package/dist/log.d.mts +1 -1
- package/dist/log.mjs +1 -1
- package/dist/machines.cjs +4 -7
- package/dist/machines.d.cts +5 -7
- package/dist/machines.d.mts +5 -7
- package/dist/machines.mjs +4 -7
- package/dist/openai.cjs +3 -3
- package/dist/openai.d.cts +5 -5
- package/dist/openai.d.mts +5 -5
- package/dist/openai.mjs +3 -3
- package/dist/otel.cjs +2 -2
- package/dist/otel.d.cts +3 -3
- package/dist/otel.d.mts +3 -3
- package/dist/otel.mjs +2 -2
- package/dist/{run-agent-CG343kFs.d.mts → run-agent-ByO-Bxyd.d.cts} +184 -179
- package/dist/{run-agent-BOfoJg5B.d.cts → run-agent-COXXc0RW.d.mts} +184 -179
- package/dist/{run-agent-Cb1-LS5P.cjs → run-agent-Dmf8GV2d.cjs} +539 -737
- package/dist/{run-agent-DvDQd4If.mjs → run-agent-xNTxpt82.mjs} +537 -705
- package/dist/{setup-agent-CFW-jPI8.mjs → setup-agent-BuanDXeh.mjs} +6 -28
- package/dist/{setup-agent-CPf7MxnG.cjs → setup-agent-DV4cTou-.cjs} +6 -28
- package/dist/testing.cjs +285 -170
- package/dist/testing.d.cts +39 -22
- package/dist/testing.d.mts +39 -22
- package/dist/testing.mjs +270 -155
- package/dist/{text-logic-Bl_ZRxDi.mjs → text-logic-CIqHAmBi.mjs} +10 -26
- package/dist/{text-logic-BFEOxyxv.d.cts → text-logic-D-czVPYp.d.mts} +27 -28
- package/dist/{text-logic-hZSiRUo1.d.mts → text-logic-IjfYy9DP.d.cts} +27 -28
- package/dist/{text-logic-C8xyr6Ag.cjs → text-logic-f5F5sCdc.cjs} +9 -43
- package/dist/{types-B1Qs2q3q.d.cts → types-BgNbNKnJ.d.mts} +3 -3
- package/dist/{types-tA3SdczG.d.mts → types-hR5f5YXa.d.cts} +3 -3
- package/dist/{utils-C1p37X_c.d.mts → utils-_q7F-AZu.d.mts} +3 -103
- package/dist/{utils-RalRn9sk.d.cts → utils-o4YRdHJX.d.cts} +3 -103
- package/dist/validate.cjs +0 -8
- package/dist/validate.mjs +0 -8
- package/package.json +5 -4
- package/readme.md +8 -4
- package/schemas/agent-workflow.json +0 -8
package/dist/testing.cjs
CHANGED
|
@@ -1,7 +1,253 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_text_logic = require("./text-logic-
|
|
3
|
-
const require_decision = require("./decision-
|
|
4
|
-
const require_run_agent = require("./run-agent-
|
|
2
|
+
const require_text_logic = require("./text-logic-f5F5sCdc.cjs");
|
|
3
|
+
const require_decision = require("./decision-CmM8-HHz.cjs");
|
|
4
|
+
const require_run_agent = require("./run-agent-Dmf8GV2d.cjs");
|
|
5
|
+
require("./validate.cjs");
|
|
6
|
+
let xstate = require("xstate");
|
|
7
|
+
//#region src/steps.ts
|
|
8
|
+
/**
|
|
9
|
+
* Internal: the pure `(state, event) => (nextState, requests)` view of an
|
|
10
|
+
* agent machine, used by model-free simulation (`simulateAgent`) and tests.
|
|
11
|
+
* An {@link AgentStep} is a snapshot plus the model requests the machine is
|
|
12
|
+
* now waiting on; nothing here executes anything. Hosts use
|
|
13
|
+
* `createAgentRuntime`.
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
/** @internal Normalizes current and legacy XState invoke effect shapes. */
|
|
17
|
+
function getInvokeEffectMetadata(action) {
|
|
18
|
+
if (action.type === "@xstate.spawn") return action;
|
|
19
|
+
if (action.type === "xstate.spawnChild") {
|
|
20
|
+
const params = action.params;
|
|
21
|
+
return params ? {
|
|
22
|
+
...params,
|
|
23
|
+
logic: action.logic
|
|
24
|
+
} : void 0;
|
|
25
|
+
}
|
|
26
|
+
if (action.type === "@xstate.start" && typeof action.src === "string") return action;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The invokes a snapshot is still waiting on, lowered to the same spawn-effect
|
|
30
|
+
* shape {@link getAgentRequests} reads. Derived from the snapshot's live
|
|
31
|
+
* children rather than the last transition's actions, so a state that invokes
|
|
32
|
+
* several actors at once (an `invoke: [...]` array, or parallel regions each
|
|
33
|
+
* invoking) keeps reporting the siblings after the first one settles —
|
|
34
|
+
* including when the settled invoke's `onDone` is targetless and starts
|
|
35
|
+
* nothing new.
|
|
36
|
+
*
|
|
37
|
+
* Entries are ordered by invoke id, so a walker that resolves one invoke per
|
|
38
|
+
* step is deterministic.
|
|
39
|
+
*
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
function getPendingInvokes(snapshot) {
|
|
43
|
+
const children = snapshot.children ?? {};
|
|
44
|
+
return Object.keys(children).sort().flatMap((id) => {
|
|
45
|
+
const child = children[id];
|
|
46
|
+
const childSnapshot = child?.getSnapshot?.();
|
|
47
|
+
if (!child || childSnapshot?.status !== "active") return [];
|
|
48
|
+
return [{
|
|
49
|
+
type: "@xstate.spawn",
|
|
50
|
+
id,
|
|
51
|
+
src: child.src,
|
|
52
|
+
input: childSnapshot.input,
|
|
53
|
+
logic: child.logic
|
|
54
|
+
}];
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Scans a set of executable actions (as returned by xstate's `transition`/
|
|
59
|
+
* `initialTransition`) for spawned `TextLogic`/`DecisionLogic` invokes and
|
|
60
|
+
* lowers each into an {@link AgentStepRequest}. Pass `options.machine` to
|
|
61
|
+
* pre-fill `schemas`/`actors` from the machine's registered `setupAgent`
|
|
62
|
+
* options instead of passing them by hand.
|
|
63
|
+
* `options.snapshot` is required to resolve a decision's candidate events
|
|
64
|
+
* (intersecting declared `allowedEvents` with what's currently legal) — omit
|
|
65
|
+
* it and decision requests report an empty `events` list.
|
|
66
|
+
*
|
|
67
|
+
* @internal
|
|
68
|
+
*/
|
|
69
|
+
function getAgentRequests(actions, { machine, ...options } = {}) {
|
|
70
|
+
if (machine) options = {
|
|
71
|
+
...require_text_logic.getRegisteredAgentExecutionOptions(machine),
|
|
72
|
+
...options
|
|
73
|
+
};
|
|
74
|
+
return actions.flatMap((action) => {
|
|
75
|
+
const params = getInvokeEffectMetadata(action);
|
|
76
|
+
if (!params || typeof params.src !== "string") return [];
|
|
77
|
+
if (typeof params.id !== "string" || params.id.length === 0) throw new Error(`Agent invoke '${params.src}' must define a durable string id.`);
|
|
78
|
+
const registeredLogic = require_text_logic.isTextLogic(params.logic) || require_decision.isDecisionLogic(params.logic) ? params.logic : options.actors?.[params.src];
|
|
79
|
+
if (require_decision.isDecisionLogic(registeredLogic)) {
|
|
80
|
+
const decisionRequest = registeredLogic.request(params.input);
|
|
81
|
+
const allowedEventTypes = registeredLogic.allowedEventTypes?.(params.input);
|
|
82
|
+
const events = options.snapshot ? require_decision.getAcceptedEvents(options.snapshot, {
|
|
83
|
+
events: options.events,
|
|
84
|
+
schemas: options.schemas,
|
|
85
|
+
eventTypes: allowedEventTypes
|
|
86
|
+
}) : [];
|
|
87
|
+
return [{
|
|
88
|
+
...decisionRequest,
|
|
89
|
+
id: params.id,
|
|
90
|
+
events
|
|
91
|
+
}];
|
|
92
|
+
}
|
|
93
|
+
const textLogic = require_text_logic.isTextLogic(registeredLogic) ? registeredLogic : void 0;
|
|
94
|
+
const input = textLogic ? textLogic.request(params.input) : void 0;
|
|
95
|
+
if (!input) return [];
|
|
96
|
+
return [{
|
|
97
|
+
kind: "text",
|
|
98
|
+
id: params.id,
|
|
99
|
+
src: params.src,
|
|
100
|
+
...textLogic ? { mode: textLogic.mode } : {},
|
|
101
|
+
input,
|
|
102
|
+
tools: input.tools ?? {},
|
|
103
|
+
events: []
|
|
104
|
+
}];
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Builds the canonical `xstate.done.actor` event xstate's `transition()`
|
|
109
|
+
* expects to resolve a spawned invoke — including the child session identity
|
|
110
|
+
* that rejects a stale completion from an earlier incarnation.
|
|
111
|
+
*
|
|
112
|
+
* @internal
|
|
113
|
+
*/
|
|
114
|
+
function doneEvent(snapshot, request, output) {
|
|
115
|
+
const id = typeof request === "string" ? request : request.id;
|
|
116
|
+
const child = snapshot.children[id];
|
|
117
|
+
return {
|
|
118
|
+
type: "xstate.done.actor",
|
|
119
|
+
actorId: id,
|
|
120
|
+
...typeof child?.sessionId === "string" ? { sessionId: child.sessionId } : {},
|
|
121
|
+
output
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The `xstate.error.actor` counterpart of {@link doneEvent} — the event a
|
|
126
|
+
* failed invoke delivers, which `onError` transitions observe. Carries the
|
|
127
|
+
* same child session identity, so xstate does not discard it as stale.
|
|
128
|
+
*
|
|
129
|
+
* @internal
|
|
130
|
+
*/
|
|
131
|
+
function errorEvent(snapshot, request, error) {
|
|
132
|
+
const id = typeof request === "string" ? request : request.id;
|
|
133
|
+
const child = snapshot.children[id];
|
|
134
|
+
return {
|
|
135
|
+
type: "xstate.error.actor",
|
|
136
|
+
actorId: id,
|
|
137
|
+
...typeof child?.sessionId === "string" ? { sessionId: child.sessionId } : {},
|
|
138
|
+
error
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Applies a request's `output` as a done event (or, with `outcome: 'error'`,
|
|
143
|
+
* its `output` as the invoke's error) via `transition(...)`, returning the raw
|
|
144
|
+
* `[snapshot, actions]` tuple. Lower-level than {@link resolveAgentStep} /
|
|
145
|
+
* {@link rejectAgentStep} — those helpers wrap this and also run
|
|
146
|
+
* {@link getAgentRequests} to produce the next {@link AgentStep}.
|
|
147
|
+
*
|
|
148
|
+
* @internal
|
|
149
|
+
*/
|
|
150
|
+
function transitionResult(logic, snapshot, request, output, outcome = "output") {
|
|
151
|
+
const event = outcome === "error" ? errorEvent(snapshot, request, output) : doneEvent(snapshot, request, output);
|
|
152
|
+
const result = (0, xstate.transition)(logic, snapshot, event);
|
|
153
|
+
applyFinalStateOutput(logic, result[0], event);
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Starts a machine and returns its first {@link AgentStep} — the step-path
|
|
158
|
+
* equivalent of `initialTransition` plus request discovery. Begins the
|
|
159
|
+
* durable/per-model-call-checkpoint loop: resolve each `step.requests` entry
|
|
160
|
+
* (via {@link executeAgentRequest} for `kind: 'text'`, or
|
|
161
|
+
* {@link resolveDecision} for `kind: 'decision'`), then advance with
|
|
162
|
+
* {@link resolveAgentStep} or {@link transitionAgentStep}.
|
|
163
|
+
*/
|
|
164
|
+
function initialAgentStep(machine, input, options) {
|
|
165
|
+
const [snapshot, actions] = (0, xstate.initialTransition)(machine, input);
|
|
166
|
+
return createAgentStep(machine, snapshot, actions, require_text_logic.getRegisteredAgentExecutionOptions(machine, options));
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Applies an externally-sent event (e.g. a decision's chosen event, or a
|
|
170
|
+
* human's reply) and returns the next {@link AgentStep}. Accepts **either**
|
|
171
|
+
* a raw snapshot **or** a prior `AgentStep` as the second argument —
|
|
172
|
+
* `.snapshot` is unwrapped automatically, so callers can thread the whole
|
|
173
|
+
* step object through without manually plucking the snapshot out.
|
|
174
|
+
*/
|
|
175
|
+
function transitionAgentStep(machine, snapshotOrStep, event, options) {
|
|
176
|
+
const [nextSnapshot, actions] = (0, xstate.transition)(machine, isAgentStep(snapshotOrStep) ? snapshotOrStep.snapshot : snapshotOrStep, event);
|
|
177
|
+
return createAgentStep(machine, nextSnapshot, actions, require_text_logic.getRegisteredAgentExecutionOptions(machine, options));
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Applies a resolved text request's output (a `kind: 'text'`
|
|
181
|
+
* {@link AgentRequest} — not a decision) as a done event and returns the
|
|
182
|
+
* next {@link AgentStep}. For decisions, resolve with `resolveDecision`
|
|
183
|
+
* (which returns a {@link ChosenEvent}) and apply it with
|
|
184
|
+
* {@link transitionAgentStep} instead — a decision has no output value of
|
|
185
|
+
* its own to feed here.
|
|
186
|
+
*/
|
|
187
|
+
function resolveAgentStep(machine, step, request, output, options) {
|
|
188
|
+
const [snapshot, actions] = transitionResult(machine, step.snapshot, request, output);
|
|
189
|
+
return createAgentStep(machine, snapshot, actions, require_text_logic.getRegisteredAgentExecutionOptions(machine, options));
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Rejects a pending invoke — the failure counterpart of
|
|
193
|
+
* {@link resolveAgentStep}. Delivers `error` as the invoke's
|
|
194
|
+
* `xstate.error.actor` event, so the machine takes that invoke's `onError`
|
|
195
|
+
* transition and returns the next {@link AgentStep}. With no `onError` in
|
|
196
|
+
* scope the returned step's snapshot has `status: 'error'`, exactly as a live
|
|
197
|
+
* run's failed invoke does.
|
|
198
|
+
*
|
|
199
|
+
* A durable host replays a failed call with this: the log says the call
|
|
200
|
+
* failed, so the replay rejects the invoke instead of resolving it. Scripted
|
|
201
|
+
* playthroughs use it too — see `simulateAgent`'s `script.errors`.
|
|
202
|
+
*
|
|
203
|
+
* `error` can be any value. An `Error` instance is the usual one, but a plain
|
|
204
|
+
* object (`{ code: 'truncated' }`) reaches the `onError` transition as
|
|
205
|
+
* `event.error` untouched, so a machine that branches on a failure code is
|
|
206
|
+
* testable without constructing a provider's error class.
|
|
207
|
+
*/
|
|
208
|
+
function rejectAgentStep(machine, step, request, error, options) {
|
|
209
|
+
const [snapshot, actions] = transitionResult(machine, step.snapshot, request, error, "error");
|
|
210
|
+
return createAgentStep(machine, snapshot, actions, require_text_logic.getRegisteredAgentExecutionOptions(machine, options));
|
|
211
|
+
}
|
|
212
|
+
function createAgentStep(machine, snapshot, actions, options) {
|
|
213
|
+
applyFinalStateOutput(machine, snapshot);
|
|
214
|
+
return {
|
|
215
|
+
snapshot,
|
|
216
|
+
actions,
|
|
217
|
+
requests: getAgentRequests(getPendingInvokes(snapshot), {
|
|
218
|
+
...options,
|
|
219
|
+
snapshot
|
|
220
|
+
}),
|
|
221
|
+
done: snapshot.status === "done"
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function resolveStateValueConfig(config, value) {
|
|
225
|
+
if (typeof value === "string") return config.states?.[value];
|
|
226
|
+
if (!value || typeof value !== "object") return;
|
|
227
|
+
for (const [key, childValue] of Object.entries(value)) {
|
|
228
|
+
const childConfig = config.states?.[key];
|
|
229
|
+
if (!childConfig) continue;
|
|
230
|
+
if (childConfig.type === "final") return childConfig;
|
|
231
|
+
const nested = resolveStateValueConfig(childConfig, childValue);
|
|
232
|
+
if (nested) return nested;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function applyFinalStateOutput(logic, snapshot, event) {
|
|
236
|
+
const machineSnapshot = snapshot;
|
|
237
|
+
if (machineSnapshot.status !== "done" || machineSnapshot.output !== void 0 || !("config" in logic)) return;
|
|
238
|
+
const config = logic.config;
|
|
239
|
+
if (!config) return;
|
|
240
|
+
const output = resolveStateValueConfig(config, machineSnapshot.value)?.output;
|
|
241
|
+
if (output === void 0) return;
|
|
242
|
+
machineSnapshot.output = typeof output === "function" ? output({
|
|
243
|
+
context: machineSnapshot.context,
|
|
244
|
+
event
|
|
245
|
+
}) : output;
|
|
246
|
+
}
|
|
247
|
+
function isAgentStep(value) {
|
|
248
|
+
return !!value && typeof value === "object" && "snapshot" in value && "actions" in value && "requests" in value;
|
|
249
|
+
}
|
|
250
|
+
//#endregion
|
|
5
251
|
//#region src/verify.ts
|
|
6
252
|
const DECIDE_SRC = "agent.decide";
|
|
7
253
|
function normalizeInvokes(invoke) {
|
|
@@ -78,7 +324,7 @@ function checkDirectObjectSrc(ctx) {
|
|
|
78
324
|
code: "direct-object-src",
|
|
79
325
|
severity: "warning",
|
|
80
326
|
path: node.path,
|
|
81
|
-
message: `State '${node.path}' invokes a direct-object agent logic. Direct-object invoke srcs cannot be rebound by
|
|
327
|
+
message: `State '${node.path}' invokes a direct-object agent logic. Direct-object invoke srcs cannot be rebound by the agent runtime, 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.`
|
|
82
328
|
});
|
|
83
329
|
}
|
|
84
330
|
return out;
|
|
@@ -192,7 +438,7 @@ function assertAgentMachine(machine, options = {}) {
|
|
|
192
438
|
}
|
|
193
439
|
function pendingInvokes(step) {
|
|
194
440
|
const out = [];
|
|
195
|
-
for (const metadata of
|
|
441
|
+
for (const metadata of getPendingInvokes(step.snapshot)) if (typeof metadata.src === "string") out.push({
|
|
196
442
|
id: metadata.id,
|
|
197
443
|
src: metadata.src
|
|
198
444
|
});
|
|
@@ -252,7 +498,7 @@ async function simulateAgent(machine, options) {
|
|
|
252
498
|
errors: mapValues(options.script.errors ?? {}, (arr) => [...arr]),
|
|
253
499
|
events: [...options.script.events ?? []]
|
|
254
500
|
};
|
|
255
|
-
let step =
|
|
501
|
+
let step = initialAgentStep(machine, options.input);
|
|
256
502
|
const trail = [{ state: step.snapshot.value }];
|
|
257
503
|
for (let i = 0; i < maxSteps; i++) {
|
|
258
504
|
if (step.done) return {
|
|
@@ -290,7 +536,7 @@ async function simulateAgent(machine, options) {
|
|
|
290
536
|
}
|
|
291
537
|
const taken = takeFromQueue(script.text, request.src);
|
|
292
538
|
if (!taken.found) throw scriptDryError("text", request.src, request.id);
|
|
293
|
-
step =
|
|
539
|
+
step = resolveAgentStep(machine, step, request, asTextResult(taken.value));
|
|
294
540
|
trail.push({
|
|
295
541
|
state: step.snapshot.value,
|
|
296
542
|
resolvedRequest: {
|
|
@@ -315,7 +561,7 @@ async function simulateAgent(machine, options) {
|
|
|
315
561
|
}
|
|
316
562
|
const taken = takeFromQueue(script.invokes, invoke.src);
|
|
317
563
|
if (!taken.found) throw scriptDryError("invoke", invoke.src, invoke.id);
|
|
318
|
-
step =
|
|
564
|
+
step = resolveAgentStep(machine, step, invoke.id, taken.value);
|
|
319
565
|
trail.push({
|
|
320
566
|
state: step.snapshot.value,
|
|
321
567
|
resolvedRequest: {
|
|
@@ -331,7 +577,7 @@ async function simulateAgent(machine, options) {
|
|
|
331
577
|
if (external.length > 0) {
|
|
332
578
|
const event = external.shift();
|
|
333
579
|
if (!step.snapshot.can(event)) throw new Error(`simulateAgent: scripted external event '${event.type}' cannot be taken in state ${JSON.stringify(step.snapshot.value)} (no handler, or its guard rejected it). A live run's send would be silently dropped here; fix the script's \`events\` queue.`);
|
|
334
|
-
step =
|
|
580
|
+
step = transitionAgentStep(machine, step, event);
|
|
335
581
|
trail.push({
|
|
336
582
|
state: step.snapshot.value,
|
|
337
583
|
appliedEvent: event,
|
|
@@ -356,7 +602,7 @@ function queueKey(channel, src, id) {
|
|
|
356
602
|
}
|
|
357
603
|
function rejectScriptedRequest(machine, step, request, error, entry) {
|
|
358
604
|
const id = typeof request === "string" ? request : request.id;
|
|
359
|
-
const next =
|
|
605
|
+
const next = rejectAgentStep(machine, step, request, error);
|
|
360
606
|
if (next.snapshot.status === "error") throw unhandledScriptedError(error, entry.kind, entry.src, id);
|
|
361
607
|
entry.trail.push({
|
|
362
608
|
state: next.snapshot.value,
|
|
@@ -394,7 +640,7 @@ async function applyScriptedDecision(machine, step, request, decisionSrc, invoke
|
|
|
394
640
|
});
|
|
395
641
|
} catch (error) {
|
|
396
642
|
if (!(error instanceof require_decision.AgentDecisionExhaustedError)) throw error;
|
|
397
|
-
const next =
|
|
643
|
+
const next = rejectAgentStep(machine, step, request.id, error);
|
|
398
644
|
if (next.snapshot.status === "error") throw error;
|
|
399
645
|
trail.push({
|
|
400
646
|
state: next.snapshot.value,
|
|
@@ -402,7 +648,7 @@ async function applyScriptedDecision(machine, step, request, decisionSrc, invoke
|
|
|
402
648
|
});
|
|
403
649
|
return next;
|
|
404
650
|
}
|
|
405
|
-
const next =
|
|
651
|
+
const next = transitionAgentStep(machine, step, chosen);
|
|
406
652
|
const rejected = dequeued.slice(0, -1);
|
|
407
653
|
trail.push({
|
|
408
654
|
state: next.snapshot.value,
|
|
@@ -412,7 +658,7 @@ async function applyScriptedDecision(machine, step, request, decisionSrc, invoke
|
|
|
412
658
|
return next;
|
|
413
659
|
}
|
|
414
660
|
function findInvokeMetadata(step, id) {
|
|
415
|
-
for (const metadata of
|
|
661
|
+
for (const metadata of getPendingInvokes(step.snapshot)) {
|
|
416
662
|
if (metadata.id !== id) continue;
|
|
417
663
|
return {
|
|
418
664
|
...typeof metadata.src === "string" ? { src: metadata.src } : {},
|
|
@@ -472,7 +718,7 @@ async function explore(machine, options, stopWhen) {
|
|
|
472
718
|
reachedValues.push(snapshot.value);
|
|
473
719
|
}
|
|
474
720
|
};
|
|
475
|
-
const initial =
|
|
721
|
+
const initial = initialAgentStep(machine, options.input);
|
|
476
722
|
recordState(initial.snapshot);
|
|
477
723
|
if (stopWhen?.(initial.snapshot)) witness = [];
|
|
478
724
|
const advance = (step) => {
|
|
@@ -493,7 +739,7 @@ async function explore(machine, options, stopWhen) {
|
|
|
493
739
|
step: current,
|
|
494
740
|
blockedSrc: request.src
|
|
495
741
|
};
|
|
496
|
-
current =
|
|
742
|
+
current = resolveAgentStep(machine, current, request, asTextResult(textScript[request.src]));
|
|
497
743
|
recordState(current.snapshot);
|
|
498
744
|
continue;
|
|
499
745
|
}
|
|
@@ -520,7 +766,7 @@ async function explore(machine, options, stopWhen) {
|
|
|
520
766
|
step: current,
|
|
521
767
|
blockedSrc: invoke.src
|
|
522
768
|
};
|
|
523
|
-
current =
|
|
769
|
+
current = resolveAgentStep(machine, current, invoke.id, invokeOutputs[invoke.src]);
|
|
524
770
|
recordState(current.snapshot);
|
|
525
771
|
continue;
|
|
526
772
|
}
|
|
@@ -551,12 +797,12 @@ async function explore(machine, options, stopWhen) {
|
|
|
551
797
|
unexplored.push(`max-depth: stopped at path [${path.map((e) => e.type).join(", ")}]`);
|
|
552
798
|
return;
|
|
553
799
|
}
|
|
554
|
-
const failed =
|
|
800
|
+
const failed = rejectAgentStep(machine, settled, fork.id, errorScript[fork.src]);
|
|
555
801
|
recordState(failed.snapshot);
|
|
556
802
|
await visit(failed, [...path, { type: `xstate.error.actor.${fork.id}` }], depth + 1);
|
|
557
803
|
if (!fork.isDecision) {
|
|
558
804
|
if (fork.hasOutput) {
|
|
559
|
-
const resolved =
|
|
805
|
+
const resolved = resolveAgentStep(machine, settled, fork.id, fork.output);
|
|
560
806
|
recordState(resolved.snapshot);
|
|
561
807
|
await visit(resolved, path, depth + 1);
|
|
562
808
|
}
|
|
@@ -625,7 +871,7 @@ async function explore(machine, options, stopWhen) {
|
|
|
625
871
|
prunedByGuard++;
|
|
626
872
|
continue;
|
|
627
873
|
}
|
|
628
|
-
const next =
|
|
874
|
+
const next = transitionAgentStep(machine, settled, event);
|
|
629
875
|
recordState(next.snapshot);
|
|
630
876
|
await visit(next, [...path, event], depth + 1);
|
|
631
877
|
}
|
|
@@ -812,11 +1058,13 @@ function matcherFor(expected) {
|
|
|
812
1058
|
* @example Trajectory assertions in a test
|
|
813
1059
|
* ```ts
|
|
814
1060
|
* const statePath: unknown[] = [];
|
|
815
|
-
* const result = await
|
|
816
|
-
*
|
|
817
|
-
*
|
|
818
|
-
*
|
|
819
|
-
*
|
|
1061
|
+
* const result = await runToQuiescence(
|
|
1062
|
+
* createAgentRuntime(machine, {
|
|
1063
|
+
* executors,
|
|
1064
|
+
* onTransition: (snapshot) => statePath.push(snapshot.value),
|
|
1065
|
+
* }),
|
|
1066
|
+
* { input },
|
|
1067
|
+
* );
|
|
820
1068
|
*
|
|
821
1069
|
* const path = matchesTrajectory(statePath, ['prompting', 'drafting', 'sent']);
|
|
822
1070
|
* expect(path.matched, JSON.stringify(path.firstMiss)).toBe(true);
|
|
@@ -870,43 +1118,7 @@ function matchesTrajectory(actual, expected, options = {}) {
|
|
|
870
1118
|
};
|
|
871
1119
|
}
|
|
872
1120
|
//#endregion
|
|
873
|
-
//#region src/
|
|
874
|
-
/**
|
|
875
|
-
* Scripted executors — a deterministic stand-in for a model host.
|
|
876
|
-
*
|
|
877
|
-
* `createScriptedExecutors` builds a full `{ generateText, streamText, decide }`
|
|
878
|
-
* set that plays back canned answers keyed by
|
|
879
|
-
* request name, so `runAgent` (or `provideExecutors`, or a bare
|
|
880
|
-
* `TextLogic.execute`) runs with no API key and no network.
|
|
881
|
-
*
|
|
882
|
-
* One rule: entries for a name are consumed in order, and the last entry
|
|
883
|
-
* repeats forever.
|
|
884
|
-
*
|
|
885
|
-
* @module
|
|
886
|
-
*/
|
|
887
|
-
function toQueues(value) {
|
|
888
|
-
return new Map(Object.entries(value ?? {}).map(([name, entries]) => [name, {
|
|
889
|
-
entries: Array.isArray(entries) ? [...entries] : [entries],
|
|
890
|
-
index: 0
|
|
891
|
-
}]));
|
|
892
|
-
}
|
|
893
|
-
/**
|
|
894
|
-
* Takes the next entry for `name`, holding on the last entry once the list is
|
|
895
|
-
* spent. `undefined` means the script has no route for this name at all.
|
|
896
|
-
* @internal
|
|
897
|
-
*/
|
|
898
|
-
function takeEntry(queues, name) {
|
|
899
|
-
const queue = queues.get(name) ?? queues.get("*");
|
|
900
|
-
if (!queue || queue.entries.length === 0) return;
|
|
901
|
-
const entry = queue.entries[Math.min(queue.index, queue.entries.length - 1)];
|
|
902
|
-
queue.index++;
|
|
903
|
-
return entry;
|
|
904
|
-
}
|
|
905
|
-
/** @internal */
|
|
906
|
-
function noAnswer(name, queues) {
|
|
907
|
-
const known = [...queues.keys()].join(", ") || "(none)";
|
|
908
|
-
return /* @__PURE__ */ new Error(`No scripted answer for request '${name}'. Known: ${known}`);
|
|
909
|
-
}
|
|
1121
|
+
//#region src/seam.ts
|
|
910
1122
|
/** The only own keys an executor result carries. @internal */
|
|
911
1123
|
const TEXT_RESULT_KEYS = new Set([
|
|
912
1124
|
"result",
|
|
@@ -925,123 +1137,26 @@ function isTextResult(value) {
|
|
|
925
1137
|
return Object.hasOwn(value, "result") && Object.keys(value).every((key) => TEXT_RESULT_KEYS.has(key));
|
|
926
1138
|
}
|
|
927
1139
|
/**
|
|
928
|
-
* Resolves ONE scripted
|
|
1140
|
+
* Resolves ONE scripted entry to an executor result: a function entry is
|
|
929
1141
|
* called with the request, and the value is taken as the executor result only
|
|
930
|
-
* when it is one (see {@link isTextResult}).
|
|
931
|
-
* queues follow the same entry conventions. @internal
|
|
1142
|
+
* when it is one (see {@link isTextResult}). @internal
|
|
932
1143
|
*/
|
|
933
|
-
async function
|
|
1144
|
+
async function resolveSeamScriptEntry(entry, request, info) {
|
|
934
1145
|
const value = typeof entry === "function" ? await entry(request, info) : entry;
|
|
935
1146
|
return require_decision.isRecord(value) && isTextResult(value) ? value : { result: value };
|
|
936
1147
|
}
|
|
937
|
-
/**
|
|
938
|
-
* Names a pending text request in an error message. Shared with `runSeam`.
|
|
939
|
-
* @internal
|
|
940
|
-
*/
|
|
1148
|
+
/** Names a pending text request in an error message. @internal */
|
|
941
1149
|
function describeText(request) {
|
|
942
1150
|
return request.name ? `'${request.name}' (model '${request.model}')` : `(model '${request.model}')`;
|
|
943
1151
|
}
|
|
944
1152
|
/**
|
|
945
|
-
* Stream semantics with no model: the whole text lands as one chunk.
|
|
946
|
-
* with `runSeam`, whose scripted answers stream the same way. @internal
|
|
1153
|
+
* Stream semantics with no model: the whole text lands as one chunk. @internal
|
|
947
1154
|
*/
|
|
948
1155
|
function emitScriptedChunk(result, info) {
|
|
949
1156
|
const text = require_decision.isRecord(result) ? result["result"] : void 0;
|
|
950
1157
|
if (typeof text === "string") info?.onChunk?.(text);
|
|
951
1158
|
}
|
|
952
1159
|
/**
|
|
953
|
-
* Executors that replay a script instead of calling a model. Every slot is
|
|
954
|
-
* provided, so any machine binds.
|
|
955
|
-
*
|
|
956
|
-
* Answers are keyed by the request's resolved name — its authored `name`, else
|
|
957
|
-
* the invoke `id` — and `"*"` serves anything unmatched.
|
|
958
|
-
* Entries for a name are consumed in order and the last one repeats forever, so
|
|
959
|
-
* a looping machine needs no extra option. A request the script has no route
|
|
960
|
-
* for throws an ordinary `Error` from inside the executor, which reaches the
|
|
961
|
-
* machine as an actor error.
|
|
962
|
-
*
|
|
963
|
-
* Queues are copied on creation, so the caller's arrays are never mutated and
|
|
964
|
-
* each call builds a fresh, independent playthrough. Entries may be plain
|
|
965
|
-
* values or functions of the request.
|
|
966
|
-
*
|
|
967
|
-
* @example
|
|
968
|
-
* ```ts
|
|
969
|
-
* const result = await runAgent(moderationMachine, {
|
|
970
|
-
* input: { comment: 'honestly this update is terrible', trust: 20 },
|
|
971
|
-
* executors: createScriptedExecutors({
|
|
972
|
-
* decisions: { moderate: [{ type: 'FLAG', reason: 'Borderline tone.' }] },
|
|
973
|
-
* }),
|
|
974
|
-
* });
|
|
975
|
-
* ```
|
|
976
|
-
*
|
|
977
|
-
* @example Dynamic entries
|
|
978
|
-
* ```ts
|
|
979
|
-
* createScriptedExecutors({
|
|
980
|
-
* text: { draft: (request) => `Draft about ${request.prompt}` },
|
|
981
|
-
* decisions: { route: (request) => ({ type: request.events[0]!.type }) },
|
|
982
|
-
* });
|
|
983
|
-
* ```
|
|
984
|
-
*/
|
|
985
|
-
function createScriptedExecutors(script = {}) {
|
|
986
|
-
const decisions = toQueues(script.decisions);
|
|
987
|
-
const text = toQueues(script.text);
|
|
988
|
-
const calls = [];
|
|
989
|
-
const withDefaultUsage = (result) => result.usage || !script.usage ? result : {
|
|
990
|
-
...result,
|
|
991
|
-
usage: script.usage
|
|
992
|
-
};
|
|
993
|
-
const nextText = async (kind, request, info) => {
|
|
994
|
-
const name = request.name ?? "*";
|
|
995
|
-
calls.push({
|
|
996
|
-
kind,
|
|
997
|
-
name,
|
|
998
|
-
input: request.input,
|
|
999
|
-
request
|
|
1000
|
-
});
|
|
1001
|
-
const entry = takeEntry(text, name);
|
|
1002
|
-
if (entry === void 0) throw noAnswer(name, text);
|
|
1003
|
-
return withDefaultUsage(await resolveScriptedTextEntry(entry, request, info));
|
|
1004
|
-
};
|
|
1005
|
-
return {
|
|
1006
|
-
calls,
|
|
1007
|
-
generateText: (request, info) => nextText("generateText", request, info),
|
|
1008
|
-
streamText: async (request, info) => {
|
|
1009
|
-
const name = request.name ?? "*";
|
|
1010
|
-
const streamEntry = script.stream?.[name] ?? script.stream?.["*"];
|
|
1011
|
-
if (streamEntry !== void 0) {
|
|
1012
|
-
calls.push({
|
|
1013
|
-
kind: "streamText",
|
|
1014
|
-
name,
|
|
1015
|
-
input: request.input,
|
|
1016
|
-
request
|
|
1017
|
-
});
|
|
1018
|
-
const resolved = typeof streamEntry === "function" ? await streamEntry(request, info) : streamEntry;
|
|
1019
|
-
const chunks = typeof resolved === "string" ? [resolved] : [...resolved];
|
|
1020
|
-
for (const chunk of chunks) info?.onChunk?.(chunk);
|
|
1021
|
-
return withDefaultUsage({ result: chunks.join("") });
|
|
1022
|
-
}
|
|
1023
|
-
const result = await nextText("streamText", request, info);
|
|
1024
|
-
emitScriptedChunk(result, info);
|
|
1025
|
-
return result;
|
|
1026
|
-
},
|
|
1027
|
-
decide: async (request) => {
|
|
1028
|
-
const name = request.name ?? request.id;
|
|
1029
|
-
calls.push({
|
|
1030
|
-
kind: "decide",
|
|
1031
|
-
name,
|
|
1032
|
-
input: request.input,
|
|
1033
|
-
request
|
|
1034
|
-
});
|
|
1035
|
-
const entry = takeEntry(decisions, name);
|
|
1036
|
-
if (entry === void 0) throw noAnswer(name, decisions);
|
|
1037
|
-
const value = typeof entry === "function" ? await entry(request) : entry;
|
|
1038
|
-
return withDefaultUsage(require_decision.isRecord(value) && typeof value["type"] !== "string" && "event" in value ? value : { event: value });
|
|
1039
|
-
}
|
|
1040
|
-
};
|
|
1041
|
-
}
|
|
1042
|
-
//#endregion
|
|
1043
|
-
//#region src/seam.ts
|
|
1044
|
-
/**
|
|
1045
1160
|
* The seam's own answer, for scoring: the executor result's `result`.
|
|
1046
1161
|
* @internal
|
|
1047
1162
|
*/
|
|
@@ -1063,8 +1178,8 @@ async function seamOutputOf(result, request) {
|
|
|
1063
1178
|
* @example No API key: the seam is scripted too, so the whole thing runs offline.
|
|
1064
1179
|
* ```ts
|
|
1065
1180
|
* const run = await runSeam(emailDrafter, {
|
|
1066
|
-
* scripts: {
|
|
1067
|
-
* seam: { request: '
|
|
1181
|
+
* scripts: { writeFollowUps: [followUps], emailDrafter: [draft] },
|
|
1182
|
+
* seam: { request: 'writeFollowUps' },
|
|
1068
1183
|
* respond: ({ state }) => (state === 'prompting' ? { type: 'PROMPT_SUBMITTED', prompt } : null),
|
|
1069
1184
|
* });
|
|
1070
1185
|
*
|
|
@@ -1100,7 +1215,7 @@ async function runSeam(machine, options) {
|
|
|
1100
1215
|
const takeScriptedSlot = async (request, info) => {
|
|
1101
1216
|
const queue = queues.get(queueKeyOf(request));
|
|
1102
1217
|
if (!queue?.length) return;
|
|
1103
|
-
return
|
|
1218
|
+
return resolveSeamScriptEntry(options.repeatLast && queue.length === 1 ? queue[0] : queue.shift(), request, info);
|
|
1104
1219
|
};
|
|
1105
1220
|
const scriptedAnswer = async (request, info) => {
|
|
1106
1221
|
const scripted = await takeScriptedSlot(request, info);
|
|
@@ -1157,9 +1272,7 @@ async function runSeam(machine, options) {
|
|
|
1157
1272
|
let result;
|
|
1158
1273
|
const maxTurns = options.maxTurns ?? 12;
|
|
1159
1274
|
for (let turn = 0; turn <= maxTurns; turn++) {
|
|
1160
|
-
result = await require_run_agent.
|
|
1161
|
-
...snapshot ? { snapshot } : { input: options.input },
|
|
1162
|
-
...event ? { event } : {},
|
|
1275
|
+
result = await require_run_agent.runToQuiescence(require_run_agent.createAgentRuntime(machine, {
|
|
1163
1276
|
...options.actors ? { actors: options.actors } : {},
|
|
1164
1277
|
executors,
|
|
1165
1278
|
onTransition: (next, causedBy) => {
|
|
@@ -1168,6 +1281,9 @@ async function runSeam(machine, options) {
|
|
|
1168
1281
|
liveEvents = events.length;
|
|
1169
1282
|
statePath.push(next.value);
|
|
1170
1283
|
}
|
|
1284
|
+
}), {
|
|
1285
|
+
...snapshot ? { snapshot } : { input: options.input },
|
|
1286
|
+
...event ? { event } : {}
|
|
1171
1287
|
});
|
|
1172
1288
|
if (result.status !== "idle" || turn === maxTurns) break;
|
|
1173
1289
|
const next = options.respond?.({
|
|
@@ -1214,7 +1330,6 @@ exports.AgentLintError = AgentLintError;
|
|
|
1214
1330
|
exports.AgentUnknownStateError = AgentUnknownStateError;
|
|
1215
1331
|
exports.assertAgentMachine = assertAgentMachine;
|
|
1216
1332
|
exports.canReach = canReach;
|
|
1217
|
-
exports.createScriptedExecutors = createScriptedExecutors;
|
|
1218
1333
|
exports.explorePaths = explorePaths;
|
|
1219
1334
|
exports.lintAgentMachine = lintAgentMachine;
|
|
1220
1335
|
exports.matchesTrajectory = matchesTrajectory;
|