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    AskQuestion, // Ask tool: waiting for user input
42}
43
44/// Event data
45#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
46#[serde(rename_all = "snake_case")]
47pub enum EventData {
48    Text {
49        delta: String,
50    },
51    Thinking {
52        delta: String,
53        signature: Option<String>,
54    },
55    ToolUse {
56        id: String,
57        name: String,
58        input: Option<serde_json::Value>,
59    },
60    ToolUseInput {
61        id: String,
62        delta: String,
63    },
64    ToolResult {
65        tool_use_id: String,
66        name: String,
67        detail: Option<String>,
68        content: String,
69        is_error: bool,
70    },
71    Error {
72        message: String,
73        code: Option<String>,
74        source: Option<String>,
75    },
76    Usage {
77        input_tokens: u64,
78        output_tokens: u64,
79        cache_creation_input_tokens: Option<u64>,
80        cache_read_input_tokens: Option<u64>,
81    },
82    SessionRestore {
83        input_tokens: u64,
84        total_output_tokens: u64,
85        message_count: usize,
86    },
87    Progress {
88        message: String,
89        percentage: Option<u8>,
90    },
91    Compression {
92        original_tokens: u64,
93        compressed_tokens: u64,
94        ratio: f32,
95    },
96    Memory {
97        summary: String,
98        entries_count: usize,
99    },
100    Keywords {
101        keywords: Vec<String>,
102        source: String,
103    }, // Extracted keywords
104    AskQuestion {
105        question: String,
106        options: Option<serde_json::Value>,
107    },
108}
109
110impl AgentEvent {
111    pub fn new(event_type: EventType) -> Self {
112        Self {
113            event_type,
114            timestamp: current_timestamp(),
115            data: None,
116        }
117    }
118
119    pub fn with_data(event_type: EventType, data: EventData) -> Self {
120        Self {
121            event_type,
122            timestamp: current_timestamp(),
123            data: Some(data),
124        }
125    }
126
127    pub fn text_delta(delta: impl Into<String>) -> Self {
128        Self::with_data(
129            EventType::TextDelta,
130            EventData::Text {
131                delta: delta.into(),
132            },
133        )
134    }
135
136    pub fn text_start() -> Self {
137        Self::new(EventType::TextStart)
138    }
139    pub fn text_end() -> Self {
140        Self::new(EventType::TextEnd)
141    }
142    pub fn thinking_start() -> Self {
143        Self::new(EventType::ThinkingStart)
144    }
145    pub fn thinking_end() -> Self {
146        Self::new(EventType::ThinkingEnd)
147    }
148    pub fn session_started() -> Self {
149        Self::new(EventType::SessionStarted)
150    }
151    pub fn session_ended() -> Self {
152        Self::new(EventType::SessionEnded)
153    }
154    pub fn session_restored(
155        input_tokens: u64,
156        total_output_tokens: u64,
157        message_count: usize,
158    ) -> Self {
159        Self::with_data(
160            EventType::SessionRestored,
161            EventData::SessionRestore {
162                input_tokens,
163                total_output_tokens,
164                message_count,
165            },
166        )
167    }
168
169    pub fn thinking_delta(delta: impl Into<String>, signature: Option<String>) -> Self {
170        Self::with_data(
171            EventType::ThinkingDelta,
172            EventData::Thinking {
173                delta: delta.into(),
174                signature,
175            },
176        )
177    }
178
179    pub fn tool_use_start(
180        id: impl Into<String>,
181        name: impl Into<String>,
182        input: Option<serde_json::Value>,
183    ) -> Self {
184        Self::with_data(
185            EventType::ToolUseStart,
186            EventData::ToolUse {
187                id: id.into(),
188                name: name.into(),
189                input,
190            },
191        )
192    }
193
194    pub fn tool_result(
195        tool_use_id: impl Into<String>,
196        name: impl Into<String>,
197        detail: Option<String>,
198        content: impl Into<String>,
199        is_error: bool,
200    ) -> Self {
201        Self::with_data(
202            EventType::ToolResult,
203            EventData::ToolResult {
204                tool_use_id: tool_use_id.into(),
205                name: name.into(),
206                detail,
207                content: content.into(),
208                is_error,
209            },
210        )
211    }
212
213    pub fn error(message: impl Into<String>, code: Option<String>, source: Option<String>) -> Self {
214        Self::with_data(
215            EventType::Error,
216            EventData::Error {
217                message: message.into(),
218                code,
219                source,
220            },
221        )
222    }
223
224    pub fn progress(message: impl Into<String>, percentage: Option<u8>) -> Self {
225        Self::with_data(
226            EventType::Progress,
227            EventData::Progress {
228                message: message.into(),
229                percentage,
230            },
231        )
232    }
233
234    pub fn usage(input_tokens: u64, output_tokens: u64) -> Self {
235        Self::with_data(
236            EventType::Usage,
237            EventData::Usage {
238                input_tokens,
239                output_tokens,
240                cache_creation_input_tokens: None,
241                cache_read_input_tokens: None,
242            },
243        )
244    }
245
246    pub fn usage_with_cache(
247        input_tokens: u64,
248        output_tokens: u64,
249        cache_read: u64,
250        cache_created: u64,
251    ) -> Self {
252        Self::with_data(
253            EventType::Usage,
254            EventData::Usage {
255                input_tokens,
256                output_tokens,
257                cache_creation_input_tokens: if cache_created > 0 {
258                    Some(cache_created)
259                } else {
260                    None
261                },
262                cache_read_input_tokens: if cache_read > 0 {
263                    Some(cache_read)
264                } else {
265                    None
266                },
267            },
268        )
269    }
270
271    pub fn to_json(&self) -> Result<String, serde_json::Error> {
272        serde_json::to_string(self)
273    }
274    pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
275        serde_json::from_str(json)
276    }
277}
278
279fn current_timestamp() -> u64 {
280    SystemTime::now()
281        .duration_since(UNIX_EPOCH)
282        .unwrap_or_default()
283        .as_millis() as u64
284}
285
286#[derive(Debug, Default)]
287pub struct EventCollector {
288    events: Vec<AgentEvent>,
289}
290
291impl EventCollector {
292    pub fn new() -> Self {
293        Self::default()
294    }
295    pub fn push(&mut self, event: AgentEvent) {
296        self.events.push(event);
297    }
298    pub fn events(&self) -> &[AgentEvent] {
299        &self.events
300    }
301    pub fn len(&self) -> usize {
302        self.events.len()
303    }
304    pub fn is_empty(&self) -> bool {
305        self.events.is_empty()
306    }
307    pub fn clear(&mut self) {
308        self.events.clear();
309    }
310    pub fn to_json_lines(&self) -> Result<Vec<String>, serde_json::Error> {
311        self.events.iter().map(|e| e.to_json()).collect()
312    }
313    pub fn output_json_lines(&self) -> Result<String, serde_json::Error> {
314        Ok(self.to_json_lines()?.join("\n"))
315    }
316}
317
318#[cfg(test)]
319mod tests {
320    use super::*;
321    #[test]
322    fn test_event() {
323        let e = AgentEvent::text_delta("Hello");
324        assert!(e.to_json().unwrap().contains("Hello"));
325    }
326}