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 NewSession,
32 CompressionTriggered,
33 CompressionCompleted,
34 MemoryLoaded,
35 MemoryDetected, KeywordsExtracted, Error,
38 Usage,
39 Progress,
40 AskQuestion, }
42
43#[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 }, 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}