nanny_runtime/tools/
mod.rs1pub mod http_get;
11
12pub use http_get::HttpGet;
13
14use nanny_core::tool::{Tool, ToolArgs, ToolCallError, ToolExecutor, ToolOutput};
15use std::collections::HashMap;
16
17pub struct ToolRegistry {
27 tools: HashMap<String, Box<dyn Tool>>,
32}
33
34impl ToolRegistry {
35 pub fn new() -> Self {
37 Self {
38 tools: HashMap::new(),
39 }
40 }
41
42 pub fn register(&mut self, tool: Box<dyn Tool>) {
47 self.tools.insert(tool.name().to_string(), tool);
48 }
49
50 pub fn registered_names(&self) -> Vec<&str> {
54 self.tools.keys().map(|s| s.as_str()).collect()
55 }
56}
57
58impl Default for ToolRegistry {
59 fn default() -> Self {
60 Self::new()
61 }
62}
63
64pub fn default_registry() -> ToolRegistry {
72 let mut registry = ToolRegistry::new();
73 registry.register(Box::new(HttpGet::new()));
74 registry
75}
76
77impl ToolExecutor for ToolRegistry {
78 fn call(&self, name: &str, args: &ToolArgs) -> Result<ToolOutput, ToolCallError> {
83 match self.tools.get(name) {
84 None => Err(ToolCallError::NotFound {
85 tool_name: name.to_string(),
86 }),
87 Some(tool) => tool
88 .execute(args)
89 .map_err(|source| ToolCallError::Execution {
90 tool_name: name.to_string(),
91 source,
92 }),
93 }
94 }
95}
96
97#[cfg(test)]
100mod tests {
101 use super::*;
102 use nanny_core::tool::{ToolError, ToolOutput};
103
104 struct EchoTool;
106 impl Tool for EchoTool {
107 fn name(&self) -> &str {
108 "echo"
109 }
110 fn execute(&self, args: &ToolArgs) -> Result<ToolOutput, ToolError> {
111 let message = args.get("message").cloned().unwrap_or_default();
112 Ok(ToolOutput { content: message })
113 }
114 }
115
116 struct FailingTool;
118 impl Tool for FailingTool {
119 fn name(&self) -> &str {
120 "failing"
121 }
122 fn execute(&self, _: &ToolArgs) -> Result<ToolOutput, ToolError> {
123 Err(ToolError::ExecutionFailed("always fails".to_string()))
124 }
125 }
126
127 #[test]
128 fn calls_registered_tool() {
129 let mut registry = ToolRegistry::new();
130 registry.register(Box::new(EchoTool));
131
132 let mut args = ToolArgs::new();
133 args.insert("message".to_string(), "hello".to_string());
134
135 let result = registry.call("echo", &args);
136 assert!(result.is_ok());
137 assert_eq!(result.unwrap().content, "hello");
138 }
139
140 #[test]
141 fn returns_not_found_for_unknown_tool() {
142 let registry = ToolRegistry::new();
143 let result = registry.call("unknown", &ToolArgs::new());
144
145 assert!(matches!(result, Err(ToolCallError::NotFound { .. })));
146 }
147
148 #[test]
149 fn returns_execution_error_on_tool_failure() {
150 let mut registry = ToolRegistry::new();
151 registry.register(Box::new(FailingTool));
152
153 let result = registry.call("failing", &ToolArgs::new());
154 assert!(matches!(result, Err(ToolCallError::Execution { .. })));
155 }
156
157 #[test]
158 fn registered_names_lists_all_tools() {
159 let mut registry = ToolRegistry::new();
160 registry.register(Box::new(EchoTool));
161 registry.register(Box::new(FailingTool));
162
163 let mut names = registry.registered_names();
164 names.sort();
165 assert_eq!(names, vec!["echo", "failing"]);
166 }
167}