diff --git a/src/cliproxy/config/generator.ts b/src/cliproxy/config/generator.ts index dd1799bc..ca832297 100644 --- a/src/cliproxy/config/generator.ts +++ b/src/cliproxy/config/generator.ts @@ -6,6 +6,7 @@ import * as fs from 'fs'; import * as path from 'path'; import { CLIProxyProvider, ProviderConfig } from '../types'; +import { getProviderDisplayName } from '../provider-capabilities'; import { getModelMappingFromConfig } from '../base-config-loader'; import { loadOrCreateUnifiedConfig } from '../../config/unified-config-loader'; 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 }, ]; -/** Provider display names (static metadata) */ -const PROVIDER_DISPLAY_NAMES: Record = { - gemini: 'Gemini', - codex: 'Codex', - agy: 'Antigravity', - qwen: 'Qwen Code', - iflow: 'iFlow', - kiro: 'Kiro (AWS)', - ghcp: 'GitHub Copilot (OAuth)', - claude: 'Claude (Anthropic)', -}; - /** * Get provider configuration * Model mappings are loaded from config/base-{provider}.settings.json */ 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 const models = getModelMappingFromConfig(provider); return { name: provider, - displayName, + displayName: getProviderDisplayName(provider), models, requiresOAuth: true, // All CLIProxy providers require OAuth }; diff --git a/src/config/migration-manager.ts b/src/config/migration-manager.ts index 9fa347d4..e14342a2 100644 --- a/src/config/migration-manager.ts +++ b/src/config/migration-manager.ts @@ -18,6 +18,7 @@ import { getCcsDir } from '../utils/config-manager'; import { expandPath } from '../utils/helpers'; import type { ProfileConfig, AccountConfig, CLIProxyVariantConfig } 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 { infoBox, warn } from '../utils/ui'; @@ -203,7 +204,7 @@ export async function migrate(dryRun = false): Promise { // 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 // 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) { const settingsFile = `${provider}.settings.json`; const settingsPath = path.join(ccsDir, settingsFile); diff --git a/src/config/unified-config-types.ts b/src/config/unified-config-types.ts index 6e6368c0..3420d406 100644 --- a/src/config/unified-config-types.ts +++ b/src/config/unified-config-types.ts @@ -10,6 +10,8 @@ */ import type { TargetType } from '../targets/target-adapter'; +import type { CLIProxyProvider } from '../cliproxy/types'; +import { CLIPROXY_PROVIDER_IDS } from '../cliproxy/provider-capabilities'; /** * Unified config version. @@ -25,18 +27,9 @@ export const UNIFIED_CONFIG_VERSION = 8; /** * 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 = [ - 'gemini', - 'codex', - 'agy', - 'qwen', - 'iflow', - 'kiro', - 'ghcp', - 'claude', -] as const; +export const CLIPROXY_SUPPORTED_PROVIDERS = CLIPROXY_PROVIDER_IDS; /** * Account configuration (formerly in profiles.json). @@ -80,7 +73,7 @@ export type OAuthAccounts = Record; */ export interface CLIProxyVariantConfig { /** Base provider to use */ - provider: 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow' | 'kiro' | 'ghcp' | 'claude'; + provider: CLIProxyProvider; /** Account nickname (references oauth_accounts) */ account?: string; /** Path to settings file (e.g., "~/.ccs/gemini-custom.settings.json") */ @@ -98,14 +91,14 @@ export interface CLIProxyVariantConfig { */ export interface CompositeTierConfig { /** Provider for this tier */ - provider: 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow' | 'kiro' | 'ghcp' | 'claude'; + provider: CLIProxyProvider; /** Model ID to use for this tier */ model: string; /** Account nickname (optional, references oauth_accounts) */ account?: string; /** Fallback provider+model if primary fails */ fallback?: { - provider: 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow' | 'kiro' | 'ghcp' | 'claude'; + provider: CLIProxyProvider; model: string; account?: string; }; diff --git a/src/types/config.ts b/src/types/config.ts index db910441..952355a9 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -3,6 +3,8 @@ * Source: ~/.ccs/config.json */ +import type { CLIProxyProvider } from '../cliproxy/types'; + /** * Profile configuration mapping * Maps profile names to settings.json paths @@ -18,7 +20,7 @@ export interface ProfilesConfig { */ export interface CLIProxyVariantConfig { /** CLIProxy provider to use */ - provider: 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow' | 'kiro' | 'ghcp' | 'claude'; + provider: CLIProxyProvider; /** Path to settings.json with custom model configuration (optional) */ settings?: string; /** Account identifier for multi-account support (optional, defaults to 'default') */ diff --git a/src/web-server/routes/proxy-routes.ts b/src/web-server/routes/proxy-routes.ts index b4cc3f68..a8c92f84 100644 --- a/src/web-server/routes/proxy-routes.ts +++ b/src/web-server/routes/proxy-routes.ts @@ -16,6 +16,7 @@ import { DEFAULT_CLIPROXY_SERVER_CONFIG, CliproxyServerConfig, } from '../../config/unified-config-types'; +import { CLIPROXY_PROVIDER_IDS } from '../../cliproxy/provider-capabilities'; const router = Router(); @@ -113,7 +114,7 @@ router.put('/backend', async (req: Request, res: Response) => { config.cliproxy = { backend, oauth_accounts: {}, - providers: ['gemini', 'codex', 'agy', 'qwen', 'iflow', 'kiro', 'ghcp'], + providers: [...CLIPROXY_PROVIDER_IDS], variants: {}, }; } else { diff --git a/tests/unit/cliproxy/backend-ui-provider-arrays-sync.test.ts b/tests/unit/cliproxy/backend-ui-provider-arrays-sync.test.ts index 50f48dea..15b6d489 100644 --- a/tests/unit/cliproxy/backend-ui-provider-arrays-sync.test.ts +++ b/tests/unit/cliproxy/backend-ui-provider-arrays-sync.test.ts @@ -7,34 +7,23 @@ import { describe, expect, test } from 'bun:test'; import { CLIPROXY_PROFILES } from '../../../src/auth/profile-detector'; - -// UI providers (must manually sync - this test validates the sync) -const UI_CLIPROXY_PROVIDERS = [ - 'gemini', - 'codex', - 'agy', - 'qwen', - 'iflow', - 'kiro', - 'ghcp', - 'claude', -] as const; +import { CLIPROXY_PROVIDERS } from '../../../ui/src/lib/provider-config'; describe('Provider Sync', () => { test('backend CLIPROXY_PROFILES matches UI CLIPROXY_PROVIDERS', () => { const backend = [...CLIPROXY_PROFILES].sort(); - const ui = [...UI_CLIPROXY_PROVIDERS].sort(); + const ui = [...CLIPROXY_PROVIDERS].sort(); expect(backend).toEqual(ui); }); 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', () => { for (const provider of CLIPROXY_PROFILES) { - expect(UI_CLIPROXY_PROVIDERS).toContain(provider); + expect(CLIPROXY_PROVIDERS).toContain(provider); } }); });