From cf45906d6ef5d1d01be27c591937ad8834aab0ab Mon Sep 17 00:00:00 2001 From: mahdiwafy Date: Mon, 27 Jul 2026 07:38:42 +0700 Subject: [PATCH 1/3] feat(claude): add Claude 5 family models (Fable 5, Opus 5, Sonnet 5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Claude Code (cc) provider's model list stopped at Opus 4.8 and was missing the entire Claude 5 generation — even though the CLI defaults in cliTools.js already point at cc/claude-fable-5 and cc/claude-sonnet-5. Add the three generally-available Claude 5 models (per Anthropic's model docs), keeping 4.x as legacy entries: - claude-fable-5 ($10/$50, 1M ctx, adaptive thinking always-on) - claude-opus-5 ($5/$25, 1M ctx, adaptive thinking) - claude-sonnet-5 ($3/$15, 1M ctx, adaptive thinking) Also add exact capabilities entries for claude-opus-5 / claude-fable-5 (so the generic *claude* pattern doesn't downgrade them to claude-budget thinking) and canonical pricing for claude-opus-5 / claude-sonnet-5. claude-mythos-5 is intentionally omitted — it is invitation-only (Project Glasswing) and not generally accessible. Co-Authored-By: Claude Fable 5 --- open-sse/providers/capabilities.js | 4 ++++ open-sse/providers/pricing.js | 2 ++ open-sse/providers/registry/claude.js | 3 +++ 3 files changed, 9 insertions(+) diff --git a/open-sse/providers/capabilities.js b/open-sse/providers/capabilities.js index 2052b8e536..2cce12083c 100644 --- a/open-sse/providers/capabilities.js +++ b/open-sse/providers/capabilities.js @@ -82,6 +82,10 @@ export const MODEL_CAPABILITIES = { "claude-opus-4-8-thinking": { vision: true, reasoning: true, search: true, thinkingFormat: "claude-adaptive", contextWindow: 1000000, maxOutput: 128000 }, "claude-sonnet-4.6": { vision: true, reasoning: true, search: true, thinkingFormat: "claude-adaptive", contextWindow: 1000000, maxOutput: 128000 }, "claude-sonnet-4-6": { vision: true, reasoning: true, search: true, thinkingFormat: "claude-adaptive", contextWindow: 1000000, maxOutput: 128000 }, + // Claude 5 family (Opus 5, Sonnet 5, Fable 5) — 1M context, adaptive thinking. + // Fable 5's adaptive thinking is always-on (no thinking.type:"enabled" support). + "claude-opus-5": { vision: true, reasoning: true, search: true, thinkingFormat: "claude-adaptive", contextWindow: 1000000, maxOutput: 128000 }, + "claude-fable-5": { vision: true, reasoning: true, search: true, thinkingFormat: "claude-adaptive", thinkingCanDisable: false, contextWindow: 1000000, maxOutput: 128000 }, "claude-sonnet-5": { vision: true, reasoning: true, search: true, thinkingFormat: "claude-adaptive", contextWindow: 1000000, maxOutput: 128000 }, "claude-sonnet-5-thinking": { vision: true, reasoning: true, search: true, thinkingFormat: "claude-adaptive", contextWindow: 1000000, maxOutput: 128000 }, "claude-sonnet-5-agentic": { vision: true, reasoning: true, search: true, thinkingFormat: "claude-adaptive", contextWindow: 1000000, maxOutput: 128000 }, diff --git a/open-sse/providers/pricing.js b/open-sse/providers/pricing.js index c2831fdb36..6b9ab04cb6 100644 --- a/open-sse/providers/pricing.js +++ b/open-sse/providers/pricing.js @@ -11,6 +11,8 @@ */ export const MODEL_PRICING = { // === Anthropic / Claude === + "claude-opus-5": { input: 5.00, output: 25.00, cached: 0.50, reasoning: 25.00, cache_creation: 6.25 }, + "claude-sonnet-5": { input: 3.00, output: 15.00, cached: 0.30, reasoning: 15.00, cache_creation: 3.75 }, "claude-opus-4-6": { input: 5.00, output: 25.00, cached: 0.50, reasoning: 25.00, cache_creation: 6.25 }, "claude-opus-4-5-20251101": { input: 5.00, output: 25.00, cached: 0.50, reasoning: 25.00, cache_creation: 6.25 }, "claude-sonnet-4-6": { input: 3.00, output: 15.00, cached: 0.30, reasoning: 15.00, cache_creation: 3.75 }, diff --git a/open-sse/providers/registry/claude.js b/open-sse/providers/registry/claude.js index 9d483d8f3c..7311057021 100644 --- a/open-sse/providers/registry/claude.js +++ b/open-sse/providers/registry/claude.js @@ -60,6 +60,9 @@ export default { }, }, models: [ + { id: "claude-fable-5", name: "Claude Fable 5" }, + { id: "claude-opus-5", name: "Claude Opus 5" }, + { id: "claude-sonnet-5", name: "Claude Sonnet 5" }, { id: "claude-opus-4-8", name: "Claude Opus 4.8" }, { id: "claude-opus-4-7", name: "Claude Opus 4.7" }, { id: "claude-opus-4-6", name: "Claude Opus 4.6" }, From fa89a84af064f2a72216bd96fd3cf9fd5d4dabcb Mon Sep 17 00:00:00 2001 From: mahdiwafy Date: Mon, 27 Jul 2026 08:13:59 +0700 Subject: [PATCH 2/3] fix(claude): request Claude Code OAuth scopes for usage Match the official Claude Code OAuth client scope set by requesting user:sessions:claude_code and user:mcp_servers in addition to the existing profile/inference scopes. Tokens minted without these scopes can connect for basic OAuth but fail Claude Code session/usage endpoints or org policy checks. Also send the same Claude CLI fingerprint headers to the OAuth usage endpoint that we already use for Claude Code inference. When Anthropic rejects OAuth for an organization, surface an explicit org-policy message instead of falling back to the misleading legacy admin-permissions text. Note that oauth_not_allowed_for_organization is still an Anthropic org policy decision; users must reconnect after this change to mint a fresh scoped token, and accounts in orgs that disable OAuth still need an org admin or a different Claude account. Co-Authored-By: Claude Fable 5 --- open-sse/providers/registry/claude.js | 5 +++++ open-sse/services/usage/claude.js | 14 +++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/open-sse/providers/registry/claude.js b/open-sse/providers/registry/claude.js index 7311057021..9ad1a6c3eb 100644 --- a/open-sse/providers/registry/claude.js +++ b/open-sse/providers/registry/claude.js @@ -79,6 +79,11 @@ export default { "org:create_api_key", "user:profile", "user:inference", + // Match the official Claude Code OAuth client scope set. Tokens minted + // without these scopes can connect for basic inference but fail Claude + // Code session/usage endpoints and some org policy checks. + "user:sessions:claude_code", + "user:mcp_servers", ], codeChallengeMethod: "S256", refreshLeadMs: 14400000, diff --git a/open-sse/services/usage/claude.js b/open-sse/services/usage/claude.js index 85ab8e6f5b..057ce274a0 100644 --- a/open-sse/services/usage/claude.js +++ b/open-sse/services/usage/claude.js @@ -3,7 +3,7 @@ */ import { proxyAwareFetch } from "../../utils/proxyFetch.js"; -import { ANTHROPIC_API_VERSION } from "../../providers/shared.js"; +import { ANTHROPIC_API_VERSION, CLAUDE_CLI_SPOOF_HEADERS } from "../../providers/shared.js"; import { U, parseResetTime } from "./shared.js"; // Claude API config (urls from registry, apiVersion is header logic kept here) @@ -31,9 +31,10 @@ export async function getClaudeUsage(accessToken, proxyOptions = null) { const oauthResponse = await proxyAwareFetch(CLAUDE_CONFIG.oauthUsageUrl, { method: "GET", headers: { + ...CLAUDE_CLI_SPOOF_HEADERS, "Authorization": `Bearer ${accessToken}`, - "anthropic-beta": "oauth-2025-04-20", - "anthropic-version": CLAUDE_CONFIG.apiVersion, + "Accept": "application/json, text/plain, */*", + "Content-Type": "application/json", }, }, proxyOptions); @@ -86,6 +87,13 @@ export async function getClaudeUsage(accessToken, proxyOptions = null) { oauthCooldown.set(accessToken, Date.now() + OAUTH_429_COOLDOWN_MS); } + const oauthErrorText = await oauthResponse.text().catch(() => ""); + if (oauthErrorText.includes("oauth_not_allowed_for_organization")) { + return { + message: "Claude connected, but this Anthropic organization does not allow OAuth authentication. Ask an org admin to enable Claude Code/OAuth access, or reconnect with a different Claude account.", + }; + } + // Fallback: legacy settings + org usage endpoint console.warn(`[Claude Usage] OAuth endpoint returned ${oauthResponse.status}, falling back to legacy`); return await getClaudeUsageLegacy(accessToken, proxyOptions); From fc143acb0bc1a66de3fd934777e70e8fd7e97d8c Mon Sep 17 00:00:00 2001 From: mahdiwafy Date: Mon, 27 Jul 2026 07:10:48 +0700 Subject: [PATCH 3/3] fix(oauth): use Anthropic's whitelisted redirect for Claude Code login MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit claude.ai/oauth/authorize rejects our redirect_uri (https://api.bevansatria.my.id/callback) with "Redirect URI ... is not supported by client" because it isn't on the whitelist for the public Claude Code client_id. Switch to the same redirect Anthropic's own Claude Code CLI uses (https://platform.claude.com/oauth/code/callback), which is whitelisted. Since that redirect lands on Anthropic's domain instead of our own /callback route, the popup can never postMessage/BroadcastChannel the code back to us — force manual-paste mode for the claude provider and accept Anthropic's bare `code#state` callback format (not a full URL). Co-Authored-By: Claude Fable 5 --- src/shared/components/OAuthModal.js | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/shared/components/OAuthModal.js b/src/shared/components/OAuthModal.js index 7e5eaea420..b607081914 100644 --- a/src/shared/components/OAuthModal.js +++ b/src/shared/components/OAuthModal.js @@ -237,6 +237,10 @@ export default function OAuthModal({ isOpen, provider, providerInfo, onSuccess, // Z.ai client (per ZCode source) only accepts custom URL scheme `zcode://zai-auth/callback`. // Browser shows ERR_UNKNOWN_URL_SCHEME; user copies URL from address bar → manual paste. redirectUri = "zcode://zai-auth/callback"; + } else if (provider === "claude") { + // Anthropic's public Claude Code client_id only whitelists this + // redirect (not our own domain) — code must be pasted back manually. + redirectUri = "https://platform.claude.com/oauth/code/callback"; } else { redirectUri = `https://api.bevansatria.my.id/callback`; } @@ -319,8 +323,11 @@ export default function OAuthModal({ isOpen, provider, providerInfo, onSuccess, if (!popupRef.current) { setStep("input"); } - } else if (!isLocalhost || provider === "codex" || provider === "xai" || provider === "zcode") { - // Non-localhost or proxy failed or zcode (custom-scheme callback): manual input mode + } else if (!isLocalhost || provider === "codex" || provider === "xai" || provider === "zcode" || provider === "claude") { + // Non-localhost, proxy failed, zcode (custom-scheme callback), or + // claude (redirect lands on platform.claude.com, not our own + // /callback route, so it can never postMessage/BroadcastChannel back): + // manual input mode. setStep("input"); window.open(data.authUrl, "_blank"); } else { @@ -519,6 +526,15 @@ export default function OAuthModal({ isOpen, provider, providerInfo, onSuccess, return; } + // Claude: Anthropic's platform.claude.com callback page shows the code + // as a bare `code#state` (or just `code`) string, not a full URL. The + // claude exchangeToken splits on `#` itself, so pass it through as-is. + if (provider === "claude" && input && !input.includes("://")) { + const [rawCode, rawState] = input.split("#"); + await exchangeTokens(rawCode, rawState || authData?.state); + return; + } + // URL parsing works for both http(s):// and custom schemes like zcode:// const url = new URL(input); const code = url.searchParams.get("code") || url.searchParams.get("authCode"); @@ -562,7 +578,9 @@ export default function OAuthModal({ isOpen, provider, providerInfo, onSuccess, const modalTitle = isXaiProvider ? "Connect Grok Build OAuth" : `Connect ${providerInfo.name}`; const manualPlaceholder = isXaiProvider ? "http://127.0.0.1:56121/callback?code=... or copied code" - : placeholderUrl; + : provider === "claude" + ? "https://platform.claude.com/oauth/code/callback?code=...&state=..." + : placeholderUrl; return (