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