fix(cliproxy): prune stale control panel alias clusters

- prune stale generated Gemini preview clusters during config regeneration

- preserve manual preview overrides on built-in Gemini alias names

- add regression coverage for stale cluster cleanup and manual pins
This commit is contained in:
Tam Nhu Tran
2026-03-29 14:07:28 -04:00
parent 9307bb857f
commit 061cd9679f
2 changed files with 252 additions and 3 deletions
+123 -3
View File
@@ -36,8 +36,9 @@ export const CCS_CONTROL_PANEL_SECRET = 'ccs';
* v12: Removed denylisted Antigravity Claude 4.5 aliases
* v13: Removed aggressive Gemini alias expansion to reduce model list noise in Control Panel
* v14: Added Gemini 3.1 Flash Antigravity aliases for upcoming rollout compatibility
* v15: Prune stale generated Antigravity Gemini preview aliases during regeneration
*/
export const CLIPROXY_CONFIG_VERSION = 14;
export const CLIPROXY_CONFIG_VERSION = 15;
interface OAuthModelAliasEntry {
name: string;
@@ -69,6 +70,12 @@ const DEFAULT_ANTIGRAVITY_ALIASES: OAuthModelAliasEntry[] = [
{ name: 'claude-opus-4-6-thinking', alias: 'claude-opus-4-6-thinking', fork: true },
];
const BUILT_IN_GEMINI_ALIAS_NAMES = new Set(
DEFAULT_ANTIGRAVITY_ALIASES.filter((entry) => entry.alias.startsWith('gemini-3')).map(
(entry) => entry.name
)
);
/**
* Get provider configuration
* Model mappings are loaded from config/base-{provider}.settings.json
@@ -148,6 +155,10 @@ function addAliasEntry(
entries.push(normalized);
}
function buildAntigravityAliasKey(entry: Pick<OAuthModelAliasEntry, 'name' | 'alias'>): string {
return `${sanitizeYamlScalar(entry.name)}\u0000${normalizeAntigravityAlias(entry.alias)}`;
}
function parseExistingAntigravityAliases(existingAliases: string): OAuthModelAliasEntry[] {
const entries: OAuthModelAliasEntry[] = [];
const lines = existingAliases.replace(/\r\n/g, '\n').split('\n');
@@ -267,6 +278,113 @@ function getCompatibilityAliases(entries: OAuthModelAliasEntry[]): OAuthModelAli
return compatibilityAliases;
}
function buildGeneratedAntigravityAliases(): OAuthModelAliasEntry[] {
const generatedAliases: OAuthModelAliasEntry[] = [];
const generatedAliasIndex = new Map<string, number>();
for (const alias of DEFAULT_ANTIGRAVITY_ALIASES) {
addAliasEntry(generatedAliases, generatedAliasIndex, alias);
}
for (const alias of getCompatibilityAliases(generatedAliases)) {
addAliasEntry(generatedAliases, generatedAliasIndex, alias);
}
return generatedAliases;
}
const GENERATED_ANTIGRAVITY_ALIASES = buildGeneratedAntigravityAliases();
const GENERATED_ANTIGRAVITY_ALIAS_MAP = new Map(
GENERATED_ANTIGRAVITY_ALIASES.map((entry) => [buildAntigravityAliasKey(entry), entry] as const)
);
function serializeAntigravityAliases(entries: OAuthModelAliasEntry[]): string {
if (entries.length === 0) {
return '';
}
const lines = [' antigravity:'];
for (const entry of entries) {
lines.push(` - name: ${entry.name}`);
lines.push(` alias: ${entry.alias}`);
if (entry.fork) {
lines.push(' fork: true');
}
}
return lines.join('\n');
}
function getLegacyGeneratedGeminiPreviewClusterKey(entry: OAuthModelAliasEntry): string | null {
if (!BUILT_IN_GEMINI_ALIAS_NAMES.has(entry.name)) {
return null;
}
const normalizedAlias = normalizeAntigravityAlias(entry.alias).toLowerCase();
if (!normalizedAlias.startsWith('gemini-3') || !normalizedAlias.includes('-preview')) {
return null;
}
const aliasWithoutCustomtools = normalizedAlias.endsWith('-customtools')
? normalizedAlias.slice(0, -'-customtools'.length)
: normalizedAlias;
const clusterAlias =
toDottedGeminiVersionAlias(aliasWithoutCustomtools) ?? aliasWithoutCustomtools;
return `${sanitizeYamlScalar(entry.name)}\u0000${clusterAlias}`;
}
function extractPreservedAntigravityAliases(existingAliases: string): string {
if (!existingAliases.trim()) {
return '';
}
const parsedEntries = parseExistingAntigravityAliases(existingAliases);
const legacyGeneratedGeminiClusterCounts = new Map<string, number>();
for (const entry of parsedEntries) {
if (entry.fork) {
continue;
}
const generatedEntry = GENERATED_ANTIGRAVITY_ALIAS_MAP.get(buildAntigravityAliasKey(entry));
if (generatedEntry) {
continue;
}
const clusterKey = getLegacyGeneratedGeminiPreviewClusterKey(entry);
if (!clusterKey) {
continue;
}
legacyGeneratedGeminiClusterCounts.set(
clusterKey,
(legacyGeneratedGeminiClusterCounts.get(clusterKey) ?? 0) + 1
);
}
const preservedEntries = parsedEntries.filter((entry) => {
const generatedEntry = GENERATED_ANTIGRAVITY_ALIAS_MAP.get(buildAntigravityAliasKey(entry));
if (generatedEntry) {
return Boolean(entry.fork) && !generatedEntry.fork;
}
if (entry.fork) {
return true;
}
const clusterKey = getLegacyGeneratedGeminiPreviewClusterKey(entry);
if (!clusterKey) {
return true;
}
return (legacyGeneratedGeminiClusterCounts.get(clusterKey) ?? 0) < 3;
});
return serializeAntigravityAliases(preservedEntries);
}
/**
* Generate oauth-model-alias YAML section.
* Merges default Antigravity aliases with any user-added custom aliases.
@@ -543,8 +661,10 @@ export function regenerateConfig(port: number = CLIPROXY_DEFAULT_PORT): string {
// Preserve claude-api-key section (managed via dashboard/API)
claudeApiKeySection = extractYamlSection(content, 'claude-api-key');
// Preserve existing oauth-model-alias user customizations
existingAliases = extractYamlSection(content, 'oauth-model-alias');
// Preserve user customizations while pruning legacy generated Gemini preview noise.
existingAliases = extractPreservedAntigravityAliases(
extractYamlSection(content, 'oauth-model-alias')
);
} catch {
// Use defaults if reading fails
}
@@ -753,6 +753,135 @@ oauth-model-alias:
);
});
it('drops stale generated Gemini preview clusters while preserving true custom aliases', () => {
const cliproxyDir = path.join(testDir, '.ccs', 'cliproxy');
fs.mkdirSync(cliproxyDir, { recursive: true });
const initialConfig = `# CLIProxyAPI config generated by CCS v12
port: 8317
api-keys:
- "ccs-internal-managed"
auth-dir: "${cliproxyDir.replace(/\\/g, '/')}/auth"
oauth-model-alias:
antigravity:
- name: gemini-3-pro-high
alias: gemini-3.9-pro-preview
- name: gemini-3-pro-high
alias: gemini-3.9-pro-preview-customtools
- name: gemini-3-pro-high
alias: gemini-3-9-pro-preview
- name: gemini-3-pro-high
alias: gemini-3-9-pro-preview-customtools
- name: gemini-3-flash
alias: gemini-3.9-flash-preview
- name: gemini-3-flash
alias: gemini-3.9-flash-preview-customtools
- name: gemini-3-flash
alias: gemini-3-9-flash-preview
- name: gemini-3-flash
alias: gemini-3-9-flash-preview-customtools
- name: custom-model
alias: keep-me
`;
fs.writeFileSync(path.join(cliproxyDir, 'config.yaml'), initialConfig);
regenerateConfig();
const newConfig = fs.readFileSync(path.join(cliproxyDir, 'config.yaml'), 'utf-8');
assert(
!newConfig.includes('alias: gemini-3.9-pro-preview'),
'Should remove stale generated Pro preview alias noise'
);
assert(
!newConfig.includes('alias: gemini-3.9-pro-preview-customtools'),
'Should remove stale generated Pro customtools alias noise'
);
assert(
!newConfig.includes('alias: gemini-3-9-pro-preview'),
'Should remove stale generated hyphenated Pro alias noise'
);
assert(
!newConfig.includes('alias: gemini-3-9-pro-preview-customtools'),
'Should remove stale generated hyphenated Pro customtools alias noise'
);
assert(
!newConfig.includes('alias: gemini-3.9-flash-preview'),
'Should remove stale generated Flash preview alias noise'
);
assert(
!newConfig.includes('alias: gemini-3.9-flash-preview-customtools'),
'Should remove stale generated Flash customtools alias noise'
);
assert(
!newConfig.includes('alias: gemini-3-9-flash-preview'),
'Should remove stale generated hyphenated Flash alias noise'
);
assert(
!newConfig.includes('alias: gemini-3-9-flash-preview-customtools'),
'Should remove stale generated Flash preview alias noise'
);
assert(newConfig.includes('alias: keep-me'), 'Should preserve true custom aliases');
assert(
newConfig.includes('alias: gemini-3-pro-preview'),
'Should still keep current baseline Pro preview alias'
);
assert(
newConfig.includes('alias: gemini-3.1-flash-preview-customtools'),
'Should still keep current baseline Flash compatibility alias'
);
});
it('preserves manual Gemini preview overrides on built-in alias names', () => {
const cliproxyDir = path.join(testDir, '.ccs', 'cliproxy');
fs.mkdirSync(cliproxyDir, { recursive: true });
const initialConfig = `# CLIProxyAPI config generated by CCS v12
port: 8317
api-keys:
- "ccs-internal-managed"
auth-dir: "${cliproxyDir.replace(/\\/g, '/')}/auth"
oauth-model-alias:
antigravity:
- name: gemini-3-pro-high
alias: gemini-3.2-pro-preview
- name: gemini-3-flash
alias: gemini-3.2-flash-preview
fork: true
`;
fs.writeFileSync(path.join(cliproxyDir, 'config.yaml'), initialConfig);
regenerateConfig();
const newConfig = fs.readFileSync(path.join(cliproxyDir, 'config.yaml'), 'utf-8');
assert(
newConfig.includes('alias: gemini-3.2-pro-preview'),
'Should preserve manual Pro preview overrides on built-in alias names'
);
assert(
newConfig.includes('alias: gemini-3.2-flash-preview'),
'Should preserve manual Flash preview overrides on built-in alias names'
);
assert(
newConfig.includes('alias: gemini-3.2-pro-preview-customtools'),
'Should still expand compatibility aliases for preserved manual Pro override'
);
assert(
newConfig.includes('alias: gemini-3.2-flash-preview-customtools'),
'Should still expand compatibility aliases for preserved manual Flash override'
);
const lines = newConfig.split('\n');
for (let i = 0; i < lines.length; i++) {
if (lines[i].includes('alias: gemini-3.2-flash-preview')) {
assert(
lines[i + 1] && lines[i + 1].trim() === 'fork: true',
'Should preserve fork: true on manual Flash preview override'
);
break;
}
}
});
it('preserves user-added aliases with fork during regeneration', () => {
const cliproxyDir = path.join(testDir, '.ccs', 'cliproxy');
fs.mkdirSync(cliproxyDir, { recursive: true });