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