1- import { afterEach , expect , mock , test } from 'bun:test'
2- // Import the real auth.js and providerConfig.js up front so we can spread
3- // their export surfaces into mock factories. `mock.module()` is process-global
4- // in bun:test and `mock.restore()` does not undo it (see user.test.ts), so
5- // any module we mock here needs to keep the full original export shape — or
6- // downstream tests that load it via openaiShim/client/codexShim crash with
7- // "Export named 'X' not found in module".
8- import * as actualAuth from './auth.js'
9- import * as actualProviderConfig from '../services/api/providerConfig.js'
10- import * as actualThinking from './thinking.js'
11- import * as actualGrowthbook from 'src/services/analytics/growthbook.js'
12- import * as actualProviders from './model/providers.js'
13- import * as actualModelSupportOverrides from './model/modelSupportOverrides.js'
1+ import { afterEach , beforeEach , expect , mock , test } from 'bun:test'
2+
3+ const ENV_KEYS = [
4+ 'CLAUDE_CODE_USE_BEDROCK' ,
5+ 'CLAUDE_CODE_USE_FOUNDRY' ,
6+ 'CLAUDE_CODE_USE_GEMINI' ,
7+ 'CLAUDE_CODE_USE_GITHUB' ,
8+ 'CLAUDE_CODE_USE_MISTRAL' ,
9+ 'CLAUDE_CODE_USE_OPENAI' ,
10+ 'CLAUDE_CODE_USE_VERTEX' ,
11+ 'CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED' ,
12+ 'MISTRAL_BASE_URL' ,
13+ 'OPENAI_BASE_URL' ,
14+ 'OPENAI_MODEL' ,
15+ 'OPENAI_API_BASE' ,
16+ 'XAI_API_KEY' ,
17+ 'MINIMAX_API_KEY' ,
18+ ] as const
19+
20+ const originalEnv : Record < string , string | undefined > = { }
21+ type TestProvider = 'codex' | 'openai' | 'firstParty'
22+
23+ function resolveProviderFromEnv ( ) : TestProvider {
24+ if ( process . env . CLAUDE_CODE_USE_OPENAI ) {
25+ const baseUrl = process . env . OPENAI_BASE_URL ?? ''
26+ const model = process . env . OPENAI_MODEL ?? ''
27+ return baseUrl . includes ( '/backend-api/codex' ) || model . startsWith ( 'codex' )
28+ ? 'codex'
29+ : 'openai'
30+ }
31+ return 'firstParty'
32+ }
1433
15- afterEach ( ( ) => {
34+ function installProviderMock ( provider ?: TestProvider ) : void {
35+ mock . module ( './model/providers.js' , ( ) => ( {
36+ getAPIProvider : ( ) => provider ?? resolveProviderFromEnv ( ) ,
37+ getAPIProviderForStatsig : ( ) => provider ?? resolveProviderFromEnv ( ) ,
38+ isFirstPartyAnthropicBaseUrl : ( ) => true ,
39+ isGithubNativeAnthropicMode : ( ) => false ,
40+ usesAnthropicAccountFlow : ( ) =>
41+ ( provider ?? resolveProviderFromEnv ( ) ) === 'firstParty' ,
42+ } ) )
43+ }
44+
45+ beforeEach ( ( ) => {
1646 mock . restore ( )
47+ installProviderMock ( )
48+ for ( const key of ENV_KEYS ) {
49+ originalEnv [ key ] = process . env [ key ]
50+ }
1751} )
1852
19- async function importFreshEffortModule ( options : {
20- provider : 'codex' | 'openai'
21- supportsCodexReasoningEffort : boolean
22- } ) {
23- mock . module ( './model/providers.js' , ( ) => ( {
24- ...actualProviders ,
25- getAPIProvider : ( ) => options . provider ,
26- } ) )
27- mock . module ( './model/modelSupportOverrides.js' , ( ) => ( {
28- ...actualModelSupportOverrides ,
29- get3PModelCapabilityOverride : ( ) => undefined ,
30- } ) )
31- mock . module ( '../services/api/providerConfig.js' , ( ) => ( {
32- ...actualProviderConfig ,
33- supportsCodexReasoningEffort : ( ) => options . supportsCodexReasoningEffort ,
34- } ) )
35- mock . module ( './auth.js' , ( ) => ( {
36- ...actualAuth ,
37- isProSubscriber : ( ) => false ,
38- isMaxSubscriber : ( ) => false ,
39- isTeamSubscriber : ( ) => false ,
40- } ) )
41- mock . module ( './thinking.js' , ( ) => ( {
42- ...actualThinking ,
43- isUltrathinkEnabled : ( ) => false ,
44- } ) )
45- mock . module ( 'src/services/analytics/growthbook.js' , ( ) => ( {
46- ...actualGrowthbook ,
47- getFeatureValue_CACHED_MAY_BE_STALE : ( _key : string , fallback : unknown ) =>
48- fallback ,
49- } ) )
53+ afterEach ( ( ) => {
54+ for ( const key of ENV_KEYS ) {
55+ if ( originalEnv [ key ] === undefined ) {
56+ delete process . env [ key ]
57+ } else {
58+ process . env [ key ] = originalEnv [ key ]
59+ }
60+ }
61+ installProviderMock ( )
62+ } )
63+
64+ async function importFreshEffortModule ( options : { provider : TestProvider } ) {
65+ for ( const key of ENV_KEYS ) {
66+ delete process . env [ key ]
67+ }
68+ installProviderMock ( options . provider )
69+ if ( options . provider === 'codex' ) {
70+ process . env . CLAUDE_CODE_USE_OPENAI = '1'
71+ process . env . OPENAI_MODEL = 'gpt-5.4'
72+ } else if ( options . provider === 'openai' ) {
73+ process . env . CLAUDE_CODE_USE_OPENAI = '1'
74+ process . env . OPENAI_BASE_URL = 'https://api.openai.com/v1'
75+ process . env . OPENAI_MODEL = 'gpt-5.4'
76+ }
5077
5178 return import ( `./effort.js?ts=${ Date . now ( ) } -${ Math . random ( ) } ` )
5279}
5380
54- test ( 'gpt-5.4 on the ChatGPT Codex backend supports effort selection' , async ( ) => {
55- const { getAvailableEffortLevels, modelSupportsEffort } =
56- await importFreshEffortModule ( {
57- provider : 'codex' ,
58- supportsCodexReasoningEffort : true ,
59- } )
60-
61- expect ( modelSupportsEffort ( 'gpt-5.4' ) ) . toBe ( true )
62- expect ( getAvailableEffortLevels ( 'gpt-5.4' ) ) . toEqual ( [
63- 'low' ,
64- 'medium' ,
65- 'high' ,
66- 'xhigh' ,
67- ] )
68- } )
81+ test (
82+ 'gpt-5.4 on the ChatGPT Codex backend supports effort selection' ,
83+ async ( ) => {
84+ const { getAvailableEffortLevels, modelSupportsEffort } =
85+ await importFreshEffortModule ( {
86+ provider : 'codex' ,
87+ } )
88+
89+ expect ( modelSupportsEffort ( 'gpt-5.4' ) ) . toBe ( true )
90+ expect ( getAvailableEffortLevels ( 'gpt-5.4' ) ) . toEqual ( [
91+ 'low' ,
92+ 'medium' ,
93+ 'high' ,
94+ 'xhigh' ,
95+ ] )
96+ } ,
97+ 15_000 ,
98+ )
6999
70100test ( 'gpt-5.4 on the OpenAI provider still supports effort selection' , async ( ) => {
71101 const { getAvailableEffortLevels, modelSupportsEffort } =
72102 await importFreshEffortModule ( {
73103 provider : 'openai' ,
74- supportsCodexReasoningEffort : true ,
75104 } )
76105
77106 expect ( modelSupportsEffort ( 'gpt-5.4' ) ) . toBe ( true )
@@ -87,7 +116,6 @@ test('gpt-5.3-codex-spark stays without effort controls', async () => {
87116 const { getAvailableEffortLevels, modelSupportsEffort } =
88117 await importFreshEffortModule ( {
89118 provider : 'codex' ,
90- supportsCodexReasoningEffort : false ,
91119 } )
92120
93121 expect ( modelSupportsEffort ( 'gpt-5.3-codex-spark' ) ) . toBe ( false )
@@ -97,7 +125,6 @@ test('gpt-5.3-codex-spark stays without effort controls', async () => {
97125test ( 'toPersistableEffort normalizes xhigh to max so it survives settings write' , async ( ) => {
98126 const { toPersistableEffort } = await importFreshEffortModule ( {
99127 provider : 'openai' ,
100- supportsCodexReasoningEffort : true ,
101128 } )
102129
103130 expect ( toPersistableEffort ( 'xhigh' ) ) . toBe ( 'max' )
@@ -112,7 +139,6 @@ test('standardEffortToOpenAI maps max to xhigh for shim payload', async () => {
112139 const { standardEffortToOpenAI, openAIEffortToStandard } =
113140 await importFreshEffortModule ( {
114141 provider : 'openai' ,
115- supportsCodexReasoningEffort : true ,
116142 } )
117143
118144 expect ( standardEffortToOpenAI ( 'max' ) ) . toBe ( 'xhigh' )
@@ -128,7 +154,6 @@ test('e2e: xhigh → persisted max → resolveAppliedEffort → wire xhigh on Op
128154 standardEffortToOpenAI,
129155 } = await importFreshEffortModule ( {
130156 provider : 'openai' ,
131- supportsCodexReasoningEffort : true ,
132157 } )
133158
134159 // Picker writes the OpenAI-shaped value; toPersistableEffort normalizes.
@@ -147,19 +172,16 @@ test('e2e: xhigh → persisted max → resolveAppliedEffort → wire xhigh on Op
147172
148173test ( 'e2e: max on non-Opus Anthropic model still clamps to high' , async ( ) => {
149174 const { resolveAppliedEffort } = await importFreshEffortModule ( {
150- provider : 'firstParty' as unknown as 'openai' ,
151- supportsCodexReasoningEffort : false ,
175+ provider : 'firstParty' ,
152176 } )
153177
154178 expect ( resolveAppliedEffort ( 'claude-sonnet-4-6' , 'max' ) ) . toBe ( 'high' )
155179} )
156180
157-
158181test ( 'getEffortSuffix shows the effective displayed effort for supported models' , async ( ) => {
159182 const { getDisplayedEffortLevel, getEffortSuffix } =
160183 await importFreshEffortModule ( {
161184 provider : 'openai' ,
162- supportsCodexReasoningEffort : true ,
163185 } )
164186
165187 expect ( getEffortSuffix ( 'gpt-5.4' , 'medium' ) ) . toBe ( ' with medium effort' )
@@ -173,7 +195,6 @@ test('getEffortSuffix shows the effective displayed effort for supported models'
173195test ( 'getEffortSuffix stays hidden for models without effort controls' , async ( ) => {
174196 const { getEffortSuffix } = await importFreshEffortModule ( {
175197 provider : 'codex' ,
176- supportsCodexReasoningEffort : false ,
177198 } )
178199
179200 expect ( getEffortSuffix ( 'gpt-5.3-codex-spark' , 'medium' ) ) . toBe ( '' )
0 commit comments