use std::collections::HashMap;
use saorsa_ai::ToolDefinition;
use crate::error::Result;
#[async_trait::async_trait]
pub trait Tool: Send + Sync {
fn name(&self) -> &str;
fn description(&self) -> &str;
fn input_schema(&self) -> serde_json::Value;
async fn execute(&self, input: serde_json::Value) -> Result<String>;
fn to_definition(&self) -> ToolDefinition {
ToolDefinition::new(self.name(), self.description(), self.input_schema())
}
}
pub struct ToolRegistry {
tools: HashMap<String, Box<dyn Tool>>,
}
impl ToolRegistry {
pub fn new() -> Self {
Self {
tools: HashMap::new(),
}
}
pub fn register(&mut self, tool: Box<dyn Tool>) {
self.tools.insert(tool.name().to_string(), tool);
}
pub fn get(&self, name: &str) -> Option<&dyn Tool> {
self.tools.get(name).map(AsRef::as_ref)
}
pub fn definitions(&self) -> Vec<ToolDefinition> {
self.tools.values().map(|t| t.to_definition()).collect()
}
pub fn names(&self) -> Vec<&str> {
self.tools.keys().map(String::as_str).collect()
}
pub fn len(&self) -> usize {
self.tools.len()
}
pub fn is_empty(&self) -> bool {
self.tools.is_empty()
}
}
impl Default for ToolRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
struct EchoTool;
#[async_trait::async_trait]
impl Tool for EchoTool {
fn name(&self) -> &str {
"echo"
}
fn description(&self) -> &str {
"Echoes input back"
}
fn input_schema(&self) -> serde_json::Value {
serde_json::json!({
"type": "object",
"properties": {
"text": {"type": "string"}
},
"required": ["text"]
})
}
async fn execute(&self, input: serde_json::Value) -> Result<String> {
let text = input
.get("text")
.and_then(|v| v.as_str())
.unwrap_or("(empty)");
Ok(text.to_string())
}
}
#[test]
fn registry_register_and_get() {
let mut registry = ToolRegistry::new();
registry.register(Box::new(EchoTool));
assert_eq!(registry.len(), 1);
assert!(!registry.is_empty());
assert!(registry.get("echo").is_some());
assert!(registry.get("nonexistent").is_none());
}
#[test]
fn registry_definitions() {
let mut registry = ToolRegistry::new();
registry.register(Box::new(EchoTool));
let defs = registry.definitions();
assert_eq!(defs.len(), 1);
assert_eq!(defs[0].name, "echo");
}
#[test]
fn registry_names() {
let mut registry = ToolRegistry::new();
registry.register(Box::new(EchoTool));
let names = registry.names();
assert!(names.contains(&"echo"));
}
#[test]
fn tool_to_definition() {
let tool = EchoTool;
let def = tool.to_definition();
assert_eq!(def.name, "echo");
assert_eq!(def.description, "Echoes input back");
}
#[test]
fn registry_default() {
let registry = ToolRegistry::default();
assert!(registry.is_empty());
}
#[tokio::test]
async fn tool_execute() {
let tool = EchoTool;
let result = tool.execute(serde_json::json!({"text": "hello"})).await;
assert!(result.is_ok());
if let Ok(output) = result {
assert_eq!(output, "hello");
}
}
}