fix(oauth): use api.anthropic.com for Claude token exchange to avoid Cloudflare bot block on VPS (#3203)

Integrated into release/v3.8.11
This commit is contained in:
Wilson
2026-06-05 02:47:42 -03:00
committed by GitHub
parent 7b4bda13b1
commit 005ee10a1e
5 changed files with 81 additions and 11 deletions

View File

@@ -74,6 +74,12 @@ _Development cycle in progress — entries are added as work merges into `releas
---
## [3.8.10] — Unreleased
_Development cycle in progress — entries are added as work merges into `release/v3.8.10` and finalized by the release flow._
---
## [3.8.9] — 2026-06-03
### ✨ New Features

View File

@@ -67,8 +67,8 @@ export const OAUTH_ENDPOINTS = {
auth: "https://auth.openai.com/oauth/authorize",
},
anthropic: {
token: "https://console.anthropic.com/v1/oauth/token",
auth: "https://console.anthropic.com/v1/oauth/authorize",
token: "https://api.anthropic.com/v1/oauth/token",
auth: "https://api.anthropic.com/v1/oauth/authorize",
},
qwen: {
token: "https://chat.qwen.ai/api/v1/oauth2/token", // From CLIProxyAPI

View File

@@ -647,7 +647,7 @@ const _REGISTRY_EAGER: Record<string, RegistryEntry> = {
oauth: {
clientIdEnv: "CLAUDE_OAUTH_CLIENT_ID",
clientIdDefault: "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
tokenUrl: "https://console.anthropic.com/v1/oauth/token",
tokenUrl: "https://api.anthropic.com/v1/oauth/token",
},
models: [
{
@@ -1337,7 +1337,12 @@ const _REGISTRY_EAGER: Record<string, RegistryEntry> = {
// #2900: big-pickle's upstream runs DeepSeek thinking mode — declare the
// interleaved reasoning_content contract so follow-up/tool-use turns replay
// it (otherwise DeepSeek returns 400 "reasoning_content ... must be passed back").
{ id: "big-pickle", name: "Big Pickle", supportsReasoning: true, interleavedField: "reasoning_content" },
{
id: "big-pickle",
name: "Big Pickle",
supportsReasoning: true,
interleavedField: "reasoning_content",
},
{ id: "deepseek-v4-flash-free", name: "DeepSeek V4 Flash Free", supportsReasoning: true },
{ id: "minimax-m2.5-free", name: "MiniMax M2.5 Free", contextLength: 204800 },
{ id: "ling-2.6-1t-free", name: "Ling 2.6 Free", contextLength: 262000 },
@@ -1347,7 +1352,13 @@ const _REGISTRY_EAGER: Record<string, RegistryEntry> = {
contextLength: 131000,
},
{ id: "nemotron-3-super-free", name: "Nemotron 3 Super Free", contextLength: 1000000 },
{ id: "qwen3.6-plus-free", name: "Qwen3.6 Plus Free", targetFormat: "claude", supportsVision: false, contextLength: 200000, },
{
id: "qwen3.6-plus-free",
name: "Qwen3.6 Plus Free",
targetFormat: "claude",
supportsVision: false,
contextLength: 200000,
},
],
},
@@ -1409,7 +1420,12 @@ const _REGISTRY_EAGER: Record<string, RegistryEntry> = {
// #2900: big-pickle's upstream runs DeepSeek thinking mode — declare the
// interleaved reasoning_content contract so follow-up/tool-use turns replay
// it (otherwise DeepSeek returns 400 "reasoning_content ... must be passed back").
{ id: "big-pickle", name: "Big Pickle", supportsReasoning: true, interleavedField: "reasoning_content" },
{
id: "big-pickle",
name: "Big Pickle",
supportsReasoning: true,
interleavedField: "reasoning_content",
},
{ id: "gpt-5-nano", name: "GPT 5 Nano", contextLength: 400000 },
{ id: "gpt-5", name: "GPT 5" },
{ id: "gpt-5-codex", name: "GPT 5 Codex" },
@@ -2346,7 +2362,6 @@ const _REGISTRY_EAGER: Record<string, RegistryEntry> = {
models: [{ id: "auto", name: "Auto" }],
},
kluster: {
id: "kluster",
alias: "kluster",
@@ -2380,7 +2395,6 @@ const _REGISTRY_EAGER: Record<string, RegistryEntry> = {
models: [{ id: "liquid-lfm-40b", name: "Liquid LFM 40B" }],
},
monsterapi: {
id: "monsterapi",
alias: "monster",
@@ -2418,7 +2432,6 @@ const _REGISTRY_EAGER: Record<string, RegistryEntry> = {
],
},
chutes: {
id: "chutes",
alias: "chutes",
@@ -3587,7 +3600,6 @@ const _REGISTRY_EAGER: Record<string, RegistryEntry> = {
],
},
hackclub: {
id: "hackclub",
alias: "hc",

View File

@@ -36,7 +36,7 @@ import { buildGitLabOAuthEndpoints, GITLAB_DUO_DEFAULT_BASE_URL } from "../gitla
export const CLAUDE_CONFIG = {
clientId: process.env.CLAUDE_OAUTH_CLIENT_ID || "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
authorizeUrl: "https://claude.ai/oauth/authorize",
tokenUrl: "https://console.anthropic.com/v1/oauth/token",
tokenUrl: "https://api.anthropic.com/v1/oauth/token",
redirectUri:
process.env.CLAUDE_CODE_REDIRECT_URI || "https://platform.claude.com/oauth/code/callback",
scopes: [

View File

@@ -0,0 +1,52 @@
import { test } from "node:test";
import assert from "node:assert";
import { CLAUDE_CONFIG } from "@/lib/oauth/constants/oauth.ts";
import { OAUTH_ENDPOINTS } from "@omniroute/open-sse/config/constants.ts";
import { REGISTRY } from "@omniroute/open-sse/config/providerRegistry.ts";
test("CLAUDE_CONFIG.tokenUrl uses api.anthropic.com (not console.anthropic.com)", () => {
assert.ok(
CLAUDE_CONFIG.tokenUrl.includes("api.anthropic.com"),
`Expected api.anthropic.com but got ${CLAUDE_CONFIG.tokenUrl}`
);
assert.equal(CLAUDE_CONFIG.tokenUrl, "https://api.anthropic.com/v1/oauth/token");
});
test("OAUTH_ENDPOINTS.anthropic uses api.anthropic.com for token and auth", () => {
assert.ok(
OAUTH_ENDPOINTS.anthropic.token.includes("api.anthropic.com"),
`Expected api.anthropic.com but got ${OAUTH_ENDPOINTS.anthropic.token}`
);
assert.ok(
OAUTH_ENDPOINTS.anthropic.auth.includes("api.anthropic.com"),
`Expected api.anthropic.com but got ${OAUTH_ENDPOINTS.anthropic.auth}`
);
assert.equal(OAUTH_ENDPOINTS.anthropic.token, "https://api.anthropic.com/v1/oauth/token");
assert.equal(OAUTH_ENDPOINTS.anthropic.auth, "https://api.anthropic.com/v1/oauth/authorize");
});
test("Provider registry claude oauth.tokenUrl uses api.anthropic.com", () => {
const claude = REGISTRY.claude;
assert.ok(claude, "claude provider should exist in registry");
assert.ok(
claude.oauth?.tokenUrl?.includes("api.anthropic.com"),
`Expected api.anthropic.com but got ${claude.oauth?.tokenUrl}`
);
assert.equal(claude.oauth.tokenUrl, "https://api.anthropic.com/v1/oauth/token");
});
test("No console.anthropic.com remains in OAuth constants or registry", () => {
const allUrls = [
CLAUDE_CONFIG.tokenUrl,
OAUTH_ENDPOINTS.anthropic.token,
OAUTH_ENDPOINTS.anthropic.auth,
REGISTRY.claude?.oauth?.tokenUrl,
].filter(Boolean);
for (const url of allUrls) {
assert.equal(
url.includes("console.anthropic.com"),
false,
`Found console.anthropic.com in ${url}`
);
}
});