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