use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Citation {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub start_index: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub end_index: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cited_text: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub extras: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Annotation {
Citation(Citation),
NonStandardAnnotation {
#[serde(default, skip_serializing_if = "Option::is_none")]
id: Option<String>,
value: Value,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ContentBlock {
Text {
text: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
annotations: Option<Vec<Annotation>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
index: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
extras: Option<Value>,
},
Image {
#[serde(default, skip_serializing_if = "Option::is_none")]
id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
base64: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
file_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
mime_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
index: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
image: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
source_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
media_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
extras: Option<Value>,
},
ImageUrl {
image_url: ImageUrlInfo,
#[serde(default, skip_serializing_if = "Option::is_none")]
extras: Option<Value>,
},
Audio {
#[serde(default, skip_serializing_if = "Option::is_none")]
id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
base64: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
file_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
mime_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
index: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
audio: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
source_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
media_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
extras: Option<Value>,
},
Video {
#[serde(default, skip_serializing_if = "Option::is_none")]
id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
base64: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
file_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
mime_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
index: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
video: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
source_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
media_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
extras: Option<Value>,
},
File {
#[serde(default, skip_serializing_if = "Option::is_none")]
id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
base64: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
file_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
mime_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
index: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
file: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
source_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
media_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
extras: Option<Value>,
},
#[serde(rename = "text-plain")]
PlainText {
#[serde(default, skip_serializing_if = "Option::is_none")]
id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
base64: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
file_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
mime_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
text: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
title: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
context: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
index: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
extras: Option<Value>,
},
Reasoning {
#[serde(default, skip_serializing_if = "Option::is_none")]
reasoning: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
index: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
extras: Option<Value>,
},
RedactedThinking {
#[serde(default, skip_serializing_if = "Option::is_none")]
data: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
extras: Option<Value>,
},
CacheControl {
#[serde(default, skip_serializing_if = "Option::is_none")]
cache_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
extras: Option<Value>,
},
ToolCall {
name: String,
args: Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
index: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
extras: Option<Value>,
},
ToolCallChunk {
#[serde(default, skip_serializing_if = "Option::is_none")]
name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
args: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
index: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
extras: Option<Value>,
},
InvalidToolCall {
#[serde(default, skip_serializing_if = "Option::is_none")]
name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
args: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
error: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
index: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
extras: Option<Value>,
},
ServerToolCall {
name: String,
args: Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
index: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
extras: Option<Value>,
},
ServerToolCallChunk {
#[serde(default, skip_serializing_if = "Option::is_none")]
name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
args: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
index: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
extras: Option<Value>,
},
ServerToolResult {
#[serde(default, skip_serializing_if = "Option::is_none")]
tool_call_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
status: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
output: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
index: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
extras: Option<Value>,
},
ToolResult {
tool_use_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
content: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
is_error: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
extras: Option<Value>,
},
Data {
data: Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
extras: Option<Value>,
},
#[serde(rename = "non_standard")]
NonStandard {
#[serde(default, skip_serializing_if = "Option::is_none")]
id: Option<String>,
value: Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
index: Option<usize>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ImageUrlInfo {
pub url: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub detail: Option<String>,
}
impl ContentBlock {
pub fn text_only(text: impl Into<String>) -> Self {
Self::Text {
text: text.into(),
id: None,
annotations: None,
index: None,
extras: None,
}
}
}
pub const KNOWN_BLOCK_TYPES: &[&str] = &[
"text",
"reasoning",
"redacted_thinking",
"cache_control",
"tool_call",
"invalid_tool_call",
"tool_call_chunk",
"tool_result",
"image",
"image_url",
"audio",
"file",
"text-plain",
"video",
"server_tool_call",
"server_tool_call_chunk",
"server_tool_result",
"data",
"non_standard",
];
pub fn is_data_content_block_type(block_type: &str) -> bool {
matches!(
block_type,
"image" | "video" | "audio" | "text-plain" | "file"
)
}