mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 02:11:28 +00:00
Issue #1161. Sweeps 127 files to import from src/config/config-loader-facade.ts instead of unified-config-loader or utils/config-manager directly. WRITE callers (32 files): replaced raw saveUnifiedConfig / mutateUnifiedConfig / updateUnifiedConfig calls with the facade's cache-coherent wrappers saveConfig / mutateConfig / updateConfig. This fixes a latent stale-cache window where direct writes through the underlying loader bypassed the facade's memoization. READ callers (95 files): mechanical import-path migration only — function names unchanged because the facade re-exports them. No behavior change. Also updated: - tests/unit/utils/browser/browser-setup.test.ts (DI interface rename) - src/management/checks/image-analysis-check.ts (dynamic import rename) - src/web-server/health-service.ts (dynamic require rename) - src/ccs.ts (path prefix fix from sweep script) After sweep: zero raw write callers remain outside src/config/. Direct imports of config-manager remain only for symbols not in the facade (getConfigPath, getCcsDirSource, etc). Behavior unchanged; full suite passes 1824/1824. Out of scope: switching loadOrCreateUnifiedConfig() callers to getCachedConfig() — needs per-callsite cache-safety analysis. Tracked as follow-up. Refs #1161
179 lines
5.5 KiB
TypeScript
179 lines
5.5 KiB
TypeScript
/**
|
|
* API Profile Reader Service
|
|
*
|
|
* Read operations for API profiles.
|
|
* Supports both unified YAML config and legacy JSON config.
|
|
*/
|
|
|
|
import * as fs from 'fs';
|
|
|
|
import { expandPath } from '../../utils/helpers';
|
|
import type { TargetType } from '../../targets/target-adapter';
|
|
import { isPersistedTargetType } from '../../targets/target-metadata';
|
|
import type { Settings } from '../../types/config';
|
|
import type { ApiProfileInfo, CliproxyVariantInfo, ApiListResult } from './profile-types';
|
|
import { resolveCliproxyBridgeMetadata } from './cliproxy-profile-bridge';
|
|
import {
|
|
isUnifiedMode,
|
|
loadConfigSafe,
|
|
loadOrCreateUnifiedConfig,
|
|
} from '../../config/config-loader-facade';
|
|
|
|
function sanitizeTarget(target: unknown): TargetType {
|
|
if (isPersistedTargetType(target)) {
|
|
return target as TargetType;
|
|
}
|
|
return 'claude';
|
|
}
|
|
|
|
/**
|
|
* Check if API profile exists in config
|
|
*/
|
|
export function apiProfileExists(name: string): boolean {
|
|
try {
|
|
if (isUnifiedMode()) {
|
|
const config = loadOrCreateUnifiedConfig();
|
|
return name in config.profiles;
|
|
}
|
|
const config = loadConfigSafe();
|
|
return name in config.profiles;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load settings file from a config reference such as ~/.ccs/name.settings.json.
|
|
*/
|
|
function loadProfileSettings(settingsReference: string | undefined): Settings | null {
|
|
if (!settingsReference || settingsReference === 'config.yaml') {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
const settingsPath = expandPath(settingsReference);
|
|
if (!fs.existsSync(settingsPath)) return null;
|
|
return JSON.parse(fs.readFileSync(settingsPath, 'utf8')) as Settings;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function isConfiguredFromSettings(settings: Settings | null): boolean {
|
|
const token = settings?.env?.ANTHROPIC_AUTH_TOKEN || settings?.env?.ANTHROPIC_API_KEY || '';
|
|
return token.length > 0 && !token.includes('YOUR_') && !token.includes('your-');
|
|
}
|
|
|
|
function resolveProfileSettingsReference(name: string): string | undefined {
|
|
if (isUnifiedMode()) {
|
|
const config = loadOrCreateUnifiedConfig();
|
|
return config.profiles[name]?.settings;
|
|
}
|
|
|
|
const config = loadConfigSafe();
|
|
return config.profiles[name];
|
|
}
|
|
|
|
/**
|
|
* Check if API profile has real API key (not placeholder)
|
|
*/
|
|
export function isApiProfileConfigured(apiName: string): boolean {
|
|
return isConfiguredFromSettings(loadProfileSettings(resolveProfileSettingsReference(apiName)));
|
|
}
|
|
|
|
/**
|
|
* List all API profiles
|
|
*
|
|
* Note: The 'default' profile (pointing to ~/.claude/settings.json) is excluded
|
|
* as it represents the user's native Claude subscription, not an API profile.
|
|
*/
|
|
export function listApiProfiles(): ApiListResult {
|
|
const profiles: ApiProfileInfo[] = [];
|
|
const variants: CliproxyVariantInfo[] = [];
|
|
|
|
if (isUnifiedMode()) {
|
|
const unifiedConfig = loadOrCreateUnifiedConfig();
|
|
for (const [name, profile] of Object.entries(unifiedConfig.profiles)) {
|
|
// Skip 'default' profile - it's the user's native Claude settings
|
|
if (name === 'default' && profile.settings?.includes('.claude/settings.json')) {
|
|
continue;
|
|
}
|
|
const settingsPath = profile.settings || 'config.yaml';
|
|
const settings = loadProfileSettings(settingsPath);
|
|
profiles.push({
|
|
name,
|
|
settingsPath,
|
|
isConfigured: isConfiguredFromSettings(settings),
|
|
configSource: 'unified',
|
|
target: sanitizeTarget(profile.target),
|
|
cliproxyBridge: resolveCliproxyBridgeMetadata(settings),
|
|
});
|
|
}
|
|
// CLIProxy variants
|
|
for (const [name, variant] of Object.entries(unifiedConfig.cliproxy?.variants || {})) {
|
|
const provider =
|
|
variant && 'type' in variant && variant.type === 'composite'
|
|
? 'composite'
|
|
: (variant as { provider?: string })?.provider || 'unknown';
|
|
variants.push({
|
|
name,
|
|
provider,
|
|
settings: variant?.settings || '-',
|
|
target: sanitizeTarget(variant?.target),
|
|
});
|
|
}
|
|
} else {
|
|
const config = loadConfigSafe();
|
|
const legacyTargetMap = (config as { profile_targets?: Record<string, unknown> })
|
|
.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')) {
|
|
continue;
|
|
}
|
|
const settings = loadProfileSettings(settingsPath as string);
|
|
profiles.push({
|
|
name,
|
|
settingsPath: settingsPath as string,
|
|
isConfigured: isConfiguredFromSettings(settings),
|
|
configSource: 'legacy',
|
|
target: sanitizeTarget(legacyTargetMap?.[name]),
|
|
cliproxyBridge: resolveCliproxyBridgeMetadata(settings),
|
|
});
|
|
}
|
|
// CLIProxy variants
|
|
if (config.cliproxy) {
|
|
for (const [name, v] of Object.entries(config.cliproxy)) {
|
|
const variant = v as { provider: string; settings: string; target?: unknown };
|
|
variants.push({
|
|
name,
|
|
provider: variant.provider,
|
|
settings: variant.settings,
|
|
target: sanitizeTarget(variant.target),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
return { profiles, variants };
|
|
}
|
|
|
|
/**
|
|
* Get list of available API profile names
|
|
*/
|
|
export function getApiProfileNames(): string[] {
|
|
if (isUnifiedMode()) {
|
|
const config = loadOrCreateUnifiedConfig();
|
|
return Object.keys(config.profiles);
|
|
}
|
|
const config = loadConfigSafe();
|
|
return Object.keys(config.profiles);
|
|
}
|
|
|
|
/**
|
|
* Check if using unified config mode
|
|
*/
|
|
export function isUsingUnifiedConfig(): boolean {
|
|
return isUnifiedMode();
|
|
}
|