1use serde::{Deserialize, Serialize, Serializer};
2
3use super::content::{ContentPart, ToolReturnValue, UserInput};
4
5#[derive(Debug, Clone, PartialEq)]
13pub enum Event {
14 TurnBegin {
16 user_input: UserInput,
18 },
19 TurnEnd,
21 StepBegin {
23 n: u32,
25 },
26 StepInterrupted,
28 CompactionBegin,
30 CompactionEnd,
32 StatusUpdate(StatusUpdate),
34 ContentPart(ContentPart),
36 ToolCall {
40 id: String,
42 function: ToolCallFunction,
44 extras: Option<serde_json::Value>,
46 },
47 ToolCallPart {
49 arguments_part: Option<String>,
51 },
52 ToolResult {
54 tool_call_id: String,
56 return_value: ToolReturnValue,
58 },
59 ApprovalResponse {
61 request_id: String,
63 response: ApprovalResponseKind,
65 feedback: Option<String>,
67 },
68 SubagentEvent {
70 parent_tool_call_id: Option<String>,
72 agent_id: Option<String>,
74 subagent_type: Option<String>,
76 event: SubagentEventPayload,
78 },
79 SteerInput {
81 user_input: UserInput,
83 },
84 PlanDisplay {
86 content: String,
88 file_path: String,
90 },
91 HookTriggered {
93 event: String,
95 target: String,
97 hook_count: u32,
99 },
100 HookResolved {
102 event: String,
104 target: String,
106 action: HookAction,
108 reason: String,
110 duration_ms: u64,
112 },
113}
114
115#[derive(Serialize, Deserialize)]
120#[serde(tag = "type")]
121pub(crate) enum FlatEvent {
122 TurnBegin { user_input: UserInput },
123 TurnEnd,
124 StepBegin { n: u32 },
125 StepInterrupted,
126 CompactionBegin,
127 CompactionEnd,
128 StatusUpdate(StatusUpdate),
129 ContentPart(ContentPart),
130 #[serde(rename = "function")]
131 ToolCall {
132 id: String,
133 function: ToolCallFunction,
134 #[serde(skip_serializing_if = "Option::is_none")]
135 extras: Option<serde_json::Value>,
136 },
137 ToolCallPart {
138 #[serde(skip_serializing_if = "Option::is_none")]
139 arguments_part: Option<String>,
140 },
141 ToolResult {
142 tool_call_id: String,
143 return_value: ToolReturnValue,
144 },
145 ApprovalResponse {
146 request_id: String,
147 response: ApprovalResponseKind,
148 #[serde(skip_serializing_if = "Option::is_none")]
149 feedback: Option<String>,
150 },
151 SubagentEvent {
152 #[serde(skip_serializing_if = "Option::is_none")]
153 parent_tool_call_id: Option<String>,
154 #[serde(skip_serializing_if = "Option::is_none")]
155 agent_id: Option<String>,
156 #[serde(skip_serializing_if = "Option::is_none")]
157 subagent_type: Option<String>,
158 event: SubagentEventPayload,
159 },
160 SteerInput { user_input: UserInput },
161 PlanDisplay { content: String, file_path: String },
162 HookTriggered { event: String, target: String, hook_count: u32 },
163 HookResolved { event: String, target: String, action: HookAction, reason: String, duration_ms: u64 },
164}
165
166impl From<Event> for FlatEvent {
167 fn from(ev: Event) -> Self {
168 match ev {
169 Event::TurnBegin { user_input } => FlatEvent::TurnBegin { user_input },
170 Event::TurnEnd => FlatEvent::TurnEnd,
171 Event::StepBegin { n } => FlatEvent::StepBegin { n },
172 Event::StepInterrupted => FlatEvent::StepInterrupted,
173 Event::CompactionBegin => FlatEvent::CompactionBegin,
174 Event::CompactionEnd => FlatEvent::CompactionEnd,
175 Event::StatusUpdate(s) => FlatEvent::StatusUpdate(s),
176 Event::ContentPart(c) => FlatEvent::ContentPart(c),
177 Event::ToolCall { id, function, extras } => FlatEvent::ToolCall { id, function, extras },
178 Event::ToolCallPart { arguments_part } => FlatEvent::ToolCallPart { arguments_part },
179 Event::ToolResult { tool_call_id, return_value } => FlatEvent::ToolResult { tool_call_id, return_value },
180 Event::ApprovalResponse { request_id, response, feedback } => FlatEvent::ApprovalResponse { request_id, response, feedback },
181 Event::SubagentEvent { parent_tool_call_id, agent_id, subagent_type, event } => FlatEvent::SubagentEvent { parent_tool_call_id, agent_id, subagent_type, event },
182 Event::SteerInput { user_input } => FlatEvent::SteerInput { user_input },
183 Event::PlanDisplay { content, file_path } => FlatEvent::PlanDisplay { content, file_path },
184 Event::HookTriggered { event, target, hook_count } => FlatEvent::HookTriggered { event, target, hook_count },
185 Event::HookResolved { event, target, action, reason, duration_ms } => FlatEvent::HookResolved { event, target, action, reason, duration_ms },
186 }
187 }
188}
189
190impl From<FlatEvent> for Event {
191 fn from(ev: FlatEvent) -> Self {
192 match ev {
193 FlatEvent::TurnBegin { user_input } => Event::TurnBegin { user_input },
194 FlatEvent::TurnEnd => Event::TurnEnd,
195 FlatEvent::StepBegin { n } => Event::StepBegin { n },
196 FlatEvent::StepInterrupted => Event::StepInterrupted,
197 FlatEvent::CompactionBegin => Event::CompactionBegin,
198 FlatEvent::CompactionEnd => Event::CompactionEnd,
199 FlatEvent::StatusUpdate(s) => Event::StatusUpdate(s),
200 FlatEvent::ContentPart(c) => Event::ContentPart(c),
201 FlatEvent::ToolCall { id, function, extras } => Event::ToolCall { id, function, extras },
202 FlatEvent::ToolCallPart { arguments_part } => Event::ToolCallPart { arguments_part },
203 FlatEvent::ToolResult { tool_call_id, return_value } => Event::ToolResult { tool_call_id, return_value },
204 FlatEvent::ApprovalResponse { request_id, response, feedback } => Event::ApprovalResponse { request_id, response, feedback },
205 FlatEvent::SubagentEvent { parent_tool_call_id, agent_id, subagent_type, event } => Event::SubagentEvent { parent_tool_call_id, agent_id, subagent_type, event },
206 FlatEvent::SteerInput { user_input } => Event::SteerInput { user_input },
207 FlatEvent::PlanDisplay { content, file_path } => Event::PlanDisplay { content, file_path },
208 FlatEvent::HookTriggered { event, target, hook_count } => Event::HookTriggered { event, target, hook_count },
209 FlatEvent::HookResolved { event, target, action, reason, duration_ms } => Event::HookResolved { event, target, action, reason, duration_ms },
210 }
211 }
212}
213
214#[derive(Serialize, Deserialize)]
219struct EventEnvelope {
220 #[serde(rename = "type")]
221 type_name: String,
222 payload: serde_json::Value,
223}
224
225impl Serialize for Event {
226 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
227 let flat = FlatEvent::from(self.clone());
228 let mut value = serde_json::to_value(&flat).map_err(serde::ser::Error::custom)?;
229 let obj = value
230 .as_object_mut()
231 .ok_or_else(|| serde::ser::Error::custom("expected object"))?;
232 let type_name = obj
233 .remove("type")
234 .and_then(|v| v.as_str().map(String::from))
235 .ok_or_else(|| serde::ser::Error::custom("missing type"))?;
236 EventEnvelope { type_name, payload: value }.serialize(serializer)
237 }
238}
239
240impl<'de> Deserialize<'de> for Event {
241 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
242 let envelope = EventEnvelope::deserialize(deserializer)?;
243 let mut value = envelope.payload;
244 if let Some(obj) = value.as_object_mut() {
245 obj.insert("type".to_string(), serde_json::Value::String(envelope.type_name));
246 }
247 let flat: FlatEvent = serde_json::from_value(value).map_err(serde::de::Error::custom)?;
248 Ok(Event::from(flat))
249 }
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
257pub struct SubagentEventPayload {
258 #[serde(rename = "type")]
260 pub type_name: String,
261 pub payload: serde_json::Value,
263}
264
265#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
267pub struct StatusUpdate {
268 #[serde(skip_serializing_if = "Option::is_none")]
270 pub context_usage: Option<f64>,
271 #[serde(skip_serializing_if = "Option::is_none")]
273 pub context_tokens: Option<u64>,
274 #[serde(skip_serializing_if = "Option::is_none")]
276 pub max_context_tokens: Option<u64>,
277 #[serde(skip_serializing_if = "Option::is_none")]
279 pub token_usage: Option<TokenUsage>,
280 #[serde(skip_serializing_if = "Option::is_none")]
282 pub message_id: Option<String>,
283 #[serde(skip_serializing_if = "Option::is_none")]
285 pub plan_mode: Option<bool>,
286}
287
288#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
290pub struct TokenUsage {
291 pub input_other: u64,
293 pub output: u64,
295 pub input_cache_read: u64,
297 pub input_cache_creation: u64,
299}
300
301#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
303pub struct ToolCallFunction {
304 pub name: String,
306 #[serde(skip_serializing_if = "Option::is_none")]
308 pub arguments: Option<String>,
309}
310
311#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
313#[serde(rename_all = "snake_case")]
314pub enum ApprovalResponseKind {
315 Approve,
317 #[serde(rename = "approve_for_session")]
319 ApproveForSession,
320 Reject,
322}
323
324#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
326#[serde(rename_all = "snake_case")]
327pub enum HookAction {
328 Allow,
330 Block,
332}