mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-03 12:21:20 +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;
|
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.
|
* Get environment variables for composite variant.
|
||||||
* Uses root URL (no /api/provider/ path) for model-based routing.
|
* 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 tiers Per-tier provider+model mappings
|
||||||
* @param defaultTier Which tier ANTHROPIC_MODEL equals
|
* @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 customSettingsPath Optional path to user's custom settings file
|
||||||
|
* @param remoteConfig Optional remote proxy config (overrides localhost URL/auth)
|
||||||
*/
|
*/
|
||||||
export function getCompositeEnvVars(
|
export function getCompositeEnvVars(
|
||||||
tiers: { opus: CompositeTierConfig; sonnet: CompositeTierConfig; haiku: CompositeTierConfig },
|
tiers: { opus: CompositeTierConfig; sonnet: CompositeTierConfig; haiku: CompositeTierConfig },
|
||||||
defaultTier: 'opus' | 'sonnet' | 'haiku',
|
defaultTier: 'opus' | 'sonnet' | 'haiku',
|
||||||
port: number = CLIPROXY_DEFAULT_PORT,
|
port: number = CLIPROXY_DEFAULT_PORT,
|
||||||
customSettingsPath?: string
|
customSettingsPath?: string,
|
||||||
|
remoteConfig?: CompositeRemoteConfig
|
||||||
): Record<string, string> {
|
): Record<string, string> {
|
||||||
const globalEnv = getGlobalEnvVars();
|
const globalEnv = getGlobalEnvVars();
|
||||||
|
|
||||||
@@ -473,12 +483,26 @@ export function getCompositeEnvVars(
|
|||||||
throw new Error(`Missing model for default tier '${defaultTier}'`);
|
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> = {
|
const env: Record<string, string> = {
|
||||||
...globalEnv,
|
...globalEnv,
|
||||||
...additionalEnvVars,
|
...additionalEnvVars,
|
||||||
// Root URL — CLIProxyAPI routes based on model name in request body
|
// Root URL — CLIProxyAPI routes based on model name in request body
|
||||||
ANTHROPIC_BASE_URL: `http://127.0.0.1:${validPort}`,
|
ANTHROPIC_BASE_URL: baseUrl,
|
||||||
ANTHROPIC_AUTH_TOKEN: getEffectiveApiKey(),
|
ANTHROPIC_AUTH_TOKEN: authToken,
|
||||||
ANTHROPIC_MODEL: defaultModel,
|
ANTHROPIC_MODEL: defaultModel,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -67,14 +67,22 @@ export function getThinkingValueForTier(
|
|||||||
return thinkingConfig.tier_defaults?.[tier] ?? DEFAULT_THINKING_TIER_DEFAULTS[tier];
|
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.
|
* Apply thinking configuration to env vars.
|
||||||
* Modifies ANTHROPIC_MODEL and tier models with thinking suffixes.
|
* Modifies ANTHROPIC_MODEL and tier models with thinking suffixes.
|
||||||
*
|
*
|
||||||
* @param envVars - Environment variables to modify
|
* @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 thinkingOverride - Optional CLI override (takes priority over config)
|
||||||
* @param compositeTierThinking - Optional per-tier thinking overrides for composite variants
|
* @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
|
* @returns Modified env vars with thinking suffixes applied
|
||||||
*/
|
*/
|
||||||
export function applyThinkingConfig(
|
export function applyThinkingConfig(
|
||||||
@@ -85,6 +93,11 @@ export function applyThinkingConfig(
|
|||||||
opus?: string;
|
opus?: string;
|
||||||
sonnet?: string;
|
sonnet?: string;
|
||||||
haiku?: string;
|
haiku?: string;
|
||||||
|
},
|
||||||
|
compositeTiers?: {
|
||||||
|
opus?: CompositeTierProvider;
|
||||||
|
sonnet?: CompositeTierProvider;
|
||||||
|
haiku?: CompositeTierProvider;
|
||||||
}
|
}
|
||||||
): NodeJS.ProcessEnv {
|
): NodeJS.ProcessEnv {
|
||||||
const thinkingConfig = getThinkingConfig();
|
const thinkingConfig = getThinkingConfig();
|
||||||
@@ -158,7 +171,7 @@ export function applyThinkingConfig(
|
|||||||
|
|
||||||
for (const tierVar of tierModels) {
|
for (const tierVar of tierModels) {
|
||||||
const model = result[tierVar];
|
const model = result[tierVar];
|
||||||
if (model && supportsThinking(provider, model)) {
|
if (model) {
|
||||||
// Get tier-specific thinking value
|
// Get tier-specific thinking value
|
||||||
const tier = tierVar.includes('OPUS')
|
const tier = tierVar.includes('OPUS')
|
||||||
? 'opus'
|
? 'opus'
|
||||||
@@ -166,6 +179,15 @@ export function applyThinkingConfig(
|
|||||||
? 'sonnet'
|
? 'sonnet'
|
||||||
: 'haiku';
|
: '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
|
// Priority chain: CLI --thinking > per-tier config > global config > defaults
|
||||||
let tierThinkingValue: string | number;
|
let tierThinkingValue: string | number;
|
||||||
if (thinkingOverride !== undefined) {
|
if (thinkingOverride !== undefined) {
|
||||||
@@ -177,8 +199,8 @@ export function applyThinkingConfig(
|
|||||||
// Per-tier config from composite variant
|
// Per-tier config from composite variant
|
||||||
tierThinkingValue = perTierValue;
|
tierThinkingValue = perTierValue;
|
||||||
} else {
|
} else {
|
||||||
// Global config or defaults
|
// Global config or defaults (use tier-specific provider for provider overrides)
|
||||||
tierThinkingValue = getThinkingValueForTier(tier, provider, thinkingConfig);
|
tierThinkingValue = getThinkingValueForTier(tier, tierProvider, thinkingConfig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,13 @@ export function buildClaudeEnvironment(config: ProxyChainConfig): Record<string,
|
|||||||
compositeTiers,
|
compositeTiers,
|
||||||
compositeDefaultTier,
|
compositeDefaultTier,
|
||||||
tunnelPort,
|
tunnelPort,
|
||||||
customSettingsPath
|
customSettingsPath,
|
||||||
|
{
|
||||||
|
host: '127.0.0.1',
|
||||||
|
port: tunnelPort,
|
||||||
|
protocol: 'http',
|
||||||
|
authToken: remoteConfig.authToken,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
envVars = getRemoteEnvVars(
|
envVars = getRemoteEnvVars(
|
||||||
@@ -113,7 +119,13 @@ export function buildClaudeEnvironment(config: ProxyChainConfig): Record<string,
|
|||||||
compositeTiers,
|
compositeTiers,
|
||||||
compositeDefaultTier,
|
compositeDefaultTier,
|
||||||
remoteConfig.port,
|
remoteConfig.port,
|
||||||
customSettingsPath
|
customSettingsPath,
|
||||||
|
{
|
||||||
|
host: remoteConfig.host,
|
||||||
|
port: remoteConfig.port,
|
||||||
|
protocol: remoteConfig.protocol,
|
||||||
|
authToken: remoteConfig.authToken,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
envVars = getRemoteEnvVars(
|
envVars = getRemoteEnvVars(
|
||||||
@@ -164,7 +176,7 @@ export function buildClaudeEnvironment(config: ProxyChainConfig): Record<string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply thinking configuration to model (auto tier-based or manual override)
|
// 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
|
// Apply extended context suffix for 1M token context window
|
||||||
// Auto-enabled for Gemini, opt-in for Claude (--1m flag)
|
// 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
|
// Clean up old variant if --force is set
|
||||||
if (variantExists(name) && parsedArgs.force) {
|
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
|
// Composite mode: select provider+model per tier
|
||||||
|
|||||||
@@ -216,6 +216,12 @@ function validateCompositeVariants(config: UnifiedConfig): void {
|
|||||||
|
|
||||||
for (const [name, variant] of Object.entries(variants)) {
|
for (const [name, variant] of Object.entries(variants)) {
|
||||||
if ('type' in variant && variant.type === 'composite') {
|
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)) {
|
for (const [tier, tierConfig] of Object.entries(variant.tiers)) {
|
||||||
if (!validProviders.has(tierConfig.provider)) {
|
if (!validProviders.has(tierConfig.provider)) {
|
||||||
console.warn(
|
console.warn(
|
||||||
|
|||||||
Reference in New Issue
Block a user