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}