mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-12 20:15:40 +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:
1 parent
eec44d54e2
commit
19b7a49eee
5 files changed
+60
-25
No files matched your search
@@ -25,6 +25,14 @@ import { supportsThinking } from './model-catalog';
|
|||||||
import { ThinkingConfig } from '../config/unified-config-types';
|
import { ThinkingConfig } from '../config/unified-config-types';
|
||||||
import { validateThinking } from './thinking-validator';
|
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 */
|
/** Settings file structure for user overrides */
|
||||||
interface ProviderSettings {
|
interface ProviderSettings {
|
||||||
env: NodeJS.ProcessEnv;
|
env: NodeJS.ProcessEnv;
|
||||||
@@ -54,14 +62,11 @@ export function detectTierFromModel(modelName: string): ModelTier {
|
|||||||
* @param model - Base model name
|
* @param model - Base model name
|
||||||
* @param thinkingValue - Level name (e.g., 'high') or numeric budget
|
* @param thinkingValue - Level name (e.g., 'high') or numeric budget
|
||||||
* @returns Model name with thinking suffix, e.g., "gemini-3-pro-preview(high)"
|
* @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 {
|
export function applyThinkingSuffix(model: string, thinkingValue: string | number): string {
|
||||||
// Don't apply if already has suffix
|
// Don't apply if model already ends with a parenthesized suffix (e.g., "model(high)" or "model(8192)")
|
||||||
// NOTE: This only detects ANY paren pair, not proper nesting (e.g., "model(foo)(8192)" passes through)
|
// Matches: ends with "(...)" where content is non-empty
|
||||||
if (model.includes('(') && model.includes(')')) {
|
if (/\([^)]+\)$/.test(model)) {
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
return `${model}(${thinkingValue})`;
|
return `${model}(${thinkingValue})`;
|
||||||
@@ -111,7 +116,7 @@ export function applyThinkingConfig(
|
|||||||
const baseModel = result.ANTHROPIC_MODEL || '';
|
const baseModel = result.ANTHROPIC_MODEL || '';
|
||||||
if (!supportsThinking(provider, baseModel)) {
|
if (!supportsThinking(provider, baseModel)) {
|
||||||
// U2: Warn user if they explicitly provided --thinking but model doesn't support it
|
// 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(
|
console.warn(
|
||||||
warn(
|
warn(
|
||||||
`Model ${baseModel || 'unknown'} (provider: ${provider}) does not support thinking budget. --thinking flag ignored.`
|
`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
|
// Validate thinking value against model capabilities
|
||||||
const validation = validateThinking(provider, baseModel, thinkingValue);
|
const validation = validateThinking(provider, baseModel, thinkingValue);
|
||||||
if (validation.warning && thinkingConfig.show_warnings !== false) {
|
if (validation.warning && shouldShowWarnings(thinkingConfig)) {
|
||||||
console.warn(warn(validation.warning));
|
console.warn(warn(validation.warning));
|
||||||
}
|
}
|
||||||
thinkingValue = validation.value;
|
thinkingValue = validation.value;
|
||||||
|
|||||||
@@ -470,6 +470,25 @@ function generateYamlWithComments(config: UnifiedConfig): string {
|
|||||||
lines.push('');
|
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)
|
// Dashboard auth section (only if configured)
|
||||||
if (config.dashboard_auth?.enabled) {
|
if (config.dashboard_auth?.enabled) {
|
||||||
lines.push('# ----------------------------------------------------------------------------');
|
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 (updates.provider_overrides !== undefined) {
|
||||||
if (
|
if (
|
||||||
typeof updates.provider_overrides !== 'object' ||
|
typeof updates.provider_overrides !== 'object' ||
|
||||||
@@ -352,19 +352,35 @@ router.put('/thinking', (req: Request, res: Response): void => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const validLevels = [...VALID_THINKING_LEVELS] as string[];
|
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() === '') {
|
if (typeof provider !== 'string' || provider.trim() === '') {
|
||||||
res
|
res
|
||||||
.status(400)
|
.status(400)
|
||||||
.json({ error: 'Invalid provider_overrides: keys must be non-empty strings' });
|
.json({ error: 'Invalid provider_overrides: keys must be non-empty strings' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (typeof level !== 'string' || !validLevels.includes(level)) {
|
// tierOverrides should be Partial<ThinkingTierDefaults>
|
||||||
|
if (typeof tierOverrides !== 'object' || tierOverrides === null) {
|
||||||
res.status(400).json({
|
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;
|
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]
|
[saveConfig]
|
||||||
);
|
);
|
||||||
|
|
||||||
const setManualOverride = useCallback(
|
|
||||||
(value: string | number | undefined) => {
|
|
||||||
saveConfig({ mode: 'manual', override: value });
|
|
||||||
},
|
|
||||||
[saveConfig]
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
config: config || DEFAULT_THINKING_CONFIG,
|
config: config || DEFAULT_THINKING_CONFIG,
|
||||||
loading,
|
loading,
|
||||||
@@ -159,6 +152,5 @@ export function useThinkingConfig() {
|
|||||||
setMode,
|
setMode,
|
||||||
setTierDefault,
|
setTierDefault,
|
||||||
setShowWarnings,
|
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" />
|
<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">
|
<div className="text-sm text-blue-700 dark:text-blue-300">
|
||||||
<p className="font-medium">Supported Providers</p>
|
<p className="font-medium">Supported Providers</p>
|
||||||
<p className="mt-1 text-blue-600 dark:text-blue-400">
|
<ul className="mt-1 space-y-0.5 text-blue-600 dark:text-blue-400">
|
||||||
Thinking budget works with: <strong>agy</strong> (Antigravity),{' '}
|
<li>
|
||||||
<strong>gemini</strong> (with thinking models). Other providers may ignore this
|
Thinking budget: <strong>agy</strong>, <strong>gemini</strong> (token-based)
|
||||||
setting.
|
</li>
|
||||||
</p>
|
<li>
|
||||||
|
Reasoning effort: <strong>codex</strong> (level-based: medium/high/xhigh)
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user