1use serde::{Deserialize, Serialize};
2
3use crate::types::{
4 CodeExecutionResultBlock, DocumentBlock, ImageBlock, ProgrammaticToolUseBlock,
5 RedactedThinkingBlock, ServerToolUseBlock, TextBlock, ThinkingBlock, ToolResultBlock,
6 ToolUseBlock, WebSearchToolResultBlock,
7};
8
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
14#[serde(tag = "type")]
15pub enum ContentBlock {
16 #[serde(rename = "text")]
18 Text(TextBlock),
19
20 #[serde(rename = "image")]
22 Image(ImageBlock),
23
24 #[serde(rename = "tool_use")]
26 ToolUse(ToolUseBlock),
27
28 #[serde(rename = "server_tool_use")]
30 ServerToolUse(ServerToolUseBlock),
31
32 #[serde(rename = "web_search_tool_result")]
34 WebSearchToolResult(WebSearchToolResultBlock),
35
36 #[serde(rename = "tool_result")]
38 ToolResult(ToolResultBlock),
39
40 #[serde(rename = "document")]
42 Document(DocumentBlock),
43
44 #[serde(rename = "thinking")]
46 Thinking(ThinkingBlock),
47
48 #[serde(rename = "redacted_thinking")]
50 RedactedThinking(RedactedThinkingBlock),
51
52 #[serde(rename = "code_execution_result")]
54 CodeExecutionResult(CodeExecutionResultBlock),
55
56 #[serde(rename = "programmatic_tool_use")]
58 ProgrammaticToolUse(ProgrammaticToolUseBlock),
59}
60
61impl ContentBlock {
62 pub fn is_text(&self) -> bool {
64 matches!(self, ContentBlock::Text(_))
65 }
66
67 pub fn is_image(&self) -> bool {
69 matches!(self, ContentBlock::Image(_))
70 }
71
72 pub fn is_tool_use(&self) -> bool {
74 matches!(self, ContentBlock::ToolUse(_))
75 }
76
77 pub fn is_server_tool_use(&self) -> bool {
79 matches!(self, ContentBlock::ServerToolUse(_))
80 }
81
82 pub fn is_web_search_tool_result(&self) -> bool {
84 matches!(self, ContentBlock::WebSearchToolResult(_))
85 }
86
87 pub fn is_tool_result(&self) -> bool {
89 matches!(self, ContentBlock::ToolResult(_))
90 }
91
92 pub fn is_document(&self) -> bool {
94 matches!(self, ContentBlock::Document(_))
95 }
96
97 pub fn is_thinking(&self) -> bool {
99 matches!(self, ContentBlock::Thinking(_))
100 }
101
102 pub fn is_redacted_thinking(&self) -> bool {
104 matches!(self, ContentBlock::RedactedThinking(_))
105 }
106
107 pub fn as_text(&self) -> Option<&TextBlock> {
110 match self {
111 ContentBlock::Text(block) => Some(block),
112 _ => None,
113 }
114 }
115
116 pub fn as_image(&self) -> Option<&ImageBlock> {
119 match self {
120 ContentBlock::Image(block) => Some(block),
121 _ => None,
122 }
123 }
124
125 pub fn as_tool_use(&self) -> Option<&ToolUseBlock> {
128 match self {
129 ContentBlock::ToolUse(block) => Some(block),
130 _ => None,
131 }
132 }
133
134 pub fn as_server_tool_use(&self) -> Option<&ServerToolUseBlock> {
137 match self {
138 ContentBlock::ServerToolUse(block) => Some(block),
139 _ => None,
140 }
141 }
142
143 pub fn as_web_search_tool_result(&self) -> Option<&WebSearchToolResultBlock> {
146 match self {
147 ContentBlock::WebSearchToolResult(block) => Some(block),
148 _ => None,
149 }
150 }
151
152 pub fn as_tool_result(&self) -> Option<&ToolResultBlock> {
155 match self {
156 ContentBlock::ToolResult(block) => Some(block),
157 _ => None,
158 }
159 }
160
161 pub fn as_document(&self) -> Option<&DocumentBlock> {
164 match self {
165 ContentBlock::Document(block) => Some(block),
166 _ => None,
167 }
168 }
169
170 pub fn as_thinking(&self) -> Option<&ThinkingBlock> {
173 match self {
174 ContentBlock::Thinking(block) => Some(block),
175 _ => None,
176 }
177 }
178
179 pub fn as_redacted_thinking(&self) -> Option<&RedactedThinkingBlock> {
182 match self {
183 ContentBlock::RedactedThinking(block) => Some(block),
184 _ => None,
185 }
186 }
187
188 pub fn is_code_execution_result(&self) -> bool {
190 matches!(self, ContentBlock::CodeExecutionResult(_))
191 }
192
193 pub fn as_code_execution_result(&self) -> Option<&CodeExecutionResultBlock> {
196 match self {
197 ContentBlock::CodeExecutionResult(block) => Some(block),
198 _ => None,
199 }
200 }
201
202 pub fn is_programmatic_tool_use(&self) -> bool {
204 matches!(self, ContentBlock::ProgrammaticToolUse(_))
205 }
206
207 pub fn as_programmatic_tool_use(&self) -> Option<&ProgrammaticToolUseBlock> {
210 match self {
211 ContentBlock::ProgrammaticToolUse(block) => Some(block),
212 _ => None,
213 }
214 }
215}
216
217impl From<TextBlock> for ContentBlock {
219 fn from(block: TextBlock) -> Self {
220 ContentBlock::Text(block)
221 }
222}
223
224impl From<ImageBlock> for ContentBlock {
225 fn from(block: ImageBlock) -> Self {
226 ContentBlock::Image(block)
227 }
228}
229
230impl From<ToolUseBlock> for ContentBlock {
231 fn from(block: ToolUseBlock) -> Self {
232 ContentBlock::ToolUse(block)
233 }
234}
235
236impl From<ServerToolUseBlock> for ContentBlock {
237 fn from(block: ServerToolUseBlock) -> Self {
238 ContentBlock::ServerToolUse(block)
239 }
240}
241
242impl From<WebSearchToolResultBlock> for ContentBlock {
243 fn from(block: WebSearchToolResultBlock) -> Self {
244 ContentBlock::WebSearchToolResult(block)
245 }
246}
247
248impl From<ToolResultBlock> for ContentBlock {
249 fn from(block: ToolResultBlock) -> Self {
250 ContentBlock::ToolResult(block)
251 }
252}
253
254impl From<DocumentBlock> for ContentBlock {
255 fn from(block: DocumentBlock) -> Self {
256 ContentBlock::Document(block)
257 }
258}
259
260impl From<ThinkingBlock> for ContentBlock {
261 fn from(block: ThinkingBlock) -> Self {
262 ContentBlock::Thinking(block)
263 }
264}
265
266impl From<RedactedThinkingBlock> for ContentBlock {
267 fn from(block: RedactedThinkingBlock) -> Self {
268 ContentBlock::RedactedThinking(block)
269 }
270}
271
272impl From<CodeExecutionResultBlock> for ContentBlock {
273 fn from(block: CodeExecutionResultBlock) -> Self {
274 ContentBlock::CodeExecutionResult(block)
275 }
276}
277
278impl From<ProgrammaticToolUseBlock> for ContentBlock {
279 fn from(block: ProgrammaticToolUseBlock) -> Self {
280 ContentBlock::ProgrammaticToolUse(block)
281 }
282}
283
284#[cfg(test)]
285mod tests {
286 use super::*;
287
288 #[test]
289 fn text_block_serialization() {
290 let text_block = TextBlock::new("This is some text content.");
291 let content_block = ContentBlock::from(text_block);
292
293 let json = serde_json::to_string(&content_block).unwrap();
294 let expected = r#"{"type":"text","text":"This is some text content."}"#;
295
296 assert_eq!(json, expected);
297 }
298
299 #[test]
300 fn tool_use_block_serialization() {
301 let input_json = serde_json::json!({
302 "query": "weather in San Francisco",
303 "limit": 5
304 });
305
306 let tool_block = ToolUseBlock::new("tool_123", "search", input_json);
307 let content_block = ContentBlock::from(tool_block);
308
309 let json = serde_json::to_string(&content_block).unwrap();
310 let actual: serde_json::Value = serde_json::from_str(&json).unwrap();
311 let expected: serde_json::Value = serde_json::json!({
312 "type": "tool_use",
313 "id": "tool_123",
314 "input": {"limit": 5, "query": "weather in San Francisco"},
315 "name": "search"
316 });
317
318 assert_eq!(actual, expected);
319 }
320
321 #[test]
322 fn server_tool_use_block_serialization() {
323 let server_block =
324 ServerToolUseBlock::new_web_search("tool_123", "weather in San Francisco");
325 let content_block = ContentBlock::from(server_block);
326
327 let json = serde_json::to_string(&content_block).unwrap();
328 let expected = r#"{"type":"server_tool_use","id":"tool_123","input":{"query":"weather in San Francisco"},"name":"web_search"}"#;
329
330 assert_eq!(json, expected);
331 }
332
333 #[test]
334 fn thinking_block_serialization() {
335 let thinking_block = ThinkingBlock::new(
336 "Let me think through this problem step by step...",
337 "abc123signature",
338 );
339 let content_block = ContentBlock::from(thinking_block);
340
341 let json = serde_json::to_string(&content_block).unwrap();
342 let expected = r#"{"type":"thinking","signature":"abc123signature","thinking":"Let me think through this problem step by step..."}"#;
343
344 assert_eq!(json, expected);
345 }
346
347 #[test]
348 fn redacted_thinking_block_serialization() {
349 let redacted_block = RedactedThinkingBlock::new("encoded-thinking-data-123");
350 let content_block = ContentBlock::from(redacted_block);
351
352 let json = serde_json::to_string(&content_block).unwrap();
353 let expected = r#"{"type":"redacted_thinking","data":"encoded-thinking-data-123"}"#;
354
355 assert_eq!(json, expected);
356 }
357
358 #[test]
359 fn deserialization() {
360 let json = r#"{"text":"This is some text content.","type":"text"}"#;
361 let content_block: ContentBlock = serde_json::from_str(json).unwrap();
362
363 assert!(content_block.is_text());
364 assert!(!content_block.is_tool_use());
365
366 if let ContentBlock::Text(block) = content_block {
367 assert_eq!(block.text, "This is some text content.");
368 } else {
369 panic!("Expected TextBlock");
370 }
371
372 let json = r#"{"id":"tool_123","input":{"query":"weather in San Francisco"},"name":"web_search","type":"server_tool_use"}"#;
373 let content_block: ContentBlock = serde_json::from_str(json).unwrap();
374
375 assert!(content_block.is_server_tool_use());
376 assert!(!content_block.is_text());
377
378 if let ContentBlock::ServerToolUse(block) = content_block {
379 assert_eq!(block.id, "tool_123");
380 assert_eq!(block.name, "web_search");
381 } else {
382 panic!("Expected ServerToolUseBlock");
383 }
384 }
385
386 #[test]
387 fn as_methods() {
388 let text_block = TextBlock::new("This is some text content.");
389 let content_block = ContentBlock::from(text_block);
390
391 assert!(content_block.as_text().is_some());
392 assert!(content_block.as_image().is_none());
393 assert!(content_block.as_tool_use().is_none());
394 assert!(content_block.as_server_tool_use().is_none());
395 assert!(content_block.as_web_search_tool_result().is_none());
396 assert!(content_block.as_tool_result().is_none());
397 assert!(content_block.as_document().is_none());
398 assert!(content_block.as_thinking().is_none());
399 assert!(content_block.as_redacted_thinking().is_none());
400
401 let text_ref = content_block.as_text().unwrap();
402 assert_eq!(text_ref.text, "This is some text content.");
403 }
404
405 #[test]
406 fn image_block_serialization() {
407 let image_source =
408 crate::types::UrlImageSource::new("https://example.com/image.jpg".to_string());
409 let image_block = ImageBlock::new_with_url(image_source);
410 let content_block = ContentBlock::from(image_block);
411
412 let json = serde_json::to_string(&content_block).unwrap();
413 let expected =
414 r#"{"type":"image","source":{"type":"url","url":"https://example.com/image.jpg"}}"#;
415
416 assert_eq!(json, expected);
417 }
418}