mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-14 08:18:04 +00:00
Merge pull request #151 from kaitranntt/kai/fix/preset-missing-base-config
fix(cliproxy): include BASE_URL and AUTH_TOKEN when applying presets
This commit is contained in:
2 files changed
+70
-22
No files matched your search
+61
-22
@@ -66,6 +66,7 @@ import { getWebSearchConfig } from '../config/unified-config-loader';
|
||||
import type { WebSearchConfig } from '../config/unified-config-types';
|
||||
import { isUnifiedConfig } from '../config/unified-config-types';
|
||||
import { isSensitiveKey, maskSensitiveValue } from '../utils/sensitive-keys';
|
||||
import { isReservedName, RESERVED_PROFILE_NAMES } from '../config/reserved-names';
|
||||
import {
|
||||
getWebSearchReadiness,
|
||||
getGeminiCliStatus,
|
||||
@@ -275,6 +276,15 @@ apiRoutes.post('/profiles', (req: Request, res: Response): void => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate reserved names
|
||||
if (isReservedName(name)) {
|
||||
res.status(400).json({
|
||||
error: `Profile name '${name}' is reserved`,
|
||||
reserved: RESERVED_PROFILE_NAMES,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const config = readConfigSafe();
|
||||
|
||||
if (config.profiles[name]) {
|
||||
@@ -378,6 +388,15 @@ apiRoutes.post('/cliproxy', (req: Request, res: Response): void => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reject reserved names as variant names (prevents collision with built-in providers)
|
||||
if (isReservedName(name)) {
|
||||
res.status(400).json({
|
||||
error: `Cannot use reserved name '${name}' as variant name`,
|
||||
reserved: RESERVED_PROFILE_NAMES,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const config = readConfigSafe();
|
||||
config.cliproxy = config.cliproxy || {};
|
||||
|
||||
@@ -472,10 +491,13 @@ apiRoutes.delete('/cliproxy/:name', (req: Request, res: Response): void => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete settings file
|
||||
const settingsPath = path.join(getCcsDir(), `${name}.settings.json`);
|
||||
if (fs.existsSync(settingsPath)) {
|
||||
fs.unlinkSync(settingsPath);
|
||||
// Never delete settings files for reserved provider names (safety guard)
|
||||
if (!isReservedName(name)) {
|
||||
// Only delete settings file for non-reserved variant names
|
||||
const settingsPath = path.join(getCcsDir(), `${name}.settings.json`);
|
||||
if (fs.existsSync(settingsPath)) {
|
||||
fs.unlinkSync(settingsPath);
|
||||
}
|
||||
}
|
||||
|
||||
delete config.cliproxy[name];
|
||||
@@ -781,6 +803,7 @@ apiRoutes.get('/settings/:profile/raw', (req: Request, res: Response): void => {
|
||||
|
||||
/**
|
||||
* PUT /api/settings/:profile - Update settings with conflict detection and backup
|
||||
* Creates the settings file if it doesn't exist (upsert behavior)
|
||||
*/
|
||||
apiRoutes.put('/settings/:profile', (req: Request, res: Response): void => {
|
||||
const { profile } = req.params;
|
||||
@@ -788,29 +811,36 @@ apiRoutes.put('/settings/:profile', (req: Request, res: Response): void => {
|
||||
const ccsDir = getCcsDir();
|
||||
const settingsPath = path.join(ccsDir, `${profile}.settings.json`);
|
||||
|
||||
if (!fs.existsSync(settingsPath)) {
|
||||
res.status(404).json({ error: 'Settings not found' });
|
||||
return;
|
||||
const fileExists = fs.existsSync(settingsPath);
|
||||
|
||||
// Only check conflict if file exists and expectedMtime was provided
|
||||
if (fileExists && expectedMtime) {
|
||||
const stat = fs.statSync(settingsPath);
|
||||
if (stat.mtime.getTime() !== expectedMtime) {
|
||||
res.status(409).json({
|
||||
error: 'File modified externally',
|
||||
currentMtime: stat.mtime.getTime(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Conflict detection
|
||||
const stat = fs.statSync(settingsPath);
|
||||
if (expectedMtime && stat.mtime.getTime() !== expectedMtime) {
|
||||
res.status(409).json({
|
||||
error: 'File modified externally',
|
||||
currentMtime: stat.mtime.getTime(),
|
||||
});
|
||||
return;
|
||||
// Create backup only if file exists
|
||||
let backupPath: string | undefined;
|
||||
if (fileExists) {
|
||||
const backupDir = path.join(ccsDir, 'backups');
|
||||
if (!fs.existsSync(backupDir)) {
|
||||
fs.mkdirSync(backupDir, { recursive: true });
|
||||
}
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
backupPath = path.join(backupDir, `${profile}.${timestamp}.settings.json`);
|
||||
fs.copyFileSync(settingsPath, backupPath);
|
||||
}
|
||||
|
||||
// Create backup
|
||||
const backupDir = path.join(ccsDir, 'backups');
|
||||
if (!fs.existsSync(backupDir)) {
|
||||
fs.mkdirSync(backupDir, { recursive: true });
|
||||
// Ensure directory exists for new files
|
||||
if (!fileExists) {
|
||||
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
|
||||
}
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const backupPath = path.join(backupDir, `${profile}.${timestamp}.settings.json`);
|
||||
fs.copyFileSync(settingsPath, backupPath);
|
||||
|
||||
// Write new settings atomically
|
||||
const tempPath = settingsPath + '.tmp';
|
||||
@@ -822,6 +852,7 @@ apiRoutes.put('/settings/:profile', (req: Request, res: Response): void => {
|
||||
profile,
|
||||
mtime: newStat.mtime.getTime(),
|
||||
backupPath,
|
||||
created: !fileExists,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1592,6 +1623,14 @@ apiRoutes.post('/cliproxy/openai-compat', (req: Request, res: Response): void =>
|
||||
res.status(400).json({ error: 'name is required' });
|
||||
return;
|
||||
}
|
||||
// Validate reserved names
|
||||
if (isReservedName(name)) {
|
||||
res.status(400).json({
|
||||
error: `Provider name '${name}' is reserved`,
|
||||
reserved: RESERVED_PROFILE_NAMES,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!baseUrl || typeof baseUrl !== 'string') {
|
||||
res.status(400).json({ error: 'baseUrl is required' });
|
||||
return;
|
||||
|
||||
@@ -296,7 +296,10 @@ export function ProviderEditor({
|
||||
sonnet: model.id,
|
||||
haiku: model.id,
|
||||
};
|
||||
// Always include BASE_URL and AUTH_TOKEN for CLIProxy providers
|
||||
updateEnvValues({
|
||||
ANTHROPIC_BASE_URL: `http://127.0.0.1:8317/api/provider/${provider}`,
|
||||
ANTHROPIC_AUTH_TOKEN: 'ccs-internal-managed',
|
||||
ANTHROPIC_MODEL: mapping.default,
|
||||
ANTHROPIC_DEFAULT_OPUS_MODEL: mapping.opus,
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL: mapping.sonnet,
|
||||
@@ -318,7 +321,10 @@ export function ProviderEditor({
|
||||
size="sm"
|
||||
className="text-xs h-7 gap-1 pr-6"
|
||||
onClick={() => {
|
||||
// Always include BASE_URL and AUTH_TOKEN for CLIProxy providers
|
||||
updateEnvValues({
|
||||
ANTHROPIC_BASE_URL: `http://127.0.0.1:8317/api/provider/${provider}`,
|
||||
ANTHROPIC_AUTH_TOKEN: 'ccs-internal-managed',
|
||||
ANTHROPIC_MODEL: preset.default,
|
||||
ANTHROPIC_DEFAULT_OPUS_MODEL: preset.opus,
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL: preset.sonnet,
|
||||
@@ -655,7 +661,10 @@ export function ProviderEditor({
|
||||
haiku: haikuModel || '',
|
||||
}}
|
||||
onApply={(values, presetName) => {
|
||||
// Always include BASE_URL and AUTH_TOKEN for CLIProxy providers
|
||||
updateEnvValues({
|
||||
ANTHROPIC_BASE_URL: `http://127.0.0.1:8317/api/provider/${provider}`,
|
||||
ANTHROPIC_AUTH_TOKEN: 'ccs-internal-managed',
|
||||
ANTHROPIC_MODEL: values.default,
|
||||
ANTHROPIC_DEFAULT_OPUS_MODEL: values.opus,
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL: values.sonnet,
|
||||
|
||||
Reference in new issue
Block a user