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 ContextSize, AskQuestion, ProxyToolRequest, ProxyToolResponse, DebugLog, SkillsLoaded, WorkflowsLoaded, }
49
50#[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 }, AskQuestion {
114 question: String,
115 options: Option<serde_json::Value>,
116 },
117 ProxyToolRequest {
119 request_id: String,
120 tool_name: String,
121 tool_input: serde_json::Value,
122 metadata: crate::tools::toolproxy::ProxyMetadata,
123 },
124 ProxyToolResponse {
126 request_id: String,
127 result: String,
128 is_error: bool,
129 },
130 DebugLog {
131 category: String,
132 message: String,
133 }, 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 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 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}