ricecoder_agents/
tool_registry.rs

1//! Tool registry for integrating ricecoder-tools with the agent system
2//!
3//! This module provides a registry for discovering and managing tools that agents can invoke.
4//! Tools are registered with metadata about their capabilities and invocation interface.
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::sync::Arc;
9use tracing::{debug, info};
10
11/// Metadata about a registered tool
12///
13/// This struct contains metadata about a tool, including its ID, name, description,
14/// and the types of operations it supports.
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct ToolMetadata {
17    /// Tool identifier (e.g., "webfetch", "patch", "todowrite", "todoread", "websearch")
18    pub id: String,
19    /// Tool name
20    pub name: String,
21    /// Tool description
22    pub description: String,
23    /// Input schema (JSON Schema)
24    pub input_schema: serde_json::Value,
25    /// Output schema (JSON Schema)
26    pub output_schema: serde_json::Value,
27    /// Whether the tool is available
28    pub available: bool,
29}
30
31/// Tool invocation interface
32///
33/// This trait defines the interface for invoking tools. Implementations handle
34/// the actual tool execution and result formatting.
35#[async_trait::async_trait]
36pub trait ToolInvoker: Send + Sync {
37    /// Invoke the tool with the given input
38    ///
39    /// # Arguments
40    ///
41    /// * `input` - The input to the tool as JSON
42    ///
43    /// # Returns
44    ///
45    /// The tool output as JSON or an error
46    async fn invoke(&self, input: serde_json::Value) -> Result<serde_json::Value, String>;
47
48    /// Get metadata about this tool
49    fn metadata(&self) -> ToolMetadata;
50}
51
52/// Registry for discovering and managing tools
53///
54/// The `ToolRegistry` maintains a registry of all available tools and provides
55/// methods to discover tools by ID and invoke them. Tools can be registered
56/// at startup or dynamically at runtime.
57///
58/// # Examples
59///
60/// ```ignore
61/// use ricecoder_agents::ToolRegistry;
62///
63/// let mut registry = ToolRegistry::new();
64/// // Register tools...
65///
66/// // Find a tool
67/// let tool = registry.find_tool("webfetch");
68/// ```
69pub struct ToolRegistry {
70    tools: HashMap<String, Arc<dyn ToolInvoker>>,
71    metadata: HashMap<String, ToolMetadata>,
72}
73
74impl ToolRegistry {
75    /// Create a new tool registry
76    ///
77    /// # Returns
78    ///
79    /// A new empty `ToolRegistry`
80    pub fn new() -> Self {
81        Self {
82            tools: HashMap::new(),
83            metadata: HashMap::new(),
84        }
85    }
86
87    /// Register a tool
88    ///
89    /// Registers a tool with the registry, making it available for agent invocation.
90    ///
91    /// # Arguments
92    ///
93    /// * `tool` - The tool invoker to register
94    pub fn register(&mut self, tool: Arc<dyn ToolInvoker>) {
95        let metadata = tool.metadata();
96        let tool_id = metadata.id.clone();
97        let tool_name = metadata.name.clone();
98
99        debug!(tool_id = %tool_id, tool_name = %tool_name, "Registering tool");
100
101        self.tools.insert(tool_id.clone(), tool);
102        self.metadata.insert(tool_id.clone(), metadata);
103
104        info!(tool_id = %tool_id, tool_name = %tool_name, "Tool registered successfully");
105    }
106
107    /// Find a tool by ID
108    ///
109    /// # Arguments
110    ///
111    /// * `tool_id` - The ID of the tool to find
112    ///
113    /// # Returns
114    ///
115    /// An `Option` containing the tool if found
116    pub fn find_tool(&self, tool_id: &str) -> Option<Arc<dyn ToolInvoker>> {
117        self.tools.get(tool_id).cloned()
118    }
119
120    /// Get all registered tools
121    ///
122    /// # Returns
123    ///
124    /// A vector of all registered tools
125    pub fn all_tools(&self) -> Vec<Arc<dyn ToolInvoker>> {
126        self.tools.values().cloned().collect()
127    }
128
129    /// Get the number of registered tools
130    ///
131    /// # Returns
132    ///
133    /// The total number of registered tools
134    pub fn tool_count(&self) -> usize {
135        self.tools.len()
136    }
137
138    /// Get metadata for a specific tool
139    ///
140    /// # Arguments
141    ///
142    /// * `tool_id` - The ID of the tool
143    ///
144    /// # Returns
145    ///
146    /// An `Option` containing the tool metadata if found
147    pub fn get_tool_metadata(&self, tool_id: &str) -> Option<ToolMetadata> {
148        self.metadata.get(tool_id).cloned()
149    }
150
151    /// Get metadata for all registered tools
152    ///
153    /// # Returns
154    ///
155    /// A vector of metadata for all registered tools
156    pub fn all_tool_metadata(&self) -> Vec<ToolMetadata> {
157        self.metadata.values().cloned().collect()
158    }
159
160    /// Check if a tool is registered
161    ///
162    /// # Arguments
163    ///
164    /// * `tool_id` - The ID of the tool to check
165    ///
166    /// # Returns
167    ///
168    /// `true` if the tool is registered, `false` otherwise
169    pub fn has_tool(&self, tool_id: &str) -> bool {
170        self.tools.contains_key(tool_id)
171    }
172
173    /// Invoke a tool
174    ///
175    /// # Arguments
176    ///
177    /// * `tool_id` - The ID of the tool to invoke
178    /// * `input` - The input to the tool as JSON
179    ///
180    /// # Returns
181    ///
182    /// The tool output as JSON or an error
183    pub async fn invoke_tool(
184        &self,
185        tool_id: &str,
186        input: serde_json::Value,
187    ) -> Result<serde_json::Value, String> {
188        let tool = self
189            .find_tool(tool_id)
190            .ok_or_else(|| format!("Tool not found: {}", tool_id))?;
191
192        debug!(tool_id = %tool_id, "Invoking tool");
193        let result = tool.invoke(input).await?;
194        debug!(tool_id = %tool_id, "Tool invocation completed");
195
196        Ok(result)
197    }
198
199    /// Discover built-in tools at startup
200    ///
201    /// This method initializes the registry with built-in tools from ricecoder-tools.
202    /// It registers webfetch, patch, todowrite, todoread, and websearch tools.
203    pub fn discover_builtin_tools(&mut self) -> Result<(), String> {
204        info!("Discovering built-in tools");
205
206        // Built-in tools will be registered here as they are implemented
207        // For now, this is a placeholder that can be extended
208        debug!("Built-in tool discovery completed");
209        Ok(())
210    }
211
212    /// Load tool configuration from project settings
213    ///
214    /// This method loads tool configuration from a configuration source.
215    /// The configuration can be used to enable/disable tools or customize
216    /// their behavior.
217    pub fn load_configuration(
218        &mut self,
219        config: HashMap<String, serde_json::Value>,
220    ) -> Result<(), String> {
221        info!(config_count = config.len(), "Loading tool configuration");
222        // Configuration loading logic can be implemented here
223        // This allows tools to be configured at runtime
224        debug!("Tool configuration loaded successfully");
225        Ok(())
226    }
227
228    /// Get all available tool IDs
229    ///
230    /// # Returns
231    ///
232    /// A vector of all registered tool IDs
233    pub fn available_tool_ids(&self) -> Vec<String> {
234        let mut ids: Vec<String> = self.tools.keys().cloned().collect();
235        ids.sort();
236        ids
237    }
238}
239
240impl Default for ToolRegistry {
241    fn default() -> Self {
242        Self::new()
243    }
244}
245
246#[cfg(test)]
247mod tests {
248    use super::*;
249
250    struct TestTool {
251        id: String,
252        name: String,
253        description: String,
254    }
255
256    #[async_trait::async_trait]
257    impl ToolInvoker for TestTool {
258        async fn invoke(&self, input: serde_json::Value) -> Result<serde_json::Value, String> {
259            Ok(serde_json::json!({
260                "success": true,
261                "input": input
262            }))
263        }
264
265        fn metadata(&self) -> ToolMetadata {
266            ToolMetadata {
267                id: self.id.clone(),
268                name: self.name.clone(),
269                description: self.description.clone(),
270                input_schema: serde_json::json!({}),
271                output_schema: serde_json::json!({}),
272                available: true,
273            }
274        }
275    }
276
277    #[test]
278    fn test_register_tool() {
279        let mut registry = ToolRegistry::new();
280        let tool = Arc::new(TestTool {
281            id: "test-tool".to_string(),
282            name: "Test Tool".to_string(),
283            description: "A test tool".to_string(),
284        });
285
286        registry.register(tool);
287        assert_eq!(registry.tool_count(), 1);
288    }
289
290    #[test]
291    fn test_find_tool_by_id() {
292        let mut registry = ToolRegistry::new();
293        let tool = Arc::new(TestTool {
294            id: "test-tool".to_string(),
295            name: "Test Tool".to_string(),
296            description: "A test tool".to_string(),
297        });
298
299        registry.register(tool);
300        let found = registry.find_tool("test-tool");
301        assert!(found.is_some());
302    }
303
304    #[test]
305    fn test_find_tool_not_found() {
306        let registry = ToolRegistry::new();
307        let found = registry.find_tool("nonexistent");
308        assert!(found.is_none());
309    }
310
311    #[test]
312    fn test_get_tool_metadata() {
313        let mut registry = ToolRegistry::new();
314        let tool = Arc::new(TestTool {
315            id: "test-tool".to_string(),
316            name: "Test Tool".to_string(),
317            description: "A test tool for metadata".to_string(),
318        });
319
320        registry.register(tool);
321        let metadata = registry.get_tool_metadata("test-tool");
322        assert!(metadata.is_some());
323
324        let meta = metadata.unwrap();
325        assert_eq!(meta.id, "test-tool");
326        assert_eq!(meta.name, "Test Tool");
327        assert_eq!(meta.description, "A test tool for metadata");
328    }
329
330    #[test]
331    fn test_all_tool_metadata() {
332        let mut registry = ToolRegistry::new();
333        let tool1 = Arc::new(TestTool {
334            id: "tool-1".to_string(),
335            name: "Tool 1".to_string(),
336            description: "First tool".to_string(),
337        });
338        let tool2 = Arc::new(TestTool {
339            id: "tool-2".to_string(),
340            name: "Tool 2".to_string(),
341            description: "Second tool".to_string(),
342        });
343
344        registry.register(tool1);
345        registry.register(tool2);
346
347        let all_metadata = registry.all_tool_metadata();
348        assert_eq!(all_metadata.len(), 2);
349    }
350
351    #[test]
352    fn test_has_tool() {
353        let mut registry = ToolRegistry::new();
354        let tool = Arc::new(TestTool {
355            id: "test-tool".to_string(),
356            name: "Test Tool".to_string(),
357            description: "A test tool".to_string(),
358        });
359
360        registry.register(tool);
361        assert!(registry.has_tool("test-tool"));
362        assert!(!registry.has_tool("nonexistent"));
363    }
364
365    #[tokio::test]
366    async fn test_invoke_tool() {
367        let mut registry = ToolRegistry::new();
368        let tool = Arc::new(TestTool {
369            id: "test-tool".to_string(),
370            name: "Test Tool".to_string(),
371            description: "A test tool".to_string(),
372        });
373
374        registry.register(tool);
375
376        let input = serde_json::json!({"test": "data"});
377        let result = registry.invoke_tool("test-tool", input).await;
378        assert!(result.is_ok());
379
380        let output = result.unwrap();
381        assert_eq!(output["success"], true);
382    }
383
384    #[tokio::test]
385    async fn test_invoke_tool_not_found() {
386        let registry = ToolRegistry::new();
387        let input = serde_json::json!({"test": "data"});
388        let result = registry.invoke_tool("nonexistent", input).await;
389        assert!(result.is_err());
390    }
391
392    #[test]
393    fn test_available_tool_ids() {
394        let mut registry = ToolRegistry::new();
395        let tool1 = Arc::new(TestTool {
396            id: "tool-1".to_string(),
397            name: "Tool 1".to_string(),
398            description: "First tool".to_string(),
399        });
400        let tool2 = Arc::new(TestTool {
401            id: "tool-2".to_string(),
402            name: "Tool 2".to_string(),
403            description: "Second tool".to_string(),
404        });
405
406        registry.register(tool1);
407        registry.register(tool2);
408
409        let ids = registry.available_tool_ids();
410        assert_eq!(ids.len(), 2);
411        assert!(ids.contains(&"tool-1".to_string()));
412        assert!(ids.contains(&"tool-2".to_string()));
413    }
414
415    #[test]
416    fn test_discover_builtin_tools() {
417        let mut registry = ToolRegistry::new();
418        let result = registry.discover_builtin_tools();
419        assert!(result.is_ok());
420    }
421
422    #[test]
423    fn test_load_configuration() {
424        let mut registry = ToolRegistry::new();
425        let mut config = HashMap::new();
426        config.insert("tool-1".to_string(), serde_json::json!({"enabled": true}));
427
428        let result = registry.load_configuration(config);
429        assert!(result.is_ok());
430    }
431
432    #[test]
433    fn test_registry_empty_discovery() {
434        let registry = ToolRegistry::new();
435        assert_eq!(registry.tool_count(), 0);
436        assert!(registry.all_tool_metadata().is_empty());
437        assert!(registry.available_tool_ids().is_empty());
438    }
439
440    #[test]
441    fn test_registry_with_multiple_tools() {
442        let mut registry = ToolRegistry::new();
443        let tools: Vec<Arc<dyn ToolInvoker>> = (1..=5)
444            .map(|i| {
445                Arc::new(TestTool {
446                    id: format!("tool-{}", i),
447                    name: format!("Tool {}", i),
448                    description: format!("Test tool {}", i),
449                }) as Arc<dyn ToolInvoker>
450            })
451            .collect();
452
453        for tool in tools {
454            registry.register(tool);
455        }
456
457        assert_eq!(registry.tool_count(), 5);
458        assert_eq!(registry.all_tool_metadata().len(), 5);
459    }
460}