Skip to main content

claudius/types/
content_block.rs

1use serde::{Deserialize, Serialize};
2
3use crate::types::{
4    DocumentBlock, ImageBlock, RedactedThinkingBlock, ServerToolUseBlock, TextBlock, ThinkingBlock,
5    ToolResultBlock, ToolUseBlock, WebSearchToolResultBlock,
6};
7
8/// A block of content in a message.
9///
10/// This enum represents the different types of content blocks that can be included
11/// in a message's content.
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
13#[serde(tag = "type")]
14pub enum ContentBlock {
15    /// A block of text content
16    #[serde(rename = "text")]
17    Text(TextBlock),
18
19    /// An image block
20    #[serde(rename = "image")]
21    Image(ImageBlock),
22
23    /// A block representing a tool use request
24    #[serde(rename = "tool_use")]
25    ToolUse(ToolUseBlock),
26
27    /// A block representing a server-side tool use request
28    #[serde(rename = "server_tool_use")]
29    ServerToolUse(ServerToolUseBlock),
30
31    /// A web search tool result block
32    #[serde(rename = "web_search_tool_result")]
33    WebSearchToolResult(WebSearchToolResultBlock),
34
35    /// A tool result block
36    #[serde(rename = "tool_result")]
37    ToolResult(ToolResultBlock),
38
39    /// A document block
40    #[serde(rename = "document")]
41    Document(DocumentBlock),
42
43    /// A block containing model thinking
44    #[serde(rename = "thinking")]
45    Thinking(ThinkingBlock),
46
47    /// A block containing redacted thinking data
48    #[serde(rename = "redacted_thinking")]
49    RedactedThinking(RedactedThinkingBlock),
50}
51
52impl ContentBlock {
53    /// Returns true if this block is a text block
54    pub fn is_text(&self) -> bool {
55        matches!(self, ContentBlock::Text(_))
56    }
57
58    /// Returns true if this block is an image block
59    pub fn is_image(&self) -> bool {
60        matches!(self, ContentBlock::Image(_))
61    }
62
63    /// Returns true if this block is a tool use block
64    pub fn is_tool_use(&self) -> bool {
65        matches!(self, ContentBlock::ToolUse(_))
66    }
67
68    /// Returns true if this block is a server tool use block
69    pub fn is_server_tool_use(&self) -> bool {
70        matches!(self, ContentBlock::ServerToolUse(_))
71    }
72
73    /// Returns true if this block is a web search tool result block
74    pub fn is_web_search_tool_result(&self) -> bool {
75        matches!(self, ContentBlock::WebSearchToolResult(_))
76    }
77
78    /// Returns true if this block is a tool result block
79    pub fn is_tool_result(&self) -> bool {
80        matches!(self, ContentBlock::ToolResult(_))
81    }
82
83    /// Returns true if this block is a document block
84    pub fn is_document(&self) -> bool {
85        matches!(self, ContentBlock::Document(_))
86    }
87
88    /// Returns true if this block is a thinking block
89    pub fn is_thinking(&self) -> bool {
90        matches!(self, ContentBlock::Thinking(_))
91    }
92
93    /// Returns true if this block is a redacted thinking block
94    pub fn is_redacted_thinking(&self) -> bool {
95        matches!(self, ContentBlock::RedactedThinking(_))
96    }
97
98    /// Returns a reference to the inner TextBlock if this is a Text variant,
99    /// or None otherwise.
100    pub fn as_text(&self) -> Option<&TextBlock> {
101        match self {
102            ContentBlock::Text(block) => Some(block),
103            _ => None,
104        }
105    }
106
107    /// Returns a reference to the inner ImageBlock if this is an Image variant,
108    /// or None otherwise.
109    pub fn as_image(&self) -> Option<&ImageBlock> {
110        match self {
111            ContentBlock::Image(block) => Some(block),
112            _ => None,
113        }
114    }
115
116    /// Returns a reference to the inner ToolUseBlock if this is a ToolUse variant,
117    /// or None otherwise.
118    pub fn as_tool_use(&self) -> Option<&ToolUseBlock> {
119        match self {
120            ContentBlock::ToolUse(block) => Some(block),
121            _ => None,
122        }
123    }
124
125    /// Returns a reference to the inner ServerToolUseBlock if this is a ServerToolUse variant,
126    /// or None otherwise.
127    pub fn as_server_tool_use(&self) -> Option<&ServerToolUseBlock> {
128        match self {
129            ContentBlock::ServerToolUse(block) => Some(block),
130            _ => None,
131        }
132    }
133
134    /// Returns a reference to the inner WebSearchToolResultBlock if this is a WebSearchToolResult variant,
135    /// or None otherwise.
136    pub fn as_web_search_tool_result(&self) -> Option<&WebSearchToolResultBlock> {
137        match self {
138            ContentBlock::WebSearchToolResult(block) => Some(block),
139            _ => None,
140        }
141    }
142
143    /// Returns a reference to the inner ToolResultBlock if this is a ToolResult variant,
144    /// or None otherwise.
145    pub fn as_tool_result(&self) -> Option<&ToolResultBlock> {
146        match self {
147            ContentBlock::ToolResult(block) => Some(block),
148            _ => None,
149        }
150    }
151
152    /// Returns a reference to the inner DocumentBlock if this is a Document variant,
153    /// or None otherwise.
154    pub fn as_document(&self) -> Option<&DocumentBlock> {
155        match self {
156            ContentBlock::Document(block) => Some(block),
157            _ => None,
158        }
159    }
160
161    /// Returns a reference to the inner ThinkingBlock if this is a Thinking variant,
162    /// or None otherwise.
163    pub fn as_thinking(&self) -> Option<&ThinkingBlock> {
164        match self {
165            ContentBlock::Thinking(block) => Some(block),
166            _ => None,
167        }
168    }
169
170    /// Returns a reference to the inner RedactedThinkingBlock if this is a RedactedThinking variant,
171    /// or None otherwise.
172    pub fn as_redacted_thinking(&self) -> Option<&RedactedThinkingBlock> {
173        match self {
174            ContentBlock::RedactedThinking(block) => Some(block),
175            _ => None,
176        }
177    }
178}
179
180/// Helper methods to create ContentBlock variants
181impl From<TextBlock> for ContentBlock {
182    fn from(block: TextBlock) -> Self {
183        ContentBlock::Text(block)
184    }
185}
186
187impl From<ImageBlock> for ContentBlock {
188    fn from(block: ImageBlock) -> Self {
189        ContentBlock::Image(block)
190    }
191}
192
193impl From<ToolUseBlock> for ContentBlock {
194    fn from(block: ToolUseBlock) -> Self {
195        ContentBlock::ToolUse(block)
196    }
197}
198
199impl From<ServerToolUseBlock> for ContentBlock {
200    fn from(block: ServerToolUseBlock) -> Self {
201        ContentBlock::ServerToolUse(block)
202    }
203}
204
205impl From<WebSearchToolResultBlock> for ContentBlock {
206    fn from(block: WebSearchToolResultBlock) -> Self {
207        ContentBlock::WebSearchToolResult(block)
208    }
209}
210
211impl From<ToolResultBlock> for ContentBlock {
212    fn from(block: ToolResultBlock) -> Self {
213        ContentBlock::ToolResult(block)
214    }
215}
216
217impl From<DocumentBlock> for ContentBlock {
218    fn from(block: DocumentBlock) -> Self {
219        ContentBlock::Document(block)
220    }
221}
222
223impl From<ThinkingBlock> for ContentBlock {
224    fn from(block: ThinkingBlock) -> Self {
225        ContentBlock::Thinking(block)
226    }
227}
228
229impl From<RedactedThinkingBlock> for ContentBlock {
230    fn from(block: RedactedThinkingBlock) -> Self {
231        ContentBlock::RedactedThinking(block)
232    }
233}
234
235#[cfg(test)]
236mod tests {
237    use super::*;
238
239    #[test]
240    fn text_block_serialization() {
241        let text_block = TextBlock::new("This is some text content.");
242        let content_block = ContentBlock::from(text_block);
243
244        let json = serde_json::to_string(&content_block).unwrap();
245        let expected = r#"{"type":"text","text":"This is some text content."}"#;
246
247        assert_eq!(json, expected);
248    }
249
250    #[test]
251    fn tool_use_block_serialization() {
252        let input_json = serde_json::json!({
253            "query": "weather in San Francisco",
254            "limit": 5
255        });
256
257        let tool_block = ToolUseBlock::new("tool_123", "search", input_json);
258        let content_block = ContentBlock::from(tool_block);
259
260        let json = serde_json::to_string(&content_block).unwrap();
261        let actual: serde_json::Value = serde_json::from_str(&json).unwrap();
262        let expected: serde_json::Value = serde_json::json!({
263            "type": "tool_use",
264            "id": "tool_123",
265            "input": {"limit": 5, "query": "weather in San Francisco"},
266            "name": "search"
267        });
268
269        assert_eq!(actual, expected);
270    }
271
272    #[test]
273    fn server_tool_use_block_serialization() {
274        let server_block =
275            ServerToolUseBlock::new_web_search("tool_123", "weather in San Francisco");
276        let content_block = ContentBlock::from(server_block);
277
278        let json = serde_json::to_string(&content_block).unwrap();
279        let expected = r#"{"type":"server_tool_use","id":"tool_123","input":{"query":"weather in San Francisco"},"name":"web_search"}"#;
280
281        assert_eq!(json, expected);
282    }
283
284    #[test]
285    fn thinking_block_serialization() {
286        let thinking_block = ThinkingBlock::new(
287            "Let me think through this problem step by step...",
288            "abc123signature",
289        );
290        let content_block = ContentBlock::from(thinking_block);
291
292        let json = serde_json::to_string(&content_block).unwrap();
293        let expected = r#"{"type":"thinking","signature":"abc123signature","thinking":"Let me think through this problem step by step..."}"#;
294
295        assert_eq!(json, expected);
296    }
297
298    #[test]
299    fn redacted_thinking_block_serialization() {
300        let redacted_block = RedactedThinkingBlock::new("encoded-thinking-data-123");
301        let content_block = ContentBlock::from(redacted_block);
302
303        let json = serde_json::to_string(&content_block).unwrap();
304        let expected = r#"{"type":"redacted_thinking","data":"encoded-thinking-data-123"}"#;
305
306        assert_eq!(json, expected);
307    }
308
309    #[test]
310    fn deserialization() {
311        let json = r#"{"text":"This is some text content.","type":"text"}"#;
312        let content_block: ContentBlock = serde_json::from_str(json).unwrap();
313
314        assert!(content_block.is_text());
315        assert!(!content_block.is_tool_use());
316
317        if let ContentBlock::Text(block) = content_block {
318            assert_eq!(block.text, "This is some text content.");
319        } else {
320            panic!("Expected TextBlock");
321        }
322
323        let json = r#"{"id":"tool_123","input":{"query":"weather in San Francisco"},"name":"web_search","type":"server_tool_use"}"#;
324        let content_block: ContentBlock = serde_json::from_str(json).unwrap();
325
326        assert!(content_block.is_server_tool_use());
327        assert!(!content_block.is_text());
328
329        if let ContentBlock::ServerToolUse(block) = content_block {
330            assert_eq!(block.id, "tool_123");
331            assert_eq!(block.name, "web_search");
332        } else {
333            panic!("Expected ServerToolUseBlock");
334        }
335    }
336
337    #[test]
338    fn as_methods() {
339        let text_block = TextBlock::new("This is some text content.");
340        let content_block = ContentBlock::from(text_block);
341
342        assert!(content_block.as_text().is_some());
343        assert!(content_block.as_image().is_none());
344        assert!(content_block.as_tool_use().is_none());
345        assert!(content_block.as_server_tool_use().is_none());
346        assert!(content_block.as_web_search_tool_result().is_none());
347        assert!(content_block.as_tool_result().is_none());
348        assert!(content_block.as_document().is_none());
349        assert!(content_block.as_thinking().is_none());
350        assert!(content_block.as_redacted_thinking().is_none());
351
352        let text_ref = content_block.as_text().unwrap();
353        assert_eq!(text_ref.text, "This is some text content.");
354    }
355
356    #[test]
357    fn image_block_serialization() {
358        let image_source =
359            crate::types::UrlImageSource::new("https://example.com/image.jpg".to_string());
360        let image_block = ImageBlock::new_with_url(image_source);
361        let content_block = ContentBlock::from(image_block);
362
363        let json = serde_json::to_string(&content_block).unwrap();
364        let expected =
365            r#"{"type":"image","source":{"type":"url","url":"https://example.com/image.jpg"}}"#;
366
367        assert_eq!(json, expected);
368    }
369}