/** * SSE stream events — the canonical wire shape consumed by the reducer. * * Backend-specific shapes (e.g. pydantic-AI's `text_delta`) get normalized * into this union by `mapPydanticAIEvent` and friends. */ import type { ChatMessage } from './message'; /** * Per-run metrics block. Emitted once per assistant turn, carried into the * message either by a non-terminal `message_metrics` event mid-run or via * the terminal `message_end` event's generic `patch` slot. * * All fields optional — the backend omits anything it could not measure. */ export interface ChatMessageMetrics { inputTokens?: number; outputTokens?: number; totalTokens?: number; cacheReadTokens?: number; cacheWriteTokens?: number; processingTimeMs?: number; firstTokenMs?: number; turns?: number; toolCallCount?: number; toolNames?: string[]; /** Model the request was sent with (may be an alias). */ model?: string; /** Concrete model the alias resolved to. */ resolvedModel?: string; /** Short summary of the model's thinking, if available. */ thinkingSummary?: string; } export type ChatStreamEvent = | { type: 'message_start'; messageId: string; sessionId: string } | { type: 'resume_start' } | { type: 'chunk'; delta: string } | { type: 'tool_activity'; tool: string; status: string } | { type: 'tool_call_start'; toolId: string; name: string; input: unknown; sourceHostname?: string; } | { type: 'tool_call_delta'; toolId: string; delta: string } | { type: 'tool_call_end'; toolId: string; output: unknown; status: 'success' | 'error'; } | { type: 'message_end'; /** * Generic finalize patch merged into the message on done — the ONE * slot every finalize feature rides (tokens, sources, blocks, OCR * text, attachments-finalized, local-preview metadata). `content` * here REPLACES the accumulated bubble body (the append-only gap). * The reducer applies it as a `Partial` merge — the same * mechanism `message_metrics`/`resolved_model` already use. Absent → * `message_end` is a pure terminal no-op (just clears `isStreaming`). */ patch?: Partial; } | { /** * Per-turn metrics for the assistant message currently streaming. * Non-terminal — arrives near the end of the run but does not * close the stream (`message_end` / `error` do that). */ type: 'message_metrics'; metrics: ChatMessageMetrics; } | { /** * One-shot model-alias resolution (e.g. `@code` → `glm-5.1`). * Non-terminal — lets the UI update the model chip mid-run. */ type: 'resolved_model'; /** Alias the user/request originally specified. */ originalAlias: string; /** Concrete model id the alias resolved to. */ resolvedModel: string; /** True when routing upgraded the model (e.g. for a larger context). */ upgraded?: boolean; /** Human-readable reason the router picked this model. */ routingReason?: string; } | { type: 'error'; code: string; message: string };