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}