ricecoder_agents/
registry.rs

1//! Agent registry for discovering and managing agents
2
3use crate::agents::Agent;
4use crate::error::{AgentError, Result};
5use crate::models::TaskType;
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::sync::Arc;
9use tracing::{debug, info};
10
11/// Metadata about a registered agent
12///
13/// This struct contains metadata about an agent, including its ID, name, description,
14/// and the task types it supports.
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct AgentMetadataInfo {
17    /// Agent identifier
18    pub id: String,
19    /// Agent name
20    pub name: String,
21    /// Agent description
22    pub description: String,
23    /// Supported task types
24    pub supported_task_types: Vec<TaskType>,
25}
26
27/// Registry for discovering and managing agents
28///
29/// The `AgentRegistry` maintains a registry of all available agents and provides
30/// methods to discover agents by ID or task type. It also manages agent metadata
31/// and configuration.
32///
33/// # Examples
34///
35/// ```ignore
36/// use ricecoder_agents::{AgentRegistry, Agent, TaskType};
37/// use std::sync::Arc;
38///
39/// let mut registry = AgentRegistry::new();
40/// // Register agents...
41///
42/// // Find agents by task type
43/// let agents = registry.find_agents_by_task_type(TaskType::CodeReview);
44/// ```
45pub struct AgentRegistry {
46    agents: HashMap<String, Arc<dyn Agent>>,
47    task_type_map: HashMap<TaskType, Vec<String>>,
48    metadata: HashMap<String, AgentMetadataInfo>,
49}
50
51impl AgentRegistry {
52    /// Create a new agent registry
53    ///
54    /// # Returns
55    ///
56    /// A new empty `AgentRegistry`
57    pub fn new() -> Self {
58        Self {
59            agents: HashMap::new(),
60            task_type_map: HashMap::new(),
61            metadata: HashMap::new(),
62        }
63    }
64
65    /// Register an agent
66    ///
67    /// Registers an agent with the registry, making it available for task execution.
68    /// The agent is indexed by ID and by each task type it supports.
69    ///
70    /// # Arguments
71    ///
72    /// * `agent` - The agent to register
73    pub fn register(&mut self, agent: Arc<dyn Agent>) {
74        let agent_id = agent.id().to_string();
75        let agent_name = agent.name().to_string();
76
77        debug!(agent_id = %agent_id, agent_name = %agent_name, "Registering agent");
78
79        self.agents.insert(agent_id.clone(), agent.clone());
80
81        // Collect supported task types
82        let supported_task_types: Vec<TaskType> = [
83            TaskType::CodeReview,
84            TaskType::TestGeneration,
85            TaskType::Documentation,
86            TaskType::Refactoring,
87            TaskType::SecurityAnalysis,
88        ]
89        .iter()
90        .copied()
91        .filter(|task_type| agent.supports(*task_type))
92        .collect();
93
94        debug!(
95            agent_id = %agent_id,
96            task_count = supported_task_types.len(),
97            "Agent supports task types"
98        );
99
100        // Register for each supported task type
101        for task_type in &supported_task_types {
102            self.task_type_map
103                .entry(*task_type)
104                .or_default()
105                .push(agent_id.clone());
106        }
107
108        // Store metadata
109        let metadata = AgentMetadataInfo {
110            id: agent_id.clone(),
111            name: agent_name.clone(),
112            description: agent.description().to_string(),
113            supported_task_types,
114        };
115        self.metadata.insert(metadata.id.clone(), metadata);
116
117        info!(agent_id = %agent_id, agent_name = %agent_name, "Agent registered successfully");
118    }
119
120    /// Find an agent by ID
121    ///
122    /// # Arguments
123    ///
124    /// * `agent_id` - The ID of the agent to find
125    ///
126    /// # Returns
127    ///
128    /// A `Result` containing the agent or an error if not found
129    pub fn find_agent(&self, agent_id: &str) -> Result<Arc<dyn Agent>> {
130        self.agents
131            .get(agent_id)
132            .cloned()
133            .ok_or_else(|| AgentError::not_found(agent_id))
134    }
135
136    /// Find agents by task type
137    ///
138    /// # Arguments
139    ///
140    /// * `task_type` - The task type to find agents for
141    ///
142    /// # Returns
143    ///
144    /// A vector of agents that support the given task type
145    pub fn find_agents_by_task_type(&self, task_type: TaskType) -> Vec<Arc<dyn Agent>> {
146        self.task_type_map
147            .get(&task_type)
148            .map(|agent_ids| {
149                agent_ids
150                    .iter()
151                    .filter_map(|id| self.agents.get(id).cloned())
152                    .collect()
153            })
154            .unwrap_or_default()
155    }
156
157    /// Get all registered agents
158    ///
159    /// # Returns
160    ///
161    /// A vector of all registered agents
162    pub fn all_agents(&self) -> Vec<Arc<dyn Agent>> {
163        self.agents.values().cloned().collect()
164    }
165
166    /// Get the number of registered agents
167    ///
168    /// # Returns
169    ///
170    /// The total number of registered agents
171    pub fn agent_count(&self) -> usize {
172        self.agents.len()
173    }
174
175    /// Get metadata for a specific agent
176    ///
177    /// # Arguments
178    ///
179    /// * `agent_id` - The ID of the agent
180    ///
181    /// # Returns
182    ///
183    /// An `Option` containing the agent metadata if found
184    pub fn get_agent_metadata(&self, agent_id: &str) -> Option<AgentMetadataInfo> {
185        self.metadata.get(agent_id).cloned()
186    }
187
188    /// Get metadata for all registered agents
189    ///
190    /// # Returns
191    ///
192    /// A vector of metadata for all registered agents
193    pub fn all_agent_metadata(&self) -> Vec<AgentMetadataInfo> {
194        self.metadata.values().cloned().collect()
195    }
196
197    /// Check if an agent is registered
198    ///
199    /// # Arguments
200    ///
201    /// * `agent_id` - The ID of the agent to check
202    ///
203    /// # Returns
204    ///
205    /// `true` if the agent is registered, `false` otherwise
206    pub fn has_agent(&self, agent_id: &str) -> bool {
207        self.agents.contains_key(agent_id)
208    }
209
210    /// Get agents that support a specific task type
211    ///
212    /// # Arguments
213    ///
214    /// * `task_type` - The task type to find agents for
215    ///
216    /// # Returns
217    ///
218    /// A vector of metadata for agents that support the given task type
219    pub fn agents_for_task_type(&self, task_type: TaskType) -> Vec<AgentMetadataInfo> {
220        self.task_type_map
221            .get(&task_type)
222            .map(|agent_ids| {
223                agent_ids
224                    .iter()
225                    .filter_map(|id| self.metadata.get(id).cloned())
226                    .collect()
227            })
228            .unwrap_or_default()
229    }
230
231    /// Discover built-in agents at startup
232    ///
233    /// This method initializes the registry with built-in agents.
234    /// In the current implementation, this is a placeholder for future
235    /// agent discovery mechanisms.
236    pub fn discover_builtin_agents(&mut self) -> Result<()> {
237        info!("Discovering built-in agents");
238        // Built-in agents will be registered here as they are implemented
239        // For now, this is a placeholder that can be extended
240        debug!("Built-in agent discovery completed");
241        Ok(())
242    }
243
244    /// Load agent configuration from project settings
245    ///
246    /// This method loads agent configuration from a configuration source.
247    /// The configuration can be used to enable/disable agents or customize
248    /// their behavior.
249    pub fn load_configuration(&mut self, config: HashMap<String, serde_json::Value>) -> Result<()> {
250        info!(config_count = config.len(), "Loading agent configuration");
251        // Configuration loading logic can be implemented here
252        // This allows agents to be configured at runtime
253        debug!("Agent configuration loaded successfully");
254        Ok(())
255    }
256
257    /// Get all task types that have registered agents
258    ///
259    /// # Returns
260    ///
261    /// A vector of all task types that have at least one registered agent
262    pub fn supported_task_types(&self) -> Vec<TaskType> {
263        let mut types: Vec<TaskType> = self.task_type_map.keys().copied().collect();
264        types.sort_by_key(|t| format!("{:?}", t));
265        types
266    }
267}
268
269impl Default for AgentRegistry {
270    fn default() -> Self {
271        Self::new()
272    }
273}
274
275#[cfg(test)]
276mod tests {
277    use super::*;
278    use crate::models::{AgentInput, AgentOutput};
279
280    struct TestAgent {
281        id: String,
282        name: String,
283        description: String,
284        task_types: Vec<TaskType>,
285    }
286
287    #[async_trait::async_trait]
288    impl Agent for TestAgent {
289        fn id(&self) -> &str {
290            &self.id
291        }
292
293        fn name(&self) -> &str {
294            &self.name
295        }
296
297        fn description(&self) -> &str {
298            &self.description
299        }
300
301        fn supports(&self, task_type: TaskType) -> bool {
302            self.task_types.contains(&task_type)
303        }
304
305        async fn execute(&self, _input: AgentInput) -> Result<AgentOutput> {
306            Ok(AgentOutput::default())
307        }
308    }
309
310    #[test]
311    fn test_register_agent() {
312        let mut registry = AgentRegistry::new();
313        let agent = Arc::new(TestAgent {
314            id: "test-agent".to_string(),
315            name: "Test Agent".to_string(),
316            description: "A test agent".to_string(),
317            task_types: vec![TaskType::CodeReview],
318        });
319
320        registry.register(agent);
321        assert_eq!(registry.agent_count(), 1);
322    }
323
324    #[test]
325    fn test_find_agent_by_id() {
326        let mut registry = AgentRegistry::new();
327        let agent = Arc::new(TestAgent {
328            id: "test-agent".to_string(),
329            name: "Test Agent".to_string(),
330            description: "A test agent".to_string(),
331            task_types: vec![TaskType::CodeReview],
332        });
333
334        registry.register(agent);
335        let found = registry.find_agent("test-agent");
336        assert!(found.is_ok());
337    }
338
339    #[test]
340    fn test_find_agent_not_found() {
341        let registry = AgentRegistry::new();
342        let found = registry.find_agent("nonexistent");
343        assert!(found.is_err());
344    }
345
346    #[test]
347    fn test_find_agents_by_task_type() {
348        let mut registry = AgentRegistry::new();
349        let agent = Arc::new(TestAgent {
350            id: "test-agent".to_string(),
351            name: "Test Agent".to_string(),
352            description: "A test agent".to_string(),
353            task_types: vec![TaskType::CodeReview],
354        });
355
356        registry.register(agent);
357        let agents = registry.find_agents_by_task_type(TaskType::CodeReview);
358        assert_eq!(agents.len(), 1);
359    }
360
361    #[test]
362    fn test_get_agent_metadata() {
363        let mut registry = AgentRegistry::new();
364        let agent = Arc::new(TestAgent {
365            id: "test-agent".to_string(),
366            name: "Test Agent".to_string(),
367            description: "A test agent for metadata".to_string(),
368            task_types: vec![TaskType::CodeReview, TaskType::SecurityAnalysis],
369        });
370
371        registry.register(agent);
372        let metadata = registry.get_agent_metadata("test-agent");
373        assert!(metadata.is_some());
374
375        let meta = metadata.unwrap();
376        assert_eq!(meta.id, "test-agent");
377        assert_eq!(meta.name, "Test Agent");
378        assert_eq!(meta.description, "A test agent for metadata");
379        assert_eq!(meta.supported_task_types.len(), 2);
380        assert!(meta.supported_task_types.contains(&TaskType::CodeReview));
381        assert!(meta
382            .supported_task_types
383            .contains(&TaskType::SecurityAnalysis));
384    }
385
386    #[test]
387    fn test_all_agent_metadata() {
388        let mut registry = AgentRegistry::new();
389        let agent1 = Arc::new(TestAgent {
390            id: "agent-1".to_string(),
391            name: "Agent 1".to_string(),
392            description: "First agent".to_string(),
393            task_types: vec![TaskType::CodeReview],
394        });
395        let agent2 = Arc::new(TestAgent {
396            id: "agent-2".to_string(),
397            name: "Agent 2".to_string(),
398            description: "Second agent".to_string(),
399            task_types: vec![TaskType::TestGeneration],
400        });
401
402        registry.register(agent1);
403        registry.register(agent2);
404
405        let all_metadata = registry.all_agent_metadata();
406        assert_eq!(all_metadata.len(), 2);
407    }
408
409    #[test]
410    fn test_has_agent() {
411        let mut registry = AgentRegistry::new();
412        let agent = Arc::new(TestAgent {
413            id: "test-agent".to_string(),
414            name: "Test Agent".to_string(),
415            description: "A test agent".to_string(),
416            task_types: vec![TaskType::CodeReview],
417        });
418
419        registry.register(agent);
420        assert!(registry.has_agent("test-agent"));
421        assert!(!registry.has_agent("nonexistent"));
422    }
423
424    #[test]
425    fn test_agents_for_task_type() {
426        let mut registry = AgentRegistry::new();
427        let agent1 = Arc::new(TestAgent {
428            id: "agent-1".to_string(),
429            name: "Agent 1".to_string(),
430            description: "First agent".to_string(),
431            task_types: vec![TaskType::CodeReview],
432        });
433        let agent2 = Arc::new(TestAgent {
434            id: "agent-2".to_string(),
435            name: "Agent 2".to_string(),
436            description: "Second agent".to_string(),
437            task_types: vec![TaskType::CodeReview, TaskType::SecurityAnalysis],
438        });
439
440        registry.register(agent1);
441        registry.register(agent2);
442
443        let code_review_agents = registry.agents_for_task_type(TaskType::CodeReview);
444        assert_eq!(code_review_agents.len(), 2);
445
446        let security_agents = registry.agents_for_task_type(TaskType::SecurityAnalysis);
447        assert_eq!(security_agents.len(), 1);
448
449        let doc_agents = registry.agents_for_task_type(TaskType::Documentation);
450        assert_eq!(doc_agents.len(), 0);
451    }
452
453    #[test]
454    fn test_multiple_agents_same_task_type() {
455        let mut registry = AgentRegistry::new();
456        let agent1 = Arc::new(TestAgent {
457            id: "agent-1".to_string(),
458            name: "Agent 1".to_string(),
459            description: "First agent".to_string(),
460            task_types: vec![TaskType::CodeReview],
461        });
462        let agent2 = Arc::new(TestAgent {
463            id: "agent-2".to_string(),
464            name: "Agent 2".to_string(),
465            description: "Second agent".to_string(),
466            task_types: vec![TaskType::CodeReview],
467        });
468
469        registry.register(agent1);
470        registry.register(agent2);
471
472        let agents = registry.find_agents_by_task_type(TaskType::CodeReview);
473        assert_eq!(agents.len(), 2);
474    }
475
476    #[test]
477    fn test_agent_metadata_serialization() {
478        let metadata = AgentMetadataInfo {
479            id: "test-agent".to_string(),
480            name: "Test Agent".to_string(),
481            description: "A test agent".to_string(),
482            supported_task_types: vec![TaskType::CodeReview, TaskType::SecurityAnalysis],
483        };
484
485        let json = serde_json::to_string(&metadata).expect("serialization failed");
486        let deserialized: AgentMetadataInfo =
487            serde_json::from_str(&json).expect("deserialization failed");
488
489        assert_eq!(deserialized.id, metadata.id);
490        assert_eq!(deserialized.name, metadata.name);
491        assert_eq!(deserialized.description, metadata.description);
492        assert_eq!(
493            deserialized.supported_task_types,
494            metadata.supported_task_types
495        );
496    }
497
498    #[test]
499    fn test_discover_builtin_agents() {
500        let mut registry = AgentRegistry::new();
501        let result = registry.discover_builtin_agents();
502        assert!(result.is_ok());
503    }
504
505    #[test]
506    fn test_load_configuration() {
507        let mut registry = AgentRegistry::new();
508        let mut config = HashMap::new();
509        config.insert("agent-1".to_string(), serde_json::json!({"enabled": true}));
510
511        let result = registry.load_configuration(config);
512        assert!(result.is_ok());
513    }
514
515    #[test]
516    fn test_supported_task_types() {
517        let mut registry = AgentRegistry::new();
518        let agent1 = Arc::new(TestAgent {
519            id: "agent-1".to_string(),
520            name: "Agent 1".to_string(),
521            description: "First agent".to_string(),
522            task_types: vec![TaskType::CodeReview],
523        });
524        let agent2 = Arc::new(TestAgent {
525            id: "agent-2".to_string(),
526            name: "Agent 2".to_string(),
527            description: "Second agent".to_string(),
528            task_types: vec![TaskType::TestGeneration, TaskType::SecurityAnalysis],
529        });
530
531        registry.register(agent1);
532        registry.register(agent2);
533
534        let supported = registry.supported_task_types();
535        assert!(supported.contains(&TaskType::CodeReview));
536        assert!(supported.contains(&TaskType::TestGeneration));
537        assert!(supported.contains(&TaskType::SecurityAnalysis));
538        assert!(!supported.contains(&TaskType::Documentation));
539    }
540
541    #[test]
542    fn test_registry_empty_discovery() {
543        let registry = AgentRegistry::new();
544        assert_eq!(registry.agent_count(), 0);
545        assert!(registry.all_agent_metadata().is_empty());
546        assert!(registry.supported_task_types().is_empty());
547    }
548
549    #[test]
550    fn test_registry_with_multiple_agents() {
551        let mut registry = AgentRegistry::new();
552        let agents: Vec<Arc<dyn Agent>> = (1..=5)
553            .map(|i| {
554                Arc::new(TestAgent {
555                    id: format!("agent-{}", i),
556                    name: format!("Agent {}", i),
557                    description: format!("Test agent {}", i),
558                    task_types: vec![TaskType::CodeReview],
559                }) as Arc<dyn Agent>
560            })
561            .collect();
562
563        for agent in agents {
564            registry.register(agent);
565        }
566
567        assert_eq!(registry.agent_count(), 5);
568        assert_eq!(registry.all_agent_metadata().len(), 5);
569    }
570}