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