README.md
1 # Alibaba Sandbox SDK for JavaScript/TypeScript 2 3 English | [中文](README_zh.md) 4 5 A TypeScript/JavaScript SDK for low-level interaction with OpenSandbox. It provides the ability to create, manage, and interact with secure sandbox environments, including executing shell commands, managing files, and reading resource metrics. 6 7 ## Installation 8 9 ### npm 10 11 ```bash 12 npm install @alibaba-group/opensandbox 13 ``` 14 15 ### pnpm 16 17 ```bash 18 pnpm add @alibaba-group/opensandbox 19 ``` 20 21 ### yarn 22 23 ```bash 24 yarn add @alibaba-group/opensandbox 25 ``` 26 27 ## Quick Start 28 29 The following example shows how to create a sandbox and execute a shell command. 30 31 > **Note**: Before running this example, ensure the OpenSandbox service is running. See the root [README.md](../../../README.md) for startup instructions. 32 33 ```ts 34 import { ConnectionConfig, Sandbox, SandboxException } from "@alibaba-group/opensandbox"; 35 36 const config = new ConnectionConfig({ 37 domain: "api.opensandbox.io", 38 apiKey: "your-api-key", 39 // protocol: "https", 40 // requestTimeoutSeconds: 60, 41 }); 42 43 try { 44 const sandbox = await Sandbox.create({ 45 connectionConfig: config, 46 image: "ubuntu", 47 timeoutSeconds: 10 * 60, 48 }); 49 50 const execution = await sandbox.commands.run("echo 'Hello Sandbox!'"); 51 console.log(execution.logs.stdout[0]?.text); 52 53 // Optional but recommended: terminate the remote instance when you are done. 54 await sandbox.kill(); 55 await sandbox.close(); 56 } catch (err) { 57 if (err instanceof SandboxException) { 58 console.error( 59 `Sandbox Error: [${err.error.code}] ${err.error.message ?? ""}`, 60 ); 61 console.error(`Request ID: ${err.requestId ?? "N/A"}`); 62 } else { 63 console.error(err); 64 } 65 } 66 ``` 67 68 ## Usage Examples 69 70 ### 1. Lifecycle Management 71 72 Manage the sandbox lifecycle, including renewal, pausing, and resuming. 73 74 ```ts 75 const info = await sandbox.getInfo(); 76 console.log("State:", info.status.state); 77 console.log("Created:", info.createdAt); 78 console.log("Expires:", info.expiresAt); // null when manual cleanup mode is used 79 80 await sandbox.pause(); 81 82 // Resume returns a fresh, connected Sandbox instance. 83 const resumed = await sandbox.resume(); 84 85 // Renew: expiresAt = now + timeoutSeconds 86 await resumed.renew(30 * 60); 87 ``` 88 89 Create a non-expiring sandbox by passing `timeoutSeconds: null`: 90 91 ```ts 92 const manual = await Sandbox.create({ 93 connectionConfig: config, 94 image: "ubuntu", 95 timeoutSeconds: null, 96 }); 97 ``` 98 99 ### 2. Custom Health Check 100 101 Define custom logic to determine whether the sandbox is ready/healthy. This overrides the default ping check used during readiness checks. 102 103 ```ts 104 const sandbox = await Sandbox.create({ 105 connectionConfig: config, 106 image: "nginx:latest", 107 healthCheck: async (sbx) => { 108 // Example: consider the sandbox healthy when port 80 endpoint becomes available 109 const ep = await sbx.getEndpoint(80); 110 return !!ep.endpoint; 111 }, 112 }); 113 ``` 114 115 ### 3. Command Execution & Streaming 116 117 Execute commands and handle output streams in real-time. 118 119 ```ts 120 import type { ExecutionHandlers } from "@alibaba-group/opensandbox"; 121 122 const handlers: ExecutionHandlers = { 123 onStdout: (m) => console.log("STDOUT:", m.text), 124 onStderr: (m) => console.error("STDERR:", m.text), 125 onExecutionComplete: (c) => 126 console.log("Finished in", c.executionTimeMs, "ms"), 127 }; 128 129 await sandbox.commands.run( 130 'for i in 1 2 3; do echo "Count $i"; sleep 0.2; done', 131 undefined, 132 handlers, 133 ); 134 ``` 135 136 ### 4. Comprehensive File Operations 137 138 Manage files and directories, including read, write, list/search, and delete. 139 140 ```ts 141 await sandbox.files.createDirectories([{ path: "/tmp/demo", mode: 755 }]); 142 143 await sandbox.files.writeFiles([ 144 { path: "/tmp/demo/hello.txt", data: "Hello World", mode: 644 }, 145 ]); 146 147 const content = await sandbox.files.readFile("/tmp/demo/hello.txt"); 148 console.log("Content:", content); 149 150 const files = await sandbox.files.search({ 151 path: "/tmp/demo", 152 pattern: "*.txt", 153 }); 154 console.log(files.map((f) => f.path)); 155 156 await sandbox.files.deleteDirectories(["/tmp/demo"]); 157 ``` 158 159 ### 5. Endpoints 160 161 `getEndpoint()` returns an endpoint **without a scheme** (for example `"localhost:44772"`). Use `getEndpointUrl()` if you want a ready-to-use absolute URL (for example `"http://localhost:44772"`). 162 163 ```ts 164 const { endpoint } = await sandbox.getEndpoint(44772); 165 const url = await sandbox.getEndpointUrl(44772); 166 ``` 167 168 ### 6. Volume Mounts 169 170 `volumes` supports `host`, `pvc`, and `ossfs` backends. Each volume must specify exactly one backend. 171 172 ```ts 173 const sandbox = await Sandbox.create({ 174 connectionConfig: config, 175 image: "ubuntu", 176 volumes: [ 177 { 178 name: "oss-data", 179 ossfs: { 180 bucket: "bucket-a", 181 endpoint: "oss-cn-hangzhou.aliyuncs.com", 182 accessKeyId: process.env.OSS_ACCESS_KEY_ID!, 183 accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET!, 184 version: "2.0", 185 }, 186 mountPath: "/mnt/oss", 187 subPath: "prefix", 188 }, 189 ], 190 }); 191 ``` 192 193 ### 7. Sandbox Management (Admin) 194 195 Use `SandboxManager` for administrative tasks and finding existing sandboxes. 196 197 ```ts 198 import { SandboxManager } from "@alibaba-group/opensandbox"; 199 200 const manager = SandboxManager.create({ connectionConfig: config }); 201 const list = await manager.listSandboxInfos({ 202 states: ["Running"], 203 pageSize: 10, 204 }); 205 console.log(list.items.map((s) => s.id)); 206 await manager.close(); 207 ``` 208 209 ## Configuration 210 211 ### 1. Connection Configuration 212 213 The `ConnectionConfig` class manages API server connection settings. 214 215 Runtime notes: 216 217 - In browsers, the SDK uses the global `fetch` implementation. 218 - In Node.js, every `Sandbox` and `SandboxManager` clones the base `ConnectionConfig` via `withTransportIfMissing()`, so each instance gets an isolated `undici` keep-alive pool. Call `sandbox.close()` or `manager.close()` when you are done so the SDK can release the associated agent. 219 220 | Parameter | Description | Default | Environment Variable | 221 | ----------------------- | ------------------------------------------------------------------------------------------------------------ | ---------------- | ---------------------- | 222 | `apiKey` | API key for authentication | Optional | `OPEN_SANDBOX_API_KEY` | 223 | `domain` | Sandbox service domain (`host[:port]`) | `localhost:8080` | `OPEN_SANDBOX_DOMAIN` | 224 | `protocol` | HTTP protocol (`http`/`https`) | `http` | - | 225 | `requestTimeoutSeconds` | Request timeout applied to SDK HTTP calls | `30` | - | 226 | `debug` | Enable basic HTTP debug logging | `false` | - | 227 | `headers` | Extra headers applied to every request | `{}` | - | 228 | `useServerProxy` | Use sandbox server as proxy for execd/endpoint requests (e.g. when client cannot reach the sandbox directly) | `false` | - | 229 230 ```ts 231 import { ConnectionConfig } from "@alibaba-group/opensandbox"; 232 233 // 1. Basic configuration 234 const config = new ConnectionConfig({ 235 domain: "api.opensandbox.io", 236 apiKey: "your-key", 237 requestTimeoutSeconds: 60, 238 }); 239 240 // 2. Advanced: custom headers 241 const config2 = new ConnectionConfig({ 242 domain: "api.opensandbox.io", 243 apiKey: "your-key", 244 headers: { "X-Custom-Header": "value" }, 245 }); 246 ``` 247 248 ### 2. Sandbox Creation Configuration 249 250 `Sandbox.create()` allows configuring the sandbox environment. 251 252 | Parameter | Description | Default | 253 | ---------------------------- | ------------------------------------------------ | ---------------------------- | 254 | `image` | Docker image to use | Required | 255 | `timeoutSeconds` | Automatic termination timeout (server-side TTL) | 10 minutes | 256 | `entrypoint` | Container entrypoint command | `["tail","-f","/dev/null"]` | 257 | `resource` | CPU and memory limits (string map) | `{"cpu":"1","memory":"2Gi"}` | 258 | `env` | Environment variables | `{}` | 259 | `metadata` | Custom metadata tags | `{}` | 260 | `networkPolicy` | Optional outbound network policy (egress) | - | 261 | `extensions` | Extra server-defined fields | `{}` | 262 | `skipHealthCheck` | Skip readiness checks (`Running` + health check) | `false` | 263 | `healthCheck` | Custom readiness check | - | 264 | `readyTimeoutSeconds` | Max time to wait for readiness | 30 seconds | 265 | `healthCheckPollingInterval` | Poll interval while waiting (milliseconds) | 200 ms | 266 267 Note: metadata keys under `opensandbox.io/` are reserved for system-managed 268 labels and will be rejected by the server. 269 270 ```ts 271 const sandbox = await Sandbox.create({ 272 connectionConfig: config, 273 image: "python:3.11", 274 networkPolicy: { 275 defaultAction: "deny", 276 egress: [{ action: "allow", target: "pypi.org" }], 277 }, 278 }); 279 ``` 280 281 ### 3. Runtime Egress Policy Updates 282 283 Runtime egress reads and patches go directly to the sandbox egress sidecar. 284 The SDK first resolves the sandbox endpoint on port `18080`, then calls the sidecar `/policy` API. 285 286 Patch uses merge semantics: 287 - Incoming rules take priority over existing rules with the same `target`. 288 - Existing rules for other targets remain unchanged. 289 - Within a single patch payload, the first rule for a `target` wins. 290 - The current `defaultAction` is preserved. 291 292 ```ts 293 const policy = await sandbox.getEgressPolicy(); 294 295 await sandbox.patchEgressRules([ 296 { action: "allow", target: "www.github.com" }, 297 { action: "deny", target: "pypi.org" }, 298 ]); 299 ``` 300 301 ### 4. Resource cleanup 302 303 Both `Sandbox` and `SandboxManager` own a scoped HTTP agent when running on Node.js 304 so you can safely reuse the same `ConnectionConfig`. Once you are finished interacting 305 with the sandbox or administration APIs, call `sandbox.close()` / `manager.close()` to 306 release the underlying agent. 307 308 ## Browser Notes 309 310 - The SDK can run in browsers, but **streaming file uploads are Node-only**. 311 - If you pass `ReadableStream` or `AsyncIterable` for `writeFiles`, the browser will fall back to **buffering in memory** before upload. 312 - Reason: browsers do not support streaming `multipart/form-data` bodies with custom boundaries (required by the execd upload API).