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 committed 2026-02-25 15:52:05 +07:00
1 parent 3dacb39deb
commit f4a6927293
2 files changed
+63 -49

No files matched your search

+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 * as fs from 'fs';
import { getConfigPath, loadConfigSafe } from '../../utils/config-manager'; import { getConfigPath, loadConfigSafe } from '../../utils/config-manager';
import { CLIProxyProvider } from '../types'; import { CLIProxyProvider } from '../types';
import type { TargetType } from '../../targets/target-adapter';
import { import {
CLIProxyVariantConfig, CLIProxyVariantConfig,
CompositeVariantConfig, CompositeVariantConfig,
@@ -20,19 +15,15 @@ import {
} from '../../config/unified-config-loader'; } from '../../config/unified-config-loader';
import { CLIPROXY_DEFAULT_PORT } from '../config-generator'; import { CLIPROXY_DEFAULT_PORT } from '../config-generator';
/** First port for variant profiles (8318 = default + 1) */
export const VARIANT_PORT_BASE = CLIPROXY_DEFAULT_PORT + 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; export const VARIANT_PORT_MAX_OFFSET = 100;
/** Variant configuration structure */
export interface VariantConfig { export interface VariantConfig {
provider: string; provider: string;
settings?: string; settings?: string;
account?: string; account?: string;
model?: string; model?: string;
port?: number; port?: number;
target?: TargetType;
/** Composite variant fields */ /** Composite variant fields */
type?: 'composite'; type?: 'composite';
default_tier?: 'opus' | 'sonnet' | 'haiku'; default_tier?: 'opus' | 'sonnet' | 'haiku';
@@ -45,9 +36,6 @@ export interface VariantConfig {
hasFallback?: boolean; hasFallback?: boolean;
} }
/**
* Check if variant exists in config
*/
export function variantExistsInConfig(name: string): boolean { export function variantExistsInConfig(name: string): boolean {
try { try {
if (isUnifiedMode()) { 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 { export function getNextAvailablePort(): number {
const variants = listVariantsFromConfig(); const variants = listVariantsFromConfig();
const usedPorts = new Set<number>(); const usedPorts = new Set<number>();
@@ -89,9 +73,6 @@ export function getNextAvailablePort(): number {
); );
} }
/**
* List variants from config
*/
export function listVariantsFromConfig(): Record<string, VariantConfig> { export function listVariantsFromConfig(): Record<string, VariantConfig> {
try { try {
if (isUnifiedMode()) { if (isUnifiedMode()) {
@@ -139,6 +120,7 @@ export function listVariantsFromConfig(): Record<string, VariantConfig> {
provider: defaultTierConfig.provider, provider: defaultTierConfig.provider,
settings: composite.settings, settings: composite.settings,
port: composite.port, port: composite.port,
target: composite.target || 'claude',
type: 'composite', type: 'composite',
default_tier: composite.default_tier, default_tier: composite.default_tier,
tiers: normalizedTiers, tiers: normalizedTiers,
@@ -151,6 +133,7 @@ export function listVariantsFromConfig(): Record<string, VariantConfig> {
settings: single.settings, settings: single.settings,
account: single.account, account: single.account,
port: single.port, port: single.port,
target: single.target || 'claude',
}; };
} }
} catch (error) { } catch (error) {
@@ -171,12 +154,14 @@ export function listVariantsFromConfig(): Record<string, VariantConfig> {
settings: string; settings: string;
account?: string; account?: string;
port?: number; port?: number;
target?: TargetType;
}; };
result[name] = { result[name] = {
provider: v.provider, provider: v.provider,
settings: v.settings, settings: v.settings,
account: v.account, account: v.account,
port: v.port, port: v.port,
target: v.target || 'claude',
}; };
} }
return result; 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 { export function saveCompositeVariantUnified(name: string, config: CompositeVariantConfig): void {
const unifiedConfig = loadOrCreateUnifiedConfig(); const unifiedConfig = loadOrCreateUnifiedConfig();
@@ -206,15 +188,13 @@ export function saveCompositeVariantUnified(name: string, config: CompositeVaria
saveUnifiedConfig(unifiedConfig); saveUnifiedConfig(unifiedConfig);
} }
/**
* Save variant to unified config
*/
export function saveVariantUnified( export function saveVariantUnified(
name: string, name: string,
provider: CLIProxyProvider, provider: CLIProxyProvider,
settingsPath: string, settingsPath: string,
account?: string, account?: string,
port?: number port?: number,
target: TargetType = 'claude'
): void { ): void {
const config = loadOrCreateUnifiedConfig(); const config = loadOrCreateUnifiedConfig();
@@ -234,20 +214,19 @@ export function saveVariantUnified(
account, account,
settings: settingsPath, settings: settingsPath,
port, port,
...(target !== 'claude' && { target }),
}; };
saveUnifiedConfig(config); saveUnifiedConfig(config);
} }
/**
* Save variant to legacy JSON config
*/
export function saveVariantLegacy( export function saveVariantLegacy(
name: string, name: string,
provider: string, provider: string,
settingsPath: string, settingsPath: string,
account?: string, account?: string,
port?: number port?: number,
target: TargetType = 'claude'
): void { ): void {
const configPath = getConfigPath(); const configPath = getConfigPath();
@@ -262,7 +241,13 @@ export function saveVariantLegacy(
config.cliproxy = {}; 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, provider,
settings: settingsPath, settings: settingsPath,
}; };
@@ -272,6 +257,9 @@ export function saveVariantLegacy(
if (port) { if (port) {
variantConfig.port = port; variantConfig.port = port;
} }
if (target !== 'claude') {
variantConfig.target = target;
}
config.cliproxy[name] = variantConfig; config.cliproxy[name] = variantConfig;
const tempPath = configPath + '.tmp'; const tempPath = configPath + '.tmp';
@@ -279,9 +267,6 @@ export function saveVariantLegacy(
fs.renameSync(tempPath, configPath); fs.renameSync(tempPath, configPath);
} }
/**
* Remove variant from unified config
*/
export function removeVariantFromUnifiedConfig(name: string): VariantConfig | null { export function removeVariantFromUnifiedConfig(name: string): VariantConfig | null {
const config = loadOrCreateUnifiedConfig(); const config = loadOrCreateUnifiedConfig();
@@ -299,6 +284,7 @@ export function removeVariantFromUnifiedConfig(name: string): VariantConfig | nu
provider: composite.tiers[composite.default_tier].provider, provider: composite.tiers[composite.default_tier].provider,
settings: composite.settings, settings: composite.settings,
port: composite.port, port: composite.port,
target: composite.target || 'claude',
type: 'composite', type: 'composite',
default_tier: composite.default_tier, default_tier: composite.default_tier,
tiers: composite.tiers, tiers: composite.tiers,
@@ -309,12 +295,10 @@ export function removeVariantFromUnifiedConfig(name: string): VariantConfig | nu
provider: singleVariant.provider, provider: singleVariant.provider,
settings: singleVariant.settings, settings: singleVariant.settings,
port: singleVariant.port, port: singleVariant.port,
target: singleVariant.target || 'claude',
}; };
} }
/**
* Remove variant from legacy JSON config
*/
export function removeVariantFromLegacyConfig(name: string): VariantConfig | null { export function removeVariantFromLegacyConfig(name: string): VariantConfig | null {
const configPath = getConfigPath(); const configPath = getConfigPath();
@@ -329,7 +313,12 @@ export function removeVariantFromLegacyConfig(name: string): VariantConfig | nul
return null; 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]; delete config.cliproxy[name];
if (Object.keys(config.cliproxy).length === 0) { 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 { CLIProxyProfileName } from '../../auth/profile-detector';
import { CLIProxyProvider, CLIProxyBackend, PLUS_ONLY_PROVIDERS } from '../types'; import { CLIProxyProvider, CLIProxyBackend, PLUS_ONLY_PROVIDERS } from '../types';
import { CompositeTierConfig, CompositeVariantConfig } from '../../config/unified-config-types'; import { CompositeTierConfig, CompositeVariantConfig } from '../../config/unified-config-types';
import type { TargetType } from '../../targets/target-adapter';
import { isReservedName, isWindowsReservedName } from '../../config/reserved-names'; import { isReservedName, isWindowsReservedName } from '../../config/reserved-names';
import { loadOrCreateUnifiedConfig } from '../../config/unified-config-loader'; import { loadOrCreateUnifiedConfig } from '../../config/unified-config-loader';
import { DEFAULT_BACKEND } from '../platform-detector'; import { DEFAULT_BACKEND } from '../platform-detector';
@@ -110,7 +111,8 @@ export function createVariant(
name: string, name: string,
provider: CLIProxyProfileName, provider: CLIProxyProfileName,
model: string, model: string,
account?: string account?: string,
target: TargetType = 'claude'
): VariantOperationResult { ): VariantOperationResult {
try { try {
// Validate provider/backend compatibility (block kiro/ghcp on original backend) // Validate provider/backend compatibility (block kiro/ghcp on original backend)
@@ -131,17 +133,25 @@ export function createVariant(
provider as CLIProxyProvider, provider as CLIProxyProvider,
getRelativeSettingsPath(provider, name), getRelativeSettingsPath(provider, name),
account, account,
port port,
target
); );
} else { } else {
settingsPath = createSettingsFile(name, provider, model, port); 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 { return {
success: true, success: true,
settingsPath, settingsPath,
variant: { provider, model, account, port }, variant: { provider, model, account, port, target },
}; };
} catch (error) { } catch (error) {
return { return {
@@ -208,6 +218,7 @@ export interface UpdateVariantOptions {
provider?: CLIProxyProfileName; provider?: CLIProxyProfileName;
account?: string; account?: string;
model?: string; model?: string;
target?: TargetType;
} }
/** /**
@@ -233,6 +244,8 @@ export function updateVariant(name: string, updates: UpdateVariantOptions): Vari
const providerChanged = const providerChanged =
updates.provider !== undefined && updates.provider !== existing.provider; 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; const hasModelUpdate = updates.model !== undefined && updates.model.trim().length > 0;
if (providerChanged && !hasModelUpdate) { if (providerChanged && !hasModelUpdate) {
@@ -257,8 +270,8 @@ export function updateVariant(name: string, updates: UpdateVariantOptions): Vari
} }
} }
// Update config entry if provider or account changed // Update config entry if provider/account/target changed
if (updates.provider !== undefined || updates.account !== undefined) { if (updates.provider !== undefined || updates.account !== undefined || targetChanged) {
const newProvider = updates.provider ?? existing.provider; const newProvider = updates.provider ?? existing.provider;
// Validate provider/backend compatibility on provider change // 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 newAccount = updates.account !== undefined ? updates.account : existing.account;
const newTarget = updates.target ?? existingTarget;
if (isUnifiedMode()) { if (isUnifiedMode()) {
saveVariantUnified( saveVariantUnified(
@@ -276,7 +290,8 @@ export function updateVariant(name: string, updates: UpdateVariantOptions): Vari
newProvider as CLIProxyProvider, newProvider as CLIProxyProvider,
existing.settings || '', existing.settings || '',
newAccount || undefined, newAccount || undefined,
existing.port existing.port,
newTarget
); );
} else { } else {
saveVariantLegacy( saveVariantLegacy(
@@ -284,7 +299,8 @@ export function updateVariant(name: string, updates: UpdateVariantOptions): Vari
newProvider, newProvider,
existing.settings || '', existing.settings || '',
newAccount || undefined, 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, account: updates.account !== undefined ? updates.account : existing.account,
port: existing.port, port: existing.port,
settings: existing.settings, settings: existing.settings,
target: updates.target ?? existingTarget,
}, },
}; };
} catch (error) { } catch (error) {
@@ -308,6 +325,7 @@ export function updateVariant(name: string, updates: UpdateVariantOptions): Vari
export interface CreateCompositeVariantOptions { export interface CreateCompositeVariantOptions {
name: string; name: string;
defaultTier: 'opus' | 'sonnet' | 'haiku'; defaultTier: 'opus' | 'sonnet' | 'haiku';
target?: TargetType;
tiers: { tiers: {
opus: CompositeTierConfig; opus: CompositeTierConfig;
sonnet: CompositeTierConfig; sonnet: CompositeTierConfig;
@@ -329,7 +347,7 @@ export function createCompositeVariant(
} }
try { try {
const { name, defaultTier, tiers } = options; const { name, defaultTier, tiers, target = 'claude' } = options;
const validationError = validateCompositeTiers(tiers, { const validationError = validateCompositeTiers(tiers, {
defaultTier, defaultTier,
@@ -361,6 +379,7 @@ export function createCompositeVariant(
tiers, tiers,
settings: settingsPath, settings: settingsPath,
port, port,
...(target !== 'claude' && { target }),
}; };
saveCompositeVariantUnified(name, compositeConfig); saveCompositeVariantUnified(name, compositeConfig);
@@ -373,6 +392,7 @@ export function createCompositeVariant(
default_tier: defaultTier, default_tier: defaultTier,
tiers, tiers,
port, port,
target,
}, },
}; };
} catch (error) { } catch (error) {
@@ -384,6 +404,7 @@ export function createCompositeVariant(
export interface UpdateCompositeVariantOptions { export interface UpdateCompositeVariantOptions {
defaultTier?: 'opus' | 'sonnet' | 'haiku'; defaultTier?: 'opus' | 'sonnet' | 'haiku';
tiers?: Partial<Record<'opus' | 'sonnet' | 'haiku', CompositeTierConfig>>; 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 newDefaultTier = updates.defaultTier ?? existing.default_tier ?? 'sonnet';
const existingTarget = existing.target || 'claude';
const newTarget = updates.target ?? existingTarget;
const validationError = validateCompositeTiers(mergedTiers, { const validationError = validateCompositeTiers(mergedTiers, {
defaultTier: newDefaultTier, defaultTier: newDefaultTier,
requireAllTiers: true, requireAllTiers: true,
@@ -455,6 +478,7 @@ export function updateCompositeVariant(
tiers: mergedTiers, tiers: mergedTiers,
settings: settingsRef, settings: settingsRef,
port: existing.port, port: existing.port,
...(newTarget !== 'claude' && { target: newTarget }),
}; };
saveCompositeVariantUnified(name, compositeConfig); saveCompositeVariantUnified(name, compositeConfig);
@@ -468,6 +492,7 @@ export function updateCompositeVariant(
tiers: mergedTiers, tiers: mergedTiers,
port: existing.port, port: existing.port,
settings: settingsRef, settings: settingsRef,
target: newTarget,
}, },
}; };
} catch (error) { } catch (error) {