mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 02:11:28 +00:00
fix(profile): close km legacy kimi compatibility gaps
This commit is contained in:
@@ -5,6 +5,8 @@
|
||||
* Mirrors the UI presets in ui/src/lib/provider-presets.ts
|
||||
*/
|
||||
|
||||
import { resolveAliasToCanonical } from '../../utils/profile-compat';
|
||||
|
||||
export type PresetCategory = 'recommended' | 'alternative';
|
||||
|
||||
export interface ProviderPreset {
|
||||
@@ -26,9 +28,6 @@ export interface ProviderPreset {
|
||||
}
|
||||
|
||||
export const OPENROUTER_BASE_URL = 'https://openrouter.ai/api';
|
||||
const LEGACY_PRESET_ALIASES: Readonly<Record<string, string>> = Object.freeze({
|
||||
kimi: 'km',
|
||||
});
|
||||
|
||||
/**
|
||||
* Provider presets available via CLI and UI
|
||||
@@ -172,8 +171,8 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
|
||||
|
||||
/** Get preset by ID */
|
||||
export function getPresetById(id: string): ProviderPreset | undefined {
|
||||
const normalized = id.toLowerCase();
|
||||
const canonical = LEGACY_PRESET_ALIASES[normalized] || normalized;
|
||||
const normalized = id.trim().toLowerCase();
|
||||
const canonical = resolveAliasToCanonical(normalized).toLowerCase();
|
||||
return PROVIDER_PRESETS.find((p) => p.id === canonical);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { getCcsDir } from '../utils/config-manager';
|
||||
import { expandPath } from '../utils/helpers';
|
||||
import { resolveAliasToCanonical } from '../utils/profile-compat';
|
||||
import type { ProfileConfig, AccountConfig, CLIProxyVariantConfig } from './unified-config-types';
|
||||
import { createEmptyUnifiedConfig } from './unified-config-types';
|
||||
import { CLIPROXY_PROVIDER_IDS } from '../cliproxy/provider-capabilities';
|
||||
@@ -23,9 +24,6 @@ import { saveUnifiedConfig, hasUnifiedConfig, loadUnifiedConfig } from './unifie
|
||||
import { infoBox, warn } from '../utils/ui';
|
||||
|
||||
const BACKUP_DIR_PREFIX = 'backup-v1-';
|
||||
const LEGACY_API_PROFILE_RENAMES: Readonly<Record<string, string>> = Object.freeze({
|
||||
kimi: 'km',
|
||||
});
|
||||
|
||||
/**
|
||||
* Migration result with details about what was migrated.
|
||||
@@ -76,7 +74,8 @@ export function loadMigrationCheckData(): MigrationCheckData {
|
||||
if (legacyConfig?.profiles && typeof legacyConfig.profiles === 'object' && unifiedConfig) {
|
||||
const legacyProfiles = legacyConfig.profiles as Record<string, unknown>;
|
||||
for (const profileName of Object.keys(legacyProfiles)) {
|
||||
if (!unifiedConfig.profiles[profileName]) {
|
||||
const targetProfileName = resolveAliasToCanonical(profileName);
|
||||
if (!unifiedConfig.profiles[targetProfileName]) {
|
||||
needsMigration = true;
|
||||
break;
|
||||
}
|
||||
@@ -185,13 +184,30 @@ export async function migrate(dryRun = false): Promise<MigrationResult> {
|
||||
// config.yaml only stores reference to the settings file
|
||||
if (oldConfig?.profiles) {
|
||||
for (const [name, settingsPath] of Object.entries(oldConfig.profiles)) {
|
||||
const targetName = LEGACY_API_PROFILE_RENAMES[name] || name;
|
||||
const sourceName = name.trim();
|
||||
const targetName = resolveAliasToCanonical(sourceName);
|
||||
const pathStr = settingsPath as string;
|
||||
const expandedPath = expandPath(pathStr);
|
||||
|
||||
const canonicalEntryValue = (oldConfig.profiles as Record<string, unknown>)[targetName];
|
||||
const canonicalPathFromLegacyConfig =
|
||||
sourceName !== targetName && typeof canonicalEntryValue === 'string'
|
||||
? canonicalEntryValue
|
||||
: undefined;
|
||||
|
||||
// Deterministic priority: explicit canonical profile wins over legacy alias rename.
|
||||
if (canonicalPathFromLegacyConfig !== undefined) {
|
||||
if (canonicalPathFromLegacyConfig !== pathStr) {
|
||||
warnings.push(
|
||||
`Skipped ${sourceName}: canonical profile "${targetName}" exists in config.json with different settings (${canonicalPathFromLegacyConfig})`
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Verify settings file exists
|
||||
if (!fs.existsSync(expandedPath)) {
|
||||
warnings.push(`Skipped ${name}: settings file not found at ${pathStr}`);
|
||||
warnings.push(`Skipped ${sourceName}: settings file not found at ${pathStr}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -199,7 +215,7 @@ export async function migrate(dryRun = false): Promise<MigrationResult> {
|
||||
const existing = unifiedConfig.profiles[targetName].settings;
|
||||
if (existing !== pathStr) {
|
||||
warnings.push(
|
||||
`Skipped ${name}: target profile "${targetName}" already exists with different settings (${existing})`
|
||||
`Skipped ${sourceName}: target profile "${targetName}" already exists with different settings (${existing})`
|
||||
);
|
||||
}
|
||||
continue;
|
||||
@@ -212,11 +228,11 @@ export async function migrate(dryRun = false): Promise<MigrationResult> {
|
||||
};
|
||||
unifiedConfig.profiles[targetName] = profile;
|
||||
migratedFiles.push(
|
||||
`config.json.profiles.${name} → config.yaml.profiles.${targetName} (settings: ${pathStr})`
|
||||
`config.json.profiles.${sourceName} → config.yaml.profiles.${targetName} (settings: ${pathStr})`
|
||||
);
|
||||
if (targetName !== name) {
|
||||
if (targetName !== sourceName) {
|
||||
warnings.push(
|
||||
`Renamed legacy API profile "${name}" to "${targetName}" (ccs kimi API profile is now ccs km)`
|
||||
`Renamed legacy API profile "${sourceName}" to "${targetName}" (ccs kimi API profile is now ccs km)`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -477,16 +493,33 @@ async function migrateProfilesToUnified(
|
||||
|
||||
// Migrate API profiles from config.json
|
||||
for (const [name, settingsPath] of Object.entries(oldConfig.profiles)) {
|
||||
const targetName = LEGACY_API_PROFILE_RENAMES[name] || name;
|
||||
const sourceName = name.trim();
|
||||
const targetName = resolveAliasToCanonical(sourceName);
|
||||
const pathStr = settingsPath as string;
|
||||
|
||||
const canonicalEntryValue = (oldConfig.profiles as Record<string, unknown>)[targetName];
|
||||
const canonicalPathFromLegacyConfig =
|
||||
sourceName !== targetName && typeof canonicalEntryValue === 'string'
|
||||
? canonicalEntryValue
|
||||
: undefined;
|
||||
|
||||
// Deterministic priority: explicit canonical profile wins over legacy alias rename.
|
||||
if (canonicalPathFromLegacyConfig !== undefined) {
|
||||
if (canonicalPathFromLegacyConfig !== pathStr) {
|
||||
warnings.push(
|
||||
`Skipped ${sourceName}: canonical profile "${targetName}" exists in config.json with different settings (${canonicalPathFromLegacyConfig})`
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// H7: Detect collision - profile exists in both configs
|
||||
if (unifiedConfig.profiles[targetName]) {
|
||||
// Check if settings differ (potential data loss)
|
||||
const existingSettings = unifiedConfig.profiles[targetName].settings;
|
||||
if (existingSettings && existingSettings !== pathStr) {
|
||||
warnings.push(
|
||||
`Profile "${targetName}" exists in both configs with different settings - keeping existing (${existingSettings}), skipping legacy ${name} (${pathStr})`
|
||||
`Profile "${targetName}" exists in both configs with different settings - keeping existing (${existingSettings}), skipping legacy ${sourceName} (${pathStr})`
|
||||
);
|
||||
}
|
||||
continue;
|
||||
@@ -496,7 +529,7 @@ async function migrateProfilesToUnified(
|
||||
|
||||
// Verify settings file exists
|
||||
if (!fs.existsSync(expandedPath)) {
|
||||
warnings.push(`Skipped ${name}: settings file not found at ${pathStr}`);
|
||||
warnings.push(`Skipped ${sourceName}: settings file not found at ${pathStr}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -506,9 +539,9 @@ async function migrateProfilesToUnified(
|
||||
settings: pathStr,
|
||||
};
|
||||
migratedFiles.push(targetName);
|
||||
if (targetName !== name) {
|
||||
if (targetName !== sourceName) {
|
||||
warnings.push(
|
||||
`Renamed legacy API profile "${name}" to "${targetName}" (ccs kimi API profile is now ccs km)`
|
||||
`Renamed legacy API profile "${sourceName}" to "${targetName}" (ccs kimi API profile is now ccs km)`
|
||||
);
|
||||
}
|
||||
modified = true;
|
||||
|
||||
@@ -10,6 +10,23 @@ const PROFILE_COMPAT_ALIASES: Readonly<Record<string, readonly string[]>> = Obje
|
||||
km: ['kimi'],
|
||||
});
|
||||
|
||||
/**
|
||||
* Resolve a legacy alias to its canonical profile name.
|
||||
* Returns trimmed input when no alias mapping exists.
|
||||
*/
|
||||
export function resolveAliasToCanonical(profileName: string): string {
|
||||
const raw = profileName.trim();
|
||||
const normalized = raw.toLowerCase();
|
||||
|
||||
for (const [canonical, aliases] of Object.entries(PROFILE_COMPAT_ALIASES)) {
|
||||
if (aliases.includes(normalized)) {
|
||||
return canonical;
|
||||
}
|
||||
}
|
||||
|
||||
return raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build lookup candidates for a profile.
|
||||
* Order: exact input -> lowercase form (if different) -> legacy aliases.
|
||||
|
||||
@@ -87,24 +87,23 @@ export function checkConfigFile(): HealthCheck {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check settings files (glm, kimi)
|
||||
* Check settings files (glm, km with legacy kimi fallback)
|
||||
*/
|
||||
export function checkSettingsFiles(ccsDir: string): HealthCheck[] {
|
||||
const checks: HealthCheck[] = [];
|
||||
const files = [
|
||||
{ name: 'glm.settings.json', profile: 'glm' },
|
||||
{ name: 'kimi.settings.json', profile: 'kimi' },
|
||||
];
|
||||
const profiles = ['glm', 'km'];
|
||||
|
||||
const { DelegationValidator } = require('../../utils/delegation-validator');
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(ccsDir, file.name);
|
||||
for (const profile of profiles) {
|
||||
const fileName = `${profile}.settings.json`;
|
||||
const filePath = path.join(ccsDir, fileName);
|
||||
const validation = DelegationValidator.validate(profile);
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
if (!validation.valid && validation.error?.includes('Profile not found')) {
|
||||
checks.push({
|
||||
id: `settings-${file.profile}`,
|
||||
name: file.name,
|
||||
id: `settings-${profile}`,
|
||||
name: fileName,
|
||||
status: 'info',
|
||||
message: 'Not configured',
|
||||
details: filePath,
|
||||
@@ -112,46 +111,50 @@ export function checkSettingsFiles(ccsDir: string): HealthCheck[] {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
JSON.parse(content);
|
||||
const resolvedPath = validation.settingsPath || filePath;
|
||||
const resolvedName = path.basename(resolvedPath);
|
||||
|
||||
const validation = DelegationValidator.validate(file.profile);
|
||||
|
||||
if (validation.valid) {
|
||||
checks.push({
|
||||
id: `settings-${file.profile}`,
|
||||
name: file.name,
|
||||
status: 'ok',
|
||||
message: 'Key configured',
|
||||
details: filePath,
|
||||
});
|
||||
} else if (validation.error && validation.error.includes('placeholder')) {
|
||||
checks.push({
|
||||
id: `settings-${file.profile}`,
|
||||
name: file.name,
|
||||
status: 'warning',
|
||||
message: 'Placeholder key',
|
||||
details: filePath,
|
||||
});
|
||||
} else {
|
||||
checks.push({
|
||||
id: `settings-${file.profile}`,
|
||||
name: file.name,
|
||||
status: 'ok',
|
||||
message: 'Valid JSON',
|
||||
details: filePath,
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
if (validation.valid) {
|
||||
checks.push({
|
||||
id: `settings-${file.profile}`,
|
||||
name: file.name,
|
||||
id: `settings-${profile}`,
|
||||
name: resolvedName,
|
||||
status: 'ok',
|
||||
message: 'Key configured',
|
||||
details: resolvedPath,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (validation.error?.includes('placeholder')) {
|
||||
checks.push({
|
||||
id: `settings-${profile}`,
|
||||
name: resolvedName,
|
||||
status: 'warning',
|
||||
message: 'Placeholder key',
|
||||
details: resolvedPath,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (validation.error?.includes('Failed to parse settings.json')) {
|
||||
checks.push({
|
||||
id: `settings-${profile}`,
|
||||
name: resolvedName,
|
||||
status: 'error',
|
||||
message: 'Invalid JSON',
|
||||
details: filePath,
|
||||
details: resolvedPath,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Keep prior behavior for non-placeholder validation issues (e.g., missing key).
|
||||
checks.push({
|
||||
id: `settings-${profile}`,
|
||||
name: resolvedName,
|
||||
status: 'ok',
|
||||
message: 'Valid JSON',
|
||||
details: resolvedPath,
|
||||
});
|
||||
}
|
||||
|
||||
return checks;
|
||||
|
||||
@@ -12,6 +12,16 @@ describe('provider-presets', () => {
|
||||
expect(preset?.id).toBe('km');
|
||||
});
|
||||
|
||||
it('resolves preset id with extra whitespace', () => {
|
||||
const preset = getPresetById(' km ');
|
||||
expect(preset?.id).toBe('km');
|
||||
});
|
||||
|
||||
it('resolves uppercase legacy alias', () => {
|
||||
const preset = getPresetById('KIMI');
|
||||
expect(preset?.id).toBe('km');
|
||||
});
|
||||
|
||||
it('treats legacy kimi alias as a valid preset id', () => {
|
||||
expect(isValidPresetId('kimi')).toBe(true);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { loadMigrationCheckData, migrate } from '../../../src/config/migration-manager';
|
||||
import { saveUnifiedConfig } from '../../../src/config/unified-config-loader';
|
||||
import { createEmptyUnifiedConfig } from '../../../src/config/unified-config-types';
|
||||
|
||||
describe('migration-manager legacy kimi compatibility', () => {
|
||||
let tempHome: string;
|
||||
let ccsDir: string;
|
||||
let originalCcsHome: string | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-migration-manager-test-'));
|
||||
originalCcsHome = process.env.CCS_HOME;
|
||||
process.env.CCS_HOME = tempHome;
|
||||
ccsDir = path.join(tempHome, '.ccs');
|
||||
fs.mkdirSync(ccsDir, { recursive: true });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (originalCcsHome !== undefined) {
|
||||
process.env.CCS_HOME = originalCcsHome;
|
||||
} else {
|
||||
delete process.env.CCS_HOME;
|
||||
}
|
||||
|
||||
if (fs.existsSync(tempHome)) {
|
||||
fs.rmSync(tempHome, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('prefers explicit canonical km profile over legacy kimi when both exist', async () => {
|
||||
const kmSettingsPath = path.join(ccsDir, 'km.settings.json');
|
||||
const kimiSettingsPath = path.join(ccsDir, 'kimi.settings.json');
|
||||
|
||||
fs.writeFileSync(kmSettingsPath, JSON.stringify({ env: { ANTHROPIC_AUTH_TOKEN: 'sk-km' } }));
|
||||
fs.writeFileSync(
|
||||
kimiSettingsPath,
|
||||
JSON.stringify({ env: { ANTHROPIC_AUTH_TOKEN: 'sk-kimi' } })
|
||||
);
|
||||
|
||||
// Intentionally place legacy alias first to verify deterministic behavior.
|
||||
fs.writeFileSync(
|
||||
path.join(ccsDir, 'config.json'),
|
||||
JSON.stringify(
|
||||
{
|
||||
profiles: {
|
||||
kimi: kimiSettingsPath,
|
||||
km: kmSettingsPath,
|
||||
},
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
|
||||
const result = await migrate(true);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(
|
||||
result.migratedFiles.some((entry) =>
|
||||
entry.includes(`config.json.profiles.km → config.yaml.profiles.km (settings: ${kmSettingsPath})`)
|
||||
)
|
||||
).toBe(true);
|
||||
expect(
|
||||
result.migratedFiles.some((entry) =>
|
||||
entry.includes(`(settings: ${kimiSettingsPath})`)
|
||||
)
|
||||
).toBe(false);
|
||||
expect(
|
||||
result.warnings.some((warning) =>
|
||||
warning.includes(
|
||||
'Skipped kimi: canonical profile "km" exists in config.json with different settings'
|
||||
)
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('renames case-variant legacy Kimi profile key to km', async () => {
|
||||
const kimiSettingsPath = path.join(ccsDir, 'kimi.settings.json');
|
||||
fs.writeFileSync(
|
||||
kimiSettingsPath,
|
||||
JSON.stringify({ env: { ANTHROPIC_AUTH_TOKEN: 'sk-kimi-case-variant' } })
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(ccsDir, 'config.json'),
|
||||
JSON.stringify(
|
||||
{
|
||||
profiles: {
|
||||
Kimi: kimiSettingsPath,
|
||||
},
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
|
||||
const result = await migrate(true);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(
|
||||
result.migratedFiles.some((entry) =>
|
||||
entry.includes('config.json.profiles.Kimi → config.yaml.profiles.km')
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('treats legacy kimi profile as migrated when unified config already has km', () => {
|
||||
const unifiedConfig = createEmptyUnifiedConfig();
|
||||
unifiedConfig.profiles.km = {
|
||||
type: 'api',
|
||||
settings: '~/.ccs/km.settings.json',
|
||||
};
|
||||
saveUnifiedConfig(unifiedConfig);
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(ccsDir, 'config.json'),
|
||||
JSON.stringify(
|
||||
{
|
||||
profiles: {
|
||||
kimi: '~/.ccs/kimi.settings.json',
|
||||
},
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
|
||||
const checkData = loadMigrationCheckData();
|
||||
expect(checkData.needsMigration).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,9 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
import { getProfileLookupCandidates, isLegacyProfileAlias } from '../../../src/utils/profile-compat';
|
||||
import {
|
||||
getProfileLookupCandidates,
|
||||
isLegacyProfileAlias,
|
||||
resolveAliasToCanonical,
|
||||
} from '../../../src/utils/profile-compat';
|
||||
|
||||
describe('profile-compat', () => {
|
||||
describe('getProfileLookupCandidates', () => {
|
||||
@@ -14,6 +18,15 @@ describe('profile-compat', () => {
|
||||
it('normalizes uppercase input and still resolves aliases', () => {
|
||||
expect(getProfileLookupCandidates('KM')).toEqual(['KM', 'km', 'kimi']);
|
||||
});
|
||||
|
||||
it('handles surrounding whitespace', () => {
|
||||
expect(getProfileLookupCandidates(' km ')).toEqual(['km', 'kimi']);
|
||||
});
|
||||
|
||||
it('returns empty candidates for empty input', () => {
|
||||
expect(getProfileLookupCandidates('')).toEqual([]);
|
||||
expect(getProfileLookupCandidates(' ')).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isLegacyProfileAlias', () => {
|
||||
@@ -30,4 +43,16 @@ describe('profile-compat', () => {
|
||||
expect(isLegacyProfileAlias('glm', 'kimi')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveAliasToCanonical', () => {
|
||||
it('maps legacy kimi alias to canonical km', () => {
|
||||
expect(resolveAliasToCanonical('kimi')).toBe('km');
|
||||
expect(resolveAliasToCanonical('KIMI')).toBe('km');
|
||||
});
|
||||
|
||||
it('keeps canonical and non-aliased names', () => {
|
||||
expect(resolveAliasToCanonical('km')).toBe('km');
|
||||
expect(resolveAliasToCanonical('glm')).toBe('glm');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { checkSettingsFiles } from '../../../src/web-server/health/config-checks';
|
||||
|
||||
describe('web-server config-checks settings compatibility', () => {
|
||||
let tempHome: string;
|
||||
let ccsDir: string;
|
||||
let originalCcsHome: string | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-config-checks-test-'));
|
||||
originalCcsHome = process.env.CCS_HOME;
|
||||
process.env.CCS_HOME = tempHome;
|
||||
ccsDir = path.join(tempHome, '.ccs');
|
||||
fs.mkdirSync(ccsDir, { recursive: true });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (originalCcsHome !== undefined) {
|
||||
process.env.CCS_HOME = originalCcsHome;
|
||||
} else {
|
||||
delete process.env.CCS_HOME;
|
||||
}
|
||||
|
||||
if (fs.existsSync(tempHome)) {
|
||||
fs.rmSync(tempHome, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('reports km as configured when only legacy kimi.settings.json exists', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(ccsDir, 'kimi.settings.json'),
|
||||
JSON.stringify({
|
||||
env: {
|
||||
ANTHROPIC_AUTH_TOKEN: 'sk-live-kimi-compat',
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const checks = checkSettingsFiles(ccsDir);
|
||||
const kmCheck = checks.find((check) => check.id === 'settings-km');
|
||||
|
||||
expect(kmCheck).toBeDefined();
|
||||
expect(kmCheck?.status).toBe('ok');
|
||||
expect(kmCheck?.name).toBe('kimi.settings.json');
|
||||
});
|
||||
});
|
||||
@@ -176,6 +176,10 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const LEGACY_PRESET_ALIASES: Readonly<Record<string, string>> = Object.freeze({
|
||||
kimi: 'km',
|
||||
});
|
||||
|
||||
/** Get presets by category */
|
||||
export function getPresetsByCategory(category: PresetCategory): ProviderPreset[] {
|
||||
return PROVIDER_PRESETS.filter((p) => p.category === category);
|
||||
@@ -183,7 +187,9 @@ export function getPresetsByCategory(category: PresetCategory): ProviderPreset[]
|
||||
|
||||
/** Get preset by ID */
|
||||
export function getPresetById(id: string): ProviderPreset | undefined {
|
||||
return PROVIDER_PRESETS.find((p) => p.id.toLowerCase() === id.toLowerCase());
|
||||
const normalized = id.trim().toLowerCase();
|
||||
const canonical = LEGACY_PRESET_ALIASES[normalized] || normalized;
|
||||
return PROVIDER_PRESETS.find((p) => p.id.toLowerCase() === canonical);
|
||||
}
|
||||
|
||||
/** Check if a URL matches a known preset */
|
||||
|
||||
Reference in New Issue
Block a user