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