Skip to main content

phi_core/tools/
registry.rs

1//! G10 — Tool registry for config-driven tool instantiation.
2//!
3//! `ToolRegistry` maps tool names to factory functions, enabling
4//! `agent_from_config_with_registry()` to resolve `tools.enabled` names
5//! into concrete `Arc<dyn AgentTool>` instances.
6
7use crate::types::AgentTool;
8use std::collections::HashMap;
9use std::sync::Arc;
10
11/// Maps tool names to factory functions for instantiation from config.
12///
13/// # Example
14///
15/// ```rust
16/// use phi_core::tools::ToolRegistry;
17///
18/// let registry = ToolRegistry::new().with_defaults();
19/// let tools = registry.resolve(&["bash".to_string(), "read_file".to_string()]);
20/// assert_eq!(tools.len(), 2);
21/// ```
22pub struct ToolRegistry {
23    factories: HashMap<String, Box<dyn Fn() -> Arc<dyn AgentTool> + Send + Sync>>,
24}
25
26impl ToolRegistry {
27    /// Create an empty registry.
28    pub fn new() -> Self {
29        Self {
30            factories: HashMap::new(),
31        }
32    }
33
34    /// Register all 6 built-in tools.
35    pub fn with_defaults(mut self) -> Self {
36        self.register("bash", || Arc::new(super::BashTool::default()));
37        self.register("read_file", || Arc::new(super::ReadFileTool::default()));
38        self.register("write_file", || Arc::new(super::WriteFileTool::new()));
39        self.register("edit_file", || Arc::new(super::EditFileTool::new()));
40        self.register("list_files", || Arc::new(super::ListFilesTool::default()));
41        self.register("search", || Arc::new(super::SearchTool::default()));
42        self
43    }
44
45    /// Register a tool factory under the given name.
46    ///
47    /// If a factory with the same name already exists, it is replaced.
48    pub fn register(
49        &mut self,
50        name: &str,
51        factory: impl Fn() -> Arc<dyn AgentTool> + Send + Sync + 'static,
52    ) {
53        self.factories.insert(name.to_string(), Box::new(factory));
54    }
55
56    /// Resolve tool names to instances. Unknown names are silently skipped.
57    pub fn resolve(&self, names: &[String]) -> Vec<Arc<dyn AgentTool>> {
58        names
59            .iter()
60            .filter_map(|name| self.factories.get(name).map(|f| f()))
61            .collect()
62    }
63
64    /// Check whether a tool name is registered.
65    pub fn contains(&self, name: &str) -> bool {
66        self.factories.contains_key(name)
67    }
68
69    /// Return the number of registered tool factories.
70    pub fn len(&self) -> usize {
71        self.factories.len()
72    }
73
74    /// Return whether the registry is empty.
75    pub fn is_empty(&self) -> bool {
76        self.factories.is_empty()
77    }
78}
79
80impl Default for ToolRegistry {
81    fn default() -> Self {
82        Self::new()
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn test_empty_registry() {
92        let registry = ToolRegistry::new();
93        assert!(registry.is_empty());
94        let tools = registry.resolve(&["bash".to_string()]);
95        assert!(tools.is_empty());
96    }
97
98    #[test]
99    fn test_with_defaults() {
100        let registry = ToolRegistry::new().with_defaults();
101        assert_eq!(registry.len(), 6);
102        assert!(registry.contains("bash"));
103        assert!(registry.contains("read_file"));
104        assert!(registry.contains("write_file"));
105        assert!(registry.contains("edit_file"));
106        assert!(registry.contains("list_files"));
107        assert!(registry.contains("search"));
108    }
109
110    #[test]
111    fn test_resolve_subset() {
112        let registry = ToolRegistry::new().with_defaults();
113        let tools = registry.resolve(&["bash".to_string(), "search".to_string()]);
114        assert_eq!(tools.len(), 2);
115    }
116
117    #[test]
118    fn test_resolve_skips_unknown() {
119        let registry = ToolRegistry::new().with_defaults();
120        let tools = registry.resolve(&["bash".to_string(), "nonexistent".to_string()]);
121        assert_eq!(tools.len(), 1);
122    }
123
124    #[test]
125    fn test_custom_registration() {
126        let mut registry = ToolRegistry::new();
127        registry.register("bash", || Arc::new(super::super::BashTool::default()));
128        assert_eq!(registry.len(), 1);
129        assert!(registry.contains("bash"));
130    }
131}