Skip to main content

claude_codes/io/
content_blocks.rs

1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2use serde_json::Value;
3use std::fmt;
4
5/// Deserialize content blocks that can be either a string or array
6pub(crate) fn deserialize_content_blocks<'de, D>(
7    deserializer: D,
8) -> Result<Vec<ContentBlock>, D::Error>
9where
10    D: Deserializer<'de>,
11{
12    let value: Value = Value::deserialize(deserializer)?;
13    match value {
14        Value::String(s) => Ok(vec![ContentBlock::Text(TextBlock { text: s })]),
15        Value::Array(_) => serde_json::from_value(value).map_err(serde::de::Error::custom),
16        _ => Err(serde::de::Error::custom(
17            "content must be a string or array",
18        )),
19    }
20}
21
22/// Content blocks for messages
23#[derive(Debug, Clone, Serialize, Deserialize)]
24#[serde(tag = "type", rename_all = "snake_case")]
25pub enum ContentBlock {
26    Text(TextBlock),
27    Image(ImageBlock),
28    Thinking(ThinkingBlock),
29    ToolUse(ToolUseBlock),
30    ToolResult(ToolResultBlock),
31}
32
33/// Text content block
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct TextBlock {
36    pub text: String,
37}
38
39/// Image content block (follows Anthropic API structure)
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct ImageBlock {
42    pub source: ImageSource,
43}
44
45/// Encoding type for image source data.
46#[derive(Debug, Clone, PartialEq, Eq, Hash)]
47pub enum ImageSourceType {
48    /// Base64-encoded image data.
49    Base64,
50    /// A source type not yet known to this version of the crate.
51    Unknown(String),
52}
53
54impl ImageSourceType {
55    pub fn as_str(&self) -> &str {
56        match self {
57            Self::Base64 => "base64",
58            Self::Unknown(s) => s.as_str(),
59        }
60    }
61}
62
63impl fmt::Display for ImageSourceType {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        f.write_str(self.as_str())
66    }
67}
68
69impl From<&str> for ImageSourceType {
70    fn from(s: &str) -> Self {
71        match s {
72            "base64" => Self::Base64,
73            other => Self::Unknown(other.to_string()),
74        }
75    }
76}
77
78impl Serialize for ImageSourceType {
79    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
80        serializer.serialize_str(self.as_str())
81    }
82}
83
84impl<'de> Deserialize<'de> for ImageSourceType {
85    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
86        let s = String::deserialize(deserializer)?;
87        Ok(Self::from(s.as_str()))
88    }
89}
90
91/// MIME type for image content.
92#[derive(Debug, Clone, PartialEq, Eq, Hash)]
93pub enum MediaType {
94    /// JPEG image.
95    Jpeg,
96    /// PNG image.
97    Png,
98    /// GIF image.
99    Gif,
100    /// WebP image.
101    Webp,
102    /// A media type not yet known to this version of the crate.
103    Unknown(String),
104}
105
106impl MediaType {
107    pub fn as_str(&self) -> &str {
108        match self {
109            Self::Jpeg => "image/jpeg",
110            Self::Png => "image/png",
111            Self::Gif => "image/gif",
112            Self::Webp => "image/webp",
113            Self::Unknown(s) => s.as_str(),
114        }
115    }
116}
117
118impl fmt::Display for MediaType {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        f.write_str(self.as_str())
121    }
122}
123
124impl From<&str> for MediaType {
125    fn from(s: &str) -> Self {
126        match s {
127            "image/jpeg" => Self::Jpeg,
128            "image/png" => Self::Png,
129            "image/gif" => Self::Gif,
130            "image/webp" => Self::Webp,
131            other => Self::Unknown(other.to_string()),
132        }
133    }
134}
135
136impl Serialize for MediaType {
137    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
138        serializer.serialize_str(self.as_str())
139    }
140}
141
142impl<'de> Deserialize<'de> for MediaType {
143    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
144        let s = String::deserialize(deserializer)?;
145        Ok(Self::from(s.as_str()))
146    }
147}
148
149/// Image source information
150#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct ImageSource {
152    #[serde(rename = "type")]
153    pub source_type: ImageSourceType,
154    pub media_type: MediaType,
155    pub data: String,
156}
157
158/// Thinking content block
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct ThinkingBlock {
161    pub thinking: String,
162    pub signature: String,
163}
164
165/// Tool use content block
166#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct ToolUseBlock {
168    pub id: String,
169    pub name: String,
170    pub input: Value,
171}
172
173impl ToolUseBlock {
174    /// Try to parse the input as a typed ToolInput.
175    ///
176    /// This attempts to deserialize the raw JSON input into a strongly-typed
177    /// `ToolInput` enum variant. Returns `None` if parsing fails.
178    ///
179    /// # Example
180    ///
181    /// ```
182    /// use claude_codes::{ToolUseBlock, ToolInput};
183    /// use serde_json::json;
184    ///
185    /// let block = ToolUseBlock {
186    ///     id: "toolu_123".to_string(),
187    ///     name: "Bash".to_string(),
188    ///     input: json!({"command": "ls -la"}),
189    /// };
190    ///
191    /// if let Some(ToolInput::Bash(bash)) = block.typed_input() {
192    ///     assert_eq!(bash.command, "ls -la");
193    /// }
194    /// ```
195    pub fn typed_input(&self) -> Option<crate::tool_inputs::ToolInput> {
196        serde_json::from_value(self.input.clone()).ok()
197    }
198
199    /// Parse the input as a typed ToolInput, returning an error on failure.
200    ///
201    /// Unlike `typed_input()`, this method returns the parsing error for debugging.
202    pub fn try_typed_input(&self) -> Result<crate::tool_inputs::ToolInput, serde_json::Error> {
203        serde_json::from_value(self.input.clone())
204    }
205}
206
207/// Tool result content block
208#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct ToolResultBlock {
210    pub tool_use_id: String,
211    #[serde(skip_serializing_if = "Option::is_none")]
212    pub content: Option<ToolResultContent>,
213    #[serde(skip_serializing_if = "Option::is_none")]
214    pub is_error: Option<bool>,
215}
216
217/// Tool result content type
218#[derive(Debug, Clone, Serialize, Deserialize)]
219#[serde(untagged)]
220pub enum ToolResultContent {
221    Text(String),
222    Structured(Vec<Value>),
223}