kowalski_code_agent/
agent.rs1use 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#[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 pub async fn new(_config: Config) -> Result<Self, KowalskiError> {
33 let code_config = CodeAgentConfig::default();
35
36 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 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 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 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}