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