fix(cliproxy): add defensive null checks for composite tier config

- Add optional chaining for tier access in env-resolver
- Throw error if default tier model missing in env-builder
- Add null checks for composite.tiers in profile-detector
This commit is contained in:
Tam Nhu Tran
2026-02-12 11:01:00 +07:00
parent f3618c51d7
commit 0c7dc398f7
3 changed files with 40 additions and 9 deletions
+18 -1
View File
@@ -130,10 +130,27 @@ class ProfileDetector {
// Handle composite variants
if ('type' in variant && variant.type === 'composite') {
const composite = variant as CompositeVariantConfig;
// Defensive: check for missing tiers or default_tier
if (!composite.tiers || !composite.default_tier) {
console.warn(
`[!] Warning: Composite variant '${profileName}' has missing tiers or default_tier`
);
return null;
}
const defaultTierConfig = composite.tiers[composite.default_tier];
if (!defaultTierConfig) {
console.warn(
`[!] Warning: Composite variant '${profileName}' missing config for default tier '${composite.default_tier}'`
);
return null;
}
return {
type: 'cliproxy',
name: profileName,
provider: composite.tiers[composite.default_tier].provider as CLIProxyProfileName,
provider: defaultTierConfig.provider as CLIProxyProfileName,
settingsPath: composite.settings,
port: composite.port,
isComposite: true,
+19 -5
View File
@@ -461,17 +461,31 @@ export function getCompositeEnvVars(
}
const validPort = validatePort(port);
const defaultModel = tiers[defaultTier].model;
return {
// Defensive: handle missing tiers gracefully
const opusModel = tiers.opus?.model;
const sonnetModel = tiers.sonnet?.model;
const haikuModel = tiers.haiku?.model;
const defaultModel = tiers[defaultTier]?.model;
// If default tier is missing, we cannot proceed meaningfully
if (!defaultModel) {
throw new Error(`Missing model for default tier '${defaultTier}'`);
}
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_MODEL: defaultModel,
ANTHROPIC_DEFAULT_OPUS_MODEL: tiers.opus.model,
ANTHROPIC_DEFAULT_SONNET_MODEL: tiers.sonnet.model,
ANTHROPIC_DEFAULT_HAIKU_MODEL: tiers.haiku.model,
};
// Only set tier env vars if the tier exists
if (opusModel) env.ANTHROPIC_DEFAULT_OPUS_MODEL = opusModel;
if (sonnetModel) env.ANTHROPIC_DEFAULT_SONNET_MODEL = sonnetModel;
if (haikuModel) env.ANTHROPIC_DEFAULT_HAIKU_MODEL = haikuModel;
return env;
}
+3 -3
View File
@@ -136,9 +136,9 @@ export function buildClaudeEnvironment(config: ProxyChainConfig): Record<string,
let compositeTierThinking: { opus?: string; sonnet?: string; haiku?: string } | undefined;
if (isComposite && compositeTiers) {
const tierThinking: { opus?: string; sonnet?: string; haiku?: string } = {};
if (compositeTiers.opus.thinking) tierThinking.opus = compositeTiers.opus.thinking;
if (compositeTiers.sonnet.thinking) tierThinking.sonnet = compositeTiers.sonnet.thinking;
if (compositeTiers.haiku.thinking) tierThinking.haiku = compositeTiers.haiku.thinking;
if (compositeTiers.opus?.thinking) tierThinking.opus = compositeTiers.opus.thinking;
if (compositeTiers.sonnet?.thinking) tierThinking.sonnet = compositeTiers.sonnet.thinking;
if (compositeTiers.haiku?.thinking) tierThinking.haiku = compositeTiers.haiku.thinking;
if (Object.keys(tierThinking).length > 0) {
compositeTierThinking = tierThinking;
}