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(
355            EventType::SkillsLoaded,
356            EventData::SkillsLoaded { names },
357        )
358    }
359
360    pub fn workflows_loaded(names: Vec<String>) -> Self {
361        Self::with_data(
362            EventType::WorkflowsLoaded,
363            EventData::WorkflowsLoaded { names },
364        )
365    }
366
367    /// MCP server added event
368    pub fn mcp_server_added(name: impl Into<String>, tool_count: usize) -> Self {
369        Self::with_data(
370            EventType::McpServerAdded,
371            EventData::McpServerAdded {
372                name: name.into(),
373                tool_count,
374            },
375        )
376    }
377
378    /// MCP server removed event
379    pub fn mcp_server_removed(name: impl Into<String>) -> Self {
380        Self::with_data(
381            EventType::McpServerRemoved,
382            EventData::McpServerRemoved {
383                name: name.into(),
384            },
385        )
386    }
387
388    /// MCP server status update event
389    pub fn mcp_server_status(servers: Vec<McpServerInfo>) -> Self {
390        Self::with_data(
391            EventType::McpServerStatus,
392            EventData::McpServerStatus { servers },
393        )
394    }
395
396    /// LSP server added event
397    pub fn lsp_server_added(name: impl Into<String>, language: impl Into<String>) -> Self {
398        Self::with_data(
399            EventType::LspServerAdded,
400            EventData::LspServerAdded {
401                name: name.into(),
402                language: language.into(),
403            },
404        )
405    }
406
407    /// LSP server removed event
408    pub fn lsp_server_removed(name: impl Into<String>) -> Self {
409        Self::with_data(
410            EventType::LspServerRemoved,
411            EventData::LspServerRemoved {
412                name: name.into(),
413            },
414        )
415    }
416
417    /// LSP server status update event
418    pub fn lsp_server_status(servers: Vec<LspServerInfo>) -> Self {
419        Self::with_data(
420            EventType::LspServerStatus,
421            EventData::LspServerStatus { servers },
422        )
423    }
424
425    /// 创建代理工具请求事件
426    pub fn proxy_tool_request(
427        request_id: impl Into<String>,
428        tool_name: impl Into<String>,
429        tool_input: serde_json::Value,
430        metadata: crate::tools::toolproxy::ProxyMetadata,
431    ) -> Self {
432        Self::with_data(
433            EventType::ProxyToolRequest,
434            EventData::ProxyToolRequest {
435                request_id: request_id.into(),
436                tool_name: tool_name.into(),
437                tool_input,
438                metadata,
439            },
440        )
441    }
442    
443    /// 创建代理工具响应事件
444    pub fn proxy_tool_response(
445        request_id: impl Into<String>,
446        result: impl Into<String>,
447        is_error: bool,
448    ) -> Self {
449        Self::with_data(
450            EventType::ProxyToolResponse,
451            EventData::ProxyToolResponse {
452                request_id: request_id.into(),
453                result: result.into(),
454                is_error,
455            },
456        )
457    }
458
459    pub fn to_json(&self) -> Result<String, serde_json::Error> {
460        serde_json::to_string(self)
461    }
462    pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
463        serde_json::from_str(json)
464    }
465}
466
467fn current_timestamp() -> u64 {
468    SystemTime::now()
469        .duration_since(UNIX_EPOCH)
470        .unwrap_or_default()
471        .as_millis() as u64
472}
473
474/// MCP server information for status display
475#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
476pub struct McpServerInfo {
477    pub name: String,
478    pub is_started: bool,
479    pub tool_count: usize,
480}
481
482impl McpServerInfo {
483    pub fn new(name: impl Into<String>, is_started: bool, tool_count: usize) -> Self {
484        Self {
485            name: name.into(),
486            is_started,
487            tool_count,
488        }
489    }
490    
491    pub fn from_status(status: &crate::mcp::ServerStatus) -> Self {
492        Self {
493            name: status.name.clone(),
494            is_started: status.is_started,
495            tool_count: status.tool_count,
496        }
497    }
498}
499
500#[derive(Debug, Default)]
501pub struct EventCollector {
502    events: Vec<AgentEvent>,
503}
504
505impl EventCollector {
506    pub fn new() -> Self {
507        Self::default()
508    }
509    pub fn push(&mut self, event: AgentEvent) {
510        self.events.push(event);
511    }
512    pub fn events(&self) -> &[AgentEvent] {
513        &self.events
514    }
515    pub fn len(&self) -> usize {
516        self.events.len()
517    }
518    pub fn is_empty(&self) -> bool {
519        self.events.is_empty()
520    }
521    pub fn clear(&mut self) {
522        self.events.clear();
523    }
524    pub fn to_json_lines(&self) -> Result<Vec<String>, serde_json::Error> {
525        self.events.iter().map(|e| e.to_json()).collect()
526    }
527    pub fn output_json_lines(&self) -> Result<String, serde_json::Error> {
528        Ok(self.to_json_lines()?.join("\n"))
529    }
530}
531
532#[cfg(test)]
533mod tests {
534    use super::*;
535    #[test]
536    fn test_event() {
537        let e = AgentEvent::text_delta("Hello");
538        assert!(e.to_json().unwrap().contains("Hello"));
539    }
540}