This commit is contained in:
decolua
2026-03-06 09:59:15 +07:00
parent 75f486b7a2
commit b7b4ac5592
7 changed files with 86 additions and 40 deletions

View File

@@ -1,5 +1,37 @@
import { NextResponse } from "next/server";
// Fetch with timeout wrapper
const fetchWithTimeout = (url, options, timeout = 10000) => {
return Promise.race([
fetch(url, options),
new Promise((_, reject) =>
setTimeout(() => reject(new Error("Request timeout")), timeout)
)
]);
};
// Validate URL format
const isValidUrl = (url) => {
try {
new URL(url);
return true;
} catch {
return false;
}
};
// Parse error details for user-friendly messages
const getErrorMessage = (error) => {
if (error.cause?.code === "ECONNREFUSED") return "Connection refused - provider node offline or unreachable";
if (error.cause?.code === "ENOTFOUND") return "DNS lookup failed - invalid domain or network issue";
if (error.cause?.code === "ETIMEDOUT") return "Connection timeout - provider node too slow";
if (error.message.includes("timeout")) return "Request timeout (>10s) - provider node not responding";
if (error.cause?.code === "CERT_HAS_EXPIRED") return "SSL certificate expired";
if (error.cause?.code === "UNABLE_TO_VERIFY_LEAF_SIGNATURE") return "SSL certificate verification failed";
if (error.cause?.code) return `Network error: ${error.cause.code}`;
return "Network connection failed - check URL and network connectivity";
};
// POST /api/provider-nodes/validate - Validate API key against base URL
export async function POST(request) {
try {
@@ -10,6 +42,11 @@ export async function POST(request) {
return NextResponse.json({ error: "Base URL and API key required" }, { status: 400 });
}
// Validate URL format
if (!isValidUrl(baseUrl)) {
return NextResponse.json({ error: "Invalid URL format" }, { status: 400 });
}
// Anthropic Compatible Validation
if (type === "anthropic-compatible") {
// Robustly construct URL: remove trailing slash, and remove trailing /messages if user added it
@@ -21,7 +58,7 @@ export async function POST(request) {
// Use /models endpoint for validation as many compatible providers support it (like OpenAI)
const modelsUrl = `${normalizedBase}/models`;
const res = await fetch(modelsUrl, {
const res = await fetchWithTimeout(modelsUrl, {
method: "GET",
headers: {
"x-api-key": apiKey,
@@ -30,18 +67,27 @@ export async function POST(request) {
}
});
return NextResponse.json({ valid: res.ok, error: res.ok ? null : "Invalid API key" });
return NextResponse.json({ valid: res.ok, error: res.ok ? null : "Invalid API key or unauthorized" });
}
// OpenAI Compatible Validation (Default)
const modelsUrl = `${baseUrl.replace(/\/$/, "")}/models`;
const res = await fetch(modelsUrl, {
const res = await fetchWithTimeout(modelsUrl, {
headers: { "Authorization": `Bearer ${apiKey}` },
});
return NextResponse.json({ valid: res.ok, error: res.ok ? null : "Invalid API key" });
return NextResponse.json({ valid: res.ok, error: res.ok ? null : "Invalid API key or unauthorized" });
} catch (error) {
console.log("Error validating provider node:", error);
return NextResponse.json({ error: "Validation failed" }, { status: 500 });
const errorMessage = getErrorMessage(error);
console.error("Error validating provider node:", {
message: error.message,
cause: error.cause,
code: error.cause?.code,
userMessage: errorMessage
});
return NextResponse.json({
valid: false,
error: errorMessage
}, { status: 500 });
}
}