use serde::{Deserialize, Serialize, Serializer};
use super::content::{ContentPart, ToolReturnValue, UserInput};
#[derive(Debug, Clone, PartialEq)]
pub enum Event {
TurnBegin {
user_input: UserInput,
},
TurnEnd,
StepBegin {
n: u32,
},
StepInterrupted,
CompactionBegin,
CompactionEnd,
StatusUpdate(StatusUpdate),
ContentPart(ContentPart),
ToolCall {
id: String,
function: ToolCallFunction,
extras: Option<serde_json::Value>,
},
ToolCallPart {
arguments_part: Option<String>,
},
ToolResult {
tool_call_id: String,
return_value: ToolReturnValue,
},
ApprovalResponse {
request_id: String,
response: ApprovalResponseKind,
feedback: Option<String>,
},
SubagentEvent {
parent_tool_call_id: Option<String>,
agent_id: Option<String>,
subagent_type: Option<String>,
event: SubagentEventPayload,
},
SteerInput {
user_input: UserInput,
},
PlanDisplay {
content: String,
file_path: String,
},
HookTriggered {
event: String,
target: String,
hook_count: u32,
},
HookResolved {
event: String,
target: String,
action: HookAction,
reason: String,
duration_ms: u64,
},
}
#[derive(Serialize, Deserialize)]
#[serde(tag = "type")]
pub(crate) enum FlatEvent {
TurnBegin { user_input: UserInput },
TurnEnd,
StepBegin { n: u32 },
StepInterrupted,
CompactionBegin,
CompactionEnd,
StatusUpdate(StatusUpdate),
ContentPart(ContentPart),
#[serde(rename = "function")]
ToolCall {
id: String,
function: ToolCallFunction,
#[serde(skip_serializing_if = "Option::is_none")]
extras: Option<serde_json::Value>,
},
ToolCallPart {
#[serde(skip_serializing_if = "Option::is_none")]
arguments_part: Option<String>,
},
ToolResult {
tool_call_id: String,
return_value: ToolReturnValue,
},
ApprovalResponse {
request_id: String,
response: ApprovalResponseKind,
#[serde(skip_serializing_if = "Option::is_none")]
feedback: Option<String>,
},
SubagentEvent {
#[serde(skip_serializing_if = "Option::is_none")]
parent_tool_call_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
agent_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
subagent_type: Option<String>,
event: SubagentEventPayload,
},
SteerInput { user_input: UserInput },
PlanDisplay { content: String, file_path: String },
HookTriggered { event: String, target: String, hook_count: u32 },
HookResolved { event: String, target: String, action: HookAction, reason: String, duration_ms: u64 },
}
impl From<Event> for FlatEvent {
fn from(ev: Event) -> Self {
match ev {
Event::TurnBegin { user_input } => FlatEvent::TurnBegin { user_input },
Event::TurnEnd => FlatEvent::TurnEnd,
Event::StepBegin { n } => FlatEvent::StepBegin { n },
Event::StepInterrupted => FlatEvent::StepInterrupted,
Event::CompactionBegin => FlatEvent::CompactionBegin,
Event::CompactionEnd => FlatEvent::CompactionEnd,
Event::StatusUpdate(s) => FlatEvent::StatusUpdate(s),
Event::ContentPart(c) => FlatEvent::ContentPart(c),
Event::ToolCall { id, function, extras } => FlatEvent::ToolCall { id, function, extras },
Event::ToolCallPart { arguments_part } => FlatEvent::ToolCallPart { arguments_part },
Event::ToolResult { tool_call_id, return_value } => FlatEvent::ToolResult { tool_call_id, return_value },
Event::ApprovalResponse { request_id, response, feedback } => FlatEvent::ApprovalResponse { request_id, response, feedback },
Event::SubagentEvent { parent_tool_call_id, agent_id, subagent_type, event } => FlatEvent::SubagentEvent { parent_tool_call_id, agent_id, subagent_type, event },
Event::SteerInput { user_input } => FlatEvent::SteerInput { user_input },
Event::PlanDisplay { content, file_path } => FlatEvent::PlanDisplay { content, file_path },
Event::HookTriggered { event, target, hook_count } => FlatEvent::HookTriggered { event, target, hook_count },
Event::HookResolved { event, target, action, reason, duration_ms } => FlatEvent::HookResolved { event, target, action, reason, duration_ms },
}
}
}
impl From<FlatEvent> for Event {
fn from(ev: FlatEvent) -> Self {
match ev {
FlatEvent::TurnBegin { user_input } => Event::TurnBegin { user_input },
FlatEvent::TurnEnd => Event::TurnEnd,
FlatEvent::StepBegin { n } => Event::StepBegin { n },
FlatEvent::StepInterrupted => Event::StepInterrupted,
FlatEvent::CompactionBegin => Event::CompactionBegin,
FlatEvent::CompactionEnd => Event::CompactionEnd,
FlatEvent::StatusUpdate(s) => Event::StatusUpdate(s),
FlatEvent::ContentPart(c) => Event::ContentPart(c),
FlatEvent::ToolCall { id, function, extras } => Event::ToolCall { id, function, extras },
FlatEvent::ToolCallPart { arguments_part } => Event::ToolCallPart { arguments_part },
FlatEvent::ToolResult { tool_call_id, return_value } => Event::ToolResult { tool_call_id, return_value },
FlatEvent::ApprovalResponse { request_id, response, feedback } => Event::ApprovalResponse { request_id, response, feedback },
FlatEvent::SubagentEvent { parent_tool_call_id, agent_id, subagent_type, event } => Event::SubagentEvent { parent_tool_call_id, agent_id, subagent_type, event },
FlatEvent::SteerInput { user_input } => Event::SteerInput { user_input },
FlatEvent::PlanDisplay { content, file_path } => Event::PlanDisplay { content, file_path },
FlatEvent::HookTriggered { event, target, hook_count } => Event::HookTriggered { event, target, hook_count },
FlatEvent::HookResolved { event, target, action, reason, duration_ms } => Event::HookResolved { event, target, action, reason, duration_ms },
}
}
}
#[derive(Serialize, Deserialize)]
struct EventEnvelope {
#[serde(rename = "type")]
type_name: String,
payload: serde_json::Value,
}
impl Serialize for Event {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let flat = FlatEvent::from(self.clone());
let mut value = serde_json::to_value(&flat).map_err(serde::ser::Error::custom)?;
let obj = value
.as_object_mut()
.ok_or_else(|| serde::ser::Error::custom("expected object"))?;
let type_name = obj
.remove("type")
.and_then(|v| v.as_str().map(String::from))
.ok_or_else(|| serde::ser::Error::custom("missing type"))?;
EventEnvelope { type_name, payload: value }.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Event {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let envelope = EventEnvelope::deserialize(deserializer)?;
let mut value = envelope.payload;
if let Some(obj) = value.as_object_mut() {
obj.insert("type".to_string(), serde_json::Value::String(envelope.type_name));
}
let flat: FlatEvent = serde_json::from_value(value).map_err(serde::de::Error::custom)?;
Ok(Event::from(flat))
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SubagentEventPayload {
#[serde(rename = "type")]
pub type_name: String,
pub payload: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct StatusUpdate {
#[serde(skip_serializing_if = "Option::is_none")]
pub context_usage: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub context_tokens: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_context_tokens: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub token_usage: Option<TokenUsage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub message_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub plan_mode: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TokenUsage {
pub input_other: u64,
pub output: u64,
pub input_cache_read: u64,
pub input_cache_creation: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ToolCallFunction {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub arguments: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum ApprovalResponseKind {
Approve,
#[serde(rename = "approve_for_session")]
ApproveForSession,
Reject,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum HookAction {
Allow,
Block,
}