ai_agent/
cli.rs

1//! Command Line Interface for the AI-Native Code Agent
2
3use clap::{Parser, Subcommand};
4use std::io::{self, Write};
5use crate::config::AgentConfig;
6use crate::models::LanguageModel;
7
8#[derive(Parser)]
9#[command(name = "ai-agent")]
10#[command(about = "AI-Native Code Assistant")]
11#[command(version)]
12pub struct Cli {
13    #[command(subcommand)]
14    pub command: Commands,
15}
16
17#[derive(Subcommand)]
18pub enum Commands {
19    /// Process a single task
20    Task {
21        /// The task description
22        task: String,
23        /// Configuration file
24        #[arg(short, long, default_value = "config.toml")]
25        config: String,
26        /// Output format (text, json, verbose)
27        #[arg(short, long, default_value = "text")]
28        output: String,
29    },
30    /// Start interactive mode
31    Interactive {
32        /// Configuration file
33        #[arg(short, long, default_value = "config.toml")]
34        config: String,
35    },
36    /// List available tools
37    Tools {
38        /// Configuration file
39        #[arg(short, long, default_value = "config.toml")]
40        config: String,
41    },
42    /// Show configuration
43    Config {
44        /// Configuration file
45        #[arg(short, long, default_value = "config.toml")]
46        config: String,
47    },
48}
49
50impl Cli {
51    /// Run the CLI command
52    pub async fn run(self) -> anyhow::Result<()> {
53        match self.command {
54            Commands::Task { task, config, output } => {
55                Self::handle_task(task, config, output).await
56            }
57            Commands::Interactive { config } => {
58                Self::handle_interactive(config).await
59            }
60            Commands::Tools { config } => {
61                Self::handle_tools(config).await
62            }
63            Commands::Config { config } => {
64                Self::handle_config(config).await
65            }
66        }
67    }
68
69    async fn handle_task(task: String, config_path: String, output: String) -> anyhow::Result<()> {
70        println!("🚀 Starting AI Agent Task Execution");
71        println!("====================================");
72        println!("📝 Task: {}", task);
73        println!("⏰ Started at: {}", chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC"));
74        println!("");
75
76        tracing::info!("Processing task: {}", task);
77
78        println!("🔧 Loading configuration...");
79        let config = AgentConfig::load_with_fallback(&config_path)
80            .map_err(|e| anyhow::anyhow!("Failed to load config: {}", e))?;
81        println!("✅ Configuration loaded successfully");
82
83        println!("🤖 Initializing AI agent...");
84        let mut agent = create_agent(&config).await?;
85        let tools = agent.get_tools().await;
86        let tool_count = tools.lock().await.get_tool_names().len();
87        println!("✅ Agent initialized with {} tools", tool_count);
88
89        println!("🧠 Processing task with AI model...");
90        println!("📋 Creating task plan...");
91        let start_time = std::time::Instant::now();
92        let result = agent.process_task(&task).await;
93        let duration = start_time.elapsed();
94
95        println!("🏁 Task execution completed in {:.2}s", duration.as_secs_f32());
96        println!("====================================");
97
98        match result {
99            Ok(task_result) => {
100                println!("✅ Task Status: SUCCESS");
101                println!("⏱️  Execution Time: {}s", task_result.execution_time.unwrap_or(0));
102
103                match output.as_str() {
104                    "json" => {
105                        println!("📄 Output (JSON format):");
106                        println!("{}", serde_json::to_string_pretty(&task_result)?);
107                    }
108                    "verbose" => {
109                        println!("📋 Task Plan:");
110                        if let Some(plan) = &task_result.task_plan {
111                            println!("  🧠 Understanding: {}", plan.understanding);
112                            println!("  🛠️  Approach: {}", plan.approach);
113                            println!("  📊 Complexity: {:?}", plan.complexity);
114                            println!("  🔢 Estimated Steps: {}", plan.estimated_steps.unwrap_or(0));
115                            if !plan.requirements.is_empty() {
116                                println!("  📋 Requirements: {}", plan.requirements.join(", "));
117                            }
118                        }
119                        println!("");
120                        println!("📋 Summary:");
121                        println!("  {}", task_result.summary);
122                        if let Some(details) = task_result.details {
123                            println!("");
124                            println!("🔍 Detailed Results:");
125                            println!("  {}", details.replace('\n', "\n  "));
126                        }
127                        if task_result.execution_time.is_some() {
128                            println!("");
129                            println!("📊 Performance Metrics:");
130                            println!("  • Internal execution time: {}s", task_result.execution_time.unwrap_or(0));
131                            println!("  • Total wall-clock time: {:.2}s", duration.as_secs_f32());
132                        }
133                    }
134                    _ => {
135                        println!("📋 Result:");
136                        println!("{}", task_result.summary);
137                        if let Some(details) = task_result.details {
138                            println!("");
139                            println!("🔍 Details:");
140                            println!("{}", details);
141                        }
142                    }
143                }
144            }
145            Err(e) => {
146                println!("❌ Task Status: FAILED");
147                println!("🚨 Error Details:");
148                println!("  {}", e);
149            }
150        }
151
152        println!("====================================");
153        println!("🏁 Task execution finished at: {}", chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC"));
154        println!("");
155
156        Ok(())
157    }
158
159    async fn handle_interactive(config_path: String) -> anyhow::Result<()> {
160        let config = AgentConfig::load_with_fallback(&config_path)
161            .map_err(|e| anyhow::anyhow!("Failed to load config: {}", e))?;
162        let mut agent = create_agent(&config).await?;
163
164        println!("AI-Native Code Agent - Interactive Mode");
165        println!("Type 'exit' or 'quit' to exit");
166        println!("Type 'help' for available commands");
167        println!();
168
169        loop {
170            print!("> ");
171            io::stdout().flush()?;
172
173            let mut input = String::new();
174            io::stdin().read_line(&mut input)?;
175
176            let input = input.trim();
177            if input.is_empty() {
178                continue;
179            }
180
181            match input {
182                "exit" | "quit" => {
183                    println!("Goodbye!");
184                    break;
185                }
186                "help" => {
187                    Self::print_help();
188                }
189                "tools" => {
190                    Self::print_available_tools(&agent).await;
191                }
192                _ => {
193                    let result = agent.process_task(input).await;
194                    match result {
195                        Ok(task_result) => {
196                            println!("✅ {}", task_result.summary);
197                            if let Some(details) = task_result.details {
198                                println!("\nDetails:\n{}", details);
199                            }
200                        }
201                        Err(e) => {
202                            println!("❌ Error: {}", e);
203                        }
204                    }
205                    println!();
206                }
207            }
208        }
209
210        Ok(())
211    }
212
213    async fn handle_tools(config_path: String) -> anyhow::Result<()> {
214        let config = AgentConfig::load_with_fallback(&config_path)
215            .map_err(|e| anyhow::anyhow!("Failed to load config: {}", e))?;
216        let agent = create_agent(&config).await?;
217
218        Self::print_available_tools(&agent).await;
219        Ok(())
220    }
221
222    async fn handle_config(config_path: String) -> anyhow::Result<()> {
223        let config = AgentConfig::load_with_fallback(&config_path)
224            .map_err(|e| anyhow::anyhow!("Failed to load config: {}", e))?;
225        println!("Configuration:");
226        println!("{:#?}", config);
227        Ok(())
228    }
229
230    fn print_help() {
231        println!("Available commands:");
232        println!("  exit, quit  - Exit the program");
233        println!("  help        - Show this help message");
234        println!("  tools       - List available tools");
235        println!("  config      - Show current configuration");
236        println!("  Any other text will be processed as a task");
237        println!();
238    }
239
240    async fn print_available_tools(agent: &crate::agent::CodeAgent) {
241        let tools = agent.get_tools().await;
242        let tool_names = tools.lock().await.get_tool_names();
243
244        println!("Available tools:");
245        for tool_name in tool_names {
246            if let Some(tool) = tools.lock().await.get_tool(&tool_name) {
247                println!("  • {} - {}", tool.name(), tool.description());
248            }
249        }
250        println!();
251    }
252}
253
254/// Create an agent with the given configuration
255async fn create_agent(config: &AgentConfig) -> anyhow::Result<crate::agent::CodeAgent> {
256    // Create model based on provider
257    let model: Box<dyn LanguageModel> = match &config.model.provider {
258        crate::config::ModelProvider::OpenAI => {
259            let api_key = config.model.api_key.clone()
260                .ok_or_else(|| anyhow::anyhow!("OpenAI API key not found"))?;
261            Box::new(crate::models::OpenAIModel::new(api_key, config.model.model_name.clone()))
262        }
263        crate::config::ModelProvider::Anthropic => {
264            let api_key = config.model.api_key.clone()
265                .ok_or_else(|| anyhow::anyhow!("Anthropic API key not found"))?;
266            Box::new(crate::models::AnthropicModel::new(api_key, config.model.model_name.clone()))
267        }
268        crate::config::ModelProvider::Zhipu => {
269            let api_key = config.model.api_key.clone()
270                .ok_or_else(|| anyhow::anyhow!("Zhipu API key not found"))?;
271            Box::new(crate::models::ZhipuModel::new(api_key, config.model.model_name.clone(), config.model.endpoint.clone()))
272        }
273        crate::config::ModelProvider::Local(ref endpoint) => {
274            Box::new(crate::models::LocalModel::new(endpoint.clone(), config.model.model_name.clone()))
275        }
276    };
277
278    let mut agent = crate::agent::CodeAgent::new(model, config.clone());
279
280    // Register basic tools
281    agent.register_tool(crate::tools::ReadFileTool).await;
282    agent.register_tool(crate::tools::WriteFileTool).await;
283    agent.register_tool(crate::tools::RunCommandTool).await;
284    agent.register_tool(crate::tools::ListFilesTool).await;
285
286    Ok(agent)
287}