fix custom provider API key validation fallback (#773)
Co-authored-by: zuolingxuan <zuolingxuan@bytedance.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
e640316382
commit
91c735c9f4
@@ -10,6 +10,10 @@ type ValidationProfile =
|
||||
| 'none';
|
||||
|
||||
type ValidationResult = { valid: boolean; error?: string; status?: number };
|
||||
type ClassifiedValidationResult = ValidationResult & { authFailure?: boolean };
|
||||
|
||||
const AUTH_ERROR_PATTERN = /\b(unauthorized|forbidden|access denied|invalid api key|api key invalid|incorrect api key|api key incorrect|authentication failed|auth failed|invalid credential|credential invalid|invalid signature|signature invalid|invalid access token|access token invalid|invalid bearer token|bearer token invalid|access token expired)\b|鉴权失败|認証失敗|认证失败|無效密鑰|无效密钥|密钥无效|密鑰無效|憑證無效|凭证无效/i;
|
||||
const AUTH_ERROR_CODE_PATTERN = /\b(unauthorized|forbidden|access[_-]?denied|invalid[_-]?api[_-]?key|api[_-]?key[_-]?invalid|incorrect[_-]?api[_-]?key|api[_-]?key[_-]?incorrect|authentication[_-]?failed|auth[_-]?failed|invalid[_-]?credential|credential[_-]?invalid|invalid[_-]?signature|signature[_-]?invalid|invalid[_-]?access[_-]?token|access[_-]?token[_-]?invalid|invalid[_-]?bearer[_-]?token|bearer[_-]?token[_-]?invalid|access[_-]?token[_-]?expired|invalid[_-]?token|token[_-]?invalid|token[_-]?expired)\b/i;
|
||||
|
||||
function logValidationStatus(provider: string, status: number): void {
|
||||
console.log(`[clawx-validate] ${provider} HTTP ${status}`);
|
||||
@@ -118,7 +122,7 @@ async function performProviderValidationRequest(
|
||||
providerLabel: string,
|
||||
url: string,
|
||||
headers: Record<string, string>,
|
||||
): Promise<ValidationResult> {
|
||||
): Promise<ClassifiedValidationResult> {
|
||||
try {
|
||||
logValidationRequest(providerLabel, 'GET', url, headers);
|
||||
const response = await proxyAwareFetch(url, { headers });
|
||||
@@ -137,16 +141,56 @@ async function performProviderValidationRequest(
|
||||
function classifyAuthResponse(
|
||||
status: number,
|
||||
data: unknown,
|
||||
): { valid: boolean; error?: string } {
|
||||
) : ClassifiedValidationResult {
|
||||
const obj = data as {
|
||||
error?: { message?: string; code?: string };
|
||||
message?: string;
|
||||
code?: string;
|
||||
} | null;
|
||||
const msg = obj?.error?.message || obj?.message || `API error: ${status}`;
|
||||
const code = obj?.error?.code || obj?.code;
|
||||
const hasAuthCode = typeof code === 'string' && AUTH_ERROR_CODE_PATTERN.test(code);
|
||||
|
||||
if (status >= 200 && status < 300) return { valid: true };
|
||||
if (status === 429) return { valid: true };
|
||||
if (status === 401 || status === 403) return { valid: false, error: 'Invalid API key' };
|
||||
if (status === 401 || status === 403) {
|
||||
return { valid: false, error: 'Invalid API key', authFailure: true };
|
||||
}
|
||||
if (status === 400 && (AUTH_ERROR_PATTERN.test(msg) || hasAuthCode)) {
|
||||
const error = hasAuthCode && msg === `API error: ${status}`
|
||||
? `Invalid API key (${code})`
|
||||
: msg || 'Invalid API key';
|
||||
return { valid: false, error, authFailure: true };
|
||||
}
|
||||
|
||||
const obj = data as { error?: { message?: string }; message?: string } | null;
|
||||
const msg = obj?.error?.message || obj?.message || `API error: ${status}`;
|
||||
return { valid: false, error: msg };
|
||||
}
|
||||
|
||||
function shouldFallbackFromModelsProbe(result: ClassifiedValidationResult): boolean {
|
||||
if (result.valid || result.status === undefined) return false;
|
||||
if (result.status === 401 || result.status === 403) return false;
|
||||
if (result.authFailure) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function classifyProbeResponse(
|
||||
status: number,
|
||||
data: unknown,
|
||||
): ClassifiedValidationResult {
|
||||
const classified = classifyAuthResponse(status, data);
|
||||
|
||||
if (status >= 200 && status < 300) {
|
||||
return { valid: true, status };
|
||||
}
|
||||
if (status === 429) {
|
||||
return { valid: true, status };
|
||||
}
|
||||
if (status === 400 && !classified.authFailure) {
|
||||
return { valid: true, status };
|
||||
}
|
||||
return { ...classified, status };
|
||||
}
|
||||
|
||||
async function validateOpenAiCompatibleKey(
|
||||
providerType: string,
|
||||
apiKey: string,
|
||||
@@ -162,9 +206,9 @@ async function validateOpenAiCompatibleKey(
|
||||
const { modelsUrl, probeUrl } = resolveOpenAiProbeUrls(trimmedBaseUrl, apiProtocol);
|
||||
const modelsResult = await performProviderValidationRequest(providerType, modelsUrl, headers);
|
||||
|
||||
if (modelsResult.status === 404) {
|
||||
if (shouldFallbackFromModelsProbe(modelsResult)) {
|
||||
console.log(
|
||||
`[clawx-validate] ${providerType} /models returned 404, falling back to ${apiProtocol} probe`,
|
||||
`[clawx-validate] ${providerType} /models returned ${modelsResult.status}, falling back to ${apiProtocol} probe`,
|
||||
);
|
||||
if (apiProtocol === 'openai-responses') {
|
||||
return await performResponsesProbe(providerType, probeUrl, headers);
|
||||
@@ -192,18 +236,7 @@ async function performResponsesProbe(
|
||||
});
|
||||
logValidationStatus(providerLabel, response.status);
|
||||
const data = await response.json().catch(() => ({}));
|
||||
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
return { valid: false, error: 'Invalid API key' };
|
||||
}
|
||||
if (
|
||||
(response.status >= 200 && response.status < 300) ||
|
||||
response.status === 400 ||
|
||||
response.status === 429
|
||||
) {
|
||||
return { valid: true };
|
||||
}
|
||||
return classifyAuthResponse(response.status, data);
|
||||
return classifyProbeResponse(response.status, data);
|
||||
} catch (error) {
|
||||
return {
|
||||
valid: false,
|
||||
@@ -230,18 +263,7 @@ async function performChatCompletionsProbe(
|
||||
});
|
||||
logValidationStatus(providerLabel, response.status);
|
||||
const data = await response.json().catch(() => ({}));
|
||||
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
return { valid: false, error: 'Invalid API key' };
|
||||
}
|
||||
if (
|
||||
(response.status >= 200 && response.status < 300) ||
|
||||
response.status === 400 ||
|
||||
response.status === 429
|
||||
) {
|
||||
return { valid: true };
|
||||
}
|
||||
return classifyAuthResponse(response.status, data);
|
||||
return classifyProbeResponse(response.status, data);
|
||||
} catch (error) {
|
||||
return {
|
||||
valid: false,
|
||||
@@ -268,18 +290,7 @@ async function performAnthropicMessagesProbe(
|
||||
});
|
||||
logValidationStatus(providerLabel, response.status);
|
||||
const data = await response.json().catch(() => ({}));
|
||||
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
return { valid: false, error: 'Invalid API key' };
|
||||
}
|
||||
if (
|
||||
(response.status >= 200 && response.status < 300) ||
|
||||
response.status === 400 ||
|
||||
response.status === 429
|
||||
) {
|
||||
return { valid: true };
|
||||
}
|
||||
return classifyAuthResponse(response.status, data);
|
||||
return classifyProbeResponse(response.status, data);
|
||||
} catch (error) {
|
||||
return {
|
||||
valid: false,
|
||||
|
||||
Reference in New Issue
Block a user