/ sdks / sandbox / javascript / README_zh.md
README_zh.md
  1  # Alibaba Sandbox JavaScript/TypeScript SDK
  2  
  3  中文 | [English](README.md)
  4  
  5  用于与 OpenSandbox 进行底层交互的 TypeScript/JavaScript SDK。它提供了创建、管理和与安全沙箱环境交互的能力,包括执行 Shell 命令、管理文件以及读取资源指标等。
  6  
  7  ## 安装指南
  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  ## 快速开始
 28  
 29  以下示例展示了如何创建一个沙箱并执行 Shell 命令。
 30  
 31  > **注意**: 在运行此示例之前,请确保 OpenSandbox 服务已启动。服务启动请参考根目录的 [README_zh.md](../../../docs/README_zh.md)。
 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    await sandbox.kill();
 54    await sandbox.close();
 55  } catch (err) {
 56    if (err instanceof SandboxException) {
 57      console.error(`沙箱错误: [${err.error.code}] ${err.error.message ?? ""}`);
 58      console.error(`Request ID: ${err.requestId ?? "N/A"}`);
 59    } else {
 60      console.error(err);
 61    }
 62  }
 63  ```
 64  
 65  ## 核心功能示例
 66  
 67  ### 1. 生命周期管理
 68  
 69  管理沙箱的生命周期,包括续期、暂停、恢复和状态查询。
 70  
 71  ```ts
 72  const info = await sandbox.getInfo();
 73  console.log("状态:", info.status.state);
 74  console.log("创建时间:", info.createdAt);
 75  console.log("过期时间:", info.expiresAt);
 76  
 77  await sandbox.pause();
 78  
 79  // resume 会返回新的、已连接的 Sandbox 实例
 80  const resumed = await sandbox.resume();
 81  
 82  // renew:expiresAt = now + timeoutSeconds
 83  await resumed.renew(30 * 60);
 84  
 85  // 获取当前状态
 86  const info = await resumed.getInfo();
 87  console.log("状态:", info.status.state);
 88  console.log("过期时间:", info.expiresAt); // 使用手动清理模式时为 null
 89  ```
 90  
 91  通过传入 `timeoutSeconds: null` 创建一个不会自动过期的沙箱:
 92  
 93  ```ts
 94  const manual = await Sandbox.create({
 95    connectionConfig: config,
 96    image: "ubuntu",
 97    timeoutSeconds: null,
 98  });
 99  ```
100  
101  ### 2. 自定义健康检查
102  
103  定义自定义逻辑来判断沙箱是否就绪/健康。这会覆盖“就绪检测”默认使用的 ping 检查逻辑。
104  
105  ```ts
106  const sandbox = await Sandbox.create({
107    connectionConfig: config,
108    image: "nginx:latest",
109    healthCheck: async (sbx) => {
110      // 示例:当 80 端口 endpoint 可获取时认为沙箱可用
111      const ep = await sbx.getEndpoint(80);
112      return !!ep.endpoint;
113    },
114  });
115  ```
116  
117  ### 3. 命令执行与流式响应
118  
119  执行命令并实时处理输出流。
120  
121  ```ts
122  import type { ExecutionHandlers } from "@alibaba-group/opensandbox";
123  
124  const handlers: ExecutionHandlers = {
125    onStdout: (m) => console.log("STDOUT:", m.text),
126    onStderr: (m) => console.error("STDERR:", m.text),
127    onExecutionComplete: (c) => console.log("耗时(ms):", c.executionTimeMs),
128  };
129  
130  await sandbox.commands.run(
131    'for i in 1 2 3; do echo "Count $i"; sleep 0.2; done',
132    undefined,
133    handlers,
134  );
135  ```
136  
137  ### 4. 全面的文件操作
138  
139  管理文件和目录,包括读写、列表/搜索与删除。
140  
141  ```ts
142  await sandbox.files.createDirectories([{ path: "/tmp/demo", mode: 755 }]);
143  
144  await sandbox.files.writeFiles([
145    { path: "/tmp/demo/hello.txt", data: "Hello World", mode: 644 },
146  ]);
147  
148  const content = await sandbox.files.readFile("/tmp/demo/hello.txt");
149  console.log("文件内容:", content);
150  
151  const files = await sandbox.files.search({
152    path: "/tmp/demo",
153    pattern: "*.txt",
154  });
155  console.log(files.map((f) => f.path));
156  
157  await sandbox.files.deleteDirectories(["/tmp/demo"]);
158  ```
159  
160  ### 5. Endpoint
161  
162  `getEndpoint()` 返回 **不带 scheme** 的 endpoint(例如 `"localhost:44772"`)。如果你希望直接得到可用的绝对 URL(例如 `"http://localhost:44772"`),请使用 `getEndpointUrl()`。
163  
164  ```ts
165  const { endpoint } = await sandbox.getEndpoint(44772);
166  const url = await sandbox.getEndpointUrl(44772);
167  ```
168  
169  ### 6. Volume 挂载
170  
171  `volumes` 现在支持 `host`、`pvc` 和 `ossfs` 三种 backend。每个 volume 必须且只能指定其中一种。
172  
173  ```ts
174  const sandbox = await Sandbox.create({
175    connectionConfig: config,
176    image: "ubuntu",
177    volumes: [
178      {
179        name: "oss-data",
180        ossfs: {
181          bucket: "bucket-a",
182          endpoint: "oss-cn-hangzhou.aliyuncs.com",
183          accessKeyId: process.env.OSS_ACCESS_KEY_ID!,
184          accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET!,
185          version: "2.0",
186        },
187        mountPath: "/mnt/oss",
188        subPath: "prefix",
189      },
190    ],
191  });
192  ```
193  
194  ### 7. 沙箱管理(Admin)
195  
196  使用 `SandboxManager` 进行管理操作,如查询现有沙箱列表。
197  
198  ```ts
199  import { SandboxManager } from "@alibaba-group/opensandbox";
200  
201  const manager = SandboxManager.create({ connectionConfig: config });
202  const list = await manager.listSandboxInfos({
203    states: ["Running"],
204    pageSize: 10,
205  });
206  console.log(list.items.map((s) => s.id));
207  ```
208  
209  ## 配置说明
210  
211  ### 1. 连接配置 (Connection Configuration)
212  
213  `ConnectionConfig` 类管理与 API 服务器的连接设置。
214  
215  运行环境说明:
216  
217  - 浏览器环境下,SDK 使用全局 `fetch`。
218  - Node.js 环境下,每个 `Sandbox` 和 `SandboxManager` 都会通过 `ConnectionConfig.withTransportIfMissing()` 创建独立的 keep-alive 池(基于 `undici`)。完成交互后请调用 `sandbox.close()` 或 `manager.close()` 来释放对应的 agent,以避免遗留连接,这与 Python SDK 的 transport 生命周期一致。
219  
220  | 参数                    | 描述                                                                      | 默认值           | 环境变量               |
221  | ----------------------- | ------------------------------------------------------------------------- | ---------------- | ---------------------- |
222  | `apiKey`                | 用于认证的 API Key                                                        | 可选             | `OPEN_SANDBOX_API_KEY` |
223  | `domain`                | 沙箱服务域名(`host[:port]`)                                             | `localhost:8080` | `OPEN_SANDBOX_DOMAIN`  |
224  | `protocol`              | HTTP 协议(`http`/`https`)                                               | `http`           | -                      |
225  | `requestTimeoutSeconds` | SDK HTTP 请求超时(秒)                                                   | `30`             | -                      |
226  | `debug`                 | 是否开启基础 HTTP 调试日志                                                | `false`          | -                      |
227  | `headers`               | 每次请求附加的 Header                                                     | `{}`             | -                      |
228  | `useServerProxy`        | 是否通过沙箱服务代理访问 execd/endpoint(适用于客户端无法直连沙箱的场景) | `false`          | -                      |
229  
230  ```ts
231  import { ConnectionConfig } from "@alibaba-group/opensandbox";
232  
233  // 1. 基础配置
234  const config = new ConnectionConfig({
235    domain: "api.opensandbox.io",
236    apiKey: "your-key",
237    requestTimeoutSeconds: 60,
238  });
239  
240  // 2. 进阶配置:自定义 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()` 用于配置沙箱环境。
251  
252  | 参数                         | 描述                                 | 默认值                       |
253  | ---------------------------- | ------------------------------------ | ---------------------------- |
254  | `image`                      | 使用的 Docker 镜像                   | 必填                         |
255  | `timeoutSeconds`             | 自动终止超时时间(服务端 TTL)       | 10 分钟                      |
256  | `entrypoint`                 | 容器启动入口命令                     | `["tail","-f","/dev/null"]`  |
257  | `resource`                   | CPU/内存限制(字符串 map)           | `{"cpu":"1","memory":"2Gi"}` |
258  | `env`                        | 环境变量                             | `{}`                         |
259  | `metadata`                   | 自定义元数据标签                     | `{}`                         |
260  | `networkPolicy`              | 可选的出站网络策略(egress)         | -                            |
261  | `extensions`                 | 额外的服务端扩展字段                 | `{}`                         |
262  | `skipHealthCheck`            | 跳过就绪检测(`Running` + 健康检查) | `false`                      |
263  | `healthCheck`                | 自定义就绪检查                       | -                            |
264  | `readyTimeoutSeconds`        | 等待就绪最大时间                     | 30 秒                        |
265  | `healthCheckPollingInterval` | 就绪轮询间隔(毫秒)                 | 200 ms                       |
266  
267  注意:`opensandbox.io/` 前缀下的 metadata key 属于系统保留标签,服务端会拒绝用户传入。
268  
269  ```ts
270  const sandbox = await Sandbox.create({
271    connectionConfig: config,
272    image: "python:3.11",
273    networkPolicy: {
274      defaultAction: "deny",
275      egress: [{ action: "allow", target: "pypi.org" }],
276    },
277  });
278  ```
279  
280  ### 3. 运行时 Egress 策略更新
281  
282  运行时的 egress 查询和 patch 会直接访问沙箱内的 egress sidecar。
283  SDK 会先解析 `18080` 端口对应的 sandbox endpoint,再调用 sidecar 的 `/policy` API。
284  
285  ```ts
286  const policy = await sandbox.getEgressPolicy();
287  
288  await sandbox.patchEgressRules([
289    { action: "allow", target: "www.github.com" },
290    { action: "deny", target: "pypi.org" },
291  ]);
292  ```
293  
294  ### 4. 资源清理
295  
296  在 Node.js 环境下,`Sandbox` 和 `SandboxManager` 会拥有各自的 HTTP agent,因此即使多个实例共享同一个 `ConnectionConfig` 也不会互相影响。SDK 会借助 `ConnectionConfig.withTransportIfMissing()` 复刻每个实例的 transport。完成使用后调用 `sandbox.close()` / `manager.close()` 来释放底层连接池;
297  
298  ## 浏览器注意事项
299  
300  - SDK 可在浏览器运行,但**流式文件上传仅支持 Node**。
301  - 如果 `writeFiles` 传入 `ReadableStream` 或 `AsyncIterable`,浏览器会回退为**先缓存在内存,再上传**。
302  - 原因:浏览器不支持以自定义 boundary 的 `multipart/form-data` 流式请求体(execd 上传接口需要此能力)。