fix(api): add type guard for tier_defaults and extract tiers constant

- Add defensive type guard in misc-routes.ts to prevent runtime crash
  if malformed tier_defaults object is passed to PUT /api/thinking
- Extract VALID_THINKING_TIERS constant to thinking-validator.ts
  to eliminate duplication across validation code
- Export constant from cliproxy barrel file

Addresses P2/P3 items from PR #351 review
This commit is contained in:
kaitranntt
2026-01-21 17:39:54 -05:00
parent 3df6587e8d
commit 299d96c011
3 changed files with 18 additions and 2 deletions
+1
View File
@@ -159,6 +159,7 @@ export {
validateThinking,
THINKING_LEVEL_BUDGETS,
VALID_THINKING_LEVELS,
VALID_THINKING_TIERS,
THINKING_OFF_VALUES,
THINKING_AUTO_VALUE,
THINKING_BUDGET_MIN,
+6
View File
@@ -57,6 +57,12 @@ export const THINKING_LEVEL_RANK: Record<string, number> = {
export const VALID_THINKING_LEVELS = ['minimal', 'low', 'medium', 'high', 'xhigh', 'auto'] as const;
export type ThinkingLevel = (typeof VALID_THINKING_LEVELS)[number];
/**
* Valid tier names for tier_defaults configuration
*/
export const VALID_THINKING_TIERS = ['opus', 'sonnet', 'haiku'] as const;
export type ThinkingTier = (typeof VALID_THINKING_TIERS)[number];
/**
* Cap a level at the model's maximum supported level.
* Returns the capped level and whether capping occurred.
+11 -2
View File
@@ -19,6 +19,7 @@ import {
THINKING_BUDGET_MIN,
THINKING_BUDGET_MAX,
VALID_THINKING_LEVELS,
VALID_THINKING_TIERS,
THINKING_OFF_VALUES,
} from '../../cliproxy';
import { validateFilePath } from './route-helpers';
@@ -326,9 +327,17 @@ router.put('/thinking', (req: Request, res: Response): void => {
// Validate tier_defaults if provided
if (updates.tier_defaults !== undefined) {
if (
typeof updates.tier_defaults !== 'object' ||
updates.tier_defaults === null ||
Array.isArray(updates.tier_defaults)
) {
res.status(400).json({ error: 'Invalid tier_defaults: must be an object' });
return;
}
const validLevels = [...VALID_THINKING_LEVELS] as string[];
for (const [tier, level] of Object.entries(updates.tier_defaults)) {
if (!['opus', 'sonnet', 'haiku'].includes(tier)) {
if (!(VALID_THINKING_TIERS as readonly string[]).includes(tier)) {
res.status(400).json({ error: `Invalid tier: ${tier}` });
return;
}
@@ -352,7 +361,7 @@ router.put('/thinking', (req: Request, res: Response): void => {
return;
}
const validLevels = [...VALID_THINKING_LEVELS] as string[];
const validTiers = ['opus', 'sonnet', 'haiku'];
const validTiers = [...VALID_THINKING_TIERS] as string[];
for (const [provider, tierOverrides] of Object.entries(updates.provider_overrides)) {
if (typeof provider !== 'string' || provider.trim() === '') {
res