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 { delta: String },
48    Thinking { delta: String, signature: Option<String> },
49    ToolUse { id: String, name: String, input: Option<serde_json::Value> },
50    ToolUseInput { id: String, delta: String },
51    ToolResult { tool_use_id: String, content: String, is_error: bool },
52    Error { message: String, code: Option<String>, source: Option<String> },
53    Usage { input_tokens: u64, output_tokens: u64, cache_creation_input_tokens: Option<u64>, cache_read_input_tokens: Option<u64> },
54    Progress { message: String, percentage: Option<u8> },
55    Compression { original_tokens: u64, compressed_tokens: u64, ratio: f32 },
56    Memory { summary: String, entries_count: usize },
57    Keywords { keywords: Vec<String>, source: String },  // Extracted keywords
58    AskQuestion { question: String, options: Option<serde_json::Value> },
59}
60
61impl AgentEvent {
62    pub fn new(event_type: EventType) -> Self {
63        Self { event_type, timestamp: current_timestamp(), data: None }
64    }
65
66    pub fn with_data(event_type: EventType, data: EventData) -> Self {
67        Self { event_type, timestamp: current_timestamp(), data: Some(data) }
68    }
69
70    pub fn text_delta(delta: impl Into<String>) -> Self {
71        Self::with_data(EventType::TextDelta, EventData::Text { delta: delta.into() })
72    }
73
74    pub fn text_start() -> Self { Self::new(EventType::TextStart) }
75    pub fn text_end() -> Self { Self::new(EventType::TextEnd) }
76    pub fn thinking_start() -> Self { Self::new(EventType::ThinkingStart) }
77    pub fn thinking_end() -> Self { Self::new(EventType::ThinkingEnd) }
78    pub fn session_started() -> Self { Self::new(EventType::SessionStarted) }
79    pub fn session_ended() -> Self { Self::new(EventType::SessionEnded) }
80
81    pub fn thinking_delta(delta: impl Into<String>, signature: Option<String>) -> Self {
82        Self::with_data(EventType::ThinkingDelta, EventData::Thinking { delta: delta.into(), signature })
83    }
84
85    pub fn tool_use_start(id: impl Into<String>, name: impl Into<String>, input: Option<serde_json::Value>) -> Self {
86        Self::with_data(EventType::ToolUseStart, EventData::ToolUse { id: id.into(), name: name.into(), input })
87    }
88
89    pub fn tool_result(tool_use_id: impl Into<String>, content: impl Into<String>, is_error: bool) -> Self {
90        Self::with_data(EventType::ToolResult, EventData::ToolResult {
91            tool_use_id: tool_use_id.into(), content: content.into(), is_error
92        })
93    }
94
95    pub fn error(message: impl Into<String>, code: Option<String>, source: Option<String>) -> Self {
96        Self::with_data(EventType::Error, EventData::Error { message: message.into(), code, source })
97    }
98
99    pub fn progress(message: impl Into<String>, percentage: Option<u8>) -> Self {
100        Self::with_data(EventType::Progress, EventData::Progress { message: message.into(), percentage })
101    }
102
103    pub fn usage(input_tokens: u64, output_tokens: u64) -> Self {
104        Self::with_data(EventType::Usage, EventData::Usage {
105            input_tokens, output_tokens,
106            cache_creation_input_tokens: None,
107            cache_read_input_tokens: None,
108        })
109    }
110
111    pub fn usage_with_cache(input_tokens: u64, output_tokens: u64, cache_read: u64, cache_created: u64) -> Self {
112        Self::with_data(EventType::Usage, EventData::Usage {
113            input_tokens, output_tokens,
114            cache_creation_input_tokens: if cache_created > 0 { Some(cache_created) } else { None },
115            cache_read_input_tokens: if cache_read > 0 { Some(cache_read) } else { None },
116        })
117    }
118
119    pub fn to_json(&self) -> Result<String, serde_json::Error> { serde_json::to_string(self) }
120    pub fn from_json(json: &str) -> Result<Self, serde_json::Error> { serde_json::from_str(json) }
121}
122
123fn current_timestamp() -> u64 {
124    SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_millis() as u64
125}
126
127#[derive(Debug, Default)]
128pub struct EventCollector { events: Vec<AgentEvent> }
129
130impl EventCollector {
131    pub fn new() -> Self { Self::default() }
132    pub fn push(&mut self, event: AgentEvent) { self.events.push(event); }
133    pub fn events(&self) -> &[AgentEvent] { &self.events }
134    pub fn len(&self) -> usize { self.events.len() }
135    pub fn is_empty(&self) -> bool { self.events.is_empty() }
136    pub fn clear(&mut self) { self.events.clear(); }
137    pub fn to_json_lines(&self) -> Result<Vec<String>, serde_json::Error> {
138        self.events.iter().map(|e| e.to_json()).collect()
139    }
140    pub fn output_json_lines(&self) -> Result<String, serde_json::Error> {
141        Ok(self.to_json_lines()?.join("\n"))
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148    #[test]
149    fn test_event() {
150        let e = AgentEvent::text_delta("Hello");
151        assert!(e.to_json().unwrap().contains("Hello"));
152    }
153}