mirror of
https://github.com/tiennm99/ccs.git
synced 2026-08-20 06:23:10 +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
|
// CLIProxy variants
|
||||||
for (const [name, variant] of Object.entries(unifiedConfig.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({
|
variants.push({
|
||||||
name,
|
name,
|
||||||
provider: variant?.provider || 'unknown',
|
provider,
|
||||||
settings: variant?.settings || '-',
|
settings: variant?.settings || '-',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,13 @@ import * as fs from 'fs';
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { findSimilarStrings, expandPath } from '../utils/helpers';
|
import { findSimilarStrings, expandPath } from '../utils/helpers';
|
||||||
import { Config, Settings, ProfileMetadata } from '../types';
|
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 { loadUnifiedConfig, isUnifiedMode } from '../config/unified-config-loader';
|
||||||
import { getCcsDir } from '../utils/config-manager';
|
import { getCcsDir } from '../utils/config-manager';
|
||||||
|
|
||||||
@@ -46,6 +52,16 @@ export interface ProfileDetectionResult {
|
|||||||
env?: Record<string, string>;
|
env?: Record<string, string>;
|
||||||
/** For copilot profile: the copilot config */
|
/** For copilot profile: the copilot config */
|
||||||
copilotConfig?: CopilotConfig;
|
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 {
|
export interface AllProfiles {
|
||||||
@@ -110,12 +126,29 @@ class ProfileDetector {
|
|||||||
// Check CLIProxy variants first
|
// Check CLIProxy variants first
|
||||||
if (config.cliproxy?.variants?.[profileName]) {
|
if (config.cliproxy?.variants?.[profileName]) {
|
||||||
const variant = 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 {
|
return {
|
||||||
type: 'cliproxy',
|
type: 'cliproxy',
|
||||||
name: profileName,
|
name: profileName,
|
||||||
provider: variant.provider as CLIProxyProfileName,
|
provider: singleVariant.provider as CLIProxyProfileName,
|
||||||
settingsPath: variant.settings,
|
settingsPath: singleVariant.settings,
|
||||||
port: variant.port,
|
port: singleVariant.port,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,7 +409,11 @@ class ProfileDetector {
|
|||||||
lines.push('CLIProxy variants (unified config):');
|
lines.push('CLIProxy variants (unified config):');
|
||||||
variants.forEach((name) => {
|
variants.forEach((name) => {
|
||||||
const variant = unifiedConfig.cliproxy?.variants[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, {
|
await execClaudeWithCLIProxy(claudeCli, provider, remainingArgs, {
|
||||||
customSettingsPath,
|
customSettingsPath,
|
||||||
port: variantPort,
|
port: variantPort,
|
||||||
|
isComposite: profileInfo.isComposite,
|
||||||
|
compositeTiers: profileInfo.compositeTiers,
|
||||||
|
compositeDefaultTier: profileInfo.compositeDefaultTier,
|
||||||
});
|
});
|
||||||
} else if (profileInfo.type === 'copilot') {
|
} else if (profileInfo.type === 'copilot') {
|
||||||
// COPILOT FLOW: GitHub Copilot subscription via copilot-api proxy
|
// COPILOT FLOW: GitHub Copilot subscription via copilot-api proxy
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import {
|
|||||||
ThinkingConfig,
|
ThinkingConfig,
|
||||||
DashboardAuthConfig,
|
DashboardAuthConfig,
|
||||||
ImageAnalysisConfig,
|
ImageAnalysisConfig,
|
||||||
|
CLIPROXY_SUPPORTED_PROVIDERS,
|
||||||
} from './unified-config-types';
|
} from './unified-config-types';
|
||||||
import { isUnifiedConfigEnabled } from './feature-flags';
|
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.
|
* Merge partial config with defaults.
|
||||||
* Preserves existing data while filling in missing sections.
|
* Preserves existing data while filling in missing sections.
|
||||||
@@ -404,7 +427,10 @@ export function loadOrCreateUnifiedConfig(): UnifiedConfig {
|
|||||||
const existing = loadUnifiedConfig();
|
const existing = loadUnifiedConfig();
|
||||||
if (existing) {
|
if (existing) {
|
||||||
// Merge with defaults to fill any missing sections
|
// 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
|
// Create empty config
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as os from 'os';
|
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 { expandPath, error } from './helpers';
|
||||||
import { info } from './ui';
|
import { info } from './ui';
|
||||||
import { isUnifiedMode, loadOrCreateUnifiedConfig } from '../config/unified-config-loader';
|
import { isUnifiedMode, loadOrCreateUnifiedConfig } from '../config/unified-config-loader';
|
||||||
@@ -191,12 +198,22 @@ export function loadConfigSafe(): Config {
|
|||||||
if (unifiedConfig.cliproxy?.variants) {
|
if (unifiedConfig.cliproxy?.variants) {
|
||||||
cliproxy = {};
|
cliproxy = {};
|
||||||
for (const [name, variant] of Object.entries(unifiedConfig.cliproxy.variants)) {
|
for (const [name, variant] of Object.entries(unifiedConfig.cliproxy.variants)) {
|
||||||
cliproxy[name] = {
|
if ('type' in variant && variant.type === 'composite') {
|
||||||
provider: variant.provider,
|
// Composite variants: use default tier's provider
|
||||||
settings: variant.settings,
|
cliproxy[name] = {
|
||||||
account: variant.account,
|
provider: variant.tiers[variant.default_tier].provider,
|
||||||
port: variant.port,
|
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