Skip to main content

matrixcode_core/
event.rs

1//! MatrixCode Event Protocol
2
3use serde::{Deserialize, Serialize};
4use std::time::{SystemTime, UNIX_EPOCH};
5
6use crate::lsp::LspServerInfo;
7
8/// Agent event
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
10pub struct AgentEvent {
11    pub event_type: EventType,
12    pub timestamp: u64,
13    #[serde(skip_serializing_if = "Option::is_none")]
14    pub data: Option<EventData>,
15}
16
17/// Event types
18#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
19#[serde(rename_all = "snake_case")]
20pub enum EventType {
21    TextStart,
22    TextDelta,
23    TextEnd,
24    ThinkingStart,
25    ThinkingDelta,
26    ThinkingEnd,
27    ToolUseStart,
28    ToolUseInputDelta,
29    ToolUseInputEnd,
30    ToolResult,
31    SessionStarted,
32    SessionEnded,
33    SessionRestored, // Session loaded from file with token stats
34    NewSession,
35    CompressionTriggered,
36    CompressionCompleted,
37    MemoryLoaded,
38    MemoryDetected,    // Memory extracted from conversation
39    KeywordsExtracted, // Keywords extracted from context (for debug)
40    Error,
41    Usage,
42    Progress,
43    ContextSize,       // Update context window size from provider
44    AskQuestion,       // Ask tool: waiting for user input
45    ProxyToolRequest,  // Proxy tool: request external execution
46    ProxyToolResponse, // Proxy tool: external execution result
47    DebugLog,          // Debug log entry for TUI debug panel
48    SkillsLoaded,      // Skills loaded notification
49    WorkflowsLoaded,   // Workflows loaded notification
50    McpServerAdded,    // MCP server added
51    QueueProcessed,    // Pending messages processed by Agent
52    McpServerRemoved,  // MCP server removed
53    McpServerStatus,   // MCP server status update
54    LspServerAdded,    // LSP server added
55    LspServerRemoved,  // LSP server removed
56    LspServerStatus,   // LSP server status update
57    SessionsList,      // Session list for interactive selector
58}
59
60/// Event data
61#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
62#[serde(rename_all = "snake_case")]
63pub enum EventData {
64    Text {
65        delta: String,
66    },
67    Thinking {
68        delta: String,
69        signature: Option<String>,
70    },
71    ToolUse {
72        id: String,
73        name: String,
74        input: Option<serde_json::Value>,
75    },
76    ToolUseInput {
77        id: String,
78        delta: String,
79    },
80    ToolResult {
81        tool_use_id: String,
82        name: String,
83        detail: Option<String>,
84        content: String,
85        is_error: bool,
86    },
87    Error {
88        message: String,
89        code: Option<String>,
90        source: Option<String>,
91    },
92    Usage {
93        input_tokens: u64,
94        output_tokens: u64,
95        cache_creation_input_tokens: Option<u64>,
96        cache_read_input_tokens: Option<u64>,
97    },
98    SessionRestore {
99        input_tokens: u64,
100        total_output_tokens: u64,
101        message_count: usize,
102    },
103    Progress {
104        message: String,
105        percentage: Option<u8>,
106    },
107    ContextSize {
108        context_size: u64,
109    },
110    Compression {
111        original_tokens: u64,
112        compressed_tokens: u64,
113        ratio: f32,
114    },
115    Memory {
116        summary: String,
117        entries_count: usize,
118    },
119    Keywords {
120        keywords: Vec<String>,
121        source: String,
122    }, // Extracted keywords
123    AskQuestion {
124        question: String,
125        options: Option<serde_json::Value>,
126    },
127    /// Proxy tool request - needs external execution
128    ProxyToolRequest {
129        request_id: String,
130        tool_name: String,
131        tool_input: serde_json::Value,
132        metadata: crate::tools::toolproxy::ProxyMetadata,
133    },
134    /// Proxy tool response - external execution result
135    ProxyToolResponse {
136        request_id: String,
137        result: String,
138        is_error: bool,
139    },
140    DebugLog {
141        category: String,
142        message: String,
143    }, // Debug log entry
144    SkillsLoaded {
145        names: Vec<String>,
146    },
147    WorkflowsLoaded {
148        names: Vec<String>,
149    },
150    McpServerAdded {
151        name: String,
152        tool_count: usize,
153    },
154    QueueProcessed {
155        count: usize,
156        messages: Vec<String>, // Messages that were processed
157    }, // Pending messages processed by Agent
158    McpServerRemoved {
159        name: String,
160    },
161    McpServerStatus {
162        servers: Vec<McpServerInfo>,
163    },
164    LspServerAdded {
165        name: String,
166        language: String,
167    },
168    LspServerRemoved {
169        name: String,
170    },
171    LspServerStatus {
172        servers: Vec<LspServerInfo>,
173    },
174    /// Session list for interactive selector
175    SessionsList {
176        sessions: Vec<SessionListItem>,
177    },
178}
179
180/// Session list item for display
181#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
182pub struct SessionListItem {
183    pub short_id: String,
184    pub title: String,      // Session display name
185    pub message_count: usize,
186    pub created_at: String, // Formatted datetime string
187}
188
189impl AgentEvent {
190    pub fn new(event_type: EventType) -> Self {
191        Self {
192            event_type,
193            timestamp: current_timestamp(),
194            data: None,
195        }
196    }
197
198    pub fn with_data(event_type: EventType, data: EventData) -> Self {
199        Self {
200            event_type,
201            timestamp: current_timestamp(),
202            data: Some(data),
203        }
204    }
205
206    pub fn text_delta(delta: impl Into<String>) -> Self {
207        Self::with_data(
208            EventType::TextDelta,
209            EventData::Text {
210                delta: delta.into(),
211            },
212        )
213    }
214
215    pub fn text_start() -> Self {
216        Self::new(EventType::TextStart)
217    }
218    pub fn text_end() -> Self {
219        Self::new(EventType::TextEnd)
220    }
221    pub fn thinking_start() -> Self {
222        Self::new(EventType::ThinkingStart)
223    }
224    pub fn thinking_end() -> Self {
225        Self::new(EventType::ThinkingEnd)
226    }
227    pub fn session_started() -> Self {
228        Self::new(EventType::SessionStarted)
229    }
230    pub fn session_ended() -> Self {
231        Self::new(EventType::SessionEnded)
232    }
233    pub fn session_restored(
234        input_tokens: u64,
235        total_output_tokens: u64,
236        message_count: usize,
237    ) -> Self {
238        Self::with_data(
239            EventType::SessionRestored,
240            EventData::SessionRestore {
241                input_tokens,
242                total_output_tokens,
243                message_count,
244            },
245        )
246    }
247
248    pub fn thinking_delta(delta: impl Into<String>, signature: Option<String>) -> Self {
249        Self::with_data(
250            EventType::ThinkingDelta,
251            EventData::Thinking {
252                delta: delta.into(),
253                signature,
254            },
255        )
256    }
257
258    pub fn tool_use_start(
259        id: impl Into<String>,
260        name: impl Into<String>,
261        input: Option<serde_json::Value>,
262    ) -> Self {
263        Self::with_data(
264            EventType::ToolUseStart,
265            EventData::ToolUse {
266                id: id.into(),
267                name: name.into(),
268                input,
269            },
270        )
271    }
272
273    pub fn tool_result(
274        tool_use_id: impl Into<String>,
275        name: impl Into<String>,
276        detail: Option<String>,
277        content: impl Into<String>,
278        is_error: bool,
279    ) -> Self {
280        Self::with_data(
281            EventType::ToolResult,
282            EventData::ToolResult {
283                tool_use_id: tool_use_id.into(),
284                name: name.into(),
285                detail,
286                content: content.into(),
287                is_error,
288            },
289        )
290    }
291
292    pub fn error(message: impl Into<String>, code: Option<String>, source: Option<String>) -> Self {
293        Self::with_data(
294            EventType::Error,
295            EventData::Error {
296                message: message.into(),
297                code,
298                source,
299            },
300        )
301    }
302
303    pub fn progress(message: impl Into<String>, percentage: Option<u8>) -> Self {
304        Self::with_data(
305            EventType::Progress,
306            EventData::Progress {
307                message: message.into(),
308                percentage,
309            },
310        )
311    }
312
313    pub fn queue_processed(count: usize, messages: Vec<String>) -> Self {
314        Self::with_data(
315            EventType::QueueProcessed,
316            EventData::QueueProcessed { count, messages },
317        )
318    }
319
320    pub fn usage(input_tokens: u64, output_tokens: u64) -> Self {
321        Self::with_data(
322            EventType::Usage,
323            EventData::Usage {
324                input_tokens,
325                output_tokens,
326                cache_creation_input_tokens: None,
327                cache_read_input_tokens: None,
328            },
329        )
330    }
331
332    pub fn usage_with_cache(
333        input_tokens: u64,
334        output_tokens: u64,
335        cache_read: u64,
336        cache_created: u64,
337    ) -> Self {
338        Self::with_data(
339            EventType::Usage,
340            EventData::Usage {
341                input_tokens,
342                output_tokens,
343                cache_creation_input_tokens: if cache_created > 0 {
344                    Some(cache_created)
345                } else {
346                    None
347                },
348                cache_read_input_tokens: if cache_read > 0 {
349                    Some(cache_read)
350                } else {
351                    None
352                },
353            },
354        )
355    }
356
357    pub fn debug_log(category: impl Into<String>, message: impl Into<String>) -> Self {
358        Self::with_data(
359            EventType::DebugLog,
360            EventData::DebugLog {
361                category: category.into(),
362                message: message.into(),
363            },
364        )
365    }
366
367    pub fn skills_loaded(names: Vec<String>) -> Self {
368        Self::with_data(EventType::SkillsLoaded, EventData::SkillsLoaded { names })
369    }
370
371    pub fn workflows_loaded(names: Vec<String>) -> Self {
372        Self::with_data(
373            EventType::WorkflowsLoaded,
374            EventData::WorkflowsLoaded { names },
375        )
376    }
377
378    /// MCP server added event
379    pub fn mcp_server_added(name: impl Into<String>, tool_count: usize) -> Self {
380        Self::with_data(
381            EventType::McpServerAdded,
382            EventData::McpServerAdded {
383                name: name.into(),
384                tool_count,
385            },
386        )
387    }
388
389    /// MCP server removed event
390    pub fn mcp_server_removed(name: impl Into<String>) -> Self {
391        Self::with_data(
392            EventType::McpServerRemoved,
393            EventData::McpServerRemoved { name: name.into() },
394        )
395    }
396
397    /// MCP server status update event
398    pub fn mcp_server_status(servers: Vec<McpServerInfo>) -> Self {
399        Self::with_data(
400            EventType::McpServerStatus,
401            EventData::McpServerStatus { servers },
402        )
403    }
404
405    /// LSP server added event
406    pub fn lsp_server_added(name: impl Into<String>, language: impl Into<String>) -> Self {
407        Self::with_data(
408            EventType::LspServerAdded,
409            EventData::LspServerAdded {
410                name: name.into(),
411                language: language.into(),
412            },
413        )
414    }
415
416    /// LSP server removed event
417    pub fn lsp_server_removed(name: impl Into<String>) -> Self {
418        Self::with_data(
419            EventType::LspServerRemoved,
420            EventData::LspServerRemoved { name: name.into() },
421        )
422    }
423
424    /// LSP server status update event
425    pub fn lsp_server_status(servers: Vec<LspServerInfo>) -> Self {
426        Self::with_data(
427            EventType::LspServerStatus,
428            EventData::LspServerStatus { servers },
429        )
430    }
431
432    /// 创建代理工具请求事件
433    pub fn proxy_tool_request(
434        request_id: impl Into<String>,
435        tool_name: impl Into<String>,
436        tool_input: serde_json::Value,
437        metadata: crate::tools::toolproxy::ProxyMetadata,
438    ) -> Self {
439        Self::with_data(
440            EventType::ProxyToolRequest,
441            EventData::ProxyToolRequest {
442                request_id: request_id.into(),
443                tool_name: tool_name.into(),
444                tool_input,
445                metadata,
446            },
447        )
448    }
449
450    /// 创建代理工具响应事件
451    pub fn proxy_tool_response(
452        request_id: impl Into<String>,
453        result: impl Into<String>,
454        is_error: bool,
455    ) -> Self {
456        Self::with_data(
457            EventType::ProxyToolResponse,
458            EventData::ProxyToolResponse {
459                request_id: request_id.into(),
460                result: result.into(),
461                is_error,
462            },
463        )
464    }
465
466    pub fn to_json(&self) -> Result<String, serde_json::Error> {
467        serde_json::to_string(self)
468    }
469    pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
470        serde_json::from_str(json)
471    }
472}
473
474fn current_timestamp() -> u64 {
475    SystemTime::now()
476        .duration_since(UNIX_EPOCH)
477        .unwrap_or_default()
478        .as_millis() as u64
479}
480
481/// MCP server information for status display
482#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
483pub struct McpServerInfo {
484    pub name: String,
485    pub is_started: bool,
486    pub tool_count: usize,
487}
488
489impl McpServerInfo {
490    pub fn new(name: impl Into<String>, is_started: bool, tool_count: usize) -> Self {
491        Self {
492            name: name.into(),
493            is_started,
494            tool_count,
495        }
496    }
497
498    pub fn from_status(status: &crate::mcp::ServerStatus) -> Self {
499        Self {
500            name: status.name.clone(),
501            is_started: status.is_started,
502            tool_count: status.tool_count,
503        }
504    }
505}
506
507#[derive(Debug, Default)]
508pub struct EventCollector {
509    events: Vec<AgentEvent>,
510}
511
512impl EventCollector {
513    pub fn new() -> Self {
514        Self::default()
515    }
516    pub fn push(&mut self, event: AgentEvent) {
517        self.events.push(event);
518    }
519    pub fn events(&self) -> &[AgentEvent] {
520        &self.events
521    }
522    pub fn len(&self) -> usize {
523        self.events.len()
524    }
525    pub fn is_empty(&self) -> bool {
526        self.events.is_empty()
527    }
528    pub fn clear(&mut self) {
529        self.events.clear();
530    }
531    pub fn to_json_lines(&self) -> Result<Vec<String>, serde_json::Error> {
532        self.events.iter().map(|e| e.to_json()).collect()
533    }
534    pub fn output_json_lines(&self) -> Result<String, serde_json::Error> {
535        Ok(self.to_json_lines()?.join("\n"))
536    }
537}
538
539#[cfg(test)]
540mod tests {
541    use super::*;
542    #[test]
543    fn test_event() {
544        let e = AgentEvent::text_delta("Hello");
545        assert!(e.to_json().unwrap().contains("Hello"));
546    }
547}