1use serde::{Deserialize, Serialize};
37
38use crate::compaction::{CompactTrigger, CompactionResult, CompactionStrategy, TokenBudgetState};
39use crate::tool::ToolOutput;
40use crate::types::{AgentId, FinishReason, ModelId, SessionId, ToolCallId};
41use crate::usage::Usage;
42
43#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
90#[serde(tag = "type", rename_all = "snake_case")]
91pub enum AgentEvent {
92 AgentStarted {
96 session_id: SessionId,
97 agent_name: String,
98 model_id: ModelId,
99 },
100
101 AgentEnded {
103 session_id: SessionId,
104 finish_reason: AgentFinishReason,
105 #[serde(skip_serializing_if = "Option::is_none")]
107 total_usage: Option<Usage>,
108 steps: u32,
110 },
111
112 StepStarted {
116 step_index: u32,
117 model_id: ModelId,
118 agent_name: String,
119 },
120
121 StepEnded {
123 step_index: u32,
124 finish_reason: FinishReason,
125 #[serde(skip_serializing_if = "Option::is_none")]
127 usage: Option<Usage>,
128 },
129
130 StepFailed {
132 step_index: u32,
133 error: String,
134 },
135
136 TextStarted {
140 content_index: usize,
141 },
142
143 TextDelta {
145 content_index: usize,
146 delta: String,
147 },
148
149 TextEnded {
151 content_index: usize,
152 text: String,
153 },
154
155 ReasoningStarted {
159 content_index: usize,
160 },
161
162 ReasoningDelta {
164 content_index: usize,
165 delta: String,
166 },
167
168 ReasoningEnded {
170 content_index: usize,
171 text: String,
172 },
173
174 ToolInputStarted {
178 call_id: ToolCallId,
179 tool_name: String,
180 },
181
182 ToolInputDelta {
184 call_id: ToolCallId,
185 delta: String,
186 },
187
188 ToolInputEnded {
190 call_id: ToolCallId,
191 arguments: serde_json::Value,
192 },
193
194 ToolCalled {
196 call_id: ToolCallId,
197 tool_name: String,
198 arguments: serde_json::Value,
199 },
200
201 ToolProgress {
203 call_id: ToolCallId,
204 message: String,
206 #[serde(skip_serializing_if = "Option::is_none")]
208 data: Option<serde_json::Value>,
209 },
210
211 ToolSucceeded {
213 call_id: ToolCallId,
214 tool_name: String,
215 output: ToolOutput,
216 },
217
218 ToolFailed {
220 call_id: ToolCallId,
221 tool_name: String,
222 error: String,
223 #[serde(default)]
225 is_retryable: bool,
226 },
227
228 Retried {
232 attempt: u32,
234 error: String,
236 delay_ms: u64,
238 },
239
240 PruneCompleted {
247 tokens_freed: u64,
249 parts_pruned: usize,
251 },
252
253 CompactionStarted {
255 trigger: CompactTrigger,
257 strategy: CompactionStrategy,
259 tokens_before: u64,
261 },
262
263 CompactionDelta {
265 delta: String,
266 },
267
268 CompactionEnded {
270 result: CompactionResult,
272 },
273
274 TokenBudgetChanged {
278 used_tokens: u64,
280 context_window: u64,
282 state: TokenBudgetState,
284 },
285
286 ModelSwitched {
290 #[serde(skip_serializing_if = "Option::is_none")]
291 from: Option<ModelId>,
292 to: ModelId,
293 },
294
295 AgentSwitched {
297 #[serde(skip_serializing_if = "Option::is_none")]
298 from_agent: Option<AgentId>,
299 to_agent: AgentId,
300 agent_name: String,
301 },
302
303 UserPrompted {
305 content_preview: String,
307 },
308}
309
310#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
329#[serde(rename_all = "snake_case")]
330pub enum AgentFinishReason {
331 Completed,
333 UserAbort,
335 MaxSteps,
337 TokenBudget,
339 Error,
341 Timeout,
343}
344
345impl std::fmt::Display for AgentFinishReason {
346 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
347 match self {
348 Self::Completed => write!(f, "completed"),
349 Self::UserAbort => write!(f, "user_abort"),
350 Self::MaxSteps => write!(f, "max_steps"),
351 Self::TokenBudget => write!(f, "token_budget"),
352 Self::Error => write!(f, "error"),
353 Self::Timeout => write!(f, "timeout"),
354 }
355 }
356}
357
358impl AgentEvent {
363 pub fn kind(&self) -> AgentEventKind {
365 match self {
366 Self::AgentStarted { .. } => AgentEventKind::AgentStarted,
367 Self::AgentEnded { .. } => AgentEventKind::AgentEnded,
368 Self::StepStarted { .. } => AgentEventKind::StepStarted,
369 Self::StepEnded { .. } => AgentEventKind::StepEnded,
370 Self::StepFailed { .. } => AgentEventKind::StepFailed,
371 Self::TextStarted { .. } => AgentEventKind::TextStarted,
372 Self::TextDelta { .. } => AgentEventKind::TextDelta,
373 Self::TextEnded { .. } => AgentEventKind::TextEnded,
374 Self::ReasoningStarted { .. } => AgentEventKind::ReasoningStarted,
375 Self::ReasoningDelta { .. } => AgentEventKind::ReasoningDelta,
376 Self::ReasoningEnded { .. } => AgentEventKind::ReasoningEnded,
377 Self::ToolInputStarted { .. } => AgentEventKind::ToolInputStarted,
378 Self::ToolInputDelta { .. } => AgentEventKind::ToolInputDelta,
379 Self::ToolInputEnded { .. } => AgentEventKind::ToolInputEnded,
380 Self::ToolCalled { .. } => AgentEventKind::ToolCalled,
381 Self::ToolProgress { .. } => AgentEventKind::ToolProgress,
382 Self::ToolSucceeded { .. } => AgentEventKind::ToolSucceeded,
383 Self::ToolFailed { .. } => AgentEventKind::ToolFailed,
384 Self::Retried { .. } => AgentEventKind::Retried,
385 Self::PruneCompleted { .. } => AgentEventKind::PruneCompleted,
386 Self::CompactionStarted { .. } => AgentEventKind::CompactionStarted,
387 Self::CompactionDelta { .. } => AgentEventKind::CompactionDelta,
388 Self::CompactionEnded { .. } => AgentEventKind::CompactionEnded,
389 Self::TokenBudgetChanged { .. } => AgentEventKind::TokenBudgetChanged,
390 Self::ModelSwitched { .. } => AgentEventKind::ModelSwitched,
391 Self::AgentSwitched { .. } => AgentEventKind::AgentSwitched,
392 Self::UserPrompted { .. } => AgentEventKind::UserPrompted,
393 }
394 }
395
396 pub fn is_terminal(&self) -> bool {
398 matches!(self, Self::AgentEnded { .. })
399 }
400
401 pub fn is_text_delta(&self) -> bool {
403 matches!(self, Self::TextDelta { .. })
404 }
405
406 pub fn as_text_delta(&self) -> Option<&str> {
408 match self {
409 Self::TextDelta { delta, .. } => Some(delta.as_str()),
410 _ => None,
411 }
412 }
413
414 pub fn is_tool_event(&self) -> bool {
416 matches!(
417 self,
418 Self::ToolInputStarted { .. }
419 | Self::ToolInputDelta { .. }
420 | Self::ToolInputEnded { .. }
421 | Self::ToolCalled { .. }
422 | Self::ToolProgress { .. }
423 | Self::ToolSucceeded { .. }
424 | Self::ToolFailed { .. }
425 )
426 }
427
428 pub fn tool_call_id(&self) -> Option<&ToolCallId> {
430 match self {
431 Self::ToolInputStarted { call_id, .. }
432 | Self::ToolInputDelta { call_id, .. }
433 | Self::ToolInputEnded { call_id, .. }
434 | Self::ToolCalled { call_id, .. }
435 | Self::ToolProgress { call_id, .. }
436 | Self::ToolSucceeded { call_id, .. }
437 | Self::ToolFailed { call_id, .. } => Some(call_id),
438 _ => None,
439 }
440 }
441
442 pub fn is_delta(&self) -> bool {
444 matches!(
445 self,
446 Self::TextDelta { .. }
447 | Self::ReasoningDelta { .. }
448 | Self::ToolInputDelta { .. }
449 | Self::CompactionDelta { .. }
450 )
451 }
452}
453
454#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
465#[serde(rename_all = "snake_case")]
466pub enum AgentEventKind {
467 AgentStarted,
469 AgentEnded,
470 StepStarted,
472 StepEnded,
473 StepFailed,
474 TextStarted,
476 TextDelta,
477 TextEnded,
478 ReasoningStarted,
480 ReasoningDelta,
481 ReasoningEnded,
482 ToolInputStarted,
484 ToolInputDelta,
485 ToolInputEnded,
486 ToolCalled,
487 ToolProgress,
488 ToolSucceeded,
489 ToolFailed,
490 Retried,
492 PruneCompleted,
494 CompactionStarted,
495 CompactionDelta,
496 CompactionEnded,
497 TokenBudgetChanged,
498 ModelSwitched,
500 AgentSwitched,
501 UserPrompted,
502}
503
504impl std::fmt::Display for AgentEventKind {
505 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
506 let s = match self {
507 Self::AgentStarted => "agent_started",
508 Self::AgentEnded => "agent_ended",
509 Self::StepStarted => "step_started",
510 Self::StepEnded => "step_ended",
511 Self::StepFailed => "step_failed",
512 Self::TextStarted => "text_started",
513 Self::TextDelta => "text_delta",
514 Self::TextEnded => "text_ended",
515 Self::ReasoningStarted => "reasoning_started",
516 Self::ReasoningDelta => "reasoning_delta",
517 Self::ReasoningEnded => "reasoning_ended",
518 Self::ToolInputStarted => "tool_input_started",
519 Self::ToolInputDelta => "tool_input_delta",
520 Self::ToolInputEnded => "tool_input_ended",
521 Self::ToolCalled => "tool_called",
522 Self::ToolProgress => "tool_progress",
523 Self::ToolSucceeded => "tool_succeeded",
524 Self::ToolFailed => "tool_failed",
525 Self::Retried => "retried",
526 Self::PruneCompleted => "prune_completed",
527 Self::CompactionStarted => "compaction_started",
528 Self::CompactionDelta => "compaction_delta",
529 Self::CompactionEnded => "compaction_ended",
530 Self::TokenBudgetChanged => "token_budget_changed",
531 Self::ModelSwitched => "model_switched",
532 Self::AgentSwitched => "agent_switched",
533 Self::UserPrompted => "user_prompted",
534 };
535 write!(f, "{s}")
536 }
537}
538
539
540#[cfg(test)]
545mod tests {
546 use super::*;
547 use serde_json::json;
548
549 #[test]
552 fn test_agent_finish_reason_serde_roundtrip() {
553 let reasons = [
554 AgentFinishReason::Completed,
555 AgentFinishReason::UserAbort,
556 AgentFinishReason::MaxSteps,
557 AgentFinishReason::TokenBudget,
558 AgentFinishReason::Error,
559 AgentFinishReason::Timeout,
560 ];
561 for reason in &reasons {
562 let json = serde_json::to_string(reason).unwrap();
563 let restored: AgentFinishReason = serde_json::from_str(&json).unwrap();
564 assert_eq!(reason, &restored);
565 }
566 }
567
568 #[test]
569 fn test_agent_finish_reason_display() {
570 assert_eq!(AgentFinishReason::Completed.to_string(), "completed");
571 assert_eq!(AgentFinishReason::UserAbort.to_string(), "user_abort");
572 assert_eq!(AgentFinishReason::MaxSteps.to_string(), "max_steps");
573 }
574
575 #[test]
578 fn test_compact_trigger_serde() {
579 for trigger in [
580 CompactTrigger::Auto,
581 CompactTrigger::Manual,
582 CompactTrigger::Overflow,
583 CompactTrigger::Idle,
584 ] {
585 let json = serde_json::to_string(&trigger).unwrap();
586 let restored: CompactTrigger = serde_json::from_str(&json).unwrap();
587 assert_eq!(trigger, restored);
588 }
589 }
590
591 #[test]
594 fn test_agent_started_serde() {
595 let event = AgentEvent::AgentStarted {
596 session_id: SessionId::new(),
597 agent_name: "coder".into(),
598 model_id: ModelId::new("gpt-4o"),
599 };
600 let json = serde_json::to_string(&event).unwrap();
601 assert!(json.contains(r#""type":"agent_started""#));
602 assert!(json.contains(r#""agent_name":"coder""#));
603 let restored: AgentEvent = serde_json::from_str(&json).unwrap();
604 assert_eq!(event, restored);
605 }
606
607 #[test]
608 fn test_agent_ended_serde() {
609 let event = AgentEvent::AgentEnded {
610 session_id: SessionId::new(),
611 finish_reason: AgentFinishReason::Completed,
612 total_usage: None,
613 steps: 3,
614 };
615 let json = serde_json::to_string(&event).unwrap();
616 assert!(json.contains(r#""type":"agent_ended""#));
617 assert!(!json.contains("total_usage"));
618 assert!(json.contains(r#""steps":3"#));
619 let restored: AgentEvent = serde_json::from_str(&json).unwrap();
620 assert_eq!(event, restored);
621 }
622
623 #[test]
624 fn test_agent_ended_with_usage() {
625 let event = AgentEvent::AgentEnded {
626 session_id: SessionId::new(),
627 finish_reason: AgentFinishReason::MaxSteps,
628 total_usage: Some(Usage {
629 input_tokens: 1000,
630 output_tokens: 500,
631 total_tokens: 1500,
632 ..Default::default()
633 }),
634 steps: 10,
635 };
636 let json = serde_json::to_string(&event).unwrap();
637 assert!(json.contains("total_usage"));
638 let restored: AgentEvent = serde_json::from_str(&json).unwrap();
639 assert_eq!(event, restored);
640 }
641
642 #[test]
645 fn test_step_started_serde() {
646 let event = AgentEvent::StepStarted {
647 step_index: 0,
648 model_id: ModelId::new("claude-sonnet-4-20250514"),
649 agent_name: "default".into(),
650 };
651 let json = serde_json::to_string(&event).unwrap();
652 assert!(json.contains(r#""type":"step_started""#));
653 let restored: AgentEvent = serde_json::from_str(&json).unwrap();
654 assert_eq!(event, restored);
655 }
656
657 #[test]
658 fn test_step_ended_serde() {
659 let event = AgentEvent::StepEnded {
660 step_index: 0,
661 finish_reason: FinishReason::Stop,
662 usage: None,
663 };
664 let json = serde_json::to_string(&event).unwrap();
665 assert!(json.contains(r#""type":"step_ended""#));
666 let restored: AgentEvent = serde_json::from_str(&json).unwrap();
667 assert_eq!(event, restored);
668 }
669
670 #[test]
671 fn test_step_failed_serde() {
672 let event = AgentEvent::StepFailed {
673 step_index: 2,
674 error: "rate limit exceeded".into(),
675 };
676 let json = serde_json::to_string(&event).unwrap();
677 assert!(json.contains(r#""type":"step_failed""#));
678 let restored: AgentEvent = serde_json::from_str(&json).unwrap();
679 assert_eq!(event, restored);
680 }
681
682 #[test]
685 fn test_text_lifecycle_serde() {
686 let started = AgentEvent::TextStarted { content_index: 0 };
687 let delta = AgentEvent::TextDelta {
688 content_index: 0,
689 delta: "Hello ".into(),
690 };
691 let ended = AgentEvent::TextEnded {
692 content_index: 0,
693 text: "Hello world".into(),
694 };
695 for event in [&started, &delta, &ended] {
696 let json = serde_json::to_string(event).unwrap();
697 let restored: AgentEvent = serde_json::from_str(&json).unwrap();
698 assert_eq!(event, &restored);
699 }
700 }
701
702 #[test]
705 fn test_reasoning_lifecycle_serde() {
706 let started = AgentEvent::ReasoningStarted { content_index: 1 };
707 let delta = AgentEvent::ReasoningDelta {
708 content_index: 1,
709 delta: "Let me think...".into(),
710 };
711 let ended = AgentEvent::ReasoningEnded {
712 content_index: 1,
713 text: "Let me think about this carefully.".into(),
714 };
715 for event in [&started, &delta, &ended] {
716 let json = serde_json::to_string(event).unwrap();
717 let restored: AgentEvent = serde_json::from_str(&json).unwrap();
718 assert_eq!(event, &restored);
719 }
720 }
721
722 #[test]
725 fn test_tool_input_lifecycle_serde() {
726 let call_id = ToolCallId::new("call_abc123");
727
728 let started = AgentEvent::ToolInputStarted {
729 call_id: call_id.clone(),
730 tool_name: "read_file".into(),
731 };
732 let delta = AgentEvent::ToolInputDelta {
733 call_id: call_id.clone(),
734 delta: r#"{"path": "src/"#.into(),
735 };
736 let ended = AgentEvent::ToolInputEnded {
737 call_id: call_id.clone(),
738 arguments: json!({"path": "src/main.rs"}),
739 };
740
741 for event in [&started, &delta, &ended] {
742 let json = serde_json::to_string(event).unwrap();
743 let restored: AgentEvent = serde_json::from_str(&json).unwrap();
744 assert_eq!(event, &restored);
745 }
746 }
747
748 #[test]
749 fn test_tool_called_serde() {
750 let event = AgentEvent::ToolCalled {
751 call_id: ToolCallId::new("call_1"),
752 tool_name: "bash".into(),
753 arguments: json!({"command": "ls -la"}),
754 };
755 let json = serde_json::to_string(&event).unwrap();
756 assert!(json.contains(r#""type":"tool_called""#));
757 let restored: AgentEvent = serde_json::from_str(&json).unwrap();
758 assert_eq!(event, restored);
759 }
760
761 #[test]
762 fn test_tool_progress_serde() {
763 let event = AgentEvent::ToolProgress {
764 call_id: ToolCallId::new("call_1"),
765 message: "Reading file...".into(),
766 data: Some(json!({"bytes_read": 1024})),
767 };
768 let json = serde_json::to_string(&event).unwrap();
769 assert!(json.contains(r#""type":"tool_progress""#));
770 let restored: AgentEvent = serde_json::from_str(&json).unwrap();
771 assert_eq!(event, restored);
772 }
773
774 #[test]
775 fn test_tool_progress_no_data_serde() {
776 let event = AgentEvent::ToolProgress {
777 call_id: ToolCallId::new("call_1"),
778 message: "Working...".into(),
779 data: None,
780 };
781 let json = serde_json::to_string(&event).unwrap();
782 assert!(!json.contains(r#""data""#));
783 let restored: AgentEvent = serde_json::from_str(&json).unwrap();
784 assert_eq!(event, restored);
785 }
786
787 #[test]
788 fn test_tool_succeeded_serde() {
789 let event = AgentEvent::ToolSucceeded {
790 call_id: ToolCallId::new("call_1"),
791 tool_name: "read_file".into(),
792 output: ToolOutput::success("file contents here"),
793 };
794 let json = serde_json::to_string(&event).unwrap();
795 assert!(json.contains(r#""type":"tool_succeeded""#));
796 let restored: AgentEvent = serde_json::from_str(&json).unwrap();
797 assert_eq!(event, restored);
798 }
799
800 #[test]
801 fn test_tool_failed_serde() {
802 let event = AgentEvent::ToolFailed {
803 call_id: ToolCallId::new("call_2"),
804 tool_name: "write_file".into(),
805 error: "permission denied".into(),
806 is_retryable: false,
807 };
808 let json = serde_json::to_string(&event).unwrap();
809 assert!(json.contains(r#""type":"tool_failed""#));
810 let restored: AgentEvent = serde_json::from_str(&json).unwrap();
811 assert_eq!(event, restored);
812 }
813
814 #[test]
815 fn test_tool_failed_retryable_default() {
816 let json = r#"{
817 "type":"tool_failed",
818 "call_id":"call_x",
819 "tool_name":"bash",
820 "error":"timeout"
821 }"#;
822 let event: AgentEvent = serde_json::from_str(json).unwrap();
823 if let AgentEvent::ToolFailed { is_retryable, .. } = event {
824 assert!(!is_retryable);
825 } else {
826 panic!("expected ToolFailed");
827 }
828 }
829
830 #[test]
833 fn test_retried_serde() {
834 let event = AgentEvent::Retried {
835 attempt: 2,
836 error: "rate limit".into(),
837 delay_ms: 5000,
838 };
839 let json = serde_json::to_string(&event).unwrap();
840 assert!(json.contains(r#""type":"retried""#));
841 let restored: AgentEvent = serde_json::from_str(&json).unwrap();
842 assert_eq!(event, restored);
843 }
844
845 #[test]
848 fn test_prune_completed_serde() {
849 let event = AgentEvent::PruneCompleted {
850 tokens_freed: 25_000,
851 parts_pruned: 12,
852 };
853 let json = serde_json::to_string(&event).unwrap();
854 assert!(json.contains(r#""type":"prune_completed""#));
855 let restored: AgentEvent = serde_json::from_str(&json).unwrap();
856 assert_eq!(event, restored);
857 }
858
859 #[test]
860 fn test_compaction_lifecycle_serde() {
861 let started = AgentEvent::CompactionStarted {
862 trigger: CompactTrigger::Auto,
863 strategy: CompactionStrategy::Summarize,
864 tokens_before: 150_000,
865 };
866 let delta = AgentEvent::CompactionDelta {
867 delta: "Summary: ...".into(),
868 };
869 let ended = AgentEvent::CompactionEnded {
870 result: CompactionResult {
871 summary: "The user asked about Rust error handling...".into(),
872 short_summary: Some("Rust error handling".into()),
873 trigger: CompactTrigger::Auto,
874 tokens_before: 150_000,
875 tokens_after: Some(5_000),
876 messages_compacted: 40,
877 messages_kept: 8,
878 success: true,
879 },
880 };
881 for event in [&started, &delta, &ended] {
882 let json = serde_json::to_string(event).unwrap();
883 let restored: AgentEvent = serde_json::from_str(&json).unwrap();
884 assert_eq!(event, &restored);
885 }
886 }
887
888 #[test]
889 fn test_token_budget_changed_serde() {
890 let event = AgentEvent::TokenBudgetChanged {
891 used_tokens: 170_000,
892 context_window: 200_000,
893 state: TokenBudgetState::Warning {
894 percent_remaining: 0.15,
895 },
896 };
897 let json = serde_json::to_string(&event).unwrap();
898 assert!(json.contains(r#""type":"token_budget_changed""#));
899 let restored: AgentEvent = serde_json::from_str(&json).unwrap();
900 assert_eq!(event, restored);
901 }
902
903 #[test]
906 fn test_model_switched_serde() {
907 let event = AgentEvent::ModelSwitched {
908 from: Some(ModelId::new("gpt-4o")),
909 to: ModelId::new("claude-sonnet-4-20250514"),
910 };
911 let json = serde_json::to_string(&event).unwrap();
912 assert!(json.contains(r#""type":"model_switched""#));
913 let restored: AgentEvent = serde_json::from_str(&json).unwrap();
914 assert_eq!(event, restored);
915 }
916
917 #[test]
918 fn test_model_switched_no_from_serde() {
919 let event = AgentEvent::ModelSwitched {
920 from: None,
921 to: ModelId::new("gpt-4o"),
922 };
923 let json = serde_json::to_string(&event).unwrap();
924 assert!(!json.contains(r#""from""#));
925 let restored: AgentEvent = serde_json::from_str(&json).unwrap();
926 assert_eq!(event, restored);
927 }
928
929 #[test]
930 fn test_agent_switched_serde() {
931 let event = AgentEvent::AgentSwitched {
932 from_agent: None,
933 to_agent: AgentId::new(),
934 agent_name: "code-reviewer".into(),
935 };
936 let json = serde_json::to_string(&event).unwrap();
937 assert!(json.contains(r#""type":"agent_switched""#));
938 let restored: AgentEvent = serde_json::from_str(&json).unwrap();
939 assert_eq!(event, restored);
940 }
941
942 #[test]
943 fn test_user_prompted_serde() {
944 let event = AgentEvent::UserPrompted {
945 content_preview: "Fix the bug in main.rs".into(),
946 };
947 let json = serde_json::to_string(&event).unwrap();
948 assert!(json.contains(r#""type":"user_prompted""#));
949 let restored: AgentEvent = serde_json::from_str(&json).unwrap();
950 assert_eq!(event, restored);
951 }
952
953 #[test]
956 fn test_agent_event_kind_display() {
957 assert_eq!(AgentEventKind::AgentStarted.to_string(), "agent_started");
958 assert_eq!(AgentEventKind::ToolCalled.to_string(), "tool_called");
959 assert_eq!(
960 AgentEventKind::CompactionEnded.to_string(),
961 "compaction_ended"
962 );
963 }
964
965
966 #[test]
969 fn test_kind_method() {
970 let event = AgentEvent::TextDelta {
971 content_index: 0,
972 delta: "hi".into(),
973 };
974 assert_eq!(event.kind(), AgentEventKind::TextDelta);
975
976 let event = AgentEvent::ToolCalled {
977 call_id: ToolCallId::new("c"),
978 tool_name: "t".into(),
979 arguments: json!({}),
980 };
981 assert_eq!(event.kind(), AgentEventKind::ToolCalled);
982 }
983
984 #[test]
985 fn test_is_terminal() {
986 let ended = AgentEvent::AgentEnded {
987 session_id: SessionId::new(),
988 finish_reason: AgentFinishReason::Completed,
989 total_usage: None,
990 steps: 1,
991 };
992 assert!(ended.is_terminal());
993
994 let started = AgentEvent::AgentStarted {
995 session_id: SessionId::new(),
996 agent_name: "a".into(),
997 model_id: ModelId::new("m"),
998 };
999 assert!(!started.is_terminal());
1000 }
1001
1002 #[test]
1003 fn test_is_text_delta() {
1004 let delta = AgentEvent::TextDelta {
1005 content_index: 0,
1006 delta: "hello".into(),
1007 };
1008 assert!(delta.is_text_delta());
1009 assert_eq!(delta.as_text_delta(), Some("hello"));
1010
1011 let other = AgentEvent::ReasoningDelta {
1012 content_index: 0,
1013 delta: "think".into(),
1014 };
1015 assert!(!other.is_text_delta());
1016 assert_eq!(other.as_text_delta(), None);
1017 }
1018
1019 #[test]
1020 fn test_is_tool_event() {
1021 let tool = AgentEvent::ToolCalled {
1022 call_id: ToolCallId::new("c"),
1023 tool_name: "t".into(),
1024 arguments: json!({}),
1025 };
1026 assert!(tool.is_tool_event());
1027 assert_eq!(tool.tool_call_id(), Some(&ToolCallId::new("c")));
1028
1029 let text = AgentEvent::TextDelta {
1030 content_index: 0,
1031 delta: "hi".into(),
1032 };
1033 assert!(!text.is_tool_event());
1034 assert_eq!(text.tool_call_id(), None);
1035 }
1036
1037 #[test]
1038 fn test_is_delta() {
1039 assert!(AgentEvent::TextDelta {
1040 content_index: 0,
1041 delta: "a".into()
1042 }
1043 .is_delta());
1044 assert!(AgentEvent::ReasoningDelta {
1045 content_index: 0,
1046 delta: "b".into()
1047 }
1048 .is_delta());
1049 assert!(AgentEvent::ToolInputDelta {
1050 call_id: ToolCallId::new("c"),
1051 delta: "d".into()
1052 }
1053 .is_delta());
1054 assert!(AgentEvent::CompactionDelta {
1055 delta: "e".into()
1056 }
1057 .is_delta());
1058
1059 assert!(!AgentEvent::TextStarted { content_index: 0 }.is_delta());
1060 assert!(!AgentEvent::ToolCalled {
1061 call_id: ToolCallId::new("c"),
1062 tool_name: "t".into(),
1063 arguments: json!({}),
1064 }
1065 .is_delta());
1066 }
1067
1068 #[test]
1069 fn test_tool_call_id_all_tool_events() {
1070 let id = ToolCallId::new("test_id");
1071 let events = vec![
1072 AgentEvent::ToolInputStarted {
1073 call_id: id.clone(),
1074 tool_name: "t".into(),
1075 },
1076 AgentEvent::ToolInputDelta {
1077 call_id: id.clone(),
1078 delta: "d".into(),
1079 },
1080 AgentEvent::ToolInputEnded {
1081 call_id: id.clone(),
1082 arguments: json!({}),
1083 },
1084 AgentEvent::ToolCalled {
1085 call_id: id.clone(),
1086 tool_name: "t".into(),
1087 arguments: json!({}),
1088 },
1089 AgentEvent::ToolProgress {
1090 call_id: id.clone(),
1091 message: "m".into(),
1092 data: None,
1093 },
1094 AgentEvent::ToolSucceeded {
1095 call_id: id.clone(),
1096 tool_name: "t".into(),
1097 output: ToolOutput::success("ok"),
1098 },
1099 AgentEvent::ToolFailed {
1100 call_id: id.clone(),
1101 tool_name: "t".into(),
1102 error: "e".into(),
1103 is_retryable: false,
1104 },
1105 ];
1106 for event in &events {
1107 assert_eq!(
1108 event.tool_call_id(),
1109 Some(&id),
1110 "tool_call_id() failed for {:?}",
1111 event.kind()
1112 );
1113 }
1114 }
1115}