@statelyai/agent 2.0.0-alpha.19 → 2.0.0-alpha.21
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 +89 -25
- package/dist/ai-sdk.d.cts +56 -12
- package/dist/ai-sdk.d.mts +56 -12
- package/dist/ai-sdk.mjs +89 -26
- package/dist/{decision-t26zsnSR.cjs → decision-BnTCsuJv.cjs} +16 -4
- package/dist/{decision-DsIkEuHz.mjs → decision-JWx6n3xR.mjs} +5 -5
- package/dist/index.cjs +378 -24
- package/dist/index.d.cts +211 -19
- package/dist/index.d.mts +211 -19
- package/dist/index.mjs +378 -26
- package/dist/machines.cjs +1 -1
- package/dist/machines.d.cts +1 -1
- package/dist/machines.d.mts +1 -1
- package/dist/machines.mjs +1 -1
- package/dist/otel.d.cts +1 -1
- package/dist/otel.d.mts +1 -1
- package/dist/{run-agent-CwmzAZwj.d.mts → run-agent-DBNI8ETM.d.mts} +3 -3
- package/dist/{run-agent--4bbms-D.d.cts → run-agent-Do094mGu.d.cts} +3 -3
- package/dist/{setup-agent-BFA4VKpN.cjs → setup-agent-91FSuZbB.cjs} +2 -2
- package/dist/{setup-agent-CPFPN06s.mjs → setup-agent-DqqdcdNh.mjs} +2 -2
- package/dist/sqlite.d.cts +1 -1
- package/dist/sqlite.d.mts +1 -1
- package/dist/{text-logic-Er5KkTX6.d.mts → text-logic-BR5twXCv.d.mts} +8 -5
- package/dist/{text-logic-Cavva1W6.d.cts → text-logic-Mmbgb2jy.d.cts} +8 -5
- package/dist/{types-DYpK3QF4.d.mts → types-CvWRGFxP.d.mts} +6 -1
- package/dist/{types-pJ5Hn8fv.d.cts → types-DSdj2tGs.d.cts} +6 -1
- package/dist/validate.cjs +2 -2
- package/dist/validate.mjs +1 -1
- package/package.json +28 -28
- package/readme.md +4 -4
- package/schemas/agent-workflow.json +1 -1
|
@@ -168,7 +168,7 @@ function toolMessage(content) {
|
|
|
168
168
|
* ```
|
|
169
169
|
*/
|
|
170
170
|
function getStateMeta(snapshot) {
|
|
171
|
-
const nodes = snapshot.
|
|
171
|
+
const nodes = snapshot.nodes;
|
|
172
172
|
const depthById = new Map(nodes?.map((node) => [node.id, node.path.length]));
|
|
173
173
|
const depth = (id) => depthById.get(id) ?? id.split(".").length;
|
|
174
174
|
const entries = Object.entries(snapshot.getMeta()).filter((entry) => entry[1] != null).sort(([a], [b]) => depth(a) - depth(b) || (a < b ? -1 : a > b ? 1 : 0));
|
|
@@ -424,7 +424,7 @@ function createBuiltinTextActor(src, mode, outputSchema) {
|
|
|
424
424
|
messages: ({ input }) => input.messages,
|
|
425
425
|
tools: ({ input }) => input.tools,
|
|
426
426
|
toolChoice: ({ input }) => input.toolChoice,
|
|
427
|
-
|
|
427
|
+
includeReasoning: ({ input }) => input.includeReasoning,
|
|
428
428
|
temperature: ({ input }) => input.temperature,
|
|
429
429
|
maxOutputTokens: ({ input }) => input.maxOutputTokens,
|
|
430
430
|
topP: ({ input }) => input.topP,
|
|
@@ -496,7 +496,7 @@ function createTextLogic(config, execute) {
|
|
|
496
496
|
tools: resolveTextLogicValue(config.tools, args),
|
|
497
497
|
toolChoice: resolveTextLogicValue(config.toolChoice, args),
|
|
498
498
|
outputSchema: schemas.output,
|
|
499
|
-
|
|
499
|
+
includeReasoning: resolveTextLogicValue(config.includeReasoning, args),
|
|
500
500
|
temperature: resolveTextLogicValue(config.temperature, args),
|
|
501
501
|
maxOutputTokens: resolveTextLogicValue(config.maxOutputTokens, args),
|
|
502
502
|
topP: resolveTextLogicValue(config.topP, args),
|
|
@@ -643,7 +643,7 @@ function buildEnvelopeSchema(inner, options = {}) {
|
|
|
643
643
|
*/
|
|
644
644
|
function parseStructuredEnvelope(request, value) {
|
|
645
645
|
if (!request.outputSchema) throw new Error("parseStructuredEnvelope: the request declares no outputSchema.");
|
|
646
|
-
return validateSchemaSync(buildEnvelopeSchema(request.outputSchema, { reasoning: request.
|
|
646
|
+
return validateSchemaSync(buildEnvelopeSchema(request.outputSchema, { reasoning: request.includeReasoning }), value);
|
|
647
647
|
}
|
|
648
648
|
/**
|
|
649
649
|
* Merges request-declared and call-site `tools`, dispatches to the
|
|
@@ -1048,12 +1048,24 @@ Object.defineProperty(exports, "DECIDE_ACTOR", {
|
|
|
1048
1048
|
return DECIDE_ACTOR;
|
|
1049
1049
|
}
|
|
1050
1050
|
});
|
|
1051
|
+
Object.defineProperty(exports, "GENERATE_TEXT_ACTOR", {
|
|
1052
|
+
enumerable: true,
|
|
1053
|
+
get: function() {
|
|
1054
|
+
return GENERATE_TEXT_ACTOR;
|
|
1055
|
+
}
|
|
1056
|
+
});
|
|
1051
1057
|
Object.defineProperty(exports, "INTERPRET_SOURCE", {
|
|
1052
1058
|
enumerable: true,
|
|
1053
1059
|
get: function() {
|
|
1054
1060
|
return INTERPRET_SOURCE;
|
|
1055
1061
|
}
|
|
1056
1062
|
});
|
|
1063
|
+
Object.defineProperty(exports, "STREAM_TEXT_ACTOR", {
|
|
1064
|
+
enumerable: true,
|
|
1065
|
+
get: function() {
|
|
1066
|
+
return STREAM_TEXT_ACTOR;
|
|
1067
|
+
}
|
|
1068
|
+
});
|
|
1057
1069
|
Object.defineProperty(exports, "USER_INPUT_ACTOR", {
|
|
1058
1070
|
enumerable: true,
|
|
1059
1071
|
get: function() {
|
|
@@ -167,7 +167,7 @@ function toolMessage(content) {
|
|
|
167
167
|
* ```
|
|
168
168
|
*/
|
|
169
169
|
function getStateMeta(snapshot) {
|
|
170
|
-
const nodes = snapshot.
|
|
170
|
+
const nodes = snapshot.nodes;
|
|
171
171
|
const depthById = new Map(nodes?.map((node) => [node.id, node.path.length]));
|
|
172
172
|
const depth = (id) => depthById.get(id) ?? id.split(".").length;
|
|
173
173
|
const entries = Object.entries(snapshot.getMeta()).filter((entry) => entry[1] != null).sort(([a], [b]) => depth(a) - depth(b) || (a < b ? -1 : a > b ? 1 : 0));
|
|
@@ -423,7 +423,7 @@ function createBuiltinTextActor(src, mode, outputSchema) {
|
|
|
423
423
|
messages: ({ input }) => input.messages,
|
|
424
424
|
tools: ({ input }) => input.tools,
|
|
425
425
|
toolChoice: ({ input }) => input.toolChoice,
|
|
426
|
-
|
|
426
|
+
includeReasoning: ({ input }) => input.includeReasoning,
|
|
427
427
|
temperature: ({ input }) => input.temperature,
|
|
428
428
|
maxOutputTokens: ({ input }) => input.maxOutputTokens,
|
|
429
429
|
topP: ({ input }) => input.topP,
|
|
@@ -495,7 +495,7 @@ function createTextLogic(config, execute) {
|
|
|
495
495
|
tools: resolveTextLogicValue(config.tools, args),
|
|
496
496
|
toolChoice: resolveTextLogicValue(config.toolChoice, args),
|
|
497
497
|
outputSchema: schemas.output,
|
|
498
|
-
|
|
498
|
+
includeReasoning: resolveTextLogicValue(config.includeReasoning, args),
|
|
499
499
|
temperature: resolveTextLogicValue(config.temperature, args),
|
|
500
500
|
maxOutputTokens: resolveTextLogicValue(config.maxOutputTokens, args),
|
|
501
501
|
topP: resolveTextLogicValue(config.topP, args),
|
|
@@ -642,7 +642,7 @@ function buildEnvelopeSchema(inner, options = {}) {
|
|
|
642
642
|
*/
|
|
643
643
|
function parseStructuredEnvelope(request, value) {
|
|
644
644
|
if (!request.outputSchema) throw new Error("parseStructuredEnvelope: the request declares no outputSchema.");
|
|
645
|
-
return validateSchemaSync(buildEnvelopeSchema(request.outputSchema, { reasoning: request.
|
|
645
|
+
return validateSchemaSync(buildEnvelopeSchema(request.outputSchema, { reasoning: request.includeReasoning }), value);
|
|
646
646
|
}
|
|
647
647
|
/**
|
|
648
648
|
* Merges request-declared and call-site `tools`, dispatches to the
|
|
@@ -1029,4 +1029,4 @@ async function resolveDecision(request, executors, options = {}) {
|
|
|
1029
1029
|
throw new AgentDecisionExhaustedError(attempts);
|
|
1030
1030
|
}
|
|
1031
1031
|
//#endregion
|
|
1032
|
-
export {
|
|
1032
|
+
export { getMachineStaticTransitionTargets as A, getJsonSchema as B, parseModelRef as C, agentExecutionOptions as D, userInputActor as E, missingActor as F, resolveMachineVersion as G, getMachineStructuralHash as H, assistantMessage as I, userMessage as J, systemMessage as K, djb2Hex as L, isUnboundPlaceholder as M, machineIdlePredicates as N, executorBoundLogics as O, machineStaticTransitionTargets as P, findNonSerializableContextPaths as R, normalizeGeneratorResult as S, parseStructuredEnvelope as T, getStateMeta as U, getJsonSchemaSync as V, isStandardSchema as W, validateSchemaSync as Y, createTextLogic as _, resolveDecision as a, getCallUsage as b, AGENT_USAGE_TOKEN_FIELDS as c, INTERPRET_SOURCE as d, STREAM_TEXT_ACTOR as f, builtinTextActors as g, buildEnvelopeSchema as h, renderDecisionAttempts as i, getRegisteredAgentExecutionOptions as j, getMachineIdlePredicate as k, DECIDE_ACTOR as l, bindRequestExecutor as m, createDecideActor as n, getAcceptedEvents as o, USER_INPUT_ACTOR as p, toolMessage as q, isDecisionLogic as r, parseAgentEvent as s, AgentDecisionExhaustedError as t, GENERATE_TEXT_ACTOR as u, executeAgentTextRequest as v, parseOutput as w, isTextLogic as x, getAgentOutputMode as y, getAgentMessages as z };
|
package/dist/index.cjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_errors = require("./errors-DUBBzRLP.cjs");
|
|
3
|
-
const require_setup_agent = require("./setup-agent-
|
|
4
|
-
const require_decision = require("./decision-
|
|
3
|
+
const require_setup_agent = require("./setup-agent-91FSuZbB.cjs");
|
|
4
|
+
const require_decision = require("./decision-BnTCsuJv.cjs");
|
|
5
5
|
const require_event_log_store = require("./event-log-store-a_TKy1gk.cjs");
|
|
6
6
|
require("./validate.cjs");
|
|
7
7
|
let xstate = require("xstate");
|
|
8
|
+
let xstate_durable = require("xstate/durable");
|
|
8
9
|
//#region src/internal/state-request-pass.ts
|
|
9
10
|
async function runTextPhase(stateRequest, baseMessages, deps) {
|
|
10
11
|
const { model, system } = stateRequest;
|
|
@@ -286,7 +287,7 @@ function serializeTraceEvent(event, options = {}) {
|
|
|
286
287
|
return out;
|
|
287
288
|
}
|
|
288
289
|
function snapshotNodes(snapshot) {
|
|
289
|
-
return (snapshot.
|
|
290
|
+
return (snapshot.nodes ?? []).map((raw) => {
|
|
290
291
|
const node = raw;
|
|
291
292
|
return {
|
|
292
293
|
id: node.id ?? "",
|
|
@@ -569,9 +570,13 @@ function invokingActorOf(self, runCtx) {
|
|
|
569
570
|
* identical event shapes by construction. @internal
|
|
570
571
|
*/
|
|
571
572
|
function bindTextLogic(logic, runCtx) {
|
|
572
|
-
return logic.withExecutor(async ({ request, self: selfArg, signal }) => {
|
|
573
|
+
return logic.withExecutor(async ({ request: rawRequest, self: selfArg, signal }) => {
|
|
573
574
|
const self = selfArg;
|
|
574
575
|
const { id, src } = selfIdAndSrc(self);
|
|
576
|
+
const request = rawRequest.name === void 0 && src !== "" && src !== "agent.generateText" && src !== "agent.streamText" ? {
|
|
577
|
+
...rawRequest,
|
|
578
|
+
name: src
|
|
579
|
+
} : rawRequest;
|
|
575
580
|
const executor = logic.mode === "stream" ? runCtx.streamText : runCtx.generateText;
|
|
576
581
|
if (!executor) throw new Error(`No '${logic.mode === "stream" ? "streamText" : "generateText"}' executor provided.`);
|
|
577
582
|
const requestWithTools = {
|
|
@@ -1161,6 +1166,7 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1161
1166
|
const aligned = Object.assign(Object.create(Object.getPrototypeOf(effectiveSnapshot)), effectiveSnapshot);
|
|
1162
1167
|
if (machineOwnVersion === void 0) delete aligned.version;
|
|
1163
1168
|
else aligned.version = machineOwnVersion;
|
|
1169
|
+
delete aligned.machine;
|
|
1164
1170
|
effectiveSnapshot = aligned;
|
|
1165
1171
|
}
|
|
1166
1172
|
const priorMessages = require_decision.getAgentMessages(effectiveSnapshot);
|
|
@@ -2084,8 +2090,8 @@ function lintAgentMachine(machine, options = {}) {
|
|
|
2084
2090
|
return diagnostics;
|
|
2085
2091
|
}
|
|
2086
2092
|
/**
|
|
2087
|
-
* Thrown by `lintAgentMachine(machine, { throw: true })`
|
|
2088
|
-
* failing diagnostics.
|
|
2093
|
+
* Thrown by `lintAgentMachine(machine, { throw: true })` (and its
|
|
2094
|
+
* {@link assertAgentMachine} alias) when lint finds failing diagnostics.
|
|
2089
2095
|
* `diagnostics` holds the findings; the message lists them one per finding,
|
|
2090
2096
|
* so a test runner's failure output reads like the CLI's lint report.
|
|
2091
2097
|
*/
|
|
@@ -2098,6 +2104,26 @@ var AgentLintError = class extends require_errors.AgentError {
|
|
|
2098
2104
|
this.diagnostics = diagnostics;
|
|
2099
2105
|
}
|
|
2100
2106
|
};
|
|
2107
|
+
/**
|
|
2108
|
+
* Asserts a machine passes {@link lintAgentMachine}: returns silently when
|
|
2109
|
+
* clean, throws {@link AgentLintError} (with the findings on `.diagnostics`)
|
|
2110
|
+
* otherwise — sugar for `lintAgentMachine(machine, { ...options, throw: true })`.
|
|
2111
|
+
* Fails on error-severity findings; set `warnings: true` to fail on warnings
|
|
2112
|
+
* too. The one-liner for tests and generation loops:
|
|
2113
|
+
*
|
|
2114
|
+
* @example
|
|
2115
|
+
* ```ts
|
|
2116
|
+
* test('agent machine is structurally sound', () => {
|
|
2117
|
+
* assertAgentMachine(machine);
|
|
2118
|
+
* });
|
|
2119
|
+
* ```
|
|
2120
|
+
*/
|
|
2121
|
+
function assertAgentMachine(machine, options = {}) {
|
|
2122
|
+
lintAgentMachine(machine, {
|
|
2123
|
+
...options,
|
|
2124
|
+
throw: true
|
|
2125
|
+
});
|
|
2126
|
+
}
|
|
2101
2127
|
function pendingInvokes(step) {
|
|
2102
2128
|
const out = [];
|
|
2103
2129
|
for (const action of step.actions) {
|
|
@@ -2124,6 +2150,21 @@ function takeFromQueue(channel, src) {
|
|
|
2124
2150
|
* the real transition logic. Returns the terminal `status`, final `snapshot`,
|
|
2125
2151
|
* and a `trail` of every step taken.
|
|
2126
2152
|
*
|
|
2153
|
+
* Decisions run through the live run's own validation/retry core,
|
|
2154
|
+
* {@link resolveDecision}, with the script standing in for the model: each
|
|
2155
|
+
* attempt consumes the next queued {@link ChosenEvent} for that src, so an
|
|
2156
|
+
* unknown, payload-invalid, or guard-rejected event is NOT silently swallowed
|
|
2157
|
+
* — the next queued decision is tried, exactly as a live run re-asks the
|
|
2158
|
+
* model. The decision logic's `maxRetries` caps attempts as it would live;
|
|
2159
|
+
* when retries continue past the end of the queue, the last queued decision
|
|
2160
|
+
* repeats (a scripted model that insists). The repeat applies only within one
|
|
2161
|
+
* decision request's retries — each new decision request must have its own
|
|
2162
|
+
* queued entry, or the dry-script error throws as usual. Exhausting all
|
|
2163
|
+
* attempts delivers
|
|
2164
|
+
* the resulting {@link AgentDecisionExhaustedError} to the machine as the
|
|
2165
|
+
* decision invoke's error (so an `onError` transition observes it, as it
|
|
2166
|
+
* would live); with no `onError` to catch it, it is thrown.
|
|
2167
|
+
*
|
|
2127
2168
|
* Throws a descriptive error when the script runs dry mid-request, naming the
|
|
2128
2169
|
* pending request's kind, src, and id so the missing scripted response is
|
|
2129
2170
|
* obvious.
|
|
@@ -2132,7 +2173,10 @@ function takeFromQueue(channel, src) {
|
|
|
2132
2173
|
* ```ts
|
|
2133
2174
|
* const { status, snapshot } = simulateAgent(machine, {
|
|
2134
2175
|
* input: { topic: 'state machines' },
|
|
2135
|
-
* script: {
|
|
2176
|
+
* script: {
|
|
2177
|
+
* decisions: { 'agent.decide': [{ type: 'ESCALATE' }] },
|
|
2178
|
+
* events: [{ type: 'APPROVE' }], // crosses the human gate
|
|
2179
|
+
* },
|
|
2136
2180
|
* });
|
|
2137
2181
|
* ```
|
|
2138
2182
|
*/
|
|
@@ -2141,11 +2185,12 @@ async function simulateAgent(machine, options) {
|
|
|
2141
2185
|
const script = {
|
|
2142
2186
|
text: mapValues(options.script.text ?? {}, (arr) => [...arr]),
|
|
2143
2187
|
decisions: mapValues(options.script.decisions ?? {}, (arr) => [...arr]),
|
|
2144
|
-
invokes: mapValues(options.script.invokes ?? {}, (arr) => [...arr])
|
|
2188
|
+
invokes: mapValues(options.script.invokes ?? {}, (arr) => [...arr]),
|
|
2189
|
+
events: [...options.script.events ?? []]
|
|
2145
2190
|
};
|
|
2146
2191
|
if (options.script.userInput?.length) script.invokes[USER_INPUT_SRC] = [...options.script.userInput, ...script.invokes[USER_INPUT_SRC] ?? []];
|
|
2147
2192
|
let step = require_setup_agent.initialAgentStep(machine, options.input);
|
|
2148
|
-
const trail = [];
|
|
2193
|
+
const trail = [{ state: step.snapshot.value }];
|
|
2149
2194
|
for (let i = 0; i < maxSteps; i++) {
|
|
2150
2195
|
if (step.done) return {
|
|
2151
2196
|
status: "done",
|
|
@@ -2155,14 +2200,10 @@ async function simulateAgent(machine, options) {
|
|
|
2155
2200
|
const request = step.requests[0];
|
|
2156
2201
|
if (request) {
|
|
2157
2202
|
if (request.kind === "decision") {
|
|
2158
|
-
const
|
|
2159
|
-
const
|
|
2160
|
-
|
|
2161
|
-
step =
|
|
2162
|
-
trail.push({
|
|
2163
|
-
state: step.snapshot.value,
|
|
2164
|
-
appliedEvent: taken.value
|
|
2165
|
-
});
|
|
2203
|
+
const invokeMeta = findInvokeMetadata(step, request.id);
|
|
2204
|
+
const src = invokeMeta?.src ?? request.id;
|
|
2205
|
+
const decisionSrc = src in (script.decisions ?? {}) ? src : request.id in (script.decisions ?? {}) ? request.id : src;
|
|
2206
|
+
step = await applyScriptedDecision(machine, step, request, decisionSrc, invokeMeta?.logic, script, trail);
|
|
2166
2207
|
continue;
|
|
2167
2208
|
}
|
|
2168
2209
|
const taken = takeFromQueue(script.text, request.src);
|
|
@@ -2193,6 +2234,18 @@ async function simulateAgent(machine, options) {
|
|
|
2193
2234
|
});
|
|
2194
2235
|
continue;
|
|
2195
2236
|
}
|
|
2237
|
+
const external = script.events;
|
|
2238
|
+
if (external.length > 0) {
|
|
2239
|
+
const event = external.shift();
|
|
2240
|
+
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.`);
|
|
2241
|
+
step = require_setup_agent.transitionAgentStep(machine, step, event);
|
|
2242
|
+
trail.push({
|
|
2243
|
+
state: step.snapshot.value,
|
|
2244
|
+
appliedEvent: event,
|
|
2245
|
+
external: true
|
|
2246
|
+
});
|
|
2247
|
+
continue;
|
|
2248
|
+
}
|
|
2196
2249
|
return {
|
|
2197
2250
|
status: "idle",
|
|
2198
2251
|
snapshot: step.snapshot,
|
|
@@ -2205,6 +2258,66 @@ async function simulateAgent(machine, options) {
|
|
|
2205
2258
|
trail
|
|
2206
2259
|
};
|
|
2207
2260
|
}
|
|
2261
|
+
async function applyScriptedDecision(machine, step, request, decisionSrc, invokeLogic, script, trail) {
|
|
2262
|
+
const dequeued = [];
|
|
2263
|
+
const decide = async () => {
|
|
2264
|
+
const taken = takeFromQueue(script.decisions, decisionSrc);
|
|
2265
|
+
if (taken.found) {
|
|
2266
|
+
dequeued.push(taken.value);
|
|
2267
|
+
return { event: taken.value };
|
|
2268
|
+
}
|
|
2269
|
+
if (dequeued.length === 0) throw scriptDryError("decision", decisionSrc, request.id, request);
|
|
2270
|
+
return { event: dequeued[dequeued.length - 1] };
|
|
2271
|
+
};
|
|
2272
|
+
const maxRetries = (require_decision.isDecisionLogic(invokeLogic) ? invokeLogic : resolveRegisteredDecisionLogic(machine, decisionSrc))?.maxRetries;
|
|
2273
|
+
let chosen;
|
|
2274
|
+
try {
|
|
2275
|
+
chosen = await require_decision.resolveDecision(request, { decide }, {
|
|
2276
|
+
maxRetries,
|
|
2277
|
+
canTake: (event) => step.snapshot.can(event)
|
|
2278
|
+
});
|
|
2279
|
+
} catch (error) {
|
|
2280
|
+
if (!(error instanceof require_decision.AgentDecisionExhaustedError)) throw error;
|
|
2281
|
+
const next = require_setup_agent.transitionAgentStep(machine, step, {
|
|
2282
|
+
type: "xstate.error.actor",
|
|
2283
|
+
actorId: request.id,
|
|
2284
|
+
...sessionIdOf(step.snapshot, request.id),
|
|
2285
|
+
error
|
|
2286
|
+
});
|
|
2287
|
+
if (next.snapshot.status === "error") throw error;
|
|
2288
|
+
trail.push({
|
|
2289
|
+
state: next.snapshot.value,
|
|
2290
|
+
rejectedEvents: dequeued
|
|
2291
|
+
});
|
|
2292
|
+
return next;
|
|
2293
|
+
}
|
|
2294
|
+
const next = require_setup_agent.transitionAgentStep(machine, step, chosen);
|
|
2295
|
+
const rejected = dequeued.slice(0, -1);
|
|
2296
|
+
trail.push({
|
|
2297
|
+
state: next.snapshot.value,
|
|
2298
|
+
appliedEvent: chosen,
|
|
2299
|
+
...rejected.length > 0 ? { rejectedEvents: rejected } : {}
|
|
2300
|
+
});
|
|
2301
|
+
return next;
|
|
2302
|
+
}
|
|
2303
|
+
function findInvokeMetadata(step, id) {
|
|
2304
|
+
for (const action of step.actions) {
|
|
2305
|
+
const metadata = require_setup_agent.getInvokeEffectMetadata(action);
|
|
2306
|
+
if (metadata?.id !== id) continue;
|
|
2307
|
+
return {
|
|
2308
|
+
...typeof metadata.src === "string" ? { src: metadata.src } : {},
|
|
2309
|
+
logic: metadata.logic ?? (typeof metadata.src === "object" ? metadata.src : void 0)
|
|
2310
|
+
};
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
function resolveRegisteredDecisionLogic(machine, src) {
|
|
2314
|
+
const candidate = require_decision.getRegisteredAgentExecutionOptions(machine).actors?.[src] ?? machine.sources?.actors?.[src];
|
|
2315
|
+
return require_decision.isDecisionLogic(candidate) ? candidate : void 0;
|
|
2316
|
+
}
|
|
2317
|
+
function sessionIdOf(snapshot, id) {
|
|
2318
|
+
const child = snapshot.children[id];
|
|
2319
|
+
return typeof child?.sessionId === "string" ? { sessionId: child.sessionId } : {};
|
|
2320
|
+
}
|
|
2208
2321
|
function mapValues(obj, fn) {
|
|
2209
2322
|
return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, fn(value)]));
|
|
2210
2323
|
}
|
|
@@ -2241,6 +2354,10 @@ async function explore(machine, options, stopWhen) {
|
|
|
2241
2354
|
const advance = (step) => {
|
|
2242
2355
|
let current = step;
|
|
2243
2356
|
for (let i = 0; i < MAX_ADVANCE_STEPS; i++) {
|
|
2357
|
+
if (stopWhen?.(current.snapshot)) return {
|
|
2358
|
+
step: current,
|
|
2359
|
+
hit: true
|
|
2360
|
+
};
|
|
2244
2361
|
if (current.done) return { step: current };
|
|
2245
2362
|
const request = current.requests[0];
|
|
2246
2363
|
if (request && request.kind === "text") {
|
|
@@ -2273,8 +2390,8 @@ async function explore(machine, options, stopWhen) {
|
|
|
2273
2390
|
hitPathCap = true;
|
|
2274
2391
|
return;
|
|
2275
2392
|
}
|
|
2276
|
-
const { step: settled, blockedSrc } = advance(step);
|
|
2277
|
-
if (
|
|
2393
|
+
const { step: settled, blockedSrc, hit } = advance(step);
|
|
2394
|
+
if (hit) {
|
|
2278
2395
|
witness = path;
|
|
2279
2396
|
return;
|
|
2280
2397
|
}
|
|
@@ -2369,8 +2486,11 @@ async function explorePaths(machine, options = {}) {
|
|
|
2369
2486
|
return (await explore(machine, options)).report;
|
|
2370
2487
|
}
|
|
2371
2488
|
/**
|
|
2372
|
-
* Answers "can the machine reach
|
|
2373
|
-
*
|
|
2489
|
+
* Answers "can the machine reach this?" by exploring its branches (a thin
|
|
2490
|
+
* wrapper over {@link explorePaths}). The target is either a state path string
|
|
2491
|
+
* (`snapshot.matches(...)` semantics) or a snapshot predicate — the predicate
|
|
2492
|
+
* form checks any property (a context invariant, a tag, a state+context
|
|
2493
|
+
* combination) without reifying a sentinel state for it. Returns
|
|
2374
2494
|
* `{ reachable: true, witness }` with the event sequence that reaches it, or
|
|
2375
2495
|
* `{ reachable: false }`.
|
|
2376
2496
|
*
|
|
@@ -2379,11 +2499,21 @@ async function explorePaths(machine, options = {}) {
|
|
|
2379
2499
|
* const { reachable, witness } = await canReach(refundMachine, 'denied', { input: { request: 'x', amount: 5000 } });
|
|
2380
2500
|
* // reachable → true; witness → [{ type: 'NEEDS_REVIEW' }, { type: 'DENY' }]
|
|
2381
2501
|
* ```
|
|
2502
|
+
*
|
|
2503
|
+
* @example Predicate target — a violation property, no sentinel state needed
|
|
2504
|
+
* ```ts
|
|
2505
|
+
* const violation = await canReach(
|
|
2506
|
+
* refundMachine,
|
|
2507
|
+
* (snapshot) => snapshot.matches('issued') && !snapshot.context.approved,
|
|
2508
|
+
* { input: { amount: 5000 } },
|
|
2509
|
+
* );
|
|
2510
|
+
* // violation.reachable → false is the safety proof
|
|
2511
|
+
* ```
|
|
2382
2512
|
*/
|
|
2383
|
-
async function canReach(machine,
|
|
2384
|
-
const { witness } = await explore(machine, options, (snapshot) => {
|
|
2513
|
+
async function canReach(machine, target, options = {}) {
|
|
2514
|
+
const { witness } = await explore(machine, options, typeof target === "function" ? target : (snapshot) => {
|
|
2385
2515
|
try {
|
|
2386
|
-
return snapshot.matches(
|
|
2516
|
+
return snapshot.matches(target);
|
|
2387
2517
|
} catch {
|
|
2388
2518
|
return false;
|
|
2389
2519
|
}
|
|
@@ -2715,6 +2845,8 @@ async function runSeam(machine, options) {
|
|
|
2715
2845
|
let calls = 0;
|
|
2716
2846
|
let seamMatches = 0;
|
|
2717
2847
|
let seamOutput;
|
|
2848
|
+
let seamUsage;
|
|
2849
|
+
const callLog = [];
|
|
2718
2850
|
let seamReached = false;
|
|
2719
2851
|
let callsBeforeSeam = -1;
|
|
2720
2852
|
let seamStateAt = 0;
|
|
@@ -2739,6 +2871,15 @@ async function runSeam(machine, options) {
|
|
|
2739
2871
|
const route = async (request, info) => {
|
|
2740
2872
|
const callIndex = calls++;
|
|
2741
2873
|
const isSeam = request.name === seam.request && seamMatches++ === (seam.occurrence ?? 0);
|
|
2874
|
+
const ledger = (source) => {
|
|
2875
|
+
callLog.push({
|
|
2876
|
+
...request.name !== void 0 ? { name: request.name } : {},
|
|
2877
|
+
model: request.model,
|
|
2878
|
+
key: queueKeyOf(request),
|
|
2879
|
+
source,
|
|
2880
|
+
seam: isSeam
|
|
2881
|
+
});
|
|
2882
|
+
};
|
|
2742
2883
|
if (isSeam && candidate) {
|
|
2743
2884
|
await takeScriptedSlot(request, info);
|
|
2744
2885
|
seamReached = true;
|
|
@@ -2747,15 +2888,19 @@ async function runSeam(machine, options) {
|
|
|
2747
2888
|
seamEventAt = liveEvents;
|
|
2748
2889
|
const result = await candidate(request, info);
|
|
2749
2890
|
seamOutput = await seamOutputOf(result, request);
|
|
2891
|
+
seamUsage = require_decision.getCallUsage(result);
|
|
2892
|
+
ledger("candidate");
|
|
2750
2893
|
return result;
|
|
2751
2894
|
}
|
|
2752
2895
|
const scripted = await scriptedAnswer(request, info);
|
|
2896
|
+
ledger("script");
|
|
2753
2897
|
if (!isSeam) return scripted;
|
|
2754
2898
|
seamReached = true;
|
|
2755
2899
|
callsBeforeSeam = callIndex;
|
|
2756
2900
|
seamStateAt = statePath.length;
|
|
2757
2901
|
seamEventAt = liveEvents;
|
|
2758
2902
|
seamOutput = scripted.output;
|
|
2903
|
+
seamUsage = scripted.usage;
|
|
2759
2904
|
return scripted;
|
|
2760
2905
|
};
|
|
2761
2906
|
const executors = {
|
|
@@ -2817,7 +2962,9 @@ async function runSeam(machine, options) {
|
|
|
2817
2962
|
return {
|
|
2818
2963
|
result,
|
|
2819
2964
|
seamOutput,
|
|
2965
|
+
...seamUsage !== void 0 ? { seamUsage } : {},
|
|
2820
2966
|
callsBeforeSeam,
|
|
2967
|
+
calls: callLog,
|
|
2821
2968
|
before: {
|
|
2822
2969
|
statePath: statePath.slice(0, splitStateAt),
|
|
2823
2970
|
events: events.slice(0, splitAt)
|
|
@@ -2829,6 +2976,211 @@ async function runSeam(machine, options) {
|
|
|
2829
2976
|
};
|
|
2830
2977
|
}
|
|
2831
2978
|
//#endregion
|
|
2979
|
+
//#region src/durable.ts
|
|
2980
|
+
/**
|
|
2981
|
+
* The durable host runner: {@link runDurableAgent} drives an executor-bound
|
|
2982
|
+
* agent machine on xstate's `createDurable` execution (`xstate/durable`),
|
|
2983
|
+
* with the agent event log as the journal.
|
|
2984
|
+
*
|
|
2985
|
+
* Where {@link replay} + `getAgentEffects` hand a host an effect list to run
|
|
2986
|
+
* itself, `runDurableAgent` owns the whole loop on the durable runtime:
|
|
2987
|
+
* invoked actors execute live through xstate's own runtime, every EXTERNAL
|
|
2988
|
+
* event (invoke completions included) is appended to the log, and a resume
|
|
2989
|
+
* folds the log back through pure transitions — an invoke whose completion is
|
|
2990
|
+
* already journaled is never re-started, so recorded model calls are never
|
|
2991
|
+
* re-executed. Crash recovery re-runs only the work that was still in flight.
|
|
2992
|
+
*
|
|
2993
|
+
* @module
|
|
2994
|
+
*/
|
|
2995
|
+
const DONE_ACTOR_EVENT_TYPE = "xstate.done.actor";
|
|
2996
|
+
const ERROR_ACTOR_EVENT_TYPE = "xstate.error.actor";
|
|
2997
|
+
function completionActorId(event) {
|
|
2998
|
+
if (event.type !== DONE_ACTOR_EVENT_TYPE && event.type !== ERROR_ACTOR_EVENT_TYPE) return;
|
|
2999
|
+
const actorId = event.actorId;
|
|
3000
|
+
return typeof actorId === "string" ? actorId : void 0;
|
|
3001
|
+
}
|
|
3002
|
+
/** Thrown by the adapter's `waitForEvent` when nothing can produce an event. */
|
|
3003
|
+
const IDLE = Symbol("agent.durable.idle");
|
|
3004
|
+
function createMailbox() {
|
|
3005
|
+
const queue = [];
|
|
3006
|
+
const waiters = [];
|
|
3007
|
+
return {
|
|
3008
|
+
push(event) {
|
|
3009
|
+
const waiter = waiters.shift();
|
|
3010
|
+
if (waiter) waiter(event);
|
|
3011
|
+
else queue.push(event);
|
|
3012
|
+
},
|
|
3013
|
+
take() {
|
|
3014
|
+
const next = queue.shift();
|
|
3015
|
+
if (next !== void 0) return Promise.resolve(next);
|
|
3016
|
+
return new Promise((resolve) => waiters.push(resolve));
|
|
3017
|
+
},
|
|
3018
|
+
size: () => queue.length
|
|
3019
|
+
};
|
|
3020
|
+
}
|
|
3021
|
+
/**
|
|
3022
|
+
* Runs an agent machine as a durable execution: journal in, journal out.
|
|
3023
|
+
*
|
|
3024
|
+
* A fresh call starts from `input` and appends a reserved init entry; a
|
|
3025
|
+
* resume call folds `entries` through pure transitions first — invokes whose
|
|
3026
|
+
* completions are journaled are suppressed (their recorded results replay
|
|
3027
|
+
* instead of re-executing), while work that was in flight at the crash
|
|
3028
|
+
* re-executes live. After the journal, an optional `options.event` is
|
|
3029
|
+
* delivered. The call settles:
|
|
3030
|
+
*
|
|
3031
|
+
* - `done` when the machine reaches a final state, with `output`;
|
|
3032
|
+
* - `idle` when the frontier needs an external event the host has not
|
|
3033
|
+
* supplied (no live work pending, or `isIdle` says the pending work is a
|
|
3034
|
+
* human wait). Persist `entries` and call again with them later.
|
|
3035
|
+
*
|
|
3036
|
+
* ```ts
|
|
3037
|
+
* const first = await runDurableAgent(machine, { input, executors });
|
|
3038
|
+
* // ... persist first.entries; later, in a new process:
|
|
3039
|
+
* const next = await runDurableAgent(machine, {
|
|
3040
|
+
* entries: first.entries,
|
|
3041
|
+
* event: { type: "APPROVE" },
|
|
3042
|
+
* executors,
|
|
3043
|
+
* });
|
|
3044
|
+
* ```
|
|
3045
|
+
*
|
|
3046
|
+
* @experimental Built on xstate's experimental `xstate/durable` entrypoint.
|
|
3047
|
+
*/
|
|
3048
|
+
async function runDurableAgent(machine, options = {}) {
|
|
3049
|
+
const bound = options.executors ? provideExecutors(machine, options.executors, {
|
|
3050
|
+
actors: options.actors,
|
|
3051
|
+
onChunk: options.onChunk,
|
|
3052
|
+
onTrace: options.onTrace
|
|
3053
|
+
}) : options.actors ? machine.provide({ actors: options.actors }) : machine;
|
|
3054
|
+
const machineId = machine.config.id ?? machine.id ?? "(machine)";
|
|
3055
|
+
const machineVersion = options.machineVersion ?? require_decision.resolveMachineVersion(machine);
|
|
3056
|
+
const priorEntries = options.entries ?? [];
|
|
3057
|
+
if (priorEntries.length > 0) require_setup_agent.validateReplayEntries(priorEntries, {
|
|
3058
|
+
machineId,
|
|
3059
|
+
machineVersion
|
|
3060
|
+
}, "Durable journal entries");
|
|
3061
|
+
const hasInit = priorEntries[0]?.event.type === require_setup_agent.AGENT_INIT_EVENT_TYPE;
|
|
3062
|
+
const input = hasInit ? priorEntries[0].event.input : options.input;
|
|
3063
|
+
const journal = priorEntries.slice(hasInit ? 1 : 0).map((entry) => entry.event);
|
|
3064
|
+
const journaledCompletions = /* @__PURE__ */ new Map();
|
|
3065
|
+
for (const event of journal) {
|
|
3066
|
+
const actorId = completionActorId(event);
|
|
3067
|
+
if (actorId !== void 0) journaledCompletions.set(actorId, (journaledCompletions.get(actorId) ?? 0) + 1);
|
|
3068
|
+
}
|
|
3069
|
+
const storedExecutionId = hasInit ? priorEntries[0].metadata?.executionId : void 0;
|
|
3070
|
+
const executionId = typeof storedExecutionId === "string" ? storedExecutionId : hasInit ? priorEntries[0].id : crypto.randomUUID();
|
|
3071
|
+
const mailbox = createMailbox();
|
|
3072
|
+
const rootAddress = machineId;
|
|
3073
|
+
const suppressedChildren = /* @__PURE__ */ new WeakSet();
|
|
3074
|
+
const startsSeen = /* @__PURE__ */ new Map();
|
|
3075
|
+
const liveInFlight = /* @__PURE__ */ new Set();
|
|
3076
|
+
const findChildRef = (effect) => {
|
|
3077
|
+
const raw = effect;
|
|
3078
|
+
const candidates = [raw.actor, ...Array.isArray(raw.args) ? raw.args : []];
|
|
3079
|
+
for (const candidate of candidates) {
|
|
3080
|
+
const ref = candidate;
|
|
3081
|
+
if (ref && typeof ref.sessionId === "string" && typeof ref.id === "string") return ref;
|
|
3082
|
+
}
|
|
3083
|
+
};
|
|
3084
|
+
let replaying = journal.length > 0;
|
|
3085
|
+
let liveEventConsumed = false;
|
|
3086
|
+
let latestSnapshot;
|
|
3087
|
+
const execution = (0, xstate_durable.createDurable)(bound, {
|
|
3088
|
+
executionId,
|
|
3089
|
+
sendEvent(source, target, event) {
|
|
3090
|
+
if (target.address === rootAddress) {
|
|
3091
|
+
mailbox.push(event);
|
|
3092
|
+
return;
|
|
3093
|
+
}
|
|
3094
|
+
(0, xstate.deliverEvent)(source, target, event);
|
|
3095
|
+
},
|
|
3096
|
+
runtime(_metadata, effect) {
|
|
3097
|
+
const type = effect.type;
|
|
3098
|
+
if (type === "@xstate.spawn" || type === "@xstate.start") {
|
|
3099
|
+
const child = findChildRef(effect);
|
|
3100
|
+
if (!child) return {};
|
|
3101
|
+
if (type === "@xstate.spawn") {
|
|
3102
|
+
const seen = (startsSeen.get(child.id) ?? 0) + 1;
|
|
3103
|
+
startsSeen.set(child.id, seen);
|
|
3104
|
+
if (seen <= (journaledCompletions.get(child.id) ?? 0)) suppressedChildren.add(child);
|
|
3105
|
+
else liveInFlight.add(child.id);
|
|
3106
|
+
}
|
|
3107
|
+
if (suppressedChildren.has(child)) return {
|
|
3108
|
+
spawnActor() {},
|
|
3109
|
+
startActor() {}
|
|
3110
|
+
};
|
|
3111
|
+
}
|
|
3112
|
+
return {};
|
|
3113
|
+
},
|
|
3114
|
+
executeAction(action) {
|
|
3115
|
+
if (replaying) return;
|
|
3116
|
+
action.exec?.();
|
|
3117
|
+
},
|
|
3118
|
+
async waitForEvent() {
|
|
3119
|
+
if (mailbox.size() > 0) return await mailbox.take();
|
|
3120
|
+
if (liveInFlight.size > 0 && !(options.isIdle?.(latestSnapshot) ?? false)) return await mailbox.take();
|
|
3121
|
+
if (!liveEventConsumed && options.event !== void 0) {
|
|
3122
|
+
liveEventConsumed = true;
|
|
3123
|
+
return options.event;
|
|
3124
|
+
}
|
|
3125
|
+
throw IDLE;
|
|
3126
|
+
}
|
|
3127
|
+
});
|
|
3128
|
+
const entries = [...priorEntries];
|
|
3129
|
+
const entryOptions = {
|
|
3130
|
+
machineVersion,
|
|
3131
|
+
verification: options.verification ?? false
|
|
3132
|
+
};
|
|
3133
|
+
const appendEntry = (event) => {
|
|
3134
|
+
const entry = require_setup_agent.createReplayEntry(machine, entries, event, entryOptions);
|
|
3135
|
+
entries.push(entry);
|
|
3136
|
+
options.onEntry?.(entry);
|
|
3137
|
+
};
|
|
3138
|
+
if (!hasInit) {
|
|
3139
|
+
const entry = require_setup_agent.initEntry(machine, input, {
|
|
3140
|
+
...entryOptions,
|
|
3141
|
+
metadata: { executionId }
|
|
3142
|
+
});
|
|
3143
|
+
entries.push(entry);
|
|
3144
|
+
options.onEntry?.(entry);
|
|
3145
|
+
}
|
|
3146
|
+
let journalIndex = 0;
|
|
3147
|
+
let [snapshot, effects] = execution.initialTransition(input);
|
|
3148
|
+
latestSnapshot = snapshot;
|
|
3149
|
+
for (;;) {
|
|
3150
|
+
await execution.executeEffects(effects);
|
|
3151
|
+
const machineSnapshot = snapshot;
|
|
3152
|
+
if (machineSnapshot.status === "done") return {
|
|
3153
|
+
status: "done",
|
|
3154
|
+
output: machineSnapshot.output,
|
|
3155
|
+
snapshot,
|
|
3156
|
+
entries
|
|
3157
|
+
};
|
|
3158
|
+
if (machineSnapshot.status === "error") throw machineSnapshot.error;
|
|
3159
|
+
let event;
|
|
3160
|
+
let fromJournal = false;
|
|
3161
|
+
if (journalIndex < journal.length) {
|
|
3162
|
+
event = journal[journalIndex];
|
|
3163
|
+
journalIndex++;
|
|
3164
|
+
fromJournal = true;
|
|
3165
|
+
replaying = journalIndex < journal.length;
|
|
3166
|
+
} else try {
|
|
3167
|
+
event = await execution.waitForEvent();
|
|
3168
|
+
} catch (error) {
|
|
3169
|
+
if (error === IDLE) return {
|
|
3170
|
+
status: "idle",
|
|
3171
|
+
snapshot,
|
|
3172
|
+
entries
|
|
3173
|
+
};
|
|
3174
|
+
throw error;
|
|
3175
|
+
}
|
|
3176
|
+
const completedId = completionActorId(event);
|
|
3177
|
+
if (completedId !== void 0) liveInFlight.delete(completedId);
|
|
3178
|
+
if (!fromJournal) appendEntry(event);
|
|
3179
|
+
[snapshot, effects] = execution.transition(snapshot, event);
|
|
3180
|
+
latestSnapshot = snapshot;
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
//#endregion
|
|
2832
3184
|
exports.AGENT_EVENT_SCHEMA_VERSION = require_event_log_store.AGENT_EVENT_SCHEMA_VERSION;
|
|
2833
3185
|
exports.AGENT_INIT_EVENT_TYPE = require_setup_agent.AGENT_INIT_EVENT_TYPE;
|
|
2834
3186
|
exports.AGENT_TRACE_SCHEMA_VERSION = AGENT_TRACE_SCHEMA_VERSION;
|
|
@@ -2846,6 +3198,7 @@ exports.AgentSnapshotVersionMismatchError = AgentSnapshotVersionMismatchError;
|
|
|
2846
3198
|
exports.NonSerializableAgentEventError = require_event_log_store.NonSerializableAgentEventError;
|
|
2847
3199
|
exports.appendMessages = require_setup_agent.appendMessages;
|
|
2848
3200
|
exports.assertAgentLogEntry = require_event_log_store.assertAgentLogEntry;
|
|
3201
|
+
exports.assertAgentMachine = assertAgentMachine;
|
|
2849
3202
|
exports.assertEventLogStoreConformance = require_event_log_store.assertEventLogStoreConformance;
|
|
2850
3203
|
exports.assertJsonSerializable = require_event_log_store.assertJsonSerializable;
|
|
2851
3204
|
exports.assistantMessage = require_decision.assistantMessage;
|
|
@@ -2890,6 +3243,7 @@ exports.renderDecisionAttempts = require_decision.renderDecisionAttempts;
|
|
|
2890
3243
|
exports.replay = require_setup_agent.replay;
|
|
2891
3244
|
exports.resolveDecision = require_decision.resolveDecision;
|
|
2892
3245
|
exports.runAgent = runAgent;
|
|
3246
|
+
exports.runDurableAgent = runDurableAgent;
|
|
2893
3247
|
exports.runSeam = runSeam;
|
|
2894
3248
|
exports.serializeTraceEvent = serializeTraceEvent;
|
|
2895
3249
|
Object.defineProperty(exports, "setupAgent", {
|