feat(cliproxy): wire composite variant into main execution flow

- Detect composite variants in ProfileDetector with tier metadata
- Pass isComposite/compositeTiers/compositeDefaultTier through ccs.ts
- Handle composite in loadConfigSafe() legacy adapter
- Add validateCompositeVariants() for provider string validation
- Display 'composite' label in API profile listing

Refs #506
This commit is contained in:
Tam Nhu Tran
2026-02-12 04:09:19 +07:00
parent 4926c200f2
commit 07bef9f50e
5 changed files with 101 additions and 14 deletions
+5 -1
View File
@@ -72,9 +72,13 @@ export function listApiProfiles(): ApiListResult {
}
// CLIProxy variants
for (const [name, variant] of Object.entries(unifiedConfig.cliproxy?.variants || {})) {
const provider =
variant && 'type' in variant && variant.type === 'composite'
? 'composite'
: (variant as { provider?: string })?.provider || 'unknown';
variants.push({
name,
provider: variant?.provider || 'unknown',
provider,
settings: variant?.settings || '-',
});
}
+42 -5
View File
@@ -13,7 +13,13 @@ import * as fs from 'fs';
import * as path from 'path';
import { findSimilarStrings, expandPath } from '../utils/helpers';
import { Config, Settings, ProfileMetadata } from '../types';
import { UnifiedConfig, CopilotConfig } from '../config/unified-config-types';
import {
UnifiedConfig,
CopilotConfig,
CLIProxyVariantConfig,
CompositeVariantConfig,
CompositeTierConfig,
} from '../config/unified-config-types';
import { loadUnifiedConfig, isUnifiedMode } from '../config/unified-config-loader';
import { getCcsDir } from '../utils/config-manager';
@@ -46,6 +52,16 @@ export interface ProfileDetectionResult {
env?: Record<string, string>;
/** For copilot profile: the copilot config */
copilotConfig?: CopilotConfig;
/** For composite variants: true when variant mixes providers per tier */
isComposite?: boolean;
/** For composite variants: which tier is the default */
compositeDefaultTier?: 'opus' | 'sonnet' | 'haiku';
/** For composite variants: per-tier provider+model mappings */
compositeTiers?: {
opus: CompositeTierConfig;
sonnet: CompositeTierConfig;
haiku: CompositeTierConfig;
};
}
export interface AllProfiles {
@@ -110,12 +126,29 @@ class ProfileDetector {
// Check CLIProxy variants first
if (config.cliproxy?.variants?.[profileName]) {
const variant = config.cliproxy.variants[profileName];
// Handle composite variants
if ('type' in variant && variant.type === 'composite') {
const composite = variant as CompositeVariantConfig;
return {
type: 'cliproxy',
name: profileName,
provider: composite.tiers[composite.default_tier].provider as CLIProxyProfileName,
settingsPath: composite.settings,
port: composite.port,
isComposite: true,
compositeDefaultTier: composite.default_tier,
compositeTiers: composite.tiers,
};
}
const singleVariant = variant as CLIProxyVariantConfig;
return {
type: 'cliproxy',
name: profileName,
provider: variant.provider as CLIProxyProfileName,
settingsPath: variant.settings,
port: variant.port,
provider: singleVariant.provider as CLIProxyProfileName,
settingsPath: singleVariant.settings,
port: singleVariant.port,
};
}
@@ -376,7 +409,11 @@ class ProfileDetector {
lines.push('CLIProxy variants (unified config):');
variants.forEach((name) => {
const variant = unifiedConfig.cliproxy?.variants[name];
lines.push(` - ${name} (${variant?.provider || 'unknown'})`);
const label =
variant && 'type' in variant && variant.type === 'composite'
? 'composite'
: (variant as { provider?: string })?.provider || 'unknown';
lines.push(` - ${name} (${label})`);
});
}
+3
View File
@@ -608,6 +608,9 @@ async function main(): Promise<void> {
await execClaudeWithCLIProxy(claudeCli, provider, remainingArgs, {
customSettingsPath,
port: variantPort,
isComposite: profileInfo.isComposite,
compositeTiers: profileInfo.compositeTiers,
compositeDefaultTier: profileInfo.compositeDefaultTier,
});
} else if (profileInfo.type === 'copilot') {
// COPILOT FLOW: GitHub Copilot subscription via copilot-api proxy
+27 -1
View File
@@ -25,6 +25,7 @@ import {
ThinkingConfig,
DashboardAuthConfig,
ImageAnalysisConfig,
CLIPROXY_SUPPORTED_PROVIDERS,
} from './unified-config-types';
import { isUnifiedConfigEnabled } from './feature-flags';
@@ -204,6 +205,28 @@ export function loadUnifiedConfig(): UnifiedConfig | null {
}
}
/**
* Validate composite variant provider strings.
* Warns about invalid providers in composite variant configurations.
*/
function validateCompositeVariants(config: UnifiedConfig): void {
const validProviders = new Set(CLIPROXY_SUPPORTED_PROVIDERS);
const variants = config.cliproxy?.variants;
if (!variants) return;
for (const [name, variant] of Object.entries(variants)) {
if ('type' in variant && variant.type === 'composite') {
for (const [tier, tierConfig] of Object.entries(variant.tiers)) {
if (!validProviders.has(tierConfig.provider)) {
console.warn(
`[!] Variant '${name}': invalid provider '${tierConfig.provider}' in ${tier} tier`
);
}
}
}
}
}
/**
* Merge partial config with defaults.
* Preserves existing data while filling in missing sections.
@@ -404,7 +427,10 @@ export function loadOrCreateUnifiedConfig(): UnifiedConfig {
const existing = loadUnifiedConfig();
if (existing) {
// Merge with defaults to fill any missing sections
return mergeWithDefaults(existing);
const merged = mergeWithDefaults(existing);
// Validate composite variant provider strings
validateCompositeVariants(merged);
return merged;
}
// Create empty config
+24 -7
View File
@@ -1,7 +1,14 @@
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { Config, isConfig, Settings, isSettings, CLIProxyVariantsConfig } from '../types';
import {
Config,
isConfig,
Settings,
isSettings,
CLIProxyVariantsConfig,
CLIProxyVariantConfig,
} from '../types';
import { expandPath, error } from './helpers';
import { info } from './ui';
import { isUnifiedMode, loadOrCreateUnifiedConfig } from '../config/unified-config-loader';
@@ -191,12 +198,22 @@ export function loadConfigSafe(): Config {
if (unifiedConfig.cliproxy?.variants) {
cliproxy = {};
for (const [name, variant] of Object.entries(unifiedConfig.cliproxy.variants)) {
cliproxy[name] = {
provider: variant.provider,
settings: variant.settings,
account: variant.account,
port: variant.port,
};
if ('type' in variant && variant.type === 'composite') {
// Composite variants: use default tier's provider
cliproxy[name] = {
provider: variant.tiers[variant.default_tier].provider,
settings: variant.settings,
port: variant.port,
};
} else {
const single = variant as CLIProxyVariantConfig;
cliproxy[name] = {
provider: single.provider,
settings: single.settings,
account: single.account,
port: single.port,
};
}
}
}