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] =?UTF-8?q?refactor(auth):=20unify=20console=20callback=20?= =?UTF-8?q?persistence=20=E2=80=94=20validate=20apiKey=20with=20callback's?= =?UTF-8?q?=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; }