@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.
- package/CHANGELOG.md +92 -0
- package/dist/browser/index.js +4156 -4611
- package/dist/browser/index.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/providers/openai-compatible.d.ts.map +1 -1
- package/dist/providers/openai-compatible.js +6 -1
- package/dist/providers/openai-compatible.js.map +1 -1
- package/dist/runtime/audit.d.ts +42 -0
- package/dist/runtime/audit.d.ts.map +1 -0
- package/dist/runtime/audit.js +73 -0
- package/dist/runtime/audit.js.map +1 -0
- package/dist/runtime/broadcast.d.ts +1 -0
- package/dist/runtime/broadcast.d.ts.map +1 -1
- package/dist/runtime/broadcast.js +171 -105
- package/dist/runtime/broadcast.js.map +1 -1
- package/dist/runtime/coordinator.d.ts +9 -2
- package/dist/runtime/coordinator.d.ts.map +1 -1
- package/dist/runtime/coordinator.js +164 -78
- package/dist/runtime/coordinator.js.map +1 -1
- package/dist/runtime/defaults.d.ts.map +1 -1
- package/dist/runtime/defaults.js +14 -5
- package/dist/runtime/defaults.js.map +1 -1
- package/dist/runtime/engine.d.ts +17 -4
- package/dist/runtime/engine.d.ts.map +1 -1
- package/dist/runtime/engine.js +577 -52
- package/dist/runtime/engine.js.map +1 -1
- package/dist/runtime/health.d.ts +51 -0
- package/dist/runtime/health.d.ts.map +1 -0
- package/dist/runtime/health.js +85 -0
- package/dist/runtime/health.js.map +1 -0
- package/dist/runtime/introspection.d.ts +96 -0
- package/dist/runtime/introspection.d.ts.map +1 -0
- package/dist/runtime/introspection.js +31 -0
- package/dist/runtime/introspection.js.map +1 -0
- package/dist/runtime/metrics.d.ts +44 -0
- package/dist/runtime/metrics.d.ts.map +1 -0
- package/dist/runtime/metrics.js +12 -0
- package/dist/runtime/metrics.js.map +1 -0
- package/dist/runtime/model.d.ts.map +1 -1
- package/dist/runtime/model.js +40 -10
- package/dist/runtime/model.js.map +1 -1
- package/dist/runtime/provenance.d.ts +25 -0
- package/dist/runtime/provenance.d.ts.map +1 -0
- package/dist/runtime/provenance.js +13 -0
- package/dist/runtime/provenance.js.map +1 -0
- package/dist/runtime/redaction.d.ts +13 -0
- package/dist/runtime/redaction.d.ts.map +1 -0
- package/dist/runtime/redaction.js +278 -0
- package/dist/runtime/redaction.js.map +1 -0
- package/dist/runtime/sanitization.d.ts +4 -0
- package/dist/runtime/sanitization.d.ts.map +1 -0
- package/dist/runtime/sanitization.js +63 -0
- package/dist/runtime/sanitization.js.map +1 -0
- package/dist/runtime/sequential.d.ts.map +1 -1
- package/dist/runtime/sequential.js +39 -36
- package/dist/runtime/sequential.js.map +1 -1
- package/dist/runtime/shared.d.ts +1 -0
- package/dist/runtime/shared.d.ts.map +1 -1
- package/dist/runtime/shared.js +167 -101
- package/dist/runtime/shared.js.map +1 -1
- package/dist/runtime/tools/built-in.d.ts +2 -0
- package/dist/runtime/tools/built-in.d.ts.map +1 -1
- package/dist/runtime/tools/built-in.js +153 -15
- package/dist/runtime/tools/built-in.js.map +1 -1
- package/dist/runtime/tools.d.ts.map +1 -1
- package/dist/runtime/tools.js +29 -7
- package/dist/runtime/tools.js.map +1 -1
- package/dist/runtime/tracing.d.ts +31 -0
- package/dist/runtime/tracing.d.ts.map +1 -0
- package/dist/runtime/tracing.js +18 -0
- package/dist/runtime/tracing.js.map +1 -0
- package/dist/runtime/validation.d.ts.map +1 -1
- package/dist/runtime/validation.js +3 -0
- package/dist/runtime/validation.js.map +1 -1
- package/dist/types/events.d.ts +13 -7
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/replay.d.ts +5 -1
- package/dist/types/replay.d.ts.map +1 -1
- package/dist/types.d.ts +144 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +46 -1
- package/src/index.ts +5 -0
- package/src/providers/openai-compatible.ts +6 -1
- package/src/runtime/audit.ts +121 -0
- package/src/runtime/broadcast.ts +195 -108
- package/src/runtime/coordinator.ts +197 -86
- package/src/runtime/defaults.ts +15 -5
- package/src/runtime/engine.ts +725 -58
- package/src/runtime/health.ts +136 -0
- package/src/runtime/introspection.ts +122 -0
- package/src/runtime/metrics.ts +45 -0
- package/src/runtime/model.ts +44 -9
- package/src/runtime/provenance.ts +43 -0
- package/src/runtime/redaction.ts +355 -0
- package/src/runtime/sanitization.ts +81 -0
- package/src/runtime/sequential.ts +40 -37
- package/src/runtime/shared.ts +191 -104
- package/src/runtime/tools/built-in.ts +168 -15
- package/src/runtime/tools.ts +39 -8
- package/src/runtime/tracing.ts +35 -0
- package/src/runtime/validation.ts +3 -0
- package/src/types/events.ts +13 -7
- package/src/types/replay.ts +5 -1
- package/src/types.ts +152 -1
package/src/runtime/engine.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { DogpileError } from "../types.js";
|
|
2
2
|
import type {
|
|
3
3
|
AbortedEvent,
|
|
4
|
+
BudgetStopEvent,
|
|
4
5
|
BudgetTier,
|
|
6
|
+
CostSummary,
|
|
5
7
|
DogpileErrorCode,
|
|
6
8
|
DogpileOptions,
|
|
7
9
|
Engine,
|
|
@@ -9,11 +11,13 @@ import type {
|
|
|
9
11
|
FinalEvent,
|
|
10
12
|
JsonObject,
|
|
11
13
|
JsonValue,
|
|
14
|
+
ModelRequestEvent,
|
|
12
15
|
ProtocolSelection,
|
|
13
16
|
RunCallOptions,
|
|
14
17
|
RunEvaluation,
|
|
15
18
|
RunEvent,
|
|
16
19
|
RunResult,
|
|
20
|
+
ReplayTraceProviderCall,
|
|
17
21
|
SubRunFailedEvent,
|
|
18
22
|
StreamErrorEvent,
|
|
19
23
|
StreamEvent,
|
|
@@ -25,6 +29,7 @@ import type {
|
|
|
25
29
|
import { runBroadcast } from "./broadcast.js";
|
|
26
30
|
import { runCoordinator, type AbortDrainFn } from "./coordinator.js";
|
|
27
31
|
import {
|
|
32
|
+
addCost,
|
|
28
33
|
createReplayTraceFinalOutput,
|
|
29
34
|
createReplayTraceBudgetStateChanges,
|
|
30
35
|
canonicalizeRunResult,
|
|
@@ -34,12 +39,14 @@ import {
|
|
|
34
39
|
createRunMetadata,
|
|
35
40
|
createRunUsage,
|
|
36
41
|
defaultAgents,
|
|
42
|
+
emptyCost,
|
|
37
43
|
normalizeProtocol,
|
|
38
44
|
orderAgentsForTemperature,
|
|
39
45
|
recomputeAccountingFromTrace,
|
|
40
46
|
resolveOnChildFailure,
|
|
41
47
|
tierTemperature
|
|
42
48
|
} from "./defaults.js";
|
|
49
|
+
import { computeHealth, DEFAULT_HEALTH_THRESHOLDS } from "./health.js";
|
|
43
50
|
import { runSequential } from "./sequential.js";
|
|
44
51
|
import { runShared } from "./shared.js";
|
|
45
52
|
import {
|
|
@@ -56,9 +63,13 @@ import {
|
|
|
56
63
|
validateProviderLocality,
|
|
57
64
|
validateRunCallOptions
|
|
58
65
|
} from "./validation.js";
|
|
66
|
+
import { DOGPILE_SPAN_NAMES, type DogpileSpan, type DogpileTracer } from "./tracing.js";
|
|
67
|
+
import type { Logger } from "./logger.js";
|
|
68
|
+
import type { MetricsHook, RunMetricsSnapshot } from "./metrics.js";
|
|
59
69
|
|
|
60
70
|
const DEFAULT_MAX_DEPTH = 4;
|
|
61
71
|
const DEFAULT_MAX_CONCURRENT_CHILDREN = 4;
|
|
72
|
+
const DEFAULT_MAX_CONCURRENT_AGENT_TURNS = 4;
|
|
62
73
|
|
|
63
74
|
const defaultHighLevelProtocol = "sequential";
|
|
64
75
|
const defaultHighLevelTier = "balanced";
|
|
@@ -89,6 +100,7 @@ export function createEngine(options: EngineOptions): Engine {
|
|
|
89
100
|
const terminate = options.terminate ?? (options.budget ? conditionFromBudget(options.budget) : undefined);
|
|
90
101
|
const engineMaxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
91
102
|
const engineMaxConcurrentChildren = options.maxConcurrentChildren ?? DEFAULT_MAX_CONCURRENT_CHILDREN;
|
|
103
|
+
const engineMaxConcurrentAgentTurns = options.maxConcurrentAgentTurns ?? DEFAULT_MAX_CONCURRENT_AGENT_TURNS;
|
|
92
104
|
const engineOnChildFailure = options.onChildFailure;
|
|
93
105
|
|
|
94
106
|
return {
|
|
@@ -110,6 +122,15 @@ export function createEngine(options: EngineOptions): Engine {
|
|
|
110
122
|
engineMaxConcurrentChildren,
|
|
111
123
|
runOptions?.maxConcurrentChildren ?? Number.POSITIVE_INFINITY
|
|
112
124
|
);
|
|
125
|
+
assertRunDoesNotRaiseEngineMax(
|
|
126
|
+
"maxConcurrentAgentTurns",
|
|
127
|
+
runOptions?.maxConcurrentAgentTurns,
|
|
128
|
+
engineMaxConcurrentAgentTurns
|
|
129
|
+
);
|
|
130
|
+
const effectiveMaxConcurrentAgentTurns = Math.min(
|
|
131
|
+
engineMaxConcurrentAgentTurns,
|
|
132
|
+
runOptions?.maxConcurrentAgentTurns ?? Number.POSITIVE_INFINITY
|
|
133
|
+
);
|
|
113
134
|
const onChildFailure = resolveOnChildFailure(runOptions?.onChildFailure, engineOnChildFailure);
|
|
114
135
|
|
|
115
136
|
const startedAtMs = Date.now();
|
|
@@ -130,9 +151,13 @@ export function createEngine(options: EngineOptions): Engine {
|
|
|
130
151
|
...(terminate ? { terminate } : {}),
|
|
131
152
|
...(options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {}),
|
|
132
153
|
...(options.evaluate ? { evaluate: options.evaluate } : {}),
|
|
154
|
+
...(options.tracer ? { tracer: options.tracer } : {}),
|
|
155
|
+
...(options.metricsHook ? { metricsHook: options.metricsHook } : {}),
|
|
156
|
+
...(options.logger ? { logger: options.logger } : {}),
|
|
133
157
|
currentDepth: 0,
|
|
134
158
|
effectiveMaxDepth,
|
|
135
159
|
effectiveMaxConcurrentChildren,
|
|
160
|
+
effectiveMaxConcurrentAgentTurns,
|
|
136
161
|
onChildFailure,
|
|
137
162
|
...(parentDeadlineMs !== undefined ? { parentDeadlineMs } : {}),
|
|
138
163
|
...(options.defaultSubRunTimeoutMs !== undefined
|
|
@@ -159,10 +184,18 @@ export function createEngine(options: EngineOptions): Engine {
|
|
|
159
184
|
engineMaxConcurrentChildren,
|
|
160
185
|
runOptions?.maxConcurrentChildren ?? Number.POSITIVE_INFINITY
|
|
161
186
|
);
|
|
187
|
+
assertRunDoesNotRaiseEngineMax(
|
|
188
|
+
"maxConcurrentAgentTurns",
|
|
189
|
+
runOptions?.maxConcurrentAgentTurns,
|
|
190
|
+
engineMaxConcurrentAgentTurns
|
|
191
|
+
);
|
|
192
|
+
const effectiveMaxConcurrentAgentTurns = Math.min(
|
|
193
|
+
engineMaxConcurrentAgentTurns,
|
|
194
|
+
runOptions?.maxConcurrentAgentTurns ?? Number.POSITIVE_INFINITY
|
|
195
|
+
);
|
|
162
196
|
const onChildFailure = resolveOnChildFailure(runOptions?.onChildFailure, engineOnChildFailure);
|
|
163
197
|
|
|
164
|
-
const
|
|
165
|
-
const pendingResolvers: Array<(value: IteratorResult<StreamEvent>) => void> = [];
|
|
198
|
+
const pendingIteratorResolvers: Array<() => void> = [];
|
|
166
199
|
const emittedEvents: StreamEvent[] = [];
|
|
167
200
|
const subscribers = new Set<StreamEventSubscriber>();
|
|
168
201
|
const abortController = new AbortController();
|
|
@@ -199,12 +232,20 @@ export function createEngine(options: EngineOptions): Engine {
|
|
|
199
232
|
cancelRun();
|
|
200
233
|
},
|
|
201
234
|
subscribe(subscriber: StreamEventSubscriber) {
|
|
202
|
-
subscribers.add(subscriber);
|
|
203
|
-
|
|
204
235
|
for (const event of emittedEvents) {
|
|
205
236
|
subscriber(event);
|
|
206
237
|
}
|
|
207
238
|
|
|
239
|
+
if (complete) {
|
|
240
|
+
return {
|
|
241
|
+
unsubscribe(): void {
|
|
242
|
+
// Completed streams replay synchronously and have no live source.
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
subscribers.add(subscriber);
|
|
248
|
+
|
|
208
249
|
return {
|
|
209
250
|
unsubscribe(): void {
|
|
210
251
|
subscribers.delete(subscriber);
|
|
@@ -212,20 +253,29 @@ export function createEngine(options: EngineOptions): Engine {
|
|
|
212
253
|
};
|
|
213
254
|
},
|
|
214
255
|
[Symbol.asyncIterator](): AsyncIterator<StreamEvent> {
|
|
256
|
+
let cursor = 0;
|
|
257
|
+
|
|
215
258
|
return {
|
|
216
259
|
next(): Promise<IteratorResult<StreamEvent>> {
|
|
217
|
-
|
|
218
|
-
if (event) {
|
|
219
|
-
return Promise.resolve({ done: false, value: event });
|
|
220
|
-
}
|
|
221
|
-
if (complete) {
|
|
222
|
-
return Promise.resolve({ done: true, value: undefined });
|
|
223
|
-
}
|
|
224
|
-
return new Promise<IteratorResult<StreamEvent>>((resolve) => {
|
|
225
|
-
pendingResolvers.push(resolve);
|
|
226
|
-
});
|
|
260
|
+
return readNext();
|
|
227
261
|
}
|
|
228
262
|
};
|
|
263
|
+
|
|
264
|
+
function readNext(): Promise<IteratorResult<StreamEvent>> {
|
|
265
|
+
const event = emittedEvents[cursor];
|
|
266
|
+
if (event !== undefined) {
|
|
267
|
+
cursor += 1;
|
|
268
|
+
return Promise.resolve({ done: false, value: event });
|
|
269
|
+
}
|
|
270
|
+
if (complete) {
|
|
271
|
+
return Promise.resolve({ done: true, value: undefined });
|
|
272
|
+
}
|
|
273
|
+
return new Promise<IteratorResult<StreamEvent>>((resolve) => {
|
|
274
|
+
pendingIteratorResolvers.push(() => {
|
|
275
|
+
void readNext().then(resolve);
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
}
|
|
229
279
|
}
|
|
230
280
|
};
|
|
231
281
|
|
|
@@ -253,11 +303,15 @@ export function createEngine(options: EngineOptions): Engine {
|
|
|
253
303
|
currentDepth: 0,
|
|
254
304
|
effectiveMaxDepth,
|
|
255
305
|
effectiveMaxConcurrentChildren,
|
|
306
|
+
effectiveMaxConcurrentAgentTurns,
|
|
256
307
|
onChildFailure,
|
|
257
308
|
...(streamParentDeadlineMs !== undefined ? { parentDeadlineMs: streamParentDeadlineMs } : {}),
|
|
258
309
|
...(options.defaultSubRunTimeoutMs !== undefined
|
|
259
310
|
? { defaultSubRunTimeoutMs: options.defaultSubRunTimeoutMs }
|
|
260
311
|
: {}),
|
|
312
|
+
...(options.tracer ? { tracer: options.tracer } : {}),
|
|
313
|
+
...(options.metricsHook ? { metricsHook: options.metricsHook } : {}),
|
|
314
|
+
...(options.logger ? { logger: options.logger } : {}),
|
|
261
315
|
streamEvents: true,
|
|
262
316
|
emit(event: RunEvent): void {
|
|
263
317
|
if (status !== "running") {
|
|
@@ -346,8 +400,8 @@ export function createEngine(options: EngineOptions): Engine {
|
|
|
346
400
|
timeoutLifecycle.cleanup();
|
|
347
401
|
abortRace.cleanup();
|
|
348
402
|
subscribers.clear();
|
|
349
|
-
for (const
|
|
350
|
-
|
|
403
|
+
for (const resolvePending of pendingIteratorResolvers.splice(0)) {
|
|
404
|
+
resolvePending();
|
|
351
405
|
}
|
|
352
406
|
}
|
|
353
407
|
|
|
@@ -367,12 +421,9 @@ export function createEngine(options: EngineOptions): Engine {
|
|
|
367
421
|
}
|
|
368
422
|
}
|
|
369
423
|
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
resolver({ done: false, value: canonicalEvent });
|
|
373
|
-
return;
|
|
424
|
+
for (const resolvePending of pendingIteratorResolvers.splice(0)) {
|
|
425
|
+
resolvePending();
|
|
374
426
|
}
|
|
375
|
-
pendingEvents.push(canonicalEvent);
|
|
376
427
|
}
|
|
377
428
|
}
|
|
378
429
|
};
|
|
@@ -659,15 +710,17 @@ interface RunProtocolOptions {
|
|
|
659
710
|
/**
|
|
660
711
|
* Current recursion depth. Top-level runs use 0; the coordinator dispatch
|
|
661
712
|
* loop increments before invoking {@link runProtocol} for a child run.
|
|
662
|
-
*
|
|
713
|
+
* Depth validation is enforced before delegate dispatch.
|
|
663
714
|
*/
|
|
664
715
|
readonly currentDepth?: number;
|
|
665
716
|
/**
|
|
666
|
-
* Effective max recursion depth
|
|
717
|
+
* Effective max recursion depth after engine and per-run ceilings are merged.
|
|
667
718
|
*/
|
|
668
719
|
readonly effectiveMaxDepth?: number;
|
|
669
720
|
/** Effective max delegated child concurrency resolved at run start. */
|
|
670
721
|
readonly effectiveMaxConcurrentChildren?: number;
|
|
722
|
+
/** Effective max agent-turn fan-out resolved at run start. */
|
|
723
|
+
readonly effectiveMaxConcurrentAgentTurns?: number;
|
|
671
724
|
readonly onChildFailure?: EngineOptions["onChildFailure"];
|
|
672
725
|
/**
|
|
673
726
|
* Root-run deadline (epoch ms) threaded through every recursive coordinator
|
|
@@ -682,10 +735,489 @@ interface RunProtocolOptions {
|
|
|
682
735
|
readonly defaultSubRunTimeoutMs?: number;
|
|
683
736
|
readonly registerAbortDrain?: (drain: AbortDrainFn) => void;
|
|
684
737
|
readonly failureInstancesByChildRunId?: Map<string, DogpileError>;
|
|
738
|
+
readonly tracer?: EngineOptions["tracer"];
|
|
739
|
+
readonly metricsHook?: EngineOptions["metricsHook"];
|
|
740
|
+
readonly logger?: EngineOptions["logger"];
|
|
741
|
+
/**
|
|
742
|
+
* Optional parent span for the next runProtocol invocation. Threaded by the
|
|
743
|
+
* coordinator when dispatching child runs so that the child's `dogpile.run`
|
|
744
|
+
* span is correctly nested under its parent's `dogpile.sub-run` span.
|
|
745
|
+
* Internal-only; not part of the public surface.
|
|
746
|
+
*/
|
|
747
|
+
readonly parentSpan?: DogpileSpan;
|
|
748
|
+
/**
|
|
749
|
+
* Per-child sub-run span lookup, keyed by childRunId. Populated by the
|
|
750
|
+
* parent's emit closure on `sub-run-started`. The coordinator dispatcher
|
|
751
|
+
* reads this to thread the correct per-child span as parent for the
|
|
752
|
+
* recursive runProtocol call. Internal-only.
|
|
753
|
+
*/
|
|
754
|
+
readonly subRunSpansByChildId?: ReadonlyMap<string, DogpileSpan>;
|
|
685
755
|
}
|
|
686
756
|
|
|
687
757
|
type NonStreamingProtocolOptions = Omit<RunProtocolOptions, "emit"> & Pick<EngineOptions, "evaluate">;
|
|
688
758
|
|
|
759
|
+
interface TracingState {
|
|
760
|
+
readonly tracer: DogpileTracer;
|
|
761
|
+
readonly runSpan: DogpileSpan;
|
|
762
|
+
readonly subRunSpans: Map<string, DogpileSpan>;
|
|
763
|
+
readonly agentTurnSpans: Map<string, DogpileSpan>;
|
|
764
|
+
readonly modelCallSpans: Map<string, DogpileSpan>;
|
|
765
|
+
readonly pendingModelRequests: Map<string, ModelRequestEvent>;
|
|
766
|
+
readonly agentTurnCounters: Map<string, number>;
|
|
767
|
+
readonly turnAccumByAgent: Map<string, TurnAccum>;
|
|
768
|
+
readonly agentIds: Set<string>;
|
|
769
|
+
runId?: string;
|
|
770
|
+
turnCount: number;
|
|
771
|
+
lastCost: CostSummary;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
interface TurnAccum {
|
|
775
|
+
inputTokens: number;
|
|
776
|
+
outputTokens: number;
|
|
777
|
+
costUsd: number;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function openRunTracing(options: {
|
|
781
|
+
readonly tracer?: DogpileTracer;
|
|
782
|
+
readonly parentSpan?: DogpileSpan;
|
|
783
|
+
readonly intent: string;
|
|
784
|
+
readonly protocolKind: string;
|
|
785
|
+
readonly tier: unknown;
|
|
786
|
+
}): TracingState | undefined {
|
|
787
|
+
if (!options.tracer) {
|
|
788
|
+
return undefined;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
const runSpan = options.tracer.startSpan(DOGPILE_SPAN_NAMES.RUN, {
|
|
792
|
+
...(options.parentSpan ? { parent: options.parentSpan } : {}),
|
|
793
|
+
attributes: {
|
|
794
|
+
"dogpile.run.protocol": options.protocolKind,
|
|
795
|
+
"dogpile.run.tier": String(options.tier),
|
|
796
|
+
"dogpile.run.intent": options.intent.slice(0, 200)
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
return {
|
|
801
|
+
tracer: options.tracer,
|
|
802
|
+
runSpan,
|
|
803
|
+
subRunSpans: new Map(),
|
|
804
|
+
agentTurnSpans: new Map(),
|
|
805
|
+
modelCallSpans: new Map(),
|
|
806
|
+
pendingModelRequests: new Map(),
|
|
807
|
+
agentTurnCounters: new Map(),
|
|
808
|
+
turnAccumByAgent: new Map(),
|
|
809
|
+
agentIds: new Set(),
|
|
810
|
+
turnCount: 0,
|
|
811
|
+
lastCost: emptyCost()
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
interface MetricsState {
|
|
816
|
+
readonly metricsHook: MetricsHook;
|
|
817
|
+
readonly logger: Logger | undefined;
|
|
818
|
+
readonly startedAtMs: number;
|
|
819
|
+
readonly subRunStartTimes: Map<string, number>;
|
|
820
|
+
totalCost: CostSummary;
|
|
821
|
+
nestedCost: CostSummary;
|
|
822
|
+
turns: number;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
function openRunMetrics(options: {
|
|
826
|
+
readonly metricsHook?: MetricsHook;
|
|
827
|
+
readonly logger?: Logger;
|
|
828
|
+
}): MetricsState | undefined {
|
|
829
|
+
if (!options.metricsHook) {
|
|
830
|
+
return undefined;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
return {
|
|
834
|
+
metricsHook: options.metricsHook,
|
|
835
|
+
logger: options.logger,
|
|
836
|
+
startedAtMs: Date.now(),
|
|
837
|
+
subRunStartTimes: new Map(),
|
|
838
|
+
totalCost: emptyCost(),
|
|
839
|
+
nestedCost: emptyCost(),
|
|
840
|
+
turns: 0
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
function routeMetricsError(err: unknown, logger: Logger | undefined): void {
|
|
845
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
846
|
+
try {
|
|
847
|
+
if (logger !== undefined) {
|
|
848
|
+
logger.error("dogpile:metricsHook threw", { error: msg });
|
|
849
|
+
} else {
|
|
850
|
+
console.error("dogpile:metricsHook threw", { error: msg });
|
|
851
|
+
}
|
|
852
|
+
} catch {
|
|
853
|
+
// A logger that throws from error() cannot be helped.
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
function fireHook(
|
|
858
|
+
callback: ((snapshot: RunMetricsSnapshot) => void | Promise<void>) | undefined,
|
|
859
|
+
snapshot: RunMetricsSnapshot,
|
|
860
|
+
logger: Logger | undefined
|
|
861
|
+
): void {
|
|
862
|
+
if (!callback) {
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
try {
|
|
867
|
+
const result = callback(snapshot);
|
|
868
|
+
if (result && typeof (result as Promise<void>).catch === "function") {
|
|
869
|
+
(result as Promise<void>).catch((err: unknown) => {
|
|
870
|
+
routeMetricsError(err, logger);
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
} catch (err: unknown) {
|
|
874
|
+
routeMetricsError(err, logger);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
function buildRunSnapshot(
|
|
879
|
+
result: RunResult,
|
|
880
|
+
startedAtMs: number
|
|
881
|
+
): RunMetricsSnapshot {
|
|
882
|
+
const nestedCosts = nestedSubRunCosts(result);
|
|
883
|
+
const budgetStopEvent = result.trace.events.find((event): event is BudgetStopEvent => event.type === "budget-stop");
|
|
884
|
+
const outcome: RunMetricsSnapshot["outcome"] = budgetStopEvent !== undefined ? "budget-stopped" : "completed";
|
|
885
|
+
const totalInputTokens = result.cost.inputTokens;
|
|
886
|
+
const totalOutputTokens = result.cost.outputTokens;
|
|
887
|
+
const totalCostUsd = result.cost.usd;
|
|
888
|
+
const ownInputTokens =
|
|
889
|
+
totalInputTokens - nestedCosts.reduce((sum, cost) => sum + cost.inputTokens, 0);
|
|
890
|
+
const ownOutputTokens =
|
|
891
|
+
totalOutputTokens - nestedCosts.reduce((sum, cost) => sum + cost.outputTokens, 0);
|
|
892
|
+
const ownCostUsd =
|
|
893
|
+
totalCostUsd - nestedCosts.reduce((sum, cost) => sum + cost.usd, 0);
|
|
894
|
+
const turns = result.trace.events.filter((event) => event.type === "agent-turn").length;
|
|
895
|
+
|
|
896
|
+
return {
|
|
897
|
+
outcome,
|
|
898
|
+
inputTokens: ownInputTokens,
|
|
899
|
+
outputTokens: ownOutputTokens,
|
|
900
|
+
costUsd: ownCostUsd,
|
|
901
|
+
totalInputTokens,
|
|
902
|
+
totalOutputTokens,
|
|
903
|
+
totalCostUsd,
|
|
904
|
+
turns,
|
|
905
|
+
durationMs: Date.now() - startedAtMs
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
function buildSubRunSnapshot(
|
|
910
|
+
subResult: RunResult,
|
|
911
|
+
durationMs: number
|
|
912
|
+
): RunMetricsSnapshot {
|
|
913
|
+
const nestedCosts = nestedSubRunCosts(subResult);
|
|
914
|
+
const budgetStopEvent = subResult.trace.events.find((event): event is BudgetStopEvent => event.type === "budget-stop");
|
|
915
|
+
const outcome: RunMetricsSnapshot["outcome"] = budgetStopEvent !== undefined ? "budget-stopped" : "completed";
|
|
916
|
+
const totalInputTokens = subResult.cost.inputTokens;
|
|
917
|
+
const totalOutputTokens = subResult.cost.outputTokens;
|
|
918
|
+
const totalCostUsd = subResult.cost.usd;
|
|
919
|
+
const ownInputTokens =
|
|
920
|
+
totalInputTokens - nestedCosts.reduce((sum, cost) => sum + cost.inputTokens, 0);
|
|
921
|
+
const ownOutputTokens =
|
|
922
|
+
totalOutputTokens - nestedCosts.reduce((sum, cost) => sum + cost.outputTokens, 0);
|
|
923
|
+
const ownCostUsd =
|
|
924
|
+
totalCostUsd - nestedCosts.reduce((sum, cost) => sum + cost.usd, 0);
|
|
925
|
+
const turns = subResult.trace.events.filter((event) => event.type === "agent-turn").length;
|
|
926
|
+
|
|
927
|
+
return {
|
|
928
|
+
outcome,
|
|
929
|
+
inputTokens: ownInputTokens,
|
|
930
|
+
outputTokens: ownOutputTokens,
|
|
931
|
+
costUsd: ownCostUsd,
|
|
932
|
+
totalInputTokens,
|
|
933
|
+
totalOutputTokens,
|
|
934
|
+
totalCostUsd,
|
|
935
|
+
turns,
|
|
936
|
+
durationMs
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
function nestedSubRunCosts(result: RunResult): CostSummary[] {
|
|
941
|
+
return result.trace.events.flatMap((event) => {
|
|
942
|
+
if (event.type === "sub-run-completed") {
|
|
943
|
+
return [event.subResult.cost];
|
|
944
|
+
}
|
|
945
|
+
if (event.type === "sub-run-failed") {
|
|
946
|
+
return [event.partialCost];
|
|
947
|
+
}
|
|
948
|
+
return [];
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
function subtractCost(total: CostSummary, nested: CostSummary): CostSummary {
|
|
953
|
+
return {
|
|
954
|
+
usd: total.usd - nested.usd,
|
|
955
|
+
inputTokens: total.inputTokens - nested.inputTokens,
|
|
956
|
+
outputTokens: total.outputTokens - nested.outputTokens,
|
|
957
|
+
totalTokens: total.totalTokens - nested.totalTokens
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
function handleMetricsEvent(state: MetricsState, event: RunEvent): void {
|
|
962
|
+
const parentRunIds = (event as { readonly parentRunIds?: readonly string[] }).parentRunIds;
|
|
963
|
+
if (parentRunIds !== undefined) {
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
switch (event.type) {
|
|
968
|
+
case "agent-turn": {
|
|
969
|
+
state.totalCost = event.cost;
|
|
970
|
+
state.turns += 1;
|
|
971
|
+
break;
|
|
972
|
+
}
|
|
973
|
+
case "broadcast":
|
|
974
|
+
case "budget-stop":
|
|
975
|
+
case "final": {
|
|
976
|
+
state.totalCost = event.cost;
|
|
977
|
+
break;
|
|
978
|
+
}
|
|
979
|
+
case "sub-run-started": {
|
|
980
|
+
state.subRunStartTimes.set(event.childRunId, Date.now());
|
|
981
|
+
break;
|
|
982
|
+
}
|
|
983
|
+
case "sub-run-completed": {
|
|
984
|
+
state.totalCost = addCost(state.totalCost, event.subResult.cost);
|
|
985
|
+
state.nestedCost = addCost(state.nestedCost, event.subResult.cost);
|
|
986
|
+
const startMs = state.subRunStartTimes.get(event.childRunId);
|
|
987
|
+
const durationMs = startMs !== undefined ? Date.now() - startMs : 0;
|
|
988
|
+
state.subRunStartTimes.delete(event.childRunId);
|
|
989
|
+
const snapshot = buildSubRunSnapshot(event.subResult, durationMs);
|
|
990
|
+
fireHook(state.metricsHook.onSubRunComplete, snapshot, state.logger);
|
|
991
|
+
break;
|
|
992
|
+
}
|
|
993
|
+
case "sub-run-failed": {
|
|
994
|
+
state.totalCost = addCost(state.totalCost, event.partialCost);
|
|
995
|
+
state.nestedCost = addCost(state.nestedCost, event.partialCost);
|
|
996
|
+
state.subRunStartTimes.delete(event.childRunId);
|
|
997
|
+
break;
|
|
998
|
+
}
|
|
999
|
+
default:
|
|
1000
|
+
break;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
function closeRunMetrics(state: MetricsState, result: RunResult | undefined): void {
|
|
1005
|
+
if (result !== undefined) {
|
|
1006
|
+
const snapshot = buildRunSnapshot(result, state.startedAtMs);
|
|
1007
|
+
fireHook(state.metricsHook.onRunComplete, snapshot, state.logger);
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
const ownCost = subtractCost(state.totalCost, state.nestedCost);
|
|
1012
|
+
const snapshot: RunMetricsSnapshot = {
|
|
1013
|
+
outcome: "aborted",
|
|
1014
|
+
inputTokens: ownCost.inputTokens,
|
|
1015
|
+
outputTokens: ownCost.outputTokens,
|
|
1016
|
+
costUsd: ownCost.usd,
|
|
1017
|
+
totalInputTokens: state.totalCost.inputTokens,
|
|
1018
|
+
totalOutputTokens: state.totalCost.outputTokens,
|
|
1019
|
+
totalCostUsd: state.totalCost.usd,
|
|
1020
|
+
turns: state.turns,
|
|
1021
|
+
durationMs: Date.now() - state.startedAtMs
|
|
1022
|
+
};
|
|
1023
|
+
fireHook(state.metricsHook.onRunComplete, snapshot, state.logger);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
function handleTracingEvent(state: TracingState, event: RunEvent): void {
|
|
1027
|
+
const parentRunIds = (event as { readonly parentRunIds?: readonly string[] }).parentRunIds;
|
|
1028
|
+
if (parentRunIds !== undefined) {
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
if (state.runId === undefined) {
|
|
1033
|
+
state.runId = event.runId;
|
|
1034
|
+
state.runSpan.setAttribute("dogpile.run.id", event.runId);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
switch (event.type) {
|
|
1038
|
+
case "model-request": {
|
|
1039
|
+
state.pendingModelRequests.set(event.callId, event);
|
|
1040
|
+
state.agentIds.add(event.agentId);
|
|
1041
|
+
|
|
1042
|
+
if (!state.agentTurnSpans.has(event.agentId)) {
|
|
1043
|
+
const turnNumber = (state.agentTurnCounters.get(event.agentId) ?? 0) + 1;
|
|
1044
|
+
state.agentTurnCounters.set(event.agentId, turnNumber);
|
|
1045
|
+
const turnParent = state.subRunSpans.get(event.runId) ?? state.runSpan;
|
|
1046
|
+
const turnSpan = state.tracer.startSpan(DOGPILE_SPAN_NAMES.AGENT_TURN, {
|
|
1047
|
+
parent: turnParent,
|
|
1048
|
+
attributes: {
|
|
1049
|
+
"dogpile.agent.id": event.agentId,
|
|
1050
|
+
"dogpile.agent.role": event.role,
|
|
1051
|
+
"dogpile.turn.number": turnNumber,
|
|
1052
|
+
"dogpile.model.id": event.modelId
|
|
1053
|
+
}
|
|
1054
|
+
});
|
|
1055
|
+
state.agentTurnSpans.set(event.agentId, turnSpan);
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
const callParent =
|
|
1059
|
+
state.agentTurnSpans.get(event.agentId) ??
|
|
1060
|
+
state.subRunSpans.get(event.runId) ??
|
|
1061
|
+
state.runSpan;
|
|
1062
|
+
const callSpan = state.tracer.startSpan(DOGPILE_SPAN_NAMES.MODEL_CALL, {
|
|
1063
|
+
parent: callParent,
|
|
1064
|
+
attributes: {
|
|
1065
|
+
"dogpile.model.id": event.modelId,
|
|
1066
|
+
"dogpile.call.id": event.callId,
|
|
1067
|
+
"dogpile.provider.id": event.providerId
|
|
1068
|
+
}
|
|
1069
|
+
});
|
|
1070
|
+
state.modelCallSpans.set(event.callId, callSpan);
|
|
1071
|
+
break;
|
|
1072
|
+
}
|
|
1073
|
+
case "model-response": {
|
|
1074
|
+
const span = state.modelCallSpans.get(event.callId);
|
|
1075
|
+
if (span) {
|
|
1076
|
+
const inputTokens = event.response.usage?.inputTokens ?? 0;
|
|
1077
|
+
const outputTokens = event.response.usage?.outputTokens ?? 0;
|
|
1078
|
+
const responseCost: CostSummary = {
|
|
1079
|
+
usd: event.response.costUsd ?? 0,
|
|
1080
|
+
inputTokens,
|
|
1081
|
+
outputTokens,
|
|
1082
|
+
totalTokens: event.response.usage?.totalTokens ?? inputTokens + outputTokens
|
|
1083
|
+
};
|
|
1084
|
+
span.setAttribute("dogpile.model.input_tokens", inputTokens);
|
|
1085
|
+
span.setAttribute("dogpile.model.output_tokens", outputTokens);
|
|
1086
|
+
if (event.response.costUsd !== undefined) {
|
|
1087
|
+
span.setAttribute("dogpile.model.cost_usd", event.response.costUsd);
|
|
1088
|
+
}
|
|
1089
|
+
span.setStatus("ok");
|
|
1090
|
+
span.end();
|
|
1091
|
+
state.modelCallSpans.delete(event.callId);
|
|
1092
|
+
const accum = state.turnAccumByAgent.get(event.agentId) ?? {
|
|
1093
|
+
inputTokens: 0,
|
|
1094
|
+
outputTokens: 0,
|
|
1095
|
+
costUsd: 0
|
|
1096
|
+
};
|
|
1097
|
+
accum.inputTokens += inputTokens;
|
|
1098
|
+
accum.outputTokens += outputTokens;
|
|
1099
|
+
accum.costUsd += responseCost.usd;
|
|
1100
|
+
state.turnAccumByAgent.set(event.agentId, accum);
|
|
1101
|
+
state.lastCost = addCost(state.lastCost, responseCost);
|
|
1102
|
+
}
|
|
1103
|
+
state.pendingModelRequests.delete(event.callId);
|
|
1104
|
+
break;
|
|
1105
|
+
}
|
|
1106
|
+
case "agent-turn": {
|
|
1107
|
+
state.agentIds.add(event.agentId);
|
|
1108
|
+
state.turnCount += 1;
|
|
1109
|
+
state.lastCost = event.cost;
|
|
1110
|
+
const turnSpan = state.agentTurnSpans.get(event.agentId);
|
|
1111
|
+
if (turnSpan) {
|
|
1112
|
+
turnSpan.setAttribute("dogpile.agent.role", event.role);
|
|
1113
|
+
const accum = state.turnAccumByAgent.get(event.agentId);
|
|
1114
|
+
turnSpan.setAttribute("dogpile.turn.cost_usd", accum?.costUsd ?? 0);
|
|
1115
|
+
turnSpan.setAttribute("dogpile.turn.input_tokens", accum?.inputTokens ?? 0);
|
|
1116
|
+
turnSpan.setAttribute("dogpile.turn.output_tokens", accum?.outputTokens ?? 0);
|
|
1117
|
+
turnSpan.setStatus("ok");
|
|
1118
|
+
turnSpan.end();
|
|
1119
|
+
state.agentTurnSpans.delete(event.agentId);
|
|
1120
|
+
}
|
|
1121
|
+
state.turnAccumByAgent.delete(event.agentId);
|
|
1122
|
+
break;
|
|
1123
|
+
}
|
|
1124
|
+
case "broadcast":
|
|
1125
|
+
case "budget-stop":
|
|
1126
|
+
case "final": {
|
|
1127
|
+
state.lastCost = event.cost;
|
|
1128
|
+
break;
|
|
1129
|
+
}
|
|
1130
|
+
case "sub-run-started": {
|
|
1131
|
+
const span = state.tracer.startSpan(DOGPILE_SPAN_NAMES.SUB_RUN, {
|
|
1132
|
+
parent: state.runSpan,
|
|
1133
|
+
attributes: {
|
|
1134
|
+
"dogpile.sub_run.child_run_id": event.childRunId,
|
|
1135
|
+
"dogpile.sub_run.parent_run_id": event.parentRunId,
|
|
1136
|
+
"dogpile.sub_run.depth": event.depth
|
|
1137
|
+
}
|
|
1138
|
+
});
|
|
1139
|
+
state.subRunSpans.set(event.childRunId, span);
|
|
1140
|
+
break;
|
|
1141
|
+
}
|
|
1142
|
+
case "sub-run-completed": {
|
|
1143
|
+
const span = state.subRunSpans.get(event.childRunId);
|
|
1144
|
+
if (span) {
|
|
1145
|
+
span.setStatus("ok");
|
|
1146
|
+
span.end();
|
|
1147
|
+
state.subRunSpans.delete(event.childRunId);
|
|
1148
|
+
}
|
|
1149
|
+
break;
|
|
1150
|
+
}
|
|
1151
|
+
case "sub-run-failed": {
|
|
1152
|
+
const span = state.subRunSpans.get(event.childRunId);
|
|
1153
|
+
if (span) {
|
|
1154
|
+
span.setStatus("error", event.error.message);
|
|
1155
|
+
span.end();
|
|
1156
|
+
state.subRunSpans.delete(event.childRunId);
|
|
1157
|
+
}
|
|
1158
|
+
break;
|
|
1159
|
+
}
|
|
1160
|
+
default:
|
|
1161
|
+
break;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
function closeRunTracing(state: TracingState, result: RunResult | undefined, error?: unknown): void {
|
|
1166
|
+
if (error !== undefined) {
|
|
1167
|
+
if (state.runId !== undefined) {
|
|
1168
|
+
state.runSpan.setAttribute("dogpile.run.id", state.runId);
|
|
1169
|
+
}
|
|
1170
|
+
state.runSpan.setAttribute("dogpile.run.agent_count", state.agentIds.size);
|
|
1171
|
+
state.runSpan.setAttribute("dogpile.run.turn_count", state.turnCount);
|
|
1172
|
+
state.runSpan.setAttribute("dogpile.run.cost_usd", state.lastCost.usd);
|
|
1173
|
+
state.runSpan.setAttribute("dogpile.run.input_tokens", state.lastCost.inputTokens);
|
|
1174
|
+
state.runSpan.setAttribute("dogpile.run.output_tokens", state.lastCost.outputTokens);
|
|
1175
|
+
state.runSpan.setAttribute("dogpile.run.outcome", "aborted");
|
|
1176
|
+
state.runSpan.setStatus("error", error instanceof Error ? error.message : String(error));
|
|
1177
|
+
closeOpenTracingSpans(state);
|
|
1178
|
+
state.runSpan.end();
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
if (result === undefined) {
|
|
1183
|
+
closeOpenTracingSpans(state);
|
|
1184
|
+
state.runSpan.end();
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
const budgetStopEvent = result.trace.events.find((event): event is BudgetStopEvent => event.type === "budget-stop");
|
|
1189
|
+
const terminationReason = budgetStopEvent?.reason;
|
|
1190
|
+
const outcome = terminationReason !== undefined ? "budget-stopped" : "completed";
|
|
1191
|
+
state.runSpan.setAttribute("dogpile.run.id", result.trace.runId);
|
|
1192
|
+
state.runSpan.setAttribute("dogpile.run.agent_count", result.trace.agentsUsed.length);
|
|
1193
|
+
state.runSpan.setAttribute("dogpile.run.turn_count", result.trace.events.filter((event) => event.type === "agent-turn").length);
|
|
1194
|
+
state.runSpan.setAttribute("dogpile.run.cost_usd", result.cost.usd);
|
|
1195
|
+
state.runSpan.setAttribute("dogpile.run.input_tokens", result.cost.inputTokens);
|
|
1196
|
+
state.runSpan.setAttribute("dogpile.run.output_tokens", result.cost.outputTokens);
|
|
1197
|
+
state.runSpan.setAttribute("dogpile.run.outcome", outcome);
|
|
1198
|
+
if (terminationReason !== undefined) {
|
|
1199
|
+
state.runSpan.setAttribute("dogpile.run.termination_reason", terminationReason);
|
|
1200
|
+
}
|
|
1201
|
+
state.runSpan.setStatus("ok");
|
|
1202
|
+
closeOpenTracingSpans(state);
|
|
1203
|
+
state.runSpan.end();
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
function closeOpenTracingSpans(state: TracingState): void {
|
|
1207
|
+
for (const span of state.modelCallSpans.values()) {
|
|
1208
|
+
span.end();
|
|
1209
|
+
}
|
|
1210
|
+
state.modelCallSpans.clear();
|
|
1211
|
+
for (const span of state.agentTurnSpans.values()) {
|
|
1212
|
+
span.end();
|
|
1213
|
+
}
|
|
1214
|
+
state.agentTurnSpans.clear();
|
|
1215
|
+
for (const span of state.subRunSpans.values()) {
|
|
1216
|
+
span.end();
|
|
1217
|
+
}
|
|
1218
|
+
state.subRunSpans.clear();
|
|
1219
|
+
}
|
|
1220
|
+
|
|
689
1221
|
async function runNonStreamingProtocol(options: NonStreamingProtocolOptions): Promise<RunResult> {
|
|
690
1222
|
const failureInstancesByChildRunId = new Map<string, DogpileError>();
|
|
691
1223
|
const abortLifecycle = createNonStreamingAbortLifecycle({
|
|
@@ -728,7 +1260,8 @@ async function runNonStreamingProtocol(options: NonStreamingProtocolOptions): Pr
|
|
|
728
1260
|
events
|
|
729
1261
|
}),
|
|
730
1262
|
eventLog: createRunEventLog(trace.runId, trace.protocol, events),
|
|
731
|
-
trace
|
|
1263
|
+
trace,
|
|
1264
|
+
health: computeHealth(trace, DEFAULT_HEALTH_THRESHOLDS)
|
|
732
1265
|
};
|
|
733
1266
|
const terminalThrow = resolveRuntimeTerminalThrow(runResult.trace, failureInstancesByChildRunId);
|
|
734
1267
|
if (terminalThrow) {
|
|
@@ -781,7 +1314,61 @@ function finalEventWithEvaluation(event: FinalEvent, evaluation: RunEvaluation):
|
|
|
781
1314
|
};
|
|
782
1315
|
}
|
|
783
1316
|
|
|
784
|
-
function runProtocol(options: RunProtocolOptions): Promise<RunResult> {
|
|
1317
|
+
async function runProtocol(options: RunProtocolOptions): Promise<RunResult> {
|
|
1318
|
+
const tracing = openRunTracing({
|
|
1319
|
+
...(options.tracer ? { tracer: options.tracer } : {}),
|
|
1320
|
+
...(options.parentSpan ? { parentSpan: options.parentSpan } : {}),
|
|
1321
|
+
intent: options.intent,
|
|
1322
|
+
protocolKind: options.protocol.kind,
|
|
1323
|
+
tier: options.tier
|
|
1324
|
+
});
|
|
1325
|
+
const metrics = openRunMetrics({
|
|
1326
|
+
...(options.metricsHook ? { metricsHook: options.metricsHook } : {}),
|
|
1327
|
+
...(options.logger ? { logger: options.logger } : {})
|
|
1328
|
+
});
|
|
1329
|
+
const emitForProtocol =
|
|
1330
|
+
tracing || metrics || options.emit
|
|
1331
|
+
? (event: RunEvent): void => {
|
|
1332
|
+
if (tracing) {
|
|
1333
|
+
handleTracingEvent(tracing, event);
|
|
1334
|
+
}
|
|
1335
|
+
if (metrics) {
|
|
1336
|
+
handleMetricsEvent(metrics, event);
|
|
1337
|
+
}
|
|
1338
|
+
options.emit?.(event);
|
|
1339
|
+
}
|
|
1340
|
+
: undefined;
|
|
1341
|
+
const protocolOptions = tracing
|
|
1342
|
+
? {
|
|
1343
|
+
...options,
|
|
1344
|
+
subRunSpansByChildId: tracing.subRunSpans
|
|
1345
|
+
}
|
|
1346
|
+
: options;
|
|
1347
|
+
|
|
1348
|
+
try {
|
|
1349
|
+
const result = await runProtocolInner(protocolOptions, emitForProtocol);
|
|
1350
|
+
if (tracing) {
|
|
1351
|
+
closeRunTracing(tracing, result);
|
|
1352
|
+
}
|
|
1353
|
+
if (metrics && (options.currentDepth === 0 || options.currentDepth === undefined)) {
|
|
1354
|
+
closeRunMetrics(metrics, result);
|
|
1355
|
+
}
|
|
1356
|
+
return result;
|
|
1357
|
+
} catch (error) {
|
|
1358
|
+
if (tracing) {
|
|
1359
|
+
closeRunTracing(tracing, undefined, error);
|
|
1360
|
+
}
|
|
1361
|
+
if (metrics && (options.currentDepth === 0 || options.currentDepth === undefined)) {
|
|
1362
|
+
closeRunMetrics(metrics, undefined);
|
|
1363
|
+
}
|
|
1364
|
+
throw error;
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
function runProtocolInner(
|
|
1369
|
+
options: RunProtocolOptions,
|
|
1370
|
+
emitForProtocol?: (event: RunEvent) => void
|
|
1371
|
+
): Promise<RunResult> {
|
|
785
1372
|
switch (options.protocol.kind) {
|
|
786
1373
|
case "sequential":
|
|
787
1374
|
return runSequential({
|
|
@@ -797,7 +1384,7 @@ function runProtocol(options: RunProtocolOptions): Promise<RunResult> {
|
|
|
797
1384
|
...(options.signal !== undefined ? { signal: options.signal } : {}),
|
|
798
1385
|
...(options.terminate ? { terminate: options.terminate } : {}),
|
|
799
1386
|
...(options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {}),
|
|
800
|
-
...(
|
|
1387
|
+
...(emitForProtocol ? { emit: emitForProtocol } : {})
|
|
801
1388
|
});
|
|
802
1389
|
case "broadcast":
|
|
803
1390
|
return runBroadcast({
|
|
@@ -813,7 +1400,10 @@ function runProtocol(options: RunProtocolOptions): Promise<RunResult> {
|
|
|
813
1400
|
...(options.signal !== undefined ? { signal: options.signal } : {}),
|
|
814
1401
|
...(options.terminate ? { terminate: options.terminate } : {}),
|
|
815
1402
|
...(options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {}),
|
|
816
|
-
...(options.
|
|
1403
|
+
...(options.effectiveMaxConcurrentAgentTurns !== undefined
|
|
1404
|
+
? { maxConcurrentAgentTurns: options.effectiveMaxConcurrentAgentTurns }
|
|
1405
|
+
: {}),
|
|
1406
|
+
...(emitForProtocol ? { emit: emitForProtocol } : {})
|
|
817
1407
|
});
|
|
818
1408
|
case "coordinator":
|
|
819
1409
|
return runCoordinator({
|
|
@@ -829,11 +1419,16 @@ function runProtocol(options: RunProtocolOptions): Promise<RunResult> {
|
|
|
829
1419
|
...(options.signal !== undefined ? { signal: options.signal } : {}),
|
|
830
1420
|
...(options.terminate ? { terminate: options.terminate } : {}),
|
|
831
1421
|
...(options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {}),
|
|
832
|
-
...(options.
|
|
1422
|
+
...(options.effectiveMaxConcurrentAgentTurns !== undefined
|
|
1423
|
+
? { maxConcurrentAgentTurns: options.effectiveMaxConcurrentAgentTurns }
|
|
1424
|
+
: {}),
|
|
1425
|
+
...(emitForProtocol ? { emit: emitForProtocol } : {}),
|
|
833
1426
|
...(options.streamEvents !== undefined ? { streamEvents: options.streamEvents } : {}),
|
|
834
1427
|
currentDepth: options.currentDepth ?? 0,
|
|
835
1428
|
effectiveMaxDepth: options.effectiveMaxDepth ?? Infinity,
|
|
836
1429
|
effectiveMaxConcurrentChildren: options.effectiveMaxConcurrentChildren ?? DEFAULT_MAX_CONCURRENT_CHILDREN,
|
|
1430
|
+
effectiveMaxConcurrentAgentTurns:
|
|
1431
|
+
options.effectiveMaxConcurrentAgentTurns ?? DEFAULT_MAX_CONCURRENT_AGENT_TURNS,
|
|
837
1432
|
onChildFailure: options.onChildFailure ?? "continue",
|
|
838
1433
|
...(options.parentDeadlineMs !== undefined ? { parentDeadlineMs: options.parentDeadlineMs } : {}),
|
|
839
1434
|
...(options.defaultSubRunTimeoutMs !== undefined
|
|
@@ -843,11 +1438,17 @@ function runProtocol(options: RunProtocolOptions): Promise<RunResult> {
|
|
|
843
1438
|
...(options.failureInstancesByChildRunId !== undefined
|
|
844
1439
|
? { failureInstancesByChildRunId: options.failureInstancesByChildRunId }
|
|
845
1440
|
: {}),
|
|
846
|
-
runProtocol: (childInput) =>
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
1441
|
+
runProtocol: (childInput) => {
|
|
1442
|
+
const { runId: childRunId, ...childProtocolInput } = childInput;
|
|
1443
|
+
const childParent = options.subRunSpansByChildId?.get(childRunId) ?? options.parentSpan;
|
|
1444
|
+
return runProtocol({
|
|
1445
|
+
...childProtocolInput,
|
|
1446
|
+
protocol: normalizeProtocol(childProtocolInput.protocol),
|
|
1447
|
+
...(options.tracer ? { tracer: options.tracer } : {}),
|
|
1448
|
+
...(childParent ? { parentSpan: childParent } : {}),
|
|
1449
|
+
...(options.logger ? { logger: options.logger } : {})
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
851
1452
|
});
|
|
852
1453
|
case "shared":
|
|
853
1454
|
return runShared({
|
|
@@ -863,7 +1464,10 @@ function runProtocol(options: RunProtocolOptions): Promise<RunResult> {
|
|
|
863
1464
|
...(options.signal !== undefined ? { signal: options.signal } : {}),
|
|
864
1465
|
...(options.terminate ? { terminate: options.terminate } : {}),
|
|
865
1466
|
...(options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {}),
|
|
866
|
-
...(options.
|
|
1467
|
+
...(options.effectiveMaxConcurrentAgentTurns !== undefined
|
|
1468
|
+
? { maxConcurrentAgentTurns: options.effectiveMaxConcurrentAgentTurns }
|
|
1469
|
+
: {}),
|
|
1470
|
+
...(emitForProtocol ? { emit: emitForProtocol } : {})
|
|
867
1471
|
});
|
|
868
1472
|
}
|
|
869
1473
|
}
|
|
@@ -916,7 +1520,14 @@ export function stream(options: DogpileOptions): StreamHandle {
|
|
|
916
1520
|
* the ergonomic {@link RunResult} wrapper from the JSON-serializable
|
|
917
1521
|
* {@link Trace} returned by a previous `run()`, `stream()`, or
|
|
918
1522
|
* `Dogpile.pile()` call.
|
|
1523
|
+
*
|
|
1524
|
+
* Tracing and metrics: replay is intentionally tracing-free and metrics-free.
|
|
1525
|
+
* Even when an engine instance has been configured with a `tracer` or
|
|
1526
|
+
* `metricsHook` on its `EngineOptions`, calling this function emits no spans
|
|
1527
|
+
* or callbacks — replaying historical events with current timestamps would
|
|
1528
|
+
* confuse observability backends. See `docs/developer-usage.md`.
|
|
919
1529
|
*/
|
|
1530
|
+
// Tracing/metrics-free: replay never uses EngineOptions tracer or metricsHook.
|
|
920
1531
|
export function replay(trace: Trace): RunResult {
|
|
921
1532
|
const cost = trace.finalOutput.cost;
|
|
922
1533
|
const lastEvent = trace.events.at(-1);
|
|
@@ -931,7 +1542,11 @@ export function replay(trace: Trace): RunResult {
|
|
|
931
1542
|
}
|
|
932
1543
|
const baseResult = {
|
|
933
1544
|
output: trace.finalOutput.output,
|
|
934
|
-
eventLog: createRunEventLog(
|
|
1545
|
+
eventLog: createRunEventLog(
|
|
1546
|
+
trace.runId,
|
|
1547
|
+
trace.protocol,
|
|
1548
|
+
synthesizeProviderEvents(trace, trace.providerCalls)
|
|
1549
|
+
),
|
|
935
1550
|
trace,
|
|
936
1551
|
transcript: trace.transcript,
|
|
937
1552
|
usage: createRunUsage(cost),
|
|
@@ -944,7 +1559,8 @@ export function replay(trace: Trace): RunResult {
|
|
|
944
1559
|
events: trace.events
|
|
945
1560
|
}),
|
|
946
1561
|
accounting,
|
|
947
|
-
cost
|
|
1562
|
+
cost,
|
|
1563
|
+
health: computeHealth(trace, DEFAULT_HEALTH_THRESHOLDS)
|
|
948
1564
|
};
|
|
949
1565
|
|
|
950
1566
|
if (lastEvent?.type !== "final") {
|
|
@@ -958,6 +1574,60 @@ export function replay(trace: Trace): RunResult {
|
|
|
958
1574
|
};
|
|
959
1575
|
}
|
|
960
1576
|
|
|
1577
|
+
function synthesizeProviderEvents(
|
|
1578
|
+
trace: Trace,
|
|
1579
|
+
providerCalls: readonly ReplayTraceProviderCall[]
|
|
1580
|
+
): readonly RunEvent[] {
|
|
1581
|
+
const hasLiveProvenance = trace.events.some(
|
|
1582
|
+
(event) => event.type === "model-request" || event.type === "model-response"
|
|
1583
|
+
);
|
|
1584
|
+
if (hasLiveProvenance) {
|
|
1585
|
+
return trace.events;
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
const baseEvents = trace.events.filter(
|
|
1589
|
+
(event) => event.type !== "model-request" && event.type !== "model-response"
|
|
1590
|
+
);
|
|
1591
|
+
const result: RunEvent[] = [];
|
|
1592
|
+
let turnCount = 0;
|
|
1593
|
+
|
|
1594
|
+
for (const event of baseEvents) {
|
|
1595
|
+
if (event.type === "agent-turn") {
|
|
1596
|
+
const call = providerCalls[turnCount];
|
|
1597
|
+
if (call !== undefined) {
|
|
1598
|
+
const modelId = typeof call.modelId === "string" && call.modelId.length > 0 ? call.modelId : call.providerId;
|
|
1599
|
+
result.push({
|
|
1600
|
+
type: "model-request",
|
|
1601
|
+
runId: trace.runId,
|
|
1602
|
+
callId: call.callId,
|
|
1603
|
+
providerId: call.providerId,
|
|
1604
|
+
modelId,
|
|
1605
|
+
startedAt: call.startedAt,
|
|
1606
|
+
agentId: call.agentId,
|
|
1607
|
+
role: call.role,
|
|
1608
|
+
request: call.request
|
|
1609
|
+
});
|
|
1610
|
+
result.push({
|
|
1611
|
+
type: "model-response",
|
|
1612
|
+
runId: trace.runId,
|
|
1613
|
+
callId: call.callId,
|
|
1614
|
+
providerId: call.providerId,
|
|
1615
|
+
modelId,
|
|
1616
|
+
startedAt: call.startedAt,
|
|
1617
|
+
completedAt: call.completedAt,
|
|
1618
|
+
agentId: call.agentId,
|
|
1619
|
+
role: call.role,
|
|
1620
|
+
response: call.response
|
|
1621
|
+
});
|
|
1622
|
+
}
|
|
1623
|
+
turnCount += 1;
|
|
1624
|
+
}
|
|
1625
|
+
result.push(event);
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
return result;
|
|
1629
|
+
}
|
|
1630
|
+
|
|
961
1631
|
function resolveRuntimeTerminalThrow(
|
|
962
1632
|
trace: Trace,
|
|
963
1633
|
failureInstancesByChildRunId: ReadonlyMap<string, DogpileError>
|
|
@@ -1060,14 +1730,21 @@ function dogpileErrorFromSerializedPayload(input: {
|
|
|
1060
1730
|
* Replay a saved completed trace as a stream without invoking a model provider.
|
|
1061
1731
|
*
|
|
1062
1732
|
* @remarks
|
|
1063
|
-
* This is the streaming counterpart to {@link replay}. It yields the
|
|
1064
|
-
*
|
|
1065
|
-
*
|
|
1066
|
-
* replay remains storage-free and
|
|
1733
|
+
* This is the streaming counterpart to {@link replay}. It yields the same
|
|
1734
|
+
* event sequence exposed by the replayed result event log, including legacy
|
|
1735
|
+
* provenance synthesis when a saved trace predates model request/response
|
|
1736
|
+
* events. Since all data comes from the trace, replay remains storage-free and
|
|
1737
|
+
* provider-free.
|
|
1738
|
+
*
|
|
1739
|
+
* Tracing and metrics: replayStream is intentionally tracing-free and
|
|
1740
|
+
* metrics-free. Even when an engine instance has been configured with a
|
|
1741
|
+
* `tracer` or `metricsHook` on its `EngineOptions`, calling this function
|
|
1742
|
+
* emits no spans or callbacks — replaying historical events with current
|
|
1743
|
+
* timestamps would confuse observability backends. See `docs/developer-usage.md`.
|
|
1067
1744
|
*/
|
|
1745
|
+
// Tracing/metrics-free: replayStream never uses EngineOptions tracer or metricsHook.
|
|
1068
1746
|
export function replayStream(trace: Trace): StreamHandle {
|
|
1069
1747
|
const result = Promise.resolve(replay(trace));
|
|
1070
|
-
const replayEvents = replayStreamEvents(trace);
|
|
1071
1748
|
|
|
1072
1749
|
return {
|
|
1073
1750
|
get status(): StreamHandleStatus {
|
|
@@ -1078,7 +1755,7 @@ export function replayStream(trace: Trace): StreamHandle {
|
|
|
1078
1755
|
// Replay streams are already completed snapshots, so cancellation is a no-op.
|
|
1079
1756
|
},
|
|
1080
1757
|
subscribe(subscriber: StreamEventSubscriber) {
|
|
1081
|
-
for (const event of
|
|
1758
|
+
for (const event of replayStreamEvents(trace)) {
|
|
1082
1759
|
subscriber(event);
|
|
1083
1760
|
}
|
|
1084
1761
|
|
|
@@ -1089,34 +1766,24 @@ export function replayStream(trace: Trace): StreamHandle {
|
|
|
1089
1766
|
};
|
|
1090
1767
|
},
|
|
1091
1768
|
[Symbol.asyncIterator](): AsyncIterator<StreamEvent> {
|
|
1092
|
-
|
|
1769
|
+
const iterator = replayStreamEvents(trace)[Symbol.iterator]();
|
|
1093
1770
|
|
|
1094
1771
|
return {
|
|
1095
1772
|
next(): Promise<IteratorResult<StreamEvent>> {
|
|
1096
|
-
|
|
1097
|
-
if (event) {
|
|
1098
|
-
index += 1;
|
|
1099
|
-
return Promise.resolve({ done: false, value: event });
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
return Promise.resolve({ done: true, value: undefined });
|
|
1773
|
+
return Promise.resolve(iterator.next());
|
|
1103
1774
|
}
|
|
1104
1775
|
};
|
|
1105
1776
|
}
|
|
1106
1777
|
};
|
|
1107
1778
|
}
|
|
1108
1779
|
|
|
1109
|
-
function replayStreamEvents(trace: Trace, parentRunIds: readonly string[] = []): StreamEvent
|
|
1110
|
-
const
|
|
1111
|
-
|
|
1112
|
-
for (const event of trace.events) {
|
|
1780
|
+
function* replayStreamEvents(trace: Trace, parentRunIds: readonly string[] = []): IterableIterator<StreamEvent> {
|
|
1781
|
+
for (const event of synthesizeProviderEvents(trace, trace.providerCalls)) {
|
|
1113
1782
|
if (event.type === "sub-run-completed") {
|
|
1114
|
-
|
|
1783
|
+
yield* replayStreamEvents(event.subResult.trace, [...parentRunIds, trace.runId]);
|
|
1115
1784
|
}
|
|
1116
|
-
|
|
1785
|
+
yield wrapReplayStreamEvent(event, parentRunIds);
|
|
1117
1786
|
}
|
|
1118
|
-
|
|
1119
|
-
return events;
|
|
1120
1787
|
}
|
|
1121
1788
|
|
|
1122
1789
|
function wrapReplayStreamEvent(event: RunEvent, parentRunIds: readonly string[]): StreamEvent {
|