@@ -13,6 +13,7 @@ import {
1313 refreshCodexAppServerAuthTokens ,
1414 resolveCodexAppServerAuthAccountCacheKey ,
1515 resolveCodexAppServerAuthProfileId ,
16+ resolveCodexAppServerFallbackApiKeyCacheKey ,
1617 resolveCodexAppServerHomeDir ,
1718 resolveCodexAppServerNativeHomeDir ,
1819} from "./auth-bridge.js" ;
@@ -172,6 +173,17 @@ async function writeCodexCliAuthFile(codexHome: string): Promise<void> {
172173 ) ;
173174}
174175
176+ async function writeCodexCliApiKeyAuthFile ( codexHome : string ) : Promise < void > {
177+ await fs . mkdir ( codexHome , { recursive : true } ) ;
178+ await fs . writeFile (
179+ path . join ( codexHome , "auth.json" ) ,
180+ `${ JSON . stringify ( {
181+ auth_mode : "apikey" ,
182+ OPENAI_API_KEY : "cli-auth-json-api-key" ,
183+ } ) } \n`,
184+ ) ;
185+ }
186+
175187describe ( "bridgeCodexAppServerStartOptions" , ( ) => {
176188 it ( "sets agent-owned CODEX_HOME without overriding HOME for local app-server launches" , async ( ) => {
177189 const agentDir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , "openclaw-codex-app-server-" ) ) ;
@@ -1042,6 +1054,92 @@ describe("bridgeCodexAppServerStartOptions", () => {
10421054 }
10431055 } ) ;
10441056
1057+ it ( "uses Codex CLI api-key auth.json when no auth profile or env key exists" , async ( ) => {
1058+ const root = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , "openclaw-codex-app-server-" ) ) ;
1059+ const agentDir = path . join ( root , "agent" ) ;
1060+ const codexHome = path . join ( root , "codex-cli" ) ;
1061+ const request = vi . fn ( async ( method : string ) => {
1062+ if ( method === "account/read" ) {
1063+ return { account : null , requiresOpenaiAuth : true } ;
1064+ }
1065+ return { type : "apiKey" } ;
1066+ } ) ;
1067+ vi . stubEnv ( "CODEX_HOME" , codexHome ) ;
1068+ vi . stubEnv ( "CODEX_API_KEY" , "" ) ;
1069+ vi . stubEnv ( "OPENAI_API_KEY" , "" ) ;
1070+ try {
1071+ await writeCodexCliApiKeyAuthFile ( codexHome ) ;
1072+
1073+ await applyCodexAppServerAuthProfile ( {
1074+ client : { request } as never ,
1075+ agentDir,
1076+ startOptions : createStartOptions ( {
1077+ env : { CODEX_HOME : path . join ( root , "isolated-codex-home" ) } ,
1078+ } ) ,
1079+ } ) ;
1080+
1081+ expect ( request ) . toHaveBeenNthCalledWith ( 1 , "account/read" , { refreshToken : false } ) ;
1082+ expect ( request ) . toHaveBeenNthCalledWith ( 2 , "account/login/start" , {
1083+ type : "apiKey" ,
1084+ apiKey : "cli-auth-json-api-key" ,
1085+ } ) ;
1086+ } finally {
1087+ await fs . rm ( root , { recursive : true , force : true } ) ;
1088+ }
1089+ } ) ;
1090+
1091+ it ( "includes Codex CLI api-key auth.json in fallback app-server cache keys" , async ( ) => {
1092+ const root = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , "openclaw-codex-app-server-" ) ) ;
1093+ const codexHome = path . join ( root , "codex-cli" ) ;
1094+ try {
1095+ await writeCodexCliApiKeyAuthFile ( codexHome ) ;
1096+
1097+ const first = resolveCodexAppServerFallbackApiKeyCacheKey ( {
1098+ startOptions : createStartOptions ( ) ,
1099+ baseEnv : { CODEX_HOME : codexHome } ,
1100+ } ) ;
1101+ await fs . writeFile (
1102+ path . join ( codexHome , "auth.json" ) ,
1103+ `${ JSON . stringify ( {
1104+ auth_mode : "apikey" ,
1105+ OPENAI_API_KEY : "second-cli-auth-json-api-key" ,
1106+ } ) } \n`,
1107+ ) ;
1108+ const second = resolveCodexAppServerFallbackApiKeyCacheKey ( {
1109+ startOptions : createStartOptions ( ) ,
1110+ baseEnv : { CODEX_HOME : codexHome } ,
1111+ } ) ;
1112+
1113+ expect ( first ) . toMatch ( / ^ C O D E X _ A U T H _ J S O N : s h a 2 5 6 : [ a - f 0 - 9 ] { 64 } $ / ) ;
1114+ expect ( second ) . toMatch ( / ^ C O D E X _ A U T H _ J S O N : s h a 2 5 6 : [ a - f 0 - 9 ] { 64 } $ / ) ;
1115+ expect ( second ) . not . toBe ( first ) ;
1116+ expect ( first ) . not . toContain ( "cli-auth-json-api-key" ) ;
1117+ expect ( second ) . not . toContain ( "second-cli-auth-json-api-key" ) ;
1118+ } finally {
1119+ await fs . rm ( root , { recursive : true , force : true } ) ;
1120+ }
1121+ } ) ;
1122+
1123+ it ( "does not include Codex CLI api-key auth.json in websocket fallback cache keys" , async ( ) => {
1124+ const root = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , "openclaw-codex-app-server-" ) ) ;
1125+ const codexHome = path . join ( root , "codex-cli" ) ;
1126+ try {
1127+ await writeCodexCliApiKeyAuthFile ( codexHome ) ;
1128+
1129+ expect (
1130+ resolveCodexAppServerFallbackApiKeyCacheKey ( {
1131+ startOptions : createStartOptions ( {
1132+ transport : "websocket" ,
1133+ url : "ws://127.0.0.1:1455" ,
1134+ } ) ,
1135+ baseEnv : { CODEX_HOME : codexHome } ,
1136+ } ) ,
1137+ ) . toBeUndefined ( ) ;
1138+ } finally {
1139+ await fs . rm ( root , { recursive : true , force : true } ) ;
1140+ }
1141+ } ) ;
1142+
10451143 it ( "honors clearEnv before env API-key fallback" , async ( ) => {
10461144 const agentDir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , "openclaw-codex-app-server-" ) ) ;
10471145 const request = vi . fn ( async ( method : string ) => {
0 commit comments