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 cost_overrides: HashMap<String, u64>,
36}
37
38impl ToolRegistry {
39 pub fn new() -> Self {
41 Self {
42 tools: HashMap::new(),
43 cost_overrides: HashMap::new(),
44 }
45 }
46
47 pub fn set_cost_override(&mut self, tool_name: &str, cost: u64) {
53 self.cost_overrides.insert(tool_name.to_string(), cost);
54 }
55
56 pub fn register(&mut self, tool: Box<dyn Tool>) {
61 self.tools.insert(tool.name().to_string(), tool);
62 }
63
64 pub fn registered_names(&self) -> Vec<&str> {
68 self.tools.keys().map(|s| s.as_str()).collect()
69 }
70}
71
72impl Default for ToolRegistry {
73 fn default() -> Self {
74 Self::new()
75 }
76}
77
78pub fn default_registry() -> ToolRegistry {
86 let mut registry = ToolRegistry::new();
87 registry.register(Box::new(HttpGet::new()));
88 registry
89}
90
91impl ToolExecutor for ToolRegistry {
92 fn call(&self, name: &str, args: &ToolArgs) -> Result<ToolOutput, ToolCallError> {
97 match self.tools.get(name) {
98 None => Err(ToolCallError::NotFound {
99 tool_name: name.to_string(),
100 }),
101 Some(tool) => tool.execute(args).map_err(|source| ToolCallError::Execution {
102 tool_name: name.to_string(),
103 source,
104 }),
105 }
106 }
107
108 fn declared_cost(&self, name: &str) -> Option<u64> {
114 if self.tools.contains_key(name) {
115 Some(self.cost_overrides.get(name).copied()
116 .unwrap_or_else(|| self.tools[name].declared_cost()))
117 } else {
118 None
119 }
120 }
121}
122
123#[cfg(test)]
126mod tests {
127 use super::*;
128 use nanny_core::tool::{ToolError, ToolOutput};
129
130 struct EchoTool;
132 impl Tool for EchoTool {
133 fn name(&self) -> &str { "echo" }
134 fn declared_cost(&self) -> u64 { 5 }
135 fn execute(&self, args: &ToolArgs) -> Result<ToolOutput, ToolError> {
136 let message = args.get("message").cloned().unwrap_or_default();
137 Ok(ToolOutput { content: message })
138 }
139 }
140
141 struct FailingTool;
143 impl Tool for FailingTool {
144 fn name(&self) -> &str { "failing" }
145 fn declared_cost(&self) -> u64 { 1 }
146 fn execute(&self, _: &ToolArgs) -> Result<ToolOutput, ToolError> {
147 Err(ToolError::ExecutionFailed("always fails".to_string()))
148 }
149 }
150
151 #[test]
152 fn calls_registered_tool() {
153 let mut registry = ToolRegistry::new();
154 registry.register(Box::new(EchoTool));
155
156 let mut args = ToolArgs::new();
157 args.insert("message".to_string(), "hello".to_string());
158
159 let result = registry.call("echo", &args);
160 assert!(result.is_ok());
161 assert_eq!(result.unwrap().content, "hello");
162 }
163
164 #[test]
165 fn returns_not_found_for_unknown_tool() {
166 let registry = ToolRegistry::new();
167 let result = registry.call("unknown", &ToolArgs::new());
168
169 assert!(matches!(result, Err(ToolCallError::NotFound { .. })));
170 }
171
172 #[test]
173 fn returns_execution_error_on_tool_failure() {
174 let mut registry = ToolRegistry::new();
175 registry.register(Box::new(FailingTool));
176
177 let result = registry.call("failing", &ToolArgs::new());
178 assert!(matches!(result, Err(ToolCallError::Execution { .. })));
179 }
180
181 #[test]
182 fn declared_cost_returns_correct_value() {
183 let mut registry = ToolRegistry::new();
184 registry.register(Box::new(EchoTool));
185
186 assert_eq!(registry.declared_cost("echo"), Some(5));
187 assert_eq!(registry.declared_cost("unknown"), None);
188 }
189
190 #[test]
191 fn cost_override_replaces_declared_cost() {
192 let mut registry = ToolRegistry::new();
193 registry.register(Box::new(EchoTool)); registry.set_cost_override("echo", 99);
195
196 assert_eq!(registry.declared_cost("echo"), Some(99));
197 }
198
199 #[test]
200 fn cost_override_does_not_affect_unregistered_tool() {
201 let mut registry = ToolRegistry::new();
202 registry.set_cost_override("ghost", 50); assert_eq!(registry.declared_cost("ghost"), None);
204 }
205
206 #[test]
207 fn registered_names_lists_all_tools() {
208 let mut registry = ToolRegistry::new();
209 registry.register(Box::new(EchoTool));
210 registry.register(Box::new(FailingTool));
211
212 let mut names = registry.registered_names();
213 names.sort();
214 assert_eq!(names, vec!["echo", "failing"]);
215 }
216}