use crate::wasm_compat::BoxFuture;
use serde::{Serialize, de::DeserializeOwned};
use std::collections::HashMap;
use thiserror::Error;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ToolDefinition {
pub name: String,
pub description: String,
pub parameters: serde_json::Value,
}
pub trait Tool {
const NAME: &'static str;
type Error: std::error::Error + 'static;
type Args: DeserializeOwned;
type Output: Serialize;
fn definition(&self) -> ToolDefinition;
fn call(&self, args: Self::Args) -> impl std::future::Future<Output = Result<Self::Output, Self::Error>>;
}
#[derive(Debug, Error)]
pub enum ToolError {
#[error("Unknown tool: {0}")]
NotFound(String),
#[error("Failed to deserialise tool arguments: {0}")]
ArgsParse(#[from] serde_json::Error),
#[error("Tool execution failed: {0}")]
Execution(String),
}
trait ErasedTool {
fn definition(&self) -> ToolDefinition;
fn call<'a>(
&'a self,
args: serde_json::Value,
) -> BoxFuture<'a, Result<serde_json::Value, ToolError>>;
}
struct ToolWrapper<T>(T);
impl<T: Tool + 'static> ErasedTool for ToolWrapper<T> {
fn definition(&self) -> ToolDefinition {
self.0.definition()
}
fn call<'a>(
&'a self,
raw: serde_json::Value,
) -> BoxFuture<'a, Result<serde_json::Value, ToolError>> {
Box::pin(async move {
let args: T::Args =
serde_json::from_value(raw).map_err(ToolError::ArgsParse)?;
let output = self
.0
.call(args)
.await
.map_err(|e| ToolError::Execution(e.to_string()))?;
serde_json::to_value(output).map_err(ToolError::ArgsParse)
})
}
}
type BoxedTool = Box<dyn ErasedTool>;
pub struct ToolSet {
tools: HashMap<String, BoxedTool>,
}
impl ToolSet {
pub fn new() -> Self {
Self { tools: HashMap::new() }
}
pub fn add<T: Tool + 'static>(&mut self, tool: T) -> &mut Self {
self.tools.insert(T::NAME.to_owned(), Box::new(ToolWrapper(tool)));
self
}
pub fn definitions(&self) -> Vec<ToolDefinition> {
self.tools.values().map(|t| t.definition()).collect()
}
pub async fn call(
&self,
name: &str,
args: serde_json::Value,
) -> Result<serde_json::Value, ToolError> {
let tool = self.tools.get(name).ok_or_else(|| ToolError::NotFound(name.to_owned()))?;
tool.call(args).await
}
pub fn is_empty(&self) -> bool {
self.tools.is_empty()
}
}
impl Default for ToolSet {
fn default() -> Self {
Self::new()
}
}