From 067e96689aa6ec31c49d323da006b62ccff30081 Mon Sep 17 00:00:00 2001 From: lishengzxc <306009337@qq.com> Date: Sun, 14 Jun 2026 20:17:46 +0800 Subject: [PATCH 01/11] feat(console): resolve gateway URL from region + site, add switchAgent support Console gateway URL and action are now resolved from a region + site mapping table instead of a single hardcoded config value. Supports cn-beijing and ap-southeast-1 with domestic/international site variants. - Add ConsoleSite type, REGION_GATEWAYS mapping, and resolveGateway() - Add switchAgent to cornerstoneParam for delegated access - Add console_site, console_region, console_switch_agent to config - Remove consoleGatewayUrl from Config (replaced by region+site resolution) - login-console callback now persists baseUrl, site, region, switchAgent - bl console call gains --site and --switch-agent flags - All callers delegate region default to callConsoleGateway (no more hardcoded cn-beijing) Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/cli/src/commands/app/list.ts | 2 +- .../cli/src/commands/auth/login-console.ts | 56 ++++++++++++++-- packages/cli/src/commands/console/call.ts | 17 ++++- packages/cli/src/commands/mcp/list.ts | 2 +- packages/cli/src/commands/quota/check.ts | 6 +- packages/cli/src/commands/quota/history.ts | 2 +- packages/cli/src/commands/quota/list.ts | 4 +- packages/cli/src/commands/quota/request.ts | 4 +- packages/cli/src/commands/usage/free.ts | 2 +- packages/cli/src/commands/usage/freetier.ts | 4 +- packages/cli/src/commands/usage/stats.ts | 4 +- packages/cli/src/commands/workspace/list.ts | 2 +- packages/core/src/config/loader.ts | 7 +- packages/core/src/config/schema.ts | 14 +++- packages/core/src/console/gateway.ts | 66 ++++++++++++++++--- packages/core/src/console/index.ts | 2 +- packages/core/src/console/models.ts | 2 +- packages/core/tests/index.test.ts | 1 - skills/bailian-cli/reference/console.md | 12 ++-- 19 files changed, 163 insertions(+), 46 deletions(-) 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; + 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(`access_token saved to ${getConfigPath()}\n`); + 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 resolved = resolveGateway(effectiveRegion, effectiveSite); + const gatewayBase = `https://${resolved.csGateway}`; + const action = resolved.action; - const gatewayBase = config.consoleGatewayUrl; + 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 From e68abb697586d7b3296c11e1baffc7f81bd830bf Mon Sep 17 00:00:00 2001 From: lishengzxc <306009337@qq.com> Date: Sun, 14 Jun 2026 22:35:33 +0800 Subject: [PATCH 02/11] refactor(auth): simplify login-console config persistence logic Extract hasConfig variable to deduplicate the multi-field condition check, remove the changed flag pattern in favor of direct write-through. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../cli/src/commands/auth/login-console.ts | 41 ++++++------------- 1 file changed, 12 insertions(+), 29 deletions(-) diff --git a/packages/cli/src/commands/auth/login-console.ts b/packages/cli/src/commands/auth/login-console.ts index 5b9fcd7..1cc8f33 100644 --- a/packages/cli/src/commands/auth/login-console.ts +++ b/packages/cli/src/commands/auth/login-console.ts @@ -13,7 +13,7 @@ import { const CONSOLE_LOGIN_TIMEOUT_MS = 15 * 60 * 1000; const MAX_AUTH_CALLBACK_BODY = 65536; -const DEFAULT_CONSOLE_ORIGIN = "https://bailian.console.aliyun.com"; +const DEFAULT_CONSOLE_ORIGIN = "https://pre-bailian.console.aliyun.com"; export function resolveConsoleOrigin(): string { return process.env.BAILIAN_CONSOLE_ORIGIN || DEFAULT_CONSOLE_ORIGIN; @@ -323,38 +323,21 @@ export async function runConsoleLogin( const { accessToken, apiKey, baseUrl, consoleSite, consoleRegion, consoleSwitchAgent } = await extractCredentialsFromRequest(req); + const hasConfig = + accessToken || baseUrl || consoleSite || consoleRegion || consoleSwitchAgent; - if (accessToken || apiKey || baseUrl || consoleSite || consoleRegion || consoleSwitchAgent) { + if (hasConfig || apiKey) { try { - const existing = readConfigFile() as Record; - let changed = false; - - if (accessToken) { - existing.access_token = accessToken; - 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) { + if (hasConfig) { + const existing = readConfigFile() as Record; + if (accessToken) existing.access_token = accessToken; + if (baseUrl) existing.base_url = baseUrl; + if (consoleSite) existing.console_site = consoleSite; + if (consoleRegion) existing.console_region = consoleRegion; + if (consoleSwitchAgent) existing.console_switch_agent = Number(consoleSwitchAgent); await writeConfigFile(existing); process.stderr.write(`Config saved to ${getConfigPath()}\n`); } - if (apiKey && opts?.onApiKey) { await opts.onApiKey(apiKey); } @@ -373,7 +356,7 @@ export async function runConsoleLogin( }); res.end("OK\n"); - if (accessToken || apiKey || baseUrl || consoleSite || consoleRegion || consoleSwitchAgent) { + if (hasConfig || apiKey) { server.close(); } } catch { From 51d9833a3d6673c7a2e7e863d7d26cb507562cf0 Mon Sep 17 00:00:00 2001 From: lishengzxc <306009337@qq.com> Date: Sun, 14 Jun 2026 22:56:54 +0800 Subject: [PATCH 03/11] fix(auth): parse baseUrl/consoleSite/consoleRegion/consoleSwitchAgent from POST body These fields were only extracted from query params but the console sends them in the JSON POST body. Add parseExtrasFromRawBody() to handle JSON and form-urlencoded bodies. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../cli/src/commands/auth/login-console.ts | 81 ++++++++++++++++++- packages/cli/src/commands/auth/login.ts | 6 +- 2 files changed, 83 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/commands/auth/login-console.ts b/packages/cli/src/commands/auth/login-console.ts index 1cc8f33..e1e45ef 100644 --- a/packages/cli/src/commands/auth/login-console.ts +++ b/packages/cli/src/commands/auth/login-console.ts @@ -210,6 +210,66 @@ function parseApiKeyFromRawBody(raw: string, contentType: string): string | null return null; } +type CallbackExtras = Pick< + CallbackCredentials, + "baseUrl" | "consoleSite" | "consoleRegion" | "consoleSwitchAgent" +>; + +function stringField(o: Record, ...keys: string[]): string | null { + for (const k of keys) { + const v = o[k]; + if (typeof v === "string" && v.trim()) return v.trim(); + } + return null; +} + +function parseExtrasFromRawBody(raw: string, contentType: string): CallbackExtras { + const empty: CallbackExtras = { + baseUrl: null, + consoleSite: null, + consoleRegion: null, + consoleSwitchAgent: null, + }; + if (!raw.trim()) return empty; + + let obj: Record | null = null; + + const ct = contentType.toLowerCase(); + if (ct.includes("application/json") || ct.includes("text/json")) { + try { + const parsed = JSON.parse(raw.trim()); + if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) obj = parsed; + } catch { + /* */ + } + } + if (!obj && ct.includes("application/x-www-form-urlencoded")) { + try { + const params = new URLSearchParams(raw.trim()); + obj = Object.fromEntries(params); + } catch { + /* */ + } + } + if (!obj) { + try { + const parsed = JSON.parse(raw.trim()); + if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) obj = parsed; + } catch { + /* */ + } + } + + if (!obj) return empty; + + return { + baseUrl: stringField(obj, "base_url", "baseUrl"), + consoleSite: stringField(obj, "console_site", "consoleSite"), + consoleRegion: stringField(obj, "console_region", "consoleRegion"), + consoleSwitchAgent: stringField(obj, "console_switch_agent", "consoleSwitchAgent"), + }; +} + interface CallbackCredentials { accessToken: string | null; apiKey: string | null; @@ -264,7 +324,17 @@ async function extractCredentialsFromRequest( const accessToken = accessTokenFromQuery?.trim() || parseAccessTokenFromRawBody(raw, contentType); const apiKey = apiKeyFromQuery?.trim() || parseApiKeyFromRawBody(raw, contentType); - return { accessToken, apiKey, ...extras }; + + const bodyExtras = parseExtrasFromRawBody(raw, contentType); + + return { + accessToken, + apiKey, + baseUrl: extras.baseUrl || bodyExtras.baseUrl, + consoleSite: extras.consoleSite || bodyExtras.consoleSite, + consoleRegion: extras.consoleRegion || bodyExtras.consoleRegion, + consoleSwitchAgent: extras.consoleSwitchAgent || bodyExtras.consoleSwitchAgent, + }; } function listenServerOnFreeLocalPort(server: http.Server): Promise { @@ -298,7 +368,10 @@ function openInBrowser(url: string): Promise { export async function runConsoleLogin( consoleOrigin: string, - opts?: { needApiKey?: boolean; onApiKey?: (key: string) => Promise }, + opts?: { + needApiKey?: boolean; + onApiKey?: ({ apiKey, baseUrl }: { apiKey: string; baseUrl?: string }) => Promise; + }, ): Promise { const state = randomBytes(16).toString("hex"); let callbackError: unknown; @@ -323,6 +396,8 @@ export async function runConsoleLogin( const { accessToken, apiKey, baseUrl, consoleSite, consoleRegion, consoleSwitchAgent } = await extractCredentialsFromRequest(req); + + console.log({ accessToken, apiKey, baseUrl, consoleSite, consoleRegion, consoleSwitchAgent }); const hasConfig = accessToken || baseUrl || consoleSite || consoleRegion || consoleSwitchAgent; @@ -339,7 +414,7 @@ export async function runConsoleLogin( process.stderr.write(`Config saved to ${getConfigPath()}\n`); } if (apiKey && opts?.onApiKey) { - await opts.onApiKey(apiKey); + await opts.onApiKey({ apiKey, baseUrl: baseUrl ?? undefined }); } } catch (err: unknown) { callbackError = err; diff --git a/packages/cli/src/commands/auth/login.ts b/packages/cli/src/commands/auth/login.ts index 0fe05ed..3e4cd03 100644 --- a/packages/cli/src/commands/auth/login.ts +++ b/packages/cli/src/commands/auth/login.ts @@ -41,6 +41,8 @@ function canRetry(err: unknown): boolean { async function validateKeyAndPersist(config: Config, key: string): Promise { process.stderr.write("Testing key... "); + process.stderr.write("\r\n" + JSON.stringify(config)); + const testConfig = { ...config, apiKey: key }; const requestOpts = { url: chatEndpoint(testConfig.baseUrl), @@ -102,7 +104,9 @@ export default defineCommand({ const hasApiKey = !!(config.apiKey || config.fileApiKey); await runConsoleLogin(resolveConsoleOrigin(), { needApiKey: !hasApiKey, - onApiKey: (key) => validateKeyAndPersist(config, key), + onApiKey: ({ apiKey, baseUrl }) => { + return validateKeyAndPersist({ ...config, ...(baseUrl ? { baseUrl } : {}) }, apiKey); + }, }); return; } From 2182a2239f03c5ee1e2395213b2ef61573bd6480 Mon Sep 17 00:00:00 2001 From: lishengzxc <306009337@qq.com> Date: Sun, 14 Jun 2026 23:20:30 +0800 Subject: [PATCH 04/11] =?UTF-8?q?refactor(auth):=20unify=20console=20callb?= =?UTF-8?q?ack=20persistence=20=E2=80=94=20validate=20apiKey=20with=20call?= =?UTF-8?q?back's=20baseUrl?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move apiKey validation into login-console.ts so it uses the baseUrl from the same callback (not stale config). All fields are now persisted in one place: config fields first, then apiKey validated + written. Remove onApiKey callback indirection from runConsoleLogin signature. Clean up debug logging. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../cli/src/commands/auth/login-console.ts | 76 +++++++++++++++++-- packages/cli/src/commands/auth/login.ts | 6 +- 2 files changed, 69 insertions(+), 13 deletions(-) diff --git a/packages/cli/src/commands/auth/login-console.ts b/packages/cli/src/commands/auth/login-console.ts index e1e45ef..0c3242c 100644 --- a/packages/cli/src/commands/auth/login-console.ts +++ b/packages/cli/src/commands/auth/login-console.ts @@ -5,15 +5,18 @@ import http from "node:http"; import { BailianError, ExitCode, + chatEndpoint, getConfigPath, readConfigFile, + requestJson, writeConfigFile, + type Config, } from "bailian-cli-core"; const CONSOLE_LOGIN_TIMEOUT_MS = 15 * 60 * 1000; const MAX_AUTH_CALLBACK_BODY = 65536; -const DEFAULT_CONSOLE_ORIGIN = "https://pre-bailian.console.aliyun.com"; +const DEFAULT_CONSOLE_ORIGIN = "https://bailian.console.aliyun.com"; export function resolveConsoleOrigin(): string { return process.env.BAILIAN_CONSOLE_ORIGIN || DEFAULT_CONSOLE_ORIGIN; @@ -366,12 +369,69 @@ function openInBrowser(url: string): Promise { }); } +const RETRY_DELAY_BASE_MS = 500; + +function canRetry(err: unknown): boolean { + if (err instanceof BailianError) { + if (err.exitCode === ExitCode.NETWORK || err.exitCode === ExitCode.TIMEOUT) return true; + const status = err.api?.httpStatus; + return status === 401 || (status !== undefined && status >= 500); + } + if (err instanceof Error) { + return ( + err.name === "AbortError" || + err.name === "TimeoutError" || + err.message.includes("timed out") || + err.message === "fetch failed" + ); + } + return false; +} + +async function validateAndPersistApiKey( + config: Config, + key: string, + baseUrl: string, +): Promise { + process.stderr.write("Testing key... "); + const testConfig = { ...config, apiKey: key, baseUrl }; + const requestOpts = { + url: chatEndpoint(testConfig.baseUrl), + method: "POST", + timeout: Math.min(config.timeout, 30), + body: { + model: "qwen-max", + messages: [{ role: "user", content: "hi" }], + max_tokens: 1, + }, + }; + + for (let attempt = 1; attempt <= 3; attempt++) { + try { + await requestJson(testConfig, requestOpts); + break; + } catch (err) { + if (attempt >= 3 || !canRetry(err)) { + process.stderr.write("Failed\n"); + throw new BailianError("API key validation failed", ExitCode.AUTH, "Invalid API key.", { + cause: err, + }); + } + const delayMs = RETRY_DELAY_BASE_MS * 2 ** (attempt - 1); + await new Promise((resolve) => setTimeout(resolve, delayMs)); + } + } + + process.stderr.write("Valid\n"); + const existing = readConfigFile() as Record; + existing.api_key = key; + await writeConfigFile(existing); +} + export async function runConsoleLogin( consoleOrigin: string, - opts?: { - needApiKey?: boolean; - onApiKey?: ({ apiKey, baseUrl }: { apiKey: string; baseUrl?: string }) => Promise; - }, + config: Config, + opts?: { needApiKey?: boolean }, ): Promise { const state = randomBytes(16).toString("hex"); let callbackError: unknown; @@ -397,7 +457,6 @@ export async function runConsoleLogin( const { accessToken, apiKey, baseUrl, consoleSite, consoleRegion, consoleSwitchAgent } = await extractCredentialsFromRequest(req); - console.log({ accessToken, apiKey, baseUrl, consoleSite, consoleRegion, consoleSwitchAgent }); const hasConfig = accessToken || baseUrl || consoleSite || consoleRegion || consoleSwitchAgent; @@ -413,8 +472,9 @@ export async function runConsoleLogin( await writeConfigFile(existing); process.stderr.write(`Config saved to ${getConfigPath()}\n`); } - if (apiKey && opts?.onApiKey) { - await opts.onApiKey({ apiKey, baseUrl: baseUrl ?? undefined }); + if (apiKey) { + const testBaseUrl = baseUrl || config.baseUrl; + await validateAndPersistApiKey(config, apiKey, testBaseUrl); } } catch (err: unknown) { callbackError = err; diff --git a/packages/cli/src/commands/auth/login.ts b/packages/cli/src/commands/auth/login.ts index 3e4cd03..540bd5e 100644 --- a/packages/cli/src/commands/auth/login.ts +++ b/packages/cli/src/commands/auth/login.ts @@ -41,7 +41,6 @@ function canRetry(err: unknown): boolean { async function validateKeyAndPersist(config: Config, key: string): Promise { process.stderr.write("Testing key... "); - process.stderr.write("\r\n" + JSON.stringify(config)); const testConfig = { ...config, apiKey: key }; const requestOpts = { @@ -102,11 +101,8 @@ export default defineCommand({ return; } const hasApiKey = !!(config.apiKey || config.fileApiKey); - await runConsoleLogin(resolveConsoleOrigin(), { + await runConsoleLogin(resolveConsoleOrigin(), config, { needApiKey: !hasApiKey, - onApiKey: ({ apiKey, baseUrl }) => { - return validateKeyAndPersist({ ...config, ...(baseUrl ? { baseUrl } : {}) }, apiKey); - }, }); return; } From 6c716e51204cbedca30ce9aab6ad2126fe4e03f5 Mon Sep 17 00:00:00 2001 From: lishengzxc <306009337@qq.com> Date: Sun, 14 Jun 2026 23:44:17 +0800 Subject: [PATCH 05/11] feat(auth): add --base-url flag to bl auth login When used with --api-key, validates the key against the specified base URL and persists it to config. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/cli/src/commands/auth/login.ts | 14 +++++++++++++- skills/bailian-cli/reference/auth.md | 9 +++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/commands/auth/login.ts b/packages/cli/src/commands/auth/login.ts index 540bd5e..71440f8 100644 --- a/packages/cli/src/commands/auth/login.ts +++ b/packages/cli/src/commands/auth/login.ts @@ -85,6 +85,10 @@ export default defineCommand({ usage: "bl auth login --api-key | bl auth login --console", options: [ { flag: "--api-key ", description: "DashScope API key to store" }, + { + flag: "--base-url ", + description: "DashScope API base URL (used with --api-key for validation)", + }, { flag: "--console", description: "Sign in via browser; opens the console login URL in your default browser", @@ -130,8 +134,16 @@ export default defineCommand({ process.exit(0); } + const baseUrl = (flags.baseUrl as string) || undefined; + const effectiveConfig = baseUrl ? { ...config, baseUrl } : config; + if (!config.dryRun) { - await validateKeyAndPersist(config, key); + await validateKeyAndPersist(effectiveConfig, key); + if (baseUrl) { + const existing = readConfigFile() as Record; + existing.base_url = baseUrl; + await writeConfigFile(existing); + } printQuickStart(); } else { emitBare("Would validate and save API key."); diff --git a/skills/bailian-cli/reference/auth.md b/skills/bailian-cli/reference/auth.md index 3790ae5..e51eb1a 100644 --- a/skills/bailian-cli/reference/auth.md +++ b/skills/bailian-cli/reference/auth.md @@ -25,10 +25,11 @@ Index: [index.md](index.md) #### Options -| Flag | Type | Required | Description | -| ----------------- | ------- | -------- | ------------------------------------------------------------------------ | -| `--api-key ` | string | no | DashScope API key to store | -| `--console` | boolean | no | Sign in via browser; opens the console login URL in your default browser | +| Flag | Type | Required | Description | +| ------------------ | ------- | -------- | ------------------------------------------------------------------------ | +| `--api-key ` | string | no | DashScope API key to store | +| `--base-url ` | string | no | DashScope API base URL (used with --api-key for validation) | +| `--console` | boolean | no | Sign in via browser; opens the console login URL in your default browser | #### Examples From 36a60a28482be7ad9902c9d9aca65f4d381abb09 Mon Sep 17 00:00:00 2001 From: lishengzxc <306009337@qq.com> Date: Sun, 14 Jun 2026 23:46:35 +0800 Subject: [PATCH 06/11] =?UTF-8?q?chore(pnpm):=20=E7=A7=BB=E9=99=A4=20vite?= =?UTF-8?q?=20=E5=92=8C=20vitest=20=E7=9A=84=20overrides=20=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pnpm-lock.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 55a0fcb..57503c7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,10 +28,6 @@ catalogs: specifier: ^2.8.3 version: 2.8.3 -overrides: - vite: npm:@voidzero-dev/vite-plus-core@latest - vitest: npm:@voidzero-dev/vite-plus-test@latest - importers: .: From 270412d146ad37c168d5431550bf00560819f323 Mon Sep 17 00:00:00 2001 From: lishengzxc <306009337@qq.com> Date: Mon, 15 Jun 2026 00:26:57 +0800 Subject: [PATCH 07/11] refactor(auth): deduplicate canRetry/validateKey between login.ts and login-console.ts Export validateAndPersistApiKey from login-console.ts and reuse in login.ts. Remove duplicated canRetry, RETRY_DELAY_BASE_MS, and validateKeyAndPersist from login.ts. Unify validation model to qwen3.7-max. Reorder base_url write before apiKey validation in the --api-key path to avoid double read-modify-write. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../cli/src/commands/auth/login-console.ts | 4 +- packages/cli/src/commands/auth/login.ts | 74 ++----------------- 2 files changed, 8 insertions(+), 70 deletions(-) diff --git a/packages/cli/src/commands/auth/login-console.ts b/packages/cli/src/commands/auth/login-console.ts index 0c3242c..15ed539 100644 --- a/packages/cli/src/commands/auth/login-console.ts +++ b/packages/cli/src/commands/auth/login-console.ts @@ -388,7 +388,7 @@ function canRetry(err: unknown): boolean { return false; } -async function validateAndPersistApiKey( +export async function validateAndPersistApiKey( config: Config, key: string, baseUrl: string, @@ -400,7 +400,7 @@ async function validateAndPersistApiKey( method: "POST", timeout: Math.min(config.timeout, 30), body: { - model: "qwen-max", + model: "qwen3.7-max", messages: [{ role: "user", content: "hi" }], max_tokens: 1, }, diff --git a/packages/cli/src/commands/auth/login.ts b/packages/cli/src/commands/auth/login.ts index 71440f8..1327ba6 100644 --- a/packages/cli/src/commands/auth/login.ts +++ b/packages/cli/src/commands/auth/login.ts @@ -1,13 +1,8 @@ import { - BailianError, - ExitCode, - chatEndpoint, defineCommand, - getConfigPath, isInteractive, maskToken, readConfigFile, - requestJson, writeConfigFile, type Config, type GlobalFlags, @@ -16,68 +11,11 @@ import { printQuickStart } from "../../output/banner.ts"; import { emitBare } from "../../output/output.ts"; import { promptConfirm } from "../../output/prompt.ts"; import { printCurrentCommandHelp } from "../../utils/command-help.ts"; -import { resolveConsoleOrigin, runConsoleLogin } from "./login-console.ts"; - -const RETRY_DELAY_BASE_MS = 500; - -function canRetry(err: unknown): boolean { - if (err instanceof BailianError) { - if (err.exitCode === ExitCode.NETWORK || err.exitCode === ExitCode.TIMEOUT) { - return true; - } - const status = err.api?.httpStatus; - return status === 401 || (status !== undefined && status >= 500); - } - if (err instanceof Error) { - return ( - err.name === "AbortError" || - err.name === "TimeoutError" || - err.message.includes("timed out") || - err.message === "fetch failed" - ); - } - return false; -} - -async function validateKeyAndPersist(config: Config, key: string): Promise { - process.stderr.write("Testing key... "); - - const testConfig = { ...config, apiKey: key }; - const requestOpts = { - url: chatEndpoint(testConfig.baseUrl), - method: "POST", - timeout: Math.min(config.timeout, 30), - body: { - model: "qwen3.7-max", - messages: [{ role: "user", content: "hi" }], - max_tokens: 1, - }, - }; - - for (let attempt = 1; attempt <= 3; attempt++) { - try { - await requestJson(testConfig, requestOpts); - break; - } catch (err) { - if (attempt >= 3 || !canRetry(err)) { - process.stderr.write("\n"); - throw new BailianError("API key validation failed", ExitCode.AUTH, "Invalid API key.", { - cause: err, - }); - } - // retry delay: 500ms, 1000ms, 2000ms - const delayMs = RETRY_DELAY_BASE_MS * 2 ** (attempt - 1); - await new Promise((resolve) => setTimeout(resolve, delayMs)); - } - } - - process.stderr.write("Valid\n"); - - const existing = readConfigFile() as Record; - existing.api_key = key; - await writeConfigFile(existing); - process.stderr.write(`Saved to ${getConfigPath()}\n`); -} +import { + resolveConsoleOrigin, + runConsoleLogin, + validateAndPersistApiKey, +} from "./login-console.ts"; export default defineCommand({ name: "auth login", @@ -138,12 +76,12 @@ export default defineCommand({ const effectiveConfig = baseUrl ? { ...config, baseUrl } : config; if (!config.dryRun) { - await validateKeyAndPersist(effectiveConfig, key); if (baseUrl) { const existing = readConfigFile() as Record; existing.base_url = baseUrl; await writeConfigFile(existing); } + await validateAndPersistApiKey(effectiveConfig, key, effectiveConfig.baseUrl); printQuickStart(); } else { emitBare("Would validate and save API key."); From f45b19c2618844670337d69c4df8a9624f0d8d66 Mon Sep 17 00:00:00 2001 From: lishengzxc <306009337@qq.com> Date: Mon, 15 Jun 2026 01:01:26 +0800 Subject: [PATCH 08/11] chore: remove console_gateway_url remnants from schema and tests Field was replaced by region+site gateway resolution but ConfigFile definition, parseConfigFile logic, and test case were left behind. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/core/src/config/schema.ts | 7 ++----- packages/core/tests/index.test.ts | 3 +-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/core/src/config/schema.ts b/packages/core/src/config/schema.ts index b98ff25..76275a6 100644 --- a/packages/core/src/config/schema.ts +++ b/packages/core/src/config/schema.ts @@ -31,7 +31,6 @@ export interface ConfigFile { access_key_id?: string; access_key_secret?: string; workspace_id?: string; - console_gateway_url?: string; console_site?: "domestic" | "international"; console_region?: string; console_switch_agent?: number; @@ -43,8 +42,8 @@ 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 - * `console_gateway_url` from the config file: the credential-bearing client + * A syntactically valid absolute http(s) URL. Used to validate `base_url` + * from the config file: the credential-bearing client * sends the Bearer token to these origins, so a bare `startsWith("http")` check * (which also accepts e.g. "httpfoo://…") is too loose. */ @@ -91,8 +90,6 @@ export function parseConfigFile(raw: unknown): ConfigFile { out.access_key_secret = obj.access_key_secret; if (typeof obj.workspace_id === "string" && obj.workspace_id.length > 0) 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) diff --git a/packages/core/tests/index.test.ts b/packages/core/tests/index.test.ts index 704e7ad..fd433e1 100644 --- a/packages/core/tests/index.test.ts +++ b/packages/core/tests/index.test.ts @@ -199,7 +199,7 @@ test("parseBooleanValue accepts only true and false strings (case-insensitive)", expect(() => parseBooleanValue("maybe")).toThrow(BailianError); }); -test("parseConfigFile accepts only well-formed http(s) base_url / console_gateway_url", () => { +test("parseConfigFile accepts only well-formed http(s) base_url", () => { expect(parseConfigFile({ base_url: "https://dashscope.aliyuncs.com" }).base_url).toBe( "https://dashscope.aliyuncs.com", ); @@ -209,5 +209,4 @@ test("parseConfigFile accepts only well-formed http(s) base_url / console_gatewa // Previously accepted because the value merely "starts with http". expect(parseConfigFile({ base_url: "httpfoo://evil" }).base_url).toBeUndefined(); expect(parseConfigFile({ base_url: "not a url" }).base_url).toBeUndefined(); - expect(parseConfigFile({ console_gateway_url: "ftp://x" }).console_gateway_url).toBeUndefined(); }); From 7a65fb850cba9caeebad6d7602ce046acb6f2ad2 Mon Sep 17 00:00:00 2001 From: lishengzxc <306009337@qq.com> Date: Mon, 15 Jun 2026 01:05:00 +0800 Subject: [PATCH 09/11] feat(auth): parse and persist workspace_id from console login callback Adapts to bailian-cli-login af06baf which added workspace_id to the notifyToken payload. The field is parsed from query/body and written to config.json as workspace_id. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../cli/src/commands/auth/login-console.ts | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/commands/auth/login-console.ts b/packages/cli/src/commands/auth/login-console.ts index 15ed539..b0da573 100644 --- a/packages/cli/src/commands/auth/login-console.ts +++ b/packages/cli/src/commands/auth/login-console.ts @@ -215,7 +215,7 @@ function parseApiKeyFromRawBody(raw: string, contentType: string): string | null type CallbackExtras = Pick< CallbackCredentials, - "baseUrl" | "consoleSite" | "consoleRegion" | "consoleSwitchAgent" + "baseUrl" | "consoleSite" | "consoleRegion" | "consoleSwitchAgent" | "workspaceId" >; function stringField(o: Record, ...keys: string[]): string | null { @@ -232,6 +232,7 @@ function parseExtrasFromRawBody(raw: string, contentType: string): CallbackExtra consoleSite: null, consoleRegion: null, consoleSwitchAgent: null, + workspaceId: null, }; if (!raw.trim()) return empty; @@ -270,6 +271,7 @@ function parseExtrasFromRawBody(raw: string, contentType: string): CallbackExtra consoleSite: stringField(obj, "console_site", "consoleSite"), consoleRegion: stringField(obj, "console_region", "consoleRegion"), consoleSwitchAgent: stringField(obj, "console_switch_agent", "consoleSwitchAgent"), + workspaceId: stringField(obj, "workspace_id", "workspaceId"), }; } @@ -280,6 +282,7 @@ interface CallbackCredentials { consoleSite: string | null; consoleRegion: string | null; consoleSwitchAgent: string | null; + workspaceId: string | null; } async function extractCredentialsFromRequest( @@ -296,12 +299,15 @@ async function extractCredentialsFromRequest( u.searchParams.get("console_region") ?? u.searchParams.get("consoleRegion"); const consoleSwitchAgentFromQuery = u.searchParams.get("console_switch_agent") ?? u.searchParams.get("consoleSwitchAgent"); + const workspaceIdFromQuery = + u.searchParams.get("workspace_id") ?? u.searchParams.get("workspaceId"); const extras = { baseUrl: baseUrlFromQuery?.trim() || null, consoleSite: consoleSiteFromQuery?.trim() || null, consoleRegion: consoleRegionFromQuery?.trim() || null, consoleSwitchAgent: consoleSwitchAgentFromQuery?.trim() || null, + workspaceId: workspaceIdFromQuery?.trim() || null, }; const m = req.method ?? "GET"; @@ -337,6 +343,7 @@ async function extractCredentialsFromRequest( consoleSite: extras.consoleSite || bodyExtras.consoleSite, consoleRegion: extras.consoleRegion || bodyExtras.consoleRegion, consoleSwitchAgent: extras.consoleSwitchAgent || bodyExtras.consoleSwitchAgent, + workspaceId: extras.workspaceId || bodyExtras.workspaceId, }; } @@ -454,11 +461,18 @@ export async function runConsoleLogin( return; } - const { accessToken, apiKey, baseUrl, consoleSite, consoleRegion, consoleSwitchAgent } = - await extractCredentialsFromRequest(req); + const { + accessToken, + apiKey, + baseUrl, + consoleSite, + consoleRegion, + consoleSwitchAgent, + workspaceId, + } = await extractCredentialsFromRequest(req); const hasConfig = - accessToken || baseUrl || consoleSite || consoleRegion || consoleSwitchAgent; + accessToken || baseUrl || consoleSite || consoleRegion || consoleSwitchAgent || workspaceId; if (hasConfig || apiKey) { try { @@ -469,6 +483,7 @@ export async function runConsoleLogin( if (consoleSite) existing.console_site = consoleSite; if (consoleRegion) existing.console_region = consoleRegion; if (consoleSwitchAgent) existing.console_switch_agent = Number(consoleSwitchAgent); + if (workspaceId) existing.workspace_id = workspaceId; await writeConfigFile(existing); process.stderr.write(`Config saved to ${getConfigPath()}\n`); } From 4749b493da64df9ebcfafdef362f8b47d6732a62 Mon Sep 17 00:00:00 2001 From: lishengzxc <306009337@qq.com> Date: Mon, 15 Jun 2026 13:11:49 +0800 Subject: [PATCH 10/11] =?UTF-8?q?feat(auth):=20=E6=9B=B4=E6=96=B0=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E6=8E=A7=E5=88=B6=E5=8F=B0=E7=99=BB=E5=BD=95=E9=A1=B5?= =?UTF-8?q?=E4=B8=BA=E4=B8=AD=E5=9B=BD=E7=AB=99=E5=9C=B0=E5=9D=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/cli/src/commands/auth/login-console.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/commands/auth/login-console.ts b/packages/cli/src/commands/auth/login-console.ts index b0da573..17eebe1 100644 --- a/packages/cli/src/commands/auth/login-console.ts +++ b/packages/cli/src/commands/auth/login-console.ts @@ -16,7 +16,8 @@ import { const CONSOLE_LOGIN_TIMEOUT_MS = 15 * 60 * 1000; const MAX_AUTH_CALLBACK_BODY = 65536; -const DEFAULT_CONSOLE_ORIGIN = "https://bailian.console.aliyun.com"; +// 总是默认打开 中国站的登录页 +const DEFAULT_CONSOLE_ORIGIN = "https://pre-bailian.console.aliyun.com"; export function resolveConsoleOrigin(): string { return process.env.BAILIAN_CONSOLE_ORIGIN || DEFAULT_CONSOLE_ORIGIN; From e5abd1b5540b38855580fd82a2d77b1fc6bde001 Mon Sep 17 00:00:00 2001 From: lishengzxc <306009337@qq.com> Date: Mon, 15 Jun 2026 13:53:41 +0800 Subject: [PATCH 11/11] =?UTF-8?q?feat(auth):=20=E6=9B=B4=E6=96=B0=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E6=8E=A7=E5=88=B6=E5=8F=B0=E7=99=BB=E5=BD=95=E9=A1=B5?= =?UTF-8?q?=E4=B8=BA=E6=AD=A3=E5=BC=8F=E4=B8=AD=E5=9B=BD=E7=AB=99=E5=9C=B0?= =?UTF-8?q?=E5=9D=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/cli/src/commands/auth/login-console.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/commands/auth/login-console.ts b/packages/cli/src/commands/auth/login-console.ts index 17eebe1..ddb159b 100644 --- a/packages/cli/src/commands/auth/login-console.ts +++ b/packages/cli/src/commands/auth/login-console.ts @@ -17,10 +17,10 @@ const CONSOLE_LOGIN_TIMEOUT_MS = 15 * 60 * 1000; const MAX_AUTH_CALLBACK_BODY = 65536; // 总是默认打开 中国站的登录页 -const DEFAULT_CONSOLE_ORIGIN = "https://pre-bailian.console.aliyun.com"; +const DEFAULT_CONSOLE_ORIGIN = "https://bailian.console.aliyun.com"; export function resolveConsoleOrigin(): string { - return process.env.BAILIAN_CONSOLE_ORIGIN || DEFAULT_CONSOLE_ORIGIN; + return DEFAULT_CONSOLE_ORIGIN; } function readBodyBounded(req: http.IncomingMessage): Promise {