Support CODEX_API_KEY for codex exec (#4615)

Allows to set API key per invocation of `codex exec`
This commit is contained in:
pakrym-oai
2025-10-02 09:59:45 -07:00
committed by GitHub
parent 35c76ad47d
commit 2f6fb37d72
13 changed files with 109 additions and 68 deletions

View File

@@ -34,7 +34,7 @@ impl MessageProcessor {
config: Arc<Config>, config: Arc<Config>,
) -> Self { ) -> Self {
let outgoing = Arc::new(outgoing); let outgoing = Arc::new(outgoing);
let auth_manager = AuthManager::shared(config.codex_home.clone()); let auth_manager = AuthManager::shared(config.codex_home.clone(), false);
let conversation_manager = Arc::new(ConversationManager::new(auth_manager.clone())); let conversation_manager = Arc::new(ConversationManager::new(auth_manager.clone()));
let codex_message_processor = CodexMessageProcessor::new( let codex_message_processor = CodexMessageProcessor::new(
auth_manager, auth_manager,

View File

@@ -190,7 +190,7 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> a
// Require ChatGPT login (SWIC). Exit with a clear message if missing. // Require ChatGPT login (SWIC). Exit with a clear message if missing.
let _token = match codex_core::config::find_codex_home() let _token = match codex_core::config::find_codex_home()
.ok() .ok()
.map(codex_login::AuthManager::new) .map(|home| codex_login::AuthManager::new(home, false))
.and_then(|am| am.auth()) .and_then(|am| am.auth())
{ {
Some(auth) => { Some(auth) => {

View File

@@ -70,7 +70,7 @@ pub async fn build_chatgpt_headers() -> HeaderMap {
HeaderValue::from_str(&ua).unwrap_or(HeaderValue::from_static("codex-cli")), HeaderValue::from_str(&ua).unwrap_or(HeaderValue::from_static("codex-cli")),
); );
if let Ok(home) = codex_core::config::find_codex_home() { if let Ok(home) = codex_core::config::find_codex_home() {
let am = codex_login::AuthManager::new(home); let am = codex_login::AuthManager::new(home, false);
if let Some(auth) = am.auth() if let Some(auth) = am.auth()
&& let Ok(tok) = auth.get_token().await && let Ok(tok) = auth.get_token().await
&& !tok.is_empty() && !tok.is_empty()

View File

@@ -73,7 +73,7 @@ impl CodexAuth {
/// Loads the available auth information from the auth.json. /// Loads the available auth information from the auth.json.
pub fn from_codex_home(codex_home: &Path) -> std::io::Result<Option<CodexAuth>> { pub fn from_codex_home(codex_home: &Path) -> std::io::Result<Option<CodexAuth>> {
load_auth(codex_home) load_auth(codex_home, false)
} }
pub async fn get_token_data(&self) -> Result<TokenData, std::io::Error> { pub async fn get_token_data(&self) -> Result<TokenData, std::io::Error> {
@@ -188,6 +188,7 @@ impl CodexAuth {
} }
pub const OPENAI_API_KEY_ENV_VAR: &str = "OPENAI_API_KEY"; pub const OPENAI_API_KEY_ENV_VAR: &str = "OPENAI_API_KEY";
pub const CODEX_API_KEY_ENV_VAR: &str = "CODEX_API_KEY";
pub fn read_openai_api_key_from_env() -> Option<String> { pub fn read_openai_api_key_from_env() -> Option<String> {
env::var(OPENAI_API_KEY_ENV_VAR) env::var(OPENAI_API_KEY_ENV_VAR)
@@ -196,6 +197,13 @@ pub fn read_openai_api_key_from_env() -> Option<String> {
.filter(|value| !value.is_empty()) .filter(|value| !value.is_empty())
} }
pub fn read_codex_api_key_from_env() -> Option<String> {
env::var(CODEX_API_KEY_ENV_VAR)
.ok()
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty())
}
pub fn get_auth_file(codex_home: &Path) -> PathBuf { pub fn get_auth_file(codex_home: &Path) -> PathBuf {
codex_home.join("auth.json") codex_home.join("auth.json")
} }
@@ -221,7 +229,18 @@ pub fn login_with_api_key(codex_home: &Path, api_key: &str) -> std::io::Result<(
write_auth_json(&get_auth_file(codex_home), &auth_dot_json) write_auth_json(&get_auth_file(codex_home), &auth_dot_json)
} }
fn load_auth(codex_home: &Path) -> std::io::Result<Option<CodexAuth>> { fn load_auth(
codex_home: &Path,
enable_codex_api_key_env: bool,
) -> std::io::Result<Option<CodexAuth>> {
if enable_codex_api_key_env && let Some(api_key) = read_codex_api_key_from_env() {
let client = crate::default_client::create_client();
return Ok(Some(CodexAuth::from_api_key_with_client(
api_key.as_str(),
client,
)));
}
let auth_file = get_auth_file(codex_home); let auth_file = get_auth_file(codex_home);
let client = crate::default_client::create_client(); let client = crate::default_client::create_client();
let auth_dot_json = match try_read_auth_json(&auth_file) { let auth_dot_json = match try_read_auth_json(&auth_file) {
@@ -455,7 +474,7 @@ mod tests {
auth_dot_json, auth_dot_json,
auth_file: _, auth_file: _,
.. ..
} = super::load_auth(codex_home.path()).unwrap().unwrap(); } = super::load_auth(codex_home.path(), false).unwrap().unwrap();
assert_eq!(None, api_key); assert_eq!(None, api_key);
assert_eq!(AuthMode::ChatGPT, mode); assert_eq!(AuthMode::ChatGPT, mode);
@@ -494,7 +513,7 @@ mod tests {
) )
.unwrap(); .unwrap();
let auth = super::load_auth(dir.path()).unwrap().unwrap(); let auth = super::load_auth(dir.path(), false).unwrap().unwrap();
assert_eq!(auth.mode, AuthMode::ApiKey); assert_eq!(auth.mode, AuthMode::ApiKey);
assert_eq!(auth.api_key, Some("sk-test-key".to_string())); assert_eq!(auth.api_key, Some("sk-test-key".to_string()));
@@ -577,6 +596,7 @@ mod tests {
pub struct AuthManager { pub struct AuthManager {
codex_home: PathBuf, codex_home: PathBuf,
inner: RwLock<CachedAuth>, inner: RwLock<CachedAuth>,
enable_codex_api_key_env: bool,
} }
impl AuthManager { impl AuthManager {
@@ -584,11 +604,14 @@ impl AuthManager {
/// preferred auth method. Errors loading auth are swallowed; `auth()` will /// preferred auth method. Errors loading auth are swallowed; `auth()` will
/// simply return `None` in that case so callers can treat it as an /// simply return `None` in that case so callers can treat it as an
/// unauthenticated state. /// unauthenticated state.
pub fn new(codex_home: PathBuf) -> Self { pub fn new(codex_home: PathBuf, enable_codex_api_key_env: bool) -> Self {
let auth = CodexAuth::from_codex_home(&codex_home).ok().flatten(); let auth = load_auth(&codex_home, enable_codex_api_key_env)
.ok()
.flatten();
Self { Self {
codex_home, codex_home,
inner: RwLock::new(CachedAuth { auth }), inner: RwLock::new(CachedAuth { auth }),
enable_codex_api_key_env,
} }
} }
@@ -598,6 +621,7 @@ impl AuthManager {
Arc::new(Self { Arc::new(Self {
codex_home: PathBuf::new(), codex_home: PathBuf::new(),
inner: RwLock::new(cached), inner: RwLock::new(cached),
enable_codex_api_key_env: false,
}) })
} }
@@ -609,7 +633,9 @@ impl AuthManager {
/// Force a reload of the auth information from auth.json. Returns /// Force a reload of the auth information from auth.json. Returns
/// whether the auth value changed. /// whether the auth value changed.
pub fn reload(&self) -> bool { pub fn reload(&self) -> bool {
let new_auth = CodexAuth::from_codex_home(&self.codex_home).ok().flatten(); let new_auth = load_auth(&self.codex_home, self.enable_codex_api_key_env)
.ok()
.flatten();
if let Ok(mut guard) = self.inner.write() { if let Ok(mut guard) = self.inner.write() {
let changed = !AuthManager::auths_equal(&guard.auth, &new_auth); let changed = !AuthManager::auths_equal(&guard.auth, &new_auth);
guard.auth = new_auth; guard.auth = new_auth;
@@ -628,8 +654,8 @@ impl AuthManager {
} }
/// Convenience constructor returning an `Arc` wrapper. /// Convenience constructor returning an `Arc` wrapper.
pub fn shared(codex_home: PathBuf) -> Arc<Self> { pub fn shared(codex_home: PathBuf, enable_codex_api_key_env: bool) -> Arc<Self> {
Arc::new(Self::new(codex_home)) Arc::new(Self::new(codex_home, enable_codex_api_key_env))
} }
/// Attempt to refresh the current auth token (if any). On success, reload /// Attempt to refresh the current auth token (if any). On success, reload

View File

@@ -1,4 +1,5 @@
#![allow(clippy::expect_used)] #![allow(clippy::expect_used)]
use codex_core::auth::CODEX_API_KEY_ENV_VAR;
use std::path::Path; use std::path::Path;
use tempfile::TempDir; use tempfile::TempDir;
use wiremock::MockServer; use wiremock::MockServer;
@@ -14,7 +15,7 @@ impl TestCodexExecBuilder {
.expect("should find binary for codex-exec"); .expect("should find binary for codex-exec");
cmd.current_dir(self.cwd.path()) cmd.current_dir(self.cwd.path())
.env("CODEX_HOME", self.home.path()) .env("CODEX_HOME", self.home.path())
.env("OPENAI_API_KEY", "dummy"); .env(CODEX_API_KEY_ENV_VAR, "dummy");
cmd cmd
} }
pub fn cmd_with_server(&self, server: &MockServer) -> assert_cmd::Command { pub fn cmd_with_server(&self, server: &MockServer) -> assert_cmd::Command {

View File

@@ -236,8 +236,8 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option<PathBuf>) -> any
std::process::exit(1); std::process::exit(1);
} }
let conversation_manager = let auth_manager = AuthManager::shared(config.codex_home.clone(), true);
ConversationManager::new(AuthManager::shared(config.codex_home.clone())); let conversation_manager = ConversationManager::new(auth_manager.clone());
// Handle resume subcommand by resolving a rollout path and using explicit resume API. // Handle resume subcommand by resolving a rollout path and using explicit resume API.
let NewConversation { let NewConversation {
@@ -249,11 +249,7 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option<PathBuf>) -> any
if let Some(path) = resume_path { if let Some(path) = resume_path {
conversation_manager conversation_manager
.resume_conversation_from_rollout( .resume_conversation_from_rollout(config.clone(), path, auth_manager.clone())
config.clone(),
path,
AuthManager::shared(config.codex_home.clone()),
)
.await? .await?
} else { } else {
conversation_manager conversation_manager

View File

@@ -0,0 +1,34 @@
#![allow(clippy::unwrap_used, clippy::expect_used)]
use core_test_support::responses::ev_completed;
use core_test_support::responses::sse;
use core_test_support::responses::sse_response;
use core_test_support::responses::start_mock_server;
use core_test_support::test_codex_exec::test_codex_exec;
use wiremock::Mock;
use wiremock::matchers::header;
use wiremock::matchers::method;
use wiremock::matchers::path;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn exec_uses_codex_api_key_env_var() -> anyhow::Result<()> {
let test = test_codex_exec();
let server = start_mock_server().await;
Mock::given(method("POST"))
.and(path("/v1/responses"))
.and(header("Authorization", "Bearer dummy"))
.respond_with(sse_response(sse(vec![ev_completed("request_0")])))
.expect(1)
.mount(&server)
.await;
test.cmd_with_server(&server)
.arg("--skip-git-repo-check")
.arg("-C")
.arg(env!("CARGO_MANIFEST_DIR"))
.arg("echo testing codex api key")
.assert()
.success();
Ok(())
}

View File

@@ -1,5 +1,6 @@
// Aggregates all former standalone integration tests as modules. // Aggregates all former standalone integration tests as modules.
mod apply_patch; mod apply_patch;
mod auth_env;
mod output_schema; mod output_schema;
mod resume; mod resume;
mod sandbox; mod sandbox;

View File

@@ -1,10 +1,9 @@
#![allow(clippy::unwrap_used, clippy::expect_used)] #![allow(clippy::unwrap_used, clippy::expect_used)]
use anyhow::Context; use anyhow::Context;
use assert_cmd::prelude::*; use core_test_support::test_codex_exec::test_codex_exec;
use serde_json::Value; use serde_json::Value;
use std::process::Command; use std::path::Path;
use std::string::ToString; use std::string::ToString;
use tempfile::TempDir;
use uuid::Uuid; use uuid::Uuid;
use walkdir::WalkDir; use walkdir::WalkDir;
@@ -72,18 +71,15 @@ fn extract_conversation_id(path: &std::path::Path) -> String {
#[test] #[test]
fn exec_resume_last_appends_to_existing_file() -> anyhow::Result<()> { fn exec_resume_last_appends_to_existing_file() -> anyhow::Result<()> {
let home = TempDir::new()?; let test = test_codex_exec();
let fixture = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) let fixture =
.join("tests/fixtures/cli_responses_fixture.sse"); Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/cli_responses_fixture.sse");
// 1) First run: create a session with a unique marker in the content. // 1) First run: create a session with a unique marker in the content.
let marker = format!("resume-last-{}", Uuid::new_v4()); let marker = format!("resume-last-{}", Uuid::new_v4());
let prompt = format!("echo {marker}"); let prompt = format!("echo {marker}");
Command::cargo_bin("codex-exec") test.cmd()
.context("should find binary for codex-exec")?
.env("CODEX_HOME", home.path())
.env("OPENAI_API_KEY", "dummy")
.env("CODEX_RS_SSE_FIXTURE", &fixture) .env("CODEX_RS_SSE_FIXTURE", &fixture)
.env("OPENAI_BASE_URL", "http://unused.local") .env("OPENAI_BASE_URL", "http://unused.local")
.arg("--skip-git-repo-check") .arg("--skip-git-repo-check")
@@ -94,7 +90,7 @@ fn exec_resume_last_appends_to_existing_file() -> anyhow::Result<()> {
.success(); .success();
// Find the created session file containing the marker. // Find the created session file containing the marker.
let sessions_dir = home.path().join("sessions"); let sessions_dir = test.home_path().join("sessions");
let path = find_session_file_containing_marker(&sessions_dir, &marker) let path = find_session_file_containing_marker(&sessions_dir, &marker)
.expect("no session file found after first run"); .expect("no session file found after first run");
@@ -102,11 +98,7 @@ fn exec_resume_last_appends_to_existing_file() -> anyhow::Result<()> {
let marker2 = format!("resume-last-2-{}", Uuid::new_v4()); let marker2 = format!("resume-last-2-{}", Uuid::new_v4());
let prompt2 = format!("echo {marker2}"); let prompt2 = format!("echo {marker2}");
let mut binding = assert_cmd::Command::cargo_bin("codex-exec") test.cmd()
.context("should find binary for codex-exec")?;
let cmd = binding
.env("CODEX_HOME", home.path())
.env("OPENAI_API_KEY", "dummy")
.env("CODEX_RS_SSE_FIXTURE", &fixture) .env("CODEX_RS_SSE_FIXTURE", &fixture)
.env("OPENAI_BASE_URL", "http://unused.local") .env("OPENAI_BASE_URL", "http://unused.local")
.arg("--skip-git-repo-check") .arg("--skip-git-repo-check")
@@ -114,8 +106,9 @@ fn exec_resume_last_appends_to_existing_file() -> anyhow::Result<()> {
.arg(env!("CARGO_MANIFEST_DIR")) .arg(env!("CARGO_MANIFEST_DIR"))
.arg(&prompt2) .arg(&prompt2)
.arg("resume") .arg("resume")
.arg("--last"); .arg("--last")
cmd.assert().success(); .assert()
.success();
// Ensure the same file was updated and contains both markers. // Ensure the same file was updated and contains both markers.
let resumed_path = find_session_file_containing_marker(&sessions_dir, &marker2) let resumed_path = find_session_file_containing_marker(&sessions_dir, &marker2)
@@ -132,18 +125,15 @@ fn exec_resume_last_appends_to_existing_file() -> anyhow::Result<()> {
#[test] #[test]
fn exec_resume_by_id_appends_to_existing_file() -> anyhow::Result<()> { fn exec_resume_by_id_appends_to_existing_file() -> anyhow::Result<()> {
let home = TempDir::new()?; let test = test_codex_exec();
let fixture = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) let fixture =
.join("tests/fixtures/cli_responses_fixture.sse"); Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/cli_responses_fixture.sse");
// 1) First run: create a session // 1) First run: create a session
let marker = format!("resume-by-id-{}", Uuid::new_v4()); let marker = format!("resume-by-id-{}", Uuid::new_v4());
let prompt = format!("echo {marker}"); let prompt = format!("echo {marker}");
Command::cargo_bin("codex-exec") test.cmd()
.context("should find binary for codex-exec")?
.env("CODEX_HOME", home.path())
.env("OPENAI_API_KEY", "dummy")
.env("CODEX_RS_SSE_FIXTURE", &fixture) .env("CODEX_RS_SSE_FIXTURE", &fixture)
.env("OPENAI_BASE_URL", "http://unused.local") .env("OPENAI_BASE_URL", "http://unused.local")
.arg("--skip-git-repo-check") .arg("--skip-git-repo-check")
@@ -153,7 +143,7 @@ fn exec_resume_by_id_appends_to_existing_file() -> anyhow::Result<()> {
.assert() .assert()
.success(); .success();
let sessions_dir = home.path().join("sessions"); let sessions_dir = test.home_path().join("sessions");
let path = find_session_file_containing_marker(&sessions_dir, &marker) let path = find_session_file_containing_marker(&sessions_dir, &marker)
.expect("no session file found after first run"); .expect("no session file found after first run");
let session_id = extract_conversation_id(&path); let session_id = extract_conversation_id(&path);
@@ -166,11 +156,7 @@ fn exec_resume_by_id_appends_to_existing_file() -> anyhow::Result<()> {
let marker2 = format!("resume-by-id-2-{}", Uuid::new_v4()); let marker2 = format!("resume-by-id-2-{}", Uuid::new_v4());
let prompt2 = format!("echo {marker2}"); let prompt2 = format!("echo {marker2}");
let mut binding = assert_cmd::Command::cargo_bin("codex-exec") test.cmd()
.context("should find binary for codex-exec")?;
let cmd = binding
.env("CODEX_HOME", home.path())
.env("OPENAI_API_KEY", "dummy")
.env("CODEX_RS_SSE_FIXTURE", &fixture) .env("CODEX_RS_SSE_FIXTURE", &fixture)
.env("OPENAI_BASE_URL", "http://unused.local") .env("OPENAI_BASE_URL", "http://unused.local")
.arg("--skip-git-repo-check") .arg("--skip-git-repo-check")
@@ -178,8 +164,9 @@ fn exec_resume_by_id_appends_to_existing_file() -> anyhow::Result<()> {
.arg(env!("CARGO_MANIFEST_DIR")) .arg(env!("CARGO_MANIFEST_DIR"))
.arg(&prompt2) .arg(&prompt2)
.arg("resume") .arg("resume")
.arg(&session_id); .arg(&session_id)
cmd.assert().success(); .assert()
.success();
let resumed_path = find_session_file_containing_marker(&sessions_dir, &marker2) let resumed_path = find_session_file_containing_marker(&sessions_dir, &marker2)
.expect("no resumed session file containing marker2"); .expect("no resumed session file containing marker2");
@@ -195,17 +182,14 @@ fn exec_resume_by_id_appends_to_existing_file() -> anyhow::Result<()> {
#[test] #[test]
fn exec_resume_preserves_cli_configuration_overrides() -> anyhow::Result<()> { fn exec_resume_preserves_cli_configuration_overrides() -> anyhow::Result<()> {
let home = TempDir::new()?; let test = test_codex_exec();
let fixture = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) let fixture =
.join("tests/fixtures/cli_responses_fixture.sse"); Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/cli_responses_fixture.sse");
let marker = format!("resume-config-{}", Uuid::new_v4()); let marker = format!("resume-config-{}", Uuid::new_v4());
let prompt = format!("echo {marker}"); let prompt = format!("echo {marker}");
Command::cargo_bin("codex-exec") test.cmd()
.context("should find binary for codex-exec")?
.env("CODEX_HOME", home.path())
.env("OPENAI_API_KEY", "dummy")
.env("CODEX_RS_SSE_FIXTURE", &fixture) .env("CODEX_RS_SSE_FIXTURE", &fixture)
.env("OPENAI_BASE_URL", "http://unused.local") .env("OPENAI_BASE_URL", "http://unused.local")
.arg("--skip-git-repo-check") .arg("--skip-git-repo-check")
@@ -219,17 +203,15 @@ fn exec_resume_preserves_cli_configuration_overrides() -> anyhow::Result<()> {
.assert() .assert()
.success(); .success();
let sessions_dir = home.path().join("sessions"); let sessions_dir = test.home_path().join("sessions");
let path = find_session_file_containing_marker(&sessions_dir, &marker) let path = find_session_file_containing_marker(&sessions_dir, &marker)
.expect("no session file found after first run"); .expect("no session file found after first run");
let marker2 = format!("resume-config-2-{}", Uuid::new_v4()); let marker2 = format!("resume-config-2-{}", Uuid::new_v4());
let prompt2 = format!("echo {marker2}"); let prompt2 = format!("echo {marker2}");
let output = Command::cargo_bin("codex-exec") let output = test
.context("should find binary for codex-exec")? .cmd()
.env("CODEX_HOME", home.path())
.env("OPENAI_API_KEY", "dummy")
.env("CODEX_RS_SSE_FIXTURE", &fixture) .env("CODEX_RS_SSE_FIXTURE", &fixture)
.env("OPENAI_BASE_URL", "http://unused.local") .env("OPENAI_BASE_URL", "http://unused.local")
.arg("--skip-git-repo-check") .arg("--skip-git-repo-check")

View File

@@ -14,6 +14,7 @@ pub use codex_core::AuthManager;
pub use codex_core::CodexAuth; pub use codex_core::CodexAuth;
pub use codex_core::auth::AuthDotJson; pub use codex_core::auth::AuthDotJson;
pub use codex_core::auth::CLIENT_ID; pub use codex_core::auth::CLIENT_ID;
pub use codex_core::auth::CODEX_API_KEY_ENV_VAR;
pub use codex_core::auth::OPENAI_API_KEY_ENV_VAR; pub use codex_core::auth::OPENAI_API_KEY_ENV_VAR;
pub use codex_core::auth::get_auth_file; pub use codex_core::auth::get_auth_file;
pub use codex_core::auth::login_with_api_key; pub use codex_core::auth::login_with_api_key;

View File

@@ -52,7 +52,7 @@ impl MessageProcessor {
config: Arc<Config>, config: Arc<Config>,
) -> Self { ) -> Self {
let outgoing = Arc::new(outgoing); let outgoing = Arc::new(outgoing);
let auth_manager = AuthManager::shared(config.codex_home.clone()); let auth_manager = AuthManager::shared(config.codex_home.clone(), false);
let conversation_manager = Arc::new(ConversationManager::new(auth_manager)); let conversation_manager = Arc::new(ConversationManager::new(auth_manager));
Self { Self {
outgoing, outgoing,

View File

@@ -361,7 +361,7 @@ async fn run_ratatui_app(
// Initialize high-fidelity session event logging if enabled. // Initialize high-fidelity session event logging if enabled.
session_log::maybe_init(&config); session_log::maybe_init(&config);
let auth_manager = AuthManager::shared(config.codex_home.clone()); let auth_manager = AuthManager::shared(config.codex_home.clone(), false);
let login_status = get_login_status(&config); let login_status = get_login_status(&config);
let should_show_onboarding = let should_show_onboarding =
should_show_onboarding(login_status, &config, should_show_trust_screen); should_show_onboarding(login_status, &config, should_show_trust_screen);

View File

@@ -58,7 +58,7 @@ export class CodexExec {
env.OPENAI_BASE_URL = args.baseUrl; env.OPENAI_BASE_URL = args.baseUrl;
} }
if (args.apiKey) { if (args.apiKey) {
env.OPENAI_API_KEY = args.apiKey; env.CODEX_API_KEY = args.apiKey;
} }
const child = spawn(this.executablePath, commandArgs, { const child = spawn(this.executablePath, commandArgs, {