use crate::models::*; use crate::process::{ProcessController, ProcessDetector}; use axum::{extract::State, http::StatusCode, Json}; use std::sync::Arc; use tokio::sync::Mutex; use tracing::{error, info}; pub type ControllerState = Arc>; pub async fn kill_instance( State(controller): State, axum::extract::Path(id): axum::extract::Path, ) -> Result, StatusCode> { // Extract PID from ID (format: "pid_timestamp") let pid = id .split('_') .next() .and_then(|s| s.parse::().ok()) .ok_or(StatusCode::BAD_REQUEST)?; let mut controller = controller.lock().await; match controller.kill_process(pid) { Ok(_) => { info!("Successfully killed process {}", pid); Ok(Json(serde_json::json!({ "status": "terminating" }))) } Err(e) => { error!("Failed to kill process {}: {}", pid, e); Err(StatusCode::INTERNAL_SERVER_ERROR) } } } pub async fn restart_instance( State(controller): State, axum::extract::Path(id): axum::extract::Path, ) -> Result, StatusCode> { info!("Restarting instance: {}", id); // Extract PID from instance ID (format: pid_timestamp) let pid: u32 = id .split('_') .next() .and_then(|s| s.parse().ok()) .ok_or(StatusCode::BAD_REQUEST)?; let mut controller = controller.lock().await; // Get stored launch params let params = controller.get_launch_params(pid) .ok_or(StatusCode::NOT_FOUND)?; // Launch new instance with same parameters let new_pid = controller.launch_g3( params.workspace.to_str().unwrap(), ¶ms.provider, ¶ms.model, ¶ms.prompt, params.autonomous, params.g3_binary_path.as_deref(), ).map_err(|e| { error!("Failed to restart instance: {}", e); StatusCode::INTERNAL_SERVER_ERROR })?; let new_id = format!("{}_{}", new_pid, chrono::Utc::now().timestamp()); Ok(Json(LaunchResponse { id: new_id, status: "starting".to_string(), })) } pub async fn launch_instance( State(controller): State, Json(request): Json, ) -> Result, (StatusCode, Json)> { info!("Launching new g3 instance: {:?}", request); // Validate binary path if provided if let Some(ref binary_path) = request.g3_binary_path { let path = std::path::Path::new(binary_path); // Check if file exists if !path.exists() { error!("G3 binary not found: {}", binary_path); return Err((StatusCode::BAD_REQUEST, Json(serde_json::json!({ "error": "G3 binary not found", "message": format!("The specified g3 binary does not exist: {}", binary_path) })))); } // Check if file is executable (Unix only) #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; if let Ok(metadata) = std::fs::metadata(path) { if metadata.permissions().mode() & 0o111 == 0 { error!("G3 binary is not executable: {}", binary_path); return Err((StatusCode::BAD_REQUEST, Json(serde_json::json!({ "error": "G3 binary is not executable", "message": format!("The specified g3 binary is not executable: {}", binary_path) })))); } } } } let workspace = request.workspace.to_str().ok_or_else(|| { (StatusCode::BAD_REQUEST, Json(serde_json::json!({ "error": "Invalid workspace path", "message": "The workspace path contains invalid characters" }))) })?; let autonomous = request.mode == LaunchMode::Ensemble; let g3_binary_path = request.g3_binary_path.as_deref(); let mut controller = controller.lock().await; match controller.launch_g3( workspace, &request.provider, &request.model, &request.prompt, autonomous, g3_binary_path, ) { Ok(pid) => { let id = format!("{}_{}", pid, chrono::Utc::now().timestamp()); info!("Successfully launched g3 instance with PID {}", pid); Ok(Json(LaunchResponse { id, status: "starting".to_string(), })) } Err(e) => { error!("Failed to launch g3 instance: {}", e); Err((StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "Failed to launch instance", "message": format!("Error: {}", e) })))) } } }