From 584cf66a70a99dd9b400cbfaf72b98066c4e633d Mon Sep 17 00:00:00 2001 From: Rehan Choirul Date: Sat, 20 Jun 2026 15:53:03 +0700 Subject: [PATCH] fix(test): use POST /v1/messages to validate anthropic-compatible connections MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GET /models is not part of the Anthropic API spec and many compatible proxies do not implement it, causing the connection test to always fail even with a valid API key. Switch to POST /v1/messages with max_tokens=1 — the same approach used for the built-in anthropic provider — and treat any non-401/403 response as valid, since 400/529 still confirms the key was accepted. Use node.defaultModel / connection.defaultModel when set so the test respects the configured model rather than always falling back to claude-3-haiku-20240307. Co-authored-by: Rehan Choirul Co-authored-by: Cursor --- src/app/api/providers/[id]/test/testUtils.js | 21 +++++++++++++++++--- src/app/api/providers/validate/route.js | 17 ++++++++++++---- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/app/api/providers/[id]/test/testUtils.js b/src/app/api/providers/[id]/test/testUtils.js index 7d57851b..26cb5de1 100644 --- a/src/app/api/providers/[id]/test/testUtils.js +++ b/src/app/api/providers/[id]/test/testUtils.js @@ -355,10 +355,25 @@ async function testApiKeyConnection(connection, effectiveProxy = null) { try { modelsBase = modelsBase.replace(/\/$/, ""); if (modelsBase.endsWith("/messages")) modelsBase = modelsBase.slice(0, -9); - const res = await fetchWithConnectionProxy(`${modelsBase}/models`, { - headers: { "x-api-key": connection.apiKey, "anthropic-version": "2023-06-01", "Authorization": `Bearer ${connection.apiKey}` }, + const messagesUrl = `${modelsBase}/v1/messages`; + const model = connection.defaultModel || "claude-3-haiku-20240307"; + const res = await fetchWithConnectionProxy(messagesUrl, { + method: "POST", + headers: { + "x-api-key": connection.apiKey, + "anthropic-version": "2023-06-01", + "content-type": "application/json", + "Authorization": `Bearer ${connection.apiKey}`, + }, + body: JSON.stringify({ + model, + max_tokens: 1, + messages: [{ role: "user", content: "test" }], + }), }, effectiveProxy); - return { valid: res.ok, error: res.ok ? null : "Invalid API key or base URL" }; + // 400/529 still confirms key accepted; only 401/403 = bad key + const valid = res.status !== 401 && res.status !== 403; + return { valid, error: valid ? null : "Invalid API key or base URL" }; } catch (err) { return { valid: false, error: err.message }; } diff --git a/src/app/api/providers/validate/route.js b/src/app/api/providers/validate/route.js index b1ac70be..85001371 100644 --- a/src/app/api/providers/validate/route.js +++ b/src/app/api/providers/validate/route.js @@ -155,17 +155,26 @@ export async function POST(request) { normalizedBase = normalizedBase.slice(0, -9); // remove /messages } - const modelsUrl = `${normalizedBase}/models`; + const messagesUrl = `${normalizedBase}/v1/messages`; + const model = node.defaultModel || "claude-3-haiku-20240307"; - const res = await fetch(modelsUrl, { + const res = await fetch(messagesUrl, { + method: "POST", headers: { "x-api-key": apiKey, "anthropic-version": "2023-06-01", - "Authorization": `Bearer ${apiKey}` + "content-type": "application/json", + "Authorization": `Bearer ${apiKey}`, }, + body: JSON.stringify({ + model, + max_tokens: 1, + messages: [{ role: "user", content: "test" }], + }), }); - isValid = res.ok; + // 400/529 still confirms key accepted; only 401/403 = bad key + isValid = res.status !== 401 && res.status !== 403; return NextResponse.json({ valid: isValid, error: isValid ? null : "Invalid API key",