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.additional_params.is_none() => Some(&text.text),
109 ToolResultContent::Text(_)
110 | ToolResultContent::Image(_)
111 | ToolResultContent::Json { .. } => None,
112 }
113 }
114
115 pub fn as_json(&self) -> Option<&serde_json::Value> {
117 if self.content.len() != 1 {
118 return None;
119 }
120
121 match self.content.first()? {
122 ToolResultContent::Json { value } => Some(value),
123 ToolResultContent::Text(_) | ToolResultContent::Image(_) => None,
124 }
125 }
126
127 pub fn as_content(&self) -> &[ToolResultContent] {
129 &self.content
130 }
131
132 pub fn into_content(self) -> Vec<ToolResultContent> {
134 self.content
135 }
136
137 pub fn render(&self) -> String {
142 if let Some(text) = self.as_text() {
143 text.to_string()
144 } else if let Some(value) = self.as_json() {
145 value.to_string()
146 } else {
147 serde_json::to_string(&self.content)
148 .unwrap_or_else(|_| "<structured tool output>".to_string())
149 }
150 }
151}
152
153impl From<String> for ToolOutput {
154 fn from(text: String) -> Self {
155 Self::text(text)
156 }
157}
158
159impl From<&str> for ToolOutput {
160 fn from(text: &str) -> Self {
161 Self::text(text)
162 }
163}
164
165impl From<serde_json::Value> for ToolOutput {
166 fn from(value: serde_json::Value) -> Self {
167 Self::json(value)
168 }
169}
170
171impl From<ToolResultContent> for ToolOutput {
172 fn from(content: ToolResultContent) -> Self {
173 Self::one(content)
174 }
175}
176
177impl TryFrom<Vec<ToolResultContent>> for ToolOutput {
178 type Error = ToolExecutionError;
179
180 fn try_from(content: Vec<ToolResultContent>) -> Result<Self, Self::Error> {
181 Self::content(content)
182 }
183}
184
185pub trait IntoToolOutput {
194 fn into_tool_output(self) -> Result<ToolOutput, ToolExecutionError>;
196}
197
198#[cfg(test)]
199mod debug_tests;
200
201impl<T> IntoToolOutput for T
202where
203 T: Serialize + 'static,
204{
205 fn into_tool_output(self) -> Result<ToolOutput, ToolExecutionError> {
206 let value = &self as &dyn Any;
209 if let Some(output) = value.downcast_ref::<ToolOutput>() {
210 return Ok(output.clone());
211 }
212 if let Some(content) = value.downcast_ref::<ToolResultContent>() {
213 return Ok(ToolOutput::one(content.clone()));
214 }
215 if let Some(content) = value.downcast_ref::<Vec<ToolResultContent>>() {
216 return ToolOutput::content(content.clone());
219 }
220 let is_explicit_json = value.is::<serde_json::Value>();
221
222 serde_json::to_value(self)
223 .map(|value| match value {
224 serde_json::Value::String(text) if !is_explicit_json => ToolOutput::text(text),
225 value => ToolOutput::json(value),
226 })
227 .map_err(|error| {
228 ToolExecutionError::other(format!("failed to serialize tool output: {error}"))
229 .with_source(error)
230 })
231 }
232}
233
234#[cfg(test)]
235mod tests;