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 } => FlatEvent::TurnBegin { user_input },
247 Event::TurnEnd => FlatEvent::TurnEnd,
248 Event::StepBegin { n } => FlatEvent::StepBegin { n },
249 Event::StepInterrupted => FlatEvent::StepInterrupted,
250 Event::StepRetry {
251 n,
252 next_attempt,
253 max_attempts,
254 wait_s,
255 error_type,
256 status_code,
257 } => FlatEvent::StepRetry {
258 n,
259 next_attempt,
260 max_attempts,
261 wait_s,
262 error_type,
263 status_code,
264 },
265 Event::CompactionBegin => FlatEvent::CompactionBegin,
266 Event::CompactionEnd => FlatEvent::CompactionEnd,
267 Event::StatusUpdate(s) => FlatEvent::StatusUpdate(s),
268 Event::ContentPart(c) => FlatEvent::ContentPart(c),
269 Event::ToolCall {
270 id,
271 function,
272 extras,
273 } => FlatEvent::ToolCall {
274 id,
275 function,
276 extras,
277 },
278 Event::ToolCallPart { arguments_part } => FlatEvent::ToolCallPart { arguments_part },
279 Event::ToolResult {
280 tool_call_id,
281 return_value,
282 } => FlatEvent::ToolResult {
283 tool_call_id,
284 return_value,
285 },
286 Event::ApprovalResponse {
287 request_id,
288 response,
289 feedback,
290 } => FlatEvent::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 } => FlatEvent::SubagentEvent {
301 parent_tool_call_id,
302 agent_id,
303 subagent_type,
304 event,
305 },
306 Event::SteerInput { user_input } => FlatEvent::SteerInput { user_input },
307 Event::BtwBegin { id, question } => FlatEvent::BtwBegin { id, question },
308 Event::BtwEnd {
309 id,
310 response,
311 error,
312 } => FlatEvent::BtwEnd {
313 id,
314 response,
315 error,
316 },
317 Event::PlanDisplay { content, file_path } => {
318 FlatEvent::PlanDisplay { content, file_path }
319 }
320 Event::HookTriggered {
321 event,
322 target,
323 hook_count,
324 } => FlatEvent::HookTriggered {
325 event,
326 target,
327 hook_count,
328 },
329 Event::HookResolved {
330 event,
331 target,
332 action,
333 reason,
334 duration_ms,
335 } => FlatEvent::HookResolved {
336 event,
337 target,
338 action,
339 reason,
340 duration_ms,
341 },
342 }
343 }
344}
345
346impl Event {
347 #[must_use]
351 pub const fn type_name(&self) -> &'static str {
352 match self {
353 Event::TurnBegin { .. } => "TurnBegin",
354 Event::TurnEnd => "TurnEnd",
355 Event::StepBegin { .. } => "StepBegin",
356 Event::StepInterrupted => "StepInterrupted",
357 Event::StepRetry { .. } => "StepRetry",
358 Event::CompactionBegin => "CompactionBegin",
359 Event::CompactionEnd => "CompactionEnd",
360 Event::StatusUpdate(_) => "StatusUpdate",
361 Event::ContentPart(_) => "ContentPart",
362 Event::ToolCall { .. } => "ToolCall",
363 Event::ToolCallPart { .. } => "ToolCallPart",
364 Event::ToolResult { .. } => "ToolResult",
365 Event::ApprovalResponse { .. } => "ApprovalResponse",
366 Event::SubagentEvent { .. } => "SubagentEvent",
367 Event::SteerInput { .. } => "SteerInput",
368 Event::BtwBegin { .. } => "BtwBegin",
369 Event::BtwEnd { .. } => "BtwEnd",
370 Event::PlanDisplay { .. } => "PlanDisplay",
371 Event::HookTriggered { .. } => "HookTriggered",
372 Event::HookResolved { .. } => "HookResolved",
373 }
374 }
375}
376
377impl From<FlatEvent> for Event {
378 fn from(ev: FlatEvent) -> Self {
379 match ev {
380 FlatEvent::TurnBegin { user_input } => Event::TurnBegin { user_input },
381 FlatEvent::TurnEnd => Event::TurnEnd,
382 FlatEvent::StepBegin { n } => Event::StepBegin { n },
383 FlatEvent::StepInterrupted => Event::StepInterrupted,
384 FlatEvent::StepRetry {
385 n,
386 next_attempt,
387 max_attempts,
388 wait_s,
389 error_type,
390 status_code,
391 } => Event::StepRetry {
392 n,
393 next_attempt,
394 max_attempts,
395 wait_s,
396 error_type,
397 status_code,
398 },
399 FlatEvent::CompactionBegin => Event::CompactionBegin,
400 FlatEvent::CompactionEnd => Event::CompactionEnd,
401 FlatEvent::StatusUpdate(s) => Event::StatusUpdate(s),
402 FlatEvent::ContentPart(c) => Event::ContentPart(c),
403 FlatEvent::ToolCall {
404 id,
405 function,
406 extras,
407 } => Event::ToolCall {
408 id,
409 function,
410 extras,
411 },
412 FlatEvent::ToolCallPart { arguments_part } => Event::ToolCallPart { arguments_part },
413 FlatEvent::ToolResult {
414 tool_call_id,
415 return_value,
416 } => Event::ToolResult {
417 tool_call_id,
418 return_value,
419 },
420 FlatEvent::ApprovalResponse {
421 request_id,
422 response,
423 feedback,
424 } => Event::ApprovalResponse {
425 request_id,
426 response,
427 feedback,
428 },
429 FlatEvent::SubagentEvent {
430 parent_tool_call_id,
431 agent_id,
432 subagent_type,
433 event,
434 } => Event::SubagentEvent {
435 parent_tool_call_id,
436 agent_id,
437 subagent_type,
438 event,
439 },
440 FlatEvent::SteerInput { user_input } => Event::SteerInput { user_input },
441 FlatEvent::BtwBegin { id, question } => Event::BtwBegin { id, question },
442 FlatEvent::BtwEnd {
443 id,
444 response,
445 error,
446 } => Event::BtwEnd {
447 id,
448 response,
449 error,
450 },
451 FlatEvent::PlanDisplay { content, file_path } => {
452 Event::PlanDisplay { content, file_path }
453 }
454 FlatEvent::HookTriggered {
455 event,
456 target,
457 hook_count,
458 } => Event::HookTriggered {
459 event,
460 target,
461 hook_count,
462 },
463 FlatEvent::HookResolved {
464 event,
465 target,
466 action,
467 reason,
468 duration_ms,
469 } => Event::HookResolved {
470 event,
471 target,
472 action,
473 reason,
474 duration_ms,
475 },
476 }
477 }
478}
479
480#[derive(Serialize, Deserialize)]
485struct EventEnvelope {
486 #[serde(rename = "type")]
487 type_name: String,
488 payload: serde_json::Value,
489}
490
491impl Serialize for Event {
492 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
493 match self {
494 Event::ContentPart(part) => {
497 let payload = serde_json::to_value(part).map_err(serde::ser::Error::custom)?;
498 EventEnvelope {
499 type_name: "ContentPart".to_string(),
500 payload,
501 }
502 .serialize(serializer)
503 }
504 Event::ToolCall {
507 id,
508 function,
509 extras,
510 } => {
511 #[derive(Serialize)]
512 struct ToolCallPayload<'a> {
513 #[serde(rename = "type")]
514 type_name: &'a str,
515 id: &'a str,
516 function: &'a ToolCallFunction,
517 #[serde(skip_serializing_if = "Option::is_none")]
518 extras: &'a Option<serde_json::Value>,
519 }
520 let payload = serde_json::to_value(&ToolCallPayload {
521 type_name: "function",
522 id,
523 function,
524 extras,
525 })
526 .map_err(serde::ser::Error::custom)?;
527 EventEnvelope {
528 type_name: "ToolCall".to_string(),
529 payload,
530 }
531 .serialize(serializer)
532 }
533 _ => {
534 let flat = FlatEvent::from(self.clone());
535 let mut value = serde_json::to_value(&flat).map_err(serde::ser::Error::custom)?;
536 let obj = value
537 .as_object_mut()
538 .ok_or_else(|| serde::ser::Error::custom("expected object"))?;
539 let type_name = obj
540 .remove("type")
541 .and_then(|v| v.as_str().map(String::from))
542 .ok_or_else(|| serde::ser::Error::custom("missing type"))?;
543 EventEnvelope {
544 type_name,
545 payload: value,
546 }
547 .serialize(serializer)
548 }
549 }
550 }
551}
552
553impl<'de> Deserialize<'de> for Event {
554 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
555 let envelope = EventEnvelope::deserialize(deserializer)?;
556 match envelope.type_name.as_str() {
557 "ContentPart" => {
558 let part: ContentPart =
559 serde_json::from_value(envelope.payload).map_err(serde::de::Error::custom)?;
560 Ok(Event::ContentPart(part))
561 }
562 _ => {
563 let mut value = envelope.payload;
564 if let Some(obj) = value.as_object_mut() {
565 obj.insert(
566 "type".to_string(),
567 serde_json::Value::String(envelope.type_name),
568 );
569 }
570 let flat: FlatEvent =
571 serde_json::from_value(value).map_err(serde::de::Error::custom)?;
572 Ok(Event::from(flat))
573 }
574 }
575 }
576}
577
578#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
583pub struct SubagentEventPayload {
584 #[serde(rename = "type")]
586 pub type_name: String,
587 pub payload: serde_json::Value,
589}
590
591#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
593pub struct StatusUpdate {
594 #[serde(skip_serializing_if = "Option::is_none")]
596 pub context_usage: Option<f64>,
597 #[serde(skip_serializing_if = "Option::is_none")]
599 pub context_tokens: Option<u64>,
600 #[serde(skip_serializing_if = "Option::is_none")]
602 pub max_context_tokens: Option<u64>,
603 #[serde(skip_serializing_if = "Option::is_none")]
605 pub token_usage: Option<TokenUsage>,
606 #[serde(skip_serializing_if = "Option::is_none")]
608 pub message_id: Option<String>,
609 #[serde(skip_serializing_if = "Option::is_none")]
611 pub plan_mode: Option<bool>,
612}
613
614#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
616pub struct TokenUsage {
617 pub input_other: u64,
619 pub output: u64,
621 pub input_cache_read: u64,
623 pub input_cache_creation: u64,
625}
626
627#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
629pub struct ToolCallFunction {
630 pub name: String,
632 #[serde(skip_serializing_if = "Option::is_none")]
634 pub arguments: Option<String>,
635}
636
637#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
639#[serde(rename_all = "snake_case")]
640#[non_exhaustive]
641pub enum ApprovalResponseKind {
642 Approve,
644 #[serde(rename = "approve_for_session")]
646 ApproveForSession,
647 Reject,
649}
650
651#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
653#[serde(rename_all = "snake_case")]
654#[non_exhaustive]
655pub enum HookAction {
656 Allow,
658 Block,
660}