Add Claude Pro/Max, Mistral, Cerebras and OpenRouter connectors

Closes the Anthropic OAuth gap with a native Claude Pro/Max device sign-in
(reusing the OAuth engine) and adds three OpenAI-compatible API-key
providers. Model lists are sourced from the provider registry. A backfill
migration seeds the new providers into existing organizations idempotently.
This commit is contained in:
ZacharyZhang-NY committed 2026-05-29 20:19:01 -04:00
1 parent 49a9f16221
commit 9ce1281054
9 files changed
+213 -20

No files matched your search

@@ -0,0 +1,70 @@
/**
* Anthropic Claude Pro/Max — authorization-code + PKCE flow.
*
* Ported from the reference implementation in @mariozechner/pi-ai. Uses a
* loopback callback server on the registered redirect URI and a JSON token
* exchange.
*/
import { startOAuthCallbackServer } from "./oauth-callback-server";
import { generatePkce } from "./pkce";
import type { OAuthCredentials, OAuthFlow, OAuthLoginCallbacks } from "./oauth-types";
const CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
const AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
const TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
const CALLBACK_PORT = 53692;
const CALLBACK_PATH = "/callback";
const REDIRECT_URI = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
const SCOPES = "org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload";
function buildAuthorizeUrl(challenge: string, state: string): string {
const params = new URLSearchParams({
code: "true",
client_id: CLIENT_ID,
response_type: "code",
redirect_uri: REDIRECT_URI,
scope: SCOPES,
code_challenge: challenge,
code_challenge_method: "S256",
state,
});
return `${AUTHORIZE_URL}?${params.toString()}`;
}
async function exchangeCode(code: string, state: string, verifier: string): Promise<OAuthCredentials> {
const response = await fetch(TOKEN_URL, {
method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" },
body: JSON.stringify({
grant_type: "authorization_code",
client_id: CLIENT_ID,
code,
state,
redirect_uri: REDIRECT_URI,
code_verifier: verifier,
}),
});
const text = await response.text();
if (!response.ok) throw new Error(`Token exchange failed: ${response.status} ${text}`);
const json = JSON.parse(text) as { access_token?: unknown; refresh_token?: unknown; expires_in?: unknown };
if (typeof json.access_token !== "string" || typeof json.refresh_token !== "string" || typeof json.expires_in !== "number") {
throw new Error("Token response was missing required fields.");
}
return { access: json.access_token, refresh: json.refresh_token, expires: Date.now() + json.expires_in * 1000 - 5 * 60 * 1000 };
}
export const anthropicClaudeFlow: OAuthFlow = {
providerKey: "claude-pro",
async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
const { verifier, challenge } = generatePkce();
const server = await startOAuthCallbackServer({ port: CALLBACK_PORT, path: CALLBACK_PATH, signal: callbacks.signal });
try {
callbacks.onAuth({ url: buildAuthorizeUrl(challenge, verifier), instructions: "Complete the Claude sign-in in your browser." });
const result = await server.waitForCode();
if (result.state !== verifier) throw new Error("OAuth state mismatch.");
return await exchangeCode(result.code, result.state, verifier);
} finally {
server.close();
}
},
};
@@ -1,6 +1,7 @@
import { githubCopilotFlow } from "./github-copilot-flow";
import { openaiCodexFlow } from "./openai-codex-flow";
import { antigravityFlow } from "./google-antigravity-flow";
import { anthropicClaudeFlow } from "./anthropic-claude-flow";
import type { OAuthFlow } from "./oauth-types";
/** Provider keys (from the catalog) mapped to their native OAuth login flow. */
@@ -8,6 +9,7 @@ const OAUTH_FLOWS: Record<string, OAuthFlow> = {
"github-copilot": githubCopilotFlow,
"gpt-account": openaiCodexFlow,
"google-antigravity": antigravityFlow,
"claude-pro": anthropicClaudeFlow,
};
export function hasOAuthFlow(providerKey: string): boolean {
@@ -253,9 +253,28 @@ export const providerAdapters: ProviderAdapterRegistry = {
endpoint: "https://api.groq.com/openai/v1/chat/completions",
healthUrl: "https://api.groq.com/openai/v1/models",
}),
mistral: openAiCompatibleAdapter({
providerKey: "mistral",
label: "Mistral",
endpoint: "https://api.mistral.ai/v1/chat/completions",
healthUrl: "https://api.mistral.ai/v1/models",
}),
cerebras: openAiCompatibleAdapter({
providerKey: "cerebras",
label: "Cerebras",
endpoint: "https://api.cerebras.ai/v1/chat/completions",
healthUrl: "https://api.cerebras.ai/v1/models",
}),
openrouter: openAiCompatibleAdapter({
providerKey: "openrouter",
label: "OpenRouter",
endpoint: "https://openrouter.ai/api/v1/chat/completions",
healthUrl: "https://openrouter.ai/api/v1/models",
}),
"github-copilot": browserTokenAdapter("github-copilot", PROVIDER_AUTH_TYPE.OAUTH_PKCE),
"google-antigravity": browserTokenAdapter("google-antigravity", PROVIDER_AUTH_TYPE.OAUTH_PKCE),
"gpt-account": browserTokenAdapter("gpt-account", PROVIDER_AUTH_TYPE.ACCOUNT_SUBSCRIPTION),
"claude-pro": browserTokenAdapter("claude-pro", PROVIDER_AUTH_TYPE.OAUTH_PKCE),
"azure-openai": {
providerKey: "azure-openai",
authType: PROVIDER_AUTH_TYPE.CLOUD,