refactor(cliproxy): remove model alias functionality

- delete model-alias-config.ts and cliproxy-alias-handler.ts

- simplify sync to use ANTHROPIC_MODEL directly

- remove alias routes, hooks, and UI components
This commit is contained in:
kaitranntt committed 2026-01-28 11:57:22 -05:00
1 parent afa3bdafce
commit 32dbd5e174
12 files changed
+92 -775

No files matched your search

+1 -9
View File
@@ -180,7 +180,7 @@ export type {
export { ManagementApiClient, createManagementClient } from './management-api-client';
// Sync module (profile sync to remote CLIProxy)
export type { SyncableProfile, SyncPreviewItem, ModelAlias, ModelAliasConfig } from './sync';
export type { SyncableProfile, SyncPreviewItem } from './sync';
export {
loadSyncableProfiles,
mapProfileToClaudeKey,
@@ -188,12 +188,4 @@ export {
generateSyncPreview,
getSyncableProfileCount,
isProfileSyncable,
getModelAliasesPath,
loadModelAliases,
saveModelAliases,
getProfileAliases,
addProfileAlias,
removeProfileAlias,
listAllAliases,
DEFAULT_MODEL_ALIASES,
} from './sync';
-13
View File
@@ -15,19 +15,6 @@ export {
isProfileSyncable,
} from './profile-mapper';
// Model alias config
export type { ModelAlias, ModelAliasConfig } from './model-alias-config';
export {
getModelAliasesPath,
loadModelAliases,
saveModelAliases,
getProfileAliases,
addProfileAlias,
removeProfileAlias,
listAllAliases,
DEFAULT_MODEL_ALIASES,
} from './model-alias-config';
// Local config sync
export { syncToLocalConfig, getLocalSyncStatus } from './local-config-sync';
+2 -1
View File
@@ -122,10 +122,11 @@ function transformToConfigFormat(key: ClaudeKey): Record<string, unknown> {
// Add empty proxy-url (required by CLIProxyAPI)
entry['proxy-url'] = '';
// Use model name directly (no alias mapping)
if (key.models && key.models.length > 0) {
entry.models = key.models.map((m) => ({
name: m.name,
alias: m.alias || '',
alias: '',
}));
}
-176
View File
@@ -1,176 +0,0 @@
/**
* Model Alias Configuration
*
* Manages model alias mappings for CLIProxy sync.
* Aliases map Claude model names to provider-specific models.
*/
import * as fs from 'fs';
import * as path from 'path';
import { getCcsDir } from '../../utils/config-manager';
/** Model alias mapping */
export interface ModelAlias {
/** Claude model name (e.g., "claude-3-5-sonnet") */
from: string;
/** Target model (e.g., "glm-4.7-airx-thinking") */
to: string;
}
/** Model alias configuration file structure */
export interface ModelAliasConfig {
/** Version for future schema changes */
version: number;
/** Aliases for each profile name */
aliases: Record<string, ModelAlias[]>;
}
/** Default model aliases (common mappings) */
export const DEFAULT_MODEL_ALIASES: Record<string, ModelAlias[]> = {
glm: [
{ from: 'claude-sonnet-4-20250514', to: 'glm-4.7-thinking' },
{ from: 'claude-3-5-sonnet-20241022', to: 'glm-4.7' },
{ from: 'claude-3-5-haiku-20241022', to: 'glm-4.7-flash' },
],
kimi: [
{ from: 'claude-sonnet-4-20250514', to: 'moonshot-v1-auto' },
{ from: 'claude-3-5-sonnet-20241022', to: 'moonshot-v1-128k' },
{ from: 'claude-3-5-haiku-20241022', to: 'moonshot-v1-32k' },
],
qwen: [
{ from: 'claude-sonnet-4-20250514', to: 'qwen-coder-plus' },
{ from: 'claude-3-5-sonnet-20241022', to: 'qwen-plus' },
{ from: 'claude-3-5-haiku-20241022', to: 'qwen-turbo' },
],
};
/**
* Get path to model aliases config file.
*/
export function getModelAliasesPath(): string {
return path.join(getCcsDir(), 'cliproxy', 'model-aliases.json');
}
/**
* Load model alias configuration.
* Returns defaults if file doesn't exist.
*/
export function loadModelAliases(): ModelAliasConfig {
const aliasPath = getModelAliasesPath();
try {
if (fs.existsSync(aliasPath)) {
const content = fs.readFileSync(aliasPath, 'utf8');
const config = JSON.parse(content) as ModelAliasConfig;
return config;
}
} catch {
// Fall through to defaults
}
// Return defaults
return {
version: 1,
aliases: { ...DEFAULT_MODEL_ALIASES },
};
}
/**
* Save model alias configuration.
* @returns Success status with optional error message
*/
export function saveModelAliases(config: ModelAliasConfig): { success: boolean; error?: string } {
try {
const aliasPath = getModelAliasesPath();
const dir = path.dirname(aliasPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(aliasPath, JSON.stringify(config, null, 2), 'utf8');
return { success: true };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : String(error),
};
}
}
/**
* Get aliases for a specific profile.
*/
export function getProfileAliases(profileName: string): ModelAlias[] {
const config = loadModelAliases();
return config.aliases[profileName] ?? [];
}
/**
* Add an alias for a profile.
*/
export function addProfileAlias(profileName: string, from: string, to: string): void {
// Validate inputs
if (!from || from.trim() === '') {
throw new Error('Model alias "from" cannot be empty');
}
if (!to || to.trim() === '') {
throw new Error('Model alias "to" cannot be empty');
}
const config = loadModelAliases();
if (!config.aliases[profileName]) {
config.aliases[profileName] = [];
}
// Check if alias already exists (update if so)
const existingIdx = config.aliases[profileName].findIndex((a) => a.from === from);
if (existingIdx >= 0) {
config.aliases[profileName][existingIdx].to = to;
} else {
config.aliases[profileName].push({ from, to });
}
const result = saveModelAliases(config);
if (!result.success) {
throw new Error(`Failed to save model aliases: ${result.error}`);
}
}
/**
* Remove an alias for a profile.
*/
export function removeProfileAlias(profileName: string, from: string): boolean {
const config = loadModelAliases();
if (!config.aliases[profileName]) {
return false;
}
const initialLen = config.aliases[profileName].length;
config.aliases[profileName] = config.aliases[profileName].filter((a) => a.from !== from);
if (config.aliases[profileName].length === initialLen) {
return false;
}
// Remove empty profile entry
if (config.aliases[profileName].length === 0) {
delete config.aliases[profileName];
}
const result = saveModelAliases(config);
if (!result.success) {
throw new Error(`Failed to save model aliases: ${result.error}`);
}
return true;
}
/**
* List all aliases.
*/
export function listAllAliases(): Record<string, ModelAlias[]> {
const config = loadModelAliases();
return config.aliases;
}
+13 -27
View File
@@ -2,15 +2,13 @@
* Profile Mapper for CLIProxy Sync
*
* Transforms CCS settings-based profiles into CLIProxy ClaudeKey format.
* Handles model alias mapping and settings normalization.
*/
import * as fs from 'fs';
import * as path from 'path';
import { getCcsDir } from '../../utils/config-manager';
import { listApiProfiles, isApiProfileConfigured } from '../../api/services/profile-reader';
import type { ClaudeKey, ClaudeModel } from '../management-api-types';
import { getProfileAliases, type ModelAlias } from './model-alias-config';
import type { ClaudeKey } from '../management-api-types';
/**
* Profile info with settings for sync.
@@ -80,16 +78,6 @@ export function loadSyncableProfiles(): SyncableProfile[] {
return syncable;
}
/**
* Map model aliases to ClaudeModel format.
*/
function mapAliasesToClaudeModels(aliases: ModelAlias[]): ClaudeModel[] {
return aliases.map((alias) => ({
name: alias.from,
alias: alias.to,
}));
}
/**
* Sanitize profile name for YAML safety.
* Replaces non-alphanumeric chars (except - and _) with hyphens.
@@ -109,6 +97,7 @@ export function mapProfileToClaudeKey(profile: SyncableProfile): ClaudeKey | nul
if (!apiKey) return null;
const baseUrl = env.ANTHROPIC_BASE_URL;
const modelName = env.ANTHROPIC_MODEL;
// Generate prefix from profile name (e.g., "glm" -> "glm-")
const sanitizedName = sanitizeProfileName(profile.name);
@@ -117,10 +106,6 @@ export function mapProfileToClaudeKey(profile: SyncableProfile): ClaudeKey | nul
}
const prefix = `${sanitizedName}-`;
// Load model aliases for this profile
const aliases = getProfileAliases(profile.name);
const models = aliases.length > 0 ? mapAliasesToClaudeModels(aliases) : undefined;
const claudeKey: ClaudeKey = {
'api-key': apiKey,
prefix,
@@ -130,8 +115,14 @@ export function mapProfileToClaudeKey(profile: SyncableProfile): ClaudeKey | nul
claudeKey['base-url'] = baseUrl;
}
if (models && models.length > 0) {
claudeKey.models = models;
// Use model name directly from profile (no alias mapping)
if (modelName) {
claudeKey.models = [
{
name: modelName,
alias: '',
},
];
}
return claudeKey;
@@ -164,10 +155,8 @@ export interface SyncPreviewItem {
name: string;
/** Base URL (masked) */
baseUrl?: string;
/** Whether profile has model aliases */
hasAliases: boolean;
/** Number of model aliases */
aliasCount: number;
/** Model name */
modelName?: string;
}
export function generateSyncPreview(): SyncPreviewItem[] {
@@ -175,13 +164,10 @@ export function generateSyncPreview(): SyncPreviewItem[] {
const preview: SyncPreviewItem[] = [];
for (const profile of profiles) {
const aliases = getProfileAliases(profile.name);
preview.push({
name: profile.name,
baseUrl: profile.env?.ANTHROPIC_BASE_URL,
hasAliases: aliases.length > 0,
aliasCount: aliases.length,
modelName: profile.env?.ANTHROPIC_MODEL,
});
}
-226
View File
@@ -1,226 +0,0 @@
/**
* CLIProxy Alias Command Handler
*
* Handles `ccs cliproxy alias` commands for managing model alias mappings.
*/
import { addProfileAlias, removeProfileAlias, listAllAliases } from '../cliproxy/sync';
import { initUI, header, subheader, color, dim, ok, fail, warn, info, table } from '../utils/ui';
interface AliasArgs {
subcommand: 'add' | 'remove' | 'list' | 'help' | null;
profile?: string;
from?: string;
to?: string;
}
/**
* Parse alias command arguments.
*/
export function parseAliasArgs(args: string[]): AliasArgs {
const rawCommand = args[0];
if (!rawCommand || rawCommand === 'help' || args.includes('--help')) {
return { subcommand: 'help' };
}
if (rawCommand === 'list' || rawCommand === 'ls') {
return { subcommand: 'list', profile: args[1] };
}
if (rawCommand === 'add') {
// ccs cliproxy alias add <profile> <from> <to>
return {
subcommand: 'add',
profile: args[1],
from: args[2],
to: args[3],
};
}
if (rawCommand === 'remove' || rawCommand === 'rm' || rawCommand === 'delete') {
// ccs cliproxy alias remove <profile> <from>
return {
subcommand: 'remove',
profile: args[1],
from: args[2],
};
}
return { subcommand: null };
}
/**
* Show alias command help.
*/
async function showAliasHelp(): Promise<void> {
await initUI();
console.log('');
console.log(header('Model Alias Management'));
console.log('');
console.log(subheader('Usage:'));
console.log(` ${color('ccs cliproxy alias', 'command')} <command> [args]`);
console.log('');
console.log(subheader('Commands:'));
const commands: [string, string][] = [
['list [profile]', 'List all aliases (or for specific profile)'],
['add <profile> <from> <to>', 'Add model alias mapping'],
['remove <profile> <from>', 'Remove model alias mapping'],
];
const maxLen = Math.max(...commands.map(([cmd]) => cmd.length));
for (const [cmd, desc] of commands) {
console.log(` ${color(cmd.padEnd(maxLen + 2), 'command')} ${desc}`);
}
console.log('');
console.log(subheader('Examples:'));
console.log(` ${dim('# List all aliases')}`);
console.log(` ${color('ccs cliproxy alias list', 'command')}`);
console.log('');
console.log(` ${dim('# Add alias for GLM profile')}`);
console.log(
` ${color('ccs cliproxy alias add glm claude-sonnet-4-20250514 glm-4.7-thinking', 'command')}`
);
console.log('');
console.log(` ${dim('# Remove alias')}`);
console.log(` ${color('ccs cliproxy alias remove glm claude-sonnet-4-20250514', 'command')}`);
console.log('');
}
/**
* Handle `ccs cliproxy alias list` command.
*/
async function handleAliasList(profile?: string): Promise<void> {
await initUI();
const allAliases = listAllAliases();
if (Object.keys(allAliases).length === 0) {
console.log(info('No model aliases configured'));
console.log('');
console.log('Add aliases with:');
console.log(` ${color('ccs cliproxy alias add <profile> <from> <to>', 'command')}`);
console.log('');
return;
}
// Filter to specific profile if provided
const profiles = profile ? { [profile]: allAliases[profile] } : allAliases;
if (profile && !allAliases[profile]) {
console.log(warn(`No aliases found for profile: ${profile}`));
console.log('');
return;
}
console.log(header('Model Aliases'));
console.log('');
for (const [profileName, aliases] of Object.entries(profiles)) {
if (!aliases || aliases.length === 0) continue;
console.log(subheader(profileName));
const rows = aliases.map((a) => [a.from, color('->', 'info'), a.to]);
console.log(
table(rows, { head: ['Claude Model', '', 'Target Model'], colWidths: [35, 4, 30] })
);
console.log('');
}
}
/**
* Handle `ccs cliproxy alias add` command.
*/
async function handleAliasAdd(profile: string, from: string, to: string): Promise<void> {
await initUI();
if (!profile || !from || !to) {
console.log(fail('Missing required arguments'));
console.log('');
console.log('Usage:');
console.log(` ${color('ccs cliproxy alias add <profile> <from> <to>', 'command')}`);
console.log('');
console.log('Example:');
console.log(
` ${color('ccs cliproxy alias add glm claude-sonnet-4-20250514 glm-4.7-thinking', 'command')}`
);
console.log('');
process.exit(1);
}
addProfileAlias(profile, from, to);
console.log(ok(`Added alias for ${profile}`));
console.log(` ${from} -> ${to}`);
console.log('');
console.log(info('Run sync to apply changes:'));
console.log(` ${color('ccs cliproxy sync', 'command')}`);
console.log('');
}
/**
* Handle `ccs cliproxy alias remove` command.
*/
async function handleAliasRemove(profile: string, from: string): Promise<void> {
await initUI();
if (!profile || !from) {
console.log(fail('Missing required arguments'));
console.log('');
console.log('Usage:');
console.log(` ${color('ccs cliproxy alias remove <profile> <from>', 'command')}`);
console.log('');
process.exit(1);
}
const removed = removeProfileAlias(profile, from);
if (!removed) {
console.log(warn(`Alias not found: ${profile}/${from}`));
console.log('');
return;
}
console.log(ok(`Removed alias from ${profile}`));
console.log(` ${from}`);
console.log('');
console.log(info('Run sync to apply changes:'));
console.log(` ${color('ccs cliproxy sync', 'command')}`);
console.log('');
}
/**
* Handle `ccs cliproxy alias` command router.
*/
export async function handleAlias(args: string[]): Promise<void> {
const parsed = parseAliasArgs(args);
switch (parsed.subcommand) {
case 'list':
await handleAliasList(parsed.profile);
break;
case 'add':
if (parsed.profile && parsed.from && parsed.to) {
await handleAliasAdd(parsed.profile, parsed.from, parsed.to);
} else {
await handleAliasAdd('', '', ''); // Will show error message
}
break;
case 'remove':
if (parsed.profile && parsed.from) {
await handleAliasRemove(parsed.profile, parsed.from);
} else {
await handleAliasRemove('', ''); // Will show error message
}
break;
case 'help':
default:
await showAliasHelp();
break;
}
}
+1 -10
View File
@@ -64,9 +64,8 @@ import {
installLatest,
} from '../cliproxy/services';
// Import sync and alias handlers
// Import sync handler
import { handleSync } from './cliproxy-sync-handler';
import { handleAlias } from './cliproxy-alias-handler';
// ============================================================================
// ARGUMENT PARSING
@@ -623,9 +622,6 @@ async function showHelp(): Promise<void> {
['sync', 'Sync API profiles to remote CLIProxy'],
['sync --dry-run', 'Preview sync without applying'],
['sync --force', 'Sync without confirmation prompt'],
['alias list', 'List model alias mappings'],
['alias add <profile> <from> <to>', 'Add model alias'],
['alias remove <profile> <from>', 'Remove model alias'],
],
],
[
@@ -1024,11 +1020,6 @@ export async function handleCliproxyCommand(args: string[]): Promise<void> {
return;
}
if (command === 'alias') {
await handleAlias(remainingArgs.slice(1));
return;
}
if (command === 'stop') {
await handleStop();
return;
+3 -3
View File
@@ -61,12 +61,12 @@ export async function handleSync(args: string[]): Promise<void> {
console.log('');
const rows = preview.map((p) => {
const aliases = p.hasAliases ? color(`${p.aliasCount} aliases`, 'info') : dim('none');
const model = p.modelName ? color(p.modelName, 'info') : dim('default');
const url = p.baseUrl ? dim(p.baseUrl) : dim('-');
return [p.name, url, aliases];
return [p.name, url, model];
});
console.log(table(rows, { head: ['Profile', 'Base URL', 'Aliases'], colWidths: [15, 40, 15] }));
console.log(table(rows, { head: ['Profile', 'Base URL', 'Model'], colWidths: [15, 40, 20] }));
console.log('');
if (parsed.verbose) {
@@ -7,9 +7,6 @@ import { loadOrCreateUnifiedConfig } from '../../config/unified-config-loader';
import {
generateSyncPayload,
generateSyncPreview,
listAllAliases,
addProfileAlias,
removeProfileAlias,
getAutoSyncStatus,
restartAutoSyncWatcher,
syncToLocalConfig,
@@ -104,68 +101,6 @@ router.post('/', async (_req: Request, res: Response): Promise<void> => {
}
});
// ==================== Model Aliases ====================
/**
* GET /api/cliproxy/sync/aliases - List all model aliases
* Returns: { aliases: Record<string, ModelAlias[]> }
*/
router.get('/aliases', async (_req: Request, res: Response): Promise<void> => {
try {
const aliases = listAllAliases();
res.json({ aliases });
} catch (error) {
res.status(500).json({ error: (error as Error).message });
}
});
/**
* POST /api/cliproxy/sync/aliases - Add a model alias
* Body: { profile: string, from: string, to: string }
* Returns: { success: true }
*/
router.post('/aliases', async (req: Request, res: Response): Promise<void> => {
try {
const { profile, from, to } = req.body;
const trimmedProfile = typeof profile === 'string' ? profile.trim() : '';
const trimmedFrom = typeof from === 'string' ? from.trim() : '';
const trimmedTo = typeof to === 'string' ? to.trim() : '';
if (!trimmedProfile || !trimmedFrom || !trimmedTo) {
res.status(400).json({ error: 'Missing required fields: profile, from, to' });
return;
}
addProfileAlias(trimmedProfile, trimmedFrom, trimmedTo);
res.json({ success: true, profile: trimmedProfile, from: trimmedFrom, to: trimmedTo });
} catch (error) {
res.status(500).json({ error: (error as Error).message });
}
});
/**
* DELETE /api/cliproxy/sync/aliases - Remove a model alias
* Body: { profile: string, from: string }
* Returns: { success: boolean }
*/
router.delete('/aliases', async (req: Request, res: Response): Promise<void> => {
try {
const { profile, from } = req.body;
const trimmedProfile = typeof profile === 'string' ? profile.trim() : '';
const trimmedFrom = typeof from === 'string' ? from.trim() : '';
if (!trimmedProfile || !trimmedFrom) {
res.status(400).json({ error: 'Missing required fields: profile, from' });
return;
}
const removed = removeProfileAlias(trimmedProfile, trimmedFrom);
res.json({ success: removed, profile: trimmedProfile, from: trimmedFrom });
} catch (error) {
res.status(500).json({ error: (error as Error).message });
}
});
// ==================== Auto-Sync ====================
/**