mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-12 02:02:13 +03:00
fix(validation): add chat completions fallback for OpenAI/Anthropic compatible providers
Previously, validateOpenAICompatibleProvider and validateAnthropicCompatibleProvider only tried GET /models. Many compatible providers don't expose this endpoint, causing the Check button to show 'Invalid' even with valid credentials. Now both validators follow a 2-step approach: 1. Try GET /models (fast path) 2. If that fails, try a minimal chat/messages request as fallback Any non-auth 4xx response (400, 422) is treated as valid since it means the API key was accepted but the test model wasn't recognized.
This commit is contained in:
@@ -255,20 +255,62 @@ async function validateOpenAICompatibleProvider({ apiKey, providerSpecificData =
|
||||
return { valid: false, error: "No base URL configured for OpenAI compatible provider" };
|
||||
}
|
||||
|
||||
const response = await fetch(`${baseUrl}/models`, {
|
||||
method: "GET",
|
||||
headers: buildBearerHeaders(apiKey),
|
||||
});
|
||||
// Step 1: Try GET /models
|
||||
try {
|
||||
const modelsRes = await fetch(`${baseUrl}/models`, {
|
||||
method: "GET",
|
||||
headers: buildBearerHeaders(apiKey),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
return { valid: true, error: null };
|
||||
if (modelsRes.ok) {
|
||||
return { valid: true, error: null };
|
||||
}
|
||||
|
||||
if (modelsRes.status === 401 || modelsRes.status === 403) {
|
||||
return { valid: false, error: "Invalid API key" };
|
||||
}
|
||||
} catch {
|
||||
// /models fetch failed (network error, etc.) — fall through to chat test
|
||||
}
|
||||
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
return { valid: false, error: "Invalid API key" };
|
||||
// Step 2: Fallback — try a minimal chat completion request
|
||||
// Many providers don't expose /models but accept chat completions fine
|
||||
const apiType = providerSpecificData.apiType || "chat";
|
||||
const chatSuffix = apiType === "responses" ? "/responses" : "/chat/completions";
|
||||
const chatUrl = `${baseUrl}${chatSuffix}`;
|
||||
|
||||
try {
|
||||
const chatRes = await fetch(chatUrl, {
|
||||
method: "POST",
|
||||
headers: buildBearerHeaders(apiKey),
|
||||
body: JSON.stringify({
|
||||
model: "gpt-4o-mini",
|
||||
messages: [{ role: "user", content: "test" }],
|
||||
max_tokens: 1,
|
||||
}),
|
||||
});
|
||||
|
||||
if (chatRes.ok) {
|
||||
return { valid: true, error: null };
|
||||
}
|
||||
|
||||
if (chatRes.status === 401 || chatRes.status === 403) {
|
||||
return { valid: false, error: "Invalid API key" };
|
||||
}
|
||||
|
||||
// 4xx other than auth (e.g. 400 bad model, 422) usually means auth passed
|
||||
if (chatRes.status >= 400 && chatRes.status < 500) {
|
||||
return { valid: true, error: null };
|
||||
}
|
||||
|
||||
if (chatRes.status >= 500) {
|
||||
return { valid: false, error: `Provider unavailable (${chatRes.status})` };
|
||||
}
|
||||
} catch (error: any) {
|
||||
return { valid: false, error: error.message || "Connection failed" };
|
||||
}
|
||||
|
||||
return { valid: false, error: `Validation failed: ${response.status}` };
|
||||
return { valid: false, error: "Validation failed" };
|
||||
}
|
||||
|
||||
async function validateAnthropicCompatibleProvider({ apiKey, providerSpecificData = {} }: any) {
|
||||
@@ -281,25 +323,52 @@ async function validateAnthropicCompatibleProvider({ apiKey, providerSpecificDat
|
||||
baseUrl = baseUrl.slice(0, -9);
|
||||
}
|
||||
|
||||
const response = await fetch(`${baseUrl}/models`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-api-key": apiKey,
|
||||
"anthropic-version": "2023-06-01",
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
},
|
||||
});
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"x-api-key": apiKey,
|
||||
"anthropic-version": "2023-06-01",
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
};
|
||||
|
||||
if (response.ok) {
|
||||
// Step 1: Try GET /models
|
||||
try {
|
||||
const modelsRes = await fetch(`${baseUrl}/models`, {
|
||||
method: "GET",
|
||||
headers,
|
||||
});
|
||||
|
||||
if (modelsRes.ok) {
|
||||
return { valid: true, error: null };
|
||||
}
|
||||
|
||||
if (modelsRes.status === 401 || modelsRes.status === 403) {
|
||||
return { valid: false, error: "Invalid API key" };
|
||||
}
|
||||
} catch {
|
||||
// /models fetch failed — fall through to messages test
|
||||
}
|
||||
|
||||
// Step 2: Fallback — try a minimal messages request
|
||||
try {
|
||||
const messagesRes = await fetch(`${baseUrl}/messages`, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
model: "claude-3-5-sonnet-20241022",
|
||||
max_tokens: 1,
|
||||
messages: [{ role: "user", content: "test" }],
|
||||
}),
|
||||
});
|
||||
|
||||
if (messagesRes.status === 401 || messagesRes.status === 403) {
|
||||
return { valid: false, error: "Invalid API key" };
|
||||
}
|
||||
|
||||
// Any other response (200, 400, 422, etc.) means auth passed
|
||||
return { valid: true, error: null };
|
||||
} catch (error: any) {
|
||||
return { valid: false, error: error.message || "Connection failed" };
|
||||
}
|
||||
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
return { valid: false, error: "Invalid API key" };
|
||||
}
|
||||
|
||||
return { valid: false, error: `Validation failed: ${response.status}` };
|
||||
}
|
||||
|
||||
export async function validateProviderApiKey({ provider, apiKey, providerSpecificData = {} }: any) {
|
||||
|
||||
Reference in New Issue
Block a user