diff --git a/src/cliproxy/auth/auth-types.ts b/src/cliproxy/auth/auth-types.ts index acb10a43..90b086d8 100644 --- a/src/cliproxy/auth/auth-types.ts +++ b/src/cliproxy/auth/auth-types.ts @@ -5,7 +5,27 @@ */ import { CLIProxyProvider } from '../types'; -import { AccountInfo } from '../account-manager'; +import type { AccountInfo } from '../account-manager'; +import { + CLIPROXY_PROVIDER_IDS, + getOAuthCallbackPort, + getCLIProxyCallbackProviderName, + getCLIProxyAuthUrlProviderName, + getProviderAuthFilePrefixes, + getProviderTokenTypeValues, +} from '../provider-capabilities'; + +function buildProviderMap( + valueFor: (provider: CLIProxyProvider) => T +): Record { + return CLIPROXY_PROVIDER_IDS.reduce( + (acc, provider) => { + acc[provider] = valueFor(provider); + return acc; + }, + {} as Record + ); +} /** * Kiro authentication methods supported by CLIProxyAPIPlus. @@ -90,17 +110,17 @@ export function toKiroManagementMethod(method: KiroAuthMethod): 'aws' | 'google' * - GHCP: Device Code Flow (polling-based, NO callback port needed) * - Kimi: Device Code Flow (polling-based, NO callback port needed) */ -export const OAUTH_CALLBACK_PORTS: Partial> = { - gemini: 8085, - codex: 1455, - agy: 51121, - iflow: 11451, - claude: 54545, - // kiro: Device Code Flow - no callback port - // qwen: Device Code Flow - no callback port - // ghcp: Device Code Flow - no callback port - // kimi: Device Code Flow - no callback port -}; +export const OAUTH_CALLBACK_PORTS: Partial> = + CLIPROXY_PROVIDER_IDS.reduce( + (acc, provider) => { + const callbackPort = getOAuthCallbackPort(provider); + if (callbackPort !== null) { + acc[provider] = callbackPort; + } + return acc; + }, + {} as Partial> + ); /** * Auth status for a provider @@ -215,66 +235,34 @@ export const OAUTH_CONFIGS: Record = { * CLIProxyAPI names auth files with provider prefix (e.g., "antigravity-user@email.json") * Note: Gemini tokens may NOT have prefix - CLIProxyAPI uses {email}-{projectID}.json format */ -export const PROVIDER_AUTH_PREFIXES: Record = { - gemini: ['gemini-', 'google-'], - codex: ['codex-', 'openai-'], - agy: ['antigravity-', 'agy-'], - qwen: ['qwen-'], - iflow: ['iflow-'], - kiro: ['kiro-', 'aws-', 'codewhisperer-'], - ghcp: ['github-copilot-', 'copilot-', 'gh-'], - claude: ['claude-', 'anthropic-'], - kimi: ['kimi-'], -}; +export const PROVIDER_AUTH_PREFIXES: Record = buildProviderMap( + (provider) => [...getProviderAuthFilePrefixes(provider)] +); /** * Provider type values inside token JSON files * CLIProxyAPI sets "type" field in token JSON (e.g., {"type": "gemini"}) */ -export const PROVIDER_TYPE_VALUES: Record = { - gemini: ['gemini'], - codex: ['codex'], - agy: ['antigravity'], - qwen: ['qwen'], - iflow: ['iflow'], - kiro: ['kiro', 'codewhisperer'], - ghcp: ['github-copilot', 'copilot'], - claude: ['claude', 'anthropic'], - kimi: ['kimi'], -}; +export const PROVIDER_TYPE_VALUES: Record = buildProviderMap( + (provider) => [...getProviderTokenTypeValues(provider)] +); /** * Maps CCS provider names to CLIProxyAPI callback provider names * Used when submitting OAuth callbacks to CLIProxyAPI management endpoint */ -export const CLIPROXY_CALLBACK_PROVIDER_MAP: Record = { - gemini: 'gemini', - codex: 'codex', - agy: 'antigravity', - kiro: 'kiro', - ghcp: 'copilot', - claude: 'anthropic', - qwen: 'qwen', - iflow: 'iflow', - kimi: 'kimi', -}; +export const CLIPROXY_CALLBACK_PROVIDER_MAP: Record = buildProviderMap( + (provider) => getCLIProxyCallbackProviderName(provider) +); /** * Maps CCS provider names to CLIProxyAPI auth-url endpoint prefixes. * Used for GET /v0/management/${prefix}-auth-url endpoints. * These differ from callback names for some providers (e.g., gemini-cli vs gemini). */ -export const CLIPROXY_AUTH_URL_PROVIDER_MAP: Record = { - gemini: 'gemini-cli', - codex: 'codex', - agy: 'antigravity', - kiro: 'kiro', - ghcp: 'github', - claude: 'anthropic', - qwen: 'qwen', - iflow: 'iflow', - kimi: 'kimi', -}; +export const CLIPROXY_AUTH_URL_PROVIDER_MAP: Record = buildProviderMap( + (provider) => getCLIProxyAuthUrlProviderName(provider) +); /** * Get OAuth config for provider diff --git a/src/cliproxy/auth/provider-refreshers/index.ts b/src/cliproxy/auth/provider-refreshers/index.ts index abcf3ff8..3e7c763c 100644 --- a/src/cliproxy/auth/provider-refreshers/index.ts +++ b/src/cliproxy/auth/provider-refreshers/index.ts @@ -11,6 +11,10 @@ */ import { CLIProxyProvider } from '../../types'; +import { + getTokenRefreshOwnership, + isRefreshDelegatedToCLIProxy, +} from '../../provider-capabilities'; import { refreshGeminiToken } from '../gemini-token-refresh'; /** Token refresh result */ @@ -22,26 +26,11 @@ export interface ProviderRefreshResult { delegated?: boolean; } -/** - * Providers where CLIProxyAPIPlus owns token refresh. - * CLIProxyAPIPlus runs background refresh automatically (e.g. kiro: every 1 min). - * CCS should not attempt to refresh these — just trust CLIProxy. - */ -const CLIPROXY_DELEGATED_REFRESH: CLIProxyProvider[] = [ - 'codex', - 'agy', - 'kiro', - 'ghcp', - 'qwen', - 'iflow', - 'kimi', -]; - /** * Check if a provider's token refresh is delegated to CLIProxy */ export function isRefreshDelegated(provider: CLIProxyProvider): boolean { - return CLIPROXY_DELEGATED_REFRESH.includes(provider); + return isRefreshDelegatedToCLIProxy(provider); } /** @@ -54,33 +43,28 @@ export async function refreshToken( provider: CLIProxyProvider, _accountId: string ): Promise { - switch (provider) { - case 'gemini': - return await refreshGeminiTokenWrapper(); - - case 'codex': - case 'agy': - case 'qwen': - case 'iflow': - case 'kiro': - case 'ghcp': - case 'kimi': - // CLIProxyAPIPlus handles refresh for these providers automatically. - // No action needed from CCS — report success with delegated flag. - return { success: true, delegated: true }; - - case 'claude': - return { - success: false, - error: `Token refresh not yet implemented for ${provider}`, - }; - - default: - return { - success: false, - error: `Unknown provider: ${provider}`, - }; + if (provider === 'gemini') { + return await refreshGeminiTokenWrapper(); } + + if (isRefreshDelegated(provider)) { + // CLIProxyAPIPlus handles refresh for these providers automatically. + // No action needed from CCS — report success with delegated flag. + return { success: true, delegated: true }; + } + + const ownership = getTokenRefreshOwnership(provider); + if (ownership === 'unsupported') { + return { + success: false, + error: `Token refresh not yet implemented for ${provider}`, + }; + } + + return { + success: false, + error: `Unknown provider: ${provider}`, + }; } /** diff --git a/src/cliproxy/provider-capabilities.ts b/src/cliproxy/provider-capabilities.ts index 4ddd4f27..5d575569 100644 --- a/src/cliproxy/provider-capabilities.ts +++ b/src/cliproxy/provider-capabilities.ts @@ -1,11 +1,22 @@ import type { CLIProxyProvider } from './types'; export type OAuthFlowType = 'authorization_code' | 'device_code'; +export type TokenRefreshOwnership = 'ccs' | 'cliproxy' | 'unsupported'; export interface ProviderCapabilities { displayName: string; oauthFlow: OAuthFlowType; callbackPort: number | null; + /** Provider name expected by CLIProxyAPI callback endpoint payload. */ + callbackProviderName: string; + /** Provider name prefix used by CLIProxyAPI auth URL endpoint. */ + authUrlProviderName: string; + /** Who owns token refresh logic for this provider. */ + refreshOwnership: TokenRefreshOwnership; + /** Filename prefixes used to identify auth tokens for this provider. */ + authFilePrefixes: readonly string[]; + /** Token JSON "type" values accepted for this provider. */ + tokenTypeValues: readonly string[]; /** * Alternative provider names used by CLIProxyAPI or stats endpoints. * These aliases normalize external names to canonical CCS provider IDs. @@ -18,54 +29,99 @@ export const PROVIDER_CAPABILITIES: Record