claudius/types/
tool_result_block.rs

1use serde::{Deserialize, Serialize};
2
3use crate::types::{CacheControlEphemeral, Content};
4
5/// A block containing the result of a tool execution.
6///
7/// ToolResultBlock represents the output from executing a tool that was previously
8/// requested via a ToolUseBlock. It contains the tool's response, which can be
9/// either successful output or an error indication.
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
11#[serde(tag = "type")]
12#[serde(rename = "tool_result")]
13pub struct ToolResultBlock {
14    /// The ID of the tool use that this result is for.
15    ///
16    /// This must match the ID from the corresponding ToolUseBlock.
17    #[serde(rename = "tool_use_id")]
18    pub tool_use_id: String,
19
20    /// Create a cache control breakpoint at this content block.
21    ///
22    /// When set, this creates an ephemeral cache point that can be reused
23    /// in subsequent requests to avoid reprocessing the tool result.
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub cache_control: Option<CacheControlEphemeral>,
26
27    /// The content of the tool result, which can be either a string or an array of content items.
28    ///
29    /// This contains the actual output from the tool execution. It can be simple text
30    /// or structured content including images and formatted text.
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub content: Option<ToolResultBlockContent>,
33
34    /// Whether this tool result represents an error.
35    ///
36    /// When true, indicates that the tool execution failed and the content
37    /// contains error information rather than successful output.
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub is_error: Option<bool>,
40}
41
42/// The content of a tool result block, which can be either a string or an array of content items.
43#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
44#[serde(untagged)]
45pub enum ToolResultBlockContent {
46    /// A simple string content.
47    String(String),
48
49    /// An array of content items.
50    Array(Vec<Content>),
51}
52
53impl From<String> for ToolResultBlockContent {
54    fn from(value: String) -> Self {
55        ToolResultBlockContent::String(value)
56    }
57}
58
59impl From<&str> for ToolResultBlockContent {
60    fn from(value: &str) -> Self {
61        ToolResultBlockContent::String(value.to_string())
62    }
63}
64
65impl ToolResultBlock {
66    /// Create a new `ToolResultBlock` with the given tool use ID.
67    pub fn new(tool_use_id: String) -> Self {
68        Self {
69            tool_use_id,
70            cache_control: None,
71            content: None,
72            is_error: None,
73        }
74    }
75
76    /// Add a cache control to this tool result block.
77    pub fn with_cache_control(mut self, cache_control: CacheControlEphemeral) -> Self {
78        self.cache_control = Some(cache_control);
79        self
80    }
81
82    /// Add string content to this tool result block.
83    pub fn with_string_content(mut self, content: String) -> Self {
84        self.content = Some(ToolResultBlockContent::String(content));
85        self
86    }
87
88    /// Add array content to this tool result block.
89    pub fn with_array_content(mut self, content: Vec<Content>) -> Self {
90        self.content = Some(ToolResultBlockContent::Array(content));
91        self
92    }
93
94    /// Add a single text content item to this tool result block.
95    pub fn with_text_content(mut self, text: crate::types::TextBlock) -> Self {
96        let content = match self.content {
97            Some(ToolResultBlockContent::Array(mut items)) => {
98                items.push(Content::Text(text));
99                ToolResultBlockContent::Array(items)
100            }
101            Some(ToolResultBlockContent::String(s)) => ToolResultBlockContent::Array(vec![
102                Content::Text(crate::types::TextBlock::new(s)),
103                Content::Text(text),
104            ]),
105            None => ToolResultBlockContent::Array(vec![Content::Text(text)]),
106        };
107        self.content = Some(content);
108        self
109    }
110
111    /// Set this tool result block as an error.
112    pub fn with_error(mut self, is_error: bool) -> Self {
113        self.is_error = Some(is_error);
114        self
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121    use serde_json::{json, to_value};
122
123    #[test]
124    fn tool_result_block_with_string_content() {
125        let block = ToolResultBlock::new("tool_1".to_string())
126            .with_string_content("Result of tool execution".to_string());
127
128        let json = to_value(&block).unwrap();
129        assert_eq!(
130            json,
131            json!({
132                "tool_use_id": "tool_1",
133                "type": "tool_result",
134                "content": "Result of tool execution"
135            })
136        );
137    }
138
139    #[test]
140    fn tool_result_block_with_array_content() {
141        let text_param = crate::types::TextBlock::new("Sample text content".to_string());
142        let content = vec![Content::Text(text_param)];
143
144        let block = ToolResultBlock::new("tool_1".to_string()).with_array_content(content);
145
146        let json = to_value(&block).unwrap();
147        assert_eq!(
148            json,
149            json!({
150                "tool_use_id": "tool_1",
151                "type": "tool_result",
152                "content": [
153                    {
154                        "text": "Sample text content",
155                        "type": "text"
156                    }
157                ]
158            })
159        );
160    }
161
162    #[test]
163    fn tool_result_block_with_error() {
164        let block = ToolResultBlock::new("tool_1".to_string())
165            .with_string_content("Error executing tool".to_string())
166            .with_error(true);
167
168        let json = to_value(&block).unwrap();
169        assert_eq!(
170            json,
171            json!({
172                "tool_use_id": "tool_1",
173                "type": "tool_result",
174                "content": "Error executing tool",
175                "is_error": true
176            })
177        );
178    }
179
180    #[test]
181    fn tool_result_block_deserialization() {
182        let json = json!({
183            "tool_use_id": "tool_1",
184            "type": "tool_result",
185            "content": "Result of tool execution",
186            "is_error": false
187        });
188
189        let block: ToolResultBlock = serde_json::from_value(json).unwrap();
190        assert_eq!(block.tool_use_id, "tool_1");
191
192        match &block.content {
193            Some(ToolResultBlockContent::String(s)) => {
194                assert_eq!(s, "Result of tool execution");
195            }
196            _ => panic!("Expected String variant"),
197        }
198
199        assert_eq!(block.is_error, Some(false));
200    }
201
202    #[test]
203    fn tool_result_block_content_from_string() {
204        // Test From<String> trait
205        let content: ToolResultBlockContent = "Test content".to_string().into();
206        match content {
207            ToolResultBlockContent::String(s) => assert_eq!(s, "Test content"),
208            _ => panic!("Expected String variant"),
209        }
210
211        // Test From<&str> trait
212        let content: ToolResultBlockContent = "Another test".into();
213        match content {
214            ToolResultBlockContent::String(s) => assert_eq!(s, "Another test"),
215            _ => panic!("Expected String variant"),
216        }
217    }
218}