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 {
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 }, 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}