coro_core/tools/
registry.rs

1//! Tool registry for managing available tools
2
3use crate::tools::{Tool, ToolExecutor};
4use std::collections::HashMap;
5
6/// Registry for managing tool creation and registration
7pub struct ToolRegistry {
8    factories: HashMap<String, Box<dyn ToolFactory>>,
9}
10
11/// Factory trait for creating tools
12pub trait ToolFactory: Send + Sync {
13    /// Create a new instance of the tool
14    fn create(&self) -> Box<dyn Tool>;
15
16    /// Get the name of the tool this factory creates
17    fn tool_name(&self) -> &str;
18
19    /// Get the description of the tool this factory creates
20    fn tool_description(&self) -> &str;
21}
22
23impl ToolRegistry {
24    /// Create a new tool registry
25    pub fn new() -> Self {
26        Self {
27            factories: HashMap::new(),
28        }
29    }
30
31    /// Register a tool factory
32    pub fn register_factory(&mut self, factory: Box<dyn ToolFactory>) {
33        self.factories
34            .insert(factory.tool_name().to_string(), factory);
35    }
36
37    /// Create a tool by name
38    pub fn create_tool(&self, name: &str) -> Option<Box<dyn Tool>> {
39        self.factories.get(name).map(|factory| factory.create())
40    }
41
42    /// List all available tool names
43    pub fn list_tools(&self) -> Vec<&str> {
44        self.factories.keys().map(|s| s.as_str()).collect()
45    }
46
47    /// Get tool information
48    pub fn get_tool_info(&self, name: &str) -> Option<(&str, &str)> {
49        self.factories
50            .get(name)
51            .map(|factory| (factory.tool_name(), factory.tool_description()))
52    }
53
54    /// Create a tool executor with the specified tools
55    pub fn create_executor(&self, tool_names: &[String]) -> ToolExecutor {
56        let mut executor = ToolExecutor::new();
57
58        for name in tool_names {
59            if let Some(tool) = self.create_tool(name) {
60                executor.register_tool(tool);
61            }
62        }
63
64        executor
65    }
66
67    /// Create a tool executor with all available tools
68    pub fn create_executor_with_all(&self) -> ToolExecutor {
69        let mut executor = ToolExecutor::new();
70
71        for factory in self.factories.values() {
72            executor.register_tool(factory.create());
73        }
74
75        executor
76    }
77}
78
79impl Default for ToolRegistry {
80    fn default() -> Self {
81        let mut registry = Self::new();
82
83        // Register built-in tools
84        registry.register_factory(Box::new(crate::tools::builtin::ThinkingToolFactory));
85        registry.register_factory(Box::new(crate::tools::builtin::TaskDoneToolFactory));
86        registry.register_factory(Box::new(crate::tools::builtin::McpToolFactory));
87
88        registry
89    }
90}
91
92/// Macro to help implement tool factories
93#[macro_export]
94macro_rules! impl_tool_factory {
95    ($factory:ident, $tool:ident, $name:expr, $description:expr) => {
96        pub struct $factory;
97
98        impl $crate::tools::ToolFactory for $factory {
99            fn create(&self) -> Box<dyn $crate::tools::Tool> {
100                Box::new($tool::new())
101            }
102
103            fn tool_name(&self) -> &str {
104                $name
105            }
106
107            fn tool_description(&self) -> &str {
108                $description
109            }
110        }
111    };
112}
113
114#[cfg(test)]
115mod tests {
116    use crate::tools::registry::ToolRegistry;
117
118    #[test]
119    fn test_default_registry_has_all_tools() {
120        let registry = ToolRegistry::default();
121        let tools = registry.list_tools();
122
123        // Expected tools based on Python version
124        let expected_tools = vec!["sequentialthinking", "task_done", "mcp_tool"];
125
126        println!("Available tools: {:?}", tools);
127
128        // Check that all expected tools are registered
129        for expected_tool in &expected_tools {
130            assert!(
131                tools.contains(expected_tool),
132                "Tool '{}' is not registered in the default registry",
133                expected_tool
134            );
135        }
136
137        // Check that we have the expected number of tools
138        assert_eq!(
139            tools.len(),
140            expected_tools.len(),
141            "Expected {} tools, but found {}. Tools: {:?}",
142            expected_tools.len(),
143            tools.len(),
144            tools
145        );
146    }
147
148    #[test]
149    fn test_tool_creation() {
150        let registry = ToolRegistry::default();
151
152        // Test creating each tool
153        let tools_to_test = vec!["sequentialthinking", "task_done", "mcp_tool"];
154
155        for tool_name in tools_to_test {
156            let tool = registry.create_tool(tool_name);
157            assert!(tool.is_some(), "Failed to create tool '{}'", tool_name);
158
159            let tool = tool.unwrap();
160            assert_eq!(
161                tool.name(),
162                tool_name,
163                "Tool name mismatch for '{}'",
164                tool_name
165            );
166
167            // Verify tool has a description
168            assert!(
169                !tool.description().is_empty(),
170                "Tool '{}' has empty description",
171                tool_name
172            );
173
174            // Verify tool has parameters schema
175            let schema = tool.parameters_schema();
176            assert!(
177                schema.is_object(),
178                "Tool '{}' parameters schema is not an object",
179                tool_name
180            );
181        }
182    }
183
184    #[test]
185    fn test_tool_info() {
186        let registry = ToolRegistry::default();
187
188        for tool_name in registry.list_tools() {
189            let info = registry.get_tool_info(tool_name);
190            assert!(
191                info.is_some(),
192                "Failed to get info for tool '{}'",
193                tool_name
194            );
195
196            let (name, description) = info.unwrap();
197            assert_eq!(name, tool_name);
198            assert!(!description.is_empty());
199        }
200    }
201
202    #[test]
203    fn test_executor_creation() {
204        let registry = ToolRegistry::default();
205
206        // Test creating executor with specific tools
207        let tool_names = vec![
208            "bash".to_string(),
209            "str_replace_based_edit_tool".to_string(),
210        ];
211        let _executor = registry.create_executor(&tool_names);
212
213        // The executor should have the requested tools
214        // Note: We can't easily test this without exposing internal state
215        // This test mainly ensures the method doesn't panic
216
217        // Test creating executor with all tools
218        let _all_executor = registry.create_executor_with_all();
219    }
220
221    #[test]
222    fn test_tool_examples() {
223        let registry = ToolRegistry::default();
224
225        for tool_name in registry.list_tools() {
226            let tool = registry.create_tool(tool_name).unwrap();
227            let examples = tool.examples();
228
229            // Each tool should have at least one example
230            assert!(!examples.is_empty(), "Tool '{}' has no examples", tool_name);
231
232            // Verify example structure
233            for (i, example) in examples.iter().enumerate() {
234                assert!(
235                    !example.description.is_empty(),
236                    "Tool '{}' example {} has empty description",
237                    tool_name,
238                    i
239                );
240
241                assert!(
242                    example.parameters.is_object(),
243                    "Tool '{}' example {} parameters is not an object",
244                    tool_name,
245                    i
246                );
247
248                assert!(
249                    !example.expected_result.is_empty(),
250                    "Tool '{}' example {} has empty expected result",
251                    tool_name,
252                    i
253                );
254            }
255        }
256    }
257
258    #[test]
259    fn test_tool_parameter_schemas() {
260        let registry = ToolRegistry::default();
261
262        for tool_name in registry.list_tools() {
263            let tool = registry.create_tool(tool_name).unwrap();
264            let schema = tool.parameters_schema();
265
266            // Schema should be an object
267            assert!(
268                schema.is_object(),
269                "Tool '{}' schema is not an object",
270                tool_name
271            );
272
273            let schema_obj = schema.as_object().unwrap();
274
275            // Should have type property
276            if let Some(type_val) = schema_obj.get("type") {
277                assert_eq!(
278                    type_val.as_str(),
279                    Some("object"),
280                    "Tool '{}' schema type is not 'object'",
281                    tool_name
282                );
283            }
284
285            // Should have properties
286            if let Some(properties) = schema_obj.get("properties") {
287                assert!(
288                    properties.is_object(),
289                    "Tool '{}' schema properties is not an object",
290                    tool_name
291                );
292
293                let props = properties.as_object().unwrap();
294                assert!(
295                    !props.is_empty(),
296                    "Tool '{}' has no properties in schema",
297                    tool_name
298                );
299            }
300        }
301    }
302}