1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
|
import type { AgentEvent, StepId, Usage } from "@dispatch/wire";
import type { StepMetrics, TelemetryState, TurnMetrics } from "./types";
/** The initial empty telemetry state. */
export function initialState(): TelemetryState {
return { turns: new Map() };
}
function mergeStep(existing: StepMetrics, patch: StepMetrics): StepMetrics {
const merged: StepMetrics = { ...existing };
if (patch.ttftMs !== undefined) (merged as { ttftMs?: number }).ttftMs = patch.ttftMs;
if (patch.decodeMs !== undefined) (merged as { decodeMs?: number }).decodeMs = patch.decodeMs;
if (patch.genTotalMs !== undefined)
(merged as { genTotalMs?: number }).genTotalMs = patch.genTotalMs;
if (patch.usage !== undefined) {
(merged as { usage?: Usage }).usage = { ...existing.usage, ...patch.usage };
}
if (patch.toolDurationMs !== undefined) {
(merged as { toolDurationMs?: number }).toolDurationMs =
(existing.toolDurationMs ?? 0) + patch.toolDurationMs;
}
return merged;
}
function upsertStep(
steps: readonly StepMetrics[],
stepId: StepId,
patch: StepMetrics,
): readonly StepMetrics[] {
const idx = steps.findIndex((s) => s.stepId === stepId);
if (idx === -1) {
return [...steps, patch];
}
return [...steps.slice(0, idx), mergeStep(steps[idx]!, patch), ...steps.slice(idx + 1)];
}
function setTurn(
turns: ReadonlyMap<string, TurnMetrics>,
turnId: string,
turn: TurnMetrics,
): ReadonlyMap<string, TurnMetrics> {
const next = new Map(turns);
next.set(turnId, turn);
return next;
}
/**
* Fold one live AgentEvent into the telemetry state.
*
* - `turn-start` records the active turnId.
* - `step-complete` creates/updates the step's timing metrics.
* - `usage` merges token counts into the step (joined by `stepId`).
* - `tool-result` accumulates `durationMs` into the step.
* - `done` records turn-level wall-clock + token totals.
* - All other event types are no-ops (content events belong to the transcript).
*
* Pure: input → output, no DOM, no side effects.
*/
export function foldMetricEvent(state: TelemetryState, event: AgentEvent): TelemetryState {
switch (event.type) {
case "turn-start": {
return {
...state,
turns: setTurn(state.turns, event.turnId, { steps: [] }),
};
}
case "step-complete": {
const turnId = event.turnId;
const existing = state.turns.get(turnId);
const patch: StepMetrics = { stepId: event.stepId };
if (event.ttftMs !== undefined) (patch as { ttftMs?: number }).ttftMs = event.ttftMs;
if (event.decodeMs !== undefined) (patch as { decodeMs?: number }).decodeMs = event.decodeMs;
if (event.genTotalMs !== undefined)
(patch as { genTotalMs?: number }).genTotalMs = event.genTotalMs;
const steps =
existing !== undefined ? upsertStep(existing.steps, event.stepId, patch) : [patch];
return {
...state,
turns: setTurn(state.turns, turnId, { ...existing, steps } as TurnMetrics),
};
}
case "usage": {
if (event.stepId === undefined) return state;
const turnId = event.turnId;
const existing = state.turns.get(turnId);
const patch: StepMetrics = { stepId: event.stepId, usage: event.usage };
const steps =
existing !== undefined ? upsertStep(existing.steps, event.stepId, patch) : [patch];
return {
...state,
turns: setTurn(state.turns, turnId, { ...existing, steps } as TurnMetrics),
};
}
case "tool-result": {
if (event.durationMs === undefined) return state;
const turnId = event.turnId;
const existing = state.turns.get(turnId);
if (existing === undefined) return state;
const patch: StepMetrics = { stepId: event.stepId, toolDurationMs: event.durationMs };
const steps = upsertStep(existing.steps, event.stepId, patch);
return { ...state, turns: setTurn(state.turns, turnId, { ...existing, steps }) };
}
case "done": {
const turnId = event.turnId;
const existing = state.turns.get(turnId);
const updated: TurnMetrics = {
...(existing ?? { steps: [] }),
};
if (event.durationMs !== undefined)
(updated as { wallMs?: number }).wallMs = event.durationMs;
if (event.usage !== undefined) (updated as { doneUsage?: Usage }).doneUsage = event.usage;
return { ...state, turns: setTurn(state.turns, turnId, updated) };
}
default:
return state;
}
}
|