refactor(cliproxy): DRY provider lists into single source of truth

- unify CLIPROXY_SUPPORTED_PROVIDERS to import from CLIPROXY_PROVIDER_IDS
- replace 4 inline union types with CLIProxyProvider import
- replace hardcoded provider arrays in migration-manager and proxy-routes
- remove duplicate PROVIDER_DISPLAY_NAMES, use getProviderDisplayName()
- sync test now imports from ui/src/lib/provider-config instead of hardcoded array

Adding a new CLIProxy provider no longer requires updating 14+ hardcoded lists.
This commit is contained in:
Tam Nhu Tran committed 2026-02-17 17:02:45 +07:00
1 parent 905b35eea5
commit 94b03c7f75
6 files changed
+20 -50

No files matched your search

+2 -18
View File
@@ -6,6 +6,7 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import { CLIProxyProvider, ProviderConfig } from '../types'; import { CLIProxyProvider, ProviderConfig } from '../types';
import { getProviderDisplayName } from '../provider-capabilities';
import { getModelMappingFromConfig } from '../base-config-loader'; import { getModelMappingFromConfig } from '../base-config-loader';
import { loadOrCreateUnifiedConfig } from '../../config/unified-config-loader'; import { loadOrCreateUnifiedConfig } from '../../config/unified-config-loader';
import { getEffectiveApiKey, getEffectiveManagementSecret } from '../auth-token-manager'; import { getEffectiveApiKey, getEffectiveManagementSecret } from '../auth-token-manager';
@@ -46,34 +47,17 @@ const DEFAULT_ANTIGRAVITY_ALIASES: Array<{ name: string; alias: string; fork?: b
{ name: 'claude-opus-4-6-thinking', alias: 'gemini-claude-opus-4-6-thinking', fork: true }, { name: 'claude-opus-4-6-thinking', alias: 'gemini-claude-opus-4-6-thinking', fork: true },
]; ];
/** Provider display names (static metadata) */
const PROVIDER_DISPLAY_NAMES: Record<CLIProxyProvider, string> = {
gemini: 'Gemini',
codex: 'Codex',
agy: 'Antigravity',
qwen: 'Qwen Code',
iflow: 'iFlow',
kiro: 'Kiro (AWS)',
ghcp: 'GitHub Copilot (OAuth)',
claude: 'Claude (Anthropic)',
};
/** /**
* Get provider configuration * Get provider configuration
* Model mappings are loaded from config/base-{provider}.settings.json * Model mappings are loaded from config/base-{provider}.settings.json
*/ */
export function getProviderConfig(provider: CLIProxyProvider): ProviderConfig { export function getProviderConfig(provider: CLIProxyProvider): ProviderConfig {
const displayName = PROVIDER_DISPLAY_NAMES[provider];
if (!displayName) {
throw new Error(`Unknown provider: ${provider}`);
}
// Load models from base config file // Load models from base config file
const models = getModelMappingFromConfig(provider); const models = getModelMappingFromConfig(provider);
return { return {
name: provider, name: provider,
displayName, displayName: getProviderDisplayName(provider),
models, models,
requiresOAuth: true, // All CLIProxy providers require OAuth requiresOAuth: true, // All CLIProxy providers require OAuth
}; };
+2 -1
View File
@@ -18,6 +18,7 @@ import { getCcsDir } from '../utils/config-manager';
import { expandPath } from '../utils/helpers'; import { expandPath } from '../utils/helpers';
import type { ProfileConfig, AccountConfig, CLIProxyVariantConfig } from './unified-config-types'; import type { ProfileConfig, AccountConfig, CLIProxyVariantConfig } from './unified-config-types';
import { createEmptyUnifiedConfig } from './unified-config-types'; import { createEmptyUnifiedConfig } from './unified-config-types';
import { CLIPROXY_PROVIDER_IDS } from '../cliproxy/provider-capabilities';
import { saveUnifiedConfig, hasUnifiedConfig, loadUnifiedConfig } from './unified-config-loader'; import { saveUnifiedConfig, hasUnifiedConfig, loadUnifiedConfig } from './unified-config-loader';
import { infoBox, warn } from '../utils/ui'; import { infoBox, warn } from '../utils/ui';
@@ -203,7 +204,7 @@ export async function migrate(dryRun = false): Promise<MigrationResult> {
// 6b. Migrate built-in CLIProxy OAuth profile settings (gemini, codex, agy, qwen, iflow) // 6b. Migrate built-in CLIProxy OAuth profile settings (gemini, codex, agy, qwen, iflow)
// Keep settings in *.settings.json files - only record reference in config.yaml // Keep settings in *.settings.json files - only record reference in config.yaml
// This matches Claude's ~/.claude/settings.json pattern for user familiarity // This matches Claude's ~/.claude/settings.json pattern for user familiarity
const builtInProviders = ['gemini', 'codex', 'agy', 'qwen', 'iflow']; const builtInProviders = [...CLIPROXY_PROVIDER_IDS];
for (const provider of builtInProviders) { for (const provider of builtInProviders) {
const settingsFile = `${provider}.settings.json`; const settingsFile = `${provider}.settings.json`;
const settingsPath = path.join(ccsDir, settingsFile); const settingsPath = path.join(ccsDir, settingsFile);
+7 -14
View File
@@ -10,6 +10,8 @@
*/ */
import type { TargetType } from '../targets/target-adapter'; import type { TargetType } from '../targets/target-adapter';
import type { CLIProxyProvider } from '../cliproxy/types';
import { CLIPROXY_PROVIDER_IDS } from '../cliproxy/provider-capabilities';
/** /**
* Unified config version. * Unified config version.
@@ -25,18 +27,9 @@ export const UNIFIED_CONFIG_VERSION = 8;
/** /**
* Supported CLIProxy providers. * Supported CLIProxy providers.
* Includes all OAuth-based providers supported by CLIProxyAPI. * Derived from CLIPROXY_PROVIDER_IDS — single source of truth in provider-capabilities.ts.
*/ */
export const CLIPROXY_SUPPORTED_PROVIDERS = [ export const CLIPROXY_SUPPORTED_PROVIDERS = CLIPROXY_PROVIDER_IDS;
'gemini',
'codex',
'agy',
'qwen',
'iflow',
'kiro',
'ghcp',
'claude',
] as const;
/** /**
* Account configuration (formerly in profiles.json). * Account configuration (formerly in profiles.json).
@@ -80,7 +73,7 @@ export type OAuthAccounts = Record<string, string>;
*/ */
export interface CLIProxyVariantConfig { export interface CLIProxyVariantConfig {
/** Base provider to use */ /** Base provider to use */
provider: 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow' | 'kiro' | 'ghcp' | 'claude'; provider: CLIProxyProvider;
/** Account nickname (references oauth_accounts) */ /** Account nickname (references oauth_accounts) */
account?: string; account?: string;
/** Path to settings file (e.g., "~/.ccs/gemini-custom.settings.json") */ /** Path to settings file (e.g., "~/.ccs/gemini-custom.settings.json") */
@@ -98,14 +91,14 @@ export interface CLIProxyVariantConfig {
*/ */
export interface CompositeTierConfig { export interface CompositeTierConfig {
/** Provider for this tier */ /** Provider for this tier */
provider: 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow' | 'kiro' | 'ghcp' | 'claude'; provider: CLIProxyProvider;
/** Model ID to use for this tier */ /** Model ID to use for this tier */
model: string; model: string;
/** Account nickname (optional, references oauth_accounts) */ /** Account nickname (optional, references oauth_accounts) */
account?: string; account?: string;
/** Fallback provider+model if primary fails */ /** Fallback provider+model if primary fails */
fallback?: { fallback?: {
provider: 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow' | 'kiro' | 'ghcp' | 'claude'; provider: CLIProxyProvider;
model: string; model: string;
account?: string; account?: string;
}; };
+3 -1
View File
@@ -3,6 +3,8 @@
* Source: ~/.ccs/config.json * Source: ~/.ccs/config.json
*/ */
import type { CLIProxyProvider } from '../cliproxy/types';
/** /**
* Profile configuration mapping * Profile configuration mapping
* Maps profile names to settings.json paths * Maps profile names to settings.json paths
@@ -18,7 +20,7 @@ export interface ProfilesConfig {
*/ */
export interface CLIProxyVariantConfig { export interface CLIProxyVariantConfig {
/** CLIProxy provider to use */ /** CLIProxy provider to use */
provider: 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow' | 'kiro' | 'ghcp' | 'claude'; provider: CLIProxyProvider;
/** Path to settings.json with custom model configuration (optional) */ /** Path to settings.json with custom model configuration (optional) */
settings?: string; settings?: string;
/** Account identifier for multi-account support (optional, defaults to 'default') */ /** Account identifier for multi-account support (optional, defaults to 'default') */
+2 -1
View File
@@ -16,6 +16,7 @@ import {
DEFAULT_CLIPROXY_SERVER_CONFIG, DEFAULT_CLIPROXY_SERVER_CONFIG,
CliproxyServerConfig, CliproxyServerConfig,
} from '../../config/unified-config-types'; } from '../../config/unified-config-types';
import { CLIPROXY_PROVIDER_IDS } from '../../cliproxy/provider-capabilities';
const router = Router(); const router = Router();
@@ -113,7 +114,7 @@ router.put('/backend', async (req: Request, res: Response) => {
config.cliproxy = { config.cliproxy = {
backend, backend,
oauth_accounts: {}, oauth_accounts: {},
providers: ['gemini', 'codex', 'agy', 'qwen', 'iflow', 'kiro', 'ghcp'], providers: [...CLIPROXY_PROVIDER_IDS],
variants: {}, variants: {},
}; };
} else { } else {
@@ -7,34 +7,23 @@
import { describe, expect, test } from 'bun:test'; import { describe, expect, test } from 'bun:test';
import { CLIPROXY_PROFILES } from '../../../src/auth/profile-detector'; import { CLIPROXY_PROFILES } from '../../../src/auth/profile-detector';
import { CLIPROXY_PROVIDERS } from '../../../ui/src/lib/provider-config';
// UI providers (must manually sync - this test validates the sync)
const UI_CLIPROXY_PROVIDERS = [
'gemini',
'codex',
'agy',
'qwen',
'iflow',
'kiro',
'ghcp',
'claude',
] as const;
describe('Provider Sync', () => { describe('Provider Sync', () => {
test('backend CLIPROXY_PROFILES matches UI CLIPROXY_PROVIDERS', () => { test('backend CLIPROXY_PROFILES matches UI CLIPROXY_PROVIDERS', () => {
const backend = [...CLIPROXY_PROFILES].sort(); const backend = [...CLIPROXY_PROFILES].sort();
const ui = [...UI_CLIPROXY_PROVIDERS].sort(); const ui = [...CLIPROXY_PROVIDERS].sort();
expect(backend).toEqual(ui); expect(backend).toEqual(ui);
}); });
test('both arrays have same length', () => { test('both arrays have same length', () => {
expect(CLIPROXY_PROFILES.length).toBe(UI_CLIPROXY_PROVIDERS.length); expect(CLIPROXY_PROFILES.length).toBe(CLIPROXY_PROVIDERS.length);
}); });
test('UI array contains all backend providers', () => { test('UI array contains all backend providers', () => {
for (const provider of CLIPROXY_PROFILES) { for (const provider of CLIPROXY_PROFILES) {
expect(UI_CLIPROXY_PROVIDERS).toContain(provider); expect(CLIPROXY_PROVIDERS).toContain(provider);
} }
}); });
}); });