use crate::errors::ToolError;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ToolParameter {
#[serde(rename = "type")]
pub param_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct ToolParameters {
pub properties: HashMap<String, ToolParameter>,
pub required: Vec<String>,
}
pub trait Tool: Send + Sync {
fn name(&self) -> &str;
fn description(&self) -> &str;
fn parameters(&self) -> ToolParameters;
fn call(&self, args: &str) -> impl Future<Output = Result<String, ToolError>> + Send;
}
pub trait DynTool: Send + Sync {
fn name(&self) -> &str;
fn description(&self) -> &str;
fn parameters(&self) -> ToolParameters;
fn call_dyn<'a>(
&'a self,
args: &'a str,
) -> Pin<Box<dyn Future<Output = Result<String, ToolError>> + Send + 'a>>;
}
impl<T: Tool> DynTool for T {
fn name(&self) -> &str {
Tool::name(self)
}
fn description(&self) -> &str {
Tool::description(self)
}
fn parameters(&self) -> ToolParameters {
Tool::parameters(self)
}
fn call_dyn<'a>(
&'a self,
args: &'a str,
) -> Pin<Box<dyn Future<Output = Result<String, ToolError>> + Send + 'a>> {
Box::pin(Tool::call(self, args))
}
}
pub struct ToolRegistry {
tools: HashMap<String, Box<dyn DynTool>>,
}
impl Default for ToolRegistry {
fn default() -> Self {
Self::new()
}
}
impl ToolRegistry {
#[must_use]
pub fn new() -> Self {
Self {
tools: HashMap::new(),
}
}
pub fn register<T: Tool + 'static>(&mut self, tool: T) {
self.tools.insert(tool.name().to_string(), Box::new(tool));
}
#[must_use]
pub fn get(&self, name: &str) -> Option<&dyn DynTool> {
self.tools.get(name).map(std::convert::AsRef::as_ref)
}
#[must_use]
pub fn list(&self) -> Vec<&dyn DynTool> {
self.tools
.values()
.map(std::convert::AsRef::as_ref)
.collect()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ToolCallInfo {
pub id: String,
pub name: String,
pub arguments: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ToolCall {
pub function: ToolCallInfo,
#[serde(rename = "type")]
pub tool_type: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ToolResult {
pub tool_call_id: String,
pub content: String,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::errors::ToolError;
struct EchoTool;
impl Tool for EchoTool {
fn name(&self) -> &'static str {
"echo"
}
fn description(&self) -> &'static str {
"Echoes the input."
}
fn parameters(&self) -> ToolParameters {
let mut properties = HashMap::new();
properties.insert(
"input".to_string(),
ToolParameter {
param_type: "string".to_string(),
description: Some("The text to echo".to_string()),
},
);
ToolParameters {
properties,
required: vec!["input".to_string()],
}
}
fn call(&self, args: &str) -> impl Future<Output = Result<String, ToolError>> + Send {
let args_owned = args.to_string();
async move { Ok(args_owned) }
}
}
#[test]
fn test_tool_registry() {
let mut registry = ToolRegistry::new();
registry.register(EchoTool);
assert!(registry.get("echo").is_some());
assert!(registry.get("missing").is_none());
assert_eq!(registry.list().len(), 1);
}
#[tokio::test]
async fn test_tool_execution() {
let tool = EchoTool;
let dyn_tool: &dyn DynTool = &tool;
let result = dyn_tool.call_dyn("hello world").await.unwrap();
assert_eq!(result, "hello world");
}
}