1#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
3#[serde(rename_all = "snake_case")]
4pub enum TimeoutBehavior {
5 ErrorAsResult,
7 FailTurn,
9}
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
13#[serde(rename_all = "snake_case")]
14pub enum CancelHint {
15 Ignore,
17 CancelExternalWork,
19}
20
21#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
28pub struct PendingCompletion {
29 #[serde(default, skip_serializing_if = "Option::is_none")]
32 pub deadline: Option<std::time::Duration>,
33 pub on_timeout: TimeoutBehavior,
35 pub on_cancel: CancelHint,
37}
38
39impl Default for PendingCompletion {
40 fn default() -> Self {
41 Self {
42 deadline: None,
43 on_timeout: TimeoutBehavior::ErrorAsResult,
44 on_cancel: CancelHint::CancelExternalWork,
45 }
46 }
47}
48
49impl PendingCompletion {
50 pub fn new() -> Self {
51 Self::default()
52 }
53
54 pub fn with_deadline(mut self, deadline: std::time::Duration) -> Self {
55 self.deadline = Some(deadline);
56 self
57 }
58
59 pub fn fail_turn_on_timeout(mut self) -> Self {
60 self.on_timeout = TimeoutBehavior::FailTurn;
61 self
62 }
63}
64
65#[derive(Clone, Debug, PartialEq)]
98pub enum ToolResult {
99 Done(Box<crate::ToolCallOutput>),
101 Pending(PendingCompletion),
106}
107
108impl ToolResult {
109 pub fn from_output(output: crate::ToolCallOutput) -> Self {
110 Self::Done(Box::new(output))
111 }
112
113 pub fn pending(pending: PendingCompletion) -> Self {
114 Self::Pending(pending)
115 }
116
117 pub fn ok(result: serde_json::Value) -> Self {
118 Self::from_output(crate::ToolCallOutput::success(result))
119 }
120
121 pub fn err(result: serde_json::Value) -> Self {
122 let message = result
123 .as_str()
124 .map(ToOwned::to_owned)
125 .unwrap_or_else(|| result.to_string());
126 Self::from_output(crate::ToolCallOutput::failure(crate::ToolFailure {
127 class: crate::ToolFailureClass::Execution,
128 code: "tool_error".to_string(),
129 message,
130 source: crate::ToolFailureSource::Tool,
131 retry: crate::ToolRetryDisposition::Never,
132 raw: Some(crate::ToolValue::from(result)),
133 }))
134 }
135
136 pub fn err_fmt(msg: impl std::fmt::Display) -> Self {
137 Self::err(serde_json::json!(msg.to_string()))
138 }
139
140 pub fn failure(failure: crate::ToolFailure) -> Self {
141 Self::from_output(crate::ToolCallOutput::failure(failure))
142 }
143
144 pub fn retryable_failure(
145 class: crate::ToolFailureClass,
146 code: impl Into<String>,
147 message: impl Into<String>,
148 after_ms: Option<u64>,
149 ) -> Self {
150 Self::failure(crate::ToolFailure::safe_retry(
151 class, code, message, after_ms,
152 ))
153 }
154
155 pub fn cancelled(message: impl Into<String>) -> Self {
156 Self::from_output(crate::ToolCallOutput::cancelled(
157 crate::ToolCancellation::runtime(message),
158 ))
159 }
160
161 pub fn cancelled_with_raw(message: impl Into<String>, raw: serde_json::Value) -> Self {
162 let mut cancellation = crate::ToolCancellation::runtime(message);
163 cancellation.raw = Some(crate::ToolValue::from(raw));
164 Self::from_output(crate::ToolCallOutput::cancelled(cancellation))
165 }
166
167 pub fn with_control(mut self, control: crate::ToolControl) -> Self {
168 if let Self::Done(output) = &mut self {
169 output.as_mut().control = Some(control);
170 }
171 self
172 }
173
174 pub fn is_success(&self) -> bool {
175 matches!(self, Self::Done(output) if output.is_success())
176 }
177
178 pub fn is_pending(&self) -> bool {
179 matches!(self, Self::Pending(_))
180 }
181
182 pub fn value_for_projection(&self) -> serde_json::Value {
183 match &self
184 .as_done_output()
185 .expect("pending tool result has no projection value")
186 .outcome
187 {
188 crate::ToolCallOutcome::Success(value) => value.to_json_value(),
189 crate::ToolCallOutcome::Failure(failure) => failure
190 .raw
191 .as_ref()
192 .map(crate::ToolValue::to_json_value)
193 .unwrap_or_else(|| failure.to_json_value()),
194 crate::ToolCallOutcome::Cancelled(cancellation) => cancellation
195 .raw
196 .as_ref()
197 .map(crate::ToolValue::to_json_value)
198 .unwrap_or_else(|| cancellation.to_json_value()),
199 }
200 }
201
202 pub fn as_done_output(&self) -> Option<&crate::ToolCallOutput> {
203 match self {
204 Self::Done(output) => Some(output.as_ref()),
205 Self::Pending(_) => None,
206 }
207 }
208
209 pub fn as_output(&self) -> &crate::ToolCallOutput {
210 self.as_done_output()
211 .expect("pending tool result cannot be viewed as completed output")
212 }
213
214 pub fn into_done_output(self) -> Result<crate::ToolCallOutput, PendingCompletion> {
215 match self {
216 Self::Done(output) => Ok(*output),
217 Self::Pending(pending) => Err(pending),
218 }
219 }
220}
221
222impl<T, E> From<Result<T, E>> for ToolResult
223where
224 T: serde::Serialize,
225 E: std::fmt::Display,
226{
227 fn from(result: Result<T, E>) -> Self {
228 match result {
229 Ok(value) => match serde_json::to_value(value) {
230 Ok(value) => Self::ok(value),
231 Err(err) => Self::err_fmt(format_args!("Failed to serialize tool result: {err}")),
232 },
233 Err(err) => Self::err_fmt(err),
234 }
235 }
236}
237
238pub(crate) fn tool_output_from_completion_resolution(
239 resolution: crate::Resolution,
240) -> crate::ToolCallOutput {
241 match resolution {
242 crate::Resolution::Ok(value) => crate::ToolCallOutput::success(value),
243 crate::Resolution::Err(err) => {
244 let mut failure =
245 crate::ToolFailure::tool(crate::ToolFailureClass::Execution, err.code, err.message);
246 failure.raw = err.raw.map(crate::ToolValue::from);
247 crate::ToolCallOutput::failure(failure)
248 }
249 crate::Resolution::Timeout => crate::ToolCallOutput::failure(crate::ToolFailure::runtime(
250 crate::ToolFailureClass::Timeout,
251 "tool_completion_timeout",
252 "pending tool completion timed out",
253 )),
254 crate::Resolution::Cancelled => crate::ToolCallOutput::cancelled(
255 crate::ToolCancellation::runtime("pending tool completion cancelled"),
256 ),
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use serde::ser::{Error as _, Serializer};
263
264 use super::*;
265
266 #[test]
267 fn tool_result_from_result_serializes_success_values() {
268 let result: ToolResult = Result::<_, std::io::Error>::Ok(vec!["alpha", "beta"]).into();
269 assert!(result.is_success());
270 assert_eq!(
271 result.value_for_projection(),
272 serde_json::json!(["alpha", "beta"])
273 );
274 }
275
276 #[test]
277 fn tool_result_from_result_formats_errors() {
278 let result: ToolResult =
279 Result::<serde_json::Value, _>::Err(std::io::Error::other("nope")).into();
280 assert!(!result.is_success());
281 assert_eq!(result.value_for_projection(), serde_json::json!("nope"));
282 assert_eq!(
283 result.as_output().value_for_projection()["message"],
284 serde_json::json!("nope")
285 );
286 }
287
288 #[test]
289 fn tool_result_from_result_reports_serialize_failures() {
290 struct BrokenValue;
291
292 impl serde::Serialize for BrokenValue {
293 fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
294 where
295 S: Serializer,
296 {
297 Err(S::Error::custom("boom"))
298 }
299 }
300
301 let result: ToolResult = Result::<BrokenValue, std::io::Error>::Ok(BrokenValue).into();
302 assert!(!result.is_success());
303 assert_eq!(
304 result.value_for_projection(),
305 serde_json::json!("Failed to serialize tool result: boom")
306 );
307 }
308
309 #[test]
310 fn pending_result_is_not_completed_output() {
311 let result = ToolResult::pending(PendingCompletion::new());
312 assert!(result.is_pending());
313 assert!(result.as_done_output().is_none());
314 assert!(result.into_done_output().is_err());
315 }
316}