feat(api): add /api/thinking endpoints for budget config

- GET /api/thinking returns current config

- PUT /api/thinking updates mode/override/tier_defaults

- validate override bounds and level names

Refs #307
This commit is contained in:
kaitranntt
2026-01-08 15:19:57 -05:00
parent 4d361b2ecf
commit 9a2598fb61
+103
View File
@@ -11,7 +11,15 @@ import {
loadOrCreateUnifiedConfig,
saveUnifiedConfig,
getGlobalEnvConfig,
getThinkingConfig,
} from '../../config/unified-config-loader';
import type { ThinkingConfig } from '../../config/unified-config-types';
import {
THINKING_BUDGET_MIN,
THINKING_BUDGET_MAX,
VALID_THINKING_LEVELS,
THINKING_OFF_VALUES,
} from '../../cliproxy';
import { validateFilePath } from './route-helpers';
const router = Router();
@@ -221,4 +229,99 @@ router.put('/global-env', (req: Request, res: Response): void => {
}
});
// ==================== Thinking Configuration ====================
/**
* GET /api/thinking - Get thinking budget configuration
* Returns the thinking section from config.yaml
*/
router.get('/thinking', (_req: Request, res: Response): void => {
try {
const config = getThinkingConfig();
res.json({ config });
} catch (error) {
res.status(500).json({ error: (error as Error).message });
}
});
/**
* PUT /api/thinking - Update thinking budget configuration
* Updates the thinking section in config.yaml
*/
router.put('/thinking', (req: Request, res: Response): void => {
try {
const updates = req.body as Partial<ThinkingConfig>;
const config = loadOrCreateUnifiedConfig();
// Validate mode if provided
if (updates.mode !== undefined) {
const validModes = ['auto', 'off', 'manual'];
if (!validModes.includes(updates.mode)) {
res.status(400).json({ error: `Invalid mode: must be one of ${validModes.join(', ')}` });
return;
}
}
// Validate override if provided (budget or level)
if (updates.override !== undefined) {
if (typeof updates.override === 'number') {
if (
!Number.isFinite(updates.override) ||
updates.override < THINKING_BUDGET_MIN ||
updates.override > THINKING_BUDGET_MAX
) {
res.status(400).json({
error: `Invalid override: must be between ${THINKING_BUDGET_MIN} and ${THINKING_BUDGET_MAX}`,
});
return;
}
} else if (typeof updates.override === 'string') {
const normalizedValue = updates.override.toLowerCase().trim();
const validValues = [...VALID_THINKING_LEVELS, ...THINKING_OFF_VALUES] as readonly string[];
if (!validValues.includes(normalizedValue)) {
res.status(400).json({
error: `Invalid override: must be a level name (${VALID_THINKING_LEVELS.join(', ')}) or a number`,
});
return;
}
}
}
// Validate tier_defaults if provided
if (updates.tier_defaults !== undefined) {
const validLevels = [...VALID_THINKING_LEVELS] as string[];
for (const [tier, level] of Object.entries(updates.tier_defaults)) {
if (!['opus', 'sonnet', 'haiku'].includes(tier)) {
res.status(400).json({ error: `Invalid tier: ${tier}` });
return;
}
if (!validLevels.includes(level)) {
res.status(400).json({
error: `Invalid level for ${tier}: must be one of ${validLevels.join(', ')}`,
});
return;
}
}
}
// Update thinking section
config.thinking = {
mode: updates.mode ?? config.thinking?.mode ?? 'auto',
override: updates.override ?? config.thinking?.override,
tier_defaults: {
opus: updates.tier_defaults?.opus ?? config.thinking?.tier_defaults?.opus ?? 'high',
sonnet: updates.tier_defaults?.sonnet ?? config.thinking?.tier_defaults?.sonnet ?? 'medium',
haiku: updates.tier_defaults?.haiku ?? config.thinking?.tier_defaults?.haiku ?? 'low',
},
provider_overrides: updates.provider_overrides ?? config.thinking?.provider_overrides,
show_warnings: updates.show_warnings ?? config.thinking?.show_warnings ?? true,
};
saveUnifiedConfig(config);
res.json({ success: true, config: config.thinking });
} catch (error) {
res.status(500).json({ error: (error as Error).message });
}
});
export default router;