1use std::{any::Any, fmt};
11
12use serde::Serialize;
13
14use crate::{message::ToolResultContent, tool::ToolExecutionError};
15
16#[derive(Clone, PartialEq)]
26pub struct ToolOutput {
27 content: Vec<ToolResultContent>,
28}
29
30impl Serialize for ToolOutput {
34 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
35 self.content.serialize(serializer)
36 }
37}
38
39impl<'de> serde::Deserialize<'de> for ToolOutput {
40 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
41 let content = Vec::<ToolResultContent>::deserialize(deserializer)?;
42 Self::content(content).map_err(serde::de::Error::custom)
43 }
44}
45
46impl fmt::Debug for ToolOutput {
47 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
48 let content_kinds = self
49 .content
50 .iter()
51 .map(|content| match content {
52 ToolResultContent::Text(_) => "text",
53 ToolResultContent::Image(_) => "image",
54 ToolResultContent::Json { .. } => "json",
55 })
56 .collect::<Vec<_>>();
57 formatter
58 .debug_struct("ToolOutput")
59 .field("content_count", &self.content.len())
60 .field("content_kinds", &content_kinds)
61 .finish()
62 }
63}
64
65impl ToolOutput {
66 pub fn text(text: impl Into<String>) -> Self {
68 Self::one(ToolResultContent::text(text))
69 }
70
71 pub fn json(value: serde_json::Value) -> Self {
76 Self::one(ToolResultContent::json(value))
77 }
78
79 pub fn content(content: Vec<ToolResultContent>) -> Result<Self, ToolExecutionError> {
82 if content.is_empty() {
83 return Err(ToolExecutionError::other(
84 "tool output has no content blocks; return at least one block — \
85 an empty text block is valid",
86 ));
87 }
88 Ok(Self { content })
89 }
90
91 pub fn one(content: ToolResultContent) -> Self {
93 Self {
94 content: vec![content],
95 }
96 }
97
98 pub fn as_text(&self) -> Option<&str> {
100 if self.content.len() != 1 {
101 return None;
102 }
103
104 match self.content.first()? {
105 ToolResultContent::Text(text) if text.native.is_none() => Some(&text.text),
106 ToolResultContent::Text(_)
107 | ToolResultContent::Image(_)
108 | ToolResultContent::Json { .. } => None,
109 }
110 }
111
112 pub fn as_json(&self) -> Option<&serde_json::Value> {
114 if self.content.len() != 1 {
115 return None;
116 }
117
118 match self.content.first()? {
119 ToolResultContent::Json { value } => Some(value),
120 ToolResultContent::Text(_) | ToolResultContent::Image(_) => None,
121 }
122 }
123
124 pub fn as_content(&self) -> &[ToolResultContent] {
126 &self.content
127 }
128
129 pub fn into_content(self) -> Vec<ToolResultContent> {
131 self.content
132 }
133
134 pub fn render(&self) -> String {
139 if let Some(text) = self.as_text() {
140 text.to_string()
141 } else if let Some(value) = self.as_json() {
142 value.to_string()
143 } else {
144 serde_json::to_string(&self.content)
145 .unwrap_or_else(|_| "<structured tool output>".to_string())
146 }
147 }
148}
149
150impl From<String> for ToolOutput {
151 fn from(text: String) -> Self {
152 Self::text(text)
153 }
154}
155
156impl From<&str> for ToolOutput {
157 fn from(text: &str) -> Self {
158 Self::text(text)
159 }
160}
161
162impl From<serde_json::Value> for ToolOutput {
163 fn from(value: serde_json::Value) -> Self {
164 Self::json(value)
165 }
166}
167
168impl From<ToolResultContent> for ToolOutput {
169 fn from(content: ToolResultContent) -> Self {
170 Self::one(content)
171 }
172}
173
174impl TryFrom<Vec<ToolResultContent>> for ToolOutput {
175 type Error = ToolExecutionError;
176
177 fn try_from(content: Vec<ToolResultContent>) -> Result<Self, Self::Error> {
178 Self::content(content)
179 }
180}
181
182pub trait IntoToolOutput {
191 fn into_tool_output(self) -> Result<ToolOutput, ToolExecutionError>;
193}
194
195#[cfg(test)]
196mod debug_tests;
197
198impl<T> IntoToolOutput for T
199where
200 T: Serialize + 'static,
201{
202 fn into_tool_output(self) -> Result<ToolOutput, ToolExecutionError> {
203 let value = &self as &dyn Any;
206 if let Some(output) = value.downcast_ref::<ToolOutput>() {
207 return Ok(output.clone());
208 }
209 if let Some(content) = value.downcast_ref::<ToolResultContent>() {
210 return Ok(ToolOutput::one(content.clone()));
211 }
212 if let Some(content) = value.downcast_ref::<Vec<ToolResultContent>>() {
213 return ToolOutput::content(content.clone());
216 }
217 let is_explicit_json = value.is::<serde_json::Value>();
218
219 serde_json::to_value(self)
220 .map(|value| match value {
221 serde_json::Value::String(text) if !is_explicit_json => ToolOutput::text(text),
222 value => ToolOutput::json(value),
223 })
224 .map_err(|error| {
225 ToolExecutionError::other(format!("failed to serialize tool output: {error}"))
226 .with_source(error)
227 })
228 }
229}
230
231#[cfg(test)]
232mod tests;