1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct TurnStartEvent {
11 pub prompt: String,
13 pub timestamp_secs: u64,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct ThinkingDeltaEvent {
20 pub content: String,
22 pub is_first: bool,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct ToolApprovalEvent {
29 pub call_id: String,
31 pub tool_name: String,
33 pub arguments: serde_json::Value,
35 pub auto_approved: bool,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct ToolStartEvent {
42 pub call_id: String,
44 pub tool_name: String,
46 pub arguments: serde_json::Value,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct ToolCompleteEvent {
53 pub call_id: String,
55 pub tool_name: String,
57 pub result: String,
59 pub success: bool,
61 pub duration_ms: u64,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct TurnEndEvent {
68 pub content: String,
70 pub tool_call_count: usize,
72 pub iterations: usize,
74 pub usage: TokenUsageInfo,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize, Default)]
80pub struct TokenUsageInfo {
81 pub prompt_tokens: u64,
82 pub completion_tokens: u64,
83 pub total_tokens: u64,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct SessionEndEvent {
89 pub content: String,
91 pub total_iterations: usize,
93 pub usage: TokenUsageInfo,
95 pub finish_reason: FinishReason,
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
101#[serde(tag = "type", content = "message")]
102pub enum FinishReason {
103 Stop,
105 MaxIterations,
107 Error(String),
109 UnknownTool(String),
111 Cancelled,
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize)]
117#[serde(tag = "event_type")]
118pub enum AgentEvent {
119 TurnStart(TurnStartEvent),
121 ThinkingDelta(ThinkingDeltaEvent),
123 ToolApproval(ToolApprovalEvent),
125 ToolStart(ToolStartEvent),
127 ToolComplete(ToolCompleteEvent),
129 TurnEnd(TurnEndEvent),
131 SessionEnd(SessionEndEvent),
133}
134
135impl AgentEvent {
136 pub fn turn_start(prompt: &str) -> Self {
138 AgentEvent::TurnStart(TurnStartEvent {
139 prompt: prompt.to_string(),
140 timestamp_secs: std::time::SystemTime::now()
141 .duration_since(std::time::UNIX_EPOCH)
142 .unwrap_or_default()
143 .as_secs(),
144 })
145 }
146
147 pub fn thinking_delta(content: &str, is_first: bool) -> Self {
149 AgentEvent::ThinkingDelta(ThinkingDeltaEvent {
150 content: content.to_string(),
151 is_first,
152 })
153 }
154
155 pub fn tool_approval(call_id: &str, tool_name: &str, arguments: serde_json::Value, auto_approved: bool) -> Self {
157 AgentEvent::ToolApproval(ToolApprovalEvent {
158 call_id: call_id.to_string(),
159 tool_name: tool_name.to_string(),
160 arguments,
161 auto_approved,
162 })
163 }
164
165 pub fn tool_start(call_id: &str, tool_name: &str, arguments: serde_json::Value) -> Self {
167 AgentEvent::ToolStart(ToolStartEvent {
168 call_id: call_id.to_string(),
169 tool_name: tool_name.to_string(),
170 arguments,
171 })
172 }
173
174 pub fn tool_complete(call_id: &str, tool_name: &str, result: &str, success: bool, duration_ms: u64) -> Self {
176 AgentEvent::ToolComplete(ToolCompleteEvent {
177 call_id: call_id.to_string(),
178 tool_name: tool_name.to_string(),
179 result: result.to_string(),
180 success,
181 duration_ms,
182 })
183 }
184
185 pub fn turn_end(content: &str, tool_call_count: usize, iterations: usize, usage: TokenUsageInfo) -> Self {
187 AgentEvent::TurnEnd(TurnEndEvent {
188 content: content.to_string(),
189 tool_call_count,
190 iterations,
191 usage,
192 })
193 }
194
195 pub fn session_end(content: &str, total_iterations: usize, usage: TokenUsageInfo, reason: FinishReason) -> Self {
197 AgentEvent::SessionEnd(SessionEndEvent {
198 content: content.to_string(),
199 total_iterations,
200 usage,
201 finish_reason: reason,
202 })
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn test_turn_start_event() {
212 let event = AgentEvent::turn_start("Hello, world!");
213 assert!(matches!(event, AgentEvent::TurnStart(_)));
214 }
215
216 #[test]
217 fn test_thinking_delta_event() {
218 let event = AgentEvent::thinking_delta("Hello", true);
219 assert!(matches!(event, AgentEvent::ThinkingDelta(_)));
220 }
221
222 #[test]
223 fn test_tool_approval_event() {
224 let args = serde_json::json!({"command": "ls"});
225 let event = AgentEvent::tool_approval("call_1", "bash", args, true);
226 assert!(matches!(event, AgentEvent::ToolApproval(_)));
227 }
228
229 #[test]
230 fn test_tool_start_event() {
231 let args = serde_json::json!({"command": "ls"});
232 let event = AgentEvent::tool_start("call_1", "bash", args);
233 assert!(matches!(event, AgentEvent::ToolStart(_)));
234 }
235
236 #[test]
237 fn test_tool_complete_event() {
238 let event = AgentEvent::tool_complete("call_1", "bash", "file1\nfile2", true, 100);
239 assert!(matches!(event, AgentEvent::ToolComplete(_)));
240 }
241
242 #[test]
243 fn test_turn_end_event() {
244 let usage = TokenUsageInfo {
245 prompt_tokens: 100,
246 completion_tokens: 50,
247 total_tokens: 150,
248 };
249 let event = AgentEvent::turn_end("Done!", 2, 5, usage);
250 assert!(matches!(event, AgentEvent::TurnEnd(_)));
251 }
252
253 #[test]
254 fn test_session_end_event() {
255 let usage = TokenUsageInfo::default();
256 let event = AgentEvent::session_end("Goodbye!", 10, usage, FinishReason::Stop);
257 assert!(matches!(event, AgentEvent::SessionEnd(_)));
258 }
259
260 #[test]
261 fn test_finish_reason_variants() {
262 let stop = FinishReason::Stop;
263 let max_iter = FinishReason::MaxIterations;
264 let error = FinishReason::Error("something went wrong".to_string());
265 let unknown = FinishReason::UnknownTool("fake_tool".to_string());
266 let cancelled = FinishReason::Cancelled;
267
268 assert!(matches!(stop, FinishReason::Stop));
269 assert!(matches!(max_iter, FinishReason::MaxIterations));
270 assert!(matches!(error, FinishReason::Error(_)));
271 assert!(matches!(unknown, FinishReason::UnknownTool(_)));
272 assert!(matches!(cancelled, FinishReason::Cancelled));
273 }
274
275 #[test]
276 fn test_event_serialization() {
277 let event = AgentEvent::turn_start("test prompt");
278 let json = serde_json::to_string(&event).unwrap();
279 assert!(json.contains("TurnStart"));
280 assert!(json.contains("test prompt"));
281 }
282}