use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct ToolCall {
pub id: String,
pub name: String,
pub args: serde_json::Value,
#[serde(default)]
pub provider_data: serde_json::Value,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ToolResult {
pub id: String,
pub content: String,
pub is_error: bool,
#[serde(default)]
pub code: Option<i32>,
}
impl ToolResult {
pub fn success(id: impl Into<String>, content: impl Into<String>) -> Self {
Self {
id: id.into(),
content: content.into(),
is_error: false,
code: None,
}
}
pub fn failure(id: impl Into<String>, content: impl Into<String>) -> Self {
Self {
id: id.into(),
content: content.into(),
is_error: true,
code: None,
}
}
pub fn with_code(mut self, code: i32) -> Self {
self.code = Some(code);
self
}
}
#[derive(Debug, Clone)]
pub struct ToolContext {
pub cwd: PathBuf,
pub run_id: String,
pub cancelled: Arc<AtomicBool>,
}
impl Default for ToolContext {
fn default() -> Self {
Self {
cwd: PathBuf::from("."),
run_id: String::new(),
cancelled: Arc::new(AtomicBool::new(false)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum ReplaySafety {
#[default]
Unsafe,
Safe,
}
#[async_trait]
pub trait ToolResolver: Send + Sync {
fn specs(&self) -> Vec<crate::request::ToolSpec>;
fn replay_safety(&self, _name: &str) -> ReplaySafety {
ReplaySafety::Unsafe
}
async fn resolve(&self, call: ToolCall, ctx: &ToolContext) -> ToolResult;
async fn resolve_streaming(
&self,
call: ToolCall,
ctx: &ToolContext,
progress: &mut (dyn for<'a> FnMut(&'a str) + Send),
) -> ToolResult {
let _ = progress;
self.resolve(call, ctx).await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_result_without_a_code_field_loads_from_an_old_history() {
let raw = r#"{"id":"c1","content":"ok","is_error":false}"#;
let result: ToolResult = serde_json::from_str(raw).unwrap();
assert_eq!(result.code, None);
assert_eq!(result.with_code(3).code, Some(3));
}
}