claudius/types/
content_block.rs1use serde::{Deserialize, Serialize};
2
3use crate::types::{
4 DocumentBlock, ImageBlock, RedactedThinkingBlock, ServerToolUseBlock, TextBlock, ThinkingBlock,
5 ToolResultBlock, ToolUseBlock, WebSearchToolResultBlock,
6};
7
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
13#[serde(tag = "type")]
14pub enum ContentBlock {
15 #[serde(rename = "text")]
17 Text(TextBlock),
18
19 #[serde(rename = "image")]
21 Image(ImageBlock),
22
23 #[serde(rename = "tool_use")]
25 ToolUse(ToolUseBlock),
26
27 #[serde(rename = "server_tool_use")]
29 ServerToolUse(ServerToolUseBlock),
30
31 #[serde(rename = "web_search_tool_result")]
33 WebSearchToolResult(WebSearchToolResultBlock),
34
35 #[serde(rename = "tool_result")]
37 ToolResult(ToolResultBlock),
38
39 #[serde(rename = "document")]
41 Document(DocumentBlock),
42
43 #[serde(rename = "thinking")]
45 Thinking(ThinkingBlock),
46
47 #[serde(rename = "redacted_thinking")]
49 RedactedThinking(RedactedThinkingBlock),
50}
51
52impl ContentBlock {
53 pub fn is_text(&self) -> bool {
55 matches!(self, ContentBlock::Text(_))
56 }
57
58 pub fn is_image(&self) -> bool {
60 matches!(self, ContentBlock::Image(_))
61 }
62
63 pub fn is_tool_use(&self) -> bool {
65 matches!(self, ContentBlock::ToolUse(_))
66 }
67
68 pub fn is_server_tool_use(&self) -> bool {
70 matches!(self, ContentBlock::ServerToolUse(_))
71 }
72
73 pub fn is_web_search_tool_result(&self) -> bool {
75 matches!(self, ContentBlock::WebSearchToolResult(_))
76 }
77
78 pub fn is_tool_result(&self) -> bool {
80 matches!(self, ContentBlock::ToolResult(_))
81 }
82
83 pub fn is_document(&self) -> bool {
85 matches!(self, ContentBlock::Document(_))
86 }
87
88 pub fn is_thinking(&self) -> bool {
90 matches!(self, ContentBlock::Thinking(_))
91 }
92
93 pub fn is_redacted_thinking(&self) -> bool {
95 matches!(self, ContentBlock::RedactedThinking(_))
96 }
97
98 pub fn as_text(&self) -> Option<&TextBlock> {
101 match self {
102 ContentBlock::Text(block) => Some(block),
103 _ => None,
104 }
105 }
106
107 pub fn as_image(&self) -> Option<&ImageBlock> {
110 match self {
111 ContentBlock::Image(block) => Some(block),
112 _ => None,
113 }
114 }
115
116 pub fn as_tool_use(&self) -> Option<&ToolUseBlock> {
119 match self {
120 ContentBlock::ToolUse(block) => Some(block),
121 _ => None,
122 }
123 }
124
125 pub fn as_server_tool_use(&self) -> Option<&ServerToolUseBlock> {
128 match self {
129 ContentBlock::ServerToolUse(block) => Some(block),
130 _ => None,
131 }
132 }
133
134 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 pub fn as_tool_result(&self) -> Option<&ToolResultBlock> {
146 match self {
147 ContentBlock::ToolResult(block) => Some(block),
148 _ => None,
149 }
150 }
151
152 pub fn as_document(&self) -> Option<&DocumentBlock> {
155 match self {
156 ContentBlock::Document(block) => Some(block),
157 _ => None,
158 }
159 }
160
161 pub fn as_thinking(&self) -> Option<&ThinkingBlock> {
164 match self {
165 ContentBlock::Thinking(block) => Some(block),
166 _ => None,
167 }
168 }
169
170 pub fn as_redacted_thinking(&self) -> Option<&RedactedThinkingBlock> {
173 match self {
174 ContentBlock::RedactedThinking(block) => Some(block),
175 _ => None,
176 }
177 }
178}
179
180impl 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}