Files
ccs/src/commands/config-thinking-command.ts
T

306 lines
10 KiB
TypeScript

/**
* 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';
import { parseThinkingCommandArgs, parseThinkingOverrideInput } from './config-thinking-parser';
const VALID_THINKING_MODES = ['auto', 'off', 'manual'] as const;
const VALID_TIERS = ['opus', 'sonnet', 'haiku'] as const;
type ThinkingTier = (typeof VALID_TIERS)[number];
export { parseThinkingCommandArgs, parseThinkingOverrideInput } from './config-thinking-parser';
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 <mode>', 'command')} Set mode (auto, off, manual)`
);
console.log(
` ${color('--override <level>', 'command')} Set persistent override (manual mode)`
);
console.log(
` ${color('--clear-override', 'command')} Remove persistent override`
);
console.log(
` ${color('--tier <tier> <level>', 'command')} Set tier default (opus/sonnet/haiku)`
);
console.log(
` ${color('--provider-override <p> <t> <l>', 'command')} Set provider-specific tier override`
);
console.log(
` ${color('--clear-provider-override <p> [t]', 'command')} Remove provider override (provider or tier)`
);
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(
` $ ${color('ccs config thinking --clear-provider-override codex opus', '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<void> {
await initUI();
const { options, error } = parseThinkingCommandArgs(args);
if (error) {
console.error(fail(error));
process.exitCode = 1;
return;
}
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) {
const normalizedMode = options.mode.trim().toLowerCase();
if (!(VALID_THINKING_MODES as readonly string[]).includes(normalizedMode)) {
console.error(fail(`Invalid mode: ${options.mode}`));
console.error(info(`Valid modes: ${VALID_THINKING_MODES.join(', ')}`));
process.exitCode = 1;
return;
}
thinkingConfig.mode = normalizedMode as 'auto' | 'off' | 'manual';
hasChanges = true;
}
// Validate and apply --override
if (options.override !== undefined) {
const parsedOverride = parseThinkingOverrideInput(options.override);
if (parsedOverride.error) {
console.error(fail(parsedOverride.error));
console.error(info(`Valid levels: ${VALID_THINKING_LEVELS.join(', ')}, or a number`));
process.exitCode = 1;
return;
}
thinkingConfig.override = parsedOverride.value;
hasChanges = true;
}
// Apply --clear-override
if (options.clearOverride) {
thinkingConfig.override = undefined;
hasChanges = true;
}
// Validate and apply --tier
if (options.tier) {
const tier = options.tier.tier.toLowerCase().trim();
const level = options.tier.level.toLowerCase().trim();
if (!(VALID_TIERS as readonly string[]).includes(tier)) {
console.error(fail(`Invalid tier: ${options.tier.tier}`));
console.error(info(`Valid tiers: ${VALID_TIERS.join(', ')}`));
process.exitCode = 1;
return;
}
if (!(VALID_THINKING_LEVELS as readonly string[]).includes(level)) {
console.error(fail(`Invalid level for ${tier}: ${options.tier.level}`));
console.error(info(`Valid levels: ${VALID_THINKING_LEVELS.join(', ')}`));
process.exitCode = 1;
return;
}
thinkingConfig.tier_defaults = {
...DEFAULT_THINKING_TIER_DEFAULTS,
...thinkingConfig.tier_defaults,
[tier]: level,
};
hasChanges = true;
}
// Validate and apply --provider-override
if (options.providerOverride) {
const provider = options.providerOverride.provider.trim().toLowerCase();
const tier = options.providerOverride.tier.trim().toLowerCase();
const level = options.providerOverride.level.trim().toLowerCase();
if (!provider) {
console.error(fail('Provider name cannot be empty'));
process.exitCode = 1;
return;
}
if (!(VALID_TIERS as readonly string[]).includes(tier)) {
console.error(fail(`Invalid tier: ${options.providerOverride.tier}`));
process.exitCode = 1;
return;
}
if (!(VALID_THINKING_LEVELS as readonly string[]).includes(level)) {
console.error(fail(`Invalid level: ${options.providerOverride.level}`));
process.exitCode = 1;
return;
}
const normalizedTier = tier as ThinkingTier;
thinkingConfig.provider_overrides = {
...thinkingConfig.provider_overrides,
[provider]: {
...thinkingConfig.provider_overrides?.[provider],
[normalizedTier]: level,
},
};
hasChanges = true;
}
// Validate and apply --clear-provider-override
if (options.clearProviderOverride) {
const provider = options.clearProviderOverride.provider.trim().toLowerCase();
const tier = options.clearProviderOverride.tier?.trim().toLowerCase();
if (!provider) {
console.error(fail('Provider name cannot be empty'));
process.exitCode = 1;
return;
}
if (tier && !(VALID_TIERS as readonly string[]).includes(tier)) {
console.error(fail(`Invalid tier: ${options.clearProviderOverride.tier}`));
console.error(info(`Valid tiers: ${VALID_TIERS.join(', ')}`));
process.exitCode = 1;
return;
}
const currentOverrides = thinkingConfig.provider_overrides ?? {};
const nextOverrides = { ...currentOverrides };
if (!nextOverrides[provider]) {
// no-op, but still considered change request to keep command deterministic
hasChanges = true;
} else if (!tier) {
delete nextOverrides[provider];
hasChanges = true;
} else {
const normalizedTier = tier as ThinkingTier;
const providerEntry = { ...nextOverrides[provider] };
delete providerEntry[normalizedTier];
if (Object.keys(providerEntry).length === 0) {
delete nextOverrides[provider];
} else {
nextOverrides[provider] = providerEntry;
}
hasChanges = true;
}
thinkingConfig.provider_overrides =
Object.keys(nextOverrides).length > 0 ? nextOverrides : undefined;
}
if (hasChanges) {
updateUnifiedConfig({ thinking: thinkingConfig });
console.log(ok('Configuration updated'));
console.log('');
}
// Always show current status
showStatus();
}