mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 04:18:05 +00:00
feat(cliproxy): delegate kiro/ghcp token refresh to CLIProxyAPIPlus (#488)
CLIProxyAPIPlus already handles background token refresh for kiro (every 1 min via background_refresh.go) and other providers. CCS was incorrectly returning "not implemented" errors for these providers, causing noisy worker logs and preventing proper token expiry tracking. Changes: - Classify providers into CCS-managed (gemini), CLIProxy-delegated (codex, agy, kiro, ghcp, qwen, iflow), and not-implemented (claude) - Delegated providers return success with delegated flag instead of error - Skip refresh_token requirement for delegated providers in expiry checker - Filter delegated providers from background worker refresh loop Closes #487
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user