1use async_trait::async_trait;
4use serde::{Deserialize, Serialize, de::DeserializeOwned};
5use serde_json::{
6 Value,
7 value::{RawValue, to_raw_value},
8};
9
10pub use crate::responses::ToolDefinition;
11
12use crate::{ImageDetail, ResponseItem};
13
14pub const DEFAULT_TOOL_OUTPUT_TOKENS: usize = 10_000;
16
17#[derive(Clone, Debug, Deserialize, Serialize)]
19#[serde(untagged)]
20pub enum ToolOutputBody {
21 Text(String),
23 Content(Vec<ToolOutputContent>),
25}
26
27#[derive(Clone, Debug, Deserialize, Serialize)]
29#[serde(tag = "type", rename_all = "snake_case")]
30pub enum ToolOutputContent {
31 InputText {
33 text: String,
35 },
36 InputImage {
38 image_url: String,
40 detail: ImageDetail,
42 },
43 InputAudio {
45 audio_url: String,
47 },
48}
49
50pub struct ToolOutput {
56 pub output: ToolOutputBody,
58 pub success: bool,
60 pub metadata: Option<Box<RawValue>>,
62 code_mode_value: Option<Value>,
63 process_trace: Option<ToolProcessTrace>,
64}
65
66#[doc(hidden)]
68#[allow(missing_docs)]
69#[derive(Deserialize, Serialize)]
70pub struct ToolOutputWire {
71 pub output: ToolOutputBody,
72 pub success: bool,
73 pub code_mode_value: Option<Box<RawValue>>,
74 pub metadata: Option<Box<RawValue>>,
75 pub process_trace: Option<ToolProcessTraceWire>,
76}
77
78#[doc(hidden)]
80#[allow(missing_docs)]
81#[derive(Clone, Copy, Debug)]
82pub struct ToolProcessTrace {
83 pub exit_code: Option<i32>,
84 pub session_id: Option<i64>,
85 pub original_token_count: Option<usize>,
86 pub output_bytes: usize,
87 pub wall_time_seconds: f64,
88}
89
90#[doc(hidden)]
92#[allow(missing_docs)]
93#[derive(Deserialize, Serialize)]
94pub struct ToolProcessTraceWire {
95 pub exit_code: Option<i32>,
96 pub session_id: Option<i64>,
97 pub original_token_count: Option<usize>,
98 pub output_bytes: usize,
99 pub wall_time_seconds: f64,
100}
101
102pub type ToolError = Box<dyn std::error::Error + Send + Sync + 'static>;
104
105pub type ToolResult = std::result::Result<ToolOutput, ToolError>;
111
112impl ToolOutput {
113 #[must_use]
115 pub fn text(output: impl Into<String>) -> Self {
116 Self {
117 output: ToolOutputBody::Text(output.into()),
118 success: true,
119 metadata: None,
120 code_mode_value: None,
121 process_trace: None,
122 }
123 }
124
125 #[must_use]
127 pub fn error(error: impl Into<String>) -> Self {
128 Self {
129 output: ToolOutputBody::Text(error.into()),
130 success: false,
131 metadata: None,
132 code_mode_value: None,
133 process_trace: None,
134 }
135 }
136
137 #[must_use]
139 pub fn json(output: &impl Serialize) -> Self {
140 match serde_json::to_string(output) {
141 Ok(output) => Self::text(output),
142 Err(error) => Self::error(format!("failed to encode tool result: {error}")),
143 }
144 }
145
146 #[must_use]
151 pub fn from_json(output: Value, success: bool) -> Self {
152 match serde_json::to_string(&output) {
153 Ok(encoded) => Self {
154 output: ToolOutputBody::Text(encoded),
155 success,
156 metadata: None,
157 code_mode_value: Some(output),
158 process_trace: None,
159 },
160 Err(error) => Self::error(format!("failed to encode tool result: {error}")),
161 }
162 }
163
164 #[must_use]
166 pub const fn content(output: Vec<ToolOutputContent>) -> Self {
167 Self {
168 output: ToolOutputBody::Content(output),
169 success: true,
170 metadata: None,
171 code_mode_value: None,
172 process_trace: None,
173 }
174 }
175
176 #[must_use]
180 pub fn with_metadata(mut self, metadata: impl Serialize) -> Self {
181 match to_raw_value(&metadata) {
182 Ok(metadata) => self.metadata = Some(metadata),
183 Err(error) => {
184 self.output =
185 ToolOutputBody::Text(format!("failed to encode tool result metadata: {error}"));
186 self.success = false;
187 }
188 }
189 self
190 }
191
192 #[doc(hidden)]
194 #[must_use]
195 pub fn code_mode_value(&self) -> Value {
196 if let Some(value) = &self.code_mode_value {
197 return value.clone();
198 }
199 match &self.output {
200 ToolOutputBody::Text(text) => {
201 serde_json::from_str(text).unwrap_or_else(|_| Value::String(text.clone()))
202 }
203 ToolOutputBody::Content(content) => {
204 serde_json::to_value(content).unwrap_or(Value::Null)
205 }
206 }
207 }
208
209 #[doc(hidden)]
211 #[must_use]
212 pub fn with_code_mode_value(mut self, value: Value) -> Self {
213 self.code_mode_value = Some(value);
214 self
215 }
216
217 #[doc(hidden)]
219 #[must_use]
220 pub const fn with_process_trace(
221 mut self,
222 exit_code: Option<i32>,
223 session_id: Option<i64>,
224 original_token_count: Option<usize>,
225 output_bytes: usize,
226 wall_time_seconds: f64,
227 ) -> Self {
228 self.process_trace = Some(ToolProcessTrace {
229 exit_code,
230 session_id,
231 original_token_count,
232 output_bytes,
233 wall_time_seconds,
234 });
235 self
236 }
237
238 #[doc(hidden)]
240 #[must_use]
241 pub const fn process_trace(&self) -> Option<&ToolProcessTrace> {
242 self.process_trace.as_ref()
243 }
244
245 #[doc(hidden)]
251 pub fn into_wire(self) -> Result<ToolOutputWire, serde_json::Error> {
252 Ok(ToolOutputWire {
253 output: self.output,
254 success: self.success,
255 code_mode_value: self
256 .code_mode_value
257 .map(|value| to_raw_value(&value))
258 .transpose()?,
259 metadata: self.metadata,
260 process_trace: self.process_trace.map(Into::into),
261 })
262 }
263
264 #[doc(hidden)]
270 pub fn from_wire(wire: ToolOutputWire) -> Result<Self, serde_json::Error> {
271 Ok(Self {
272 output: wire.output,
273 success: wire.success,
274 metadata: wire.metadata,
275 code_mode_value: wire
276 .code_mode_value
277 .map(|value| serde_json::from_str(value.get()))
278 .transpose()?,
279 process_trace: wire.process_trace.map(Into::into),
280 })
281 }
282}
283
284impl From<ToolProcessTrace> for ToolProcessTraceWire {
285 fn from(trace: ToolProcessTrace) -> Self {
286 Self {
287 exit_code: trace.exit_code,
288 session_id: trace.session_id,
289 original_token_count: trace.original_token_count,
290 output_bytes: trace.output_bytes,
291 wall_time_seconds: trace.wall_time_seconds,
292 }
293 }
294}
295
296impl From<ToolProcessTraceWire> for ToolProcessTrace {
297 fn from(trace: ToolProcessTraceWire) -> Self {
298 Self {
299 exit_code: trace.exit_code,
300 session_id: trace.session_id,
301 original_token_count: trace.original_token_count,
302 output_bytes: trace.output_bytes,
303 wall_time_seconds: trace.wall_time_seconds,
304 }
305 }
306}
307
308#[derive(Clone, Copy)]
310pub struct ToolContext<'a> {
311 model: &'a str,
312 session_id: &'a str,
313 call_id: &'a str,
314 history: &'a [ResponseItem],
315 output_token_budget: usize,
316}
317
318impl<'a> ToolContext<'a> {
319 #[must_use]
321 pub const fn new(
322 model: &'a str,
323 session_id: &'a str,
324 call_id: &'a str,
325 history: &'a [ResponseItem],
326 output_token_budget: usize,
327 ) -> Self {
328 Self {
329 model,
330 session_id,
331 call_id,
332 history,
333 output_token_budget,
334 }
335 }
336
337 #[must_use]
339 pub const fn model(self) -> &'a str {
340 self.model
341 }
342
343 #[must_use]
345 pub const fn session_id(self) -> &'a str {
346 self.session_id
347 }
348
349 #[must_use]
351 pub const fn call_id(self) -> &'a str {
352 self.call_id
353 }
354
355 #[must_use]
357 pub const fn history(self) -> &'a [ResponseItem] {
358 self.history
359 }
360
361 #[must_use]
363 pub const fn output_token_budget(self) -> usize {
364 self.output_token_budget
365 }
366}
367
368pub enum ToolInput {
370 Function(Box<RawValue>),
372 Freeform(String),
374}
375
376impl ToolInput {
377 pub fn function_json(&self) -> Result<&RawValue, ToolInputError> {
383 match self {
384 Self::Function(input) => Ok(input),
385 Self::Freeform(_) => Err(ToolInputError::ExpectedFunction),
386 }
387 }
388
389 pub fn decode_json<T: DeserializeOwned>(&self) -> Result<T, ToolInputError> {
395 serde_json::from_str(self.function_json()?.get()).map_err(ToolInputError::Decode)
396 }
397
398 pub fn into_freeform(self) -> Result<String, ToolInputError> {
404 match self {
405 Self::Freeform(input) => Ok(input),
406 Self::Function(_) => Err(ToolInputError::ExpectedFreeform),
407 }
408 }
409}
410
411#[derive(Debug, thiserror::Error)]
413pub enum ToolInputError {
414 #[error("expected JSON function arguments")]
416 ExpectedFunction,
417 #[error("expected freeform tool input")]
419 ExpectedFreeform,
420 #[error("failed to parse function arguments: {0}")]
422 Decode(#[source] serde_json::Error),
423}
424
425#[async_trait]
464pub trait Tool: Send + Sync + 'static {
465 fn definition(&self) -> ToolDefinition;
467
468 fn supports_parallel_tool_calls(&self) -> bool {
473 false
474 }
475
476 async fn execute(&self, input: ToolInput, context: ToolContext<'_>) -> ToolResult;
478}