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.
This commit is contained in:
ramhaidar committed 2026-02-03 15:11:41 +07:00
1 parent 7881db81ec
commit da5bdef4cb
13 files changed
+614 -154

No files matched your search

+23 -7
View File
@@ -21,7 +21,8 @@ export async function PUT(request, { params }) {
return NextResponse.json({ error: "Prefix is required" }, { status: 400 });
}
if (!apiType || !["chat", "responses"].includes(apiType)) {
// Only validate apiType for OpenAI Compatible nodes
if (node.type === "openai-compatible" && (!apiType || !["chat", "responses"].includes(apiType))) {
return NextResponse.json({ error: "Invalid OpenAI compatible API type" }, { status: 400 });
}
@@ -29,12 +30,27 @@ export async function PUT(request, { params }) {
return NextResponse.json({ error: "Base URL is required" }, { status: 400 });
}
const updated = await updateProviderNode(id, {
let sanitizedBaseUrl = baseUrl.trim();
// Sanitize Base URL for Anthropic Compatible
if (node.type === "anthropic-compatible") {
sanitizedBaseUrl = sanitizedBaseUrl.replace(/\/$/, "");
if (sanitizedBaseUrl.endsWith("/messages")) {
sanitizedBaseUrl = sanitizedBaseUrl.slice(0, -9); // remove /messages
}
}
const updates = {
name: name.trim(),
prefix: prefix.trim(),
apiType,
baseUrl: baseUrl.trim(),
});
baseUrl: sanitizedBaseUrl,
};
if (node.type === "openai-compatible") {
updates.apiType = apiType;
}
const updated = await updateProviderNode(id, updates);
const connections = await getProviderConnections({ provider: id });
await Promise.all(connections.map((connection) => (
@@ -42,8 +58,8 @@ export async function PUT(request, { params }) {
providerSpecificData: {
...(connection.providerSpecificData || {}),
prefix: prefix.trim(),
apiType,
baseUrl: baseUrl.trim(),
apiType: node.type === "openai-compatible" ? apiType : undefined,
baseUrl: sanitizedBaseUrl,
nodeName: updated.name,
}
})