1use serde::{Deserialize, Serialize};
8
9use super::content::ContentBlock;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
34#[serde(tag = "type", rename_all = "snake_case")]
35#[non_exhaustive]
36pub enum UserEvent {
37 #[serde(rename = "user.message")]
39 Message {
40 content: Vec<ContentBlock>,
42 },
43
44 #[serde(rename = "user.interrupt")]
46 Interrupt {},
47
48 #[serde(rename = "user.tool_confirmation")]
50 ToolConfirmation {
51 tool_use_id: String,
53 result: ConfirmationResult,
55 #[serde(skip_serializing_if = "Option::is_none")]
57 deny_message: Option<String>,
58 },
59
60 #[serde(rename = "user.custom_tool_result")]
62 CustomToolResult {
63 custom_tool_use_id: String,
65 content: Vec<ContentBlock>,
67 },
68
69 #[serde(rename = "user.tool_result")]
72 ToolResult {
73 tool_use_id: String,
75 content: Vec<ContentBlock>,
77 },
78
79 #[serde(rename = "user.define_outcome")]
81 DefineOutcome {
82 criteria: String,
84 },
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
89#[serde(rename_all = "lowercase")]
90pub enum ConfirmationResult {
91 Allow,
93 Deny,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
123#[serde(tag = "type", rename_all = "snake_case")]
124#[non_exhaustive]
125pub enum SessionEvent {
126 #[serde(rename = "agent.message")]
128 Message {
129 content: Vec<ContentBlock>,
131 seq: u64,
133 },
134
135 #[serde(rename = "agent.tool_use")]
137 ToolUse {
138 tool_use_id: String,
140 name: String,
142 input: serde_json::Value,
144 seq: u64,
146 },
147
148 #[serde(rename = "agent.custom_tool_use")]
151 CustomToolUse {
152 custom_tool_use_id: String,
154 name: String,
156 input: serde_json::Value,
158 seq: u64,
160 },
161
162 #[serde(rename = "agent.mcp_tool_use")]
164 McpToolUse {
165 tool_use_id: String,
167 name: String,
169 input: serde_json::Value,
171 seq: u64,
173 },
174
175 #[serde(rename = "status.running")]
177 StatusRunning {
178 seq: u64,
180 },
181
182 #[serde(rename = "status.idle")]
186 StatusIdle {
187 seq: u64,
189 stop_reason: Option<StopReason>,
191 #[serde(skip_serializing_if = "Option::is_none")]
194 usage: Option<crate::usage::UsageReport>,
195 },
196
197 #[serde(rename = "error")]
199 Error {
200 code: String,
202 message: String,
204 seq: u64,
206 },
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize)]
219#[serde(tag = "reason", rename_all = "snake_case")]
220#[non_exhaustive]
221pub enum StopReason {
222 EndTurn,
224 RequiresAction {
227 event_ids: Vec<String>,
229 },
230 MaxTokens,
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237 use serde_json::json;
238
239 #[test]
242 fn test_user_message_serialization() {
243 let event =
244 UserEvent::Message { content: vec![ContentBlock::Text { text: "Hello".to_string() }] };
245 let value = serde_json::to_value(&event).unwrap();
246 assert_eq!(value["type"], "user.message");
247 assert_eq!(value["content"][0]["type"], "text");
248 assert_eq!(value["content"][0]["text"], "Hello");
249 }
250
251 #[test]
252 fn test_user_interrupt_serialization() {
253 let event = UserEvent::Interrupt {};
254 let value = serde_json::to_value(&event).unwrap();
255 assert_eq!(value["type"], "user.interrupt");
256 }
257
258 #[test]
259 fn test_user_tool_confirmation_serialization() {
260 let event = UserEvent::ToolConfirmation {
261 tool_use_id: "tu_123".to_string(),
262 result: ConfirmationResult::Allow,
263 deny_message: None,
264 };
265 let value = serde_json::to_value(&event).unwrap();
266 assert_eq!(value["type"], "user.tool_confirmation");
267 assert_eq!(value["tool_use_id"], "tu_123");
268 assert_eq!(value["result"], "allow");
269 assert!(value.get("deny_message").is_none());
270 }
271
272 #[test]
273 fn test_user_tool_confirmation_with_deny() {
274 let event = UserEvent::ToolConfirmation {
275 tool_use_id: "tu_456".to_string(),
276 result: ConfirmationResult::Deny,
277 deny_message: Some("Not authorized".to_string()),
278 };
279 let value = serde_json::to_value(&event).unwrap();
280 assert_eq!(value["type"], "user.tool_confirmation");
281 assert_eq!(value["result"], "deny");
282 assert_eq!(value["deny_message"], "Not authorized");
283 }
284
285 #[test]
286 fn test_user_custom_tool_result_serialization() {
287 let event = UserEvent::CustomToolResult {
288 custom_tool_use_id: "ctu_789".to_string(),
289 content: vec![ContentBlock::Text { text: "result data".to_string() }],
290 };
291 let value = serde_json::to_value(&event).unwrap();
292 assert_eq!(value["type"], "user.custom_tool_result");
293 assert_eq!(value["custom_tool_use_id"], "ctu_789");
294 }
295
296 #[test]
297 fn test_user_tool_result_serialization() {
298 let event = UserEvent::ToolResult {
299 tool_use_id: "tu_self_001".to_string(),
300 content: vec![ContentBlock::Text { text: "tool output".to_string() }],
301 };
302 let value = serde_json::to_value(&event).unwrap();
303 assert_eq!(value["type"], "user.tool_result");
304 assert_eq!(value["tool_use_id"], "tu_self_001");
305 }
306
307 #[test]
308 fn test_user_define_outcome_serialization() {
309 let event =
310 UserEvent::DefineOutcome { criteria: "Task is completed successfully".to_string() };
311 let value = serde_json::to_value(&event).unwrap();
312 assert_eq!(value["type"], "user.define_outcome");
313 assert_eq!(value["criteria"], "Task is completed successfully");
314 }
315
316 #[test]
317 fn test_user_event_unknown_type_rejected() {
318 let json_str = r#"{"type": "user.unknown", "data": "something"}"#;
319 let result: Result<UserEvent, _> = serde_json::from_str(json_str);
320 assert!(result.is_err(), "Unknown type should be rejected");
321 }
322
323 #[test]
324 fn test_user_event_round_trip() {
325 let event = UserEvent::Message {
326 content: vec![ContentBlock::Text { text: "Round trip".to_string() }],
327 };
328 let json = serde_json::to_string(&event).unwrap();
329 let deserialized: UserEvent = serde_json::from_str(&json).unwrap();
330 match deserialized {
331 UserEvent::Message { content } => {
332 assert_eq!(content.len(), 1);
333 match &content[0] {
334 ContentBlock::Text { text } => assert_eq!(text, "Round trip"),
335 _ => panic!("Expected Text content block"),
336 }
337 }
338 _ => panic!("Expected Message variant"),
339 }
340 }
341
342 #[test]
345 fn test_session_message_serialization() {
346 let event = SessionEvent::Message {
347 content: vec![ContentBlock::Text { text: "Hi there".to_string() }],
348 seq: 1,
349 };
350 let value = serde_json::to_value(&event).unwrap();
351 assert_eq!(value["type"], "agent.message");
352 assert_eq!(value["seq"], 1);
353 assert_eq!(value["content"][0]["text"], "Hi there");
354 }
355
356 #[test]
357 fn test_session_tool_use_serialization() {
358 let event = SessionEvent::ToolUse {
359 tool_use_id: "tu_001".to_string(),
360 name: "search".to_string(),
361 input: json!({"query": "rust async"}),
362 seq: 2,
363 };
364 let value = serde_json::to_value(&event).unwrap();
365 assert_eq!(value["type"], "agent.tool_use");
366 assert_eq!(value["tool_use_id"], "tu_001");
367 assert_eq!(value["name"], "search");
368 assert_eq!(value["input"]["query"], "rust async");
369 assert_eq!(value["seq"], 2);
370 }
371
372 #[test]
373 fn test_session_custom_tool_use_serialization() {
374 let event = SessionEvent::CustomToolUse {
375 custom_tool_use_id: "ctu_001".to_string(),
376 name: "deploy".to_string(),
377 input: json!({"target": "production"}),
378 seq: 3,
379 };
380 let value = serde_json::to_value(&event).unwrap();
381 assert_eq!(value["type"], "agent.custom_tool_use");
382 assert_eq!(value["custom_tool_use_id"], "ctu_001");
383 assert_eq!(value["name"], "deploy");
384 assert_eq!(value["input"]["target"], "production");
385 assert_eq!(value["seq"], 3);
386 }
387
388 #[test]
389 fn test_session_mcp_tool_use_serialization() {
390 let event = SessionEvent::McpToolUse {
391 tool_use_id: "mcp_001".to_string(),
392 name: "file_read".to_string(),
393 input: json!({"path": "/tmp/data.txt"}),
394 seq: 4,
395 };
396 let value = serde_json::to_value(&event).unwrap();
397 assert_eq!(value["type"], "agent.mcp_tool_use");
398 assert_eq!(value["tool_use_id"], "mcp_001");
399 assert_eq!(value["name"], "file_read");
400 assert_eq!(value["input"]["path"], "/tmp/data.txt");
401 assert_eq!(value["seq"], 4);
402 }
403
404 #[test]
405 fn test_session_status_running_serialization() {
406 let event = SessionEvent::StatusRunning { seq: 5 };
407 let value = serde_json::to_value(&event).unwrap();
408 assert_eq!(value["type"], "status.running");
409 assert_eq!(value["seq"], 5);
410 }
411
412 #[test]
413 fn test_session_status_idle_serialization_no_stop_reason() {
414 let event = SessionEvent::StatusIdle { seq: 6, stop_reason: None, usage: None };
415 let value = serde_json::to_value(&event).unwrap();
416 assert_eq!(value["type"], "status.idle");
417 assert_eq!(value["seq"], 6);
418 assert_eq!(value["stop_reason"], json!(null));
419 }
420
421 #[test]
422 fn test_session_status_idle_with_end_turn() {
423 let event = SessionEvent::StatusIdle {
424 seq: 7,
425 stop_reason: Some(StopReason::EndTurn),
426 usage: None,
427 };
428 let value = serde_json::to_value(&event).unwrap();
429 assert_eq!(value["type"], "status.idle");
430 assert_eq!(value["seq"], 7);
431 assert_eq!(value["stop_reason"]["reason"], "end_turn");
432 }
433
434 #[test]
435 fn test_session_status_idle_with_requires_action() {
436 let event = SessionEvent::StatusIdle {
437 seq: 8,
438 stop_reason: Some(StopReason::RequiresAction {
439 event_ids: vec!["evt_001".to_string(), "evt_002".to_string()],
440 }),
441 usage: None,
442 };
443 let value = serde_json::to_value(&event).unwrap();
444 assert_eq!(value["type"], "status.idle");
445 assert_eq!(value["seq"], 8);
446 assert_eq!(value["stop_reason"]["reason"], "requires_action");
447 assert_eq!(value["stop_reason"]["event_ids"], json!(["evt_001", "evt_002"]));
448 }
449
450 #[test]
451 fn test_session_status_idle_with_max_tokens() {
452 let event = SessionEvent::StatusIdle {
453 seq: 9,
454 stop_reason: Some(StopReason::MaxTokens),
455 usage: None,
456 };
457 let value = serde_json::to_value(&event).unwrap();
458 assert_eq!(value["type"], "status.idle");
459 assert_eq!(value["seq"], 9);
460 assert_eq!(value["stop_reason"]["reason"], "max_tokens");
461 }
462
463 #[test]
464 fn test_session_error_serialization() {
465 let event = SessionEvent::Error {
466 code: "provider_error".to_string(),
467 message: "Model unavailable".to_string(),
468 seq: 10,
469 };
470 let value = serde_json::to_value(&event).unwrap();
471 assert_eq!(value["type"], "error");
472 assert_eq!(value["code"], "provider_error");
473 assert_eq!(value["message"], "Model unavailable");
474 assert_eq!(value["seq"], 10);
475 }
476
477 #[test]
478 fn test_session_event_seq_strictly_increasing() {
479 let events = vec![
481 SessionEvent::StatusRunning { seq: 0 },
482 SessionEvent::Message {
483 content: vec![ContentBlock::Text { text: "Hello".to_string() }],
484 seq: 1,
485 },
486 SessionEvent::ToolUse {
487 tool_use_id: "tu_1".to_string(),
488 name: "search".to_string(),
489 input: json!({}),
490 seq: 2,
491 },
492 SessionEvent::CustomToolUse {
493 custom_tool_use_id: "ctu_1".to_string(),
494 name: "deploy".to_string(),
495 input: json!({}),
496 seq: 3,
497 },
498 SessionEvent::McpToolUse {
499 tool_use_id: "mcp_1".to_string(),
500 name: "read".to_string(),
501 input: json!({}),
502 seq: 4,
503 },
504 SessionEvent::StatusIdle {
505 seq: 5,
506 stop_reason: Some(StopReason::EndTurn),
507 usage: None,
508 },
509 ];
510
511 let seqs: Vec<u64> = events
513 .iter()
514 .map(|e| match e {
515 SessionEvent::StatusRunning { seq }
516 | SessionEvent::Message { seq, .. }
517 | SessionEvent::ToolUse { seq, .. }
518 | SessionEvent::CustomToolUse { seq, .. }
519 | SessionEvent::McpToolUse { seq, .. }
520 | SessionEvent::StatusIdle { seq, .. }
521 | SessionEvent::Error { seq, .. } => *seq,
522 })
523 .collect();
524
525 for window in seqs.windows(2) {
526 assert!(
527 window[1] > window[0],
528 "seq must be strictly increasing: {} should be > {}",
529 window[1],
530 window[0]
531 );
532 }
533 }
534
535 #[test]
536 fn test_session_event_round_trip() {
537 let event = SessionEvent::CustomToolUse {
538 custom_tool_use_id: "ctu_rt".to_string(),
539 name: "execute".to_string(),
540 input: json!({"command": "ls -la"}),
541 seq: 42,
542 };
543 let json = serde_json::to_string(&event).unwrap();
544 let deserialized: SessionEvent = serde_json::from_str(&json).unwrap();
545 match deserialized {
546 SessionEvent::CustomToolUse { custom_tool_use_id, name, input, seq } => {
547 assert_eq!(custom_tool_use_id, "ctu_rt");
548 assert_eq!(name, "execute");
549 assert_eq!(input["command"], "ls -la");
550 assert_eq!(seq, 42);
551 }
552 _ => panic!("Expected CustomToolUse variant"),
553 }
554 }
555
556 #[test]
559 fn test_stop_reason_end_turn_serialization() {
560 let reason = StopReason::EndTurn;
561 let value = serde_json::to_value(&reason).unwrap();
562 assert_eq!(value, json!({"reason": "end_turn"}));
563 }
564
565 #[test]
566 fn test_stop_reason_requires_action_serialization() {
567 let reason = StopReason::RequiresAction {
568 event_ids: vec!["evt_a".to_string(), "evt_b".to_string()],
569 };
570 let value = serde_json::to_value(&reason).unwrap();
571 assert_eq!(value, json!({"reason": "requires_action", "event_ids": ["evt_a", "evt_b"]}));
572 }
573
574 #[test]
575 fn test_stop_reason_max_tokens_serialization() {
576 let reason = StopReason::MaxTokens;
577 let value = serde_json::to_value(&reason).unwrap();
578 assert_eq!(value, json!({"reason": "max_tokens"}));
579 }
580
581 #[test]
582 fn test_stop_reason_round_trip() {
583 let reasons = vec![
584 StopReason::EndTurn,
585 StopReason::RequiresAction { event_ids: vec!["id1".to_string()] },
586 StopReason::MaxTokens,
587 ];
588 for reason in reasons {
589 let json = serde_json::to_string(&reason).unwrap();
590 let deserialized: StopReason = serde_json::from_str(&json).unwrap();
591 let re_serialized = serde_json::to_string(&deserialized).unwrap();
592 assert_eq!(json, re_serialized);
593 }
594 }
595}