From 7c459c7d919d1e08a228e8abc56129be174d8abe Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Sun, 7 Jun 2026 17:46:53 +0900 Subject: feat(wire,kernel,session-orchestrator): live turn metrics on the stream Expose the backend's authoritative token+timing metrics on the live AgentEvent stream (observability-only -> now also client-facing). All additive/optional. - wire@0.3.0: new TurnStepCompleteEvent (type:step-complete) with per-step ttftMs/decodeMs/genTotalMs; usage += stepId; tool-result += durationMs (exec); done += durationMs (turn wall-clock) + usage (turn total). RunTurnInput += now?. transport-contract@0.3.0 (re-export bump). - kernel-runtime: when now injected, measures + emits the above (reuses the ttft/decode first-token detection); omits timing gracefully without a clock. - session-orchestrator: adds now? to deps, threads into RunTurnInput; extension activate injects () => Date.now(). - transport/cli/host-bin: untouched (verbatim pass-through; additive fields). FE handoff: frontend-metrics-handoff.md. typecheck clean; 520 vitest + 89 bun; biome 0/0. Replay/persistence = deferred Pass 2 (documented in tasks.md). --- packages/kernel/src/runtime/events.ts | 80 +++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 4 deletions(-) (limited to 'packages/kernel/src/runtime/events.ts') diff --git a/packages/kernel/src/runtime/events.ts b/packages/kernel/src/runtime/events.ts index deeb012..300e711 100644 --- a/packages/kernel/src/runtime/events.ts +++ b/packages/kernel/src/runtime/events.ts @@ -33,9 +33,10 @@ export function toolResultEvent( toolName: string, content: string, isError: boolean, + durationMs?: number, ): AgentEvent { - return { - type: "tool-result", + const base = { + type: "tool-result" as const, conversationId, turnId, stepId, @@ -44,6 +45,10 @@ export function toolResultEvent( content, isError, }; + if (durationMs !== undefined) { + return { ...base, durationMs }; + } + return base; } export function toolOutputEvent( @@ -56,7 +61,15 @@ export function toolOutputEvent( return { type: "tool-output", conversationId, turnId, toolCallId, data, stream }; } -export function usageEvent(conversationId: string, turnId: string, usage: Usage): AgentEvent { +export function usageEvent( + conversationId: string, + turnId: string, + usage: Usage, + stepId?: StepId, +): AgentEvent { + if (stepId !== undefined) { + return { type: "usage", conversationId, turnId, usage, stepId }; + } return { type: "usage", conversationId, turnId, usage }; } @@ -64,7 +77,66 @@ export function turnStartEvent(conversationId: string, turnId: string): AgentEve return { type: "turn-start", conversationId, turnId }; } -export function doneEvent(conversationId: string, turnId: string, reason: string): AgentEvent { +export function stepCompleteEvent( + conversationId: string, + turnId: string, + stepId: StepId, + timing?: { ttftMs?: number; decodeMs?: number; genTotalMs?: number }, +): AgentEvent { + if (timing !== undefined) { + if (timing.ttftMs !== undefined) { + if (timing.decodeMs !== undefined && timing.genTotalMs !== undefined) { + return { + type: "step-complete", + conversationId, + turnId, + stepId, + ttftMs: timing.ttftMs, + decodeMs: timing.decodeMs, + genTotalMs: timing.genTotalMs, + }; + } + if (timing.genTotalMs !== undefined) { + return { + type: "step-complete", + conversationId, + turnId, + stepId, + ttftMs: timing.ttftMs, + genTotalMs: timing.genTotalMs, + }; + } + return { type: "step-complete", conversationId, turnId, stepId, ttftMs: timing.ttftMs }; + } + if (timing.genTotalMs !== undefined) { + return { + type: "step-complete", + conversationId, + turnId, + stepId, + genTotalMs: timing.genTotalMs, + }; + } + } + return { type: "step-complete", conversationId, turnId, stepId }; +} + +export function doneEvent( + conversationId: string, + turnId: string, + reason: string, + durationMs?: number, + usage?: Usage, +): AgentEvent { + if (durationMs !== undefined && usage !== undefined) { + return { type: "done", conversationId, turnId, reason, durationMs, usage }; + } + if (durationMs !== undefined) { + return { type: "done", conversationId, turnId, reason, durationMs }; + } + if (usage !== undefined) { + return { type: "done", conversationId, turnId, reason, usage }; + } return { type: "done", conversationId, turnId, reason }; } -- cgit v1.2.3