diff options
| author | Adam Malczewski <[email protected]> | 2026-06-07 17:46:53 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-07 17:46:53 +0900 |
| commit | 7c459c7d919d1e08a228e8abc56129be174d8abe (patch) | |
| tree | 93011125c001945723ac9b9358c4ddd450f87f72 /packages/kernel/src/runtime/events.ts | |
| parent | 5746cf4e545cd5b0d7faf0595554f273f236f3a9 (diff) | |
| download | dispatch-7c459c7d919d1e08a228e8abc56129be174d8abe.tar.gz dispatch-7c459c7d919d1e08a228e8abc56129be174d8abe.zip | |
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.
- [email protected]: 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?.
[email protected] (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).
Diffstat (limited to 'packages/kernel/src/runtime/events.ts')
| -rw-r--r-- | packages/kernel/src/runtime/events.ts | 80 |
1 files changed, 76 insertions, 4 deletions
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 }; } |
