@statelyai/agent 2.0.0-alpha.15 → 2.0.0-alpha.17
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 +1 -1
- package/dist/ai-sdk.d.cts +2 -2
- package/dist/ai-sdk.d.mts +2 -2
- package/dist/ai-sdk.mjs +1 -1
- package/dist/{decision-D9Zi7Xi5.mjs → decision-C11xuud2.mjs} +35 -26
- package/dist/{decision-C0cUKvNt.cjs → decision-DnQCQPew.cjs} +46 -31
- package/dist/{event-log-store-D7pWtIhb.mjs → event-log-store-B-1fcfkT.mjs} +149 -141
- package/dist/{event-log-store-BkUNtyOF.d.mts → event-log-store-BrC9Q1xW.d.mts} +14 -12
- package/dist/{event-log-store-CVd2eyRy.d.cts → event-log-store-CQJq8_v4.d.cts} +14 -12
- package/dist/{event-log-store-CNT_7F0V.cjs → event-log-store-yquOV1TX.cjs} +148 -140
- package/dist/index.cjs +498 -520
- package/dist/index.d.cts +130 -104
- package/dist/index.d.mts +130 -104
- package/dist/index.mjs +497 -519
- 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-B_n4Qxye.d.cts → run-agent-BxjGaVpL.d.cts} +49 -109
- package/dist/{run-agent-BWzo4FLv.d.mts → run-agent-COHoCgQd.d.mts} +49 -109
- package/dist/{setup-agent-DeHRW-qX.mjs → setup-agent-CTg57Pa4.mjs} +154 -167
- package/dist/{setup-agent-CpK0ZRWV.cjs → setup-agent-D_EyJ0Ik.cjs} +159 -172
- package/dist/sqlite.cjs +1 -1
- package/dist/sqlite.d.cts +2 -2
- package/dist/sqlite.d.mts +2 -2
- package/dist/sqlite.mjs +1 -1
- package/dist/{text-logic-DZW7XWy9.d.cts → text-logic-BFX5q7fM.d.cts} +23 -2
- package/dist/{text-logic-C7anC7qX.d.mts → text-logic-DQW8_DWW.d.mts} +23 -2
- package/dist/{types-DFD28AWe.d.cts → types-DYpK3QF4.d.mts} +7 -6
- package/dist/{types-CTBhMnFu.d.mts → types-pJ5Hn8fv.d.cts} +7 -6
- package/package.json +28 -28
- package/readme.md +6 -19
- package/schemas/agent-workflow.json +9 -4
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as AgentError } from "./errors-CeSXQx0v.mjs";
|
|
2
|
-
import { _ as resolveAgentStep, a as AgentReplayDivergenceError, b as messagesSchema, c as diffEventLogs, d as
|
|
3
|
-
import { A as isUnboundPlaceholder, B as
|
|
4
|
-
import { a as
|
|
2
|
+
import { _ as resolveAgentStep, a as AgentReplayDivergenceError, b as messagesSchema, c as diffEventLogs, d as replay, f as validateReplayEntries, g as initialAgentStep, h as getInvokeEffectMetadata, i as AGENT_USAGE_EVENT_TYPE, l as getAgentEffects, m as executeAgentRequest, n as setupAgent, o as AgentReplayMachineMismatchError, p as verifyReplay, r as AGENT_INIT_EVENT_TYPE, s as createReplayEntry, t as createAgentSchemas, u as initEntry, v as transitionAgentStep, y as appendMessages } from "./setup-agent-CTg57Pa4.mjs";
|
|
3
|
+
import { A as isUnboundPlaceholder, B as getMachineStructuralHash, C as parseStructuredEnvelope, D as getMachineStaticTransitionTargets, E as executorBoundLogics, G as toolMessage, H as isStandardSchema, I as findNonSerializableContextPaths, K as userMessage, L as getAgentMessages, O as getMachineSuspensionPredicate, P as assistantMessage, R as getJsonSchema, S as parseOutput, U as persistSnapshot, 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-C11xuud2.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-B-1fcfkT.mjs";
|
|
5
5
|
import { createActor, createAsyncLogic, getNextTransitions, isMachineSnapshot } from "xstate";
|
|
6
6
|
//#region src/internal/state-request-pass.ts
|
|
7
7
|
async function runTextPhase(stateRequest, baseMessages, deps) {
|
|
@@ -41,7 +41,7 @@ async function runTextPhase(stateRequest, baseMessages, deps) {
|
|
|
41
41
|
output = await normalizeGeneratorResult(raw, id, { request });
|
|
42
42
|
const rawReasoning = raw?.reasoning;
|
|
43
43
|
const reasoning = typeof rawReasoning === "string" ? rawReasoning : void 0;
|
|
44
|
-
const usage =
|
|
44
|
+
const usage = getCallUsage(raw);
|
|
45
45
|
if (usage) deps.recordUsage?.(usage);
|
|
46
46
|
deps.onResult?.(agentRequest, {
|
|
47
47
|
output,
|
|
@@ -346,22 +346,22 @@ function isStateMachine(logic) {
|
|
|
346
346
|
* its own executor (`.withExecutor(...)`, tracked in `executorBoundLogics`)
|
|
347
347
|
* always runs itself; explicit binding shadows inheritance.
|
|
348
348
|
*/
|
|
349
|
-
function assertBindable(machine, effectiveSources,
|
|
350
|
-
assertMachineBindable(machine, effectiveSources,
|
|
349
|
+
function assertBindable(machine, effectiveSources, executors) {
|
|
350
|
+
assertMachineBindable(machine, effectiveSources, executors, {
|
|
351
351
|
isChild: false,
|
|
352
352
|
childPath: "",
|
|
353
353
|
rebindable: true,
|
|
354
354
|
visited: new Set([machine])
|
|
355
355
|
});
|
|
356
356
|
}
|
|
357
|
-
function assertMachineBindable(machine, effectiveSources,
|
|
357
|
+
function assertMachineBindable(machine, effectiveSources, executors, ctx) {
|
|
358
358
|
const invokes = [];
|
|
359
359
|
collectConfiguredInvokeSrcs(machine.config, machine.config.id ?? "(root)", invokes);
|
|
360
360
|
const where = ctx.isChild ? `child machine '${ctx.childPath}' state` : "state";
|
|
361
361
|
for (const { stateName, src } of invokes) {
|
|
362
362
|
if (typeof src !== "string") {
|
|
363
363
|
if (isStateMachine(src)) {
|
|
364
|
-
assertChildMachineBindable(src, src, stateName,
|
|
364
|
+
assertChildMachineBindable(src, src, stateName, executors, ctx);
|
|
365
365
|
continue;
|
|
366
366
|
}
|
|
367
367
|
if ((isTextLogic(src) || isDecisionLogic(src)) && !executorBoundLogics.has(src)) throw new Error(`runAgent: ${where} '${stateName}' invokes a direct-object actor logic (kind: '${src.kind}'). Direct-object invoke srcs cannot be rebound by runAgent — either call '.withExecutor(...)' on the logic before invoking it, or register it as a string-keyed actor source instead (machine.provide({ actors: { name: logic } })) and invoke it by name.`);
|
|
@@ -370,21 +370,21 @@ function assertMachineBindable(machine, effectiveSources, options, ctx) {
|
|
|
370
370
|
const logic = effectiveSources[src];
|
|
371
371
|
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
372
|
if (isStateMachine(logic)) {
|
|
373
|
-
assertChildMachineBindable(logic, src, stateName,
|
|
373
|
+
assertChildMachineBindable(logic, src, stateName, executors, ctx);
|
|
374
374
|
continue;
|
|
375
375
|
}
|
|
376
376
|
if (src === "agent.userInput") continue;
|
|
377
377
|
if (isDecisionLogic(logic)) {
|
|
378
378
|
if (executorBoundLogics.has(logic)) continue;
|
|
379
379
|
if (!ctx.rebindable) throw unrebindableChildRequestError(ctx.childPath, stateName, src, "decision");
|
|
380
|
-
if (!
|
|
380
|
+
if (!executors.decide) throw new Error(`runAgent: ${where} '${stateName}' invokes decision source '${src}' but no 'decide' executor was provided to runAgent(...).`);
|
|
381
381
|
continue;
|
|
382
382
|
}
|
|
383
383
|
if (isTextLogic(logic)) {
|
|
384
384
|
if (executorBoundLogics.has(logic)) continue;
|
|
385
385
|
if (!ctx.rebindable) throw unrebindableChildRequestError(ctx.childPath, stateName, src, logic.mode === "stream" ? "streaming text" : "text");
|
|
386
|
-
if (logic.mode === "stream" && !
|
|
387
|
-
if (logic.mode !== "stream" && !
|
|
386
|
+
if (logic.mode === "stream" && !executors.streamText) throw new Error(`runAgent: ${where} '${stateName}' invokes streaming text source '${src}' but no 'streamText' executor was provided to runAgent(...).`);
|
|
387
|
+
if (logic.mode !== "stream" && !executors.generateText) throw new Error(`runAgent: ${where} '${stateName}' invokes text source '${src}' but no 'generateText' executor was provided to runAgent(...).`);
|
|
388
388
|
continue;
|
|
389
389
|
}
|
|
390
390
|
if (isUnboundPlaceholder(logic)) throw new Error(`runAgent: ${where} '${stateName}' invokes actor source '${src}', which has no host execution. Provide it via machine.provide({ actors: { '${src}': ... } }) or runAgent(machine, { actors: { '${src}': ... } }).`);
|
|
@@ -392,12 +392,12 @@ function assertMachineBindable(machine, effectiveSources, options, ctx) {
|
|
|
392
392
|
}
|
|
393
393
|
/** Descends the bind-time walk into an invoked child state machine, guarding
|
|
394
394
|
* against a machine that (transitively) invokes itself. */
|
|
395
|
-
function assertChildMachineBindable(childMachine, childSrc, stateName,
|
|
395
|
+
function assertChildMachineBindable(childMachine, childSrc, stateName, executors, ctx) {
|
|
396
396
|
if (ctx.visited.has(childMachine)) return;
|
|
397
397
|
const childName = typeof childSrc === "string" ? childSrc : childMachine.config.id ?? "(child machine)";
|
|
398
398
|
const childPath = ctx.childPath ? `${ctx.childPath} > ${childName}` : childName;
|
|
399
399
|
const childSources = childMachine.sources.actors;
|
|
400
|
-
assertMachineBindable(childMachine, childSources,
|
|
400
|
+
assertMachineBindable(childMachine, childSources, executors, {
|
|
401
401
|
isChild: true,
|
|
402
402
|
childPath,
|
|
403
403
|
rebindable: ctx.rebindable && typeof childSrc === "string",
|
|
@@ -428,12 +428,45 @@ function unrebindableChildRequestError(childPath, stateName, requestSrc, kind) {
|
|
|
428
428
|
function declaresUsageTransition(snapshot) {
|
|
429
429
|
return getNextTransitions(snapshot).some((transition) => transition.eventType === AGENT_USAGE_EVENT_TYPE);
|
|
430
430
|
}
|
|
431
|
+
/**
|
|
432
|
+
* Builds the ONE place a trace payload is emitted: it hands the payload to the
|
|
433
|
+
* envelope-stamping trace sink and, from that same payload, invokes the sugar
|
|
434
|
+
* callbacks that are projections of it — {@link RunAgentOptions.onChunk},
|
|
435
|
+
* {@link RunAgentOptions.onResult}, {@link RunAgentOptions.onTransition}. Each
|
|
436
|
+
* keeps its historical position relative to the trace: `onResult` fires just
|
|
437
|
+
* BEFORE its `request.end`, `onChunk`/`onTransition` just AFTER their
|
|
438
|
+
* `stream.chunk`/`machine.transition`. Sugar dispatch never depends on whether
|
|
439
|
+
* a trace sink is present, and the trace sink is never called when it is
|
|
440
|
+
* absent — so an `onTrace`-less run still mints no envelope (and advances no
|
|
441
|
+
* `seq`). @internal
|
|
442
|
+
*/
|
|
443
|
+
function createTraceDispatch(sinks) {
|
|
444
|
+
return (payload, self) => {
|
|
445
|
+
switch (payload.type) {
|
|
446
|
+
case "stream.chunk":
|
|
447
|
+
sinks.onTrace?.(payload, self);
|
|
448
|
+
sinks.onChunk?.(payload.chunk, { request: payload.request });
|
|
449
|
+
return;
|
|
450
|
+
case "request.end":
|
|
451
|
+
sinks.onResult?.(payload.request, {
|
|
452
|
+
output: payload.output,
|
|
453
|
+
raw: payload.raw
|
|
454
|
+
});
|
|
455
|
+
sinks.onTrace?.(payload, self);
|
|
456
|
+
return;
|
|
457
|
+
case "machine.transition":
|
|
458
|
+
sinks.onTrace?.(payload, self);
|
|
459
|
+
sinks.onTransition?.(payload.snapshot, payload.event);
|
|
460
|
+
return;
|
|
461
|
+
default: sinks.onTrace?.(payload, self);
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
}
|
|
431
465
|
/** Reads the durable invoke id/src off the async actor's own ref (`self`). */
|
|
432
466
|
function selfIdAndSrc(self) {
|
|
433
|
-
const ref = self;
|
|
434
467
|
return {
|
|
435
|
-
id: typeof
|
|
436
|
-
src: typeof
|
|
468
|
+
id: typeof self?.id === "string" ? self.id : "",
|
|
469
|
+
src: typeof self?.src === "string" ? self.src : ""
|
|
437
470
|
};
|
|
438
471
|
}
|
|
439
472
|
/**
|
|
@@ -455,8 +488,9 @@ function invokingActorOf(self, runCtx) {
|
|
|
455
488
|
* Used by both `runAgent` and `provideExecutors` so the two paths produce
|
|
456
489
|
* identical event shapes by construction. @internal
|
|
457
490
|
*/
|
|
458
|
-
function
|
|
459
|
-
return logic.withExecutor(async ({ request, self, signal }) => {
|
|
491
|
+
function bindTextLogic(logic, runCtx) {
|
|
492
|
+
return logic.withExecutor(async ({ request, self: selfArg, signal }) => {
|
|
493
|
+
const self = selfArg;
|
|
460
494
|
const { id, src } = selfIdAndSrc(self);
|
|
461
495
|
const executor = logic.mode === "stream" ? runCtx.streamText : runCtx.generateText;
|
|
462
496
|
if (!executor) throw new Error(`runAgent: no '${logic.mode === "stream" ? "streamText" : "generateText"}' executor provided.`);
|
|
@@ -474,19 +508,18 @@ function wrapTextLogicForRunAgent(logic, runCtx) {
|
|
|
474
508
|
events: []
|
|
475
509
|
};
|
|
476
510
|
runCtx.consumeModelCall();
|
|
477
|
-
runCtx.
|
|
511
|
+
runCtx.emitTrace?.({
|
|
478
512
|
type: "request.start",
|
|
479
513
|
request: agentRequest
|
|
480
514
|
}, self);
|
|
481
515
|
try {
|
|
482
516
|
const raw = await executor(requestWithTools, {
|
|
483
517
|
onChunk: (chunk) => {
|
|
484
|
-
runCtx.
|
|
518
|
+
runCtx.emitTrace?.({
|
|
485
519
|
type: "stream.chunk",
|
|
486
520
|
request: agentRequest,
|
|
487
521
|
chunk
|
|
488
522
|
}, self);
|
|
489
|
-
runCtx.onChunk?.(chunk, { request: agentRequest });
|
|
490
523
|
},
|
|
491
524
|
signal,
|
|
492
525
|
...runCtx.runId !== void 0 ? { runId: runCtx.runId } : {},
|
|
@@ -495,17 +528,16 @@ function wrapTextLogicForRunAgent(logic, runCtx) {
|
|
|
495
528
|
const output = await normalizeGeneratorResult(raw, id, {
|
|
496
529
|
request,
|
|
497
530
|
onChunk: (chunk) => {
|
|
498
|
-
runCtx.
|
|
531
|
+
runCtx.emitTrace?.({
|
|
499
532
|
type: "stream.chunk",
|
|
500
533
|
request: agentRequest,
|
|
501
534
|
chunk
|
|
502
535
|
}, self);
|
|
503
|
-
runCtx.onChunk?.(chunk, { request: agentRequest });
|
|
504
536
|
}
|
|
505
537
|
});
|
|
506
538
|
const rawReasoning = raw?.reasoning;
|
|
507
539
|
const reasoning = typeof rawReasoning === "string" ? rawReasoning : void 0;
|
|
508
|
-
const usage =
|
|
540
|
+
const usage = getCallUsage(raw);
|
|
509
541
|
if (usage) runCtx.recordUsage?.(usage, {
|
|
510
542
|
kind: "text",
|
|
511
543
|
...id !== "" ? { id } : {},
|
|
@@ -513,11 +545,7 @@ function wrapTextLogicForRunAgent(logic, runCtx) {
|
|
|
513
545
|
model: request.model,
|
|
514
546
|
...request.name !== void 0 ? { name: request.name } : {}
|
|
515
547
|
}, self);
|
|
516
|
-
runCtx.
|
|
517
|
-
output,
|
|
518
|
-
raw
|
|
519
|
-
});
|
|
520
|
-
runCtx.onTrace?.({
|
|
548
|
+
runCtx.emitTrace?.({
|
|
521
549
|
type: "request.end",
|
|
522
550
|
request: agentRequest,
|
|
523
551
|
output,
|
|
@@ -527,7 +555,7 @@ function wrapTextLogicForRunAgent(logic, runCtx) {
|
|
|
527
555
|
}, self);
|
|
528
556
|
return { output };
|
|
529
557
|
} catch (error) {
|
|
530
|
-
runCtx.
|
|
558
|
+
runCtx.emitTrace?.({
|
|
531
559
|
type: "request.error",
|
|
532
560
|
request: agentRequest,
|
|
533
561
|
error
|
|
@@ -539,7 +567,7 @@ function wrapTextLogicForRunAgent(logic, runCtx) {
|
|
|
539
567
|
function createCountingDecide(runCtx, self) {
|
|
540
568
|
return async (attemptRequest) => {
|
|
541
569
|
runCtx.consumeModelCall();
|
|
542
|
-
runCtx.
|
|
570
|
+
runCtx.emitTrace?.({
|
|
543
571
|
type: "request.start",
|
|
544
572
|
request: attemptRequest
|
|
545
573
|
}, self);
|
|
@@ -548,7 +576,7 @@ function createCountingDecide(runCtx, self) {
|
|
|
548
576
|
...attemptRequest,
|
|
549
577
|
runId: runCtx.runId
|
|
550
578
|
} : attemptRequest);
|
|
551
|
-
const usage =
|
|
579
|
+
const usage = getCallUsage(result);
|
|
552
580
|
if (usage) {
|
|
553
581
|
const { src } = selfIdAndSrc(self);
|
|
554
582
|
runCtx.recordUsage?.(usage, {
|
|
@@ -558,11 +586,7 @@ function createCountingDecide(runCtx, self) {
|
|
|
558
586
|
model: attemptRequest.model
|
|
559
587
|
}, self);
|
|
560
588
|
}
|
|
561
|
-
runCtx.
|
|
562
|
-
output: result.event,
|
|
563
|
-
raw: result
|
|
564
|
-
});
|
|
565
|
-
runCtx.onTrace?.({
|
|
589
|
+
runCtx.emitTrace?.({
|
|
566
590
|
type: "request.end",
|
|
567
591
|
request: attemptRequest,
|
|
568
592
|
output: result.event,
|
|
@@ -571,7 +595,7 @@ function createCountingDecide(runCtx, self) {
|
|
|
571
595
|
}, self);
|
|
572
596
|
return result;
|
|
573
597
|
} catch (error) {
|
|
574
|
-
runCtx.
|
|
598
|
+
runCtx.emitTrace?.({
|
|
575
599
|
type: "request.error",
|
|
576
600
|
request: attemptRequest,
|
|
577
601
|
error
|
|
@@ -595,9 +619,10 @@ function createCountingDecide(runCtx, self) {
|
|
|
595
619
|
* send-then-complete note inside `run` for how exit-cancels-invoke interacts
|
|
596
620
|
* with `onDone`.
|
|
597
621
|
*/
|
|
598
|
-
function
|
|
599
|
-
const decisionLogic = createAsyncLogic({ run: async ({ input, signal, self }) => {
|
|
622
|
+
function bindDecisionLogic(logic, runCtx) {
|
|
623
|
+
const decisionLogic = createAsyncLogic({ run: async ({ input, signal, self: selfArg }) => {
|
|
600
624
|
if (!runCtx.decide) throw new Error("runAgent: no 'decide' executor provided.");
|
|
625
|
+
const self = selfArg;
|
|
601
626
|
const { id } = selfIdAndSrc(self);
|
|
602
627
|
const declaredEventTypes = logic.allowedEventTypes?.(input);
|
|
603
628
|
await Promise.resolve();
|
|
@@ -623,7 +648,7 @@ function createRunAgentDecisionLogic(logic, runCtx) {
|
|
|
623
648
|
kind: "statelyai.decisionLogic",
|
|
624
649
|
maxRetries: logic.maxRetries,
|
|
625
650
|
request: logic.request,
|
|
626
|
-
withExecutor: (nextExecute) =>
|
|
651
|
+
withExecutor: (nextExecute) => bindDecisionLogic(logic.withExecutor(nextExecute), runCtx)
|
|
627
652
|
});
|
|
628
653
|
}
|
|
629
654
|
/**
|
|
@@ -646,8 +671,7 @@ let nextProvideRunId = 1;
|
|
|
646
671
|
/** Walks `self._parent` from an invoked async leaf actor up to its root actor. */
|
|
647
672
|
function rootActorOf(self) {
|
|
648
673
|
let ref = self;
|
|
649
|
-
|
|
650
|
-
while (ref._parent) ref = ref._parent;
|
|
674
|
+
while (ref?._parent) ref = ref._parent;
|
|
651
675
|
return ref;
|
|
652
676
|
}
|
|
653
677
|
/** The per-root envelope state, minted on first use (runId `run_<n>`, matching runAgent). */
|
|
@@ -680,7 +704,7 @@ function stampRootTrace(root, payload) {
|
|
|
680
704
|
...payload
|
|
681
705
|
};
|
|
682
706
|
}
|
|
683
|
-
/** Adapts a public `onTrace` into the payload-level {@link
|
|
707
|
+
/** Adapts a public `onTrace` into the payload-level trace sink a {@link TraceDispatch} fans out to. */
|
|
684
708
|
function provideTraceSink(onTrace) {
|
|
685
709
|
if (!onTrace) return;
|
|
686
710
|
return (payload, self) => {
|
|
@@ -699,43 +723,48 @@ function provideTraceSink(onTrace) {
|
|
|
699
723
|
*
|
|
700
724
|
* `recordUsage` has no run-level aggregate to fold into here (there is no
|
|
701
725
|
* run), so it does one thing: deliver the reserved `@agent.usage` event, gated
|
|
702
|
-
* exactly like runAgent's — see {@link
|
|
726
|
+
* exactly like runAgent's — see {@link deliverUsageEvent}.
|
|
703
727
|
*/
|
|
704
728
|
function provideBindContext(machine, executors, options) {
|
|
729
|
+
const traceSink = provideTraceSink(options.onTrace);
|
|
730
|
+
const onChunk = options.onChunk;
|
|
705
731
|
return {
|
|
706
732
|
generateText: executors.generateText,
|
|
707
733
|
streamText: executors.streamText,
|
|
708
734
|
decide: executors.decide,
|
|
709
|
-
|
|
710
|
-
|
|
735
|
+
emitTrace: traceSink || onChunk ? createTraceDispatch({
|
|
736
|
+
onTrace: traceSink,
|
|
737
|
+
onChunk: onChunk ? (chunk) => onChunk(chunk) : void 0
|
|
738
|
+
}) : void 0,
|
|
711
739
|
consumeModelCall: () => {},
|
|
712
740
|
recordUsage: (usage, source, self) => {
|
|
713
|
-
|
|
741
|
+
deliverUsageEvent(usage, source ?? {}, () => self?._parent);
|
|
714
742
|
},
|
|
715
743
|
actorHolder: { actorRef: void 0 },
|
|
716
744
|
schemas: getRegisteredAgentExecutionOptions(machine).schemas
|
|
717
745
|
};
|
|
718
746
|
}
|
|
719
747
|
/**
|
|
720
|
-
* `
|
|
721
|
-
* bound call settles with reported usage, send the
|
|
722
|
-
*
|
|
723
|
-
*
|
|
724
|
-
* `createActor` tree
|
|
725
|
-
* root actor on this path).
|
|
748
|
+
* The single reserved-`@agent.usage` delivery seam, shared by both bind paths:
|
|
749
|
+
* after a bound call settles with reported usage, send the event to the machine
|
|
750
|
+
* actor `resolveActorRef` names — the run's root actor on the `runAgent` path,
|
|
751
|
+
* the settling request actor's `self._parent` (always the invoking machine
|
|
752
|
+
* under a live `createActor` tree) on the `provideExecutors` path.
|
|
726
753
|
*
|
|
727
|
-
*
|
|
728
|
-
*
|
|
729
|
-
*
|
|
730
|
-
*
|
|
731
|
-
*
|
|
754
|
+
* Gating is identical on both: the target snapshot must be active, must declare
|
|
755
|
+
* an `'@agent.usage'` transition EXPLICITLY (see {@link declaresUsageTransition}
|
|
756
|
+
* — a catch-all `on: { '*' }` is not an opt-in), and must be able to take the
|
|
757
|
+
* event. `onDropped` is the run path's straggler gate: it returns `true` for a
|
|
758
|
+
* call that settled after the cycle resolved, which drops the event (traced as
|
|
759
|
+
* `usage.dropped`) rather than delivering it. Uncontrolled mode has no cycle to
|
|
760
|
+
* settle, so it passes no gate and has no dropped stragglers.
|
|
732
761
|
*
|
|
733
|
-
* Delivery follows
|
|
734
|
-
*
|
|
762
|
+
* Delivery follows each path's binding boundary: only sources IT bound report
|
|
763
|
+
* here, so an invoked child machine that was not itself passed through
|
|
735
764
|
* `provideExecutors` reports nothing. @internal
|
|
736
765
|
*/
|
|
737
|
-
function
|
|
738
|
-
const actorRef =
|
|
766
|
+
function deliverUsageEvent(usage, source, resolveActorRef, onDropped) {
|
|
767
|
+
const actorRef = resolveActorRef();
|
|
739
768
|
if (!actorRef) return;
|
|
740
769
|
const snapshot = actorRef.getSnapshot();
|
|
741
770
|
if (snapshot?.status !== "active" || !declaresUsageTransition(snapshot)) return;
|
|
@@ -744,17 +773,18 @@ function deliverUsageToInvokingActor(usage, source, self) {
|
|
|
744
773
|
...source,
|
|
745
774
|
usage
|
|
746
775
|
};
|
|
776
|
+
if (onDropped?.(event)) return;
|
|
747
777
|
if (!snapshot.can(event)) return;
|
|
748
778
|
actorRef.send(event);
|
|
749
779
|
}
|
|
750
780
|
/**
|
|
751
781
|
* Host-binds one text/stream source for {@link provideExecutors} using the SAME
|
|
752
|
-
* emission helper as `runAgent` ({@link
|
|
782
|
+
* emission helper as `runAgent` ({@link bindTextLogic}), so a bound
|
|
753
783
|
* text request emits request.start/stream.chunk/request.end/request.error with
|
|
754
784
|
* identical shapes. @internal
|
|
755
785
|
*/
|
|
756
786
|
function bindTextForProvide(machine, logic, executors, options) {
|
|
757
|
-
return
|
|
787
|
+
return bindTextLogic(logic, provideBindContext(machine, executors, options));
|
|
758
788
|
}
|
|
759
789
|
/**
|
|
760
790
|
* Host-binds one `DecisionLogic`/`agent.decide` source for
|
|
@@ -763,7 +793,7 @@ function bindTextForProvide(machine, logic, executors, options) {
|
|
|
763
793
|
* request-level tracing runAgent emits, minus run-scoped counting. @internal
|
|
764
794
|
*/
|
|
765
795
|
function bindDecisionForProvide(machine, logic, executors, options) {
|
|
766
|
-
return
|
|
796
|
+
return bindDecisionLogic(logic, provideBindContext(machine, executors, options));
|
|
767
797
|
}
|
|
768
798
|
/**
|
|
769
799
|
* Validates `input` against the machine's registered input schema, returning
|
|
@@ -806,11 +836,11 @@ function rebindChildMachine(childMachine, runCtx, visited) {
|
|
|
806
836
|
const wrapped = {};
|
|
807
837
|
for (const [key, logic] of Object.entries(sources)) {
|
|
808
838
|
if (isDecisionLogic(logic)) {
|
|
809
|
-
if (!executorBoundLogics.has(logic)) wrapped[key] =
|
|
839
|
+
if (!executorBoundLogics.has(logic)) wrapped[key] = bindDecisionLogic(logic, runCtx);
|
|
810
840
|
continue;
|
|
811
841
|
}
|
|
812
842
|
if (isTextLogic(logic)) {
|
|
813
|
-
if (!executorBoundLogics.has(logic)) wrapped[key] =
|
|
843
|
+
if (!executorBoundLogics.has(logic)) wrapped[key] = bindTextLogic(logic, runCtx);
|
|
814
844
|
continue;
|
|
815
845
|
}
|
|
816
846
|
if (isStateMachine(logic)) {
|
|
@@ -902,20 +932,22 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
902
932
|
machineId,
|
|
903
933
|
version: machineVersion
|
|
904
934
|
};
|
|
905
|
-
const
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
935
|
+
const onTrace = createTraceDispatch({
|
|
936
|
+
onTrace: (payload) => {
|
|
937
|
+
options.onTrace?.({
|
|
938
|
+
schemaVersion: 1,
|
|
939
|
+
runId,
|
|
940
|
+
seq: ++traceSeq,
|
|
941
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
942
|
+
machineId,
|
|
943
|
+
machineVersion,
|
|
944
|
+
...payload
|
|
945
|
+
});
|
|
946
|
+
},
|
|
947
|
+
onChunk: options.onChunk,
|
|
948
|
+
onResult: options.onResult,
|
|
949
|
+
onTransition: options.onTransition
|
|
950
|
+
});
|
|
919
951
|
const consumeModelCall = () => {
|
|
920
952
|
if (budgetExceeded) throw new AgentMaxModelCallsExceededError();
|
|
921
953
|
if (modelCallCount + 1 > maxModelCalls) {
|
|
@@ -926,33 +958,20 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
926
958
|
};
|
|
927
959
|
const tokenTotals = {};
|
|
928
960
|
const cycleGate = { isResolved: () => false };
|
|
929
|
-
const deliverUsageEvent = (usage, source) => {
|
|
930
|
-
const actorRef = actorHolder.actorRef;
|
|
931
|
-
if (!actorRef) return;
|
|
932
|
-
const event = {
|
|
933
|
-
type: AGENT_USAGE_EVENT_TYPE,
|
|
934
|
-
...source,
|
|
935
|
-
usage
|
|
936
|
-
};
|
|
937
|
-
const snapshot = actorRef.getSnapshot();
|
|
938
|
-
if (snapshot?.status !== "active" || !declaresUsageTransition(snapshot)) return;
|
|
939
|
-
if (cycleGate.isResolved()) {
|
|
940
|
-
onTrace({
|
|
941
|
-
type: "usage.dropped",
|
|
942
|
-
event,
|
|
943
|
-
reason: "settled"
|
|
944
|
-
});
|
|
945
|
-
return;
|
|
946
|
-
}
|
|
947
|
-
if (!snapshot.can(event)) return;
|
|
948
|
-
actorRef.send(event);
|
|
949
|
-
};
|
|
950
961
|
const recordUsage = (usage, source = {}) => {
|
|
951
962
|
for (const field of AGENT_USAGE_TOKEN_FIELDS) {
|
|
952
963
|
const value = usage[field];
|
|
953
964
|
if (typeof value === "number" && Number.isFinite(value)) tokenTotals[field] = (tokenTotals[field] ?? 0) + value;
|
|
954
965
|
}
|
|
955
|
-
deliverUsageEvent(usage, source)
|
|
966
|
+
deliverUsageEvent(usage, source, () => actorHolder.actorRef, (event) => {
|
|
967
|
+
if (!cycleGate.isResolved()) return false;
|
|
968
|
+
onTrace({
|
|
969
|
+
type: "usage.dropped",
|
|
970
|
+
event,
|
|
971
|
+
reason: "settled"
|
|
972
|
+
});
|
|
973
|
+
return true;
|
|
974
|
+
});
|
|
956
975
|
};
|
|
957
976
|
const runUsage = () => ({
|
|
958
977
|
...tokenTotals,
|
|
@@ -972,20 +991,14 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
972
991
|
};
|
|
973
992
|
const provided = machine.provide({ actors: options.actors });
|
|
974
993
|
const effectiveSources = provided.sources.actors;
|
|
975
|
-
assertBindable(provided, effectiveSources, {
|
|
976
|
-
hasGenerateText: !!options.executors?.generateText,
|
|
977
|
-
hasDecide: !!options.executors?.decide,
|
|
978
|
-
hasStreamText: !!options.executors?.streamText
|
|
979
|
-
});
|
|
994
|
+
assertBindable(provided, effectiveSources, options.executors ?? {});
|
|
980
995
|
if (options.getRequests && !options.executors?.generateText && !options.executors?.decide) throw new Error("runAgent: 'getRequests' requires a 'generateText' and/or 'decide' executor — the returned requests run through them.");
|
|
981
996
|
const actorHolder = { actorRef: void 0 };
|
|
982
997
|
const runCtx = {
|
|
983
998
|
generateText: options.executors?.generateText,
|
|
984
999
|
streamText: options.executors?.streamText,
|
|
985
1000
|
decide: options.executors?.decide,
|
|
986
|
-
|
|
987
|
-
onResult: options.onResult,
|
|
988
|
-
onTrace,
|
|
1001
|
+
emitTrace: onTrace,
|
|
989
1002
|
consumeModelCall,
|
|
990
1003
|
recordUsage,
|
|
991
1004
|
actorHolder,
|
|
@@ -1006,11 +1019,11 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1006
1019
|
continue;
|
|
1007
1020
|
}
|
|
1008
1021
|
if (isDecisionLogic(logic)) {
|
|
1009
|
-
wrappedSources[key] =
|
|
1022
|
+
wrappedSources[key] = bindDecisionLogic(logic, runCtx);
|
|
1010
1023
|
continue;
|
|
1011
1024
|
}
|
|
1012
1025
|
if (isTextLogic(logic)) {
|
|
1013
|
-
if (!executorBoundLogics.has(logic)) wrappedSources[key] =
|
|
1026
|
+
if (!executorBoundLogics.has(logic)) wrappedSources[key] = bindTextLogic(logic, runCtx);
|
|
1014
1027
|
continue;
|
|
1015
1028
|
}
|
|
1016
1029
|
if (isStateMachine(logic)) {
|
|
@@ -1052,9 +1065,11 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1052
1065
|
}
|
|
1053
1066
|
const priorMessages = getAgentMessages(effectiveSnapshot);
|
|
1054
1067
|
const messages = typeof options.messages === "function" ? [...options.messages([...priorMessages])] : [...priorMessages, ...options.messages ?? []];
|
|
1055
|
-
const
|
|
1068
|
+
const stampSettledSnapshot = (snapshot) => {
|
|
1069
|
+
if (!snapshot || typeof snapshot !== "object") return;
|
|
1070
|
+
snapshot.agentMeta = agentMeta;
|
|
1056
1071
|
if (!options.getRequests && !options.messages && messages.length === 0) return;
|
|
1057
|
-
|
|
1072
|
+
snapshot.messages = [...messages];
|
|
1058
1073
|
};
|
|
1059
1074
|
if (effectiveSnapshot !== void 0 && options.event !== void 0 && (options.onIllegalResumeEvent ?? "throw") === "throw") {
|
|
1060
1075
|
const acceptedTypes = getAcceptedEvents(createActor(boundMachine, { snapshot: effectiveSnapshot }).getSnapshot(), { schemas: runCtx.schemas }).map((descriptor) => descriptor.type);
|
|
@@ -1062,21 +1077,11 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1062
1077
|
if (!acceptedTypes.includes(eventType)) throw new AgentIllegalResumeEventError(eventType, acceptedTypes);
|
|
1063
1078
|
}
|
|
1064
1079
|
const replayEvents = [...resumeEvents ?? []];
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
if (entry.machineId !== machineId || entry.machineVersion !== machineVersion) throw new AgentReplayMachineMismatchError(entry.id, entry.index, {
|
|
1071
|
-
machineId,
|
|
1072
|
-
machineVersion
|
|
1073
|
-
}, {
|
|
1074
|
-
machineId: entry.machineId,
|
|
1075
|
-
machineVersion: entry.machineVersion
|
|
1076
|
-
});
|
|
1077
|
-
if (replayEventIds.has(entry.id)) throw new Error(`runAgent events contain duplicate event id '${entry.id}'.`);
|
|
1078
|
-
replayEventIds.add(entry.id);
|
|
1079
|
-
}
|
|
1080
|
+
validateReplayEntries(replayEvents, {
|
|
1081
|
+
machineId,
|
|
1082
|
+
machineVersion
|
|
1083
|
+
}, "runAgent events");
|
|
1084
|
+
const replayEventIds = new Set(replayEvents.map((entry) => entry.id));
|
|
1080
1085
|
const hasCompleteReplayHistory = replayEvents[0]?.event.type === "@agent.init" || effectiveSnapshot === void 0 && replayEvents.length === 0;
|
|
1081
1086
|
const appendReplayEvent = (event) => {
|
|
1082
1087
|
const entry = createReplayEntry(machine, replayEvents, event, {
|
|
@@ -1095,247 +1100,239 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1095
1100
|
replayEvents.push(entry);
|
|
1096
1101
|
options.onEvent?.(entry);
|
|
1097
1102
|
}
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
usage: runUsage()
|
|
1114
|
-
};
|
|
1115
|
-
if (idleTimer !== void 0) clearTimeout(idleTimer);
|
|
1116
|
-
stampAgentMeta(result.snapshot);
|
|
1117
|
-
stampMessages(result.snapshot);
|
|
1118
|
-
if ("persistedSnapshot" in result) {
|
|
1119
|
-
stampAgentMeta(result.persistedSnapshot);
|
|
1120
|
-
stampMessages(result.persistedSnapshot);
|
|
1121
|
-
}
|
|
1122
|
-
onTrace({
|
|
1123
|
-
type: "run.end",
|
|
1124
|
-
...outcome
|
|
1125
|
-
});
|
|
1126
|
-
if (lifecycle.oneShot || result.status !== "idle") {
|
|
1127
|
-
finalized = true;
|
|
1128
|
-
if (options.signal) options.signal.removeEventListener("abort", onAbort);
|
|
1129
|
-
actor.stop();
|
|
1130
|
-
}
|
|
1131
|
-
lastResult = result;
|
|
1132
|
-
for (const resolve of waiters.splice(0)) resolve(result);
|
|
1103
|
+
let settled = false;
|
|
1104
|
+
let finalized = false;
|
|
1105
|
+
cycleGate.isResolved = () => settled;
|
|
1106
|
+
let lastResult;
|
|
1107
|
+
const waiters = [];
|
|
1108
|
+
let idleTimer;
|
|
1109
|
+
let actor;
|
|
1110
|
+
let deliveringResumeEvent = options.event !== void 0;
|
|
1111
|
+
const settle = (outcome) => {
|
|
1112
|
+
if (settled) return;
|
|
1113
|
+
settled = true;
|
|
1114
|
+
const result = {
|
|
1115
|
+
...outcome,
|
|
1116
|
+
events: [...replayEvents],
|
|
1117
|
+
usage: runUsage()
|
|
1133
1118
|
};
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1119
|
+
if (idleTimer !== void 0) clearTimeout(idleTimer);
|
|
1120
|
+
stampSettledSnapshot(result.snapshot);
|
|
1121
|
+
if ("persistedSnapshot" in result) stampSettledSnapshot(result.persistedSnapshot);
|
|
1122
|
+
onTrace({
|
|
1123
|
+
type: "run.end",
|
|
1124
|
+
...outcome
|
|
1125
|
+
});
|
|
1126
|
+
if (lifecycle.oneShot || result.status !== "idle") {
|
|
1127
|
+
finalized = true;
|
|
1128
|
+
if (options.signal) options.signal.removeEventListener("abort", onAbort);
|
|
1129
|
+
actor.stop();
|
|
1130
|
+
}
|
|
1131
|
+
lastResult = result;
|
|
1132
|
+
for (const resolve of waiters.splice(0)) resolve(result);
|
|
1133
|
+
};
|
|
1134
|
+
const onAbort = () => {
|
|
1135
|
+
settle({
|
|
1136
|
+
status: "error",
|
|
1137
|
+
cause: "aborted",
|
|
1138
|
+
error: options.signal?.reason ?? /* @__PURE__ */ new Error("Aborted"),
|
|
1139
|
+
snapshot: actor.getSnapshot()
|
|
1140
|
+
});
|
|
1141
|
+
};
|
|
1142
|
+
const settleIdle = (current) => {
|
|
1143
|
+
warnNonSerializableContext(current);
|
|
1144
|
+
const pendingUserInputs = userInputIsPlaceholder ? collectPendingUserInputs(current) : [];
|
|
1145
|
+
settle({
|
|
1146
|
+
status: "idle",
|
|
1147
|
+
snapshot: current,
|
|
1148
|
+
persistedSnapshot: actor.getPersistedSnapshot(),
|
|
1149
|
+
...pendingUserInputs.length > 0 ? { pendingUserInputs } : {}
|
|
1150
|
+
});
|
|
1151
|
+
};
|
|
1152
|
+
let interpreting = false;
|
|
1153
|
+
let interpretSeq = 0;
|
|
1154
|
+
const appendToLog = (...items) => {
|
|
1155
|
+
messages.push(...items);
|
|
1156
|
+
if (options.onMessage) {
|
|
1157
|
+
const info = {
|
|
1158
|
+
runId,
|
|
1159
|
+
machineId,
|
|
1160
|
+
machineVersion
|
|
1161
|
+
};
|
|
1162
|
+
for (const item of items) options.onMessage(item, info);
|
|
1163
|
+
}
|
|
1164
|
+
};
|
|
1165
|
+
const runErrorCause = (error) => budgetExceeded ? "max-model-calls" : wrapsDecisionExhausted(error) ? "decision-exhausted" : "machine";
|
|
1166
|
+
const settleInterpretError = (error) => {
|
|
1167
|
+
settle({
|
|
1168
|
+
status: "error",
|
|
1169
|
+
cause: runErrorCause(error),
|
|
1170
|
+
error,
|
|
1171
|
+
snapshot: actor.getSnapshot()
|
|
1172
|
+
});
|
|
1173
|
+
};
|
|
1174
|
+
const passDeps = {
|
|
1175
|
+
getSnapshot: () => actor.getSnapshot(),
|
|
1176
|
+
send: (event) => actor.send(event),
|
|
1177
|
+
isSettled: () => settled,
|
|
1178
|
+
messages,
|
|
1179
|
+
appendToLog,
|
|
1180
|
+
generateText: runCtx.generateText,
|
|
1181
|
+
decide: runCtx.decide ? createCountingDecide(runCtx, void 0) : void 0,
|
|
1182
|
+
consumeModelCall,
|
|
1183
|
+
recordUsage,
|
|
1184
|
+
nextRequestId: () => `interpret_${++interpretSeq}`,
|
|
1185
|
+
onTrace,
|
|
1186
|
+
schemas: runCtx.schemas,
|
|
1187
|
+
signal: options.signal
|
|
1188
|
+
};
|
|
1189
|
+
const maybeInterpret = (snapshot) => {
|
|
1190
|
+
if (!options.getRequests || settled) return false;
|
|
1191
|
+
if (interpreting) return true;
|
|
1192
|
+
let requested;
|
|
1193
|
+
try {
|
|
1194
|
+
requested = options.getRequests(snapshot, { messages });
|
|
1195
|
+
} catch (error) {
|
|
1196
|
+
settleInterpretError(error);
|
|
1211
1197
|
return true;
|
|
1212
|
-
}
|
|
1213
|
-
const
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1198
|
+
}
|
|
1199
|
+
const requests = (Array.isArray(requested) ? requested : requested ? [requested] : []).filter((stateRequest) => Boolean(stateRequest));
|
|
1200
|
+
if (requests.length === 0) return false;
|
|
1201
|
+
interpreting = true;
|
|
1202
|
+
runStateRequestPass(requests, passDeps).then(({ sentAny }) => {
|
|
1203
|
+
if (settled || sentAny) return;
|
|
1204
|
+
const current = actor.getSnapshot();
|
|
1205
|
+
if (isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) settleIdle(current);
|
|
1206
|
+
}).catch((error) => settleInterpretError(error)).finally(() => {
|
|
1207
|
+
interpreting = false;
|
|
1208
|
+
if (!settled) scheduleIdleCheck();
|
|
1209
|
+
});
|
|
1210
|
+
return true;
|
|
1211
|
+
};
|
|
1212
|
+
const scheduleIdleCheck = () => {
|
|
1213
|
+
if (idleTimer !== void 0) clearTimeout(idleTimer);
|
|
1214
|
+
idleTimer = setTimeout(() => {
|
|
1215
|
+
idleTimer = void 0;
|
|
1216
|
+
if (settled) return;
|
|
1217
|
+
const current = actor.getSnapshot();
|
|
1218
|
+
if (isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) {
|
|
1219
|
+
if (!maybeInterpret(current)) {
|
|
1220
|
+
if (!declaredSuspensionPredicate && current.status === "active" && !warnedHeuristicIdle && process.env.NODE_ENV !== "production") {
|
|
1221
|
+
warnedHeuristicIdle = true;
|
|
1222
|
+
console.warn("[@statelyai/agent] runAgent settled idle via the timing heuristic (no suspension predicate declared). This is best-effort; for deterministic idle detection, declare setupAgent({ isSuspended }) or pass runAgent(machine, { isSuspended }), e.g. (s) => s.hasTag('waiting').");
|
|
1226
1223
|
}
|
|
1224
|
+
settleIdle(current);
|
|
1227
1225
|
}
|
|
1228
|
-
}, 0);
|
|
1229
|
-
};
|
|
1230
|
-
actor = createActor(boundMachine, {
|
|
1231
|
-
input: resolvedInput,
|
|
1232
|
-
snapshot: effectiveSnapshot,
|
|
1233
|
-
inspect: (event) => {
|
|
1234
|
-
if (typeof options.inspect === "function") options.inspect(event);
|
|
1235
|
-
else options.inspect?.next?.(event);
|
|
1236
|
-
if (event.type !== "@xstate.transition" || event.actorRef !== actor.ref) return;
|
|
1237
|
-
if (settled) {
|
|
1238
|
-
if (finalized) return;
|
|
1239
|
-
settled = false;
|
|
1240
|
-
lastResult = void 0;
|
|
1241
|
-
}
|
|
1242
|
-
const snapshot = event.snapshot;
|
|
1243
|
-
let eventId;
|
|
1244
|
-
if (event.event.type !== "@xstate.init" && (event.sourceRef !== event.actorRef || event.event.type === "xstate.timer")) eventId = appendReplayEvent(event.event).id;
|
|
1245
|
-
else if (event.event.type === "@xstate.init") eventId = replayEvents[0]?.event.type === "@agent.init" ? replayEvents[0].id : void 0;
|
|
1246
|
-
onTrace({
|
|
1247
|
-
type: "machine.transition",
|
|
1248
|
-
snapshot,
|
|
1249
|
-
event: event.event,
|
|
1250
|
-
...eventId !== void 0 ? { eventId } : {}
|
|
1251
|
-
});
|
|
1252
|
-
options.onTransition?.(snapshot, event.event);
|
|
1253
|
-
if (snapshot.status === "done") {
|
|
1254
|
-
settle({
|
|
1255
|
-
status: "done",
|
|
1256
|
-
output: snapshot.output,
|
|
1257
|
-
snapshot
|
|
1258
|
-
});
|
|
1259
|
-
return;
|
|
1260
|
-
}
|
|
1261
|
-
if (snapshot.status === "error") {
|
|
1262
|
-
settle({
|
|
1263
|
-
status: "error",
|
|
1264
|
-
cause: runErrorCause(snapshot.error),
|
|
1265
|
-
error: snapshot.error,
|
|
1266
|
-
snapshot
|
|
1267
|
-
});
|
|
1268
|
-
return;
|
|
1269
|
-
}
|
|
1270
|
-
if (snapshot.status === "stopped") {
|
|
1271
|
-
settle({
|
|
1272
|
-
status: "error",
|
|
1273
|
-
cause: "stopped",
|
|
1274
|
-
error: /* @__PURE__ */ new Error("Actor stopped externally."),
|
|
1275
|
-
snapshot
|
|
1276
|
-
});
|
|
1277
|
-
return;
|
|
1278
|
-
}
|
|
1279
|
-
if (!deliveringResumeEvent && isSuspended(snapshot) && isIdleSnapshot(snapshot, { ignoreUserInputChildren: userInputIsPlaceholder })) {
|
|
1280
|
-
queueMicrotask(() => {
|
|
1281
|
-
if (settled) return;
|
|
1282
|
-
const current = actor.getSnapshot();
|
|
1283
|
-
if (isSuspended(current) && isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) {
|
|
1284
|
-
if (!maybeInterpret(current)) settleIdle(current);
|
|
1285
|
-
} else scheduleIdleCheck();
|
|
1286
|
-
});
|
|
1287
|
-
return;
|
|
1288
|
-
}
|
|
1289
|
-
scheduleIdleCheck();
|
|
1290
1226
|
}
|
|
1291
|
-
});
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1227
|
+
}, 0);
|
|
1228
|
+
};
|
|
1229
|
+
actor = createActor(boundMachine, {
|
|
1230
|
+
input: resolvedInput,
|
|
1231
|
+
snapshot: effectiveSnapshot,
|
|
1232
|
+
inspect: (event) => {
|
|
1233
|
+
if (typeof options.inspect === "function") options.inspect(event);
|
|
1234
|
+
else options.inspect?.next?.(event);
|
|
1235
|
+
if (event.type !== "@xstate.transition" || event.actorRef !== actor.ref) return;
|
|
1236
|
+
if (settled) {
|
|
1237
|
+
if (finalized) return;
|
|
1238
|
+
settled = false;
|
|
1239
|
+
lastResult = void 0;
|
|
1240
|
+
}
|
|
1241
|
+
const snapshot = event.snapshot;
|
|
1242
|
+
let eventId;
|
|
1243
|
+
if (event.event.type !== "@xstate.init" && (event.sourceRef !== event.actorRef || event.event.type === "xstate.timer")) eventId = appendReplayEvent(event.event).id;
|
|
1244
|
+
else if (event.event.type === "@xstate.init") eventId = replayEvents[0]?.event.type === "@agent.init" ? replayEvents[0].id : void 0;
|
|
1295
1245
|
onTrace({
|
|
1296
|
-
type: "
|
|
1297
|
-
|
|
1246
|
+
type: "machine.transition",
|
|
1247
|
+
snapshot,
|
|
1248
|
+
event: event.event,
|
|
1249
|
+
...eventId !== void 0 ? { eventId } : {}
|
|
1298
1250
|
});
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
usage: runUsage,
|
|
1307
|
-
settled: () => settled && lastResult !== void 0 ? Promise.resolve(lastResult) : new Promise((resolve) => {
|
|
1308
|
-
waiters.push(resolve);
|
|
1309
|
-
}),
|
|
1310
|
-
stop: () => {
|
|
1311
|
-
actor.stop();
|
|
1251
|
+
if (snapshot.status === "done") {
|
|
1252
|
+
settle({
|
|
1253
|
+
status: "done",
|
|
1254
|
+
output: snapshot.output,
|
|
1255
|
+
snapshot
|
|
1256
|
+
});
|
|
1257
|
+
return;
|
|
1312
1258
|
}
|
|
1313
|
-
|
|
1314
|
-
if (options.signal) {
|
|
1315
|
-
if (options.signal.aborted) {
|
|
1259
|
+
if (snapshot.status === "error") {
|
|
1316
1260
|
settle({
|
|
1317
1261
|
status: "error",
|
|
1318
|
-
cause:
|
|
1319
|
-
error:
|
|
1320
|
-
snapshot
|
|
1262
|
+
cause: runErrorCause(snapshot.error),
|
|
1263
|
+
error: snapshot.error,
|
|
1264
|
+
snapshot
|
|
1265
|
+
});
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
if (snapshot.status === "stopped") {
|
|
1269
|
+
settle({
|
|
1270
|
+
status: "error",
|
|
1271
|
+
cause: "stopped",
|
|
1272
|
+
error: /* @__PURE__ */ new Error("Actor stopped externally."),
|
|
1273
|
+
snapshot
|
|
1274
|
+
});
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
if (!deliveringResumeEvent && isSuspended(snapshot) && isIdleSnapshot(snapshot, { ignoreUserInputChildren: userInputIsPlaceholder })) {
|
|
1278
|
+
queueMicrotask(() => {
|
|
1279
|
+
if (settled) return;
|
|
1280
|
+
const current = actor.getSnapshot();
|
|
1281
|
+
if (isSuspended(current) && isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) {
|
|
1282
|
+
if (!maybeInterpret(current)) settleIdle(current);
|
|
1283
|
+
} else scheduleIdleCheck();
|
|
1321
1284
|
});
|
|
1322
|
-
return
|
|
1285
|
+
return;
|
|
1323
1286
|
}
|
|
1324
|
-
|
|
1287
|
+
scheduleIdleCheck();
|
|
1325
1288
|
}
|
|
1289
|
+
});
|
|
1290
|
+
actorHolder.actorRef = actor;
|
|
1291
|
+
actor.subscribe({ error: () => {} });
|
|
1292
|
+
actor.on("*", (event) => {
|
|
1326
1293
|
onTrace({
|
|
1327
|
-
type: "
|
|
1328
|
-
|
|
1329
|
-
...effectiveSnapshot !== void 0 ? { snapshot: effectiveSnapshot } : {},
|
|
1330
|
-
...options.event !== void 0 ? { event: options.event } : {}
|
|
1294
|
+
type: "emit",
|
|
1295
|
+
event
|
|
1331
1296
|
});
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1297
|
+
});
|
|
1298
|
+
for (const [type, handler] of Object.entries(options.on ?? {})) if (typeof handler === "function") actor.on(type, handler);
|
|
1299
|
+
const sessionApi = {
|
|
1300
|
+
actor,
|
|
1301
|
+
get events() {
|
|
1302
|
+
return replayEvents;
|
|
1303
|
+
},
|
|
1304
|
+
usage: runUsage,
|
|
1305
|
+
settled: () => settled && lastResult !== void 0 ? Promise.resolve(lastResult) : new Promise((resolve) => {
|
|
1306
|
+
waiters.push(resolve);
|
|
1307
|
+
}),
|
|
1308
|
+
stop: () => {
|
|
1309
|
+
actor.stop();
|
|
1310
|
+
}
|
|
1311
|
+
};
|
|
1312
|
+
if (options.signal) {
|
|
1313
|
+
if (options.signal.aborted) {
|
|
1314
|
+
settle({
|
|
1315
|
+
status: "error",
|
|
1316
|
+
cause: "aborted",
|
|
1317
|
+
error: options.signal.reason ?? /* @__PURE__ */ new Error("Aborted"),
|
|
1318
|
+
snapshot: actor.getSnapshot()
|
|
1319
|
+
});
|
|
1320
|
+
return sessionApi;
|
|
1336
1321
|
}
|
|
1337
|
-
|
|
1338
|
-
}
|
|
1322
|
+
options.signal.addEventListener("abort", onAbort);
|
|
1323
|
+
}
|
|
1324
|
+
onTrace({
|
|
1325
|
+
type: "run.start",
|
|
1326
|
+
...resolvedInput !== void 0 ? { input: resolvedInput } : {},
|
|
1327
|
+
...effectiveSnapshot !== void 0 ? { snapshot: effectiveSnapshot } : {},
|
|
1328
|
+
...options.event !== void 0 ? { event: options.event } : {}
|
|
1329
|
+
});
|
|
1330
|
+
actor.start();
|
|
1331
|
+
if (options.event) {
|
|
1332
|
+
deliveringResumeEvent = false;
|
|
1333
|
+
actor.send(options.event);
|
|
1334
|
+
}
|
|
1335
|
+
return sessionApi;
|
|
1339
1336
|
}
|
|
1340
1337
|
/**
|
|
1341
1338
|
* Runs an agent machine to a **final state**, for run-to-done flows where an
|
|
@@ -1491,14 +1488,17 @@ function createAgentRun(machine, options) {
|
|
|
1491
1488
|
});
|
|
1492
1489
|
else buffer.push(event);
|
|
1493
1490
|
};
|
|
1494
|
-
const
|
|
1495
|
-
if (closed) return;
|
|
1496
|
-
closed = true;
|
|
1491
|
+
const drain = () => {
|
|
1497
1492
|
while (resolvers.length > 0) resolvers.shift()({
|
|
1498
1493
|
value: void 0,
|
|
1499
1494
|
done: true
|
|
1500
1495
|
});
|
|
1501
1496
|
};
|
|
1497
|
+
const close = () => {
|
|
1498
|
+
if (closed) return;
|
|
1499
|
+
closed = true;
|
|
1500
|
+
drain();
|
|
1501
|
+
};
|
|
1502
1502
|
const userOnTrace = options.onTrace;
|
|
1503
1503
|
const result = runAgent(machine, {
|
|
1504
1504
|
...options,
|
|
@@ -1527,10 +1527,7 @@ function createAgentRun(machine, options) {
|
|
|
1527
1527
|
return() {
|
|
1528
1528
|
stopped = true;
|
|
1529
1529
|
buffer.length = 0;
|
|
1530
|
-
|
|
1531
|
-
value: void 0,
|
|
1532
|
-
done: true
|
|
1533
|
-
});
|
|
1530
|
+
drain();
|
|
1534
1531
|
return Promise.resolve({
|
|
1535
1532
|
value: void 0,
|
|
1536
1533
|
done: true
|
|
@@ -1578,7 +1575,7 @@ function createAgentRun(machine, options) {
|
|
|
1578
1575
|
* Throws at bind time if a source needs an executor kind that `executors` does
|
|
1579
1576
|
* not provide.
|
|
1580
1577
|
*
|
|
1581
|
-
*
|
|
1578
|
+
* `provideExecutors` does not descend into invoked child state machines: a string-keyed child
|
|
1582
1579
|
* machine source is left untouched, so a child with its own agent invokes needs
|
|
1583
1580
|
* its own `provideExecutors(...)` (or `runAgent`, which does rebind children).
|
|
1584
1581
|
*/
|
|
@@ -1587,32 +1584,36 @@ function provideExecutors(machine, executors, options = {}) {
|
|
|
1587
1584
|
onChunk: options.onChunk,
|
|
1588
1585
|
onTrace: options.onTrace
|
|
1589
1586
|
};
|
|
1590
|
-
const
|
|
1587
|
+
const withActors = (target, actors) => target.provide({ actors });
|
|
1588
|
+
const provided = options.actors ? withActors(machine, options.actors) : machine;
|
|
1591
1589
|
const effectiveSources = provided.sources.actors;
|
|
1592
1590
|
const wrappedSources = {};
|
|
1593
1591
|
const invokedSrcs = getConfiguredInvokeSrcs(provided);
|
|
1594
1592
|
for (const [key, logic] of Object.entries(effectiveSources)) {
|
|
1595
1593
|
if (key === "agent.userInput") continue;
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1594
|
+
let binding;
|
|
1595
|
+
if (isDecisionLogic(logic)) binding = {
|
|
1596
|
+
executorKey: "decide",
|
|
1597
|
+
kind: "decision",
|
|
1598
|
+
bind: () => bindDecisionForProvide(provided, logic, executors, bindOptions)
|
|
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
|
+
};
|
|
1604
1607
|
}
|
|
1605
|
-
if (
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
continue;
|
|
1610
|
-
}
|
|
1611
|
-
wrappedSources[key] = bindTextForProvide(provided, logic, executors, bindOptions);
|
|
1608
|
+
if (!binding) continue;
|
|
1609
|
+
if (executorBoundLogics.has(logic)) continue;
|
|
1610
|
+
if (!executors[binding.executorKey]) {
|
|
1611
|
+
if (invokedSrcs.has(key)) throw missingExecutorError(key, binding.kind, binding.executorKey);
|
|
1612
1612
|
continue;
|
|
1613
1613
|
}
|
|
1614
|
+
wrappedSources[key] = binding.bind();
|
|
1614
1615
|
}
|
|
1615
|
-
return provided
|
|
1616
|
+
return withActors(provided, wrappedSources);
|
|
1616
1617
|
}
|
|
1617
1618
|
function missingExecutorError(src, kind, executor) {
|
|
1618
1619
|
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.`);
|
|
@@ -1632,10 +1633,8 @@ function buildStateIndex(rootConfig) {
|
|
|
1632
1633
|
const hasChildren = !!config.states && Object.keys(config.states).length > 0;
|
|
1633
1634
|
index.set(path, {
|
|
1634
1635
|
path,
|
|
1635
|
-
name,
|
|
1636
1636
|
config,
|
|
1637
1637
|
parentPath,
|
|
1638
|
-
type: config.type,
|
|
1639
1638
|
isFinal: config.type === "final",
|
|
1640
1639
|
isParallel: config.type === "parallel",
|
|
1641
1640
|
isCompound: hasChildren && config.type !== "parallel",
|
|
@@ -1753,36 +1752,20 @@ function ancestorChain(node, index) {
|
|
|
1753
1752
|
function hasNonEmptyOn(config) {
|
|
1754
1753
|
return !!config.on && Object.keys(config.on).length > 0;
|
|
1755
1754
|
}
|
|
1756
|
-
function
|
|
1757
|
-
if (typeof src === "string")
|
|
1758
|
-
|
|
1759
|
-
return isDecisionLogic(actors[src]) ? "decision" : void 0;
|
|
1760
|
-
}
|
|
1761
|
-
return isDecisionLogic(src) ? "decision" : void 0;
|
|
1755
|
+
function isDecisionInvoke(src, actors) {
|
|
1756
|
+
if (typeof src === "string") return src === DECIDE_SRC || isDecisionLogic(actors[src]);
|
|
1757
|
+
return isDecisionLogic(src);
|
|
1762
1758
|
}
|
|
1763
1759
|
function isAgentLogicNeedingBinding(src) {
|
|
1764
1760
|
return (isTextLogic(src) || isDecisionLogic(src)) && !executorBoundLogics.has(src);
|
|
1765
1761
|
}
|
|
1766
|
-
function
|
|
1767
|
-
if (!schema) return
|
|
1768
|
-
try {
|
|
1769
|
-
return getJsonSchemaSync(schema) !== void 0;
|
|
1770
|
-
} catch {
|
|
1771
|
-
return false;
|
|
1772
|
-
}
|
|
1773
|
-
}
|
|
1774
|
-
function isDeclaredOutputSchema(schema) {
|
|
1775
|
-
if (!schema) return false;
|
|
1776
|
-
let json;
|
|
1762
|
+
function readJsonSchema(schema) {
|
|
1763
|
+
if (!schema) return;
|
|
1777
1764
|
try {
|
|
1778
|
-
|
|
1765
|
+
return getJsonSchemaSync(schema);
|
|
1779
1766
|
} catch {
|
|
1780
|
-
return
|
|
1767
|
+
return;
|
|
1781
1768
|
}
|
|
1782
|
-
if (!json) return false;
|
|
1783
|
-
const properties = json.properties;
|
|
1784
|
-
const required = json.required;
|
|
1785
|
-
return json.type === "object" && !!properties && Object.keys(properties).length > 0 || Array.isArray(required) && required.length > 0;
|
|
1786
1769
|
}
|
|
1787
1770
|
function checkUnreachableStates(ctx) {
|
|
1788
1771
|
const out = [];
|
|
@@ -1797,8 +1780,7 @@ function checkUnreachableStates(ctx) {
|
|
|
1797
1780
|
function checkDecideWithoutEvents(ctx) {
|
|
1798
1781
|
const out = [];
|
|
1799
1782
|
for (const node of ctx.index.values()) for (const invoke of node.invokes) {
|
|
1800
|
-
|
|
1801
|
-
if (!kind) continue;
|
|
1783
|
+
if (!isDecisionInvoke(invoke.src, ctx.actors)) continue;
|
|
1802
1784
|
const selfHandles = hasNonEmptyOn(node.config);
|
|
1803
1785
|
const ancestorHandles = ancestorChain(node, ctx.index).some((ancestor) => hasNonEmptyOn(ancestor.config));
|
|
1804
1786
|
const invokeObserves = invoke.onDone !== void 0;
|
|
@@ -1808,7 +1790,7 @@ function checkDecideWithoutEvents(ctx) {
|
|
|
1808
1790
|
code: "decide-without-events",
|
|
1809
1791
|
severity: "error",
|
|
1810
1792
|
path: node.path,
|
|
1811
|
-
message: `State '${node.path}' invokes
|
|
1793
|
+
message: `State '${node.path}' invokes decision source '${srcName}', but neither it nor any ancestor handles any event (no 'on:'), so the decision's chosen event can never be delivered. Add an 'on:' handler for the candidate events.`
|
|
1812
1794
|
});
|
|
1813
1795
|
}
|
|
1814
1796
|
return out;
|
|
@@ -1816,7 +1798,7 @@ function checkDecideWithoutEvents(ctx) {
|
|
|
1816
1798
|
function checkUnserializableContext(ctx) {
|
|
1817
1799
|
const contextSchema = ctx.schemas?.context;
|
|
1818
1800
|
if (!contextSchema) return [];
|
|
1819
|
-
if (
|
|
1801
|
+
if (readJsonSchema(contextSchema)) return [];
|
|
1820
1802
|
return [{
|
|
1821
1803
|
code: "unserializable-context",
|
|
1822
1804
|
severity: "warning",
|
|
@@ -1840,7 +1822,10 @@ function checkDirectObjectSrc(ctx) {
|
|
|
1840
1822
|
return out;
|
|
1841
1823
|
}
|
|
1842
1824
|
function checkFinalWithoutOutput(ctx) {
|
|
1843
|
-
|
|
1825
|
+
const outputJson = readJsonSchema(ctx.schemas?.output);
|
|
1826
|
+
const properties = outputJson?.properties;
|
|
1827
|
+
const required = outputJson?.required;
|
|
1828
|
+
if (!(outputJson?.type === "object" && !!properties && Object.keys(properties).length > 0 || Array.isArray(required) && required.length > 0)) return [];
|
|
1844
1829
|
if (ctx.config.output !== void 0) return [];
|
|
1845
1830
|
const out = [];
|
|
1846
1831
|
for (const node of ctx.index.values()) {
|
|
@@ -1934,7 +1919,6 @@ function lintAgentMachine(machine, options = {}) {
|
|
|
1934
1919
|
const reachable = computeReachable(config, index, getMachineStaticTransitionTargets(machine));
|
|
1935
1920
|
const registered = getRegisteredAgentExecutionOptions(machine);
|
|
1936
1921
|
const ctx = {
|
|
1937
|
-
machine,
|
|
1938
1922
|
config,
|
|
1939
1923
|
index,
|
|
1940
1924
|
reachable,
|
|
@@ -2017,7 +2001,7 @@ function takeFromQueue(channel, src) {
|
|
|
2017
2001
|
async function simulateAgent(machine, options) {
|
|
2018
2002
|
const maxSteps = options.maxSteps ?? 100;
|
|
2019
2003
|
const script = {
|
|
2020
|
-
text:
|
|
2004
|
+
text: mapValues(options.script.text ?? {}, (arr) => [...arr]),
|
|
2021
2005
|
decisions: mapValues(options.script.decisions ?? {}, (arr) => [...arr]),
|
|
2022
2006
|
invokes: mapValues(options.script.invokes ?? {}, (arr) => [...arr])
|
|
2023
2007
|
};
|
|
@@ -2089,6 +2073,7 @@ function scriptDryError(kind, src, id, request) {
|
|
|
2089
2073
|
const events = request?.kind === "decision" ? ` Candidate events: ${request.events.map((e) => e.type).join(", ") || "(none)"}.` : "";
|
|
2090
2074
|
return /* @__PURE__ */ new Error(`simulateAgent: script ran dry on a pending ${kind} request for src '${src}' (id '${id}'). Add a '${kind}' entry for '${src}' to the script.${events}`);
|
|
2091
2075
|
}
|
|
2076
|
+
const MAX_ADVANCE_STEPS = 1e3;
|
|
2092
2077
|
async function explore(machine, options, stopWhen) {
|
|
2093
2078
|
const maxDepth = options.maxDepth ?? 8;
|
|
2094
2079
|
const maxPaths = options.maxPaths ?? 200;
|
|
@@ -2113,7 +2098,7 @@ async function explore(machine, options, stopWhen) {
|
|
|
2113
2098
|
if (stopWhen?.(initial.snapshot)) witness = [];
|
|
2114
2099
|
const advance = (step) => {
|
|
2115
2100
|
let current = step;
|
|
2116
|
-
for (let i = 0; i <
|
|
2101
|
+
for (let i = 0; i < MAX_ADVANCE_STEPS; i++) {
|
|
2117
2102
|
if (current.done) return { step: current };
|
|
2118
2103
|
const request = current.requests[0];
|
|
2119
2104
|
if (request && request.kind === "text") {
|
|
@@ -2265,19 +2250,22 @@ async function canReach(machine, statePath, options = {}) {
|
|
|
2265
2250
|
} : { canReach: false };
|
|
2266
2251
|
}
|
|
2267
2252
|
//#endregion
|
|
2268
|
-
//#region src/
|
|
2269
|
-
|
|
2253
|
+
//#region src/internal/is-record.ts
|
|
2254
|
+
/** True for a plain object-like value (arrays excluded). @internal */
|
|
2255
|
+
function isRecord(value) {
|
|
2270
2256
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2271
2257
|
}
|
|
2258
|
+
//#endregion
|
|
2259
|
+
//#region src/trajectory.ts
|
|
2272
2260
|
/**
|
|
2273
2261
|
* Classifies one item. Event-shaped things (log entries, events) become events;
|
|
2274
2262
|
* everything else is a state value. A snapshot is unwrapped to its `value`.
|
|
2275
2263
|
* @internal
|
|
2276
2264
|
*/
|
|
2277
2265
|
function normalize(item) {
|
|
2278
|
-
if (isRecord
|
|
2266
|
+
if (isRecord(item)) {
|
|
2279
2267
|
const nested = item["event"];
|
|
2280
|
-
if (isRecord
|
|
2268
|
+
if (isRecord(nested) && typeof nested["type"] === "string") return {
|
|
2281
2269
|
kind: "event",
|
|
2282
2270
|
event: nested
|
|
2283
2271
|
};
|
|
@@ -2298,7 +2286,7 @@ function normalize(item) {
|
|
|
2298
2286
|
/** Every leaf of a state value as a dot path (`{ review: 'editing' }` → `review.editing`). @internal */
|
|
2299
2287
|
function statePaths(value) {
|
|
2300
2288
|
if (typeof value === "string") return [value];
|
|
2301
|
-
if (!isRecord
|
|
2289
|
+
if (!isRecord(value)) return [];
|
|
2302
2290
|
return Object.entries(value).flatMap(([key, child]) => statePaths(child).map((path) => `${key}.${path}`));
|
|
2303
2291
|
}
|
|
2304
2292
|
/**
|
|
@@ -2311,12 +2299,13 @@ function stateMatchesPath(path, value) {
|
|
|
2311
2299
|
function deepEqual(a, b) {
|
|
2312
2300
|
if (Object.is(a, b)) return true;
|
|
2313
2301
|
if (Array.isArray(a) || Array.isArray(b)) return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((item, index) => deepEqual(item, b[index]));
|
|
2314
|
-
if (!isRecord
|
|
2302
|
+
if (!isRecord(a) || !isRecord(b)) return false;
|
|
2315
2303
|
const keys = Object.keys(a);
|
|
2316
2304
|
return keys.length === Object.keys(b).length && keys.every((key) => deepEqual(a[key], b[key]));
|
|
2317
2305
|
}
|
|
2318
2306
|
/**
|
|
2319
|
-
*
|
|
2307
|
+
* Normalizes ONE expected item into a predicate over actual items, so the
|
|
2308
|
+
* expectation is classified once instead of once per candidate:
|
|
2320
2309
|
*
|
|
2321
2310
|
* - A string expectation matches an event's `type`, or a state value it names.
|
|
2322
2311
|
* - An event expectation matches an event with the same `type` whose other
|
|
@@ -2324,13 +2313,22 @@ function deepEqual(a, b) {
|
|
|
2324
2313
|
* - A state-value expectation matches structurally.
|
|
2325
2314
|
* @internal
|
|
2326
2315
|
*/
|
|
2327
|
-
function
|
|
2328
|
-
|
|
2329
|
-
|
|
2316
|
+
function matcherFor(expected) {
|
|
2317
|
+
if (typeof expected === "string") return (actual) => {
|
|
2318
|
+
const actualItem = normalize(actual);
|
|
2319
|
+
return actualItem.kind === "event" ? actualItem.event.type === expected : stateMatchesPath(expected, actualItem.value);
|
|
2320
|
+
};
|
|
2330
2321
|
const expectedItem = normalize(expected);
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2322
|
+
switch (expectedItem.kind) {
|
|
2323
|
+
case "event": return (actual) => {
|
|
2324
|
+
const actualItem = normalize(actual);
|
|
2325
|
+
return actualItem.kind === "event" && Object.keys(expectedItem.event).every((key) => deepEqual(expectedItem.event[key], actualItem.event[key]));
|
|
2326
|
+
};
|
|
2327
|
+
case "state": return (actual) => {
|
|
2328
|
+
const actualItem = normalize(actual);
|
|
2329
|
+
return actualItem.kind === "state" && deepEqual(expectedItem.value, actualItem.value);
|
|
2330
|
+
};
|
|
2331
|
+
}
|
|
2334
2332
|
}
|
|
2335
2333
|
/**
|
|
2336
2334
|
* Matches a run's trajectory against an expected one.
|
|
@@ -2367,59 +2365,41 @@ function matchesItem(expected, actual) {
|
|
|
2367
2365
|
*/
|
|
2368
2366
|
function matchesTrajectory(actual, expected, options = {}) {
|
|
2369
2367
|
const expectedCount = expected.length;
|
|
2370
|
-
|
|
2371
|
-
let matchedCount = 0;
|
|
2372
|
-
let firstMiss;
|
|
2373
|
-
for (let index = 0; index < expectedCount; index++) {
|
|
2374
|
-
if (index < actual.length && matchesItem(expected[index], actual[index])) {
|
|
2375
|
-
matchedCount++;
|
|
2376
|
-
continue;
|
|
2377
|
-
}
|
|
2378
|
-
firstMiss = {
|
|
2379
|
-
index,
|
|
2380
|
-
expected: expected[index],
|
|
2381
|
-
searchedFrom: index
|
|
2382
|
-
};
|
|
2383
|
-
break;
|
|
2384
|
-
}
|
|
2385
|
-
const matched = !firstMiss && actual.length === expectedCount;
|
|
2386
|
-
const denominator = Math.max(expectedCount, actual.length);
|
|
2387
|
-
return {
|
|
2388
|
-
matched,
|
|
2389
|
-
matchedCount,
|
|
2390
|
-
expectedCount,
|
|
2391
|
-
score: matched ? 1 : denominator === 0 ? 1 : matchedCount / denominator,
|
|
2392
|
-
...firstMiss ? { firstMiss } : {}
|
|
2393
|
-
};
|
|
2394
|
-
}
|
|
2368
|
+
const exact = options.exact === true;
|
|
2395
2369
|
let cursor = 0;
|
|
2396
2370
|
let matchedCount = 0;
|
|
2371
|
+
let firstMiss;
|
|
2397
2372
|
for (let index = 0; index < expectedCount; index++) {
|
|
2398
2373
|
const want = expected[index];
|
|
2374
|
+
const matches = matcherFor(want);
|
|
2375
|
+
const searchedFrom = exact ? index : cursor;
|
|
2399
2376
|
let found = -1;
|
|
2400
|
-
for (let at =
|
|
2401
|
-
|
|
2402
|
-
|
|
2377
|
+
for (let at = searchedFrom; at < actual.length; at++) {
|
|
2378
|
+
if (matches(actual[at])) {
|
|
2379
|
+
found = at;
|
|
2380
|
+
break;
|
|
2381
|
+
}
|
|
2382
|
+
if (exact) break;
|
|
2403
2383
|
}
|
|
2404
|
-
if (found === -1)
|
|
2405
|
-
|
|
2406
|
-
matchedCount,
|
|
2407
|
-
expectedCount,
|
|
2408
|
-
score: matchedCount / expectedCount,
|
|
2409
|
-
firstMiss: {
|
|
2384
|
+
if (found === -1) {
|
|
2385
|
+
firstMiss = {
|
|
2410
2386
|
index,
|
|
2411
2387
|
expected: want,
|
|
2412
|
-
searchedFrom
|
|
2413
|
-
}
|
|
2414
|
-
|
|
2388
|
+
searchedFrom
|
|
2389
|
+
};
|
|
2390
|
+
break;
|
|
2391
|
+
}
|
|
2415
2392
|
matchedCount++;
|
|
2416
2393
|
cursor = found + 1;
|
|
2417
2394
|
}
|
|
2395
|
+
const matched = !firstMiss && (!exact || actual.length === expectedCount);
|
|
2396
|
+
const denominator = exact ? Math.max(expectedCount, actual.length) : expectedCount;
|
|
2418
2397
|
return {
|
|
2419
|
-
matched
|
|
2398
|
+
matched,
|
|
2420
2399
|
matchedCount,
|
|
2421
2400
|
expectedCount,
|
|
2422
|
-
score: 1
|
|
2401
|
+
score: matched ? 1 : denominator === 0 ? 1 : matchedCount / denominator,
|
|
2402
|
+
...firstMiss ? { firstMiss } : {}
|
|
2423
2403
|
};
|
|
2424
2404
|
}
|
|
2425
2405
|
//#endregion
|
|
@@ -2432,17 +2412,9 @@ function matchesTrajectory(actual, expected, options = {}) {
|
|
|
2432
2412
|
* `provideExecutors`, or a bare `TextLogic.execute`) runs with no API key and no
|
|
2433
2413
|
* network. It is the fastest way to see a machine run, and the least ceremonial
|
|
2434
2414
|
* way to test one: same machine, same executor contract, scripted answers.
|
|
2415
|
+
*
|
|
2416
|
+
* @module
|
|
2435
2417
|
*/
|
|
2436
|
-
/** Thrown when a scripted queue runs dry on a pending request. */
|
|
2437
|
-
var ScriptedExecutorsError = class extends AgentError {
|
|
2438
|
-
constructor(message) {
|
|
2439
|
-
super("scripted-executors-exhausted", message);
|
|
2440
|
-
this.name = "ScriptedExecutorsError";
|
|
2441
|
-
}
|
|
2442
|
-
};
|
|
2443
|
-
function isRecord$1(value) {
|
|
2444
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2445
|
-
}
|
|
2446
2418
|
/** The only own keys an executor-result envelope carries. @internal */
|
|
2447
2419
|
const TEXT_ENVELOPE_KEYS = new Set([
|
|
2448
2420
|
"output",
|
|
@@ -2467,7 +2439,22 @@ function isTextEnvelope(value) {
|
|
|
2467
2439
|
*/
|
|
2468
2440
|
async function resolveScriptedTextEntry(entry, request, info) {
|
|
2469
2441
|
const value = typeof entry === "function" ? await entry(request, info) : entry;
|
|
2470
|
-
return isRecord
|
|
2442
|
+
return isRecord(value) && isTextEnvelope(value) ? value : { output: value };
|
|
2443
|
+
}
|
|
2444
|
+
/**
|
|
2445
|
+
* Names a pending text request in an error message. Shared with `runSeam`.
|
|
2446
|
+
* @internal
|
|
2447
|
+
*/
|
|
2448
|
+
function describeText(request) {
|
|
2449
|
+
return request.name ? `'${request.name}' (model '${request.model}')` : `(model '${request.model}')`;
|
|
2450
|
+
}
|
|
2451
|
+
/**
|
|
2452
|
+
* Stream semantics with no model: the whole text lands as one chunk. Shared
|
|
2453
|
+
* with `runSeam`, whose scripted answers stream the same way. @internal
|
|
2454
|
+
*/
|
|
2455
|
+
function emitScriptedChunk(result, info) {
|
|
2456
|
+
const output = isRecord(result) ? result["output"] : void 0;
|
|
2457
|
+
if (typeof output === "string") info?.onChunk?.(output);
|
|
2471
2458
|
}
|
|
2472
2459
|
/**
|
|
2473
2460
|
* Keyless executors that replay a script instead of calling a model. Every
|
|
@@ -2502,45 +2489,26 @@ function createScriptedExecutors(script = {}) {
|
|
|
2502
2489
|
const decisions = [...script.decisions ?? []];
|
|
2503
2490
|
const text = [...script.text ?? []];
|
|
2504
2491
|
const nextText = async (request, info) => {
|
|
2505
|
-
if (text.length === 0) throw new
|
|
2492
|
+
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.`);
|
|
2506
2493
|
return resolveScriptedTextEntry(text.shift(), request, info);
|
|
2507
2494
|
};
|
|
2508
2495
|
return {
|
|
2509
2496
|
generateText: nextText,
|
|
2510
2497
|
streamText: async (request, info) => {
|
|
2511
2498
|
const result = await nextText(request, info);
|
|
2512
|
-
|
|
2499
|
+
emitScriptedChunk(result, info);
|
|
2513
2500
|
return result;
|
|
2514
2501
|
},
|
|
2515
2502
|
decide: async (request) => {
|
|
2516
|
-
if (decisions.length === 0) throw new
|
|
2503
|
+
if (decisions.length === 0) throw new AgentError("scripted-executors-exhausted", `createScriptedExecutors: script ran dry on a pending decision request (id '${request.id}'). Add another entry to the script's \`decisions\` queue. Candidate events: ${request.events.map((event) => event.type).join(", ") || "(none)"}.`);
|
|
2517
2504
|
const entry = decisions.shift();
|
|
2518
2505
|
const value = typeof entry === "function" ? await entry(request) : entry;
|
|
2519
|
-
return typeof value
|
|
2506
|
+
return isRecord(value) && typeof value["type"] !== "string" && "event" in value ? value : { event: value };
|
|
2520
2507
|
}
|
|
2521
2508
|
};
|
|
2522
2509
|
}
|
|
2523
|
-
function describeText(request) {
|
|
2524
|
-
return request.name ? `'${request.name}'` : `(model '${request.model}')`;
|
|
2525
|
-
}
|
|
2526
2510
|
//#endregion
|
|
2527
2511
|
//#region src/seam.ts
|
|
2528
|
-
/** Thrown when the call plan runs dry on a request that is not the live seam. */
|
|
2529
|
-
var SeamScriptError = class extends AgentError {
|
|
2530
|
-
constructor(message) {
|
|
2531
|
-
super("seam-script-exhausted", message);
|
|
2532
|
-
this.name = "SeamScriptError";
|
|
2533
|
-
}
|
|
2534
|
-
};
|
|
2535
|
-
function seamKeyOf(seam) {
|
|
2536
|
-
return seam.request ?? seam.model;
|
|
2537
|
-
}
|
|
2538
|
-
function describeRequest(request) {
|
|
2539
|
-
return request.name ? `'${request.name}' (model '${request.model}')` : `(model '${request.model}')`;
|
|
2540
|
-
}
|
|
2541
|
-
function isRecord(value) {
|
|
2542
|
-
return typeof value === "object" && value !== null;
|
|
2543
|
-
}
|
|
2544
2512
|
/**
|
|
2545
2513
|
* The seam's own answer, for scoring. Our `{ output }` envelope is read
|
|
2546
2514
|
* directly; a raw AI SDK `generateText` result contributes its `text`. A raw
|
|
@@ -2595,40 +2563,50 @@ async function runSeam(machine, options) {
|
|
|
2595
2563
|
let seamStateAt = 0;
|
|
2596
2564
|
let seamEventAt = 0;
|
|
2597
2565
|
let liveEvents = 0;
|
|
2598
|
-
const
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2566
|
+
const queueKeyOf = (request) => request.name !== void 0 && queues.has(request.name) ? request.name : request.model;
|
|
2567
|
+
/**
|
|
2568
|
+
* Consumes this request's slot in the call plan, or resolves `undefined` when
|
|
2569
|
+
* its queue is dry. The LAST entry repeats: a live seam that branches further
|
|
2570
|
+
* still finds an answer instead of running dry.
|
|
2571
|
+
*/
|
|
2572
|
+
const takeScriptedSlot = async (request, info) => {
|
|
2573
|
+
const queue = queues.get(queueKeyOf(request));
|
|
2574
|
+
if (!queue?.length) return;
|
|
2605
2575
|
return resolveScriptedTextEntry(queue.length === 1 ? queue[0] : queue.shift(), request, info);
|
|
2606
2576
|
};
|
|
2577
|
+
const scriptedAnswer = async (request, info) => {
|
|
2578
|
+
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)}\` — its last entry repeats, so one extra answer covers a longer branch.`);
|
|
2580
|
+
return scripted;
|
|
2581
|
+
};
|
|
2607
2582
|
const route = async (request, info) => {
|
|
2608
2583
|
const callIndex = calls++;
|
|
2609
2584
|
const isSeam = (seam.request !== void 0 ? request.name === seam.request : request.model === seam.model) && seamMatches++ === (seam.occurrence ?? 0);
|
|
2610
|
-
|
|
2585
|
+
if (isSeam && candidate) {
|
|
2586
|
+
await takeScriptedSlot(request, info);
|
|
2587
|
+
seamReached = true;
|
|
2588
|
+
callsBeforeSeam = callIndex;
|
|
2589
|
+
seamStateAt = statePath.length;
|
|
2590
|
+
seamEventAt = liveEvents;
|
|
2591
|
+
const result = await candidate(request, info);
|
|
2592
|
+
seamOutput = await seamOutputOf(result, request);
|
|
2593
|
+
return result;
|
|
2594
|
+
}
|
|
2595
|
+
const scripted = await scriptedAnswer(request, info);
|
|
2611
2596
|
if (!isSeam) return scripted;
|
|
2612
2597
|
seamReached = true;
|
|
2613
2598
|
callsBeforeSeam = callIndex;
|
|
2614
2599
|
seamStateAt = statePath.length;
|
|
2615
2600
|
seamEventAt = liveEvents;
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
seamOutput = scripted.output;
|
|
2619
|
-
return scripted;
|
|
2620
|
-
}
|
|
2621
|
-
const result = await candidate(request, info);
|
|
2622
|
-
seamOutput = await seamOutputOf(result, request);
|
|
2623
|
-
return result;
|
|
2601
|
+
seamOutput = scripted.output;
|
|
2602
|
+
return scripted;
|
|
2624
2603
|
};
|
|
2625
2604
|
const executors = {
|
|
2626
2605
|
...options.executors,
|
|
2627
2606
|
generateText: route,
|
|
2628
2607
|
streamText: async (request, info) => {
|
|
2629
2608
|
const result = await route(request, info);
|
|
2630
|
-
|
|
2631
|
-
if (typeof output === "string") info?.onChunk?.(output);
|
|
2609
|
+
emitScriptedChunk(result, info);
|
|
2632
2610
|
return result;
|
|
2633
2611
|
}
|
|
2634
2612
|
};
|