1use std::collections::BTreeMap;
2use std::path::PathBuf;
3
4use serde::{Deserialize, Serialize};
5use serde_json::{json, Value};
6
7use crate::types::{ToolDirective, ToolExecutionResult, ToolResultStatus};
8
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10#[serde(tag = "type", rename_all = "snake_case")]
11pub enum ToolOutput {
12 Text {
13 text: String,
14 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
15 metadata: BTreeMap<String, Value>,
16 },
17 Json {
18 data: Value,
19 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
20 metadata: BTreeMap<String, Value>,
21 },
22 Image {
23 url: Option<String>,
24 path: Option<PathBuf>,
25 mime_type: Option<String>,
26 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
27 metadata: BTreeMap<String, Value>,
28 },
29 File {
30 path: PathBuf,
31 mime_type: Option<String>,
32 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
33 metadata: BTreeMap<String, Value>,
34 },
35 Error {
36 message: String,
37 error_code: Option<String>,
38 retryable: bool,
39 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
40 metadata: BTreeMap<String, Value>,
41 },
42}
43
44impl ToolOutput {
45 pub fn text(text: impl Into<String>) -> Self {
46 Self::Text {
47 text: text.into(),
48 metadata: BTreeMap::new(),
49 }
50 }
51
52 pub fn json(data: Value) -> Self {
53 Self::Json {
54 data,
55 metadata: BTreeMap::new(),
56 }
57 }
58
59 pub fn error(message: impl Into<String>) -> Self {
60 Self::Error {
61 message: message.into(),
62 error_code: None,
63 retryable: false,
64 metadata: BTreeMap::new(),
65 }
66 }
67
68 pub fn with_code(mut self, code: impl Into<String>) -> Self {
69 if let Self::Error {
70 error_code: field, ..
71 } = &mut self
72 {
73 *field = Some(code.into());
74 }
75 self
76 }
77
78 pub fn retryable(mut self, retryable: bool) -> Self {
79 if let Self::Error {
80 retryable: field, ..
81 } = &mut self
82 {
83 *field = retryable;
84 }
85 self
86 }
87
88 pub fn with_metadata(mut self, key: impl Into<String>, value: Value) -> Self {
89 match &mut self {
90 Self::Text { metadata, .. }
91 | Self::Json { metadata, .. }
92 | Self::Image { metadata, .. }
93 | Self::File { metadata, .. }
94 | Self::Error { metadata, .. } => {
95 metadata.insert(key.into(), value);
96 }
97 }
98 self
99 }
100
101 pub fn to_result(&self, tool_call_id: impl Into<String>) -> ToolExecutionResult {
102 let tool_call_id = tool_call_id.into();
103 match self {
104 Self::Text { text, metadata } => {
105 let mut result = ToolExecutionResult::success(tool_call_id, text.clone());
106 result.metadata.extend(metadata.clone());
107 result
108 }
109 Self::Json { data, metadata } => {
110 let mut result = ToolExecutionResult::success(tool_call_id, data.to_string());
111 result
112 .metadata
113 .insert("output_type".to_string(), json!("json"));
114 result.metadata.extend(metadata.clone());
115 result
116 }
117 Self::Image {
118 url,
119 path,
120 mime_type,
121 metadata,
122 } => {
123 let mut result = ToolExecutionResult::success(
124 tool_call_id,
125 json!({
126 "url": url,
127 "path": path,
128 "mime_type": mime_type,
129 })
130 .to_string(),
131 );
132 result
133 .metadata
134 .insert("output_type".to_string(), json!("image"));
135 result.image_url = url.clone();
136 result.image_path = path.as_ref().map(|path| path.display().to_string());
137 result.metadata.extend(metadata.clone());
138 result
139 }
140 Self::File {
141 path,
142 mime_type,
143 metadata,
144 } => {
145 let mut result = ToolExecutionResult::success(
146 tool_call_id,
147 json!({
148 "path": path,
149 "mime_type": mime_type,
150 })
151 .to_string(),
152 );
153 result
154 .metadata
155 .insert("output_type".to_string(), json!("file"));
156 result.metadata.extend(metadata.clone());
157 result
158 }
159 Self::Error {
160 message,
161 error_code,
162 retryable,
163 metadata,
164 } => ToolExecutionResult {
165 tool_call_id,
166 content: json!({
167 "ok": false,
168 "error": message,
169 "error_code": error_code,
170 "retryable": retryable,
171 })
172 .to_string(),
173 status: ToolResultStatus::Error,
174 directive: ToolDirective::Continue,
175 error_code: error_code.clone(),
176 metadata: [
177 ("output_type".to_string(), json!("error")),
178 ("retryable".to_string(), json!(retryable)),
179 ]
180 .into_iter()
181 .chain(metadata.clone())
182 .collect(),
183 image_url: None,
184 image_path: None,
185 },
186 }
187 }
188}