ricecoder_agents/agents/
mod.rs

1//! Agent trait and implementations
2
3pub mod backend;
4pub mod code_review;
5pub mod devops;
6pub mod web;
7
8use crate::error::Result;
9use crate::models::{AgentInput, AgentMetrics, AgentOutput, ConfigSchema, TaskType};
10use async_trait::async_trait;
11
12pub use backend::BackendAgent;
13pub use code_review::CodeReviewAgent;
14pub use devops::DevOpsAgent;
15pub use web::WebAgent;
16
17/// Trait that all agents must implement
18///
19/// The `Agent` trait defines the interface for specialized agents that perform different tasks
20/// within the RiceCoder framework. All agents must implement this trait to be registered and
21/// executed by the orchestrator.
22///
23/// # Examples
24///
25/// ```ignore
26/// use ricecoder_agents::{Agent, AgentInput, AgentOutput};
27/// use async_trait::async_trait;
28///
29/// struct MyAgent;
30///
31/// #[async_trait]
32/// impl Agent for MyAgent {
33///     fn id(&self) -> &str {
34///         "my-agent"
35///     }
36///
37///     fn name(&self) -> &str {
38///         "My Agent"
39///     }
40///
41///     fn description(&self) -> &str {
42///         "A custom agent for specific tasks"
43///     }
44///
45///     fn supports(&self, task_type: TaskType) -> bool {
46///         matches!(task_type, TaskType::CodeReview)
47///     }
48///
49///     async fn execute(&self, input: AgentInput) -> Result<AgentOutput> {
50///         // Implement agent logic here
51///         Ok(AgentOutput::default())
52///     }
53/// }
54/// ```
55#[async_trait]
56pub trait Agent: Send + Sync {
57    /// Get the agent's unique identifier
58    ///
59    /// The ID should be a stable, unique identifier for this agent that can be used
60    /// to look up the agent in the registry.
61    ///
62    /// # Returns
63    ///
64    /// A string slice containing the agent's unique identifier
65    fn id(&self) -> &str;
66
67    /// Get the agent's human-readable name
68    ///
69    /// The name is used for display purposes and should be descriptive but concise.
70    ///
71    /// # Returns
72    ///
73    /// A string slice containing the agent's human-readable name
74    fn name(&self) -> &str;
75
76    /// Get the agent's description
77    ///
78    /// The description provides more detailed information about what the agent does
79    /// and can be used for help text or documentation.
80    ///
81    /// # Returns
82    ///
83    /// A string slice containing the agent's description
84    fn description(&self) -> &str;
85
86    /// Check if the agent supports a specific task type
87    ///
88    /// This method is used by the registry to determine which agents can handle
89    /// specific task types. An agent can support multiple task types.
90    ///
91    /// # Arguments
92    ///
93    /// * `task_type` - The task type to check support for
94    ///
95    /// # Returns
96    ///
97    /// `true` if the agent supports the given task type, `false` otherwise
98    fn supports(&self, task_type: TaskType) -> bool;
99
100    /// Execute the agent with the given input
101    ///
102    /// This is the main method that performs the agent's work. It should be async
103    /// to support non-blocking I/O and streaming responses.
104    ///
105    /// # Arguments
106    ///
107    /// * `input` - The input containing the task, context, and configuration
108    ///
109    /// # Returns
110    ///
111    /// A `Result` containing the agent's output or an error
112    ///
113    /// # Errors
114    ///
115    /// Returns an error if the agent execution fails for any reason
116    async fn execute(&self, input: AgentInput) -> Result<AgentOutput>;
117
118    /// Get the agent's configuration schema
119    ///
120    /// This method returns a JSON schema describing the configuration options
121    /// that the agent accepts. This is used for validation and documentation.
122    ///
123    /// # Returns
124    ///
125    /// A `ConfigSchema` describing the agent's configuration options
126    fn config_schema(&self) -> ConfigSchema {
127        ConfigSchema::default()
128    }
129
130    /// Get the agent's performance metrics
131    ///
132    /// This method returns metrics about the agent's performance, including
133    /// execution counts, success rates, and average execution time.
134    ///
135    /// # Returns
136    ///
137    /// An `AgentMetrics` struct containing performance metrics
138    fn metrics(&self) -> AgentMetrics {
139        AgentMetrics::default()
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146    use crate::models::{
147        AgentConfig, AgentTask, ProjectContext, TaskOptions, TaskScope, TaskTarget,
148    };
149    use std::path::PathBuf;
150
151    /// Mock agent for testing
152    struct MockAgent {
153        id: String,
154        name: String,
155        description: String,
156        supported_types: Vec<TaskType>,
157    }
158
159    #[async_trait]
160    impl Agent for MockAgent {
161        fn id(&self) -> &str {
162            &self.id
163        }
164
165        fn name(&self) -> &str {
166            &self.name
167        }
168
169        fn description(&self) -> &str {
170            &self.description
171        }
172
173        fn supports(&self, task_type: TaskType) -> bool {
174            self.supported_types.contains(&task_type)
175        }
176
177        async fn execute(&self, _input: AgentInput) -> Result<AgentOutput> {
178            Ok(AgentOutput::default())
179        }
180    }
181
182    #[test]
183    fn test_agent_trait_implementation() {
184        let agent = MockAgent {
185            id: "test-agent".to_string(),
186            name: "Test Agent".to_string(),
187            description: "A test agent".to_string(),
188            supported_types: vec![TaskType::CodeReview],
189        };
190
191        assert_eq!(agent.id(), "test-agent");
192        assert_eq!(agent.name(), "Test Agent");
193        assert_eq!(agent.description(), "A test agent");
194        assert!(agent.supports(TaskType::CodeReview));
195        assert!(!agent.supports(TaskType::TestGeneration));
196    }
197
198    #[test]
199    fn test_agent_default_metrics() {
200        let agent = MockAgent {
201            id: "test-agent".to_string(),
202            name: "Test Agent".to_string(),
203            description: "A test agent".to_string(),
204            supported_types: vec![],
205        };
206
207        let metrics = agent.metrics();
208        assert_eq!(metrics.execution_count, 0);
209        assert_eq!(metrics.success_count, 0);
210        assert_eq!(metrics.error_count, 0);
211        assert_eq!(metrics.avg_duration_ms, 0.0);
212    }
213
214    #[test]
215    fn test_agent_default_config_schema() {
216        let agent = MockAgent {
217            id: "test-agent".to_string(),
218            name: "Test Agent".to_string(),
219            description: "A test agent".to_string(),
220            supported_types: vec![],
221        };
222
223        let schema = agent.config_schema();
224        assert!(schema.properties.is_empty());
225    }
226
227    #[tokio::test]
228    async fn test_agent_execute() {
229        let agent = MockAgent {
230            id: "test-agent".to_string(),
231            name: "Test Agent".to_string(),
232            description: "A test agent".to_string(),
233            supported_types: vec![TaskType::CodeReview],
234        };
235
236        let input = AgentInput {
237            task: AgentTask {
238                id: "task-1".to_string(),
239                task_type: TaskType::CodeReview,
240                target: TaskTarget {
241                    files: vec![PathBuf::from("test.rs")],
242                    scope: TaskScope::File,
243                },
244                options: TaskOptions::default(),
245            },
246            context: ProjectContext {
247                name: "test-project".to_string(),
248                root: PathBuf::from("/tmp/test"),
249            },
250            config: AgentConfig::default(),
251        };
252
253        let result = agent.execute(input).await;
254        assert!(result.is_ok());
255
256        let output = result.unwrap();
257        assert!(output.findings.is_empty());
258        assert!(output.suggestions.is_empty());
259        assert!(output.generated.is_empty());
260    }
261
262    #[test]
263    fn test_multiple_supported_task_types() {
264        let agent = MockAgent {
265            id: "multi-agent".to_string(),
266            name: "Multi Agent".to_string(),
267            description: "An agent supporting multiple task types".to_string(),
268            supported_types: vec![
269                TaskType::CodeReview,
270                TaskType::SecurityAnalysis,
271                TaskType::Refactoring,
272            ],
273        };
274
275        assert!(agent.supports(TaskType::CodeReview));
276        assert!(agent.supports(TaskType::SecurityAnalysis));
277        assert!(agent.supports(TaskType::Refactoring));
278        assert!(!agent.supports(TaskType::TestGeneration));
279        assert!(!agent.supports(TaskType::Documentation));
280    }
281}