use super::{ExecutionResult, SandboxRunner};
use async_trait::async_trait;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct E2BSandbox {
pub api_key: String,
pub endpoint: String,
}
impl E2BSandbox {
pub fn new(api_key: String, endpoint: String) -> Self {
Self { api_key, endpoint }
}
pub fn new_with_default_endpoint(api_key: String) -> Self {
Self::new(api_key, "https://api.e2b.dev".to_string())
}
}
#[async_trait]
impl SandboxRunner for E2BSandbox {
async fn execute(
&self,
code: &str,
env: HashMap<String, String>,
) -> Result<ExecutionResult, anyhow::Error> {
tracing::debug!(
"E2B sandbox execution requested for {} chars of code with {} env vars",
code.len(),
env.len()
);
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(30))
.build()
.map_err(|e| anyhow::anyhow!("Failed to create HTTP client: {}", e))?;
let execution_request = serde_json::json!({
"code": code,
"environment": env,
"timeout": 30000, "language": "python" });
let execution_url = format!("{}/v1/sandboxes/execute", self.endpoint);
let start_time = std::time::Instant::now();
let response = client
.post(&execution_url)
.header("Authorization", format!("Bearer {}", self.api_key))
.header("Content-Type", "application/json")
.json(&execution_request)
.send()
.await
.map_err(|e| anyhow::anyhow!("E2B API request failed: {}", e))?;
let execution_duration = start_time.elapsed().as_millis() as u64;
let status_code = response.status();
if !status_code.is_success() {
let error_text = response.text().await.unwrap_or_default();
return Err(anyhow::anyhow!(
"E2B execution failed with status {}: {}",
status_code,
error_text
));
}
let response_json: serde_json::Value = response
.json()
.await
.map_err(|e| anyhow::anyhow!("Failed to parse E2B response: {}", e))?;
let success = response_json
.get("success")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let stdout = response_json
.get("stdout")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let stderr = response_json
.get("stderr")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let exit_code = response_json
.get("exit_code")
.and_then(|v| v.as_i64())
.unwrap_or(if success { 0 } else { 1 }) as i32;
tracing::info!(
"E2B execution completed in {}ms, exit_code: {}, success: {}",
execution_duration,
exit_code,
success
);
if success {
Ok(ExecutionResult::success(stdout, execution_duration))
} else {
Ok(ExecutionResult::failure(
exit_code,
stderr,
execution_duration,
))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_e2b_sandbox_creation() {
let sandbox = E2BSandbox::new(
"test_api_key".to_string(),
"https://test.e2b.dev".to_string(),
);
assert_eq!(sandbox.api_key, "test_api_key");
assert_eq!(sandbox.endpoint, "https://test.e2b.dev");
}
#[test]
fn test_e2b_sandbox_default_endpoint() {
let sandbox = E2BSandbox::new_with_default_endpoint("test_api_key".to_string());
assert_eq!(sandbox.api_key, "test_api_key");
assert_eq!(sandbox.endpoint, "https://api.e2b.dev");
}
#[tokio::test]
async fn test_e2b_sandbox_execute() {
let sandbox = E2BSandbox::new(
"test_api_key".to_string(),
"https://test.e2b.dev".to_string(),
);
let mut env = HashMap::new();
env.insert("TEST_VAR".to_string(), "test_value".to_string());
let result = sandbox.execute("print('hello')", env).await.unwrap();
assert!(result.success);
assert_eq!(result.exit_code, 0);
assert!(!result.stdout.is_empty());
}
}