Skip to main content

anthropic_async/types/
content.rs

1use serde::{Deserialize, Serialize};
2
3use super::common::CacheControl;
4
5/// Content for tool results
6///
7/// Can be either a simple string or an array of content blocks.
8/// Tool results can contain text or images, but not nested tool results.
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
10#[serde(untagged)]
11pub enum ToolResultContent {
12    /// Simple string content
13    String(String),
14    /// Array of content blocks (text or image)
15    Blocks(Vec<ToolResultContentBlock>),
16}
17
18/// Content block for tool results
19///
20/// Tool results can contain text or images, but not other tool results.
21#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
22#[serde(tag = "type", rename_all = "snake_case")]
23pub enum ToolResultContentBlock {
24    /// Text content block
25    Text {
26        /// The text content
27        text: String,
28        /// Optional cache control for prompt caching
29        #[serde(skip_serializing_if = "Option::is_none")]
30        cache_control: Option<CacheControl>,
31    },
32    /// Image content block
33    Image {
34        /// Image source (base64 or URL)
35        source: ImageSource,
36        /// Optional cache control for prompt caching
37        #[serde(skip_serializing_if = "Option::is_none")]
38        cache_control: Option<CacheControl>,
39    },
40}
41
42impl From<&str> for ToolResultContent {
43    fn from(s: &str) -> Self {
44        Self::String(s.to_string())
45    }
46}
47
48impl From<String> for ToolResultContent {
49    fn from(s: String) -> Self {
50        Self::String(s)
51    }
52}
53
54/// Image source for multimodal content
55#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
56#[serde(tag = "type", rename_all = "snake_case")]
57pub enum ImageSource {
58    /// Base64-encoded image data
59    Base64 {
60        /// Media type (e.g., "image/png")
61        media_type: String,
62        /// Base64-encoded image data
63        data: String,
64    },
65    /// Image URL
66    Url {
67        /// URL to the image
68        url: String,
69    },
70}
71
72/// Document source for multimodal content
73#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
74#[serde(tag = "type", rename_all = "snake_case")]
75pub enum DocumentSource {
76    /// Base64-encoded document data
77    Base64 {
78        /// Media type (e.g., "application/pdf")
79        media_type: String,
80        /// Base64-encoded document data
81        data: String,
82    },
83    /// Document URL
84    Url {
85        /// URL to the document
86        url: String,
87    },
88}
89
90/// Role of a message in a conversation
91#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
92#[serde(rename_all = "snake_case")]
93pub enum MessageRole {
94    /// User message
95    User,
96    /// Assistant message
97    Assistant,
98}
99
100// Request-side content blocks
101/// Content block parameter for requests
102///
103/// This enum represents the various types of content that can be sent in a request.
104/// Note that this is separate from the response `ContentBlock` enum due to the
105/// asymmetric nature of the Anthropic API - requests accept more content types
106/// than responses return.
107#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
108#[serde(tag = "type", rename_all = "snake_case")]
109pub enum ContentBlockParam {
110    /// Text content block
111    Text {
112        /// The text content
113        text: String,
114        /// Optional cache control for prompt caching
115        #[serde(skip_serializing_if = "Option::is_none")]
116        cache_control: Option<CacheControl>,
117    },
118    /// Tool result block
119    ToolResult {
120        /// ID of the tool use that this is responding to
121        tool_use_id: String,
122        /// Optional result content (string or array of content blocks)
123        #[serde(skip_serializing_if = "Option::is_none")]
124        content: Option<ToolResultContent>,
125        /// Whether this is an error result
126        #[serde(skip_serializing_if = "Option::is_none")]
127        is_error: Option<bool>,
128        /// Optional cache control for prompt caching
129        #[serde(skip_serializing_if = "Option::is_none")]
130        cache_control: Option<CacheControl>,
131    },
132    /// Image content block
133    Image {
134        /// Image source (base64 or URL)
135        source: ImageSource,
136        /// Optional cache control for prompt caching
137        #[serde(skip_serializing_if = "Option::is_none")]
138        cache_control: Option<CacheControl>,
139    },
140    /// Document content block
141    Document {
142        /// Document source (base64 or URL)
143        source: DocumentSource,
144        /// Optional cache control for prompt caching
145        #[serde(skip_serializing_if = "Option::is_none")]
146        cache_control: Option<CacheControl>,
147    },
148}
149
150// Response-side content blocks
151/// Content block in a response
152///
153/// This enum represents the various types of content that can be returned in a response.
154/// Note that this is separate from the request `ContentBlockParam` enum due to the
155/// asymmetric nature of the Anthropic API.
156#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
157#[serde(tag = "type", rename_all = "snake_case")]
158pub enum ContentBlock {
159    /// Text content block
160    Text {
161        /// The text content
162        text: String,
163    },
164    /// Tool use block
165    ToolUse {
166        /// Tool use ID
167        id: String,
168        /// Tool name
169        name: String,
170        /// Tool input as JSON value
171        input: serde_json::Value,
172    },
173}
174
175/// System prompt parameter
176///
177/// Can be either a simple string or an array of text blocks with cache control.
178#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
179#[serde(untagged)]
180pub enum SystemParam {
181    /// Simple string system prompt
182    String(String),
183    /// Array of text blocks with optional cache control
184    Blocks(Vec<TextBlockParam>),
185}
186
187/// Message content parameter
188///
189/// Can be either a simple string or an array of content blocks.
190#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
191#[serde(untagged)]
192pub enum MessageContentParam {
193    /// Simple string content
194    String(String),
195    /// Array of content blocks
196    Blocks(Vec<ContentBlockParam>),
197}
198
199/// Text block parameter for system prompts
200#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
201pub struct TextBlockParam {
202    /// The text content
203    pub text: String,
204    /// Type field for serialization (always "text")
205    #[serde(
206        rename = "type",
207        default = "text_type",
208        skip_serializing_if = "is_text"
209    )]
210    pub kind: String,
211    /// Optional cache control for prompt caching
212    #[serde(skip_serializing_if = "Option::is_none")]
213    pub cache_control: Option<CacheControl>,
214}
215
216fn text_type() -> String {
217    "text".to_string()
218}
219
220fn is_text(s: &str) -> bool {
221    s == "text"
222}
223
224impl TextBlockParam {
225    /// Creates a new text block without cache control
226    #[must_use]
227    pub fn new(text: impl Into<String>) -> Self {
228        Self {
229            text: text.into(),
230            kind: "text".to_string(),
231            cache_control: None,
232        }
233    }
234
235    /// Creates a new text block with cache control
236    #[must_use]
237    pub fn with_cache_control(text: impl Into<String>, cache_control: CacheControl) -> Self {
238        Self {
239            text: text.into(),
240            kind: "text".to_string(),
241            cache_control: Some(cache_control),
242        }
243    }
244}
245
246/// A message parameter in a conversation
247#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
248pub struct MessageParam {
249    /// Role of the message
250    pub role: MessageRole,
251    /// Content of the message
252    pub content: MessageContentParam,
253}
254
255// Ergonomic conversions
256impl From<&str> for MessageContentParam {
257    fn from(s: &str) -> Self {
258        Self::String(s.to_string())
259    }
260}
261
262impl From<String> for MessageContentParam {
263    fn from(s: String) -> Self {
264        Self::String(s)
265    }
266}
267
268impl From<&str> for SystemParam {
269    fn from(s: &str) -> Self {
270        Self::String(s.to_string())
271    }
272}
273
274impl From<String> for SystemParam {
275    fn from(s: String) -> Self {
276        Self::String(s)
277    }
278}
279
280#[cfg(test)]
281mod tests {
282    use super::*;
283
284    #[test]
285    fn message_role_ser() {
286        assert_eq!(
287            serde_json::to_string(&MessageRole::User).unwrap(),
288            r#""user""#
289        );
290        assert_eq!(
291            serde_json::to_string(&MessageRole::Assistant).unwrap(),
292            r#""assistant""#
293        );
294    }
295
296    #[test]
297    fn content_block_param_text_ser() {
298        let cb = ContentBlockParam::Text {
299            text: "hello".into(),
300            cache_control: None,
301        };
302        let s = serde_json::to_string(&cb).unwrap();
303        assert!(s.contains(r#""type":"text""#));
304        assert!(s.contains(r#""text":"hello""#));
305        assert!(!s.contains("cache_control"));
306    }
307
308    #[test]
309    fn content_block_response_text_ser() {
310        let cb = ContentBlock::Text {
311            text: "response".into(),
312        };
313        let s = serde_json::to_string(&cb).unwrap();
314        assert!(s.contains(r#""type":"text""#));
315        assert!(s.contains(r#""text":"response""#));
316    }
317
318    #[test]
319    fn system_param_string() {
320        let sys: SystemParam = "You are helpful".into();
321        let s = serde_json::to_string(&sys).unwrap();
322        assert_eq!(s, r#""You are helpful""#);
323    }
324
325    #[test]
326    fn system_param_blocks() {
327        let sys = SystemParam::Blocks(vec![TextBlockParam::new("test")]);
328        let s = serde_json::to_string(&sys).unwrap();
329        assert!(s.contains(r#""text":"test""#));
330    }
331
332    #[test]
333    fn message_content_param_string() {
334        let content: MessageContentParam = "hello".into();
335        let s = serde_json::to_string(&content).unwrap();
336        assert_eq!(s, r#""hello""#);
337    }
338
339    #[test]
340    fn message_content_param_blocks() {
341        let content = MessageContentParam::Blocks(vec![ContentBlockParam::Text {
342            text: "test".into(),
343            cache_control: None,
344        }]);
345        let s = serde_json::to_string(&content).unwrap();
346        assert!(s.contains(r#""type":"text""#));
347        assert!(s.contains(r#""text":"test""#));
348    }
349
350    #[test]
351    fn text_block_param_with_cache() {
352        let tb = TextBlockParam::with_cache_control("cached", CacheControl::ephemeral_1h());
353        let s = serde_json::to_string(&tb).unwrap();
354        assert!(s.contains(r#""text":"cached""#));
355        assert!(s.contains(r#""cache_control""#));
356    }
357}