Skip to content

Commit d86c954

Browse files
DavidShawagpt-5.6-terra
andcommitted
feat(openai): enable effort for compatible chat models
Send reasoning effort through Chat Completions while keeping thinking controls and model capabilities aligned across login, UI, and requests. Co-Authored-By: gpt-5.6-terra <openai@claude-code-best.win>
1 parent feb76f1 commit d86c954

9 files changed

Lines changed: 251 additions & 111 deletions

File tree

src/commands/effort/effort.tsx

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
getEffortEnvOverride,
1414
getEffortValueDescription,
1515
isEffortLevel,
16+
modelSupportsEffort,
1617
toPersistableEffort,
1718
} from '../../utils/effort.js';
1819
import { updateSettingsForSource } from '../../utils/settings/settings.js';
@@ -130,14 +131,12 @@ function ShowCurrentEffort({ onDone }: { onDone: (result: string) => void }): Re
130131
return null;
131132
}
132133

133-
function ApplyEffortAndClose({
134-
result,
135-
onDone,
136-
}: {
137-
result: EffortCommandResult;
138-
onDone: (result: string) => void;
139-
}): React.ReactNode {
134+
function ApplyEffortAndClose({ args, onDone }: { args: string; onDone: (result: string) => void }): React.ReactNode {
140135
const setAppState = useSetAppState();
136+
const model = useMainLoopModel();
137+
const result = modelSupportsEffort(model)
138+
? executeEffort(args)
139+
: { message: 'Effort is disabled because thinking is disabled for the current model.' };
141140
const { effortUpdate, message } = result;
142141
React.useEffect(() => {
143142
if (effortUpdate) {
@@ -169,11 +168,15 @@ export async function call(onDone: LocalJSXCommandOnDone, _context: unknown, arg
169168
return <EffortPanelWrapper onDone={onDone} />;
170169
}
171170

172-
const result = executeEffort(args);
173-
return <ApplyEffortAndClose result={result} onDone={onDone} />;
171+
return <ApplyEffortAndClose args={args} onDone={onDone} />;
174172
}
175173

176174
function EffortPanelWrapper({ onDone }: { onDone: (result: string) => void }): React.ReactNode {
177175
const effortValue = useAppState(s => s.effortValue);
176+
const model = useMainLoopModel();
177+
if (!modelSupportsEffort(model)) {
178+
onDone('Effort is disabled because thinking is disabled for the current model.');
179+
return null;
180+
}
178181
return <EffortPanel appStateEffort={effortValue} onDone={onDone} />;
179182
}

src/components/ConsoleOAuthFlow.tsx

Lines changed: 71 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ type OAuthStatus =
5252
haikuModel: string;
5353
sonnetModel: string;
5454
opusModel: string;
55-
activeField: 'base_url' | 'api_key' | 'haiku_model' | 'sonnet_model' | 'opus_model';
55+
thinkingEnabled: boolean;
56+
activeField: 'base_url' | 'api_key' | 'haiku_model' | 'sonnet_model' | 'opus_model' | 'thinking_enabled';
5657
} // OpenAI Chat Completions API platform
5758
| {
5859
state: 'chatgpt_subscription';
@@ -84,6 +85,10 @@ type OAuthStatus =
8485
};
8586

8687
const PASTE_HERE_MSG = 'Paste code here if prompted > ';
88+
89+
export function getOpenAIThinkingEnabled(value: string | undefined): boolean {
90+
return !['0', 'false', 'no', 'off'].includes(value?.toLowerCase().trim() ?? '');
91+
}
8792
export function ConsoleOAuthFlow({
8893
onDone,
8994
startingMessage,
@@ -550,6 +555,7 @@ function OAuthStatusMessage({
550555
haikuModel: process.env.OPENAI_DEFAULT_HAIKU_MODEL ?? '',
551556
sonnetModel: process.env.OPENAI_DEFAULT_SONNET_MODEL ?? '',
552557
opusModel: process.env.OPENAI_DEFAULT_OPUS_MODEL ?? '',
558+
thinkingEnabled: getOpenAIThinkingEnabled(process.env.OPENAI_ENABLE_THINKING),
553559
activeField: 'base_url',
554560
});
555561
} else if (value === 'china_providers') {
@@ -799,8 +805,15 @@ function OAuthStatusMessage({
799805
}
800806

801807
case 'openai_chat_api': {
802-
type OpenAIField = 'base_url' | 'api_key' | 'haiku_model' | 'sonnet_model' | 'opus_model';
803-
const OPENAI_FIELDS: OpenAIField[] = ['base_url', 'api_key', 'haiku_model', 'sonnet_model', 'opus_model'];
808+
type OpenAIField = 'base_url' | 'api_key' | 'haiku_model' | 'sonnet_model' | 'opus_model' | 'thinking_enabled';
809+
const OPENAI_FIELDS: OpenAIField[] = [
810+
'base_url',
811+
'api_key',
812+
'haiku_model',
813+
'sonnet_model',
814+
'opus_model',
815+
'thinking_enabled',
816+
];
804817
const op = oauthStatus as {
805818
state: 'openai_chat_api';
806819
activeField: OpenAIField;
@@ -809,19 +822,22 @@ function OAuthStatusMessage({
809822
haikuModel: string;
810823
sonnetModel: string;
811824
opusModel: string;
825+
thinkingEnabled: boolean;
812826
};
813-
const { activeField, baseUrl, apiKey, haikuModel, sonnetModel, opusModel } = op;
814-
const openaiDisplayValues: Record<OpenAIField, string> = {
827+
const { activeField, baseUrl, apiKey, haikuModel, sonnetModel, opusModel, thinkingEnabled } = op;
828+
const openaiDisplayValues: Record<Exclude<OpenAIField, 'thinking_enabled'>, string> = {
815829
base_url: baseUrl,
816830
api_key: apiKey,
817831
haiku_model: haikuModel,
818832
sonnet_model: sonnetModel,
819833
opus_model: opusModel,
820834
};
835+
const getOpenAIFieldValue = (field: OpenAIField): string =>
836+
field === 'thinking_enabled' ? '' : openaiDisplayValues[field];
821837

822-
const [openaiInputValue, setOpenaiInputValue] = useState(() => openaiDisplayValues[activeField]);
838+
const [openaiInputValue, setOpenaiInputValue] = useState(() => getOpenAIFieldValue(activeField));
823839
const [openaiInputCursorOffset, setOpenaiInputCursorOffset] = useState(
824-
() => openaiDisplayValues[activeField].length,
840+
() => getOpenAIFieldValue(activeField).length,
825841
);
826842

827843
const buildOpenAIState = useCallback(
@@ -834,6 +850,7 @@ function OAuthStatusMessage({
834850
haikuModel,
835851
sonnetModel,
836852
opusModel,
853+
thinkingEnabled,
837854
};
838855
switch (field) {
839856
case 'base_url':
@@ -846,15 +863,21 @@ function OAuthStatusMessage({
846863
return { ...s, sonnetModel: value };
847864
case 'opus_model':
848865
return { ...s, opusModel: value };
866+
case 'thinking_enabled':
867+
return s;
849868
}
850869
},
851-
[activeField, baseUrl, apiKey, haikuModel, sonnetModel, opusModel],
870+
[activeField, baseUrl, apiKey, haikuModel, sonnetModel, opusModel, thinkingEnabled],
852871
);
853872

854873
const doOpenAISave = useCallback(() => {
855-
const finalVals = { ...openaiDisplayValues, [activeField]: openaiInputValue };
874+
const finalVals =
875+
activeField === 'thinking_enabled'
876+
? openaiDisplayValues
877+
: { ...openaiDisplayValues, [activeField]: openaiInputValue };
856878
const env: Record<string, string | undefined> = {
857879
OPENAI_AUTH_MODE: undefined,
880+
OPENAI_ENABLE_THINKING: String(thinkingEnabled),
858881
};
859882

860883
// Validate base_url if provided
@@ -872,6 +895,7 @@ function OAuthStatusMessage({
872895
haikuModel: '',
873896
sonnetModel: '',
874897
opusModel: '',
898+
thinkingEnabled,
875899
activeField: 'base_url',
876900
},
877901
});
@@ -900,6 +924,7 @@ function OAuthStatusMessage({
900924
haikuModel: finalVals.haiku_model ?? '',
901925
sonnetModel: finalVals.sonnet_model ?? '',
902926
opusModel: finalVals.opus_model ?? '',
927+
thinkingEnabled,
903928
activeField: 'base_url',
904929
},
905930
});
@@ -919,7 +944,7 @@ function OAuthStatusMessage({
919944
setOAuthStatus({ state: 'success' });
920945
void onDone();
921946
}
922-
}, [activeField, openaiInputValue, openaiDisplayValues, setOAuthStatus, onDone]);
947+
}, [activeField, openaiInputValue, openaiDisplayValues, thinkingEnabled, setOAuthStatus, onDone]);
923948

924949
const handleOpenAIEnter = useCallback(() => {
925950
const idx = OPENAI_FIELDS.indexOf(activeField);
@@ -929,8 +954,8 @@ function OAuthStatusMessage({
929954
} else {
930955
const next = OPENAI_FIELDS[idx + 1]!;
931956
setOAuthStatus(buildOpenAIState(activeField, openaiInputValue, next));
932-
setOpenaiInputValue(openaiDisplayValues[next] ?? '');
933-
setOpenaiInputCursorOffset((openaiDisplayValues[next] ?? '').length);
957+
setOpenaiInputValue(getOpenAIFieldValue(next));
958+
setOpenaiInputCursorOffset(getOpenAIFieldValue(next).length);
934959
}
935960
}, [activeField, openaiInputValue, buildOpenAIState, doOpenAISave, openaiDisplayValues, setOAuthStatus]);
936961

@@ -939,9 +964,10 @@ function OAuthStatusMessage({
939964
() => {
940965
const idx = OPENAI_FIELDS.indexOf(activeField);
941966
if (idx < OPENAI_FIELDS.length - 1) {
942-
setOAuthStatus(buildOpenAIState(activeField, openaiInputValue, OPENAI_FIELDS[idx + 1]));
943-
setOpenaiInputValue(openaiDisplayValues[OPENAI_FIELDS[idx + 1]!] ?? '');
944-
setOpenaiInputCursorOffset((openaiDisplayValues[OPENAI_FIELDS[idx + 1]!] ?? '').length);
967+
const next = OPENAI_FIELDS[idx + 1]!;
968+
setOAuthStatus(buildOpenAIState(activeField, openaiInputValue, next));
969+
setOpenaiInputValue(getOpenAIFieldValue(next));
970+
setOpenaiInputCursorOffset(getOpenAIFieldValue(next).length);
945971
}
946972
},
947973
{ context: 'FormField' },
@@ -952,12 +978,24 @@ function OAuthStatusMessage({
952978
const idx = OPENAI_FIELDS.indexOf(activeField);
953979
if (idx > 0) {
954980
setOAuthStatus(buildOpenAIState(activeField, openaiInputValue, OPENAI_FIELDS[idx - 1]));
955-
setOpenaiInputValue(openaiDisplayValues[OPENAI_FIELDS[idx - 1]!] ?? '');
956-
setOpenaiInputCursorOffset((openaiDisplayValues[OPENAI_FIELDS[idx - 1]!] ?? '').length);
981+
const previous = OPENAI_FIELDS[idx - 1]!;
982+
setOpenaiInputValue(getOpenAIFieldValue(previous));
983+
setOpenaiInputCursorOffset(getOpenAIFieldValue(previous).length);
957984
}
958985
},
959986
{ context: 'FormField' },
960987
);
988+
useKeybinding(
989+
'confirm:toggle',
990+
() => {
991+
setOAuthStatus({ ...op, thinkingEnabled: !thinkingEnabled });
992+
},
993+
{ context: 'Confirmation', isActive: activeField === 'thinking_enabled' },
994+
);
995+
useKeybinding('confirm:yes', doOpenAISave, {
996+
context: 'Confirmation',
997+
isActive: activeField === 'thinking_enabled',
998+
});
961999
useKeybinding(
9621000
'confirm:no',
9631001
() => {
@@ -968,7 +1006,11 @@ function OAuthStatusMessage({
9681006

9691007
const openaiColumns = useTerminalSize().columns - 20;
9701008

971-
const renderOpenAIRow = (field: OpenAIField, label: string, opts?: { mask?: boolean }) => {
1009+
const renderOpenAIRow = (
1010+
field: Exclude<OpenAIField, 'thinking_enabled'>,
1011+
label: string,
1012+
opts?: { mask?: boolean },
1013+
) => {
9721014
const active = activeField === field;
9731015
const val = openaiDisplayValues[field];
9741016
return (
@@ -1007,8 +1049,18 @@ function OAuthStatusMessage({
10071049
{renderOpenAIRow('haiku_model', 'Haiku ')}
10081050
{renderOpenAIRow('sonnet_model', 'Sonnet ')}
10091051
{renderOpenAIRow('opus_model', 'Opus ')}
1052+
<Box>
1053+
<Text
1054+
backgroundColor={activeField === 'thinking_enabled' ? 'suggestion' : undefined}
1055+
color={activeField === 'thinking_enabled' ? 'inverseText' : undefined}
1056+
>
1057+
{' Thinking/effort '}
1058+
</Text>
1059+
<Text> </Text>
1060+
<Text color={thinkingEnabled ? 'success' : 'warning'}>{String(thinkingEnabled)}</Text>
1061+
</Box>
10101062
</Box>
1011-
<Text dimColor>↑↓/Tab to switch · Enter on last field to save · Esc to go back</Text>
1063+
<Text dimColor>↑↓/Tab to switch · Space to toggle · Enter to save · Esc to go back</Text>
10121064
</Box>
10131065
);
10141066
}

src/services/api/openai/__tests__/thinking.test.ts

Lines changed: 20 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -43,29 +43,11 @@ describe('isOpenAIThinkingEnabled', () => {
4343
})
4444

4545
describe('OPENAI_ENABLE_THINKING env var', () => {
46-
test('returns true when OPENAI_ENABLE_THINKING=1', () => {
47-
process.env.OPENAI_ENABLE_THINKING = '1'
48-
expect(isOpenAIThinkingEnabled('gpt-4o')).toBe(true)
49-
})
50-
51-
test('returns true when OPENAI_ENABLE_THINKING=true', () => {
52-
process.env.OPENAI_ENABLE_THINKING = 'true'
53-
expect(isOpenAIThinkingEnabled('gpt-4o')).toBe(true)
54-
})
55-
56-
test('returns true when OPENAI_ENABLE_THINKING=yes', () => {
57-
process.env.OPENAI_ENABLE_THINKING = 'yes'
58-
expect(isOpenAIThinkingEnabled('gpt-4o')).toBe(true)
59-
})
60-
61-
test('returns true when OPENAI_ENABLE_THINKING=on', () => {
62-
process.env.OPENAI_ENABLE_THINKING = 'on'
63-
expect(isOpenAIThinkingEnabled('gpt-4o')).toBe(true)
64-
})
65-
66-
test('returns true when OPENAI_ENABLE_THINKING=TRUE (case insensitive)', () => {
67-
process.env.OPENAI_ENABLE_THINKING = 'TRUE'
68-
expect(isOpenAIThinkingEnabled('gpt-4o')).toBe(true)
46+
test('does not send provider-specific thinking fields to generic models', () => {
47+
for (const value of ['1', 'true', 'yes', 'on', 'TRUE']) {
48+
process.env.OPENAI_ENABLE_THINKING = value
49+
expect(isOpenAIThinkingEnabled('gpt-4o')).toBe(false)
50+
}
6951
})
7052

7153
test('returns false when OPENAI_ENABLE_THINKING=0', () => {
@@ -173,11 +155,11 @@ describe('isOpenAIThinkingEnabled', () => {
173155
})
174156

175157
describe('priority and combined detection', () => {
176-
test('OPENAI_ENABLE_THINKING=1 enables thinking for any model', () => {
158+
test('OPENAI_ENABLE_THINKING=1 only enables provider-specific fields for detected models', () => {
177159
process.env.OPENAI_ENABLE_THINKING = '1'
178-
expect(isOpenAIThinkingEnabled('gpt-4o')).toBe(true)
160+
expect(isOpenAIThinkingEnabled('gpt-4o')).toBe(false)
179161
expect(isOpenAIThinkingEnabled('deepseek-v3')).toBe(true)
180-
expect(isOpenAIThinkingEnabled('qwen-3')).toBe(true)
162+
expect(isOpenAIThinkingEnabled('qwen-3')).toBe(false)
181163
})
182164

183165
test('OPENAI_ENABLE_THINKING=false disables thinking even for deepseek-reasoner', () => {
@@ -221,30 +203,26 @@ describe('buildOpenAIRequestBody — thinking params', () => {
221203
expect(body.prompt_cache_key).toBe('ccb:test-session')
222204
})
223205

224-
test('includes vLLM/self-hosted thinking format when enabled', () => {
225-
const body = buildOpenAIRequestBody({ ...baseParams, enableThinking: true })
226-
expect(body.enable_thinking).toBe(true)
227-
expect(body.chat_template_kwargs).toEqual({
228-
thinking: true,
229-
enable_thinking: true,
230-
})
231-
})
232-
233-
test('includes both formats simultaneously when enabled', () => {
234-
const body = buildOpenAIRequestBody({ ...baseParams, enableThinking: true })
206+
test('sends reasoning effort with the DeepSeek thinking body', () => {
207+
const body = buildOpenAIRequestBody({
208+
...baseParams,
209+
enableThinking: true,
210+
reasoningEffort: 'high',
211+
}) as Record<string, unknown>
212+
expect(body.reasoning_effort).toBe('high')
235213
expect(body.thinking).toEqual({ type: 'enabled' })
236-
expect(body.enable_thinking).toBe(true)
237-
expect(body.chat_template_kwargs!.thinking).toBe(true)
214+
expect('enable_thinking' in body).toBe(false)
215+
expect('chat_template_kwargs' in body).toBe(false)
238216
})
239217

240218
test('does NOT include thinking params when disabled', () => {
241219
const body = buildOpenAIRequestBody({
242220
...baseParams,
243221
enableThinking: false,
244-
})
222+
}) as Record<string, unknown>
245223
expect(body.thinking).toBeUndefined()
246-
expect(body.enable_thinking).toBeUndefined()
247-
expect(body.chat_template_kwargs).toBeUndefined()
224+
expect('enable_thinking' in body).toBe(false)
225+
expect('chat_template_kwargs' in body).toBe(false)
248226
})
249227

250228
test('always includes stream and stream_options', () => {

0 commit comments

Comments
 (0)