From c48ed2ea7fe7fcf0725805274233bd5760f284d8 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Thu, 19 Feb 2026 12:45:23 +0700 Subject: [PATCH] feat(thinking): complete thinking UX/DX for all providers - Fix codex off-mode bug: add disableEffort to CodexReasoningProxy so mode=off actually skips reasoning injection - Add CCS_THINKING env var (priority: flag > env > config) - Add startup feedback: [i] Thinking: () - Add ccs config thinking CLI subcommand with --mode, --override, --tier, --provider-override, --clear-override - Dashboard: add manual override selector when mode=manual - Dashboard: add provider overrides collapsible section - Dashboard: update info box with CCS_THINKING env var docs - Update help-command.ts with CCS_THINKING and config thinking Closes #583 --- src/cliproxy/codex-reasoning-proxy.ts | 24 +- src/cliproxy/executor/index.ts | 52 +++- src/commands/config-command.ts | 14 + src/commands/config-thinking-command.ts | 279 ++++++++++++++++++ src/commands/help-command.ts | 3 + .../settings/hooks/use-thinking-config.ts | 38 +++ .../settings/sections/thinking/index.tsx | 117 +++++++- 7 files changed, 515 insertions(+), 12 deletions(-) create mode 100644 src/commands/config-thinking-command.ts diff --git a/src/cliproxy/codex-reasoning-proxy.ts b/src/cliproxy/codex-reasoning-proxy.ts index 0558c2ab..0ab9b976 100644 --- a/src/cliproxy/codex-reasoning-proxy.ts +++ b/src/cliproxy/codex-reasoning-proxy.ts @@ -25,6 +25,8 @@ export interface CodexReasoningProxyConfig { * Example: '/api/provider/codex' will transform '/api/provider/codex/v1/messages' to '/v1/messages' */ stripPathPrefix?: string; + /** When true, skip reasoning effort injection entirely (thinking mode: off) */ + disableEffort?: boolean; } const EXTENDED_CONTEXT_SUFFIX_REGEX = /\[1m\]$/i; @@ -147,7 +149,12 @@ export class CodexReasoningProxy { private readonly config: Required< Pick< CodexReasoningProxyConfig, - 'upstreamBaseUrl' | 'verbose' | 'timeoutMs' | 'defaultEffort' | 'traceFilePath' + | 'upstreamBaseUrl' + | 'verbose' + | 'timeoutMs' + | 'defaultEffort' + | 'traceFilePath' + | 'disableEffort' > > & Pick; @@ -170,6 +177,7 @@ export class CodexReasoningProxy { defaultEffort: config.defaultEffort ?? 'medium', traceFilePath: config.traceFilePath ?? '', stripPathPrefix: config.stripPathPrefix, + disableEffort: config.disableEffort ?? false, }; this.modelEffort = buildCodexModelEffortMap(this.config.modelMap, this.config.defaultEffort); } @@ -330,6 +338,20 @@ export class CodexReasoningProxy { ? stripExtendedContextSuffix(originalModel) : null; + // When effort is disabled (thinking mode: off), strip model suffix but don't inject reasoning + if (this.config.disableEffort) { + const suffixParsed = normalizedRequestModel + ? parseModelEffortSuffix(normalizedRequestModel) + : null; + const upstreamModel = suffixParsed?.upstreamModel ?? normalizedRequestModel; + const forwarded = + upstreamModel && isRecord(parsed) ? { ...parsed, model: upstreamModel } : parsed; + + this.log(`[disabled] model=${originalModel ?? 'null'} -> passthrough (no reasoning)`); + await this.forwardJson(req, res, fullUpstreamUrl, forwarded); + return; + } + // Support "model aliases" like `gpt-5.2-codex-xhigh` by translating to: // - upstream model: `gpt-5.2-codex` // - reasoning.effort: `xhigh` diff --git a/src/cliproxy/executor/index.ts b/src/cliproxy/executor/index.ts index edc701e4..d3c87a64 100644 --- a/src/cliproxy/executor/index.ts +++ b/src/cliproxy/executor/index.ts @@ -49,7 +49,7 @@ import { installWebSearchHook, displayWebSearchStatus, } from '../../utils/websearch-manager'; -import { loadOrCreateUnifiedConfig } from '../../config/unified-config-loader'; +import { loadOrCreateUnifiedConfig, getThinkingConfig } from '../../config/unified-config-loader'; import { installImageAnalyzerHook } from '../../utils/hooks'; import { HttpsTunnelProxy } from '../https-tunnel-proxy'; import { isKiroAuthMethod, KiroAuthMethod, normalizeKiroAuthMethod } from '../auth/auth-types'; @@ -354,7 +354,20 @@ export async function execClaudeWithCLIProxy( process.exit(1); } - const thinkingOverride = thinkingParse.value; + // Priority: CLI flag > CCS_THINKING env var > config.yaml + let thinkingOverride = thinkingParse.value; + let thinkingSource: 'flag' | 'env' | 'config' | undefined = + thinkingOverride !== undefined ? 'flag' : undefined; + + if (thinkingOverride === undefined && process.env.CCS_THINKING) { + const envVal = process.env.CCS_THINKING.trim(); + if (envVal) { + // Parse same as CLI: integer string → number, else string + thinkingOverride = /^-?\d+$/.test(envVal) ? Number.parseInt(envVal, 10) : envVal; + thinkingSource = 'env'; + } + } + if (thinkingParse.duplicateDisplays.length > 0) { console.warn( `[!] Multiple reasoning flags detected. Using first occurrence: ${thinkingParse.sourceDisplay}` @@ -804,10 +817,18 @@ export async function execClaudeWithCLIProxy( process.env.CCS_CODEX_REASONING_TRACE === '1' || process.env.CCS_CODEX_REASONING_TRACE === 'true'; const stripPathPrefix = useRemoteProxy ? '/api/provider/codex' : undefined; + const thinkingCfg = getThinkingConfig(); + const codexThinkingOff = + (thinkingCfg.mode === 'off' && thinkingOverride === undefined) || + thinkingOverride === 'off' || + (thinkingOverride === undefined && + thinkingCfg.mode === 'manual' && + thinkingCfg.override === 'off'); codexReasoningProxy = new CodexReasoningProxy({ upstreamBaseUrl: postSanitizationBaseUrl, verbose, defaultEffort: 'medium', + disableEffort: codexThinkingOff, traceFilePath: traceEnabled ? path.join(getCcsDir(), 'codex-reasoning-proxy.log') : '', modelMap: { defaultModel: initialEnvVars.ANTHROPIC_MODEL, @@ -875,6 +896,33 @@ export async function execClaudeWithCLIProxy( const webSearchEnv = getWebSearchHookEnv(); logEnvironment(env, webSearchEnv, verbose); + // 11b. Print thinking status feedback (TTY only, non-piped sessions) + if (process.stderr.isTTY) { + const thinkingCfgStatus = getThinkingConfig(); + let thinkingLabel: string; + let sourceLabel: string; + + if (thinkingOverride === 'off' || thinkingCfgStatus.mode === 'off') { + thinkingLabel = 'off'; + sourceLabel = + thinkingSource === 'flag' ? 'flag' : thinkingSource === 'env' ? 'env' : 'config'; + } else if (thinkingSource === 'flag') { + thinkingLabel = String(thinkingOverride); + sourceLabel = `flag: ${thinkingParse.sourceDisplay}`; + } else if (thinkingSource === 'env') { + thinkingLabel = String(thinkingOverride); + sourceLabel = 'env: CCS_THINKING'; + } else if (thinkingCfgStatus.mode === 'manual' && thinkingCfgStatus.override !== undefined) { + thinkingLabel = String(thinkingCfgStatus.override); + sourceLabel = 'config: manual'; + } else { + thinkingLabel = thinkingCfgStatus.mode === 'auto' ? 'auto' : 'default'; + sourceLabel = 'config: auto'; + } + + console.error(`[i] Thinking: ${thinkingLabel} (${sourceLabel})`); + } + // 12. Filter CCS-specific flags before passing to Claude CLI const ccsFlags = [ '--auth', diff --git a/src/commands/config-command.ts b/src/commands/config-command.ts index 1db499e1..faffcaf7 100644 --- a/src/commands/config-command.ts +++ b/src/commands/config-command.ts @@ -68,6 +68,11 @@ function showHelp(): void { console.log(' --timeout Set analysis timeout (seconds)'); console.log(' --set-model

Set model for provider'); console.log(''); + console.log(' thinking Manage thinking/reasoning settings'); + console.log(' --mode Set mode (auto, off, manual)'); + console.log(' --override Set persistent override level'); + console.log(' --tier Set tier default level'); + console.log(''); console.log('Options:'); console.log(' --port, -p PORT Specify server port (default: auto-detect)'); console.log(' --dev Development mode with Vite HMR'); @@ -80,6 +85,8 @@ function showHelp(): void { console.log(' ccs config auth setup Configure dashboard login'); console.log(' ccs config image-analysis Show image settings'); console.log(' ccs config image-analysis --enable Enable feature'); + console.log(' ccs config thinking Show thinking settings'); + console.log(' ccs config thinking --mode auto Set auto mode'); console.log(''); } @@ -101,6 +108,13 @@ export async function handleConfigCommand(args: string[]): Promise { return; } + // Route thinking subcommand + if (args[0] === 'thinking') { + const { handleConfigThinkingCommand } = await import('./config-thinking-command'); + await handleConfigThinkingCommand(args.slice(1)); + return; + } + await initUI(); const options = parseArgs(args); diff --git a/src/commands/config-thinking-command.ts b/src/commands/config-thinking-command.ts new file mode 100644 index 00000000..7b35e6a4 --- /dev/null +++ b/src/commands/config-thinking-command.ts @@ -0,0 +1,279 @@ +/** + * Config Thinking Command Handler + * + * Manages thinking section of config.yaml via CLI. + * Usage: ccs config thinking [options] + */ + +import { initUI, header, ok, info, warn, fail, subheader, color, dim } from '../utils/ui'; +import { + getThinkingConfig, + updateUnifiedConfig, + loadOrCreateUnifiedConfig, +} from '../config/unified-config-loader'; +import { DEFAULT_THINKING_TIER_DEFAULTS } from '../config/unified-config-types'; +import { VALID_THINKING_LEVELS } from '../cliproxy/thinking-validator'; + +const VALID_THINKING_MODES = ['auto', 'off', 'manual'] as const; + +interface ThinkingCommandOptions { + mode?: string; + override?: string; + clearOverride?: boolean; + tier?: { tier: string; level: string }; + providerOverride?: { provider: string; tier: string; level: string }; + help?: boolean; +} + +const VALID_TIERS = ['opus', 'sonnet', 'haiku'] as const; + +function parseArgs(args: string[]): ThinkingCommandOptions { + const options: ThinkingCommandOptions = {}; + + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + + if (arg === '--mode' && args[i + 1]) { + options.mode = args[++i]; + } else if (arg === '--override' && args[i + 1]) { + options.override = args[++i]; + } else if (arg === '--clear-override') { + options.clearOverride = true; + } else if (arg === '--tier' && args[i + 1] && args[i + 2]) { + options.tier = { tier: args[++i], level: args[++i] }; + } else if (arg === '--provider-override' && args[i + 1] && args[i + 2] && args[i + 3]) { + options.providerOverride = { + provider: args[++i], + tier: args[++i], + level: args[++i], + }; + } else if (arg === '--help' || arg === '-h') { + options.help = true; + } + } + + return options; +} + +function showHelp(): void { + console.log(''); + console.log(header('ccs config thinking')); + console.log(''); + console.log(' Configure extended thinking/reasoning for CLIProxy providers.'); + console.log(''); + + console.log(subheader('Usage:')); + console.log(` ${color('ccs config thinking', 'command')} [options]`); + console.log(''); + + console.log(subheader('Options:')); + console.log( + ` ${color('--mode ', 'command')} Set mode (auto, off, manual)` + ); + console.log( + ` ${color('--override ', 'command')} Set persistent override (manual mode)` + ); + console.log( + ` ${color('--clear-override', 'command')} Remove persistent override` + ); + console.log( + ` ${color('--tier ', 'command')} Set tier default (opus/sonnet/haiku)` + ); + console.log( + ` ${color('--provider-override

', 'command')} Set provider-specific tier override` + ); + console.log(` ${color('--help, -h', 'command')} Show this help`); + console.log(''); + + console.log(subheader('Levels:')); + console.log( + ` ${dim('minimal (512), low (1K), medium (8K), high (24K), xhigh (32K), auto, off')}` + ); + console.log(''); + + console.log(subheader('Examples:')); + console.log( + ` $ ${color('ccs config thinking', 'command')} ${dim('# Show status')}` + ); + console.log( + ` $ ${color('ccs config thinking --mode auto', 'command')} ${dim('# Auto mode')}` + ); + console.log( + ` $ ${color('ccs config thinking --mode manual --override high', 'command')} ${dim('# Persistent high')}` + ); + console.log( + ` $ ${color('ccs config thinking --tier opus xhigh', 'command')} ${dim('# Opus -> xhigh')}` + ); + console.log( + ` $ ${color('ccs config thinking --provider-override codex opus xhigh', 'command')}` + ); + console.log(''); + + console.log(subheader('Environment:')); + console.log( + ` ${color('CCS_THINKING', 'command')} Override per-session via env var (priority: flag > env > config)` + ); + console.log(` ${dim('Example: CCS_THINKING=high ccs codex "debug this"')}`); + console.log(''); +} + +function showStatus(): void { + const config = getThinkingConfig(); + + console.log(''); + console.log(header('Thinking Configuration')); + console.log(''); + + // Mode + const modeText = + config.mode === 'auto' ? ok('Auto') : config.mode === 'off' ? warn('Off') : info('Manual'); + console.log(` Mode: ${modeText}`); + + // Override + if (config.override !== undefined) { + console.log(` Override: ${color(String(config.override), 'command')}`); + } + + // Warnings + console.log(` Warnings: ${config.show_warnings !== false ? 'on' : 'off'}`); + console.log(''); + + // Tier defaults + console.log(subheader('Tier Defaults:')); + for (const tier of VALID_TIERS) { + const level = config.tier_defaults?.[tier] ?? DEFAULT_THINKING_TIER_DEFAULTS[tier]; + const isDefault = level === DEFAULT_THINKING_TIER_DEFAULTS[tier]; + const suffix = isDefault ? dim(' (default)') : ''; + console.log(` ${color(tier.padEnd(10), 'command')} ${level}${suffix}`); + } + console.log(''); + + // Provider overrides + const overrides = config.provider_overrides; + if (overrides && Object.keys(overrides).length > 0) { + console.log(subheader('Provider Overrides:')); + for (const [provider, tierOverrides] of Object.entries(overrides)) { + const parts = Object.entries(tierOverrides) + .map(([t, l]) => `${t}=${l}`) + .join(', '); + console.log(` ${color(provider.padEnd(10), 'command')} ${parts}`); + } + console.log(''); + } + + // Config location + console.log(subheader('Configuration:')); + console.log(` File: ${color('~/.ccs/config.yaml', 'path')}`); + console.log(` Section: ${dim('thinking')}`); + console.log(''); + + // Env var hint + if (process.env.CCS_THINKING) { + console.log(info(`CCS_THINKING env var active: ${process.env.CCS_THINKING}`)); + console.log(''); + } +} + +export async function handleConfigThinkingCommand(args: string[]): Promise { + await initUI(); + + const options = parseArgs(args); + + if (options.help) { + showHelp(); + return; + } + + let hasChanges = false; + const config = loadOrCreateUnifiedConfig(); + const thinkingConfig = config.thinking ?? { + mode: 'auto' as const, + tier_defaults: { ...DEFAULT_THINKING_TIER_DEFAULTS }, + show_warnings: true, + }; + + // Validate and apply --mode + if (options.mode !== undefined) { + if (!(VALID_THINKING_MODES as readonly string[]).includes(options.mode)) { + console.error(fail(`Invalid mode: ${options.mode}`)); + console.error(info(`Valid modes: ${VALID_THINKING_MODES.join(', ')}`)); + process.exit(1); + } + thinkingConfig.mode = options.mode as 'auto' | 'off' | 'manual'; + hasChanges = true; + } + + // Validate and apply --override + if (options.override !== undefined) { + const normalized = options.override.toLowerCase().trim(); + if ( + !(VALID_THINKING_LEVELS as readonly string[]).includes(normalized) && + !/^\d+$/.test(normalized) + ) { + console.error(fail(`Invalid override: ${options.override}`)); + console.error(info(`Valid levels: ${VALID_THINKING_LEVELS.join(', ')}, or a number`)); + process.exit(1); + } + thinkingConfig.override = /^\d+$/.test(normalized) + ? Number.parseInt(normalized, 10) + : normalized; + hasChanges = true; + } + + // Apply --clear-override + if (options.clearOverride) { + thinkingConfig.override = undefined; + hasChanges = true; + } + + // Validate and apply --tier + if (options.tier) { + const { tier, level } = options.tier; + if (!(VALID_TIERS as readonly string[]).includes(tier)) { + console.error(fail(`Invalid tier: ${tier}`)); + console.error(info(`Valid tiers: ${VALID_TIERS.join(', ')}`)); + process.exit(1); + } + if (!(VALID_THINKING_LEVELS as readonly string[]).includes(level.toLowerCase())) { + console.error(fail(`Invalid level for ${tier}: ${level}`)); + console.error(info(`Valid levels: ${VALID_THINKING_LEVELS.join(', ')}`)); + process.exit(1); + } + thinkingConfig.tier_defaults = { + ...DEFAULT_THINKING_TIER_DEFAULTS, + ...thinkingConfig.tier_defaults, + [tier]: level.toLowerCase(), + }; + hasChanges = true; + } + + // Validate and apply --provider-override + if (options.providerOverride) { + const { provider, tier, level } = options.providerOverride; + if (!(VALID_TIERS as readonly string[]).includes(tier)) { + console.error(fail(`Invalid tier: ${tier}`)); + process.exit(1); + } + if (!(VALID_THINKING_LEVELS as readonly string[]).includes(level.toLowerCase())) { + console.error(fail(`Invalid level: ${level}`)); + process.exit(1); + } + thinkingConfig.provider_overrides = { + ...thinkingConfig.provider_overrides, + [provider]: { + ...thinkingConfig.provider_overrides?.[provider], + [tier]: level.toLowerCase(), + }, + }; + hasChanges = true; + } + + if (hasChanges) { + updateUnifiedConfig({ thinking: thinkingConfig }); + console.log(ok('Configuration updated')); + console.log(''); + } + + // Always show current status + showStatus(); +} diff --git a/src/commands/help-command.ts b/src/commands/help-command.ts index 868d1060..2ee3fcb8 100644 --- a/src/commands/help-command.ts +++ b/src/commands/help-command.ts @@ -282,6 +282,8 @@ Run ${color('ccs config', 'command')} for web dashboard`.trim(); ['ccs config auth show', 'Show dashboard auth status'], ['ccs config image-analysis', 'Show image analysis settings'], ['ccs config image-analysis --enable', 'Enable image analysis'], + ['ccs config thinking', 'Show thinking/reasoning settings'], + ['ccs config thinking --mode auto', 'Set thinking mode'], ['ccs config --port 3000', 'Use specific port'], ['ccs persist ', 'Write profile env to ~/.claude/settings.json'], ['ccs persist --list-backups', 'List available settings.json backups'], @@ -404,6 +406,7 @@ Run ${color('ccs config', 'command')} for web dashboard`.trim(); ['CCS_DIR', 'Override CCS config directory (default: ~/.ccs)'], ['CCS_HOME', 'Override home directory (legacy, appends .ccs)'], ['CCS_DEBUG', 'Enable debug logging'], + ['CCS_THINKING', 'Override thinking level (flag > env > config)'], ]); // CLI Proxy env vars diff --git a/ui/src/pages/settings/hooks/use-thinking-config.ts b/ui/src/pages/settings/hooks/use-thinking-config.ts index 4decf2f9..d73ef659 100644 --- a/ui/src/pages/settings/hooks/use-thinking-config.ts +++ b/ui/src/pages/settings/hooks/use-thinking-config.ts @@ -156,6 +156,42 @@ export function useThinkingConfig() { [saveConfig] ); + const setOverride = useCallback( + (value: string | number | undefined) => { + saveConfig({ override: value }); + }, + [saveConfig] + ); + + const setProviderOverride = useCallback( + (provider: string, tier: 'opus' | 'sonnet' | 'haiku', level: string | undefined) => { + const currentOverrides = config?.provider_overrides || {}; + const currentProviderOverrides = currentOverrides[provider] || {}; + + if (level === undefined) { + // Remove the tier override + const { [tier]: _, ...rest } = currentProviderOverrides; + const updatedProvider = Object.keys(rest).length > 0 ? rest : undefined; + const { [provider]: __, ...otherProviders } = currentOverrides; + const updatedOverrides = updatedProvider + ? { ...otherProviders, [provider]: updatedProvider } + : otherProviders; + saveConfig({ + provider_overrides: + Object.keys(updatedOverrides).length > 0 ? updatedOverrides : undefined, + }); + } else { + saveConfig({ + provider_overrides: { + ...currentOverrides, + [provider]: { ...currentProviderOverrides, [tier]: level }, + }, + }); + } + }, + [config, saveConfig] + ); + return { config: config || DEFAULT_THINKING_CONFIG, loading, @@ -167,5 +203,7 @@ export function useThinkingConfig() { setMode, setTierDefault, setShowWarnings, + setOverride, + setProviderOverride, }; } diff --git a/ui/src/pages/settings/sections/thinking/index.tsx b/ui/src/pages/settings/sections/thinking/index.tsx index 91368a4b..181db25e 100644 --- a/ui/src/pages/settings/sections/thinking/index.tsx +++ b/ui/src/pages/settings/sections/thinking/index.tsx @@ -3,7 +3,7 @@ * Settings section for thinking budget configuration */ -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { Button } from '@/components/ui/button'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { ScrollArea } from '@/components/ui/scroll-area'; @@ -16,7 +16,7 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select'; -import { RefreshCw, CheckCircle2, AlertCircle, Brain, Info } from 'lucide-react'; +import { RefreshCw, CheckCircle2, AlertCircle, Brain, Info, ChevronDown } from 'lucide-react'; import { useThinkingConfig } from '../../hooks'; import type { ThinkingMode } from '../../types'; @@ -29,6 +29,14 @@ const THINKING_LEVELS = [ { value: 'auto', label: 'Auto (dynamic)' }, ]; +const OVERRIDE_LEVELS = [ + { value: '__none__', label: 'None (use CLI flags only)' }, + ...THINKING_LEVELS, + { value: 'off', label: 'Off (disable thinking)' }, +]; + +const KNOWN_PROVIDERS = ['agy', 'gemini', 'codex'] as const; + export default function ThinkingSection() { const { config, @@ -40,7 +48,10 @@ export default function ThinkingSection() { setMode, setTierDefault, setShowWarnings, + setOverride, + setProviderOverride, } = useThinkingConfig(); + const [providerOverridesOpen, setProviderOverridesOpen] = useState(false); useEffect(() => { fetchConfig(); @@ -146,7 +157,7 @@ export default function ThinkingSection() { {mode === 'auto' && 'Automatically set thinking based on model tier'} {mode === 'off' && 'Disable extended thinking'} {mode === 'manual' && - 'Use --thinking (agy/gemini) or --effort (codex) per run'} + 'Set a persistent override level or use CLI flags per run'}

)} + {/* Manual Override (shown when mode is manual) */} + {config.mode === 'manual' && ( +
+

Persistent Override

+

+ Applied to all sessions. CLI flags still take priority. +

+ +
+ )} + + {/* Provider Overrides (collapsible) */} +
+ + {providerOverridesOpen && ( +
+

+ Override tier defaults for specific providers. +

+ {KNOWN_PROVIDERS.map((provider) => ( +
+ +
+ {(['opus', 'sonnet', 'haiku'] as const).map((tier) => { + const currentValue = + config.provider_overrides?.[provider]?.[tier] || '__default__'; + return ( +
+ + +
+ ); + })} +
+
+ ))} +
+ )} +
+ {/* Show Warnings Toggle */}
@@ -212,17 +311,17 @@ export default function ThinkingSection() { {/* Info Box */}
-

CLI Override

+

CLI & Env Override

- Override per session with{' '} - --thinking (agy/gemini) or{' '} - --effort (codex): + Override per session with flags or{' '} + CCS_THINKING env var. + Priority: flag > env > config.

ccs gemini --thinking high

-

ccs agy --thinking 16384

ccs codex --effort xhigh

-

ccs codex -p "Refactor this module" --effort xhigh

+

CCS_THINKING=high ccs codex "debug this"

+

ccs config thinking --mode auto