@statelyai/agent 2.0.0-alpha.15 → 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 +498 -520
- package/dist/index.d.cts +109 -104
- package/dist/index.d.mts +109 -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-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-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 +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,7 @@ 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));
|
|
768
798
|
}
|
|
769
799
|
/**
|
|
770
800
|
* Validates `input` against the machine's registered input schema, returning
|
|
@@ -807,11 +837,11 @@ function rebindChildMachine(childMachine, runCtx, visited) {
|
|
|
807
837
|
const wrapped = {};
|
|
808
838
|
for (const [key, logic] of Object.entries(sources)) {
|
|
809
839
|
if (require_decision.isDecisionLogic(logic)) {
|
|
810
|
-
if (!require_decision.executorBoundLogics.has(logic)) wrapped[key] =
|
|
840
|
+
if (!require_decision.executorBoundLogics.has(logic)) wrapped[key] = bindDecisionLogic(logic, runCtx);
|
|
811
841
|
continue;
|
|
812
842
|
}
|
|
813
843
|
if (require_decision.isTextLogic(logic)) {
|
|
814
|
-
if (!require_decision.executorBoundLogics.has(logic)) wrapped[key] =
|
|
844
|
+
if (!require_decision.executorBoundLogics.has(logic)) wrapped[key] = bindTextLogic(logic, runCtx);
|
|
815
845
|
continue;
|
|
816
846
|
}
|
|
817
847
|
if (isStateMachine(logic)) {
|
|
@@ -903,20 +933,22 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
903
933
|
machineId,
|
|
904
934
|
version: machineVersion
|
|
905
935
|
};
|
|
906
|
-
const
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
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
|
+
});
|
|
920
952
|
const consumeModelCall = () => {
|
|
921
953
|
if (budgetExceeded) throw new AgentMaxModelCallsExceededError();
|
|
922
954
|
if (modelCallCount + 1 > maxModelCalls) {
|
|
@@ -927,33 +959,20 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
927
959
|
};
|
|
928
960
|
const tokenTotals = {};
|
|
929
961
|
const cycleGate = { isResolved: () => false };
|
|
930
|
-
const deliverUsageEvent = (usage, source) => {
|
|
931
|
-
const actorRef = actorHolder.actorRef;
|
|
932
|
-
if (!actorRef) return;
|
|
933
|
-
const event = {
|
|
934
|
-
type: require_setup_agent.AGENT_USAGE_EVENT_TYPE,
|
|
935
|
-
...source,
|
|
936
|
-
usage
|
|
937
|
-
};
|
|
938
|
-
const snapshot = actorRef.getSnapshot();
|
|
939
|
-
if (snapshot?.status !== "active" || !declaresUsageTransition(snapshot)) return;
|
|
940
|
-
if (cycleGate.isResolved()) {
|
|
941
|
-
onTrace({
|
|
942
|
-
type: "usage.dropped",
|
|
943
|
-
event,
|
|
944
|
-
reason: "settled"
|
|
945
|
-
});
|
|
946
|
-
return;
|
|
947
|
-
}
|
|
948
|
-
if (!snapshot.can(event)) return;
|
|
949
|
-
actorRef.send(event);
|
|
950
|
-
};
|
|
951
962
|
const recordUsage = (usage, source = {}) => {
|
|
952
963
|
for (const field of require_decision.AGENT_USAGE_TOKEN_FIELDS) {
|
|
953
964
|
const value = usage[field];
|
|
954
965
|
if (typeof value === "number" && Number.isFinite(value)) tokenTotals[field] = (tokenTotals[field] ?? 0) + value;
|
|
955
966
|
}
|
|
956
|
-
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
|
+
});
|
|
957
976
|
};
|
|
958
977
|
const runUsage = () => ({
|
|
959
978
|
...tokenTotals,
|
|
@@ -973,20 +992,14 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
973
992
|
};
|
|
974
993
|
const provided = machine.provide({ actors: options.actors });
|
|
975
994
|
const effectiveSources = provided.sources.actors;
|
|
976
|
-
assertBindable(provided, effectiveSources, {
|
|
977
|
-
hasGenerateText: !!options.executors?.generateText,
|
|
978
|
-
hasDecide: !!options.executors?.decide,
|
|
979
|
-
hasStreamText: !!options.executors?.streamText
|
|
980
|
-
});
|
|
995
|
+
assertBindable(provided, effectiveSources, options.executors ?? {});
|
|
981
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.");
|
|
982
997
|
const actorHolder = { actorRef: void 0 };
|
|
983
998
|
const runCtx = {
|
|
984
999
|
generateText: options.executors?.generateText,
|
|
985
1000
|
streamText: options.executors?.streamText,
|
|
986
1001
|
decide: options.executors?.decide,
|
|
987
|
-
|
|
988
|
-
onResult: options.onResult,
|
|
989
|
-
onTrace,
|
|
1002
|
+
emitTrace: onTrace,
|
|
990
1003
|
consumeModelCall,
|
|
991
1004
|
recordUsage,
|
|
992
1005
|
actorHolder,
|
|
@@ -1007,11 +1020,11 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1007
1020
|
continue;
|
|
1008
1021
|
}
|
|
1009
1022
|
if (require_decision.isDecisionLogic(logic)) {
|
|
1010
|
-
wrappedSources[key] =
|
|
1023
|
+
wrappedSources[key] = bindDecisionLogic(logic, runCtx);
|
|
1011
1024
|
continue;
|
|
1012
1025
|
}
|
|
1013
1026
|
if (require_decision.isTextLogic(logic)) {
|
|
1014
|
-
if (!require_decision.executorBoundLogics.has(logic)) wrappedSources[key] =
|
|
1027
|
+
if (!require_decision.executorBoundLogics.has(logic)) wrappedSources[key] = bindTextLogic(logic, runCtx);
|
|
1015
1028
|
continue;
|
|
1016
1029
|
}
|
|
1017
1030
|
if (isStateMachine(logic)) {
|
|
@@ -1053,9 +1066,11 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1053
1066
|
}
|
|
1054
1067
|
const priorMessages = require_decision.getAgentMessages(effectiveSnapshot);
|
|
1055
1068
|
const messages = typeof options.messages === "function" ? [...options.messages([...priorMessages])] : [...priorMessages, ...options.messages ?? []];
|
|
1056
|
-
const
|
|
1069
|
+
const stampSettledSnapshot = (snapshot) => {
|
|
1070
|
+
if (!snapshot || typeof snapshot !== "object") return;
|
|
1071
|
+
snapshot.agentMeta = agentMeta;
|
|
1057
1072
|
if (!options.getRequests && !options.messages && messages.length === 0) return;
|
|
1058
|
-
|
|
1073
|
+
snapshot.messages = [...messages];
|
|
1059
1074
|
};
|
|
1060
1075
|
if (effectiveSnapshot !== void 0 && options.event !== void 0 && (options.onIllegalResumeEvent ?? "throw") === "throw") {
|
|
1061
1076
|
const acceptedTypes = require_decision.getAcceptedEvents((0, xstate.createActor)(boundMachine, { snapshot: effectiveSnapshot }).getSnapshot(), { schemas: runCtx.schemas }).map((descriptor) => descriptor.type);
|
|
@@ -1063,21 +1078,11 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1063
1078
|
if (!acceptedTypes.includes(eventType)) throw new AgentIllegalResumeEventError(eventType, acceptedTypes);
|
|
1064
1079
|
}
|
|
1065
1080
|
const replayEvents = [...resumeEvents ?? []];
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
if (entry.machineId !== machineId || entry.machineVersion !== machineVersion) throw new require_setup_agent.AgentReplayMachineMismatchError(entry.id, entry.index, {
|
|
1072
|
-
machineId,
|
|
1073
|
-
machineVersion
|
|
1074
|
-
}, {
|
|
1075
|
-
machineId: entry.machineId,
|
|
1076
|
-
machineVersion: entry.machineVersion
|
|
1077
|
-
});
|
|
1078
|
-
if (replayEventIds.has(entry.id)) throw new Error(`runAgent events contain duplicate event id '${entry.id}'.`);
|
|
1079
|
-
replayEventIds.add(entry.id);
|
|
1080
|
-
}
|
|
1081
|
+
require_setup_agent.validateReplayEntries(replayEvents, {
|
|
1082
|
+
machineId,
|
|
1083
|
+
machineVersion
|
|
1084
|
+
}, "runAgent events");
|
|
1085
|
+
const replayEventIds = new Set(replayEvents.map((entry) => entry.id));
|
|
1081
1086
|
const hasCompleteReplayHistory = replayEvents[0]?.event.type === "@agent.init" || effectiveSnapshot === void 0 && replayEvents.length === 0;
|
|
1082
1087
|
const appendReplayEvent = (event) => {
|
|
1083
1088
|
const entry = require_setup_agent.createReplayEntry(machine, replayEvents, event, {
|
|
@@ -1096,247 +1101,239 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1096
1101
|
replayEvents.push(entry);
|
|
1097
1102
|
options.onEvent?.(entry);
|
|
1098
1103
|
}
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
usage: runUsage()
|
|
1115
|
-
};
|
|
1116
|
-
if (idleTimer !== void 0) clearTimeout(idleTimer);
|
|
1117
|
-
stampAgentMeta(result.snapshot);
|
|
1118
|
-
stampMessages(result.snapshot);
|
|
1119
|
-
if ("persistedSnapshot" in result) {
|
|
1120
|
-
stampAgentMeta(result.persistedSnapshot);
|
|
1121
|
-
stampMessages(result.persistedSnapshot);
|
|
1122
|
-
}
|
|
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);
|
|
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()
|
|
1134
1119
|
};
|
|
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
|
-
|
|
1211
|
-
|
|
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);
|
|
1212
1198
|
return true;
|
|
1213
|
-
}
|
|
1214
|
-
const
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
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').");
|
|
1227
1224
|
}
|
|
1225
|
+
settleIdle(current);
|
|
1228
1226
|
}
|
|
1229
|
-
}, 0);
|
|
1230
|
-
};
|
|
1231
|
-
actor = (0, xstate.createActor)(boundMachine, {
|
|
1232
|
-
input: resolvedInput,
|
|
1233
|
-
snapshot: effectiveSnapshot,
|
|
1234
|
-
inspect: (event) => {
|
|
1235
|
-
if (typeof options.inspect === "function") options.inspect(event);
|
|
1236
|
-
else options.inspect?.next?.(event);
|
|
1237
|
-
if (event.type !== "@xstate.transition" || event.actorRef !== actor.ref) return;
|
|
1238
|
-
if (settled) {
|
|
1239
|
-
if (finalized) return;
|
|
1240
|
-
settled = false;
|
|
1241
|
-
lastResult = void 0;
|
|
1242
|
-
}
|
|
1243
|
-
const snapshot = event.snapshot;
|
|
1244
|
-
let eventId;
|
|
1245
|
-
if (event.event.type !== "@xstate.init" && (event.sourceRef !== event.actorRef || event.event.type === "xstate.timer")) eventId = appendReplayEvent(event.event).id;
|
|
1246
|
-
else if (event.event.type === "@xstate.init") eventId = replayEvents[0]?.event.type === "@agent.init" ? replayEvents[0].id : void 0;
|
|
1247
|
-
onTrace({
|
|
1248
|
-
type: "machine.transition",
|
|
1249
|
-
snapshot,
|
|
1250
|
-
event: event.event,
|
|
1251
|
-
...eventId !== void 0 ? { eventId } : {}
|
|
1252
|
-
});
|
|
1253
|
-
options.onTransition?.(snapshot, event.event);
|
|
1254
|
-
if (snapshot.status === "done") {
|
|
1255
|
-
settle({
|
|
1256
|
-
status: "done",
|
|
1257
|
-
output: snapshot.output,
|
|
1258
|
-
snapshot
|
|
1259
|
-
});
|
|
1260
|
-
return;
|
|
1261
|
-
}
|
|
1262
|
-
if (snapshot.status === "error") {
|
|
1263
|
-
settle({
|
|
1264
|
-
status: "error",
|
|
1265
|
-
cause: runErrorCause(snapshot.error),
|
|
1266
|
-
error: snapshot.error,
|
|
1267
|
-
snapshot
|
|
1268
|
-
});
|
|
1269
|
-
return;
|
|
1270
|
-
}
|
|
1271
|
-
if (snapshot.status === "stopped") {
|
|
1272
|
-
settle({
|
|
1273
|
-
status: "error",
|
|
1274
|
-
cause: "stopped",
|
|
1275
|
-
error: /* @__PURE__ */ new Error("Actor stopped externally."),
|
|
1276
|
-
snapshot
|
|
1277
|
-
});
|
|
1278
|
-
return;
|
|
1279
|
-
}
|
|
1280
|
-
if (!deliveringResumeEvent && isSuspended(snapshot) && isIdleSnapshot(snapshot, { ignoreUserInputChildren: userInputIsPlaceholder })) {
|
|
1281
|
-
queueMicrotask(() => {
|
|
1282
|
-
if (settled) return;
|
|
1283
|
-
const current = actor.getSnapshot();
|
|
1284
|
-
if (isSuspended(current) && isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) {
|
|
1285
|
-
if (!maybeInterpret(current)) settleIdle(current);
|
|
1286
|
-
} else scheduleIdleCheck();
|
|
1287
|
-
});
|
|
1288
|
-
return;
|
|
1289
|
-
}
|
|
1290
|
-
scheduleIdleCheck();
|
|
1291
1227
|
}
|
|
1292
|
-
});
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
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;
|
|
1296
1246
|
onTrace({
|
|
1297
|
-
type: "
|
|
1298
|
-
|
|
1247
|
+
type: "machine.transition",
|
|
1248
|
+
snapshot,
|
|
1249
|
+
event: event.event,
|
|
1250
|
+
...eventId !== void 0 ? { eventId } : {}
|
|
1299
1251
|
});
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
usage: runUsage,
|
|
1308
|
-
settled: () => settled && lastResult !== void 0 ? Promise.resolve(lastResult) : new Promise((resolve) => {
|
|
1309
|
-
waiters.push(resolve);
|
|
1310
|
-
}),
|
|
1311
|
-
stop: () => {
|
|
1312
|
-
actor.stop();
|
|
1252
|
+
if (snapshot.status === "done") {
|
|
1253
|
+
settle({
|
|
1254
|
+
status: "done",
|
|
1255
|
+
output: snapshot.output,
|
|
1256
|
+
snapshot
|
|
1257
|
+
});
|
|
1258
|
+
return;
|
|
1313
1259
|
}
|
|
1314
|
-
|
|
1315
|
-
if (options.signal) {
|
|
1316
|
-
if (options.signal.aborted) {
|
|
1260
|
+
if (snapshot.status === "error") {
|
|
1317
1261
|
settle({
|
|
1318
1262
|
status: "error",
|
|
1319
|
-
cause:
|
|
1320
|
-
error:
|
|
1321
|
-
snapshot
|
|
1263
|
+
cause: runErrorCause(snapshot.error),
|
|
1264
|
+
error: snapshot.error,
|
|
1265
|
+
snapshot
|
|
1266
|
+
});
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
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();
|
|
1322
1285
|
});
|
|
1323
|
-
return
|
|
1286
|
+
return;
|
|
1324
1287
|
}
|
|
1325
|
-
|
|
1288
|
+
scheduleIdleCheck();
|
|
1326
1289
|
}
|
|
1290
|
+
});
|
|
1291
|
+
actorHolder.actorRef = actor;
|
|
1292
|
+
actor.subscribe({ error: () => {} });
|
|
1293
|
+
actor.on("*", (event) => {
|
|
1327
1294
|
onTrace({
|
|
1328
|
-
type: "
|
|
1329
|
-
|
|
1330
|
-
...effectiveSnapshot !== void 0 ? { snapshot: effectiveSnapshot } : {},
|
|
1331
|
-
...options.event !== void 0 ? { event: options.event } : {}
|
|
1295
|
+
type: "emit",
|
|
1296
|
+
event
|
|
1332
1297
|
});
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
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;
|
|
1337
1322
|
}
|
|
1338
|
-
|
|
1339
|
-
}
|
|
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;
|
|
1340
1337
|
}
|
|
1341
1338
|
/**
|
|
1342
1339
|
* Runs an agent machine to a **final state**, for run-to-done flows where an
|
|
@@ -1492,14 +1489,17 @@ function createAgentRun(machine, options) {
|
|
|
1492
1489
|
});
|
|
1493
1490
|
else buffer.push(event);
|
|
1494
1491
|
};
|
|
1495
|
-
const
|
|
1496
|
-
if (closed) return;
|
|
1497
|
-
closed = true;
|
|
1492
|
+
const drain = () => {
|
|
1498
1493
|
while (resolvers.length > 0) resolvers.shift()({
|
|
1499
1494
|
value: void 0,
|
|
1500
1495
|
done: true
|
|
1501
1496
|
});
|
|
1502
1497
|
};
|
|
1498
|
+
const close = () => {
|
|
1499
|
+
if (closed) return;
|
|
1500
|
+
closed = true;
|
|
1501
|
+
drain();
|
|
1502
|
+
};
|
|
1503
1503
|
const userOnTrace = options.onTrace;
|
|
1504
1504
|
const result = runAgent(machine, {
|
|
1505
1505
|
...options,
|
|
@@ -1528,10 +1528,7 @@ function createAgentRun(machine, options) {
|
|
|
1528
1528
|
return() {
|
|
1529
1529
|
stopped = true;
|
|
1530
1530
|
buffer.length = 0;
|
|
1531
|
-
|
|
1532
|
-
value: void 0,
|
|
1533
|
-
done: true
|
|
1534
|
-
});
|
|
1531
|
+
drain();
|
|
1535
1532
|
return Promise.resolve({
|
|
1536
1533
|
value: void 0,
|
|
1537
1534
|
done: true
|
|
@@ -1579,7 +1576,7 @@ function createAgentRun(machine, options) {
|
|
|
1579
1576
|
* Throws at bind time if a source needs an executor kind that `executors` does
|
|
1580
1577
|
* not provide.
|
|
1581
1578
|
*
|
|
1582
|
-
*
|
|
1579
|
+
* `provideExecutors` does not descend into invoked child state machines: a string-keyed child
|
|
1583
1580
|
* machine source is left untouched, so a child with its own agent invokes needs
|
|
1584
1581
|
* its own `provideExecutors(...)` (or `runAgent`, which does rebind children).
|
|
1585
1582
|
*/
|
|
@@ -1588,32 +1585,36 @@ function provideExecutors(machine, executors, options = {}) {
|
|
|
1588
1585
|
onChunk: options.onChunk,
|
|
1589
1586
|
onTrace: options.onTrace
|
|
1590
1587
|
};
|
|
1591
|
-
const
|
|
1588
|
+
const withActors = (target, actors) => target.provide({ actors });
|
|
1589
|
+
const provided = options.actors ? withActors(machine, options.actors) : machine;
|
|
1592
1590
|
const effectiveSources = provided.sources.actors;
|
|
1593
1591
|
const wrappedSources = {};
|
|
1594
1592
|
const invokedSrcs = getConfiguredInvokeSrcs(provided);
|
|
1595
1593
|
for (const [key, logic] of Object.entries(effectiveSources)) {
|
|
1596
1594
|
if (key === "agent.userInput") continue;
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
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
|
+
};
|
|
1605
1608
|
}
|
|
1606
|
-
if (
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
continue;
|
|
1611
|
-
}
|
|
1612
|
-
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);
|
|
1613
1613
|
continue;
|
|
1614
1614
|
}
|
|
1615
|
+
wrappedSources[key] = binding.bind();
|
|
1615
1616
|
}
|
|
1616
|
-
return provided
|
|
1617
|
+
return withActors(provided, wrappedSources);
|
|
1617
1618
|
}
|
|
1618
1619
|
function missingExecutorError(src, kind, executor) {
|
|
1619
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.`);
|
|
@@ -1633,10 +1634,8 @@ function buildStateIndex(rootConfig) {
|
|
|
1633
1634
|
const hasChildren = !!config.states && Object.keys(config.states).length > 0;
|
|
1634
1635
|
index.set(path, {
|
|
1635
1636
|
path,
|
|
1636
|
-
name,
|
|
1637
1637
|
config,
|
|
1638
1638
|
parentPath,
|
|
1639
|
-
type: config.type,
|
|
1640
1639
|
isFinal: config.type === "final",
|
|
1641
1640
|
isParallel: config.type === "parallel",
|
|
1642
1641
|
isCompound: hasChildren && config.type !== "parallel",
|
|
@@ -1754,36 +1753,20 @@ function ancestorChain(node, index) {
|
|
|
1754
1753
|
function hasNonEmptyOn(config) {
|
|
1755
1754
|
return !!config.on && Object.keys(config.on).length > 0;
|
|
1756
1755
|
}
|
|
1757
|
-
function
|
|
1758
|
-
if (typeof src === "string")
|
|
1759
|
-
|
|
1760
|
-
return require_decision.isDecisionLogic(actors[src]) ? "decision" : void 0;
|
|
1761
|
-
}
|
|
1762
|
-
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);
|
|
1763
1759
|
}
|
|
1764
1760
|
function isAgentLogicNeedingBinding(src) {
|
|
1765
1761
|
return (require_decision.isTextLogic(src) || require_decision.isDecisionLogic(src)) && !require_decision.executorBoundLogics.has(src);
|
|
1766
1762
|
}
|
|
1767
|
-
function
|
|
1768
|
-
if (!schema) return
|
|
1769
|
-
try {
|
|
1770
|
-
return require_decision.getJsonSchemaSync(schema) !== void 0;
|
|
1771
|
-
} catch {
|
|
1772
|
-
return false;
|
|
1773
|
-
}
|
|
1774
|
-
}
|
|
1775
|
-
function isDeclaredOutputSchema(schema) {
|
|
1776
|
-
if (!schema) return false;
|
|
1777
|
-
let json;
|
|
1763
|
+
function readJsonSchema(schema) {
|
|
1764
|
+
if (!schema) return;
|
|
1778
1765
|
try {
|
|
1779
|
-
|
|
1766
|
+
return require_decision.getJsonSchemaSync(schema);
|
|
1780
1767
|
} catch {
|
|
1781
|
-
return
|
|
1768
|
+
return;
|
|
1782
1769
|
}
|
|
1783
|
-
if (!json) return false;
|
|
1784
|
-
const properties = json.properties;
|
|
1785
|
-
const required = json.required;
|
|
1786
|
-
return json.type === "object" && !!properties && Object.keys(properties).length > 0 || Array.isArray(required) && required.length > 0;
|
|
1787
1770
|
}
|
|
1788
1771
|
function checkUnreachableStates(ctx) {
|
|
1789
1772
|
const out = [];
|
|
@@ -1798,8 +1781,7 @@ function checkUnreachableStates(ctx) {
|
|
|
1798
1781
|
function checkDecideWithoutEvents(ctx) {
|
|
1799
1782
|
const out = [];
|
|
1800
1783
|
for (const node of ctx.index.values()) for (const invoke of node.invokes) {
|
|
1801
|
-
|
|
1802
|
-
if (!kind) continue;
|
|
1784
|
+
if (!isDecisionInvoke(invoke.src, ctx.actors)) continue;
|
|
1803
1785
|
const selfHandles = hasNonEmptyOn(node.config);
|
|
1804
1786
|
const ancestorHandles = ancestorChain(node, ctx.index).some((ancestor) => hasNonEmptyOn(ancestor.config));
|
|
1805
1787
|
const invokeObserves = invoke.onDone !== void 0;
|
|
@@ -1809,7 +1791,7 @@ function checkDecideWithoutEvents(ctx) {
|
|
|
1809
1791
|
code: "decide-without-events",
|
|
1810
1792
|
severity: "error",
|
|
1811
1793
|
path: node.path,
|
|
1812
|
-
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.`
|
|
1813
1795
|
});
|
|
1814
1796
|
}
|
|
1815
1797
|
return out;
|
|
@@ -1817,7 +1799,7 @@ function checkDecideWithoutEvents(ctx) {
|
|
|
1817
1799
|
function checkUnserializableContext(ctx) {
|
|
1818
1800
|
const contextSchema = ctx.schemas?.context;
|
|
1819
1801
|
if (!contextSchema) return [];
|
|
1820
|
-
if (
|
|
1802
|
+
if (readJsonSchema(contextSchema)) return [];
|
|
1821
1803
|
return [{
|
|
1822
1804
|
code: "unserializable-context",
|
|
1823
1805
|
severity: "warning",
|
|
@@ -1841,7 +1823,10 @@ function checkDirectObjectSrc(ctx) {
|
|
|
1841
1823
|
return out;
|
|
1842
1824
|
}
|
|
1843
1825
|
function checkFinalWithoutOutput(ctx) {
|
|
1844
|
-
|
|
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 [];
|
|
1845
1830
|
if (ctx.config.output !== void 0) return [];
|
|
1846
1831
|
const out = [];
|
|
1847
1832
|
for (const node of ctx.index.values()) {
|
|
@@ -1935,7 +1920,6 @@ function lintAgentMachine(machine, options = {}) {
|
|
|
1935
1920
|
const reachable = computeReachable(config, index, require_decision.getMachineStaticTransitionTargets(machine));
|
|
1936
1921
|
const registered = require_decision.getRegisteredAgentExecutionOptions(machine);
|
|
1937
1922
|
const ctx = {
|
|
1938
|
-
machine,
|
|
1939
1923
|
config,
|
|
1940
1924
|
index,
|
|
1941
1925
|
reachable,
|
|
@@ -2018,7 +2002,7 @@ function takeFromQueue(channel, src) {
|
|
|
2018
2002
|
async function simulateAgent(machine, options) {
|
|
2019
2003
|
const maxSteps = options.maxSteps ?? 100;
|
|
2020
2004
|
const script = {
|
|
2021
|
-
text:
|
|
2005
|
+
text: mapValues(options.script.text ?? {}, (arr) => [...arr]),
|
|
2022
2006
|
decisions: mapValues(options.script.decisions ?? {}, (arr) => [...arr]),
|
|
2023
2007
|
invokes: mapValues(options.script.invokes ?? {}, (arr) => [...arr])
|
|
2024
2008
|
};
|
|
@@ -2090,6 +2074,7 @@ function scriptDryError(kind, src, id, request) {
|
|
|
2090
2074
|
const events = request?.kind === "decision" ? ` Candidate events: ${request.events.map((e) => e.type).join(", ") || "(none)"}.` : "";
|
|
2091
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}`);
|
|
2092
2076
|
}
|
|
2077
|
+
const MAX_ADVANCE_STEPS = 1e3;
|
|
2093
2078
|
async function explore(machine, options, stopWhen) {
|
|
2094
2079
|
const maxDepth = options.maxDepth ?? 8;
|
|
2095
2080
|
const maxPaths = options.maxPaths ?? 200;
|
|
@@ -2114,7 +2099,7 @@ async function explore(machine, options, stopWhen) {
|
|
|
2114
2099
|
if (stopWhen?.(initial.snapshot)) witness = [];
|
|
2115
2100
|
const advance = (step) => {
|
|
2116
2101
|
let current = step;
|
|
2117
|
-
for (let i = 0; i <
|
|
2102
|
+
for (let i = 0; i < MAX_ADVANCE_STEPS; i++) {
|
|
2118
2103
|
if (current.done) return { step: current };
|
|
2119
2104
|
const request = current.requests[0];
|
|
2120
2105
|
if (request && request.kind === "text") {
|
|
@@ -2266,19 +2251,22 @@ async function canReach(machine, statePath, options = {}) {
|
|
|
2266
2251
|
} : { canReach: false };
|
|
2267
2252
|
}
|
|
2268
2253
|
//#endregion
|
|
2269
|
-
//#region src/
|
|
2270
|
-
|
|
2254
|
+
//#region src/internal/is-record.ts
|
|
2255
|
+
/** True for a plain object-like value (arrays excluded). @internal */
|
|
2256
|
+
function isRecord(value) {
|
|
2271
2257
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2272
2258
|
}
|
|
2259
|
+
//#endregion
|
|
2260
|
+
//#region src/trajectory.ts
|
|
2273
2261
|
/**
|
|
2274
2262
|
* Classifies one item. Event-shaped things (log entries, events) become events;
|
|
2275
2263
|
* everything else is a state value. A snapshot is unwrapped to its `value`.
|
|
2276
2264
|
* @internal
|
|
2277
2265
|
*/
|
|
2278
2266
|
function normalize(item) {
|
|
2279
|
-
if (isRecord
|
|
2267
|
+
if (isRecord(item)) {
|
|
2280
2268
|
const nested = item["event"];
|
|
2281
|
-
if (isRecord
|
|
2269
|
+
if (isRecord(nested) && typeof nested["type"] === "string") return {
|
|
2282
2270
|
kind: "event",
|
|
2283
2271
|
event: nested
|
|
2284
2272
|
};
|
|
@@ -2299,7 +2287,7 @@ function normalize(item) {
|
|
|
2299
2287
|
/** Every leaf of a state value as a dot path (`{ review: 'editing' }` → `review.editing`). @internal */
|
|
2300
2288
|
function statePaths(value) {
|
|
2301
2289
|
if (typeof value === "string") return [value];
|
|
2302
|
-
if (!isRecord
|
|
2290
|
+
if (!isRecord(value)) return [];
|
|
2303
2291
|
return Object.entries(value).flatMap(([key, child]) => statePaths(child).map((path) => `${key}.${path}`));
|
|
2304
2292
|
}
|
|
2305
2293
|
/**
|
|
@@ -2312,12 +2300,13 @@ function stateMatchesPath(path, value) {
|
|
|
2312
2300
|
function deepEqual(a, b) {
|
|
2313
2301
|
if (Object.is(a, b)) return true;
|
|
2314
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]));
|
|
2315
|
-
if (!isRecord
|
|
2303
|
+
if (!isRecord(a) || !isRecord(b)) return false;
|
|
2316
2304
|
const keys = Object.keys(a);
|
|
2317
2305
|
return keys.length === Object.keys(b).length && keys.every((key) => deepEqual(a[key], b[key]));
|
|
2318
2306
|
}
|
|
2319
2307
|
/**
|
|
2320
|
-
*
|
|
2308
|
+
* Normalizes ONE expected item into a predicate over actual items, so the
|
|
2309
|
+
* expectation is classified once instead of once per candidate:
|
|
2321
2310
|
*
|
|
2322
2311
|
* - A string expectation matches an event's `type`, or a state value it names.
|
|
2323
2312
|
* - An event expectation matches an event with the same `type` whose other
|
|
@@ -2325,13 +2314,22 @@ function deepEqual(a, b) {
|
|
|
2325
2314
|
* - A state-value expectation matches structurally.
|
|
2326
2315
|
* @internal
|
|
2327
2316
|
*/
|
|
2328
|
-
function
|
|
2329
|
-
|
|
2330
|
-
|
|
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
|
+
};
|
|
2331
2322
|
const expectedItem = normalize(expected);
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
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
|
+
}
|
|
2335
2333
|
}
|
|
2336
2334
|
/**
|
|
2337
2335
|
* Matches a run's trajectory against an expected one.
|
|
@@ -2368,59 +2366,41 @@ function matchesItem(expected, actual) {
|
|
|
2368
2366
|
*/
|
|
2369
2367
|
function matchesTrajectory(actual, expected, options = {}) {
|
|
2370
2368
|
const expectedCount = expected.length;
|
|
2371
|
-
|
|
2372
|
-
let matchedCount = 0;
|
|
2373
|
-
let firstMiss;
|
|
2374
|
-
for (let index = 0; index < expectedCount; index++) {
|
|
2375
|
-
if (index < actual.length && matchesItem(expected[index], actual[index])) {
|
|
2376
|
-
matchedCount++;
|
|
2377
|
-
continue;
|
|
2378
|
-
}
|
|
2379
|
-
firstMiss = {
|
|
2380
|
-
index,
|
|
2381
|
-
expected: expected[index],
|
|
2382
|
-
searchedFrom: index
|
|
2383
|
-
};
|
|
2384
|
-
break;
|
|
2385
|
-
}
|
|
2386
|
-
const matched = !firstMiss && actual.length === expectedCount;
|
|
2387
|
-
const denominator = Math.max(expectedCount, actual.length);
|
|
2388
|
-
return {
|
|
2389
|
-
matched,
|
|
2390
|
-
matchedCount,
|
|
2391
|
-
expectedCount,
|
|
2392
|
-
score: matched ? 1 : denominator === 0 ? 1 : matchedCount / denominator,
|
|
2393
|
-
...firstMiss ? { firstMiss } : {}
|
|
2394
|
-
};
|
|
2395
|
-
}
|
|
2369
|
+
const exact = options.exact === true;
|
|
2396
2370
|
let cursor = 0;
|
|
2397
2371
|
let matchedCount = 0;
|
|
2372
|
+
let firstMiss;
|
|
2398
2373
|
for (let index = 0; index < expectedCount; index++) {
|
|
2399
2374
|
const want = expected[index];
|
|
2375
|
+
const matches = matcherFor(want);
|
|
2376
|
+
const searchedFrom = exact ? index : cursor;
|
|
2400
2377
|
let found = -1;
|
|
2401
|
-
for (let at =
|
|
2402
|
-
|
|
2403
|
-
|
|
2378
|
+
for (let at = searchedFrom; at < actual.length; at++) {
|
|
2379
|
+
if (matches(actual[at])) {
|
|
2380
|
+
found = at;
|
|
2381
|
+
break;
|
|
2382
|
+
}
|
|
2383
|
+
if (exact) break;
|
|
2404
2384
|
}
|
|
2405
|
-
if (found === -1)
|
|
2406
|
-
|
|
2407
|
-
matchedCount,
|
|
2408
|
-
expectedCount,
|
|
2409
|
-
score: matchedCount / expectedCount,
|
|
2410
|
-
firstMiss: {
|
|
2385
|
+
if (found === -1) {
|
|
2386
|
+
firstMiss = {
|
|
2411
2387
|
index,
|
|
2412
2388
|
expected: want,
|
|
2413
|
-
searchedFrom
|
|
2414
|
-
}
|
|
2415
|
-
|
|
2389
|
+
searchedFrom
|
|
2390
|
+
};
|
|
2391
|
+
break;
|
|
2392
|
+
}
|
|
2416
2393
|
matchedCount++;
|
|
2417
2394
|
cursor = found + 1;
|
|
2418
2395
|
}
|
|
2396
|
+
const matched = !firstMiss && (!exact || actual.length === expectedCount);
|
|
2397
|
+
const denominator = exact ? Math.max(expectedCount, actual.length) : expectedCount;
|
|
2419
2398
|
return {
|
|
2420
|
-
matched
|
|
2399
|
+
matched,
|
|
2421
2400
|
matchedCount,
|
|
2422
2401
|
expectedCount,
|
|
2423
|
-
score: 1
|
|
2402
|
+
score: matched ? 1 : denominator === 0 ? 1 : matchedCount / denominator,
|
|
2403
|
+
...firstMiss ? { firstMiss } : {}
|
|
2424
2404
|
};
|
|
2425
2405
|
}
|
|
2426
2406
|
//#endregion
|
|
@@ -2433,17 +2413,9 @@ function matchesTrajectory(actual, expected, options = {}) {
|
|
|
2433
2413
|
* `provideExecutors`, or a bare `TextLogic.execute`) runs with no API key and no
|
|
2434
2414
|
* network. It is the fastest way to see a machine run, and the least ceremonial
|
|
2435
2415
|
* way to test one: same machine, same executor contract, scripted answers.
|
|
2416
|
+
*
|
|
2417
|
+
* @module
|
|
2436
2418
|
*/
|
|
2437
|
-
/** Thrown when a scripted queue runs dry on a pending request. */
|
|
2438
|
-
var ScriptedExecutorsError = class extends require_errors.AgentError {
|
|
2439
|
-
constructor(message) {
|
|
2440
|
-
super("scripted-executors-exhausted", message);
|
|
2441
|
-
this.name = "ScriptedExecutorsError";
|
|
2442
|
-
}
|
|
2443
|
-
};
|
|
2444
|
-
function isRecord$1(value) {
|
|
2445
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2446
|
-
}
|
|
2447
2419
|
/** The only own keys an executor-result envelope carries. @internal */
|
|
2448
2420
|
const TEXT_ENVELOPE_KEYS = new Set([
|
|
2449
2421
|
"output",
|
|
@@ -2468,7 +2440,22 @@ function isTextEnvelope(value) {
|
|
|
2468
2440
|
*/
|
|
2469
2441
|
async function resolveScriptedTextEntry(entry, request, info) {
|
|
2470
2442
|
const value = typeof entry === "function" ? await entry(request, info) : entry;
|
|
2471
|
-
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);
|
|
2472
2459
|
}
|
|
2473
2460
|
/**
|
|
2474
2461
|
* Keyless executors that replay a script instead of calling a model. Every
|
|
@@ -2503,45 +2490,26 @@ function createScriptedExecutors(script = {}) {
|
|
|
2503
2490
|
const decisions = [...script.decisions ?? []];
|
|
2504
2491
|
const text = [...script.text ?? []];
|
|
2505
2492
|
const nextText = async (request, info) => {
|
|
2506
|
-
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.`);
|
|
2507
2494
|
return resolveScriptedTextEntry(text.shift(), request, info);
|
|
2508
2495
|
};
|
|
2509
2496
|
return {
|
|
2510
2497
|
generateText: nextText,
|
|
2511
2498
|
streamText: async (request, info) => {
|
|
2512
2499
|
const result = await nextText(request, info);
|
|
2513
|
-
|
|
2500
|
+
emitScriptedChunk(result, info);
|
|
2514
2501
|
return result;
|
|
2515
2502
|
},
|
|
2516
2503
|
decide: async (request) => {
|
|
2517
|
-
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)"}.`);
|
|
2518
2505
|
const entry = decisions.shift();
|
|
2519
2506
|
const value = typeof entry === "function" ? await entry(request) : entry;
|
|
2520
|
-
return typeof value
|
|
2507
|
+
return isRecord(value) && typeof value["type"] !== "string" && "event" in value ? value : { event: value };
|
|
2521
2508
|
}
|
|
2522
2509
|
};
|
|
2523
2510
|
}
|
|
2524
|
-
function describeText(request) {
|
|
2525
|
-
return request.name ? `'${request.name}'` : `(model '${request.model}')`;
|
|
2526
|
-
}
|
|
2527
2511
|
//#endregion
|
|
2528
2512
|
//#region src/seam.ts
|
|
2529
|
-
/** Thrown when the call plan runs dry on a request that is not the live seam. */
|
|
2530
|
-
var SeamScriptError = class extends require_errors.AgentError {
|
|
2531
|
-
constructor(message) {
|
|
2532
|
-
super("seam-script-exhausted", message);
|
|
2533
|
-
this.name = "SeamScriptError";
|
|
2534
|
-
}
|
|
2535
|
-
};
|
|
2536
|
-
function seamKeyOf(seam) {
|
|
2537
|
-
return seam.request ?? seam.model;
|
|
2538
|
-
}
|
|
2539
|
-
function describeRequest(request) {
|
|
2540
|
-
return request.name ? `'${request.name}' (model '${request.model}')` : `(model '${request.model}')`;
|
|
2541
|
-
}
|
|
2542
|
-
function isRecord(value) {
|
|
2543
|
-
return typeof value === "object" && value !== null;
|
|
2544
|
-
}
|
|
2545
2513
|
/**
|
|
2546
2514
|
* The seam's own answer, for scoring. Our `{ output }` envelope is read
|
|
2547
2515
|
* directly; a raw AI SDK `generateText` result contributes its `text`. A raw
|
|
@@ -2596,40 +2564,50 @@ async function runSeam(machine, options) {
|
|
|
2596
2564
|
let seamStateAt = 0;
|
|
2597
2565
|
let seamEventAt = 0;
|
|
2598
2566
|
let liveEvents = 0;
|
|
2599
|
-
const
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
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;
|
|
2606
2576
|
return resolveScriptedTextEntry(queue.length === 1 ? queue[0] : queue.shift(), request, info);
|
|
2607
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
|
+
};
|
|
2608
2583
|
const route = async (request, info) => {
|
|
2609
2584
|
const callIndex = calls++;
|
|
2610
2585
|
const isSeam = (seam.request !== void 0 ? request.name === seam.request : request.model === seam.model) && seamMatches++ === (seam.occurrence ?? 0);
|
|
2611
|
-
|
|
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);
|
|
2612
2597
|
if (!isSeam) return scripted;
|
|
2613
2598
|
seamReached = true;
|
|
2614
2599
|
callsBeforeSeam = callIndex;
|
|
2615
2600
|
seamStateAt = statePath.length;
|
|
2616
2601
|
seamEventAt = liveEvents;
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
seamOutput = scripted.output;
|
|
2620
|
-
return scripted;
|
|
2621
|
-
}
|
|
2622
|
-
const result = await candidate(request, info);
|
|
2623
|
-
seamOutput = await seamOutputOf(result, request);
|
|
2624
|
-
return result;
|
|
2602
|
+
seamOutput = scripted.output;
|
|
2603
|
+
return scripted;
|
|
2625
2604
|
};
|
|
2626
2605
|
const executors = {
|
|
2627
2606
|
...options.executors,
|
|
2628
2607
|
generateText: route,
|
|
2629
2608
|
streamText: async (request, info) => {
|
|
2630
2609
|
const result = await route(request, info);
|
|
2631
|
-
|
|
2632
|
-
if (typeof output === "string") info?.onChunk?.(output);
|
|
2610
|
+
emitScriptedChunk(result, info);
|
|
2633
2611
|
return result;
|
|
2634
2612
|
}
|
|
2635
2613
|
};
|
|
@@ -2733,7 +2711,7 @@ exports.getAcceptedEvents = require_decision.getAcceptedEvents;
|
|
|
2733
2711
|
exports.getAgentEffects = require_setup_agent.getAgentEffects;
|
|
2734
2712
|
exports.getAgentMessages = require_decision.getAgentMessages;
|
|
2735
2713
|
exports.getAgentOutputMode = require_decision.getAgentOutputMode;
|
|
2736
|
-
exports.getCallUsage =
|
|
2714
|
+
exports.getCallUsage = require_decision.getCallUsage;
|
|
2737
2715
|
exports.getJsonSchema = require_decision.getJsonSchema;
|
|
2738
2716
|
exports.getJsonSchemaSync = require_decision.getJsonSchemaSync;
|
|
2739
2717
|
exports.getMachineStructuralHash = require_decision.getMachineStructuralHash;
|