1use crate::events::{ApprovalDecision, Event, FileChangeType, SessionEndReason};
2use crate::{Item, ThreadEvent, TokenUsage};
3
4impl From<ThreadEvent> for Event {
5 fn from(thread_event: ThreadEvent) -> Self {
6 match thread_event {
7 ThreadEvent::ThreadStarted { thread_id } => Event::SessionStarted {
8 session_id: thread_id,
9 parent_id: None,
10 },
11 ThreadEvent::TurnStarted { turn_number } => Event::TurnStarted {
12 session_id: String::new(),
13 turn_number,
14 },
15 ThreadEvent::TurnCompleted { turn_number, usage } => Event::TurnCompleted {
16 session_id: String::new(),
17 turn_number,
18 usage,
19 },
20 ThreadEvent::ItemStarted { item } => match item {
21 Item::ToolCall { id, name, input } => Event::ToolExecutionStarted {
22 session_id: String::new(),
23 tool_call_id: id,
24 tool_name: name,
25 input,
26 },
27 Item::ApprovalRequest {
28 id,
29 tool_name,
30 input,
31 reason,
32 } => Event::ApprovalRequested {
33 session_id: String::new(),
34 request_id: id,
35 tool_name,
36 input,
37 reason,
38 },
39 _ => Event::MessagesTransform {
40 session_id: String::new(),
41 },
42 },
43 ThreadEvent::ItemCompleted { item } => match item {
44 Item::ToolResult {
45 tool_call_id,
46 output,
47 is_error,
48 } => Event::ToolExecutionCompleted {
49 session_id: String::new(),
50 tool_call_id: tool_call_id.clone(),
51 tool_name: String::new(),
52 output: serde_json::json!({ "output": output, "is_error": is_error }),
53 error: if is_error { Some(output) } else { None },
54 duration_ms: 0,
55 },
56 Item::ApprovalDecision {
57 request_id,
58 approved,
59 } => Event::ApprovalDecided {
60 session_id: String::new(),
61 request_id,
62 decision: if approved {
63 ApprovalDecision::Approved
64 } else {
65 ApprovalDecision::Denied { reason: None }
66 },
67 },
68 Item::FileChange {
69 path,
70 change_type,
71 patch,
72 } => Event::FileChanged {
73 session_id: String::new(),
74 path,
75 change_type: match change_type {
76 crate::protocol::FileChangeType::Create => FileChangeType::Create,
77 crate::protocol::FileChangeType::Modify => FileChangeType::Modify,
78 crate::protocol::FileChangeType::Delete => FileChangeType::Delete,
79 crate::protocol::FileChangeType::Rename => FileChangeType::Rename,
80 },
81 patch,
82 },
83 _ => Event::MessagesTransform {
84 session_id: String::new(),
85 },
86 },
87 ThreadEvent::ContentDelta { delta } => Event::ContentDelta {
88 session_id: String::new(),
89 delta,
90 },
91 ThreadEvent::ThinkingDelta { thinking } => Event::ThinkingDelta {
92 session_id: String::new(),
93 delta: thinking,
94 },
95 ThreadEvent::WaitingForInput { prompt } => Event::UserInputRequested {
96 session_id: String::new(),
97 prompt,
98 },
99 ThreadEvent::Error {
100 message,
101 recoverable,
102 } => Event::Error {
103 session_id: String::new(),
104 message,
105 recoverable,
106 },
107 ThreadEvent::ThreadCompleted { usage: _ } => Event::SessionEnded {
108 session_id: String::new(),
109 reason: SessionEndReason::Completed,
110 last_response: None,
111 },
112 ThreadEvent::ThreadCancelled => Event::SessionEnded {
113 session_id: String::new(),
114 reason: SessionEndReason::Cancelled,
115 last_response: None,
116 },
117 ThreadEvent::GoalVerificationStarted { goals, method } => {
118 Event::GoalVerificationStarted {
119 session_id: String::new(),
120 goals,
121 method,
122 }
123 }
124 ThreadEvent::GoalVerificationResult {
125 goal,
126 score,
127 target,
128 passed,
129 duration_ms,
130 } => Event::GoalVerificationResult {
131 session_id: String::new(),
132 goal,
133 score,
134 target,
135 passed,
136 duration_ms,
137 },
138 ThreadEvent::GoalVerificationCompleted {
139 all_passed,
140 passed_count,
141 total_count,
142 } => Event::GoalVerificationCompleted {
143 session_id: String::new(),
144 all_passed,
145 passed_count,
146 total_count,
147 },
148 ThreadEvent::BackgroundTaskSpawned {
149 task_id,
150 description,
151 agent,
152 } => Event::BackgroundTaskSpawned {
153 task_id,
154 description,
155 agent,
156 },
157 ThreadEvent::BackgroundTaskProgress {
158 task_id,
159 status,
160 message,
161 } => Event::BackgroundTaskProgress {
162 task_id,
163 status,
164 message,
165 },
166 ThreadEvent::BackgroundTaskCompleted {
167 task_id,
168 success,
169 result_preview,
170 duration_secs,
171 } => Event::BackgroundTaskCompleted {
172 task_id,
173 success,
174 result_preview,
175 duration_secs,
176 },
177 ThreadEvent::ModelSwitched { model, provider } => Event::ModelSwitched {
178 session_id: String::new(),
179 model,
180 provider,
181 },
182 ThreadEvent::PermissionEvaluated {
183 permission,
184 path,
185 action,
186 rule_matched,
187 } => Event::PermissionEvaluated {
188 session_id: String::new(),
189 permission,
190 path,
191 action: match action.as_str() {
192 "allow" => crate::events::PermissionAction::Allow,
193 "deny" => crate::events::PermissionAction::Deny,
194 _ => crate::events::PermissionAction::Ask,
195 },
196 rule_matched,
197 },
198 ThreadEvent::ApprovalCached {
199 tool_name,
200 pattern,
201 decision: _,
202 } => Event::ApprovalCached {
203 session_id: String::new(),
204 tool_name,
205 pattern,
206 },
207 ThreadEvent::CompactionStarted {
208 strategy,
209 token_count_before,
210 } => Event::CompactionStarted {
211 session_id: String::new(),
212 strategy,
213 token_count_before,
214 },
215 ThreadEvent::CompactionCompleted {
216 token_count_before,
217 token_count_after,
218 messages_removed,
219 } => Event::CompactionCompleted {
220 session_id: String::new(),
221 token_count_before,
222 token_count_after,
223 messages_removed,
224 },
225 ThreadEvent::TodoUpdated { todos } => Event::TodoUpdated {
226 session_id: String::new(),
227 todos,
228 },
229 _ => Event::MessagesTransform {
230 session_id: String::new(),
231 },
232 }
233 }
234}
235
236impl From<Event> for Option<ThreadEvent> {
237 fn from(event: Event) -> Self {
238 match event {
239 Event::SessionStarted { session_id, .. } => Some(ThreadEvent::ThreadStarted {
240 thread_id: session_id,
241 }),
242 Event::TurnStarted { turn_number, .. } => {
243 Some(ThreadEvent::TurnStarted { turn_number })
244 }
245 Event::TurnCompleted {
246 turn_number, usage, ..
247 } => Some(ThreadEvent::TurnCompleted { turn_number, usage }),
248 Event::ContentDelta { delta, .. } => Some(ThreadEvent::ContentDelta { delta }),
249 Event::ThinkingDelta { delta, .. } => {
250 Some(ThreadEvent::ThinkingDelta { thinking: delta })
251 }
252 Event::UserInputRequested { prompt, .. } => {
253 Some(ThreadEvent::WaitingForInput { prompt })
254 }
255 Event::Error {
256 message,
257 recoverable,
258 ..
259 } => Some(ThreadEvent::Error {
260 message,
261 recoverable,
262 }),
263 Event::SessionEnded { reason, .. } => match reason {
264 SessionEndReason::Completed => Some(ThreadEvent::ThreadCompleted {
265 usage: TokenUsage::default(),
266 }),
267 SessionEndReason::Cancelled => Some(ThreadEvent::ThreadCancelled),
268 _ => Some(ThreadEvent::ThreadCompleted {
269 usage: TokenUsage::default(),
270 }),
271 },
272 Event::GoalVerificationStarted { goals, method, .. } => {
273 Some(ThreadEvent::GoalVerificationStarted { goals, method })
274 }
275 Event::GoalVerificationResult {
276 goal,
277 score,
278 target,
279 passed,
280 duration_ms,
281 ..
282 } => Some(ThreadEvent::GoalVerificationResult {
283 goal,
284 score,
285 target,
286 passed,
287 duration_ms,
288 }),
289 Event::GoalVerificationCompleted {
290 all_passed,
291 passed_count,
292 total_count,
293 ..
294 } => Some(ThreadEvent::GoalVerificationCompleted {
295 all_passed,
296 passed_count,
297 total_count,
298 }),
299 Event::BackgroundTaskSpawned {
300 task_id,
301 description,
302 agent,
303 } => Some(ThreadEvent::BackgroundTaskSpawned {
304 task_id,
305 description,
306 agent,
307 }),
308 Event::BackgroundTaskProgress {
309 task_id,
310 status,
311 message,
312 } => Some(ThreadEvent::BackgroundTaskProgress {
313 task_id,
314 status,
315 message,
316 }),
317 Event::BackgroundTaskCompleted {
318 task_id,
319 success,
320 result_preview,
321 duration_secs,
322 } => Some(ThreadEvent::BackgroundTaskCompleted {
323 task_id,
324 success,
325 result_preview,
326 duration_secs,
327 }),
328 Event::ModelSwitched {
329 model, provider, ..
330 } => Some(ThreadEvent::ModelSwitched { model, provider }),
331 Event::PermissionEvaluated {
332 permission,
333 path,
334 action,
335 rule_matched,
336 ..
337 } => Some(ThreadEvent::PermissionEvaluated {
338 permission,
339 path,
340 action: match action {
341 crate::events::PermissionAction::Allow => "allow".to_string(),
342 crate::events::PermissionAction::Deny => "deny".to_string(),
343 crate::events::PermissionAction::Ask => "ask".to_string(),
344 },
345 rule_matched,
346 }),
347 Event::ApprovalCached {
348 tool_name, pattern, ..
349 } => Some(ThreadEvent::ApprovalCached {
350 tool_name,
351 pattern,
352 decision: "cached".to_string(),
353 }),
354 Event::TodoUpdated { todos, .. } => Some(ThreadEvent::TodoUpdated { todos }),
355 Event::CompactionStarted {
356 strategy,
357 token_count_before,
358 ..
359 } => Some(ThreadEvent::CompactionStarted {
360 strategy,
361 token_count_before,
362 }),
363 Event::CompactionCompleted {
364 token_count_before,
365 token_count_after,
366 messages_removed,
367 ..
368 } => Some(ThreadEvent::CompactionCompleted {
369 token_count_before,
370 token_count_after,
371 messages_removed,
372 }),
373 _ => None,
374 }
375 }
376}
377
378#[cfg(test)]
379mod tests {
380 use super::*;
381
382 #[test]
383 fn test_thread_event_to_event() {
384 let thread_event = ThreadEvent::ThreadStarted {
385 thread_id: "test_123".to_string(),
386 };
387 let event: Event = thread_event.into();
388
389 match event {
390 Event::SessionStarted { session_id, .. } => {
391 assert_eq!(session_id, "test_123");
392 }
393 _ => panic!("Wrong event type"),
394 }
395 }
396
397 #[test]
398 fn test_event_to_thread_event() {
399 let event = Event::ContentDelta {
400 session_id: "test".to_string(),
401 delta: "Hello".to_string(),
402 };
403 let thread_event: Option<ThreadEvent> = event.into();
404
405 match thread_event {
406 Some(ThreadEvent::ContentDelta { delta }) => {
407 assert_eq!(delta, "Hello");
408 }
409 _ => panic!("Wrong event type"),
410 }
411 }
412}