@dogpile/sdk 0.4.0 → 0.6.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 (108) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/dist/browser/index.js +4156 -4611
  3. package/dist/browser/index.js.map +1 -1
  4. package/dist/index.d.ts +3 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +1 -0
  7. package/dist/index.js.map +1 -1
  8. package/dist/providers/openai-compatible.d.ts.map +1 -1
  9. package/dist/providers/openai-compatible.js +6 -1
  10. package/dist/providers/openai-compatible.js.map +1 -1
  11. package/dist/runtime/audit.d.ts +42 -0
  12. package/dist/runtime/audit.d.ts.map +1 -0
  13. package/dist/runtime/audit.js +73 -0
  14. package/dist/runtime/audit.js.map +1 -0
  15. package/dist/runtime/broadcast.d.ts +1 -0
  16. package/dist/runtime/broadcast.d.ts.map +1 -1
  17. package/dist/runtime/broadcast.js +171 -105
  18. package/dist/runtime/broadcast.js.map +1 -1
  19. package/dist/runtime/coordinator.d.ts +9 -2
  20. package/dist/runtime/coordinator.d.ts.map +1 -1
  21. package/dist/runtime/coordinator.js +164 -78
  22. package/dist/runtime/coordinator.js.map +1 -1
  23. package/dist/runtime/defaults.d.ts.map +1 -1
  24. package/dist/runtime/defaults.js +14 -5
  25. package/dist/runtime/defaults.js.map +1 -1
  26. package/dist/runtime/engine.d.ts +17 -4
  27. package/dist/runtime/engine.d.ts.map +1 -1
  28. package/dist/runtime/engine.js +577 -52
  29. package/dist/runtime/engine.js.map +1 -1
  30. package/dist/runtime/health.d.ts +51 -0
  31. package/dist/runtime/health.d.ts.map +1 -0
  32. package/dist/runtime/health.js +85 -0
  33. package/dist/runtime/health.js.map +1 -0
  34. package/dist/runtime/introspection.d.ts +96 -0
  35. package/dist/runtime/introspection.d.ts.map +1 -0
  36. package/dist/runtime/introspection.js +31 -0
  37. package/dist/runtime/introspection.js.map +1 -0
  38. package/dist/runtime/metrics.d.ts +44 -0
  39. package/dist/runtime/metrics.d.ts.map +1 -0
  40. package/dist/runtime/metrics.js +12 -0
  41. package/dist/runtime/metrics.js.map +1 -0
  42. package/dist/runtime/model.d.ts.map +1 -1
  43. package/dist/runtime/model.js +40 -10
  44. package/dist/runtime/model.js.map +1 -1
  45. package/dist/runtime/provenance.d.ts +25 -0
  46. package/dist/runtime/provenance.d.ts.map +1 -0
  47. package/dist/runtime/provenance.js +13 -0
  48. package/dist/runtime/provenance.js.map +1 -0
  49. package/dist/runtime/redaction.d.ts +13 -0
  50. package/dist/runtime/redaction.d.ts.map +1 -0
  51. package/dist/runtime/redaction.js +278 -0
  52. package/dist/runtime/redaction.js.map +1 -0
  53. package/dist/runtime/sanitization.d.ts +4 -0
  54. package/dist/runtime/sanitization.d.ts.map +1 -0
  55. package/dist/runtime/sanitization.js +63 -0
  56. package/dist/runtime/sanitization.js.map +1 -0
  57. package/dist/runtime/sequential.d.ts.map +1 -1
  58. package/dist/runtime/sequential.js +39 -36
  59. package/dist/runtime/sequential.js.map +1 -1
  60. package/dist/runtime/shared.d.ts +1 -0
  61. package/dist/runtime/shared.d.ts.map +1 -1
  62. package/dist/runtime/shared.js +167 -101
  63. package/dist/runtime/shared.js.map +1 -1
  64. package/dist/runtime/tools/built-in.d.ts +2 -0
  65. package/dist/runtime/tools/built-in.d.ts.map +1 -1
  66. package/dist/runtime/tools/built-in.js +153 -15
  67. package/dist/runtime/tools/built-in.js.map +1 -1
  68. package/dist/runtime/tools.d.ts.map +1 -1
  69. package/dist/runtime/tools.js +29 -7
  70. package/dist/runtime/tools.js.map +1 -1
  71. package/dist/runtime/tracing.d.ts +31 -0
  72. package/dist/runtime/tracing.d.ts.map +1 -0
  73. package/dist/runtime/tracing.js +18 -0
  74. package/dist/runtime/tracing.js.map +1 -0
  75. package/dist/runtime/validation.d.ts.map +1 -1
  76. package/dist/runtime/validation.js +3 -0
  77. package/dist/runtime/validation.js.map +1 -1
  78. package/dist/types/events.d.ts +13 -7
  79. package/dist/types/events.d.ts.map +1 -1
  80. package/dist/types/replay.d.ts +5 -1
  81. package/dist/types/replay.d.ts.map +1 -1
  82. package/dist/types.d.ts +144 -1
  83. package/dist/types.d.ts.map +1 -1
  84. package/dist/types.js.map +1 -1
  85. package/package.json +46 -1
  86. package/src/index.ts +5 -0
  87. package/src/providers/openai-compatible.ts +6 -1
  88. package/src/runtime/audit.ts +121 -0
  89. package/src/runtime/broadcast.ts +195 -108
  90. package/src/runtime/coordinator.ts +197 -86
  91. package/src/runtime/defaults.ts +15 -5
  92. package/src/runtime/engine.ts +725 -58
  93. package/src/runtime/health.ts +136 -0
  94. package/src/runtime/introspection.ts +122 -0
  95. package/src/runtime/metrics.ts +45 -0
  96. package/src/runtime/model.ts +44 -9
  97. package/src/runtime/provenance.ts +43 -0
  98. package/src/runtime/redaction.ts +355 -0
  99. package/src/runtime/sanitization.ts +81 -0
  100. package/src/runtime/sequential.ts +40 -37
  101. package/src/runtime/shared.ts +191 -104
  102. package/src/runtime/tools/built-in.ts +168 -15
  103. package/src/runtime/tools.ts +39 -8
  104. package/src/runtime/tracing.ts +35 -0
  105. package/src/runtime/validation.ts +3 -0
  106. package/src/types/events.ts +13 -7
  107. package/src/types/replay.ts +5 -1
  108. package/src/types.ts +152 -1
@@ -1,14 +1,17 @@
1
1
  import { DogpileError } from "../types.js";
2
2
  import { runBroadcast } from "./broadcast.js";
3
3
  import { runCoordinator } from "./coordinator.js";
4
- import { createReplayTraceFinalOutput, createReplayTraceBudgetStateChanges, canonicalizeRunResult, canonicalizeSerializable, createRunAccounting, createRunEventLog, createRunMetadata, createRunUsage, defaultAgents, normalizeProtocol, orderAgentsForTemperature, recomputeAccountingFromTrace, resolveOnChildFailure, tierTemperature } from "./defaults.js";
4
+ import { addCost, createReplayTraceFinalOutput, createReplayTraceBudgetStateChanges, canonicalizeRunResult, canonicalizeSerializable, createRunAccounting, createRunEventLog, createRunMetadata, createRunUsage, defaultAgents, emptyCost, normalizeProtocol, orderAgentsForTemperature, recomputeAccountingFromTrace, resolveOnChildFailure, tierTemperature } from "./defaults.js";
5
+ import { computeHealth, DEFAULT_HEALTH_THRESHOLDS } from "./health.js";
5
6
  import { runSequential } from "./sequential.js";
6
7
  import { runShared } from "./shared.js";
7
8
  import { classifyChildTimeoutSource, createAbortErrorFromSignal, createEngineDeadlineTimeoutError, createTimeoutError } from "./cancellation.js";
8
9
  import { budget as budgetCondition } from "./termination.js";
9
10
  import { validateDogpileOptions, validateEngineOptions, validateMissionIntent, validateProviderLocality, validateRunCallOptions } from "./validation.js";
11
+ import { DOGPILE_SPAN_NAMES } from "./tracing.js";
10
12
  const DEFAULT_MAX_DEPTH = 4;
11
13
  const DEFAULT_MAX_CONCURRENT_CHILDREN = 4;
14
+ const DEFAULT_MAX_CONCURRENT_AGENT_TURNS = 4;
12
15
  const defaultHighLevelProtocol = "sequential";
13
16
  const defaultHighLevelTier = "balanced";
14
17
  /**
@@ -31,6 +34,7 @@ export function createEngine(options) {
31
34
  const terminate = options.terminate ?? (options.budget ? conditionFromBudget(options.budget) : undefined);
32
35
  const engineMaxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
33
36
  const engineMaxConcurrentChildren = options.maxConcurrentChildren ?? DEFAULT_MAX_CONCURRENT_CHILDREN;
37
+ const engineMaxConcurrentAgentTurns = options.maxConcurrentAgentTurns ?? DEFAULT_MAX_CONCURRENT_AGENT_TURNS;
34
38
  const engineOnChildFailure = options.onChildFailure;
35
39
  return {
36
40
  run(intent, runOptions) {
@@ -40,6 +44,8 @@ export function createEngine(options) {
40
44
  const effectiveMaxDepth = Math.min(engineMaxDepth, runOptions?.maxDepth ?? Number.POSITIVE_INFINITY);
41
45
  assertRunDoesNotRaiseEngineMax("maxConcurrentChildren", runOptions?.maxConcurrentChildren, engineMaxConcurrentChildren);
42
46
  const effectiveMaxConcurrentChildren = Math.min(engineMaxConcurrentChildren, runOptions?.maxConcurrentChildren ?? Number.POSITIVE_INFINITY);
47
+ assertRunDoesNotRaiseEngineMax("maxConcurrentAgentTurns", runOptions?.maxConcurrentAgentTurns, engineMaxConcurrentAgentTurns);
48
+ const effectiveMaxConcurrentAgentTurns = Math.min(engineMaxConcurrentAgentTurns, runOptions?.maxConcurrentAgentTurns ?? Number.POSITIVE_INFINITY);
43
49
  const onChildFailure = resolveOnChildFailure(runOptions?.onChildFailure, engineOnChildFailure);
44
50
  const startedAtMs = Date.now();
45
51
  const parentDeadlineMs = options.budget?.timeoutMs !== undefined ? startedAtMs + options.budget.timeoutMs : undefined;
@@ -57,9 +63,13 @@ export function createEngine(options) {
57
63
  ...(terminate ? { terminate } : {}),
58
64
  ...(options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {}),
59
65
  ...(options.evaluate ? { evaluate: options.evaluate } : {}),
66
+ ...(options.tracer ? { tracer: options.tracer } : {}),
67
+ ...(options.metricsHook ? { metricsHook: options.metricsHook } : {}),
68
+ ...(options.logger ? { logger: options.logger } : {}),
60
69
  currentDepth: 0,
61
70
  effectiveMaxDepth,
62
71
  effectiveMaxConcurrentChildren,
72
+ effectiveMaxConcurrentAgentTurns,
63
73
  onChildFailure,
64
74
  ...(parentDeadlineMs !== undefined ? { parentDeadlineMs } : {}),
65
75
  ...(options.defaultSubRunTimeoutMs !== undefined
@@ -74,9 +84,10 @@ export function createEngine(options) {
74
84
  const effectiveMaxDepth = Math.min(engineMaxDepth, runOptions?.maxDepth ?? Number.POSITIVE_INFINITY);
75
85
  assertRunDoesNotRaiseEngineMax("maxConcurrentChildren", runOptions?.maxConcurrentChildren, engineMaxConcurrentChildren);
76
86
  const effectiveMaxConcurrentChildren = Math.min(engineMaxConcurrentChildren, runOptions?.maxConcurrentChildren ?? Number.POSITIVE_INFINITY);
87
+ assertRunDoesNotRaiseEngineMax("maxConcurrentAgentTurns", runOptions?.maxConcurrentAgentTurns, engineMaxConcurrentAgentTurns);
88
+ const effectiveMaxConcurrentAgentTurns = Math.min(engineMaxConcurrentAgentTurns, runOptions?.maxConcurrentAgentTurns ?? Number.POSITIVE_INFINITY);
77
89
  const onChildFailure = resolveOnChildFailure(runOptions?.onChildFailure, engineOnChildFailure);
78
- const pendingEvents = [];
79
- const pendingResolvers = [];
90
+ const pendingIteratorResolvers = [];
80
91
  const emittedEvents = [];
81
92
  const subscribers = new Set();
82
93
  const abortController = new AbortController();
@@ -111,10 +122,17 @@ export function createEngine(options) {
111
122
  cancelRun();
112
123
  },
113
124
  subscribe(subscriber) {
114
- subscribers.add(subscriber);
115
125
  for (const event of emittedEvents) {
116
126
  subscriber(event);
117
127
  }
128
+ if (complete) {
129
+ return {
130
+ unsubscribe() {
131
+ // Completed streams replay synchronously and have no live source.
132
+ }
133
+ };
134
+ }
135
+ subscribers.add(subscriber);
118
136
  return {
119
137
  unsubscribe() {
120
138
  subscribers.delete(subscriber);
@@ -122,20 +140,27 @@ export function createEngine(options) {
122
140
  };
123
141
  },
124
142
  [Symbol.asyncIterator]() {
143
+ let cursor = 0;
125
144
  return {
126
145
  next() {
127
- const event = pendingEvents.shift();
128
- if (event) {
129
- return Promise.resolve({ done: false, value: event });
130
- }
131
- if (complete) {
132
- return Promise.resolve({ done: true, value: undefined });
133
- }
134
- return new Promise((resolve) => {
135
- pendingResolvers.push(resolve);
136
- });
146
+ return readNext();
137
147
  }
138
148
  };
149
+ function readNext() {
150
+ const event = emittedEvents[cursor];
151
+ if (event !== undefined) {
152
+ cursor += 1;
153
+ return Promise.resolve({ done: false, value: event });
154
+ }
155
+ if (complete) {
156
+ return Promise.resolve({ done: true, value: undefined });
157
+ }
158
+ return new Promise((resolve) => {
159
+ pendingIteratorResolvers.push(() => {
160
+ void readNext().then(resolve);
161
+ });
162
+ });
163
+ }
139
164
  }
140
165
  };
141
166
  async function execute() {
@@ -160,11 +185,15 @@ export function createEngine(options) {
160
185
  currentDepth: 0,
161
186
  effectiveMaxDepth,
162
187
  effectiveMaxConcurrentChildren,
188
+ effectiveMaxConcurrentAgentTurns,
163
189
  onChildFailure,
164
190
  ...(streamParentDeadlineMs !== undefined ? { parentDeadlineMs: streamParentDeadlineMs } : {}),
165
191
  ...(options.defaultSubRunTimeoutMs !== undefined
166
192
  ? { defaultSubRunTimeoutMs: options.defaultSubRunTimeoutMs }
167
193
  : {}),
194
+ ...(options.tracer ? { tracer: options.tracer } : {}),
195
+ ...(options.metricsHook ? { metricsHook: options.metricsHook } : {}),
196
+ ...(options.logger ? { logger: options.logger } : {}),
168
197
  streamEvents: true,
169
198
  emit(event) {
170
199
  if (status !== "running") {
@@ -246,8 +275,8 @@ export function createEngine(options) {
246
275
  timeoutLifecycle.cleanup();
247
276
  abortRace.cleanup();
248
277
  subscribers.clear();
249
- for (const resolver of pendingResolvers.splice(0)) {
250
- resolver({ done: true, value: undefined });
278
+ for (const resolvePending of pendingIteratorResolvers.splice(0)) {
279
+ resolvePending();
251
280
  }
252
281
  }
253
282
  function publish(event) {
@@ -264,12 +293,9 @@ export function createEngine(options) {
264
293
  // Subscriber failures should not cancel the underlying SDK run.
265
294
  }
266
295
  }
267
- const resolver = pendingResolvers.shift();
268
- if (resolver) {
269
- resolver({ done: false, value: canonicalEvent });
270
- return;
296
+ for (const resolvePending of pendingIteratorResolvers.splice(0)) {
297
+ resolvePending();
271
298
  }
272
- pendingEvents.push(canonicalEvent);
273
299
  }
274
300
  }
275
301
  };
@@ -485,6 +511,387 @@ function dogpileErrorStreamDetail(error) {
485
511
  }
486
512
  return detail;
487
513
  }
514
+ function openRunTracing(options) {
515
+ if (!options.tracer) {
516
+ return undefined;
517
+ }
518
+ const runSpan = options.tracer.startSpan(DOGPILE_SPAN_NAMES.RUN, {
519
+ ...(options.parentSpan ? { parent: options.parentSpan } : {}),
520
+ attributes: {
521
+ "dogpile.run.protocol": options.protocolKind,
522
+ "dogpile.run.tier": String(options.tier),
523
+ "dogpile.run.intent": options.intent.slice(0, 200)
524
+ }
525
+ });
526
+ return {
527
+ tracer: options.tracer,
528
+ runSpan,
529
+ subRunSpans: new Map(),
530
+ agentTurnSpans: new Map(),
531
+ modelCallSpans: new Map(),
532
+ pendingModelRequests: new Map(),
533
+ agentTurnCounters: new Map(),
534
+ turnAccumByAgent: new Map(),
535
+ agentIds: new Set(),
536
+ turnCount: 0,
537
+ lastCost: emptyCost()
538
+ };
539
+ }
540
+ function openRunMetrics(options) {
541
+ if (!options.metricsHook) {
542
+ return undefined;
543
+ }
544
+ return {
545
+ metricsHook: options.metricsHook,
546
+ logger: options.logger,
547
+ startedAtMs: Date.now(),
548
+ subRunStartTimes: new Map(),
549
+ totalCost: emptyCost(),
550
+ nestedCost: emptyCost(),
551
+ turns: 0
552
+ };
553
+ }
554
+ function routeMetricsError(err, logger) {
555
+ const msg = err instanceof Error ? err.message : String(err);
556
+ try {
557
+ if (logger !== undefined) {
558
+ logger.error("dogpile:metricsHook threw", { error: msg });
559
+ }
560
+ else {
561
+ console.error("dogpile:metricsHook threw", { error: msg });
562
+ }
563
+ }
564
+ catch {
565
+ // A logger that throws from error() cannot be helped.
566
+ }
567
+ }
568
+ function fireHook(callback, snapshot, logger) {
569
+ if (!callback) {
570
+ return;
571
+ }
572
+ try {
573
+ const result = callback(snapshot);
574
+ if (result && typeof result.catch === "function") {
575
+ result.catch((err) => {
576
+ routeMetricsError(err, logger);
577
+ });
578
+ }
579
+ }
580
+ catch (err) {
581
+ routeMetricsError(err, logger);
582
+ }
583
+ }
584
+ function buildRunSnapshot(result, startedAtMs) {
585
+ const nestedCosts = nestedSubRunCosts(result);
586
+ const budgetStopEvent = result.trace.events.find((event) => event.type === "budget-stop");
587
+ const outcome = budgetStopEvent !== undefined ? "budget-stopped" : "completed";
588
+ const totalInputTokens = result.cost.inputTokens;
589
+ const totalOutputTokens = result.cost.outputTokens;
590
+ const totalCostUsd = result.cost.usd;
591
+ const ownInputTokens = totalInputTokens - nestedCosts.reduce((sum, cost) => sum + cost.inputTokens, 0);
592
+ const ownOutputTokens = totalOutputTokens - nestedCosts.reduce((sum, cost) => sum + cost.outputTokens, 0);
593
+ const ownCostUsd = totalCostUsd - nestedCosts.reduce((sum, cost) => sum + cost.usd, 0);
594
+ const turns = result.trace.events.filter((event) => event.type === "agent-turn").length;
595
+ return {
596
+ outcome,
597
+ inputTokens: ownInputTokens,
598
+ outputTokens: ownOutputTokens,
599
+ costUsd: ownCostUsd,
600
+ totalInputTokens,
601
+ totalOutputTokens,
602
+ totalCostUsd,
603
+ turns,
604
+ durationMs: Date.now() - startedAtMs
605
+ };
606
+ }
607
+ function buildSubRunSnapshot(subResult, durationMs) {
608
+ const nestedCosts = nestedSubRunCosts(subResult);
609
+ const budgetStopEvent = subResult.trace.events.find((event) => event.type === "budget-stop");
610
+ const outcome = budgetStopEvent !== undefined ? "budget-stopped" : "completed";
611
+ const totalInputTokens = subResult.cost.inputTokens;
612
+ const totalOutputTokens = subResult.cost.outputTokens;
613
+ const totalCostUsd = subResult.cost.usd;
614
+ const ownInputTokens = totalInputTokens - nestedCosts.reduce((sum, cost) => sum + cost.inputTokens, 0);
615
+ const ownOutputTokens = totalOutputTokens - nestedCosts.reduce((sum, cost) => sum + cost.outputTokens, 0);
616
+ const ownCostUsd = totalCostUsd - nestedCosts.reduce((sum, cost) => sum + cost.usd, 0);
617
+ const turns = subResult.trace.events.filter((event) => event.type === "agent-turn").length;
618
+ return {
619
+ outcome,
620
+ inputTokens: ownInputTokens,
621
+ outputTokens: ownOutputTokens,
622
+ costUsd: ownCostUsd,
623
+ totalInputTokens,
624
+ totalOutputTokens,
625
+ totalCostUsd,
626
+ turns,
627
+ durationMs
628
+ };
629
+ }
630
+ function nestedSubRunCosts(result) {
631
+ return result.trace.events.flatMap((event) => {
632
+ if (event.type === "sub-run-completed") {
633
+ return [event.subResult.cost];
634
+ }
635
+ if (event.type === "sub-run-failed") {
636
+ return [event.partialCost];
637
+ }
638
+ return [];
639
+ });
640
+ }
641
+ function subtractCost(total, nested) {
642
+ return {
643
+ usd: total.usd - nested.usd,
644
+ inputTokens: total.inputTokens - nested.inputTokens,
645
+ outputTokens: total.outputTokens - nested.outputTokens,
646
+ totalTokens: total.totalTokens - nested.totalTokens
647
+ };
648
+ }
649
+ function handleMetricsEvent(state, event) {
650
+ const parentRunIds = event.parentRunIds;
651
+ if (parentRunIds !== undefined) {
652
+ return;
653
+ }
654
+ switch (event.type) {
655
+ case "agent-turn": {
656
+ state.totalCost = event.cost;
657
+ state.turns += 1;
658
+ break;
659
+ }
660
+ case "broadcast":
661
+ case "budget-stop":
662
+ case "final": {
663
+ state.totalCost = event.cost;
664
+ break;
665
+ }
666
+ case "sub-run-started": {
667
+ state.subRunStartTimes.set(event.childRunId, Date.now());
668
+ break;
669
+ }
670
+ case "sub-run-completed": {
671
+ state.totalCost = addCost(state.totalCost, event.subResult.cost);
672
+ state.nestedCost = addCost(state.nestedCost, event.subResult.cost);
673
+ const startMs = state.subRunStartTimes.get(event.childRunId);
674
+ const durationMs = startMs !== undefined ? Date.now() - startMs : 0;
675
+ state.subRunStartTimes.delete(event.childRunId);
676
+ const snapshot = buildSubRunSnapshot(event.subResult, durationMs);
677
+ fireHook(state.metricsHook.onSubRunComplete, snapshot, state.logger);
678
+ break;
679
+ }
680
+ case "sub-run-failed": {
681
+ state.totalCost = addCost(state.totalCost, event.partialCost);
682
+ state.nestedCost = addCost(state.nestedCost, event.partialCost);
683
+ state.subRunStartTimes.delete(event.childRunId);
684
+ break;
685
+ }
686
+ default:
687
+ break;
688
+ }
689
+ }
690
+ function closeRunMetrics(state, result) {
691
+ if (result !== undefined) {
692
+ const snapshot = buildRunSnapshot(result, state.startedAtMs);
693
+ fireHook(state.metricsHook.onRunComplete, snapshot, state.logger);
694
+ return;
695
+ }
696
+ const ownCost = subtractCost(state.totalCost, state.nestedCost);
697
+ const snapshot = {
698
+ outcome: "aborted",
699
+ inputTokens: ownCost.inputTokens,
700
+ outputTokens: ownCost.outputTokens,
701
+ costUsd: ownCost.usd,
702
+ totalInputTokens: state.totalCost.inputTokens,
703
+ totalOutputTokens: state.totalCost.outputTokens,
704
+ totalCostUsd: state.totalCost.usd,
705
+ turns: state.turns,
706
+ durationMs: Date.now() - state.startedAtMs
707
+ };
708
+ fireHook(state.metricsHook.onRunComplete, snapshot, state.logger);
709
+ }
710
+ function handleTracingEvent(state, event) {
711
+ const parentRunIds = event.parentRunIds;
712
+ if (parentRunIds !== undefined) {
713
+ return;
714
+ }
715
+ if (state.runId === undefined) {
716
+ state.runId = event.runId;
717
+ state.runSpan.setAttribute("dogpile.run.id", event.runId);
718
+ }
719
+ switch (event.type) {
720
+ case "model-request": {
721
+ state.pendingModelRequests.set(event.callId, event);
722
+ state.agentIds.add(event.agentId);
723
+ if (!state.agentTurnSpans.has(event.agentId)) {
724
+ const turnNumber = (state.agentTurnCounters.get(event.agentId) ?? 0) + 1;
725
+ state.agentTurnCounters.set(event.agentId, turnNumber);
726
+ const turnParent = state.subRunSpans.get(event.runId) ?? state.runSpan;
727
+ const turnSpan = state.tracer.startSpan(DOGPILE_SPAN_NAMES.AGENT_TURN, {
728
+ parent: turnParent,
729
+ attributes: {
730
+ "dogpile.agent.id": event.agentId,
731
+ "dogpile.agent.role": event.role,
732
+ "dogpile.turn.number": turnNumber,
733
+ "dogpile.model.id": event.modelId
734
+ }
735
+ });
736
+ state.agentTurnSpans.set(event.agentId, turnSpan);
737
+ }
738
+ const callParent = state.agentTurnSpans.get(event.agentId) ??
739
+ state.subRunSpans.get(event.runId) ??
740
+ state.runSpan;
741
+ const callSpan = state.tracer.startSpan(DOGPILE_SPAN_NAMES.MODEL_CALL, {
742
+ parent: callParent,
743
+ attributes: {
744
+ "dogpile.model.id": event.modelId,
745
+ "dogpile.call.id": event.callId,
746
+ "dogpile.provider.id": event.providerId
747
+ }
748
+ });
749
+ state.modelCallSpans.set(event.callId, callSpan);
750
+ break;
751
+ }
752
+ case "model-response": {
753
+ const span = state.modelCallSpans.get(event.callId);
754
+ if (span) {
755
+ const inputTokens = event.response.usage?.inputTokens ?? 0;
756
+ const outputTokens = event.response.usage?.outputTokens ?? 0;
757
+ const responseCost = {
758
+ usd: event.response.costUsd ?? 0,
759
+ inputTokens,
760
+ outputTokens,
761
+ totalTokens: event.response.usage?.totalTokens ?? inputTokens + outputTokens
762
+ };
763
+ span.setAttribute("dogpile.model.input_tokens", inputTokens);
764
+ span.setAttribute("dogpile.model.output_tokens", outputTokens);
765
+ if (event.response.costUsd !== undefined) {
766
+ span.setAttribute("dogpile.model.cost_usd", event.response.costUsd);
767
+ }
768
+ span.setStatus("ok");
769
+ span.end();
770
+ state.modelCallSpans.delete(event.callId);
771
+ const accum = state.turnAccumByAgent.get(event.agentId) ?? {
772
+ inputTokens: 0,
773
+ outputTokens: 0,
774
+ costUsd: 0
775
+ };
776
+ accum.inputTokens += inputTokens;
777
+ accum.outputTokens += outputTokens;
778
+ accum.costUsd += responseCost.usd;
779
+ state.turnAccumByAgent.set(event.agentId, accum);
780
+ state.lastCost = addCost(state.lastCost, responseCost);
781
+ }
782
+ state.pendingModelRequests.delete(event.callId);
783
+ break;
784
+ }
785
+ case "agent-turn": {
786
+ state.agentIds.add(event.agentId);
787
+ state.turnCount += 1;
788
+ state.lastCost = event.cost;
789
+ const turnSpan = state.agentTurnSpans.get(event.agentId);
790
+ if (turnSpan) {
791
+ turnSpan.setAttribute("dogpile.agent.role", event.role);
792
+ const accum = state.turnAccumByAgent.get(event.agentId);
793
+ turnSpan.setAttribute("dogpile.turn.cost_usd", accum?.costUsd ?? 0);
794
+ turnSpan.setAttribute("dogpile.turn.input_tokens", accum?.inputTokens ?? 0);
795
+ turnSpan.setAttribute("dogpile.turn.output_tokens", accum?.outputTokens ?? 0);
796
+ turnSpan.setStatus("ok");
797
+ turnSpan.end();
798
+ state.agentTurnSpans.delete(event.agentId);
799
+ }
800
+ state.turnAccumByAgent.delete(event.agentId);
801
+ break;
802
+ }
803
+ case "broadcast":
804
+ case "budget-stop":
805
+ case "final": {
806
+ state.lastCost = event.cost;
807
+ break;
808
+ }
809
+ case "sub-run-started": {
810
+ const span = state.tracer.startSpan(DOGPILE_SPAN_NAMES.SUB_RUN, {
811
+ parent: state.runSpan,
812
+ attributes: {
813
+ "dogpile.sub_run.child_run_id": event.childRunId,
814
+ "dogpile.sub_run.parent_run_id": event.parentRunId,
815
+ "dogpile.sub_run.depth": event.depth
816
+ }
817
+ });
818
+ state.subRunSpans.set(event.childRunId, span);
819
+ break;
820
+ }
821
+ case "sub-run-completed": {
822
+ const span = state.subRunSpans.get(event.childRunId);
823
+ if (span) {
824
+ span.setStatus("ok");
825
+ span.end();
826
+ state.subRunSpans.delete(event.childRunId);
827
+ }
828
+ break;
829
+ }
830
+ case "sub-run-failed": {
831
+ const span = state.subRunSpans.get(event.childRunId);
832
+ if (span) {
833
+ span.setStatus("error", event.error.message);
834
+ span.end();
835
+ state.subRunSpans.delete(event.childRunId);
836
+ }
837
+ break;
838
+ }
839
+ default:
840
+ break;
841
+ }
842
+ }
843
+ function closeRunTracing(state, result, error) {
844
+ if (error !== undefined) {
845
+ if (state.runId !== undefined) {
846
+ state.runSpan.setAttribute("dogpile.run.id", state.runId);
847
+ }
848
+ state.runSpan.setAttribute("dogpile.run.agent_count", state.agentIds.size);
849
+ state.runSpan.setAttribute("dogpile.run.turn_count", state.turnCount);
850
+ state.runSpan.setAttribute("dogpile.run.cost_usd", state.lastCost.usd);
851
+ state.runSpan.setAttribute("dogpile.run.input_tokens", state.lastCost.inputTokens);
852
+ state.runSpan.setAttribute("dogpile.run.output_tokens", state.lastCost.outputTokens);
853
+ state.runSpan.setAttribute("dogpile.run.outcome", "aborted");
854
+ state.runSpan.setStatus("error", error instanceof Error ? error.message : String(error));
855
+ closeOpenTracingSpans(state);
856
+ state.runSpan.end();
857
+ return;
858
+ }
859
+ if (result === undefined) {
860
+ closeOpenTracingSpans(state);
861
+ state.runSpan.end();
862
+ return;
863
+ }
864
+ const budgetStopEvent = result.trace.events.find((event) => event.type === "budget-stop");
865
+ const terminationReason = budgetStopEvent?.reason;
866
+ const outcome = terminationReason !== undefined ? "budget-stopped" : "completed";
867
+ state.runSpan.setAttribute("dogpile.run.id", result.trace.runId);
868
+ state.runSpan.setAttribute("dogpile.run.agent_count", result.trace.agentsUsed.length);
869
+ state.runSpan.setAttribute("dogpile.run.turn_count", result.trace.events.filter((event) => event.type === "agent-turn").length);
870
+ state.runSpan.setAttribute("dogpile.run.cost_usd", result.cost.usd);
871
+ state.runSpan.setAttribute("dogpile.run.input_tokens", result.cost.inputTokens);
872
+ state.runSpan.setAttribute("dogpile.run.output_tokens", result.cost.outputTokens);
873
+ state.runSpan.setAttribute("dogpile.run.outcome", outcome);
874
+ if (terminationReason !== undefined) {
875
+ state.runSpan.setAttribute("dogpile.run.termination_reason", terminationReason);
876
+ }
877
+ state.runSpan.setStatus("ok");
878
+ closeOpenTracingSpans(state);
879
+ state.runSpan.end();
880
+ }
881
+ function closeOpenTracingSpans(state) {
882
+ for (const span of state.modelCallSpans.values()) {
883
+ span.end();
884
+ }
885
+ state.modelCallSpans.clear();
886
+ for (const span of state.agentTurnSpans.values()) {
887
+ span.end();
888
+ }
889
+ state.agentTurnSpans.clear();
890
+ for (const span of state.subRunSpans.values()) {
891
+ span.end();
892
+ }
893
+ state.subRunSpans.clear();
894
+ }
488
895
  async function runNonStreamingProtocol(options) {
489
896
  const failureInstancesByChildRunId = new Map();
490
897
  const abortLifecycle = createNonStreamingAbortLifecycle({
@@ -524,7 +931,8 @@ async function runNonStreamingProtocol(options) {
524
931
  events
525
932
  }),
526
933
  eventLog: createRunEventLog(trace.runId, trace.protocol, events),
527
- trace
934
+ trace,
935
+ health: computeHealth(trace, DEFAULT_HEALTH_THRESHOLDS)
528
936
  };
529
937
  const terminalThrow = resolveRuntimeTerminalThrow(runResult.trace, failureInstancesByChildRunId);
530
938
  if (terminalThrow) {
@@ -570,7 +978,56 @@ function finalEventWithEvaluation(event, evaluation) {
570
978
  evaluation
571
979
  };
572
980
  }
573
- function runProtocol(options) {
981
+ async function runProtocol(options) {
982
+ const tracing = openRunTracing({
983
+ ...(options.tracer ? { tracer: options.tracer } : {}),
984
+ ...(options.parentSpan ? { parentSpan: options.parentSpan } : {}),
985
+ intent: options.intent,
986
+ protocolKind: options.protocol.kind,
987
+ tier: options.tier
988
+ });
989
+ const metrics = openRunMetrics({
990
+ ...(options.metricsHook ? { metricsHook: options.metricsHook } : {}),
991
+ ...(options.logger ? { logger: options.logger } : {})
992
+ });
993
+ const emitForProtocol = tracing || metrics || options.emit
994
+ ? (event) => {
995
+ if (tracing) {
996
+ handleTracingEvent(tracing, event);
997
+ }
998
+ if (metrics) {
999
+ handleMetricsEvent(metrics, event);
1000
+ }
1001
+ options.emit?.(event);
1002
+ }
1003
+ : undefined;
1004
+ const protocolOptions = tracing
1005
+ ? {
1006
+ ...options,
1007
+ subRunSpansByChildId: tracing.subRunSpans
1008
+ }
1009
+ : options;
1010
+ try {
1011
+ const result = await runProtocolInner(protocolOptions, emitForProtocol);
1012
+ if (tracing) {
1013
+ closeRunTracing(tracing, result);
1014
+ }
1015
+ if (metrics && (options.currentDepth === 0 || options.currentDepth === undefined)) {
1016
+ closeRunMetrics(metrics, result);
1017
+ }
1018
+ return result;
1019
+ }
1020
+ catch (error) {
1021
+ if (tracing) {
1022
+ closeRunTracing(tracing, undefined, error);
1023
+ }
1024
+ if (metrics && (options.currentDepth === 0 || options.currentDepth === undefined)) {
1025
+ closeRunMetrics(metrics, undefined);
1026
+ }
1027
+ throw error;
1028
+ }
1029
+ }
1030
+ function runProtocolInner(options, emitForProtocol) {
574
1031
  switch (options.protocol.kind) {
575
1032
  case "sequential":
576
1033
  return runSequential({
@@ -586,7 +1043,7 @@ function runProtocol(options) {
586
1043
  ...(options.signal !== undefined ? { signal: options.signal } : {}),
587
1044
  ...(options.terminate ? { terminate: options.terminate } : {}),
588
1045
  ...(options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {}),
589
- ...(options.emit ? { emit: options.emit } : {})
1046
+ ...(emitForProtocol ? { emit: emitForProtocol } : {})
590
1047
  });
591
1048
  case "broadcast":
592
1049
  return runBroadcast({
@@ -602,7 +1059,10 @@ function runProtocol(options) {
602
1059
  ...(options.signal !== undefined ? { signal: options.signal } : {}),
603
1060
  ...(options.terminate ? { terminate: options.terminate } : {}),
604
1061
  ...(options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {}),
605
- ...(options.emit ? { emit: options.emit } : {})
1062
+ ...(options.effectiveMaxConcurrentAgentTurns !== undefined
1063
+ ? { maxConcurrentAgentTurns: options.effectiveMaxConcurrentAgentTurns }
1064
+ : {}),
1065
+ ...(emitForProtocol ? { emit: emitForProtocol } : {})
606
1066
  });
607
1067
  case "coordinator":
608
1068
  return runCoordinator({
@@ -618,11 +1078,15 @@ function runProtocol(options) {
618
1078
  ...(options.signal !== undefined ? { signal: options.signal } : {}),
619
1079
  ...(options.terminate ? { terminate: options.terminate } : {}),
620
1080
  ...(options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {}),
621
- ...(options.emit ? { emit: options.emit } : {}),
1081
+ ...(options.effectiveMaxConcurrentAgentTurns !== undefined
1082
+ ? { maxConcurrentAgentTurns: options.effectiveMaxConcurrentAgentTurns }
1083
+ : {}),
1084
+ ...(emitForProtocol ? { emit: emitForProtocol } : {}),
622
1085
  ...(options.streamEvents !== undefined ? { streamEvents: options.streamEvents } : {}),
623
1086
  currentDepth: options.currentDepth ?? 0,
624
1087
  effectiveMaxDepth: options.effectiveMaxDepth ?? Infinity,
625
1088
  effectiveMaxConcurrentChildren: options.effectiveMaxConcurrentChildren ?? DEFAULT_MAX_CONCURRENT_CHILDREN,
1089
+ effectiveMaxConcurrentAgentTurns: options.effectiveMaxConcurrentAgentTurns ?? DEFAULT_MAX_CONCURRENT_AGENT_TURNS,
626
1090
  onChildFailure: options.onChildFailure ?? "continue",
627
1091
  ...(options.parentDeadlineMs !== undefined ? { parentDeadlineMs: options.parentDeadlineMs } : {}),
628
1092
  ...(options.defaultSubRunTimeoutMs !== undefined
@@ -632,10 +1096,17 @@ function runProtocol(options) {
632
1096
  ...(options.failureInstancesByChildRunId !== undefined
633
1097
  ? { failureInstancesByChildRunId: options.failureInstancesByChildRunId }
634
1098
  : {}),
635
- runProtocol: (childInput) => runProtocol({
636
- ...childInput,
637
- protocol: normalizeProtocol(childInput.protocol)
638
- })
1099
+ runProtocol: (childInput) => {
1100
+ const { runId: childRunId, ...childProtocolInput } = childInput;
1101
+ const childParent = options.subRunSpansByChildId?.get(childRunId) ?? options.parentSpan;
1102
+ return runProtocol({
1103
+ ...childProtocolInput,
1104
+ protocol: normalizeProtocol(childProtocolInput.protocol),
1105
+ ...(options.tracer ? { tracer: options.tracer } : {}),
1106
+ ...(childParent ? { parentSpan: childParent } : {}),
1107
+ ...(options.logger ? { logger: options.logger } : {})
1108
+ });
1109
+ }
639
1110
  });
640
1111
  case "shared":
641
1112
  return runShared({
@@ -651,7 +1122,10 @@ function runProtocol(options) {
651
1122
  ...(options.signal !== undefined ? { signal: options.signal } : {}),
652
1123
  ...(options.terminate ? { terminate: options.terminate } : {}),
653
1124
  ...(options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {}),
654
- ...(options.emit ? { emit: options.emit } : {})
1125
+ ...(options.effectiveMaxConcurrentAgentTurns !== undefined
1126
+ ? { maxConcurrentAgentTurns: options.effectiveMaxConcurrentAgentTurns }
1127
+ : {}),
1128
+ ...(emitForProtocol ? { emit: emitForProtocol } : {})
655
1129
  });
656
1130
  }
657
1131
  }
@@ -699,7 +1173,14 @@ export function stream(options) {
699
1173
  * the ergonomic {@link RunResult} wrapper from the JSON-serializable
700
1174
  * {@link Trace} returned by a previous `run()`, `stream()`, or
701
1175
  * `Dogpile.pile()` call.
1176
+ *
1177
+ * Tracing and metrics: replay is intentionally tracing-free and metrics-free.
1178
+ * Even when an engine instance has been configured with a `tracer` or
1179
+ * `metricsHook` on its `EngineOptions`, calling this function emits no spans
1180
+ * or callbacks — replaying historical events with current timestamps would
1181
+ * confuse observability backends. See `docs/developer-usage.md`.
702
1182
  */
1183
+ // Tracing/metrics-free: replay never uses EngineOptions tracer or metricsHook.
703
1184
  export function replay(trace) {
704
1185
  const cost = trace.finalOutput.cost;
705
1186
  const lastEvent = trace.events.at(-1);
@@ -714,7 +1195,7 @@ export function replay(trace) {
714
1195
  }
715
1196
  const baseResult = {
716
1197
  output: trace.finalOutput.output,
717
- eventLog: createRunEventLog(trace.runId, trace.protocol, trace.events),
1198
+ eventLog: createRunEventLog(trace.runId, trace.protocol, synthesizeProviderEvents(trace, trace.providerCalls)),
718
1199
  trace,
719
1200
  transcript: trace.transcript,
720
1201
  usage: createRunUsage(cost),
@@ -727,7 +1208,8 @@ export function replay(trace) {
727
1208
  events: trace.events
728
1209
  }),
729
1210
  accounting,
730
- cost
1211
+ cost,
1212
+ health: computeHealth(trace, DEFAULT_HEALTH_THRESHOLDS)
731
1213
  };
732
1214
  if (lastEvent?.type !== "final") {
733
1215
  return baseResult;
@@ -738,6 +1220,49 @@ export function replay(trace) {
738
1220
  ...(lastEvent.evaluation !== undefined ? { evaluation: lastEvent.evaluation } : {})
739
1221
  };
740
1222
  }
1223
+ function synthesizeProviderEvents(trace, providerCalls) {
1224
+ const hasLiveProvenance = trace.events.some((event) => event.type === "model-request" || event.type === "model-response");
1225
+ if (hasLiveProvenance) {
1226
+ return trace.events;
1227
+ }
1228
+ const baseEvents = trace.events.filter((event) => event.type !== "model-request" && event.type !== "model-response");
1229
+ const result = [];
1230
+ let turnCount = 0;
1231
+ for (const event of baseEvents) {
1232
+ if (event.type === "agent-turn") {
1233
+ const call = providerCalls[turnCount];
1234
+ if (call !== undefined) {
1235
+ const modelId = typeof call.modelId === "string" && call.modelId.length > 0 ? call.modelId : call.providerId;
1236
+ result.push({
1237
+ type: "model-request",
1238
+ runId: trace.runId,
1239
+ callId: call.callId,
1240
+ providerId: call.providerId,
1241
+ modelId,
1242
+ startedAt: call.startedAt,
1243
+ agentId: call.agentId,
1244
+ role: call.role,
1245
+ request: call.request
1246
+ });
1247
+ result.push({
1248
+ type: "model-response",
1249
+ runId: trace.runId,
1250
+ callId: call.callId,
1251
+ providerId: call.providerId,
1252
+ modelId,
1253
+ startedAt: call.startedAt,
1254
+ completedAt: call.completedAt,
1255
+ agentId: call.agentId,
1256
+ role: call.role,
1257
+ response: call.response
1258
+ });
1259
+ }
1260
+ turnCount += 1;
1261
+ }
1262
+ result.push(event);
1263
+ }
1264
+ return result;
1265
+ }
741
1266
  function resolveRuntimeTerminalThrow(trace, failureInstancesByChildRunId) {
742
1267
  if (trace.triggeringFailureForAbortMode !== undefined) {
743
1268
  return failureInstancesByChildRunId.get(trace.triggeringFailureForAbortMode.childRunId) ?? null;
@@ -816,14 +1341,21 @@ function dogpileErrorFromSerializedPayload(input) {
816
1341
  * Replay a saved completed trace as a stream without invoking a model provider.
817
1342
  *
818
1343
  * @remarks
819
- * This is the streaming counterpart to {@link replay}. It yields the exact
820
- * saved {@link Trace.events} in order and resolves {@link StreamHandle.result}
821
- * to the rehydrated {@link RunResult}. Since all data comes from the trace,
822
- * replay remains storage-free and provider-free.
1344
+ * This is the streaming counterpart to {@link replay}. It yields the same
1345
+ * event sequence exposed by the replayed result event log, including legacy
1346
+ * provenance synthesis when a saved trace predates model request/response
1347
+ * events. Since all data comes from the trace, replay remains storage-free and
1348
+ * provider-free.
1349
+ *
1350
+ * Tracing and metrics: replayStream is intentionally tracing-free and
1351
+ * metrics-free. Even when an engine instance has been configured with a
1352
+ * `tracer` or `metricsHook` on its `EngineOptions`, calling this function
1353
+ * emits no spans or callbacks — replaying historical events with current
1354
+ * timestamps would confuse observability backends. See `docs/developer-usage.md`.
823
1355
  */
1356
+ // Tracing/metrics-free: replayStream never uses EngineOptions tracer or metricsHook.
824
1357
  export function replayStream(trace) {
825
1358
  const result = Promise.resolve(replay(trace));
826
- const replayEvents = replayStreamEvents(trace);
827
1359
  return {
828
1360
  get status() {
829
1361
  return "completed";
@@ -833,7 +1365,7 @@ export function replayStream(trace) {
833
1365
  // Replay streams are already completed snapshots, so cancellation is a no-op.
834
1366
  },
835
1367
  subscribe(subscriber) {
836
- for (const event of replayEvents) {
1368
+ for (const event of replayStreamEvents(trace)) {
837
1369
  subscriber(event);
838
1370
  }
839
1371
  return {
@@ -843,29 +1375,22 @@ export function replayStream(trace) {
843
1375
  };
844
1376
  },
845
1377
  [Symbol.asyncIterator]() {
846
- let index = 0;
1378
+ const iterator = replayStreamEvents(trace)[Symbol.iterator]();
847
1379
  return {
848
1380
  next() {
849
- const event = replayEvents[index];
850
- if (event) {
851
- index += 1;
852
- return Promise.resolve({ done: false, value: event });
853
- }
854
- return Promise.resolve({ done: true, value: undefined });
1381
+ return Promise.resolve(iterator.next());
855
1382
  }
856
1383
  };
857
1384
  }
858
1385
  };
859
1386
  }
860
- function replayStreamEvents(trace, parentRunIds = []) {
861
- const events = [];
862
- for (const event of trace.events) {
1387
+ function* replayStreamEvents(trace, parentRunIds = []) {
1388
+ for (const event of synthesizeProviderEvents(trace, trace.providerCalls)) {
863
1389
  if (event.type === "sub-run-completed") {
864
- events.push(...replayStreamEvents(event.subResult.trace, [...parentRunIds, trace.runId]));
1390
+ yield* replayStreamEvents(event.subResult.trace, [...parentRunIds, trace.runId]);
865
1391
  }
866
- events.push(wrapReplayStreamEvent(event, parentRunIds));
1392
+ yield wrapReplayStreamEvent(event, parentRunIds);
867
1393
  }
868
- return events;
869
1394
  }
870
1395
  function wrapReplayStreamEvent(event, parentRunIds) {
871
1396
  if (parentRunIds.length === 0) {