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
99pub use crate::coordinator::FinishReason;
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
104#[serde(tag = "event_type")]
105pub enum AgentEvent {
106 TurnStart(TurnStartEvent),
108 ThinkingDelta(ThinkingDeltaEvent),
110 ToolApproval(ToolApprovalEvent),
112 ToolStart(ToolStartEvent),
114 ToolComplete(ToolCompleteEvent),
116 TurnEnd(TurnEndEvent),
118 SessionEnd(SessionEndEvent),
120}
121
122impl AgentEvent {
123 pub fn turn_start(prompt: &str) -> Self {
125 AgentEvent::TurnStart(TurnStartEvent {
126 prompt: prompt.to_string(),
127 timestamp_secs: std::time::SystemTime::now()
128 .duration_since(std::time::UNIX_EPOCH)
129 .unwrap_or_default()
130 .as_secs(),
131 })
132 }
133
134 pub fn thinking_delta(content: &str, is_first: bool) -> Self {
136 AgentEvent::ThinkingDelta(ThinkingDeltaEvent {
137 content: content.to_string(),
138 is_first,
139 })
140 }
141
142 pub fn tool_approval(
144 call_id: &str,
145 tool_name: &str,
146 arguments: serde_json::Value,
147 auto_approved: bool,
148 ) -> Self {
149 AgentEvent::ToolApproval(ToolApprovalEvent {
150 call_id: call_id.to_string(),
151 tool_name: tool_name.to_string(),
152 arguments,
153 auto_approved,
154 })
155 }
156
157 pub fn tool_start(call_id: &str, tool_name: &str, arguments: serde_json::Value) -> Self {
159 AgentEvent::ToolStart(ToolStartEvent {
160 call_id: call_id.to_string(),
161 tool_name: tool_name.to_string(),
162 arguments,
163 })
164 }
165
166 pub fn tool_complete(
168 call_id: &str,
169 tool_name: &str,
170 result: &str,
171 success: bool,
172 duration_ms: u64,
173 ) -> Self {
174 AgentEvent::ToolComplete(ToolCompleteEvent {
175 call_id: call_id.to_string(),
176 tool_name: tool_name.to_string(),
177 result: result.to_string(),
178 success,
179 duration_ms,
180 })
181 }
182
183 pub fn turn_end(
185 content: &str,
186 tool_call_count: usize,
187 iterations: usize,
188 usage: TokenUsageInfo,
189 ) -> Self {
190 AgentEvent::TurnEnd(TurnEndEvent {
191 content: content.to_string(),
192 tool_call_count,
193 iterations,
194 usage,
195 })
196 }
197
198 pub fn session_end(
200 content: &str,
201 total_iterations: usize,
202 usage: TokenUsageInfo,
203 reason: FinishReason,
204 ) -> Self {
205 AgentEvent::SessionEnd(SessionEndEvent {
206 content: content.to_string(),
207 total_iterations,
208 usage,
209 finish_reason: reason,
210 })
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217
218 #[test]
219 fn test_turn_start_event() {
220 let event = AgentEvent::turn_start("Hello, world!");
221 assert!(matches!(event, AgentEvent::TurnStart(_)));
222 }
223
224 #[test]
225 fn test_thinking_delta_event() {
226 let event = AgentEvent::thinking_delta("Hello", true);
227 assert!(matches!(event, AgentEvent::ThinkingDelta(_)));
228 }
229
230 #[test]
231 fn test_tool_approval_event() {
232 let args = serde_json::json!({"command": "ls"});
233 let event = AgentEvent::tool_approval("call_1", "bash", args, true);
234 assert!(matches!(event, AgentEvent::ToolApproval(_)));
235 }
236
237 #[test]
238 fn test_tool_start_event() {
239 let args = serde_json::json!({"command": "ls"});
240 let event = AgentEvent::tool_start("call_1", "bash", args);
241 assert!(matches!(event, AgentEvent::ToolStart(_)));
242 }
243
244 #[test]
245 fn test_tool_complete_event() {
246 let event = AgentEvent::tool_complete("call_1", "bash", "file1\nfile2", true, 100);
247 assert!(matches!(event, AgentEvent::ToolComplete(_)));
248 }
249
250 #[test]
251 fn test_turn_end_event() {
252 let usage = TokenUsageInfo {
253 prompt_tokens: 100,
254 completion_tokens: 50,
255 total_tokens: 150,
256 };
257 let event = AgentEvent::turn_end("Done!", 2, 5, usage);
258 assert!(matches!(event, AgentEvent::TurnEnd(_)));
259 }
260
261 #[test]
262 fn test_session_end_event() {
263 let usage = TokenUsageInfo::default();
264 let event = AgentEvent::session_end("Goodbye!", 10, usage, FinishReason::Stop);
265 assert!(matches!(event, AgentEvent::SessionEnd(_)));
266 }
267
268 #[test]
269 fn test_finish_reason_variants() {
270 let stop = FinishReason::Stop;
271 let max_iter = FinishReason::MaxIterations;
272 let error = FinishReason::Error("something went wrong".to_string());
273 let unknown = FinishReason::UnknownTool("fake_tool".to_string());
274 let cancelled = FinishReason::Cancelled;
275
276 assert!(matches!(stop, FinishReason::Stop));
277 assert!(matches!(max_iter, FinishReason::MaxIterations));
278 assert!(matches!(error, FinishReason::Error(_)));
279 assert!(matches!(unknown, FinishReason::UnknownTool(_)));
280 assert!(matches!(cancelled, FinishReason::Cancelled));
281 }
282
283 #[test]
284 fn test_event_serialization() {
285 let event = AgentEvent::turn_start("test prompt");
286 let json = serde_json::to_string(&event).unwrap();
287 assert!(json.contains("TurnStart"));
288 assert!(json.contains("test prompt"));
289 }
290}