claude_code_rs/types/
content.rs1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
6#[serde(tag = "type", rename_all = "snake_case")]
7pub enum ContentBlock {
8 Text {
9 text: String,
10 },
11 Thinking {
12 thinking: String,
13 #[serde(default)]
14 signature: Option<String>,
15 },
16 ToolUse {
17 id: String,
18 name: String,
19 input: Value,
20 },
21 ToolResult {
22 tool_use_id: String,
23 content: ToolResultContent,
24 #[serde(default)]
25 is_error: bool,
26 },
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
31#[serde(untagged)]
32pub enum ToolResultContent {
33 Text(String),
34 Blocks(Vec<ToolResultBlock>),
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
38#[serde(tag = "type", rename_all = "snake_case")]
39pub enum ToolResultBlock {
40 Text { text: String },
41 Image { source: ImageSource },
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
45pub struct ImageSource {
46 #[serde(rename = "type")]
47 pub source_type: String,
48 pub media_type: String,
49 pub data: String,
50}
51
52impl ContentBlock {
53 pub fn as_text(&self) -> Option<&str> {
55 match self {
56 ContentBlock::Text { text } => Some(text),
57 _ => None,
58 }
59 }
60
61 pub fn as_thinking(&self) -> Option<&str> {
63 match self {
64 ContentBlock::Thinking { thinking, .. } => Some(thinking),
65 _ => None,
66 }
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn deserialize_text_block() {
76 let json = r#"{"type": "text", "text": "hello"}"#;
77 let block: ContentBlock = serde_json::from_str(json).unwrap();
78 assert_eq!(block.as_text(), Some("hello"));
79 }
80
81 #[test]
82 fn deserialize_tool_use_block() {
83 let json = r#"{"type": "tool_use", "id": "tu_1", "name": "Bash", "input": {"command": "ls"}}"#;
84 let block: ContentBlock = serde_json::from_str(json).unwrap();
85 match block {
86 ContentBlock::ToolUse { id, name, input } => {
87 assert_eq!(id, "tu_1");
88 assert_eq!(name, "Bash");
89 assert_eq!(input["command"], "ls");
90 }
91 _ => panic!("expected ToolUse"),
92 }
93 }
94
95 #[test]
96 fn deserialize_tool_result_block() {
97 let json = r#"{"type": "tool_result", "tool_use_id": "tu_1", "content": "ok", "is_error": false}"#;
98 let block: ContentBlock = serde_json::from_str(json).unwrap();
99 match block {
100 ContentBlock::ToolResult {
101 tool_use_id,
102 content,
103 is_error,
104 } => {
105 assert_eq!(tool_use_id, "tu_1");
106 assert_eq!(content, ToolResultContent::Text("ok".into()));
107 assert!(!is_error);
108 }
109 _ => panic!("expected ToolResult"),
110 }
111 }
112
113 #[test]
114 fn roundtrip_content_block() {
115 let block = ContentBlock::Thinking {
116 thinking: "hmm".into(),
117 signature: Some("sig".into()),
118 };
119 let json = serde_json::to_string(&block).unwrap();
120 let back: ContentBlock = serde_json::from_str(&json).unwrap();
121 assert_eq!(block, back);
122 }
123}