feat(cliproxy): persist target on variants

- add target to variant create/update flows for single and composite modes

- persist target in unified config and legacy cliproxy entries

- include target in variant list/remove return payloads
This commit is contained in:
Tam Nhu Tran
2026-02-25 15:52:05 +07:00
parent 3dacb39deb
commit f4a6927293
2 changed files with 63 additions and 49 deletions
+29 -40
View File
@@ -1,12 +1,7 @@
/**
* CLIProxy Variant Config Adapters
*
* Handles reading/writing variant config in both unified and legacy formats.
*/
import * as fs from 'fs';
import { getConfigPath, loadConfigSafe } from '../../utils/config-manager';
import { CLIProxyProvider } from '../types';
import type { TargetType } from '../../targets/target-adapter';
import {
CLIProxyVariantConfig,
CompositeVariantConfig,
@@ -20,19 +15,15 @@ import {
} from '../../config/unified-config-loader';
import { CLIPROXY_DEFAULT_PORT } from '../config-generator';
/** First port for variant profiles (8318 = default + 1) */
export const VARIANT_PORT_BASE = CLIPROXY_DEFAULT_PORT + 1;
/** Maximum port offset for variants (100 ports: 8318-8417) */
export const VARIANT_PORT_MAX_OFFSET = 100;
/** Variant configuration structure */
export interface VariantConfig {
provider: string;
settings?: string;
account?: string;
model?: string;
port?: number;
target?: TargetType;
/** Composite variant fields */
type?: 'composite';
default_tier?: 'opus' | 'sonnet' | 'haiku';
@@ -45,9 +36,6 @@ export interface VariantConfig {
hasFallback?: boolean;
}
/**
* Check if variant exists in config
*/
export function variantExistsInConfig(name: string): boolean {
try {
if (isUnifiedMode()) {
@@ -61,10 +49,6 @@ export function variantExistsInConfig(name: string): boolean {
}
}
/**
* Get next available port for a new variant.
* Scans existing variants, returns first unused port starting from VARIANT_PORT_BASE.
*/
export function getNextAvailablePort(): number {
const variants = listVariantsFromConfig();
const usedPorts = new Set<number>();
@@ -89,9 +73,6 @@ export function getNextAvailablePort(): number {
);
}
/**
* List variants from config
*/
export function listVariantsFromConfig(): Record<string, VariantConfig> {
try {
if (isUnifiedMode()) {
@@ -139,6 +120,7 @@ export function listVariantsFromConfig(): Record<string, VariantConfig> {
provider: defaultTierConfig.provider,
settings: composite.settings,
port: composite.port,
target: composite.target || 'claude',
type: 'composite',
default_tier: composite.default_tier,
tiers: normalizedTiers,
@@ -151,6 +133,7 @@ export function listVariantsFromConfig(): Record<string, VariantConfig> {
settings: single.settings,
account: single.account,
port: single.port,
target: single.target || 'claude',
};
}
} catch (error) {
@@ -171,12 +154,14 @@ export function listVariantsFromConfig(): Record<string, VariantConfig> {
settings: string;
account?: string;
port?: number;
target?: TargetType;
};
result[name] = {
provider: v.provider,
settings: v.settings,
account: v.account,
port: v.port,
target: v.target || 'claude',
};
}
return result;
@@ -185,9 +170,6 @@ export function listVariantsFromConfig(): Record<string, VariantConfig> {
}
}
/**
* Save composite variant to unified config
*/
export function saveCompositeVariantUnified(name: string, config: CompositeVariantConfig): void {
const unifiedConfig = loadOrCreateUnifiedConfig();
@@ -206,15 +188,13 @@ export function saveCompositeVariantUnified(name: string, config: CompositeVaria
saveUnifiedConfig(unifiedConfig);
}
/**
* Save variant to unified config
*/
export function saveVariantUnified(
name: string,
provider: CLIProxyProvider,
settingsPath: string,
account?: string,
port?: number
port?: number,
target: TargetType = 'claude'
): void {
const config = loadOrCreateUnifiedConfig();
@@ -234,20 +214,19 @@ export function saveVariantUnified(
account,
settings: settingsPath,
port,
...(target !== 'claude' && { target }),
};
saveUnifiedConfig(config);
}
/**
* Save variant to legacy JSON config
*/
export function saveVariantLegacy(
name: string,
provider: string,
settingsPath: string,
account?: string,
port?: number
port?: number,
target: TargetType = 'claude'
): void {
const configPath = getConfigPath();
@@ -262,7 +241,13 @@ export function saveVariantLegacy(
config.cliproxy = {};
}
const variantConfig: { provider: string; settings: string; account?: string; port?: number } = {
const variantConfig: {
provider: string;
settings: string;
account?: string;
port?: number;
target?: TargetType;
} = {
provider,
settings: settingsPath,
};
@@ -272,6 +257,9 @@ export function saveVariantLegacy(
if (port) {
variantConfig.port = port;
}
if (target !== 'claude') {
variantConfig.target = target;
}
config.cliproxy[name] = variantConfig;
const tempPath = configPath + '.tmp';
@@ -279,9 +267,6 @@ export function saveVariantLegacy(
fs.renameSync(tempPath, configPath);
}
/**
* Remove variant from unified config
*/
export function removeVariantFromUnifiedConfig(name: string): VariantConfig | null {
const config = loadOrCreateUnifiedConfig();
@@ -299,6 +284,7 @@ export function removeVariantFromUnifiedConfig(name: string): VariantConfig | nu
provider: composite.tiers[composite.default_tier].provider,
settings: composite.settings,
port: composite.port,
target: composite.target || 'claude',
type: 'composite',
default_tier: composite.default_tier,
tiers: composite.tiers,
@@ -309,12 +295,10 @@ export function removeVariantFromUnifiedConfig(name: string): VariantConfig | nu
provider: singleVariant.provider,
settings: singleVariant.settings,
port: singleVariant.port,
target: singleVariant.target || 'claude',
};
}
/**
* Remove variant from legacy JSON config
*/
export function removeVariantFromLegacyConfig(name: string): VariantConfig | null {
const configPath = getConfigPath();
@@ -329,7 +313,12 @@ export function removeVariantFromLegacyConfig(name: string): VariantConfig | nul
return null;
}
const variant = config.cliproxy[name] as { provider: string; settings: string; port?: number };
const variant = config.cliproxy[name] as {
provider: string;
settings: string;
port?: number;
target?: TargetType;
};
delete config.cliproxy[name];
if (Object.keys(config.cliproxy).length === 0) {
+34 -9
View File
@@ -10,6 +10,7 @@ import * as path from 'path';
import { CLIProxyProfileName } from '../../auth/profile-detector';
import { CLIProxyProvider, CLIProxyBackend, PLUS_ONLY_PROVIDERS } from '../types';
import { CompositeTierConfig, CompositeVariantConfig } from '../../config/unified-config-types';
import type { TargetType } from '../../targets/target-adapter';
import { isReservedName, isWindowsReservedName } from '../../config/reserved-names';
import { loadOrCreateUnifiedConfig } from '../../config/unified-config-loader';
import { DEFAULT_BACKEND } from '../platform-detector';
@@ -110,7 +111,8 @@ export function createVariant(
name: string,
provider: CLIProxyProfileName,
model: string,
account?: string
account?: string,
target: TargetType = 'claude'
): VariantOperationResult {
try {
// Validate provider/backend compatibility (block kiro/ghcp on original backend)
@@ -131,17 +133,25 @@ export function createVariant(
provider as CLIProxyProvider,
getRelativeSettingsPath(provider, name),
account,
port
port,
target
);
} else {
settingsPath = createSettingsFile(name, provider, model, port);
saveVariantLegacy(name, provider, `~/.ccs/${path.basename(settingsPath)}`, account, port);
saveVariantLegacy(
name,
provider,
`~/.ccs/${path.basename(settingsPath)}`,
account,
port,
target
);
}
return {
success: true,
settingsPath,
variant: { provider, model, account, port },
variant: { provider, model, account, port, target },
};
} catch (error) {
return {
@@ -208,6 +218,7 @@ export interface UpdateVariantOptions {
provider?: CLIProxyProfileName;
account?: string;
model?: string;
target?: TargetType;
}
/**
@@ -233,6 +244,8 @@ export function updateVariant(name: string, updates: UpdateVariantOptions): Vari
const providerChanged =
updates.provider !== undefined && updates.provider !== existing.provider;
const existingTarget = existing.target || 'claude';
const targetChanged = updates.target !== undefined && updates.target !== existingTarget;
const hasModelUpdate = updates.model !== undefined && updates.model.trim().length > 0;
if (providerChanged && !hasModelUpdate) {
@@ -257,8 +270,8 @@ export function updateVariant(name: string, updates: UpdateVariantOptions): Vari
}
}
// Update config entry if provider or account changed
if (updates.provider !== undefined || updates.account !== undefined) {
// Update config entry if provider/account/target changed
if (updates.provider !== undefined || updates.account !== undefined || targetChanged) {
const newProvider = updates.provider ?? existing.provider;
// Validate provider/backend compatibility on provider change
@@ -269,6 +282,7 @@ export function updateVariant(name: string, updates: UpdateVariantOptions): Vari
}
}
const newAccount = updates.account !== undefined ? updates.account : existing.account;
const newTarget = updates.target ?? existingTarget;
if (isUnifiedMode()) {
saveVariantUnified(
@@ -276,7 +290,8 @@ export function updateVariant(name: string, updates: UpdateVariantOptions): Vari
newProvider as CLIProxyProvider,
existing.settings || '',
newAccount || undefined,
existing.port
existing.port,
newTarget
);
} else {
saveVariantLegacy(
@@ -284,7 +299,8 @@ export function updateVariant(name: string, updates: UpdateVariantOptions): Vari
newProvider,
existing.settings || '',
newAccount || undefined,
existing.port
existing.port,
newTarget
);
}
}
@@ -297,6 +313,7 @@ export function updateVariant(name: string, updates: UpdateVariantOptions): Vari
account: updates.account !== undefined ? updates.account : existing.account,
port: existing.port,
settings: existing.settings,
target: updates.target ?? existingTarget,
},
};
} catch (error) {
@@ -308,6 +325,7 @@ export function updateVariant(name: string, updates: UpdateVariantOptions): Vari
export interface CreateCompositeVariantOptions {
name: string;
defaultTier: 'opus' | 'sonnet' | 'haiku';
target?: TargetType;
tiers: {
opus: CompositeTierConfig;
sonnet: CompositeTierConfig;
@@ -329,7 +347,7 @@ export function createCompositeVariant(
}
try {
const { name, defaultTier, tiers } = options;
const { name, defaultTier, tiers, target = 'claude' } = options;
const validationError = validateCompositeTiers(tiers, {
defaultTier,
@@ -361,6 +379,7 @@ export function createCompositeVariant(
tiers,
settings: settingsPath,
port,
...(target !== 'claude' && { target }),
};
saveCompositeVariantUnified(name, compositeConfig);
@@ -373,6 +392,7 @@ export function createCompositeVariant(
default_tier: defaultTier,
tiers,
port,
target,
},
};
} catch (error) {
@@ -384,6 +404,7 @@ export function createCompositeVariant(
export interface UpdateCompositeVariantOptions {
defaultTier?: 'opus' | 'sonnet' | 'haiku';
tiers?: Partial<Record<'opus' | 'sonnet' | 'haiku', CompositeTierConfig>>;
target?: TargetType;
}
/**
@@ -418,6 +439,8 @@ export function updateCompositeVariant(
};
const newDefaultTier = updates.defaultTier ?? existing.default_tier ?? 'sonnet';
const existingTarget = existing.target || 'claude';
const newTarget = updates.target ?? existingTarget;
const validationError = validateCompositeTiers(mergedTiers, {
defaultTier: newDefaultTier,
requireAllTiers: true,
@@ -455,6 +478,7 @@ export function updateCompositeVariant(
tiers: mergedTiers,
settings: settingsRef,
port: existing.port,
...(newTarget !== 'claude' && { target: newTarget }),
};
saveCompositeVariantUnified(name, compositeConfig);
@@ -468,6 +492,7 @@ export function updateCompositeVariant(
tiers: mergedTiers,
port: existing.port,
settings: settingsRef,
target: newTarget,
},
};
} catch (error) {