diff --git a/packages/cli/src/commands/app/list.ts b/packages/cli/src/commands/app/list.ts index 904ad47..d4f8784 100644 --- a/packages/cli/src/commands/app/list.ts +++ b/packages/cli/src/commands/app/list.ts @@ -44,7 +44,7 @@ export default defineCommand({ const name = (flags.name as string) || ""; const pageNo = (flags.page as number) || 1; const pageSize = (flags.pageSize as number) || 30; - const region = (flags.region as string) || "cn-beijing"; + const region = (flags.region as string) || undefined; const format = detectOutputFormat(config.output); const credential = await resolveConsoleGatewayCredential(config); diff --git a/packages/cli/src/commands/auth/login-console.ts b/packages/cli/src/commands/auth/login-console.ts index f212325..5b9fcd7 100644 --- a/packages/cli/src/commands/auth/login-console.ts +++ b/packages/cli/src/commands/auth/login-console.ts @@ -213,6 +213,10 @@ function parseApiKeyFromRawBody(raw: string, contentType: string): string | null interface CallbackCredentials { accessToken: string | null; apiKey: string | null; + baseUrl: string | null; + consoleSite: string | null; + consoleRegion: string | null; + consoleSwitchAgent: string | null; } async function extractCredentialsFromRequest( @@ -222,12 +226,27 @@ async function extractCredentialsFromRequest( const accessTokenFromQuery = u.searchParams.get("access_token") ?? u.searchParams.get("accessToken"); const apiKeyFromQuery = u.searchParams.get("api_key") ?? u.searchParams.get("apiKey"); + const baseUrlFromQuery = u.searchParams.get("base_url") ?? u.searchParams.get("baseUrl"); + const consoleSiteFromQuery = + u.searchParams.get("console_site") ?? u.searchParams.get("consoleSite"); + const consoleRegionFromQuery = + u.searchParams.get("console_region") ?? u.searchParams.get("consoleRegion"); + const consoleSwitchAgentFromQuery = + u.searchParams.get("console_switch_agent") ?? u.searchParams.get("consoleSwitchAgent"); + + const extras = { + baseUrl: baseUrlFromQuery?.trim() || null, + consoleSite: consoleSiteFromQuery?.trim() || null, + consoleRegion: consoleRegionFromQuery?.trim() || null, + consoleSwitchAgent: consoleSwitchAgentFromQuery?.trim() || null, + }; const m = req.method ?? "GET"; if (m !== "POST" && m !== "PUT" && m !== "PATCH") { return { accessToken: accessTokenFromQuery?.trim() || null, apiKey: apiKeyFromQuery?.trim() || null, + ...extras, }; } @@ -239,12 +258,13 @@ async function extractCredentialsFromRequest( return { accessToken: accessTokenFromQuery?.trim() || null, apiKey: apiKeyFromQuery?.trim() || null, + ...extras, }; } const accessToken = accessTokenFromQuery?.trim() || parseAccessTokenFromRawBody(raw, contentType); const apiKey = apiKeyFromQuery?.trim() || parseApiKeyFromRawBody(raw, contentType); - return { accessToken, apiKey }; + return { accessToken, apiKey, ...extras }; } function listenServerOnFreeLocalPort(server: http.Server): Promise { @@ -301,16 +321,40 @@ export async function runConsoleLogin( return; } - const { accessToken, apiKey } = await extractCredentialsFromRequest(req); + const { accessToken, apiKey, baseUrl, consoleSite, consoleRegion, consoleSwitchAgent } = + await extractCredentialsFromRequest(req); - if (accessToken || apiKey) { + if (accessToken || apiKey || baseUrl || consoleSite || consoleRegion || consoleSwitchAgent) { try { + const existing = readConfigFile() as Record; + let changed = false; + if (accessToken) { - const existing = readConfigFile() as Record; existing.access_token = accessToken; - await writeConfigFile(existing); - process.stderr.write(`access_token saved to ${getConfigPath()}\n`); + changed = true; } + if (baseUrl) { + existing.base_url = baseUrl; + changed = true; + } + if (consoleSite) { + existing.console_site = consoleSite; + changed = true; + } + if (consoleRegion) { + existing.console_region = consoleRegion; + changed = true; + } + if (consoleSwitchAgent) { + existing.console_switch_agent = Number(consoleSwitchAgent); + changed = true; + } + + if (changed) { + await writeConfigFile(existing); + process.stderr.write(`Config saved to ${getConfigPath()}\n`); + } + if (apiKey && opts?.onApiKey) { await opts.onApiKey(apiKey); } @@ -329,7 +373,7 @@ export async function runConsoleLogin( }); res.end("OK\n"); - if (accessToken || apiKey) { + if (accessToken || apiKey || baseUrl || consoleSite || consoleRegion || consoleSwitchAgent) { server.close(); } } catch { diff --git a/packages/cli/src/commands/console/call.ts b/packages/cli/src/commands/console/call.ts index b3d01ba..ae4e552 100644 --- a/packages/cli/src/commands/console/call.ts +++ b/packages/cli/src/commands/console/call.ts @@ -7,6 +7,7 @@ import { detectOutputFormat, type Config, type GlobalFlags, + type ConsoleSite, } from "bailian-cli-core"; import { failIfMissing } from "../../output/prompt.ts"; import { emitResult } from "../../output/output.ts"; @@ -28,7 +29,15 @@ export default defineCommand({ }, { flag: "--region ", - description: "API region (default: cn-beijing)", + description: "Console region (e.g. cn-beijing, ap-southeast-1)", + }, + { + flag: "--site ", + description: "Console site: domestic or international", + }, + { + flag: "--switch-agent ", + description: "Switch agent UID for delegated access", }, ], examples: [ @@ -50,7 +59,9 @@ export default defineCommand({ process.exit(1); } - const region = (flags.region as string) || "cn-beijing"; + const region = (flags.region as string) || undefined; + const site = ((flags.site as string) || undefined) as ConsoleSite | undefined; + const switchAgent = flags.switchAgent ? Number(flags.switchAgent) : undefined; const format = detectOutputFormat(config.output); let token: string | undefined; @@ -71,6 +82,8 @@ export default defineCommand({ api, data, region, + site, + switchAgent, }); emitResult(result, format); diff --git a/packages/cli/src/commands/mcp/list.ts b/packages/cli/src/commands/mcp/list.ts index 561bd0e..5edd6f0 100644 --- a/packages/cli/src/commands/mcp/list.ts +++ b/packages/cli/src/commands/mcp/list.ts @@ -43,7 +43,7 @@ export default defineCommand({ const type = (flags.type as string) || "OFFICIAL"; const pageNo = (flags.page as number) || 1; const pageSize = (flags.pageSize as number) || 30; - const region = (flags.region as string) || "cn-beijing"; + const region = (flags.region as string) || undefined; const format = detectOutputFormat(config.output); const data = { diff --git a/packages/cli/src/commands/quota/check.ts b/packages/cli/src/commands/quota/check.ts index 5c52fa8..ba7a225 100644 --- a/packages/cli/src/commands/quota/check.ts +++ b/packages/cli/src/commands/quota/check.ts @@ -94,7 +94,7 @@ function extractResponseData(result: Record): Record { const allModels: ModelWithQpm[] = []; let pageNo = 1; @@ -130,7 +130,7 @@ async function fetchAllModelsWithQpm( async function fetchMonitorData( config: Config, token: string, - region: string, + region: string | undefined, modelName: string, windowMinutes: number, ): Promise<{ rpm: number; tpm: number }> { @@ -280,7 +280,7 @@ export default defineCommand({ process.exit(1); } const windowMinutes = rawPeriod; - const region = (flags.region as string) || "cn-beijing"; + const region = (flags.region as string) || undefined; const format = detectOutputFormat(config.output); const credential = await resolveConsoleGatewayCredential(config); diff --git a/packages/cli/src/commands/quota/history.ts b/packages/cli/src/commands/quota/history.ts index 99dd510..033bb85 100644 --- a/packages/cli/src/commands/quota/history.ts +++ b/packages/cli/src/commands/quota/history.ts @@ -130,7 +130,7 @@ export default defineCommand({ const page = Number(flags.page) || 1; const pageSize = Number(flags.pageSize) || 10; const modelFilter = (flags.model as string) || undefined; - const region = (flags.region as string) || "cn-beijing"; + const region = (flags.region as string) || undefined; const format = detectOutputFormat(config.output); const credential = await resolveConsoleGatewayCredential(config); diff --git a/packages/cli/src/commands/quota/list.ts b/packages/cli/src/commands/quota/list.ts index 3002757..1f4761d 100644 --- a/packages/cli/src/commands/quota/list.ts +++ b/packages/cli/src/commands/quota/list.ts @@ -68,7 +68,7 @@ function extractResponseData(result: Record): Record { const allModels: ModelWithQpm[] = []; @@ -186,7 +186,7 @@ export default defineCommand({ async run(config: Config, flags: GlobalFlags) { const modelFlag = (flags.model as string) || undefined; const showAll = Boolean(flags.all); - const region = (flags.region as string) || "cn-beijing"; + const region = (flags.region as string) || undefined; const format = detectOutputFormat(config.output); const credential = await resolveConsoleGatewayCredential(config); diff --git a/packages/cli/src/commands/quota/request.ts b/packages/cli/src/commands/quota/request.ts index 60f727b..c18045e 100644 --- a/packages/cli/src/commands/quota/request.ts +++ b/packages/cli/src/commands/quota/request.ts @@ -53,7 +53,7 @@ function extractResponseData(result: Record): Record } | undefined> { const raw = await callConsoleGateway(config, token, { @@ -122,7 +122,7 @@ export default defineCommand({ } const autoConfirm = Boolean(flags.yes) || config.yes; - const region = (flags.region as string) || "cn-beijing"; + const region = (flags.region as string) || undefined; const format = detectOutputFormat(config.output); const credential = await resolveConsoleGatewayCredential(config); diff --git a/packages/cli/src/commands/usage/free.ts b/packages/cli/src/commands/usage/free.ts index 3dd11a0..4ac1eb7 100644 --- a/packages/cli/src/commands/usage/free.ts +++ b/packages/cli/src/commands/usage/free.ts @@ -232,7 +232,7 @@ export default defineCommand({ ); process.exit(1); } - const region = (flags.region as string) || "cn-beijing"; + const region = (flags.region as string) || undefined; const format = detectOutputFormat(config.output); const credential = await resolveConsoleGatewayCredential(config); diff --git a/packages/cli/src/commands/usage/freetier.ts b/packages/cli/src/commands/usage/freetier.ts index 8894b02..671bb89 100644 --- a/packages/cli/src/commands/usage/freetier.ts +++ b/packages/cli/src/commands/usage/freetier.ts @@ -63,7 +63,7 @@ async function pollUntilDone( api: string, requestKey: string, models: string[], - region: string, + region: string | undefined, ): Promise { let nextTaskId: string | undefined; @@ -140,7 +140,7 @@ export default defineCommand({ const modelFlag = (flags.model as string) || undefined; const all = Boolean(flags.all); const off = Boolean(flags.off); - const region = (flags.region as string) || "cn-beijing"; + const region = (flags.region as string) || undefined; const format = detectOutputFormat(config.output); if (!modelFlag && !all) { diff --git a/packages/cli/src/commands/usage/stats.ts b/packages/cli/src/commands/usage/stats.ts index fb5074b..35e5c0b 100644 --- a/packages/cli/src/commands/usage/stats.ts +++ b/packages/cli/src/commands/usage/stats.ts @@ -70,7 +70,7 @@ async function pollTelemetryApi( token: string, api: string, reqDTO: Record, - region: string, + region: string | undefined, ): Promise { let nextTaskId: string | undefined; @@ -343,7 +343,7 @@ export default defineCommand({ const modelFlag = (flags.model as string) || undefined; const daysFlag = Number(flags.days) || 7; const typeFlag = (flags.type as string) || undefined; - const region = (flags.region as string) || "cn-beijing"; + const region = (flags.region as string) || undefined; const format = detectOutputFormat(config.output); const flagWorkspaceId = (flags.workspaceId as string) || undefined; diff --git a/packages/cli/src/commands/workspace/list.ts b/packages/cli/src/commands/workspace/list.ts index b9f6edb..58857ef 100644 --- a/packages/cli/src/commands/workspace/list.ts +++ b/packages/cli/src/commands/workspace/list.ts @@ -100,7 +100,7 @@ export default defineCommand({ ], examples: ["bl workspace list", "bl workspace list --list 5", "bl workspace list --output json"], async run(config: Config, flags: GlobalFlags) { - const region = (flags.region as string) || "cn-beijing"; + const region = (flags.region as string) || undefined; const limit = Number(flags.list) || 0; const format = detectOutputFormat(config.output); diff --git a/packages/core/src/config/loader.ts b/packages/core/src/config/loader.ts index 30ce611..4e5f39e 100644 --- a/packages/core/src/config/loader.ts +++ b/packages/core/src/config/loader.ts @@ -84,10 +84,9 @@ export function loadConfig(flags: GlobalFlags): Config { accessKeySecret: process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET || file.access_key_secret || undefined, workspaceId: process.env.BAILIAN_WORKSPACE_ID || file.workspace_id || undefined, - consoleGatewayUrl: - process.env.BAILIAN_CONSOLE_GATEWAY_URL || - file.console_gateway_url || - "https://bailian-cs.console.aliyun.com", + consoleSite: file.console_site || undefined, + consoleRegion: file.console_region || undefined, + consoleSwitchAgent: file.console_switch_agent || undefined, verbose: flags.verbose || process.env.DASHSCOPE_VERBOSE === "1", quiet: flags.quiet || false, noColor: flags.noColor || process.env.NO_COLOR !== undefined || !process.stdout.isTTY, diff --git a/packages/core/src/config/schema.ts b/packages/core/src/config/schema.ts index 7fad89f..b98ff25 100644 --- a/packages/core/src/config/schema.ts +++ b/packages/core/src/config/schema.ts @@ -32,11 +32,15 @@ export interface ConfigFile { access_key_secret?: string; workspace_id?: string; console_gateway_url?: string; + console_site?: "domestic" | "international"; + console_region?: string; + console_switch_agent?: number; telemetry?: boolean; } const VALID_REGIONS = new Set(["cn", "us", "intl"]); const VALID_OUTPUTS = new Set(["text", "json"]); +const VALID_CONSOLE_SITES = new Set(["domestic", "international"]); /** * A syntactically valid absolute http(s) URL. Used to validate `base_url` and @@ -89,6 +93,12 @@ export function parseConfigFile(raw: unknown): ConfigFile { out.workspace_id = obj.workspace_id; if (typeof obj.console_gateway_url === "string" && isHttpUrl(obj.console_gateway_url)) out.console_gateway_url = obj.console_gateway_url; + if (typeof obj.console_site === "string" && VALID_CONSOLE_SITES.has(obj.console_site)) + out.console_site = obj.console_site as ConfigFile["console_site"]; + if (typeof obj.console_region === "string" && obj.console_region.length > 0) + out.console_region = obj.console_region; + if (typeof obj.console_switch_agent === "number" && obj.console_switch_agent > 0) + out.console_switch_agent = obj.console_switch_agent; if (typeof obj.telemetry === "boolean") out.telemetry = obj.telemetry; return out; @@ -118,7 +128,9 @@ export interface Config { accessKeyId?: string; accessKeySecret?: string; workspaceId?: string; - consoleGatewayUrl: string; + consoleSite?: "domestic" | "international"; + consoleRegion?: string; + consoleSwitchAgent?: number; verbose: boolean; quiet: boolean; noColor: boolean; diff --git a/packages/core/src/console/gateway.ts b/packages/core/src/console/gateway.ts index a698d12..d391784 100644 --- a/packages/core/src/console/gateway.ts +++ b/packages/core/src/console/gateway.ts @@ -2,18 +2,56 @@ import type { Config } from "../config/schema.ts"; import { BailianError } from "../errors/base.ts"; import { ExitCode } from "../errors/codes.ts"; -const GATEWAY_ACTION = "BroadScopeAspnGateway"; const GATEWAY_PRODUCT = "sfm_bailian"; +export type ConsoleSite = "domestic" | "international"; + +interface ConsoleGatewayInfo { + csGateway: string; + action: string; +} + +const REGION_GATEWAYS: Record> = { + "cn-beijing": { + domestic: { csGateway: "bailian-cs.console.aliyun.com", action: "BroadScopeAspnGateway" }, + international: { + csGateway: "bailian-cs.console.alibabacloud.com", + action: "BroadScopeAspnGateway", + }, + }, + "ap-southeast-1": { + domestic: { + csGateway: "modelstudio-cs.console.aliyun.com", + action: "IntlBroadScopeAspnGateway", + }, + international: { + csGateway: "bailian-singapore-cs.alibabacloud.com", + action: "IntlBroadScopeAspnGateway", + }, + }, +}; + +function resolveGateway(region: string, site: ConsoleSite): ConsoleGatewayInfo { + return REGION_GATEWAYS[region]?.[site] ?? REGION_GATEWAYS["cn-beijing"]![site]; +} + export interface ConsoleGatewayRequest { /** Console API name, e.g. zeldaEasy.broadscope-bailian.freeTrial.queryFreeTierQuota */ api: string; data: Record; - /** Console region (default: cn-beijing), distinct from DashScope `config.region`. */ + /** Console region (e.g. cn-beijing, ap-southeast-1). Falls back to config.consoleRegion, then "cn-beijing". */ region?: string; + /** Console site. Falls back to config.consoleSite, then "domestic". */ + site?: ConsoleSite; + /** Switch-agent UID for delegated access. Falls back to config.consoleSwitchAgent. */ + switchAgent?: number; } -function buildGatewayParams(api: string, data: Record): string { +function buildGatewayParams( + api: string, + data: Record, + switchAgent?: number, +): string { return JSON.stringify({ Api: api, V: "1.0", @@ -24,6 +62,7 @@ function buildGatewayParams(api: string, data: Record): string console: "ONE_CONSOLE", productCode: "p_efm", consoleSite: "BAILIAN_ALIYUN", + ...(switchAgent != null ? { switchAgent } : {}), ...(typeof data.cornerstoneParam === "object" && data.cornerstoneParam !== null ? (data.cornerstoneParam as Record) : {}), @@ -37,17 +76,26 @@ function buildGatewayParams(api: string, data: Record): string * `token` is the console `access_token` (from `bl auth login --console`); when * omitted the request is sent without an Authorization header, which works for * public console APIs that don't require a login session. + * + * Gateway URL and action are resolved from `region + site` via {@link REGION_GATEWAYS}. + * Each parameter falls back to the corresponding config value, then to a hardcoded default. */ export async function callConsoleGateway( config: Config, token: string | undefined, - { api, data, region = "cn-beijing" }: ConsoleGatewayRequest, + { api, data, region, site, switchAgent }: ConsoleGatewayRequest, ): Promise { - const params = buildGatewayParams(api, data); - const body = new URLSearchParams({ params, region }); - const timeoutMs = config.timeout * 1000; + const effectiveRegion = region ?? config.consoleRegion ?? "cn-beijing"; + const effectiveSite = site ?? config.consoleSite ?? "domestic"; + const effectiveSwitchAgent = switchAgent ?? config.consoleSwitchAgent; - const gatewayBase = config.consoleGatewayUrl; + const resolved = resolveGateway(effectiveRegion, effectiveSite); + const gatewayBase = `https://${resolved.csGateway}`; + const action = resolved.action; + + const params = buildGatewayParams(api, data, effectiveSwitchAgent); + const body = new URLSearchParams({ params, region: effectiveRegion }); + const timeoutMs = config.timeout * 1000; const headers: Record = { Accept: "*/*", @@ -56,7 +104,7 @@ export async function callConsoleGateway( if (token) headers.Authorization = `Bearer ${token}`; const res = await fetch( - `${gatewayBase}/cli/api.json?action=${GATEWAY_ACTION}&product=${GATEWAY_PRODUCT}&api=${encodeURIComponent(api)}`, + `${gatewayBase}/cli/api.json?action=${action}&product=${GATEWAY_PRODUCT}&api=${encodeURIComponent(api)}`, { method: "POST", headers, diff --git a/packages/core/src/console/index.ts b/packages/core/src/console/index.ts index e08e08a..61cf595 100644 --- a/packages/core/src/console/index.ts +++ b/packages/core/src/console/index.ts @@ -1,4 +1,4 @@ -export type { ConsoleGatewayRequest } from "./gateway.ts"; +export type { ConsoleGatewayRequest, ConsoleSite } from "./gateway.ts"; export { callConsoleGateway } from "./gateway.ts"; export type { ModelListParams, ModelListResult } from "./models.ts"; export { fetchModelList } from "./models.ts"; diff --git a/packages/core/src/console/models.ts b/packages/core/src/console/models.ts index 0711790..70345c2 100644 --- a/packages/core/src/console/models.ts +++ b/packages/core/src/console/models.ts @@ -28,7 +28,7 @@ export async function fetchModelList( name = "", providers = [], capabilities = [], - region = "cn-beijing", + region, } = params; const result = (await callConsoleGateway(config, token, { diff --git a/packages/core/tests/index.test.ts b/packages/core/tests/index.test.ts index 5514413..704e7ad 100644 --- a/packages/core/tests/index.test.ts +++ b/packages/core/tests/index.test.ts @@ -22,7 +22,6 @@ function testConfig(overrides: Partial = {}): Config { nonInteractive: true, async: false, telemetry: true, - consoleGatewayUrl: "https://bailian-cs.console.aliyun.com", ...overrides, }; } diff --git a/skills/bailian-cli/reference/console.md b/skills/bailian-cli/reference/console.md index 50e5ea1..12e4e95 100644 --- a/skills/bailian-cli/reference/console.md +++ b/skills/bailian-cli/reference/console.md @@ -23,11 +23,13 @@ Index: [index.md](index.md) #### Options -| Flag | Type | Required | Description | -| ------------------- | ------ | -------- | ------------------------------------------------------------------------ | -| `--api ` | string | yes | API name (e.g. zeldaEasy.broadscope-bailian.memory-library.getLibraries) | -| `--data ` | string | yes | Request data as JSON string | -| `--region ` | string | no | API region (default: cn-beijing) | +| Flag | Type | Required | Description | +| ---------------------- | ------ | -------- | ------------------------------------------------------------------------ | +| `--api ` | string | yes | API name (e.g. zeldaEasy.broadscope-bailian.memory-library.getLibraries) | +| `--data ` | string | yes | Request data as JSON string | +| `--region ` | string | no | Console region (e.g. cn-beijing, ap-southeast-1) | +| `--site ` | string | no | Console site: domestic or international | +| `--switch-agent ` | string | no | Switch agent UID for delegated access | #### Examples