From 299d96c01186fd065e5454edb7cb9aee6ab12bb0 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Wed, 21 Jan 2026 17:39:54 -0500 Subject: [PATCH] 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 --- src/cliproxy/index.ts | 1 + src/cliproxy/thinking-validator.ts | 6 ++++++ src/web-server/routes/misc-routes.ts | 13 +++++++++++-- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/cliproxy/index.ts b/src/cliproxy/index.ts index a89c7bc7..df83c881 100644 --- a/src/cliproxy/index.ts +++ b/src/cliproxy/index.ts @@ -159,6 +159,7 @@ export { validateThinking, THINKING_LEVEL_BUDGETS, VALID_THINKING_LEVELS, + VALID_THINKING_TIERS, THINKING_OFF_VALUES, THINKING_AUTO_VALUE, THINKING_BUDGET_MIN, diff --git a/src/cliproxy/thinking-validator.ts b/src/cliproxy/thinking-validator.ts index e64e1c46..c99e424e 100644 --- a/src/cliproxy/thinking-validator.ts +++ b/src/cliproxy/thinking-validator.ts @@ -57,6 +57,12 @@ export const THINKING_LEVEL_RANK: Record = { 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. diff --git a/src/web-server/routes/misc-routes.ts b/src/web-server/routes/misc-routes.ts index 712b56f7..98f933fe 100644 --- a/src/web-server/routes/misc-routes.ts +++ b/src/web-server/routes/misc-routes.ts @@ -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