pub mod http_get;
pub use http_get::HttpGet;
use nanny_core::tool::{Tool, ToolArgs, ToolCallError, ToolExecutor, ToolOutput};
use std::collections::HashMap;
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 registered_names(&self) -> Vec<&str> {
self.tools.keys().map(|s| s.as_str()).collect()
}
}
impl Default for ToolRegistry {
fn default() -> Self {
Self::new()
}
}
pub fn default_registry() -> ToolRegistry {
let mut registry = ToolRegistry::new();
registry.register(Box::new(HttpGet::new()));
registry
}
impl ToolExecutor for ToolRegistry {
fn call(&self, name: &str, args: &ToolArgs) -> Result<ToolOutput, ToolCallError> {
match self.tools.get(name) {
None => Err(ToolCallError::NotFound {
tool_name: name.to_string(),
}),
Some(tool) => tool
.execute(args)
.map_err(|source| ToolCallError::Execution {
tool_name: name.to_string(),
source,
}),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use nanny_core::tool::{ToolError, ToolOutput};
struct EchoTool;
impl Tool for EchoTool {
fn name(&self) -> &str {
"echo"
}
fn execute(&self, args: &ToolArgs) -> Result<ToolOutput, ToolError> {
let message = args.get("message").cloned().unwrap_or_default();
Ok(ToolOutput { content: message })
}
}
struct FailingTool;
impl Tool for FailingTool {
fn name(&self) -> &str {
"failing"
}
fn execute(&self, _: &ToolArgs) -> Result<ToolOutput, ToolError> {
Err(ToolError::ExecutionFailed("always fails".to_string()))
}
}
#[test]
fn calls_registered_tool() {
let mut registry = ToolRegistry::new();
registry.register(Box::new(EchoTool));
let mut args = ToolArgs::new();
args.insert("message".to_string(), "hello".to_string());
let result = registry.call("echo", &args);
assert!(result.is_ok());
assert_eq!(result.unwrap().content, "hello");
}
#[test]
fn returns_not_found_for_unknown_tool() {
let registry = ToolRegistry::new();
let result = registry.call("unknown", &ToolArgs::new());
assert!(matches!(result, Err(ToolCallError::NotFound { .. })));
}
#[test]
fn returns_execution_error_on_tool_failure() {
let mut registry = ToolRegistry::new();
registry.register(Box::new(FailingTool));
let result = registry.call("failing", &ToolArgs::new());
assert!(matches!(result, Err(ToolCallError::Execution { .. })));
}
#[test]
fn registered_names_lists_all_tools() {
let mut registry = ToolRegistry::new();
registry.register(Box::new(EchoTool));
registry.register(Box::new(FailingTool));
let mut names = registry.registered_names();
names.sort();
assert_eq!(names, vec!["echo", "failing"]);
}
}