mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-17 00:16:46 +00:00
fix(cliproxy): address 4 functional regressions in composite variants
- P1: Remote composite env now uses remote endpoint/auth Add CompositeRemoteConfig parameter to getCompositeEnvVars() Pass remote config when useRemoteProxy is true - P2: Tier thinking uses tier-specific provider Extract tier provider from compositeTiers for supportsThinking() Fix mixed-provider composite thinking config - P2: Force variant removal checks result Fail fast if removeVariant fails in --force mode - P2: Guard composite validation against missing tiers Add null check before Object.entries(variant.tiers)
This commit is contained in:
@@ -415,6 +415,14 @@ export function getRemoteEnvVars(
|
||||
return env;
|
||||
}
|
||||
|
||||
/** Remote config for composite variant (passed from env-resolver) */
|
||||
export interface CompositeRemoteConfig {
|
||||
host: string;
|
||||
port: number;
|
||||
protocol: 'http' | 'https';
|
||||
authToken?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get environment variables for composite variant.
|
||||
* Uses root URL (no /api/provider/ path) for model-based routing.
|
||||
@@ -422,14 +430,16 @@ export function getRemoteEnvVars(
|
||||
*
|
||||
* @param tiers Per-tier provider+model mappings
|
||||
* @param defaultTier Which tier ANTHROPIC_MODEL equals
|
||||
* @param port Local CLIProxy port
|
||||
* @param port Local CLIProxy port (ignored if remoteConfig provided)
|
||||
* @param customSettingsPath Optional path to user's custom settings file
|
||||
* @param remoteConfig Optional remote proxy config (overrides localhost URL/auth)
|
||||
*/
|
||||
export function getCompositeEnvVars(
|
||||
tiers: { opus: CompositeTierConfig; sonnet: CompositeTierConfig; haiku: CompositeTierConfig },
|
||||
defaultTier: 'opus' | 'sonnet' | 'haiku',
|
||||
port: number = CLIPROXY_DEFAULT_PORT,
|
||||
customSettingsPath?: string
|
||||
customSettingsPath?: string,
|
||||
remoteConfig?: CompositeRemoteConfig
|
||||
): Record<string, string> {
|
||||
const globalEnv = getGlobalEnvVars();
|
||||
|
||||
@@ -473,12 +483,26 @@ export function getCompositeEnvVars(
|
||||
throw new Error(`Missing model for default tier '${defaultTier}'`);
|
||||
}
|
||||
|
||||
// Determine base URL and auth token based on remote vs local mode
|
||||
const baseUrl = remoteConfig
|
||||
? (() => {
|
||||
const normalizedProtocol = normalizeProtocol(remoteConfig.protocol);
|
||||
const effectivePort =
|
||||
validateRemotePort(remoteConfig.port) ?? getRemoteDefaultPort(normalizedProtocol);
|
||||
const standardWebPort = normalizedProtocol === 'https' ? 443 : 80;
|
||||
const portSuffix = effectivePort === standardWebPort ? '' : `:${effectivePort}`;
|
||||
return `${normalizedProtocol}://${remoteConfig.host}${portSuffix}`;
|
||||
})()
|
||||
: `http://127.0.0.1:${validPort}`;
|
||||
|
||||
const authToken = remoteConfig?.authToken ?? getEffectiveApiKey();
|
||||
|
||||
const env: Record<string, string> = {
|
||||
...globalEnv,
|
||||
...additionalEnvVars,
|
||||
// Root URL — CLIProxyAPI routes based on model name in request body
|
||||
ANTHROPIC_BASE_URL: `http://127.0.0.1:${validPort}`,
|
||||
ANTHROPIC_AUTH_TOKEN: getEffectiveApiKey(),
|
||||
ANTHROPIC_BASE_URL: baseUrl,
|
||||
ANTHROPIC_AUTH_TOKEN: authToken,
|
||||
ANTHROPIC_MODEL: defaultModel,
|
||||
};
|
||||
|
||||
|
||||
@@ -67,14 +67,22 @@ export function getThinkingValueForTier(
|
||||
return thinkingConfig.tier_defaults?.[tier] ?? DEFAULT_THINKING_TIER_DEFAULTS[tier];
|
||||
}
|
||||
|
||||
/**
|
||||
* Composite tier config for provider lookup (subset of full CompositeTierConfig)
|
||||
*/
|
||||
interface CompositeTierProvider {
|
||||
provider?: CLIProxyProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply thinking configuration to env vars.
|
||||
* Modifies ANTHROPIC_MODEL and tier models with thinking suffixes.
|
||||
*
|
||||
* @param envVars - Environment variables to modify
|
||||
* @param provider - CLIProxy provider
|
||||
* @param provider - CLIProxy provider (default provider for base model)
|
||||
* @param thinkingOverride - Optional CLI override (takes priority over config)
|
||||
* @param compositeTierThinking - Optional per-tier thinking overrides for composite variants
|
||||
* @param compositeTiers - Optional per-tier provider config for composite variants
|
||||
* @returns Modified env vars with thinking suffixes applied
|
||||
*/
|
||||
export function applyThinkingConfig(
|
||||
@@ -85,6 +93,11 @@ export function applyThinkingConfig(
|
||||
opus?: string;
|
||||
sonnet?: string;
|
||||
haiku?: string;
|
||||
},
|
||||
compositeTiers?: {
|
||||
opus?: CompositeTierProvider;
|
||||
sonnet?: CompositeTierProvider;
|
||||
haiku?: CompositeTierProvider;
|
||||
}
|
||||
): NodeJS.ProcessEnv {
|
||||
const thinkingConfig = getThinkingConfig();
|
||||
@@ -158,7 +171,7 @@ export function applyThinkingConfig(
|
||||
|
||||
for (const tierVar of tierModels) {
|
||||
const model = result[tierVar];
|
||||
if (model && supportsThinking(provider, model)) {
|
||||
if (model) {
|
||||
// Get tier-specific thinking value
|
||||
const tier = tierVar.includes('OPUS')
|
||||
? 'opus'
|
||||
@@ -166,6 +179,15 @@ export function applyThinkingConfig(
|
||||
? 'sonnet'
|
||||
: 'haiku';
|
||||
|
||||
// P2 FIX: Use tier-specific provider from compositeTiers for mixed-provider composites
|
||||
// Falls back to the default provider if not a composite or tier not specified
|
||||
const tierProvider = compositeTiers?.[tier]?.provider ?? provider;
|
||||
|
||||
// Check if this tier's model supports thinking (using tier-specific provider)
|
||||
if (!supportsThinking(tierProvider, model)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Priority chain: CLI --thinking > per-tier config > global config > defaults
|
||||
let tierThinkingValue: string | number;
|
||||
if (thinkingOverride !== undefined) {
|
||||
@@ -177,8 +199,8 @@ export function applyThinkingConfig(
|
||||
// Per-tier config from composite variant
|
||||
tierThinkingValue = perTierValue;
|
||||
} else {
|
||||
// Global config or defaults
|
||||
tierThinkingValue = getThinkingValueForTier(tier, provider, thinkingConfig);
|
||||
// Global config or defaults (use tier-specific provider for provider overrides)
|
||||
tierThinkingValue = getThinkingValueForTier(tier, tierProvider, thinkingConfig);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -92,7 +92,13 @@ export function buildClaudeEnvironment(config: ProxyChainConfig): Record<string,
|
||||
compositeTiers,
|
||||
compositeDefaultTier,
|
||||
tunnelPort,
|
||||
customSettingsPath
|
||||
customSettingsPath,
|
||||
{
|
||||
host: '127.0.0.1',
|
||||
port: tunnelPort,
|
||||
protocol: 'http',
|
||||
authToken: remoteConfig.authToken,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
envVars = getRemoteEnvVars(
|
||||
@@ -113,7 +119,13 @@ export function buildClaudeEnvironment(config: ProxyChainConfig): Record<string,
|
||||
compositeTiers,
|
||||
compositeDefaultTier,
|
||||
remoteConfig.port,
|
||||
customSettingsPath
|
||||
customSettingsPath,
|
||||
{
|
||||
host: remoteConfig.host,
|
||||
port: remoteConfig.port,
|
||||
protocol: remoteConfig.protocol,
|
||||
authToken: remoteConfig.authToken,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
envVars = getRemoteEnvVars(
|
||||
@@ -164,7 +176,7 @@ export function buildClaudeEnvironment(config: ProxyChainConfig): Record<string,
|
||||
}
|
||||
|
||||
// Apply thinking configuration to model (auto tier-based or manual override)
|
||||
applyThinkingConfig(envVars, provider, thinkingOverride, compositeTierThinking);
|
||||
applyThinkingConfig(envVars, provider, thinkingOverride, compositeTierThinking, compositeTiers);
|
||||
|
||||
// Apply extended context suffix for 1M token context window
|
||||
// Auto-enabled for Gemini, opt-in for Claude (--1m flag)
|
||||
|
||||
@@ -170,7 +170,11 @@ export async function handleCreate(
|
||||
|
||||
// Clean up old variant if --force is set
|
||||
if (variantExists(name) && parsedArgs.force) {
|
||||
removeVariant(name);
|
||||
const removeResult = removeVariant(name);
|
||||
if (!removeResult.success) {
|
||||
console.log(fail(`Cannot overwrite variant '${name}': ${removeResult.error}`));
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Composite mode: select provider+model per tier
|
||||
|
||||
@@ -216,6 +216,12 @@ function validateCompositeVariants(config: UnifiedConfig): void {
|
||||
|
||||
for (const [name, variant] of Object.entries(variants)) {
|
||||
if ('type' in variant && variant.type === 'composite') {
|
||||
// Guard against malformed composite variants
|
||||
if (!variant.tiers || typeof variant.tiers !== 'object') {
|
||||
console.warn(`[!] Composite variant '${name}' missing tiers object, skipping validation`);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [tier, tierConfig] of Object.entries(variant.tiers)) {
|
||||
if (!validProviders.has(tierConfig.provider)) {
|
||||
console.warn(
|
||||
|
||||
Reference in New Issue
Block a user