summaryrefslogtreecommitdiffhomepage
path: root/packages/kernel/src/runtime/events.ts
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-07 17:46:53 +0900
committerAdam Malczewski <[email protected]>2026-06-07 17:46:53 +0900
commit7c459c7d919d1e08a228e8abc56129be174d8abe (patch)
tree93011125c001945723ac9b9358c4ddd450f87f72 /packages/kernel/src/runtime/events.ts
parent5746cf4e545cd5b0d7faf0595554f273f236f3a9 (diff)
downloaddispatch-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.ts80
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 };
}