Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions open-sse/providers/capabilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
2 changes: 2 additions & 0 deletions open-sse/providers/pricing.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
8 changes: 8 additions & 0 deletions open-sse/providers/registry/claude.js
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand All @@ -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,
Expand Down
14 changes: 11 additions & 3 deletions open-sse/services/usage/claude.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand Down
24 changes: 21 additions & 3 deletions src/shared/components/OAuthModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`;
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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 (
<Modal isOpen={isOpen} title={modalTitle} onClose={handleClose} size="lg">
Expand Down
Loading