turboclaude_protocol/
content.rs1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
10#[serde(tag = "type", rename_all = "snake_case")]
11pub enum ContentBlock {
12 #[serde(rename = "text")]
14 Text {
15 text: String,
17 },
18
19 #[serde(rename = "image")]
21 Image {
22 #[serde(skip_serializing_if = "Option::is_none")]
24 source: Option<ImageSource>,
25 },
26
27 #[serde(rename = "tool_use")]
29 ToolUse {
30 id: String,
32 name: String,
34 #[serde(default)]
36 input: serde_json::Value,
37 },
38
39 #[serde(rename = "tool_result")]
41 ToolResult {
42 tool_use_id: String,
44 #[serde(skip_serializing_if = "Option::is_none")]
46 content: Option<String>,
47 #[serde(skip_serializing_if = "Option::is_none")]
49 is_error: Option<bool>,
50 },
51
52 #[serde(rename = "thinking")]
54 Thinking {
55 thinking: String,
57 },
58
59 #[serde(rename = "document")]
61 Document {
62 source: DocumentSource,
64 #[serde(skip_serializing_if = "Option::is_none")]
66 title: Option<String>,
67 },
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
72#[serde(tag = "type", rename_all = "snake_case")]
73pub enum ImageSource {
74 #[serde(rename = "base64")]
76 Base64 {
77 media_type: String,
79 data: String,
81 },
82
83 #[serde(rename = "url")]
85 Url {
86 url: String,
88 },
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
93#[serde(tag = "type", rename_all = "snake_case")]
94pub enum DocumentSource {
95 #[serde(rename = "pdf")]
97 Pdf {
98 data: String,
100 },
101
102 #[serde(rename = "text")]
104 Text {
105 text: String,
107 },
108
109 #[serde(rename = "url")]
111 Url {
112 url: String,
114 },
115}
116
117impl ContentBlock {
118 pub fn text(text: impl Into<String>) -> Self {
120 Self::Text { text: text.into() }
121 }
122
123 pub fn tool_use(
125 id: impl Into<String>,
126 name: impl Into<String>,
127 input: serde_json::Value,
128 ) -> Self {
129 Self::ToolUse {
130 id: id.into(),
131 name: name.into(),
132 input,
133 }
134 }
135
136 pub fn tool_result(tool_use_id: impl Into<String>, content: impl Into<String>) -> Self {
138 Self::ToolResult {
139 tool_use_id: tool_use_id.into(),
140 content: Some(content.into()),
141 is_error: None,
142 }
143 }
144
145 pub fn tool_error(tool_use_id: impl Into<String>, error: impl Into<String>) -> Self {
147 Self::ToolResult {
148 tool_use_id: tool_use_id.into(),
149 content: Some(error.into()),
150 is_error: Some(true),
151 }
152 }
153
154 pub fn thinking(thinking: impl Into<String>) -> Self {
156 Self::Thinking {
157 thinking: thinking.into(),
158 }
159 }
160
161 pub fn type_name(&self) -> &'static str {
163 match self {
164 Self::Text { .. } => "text",
165 Self::Image { .. } => "image",
166 Self::ToolUse { .. } => "tool_use",
167 Self::ToolResult { .. } => "tool_result",
168 Self::Thinking { .. } => "thinking",
169 Self::Document { .. } => "document",
170 }
171 }
172
173 pub fn is_text(&self) -> bool {
175 matches!(self, Self::Text { .. })
176 }
177
178 pub fn is_tool_use(&self) -> bool {
180 matches!(self, Self::ToolUse { .. })
181 }
182
183 pub fn is_tool_result(&self) -> bool {
185 matches!(self, Self::ToolResult { .. })
186 }
187
188 pub fn as_text(&self) -> Option<&str> {
190 match self {
191 Self::Text { text } => Some(text),
192 _ => None,
193 }
194 }
195
196 pub fn as_tool_use(&self) -> Option<(&str, &str, &serde_json::Value)> {
198 match self {
199 Self::ToolUse { id, name, input } => Some((id, name, input)),
200 _ => None,
201 }
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208
209 #[test]
210 fn test_text_content_serialization() {
211 let content = ContentBlock::text("Hello, world!");
212 let json = serde_json::to_string(&content).unwrap();
213 let deserialized: ContentBlock = serde_json::from_str(&json).unwrap();
214 assert_eq!(content, deserialized);
215 }
216
217 #[test]
218 fn test_tool_use_content() {
219 let content =
220 ContentBlock::tool_use("id_123", "bash", serde_json::json!({ "command": "ls" }));
221 assert!(content.is_tool_use());
222 assert_eq!(content.type_name(), "tool_use");
223 }
224
225 #[test]
226 fn test_content_type_checks() {
227 let text = ContentBlock::text("test");
228 assert!(text.is_text());
229 assert!(!text.is_tool_use());
230 }
231}