@dogpile/sdk 0.3.0 → 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.
Files changed (105) hide show
  1. package/CHANGELOG.md +145 -0
  2. package/README.md +1 -0
  3. package/dist/browser/index.js +2270 -507
  4. package/dist/browser/index.js.map +1 -1
  5. package/dist/index.d.ts +5 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +2 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/providers/openai-compatible.d.ts +11 -0
  10. package/dist/providers/openai-compatible.d.ts.map +1 -1
  11. package/dist/providers/openai-compatible.js +87 -2
  12. package/dist/providers/openai-compatible.js.map +1 -1
  13. package/dist/runtime/broadcast.d.ts.map +1 -1
  14. package/dist/runtime/broadcast.js +1 -13
  15. package/dist/runtime/broadcast.js.map +1 -1
  16. package/dist/runtime/cancellation.d.ts +26 -0
  17. package/dist/runtime/cancellation.d.ts.map +1 -1
  18. package/dist/runtime/cancellation.js +38 -1
  19. package/dist/runtime/cancellation.js.map +1 -1
  20. package/dist/runtime/coordinator.d.ts +74 -1
  21. package/dist/runtime/coordinator.d.ts.map +1 -1
  22. package/dist/runtime/coordinator.js +929 -34
  23. package/dist/runtime/coordinator.js.map +1 -1
  24. package/dist/runtime/decisions.d.ts +25 -3
  25. package/dist/runtime/decisions.d.ts.map +1 -1
  26. package/dist/runtime/decisions.js +241 -3
  27. package/dist/runtime/decisions.js.map +1 -1
  28. package/dist/runtime/defaults.d.ts +37 -1
  29. package/dist/runtime/defaults.d.ts.map +1 -1
  30. package/dist/runtime/defaults.js +347 -0
  31. package/dist/runtime/defaults.js.map +1 -1
  32. package/dist/runtime/engine.d.ts.map +1 -1
  33. package/dist/runtime/engine.js +254 -24
  34. package/dist/runtime/engine.js.map +1 -1
  35. package/dist/runtime/ids.d.ts +19 -0
  36. package/dist/runtime/ids.d.ts.map +1 -0
  37. package/dist/runtime/ids.js +36 -0
  38. package/dist/runtime/ids.js.map +1 -0
  39. package/dist/runtime/logger.d.ts +61 -0
  40. package/dist/runtime/logger.d.ts.map +1 -0
  41. package/dist/runtime/logger.js +114 -0
  42. package/dist/runtime/logger.js.map +1 -0
  43. package/dist/runtime/retry.d.ts +99 -0
  44. package/dist/runtime/retry.d.ts.map +1 -0
  45. package/dist/runtime/retry.js +181 -0
  46. package/dist/runtime/retry.js.map +1 -0
  47. package/dist/runtime/sequential.d.ts.map +1 -1
  48. package/dist/runtime/sequential.js +9 -11
  49. package/dist/runtime/sequential.js.map +1 -1
  50. package/dist/runtime/shared.d.ts.map +1 -1
  51. package/dist/runtime/shared.js +1 -13
  52. package/dist/runtime/shared.js.map +1 -1
  53. package/dist/runtime/tools/built-in.d.ts +99 -0
  54. package/dist/runtime/tools/built-in.d.ts.map +1 -0
  55. package/dist/runtime/tools/built-in.js +577 -0
  56. package/dist/runtime/tools/built-in.js.map +1 -0
  57. package/dist/runtime/tools/vercel-ai.d.ts +67 -0
  58. package/dist/runtime/tools/vercel-ai.d.ts.map +1 -0
  59. package/dist/runtime/tools/vercel-ai.js +148 -0
  60. package/dist/runtime/tools/vercel-ai.js.map +1 -0
  61. package/dist/runtime/tools.d.ts +5 -268
  62. package/dist/runtime/tools.d.ts.map +1 -1
  63. package/dist/runtime/tools.js +7 -770
  64. package/dist/runtime/tools.js.map +1 -1
  65. package/dist/runtime/validation.d.ts +10 -0
  66. package/dist/runtime/validation.d.ts.map +1 -1
  67. package/dist/runtime/validation.js +73 -0
  68. package/dist/runtime/validation.js.map +1 -1
  69. package/dist/types/benchmark.d.ts +276 -0
  70. package/dist/types/benchmark.d.ts.map +1 -0
  71. package/dist/types/benchmark.js +2 -0
  72. package/dist/types/benchmark.js.map +1 -0
  73. package/dist/types/events.d.ts +816 -0
  74. package/dist/types/events.d.ts.map +1 -0
  75. package/dist/types/events.js +2 -0
  76. package/dist/types/events.js.map +1 -0
  77. package/dist/types/replay.d.ts +173 -0
  78. package/dist/types/replay.d.ts.map +1 -0
  79. package/dist/types/replay.js +2 -0
  80. package/dist/types/replay.js.map +1 -0
  81. package/dist/types.d.ts +135 -938
  82. package/dist/types.d.ts.map +1 -1
  83. package/dist/types.js.map +1 -1
  84. package/package.json +27 -1
  85. package/src/index.ts +14 -0
  86. package/src/providers/openai-compatible.ts +82 -3
  87. package/src/runtime/broadcast.ts +1 -16
  88. package/src/runtime/cancellation.ts +59 -1
  89. package/src/runtime/coordinator.ts +1164 -34
  90. package/src/runtime/decisions.ts +307 -4
  91. package/src/runtime/defaults.ts +376 -0
  92. package/src/runtime/engine.ts +363 -24
  93. package/src/runtime/ids.ts +41 -0
  94. package/src/runtime/logger.ts +152 -0
  95. package/src/runtime/retry.ts +270 -0
  96. package/src/runtime/sequential.ts +10 -13
  97. package/src/runtime/shared.ts +1 -16
  98. package/src/runtime/tools/built-in.ts +875 -0
  99. package/src/runtime/tools/vercel-ai.ts +269 -0
  100. package/src/runtime/tools.ts +60 -1255
  101. package/src/runtime/validation.ts +81 -0
  102. package/src/types/benchmark.ts +300 -0
  103. package/src/types/events.ts +895 -0
  104. package/src/types/replay.ts +212 -0
  105. package/src/types.ts +251 -997
@@ -67,6 +67,39 @@ function isRecord$2(value) {
67
67
  return typeof value === "object" && value !== null && !Array.isArray(value);
68
68
  }
69
69
  //#endregion
70
+ //#region src/runtime/ids.ts
71
+ /**
72
+ * Repo-internal id and timing helpers used across all four protocols.
73
+ *
74
+ * Centralized here so a change to id format or fallback semantics happens in
75
+ * exactly one place — switching `protocol` must not change the run-id contract.
76
+ */
77
+ /**
78
+ * Generates a fresh run id using `globalThis.crypto.randomUUID`.
79
+ *
80
+ * Throws a `DogpileError` when no UUID source is available rather than falling
81
+ * back to a millisecond-based id (which collides under back-to-back runs in
82
+ * the same tick). Node 22+, Bun latest, and modern browsers all expose
83
+ * `crypto.randomUUID`; environments without it are unsupported by Dogpile.
84
+ */
85
+ function createRunId() {
86
+ const random = globalThis.crypto?.randomUUID?.();
87
+ if (typeof random === "string" && random.length > 0) return random;
88
+ throw new DogpileError({
89
+ code: "invalid-configuration",
90
+ message: "Dogpile requires globalThis.crypto.randomUUID to mint a run id. Run on Node 22+, Bun latest, or a modern browser ESM environment."
91
+ });
92
+ }
93
+ function nowMs() {
94
+ return globalThis.performance?.now() ?? Date.now();
95
+ }
96
+ function elapsedMs(startedAtMs) {
97
+ return Math.max(0, nowMs() - startedAtMs);
98
+ }
99
+ function providerCallIdFor(runId, oneBasedIndex) {
100
+ return `${runId}:provider-call:${oneBasedIndex}`;
101
+ }
102
+ //#endregion
70
103
  //#region src/runtime/defaults.ts
71
104
  function normalizeProtocol(protocol) {
72
105
  if (typeof protocol !== "string") return protocol;
@@ -160,6 +193,27 @@ function addCost(left, right) {
160
193
  totalTokens: left.totalTokens + right.totalTokens
161
194
  };
162
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
+ }
163
217
  function createTranscriptLink(transcript) {
164
218
  return {
165
219
  kind: "trace-transcript",
@@ -258,7 +312,14 @@ function createReplayTraceBudgetStateChanges(events) {
258
312
  case "model-response":
259
313
  case "model-output-chunk":
260
314
  case "tool-call":
261
- case "tool-result": return [];
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 [];
262
323
  }
263
324
  });
264
325
  }
@@ -355,6 +416,24 @@ function createReplayTraceProtocolDecision(protocol, event, eventIndex, options
355
416
  output: event.output,
356
417
  cost: event.cost
357
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 };
358
437
  }
359
438
  }
360
439
  function defaultProtocolDecision(event) {
@@ -369,6 +448,13 @@ function defaultProtocolDecision(event) {
369
448
  case "broadcast": return "collect-broadcast-round";
370
449
  case "budget-stop": return "stop-for-budget";
371
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";
372
458
  }
373
459
  }
374
460
  function eventAgentScope(event) {
@@ -434,6 +520,216 @@ function canonicalizeRunResult(result) {
434
520
  function stableJsonStringify(value) {
435
521
  return JSON.stringify(canonicalizeSerializable(value));
436
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
+ }
437
733
  function canonicalizeSerializable(value) {
438
734
  if (Array.isArray(value)) return value.map((item) => canonicalizeSerializable(item));
439
735
  if (typeof value === "number") {
@@ -452,6 +748,24 @@ function canonicalizeSerializable(value) {
452
748
  }
453
749
  //#endregion
454
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
+ }
455
769
  function throwIfAborted(signal, providerId) {
456
770
  if (!signal?.aborted) return;
457
771
  throw createAbortErrorFromSignal(signal, providerId);
@@ -468,7 +782,7 @@ function createAbortError(providerId, detail, cause) {
468
782
  }
469
783
  function createAbortErrorFromSignal(signal, providerId) {
470
784
  if (DogpileError.isInstance(signal.reason)) return signal.reason;
471
- return createAbortError(providerId, void 0, signal.reason);
785
+ return createAbortError(providerId, { reason: classifyAbortReason(signal.reason) }, signal.reason);
472
786
  }
473
787
  function createTimeoutError(providerId, timeoutMs) {
474
788
  return new DogpileError({
@@ -479,23 +793,223 @@ function createTimeoutError(providerId, timeoutMs) {
479
793
  detail: { timeoutMs }
480
794
  });
481
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
+ }
482
808
  //#endregion
483
809
  //#region src/runtime/decisions.ts
484
- function parseAgentDecision(output) {
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) {
485
829
  const selectedRole = matchLine(output, /^role_selected:\s*(.+)$/imu);
486
830
  const participation = matchLine(output, /^participation:\s*(contribute|abstain)$/imu);
487
831
  const rationale = matchLine(output, /^rationale:\s*(.+)$/imu);
488
832
  const contribution = matchContribution(output);
489
833
  if (!selectedRole || !participation || !isAgentParticipation(participation) || !rationale || !contribution) return;
490
834
  return {
835
+ type: "participate",
491
836
  selectedRole,
492
837
  participation,
493
838
  rationale,
494
839
  contribution
495
840
  };
496
841
  }
497
- function isParticipatingDecision(decision) {
498
- return decision?.participation !== "abstain";
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
+ });
499
1013
  }
500
1014
  function matchLine(output, pattern) {
501
1015
  return output.match(pattern)?.[1]?.trim();
@@ -507,6 +1021,15 @@ function matchContribution(output) {
507
1021
  function isAgentParticipation(value) {
508
1022
  return value === "contribute" || value === "abstain";
509
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
+ }
510
1033
  //#endregion
511
1034
  //#region src/runtime/model.ts
512
1035
  async function generateModelTurn(options) {
@@ -964,6 +1487,7 @@ var budgetTiers = [
964
1487
  "balanced",
965
1488
  "quality"
966
1489
  ];
1490
+ var onChildFailureModes = ["continue", "abort"];
967
1491
  /**
968
1492
  * Validate high-level caller options before any protocol execution starts.
969
1493
  */
@@ -982,11 +1506,25 @@ function validateDogpileOptions(options) {
982
1506
  validateOptionalFunction(options.evaluate, "evaluate");
983
1507
  validateOptionalSeed(options.seed, "seed");
984
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");
985
1513
  }
986
1514
  function validateMissionIntent(intent, path = "intent") {
987
1515
  validateNonEmptyString(intent, path, "intent is required.");
988
1516
  }
989
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
+ /**
990
1528
  * Validate low-level engine configuration before normalizing reusable controls.
991
1529
  */
992
1530
  function validateEngineOptions(options) {
@@ -1003,6 +1541,10 @@ function validateEngineOptions(options) {
1003
1541
  validateOptionalFunction(options.evaluate, "evaluate");
1004
1542
  validateOptionalSeed(options.seed, "seed");
1005
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");
1006
1548
  }
1007
1549
  function validateProtocolSelection(value, path) {
1008
1550
  if (typeof value === "string") {
@@ -1050,6 +1592,23 @@ function validateBudgetTier(value, path) {
1050
1592
  actual: value
1051
1593
  });
1052
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
+ }
1053
1612
  /**
1054
1613
  * Validate configured model provider definitions at registration boundaries.
1055
1614
  */
@@ -1059,6 +1618,21 @@ function validateModelProviderRegistration(value, path = "model") {
1059
1618
  validateFunction(record.generate, `${path}.generate`);
1060
1619
  validateOptionalFunction(record.stream, `${path}.stream`);
1061
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
+ }
1062
1636
  function validateOptionalAgents(value, path) {
1063
1637
  if (value === void 0) return;
1064
1638
  if (!Array.isArray(value)) invalidConfiguration({
@@ -1338,6 +1912,16 @@ function validateOptionalNonNegativeInteger(value, path) {
1338
1912
  actual: value
1339
1913
  });
1340
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
+ }
1341
1925
  function validateOptionalNonNegativeNumber(value, path) {
1342
1926
  if (value === void 0) return;
1343
1927
  if (typeof value !== "number" || !Number.isFinite(value) || value < 0) invalidConfiguration({
@@ -1402,7 +1986,7 @@ function describeValue(value) {
1402
1986
  return typeof value;
1403
1987
  }
1404
1988
  //#endregion
1405
- //#region src/runtime/tools.ts
1989
+ //#region src/runtime/tools/built-in.ts
1406
1990
  var webSearchIdentity = {
1407
1991
  id: "dogpile.tools.webSearch",
1408
1992
  namespace: "dogpile",
@@ -1488,9 +2072,6 @@ function builtInDogpileToolIdentity(name) {
1488
2072
  function builtInDogpileToolInputSchema(name) {
1489
2073
  return name === "webSearch" ? webSearchInputSchema : codeExecInputSchema;
1490
2074
  }
1491
- /**
1492
- * Return the default permission declarations for one built-in tool name.
1493
- */
1494
2075
  function builtInDogpileToolPermissions(name) {
1495
2076
  return name === "webSearch" ? webSearchPermissions : codeExecPermissions;
1496
2077
  }
@@ -1501,276 +2082,28 @@ function validateBuiltInDogpileToolInput(name, input) {
1501
2082
  issues
1502
2083
  };
1503
2084
  }
1504
- /**
1505
- * Create the shared runtime tool executor used by every first-party protocol.
1506
- *
1507
- * @remarks
1508
- * The executor owns call id generation, read-only trace context construction,
1509
- * adapter validation, error normalization, and matched `tool-call` /
1510
- * `tool-result` events. Protocols only supply a normalized
1511
- * {@link RuntimeToolExecutionRequest}, which keeps tool execution independent
1512
- * of Coordinator, Sequential, Broadcast, or Shared control flow.
1513
- */
1514
- function createRuntimeToolExecutor(options) {
1515
- validateRuntimeToolRegistrations(options.tools);
1516
- const tools = Array.from(options.tools);
1517
- let callCount = 0;
1518
- return {
1519
- tools,
1520
- async execute(request) {
1521
- const tool = tools.find((candidate) => candidate.identity.id === request.toolId);
1522
- const identity = tool?.identity ?? {
1523
- id: request.toolId,
1524
- name: request.toolId
1525
- };
1526
- const callIndex = callCount;
1527
- callCount += 1;
1528
- const toolCallId = request.toolCallId ?? options.makeToolCallId?.(identity, callIndex) ?? defaultToolCallId(options.runId, callIndex);
1529
- const context = createExecutionContext(options, request, toolCallId);
1530
- options.emit?.({
1531
- type: "tool-call",
1532
- runId: options.runId,
1533
- at: (/* @__PURE__ */ new Date()).toISOString(),
1534
- toolCallId,
1535
- tool: identity,
1536
- input: request.input,
1537
- ...request.agentId ? { agentId: request.agentId } : {},
1538
- ...request.role ? { role: request.role } : {}
1539
- });
1540
- const result = await executeRuntimeTool(tool, identity, request.input, context);
1541
- options.emit?.({
1542
- type: "tool-result",
1543
- runId: options.runId,
1544
- at: (/* @__PURE__ */ new Date()).toISOString(),
1545
- toolCallId,
2085
+ function createWebSearchToolAdapter(options) {
2086
+ const identity = mergeIdentity(webSearchIdentity, options.identity);
2087
+ return normalizeBuiltInDogpileTool({
2088
+ name: "webSearch",
2089
+ ...options.identity ? { identity: options.identity } : {},
2090
+ ...options.permissions ? { permissions: options.permissions } : {},
2091
+ async execute(input, context) {
2092
+ const fetchImplementation = options.fetch ?? globalThis.fetch;
2093
+ if (!fetchImplementation) return {
2094
+ type: "error",
2095
+ toolCallId: context.toolCallId,
1546
2096
  tool: identity,
1547
- result,
1548
- ...request.agentId ? { agentId: request.agentId } : {},
1549
- ...request.role ? { role: request.role } : {}
1550
- });
1551
- return result;
1552
- }
1553
- };
1554
- }
1555
- /**
1556
- * Return a JSON-serializable manifest for tools visible to a protocol run.
1557
- */
1558
- function runtimeToolManifest(tools) {
1559
- return tools.map((tool) => {
1560
- const inputSchema = {
1561
- kind: tool.inputSchema.kind,
1562
- schema: tool.inputSchema.schema,
1563
- ...tool.inputSchema.description ? { description: tool.inputSchema.description } : {}
1564
- };
1565
- return {
1566
- identity: runtimeToolIdentityManifest(tool.identity),
1567
- inputSchema,
1568
- permissions: Array.from(tool.permissions ?? []).map(runtimeToolPermissionManifest)
1569
- };
1570
- });
1571
- }
1572
- /**
1573
- * Return request metadata that makes runtime tools visible to provider
1574
- * adapters, or an empty object when no tools are available.
1575
- */
1576
- function runtimeToolAvailability(tools) {
1577
- const manifest = runtimeToolManifest(tools);
1578
- return manifest.length > 0 ? { tools: manifest } : {};
1579
- }
1580
- /**
1581
- * Execute normalized tool requests returned by a provider response.
1582
- */
1583
- async function executeModelResponseToolRequests(options) {
1584
- const toolCalls = [];
1585
- for (const request of options.response.toolRequests ?? []) {
1586
- const result = await options.executor.execute({
1587
- ...request,
1588
- agentId: request.agentId ?? options.agentId,
1589
- role: request.role ?? options.role,
1590
- turn: request.turn ?? options.turn,
1591
- metadata: mergeToolMetadata(options.metadata, request.metadata)
1592
- });
1593
- toolCalls.push({
1594
- toolCallId: result.toolCallId,
1595
- tool: result.tool,
1596
- input: request.input,
1597
- result
1598
- });
1599
- }
1600
- return toolCalls;
1601
- }
1602
- function runtimeToolIdentityManifest(identity) {
1603
- return {
1604
- id: identity.id,
1605
- name: identity.name,
1606
- ...identity.namespace ? { namespace: identity.namespace } : {},
1607
- ...identity.version ? { version: identity.version } : {},
1608
- ...identity.description ? { description: identity.description } : {}
1609
- };
1610
- }
1611
- function runtimeToolPermissionManifest(permission) {
1612
- if (permission.kind === "network") return {
1613
- kind: permission.kind,
1614
- ...permission.allowHosts ? { allowHosts: Array.from(permission.allowHosts) } : {},
1615
- ...permission.allowPrivateNetwork === void 0 ? {} : { allowPrivateNetwork: permission.allowPrivateNetwork }
1616
- };
1617
- if (permission.kind === "code-execution") return {
1618
- kind: permission.kind,
1619
- sandbox: permission.sandbox,
1620
- ...permission.languages ? { languages: Array.from(permission.languages) } : {},
1621
- ...permission.allowNetwork === void 0 ? {} : { allowNetwork: permission.allowNetwork }
1622
- };
1623
- return {
1624
- kind: permission.kind,
1625
- name: permission.name,
1626
- ...permission.description ? { description: permission.description } : {},
1627
- ...permission.metadata ? { metadata: permission.metadata } : {}
1628
- };
1629
- }
1630
- function validateCodeExecAdapterInput(input, options) {
1631
- const issues = [...validateCodeExecInput(input)];
1632
- const languages = options.languages ?? codeExecLanguages;
1633
- if (typeof input.language === "string" && isCodeExecLanguage(input.language) && !languages.includes(input.language)) issues.push({
1634
- code: "invalid-value",
1635
- path: "language",
1636
- message: "codeExec.language is not enabled for this adapter.",
1637
- detail: { allowed: Array.from(languages) }
1638
- });
1639
- const effectiveTimeoutMs = input.timeoutMs ?? options.defaultTimeoutMs;
1640
- if (effectiveTimeoutMs !== void 0 && options.maxTimeoutMs !== void 0 && Number.isFinite(effectiveTimeoutMs) && Number.isFinite(options.maxTimeoutMs) && effectiveTimeoutMs > options.maxTimeoutMs) issues.push({
1641
- code: "out-of-range",
1642
- path: input.timeoutMs === void 0 ? "defaultTimeoutMs" : "timeoutMs",
1643
- message: `codeExec.timeoutMs must be less than or equal to ${options.maxTimeoutMs}.`,
1644
- detail: { maximum: options.maxTimeoutMs }
1645
- });
1646
- return issues.length === 0 ? { type: "valid" } : {
1647
- type: "invalid",
1648
- issues
1649
- };
1650
- }
1651
- function createExecutionContext(options, request, toolCallId) {
1652
- return {
1653
- runId: options.runId,
1654
- toolCallId,
1655
- protocol: options.protocol,
1656
- tier: options.tier,
1657
- ...request.agentId ? { agentId: request.agentId } : {},
1658
- ...request.role ? { role: request.role } : {},
1659
- ...request.turn !== void 0 ? { turn: request.turn } : {},
1660
- ...options.getTrace ? { trace: options.getTrace() } : {},
1661
- ...request.abortSignal ?? options.abortSignal ? { abortSignal: request.abortSignal ?? options.abortSignal } : {},
1662
- ...options.metadata || request.metadata ? { metadata: mergeToolMetadata(options.metadata, request.metadata) } : {}
1663
- };
1664
- }
1665
- async function executeRuntimeTool(tool, identity, input, context) {
1666
- if (!tool) return {
1667
- type: "error",
1668
- toolCallId: context.toolCallId,
1669
- tool: identity,
1670
- error: {
1671
- code: "unavailable",
1672
- message: `Runtime tool "${identity.id}" is not registered.`,
1673
- retryable: false
1674
- }
1675
- };
1676
- const validation = validateRuntimeToolInput(tool, input);
1677
- if (validation.type === "invalid") return {
1678
- type: "error",
1679
- toolCallId: context.toolCallId,
1680
- tool: identity,
1681
- error: {
1682
- code: "invalid-input",
1683
- message: "Runtime tool input failed validation.",
1684
- retryable: false,
1685
- detail: { issues: validation.issues.map((issue) => ({
1686
- code: issue.code,
1687
- path: issue.path,
1688
- message: issue.message,
1689
- ...issue.detail ? { detail: issue.detail } : {}
1690
- })) }
1691
- }
1692
- };
1693
- try {
1694
- return await tool.execute(input, context);
1695
- } catch (error) {
1696
- return {
1697
- type: "error",
1698
- toolCallId: context.toolCallId,
1699
- tool: identity,
1700
- error: normalizeRuntimeToolAdapterError(error)
1701
- };
1702
- }
1703
- }
1704
- function validateRuntimeToolInput(tool, input) {
1705
- if (typeof tool.validateInput !== "function") return { type: "valid" };
1706
- return tool.validateInput(input);
1707
- }
1708
- function mergeToolMetadata(base, request) {
1709
- return {
1710
- ...base ?? {},
1711
- ...request ?? {}
1712
- };
1713
- }
1714
- function defaultToolCallId(runId, callIndex) {
1715
- return `${runId}:tool-${callIndex + 1}`;
1716
- }
1717
- /**
1718
- * Convert an unknown adapter failure into Dogpile's serializable error data.
1719
- */
1720
- function normalizeRuntimeToolAdapterError(error) {
1721
- if (isRuntimeToolAdapterError(error)) return error;
1722
- if (error instanceof DOMException && error.name === "AbortError") return {
1723
- code: "aborted",
1724
- message: error.message || "Tool execution was aborted.",
1725
- retryable: true,
1726
- detail: { name: error.name }
1727
- };
1728
- if (error instanceof Error) return {
1729
- code: "backend-error",
1730
- message: error.message,
1731
- retryable: false,
1732
- detail: { name: error.name }
1733
- };
1734
- return {
1735
- code: "unknown",
1736
- message: "Tool execution failed with a non-Error value.",
1737
- retryable: false,
1738
- detail: { valueType: typeof error }
1739
- };
1740
- }
1741
- /**
1742
- * Create Dogpile's built-in fetch-based web search adapter.
1743
- *
1744
- * @remarks
1745
- * The adapter is backend-neutral: by default it sends a GET request with
1746
- * `q` and `limit` query parameters, then accepts either `{ results: [...] }`
1747
- * or a bare array of result objects from the response JSON. Callers can replace
1748
- * request construction or response parsing for a specific search API while
1749
- * keeping Dogpile's shared runtime tool contract, identity, permissions, input
1750
- * validation, and serializable errors.
1751
- */
1752
- function createWebSearchToolAdapter(options) {
1753
- const identity = mergeIdentity(webSearchIdentity, options.identity);
1754
- return normalizeBuiltInDogpileTool({
1755
- name: "webSearch",
1756
- ...options.identity ? { identity: options.identity } : {},
1757
- ...options.permissions ? { permissions: options.permissions } : {},
1758
- async execute(input, context) {
1759
- const fetchImplementation = options.fetch ?? globalThis.fetch;
1760
- if (!fetchImplementation) return {
1761
- type: "error",
1762
- toolCallId: context.toolCallId,
1763
- tool: identity,
1764
- error: {
1765
- code: "unavailable",
1766
- message: "No fetch implementation is available for webSearch.",
1767
- retryable: false
1768
- }
1769
- };
1770
- const request = options.buildRequest ? options.buildRequest(input, context) : defaultWebSearchRequest(options, input, context);
1771
- const response = await fetchImplementation(request.url, {
1772
- ...request.init,
1773
- ...context.abortSignal ? { signal: context.abortSignal } : {}
2097
+ error: {
2098
+ code: "unavailable",
2099
+ message: "No fetch implementation is available for webSearch.",
2100
+ retryable: false
2101
+ }
2102
+ };
2103
+ const request = options.buildRequest ? options.buildRequest(input, context) : defaultWebSearchRequest(options, input, context);
2104
+ const response = await fetchImplementation(request.url, {
2105
+ ...request.init,
2106
+ ...context.abortSignal ? { signal: context.abortSignal } : {}
1774
2107
  });
1775
2108
  if (!response.ok) throw {
1776
2109
  code: response.status >= 500 ? "unavailable" : "backend-error",
@@ -1791,15 +2124,6 @@ function createWebSearchToolAdapter(options) {
1791
2124
  }
1792
2125
  });
1793
2126
  }
1794
- /**
1795
- * Create Dogpile's built-in code execution adapter around a caller-owned sandbox.
1796
- *
1797
- * @remarks
1798
- * Dogpile core stays runtime-portable and never evaluates code itself. This
1799
- * adapter supplies the stable `codeExec` identity, schema, permissions,
1800
- * validation, timeout defaults, abort handling, and serializable errors while
1801
- * the host application owns the sandbox boundary.
1802
- */
1803
2127
  function createCodeExecToolAdapter(options) {
1804
2128
  const identity = mergeIdentity(codeExecIdentity, options.identity);
1805
2129
  const permissions = options.permissions ?? codeExecPermissionsFor(options.languages ?? codeExecLanguages, options.allowNetwork ?? false);
@@ -1818,7 +2142,7 @@ function createCodeExecToolAdapter(options) {
1818
2142
  code: "invalid-input",
1819
2143
  message: "Invalid codeExec tool input.",
1820
2144
  retryable: false,
1821
- detail: { issues: validation.issues }
2145
+ detail: { issues: serializeValidationIssues(validation.issues) }
1822
2146
  }
1823
2147
  };
1824
2148
  const timeoutMs = input.timeoutMs ?? options.defaultTimeoutMs;
@@ -1871,9 +2195,6 @@ function normalizeBuiltInDogpileTool(definition) {
1871
2195
  }
1872
2196
  }
1873
2197
  }
1874
- /**
1875
- * Normalize configured built-in Dogpile tool executors into runtime tools.
1876
- */
1877
2198
  function normalizeBuiltInDogpileTools(tools) {
1878
2199
  const normalized = [];
1879
2200
  if (tools.webSearch) normalized.push(normalizeBuiltInDogpileTool(asWebSearchDefinition(tools.webSearch)));
@@ -1890,7 +2211,7 @@ async function executeBuiltInTool(identity, execute, input, context, name) {
1890
2211
  code: "invalid-input",
1891
2212
  message: `Invalid ${name} tool input.`,
1892
2213
  retryable: false,
1893
- detail: { issues: validation.issues }
2214
+ detail: { issues: serializeValidationIssues(validation.issues) }
1894
2215
  }
1895
2216
  };
1896
2217
  try {
@@ -1925,6 +2246,27 @@ function mergeIdentity(defaultIdentity, options) {
1925
2246
  ...options.description !== void 0 ? { description: options.description } : {}
1926
2247
  };
1927
2248
  }
2249
+ function validateCodeExecAdapterInput(input, options) {
2250
+ const issues = [...validateCodeExecInput(input)];
2251
+ const languages = options.languages ?? codeExecLanguages;
2252
+ if (typeof input.language === "string" && isCodeExecLanguage(input.language) && !languages.includes(input.language)) issues.push({
2253
+ code: "invalid-value",
2254
+ path: "language",
2255
+ message: "codeExec.language is not enabled for this adapter.",
2256
+ detail: { allowed: Array.from(languages) }
2257
+ });
2258
+ const effectiveTimeoutMs = input.timeoutMs ?? options.defaultTimeoutMs;
2259
+ if (effectiveTimeoutMs !== void 0 && options.maxTimeoutMs !== void 0 && Number.isFinite(effectiveTimeoutMs) && Number.isFinite(options.maxTimeoutMs) && effectiveTimeoutMs > options.maxTimeoutMs) issues.push({
2260
+ code: "out-of-range",
2261
+ path: input.timeoutMs === void 0 ? "defaultTimeoutMs" : "timeoutMs",
2262
+ message: `codeExec.timeoutMs must be less than or equal to ${options.maxTimeoutMs}.`,
2263
+ detail: { maximum: options.maxTimeoutMs }
2264
+ });
2265
+ return issues.length === 0 ? { type: "valid" } : {
2266
+ type: "invalid",
2267
+ issues
2268
+ };
2269
+ }
1928
2270
  function defaultWebSearchRequest(options, input, _context) {
1929
2271
  const url = new URL(String(options.endpoint));
1930
2272
  url.searchParams.set("q", input.query);
@@ -2024,122 +2366,341 @@ function normalizeWebSearchResult(value) {
2024
2366
  message: "Web search result must be a JSON object.",
2025
2367
  retryable: false
2026
2368
  };
2027
- const title = jsonString(value.title, "title");
2028
- const url = jsonString(value.url, "url");
2029
- const snippet = optionalJsonString(value.snippet, "snippet");
2030
- const metadata = optionalJsonObject(value.metadata, "metadata");
2369
+ const title = jsonString(value.title, "title");
2370
+ const url = jsonString(value.url, "url");
2371
+ const snippet = optionalJsonString(value.snippet, "snippet");
2372
+ const metadata = optionalJsonObject(value.metadata, "metadata");
2373
+ return {
2374
+ title,
2375
+ url,
2376
+ ...snippet !== void 0 ? { snippet } : {},
2377
+ ...metadata !== void 0 ? { metadata } : {}
2378
+ };
2379
+ }
2380
+ function jsonString(value, fieldName) {
2381
+ if (typeof value !== "string" || value.trim().length === 0) throw {
2382
+ code: "backend-error",
2383
+ message: `Web search result ${fieldName} must be a non-empty string.`,
2384
+ retryable: false
2385
+ };
2386
+ return value;
2387
+ }
2388
+ function optionalJsonString(value, fieldName) {
2389
+ if (value === void 0) return void 0;
2390
+ if (typeof value !== "string") throw {
2391
+ code: "backend-error",
2392
+ message: `Web search result ${fieldName} must be a string when present.`,
2393
+ retryable: false
2394
+ };
2395
+ return value;
2396
+ }
2397
+ function optionalJsonObject(value, fieldName) {
2398
+ if (value === void 0) return void 0;
2399
+ if (!isJsonObject(value)) throw {
2400
+ code: "backend-error",
2401
+ message: `Web search result ${fieldName} must be a JSON object when present.`,
2402
+ retryable: false
2403
+ };
2404
+ return value;
2405
+ }
2406
+ function isJsonObject(value) {
2407
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2408
+ }
2409
+ function validateWebSearchInput(input) {
2410
+ const issues = [];
2411
+ if (typeof input.query !== "string") issues.push({
2412
+ code: input.query === void 0 ? "missing-field" : "invalid-type",
2413
+ path: "query",
2414
+ message: "webSearch.query must be a string."
2415
+ });
2416
+ else if (input.query.trim().length === 0) issues.push({
2417
+ code: "invalid-value",
2418
+ path: "query",
2419
+ message: "webSearch.query must not be empty."
2420
+ });
2421
+ if (input.maxResults !== void 0) {
2422
+ if (typeof input.maxResults !== "number" || !Number.isFinite(input.maxResults)) issues.push({
2423
+ code: "invalid-type",
2424
+ path: "maxResults",
2425
+ message: "webSearch.maxResults must be a finite number."
2426
+ });
2427
+ else if (input.maxResults < 1) issues.push({
2428
+ code: "out-of-range",
2429
+ path: "maxResults",
2430
+ message: "webSearch.maxResults must be greater than or equal to 1.",
2431
+ detail: { minimum: 1 }
2432
+ });
2433
+ }
2434
+ return issues;
2435
+ }
2436
+ function validateCodeExecInput(input) {
2437
+ const issues = [];
2438
+ if (typeof input.language !== "string") issues.push({
2439
+ code: input.language === void 0 ? "missing-field" : "invalid-type",
2440
+ path: "language",
2441
+ message: "codeExec.language must be a string."
2442
+ });
2443
+ else if (!isCodeExecLanguage(input.language)) issues.push({
2444
+ code: "invalid-value",
2445
+ path: "language",
2446
+ message: "codeExec.language must be one of javascript, typescript, python, bash, or shell.",
2447
+ detail: { allowed: [
2448
+ "javascript",
2449
+ "typescript",
2450
+ "python",
2451
+ "bash",
2452
+ "shell"
2453
+ ] }
2454
+ });
2455
+ if (typeof input.code !== "string") issues.push({
2456
+ code: input.code === void 0 ? "missing-field" : "invalid-type",
2457
+ path: "code",
2458
+ message: "codeExec.code must be a string."
2459
+ });
2460
+ if (input.timeoutMs !== void 0) {
2461
+ if (typeof input.timeoutMs !== "number" || !Number.isFinite(input.timeoutMs)) issues.push({
2462
+ code: "invalid-type",
2463
+ path: "timeoutMs",
2464
+ message: "codeExec.timeoutMs must be a finite number."
2465
+ });
2466
+ else if (input.timeoutMs < 1) issues.push({
2467
+ code: "out-of-range",
2468
+ path: "timeoutMs",
2469
+ message: "codeExec.timeoutMs must be greater than or equal to 1.",
2470
+ detail: { minimum: 1 }
2471
+ });
2472
+ }
2473
+ return issues;
2474
+ }
2475
+ function isCodeExecLanguage(value) {
2476
+ return value === "javascript" || value === "typescript" || value === "python" || value === "bash" || value === "shell";
2477
+ }
2478
+ function normalizeRuntimeToolAdapterError(error) {
2479
+ if (isRuntimeToolAdapterError(error)) return error;
2480
+ if (error instanceof DOMException && error.name === "AbortError") return {
2481
+ code: "aborted",
2482
+ message: error.message || "Tool execution was aborted.",
2483
+ retryable: true,
2484
+ detail: { name: error.name }
2485
+ };
2486
+ if (error instanceof Error) return {
2487
+ code: "backend-error",
2488
+ message: error.message,
2489
+ retryable: false,
2490
+ detail: { name: error.name }
2491
+ };
2492
+ return {
2493
+ code: "unknown",
2494
+ message: "Tool execution failed with a non-Error value.",
2495
+ retryable: false,
2496
+ detail: { valueType: typeof error }
2497
+ };
2498
+ }
2499
+ function isRuntimeToolAdapterError(error) {
2500
+ if (typeof error !== "object" || error === null || !("code" in error) || !("message" in error)) return false;
2501
+ const candidate = error;
2502
+ return isRuntimeToolAdapterErrorCode(candidate.code) && typeof candidate.message === "string";
2503
+ }
2504
+ function isRuntimeToolAdapterErrorCode(value) {
2505
+ return value === "invalid-input" || value === "permission-denied" || value === "timeout" || value === "aborted" || value === "unavailable" || value === "backend-error" || value === "unknown";
2506
+ }
2507
+ function serializeValidationIssues(issues) {
2508
+ return issues.map((issue) => ({
2509
+ code: issue.code,
2510
+ path: issue.path,
2511
+ message: issue.message,
2512
+ ...issue.detail !== void 0 ? { detail: issue.detail } : {}
2513
+ }));
2514
+ }
2515
+ //#endregion
2516
+ //#region src/runtime/tools.ts
2517
+ /**
2518
+ * Create the shared runtime tool executor used by every first-party protocol.
2519
+ *
2520
+ * @remarks
2521
+ * The executor owns call id generation, read-only trace context construction,
2522
+ * adapter validation, error normalization, and matched `tool-call` /
2523
+ * `tool-result` events. Protocols only supply a normalized
2524
+ * {@link RuntimeToolExecutionRequest}, which keeps tool execution independent
2525
+ * of Coordinator, Sequential, Broadcast, or Shared control flow.
2526
+ */
2527
+ function createRuntimeToolExecutor(options) {
2528
+ validateRuntimeToolRegistrations(options.tools);
2529
+ const tools = Array.from(options.tools);
2530
+ let callCount = 0;
2531
+ return {
2532
+ tools,
2533
+ async execute(request) {
2534
+ const tool = tools.find((candidate) => candidate.identity.id === request.toolId);
2535
+ const identity = tool?.identity ?? {
2536
+ id: request.toolId,
2537
+ name: request.toolId
2538
+ };
2539
+ const callIndex = callCount;
2540
+ callCount += 1;
2541
+ const toolCallId = request.toolCallId ?? options.makeToolCallId?.(identity, callIndex) ?? defaultToolCallId(options.runId, callIndex);
2542
+ const context = createExecutionContext(options, request, toolCallId);
2543
+ options.emit?.({
2544
+ type: "tool-call",
2545
+ runId: options.runId,
2546
+ at: (/* @__PURE__ */ new Date()).toISOString(),
2547
+ toolCallId,
2548
+ tool: identity,
2549
+ input: request.input,
2550
+ ...request.agentId ? { agentId: request.agentId } : {},
2551
+ ...request.role ? { role: request.role } : {}
2552
+ });
2553
+ const result = await executeRuntimeTool(tool, identity, request.input, context);
2554
+ options.emit?.({
2555
+ type: "tool-result",
2556
+ runId: options.runId,
2557
+ at: (/* @__PURE__ */ new Date()).toISOString(),
2558
+ toolCallId,
2559
+ tool: identity,
2560
+ result,
2561
+ ...request.agentId ? { agentId: request.agentId } : {},
2562
+ ...request.role ? { role: request.role } : {}
2563
+ });
2564
+ return result;
2565
+ }
2566
+ };
2567
+ }
2568
+ /**
2569
+ * Return a JSON-serializable manifest for tools visible to a protocol run.
2570
+ */
2571
+ function runtimeToolManifest(tools) {
2572
+ return tools.map((tool) => {
2573
+ const inputSchema = {
2574
+ kind: tool.inputSchema.kind,
2575
+ schema: tool.inputSchema.schema,
2576
+ ...tool.inputSchema.description ? { description: tool.inputSchema.description } : {}
2577
+ };
2578
+ return {
2579
+ identity: runtimeToolIdentityManifest(tool.identity),
2580
+ inputSchema,
2581
+ permissions: Array.from(tool.permissions ?? []).map(runtimeToolPermissionManifest)
2582
+ };
2583
+ });
2584
+ }
2585
+ /**
2586
+ * Return request metadata that makes runtime tools visible to provider
2587
+ * adapters, or an empty object when no tools are available.
2588
+ */
2589
+ function runtimeToolAvailability(tools) {
2590
+ const manifest = runtimeToolManifest(tools);
2591
+ return manifest.length > 0 ? { tools: manifest } : {};
2592
+ }
2593
+ /**
2594
+ * Execute normalized tool requests returned by a provider response.
2595
+ */
2596
+ async function executeModelResponseToolRequests(options) {
2597
+ const toolCalls = [];
2598
+ for (const request of options.response.toolRequests ?? []) {
2599
+ const result = await options.executor.execute({
2600
+ ...request,
2601
+ agentId: request.agentId ?? options.agentId,
2602
+ role: request.role ?? options.role,
2603
+ turn: request.turn ?? options.turn,
2604
+ metadata: mergeToolMetadata(options.metadata, request.metadata)
2605
+ });
2606
+ toolCalls.push({
2607
+ toolCallId: result.toolCallId,
2608
+ tool: result.tool,
2609
+ input: request.input,
2610
+ result
2611
+ });
2612
+ }
2613
+ return toolCalls;
2614
+ }
2615
+ function runtimeToolIdentityManifest(identity) {
2616
+ return {
2617
+ id: identity.id,
2618
+ name: identity.name,
2619
+ ...identity.namespace ? { namespace: identity.namespace } : {},
2620
+ ...identity.version ? { version: identity.version } : {},
2621
+ ...identity.description ? { description: identity.description } : {}
2622
+ };
2623
+ }
2624
+ function runtimeToolPermissionManifest(permission) {
2625
+ if (permission.kind === "network") return {
2626
+ kind: permission.kind,
2627
+ ...permission.allowHosts ? { allowHosts: Array.from(permission.allowHosts) } : {},
2628
+ ...permission.allowPrivateNetwork === void 0 ? {} : { allowPrivateNetwork: permission.allowPrivateNetwork }
2629
+ };
2630
+ if (permission.kind === "code-execution") return {
2631
+ kind: permission.kind,
2632
+ sandbox: permission.sandbox,
2633
+ ...permission.languages ? { languages: Array.from(permission.languages) } : {},
2634
+ ...permission.allowNetwork === void 0 ? {} : { allowNetwork: permission.allowNetwork }
2635
+ };
2031
2636
  return {
2032
- title,
2033
- url,
2034
- ...snippet !== void 0 ? { snippet } : {},
2035
- ...metadata !== void 0 ? { metadata } : {}
2637
+ kind: permission.kind,
2638
+ name: permission.name,
2639
+ ...permission.description ? { description: permission.description } : {},
2640
+ ...permission.metadata ? { metadata: permission.metadata } : {}
2036
2641
  };
2037
2642
  }
2038
- function jsonString(value, fieldName) {
2039
- if (typeof value !== "string" || value.trim().length === 0) throw {
2040
- code: "backend-error",
2041
- message: `Web search result ${fieldName} must be a non-empty string.`,
2042
- retryable: false
2643
+ function createExecutionContext(options, request, toolCallId) {
2644
+ return {
2645
+ runId: options.runId,
2646
+ toolCallId,
2647
+ protocol: options.protocol,
2648
+ tier: options.tier,
2649
+ ...request.agentId ? { agentId: request.agentId } : {},
2650
+ ...request.role ? { role: request.role } : {},
2651
+ ...request.turn !== void 0 ? { turn: request.turn } : {},
2652
+ ...options.getTrace ? { trace: options.getTrace() } : {},
2653
+ ...request.abortSignal ?? options.abortSignal ? { abortSignal: request.abortSignal ?? options.abortSignal } : {},
2654
+ ...options.metadata || request.metadata ? { metadata: mergeToolMetadata(options.metadata, request.metadata) } : {}
2043
2655
  };
2044
- return value;
2045
2656
  }
2046
- function optionalJsonString(value, fieldName) {
2047
- if (value === void 0) return;
2048
- if (typeof value !== "string") throw {
2049
- code: "backend-error",
2050
- message: `Web search result ${fieldName} must be a string when present.`,
2051
- retryable: false
2657
+ async function executeRuntimeTool(tool, identity, input, context) {
2658
+ if (!tool) return {
2659
+ type: "error",
2660
+ toolCallId: context.toolCallId,
2661
+ tool: identity,
2662
+ error: {
2663
+ code: "unavailable",
2664
+ message: `Runtime tool "${identity.id}" is not registered.`,
2665
+ retryable: false
2666
+ }
2052
2667
  };
2053
- return value;
2054
- }
2055
- function optionalJsonObject(value, fieldName) {
2056
- if (value === void 0) return;
2057
- if (!isJsonObject(value)) throw {
2058
- code: "backend-error",
2059
- message: `Web search result ${fieldName} must be a JSON object when present.`,
2060
- retryable: false
2668
+ const validation = tool.validateInput?.(input);
2669
+ if (validation?.type === "invalid") return {
2670
+ type: "error",
2671
+ toolCallId: context.toolCallId,
2672
+ tool: identity,
2673
+ error: {
2674
+ code: "invalid-input",
2675
+ message: "Runtime tool input failed validation.",
2676
+ retryable: false,
2677
+ detail: { issues: validation.issues.map((issue) => ({
2678
+ code: issue.code,
2679
+ path: issue.path,
2680
+ message: issue.message,
2681
+ ...issue.detail ? { detail: issue.detail } : {}
2682
+ })) }
2683
+ }
2061
2684
  };
2062
- return value;
2063
- }
2064
- function isJsonObject(value) {
2065
- return typeof value === "object" && value !== null && !Array.isArray(value);
2066
- }
2067
- function validateWebSearchInput(input) {
2068
- const issues = [];
2069
- if (typeof input.query !== "string") issues.push({
2070
- code: input.query === void 0 ? "missing-field" : "invalid-type",
2071
- path: "query",
2072
- message: "webSearch.query must be a string."
2073
- });
2074
- else if (input.query.trim().length === 0) issues.push({
2075
- code: "invalid-value",
2076
- path: "query",
2077
- message: "webSearch.query must not be empty."
2078
- });
2079
- if (input.maxResults !== void 0) {
2080
- if (typeof input.maxResults !== "number" || !Number.isFinite(input.maxResults)) issues.push({
2081
- code: "invalid-type",
2082
- path: "maxResults",
2083
- message: "webSearch.maxResults must be a finite number."
2084
- });
2085
- else if (input.maxResults < 1) issues.push({
2086
- code: "out-of-range",
2087
- path: "maxResults",
2088
- message: "webSearch.maxResults must be greater than or equal to 1.",
2089
- detail: { minimum: 1 }
2090
- });
2091
- }
2092
- return issues;
2093
- }
2094
- function validateCodeExecInput(input) {
2095
- const issues = [];
2096
- if (typeof input.language !== "string") issues.push({
2097
- code: input.language === void 0 ? "missing-field" : "invalid-type",
2098
- path: "language",
2099
- message: "codeExec.language must be a string."
2100
- });
2101
- else if (!isCodeExecLanguage(input.language)) issues.push({
2102
- code: "invalid-value",
2103
- path: "language",
2104
- message: "codeExec.language must be one of javascript, typescript, python, bash, or shell.",
2105
- detail: { allowed: [
2106
- "javascript",
2107
- "typescript",
2108
- "python",
2109
- "bash",
2110
- "shell"
2111
- ] }
2112
- });
2113
- if (typeof input.code !== "string") issues.push({
2114
- code: input.code === void 0 ? "missing-field" : "invalid-type",
2115
- path: "code",
2116
- message: "codeExec.code must be a string."
2117
- });
2118
- if (input.timeoutMs !== void 0) {
2119
- if (typeof input.timeoutMs !== "number" || !Number.isFinite(input.timeoutMs)) issues.push({
2120
- code: "invalid-type",
2121
- path: "timeoutMs",
2122
- message: "codeExec.timeoutMs must be a finite number."
2123
- });
2124
- else if (input.timeoutMs < 1) issues.push({
2125
- code: "out-of-range",
2126
- path: "timeoutMs",
2127
- message: "codeExec.timeoutMs must be greater than or equal to 1.",
2128
- detail: { minimum: 1 }
2129
- });
2685
+ try {
2686
+ return await tool.execute(input, context);
2687
+ } catch (error) {
2688
+ return {
2689
+ type: "error",
2690
+ toolCallId: context.toolCallId,
2691
+ tool: identity,
2692
+ error: normalizeRuntimeToolAdapterError(error)
2693
+ };
2130
2694
  }
2131
- return issues;
2132
- }
2133
- function isCodeExecLanguage(value) {
2134
- return value === "javascript" || value === "typescript" || value === "python" || value === "bash" || value === "shell";
2135
2695
  }
2136
- function isRuntimeToolAdapterError(error) {
2137
- if (typeof error !== "object" || error === null || !("code" in error) || !("message" in error)) return false;
2138
- const candidate = error;
2139
- return isRuntimeToolAdapterErrorCode(candidate.code) && typeof candidate.message === "string";
2696
+ function mergeToolMetadata(base, request) {
2697
+ return {
2698
+ ...base ?? {},
2699
+ ...request ?? {}
2700
+ };
2140
2701
  }
2141
- function isRuntimeToolAdapterErrorCode(value) {
2142
- return value === "invalid-input" || value === "permission-denied" || value === "timeout" || value === "aborted" || value === "unavailable" || value === "backend-error" || value === "unknown";
2702
+ function defaultToolCallId(runId, callIndex) {
2703
+ return `${runId}:tool-${callIndex + 1}`;
2143
2704
  }
2144
2705
  //#endregion
2145
2706
  //#region src/runtime/wrap-up.ts
@@ -2289,7 +2850,7 @@ function minCap(left, right) {
2289
2850
  //#endregion
2290
2851
  //#region src/runtime/broadcast.ts
2291
2852
  async function runBroadcast(options) {
2292
- const runId = createRunId$3();
2853
+ const runId = createRunId();
2293
2854
  const events = [];
2294
2855
  const transcript = [];
2295
2856
  const protocolDecisions = [];
@@ -2298,7 +2859,7 @@ async function runBroadcast(options) {
2298
2859
  const maxRounds = options.protocol.maxRounds ?? 2;
2299
2860
  let firstRoundContributions = [];
2300
2861
  let lastContributions = [];
2301
- const startedAtMs = nowMs$3();
2862
+ const startedAtMs = nowMs();
2302
2863
  let stopped = false;
2303
2864
  let termination;
2304
2865
  const wrapUpHint = createWrapUpHintController({
@@ -2375,7 +2936,7 @@ async function runBroadcast(options) {
2375
2936
  events,
2376
2937
  transcript,
2377
2938
  iteration: transcript.length,
2378
- elapsedMs: elapsedMs$3(startedAtMs)
2939
+ elapsedMs: elapsedMs(startedAtMs)
2379
2940
  })
2380
2941
  };
2381
2942
  const response = await generateModelTurn({
@@ -2385,7 +2946,7 @@ async function runBroadcast(options) {
2385
2946
  agent,
2386
2947
  input,
2387
2948
  emit,
2388
- callId: providerCallIdFor$2(runId, providerCalls.length + agentIndex + 1),
2949
+ callId: providerCallIdFor(runId, providerCalls.length + agentIndex + 1),
2389
2950
  onProviderCall(call) {
2390
2951
  providerCallSlots[agentIndex] = call;
2391
2952
  }
@@ -2550,7 +3111,7 @@ async function runBroadcast(options) {
2550
3111
  events,
2551
3112
  transcript,
2552
3113
  iteration: transcript.length,
2553
- elapsedMs: elapsedMs$3(startedAtMs)
3114
+ elapsedMs: elapsedMs(startedAtMs)
2554
3115
  }));
2555
3116
  if (!stopRecord) return false;
2556
3117
  stopped = true;
@@ -2566,7 +3127,7 @@ async function runBroadcast(options) {
2566
3127
  reason: record.budgetReason ?? "cost",
2567
3128
  cost: totalCost,
2568
3129
  iteration: transcript.length,
2569
- elapsedMs: elapsedMs$3(startedAtMs),
3130
+ elapsedMs: elapsedMs(startedAtMs),
2570
3131
  detail: record.detail ?? {}
2571
3132
  };
2572
3133
  emit(event);
@@ -2596,33 +3157,76 @@ function responseCost$3(response) {
2596
3157
  totalTokens: response.usage?.totalTokens ?? 0
2597
3158
  };
2598
3159
  }
2599
- function createRunId$3() {
2600
- return globalThis.crypto?.randomUUID?.() ?? `run-${Date.now().toString(36)}`;
2601
- }
2602
- function nowMs$3() {
2603
- return globalThis.performance?.now() ?? Date.now();
2604
- }
2605
- function elapsedMs$3(startedAtMs) {
2606
- return Math.max(0, nowMs$3() - startedAtMs);
2607
- }
2608
- function providerCallIdFor$2(runId, oneBasedIndex) {
2609
- return `${runId}:provider-call:${oneBasedIndex}`;
2610
- }
2611
3160
  //#endregion
2612
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
+ }
2613
3214
  async function runCoordinator(options) {
2614
- const runId = createRunId$2();
3215
+ const runId = createRunId();
2615
3216
  const events = [];
2616
3217
  const transcript = [];
2617
3218
  const protocolDecisions = [];
2618
3219
  const providerCalls = [];
3220
+ const dispatchedChildren = /* @__PURE__ */ new Map();
2619
3221
  let totalCost = emptyCost();
3222
+ let concurrencyClampEmitted = false;
2620
3223
  const maxTurns = options.protocol.maxTurns ?? options.agents.length;
2621
3224
  const activeAgents = options.agents.slice(0, maxTurns);
2622
3225
  const coordinator = activeAgents[0];
2623
- const startedAtMs = nowMs$2();
3226
+ const startedAtMs = nowMs();
2624
3227
  let stopped = false;
2625
3228
  let termination;
3229
+ let triggeringFailureForAbortMode;
2626
3230
  const wrapUpHint = createWrapUpHintController({
2627
3231
  protocol: options.protocol,
2628
3232
  tier: options.tier,
@@ -2638,6 +3242,51 @@ async function runCoordinator(options) {
2638
3242
  const recordProtocolDecision = (event, decisionOptions) => {
2639
3243
  protocolDecisions.push(createReplayTraceProtocolDecision("coordinator", event, events.length - 1, decisionOptions));
2640
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);
2641
3290
  const toolExecutor = createRuntimeToolExecutor({
2642
3291
  runId,
2643
3292
  protocol: "coordinator",
@@ -2668,24 +3317,231 @@ async function runCoordinator(options) {
2668
3317
  }
2669
3318
  if (coordinator) {
2670
3319
  if (!stopIfNeeded()) {
2671
- totalCost = await runCoordinatorTurn({
2672
- agent: coordinator,
2673
- coordinator,
2674
- input: buildCoordinatorPlanInput(options.intent, coordinator),
2675
- phase: "plan",
2676
- options,
2677
- runId,
2678
- transcript,
2679
- totalCost,
2680
- providerCalls,
2681
- toolExecutor,
2682
- toolAvailability,
2683
- events,
2684
- startedAtMs,
2685
- wrapUpHint,
2686
- emit,
2687
- recordProtocolDecision
2688
- });
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
+ }
2689
3545
  stopIfNeeded();
2690
3546
  }
2691
3547
  if (!stopIfNeeded()) {
@@ -2699,7 +3555,7 @@ async function runCoordinator(options) {
2699
3555
  options,
2700
3556
  runId,
2701
3557
  turn: transcript.length + index + 1,
2702
- providerCallId: providerCallIdFor$1(runId, providerCalls.length + index + 1),
3558
+ providerCallId: providerCallIdFor(runId, providerCalls.length + index + 1),
2703
3559
  providerCallIndex: index,
2704
3560
  providerCallSlots,
2705
3561
  toolExecutor,
@@ -2743,7 +3599,7 @@ async function runCoordinator(options) {
2743
3599
  stopIfNeeded();
2744
3600
  }
2745
3601
  if (!stopIfNeeded()) {
2746
- totalCost = await runCoordinatorTurn({
3602
+ const synthesisOutcome = await runCoordinatorTurn({
2747
3603
  agent: coordinator,
2748
3604
  coordinator,
2749
3605
  input: buildFinalSynthesisInput(options.intent, transcript, coordinator),
@@ -2761,6 +3617,17 @@ async function runCoordinator(options) {
2761
3617
  emit,
2762
3618
  recordProtocolDecision
2763
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
+ });
2764
3631
  stopIfNeeded();
2765
3632
  }
2766
3633
  }
@@ -2813,6 +3680,7 @@ async function runCoordinator(options) {
2813
3680
  cost: totalCost,
2814
3681
  transcript: createTranscriptLink(transcript)
2815
3682
  }),
3683
+ ...triggeringFailureForAbortMode !== void 0 ? { triggeringFailureForAbortMode } : {},
2816
3684
  events,
2817
3685
  transcript
2818
3686
  },
@@ -2847,7 +3715,7 @@ async function runCoordinator(options) {
2847
3715
  events,
2848
3716
  transcript,
2849
3717
  iteration: transcript.length,
2850
- elapsedMs: elapsedMs$2(startedAtMs)
3718
+ elapsedMs: elapsedMs(startedAtMs)
2851
3719
  }));
2852
3720
  if (!stopRecord) return false;
2853
3721
  stopped = true;
@@ -2863,13 +3731,16 @@ async function runCoordinator(options) {
2863
3731
  reason: record.budgetReason ?? "cost",
2864
3732
  cost: totalCost,
2865
3733
  iteration: transcript.length,
2866
- elapsedMs: elapsedMs$2(startedAtMs),
3734
+ elapsedMs: elapsedMs(startedAtMs),
2867
3735
  detail: record.detail ?? {}
2868
3736
  };
2869
3737
  emit(event);
2870
3738
  recordProtocolDecision(event, { transcriptEntryCount: transcript.length });
2871
3739
  }
2872
3740
  }
3741
+ function isDelegateValidationError(error) {
3742
+ return DogpileError.isInstance(error) && error.code === "invalid-configuration" && error.detail?.["kind"] === "delegate-validation";
3743
+ }
2873
3744
  async function runCoordinatorTurn(turn) {
2874
3745
  throwIfAborted(turn.options.signal, turn.options.model.id);
2875
3746
  const request = {
@@ -2898,7 +3769,7 @@ async function runCoordinatorTurn(turn) {
2898
3769
  events: turn.events,
2899
3770
  transcript: turn.transcript,
2900
3771
  iteration: turn.transcript.length,
2901
- elapsedMs: elapsedMs$2(turn.startedAtMs)
3772
+ elapsedMs: elapsedMs(turn.startedAtMs)
2902
3773
  })
2903
3774
  };
2904
3775
  const response = await generateModelTurn({
@@ -2913,7 +3784,11 @@ async function runCoordinatorTurn(turn) {
2913
3784
  turn.providerCalls.push(call);
2914
3785
  }
2915
3786
  });
2916
- 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
+ });
2917
3792
  const totalCost = addCost(turn.totalCost, responseCost$2(response));
2918
3793
  const toolCalls = await executeModelResponseToolRequests({
2919
3794
  response,
@@ -2949,7 +3824,10 @@ async function runCoordinatorTurn(turn) {
2949
3824
  phase: turn.phase,
2950
3825
  transcriptEntryCount: turn.transcript.length
2951
3826
  });
2952
- return totalCost;
3827
+ return {
3828
+ totalCost,
3829
+ decision
3830
+ };
2953
3831
  }
2954
3832
  async function runCoordinatorWorkerTurn(turn) {
2955
3833
  throwIfAborted(turn.options.signal, turn.options.model.id);
@@ -2979,7 +3857,7 @@ async function runCoordinatorWorkerTurn(turn) {
2979
3857
  events: turn.events,
2980
3858
  transcript: turn.transcript,
2981
3859
  iteration: turn.turn - 1,
2982
- elapsedMs: elapsedMs$2(turn.startedAtMs)
3860
+ elapsedMs: elapsedMs(turn.startedAtMs)
2983
3861
  })
2984
3862
  };
2985
3863
  const response = await generateModelTurn({
@@ -2994,7 +3872,21 @@ async function runCoordinatorWorkerTurn(turn) {
2994
3872
  turn.providerCallSlots[turn.providerCallIndex] = call;
2995
3873
  }
2996
3874
  });
2997
- 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
+ });
2998
3890
  const toolCalls = await executeModelResponseToolRequests({
2999
3891
  response,
3000
3892
  executor: turn.toolExecutor,
@@ -3021,6 +3913,30 @@ function buildSystemPrompt$2(agent, coordinator) {
3021
3913
  function buildCoordinatorPlanInput(intent, coordinator) {
3022
3914
  return `Mission: ${intent}\nCoordinator ${coordinator.id}: assign the work, name the plan, and provide the first contribution.`;
3023
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
+ }
3024
3940
  function buildWorkerInput(intent, transcript, coordinator) {
3025
3941
  const prior = transcript.map((entry) => `${entry.role} (${entry.agentId}): ${entry.output}`).join("\n\n");
3026
3942
  return `Mission: ${intent}\n\nCoordinator: ${coordinator.id}\nPrior contributions:\n${prior}\n\nFollow the coordinator-managed plan and provide your assigned contribution.`;
@@ -3037,22 +3953,386 @@ function responseCost$2(response) {
3037
3953
  totalTokens: response.usage?.totalTokens ?? 0
3038
3954
  };
3039
3955
  }
3040
- function createRunId$2() {
3041
- return globalThis.crypto?.randomUUID?.() ?? `run-${Date.now().toString(36)}`;
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");
3042
4221
  }
3043
- function nowMs$2() {
3044
- return globalThis.performance?.now() ?? Date.now();
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
+ });
3045
4292
  }
3046
- function elapsedMs$2(startedAtMs) {
3047
- return Math.max(0, nowMs$2() - startedAtMs);
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
+ });
3048
4312
  }
3049
- function providerCallIdFor$1(runId, oneBasedIndex) {
3050
- return `${runId}:provider-call:${oneBasedIndex}`;
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
+ };
3051
4331
  }
3052
4332
  //#endregion
3053
4333
  //#region src/runtime/sequential.ts
3054
4334
  async function runSequential(options) {
3055
- const runId = createRunId$1();
4335
+ const runId = createRunId();
3056
4336
  const events = [];
3057
4337
  const transcript = [];
3058
4338
  const protocolDecisions = [];
@@ -3060,7 +4340,7 @@ async function runSequential(options) {
3060
4340
  let totalCost = emptyCost();
3061
4341
  const maxTurns = options.protocol.maxTurns ?? options.agents.length;
3062
4342
  const activeAgents = options.agents.slice(0, maxTurns);
3063
- const startedAtMs = nowMs$1();
4343
+ const startedAtMs = nowMs();
3064
4344
  let stopped = false;
3065
4345
  let termination;
3066
4346
  const wrapUpHint = createWrapUpHintController({
@@ -3135,7 +4415,7 @@ async function runSequential(options) {
3135
4415
  events,
3136
4416
  transcript,
3137
4417
  iteration: transcript.length,
3138
- elapsedMs: elapsedMs$1(startedAtMs)
4418
+ elapsedMs: elapsedMs(startedAtMs)
3139
4419
  })
3140
4420
  };
3141
4421
  const response = await generateModelTurn({
@@ -3187,7 +4467,8 @@ async function runSequential(options) {
3187
4467
  });
3188
4468
  if (stopIfNeeded()) break;
3189
4469
  }
3190
- const output = [...transcript].reverse().find((entry) => isParticipatingDecision(entry.decision))?.output ?? "";
4470
+ const reversed = [...transcript].reverse();
4471
+ const output = reversed.find((entry) => isParticipatingDecision(entry.decision))?.output ?? reversed.find((entry) => entry.decision === void 0)?.output ?? "";
3191
4472
  throwIfAborted(options.signal, options.model.id);
3192
4473
  const final = {
3193
4474
  type: "final",
@@ -3270,7 +4551,7 @@ async function runSequential(options) {
3270
4551
  events,
3271
4552
  transcript,
3272
4553
  iteration: transcript.length,
3273
- elapsedMs: elapsedMs$1(startedAtMs)
4554
+ elapsedMs: elapsedMs(startedAtMs)
3274
4555
  }));
3275
4556
  if (!stopRecord) return false;
3276
4557
  stopped = true;
@@ -3286,7 +4567,7 @@ async function runSequential(options) {
3286
4567
  reason: record.budgetReason ?? "cost",
3287
4568
  cost: totalCost,
3288
4569
  iteration: transcript.length,
3289
- elapsedMs: elapsedMs$1(startedAtMs),
4570
+ elapsedMs: elapsedMs(startedAtMs),
3290
4571
  detail: record.detail ?? {}
3291
4572
  };
3292
4573
  emit(event);
@@ -3309,15 +4590,6 @@ function responseCost$1(response) {
3309
4590
  totalTokens: response.usage?.totalTokens ?? 0
3310
4591
  };
3311
4592
  }
3312
- function createRunId$1() {
3313
- return globalThis.crypto?.randomUUID?.() ?? `run-${Date.now().toString(36)}`;
3314
- }
3315
- function nowMs$1() {
3316
- return globalThis.performance?.now() ?? Date.now();
3317
- }
3318
- function elapsedMs$1(startedAtMs) {
3319
- return Math.max(0, nowMs$1() - startedAtMs);
3320
- }
3321
4593
  //#endregion
3322
4594
  //#region src/runtime/shared.ts
3323
4595
  async function runShared(options) {
@@ -3594,20 +4866,10 @@ function responseCost(response) {
3594
4866
  totalTokens: response.usage?.totalTokens ?? 0
3595
4867
  };
3596
4868
  }
3597
- function createRunId() {
3598
- return globalThis.crypto?.randomUUID?.() ?? `run-${Date.now().toString(36)}`;
3599
- }
3600
- function nowMs() {
3601
- return globalThis.performance?.now() ?? Date.now();
3602
- }
3603
- function elapsedMs(startedAtMs) {
3604
- return Math.max(0, nowMs() - startedAtMs);
3605
- }
3606
- function providerCallIdFor(runId, oneBasedIndex) {
3607
- return `${runId}:provider-call:${oneBasedIndex}`;
3608
- }
3609
4869
  //#endregion
3610
4870
  //#region src/runtime/engine.ts
4871
+ var DEFAULT_MAX_DEPTH = 4;
4872
+ var DEFAULT_MAX_CONCURRENT_CHILDREN = 4;
3611
4873
  var defaultHighLevelProtocol = "sequential";
3612
4874
  var defaultHighLevelTier = "balanced";
3613
4875
  /**
@@ -3628,9 +4890,20 @@ function createEngine(options) {
3628
4890
  const temperature = options.temperature ?? tierTemperature(options.tier);
3629
4891
  const agents = orderAgentsForTemperature(options.agents ?? defaultAgents(), temperature, options.seed);
3630
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;
3631
4896
  return {
3632
- run(intent) {
4897
+ run(intent, runOptions) {
3633
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;
3634
4907
  return runNonStreamingProtocol({
3635
4908
  intent,
3636
4909
  protocol,
@@ -3644,11 +4917,23 @@ function createEngine(options) {
3644
4917
  ...options.signal !== void 0 ? { signal: options.signal } : {},
3645
4918
  ...terminate ? { terminate } : {},
3646
4919
  ...options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {},
3647
- ...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 } : {}
3648
4927
  });
3649
4928
  },
3650
- stream(intent) {
4929
+ stream(intent, runOptions) {
3651
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);
3652
4937
  const pendingEvents = [];
3653
4938
  const pendingResolvers = [];
3654
4939
  const emittedEvents = [];
@@ -3665,7 +4950,10 @@ function createEngine(options) {
3665
4950
  const abortRace = createAbortRace(abortController.signal, options.model.id);
3666
4951
  let complete = false;
3667
4952
  let lastRunId = "";
4953
+ let rootRunId;
3668
4954
  let pendingFinalEvent;
4955
+ let activeAbortDrain;
4956
+ const failureInstancesByChildRunId = /* @__PURE__ */ new Map();
3669
4957
  let status = "running";
3670
4958
  let resolveResult;
3671
4959
  let rejectResult;
@@ -3711,6 +4999,8 @@ function createEngine(options) {
3711
4999
  async function execute() {
3712
5000
  if (status !== "running") return;
3713
5001
  try {
5002
+ const streamStartedAtMs = Date.now();
5003
+ const streamParentDeadlineMs = options.budget?.timeoutMs !== void 0 ? streamStartedAtMs + options.budget.timeoutMs : void 0;
3714
5004
  const baseResult = await abortRace.run(runProtocol({
3715
5005
  intent,
3716
5006
  protocol,
@@ -3723,17 +5013,32 @@ function createEngine(options) {
3723
5013
  ...options.seed !== void 0 ? { seed: options.seed } : {},
3724
5014
  signal: abortController.signal,
3725
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,
3726
5023
  emit(event) {
3727
5024
  if (status !== "running") return;
5025
+ const parentRunIds = event.parentRunIds;
5026
+ if (rootRunId === void 0 && parentRunIds === void 0) rootRunId = event.runId;
3728
5027
  lastRunId = event.runId;
3729
- if (event.type === "final") {
5028
+ if (event.type === "final" && event.runId === rootRunId) {
3730
5029
  pendingFinalEvent = event;
3731
5030
  return;
3732
5031
  }
3733
5032
  publish(event);
3734
- }
5033
+ },
5034
+ registerAbortDrain(drain) {
5035
+ activeAbortDrain = drain;
5036
+ },
5037
+ failureInstancesByChildRunId
3735
5038
  }));
3736
5039
  if (status !== "running") return;
5040
+ const terminalThrow = resolveRuntimeTerminalThrow(baseResult.trace, failureInstancesByChildRunId);
5041
+ if (terminalThrow) throw terminalThrow;
3737
5042
  const finalizedResult = await abortRace.run(applyRunEvaluation(baseResult, options.evaluate));
3738
5043
  if (status !== "running") return;
3739
5044
  const finalEvent = finalizedResult.trace.events.at(-1);
@@ -3746,6 +5051,10 @@ function createEngine(options) {
3746
5051
  if (isStreamHandleStatus(status, "cancelled")) return;
3747
5052
  const runtimeError = timeoutLifecycle.translateError(error);
3748
5053
  status = isCancellationError(runtimeError) ? "cancelled" : "failed";
5054
+ if (shouldPublishAborted(runtimeError)) {
5055
+ activeAbortDrain?.(runtimeError);
5056
+ publish(createStreamAbortedEvent(runtimeError, lastRunId));
5057
+ }
3749
5058
  publish(createStreamErrorEvent(runtimeError, lastRunId));
3750
5059
  closeStream();
3751
5060
  rejectResult(runtimeError);
@@ -3754,15 +5063,18 @@ function createEngine(options) {
3754
5063
  function cancelRun(cause) {
3755
5064
  if (status !== "running") return;
3756
5065
  const error = createStreamCancellationError(options.model.id, cause);
3757
- status = "cancelled";
3758
5066
  abortController.abort(error);
5067
+ activeAbortDrain?.(error);
5068
+ publish(createStreamAbortedEvent(error, lastRunId));
3759
5069
  publish(createStreamErrorEvent(error, lastRunId));
5070
+ status = "cancelled";
3760
5071
  closeStream();
3761
5072
  rejectResult(error);
3762
5073
  }
3763
5074
  function closeStream() {
3764
5075
  if (complete) return;
3765
5076
  complete = true;
5077
+ failureInstancesByChildRunId.clear();
3766
5078
  removeCallerAbortListener();
3767
5079
  timeoutLifecycle.cleanup();
3768
5080
  abortRace.cleanup();
@@ -3818,7 +5130,8 @@ function createNonStreamingAbortLifecycle(options) {
3818
5130
  const timeoutLifecycle = createTimeoutAbortLifecycle({
3819
5131
  abortController,
3820
5132
  timeoutMs: options.timeoutMs,
3821
- providerId: options.providerId
5133
+ providerId: options.providerId,
5134
+ timeoutErrorSource: options.timeoutErrorSource ?? "runtime"
3822
5135
  });
3823
5136
  const abortRace = createAbortRace(abortController.signal, options.providerId);
3824
5137
  const removeCallerAbortListener = wireCallerAbortSignal(options.callerSignal, abortController, () => {
@@ -3846,7 +5159,11 @@ function createTimeoutAbortLifecycle(options) {
3846
5159
  },
3847
5160
  cleanup() {}
3848
5161
  };
3849
- const timeoutError = createTimeoutError(options.providerId, options.timeoutMs);
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);
3850
5167
  const timeoutId = setTimeout(() => {
3851
5168
  options.abortController.abort(timeoutError);
3852
5169
  }, options.timeoutMs);
@@ -3914,6 +5231,23 @@ function timeoutMsFromTermination(condition) {
3914
5231
  function readAbortSignalReason(signal) {
3915
5232
  return signal?.aborted ? signal.reason : void 0;
3916
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
+ }
3917
5251
  function createStreamErrorEvent(error, runId) {
3918
5252
  if (DogpileError.isInstance(error)) return {
3919
5253
  type: "error",
@@ -3946,10 +5280,12 @@ function dogpileErrorStreamDetail(error) {
3946
5280
  return detail;
3947
5281
  }
3948
5282
  async function runNonStreamingProtocol(options) {
5283
+ const failureInstancesByChildRunId = /* @__PURE__ */ new Map();
3949
5284
  const abortLifecycle = createNonStreamingAbortLifecycle({
3950
5285
  callerSignal: options.signal,
3951
5286
  timeoutMs: runtimeTimeoutMs(options),
3952
- providerId: options.model.id
5287
+ providerId: options.model.id,
5288
+ timeoutErrorSource: options.currentDepth !== void 0 && options.currentDepth > 0 && options.parentDeadlineMs === void 0 ? "engine" : "runtime"
3953
5289
  });
3954
5290
  try {
3955
5291
  const emittedEvents = [];
@@ -3958,7 +5294,8 @@ async function runNonStreamingProtocol(options) {
3958
5294
  ...abortLifecycle.signal !== void 0 ? { signal: abortLifecycle.signal } : {},
3959
5295
  emit(event) {
3960
5296
  emittedEvents.push(event);
3961
- }
5297
+ },
5298
+ failureInstancesByChildRunId
3962
5299
  }));
3963
5300
  const events = emittedEvents.length > 0 ? emittedEvents : result.trace.events;
3964
5301
  const trace = {
@@ -3979,10 +5316,13 @@ async function runNonStreamingProtocol(options) {
3979
5316
  eventLog: createRunEventLog(trace.runId, trace.protocol, events),
3980
5317
  trace
3981
5318
  };
5319
+ const terminalThrow = resolveRuntimeTerminalThrow(runResult.trace, failureInstancesByChildRunId);
5320
+ if (terminalThrow) throw terminalThrow;
3982
5321
  return canonicalizeRunResult(await abortLifecycle.run(applyRunEvaluation(runResult, options.evaluate)));
3983
5322
  } catch (error) {
3984
5323
  throw abortLifecycle.translateError(error);
3985
5324
  } finally {
5325
+ failureInstancesByChildRunId.clear();
3986
5326
  abortLifecycle.cleanup();
3987
5327
  }
3988
5328
  }
@@ -4057,7 +5397,20 @@ function runProtocol(options) {
4057
5397
  ...options.signal !== void 0 ? { signal: options.signal } : {},
4058
5398
  ...options.terminate ? { terminate: options.terminate } : {},
4059
5399
  ...options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {},
4060
- ...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
+ })
4061
5414
  });
4062
5415
  case "shared": return runShared({
4063
5416
  intent: options.intent,
@@ -4124,6 +5477,9 @@ function stream(options) {
4124
5477
  function replay(trace) {
4125
5478
  const cost = trace.finalOutput.cost;
4126
5479
  const lastEvent = trace.events.at(-1);
5480
+ const accounting = recomputeAccountingFromTrace(trace);
5481
+ const replayThrow = resolveReplayTerminalThrow(trace);
5482
+ if (replayThrow) throw replayThrow;
4127
5483
  const baseResult = {
4128
5484
  output: trace.finalOutput.output,
4129
5485
  eventLog: createRunEventLog(trace.runId, trace.protocol, trace.events),
@@ -4138,13 +5494,7 @@ function replay(trace) {
4138
5494
  agentsUsed: trace.agentsUsed,
4139
5495
  events: trace.events
4140
5496
  }),
4141
- accounting: createRunAccounting({
4142
- tier: trace.tier,
4143
- ...trace.budget.caps ? { budget: trace.budget.caps } : {},
4144
- ...trace.budget.termination ? { termination: trace.budget.termination } : {},
4145
- cost,
4146
- events: trace.events
4147
- }),
5497
+ accounting,
4148
5498
  cost
4149
5499
  };
4150
5500
  if (lastEvent?.type !== "final") return baseResult;
@@ -4154,6 +5504,64 @@ function replay(trace) {
4154
5504
  ...lastEvent.evaluation !== void 0 ? { evaluation: lastEvent.evaluation } : {}
4155
5505
  };
4156
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
+ }
4157
5565
  /**
4158
5566
  * Replay a saved completed trace as a stream without invoking a model provider.
4159
5567
  *
@@ -4164,20 +5572,22 @@ function replay(trace) {
4164
5572
  * replay remains storage-free and provider-free.
4165
5573
  */
4166
5574
  function replayStream(trace) {
5575
+ const result = Promise.resolve(replay(trace));
5576
+ const replayEvents = replayStreamEvents(trace);
4167
5577
  return {
4168
5578
  get status() {
4169
5579
  return "completed";
4170
5580
  },
4171
- result: Promise.resolve(replay(trace)),
5581
+ result,
4172
5582
  cancel() {},
4173
5583
  subscribe(subscriber) {
4174
- for (const event of trace.events) subscriber(event);
5584
+ for (const event of replayEvents) subscriber(event);
4175
5585
  return { unsubscribe() {} };
4176
5586
  },
4177
5587
  [Symbol.asyncIterator]() {
4178
5588
  let index = 0;
4179
5589
  return { next() {
4180
- const event = trace.events[index];
5590
+ const event = replayEvents[index];
4181
5591
  if (event) {
4182
5592
  index += 1;
4183
5593
  return Promise.resolve({
@@ -4193,6 +5603,22 @@ function replayStream(trace) {
4193
5603
  }
4194
5604
  };
4195
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
+ }
4196
5622
  function wireCallerAbortSignal(callerSignal, abortController, cancelRun) {
4197
5623
  if (!callerSignal) return () => {};
4198
5624
  const cancelFromCaller = () => {
@@ -4216,7 +5642,10 @@ function createStreamCancellationError(providerId, cause) {
4216
5642
  retryable: false,
4217
5643
  providerId,
4218
5644
  ...cause !== void 0 ? { cause } : {},
4219
- detail: { status: "cancelled" }
5645
+ detail: {
5646
+ status: "cancelled",
5647
+ reason: "parent-aborted"
5648
+ }
4220
5649
  });
4221
5650
  }
4222
5651
  function isCancellationError(error) {
@@ -4230,6 +5659,20 @@ function withHighLevelDefaults(options) {
4230
5659
  tier: options.tier ?? defaultHighLevelTier
4231
5660
  };
4232
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
+ }
4233
5676
  /**
4234
5677
  * Branded high-level SDK namespace.
4235
5678
  *
@@ -4256,6 +5699,8 @@ function createOpenAICompatibleProvider(options) {
4256
5699
  validateOptions(options);
4257
5700
  const providerId = options.id ?? `openai-compatible:${options.model}`;
4258
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;
4259
5704
  if (!fetchImplementation) throw new DogpileError({
4260
5705
  code: "invalid-configuration",
4261
5706
  message: "createOpenAICompatibleProvider() requires a fetch implementation in this runtime.",
@@ -4269,6 +5714,7 @@ function createOpenAICompatibleProvider(options) {
4269
5714
  });
4270
5715
  return {
4271
5716
  id: providerId,
5717
+ metadata: { locality: resolvedLocality },
4272
5718
  async generate(request) {
4273
5719
  let response;
4274
5720
  try {
@@ -4281,9 +5727,11 @@ function createOpenAICompatibleProvider(options) {
4281
5727
  } catch (error) {
4282
5728
  throw normalizeFetchError(error, providerId);
4283
5729
  }
4284
- const payload = await readJson(response, providerId);
4285
- if (!response.ok) throw createProviderError(response, payload, providerId);
4286
- const completion = asChatCompletionResponse(payload, providerId);
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);
4287
5735
  const text = readAssistantText(completion, providerId);
4288
5736
  const usage = normalizeUsage(completion.usage);
4289
5737
  const finishReason = normalizeFinishReason(completion.choices?.[0]?.finish_reason);
@@ -4312,6 +5760,22 @@ function validateOptions(options) {
4312
5760
  if (options.fetch !== void 0 && typeof options.fetch !== "function") throwInvalid("fetch", "a fetch-compatible function when provided");
4313
5761
  if (options.maxOutputTokens !== void 0 && (!Number.isInteger(options.maxOutputTokens) || options.maxOutputTokens <= 0)) throwInvalid("maxOutputTokens", "a positive integer when provided");
4314
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
+ }
4315
5779
  }
4316
5780
  function throwInvalid(path, expected) {
4317
5781
  throw new DogpileError({
@@ -4325,6 +5789,38 @@ function throwInvalid(path, expected) {
4325
5789
  }
4326
5790
  });
4327
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
+ }
4328
5824
  function createURL(options) {
4329
5825
  const baseURL = new URL(String(options.baseURL ?? defaultBaseURL));
4330
5826
  const path = options.path ?? defaultPath;
@@ -4374,6 +5870,13 @@ async function readJson(response, providerId) {
4374
5870
  });
4375
5871
  }
4376
5872
  }
5873
+ async function readJsonLenient(response) {
5874
+ try {
5875
+ return await response.json();
5876
+ } catch {
5877
+ return;
5878
+ }
5879
+ }
4377
5880
  function asChatCompletionResponse(payload, providerId) {
4378
5881
  if (!isRecord(payload)) throw new DogpileError({
4379
5882
  code: "provider-invalid-response",
@@ -4444,14 +5947,17 @@ function responseMetadata(response) {
4444
5947
  });
4445
5948
  }
4446
5949
  function createProviderError(response, payload, providerId) {
5950
+ const code = codeForStatus(response.status);
5951
+ const timeoutSource = code === "provider-timeout" ? { source: "provider" } : {};
4447
5952
  return new DogpileError({
4448
- code: codeForStatus(response.status),
5953
+ code,
4449
5954
  message: providerResponseErrorMessage(response, payload),
4450
5955
  retryable: response.status === 408 || response.status === 429 || response.status >= 500,
4451
5956
  providerId,
4452
5957
  detail: removeUndefined({
4453
5958
  statusCode: response.status,
4454
5959
  statusText: response.statusText,
5960
+ ...timeoutSource,
4455
5961
  response: isJsonValue(payload) ? payload : void 0
4456
5962
  })
4457
5963
  });
@@ -4515,6 +6021,263 @@ function isJsonValue(value) {
4515
6021
  return false;
4516
6022
  }
4517
6023
  //#endregion
4518
- export { Dogpile, DogpileError, budget, builtInDogpileToolIdentity, builtInDogpileToolInputSchema, builtInDogpileToolPermissions, combineTerminationDecisions, convergence, createCodeExecToolAdapter, createEngine, createOpenAICompatibleProvider, createRuntimeToolExecutor, createWebSearchToolAdapter, evaluateBudget, evaluateConvergence, evaluateFirstOf, evaluateJudge, evaluateTermination, evaluateTerminationStop, firstOf, judge, normalizeBuiltInDogpileTool, normalizeBuiltInDogpileTools, normalizeRuntimeToolAdapterError, replay, replayStream, run, runtimeToolAvailability, runtimeToolManifest, stream, validateBuiltInDogpileToolInput };
6024
+ //#region src/runtime/logger.ts
6025
+ /** Logger that drops every call. The default when no logger is supplied. */
6026
+ var noopLogger = {
6027
+ debug() {},
6028
+ info() {},
6029
+ warn() {},
6030
+ error() {}
6031
+ };
6032
+ /**
6033
+ * Build a console-backed logger respecting a minimum level.
6034
+ *
6035
+ * The output format is JSON-on-one-line so it can be piped straight into log
6036
+ * collectors. Use `loggerFromEvents` to bridge it to a Dogpile stream handle.
6037
+ */
6038
+ function consoleLogger(options = {}) {
6039
+ const minLevel = options.level ?? "info";
6040
+ const allowed = (level) => LEVEL_ORDER[level] >= LEVEL_ORDER[minLevel];
6041
+ const emit = (level, message, fields) => {
6042
+ if (!allowed(level)) return;
6043
+ const payload = {
6044
+ level,
6045
+ message
6046
+ };
6047
+ if (fields !== void 0) payload.fields = fields;
6048
+ (level === "error" ? console.error : level === "warn" ? console.warn : console.log)(JSON.stringify(payload));
6049
+ };
6050
+ return {
6051
+ debug: (message, fields) => emit("debug", message, fields),
6052
+ info: (message, fields) => emit("info", message, fields),
6053
+ warn: (message, fields) => emit("warn", message, fields),
6054
+ error: (message, fields) => emit("error", message, fields)
6055
+ };
6056
+ }
6057
+ var LEVEL_ORDER = {
6058
+ debug: 0,
6059
+ info: 1,
6060
+ warn: 2,
6061
+ error: 3
6062
+ };
6063
+ /**
6064
+ * Bridge a `Logger` to a Dogpile stream handle by returning a
6065
+ * `StreamEventSubscriber`. Pass it to `handle.subscribe(...)`.
6066
+ *
6067
+ * Logger throws are caught and routed to `logger.error` so a misbehaving
6068
+ * logger can never crash an in-flight run.
6069
+ *
6070
+ * @example
6071
+ * ```ts
6072
+ * const handle = Dogpile.stream({ intent, model });
6073
+ * handle.subscribe(loggerFromEvents(consoleLogger({ level: "info" })));
6074
+ * const result = await handle.result;
6075
+ * ```
6076
+ */
6077
+ function loggerFromEvents(logger, options = {}) {
6078
+ const includeSet = options.include ? new Set(options.include) : void 0;
6079
+ return (event) => {
6080
+ const eventType = event.type;
6081
+ if (includeSet && !includeSet.has(eventType)) return;
6082
+ const level = options.levelFor?.(event) ?? defaultLevel(event);
6083
+ const message = describeEvent(event);
6084
+ const fields = summarizeEvent(event);
6085
+ try {
6086
+ logger[level](message, fields);
6087
+ } catch (cause) {
6088
+ try {
6089
+ logger.error("dogpile logger threw while handling event", {
6090
+ eventType,
6091
+ error: cause instanceof Error ? cause.message : String(cause)
6092
+ });
6093
+ } catch {}
6094
+ }
6095
+ };
6096
+ }
6097
+ function defaultLevel(event) {
6098
+ switch (event.type) {
6099
+ case "model-output-chunk": return "debug";
6100
+ case "budget-stop": return "warn";
6101
+ case "error": return "error";
6102
+ case "tool-result": return event.result?.type === "error" ? "warn" : "info";
6103
+ default: return "info";
6104
+ }
6105
+ }
6106
+ function describeEvent(event) {
6107
+ return `dogpile:${event.type}`;
6108
+ }
6109
+ function summarizeEvent(event) {
6110
+ const fields = { eventType: event.type };
6111
+ const at = event.at;
6112
+ if (typeof at === "string") fields.at = at;
6113
+ const runId = event.runId;
6114
+ if (typeof runId === "string") fields.runId = runId;
6115
+ const agentId = event.agentId;
6116
+ if (typeof agentId === "string") fields.agentId = agentId;
6117
+ const role = event.role;
6118
+ if (typeof role === "string") fields.role = role;
6119
+ return fields;
6120
+ }
6121
+ //#endregion
6122
+ //#region src/runtime/retry.ts
6123
+ /**
6124
+ * Default DogpileError codes that `withRetry` retries when no `retryOn`
6125
+ * predicate is supplied. These map to the transient provider failures listed
6126
+ * in `docs/developer-usage.md`.
6127
+ */
6128
+ var DEFAULT_RETRYABLE_DOGPILE_CODES = [
6129
+ "provider-rate-limited",
6130
+ "provider-timeout",
6131
+ "provider-unavailable"
6132
+ ];
6133
+ var DEFAULTS = {
6134
+ maxAttempts: 3,
6135
+ baseDelayMs: 250,
6136
+ maxDelayMs: 4e3,
6137
+ jitter: "full"
6138
+ };
6139
+ /**
6140
+ * Wrap a `ConfiguredModelProvider` with a retry policy. The wrapper:
6141
+ *
6142
+ * - Preserves the provider `id` so traces remain stable.
6143
+ * - Retries `generate()` calls when the policy says the error is retryable.
6144
+ * - Propagates `AbortSignal` cancellation immediately — never retries after
6145
+ * the caller cancels.
6146
+ * - Honors a `Retry-After`-style hint exposed via `error.detail.retryAfterMs`
6147
+ * when present and the policy did not provide its own `delayForError`.
6148
+ * - Forwards `stream()` calls through unchanged — streaming retries cannot be
6149
+ * safely automated because partial output may have already been observed.
6150
+ *
6151
+ * @example
6152
+ * ```ts
6153
+ * const robustProvider = withRetry(rawProvider, {
6154
+ * maxAttempts: 4,
6155
+ * baseDelayMs: 500,
6156
+ * onRetry: ({ attempt, delayMs, error }) => {
6157
+ * logger.warn("provider retry", { attempt, delayMs, error });
6158
+ * }
6159
+ * });
6160
+ * ```
6161
+ */
6162
+ function withRetry(provider, policy = {}) {
6163
+ const settings = {
6164
+ maxAttempts: policy.maxAttempts ?? DEFAULTS.maxAttempts,
6165
+ baseDelayMs: policy.baseDelayMs ?? DEFAULTS.baseDelayMs,
6166
+ maxDelayMs: policy.maxDelayMs ?? DEFAULTS.maxDelayMs,
6167
+ jitter: policy.jitter ?? DEFAULTS.jitter,
6168
+ retryOn: policy.retryOn ?? defaultRetryOn,
6169
+ random: policy.random ?? Math.random,
6170
+ sleep: policy.sleep ?? defaultSleep
6171
+ };
6172
+ if (settings.maxAttempts < 1) throw new DogpileError({
6173
+ code: "invalid-configuration",
6174
+ message: "withRetry: maxAttempts must be >= 1.",
6175
+ detail: { maxAttempts: settings.maxAttempts }
6176
+ });
6177
+ if (settings.baseDelayMs < 0 || settings.maxDelayMs < 0) throw new DogpileError({
6178
+ code: "invalid-configuration",
6179
+ message: "withRetry: delay fields must be non-negative.",
6180
+ detail: {
6181
+ baseDelayMs: settings.baseDelayMs,
6182
+ maxDelayMs: settings.maxDelayMs
6183
+ }
6184
+ });
6185
+ const wrapped = {
6186
+ id: provider.id,
6187
+ async generate(request) {
6188
+ let lastError;
6189
+ for (let attempt = 1; attempt <= settings.maxAttempts; attempt++) {
6190
+ if (request.signal?.aborted) throw abortReason(request.signal);
6191
+ try {
6192
+ return await provider.generate(request);
6193
+ } catch (error) {
6194
+ lastError = error;
6195
+ if (isAbortError(error) || request.signal?.aborted) throw error;
6196
+ if (attempt >= settings.maxAttempts || !settings.retryOn(error)) throw error;
6197
+ const delayMs = chooseDelay({
6198
+ attempt,
6199
+ error,
6200
+ settings,
6201
+ policy
6202
+ });
6203
+ policy.onRetry?.({
6204
+ attempt,
6205
+ maxAttempts: settings.maxAttempts,
6206
+ delayMs,
6207
+ error,
6208
+ providerId: provider.id
6209
+ });
6210
+ await settings.sleep(delayMs, request.signal);
6211
+ }
6212
+ }
6213
+ throw lastError ?? new DogpileError({
6214
+ code: "unknown",
6215
+ message: "withRetry: exhausted attempts without throwing or returning."
6216
+ });
6217
+ }
6218
+ };
6219
+ if (typeof provider.stream === "function") {
6220
+ const upstreamStream = provider.stream.bind(provider);
6221
+ wrapped.stream = (request) => upstreamStream(request);
6222
+ }
6223
+ return wrapped;
6224
+ }
6225
+ function chooseDelay(args) {
6226
+ const override = args.policy.delayForError?.(args.error) ?? retryAfterFromError(args.error);
6227
+ if (override !== void 0 && Number.isFinite(override) && override >= 0) return Math.min(args.settings.maxDelayMs, override);
6228
+ const exponential = args.settings.baseDelayMs * 2 ** (args.attempt - 1);
6229
+ const capped = Math.min(args.settings.maxDelayMs, exponential);
6230
+ if (args.settings.jitter === "none") return capped;
6231
+ return Math.floor(capped * args.settings.random());
6232
+ }
6233
+ function defaultRetryOn(error) {
6234
+ if (isAbortError(error)) return false;
6235
+ if (DogpileError.isInstance(error)) {
6236
+ if (error.code === "aborted" || error.code === "invalid-configuration") return false;
6237
+ return DEFAULT_RETRYABLE_DOGPILE_CODES.includes(error.code);
6238
+ }
6239
+ if (error instanceof TypeError) return true;
6240
+ return false;
6241
+ }
6242
+ function isAbortError(error) {
6243
+ if (DogpileError.isInstance(error) && error.code === "aborted") return true;
6244
+ if (typeof error === "object" && error !== null) {
6245
+ if (error.name === "AbortError") return true;
6246
+ }
6247
+ return false;
6248
+ }
6249
+ function abortReason(signal) {
6250
+ return signal.reason ?? new DogpileError({
6251
+ code: "aborted",
6252
+ message: "Request aborted."
6253
+ });
6254
+ }
6255
+ function retryAfterFromError(error) {
6256
+ if (!DogpileError.isInstance(error)) return void 0;
6257
+ const detail = error.detail;
6258
+ if (!detail || typeof detail !== "object") return void 0;
6259
+ const candidate = detail.retryAfterMs;
6260
+ if (typeof candidate === "number" && Number.isFinite(candidate) && candidate >= 0) return candidate;
6261
+ }
6262
+ function defaultSleep(ms, signal) {
6263
+ if (ms <= 0) return Promise.resolve();
6264
+ return new Promise((resolve, reject) => {
6265
+ if (signal?.aborted) {
6266
+ reject(abortReason(signal));
6267
+ return;
6268
+ }
6269
+ const timer = setTimeout(() => {
6270
+ signal?.removeEventListener("abort", onAbort);
6271
+ resolve();
6272
+ }, ms);
6273
+ const onAbort = () => {
6274
+ clearTimeout(timer);
6275
+ reject(abortReason(signal));
6276
+ };
6277
+ signal?.addEventListener("abort", onAbort, { once: true });
6278
+ });
6279
+ }
6280
+ //#endregion
6281
+ export { DEFAULT_RETRYABLE_DOGPILE_CODES, Dogpile, DogpileError, budget, builtInDogpileToolIdentity, builtInDogpileToolInputSchema, builtInDogpileToolPermissions, combineTerminationDecisions, consoleLogger, convergence, createCodeExecToolAdapter, createEngine, createOpenAICompatibleProvider, createRuntimeToolExecutor, createWebSearchToolAdapter, evaluateBudget, evaluateConvergence, evaluateFirstOf, evaluateJudge, evaluateTermination, evaluateTerminationStop, firstOf, judge, loggerFromEvents, noopLogger, normalizeBuiltInDogpileTool, normalizeBuiltInDogpileTools, normalizeRuntimeToolAdapterError, replay, replayStream, run, runtimeToolAvailability, runtimeToolManifest, stream, validateBuiltInDogpileToolInput, withRetry };
4519
6282
 
4520
6283
  //# sourceMappingURL=index.js.map