Skip to main content

matrixcode_core/
event.rs

1//! MatrixCode Event Protocol
2
3use serde::{Deserialize, Serialize};
4use std::time::{SystemTime, UNIX_EPOCH};
5
6/// Agent event
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
8pub struct AgentEvent {
9    pub event_type: EventType,
10    pub timestamp: u64,
11    #[serde(skip_serializing_if = "Option::is_none")]
12    pub data: Option<EventData>,
13}
14
15/// Event types
16#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
17#[serde(rename_all = "snake_case")]
18pub enum EventType {
19    TextStart,
20    TextDelta,
21    TextEnd,
22    ThinkingStart,
23    ThinkingDelta,
24    ThinkingEnd,
25    ToolUseStart,
26    ToolUseInputDelta,
27    ToolUseInputEnd,
28    ToolResult,
29    SessionStarted,
30    SessionEnded,
31    SessionRestored, // Session loaded from file with token stats
32    NewSession,
33    CompressionTriggered,
34    CompressionCompleted,
35    MemoryLoaded,
36    MemoryDetected,    // Memory extracted from conversation
37    KeywordsExtracted, // Keywords extracted from context (for debug)
38    Error,
39    Usage,
40    Progress,
41    ContextSize, // Update context window size from provider
42    AskQuestion, // Ask tool: waiting for user input
43    ProxyToolRequest, // Proxy tool: request external execution
44    ProxyToolResponse, // Proxy tool: external execution result
45    DebugLog,    // Debug log entry for TUI debug panel
46    SkillsLoaded,   // Skills loaded notification
47    WorkflowsLoaded, // Workflows loaded notification
48}
49
50/// Event data
51#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
52#[serde(rename_all = "snake_case")]
53pub enum EventData {
54    Text {
55        delta: String,
56    },
57    Thinking {
58        delta: String,
59        signature: Option<String>,
60    },
61    ToolUse {
62        id: String,
63        name: String,
64        input: Option<serde_json::Value>,
65    },
66    ToolUseInput {
67        id: String,
68        delta: String,
69    },
70    ToolResult {
71        tool_use_id: String,
72        name: String,
73        detail: Option<String>,
74        content: String,
75        is_error: bool,
76    },
77    Error {
78        message: String,
79        code: Option<String>,
80        source: Option<String>,
81    },
82    Usage {
83        input_tokens: u64,
84        output_tokens: u64,
85        cache_creation_input_tokens: Option<u64>,
86        cache_read_input_tokens: Option<u64>,
87    },
88    SessionRestore {
89        input_tokens: u64,
90        total_output_tokens: u64,
91        message_count: usize,
92    },
93    Progress {
94        message: String,
95        percentage: Option<u8>,
96    },
97    ContextSize {
98        context_size: u64,
99    },
100    Compression {
101        original_tokens: u64,
102        compressed_tokens: u64,
103        ratio: f32,
104    },
105    Memory {
106        summary: String,
107        entries_count: usize,
108    },
109    Keywords {
110        keywords: Vec<String>,
111        source: String,
112    }, // Extracted keywords
113    AskQuestion {
114        question: String,
115        options: Option<serde_json::Value>,
116    },
117    /// Proxy tool request - needs external execution
118    ProxyToolRequest {
119        request_id: String,
120        tool_name: String,
121        tool_input: serde_json::Value,
122        metadata: crate::tools::toolproxy::ProxyMetadata,
123    },
124    /// Proxy tool response - external execution result
125    ProxyToolResponse {
126        request_id: String,
127        result: String,
128        is_error: bool,
129    },
130    DebugLog {
131        category: String,
132        message: String,
133    }, // Debug log entry
134    SkillsLoaded {
135        names: Vec<String>,
136    },
137    WorkflowsLoaded {
138        names: Vec<String>,
139    },
140}
141
142impl AgentEvent {
143    pub fn new(event_type: EventType) -> Self {
144        Self {
145            event_type,
146            timestamp: current_timestamp(),
147            data: None,
148        }
149    }
150
151    pub fn with_data(event_type: EventType, data: EventData) -> Self {
152        Self {
153            event_type,
154            timestamp: current_timestamp(),
155            data: Some(data),
156        }
157    }
158
159    pub fn text_delta(delta: impl Into<String>) -> Self {
160        Self::with_data(
161            EventType::TextDelta,
162            EventData::Text {
163                delta: delta.into(),
164            },
165        )
166    }
167
168    pub fn text_start() -> Self {
169        Self::new(EventType::TextStart)
170    }
171    pub fn text_end() -> Self {
172        Self::new(EventType::TextEnd)
173    }
174    pub fn thinking_start() -> Self {
175        Self::new(EventType::ThinkingStart)
176    }
177    pub fn thinking_end() -> Self {
178        Self::new(EventType::ThinkingEnd)
179    }
180    pub fn session_started() -> Self {
181        Self::new(EventType::SessionStarted)
182    }
183    pub fn session_ended() -> Self {
184        Self::new(EventType::SessionEnded)
185    }
186    pub fn session_restored(
187        input_tokens: u64,
188        total_output_tokens: u64,
189        message_count: usize,
190    ) -> Self {
191        Self::with_data(
192            EventType::SessionRestored,
193            EventData::SessionRestore {
194                input_tokens,
195                total_output_tokens,
196                message_count,
197            },
198        )
199    }
200
201    pub fn thinking_delta(delta: impl Into<String>, signature: Option<String>) -> Self {
202        Self::with_data(
203            EventType::ThinkingDelta,
204            EventData::Thinking {
205                delta: delta.into(),
206                signature,
207            },
208        )
209    }
210
211    pub fn tool_use_start(
212        id: impl Into<String>,
213        name: impl Into<String>,
214        input: Option<serde_json::Value>,
215    ) -> Self {
216        Self::with_data(
217            EventType::ToolUseStart,
218            EventData::ToolUse {
219                id: id.into(),
220                name: name.into(),
221                input,
222            },
223        )
224    }
225
226    pub fn tool_result(
227        tool_use_id: impl Into<String>,
228        name: impl Into<String>,
229        detail: Option<String>,
230        content: impl Into<String>,
231        is_error: bool,
232    ) -> Self {
233        Self::with_data(
234            EventType::ToolResult,
235            EventData::ToolResult {
236                tool_use_id: tool_use_id.into(),
237                name: name.into(),
238                detail,
239                content: content.into(),
240                is_error,
241            },
242        )
243    }
244
245    pub fn error(message: impl Into<String>, code: Option<String>, source: Option<String>) -> Self {
246        Self::with_data(
247            EventType::Error,
248            EventData::Error {
249                message: message.into(),
250                code,
251                source,
252            },
253        )
254    }
255
256    pub fn progress(message: impl Into<String>, percentage: Option<u8>) -> Self {
257        Self::with_data(
258            EventType::Progress,
259            EventData::Progress {
260                message: message.into(),
261                percentage,
262            },
263        )
264    }
265
266    pub fn usage(input_tokens: u64, output_tokens: u64) -> Self {
267        Self::with_data(
268            EventType::Usage,
269            EventData::Usage {
270                input_tokens,
271                output_tokens,
272                cache_creation_input_tokens: None,
273                cache_read_input_tokens: None,
274            },
275        )
276    }
277
278    pub fn usage_with_cache(
279        input_tokens: u64,
280        output_tokens: u64,
281        cache_read: u64,
282        cache_created: u64,
283    ) -> Self {
284        Self::with_data(
285            EventType::Usage,
286            EventData::Usage {
287                input_tokens,
288                output_tokens,
289                cache_creation_input_tokens: if cache_created > 0 {
290                    Some(cache_created)
291                } else {
292                    None
293                },
294                cache_read_input_tokens: if cache_read > 0 {
295                    Some(cache_read)
296                } else {
297                    None
298                },
299            },
300        )
301    }
302
303    pub fn debug_log(category: impl Into<String>, message: impl Into<String>) -> Self {
304        Self::with_data(
305            EventType::DebugLog,
306            EventData::DebugLog {
307                category: category.into(),
308                message: message.into(),
309            },
310        )
311    }
312
313    pub fn skills_loaded(names: Vec<String>) -> Self {
314        Self::with_data(
315            EventType::SkillsLoaded,
316            EventData::SkillsLoaded { names },
317        )
318    }
319
320    pub fn workflows_loaded(names: Vec<String>) -> Self {
321        Self::with_data(
322            EventType::WorkflowsLoaded,
323            EventData::WorkflowsLoaded { names },
324        )
325    }
326
327    /// 创建代理工具请求事件
328    pub fn proxy_tool_request(
329        request_id: impl Into<String>,
330        tool_name: impl Into<String>,
331        tool_input: serde_json::Value,
332        metadata: crate::tools::toolproxy::ProxyMetadata,
333    ) -> Self {
334        Self::with_data(
335            EventType::ProxyToolRequest,
336            EventData::ProxyToolRequest {
337                request_id: request_id.into(),
338                tool_name: tool_name.into(),
339                tool_input,
340                metadata,
341            },
342        )
343    }
344    
345    /// 创建代理工具响应事件
346    pub fn proxy_tool_response(
347        request_id: impl Into<String>,
348        result: impl Into<String>,
349        is_error: bool,
350    ) -> Self {
351        Self::with_data(
352            EventType::ProxyToolResponse,
353            EventData::ProxyToolResponse {
354                request_id: request_id.into(),
355                result: result.into(),
356                is_error,
357            },
358        )
359    }
360
361    pub fn to_json(&self) -> Result<String, serde_json::Error> {
362        serde_json::to_string(self)
363    }
364    pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
365        serde_json::from_str(json)
366    }
367}
368
369fn current_timestamp() -> u64 {
370    SystemTime::now()
371        .duration_since(UNIX_EPOCH)
372        .unwrap_or_default()
373        .as_millis() as u64
374}
375
376#[derive(Debug, Default)]
377pub struct EventCollector {
378    events: Vec<AgentEvent>,
379}
380
381impl EventCollector {
382    pub fn new() -> Self {
383        Self::default()
384    }
385    pub fn push(&mut self, event: AgentEvent) {
386        self.events.push(event);
387    }
388    pub fn events(&self) -> &[AgentEvent] {
389        &self.events
390    }
391    pub fn len(&self) -> usize {
392        self.events.len()
393    }
394    pub fn is_empty(&self) -> bool {
395        self.events.is_empty()
396    }
397    pub fn clear(&mut self) {
398        self.events.clear();
399    }
400    pub fn to_json_lines(&self) -> Result<Vec<String>, serde_json::Error> {
401        self.events.iter().map(|e| e.to_json()).collect()
402    }
403    pub fn output_json_lines(&self) -> Result<String, serde_json::Error> {
404        Ok(self.to_json_lines()?.join("\n"))
405    }
406}
407
408#[cfg(test)]
409mod tests {
410    use super::*;
411    #[test]
412    fn test_event() {
413        let e = AgentEvent::text_delta("Hello");
414        assert!(e.to_json().unwrap().contains("Hello"));
415    }
416}