feat(auth): add retry logic and error handling for API key validation

This commit is contained in:
clh02467605
2026-06-02 16:29:43 +08:00
parent 842bec922d
commit a490969d58
2 changed files with 52 additions and 3 deletions
@@ -281,6 +281,7 @@ export async function runConsoleLogin(
opts?: { needApiKey?: boolean; onApiKey?: (key: string) => Promise<void> },
): Promise<void> {
const state = randomBytes(16).toString("hex");
let callbackError: unknown;
const server = http.createServer(async (req, res) => {
try {
if (req.method === "OPTIONS") {
@@ -313,9 +314,11 @@ export async function runConsoleLogin(
if (apiKey && opts?.onApiKey) {
await opts.onApiKey(apiKey);
}
} catch {
} catch (err: unknown) {
callbackError = err;
res.writeHead(500, { "Content-Type": "text/plain; charset=utf-8" });
res.end("Failed to save credentials\n");
server.close();
return;
}
}
@@ -388,4 +391,8 @@ export async function runConsoleLogin(
}
});
});
if (callbackError) {
throw callbackError;
}
}
+44 -2
View File
@@ -1,4 +1,6 @@
import {
BailianError,
ExitCode,
chatEndpoint,
defineCommand,
getConfigPath,
@@ -16,18 +18,58 @@ 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<void> {
process.stderr.write("Testing key... ");
const testConfig = { ...config, apiKey: key };
await requestJson<unknown>(testConfig, {
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<unknown>(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<string, unknown>;