Skip to content

Commit c0fca86

Browse files
committed
fix(sandbox): support shared --base-url configuration with workspace fallback
1 parent 332efb0 commit c0fca86

18 files changed

Lines changed: 604 additions & 195 deletions

File tree

‎packages/commands/src/commands/sandbox/instance.ts‎

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {
33
detectOutputFormat,
44
defineCommand,
55
ExitCode,
6-
sandboxEndpoint,
76
SANDBOX_PATHS,
87
sandboxInstanceActionPath,
98
sandboxInstancePath,
@@ -20,7 +19,7 @@ import {
2019
readRequestBody,
2120
redactConnectionCredentials,
2221
redactRequestSecrets,
23-
resolveWorkspaceId,
22+
resolveSandboxEndpoint,
2423
SANDBOX_NOTES,
2524
setDefined,
2625
SHOW_CREDENTIALS_FLAG,
@@ -222,14 +221,14 @@ export const sandboxCreate = defineCommand({
222221
flags: CREATE_FLAGS,
223222
exampleArgs: [
224223
"--template-id tpl-xxx --instance-timeout 3600",
224+
"--template-id tpl-xxx --base-url https://workspace.cn-beijing.maas.aliyuncs.com",
225225
"--body @sandbox.json --dry-run --output json",
226226
"--template-id tpl-xxx --show-credentials --output json",
227227
],
228228
notes: SANDBOX_NOTES,
229229
async run(ctx) {
230230
const format = detectOutputFormat(ctx.settings.output);
231-
const workspaceId = resolveWorkspaceId(ctx);
232-
const endpoint = sandboxEndpoint(workspaceId, SANDBOX_PATHS.sandboxes);
231+
const endpoint = resolveSandboxEndpoint(ctx, SANDBOX_PATHS.sandboxes);
233232
const body = await buildSandboxCreateBody(ctx.flags);
234233
if (ctx.settings.dryRun) {
235234
emitResult({ method: "POST", endpoint, request: redactRequestSecrets(body) }, format);
@@ -265,8 +264,7 @@ export const sandboxList = defineCommand({
265264
return undefined;
266265
},
267266
async run(ctx) {
268-
const workspaceId = resolveWorkspaceId(ctx);
269-
const url = new URL(sandboxEndpoint(workspaceId, SANDBOX_PATHS.sandboxList));
267+
const url = new URL(resolveSandboxEndpoint(ctx, SANDBOX_PATHS.sandboxList));
270268
if (ctx.flags.templateId) url.searchParams.set("templateID", ctx.flags.templateId);
271269
if (ctx.flags.sandboxId) url.searchParams.set("sandboxID", ctx.flags.sandboxId);
272270
if (ctx.flags.state) url.searchParams.set("state", ctx.flags.state);
@@ -314,8 +312,7 @@ export const sandboxGet = defineCommand({
314312
exampleArgs: ["--sandbox-id sbx-xxx", "--sandbox-id sbx-xxx --show-credentials --output json"],
315313
notes: SANDBOX_NOTES,
316314
async run(ctx) {
317-
const workspaceId = resolveWorkspaceId(ctx);
318-
const endpoint = sandboxEndpoint(workspaceId, sandboxInstancePath(ctx.flags.sandboxId));
315+
const endpoint = resolveSandboxEndpoint(ctx, sandboxInstancePath(ctx.flags.sandboxId));
319316
const response = await ctx.client.requestJson<SandboxInfo>({ path: endpoint, method: "GET" });
320317
emitSandboxObject(response, {
321318
format: detectOutputFormat(ctx.settings.output),
@@ -343,9 +340,8 @@ function connectionCommand(action: "connect" | "resume") {
343340
notes: SANDBOX_NOTES,
344341
async run(ctx) {
345342
const format = detectOutputFormat(ctx.settings.output);
346-
const workspaceId = resolveWorkspaceId(ctx);
347-
const endpoint = sandboxEndpoint(
348-
workspaceId,
343+
const endpoint = resolveSandboxEndpoint(
344+
ctx,
349345
sandboxInstanceActionPath(ctx.flags.sandboxId, action),
350346
);
351347
const body = await buildConnectionBody(ctx.flags);
@@ -379,9 +375,8 @@ export const sandboxPause = defineCommand({
379375
notes: SANDBOX_NOTES,
380376
async run(ctx) {
381377
const format = detectOutputFormat(ctx.settings.output);
382-
const workspaceId = resolveWorkspaceId(ctx);
383-
const endpoint = sandboxEndpoint(
384-
workspaceId,
378+
const endpoint = resolveSandboxEndpoint(
379+
ctx,
385380
sandboxInstanceActionPath(ctx.flags.sandboxId, "pause"),
386381
);
387382
if (ctx.settings.dryRun) {
@@ -410,8 +405,7 @@ export const sandboxDelete = defineCommand({
410405
notes: SANDBOX_NOTES,
411406
async run(ctx) {
412407
const format = detectOutputFormat(ctx.settings.output);
413-
const workspaceId = resolveWorkspaceId(ctx);
414-
const endpoint = sandboxEndpoint(workspaceId, sandboxInstancePath(ctx.flags.sandboxId));
408+
const endpoint = resolveSandboxEndpoint(ctx, sandboxInstancePath(ctx.flags.sandboxId));
415409
if (ctx.settings.dryRun) {
416410
emitResult({ method: "DELETE", endpoint, request: null }, format);
417411
return;

‎packages/commands/src/commands/sandbox/shared.ts‎

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
import { readFile } from "node:fs/promises";
2-
import { BailianError, ExitCode, type FlagsDef, type LocalizedText } from "bailian-cli-core";
2+
import {
3+
BailianError,
4+
ExitCode,
5+
sandboxApiPath,
6+
sandboxBaseUrl,
7+
type Client,
8+
type FlagsDef,
9+
type LocalizedText,
10+
} from "bailian-cli-core";
311

412
export const WORKSPACE_FLAG = {
513
workspaceId: {
614
type: "string",
715
valueHint: "<id>",
816
description: {
9-
"en-US": "Workspace ID for the Sandbox endpoint (or set BAILIAN_WORKSPACE_ID)",
10-
"zh-CN": "Sandbox Endpoint 使用的 Workspace ID(也可设置 BAILIAN_WORKSPACE_ID)",
17+
"en-US": "Workspace ID for the default Sandbox endpoint; optional with a configured base URL",
18+
"zh-CN": "默认 Sandbox Endpoint 的 Workspace ID;已配置 Base URL 时可省略",
1119
},
1220
},
1321
} satisfies FlagsDef;
@@ -62,8 +70,15 @@ export const SANDBOX_NOTES: LocalizedText[] = [
6270
},
6371
{
6472
"en-US":
65-
"The workspace is resolved from --workspace-id, BAILIAN_WORKSPACE_ID, then config workspace_id.",
66-
"zh-CN": "Workspace 依次从 --workspace-id、BAILIAN_WORKSPACE_ID、配置项 workspace_id 解析。",
73+
"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.",
74+
"zh-CN":
75+
"Base URL 优先级:--base-url > DASHSCOPE_BASE_URL > 登录/Profile 的 base_url。CLI 取其 origin 并追加 /api/v1/agentstudio/sandbox;未配置时使用工作空间的 cn-beijing Endpoint。",
76+
},
77+
{
78+
"en-US":
79+
"Without a configured base URL, workspace is required: --workspace-id > BAILIAN_WORKSPACE_ID > config workspace_id.",
80+
"zh-CN":
81+
"未配置 Base URL 时必须提供 Workspace:--workspace-id > BAILIAN_WORKSPACE_ID > 配置项 workspace_id。",
6782
},
6883
{
6984
"en-US":
@@ -80,17 +95,23 @@ export const SANDBOX_NOTES: LocalizedText[] = [
8095

8196
export type JsonObject = Record<string, unknown>;
8297

83-
export function resolveWorkspaceId(ctx: {
98+
interface SandboxEndpointContext {
8499
flags: { workspaceId?: string };
85100
settings: { workspaceId?: string };
86-
identity: { binName: string };
87-
}): string {
101+
client: Pick<Client, "url">;
102+
}
103+
104+
export function resolveSandboxEndpoint(ctx: SandboxEndpointContext, path: string): string {
105+
return ctx.client.url(sandboxApiPath(path), () => sandboxBaseUrl(resolveWorkspaceId(ctx)));
106+
}
107+
108+
export function resolveWorkspaceId(ctx: Omit<SandboxEndpointContext, "client">): string {
88109
const workspaceId = ctx.flags.workspaceId || ctx.settings.workspaceId;
89110
if (!workspaceId) {
90111
throw new BailianError(
91-
"Workspace ID is required.",
112+
"Workspace ID is required when no base URL is configured. / 未配置 Base URL 时必须提供 Workspace ID。",
92113
ExitCode.USAGE,
93-
`Pass --workspace-id, set BAILIAN_WORKSPACE_ID, or configure: ${ctx.identity.binName} config set workspace_id <id>`,
114+
"Pass --workspace-id, set BAILIAN_WORKSPACE_ID, configure workspace_id, or set --base-url. / 请传入 --workspace-id、设置 BAILIAN_WORKSPACE_ID 或 workspace_id 配置,或通过 --base-url 指定地址。",
94115
);
95116
}
96117
return workspaceId;

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

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
detectOutputFormat,
44
defineCommand,
55
ExitCode,
6-
sandboxEndpoint,
6+
sandboxApiPath,
77
SANDBOX_PATHS,
88
sandboxTemplateBuildStatusPath,
99
sandboxTemplatePath,
@@ -21,7 +21,7 @@ import {
2121
POLL_INTERVAL_FLAG,
2222
readRequestBody,
2323
redactRequestSecrets,
24-
resolveWorkspaceId,
24+
resolveSandboxEndpoint,
2525
SANDBOX_NOTES,
2626
setDefined,
2727
validateIntegerRange,
@@ -312,7 +312,7 @@ function validatePollInterval(flags: { pollInterval?: number }): string | undefi
312312

313313
async function emitTemplateMutationResult(options: {
314314
response: TemplateInfo;
315-
workspaceId: string;
315+
endpoint: string;
316316
client: Client;
317317
settings: Settings;
318318
async: boolean;
@@ -333,10 +333,13 @@ async function emitTemplateMutationResult(options: {
333333
}
334334
return;
335335
}
336-
const buildEndpoint = sandboxEndpoint(
337-
options.workspaceId,
338-
sandboxTemplateBuildStatusPath(options.response.templateID, options.response.buildID),
339-
);
336+
// Poll the same origin that accepted the build, including custom gateways.
337+
const buildEndpoint = new URL(
338+
sandboxApiPath(
339+
sandboxTemplateBuildStatusPath(options.response.templateID, options.response.buildID),
340+
),
341+
options.endpoint,
342+
).toString();
340343
const build = await waitForTemplateBuild(
341344
options.client,
342345
options.settings,
@@ -372,8 +375,7 @@ export const sandboxTemplateCreate = defineCommand({
372375
validate: validatePollInterval,
373376
async run(ctx) {
374377
const format = detectOutputFormat(ctx.settings.output);
375-
const workspaceId = resolveWorkspaceId(ctx);
376-
const endpoint = sandboxEndpoint(workspaceId, SANDBOX_PATHS.templateCreate);
378+
const endpoint = resolveSandboxEndpoint(ctx, SANDBOX_PATHS.templateCreate);
377379
const body = await buildTemplateCreateBody(ctx.flags);
378380
if (ctx.settings.dryRun) {
379381
emitResult({ method: "POST", endpoint, request: redactRequestSecrets(body) }, format);
@@ -386,7 +388,7 @@ export const sandboxTemplateCreate = defineCommand({
386388
});
387389
await emitTemplateMutationResult({
388390
response,
389-
workspaceId,
391+
endpoint,
390392
client: ctx.client,
391393
settings: ctx.settings,
392394
async: ctx.flags.async,
@@ -419,8 +421,7 @@ export const sandboxTemplateList = defineCommand({
419421
return undefined;
420422
},
421423
async run(ctx) {
422-
const workspaceId = resolveWorkspaceId(ctx);
423-
const url = new URL(sandboxEndpoint(workspaceId, SANDBOX_PATHS.templateList));
424+
const url = new URL(resolveSandboxEndpoint(ctx, SANDBOX_PATHS.templateList));
424425
if (ctx.flags.limit !== undefined) url.searchParams.set("limit", String(ctx.flags.limit));
425426
if (ctx.flags.cursor) url.searchParams.set("cursor", ctx.flags.cursor);
426427
const response = await ctx.client.requestJson<TemplateInfo[]>({
@@ -466,8 +467,7 @@ export const sandboxTemplateGet = defineCommand({
466467
exampleArgs: ["--template-id tpl-xxx", "--template-id tpl-xxx --output json"],
467468
notes: SANDBOX_NOTES,
468469
async run(ctx) {
469-
const workspaceId = resolveWorkspaceId(ctx);
470-
const endpoint = sandboxEndpoint(workspaceId, sandboxTemplatePath(ctx.flags.templateId));
470+
const endpoint = resolveSandboxEndpoint(ctx, sandboxTemplatePath(ctx.flags.templateId));
471471
const response = await ctx.client.requestJson<TemplateInfo>({ path: endpoint, method: "GET" });
472472
if (ctx.settings.quiet) emitBare(displayValue(response.templateID));
473473
else emitResult(response, detectOutputFormat(ctx.settings.output));
@@ -499,8 +499,7 @@ export const sandboxTemplateUpdate = defineCommand({
499499
validate: validatePollInterval,
500500
async run(ctx) {
501501
const format = detectOutputFormat(ctx.settings.output);
502-
const workspaceId = resolveWorkspaceId(ctx);
503-
const endpoint = sandboxEndpoint(workspaceId, sandboxTemplatePath(ctx.flags.templateId));
502+
const endpoint = resolveSandboxEndpoint(ctx, sandboxTemplatePath(ctx.flags.templateId));
504503
const body = await buildTemplateUpdateBody(ctx.flags);
505504
if (ctx.settings.dryRun) {
506505
emitResult({ method: "PUT", endpoint, request: redactRequestSecrets(body) }, format);
@@ -513,7 +512,7 @@ export const sandboxTemplateUpdate = defineCommand({
513512
});
514513
await emitTemplateMutationResult({
515514
response,
516-
workspaceId,
515+
endpoint,
517516
client: ctx.client,
518517
settings: ctx.settings,
519518
async: ctx.flags.async,
@@ -536,9 +535,8 @@ export const sandboxTemplateBuildStatus = defineCommand({
536535
],
537536
notes: SANDBOX_NOTES,
538537
async run(ctx) {
539-
const workspaceId = resolveWorkspaceId(ctx);
540-
const endpoint = sandboxEndpoint(
541-
workspaceId,
538+
const endpoint = resolveSandboxEndpoint(
539+
ctx,
542540
sandboxTemplateBuildStatusPath(ctx.flags.templateId, ctx.flags.buildId),
543541
);
544542
const response = await ctx.client.requestJson<TemplateBuildStatus>({
@@ -573,8 +571,7 @@ export const sandboxTemplateDelete = defineCommand({
573571
],
574572
async run(ctx) {
575573
const format = detectOutputFormat(ctx.settings.output);
576-
const workspaceId = resolveWorkspaceId(ctx);
577-
const endpoint = sandboxEndpoint(workspaceId, sandboxTemplatePath(ctx.flags.templateId));
574+
const endpoint = resolveSandboxEndpoint(ctx, sandboxTemplatePath(ctx.flags.templateId));
578575
if (ctx.settings.dryRun) {
579576
emitResult({ method: "DELETE", endpoint, request: null }, format);
580577
return;

0 commit comments

Comments
 (0)