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(call_id: &str, tool_name: &str, arguments: serde_json::Value, auto_approved: bool) -> Self {
144 AgentEvent::ToolApproval(ToolApprovalEvent {
145 call_id: call_id.to_string(),
146 tool_name: tool_name.to_string(),
147 arguments,
148 auto_approved,
149 })
150 }
151
152 pub fn tool_start(call_id: &str, tool_name: &str, arguments: serde_json::Value) -> Self {
154 AgentEvent::ToolStart(ToolStartEvent {
155 call_id: call_id.to_string(),
156 tool_name: tool_name.to_string(),
157 arguments,
158 })
159 }
160
161 pub fn tool_complete(call_id: &str, tool_name: &str, result: &str, success: bool, duration_ms: u64) -> Self {
163 AgentEvent::ToolComplete(ToolCompleteEvent {
164 call_id: call_id.to_string(),
165 tool_name: tool_name.to_string(),
166 result: result.to_string(),
167 success,
168 duration_ms,
169 })
170 }
171
172 pub fn turn_end(content: &str, tool_call_count: usize, iterations: usize, usage: TokenUsageInfo) -> Self {
174 AgentEvent::TurnEnd(TurnEndEvent {
175 content: content.to_string(),
176 tool_call_count,
177 iterations,
178 usage,
179 })
180 }
181
182 pub fn session_end(content: &str, total_iterations: usize, usage: TokenUsageInfo, reason: FinishReason) -> Self {
184 AgentEvent::SessionEnd(SessionEndEvent {
185 content: content.to_string(),
186 total_iterations,
187 usage,
188 finish_reason: reason,
189 })
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn test_turn_start_event() {
199 let event = AgentEvent::turn_start("Hello, world!");
200 assert!(matches!(event, AgentEvent::TurnStart(_)));
201 }
202
203 #[test]
204 fn test_thinking_delta_event() {
205 let event = AgentEvent::thinking_delta("Hello", true);
206 assert!(matches!(event, AgentEvent::ThinkingDelta(_)));
207 }
208
209 #[test]
210 fn test_tool_approval_event() {
211 let args = serde_json::json!({"command": "ls"});
212 let event = AgentEvent::tool_approval("call_1", "bash", args, true);
213 assert!(matches!(event, AgentEvent::ToolApproval(_)));
214 }
215
216 #[test]
217 fn test_tool_start_event() {
218 let args = serde_json::json!({"command": "ls"});
219 let event = AgentEvent::tool_start("call_1", "bash", args);
220 assert!(matches!(event, AgentEvent::ToolStart(_)));
221 }
222
223 #[test]
224 fn test_tool_complete_event() {
225 let event = AgentEvent::tool_complete("call_1", "bash", "file1\nfile2", true, 100);
226 assert!(matches!(event, AgentEvent::ToolComplete(_)));
227 }
228
229 #[test]
230 fn test_turn_end_event() {
231 let usage = TokenUsageInfo {
232 prompt_tokens: 100,
233 completion_tokens: 50,
234 total_tokens: 150,
235 };
236 let event = AgentEvent::turn_end("Done!", 2, 5, usage);
237 assert!(matches!(event, AgentEvent::TurnEnd(_)));
238 }
239
240 #[test]
241 fn test_session_end_event() {
242 let usage = TokenUsageInfo::default();
243 let event = AgentEvent::session_end("Goodbye!", 10, usage, FinishReason::Stop);
244 assert!(matches!(event, AgentEvent::SessionEnd(_)));
245 }
246
247 #[test]
248 fn test_finish_reason_variants() {
249 let stop = FinishReason::Stop;
250 let max_iter = FinishReason::MaxIterations;
251 let error = FinishReason::Error("something went wrong".to_string());
252 let unknown = FinishReason::UnknownTool("fake_tool".to_string());
253 let cancelled = FinishReason::Cancelled;
254
255 assert!(matches!(stop, FinishReason::Stop));
256 assert!(matches!(max_iter, FinishReason::MaxIterations));
257 assert!(matches!(error, FinishReason::Error(_)));
258 assert!(matches!(unknown, FinishReason::UnknownTool(_)));
259 assert!(matches!(cancelled, FinishReason::Cancelled));
260 }
261
262 #[test]
263 fn test_event_serialization() {
264 let event = AgentEvent::turn_start("test prompt");
265 let json = serde_json::to_string(&event).unwrap();
266 assert!(json.contains("TurnStart"));
267 assert!(json.contains("test prompt"));
268 }
269}