mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 14:16:43 +00:00
feat(api): support profile target in routes
- validate target payloads for profile create/update - return target in profile list and create responses - allow updating target independently from settings values
This commit is contained in:
@@ -7,12 +7,34 @@
|
||||
|
||||
import { Router, Request, Response } from 'express';
|
||||
import { isReservedName, RESERVED_PROFILE_NAMES } from '../../config/reserved-names';
|
||||
import { createApiProfile, removeApiProfile } from '../../api/services/profile-writer';
|
||||
import {
|
||||
createApiProfile,
|
||||
removeApiProfile,
|
||||
updateApiProfileTarget,
|
||||
} from '../../api/services/profile-writer';
|
||||
import { apiProfileExists, listApiProfiles } from '../../api/services/profile-reader';
|
||||
import type { TargetType } from '../../targets/target-adapter';
|
||||
import { updateSettingsFile } from './route-helpers';
|
||||
|
||||
const router = Router();
|
||||
|
||||
function parseTarget(rawTarget: unknown): TargetType | null {
|
||||
if (rawTarget === undefined || rawTarget === null || rawTarget === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof rawTarget !== 'string') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalized = rawTarget.trim().toLowerCase();
|
||||
if (normalized === 'claude' || normalized === 'droid') {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// ==================== Profile CRUD ====================
|
||||
|
||||
/**
|
||||
@@ -26,6 +48,7 @@ router.get('/', (_req: Request, res: Response): void => {
|
||||
name: p.name,
|
||||
settingsPath: p.settingsPath,
|
||||
configured: p.isConfigured,
|
||||
target: p.target,
|
||||
}));
|
||||
res.json({ profiles });
|
||||
} catch (error) {
|
||||
@@ -37,7 +60,13 @@ router.get('/', (_req: Request, res: Response): void => {
|
||||
* POST /api/profiles - Create new profile
|
||||
*/
|
||||
router.post('/', (req: Request, res: Response): void => {
|
||||
const { name, baseUrl, apiKey, model, opusModel, sonnetModel, haikuModel } = req.body;
|
||||
const { name, baseUrl, apiKey, model, opusModel, sonnetModel, haikuModel, target } = req.body;
|
||||
|
||||
const parsedTarget = parseTarget(target);
|
||||
if (target !== undefined && parsedTarget === null) {
|
||||
res.status(400).json({ error: 'Invalid target. Expected: claude or droid' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!name || !baseUrl || !apiKey) {
|
||||
res.status(400).json({ error: 'Missing required fields: name, baseUrl, apiKey' });
|
||||
@@ -60,19 +89,29 @@ router.post('/', (req: Request, res: Response): void => {
|
||||
}
|
||||
|
||||
// Create profile using unified-config-aware service
|
||||
const result = createApiProfile(name, baseUrl, apiKey, {
|
||||
default: model || '',
|
||||
opus: opusModel || model || '',
|
||||
sonnet: sonnetModel || model || '',
|
||||
haiku: haikuModel || model || '',
|
||||
});
|
||||
const result = createApiProfile(
|
||||
name,
|
||||
baseUrl,
|
||||
apiKey,
|
||||
{
|
||||
default: model || '',
|
||||
opus: opusModel || model || '',
|
||||
sonnet: sonnetModel || model || '',
|
||||
haiku: haikuModel || model || '',
|
||||
},
|
||||
parsedTarget || 'claude'
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
res.status(500).json({ error: result.error || 'Failed to create profile' });
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(201).json({ name, settingsPath: result.settingsFile });
|
||||
res.status(201).json({
|
||||
name,
|
||||
settingsPath: result.settingsFile,
|
||||
target: parsedTarget || 'claude',
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -80,7 +119,13 @@ router.post('/', (req: Request, res: Response): void => {
|
||||
*/
|
||||
router.put('/:name', (req: Request, res: Response): void => {
|
||||
const { name } = req.params;
|
||||
const { baseUrl, apiKey, model, opusModel, sonnetModel, haikuModel } = req.body;
|
||||
const { baseUrl, apiKey, model, opusModel, sonnetModel, haikuModel, target } = req.body;
|
||||
|
||||
const parsedTarget = parseTarget(target);
|
||||
if (target !== undefined && parsedTarget === null) {
|
||||
res.status(400).json({ error: 'Invalid target. Expected: claude or droid' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if profile exists (uses unified config when available)
|
||||
if (!apiProfileExists(name)) {
|
||||
@@ -99,8 +144,37 @@ router.put('/:name', (req: Request, res: Response): void => {
|
||||
}
|
||||
|
||||
try {
|
||||
updateSettingsFile(name, { baseUrl, apiKey, model, opusModel, sonnetModel, haikuModel });
|
||||
res.json({ name, updated: true });
|
||||
const hasSettingsUpdates =
|
||||
baseUrl !== undefined ||
|
||||
apiKey !== undefined ||
|
||||
model !== undefined ||
|
||||
opusModel !== undefined ||
|
||||
sonnetModel !== undefined ||
|
||||
haikuModel !== undefined;
|
||||
const hasTargetUpdate = target !== undefined;
|
||||
|
||||
if (!hasSettingsUpdates && !hasTargetUpdate) {
|
||||
res.status(400).json({ error: 'No updates provided' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasSettingsUpdates) {
|
||||
updateSettingsFile(name, { baseUrl, apiKey, model, opusModel, sonnetModel, haikuModel });
|
||||
}
|
||||
|
||||
if (hasTargetUpdate && parsedTarget) {
|
||||
const targetUpdate = updateApiProfileTarget(name, parsedTarget);
|
||||
if (!targetUpdate.success) {
|
||||
res.status(500).json({ error: targetUpdate.error || 'Failed to update target' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
name,
|
||||
updated: true,
|
||||
...(hasTargetUpdate && parsedTarget && { target: parsedTarget }),
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: (error as Error).message });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user