Files
9router/src/app/api/providers/[id]/models/route.js
T
ramhaidar da5bdef4cb feat: Add Anthropic Compatible provider support
- Added support for 'anthropic-compatible' provider nodes in backend.
- Implemented isAnthropicCompatible logic in open-sse for /messages URL construction and headers.
- Added UI for creating and managing Anthropic Compatible providers in the dashboard.
- Updated validation logic for Anthropic-compatible endpoints.
- Sanitize base URL input (strip trailing /messages) to prevent 404s and improve UX.
- Improve validation: use GET /models (2xx success), and support x-api-key / Authorization Bearer hybrid proxies.
- Enable model import via /models for Anthropic Compatible providers.
- Ensure Authorization is omitted when x-api-key is present to avoid strict proxy conflicts.
- Resolve Anthropic-compatible credentials by prefix during model resolution (e.g., acx/model).
- Update default executor to match provider header/url behavior for Anthropic-compatible providers.
2026-02-03 15:11:41 +07:00

223 lines
6.7 KiB
JavaScript

import { NextResponse } from "next/server";
import { getProviderConnectionById } from "@/models";
import { isOpenAICompatibleProvider, isAnthropicCompatibleProvider } from "@/shared/constants/providers";
// Provider models endpoints configuration
const PROVIDER_MODELS_CONFIG = {
claude: {
url: "https://api.anthropic.com/v1/models",
method: "GET",
headers: {
"Anthropic-Version": "2023-06-01",
"Content-Type": "application/json"
},
authHeader: "x-api-key",
parseResponse: (data) => data.data || []
},
gemini: {
url: "https://generativelanguage.googleapis.com/v1beta/models",
method: "GET",
headers: { "Content-Type": "application/json" },
authQuery: "key", // Use query param for API key
parseResponse: (data) => data.models || []
},
"gemini-cli": {
url: "https://generativelanguage.googleapis.com/v1beta/models",
method: "GET",
headers: { "Content-Type": "application/json" },
authHeader: "Authorization",
authPrefix: "Bearer ",
parseResponse: (data) => data.models || []
},
qwen: {
url: "https://portal.qwen.ai/v1/models",
method: "GET",
headers: { "Content-Type": "application/json" },
authHeader: "Authorization",
authPrefix: "Bearer ",
parseResponse: (data) => data.data || []
},
antigravity: {
url: "https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:models",
method: "POST",
headers: { "Content-Type": "application/json" },
authHeader: "Authorization",
authPrefix: "Bearer ",
body: {},
parseResponse: (data) => data.models || []
},
openai: {
url: "https://api.openai.com/v1/models",
method: "GET",
headers: { "Content-Type": "application/json" },
authHeader: "Authorization",
authPrefix: "Bearer ",
parseResponse: (data) => data.data || []
},
openrouter: {
url: "https://openrouter.ai/api/v1/models",
method: "GET",
headers: { "Content-Type": "application/json" },
authHeader: "Authorization",
authPrefix: "Bearer ",
parseResponse: (data) => data.data || []
},
anthropic: {
url: "https://api.anthropic.com/v1/models",
method: "GET",
headers: {
"Anthropic-Version": "2023-06-01",
"Content-Type": "application/json"
},
authHeader: "x-api-key",
parseResponse: (data) => data.data || []
}
};
/**
* GET /api/providers/[id]/models - Get models list from provider
*/
export async function GET(request, { params }) {
try {
const { id } = await params;
const connection = await getProviderConnectionById(id);
if (!connection) {
return NextResponse.json({ error: "Connection not found" }, { status: 404 });
}
if (isOpenAICompatibleProvider(connection.provider)) {
const baseUrl = connection.providerSpecificData?.baseUrl;
if (!baseUrl) {
return NextResponse.json({ error: "No base URL configured for OpenAI compatible provider" }, { status: 400 });
}
const url = `${baseUrl.replace(/\/$/, "")}/models`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${connection.apiKey}`,
},
});
if (!response.ok) {
const errorText = await response.text();
console.log(`Error fetching models from ${connection.provider}:`, errorText);
return NextResponse.json(
{ error: `Failed to fetch models: ${response.status}` },
{ status: response.status }
);
}
const data = await response.json();
const models = data.data || data.models || [];
return NextResponse.json({
provider: connection.provider,
connectionId: connection.id,
models
});
}
if (isAnthropicCompatibleProvider(connection.provider)) {
let baseUrl = connection.providerSpecificData?.baseUrl;
if (!baseUrl) {
return NextResponse.json({ error: "No base URL configured for Anthropic compatible provider" }, { status: 400 });
}
baseUrl = baseUrl.replace(/\/$/, "");
if (baseUrl.endsWith("/messages")) {
baseUrl = baseUrl.slice(0, -9);
}
const url = `${baseUrl}/models`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
"x-api-key": connection.apiKey,
"anthropic-version": "2023-06-01",
"Authorization": `Bearer ${connection.apiKey}`
},
});
if (!response.ok) {
const errorText = await response.text();
console.log(`Error fetching models from ${connection.provider}:`, errorText);
return NextResponse.json(
{ error: `Failed to fetch models: ${response.status}` },
{ status: response.status }
);
}
const data = await response.json();
const models = data.data || data.models || [];
return NextResponse.json({
provider: connection.provider,
connectionId: connection.id,
models
});
}
const config = PROVIDER_MODELS_CONFIG[connection.provider];
if (!config) {
return NextResponse.json(
{ error: `Provider ${connection.provider} does not support models listing` },
{ status: 400 }
);
}
// Get auth token
const token = connection.accessToken || connection.apiKey;
if (!token) {
return NextResponse.json({ error: "No valid token found" }, { status: 401 });
}
// Build request URL
let url = config.url;
if (config.authQuery) {
url += `?${config.authQuery}=${token}`;
}
// Build headers
const headers = { ...config.headers };
if (config.authHeader && !config.authQuery) {
headers[config.authHeader] = (config.authPrefix || "") + token;
}
// Make request
const fetchOptions = {
method: config.method,
headers
};
if (config.body && config.method === "POST") {
fetchOptions.body = JSON.stringify(config.body);
}
const response = await fetch(url, fetchOptions);
if (!response.ok) {
const errorText = await response.text();
console.log(`Error fetching models from ${connection.provider}:`, errorText);
return NextResponse.json(
{ error: `Failed to fetch models: ${response.status}` },
{ status: response.status }
);
}
const data = await response.json();
const models = config.parseResponse(data);
return NextResponse.json({
provider: connection.provider,
connectionId: connection.id,
models
});
} catch (error) {
console.log("Error fetching provider models:", error);
return NextResponse.json({ error: "Failed to fetch models" }, { status: 500 });
}
}