Skip to main content

traitclaw_core/traits/
tool_registry.rs

1//! Dynamic tool registry — pluggable tool management for v0.3.0.
2//!
3//! [`ToolRegistry`] provides a unified interface for managing tools at runtime.
4//! It supports both read-only registries (like [`SimpleRegistry`]) and
5//! dynamic registries that allow runtime tool activation/deactivation.
6//!
7//! # Design
8//!
9//! All methods take `&self` to enable shared ownership via `Arc<dyn ToolRegistry>`.
10//! Mutable operations (register, unregister, set_enabled) use interior mutability
11//! (e.g., `RwLock`) in implementations that support them.
12//!
13//! # Example
14//!
15//! ```rust
16//! use traitclaw_core::traits::tool_registry::{ToolRegistry, SimpleRegistry};
17//! use traitclaw_core::traits::tool::ErasedTool;
18//! use std::sync::Arc;
19//!
20//! // Wrap existing tools in a SimpleRegistry
21//! let tools: Vec<Arc<dyn ErasedTool>> = vec![];
22//! let registry = SimpleRegistry::new(tools);
23//! assert_eq!(registry.get_tools().len(), 0);
24//! ```
25
26use std::sync::Arc;
27
28use crate::traits::tool::ErasedTool;
29
30/// Trait for pluggable tool management.
31///
32/// Provides read access to the current tool set and optional write operations
33/// for dynamic tool management. Write methods return `bool` to indicate success.
34///
35/// All methods take `&self` — implementations requiring mutation should use
36/// interior mutability (e.g., `RwLock`).
37pub trait ToolRegistry: Send + Sync {
38    /// Get all currently enabled tools.
39    fn get_tools(&self) -> Vec<Arc<dyn ErasedTool>>;
40
41    /// Find a tool by name.
42    fn find_tool(&self, name: &str) -> Option<Arc<dyn ErasedTool>> {
43        self.get_tools()
44            .into_iter()
45            .find(|t| t.schema().name == name)
46    }
47
48    /// Register a new tool. Returns `true` if the tool was added.
49    ///
50    /// Default implementation returns `false` (read-only registry).
51    fn register(&self, _tool: Arc<dyn ErasedTool>) -> bool {
52        false
53    }
54
55    /// Unregister a tool by name. Returns `true` if the tool was removed.
56    ///
57    /// Default implementation returns `false` (read-only registry).
58    fn unregister(&self, _name: &str) -> bool {
59        false
60    }
61
62    /// Enable or disable a tool by name. Returns `true` if the state changed.
63    ///
64    /// Default implementation returns `false` (read-only registry).
65    fn set_enabled(&self, _name: &str, _enabled: bool) -> bool {
66        false
67    }
68
69    /// Check if a tool is currently enabled.
70    fn is_enabled(&self, name: &str) -> bool {
71        self.find_tool(name).is_some()
72    }
73
74    /// Get the number of currently enabled tools.
75    fn len(&self) -> usize {
76        self.get_tools().len()
77    }
78
79    /// Check if the registry has no enabled tools.
80    fn is_empty(&self) -> bool {
81        self.len() == 0
82    }
83}
84
85// ---------------------------------------------------------------------------
86// SimpleRegistry — immutable wrapper around Vec<Arc<dyn ErasedTool>>
87// ---------------------------------------------------------------------------
88
89/// A simple, immutable tool registry that wraps `Vec<Arc<dyn ErasedTool>>`.
90///
91/// This is the default registry used when no custom registry is configured.
92/// It preserves the v0.2.0 behavior where tools are fixed at agent construction time.
93///
94/// For dynamic tool management, use `DynamicRegistry` (Story 3.3).
95pub struct SimpleRegistry {
96    tools: Vec<Arc<dyn ErasedTool>>,
97}
98
99impl SimpleRegistry {
100    /// Create a registry from an existing tool list.
101    #[must_use]
102    pub fn new(tools: Vec<Arc<dyn ErasedTool>>) -> Self {
103        Self { tools }
104    }
105}
106
107impl ToolRegistry for SimpleRegistry {
108    fn get_tools(&self) -> Vec<Arc<dyn ErasedTool>> {
109        self.tools.clone()
110    }
111
112    fn find_tool(&self, name: &str) -> Option<Arc<dyn ErasedTool>> {
113        self.tools.iter().find(|t| t.schema().name == name).cloned()
114    }
115
116    fn len(&self) -> usize {
117        self.tools.len()
118    }
119
120    fn is_empty(&self) -> bool {
121        self.tools.is_empty()
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    // ── Object safety ───────────────────────────────────────────────────
130    #[test]
131    fn test_tool_registry_is_object_safe() {
132        let registry = SimpleRegistry::new(vec![]);
133        let _: Arc<dyn ToolRegistry> = Arc::new(registry);
134    }
135
136    // ── SimpleRegistry basic operations ─────────────────────────────────
137    #[test]
138    fn test_simple_registry_empty() {
139        let registry = SimpleRegistry::new(vec![]);
140        assert!(registry.is_empty());
141        assert_eq!(registry.len(), 0);
142        assert_eq!(registry.get_tools().len(), 0);
143        assert!(registry.find_tool("nonexistent").is_none());
144    }
145
146    // ── Write operations rejected on SimpleRegistry ─────────────────────
147    #[test]
148    fn test_simple_registry_is_read_only() {
149        let registry = SimpleRegistry::new(vec![]);
150        assert!(!registry.register(Arc::new(DummyTool)));
151        assert!(!registry.unregister("dummy"));
152        assert!(!registry.set_enabled("dummy", false));
153    }
154
155    // ── SimpleRegistry with tools ──────────────────────────────────────
156    #[test]
157    fn test_simple_registry_with_tools() {
158        let tool: Arc<dyn ErasedTool> = Arc::new(DummyTool);
159        let registry = SimpleRegistry::new(vec![tool]);
160        assert_eq!(registry.len(), 1);
161        assert!(!registry.is_empty());
162        assert!(registry.find_tool("dummy_tool").is_some());
163        assert!(registry.find_tool("nonexistent").is_none());
164        assert!(registry.is_enabled("dummy_tool"));
165        assert!(!registry.is_enabled("nonexistent"));
166    }
167
168    // ── Dummy tool for tests ────────────────────────────────────────────
169    struct DummyTool;
170
171    #[async_trait::async_trait]
172    impl ErasedTool for DummyTool {
173        fn name(&self) -> &str {
174            "dummy_tool"
175        }
176
177        fn description(&self) -> &str {
178            "A test tool"
179        }
180
181        fn schema(&self) -> crate::traits::tool::ToolSchema {
182            crate::traits::tool::ToolSchema {
183                name: "dummy_tool".to_string(),
184                description: "A test tool".to_string(),
185                parameters: serde_json::json!({"type": "object", "properties": {}}),
186            }
187        }
188
189        async fn execute_json(
190            &self,
191            _input: serde_json::Value,
192        ) -> crate::Result<serde_json::Value> {
193            Ok(serde_json::json!("ok"))
194        }
195    }
196}