mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 12:15:57 +00:00
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:
@@ -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 || '-',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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})`);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user