mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 08:17:11 +00:00
feat(api): persist profile target metadata
- add target fields to API profile and CLIProxy variant service types - persist target in unified config and legacy profile_targets map - expose target update API via updateApiProfileTarget
This commit is contained in:
@@ -15,6 +15,7 @@ export {
|
||||
type ApiListResult,
|
||||
type CreateApiProfileResult,
|
||||
type RemoveApiProfileResult,
|
||||
type UpdateApiProfileTargetResult,
|
||||
} from './profile-types';
|
||||
|
||||
// Profile read operations
|
||||
@@ -27,7 +28,7 @@ export {
|
||||
} from './profile-reader';
|
||||
|
||||
// Profile write operations
|
||||
export { createApiProfile, removeApiProfile } from './profile-writer';
|
||||
export { createApiProfile, removeApiProfile, updateApiProfileTarget } from './profile-writer';
|
||||
|
||||
// OpenRouter catalog and picker
|
||||
export { isOpenRouterUrl, fetchOpenRouterModels, type OpenRouterModel } from './openrouter-catalog';
|
||||
|
||||
@@ -9,6 +9,7 @@ import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { getCcsDir, loadConfigSafe } from '../../utils/config-manager';
|
||||
import { loadOrCreateUnifiedConfig, isUnifiedMode } from '../../config/unified-config-loader';
|
||||
import type { TargetType } from '../../targets/target-adapter';
|
||||
import type { ApiProfileInfo, CliproxyVariantInfo, ApiListResult } from './profile-types';
|
||||
|
||||
/**
|
||||
@@ -68,6 +69,7 @@ export function listApiProfiles(): ApiListResult {
|
||||
settingsPath: profile.settings || 'config.yaml',
|
||||
isConfigured: isApiProfileConfigured(name),
|
||||
configSource: 'unified',
|
||||
target: profile.target || 'claude',
|
||||
});
|
||||
}
|
||||
// CLIProxy variants
|
||||
@@ -80,10 +82,13 @@ export function listApiProfiles(): ApiListResult {
|
||||
name,
|
||||
provider,
|
||||
settings: variant?.settings || '-',
|
||||
target: variant?.target || 'claude',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const config = loadConfigSafe();
|
||||
const legacyTargetMap = (config as { profile_targets?: Record<string, TargetType> })
|
||||
.profile_targets;
|
||||
for (const [name, settingsPath] of Object.entries(config.profiles)) {
|
||||
// Skip 'default' profile - it's the user's native Claude settings
|
||||
if (name === 'default' && (settingsPath as string).includes('.claude/settings.json')) {
|
||||
@@ -94,16 +99,18 @@ export function listApiProfiles(): ApiListResult {
|
||||
settingsPath: settingsPath as string,
|
||||
isConfigured: isApiProfileConfigured(name),
|
||||
configSource: 'legacy',
|
||||
target: legacyTargetMap?.[name] || 'claude',
|
||||
});
|
||||
}
|
||||
// CLIProxy variants
|
||||
if (config.cliproxy) {
|
||||
for (const [name, v] of Object.entries(config.cliproxy)) {
|
||||
const variant = v as { provider: string; settings: string };
|
||||
const variant = v as { provider: string; settings: string; target?: TargetType };
|
||||
variants.push({
|
||||
name,
|
||||
provider: variant.provider,
|
||||
settings: variant.settings,
|
||||
target: variant.target || 'claude',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
* Shared type definitions for API profile services.
|
||||
*/
|
||||
|
||||
import type { TargetType } from '../../targets/target-adapter';
|
||||
|
||||
/** Model mapping for API profiles */
|
||||
export interface ModelMapping {
|
||||
default: string;
|
||||
@@ -18,6 +20,7 @@ export interface ApiProfileInfo {
|
||||
settingsPath: string;
|
||||
isConfigured: boolean;
|
||||
configSource: 'unified' | 'legacy';
|
||||
target: TargetType;
|
||||
}
|
||||
|
||||
/** CLIProxy variant info */
|
||||
@@ -25,6 +28,7 @@ export interface CliproxyVariantInfo {
|
||||
name: string;
|
||||
provider: string;
|
||||
settings: string;
|
||||
target: TargetType;
|
||||
}
|
||||
|
||||
/** Result from list operation */
|
||||
@@ -45,3 +49,10 @@ export interface RemoveApiProfileResult {
|
||||
success: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/** Result from updating API profile target */
|
||||
export interface UpdateApiProfileTargetResult {
|
||||
success: boolean;
|
||||
target?: TargetType;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,13 @@ import {
|
||||
isUnifiedMode,
|
||||
} from '../../config/unified-config-loader';
|
||||
import { ensureProfileHooks } from '../../utils/websearch/profile-hook-injector';
|
||||
import type { ModelMapping, CreateApiProfileResult, RemoveApiProfileResult } from './profile-types';
|
||||
import type { TargetType } from '../../targets/target-adapter';
|
||||
import type {
|
||||
ModelMapping,
|
||||
CreateApiProfileResult,
|
||||
RemoveApiProfileResult,
|
||||
UpdateApiProfileTargetResult,
|
||||
} from './profile-types';
|
||||
|
||||
/** Check if URL is an OpenRouter endpoint */
|
||||
function isOpenRouterUrl(baseUrl: string): boolean {
|
||||
@@ -51,11 +57,15 @@ function createSettingsFile(
|
||||
}
|
||||
|
||||
/** Update config.json with new API profile (legacy format) */
|
||||
function updateLegacyConfig(name: string): void {
|
||||
function updateLegacyConfig(name: string, target: TargetType = 'claude'): void {
|
||||
const configPath = getConfigPath();
|
||||
const ccsDir = getCcsDir();
|
||||
|
||||
let config: { profiles: Record<string, string>; cliproxy?: Record<string, unknown> };
|
||||
let config: {
|
||||
profiles: Record<string, string>;
|
||||
cliproxy?: Record<string, unknown>;
|
||||
profile_targets?: Record<string, TargetType>;
|
||||
};
|
||||
try {
|
||||
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||
} catch {
|
||||
@@ -64,6 +74,12 @@ function updateLegacyConfig(name: string): void {
|
||||
|
||||
const relativePath = `~/.ccs/${name}.settings.json`;
|
||||
config.profiles[name] = relativePath;
|
||||
config.profile_targets = config.profile_targets || {};
|
||||
if (target === 'claude') {
|
||||
delete config.profile_targets[name];
|
||||
} else {
|
||||
config.profile_targets[name] = target;
|
||||
}
|
||||
|
||||
if (!fs.existsSync(ccsDir)) {
|
||||
fs.mkdirSync(ccsDir, { recursive: true });
|
||||
@@ -80,7 +96,8 @@ function createApiProfileUnified(
|
||||
name: string,
|
||||
baseUrl: string,
|
||||
apiKey: string,
|
||||
models: ModelMapping
|
||||
models: ModelMapping,
|
||||
target: TargetType = 'claude'
|
||||
): void {
|
||||
const ccsDir = getCcsDir();
|
||||
const settingsFile = `${name}.settings.json`;
|
||||
@@ -112,6 +129,7 @@ function createApiProfileUnified(
|
||||
config.profiles[name] = {
|
||||
type: 'api',
|
||||
settings: `~/.ccs/${settingsFile}`,
|
||||
...(target !== 'claude' && { target }),
|
||||
};
|
||||
saveUnifiedConfig(config);
|
||||
}
|
||||
@@ -121,16 +139,17 @@ export function createApiProfile(
|
||||
name: string,
|
||||
baseUrl: string,
|
||||
apiKey: string,
|
||||
models: ModelMapping
|
||||
models: ModelMapping,
|
||||
target: TargetType = 'claude'
|
||||
): CreateApiProfileResult {
|
||||
try {
|
||||
const settingsFile = `~/.ccs/${name}.settings.json`;
|
||||
|
||||
if (isUnifiedMode()) {
|
||||
createApiProfileUnified(name, baseUrl, apiKey, models);
|
||||
createApiProfileUnified(name, baseUrl, apiKey, models, target);
|
||||
} else {
|
||||
createSettingsFile(name, baseUrl, apiKey, models);
|
||||
updateLegacyConfig(name);
|
||||
updateLegacyConfig(name, target);
|
||||
}
|
||||
|
||||
return { success: true, settingsFile };
|
||||
@@ -143,6 +162,63 @@ export function createApiProfile(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update API profile target (claude/droid).
|
||||
* Persists to config.yaml in unified mode and config.json profile_targets in legacy mode.
|
||||
*/
|
||||
export function updateApiProfileTarget(
|
||||
name: string,
|
||||
target: TargetType
|
||||
): UpdateApiProfileTargetResult {
|
||||
try {
|
||||
if (isUnifiedMode()) {
|
||||
const config = loadOrCreateUnifiedConfig();
|
||||
if (!config.profiles[name]) {
|
||||
return { success: false, error: `API profile not found: ${name}` };
|
||||
}
|
||||
|
||||
if (target === 'claude') {
|
||||
delete config.profiles[name].target;
|
||||
} else {
|
||||
config.profiles[name].target = target;
|
||||
}
|
||||
saveUnifiedConfig(config);
|
||||
return { success: true, target };
|
||||
}
|
||||
|
||||
const configPath = getConfigPath();
|
||||
let config: {
|
||||
profiles: Record<string, string>;
|
||||
cliproxy?: Record<string, unknown>;
|
||||
profile_targets?: Record<string, TargetType>;
|
||||
};
|
||||
try {
|
||||
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||
} catch {
|
||||
config = { profiles: {} };
|
||||
}
|
||||
|
||||
if (!config.profiles[name]) {
|
||||
return { success: false, error: `API profile not found: ${name}` };
|
||||
}
|
||||
|
||||
config.profile_targets = config.profile_targets || {};
|
||||
if (target === 'claude') {
|
||||
delete config.profile_targets[name];
|
||||
} else {
|
||||
config.profile_targets[name] = target;
|
||||
}
|
||||
|
||||
const tempPath = configPath + '.tmp';
|
||||
fs.writeFileSync(tempPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
||||
fs.renameSync(tempPath, configPath);
|
||||
|
||||
return { success: true, target };
|
||||
} catch (error) {
|
||||
return { success: false, error: (error as Error).message };
|
||||
}
|
||||
}
|
||||
|
||||
/** Remove API profile from unified config */
|
||||
function removeApiProfileUnified(name: string): void {
|
||||
const config = loadOrCreateUnifiedConfig();
|
||||
|
||||
Reference in New Issue
Block a user