fix: test Codex connection against actual endpoint (#347)

Change Codex test from token-expiry-only check to probing the real
Codex API endpoint. Sends a minimal request body that triggers a fast
400 without consuming quota. A 400 confirms auth works; only 401/403
indicates a bad token.

Also adds generic acceptStatuses support to the OAuth test framework
so other providers can define non-200 success statuses.
This commit is contained in:
Ryan
2026-03-23 06:29:28 +03:00
committed by GitHub
parent 1ed6c4c76f
commit 97f2a00e74

View File

@@ -18,7 +18,18 @@ import { buildClineHeaders } from "@/shared/utils/clineAuth";
// OAuth provider test endpoints
const OAUTH_TEST_CONFIG = {
claude: { checkExpiry: true, refreshable: true },
codex: { checkExpiry: true, refreshable: true },
codex: {
url: "https://chatgpt.com/backend-api/codex/responses",
method: "POST",
authHeader: "Authorization",
authPrefix: "Bearer ",
extraHeaders: { "Content-Type": "application/json", "originator": "codex-cli", "User-Agent": "codex-cli/1.0.18 (macOS; arm64)" },
// Minimal invalid body — triggers fast 400 without consuming quota
body: JSON.stringify({ model: "gpt-5.3-codex", input: [], stream: false, store: false }),
// 400 (bad request) means auth succeeded; only 401/403 means token is bad
acceptStatuses: [400],
refreshable: true,
},
"gemini-cli": {
url: "https://www.googleapis.com/oauth2/v1/userinfo?alt=json",
method: "GET",
@@ -263,9 +274,12 @@ async function testOAuthConnection(connection, effectiveProxy = null) {
const headers = config.noAuth
? { ...config.extraHeaders }
: { [config.authHeader]: `${config.authPrefix}${accessToken}`, ...config.extraHeaders };
const res = await fetchWithConnectionProxy(testUrl, { method: config.method, headers }, effectiveProxy);
const fetchOpts = { method: config.method, headers };
if (config.body) fetchOpts.body = config.body;
const res = await fetchWithConnectionProxy(testUrl, fetchOpts, effectiveProxy);
if (res.ok) return { valid: true, error: null, refreshed, newTokens };
const accepted = res.ok || (config.acceptStatuses && config.acceptStatuses.includes(res.status));
if (accepted) return { valid: true, error: null, refreshed, newTokens };
if (res.status === 401 && config.refreshable && !refreshed && connection.refreshToken) {
const tokens = await refreshOAuthToken(connection);
@@ -274,11 +288,11 @@ async function testOAuthConnection(connection, effectiveProxy = null) {
const retryHeaders = config.noAuth
? { ...config.extraHeaders }
: { [config.authHeader]: `${config.authPrefix}${tokens.accessToken}`, ...config.extraHeaders };
const retryRes = await fetchWithConnectionProxy(retryUrl, {
method: config.method,
headers: retryHeaders,
}, effectiveProxy);
if (retryRes.ok) return { valid: true, error: null, refreshed: true, newTokens: tokens };
const retryOpts = { method: config.method, headers: retryHeaders };
if (config.body) retryOpts.body = config.body;
const retryRes = await fetchWithConnectionProxy(retryUrl, retryOpts, effectiveProxy);
const retryAccepted = retryRes.ok || (config.acceptStatuses && config.acceptStatuses.includes(retryRes.status));
if (retryAccepted) return { valid: true, error: null, refreshed: true, newTokens: tokens };
}
return { valid: false, error: "Token invalid or revoked", refreshed: false };
}