mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 12:15:57 +00:00
fix(cliproxy): unify codex reasoning flags and thinking flow
This commit is contained in:
+23
-4
@@ -563,10 +563,29 @@ async function main(): Promise<void> {
|
||||
|
||||
// Special case: headless delegation (-p flag)
|
||||
if (args.includes('-p') || args.includes('--prompt')) {
|
||||
const { DelegationHandler } = await import('./delegation/delegation-handler');
|
||||
const handler = new DelegationHandler();
|
||||
await handler.route(args);
|
||||
return;
|
||||
// CLIProxy profiles (codex/gemini/agy/etc, including user variants) must stay on
|
||||
// the normal CLIProxy path so provider-specific flags (e.g. --effort/--thinking)
|
||||
// and proxy chains are applied consistently.
|
||||
let shouldUseDelegation = true;
|
||||
const { profile } = detectProfile(args);
|
||||
try {
|
||||
const ProfileDetectorModule = await import('./auth/profile-detector');
|
||||
const ProfileDetector = ProfileDetectorModule.default;
|
||||
const detector = new ProfileDetector();
|
||||
const profileInfo = detector.detectProfileType(profile);
|
||||
if (profileInfo.type === 'cliproxy') {
|
||||
shouldUseDelegation = false;
|
||||
}
|
||||
} catch {
|
||||
// Best effort detection only; keep delegation fallback behavior.
|
||||
}
|
||||
|
||||
if (shouldUseDelegation) {
|
||||
const { DelegationHandler } = await import('./delegation/delegation-handler');
|
||||
const handler = new DelegationHandler();
|
||||
await handler.route(args);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// First-time install: offer setup wizard for interactive users
|
||||
|
||||
@@ -13,6 +13,30 @@ import { warn } from '../../utils/ui';
|
||||
/** Model tier types for thinking budget defaults */
|
||||
export type ModelTier = 'opus' | 'sonnet' | 'haiku';
|
||||
|
||||
/**
|
||||
* Normalize model ID for provider capability lookup.
|
||||
* Codex may carry effort suffixes in either legacy "(high)" or current "-high" forms.
|
||||
*/
|
||||
function normalizeModelForThinkingLookup(model: string, provider: CLIProxyProvider): string {
|
||||
const withoutExtendedContext = model.replace(/\[1m\]$/i, '').trim();
|
||||
|
||||
if (provider !== 'codex') return withoutExtendedContext;
|
||||
|
||||
// New codex suffix form: gpt-5.3-codex-high -> gpt-5.3-codex
|
||||
const codexSuffixMatch = withoutExtendedContext.match(/^(.*)-(xhigh|high|medium)$/i);
|
||||
if (codexSuffixMatch?.[1]) {
|
||||
return codexSuffixMatch[1].trim();
|
||||
}
|
||||
|
||||
// Legacy codex suffix form: gpt-5.3-codex(high) -> gpt-5.3-codex
|
||||
const codexLegacyMatch = withoutExtendedContext.match(/^(.*)\((xhigh|high|medium)\)$/i);
|
||||
if (codexLegacyMatch?.[1]) {
|
||||
return codexLegacyMatch[1].trim();
|
||||
}
|
||||
|
||||
return withoutExtendedContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if warnings should be shown based on thinking config.
|
||||
* Defaults to true if show_warnings is not explicitly false.
|
||||
@@ -41,11 +65,46 @@ export function detectTierFromModel(modelName: string): ModelTier {
|
||||
* @returns Model name with thinking suffix, e.g., "gemini-3-pro-preview(high)"
|
||||
*/
|
||||
export function applyThinkingSuffix(model: string, thinkingValue: string | number): string {
|
||||
// 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 applyThinkingSuffixForProvider(model, thinkingValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply provider-aware thinking suffix to model name.
|
||||
* - Most providers: model(level|budget) e.g. "gemini-2.5-pro(high)"
|
||||
* - Codex: model-effort e.g. "gpt-5.3-codex-xhigh"
|
||||
*/
|
||||
function applyThinkingSuffixForProvider(
|
||||
model: string,
|
||||
thinkingValue: string | number,
|
||||
provider?: CLIProxyProvider
|
||||
): string {
|
||||
const codexEffortRegex = /^(medium|high|xhigh)$/i;
|
||||
const codexModelSuffixRegex = /-(medium|high|xhigh)$/i;
|
||||
const parenthesizedSuffixMatch = model.match(/\(([^)]+)\)$/);
|
||||
|
||||
// Existing parenthesized suffix:
|
||||
// - keep as-is for non-codex providers
|
||||
// - for codex effort levels, normalize to codex model suffix style
|
||||
if (parenthesizedSuffixMatch) {
|
||||
if (provider === 'codex') {
|
||||
const normalizedParensValue = parenthesizedSuffixMatch[1]?.trim().toLowerCase() || '';
|
||||
if (codexEffortRegex.test(normalizedParensValue)) {
|
||||
return model.replace(/\([^)]+\)$/, `-${normalizedParensValue}`);
|
||||
}
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
if (provider === 'codex') {
|
||||
if (codexModelSuffixRegex.test(model)) {
|
||||
return model;
|
||||
}
|
||||
const normalized = String(thinkingValue).trim().toLowerCase();
|
||||
if (codexEffortRegex.test(normalized)) {
|
||||
return `${model}-${normalized}`;
|
||||
}
|
||||
}
|
||||
|
||||
return `${model}(${thinkingValue})`;
|
||||
}
|
||||
|
||||
@@ -120,7 +179,8 @@ export function applyThinkingConfig(
|
||||
|
||||
// Get base model to check thinking support
|
||||
const baseModel = result.ANTHROPIC_MODEL || '';
|
||||
if (!supportsThinking(provider, baseModel)) {
|
||||
const normalizedBaseModel = normalizeModelForThinkingLookup(baseModel, provider);
|
||||
if (!supportsThinking(provider, normalizedBaseModel)) {
|
||||
// U2: Warn user if they explicitly provided --thinking but model doesn't support it
|
||||
if (thinkingOverride !== undefined && shouldShowWarnings(thinkingConfig)) {
|
||||
console.warn(
|
||||
@@ -143,7 +203,7 @@ export function applyThinkingConfig(
|
||||
thinkingValue = thinkingConfig.override;
|
||||
} else if (thinkingConfig.mode === 'auto') {
|
||||
// Auto mode: detect tier and apply default
|
||||
const tier = detectTierFromModel(baseModel);
|
||||
const tier = detectTierFromModel(normalizedBaseModel);
|
||||
// Check per-tier config first if composite
|
||||
const perTierValue = compositeTierThinking?.[tier];
|
||||
if (perTierValue !== undefined) {
|
||||
@@ -156,7 +216,7 @@ export function applyThinkingConfig(
|
||||
}
|
||||
|
||||
// Validate thinking value against model capabilities
|
||||
const validation = validateThinking(provider, baseModel, thinkingValue);
|
||||
const validation = validateThinking(provider, normalizedBaseModel, thinkingValue);
|
||||
if (validation.warning && shouldShowWarnings(thinkingConfig)) {
|
||||
console.warn(warn(validation.warning));
|
||||
}
|
||||
@@ -174,7 +234,11 @@ export function applyThinkingConfig(
|
||||
// Otherwise, continue to process tiers with their own config (skip main model)
|
||||
} else if (result.ANTHROPIC_MODEL) {
|
||||
// Apply thinking suffix to main model (only if not off)
|
||||
result.ANTHROPIC_MODEL = applyThinkingSuffix(result.ANTHROPIC_MODEL, thinkingValue);
|
||||
result.ANTHROPIC_MODEL = applyThinkingSuffixForProvider(
|
||||
result.ANTHROPIC_MODEL,
|
||||
thinkingValue,
|
||||
provider
|
||||
);
|
||||
}
|
||||
|
||||
// Apply to tier models if they support thinking
|
||||
@@ -197,9 +261,10 @@ export function applyThinkingConfig(
|
||||
// P2 FIX: Use tier-specific provider from compositeTiers for mixed-provider composites
|
||||
// Falls back to the default provider if not a composite or tier not specified
|
||||
const tierProvider = compositeTiers?.[tier]?.provider ?? provider;
|
||||
const normalizedTierModel = normalizeModelForThinkingLookup(model, tierProvider);
|
||||
|
||||
// Check if this tier's model supports thinking (using tier-specific provider)
|
||||
if (!supportsThinking(tierProvider, model)) {
|
||||
if (!supportsThinking(tierProvider, normalizedTierModel)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -224,7 +289,7 @@ export function applyThinkingConfig(
|
||||
continue;
|
||||
}
|
||||
|
||||
result[tierVar] = applyThinkingSuffix(model, tierThinkingValue);
|
||||
result[tierVar] = applyThinkingSuffixForProvider(model, tierThinkingValue, tierProvider);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -176,7 +176,13 @@ export function buildClaudeEnvironment(config: ProxyChainConfig): Record<string,
|
||||
}
|
||||
|
||||
// Apply thinking configuration to model (auto tier-based or manual override)
|
||||
applyThinkingConfig(envVars, provider, thinkingOverride, compositeTierThinking, compositeTiers);
|
||||
envVars = applyThinkingConfig(
|
||||
envVars,
|
||||
provider,
|
||||
thinkingOverride,
|
||||
compositeTierThinking,
|
||||
compositeTiers
|
||||
);
|
||||
|
||||
// Apply extended context suffix for 1M token context window
|
||||
// Auto-enabled for Gemini, opt-in for Claude (--1m flag)
|
||||
|
||||
@@ -63,6 +63,7 @@ import {
|
||||
handleQuotaCheck,
|
||||
} from './retry-handler';
|
||||
import { checkOrJoinProxy, registerProxySession, setupCleanupHandlers } from './session-bridge';
|
||||
import { parseThinkingOverride } from './thinking-arg-parser';
|
||||
import {
|
||||
warnCrossProviderDuplicates,
|
||||
cleanupStaleAutoPauses,
|
||||
@@ -306,50 +307,36 @@ export async function execClaudeWithCLIProxy(
|
||||
setNickname = argsWithoutProxy[nicknameIdx + 1];
|
||||
}
|
||||
|
||||
// Parse --thinking flag
|
||||
let thinkingOverride: string | number | undefined;
|
||||
const thinkingEqArg = argsWithoutProxy.find((arg) => arg.startsWith('--thinking='));
|
||||
if (thinkingEqArg) {
|
||||
const val = thinkingEqArg.substring('--thinking='.length);
|
||||
if (!val || val.trim() === '') {
|
||||
console.error(fail('--thinking requires a value'));
|
||||
console.error(' Examples: --thinking=low, --thinking=8192, --thinking=off');
|
||||
// Parse --thinking / --effort flags (aliases; first occurrence wins)
|
||||
const thinkingParse = parseThinkingOverride(argsWithoutProxy);
|
||||
if (thinkingParse.error) {
|
||||
const { flag } = thinkingParse.error;
|
||||
console.error(fail(`${flag} requires a value`));
|
||||
|
||||
if (provider === 'codex') {
|
||||
console.error(' Codex examples: --effort xhigh, --effort high, --effort medium');
|
||||
console.error(' Alias: --thinking xhigh (same behavior)');
|
||||
} else {
|
||||
console.error(' Examples: --thinking low, --thinking 8192, --thinking off');
|
||||
console.error(' Levels: minimal, low, medium, high, xhigh, auto');
|
||||
process.exit(1);
|
||||
}
|
||||
const numVal = parseInt(val, 10);
|
||||
thinkingOverride = !isNaN(numVal) ? numVal : val;
|
||||
|
||||
const allThinkingFlags = argsWithoutProxy.filter(
|
||||
(arg) => arg === '--thinking' || arg.startsWith('--thinking=')
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const thinkingOverride = thinkingParse.value;
|
||||
if (thinkingParse.duplicateDisplays.length > 0) {
|
||||
console.warn(
|
||||
`[!] Multiple reasoning flags detected. Using first occurrence: ${thinkingParse.sourceDisplay}`
|
||||
);
|
||||
if (allThinkingFlags.length > 1) {
|
||||
console.warn(
|
||||
`[!] Multiple --thinking flags detected. Using first occurrence: ${thinkingEqArg}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const thinkingIdx = argsWithoutProxy.indexOf('--thinking');
|
||||
if (thinkingIdx !== -1) {
|
||||
const nextArg = argsWithoutProxy[thinkingIdx + 1];
|
||||
if (!nextArg || nextArg.startsWith('-')) {
|
||||
console.error(fail('--thinking requires a value'));
|
||||
console.error(' Examples: --thinking low, --thinking 8192, --thinking off');
|
||||
console.error(' Levels: minimal, low, medium, high, xhigh, auto');
|
||||
process.exit(1);
|
||||
}
|
||||
const numVal = parseInt(nextArg, 10);
|
||||
thinkingOverride = !isNaN(numVal) ? numVal : nextArg;
|
||||
}
|
||||
|
||||
const allThinkingFlags = argsWithoutProxy.filter(
|
||||
(arg) => arg === '--thinking' || arg.startsWith('--thinking=')
|
||||
);
|
||||
if (allThinkingFlags.length > 1) {
|
||||
console.warn(
|
||||
`[!] Multiple --thinking flags detected. Using first occurrence: --thinking ${nextArg}`
|
||||
);
|
||||
}
|
||||
}
|
||||
if (thinkingParse.sourceFlag === '--effort' && provider !== 'codex') {
|
||||
console.warn(
|
||||
warn(
|
||||
'`--effort` is primarily for codex. Continuing as alias of `--thinking` for compatibility.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Parse --1m / --no-1m flags for extended context (1M token window)
|
||||
@@ -868,6 +855,7 @@ export async function execClaudeWithCLIProxy(
|
||||
'--use',
|
||||
'--nickname',
|
||||
'--thinking',
|
||||
'--effort',
|
||||
'--1m',
|
||||
'--no-1m',
|
||||
'--incognito',
|
||||
@@ -879,11 +867,13 @@ export async function execClaudeWithCLIProxy(
|
||||
const claudeArgs = argsWithoutProxy.filter((arg, idx) => {
|
||||
if (ccsFlags.includes(arg)) return false;
|
||||
if (arg.startsWith('--thinking=')) return false;
|
||||
if (arg.startsWith('--effort=')) return false;
|
||||
if (arg.startsWith('--1m=') || arg.startsWith('--no-1m=')) return false;
|
||||
if (
|
||||
argsWithoutProxy[idx - 1] === '--use' ||
|
||||
argsWithoutProxy[idx - 1] === '--nickname' ||
|
||||
argsWithoutProxy[idx - 1] === '--thinking'
|
||||
argsWithoutProxy[idx - 1] === '--thinking' ||
|
||||
argsWithoutProxy[idx - 1] === '--effort'
|
||||
)
|
||||
return false;
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Parse CLI thinking/effort override flags.
|
||||
*
|
||||
* Supported aliases:
|
||||
* - --thinking <value>
|
||||
* - --thinking=<value>
|
||||
* - --effort <value>
|
||||
* - --effort=<value>
|
||||
*/
|
||||
|
||||
export type ThinkingFlag = '--thinking' | '--effort';
|
||||
|
||||
export interface ThinkingParseError {
|
||||
flag: ThinkingFlag;
|
||||
form: 'inline' | 'separate';
|
||||
}
|
||||
|
||||
export interface ThinkingParseResult {
|
||||
value?: string | number;
|
||||
sourceFlag?: ThinkingFlag;
|
||||
sourceDisplay?: string;
|
||||
duplicateDisplays: string[];
|
||||
error?: ThinkingParseError;
|
||||
}
|
||||
|
||||
function parseThinkingValue(raw: string): string | number {
|
||||
const trimmed = raw.trim();
|
||||
|
||||
// Keep strict integer parsing for legacy compatibility with --thinking numeric budgets.
|
||||
if (/^-?\d+$/.test(trimmed)) {
|
||||
return Number.parseInt(trimmed, 10);
|
||||
}
|
||||
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse first thinking/effort value from args.
|
||||
* If multiple flags are provided, first occurrence wins and later ones are reported as duplicates.
|
||||
*/
|
||||
export function parseThinkingOverride(args: string[]): ThinkingParseResult {
|
||||
let value: string | number | undefined;
|
||||
let sourceFlag: ThinkingFlag | undefined;
|
||||
let sourceDisplay: string | undefined;
|
||||
const duplicateDisplays: string[] = [];
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i];
|
||||
|
||||
let flag: ThinkingFlag | null = null;
|
||||
let rawValue: string | undefined;
|
||||
let display = '';
|
||||
let form: 'inline' | 'separate' = 'separate';
|
||||
|
||||
if (arg === '--thinking' || arg === '--effort') {
|
||||
flag = arg;
|
||||
const nextArg = args[i + 1];
|
||||
if (!nextArg || nextArg.startsWith('-')) {
|
||||
if (value === undefined) {
|
||||
return { value, sourceFlag, sourceDisplay, duplicateDisplays, error: { flag, form } };
|
||||
}
|
||||
duplicateDisplays.push(`${flag} <missing-value>`);
|
||||
continue;
|
||||
}
|
||||
rawValue = nextArg;
|
||||
display = `${flag} ${rawValue}`;
|
||||
i += 1; // Consume value
|
||||
} else if (arg.startsWith('--thinking=') || arg.startsWith('--effort=')) {
|
||||
const [rawFlag, ...rest] = arg.split('=');
|
||||
const joined = rest.join('=');
|
||||
flag = rawFlag as ThinkingFlag;
|
||||
rawValue = joined;
|
||||
form = 'inline';
|
||||
if (!rawValue || rawValue.trim() === '') {
|
||||
if (value === undefined) {
|
||||
return { value, sourceFlag, sourceDisplay, duplicateDisplays, error: { flag, form } };
|
||||
}
|
||||
duplicateDisplays.push(`${flag}=<missing-value>`);
|
||||
continue;
|
||||
}
|
||||
display = `${flag}=${rawValue}`;
|
||||
}
|
||||
|
||||
if (!flag || rawValue === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (value === undefined) {
|
||||
value = parseThinkingValue(rawValue);
|
||||
sourceFlag = flag;
|
||||
sourceDisplay = display;
|
||||
} else {
|
||||
duplicateDisplays.push(display);
|
||||
}
|
||||
}
|
||||
|
||||
return { value, sourceFlag, sourceDisplay, duplicateDisplays };
|
||||
}
|
||||
@@ -168,7 +168,7 @@ Run ${color('ccs config', 'command')} for web dashboard`.trim();
|
||||
],
|
||||
[
|
||||
['ccs gemini', 'Google Gemini (gemini-2.5-pro or 3-pro)'],
|
||||
['ccs codex', 'OpenAI Codex (gpt-5.1-codex-max)'],
|
||||
['ccs codex', 'OpenAI Codex (gpt-5.3-codex)'],
|
||||
['ccs agy', 'Antigravity (Claude/Gemini models)'],
|
||||
['ccs qwen', 'Qwen Code (qwen3-coder)'],
|
||||
['ccs kiro', 'Kiro (AWS CodeWhisperer Claude models)'],
|
||||
@@ -187,6 +187,7 @@ Run ${color('ccs config', 'command')} for web dashboard`.trim();
|
||||
'ccs <provider> --thinking <value>',
|
||||
'Set thinking budget (low/medium/high/xhigh/auto/off or number)',
|
||||
],
|
||||
['ccs codex --effort <level>', 'Set codex reasoning effort (medium/high/xhigh)'],
|
||||
['ccs <provider> --1m', 'Enable 1M token context window'],
|
||||
['ccs <provider> --no-1m', 'Disable 1M context (use 200K default)'],
|
||||
['ccs <provider> --logout', 'Clear authentication'],
|
||||
@@ -332,7 +333,7 @@ Run ${color('ccs config', 'command')} for web dashboard`.trim();
|
||||
]);
|
||||
|
||||
// W3: Thinking Budget explanation
|
||||
printSubSection('Extended Thinking (--thinking)', [
|
||||
printSubSection('Extended Thinking / Reasoning', [
|
||||
['--thinking off', 'Disable extended thinking'],
|
||||
['--thinking auto', 'Let model decide dynamically'],
|
||||
['--thinking low', '1K tokens - Quick responses'],
|
||||
@@ -341,8 +342,12 @@ Run ${color('ccs config', 'command')} for web dashboard`.trim();
|
||||
['--thinking xhigh', '32K tokens - Maximum depth'],
|
||||
['--thinking <number>', 'Custom token budget (512-100000)'],
|
||||
['', ''],
|
||||
['--effort <level>', 'Codex alias for reasoning effort (medium/high/xhigh)'],
|
||||
['--effort xhigh', 'Codex 5.3 full-depth reasoning'],
|
||||
['', ''],
|
||||
['Note:', 'Extended thinking allocates compute for step-by-step reasoning'],
|
||||
['', 'before responding. Supported: agy, gemini (thinking models).'],
|
||||
['', 'before responding.'],
|
||||
['', 'Providers: agy/gemini use --thinking, codex uses --effort (or --thinking alias).'],
|
||||
]);
|
||||
|
||||
// Extended Context (1M)
|
||||
|
||||
@@ -638,7 +638,9 @@ function generateYamlWithComments(config: UnifiedConfig): string {
|
||||
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(
|
||||
'# Modes: auto (use tier_defaults), off (disable), manual (--thinking/--effort flags)'
|
||||
);
|
||||
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');
|
||||
|
||||
@@ -57,4 +57,16 @@ describe('buildClaudeEnvironment - composite remote routing', () => {
|
||||
expect(env.ANTHROPIC_AUTH_TOKEN).toBe('remote-auth-token');
|
||||
expect(env.ANTHROPIC_BASE_URL).not.toContain('/api/provider/');
|
||||
});
|
||||
|
||||
it('applies thinking override to resolved env models', () => {
|
||||
const env = buildClaudeEnvironment({
|
||||
provider: 'gemini',
|
||||
useRemoteProxy: false,
|
||||
localPort: 8318,
|
||||
verbose: false,
|
||||
thinkingOverride: 'high',
|
||||
});
|
||||
|
||||
expect(env.ANTHROPIC_MODEL).toContain('(high)');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -476,4 +476,36 @@ describe('applyThinkingConfig - composite variant integration', () => {
|
||||
// Opus stays unchanged because function returned early
|
||||
expect(result.ANTHROPIC_DEFAULT_OPUS_MODEL).toBe('claude-opus-4-6-thinking');
|
||||
});
|
||||
|
||||
it('should use codex effort suffix style when provider is codex', () => {
|
||||
const envVars: NodeJS.ProcessEnv = {
|
||||
ANTHROPIC_MODEL: 'gpt-5.3-codex',
|
||||
ANTHROPIC_DEFAULT_OPUS_MODEL: 'gpt-5.3-codex',
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL: 'gpt-5.3-codex',
|
||||
ANTHROPIC_DEFAULT_HAIKU_MODEL: 'gpt-5-mini',
|
||||
};
|
||||
|
||||
const result = applyThinkingConfig(
|
||||
envVars,
|
||||
'codex' as CLIProxyProvider,
|
||||
'xhigh', // Explicit CLI override
|
||||
undefined
|
||||
);
|
||||
|
||||
expect(result.ANTHROPIC_MODEL).toBe('gpt-5.3-codex-xhigh');
|
||||
expect(result.ANTHROPIC_DEFAULT_OPUS_MODEL).toBe('gpt-5.3-codex-xhigh');
|
||||
expect(result.ANTHROPIC_DEFAULT_SONNET_MODEL).toBe('gpt-5.3-codex-xhigh');
|
||||
expect(result.ANTHROPIC_DEFAULT_HAIKU_MODEL).toBe('gpt-5-mini-xhigh');
|
||||
});
|
||||
|
||||
it('should normalize legacy codex parenthesized tier suffix to effort suffix format', () => {
|
||||
const envVars: NodeJS.ProcessEnv = {
|
||||
ANTHROPIC_MODEL: 'gpt-5.3-codex',
|
||||
ANTHROPIC_DEFAULT_OPUS_MODEL: 'gpt-5.3-codex(high)',
|
||||
};
|
||||
|
||||
const result = applyThinkingConfig(envVars, 'codex' as CLIProxyProvider, 'high');
|
||||
|
||||
expect(result.ANTHROPIC_DEFAULT_OPUS_MODEL).toBe('gpt-5.3-codex-high');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { describe, it, expect } from 'bun:test';
|
||||
import { parseThinkingOverride } from '../../../src/cliproxy/executor/thinking-arg-parser';
|
||||
|
||||
describe('parseThinkingOverride', () => {
|
||||
it('parses --thinking with separate value', () => {
|
||||
const out = parseThinkingOverride(['--thinking', 'high']);
|
||||
expect(out.value).toBe('high');
|
||||
expect(out.sourceFlag).toBe('--thinking');
|
||||
expect(out.error).toBeUndefined();
|
||||
});
|
||||
|
||||
it('parses --thinking=<value> inline', () => {
|
||||
const out = parseThinkingOverride(['--thinking=xhigh']);
|
||||
expect(out.value).toBe('xhigh');
|
||||
expect(out.sourceFlag).toBe('--thinking');
|
||||
expect(out.error).toBeUndefined();
|
||||
});
|
||||
|
||||
it('parses --effort alias', () => {
|
||||
const out = parseThinkingOverride(['--effort', 'medium']);
|
||||
expect(out.value).toBe('medium');
|
||||
expect(out.sourceFlag).toBe('--effort');
|
||||
expect(out.error).toBeUndefined();
|
||||
});
|
||||
|
||||
it('parses integer values as numbers', () => {
|
||||
const out = parseThinkingOverride(['--thinking', '8192']);
|
||||
expect(out.value).toBe(8192);
|
||||
expect(out.sourceFlag).toBe('--thinking');
|
||||
});
|
||||
|
||||
it('returns first occurrence and tracks duplicates across aliases', () => {
|
||||
const out = parseThinkingOverride(['--effort=high', '--thinking', 'xhigh']);
|
||||
expect(out.value).toBe('high');
|
||||
expect(out.sourceFlag).toBe('--effort');
|
||||
expect(out.duplicateDisplays).toEqual(['--thinking xhigh']);
|
||||
expect(out.error).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns error for missing separate value', () => {
|
||||
const out = parseThinkingOverride(['--effort', '--verbose']);
|
||||
expect(out.error).toEqual({ flag: '--effort', form: 'separate' });
|
||||
});
|
||||
|
||||
it('returns error for missing inline value', () => {
|
||||
const out = parseThinkingOverride(['--thinking=']);
|
||||
expect(out.error).toEqual({ flag: '--thinking', form: 'inline' });
|
||||
});
|
||||
|
||||
it('keeps first valid value when later duplicate is missing separate value', () => {
|
||||
const out = parseThinkingOverride(['--effort', 'high', '--thinking']);
|
||||
expect(out.value).toBe('high');
|
||||
expect(out.sourceFlag).toBe('--effort');
|
||||
expect(out.error).toBeUndefined();
|
||||
expect(out.duplicateDisplays).toEqual(['--thinking <missing-value>']);
|
||||
});
|
||||
|
||||
it('keeps first valid value when later duplicate is missing inline value', () => {
|
||||
const out = parseThinkingOverride(['--thinking=medium', '--effort=']);
|
||||
expect(out.value).toBe('medium');
|
||||
expect(out.sourceFlag).toBe('--thinking');
|
||||
expect(out.error).toBeUndefined();
|
||||
expect(out.duplicateDisplays).toEqual(['--effort=<missing-value>']);
|
||||
});
|
||||
});
|
||||
@@ -140,7 +140,8 @@ export default function ThinkingSection() {
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{mode === 'auto' && 'Automatically set thinking based on model tier'}
|
||||
{mode === 'off' && 'Disable extended thinking'}
|
||||
{mode === 'manual' && 'Use --thinking flag to control manually'}
|
||||
{mode === 'manual' &&
|
||||
'Use --thinking (agy/gemini) or --effort (codex) per run'}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
@@ -208,13 +209,15 @@ export default function ThinkingSection() {
|
||||
<div className="p-4 rounded-lg border bg-muted/30">
|
||||
<h4 className="text-sm font-medium mb-2">CLI Override</h4>
|
||||
<p className="text-sm text-muted-foreground mb-2">
|
||||
Use <code className="px-1.5 py-0.5 rounded bg-muted">--thinking</code> flag to
|
||||
override settings per session:
|
||||
Override per session with{' '}
|
||||
<code className="px-1.5 py-0.5 rounded bg-muted">--thinking</code> (agy/gemini) or{' '}
|
||||
<code className="px-1.5 py-0.5 rounded bg-muted">--effort</code> (codex):
|
||||
</p>
|
||||
<div className="space-y-1 text-sm font-mono text-muted-foreground">
|
||||
<p>ccs gemini --thinking high</p>
|
||||
<p>ccs agy --thinking 16384</p>
|
||||
<p>ccs gemini --thinking off</p>
|
||||
<p>ccs codex --effort xhigh</p>
|
||||
<p>ccs codex -p "Refactor this module" --effort xhigh</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user