mirror of
https://github.com/tiennm99/ccs.git
synced 2026-08-05 12:22:35 +00:00
feat(cliproxy): accept target in variant routes
- validate target request payloads for create and update APIs - pass target into single/composite service operations - include target in variant API responses
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
import { Router, Request, Response } from 'express';
|
import { Router, Request, Response } from 'express';
|
||||||
import { isReservedName, RESERVED_PROFILE_NAMES } from '../../config/reserved-names';
|
import { isReservedName, RESERVED_PROFILE_NAMES } from '../../config/reserved-names';
|
||||||
import type { CLIProxyProvider } from '../../cliproxy/types';
|
import type { CLIProxyProvider } from '../../cliproxy/types';
|
||||||
|
import type { TargetType } from '../../targets/target-adapter';
|
||||||
import {
|
import {
|
||||||
createVariant,
|
createVariant,
|
||||||
removeVariant,
|
removeVariant,
|
||||||
@@ -23,6 +24,23 @@ import {
|
|||||||
|
|
||||||
const router = Router();
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/cliproxy - List cliproxy variants
|
* GET /api/cliproxy - List cliproxy variants
|
||||||
* Uses variant-service for consistent behavior with CLI
|
* Uses variant-service for consistent behavior with CLI
|
||||||
@@ -36,6 +54,7 @@ router.get('/', (_req: Request, res: Response) => {
|
|||||||
account: variant.account || 'default',
|
account: variant.account || 'default',
|
||||||
port: variant.port, // Include port for port isolation
|
port: variant.port, // Include port for port isolation
|
||||||
model: variant.model,
|
model: variant.model,
|
||||||
|
target: variant.target || 'claude',
|
||||||
type: variant.type,
|
type: variant.type,
|
||||||
default_tier: variant.default_tier,
|
default_tier: variant.default_tier,
|
||||||
tiers: variant.tiers,
|
tiers: variant.tiers,
|
||||||
@@ -50,6 +69,12 @@ router.get('/', (_req: Request, res: Response) => {
|
|||||||
*/
|
*/
|
||||||
router.post('/', (req: Request, res: Response): void => {
|
router.post('/', (req: Request, res: Response): void => {
|
||||||
const { name, provider, model, account, type, default_tier, tiers } = req.body;
|
const { name, provider, model, account, type, default_tier, tiers } = req.body;
|
||||||
|
const parsedTarget = parseTarget(req.body.target);
|
||||||
|
|
||||||
|
if (req.body.target !== undefined && parsedTarget === null) {
|
||||||
|
res.status(400).json({ error: 'Invalid target. Expected: claude or droid' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
res.status(400).json({ error: 'Missing required field: name' });
|
res.status(400).json({ error: 'Missing required field: name' });
|
||||||
@@ -91,7 +116,12 @@ router.post('/', (req: Request, res: Response): void => {
|
|||||||
|
|
||||||
let result;
|
let result;
|
||||||
try {
|
try {
|
||||||
result = createCompositeVariant({ name, defaultTier: default_tier, tiers });
|
result = createCompositeVariant({
|
||||||
|
name,
|
||||||
|
defaultTier: default_tier,
|
||||||
|
target: parsedTarget || 'claude',
|
||||||
|
tiers,
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(400).json({ error: (error as Error).message });
|
res.status(400).json({ error: (error as Error).message });
|
||||||
return;
|
return;
|
||||||
@@ -109,6 +139,7 @@ router.post('/', (req: Request, res: Response): void => {
|
|||||||
tiers,
|
tiers,
|
||||||
settings: result.settingsPath,
|
settings: result.settingsPath,
|
||||||
port: result.variant?.port,
|
port: result.variant?.port,
|
||||||
|
target: result.variant?.target || 'claude',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -126,7 +157,13 @@ router.post('/', (req: Request, res: Response): void => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Use variant-service for proper port allocation
|
// Use variant-service for proper port allocation
|
||||||
const result = createVariant(name, provider as CLIProxyProvider, model, account);
|
const result = createVariant(
|
||||||
|
name,
|
||||||
|
provider as CLIProxyProvider,
|
||||||
|
model,
|
||||||
|
account,
|
||||||
|
parsedTarget || 'claude'
|
||||||
|
);
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
res.status(409).json({ error: result.error });
|
res.status(409).json({ error: result.error });
|
||||||
@@ -140,6 +177,7 @@ router.post('/', (req: Request, res: Response): void => {
|
|||||||
account: account || 'default',
|
account: account || 'default',
|
||||||
port: result.variant?.port,
|
port: result.variant?.port,
|
||||||
model: result.variant?.model,
|
model: result.variant?.model,
|
||||||
|
target: result.variant?.target || 'claude',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -154,6 +192,12 @@ router.put('/:name', (req: Request, res: Response): void => {
|
|||||||
try {
|
try {
|
||||||
const { name } = req.params;
|
const { name } = req.params;
|
||||||
const { provider, account, model, default_tier, tiers } = req.body;
|
const { provider, account, model, default_tier, tiers } = req.body;
|
||||||
|
const parsedTarget = parseTarget(req.body.target);
|
||||||
|
|
||||||
|
if (req.body.target !== undefined && parsedTarget === null) {
|
||||||
|
res.status(400).json({ error: 'Invalid target. Expected: claude or droid' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if variant is composite - use updateCompositeVariant if so
|
// Check if variant is composite - use updateCompositeVariant if so
|
||||||
const variants = listVariants();
|
const variants = listVariants();
|
||||||
@@ -165,8 +209,8 @@ router.put('/:name', (req: Request, res: Response): void => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (existing.type === 'composite') {
|
if (existing.type === 'composite') {
|
||||||
if (!default_tier && !tiers) {
|
if (!default_tier && !tiers && req.body.target === undefined) {
|
||||||
res.status(400).json({ error: 'Must provide at least default_tier or tiers' });
|
res.status(400).json({ error: 'Must provide at least default_tier, tiers, or target' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,7 +233,11 @@ router.put('/:name', (req: Request, res: Response): void => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = updateCompositeVariant(name, { defaultTier: default_tier, tiers });
|
const result = updateCompositeVariant(name, {
|
||||||
|
defaultTier: default_tier,
|
||||||
|
tiers,
|
||||||
|
target: req.body.target !== undefined && parsedTarget ? parsedTarget : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
const status = result.error?.includes('not found') ? 404 : 400;
|
const status = result.error?.includes('not found') ? 404 : 400;
|
||||||
@@ -207,13 +255,19 @@ router.put('/:name', (req: Request, res: Response): void => {
|
|||||||
tiers: persisted?.tiers,
|
tiers: persisted?.tiers,
|
||||||
settings: persisted?.settings,
|
settings: persisted?.settings,
|
||||||
port: persisted?.port,
|
port: persisted?.port,
|
||||||
|
target: persisted?.target || 'claude',
|
||||||
updated: true,
|
updated: true,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use variant-service for proper update handling (single provider)
|
// Use variant-service for proper update handling (single provider)
|
||||||
const result = updateVariant(name, { provider, account, model });
|
const result = updateVariant(name, {
|
||||||
|
provider,
|
||||||
|
account,
|
||||||
|
model,
|
||||||
|
target: req.body.target !== undefined && parsedTarget ? parsedTarget : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
const status = result.error?.includes('not found') ? 404 : 400;
|
const status = result.error?.includes('not found') ? 404 : 400;
|
||||||
@@ -227,6 +281,7 @@ router.put('/:name', (req: Request, res: Response): void => {
|
|||||||
account: result.variant?.account || 'default',
|
account: result.variant?.account || 'default',
|
||||||
settings: result.variant?.settings,
|
settings: result.variant?.settings,
|
||||||
port: result.variant?.port,
|
port: result.variant?.port,
|
||||||
|
target: result.variant?.target || 'claude',
|
||||||
updated: true,
|
updated: true,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user