1use reqwest::{Client};
2use serde::{Deserialize, Serialize};
3use std::error::Error;
4use std::time::Duration;
5use tokio::sync::mpsc;
6use futures_util::StreamExt;
7use chrono::{DateTime, Utc};
8
9#[derive(Debug, Clone)]
10pub struct E2BClient {
11 api_key: String,
12 client: Client,
13}
14
15#[derive(Debug, Serialize)]
16struct CreateSandboxRequest {
17 #[serde(rename = "templateID")]
18 template_id: String,
19 timeout: u32,
20}
21
22#[derive(Debug, Deserialize)]
23struct CreateSandboxResponse {
24 #[serde(rename = "sandboxID")]
25 pub sandbox_id: String,
26 #[serde(rename = "clientID")]
27 pub client_id: String,
28}
29
30#[derive(Debug, Serialize)]
31struct ExecuteCodeRequest {
32 code: String,
33}
34
35#[derive(Debug, Deserialize)]
36#[serde(tag = "type")]
37pub enum ExecuteResponse {
38 #[serde(rename = "stdout")]
39 Stdout {
40 text: String,
41 timestamp: DateTime<Utc>,
42 },
43 #[serde(rename = "stderr")]
44 Stderr { name: String },
45 #[serde(rename = "result")]
46 Result { content: String },
47 #[serde(rename = "error")]
48 Error { name: String, value: String },
49}
50
51impl E2BClient {
52 pub fn new(api_key: String) -> Self {
53 let client = Client::builder()
54 .timeout(Duration::from_secs(30))
55 .build()
56 .expect("Failed to create HTTP client");
57
58 Self { api_key, client }
59 }
60
61 pub async fn create_sandbox(&self) -> Result<String, Box<dyn Error>> {
62 let request = CreateSandboxRequest {
63 template_id: "code-interpreter-beta".to_string(),
64 timeout: 10,
65 };
66
67 let response = self.client
68 .post("https://api.e2b.dev/sandboxes")
69 .header("X-API-Key", &self.api_key)
70 .json(&request)
71 .send()
72 .await?;
73
74 log::debug!("Status: {}", response.status());
76 log::debug!("Headers: {:#?}", response.headers());
77
78 if !response.status().is_success() {
79 let error_text = response.text().await?;
80 return Err(format!("API error: {}", error_text).into());
81 }
82 let body = response.text().await?;
83 log::debug!("Response body: {}", body);
84 let parsed: CreateSandboxResponse = serde_json::from_str(&body)?;
85
86 Ok(format!("{}-{}", parsed.sandbox_id, parsed.client_id))
87 }
88
89 pub async fn execute_code(
90 &self,
91 sandbox_id: String,
92 code: &str,
93 ) -> Result<mpsc::Receiver<ExecuteResponse>, Box<dyn Error>> {
94 let request = ExecuteCodeRequest {
95 code: code.to_string(),
96 };
97
98 let (tx, rx) = mpsc::channel(100);
99 let client = self.client.clone();
100 let api_key = self.api_key.clone();
101 let uri = format!("https://49999-{}.e2b.dev/execute", &sandbox_id);
102 tokio::spawn(async move {
103 let response = client
104 .post(&uri)
105 .header("Authorization", format!("Bearer {}", api_key))
106 .json(&request)
107 .send()
108 .await;
109
110 match response {
111 Ok(stream) => {
112 let mut stream = stream.bytes_stream();
113 while let Some(chunk) = stream.next().await {
114 if let Ok(bytes) = chunk {
115 if let Ok(response) = serde_json::from_slice::<ExecuteResponse>(&bytes) {
116 log::debug!("Sending response through channel {:?}", &response);
117 if let Err(e) = tx.send(response).await {
118 eprintln!("Failed to send response through channel: {}", e);
119 break;
120 }
121 }
122 else {
123 let response = String::from_utf8_lossy(&bytes);
124 if response.contains("end_of_execution") {
126 break;
127 }
128 else{
129 eprintln!("Got error response {:?}", String::from_utf8_lossy(&bytes));
130 }
131 }
132 }
133 }
134 }
135 Err(e) => {
136 eprintln!("Failed to execute code: {}", e);
137 }
138 }
139 });
140
141 Ok(rx)
142 }
143
144 pub async fn kill_sandbox(&self, sandbox_id: &str) -> Result<(), Box<dyn Error>> {
145 self.client
146 .delete(format!("https://api.e2b.dev/sandboxes/{}", sandbox_id))
147 .header("X-API-Key", &self.api_key)
148 .send()
149 .await?;
150
151 Ok(())
152 }
153}