use anyhow::Result; use regex::Regex; use std::process::Command; use tempfile::NamedTempFile; use std::io::Write; use tracing::{info, debug, error}; pub struct CodeExecutor { // Future: add configuration for execution limits, sandboxing, etc. } #[derive(Debug, Clone)] pub struct ExecutionResult { pub stdout: String, pub stderr: String, pub exit_code: i32, pub success: bool, } impl CodeExecutor { pub fn new() -> Self { Self {} } /// Extract code blocks from LLM response and execute them pub async fn execute_from_response(&self, response: &str) -> Result { self.execute_from_response_with_options(response, true).await } /// Extract code blocks from LLM response and execute them with UI options pub async fn execute_from_response_with_options(&self, response: &str, show_code: bool) -> Result { debug!("CodeExecutor received response ({} chars): {}", response.len(), response); let code_blocks = self.extract_code_blocks(response)?; if code_blocks.is_empty() { if show_code { return Ok(format!("āš ļø No executable code blocks found in response.\n\n{}", response)); } else { return Ok("āš ļø No executable code found.".to_string()); } } let mut results = Vec::new(); // Only show the original LLM response if show_code is true if show_code { results.push(response.to_string()); results.push("\nšŸš€ Executing code...\n".to_string()); } for (language, code) in code_blocks { info!("Executing {} code", language); if show_code { results.push(format!("šŸ“‹ Running {} code:", language)); } match self.execute_code(&language, &code).await { Ok(result) => { if result.success { if show_code { results.push("āœ… Success".to_string()); } // Always show stdout if there is any, regardless of show_code if !result.stdout.is_empty() { results.push(result.stdout.trim().to_string()); } } else { results.push("āŒ Failed".to_string()); if !result.stderr.is_empty() { results.push(format!("Error: {}", result.stderr.trim())); } } } Err(e) => { error!("Failed to execute {} code: {}", language, e); results.push(format!("āŒ Execution failed: {}", e)); } } } // If no results were added (e.g., successful execution with no output), // return a simple success message when show_code is false if results.is_empty() && !show_code { Ok("āœ… Done".to_string()) } else { Ok(results.join("\n")) } } /// Extract code blocks from markdown-formatted text fn extract_code_blocks(&self, text: &str) -> Result> { let mut blocks = Vec::new(); debug!("Extracting code blocks from text: {}", text); // Pattern 1: Standard markdown format ```language\ncode``` let markdown_re = Regex::new(r"(?s)```(\w+)?\n(.*?)```")?; for cap in markdown_re.captures_iter(text) { let language = cap.get(1) .map(|m| m.as_str().to_lowercase()) .unwrap_or_else(|| "bash".to_string()); // Default to bash let code = cap.get(2).map(|m| m.as_str()).unwrap_or("").trim(); debug!("Found markdown code block - language: '{}', code: '{}'", language, code); if !code.is_empty() { blocks.push((language, code.to_string())); } } // Pattern 2: Bracket format [Language]code[/Language] let bracket_re = Regex::new(r"(?s)\[(\w+)\]\s*(.*?)\s*\[/(\w+)\]")?; for cap in bracket_re.captures_iter(text) { let open_lang = cap.get(1).map(|m| m.as_str()).unwrap_or(""); let close_lang = cap.get(3).map(|m| m.as_str()).unwrap_or(""); // Only match if opening and closing tags are the same (case insensitive) if open_lang.to_lowercase() == close_lang.to_lowercase() { let language = open_lang.to_lowercase(); let code = cap.get(2).map(|m| m.as_str()).unwrap_or("").trim(); debug!("Found bracket code block - language: '{}', code: '{}'", language, code); if !code.is_empty() { blocks.push((language, code.to_string())); } } } debug!("Total code blocks found: {}", blocks.len()); Ok(blocks) } /// Execute code in the specified language pub async fn execute_code(&self, language: &str, code: &str) -> Result { match language.to_lowercase().as_str() { "python" | "py" => self.execute_python(code).await, "bash" | "shell" | "sh" => self.execute_bash(code).await, "javascript" | "js" => self.execute_javascript(code).await, _ => { // Try to execute as bash by default debug!("Unknown language '{}', trying as bash", language); self.execute_bash(code).await } } } /// Execute Python code async fn execute_python(&self, code: &str) -> Result { let mut temp_file = NamedTempFile::new()?; temp_file.write_all(code.as_bytes())?; let temp_path = temp_file.path(); let output = Command::new("python3") .arg(temp_path) .output()?; Ok(ExecutionResult { stdout: String::from_utf8_lossy(&output.stdout).to_string(), stderr: String::from_utf8_lossy(&output.stderr).to_string(), exit_code: output.status.code().unwrap_or(-1), success: output.status.success(), }) } /// Execute Bash code async fn execute_bash(&self, code: &str) -> Result { let output = Command::new("bash") .arg("-c") .arg(code) .output()?; Ok(ExecutionResult { stdout: String::from_utf8_lossy(&output.stdout).to_string(), stderr: String::from_utf8_lossy(&output.stderr).to_string(), exit_code: output.status.code().unwrap_or(-1), success: output.status.success(), }) } /// Execute JavaScript code (requires Node.js) async fn execute_javascript(&self, code: &str) -> Result { let mut temp_file = NamedTempFile::new()?; temp_file.write_all(code.as_bytes())?; let temp_path = temp_file.path(); let output = Command::new("node") .arg(temp_path) .output()?; Ok(ExecutionResult { stdout: String::from_utf8_lossy(&output.stdout).to_string(), stderr: String::from_utf8_lossy(&output.stderr).to_string(), exit_code: output.status.code().unwrap_or(-1), success: output.status.success(), }) } } impl Default for CodeExecutor { fn default() -> Self { Self::new() } }