g3 console initial cut + error doesnt kill auto
This commit is contained in:
147
crates/g3-console/src/api/control.rs
Normal file
147
crates/g3-console/src/api/control.rs
Normal file
@@ -0,0 +1,147 @@
|
||||
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<Mutex<ProcessController>>;
|
||||
|
||||
pub async fn kill_instance(
|
||||
State(controller): State<ControllerState>,
|
||||
axum::extract::Path(id): axum::extract::Path<String>,
|
||||
) -> Result<Json<serde_json::Value>, StatusCode> {
|
||||
// Extract PID from ID (format: "pid_timestamp")
|
||||
let pid = id
|
||||
.split('_')
|
||||
.next()
|
||||
.and_then(|s| s.parse::<u32>().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<ControllerState>,
|
||||
axum::extract::Path(id): axum::extract::Path<String>,
|
||||
) -> Result<Json<LaunchResponse>, 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<ControllerState>,
|
||||
Json(request): Json<LaunchRequest>,
|
||||
) -> Result<Json<LaunchResponse>, (StatusCode, Json<serde_json::Value>)> {
|
||||
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)
|
||||
}))))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user