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..9ad1a6c3eb 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" }, @@ -76,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); 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 (