ricecoder_agents/
models.rs

1//! Data models for the agent framework
2//!
3//! This module contains all the data structures used for agent communication,
4//! configuration, and result reporting.
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::path::PathBuf;
9
10/// Configuration for an agent
11///
12/// This struct holds the configuration settings for an agent, including whether
13/// it's enabled and any custom settings specific to that agent.
14///
15/// # Examples
16///
17/// ```ignore
18/// use ricecoder_agents::AgentConfig;
19/// use std::collections::HashMap;
20///
21/// let mut config = AgentConfig::default();
22/// config.enabled = true;
23/// config.settings.insert("timeout".to_string(), serde_json::json!(5000));
24/// ```
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct AgentConfig {
27    /// Whether the agent is enabled
28    pub enabled: bool,
29    /// Custom settings for the agent
30    pub settings: HashMap<String, serde_json::Value>,
31}
32
33impl Default for AgentConfig {
34    fn default() -> Self {
35        Self {
36            enabled: true,
37            settings: HashMap::new(),
38        }
39    }
40}
41
42/// Input for an agent task
43///
44/// This struct contains all the information needed for an agent to execute a task,
45/// including the task itself, project context, and configuration.
46///
47/// # Examples
48///
49/// ```ignore
50/// use ricecoder_agents::{AgentInput, AgentTask, ProjectContext, AgentConfig, TaskType, TaskTarget, TaskScope};
51/// use std::path::PathBuf;
52///
53/// let input = AgentInput {
54///     task: AgentTask {
55///         id: "task-1".to_string(),
56///         task_type: TaskType::CodeReview,
57///         target: TaskTarget {
58///             files: vec![PathBuf::from("src/main.rs")],
59///             scope: TaskScope::File,
60///         },
61///         options: Default::default(),
62///     },
63///     context: ProjectContext {
64///         name: "my-project".to_string(),
65///         root: PathBuf::from("/path/to/project"),
66///     },
67///     config: AgentConfig::default(),
68/// };
69/// ```
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct AgentInput {
72    /// The task to execute
73    pub task: AgentTask,
74    /// Project context
75    pub context: ProjectContext,
76    /// Agent configuration
77    pub config: AgentConfig,
78}
79
80/// A task for an agent to execute
81///
82/// This struct represents a single task that an agent should perform. It includes
83/// the task type, target files/scope, and any additional options.
84///
85/// # Examples
86///
87/// ```ignore
88/// use ricecoder_agents::{AgentTask, TaskType, TaskTarget, TaskScope};
89/// use std::path::PathBuf;
90///
91/// let task = AgentTask {
92///     id: "task-1".to_string(),
93///     task_type: TaskType::CodeReview,
94///     target: TaskTarget {
95///         files: vec![PathBuf::from("src/main.rs")],
96///         scope: TaskScope::File,
97///     },
98///     options: Default::default(),
99/// };
100/// ```
101#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct AgentTask {
103    /// Unique task identifier
104    pub id: String,
105    /// Type of task
106    pub task_type: TaskType,
107    /// Target for the task
108    pub target: TaskTarget,
109    /// Task options
110    pub options: TaskOptions,
111}
112
113/// Type of task an agent can perform
114///
115/// This enum defines the different types of tasks that agents can handle.
116/// Each task type represents a specific kind of analysis or transformation.
117#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
118pub enum TaskType {
119    /// Code review task - analyzes code for quality, security, and best practices
120    CodeReview,
121    /// Test generation task - generates tests for code
122    TestGeneration,
123    /// Documentation task - generates or updates documentation
124    Documentation,
125    /// Refactoring task - suggests or performs code refactoring
126    Refactoring,
127    /// Security analysis task - analyzes code for security vulnerabilities
128    SecurityAnalysis,
129}
130
131/// Target for a task
132///
133/// This struct specifies which files and what scope a task should operate on.
134#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct TaskTarget {
136    /// Files to target
137    pub files: Vec<PathBuf>,
138    /// Scope of the task
139    pub scope: TaskScope,
140}
141
142/// Scope of a task
143///
144/// This enum defines the scope at which a task operates.
145#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
146pub enum TaskScope {
147    /// Single file scope - task operates on individual files
148    File,
149    /// Module scope - task operates on a module or directory
150    Module,
151    /// Project scope - task operates on the entire project
152    Project,
153}
154
155/// Options for a task
156///
157/// This struct holds custom options that can be passed to a task.
158/// Options are stored as JSON values for flexibility.
159#[derive(Debug, Clone, Default, Serialize, Deserialize)]
160pub struct TaskOptions {
161    /// Custom options
162    pub custom: HashMap<String, serde_json::Value>,
163}
164
165/// Project context for agent execution
166///
167/// This struct provides context about the project that an agent is working on.
168/// It includes the project name and root directory.
169#[derive(Debug, Clone, Serialize, Deserialize)]
170pub struct ProjectContext {
171    /// Project name
172    pub name: String,
173    /// Project root path
174    pub root: PathBuf,
175}
176
177/// Output from an agent
178///
179/// This struct contains all the results produced by an agent execution,
180/// including findings, suggestions, generated content, and metadata.
181///
182/// # Examples
183///
184/// ```ignore
185/// use ricecoder_agents::{AgentOutput, Finding, Severity};
186///
187/// let output = AgentOutput {
188///     findings: vec![Finding {
189///         id: "finding-1".to_string(),
190///         severity: Severity::Warning,
191///         category: "quality".to_string(),
192///         message: "Function is too long".to_string(),
193///         location: None,
194///         suggestion: Some("Consider breaking into smaller functions".to_string()),
195///     }],
196///     suggestions: vec![],
197///     generated: vec![],
198///     metadata: Default::default(),
199/// };
200/// ```
201#[derive(Debug, Clone, Default, Serialize, Deserialize)]
202pub struct AgentOutput {
203    /// Findings from the agent
204    pub findings: Vec<Finding>,
205    /// Suggestions from the agent
206    pub suggestions: Vec<Suggestion>,
207    /// Generated content
208    pub generated: Vec<GeneratedContent>,
209    /// Metadata about the execution
210    pub metadata: AgentMetadata,
211}
212
213/// A finding from an agent
214///
215/// This struct represents a single finding or issue discovered by an agent.
216/// Findings can include code quality issues, security vulnerabilities, or other observations.
217#[derive(Debug, Clone, Serialize, Deserialize)]
218pub struct Finding {
219    /// Unique finding identifier
220    pub id: String,
221    /// Severity level
222    pub severity: Severity,
223    /// Category of the finding
224    pub category: String,
225    /// Message describing the finding
226    pub message: String,
227    /// Location in code (optional)
228    pub location: Option<CodeLocation>,
229    /// Suggested fix (optional)
230    pub suggestion: Option<String>,
231}
232
233/// Severity level of a finding
234///
235/// This enum defines the severity levels for findings, ordered from least to most severe.
236#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
237pub enum Severity {
238    /// Informational - not a problem, just information
239    Info,
240    /// Warning - potential issue that should be addressed
241    Warning,
242    /// Critical issue - must be fixed
243    Critical,
244}
245
246/// Location in code
247///
248/// This struct specifies a location in source code using file path, line, and column.
249#[derive(Debug, Clone, Serialize, Deserialize)]
250pub struct CodeLocation {
251    /// File path
252    pub file: PathBuf,
253    /// Line number
254    pub line: usize,
255    /// Column number
256    pub column: usize,
257}
258
259/// A suggestion from an agent
260///
261/// This struct represents a suggestion for improvement, which may include
262/// a diff showing the proposed changes and whether it can be auto-fixed.
263#[derive(Debug, Clone, Serialize, Deserialize)]
264pub struct Suggestion {
265    /// Unique suggestion identifier
266    pub id: String,
267    /// Description of the suggestion
268    pub description: String,
269    /// Diff (optional)
270    pub diff: Option<FileDiff>,
271    /// Whether the suggestion can be auto-fixed
272    pub auto_fixable: bool,
273}
274
275/// A file diff
276///
277/// This struct represents a unified diff for a file, showing the proposed changes.
278#[derive(Debug, Clone, Serialize, Deserialize)]
279pub struct FileDiff {
280    /// File path
281    pub file: PathBuf,
282    /// Diff content
283    pub content: String,
284}
285
286/// Generated content from an agent
287///
288/// This struct represents content that was generated by an agent,
289/// such as new files or code snippets.
290#[derive(Debug, Clone, Serialize, Deserialize)]
291pub struct GeneratedContent {
292    /// File path for the generated content
293    pub file: PathBuf,
294    /// Content
295    pub content: String,
296}
297
298/// Metadata about agent execution
299///
300/// This struct contains metadata about a single agent execution,
301/// including execution time and resource usage.
302#[derive(Debug, Clone, Default, Serialize, Deserialize)]
303pub struct AgentMetadata {
304    /// Agent identifier
305    pub agent_id: String,
306    /// Execution time in milliseconds
307    pub execution_time_ms: u64,
308    /// Tokens used (if applicable)
309    pub tokens_used: usize,
310}
311
312/// Metrics for an agent
313///
314/// This struct contains performance metrics for an agent across multiple executions.
315#[derive(Debug, Clone, Default)]
316pub struct AgentMetrics {
317    /// Total execution count
318    pub execution_count: u64,
319    /// Successful executions
320    pub success_count: u64,
321    /// Failed executions
322    pub error_count: u64,
323    /// Average duration in milliseconds
324    pub avg_duration_ms: f64,
325}
326
327/// Configuration schema for an agent
328///
329/// This struct defines the configuration schema for an agent,
330/// describing what configuration options are available.
331#[derive(Debug, Clone, Default, Serialize, Deserialize)]
332pub struct ConfigSchema {
333    /// Schema properties
334    pub properties: HashMap<String, serde_json::Value>,
335}
336
337#[cfg(test)]
338mod tests {
339    use super::*;
340
341    #[test]
342    fn test_agent_config_default() {
343        let config = AgentConfig::default();
344        assert!(config.enabled);
345        assert!(config.settings.is_empty());
346    }
347
348    #[test]
349    fn test_agent_config_serialization() {
350        let config = AgentConfig {
351            enabled: true,
352            settings: {
353                let mut map = HashMap::new();
354                map.insert("key".to_string(), serde_json::json!("value"));
355                map
356            },
357        };
358
359        let json = serde_json::to_string(&config).expect("serialization failed");
360        let deserialized: AgentConfig =
361            serde_json::from_str(&json).expect("deserialization failed");
362
363        assert_eq!(deserialized.enabled, config.enabled);
364        assert_eq!(deserialized.settings, config.settings);
365    }
366
367    #[test]
368    fn test_agent_output_default() {
369        let output = AgentOutput::default();
370        assert!(output.findings.is_empty());
371        assert!(output.suggestions.is_empty());
372        assert!(output.generated.is_empty());
373    }
374
375    #[test]
376    fn test_agent_output_serialization() {
377        let output = AgentOutput {
378            findings: vec![Finding {
379                id: "finding-1".to_string(),
380                severity: Severity::Warning,
381                category: "quality".to_string(),
382                message: "Test finding".to_string(),
383                location: None,
384                suggestion: None,
385            }],
386            suggestions: vec![],
387            generated: vec![],
388            metadata: AgentMetadata::default(),
389        };
390
391        let json = serde_json::to_string(&output).expect("serialization failed");
392        let deserialized: AgentOutput =
393            serde_json::from_str(&json).expect("deserialization failed");
394
395        assert_eq!(deserialized.findings.len(), 1);
396        assert_eq!(deserialized.findings[0].id, "finding-1");
397        assert_eq!(deserialized.findings[0].severity, Severity::Warning);
398    }
399
400    #[test]
401    fn test_severity_ordering() {
402        assert!(Severity::Info < Severity::Warning);
403        assert!(Severity::Warning < Severity::Critical);
404        assert!(Severity::Info < Severity::Critical);
405    }
406
407    #[test]
408    fn test_task_type_serialization() {
409        let task_type = TaskType::CodeReview;
410        let json = serde_json::to_string(&task_type).expect("serialization failed");
411        let deserialized: TaskType = serde_json::from_str(&json).expect("deserialization failed");
412
413        assert_eq!(deserialized, task_type);
414    }
415
416    #[test]
417    fn test_task_scope_serialization() {
418        let scope = TaskScope::Module;
419        let json = serde_json::to_string(&scope).expect("serialization failed");
420        let deserialized: TaskScope = serde_json::from_str(&json).expect("deserialization failed");
421
422        assert_eq!(deserialized, scope);
423    }
424
425    #[test]
426    fn test_finding_with_location() {
427        let finding = Finding {
428            id: "finding-1".to_string(),
429            severity: Severity::Critical,
430            category: "security".to_string(),
431            message: "Security vulnerability".to_string(),
432            location: Some(CodeLocation {
433                file: PathBuf::from("src/main.rs"),
434                line: 42,
435                column: 10,
436            }),
437            suggestion: Some("Use safe alternative".to_string()),
438        };
439
440        assert_eq!(finding.id, "finding-1");
441        assert_eq!(finding.severity, Severity::Critical);
442        assert!(finding.location.is_some());
443        assert!(finding.suggestion.is_some());
444
445        let location = finding.location.unwrap();
446        assert_eq!(location.line, 42);
447        assert_eq!(location.column, 10);
448    }
449
450    #[test]
451    fn test_suggestion_auto_fixable() {
452        let suggestion = Suggestion {
453            id: "suggestion-1".to_string(),
454            description: "Fix naming".to_string(),
455            diff: None,
456            auto_fixable: true,
457        };
458
459        assert!(suggestion.auto_fixable);
460    }
461
462    #[test]
463    fn test_agent_metadata_serialization() {
464        let metadata = AgentMetadata {
465            agent_id: "test-agent".to_string(),
466            execution_time_ms: 1000,
467            tokens_used: 500,
468        };
469
470        let json = serde_json::to_string(&metadata).expect("serialization failed");
471        let deserialized: AgentMetadata =
472            serde_json::from_str(&json).expect("deserialization failed");
473
474        assert_eq!(deserialized.agent_id, "test-agent");
475        assert_eq!(deserialized.execution_time_ms, 1000);
476        assert_eq!(deserialized.tokens_used, 500);
477    }
478
479    #[test]
480    fn test_agent_metrics_default() {
481        let metrics = AgentMetrics::default();
482        assert_eq!(metrics.execution_count, 0);
483        assert_eq!(metrics.success_count, 0);
484        assert_eq!(metrics.error_count, 0);
485        assert_eq!(metrics.avg_duration_ms, 0.0);
486    }
487
488    #[test]
489    fn test_config_schema_default() {
490        let schema = ConfigSchema::default();
491        assert!(schema.properties.is_empty());
492    }
493
494    #[test]
495    fn test_task_target_multiple_files() {
496        let target = TaskTarget {
497            files: vec![
498                PathBuf::from("src/main.rs"),
499                PathBuf::from("src/lib.rs"),
500                PathBuf::from("tests/test.rs"),
501            ],
502            scope: TaskScope::Project,
503        };
504
505        assert_eq!(target.files.len(), 3);
506        assert_eq!(target.scope, TaskScope::Project);
507    }
508
509    #[test]
510    fn test_project_context() {
511        let context = ProjectContext {
512            name: "my-project".to_string(),
513            root: PathBuf::from("/home/user/projects/my-project"),
514        };
515
516        assert_eq!(context.name, "my-project");
517        assert_eq!(
518            context.root,
519            PathBuf::from("/home/user/projects/my-project")
520        );
521    }
522
523    #[test]
524    fn test_generated_content() {
525        let content = GeneratedContent {
526            file: PathBuf::from("generated/test.rs"),
527            content: "// Generated code".to_string(),
528        };
529
530        assert_eq!(content.file, PathBuf::from("generated/test.rs"));
531        assert_eq!(content.content, "// Generated code");
532    }
533}