e2b_sdk/
lib.rs

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           // Print the status code and headers
75           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                                // check if response matched "end_of_execution", which is positive
125                                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}