diff --git a/src/auth/profile-detector.ts b/src/auth/profile-detector.ts index 16accd5f..a3fad69b 100644 --- a/src/auth/profile-detector.ts +++ b/src/auth/profile-detector.ts @@ -39,6 +39,8 @@ export interface ProfileDetectionResult { message?: string; /** For cliproxy variants: the underlying provider (gemini, codex, agy, qwen) */ provider?: CLIProxyProfileName; + /** For cliproxy variants: dedicated port for isolation (8318-8417) */ + port?: number; /** For unified config profiles: merged env vars (config + secrets) */ env?: Record; /** For copilot profile: the copilot config */ @@ -110,6 +112,8 @@ class ProfileDetector { type: 'cliproxy', name: profileName, provider: variant.provider as CLIProxyProfileName, + settingsPath: variant.settings, + port: variant.port, }; } @@ -250,6 +254,7 @@ class ProfileDetector { name: profileName, provider: variant.provider as CLIProxyProfileName, settingsPath: variant.settings, + port: variant.port, }; } diff --git a/src/ccs.ts b/src/ccs.ts index dc2df697..fc1d1d83 100644 --- a/src/ccs.ts +++ b/src/ccs.ts @@ -511,7 +511,11 @@ async function main(): Promise { // CLIPROXY FLOW: OAuth-based profiles (gemini, codex, agy, qwen) or user-defined variants const provider = profileInfo.provider || (profileInfo.name as CLIProxyProvider); const customSettingsPath = profileInfo.settingsPath; // undefined for hardcoded profiles - await execClaudeWithCLIProxy(claudeCli, provider, remainingArgs, { customSettingsPath }); + const variantPort = profileInfo.port; // variant-specific port for isolation + await execClaudeWithCLIProxy(claudeCli, provider, remainingArgs, { + customSettingsPath, + port: variantPort, + }); } else if (profileInfo.type === 'copilot') { // COPILOT FLOW: GitHub Copilot subscription via copilot-api proxy const { executeCopilotProfile } = await import('./copilot'); diff --git a/src/cliproxy/cliproxy-executor.ts b/src/cliproxy/cliproxy-executor.ts index f8ebbf20..d23e3506 100644 --- a/src/cliproxy/cliproxy-executor.ts +++ b/src/cliproxy/cliproxy-executor.ts @@ -154,7 +154,17 @@ export async function execClaudeWithCLIProxy( }); // Use resolved port from proxy config (overrides ExecutorConfig) - if (proxyConfig.port !== CLIPROXY_DEFAULT_PORT) { + // Priority: CLI flags > Variant port (cfg.port) > config.yaml > default + // cfg.port is set if variant has dedicated port; proxyConfig.port comes from CLI/ENV/config.yaml + if (cfg.port && cfg.port !== CLIPROXY_DEFAULT_PORT) { + // Variant port already set via config param - keep it (highest priority after CLI flags) + // CLI flags would have been parsed in resolveProxyConfig and set proxyConfig.port + // So if proxyConfig.port differs from default AND cfg.port differs, CLI wins + if (proxyConfig.port !== CLIPROXY_DEFAULT_PORT) { + cfg.port = proxyConfig.port; // CLI/ENV override variant port + } + // else: keep cfg.port (variant port) + } else if (proxyConfig.port !== CLIPROXY_DEFAULT_PORT) { cfg.port = proxyConfig.port; } diff --git a/ui/src/components/cliproxy/provider-editor/index.tsx b/ui/src/components/cliproxy/provider-editor/index.tsx index 17aa3e7d..7e79ffaf 100644 --- a/ui/src/components/cliproxy/provider-editor/index.tsx +++ b/ui/src/components/cliproxy/provider-editor/index.tsx @@ -94,8 +94,9 @@ export function ProviderEditor({ const accounts = authStatus.accounts || []; const handleApplyPreset = (updates: Record) => { + const effectivePort = port ?? CLIPROXY_PORT; updateEnvValues({ - ANTHROPIC_BASE_URL: `http://127.0.0.1:${CLIPROXY_PORT}/api/provider/${provider}`, + ANTHROPIC_BASE_URL: `http://127.0.0.1:${effectivePort}/api/provider/${provider}`, ANTHROPIC_AUTH_TOKEN: 'ccs-internal-managed', ...updates, }); @@ -103,8 +104,9 @@ export function ProviderEditor({ }; const handleCustomPresetApply = (values: ModelMappingValues, presetName?: string) => { + const effectivePort = port ?? CLIPROXY_PORT; updateEnvValues({ - ANTHROPIC_BASE_URL: `http://127.0.0.1:${CLIPROXY_PORT}/api/provider/${provider}`, + ANTHROPIC_BASE_URL: `http://127.0.0.1:${effectivePort}/api/provider/${provider}`, ANTHROPIC_AUTH_TOKEN: 'ccs-internal-managed', ANTHROPIC_MODEL: values.default, ANTHROPIC_DEFAULT_OPUS_MODEL: values.opus, diff --git a/ui/src/lib/preset-utils.ts b/ui/src/lib/preset-utils.ts index 7b74ed6b..80ce9673 100644 --- a/ui/src/lib/preset-utils.ts +++ b/ui/src/lib/preset-utils.ts @@ -13,10 +13,12 @@ export const CLIPROXY_PORT = 8317; * Uses the first model's presetMapping or falls back to using defaultModel for all tiers * * @param provider - The provider ID (e.g., 'gemini', 'codex', 'agy') + * @param port - Optional custom port (defaults to CLIPROXY_PORT) * @returns Object with success status and applied preset name */ export async function applyDefaultPreset( - provider: string + provider: string, + port?: number ): Promise<{ success: boolean; presetName?: string }> { const catalog = MODEL_CATALOGS[provider]; if (!catalog) return { success: false }; @@ -30,9 +32,10 @@ export async function applyDefaultPreset( haiku: catalog.defaultModel, }; + const effectivePort = port ?? CLIPROXY_PORT; const settings = { env: { - ANTHROPIC_BASE_URL: `http://127.0.0.1:${CLIPROXY_PORT}/api/provider/${provider}`, + ANTHROPIC_BASE_URL: `http://127.0.0.1:${effectivePort}/api/provider/${provider}`, ANTHROPIC_AUTH_TOKEN: 'ccs-internal-managed', ANTHROPIC_MODEL: mapping.default, ANTHROPIC_DEFAULT_OPUS_MODEL: mapping.opus,