返回 CodeWhale
opensandbox.rs
根目录 / crates / tui / src / sandbox / opensandbox.rs
1 //! Alibaba OpenSandbox backend adapter.
2 //!
3 //! Sends shell commands to an OpenSandbox-compatible HTTP API for remote
4 //! execution. The API endpoint is `POST {base_url}/v1/sandbox/run` with
5 //! JSON body `{"cmd": "...", "env": {...}}` and expects a JSON response
6 //! `{"stdout": "...", "stderr": "...", "exit_code": 0}`.
7
8 use std::collections::HashMap;
9 use std::time::Duration;
10
11 use anyhow::{Context, Result};
12 use async_trait::async_trait;
13 use serde::Deserialize;
14 use serde::Serialize;
15
16 use super::backend::{SandboxBackend, SandboxOutput};
17
18 /// Request body sent to the OpenSandbox `/v1/sandbox/run` endpoint.
19 #[derive(Debug, Serialize)]
20 struct SandboxRunRequest {
21 /// Full shell command to execute.
22 cmd: String,
23 /// Environment variables to set in the sandbox.
24 env: HashMap<String, String>,
25 }
26
27 /// Response body from the OpenSandbox `/v1/sandbox/run` endpoint.
28 #[derive(Debug, Deserialize)]
29 struct SandboxRunResponse {
30 /// Standard output from the command.
31 stdout: String,
32 /// Standard error from the command.
33 stderr: String,
34 /// Exit code (0 for success).
35 exit_code: i32,
36 }
37
38 /// An OpenSandbox-compatible remote execution backend.
39 ///
40 /// Constructed with a base URL (e.g. `"http://localhost:8080"`), an optional
41 /// API key sent as a `Bearer` token, and a timeout in seconds.
42 pub struct OpenSandboxBackend {
43 base_url: String,
44 api_key: Option<String>,
45 timeout_secs: u64,
46 client: reqwest::Client,
47 }
48
49 impl OpenSandboxBackend {
50 /// Create a new OpenSandbox backend.
51 ///
52 /// `base_url` should be the root of the OpenSandbox API (e.g.
53 /// `"http://localhost:8080"`). `api_key` is optional and sent as
54 /// `Authorization: Bearer <key>` when set. `timeout_secs` controls the
55 /// HTTP request timeout.
56 pub fn new(base_url: String, api_key: Option<String>, timeout_secs: u64) -> Result<Self> {
57 let client = crate::tls::reqwest_client_builder()
58 .timeout(Duration::from_secs(timeout_secs))
59 .build()
60 .context("failed to construct HTTP client for OpenSandbox backend")?;
61
62 Ok(Self {
63 base_url,
64 api_key,
65 timeout_secs,
66 client,
67 })
68 }
69
70 /// Build the full URL for the sandbox run endpoint.
71 fn run_url(&self) -> String {
72 format!("{}/v1/sandbox/run", self.base_url.trim_end_matches('/'))
73 }
74 }
75
76 #[async_trait]
77 impl SandboxBackend for OpenSandboxBackend {
78 async fn exec(&self, cmd: &str, env: &HashMap<String, String>) -> Result<SandboxOutput> {
79 let request_body = SandboxRunRequest {
80 cmd: cmd.to_string(),
81 env: env.clone(),
82 };
83
84 let mut req = self.client.post(self.run_url()).json(&request_body);
85
86 if let Some(ref api_key) = self.api_key {
87 req = req.bearer_auth(api_key);
88 }
89
90 let response = req
91 .send()
92 .await
93 .context("Failed to reach OpenSandbox endpoint")?;
94
95 let status = response.status();
96 if !status.is_success() {
97 let body = response.text().await.unwrap_or_default();
98 anyhow::bail!("OpenSandbox returned HTTP {}: {}", status.as_u16(), body);
99 }
100
101 let parsed: SandboxRunResponse = response
102 .json()
103 .await
104 .context("Failed to parse OpenSandbox response")?;
105
106 Ok(SandboxOutput {
107 stdout: parsed.stdout,
108 stderr: parsed.stderr,
109 exit_code: parsed.exit_code,
110 })
111 }
112 }
113
113 lines RUST