@dogpile/sdk 0.3.1 → 0.4.0
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/CHANGELOG.md +136 -0
- package/README.md +1 -0
- package/dist/browser/index.js +1595 -54
- package/dist/browser/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/providers/openai-compatible.d.ts +11 -0
- package/dist/providers/openai-compatible.d.ts.map +1 -1
- package/dist/providers/openai-compatible.js +87 -2
- package/dist/providers/openai-compatible.js.map +1 -1
- package/dist/runtime/cancellation.d.ts +26 -0
- package/dist/runtime/cancellation.d.ts.map +1 -1
- package/dist/runtime/cancellation.js +38 -1
- package/dist/runtime/cancellation.js.map +1 -1
- package/dist/runtime/coordinator.d.ts +74 -1
- package/dist/runtime/coordinator.d.ts.map +1 -1
- package/dist/runtime/coordinator.js +932 -25
- package/dist/runtime/coordinator.js.map +1 -1
- package/dist/runtime/decisions.d.ts +25 -3
- package/dist/runtime/decisions.d.ts.map +1 -1
- package/dist/runtime/decisions.js +241 -3
- package/dist/runtime/decisions.js.map +1 -1
- package/dist/runtime/defaults.d.ts +37 -1
- package/dist/runtime/defaults.d.ts.map +1 -1
- package/dist/runtime/defaults.js +347 -0
- package/dist/runtime/defaults.js.map +1 -1
- package/dist/runtime/engine.d.ts.map +1 -1
- package/dist/runtime/engine.js +254 -24
- package/dist/runtime/engine.js.map +1 -1
- package/dist/runtime/sequential.d.ts.map +1 -1
- package/dist/runtime/sequential.js +8 -1
- package/dist/runtime/sequential.js.map +1 -1
- package/dist/runtime/validation.d.ts +10 -0
- package/dist/runtime/validation.d.ts.map +1 -1
- package/dist/runtime/validation.js +73 -0
- package/dist/runtime/validation.js.map +1 -1
- package/dist/types/events.d.ts +329 -8
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/replay.d.ts +5 -1
- package/dist/types/replay.d.ts.map +1 -1
- package/dist/types.d.ts +131 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +10 -0
- package/src/providers/openai-compatible.ts +82 -3
- package/src/runtime/cancellation.ts +59 -1
- package/src/runtime/coordinator.ts +1170 -25
- package/src/runtime/decisions.ts +307 -4
- package/src/runtime/defaults.ts +376 -0
- package/src/runtime/engine.ts +363 -24
- package/src/runtime/sequential.ts +9 -1
- package/src/runtime/validation.ts +81 -0
- package/src/types/events.ts +359 -8
- package/src/types/replay.ts +12 -1
- package/src/types.ts +147 -3
package/dist/browser/index.js
CHANGED
|
@@ -193,6 +193,27 @@ function addCost(left, right) {
|
|
|
193
193
|
totalTokens: left.totalTokens + right.totalTokens
|
|
194
194
|
};
|
|
195
195
|
}
|
|
196
|
+
function resolveOnChildFailure(runOption, engineOption) {
|
|
197
|
+
return runOption ?? engineOption ?? "continue";
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Walk a parent's events and accumulate the cost contributed by every
|
|
201
|
+
* sub-run (BUDGET-03 / D-06). Internal helper — not part of the public surface.
|
|
202
|
+
*
|
|
203
|
+
* - `sub-run-completed` events contribute `event.subResult.cost`.
|
|
204
|
+
* - `sub-run-failed` events contribute `event.partialCost` (real provider
|
|
205
|
+
* spend captured before the throw).
|
|
206
|
+
*
|
|
207
|
+
* Used by the `parent-rollup-drift` parity check in
|
|
208
|
+
* {@link recomputeAccountingFromTrace} to verify the parent's recorded
|
|
209
|
+
* accounting equals `localOnly + Σ children` recursively.
|
|
210
|
+
*/
|
|
211
|
+
function accumulateSubRunCost(events) {
|
|
212
|
+
let total = emptyCost();
|
|
213
|
+
for (const event of events) if (event.type === "sub-run-completed") total = addCost(total, event.subResult.cost);
|
|
214
|
+
else if (event.type === "sub-run-failed") total = addCost(total, event.partialCost);
|
|
215
|
+
return total;
|
|
216
|
+
}
|
|
196
217
|
function createTranscriptLink(transcript) {
|
|
197
218
|
return {
|
|
198
219
|
kind: "trace-transcript",
|
|
@@ -291,7 +312,14 @@ function createReplayTraceBudgetStateChanges(events) {
|
|
|
291
312
|
case "model-response":
|
|
292
313
|
case "model-output-chunk":
|
|
293
314
|
case "tool-call":
|
|
294
|
-
case "tool-result":
|
|
315
|
+
case "tool-result":
|
|
316
|
+
case "sub-run-started":
|
|
317
|
+
case "sub-run-completed":
|
|
318
|
+
case "sub-run-failed":
|
|
319
|
+
case "sub-run-parent-aborted":
|
|
320
|
+
case "sub-run-budget-clamped":
|
|
321
|
+
case "sub-run-queued":
|
|
322
|
+
case "sub-run-concurrency-clamped": return [];
|
|
295
323
|
}
|
|
296
324
|
});
|
|
297
325
|
}
|
|
@@ -388,6 +416,24 @@ function createReplayTraceProtocolDecision(protocol, event, eventIndex, options
|
|
|
388
416
|
output: event.output,
|
|
389
417
|
cost: event.cost
|
|
390
418
|
};
|
|
419
|
+
case "sub-run-started": return {
|
|
420
|
+
...base,
|
|
421
|
+
input: event.intent
|
|
422
|
+
};
|
|
423
|
+
case "sub-run-completed": return {
|
|
424
|
+
...base,
|
|
425
|
+
output: event.subResult.output,
|
|
426
|
+
cost: event.subResult.cost
|
|
427
|
+
};
|
|
428
|
+
case "sub-run-failed": return { ...base };
|
|
429
|
+
case "sub-run-parent-aborted": return { ...base };
|
|
430
|
+
case "sub-run-budget-clamped": return { ...base };
|
|
431
|
+
case "sub-run-queued": return {
|
|
432
|
+
...base,
|
|
433
|
+
childRunId: event.childRunId,
|
|
434
|
+
queuePosition: event.queuePosition
|
|
435
|
+
};
|
|
436
|
+
case "sub-run-concurrency-clamped": return { ...base };
|
|
391
437
|
}
|
|
392
438
|
}
|
|
393
439
|
function defaultProtocolDecision(event) {
|
|
@@ -402,6 +448,13 @@ function defaultProtocolDecision(event) {
|
|
|
402
448
|
case "broadcast": return "collect-broadcast-round";
|
|
403
449
|
case "budget-stop": return "stop-for-budget";
|
|
404
450
|
case "final": return "finalize-output";
|
|
451
|
+
case "sub-run-started": return "start-sub-run";
|
|
452
|
+
case "sub-run-completed": return "complete-sub-run";
|
|
453
|
+
case "sub-run-failed": return "fail-sub-run";
|
|
454
|
+
case "sub-run-parent-aborted": return "mark-sub-run-parent-aborted";
|
|
455
|
+
case "sub-run-budget-clamped": return "mark-sub-run-budget-clamped";
|
|
456
|
+
case "sub-run-queued": return "queue-sub-run";
|
|
457
|
+
case "sub-run-concurrency-clamped": return "mark-sub-run-concurrency-clamped";
|
|
405
458
|
}
|
|
406
459
|
}
|
|
407
460
|
function eventAgentScope(event) {
|
|
@@ -467,6 +520,216 @@ function canonicalizeRunResult(result) {
|
|
|
467
520
|
function stableJsonStringify(value) {
|
|
468
521
|
return JSON.stringify(canonicalizeSerializable(value));
|
|
469
522
|
}
|
|
523
|
+
/**
|
|
524
|
+
* The eight numeric fields recursively verified by `recomputeAccountingFromTrace`.
|
|
525
|
+
*
|
|
526
|
+
* These are the only summable scalars on `RunAccounting`. Non-numeric fields
|
|
527
|
+
* (`kind`, `tier`, `budget`, `termination`, `budgetStateChanges`) and derived
|
|
528
|
+
* ratios (`usdCapUtilization`, `totalTokenCapUtilization`) are NOT in this set.
|
|
529
|
+
*/
|
|
530
|
+
var RECOMPUTE_FIELD_ORDER = [
|
|
531
|
+
"cost.usd",
|
|
532
|
+
"cost.inputTokens",
|
|
533
|
+
"cost.outputTokens",
|
|
534
|
+
"cost.totalTokens",
|
|
535
|
+
"usage.usd",
|
|
536
|
+
"usage.inputTokens",
|
|
537
|
+
"usage.outputTokens",
|
|
538
|
+
"usage.totalTokens"
|
|
539
|
+
];
|
|
540
|
+
var USD_FIELDS = new Set(["cost.usd", "usage.usd"]);
|
|
541
|
+
var FLOAT_EPSILON = 1e-9;
|
|
542
|
+
function readNumericField(accounting, field) {
|
|
543
|
+
switch (field) {
|
|
544
|
+
case "cost.usd": return accounting.cost.usd;
|
|
545
|
+
case "cost.inputTokens": return accounting.cost.inputTokens;
|
|
546
|
+
case "cost.outputTokens": return accounting.cost.outputTokens;
|
|
547
|
+
case "cost.totalTokens": return accounting.cost.totalTokens;
|
|
548
|
+
case "usage.usd": return accounting.usage.usd;
|
|
549
|
+
case "usage.inputTokens": return accounting.usage.inputTokens;
|
|
550
|
+
case "usage.outputTokens": return accounting.usage.outputTokens;
|
|
551
|
+
case "usage.totalTokens": return accounting.usage.totalTokens;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
function fieldsEqual(field, a, b) {
|
|
555
|
+
if (USD_FIELDS.has(field)) return Math.abs(a - b) < FLOAT_EPSILON;
|
|
556
|
+
return a === b;
|
|
557
|
+
}
|
|
558
|
+
function firstDifferingField(recorded, recomputed) {
|
|
559
|
+
for (const field of RECOMPUTE_FIELD_ORDER) {
|
|
560
|
+
const a = readNumericField(recorded, field);
|
|
561
|
+
const b = readNumericField(recomputed, field);
|
|
562
|
+
if (!fieldsEqual(field, a, b)) return {
|
|
563
|
+
field,
|
|
564
|
+
recorded: a,
|
|
565
|
+
recomputed: b
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
return null;
|
|
569
|
+
}
|
|
570
|
+
function buildLocalAccounting(trace) {
|
|
571
|
+
return createRunAccounting({
|
|
572
|
+
tier: trace.tier,
|
|
573
|
+
...trace.budget.caps ? { budget: trace.budget.caps } : {},
|
|
574
|
+
...trace.budget.termination ? { termination: trace.budget.termination } : {},
|
|
575
|
+
cost: trace.finalOutput.cost,
|
|
576
|
+
events: trace.events
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
function lastCostBearingEventCost(events) {
|
|
580
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
581
|
+
const event = events[index];
|
|
582
|
+
if (event === void 0) continue;
|
|
583
|
+
if (event.type === "final" || event.type === "agent-turn" || event.type === "broadcast" || event.type === "budget-stop") return event.cost;
|
|
584
|
+
}
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Recompute a parent's `RunAccounting` from a saved `Trace` for replay-time
|
|
589
|
+
* tamper detection.
|
|
590
|
+
*
|
|
591
|
+
* @remarks
|
|
592
|
+
* Returns the parent's local `RunAccounting` (built the same way `replay()`
|
|
593
|
+
* builds it today, from `trace.finalOutput.cost` and `trace.events`). While
|
|
594
|
+
* walking events, every `sub-run-completed` is recursed into and the
|
|
595
|
+
* recomputed child accounting is compared field-by-field to the recorded
|
|
596
|
+
* `event.subResult.accounting`. A mismatch on any of the eight enumerated
|
|
597
|
+
* numeric fields throws `DogpileError({ code: "invalid-configuration" })`
|
|
598
|
+
* with `detail.reason: "trace-accounting-mismatch"` and a concrete
|
|
599
|
+
* `detail.field` identifying the first differing numeric.
|
|
600
|
+
*
|
|
601
|
+
* Pure: no provider calls, no I/O, no clock reads.
|
|
602
|
+
*
|
|
603
|
+
* Non-summed fields (`kind`, `tier`, `budget`, `termination`,
|
|
604
|
+
* `budgetStateChanges`) and derived ratios (`usdCapUtilization`,
|
|
605
|
+
* `totalTokenCapUtilization`) are not in the comparison set.
|
|
606
|
+
*/
|
|
607
|
+
function recomputeAccountingFromTrace(trace) {
|
|
608
|
+
const local = buildLocalAccounting(trace);
|
|
609
|
+
const lastEventCost = lastCostBearingEventCost(trace.events);
|
|
610
|
+
if (lastEventCost !== null) {
|
|
611
|
+
const drift = firstDifferingField(local, createRunAccounting({
|
|
612
|
+
tier: trace.tier,
|
|
613
|
+
...trace.budget.caps ? { budget: trace.budget.caps } : {},
|
|
614
|
+
...trace.budget.termination ? { termination: trace.budget.termination } : {},
|
|
615
|
+
cost: lastEventCost,
|
|
616
|
+
events: trace.events
|
|
617
|
+
}));
|
|
618
|
+
if (drift !== null) throw new DogpileError({
|
|
619
|
+
code: "invalid-configuration",
|
|
620
|
+
message: `Trace accounting mismatch at parent run ${trace.runId}: field "${drift.field}" recorded ${drift.recorded}, recomputed ${drift.recomputed}.`,
|
|
621
|
+
retryable: false,
|
|
622
|
+
detail: {
|
|
623
|
+
kind: "trace-validation",
|
|
624
|
+
reason: "trace-accounting-mismatch",
|
|
625
|
+
eventIndex: -1,
|
|
626
|
+
childRunId: trace.runId,
|
|
627
|
+
field: drift.field,
|
|
628
|
+
recorded: drift.recorded,
|
|
629
|
+
recomputed: drift.recomputed
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
for (let eventIndex = 0; eventIndex < trace.events.length; eventIndex += 1) {
|
|
634
|
+
const event = trace.events[eventIndex];
|
|
635
|
+
if (event === void 0) continue;
|
|
636
|
+
if (event.type === "sub-run-completed") {
|
|
637
|
+
const childRecordedRollup = createRunAccounting({
|
|
638
|
+
tier: trace.tier,
|
|
639
|
+
cost: event.subResult.cost,
|
|
640
|
+
events: []
|
|
641
|
+
});
|
|
642
|
+
const childRecordedAccounting = event.subResult.accounting;
|
|
643
|
+
const drift = firstDifferingField(childRecordedAccounting, childRecordedRollup);
|
|
644
|
+
if (drift !== null) throw new DogpileError({
|
|
645
|
+
code: "invalid-configuration",
|
|
646
|
+
message: `Trace parent-rollup mismatch at sub-run ${event.childRunId}: field "${drift.field}" recorded ${drift.recorded} on accounting, ${drift.recomputed} on subResult.cost.`,
|
|
647
|
+
retryable: false,
|
|
648
|
+
detail: {
|
|
649
|
+
kind: "trace-validation",
|
|
650
|
+
reason: "trace-accounting-mismatch",
|
|
651
|
+
subReason: "parent-rollup-drift",
|
|
652
|
+
eventIndex,
|
|
653
|
+
childRunId: event.childRunId,
|
|
654
|
+
field: drift.field,
|
|
655
|
+
recorded: drift.recorded,
|
|
656
|
+
recomputed: drift.recomputed
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
} else if (event.type === "sub-run-failed") {
|
|
660
|
+
const partialFromTrace = lastCostBearingEventCost(event.partialTrace.events) ?? emptyCost();
|
|
661
|
+
const drift = firstDifferingField(createRunAccounting({
|
|
662
|
+
tier: trace.tier,
|
|
663
|
+
cost: event.partialCost,
|
|
664
|
+
events: []
|
|
665
|
+
}), createRunAccounting({
|
|
666
|
+
tier: trace.tier,
|
|
667
|
+
cost: partialFromTrace,
|
|
668
|
+
events: []
|
|
669
|
+
}));
|
|
670
|
+
if (drift !== null) throw new DogpileError({
|
|
671
|
+
code: "invalid-configuration",
|
|
672
|
+
message: `Trace parent-rollup mismatch at sub-run ${event.childRunId}: partialCost field "${drift.field}" recorded ${drift.recorded}, recomputed ${drift.recomputed} from partialTrace events.`,
|
|
673
|
+
retryable: false,
|
|
674
|
+
detail: {
|
|
675
|
+
kind: "trace-validation",
|
|
676
|
+
reason: "trace-accounting-mismatch",
|
|
677
|
+
subReason: "parent-rollup-drift",
|
|
678
|
+
eventIndex,
|
|
679
|
+
childRunId: event.childRunId,
|
|
680
|
+
field: drift.field,
|
|
681
|
+
recorded: drift.recorded,
|
|
682
|
+
recomputed: drift.recomputed
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
const subRunTotal = accumulateSubRunCost(trace.events);
|
|
688
|
+
const parentTotal = trace.finalOutput.cost;
|
|
689
|
+
for (const field of RECOMPUTE_FIELD_ORDER) {
|
|
690
|
+
if (field.startsWith("usage.")) continue;
|
|
691
|
+
const [, key] = field.split(".");
|
|
692
|
+
const parentValue = parentTotal[key];
|
|
693
|
+
const childValue = subRunTotal[key];
|
|
694
|
+
if (childValue - parentValue > FLOAT_EPSILON) throw new DogpileError({
|
|
695
|
+
code: "invalid-configuration",
|
|
696
|
+
message: `Trace parent-rollup mismatch at run ${trace.runId}: field "${field}" Σ children ${childValue} exceeds parent recorded ${parentValue}.`,
|
|
697
|
+
retryable: false,
|
|
698
|
+
detail: {
|
|
699
|
+
kind: "trace-validation",
|
|
700
|
+
reason: "trace-accounting-mismatch",
|
|
701
|
+
subReason: "parent-rollup-drift",
|
|
702
|
+
eventIndex: -1,
|
|
703
|
+
childRunId: trace.runId,
|
|
704
|
+
field,
|
|
705
|
+
recorded: parentValue,
|
|
706
|
+
recomputed: childValue
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
for (let eventIndex = 0; eventIndex < trace.events.length; eventIndex += 1) {
|
|
711
|
+
const event = trace.events[eventIndex];
|
|
712
|
+
if (event === void 0 || event.type !== "sub-run-completed") continue;
|
|
713
|
+
const childRecomputed = recomputeAccountingFromTrace(event.subResult.trace);
|
|
714
|
+
const childRecorded = event.subResult.accounting;
|
|
715
|
+
const drift = firstDifferingField(childRecorded, childRecomputed);
|
|
716
|
+
if (drift !== null) throw new DogpileError({
|
|
717
|
+
code: "invalid-configuration",
|
|
718
|
+
message: `Trace accounting mismatch at sub-run ${event.childRunId}: field "${drift.field}" recorded ${drift.recorded}, recomputed ${drift.recomputed}.`,
|
|
719
|
+
retryable: false,
|
|
720
|
+
detail: {
|
|
721
|
+
kind: "trace-validation",
|
|
722
|
+
reason: "trace-accounting-mismatch",
|
|
723
|
+
eventIndex,
|
|
724
|
+
childRunId: event.childRunId,
|
|
725
|
+
field: drift.field,
|
|
726
|
+
recorded: drift.recorded,
|
|
727
|
+
recomputed: drift.recomputed
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
return local;
|
|
732
|
+
}
|
|
470
733
|
function canonicalizeSerializable(value) {
|
|
471
734
|
if (Array.isArray(value)) return value.map((item) => canonicalizeSerializable(item));
|
|
472
735
|
if (typeof value === "number") {
|
|
@@ -485,6 +748,24 @@ function canonicalizeSerializable(value) {
|
|
|
485
748
|
}
|
|
486
749
|
//#endregion
|
|
487
750
|
//#region src/runtime/cancellation.ts
|
|
751
|
+
/**
|
|
752
|
+
* Classify an abort signal's reason into the BUDGET-01 / BUDGET-02
|
|
753
|
+
* `detail.reason` discriminator.
|
|
754
|
+
*
|
|
755
|
+
* - `"timeout"` when the reason is a {@link DogpileError} with `code === "timeout"`
|
|
756
|
+
* (matches the parent-deadline abort path in `engine.ts:createTimeoutAbortLifecycle`).
|
|
757
|
+
* - `"parent-aborted"` for every other reason — explicit caller abort, plain
|
|
758
|
+
* `Error`, `undefined`, or arbitrary primitive.
|
|
759
|
+
*/
|
|
760
|
+
function classifyAbortReason(signalReasonOrError) {
|
|
761
|
+
if (DogpileError.isInstance(signalReasonOrError) && signalReasonOrError.code === "timeout") return "timeout";
|
|
762
|
+
return "parent-aborted";
|
|
763
|
+
}
|
|
764
|
+
function classifyChildTimeoutSource(_error, context) {
|
|
765
|
+
if (context.isProviderError) return "provider";
|
|
766
|
+
if (context.decisionTimeoutMs !== void 0 || context.engineDefaultTimeoutMs !== void 0) return "engine";
|
|
767
|
+
return "provider";
|
|
768
|
+
}
|
|
488
769
|
function throwIfAborted(signal, providerId) {
|
|
489
770
|
if (!signal?.aborted) return;
|
|
490
771
|
throw createAbortErrorFromSignal(signal, providerId);
|
|
@@ -501,7 +782,7 @@ function createAbortError(providerId, detail, cause) {
|
|
|
501
782
|
}
|
|
502
783
|
function createAbortErrorFromSignal(signal, providerId) {
|
|
503
784
|
if (DogpileError.isInstance(signal.reason)) return signal.reason;
|
|
504
|
-
return createAbortError(providerId,
|
|
785
|
+
return createAbortError(providerId, { reason: classifyAbortReason(signal.reason) }, signal.reason);
|
|
505
786
|
}
|
|
506
787
|
function createTimeoutError(providerId, timeoutMs) {
|
|
507
788
|
return new DogpileError({
|
|
@@ -512,23 +793,223 @@ function createTimeoutError(providerId, timeoutMs) {
|
|
|
512
793
|
detail: { timeoutMs }
|
|
513
794
|
});
|
|
514
795
|
}
|
|
796
|
+
function createEngineDeadlineTimeoutError(providerId, timeoutMs) {
|
|
797
|
+
return new DogpileError({
|
|
798
|
+
code: "provider-timeout",
|
|
799
|
+
message: `The child engine deadline expired after ${timeoutMs}ms.`,
|
|
800
|
+
retryable: true,
|
|
801
|
+
providerId,
|
|
802
|
+
detail: {
|
|
803
|
+
timeoutMs,
|
|
804
|
+
source: "engine"
|
|
805
|
+
}
|
|
806
|
+
});
|
|
807
|
+
}
|
|
515
808
|
//#endregion
|
|
516
809
|
//#region src/runtime/decisions.ts
|
|
517
|
-
|
|
810
|
+
var PROTOCOL_NAMES = [
|
|
811
|
+
"coordinator",
|
|
812
|
+
"sequential",
|
|
813
|
+
"broadcast",
|
|
814
|
+
"shared"
|
|
815
|
+
];
|
|
816
|
+
function parseAgentDecision(output, context = {}) {
|
|
817
|
+
const delegateBlock = matchDelegateBlock(output);
|
|
818
|
+
if (delegateBlock !== void 0) return parseDelegateDecision(delegateBlock, context);
|
|
819
|
+
return parseParticipateDecision(output);
|
|
820
|
+
}
|
|
821
|
+
function isParticipatingDecision(decision) {
|
|
822
|
+
if (decision === void 0 || isDelegateDecisionArray(decision) || decision.type !== "participate") return false;
|
|
823
|
+
return decision.participation !== "abstain";
|
|
824
|
+
}
|
|
825
|
+
function isDelegateDecisionArray(decision) {
|
|
826
|
+
return Array.isArray(decision);
|
|
827
|
+
}
|
|
828
|
+
function parseParticipateDecision(output) {
|
|
518
829
|
const selectedRole = matchLine(output, /^role_selected:\s*(.+)$/imu);
|
|
519
830
|
const participation = matchLine(output, /^participation:\s*(contribute|abstain)$/imu);
|
|
520
831
|
const rationale = matchLine(output, /^rationale:\s*(.+)$/imu);
|
|
521
832
|
const contribution = matchContribution(output);
|
|
522
833
|
if (!selectedRole || !participation || !isAgentParticipation(participation) || !rationale || !contribution) return;
|
|
523
834
|
return {
|
|
835
|
+
type: "participate",
|
|
524
836
|
selectedRole,
|
|
525
837
|
participation,
|
|
526
838
|
rationale,
|
|
527
839
|
contribution
|
|
528
840
|
};
|
|
529
841
|
}
|
|
530
|
-
|
|
531
|
-
|
|
842
|
+
/**
|
|
843
|
+
* Locate a `delegate:` line followed by a fenced JSON block in the agent's
|
|
844
|
+
* output. Returns the raw JSON text inside the fence, or `undefined` when no
|
|
845
|
+
* delegate block is present. Tolerates ```` ```json ```` and bare ```` ``` ````.
|
|
846
|
+
*/
|
|
847
|
+
function matchDelegateBlock(output) {
|
|
848
|
+
return output.match(/^delegate:\s*\r?\n\s*```(?:json)?\s*\r?\n([\s\S]*?)\r?\n\s*```/imu)?.[1];
|
|
849
|
+
}
|
|
850
|
+
function parseDelegateDecision(jsonText, context) {
|
|
851
|
+
let parsed;
|
|
852
|
+
try {
|
|
853
|
+
parsed = JSON.parse(jsonText);
|
|
854
|
+
} catch (error) {
|
|
855
|
+
throwInvalidDelegate({
|
|
856
|
+
path: "decision",
|
|
857
|
+
message: `delegate JSON did not parse: ${error instanceof Error ? error.message : String(error)}`,
|
|
858
|
+
expected: "valid JSON object",
|
|
859
|
+
received: truncate(jsonText)
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
if (Array.isArray(parsed)) {
|
|
863
|
+
if (parsed.length === 0) throwInvalidDelegate({
|
|
864
|
+
path: "decision",
|
|
865
|
+
message: "delegate array must not be empty.",
|
|
866
|
+
expected: "array with 1..8 delegate objects",
|
|
867
|
+
received: "empty array"
|
|
868
|
+
});
|
|
869
|
+
return parsed.map((item) => parseSingleDelegateObject(item, context));
|
|
870
|
+
}
|
|
871
|
+
return parseSingleDelegateObject(parsed, context);
|
|
872
|
+
}
|
|
873
|
+
function parseSingleDelegateObject(parsed, context) {
|
|
874
|
+
if (parsed === null || typeof parsed !== "object") throwInvalidDelegate({
|
|
875
|
+
path: "decision",
|
|
876
|
+
message: "delegate decision must be a JSON object.",
|
|
877
|
+
expected: "object",
|
|
878
|
+
received: describe(parsed)
|
|
879
|
+
});
|
|
880
|
+
const record = parsed;
|
|
881
|
+
const protocol = record["protocol"];
|
|
882
|
+
if (typeof protocol !== "string" || !PROTOCOL_NAMES.includes(protocol)) throwInvalidDelegate({
|
|
883
|
+
path: "decision.protocol",
|
|
884
|
+
message: `protocol "${describe(protocol)}" is not a known coordination protocol.`,
|
|
885
|
+
expected: PROTOCOL_NAMES.join(" | "),
|
|
886
|
+
received: describe(protocol)
|
|
887
|
+
});
|
|
888
|
+
const intentRaw = record["intent"];
|
|
889
|
+
const intent = typeof intentRaw === "string" ? intentRaw.trim() : "";
|
|
890
|
+
if (intent.length === 0) throwInvalidDelegate({
|
|
891
|
+
path: "decision.intent",
|
|
892
|
+
message: "delegate decision must include a non-empty intent string.",
|
|
893
|
+
expected: "non-empty string",
|
|
894
|
+
received: describe(intentRaw)
|
|
895
|
+
});
|
|
896
|
+
const result = {
|
|
897
|
+
type: "delegate",
|
|
898
|
+
protocol,
|
|
899
|
+
intent
|
|
900
|
+
};
|
|
901
|
+
if (record["model"] !== void 0) {
|
|
902
|
+
const model = record["model"];
|
|
903
|
+
if (typeof model !== "string" || model.length === 0) throwInvalidDelegate({
|
|
904
|
+
path: "decision.model",
|
|
905
|
+
message: "delegate decision model must be a non-empty string when present.",
|
|
906
|
+
expected: "non-empty string",
|
|
907
|
+
received: describe(model)
|
|
908
|
+
});
|
|
909
|
+
if (context.parentProviderId !== void 0 && model !== context.parentProviderId) throwInvalidDelegate({
|
|
910
|
+
path: "decision.model",
|
|
911
|
+
message: `delegate decision model "${model}" does not match parent provider id "${context.parentProviderId}".`,
|
|
912
|
+
expected: context.parentProviderId,
|
|
913
|
+
received: model
|
|
914
|
+
});
|
|
915
|
+
result.model = model;
|
|
916
|
+
}
|
|
917
|
+
if (record["budget"] !== void 0) result.budget = parseDelegateBudget(record["budget"]);
|
|
918
|
+
if (record["maxConcurrentChildren"] !== void 0) {
|
|
919
|
+
const value = record["maxConcurrentChildren"];
|
|
920
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 1) throwInvalidDelegate({
|
|
921
|
+
path: "decision.maxConcurrentChildren",
|
|
922
|
+
message: "delegate decision maxConcurrentChildren must be a positive integer when present.",
|
|
923
|
+
expected: "integer >= 1",
|
|
924
|
+
received: describe(value)
|
|
925
|
+
});
|
|
926
|
+
result.maxConcurrentChildren = value;
|
|
927
|
+
}
|
|
928
|
+
if (context.currentDepth !== void 0 && context.maxDepth !== void 0) {
|
|
929
|
+
if (context.currentDepth + 1 > context.maxDepth) throw depthOverflowError(context.currentDepth, context.maxDepth);
|
|
930
|
+
}
|
|
931
|
+
return result;
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* Build the canonical depth-overflow `DogpileError`. Used by the parser (this
|
|
935
|
+
* file) and the coordinator dispatcher; kept here so both call sites produce
|
|
936
|
+
* the exact same error shape (D-14, D-15).
|
|
937
|
+
*/
|
|
938
|
+
function depthOverflowError(currentDepth, maxDepth) {
|
|
939
|
+
return new DogpileError({
|
|
940
|
+
code: "invalid-configuration",
|
|
941
|
+
message: `Depth overflow: cannot dispatch sub-run at depth ${currentDepth + 1} (maxDepth = ${maxDepth}).`,
|
|
942
|
+
retryable: false,
|
|
943
|
+
detail: {
|
|
944
|
+
kind: "delegate-validation",
|
|
945
|
+
path: "decision.protocol",
|
|
946
|
+
reason: "depth-overflow",
|
|
947
|
+
currentDepth,
|
|
948
|
+
maxDepth
|
|
949
|
+
}
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Dispatcher-time depth gate. Throws the same error shape the parser uses; the
|
|
954
|
+
* dual gate (parser + dispatcher) defends against any TOCTOU window between
|
|
955
|
+
* decision parsing and child-run spin-up (D-14).
|
|
956
|
+
*/
|
|
957
|
+
function assertDepthWithinLimit(currentDepth, maxDepth) {
|
|
958
|
+
if (currentDepth + 1 > maxDepth) throw depthOverflowError(currentDepth, maxDepth);
|
|
959
|
+
}
|
|
960
|
+
function parseDelegateBudget(raw) {
|
|
961
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) throwInvalidDelegate({
|
|
962
|
+
path: "decision.budget",
|
|
963
|
+
message: "delegate decision budget must be an object.",
|
|
964
|
+
expected: "object",
|
|
965
|
+
received: describe(raw)
|
|
966
|
+
});
|
|
967
|
+
const record = raw;
|
|
968
|
+
const budget = {};
|
|
969
|
+
if (record["timeoutMs"] !== void 0) {
|
|
970
|
+
const value = record["timeoutMs"];
|
|
971
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) throwInvalidDelegate({
|
|
972
|
+
path: "decision.budget.timeoutMs",
|
|
973
|
+
message: "delegate decision budget.timeoutMs must be a non-negative integer.",
|
|
974
|
+
expected: "integer >= 0",
|
|
975
|
+
received: describe(value)
|
|
976
|
+
});
|
|
977
|
+
budget.timeoutMs = value;
|
|
978
|
+
}
|
|
979
|
+
if (record["maxTokens"] !== void 0) {
|
|
980
|
+
const value = record["maxTokens"];
|
|
981
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) throwInvalidDelegate({
|
|
982
|
+
path: "decision.budget.maxTokens",
|
|
983
|
+
message: "delegate decision budget.maxTokens must be a non-negative integer.",
|
|
984
|
+
expected: "integer >= 0",
|
|
985
|
+
received: describe(value)
|
|
986
|
+
});
|
|
987
|
+
budget.maxTokens = value;
|
|
988
|
+
}
|
|
989
|
+
if (record["maxIterations"] !== void 0) {
|
|
990
|
+
const value = record["maxIterations"];
|
|
991
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) throwInvalidDelegate({
|
|
992
|
+
path: "decision.budget.maxIterations",
|
|
993
|
+
message: "delegate decision budget.maxIterations must be a non-negative integer.",
|
|
994
|
+
expected: "integer >= 0",
|
|
995
|
+
received: describe(value)
|
|
996
|
+
});
|
|
997
|
+
budget.maxIterations = value;
|
|
998
|
+
}
|
|
999
|
+
return budget;
|
|
1000
|
+
}
|
|
1001
|
+
function throwInvalidDelegate(failure) {
|
|
1002
|
+
throw new DogpileError({
|
|
1003
|
+
code: "invalid-configuration",
|
|
1004
|
+
message: `Invalid Dogpile configuration at ${failure.path}: ${failure.message}`,
|
|
1005
|
+
retryable: false,
|
|
1006
|
+
detail: {
|
|
1007
|
+
kind: "delegate-validation",
|
|
1008
|
+
path: failure.path,
|
|
1009
|
+
expected: failure.expected,
|
|
1010
|
+
received: failure.received
|
|
1011
|
+
}
|
|
1012
|
+
});
|
|
532
1013
|
}
|
|
533
1014
|
function matchLine(output, pattern) {
|
|
534
1015
|
return output.match(pattern)?.[1]?.trim();
|
|
@@ -540,6 +1021,15 @@ function matchContribution(output) {
|
|
|
540
1021
|
function isAgentParticipation(value) {
|
|
541
1022
|
return value === "contribute" || value === "abstain";
|
|
542
1023
|
}
|
|
1024
|
+
function describe(value) {
|
|
1025
|
+
if (value === null) return "null";
|
|
1026
|
+
if (Array.isArray(value)) return "array";
|
|
1027
|
+
if (typeof value === "string") return JSON.stringify(value).slice(0, 200);
|
|
1028
|
+
return typeof value;
|
|
1029
|
+
}
|
|
1030
|
+
function truncate(value) {
|
|
1031
|
+
return value.length > 200 ? `${value.slice(0, 200)}…` : value;
|
|
1032
|
+
}
|
|
543
1033
|
//#endregion
|
|
544
1034
|
//#region src/runtime/model.ts
|
|
545
1035
|
async function generateModelTurn(options) {
|
|
@@ -997,6 +1487,7 @@ var budgetTiers = [
|
|
|
997
1487
|
"balanced",
|
|
998
1488
|
"quality"
|
|
999
1489
|
];
|
|
1490
|
+
var onChildFailureModes = ["continue", "abort"];
|
|
1000
1491
|
/**
|
|
1001
1492
|
* Validate high-level caller options before any protocol execution starts.
|
|
1002
1493
|
*/
|
|
@@ -1015,11 +1506,25 @@ function validateDogpileOptions(options) {
|
|
|
1015
1506
|
validateOptionalFunction(options.evaluate, "evaluate");
|
|
1016
1507
|
validateOptionalSeed(options.seed, "seed");
|
|
1017
1508
|
validateOptionalAbortSignal(options.signal, "signal");
|
|
1509
|
+
validateOptionalNonNegativeInteger(options.maxDepth, "maxDepth");
|
|
1510
|
+
validateOptionalPositiveInteger(options.maxConcurrentChildren, "maxConcurrentChildren");
|
|
1511
|
+
validateOptionalPositiveFiniteNumber(options.defaultSubRunTimeoutMs, "defaultSubRunTimeoutMs");
|
|
1512
|
+
validateOptionalOnChildFailure(options.onChildFailure, "onChildFailure");
|
|
1018
1513
|
}
|
|
1019
1514
|
function validateMissionIntent(intent, path = "intent") {
|
|
1020
1515
|
validateNonEmptyString(intent, path, "intent is required.");
|
|
1021
1516
|
}
|
|
1022
1517
|
/**
|
|
1518
|
+
* Validate per-call run/stream options (`Engine.run(intent, options)` / `Engine.stream(...)`).
|
|
1519
|
+
*/
|
|
1520
|
+
function validateRunCallOptions(options, path = "options") {
|
|
1521
|
+
if (options === void 0) return;
|
|
1522
|
+
const record = requireRecord(options, path);
|
|
1523
|
+
validateOptionalNonNegativeInteger(record.maxDepth, `${path}.maxDepth`);
|
|
1524
|
+
validateOptionalPositiveInteger(record.maxConcurrentChildren, `${path}.maxConcurrentChildren`);
|
|
1525
|
+
validateOptionalOnChildFailure(record.onChildFailure, `${path}.onChildFailure`);
|
|
1526
|
+
}
|
|
1527
|
+
/**
|
|
1023
1528
|
* Validate low-level engine configuration before normalizing reusable controls.
|
|
1024
1529
|
*/
|
|
1025
1530
|
function validateEngineOptions(options) {
|
|
@@ -1036,6 +1541,10 @@ function validateEngineOptions(options) {
|
|
|
1036
1541
|
validateOptionalFunction(options.evaluate, "evaluate");
|
|
1037
1542
|
validateOptionalSeed(options.seed, "seed");
|
|
1038
1543
|
validateOptionalAbortSignal(options.signal, "signal");
|
|
1544
|
+
validateOptionalNonNegativeInteger(options.maxDepth, "maxDepth");
|
|
1545
|
+
validateOptionalPositiveInteger(options.maxConcurrentChildren, "maxConcurrentChildren");
|
|
1546
|
+
validateOptionalPositiveFiniteNumber(options.defaultSubRunTimeoutMs, "defaultSubRunTimeoutMs");
|
|
1547
|
+
validateOptionalOnChildFailure(options.onChildFailure, "onChildFailure");
|
|
1039
1548
|
}
|
|
1040
1549
|
function validateProtocolSelection(value, path) {
|
|
1041
1550
|
if (typeof value === "string") {
|
|
@@ -1083,6 +1592,23 @@ function validateBudgetTier(value, path) {
|
|
|
1083
1592
|
actual: value
|
|
1084
1593
|
});
|
|
1085
1594
|
}
|
|
1595
|
+
function validateOptionalOnChildFailure(value, path) {
|
|
1596
|
+
if (value === void 0) return;
|
|
1597
|
+
if (value === "continue" || value === "abort") return;
|
|
1598
|
+
throw new DogpileError({
|
|
1599
|
+
code: "invalid-configuration",
|
|
1600
|
+
message: `Invalid onChildFailure: expected "continue" or "abort", got ${JSON.stringify(value)}`,
|
|
1601
|
+
retryable: false,
|
|
1602
|
+
detail: {
|
|
1603
|
+
kind: "configuration-validation",
|
|
1604
|
+
path,
|
|
1605
|
+
rule: "enum",
|
|
1606
|
+
expected: onChildFailureModes.join(" | "),
|
|
1607
|
+
received: describeValue(value),
|
|
1608
|
+
reason: "invalid-on-child-failure"
|
|
1609
|
+
}
|
|
1610
|
+
});
|
|
1611
|
+
}
|
|
1086
1612
|
/**
|
|
1087
1613
|
* Validate configured model provider definitions at registration boundaries.
|
|
1088
1614
|
*/
|
|
@@ -1092,6 +1618,21 @@ function validateModelProviderRegistration(value, path = "model") {
|
|
|
1092
1618
|
validateFunction(record.generate, `${path}.generate`);
|
|
1093
1619
|
validateOptionalFunction(record.stream, `${path}.stream`);
|
|
1094
1620
|
}
|
|
1621
|
+
/**
|
|
1622
|
+
* Engine-time defense-in-depth check that a provider's optional
|
|
1623
|
+
* `metadata.locality` is one of the two valid values when present (Phase 3 D-03).
|
|
1624
|
+
* Catches user-implemented providers that bypass TypeScript checks.
|
|
1625
|
+
*/
|
|
1626
|
+
function validateProviderLocality(provider, pathPrefix = "model") {
|
|
1627
|
+
const loc = provider.metadata?.locality;
|
|
1628
|
+
if (loc !== void 0 && loc !== "local" && loc !== "remote") invalidConfiguration({
|
|
1629
|
+
path: `${pathPrefix}.metadata.locality`,
|
|
1630
|
+
rule: "enum",
|
|
1631
|
+
message: `${pathPrefix}.metadata.locality must be "local" or "remote" when provided.`,
|
|
1632
|
+
expected: "\"local\" | \"remote\"",
|
|
1633
|
+
actual: loc
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1095
1636
|
function validateOptionalAgents(value, path) {
|
|
1096
1637
|
if (value === void 0) return;
|
|
1097
1638
|
if (!Array.isArray(value)) invalidConfiguration({
|
|
@@ -1371,6 +1912,16 @@ function validateOptionalNonNegativeInteger(value, path) {
|
|
|
1371
1912
|
actual: value
|
|
1372
1913
|
});
|
|
1373
1914
|
}
|
|
1915
|
+
function validateOptionalPositiveFiniteNumber(value, path) {
|
|
1916
|
+
if (value === void 0) return;
|
|
1917
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) invalidConfiguration({
|
|
1918
|
+
path,
|
|
1919
|
+
rule: "positive-finite-number",
|
|
1920
|
+
message: "value must be a positive finite number.",
|
|
1921
|
+
expected: "finite number > 0",
|
|
1922
|
+
actual: value
|
|
1923
|
+
});
|
|
1924
|
+
}
|
|
1374
1925
|
function validateOptionalNonNegativeNumber(value, path) {
|
|
1375
1926
|
if (value === void 0) return;
|
|
1376
1927
|
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) invalidConfiguration({
|
|
@@ -2608,19 +3159,74 @@ function responseCost$3(response) {
|
|
|
2608
3159
|
}
|
|
2609
3160
|
//#endregion
|
|
2610
3161
|
//#region src/runtime/coordinator.ts
|
|
3162
|
+
/**
|
|
3163
|
+
* Hard-coded loop guard for the delegate dispatch in the coordinator plan
|
|
3164
|
+
* turn. After this many consecutive delegate decisions the coordinator throws
|
|
3165
|
+
* `invalid-configuration` (T-03-01). Not a public option.
|
|
3166
|
+
*/
|
|
3167
|
+
var MAX_DISPATCH_PER_TURN = 8;
|
|
3168
|
+
var DEFAULT_MAX_CONCURRENT_CHILDREN$1 = 4;
|
|
3169
|
+
function createSemaphore(maxConcurrent) {
|
|
3170
|
+
let inFlight = 0;
|
|
3171
|
+
const waiters = [];
|
|
3172
|
+
return {
|
|
3173
|
+
acquire() {
|
|
3174
|
+
if (inFlight < maxConcurrent) {
|
|
3175
|
+
inFlight += 1;
|
|
3176
|
+
return Promise.resolve();
|
|
3177
|
+
}
|
|
3178
|
+
return new Promise((resolve) => {
|
|
3179
|
+
waiters.push(() => {
|
|
3180
|
+
inFlight += 1;
|
|
3181
|
+
resolve();
|
|
3182
|
+
});
|
|
3183
|
+
});
|
|
3184
|
+
},
|
|
3185
|
+
release() {
|
|
3186
|
+
inFlight -= 1;
|
|
3187
|
+
const next = waiters.shift();
|
|
3188
|
+
if (next !== void 0) next();
|
|
3189
|
+
},
|
|
3190
|
+
get inFlight() {
|
|
3191
|
+
return inFlight;
|
|
3192
|
+
},
|
|
3193
|
+
get queued() {
|
|
3194
|
+
return waiters.length;
|
|
3195
|
+
}
|
|
3196
|
+
};
|
|
3197
|
+
}
|
|
3198
|
+
/**
|
|
3199
|
+
* Walk the coordinator's active provider set and return the FIRST provider
|
|
3200
|
+
* whose metadata.locality === "local", or undefined if none found.
|
|
3201
|
+
*
|
|
3202
|
+
* Walk order (forward-compat): options.model first, then options.agents in
|
|
3203
|
+
* declaration order. AgentSpec has no `model` field today (Phase 3 D-11
|
|
3204
|
+
* forward-compat scaffolding); the agent walk uses optional chaining and
|
|
3205
|
+
* effectively no-ops until a future phase adds AgentSpec.model.
|
|
3206
|
+
*/
|
|
3207
|
+
function findFirstLocalProvider(options) {
|
|
3208
|
+
if (options.model.metadata?.locality === "local") return options.model;
|
|
3209
|
+
for (const agent of options.agents) {
|
|
3210
|
+
const agentModel = agent.model;
|
|
3211
|
+
if (agentModel?.metadata?.locality === "local") return agentModel;
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
2611
3214
|
async function runCoordinator(options) {
|
|
2612
3215
|
const runId = createRunId();
|
|
2613
3216
|
const events = [];
|
|
2614
3217
|
const transcript = [];
|
|
2615
3218
|
const protocolDecisions = [];
|
|
2616
3219
|
const providerCalls = [];
|
|
3220
|
+
const dispatchedChildren = /* @__PURE__ */ new Map();
|
|
2617
3221
|
let totalCost = emptyCost();
|
|
3222
|
+
let concurrencyClampEmitted = false;
|
|
2618
3223
|
const maxTurns = options.protocol.maxTurns ?? options.agents.length;
|
|
2619
3224
|
const activeAgents = options.agents.slice(0, maxTurns);
|
|
2620
3225
|
const coordinator = activeAgents[0];
|
|
2621
3226
|
const startedAtMs = nowMs();
|
|
2622
3227
|
let stopped = false;
|
|
2623
3228
|
let termination;
|
|
3229
|
+
let triggeringFailureForAbortMode;
|
|
2624
3230
|
const wrapUpHint = createWrapUpHintController({
|
|
2625
3231
|
protocol: options.protocol,
|
|
2626
3232
|
tier: options.tier,
|
|
@@ -2636,6 +3242,51 @@ async function runCoordinator(options) {
|
|
|
2636
3242
|
const recordProtocolDecision = (event, decisionOptions) => {
|
|
2637
3243
|
protocolDecisions.push(createReplayTraceProtocolDecision("coordinator", event, events.length - 1, decisionOptions));
|
|
2638
3244
|
};
|
|
3245
|
+
const drainOnParentAbort = (reasonSource) => {
|
|
3246
|
+
const reason = classifyAbortReason(reasonSource);
|
|
3247
|
+
for (const child of dispatchedChildren.values()) {
|
|
3248
|
+
if (child.closed) continue;
|
|
3249
|
+
const partialCost = child.started ? lastCostBearingEventCost(child.childEvents) ?? emptyCost() : emptyCost();
|
|
3250
|
+
const partialTrace = buildPartialTrace({
|
|
3251
|
+
childRunId: child.childRunId,
|
|
3252
|
+
events: [...child.childEvents],
|
|
3253
|
+
startedAtMs: child.startedAtMs,
|
|
3254
|
+
protocol: child.decision.protocol,
|
|
3255
|
+
tier: options.tier,
|
|
3256
|
+
modelProviderId: options.model.id,
|
|
3257
|
+
agents: options.agents,
|
|
3258
|
+
intent: child.decision.intent,
|
|
3259
|
+
temperature: options.temperature,
|
|
3260
|
+
...child.childTimeoutMs !== void 0 ? { childTimeoutMs: child.childTimeoutMs } : {},
|
|
3261
|
+
...options.seed !== void 0 ? { seed: options.seed } : {}
|
|
3262
|
+
});
|
|
3263
|
+
const failedEvent = {
|
|
3264
|
+
type: "sub-run-failed",
|
|
3265
|
+
runId,
|
|
3266
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3267
|
+
childRunId: child.childRunId,
|
|
3268
|
+
parentRunId: runId,
|
|
3269
|
+
parentDecisionId: child.parentDecisionId,
|
|
3270
|
+
parentDecisionArrayIndex: child.parentDecisionArrayIndex,
|
|
3271
|
+
error: child.started ? {
|
|
3272
|
+
code: "aborted",
|
|
3273
|
+
message: "Parent run aborted.",
|
|
3274
|
+
detail: { reason }
|
|
3275
|
+
} : {
|
|
3276
|
+
code: "aborted",
|
|
3277
|
+
message: "Sibling delegate failed; queued delegate never started.",
|
|
3278
|
+
detail: { reason: "sibling-failed" }
|
|
3279
|
+
},
|
|
3280
|
+
partialTrace,
|
|
3281
|
+
partialCost
|
|
3282
|
+
};
|
|
3283
|
+
child.closed = true;
|
|
3284
|
+
totalCost = addCost(totalCost, partialCost);
|
|
3285
|
+
emit(failedEvent);
|
|
3286
|
+
recordProtocolDecision(failedEvent);
|
|
3287
|
+
}
|
|
3288
|
+
};
|
|
3289
|
+
options.registerAbortDrain?.(drainOnParentAbort);
|
|
2639
3290
|
const toolExecutor = createRuntimeToolExecutor({
|
|
2640
3291
|
runId,
|
|
2641
3292
|
protocol: "coordinator",
|
|
@@ -2666,24 +3317,231 @@ async function runCoordinator(options) {
|
|
|
2666
3317
|
}
|
|
2667
3318
|
if (coordinator) {
|
|
2668
3319
|
if (!stopIfNeeded()) {
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
3320
|
+
let dispatchInput = buildCoordinatorPlanInput(options.intent, coordinator);
|
|
3321
|
+
let dispatchCount = 0;
|
|
3322
|
+
while (true) {
|
|
3323
|
+
const turnOutcome = await runCoordinatorTurn({
|
|
3324
|
+
agent: coordinator,
|
|
3325
|
+
coordinator,
|
|
3326
|
+
input: dispatchInput,
|
|
3327
|
+
phase: "plan",
|
|
3328
|
+
options,
|
|
3329
|
+
runId,
|
|
3330
|
+
transcript,
|
|
3331
|
+
totalCost,
|
|
3332
|
+
providerCalls,
|
|
3333
|
+
toolExecutor,
|
|
3334
|
+
toolAvailability,
|
|
3335
|
+
events,
|
|
3336
|
+
startedAtMs,
|
|
3337
|
+
wrapUpHint,
|
|
3338
|
+
emit,
|
|
3339
|
+
recordProtocolDecision
|
|
3340
|
+
});
|
|
3341
|
+
totalCost = turnOutcome.totalCost;
|
|
3342
|
+
if (turnOutcome.decision === void 0) break;
|
|
3343
|
+
const delegates = Array.isArray(turnOutcome.decision) ? turnOutcome.decision : turnOutcome.decision.type === "delegate" ? [turnOutcome.decision] : [];
|
|
3344
|
+
if (delegates.length === 0) break;
|
|
3345
|
+
if (dispatchCount + delegates.length > MAX_DISPATCH_PER_TURN) throw new DogpileError({
|
|
3346
|
+
code: "invalid-configuration",
|
|
3347
|
+
message: `Coordinator plan turn delegated ${delegates.length} more children after ${dispatchCount}; max is ${MAX_DISPATCH_PER_TURN}.`,
|
|
3348
|
+
retryable: false,
|
|
3349
|
+
detail: {
|
|
3350
|
+
kind: "delegate-validation",
|
|
3351
|
+
path: "decision",
|
|
3352
|
+
reason: "loop-guard-exceeded",
|
|
3353
|
+
maxDispatchPerTurn: MAX_DISPATCH_PER_TURN
|
|
3354
|
+
}
|
|
3355
|
+
});
|
|
3356
|
+
const parentDecisionId = String(events.length - 1);
|
|
3357
|
+
const parentDepth = options.currentDepth ?? 0;
|
|
3358
|
+
const decisionMax = delegates.reduce((max, delegate) => Math.min(max, delegate.maxConcurrentChildren ?? Number.POSITIVE_INFINITY), Number.POSITIVE_INFINITY);
|
|
3359
|
+
let effectiveForTurn = Math.min(options.effectiveMaxConcurrentChildren ?? DEFAULT_MAX_CONCURRENT_CHILDREN$1, decisionMax);
|
|
3360
|
+
const requestedMax = effectiveForTurn;
|
|
3361
|
+
const localProvider = findFirstLocalProvider(options);
|
|
3362
|
+
if (localProvider !== void 0) {
|
|
3363
|
+
effectiveForTurn = 1;
|
|
3364
|
+
if (!concurrencyClampEmitted) {
|
|
3365
|
+
const clampEvent = {
|
|
3366
|
+
type: "sub-run-concurrency-clamped",
|
|
3367
|
+
runId,
|
|
3368
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3369
|
+
requestedMax,
|
|
3370
|
+
effectiveMax: 1,
|
|
3371
|
+
reason: "local-provider-detected",
|
|
3372
|
+
providerId: localProvider.id
|
|
3373
|
+
};
|
|
3374
|
+
emit(clampEvent);
|
|
3375
|
+
recordProtocolDecision(clampEvent);
|
|
3376
|
+
concurrencyClampEmitted = true;
|
|
3377
|
+
}
|
|
3378
|
+
}
|
|
3379
|
+
const semaphore = createSemaphore(effectiveForTurn);
|
|
3380
|
+
const childRunIds = delegates.map(() => createRunId());
|
|
3381
|
+
const dispatchedForTurn = delegates.map((delegate, index) => {
|
|
3382
|
+
const childRunId = childRunIds[index];
|
|
3383
|
+
if (childRunId === void 0) throw new Error("missing child run id");
|
|
3384
|
+
const dispatchedChild = {
|
|
3385
|
+
childRunId,
|
|
3386
|
+
decision: delegate,
|
|
3387
|
+
parentDecisionId,
|
|
3388
|
+
parentDecisionArrayIndex: index,
|
|
3389
|
+
parentDepth,
|
|
3390
|
+
controller: new AbortController(),
|
|
3391
|
+
removeParentListener: void 0,
|
|
3392
|
+
childEvents: [],
|
|
3393
|
+
started: false,
|
|
3394
|
+
closed: false,
|
|
3395
|
+
startedAtMs: Date.now(),
|
|
3396
|
+
childTimeoutMs: void 0,
|
|
3397
|
+
failure: void 0
|
|
3398
|
+
};
|
|
3399
|
+
dispatchedChildren.set(childRunId, dispatchedChild);
|
|
3400
|
+
return dispatchedChild;
|
|
3401
|
+
});
|
|
3402
|
+
const dispatchResults = [];
|
|
3403
|
+
let firstFailureIndex;
|
|
3404
|
+
const tasks = delegates.map(async (delegate, index) => {
|
|
3405
|
+
const childRunId = childRunIds[index];
|
|
3406
|
+
if (childRunId === void 0) throw new Error("missing child run id");
|
|
3407
|
+
if (semaphore.inFlight >= effectiveForTurn) {
|
|
3408
|
+
const queuedEvent = {
|
|
3409
|
+
type: "sub-run-queued",
|
|
3410
|
+
runId,
|
|
3411
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3412
|
+
childRunId,
|
|
3413
|
+
parentRunId: runId,
|
|
3414
|
+
parentDecisionId,
|
|
3415
|
+
parentDecisionArrayIndex: index,
|
|
3416
|
+
protocol: delegate.protocol,
|
|
3417
|
+
intent: delegate.intent,
|
|
3418
|
+
depth: parentDepth + 1,
|
|
3419
|
+
queuePosition: semaphore.queued
|
|
3420
|
+
};
|
|
3421
|
+
emit(queuedEvent);
|
|
3422
|
+
recordProtocolDecision(queuedEvent);
|
|
3423
|
+
}
|
|
3424
|
+
await semaphore.acquire();
|
|
3425
|
+
try {
|
|
3426
|
+
const dispatchedChild = dispatchedForTurn[index];
|
|
3427
|
+
if (!dispatchedChild) throw new Error("missing dispatched child");
|
|
3428
|
+
if (firstFailureIndex !== void 0) {
|
|
3429
|
+
if (dispatchedChild.closed) {
|
|
3430
|
+
dispatchResults.push({
|
|
3431
|
+
index,
|
|
3432
|
+
result: {
|
|
3433
|
+
nextInput: "",
|
|
3434
|
+
taggedText: `[sub-run ${childRunId}]: skipped because the parent run aborted`,
|
|
3435
|
+
completedAtMs: Date.now()
|
|
3436
|
+
}
|
|
3437
|
+
});
|
|
3438
|
+
return;
|
|
3439
|
+
}
|
|
3440
|
+
const partialCost = emptyCost();
|
|
3441
|
+
const partialTrace = buildPartialTrace({
|
|
3442
|
+
childRunId,
|
|
3443
|
+
events: [],
|
|
3444
|
+
startedAtMs: Date.now(),
|
|
3445
|
+
protocol: delegate.protocol,
|
|
3446
|
+
tier: options.tier,
|
|
3447
|
+
modelProviderId: options.model.id,
|
|
3448
|
+
agents: options.agents,
|
|
3449
|
+
intent: delegate.intent,
|
|
3450
|
+
temperature: options.temperature,
|
|
3451
|
+
...options.seed !== void 0 ? { seed: options.seed } : {}
|
|
3452
|
+
});
|
|
3453
|
+
const failedEvent = {
|
|
3454
|
+
type: "sub-run-failed",
|
|
3455
|
+
runId,
|
|
3456
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3457
|
+
childRunId,
|
|
3458
|
+
parentRunId: runId,
|
|
3459
|
+
parentDecisionId,
|
|
3460
|
+
parentDecisionArrayIndex: index,
|
|
3461
|
+
error: {
|
|
3462
|
+
code: "aborted",
|
|
3463
|
+
message: "Sibling delegate failed; queued delegate never started.",
|
|
3464
|
+
detail: { reason: "sibling-failed" }
|
|
3465
|
+
},
|
|
3466
|
+
partialTrace,
|
|
3467
|
+
partialCost
|
|
3468
|
+
};
|
|
3469
|
+
emit(failedEvent);
|
|
3470
|
+
recordProtocolDecision(failedEvent);
|
|
3471
|
+
dispatchedChild.closed = true;
|
|
3472
|
+
dispatchResults.push({
|
|
3473
|
+
index,
|
|
3474
|
+
result: {
|
|
3475
|
+
nextInput: "",
|
|
3476
|
+
taggedText: `[sub-run ${childRunId}]: skipped because a sibling delegate failed`,
|
|
3477
|
+
completedAtMs: Date.now()
|
|
3478
|
+
}
|
|
3479
|
+
});
|
|
3480
|
+
return;
|
|
3481
|
+
}
|
|
3482
|
+
const result = await dispatchDelegate({
|
|
3483
|
+
decision: delegate,
|
|
3484
|
+
childRunId,
|
|
3485
|
+
parentDecisionId,
|
|
3486
|
+
parentDecisionArrayIndex: index,
|
|
3487
|
+
parentDepth,
|
|
3488
|
+
parentRunId: runId,
|
|
3489
|
+
options,
|
|
3490
|
+
transcript,
|
|
3491
|
+
emit,
|
|
3492
|
+
recordProtocolDecision,
|
|
3493
|
+
recordSubRunCost: (cost) => {
|
|
3494
|
+
totalCost = addCost(totalCost, cost);
|
|
3495
|
+
},
|
|
3496
|
+
dispatchedChild
|
|
3497
|
+
});
|
|
3498
|
+
dispatchResults.push({
|
|
3499
|
+
index,
|
|
3500
|
+
result
|
|
3501
|
+
});
|
|
3502
|
+
} catch (error) {
|
|
3503
|
+
firstFailureIndex ??= index;
|
|
3504
|
+
const failure = dispatchedForTurn[index]?.failure;
|
|
3505
|
+
if (delegates.length === 1 && (options.onChildFailure === "abort" || failure === void 0 || isDelegateValidationError(error))) throw error;
|
|
3506
|
+
let taggedText = `[sub-run ${childRunId} failed]: ${error instanceof Error ? error.message : String(error)}`;
|
|
3507
|
+
if (failure) {
|
|
3508
|
+
const error = failure.error;
|
|
3509
|
+
taggedText = `[sub-run ${childRunId} failed | code=${error.code} | spent=$${failure.partialCost.usd.toFixed(3)}]: ${error.message}`;
|
|
3510
|
+
}
|
|
3511
|
+
dispatchResults.push({
|
|
3512
|
+
index,
|
|
3513
|
+
result: {
|
|
3514
|
+
nextInput: "",
|
|
3515
|
+
taggedText,
|
|
3516
|
+
completedAtMs: Date.now()
|
|
3517
|
+
}
|
|
3518
|
+
});
|
|
3519
|
+
} finally {
|
|
3520
|
+
semaphore.release();
|
|
3521
|
+
}
|
|
3522
|
+
});
|
|
3523
|
+
const firstRejected = (await Promise.allSettled(tasks)).find((result) => result.status === "rejected");
|
|
3524
|
+
if (firstRejected?.status === "rejected" && delegates.length === 1 && (options.onChildFailure === "abort" || dispatchResults.length === 0)) throw firstRejected.reason;
|
|
3525
|
+
dispatchResults.sort((a, b) => a.result.completedAtMs - b.result.completedAtMs);
|
|
3526
|
+
const taggedResults = dispatchResults.map((entry) => entry.result.taggedText).join("\n\n");
|
|
3527
|
+
const currentWaveFailures = dispatchedForTurn.map((child) => child.failure).filter((failure) => failure !== void 0);
|
|
3528
|
+
if (options.onChildFailure === "abort" && currentWaveFailures.length > 0) {
|
|
3529
|
+
triggeringFailureForAbortMode ??= currentWaveFailures[0];
|
|
3530
|
+
break;
|
|
3531
|
+
}
|
|
3532
|
+
const failuresSection = buildFailuresSection(currentWaveFailures);
|
|
3533
|
+
const coordinatorAgent = options.agents[0] ?? {
|
|
3534
|
+
id: "coordinator",
|
|
3535
|
+
role: "coordinator"
|
|
3536
|
+
};
|
|
3537
|
+
dispatchInput = [
|
|
3538
|
+
buildCoordinatorPlanInput(options.intent, coordinatorAgent),
|
|
3539
|
+
taggedResults,
|
|
3540
|
+
failuresSection,
|
|
3541
|
+
"Using the sub-run results above, decide the next step (participate or delegate)."
|
|
3542
|
+
].filter((section) => Boolean(section)).join("\n\n");
|
|
3543
|
+
dispatchCount += delegates.length;
|
|
3544
|
+
}
|
|
2687
3545
|
stopIfNeeded();
|
|
2688
3546
|
}
|
|
2689
3547
|
if (!stopIfNeeded()) {
|
|
@@ -2741,7 +3599,7 @@ async function runCoordinator(options) {
|
|
|
2741
3599
|
stopIfNeeded();
|
|
2742
3600
|
}
|
|
2743
3601
|
if (!stopIfNeeded()) {
|
|
2744
|
-
|
|
3602
|
+
const synthesisOutcome = await runCoordinatorTurn({
|
|
2745
3603
|
agent: coordinator,
|
|
2746
3604
|
coordinator,
|
|
2747
3605
|
input: buildFinalSynthesisInput(options.intent, transcript, coordinator),
|
|
@@ -2759,6 +3617,17 @@ async function runCoordinator(options) {
|
|
|
2759
3617
|
emit,
|
|
2760
3618
|
recordProtocolDecision
|
|
2761
3619
|
});
|
|
3620
|
+
totalCost = synthesisOutcome.totalCost;
|
|
3621
|
+
if (Array.isArray(synthesisOutcome.decision) || synthesisOutcome.decision?.type === "delegate") throw new DogpileError({
|
|
3622
|
+
code: "invalid-configuration",
|
|
3623
|
+
message: "Coordinator final-synthesis turn cannot emit a delegate decision in Phase 1",
|
|
3624
|
+
retryable: false,
|
|
3625
|
+
detail: {
|
|
3626
|
+
kind: "delegate-validation",
|
|
3627
|
+
path: "decision",
|
|
3628
|
+
phase: "final-synthesis"
|
|
3629
|
+
}
|
|
3630
|
+
});
|
|
2762
3631
|
stopIfNeeded();
|
|
2763
3632
|
}
|
|
2764
3633
|
}
|
|
@@ -2811,6 +3680,7 @@ async function runCoordinator(options) {
|
|
|
2811
3680
|
cost: totalCost,
|
|
2812
3681
|
transcript: createTranscriptLink(transcript)
|
|
2813
3682
|
}),
|
|
3683
|
+
...triggeringFailureForAbortMode !== void 0 ? { triggeringFailureForAbortMode } : {},
|
|
2814
3684
|
events,
|
|
2815
3685
|
transcript
|
|
2816
3686
|
},
|
|
@@ -2868,6 +3738,9 @@ async function runCoordinator(options) {
|
|
|
2868
3738
|
recordProtocolDecision(event, { transcriptEntryCount: transcript.length });
|
|
2869
3739
|
}
|
|
2870
3740
|
}
|
|
3741
|
+
function isDelegateValidationError(error) {
|
|
3742
|
+
return DogpileError.isInstance(error) && error.code === "invalid-configuration" && error.detail?.["kind"] === "delegate-validation";
|
|
3743
|
+
}
|
|
2871
3744
|
async function runCoordinatorTurn(turn) {
|
|
2872
3745
|
throwIfAborted(turn.options.signal, turn.options.model.id);
|
|
2873
3746
|
const request = {
|
|
@@ -2911,7 +3784,11 @@ async function runCoordinatorTurn(turn) {
|
|
|
2911
3784
|
turn.providerCalls.push(call);
|
|
2912
3785
|
}
|
|
2913
3786
|
});
|
|
2914
|
-
const decision = parseAgentDecision(response.text
|
|
3787
|
+
const decision = parseAgentDecision(response.text, {
|
|
3788
|
+
parentProviderId: turn.options.model.id,
|
|
3789
|
+
currentDepth: turn.options.currentDepth ?? 0,
|
|
3790
|
+
maxDepth: turn.options.effectiveMaxDepth ?? Number.POSITIVE_INFINITY
|
|
3791
|
+
});
|
|
2915
3792
|
const totalCost = addCost(turn.totalCost, responseCost$2(response));
|
|
2916
3793
|
const toolCalls = await executeModelResponseToolRequests({
|
|
2917
3794
|
response,
|
|
@@ -2947,7 +3824,10 @@ async function runCoordinatorTurn(turn) {
|
|
|
2947
3824
|
phase: turn.phase,
|
|
2948
3825
|
transcriptEntryCount: turn.transcript.length
|
|
2949
3826
|
});
|
|
2950
|
-
return
|
|
3827
|
+
return {
|
|
3828
|
+
totalCost,
|
|
3829
|
+
decision
|
|
3830
|
+
};
|
|
2951
3831
|
}
|
|
2952
3832
|
async function runCoordinatorWorkerTurn(turn) {
|
|
2953
3833
|
throwIfAborted(turn.options.signal, turn.options.model.id);
|
|
@@ -2992,7 +3872,21 @@ async function runCoordinatorWorkerTurn(turn) {
|
|
|
2992
3872
|
turn.providerCallSlots[turn.providerCallIndex] = call;
|
|
2993
3873
|
}
|
|
2994
3874
|
});
|
|
2995
|
-
const decision = parseAgentDecision(response.text
|
|
3875
|
+
const decision = parseAgentDecision(response.text, {
|
|
3876
|
+
parentProviderId: turn.options.model.id,
|
|
3877
|
+
currentDepth: turn.options.currentDepth ?? 0,
|
|
3878
|
+
maxDepth: turn.options.effectiveMaxDepth ?? Number.POSITIVE_INFINITY
|
|
3879
|
+
});
|
|
3880
|
+
if (Array.isArray(decision) || decision?.type === "delegate") throw new DogpileError({
|
|
3881
|
+
code: "invalid-configuration",
|
|
3882
|
+
message: "Workers cannot emit delegate decisions in Phase 1",
|
|
3883
|
+
retryable: false,
|
|
3884
|
+
detail: {
|
|
3885
|
+
kind: "delegate-validation",
|
|
3886
|
+
path: "decision",
|
|
3887
|
+
phase: "worker"
|
|
3888
|
+
}
|
|
3889
|
+
});
|
|
2996
3890
|
const toolCalls = await executeModelResponseToolRequests({
|
|
2997
3891
|
response,
|
|
2998
3892
|
executor: turn.toolExecutor,
|
|
@@ -3019,6 +3913,30 @@ function buildSystemPrompt$2(agent, coordinator) {
|
|
|
3019
3913
|
function buildCoordinatorPlanInput(intent, coordinator) {
|
|
3020
3914
|
return `Mission: ${intent}\nCoordinator ${coordinator.id}: assign the work, name the plan, and provide the first contribution.`;
|
|
3021
3915
|
}
|
|
3916
|
+
function buildFailuresSection(failures) {
|
|
3917
|
+
if (failures.length === 0) return null;
|
|
3918
|
+
return [
|
|
3919
|
+
"## Sub-run failures since last decision",
|
|
3920
|
+
"",
|
|
3921
|
+
"```json",
|
|
3922
|
+
JSON.stringify(failures, null, 2),
|
|
3923
|
+
"```"
|
|
3924
|
+
].join("\n");
|
|
3925
|
+
}
|
|
3926
|
+
function dispatchWaveFailureFromEvent(intent, event) {
|
|
3927
|
+
const reason = typeof event.error.detail?.["reason"] === "string" ? event.error.detail["reason"] : void 0;
|
|
3928
|
+
if (reason === "sibling-failed" || reason === "parent-aborted") return;
|
|
3929
|
+
return {
|
|
3930
|
+
childRunId: event.childRunId,
|
|
3931
|
+
intent,
|
|
3932
|
+
error: {
|
|
3933
|
+
code: event.error.code,
|
|
3934
|
+
message: event.error.message,
|
|
3935
|
+
...reason !== void 0 ? { detail: { reason } } : {}
|
|
3936
|
+
},
|
|
3937
|
+
partialCost: { usd: event.partialCost.usd }
|
|
3938
|
+
};
|
|
3939
|
+
}
|
|
3022
3940
|
function buildWorkerInput(intent, transcript, coordinator) {
|
|
3023
3941
|
const prior = transcript.map((entry) => `${entry.role} (${entry.agentId}): ${entry.output}`).join("\n\n");
|
|
3024
3942
|
return `Mission: ${intent}\n\nCoordinator: ${coordinator.id}\nPrior contributions:\n${prior}\n\nFollow the coordinator-managed plan and provide your assigned contribution.`;
|
|
@@ -3035,6 +3953,382 @@ function responseCost$2(response) {
|
|
|
3035
3953
|
totalTokens: response.usage?.totalTokens ?? 0
|
|
3036
3954
|
};
|
|
3037
3955
|
}
|
|
3956
|
+
/**
|
|
3957
|
+
* Dispatch a single delegate decision as a recursive sub-run.
|
|
3958
|
+
*
|
|
3959
|
+
* D-11: child reuses the parent provider object verbatim.
|
|
3960
|
+
* D-16: `recursive: true` flag set when both parent and child protocol are
|
|
3961
|
+
* `coordinator`.
|
|
3962
|
+
* D-17: tagged result text appended to the next coordinator prompt.
|
|
3963
|
+
* D-18: synthetic transcript entry pushed for replay/provenance.
|
|
3964
|
+
*
|
|
3965
|
+
* On thrown error from the child engine, builds `partialTrace` from a locally
|
|
3966
|
+
* tee'd `childEvents` buffer — `runProtocol`'s error contract is unchanged.
|
|
3967
|
+
*/
|
|
3968
|
+
async function dispatchDelegate(input) {
|
|
3969
|
+
const { decision, options } = input;
|
|
3970
|
+
if (options.effectiveMaxDepth !== void 0) assertDepthWithinLimit(input.parentDepth, options.effectiveMaxDepth);
|
|
3971
|
+
const childRunId = input.childRunId ?? createRunId();
|
|
3972
|
+
const recursive = decision.protocol === "coordinator";
|
|
3973
|
+
const decisionTimeoutMs = decision.budget?.timeoutMs;
|
|
3974
|
+
const parentDeadlineMs = options.parentDeadlineMs;
|
|
3975
|
+
const remainingMs = parentDeadlineMs !== void 0 ? Math.max(0, parentDeadlineMs - Date.now()) : void 0;
|
|
3976
|
+
if (parentDeadlineMs !== void 0 && remainingMs === 0) throw new DogpileError({
|
|
3977
|
+
code: "aborted",
|
|
3978
|
+
message: "Parent deadline elapsed before sub-run dispatch.",
|
|
3979
|
+
retryable: false,
|
|
3980
|
+
providerId: options.model.id,
|
|
3981
|
+
detail: { reason: "timeout" }
|
|
3982
|
+
});
|
|
3983
|
+
let childTimeoutMs;
|
|
3984
|
+
let clampedFrom;
|
|
3985
|
+
if (remainingMs !== void 0) if (decisionTimeoutMs !== void 0) if (decisionTimeoutMs > remainingMs) {
|
|
3986
|
+
clampedFrom = decisionTimeoutMs;
|
|
3987
|
+
childTimeoutMs = remainingMs;
|
|
3988
|
+
} else childTimeoutMs = decisionTimeoutMs;
|
|
3989
|
+
else childTimeoutMs = remainingMs;
|
|
3990
|
+
else if (decisionTimeoutMs !== void 0) childTimeoutMs = decisionTimeoutMs;
|
|
3991
|
+
else if (options.defaultSubRunTimeoutMs !== void 0) childTimeoutMs = options.defaultSubRunTimeoutMs;
|
|
3992
|
+
if (!options.runProtocol) throw new DogpileError({
|
|
3993
|
+
code: "invalid-configuration",
|
|
3994
|
+
message: "Coordinator delegate dispatch requires the engine `runProtocol` callback. Use `Dogpile.run` / `createEngine` rather than calling `runCoordinator` directly when delegate is in play.",
|
|
3995
|
+
retryable: false,
|
|
3996
|
+
detail: {
|
|
3997
|
+
kind: "delegate-validation",
|
|
3998
|
+
path: "runProtocol"
|
|
3999
|
+
}
|
|
4000
|
+
});
|
|
4001
|
+
const childEvents = input.dispatchedChild.childEvents;
|
|
4002
|
+
const parentEmit = input.emit;
|
|
4003
|
+
const teedEmit = (event) => {
|
|
4004
|
+
childEvents.push(event);
|
|
4005
|
+
if (input.dispatchedChild.closed) return;
|
|
4006
|
+
if (options.streamEvents && options.emit) {
|
|
4007
|
+
const inbound = event.parentRunIds;
|
|
4008
|
+
options.emit({
|
|
4009
|
+
...event,
|
|
4010
|
+
parentRunIds: [input.parentRunId, ...inbound ?? []]
|
|
4011
|
+
});
|
|
4012
|
+
}
|
|
4013
|
+
};
|
|
4014
|
+
const childStartedAt = Date.now();
|
|
4015
|
+
input.dispatchedChild.startedAtMs = childStartedAt;
|
|
4016
|
+
if (clampedFrom !== void 0 && childTimeoutMs !== void 0) {
|
|
4017
|
+
const clampEvent = {
|
|
4018
|
+
type: "sub-run-budget-clamped",
|
|
4019
|
+
runId: input.parentRunId,
|
|
4020
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4021
|
+
childRunId,
|
|
4022
|
+
parentRunId: input.parentRunId,
|
|
4023
|
+
parentDecisionId: input.parentDecisionId,
|
|
4024
|
+
requestedTimeoutMs: clampedFrom,
|
|
4025
|
+
clampedTimeoutMs: childTimeoutMs,
|
|
4026
|
+
reason: "exceeded-parent-remaining"
|
|
4027
|
+
};
|
|
4028
|
+
input.emit(clampEvent);
|
|
4029
|
+
input.recordProtocolDecision(clampEvent);
|
|
4030
|
+
}
|
|
4031
|
+
const startEvent = {
|
|
4032
|
+
type: "sub-run-started",
|
|
4033
|
+
runId: input.parentRunId,
|
|
4034
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4035
|
+
childRunId,
|
|
4036
|
+
parentRunId: input.parentRunId,
|
|
4037
|
+
parentDecisionId: input.parentDecisionId,
|
|
4038
|
+
parentDecisionArrayIndex: input.parentDecisionArrayIndex,
|
|
4039
|
+
protocol: decision.protocol,
|
|
4040
|
+
intent: decision.intent,
|
|
4041
|
+
depth: input.parentDepth + 1,
|
|
4042
|
+
...recursive ? { recursive: true } : {}
|
|
4043
|
+
};
|
|
4044
|
+
parentEmit(startEvent);
|
|
4045
|
+
input.recordProtocolDecision(startEvent);
|
|
4046
|
+
const parentSignal = options.signal;
|
|
4047
|
+
let removeParentAbortListener;
|
|
4048
|
+
if (parentSignal !== void 0) if (parentSignal.aborted) input.dispatchedChild.controller.abort(parentSignal.reason);
|
|
4049
|
+
else {
|
|
4050
|
+
const handler = () => {
|
|
4051
|
+
input.dispatchedChild.controller.abort(parentSignal.reason);
|
|
4052
|
+
};
|
|
4053
|
+
parentSignal.addEventListener("abort", handler, { once: true });
|
|
4054
|
+
removeParentAbortListener = () => {
|
|
4055
|
+
parentSignal.removeEventListener("abort", handler);
|
|
4056
|
+
};
|
|
4057
|
+
}
|
|
4058
|
+
input.dispatchedChild.removeParentListener = removeParentAbortListener;
|
|
4059
|
+
input.dispatchedChild.started = true;
|
|
4060
|
+
input.dispatchedChild.childTimeoutMs = childTimeoutMs;
|
|
4061
|
+
const childDeadlineReason = childTimeoutMs !== void 0 && parentDeadlineMs === void 0 ? createEngineDeadlineTimeoutError(options.model.id, childTimeoutMs) : void 0;
|
|
4062
|
+
const childDeadlineTimer = childDeadlineReason !== void 0 ? setTimeout(() => {
|
|
4063
|
+
input.dispatchedChild.controller.abort(childDeadlineReason);
|
|
4064
|
+
}, childTimeoutMs) : void 0;
|
|
4065
|
+
const childOptions = {
|
|
4066
|
+
intent: decision.intent,
|
|
4067
|
+
protocol: decision.protocol,
|
|
4068
|
+
tier: options.tier,
|
|
4069
|
+
model: options.model,
|
|
4070
|
+
agents: options.agents,
|
|
4071
|
+
tools: options.tools,
|
|
4072
|
+
temperature: options.temperature,
|
|
4073
|
+
...childTimeoutMs !== void 0 ? { budget: { timeoutMs: childTimeoutMs } } : {},
|
|
4074
|
+
signal: input.dispatchedChild.controller.signal,
|
|
4075
|
+
emit: teedEmit,
|
|
4076
|
+
...options.streamEvents !== void 0 ? { streamEvents: options.streamEvents } : {},
|
|
4077
|
+
currentDepth: input.parentDepth + 1,
|
|
4078
|
+
...options.effectiveMaxDepth !== void 0 ? { effectiveMaxDepth: options.effectiveMaxDepth } : {},
|
|
4079
|
+
...options.effectiveMaxConcurrentChildren !== void 0 ? { effectiveMaxConcurrentChildren: options.effectiveMaxConcurrentChildren } : {},
|
|
4080
|
+
...options.onChildFailure !== void 0 ? { onChildFailure: options.onChildFailure } : {},
|
|
4081
|
+
...parentDeadlineMs !== void 0 ? { parentDeadlineMs } : {},
|
|
4082
|
+
...options.defaultSubRunTimeoutMs !== void 0 ? { defaultSubRunTimeoutMs: options.defaultSubRunTimeoutMs } : {}
|
|
4083
|
+
};
|
|
4084
|
+
let subResult;
|
|
4085
|
+
try {
|
|
4086
|
+
subResult = await options.runProtocol(childOptions);
|
|
4087
|
+
} catch (error) {
|
|
4088
|
+
if (childDeadlineTimer !== void 0) clearTimeout(childDeadlineTimer);
|
|
4089
|
+
removeParentAbortListener?.();
|
|
4090
|
+
if (input.dispatchedChild.closed) {
|
|
4091
|
+
const enrichedError = enrichAbortErrorWithParentReason(error, parentSignal);
|
|
4092
|
+
if (DogpileError.isInstance(enrichedError)) throw enrichedError;
|
|
4093
|
+
throw error;
|
|
4094
|
+
}
|
|
4095
|
+
const failedDecision = {
|
|
4096
|
+
type: "delegate",
|
|
4097
|
+
protocol: decision.protocol,
|
|
4098
|
+
intent: decision.intent,
|
|
4099
|
+
...decision.model !== void 0 ? { model: decision.model } : {},
|
|
4100
|
+
...decision.budget !== void 0 ? { budget: decision.budget } : {}
|
|
4101
|
+
};
|
|
4102
|
+
const partialTrace = buildPartialTrace({
|
|
4103
|
+
childRunId,
|
|
4104
|
+
events: childEvents,
|
|
4105
|
+
startedAtMs: childStartedAt,
|
|
4106
|
+
protocol: decision.protocol,
|
|
4107
|
+
tier: options.tier,
|
|
4108
|
+
modelProviderId: options.model.id,
|
|
4109
|
+
agents: options.agents,
|
|
4110
|
+
intent: decision.intent,
|
|
4111
|
+
temperature: options.temperature,
|
|
4112
|
+
...childTimeoutMs !== void 0 ? { childTimeoutMs } : {},
|
|
4113
|
+
...options.seed !== void 0 ? { seed: options.seed } : {}
|
|
4114
|
+
});
|
|
4115
|
+
const enrichedError = enrichProviderTimeoutSource(enrichAbortErrorWithParentReason(error, parentSignal), {
|
|
4116
|
+
...decisionTimeoutMs !== void 0 ? { decisionTimeoutMs } : {},
|
|
4117
|
+
...options.defaultSubRunTimeoutMs !== void 0 ? { engineDefaultTimeoutMs: options.defaultSubRunTimeoutMs } : {}
|
|
4118
|
+
});
|
|
4119
|
+
if (DogpileError.isInstance(enrichedError)) options.failureInstancesByChildRunId?.set(childRunId, enrichedError);
|
|
4120
|
+
const errorPayload = errorPayloadFromUnknown(enrichedError, failedDecision);
|
|
4121
|
+
const partialCost = lastCostBearingEventCost(childEvents) ?? emptyCost();
|
|
4122
|
+
input.recordSubRunCost(partialCost);
|
|
4123
|
+
const failEvent = {
|
|
4124
|
+
type: "sub-run-failed",
|
|
4125
|
+
runId: input.parentRunId,
|
|
4126
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4127
|
+
childRunId,
|
|
4128
|
+
parentRunId: input.parentRunId,
|
|
4129
|
+
parentDecisionId: input.parentDecisionId,
|
|
4130
|
+
parentDecisionArrayIndex: input.parentDecisionArrayIndex,
|
|
4131
|
+
error: errorPayload,
|
|
4132
|
+
partialTrace,
|
|
4133
|
+
partialCost
|
|
4134
|
+
};
|
|
4135
|
+
parentEmit(failEvent);
|
|
4136
|
+
input.recordProtocolDecision(failEvent);
|
|
4137
|
+
input.dispatchedChild.closed = true;
|
|
4138
|
+
input.dispatchedChild.failure = dispatchWaveFailureFromEvent(decision.intent, failEvent);
|
|
4139
|
+
if (DogpileError.isInstance(enrichedError)) throw enrichedError;
|
|
4140
|
+
throw new DogpileError({
|
|
4141
|
+
code: "invalid-configuration",
|
|
4142
|
+
message: error instanceof Error ? error.message : String(error),
|
|
4143
|
+
retryable: false,
|
|
4144
|
+
detail: {
|
|
4145
|
+
kind: "delegate-validation",
|
|
4146
|
+
path: "decision",
|
|
4147
|
+
reason: "child-run-failed"
|
|
4148
|
+
}
|
|
4149
|
+
});
|
|
4150
|
+
}
|
|
4151
|
+
if (childDeadlineTimer !== void 0) clearTimeout(childDeadlineTimer);
|
|
4152
|
+
removeParentAbortListener?.();
|
|
4153
|
+
input.recordSubRunCost(subResult.cost);
|
|
4154
|
+
const completedEvent = {
|
|
4155
|
+
type: "sub-run-completed",
|
|
4156
|
+
runId: input.parentRunId,
|
|
4157
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4158
|
+
childRunId,
|
|
4159
|
+
parentRunId: input.parentRunId,
|
|
4160
|
+
parentDecisionId: input.parentDecisionId,
|
|
4161
|
+
parentDecisionArrayIndex: input.parentDecisionArrayIndex,
|
|
4162
|
+
subResult
|
|
4163
|
+
};
|
|
4164
|
+
parentEmit(completedEvent);
|
|
4165
|
+
input.recordProtocolDecision(completedEvent);
|
|
4166
|
+
input.dispatchedChild.closed = true;
|
|
4167
|
+
if (parentSignal?.aborted) {
|
|
4168
|
+
const abortMarker = {
|
|
4169
|
+
type: "sub-run-parent-aborted",
|
|
4170
|
+
runId: input.parentRunId,
|
|
4171
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4172
|
+
childRunId,
|
|
4173
|
+
parentRunId: input.parentRunId,
|
|
4174
|
+
reason: "parent-aborted"
|
|
4175
|
+
};
|
|
4176
|
+
parentEmit(abortMarker);
|
|
4177
|
+
input.recordProtocolDecision(abortMarker);
|
|
4178
|
+
throw enrichAbortErrorWithParentReason(createAbortErrorFromSignal(parentSignal, options.model.id), parentSignal);
|
|
4179
|
+
}
|
|
4180
|
+
const decisionAsJson = {
|
|
4181
|
+
type: "delegate",
|
|
4182
|
+
protocol: decision.protocol,
|
|
4183
|
+
intent: decision.intent,
|
|
4184
|
+
...decision.model !== void 0 ? { model: decision.model } : {},
|
|
4185
|
+
...decision.budget !== void 0 ? { budget: decision.budget } : {}
|
|
4186
|
+
};
|
|
4187
|
+
const taggedText = renderSubRunResult(childRunId, subResult);
|
|
4188
|
+
input.transcript.push({
|
|
4189
|
+
agentId: `sub-run:${childRunId}`,
|
|
4190
|
+
role: "delegate-result",
|
|
4191
|
+
input: JSON.stringify(decisionAsJson),
|
|
4192
|
+
output: taggedText
|
|
4193
|
+
});
|
|
4194
|
+
const coordinatorAgent = options.agents[0];
|
|
4195
|
+
return {
|
|
4196
|
+
nextInput: `${buildCoordinatorPlanInput(input.options.intent, coordinatorAgent ?? {
|
|
4197
|
+
id: "coordinator",
|
|
4198
|
+
role: "coordinator"
|
|
4199
|
+
})}\n\n${taggedText}\n\nUsing the sub-run result above, decide the next step (participate or delegate).`,
|
|
4200
|
+
taggedText,
|
|
4201
|
+
completedAtMs: Date.now()
|
|
4202
|
+
};
|
|
4203
|
+
}
|
|
4204
|
+
/**
|
|
4205
|
+
* D-17 prompt-injection helper. Renders a child `RunResult` as the canonical
|
|
4206
|
+
* tagged-result block injected into the parent coordinator's next prompt.
|
|
4207
|
+
*
|
|
4208
|
+
* Format:
|
|
4209
|
+
* `[sub-run <childRunId>]: <output>`
|
|
4210
|
+
* `[sub-run <childRunId> stats]: turns=<N> costUsd=<X> durationMs=<Y>`
|
|
4211
|
+
*
|
|
4212
|
+
* The stats line is a soft contract — field names stable, ordering stable.
|
|
4213
|
+
*/
|
|
4214
|
+
function renderSubRunResult(childRunId, subResult) {
|
|
4215
|
+
const turns = subResult.transcript.length;
|
|
4216
|
+
const costUsd = subResult.cost.usd ?? 0;
|
|
4217
|
+
const startedAt = subResult.trace.events[0]?.at;
|
|
4218
|
+
const endedAt = subResult.trace.events.at(-1)?.at;
|
|
4219
|
+
const durationMs = startedAt && endedAt ? Math.max(0, Date.parse(endedAt) - Date.parse(startedAt)) : 0;
|
|
4220
|
+
return [`[sub-run ${childRunId}]: ${subResult.output}`, `[sub-run ${childRunId} stats]: turns=${turns} costUsd=${costUsd} durationMs=${durationMs}`].join("\n");
|
|
4221
|
+
}
|
|
4222
|
+
/**
|
|
4223
|
+
* Build a JSON-serializable {@link Trace} for `sub-run-failed.partialTrace`
|
|
4224
|
+
* from a buffered tee of child emits. Keeps `runProtocol`'s error contract
|
|
4225
|
+
* unchanged — Plan 03 step 8.
|
|
4226
|
+
*/
|
|
4227
|
+
function buildPartialTrace(input) {
|
|
4228
|
+
const protocolName = typeof input.protocol === "string" ? input.protocol : input.protocol.kind;
|
|
4229
|
+
const protocolConfig = typeof input.protocol === "string" ? { kind: input.protocol } : input.protocol;
|
|
4230
|
+
return {
|
|
4231
|
+
schemaVersion: "1.0",
|
|
4232
|
+
runId: input.childRunId,
|
|
4233
|
+
protocol: protocolName,
|
|
4234
|
+
tier: input.tier,
|
|
4235
|
+
modelProviderId: input.modelProviderId,
|
|
4236
|
+
agentsUsed: input.agents,
|
|
4237
|
+
inputs: createReplayTraceRunInputs({
|
|
4238
|
+
intent: input.intent,
|
|
4239
|
+
protocol: protocolConfig,
|
|
4240
|
+
tier: input.tier,
|
|
4241
|
+
modelProviderId: input.modelProviderId,
|
|
4242
|
+
agents: input.agents,
|
|
4243
|
+
temperature: input.temperature
|
|
4244
|
+
}),
|
|
4245
|
+
budget: createReplayTraceBudget({
|
|
4246
|
+
tier: input.tier,
|
|
4247
|
+
...input.childTimeoutMs !== void 0 ? { caps: { timeoutMs: input.childTimeoutMs } } : {}
|
|
4248
|
+
}),
|
|
4249
|
+
budgetStateChanges: createReplayTraceBudgetStateChanges(input.events),
|
|
4250
|
+
seed: createReplayTraceSeed(input.seed),
|
|
4251
|
+
protocolDecisions: [],
|
|
4252
|
+
providerCalls: [],
|
|
4253
|
+
finalOutput: {
|
|
4254
|
+
kind: "replay-trace-final-output",
|
|
4255
|
+
output: "",
|
|
4256
|
+
cost: emptyCost(),
|
|
4257
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4258
|
+
transcript: createTranscriptLink([])
|
|
4259
|
+
},
|
|
4260
|
+
events: input.events,
|
|
4261
|
+
transcript: []
|
|
4262
|
+
};
|
|
4263
|
+
}
|
|
4264
|
+
/**
|
|
4265
|
+
* BUDGET-01 / D-08: when a child sub-run threw because the parent's signal
|
|
4266
|
+
* aborted, lock the `detail.reason` discriminator on the resulting
|
|
4267
|
+
* `code: "aborted"` error. Preserves any pre-existing detail keys (e.g.,
|
|
4268
|
+
* `detail.status: "cancelled"` attached by `createStreamCancellationError`).
|
|
4269
|
+
*
|
|
4270
|
+
* No-op when:
|
|
4271
|
+
* - parent.signal is undefined or not aborted (child failure was unrelated)
|
|
4272
|
+
* - error is not a DogpileError with `code: "aborted"`
|
|
4273
|
+
* - error already has a `detail.reason` set (preserve upstream classification)
|
|
4274
|
+
*/
|
|
4275
|
+
function enrichAbortErrorWithParentReason(error, parentSignal) {
|
|
4276
|
+
if (parentSignal === void 0 || !parentSignal.aborted) return error;
|
|
4277
|
+
if (!DogpileError.isInstance(error) || error.code !== "aborted") return error;
|
|
4278
|
+
const existingDetail = error.detail ?? {};
|
|
4279
|
+
if (existingDetail["reason"] !== void 0) return error;
|
|
4280
|
+
const reason = classifyAbortReason(parentSignal.reason);
|
|
4281
|
+
return new DogpileError({
|
|
4282
|
+
code: "aborted",
|
|
4283
|
+
message: error.message,
|
|
4284
|
+
retryable: error.retryable ?? false,
|
|
4285
|
+
...error.providerId !== void 0 ? { providerId: error.providerId } : {},
|
|
4286
|
+
detail: {
|
|
4287
|
+
...existingDetail,
|
|
4288
|
+
reason
|
|
4289
|
+
},
|
|
4290
|
+
...error.cause !== void 0 ? { cause: error.cause } : {}
|
|
4291
|
+
});
|
|
4292
|
+
}
|
|
4293
|
+
function enrichProviderTimeoutSource(error, context) {
|
|
4294
|
+
if (!DogpileError.isInstance(error) || error.code !== "provider-timeout") return error;
|
|
4295
|
+
const existingDetail = error.detail ?? {};
|
|
4296
|
+
if (existingDetail["source"] !== void 0) return error;
|
|
4297
|
+
const source = classifyChildTimeoutSource(error, {
|
|
4298
|
+
...context,
|
|
4299
|
+
isProviderError: true
|
|
4300
|
+
});
|
|
4301
|
+
return new DogpileError({
|
|
4302
|
+
code: "provider-timeout",
|
|
4303
|
+
message: error.message,
|
|
4304
|
+
retryable: error.retryable ?? true,
|
|
4305
|
+
...error.providerId !== void 0 ? { providerId: error.providerId } : {},
|
|
4306
|
+
detail: {
|
|
4307
|
+
...existingDetail,
|
|
4308
|
+
source
|
|
4309
|
+
},
|
|
4310
|
+
...error.cause !== void 0 ? { cause: error.cause } : {}
|
|
4311
|
+
});
|
|
4312
|
+
}
|
|
4313
|
+
function errorPayloadFromUnknown(error, failedDecision) {
|
|
4314
|
+
if (DogpileError.isInstance(error)) {
|
|
4315
|
+
const detail = {
|
|
4316
|
+
...error.detail ?? {},
|
|
4317
|
+
failedDecision
|
|
4318
|
+
};
|
|
4319
|
+
return {
|
|
4320
|
+
code: error.code,
|
|
4321
|
+
message: error.message,
|
|
4322
|
+
...error.providerId !== void 0 ? { providerId: error.providerId } : {},
|
|
4323
|
+
detail
|
|
4324
|
+
};
|
|
4325
|
+
}
|
|
4326
|
+
return {
|
|
4327
|
+
code: "invalid-configuration",
|
|
4328
|
+
message: error instanceof Error ? error.message : String(error),
|
|
4329
|
+
detail: { failedDecision }
|
|
4330
|
+
};
|
|
4331
|
+
}
|
|
3038
4332
|
//#endregion
|
|
3039
4333
|
//#region src/runtime/sequential.ts
|
|
3040
4334
|
async function runSequential(options) {
|
|
@@ -3173,7 +4467,8 @@ async function runSequential(options) {
|
|
|
3173
4467
|
});
|
|
3174
4468
|
if (stopIfNeeded()) break;
|
|
3175
4469
|
}
|
|
3176
|
-
const
|
|
4470
|
+
const reversed = [...transcript].reverse();
|
|
4471
|
+
const output = reversed.find((entry) => isParticipatingDecision(entry.decision))?.output ?? reversed.find((entry) => entry.decision === void 0)?.output ?? "";
|
|
3177
4472
|
throwIfAborted(options.signal, options.model.id);
|
|
3178
4473
|
const final = {
|
|
3179
4474
|
type: "final",
|
|
@@ -3573,6 +4868,8 @@ function responseCost(response) {
|
|
|
3573
4868
|
}
|
|
3574
4869
|
//#endregion
|
|
3575
4870
|
//#region src/runtime/engine.ts
|
|
4871
|
+
var DEFAULT_MAX_DEPTH = 4;
|
|
4872
|
+
var DEFAULT_MAX_CONCURRENT_CHILDREN = 4;
|
|
3576
4873
|
var defaultHighLevelProtocol = "sequential";
|
|
3577
4874
|
var defaultHighLevelTier = "balanced";
|
|
3578
4875
|
/**
|
|
@@ -3593,9 +4890,20 @@ function createEngine(options) {
|
|
|
3593
4890
|
const temperature = options.temperature ?? tierTemperature(options.tier);
|
|
3594
4891
|
const agents = orderAgentsForTemperature(options.agents ?? defaultAgents(), temperature, options.seed);
|
|
3595
4892
|
const terminate = options.terminate ?? (options.budget ? conditionFromBudget(options.budget) : void 0);
|
|
4893
|
+
const engineMaxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
4894
|
+
const engineMaxConcurrentChildren = options.maxConcurrentChildren ?? DEFAULT_MAX_CONCURRENT_CHILDREN;
|
|
4895
|
+
const engineOnChildFailure = options.onChildFailure;
|
|
3596
4896
|
return {
|
|
3597
|
-
run(intent) {
|
|
4897
|
+
run(intent, runOptions) {
|
|
3598
4898
|
validateMissionIntent(intent);
|
|
4899
|
+
validateRunCallOptions(runOptions);
|
|
4900
|
+
validateProviderLocality(options.model, "model");
|
|
4901
|
+
const effectiveMaxDepth = Math.min(engineMaxDepth, runOptions?.maxDepth ?? Number.POSITIVE_INFINITY);
|
|
4902
|
+
assertRunDoesNotRaiseEngineMax("maxConcurrentChildren", runOptions?.maxConcurrentChildren, engineMaxConcurrentChildren);
|
|
4903
|
+
const effectiveMaxConcurrentChildren = Math.min(engineMaxConcurrentChildren, runOptions?.maxConcurrentChildren ?? Number.POSITIVE_INFINITY);
|
|
4904
|
+
const onChildFailure = resolveOnChildFailure(runOptions?.onChildFailure, engineOnChildFailure);
|
|
4905
|
+
const startedAtMs = Date.now();
|
|
4906
|
+
const parentDeadlineMs = options.budget?.timeoutMs !== void 0 ? startedAtMs + options.budget.timeoutMs : void 0;
|
|
3599
4907
|
return runNonStreamingProtocol({
|
|
3600
4908
|
intent,
|
|
3601
4909
|
protocol,
|
|
@@ -3609,11 +4917,23 @@ function createEngine(options) {
|
|
|
3609
4917
|
...options.signal !== void 0 ? { signal: options.signal } : {},
|
|
3610
4918
|
...terminate ? { terminate } : {},
|
|
3611
4919
|
...options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {},
|
|
3612
|
-
...options.evaluate ? { evaluate: options.evaluate } : {}
|
|
4920
|
+
...options.evaluate ? { evaluate: options.evaluate } : {},
|
|
4921
|
+
currentDepth: 0,
|
|
4922
|
+
effectiveMaxDepth,
|
|
4923
|
+
effectiveMaxConcurrentChildren,
|
|
4924
|
+
onChildFailure,
|
|
4925
|
+
...parentDeadlineMs !== void 0 ? { parentDeadlineMs } : {},
|
|
4926
|
+
...options.defaultSubRunTimeoutMs !== void 0 ? { defaultSubRunTimeoutMs: options.defaultSubRunTimeoutMs } : {}
|
|
3613
4927
|
});
|
|
3614
4928
|
},
|
|
3615
|
-
stream(intent) {
|
|
4929
|
+
stream(intent, runOptions) {
|
|
3616
4930
|
validateMissionIntent(intent);
|
|
4931
|
+
validateRunCallOptions(runOptions);
|
|
4932
|
+
validateProviderLocality(options.model, "model");
|
|
4933
|
+
const effectiveMaxDepth = Math.min(engineMaxDepth, runOptions?.maxDepth ?? Number.POSITIVE_INFINITY);
|
|
4934
|
+
assertRunDoesNotRaiseEngineMax("maxConcurrentChildren", runOptions?.maxConcurrentChildren, engineMaxConcurrentChildren);
|
|
4935
|
+
const effectiveMaxConcurrentChildren = Math.min(engineMaxConcurrentChildren, runOptions?.maxConcurrentChildren ?? Number.POSITIVE_INFINITY);
|
|
4936
|
+
const onChildFailure = resolveOnChildFailure(runOptions?.onChildFailure, engineOnChildFailure);
|
|
3617
4937
|
const pendingEvents = [];
|
|
3618
4938
|
const pendingResolvers = [];
|
|
3619
4939
|
const emittedEvents = [];
|
|
@@ -3630,7 +4950,10 @@ function createEngine(options) {
|
|
|
3630
4950
|
const abortRace = createAbortRace(abortController.signal, options.model.id);
|
|
3631
4951
|
let complete = false;
|
|
3632
4952
|
let lastRunId = "";
|
|
4953
|
+
let rootRunId;
|
|
3633
4954
|
let pendingFinalEvent;
|
|
4955
|
+
let activeAbortDrain;
|
|
4956
|
+
const failureInstancesByChildRunId = /* @__PURE__ */ new Map();
|
|
3634
4957
|
let status = "running";
|
|
3635
4958
|
let resolveResult;
|
|
3636
4959
|
let rejectResult;
|
|
@@ -3676,6 +4999,8 @@ function createEngine(options) {
|
|
|
3676
4999
|
async function execute() {
|
|
3677
5000
|
if (status !== "running") return;
|
|
3678
5001
|
try {
|
|
5002
|
+
const streamStartedAtMs = Date.now();
|
|
5003
|
+
const streamParentDeadlineMs = options.budget?.timeoutMs !== void 0 ? streamStartedAtMs + options.budget.timeoutMs : void 0;
|
|
3679
5004
|
const baseResult = await abortRace.run(runProtocol({
|
|
3680
5005
|
intent,
|
|
3681
5006
|
protocol,
|
|
@@ -3688,17 +5013,32 @@ function createEngine(options) {
|
|
|
3688
5013
|
...options.seed !== void 0 ? { seed: options.seed } : {},
|
|
3689
5014
|
signal: abortController.signal,
|
|
3690
5015
|
...terminate ? { terminate } : {},
|
|
5016
|
+
currentDepth: 0,
|
|
5017
|
+
effectiveMaxDepth,
|
|
5018
|
+
effectiveMaxConcurrentChildren,
|
|
5019
|
+
onChildFailure,
|
|
5020
|
+
...streamParentDeadlineMs !== void 0 ? { parentDeadlineMs: streamParentDeadlineMs } : {},
|
|
5021
|
+
...options.defaultSubRunTimeoutMs !== void 0 ? { defaultSubRunTimeoutMs: options.defaultSubRunTimeoutMs } : {},
|
|
5022
|
+
streamEvents: true,
|
|
3691
5023
|
emit(event) {
|
|
3692
5024
|
if (status !== "running") return;
|
|
5025
|
+
const parentRunIds = event.parentRunIds;
|
|
5026
|
+
if (rootRunId === void 0 && parentRunIds === void 0) rootRunId = event.runId;
|
|
3693
5027
|
lastRunId = event.runId;
|
|
3694
|
-
if (event.type === "final") {
|
|
5028
|
+
if (event.type === "final" && event.runId === rootRunId) {
|
|
3695
5029
|
pendingFinalEvent = event;
|
|
3696
5030
|
return;
|
|
3697
5031
|
}
|
|
3698
5032
|
publish(event);
|
|
3699
|
-
}
|
|
5033
|
+
},
|
|
5034
|
+
registerAbortDrain(drain) {
|
|
5035
|
+
activeAbortDrain = drain;
|
|
5036
|
+
},
|
|
5037
|
+
failureInstancesByChildRunId
|
|
3700
5038
|
}));
|
|
3701
5039
|
if (status !== "running") return;
|
|
5040
|
+
const terminalThrow = resolveRuntimeTerminalThrow(baseResult.trace, failureInstancesByChildRunId);
|
|
5041
|
+
if (terminalThrow) throw terminalThrow;
|
|
3702
5042
|
const finalizedResult = await abortRace.run(applyRunEvaluation(baseResult, options.evaluate));
|
|
3703
5043
|
if (status !== "running") return;
|
|
3704
5044
|
const finalEvent = finalizedResult.trace.events.at(-1);
|
|
@@ -3711,6 +5051,10 @@ function createEngine(options) {
|
|
|
3711
5051
|
if (isStreamHandleStatus(status, "cancelled")) return;
|
|
3712
5052
|
const runtimeError = timeoutLifecycle.translateError(error);
|
|
3713
5053
|
status = isCancellationError(runtimeError) ? "cancelled" : "failed";
|
|
5054
|
+
if (shouldPublishAborted(runtimeError)) {
|
|
5055
|
+
activeAbortDrain?.(runtimeError);
|
|
5056
|
+
publish(createStreamAbortedEvent(runtimeError, lastRunId));
|
|
5057
|
+
}
|
|
3714
5058
|
publish(createStreamErrorEvent(runtimeError, lastRunId));
|
|
3715
5059
|
closeStream();
|
|
3716
5060
|
rejectResult(runtimeError);
|
|
@@ -3719,15 +5063,18 @@ function createEngine(options) {
|
|
|
3719
5063
|
function cancelRun(cause) {
|
|
3720
5064
|
if (status !== "running") return;
|
|
3721
5065
|
const error = createStreamCancellationError(options.model.id, cause);
|
|
3722
|
-
status = "cancelled";
|
|
3723
5066
|
abortController.abort(error);
|
|
5067
|
+
activeAbortDrain?.(error);
|
|
5068
|
+
publish(createStreamAbortedEvent(error, lastRunId));
|
|
3724
5069
|
publish(createStreamErrorEvent(error, lastRunId));
|
|
5070
|
+
status = "cancelled";
|
|
3725
5071
|
closeStream();
|
|
3726
5072
|
rejectResult(error);
|
|
3727
5073
|
}
|
|
3728
5074
|
function closeStream() {
|
|
3729
5075
|
if (complete) return;
|
|
3730
5076
|
complete = true;
|
|
5077
|
+
failureInstancesByChildRunId.clear();
|
|
3731
5078
|
removeCallerAbortListener();
|
|
3732
5079
|
timeoutLifecycle.cleanup();
|
|
3733
5080
|
abortRace.cleanup();
|
|
@@ -3783,7 +5130,8 @@ function createNonStreamingAbortLifecycle(options) {
|
|
|
3783
5130
|
const timeoutLifecycle = createTimeoutAbortLifecycle({
|
|
3784
5131
|
abortController,
|
|
3785
5132
|
timeoutMs: options.timeoutMs,
|
|
3786
|
-
providerId: options.providerId
|
|
5133
|
+
providerId: options.providerId,
|
|
5134
|
+
timeoutErrorSource: options.timeoutErrorSource ?? "runtime"
|
|
3787
5135
|
});
|
|
3788
5136
|
const abortRace = createAbortRace(abortController.signal, options.providerId);
|
|
3789
5137
|
const removeCallerAbortListener = wireCallerAbortSignal(options.callerSignal, abortController, () => {
|
|
@@ -3811,7 +5159,11 @@ function createTimeoutAbortLifecycle(options) {
|
|
|
3811
5159
|
},
|
|
3812
5160
|
cleanup() {}
|
|
3813
5161
|
};
|
|
3814
|
-
const
|
|
5162
|
+
const timeoutSource = classifyChildTimeoutSource(void 0, {
|
|
5163
|
+
...options.timeoutErrorSource === "engine" ? { engineDefaultTimeoutMs: options.timeoutMs } : {},
|
|
5164
|
+
isProviderError: false
|
|
5165
|
+
});
|
|
5166
|
+
const timeoutError = options.timeoutErrorSource === "engine" && timeoutSource === "engine" ? createEngineDeadlineTimeoutError(options.providerId, options.timeoutMs) : createTimeoutError(options.providerId, options.timeoutMs);
|
|
3815
5167
|
const timeoutId = setTimeout(() => {
|
|
3816
5168
|
options.abortController.abort(timeoutError);
|
|
3817
5169
|
}, options.timeoutMs);
|
|
@@ -3879,6 +5231,23 @@ function timeoutMsFromTermination(condition) {
|
|
|
3879
5231
|
function readAbortSignalReason(signal) {
|
|
3880
5232
|
return signal?.aborted ? signal.reason : void 0;
|
|
3881
5233
|
}
|
|
5234
|
+
function createStreamAbortedEvent(error, runId) {
|
|
5235
|
+
return {
|
|
5236
|
+
type: "aborted",
|
|
5237
|
+
runId,
|
|
5238
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5239
|
+
reason: streamAbortedReason(error)
|
|
5240
|
+
};
|
|
5241
|
+
}
|
|
5242
|
+
function shouldPublishAborted(error) {
|
|
5243
|
+
return DogpileError.isInstance(error) && (error.code === "aborted" || error.code === "timeout");
|
|
5244
|
+
}
|
|
5245
|
+
function streamAbortedReason(error) {
|
|
5246
|
+
if (DogpileError.isInstance(error)) {
|
|
5247
|
+
if (error.code === "timeout" || error.detail?.["reason"] === "timeout") return "timeout";
|
|
5248
|
+
}
|
|
5249
|
+
return "parent-aborted";
|
|
5250
|
+
}
|
|
3882
5251
|
function createStreamErrorEvent(error, runId) {
|
|
3883
5252
|
if (DogpileError.isInstance(error)) return {
|
|
3884
5253
|
type: "error",
|
|
@@ -3911,10 +5280,12 @@ function dogpileErrorStreamDetail(error) {
|
|
|
3911
5280
|
return detail;
|
|
3912
5281
|
}
|
|
3913
5282
|
async function runNonStreamingProtocol(options) {
|
|
5283
|
+
const failureInstancesByChildRunId = /* @__PURE__ */ new Map();
|
|
3914
5284
|
const abortLifecycle = createNonStreamingAbortLifecycle({
|
|
3915
5285
|
callerSignal: options.signal,
|
|
3916
5286
|
timeoutMs: runtimeTimeoutMs(options),
|
|
3917
|
-
providerId: options.model.id
|
|
5287
|
+
providerId: options.model.id,
|
|
5288
|
+
timeoutErrorSource: options.currentDepth !== void 0 && options.currentDepth > 0 && options.parentDeadlineMs === void 0 ? "engine" : "runtime"
|
|
3918
5289
|
});
|
|
3919
5290
|
try {
|
|
3920
5291
|
const emittedEvents = [];
|
|
@@ -3923,7 +5294,8 @@ async function runNonStreamingProtocol(options) {
|
|
|
3923
5294
|
...abortLifecycle.signal !== void 0 ? { signal: abortLifecycle.signal } : {},
|
|
3924
5295
|
emit(event) {
|
|
3925
5296
|
emittedEvents.push(event);
|
|
3926
|
-
}
|
|
5297
|
+
},
|
|
5298
|
+
failureInstancesByChildRunId
|
|
3927
5299
|
}));
|
|
3928
5300
|
const events = emittedEvents.length > 0 ? emittedEvents : result.trace.events;
|
|
3929
5301
|
const trace = {
|
|
@@ -3944,10 +5316,13 @@ async function runNonStreamingProtocol(options) {
|
|
|
3944
5316
|
eventLog: createRunEventLog(trace.runId, trace.protocol, events),
|
|
3945
5317
|
trace
|
|
3946
5318
|
};
|
|
5319
|
+
const terminalThrow = resolveRuntimeTerminalThrow(runResult.trace, failureInstancesByChildRunId);
|
|
5320
|
+
if (terminalThrow) throw terminalThrow;
|
|
3947
5321
|
return canonicalizeRunResult(await abortLifecycle.run(applyRunEvaluation(runResult, options.evaluate)));
|
|
3948
5322
|
} catch (error) {
|
|
3949
5323
|
throw abortLifecycle.translateError(error);
|
|
3950
5324
|
} finally {
|
|
5325
|
+
failureInstancesByChildRunId.clear();
|
|
3951
5326
|
abortLifecycle.cleanup();
|
|
3952
5327
|
}
|
|
3953
5328
|
}
|
|
@@ -4022,7 +5397,20 @@ function runProtocol(options) {
|
|
|
4022
5397
|
...options.signal !== void 0 ? { signal: options.signal } : {},
|
|
4023
5398
|
...options.terminate ? { terminate: options.terminate } : {},
|
|
4024
5399
|
...options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {},
|
|
4025
|
-
...options.emit ? { emit: options.emit } : {}
|
|
5400
|
+
...options.emit ? { emit: options.emit } : {},
|
|
5401
|
+
...options.streamEvents !== void 0 ? { streamEvents: options.streamEvents } : {},
|
|
5402
|
+
currentDepth: options.currentDepth ?? 0,
|
|
5403
|
+
effectiveMaxDepth: options.effectiveMaxDepth ?? Infinity,
|
|
5404
|
+
effectiveMaxConcurrentChildren: options.effectiveMaxConcurrentChildren ?? DEFAULT_MAX_CONCURRENT_CHILDREN,
|
|
5405
|
+
onChildFailure: options.onChildFailure ?? "continue",
|
|
5406
|
+
...options.parentDeadlineMs !== void 0 ? { parentDeadlineMs: options.parentDeadlineMs } : {},
|
|
5407
|
+
...options.defaultSubRunTimeoutMs !== void 0 ? { defaultSubRunTimeoutMs: options.defaultSubRunTimeoutMs } : {},
|
|
5408
|
+
...options.registerAbortDrain !== void 0 ? { registerAbortDrain: options.registerAbortDrain } : {},
|
|
5409
|
+
...options.failureInstancesByChildRunId !== void 0 ? { failureInstancesByChildRunId: options.failureInstancesByChildRunId } : {},
|
|
5410
|
+
runProtocol: (childInput) => runProtocol({
|
|
5411
|
+
...childInput,
|
|
5412
|
+
protocol: normalizeProtocol(childInput.protocol)
|
|
5413
|
+
})
|
|
4026
5414
|
});
|
|
4027
5415
|
case "shared": return runShared({
|
|
4028
5416
|
intent: options.intent,
|
|
@@ -4089,6 +5477,9 @@ function stream(options) {
|
|
|
4089
5477
|
function replay(trace) {
|
|
4090
5478
|
const cost = trace.finalOutput.cost;
|
|
4091
5479
|
const lastEvent = trace.events.at(-1);
|
|
5480
|
+
const accounting = recomputeAccountingFromTrace(trace);
|
|
5481
|
+
const replayThrow = resolveReplayTerminalThrow(trace);
|
|
5482
|
+
if (replayThrow) throw replayThrow;
|
|
4092
5483
|
const baseResult = {
|
|
4093
5484
|
output: trace.finalOutput.output,
|
|
4094
5485
|
eventLog: createRunEventLog(trace.runId, trace.protocol, trace.events),
|
|
@@ -4103,13 +5494,7 @@ function replay(trace) {
|
|
|
4103
5494
|
agentsUsed: trace.agentsUsed,
|
|
4104
5495
|
events: trace.events
|
|
4105
5496
|
}),
|
|
4106
|
-
accounting
|
|
4107
|
-
tier: trace.tier,
|
|
4108
|
-
...trace.budget.caps ? { budget: trace.budget.caps } : {},
|
|
4109
|
-
...trace.budget.termination ? { termination: trace.budget.termination } : {},
|
|
4110
|
-
cost,
|
|
4111
|
-
events: trace.events
|
|
4112
|
-
}),
|
|
5497
|
+
accounting,
|
|
4113
5498
|
cost
|
|
4114
5499
|
};
|
|
4115
5500
|
if (lastEvent?.type !== "final") return baseResult;
|
|
@@ -4119,6 +5504,64 @@ function replay(trace) {
|
|
|
4119
5504
|
...lastEvent.evaluation !== void 0 ? { evaluation: lastEvent.evaluation } : {}
|
|
4120
5505
|
};
|
|
4121
5506
|
}
|
|
5507
|
+
function resolveRuntimeTerminalThrow(trace, failureInstancesByChildRunId) {
|
|
5508
|
+
if (trace.triggeringFailureForAbortMode !== void 0) return failureInstancesByChildRunId.get(trace.triggeringFailureForAbortMode.childRunId) ?? null;
|
|
5509
|
+
const finalEvent = trace.events.at(-1);
|
|
5510
|
+
if (finalEvent?.type !== "final" || finalEvent.termination === void 0) return null;
|
|
5511
|
+
const lastFailure = findLastRealFailure(trace.events, failureInstancesByChildRunId);
|
|
5512
|
+
if (lastFailure === null) return null;
|
|
5513
|
+
if (hasFinalSynthesisAfterEvent(trace, lastFailure.eventIndex)) return null;
|
|
5514
|
+
return lastFailure.error;
|
|
5515
|
+
}
|
|
5516
|
+
function findLastRealFailure(events, failureInstancesByChildRunId) {
|
|
5517
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
5518
|
+
const event = events[index];
|
|
5519
|
+
if (event?.type !== "sub-run-failed") continue;
|
|
5520
|
+
const instance = failureInstancesByChildRunId.get(event.childRunId);
|
|
5521
|
+
if (instance) return {
|
|
5522
|
+
error: instance,
|
|
5523
|
+
eventIndex: index
|
|
5524
|
+
};
|
|
5525
|
+
}
|
|
5526
|
+
return null;
|
|
5527
|
+
}
|
|
5528
|
+
function resolveReplayTerminalThrow(trace) {
|
|
5529
|
+
if (trace.triggeringFailureForAbortMode !== void 0) return dogpileErrorFromSerializedPayload(trace.triggeringFailureForAbortMode.error);
|
|
5530
|
+
const finalEvent = trace.events.at(-1);
|
|
5531
|
+
if (finalEvent?.type !== "final" || finalEvent.termination === void 0) return null;
|
|
5532
|
+
const lastFailure = reconstructLastRealFailure(trace.events);
|
|
5533
|
+
if (lastFailure === null) return null;
|
|
5534
|
+
if (hasFinalSynthesisAfterEvent(trace, lastFailure.eventIndex)) return null;
|
|
5535
|
+
return lastFailure.error;
|
|
5536
|
+
}
|
|
5537
|
+
function reconstructLastRealFailure(events) {
|
|
5538
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
5539
|
+
const event = events[index];
|
|
5540
|
+
if (event?.type !== "sub-run-failed" || isSyntheticSubRunFailure(event)) continue;
|
|
5541
|
+
return {
|
|
5542
|
+
error: dogpileErrorFromSerializedPayload(event.error),
|
|
5543
|
+
eventIndex: index
|
|
5544
|
+
};
|
|
5545
|
+
}
|
|
5546
|
+
return null;
|
|
5547
|
+
}
|
|
5548
|
+
function hasFinalSynthesisAfterEvent(trace, eventIndex) {
|
|
5549
|
+
return trace.protocolDecisions.some((decision) => {
|
|
5550
|
+
return decision.phase === "final-synthesis" && decision.eventIndex > eventIndex;
|
|
5551
|
+
});
|
|
5552
|
+
}
|
|
5553
|
+
function isSyntheticSubRunFailure(event) {
|
|
5554
|
+
const reason = event.error.detail?.["reason"];
|
|
5555
|
+
return reason === "sibling-failed" || reason === "parent-aborted";
|
|
5556
|
+
}
|
|
5557
|
+
function dogpileErrorFromSerializedPayload(input) {
|
|
5558
|
+
return new DogpileError({
|
|
5559
|
+
code: input.code,
|
|
5560
|
+
message: input.message,
|
|
5561
|
+
...input.providerId !== void 0 ? { providerId: input.providerId } : {},
|
|
5562
|
+
...input.detail !== void 0 ? { detail: input.detail } : {}
|
|
5563
|
+
});
|
|
5564
|
+
}
|
|
4122
5565
|
/**
|
|
4123
5566
|
* Replay a saved completed trace as a stream without invoking a model provider.
|
|
4124
5567
|
*
|
|
@@ -4129,20 +5572,22 @@ function replay(trace) {
|
|
|
4129
5572
|
* replay remains storage-free and provider-free.
|
|
4130
5573
|
*/
|
|
4131
5574
|
function replayStream(trace) {
|
|
5575
|
+
const result = Promise.resolve(replay(trace));
|
|
5576
|
+
const replayEvents = replayStreamEvents(trace);
|
|
4132
5577
|
return {
|
|
4133
5578
|
get status() {
|
|
4134
5579
|
return "completed";
|
|
4135
5580
|
},
|
|
4136
|
-
result
|
|
5581
|
+
result,
|
|
4137
5582
|
cancel() {},
|
|
4138
5583
|
subscribe(subscriber) {
|
|
4139
|
-
for (const event of
|
|
5584
|
+
for (const event of replayEvents) subscriber(event);
|
|
4140
5585
|
return { unsubscribe() {} };
|
|
4141
5586
|
},
|
|
4142
5587
|
[Symbol.asyncIterator]() {
|
|
4143
5588
|
let index = 0;
|
|
4144
5589
|
return { next() {
|
|
4145
|
-
const event =
|
|
5590
|
+
const event = replayEvents[index];
|
|
4146
5591
|
if (event) {
|
|
4147
5592
|
index += 1;
|
|
4148
5593
|
return Promise.resolve({
|
|
@@ -4158,6 +5603,22 @@ function replayStream(trace) {
|
|
|
4158
5603
|
}
|
|
4159
5604
|
};
|
|
4160
5605
|
}
|
|
5606
|
+
function replayStreamEvents(trace, parentRunIds = []) {
|
|
5607
|
+
const events = [];
|
|
5608
|
+
for (const event of trace.events) {
|
|
5609
|
+
if (event.type === "sub-run-completed") events.push(...replayStreamEvents(event.subResult.trace, [...parentRunIds, trace.runId]));
|
|
5610
|
+
events.push(wrapReplayStreamEvent(event, parentRunIds));
|
|
5611
|
+
}
|
|
5612
|
+
return events;
|
|
5613
|
+
}
|
|
5614
|
+
function wrapReplayStreamEvent(event, parentRunIds) {
|
|
5615
|
+
if (parentRunIds.length === 0) return event;
|
|
5616
|
+
const inbound = event.parentRunIds;
|
|
5617
|
+
return {
|
|
5618
|
+
...event,
|
|
5619
|
+
parentRunIds: [...parentRunIds, ...inbound ?? []]
|
|
5620
|
+
};
|
|
5621
|
+
}
|
|
4161
5622
|
function wireCallerAbortSignal(callerSignal, abortController, cancelRun) {
|
|
4162
5623
|
if (!callerSignal) return () => {};
|
|
4163
5624
|
const cancelFromCaller = () => {
|
|
@@ -4181,7 +5642,10 @@ function createStreamCancellationError(providerId, cause) {
|
|
|
4181
5642
|
retryable: false,
|
|
4182
5643
|
providerId,
|
|
4183
5644
|
...cause !== void 0 ? { cause } : {},
|
|
4184
|
-
detail: {
|
|
5645
|
+
detail: {
|
|
5646
|
+
status: "cancelled",
|
|
5647
|
+
reason: "parent-aborted"
|
|
5648
|
+
}
|
|
4185
5649
|
});
|
|
4186
5650
|
}
|
|
4187
5651
|
function isCancellationError(error) {
|
|
@@ -4195,6 +5659,20 @@ function withHighLevelDefaults(options) {
|
|
|
4195
5659
|
tier: options.tier ?? defaultHighLevelTier
|
|
4196
5660
|
};
|
|
4197
5661
|
}
|
|
5662
|
+
function assertRunDoesNotRaiseEngineMax(path, runValue, engineValue) {
|
|
5663
|
+
if (runValue === void 0 || runValue <= engineValue) return;
|
|
5664
|
+
throw new DogpileError({
|
|
5665
|
+
code: "invalid-configuration",
|
|
5666
|
+
message: `${path} cannot raise the engine ceiling (${engineValue}).`,
|
|
5667
|
+
retryable: false,
|
|
5668
|
+
detail: {
|
|
5669
|
+
kind: "configuration-validation",
|
|
5670
|
+
path,
|
|
5671
|
+
expected: `integer <= ${engineValue}`,
|
|
5672
|
+
actual: runValue
|
|
5673
|
+
}
|
|
5674
|
+
});
|
|
5675
|
+
}
|
|
4198
5676
|
/**
|
|
4199
5677
|
* Branded high-level SDK namespace.
|
|
4200
5678
|
*
|
|
@@ -4221,6 +5699,8 @@ function createOpenAICompatibleProvider(options) {
|
|
|
4221
5699
|
validateOptions(options);
|
|
4222
5700
|
const providerId = options.id ?? `openai-compatible:${options.model}`;
|
|
4223
5701
|
const fetchImplementation = options.fetch ?? globalThis.fetch?.bind(globalThis);
|
|
5702
|
+
const detectedLocality = classifyHostLocality(new URL(String(options.baseURL ?? defaultBaseURL)).hostname);
|
|
5703
|
+
const resolvedLocality = options.locality === "local" ? "local" : options.locality === "remote" ? "remote" : detectedLocality;
|
|
4224
5704
|
if (!fetchImplementation) throw new DogpileError({
|
|
4225
5705
|
code: "invalid-configuration",
|
|
4226
5706
|
message: "createOpenAICompatibleProvider() requires a fetch implementation in this runtime.",
|
|
@@ -4234,6 +5714,7 @@ function createOpenAICompatibleProvider(options) {
|
|
|
4234
5714
|
});
|
|
4235
5715
|
return {
|
|
4236
5716
|
id: providerId,
|
|
5717
|
+
metadata: { locality: resolvedLocality },
|
|
4237
5718
|
async generate(request) {
|
|
4238
5719
|
let response;
|
|
4239
5720
|
try {
|
|
@@ -4246,9 +5727,11 @@ function createOpenAICompatibleProvider(options) {
|
|
|
4246
5727
|
} catch (error) {
|
|
4247
5728
|
throw normalizeFetchError(error, providerId);
|
|
4248
5729
|
}
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
5730
|
+
if (!response.ok) {
|
|
5731
|
+
const payload = await readJsonLenient(response);
|
|
5732
|
+
throw createProviderError(response, payload, providerId);
|
|
5733
|
+
}
|
|
5734
|
+
const completion = asChatCompletionResponse(await readJson(response, providerId), providerId);
|
|
4252
5735
|
const text = readAssistantText(completion, providerId);
|
|
4253
5736
|
const usage = normalizeUsage(completion.usage);
|
|
4254
5737
|
const finishReason = normalizeFinishReason(completion.choices?.[0]?.finish_reason);
|
|
@@ -4277,6 +5760,22 @@ function validateOptions(options) {
|
|
|
4277
5760
|
if (options.fetch !== void 0 && typeof options.fetch !== "function") throwInvalid("fetch", "a fetch-compatible function when provided");
|
|
4278
5761
|
if (options.maxOutputTokens !== void 0 && (!Number.isInteger(options.maxOutputTokens) || options.maxOutputTokens <= 0)) throwInvalid("maxOutputTokens", "a positive integer when provided");
|
|
4279
5762
|
if (options.costEstimator !== void 0 && typeof options.costEstimator !== "function") throwInvalid("costEstimator", "a function when provided");
|
|
5763
|
+
if (options.locality !== void 0 && options.locality !== "local" && options.locality !== "remote") throwInvalid("locality", "\"local\" | \"remote\" when provided");
|
|
5764
|
+
if (options.locality === "remote") {
|
|
5765
|
+
const baseURL = new URL(String(options.baseURL ?? defaultBaseURL));
|
|
5766
|
+
if (classifyHostLocality(baseURL.hostname) === "local") throw new DogpileError({
|
|
5767
|
+
code: "invalid-configuration",
|
|
5768
|
+
message: `locality "remote" cannot be set when baseURL resolves to a local host (${baseURL.hostname}).`,
|
|
5769
|
+
retryable: false,
|
|
5770
|
+
detail: {
|
|
5771
|
+
kind: "configuration-validation",
|
|
5772
|
+
path: "locality",
|
|
5773
|
+
expected: "\"local\" (or omit to auto-detect)",
|
|
5774
|
+
reason: "remote-override-on-local-host",
|
|
5775
|
+
host: baseURL.hostname
|
|
5776
|
+
}
|
|
5777
|
+
});
|
|
5778
|
+
}
|
|
4280
5779
|
}
|
|
4281
5780
|
function throwInvalid(path, expected) {
|
|
4282
5781
|
throw new DogpileError({
|
|
@@ -4290,6 +5789,38 @@ function throwInvalid(path, expected) {
|
|
|
4290
5789
|
}
|
|
4291
5790
|
});
|
|
4292
5791
|
}
|
|
5792
|
+
/**
|
|
5793
|
+
* Classify a URL hostname as "local" or "remote" per Phase 3 D-02.
|
|
5794
|
+
* Local: localhost, *.local mDNS, IPv4 loopback (127.0.0.0/8), RFC1918
|
|
5795
|
+
* (10/8, 172.16/12, 192.168/16), link-local (169.254/16), IPv6 loopback (::1),
|
|
5796
|
+
* IPv6 ULA (fc00::/7), IPv6 link-local (fe80::/10).
|
|
5797
|
+
*
|
|
5798
|
+
* Pure function: no I/O, no side effects. Exported for tests and future reuse.
|
|
5799
|
+
*/
|
|
5800
|
+
function classifyHostLocality(host) {
|
|
5801
|
+
const lower = host.toLowerCase().replace(/^\[|\]$/g, "");
|
|
5802
|
+
const mappedIpv4 = ipv4MappedToDottedQuad(lower);
|
|
5803
|
+
if (mappedIpv4 !== void 0) return classifyHostLocality(mappedIpv4);
|
|
5804
|
+
if (lower === "localhost") return "local";
|
|
5805
|
+
if (lower.endsWith(".local")) return "local";
|
|
5806
|
+
if (/^127(?:\.\d{1,3}){3}$/.test(lower)) return "local";
|
|
5807
|
+
if (/^10(?:\.\d{1,3}){3}$/.test(lower)) return "local";
|
|
5808
|
+
if (/^172\.(?:1[6-9]|2\d|3[01])(?:\.\d{1,3}){2}$/.test(lower)) return "local";
|
|
5809
|
+
if (/^192\.168(?:\.\d{1,3}){2}$/.test(lower)) return "local";
|
|
5810
|
+
if (/^169\.254(?:\.\d{1,3}){2}$/.test(lower)) return "local";
|
|
5811
|
+
if (lower === "::1") return "local";
|
|
5812
|
+
if (/^f[cd][0-9a-f]{2}:/.test(lower)) return "local";
|
|
5813
|
+
if (/^fe[89ab][0-9a-f]?:/.test(lower)) return "local";
|
|
5814
|
+
return "remote";
|
|
5815
|
+
}
|
|
5816
|
+
function ipv4MappedToDottedQuad(host) {
|
|
5817
|
+
const match = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(host);
|
|
5818
|
+
if (match === null) return;
|
|
5819
|
+
const high = Number.parseInt(match[1] ?? "", 16);
|
|
5820
|
+
const low = Number.parseInt(match[2] ?? "", 16);
|
|
5821
|
+
if (!Number.isFinite(high) || !Number.isFinite(low)) return;
|
|
5822
|
+
return `${high >> 8}.${high & 255}.${low >> 8}.${low & 255}`;
|
|
5823
|
+
}
|
|
4293
5824
|
function createURL(options) {
|
|
4294
5825
|
const baseURL = new URL(String(options.baseURL ?? defaultBaseURL));
|
|
4295
5826
|
const path = options.path ?? defaultPath;
|
|
@@ -4339,6 +5870,13 @@ async function readJson(response, providerId) {
|
|
|
4339
5870
|
});
|
|
4340
5871
|
}
|
|
4341
5872
|
}
|
|
5873
|
+
async function readJsonLenient(response) {
|
|
5874
|
+
try {
|
|
5875
|
+
return await response.json();
|
|
5876
|
+
} catch {
|
|
5877
|
+
return;
|
|
5878
|
+
}
|
|
5879
|
+
}
|
|
4342
5880
|
function asChatCompletionResponse(payload, providerId) {
|
|
4343
5881
|
if (!isRecord(payload)) throw new DogpileError({
|
|
4344
5882
|
code: "provider-invalid-response",
|
|
@@ -4409,14 +5947,17 @@ function responseMetadata(response) {
|
|
|
4409
5947
|
});
|
|
4410
5948
|
}
|
|
4411
5949
|
function createProviderError(response, payload, providerId) {
|
|
5950
|
+
const code = codeForStatus(response.status);
|
|
5951
|
+
const timeoutSource = code === "provider-timeout" ? { source: "provider" } : {};
|
|
4412
5952
|
return new DogpileError({
|
|
4413
|
-
code
|
|
5953
|
+
code,
|
|
4414
5954
|
message: providerResponseErrorMessage(response, payload),
|
|
4415
5955
|
retryable: response.status === 408 || response.status === 429 || response.status >= 500,
|
|
4416
5956
|
providerId,
|
|
4417
5957
|
detail: removeUndefined({
|
|
4418
5958
|
statusCode: response.status,
|
|
4419
5959
|
statusText: response.statusText,
|
|
5960
|
+
...timeoutSource,
|
|
4420
5961
|
response: isJsonValue(payload) ? payload : void 0
|
|
4421
5962
|
})
|
|
4422
5963
|
});
|