//! The event contract shared by session drivers, transcripts, and clients. use serde::{Deserialize, Serialize}; pub type ImageRef = String; pub type AttachmentRef = String; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct QuestionOption { pub label: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub description: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub preview: Option, } impl QuestionOption { pub fn plain(label: impl Into) -> Self { Self { label: label.into(), description: None, preview: None, } } } /// Everything a session can append to its transcript. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] // `rename_all` does not rename fields inside enum variants. #[serde( tag = "type", rename_all = "camelCase", rename_all_fields = "camelCase" )] pub enum Event { UserMessage { /// The queued message this resolves, if any. #[serde(default, skip_serializing_if = "Option::is_none")] id: Option, text: String, #[serde(default, alias = "images", skip_serializing_if = "Vec::is_empty")] attachments: Vec, }, MessageQueued { id: String, text: String, #[serde(default, alias = "images", skip_serializing_if = "Vec::is_empty")] attachments: Vec, }, MessageDropped { id: String, }, /// Driver-internal acknowledgement; the manager records `UserMessage`. MessageTaken { id: Option, text: String, #[serde(default, alias = "images", skip_serializing_if = "Vec::is_empty")] attachments: Vec, }, AssistantText { delta: String, }, ToolStart { id: String, tool: String, input: serde_json::Value, }, ToolUpdate { id: String, output: String, }, ToolEnd { id: String, output: String, #[serde(default)] is_error: bool, }, Image { #[serde(rename = "ref")] image: ImageRef, /// The tool call whose result carried the image. #[serde(default, skip_serializing_if = "Option::is_none")] about: Option, }, Question { id: String, prompt: String, header: Option, options: Vec, #[serde(default, skip_serializing_if = "std::ops::Not::not")] multi_select: bool, /// The related tool call, for permission questions. #[serde(default, skip_serializing_if = "Option::is_none")] about: Option, }, PeerMessage { from: String, text: String, /// Seq of the `Running` event that opened the turn it belongs above. #[serde(default, skip_serializing_if = "Option::is_none")] turn_start: Option, }, Answered { id: String, answers: Vec, }, Status { state: SessionStatus, }, /// Settings confirmed by the session, not merely requested. Settings { #[serde(default, skip_serializing_if = "Option::is_none")] model: Option, #[serde(default, skip_serializing_if = "Option::is_none")] permission_mode: Option, }, UsageDelta { tokens: u64, /// Context held when the turn ended; this is not cumulative usage. #[serde(default, skip_serializing_if = "Option::is_none")] context: Option, }, Compacted { #[serde(default, skip_serializing_if = "Option::is_none")] pre_tokens: Option, #[serde(default, skip_serializing_if = "Option::is_none")] post_tokens: Option, #[serde(default, skip_serializing_if = "Option::is_none")] trigger: Option, }, CommandQueued { id: String, text: String, }, CommandSent { id: String, text: String, }, /// Clears model context without truncating transcript history. Cleared, LimitReached { /// Epoch seconds; absent when the dialect did not report a reset. #[serde(default, skip_serializing_if = "Option::is_none")] resets_at: Option, }, Error { message: String, }, } /// Input context is prompt plus both cache figures, never output tokens. pub fn context_tokens(input: u64, cache_creation: u64, cache_read: u64) -> u64 { input + cache_creation + cache_read } pub fn context_after(current: Option, event: &Event) -> Option { match event { // Missing usage preserves the last measurement; clear does not. Event::UsageDelta { context, .. } => context.or(current), Event::Compacted { post_tokens, .. } => *post_tokens, Event::Cleared => None, _ => current, } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum SessionStatus { Idle, Running, AwaitingInput, Compacting, Exited, /// A process is recorded but liveness could not be determined. Unknown, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct SeqEvent { pub seq: u64, pub ts: f64, #[serde(flatten)] pub event: Event, }