mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 10:16:49 +00:00
feat(thinking): improve config validation and codex support
- Add shouldShowWarnings() helper for cleaner warning control - Improve applyThinkingSuffix() regex for accurate suffix detection - Fix provider_overrides validation for nested tier structure - Add thinking section to YAML config export with documentation - Update UI to clarify codex uses reasoning effort levels - Remove unused setManualOverride hook
This commit is contained in:
@@ -25,6 +25,14 @@ import { supportsThinking } from './model-catalog';
|
||||
import { ThinkingConfig } from '../config/unified-config-types';
|
||||
import { validateThinking } from './thinking-validator';
|
||||
|
||||
/**
|
||||
* Check if warnings should be shown based on thinking config.
|
||||
* Defaults to true if show_warnings is not explicitly false.
|
||||
*/
|
||||
function shouldShowWarnings(thinkingConfig: ThinkingConfig): boolean {
|
||||
return thinkingConfig.show_warnings !== false;
|
||||
}
|
||||
|
||||
/** Settings file structure for user overrides */
|
||||
interface ProviderSettings {
|
||||
env: NodeJS.ProcessEnv;
|
||||
@@ -54,14 +62,11 @@ export function detectTierFromModel(modelName: string): ModelTier {
|
||||
* @param model - Base model name
|
||||
* @param thinkingValue - Level name (e.g., 'high') or numeric budget
|
||||
* @returns Model name with thinking suffix, e.g., "gemini-3-pro-preview(high)"
|
||||
*
|
||||
* @note Paren matching only handles one level. Nested parens like "model(foo)(8192)"
|
||||
* will be treated as already having suffix and returned unchanged.
|
||||
*/
|
||||
export function applyThinkingSuffix(model: string, thinkingValue: string | number): string {
|
||||
// Don't apply if already has suffix
|
||||
// NOTE: This only detects ANY paren pair, not proper nesting (e.g., "model(foo)(8192)" passes through)
|
||||
if (model.includes('(') && model.includes(')')) {
|
||||
// Don't apply if model already ends with a parenthesized suffix (e.g., "model(high)" or "model(8192)")
|
||||
// Matches: ends with "(...)" where content is non-empty
|
||||
if (/\([^)]+\)$/.test(model)) {
|
||||
return model;
|
||||
}
|
||||
return `${model}(${thinkingValue})`;
|
||||
@@ -111,7 +116,7 @@ export function applyThinkingConfig(
|
||||
const baseModel = result.ANTHROPIC_MODEL || '';
|
||||
if (!supportsThinking(provider, baseModel)) {
|
||||
// U2: Warn user if they explicitly provided --thinking but model doesn't support it
|
||||
if (thinkingOverride !== undefined && thinkingConfig.show_warnings !== false) {
|
||||
if (thinkingOverride !== undefined && shouldShowWarnings(thinkingConfig)) {
|
||||
console.warn(
|
||||
warn(
|
||||
`Model ${baseModel || 'unknown'} (provider: ${provider}) does not support thinking budget. --thinking flag ignored.`
|
||||
@@ -140,7 +145,7 @@ export function applyThinkingConfig(
|
||||
|
||||
// Validate thinking value against model capabilities
|
||||
const validation = validateThinking(provider, baseModel, thinkingValue);
|
||||
if (validation.warning && thinkingConfig.show_warnings !== false) {
|
||||
if (validation.warning && shouldShowWarnings(thinkingConfig)) {
|
||||
console.warn(warn(validation.warning));
|
||||
}
|
||||
thinkingValue = validation.value;
|
||||
|
||||
@@ -470,6 +470,25 @@ function generateYamlWithComments(config: UnifiedConfig): string {
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
// Thinking section (extended thinking/reasoning configuration)
|
||||
if (config.thinking) {
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push('# Thinking: Extended thinking/reasoning budget configuration');
|
||||
lines.push('# Controls reasoning depth for supported providers (agy, gemini, codex).');
|
||||
lines.push('#');
|
||||
lines.push('# Modes: auto (use tier_defaults), off (disable), manual (--thinking flag only)');
|
||||
lines.push('# Levels: minimal (512), low (1K), medium (8K), high (24K), xhigh (32K), auto');
|
||||
lines.push('# Override: Set global override value (number or level name)');
|
||||
lines.push('# Provider overrides: Per-provider tier defaults');
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push(
|
||||
yaml
|
||||
.dump({ thinking: config.thinking }, { indent: 2, lineWidth: -1, quotingType: '"' })
|
||||
.trim()
|
||||
);
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
// Dashboard auth section (only if configured)
|
||||
if (config.dashboard_auth?.enabled) {
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
|
||||
@@ -341,7 +341,7 @@ router.put('/thinking', (req: Request, res: Response): void => {
|
||||
}
|
||||
}
|
||||
|
||||
// C4: Validate provider_overrides if provided
|
||||
// C4: Validate provider_overrides if provided (nested structure: Record<string, Partial<ThinkingTierDefaults>>)
|
||||
if (updates.provider_overrides !== undefined) {
|
||||
if (
|
||||
typeof updates.provider_overrides !== 'object' ||
|
||||
@@ -352,19 +352,35 @@ router.put('/thinking', (req: Request, res: Response): void => {
|
||||
return;
|
||||
}
|
||||
const validLevels = [...VALID_THINKING_LEVELS] as string[];
|
||||
for (const [provider, level] of Object.entries(updates.provider_overrides)) {
|
||||
const validTiers = ['opus', 'sonnet', 'haiku'];
|
||||
for (const [provider, tierOverrides] of Object.entries(updates.provider_overrides)) {
|
||||
if (typeof provider !== 'string' || provider.trim() === '') {
|
||||
res
|
||||
.status(400)
|
||||
.json({ error: 'Invalid provider_overrides: keys must be non-empty strings' });
|
||||
return;
|
||||
}
|
||||
if (typeof level !== 'string' || !validLevels.includes(level)) {
|
||||
// tierOverrides should be Partial<ThinkingTierDefaults>
|
||||
if (typeof tierOverrides !== 'object' || tierOverrides === null) {
|
||||
res.status(400).json({
|
||||
error: `Invalid level for provider ${provider}: must be one of ${validLevels.join(', ')}`,
|
||||
error: `Invalid provider_overrides for ${provider}: must be an object with tier→level mapping`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
for (const [tier, level] of Object.entries(tierOverrides)) {
|
||||
if (!validTiers.includes(tier)) {
|
||||
res.status(400).json({
|
||||
error: `Invalid tier '${tier}' in provider_overrides.${provider}: must be one of ${validTiers.join(', ')}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (typeof level !== 'string' || !validLevels.includes(level)) {
|
||||
res.status(400).json({
|
||||
error: `Invalid level for provider_overrides.${provider}.${tier}: must be one of ${validLevels.join(', ')}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -141,13 +141,6 @@ export function useThinkingConfig() {
|
||||
[saveConfig]
|
||||
);
|
||||
|
||||
const setManualOverride = useCallback(
|
||||
(value: string | number | undefined) => {
|
||||
saveConfig({ mode: 'manual', override: value });
|
||||
},
|
||||
[saveConfig]
|
||||
);
|
||||
|
||||
return {
|
||||
config: config || DEFAULT_THINKING_CONFIG,
|
||||
loading,
|
||||
@@ -159,6 +152,5 @@ export function useThinkingConfig() {
|
||||
setMode,
|
||||
setTierDefault,
|
||||
setShowWarnings,
|
||||
setManualOverride,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -110,11 +110,14 @@ export default function ThinkingSection() {
|
||||
<Info className="w-4 h-4 text-blue-600 dark:text-blue-400 shrink-0 mt-0.5" />
|
||||
<div className="text-sm text-blue-700 dark:text-blue-300">
|
||||
<p className="font-medium">Supported Providers</p>
|
||||
<p className="mt-1 text-blue-600 dark:text-blue-400">
|
||||
Thinking budget works with: <strong>agy</strong> (Antigravity),{' '}
|
||||
<strong>gemini</strong> (with thinking models). Other providers may ignore this
|
||||
setting.
|
||||
</p>
|
||||
<ul className="mt-1 space-y-0.5 text-blue-600 dark:text-blue-400">
|
||||
<li>
|
||||
Thinking budget: <strong>agy</strong>, <strong>gemini</strong> (token-based)
|
||||
</li>
|
||||
<li>
|
||||
Reasoning effort: <strong>codex</strong> (level-based: medium/high/xhigh)
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user