use std::collections::HashMap;
use std::time::Duration;
use anyhow::{Context, Result};
use async_trait::async_trait;
use serde::Deserialize;
use serde::Serialize;
use super::backend::{SandboxBackend, SandboxOutput};
#[derive(Debug, Serialize)]
struct SandboxRunRequest {
cmd: String,
env: HashMap<String, String>,
}
#[derive(Debug, Deserialize)]
struct SandboxRunResponse {
stdout: String,
stderr: String,
exit_code: i32,
}
pub struct OpenSandboxBackend {
base_url: String,
api_key: Option<String>,
timeout_secs: u64,
client: reqwest::Client,
}
impl OpenSandboxBackend {
pub fn new(base_url: String, api_key: Option<String>, timeout_secs: u64) -> Result<Self> {
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(timeout_secs))
.build()
.context("failed to construct HTTP client for OpenSandbox backend")?;
Ok(Self {
base_url,
api_key,
timeout_secs,
client,
})
}
fn run_url(&self) -> String {
format!("{}/v1/sandbox/run", self.base_url.trim_end_matches('/'))
}
}
#[async_trait]
impl SandboxBackend for OpenSandboxBackend {
async fn exec(&self, cmd: &str, env: &HashMap<String, String>) -> Result<SandboxOutput> {
let request_body = SandboxRunRequest {
cmd: cmd.to_string(),
env: env.clone(),
};
let mut req = self.client.post(self.run_url()).json(&request_body);
if let Some(ref api_key) = self.api_key {
req = req.bearer_auth(api_key);
}
let response = req
.send()
.await
.context("Failed to reach OpenSandbox endpoint")?;
let status = response.status();
if !status.is_success() {
let body = response.text().await.unwrap_or_default();
anyhow::bail!("OpenSandbox returned HTTP {}: {}", status.as_u16(), body);
}
let parsed: SandboxRunResponse = response
.json()
.await
.context("Failed to parse OpenSandbox response")?;
Ok(SandboxOutput {
stdout: parsed.stdout,
stderr: parsed.stderr,
exit_code: parsed.exit_code,
})
}
}