@statelyai/agent 2.0.0-alpha.14 → 2.0.0-alpha.16
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-C0cUKvNt.cjs → decision-Ba8qrT8r.cjs} +42 -28
- package/dist/{decision-D9Zi7Xi5.mjs → decision-Bt2HYYRo.mjs} +31 -23
- 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 +520 -521
- package/dist/index.d.cts +128 -106
- package/dist/index.d.mts +128 -106
- package/dist/index.mjs +519 -520
- 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-BlqKwHIF.d.cts → run-agent-BxjGaVpL.d.cts} +72 -111
- package/dist/{run-agent-2MnlQTkB.d.mts → run-agent-COHoCgQd.d.mts} +72 -111
- package/dist/{setup-agent-CpK0ZRWV.cjs → setup-agent-BLU77gqr.cjs} +141 -172
- package/dist/{setup-agent-DeHRW-qX.mjs → setup-agent-SbiiSbAU.mjs} +136 -167
- 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-Jkilp1Ie.d.cts → text-logic-BFX5q7fM.d.cts} +26 -3
- package/dist/{text-logic-C5kbjaDz.d.mts → text-logic-DQW8_DWW.d.mts} +26 -3
- package/dist/{types-rMe7x6NR.d.cts → types-DYpK3QF4.d.mts} +35 -7
- package/dist/{types-9Bqg5rZB.d.mts → types-pJ5Hn8fv.d.cts} +35 -7
- package/package.json +28 -28
- package/readme.md +6 -19
- package/schemas/agent-workflow.json +1 -4
package/dist/index.cjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_errors = require("./errors-DUBBzRLP.cjs");
|
|
3
|
-
const require_setup_agent = require("./setup-agent-
|
|
4
|
-
const require_decision = require("./decision-
|
|
5
|
-
const require_event_log_store = require("./event-log-store-
|
|
3
|
+
const require_setup_agent = require("./setup-agent-BLU77gqr.cjs");
|
|
4
|
+
const require_decision = require("./decision-Ba8qrT8r.cjs");
|
|
5
|
+
const require_event_log_store = require("./event-log-store-yquOV1TX.cjs");
|
|
6
6
|
let xstate = require("xstate");
|
|
7
7
|
//#region src/internal/state-request-pass.ts
|
|
8
8
|
async function runTextPhase(stateRequest, baseMessages, deps) {
|
|
@@ -42,7 +42,7 @@ async function runTextPhase(stateRequest, baseMessages, deps) {
|
|
|
42
42
|
output = await require_decision.normalizeGeneratorResult(raw, id, { request });
|
|
43
43
|
const rawReasoning = raw?.reasoning;
|
|
44
44
|
const reasoning = typeof rawReasoning === "string" ? rawReasoning : void 0;
|
|
45
|
-
const usage = require_decision.
|
|
45
|
+
const usage = require_decision.getCallUsage(raw);
|
|
46
46
|
if (usage) deps.recordUsage?.(usage);
|
|
47
47
|
deps.onResult?.(agentRequest, {
|
|
48
48
|
output,
|
|
@@ -347,22 +347,22 @@ function isStateMachine(logic) {
|
|
|
347
347
|
* its own executor (`.withExecutor(...)`, tracked in `executorBoundLogics`)
|
|
348
348
|
* always runs itself; explicit binding shadows inheritance.
|
|
349
349
|
*/
|
|
350
|
-
function assertBindable(machine, effectiveSources,
|
|
351
|
-
assertMachineBindable(machine, effectiveSources,
|
|
350
|
+
function assertBindable(machine, effectiveSources, executors) {
|
|
351
|
+
assertMachineBindable(machine, effectiveSources, executors, {
|
|
352
352
|
isChild: false,
|
|
353
353
|
childPath: "",
|
|
354
354
|
rebindable: true,
|
|
355
355
|
visited: new Set([machine])
|
|
356
356
|
});
|
|
357
357
|
}
|
|
358
|
-
function assertMachineBindable(machine, effectiveSources,
|
|
358
|
+
function assertMachineBindable(machine, effectiveSources, executors, ctx) {
|
|
359
359
|
const invokes = [];
|
|
360
360
|
collectConfiguredInvokeSrcs(machine.config, machine.config.id ?? "(root)", invokes);
|
|
361
361
|
const where = ctx.isChild ? `child machine '${ctx.childPath}' state` : "state";
|
|
362
362
|
for (const { stateName, src } of invokes) {
|
|
363
363
|
if (typeof src !== "string") {
|
|
364
364
|
if (isStateMachine(src)) {
|
|
365
|
-
assertChildMachineBindable(src, src, stateName,
|
|
365
|
+
assertChildMachineBindable(src, src, stateName, executors, ctx);
|
|
366
366
|
continue;
|
|
367
367
|
}
|
|
368
368
|
if ((require_decision.isTextLogic(src) || require_decision.isDecisionLogic(src)) && !require_decision.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.`);
|
|
@@ -371,21 +371,21 @@ function assertMachineBindable(machine, effectiveSources, options, ctx) {
|
|
|
371
371
|
const logic = effectiveSources[src];
|
|
372
372
|
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}': ... } }).`);
|
|
373
373
|
if (isStateMachine(logic)) {
|
|
374
|
-
assertChildMachineBindable(logic, src, stateName,
|
|
374
|
+
assertChildMachineBindable(logic, src, stateName, executors, ctx);
|
|
375
375
|
continue;
|
|
376
376
|
}
|
|
377
377
|
if (src === "agent.userInput") continue;
|
|
378
378
|
if (require_decision.isDecisionLogic(logic)) {
|
|
379
379
|
if (require_decision.executorBoundLogics.has(logic)) continue;
|
|
380
380
|
if (!ctx.rebindable) throw unrebindableChildRequestError(ctx.childPath, stateName, src, "decision");
|
|
381
|
-
if (!
|
|
381
|
+
if (!executors.decide) throw new Error(`runAgent: ${where} '${stateName}' invokes decision source '${src}' but no 'decide' executor was provided to runAgent(...).`);
|
|
382
382
|
continue;
|
|
383
383
|
}
|
|
384
384
|
if (require_decision.isTextLogic(logic)) {
|
|
385
385
|
if (require_decision.executorBoundLogics.has(logic)) continue;
|
|
386
386
|
if (!ctx.rebindable) throw unrebindableChildRequestError(ctx.childPath, stateName, src, logic.mode === "stream" ? "streaming text" : "text");
|
|
387
|
-
if (logic.mode === "stream" && !
|
|
388
|
-
if (logic.mode !== "stream" && !
|
|
387
|
+
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(...).`);
|
|
388
|
+
if (logic.mode !== "stream" && !executors.generateText) throw new Error(`runAgent: ${where} '${stateName}' invokes text source '${src}' but no 'generateText' executor was provided to runAgent(...).`);
|
|
389
389
|
continue;
|
|
390
390
|
}
|
|
391
391
|
if (require_decision.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}': ... } }).`);
|
|
@@ -393,12 +393,12 @@ function assertMachineBindable(machine, effectiveSources, options, ctx) {
|
|
|
393
393
|
}
|
|
394
394
|
/** Descends the bind-time walk into an invoked child state machine, guarding
|
|
395
395
|
* against a machine that (transitively) invokes itself. */
|
|
396
|
-
function assertChildMachineBindable(childMachine, childSrc, stateName,
|
|
396
|
+
function assertChildMachineBindable(childMachine, childSrc, stateName, executors, ctx) {
|
|
397
397
|
if (ctx.visited.has(childMachine)) return;
|
|
398
398
|
const childName = typeof childSrc === "string" ? childSrc : childMachine.config.id ?? "(child machine)";
|
|
399
399
|
const childPath = ctx.childPath ? `${ctx.childPath} > ${childName}` : childName;
|
|
400
400
|
const childSources = childMachine.sources.actors;
|
|
401
|
-
assertMachineBindable(childMachine, childSources,
|
|
401
|
+
assertMachineBindable(childMachine, childSources, executors, {
|
|
402
402
|
isChild: true,
|
|
403
403
|
childPath,
|
|
404
404
|
rebindable: ctx.rebindable && typeof childSrc === "string",
|
|
@@ -429,12 +429,45 @@ function unrebindableChildRequestError(childPath, stateName, requestSrc, kind) {
|
|
|
429
429
|
function declaresUsageTransition(snapshot) {
|
|
430
430
|
return (0, xstate.getNextTransitions)(snapshot).some((transition) => transition.eventType === require_setup_agent.AGENT_USAGE_EVENT_TYPE);
|
|
431
431
|
}
|
|
432
|
+
/**
|
|
433
|
+
* Builds the ONE place a trace payload is emitted: it hands the payload to the
|
|
434
|
+
* envelope-stamping trace sink and, from that same payload, invokes the sugar
|
|
435
|
+
* callbacks that are projections of it — {@link RunAgentOptions.onChunk},
|
|
436
|
+
* {@link RunAgentOptions.onResult}, {@link RunAgentOptions.onTransition}. Each
|
|
437
|
+
* keeps its historical position relative to the trace: `onResult` fires just
|
|
438
|
+
* BEFORE its `request.end`, `onChunk`/`onTransition` just AFTER their
|
|
439
|
+
* `stream.chunk`/`machine.transition`. Sugar dispatch never depends on whether
|
|
440
|
+
* a trace sink is present, and the trace sink is never called when it is
|
|
441
|
+
* absent — so an `onTrace`-less run still mints no envelope (and advances no
|
|
442
|
+
* `seq`). @internal
|
|
443
|
+
*/
|
|
444
|
+
function createTraceDispatch(sinks) {
|
|
445
|
+
return (payload, self) => {
|
|
446
|
+
switch (payload.type) {
|
|
447
|
+
case "stream.chunk":
|
|
448
|
+
sinks.onTrace?.(payload, self);
|
|
449
|
+
sinks.onChunk?.(payload.chunk, { request: payload.request });
|
|
450
|
+
return;
|
|
451
|
+
case "request.end":
|
|
452
|
+
sinks.onResult?.(payload.request, {
|
|
453
|
+
output: payload.output,
|
|
454
|
+
raw: payload.raw
|
|
455
|
+
});
|
|
456
|
+
sinks.onTrace?.(payload, self);
|
|
457
|
+
return;
|
|
458
|
+
case "machine.transition":
|
|
459
|
+
sinks.onTrace?.(payload, self);
|
|
460
|
+
sinks.onTransition?.(payload.snapshot, payload.event);
|
|
461
|
+
return;
|
|
462
|
+
default: sinks.onTrace?.(payload, self);
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
}
|
|
432
466
|
/** Reads the durable invoke id/src off the async actor's own ref (`self`). */
|
|
433
467
|
function selfIdAndSrc(self) {
|
|
434
|
-
const ref = self;
|
|
435
468
|
return {
|
|
436
|
-
id: typeof
|
|
437
|
-
src: typeof
|
|
469
|
+
id: typeof self?.id === "string" ? self.id : "",
|
|
470
|
+
src: typeof self?.src === "string" ? self.src : ""
|
|
438
471
|
};
|
|
439
472
|
}
|
|
440
473
|
/**
|
|
@@ -456,8 +489,9 @@ function invokingActorOf(self, runCtx) {
|
|
|
456
489
|
* Used by both `runAgent` and `provideExecutors` so the two paths produce
|
|
457
490
|
* identical event shapes by construction. @internal
|
|
458
491
|
*/
|
|
459
|
-
function
|
|
460
|
-
return logic.withExecutor(async ({ request, self, signal }) => {
|
|
492
|
+
function bindTextLogic(logic, runCtx) {
|
|
493
|
+
return logic.withExecutor(async ({ request, self: selfArg, signal }) => {
|
|
494
|
+
const self = selfArg;
|
|
461
495
|
const { id, src } = selfIdAndSrc(self);
|
|
462
496
|
const executor = logic.mode === "stream" ? runCtx.streamText : runCtx.generateText;
|
|
463
497
|
if (!executor) throw new Error(`runAgent: no '${logic.mode === "stream" ? "streamText" : "generateText"}' executor provided.`);
|
|
@@ -475,19 +509,18 @@ function wrapTextLogicForRunAgent(logic, runCtx) {
|
|
|
475
509
|
events: []
|
|
476
510
|
};
|
|
477
511
|
runCtx.consumeModelCall();
|
|
478
|
-
runCtx.
|
|
512
|
+
runCtx.emitTrace?.({
|
|
479
513
|
type: "request.start",
|
|
480
514
|
request: agentRequest
|
|
481
515
|
}, self);
|
|
482
516
|
try {
|
|
483
517
|
const raw = await executor(requestWithTools, {
|
|
484
518
|
onChunk: (chunk) => {
|
|
485
|
-
runCtx.
|
|
519
|
+
runCtx.emitTrace?.({
|
|
486
520
|
type: "stream.chunk",
|
|
487
521
|
request: agentRequest,
|
|
488
522
|
chunk
|
|
489
523
|
}, self);
|
|
490
|
-
runCtx.onChunk?.(chunk, { request: agentRequest });
|
|
491
524
|
},
|
|
492
525
|
signal,
|
|
493
526
|
...runCtx.runId !== void 0 ? { runId: runCtx.runId } : {},
|
|
@@ -496,17 +529,16 @@ function wrapTextLogicForRunAgent(logic, runCtx) {
|
|
|
496
529
|
const output = await require_decision.normalizeGeneratorResult(raw, id, {
|
|
497
530
|
request,
|
|
498
531
|
onChunk: (chunk) => {
|
|
499
|
-
runCtx.
|
|
532
|
+
runCtx.emitTrace?.({
|
|
500
533
|
type: "stream.chunk",
|
|
501
534
|
request: agentRequest,
|
|
502
535
|
chunk
|
|
503
536
|
}, self);
|
|
504
|
-
runCtx.onChunk?.(chunk, { request: agentRequest });
|
|
505
537
|
}
|
|
506
538
|
});
|
|
507
539
|
const rawReasoning = raw?.reasoning;
|
|
508
540
|
const reasoning = typeof rawReasoning === "string" ? rawReasoning : void 0;
|
|
509
|
-
const usage = require_decision.
|
|
541
|
+
const usage = require_decision.getCallUsage(raw);
|
|
510
542
|
if (usage) runCtx.recordUsage?.(usage, {
|
|
511
543
|
kind: "text",
|
|
512
544
|
...id !== "" ? { id } : {},
|
|
@@ -514,11 +546,7 @@ function wrapTextLogicForRunAgent(logic, runCtx) {
|
|
|
514
546
|
model: request.model,
|
|
515
547
|
...request.name !== void 0 ? { name: request.name } : {}
|
|
516
548
|
}, self);
|
|
517
|
-
runCtx.
|
|
518
|
-
output,
|
|
519
|
-
raw
|
|
520
|
-
});
|
|
521
|
-
runCtx.onTrace?.({
|
|
549
|
+
runCtx.emitTrace?.({
|
|
522
550
|
type: "request.end",
|
|
523
551
|
request: agentRequest,
|
|
524
552
|
output,
|
|
@@ -528,7 +556,7 @@ function wrapTextLogicForRunAgent(logic, runCtx) {
|
|
|
528
556
|
}, self);
|
|
529
557
|
return { output };
|
|
530
558
|
} catch (error) {
|
|
531
|
-
runCtx.
|
|
559
|
+
runCtx.emitTrace?.({
|
|
532
560
|
type: "request.error",
|
|
533
561
|
request: agentRequest,
|
|
534
562
|
error
|
|
@@ -540,7 +568,7 @@ function wrapTextLogicForRunAgent(logic, runCtx) {
|
|
|
540
568
|
function createCountingDecide(runCtx, self) {
|
|
541
569
|
return async (attemptRequest) => {
|
|
542
570
|
runCtx.consumeModelCall();
|
|
543
|
-
runCtx.
|
|
571
|
+
runCtx.emitTrace?.({
|
|
544
572
|
type: "request.start",
|
|
545
573
|
request: attemptRequest
|
|
546
574
|
}, self);
|
|
@@ -549,7 +577,7 @@ function createCountingDecide(runCtx, self) {
|
|
|
549
577
|
...attemptRequest,
|
|
550
578
|
runId: runCtx.runId
|
|
551
579
|
} : attemptRequest);
|
|
552
|
-
const usage = require_decision.
|
|
580
|
+
const usage = require_decision.getCallUsage(result);
|
|
553
581
|
if (usage) {
|
|
554
582
|
const { src } = selfIdAndSrc(self);
|
|
555
583
|
runCtx.recordUsage?.(usage, {
|
|
@@ -559,11 +587,7 @@ function createCountingDecide(runCtx, self) {
|
|
|
559
587
|
model: attemptRequest.model
|
|
560
588
|
}, self);
|
|
561
589
|
}
|
|
562
|
-
runCtx.
|
|
563
|
-
output: result.event,
|
|
564
|
-
raw: result
|
|
565
|
-
});
|
|
566
|
-
runCtx.onTrace?.({
|
|
590
|
+
runCtx.emitTrace?.({
|
|
567
591
|
type: "request.end",
|
|
568
592
|
request: attemptRequest,
|
|
569
593
|
output: result.event,
|
|
@@ -572,7 +596,7 @@ function createCountingDecide(runCtx, self) {
|
|
|
572
596
|
}, self);
|
|
573
597
|
return result;
|
|
574
598
|
} catch (error) {
|
|
575
|
-
runCtx.
|
|
599
|
+
runCtx.emitTrace?.({
|
|
576
600
|
type: "request.error",
|
|
577
601
|
request: attemptRequest,
|
|
578
602
|
error
|
|
@@ -596,9 +620,10 @@ function createCountingDecide(runCtx, self) {
|
|
|
596
620
|
* send-then-complete note inside `run` for how exit-cancels-invoke interacts
|
|
597
621
|
* with `onDone`.
|
|
598
622
|
*/
|
|
599
|
-
function
|
|
600
|
-
const decisionLogic = (0, xstate.createAsyncLogic)({ run: async ({ input, signal, self }) => {
|
|
623
|
+
function bindDecisionLogic(logic, runCtx) {
|
|
624
|
+
const decisionLogic = (0, xstate.createAsyncLogic)({ run: async ({ input, signal, self: selfArg }) => {
|
|
601
625
|
if (!runCtx.decide) throw new Error("runAgent: no 'decide' executor provided.");
|
|
626
|
+
const self = selfArg;
|
|
602
627
|
const { id } = selfIdAndSrc(self);
|
|
603
628
|
const declaredEventTypes = logic.allowedEventTypes?.(input);
|
|
604
629
|
await Promise.resolve();
|
|
@@ -624,7 +649,7 @@ function createRunAgentDecisionLogic(logic, runCtx) {
|
|
|
624
649
|
kind: "statelyai.decisionLogic",
|
|
625
650
|
maxRetries: logic.maxRetries,
|
|
626
651
|
request: logic.request,
|
|
627
|
-
withExecutor: (nextExecute) =>
|
|
652
|
+
withExecutor: (nextExecute) => bindDecisionLogic(logic.withExecutor(nextExecute), runCtx)
|
|
628
653
|
});
|
|
629
654
|
}
|
|
630
655
|
/**
|
|
@@ -647,8 +672,7 @@ let nextProvideRunId = 1;
|
|
|
647
672
|
/** Walks `self._parent` from an invoked async leaf actor up to its root actor. */
|
|
648
673
|
function rootActorOf(self) {
|
|
649
674
|
let ref = self;
|
|
650
|
-
|
|
651
|
-
while (ref._parent) ref = ref._parent;
|
|
675
|
+
while (ref?._parent) ref = ref._parent;
|
|
652
676
|
return ref;
|
|
653
677
|
}
|
|
654
678
|
/** The per-root envelope state, minted on first use (runId `run_<n>`, matching runAgent). */
|
|
@@ -681,7 +705,7 @@ function stampRootTrace(root, payload) {
|
|
|
681
705
|
...payload
|
|
682
706
|
};
|
|
683
707
|
}
|
|
684
|
-
/** Adapts a public `onTrace` into the payload-level {@link
|
|
708
|
+
/** Adapts a public `onTrace` into the payload-level trace sink a {@link TraceDispatch} fans out to. */
|
|
685
709
|
function provideTraceSink(onTrace) {
|
|
686
710
|
if (!onTrace) return;
|
|
687
711
|
return (payload, self) => {
|
|
@@ -700,43 +724,48 @@ function provideTraceSink(onTrace) {
|
|
|
700
724
|
*
|
|
701
725
|
* `recordUsage` has no run-level aggregate to fold into here (there is no
|
|
702
726
|
* run), so it does one thing: deliver the reserved `@agent.usage` event, gated
|
|
703
|
-
* exactly like runAgent's — see {@link
|
|
727
|
+
* exactly like runAgent's — see {@link deliverUsageEvent}.
|
|
704
728
|
*/
|
|
705
729
|
function provideBindContext(machine, executors, options) {
|
|
730
|
+
const traceSink = provideTraceSink(options.onTrace);
|
|
731
|
+
const onChunk = options.onChunk;
|
|
706
732
|
return {
|
|
707
733
|
generateText: executors.generateText,
|
|
708
734
|
streamText: executors.streamText,
|
|
709
735
|
decide: executors.decide,
|
|
710
|
-
|
|
711
|
-
|
|
736
|
+
emitTrace: traceSink || onChunk ? createTraceDispatch({
|
|
737
|
+
onTrace: traceSink,
|
|
738
|
+
onChunk: onChunk ? (chunk) => onChunk(chunk) : void 0
|
|
739
|
+
}) : void 0,
|
|
712
740
|
consumeModelCall: () => {},
|
|
713
741
|
recordUsage: (usage, source, self) => {
|
|
714
|
-
|
|
742
|
+
deliverUsageEvent(usage, source ?? {}, () => self?._parent);
|
|
715
743
|
},
|
|
716
744
|
actorHolder: { actorRef: void 0 },
|
|
717
745
|
schemas: require_decision.getRegisteredAgentExecutionOptions(machine).schemas
|
|
718
746
|
};
|
|
719
747
|
}
|
|
720
748
|
/**
|
|
721
|
-
* `
|
|
722
|
-
* bound call settles with reported usage, send the
|
|
723
|
-
*
|
|
724
|
-
*
|
|
725
|
-
* `createActor` tree
|
|
726
|
-
* root actor on this path).
|
|
749
|
+
* The single reserved-`@agent.usage` delivery seam, shared by both bind paths:
|
|
750
|
+
* after a bound call settles with reported usage, send the event to the machine
|
|
751
|
+
* actor `resolveActorRef` names — the run's root actor on the `runAgent` path,
|
|
752
|
+
* the settling request actor's `self._parent` (always the invoking machine
|
|
753
|
+
* under a live `createActor` tree) on the `provideExecutors` path.
|
|
727
754
|
*
|
|
728
|
-
*
|
|
729
|
-
*
|
|
730
|
-
*
|
|
731
|
-
*
|
|
732
|
-
*
|
|
755
|
+
* Gating is identical on both: the target snapshot must be active, must declare
|
|
756
|
+
* an `'@agent.usage'` transition EXPLICITLY (see {@link declaresUsageTransition}
|
|
757
|
+
* — a catch-all `on: { '*' }` is not an opt-in), and must be able to take the
|
|
758
|
+
* event. `onDropped` is the run path's straggler gate: it returns `true` for a
|
|
759
|
+
* call that settled after the cycle resolved, which drops the event (traced as
|
|
760
|
+
* `usage.dropped`) rather than delivering it. Uncontrolled mode has no cycle to
|
|
761
|
+
* settle, so it passes no gate and has no dropped stragglers.
|
|
733
762
|
*
|
|
734
|
-
* Delivery follows
|
|
735
|
-
*
|
|
763
|
+
* Delivery follows each path's binding boundary: only sources IT bound report
|
|
764
|
+
* here, so an invoked child machine that was not itself passed through
|
|
736
765
|
* `provideExecutors` reports nothing. @internal
|
|
737
766
|
*/
|
|
738
|
-
function
|
|
739
|
-
const actorRef =
|
|
767
|
+
function deliverUsageEvent(usage, source, resolveActorRef, onDropped) {
|
|
768
|
+
const actorRef = resolveActorRef();
|
|
740
769
|
if (!actorRef) return;
|
|
741
770
|
const snapshot = actorRef.getSnapshot();
|
|
742
771
|
if (snapshot?.status !== "active" || !declaresUsageTransition(snapshot)) return;
|
|
@@ -745,17 +774,18 @@ function deliverUsageToInvokingActor(usage, source, self) {
|
|
|
745
774
|
...source,
|
|
746
775
|
usage
|
|
747
776
|
};
|
|
777
|
+
if (onDropped?.(event)) return;
|
|
748
778
|
if (!snapshot.can(event)) return;
|
|
749
779
|
actorRef.send(event);
|
|
750
780
|
}
|
|
751
781
|
/**
|
|
752
782
|
* Host-binds one text/stream source for {@link provideExecutors} using the SAME
|
|
753
|
-
* emission helper as `runAgent` ({@link
|
|
783
|
+
* emission helper as `runAgent` ({@link bindTextLogic}), so a bound
|
|
754
784
|
* text request emits request.start/stream.chunk/request.end/request.error with
|
|
755
785
|
* identical shapes. @internal
|
|
756
786
|
*/
|
|
757
787
|
function bindTextForProvide(machine, logic, executors, options) {
|
|
758
|
-
return
|
|
788
|
+
return bindTextLogic(logic, provideBindContext(machine, executors, options));
|
|
759
789
|
}
|
|
760
790
|
/**
|
|
761
791
|
* Host-binds one `DecisionLogic`/`agent.decide` source for
|
|
@@ -764,7 +794,27 @@ function bindTextForProvide(machine, logic, executors, options) {
|
|
|
764
794
|
* request-level tracing runAgent emits, minus run-scoped counting. @internal
|
|
765
795
|
*/
|
|
766
796
|
function bindDecisionForProvide(machine, logic, executors, options) {
|
|
767
|
-
return
|
|
797
|
+
return bindDecisionLogic(logic, provideBindContext(machine, executors, options));
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Validates `input` against the machine's registered input schema, returning
|
|
801
|
+
* the schema's output — so defaults are filled and transforms applied before
|
|
802
|
+
* the value reaches `createActor` or the replayable event log.
|
|
803
|
+
*
|
|
804
|
+
* Standard Schema only (no validation library is referenced), so this works for
|
|
805
|
+
* whatever the machine was declared with. Omitted input stays omitted rather
|
|
806
|
+
* than being validated as `{}`: "started with no input" keeps meaning what it
|
|
807
|
+
* has always meant, instead of newly failing schemas with required fields.
|
|
808
|
+
*/
|
|
809
|
+
function resolveMachineInput(machine, input) {
|
|
810
|
+
if (input === void 0) return input;
|
|
811
|
+
const schema = require_decision.getRegisteredAgentExecutionOptions(machine).schemas?.input;
|
|
812
|
+
if (!require_decision.isStandardSchema(schema)) return input;
|
|
813
|
+
try {
|
|
814
|
+
return require_decision.validateSchemaSync(schema, input);
|
|
815
|
+
} catch (error) {
|
|
816
|
+
throw new require_errors.AgentError("invalid-machine-input", `runAgent: machine input failed validation against the declared input schema: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
817
|
+
}
|
|
768
818
|
}
|
|
769
819
|
/**
|
|
770
820
|
* Recursively rebinds an invoked child machine's own agent sources with the
|
|
@@ -787,11 +837,11 @@ function rebindChildMachine(childMachine, runCtx, visited) {
|
|
|
787
837
|
const wrapped = {};
|
|
788
838
|
for (const [key, logic] of Object.entries(sources)) {
|
|
789
839
|
if (require_decision.isDecisionLogic(logic)) {
|
|
790
|
-
if (!require_decision.executorBoundLogics.has(logic)) wrapped[key] =
|
|
840
|
+
if (!require_decision.executorBoundLogics.has(logic)) wrapped[key] = bindDecisionLogic(logic, runCtx);
|
|
791
841
|
continue;
|
|
792
842
|
}
|
|
793
843
|
if (require_decision.isTextLogic(logic)) {
|
|
794
|
-
if (!require_decision.executorBoundLogics.has(logic)) wrapped[key] =
|
|
844
|
+
if (!require_decision.executorBoundLogics.has(logic)) wrapped[key] = bindTextLogic(logic, runCtx);
|
|
795
845
|
continue;
|
|
796
846
|
}
|
|
797
847
|
if (isStateMachine(logic)) {
|
|
@@ -876,26 +926,29 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
876
926
|
let warnedHeuristicIdle = false;
|
|
877
927
|
const runId = `run_${nextRunAgentTraceId++}`;
|
|
878
928
|
let traceSeq = 0;
|
|
929
|
+
const resolvedInput = resolveMachineInput(machine, options.input);
|
|
879
930
|
const machineId = machine.config.id ?? machine.id ?? "(machine)";
|
|
880
931
|
const machineVersion = options.machineVersion ?? machine.version ?? require_decision.getMachineStructuralHash(machine);
|
|
881
932
|
const agentMeta = {
|
|
882
933
|
machineId,
|
|
883
934
|
version: machineVersion
|
|
884
935
|
};
|
|
885
|
-
const
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
936
|
+
const onTrace = createTraceDispatch({
|
|
937
|
+
onTrace: (payload) => {
|
|
938
|
+
options.onTrace?.({
|
|
939
|
+
schemaVersion: 1,
|
|
940
|
+
runId,
|
|
941
|
+
seq: ++traceSeq,
|
|
942
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
943
|
+
machineId,
|
|
944
|
+
machineVersion,
|
|
945
|
+
...payload
|
|
946
|
+
});
|
|
947
|
+
},
|
|
948
|
+
onChunk: options.onChunk,
|
|
949
|
+
onResult: options.onResult,
|
|
950
|
+
onTransition: options.onTransition
|
|
951
|
+
});
|
|
899
952
|
const consumeModelCall = () => {
|
|
900
953
|
if (budgetExceeded) throw new AgentMaxModelCallsExceededError();
|
|
901
954
|
if (modelCallCount + 1 > maxModelCalls) {
|
|
@@ -906,33 +959,20 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
906
959
|
};
|
|
907
960
|
const tokenTotals = {};
|
|
908
961
|
const cycleGate = { isResolved: () => false };
|
|
909
|
-
const deliverUsageEvent = (usage, source) => {
|
|
910
|
-
const actorRef = actorHolder.actorRef;
|
|
911
|
-
if (!actorRef) return;
|
|
912
|
-
const event = {
|
|
913
|
-
type: require_setup_agent.AGENT_USAGE_EVENT_TYPE,
|
|
914
|
-
...source,
|
|
915
|
-
usage
|
|
916
|
-
};
|
|
917
|
-
const snapshot = actorRef.getSnapshot();
|
|
918
|
-
if (snapshot?.status !== "active" || !declaresUsageTransition(snapshot)) return;
|
|
919
|
-
if (cycleGate.isResolved()) {
|
|
920
|
-
onTrace({
|
|
921
|
-
type: "usage.dropped",
|
|
922
|
-
event,
|
|
923
|
-
reason: "settled"
|
|
924
|
-
});
|
|
925
|
-
return;
|
|
926
|
-
}
|
|
927
|
-
if (!snapshot.can(event)) return;
|
|
928
|
-
actorRef.send(event);
|
|
929
|
-
};
|
|
930
962
|
const recordUsage = (usage, source = {}) => {
|
|
931
963
|
for (const field of require_decision.AGENT_USAGE_TOKEN_FIELDS) {
|
|
932
964
|
const value = usage[field];
|
|
933
965
|
if (typeof value === "number" && Number.isFinite(value)) tokenTotals[field] = (tokenTotals[field] ?? 0) + value;
|
|
934
966
|
}
|
|
935
|
-
deliverUsageEvent(usage, source)
|
|
967
|
+
deliverUsageEvent(usage, source, () => actorHolder.actorRef, (event) => {
|
|
968
|
+
if (!cycleGate.isResolved()) return false;
|
|
969
|
+
onTrace({
|
|
970
|
+
type: "usage.dropped",
|
|
971
|
+
event,
|
|
972
|
+
reason: "settled"
|
|
973
|
+
});
|
|
974
|
+
return true;
|
|
975
|
+
});
|
|
936
976
|
};
|
|
937
977
|
const runUsage = () => ({
|
|
938
978
|
...tokenTotals,
|
|
@@ -952,20 +992,14 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
952
992
|
};
|
|
953
993
|
const provided = machine.provide({ actors: options.actors });
|
|
954
994
|
const effectiveSources = provided.sources.actors;
|
|
955
|
-
assertBindable(provided, effectiveSources, {
|
|
956
|
-
hasGenerateText: !!options.executors?.generateText,
|
|
957
|
-
hasDecide: !!options.executors?.decide,
|
|
958
|
-
hasStreamText: !!options.executors?.streamText
|
|
959
|
-
});
|
|
995
|
+
assertBindable(provided, effectiveSources, options.executors ?? {});
|
|
960
996
|
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.");
|
|
961
997
|
const actorHolder = { actorRef: void 0 };
|
|
962
998
|
const runCtx = {
|
|
963
999
|
generateText: options.executors?.generateText,
|
|
964
1000
|
streamText: options.executors?.streamText,
|
|
965
1001
|
decide: options.executors?.decide,
|
|
966
|
-
|
|
967
|
-
onResult: options.onResult,
|
|
968
|
-
onTrace,
|
|
1002
|
+
emitTrace: onTrace,
|
|
969
1003
|
consumeModelCall,
|
|
970
1004
|
recordUsage,
|
|
971
1005
|
actorHolder,
|
|
@@ -986,11 +1020,11 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
986
1020
|
continue;
|
|
987
1021
|
}
|
|
988
1022
|
if (require_decision.isDecisionLogic(logic)) {
|
|
989
|
-
wrappedSources[key] =
|
|
1023
|
+
wrappedSources[key] = bindDecisionLogic(logic, runCtx);
|
|
990
1024
|
continue;
|
|
991
1025
|
}
|
|
992
1026
|
if (require_decision.isTextLogic(logic)) {
|
|
993
|
-
if (!require_decision.executorBoundLogics.has(logic)) wrappedSources[key] =
|
|
1027
|
+
if (!require_decision.executorBoundLogics.has(logic)) wrappedSources[key] = bindTextLogic(logic, runCtx);
|
|
994
1028
|
continue;
|
|
995
1029
|
}
|
|
996
1030
|
if (isStateMachine(logic)) {
|
|
@@ -1032,9 +1066,11 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1032
1066
|
}
|
|
1033
1067
|
const priorMessages = require_decision.getAgentMessages(effectiveSnapshot);
|
|
1034
1068
|
const messages = typeof options.messages === "function" ? [...options.messages([...priorMessages])] : [...priorMessages, ...options.messages ?? []];
|
|
1035
|
-
const
|
|
1069
|
+
const stampSettledSnapshot = (snapshot) => {
|
|
1070
|
+
if (!snapshot || typeof snapshot !== "object") return;
|
|
1071
|
+
snapshot.agentMeta = agentMeta;
|
|
1036
1072
|
if (!options.getRequests && !options.messages && messages.length === 0) return;
|
|
1037
|
-
|
|
1073
|
+
snapshot.messages = [...messages];
|
|
1038
1074
|
};
|
|
1039
1075
|
if (effectiveSnapshot !== void 0 && options.event !== void 0 && (options.onIllegalResumeEvent ?? "throw") === "throw") {
|
|
1040
1076
|
const acceptedTypes = require_decision.getAcceptedEvents((0, xstate.createActor)(boundMachine, { snapshot: effectiveSnapshot }).getSnapshot(), { schemas: runCtx.schemas }).map((descriptor) => descriptor.type);
|
|
@@ -1042,21 +1078,11 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1042
1078
|
if (!acceptedTypes.includes(eventType)) throw new AgentIllegalResumeEventError(eventType, acceptedTypes);
|
|
1043
1079
|
}
|
|
1044
1080
|
const replayEvents = [...resumeEvents ?? []];
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
if (entry.machineId !== machineId || entry.machineVersion !== machineVersion) throw new require_setup_agent.AgentReplayMachineMismatchError(entry.id, entry.index, {
|
|
1051
|
-
machineId,
|
|
1052
|
-
machineVersion
|
|
1053
|
-
}, {
|
|
1054
|
-
machineId: entry.machineId,
|
|
1055
|
-
machineVersion: entry.machineVersion
|
|
1056
|
-
});
|
|
1057
|
-
if (replayEventIds.has(entry.id)) throw new Error(`runAgent events contain duplicate event id '${entry.id}'.`);
|
|
1058
|
-
replayEventIds.add(entry.id);
|
|
1059
|
-
}
|
|
1081
|
+
require_setup_agent.validateReplayEntries(replayEvents, {
|
|
1082
|
+
machineId,
|
|
1083
|
+
machineVersion
|
|
1084
|
+
}, "runAgent events");
|
|
1085
|
+
const replayEventIds = new Set(replayEvents.map((entry) => entry.id));
|
|
1060
1086
|
const hasCompleteReplayHistory = replayEvents[0]?.event.type === "@agent.init" || effectiveSnapshot === void 0 && replayEvents.length === 0;
|
|
1061
1087
|
const appendReplayEvent = (event) => {
|
|
1062
1088
|
const entry = require_setup_agent.createReplayEntry(machine, replayEvents, event, {
|
|
@@ -1070,252 +1096,244 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1070
1096
|
return entry;
|
|
1071
1097
|
};
|
|
1072
1098
|
if (replayEvents.length === 0 && effectiveSnapshot === void 0) {
|
|
1073
|
-
const entry = require_setup_agent.initEntry(machine,
|
|
1099
|
+
const entry = require_setup_agent.initEntry(machine, resolvedInput, { machineVersion });
|
|
1074
1100
|
replayEventIds.add(entry.id);
|
|
1075
1101
|
replayEvents.push(entry);
|
|
1076
1102
|
options.onEvent?.(entry);
|
|
1077
1103
|
}
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
usage: runUsage()
|
|
1094
|
-
};
|
|
1095
|
-
if (idleTimer !== void 0) clearTimeout(idleTimer);
|
|
1096
|
-
stampAgentMeta(result.snapshot);
|
|
1097
|
-
stampMessages(result.snapshot);
|
|
1098
|
-
if ("persistedSnapshot" in result) {
|
|
1099
|
-
stampAgentMeta(result.persistedSnapshot);
|
|
1100
|
-
stampMessages(result.persistedSnapshot);
|
|
1101
|
-
}
|
|
1102
|
-
onTrace({
|
|
1103
|
-
type: "run.end",
|
|
1104
|
-
...outcome
|
|
1105
|
-
});
|
|
1106
|
-
if (lifecycle.oneShot || result.status !== "idle") {
|
|
1107
|
-
finalized = true;
|
|
1108
|
-
if (options.signal) options.signal.removeEventListener("abort", onAbort);
|
|
1109
|
-
actor.stop();
|
|
1110
|
-
}
|
|
1111
|
-
lastResult = result;
|
|
1112
|
-
for (const resolve of waiters.splice(0)) resolve(result);
|
|
1113
|
-
};
|
|
1114
|
-
const onAbort = () => {
|
|
1115
|
-
settle({
|
|
1116
|
-
status: "error",
|
|
1117
|
-
cause: "aborted",
|
|
1118
|
-
error: options.signal?.reason ?? /* @__PURE__ */ new Error("Aborted"),
|
|
1119
|
-
snapshot: actor.getSnapshot()
|
|
1120
|
-
});
|
|
1121
|
-
};
|
|
1122
|
-
const settleIdle = (current) => {
|
|
1123
|
-
warnNonSerializableContext(current);
|
|
1124
|
-
const pendingUserInputs = userInputIsPlaceholder ? collectPendingUserInputs(current) : [];
|
|
1125
|
-
settle({
|
|
1126
|
-
status: "idle",
|
|
1127
|
-
snapshot: current,
|
|
1128
|
-
persistedSnapshot: actor.getPersistedSnapshot(),
|
|
1129
|
-
...pendingUserInputs.length > 0 ? { pendingUserInputs } : {}
|
|
1130
|
-
});
|
|
1104
|
+
let settled = false;
|
|
1105
|
+
let finalized = false;
|
|
1106
|
+
cycleGate.isResolved = () => settled;
|
|
1107
|
+
let lastResult;
|
|
1108
|
+
const waiters = [];
|
|
1109
|
+
let idleTimer;
|
|
1110
|
+
let actor;
|
|
1111
|
+
let deliveringResumeEvent = options.event !== void 0;
|
|
1112
|
+
const settle = (outcome) => {
|
|
1113
|
+
if (settled) return;
|
|
1114
|
+
settled = true;
|
|
1115
|
+
const result = {
|
|
1116
|
+
...outcome,
|
|
1117
|
+
events: [...replayEvents],
|
|
1118
|
+
usage: runUsage()
|
|
1131
1119
|
};
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
const
|
|
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
|
-
|
|
1120
|
+
if (idleTimer !== void 0) clearTimeout(idleTimer);
|
|
1121
|
+
stampSettledSnapshot(result.snapshot);
|
|
1122
|
+
if ("persistedSnapshot" in result) stampSettledSnapshot(result.persistedSnapshot);
|
|
1123
|
+
onTrace({
|
|
1124
|
+
type: "run.end",
|
|
1125
|
+
...outcome
|
|
1126
|
+
});
|
|
1127
|
+
if (lifecycle.oneShot || result.status !== "idle") {
|
|
1128
|
+
finalized = true;
|
|
1129
|
+
if (options.signal) options.signal.removeEventListener("abort", onAbort);
|
|
1130
|
+
actor.stop();
|
|
1131
|
+
}
|
|
1132
|
+
lastResult = result;
|
|
1133
|
+
for (const resolve of waiters.splice(0)) resolve(result);
|
|
1134
|
+
};
|
|
1135
|
+
const onAbort = () => {
|
|
1136
|
+
settle({
|
|
1137
|
+
status: "error",
|
|
1138
|
+
cause: "aborted",
|
|
1139
|
+
error: options.signal?.reason ?? /* @__PURE__ */ new Error("Aborted"),
|
|
1140
|
+
snapshot: actor.getSnapshot()
|
|
1141
|
+
});
|
|
1142
|
+
};
|
|
1143
|
+
const settleIdle = (current) => {
|
|
1144
|
+
warnNonSerializableContext(current);
|
|
1145
|
+
const pendingUserInputs = userInputIsPlaceholder ? collectPendingUserInputs(current) : [];
|
|
1146
|
+
settle({
|
|
1147
|
+
status: "idle",
|
|
1148
|
+
snapshot: current,
|
|
1149
|
+
persistedSnapshot: actor.getPersistedSnapshot(),
|
|
1150
|
+
...pendingUserInputs.length > 0 ? { pendingUserInputs } : {}
|
|
1151
|
+
});
|
|
1152
|
+
};
|
|
1153
|
+
let interpreting = false;
|
|
1154
|
+
let interpretSeq = 0;
|
|
1155
|
+
const appendToLog = (...items) => {
|
|
1156
|
+
messages.push(...items);
|
|
1157
|
+
if (options.onMessage) {
|
|
1158
|
+
const info = {
|
|
1159
|
+
runId,
|
|
1160
|
+
machineId,
|
|
1161
|
+
machineVersion
|
|
1162
|
+
};
|
|
1163
|
+
for (const item of items) options.onMessage(item, info);
|
|
1164
|
+
}
|
|
1165
|
+
};
|
|
1166
|
+
const runErrorCause = (error) => budgetExceeded ? "max-model-calls" : wrapsDecisionExhausted(error) ? "decision-exhausted" : "machine";
|
|
1167
|
+
const settleInterpretError = (error) => {
|
|
1168
|
+
settle({
|
|
1169
|
+
status: "error",
|
|
1170
|
+
cause: runErrorCause(error),
|
|
1171
|
+
error,
|
|
1172
|
+
snapshot: actor.getSnapshot()
|
|
1173
|
+
});
|
|
1174
|
+
};
|
|
1175
|
+
const passDeps = {
|
|
1176
|
+
getSnapshot: () => actor.getSnapshot(),
|
|
1177
|
+
send: (event) => actor.send(event),
|
|
1178
|
+
isSettled: () => settled,
|
|
1179
|
+
messages,
|
|
1180
|
+
appendToLog,
|
|
1181
|
+
generateText: runCtx.generateText,
|
|
1182
|
+
decide: runCtx.decide ? createCountingDecide(runCtx, void 0) : void 0,
|
|
1183
|
+
consumeModelCall,
|
|
1184
|
+
recordUsage,
|
|
1185
|
+
nextRequestId: () => `interpret_${++interpretSeq}`,
|
|
1186
|
+
onTrace,
|
|
1187
|
+
schemas: runCtx.schemas,
|
|
1188
|
+
signal: options.signal
|
|
1189
|
+
};
|
|
1190
|
+
const maybeInterpret = (snapshot) => {
|
|
1191
|
+
if (!options.getRequests || settled) return false;
|
|
1192
|
+
if (interpreting) return true;
|
|
1193
|
+
let requested;
|
|
1194
|
+
try {
|
|
1195
|
+
requested = options.getRequests(snapshot, { messages });
|
|
1196
|
+
} catch (error) {
|
|
1197
|
+
settleInterpretError(error);
|
|
1191
1198
|
return true;
|
|
1192
|
-
}
|
|
1193
|
-
const
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1199
|
+
}
|
|
1200
|
+
const requests = (Array.isArray(requested) ? requested : requested ? [requested] : []).filter((stateRequest) => Boolean(stateRequest));
|
|
1201
|
+
if (requests.length === 0) return false;
|
|
1202
|
+
interpreting = true;
|
|
1203
|
+
runStateRequestPass(requests, passDeps).then(({ sentAny }) => {
|
|
1204
|
+
if (settled || sentAny) return;
|
|
1205
|
+
const current = actor.getSnapshot();
|
|
1206
|
+
if (isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) settleIdle(current);
|
|
1207
|
+
}).catch((error) => settleInterpretError(error)).finally(() => {
|
|
1208
|
+
interpreting = false;
|
|
1209
|
+
if (!settled) scheduleIdleCheck();
|
|
1210
|
+
});
|
|
1211
|
+
return true;
|
|
1212
|
+
};
|
|
1213
|
+
const scheduleIdleCheck = () => {
|
|
1214
|
+
if (idleTimer !== void 0) clearTimeout(idleTimer);
|
|
1215
|
+
idleTimer = setTimeout(() => {
|
|
1216
|
+
idleTimer = void 0;
|
|
1217
|
+
if (settled) return;
|
|
1218
|
+
const current = actor.getSnapshot();
|
|
1219
|
+
if (isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) {
|
|
1220
|
+
if (!maybeInterpret(current)) {
|
|
1221
|
+
if (!declaredSuspensionPredicate && current.status === "active" && !warnedHeuristicIdle && process.env.NODE_ENV !== "production") {
|
|
1222
|
+
warnedHeuristicIdle = true;
|
|
1223
|
+
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').");
|
|
1206
1224
|
}
|
|
1225
|
+
settleIdle(current);
|
|
1207
1226
|
}
|
|
1208
|
-
}, 0);
|
|
1209
|
-
};
|
|
1210
|
-
actor = (0, xstate.createActor)(boundMachine, {
|
|
1211
|
-
input: options.input,
|
|
1212
|
-
snapshot: effectiveSnapshot,
|
|
1213
|
-
inspect: (event) => {
|
|
1214
|
-
if (typeof options.inspect === "function") options.inspect(event);
|
|
1215
|
-
else options.inspect?.next?.(event);
|
|
1216
|
-
if (event.type !== "@xstate.transition" || event.actorRef !== actor.ref) return;
|
|
1217
|
-
if (settled) {
|
|
1218
|
-
if (finalized) return;
|
|
1219
|
-
settled = false;
|
|
1220
|
-
lastResult = void 0;
|
|
1221
|
-
}
|
|
1222
|
-
const snapshot = event.snapshot;
|
|
1223
|
-
let eventId;
|
|
1224
|
-
if (event.event.type !== "@xstate.init" && (event.sourceRef !== event.actorRef || event.event.type === "xstate.timer")) eventId = appendReplayEvent(event.event).id;
|
|
1225
|
-
else if (event.event.type === "@xstate.init") eventId = replayEvents[0]?.event.type === "@agent.init" ? replayEvents[0].id : void 0;
|
|
1226
|
-
onTrace({
|
|
1227
|
-
type: "machine.transition",
|
|
1228
|
-
snapshot,
|
|
1229
|
-
event: event.event,
|
|
1230
|
-
...eventId !== void 0 ? { eventId } : {}
|
|
1231
|
-
});
|
|
1232
|
-
options.onTransition?.(snapshot, event.event);
|
|
1233
|
-
if (snapshot.status === "done") {
|
|
1234
|
-
settle({
|
|
1235
|
-
status: "done",
|
|
1236
|
-
output: snapshot.output,
|
|
1237
|
-
snapshot
|
|
1238
|
-
});
|
|
1239
|
-
return;
|
|
1240
|
-
}
|
|
1241
|
-
if (snapshot.status === "error") {
|
|
1242
|
-
settle({
|
|
1243
|
-
status: "error",
|
|
1244
|
-
cause: runErrorCause(snapshot.error),
|
|
1245
|
-
error: snapshot.error,
|
|
1246
|
-
snapshot
|
|
1247
|
-
});
|
|
1248
|
-
return;
|
|
1249
|
-
}
|
|
1250
|
-
if (snapshot.status === "stopped") {
|
|
1251
|
-
settle({
|
|
1252
|
-
status: "error",
|
|
1253
|
-
cause: "stopped",
|
|
1254
|
-
error: /* @__PURE__ */ new Error("Actor stopped externally."),
|
|
1255
|
-
snapshot
|
|
1256
|
-
});
|
|
1257
|
-
return;
|
|
1258
|
-
}
|
|
1259
|
-
if (!deliveringResumeEvent && isSuspended(snapshot) && isIdleSnapshot(snapshot, { ignoreUserInputChildren: userInputIsPlaceholder })) {
|
|
1260
|
-
queueMicrotask(() => {
|
|
1261
|
-
if (settled) return;
|
|
1262
|
-
const current = actor.getSnapshot();
|
|
1263
|
-
if (isSuspended(current) && isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) {
|
|
1264
|
-
if (!maybeInterpret(current)) settleIdle(current);
|
|
1265
|
-
} else scheduleIdleCheck();
|
|
1266
|
-
});
|
|
1267
|
-
return;
|
|
1268
|
-
}
|
|
1269
|
-
scheduleIdleCheck();
|
|
1270
1227
|
}
|
|
1271
|
-
});
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1228
|
+
}, 0);
|
|
1229
|
+
};
|
|
1230
|
+
actor = (0, xstate.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;
|
|
1275
1246
|
onTrace({
|
|
1276
|
-
type: "
|
|
1277
|
-
|
|
1247
|
+
type: "machine.transition",
|
|
1248
|
+
snapshot,
|
|
1249
|
+
event: event.event,
|
|
1250
|
+
...eventId !== void 0 ? { eventId } : {}
|
|
1278
1251
|
});
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
usage: runUsage,
|
|
1287
|
-
settled: () => settled && lastResult !== void 0 ? Promise.resolve(lastResult) : new Promise((resolve) => {
|
|
1288
|
-
waiters.push(resolve);
|
|
1289
|
-
}),
|
|
1290
|
-
stop: () => {
|
|
1291
|
-
actor.stop();
|
|
1252
|
+
if (snapshot.status === "done") {
|
|
1253
|
+
settle({
|
|
1254
|
+
status: "done",
|
|
1255
|
+
output: snapshot.output,
|
|
1256
|
+
snapshot
|
|
1257
|
+
});
|
|
1258
|
+
return;
|
|
1292
1259
|
}
|
|
1293
|
-
|
|
1294
|
-
if (options.signal) {
|
|
1295
|
-
if (options.signal.aborted) {
|
|
1260
|
+
if (snapshot.status === "error") {
|
|
1296
1261
|
settle({
|
|
1297
1262
|
status: "error",
|
|
1298
|
-
cause:
|
|
1299
|
-
error:
|
|
1300
|
-
snapshot
|
|
1263
|
+
cause: runErrorCause(snapshot.error),
|
|
1264
|
+
error: snapshot.error,
|
|
1265
|
+
snapshot
|
|
1301
1266
|
});
|
|
1302
|
-
return
|
|
1267
|
+
return;
|
|
1303
1268
|
}
|
|
1304
|
-
|
|
1269
|
+
if (snapshot.status === "stopped") {
|
|
1270
|
+
settle({
|
|
1271
|
+
status: "error",
|
|
1272
|
+
cause: "stopped",
|
|
1273
|
+
error: /* @__PURE__ */ new Error("Actor stopped externally."),
|
|
1274
|
+
snapshot
|
|
1275
|
+
});
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
if (!deliveringResumeEvent && isSuspended(snapshot) && isIdleSnapshot(snapshot, { ignoreUserInputChildren: userInputIsPlaceholder })) {
|
|
1279
|
+
queueMicrotask(() => {
|
|
1280
|
+
if (settled) return;
|
|
1281
|
+
const current = actor.getSnapshot();
|
|
1282
|
+
if (isSuspended(current) && isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) {
|
|
1283
|
+
if (!maybeInterpret(current)) settleIdle(current);
|
|
1284
|
+
} else scheduleIdleCheck();
|
|
1285
|
+
});
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
scheduleIdleCheck();
|
|
1305
1289
|
}
|
|
1290
|
+
});
|
|
1291
|
+
actorHolder.actorRef = actor;
|
|
1292
|
+
actor.subscribe({ error: () => {} });
|
|
1293
|
+
actor.on("*", (event) => {
|
|
1306
1294
|
onTrace({
|
|
1307
|
-
type: "
|
|
1308
|
-
|
|
1309
|
-
...effectiveSnapshot !== void 0 ? { snapshot: effectiveSnapshot } : {},
|
|
1310
|
-
...options.event !== void 0 ? { event: options.event } : {}
|
|
1295
|
+
type: "emit",
|
|
1296
|
+
event
|
|
1311
1297
|
});
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1298
|
+
});
|
|
1299
|
+
for (const [type, handler] of Object.entries(options.on ?? {})) if (typeof handler === "function") actor.on(type, handler);
|
|
1300
|
+
const sessionApi = {
|
|
1301
|
+
actor,
|
|
1302
|
+
get events() {
|
|
1303
|
+
return replayEvents;
|
|
1304
|
+
},
|
|
1305
|
+
usage: runUsage,
|
|
1306
|
+
settled: () => settled && lastResult !== void 0 ? Promise.resolve(lastResult) : new Promise((resolve) => {
|
|
1307
|
+
waiters.push(resolve);
|
|
1308
|
+
}),
|
|
1309
|
+
stop: () => {
|
|
1310
|
+
actor.stop();
|
|
1311
|
+
}
|
|
1312
|
+
};
|
|
1313
|
+
if (options.signal) {
|
|
1314
|
+
if (options.signal.aborted) {
|
|
1315
|
+
settle({
|
|
1316
|
+
status: "error",
|
|
1317
|
+
cause: "aborted",
|
|
1318
|
+
error: options.signal.reason ?? /* @__PURE__ */ new Error("Aborted"),
|
|
1319
|
+
snapshot: actor.getSnapshot()
|
|
1320
|
+
});
|
|
1321
|
+
return sessionApi;
|
|
1316
1322
|
}
|
|
1317
|
-
|
|
1318
|
-
}
|
|
1323
|
+
options.signal.addEventListener("abort", onAbort);
|
|
1324
|
+
}
|
|
1325
|
+
onTrace({
|
|
1326
|
+
type: "run.start",
|
|
1327
|
+
...resolvedInput !== void 0 ? { input: resolvedInput } : {},
|
|
1328
|
+
...effectiveSnapshot !== void 0 ? { snapshot: effectiveSnapshot } : {},
|
|
1329
|
+
...options.event !== void 0 ? { event: options.event } : {}
|
|
1330
|
+
});
|
|
1331
|
+
actor.start();
|
|
1332
|
+
if (options.event) {
|
|
1333
|
+
deliveringResumeEvent = false;
|
|
1334
|
+
actor.send(options.event);
|
|
1335
|
+
}
|
|
1336
|
+
return sessionApi;
|
|
1319
1337
|
}
|
|
1320
1338
|
/**
|
|
1321
1339
|
* Runs an agent machine to a **final state**, for run-to-done flows where an
|
|
@@ -1471,14 +1489,17 @@ function createAgentRun(machine, options) {
|
|
|
1471
1489
|
});
|
|
1472
1490
|
else buffer.push(event);
|
|
1473
1491
|
};
|
|
1474
|
-
const
|
|
1475
|
-
if (closed) return;
|
|
1476
|
-
closed = true;
|
|
1492
|
+
const drain = () => {
|
|
1477
1493
|
while (resolvers.length > 0) resolvers.shift()({
|
|
1478
1494
|
value: void 0,
|
|
1479
1495
|
done: true
|
|
1480
1496
|
});
|
|
1481
1497
|
};
|
|
1498
|
+
const close = () => {
|
|
1499
|
+
if (closed) return;
|
|
1500
|
+
closed = true;
|
|
1501
|
+
drain();
|
|
1502
|
+
};
|
|
1482
1503
|
const userOnTrace = options.onTrace;
|
|
1483
1504
|
const result = runAgent(machine, {
|
|
1484
1505
|
...options,
|
|
@@ -1507,10 +1528,7 @@ function createAgentRun(machine, options) {
|
|
|
1507
1528
|
return() {
|
|
1508
1529
|
stopped = true;
|
|
1509
1530
|
buffer.length = 0;
|
|
1510
|
-
|
|
1511
|
-
value: void 0,
|
|
1512
|
-
done: true
|
|
1513
|
-
});
|
|
1531
|
+
drain();
|
|
1514
1532
|
return Promise.resolve({
|
|
1515
1533
|
value: void 0,
|
|
1516
1534
|
done: true
|
|
@@ -1558,7 +1576,7 @@ function createAgentRun(machine, options) {
|
|
|
1558
1576
|
* Throws at bind time if a source needs an executor kind that `executors` does
|
|
1559
1577
|
* not provide.
|
|
1560
1578
|
*
|
|
1561
|
-
*
|
|
1579
|
+
* `provideExecutors` does not descend into invoked child state machines: a string-keyed child
|
|
1562
1580
|
* machine source is left untouched, so a child with its own agent invokes needs
|
|
1563
1581
|
* its own `provideExecutors(...)` (or `runAgent`, which does rebind children).
|
|
1564
1582
|
*/
|
|
@@ -1567,32 +1585,36 @@ function provideExecutors(machine, executors, options = {}) {
|
|
|
1567
1585
|
onChunk: options.onChunk,
|
|
1568
1586
|
onTrace: options.onTrace
|
|
1569
1587
|
};
|
|
1570
|
-
const
|
|
1588
|
+
const withActors = (target, actors) => target.provide({ actors });
|
|
1589
|
+
const provided = options.actors ? withActors(machine, options.actors) : machine;
|
|
1571
1590
|
const effectiveSources = provided.sources.actors;
|
|
1572
1591
|
const wrappedSources = {};
|
|
1573
1592
|
const invokedSrcs = getConfiguredInvokeSrcs(provided);
|
|
1574
1593
|
for (const [key, logic] of Object.entries(effectiveSources)) {
|
|
1575
1594
|
if (key === "agent.userInput") continue;
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1595
|
+
let binding;
|
|
1596
|
+
if (require_decision.isDecisionLogic(logic)) binding = {
|
|
1597
|
+
executorKey: "decide",
|
|
1598
|
+
kind: "decision",
|
|
1599
|
+
bind: () => bindDecisionForProvide(provided, logic, executors, bindOptions)
|
|
1600
|
+
};
|
|
1601
|
+
else if (require_decision.isTextLogic(logic)) {
|
|
1602
|
+
const streaming = logic.mode === "stream";
|
|
1603
|
+
binding = {
|
|
1604
|
+
executorKey: streaming ? "streamText" : "generateText",
|
|
1605
|
+
kind: streaming ? "streaming text" : "text",
|
|
1606
|
+
bind: () => bindTextForProvide(provided, logic, executors, bindOptions)
|
|
1607
|
+
};
|
|
1584
1608
|
}
|
|
1585
|
-
if (
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
continue;
|
|
1590
|
-
}
|
|
1591
|
-
wrappedSources[key] = bindTextForProvide(provided, logic, executors, bindOptions);
|
|
1609
|
+
if (!binding) continue;
|
|
1610
|
+
if (require_decision.executorBoundLogics.has(logic)) continue;
|
|
1611
|
+
if (!executors[binding.executorKey]) {
|
|
1612
|
+
if (invokedSrcs.has(key)) throw missingExecutorError(key, binding.kind, binding.executorKey);
|
|
1592
1613
|
continue;
|
|
1593
1614
|
}
|
|
1615
|
+
wrappedSources[key] = binding.bind();
|
|
1594
1616
|
}
|
|
1595
|
-
return provided
|
|
1617
|
+
return withActors(provided, wrappedSources);
|
|
1596
1618
|
}
|
|
1597
1619
|
function missingExecutorError(src, kind, executor) {
|
|
1598
1620
|
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.`);
|
|
@@ -1612,10 +1634,8 @@ function buildStateIndex(rootConfig) {
|
|
|
1612
1634
|
const hasChildren = !!config.states && Object.keys(config.states).length > 0;
|
|
1613
1635
|
index.set(path, {
|
|
1614
1636
|
path,
|
|
1615
|
-
name,
|
|
1616
1637
|
config,
|
|
1617
1638
|
parentPath,
|
|
1618
|
-
type: config.type,
|
|
1619
1639
|
isFinal: config.type === "final",
|
|
1620
1640
|
isParallel: config.type === "parallel",
|
|
1621
1641
|
isCompound: hasChildren && config.type !== "parallel",
|
|
@@ -1733,36 +1753,20 @@ function ancestorChain(node, index) {
|
|
|
1733
1753
|
function hasNonEmptyOn(config) {
|
|
1734
1754
|
return !!config.on && Object.keys(config.on).length > 0;
|
|
1735
1755
|
}
|
|
1736
|
-
function
|
|
1737
|
-
if (typeof src === "string")
|
|
1738
|
-
|
|
1739
|
-
return require_decision.isDecisionLogic(actors[src]) ? "decision" : void 0;
|
|
1740
|
-
}
|
|
1741
|
-
return require_decision.isDecisionLogic(src) ? "decision" : void 0;
|
|
1756
|
+
function isDecisionInvoke(src, actors) {
|
|
1757
|
+
if (typeof src === "string") return src === DECIDE_SRC || require_decision.isDecisionLogic(actors[src]);
|
|
1758
|
+
return require_decision.isDecisionLogic(src);
|
|
1742
1759
|
}
|
|
1743
1760
|
function isAgentLogicNeedingBinding(src) {
|
|
1744
1761
|
return (require_decision.isTextLogic(src) || require_decision.isDecisionLogic(src)) && !require_decision.executorBoundLogics.has(src);
|
|
1745
1762
|
}
|
|
1746
|
-
function
|
|
1747
|
-
if (!schema) return
|
|
1748
|
-
try {
|
|
1749
|
-
return require_decision.getJsonSchemaSync(schema) !== void 0;
|
|
1750
|
-
} catch {
|
|
1751
|
-
return false;
|
|
1752
|
-
}
|
|
1753
|
-
}
|
|
1754
|
-
function isDeclaredOutputSchema(schema) {
|
|
1755
|
-
if (!schema) return false;
|
|
1756
|
-
let json;
|
|
1763
|
+
function readJsonSchema(schema) {
|
|
1764
|
+
if (!schema) return;
|
|
1757
1765
|
try {
|
|
1758
|
-
|
|
1766
|
+
return require_decision.getJsonSchemaSync(schema);
|
|
1759
1767
|
} catch {
|
|
1760
|
-
return
|
|
1768
|
+
return;
|
|
1761
1769
|
}
|
|
1762
|
-
if (!json) return false;
|
|
1763
|
-
const properties = json.properties;
|
|
1764
|
-
const required = json.required;
|
|
1765
|
-
return json.type === "object" && !!properties && Object.keys(properties).length > 0 || Array.isArray(required) && required.length > 0;
|
|
1766
1770
|
}
|
|
1767
1771
|
function checkUnreachableStates(ctx) {
|
|
1768
1772
|
const out = [];
|
|
@@ -1777,8 +1781,7 @@ function checkUnreachableStates(ctx) {
|
|
|
1777
1781
|
function checkDecideWithoutEvents(ctx) {
|
|
1778
1782
|
const out = [];
|
|
1779
1783
|
for (const node of ctx.index.values()) for (const invoke of node.invokes) {
|
|
1780
|
-
|
|
1781
|
-
if (!kind) continue;
|
|
1784
|
+
if (!isDecisionInvoke(invoke.src, ctx.actors)) continue;
|
|
1782
1785
|
const selfHandles = hasNonEmptyOn(node.config);
|
|
1783
1786
|
const ancestorHandles = ancestorChain(node, ctx.index).some((ancestor) => hasNonEmptyOn(ancestor.config));
|
|
1784
1787
|
const invokeObserves = invoke.onDone !== void 0;
|
|
@@ -1788,7 +1791,7 @@ function checkDecideWithoutEvents(ctx) {
|
|
|
1788
1791
|
code: "decide-without-events",
|
|
1789
1792
|
severity: "error",
|
|
1790
1793
|
path: node.path,
|
|
1791
|
-
message: `State '${node.path}' invokes
|
|
1794
|
+
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.`
|
|
1792
1795
|
});
|
|
1793
1796
|
}
|
|
1794
1797
|
return out;
|
|
@@ -1796,7 +1799,7 @@ function checkDecideWithoutEvents(ctx) {
|
|
|
1796
1799
|
function checkUnserializableContext(ctx) {
|
|
1797
1800
|
const contextSchema = ctx.schemas?.context;
|
|
1798
1801
|
if (!contextSchema) return [];
|
|
1799
|
-
if (
|
|
1802
|
+
if (readJsonSchema(contextSchema)) return [];
|
|
1800
1803
|
return [{
|
|
1801
1804
|
code: "unserializable-context",
|
|
1802
1805
|
severity: "warning",
|
|
@@ -1820,7 +1823,10 @@ function checkDirectObjectSrc(ctx) {
|
|
|
1820
1823
|
return out;
|
|
1821
1824
|
}
|
|
1822
1825
|
function checkFinalWithoutOutput(ctx) {
|
|
1823
|
-
|
|
1826
|
+
const outputJson = readJsonSchema(ctx.schemas?.output);
|
|
1827
|
+
const properties = outputJson?.properties;
|
|
1828
|
+
const required = outputJson?.required;
|
|
1829
|
+
if (!(outputJson?.type === "object" && !!properties && Object.keys(properties).length > 0 || Array.isArray(required) && required.length > 0)) return [];
|
|
1824
1830
|
if (ctx.config.output !== void 0) return [];
|
|
1825
1831
|
const out = [];
|
|
1826
1832
|
for (const node of ctx.index.values()) {
|
|
@@ -1914,7 +1920,6 @@ function lintAgentMachine(machine, options = {}) {
|
|
|
1914
1920
|
const reachable = computeReachable(config, index, require_decision.getMachineStaticTransitionTargets(machine));
|
|
1915
1921
|
const registered = require_decision.getRegisteredAgentExecutionOptions(machine);
|
|
1916
1922
|
const ctx = {
|
|
1917
|
-
machine,
|
|
1918
1923
|
config,
|
|
1919
1924
|
index,
|
|
1920
1925
|
reachable,
|
|
@@ -1997,7 +2002,7 @@ function takeFromQueue(channel, src) {
|
|
|
1997
2002
|
async function simulateAgent(machine, options) {
|
|
1998
2003
|
const maxSteps = options.maxSteps ?? 100;
|
|
1999
2004
|
const script = {
|
|
2000
|
-
text:
|
|
2005
|
+
text: mapValues(options.script.text ?? {}, (arr) => [...arr]),
|
|
2001
2006
|
decisions: mapValues(options.script.decisions ?? {}, (arr) => [...arr]),
|
|
2002
2007
|
invokes: mapValues(options.script.invokes ?? {}, (arr) => [...arr])
|
|
2003
2008
|
};
|
|
@@ -2069,6 +2074,7 @@ function scriptDryError(kind, src, id, request) {
|
|
|
2069
2074
|
const events = request?.kind === "decision" ? ` Candidate events: ${request.events.map((e) => e.type).join(", ") || "(none)"}.` : "";
|
|
2070
2075
|
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}`);
|
|
2071
2076
|
}
|
|
2077
|
+
const MAX_ADVANCE_STEPS = 1e3;
|
|
2072
2078
|
async function explore(machine, options, stopWhen) {
|
|
2073
2079
|
const maxDepth = options.maxDepth ?? 8;
|
|
2074
2080
|
const maxPaths = options.maxPaths ?? 200;
|
|
@@ -2093,7 +2099,7 @@ async function explore(machine, options, stopWhen) {
|
|
|
2093
2099
|
if (stopWhen?.(initial.snapshot)) witness = [];
|
|
2094
2100
|
const advance = (step) => {
|
|
2095
2101
|
let current = step;
|
|
2096
|
-
for (let i = 0; i <
|
|
2102
|
+
for (let i = 0; i < MAX_ADVANCE_STEPS; i++) {
|
|
2097
2103
|
if (current.done) return { step: current };
|
|
2098
2104
|
const request = current.requests[0];
|
|
2099
2105
|
if (request && request.kind === "text") {
|
|
@@ -2245,19 +2251,22 @@ async function canReach(machine, statePath, options = {}) {
|
|
|
2245
2251
|
} : { canReach: false };
|
|
2246
2252
|
}
|
|
2247
2253
|
//#endregion
|
|
2248
|
-
//#region src/
|
|
2249
|
-
|
|
2254
|
+
//#region src/internal/is-record.ts
|
|
2255
|
+
/** True for a plain object-like value (arrays excluded). @internal */
|
|
2256
|
+
function isRecord(value) {
|
|
2250
2257
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2251
2258
|
}
|
|
2259
|
+
//#endregion
|
|
2260
|
+
//#region src/trajectory.ts
|
|
2252
2261
|
/**
|
|
2253
2262
|
* Classifies one item. Event-shaped things (log entries, events) become events;
|
|
2254
2263
|
* everything else is a state value. A snapshot is unwrapped to its `value`.
|
|
2255
2264
|
* @internal
|
|
2256
2265
|
*/
|
|
2257
2266
|
function normalize(item) {
|
|
2258
|
-
if (isRecord
|
|
2267
|
+
if (isRecord(item)) {
|
|
2259
2268
|
const nested = item["event"];
|
|
2260
|
-
if (isRecord
|
|
2269
|
+
if (isRecord(nested) && typeof nested["type"] === "string") return {
|
|
2261
2270
|
kind: "event",
|
|
2262
2271
|
event: nested
|
|
2263
2272
|
};
|
|
@@ -2278,7 +2287,7 @@ function normalize(item) {
|
|
|
2278
2287
|
/** Every leaf of a state value as a dot path (`{ review: 'editing' }` → `review.editing`). @internal */
|
|
2279
2288
|
function statePaths(value) {
|
|
2280
2289
|
if (typeof value === "string") return [value];
|
|
2281
|
-
if (!isRecord
|
|
2290
|
+
if (!isRecord(value)) return [];
|
|
2282
2291
|
return Object.entries(value).flatMap(([key, child]) => statePaths(child).map((path) => `${key}.${path}`));
|
|
2283
2292
|
}
|
|
2284
2293
|
/**
|
|
@@ -2291,12 +2300,13 @@ function stateMatchesPath(path, value) {
|
|
|
2291
2300
|
function deepEqual(a, b) {
|
|
2292
2301
|
if (Object.is(a, b)) return true;
|
|
2293
2302
|
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]));
|
|
2294
|
-
if (!isRecord
|
|
2303
|
+
if (!isRecord(a) || !isRecord(b)) return false;
|
|
2295
2304
|
const keys = Object.keys(a);
|
|
2296
2305
|
return keys.length === Object.keys(b).length && keys.every((key) => deepEqual(a[key], b[key]));
|
|
2297
2306
|
}
|
|
2298
2307
|
/**
|
|
2299
|
-
*
|
|
2308
|
+
* Normalizes ONE expected item into a predicate over actual items, so the
|
|
2309
|
+
* expectation is classified once instead of once per candidate:
|
|
2300
2310
|
*
|
|
2301
2311
|
* - A string expectation matches an event's `type`, or a state value it names.
|
|
2302
2312
|
* - An event expectation matches an event with the same `type` whose other
|
|
@@ -2304,13 +2314,22 @@ function deepEqual(a, b) {
|
|
|
2304
2314
|
* - A state-value expectation matches structurally.
|
|
2305
2315
|
* @internal
|
|
2306
2316
|
*/
|
|
2307
|
-
function
|
|
2308
|
-
|
|
2309
|
-
|
|
2317
|
+
function matcherFor(expected) {
|
|
2318
|
+
if (typeof expected === "string") return (actual) => {
|
|
2319
|
+
const actualItem = normalize(actual);
|
|
2320
|
+
return actualItem.kind === "event" ? actualItem.event.type === expected : stateMatchesPath(expected, actualItem.value);
|
|
2321
|
+
};
|
|
2310
2322
|
const expectedItem = normalize(expected);
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2323
|
+
switch (expectedItem.kind) {
|
|
2324
|
+
case "event": return (actual) => {
|
|
2325
|
+
const actualItem = normalize(actual);
|
|
2326
|
+
return actualItem.kind === "event" && Object.keys(expectedItem.event).every((key) => deepEqual(expectedItem.event[key], actualItem.event[key]));
|
|
2327
|
+
};
|
|
2328
|
+
case "state": return (actual) => {
|
|
2329
|
+
const actualItem = normalize(actual);
|
|
2330
|
+
return actualItem.kind === "state" && deepEqual(expectedItem.value, actualItem.value);
|
|
2331
|
+
};
|
|
2332
|
+
}
|
|
2314
2333
|
}
|
|
2315
2334
|
/**
|
|
2316
2335
|
* Matches a run's trajectory against an expected one.
|
|
@@ -2347,59 +2366,41 @@ function matchesItem(expected, actual) {
|
|
|
2347
2366
|
*/
|
|
2348
2367
|
function matchesTrajectory(actual, expected, options = {}) {
|
|
2349
2368
|
const expectedCount = expected.length;
|
|
2350
|
-
|
|
2351
|
-
let matchedCount = 0;
|
|
2352
|
-
let firstMiss;
|
|
2353
|
-
for (let index = 0; index < expectedCount; index++) {
|
|
2354
|
-
if (index < actual.length && matchesItem(expected[index], actual[index])) {
|
|
2355
|
-
matchedCount++;
|
|
2356
|
-
continue;
|
|
2357
|
-
}
|
|
2358
|
-
firstMiss = {
|
|
2359
|
-
index,
|
|
2360
|
-
expected: expected[index],
|
|
2361
|
-
searchedFrom: index
|
|
2362
|
-
};
|
|
2363
|
-
break;
|
|
2364
|
-
}
|
|
2365
|
-
const matched = !firstMiss && actual.length === expectedCount;
|
|
2366
|
-
const denominator = Math.max(expectedCount, actual.length);
|
|
2367
|
-
return {
|
|
2368
|
-
matched,
|
|
2369
|
-
matchedCount,
|
|
2370
|
-
expectedCount,
|
|
2371
|
-
score: matched ? 1 : denominator === 0 ? 1 : matchedCount / denominator,
|
|
2372
|
-
...firstMiss ? { firstMiss } : {}
|
|
2373
|
-
};
|
|
2374
|
-
}
|
|
2369
|
+
const exact = options.exact === true;
|
|
2375
2370
|
let cursor = 0;
|
|
2376
2371
|
let matchedCount = 0;
|
|
2372
|
+
let firstMiss;
|
|
2377
2373
|
for (let index = 0; index < expectedCount; index++) {
|
|
2378
2374
|
const want = expected[index];
|
|
2375
|
+
const matches = matcherFor(want);
|
|
2376
|
+
const searchedFrom = exact ? index : cursor;
|
|
2379
2377
|
let found = -1;
|
|
2380
|
-
for (let at =
|
|
2381
|
-
|
|
2382
|
-
|
|
2378
|
+
for (let at = searchedFrom; at < actual.length; at++) {
|
|
2379
|
+
if (matches(actual[at])) {
|
|
2380
|
+
found = at;
|
|
2381
|
+
break;
|
|
2382
|
+
}
|
|
2383
|
+
if (exact) break;
|
|
2383
2384
|
}
|
|
2384
|
-
if (found === -1)
|
|
2385
|
-
|
|
2386
|
-
matchedCount,
|
|
2387
|
-
expectedCount,
|
|
2388
|
-
score: matchedCount / expectedCount,
|
|
2389
|
-
firstMiss: {
|
|
2385
|
+
if (found === -1) {
|
|
2386
|
+
firstMiss = {
|
|
2390
2387
|
index,
|
|
2391
2388
|
expected: want,
|
|
2392
|
-
searchedFrom
|
|
2393
|
-
}
|
|
2394
|
-
|
|
2389
|
+
searchedFrom
|
|
2390
|
+
};
|
|
2391
|
+
break;
|
|
2392
|
+
}
|
|
2395
2393
|
matchedCount++;
|
|
2396
2394
|
cursor = found + 1;
|
|
2397
2395
|
}
|
|
2396
|
+
const matched = !firstMiss && (!exact || actual.length === expectedCount);
|
|
2397
|
+
const denominator = exact ? Math.max(expectedCount, actual.length) : expectedCount;
|
|
2398
2398
|
return {
|
|
2399
|
-
matched
|
|
2399
|
+
matched,
|
|
2400
2400
|
matchedCount,
|
|
2401
2401
|
expectedCount,
|
|
2402
|
-
score: 1
|
|
2402
|
+
score: matched ? 1 : denominator === 0 ? 1 : matchedCount / denominator,
|
|
2403
|
+
...firstMiss ? { firstMiss } : {}
|
|
2403
2404
|
};
|
|
2404
2405
|
}
|
|
2405
2406
|
//#endregion
|
|
@@ -2412,17 +2413,9 @@ function matchesTrajectory(actual, expected, options = {}) {
|
|
|
2412
2413
|
* `provideExecutors`, or a bare `TextLogic.execute`) runs with no API key and no
|
|
2413
2414
|
* network. It is the fastest way to see a machine run, and the least ceremonial
|
|
2414
2415
|
* way to test one: same machine, same executor contract, scripted answers.
|
|
2416
|
+
*
|
|
2417
|
+
* @module
|
|
2415
2418
|
*/
|
|
2416
|
-
/** Thrown when a scripted queue runs dry on a pending request. */
|
|
2417
|
-
var ScriptedExecutorsError = class extends require_errors.AgentError {
|
|
2418
|
-
constructor(message) {
|
|
2419
|
-
super("scripted-executors-exhausted", message);
|
|
2420
|
-
this.name = "ScriptedExecutorsError";
|
|
2421
|
-
}
|
|
2422
|
-
};
|
|
2423
|
-
function isRecord$1(value) {
|
|
2424
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2425
|
-
}
|
|
2426
2419
|
/** The only own keys an executor-result envelope carries. @internal */
|
|
2427
2420
|
const TEXT_ENVELOPE_KEYS = new Set([
|
|
2428
2421
|
"output",
|
|
@@ -2447,7 +2440,22 @@ function isTextEnvelope(value) {
|
|
|
2447
2440
|
*/
|
|
2448
2441
|
async function resolveScriptedTextEntry(entry, request, info) {
|
|
2449
2442
|
const value = typeof entry === "function" ? await entry(request, info) : entry;
|
|
2450
|
-
return isRecord
|
|
2443
|
+
return isRecord(value) && isTextEnvelope(value) ? value : { output: value };
|
|
2444
|
+
}
|
|
2445
|
+
/**
|
|
2446
|
+
* Names a pending text request in an error message. Shared with `runSeam`.
|
|
2447
|
+
* @internal
|
|
2448
|
+
*/
|
|
2449
|
+
function describeText(request) {
|
|
2450
|
+
return request.name ? `'${request.name}' (model '${request.model}')` : `(model '${request.model}')`;
|
|
2451
|
+
}
|
|
2452
|
+
/**
|
|
2453
|
+
* Stream semantics with no model: the whole text lands as one chunk. Shared
|
|
2454
|
+
* with `runSeam`, whose scripted answers stream the same way. @internal
|
|
2455
|
+
*/
|
|
2456
|
+
function emitScriptedChunk(result, info) {
|
|
2457
|
+
const output = isRecord(result) ? result["output"] : void 0;
|
|
2458
|
+
if (typeof output === "string") info?.onChunk?.(output);
|
|
2451
2459
|
}
|
|
2452
2460
|
/**
|
|
2453
2461
|
* Keyless executors that replay a script instead of calling a model. Every
|
|
@@ -2482,45 +2490,26 @@ function createScriptedExecutors(script = {}) {
|
|
|
2482
2490
|
const decisions = [...script.decisions ?? []];
|
|
2483
2491
|
const text = [...script.text ?? []];
|
|
2484
2492
|
const nextText = async (request, info) => {
|
|
2485
|
-
if (text.length === 0) throw new
|
|
2493
|
+
if (text.length === 0) throw new require_errors.AgentError("scripted-executors-exhausted", `createScriptedExecutors: script ran dry on a pending text request ${describeText(request)}. Add another entry to the script's \`text\` queue.`);
|
|
2486
2494
|
return resolveScriptedTextEntry(text.shift(), request, info);
|
|
2487
2495
|
};
|
|
2488
2496
|
return {
|
|
2489
2497
|
generateText: nextText,
|
|
2490
2498
|
streamText: async (request, info) => {
|
|
2491
2499
|
const result = await nextText(request, info);
|
|
2492
|
-
|
|
2500
|
+
emitScriptedChunk(result, info);
|
|
2493
2501
|
return result;
|
|
2494
2502
|
},
|
|
2495
2503
|
decide: async (request) => {
|
|
2496
|
-
if (decisions.length === 0) throw new
|
|
2504
|
+
if (decisions.length === 0) throw new require_errors.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)"}.`);
|
|
2497
2505
|
const entry = decisions.shift();
|
|
2498
2506
|
const value = typeof entry === "function" ? await entry(request) : entry;
|
|
2499
|
-
return typeof value
|
|
2507
|
+
return isRecord(value) && typeof value["type"] !== "string" && "event" in value ? value : { event: value };
|
|
2500
2508
|
}
|
|
2501
2509
|
};
|
|
2502
2510
|
}
|
|
2503
|
-
function describeText(request) {
|
|
2504
|
-
return request.name ? `'${request.name}'` : `(model '${request.model}')`;
|
|
2505
|
-
}
|
|
2506
2511
|
//#endregion
|
|
2507
2512
|
//#region src/seam.ts
|
|
2508
|
-
/** Thrown when the call plan runs dry on a request that is not the live seam. */
|
|
2509
|
-
var SeamScriptError = class extends require_errors.AgentError {
|
|
2510
|
-
constructor(message) {
|
|
2511
|
-
super("seam-script-exhausted", message);
|
|
2512
|
-
this.name = "SeamScriptError";
|
|
2513
|
-
}
|
|
2514
|
-
};
|
|
2515
|
-
function seamKeyOf(seam) {
|
|
2516
|
-
return seam.request ?? seam.model;
|
|
2517
|
-
}
|
|
2518
|
-
function describeRequest(request) {
|
|
2519
|
-
return request.name ? `'${request.name}' (model '${request.model}')` : `(model '${request.model}')`;
|
|
2520
|
-
}
|
|
2521
|
-
function isRecord(value) {
|
|
2522
|
-
return typeof value === "object" && value !== null;
|
|
2523
|
-
}
|
|
2524
2513
|
/**
|
|
2525
2514
|
* The seam's own answer, for scoring. Our `{ output }` envelope is read
|
|
2526
2515
|
* directly; a raw AI SDK `generateText` result contributes its `text`. A raw
|
|
@@ -2575,40 +2564,50 @@ async function runSeam(machine, options) {
|
|
|
2575
2564
|
let seamStateAt = 0;
|
|
2576
2565
|
let seamEventAt = 0;
|
|
2577
2566
|
let liveEvents = 0;
|
|
2578
|
-
const
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2567
|
+
const queueKeyOf = (request) => request.name !== void 0 && queues.has(request.name) ? request.name : request.model;
|
|
2568
|
+
/**
|
|
2569
|
+
* Consumes this request's slot in the call plan, or resolves `undefined` when
|
|
2570
|
+
* its queue is dry. The LAST entry repeats: a live seam that branches further
|
|
2571
|
+
* still finds an answer instead of running dry.
|
|
2572
|
+
*/
|
|
2573
|
+
const takeScriptedSlot = async (request, info) => {
|
|
2574
|
+
const queue = queues.get(queueKeyOf(request));
|
|
2575
|
+
if (!queue?.length) return;
|
|
2585
2576
|
return resolveScriptedTextEntry(queue.length === 1 ? queue[0] : queue.shift(), request, info);
|
|
2586
2577
|
};
|
|
2578
|
+
const scriptedAnswer = async (request, info) => {
|
|
2579
|
+
const scripted = await takeScriptedSlot(request, info);
|
|
2580
|
+
if (!scripted) throw new require_errors.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.`);
|
|
2581
|
+
return scripted;
|
|
2582
|
+
};
|
|
2587
2583
|
const route = async (request, info) => {
|
|
2588
2584
|
const callIndex = calls++;
|
|
2589
2585
|
const isSeam = (seam.request !== void 0 ? request.name === seam.request : request.model === seam.model) && seamMatches++ === (seam.occurrence ?? 0);
|
|
2590
|
-
|
|
2586
|
+
if (isSeam && candidate) {
|
|
2587
|
+
await takeScriptedSlot(request, info);
|
|
2588
|
+
seamReached = true;
|
|
2589
|
+
callsBeforeSeam = callIndex;
|
|
2590
|
+
seamStateAt = statePath.length;
|
|
2591
|
+
seamEventAt = liveEvents;
|
|
2592
|
+
const result = await candidate(request, info);
|
|
2593
|
+
seamOutput = await seamOutputOf(result, request);
|
|
2594
|
+
return result;
|
|
2595
|
+
}
|
|
2596
|
+
const scripted = await scriptedAnswer(request, info);
|
|
2591
2597
|
if (!isSeam) return scripted;
|
|
2592
2598
|
seamReached = true;
|
|
2593
2599
|
callsBeforeSeam = callIndex;
|
|
2594
2600
|
seamStateAt = statePath.length;
|
|
2595
2601
|
seamEventAt = liveEvents;
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
seamOutput = scripted.output;
|
|
2599
|
-
return scripted;
|
|
2600
|
-
}
|
|
2601
|
-
const result = await candidate(request, info);
|
|
2602
|
-
seamOutput = await seamOutputOf(result, request);
|
|
2603
|
-
return result;
|
|
2602
|
+
seamOutput = scripted.output;
|
|
2603
|
+
return scripted;
|
|
2604
2604
|
};
|
|
2605
2605
|
const executors = {
|
|
2606
2606
|
...options.executors,
|
|
2607
2607
|
generateText: route,
|
|
2608
2608
|
streamText: async (request, info) => {
|
|
2609
2609
|
const result = await route(request, info);
|
|
2610
|
-
|
|
2611
|
-
if (typeof output === "string") info?.onChunk?.(output);
|
|
2610
|
+
emitScriptedChunk(result, info);
|
|
2612
2611
|
return result;
|
|
2613
2612
|
}
|
|
2614
2613
|
};
|
|
@@ -2712,7 +2711,7 @@ exports.getAcceptedEvents = require_decision.getAcceptedEvents;
|
|
|
2712
2711
|
exports.getAgentEffects = require_setup_agent.getAgentEffects;
|
|
2713
2712
|
exports.getAgentMessages = require_decision.getAgentMessages;
|
|
2714
2713
|
exports.getAgentOutputMode = require_decision.getAgentOutputMode;
|
|
2715
|
-
exports.getCallUsage =
|
|
2714
|
+
exports.getCallUsage = require_decision.getCallUsage;
|
|
2716
2715
|
exports.getJsonSchema = require_decision.getJsonSchema;
|
|
2717
2716
|
exports.getJsonSchemaSync = require_decision.getJsonSchemaSync;
|
|
2718
2717
|
exports.getMachineStructuralHash = require_decision.getMachineStructuralHash;
|