1use std::collections::HashMap;
7use std::sync::{Arc, RwLock};
8
9use crate::error::{AgentError, Result};
10use crate::tools::Tool;
11use crate::types::{SubAgent, SubAgentDirectory, ToolSpec};
12
13pub struct StaticToolCatalog {
16 tools: RwLock<HashMap<String, Arc<dyn Tool>>>,
17 specs: RwLock<HashMap<String, ToolSpec>>,
18 order: RwLock<Vec<String>>,
19}
20
21impl StaticToolCatalog {
22 pub fn new() -> Self {
24 Self {
25 tools: RwLock::new(HashMap::new()),
26 specs: RwLock::new(HashMap::new()),
27 order: RwLock::new(Vec::new()),
28 }
29 }
30
31 pub fn register(&self, tool: Arc<dyn Tool>) -> Result<()> {
34 let spec = tool.spec();
35 let key = spec.name.to_lowercase().trim().to_string();
36
37 if key.is_empty() {
38 return Err(AgentError::ToolError("tool name is empty".into()));
39 }
40
41 let mut tools = self.tools.write().unwrap();
42 let mut specs = self.specs.write().unwrap();
43 let mut order = self.order.write().unwrap();
44
45 if tools.contains_key(&key) {
46 return Err(AgentError::ToolError(format!(
47 "tool {} already registered",
48 spec.name
49 )));
50 }
51
52 tools.insert(key.clone(), tool);
53 specs.insert(key.clone(), spec);
54 order.push(key);
55
56 Ok(())
57 }
58
59 pub fn lookup(&self, name: &str) -> Option<(Arc<dyn Tool>, ToolSpec)> {
61 let key = name.to_lowercase().trim().to_string();
62
63 let tools = self.tools.read().unwrap();
64 let specs = self.specs.read().unwrap();
65
66 if let Some(tool) = tools.get(&key) {
67 if let Some(spec) = specs.get(&key) {
68 return Some((Arc::clone(tool), spec.clone()));
69 }
70 }
71
72 None
73 }
74
75 pub fn specs(&self) -> Vec<ToolSpec> {
77 let order = self.order.read().unwrap();
78 let specs = self.specs.read().unwrap();
79
80 order
81 .iter()
82 .filter_map(|key| specs.get(key).cloned())
83 .collect()
84 }
85
86 pub fn tools(&self) -> Vec<Arc<dyn Tool>> {
88 let order = self.order.read().unwrap();
89 let tools = self.tools.read().unwrap();
90
91 order
92 .iter()
93 .filter_map(|key| tools.get(key).map(Arc::clone))
94 .collect()
95 }
96}
97
98impl Default for StaticToolCatalog {
99 fn default() -> Self {
100 Self::new()
101 }
102}
103
104pub struct StaticSubAgentDirectory {
107 subagents: RwLock<HashMap<String, Arc<dyn SubAgent>>>,
108 order: RwLock<Vec<String>>,
109}
110
111impl StaticSubAgentDirectory {
112 pub fn new() -> Self {
114 Self {
115 subagents: RwLock::new(HashMap::new()),
116 order: RwLock::new(Vec::new()),
117 }
118 }
119}
120
121impl Default for StaticSubAgentDirectory {
122 fn default() -> Self {
123 Self::new()
124 }
125}
126
127impl SubAgentDirectory for StaticSubAgentDirectory {
128 fn register(&self, subagent: Arc<dyn SubAgent>) -> Result<()> {
130 let name = subagent.name();
131 let key = name.to_lowercase().trim().to_string();
132
133 if key.is_empty() {
134 return Err(AgentError::Other("sub-agent name is empty".into()));
135 }
136
137 let mut subagents = self.subagents.write().unwrap();
138 let mut order = self.order.write().unwrap();
139
140 if subagents.contains_key(&key) {
141 return Err(AgentError::Other(format!(
142 "sub-agent {} already registered",
143 name
144 )));
145 }
146
147 subagents.insert(key.clone(), subagent);
148 order.push(key);
149
150 Ok(())
151 }
152
153 fn lookup(&self, name: &str) -> Option<Arc<dyn SubAgent>> {
155 let key = name.to_lowercase().trim().to_string();
156 let subagents = self.subagents.read().unwrap();
157 subagents.get(&key).map(Arc::clone)
158 }
159
160 fn all(&self) -> Vec<Arc<dyn SubAgent>> {
162 let order = self.order.read().unwrap();
163 let subagents = self.subagents.read().unwrap();
164
165 order
166 .iter()
167 .filter_map(|key| subagents.get(key).map(Arc::clone))
168 .collect()
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175 use async_trait::async_trait;
176 use crate::types::{ToolRequest, ToolResponse};
177
178 struct TestTool {
179 name: String,
180 }
181
182 #[async_trait]
183 impl Tool for TestTool {
184 fn spec(&self) -> ToolSpec {
185 ToolSpec {
186 name: self.name.clone(),
187 description: "Test tool".into(),
188 input_schema: serde_json::json!({}),
189 examples: None,
190 }
191 }
192
193 async fn invoke(&self, _req: ToolRequest) -> Result<ToolResponse> {
194 Ok(ToolResponse {
195 content: "test".into(),
196 metadata: None,
197 })
198 }
199 }
200
201 #[test]
202 fn catalog_registers_and_lookups_tools() {
203 let catalog = StaticToolCatalog::new();
204 let tool = Arc::new(TestTool {
205 name: "test.tool".into(),
206 });
207
208 catalog.register(tool).unwrap();
209 assert!(catalog.lookup("test.tool").is_some());
210 assert!(catalog.lookup("unknown").is_none());
211 }
212
213 #[test]
214 fn catalog_prevents_duplicate_registration() {
215 let catalog = StaticToolCatalog::new();
216 let tool1 = Arc::new(TestTool {
217 name: "test.tool".into(),
218 });
219 let tool2 = Arc::new(TestTool {
220 name: "test.tool".into(),
221 });
222
223 catalog.register(tool1).unwrap();
224 assert!(catalog.register(tool2).is_err());
225 }
226
227 struct TestSubAgent {
228 name: String,
229 }
230
231 #[async_trait]
232 impl SubAgent for TestSubAgent {
233 fn name(&self) -> String {
234 self.name.clone()
235 }
236
237 fn description(&self) -> String {
238 "Test sub-agent".into()
239 }
240
241 async fn run(&self, _input: String) -> Result<String> {
242 Ok("test output".into())
243 }
244 }
245
246 #[test]
247 fn directory_registers_and_lookups_subagents() {
248 let dir = StaticSubAgentDirectory::new();
249 let subagent = Arc::new(TestSubAgent {
250 name: "test.agent".into(),
251 });
252
253 dir.register(subagent).unwrap();
254 assert!(dir.lookup("test.agent").is_some());
255 assert!(dir.lookup("unknown").is_none());
256 }
257
258 #[test]
259 fn directory_prevents_duplicate_registration() {
260 let dir = StaticSubAgentDirectory::new();
261 let sa1 = Arc::new(TestSubAgent {
262 name: "test.agent".into(),
263 });
264 let sa2 = Arc::new(TestSubAgent {
265 name: "test.agent".into(),
266 });
267
268 dir.register(sa1).unwrap();
269 assert!(dir.register(sa2).is_err());
270 }
271}