mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
fix(sandbox): support shared --base-url configuration with workspace fallback
This commit is contained in:
@@ -3,7 +3,6 @@ import {
|
||||
detectOutputFormat,
|
||||
defineCommand,
|
||||
ExitCode,
|
||||
sandboxEndpoint,
|
||||
SANDBOX_PATHS,
|
||||
sandboxInstanceActionPath,
|
||||
sandboxInstancePath,
|
||||
@@ -20,7 +19,7 @@ import {
|
||||
readRequestBody,
|
||||
redactConnectionCredentials,
|
||||
redactRequestSecrets,
|
||||
resolveWorkspaceId,
|
||||
resolveSandboxEndpoint,
|
||||
SANDBOX_NOTES,
|
||||
setDefined,
|
||||
SHOW_CREDENTIALS_FLAG,
|
||||
@@ -222,14 +221,14 @@ export const sandboxCreate = defineCommand({
|
||||
flags: CREATE_FLAGS,
|
||||
exampleArgs: [
|
||||
"--template-id tpl-xxx --instance-timeout 3600",
|
||||
"--template-id tpl-xxx --base-url https://workspace.cn-beijing.maas.aliyuncs.com",
|
||||
"--body @sandbox.json --dry-run --output json",
|
||||
"--template-id tpl-xxx --show-credentials --output json",
|
||||
],
|
||||
notes: SANDBOX_NOTES,
|
||||
async run(ctx) {
|
||||
const format = detectOutputFormat(ctx.settings.output);
|
||||
const workspaceId = resolveWorkspaceId(ctx);
|
||||
const endpoint = sandboxEndpoint(workspaceId, SANDBOX_PATHS.sandboxes);
|
||||
const endpoint = resolveSandboxEndpoint(ctx, SANDBOX_PATHS.sandboxes);
|
||||
const body = await buildSandboxCreateBody(ctx.flags);
|
||||
if (ctx.settings.dryRun) {
|
||||
emitResult({ method: "POST", endpoint, request: redactRequestSecrets(body) }, format);
|
||||
@@ -265,8 +264,7 @@ export const sandboxList = defineCommand({
|
||||
return undefined;
|
||||
},
|
||||
async run(ctx) {
|
||||
const workspaceId = resolveWorkspaceId(ctx);
|
||||
const url = new URL(sandboxEndpoint(workspaceId, SANDBOX_PATHS.sandboxList));
|
||||
const url = new URL(resolveSandboxEndpoint(ctx, SANDBOX_PATHS.sandboxList));
|
||||
if (ctx.flags.templateId) url.searchParams.set("templateID", ctx.flags.templateId);
|
||||
if (ctx.flags.sandboxId) url.searchParams.set("sandboxID", ctx.flags.sandboxId);
|
||||
if (ctx.flags.state) url.searchParams.set("state", ctx.flags.state);
|
||||
@@ -314,8 +312,7 @@ export const sandboxGet = defineCommand({
|
||||
exampleArgs: ["--sandbox-id sbx-xxx", "--sandbox-id sbx-xxx --show-credentials --output json"],
|
||||
notes: SANDBOX_NOTES,
|
||||
async run(ctx) {
|
||||
const workspaceId = resolveWorkspaceId(ctx);
|
||||
const endpoint = sandboxEndpoint(workspaceId, sandboxInstancePath(ctx.flags.sandboxId));
|
||||
const endpoint = resolveSandboxEndpoint(ctx, sandboxInstancePath(ctx.flags.sandboxId));
|
||||
const response = await ctx.client.requestJson<SandboxInfo>({ path: endpoint, method: "GET" });
|
||||
emitSandboxObject(response, {
|
||||
format: detectOutputFormat(ctx.settings.output),
|
||||
@@ -343,9 +340,8 @@ function connectionCommand(action: "connect" | "resume") {
|
||||
notes: SANDBOX_NOTES,
|
||||
async run(ctx) {
|
||||
const format = detectOutputFormat(ctx.settings.output);
|
||||
const workspaceId = resolveWorkspaceId(ctx);
|
||||
const endpoint = sandboxEndpoint(
|
||||
workspaceId,
|
||||
const endpoint = resolveSandboxEndpoint(
|
||||
ctx,
|
||||
sandboxInstanceActionPath(ctx.flags.sandboxId, action),
|
||||
);
|
||||
const body = await buildConnectionBody(ctx.flags);
|
||||
@@ -379,9 +375,8 @@ export const sandboxPause = defineCommand({
|
||||
notes: SANDBOX_NOTES,
|
||||
async run(ctx) {
|
||||
const format = detectOutputFormat(ctx.settings.output);
|
||||
const workspaceId = resolveWorkspaceId(ctx);
|
||||
const endpoint = sandboxEndpoint(
|
||||
workspaceId,
|
||||
const endpoint = resolveSandboxEndpoint(
|
||||
ctx,
|
||||
sandboxInstanceActionPath(ctx.flags.sandboxId, "pause"),
|
||||
);
|
||||
if (ctx.settings.dryRun) {
|
||||
@@ -410,8 +405,7 @@ export const sandboxDelete = defineCommand({
|
||||
notes: SANDBOX_NOTES,
|
||||
async run(ctx) {
|
||||
const format = detectOutputFormat(ctx.settings.output);
|
||||
const workspaceId = resolveWorkspaceId(ctx);
|
||||
const endpoint = sandboxEndpoint(workspaceId, sandboxInstancePath(ctx.flags.sandboxId));
|
||||
const endpoint = resolveSandboxEndpoint(ctx, sandboxInstancePath(ctx.flags.sandboxId));
|
||||
if (ctx.settings.dryRun) {
|
||||
emitResult({ method: "DELETE", endpoint, request: null }, format);
|
||||
return;
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { BailianError, ExitCode, type FlagsDef, type LocalizedText } from "bailian-cli-core";
|
||||
import {
|
||||
BailianError,
|
||||
ExitCode,
|
||||
sandboxApiPath,
|
||||
sandboxBaseUrl,
|
||||
type Client,
|
||||
type FlagsDef,
|
||||
type LocalizedText,
|
||||
} from "bailian-cli-core";
|
||||
|
||||
export const WORKSPACE_FLAG = {
|
||||
workspaceId: {
|
||||
type: "string",
|
||||
valueHint: "<id>",
|
||||
description: {
|
||||
"en-US": "Workspace ID for the Sandbox endpoint (or set BAILIAN_WORKSPACE_ID)",
|
||||
"zh-CN": "Sandbox Endpoint 使用的 Workspace ID(也可设置 BAILIAN_WORKSPACE_ID)",
|
||||
"en-US": "Workspace ID for the default Sandbox endpoint; optional with a configured base URL",
|
||||
"zh-CN": "默认 Sandbox Endpoint 的 Workspace ID;已配置 Base URL 时可省略",
|
||||
},
|
||||
},
|
||||
} satisfies FlagsDef;
|
||||
@@ -62,8 +70,15 @@ export const SANDBOX_NOTES: LocalizedText[] = [
|
||||
},
|
||||
{
|
||||
"en-US":
|
||||
"The workspace is resolved from --workspace-id, BAILIAN_WORKSPACE_ID, then config workspace_id.",
|
||||
"zh-CN": "Workspace 依次从 --workspace-id、BAILIAN_WORKSPACE_ID、配置项 workspace_id 解析。",
|
||||
"Base URL: --base-url > DASHSCOPE_BASE_URL > login/profile base_url. The CLI uses its origin and appends /api/v1/agentstudio/sandbox; otherwise it uses the workspace-scoped cn-beijing endpoint.",
|
||||
"zh-CN":
|
||||
"Base URL 优先级:--base-url > DASHSCOPE_BASE_URL > 登录/Profile 的 base_url。CLI 取其 origin 并追加 /api/v1/agentstudio/sandbox;未配置时使用工作空间的 cn-beijing Endpoint。",
|
||||
},
|
||||
{
|
||||
"en-US":
|
||||
"Without a configured base URL, workspace is required: --workspace-id > BAILIAN_WORKSPACE_ID > config workspace_id.",
|
||||
"zh-CN":
|
||||
"未配置 Base URL 时必须提供 Workspace:--workspace-id > BAILIAN_WORKSPACE_ID > 配置项 workspace_id。",
|
||||
},
|
||||
{
|
||||
"en-US":
|
||||
@@ -80,17 +95,23 @@ export const SANDBOX_NOTES: LocalizedText[] = [
|
||||
|
||||
export type JsonObject = Record<string, unknown>;
|
||||
|
||||
export function resolveWorkspaceId(ctx: {
|
||||
interface SandboxEndpointContext {
|
||||
flags: { workspaceId?: string };
|
||||
settings: { workspaceId?: string };
|
||||
identity: { binName: string };
|
||||
}): string {
|
||||
client: Pick<Client, "url">;
|
||||
}
|
||||
|
||||
export function resolveSandboxEndpoint(ctx: SandboxEndpointContext, path: string): string {
|
||||
return ctx.client.url(sandboxApiPath(path), () => sandboxBaseUrl(resolveWorkspaceId(ctx)));
|
||||
}
|
||||
|
||||
export function resolveWorkspaceId(ctx: Omit<SandboxEndpointContext, "client">): string {
|
||||
const workspaceId = ctx.flags.workspaceId || ctx.settings.workspaceId;
|
||||
if (!workspaceId) {
|
||||
throw new BailianError(
|
||||
"Workspace ID is required.",
|
||||
"Workspace ID is required when no base URL is configured. / 未配置 Base URL 时必须提供 Workspace ID。",
|
||||
ExitCode.USAGE,
|
||||
`Pass --workspace-id, set BAILIAN_WORKSPACE_ID, or configure: ${ctx.identity.binName} config set workspace_id <id>`,
|
||||
"Pass --workspace-id, set BAILIAN_WORKSPACE_ID, configure workspace_id, or set --base-url. / 请传入 --workspace-id、设置 BAILIAN_WORKSPACE_ID 或 workspace_id 配置,或通过 --base-url 指定地址。",
|
||||
);
|
||||
}
|
||||
return workspaceId;
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
detectOutputFormat,
|
||||
defineCommand,
|
||||
ExitCode,
|
||||
sandboxEndpoint,
|
||||
sandboxApiPath,
|
||||
SANDBOX_PATHS,
|
||||
sandboxTemplateBuildStatusPath,
|
||||
sandboxTemplatePath,
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
POLL_INTERVAL_FLAG,
|
||||
readRequestBody,
|
||||
redactRequestSecrets,
|
||||
resolveWorkspaceId,
|
||||
resolveSandboxEndpoint,
|
||||
SANDBOX_NOTES,
|
||||
setDefined,
|
||||
validateIntegerRange,
|
||||
@@ -312,7 +312,7 @@ function validatePollInterval(flags: { pollInterval?: number }): string | undefi
|
||||
|
||||
async function emitTemplateMutationResult(options: {
|
||||
response: TemplateInfo;
|
||||
workspaceId: string;
|
||||
endpoint: string;
|
||||
client: Client;
|
||||
settings: Settings;
|
||||
async: boolean;
|
||||
@@ -333,10 +333,13 @@ async function emitTemplateMutationResult(options: {
|
||||
}
|
||||
return;
|
||||
}
|
||||
const buildEndpoint = sandboxEndpoint(
|
||||
options.workspaceId,
|
||||
sandboxTemplateBuildStatusPath(options.response.templateID, options.response.buildID),
|
||||
);
|
||||
// Poll the same origin that accepted the build, including custom gateways.
|
||||
const buildEndpoint = new URL(
|
||||
sandboxApiPath(
|
||||
sandboxTemplateBuildStatusPath(options.response.templateID, options.response.buildID),
|
||||
),
|
||||
options.endpoint,
|
||||
).toString();
|
||||
const build = await waitForTemplateBuild(
|
||||
options.client,
|
||||
options.settings,
|
||||
@@ -372,8 +375,7 @@ export const sandboxTemplateCreate = defineCommand({
|
||||
validate: validatePollInterval,
|
||||
async run(ctx) {
|
||||
const format = detectOutputFormat(ctx.settings.output);
|
||||
const workspaceId = resolveWorkspaceId(ctx);
|
||||
const endpoint = sandboxEndpoint(workspaceId, SANDBOX_PATHS.templateCreate);
|
||||
const endpoint = resolveSandboxEndpoint(ctx, SANDBOX_PATHS.templateCreate);
|
||||
const body = await buildTemplateCreateBody(ctx.flags);
|
||||
if (ctx.settings.dryRun) {
|
||||
emitResult({ method: "POST", endpoint, request: redactRequestSecrets(body) }, format);
|
||||
@@ -386,7 +388,7 @@ export const sandboxTemplateCreate = defineCommand({
|
||||
});
|
||||
await emitTemplateMutationResult({
|
||||
response,
|
||||
workspaceId,
|
||||
endpoint,
|
||||
client: ctx.client,
|
||||
settings: ctx.settings,
|
||||
async: ctx.flags.async,
|
||||
@@ -419,8 +421,7 @@ export const sandboxTemplateList = defineCommand({
|
||||
return undefined;
|
||||
},
|
||||
async run(ctx) {
|
||||
const workspaceId = resolveWorkspaceId(ctx);
|
||||
const url = new URL(sandboxEndpoint(workspaceId, SANDBOX_PATHS.templateList));
|
||||
const url = new URL(resolveSandboxEndpoint(ctx, SANDBOX_PATHS.templateList));
|
||||
if (ctx.flags.limit !== undefined) url.searchParams.set("limit", String(ctx.flags.limit));
|
||||
if (ctx.flags.cursor) url.searchParams.set("cursor", ctx.flags.cursor);
|
||||
const response = await ctx.client.requestJson<TemplateInfo[]>({
|
||||
@@ -466,8 +467,7 @@ export const sandboxTemplateGet = defineCommand({
|
||||
exampleArgs: ["--template-id tpl-xxx", "--template-id tpl-xxx --output json"],
|
||||
notes: SANDBOX_NOTES,
|
||||
async run(ctx) {
|
||||
const workspaceId = resolveWorkspaceId(ctx);
|
||||
const endpoint = sandboxEndpoint(workspaceId, sandboxTemplatePath(ctx.flags.templateId));
|
||||
const endpoint = resolveSandboxEndpoint(ctx, sandboxTemplatePath(ctx.flags.templateId));
|
||||
const response = await ctx.client.requestJson<TemplateInfo>({ path: endpoint, method: "GET" });
|
||||
if (ctx.settings.quiet) emitBare(displayValue(response.templateID));
|
||||
else emitResult(response, detectOutputFormat(ctx.settings.output));
|
||||
@@ -499,8 +499,7 @@ export const sandboxTemplateUpdate = defineCommand({
|
||||
validate: validatePollInterval,
|
||||
async run(ctx) {
|
||||
const format = detectOutputFormat(ctx.settings.output);
|
||||
const workspaceId = resolveWorkspaceId(ctx);
|
||||
const endpoint = sandboxEndpoint(workspaceId, sandboxTemplatePath(ctx.flags.templateId));
|
||||
const endpoint = resolveSandboxEndpoint(ctx, sandboxTemplatePath(ctx.flags.templateId));
|
||||
const body = await buildTemplateUpdateBody(ctx.flags);
|
||||
if (ctx.settings.dryRun) {
|
||||
emitResult({ method: "PUT", endpoint, request: redactRequestSecrets(body) }, format);
|
||||
@@ -513,7 +512,7 @@ export const sandboxTemplateUpdate = defineCommand({
|
||||
});
|
||||
await emitTemplateMutationResult({
|
||||
response,
|
||||
workspaceId,
|
||||
endpoint,
|
||||
client: ctx.client,
|
||||
settings: ctx.settings,
|
||||
async: ctx.flags.async,
|
||||
@@ -536,9 +535,8 @@ export const sandboxTemplateBuildStatus = defineCommand({
|
||||
],
|
||||
notes: SANDBOX_NOTES,
|
||||
async run(ctx) {
|
||||
const workspaceId = resolveWorkspaceId(ctx);
|
||||
const endpoint = sandboxEndpoint(
|
||||
workspaceId,
|
||||
const endpoint = resolveSandboxEndpoint(
|
||||
ctx,
|
||||
sandboxTemplateBuildStatusPath(ctx.flags.templateId, ctx.flags.buildId),
|
||||
);
|
||||
const response = await ctx.client.requestJson<TemplateBuildStatus>({
|
||||
@@ -573,8 +571,7 @@ export const sandboxTemplateDelete = defineCommand({
|
||||
],
|
||||
async run(ctx) {
|
||||
const format = detectOutputFormat(ctx.settings.output);
|
||||
const workspaceId = resolveWorkspaceId(ctx);
|
||||
const endpoint = sandboxEndpoint(workspaceId, sandboxTemplatePath(ctx.flags.templateId));
|
||||
const endpoint = resolveSandboxEndpoint(ctx, sandboxTemplatePath(ctx.flags.templateId));
|
||||
if (ctx.settings.dryRun) {
|
||||
emitResult({ method: "DELETE", endpoint, request: null }, format);
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { createServer, 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 } from "./helpers.ts";
|
||||
import { SANDBOX_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
const ROUTES = { ...SANDBOX_ROUTES, "auth login": "authLogin" } as const;
|
||||
const tempDirectories: string[] = [];
|
||||
const servers: Server[] = [];
|
||||
const API_PATH = "/api/v1/agentstudio/sandbox";
|
||||
const PROFILE_CONFIG = {
|
||||
api_key: "sk-default-test",
|
||||
base_url: "https://default.example.test",
|
||||
active_config: "sandbox-test",
|
||||
"sandbox-test": {
|
||||
api_key: "sk-profile-test",
|
||||
base_url: "https://profile.example.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 tempDirectories.splice(0)) {
|
||||
rmSync(directory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
function makeConfigEnv(config: Record<string, unknown> = {}): NodeJS.ProcessEnv {
|
||||
const directory = mkdtempSync(join(tmpdir(), "bl-sandbox-base-url-"));
|
||||
tempDirectories.push(directory);
|
||||
writeFileSync(join(directory, "config.json"), JSON.stringify(config));
|
||||
return {
|
||||
BAILIAN_CONFIG_DIR: directory,
|
||||
DASHSCOPE_API_KEY: "",
|
||||
DASHSCOPE_BASE_URL: "",
|
||||
BAILIAN_WORKSPACE_ID: "",
|
||||
};
|
||||
}
|
||||
|
||||
describe("e2e: Sandbox shared base URL resolution", () => {
|
||||
test.each([
|
||||
{
|
||||
name: "flag overrides env and profile, normalizing paths and preserving the port",
|
||||
args: ["--base-url", "https://flag.example.test:8443/api/v1/agentstudio/?ignored=1#fragment"],
|
||||
envBaseUrl: "https://env.example.test",
|
||||
config: PROFILE_CONFIG,
|
||||
expectedOrigin: "https://flag.example.test:8443",
|
||||
},
|
||||
{
|
||||
name: "env overrides the active profile",
|
||||
args: [],
|
||||
envBaseUrl: "https://env.example.test/compatible-mode/v1/",
|
||||
config: PROFILE_CONFIG,
|
||||
expectedOrigin: "https://env.example.test",
|
||||
},
|
||||
{
|
||||
name: "the active profile supplies its configured origin",
|
||||
args: [],
|
||||
envBaseUrl: "",
|
||||
config: PROFILE_CONFIG,
|
||||
expectedOrigin: "https://profile.example.test",
|
||||
},
|
||||
{
|
||||
name: "an explicit profile selection uses that profile",
|
||||
args: ["--config", "default"],
|
||||
envBaseUrl: "",
|
||||
config: PROFILE_CONFIG,
|
||||
expectedOrigin: "https://default.example.test",
|
||||
},
|
||||
{
|
||||
name: "an explicit default model origin still overrides workspace inference",
|
||||
args: ["--base-url", "https://dashscope.aliyuncs.com", "--workspace-id", "unused"],
|
||||
envBaseUrl: "",
|
||||
config: {},
|
||||
expectedOrigin: "https://dashscope.aliyuncs.com",
|
||||
},
|
||||
{
|
||||
name: "without any base URL the workspace endpoint is preserved",
|
||||
args: ["--workspace-id", "ws-test"],
|
||||
envBaseUrl: "",
|
||||
config: {},
|
||||
expectedOrigin: "https://ws-test.cn-beijing.maas.aliyuncs.com",
|
||||
},
|
||||
])("$name", async ({ args, envBaseUrl, config, expectedOrigin }) => {
|
||||
const result = await runCommandE2e(
|
||||
ROUTES,
|
||||
["sandbox", "create", "--template-id", "tpl-test", ...args, "--dry-run", "--output", "json"],
|
||||
{ ...makeConfigEnv(config), DASHSCOPE_BASE_URL: envBaseUrl },
|
||||
);
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
expect(parseStdoutJson(result.stdout)).toMatchObject({
|
||||
endpoint: `${expectedOrigin}${API_PATH}/sandboxes`,
|
||||
});
|
||||
});
|
||||
|
||||
test("an unconfigured URL still requires a workspace, while malformed URLs fail validation", async () => {
|
||||
const env = makeConfigEnv();
|
||||
for (const args of [[], ["--base-url", "invalid"], ["--base-url", "file:///tmp/gateway"]]) {
|
||||
const result = await runCommandE2e(
|
||||
ROUTES,
|
||||
[
|
||||
"sandbox",
|
||||
"create",
|
||||
"--template-id",
|
||||
"tpl-test",
|
||||
...args,
|
||||
"--dry-run",
|
||||
"--output",
|
||||
"json",
|
||||
],
|
||||
env,
|
||||
);
|
||||
expect(result.exitCode).toBe(2);
|
||||
expect(JSON.parse(result.stderr).error.message).toMatch(
|
||||
/Workspace ID|Invalid model base URL/,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("auth login persists a base URL that Sandbox uses without a workspace flag", async () => {
|
||||
const env = makeConfigEnv();
|
||||
const login = await runCommandE2e(
|
||||
ROUTES,
|
||||
[
|
||||
"auth",
|
||||
"login",
|
||||
"--config",
|
||||
"sandbox-test",
|
||||
"--api-key",
|
||||
"sk-login-test",
|
||||
"--base-url",
|
||||
"https://login.example.test/api/v1/agentstudio/sandbox/",
|
||||
],
|
||||
env,
|
||||
);
|
||||
expect(login.exitCode, login.stderr).toBe(0);
|
||||
const stored = JSON.parse(readFileSync(join(env.BAILIAN_CONFIG_DIR!, "config.json"), "utf8"));
|
||||
expect(stored.active_config).toBe("sandbox-test");
|
||||
expect(stored["sandbox-test"].base_url).toBe("https://login.example.test");
|
||||
|
||||
const result = await runCommandE2e(
|
||||
ROUTES,
|
||||
["sandbox", "pause", "--sandbox-id", "sbx-test", "--dry-run", "--output", "json"],
|
||||
env,
|
||||
);
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
expect(parseStdoutJson(result.stdout)).toMatchObject({
|
||||
endpoint: `https://login.example.test${API_PATH}/sandboxes/sbx-test/pause`,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("e2e: Sandbox custom gateway transport", () => {
|
||||
test.each([
|
||||
{
|
||||
action: "create",
|
||||
args: ["--name", "python", "--cpu-count", "1", "--memory-mb", "2048"],
|
||||
method: "POST",
|
||||
suffix: "/v3/templates",
|
||||
},
|
||||
{
|
||||
action: "update",
|
||||
args: ["--template-id", "template/a", "--description", "updated"],
|
||||
method: "PUT",
|
||||
suffix: "/templates/template%2Fa",
|
||||
},
|
||||
])(
|
||||
"template $action and build polling use the same gateway and Bearer key",
|
||||
async ({ action, args, method, suffix }) => {
|
||||
const received: {
|
||||
method?: string;
|
||||
path?: string;
|
||||
authorization?: string;
|
||||
e2bKey?: string | string[];
|
||||
}[] = [];
|
||||
const server = createServer((request, response) => {
|
||||
request.resume();
|
||||
received.push({
|
||||
method: request.method,
|
||||
path: request.url,
|
||||
authorization: request.headers.authorization,
|
||||
e2bKey: request.headers["x-api-key"],
|
||||
});
|
||||
response.setHeader("content-type", "application/json");
|
||||
response.end(
|
||||
JSON.stringify({
|
||||
templateID: "template/a",
|
||||
buildID: "build a",
|
||||
status: request.method === "GET" ? "ready" : "building",
|
||||
}),
|
||||
);
|
||||
});
|
||||
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.");
|
||||
const origin = `http://127.0.0.1:${address.port}`;
|
||||
const result = await runCommandE2e(
|
||||
ROUTES,
|
||||
["sandbox", "template", action, ...args, "--poll-interval", "1", "--output", "json"],
|
||||
makeConfigEnv({ api_key: "sk-gateway-test", base_url: origin }),
|
||||
);
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
expect(received).toEqual([
|
||||
{
|
||||
method,
|
||||
path: `${API_PATH}${suffix}`,
|
||||
authorization: "Bearer sk-gateway-test",
|
||||
e2bKey: undefined,
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
path: `${API_PATH}/templates/template%2Fa/builds/build%20a/status`,
|
||||
authorization: "Bearer sk-gateway-test",
|
||||
e2bKey: undefined,
|
||||
},
|
||||
]);
|
||||
expect(parseStdoutJson(result.stdout)).toMatchObject({ build: { status: "ready" } });
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -1,8 +1,30 @@
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import { parseStdoutJson, runCommandE2e, runCommandHelp } from "./helpers.ts";
|
||||
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, test } from "vite-plus/test";
|
||||
import { parseStdoutJson, runCommandE2e as runBaseCommandE2e, runCommandHelp } from "./helpers.ts";
|
||||
import { SANDBOX_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
const AUTH_ARGS = ["--api-key", "sk-sandbox-e2e", "--workspace-id", "ws-e2e"];
|
||||
let configDirectory: string;
|
||||
|
||||
beforeEach(() => {
|
||||
configDirectory = mkdtempSync(join(tmpdir(), "bl-sandbox-e2e-"));
|
||||
writeFileSync(join(configDirectory, "config.json"), "{}");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
rmSync(configDirectory, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
function runCommandE2e(routes: typeof SANDBOX_ROUTES, args: string[]) {
|
||||
return runBaseCommandE2e(routes, args, {
|
||||
BAILIAN_CONFIG_DIR: configDirectory,
|
||||
DASHSCOPE_BASE_URL: "",
|
||||
DASHSCOPE_API_KEY: "",
|
||||
BAILIAN_WORKSPACE_ID: "",
|
||||
});
|
||||
}
|
||||
|
||||
describe("e2e: Sandbox command discovery", () => {
|
||||
test.each([
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { BailianError, ExitCode, type AnyCommand, type Settings } from "bailian-cli-core";
|
||||
import { BailianError, Client, ExitCode, type AnyCommand, type Settings } from "bailian-cli-core";
|
||||
import { afterEach, describe, expect, test, vi } from "vite-plus/test";
|
||||
import {
|
||||
buildSandboxCreateBody,
|
||||
@@ -51,10 +51,26 @@ interface RecordedRequest {
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
function createUrlResolver(baseUrl?: string): Client["url"] {
|
||||
const client = new Client({
|
||||
identity: {
|
||||
binName: "bl",
|
||||
version: "test",
|
||||
npmPackage: "bailian-cli",
|
||||
clientName: "bailian-cli",
|
||||
},
|
||||
settings: SETTINGS,
|
||||
baseUrl: baseUrl ?? "https://dashscope.aliyuncs.com",
|
||||
baseUrlIsDefault: baseUrl === undefined,
|
||||
});
|
||||
return client.url.bind(client);
|
||||
}
|
||||
|
||||
async function runCommand(
|
||||
command: AnyCommand,
|
||||
flags: Record<string, unknown>,
|
||||
response: unknown,
|
||||
baseUrl?: string,
|
||||
): Promise<RecordedRequest> {
|
||||
vi.spyOn(process.stdout, "write").mockImplementation(() => true);
|
||||
const requestJson = vi.fn(async (_request: RecordedRequest) => response);
|
||||
@@ -71,7 +87,7 @@ async function runCommand(
|
||||
},
|
||||
settings: SETTINGS,
|
||||
flags,
|
||||
client: { requestJson, request },
|
||||
client: { requestJson, request, url: createUrlResolver(baseUrl) },
|
||||
} as never);
|
||||
|
||||
const recorded = requestJson.mock.calls[0]?.[0] ?? request.mock.calls[0]?.[0];
|
||||
@@ -336,6 +352,20 @@ describe("Sandbox command transport", () => {
|
||||
"maps $name to its documented method and path",
|
||||
async ({ command, flags, response, request }) => {
|
||||
expect(await runCommand(command, flags, response)).toEqual(request);
|
||||
expect(
|
||||
await runCommand(
|
||||
command,
|
||||
{ ...flags, workspaceId: undefined },
|
||||
response,
|
||||
"https://gateway.example.test",
|
||||
),
|
||||
).toEqual({
|
||||
...request,
|
||||
path: request.path.replace(
|
||||
"https://ws-test.cn-beijing.maas.aliyuncs.com",
|
||||
"https://gateway.example.test",
|
||||
),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@@ -357,6 +387,7 @@ describe("Sandbox command transport", () => {
|
||||
showCredentials: testCase.showCredentials,
|
||||
},
|
||||
client: {
|
||||
url: createUrlResolver(),
|
||||
requestJson: async () => ({
|
||||
sandboxID: "sandbox-test",
|
||||
envdAccessToken: "envd-secret",
|
||||
@@ -435,62 +466,65 @@ describe("Sandbox template build polling", () => {
|
||||
memoryMb: 2048,
|
||||
async: true,
|
||||
},
|
||||
client: { requestJson },
|
||||
client: { requestJson, url: createUrlResolver() },
|
||||
} as never);
|
||||
expect(requestJson).toHaveBeenCalledTimes(1);
|
||||
expect(stdout).toBe("template-test\tbuild-test\n");
|
||||
});
|
||||
|
||||
test("default template creation polls build-status and emits the final envelope", async () => {
|
||||
let stdout = "";
|
||||
vi.spyOn(process.stdout, "write").mockImplementation((chunk) => {
|
||||
stdout += String(chunk);
|
||||
return true;
|
||||
});
|
||||
const requestJson = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
templateID: "template-test",
|
||||
buildID: "build-test",
|
||||
buildStatus: "building",
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
templateID: "template-test",
|
||||
buildID: "build-test",
|
||||
status: "ready",
|
||||
test.each([undefined, "https://gateway.example.test"])(
|
||||
"template creation polls the selected origin %s and emits the final envelope",
|
||||
async (baseUrl) => {
|
||||
let stdout = "";
|
||||
vi.spyOn(process.stdout, "write").mockImplementation((chunk) => {
|
||||
stdout += String(chunk);
|
||||
return true;
|
||||
});
|
||||
await sandboxTemplateCreate.run({
|
||||
identity: { binName: "bl" },
|
||||
settings: { ...SETTINGS, quiet: false },
|
||||
flags: {
|
||||
workspaceId: "ws-test",
|
||||
name: "python",
|
||||
cpuCount: 1,
|
||||
memoryMb: 2048,
|
||||
async: false,
|
||||
pollInterval: 1,
|
||||
},
|
||||
client: { requestJson },
|
||||
} as never);
|
||||
const requestJson = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
templateID: "template-test",
|
||||
buildID: "build-test",
|
||||
buildStatus: "building",
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
templateID: "template-test",
|
||||
buildID: "build-test",
|
||||
status: "ready",
|
||||
});
|
||||
await sandboxTemplateCreate.run({
|
||||
identity: { binName: "bl" },
|
||||
settings: { ...SETTINGS, quiet: false },
|
||||
flags: {
|
||||
workspaceId: "ws-test",
|
||||
name: "python",
|
||||
cpuCount: 1,
|
||||
memoryMb: 2048,
|
||||
async: false,
|
||||
pollInterval: 1,
|
||||
},
|
||||
client: { requestJson, url: createUrlResolver(baseUrl) },
|
||||
} as never);
|
||||
|
||||
expect(requestJson).toHaveBeenCalledTimes(2);
|
||||
expect(requestJson.mock.calls[1]?.[0]).toMatchObject({
|
||||
method: "GET",
|
||||
path: "https://ws-test.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio/sandbox/templates/template-test/builds/build-test/status",
|
||||
});
|
||||
expect(JSON.parse(stdout)).toEqual({
|
||||
template: {
|
||||
templateID: "template-test",
|
||||
buildID: "build-test",
|
||||
buildStatus: "building",
|
||||
},
|
||||
build: {
|
||||
templateID: "template-test",
|
||||
buildID: "build-test",
|
||||
status: "ready",
|
||||
},
|
||||
});
|
||||
});
|
||||
expect(requestJson).toHaveBeenCalledTimes(2);
|
||||
expect(requestJson.mock.calls[1]?.[0]).toMatchObject({
|
||||
method: "GET",
|
||||
path: `${baseUrl ?? "https://ws-test.cn-beijing.maas.aliyuncs.com"}/api/v1/agentstudio/sandbox/templates/template-test/builds/build-test/status`,
|
||||
});
|
||||
expect(JSON.parse(stdout)).toEqual({
|
||||
template: {
|
||||
templateID: "template-test",
|
||||
buildID: "build-test",
|
||||
buildStatus: "building",
|
||||
},
|
||||
build: {
|
||||
templateID: "template-test",
|
||||
buildID: "build-test",
|
||||
status: "ready",
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test("Sandbox validation errors use CLI usage exit codes", async () => {
|
||||
|
||||
@@ -4,6 +4,7 @@ export {
|
||||
resolveOpenApi,
|
||||
describeAuthState,
|
||||
resolveModelBaseUrl,
|
||||
resolveModelBaseUrlState,
|
||||
} from "./resolver.ts";
|
||||
export { makeAuthStore, type AuthStore, type AuthPersistPatch } from "./store.ts";
|
||||
export type {
|
||||
|
||||
@@ -8,11 +8,22 @@ import { ExitCode } from "../errors/codes.ts";
|
||||
// Resolve the credential for a command's declared domain (model = api-key,
|
||||
// console = access-token), by priority, or throw. Read only from sources.
|
||||
|
||||
/** Preserve whether the shared base URL chain selected a configured origin or its default. */
|
||||
export function resolveModelBaseUrlState(
|
||||
sources: ResolutionSources,
|
||||
fallback: string = REGIONS.cn,
|
||||
): { baseUrl: string; baseUrlIsDefault: boolean } {
|
||||
const configuredBaseUrl =
|
||||
sources.flags.baseUrl || sources.env.DASHSCOPE_BASE_URL || sources.file.base_url;
|
||||
return {
|
||||
baseUrl: normalizeModelBaseUrl(configuredBaseUrl || fallback),
|
||||
baseUrlIsDefault: !configuredBaseUrl,
|
||||
};
|
||||
}
|
||||
|
||||
/** Model-domain baseUrl(flag > env > config file > fallback);无需 key 也可解析。 */
|
||||
export function resolveModelBaseUrl(s: ResolutionSources, fallback: string = REGIONS.cn): string {
|
||||
return normalizeModelBaseUrl(
|
||||
s.flags.baseUrl || s.env.DASHSCOPE_BASE_URL || s.file.base_url || fallback,
|
||||
);
|
||||
return resolveModelBaseUrlState(s, fallback).baseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,6 +23,8 @@ export interface ClientDeps {
|
||||
settings: Settings;
|
||||
/** Model 域 base URL(凭证无关链解析,resolveModelBaseUrl;有 apiCred 时两者一致)。 */
|
||||
baseUrl: string;
|
||||
/** True only when the shared URL chain used its default; explicit origins win over service defaults. */
|
||||
baseUrlIsDefault?: boolean;
|
||||
apiCred?: ApiKeyCredential;
|
||||
consoleCred?: ConsoleCredential;
|
||||
openApiCred?: OpenApiCredential;
|
||||
@@ -108,9 +110,13 @@ export class Client {
|
||||
return this.deps.apiCred;
|
||||
}
|
||||
|
||||
/** Full URL for a model-domain {@link path}; build request/display URLs only through this. */
|
||||
url(path: string): string {
|
||||
return this.baseUrl + path;
|
||||
/**
|
||||
* Full URL for a model-domain path. Services may supply a lazy default origin,
|
||||
* evaluated only when no flag/env/profile base URL was configured.
|
||||
*/
|
||||
url(path: string, defaultBaseUrl?: () => string): string {
|
||||
const baseUrl = this.deps.baseUrlIsDefault && defaultBaseUrl ? defaultBaseUrl() : this.baseUrl;
|
||||
return baseUrl + path;
|
||||
}
|
||||
|
||||
private toOpts({ path, ...rest }: ClientRequestOpts): RequestOpts {
|
||||
|
||||
@@ -228,14 +228,19 @@ export function ragEndpoint(workspaceId: string, path: string): string {
|
||||
|
||||
// ---- Sandbox control plane (workspace-based host, cn-beijing only) ----
|
||||
|
||||
/**
|
||||
* Build an absolute Bailian Sandbox control-plane URL.
|
||||
*
|
||||
* Sandbox currently supports cn-beijing only. Keep the workspace-specific host
|
||||
* centralized here so commands never hard-code API endpoints.
|
||||
*/
|
||||
/** 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`;
|
||||
}
|
||||
|
||||
/** Sandbox service prefix, appended to the selected origin just like AgentStudio SDK paths. */
|
||||
export function sandboxApiPath(path: string): string {
|
||||
return `/api/v1/agentstudio/sandbox${path}`;
|
||||
}
|
||||
|
||||
/** Build the default workspace-scoped absolute Sandbox control-plane URL. */
|
||||
export function sandboxEndpoint(workspaceId: string, path: string): string {
|
||||
return `https://${workspaceId}.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio/sandbox${path}`;
|
||||
return sandboxBaseUrl(workspaceId) + sandboxApiPath(path);
|
||||
}
|
||||
|
||||
export const SANDBOX_PATHS = {
|
||||
|
||||
@@ -19,6 +19,8 @@ export {
|
||||
ragEndpoint,
|
||||
RAG_PATHS,
|
||||
sandboxEndpoint,
|
||||
sandboxBaseUrl,
|
||||
sandboxApiPath,
|
||||
SANDBOX_PATHS,
|
||||
sandboxInstancePath,
|
||||
sandboxInstanceActionPath,
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
resolveApiKey,
|
||||
resolveConsole,
|
||||
resolveModelBaseUrl,
|
||||
resolveModelBaseUrlState,
|
||||
resolveOpenApi,
|
||||
} from "../src/auth/resolver.ts";
|
||||
import { getModelProfilePreset } from "../src/config/profile-presets.ts";
|
||||
@@ -98,6 +99,24 @@ test("baseUrl:非法 flag/env 在 resolver 边界报 usage error", () => {
|
||||
).toThrow(/Invalid model base URL/);
|
||||
});
|
||||
|
||||
test("baseUrl state distinguishes an implicit default from an explicitly configured default origin", () => {
|
||||
const defaultOrigin = "https://dashscope.aliyuncs.com";
|
||||
expect(resolveModelBaseUrlState(src({}))).toEqual({
|
||||
baseUrl: defaultOrigin,
|
||||
baseUrlIsDefault: true,
|
||||
});
|
||||
for (const sources of [
|
||||
src({ flags: { baseUrl: defaultOrigin } }),
|
||||
src({ env: { DASHSCOPE_BASE_URL: defaultOrigin } }),
|
||||
src({ file: { base_url: defaultOrigin } }),
|
||||
]) {
|
||||
expect(resolveModelBaseUrlState(sources)).toEqual({
|
||||
baseUrl: defaultOrigin,
|
||||
baseUrlIsDefault: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test("命名 config 仍保持 flag > env > selected file", () => {
|
||||
const env = {
|
||||
DASHSCOPE_BASE_URL: "https://env.example.com",
|
||||
|
||||
@@ -142,6 +142,29 @@ test("Client sends Sandbox REST requests with Bearer auth and no E2B API key", a
|
||||
}
|
||||
});
|
||||
|
||||
test("Client.url evaluates a service default only when the shared origin was not configured", () => {
|
||||
const deps = testDeps();
|
||||
const defaultClient = new Client({
|
||||
...deps,
|
||||
baseUrl: "https://dashscope.aliyuncs.com",
|
||||
baseUrlIsDefault: true,
|
||||
});
|
||||
const configuredClient = new Client({
|
||||
...deps,
|
||||
baseUrl: "https://dashscope.aliyuncs.com",
|
||||
baseUrlIsDefault: false,
|
||||
});
|
||||
expect(defaultClient.url("/service")).toBe("https://dashscope.aliyuncs.com/service");
|
||||
expect(defaultClient.url("/service", () => "https://workspace.example.test")).toBe(
|
||||
"https://workspace.example.test/service",
|
||||
);
|
||||
expect(
|
||||
configuredClient.url("/service", () => {
|
||||
throw new Error("A configured origin must not require a workspace default.");
|
||||
}),
|
||||
).toBe("https://dashscope.aliyuncs.com/service");
|
||||
});
|
||||
|
||||
test("BailianError propagates cause via options-bag and exposes it in toJSON", () => {
|
||||
const root = Object.assign(new Error("getaddrinfo ENOTFOUND example.invalid"), {
|
||||
code: "ENOTFOUND",
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
buildSources,
|
||||
buildSettings,
|
||||
describeAuthState,
|
||||
resolveModelBaseUrl,
|
||||
resolveModelBaseUrlState,
|
||||
makeConfigStore,
|
||||
makeAuthStore,
|
||||
flushTelemetry,
|
||||
@@ -238,7 +238,7 @@ export function createCli(commands: Record<string, AnyCommand>, opts: CliOptions
|
||||
client: new Client({
|
||||
identity,
|
||||
settings,
|
||||
baseUrl: resolveModelBaseUrl(sources),
|
||||
...resolveModelBaseUrlState(sources),
|
||||
}),
|
||||
};
|
||||
await runMiddleware(ctx);
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
resolveApiKey,
|
||||
resolveConsole,
|
||||
resolveOpenApi,
|
||||
resolveModelBaseUrl,
|
||||
resolveModelBaseUrlState,
|
||||
selectApiKeyResolutionSources,
|
||||
trackCommandExecution,
|
||||
} from "bailian-cli-core";
|
||||
@@ -128,7 +128,7 @@ export const authStage: Middleware = async (ctx, next) => {
|
||||
const base = {
|
||||
identity: ctx.identity,
|
||||
settings,
|
||||
baseUrl: resolveModelBaseUrl(sources),
|
||||
...resolveModelBaseUrlState(sources),
|
||||
};
|
||||
if (command.auth === "apiKey") {
|
||||
const capability = ctx.path.join(".");
|
||||
@@ -145,7 +145,7 @@ export const authStage: Middleware = async (ctx, next) => {
|
||||
}
|
||||
ctx.client = new Client({
|
||||
...base,
|
||||
baseUrl: resolveModelBaseUrl(apiSources),
|
||||
...resolveModelBaseUrlState(apiSources),
|
||||
apiCred: cred,
|
||||
});
|
||||
if (cred) maybeShowStatusBar(settings, cred.token, cred);
|
||||
|
||||
@@ -81,6 +81,28 @@ async function runAuth(context: RunContext): Promise<void> {
|
||||
await authStage(context, async () => {});
|
||||
}
|
||||
|
||||
test.each([undefined, "https://default.example.com", "https://dashscope.aliyuncs.com"])(
|
||||
"service defaults follow the effective profile base URL after capability fallback: %s",
|
||||
async (defaultBaseUrl) => {
|
||||
useTempConfigDir();
|
||||
await writeConfigFile({ api_key: "sk-default", base_url: defaultBaseUrl });
|
||||
await writeConfigFile(
|
||||
{
|
||||
api_key: "sk-plan",
|
||||
base_url: "https://plan.example.com",
|
||||
api_key_capabilities: ["text.chat"],
|
||||
},
|
||||
"company-plan",
|
||||
);
|
||||
const context = makeContext(["sandbox", "list"]);
|
||||
await runAuth(context);
|
||||
expect(context.client.url("/service", () => "https://workspace.example.test")).toBe(
|
||||
`${defaultBaseUrl ?? "https://workspace.example.test"}/service`,
|
||||
);
|
||||
expect(context.client.exportApiCredential()?.token).toBe("sk-default");
|
||||
},
|
||||
);
|
||||
|
||||
async function captureStderr(operation: () => Promise<void>): Promise<{
|
||||
output: string;
|
||||
error?: unknown;
|
||||
|
||||
@@ -19,8 +19,8 @@ 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 the workspace from `--workspace-id`, then `BAILIAN_WORKSPACE_ID`, then configured `workspace_id`. The current CLI targets `cn-beijing` and requires prior Sandbox SLR authorization.
|
||||
- The current endpoint is `https://{workspace_id}.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio/sandbox`. Login and command-level `--base-url` do not override the Sandbox endpoint; the saved API Key is reused.
|
||||
- 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.
|
||||
- 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
|
||||
@@ -36,6 +36,8 @@ Before running `bl`, read the shared [bailian-protocol](../bailian-protocol/SKIL
|
||||
|
||||
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.
|
||||
|
||||
To save the origin in an isolated Profile, use `bl auth login --config sandbox --api-key <key> --base-url <origin>`. For a one-command override, use `bl sandbox list --base-url <origin>`. Custom gateways also apply to template build-status polling.
|
||||
|
||||
## Operational boundaries
|
||||
|
||||
- Mutating commands act on remote resources. Only perform the requested operation and scope; read-only discovery does not authorize creating, pausing, resuming, or deleting resources.
|
||||
|
||||
@@ -38,7 +38,7 @@ Index: [index.md](index.md)
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------ | ------ | -------- | ---------------------------------------------------------------------------------- |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for the Sandbox endpoint (or set BAILIAN_WORKSPACE_ID) |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for the default Sandbox endpoint; optional with a configured base URL |
|
||||
| `--sandbox-id <id>` | string | yes | Sandbox instance ID |
|
||||
| `--body <json\|@path>` | string | no | JSON request body, inline or loaded from an @file path; explicit flags override it |
|
||||
| `--instance-timeout <seconds>` | number | no | Sandbox lifetime after this operation (300-604800 seconds) |
|
||||
@@ -49,7 +49,8 @@ Index: [index.md](index.md)
|
||||
#### Notes
|
||||
|
||||
- Auth: uses a Bailian API Key as an Authorization Bearer token; no E2B key is sent.
|
||||
- The workspace is resolved from --workspace-id, BAILIAN_WORKSPACE_ID, then config workspace_id.
|
||||
- Base URL: --base-url > DASHSCOPE_BASE_URL > login/profile base_url. The CLI uses its origin and appends /api/v1/agentstudio/sandbox; otherwise it uses the workspace-scoped cn-beijing endpoint.
|
||||
- Without a configured base URL, workspace is required: --workspace-id > BAILIAN_WORKSPACE_ID > config workspace_id.
|
||||
- Sandbox is currently available in cn-beijing only and requires prior SLR authorization.
|
||||
- Global --timeout limits HTTP requests and total template-build polling; --instance-timeout maps to the Sandbox API lifetime field.
|
||||
|
||||
@@ -76,7 +77,7 @@ bl sandbox connect --sandbox-id sbx-xxx --show-credentials --output json
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| -------------------------------- | ------- | -------- | ---------------------------------------------------------------------------------- |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for the Sandbox endpoint (or set BAILIAN_WORKSPACE_ID) |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for the default Sandbox endpoint; optional with a configured base URL |
|
||||
| `--body <json\|@path>` | string | no | JSON request body, inline or loaded from an @file path; explicit flags override it |
|
||||
| `--template-id <id>` | string | no | Ready template ID; may alternatively be supplied as templateID in --body |
|
||||
| `--instance-timeout <seconds>` | number | no | Sandbox lifetime after this operation (300-604800 seconds) |
|
||||
@@ -95,7 +96,8 @@ bl sandbox connect --sandbox-id sbx-xxx --show-credentials --output json
|
||||
#### Notes
|
||||
|
||||
- Auth: uses a Bailian API Key as an Authorization Bearer token; no E2B key is sent.
|
||||
- The workspace is resolved from --workspace-id, BAILIAN_WORKSPACE_ID, then config workspace_id.
|
||||
- Base URL: --base-url > DASHSCOPE_BASE_URL > login/profile base_url. The CLI uses its origin and appends /api/v1/agentstudio/sandbox; otherwise it uses the workspace-scoped cn-beijing endpoint.
|
||||
- Without a configured base URL, workspace is required: --workspace-id > BAILIAN_WORKSPACE_ID > config workspace_id.
|
||||
- Sandbox is currently available in cn-beijing only and requires prior SLR authorization.
|
||||
- Global --timeout limits HTTP requests and total template-build polling; --instance-timeout maps to the Sandbox API lifetime field.
|
||||
|
||||
@@ -105,6 +107,10 @@ bl sandbox connect --sandbox-id sbx-xxx --show-credentials --output json
|
||||
bl sandbox create --template-id tpl-xxx --instance-timeout 3600
|
||||
```
|
||||
|
||||
```bash
|
||||
bl sandbox create --template-id tpl-xxx --base-url https://workspace.cn-beijing.maas.aliyuncs.com
|
||||
```
|
||||
|
||||
```bash
|
||||
bl sandbox create --body @sandbox.json --dry-run --output json
|
||||
```
|
||||
@@ -128,18 +134,19 @@ bl sandbox create --template-id tpl-xxx --show-credentials --output json
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------- | ------ | -------- | ------------------------------------------------------------------- |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for the Sandbox endpoint (or set BAILIAN_WORKSPACE_ID) |
|
||||
| `--sandbox-id <id>` | string | yes | Sandbox instance ID |
|
||||
| `--yes` | switch | no | Confirm this high-risk operation |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------- | ------ | -------- | ---------------------------------------------------------------------------------- |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for the default Sandbox endpoint; optional with a configured base URL |
|
||||
| `--sandbox-id <id>` | string | yes | Sandbox instance ID |
|
||||
| `--yes` | switch | no | Confirm this high-risk operation |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
|
||||
#### Notes
|
||||
|
||||
- Auth: uses a Bailian API Key as an Authorization Bearer token; no E2B key is sent.
|
||||
- The workspace is resolved from --workspace-id, BAILIAN_WORKSPACE_ID, then config workspace_id.
|
||||
- Base URL: --base-url > DASHSCOPE_BASE_URL > login/profile base_url. The CLI uses its origin and appends /api/v1/agentstudio/sandbox; otherwise it uses the workspace-scoped cn-beijing endpoint.
|
||||
- Without a configured base URL, workspace is required: --workspace-id > BAILIAN_WORKSPACE_ID > config workspace_id.
|
||||
- Sandbox is currently available in cn-beijing only and requires prior SLR authorization.
|
||||
- Global --timeout limits HTTP requests and total template-build polling; --instance-timeout maps to the Sandbox API lifetime field.
|
||||
|
||||
@@ -165,18 +172,19 @@ bl sandbox delete --sandbox-id sbx-xxx --yes
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------- | ------ | -------- | ------------------------------------------------------------------- |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for the Sandbox endpoint (or set BAILIAN_WORKSPACE_ID) |
|
||||
| `--sandbox-id <id>` | string | yes | Sandbox instance ID |
|
||||
| `--show-credentials` | switch | no | Print envd and traffic access tokens instead of redacting them |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------- | ------ | -------- | ---------------------------------------------------------------------------------- |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for the default Sandbox endpoint; optional with a configured base URL |
|
||||
| `--sandbox-id <id>` | string | yes | Sandbox instance ID |
|
||||
| `--show-credentials` | switch | no | Print envd and traffic access tokens instead of redacting them |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
|
||||
#### Notes
|
||||
|
||||
- Auth: uses a Bailian API Key as an Authorization Bearer token; no E2B key is sent.
|
||||
- The workspace is resolved from --workspace-id, BAILIAN_WORKSPACE_ID, then config workspace_id.
|
||||
- Base URL: --base-url > DASHSCOPE_BASE_URL > login/profile base_url. The CLI uses its origin and appends /api/v1/agentstudio/sandbox; otherwise it uses the workspace-scoped cn-beijing endpoint.
|
||||
- Without a configured base URL, workspace is required: --workspace-id > BAILIAN_WORKSPACE_ID > config workspace_id.
|
||||
- Sandbox is currently available in cn-beijing only and requires prior SLR authorization.
|
||||
- Global --timeout limits HTTP requests and total template-build polling; --instance-timeout maps to the Sandbox API lifetime field.
|
||||
|
||||
@@ -201,20 +209,21 @@ bl sandbox get --sandbox-id sbx-xxx --show-credentials --output json
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------- | ------ | -------- | ------------------------------------------------------------------- |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for the Sandbox endpoint (or set BAILIAN_WORKSPACE_ID) |
|
||||
| `--template-id <id>` | string | no | Filter by template ID |
|
||||
| `--sandbox-id <id>` | string | no | Filter by sandbox ID |
|
||||
| `--state <state>` | string | no | Filter by state, for example running or paused |
|
||||
| `--limit <n>` | number | no | Maximum results (1-50) |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------- | ------ | -------- | ---------------------------------------------------------------------------------- |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for the default Sandbox endpoint; optional with a configured base URL |
|
||||
| `--template-id <id>` | string | no | Filter by template ID |
|
||||
| `--sandbox-id <id>` | string | no | Filter by sandbox ID |
|
||||
| `--state <state>` | string | no | Filter by state, for example running or paused |
|
||||
| `--limit <n>` | number | no | Maximum results (1-50) |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
|
||||
#### Notes
|
||||
|
||||
- Auth: uses a Bailian API Key as an Authorization Bearer token; no E2B key is sent.
|
||||
- The workspace is resolved from --workspace-id, BAILIAN_WORKSPACE_ID, then config workspace_id.
|
||||
- Base URL: --base-url > DASHSCOPE_BASE_URL > login/profile base_url. The CLI uses its origin and appends /api/v1/agentstudio/sandbox; otherwise it uses the workspace-scoped cn-beijing endpoint.
|
||||
- Without a configured base URL, workspace is required: --workspace-id > BAILIAN_WORKSPACE_ID > config workspace_id.
|
||||
- Sandbox is currently available in cn-beijing only and requires prior SLR authorization.
|
||||
- Global --timeout limits HTTP requests and total template-build polling; --instance-timeout maps to the Sandbox API lifetime field.
|
||||
|
||||
@@ -243,17 +252,18 @@ bl sandbox list --template-id tpl-xxx --output json
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------- | ------ | -------- | ------------------------------------------------------------------- |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for the Sandbox endpoint (or set BAILIAN_WORKSPACE_ID) |
|
||||
| `--sandbox-id <id>` | string | yes | Sandbox instance ID |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------- | ------ | -------- | ---------------------------------------------------------------------------------- |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for the default Sandbox endpoint; optional with a configured base URL |
|
||||
| `--sandbox-id <id>` | string | yes | Sandbox instance ID |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
|
||||
#### Notes
|
||||
|
||||
- Auth: uses a Bailian API Key as an Authorization Bearer token; no E2B key is sent.
|
||||
- The workspace is resolved from --workspace-id, BAILIAN_WORKSPACE_ID, then config workspace_id.
|
||||
- Base URL: --base-url > DASHSCOPE_BASE_URL > login/profile base_url. The CLI uses its origin and appends /api/v1/agentstudio/sandbox; otherwise it uses the workspace-scoped cn-beijing endpoint.
|
||||
- Without a configured base URL, workspace is required: --workspace-id > BAILIAN_WORKSPACE_ID > config workspace_id.
|
||||
- Sandbox is currently available in cn-beijing only and requires prior SLR authorization.
|
||||
- Global --timeout limits HTTP requests and total template-build polling; --instance-timeout maps to the Sandbox API lifetime field.
|
||||
|
||||
@@ -280,7 +290,7 @@ bl sandbox pause --sandbox-id sbx-xxx --dry-run --output json
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------ | ------ | -------- | ---------------------------------------------------------------------------------- |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for the Sandbox endpoint (or set BAILIAN_WORKSPACE_ID) |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for the default Sandbox endpoint; optional with a configured base URL |
|
||||
| `--sandbox-id <id>` | string | yes | Sandbox instance ID |
|
||||
| `--body <json\|@path>` | string | no | JSON request body, inline or loaded from an @file path; explicit flags override it |
|
||||
| `--instance-timeout <seconds>` | number | no | Sandbox lifetime after this operation (300-604800 seconds) |
|
||||
@@ -291,7 +301,8 @@ bl sandbox pause --sandbox-id sbx-xxx --dry-run --output json
|
||||
#### Notes
|
||||
|
||||
- Auth: uses a Bailian API Key as an Authorization Bearer token; no E2B key is sent.
|
||||
- The workspace is resolved from --workspace-id, BAILIAN_WORKSPACE_ID, then config workspace_id.
|
||||
- Base URL: --base-url > DASHSCOPE_BASE_URL > login/profile base_url. The CLI uses its origin and appends /api/v1/agentstudio/sandbox; otherwise it uses the workspace-scoped cn-beijing endpoint.
|
||||
- Without a configured base URL, workspace is required: --workspace-id > BAILIAN_WORKSPACE_ID > config workspace_id.
|
||||
- Sandbox is currently available in cn-beijing only and requires prior SLR authorization.
|
||||
- Global --timeout limits HTTP requests and total template-build polling; --instance-timeout maps to the Sandbox API lifetime field.
|
||||
|
||||
@@ -316,18 +327,19 @@ bl sandbox resume --sandbox-id sbx-xxx --show-credentials --output json
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------- | ------ | -------- | ------------------------------------------------------------------- |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for the Sandbox endpoint (or set BAILIAN_WORKSPACE_ID) |
|
||||
| `--template-id <id>` | string | yes | Sandbox template ID |
|
||||
| `--build-id <id>` | string | yes | Template build ID |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------- | ------ | -------- | ---------------------------------------------------------------------------------- |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for the default Sandbox endpoint; optional with a configured base URL |
|
||||
| `--template-id <id>` | string | yes | Sandbox template ID |
|
||||
| `--build-id <id>` | string | yes | Template build ID |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
|
||||
#### Notes
|
||||
|
||||
- Auth: uses a Bailian API Key as an Authorization Bearer token; no E2B key is sent.
|
||||
- The workspace is resolved from --workspace-id, BAILIAN_WORKSPACE_ID, then config workspace_id.
|
||||
- Base URL: --base-url > DASHSCOPE_BASE_URL > login/profile base_url. The CLI uses its origin and appends /api/v1/agentstudio/sandbox; otherwise it uses the workspace-scoped cn-beijing endpoint.
|
||||
- Without a configured base URL, workspace is required: --workspace-id > BAILIAN_WORKSPACE_ID > config workspace_id.
|
||||
- Sandbox is currently available in cn-beijing only and requires prior SLR authorization.
|
||||
- Global --timeout limits HTTP requests and total template-build polling; --instance-timeout maps to the Sandbox API lifetime field.
|
||||
|
||||
@@ -354,7 +366,7 @@ bl sandbox template build-status --template-id tpl-xxx --build-id build-xxx --ou
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------ | ------ | -------- | ---------------------------------------------------------------------------------- |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for the Sandbox endpoint (or set BAILIAN_WORKSPACE_ID) |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for the default Sandbox endpoint; optional with a configured base URL |
|
||||
| `--body <json\|@path>` | string | no | JSON request body, inline or loaded from an @file path; explicit flags override it |
|
||||
| `--name <name>` | string | no | Template name |
|
||||
| `--cpu-count <cores>` | number | no | vCPU count |
|
||||
@@ -377,7 +389,8 @@ bl sandbox template build-status --template-id tpl-xxx --build-id build-xxx --ou
|
||||
#### Notes
|
||||
|
||||
- Auth: uses a Bailian API Key as an Authorization Bearer token; no E2B key is sent.
|
||||
- The workspace is resolved from --workspace-id, BAILIAN_WORKSPACE_ID, then config workspace_id.
|
||||
- Base URL: --base-url > DASHSCOPE_BASE_URL > login/profile base_url. The CLI uses its origin and appends /api/v1/agentstudio/sandbox; otherwise it uses the workspace-scoped cn-beijing endpoint.
|
||||
- Without a configured base URL, workspace is required: --workspace-id > BAILIAN_WORKSPACE_ID > config workspace_id.
|
||||
- Sandbox is currently available in cn-beijing only and requires prior SLR authorization.
|
||||
- Global --timeout limits HTTP requests and total template-build polling; --instance-timeout maps to the Sandbox API lifetime field.
|
||||
- File mounts and other complete nested structures can be supplied through --body.
|
||||
@@ -412,18 +425,19 @@ bl sandbox template create --name browser --cpu-count 4 --memory-mb 8192 --dry-r
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------- | ------ | -------- | ------------------------------------------------------------------- |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for the Sandbox endpoint (or set BAILIAN_WORKSPACE_ID) |
|
||||
| `--template-id <id>` | string | yes | Sandbox template ID |
|
||||
| `--yes` | switch | no | Confirm this high-risk operation |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------- | ------ | -------- | ---------------------------------------------------------------------------------- |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for the default Sandbox endpoint; optional with a configured base URL |
|
||||
| `--template-id <id>` | string | yes | Sandbox template ID |
|
||||
| `--yes` | switch | no | Confirm this high-risk operation |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
|
||||
#### Notes
|
||||
|
||||
- Auth: uses a Bailian API Key as an Authorization Bearer token; no E2B key is sent.
|
||||
- The workspace is resolved from --workspace-id, BAILIAN_WORKSPACE_ID, then config workspace_id.
|
||||
- Base URL: --base-url > DASHSCOPE_BASE_URL > login/profile base_url. The CLI uses its origin and appends /api/v1/agentstudio/sandbox; otherwise it uses the workspace-scoped cn-beijing endpoint.
|
||||
- Without a configured base URL, workspace is required: --workspace-id > BAILIAN_WORKSPACE_ID > config workspace_id.
|
||||
- Sandbox is currently available in cn-beijing only and requires prior SLR authorization.
|
||||
- Global --timeout limits HTTP requests and total template-build polling; --instance-timeout maps to the Sandbox API lifetime field.
|
||||
- The server rejects deletion while running or paused instances still use the template.
|
||||
@@ -450,17 +464,18 @@ bl sandbox template delete --template-id tpl-xxx --yes
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------- | ------ | -------- | ------------------------------------------------------------------- |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for the Sandbox endpoint (or set BAILIAN_WORKSPACE_ID) |
|
||||
| `--template-id <id>` | string | yes | Sandbox template ID |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------- | ------ | -------- | ---------------------------------------------------------------------------------- |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for the default Sandbox endpoint; optional with a configured base URL |
|
||||
| `--template-id <id>` | string | yes | Sandbox template ID |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
|
||||
#### Notes
|
||||
|
||||
- Auth: uses a Bailian API Key as an Authorization Bearer token; no E2B key is sent.
|
||||
- The workspace is resolved from --workspace-id, BAILIAN_WORKSPACE_ID, then config workspace_id.
|
||||
- Base URL: --base-url > DASHSCOPE_BASE_URL > login/profile base_url. The CLI uses its origin and appends /api/v1/agentstudio/sandbox; otherwise it uses the workspace-scoped cn-beijing endpoint.
|
||||
- Without a configured base URL, workspace is required: --workspace-id > BAILIAN_WORKSPACE_ID > config workspace_id.
|
||||
- Sandbox is currently available in cn-beijing only and requires prior SLR authorization.
|
||||
- Global --timeout limits HTTP requests and total template-build polling; --instance-timeout maps to the Sandbox API lifetime field.
|
||||
|
||||
@@ -485,18 +500,19 @@ bl sandbox template get --template-id tpl-xxx --output json
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------- | ------ | -------- | ------------------------------------------------------------------- |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for the Sandbox endpoint (or set BAILIAN_WORKSPACE_ID) |
|
||||
| `--limit <n>` | number | no | Maximum results (1-100) |
|
||||
| `--cursor <cursor>` | string | no | Server-side pagination cursor |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------- | ------ | -------- | ---------------------------------------------------------------------------------- |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for the default Sandbox endpoint; optional with a configured base URL |
|
||||
| `--limit <n>` | number | no | Maximum results (1-100) |
|
||||
| `--cursor <cursor>` | string | no | Server-side pagination cursor |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
|
||||
#### Notes
|
||||
|
||||
- Auth: uses a Bailian API Key as an Authorization Bearer token; no E2B key is sent.
|
||||
- The workspace is resolved from --workspace-id, BAILIAN_WORKSPACE_ID, then config workspace_id.
|
||||
- Base URL: --base-url > DASHSCOPE_BASE_URL > login/profile base_url. The CLI uses its origin and appends /api/v1/agentstudio/sandbox; otherwise it uses the workspace-scoped cn-beijing endpoint.
|
||||
- Without a configured base URL, workspace is required: --workspace-id > BAILIAN_WORKSPACE_ID > config workspace_id.
|
||||
- Sandbox is currently available in cn-beijing only and requires prior SLR authorization.
|
||||
- Global --timeout limits HTTP requests and total template-build polling; --instance-timeout maps to the Sandbox API lifetime field.
|
||||
- The API response does not expose a next cursor, so automatic --all pagination is unavailable.
|
||||
@@ -524,7 +540,7 @@ bl sandbox template list --limit 100 --output json
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------ | ------ | -------- | ---------------------------------------------------------------------------------- |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for the Sandbox endpoint (or set BAILIAN_WORKSPACE_ID) |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for the default Sandbox endpoint; optional with a configured base URL |
|
||||
| `--template-id <id>` | string | yes | Sandbox template ID |
|
||||
| `--body <json\|@path>` | string | no | JSON request body, inline or loaded from an @file path; explicit flags override it |
|
||||
| `--name <name>` | string | no | Template name |
|
||||
@@ -546,7 +562,8 @@ bl sandbox template list --limit 100 --output json
|
||||
#### Notes
|
||||
|
||||
- Auth: uses a Bailian API Key as an Authorization Bearer token; no E2B key is sent.
|
||||
- The workspace is resolved from --workspace-id, BAILIAN_WORKSPACE_ID, then config workspace_id.
|
||||
- Base URL: --base-url > DASHSCOPE_BASE_URL > login/profile base_url. The CLI uses its origin and appends /api/v1/agentstudio/sandbox; otherwise it uses the workspace-scoped cn-beijing endpoint.
|
||||
- Without a configured base URL, workspace is required: --workspace-id > BAILIAN_WORKSPACE_ID > config workspace_id.
|
||||
- Sandbox is currently available in cn-beijing only and requires prior SLR authorization.
|
||||
- Global --timeout limits HTTP requests and total template-build polling; --instance-timeout maps to the Sandbox API lifetime field.
|
||||
- Supplying envConfig or --env replaces the template's complete environment map.
|
||||
|
||||
Reference in New Issue
Block a user