mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-03 06:18:55 +00:00
fix(cliproxy): narrow legacy gemini alias cleanup
This commit is contained in:
@@ -37,8 +37,9 @@ export const CCS_CONTROL_PANEL_SECRET = 'ccs';
|
|||||||
* v13: Removed aggressive Gemini alias expansion to reduce model list noise in Control Panel
|
* 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
|
* v14: Added Gemini 3.1 Flash Antigravity aliases for upcoming rollout compatibility
|
||||||
* v15: Prune stale generated Antigravity Gemini preview aliases during regeneration
|
* v15: Prune stale generated Antigravity Gemini preview aliases during regeneration
|
||||||
|
* v16: Narrow stale Gemini alias cleanup to broad multi-version guessed ranges
|
||||||
*/
|
*/
|
||||||
export const CLIPROXY_CONFIG_VERSION = 15;
|
export const CLIPROXY_CONFIG_VERSION = 16;
|
||||||
|
|
||||||
interface OAuthModelAliasEntry {
|
interface OAuthModelAliasEntry {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -46,6 +47,11 @@ interface OAuthModelAliasEntry {
|
|||||||
fork?: boolean;
|
fork?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface PreservedAntigravityAliasesResult {
|
||||||
|
yaml: string;
|
||||||
|
prunedLegacyAliasCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
const DEPRECATED_ANTIGRAVITY_ALIAS_PREFIX = 'gemini-claude-';
|
const DEPRECATED_ANTIGRAVITY_ALIAS_PREFIX = 'gemini-claude-';
|
||||||
const UPSTREAM_CLAUDE_ALIAS_PREFIX = 'claude-';
|
const UPSTREAM_CLAUDE_ALIAS_PREFIX = 'claude-';
|
||||||
const DEPRECATED_ANTIGRAVITY_SONNET_46_THINKING_REGEX = /^claude-sonnet-4(?:[.-])6-thinking$/i;
|
const DEPRECATED_ANTIGRAVITY_SONNET_46_THINKING_REGEX = /^claude-sonnet-4(?:[.-])6-thinking$/i;
|
||||||
@@ -75,6 +81,10 @@ const BUILT_IN_GEMINI_ALIAS_NAMES = new Set(
|
|||||||
(entry) => entry.name
|
(entry) => entry.name
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
const MIN_STALE_GUESSED_GEMINI_MINOR_VERSIONS = 2;
|
||||||
|
const MIN_STALE_GUESSED_GEMINI_AVERAGE_VARIANTS_PER_MINOR = 2;
|
||||||
|
const LEGACY_GEMINI_STALE_ALIAS_MIGRATION_VERSION = 16;
|
||||||
|
const MAX_LEGACY_MANUAL_GEMINI_MINOR_VERSION = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get provider configuration
|
* Get provider configuration
|
||||||
@@ -215,6 +225,16 @@ function parseExistingAntigravityAliases(existingAliases: string): OAuthModelAli
|
|||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getConfigVersionFromContent(content: string): number | null {
|
||||||
|
const versionMatch = content.match(/CCS v(\d+)/);
|
||||||
|
if (!versionMatch) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedVersion = Number.parseInt(versionMatch[1], 10);
|
||||||
|
return Number.isNaN(parsedVersion) ? null : parsedVersion;
|
||||||
|
}
|
||||||
|
|
||||||
function toDottedGeminiVersionAlias(alias: string): string | null {
|
function toDottedGeminiVersionAlias(alias: string): string | null {
|
||||||
const match = alias.match(/^(gemini-\d+)-(\d+)(-.+)$/);
|
const match = alias.match(/^(gemini-\d+)-(\d+)(-.+)$/);
|
||||||
if (!match) return null;
|
if (!match) return null;
|
||||||
@@ -315,7 +335,9 @@ function serializeAntigravityAliases(entries: OAuthModelAliasEntry[]): string {
|
|||||||
return lines.join('\n');
|
return lines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLegacyGeneratedGeminiPreviewClusterKey(entry: OAuthModelAliasEntry): string | null {
|
function getLegacyGeneratedGeminiPreviewInfo(
|
||||||
|
entry: OAuthModelAliasEntry
|
||||||
|
): { nameKey: string; minorVersion: string } | null {
|
||||||
if (!BUILT_IN_GEMINI_ALIAS_NAMES.has(entry.name)) {
|
if (!BUILT_IN_GEMINI_ALIAS_NAMES.has(entry.name)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -328,43 +350,100 @@ function getLegacyGeneratedGeminiPreviewClusterKey(entry: OAuthModelAliasEntry):
|
|||||||
const aliasWithoutCustomtools = normalizedAlias.endsWith('-customtools')
|
const aliasWithoutCustomtools = normalizedAlias.endsWith('-customtools')
|
||||||
? normalizedAlias.slice(0, -'-customtools'.length)
|
? normalizedAlias.slice(0, -'-customtools'.length)
|
||||||
: normalizedAlias;
|
: normalizedAlias;
|
||||||
const clusterAlias =
|
const canonicalAlias =
|
||||||
toDottedGeminiVersionAlias(aliasWithoutCustomtools) ?? aliasWithoutCustomtools;
|
toDottedGeminiVersionAlias(aliasWithoutCustomtools) ?? aliasWithoutCustomtools;
|
||||||
|
const minorVersionMatch = canonicalAlias.match(/^gemini-3\.(\d+)(-.+-preview)$/);
|
||||||
return `${sanitizeYamlScalar(entry.name)}\u0000${clusterAlias}`;
|
if (!minorVersionMatch) {
|
||||||
}
|
return null;
|
||||||
|
|
||||||
function extractPreservedAntigravityAliases(existingAliases: string): string {
|
|
||||||
if (!existingAliases.trim()) {
|
|
||||||
return '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsedEntries = parseExistingAntigravityAliases(existingAliases);
|
return {
|
||||||
const legacyGeneratedGeminiClusterCounts = new Map<string, number>();
|
nameKey: sanitizeYamlScalar(entry.name),
|
||||||
|
minorVersion: minorVersionMatch[1],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildLegacyGeneratedGeminiAliasPruneSet(
|
||||||
|
parsedEntries: OAuthModelAliasEntry[]
|
||||||
|
): Set<string> {
|
||||||
|
const aliasKeysByNameAndMinor = new Map<string, Map<string, string[]>>();
|
||||||
|
|
||||||
for (const entry of parsedEntries) {
|
for (const entry of parsedEntries) {
|
||||||
if (entry.fork) {
|
if (entry.fork) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const generatedEntry = GENERATED_ANTIGRAVITY_ALIAS_MAP.get(buildAntigravityAliasKey(entry));
|
const aliasKey = buildAntigravityAliasKey(entry);
|
||||||
if (generatedEntry) {
|
if (GENERATED_ANTIGRAVITY_ALIAS_MAP.has(aliasKey)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const clusterKey = getLegacyGeneratedGeminiPreviewClusterKey(entry);
|
const previewInfo = getLegacyGeneratedGeminiPreviewInfo(entry);
|
||||||
if (!clusterKey) {
|
if (!previewInfo) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
legacyGeneratedGeminiClusterCounts.set(
|
const aliasKeysByMinor = aliasKeysByNameAndMinor.get(previewInfo.nameKey) ?? new Map();
|
||||||
clusterKey,
|
const aliasKeys = aliasKeysByMinor.get(previewInfo.minorVersion) ?? [];
|
||||||
(legacyGeneratedGeminiClusterCounts.get(clusterKey) ?? 0) + 1
|
aliasKeys.push(aliasKey);
|
||||||
);
|
aliasKeysByMinor.set(previewInfo.minorVersion, aliasKeys);
|
||||||
|
aliasKeysByNameAndMinor.set(previewInfo.nameKey, aliasKeysByMinor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const staleAliasKeys = new Set<string>();
|
||||||
|
|
||||||
|
for (const aliasKeysByMinor of aliasKeysByNameAndMinor.values()) {
|
||||||
|
const sortedMinorVersions = [...aliasKeysByMinor.keys()].sort((left, right) => {
|
||||||
|
return Number(left) - Number(right);
|
||||||
|
});
|
||||||
|
const totalAliasCount = sortedMinorVersions.reduce((total, minorVersion) => {
|
||||||
|
return total + (aliasKeysByMinor.get(minorVersion)?.length ?? 0);
|
||||||
|
}, 0);
|
||||||
|
if (sortedMinorVersions.length < MIN_STALE_GUESSED_GEMINI_MINOR_VERSIONS) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
totalAliasCount <
|
||||||
|
sortedMinorVersions.length * MIN_STALE_GUESSED_GEMINI_AVERAGE_VARIANTS_PER_MINOR
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const preservedMinorVersion =
|
||||||
|
Number(sortedMinorVersions[0]) <= MAX_LEGACY_MANUAL_GEMINI_MINOR_VERSION
|
||||||
|
? sortedMinorVersions[0]
|
||||||
|
: null;
|
||||||
|
for (const minorVersion of sortedMinorVersions) {
|
||||||
|
if (minorVersion === preservedMinorVersion) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const aliasKeys = aliasKeysByMinor.get(minorVersion) ?? [];
|
||||||
|
for (const aliasKey of aliasKeys) {
|
||||||
|
staleAliasKeys.add(aliasKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return staleAliasKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractPreservedAntigravityAliases(
|
||||||
|
existingAliases: string,
|
||||||
|
options?: { enableLegacyGeminiStaleCleanup?: boolean }
|
||||||
|
): PreservedAntigravityAliasesResult {
|
||||||
|
if (!existingAliases.trim()) {
|
||||||
|
return { yaml: '', prunedLegacyAliasCount: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedEntries = parseExistingAntigravityAliases(existingAliases);
|
||||||
|
const staleAliasKeys = options?.enableLegacyGeminiStaleCleanup
|
||||||
|
? buildLegacyGeneratedGeminiAliasPruneSet(parsedEntries)
|
||||||
|
: new Set<string>();
|
||||||
|
|
||||||
const preservedEntries = parsedEntries.filter((entry) => {
|
const preservedEntries = parsedEntries.filter((entry) => {
|
||||||
const generatedEntry = GENERATED_ANTIGRAVITY_ALIAS_MAP.get(buildAntigravityAliasKey(entry));
|
const aliasKey = buildAntigravityAliasKey(entry);
|
||||||
|
const generatedEntry = GENERATED_ANTIGRAVITY_ALIAS_MAP.get(aliasKey);
|
||||||
|
|
||||||
if (generatedEntry) {
|
if (generatedEntry) {
|
||||||
return Boolean(entry.fork) && !generatedEntry.fork;
|
return Boolean(entry.fork) && !generatedEntry.fork;
|
||||||
@@ -374,15 +453,24 @@ function extractPreservedAntigravityAliases(existingAliases: string): string {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const clusterKey = getLegacyGeneratedGeminiPreviewClusterKey(entry);
|
return !staleAliasKeys.has(aliasKey);
|
||||||
if (!clusterKey) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (legacyGeneratedGeminiClusterCounts.get(clusterKey) ?? 0) < 3;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return serializeAntigravityAliases(preservedEntries);
|
return {
|
||||||
|
yaml: serializeAntigravityAliases(preservedEntries),
|
||||||
|
prunedLegacyAliasCount: parsedEntries.filter((entry) =>
|
||||||
|
staleAliasKeys.has(buildAntigravityAliasKey(entry))
|
||||||
|
).length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeLegacyGeminiAliasCleanupBackup(configPath: string, existingContent: string): void {
|
||||||
|
const backupPath = `${configPath}.pre-v16-gemini-alias-cleanup.bak`;
|
||||||
|
if (fs.existsSync(backupPath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(backupPath, existingContent, { mode: 0o600 });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -662,9 +750,19 @@ export function regenerateConfig(port: number = CLIPROXY_DEFAULT_PORT): string {
|
|||||||
claudeApiKeySection = extractYamlSection(content, 'claude-api-key');
|
claudeApiKeySection = extractYamlSection(content, 'claude-api-key');
|
||||||
|
|
||||||
// Preserve user customizations while pruning legacy generated Gemini preview noise.
|
// Preserve user customizations while pruning legacy generated Gemini preview noise.
|
||||||
existingAliases = extractPreservedAntigravityAliases(
|
const existingConfigVersion = getConfigVersionFromContent(content);
|
||||||
extractYamlSection(content, 'oauth-model-alias')
|
const preservedAliases = extractPreservedAntigravityAliases(
|
||||||
|
extractYamlSection(content, 'oauth-model-alias'),
|
||||||
|
{
|
||||||
|
enableLegacyGeminiStaleCleanup:
|
||||||
|
existingConfigVersion === null ||
|
||||||
|
existingConfigVersion < LEGACY_GEMINI_STALE_ALIAS_MIGRATION_VERSION,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
existingAliases = preservedAliases.yaml;
|
||||||
|
if (preservedAliases.prunedLegacyAliasCount > 0) {
|
||||||
|
writeLegacyGeminiAliasCleanupBackup(configPath, content);
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Use defaults if reading fails
|
// Use defaults if reading fails
|
||||||
}
|
}
|
||||||
@@ -703,12 +801,10 @@ export function configNeedsRegeneration(): boolean {
|
|||||||
const content = fs.readFileSync(configPath, 'utf-8');
|
const content = fs.readFileSync(configPath, 'utf-8');
|
||||||
|
|
||||||
// Check for version marker
|
// Check for version marker
|
||||||
const versionMatch = content.match(/CCS v(\d+)/);
|
const configVersion = getConfigVersionFromContent(content);
|
||||||
if (!versionMatch) {
|
if (configVersion === null) {
|
||||||
return true; // No version marker = old config
|
return true; // No version marker = old config
|
||||||
}
|
}
|
||||||
|
|
||||||
const configVersion = parseInt(versionMatch[1], 10);
|
|
||||||
return configVersion < CLIPROXY_CONFIG_VERSION;
|
return configVersion < CLIPROXY_CONFIG_VERSION;
|
||||||
} catch {
|
} catch {
|
||||||
return true; // Error reading = regenerate
|
return true; // Error reading = regenerate
|
||||||
|
|||||||
@@ -753,7 +753,202 @@ oauth-model-alias:
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('drops stale generated Gemini preview clusters while preserving true custom aliases', () => {
|
it('drops stale multi-version Gemini preview ranges while preserving true custom aliases', () => {
|
||||||
|
const cliproxyDir = path.join(testDir, '.ccs', 'cliproxy');
|
||||||
|
fs.mkdirSync(cliproxyDir, { recursive: true });
|
||||||
|
const guessedMinors = ['3', '4', '5', '6'];
|
||||||
|
const buildGuessedRange = (name, family) =>
|
||||||
|
guessedMinors
|
||||||
|
.flatMap((minor) => [
|
||||||
|
` - name: ${name}`,
|
||||||
|
` alias: gemini-3.${minor}-${family}-preview`,
|
||||||
|
` - name: ${name}`,
|
||||||
|
` alias: gemini-3.${minor}-${family}-preview-customtools`,
|
||||||
|
` - name: ${name}`,
|
||||||
|
` alias: gemini-3-${minor}-${family}-preview`,
|
||||||
|
` - name: ${name}`,
|
||||||
|
` alias: gemini-3-${minor}-${family}-preview-customtools`,
|
||||||
|
])
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
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:
|
||||||
|
${buildGuessedRange('gemini-3-pro-high', 'pro')}
|
||||||
|
${buildGuessedRange('gemini-3-flash', 'flash')}
|
||||||
|
- 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');
|
||||||
|
for (const minor of guessedMinors) {
|
||||||
|
assert(
|
||||||
|
!newConfig.includes(`alias: gemini-3.${minor}-pro-preview`),
|
||||||
|
`Should remove stale generated Pro aliases for 3.${minor}`
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
!newConfig.includes(`alias: gemini-3-${minor}-flash-preview-customtools`),
|
||||||
|
`Should remove stale generated Flash aliases for 3.${minor}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
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 sparse manual minor-version overrides when a stale guessed range is pruned', () => {
|
||||||
|
const cliproxyDir = path.join(testDir, '.ccs', 'cliproxy');
|
||||||
|
fs.mkdirSync(cliproxyDir, { recursive: true });
|
||||||
|
const guessedMinors = ['3', '4', '5', '6'];
|
||||||
|
const staleRange = guessedMinors
|
||||||
|
.flatMap((minor) => [
|
||||||
|
` - name: gemini-3-pro-high`,
|
||||||
|
` alias: gemini-3.${minor}-pro-preview`,
|
||||||
|
` - name: gemini-3-pro-high`,
|
||||||
|
` alias: gemini-3.${minor}-pro-preview-customtools`,
|
||||||
|
` - name: gemini-3-pro-high`,
|
||||||
|
` alias: gemini-3-${minor}-pro-preview`,
|
||||||
|
` - name: gemini-3-pro-high`,
|
||||||
|
` alias: gemini-3-${minor}-pro-preview-customtools`,
|
||||||
|
])
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
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
|
||||||
|
${staleRange}
|
||||||
|
`;
|
||||||
|
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 sparse manual minor-version override'
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
newConfig.includes('alias: gemini-3.2-pro-preview-customtools'),
|
||||||
|
'Should still expand compatibility aliases for preserved sparse manual override'
|
||||||
|
);
|
||||||
|
for (const minor of guessedMinors) {
|
||||||
|
assert(
|
||||||
|
!newConfig.includes(`alias: gemini-3.${minor}-pro-preview`),
|
||||||
|
`Should prune stale guessed aliases for 3.${minor}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('preserves a manual 3.2 compatibility cluster while pruning higher stale guessed minors', () => {
|
||||||
|
const cliproxyDir = path.join(testDir, '.ccs', 'cliproxy');
|
||||||
|
fs.mkdirSync(cliproxyDir, { recursive: true });
|
||||||
|
const guessedMinors = ['3', '4', '5', '6'];
|
||||||
|
const staleRange = guessedMinors
|
||||||
|
.flatMap((minor) => [
|
||||||
|
` - name: gemini-3-pro-high`,
|
||||||
|
` alias: gemini-3.${minor}-pro-preview`,
|
||||||
|
` - name: gemini-3-pro-high`,
|
||||||
|
` alias: gemini-3.${minor}-pro-preview-customtools`,
|
||||||
|
` - name: gemini-3-pro-high`,
|
||||||
|
` alias: gemini-3-${minor}-pro-preview`,
|
||||||
|
` - name: gemini-3-pro-high`,
|
||||||
|
` alias: gemini-3-${minor}-pro-preview-customtools`,
|
||||||
|
])
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
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-pro-high
|
||||||
|
alias: gemini-3.2-pro-preview-customtools
|
||||||
|
- name: gemini-3-pro-high
|
||||||
|
alias: gemini-3-2-pro-preview
|
||||||
|
- name: gemini-3-pro-high
|
||||||
|
alias: gemini-3-2-pro-preview-customtools
|
||||||
|
${staleRange}
|
||||||
|
`;
|
||||||
|
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 the manual 3.2 compatibility cluster during legacy cleanup'
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
newConfig.includes('alias: gemini-3-2-pro-preview-customtools'),
|
||||||
|
'Should preserve hyphenated manual compatibility aliases for 3.2'
|
||||||
|
);
|
||||||
|
for (const minor of guessedMinors) {
|
||||||
|
assert(
|
||||||
|
!newConfig.includes(`alias: gemini-3.${minor}-pro-preview`),
|
||||||
|
`Should prune higher stale guessed aliases for 3.${minor}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('prunes partially retained guessed ranges during legacy cleanup', () => {
|
||||||
|
const cliproxyDir = path.join(testDir, '.ccs', 'cliproxy');
|
||||||
|
fs.mkdirSync(cliproxyDir, { recursive: true });
|
||||||
|
const guessedMinors = ['3', '4'];
|
||||||
|
const partialRange = guessedMinors
|
||||||
|
.flatMap((minor) => [
|
||||||
|
` - name: gemini-3-pro-high`,
|
||||||
|
` alias: gemini-3.${minor}-pro-preview`,
|
||||||
|
` - name: gemini-3-pro-high`,
|
||||||
|
` alias: gemini-3.${minor}-pro-preview-customtools`,
|
||||||
|
])
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
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:
|
||||||
|
${partialRange}
|
||||||
|
`;
|
||||||
|
fs.writeFileSync(path.join(cliproxyDir, 'config.yaml'), initialConfig);
|
||||||
|
|
||||||
|
regenerateConfig();
|
||||||
|
|
||||||
|
const newConfig = fs.readFileSync(path.join(cliproxyDir, 'config.yaml'), 'utf-8');
|
||||||
|
for (const minor of guessedMinors) {
|
||||||
|
assert(
|
||||||
|
!newConfig.includes(`alias: gemini-3.${minor}-pro-preview`),
|
||||||
|
`Should prune partially retained guessed aliases for 3.${minor}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('preserves single-minor high-version aliases when they are not part of a guessed range', () => {
|
||||||
const cliproxyDir = path.join(testDir, '.ccs', 'cliproxy');
|
const cliproxyDir = path.join(testDir, '.ccs', 'cliproxy');
|
||||||
fs.mkdirSync(cliproxyDir, { recursive: true });
|
fs.mkdirSync(cliproxyDir, { recursive: true });
|
||||||
|
|
||||||
@@ -765,23 +960,9 @@ auth-dir: "${cliproxyDir.replace(/\\/g, '/')}/auth"
|
|||||||
oauth-model-alias:
|
oauth-model-alias:
|
||||||
antigravity:
|
antigravity:
|
||||||
- name: gemini-3-pro-high
|
- name: gemini-3-pro-high
|
||||||
alias: gemini-3.9-pro-preview
|
alias: gemini-3.11-pro-preview
|
||||||
- name: gemini-3-pro-high
|
- name: gemini-3-pro-high
|
||||||
alias: gemini-3.9-pro-preview-customtools
|
alias: gemini-3.11-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);
|
fs.writeFileSync(path.join(cliproxyDir, 'config.yaml'), initialConfig);
|
||||||
|
|
||||||
@@ -789,45 +970,47 @@ oauth-model-alias:
|
|||||||
|
|
||||||
const newConfig = fs.readFileSync(path.join(cliproxyDir, 'config.yaml'), 'utf-8');
|
const newConfig = fs.readFileSync(path.join(cliproxyDir, 'config.yaml'), 'utf-8');
|
||||||
assert(
|
assert(
|
||||||
!newConfig.includes('alias: gemini-3.9-pro-preview'),
|
newConfig.includes('alias: gemini-3.11-pro-preview'),
|
||||||
'Should remove stale generated Pro preview alias noise'
|
'Should preserve lone manual high-version aliases during legacy cleanup'
|
||||||
);
|
);
|
||||||
assert(
|
assert(
|
||||||
!newConfig.includes('alias: gemini-3.9-pro-preview-customtools'),
|
newConfig.includes('alias: gemini-3.11-pro-preview-customtools'),
|
||||||
'Should remove stale generated Pro customtools alias noise'
|
'Should still expand compatibility aliases for preserved high-version aliases'
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('writes a one-time backup before pruning legacy Gemini aliases during migration', () => {
|
||||||
|
const cliproxyDir = path.join(testDir, '.ccs', 'cliproxy');
|
||||||
|
fs.mkdirSync(cliproxyDir, { recursive: true });
|
||||||
|
const configPath = path.join(cliproxyDir, 'config.yaml');
|
||||||
|
const guessedMinors = ['3', '4', '5'];
|
||||||
|
const staleRange = guessedMinors
|
||||||
|
.flatMap((minor) => [
|
||||||
|
` - name: gemini-3-pro-high`,
|
||||||
|
` alias: gemini-3.${minor}-pro-preview`,
|
||||||
|
` - name: gemini-3-pro-high`,
|
||||||
|
` alias: gemini-3.${minor}-pro-preview-customtools`,
|
||||||
|
])
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
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:
|
||||||
|
${staleRange}
|
||||||
|
`;
|
||||||
|
fs.writeFileSync(configPath, initialConfig);
|
||||||
|
|
||||||
|
regenerateConfig();
|
||||||
|
|
||||||
|
const backupPath = `${configPath}.pre-v16-gemini-alias-cleanup.bak`;
|
||||||
|
assert(fs.existsSync(backupPath), 'Should keep a backup of the legacy config before pruning');
|
||||||
assert(
|
assert(
|
||||||
!newConfig.includes('alias: gemini-3-9-pro-preview'),
|
fs.readFileSync(backupPath, 'utf-8').includes('gemini-3.5-pro-preview'),
|
||||||
'Should remove stale generated hyphenated Pro alias noise'
|
'Should preserve the pruned legacy alias in the migration backup'
|
||||||
);
|
|
||||||
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'
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -882,6 +1065,86 @@ oauth-model-alias:
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('preserves manual Gemini preview compatibility clusters 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-pro-high
|
||||||
|
alias: gemini-3.2-pro-preview-customtools
|
||||||
|
- name: gemini-3-pro-high
|
||||||
|
alias: gemini-3-2-pro-preview
|
||||||
|
- name: gemini-3-pro-high
|
||||||
|
alias: gemini-3-2-pro-preview-customtools
|
||||||
|
`;
|
||||||
|
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 dotted Pro preview override'
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
newConfig.includes('alias: gemini-3.2-pro-preview-customtools'),
|
||||||
|
'Should preserve manual dotted Pro customtools override'
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
newConfig.includes('alias: gemini-3-2-pro-preview'),
|
||||||
|
'Should preserve manual hyphenated Pro preview override'
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
newConfig.includes('alias: gemini-3-2-pro-preview-customtools'),
|
||||||
|
'Should preserve manual hyphenated Pro customtools override'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not re-prune multi-version manual clusters once the config is already migrated', () => {
|
||||||
|
const cliproxyDir = path.join(testDir, '.ccs', 'cliproxy');
|
||||||
|
fs.mkdirSync(cliproxyDir, { recursive: true });
|
||||||
|
|
||||||
|
const initialConfig = `# CLIProxyAPI config generated by CCS v16
|
||||||
|
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-pro-high
|
||||||
|
alias: gemini-3.3-pro-preview
|
||||||
|
- name: gemini-3-pro-high
|
||||||
|
alias: gemini-3.4-pro-preview
|
||||||
|
`;
|
||||||
|
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 aliases after legacy migration has already run'
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
newConfig.includes('alias: gemini-3.3-pro-preview'),
|
||||||
|
'Should not re-prune later manual version clusters on v16+ configs'
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
newConfig.includes('alias: gemini-3.4-pro-preview'),
|
||||||
|
'Should leave already-migrated multi-version manual aliases untouched'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('preserves user-added aliases with fork during regeneration', () => {
|
it('preserves user-added aliases with fork during regeneration', () => {
|
||||||
const cliproxyDir = path.join(testDir, '.ccs', 'cliproxy');
|
const cliproxyDir = path.join(testDir, '.ccs', 'cliproxy');
|
||||||
fs.mkdirSync(cliproxyDir, { recursive: true });
|
fs.mkdirSync(cliproxyDir, { recursive: true });
|
||||||
|
|||||||
Reference in New Issue
Block a user