1use serde::{Deserialize, Serialize};
2
3use crate::{Action, PlanItem, TurnResult, Usage};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6#[serde(tag = "type", rename_all = "snake_case")]
7#[non_exhaustive]
8pub enum AgentEvent {
9 SessionStarted {
11 session_id: String,
12 },
13 TurnStarted,
14 TurnCompleted {
15 #[serde(default)]
16 usage: Option<Usage>,
17 #[serde(default)]
18 result: Option<TurnResult>,
19 },
20 TurnFailed {
21 message: String,
22 },
23
24 Message {
26 id: String,
27 text: String,
28 },
29 TextDelta {
30 id: String,
31 delta: String,
32 },
33 Reasoning {
34 id: String,
35 text: String,
36 },
37
38 ActionStarted {
40 id: String,
41 action: Action,
42 },
43 ActionUpdated {
44 id: String,
45 action: Action,
46 },
47 ActionCompleted {
48 id: String,
49 action: Action,
50 },
51
52 PlanUpdated {
54 items: Vec<PlanItem>,
55 },
56
57 ToolCall {
59 id: String,
61 name: String,
63 arguments: String,
65 },
66
67 Error {
69 message: String,
70 },
71
72 Raw {
74 backend: String,
75 data: serde_json::Value,
76 },
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82 use crate::ActionStatus;
83
84 #[test]
85 fn session_started_serde() {
86 let event = AgentEvent::SessionStarted {
87 session_id: "abc-123".into(),
88 };
89 let json = serde_json::to_string(&event).unwrap();
90 assert!(json.contains("\"type\":\"session_started\""));
91
92 let parsed: AgentEvent = serde_json::from_str(&json).unwrap();
93 match parsed {
94 AgentEvent::SessionStarted { session_id } => assert_eq!(session_id, "abc-123"),
95 _ => panic!("wrong variant"),
96 }
97 }
98
99 #[test]
100 fn turn_completed_with_usage() {
101 let event = AgentEvent::TurnCompleted {
102 usage: Some(Usage {
103 input_tokens: Some(100),
104 output_tokens: Some(50),
105 ..Default::default()
106 }),
107 result: None,
108 };
109 let json = serde_json::to_string(&event).unwrap();
110 let parsed: AgentEvent = serde_json::from_str(&json).unwrap();
111 match parsed {
112 AgentEvent::TurnCompleted { usage, .. } => {
113 let u = usage.unwrap();
114 assert_eq!(u.input_tokens, Some(100));
115 assert_eq!(u.output_tokens, Some(50));
116 }
117 _ => panic!("wrong variant"),
118 }
119 }
120
121 #[test]
122 fn action_started_serde() {
123 let event = AgentEvent::ActionStarted {
124 id: "item-1".into(),
125 action: Action::Command {
126 command: "echo hi".into(),
127 output: String::new(),
128 exit_code: None,
129 status: ActionStatus::InProgress,
130 },
131 };
132 let json = serde_json::to_string(&event).unwrap();
133 assert!(json.contains("\"type\":\"action_started\""));
134
135 let parsed: AgentEvent = serde_json::from_str(&json).unwrap();
136 match parsed {
137 AgentEvent::ActionStarted { id, .. } => assert_eq!(id, "item-1"),
138 _ => panic!("wrong variant"),
139 }
140 }
141
142 #[test]
143 fn raw_event_serde() {
144 let event = AgentEvent::Raw {
145 backend: "codex".into(),
146 data: serde_json::json!({"custom": true}),
147 };
148 let json = serde_json::to_string(&event).unwrap();
149 let parsed: AgentEvent = serde_json::from_str(&json).unwrap();
150 match parsed {
151 AgentEvent::Raw { backend, data } => {
152 assert_eq!(backend, "codex");
153 assert_eq!(data["custom"], true);
154 }
155 _ => panic!("wrong variant"),
156 }
157 }
158}