@dogpile/sdk 0.4.0 → 0.5.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 +65 -0
- package/dist/browser/index.js +726 -176
- 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 +1 -0
- 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.map +1 -1
- package/dist/runtime/broadcast.js +39 -36
- package/dist/runtime/broadcast.js.map +1 -1
- package/dist/runtime/coordinator.d.ts +5 -0
- package/dist/runtime/coordinator.d.ts.map +1 -1
- package/dist/runtime/coordinator.js +50 -39
- package/dist/runtime/coordinator.js.map +1 -1
- package/dist/runtime/defaults.d.ts.map +1 -1
- package/dist/runtime/defaults.js +12 -4
- 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 +523 -18
- 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 +34 -7
- 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/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.map +1 -1
- package/dist/runtime/shared.js +39 -36
- package/dist/runtime/shared.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/types/events.d.ts +10 -4
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/replay.d.ts +2 -0
- package/dist/types/replay.d.ts.map +1 -1
- package/dist/types.d.ts +124 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +39 -1
- package/src/index.ts +5 -0
- package/src/providers/openai-compatible.ts +1 -0
- package/src/runtime/audit.ts +121 -0
- package/src/runtime/broadcast.ts +40 -37
- package/src/runtime/coordinator.ts +54 -39
- package/src/runtime/defaults.ts +13 -4
- package/src/runtime/engine.ts +648 -18
- 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 +38 -6
- package/src/runtime/provenance.ts +43 -0
- package/src/runtime/sequential.ts +40 -37
- package/src/runtime/shared.ts +40 -37
- package/src/runtime/tracing.ts +35 -0
- package/src/types/events.ts +10 -4
- package/src/types/replay.ts +2 -0
- package/src/types.ts +132 -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,6 +63,9 @@ 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;
|
|
@@ -130,6 +140,9 @@ export function createEngine(options: EngineOptions): Engine {
|
|
|
130
140
|
...(terminate ? { terminate } : {}),
|
|
131
141
|
...(options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {}),
|
|
132
142
|
...(options.evaluate ? { evaluate: options.evaluate } : {}),
|
|
143
|
+
...(options.tracer ? { tracer: options.tracer } : {}),
|
|
144
|
+
...(options.metricsHook ? { metricsHook: options.metricsHook } : {}),
|
|
145
|
+
...(options.logger ? { logger: options.logger } : {}),
|
|
133
146
|
currentDepth: 0,
|
|
134
147
|
effectiveMaxDepth,
|
|
135
148
|
effectiveMaxConcurrentChildren,
|
|
@@ -258,6 +271,9 @@ export function createEngine(options: EngineOptions): Engine {
|
|
|
258
271
|
...(options.defaultSubRunTimeoutMs !== undefined
|
|
259
272
|
? { defaultSubRunTimeoutMs: options.defaultSubRunTimeoutMs }
|
|
260
273
|
: {}),
|
|
274
|
+
...(options.tracer ? { tracer: options.tracer } : {}),
|
|
275
|
+
...(options.metricsHook ? { metricsHook: options.metricsHook } : {}),
|
|
276
|
+
...(options.logger ? { logger: options.logger } : {}),
|
|
261
277
|
streamEvents: true,
|
|
262
278
|
emit(event: RunEvent): void {
|
|
263
279
|
if (status !== "running") {
|
|
@@ -682,10 +698,489 @@ interface RunProtocolOptions {
|
|
|
682
698
|
readonly defaultSubRunTimeoutMs?: number;
|
|
683
699
|
readonly registerAbortDrain?: (drain: AbortDrainFn) => void;
|
|
684
700
|
readonly failureInstancesByChildRunId?: Map<string, DogpileError>;
|
|
701
|
+
readonly tracer?: EngineOptions["tracer"];
|
|
702
|
+
readonly metricsHook?: EngineOptions["metricsHook"];
|
|
703
|
+
readonly logger?: EngineOptions["logger"];
|
|
704
|
+
/**
|
|
705
|
+
* Optional parent span for the next runProtocol invocation. Threaded by the
|
|
706
|
+
* coordinator when dispatching child runs so that the child's `dogpile.run`
|
|
707
|
+
* span is correctly nested under its parent's `dogpile.sub-run` span.
|
|
708
|
+
* Internal-only; not part of the public surface.
|
|
709
|
+
*/
|
|
710
|
+
readonly parentSpan?: DogpileSpan;
|
|
711
|
+
/**
|
|
712
|
+
* Per-child sub-run span lookup, keyed by childRunId. Populated by the
|
|
713
|
+
* parent's emit closure on `sub-run-started`. The coordinator dispatcher
|
|
714
|
+
* reads this to thread the correct per-child span as parent for the
|
|
715
|
+
* recursive runProtocol call. Internal-only.
|
|
716
|
+
*/
|
|
717
|
+
readonly subRunSpansByChildId?: ReadonlyMap<string, DogpileSpan>;
|
|
685
718
|
}
|
|
686
719
|
|
|
687
720
|
type NonStreamingProtocolOptions = Omit<RunProtocolOptions, "emit"> & Pick<EngineOptions, "evaluate">;
|
|
688
721
|
|
|
722
|
+
interface TracingState {
|
|
723
|
+
readonly tracer: DogpileTracer;
|
|
724
|
+
readonly runSpan: DogpileSpan;
|
|
725
|
+
readonly subRunSpans: Map<string, DogpileSpan>;
|
|
726
|
+
readonly agentTurnSpans: Map<string, DogpileSpan>;
|
|
727
|
+
readonly modelCallSpans: Map<string, DogpileSpan>;
|
|
728
|
+
readonly pendingModelRequests: Map<string, ModelRequestEvent>;
|
|
729
|
+
readonly agentTurnCounters: Map<string, number>;
|
|
730
|
+
readonly turnAccumByAgent: Map<string, TurnAccum>;
|
|
731
|
+
readonly agentIds: Set<string>;
|
|
732
|
+
runId?: string;
|
|
733
|
+
turnCount: number;
|
|
734
|
+
lastCost: CostSummary;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
interface TurnAccum {
|
|
738
|
+
inputTokens: number;
|
|
739
|
+
outputTokens: number;
|
|
740
|
+
costUsd: number;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function openRunTracing(options: {
|
|
744
|
+
readonly tracer?: DogpileTracer;
|
|
745
|
+
readonly parentSpan?: DogpileSpan;
|
|
746
|
+
readonly intent: string;
|
|
747
|
+
readonly protocolKind: string;
|
|
748
|
+
readonly tier: unknown;
|
|
749
|
+
}): TracingState | undefined {
|
|
750
|
+
if (!options.tracer) {
|
|
751
|
+
return undefined;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
const runSpan = options.tracer.startSpan(DOGPILE_SPAN_NAMES.RUN, {
|
|
755
|
+
...(options.parentSpan ? { parent: options.parentSpan } : {}),
|
|
756
|
+
attributes: {
|
|
757
|
+
"dogpile.run.protocol": options.protocolKind,
|
|
758
|
+
"dogpile.run.tier": String(options.tier),
|
|
759
|
+
"dogpile.run.intent": options.intent.slice(0, 200)
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
return {
|
|
764
|
+
tracer: options.tracer,
|
|
765
|
+
runSpan,
|
|
766
|
+
subRunSpans: new Map(),
|
|
767
|
+
agentTurnSpans: new Map(),
|
|
768
|
+
modelCallSpans: new Map(),
|
|
769
|
+
pendingModelRequests: new Map(),
|
|
770
|
+
agentTurnCounters: new Map(),
|
|
771
|
+
turnAccumByAgent: new Map(),
|
|
772
|
+
agentIds: new Set(),
|
|
773
|
+
turnCount: 0,
|
|
774
|
+
lastCost: emptyCost()
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
interface MetricsState {
|
|
779
|
+
readonly metricsHook: MetricsHook;
|
|
780
|
+
readonly logger: Logger | undefined;
|
|
781
|
+
readonly startedAtMs: number;
|
|
782
|
+
readonly subRunStartTimes: Map<string, number>;
|
|
783
|
+
totalCost: CostSummary;
|
|
784
|
+
nestedCost: CostSummary;
|
|
785
|
+
turns: number;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
function openRunMetrics(options: {
|
|
789
|
+
readonly metricsHook?: MetricsHook;
|
|
790
|
+
readonly logger?: Logger;
|
|
791
|
+
}): MetricsState | undefined {
|
|
792
|
+
if (!options.metricsHook) {
|
|
793
|
+
return undefined;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
return {
|
|
797
|
+
metricsHook: options.metricsHook,
|
|
798
|
+
logger: options.logger,
|
|
799
|
+
startedAtMs: Date.now(),
|
|
800
|
+
subRunStartTimes: new Map(),
|
|
801
|
+
totalCost: emptyCost(),
|
|
802
|
+
nestedCost: emptyCost(),
|
|
803
|
+
turns: 0
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
function routeMetricsError(err: unknown, logger: Logger | undefined): void {
|
|
808
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
809
|
+
try {
|
|
810
|
+
if (logger !== undefined) {
|
|
811
|
+
logger.error("dogpile:metricsHook threw", { error: msg });
|
|
812
|
+
} else {
|
|
813
|
+
console.error("dogpile:metricsHook threw", { error: msg });
|
|
814
|
+
}
|
|
815
|
+
} catch {
|
|
816
|
+
// A logger that throws from error() cannot be helped.
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
function fireHook(
|
|
821
|
+
callback: ((snapshot: RunMetricsSnapshot) => void | Promise<void>) | undefined,
|
|
822
|
+
snapshot: RunMetricsSnapshot,
|
|
823
|
+
logger: Logger | undefined
|
|
824
|
+
): void {
|
|
825
|
+
if (!callback) {
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
try {
|
|
830
|
+
const result = callback(snapshot);
|
|
831
|
+
if (result && typeof (result as Promise<void>).catch === "function") {
|
|
832
|
+
(result as Promise<void>).catch((err: unknown) => {
|
|
833
|
+
routeMetricsError(err, logger);
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
} catch (err: unknown) {
|
|
837
|
+
routeMetricsError(err, logger);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function buildRunSnapshot(
|
|
842
|
+
result: RunResult,
|
|
843
|
+
startedAtMs: number
|
|
844
|
+
): RunMetricsSnapshot {
|
|
845
|
+
const nestedCosts = nestedSubRunCosts(result);
|
|
846
|
+
const budgetStopEvent = result.trace.events.find((event): event is BudgetStopEvent => event.type === "budget-stop");
|
|
847
|
+
const outcome: RunMetricsSnapshot["outcome"] = budgetStopEvent !== undefined ? "budget-stopped" : "completed";
|
|
848
|
+
const totalInputTokens = result.cost.inputTokens;
|
|
849
|
+
const totalOutputTokens = result.cost.outputTokens;
|
|
850
|
+
const totalCostUsd = result.cost.usd;
|
|
851
|
+
const ownInputTokens =
|
|
852
|
+
totalInputTokens - nestedCosts.reduce((sum, cost) => sum + cost.inputTokens, 0);
|
|
853
|
+
const ownOutputTokens =
|
|
854
|
+
totalOutputTokens - nestedCosts.reduce((sum, cost) => sum + cost.outputTokens, 0);
|
|
855
|
+
const ownCostUsd =
|
|
856
|
+
totalCostUsd - nestedCosts.reduce((sum, cost) => sum + cost.usd, 0);
|
|
857
|
+
const turns = result.trace.events.filter((event) => event.type === "agent-turn").length;
|
|
858
|
+
|
|
859
|
+
return {
|
|
860
|
+
outcome,
|
|
861
|
+
inputTokens: ownInputTokens,
|
|
862
|
+
outputTokens: ownOutputTokens,
|
|
863
|
+
costUsd: ownCostUsd,
|
|
864
|
+
totalInputTokens,
|
|
865
|
+
totalOutputTokens,
|
|
866
|
+
totalCostUsd,
|
|
867
|
+
turns,
|
|
868
|
+
durationMs: Date.now() - startedAtMs
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
function buildSubRunSnapshot(
|
|
873
|
+
subResult: RunResult,
|
|
874
|
+
durationMs: number
|
|
875
|
+
): RunMetricsSnapshot {
|
|
876
|
+
const nestedCosts = nestedSubRunCosts(subResult);
|
|
877
|
+
const budgetStopEvent = subResult.trace.events.find((event): event is BudgetStopEvent => event.type === "budget-stop");
|
|
878
|
+
const outcome: RunMetricsSnapshot["outcome"] = budgetStopEvent !== undefined ? "budget-stopped" : "completed";
|
|
879
|
+
const totalInputTokens = subResult.cost.inputTokens;
|
|
880
|
+
const totalOutputTokens = subResult.cost.outputTokens;
|
|
881
|
+
const totalCostUsd = subResult.cost.usd;
|
|
882
|
+
const ownInputTokens =
|
|
883
|
+
totalInputTokens - nestedCosts.reduce((sum, cost) => sum + cost.inputTokens, 0);
|
|
884
|
+
const ownOutputTokens =
|
|
885
|
+
totalOutputTokens - nestedCosts.reduce((sum, cost) => sum + cost.outputTokens, 0);
|
|
886
|
+
const ownCostUsd =
|
|
887
|
+
totalCostUsd - nestedCosts.reduce((sum, cost) => sum + cost.usd, 0);
|
|
888
|
+
const turns = subResult.trace.events.filter((event) => event.type === "agent-turn").length;
|
|
889
|
+
|
|
890
|
+
return {
|
|
891
|
+
outcome,
|
|
892
|
+
inputTokens: ownInputTokens,
|
|
893
|
+
outputTokens: ownOutputTokens,
|
|
894
|
+
costUsd: ownCostUsd,
|
|
895
|
+
totalInputTokens,
|
|
896
|
+
totalOutputTokens,
|
|
897
|
+
totalCostUsd,
|
|
898
|
+
turns,
|
|
899
|
+
durationMs
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
function nestedSubRunCosts(result: RunResult): CostSummary[] {
|
|
904
|
+
return result.trace.events.flatMap((event) => {
|
|
905
|
+
if (event.type === "sub-run-completed") {
|
|
906
|
+
return [event.subResult.cost];
|
|
907
|
+
}
|
|
908
|
+
if (event.type === "sub-run-failed") {
|
|
909
|
+
return [event.partialCost];
|
|
910
|
+
}
|
|
911
|
+
return [];
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
function subtractCost(total: CostSummary, nested: CostSummary): CostSummary {
|
|
916
|
+
return {
|
|
917
|
+
usd: total.usd - nested.usd,
|
|
918
|
+
inputTokens: total.inputTokens - nested.inputTokens,
|
|
919
|
+
outputTokens: total.outputTokens - nested.outputTokens,
|
|
920
|
+
totalTokens: total.totalTokens - nested.totalTokens
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
function handleMetricsEvent(state: MetricsState, event: RunEvent): void {
|
|
925
|
+
const parentRunIds = (event as { readonly parentRunIds?: readonly string[] }).parentRunIds;
|
|
926
|
+
if (parentRunIds !== undefined) {
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
switch (event.type) {
|
|
931
|
+
case "agent-turn": {
|
|
932
|
+
state.totalCost = event.cost;
|
|
933
|
+
state.turns += 1;
|
|
934
|
+
break;
|
|
935
|
+
}
|
|
936
|
+
case "broadcast":
|
|
937
|
+
case "budget-stop":
|
|
938
|
+
case "final": {
|
|
939
|
+
state.totalCost = event.cost;
|
|
940
|
+
break;
|
|
941
|
+
}
|
|
942
|
+
case "sub-run-started": {
|
|
943
|
+
state.subRunStartTimes.set(event.childRunId, Date.now());
|
|
944
|
+
break;
|
|
945
|
+
}
|
|
946
|
+
case "sub-run-completed": {
|
|
947
|
+
state.totalCost = addCost(state.totalCost, event.subResult.cost);
|
|
948
|
+
state.nestedCost = addCost(state.nestedCost, event.subResult.cost);
|
|
949
|
+
const startMs = state.subRunStartTimes.get(event.childRunId);
|
|
950
|
+
const durationMs = startMs !== undefined ? Date.now() - startMs : 0;
|
|
951
|
+
state.subRunStartTimes.delete(event.childRunId);
|
|
952
|
+
const snapshot = buildSubRunSnapshot(event.subResult, durationMs);
|
|
953
|
+
fireHook(state.metricsHook.onSubRunComplete, snapshot, state.logger);
|
|
954
|
+
break;
|
|
955
|
+
}
|
|
956
|
+
case "sub-run-failed": {
|
|
957
|
+
state.totalCost = addCost(state.totalCost, event.partialCost);
|
|
958
|
+
state.nestedCost = addCost(state.nestedCost, event.partialCost);
|
|
959
|
+
state.subRunStartTimes.delete(event.childRunId);
|
|
960
|
+
break;
|
|
961
|
+
}
|
|
962
|
+
default:
|
|
963
|
+
break;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
function closeRunMetrics(state: MetricsState, result: RunResult | undefined): void {
|
|
968
|
+
if (result !== undefined) {
|
|
969
|
+
const snapshot = buildRunSnapshot(result, state.startedAtMs);
|
|
970
|
+
fireHook(state.metricsHook.onRunComplete, snapshot, state.logger);
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
const ownCost = subtractCost(state.totalCost, state.nestedCost);
|
|
975
|
+
const snapshot: RunMetricsSnapshot = {
|
|
976
|
+
outcome: "aborted",
|
|
977
|
+
inputTokens: ownCost.inputTokens,
|
|
978
|
+
outputTokens: ownCost.outputTokens,
|
|
979
|
+
costUsd: ownCost.usd,
|
|
980
|
+
totalInputTokens: state.totalCost.inputTokens,
|
|
981
|
+
totalOutputTokens: state.totalCost.outputTokens,
|
|
982
|
+
totalCostUsd: state.totalCost.usd,
|
|
983
|
+
turns: state.turns,
|
|
984
|
+
durationMs: Date.now() - state.startedAtMs
|
|
985
|
+
};
|
|
986
|
+
fireHook(state.metricsHook.onRunComplete, snapshot, state.logger);
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
function handleTracingEvent(state: TracingState, event: RunEvent): void {
|
|
990
|
+
const parentRunIds = (event as { readonly parentRunIds?: readonly string[] }).parentRunIds;
|
|
991
|
+
if (parentRunIds !== undefined) {
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
if (state.runId === undefined) {
|
|
996
|
+
state.runId = event.runId;
|
|
997
|
+
state.runSpan.setAttribute("dogpile.run.id", event.runId);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
switch (event.type) {
|
|
1001
|
+
case "model-request": {
|
|
1002
|
+
state.pendingModelRequests.set(event.callId, event);
|
|
1003
|
+
state.agentIds.add(event.agentId);
|
|
1004
|
+
|
|
1005
|
+
if (!state.agentTurnSpans.has(event.agentId)) {
|
|
1006
|
+
const turnNumber = (state.agentTurnCounters.get(event.agentId) ?? 0) + 1;
|
|
1007
|
+
state.agentTurnCounters.set(event.agentId, turnNumber);
|
|
1008
|
+
const turnParent = state.subRunSpans.get(event.runId) ?? state.runSpan;
|
|
1009
|
+
const turnSpan = state.tracer.startSpan(DOGPILE_SPAN_NAMES.AGENT_TURN, {
|
|
1010
|
+
parent: turnParent,
|
|
1011
|
+
attributes: {
|
|
1012
|
+
"dogpile.agent.id": event.agentId,
|
|
1013
|
+
"dogpile.agent.role": event.role,
|
|
1014
|
+
"dogpile.turn.number": turnNumber,
|
|
1015
|
+
"dogpile.model.id": event.modelId
|
|
1016
|
+
}
|
|
1017
|
+
});
|
|
1018
|
+
state.agentTurnSpans.set(event.agentId, turnSpan);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
const callParent =
|
|
1022
|
+
state.agentTurnSpans.get(event.agentId) ??
|
|
1023
|
+
state.subRunSpans.get(event.runId) ??
|
|
1024
|
+
state.runSpan;
|
|
1025
|
+
const callSpan = state.tracer.startSpan(DOGPILE_SPAN_NAMES.MODEL_CALL, {
|
|
1026
|
+
parent: callParent,
|
|
1027
|
+
attributes: {
|
|
1028
|
+
"dogpile.model.id": event.modelId,
|
|
1029
|
+
"dogpile.call.id": event.callId,
|
|
1030
|
+
"dogpile.provider.id": event.providerId
|
|
1031
|
+
}
|
|
1032
|
+
});
|
|
1033
|
+
state.modelCallSpans.set(event.callId, callSpan);
|
|
1034
|
+
break;
|
|
1035
|
+
}
|
|
1036
|
+
case "model-response": {
|
|
1037
|
+
const span = state.modelCallSpans.get(event.callId);
|
|
1038
|
+
if (span) {
|
|
1039
|
+
const inputTokens = event.response.usage?.inputTokens ?? 0;
|
|
1040
|
+
const outputTokens = event.response.usage?.outputTokens ?? 0;
|
|
1041
|
+
const responseCost: CostSummary = {
|
|
1042
|
+
usd: event.response.costUsd ?? 0,
|
|
1043
|
+
inputTokens,
|
|
1044
|
+
outputTokens,
|
|
1045
|
+
totalTokens: event.response.usage?.totalTokens ?? inputTokens + outputTokens
|
|
1046
|
+
};
|
|
1047
|
+
span.setAttribute("dogpile.model.input_tokens", inputTokens);
|
|
1048
|
+
span.setAttribute("dogpile.model.output_tokens", outputTokens);
|
|
1049
|
+
if (event.response.costUsd !== undefined) {
|
|
1050
|
+
span.setAttribute("dogpile.model.cost_usd", event.response.costUsd);
|
|
1051
|
+
}
|
|
1052
|
+
span.setStatus("ok");
|
|
1053
|
+
span.end();
|
|
1054
|
+
state.modelCallSpans.delete(event.callId);
|
|
1055
|
+
const accum = state.turnAccumByAgent.get(event.agentId) ?? {
|
|
1056
|
+
inputTokens: 0,
|
|
1057
|
+
outputTokens: 0,
|
|
1058
|
+
costUsd: 0
|
|
1059
|
+
};
|
|
1060
|
+
accum.inputTokens += inputTokens;
|
|
1061
|
+
accum.outputTokens += outputTokens;
|
|
1062
|
+
accum.costUsd += responseCost.usd;
|
|
1063
|
+
state.turnAccumByAgent.set(event.agentId, accum);
|
|
1064
|
+
state.lastCost = addCost(state.lastCost, responseCost);
|
|
1065
|
+
}
|
|
1066
|
+
state.pendingModelRequests.delete(event.callId);
|
|
1067
|
+
break;
|
|
1068
|
+
}
|
|
1069
|
+
case "agent-turn": {
|
|
1070
|
+
state.agentIds.add(event.agentId);
|
|
1071
|
+
state.turnCount += 1;
|
|
1072
|
+
state.lastCost = event.cost;
|
|
1073
|
+
const turnSpan = state.agentTurnSpans.get(event.agentId);
|
|
1074
|
+
if (turnSpan) {
|
|
1075
|
+
turnSpan.setAttribute("dogpile.agent.role", event.role);
|
|
1076
|
+
const accum = state.turnAccumByAgent.get(event.agentId);
|
|
1077
|
+
turnSpan.setAttribute("dogpile.turn.cost_usd", accum?.costUsd ?? 0);
|
|
1078
|
+
turnSpan.setAttribute("dogpile.turn.input_tokens", accum?.inputTokens ?? 0);
|
|
1079
|
+
turnSpan.setAttribute("dogpile.turn.output_tokens", accum?.outputTokens ?? 0);
|
|
1080
|
+
turnSpan.setStatus("ok");
|
|
1081
|
+
turnSpan.end();
|
|
1082
|
+
state.agentTurnSpans.delete(event.agentId);
|
|
1083
|
+
}
|
|
1084
|
+
state.turnAccumByAgent.delete(event.agentId);
|
|
1085
|
+
break;
|
|
1086
|
+
}
|
|
1087
|
+
case "broadcast":
|
|
1088
|
+
case "budget-stop":
|
|
1089
|
+
case "final": {
|
|
1090
|
+
state.lastCost = event.cost;
|
|
1091
|
+
break;
|
|
1092
|
+
}
|
|
1093
|
+
case "sub-run-started": {
|
|
1094
|
+
const span = state.tracer.startSpan(DOGPILE_SPAN_NAMES.SUB_RUN, {
|
|
1095
|
+
parent: state.runSpan,
|
|
1096
|
+
attributes: {
|
|
1097
|
+
"dogpile.sub_run.child_run_id": event.childRunId,
|
|
1098
|
+
"dogpile.sub_run.parent_run_id": event.parentRunId,
|
|
1099
|
+
"dogpile.sub_run.depth": event.depth
|
|
1100
|
+
}
|
|
1101
|
+
});
|
|
1102
|
+
state.subRunSpans.set(event.childRunId, span);
|
|
1103
|
+
break;
|
|
1104
|
+
}
|
|
1105
|
+
case "sub-run-completed": {
|
|
1106
|
+
const span = state.subRunSpans.get(event.childRunId);
|
|
1107
|
+
if (span) {
|
|
1108
|
+
span.setStatus("ok");
|
|
1109
|
+
span.end();
|
|
1110
|
+
state.subRunSpans.delete(event.childRunId);
|
|
1111
|
+
}
|
|
1112
|
+
break;
|
|
1113
|
+
}
|
|
1114
|
+
case "sub-run-failed": {
|
|
1115
|
+
const span = state.subRunSpans.get(event.childRunId);
|
|
1116
|
+
if (span) {
|
|
1117
|
+
span.setStatus("error", event.error.message);
|
|
1118
|
+
span.end();
|
|
1119
|
+
state.subRunSpans.delete(event.childRunId);
|
|
1120
|
+
}
|
|
1121
|
+
break;
|
|
1122
|
+
}
|
|
1123
|
+
default:
|
|
1124
|
+
break;
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
function closeRunTracing(state: TracingState, result: RunResult | undefined, error?: unknown): void {
|
|
1129
|
+
if (error !== undefined) {
|
|
1130
|
+
if (state.runId !== undefined) {
|
|
1131
|
+
state.runSpan.setAttribute("dogpile.run.id", state.runId);
|
|
1132
|
+
}
|
|
1133
|
+
state.runSpan.setAttribute("dogpile.run.agent_count", state.agentIds.size);
|
|
1134
|
+
state.runSpan.setAttribute("dogpile.run.turn_count", state.turnCount);
|
|
1135
|
+
state.runSpan.setAttribute("dogpile.run.cost_usd", state.lastCost.usd);
|
|
1136
|
+
state.runSpan.setAttribute("dogpile.run.input_tokens", state.lastCost.inputTokens);
|
|
1137
|
+
state.runSpan.setAttribute("dogpile.run.output_tokens", state.lastCost.outputTokens);
|
|
1138
|
+
state.runSpan.setAttribute("dogpile.run.outcome", "aborted");
|
|
1139
|
+
state.runSpan.setStatus("error", error instanceof Error ? error.message : String(error));
|
|
1140
|
+
closeOpenTracingSpans(state);
|
|
1141
|
+
state.runSpan.end();
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
if (result === undefined) {
|
|
1146
|
+
closeOpenTracingSpans(state);
|
|
1147
|
+
state.runSpan.end();
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
const budgetStopEvent = result.trace.events.find((event): event is BudgetStopEvent => event.type === "budget-stop");
|
|
1152
|
+
const terminationReason = budgetStopEvent?.reason;
|
|
1153
|
+
const outcome = terminationReason !== undefined ? "budget-stopped" : "completed";
|
|
1154
|
+
state.runSpan.setAttribute("dogpile.run.id", result.trace.runId);
|
|
1155
|
+
state.runSpan.setAttribute("dogpile.run.agent_count", result.trace.agentsUsed.length);
|
|
1156
|
+
state.runSpan.setAttribute("dogpile.run.turn_count", result.trace.events.filter((event) => event.type === "agent-turn").length);
|
|
1157
|
+
state.runSpan.setAttribute("dogpile.run.cost_usd", result.cost.usd);
|
|
1158
|
+
state.runSpan.setAttribute("dogpile.run.input_tokens", result.cost.inputTokens);
|
|
1159
|
+
state.runSpan.setAttribute("dogpile.run.output_tokens", result.cost.outputTokens);
|
|
1160
|
+
state.runSpan.setAttribute("dogpile.run.outcome", outcome);
|
|
1161
|
+
if (terminationReason !== undefined) {
|
|
1162
|
+
state.runSpan.setAttribute("dogpile.run.termination_reason", terminationReason);
|
|
1163
|
+
}
|
|
1164
|
+
state.runSpan.setStatus("ok");
|
|
1165
|
+
closeOpenTracingSpans(state);
|
|
1166
|
+
state.runSpan.end();
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
function closeOpenTracingSpans(state: TracingState): void {
|
|
1170
|
+
for (const span of state.modelCallSpans.values()) {
|
|
1171
|
+
span.end();
|
|
1172
|
+
}
|
|
1173
|
+
state.modelCallSpans.clear();
|
|
1174
|
+
for (const span of state.agentTurnSpans.values()) {
|
|
1175
|
+
span.end();
|
|
1176
|
+
}
|
|
1177
|
+
state.agentTurnSpans.clear();
|
|
1178
|
+
for (const span of state.subRunSpans.values()) {
|
|
1179
|
+
span.end();
|
|
1180
|
+
}
|
|
1181
|
+
state.subRunSpans.clear();
|
|
1182
|
+
}
|
|
1183
|
+
|
|
689
1184
|
async function runNonStreamingProtocol(options: NonStreamingProtocolOptions): Promise<RunResult> {
|
|
690
1185
|
const failureInstancesByChildRunId = new Map<string, DogpileError>();
|
|
691
1186
|
const abortLifecycle = createNonStreamingAbortLifecycle({
|
|
@@ -728,7 +1223,8 @@ async function runNonStreamingProtocol(options: NonStreamingProtocolOptions): Pr
|
|
|
728
1223
|
events
|
|
729
1224
|
}),
|
|
730
1225
|
eventLog: createRunEventLog(trace.runId, trace.protocol, events),
|
|
731
|
-
trace
|
|
1226
|
+
trace,
|
|
1227
|
+
health: computeHealth(trace, DEFAULT_HEALTH_THRESHOLDS)
|
|
732
1228
|
};
|
|
733
1229
|
const terminalThrow = resolveRuntimeTerminalThrow(runResult.trace, failureInstancesByChildRunId);
|
|
734
1230
|
if (terminalThrow) {
|
|
@@ -781,7 +1277,61 @@ function finalEventWithEvaluation(event: FinalEvent, evaluation: RunEvaluation):
|
|
|
781
1277
|
};
|
|
782
1278
|
}
|
|
783
1279
|
|
|
784
|
-
function runProtocol(options: RunProtocolOptions): Promise<RunResult> {
|
|
1280
|
+
async function runProtocol(options: RunProtocolOptions): Promise<RunResult> {
|
|
1281
|
+
const tracing = openRunTracing({
|
|
1282
|
+
...(options.tracer ? { tracer: options.tracer } : {}),
|
|
1283
|
+
...(options.parentSpan ? { parentSpan: options.parentSpan } : {}),
|
|
1284
|
+
intent: options.intent,
|
|
1285
|
+
protocolKind: options.protocol.kind,
|
|
1286
|
+
tier: options.tier
|
|
1287
|
+
});
|
|
1288
|
+
const metrics = openRunMetrics({
|
|
1289
|
+
...(options.metricsHook ? { metricsHook: options.metricsHook } : {}),
|
|
1290
|
+
...(options.logger ? { logger: options.logger } : {})
|
|
1291
|
+
});
|
|
1292
|
+
const emitForProtocol =
|
|
1293
|
+
tracing || metrics || options.emit
|
|
1294
|
+
? (event: RunEvent): void => {
|
|
1295
|
+
if (tracing) {
|
|
1296
|
+
handleTracingEvent(tracing, event);
|
|
1297
|
+
}
|
|
1298
|
+
if (metrics) {
|
|
1299
|
+
handleMetricsEvent(metrics, event);
|
|
1300
|
+
}
|
|
1301
|
+
options.emit?.(event);
|
|
1302
|
+
}
|
|
1303
|
+
: undefined;
|
|
1304
|
+
const protocolOptions = tracing
|
|
1305
|
+
? {
|
|
1306
|
+
...options,
|
|
1307
|
+
subRunSpansByChildId: tracing.subRunSpans
|
|
1308
|
+
}
|
|
1309
|
+
: options;
|
|
1310
|
+
|
|
1311
|
+
try {
|
|
1312
|
+
const result = await runProtocolInner(protocolOptions, emitForProtocol);
|
|
1313
|
+
if (tracing) {
|
|
1314
|
+
closeRunTracing(tracing, result);
|
|
1315
|
+
}
|
|
1316
|
+
if (metrics && (options.currentDepth === 0 || options.currentDepth === undefined)) {
|
|
1317
|
+
closeRunMetrics(metrics, result);
|
|
1318
|
+
}
|
|
1319
|
+
return result;
|
|
1320
|
+
} catch (error) {
|
|
1321
|
+
if (tracing) {
|
|
1322
|
+
closeRunTracing(tracing, undefined, error);
|
|
1323
|
+
}
|
|
1324
|
+
if (metrics && (options.currentDepth === 0 || options.currentDepth === undefined)) {
|
|
1325
|
+
closeRunMetrics(metrics, undefined);
|
|
1326
|
+
}
|
|
1327
|
+
throw error;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
function runProtocolInner(
|
|
1332
|
+
options: RunProtocolOptions,
|
|
1333
|
+
emitForProtocol?: (event: RunEvent) => void
|
|
1334
|
+
): Promise<RunResult> {
|
|
785
1335
|
switch (options.protocol.kind) {
|
|
786
1336
|
case "sequential":
|
|
787
1337
|
return runSequential({
|
|
@@ -797,7 +1347,7 @@ function runProtocol(options: RunProtocolOptions): Promise<RunResult> {
|
|
|
797
1347
|
...(options.signal !== undefined ? { signal: options.signal } : {}),
|
|
798
1348
|
...(options.terminate ? { terminate: options.terminate } : {}),
|
|
799
1349
|
...(options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {}),
|
|
800
|
-
...(
|
|
1350
|
+
...(emitForProtocol ? { emit: emitForProtocol } : {})
|
|
801
1351
|
});
|
|
802
1352
|
case "broadcast":
|
|
803
1353
|
return runBroadcast({
|
|
@@ -813,7 +1363,7 @@ function runProtocol(options: RunProtocolOptions): Promise<RunResult> {
|
|
|
813
1363
|
...(options.signal !== undefined ? { signal: options.signal } : {}),
|
|
814
1364
|
...(options.terminate ? { terminate: options.terminate } : {}),
|
|
815
1365
|
...(options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {}),
|
|
816
|
-
...(
|
|
1366
|
+
...(emitForProtocol ? { emit: emitForProtocol } : {})
|
|
817
1367
|
});
|
|
818
1368
|
case "coordinator":
|
|
819
1369
|
return runCoordinator({
|
|
@@ -829,7 +1379,7 @@ function runProtocol(options: RunProtocolOptions): Promise<RunResult> {
|
|
|
829
1379
|
...(options.signal !== undefined ? { signal: options.signal } : {}),
|
|
830
1380
|
...(options.terminate ? { terminate: options.terminate } : {}),
|
|
831
1381
|
...(options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {}),
|
|
832
|
-
...(
|
|
1382
|
+
...(emitForProtocol ? { emit: emitForProtocol } : {}),
|
|
833
1383
|
...(options.streamEvents !== undefined ? { streamEvents: options.streamEvents } : {}),
|
|
834
1384
|
currentDepth: options.currentDepth ?? 0,
|
|
835
1385
|
effectiveMaxDepth: options.effectiveMaxDepth ?? Infinity,
|
|
@@ -843,11 +1393,17 @@ function runProtocol(options: RunProtocolOptions): Promise<RunResult> {
|
|
|
843
1393
|
...(options.failureInstancesByChildRunId !== undefined
|
|
844
1394
|
? { failureInstancesByChildRunId: options.failureInstancesByChildRunId }
|
|
845
1395
|
: {}),
|
|
846
|
-
runProtocol: (childInput) =>
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
1396
|
+
runProtocol: (childInput) => {
|
|
1397
|
+
const { runId: childRunId, ...childProtocolInput } = childInput;
|
|
1398
|
+
const childParent = options.subRunSpansByChildId?.get(childRunId) ?? options.parentSpan;
|
|
1399
|
+
return runProtocol({
|
|
1400
|
+
...childProtocolInput,
|
|
1401
|
+
protocol: normalizeProtocol(childProtocolInput.protocol),
|
|
1402
|
+
...(options.tracer ? { tracer: options.tracer } : {}),
|
|
1403
|
+
...(childParent ? { parentSpan: childParent } : {}),
|
|
1404
|
+
...(options.logger ? { logger: options.logger } : {})
|
|
1405
|
+
});
|
|
1406
|
+
}
|
|
851
1407
|
});
|
|
852
1408
|
case "shared":
|
|
853
1409
|
return runShared({
|
|
@@ -863,7 +1419,7 @@ function runProtocol(options: RunProtocolOptions): Promise<RunResult> {
|
|
|
863
1419
|
...(options.signal !== undefined ? { signal: options.signal } : {}),
|
|
864
1420
|
...(options.terminate ? { terminate: options.terminate } : {}),
|
|
865
1421
|
...(options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {}),
|
|
866
|
-
...(
|
|
1422
|
+
...(emitForProtocol ? { emit: emitForProtocol } : {})
|
|
867
1423
|
});
|
|
868
1424
|
}
|
|
869
1425
|
}
|
|
@@ -916,7 +1472,14 @@ export function stream(options: DogpileOptions): StreamHandle {
|
|
|
916
1472
|
* the ergonomic {@link RunResult} wrapper from the JSON-serializable
|
|
917
1473
|
* {@link Trace} returned by a previous `run()`, `stream()`, or
|
|
918
1474
|
* `Dogpile.pile()` call.
|
|
1475
|
+
*
|
|
1476
|
+
* Tracing and metrics: replay is intentionally tracing-free and metrics-free.
|
|
1477
|
+
* Even when an engine instance has been configured with a `tracer` or
|
|
1478
|
+
* `metricsHook` on its `EngineOptions`, calling this function emits no spans
|
|
1479
|
+
* or callbacks — replaying historical events with current timestamps would
|
|
1480
|
+
* confuse observability backends. See `docs/developer-usage.md`.
|
|
919
1481
|
*/
|
|
1482
|
+
// Tracing/metrics-free: replay never uses EngineOptions tracer or metricsHook.
|
|
920
1483
|
export function replay(trace: Trace): RunResult {
|
|
921
1484
|
const cost = trace.finalOutput.cost;
|
|
922
1485
|
const lastEvent = trace.events.at(-1);
|
|
@@ -931,7 +1494,11 @@ export function replay(trace: Trace): RunResult {
|
|
|
931
1494
|
}
|
|
932
1495
|
const baseResult = {
|
|
933
1496
|
output: trace.finalOutput.output,
|
|
934
|
-
eventLog: createRunEventLog(
|
|
1497
|
+
eventLog: createRunEventLog(
|
|
1498
|
+
trace.runId,
|
|
1499
|
+
trace.protocol,
|
|
1500
|
+
synthesizeProviderEvents(trace, trace.providerCalls)
|
|
1501
|
+
),
|
|
935
1502
|
trace,
|
|
936
1503
|
transcript: trace.transcript,
|
|
937
1504
|
usage: createRunUsage(cost),
|
|
@@ -944,7 +1511,8 @@ export function replay(trace: Trace): RunResult {
|
|
|
944
1511
|
events: trace.events
|
|
945
1512
|
}),
|
|
946
1513
|
accounting,
|
|
947
|
-
cost
|
|
1514
|
+
cost,
|
|
1515
|
+
health: computeHealth(trace, DEFAULT_HEALTH_THRESHOLDS)
|
|
948
1516
|
};
|
|
949
1517
|
|
|
950
1518
|
if (lastEvent?.type !== "final") {
|
|
@@ -958,6 +1526,60 @@ export function replay(trace: Trace): RunResult {
|
|
|
958
1526
|
};
|
|
959
1527
|
}
|
|
960
1528
|
|
|
1529
|
+
function synthesizeProviderEvents(
|
|
1530
|
+
trace: Trace,
|
|
1531
|
+
providerCalls: readonly ReplayTraceProviderCall[]
|
|
1532
|
+
): readonly RunEvent[] {
|
|
1533
|
+
const hasLiveProvenance = trace.events.some(
|
|
1534
|
+
(event) => event.type === "model-request" || event.type === "model-response"
|
|
1535
|
+
);
|
|
1536
|
+
if (hasLiveProvenance) {
|
|
1537
|
+
return trace.events;
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
const baseEvents = trace.events.filter(
|
|
1541
|
+
(event) => event.type !== "model-request" && event.type !== "model-response"
|
|
1542
|
+
);
|
|
1543
|
+
const result: RunEvent[] = [];
|
|
1544
|
+
let turnCount = 0;
|
|
1545
|
+
|
|
1546
|
+
for (const event of baseEvents) {
|
|
1547
|
+
if (event.type === "agent-turn") {
|
|
1548
|
+
const call = providerCalls[turnCount];
|
|
1549
|
+
if (call !== undefined) {
|
|
1550
|
+
const modelId = typeof call.modelId === "string" && call.modelId.length > 0 ? call.modelId : call.providerId;
|
|
1551
|
+
result.push({
|
|
1552
|
+
type: "model-request",
|
|
1553
|
+
runId: trace.runId,
|
|
1554
|
+
callId: call.callId,
|
|
1555
|
+
providerId: call.providerId,
|
|
1556
|
+
modelId,
|
|
1557
|
+
startedAt: call.startedAt,
|
|
1558
|
+
agentId: call.agentId,
|
|
1559
|
+
role: call.role,
|
|
1560
|
+
request: call.request
|
|
1561
|
+
});
|
|
1562
|
+
result.push({
|
|
1563
|
+
type: "model-response",
|
|
1564
|
+
runId: trace.runId,
|
|
1565
|
+
callId: call.callId,
|
|
1566
|
+
providerId: call.providerId,
|
|
1567
|
+
modelId,
|
|
1568
|
+
startedAt: call.startedAt,
|
|
1569
|
+
completedAt: call.completedAt,
|
|
1570
|
+
agentId: call.agentId,
|
|
1571
|
+
role: call.role,
|
|
1572
|
+
response: call.response
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
turnCount += 1;
|
|
1576
|
+
}
|
|
1577
|
+
result.push(event);
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
return result;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
961
1583
|
function resolveRuntimeTerminalThrow(
|
|
962
1584
|
trace: Trace,
|
|
963
1585
|
failureInstancesByChildRunId: ReadonlyMap<string, DogpileError>
|
|
@@ -1060,11 +1682,19 @@ function dogpileErrorFromSerializedPayload(input: {
|
|
|
1060
1682
|
* Replay a saved completed trace as a stream without invoking a model provider.
|
|
1061
1683
|
*
|
|
1062
1684
|
* @remarks
|
|
1063
|
-
* This is the streaming counterpart to {@link replay}. It yields the
|
|
1064
|
-
*
|
|
1065
|
-
*
|
|
1066
|
-
* replay remains storage-free and
|
|
1685
|
+
* This is the streaming counterpart to {@link replay}. It yields the same
|
|
1686
|
+
* event sequence exposed by the replayed result event log, including legacy
|
|
1687
|
+
* provenance synthesis when a saved trace predates model request/response
|
|
1688
|
+
* events. Since all data comes from the trace, replay remains storage-free and
|
|
1689
|
+
* provider-free.
|
|
1690
|
+
*
|
|
1691
|
+
* Tracing and metrics: replayStream is intentionally tracing-free and
|
|
1692
|
+
* metrics-free. Even when an engine instance has been configured with a
|
|
1693
|
+
* `tracer` or `metricsHook` on its `EngineOptions`, calling this function
|
|
1694
|
+
* emits no spans or callbacks — replaying historical events with current
|
|
1695
|
+
* timestamps would confuse observability backends. See `docs/developer-usage.md`.
|
|
1067
1696
|
*/
|
|
1697
|
+
// Tracing/metrics-free: replayStream never uses EngineOptions tracer or metricsHook.
|
|
1068
1698
|
export function replayStream(trace: Trace): StreamHandle {
|
|
1069
1699
|
const result = Promise.resolve(replay(trace));
|
|
1070
1700
|
const replayEvents = replayStreamEvents(trace);
|
|
@@ -1109,7 +1739,7 @@ export function replayStream(trace: Trace): StreamHandle {
|
|
|
1109
1739
|
function replayStreamEvents(trace: Trace, parentRunIds: readonly string[] = []): StreamEvent[] {
|
|
1110
1740
|
const events: StreamEvent[] = [];
|
|
1111
1741
|
|
|
1112
|
-
for (const event of trace.
|
|
1742
|
+
for (const event of synthesizeProviderEvents(trace, trace.providerCalls)) {
|
|
1113
1743
|
if (event.type === "sub-run-completed") {
|
|
1114
1744
|
events.push(...replayStreamEvents(event.subResult.trace, [...parentRunIds, trace.runId]));
|
|
1115
1745
|
}
|