@statelyai/agent 2.0.0-alpha.18 → 2.0.0-alpha.20
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 -2
- package/dist/ai-sdk.d.cts +1 -1
- package/dist/ai-sdk.d.mts +1 -1
- package/dist/ai-sdk.mjs +3 -2
- package/dist/{decision-C11xuud2.mjs → decision-BfhSgCc6.mjs} +48 -28
- package/dist/{decision-DnQCQPew.cjs → decision-DhsKLYAI.cjs} +64 -43
- package/dist/{event-log-store-CQJq8_v4.d.cts → event-log-store-Bz7HDBkE.d.cts} +11 -12
- package/dist/{event-log-store-B-1fcfkT.mjs → event-log-store-DmIDosD6.mjs} +22 -14
- package/dist/{event-log-store-yquOV1TX.cjs → event-log-store-a_TKy1gk.cjs} +22 -14
- package/dist/{event-log-store-BrC9Q1xW.d.mts → event-log-store-hrA1vqtN.d.mts} +11 -12
- package/dist/index.cjs +458 -109
- package/dist/index.d.cts +209 -81
- package/dist/index.d.mts +209 -81
- package/dist/index.mjs +455 -108
- package/dist/machines.cjs +13 -17
- package/dist/machines.d.cts +14 -17
- package/dist/machines.d.mts +14 -17
- package/dist/machines.mjs +13 -17
- package/dist/otel.cjs +1 -0
- package/dist/otel.d.cts +1 -1
- package/dist/otel.d.mts +1 -1
- package/dist/{run-agent-r9OD4z8F.d.cts → run-agent--4bbms-D.d.cts} +121 -48
- package/dist/{run-agent-DQIDikfd.d.mts → run-agent-CwmzAZwj.d.mts} +121 -48
- package/dist/{setup-agent-BrE2zFDy.mjs → setup-agent-BOcSpsIq.mjs} +38 -28
- package/dist/{setup-agent-C3ETi_HZ.cjs → setup-agent-gISRLxRe.cjs} +44 -33
- package/dist/sqlite.cjs +3 -9
- package/dist/sqlite.d.cts +1 -1
- package/dist/sqlite.d.mts +1 -1
- package/dist/sqlite.mjs +3 -9
- package/dist/{text-logic-VcWqO-Cl.d.cts → text-logic-Cavva1W6.d.cts} +24 -8
- package/dist/{text-logic-RvnlD-An.d.mts → text-logic-Er5KkTX6.d.mts} +24 -8
- package/dist/validate.cjs +436 -0
- package/dist/validate.d.cts +31 -0
- package/dist/validate.d.mts +31 -0
- package/dist/validate.mjs +411 -0
- package/package.json +17 -3
- package/schemas/agent-workflow.json +2 -2
- package/skills/generate-machine/SKILL.md +12 -14
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { t as AgentError } from "./errors-CeSXQx0v.mjs";
|
|
2
|
-
import { _ as initialAgentStep, a as AGENT_USAGE_EVENT_TYPE, b as appendMessages, c as createReplayEntry, d as initEntry, f as
|
|
3
|
-
import { A as isUnboundPlaceholder, B as getMachineStructuralHash, C as parseStructuredEnvelope, D as
|
|
4
|
-
import { a as assertJsonSerializable, i as assertAgentLogEntry, n as AgentEventLogConflictError, o as createInMemoryEventLogStore, r as NonSerializableAgentEventError, s as assertEventLogStoreConformance, t as AGENT_EVENT_SCHEMA_VERSION } from "./event-log-store-
|
|
5
|
-
import { createActor, createAsyncLogic, getNextTransitions, isMachineSnapshot } from "xstate";
|
|
2
|
+
import { _ as initialAgentStep, a as AGENT_USAGE_EVENT_TYPE, b as appendMessages, c as createReplayEntry, d as initEntry, f as rebindActorSession, g as getInvokeEffectMetadata, h as executeAgentRequest, i as AGENT_INIT_EVENT_TYPE, l as diffEventLogs, m as validateReplayEntries, n as getAgentSchemas, o as AgentReplayDivergenceError, p as replay, r as setupAgent, s as AgentReplayMachineMismatchError, t as createAgentSchemas, u as getAgentEffects, v as resolveAgentStep, x as messagesSchema, y as transitionAgentStep } from "./setup-agent-BOcSpsIq.mjs";
|
|
3
|
+
import { A as isUnboundPlaceholder, B as getMachineStructuralHash, C as parseStructuredEnvelope, D as getMachineIdlePredicate, E as executorBoundLogics, G as toolMessage, H as isStandardSchema, I as findNonSerializableContextPaths, K as userMessage, L as getAgentMessages, O as getMachineStaticTransitionTargets, P as assistantMessage, R as getJsonSchema, S as parseOutput, U as resolveMachineVersion, V as getStateMeta, W as systemMessage, _ as getAgentOutputMode, a as resolveDecision, b as normalizeGeneratorResult, c as AGENT_USAGE_TOKEN_FIELDS, f as bindRequestExecutor, h as createTextLogic, i as renderDecisionAttempts, k as getRegisteredAgentExecutionOptions, o as getAcceptedEvents, p as buildEnvelopeSchema, q as validateSchemaSync, r as isDecisionLogic, s as parseAgentEvent, t as AgentDecisionExhaustedError, u as INTERPRET_SOURCE, v as getCallUsage, x as parseModelRef, y as isTextLogic, z as getJsonSchemaSync } from "./decision-BfhSgCc6.mjs";
|
|
4
|
+
import { a as assertJsonSerializable, i as assertAgentLogEntry, n as AgentEventLogConflictError, o as createInMemoryEventLogStore, r as NonSerializableAgentEventError, s as assertEventLogStoreConformance, t as AGENT_EVENT_SCHEMA_VERSION } from "./event-log-store-DmIDosD6.mjs";
|
|
5
|
+
import { createActor, createAsyncLogic, deliverEvent, getNextTransitions, isMachineSnapshot } from "xstate";
|
|
6
|
+
import { createDurable } from "xstate/durable";
|
|
6
7
|
//#region src/internal/state-request-pass.ts
|
|
7
8
|
async function runTextPhase(stateRequest, baseMessages, deps) {
|
|
8
9
|
const { model, system } = stateRequest;
|
|
@@ -102,7 +103,7 @@ async function runAdvancePhase(plan, deps) {
|
|
|
102
103
|
messages: [...deps.messages],
|
|
103
104
|
events,
|
|
104
105
|
attempts: []
|
|
105
|
-
}, deps.decide, {
|
|
106
|
+
}, { decide: deps.decide }, {
|
|
106
107
|
signal: deps.signal,
|
|
107
108
|
canTake: (event) => deps.getSnapshot().can(event)
|
|
108
109
|
});
|
|
@@ -138,8 +139,7 @@ async function runStateRequestPass(requests, deps) {
|
|
|
138
139
|
* {@link getAcceptedEvents}). A programmer/integration error, in the same
|
|
139
140
|
* class as runAgent's bind-time throws — it throws rather than settling an
|
|
140
141
|
* `error` result. A type-legal event a guard rejects is NOT this error (the
|
|
141
|
-
* machine simply takes no transition).
|
|
142
|
-
* {@link RunAgentOptions.onIllegalResumeEvent} `'ignore'`.
|
|
142
|
+
* machine simply takes no transition). Always enforced; there is no opt-out.
|
|
143
143
|
*/
|
|
144
144
|
var AgentIllegalResumeEventError = class extends AgentError {
|
|
145
145
|
eventType;
|
|
@@ -257,7 +257,7 @@ function toJsonValue(value, ancestors) {
|
|
|
257
257
|
* in a JSONL file). Live values are sanitized rather than trusted:
|
|
258
258
|
*
|
|
259
259
|
* - Snapshots (`run.start`, `machine.transition`, `run.end`) go through the
|
|
260
|
-
* same JSON round-trip as
|
|
260
|
+
* same JSON round-trip as `machine.getPersistedSnapshot(...)`, so what lands on disk is
|
|
261
261
|
* what a resume would see.
|
|
262
262
|
* - `request.end`'s `raw` (a provider SDK object, frequently cyclic) is DROPPED
|
|
263
263
|
* unless `includeRaw` is set, in which case it is sanitized like everything
|
|
@@ -284,11 +284,92 @@ function serializeTraceEvent(event, options = {}) {
|
|
|
284
284
|
}
|
|
285
285
|
return out;
|
|
286
286
|
}
|
|
287
|
+
function snapshotNodes(snapshot) {
|
|
288
|
+
return (snapshot.nodes ?? []).map((raw) => {
|
|
289
|
+
const node = raw;
|
|
290
|
+
return {
|
|
291
|
+
id: node.id ?? "",
|
|
292
|
+
key: node.key ?? "",
|
|
293
|
+
...node.description !== void 0 ? { description: node.description } : {},
|
|
294
|
+
tags: [...node.tags ?? []],
|
|
295
|
+
...node.meta !== void 0 ? { meta: node.meta } : {},
|
|
296
|
+
ownEvents: [...node.ownEvents ?? []],
|
|
297
|
+
leaf: Object.keys(node.states ?? {}).length === 0
|
|
298
|
+
};
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* The active state nodes of a snapshot, as plain {@link AgentSnapshotNode}
|
|
303
|
+
* descriptors. The escape hatch under {@link getSnapshotRequests}: use it when
|
|
304
|
+
* your `getRequests` hook needs to build requests some other way, so host code
|
|
305
|
+
* never touches xstate's private `snapshot._nodes`.
|
|
306
|
+
*/
|
|
307
|
+
function getSnapshotNodes(snapshot) {
|
|
308
|
+
return snapshotNodes(snapshot);
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Builds the {@link AgentStateRequest}s for a snapshot straight from its active
|
|
312
|
+
* state nodes — the prompts-in-descriptions recipe as a function, so a
|
|
313
|
+
* {@link RunAgentOptions.getRequests} hook is one line and never reaches into
|
|
314
|
+
* xstate's private `snapshot._nodes`:
|
|
315
|
+
*
|
|
316
|
+
* ```ts
|
|
317
|
+
* runAgent(machine, {
|
|
318
|
+
* executors,
|
|
319
|
+
* getRequests: (snapshot) => getSnapshotRequests(snapshot, { model: 'writer' }),
|
|
320
|
+
* });
|
|
321
|
+
* ```
|
|
322
|
+
*
|
|
323
|
+
* Each described active node becomes one request: `prompt` from the node's
|
|
324
|
+
* `description`, `kind: 'decision'` when it is tagged `'decision'`, `system`
|
|
325
|
+
* from `meta.role` when present, `allowedEvents` scoped to the node's own
|
|
326
|
+
* events, and an explicit `onDone` when the node has exactly one own event
|
|
327
|
+
* (single-outcome states advance deterministically; anything else falls
|
|
328
|
+
* through to a `decide` call). Nodes tagged `'waiting'` produce nothing, so the
|
|
329
|
+
* run settles idle for a human. Override any of it with `filter`/`map`.
|
|
330
|
+
*/
|
|
331
|
+
function getSnapshotRequests(snapshot, options) {
|
|
332
|
+
const filter = options.filter ?? ((node) => !!node.description && !node.tags.includes("waiting"));
|
|
333
|
+
const requests = [];
|
|
334
|
+
for (const node of snapshotNodes(snapshot)) {
|
|
335
|
+
if (!filter(node)) continue;
|
|
336
|
+
const system = node.meta?.role;
|
|
337
|
+
const request = {
|
|
338
|
+
model: options.model,
|
|
339
|
+
prompt: node.description ?? "",
|
|
340
|
+
kind: node.tags.includes("decision") ? "decision" : "text",
|
|
341
|
+
...system !== void 0 ? { system } : {},
|
|
342
|
+
...node.ownEvents.length > 0 ? { allowedEvents: node.ownEvents } : {},
|
|
343
|
+
...node.ownEvents.length === 1 ? { onDone: { type: node.ownEvents[0] } } : {}
|
|
344
|
+
};
|
|
345
|
+
const mapped = options.map ? options.map(request, node) : request;
|
|
346
|
+
if (mapped) requests.push(mapped);
|
|
347
|
+
}
|
|
348
|
+
return requests;
|
|
349
|
+
}
|
|
287
350
|
let nextRunAgentTraceId = 1;
|
|
351
|
+
/**
|
|
352
|
+
* Thrown into the invoke that would have made the call once
|
|
353
|
+
* {@link RunAgentOptions.maxModelCalls} is spent. It reaches the machine
|
|
354
|
+
* through the normal error channel, so an invoke's `onError` can branch on it
|
|
355
|
+
* (`error.code === 'max-model-calls'`, the same string the settled result's
|
|
356
|
+
* `cause` uses) and route to a degraded/finish state instead of failing the
|
|
357
|
+
* run. Unhandled, it settles `{ status: 'error', cause: 'max-model-calls' }`.
|
|
358
|
+
*
|
|
359
|
+
* ```ts
|
|
360
|
+
* onError: [
|
|
361
|
+
* { guard: ({ event }) => event.error?.code === 'max-model-calls', target: 'budgetSpent' },
|
|
362
|
+
* { target: 'failed' },
|
|
363
|
+
* ]
|
|
364
|
+
* ```
|
|
365
|
+
*/
|
|
288
366
|
var AgentMaxModelCallsExceededError = class extends AgentError {
|
|
289
|
-
|
|
290
|
-
|
|
367
|
+
/** The budget that was exceeded (`options.maxModelCalls`). */
|
|
368
|
+
maxModelCalls;
|
|
369
|
+
constructor(maxModelCalls) {
|
|
370
|
+
super("max-model-calls", `runAgent exceeded maxModelCalls (${maxModelCalls}). Raise the budget, or handle it in the invoke's onError (error.code === 'max-model-calls').`);
|
|
291
371
|
this.name = "AgentMaxModelCallsExceededError";
|
|
372
|
+
this.maxModelCalls = maxModelCalls;
|
|
292
373
|
}
|
|
293
374
|
};
|
|
294
375
|
function wrapsDecisionExhausted(error) {
|
|
@@ -328,7 +409,7 @@ function collectConfiguredInvokeSrcs(stateConfig, stateName, out) {
|
|
|
328
409
|
* walk into invoked child machines (their internal agent requests are opaque
|
|
329
410
|
* to the parent-level source walk otherwise).
|
|
330
411
|
*/
|
|
331
|
-
function
|
|
412
|
+
function isStateMachineLogic(logic) {
|
|
332
413
|
return !!logic && typeof logic === "object" && "config" in logic && "root" in logic && typeof logic.provide === "function" && typeof logic.sources === "object" && !!logic.sources?.actors;
|
|
333
414
|
}
|
|
334
415
|
/**
|
|
@@ -360,7 +441,7 @@ function assertMachineBindable(machine, effectiveSources, executors, ctx) {
|
|
|
360
441
|
const where = ctx.isChild ? `child machine '${ctx.childPath}' state` : "state";
|
|
361
442
|
for (const { stateName, src } of invokes) {
|
|
362
443
|
if (typeof src !== "string") {
|
|
363
|
-
if (
|
|
444
|
+
if (isStateMachineLogic(src)) {
|
|
364
445
|
assertChildMachineBindable(src, src, stateName, executors, ctx);
|
|
365
446
|
continue;
|
|
366
447
|
}
|
|
@@ -369,7 +450,7 @@ function assertMachineBindable(machine, effectiveSources, executors, ctx) {
|
|
|
369
450
|
}
|
|
370
451
|
const logic = effectiveSources[src];
|
|
371
452
|
if (logic === void 0) throw new Error(`runAgent: ${where} '${stateName}' invokes unregistered actor source '${src}'. Provide it via machine.provide({ actors: { '${src}': ... } }) or runAgent(machine, { actors: { '${src}': ... } }).`);
|
|
372
|
-
if (
|
|
453
|
+
if (isStateMachineLogic(logic)) {
|
|
373
454
|
assertChildMachineBindable(logic, src, stateName, executors, ctx);
|
|
374
455
|
continue;
|
|
375
456
|
}
|
|
@@ -415,18 +496,16 @@ function unrebindableChildRequestError(childPath, stateName, requestSrc, kind) {
|
|
|
415
496
|
return /* @__PURE__ */ new Error(`runAgent: child machine '${childPath}' (state '${stateName}') invokes ${kind} source '${requestSrc}', which has no host execution and is reached through a direct-object invoke src that runAgent cannot rebind. Requests reached through string-keyed actor sources inherit runAgent's generateText/streamText/decide executors automatically; a direct-object child machine does not. Either bind the request with its own executor (requestLogic.withExecutor(...)), or register the child as a string-keyed actor source (machine.provide({ actors: { <child>: childMachine } })) and invoke it by name.`);
|
|
416
497
|
}
|
|
417
498
|
/**
|
|
418
|
-
* True when the snapshot's active states declare a transition
|
|
419
|
-
* `'@agent.usage'` type
|
|
420
|
-
*
|
|
421
|
-
*
|
|
422
|
-
*
|
|
423
|
-
*
|
|
424
|
-
*
|
|
425
|
-
* a wildcard machine's context and event log byte-identical to a run without
|
|
426
|
-
* the feature. @internal
|
|
499
|
+
* True when the snapshot's active states declare a transition that would
|
|
500
|
+
* receive the reserved `'@agent.usage'` type — an explicit `on: { '@agent.usage'
|
|
501
|
+
* }` OR a catch-all `on: { '*': … }`. Plain XState semantics apply unmodified:
|
|
502
|
+
* a wildcard matches every event delivered to the machine, reserved ones
|
|
503
|
+
* included. (The MODEL-facing side stays closed: `getAcceptedEvents` drops
|
|
504
|
+
* `@agent.*` before any `allowedEvents` matching, so a wildcard never offers
|
|
505
|
+
* the reserved event as a decision candidate.) @internal
|
|
427
506
|
*/
|
|
428
507
|
function declaresUsageTransition(snapshot) {
|
|
429
|
-
return getNextTransitions(snapshot).some((transition) => transition.eventType ===
|
|
508
|
+
return getNextTransitions(snapshot).some((transition) => transition.eventType === "@agent.usage" || transition.eventType === "*");
|
|
430
509
|
}
|
|
431
510
|
/**
|
|
432
511
|
* Builds the ONE place a trace payload is emitted: it hands the payload to the
|
|
@@ -493,7 +572,7 @@ function bindTextLogic(logic, runCtx) {
|
|
|
493
572
|
const self = selfArg;
|
|
494
573
|
const { id, src } = selfIdAndSrc(self);
|
|
495
574
|
const executor = logic.mode === "stream" ? runCtx.streamText : runCtx.generateText;
|
|
496
|
-
if (!executor) throw new Error(`
|
|
575
|
+
if (!executor) throw new Error(`No '${logic.mode === "stream" ? "streamText" : "generateText"}' executor provided.`);
|
|
497
576
|
const requestWithTools = {
|
|
498
577
|
...request,
|
|
499
578
|
tools: request.tools ?? {}
|
|
@@ -565,17 +644,22 @@ function bindTextLogic(logic, runCtx) {
|
|
|
565
644
|
});
|
|
566
645
|
}
|
|
567
646
|
function createCountingDecide(runCtx, self) {
|
|
568
|
-
return async (attemptRequest) => {
|
|
647
|
+
return async (attemptRequest, info) => {
|
|
569
648
|
runCtx.consumeModelCall();
|
|
570
649
|
runCtx.emitTrace?.({
|
|
571
650
|
type: "request.start",
|
|
572
651
|
request: attemptRequest
|
|
573
652
|
}, self);
|
|
574
653
|
try {
|
|
654
|
+
const { id } = selfIdAndSrc(self);
|
|
575
655
|
const result = await runCtx.decide(runCtx.runId !== void 0 ? {
|
|
576
656
|
...attemptRequest,
|
|
577
657
|
runId: runCtx.runId
|
|
578
|
-
} : attemptRequest
|
|
658
|
+
} : attemptRequest, {
|
|
659
|
+
...info,
|
|
660
|
+
...runCtx.runId !== void 0 ? { runId: runCtx.runId } : {},
|
|
661
|
+
...info?.requestId === void 0 && id !== "" ? { requestId: id } : {}
|
|
662
|
+
});
|
|
579
663
|
const usage = getCallUsage(result);
|
|
580
664
|
if (usage) {
|
|
581
665
|
const { src } = selfIdAndSrc(self);
|
|
@@ -621,7 +705,7 @@ function createCountingDecide(runCtx, self) {
|
|
|
621
705
|
*/
|
|
622
706
|
function bindDecisionLogic(logic, runCtx) {
|
|
623
707
|
const decisionLogic = createAsyncLogic({ run: async ({ input, signal, self: selfArg }) => {
|
|
624
|
-
if (!runCtx.decide) throw new Error("
|
|
708
|
+
if (!runCtx.decide) throw new Error("No 'decide' executor provided.");
|
|
625
709
|
const self = selfArg;
|
|
626
710
|
const { id } = selfIdAndSrc(self);
|
|
627
711
|
const declaredEventTypes = logic.allowedEventTypes?.(input);
|
|
@@ -635,7 +719,7 @@ function bindDecisionLogic(logic, runCtx) {
|
|
|
635
719
|
...logic.request(input),
|
|
636
720
|
id,
|
|
637
721
|
events
|
|
638
|
-
}, createCountingDecide(runCtx, self), {
|
|
722
|
+
}, { decide: createCountingDecide(runCtx, self) }, {
|
|
639
723
|
maxRetries: logic.maxRetries,
|
|
640
724
|
signal,
|
|
641
725
|
canTake: (event) => actorRef ? actorRef.getSnapshot().can(event) : true
|
|
@@ -680,7 +764,7 @@ function rootTraceState(root) {
|
|
|
680
764
|
if (!state) {
|
|
681
765
|
const logic = root.logic;
|
|
682
766
|
const machineId = (logic?.config)?.id ?? logic?.id ?? "(machine)";
|
|
683
|
-
const machineVersion = logic ?
|
|
767
|
+
const machineVersion = logic ? resolveMachineVersion(logic) : "";
|
|
684
768
|
state = {
|
|
685
769
|
runId: `run_${nextProvideRunId++}`,
|
|
686
770
|
seq: 0,
|
|
@@ -752,9 +836,9 @@ function provideBindContext(machine, executors, options) {
|
|
|
752
836
|
* under a live `createActor` tree) on the `provideExecutors` path.
|
|
753
837
|
*
|
|
754
838
|
* Gating is identical on both: the target snapshot must be active, must declare
|
|
755
|
-
* an `'@agent.usage'` transition
|
|
756
|
-
*
|
|
757
|
-
* event. `onDropped` is the run path's straggler gate: it returns `true` for a
|
|
839
|
+
* an `'@agent.usage'` transition — explicitly, or through a catch-all
|
|
840
|
+
* `on: { '*' }` (see {@link declaresUsageTransition}) — and must be able to
|
|
841
|
+
* take the event. `onDropped` is the run path's straggler gate: it returns `true` for a
|
|
758
842
|
* call that settled after the cycle resolved, which drops the event (traced as
|
|
759
843
|
* `usage.dropped`) rather than delivering it. Uncontrolled mode has no cycle to
|
|
760
844
|
* settle, so it passes no gate and has no dropped stragglers.
|
|
@@ -796,6 +880,19 @@ function bindDecisionForProvide(machine, logic, executors, options) {
|
|
|
796
880
|
return bindDecisionLogic(logic, provideBindContext(machine, executors, options));
|
|
797
881
|
}
|
|
798
882
|
/**
|
|
883
|
+
* Recursively binds an invoked child state machine for {@link provideExecutors},
|
|
884
|
+
* with the same semantics `runAgent` applies ({@link rebindChildMachine}):
|
|
885
|
+
* string-keyed text/decision sources at any depth inherit the host executors,
|
|
886
|
+
* a source that carries its own executor is left alone, and a cycle is
|
|
887
|
+
* returned as-is. Each machine in the tree is bound with its own registered
|
|
888
|
+
* `setupAgent` schemas. Returns the original machine when nothing needed
|
|
889
|
+
* wrapping. @internal
|
|
890
|
+
*/
|
|
891
|
+
function bindChildMachineForProvide(childMachine, executors, options, visited) {
|
|
892
|
+
const ctxFor = (target) => provideBindContext(target, executors, options);
|
|
893
|
+
return rebindChildMachine(childMachine, ctxFor(childMachine), visited, ctxFor);
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
799
896
|
* Validates `input` against the machine's registered input schema, returning
|
|
800
897
|
* the schema's output — so defaults are filled and transforms applied before
|
|
801
898
|
* the value reaches `createActor` or the replayable event log.
|
|
@@ -829,9 +926,10 @@ function resolveMachineInput(machine, input) {
|
|
|
829
926
|
* (`executorBoundLogics`) is left as-is: explicit binding shadows inheritance.
|
|
830
927
|
* Cycle-safe via `visited` (a machine that invokes itself is returned as-is).
|
|
831
928
|
*/
|
|
832
|
-
function rebindChildMachine(childMachine, runCtx, visited) {
|
|
929
|
+
function rebindChildMachine(childMachine, runCtx, visited, ctxFor) {
|
|
833
930
|
if (visited.has(childMachine)) return childMachine;
|
|
834
931
|
const childVisited = new Set([...visited, childMachine]);
|
|
932
|
+
runCtx = ctxFor ? ctxFor(childMachine) : runCtx;
|
|
835
933
|
const sources = childMachine.sources.actors;
|
|
836
934
|
const wrapped = {};
|
|
837
935
|
for (const [key, logic] of Object.entries(sources)) {
|
|
@@ -843,8 +941,8 @@ function rebindChildMachine(childMachine, runCtx, visited) {
|
|
|
843
941
|
if (!executorBoundLogics.has(logic)) wrapped[key] = bindTextLogic(logic, runCtx);
|
|
844
942
|
continue;
|
|
845
943
|
}
|
|
846
|
-
if (
|
|
847
|
-
const rebound = rebindChildMachine(logic, runCtx, childVisited);
|
|
944
|
+
if (isStateMachineLogic(logic)) {
|
|
945
|
+
const rebound = rebindChildMachine(logic, runCtx, childVisited, ctxFor);
|
|
848
946
|
if (rebound !== logic) wrapped[key] = rebound;
|
|
849
947
|
continue;
|
|
850
948
|
}
|
|
@@ -927,7 +1025,7 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
927
1025
|
let traceSeq = 0;
|
|
928
1026
|
const resolvedInput = resolveMachineInput(machine, options.input);
|
|
929
1027
|
const machineId = machine.config.id ?? machine.id ?? "(machine)";
|
|
930
|
-
const machineVersion =
|
|
1028
|
+
const machineVersion = resolveMachineVersion(machine);
|
|
931
1029
|
const agentMeta = {
|
|
932
1030
|
machineId,
|
|
933
1031
|
version: machineVersion
|
|
@@ -949,10 +1047,10 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
949
1047
|
onTransition: options.onTransition
|
|
950
1048
|
});
|
|
951
1049
|
const consumeModelCall = () => {
|
|
952
|
-
if (budgetExceeded) throw new AgentMaxModelCallsExceededError();
|
|
1050
|
+
if (budgetExceeded) throw new AgentMaxModelCallsExceededError(maxModelCalls);
|
|
953
1051
|
if (modelCallCount + 1 > maxModelCalls) {
|
|
954
1052
|
budgetExceeded = true;
|
|
955
|
-
throw new AgentMaxModelCallsExceededError();
|
|
1053
|
+
throw new AgentMaxModelCallsExceededError(maxModelCalls);
|
|
956
1054
|
}
|
|
957
1055
|
modelCallCount += 1;
|
|
958
1056
|
};
|
|
@@ -1026,19 +1124,20 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1026
1124
|
if (!executorBoundLogics.has(logic)) wrappedSources[key] = bindTextLogic(logic, runCtx);
|
|
1027
1125
|
continue;
|
|
1028
1126
|
}
|
|
1029
|
-
if (
|
|
1127
|
+
if (isStateMachineLogic(logic)) {
|
|
1030
1128
|
const rebound = rebindChildMachine(logic, runCtx, new Set([machine]));
|
|
1031
1129
|
if (rebound !== logic) wrappedSources[key] = rebound;
|
|
1032
1130
|
continue;
|
|
1033
1131
|
}
|
|
1034
1132
|
}
|
|
1035
1133
|
const boundMachine = provided.provide({ actors: wrappedSources });
|
|
1036
|
-
const
|
|
1037
|
-
const
|
|
1134
|
+
const declaredIdlePredicate = options.isIdle ?? getMachineIdlePredicate(machine);
|
|
1135
|
+
const isIdle = declaredIdlePredicate ?? (() => false);
|
|
1136
|
+
const machineDeclaresMigrate = typeof machine.config.migrate === "function";
|
|
1038
1137
|
let effectiveSnapshot = options.snapshot;
|
|
1039
1138
|
if (effectiveSnapshot !== void 0) {
|
|
1040
1139
|
const from = effectiveSnapshot.agentMeta?.version ?? effectiveSnapshot.version;
|
|
1041
|
-
if (from !== void 0 && from !== machineVersion) {
|
|
1140
|
+
if (from !== void 0 && from !== machineVersion && !(machineDeclaresMigrate && !options.migrateSnapshot)) {
|
|
1042
1141
|
const info = {
|
|
1043
1142
|
from,
|
|
1044
1143
|
to: machineVersion
|
|
@@ -1057,10 +1156,11 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1057
1156
|
effectiveSnapshot = machine.getPersistedSnapshot(replayedSnapshot);
|
|
1058
1157
|
}
|
|
1059
1158
|
const machineOwnVersion = machine.version;
|
|
1060
|
-
if (effectiveSnapshot !== void 0 && effectiveSnapshot.version !== machineOwnVersion) {
|
|
1159
|
+
if (!machineDeclaresMigrate && effectiveSnapshot !== void 0 && effectiveSnapshot.version !== machineOwnVersion) {
|
|
1061
1160
|
const aligned = Object.assign(Object.create(Object.getPrototypeOf(effectiveSnapshot)), effectiveSnapshot);
|
|
1062
1161
|
if (machineOwnVersion === void 0) delete aligned.version;
|
|
1063
1162
|
else aligned.version = machineOwnVersion;
|
|
1163
|
+
delete aligned.machine;
|
|
1064
1164
|
effectiveSnapshot = aligned;
|
|
1065
1165
|
}
|
|
1066
1166
|
const priorMessages = getAgentMessages(effectiveSnapshot);
|
|
@@ -1071,7 +1171,7 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1071
1171
|
if (!options.getRequests && !options.messages && messages.length === 0) return;
|
|
1072
1172
|
snapshot.messages = [...messages];
|
|
1073
1173
|
};
|
|
1074
|
-
if (effectiveSnapshot !== void 0 && options.event !== void 0
|
|
1174
|
+
if (effectiveSnapshot !== void 0 && options.event !== void 0) {
|
|
1075
1175
|
const acceptedTypes = getAcceptedEvents(createActor(boundMachine, { snapshot: effectiveSnapshot }).getSnapshot(), { schemas: runCtx.schemas }).map((descriptor) => descriptor.type);
|
|
1076
1176
|
const eventType = options.event.type;
|
|
1077
1177
|
if (!acceptedTypes.includes(eventType)) throw new AgentIllegalResumeEventError(eventType, acceptedTypes);
|
|
@@ -1217,9 +1317,9 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1217
1317
|
const current = actor.getSnapshot();
|
|
1218
1318
|
if (isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) {
|
|
1219
1319
|
if (!maybeInterpret(current)) {
|
|
1220
|
-
if (!
|
|
1320
|
+
if (!declaredIdlePredicate && current.status === "active" && !warnedHeuristicIdle && process.env.NODE_ENV !== "production") {
|
|
1221
1321
|
warnedHeuristicIdle = true;
|
|
1222
|
-
console.warn("[@statelyai/agent] runAgent settled idle via the timing heuristic (no
|
|
1322
|
+
console.warn("[@statelyai/agent] runAgent settled idle via the timing heuristic (no idle predicate declared). This is best-effort; for deterministic idle detection, declare setupAgent({ isIdle }) or pass runAgent(machine, { isIdle }), e.g. (s) => s.hasTag('waiting').");
|
|
1223
1323
|
}
|
|
1224
1324
|
settleIdle(current);
|
|
1225
1325
|
}
|
|
@@ -1274,11 +1374,11 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1274
1374
|
});
|
|
1275
1375
|
return;
|
|
1276
1376
|
}
|
|
1277
|
-
if (!deliveringResumeEvent &&
|
|
1377
|
+
if (!deliveringResumeEvent && isIdle(snapshot) && isIdleSnapshot(snapshot, { ignoreUserInputChildren: userInputIsPlaceholder })) {
|
|
1278
1378
|
queueMicrotask(() => {
|
|
1279
1379
|
if (settled) return;
|
|
1280
1380
|
const current = actor.getSnapshot();
|
|
1281
|
-
if (
|
|
1381
|
+
if (isIdle(current) && isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) {
|
|
1282
1382
|
if (!maybeInterpret(current)) settleIdle(current);
|
|
1283
1383
|
} else scheduleIdleCheck();
|
|
1284
1384
|
});
|
|
@@ -1575,9 +1675,12 @@ function createAgentRun(machine, options) {
|
|
|
1575
1675
|
* Throws at bind time if a source needs an executor kind that `executors` does
|
|
1576
1676
|
* not provide.
|
|
1577
1677
|
*
|
|
1578
|
-
*
|
|
1579
|
-
* machine
|
|
1580
|
-
*
|
|
1678
|
+
* Executor inheritance is RECURSIVE, exactly as in `runAgent`: a string-keyed
|
|
1679
|
+
* invoked child machine is rebound too, so its own text/decision requests — at
|
|
1680
|
+
* any depth — reach the same host executors. A direct-object invoke `src`
|
|
1681
|
+
* cannot be swapped via `.provide`, so nothing under one inherits; bind those
|
|
1682
|
+
* with `.withExecutor(...)` or register the child as a string-keyed source. A
|
|
1683
|
+
* source that already carries its own executor is never overwritten.
|
|
1581
1684
|
*/
|
|
1582
1685
|
function provideExecutors(machine, executors, options = {}) {
|
|
1583
1686
|
const bindOptions = {
|
|
@@ -1591,21 +1694,18 @@ function provideExecutors(machine, executors, options = {}) {
|
|
|
1591
1694
|
const invokedSrcs = getConfiguredInvokeSrcs(provided);
|
|
1592
1695
|
for (const [key, logic] of Object.entries(effectiveSources)) {
|
|
1593
1696
|
if (key === "agent.userInput") continue;
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
};
|
|
1600
|
-
else if (isTextLogic(logic)) {
|
|
1601
|
-
const streaming = logic.mode === "stream";
|
|
1602
|
-
binding = {
|
|
1603
|
-
executorKey: streaming ? "streamText" : "generateText",
|
|
1604
|
-
kind: streaming ? "streaming text" : "text",
|
|
1605
|
-
bind: () => bindTextForProvide(provided, logic, executors, bindOptions)
|
|
1606
|
-
};
|
|
1697
|
+
if (isStateMachineLogic(logic)) {
|
|
1698
|
+
if (invokedSrcs.has(key)) assertChildBindable(logic, executors, key, new Set([provided]));
|
|
1699
|
+
const rebound = bindChildMachineForProvide(logic, executors, bindOptions, new Set([provided]));
|
|
1700
|
+
if (rebound !== logic) wrappedSources[key] = rebound;
|
|
1701
|
+
continue;
|
|
1607
1702
|
}
|
|
1608
|
-
|
|
1703
|
+
const requirement = executorRequirementOf(logic);
|
|
1704
|
+
if (!requirement) continue;
|
|
1705
|
+
const binding = {
|
|
1706
|
+
...requirement,
|
|
1707
|
+
bind: () => isDecisionLogic(logic) ? bindDecisionForProvide(provided, logic, executors, bindOptions) : bindTextForProvide(provided, logic, executors, bindOptions)
|
|
1708
|
+
};
|
|
1609
1709
|
if (executorBoundLogics.has(logic)) continue;
|
|
1610
1710
|
if (!executors[binding.executorKey]) {
|
|
1611
1711
|
if (invokedSrcs.has(key)) throw missingExecutorError(key, binding.kind, binding.executorKey);
|
|
@@ -1615,12 +1715,50 @@ function provideExecutors(machine, executors, options = {}) {
|
|
|
1615
1715
|
}
|
|
1616
1716
|
return withActors(provided, wrappedSources);
|
|
1617
1717
|
}
|
|
1718
|
+
/** The executor slot + label an agent logic needs, or `undefined` for a non-agent actor. */
|
|
1719
|
+
function executorRequirementOf(logic) {
|
|
1720
|
+
if (isDecisionLogic(logic)) return {
|
|
1721
|
+
executorKey: "decide",
|
|
1722
|
+
kind: "decision"
|
|
1723
|
+
};
|
|
1724
|
+
if (isTextLogic(logic)) return logic.mode === "stream" ? {
|
|
1725
|
+
executorKey: "streamText",
|
|
1726
|
+
kind: "streaming text"
|
|
1727
|
+
} : {
|
|
1728
|
+
executorKey: "generateText",
|
|
1729
|
+
kind: "text"
|
|
1730
|
+
};
|
|
1731
|
+
}
|
|
1732
|
+
/**
|
|
1733
|
+
* Walks an invoked child machine's own invoked sources (recursively, at any
|
|
1734
|
+
* depth) and throws the same missing-executor error `provideExecutors` throws
|
|
1735
|
+
* for the top-level machine — before any actor starts. Mirrors runAgent's
|
|
1736
|
+
* `assertBindable` for the uncontrolled path. Cycle-safe via `visited`.
|
|
1737
|
+
*/
|
|
1738
|
+
function assertChildBindable(childMachine, executors, path, visited) {
|
|
1739
|
+
if (visited.has(childMachine)) return;
|
|
1740
|
+
const nextVisited = new Set([...visited, childMachine]);
|
|
1741
|
+
const sources = childMachine.sources.actors;
|
|
1742
|
+
for (const src of getConfiguredInvokeSrcs(childMachine)) {
|
|
1743
|
+
if (src === "agent.userInput") continue;
|
|
1744
|
+
const logic = sources[src];
|
|
1745
|
+
if (!logic) continue;
|
|
1746
|
+
if (isStateMachineLogic(logic)) {
|
|
1747
|
+
assertChildBindable(logic, executors, `${path} > ${src}`, nextVisited);
|
|
1748
|
+
continue;
|
|
1749
|
+
}
|
|
1750
|
+
const requirement = executorRequirementOf(logic);
|
|
1751
|
+
if (!requirement || executorBoundLogics.has(logic)) continue;
|
|
1752
|
+
if (!executors[requirement.executorKey]) throw missingExecutorError(`${path} > ${src}`, requirement.kind, requirement.executorKey);
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1618
1755
|
function missingExecutorError(src, kind, executor) {
|
|
1619
1756
|
return /* @__PURE__ */ new Error(`provideExecutors: actor source '${src}' is a ${kind} source but no '${executor}' executor was provided. Add it to the executors object, or bind the source with its own executor (logic.withExecutor(...)) before calling provideExecutors.`);
|
|
1620
1757
|
}
|
|
1621
1758
|
//#endregion
|
|
1622
1759
|
//#region src/verify.ts
|
|
1623
1760
|
const DECIDE_SRC = "agent.decide";
|
|
1761
|
+
const USER_INPUT_SRC = "agent.userInput";
|
|
1624
1762
|
function normalizeInvokes(invoke) {
|
|
1625
1763
|
if (invoke === void 0 || invoke === null) return [];
|
|
1626
1764
|
return Array.isArray(invoke) ? invoke : [invoke];
|
|
@@ -1907,11 +2045,23 @@ const LINT_CHECKS = [
|
|
|
1907
2045
|
* call this to catch dead states, undeliverable decisions, un-rebindable
|
|
1908
2046
|
* invoke srcs, and output-contract gaps before ever running it.
|
|
1909
2047
|
*
|
|
2048
|
+
* Pass `{ throw: true }` for the one-liner form used in tests and generation
|
|
2049
|
+
* loops: it returns silently when the machine is clean and throws
|
|
2050
|
+
* {@link AgentLintError} (findings on `.diagnostics`) on error-severity
|
|
2051
|
+
* findings, or on warnings too with `{ throw: true, warnings: true }`.
|
|
2052
|
+
*
|
|
1910
2053
|
* @example
|
|
1911
2054
|
* ```ts
|
|
1912
2055
|
* const errors = lintAgentMachine(machine).filter((d) => d.severity === 'error');
|
|
1913
2056
|
* if (errors.length) throw new Error(errors.map((e) => `${e.path}: ${e.message}`).join('\n'));
|
|
1914
2057
|
* ```
|
|
2058
|
+
*
|
|
2059
|
+
* @example Throwing form
|
|
2060
|
+
* ```ts
|
|
2061
|
+
* test('agent machine is structurally sound', () => {
|
|
2062
|
+
* lintAgentMachine(machine, { throw: true });
|
|
2063
|
+
* });
|
|
2064
|
+
* ```
|
|
1915
2065
|
*/
|
|
1916
2066
|
function lintAgentMachine(machine, options = {}) {
|
|
1917
2067
|
const config = machine.config ?? {};
|
|
@@ -1926,10 +2076,16 @@ function lintAgentMachine(machine, options = {}) {
|
|
|
1926
2076
|
actors: registered.actors ?? machine.sources?.actors ?? {}
|
|
1927
2077
|
};
|
|
1928
2078
|
const disabled = new Set(options.disable ?? []);
|
|
1929
|
-
|
|
2079
|
+
const diagnostics = LINT_CHECKS.flatMap((check) => check(ctx)).filter((d) => !disabled.has(d.code));
|
|
2080
|
+
if (options.throw) {
|
|
2081
|
+
const failing = options.warnings ? diagnostics : diagnostics.filter((d) => d.severity === "error");
|
|
2082
|
+
if (failing.length > 0) throw new AgentLintError(machine.id ?? "(machine)", failing);
|
|
2083
|
+
}
|
|
2084
|
+
return diagnostics;
|
|
1930
2085
|
}
|
|
1931
2086
|
/**
|
|
1932
|
-
* Thrown by {
|
|
2087
|
+
* Thrown by `lintAgentMachine(machine, { throw: true })` when lint finds
|
|
2088
|
+
* failing diagnostics.
|
|
1933
2089
|
* `diagnostics` holds the findings; the message lists them one per finding,
|
|
1934
2090
|
* so a test runner's failure output reads like the CLI's lint report.
|
|
1935
2091
|
*/
|
|
@@ -1942,24 +2098,6 @@ var AgentLintError = class extends AgentError {
|
|
|
1942
2098
|
this.diagnostics = diagnostics;
|
|
1943
2099
|
}
|
|
1944
2100
|
};
|
|
1945
|
-
/**
|
|
1946
|
-
* Asserts a machine passes {@link lintAgentMachine}: returns silently when
|
|
1947
|
-
* clean, throws {@link AgentLintError} (with the findings on `.diagnostics`)
|
|
1948
|
-
* otherwise. Fails on error-severity findings; set `warnings: true` to fail on
|
|
1949
|
-
* warnings too. The one-liner for tests and generation loops:
|
|
1950
|
-
*
|
|
1951
|
-
* @example
|
|
1952
|
-
* ```ts
|
|
1953
|
-
* test('agent machine is structurally sound', () => {
|
|
1954
|
-
* assertAgentMachine(machine);
|
|
1955
|
-
* });
|
|
1956
|
-
* ```
|
|
1957
|
-
*/
|
|
1958
|
-
function assertAgentMachine(machine, options = {}) {
|
|
1959
|
-
const diagnostics = lintAgentMachine(machine, options);
|
|
1960
|
-
const failing = options.warnings ? diagnostics : diagnostics.filter((d) => d.severity === "error");
|
|
1961
|
-
if (failing.length > 0) throw new AgentLintError(machine.id ?? "(machine)", failing);
|
|
1962
|
-
}
|
|
1963
2101
|
function pendingInvokes(step) {
|
|
1964
2102
|
const out = [];
|
|
1965
2103
|
for (const action of step.actions) {
|
|
@@ -2005,6 +2143,7 @@ async function simulateAgent(machine, options) {
|
|
|
2005
2143
|
decisions: mapValues(options.script.decisions ?? {}, (arr) => [...arr]),
|
|
2006
2144
|
invokes: mapValues(options.script.invokes ?? {}, (arr) => [...arr])
|
|
2007
2145
|
};
|
|
2146
|
+
if (options.script.userInput?.length) script.invokes[USER_INPUT_SRC] = [...options.script.userInput, ...script.invokes[USER_INPUT_SRC] ?? []];
|
|
2008
2147
|
let step = initialAgentStep(machine, options.input);
|
|
2009
2148
|
const trail = [];
|
|
2010
2149
|
for (let i = 0; i < maxSteps; i++) {
|
|
@@ -2071,13 +2210,16 @@ function mapValues(obj, fn) {
|
|
|
2071
2210
|
}
|
|
2072
2211
|
function scriptDryError(kind, src, id, request) {
|
|
2073
2212
|
const events = request?.kind === "decision" ? ` Candidate events: ${request.events.map((e) => e.type).join(", ") || "(none)"}.` : "";
|
|
2074
|
-
|
|
2213
|
+
const key = kind === "text" ? `text['${src}']` : kind === "decision" ? `decisions['${src}']` : src === USER_INPUT_SRC ? "userInput" : `invokes['${src}']`;
|
|
2214
|
+
return /* @__PURE__ */ new Error(`simulateAgent: script ran dry on a pending ${kind} request for src '${src}' (id '${id}'). Add an entry to the script's \`${key}\` queue.${events}`);
|
|
2075
2215
|
}
|
|
2076
2216
|
const MAX_ADVANCE_STEPS = 1e3;
|
|
2077
2217
|
async function explore(machine, options, stopWhen) {
|
|
2078
2218
|
const maxDepth = options.maxDepth ?? 8;
|
|
2079
2219
|
const maxPaths = options.maxPaths ?? 200;
|
|
2080
|
-
const
|
|
2220
|
+
const textScript = options.text ?? {};
|
|
2221
|
+
const invokeOutputs = { ...options.invokes ?? {} };
|
|
2222
|
+
if ("userInput" in options) invokeOutputs[USER_INPUT_SRC] = options.userInput;
|
|
2081
2223
|
const reachedStates = /* @__PURE__ */ new Set();
|
|
2082
2224
|
const reachedValues = [];
|
|
2083
2225
|
const terminals = [];
|
|
@@ -2102,22 +2244,22 @@ async function explore(machine, options, stopWhen) {
|
|
|
2102
2244
|
if (current.done) return { step: current };
|
|
2103
2245
|
const request = current.requests[0];
|
|
2104
2246
|
if (request && request.kind === "text") {
|
|
2105
|
-
if (!(request.src in
|
|
2247
|
+
if (!(request.src in textScript)) return {
|
|
2106
2248
|
step: current,
|
|
2107
2249
|
blockedSrc: request.src
|
|
2108
2250
|
};
|
|
2109
|
-
current = resolveAgentStep(machine, current, request,
|
|
2251
|
+
current = resolveAgentStep(machine, current, request, textScript[request.src]);
|
|
2110
2252
|
recordState(current.snapshot);
|
|
2111
2253
|
continue;
|
|
2112
2254
|
}
|
|
2113
2255
|
if (request && request.kind === "decision") return { step: current };
|
|
2114
2256
|
const [invoke] = pendingInvokes(current);
|
|
2115
2257
|
if (invoke) {
|
|
2116
|
-
if (!(invoke.src in
|
|
2258
|
+
if (!(invoke.src in invokeOutputs)) return {
|
|
2117
2259
|
step: current,
|
|
2118
2260
|
blockedSrc: invoke.src
|
|
2119
2261
|
};
|
|
2120
|
-
current = resolveAgentStep(machine, current, invoke.id,
|
|
2262
|
+
current = resolveAgentStep(machine, current, invoke.id, invokeOutputs[invoke.src]);
|
|
2121
2263
|
recordState(current.snapshot);
|
|
2122
2264
|
continue;
|
|
2123
2265
|
}
|
|
@@ -2209,9 +2351,10 @@ async function explore(machine, options, stopWhen) {
|
|
|
2209
2351
|
* depth, model-free, and reports which states are reached and how each path
|
|
2210
2352
|
* terminates. At each decision request it forks one branch per candidate event
|
|
2211
2353
|
* (guard-rejected candidates are counted in `prunedByGuard`, not explored); at
|
|
2212
|
-
* an idle wait it forks per externally-accepted event. Text
|
|
2213
|
-
*
|
|
2214
|
-
*
|
|
2354
|
+
* an idle wait it forks per externally-accepted event. Text requests resolve
|
|
2355
|
+
* from `text`, other invokes from `invokes` (or `userInput` for
|
|
2356
|
+
* `agent.userInput`) — all by-src canned-output maps, and a missing src halts
|
|
2357
|
+
* that branch with a `needs-output` terminal rather than throwing.
|
|
2215
2358
|
*
|
|
2216
2359
|
* Combinatorics are bounded by `maxDepth` (default 8) and `maxPaths` (default
|
|
2217
2360
|
* 200, reported via `hitPathCap`).
|
|
@@ -2227,13 +2370,14 @@ async function explorePaths(machine, options = {}) {
|
|
|
2227
2370
|
}
|
|
2228
2371
|
/**
|
|
2229
2372
|
* Answers "can the machine reach `statePath`?" by exploring its branches (a
|
|
2230
|
-
* thin wrapper over {@link explorePaths}). Returns
|
|
2231
|
-
* with the event sequence that reaches it, or
|
|
2373
|
+
* thin wrapper over {@link explorePaths}). Returns
|
|
2374
|
+
* `{ reachable: true, witness }` with the event sequence that reaches it, or
|
|
2375
|
+
* `{ reachable: false }`.
|
|
2232
2376
|
*
|
|
2233
2377
|
* @example
|
|
2234
2378
|
* ```ts
|
|
2235
|
-
* const {
|
|
2236
|
-
* //
|
|
2379
|
+
* const { reachable, witness } = await canReach(refundMachine, 'denied', { input: { request: 'x', amount: 5000 } });
|
|
2380
|
+
* // reachable → true; witness → [{ type: 'NEEDS_REVIEW' }, { type: 'DENY' }]
|
|
2237
2381
|
* ```
|
|
2238
2382
|
*/
|
|
2239
2383
|
async function canReach(machine, statePath, options = {}) {
|
|
@@ -2245,9 +2389,9 @@ async function canReach(machine, statePath, options = {}) {
|
|
|
2245
2389
|
}
|
|
2246
2390
|
});
|
|
2247
2391
|
return witness !== void 0 ? {
|
|
2248
|
-
|
|
2392
|
+
reachable: true,
|
|
2249
2393
|
witness
|
|
2250
|
-
} : {
|
|
2394
|
+
} : { reachable: false };
|
|
2251
2395
|
}
|
|
2252
2396
|
//#endregion
|
|
2253
2397
|
//#region src/internal/is-record.ts
|
|
@@ -2408,7 +2552,8 @@ function matchesTrajectory(actual, expected, options = {}) {
|
|
|
2408
2552
|
* Scripted executors — a keyless, deterministic stand-in for a model host.
|
|
2409
2553
|
*
|
|
2410
2554
|
* `createScriptedExecutors` builds a full `{ generateText, streamText, decide }`
|
|
2411
|
-
* set that plays back canned answers from FIFO
|
|
2555
|
+
* set (plus a `userInput` handler) that plays back canned answers from FIFO
|
|
2556
|
+
* queues, so `runAgent` (or
|
|
2412
2557
|
* `provideExecutors`, or a bare `TextLogic.execute`) runs with no API key and no
|
|
2413
2558
|
* network. It is the fastest way to see a machine run, and the least ceremonial
|
|
2414
2559
|
* way to test one: same machine, same executor contract, scripted answers.
|
|
@@ -2484,15 +2629,27 @@ function emitScriptedChunk(result, info) {
|
|
|
2484
2629
|
* decisions: [(request) => ({ type: request.events[0]!.type })],
|
|
2485
2630
|
* });
|
|
2486
2631
|
* ```
|
|
2632
|
+
*
|
|
2633
|
+
* @example Scripted human input
|
|
2634
|
+
* ```ts
|
|
2635
|
+
* const scripted = createScriptedExecutors({ userInput: ['ship it'] });
|
|
2636
|
+
* await runAgent(machine, { executors: scripted, userInput: scripted.userInput });
|
|
2637
|
+
* ```
|
|
2487
2638
|
*/
|
|
2488
2639
|
function createScriptedExecutors(script = {}) {
|
|
2489
2640
|
const decisions = [...script.decisions ?? []];
|
|
2490
2641
|
const text = [...script.text ?? []];
|
|
2642
|
+
const userInput = [...script.userInput ?? []];
|
|
2491
2643
|
const nextText = async (request, info) => {
|
|
2492
2644
|
if (text.length === 0) throw new AgentError("scripted-executors-exhausted", `createScriptedExecutors: script ran dry on a pending text request ${describeText(request)}. Add another entry to the script's \`text\` queue.`);
|
|
2493
2645
|
return resolveScriptedTextEntry(text.shift(), request, info);
|
|
2494
2646
|
};
|
|
2495
2647
|
return {
|
|
2648
|
+
userInput: async (input) => {
|
|
2649
|
+
if (userInput.length === 0) throw new AgentError("scripted-executors-exhausted", `createScriptedExecutors: script ran dry on a pending userInput request (prompt: ${input.prompt ? `'${input.prompt}'` : "(none)"}). Add another entry to the script's \`userInput\` queue.`);
|
|
2650
|
+
const entry = userInput.shift();
|
|
2651
|
+
return typeof entry === "function" ? await entry(input) : entry;
|
|
2652
|
+
},
|
|
2496
2653
|
generateText: nextText,
|
|
2497
2654
|
streamText: async (request, info) => {
|
|
2498
2655
|
const result = await nextText(request, info);
|
|
@@ -2566,22 +2723,22 @@ async function runSeam(machine, options) {
|
|
|
2566
2723
|
const queueKeyOf = (request) => request.name !== void 0 && queues.has(request.name) ? request.name : request.model;
|
|
2567
2724
|
/**
|
|
2568
2725
|
* Consumes this request's slot in the call plan, or resolves `undefined` when
|
|
2569
|
-
* its queue is dry.
|
|
2570
|
-
*
|
|
2726
|
+
* its queue is dry. With `repeatLast`, the last entry is replayed instead of
|
|
2727
|
+
* running dry.
|
|
2571
2728
|
*/
|
|
2572
2729
|
const takeScriptedSlot = async (request, info) => {
|
|
2573
2730
|
const queue = queues.get(queueKeyOf(request));
|
|
2574
2731
|
if (!queue?.length) return;
|
|
2575
|
-
return resolveScriptedTextEntry(queue.length === 1 ? queue[0] : queue.shift(), request, info);
|
|
2732
|
+
return resolveScriptedTextEntry(options.repeatLast && queue.length === 1 ? queue[0] : queue.shift(), request, info);
|
|
2576
2733
|
};
|
|
2577
2734
|
const scriptedAnswer = async (request, info) => {
|
|
2578
2735
|
const scripted = await takeScriptedSlot(request, info);
|
|
2579
|
-
if (!scripted) throw new AgentError("seam-script-exhausted", `runSeam: no scripted answer left for request ${describeText(request)}. Add an entry to \`scripts.${queueKeyOf(request)}
|
|
2736
|
+
if (!scripted) throw new AgentError("seam-script-exhausted", `runSeam: no scripted answer left for request ${describeText(request)}. Add an entry to \`scripts.${queueKeyOf(request)}\`, or pass \`repeatLast: true\` to replay its last entry down a longer branch.`);
|
|
2580
2737
|
return scripted;
|
|
2581
2738
|
};
|
|
2582
2739
|
const route = async (request, info) => {
|
|
2583
2740
|
const callIndex = calls++;
|
|
2584
|
-
const isSeam =
|
|
2741
|
+
const isSeam = request.name === seam.request && seamMatches++ === (seam.occurrence ?? 0);
|
|
2585
2742
|
if (isSeam && candidate) {
|
|
2586
2743
|
await takeScriptedSlot(request, info);
|
|
2587
2744
|
seamReached = true;
|
|
@@ -2619,7 +2776,7 @@ async function runSeam(machine, options) {
|
|
|
2619
2776
|
result = await runAgent(machine, {
|
|
2620
2777
|
...snapshot ? { snapshot } : { input: options.input },
|
|
2621
2778
|
...event ? { event } : {},
|
|
2622
|
-
...options.
|
|
2779
|
+
...options.isIdle ? { isIdle: options.isIdle } : {},
|
|
2623
2780
|
...options.actors ? { actors: options.actors } : {},
|
|
2624
2781
|
events,
|
|
2625
2782
|
executors,
|
|
@@ -2672,4 +2829,194 @@ async function runSeam(machine, options) {
|
|
|
2672
2829
|
};
|
|
2673
2830
|
}
|
|
2674
2831
|
//#endregion
|
|
2675
|
-
|
|
2832
|
+
//#region src/durable.ts
|
|
2833
|
+
/**
|
|
2834
|
+
* The durable host runner: {@link runDurableAgent} drives an executor-bound
|
|
2835
|
+
* agent machine on xstate's `createDurable` execution (`xstate/durable`),
|
|
2836
|
+
* with the agent event log as the journal.
|
|
2837
|
+
*
|
|
2838
|
+
* Where {@link replay} + `getAgentEffects` hand a host an effect list to run
|
|
2839
|
+
* itself, `runDurableAgent` owns the whole loop on the durable runtime:
|
|
2840
|
+
* invoked actors execute live through xstate's own runtime, every EXTERNAL
|
|
2841
|
+
* event (invoke completions included) is appended to the log, and a resume
|
|
2842
|
+
* folds the log back through pure transitions — an invoke whose completion is
|
|
2843
|
+
* already journaled is never re-started, so recorded model calls are never
|
|
2844
|
+
* re-executed. Crash recovery re-runs only the work that was still in flight.
|
|
2845
|
+
*
|
|
2846
|
+
* @module
|
|
2847
|
+
*/
|
|
2848
|
+
const DONE_ACTOR_EVENT_TYPE = "xstate.done.actor";
|
|
2849
|
+
const ERROR_ACTOR_EVENT_TYPE = "xstate.error.actor";
|
|
2850
|
+
function completionActorId(event) {
|
|
2851
|
+
if (event.type !== DONE_ACTOR_EVENT_TYPE && event.type !== ERROR_ACTOR_EVENT_TYPE) return;
|
|
2852
|
+
const actorId = event.actorId;
|
|
2853
|
+
return typeof actorId === "string" ? actorId : void 0;
|
|
2854
|
+
}
|
|
2855
|
+
function createMailbox() {
|
|
2856
|
+
const queue = [];
|
|
2857
|
+
const waiters = [];
|
|
2858
|
+
return {
|
|
2859
|
+
push(event) {
|
|
2860
|
+
const waiter = waiters.shift();
|
|
2861
|
+
if (waiter) waiter(event);
|
|
2862
|
+
else queue.push(event);
|
|
2863
|
+
},
|
|
2864
|
+
take() {
|
|
2865
|
+
const next = queue.shift();
|
|
2866
|
+
if (next !== void 0) return Promise.resolve(next);
|
|
2867
|
+
return new Promise((resolve) => waiters.push(resolve));
|
|
2868
|
+
},
|
|
2869
|
+
size: () => queue.length
|
|
2870
|
+
};
|
|
2871
|
+
}
|
|
2872
|
+
/**
|
|
2873
|
+
* Runs an agent machine as a durable execution: journal in, journal out.
|
|
2874
|
+
*
|
|
2875
|
+
* A fresh call starts from `input` and appends a reserved init entry; a
|
|
2876
|
+
* resume call folds `entries` through pure transitions first — invokes whose
|
|
2877
|
+
* completions are journaled are suppressed (their recorded results replay
|
|
2878
|
+
* instead of re-executing), while work that was in flight at the crash
|
|
2879
|
+
* re-executes live. After the journal, an optional `options.event` is
|
|
2880
|
+
* delivered. The call settles:
|
|
2881
|
+
*
|
|
2882
|
+
* - `done` when the machine reaches a final state, with `output`;
|
|
2883
|
+
* - `idle` when the frontier needs an external event the host has not
|
|
2884
|
+
* supplied (no live work pending, or `isIdle` says the pending work is a
|
|
2885
|
+
* human wait). Persist `entries` and call again with them later.
|
|
2886
|
+
*
|
|
2887
|
+
* ```ts
|
|
2888
|
+
* const first = await runDurableAgent(machine, { input, executors });
|
|
2889
|
+
* // ... persist first.entries; later, in a new process:
|
|
2890
|
+
* const next = await runDurableAgent(machine, {
|
|
2891
|
+
* entries: first.entries,
|
|
2892
|
+
* event: { type: "APPROVE" },
|
|
2893
|
+
* executors,
|
|
2894
|
+
* });
|
|
2895
|
+
* ```
|
|
2896
|
+
*
|
|
2897
|
+
* @experimental Built on xstate's experimental `xstate/durable` entrypoint.
|
|
2898
|
+
*/
|
|
2899
|
+
async function runDurableAgent(machine, options = {}) {
|
|
2900
|
+
const bound = options.executors ? provideExecutors(machine, options.executors, {
|
|
2901
|
+
actors: options.actors,
|
|
2902
|
+
onChunk: options.onChunk,
|
|
2903
|
+
onTrace: options.onTrace
|
|
2904
|
+
}) : options.actors ? machine.provide({ actors: options.actors }) : machine;
|
|
2905
|
+
const machineId = machine.config.id ?? machine.id ?? "(machine)";
|
|
2906
|
+
const machineVersion = options.machineVersion ?? resolveMachineVersion(machine);
|
|
2907
|
+
const priorEntries = options.entries ?? [];
|
|
2908
|
+
if (priorEntries.length > 0) validateReplayEntries(priorEntries, {
|
|
2909
|
+
machineId,
|
|
2910
|
+
machineVersion
|
|
2911
|
+
}, "Durable journal entries");
|
|
2912
|
+
const hasInit = priorEntries[0]?.event.type === AGENT_INIT_EVENT_TYPE;
|
|
2913
|
+
const input = hasInit ? priorEntries[0].event.input : options.input;
|
|
2914
|
+
const journal = priorEntries.slice(hasInit ? 1 : 0).map((entry) => entry.event);
|
|
2915
|
+
const journaledCompletions = /* @__PURE__ */ new Map();
|
|
2916
|
+
for (const event of journal) {
|
|
2917
|
+
const actorId = completionActorId(event);
|
|
2918
|
+
if (actorId !== void 0) journaledCompletions.set(actorId, (journaledCompletions.get(actorId) ?? 0) + 1);
|
|
2919
|
+
}
|
|
2920
|
+
const mailbox = createMailbox();
|
|
2921
|
+
const rootAddress = machineId;
|
|
2922
|
+
const suppressedChildren = /* @__PURE__ */ new WeakSet();
|
|
2923
|
+
const startsSeen = /* @__PURE__ */ new Map();
|
|
2924
|
+
const liveInFlight = /* @__PURE__ */ new Set();
|
|
2925
|
+
const findChildRef = (effect) => {
|
|
2926
|
+
const raw = effect;
|
|
2927
|
+
const candidates = [raw.actor, ...Array.isArray(raw.args) ? raw.args : []];
|
|
2928
|
+
for (const candidate of candidates) {
|
|
2929
|
+
const ref = candidate;
|
|
2930
|
+
if (ref && typeof ref.sessionId === "string" && typeof ref.id === "string") return ref;
|
|
2931
|
+
}
|
|
2932
|
+
};
|
|
2933
|
+
let replaying = journal.length > 0;
|
|
2934
|
+
const execution = createDurable(bound, {
|
|
2935
|
+
sendEvent(source, target, event) {
|
|
2936
|
+
if (target.address === rootAddress) {
|
|
2937
|
+
mailbox.push(event);
|
|
2938
|
+
return;
|
|
2939
|
+
}
|
|
2940
|
+
deliverEvent(source, target, event);
|
|
2941
|
+
},
|
|
2942
|
+
runtime(_metadata, effect) {
|
|
2943
|
+
const type = effect.type;
|
|
2944
|
+
if (type === "@xstate.spawn" || type === "@xstate.start") {
|
|
2945
|
+
const child = findChildRef(effect);
|
|
2946
|
+
if (!child) return {};
|
|
2947
|
+
if (type === "@xstate.spawn") {
|
|
2948
|
+
const seen = (startsSeen.get(child.id) ?? 0) + 1;
|
|
2949
|
+
startsSeen.set(child.id, seen);
|
|
2950
|
+
if (seen <= (journaledCompletions.get(child.id) ?? 0)) suppressedChildren.add(child);
|
|
2951
|
+
else liveInFlight.add(child.id);
|
|
2952
|
+
}
|
|
2953
|
+
if (suppressedChildren.has(child)) return {
|
|
2954
|
+
spawnActor() {},
|
|
2955
|
+
startActor() {}
|
|
2956
|
+
};
|
|
2957
|
+
}
|
|
2958
|
+
return {};
|
|
2959
|
+
},
|
|
2960
|
+
executeAction(action) {
|
|
2961
|
+
if (replaying) return;
|
|
2962
|
+
action.exec?.();
|
|
2963
|
+
},
|
|
2964
|
+
waitForEvent() {
|
|
2965
|
+
return mailbox.take();
|
|
2966
|
+
}
|
|
2967
|
+
});
|
|
2968
|
+
const entries = [...priorEntries];
|
|
2969
|
+
const entryOptions = {
|
|
2970
|
+
machineVersion,
|
|
2971
|
+
verification: options.verification ?? false
|
|
2972
|
+
};
|
|
2973
|
+
const appendEntry = (event) => {
|
|
2974
|
+
const entry = createReplayEntry(machine, entries, event, entryOptions);
|
|
2975
|
+
entries.push(entry);
|
|
2976
|
+
options.onEntry?.(entry);
|
|
2977
|
+
};
|
|
2978
|
+
if (!hasInit) {
|
|
2979
|
+
const entry = initEntry(machine, input, entryOptions);
|
|
2980
|
+
entries.push(entry);
|
|
2981
|
+
options.onEntry?.(entry);
|
|
2982
|
+
}
|
|
2983
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
2984
|
+
let journalIndex = 0;
|
|
2985
|
+
let liveEventConsumed = false;
|
|
2986
|
+
let [snapshot, effects] = execution.initialTransition(input);
|
|
2987
|
+
for (;;) {
|
|
2988
|
+
const captured = await execution.executeEffects(effects);
|
|
2989
|
+
for (const rootEvent of captured) mailbox.push(rootEvent.event);
|
|
2990
|
+
const machineSnapshot = snapshot;
|
|
2991
|
+
if (machineSnapshot.status === "done") return {
|
|
2992
|
+
status: "done",
|
|
2993
|
+
output: machineSnapshot.output,
|
|
2994
|
+
snapshot,
|
|
2995
|
+
entries
|
|
2996
|
+
};
|
|
2997
|
+
if (machineSnapshot.status === "error") throw machineSnapshot.error;
|
|
2998
|
+
let event;
|
|
2999
|
+
let fromJournal = false;
|
|
3000
|
+
if (journalIndex < journal.length) {
|
|
3001
|
+
event = rebindActorSession(journal[journalIndex], machineSnapshot, sessions);
|
|
3002
|
+
journalIndex++;
|
|
3003
|
+
fromJournal = true;
|
|
3004
|
+
replaying = journalIndex < journal.length;
|
|
3005
|
+
} else if (mailbox.size() > 0) event = await mailbox.take();
|
|
3006
|
+
else if (liveInFlight.size > 0 && !(options.isIdle?.(snapshot) ?? false)) event = await mailbox.take();
|
|
3007
|
+
else if (!liveEventConsumed && options.event !== void 0) {
|
|
3008
|
+
event = options.event;
|
|
3009
|
+
liveEventConsumed = true;
|
|
3010
|
+
} else return {
|
|
3011
|
+
status: "idle",
|
|
3012
|
+
snapshot,
|
|
3013
|
+
entries
|
|
3014
|
+
};
|
|
3015
|
+
const completedId = completionActorId(event);
|
|
3016
|
+
if (completedId !== void 0) liveInFlight.delete(completedId);
|
|
3017
|
+
if (!fromJournal) appendEntry(event);
|
|
3018
|
+
[snapshot, effects] = execution.transition(snapshot, event);
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
//#endregion
|
|
3022
|
+
export { AGENT_EVENT_SCHEMA_VERSION, AGENT_INIT_EVENT_TYPE, AGENT_TRACE_SCHEMA_VERSION, AGENT_USAGE_EVENT_TYPE, AgentDecisionExhaustedError, AgentError, AgentEventLogConflictError, AgentIdleError, AgentIllegalResumeEventError, AgentLintError, AgentMaxModelCallsExceededError, AgentReplayDivergenceError, AgentReplayMachineMismatchError, AgentSnapshotVersionMismatchError, NonSerializableAgentEventError, appendMessages, assertAgentLogEntry, assertEventLogStoreConformance, assertJsonSerializable, assistantMessage, bindRequestExecutor, buildEnvelopeSchema, canReach, createAgentActor, createAgentRun, createAgentSchemas, createInMemoryEventLogStore, createReplayEntry, createScriptedExecutors, createTextLogic, diffEventLogs, executeAgentRequest, explorePaths, generateResult, getAcceptedEvents, getAgentEffects, getAgentMessages, getAgentOutputMode, getAgentSchemas, getCallUsage, getJsonSchema, getJsonSchemaSync, getMachineStructuralHash, getSnapshotNodes, getSnapshotRequests, getStateMeta, initEntry, inspectTransitions, isStandardSchema, lintAgentMachine, matchesTrajectory, messagesSchema, parseAgentEvent, parseModelRef, parseOutput, parseStructuredEnvelope, provideExecutors, renderDecisionAttempts, replay, resolveDecision, runAgent, runDurableAgent, runSeam, serializeTraceEvent, setupAgent, simulateAgent, systemMessage, toolMessage, traceTransitions, userMessage };
|