feat(sandbox): add template file upload with sandbox_template source

This commit is contained in:
chenanran555
2026-09-09 17:35:23 +08:00
parent 0dde03c783
commit ceef514a50
11 changed files with 507 additions and 18 deletions
+2
View File
@@ -208,6 +208,7 @@ import {
managedAgentFileDelete,
sandboxCreate,
sandboxOfficialImages,
sandboxFileUpload,
sandboxList,
sandboxGet,
sandboxConnect,
@@ -439,6 +440,7 @@ export const commands: Record<string, AnyCommand> = {
"managed-agent file delete": managedAgentFileDelete,
"sandbox create": sandboxCreate,
"sandbox official-images": sandboxOfficialImages,
"sandbox file upload": sandboxFileUpload,
"sandbox list": sandboxList,
"sandbox get": sandboxGet,
"sandbox connect": sandboxConnect,
@@ -0,0 +1,113 @@
import { readFile } from "node:fs/promises";
import { basename } from "node:path";
import {
agentStudioFilesPath,
BailianError,
defineCommand,
detectOutputFormat,
ExitCode,
sandboxBaseUrl,
} from "bailian-cli-core";
import { emitBare, emitResult } from "bailian-cli-runtime";
import { redactConnectionCredentials, resolveWorkspaceId, WORKSPACE_FLAG } from "./shared.ts";
const UPLOAD_SOURCE = "sandbox_template";
export const sandboxFileUpload = defineCommand({
description: {
"en-US": "Upload a workspace file for Sandbox template mounts",
"zh-CN": "上传工作空间文件,供 Sandbox 模版挂载使用",
},
auth: "apiKey",
usageArgs: "--path <path> [--filename <name>] [--mime-type <type>]",
flags: {
...WORKSPACE_FLAG,
path: {
type: "string",
valueHint: "<path>",
required: true,
description: { "en-US": "Local file path", "zh-CN": "本地文件路径" },
},
filename: {
type: "string",
valueHint: "<name>",
description: { "en-US": "Remote filename override", "zh-CN": "覆盖远端文件名" },
},
mimeType: {
type: "string",
valueHint: "<type>",
description: {
"en-US": "Multipart file MIME type (default: application/octet-stream)",
"zh-CN": "Multipart 文件部分的 MIME 类型(默认:application/octet-stream)",
},
},
},
exampleArgs: [
"--path ./config.json --output json",
"--path ./config.json --quiet",
"--path ./notes.txt --filename notes.txt --mime-type text/plain --dry-run --output json",
],
notes: [
{
"en-US":
"POST /api/v1/agentstudio/files with multipart fields file and source=sandbox_template. Uses a Bailian Bearer API Key, not Console authentication or an E2B key; no agents.yaml is needed.",
"zh-CN":
"向 /api/v1/agentstudio/files 发送 multipart 字段 file 和 source=sandbox_template。使用百炼 Bearer API Key,不使用 Console 鉴权或 E2B Key;无需 agents.yaml。",
},
{
"en-US":
"Base URL follows Sandbox: --base-url > DASHSCOPE_BASE_URL > login/profile base_url. Without one, --workspace-id > BAILIAN_WORKSPACE_ID > config workspace_id selects the cn-beijing origin. The upload path has no /sandbox prefix.",
"zh-CN":
"Base URL 沿用 Sandbox:--base-url > DASHSCOPE_BASE_URL > 登录/Profile 的 base_url。未配置时,按 --workspace-id > BAILIAN_WORKSPACE_ID > 配置项 workspace_id 选择 cn-beijing 地址。上传路径不带 /sandbox 前缀。",
},
{
"en-US":
"Returns the upload response immediately; --quiet prints only its id. Upload does not wait for security review: status=checking is not ready to mount. Use an available file's id as mntConfig[].originFileId in template create/update --body, together with mountPath and optional originFileName, in the same workspace. This does not transfer files into a running instance.",
"zh-CN":
"上传响应返回后立即输出,--quiet 仅输出 id。不会等待安全审核:status=checking 不代表已可挂载。在同一工作空间的 template create/update --body 中,将可用文件的 id 填入 mntConfig[].originFileId,同时传入 mountPath 和可选的 originFileName。此命令不向运行中的实例传文件。",
},
{
"en-US":
"--dry-run previews the endpoint, source, and local path without reading or uploading the file. The service detects the MIME type and enforces upload limits.",
"zh-CN":
"--dry-run 仅预览 Endpoint、source 和本地路径,不读取或上传文件。MIME 类型检测和上传限制由服务端执行。",
},
],
async run(ctx) {
const endpoint = ctx.client.url(agentStudioFilesPath(), () =>
sandboxBaseUrl(resolveWorkspaceId(ctx)),
);
const filename = ctx.flags.filename ?? basename(ctx.flags.path);
const mimeType = ctx.flags.mimeType ?? "application/octet-stream";
const format = detectOutputFormat(ctx.settings.output);
if (ctx.settings.dryRun) {
emitResult(
{
method: "POST",
endpoint,
request: { source: UPLOAD_SOURCE, file: { path: ctx.flags.path, filename, mimeType } },
},
format,
);
return;
}
const content = new Uint8Array(await readFile(ctx.flags.path));
const form = new FormData();
form.append("source", UPLOAD_SOURCE);
form.append("file", new Blob([content], { type: mimeType }), filename);
const file = await ctx.client.requestJson<Record<string, unknown>>({
method: "POST",
path: endpoint,
body: form,
});
if (!file || typeof file.id !== "string" || !file.id.trim()) {
throw new BailianError(
"Upload response did not contain a File ID. / 上传响应未包含 File ID。",
ExitCode.GENERAL,
);
}
if (ctx.settings.quiet) emitBare(file.id);
else emitResult(redactConnectionCredentials(file), format);
},
});
@@ -386,8 +386,10 @@ export const sandboxTemplateCreate = defineCommand({
"--image 覆盖 body 中的 fromImage/imageName;显式 --from-image/--image-name 再覆盖对应预设字段。不传 --image 时保持原有镜像行为。",
},
{
"en-US": "File mounts and other complete nested structures can be supplied through --body.",
"zh-CN": "文件挂载等完整嵌套结构可通过 --body 提供。",
"en-US":
"For local file mounts, use sandbox file upload first; put the available file's id into --body mntConfig[].originFileId with mountPath and optional originFileName. Upload and template must use the same workspace. Other complete nested structures can also be supplied through --body.",
"zh-CN":
"挂载本地文件前先调用 sandbox file upload;将可用文件的 id 填入 --body 的 mntConfig[].originFileId,并传入 mountPath 和可选的 originFileName。上传和模版须位于同一工作空间。其他完整嵌套结构也可通过 --body 提供。",
},
{
"en-US":
@@ -521,6 +523,12 @@ export const sandboxTemplateUpdate = defineCommand({
"en-US": "Supplying envConfig or --env replaces the template's complete environment map.",
"zh-CN": "传入 envConfig 或 --env 会整体替换模版的环境变量 Map。",
},
{
"en-US":
"Use sandbox file upload for local mount files. In --body mntConfig[], set originFileId to the available file's id and supply mountPath; upload and template must use the same workspace.",
"zh-CN":
"本地挂载文件先通过 sandbox file upload 上传。在 --body 的 mntConfig[] 中,将 originFileId 设为可用文件的 id 并传入 mountPath;上传和模版须位于同一工作空间。",
},
{
"en-US":
"By default the command waits for build status ready; --async returns the submitted build immediately.",
+1
View File
@@ -224,6 +224,7 @@ export { default as skillRemove } from "./commands/skill/remove.ts";
export { default as skillList } from "./commands/skill/list.ts";
export { default as skillInit } from "./commands/skill/init.ts";
export { sandboxOfficialImages } from "./commands/sandbox/images.ts";
export { sandboxFileUpload } from "./commands/sandbox/file.ts";
export {
sandboxConnect,
sandboxCreate,
@@ -0,0 +1,312 @@
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { createServer, type IncomingHttpHeaders, type Server } from "node:http";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, test } from "vite-plus/test";
import { parseStdoutJson, runCommandE2e, runCommandHelp } from "./helpers.ts";
import { SANDBOX_ROUTES } from "./topic-routes.ts";
const directories: string[] = [];
const servers: Server[] = [];
const FILE_PATH = "/api/v1/agentstudio/files";
const UPLOAD_COMMAND = ["sandbox", "file", "upload"];
const RESPONSE = {
id: "file_sandbox_test",
filename: "config.json",
type: "file",
status: "available",
mime_type: "application/json",
size_bytes: 12,
requestId: "request-test",
};
afterEach(async () => {
await Promise.all(
servers.splice(0).map(
(server) =>
new Promise<void>((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()));
}),
),
);
for (const directory of directories.splice(0)) {
rmSync(directory, { recursive: true, force: true });
}
});
function setup(config: Record<string, unknown> = {}) {
const directory = mkdtempSync(join(tmpdir(), "bl-sandbox-file-"));
directories.push(directory);
writeFileSync(join(directory, "config.json"), JSON.stringify(config));
return {
directory,
env: {
BAILIAN_CONFIG_DIR: directory,
DASHSCOPE_API_KEY: "",
DASHSCOPE_BASE_URL: "",
BAILIAN_WORKSPACE_ID: "",
},
};
}
async function gateway(responseBody: unknown = RESPONSE, status = 200) {
const received: {
method?: string;
path?: string;
headers: IncomingHttpHeaders;
body: Buffer;
}[] = [];
const server = createServer((request, response) => {
const chunks: Buffer[] = [];
request.on("data", (chunk: Buffer) => chunks.push(chunk));
request.on("end", () => {
received.push({
method: request.method,
path: request.url,
headers: request.headers,
body: Buffer.concat(chunks),
});
response.writeHead(status, { "content-type": "application/json" });
response.end(JSON.stringify(responseBody));
});
});
await new Promise<void>((resolve, reject) => {
server.once("error", reject);
server.listen(0, "127.0.0.1", resolve);
});
servers.push(server);
const address = server.address();
if (!address || typeof address === "string") throw new Error("Expected a local TCP server.");
return { origin: `http://127.0.0.1:${address.port}`, received };
}
describe("e2e: Sandbox template file upload", () => {
test("help documents upload auth, source, mount mapping, and the runtime boundary", async () => {
const result = await runCommandHelp(SANDBOX_ROUTES, [...UPLOAD_COMMAND, "--help"]);
expect(result.exitCode).toBe(0);
for (const text of [
"bl sandbox file upload",
"--path",
"--filename",
"--mime-type",
"--base-url",
"--workspace-id",
"source=sandbox_template",
FILE_PATH,
"mntConfig[].originFileId",
"status=checking",
"running instance",
]) {
expect(result.stderr).toContain(text);
}
expect(result.stderr).not.toContain("--console-site");
expect(result.stderr).not.toContain("--yes");
});
test("requires --path and does not expose a source override", async () => {
const { env } = setup();
for (const args of [[], ["--path", "unused", "--source", "other"]]) {
const result = await runCommandE2e(
SANDBOX_ROUTES,
[...UPLOAD_COMMAND, ...args, "--output", "json"],
env,
);
expect(result.exitCode).toBe(2);
}
});
test.each(["flag", "env", "profile", "workspace"])(
"%s determines the upload origin; dry-run does not read the local file or need credentials",
async (source) => {
const selectedOrigin = `https://${source}.example.test:8443`;
const { directory, env } = setup(
source === "profile"
? { active_config: "sandbox", sandbox: { base_url: selectedOrigin } }
: {},
);
const path = join(directory, "does-not-exist.json");
const args =
source === "flag"
? ["--base-url", `${selectedOrigin}/api/v1/agentstudio/sandbox?ignored=1#fragment`]
: source === "workspace"
? ["--workspace-id", "ws-files"]
: [];
const result = await runCommandE2e(
SANDBOX_ROUTES,
[...UPLOAD_COMMAND, "--path", path, ...args, "--dry-run", "--output", "json"],
{ ...env, DASHSCOPE_BASE_URL: source === "env" ? selectedOrigin : "" },
);
expect(result.exitCode, result.stderr).toBe(0);
expect(parseStdoutJson(result.stdout)).toEqual({
method: "POST",
endpoint:
(source === "workspace"
? "https://ws-files.cn-beijing.maas.aliyuncs.com"
: selectedOrigin) + FILE_PATH,
request: {
source: "sandbox_template",
file: { path, filename: "does-not-exist.json", mimeType: "application/octet-stream" },
},
});
},
);
test("uploads exact multipart bytes and source with saved Bearer credentials, then reuses the ID in a template mount", async () => {
const { origin, received } = await gateway();
const { directory, env } = setup({
active_config: "sandbox",
sandbox: { api_key: "sk-upload-test-only", base_url: origin },
});
const path = join(directory, "local data.bin");
const content = Buffer.from([0, 1, 255, 13, 10, 65]);
writeFileSync(path, content);
const result = await runCommandE2e(
SANDBOX_ROUTES,
[
...UPLOAD_COMMAND,
"--path",
path,
"--filename",
"config.json",
"--mime-type",
"application/json",
"--output",
"json",
"--verbose",
],
env,
);
expect(result.exitCode, result.stderr).toBe(0);
expect(parseStdoutJson(result.stdout)).toEqual(RESPONSE);
expect(result.stdout + result.stderr).not.toContain("sk-upload-test-only");
expect(received).toHaveLength(1);
const request = received[0];
expect(request.method).toBe("POST");
expect(request.path).toBe(FILE_PATH);
expect(request.headers.authorization).toBe("Bearer sk-upload-test-only");
expect(request.headers["x-api-key"]).toBeUndefined();
expect(request.headers["content-type"]).toMatch(/^multipart\/form-data; boundary=/);
const form = await new Request(`${origin}${FILE_PATH}`, {
method: "POST",
headers: { "content-type": request.headers["content-type"]! },
body: new Uint8Array(request.body),
}).formData();
expect([...form.keys()].sort()).toEqual(["file", "source"]);
expect(form.get("source")).toBe("sandbox_template");
const file = form.get("file");
if (!file || typeof file === "string") throw new Error("Expected a multipart file.");
expect(file.name).toBe("config.json");
expect(file.type).toBe("application/json");
expect(Buffer.from(await file.arrayBuffer())).toEqual(content);
const mount = {
originFileId: RESPONSE.id,
originFileName: RESPONSE.filename,
mountPath: "/home/user/config.json",
};
const template = await runCommandE2e(
SANDBOX_ROUTES,
[
"sandbox",
"template",
"create",
"--name",
"files",
"--cpu-count",
"1",
"--memory-mb",
"2048",
"--body",
JSON.stringify({ mntConfig: [mount] }),
"--dry-run",
"--output",
"json",
],
env,
);
expect(template.exitCode, template.stderr).toBe(0);
expect(parseStdoutJson(template.stdout)).toMatchObject({ request: { mntConfig: [mount] } });
expect(received).toHaveLength(1);
});
test.each(["json", "text", "quiet"])(
"%s output returns checking status or the bare ID without polling or creating a template",
async (output) => {
const pending = { ...RESPONSE, status: "checking" };
const { origin, received } = await gateway(pending);
const { directory, env } = setup({ api_key: "sk-upload-test-only", base_url: origin });
const path = join(directory, "notes.txt");
writeFileSync(path, "Only synthetic test data.");
const result = await runCommandE2e(
SANDBOX_ROUTES,
[
...UPLOAD_COMMAND,
"--path",
path,
...(output === "quiet" ? ["--quiet"] : ["--output", output]),
],
env,
);
expect(result.exitCode, result.stderr).toBe(0);
if (output === "quiet") expect(result.stdout.trim()).toBe(RESPONSE.id);
else {
expect(result.stdout).toContain("checking");
expect(result.stdout).toContain(RESPONSE.id);
}
expect(received).toHaveLength(1);
},
);
test("missing files fail locally without an upload request", async () => {
const { origin, received } = await gateway();
const { directory, env } = setup({ api_key: "sk-upload-test-only", base_url: origin });
const result = await runCommandE2e(
SANDBOX_ROUTES,
[...UPLOAD_COMMAND, "--path", join(directory, "missing.txt"), "--output", "json"],
env,
);
expect(result.exitCode).toBe(1);
expect(result.stderr).toContain("ENOENT");
expect(received).toHaveLength(0);
});
test("server errors are passed through without translating them", async () => {
const { origin, received } = await gateway(
{ code: "InvalidSource", message: "source rejected by server", request_id: "req-error" },
400,
);
const { directory, env } = setup({ api_key: "sk-upload-test-only", base_url: origin });
const path = join(directory, "notes.txt");
writeFileSync(path, "test");
const result = await runCommandE2e(
SANDBOX_ROUTES,
[...UPLOAD_COMMAND, "--path", path, "--output", "json"],
env,
);
expect(result.exitCode).toBe(1);
expect(JSON.parse(result.stderr)).toMatchObject({
error: {
message: "source rejected by server",
http_status: 400,
api_code: "InvalidSource",
request_id: "req-error",
},
});
expect(received).toHaveLength(1);
});
test("a response without an ID is not reported as a successful upload", async () => {
const { origin } = await gateway({ status: "checking" });
const { directory, env } = setup({ api_key: "sk-upload-test-only", base_url: origin });
const path = join(directory, "notes.txt");
writeFileSync(path, "test");
const result = await runCommandE2e(
SANDBOX_ROUTES,
[...UPLOAD_COMMAND, "--path", path, "--quiet", "--output", "json"],
env,
);
expect(result.exitCode).toBe(1);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("File ID");
});
});
@@ -185,6 +185,7 @@ export const SKILL_ROUTES: E2eRouteExports = {
export const SANDBOX_ROUTES: E2eRouteExports = {
"sandbox create": "sandboxCreate",
"sandbox official-images": "sandboxOfficialImages",
"sandbox file upload": "sandboxFileUpload",
"sandbox list": "sandboxList",
"sandbox get": "sandboxGet",
"sandbox connect": "sandboxConnect",
+5
View File
@@ -228,6 +228,11 @@ export function ragEndpoint(workspaceId: string, path: string): string {
// ---- Sandbox control plane (workspace-based host, cn-beijing only) ----
/** AgentStudio workspace files; template uploads send source=sandbox_template. */
export function agentStudioFilesPath(): string {
return "/api/v1/agentstudio/files";
}
/** Default Sandbox origin when no shared base URL was configured (cn-beijing only). */
export function sandboxBaseUrl(workspaceId: string): string {
return `https://${workspaceId}.cn-beijing.maas.aliyuncs.com`;
+1
View File
@@ -18,6 +18,7 @@ export {
profileSchemaPath,
ragEndpoint,
RAG_PATHS,
agentStudioFilesPath,
sandboxEndpoint,
sandboxBaseUrl,
sandboxApiPath,
+14 -12
View File
@@ -6,7 +6,7 @@ metadata:
bins: ["bl"]
description: >-
阿里云百炼 Sandbox 沙箱实例与模版生命周期管理入口:用户要创建、查询、连接、暂停、恢复或释放百炼沙箱,
或查看内置基础镜像、创建、更新、查询、删除沙箱模版、查看模版构建状态时,使用 `bl sandbox`。
或查看内置基础镜像、上传模版挂载文件、创建、更新、查询、删除沙箱模版、查看模版构建状态时,使用 `bl sandbox`。
仅覆盖百炼 Sandbox 管控面;不用于宿主执行沙箱设置、E2B 官方云资源或沙箱内命令执行与文件传输。
agents.yaml 托管 Agent / Session / Environment 管理交给 bailian-managed-agent。
官方安装:`bl skill init`(与共享协议 bailian-protocol 同装)。
@@ -19,21 +19,22 @@ Before running `bl`, read the shared [bailian-protocol](../bailian-protocol/SKIL
## Scope and setup
- Manage Sandbox instances and templates through Bailian's E2B-compatible REST control plane. No E2B SDK or E2B API key is required; authentication uses the Bailian API Key as an Authorization Bearer token.
- Resolve Base URL through the same CLI chain as Managed Agent: `--base-url` > `DASHSCOPE_BASE_URL` > login/profile `base_url`. Use an origin such as `https://workspace.cn-beijing.maas.aliyuncs.com`; the CLI strips URL paths/query/fragment and appends `/api/v1/agentstudio/sandbox`. The saved API Key is reused. Profile capability fallback follows the shared protocol for both the key and Base URL.
- Resolve Base URL through the same CLI chain as Managed Agent: `--base-url` > `DASHSCOPE_BASE_URL` > login/profile `base_url`. Use an origin such as `https://workspace.cn-beijing.maas.aliyuncs.com`; the CLI strips URL paths/query/fragment and appends `/api/v1/agentstudio/sandbox` for lifecycle operations, or `/api/v1/agentstudio/files` for template file uploads. The saved API Key is reused. Profile capability fallback follows the shared protocol for both the key and Base URL.
- If no Base URL is configured, resolve the workspace from `--workspace-id`, then `BAILIAN_WORKSPACE_ID`, then configured `workspace_id`, and use `https://{workspace_id}.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio/sandbox`. With a configured Base URL, the workspace flag is optional. The service currently supports `cn-beijing` and requires prior Sandbox SLR authorization.
- No `agents.yaml` or local IaC state is required. `connect` returns instance connection information; it does not open an interactive shell. Do not invent commands for executing code or transferring files inside the sandbox.
## Choose the operation
| User intent | Command family |
| ------------------------------------- | --------------------------------------------------------- |
| Discover built-in base image presets | `bl sandbox official-images` (offline, no authentication) |
| Inspect or create instances | `bl sandbox list` / `get` / `create` |
| Connect, pause, or resume an instance | `bl sandbox connect` / `pause` / `resume` |
| Release an instance | `bl sandbox delete` |
| Inspect or build templates | `bl sandbox template list` / `get` / `create` / `update` |
| Check a submitted template build | `bl sandbox template build-status` |
| Delete a template | `bl sandbox template delete` |
| User intent | Command family |
| --------------------------------------- | --------------------------------------------------------- |
| Discover built-in base image presets | `bl sandbox official-images` (offline, no authentication) |
| Upload a local file for template mounts | `bl sandbox file upload` |
| Inspect or create instances | `bl sandbox list` / `get` / `create` |
| Connect, pause, or resume an instance | `bl sandbox connect` / `pause` / `resume` |
| Release an instance | `bl sandbox delete` |
| Inspect or build templates | `bl sandbox template list` / `get` / `create` / `update` |
| Check a submitted template build | `bl sandbox template build-status` |
| Delete a template | `bl sandbox template delete` |
Read [reference/index.md](reference/index.md) and the relevant section of [reference/sandbox.md](reference/sandbox.md) for exact flags, usage, and examples, or run the matching command with `--help`. Do not guess flags.
@@ -48,7 +49,8 @@ For built-in template images, discover the preset ID or Chinese name through `sa
- Connection credentials are redacted by default. Use `--show-credentials` only when the user explicitly needs the connection tokens, and keep them out of chat summaries, logs, and committed files.
- Template create/update wait by polling the build-status endpoint, not template details. `--async` returns after the submission response with `templateID` / `buildID`; it does not mean the build is ready. Use those IDs with `template build-status` to check completion.
- Global `--timeout` limits HTTP requests and total template-build polling. `--instance-timeout` sets instance lifetime; these are different limits. A polling timeout does not prove the remote build failed or stopped; check its status before submitting another build.
- `--body` accepts a JSON object inline or through `@path`; explicit flags override body fields. Template file mounts require workspace File IDs, not temporary `oss://` URLs from `bl file upload`.
- `--body` accepts a JSON object inline or through `@path`; explicit flags override body fields. For local template mounts, first use `bl sandbox file upload`: it sends multipart `file` and fixed `source=sandbox_template` directly to `/api/v1/agentstudio/files` with the Bailian API Key. Use the returned `id` as `mntConfig[].originFileId`, with `mountPath` and optional `originFileName`, in template create/update `--body`. The upload and template must use the same workspace. This is not the temporary OSS upload from `bl file upload` or a transfer into a running instance.
- File upload returns immediately after the upload response and does not poll security review. `status=checking` is not ready to mount; only use files whose status is `available`. `--quiet` returns the File ID only; inspect the normal/JSON response for status. Upload alone does not authorize creating a template or instance.
## Common hand-offs
+4 -3
View File
@@ -14,6 +14,7 @@ Use this index for the skill-scoped quick index and global flags.
| `bl sandbox connect` | API Key | Connect to a Sandbox instance and return connection information | [sandbox.md](sandbox.md) |
| `bl sandbox create` | API Key | Create a Sandbox instance | [sandbox.md](sandbox.md) |
| `bl sandbox delete` | API Key | Release a Sandbox instance | [sandbox.md](sandbox.md) |
| `bl sandbox file upload` | API Key | Upload a workspace file for Sandbox template mounts | [sandbox.md](sandbox.md) |
| `bl sandbox get` | API Key | Get Sandbox instance details | [sandbox.md](sandbox.md) |
| `bl sandbox list` | API Key | List Sandbox instances | [sandbox.md](sandbox.md) |
| `bl sandbox official-images` | No Auth | List the built-in Sandbox base images (offline) | [sandbox.md](sandbox.md) |
@@ -28,9 +29,9 @@ Use this index for the skill-scoped quick index and global flags.
## By group
| Group | Commands | Reference |
| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
| `sandbox` | `connect`, `create`, `delete`, `get`, `list`, `official-images`, `pause`, `resume`, `template build-status`, `template create`, `template delete`, `template get`, `template list`, `template update` | [sandbox.md](sandbox.md) |
| Group | Commands | Reference |
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
| `sandbox` | `connect`, `create`, `delete`, `file upload`, `get`, `list`, `official-images`, `pause`, `resume`, `template build-status`, `template create`, `template delete`, `template get`, `template list`, `template update` | [sandbox.md](sandbox.md) |
## Global flags
+44 -1
View File
@@ -12,6 +12,7 @@ Index: [index.md](index.md)
| `bl sandbox connect` | API Key | Connect to a Sandbox instance and return connection information |
| `bl sandbox create` | API Key | Create a Sandbox instance |
| `bl sandbox delete` | API Key | Release a Sandbox instance |
| `bl sandbox file upload` | API Key | Upload a workspace file for Sandbox template mounts |
| `bl sandbox get` | API Key | Get Sandbox instance details |
| `bl sandbox list` | API Key | List Sandbox instances |
| `bl sandbox official-images` | No Auth | List the built-in Sandbox base images (offline) |
@@ -162,6 +163,47 @@ bl sandbox delete --sandbox-id sbx-xxx --dry-run
bl sandbox delete --sandbox-id sbx-xxx --yes
```
### `bl sandbox file upload`
| Field | Value |
| ------------------ | ------------------------------------------------------------------------------- |
| **Name** | `sandbox file upload` |
| **Description** | Upload a workspace file for Sandbox template mounts |
| **Authentication** | API Key |
| **Usage** | `bl sandbox file upload --path <path> [--filename <name>] [--mime-type <type>]` |
#### Flags
| Flag | Type | Required | Description |
| --------------------- | ------ | -------- | ---------------------------------------------------------------------------------- |
| `--workspace-id <id>` | string | no | Workspace ID for the default Sandbox endpoint; optional with a configured base URL |
| `--path <path>` | string | yes | Local file path |
| `--filename <name>` | string | no | Remote filename override |
| `--mime-type <type>` | string | no | Multipart file MIME type (default: application/octet-stream) |
| `--api-key <key>` | string | no | API key |
| `--base-url <url>` | string | no | API base URL |
#### Notes
- POST /api/v1/agentstudio/files with multipart fields file and source=sandbox_template. Uses a Bailian Bearer API Key, not Console authentication or an E2B key; no agents.yaml is needed.
- Base URL follows Sandbox: --base-url > DASHSCOPE_BASE_URL > login/profile base_url. Without one, --workspace-id > BAILIAN_WORKSPACE_ID > config workspace_id selects the cn-beijing origin. The upload path has no /sandbox prefix.
- Returns the upload response immediately; --quiet prints only its id. Upload does not wait for security review: status=checking is not ready to mount. Use an available file's id as mntConfig[].originFileId in template create/update --body, together with mountPath and optional originFileName, in the same workspace. This does not transfer files into a running instance.
- --dry-run previews the endpoint, source, and local path without reading or uploading the file. The service detects the MIME type and enforces upload limits.
#### Examples
```bash
bl sandbox file upload --path ./config.json --output json
```
```bash
bl sandbox file upload --path ./config.json --quiet
```
```bash
bl sandbox file upload --path ./notes.txt --filename notes.txt --mime-type text/plain --dry-run --output json
```
### `bl sandbox get`
| Field | Value |
@@ -433,7 +475,7 @@ bl sandbox template build-status --template-id tpl-xxx --build-id build-xxx --ou
- browser (浏览器): Chromium and a visual desktop for clicking, filling forms, and screenshots. fc-e2b-registry.cn-beijing.cr.aliyuncs.com/runtime/browser:v0.0.44
- all-in-one (全能型): Code execution and browser capabilities together. fc-e2b-registry.cn-beijing.cr.aliyuncs.com/runtime/all-in-one:v0.0.44
- --image overrides body fromImage/imageName; explicit --from-image/--image-name override the corresponding preset fields. Without --image, image behavior is unchanged.
- File mounts and other complete nested structures can be supplied through --body.
- For local file mounts, use sandbox file upload first; put the available file's id into --body mntConfig[].originFileId with mountPath and optional originFileName. Upload and template must use the same workspace. Other complete nested structures can also be supplied through --body.
- By default the command waits for build status ready; --async returns the submitted build immediately.
#### Examples
@@ -616,6 +658,7 @@ bl sandbox template list --limit 100 --output json
- all-in-one (全能型): Code execution and browser capabilities together. fc-e2b-registry.cn-beijing.cr.aliyuncs.com/runtime/all-in-one:v0.0.44
- --image overrides body fromImage/imageName; explicit --from-image/--image-name override the corresponding preset fields. Without --image, image behavior is unchanged.
- Supplying envConfig or --env replaces the template's complete environment map.
- Use sandbox file upload for local mount files. In --body mntConfig[], set originFileId to the available file's id and supply mountPath; upload and template must use the same workspace.
- By default the command waits for build status ready; --async returns the submitted build immediately.
#### Examples