use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Message {
System(SystemMessage),
User(UserMessage),
Assistant(AssistantMessage),
Result(ResultMessage),
StreamEvent(StreamEvent),
RateLimitEvent(RateLimitEvent),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SystemMessage {
pub subtype: String,
#[serde(flatten)]
pub data: serde_json::Map<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserMessage {
pub message: UserMessageContent,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub uuid: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent_tool_use_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tool_use_result: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserMessageContent {
#[serde(default = "default_user_role")]
pub role: String,
pub content: UserContent,
}
fn default_user_role() -> String {
"user".to_owned()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum UserContent {
Text(String),
Blocks(Vec<ContentBlock>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AssistantMessage {
pub message: AssistantMessageContent,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent_tool_use_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub session_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub uuid: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AssistantMessageContent {
pub model: String,
#[serde(default)]
pub content: Vec<ContentBlock>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub role: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stop_reason: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub usage: Option<serde_json::Value>,
#[serde(flatten)]
pub extra: serde_json::Map<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResultMessage {
pub subtype: String,
pub duration_ms: u64,
pub duration_api_ms: u64,
pub is_error: bool,
pub num_turns: u64,
pub session_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub total_cost_usd: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub usage: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub result: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub structured_output: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StreamEvent {
pub uuid: String,
pub session_id: String,
pub event: StreamEventPayload,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent_tool_use_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RateLimitEvent {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rate_limit_info: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub uuid: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub session_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum StreamEventPayload {
MessageStart {
message: serde_json::Value,
},
ContentBlockStart {
index: u64,
content_block: ContentBlockInfo,
},
ContentBlockDelta {
index: u64,
delta: Delta,
},
ContentBlockStop {
index: u64,
},
MessageDelta {
delta: serde_json::Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
usage: Option<serde_json::Value>,
},
MessageStop,
#[serde(other)]
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ContentBlockInfo {
Text {
#[serde(default)]
text: String,
},
Thinking {
#[serde(default)]
thinking: String,
},
ToolUse {
id: String,
name: String,
#[serde(default)]
input: serde_json::Value,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Delta {
TextDelta {
text: String,
},
ThinkingDelta {
thinking: String,
},
InputJsonDelta {
partial_json: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ContentBlock {
Text {
text: String,
},
Thinking {
thinking: String,
signature: String,
},
ToolUse {
id: String,
name: String,
input: serde_json::Value,
},
ToolResult {
tool_use_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
content: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
is_error: Option<bool>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum InputMessage {
User {
message: InputMessageContent,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InputMessageContent {
pub role: String,
pub content: InputContent,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum InputContent {
Text(String),
Blocks(Vec<InputContentBlock>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum InputContentBlock {
Text {
text: String,
},
Image {
source: ImageSource,
},
ToolResult {
tool_use_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
content: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
is_error: Option<bool>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImageSource {
#[serde(rename = "type")]
pub source_type: String,
pub media_type: String,
pub data: String,
}
impl InputMessage {
pub fn user_text(text: impl Into<String>) -> Self {
InputMessage::User {
message: InputMessageContent {
role: "user".to_owned(),
content: InputContent::Text(text.into()),
},
}
}
pub fn user_with_images(
text: impl Into<String>,
images: Vec<(String, String)>,
) -> Self {
let mut blocks: Vec<InputContentBlock> = images
.into_iter()
.map(|(media_type, data)| InputContentBlock::Image {
source: ImageSource {
source_type: "base64".to_owned(),
media_type,
data,
},
})
.collect();
let text = text.into();
if !text.is_empty() {
blocks.push(InputContentBlock::Text { text });
}
InputMessage::User {
message: InputMessageContent {
role: "user".to_owned(),
content: InputContent::Blocks(blocks),
},
}
}
pub fn tool_result(
tool_use_id: impl Into<String>,
output: impl Into<String>,
is_error: bool,
) -> Self {
InputMessage::User {
message: InputMessageContent {
role: "user".to_owned(),
content: InputContent::Blocks(vec![InputContentBlock::ToolResult {
tool_use_id: tool_use_id.into(),
content: Some(output.into()),
is_error: if is_error { Some(true) } else { None },
}]),
},
}
}
}
impl Message {
pub fn is_result(&self) -> bool {
matches!(self, Message::Result(_))
}
pub fn is_stream_event(&self) -> bool {
matches!(self, Message::StreamEvent(_))
}
pub fn is_assistant(&self) -> bool {
matches!(self, Message::Assistant(_))
}
pub fn as_assistant(&self) -> Option<&AssistantMessage> {
match self {
Message::Assistant(m) => Some(m),
_ => None,
}
}
pub fn as_result(&self) -> Option<&ResultMessage> {
match self {
Message::Result(m) => Some(m),
_ => None,
}
}
pub fn as_stream_event(&self) -> Option<&StreamEvent> {
match self {
Message::StreamEvent(e) => Some(e),
_ => None,
}
}
}