//! Code execution tools using Docker sandboxing use crate::sandbox::Sandbox; use async_trait::async_trait; use owlen_core::Result; use owlen_core::tools::{Tool, ToolResult}; use serde_json::{Value, json}; use std::path::PathBuf; /// Tool for compiling projects (Rust, Node.js, Python) pub struct CompileProjectTool { sandbox: Sandbox, } impl Default for CompileProjectTool { fn default() -> Self { Self::new() } } impl CompileProjectTool { pub fn new() -> Self { Self { sandbox: Sandbox::default(), } } } #[async_trait] impl Tool for CompileProjectTool { fn name(&self) -> &'static str { "compile_project" } fn description(&self) -> &'static str { "Compile a project (Rust, Node.js, Python). Detects project type automatically." } fn schema(&self) -> Value { json!({ "type": "object", "properties": { "project_path": { "type": "string", "description": "Path to the project root" }, "project_type": { "type": "string", "enum": ["rust", "node", "python"], "description": "Project type (auto-detected if not specified)" } }, "required": ["project_path"] }) } async fn execute(&self, args: Value) -> Result { let project_path = args .get("project_path") .and_then(|v| v.as_str()) .ok_or_else(|| owlen_core::Error::InvalidInput("Missing project_path".into()))?; let path = PathBuf::from(project_path); if !path.exists() { return Ok(ToolResult::error("Project path does not exist")); } // Detect project type let project_type = if let Some(pt) = args.get("project_type").and_then(|v| v.as_str()) { pt.to_string() } else if path.join("Cargo.toml").exists() { "rust".to_string() } else if path.join("package.json").exists() { "node".to_string() } else if path.join("setup.py").exists() || path.join("pyproject.toml").exists() { "python".to_string() } else { return Ok(ToolResult::error("Could not detect project type")); }; // Execute compilation let result = match project_type.as_str() { "rust" => self.sandbox.execute_rust(&path, &["cargo", "build"]).await, "node" => { self.sandbox .execute_node(&path, &["npm", "run", "build"]) .await } "python" => { // Python typically doesn't need compilation, but we can check syntax self.sandbox .execute_python(&path, &["python", "-m", "compileall", "."]) .await } _ => return Ok(ToolResult::error("Unsupported project type")), }; match result { Ok(exec_result) => { if exec_result.timed_out { Ok(ToolResult::error("Compilation timed out")) } else if exec_result.exit_code == 0 { Ok(ToolResult::success(json!({ "success": true, "stdout": exec_result.stdout, "stderr": exec_result.stderr, "project_type": project_type }))) } else { Ok(ToolResult::success(json!({ "success": false, "exit_code": exec_result.exit_code, "stdout": exec_result.stdout, "stderr": exec_result.stderr, "project_type": project_type }))) } } Err(e) => Ok(ToolResult::error(&format!("Compilation failed: {}", e))), } } } /// Tool for running test suites pub struct RunTestsTool { sandbox: Sandbox, } impl Default for RunTestsTool { fn default() -> Self { Self::new() } } impl RunTestsTool { pub fn new() -> Self { Self { sandbox: Sandbox::default(), } } } #[async_trait] impl Tool for RunTestsTool { fn name(&self) -> &'static str { "run_tests" } fn description(&self) -> &'static str { "Run tests for a project (Rust, Node.js, Python)" } fn schema(&self) -> Value { json!({ "type": "object", "properties": { "project_path": { "type": "string", "description": "Path to the project root" }, "test_filter": { "type": "string", "description": "Optional test filter/pattern" } }, "required": ["project_path"] }) } async fn execute(&self, args: Value) -> Result { let project_path = args .get("project_path") .and_then(|v| v.as_str()) .ok_or_else(|| owlen_core::Error::InvalidInput("Missing project_path".into()))?; let path = PathBuf::from(project_path); if !path.exists() { return Ok(ToolResult::error("Project path does not exist")); } let test_filter = args.get("test_filter").and_then(|v| v.as_str()); // Detect project type and run tests let result = if path.join("Cargo.toml").exists() { let cmd = if let Some(filter) = test_filter { vec!["cargo", "test", filter] } else { vec!["cargo", "test"] }; self.sandbox.execute_rust(&path, &cmd).await } else if path.join("package.json").exists() { self.sandbox.execute_node(&path, &["npm", "test"]).await } else if path.join("pytest.ini").exists() || path.join("setup.py").exists() || path.join("pyproject.toml").exists() { let cmd = if let Some(filter) = test_filter { vec!["pytest", "-k", filter] } else { vec!["pytest"] }; self.sandbox.execute_python(&path, &cmd).await } else { return Ok(ToolResult::error("Could not detect test framework")); }; match result { Ok(exec_result) => Ok(ToolResult::success(json!({ "success": exec_result.exit_code == 0 && !exec_result.timed_out, "exit_code": exec_result.exit_code, "stdout": exec_result.stdout, "stderr": exec_result.stderr, "timed_out": exec_result.timed_out }))), Err(e) => Ok(ToolResult::error(&format!("Tests failed to run: {}", e))), } } } /// Tool for formatting code pub struct FormatCodeTool { sandbox: Sandbox, } impl Default for FormatCodeTool { fn default() -> Self { Self::new() } } impl FormatCodeTool { pub fn new() -> Self { Self { sandbox: Sandbox::default(), } } } #[async_trait] impl Tool for FormatCodeTool { fn name(&self) -> &'static str { "format_code" } fn description(&self) -> &'static str { "Format code using project-appropriate formatter (rustfmt, prettier, black)" } fn schema(&self) -> Value { json!({ "type": "object", "properties": { "project_path": { "type": "string", "description": "Path to the project root" }, "check_only": { "type": "boolean", "description": "Only check formatting without modifying files", "default": false } }, "required": ["project_path"] }) } async fn execute(&self, args: Value) -> Result { let project_path = args .get("project_path") .and_then(|v| v.as_str()) .ok_or_else(|| owlen_core::Error::InvalidInput("Missing project_path".into()))?; let path = PathBuf::from(project_path); if !path.exists() { return Ok(ToolResult::error("Project path does not exist")); } let check_only = args .get("check_only") .and_then(|v| v.as_bool()) .unwrap_or(false); // Detect project type and run formatter let result = if path.join("Cargo.toml").exists() { let cmd = if check_only { vec!["cargo", "fmt", "--", "--check"] } else { vec!["cargo", "fmt"] }; self.sandbox.execute_rust(&path, &cmd).await } else if path.join("package.json").exists() { let cmd = if check_only { vec!["npx", "prettier", "--check", "."] } else { vec!["npx", "prettier", "--write", "."] }; self.sandbox.execute_node(&path, &cmd).await } else if path.join("setup.py").exists() || path.join("pyproject.toml").exists() { let cmd = if check_only { vec!["black", "--check", "."] } else { vec!["black", "."] }; self.sandbox.execute_python(&path, &cmd).await } else { return Ok(ToolResult::error("Could not detect project type")); }; match result { Ok(exec_result) => Ok(ToolResult::success(json!({ "success": exec_result.exit_code == 0, "formatted": !check_only && exec_result.exit_code == 0, "stdout": exec_result.stdout, "stderr": exec_result.stderr }))), Err(e) => Ok(ToolResult::error(&format!("Formatting failed: {}", e))), } } } /// Tool for linting code pub struct LintCodeTool { sandbox: Sandbox, } impl Default for LintCodeTool { fn default() -> Self { Self::new() } } impl LintCodeTool { pub fn new() -> Self { Self { sandbox: Sandbox::default(), } } } #[async_trait] impl Tool for LintCodeTool { fn name(&self) -> &'static str { "lint_code" } fn description(&self) -> &'static str { "Lint code using project-appropriate linter (clippy, eslint, pylint)" } fn schema(&self) -> Value { json!({ "type": "object", "properties": { "project_path": { "type": "string", "description": "Path to the project root" }, "fix": { "type": "boolean", "description": "Automatically fix issues if possible", "default": false } }, "required": ["project_path"] }) } async fn execute(&self, args: Value) -> Result { let project_path = args .get("project_path") .and_then(|v| v.as_str()) .ok_or_else(|| owlen_core::Error::InvalidInput("Missing project_path".into()))?; let path = PathBuf::from(project_path); if !path.exists() { return Ok(ToolResult::error("Project path does not exist")); } let fix = args.get("fix").and_then(|v| v.as_bool()).unwrap_or(false); // Detect project type and run linter let result = if path.join("Cargo.toml").exists() { let cmd = if fix { vec!["cargo", "clippy", "--fix", "--allow-dirty"] } else { vec!["cargo", "clippy"] }; self.sandbox.execute_rust(&path, &cmd).await } else if path.join("package.json").exists() { let cmd = if fix { vec!["npx", "eslint", ".", "--fix"] } else { vec!["npx", "eslint", "."] }; self.sandbox.execute_node(&path, &cmd).await } else if path.join("setup.py").exists() || path.join("pyproject.toml").exists() { // pylint doesn't have auto-fix self.sandbox.execute_python(&path, &["pylint", "."]).await } else { return Ok(ToolResult::error("Could not detect project type")); }; match result { Ok(exec_result) => { let issues_found = exec_result.exit_code != 0; Ok(ToolResult::success(json!({ "success": true, "issues_found": issues_found, "exit_code": exec_result.exit_code, "stdout": exec_result.stdout, "stderr": exec_result.stderr }))) } Err(e) => Ok(ToolResult::error(&format!("Linting failed: {}", e))), } } }