fix(thinking): harden codex reasoning controls across cli and dashboard

This commit is contained in:
Tam Nhu Tran committed 2026-02-19 14:13:41 +07:00
1 parent c48ed2ea7f
commit 92e2ec111d
16 files changed
+860 -146

No files matched your search

@@ -141,4 +141,87 @@ describe('CodexReasoningProxy extended-context compatibility', () => {
expect(capturedBody?.model).toBe('gpt-5.3-codex');
expect((capturedBody?.reasoning as JsonRecord | undefined)?.effort).toBe('high');
});
it('skips reasoning injection when disableEffort is enabled', async () => {
let capturedBody: JsonRecord | null = null;
const upstream = http.createServer((req, res) => {
let rawBody = '';
req.setEncoding('utf8');
req.on('data', (chunk) => {
rawBody += chunk;
});
req.on('end', () => {
capturedBody = rawBody ? (JSON.parse(rawBody) as JsonRecord) : {};
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ ok: true }));
});
});
cleanupServers.push(upstream);
const upstreamPort = await listenOnRandomPort(upstream);
const proxy = new CodexReasoningProxy({
upstreamBaseUrl: `http://127.0.0.1:${upstreamPort}`,
modelMap: {
sonnetModel: 'gpt-5.3-codex-high',
},
disableEffort: true,
});
const proxyPort = await proxy.start();
const response = await postJson(
`http://127.0.0.1:${proxyPort}/api/provider/codex/v1/messages`,
{
model: 'gpt-5.3-codex-high',
messages: [],
}
);
proxy.stop();
expect(response.statusCode).toBe(200);
expect(capturedBody?.model).toBe('gpt-5.3-codex');
expect((capturedBody?.reasoning as JsonRecord | undefined)?.effort).toBeUndefined();
});
it('does not strip unknown model ids that merely end with "-high"', async () => {
let capturedBody: JsonRecord | null = null;
const upstream = http.createServer((req, res) => {
let rawBody = '';
req.setEncoding('utf8');
req.on('data', (chunk) => {
rawBody += chunk;
});
req.on('end', () => {
capturedBody = rawBody ? (JSON.parse(rawBody) as JsonRecord) : {};
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ ok: true }));
});
});
cleanupServers.push(upstream);
const upstreamPort = await listenOnRandomPort(upstream);
const proxy = new CodexReasoningProxy({
upstreamBaseUrl: `http://127.0.0.1:${upstreamPort}`,
modelMap: {
defaultModel: 'gpt-5.1-codex-mini',
},
defaultEffort: 'medium',
});
const proxyPort = await proxy.start();
const response = await postJson(
`http://127.0.0.1:${proxyPort}/api/provider/codex/v1/messages`,
{
model: 'enterprise-internal-high',
messages: [],
}
);
proxy.stop();
expect(response.statusCode).toBe(200);
expect(capturedBody?.model).toBe('enterprise-internal-high');
});
});
@@ -311,6 +311,24 @@ describe('applyThinkingConfig - composite variant integration', () => {
expect(result.ANTHROPIC_DEFAULT_HAIKU_MODEL).toBe('claude-haiku-4-5-20251001');
});
it('treats off override aliases case-insensitively', () => {
const envVars: NodeJS.ProcessEnv = {
ANTHROPIC_MODEL: 'claude-sonnet-4-5-thinking',
ANTHROPIC_DEFAULT_OPUS_MODEL: 'claude-opus-4-6-thinking',
ANTHROPIC_DEFAULT_SONNET_MODEL: 'claude-sonnet-4-5-thinking',
ANTHROPIC_DEFAULT_HAIKU_MODEL: 'claude-haiku-4-5-20251001',
};
const result = applyThinkingConfig(envVars, 'agy' as CLIProxyProvider, 'OFF', {
opus: 'xhigh',
sonnet: 'high',
});
expect(result.ANTHROPIC_MODEL).toBe('claude-sonnet-4-5-thinking');
expect(result.ANTHROPIC_DEFAULT_OPUS_MODEL).toBe('claude-opus-4-6-thinking');
expect(result.ANTHROPIC_DEFAULT_SONNET_MODEL).toBe('claude-sonnet-4-5-thinking');
});
it('uses per-tier provider capability checks for mixed-provider composites', () => {
const envVars: NodeJS.ProcessEnv = {
ANTHROPIC_MODEL: 'gemini-2.5-pro',
@@ -0,0 +1,87 @@
import { describe, expect, it } from 'bun:test';
import type { ThinkingConfig } from '../../../src/config/unified-config-types';
import {
buildThinkingStartupStatus,
parseEnvThinkingOverride,
resolveRuntimeThinkingOverride,
shouldDisableCodexReasoning,
} from '../../../src/cliproxy/executor/thinking-override-resolver';
const baseConfig: ThinkingConfig = {
mode: 'auto',
tier_defaults: {
opus: 'high',
sonnet: 'medium',
haiku: 'low',
},
show_warnings: true,
};
describe('thinking-override-resolver', () => {
it('parses env thinking values with CLI-compatible integer handling', () => {
expect(parseEnvThinkingOverride(undefined)).toBeUndefined();
expect(parseEnvThinkingOverride(' ')).toBeUndefined();
expect(parseEnvThinkingOverride('8192')).toBe(8192);
expect(parseEnvThinkingOverride(' OFF ')).toBe('off');
expect(parseEnvThinkingOverride('bogus')).toBeUndefined();
expect(parseEnvThinkingOverride('-1')).toBeUndefined();
expect(parseEnvThinkingOverride('100001')).toBeUndefined();
});
it('resolves runtime priority as flag > env', () => {
expect(resolveRuntimeThinkingOverride('high', 'low')).toEqual({
thinkingOverride: 'high',
thinkingSource: 'flag',
});
expect(resolveRuntimeThinkingOverride(undefined, 'xhigh')).toEqual({
thinkingOverride: 'xhigh',
thinkingSource: 'env',
});
expect(resolveRuntimeThinkingOverride(undefined, 'invalid')).toEqual({
thinkingOverride: undefined,
thinkingSource: undefined,
});
});
it('disables codex reasoning for off aliases regardless of case', () => {
expect(shouldDisableCodexReasoning(baseConfig, 'OFF')).toBe(true);
expect(
shouldDisableCodexReasoning(
{
...baseConfig,
mode: 'off',
},
'high'
)
).toBe(false);
expect(
shouldDisableCodexReasoning(
{
...baseConfig,
mode: 'manual',
override: 'off',
},
undefined
)
).toBe(true);
});
it('builds startup status from effective precedence instead of raw config mode', () => {
const offConfig: ThinkingConfig = { ...baseConfig, mode: 'off' };
expect(buildThinkingStartupStatus(offConfig, 'high', 'env')).toEqual({
thinkingLabel: 'high',
sourceLabel: 'env: CCS_THINKING',
});
expect(buildThinkingStartupStatus(offConfig, undefined, undefined)).toEqual({
thinkingLabel: 'off',
sourceLabel: 'config: off',
});
expect(buildThinkingStartupStatus(baseConfig, 'off', 'flag', '--effort off')).toEqual({
thinkingLabel: 'off',
sourceLabel: 'flag: --effort off',
});
});
});
@@ -0,0 +1,43 @@
import { describe, expect, it } from 'bun:test';
import {
parseThinkingCommandArgs,
parseThinkingOverrideInput,
} from '../../../src/commands/config-thinking-command';
describe('config thinking command parser', () => {
it('rejects missing required option values', () => {
const result = parseThinkingCommandArgs(['--mode']);
expect(result.error).toBe('--mode requires a value');
});
it('rejects unknown options', () => {
const result = parseThinkingCommandArgs(['--unknown-flag']);
expect(result.error).toBe('Unknown option: --unknown-flag');
});
it('parses clear-provider-override with optional tier', () => {
const withTier = parseThinkingCommandArgs(['--clear-provider-override', 'codex', 'opus']);
expect(withTier.error).toBeUndefined();
expect(withTier.options.clearProviderOverride).toEqual({ provider: 'codex', tier: 'opus' });
const withoutTier = parseThinkingCommandArgs(['--clear-provider-override', 'codex']);
expect(withoutTier.error).toBeUndefined();
expect(withoutTier.options.clearProviderOverride).toEqual({ provider: 'codex', tier: undefined });
});
});
describe('config thinking override normalization', () => {
it('normalizes off aliases and case', () => {
expect(parseThinkingOverrideInput('OFF')).toEqual({ value: 'off' });
expect(parseThinkingOverrideInput('0')).toEqual({ value: 'off' });
});
it('accepts valid levels', () => {
expect(parseThinkingOverrideInput('High')).toEqual({ value: 'high' });
});
it('validates numeric bounds', () => {
expect(parseThinkingOverrideInput('100001').error).toContain('between 0 and 100000');
expect(parseThinkingOverrideInput('8192')).toEqual({ value: 8192 });
});
});