diff --git a/src/cliproxy/auth/provider-refreshers/index.ts b/src/cliproxy/auth/provider-refreshers/index.ts index 89df2ab2..3c642340 100644 --- a/src/cliproxy/auth/provider-refreshers/index.ts +++ b/src/cliproxy/auth/provider-refreshers/index.ts @@ -2,7 +2,12 @@ * Provider Token Refreshers * * Exports refresh functions for each OAuth provider. - * Currently only Gemini is implemented; others return placeholder errors. + * + * Refresh responsibility: + * - CCS-managed: gemini (CCS refreshes tokens directly via Google OAuth) + * - CLIProxy-delegated: codex, agy, kiro, ghcp, qwen, iflow + * (CLIProxyAPIPlus handles refresh automatically in background) + * - Not implemented: claude */ import { CLIProxyProvider } from '../../types'; @@ -13,6 +18,29 @@ export interface ProviderRefreshResult { success: boolean; error?: string; expiresAt?: number; + /** True if refresh is delegated to CLIProxy (not handled by CCS) */ + 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', +]; + +/** + * Check if a provider's token refresh is delegated to CLIProxy + */ +export function isRefreshDelegated(provider: CLIProxyProvider): boolean { + return CLIPROXY_DELEGATED_REFRESH.includes(provider); } /** @@ -35,6 +63,10 @@ export async function refreshToken( case 'iflow': case 'kiro': case 'ghcp': + // 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, diff --git a/src/cliproxy/auth/token-expiry-checker.ts b/src/cliproxy/auth/token-expiry-checker.ts index 066f1d9b..a8f13177 100644 --- a/src/cliproxy/auth/token-expiry-checker.ts +++ b/src/cliproxy/auth/token-expiry-checker.ts @@ -10,6 +10,7 @@ import * as path from 'path'; import { CLIProxyProvider } from '../types'; import { CLIPROXY_PROFILES } from '../../auth/profile-detector'; import { getProviderAccounts, getAccountTokenPath } from '../account-manager'; +import { isRefreshDelegated } from './provider-refreshers'; /** Preemptive refresh time: refresh tokens 45 minutes before expiry */ export const PREEMPTIVE_REFRESH_MINUTES = 45; @@ -74,9 +75,13 @@ export function getTokenExpiryInfo( const content = fs.readFileSync(tokenPath, 'utf-8'); const data: TokenData = JSON.parse(content); - // Validate refresh_token exists (required for refresh) - if (!data.refresh_token || typeof data.refresh_token !== 'string') { - return null; + // Validate refresh_token exists (required for CCS-managed refresh). + // CLIProxy-delegated providers may not expose refresh_token in token files — + // CLIProxyAPIPlus manages refresh internally, so skip this check for them. + if (!isRefreshDelegated(provider)) { + if (!data.refresh_token || typeof data.refresh_token !== 'string') { + return null; + } } // Calculate expiry time with validation diff --git a/src/cliproxy/auth/token-manager.ts b/src/cliproxy/auth/token-manager.ts index 41ef2092..bec57b39 100644 --- a/src/cliproxy/auth/token-manager.ts +++ b/src/cliproxy/auth/token-manager.ts @@ -317,6 +317,13 @@ export function displayAuthStatus(): void { * Ensure OAuth token is valid for provider, refreshing if expired or expiring soon. * This prevents UND_ERR_SOCKET errors caused by expired tokens during API calls. * + * Refresh responsibility: + * - gemini: CCS refreshes directly via Google OAuth + * - codex, agy, kiro, ghcp, qwen, iflow: CLIProxyAPIPlus handles refresh + * automatically in background (e.g. kiro refreshes every 1 min). + * CCS only checks if token file exists (authentication state). + * - claude: not yet implemented + * * @param provider The CLIProxy provider * @param verbose Log progress if true * @returns Object with valid status and whether refresh occurred @@ -325,14 +332,12 @@ export async function ensureTokenValid( provider: CLIProxyProvider, verbose = false ): Promise<{ valid: boolean; refreshed: boolean; error?: string }> { - // Currently only Gemini uses oauth_creds.json with expiry_date - // Other providers (agy, codex) use CLIProxyAPI's internal auth management if (provider === 'gemini') { const { ensureGeminiTokenValid } = await import('./gemini-token-refresh'); return ensureGeminiTokenValid(verbose); } - // For other providers, assume token is valid if authenticated - // CLIProxyAPI handles token refresh internally for antigravity/codex + // For CLIProxy-delegated providers, token refresh is handled by CLIProxyAPIPlus. + // CCS only verifies the token file exists (authentication state). return { valid: isAuthenticated(provider), refreshed: false }; } diff --git a/src/cliproxy/auth/token-refresh-worker.ts b/src/cliproxy/auth/token-refresh-worker.ts index 071e63d9..4e1c5304 100644 --- a/src/cliproxy/auth/token-refresh-worker.ts +++ b/src/cliproxy/auth/token-refresh-worker.ts @@ -7,7 +7,7 @@ import { CLIProxyProvider } from '../types'; import { getAllTokenExpiryInfo, TokenExpiryInfo } from './token-expiry-checker'; -import { refreshToken } from './provider-refreshers'; +import { refreshToken, isRefreshDelegated } from './provider-refreshers'; /** Worker configuration */ export interface TokenRefreshConfig { @@ -182,7 +182,10 @@ export class TokenRefreshWorker { try { const tokens = getAllTokenExpiryInfo(); - const tokensNeedingRefresh = tokens.filter((t) => t.needsRefresh); + // Skip CLIProxy-delegated providers — they handle their own refresh + const tokensNeedingRefresh = tokens.filter( + (t) => t.needsRefresh && !isRefreshDelegated(t.provider) + ); if (tokensNeedingRefresh.length === 0) { this.log('[OK] All tokens valid, no refresh needed');