/** * Engine output and streaming event types. * * Defines all events yielded by engine adapters during query execution. * The stream-manager routes each event type: * - Messages → save to DB + emit to WebSocket * - Stream events → forward to frontend for live typing * - System/result events → extract metadata, emit notifications */ import type { TokenUsage } from './common'; import type { UserMessage, AssistantMessage, ReasoningMessage, CompactBoundaryMessage, UnifiedMessage, MessageEngine, MessageSender, } from './message'; // ============================================================ // Stream Events (transient, not persisted) // ============================================================ export interface TextDeltaEvent { type: 'stream_event'; event: 'delta'; sessionId: string; text: string; reasoning: boolean; } export interface StreamLifecycleEvent { type: 'stream_event'; event: 'start' | 'stop'; sessionId: string; reasoning: boolean; } export type StreamEvent = | TextDeltaEvent | StreamLifecycleEvent; // ============================================================ // Result Events (transient, completion info) // ============================================================ export interface SuccessResultEvent { type: 'result'; subtype: 'success'; sessionId: string; numTurns: number; totalCostUsd: number; usage: TokenUsage; stopReason: string | null; } export interface ErrorResultEvent { type: 'result'; subtype: 'error_max_turns' | 'error_during_execution' | 'error_max_budget'; sessionId: string; errors: string[]; } export type ResultEvent = | SuccessResultEvent | ErrorResultEvent; // ============================================================ // System Events (transient) // ============================================================ export interface SystemInitEvent { type: 'system_init'; sessionId: string; model: string; engine: string; tools: string[]; mcpServers: McpServerStatus[]; } export interface McpServerStatus { name: string; status: 'connected' | 'disconnected' | 'error'; } export type RateLimitType = | 'five_hour' | 'seven_day' | 'seven_day_opus' | 'seven_day_sonnet' | 'overage' | 'seven_day_overage_included'; export interface RateLimitEvent { type: 'rate_limit'; sessionId: string; status: 'rejected' | 'allowed_warning'; utilization: number; resetsAt: number | null; rateLimitType: RateLimitType | null; } /** * Transient, non-persisted notification surfaced as a toast; the stream-manager * routes it to a `chat:notification`. * * Reserved for things the transcript alone cannot explain: a turn that produced * no output (Pi, Cline), an answer that came from a fallback model after a * refusal (Claude), a request cancelled because the account's credit limit ran * out (Copilot). Routine progress does NOT belong here — backgrounded task * outcomes already land in the transcript, and toasting them was pure noise. */ export interface NotificationEvent { type: 'notification'; sessionId: string; level: 'info' | 'warning' | 'error'; title: string; message: string; } // ============================================================ // Engine Output // ============================================================ /** * Union of all events yielded by engine adapters via AsyncGenerator. * Stream-manager discriminates on `type` to route each event. */ export type EngineOutput = | UserMessage | AssistantMessage | ReasoningMessage | CompactBoundaryMessage | StreamEvent | ResultEvent | SystemInitEvent | RateLimitEvent | NotificationEvent; // ============================================================ // Stream Transport (WebSocket layer) // ============================================================ export type StreamEventType = | 'connection' | 'message' | 'partial' | 'notification' | 'complete' | 'error' | 'cancelled'; export type StreamStatus = 'active' | 'completed' | 'error' | 'cancelled'; export interface StreamNotification { type: 'info' | 'warning' | 'error'; title: string; message: string; } export interface PartialMessageData { processId: string; eventType: 'start' | 'update' | 'end'; partialText: string; deltaText: string; reasoning: boolean; timestamp: string; } export interface MessageTransportData { processId: string; message: UnifiedMessage; usage?: TokenUsage; } export interface StreamRequest { projectPath: string; projectId: string; chatSessionId: string; prompt: UserMessage; engine: MessageEngine; sender: MessageSender; /** * Server-trusted id of the user who started the stream (from the WS * connection, never the client-supplied `sender`). Used to route Web Push * notifications to the requester's devices when Chrome is closed. * Absent for streams started before this field existed. */ requestedByUserId?: string; /** * Reasoning/thinking level token chosen for this session (native per engine). * `null`/absent → the engine's own default applies. Persisted to * `chat_sessions.reasoning_effort` like engine/model. */ reasoningEffort?: string | null; /** * Active Profile chosen for this session (bundle of Skills/Commands/Subagents/ * MCP/Permissions). `null` = explicit "no profile"; `undefined` = client didn't * send one → the stream falls back to the project's default profile. Persisted * to `chat_sessions.profile_id` like engine/model. */ profileId?: number | null; }