1use base64::{Engine, engine::general_purpose::STANDARD};
4use tracing::{debug, info};
5use uuid::Uuid;
6
7use crate::{
8 client::DaytonaClient,
9 error::Result,
10 models::{
11 CreateSessionRequest, ExecuteRequest, ExecuteResponse, Language, Session, SessionCommand,
12 SessionExecuteRequest, SessionExecuteResponse,
13 },
14};
15
16pub struct ProcessExecutor<'a> {
18 client: &'a DaytonaClient,
19}
20
21impl<'a> ProcessExecutor<'a> {
22 pub fn new(client: &'a DaytonaClient) -> Self {
24 Self { client }
25 }
26
27 pub async fn execute_command(
29 &self,
30 sandbox_id: &Uuid,
31 command: &str,
32 ) -> Result<ExecuteResponse> {
33 info!("Executing command in sandbox {}: {}", sandbox_id, command);
34
35 let request = ExecuteRequest {
36 command: command.to_string(),
37 cwd: None,
38 timeout: Some(self.client.config.timeout_seconds),
39 };
40
41 let response = self
42 .client
43 .build_request(
44 reqwest::Method::POST,
45 &format!("/toolbox/{}/toolbox/process/execute", sandbox_id),
46 )
47 .json(&request)
48 .send()
49 .await?;
50
51 self.client.handle_response(response).await
52 }
53
54 pub async fn execute_with_options(
56 &self,
57 sandbox_id: &Uuid,
58 request: ExecuteRequest,
59 ) -> Result<ExecuteResponse> {
60 info!("Executing command with options in sandbox {}", sandbox_id);
61
62 let response = self
63 .client
64 .build_request(
65 reqwest::Method::POST,
66 &format!("/toolbox/{}/toolbox/process/execute", sandbox_id),
67 )
68 .json(&request)
69 .send()
70 .await?;
71
72 self.client.handle_response(response).await
73 }
74
75 pub async fn execute_code(
77 &self,
78 sandbox_id: &Uuid,
79 language: Language,
80 code: &str,
81 ) -> Result<ExecuteResponse> {
82 info!("Executing {:?} code in sandbox {}", language, sandbox_id);
83 debug!("Code:\n{}", code);
84
85 let file_ext = match language {
87 Language::Python => "py",
88 Language::Javascript => "js",
89 Language::Typescript => "ts",
90 Language::Rust => "rs",
91 Language::Go => "go",
92 Language::Java => "java",
93 Language::Ruby => "rb",
94 Language::Php => "php",
95 Language::Shell => "sh",
96 };
97
98 let filename = format!("/tmp/code_{}.{}", Uuid::new_v4(), file_ext);
99
100 let files = crate::files::FileManager::new(self.client);
102 files.upload(sandbox_id, &filename, code.as_bytes()).await?;
103
104 let command = match language {
106 Language::Python => format!("python {}", filename),
107 Language::Javascript => format!("node {}", filename),
108 Language::Typescript => format!("ts-node {}", filename),
109 Language::Rust => format!("rustc {} -o /tmp/rust_out && /tmp/rust_out", filename),
110 Language::Go => format!("go run {}", filename),
111 Language::Java => {
112 let class_name = filename.strip_suffix(".java").unwrap_or(&filename);
113 format!("javac {} && java {}", filename, class_name)
114 }
115 Language::Ruby => format!("ruby {}", filename),
116 Language::Php => format!("php {}", filename),
117 Language::Shell => format!("bash {}", filename),
118 };
119
120 self.execute_command(sandbox_id, &command).await
121 }
122
123 pub async fn execute_code_stdin(
125 &self,
126 sandbox_id: &Uuid,
127 language: Language,
128 code: &str,
129 ) -> Result<ExecuteResponse> {
130 let encoded_code = STANDARD.encode(code.as_bytes());
132
133 let command = match language {
134 Language::Python => format!("echo {} | base64 -d | python", encoded_code),
135 Language::Javascript => format!("echo {} | base64 -d | node", encoded_code),
136 Language::Shell => format!("echo {} | base64 -d | bash", encoded_code),
137 _ => {
138 return self.execute_code(sandbox_id, language, code).await;
140 }
141 };
142
143 self.execute_command(sandbox_id, &command).await
144 }
145
146 pub async fn run_script(&self, sandbox_id: &Uuid, script: &str) -> Result<ExecuteResponse> {
148 self.execute_code(sandbox_id, Language::Shell, script).await
149 }
150
151 pub async fn install_packages(
153 &self,
154 sandbox_id: &Uuid,
155 language: Language,
156 packages: Vec<String>,
157 ) -> Result<ExecuteResponse> {
158 let command = match language {
159 Language::Python => format!("pip install {}", packages.join(" ")),
160 Language::Javascript => format!("npm install {}", packages.join(" ")),
161 Language::Ruby => format!("gem install {}", packages.join(" ")),
162 Language::Php => format!("composer require {}", packages.join(" ")),
163 Language::Go => format!("go get {}", packages.join(" ")),
164 Language::Rust => format!("cargo add {}", packages.join(" ")),
165 _ => {
166 return Err(crate::error::DaytonaError::InvalidParameters(format!(
167 "Package installation not supported for {:?}",
168 language
169 )));
170 }
171 };
172
173 self.execute_command(sandbox_id, &command).await
174 }
175
176 pub async fn create_session(&self, sandbox_id: &Uuid, session_id: &str) -> Result<()> {
178 info!(
179 "Creating new session {} in sandbox {}",
180 session_id, sandbox_id
181 );
182
183 let request = CreateSessionRequest {
184 session_id: session_id.to_string(),
185 };
186
187 let response = self
188 .client
189 .build_request(
190 reqwest::Method::POST,
191 &format!("/toolbox/{}/toolbox/process/session", sandbox_id),
192 )
193 .json(&request)
194 .send()
195 .await?;
196
197 if response.status().is_success() {
198 Ok(())
199 } else {
200 let error_text = response.text().await?;
201 Err(crate::error::DaytonaError::RequestFailed(format!(
202 "Failed to create session: {}",
203 error_text
204 )))
205 }
206 }
207
208 pub async fn delete_session(&self, sandbox_id: &Uuid, session_id: &str) -> Result<()> {
210 info!("Deleting session {} in sandbox {}", session_id, sandbox_id);
211
212 let response = self
213 .client
214 .build_request(
215 reqwest::Method::DELETE,
216 &format!(
217 "/toolbox/{}/toolbox/process/session/{}",
218 sandbox_id, session_id
219 ),
220 )
221 .send()
222 .await?;
223
224 if response.status().is_success() {
225 Ok(())
226 } else {
227 let error_text = response.text().await?;
228 Err(crate::error::DaytonaError::RequestFailed(format!(
229 "Failed to delete session: {}",
230 error_text
231 )))
232 }
233 }
234
235 pub async fn get_session(&self, sandbox_id: &Uuid, session_id: &str) -> Result<Session> {
237 debug!("Getting session {} in sandbox {}", session_id, sandbox_id);
238
239 let response = self
240 .client
241 .build_request(
242 reqwest::Method::GET,
243 &format!(
244 "/toolbox/{}/toolbox/process/session/{}",
245 sandbox_id, session_id
246 ),
247 )
248 .send()
249 .await?;
250
251 self.client.handle_response(response).await
252 }
253
254 pub async fn list_sessions(&self, sandbox_id: &Uuid) -> Result<Vec<Session>> {
256 debug!("Listing sessions in sandbox {}", sandbox_id);
257
258 let response = self
259 .client
260 .build_request(
261 reqwest::Method::GET,
262 &format!("/toolbox/{}/toolbox/process/session", sandbox_id),
263 )
264 .send()
265 .await?;
266
267 self.client.handle_response(response).await
268 }
269
270 pub async fn execute_session_command(
272 &self,
273 sandbox_id: &Uuid,
274 session_id: &str,
275 request: SessionExecuteRequest,
276 ) -> Result<SessionExecuteResponse> {
277 info!(
278 "Executing command '{}' in session {} in sandbox {}",
279 request.command, session_id, sandbox_id
280 );
281
282 let response = self
283 .client
284 .build_request(
285 reqwest::Method::POST,
286 &format!(
287 "/toolbox/{}/toolbox/process/session/{}/exec",
288 sandbox_id, session_id
289 ),
290 )
291 .json(&request)
292 .send()
293 .await?;
294
295 self.client.handle_response(response).await
296 }
297
298 pub async fn get_session_command(
300 &self,
301 sandbox_id: &Uuid,
302 session_id: &str,
303 command_id: &str,
304 ) -> Result<SessionCommand> {
305 debug!(
306 "Getting command {} from session {} in sandbox {}",
307 command_id, session_id, sandbox_id
308 );
309
310 let response = self
311 .client
312 .build_request(
313 reqwest::Method::GET,
314 &format!(
315 "/toolbox/{}/toolbox/process/session/{}/command/{}",
316 sandbox_id, session_id, command_id
317 ),
318 )
319 .send()
320 .await?;
321
322 self.client.handle_response(response).await
323 }
324
325 pub async fn get_session_command_logs(
327 &self,
328 sandbox_id: &Uuid,
329 session_id: &str,
330 command_id: &str,
331 ) -> Result<String> {
332 debug!(
333 "Getting logs for command {} from session {} in sandbox {}",
334 command_id, session_id, sandbox_id
335 );
336
337 let response = self
338 .client
339 .build_request(
340 reqwest::Method::GET,
341 &format!(
342 "/toolbox/{}/toolbox/process/session/{}/command/{}/logs",
343 sandbox_id, session_id, command_id
344 ),
345 )
346 .send()
347 .await?;
348
349 if response.status().is_success() {
350 Ok(response.text().await?)
351 } else {
352 let error_text = response.text().await?;
353 Err(crate::error::DaytonaError::RequestFailed(format!(
354 "Failed to get command logs: {}",
355 error_text
356 )))
357 }
358 }
359
360 pub async fn get_project_dir(&self, sandbox_id: &Uuid) -> Result<String> {
362 debug!("Getting project directory for sandbox {}", sandbox_id);
363
364 let response = self
365 .client
366 .build_request(
367 reqwest::Method::GET,
368 &format!("/toolbox/{}/toolbox/project-dir", sandbox_id),
369 )
370 .send()
371 .await?;
372
373 if response.status().is_success() {
374 Ok(response.text().await?.trim().to_string())
375 } else {
376 let error_text = response.text().await?;
377 Err(crate::error::DaytonaError::RequestFailed(format!(
378 "Failed to get project directory: {}",
379 error_text
380 )))
381 }
382 }
383}
384
385impl DaytonaClient {
386 pub fn process(&self) -> ProcessExecutor<'_> {
388 ProcessExecutor::new(self)
389 }
390}