Skip to main content

tycode_core/chat/
events.rs

1use crate::ai::{model::Model, ImageData, ReasoningData, TokenUsage, ToolUseData};
2use crate::modules::task_list::TaskList;
3use crate::persistence::session::SessionMetadata;
4use chrono::Utc;
5use schemars::schema::RootSchema;
6use serde::{Deserialize, Serialize};
7use std::sync::{Arc, Mutex};
8use std::time::Duration;
9use tokio::sync::mpsc;
10
11/// `ChatEvent` are the messages sent from the actor - the output of the actor.
12///
13/// The actor is built with 2 channels - an input and output channel. Requests
14/// are sent to the actor through the input channel. Requests may generate 1 or
15/// move `ChatEvent`s in response which are sent to the output channel. Various
16/// applications (CLI/VSCode/Tests) process chat events to implement their
17/// application sepecific logic/rendering.
18#[derive(Debug, Clone, Serialize, Deserialize)]
19#[serde(tag = "kind", content = "data")]
20pub enum ChatEvent {
21    MessageAdded(ChatMessage),
22    StreamStart {
23        message_id: String,
24        agent: String,
25        model: Model,
26    },
27    StreamDelta {
28        message_id: String,
29        text: String,
30    },
31    StreamReasoningDelta {
32        message_id: String,
33        text: String,
34    },
35    StreamEnd {
36        message: ChatMessage,
37    },
38    Settings(serde_json::Value),
39    TypingStatusChanged(bool),
40    ConversationCleared,
41    ToolRequest(ToolRequest),
42    ToolExecutionCompleted {
43        tool_call_id: String,
44        tool_name: String,
45        tool_result: ToolExecutionResult,
46        success: bool,
47        error: Option<String>,
48    },
49    OperationCancelled {
50        message: String,
51    },
52    RetryAttempt {
53        attempt: u32,
54        max_retries: u32,
55        error: String,
56        backoff_ms: u64,
57    },
58    TaskUpdate(TaskList),
59    SessionsList {
60        sessions: Vec<SessionMetadata>,
61    },
62    ProfilesList {
63        profiles: Vec<String>,
64    },
65    TimingUpdate {
66        waiting_for_human: Duration,
67        ai_processing: Duration,
68        tool_execution: Duration,
69    },
70    ModuleSchemas {
71        schemas: Vec<ModuleSchemaInfo>,
72    },
73    Error(String),
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct ModuleSchemaInfo {
78    pub namespace: String,
79    pub schema: RootSchema,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct ChatMessage {
84    pub timestamp: u64,
85    pub sender: MessageSender,
86    pub content: String,
87    pub reasoning: Option<ReasoningData>,
88    pub tool_calls: Vec<ToolUseData>,
89    pub model_info: Option<ModelInfo>,
90    pub token_usage: Option<TokenUsage>,
91    #[serde(default)]
92    pub images: Vec<ImageData>,
93}
94
95impl ChatMessage {
96    pub fn user(content: String) -> Self {
97        Self {
98            timestamp: Utc::now().timestamp_millis() as u64,
99            sender: MessageSender::User,
100            content,
101            reasoning: None,
102            tool_calls: vec![],
103            model_info: None,
104            token_usage: None,
105            images: vec![],
106        }
107    }
108
109    pub fn user_with_images(content: String, images: Vec<ImageData>) -> Self {
110        Self {
111            timestamp: Utc::now().timestamp_millis() as u64,
112            sender: MessageSender::User,
113            content,
114            reasoning: None,
115            tool_calls: vec![],
116            model_info: None,
117            token_usage: None,
118            images,
119        }
120    }
121
122    pub fn assistant(
123        agent: String,
124        content: String,
125        tool_calls: Vec<ToolUseData>,
126        model_info: ModelInfo,
127        token_usage: TokenUsage,
128        reasoning: Option<ReasoningData>,
129    ) -> Self {
130        Self {
131            timestamp: Utc::now().timestamp_millis() as u64,
132            sender: MessageSender::Assistant { agent },
133            content,
134            reasoning,
135            tool_calls,
136            model_info: Some(model_info),
137            token_usage: Some(token_usage),
138            images: vec![],
139        }
140    }
141
142    pub fn system(content: String) -> Self {
143        Self {
144            timestamp: Utc::now().timestamp_millis() as u64,
145            sender: MessageSender::System,
146            content,
147            reasoning: None,
148            tool_calls: vec![],
149            model_info: None,
150            token_usage: None,
151            images: vec![],
152        }
153    }
154
155    pub fn warning(content: String) -> Self {
156        Self {
157            timestamp: Utc::now().timestamp_millis() as u64,
158            sender: MessageSender::Warning,
159            content,
160            reasoning: None,
161            tool_calls: vec![],
162            model_info: None,
163            token_usage: None,
164            images: vec![],
165        }
166    }
167
168    pub fn error(content: String) -> Self {
169        Self {
170            timestamp: Utc::now().timestamp_millis() as u64,
171            sender: MessageSender::Error,
172            content,
173            reasoning: None,
174            tool_calls: vec![],
175            model_info: None,
176            token_usage: None,
177            images: vec![],
178        }
179    }
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct FileInfo {
184    pub path: String,
185    pub bytes: usize,
186}
187
188#[derive(Debug, Clone, Serialize, Deserialize)]
189pub struct ModelInfo {
190    pub model: Model,
191}
192
193#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
194pub enum MessageSender {
195    User,
196    Assistant { agent: String },
197    System,
198    Warning,
199    Error,
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct ToolRequest {
204    pub tool_call_id: String,
205    pub tool_name: String,
206
207    pub tool_type: ToolRequestType,
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize)]
211#[serde(tag = "kind")]
212pub enum ToolRequestType {
213    ModifyFile {
214        file_path: String,
215        before: String,
216        after: String,
217    },
218    RunCommand {
219        command: String,
220        working_directory: String,
221    },
222    ReadFiles {
223        file_paths: Vec<String>,
224    },
225    Other {
226        args: serde_json::Value,
227    },
228    SearchTypes {
229        language: String,
230        workspace_root: String,
231        type_name: String,
232    },
233    GetTypeDocs {
234        language: String,
235        workspace_root: String,
236        type_path: String,
237    },
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize)]
241#[serde(tag = "kind")]
242pub enum ToolExecutionResult {
243    ModifyFile {
244        lines_added: u32,
245        lines_removed: u32,
246    },
247    RunCommand {
248        exit_code: i32,
249        stdout: String,
250        stderr: String,
251    },
252    ReadFiles {
253        files: Vec<FileInfo>,
254    },
255    SearchTypes {
256        types: Vec<String>,
257    },
258    GetTypeDocs {
259        documentation: String,
260    },
261    Error {
262        short_message: String,
263        detailed_message: String,
264    },
265    Other {
266        result: serde_json::Value,
267    },
268}
269
270/// A small wrapper over the `event_tx` for convienance.
271#[derive(Clone)]
272pub struct EventSender {
273    event_tx: mpsc::UnboundedSender<ChatEvent>,
274    event_history: Arc<Mutex<Vec<ChatEvent>>>,
275}
276
277impl EventSender {
278    pub fn new() -> (Self, mpsc::UnboundedReceiver<ChatEvent>) {
279        let (event_tx, rx) = mpsc::unbounded_channel();
280        (
281            Self {
282                event_tx,
283                event_history: Arc::new(Mutex::new(Vec::new())),
284            },
285            rx,
286        )
287    }
288
289    pub fn add_message(&self, message: ChatMessage) {
290        let _ = self.event_tx.send(ChatEvent::MessageAdded(message));
291    }
292
293    pub fn set_typing(&self, typing: bool) {
294        let _ = self.event_tx.send(ChatEvent::TypingStatusChanged(typing));
295    }
296
297    pub fn clear_conversation(&self) {
298        let _ = self.event_tx.send(ChatEvent::ConversationCleared);
299    }
300
301    pub fn send(&self, event: ChatEvent) {
302        self.event_history.lock().unwrap().push(event.clone());
303        let _ = self.event_tx.send(event);
304    }
305
306    pub fn send_message(&self, message: ChatMessage) {
307        let event = ChatEvent::MessageAdded(message);
308        self.send(event);
309    }
310
311    pub fn send_replay(&self, event: ChatEvent) {
312        let _ = self.event_tx.send(event);
313    }
314
315    pub fn event_history(&self) -> Vec<ChatEvent> {
316        self.event_history.lock().unwrap().clone()
317    }
318
319    pub fn stream_start(&self, message_id: String, agent: String, model: Model) {
320        self.send(ChatEvent::StreamStart {
321            message_id,
322            agent,
323            model,
324        });
325    }
326
327    pub fn stream_delta(&self, message_id: String, text: String) {
328        self.send(ChatEvent::StreamDelta { message_id, text });
329    }
330
331    pub fn stream_reasoning_delta(&self, message_id: String, text: String) {
332        self.send(ChatEvent::StreamReasoningDelta { message_id, text });
333    }
334
335    pub fn stream_end(&self, message: ChatMessage) {
336        self.send(ChatEvent::StreamEnd { message });
337    }
338
339    pub(crate) fn clear_history(&self) {
340        self.event_history.lock().unwrap().clear();
341    }
342}