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