1use serde::{Deserialize, Serialize, Serializer};
2
3use super::content::{ContentPart, ToolReturnValue, UserInput};
4
5#[derive(Debug, Clone, PartialEq)]
13#[non_exhaustive]
14pub enum Event {
15 TurnBegin {
17 user_input: UserInput,
19 },
20 TurnEnd,
22 StepBegin {
24 n: u32,
26 },
27 StepInterrupted,
29 StepRetry {
33 n: u32,
35 next_attempt: u32,
37 max_attempts: u32,
39 wait_s: u32,
41 error_type: String,
43 status_code: Option<u32>,
45 },
46 CompactionBegin,
48 CompactionEnd,
50 StatusUpdate(StatusUpdate),
52 ContentPart(ContentPart),
54 ToolCall {
59 id: String,
61 function: ToolCallFunction,
63 extras: Option<serde_json::Value>,
65 },
66 ToolCallPart {
68 arguments_part: Option<String>,
70 },
71 ToolResult {
73 tool_call_id: String,
75 return_value: ToolReturnValue,
77 },
78 ApprovalResponse {
80 request_id: String,
82 response: ApprovalResponseKind,
84 feedback: Option<String>,
86 },
87 SubagentEvent {
89 parent_tool_call_id: Option<String>,
91 agent_id: Option<String>,
93 subagent_type: Option<String>,
95 event: SubagentEventPayload,
97 },
98 SteerInput {
100 user_input: UserInput,
102 },
103 BtwBegin {
107 id: String,
109 question: String,
111 },
112 BtwEnd {
116 id: String,
118 response: Option<String>,
120 error: Option<String>,
122 },
123 PlanDisplay {
125 content: String,
127 file_path: String,
129 },
130 HookTriggered {
132 event: String,
134 target: String,
136 hook_count: u32,
138 },
139 HookResolved {
141 event: String,
143 target: String,
145 action: HookAction,
147 reason: String,
149 duration_ms: u64,
151 },
152}
153
154#[derive(Serialize, Deserialize)]
159#[serde(tag = "type")]
160pub(crate) enum FlatEvent {
161 TurnBegin {
162 user_input: UserInput,
163 },
164 TurnEnd,
165 StepBegin {
166 n: u32,
167 },
168 StepInterrupted,
169 StepRetry {
170 n: u32,
171 next_attempt: u32,
172 max_attempts: u32,
173 wait_s: u32,
174 error_type: String,
175 #[serde(skip_serializing_if = "Option::is_none")]
176 status_code: Option<u32>,
177 },
178 CompactionBegin,
179 CompactionEnd,
180 StatusUpdate(StatusUpdate),
181 ContentPart(ContentPart),
182 ToolCall {
183 id: String,
184 function: ToolCallFunction,
185 #[serde(skip_serializing_if = "Option::is_none")]
186 extras: Option<serde_json::Value>,
187 },
188 ToolCallPart {
189 #[serde(skip_serializing_if = "Option::is_none")]
190 arguments_part: Option<String>,
191 },
192 ToolResult {
193 tool_call_id: String,
194 return_value: ToolReturnValue,
195 },
196 ApprovalResponse {
197 request_id: String,
198 response: ApprovalResponseKind,
199 #[serde(skip_serializing_if = "Option::is_none")]
200 feedback: Option<String>,
201 },
202 SubagentEvent {
203 #[serde(skip_serializing_if = "Option::is_none")]
204 parent_tool_call_id: Option<String>,
205 #[serde(skip_serializing_if = "Option::is_none")]
206 agent_id: Option<String>,
207 #[serde(skip_serializing_if = "Option::is_none")]
208 subagent_type: Option<String>,
209 event: SubagentEventPayload,
210 },
211 SteerInput {
212 user_input: UserInput,
213 },
214 BtwBegin {
215 id: String,
216 question: String,
217 },
218 BtwEnd {
219 id: String,
220 #[serde(skip_serializing_if = "Option::is_none")]
221 response: Option<String>,
222 #[serde(skip_serializing_if = "Option::is_none")]
223 error: Option<String>,
224 },
225 PlanDisplay {
226 content: String,
227 file_path: String,
228 },
229 HookTriggered {
230 event: String,
231 target: String,
232 hook_count: u32,
233 },
234 HookResolved {
235 event: String,
236 target: String,
237 action: HookAction,
238 reason: String,
239 duration_ms: u64,
240 },
241}
242
243impl From<Event> for FlatEvent {
244 fn from(ev: Event) -> Self {
245 match ev {
246 Event::TurnBegin { user_input } => Self::TurnBegin { user_input },
247 Event::TurnEnd => Self::TurnEnd,
248 Event::StepBegin { n } => Self::StepBegin { n },
249 Event::StepInterrupted => Self::StepInterrupted,
250 Event::StepRetry {
251 n,
252 next_attempt,
253 max_attempts,
254 wait_s,
255 error_type,
256 status_code,
257 } => Self::StepRetry {
258 n,
259 next_attempt,
260 max_attempts,
261 wait_s,
262 error_type,
263 status_code,
264 },
265 Event::CompactionBegin => Self::CompactionBegin,
266 Event::CompactionEnd => Self::CompactionEnd,
267 Event::StatusUpdate(s) => Self::StatusUpdate(s),
268 Event::ContentPart(c) => Self::ContentPart(c),
269 Event::ToolCall {
270 id,
271 function,
272 extras,
273 } => Self::ToolCall {
274 id,
275 function,
276 extras,
277 },
278 Event::ToolCallPart { arguments_part } => Self::ToolCallPart { arguments_part },
279 Event::ToolResult {
280 tool_call_id,
281 return_value,
282 } => Self::ToolResult {
283 tool_call_id,
284 return_value,
285 },
286 Event::ApprovalResponse {
287 request_id,
288 response,
289 feedback,
290 } => Self::ApprovalResponse {
291 request_id,
292 response,
293 feedback,
294 },
295 Event::SubagentEvent {
296 parent_tool_call_id,
297 agent_id,
298 subagent_type,
299 event,
300 } => Self::SubagentEvent {
301 parent_tool_call_id,
302 agent_id,
303 subagent_type,
304 event,
305 },
306 Event::SteerInput { user_input } => Self::SteerInput { user_input },
307 Event::BtwBegin { id, question } => Self::BtwBegin { id, question },
308 Event::BtwEnd {
309 id,
310 response,
311 error,
312 } => Self::BtwEnd {
313 id,
314 response,
315 error,
316 },
317 Event::PlanDisplay { content, file_path } => Self::PlanDisplay { content, file_path },
318 Event::HookTriggered {
319 event,
320 target,
321 hook_count,
322 } => Self::HookTriggered {
323 event,
324 target,
325 hook_count,
326 },
327 Event::HookResolved {
328 event,
329 target,
330 action,
331 reason,
332 duration_ms,
333 } => Self::HookResolved {
334 event,
335 target,
336 action,
337 reason,
338 duration_ms,
339 },
340 }
341 }
342}
343
344impl Event {
345 #[must_use]
349 pub const fn type_name(&self) -> &'static str {
350 match self {
351 Self::TurnBegin { .. } => "TurnBegin",
352 Self::TurnEnd => "TurnEnd",
353 Self::StepBegin { .. } => "StepBegin",
354 Self::StepInterrupted => "StepInterrupted",
355 Self::StepRetry { .. } => "StepRetry",
356 Self::CompactionBegin => "CompactionBegin",
357 Self::CompactionEnd => "CompactionEnd",
358 Self::StatusUpdate(_) => "StatusUpdate",
359 Self::ContentPart(_) => "ContentPart",
360 Self::ToolCall { .. } => "ToolCall",
361 Self::ToolCallPart { .. } => "ToolCallPart",
362 Self::ToolResult { .. } => "ToolResult",
363 Self::ApprovalResponse { .. } => "ApprovalResponse",
364 Self::SubagentEvent { .. } => "SubagentEvent",
365 Self::SteerInput { .. } => "SteerInput",
366 Self::BtwBegin { .. } => "BtwBegin",
367 Self::BtwEnd { .. } => "BtwEnd",
368 Self::PlanDisplay { .. } => "PlanDisplay",
369 Self::HookTriggered { .. } => "HookTriggered",
370 Self::HookResolved { .. } => "HookResolved",
371 }
372 }
373}
374
375impl From<FlatEvent> for Event {
376 fn from(ev: FlatEvent) -> Self {
377 match ev {
378 FlatEvent::TurnBegin { user_input } => Self::TurnBegin { user_input },
379 FlatEvent::TurnEnd => Self::TurnEnd,
380 FlatEvent::StepBegin { n } => Self::StepBegin { n },
381 FlatEvent::StepInterrupted => Self::StepInterrupted,
382 FlatEvent::StepRetry {
383 n,
384 next_attempt,
385 max_attempts,
386 wait_s,
387 error_type,
388 status_code,
389 } => Self::StepRetry {
390 n,
391 next_attempt,
392 max_attempts,
393 wait_s,
394 error_type,
395 status_code,
396 },
397 FlatEvent::CompactionBegin => Self::CompactionBegin,
398 FlatEvent::CompactionEnd => Self::CompactionEnd,
399 FlatEvent::StatusUpdate(s) => Self::StatusUpdate(s),
400 FlatEvent::ContentPart(c) => Self::ContentPart(c),
401 FlatEvent::ToolCall {
402 id,
403 function,
404 extras,
405 } => Self::ToolCall {
406 id,
407 function,
408 extras,
409 },
410 FlatEvent::ToolCallPart { arguments_part } => Self::ToolCallPart { arguments_part },
411 FlatEvent::ToolResult {
412 tool_call_id,
413 return_value,
414 } => Self::ToolResult {
415 tool_call_id,
416 return_value,
417 },
418 FlatEvent::ApprovalResponse {
419 request_id,
420 response,
421 feedback,
422 } => Self::ApprovalResponse {
423 request_id,
424 response,
425 feedback,
426 },
427 FlatEvent::SubagentEvent {
428 parent_tool_call_id,
429 agent_id,
430 subagent_type,
431 event,
432 } => Self::SubagentEvent {
433 parent_tool_call_id,
434 agent_id,
435 subagent_type,
436 event,
437 },
438 FlatEvent::SteerInput { user_input } => Self::SteerInput { user_input },
439 FlatEvent::BtwBegin { id, question } => Self::BtwBegin { id, question },
440 FlatEvent::BtwEnd {
441 id,
442 response,
443 error,
444 } => Self::BtwEnd {
445 id,
446 response,
447 error,
448 },
449 FlatEvent::PlanDisplay { content, file_path } => {
450 Self::PlanDisplay { content, file_path }
451 }
452 FlatEvent::HookTriggered {
453 event,
454 target,
455 hook_count,
456 } => Self::HookTriggered {
457 event,
458 target,
459 hook_count,
460 },
461 FlatEvent::HookResolved {
462 event,
463 target,
464 action,
465 reason,
466 duration_ms,
467 } => Self::HookResolved {
468 event,
469 target,
470 action,
471 reason,
472 duration_ms,
473 },
474 }
475 }
476}
477
478#[derive(Serialize, Deserialize)]
483struct EventEnvelope {
484 #[serde(rename = "type")]
485 type_name: String,
486 payload: serde_json::Value,
487}
488
489impl Serialize for Event {
490 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
491 match self {
492 Self::ContentPart(part) => {
495 let payload = serde_json::to_value(part).map_err(serde::ser::Error::custom)?;
496 EventEnvelope {
497 type_name: "ContentPart".to_string(),
498 payload,
499 }
500 .serialize(serializer)
501 }
502 Self::ToolCall {
505 id,
506 function,
507 extras,
508 } => {
509 #[derive(Serialize)]
510 struct ToolCallPayload<'a> {
511 #[serde(rename = "type")]
512 type_name: &'a str,
513 id: &'a str,
514 function: &'a ToolCallFunction,
515 #[serde(skip_serializing_if = "Option::is_none")]
516 extras: &'a Option<serde_json::Value>,
517 }
518 let payload = serde_json::to_value(&ToolCallPayload {
519 type_name: "function",
520 id,
521 function,
522 extras,
523 })
524 .map_err(serde::ser::Error::custom)?;
525 EventEnvelope {
526 type_name: "ToolCall".to_string(),
527 payload,
528 }
529 .serialize(serializer)
530 }
531 _ => {
532 let flat = FlatEvent::from(self.clone());
533 let mut value = serde_json::to_value(&flat).map_err(serde::ser::Error::custom)?;
534 let obj = value
535 .as_object_mut()
536 .ok_or_else(|| serde::ser::Error::custom("expected object"))?;
537 let type_name = obj
538 .remove("type")
539 .and_then(|v| v.as_str().map(String::from))
540 .ok_or_else(|| serde::ser::Error::custom("missing type"))?;
541 EventEnvelope {
542 type_name,
543 payload: value,
544 }
545 .serialize(serializer)
546 }
547 }
548 }
549}
550
551impl<'de> Deserialize<'de> for Event {
552 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
553 let envelope = EventEnvelope::deserialize(deserializer)?;
554 if envelope.type_name.as_str() == "ContentPart" {
555 let part: ContentPart =
556 serde_json::from_value(envelope.payload).map_err(serde::de::Error::custom)?;
557 Ok(Self::ContentPart(part))
558 } else {
559 let mut value = envelope.payload;
560 if let Some(obj) = value.as_object_mut() {
561 obj.insert(
562 "type".to_string(),
563 serde_json::Value::String(envelope.type_name),
564 );
565 }
566 let flat: FlatEvent =
567 serde_json::from_value(value).map_err(serde::de::Error::custom)?;
568 Ok(Self::from(flat))
569 }
570 }
571}
572
573#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
578pub struct SubagentEventPayload {
579 #[serde(rename = "type")]
581 pub type_name: String,
582 pub payload: serde_json::Value,
584}
585
586#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
588pub struct StatusUpdate {
589 #[serde(skip_serializing_if = "Option::is_none")]
591 pub context_usage: Option<f64>,
592 #[serde(skip_serializing_if = "Option::is_none")]
594 pub context_tokens: Option<u64>,
595 #[serde(skip_serializing_if = "Option::is_none")]
597 pub max_context_tokens: Option<u64>,
598 #[serde(skip_serializing_if = "Option::is_none")]
600 pub token_usage: Option<TokenUsage>,
601 #[serde(skip_serializing_if = "Option::is_none")]
603 pub message_id: Option<String>,
604 #[serde(skip_serializing_if = "Option::is_none")]
606 pub plan_mode: Option<bool>,
607}
608
609#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
611pub struct TokenUsage {
612 pub input_other: u64,
614 pub output: u64,
616 pub input_cache_read: u64,
618 pub input_cache_creation: u64,
620}
621
622#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
624pub struct ToolCallFunction {
625 pub name: String,
627 #[serde(skip_serializing_if = "Option::is_none")]
629 pub arguments: Option<String>,
630}
631
632#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
634#[serde(rename_all = "snake_case")]
635#[non_exhaustive]
636pub enum ApprovalResponseKind {
637 Approve,
639 #[serde(rename = "approve_for_session")]
641 ApproveForSession,
642 Reject,
644}
645
646#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
648#[serde(rename_all = "snake_case")]
649#[non_exhaustive]
650pub enum HookAction {
651 Allow,
653 Block,
655}