@@ -172,6 +172,42 @@ function createCodexOAuthAuthStore() {
172172 } ;
173173}
174174
175+ function createCodexApiKeyAuthStore ( ) {
176+ return {
177+ version : 1 as const ,
178+ profiles : {
179+ "openai-codex:manual" : {
180+ type : "api_key" as const ,
181+ provider : "openai-codex" ,
182+ key : "codex-api-key" ,
183+ } ,
184+ } ,
185+ } ;
186+ }
187+
188+ function createCodexTokenAuthStore ( ) {
189+ return {
190+ version : 1 as const ,
191+ profiles : {
192+ "openai-codex:token" : {
193+ type : "token" as const ,
194+ provider : "openai-codex" ,
195+ token : "codex-token" ,
196+ } ,
197+ } ,
198+ } ;
199+ }
200+
201+ function createMixedCodexAuthStore ( ) {
202+ return {
203+ version : 1 as const ,
204+ profiles : {
205+ ...createCodexTokenAuthStore ( ) . profiles ,
206+ ...createCodexApiKeyAuthStore ( ) . profiles ,
207+ } ,
208+ } ;
209+ }
210+
175211type MockWithCalls = {
176212 mock : {
177213 calls : readonly ( readonly unknown [ ] ) [ ] ;
@@ -841,6 +877,180 @@ describe("openai image generation provider", () => {
841877 } ) ;
842878 } ) ;
843879
880+ it ( "does not treat Codex API key profiles as configured Codex OAuth image auth" , async ( ) => {
881+ mockGeneratedPngResponse ( ) ;
882+ resolveApiKeyForProviderMock . mockImplementation ( async ( params ?: { provider ?: string } ) => {
883+ if ( params ?. provider === "openai" ) {
884+ return { apiKey : "openai-key" , source : "profile:openai" , mode : "api-key" } ;
885+ }
886+ throw new Error ( `Unexpected auth provider ${ params ?. provider ?? "" } ` ) ;
887+ } ) ;
888+
889+ const provider = buildOpenAIImageGenerationProvider ( ) ;
890+ await provider . generateImage ( {
891+ provider : "openai" ,
892+ model : "gpt-image-2" ,
893+ prompt : "Draw with OpenAI auth despite a Codex API key profile" ,
894+ cfg : { } ,
895+ authStore : createCodexApiKeyAuthStore ( ) ,
896+ } ) ;
897+
898+ expect ( resolveApiKeyForProviderMock ) . toHaveBeenCalledTimes ( 1 ) ;
899+ expect ( authResolutionCall ( ) . provider ) . toBe ( "openai" ) ;
900+ expect ( jsonRequestCall ( ) . url ) . toBe ( "https://api.openai.com/v1/images/generations" ) ;
901+ expect ( httpConfigCall ( ) . provider ) . toBe ( "openai" ) ;
902+ expect ( logInfoMock ) . not . toHaveBeenCalledWith (
903+ expect . stringContaining ( "transport=codex-responses" ) ,
904+ ) ;
905+ } ) ;
906+
907+ it ( "uses native OpenAI image requests when only Codex API key auth is available" , async ( ) => {
908+ mockGeneratedPngResponse ( ) ;
909+ resolveApiKeyForProviderMock . mockImplementation ( async ( params ?: { provider ?: string } ) => {
910+ if ( params ?. provider === "openai" ) {
911+ return { } ;
912+ }
913+ if ( params ?. provider === "openai-codex" ) {
914+ return {
915+ apiKey : "codex-api-key" ,
916+ source : "profile:openai-codex:manual" ,
917+ mode : "api-key" ,
918+ } ;
919+ }
920+ return { } ;
921+ } ) ;
922+
923+ const provider = buildOpenAIImageGenerationProvider ( ) ;
924+ await provider . generateImage ( {
925+ provider : "openai" ,
926+ model : "gpt-image-2" ,
927+ prompt : "Draw through Codex API key auth" ,
928+ cfg : { } ,
929+ authStore : createCodexApiKeyAuthStore ( ) ,
930+ } ) ;
931+
932+ expect ( resolveApiKeyForProviderMock ) . toHaveBeenCalledTimes ( 2 ) ;
933+ expect ( authResolutionCall ( 0 ) . provider ) . toBe ( "openai" ) ;
934+ expect ( authResolutionCall ( 1 ) . provider ) . toBe ( "openai-codex" ) ;
935+ const configCall = httpConfigCall ( ) ;
936+ expect ( configCall . defaultBaseUrl ) . toBe ( "https://api.openai.com/v1" ) ;
937+ expect ( configCall . defaultHeaders ) . toEqual ( {
938+ Authorization : "Bearer codex-api-key" ,
939+ } ) ;
940+ expect ( configCall . provider ) . toBe ( "openai" ) ;
941+ expect ( configCall . api ) . toBeUndefined ( ) ;
942+ expect ( jsonRequestCall ( ) . url ) . toBe ( "https://api.openai.com/v1/images/generations" ) ;
943+ expect ( postMultipartRequestMock ) . not . toHaveBeenCalled ( ) ;
944+ expect ( logInfoMock ) . not . toHaveBeenCalledWith (
945+ expect . stringContaining ( "transport=codex-responses" ) ,
946+ ) ;
947+ } ) ;
948+
949+ it ( "uses native OpenAI image requests when mixed Codex profiles resolve to an API key" , async ( ) => {
950+ mockGeneratedPngResponse ( ) ;
951+ resolveApiKeyForProviderMock . mockImplementation ( async ( params ?: { provider ?: string } ) => {
952+ if ( params ?. provider === "openai-codex" ) {
953+ return {
954+ apiKey : "codex-api-key" ,
955+ source : "profile:openai-codex:manual" ,
956+ mode : "api-key" ,
957+ } ;
958+ }
959+ throw new Error ( `Unexpected auth provider ${ params ?. provider ?? "" } ` ) ;
960+ } ) ;
961+
962+ const provider = buildOpenAIImageGenerationProvider ( ) ;
963+ await provider . generateImage ( {
964+ provider : "openai" ,
965+ model : "gpt-image-2" ,
966+ prompt : "Draw through resolved Codex API key auth" ,
967+ cfg : { } ,
968+ authStore : createMixedCodexAuthStore ( ) ,
969+ } ) ;
970+
971+ expect ( resolveApiKeyForProviderMock ) . toHaveBeenCalledTimes ( 1 ) ;
972+ expect ( authResolutionCall ( ) . provider ) . toBe ( "openai-codex" ) ;
973+ expect ( httpConfigCall ( ) . defaultBaseUrl ) . toBe ( "https://api.openai.com/v1" ) ;
974+ expect ( httpConfigCall ( ) . defaultHeaders ) . toEqual ( {
975+ Authorization : "Bearer codex-api-key" ,
976+ } ) ;
977+ expect ( httpConfigCall ( ) . provider ) . toBe ( "openai" ) ;
978+ expect ( jsonRequestCall ( ) . url ) . toBe ( "https://api.openai.com/v1/images/generations" ) ;
979+ expect ( logInfoMock ) . not . toHaveBeenCalledWith (
980+ expect . stringContaining ( "transport=codex-responses" ) ,
981+ ) ;
982+ } ) ;
983+
984+ it ( "keeps Codex token auth on the Codex image transport" , async ( ) => {
985+ mockCodexImageStream ( { imageData : "codex-token-image" } ) ;
986+ resolveApiKeyForProviderMock . mockImplementation ( async ( params ?: { provider ?: string } ) => {
987+ if ( params ?. provider === "openai" ) {
988+ return { } ;
989+ }
990+ if ( params ?. provider === "openai-codex" ) {
991+ return {
992+ apiKey : "codex-token" ,
993+ source : "profile:openai-codex:token" ,
994+ mode : "token" ,
995+ } ;
996+ }
997+ return { } ;
998+ } ) ;
999+
1000+ const provider = buildOpenAIImageGenerationProvider ( ) ;
1001+ const result = await provider . generateImage ( {
1002+ provider : "openai" ,
1003+ model : "gpt-image-2" ,
1004+ prompt : "Draw through Codex token auth" ,
1005+ cfg : { } ,
1006+ authStore : createCodexTokenAuthStore ( ) ,
1007+ } ) ;
1008+
1009+ expect ( resolveApiKeyForProviderMock ) . toHaveBeenCalledTimes ( 1 ) ;
1010+ expect ( authResolutionCall ( ) . provider ) . toBe ( "openai-codex" ) ;
1011+ expect ( httpConfigCall ( ) . defaultBaseUrl ) . toBe ( "https://chatgpt.com/backend-api/codex" ) ;
1012+ expect ( httpConfigCall ( ) . provider ) . toBe ( "openai-codex" ) ;
1013+ expect ( httpConfigCall ( ) . api ) . toBe ( "openai-codex-responses" ) ;
1014+ expect ( jsonRequestCall ( ) . url ) . toBe ( "https://chatgpt.com/backend-api/codex/responses" ) ;
1015+ expect ( postMultipartRequestMock ) . not . toHaveBeenCalled ( ) ;
1016+ expect ( result . images [ 0 ] ?. buffer ) . toEqual ( Buffer . from ( "codex-token-image" ) ) ;
1017+ } ) ;
1018+
1019+ it ( "uses configured Codex token auth before probing an available OpenAI API key" , async ( ) => {
1020+ mockCodexImageStream ( { imageData : "codex-token-image" } ) ;
1021+ resolveApiKeyForProviderMock . mockImplementation ( async ( params ?: { provider ?: string } ) => {
1022+ if ( params ?. provider === "openai" ) {
1023+ return { apiKey : "openai-key" , source : "OPENAI_API_KEY" , mode : "api-key" } ;
1024+ }
1025+ if ( params ?. provider === "openai-codex" ) {
1026+ return {
1027+ apiKey : "codex-token" ,
1028+ source : "profile:openai-codex:token" ,
1029+ mode : "token" ,
1030+ } ;
1031+ }
1032+ return { } ;
1033+ } ) ;
1034+
1035+ const provider = buildOpenAIImageGenerationProvider ( ) ;
1036+ await provider . generateImage ( {
1037+ provider : "openai" ,
1038+ model : "gpt-image-2" ,
1039+ prompt : "Draw using configured Codex token auth" ,
1040+ cfg : { } ,
1041+ authStore : createCodexTokenAuthStore ( ) ,
1042+ } ) ;
1043+
1044+ expect ( resolveApiKeyForProviderMock ) . toHaveBeenCalledTimes ( 1 ) ;
1045+ expect ( authResolutionCall ( ) . provider ) . toBe ( "openai-codex" ) ;
1046+ expect (
1047+ resolveApiKeyForProviderMock . mock . calls . some (
1048+ ( [ call ] ) => ( call as AuthResolutionCall ) . provider === "openai" ,
1049+ ) ,
1050+ ) . toBe ( false ) ;
1051+ expect ( jsonRequestCall ( ) . url ) . toBe ( "https://chatgpt.com/backend-api/codex/responses" ) ;
1052+ } ) ;
1053+
8441054 it ( "routes transparent default-model Codex OAuth requests to the alpha-capable image model" , async ( ) => {
8451055 mockCodexAuthOnly ( ) ;
8461056 mockCodexImageStream ( { imageData : "codex-transparent-image" } ) ;
0 commit comments