kowalski_code_agent/
agent.rs

1use crate::config::CodeAgentConfig;
2use async_trait::async_trait;
3use kowalski_agent_template::TemplateAgent;
4use kowalski_agent_template::templates::general::GeneralTemplate;
5use kowalski_core::agent::Agent;
6use kowalski_core::config::Config;
7use kowalski_core::conversation::Conversation;
8use kowalski_core::error::KowalskiError;
9use kowalski_core::role::Role;
10use kowalski_core::tools::{Tool, ToolInput};
11use kowalski_tools::code::{JavaAnalysisTool, PythonAnalysisTool, RustAnalysisTool};
12use serde::{Deserialize, Serialize};
13
14/// CodeAgent: A specialized agent for code analysis and development tasks
15/// This agent is built on top of the TemplateAgent and provides code-specific functionality
16#[allow(dead_code)]
17pub struct CodeAgent {
18    agent: TemplateAgent,
19    config: CodeAgentConfig,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct CodeAnalysisResult {
24    pub language: String,
25    pub metrics: serde_json::Value,
26    pub suggestions: Vec<String>,
27    pub issues: Vec<String>,
28}
29
30impl CodeAgent {
31    /// Creates a new CodeAgent with the specified configuration
32    pub async fn new(_config: Config) -> Result<Self, KowalskiError> {
33        // TODO: Convert Config to CodeAgentConfig if needed
34        let code_config = CodeAgentConfig::default();
35
36        // Create language-specific analysis tools
37        let java_tool = JavaAnalysisTool::new();
38        let python_tool = PythonAnalysisTool::new();
39        let rust_tool = RustAnalysisTool::new();
40
41        let tools: Vec<Box<dyn Tool + Send + Sync>> = vec![
42            Box::new(java_tool),
43            Box::new(python_tool),
44            Box::new(rust_tool),
45        ];
46
47        let builder = GeneralTemplate::create_agent(
48            tools,
49            Some("You are a code analysis and development assistant specialized in analyzing, refactoring, and documenting code. You have access to java_analysis, python_analysis, and rust_analysis tools. Use them to answer questions about code.".to_string()),
50            Some(0.7),
51        )
52        .await
53        .map_err(|e| KowalskiError::Configuration(e.to_string()))?;
54        let agent = builder.build().await?;
55
56        Ok(Self {
57            agent,
58            config: code_config,
59        })
60    }
61
62    /// Analyzes Java code
63    pub async fn analyze_java(&self, code: &str) -> Result<CodeAnalysisResult, KowalskiError> {
64        let mut tools = self.agent.tool_chain.write().await;
65        let tool = tools.iter_mut().find(|t| t.name() == "java_analysis");
66        let tool = match tool {
67            Some(t) => t,
68            None => {
69                return Err(KowalskiError::ToolExecution(
70                    "java_analysis tool not found".to_string(),
71                ));
72            }
73        };
74        let input = ToolInput::new(
75            "analyze_java".to_string(),
76            code.to_string(),
77            serde_json::json!({}),
78        );
79        let output = tool.execute(input).await?;
80
81        let result = output.result;
82        Ok(CodeAnalysisResult {
83            language: "java".to_string(),
84            metrics: result["metrics"].clone(),
85            suggestions: result["suggestions"]
86                .as_array()
87                .unwrap_or(&Vec::new())
88                .iter()
89                .filter_map(|v| v.as_str().map(|s| s.to_string()))
90                .collect(),
91            issues: result["syntax_errors"]
92                .as_array()
93                .unwrap_or(&Vec::new())
94                .iter()
95                .filter_map(|v| v.as_str().map(|s| s.to_string()))
96                .collect(),
97        })
98    }
99
100    /// Analyzes Python code
101    pub async fn analyze_python(&self, code: &str) -> Result<CodeAnalysisResult, KowalskiError> {
102        let mut tools = self.agent.tool_chain.write().await;
103        let tool = tools.iter_mut().find(|t| t.name() == "python_analysis");
104        let tool = match tool {
105            Some(t) => t,
106            None => {
107                return Err(KowalskiError::ToolExecution(
108                    "python_analysis tool not found".to_string(),
109                ));
110            }
111        };
112        let input = ToolInput::new(
113            "analyze_python".to_string(),
114            code.to_string(),
115            serde_json::json!({}),
116        );
117        let output = tool.execute(input).await?;
118
119        let result = output.result;
120        Ok(CodeAnalysisResult {
121            language: "python".to_string(),
122            metrics: result["metrics"].clone(),
123            suggestions: result["suggestions"]
124                .as_array()
125                .unwrap_or(&Vec::new())
126                .iter()
127                .filter_map(|v| v.as_str().map(|s| s.to_string()))
128                .collect(),
129            issues: result["pep8_issues"]
130                .as_array()
131                .unwrap_or(&Vec::new())
132                .iter()
133                .filter_map(|v| v.as_str().map(|s| s.to_string()))
134                .collect(),
135        })
136    }
137
138    /// Analyzes Rust code
139    pub async fn analyze_rust(&self, code: &str) -> Result<CodeAnalysisResult, KowalskiError> {
140        let mut tools = self.agent.tool_chain.write().await;
141        let tool = tools.iter_mut().find(|t| t.name() == "rust_analysis");
142        let tool = match tool {
143            Some(t) => t,
144            None => {
145                return Err(KowalskiError::ToolExecution(
146                    "rust_analysis tool not found".to_string(),
147                ));
148            }
149        };
150        let input = ToolInput::new(
151            "analyze_rust".to_string(),
152            code.to_string(),
153            serde_json::json!({}),
154        );
155        let output = tool.execute(input).await?;
156
157        let result = output.result;
158        Ok(CodeAnalysisResult {
159            language: "rust".to_string(),
160            metrics: result["metrics"].clone(),
161            suggestions: result["suggestions"]
162                .as_array()
163                .unwrap_or(&Vec::new())
164                .iter()
165                .filter_map(|v| v.as_str().map(|s| s.to_string()))
166                .collect(),
167            issues: result["rust_issues"]
168                .as_array()
169                .unwrap_or(&Vec::new())
170                .iter()
171                .filter_map(|v| v.as_str().map(|s| s.to_string()))
172                .collect(),
173        })
174    }
175}
176
177#[async_trait]
178impl Agent for CodeAgent {
179    async fn new(config: Config) -> Result<Self, KowalskiError> {
180        CodeAgent::new(config).await
181    }
182
183    fn start_conversation(&mut self, model: &str) -> String {
184        self.agent.base_mut().start_conversation(model)
185    }
186
187    fn get_conversation(&self, id: &str) -> Option<&Conversation> {
188        self.agent.base().get_conversation(id)
189    }
190
191    fn list_conversations(&self) -> Vec<&Conversation> {
192        self.agent.base().list_conversations()
193    }
194
195    fn delete_conversation(&mut self, id: &str) -> bool {
196        self.agent.base_mut().delete_conversation(id)
197    }
198
199    async fn chat_with_history(
200        &mut self,
201        conversation_id: &str,
202        content: &str,
203        role: Option<Role>,
204    ) -> Result<reqwest::Response, KowalskiError> {
205        self.agent
206            .base_mut()
207            .chat_with_history(conversation_id, content, role)
208            .await
209    }
210
211    async fn process_stream_response(
212        &mut self,
213        conversation_id: &str,
214        chunk: &[u8],
215    ) -> Result<Option<kowalski_core::conversation::Message>, KowalskiError> {
216        self.agent
217            .base_mut()
218            .process_stream_response(conversation_id, chunk)
219            .await
220    }
221
222    async fn add_message(&mut self, conversation_id: &str, role: &str, content: &str) {
223        self.agent
224            .base_mut()
225            .add_message(conversation_id, role, content)
226            .await;
227    }
228
229    fn name(&self) -> &str {
230        self.agent.base().name()
231    }
232
233    fn description(&self) -> &str {
234        self.agent.base().description()
235    }
236}
237
238#[cfg(test)]
239mod tests {
240    use super::*;
241    use kowalski_core::config::Config;
242
243    #[tokio::test]
244    async fn test_code_agent_creation() {
245        let config = Config::default();
246        let agent = CodeAgent::new(config).await;
247        assert!(agent.is_ok());
248    }
249
250    #[tokio::test]
251    async fn test_code_agent_conversation() {
252        let config = Config::default();
253        let mut agent = CodeAgent::new(config).await.unwrap();
254        let conv_id = agent.start_conversation("test-model");
255        assert!(!conv_id.is_empty());
256    }
257}