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