Skip to content

Commit ceef514

Browse files
committed
feat(sandbox): add template file upload with sandbox_template source
1 parent 0dde03c commit ceef514

11 files changed

Lines changed: 507 additions & 18 deletions

File tree

packages/cli/src/commands.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ import {
208208
managedAgentFileDelete,
209209
sandboxCreate,
210210
sandboxOfficialImages,
211+
sandboxFileUpload,
211212
sandboxList,
212213
sandboxGet,
213214
sandboxConnect,
@@ -439,6 +440,7 @@ export const commands: Record<string, AnyCommand> = {
439440
"managed-agent file delete": managedAgentFileDelete,
440441
"sandbox create": sandboxCreate,
441442
"sandbox official-images": sandboxOfficialImages,
443+
"sandbox file upload": sandboxFileUpload,
442444
"sandbox list": sandboxList,
443445
"sandbox get": sandboxGet,
444446
"sandbox connect": sandboxConnect,
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { readFile } from "node:fs/promises";
2+
import { basename } from "node:path";
3+
import {
4+
agentStudioFilesPath,
5+
BailianError,
6+
defineCommand,
7+
detectOutputFormat,
8+
ExitCode,
9+
sandboxBaseUrl,
10+
} from "bailian-cli-core";
11+
import { emitBare, emitResult } from "bailian-cli-runtime";
12+
import { redactConnectionCredentials, resolveWorkspaceId, WORKSPACE_FLAG } from "./shared.ts";
13+
14+
const UPLOAD_SOURCE = "sandbox_template";
15+
16+
export const sandboxFileUpload = defineCommand({
17+
description: {
18+
"en-US": "Upload a workspace file for Sandbox template mounts",
19+
"zh-CN": "上传工作空间文件,供 Sandbox 模版挂载使用",
20+
},
21+
auth: "apiKey",
22+
usageArgs: "--path <path> [--filename <name>] [--mime-type <type>]",
23+
flags: {
24+
...WORKSPACE_FLAG,
25+
path: {
26+
type: "string",
27+
valueHint: "<path>",
28+
required: true,
29+
description: { "en-US": "Local file path", "zh-CN": "本地文件路径" },
30+
},
31+
filename: {
32+
type: "string",
33+
valueHint: "<name>",
34+
description: { "en-US": "Remote filename override", "zh-CN": "覆盖远端文件名" },
35+
},
36+
mimeType: {
37+
type: "string",
38+
valueHint: "<type>",
39+
description: {
40+
"en-US": "Multipart file MIME type (default: application/octet-stream)",
41+
"zh-CN": "Multipart 文件部分的 MIME 类型(默认:application/octet-stream)",
42+
},
43+
},
44+
},
45+
exampleArgs: [
46+
"--path ./config.json --output json",
47+
"--path ./config.json --quiet",
48+
"--path ./notes.txt --filename notes.txt --mime-type text/plain --dry-run --output json",
49+
],
50+
notes: [
51+
{
52+
"en-US":
53+
"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.",
54+
"zh-CN":
55+
"向 /api/v1/agentstudio/files 发送 multipart 字段 file 和 source=sandbox_template。使用百炼 Bearer API Key,不使用 Console 鉴权或 E2B Key;无需 agents.yaml。",
56+
},
57+
{
58+
"en-US":
59+
"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.",
60+
"zh-CN":
61+
"Base URL 沿用 Sandbox:--base-url > DASHSCOPE_BASE_URL > 登录/Profile 的 base_url。未配置时,按 --workspace-id > BAILIAN_WORKSPACE_ID > 配置项 workspace_id 选择 cn-beijing 地址。上传路径不带 /sandbox 前缀。",
62+
},
63+
{
64+
"en-US":
65+
"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.",
66+
"zh-CN":
67+
"上传响应返回后立即输出,--quiet 仅输出 id。不会等待安全审核:status=checking 不代表已可挂载。在同一工作空间的 template create/update --body 中,将可用文件的 id 填入 mntConfig[].originFileId,同时传入 mountPath 和可选的 originFileName。此命令不向运行中的实例传文件。",
68+
},
69+
{
70+
"en-US":
71+
"--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.",
72+
"zh-CN":
73+
"--dry-run 仅预览 Endpoint、source 和本地路径,不读取或上传文件。MIME 类型检测和上传限制由服务端执行。",
74+
},
75+
],
76+
async run(ctx) {
77+
const endpoint = ctx.client.url(agentStudioFilesPath(), () =>
78+
sandboxBaseUrl(resolveWorkspaceId(ctx)),
79+
);
80+
const filename = ctx.flags.filename ?? basename(ctx.flags.path);
81+
const mimeType = ctx.flags.mimeType ?? "application/octet-stream";
82+
const format = detectOutputFormat(ctx.settings.output);
83+
if (ctx.settings.dryRun) {
84+
emitResult(
85+
{
86+
method: "POST",
87+
endpoint,
88+
request: { source: UPLOAD_SOURCE, file: { path: ctx.flags.path, filename, mimeType } },
89+
},
90+
format,
91+
);
92+
return;
93+
}
94+
95+
const content = new Uint8Array(await readFile(ctx.flags.path));
96+
const form = new FormData();
97+
form.append("source", UPLOAD_SOURCE);
98+
form.append("file", new Blob([content], { type: mimeType }), filename);
99+
const file = await ctx.client.requestJson<Record<string, unknown>>({
100+
method: "POST",
101+
path: endpoint,
102+
body: form,
103+
});
104+
if (!file || typeof file.id !== "string" || !file.id.trim()) {
105+
throw new BailianError(
106+
"Upload response did not contain a File ID. / 上传响应未包含 File ID。",
107+
ExitCode.GENERAL,
108+
);
109+
}
110+
if (ctx.settings.quiet) emitBare(file.id);
111+
else emitResult(redactConnectionCredentials(file), format);
112+
},
113+
});

packages/commands/src/commands/sandbox/template.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -386,8 +386,10 @@ export const sandboxTemplateCreate = defineCommand({
386386
"--image 覆盖 body 中的 fromImage/imageName;显式 --from-image/--image-name 再覆盖对应预设字段。不传 --image 时保持原有镜像行为。",
387387
},
388388
{
389-
"en-US": "File mounts and other complete nested structures can be supplied through --body.",
390-
"zh-CN": "文件挂载等完整嵌套结构可通过 --body 提供。",
389+
"en-US":
390+
"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.",
391+
"zh-CN":
392+
"挂载本地文件前先调用 sandbox file upload;将可用文件的 id 填入 --body 的 mntConfig[].originFileId,并传入 mountPath 和可选的 originFileName。上传和模版须位于同一工作空间。其他完整嵌套结构也可通过 --body 提供。",
391393
},
392394
{
393395
"en-US":
@@ -521,6 +523,12 @@ export const sandboxTemplateUpdate = defineCommand({
521523
"en-US": "Supplying envConfig or --env replaces the template's complete environment map.",
522524
"zh-CN": "传入 envConfig 或 --env 会整体替换模版的环境变量 Map。",
523525
},
526+
{
527+
"en-US":
528+
"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.",
529+
"zh-CN":
530+
"本地挂载文件先通过 sandbox file upload 上传。在 --body 的 mntConfig[] 中,将 originFileId 设为可用文件的 id 并传入 mountPath;上传和模版须位于同一工作空间。",
531+
},
524532
{
525533
"en-US":
526534
"By default the command waits for build status ready; --async returns the submitted build immediately.",

packages/commands/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ export { default as skillRemove } from "./commands/skill/remove.ts";
224224
export { default as skillList } from "./commands/skill/list.ts";
225225
export { default as skillInit } from "./commands/skill/init.ts";
226226
export { sandboxOfficialImages } from "./commands/sandbox/images.ts";
227+
export { sandboxFileUpload } from "./commands/sandbox/file.ts";
227228
export {
228229
sandboxConnect,
229230
sandboxCreate,

0 commit comments

Comments
 (0)