Replace config.responses_originator_header_internal_override with CODEX_INTERNAL_ORIGINATOR_OVERRIDE_ENV_VAR (#3388)

The previous config approach had a few issues:
1. It is part of the config but not designed to be used externally
2. It had to be wired through many places (look at the +/- on this PR
3. It wasn't guaranteed to be set consistently everywhere because we
don't have a super well defined way that configs stack. For example, the
extension would configure during newConversation but anything that
happened outside of that (like login) wouldn't get it.

This env var approach is cleaner and also creates one less thing we have
to deal with when coming up with a better holistic story around configs.

One downside is that I removed the unit test testing for the override
because I don't want to deal with setting the global env or spawning
child processes and figuring out how to introspect their originator
header. The new code is sufficiently simple and I tested it e2e that I
feel as if this is still worth it.
This commit is contained in:
Gabriel Peal
2025-09-09 14:23:23 -07:00
committed by GitHub
parent f656e192bf
commit 5eab4c7ab4
20 changed files with 92 additions and 206 deletions

View File

@@ -31,7 +31,7 @@ pub async fn run_apply_command(
ConfigOverrides::default(), ConfigOverrides::default(),
)?; )?;
init_chatgpt_token_from_auth(&config.codex_home, &config.responses_originator_header).await?; init_chatgpt_token_from_auth(&config.codex_home).await?;
let task_response = get_task(&config, apply_cli.task_id).await?; let task_response = get_task(&config, apply_cli.task_id).await?;
apply_diff_from_task(task_response, cwd).await apply_diff_from_task(task_response, cwd).await

View File

@@ -13,10 +13,10 @@ pub(crate) async fn chatgpt_get_request<T: DeserializeOwned>(
path: String, path: String,
) -> anyhow::Result<T> { ) -> anyhow::Result<T> {
let chatgpt_base_url = &config.chatgpt_base_url; let chatgpt_base_url = &config.chatgpt_base_url;
init_chatgpt_token_from_auth(&config.codex_home, &config.responses_originator_header).await?; init_chatgpt_token_from_auth(&config.codex_home).await?;
// Make direct HTTP request to ChatGPT backend API with the token // Make direct HTTP request to ChatGPT backend API with the token
let client = create_client(&config.responses_originator_header); let client = create_client();
let url = format!("{chatgpt_base_url}{path}"); let url = format!("{chatgpt_base_url}{path}");
let token = let token =

View File

@@ -19,11 +19,8 @@ pub fn set_chatgpt_token_data(value: TokenData) {
} }
/// Initialize the ChatGPT token from auth.json file /// Initialize the ChatGPT token from auth.json file
pub async fn init_chatgpt_token_from_auth( pub async fn init_chatgpt_token_from_auth(codex_home: &Path) -> std::io::Result<()> {
codex_home: &Path, let auth = CodexAuth::from_codex_home(codex_home, AuthMode::ChatGPT)?;
originator: &str,
) -> std::io::Result<()> {
let auth = CodexAuth::from_codex_home(codex_home, AuthMode::ChatGPT, originator)?;
if let Some(auth) = auth { if let Some(auth) = auth {
let token_data = auth.get_token_data().await?; let token_data = auth.get_token_data().await?;
set_chatgpt_token_data(token_data); set_chatgpt_token_data(token_data);

View File

@@ -12,8 +12,8 @@ use codex_protocol::mcp_protocol::AuthMode;
use std::env; use std::env;
use std::path::PathBuf; use std::path::PathBuf;
pub async fn login_with_chatgpt(codex_home: PathBuf, originator: String) -> std::io::Result<()> { pub async fn login_with_chatgpt(codex_home: PathBuf) -> std::io::Result<()> {
let opts = ServerOptions::new(codex_home, CLIENT_ID.to_string(), originator); let opts = ServerOptions::new(codex_home, CLIENT_ID.to_string());
let server = run_login_server(opts)?; let server = run_login_server(opts)?;
eprintln!( eprintln!(
@@ -27,12 +27,7 @@ pub async fn login_with_chatgpt(codex_home: PathBuf, originator: String) -> std:
pub async fn run_login_with_chatgpt(cli_config_overrides: CliConfigOverrides) -> ! { pub async fn run_login_with_chatgpt(cli_config_overrides: CliConfigOverrides) -> ! {
let config = load_config_or_exit(cli_config_overrides); let config = load_config_or_exit(cli_config_overrides);
match login_with_chatgpt( match login_with_chatgpt(config.codex_home).await {
config.codex_home,
config.responses_originator_header.clone(),
)
.await
{
Ok(_) => { Ok(_) => {
eprintln!("Successfully logged in"); eprintln!("Successfully logged in");
std::process::exit(0); std::process::exit(0);
@@ -65,11 +60,7 @@ pub async fn run_login_with_api_key(
pub async fn run_login_status(cli_config_overrides: CliConfigOverrides) -> ! { pub async fn run_login_status(cli_config_overrides: CliConfigOverrides) -> ! {
let config = load_config_or_exit(cli_config_overrides); let config = load_config_or_exit(cli_config_overrides);
match CodexAuth::from_codex_home( match CodexAuth::from_codex_home(&config.codex_home, config.preferred_auth_method) {
&config.codex_home,
config.preferred_auth_method,
&config.responses_originator_header,
) {
Ok(Some(auth)) => match auth.mode { Ok(Some(auth)) => match auth.mode {
AuthMode::ApiKey => match auth.get_token().await { AuthMode::ApiKey => match auth.get_token().await {
Ok(api_key) => { Ok(api_key) => {

View File

@@ -40,7 +40,6 @@ pub async fn run_main(opts: ProtoCli) -> anyhow::Result<()> {
let conversation_manager = ConversationManager::new(AuthManager::shared( let conversation_manager = ConversationManager::new(AuthManager::shared(
config.codex_home.clone(), config.codex_home.clone(),
config.preferred_auth_method, config.preferred_auth_method,
config.responses_originator_header.clone(),
)); ));
let NewConversation { let NewConversation {
conversation_id: _, conversation_id: _,

View File

@@ -75,9 +75,8 @@ impl CodexAuth {
pub fn from_codex_home( pub fn from_codex_home(
codex_home: &Path, codex_home: &Path,
preferred_auth_method: AuthMode, preferred_auth_method: AuthMode,
originator: &str,
) -> std::io::Result<Option<CodexAuth>> { ) -> std::io::Result<Option<CodexAuth>> {
load_auth(codex_home, true, preferred_auth_method, originator) load_auth(codex_home, true, preferred_auth_method)
} }
pub async fn get_token_data(&self) -> Result<TokenData, std::io::Error> { pub async fn get_token_data(&self) -> Result<TokenData, std::io::Error> {
@@ -173,7 +172,7 @@ impl CodexAuth {
mode: AuthMode::ChatGPT, mode: AuthMode::ChatGPT,
auth_file: PathBuf::new(), auth_file: PathBuf::new(),
auth_dot_json, auth_dot_json,
client: crate::default_client::create_client("codex_cli_rs"), client: crate::default_client::create_client(),
} }
} }
@@ -188,10 +187,7 @@ impl CodexAuth {
} }
pub fn from_api_key(api_key: &str) -> Self { pub fn from_api_key(api_key: &str) -> Self {
Self::from_api_key_with_client( Self::from_api_key_with_client(api_key, crate::default_client::create_client())
api_key,
crate::default_client::create_client(crate::default_client::DEFAULT_ORIGINATOR),
)
} }
} }
@@ -232,13 +228,12 @@ fn load_auth(
codex_home: &Path, codex_home: &Path,
include_env_var: bool, include_env_var: bool,
preferred_auth_method: AuthMode, preferred_auth_method: AuthMode,
originator: &str,
) -> std::io::Result<Option<CodexAuth>> { ) -> std::io::Result<Option<CodexAuth>> {
// First, check to see if there is a valid auth.json file. If not, we fall // First, check to see if there is a valid auth.json file. If not, we fall
// back to AuthMode::ApiKey using the OPENAI_API_KEY environment variable // back to AuthMode::ApiKey using the OPENAI_API_KEY environment variable
// (if it is set). // (if it is set).
let auth_file = get_auth_file(codex_home); let auth_file = get_auth_file(codex_home);
let client = crate::default_client::create_client(originator); 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) {
Ok(auth) => auth, Ok(auth) => auth,
// If auth.json does not exist, try to read the OPENAI_API_KEY from the // If auth.json does not exist, try to read the OPENAI_API_KEY from the
@@ -473,7 +468,7 @@ mod tests {
auth_dot_json, auth_dot_json,
auth_file: _, auth_file: _,
.. ..
} = super::load_auth(codex_home.path(), false, AuthMode::ChatGPT, "codex_cli_rs") } = super::load_auth(codex_home.path(), false, AuthMode::ChatGPT)
.unwrap() .unwrap()
.unwrap(); .unwrap();
assert_eq!(None, api_key); assert_eq!(None, api_key);
@@ -525,7 +520,7 @@ mod tests {
auth_dot_json, auth_dot_json,
auth_file: _, auth_file: _,
.. ..
} = super::load_auth(codex_home.path(), false, AuthMode::ChatGPT, "codex_cli_rs") } = super::load_auth(codex_home.path(), false, AuthMode::ChatGPT)
.unwrap() .unwrap()
.unwrap(); .unwrap();
assert_eq!(None, api_key); assert_eq!(None, api_key);
@@ -576,7 +571,7 @@ mod tests {
auth_dot_json, auth_dot_json,
auth_file: _, auth_file: _,
.. ..
} = super::load_auth(codex_home.path(), false, AuthMode::ChatGPT, "codex_cli_rs") } = super::load_auth(codex_home.path(), false, AuthMode::ChatGPT)
.unwrap() .unwrap()
.unwrap(); .unwrap();
assert_eq!(Some("sk-test-key".to_string()), api_key); assert_eq!(Some("sk-test-key".to_string()), api_key);
@@ -596,7 +591,7 @@ mod tests {
) )
.unwrap(); .unwrap();
let auth = super::load_auth(dir.path(), false, AuthMode::ChatGPT, "codex_cli_rs") let auth = super::load_auth(dir.path(), false, AuthMode::ChatGPT)
.unwrap() .unwrap()
.unwrap(); .unwrap();
assert_eq!(auth.mode, AuthMode::ApiKey); assert_eq!(auth.mode, AuthMode::ApiKey);
@@ -680,7 +675,6 @@ mod tests {
#[derive(Debug)] #[derive(Debug)]
pub struct AuthManager { pub struct AuthManager {
codex_home: PathBuf, codex_home: PathBuf,
originator: String,
inner: RwLock<CachedAuth>, inner: RwLock<CachedAuth>,
} }
@@ -689,13 +683,12 @@ 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, preferred_auth_mode: AuthMode, originator: String) -> Self { pub fn new(codex_home: PathBuf, preferred_auth_mode: AuthMode) -> Self {
let auth = CodexAuth::from_codex_home(&codex_home, preferred_auth_mode, &originator) let auth = CodexAuth::from_codex_home(&codex_home, preferred_auth_mode)
.ok() .ok()
.flatten(); .flatten();
Self { Self {
codex_home, codex_home,
originator,
inner: RwLock::new(CachedAuth { inner: RwLock::new(CachedAuth {
preferred_auth_mode, preferred_auth_mode,
auth, auth,
@@ -712,7 +705,6 @@ impl AuthManager {
}; };
Arc::new(Self { Arc::new(Self {
codex_home: PathBuf::new(), codex_home: PathBuf::new(),
originator: "codex_cli_rs".to_string(),
inner: RwLock::new(cached), inner: RwLock::new(cached),
}) })
} }
@@ -734,7 +726,7 @@ impl AuthManager {
/// whether the auth value changed. /// whether the auth value changed.
pub fn reload(&self) -> bool { pub fn reload(&self) -> bool {
let preferred = self.preferred_auth_method(); let preferred = self.preferred_auth_method();
let new_auth = CodexAuth::from_codex_home(&self.codex_home, preferred, &self.originator) let new_auth = CodexAuth::from_codex_home(&self.codex_home, preferred)
.ok() .ok()
.flatten(); .flatten();
if let Ok(mut guard) = self.inner.write() { if let Ok(mut guard) = self.inner.write() {
@@ -755,12 +747,8 @@ impl AuthManager {
} }
/// Convenience constructor returning an `Arc` wrapper. /// Convenience constructor returning an `Arc` wrapper.
pub fn shared( pub fn shared(codex_home: PathBuf, preferred_auth_mode: AuthMode) -> Arc<Self> {
codex_home: PathBuf, Arc::new(Self::new(codex_home, preferred_auth_mode))
preferred_auth_mode: AuthMode,
originator: String,
) -> Arc<Self> {
Arc::new(Self::new(codex_home, preferred_auth_mode, originator))
} }
/// 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

@@ -84,7 +84,7 @@ impl ModelClient {
summary: ReasoningSummaryConfig, summary: ReasoningSummaryConfig,
conversation_id: ConversationId, conversation_id: ConversationId,
) -> Self { ) -> Self {
let client = create_client(&config.responses_originator_header); let client = create_client();
Self { Self {
config, config,

View File

@@ -40,8 +40,6 @@ pub(crate) const PROJECT_DOC_MAX_BYTES: usize = 32 * 1024; // 32 KiB
const CONFIG_TOML_FILE: &str = "config.toml"; const CONFIG_TOML_FILE: &str = "config.toml";
const DEFAULT_RESPONSES_ORIGINATOR_HEADER: &str = "codex_cli_rs";
/// Application configuration loaded from disk and merged with overrides. /// Application configuration loaded from disk and merged with overrides.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Config { pub struct Config {
@@ -169,9 +167,6 @@ pub struct Config {
pub tools_web_search_request: bool, pub tools_web_search_request: bool,
/// The value for the `originator` header included with Responses API requests.
pub responses_originator_header: String,
/// If set to `true`, the API key will be signed with the `originator` header. /// If set to `true`, the API key will be signed with the `originator` header.
pub preferred_auth_method: AuthMode, pub preferred_auth_method: AuthMode,
@@ -478,9 +473,6 @@ pub struct ConfigToml {
pub experimental_use_exec_command_tool: Option<bool>, pub experimental_use_exec_command_tool: Option<bool>,
/// The value for the `originator` header included with Responses API requests.
pub responses_originator_header_internal_override: Option<String>,
pub projects: Option<HashMap<String, ProjectConfig>>, pub projects: Option<HashMap<String, ProjectConfig>>,
/// If set to `true`, the API key will be signed with the `originator` header. /// If set to `true`, the API key will be signed with the `originator` header.
@@ -773,10 +765,6 @@ impl Config {
Self::get_base_instructions(experimental_instructions_path, &resolved_cwd)?; Self::get_base_instructions(experimental_instructions_path, &resolved_cwd)?;
let base_instructions = base_instructions.or(file_base_instructions); let base_instructions = base_instructions.or(file_base_instructions);
let responses_originator_header: String = cfg
.responses_originator_header_internal_override
.unwrap_or(DEFAULT_RESPONSES_ORIGINATOR_HEADER.to_owned());
let config = Self { let config = Self {
model, model,
model_family, model_family,
@@ -826,7 +814,6 @@ impl Config {
include_plan_tool: include_plan_tool.unwrap_or(false), include_plan_tool: include_plan_tool.unwrap_or(false),
include_apply_patch_tool: include_apply_patch_tool.unwrap_or(false), include_apply_patch_tool: include_apply_patch_tool.unwrap_or(false),
tools_web_search_request, tools_web_search_request,
responses_originator_header,
preferred_auth_method: cfg.preferred_auth_method.unwrap_or(AuthMode::ChatGPT), preferred_auth_method: cfg.preferred_auth_method.unwrap_or(AuthMode::ChatGPT),
use_experimental_streamable_shell_tool: cfg use_experimental_streamable_shell_tool: cfg
.experimental_use_exec_command_tool .experimental_use_exec_command_tool
@@ -1203,7 +1190,6 @@ model_verbosity = "high"
include_plan_tool: false, include_plan_tool: false,
include_apply_patch_tool: false, include_apply_patch_tool: false,
tools_web_search_request: false, tools_web_search_request: false,
responses_originator_header: "codex_cli_rs".to_string(),
preferred_auth_method: AuthMode::ChatGPT, preferred_auth_method: AuthMode::ChatGPT,
use_experimental_streamable_shell_tool: false, use_experimental_streamable_shell_tool: false,
include_view_image_tool: true, include_view_image_tool: true,
@@ -1260,7 +1246,6 @@ model_verbosity = "high"
include_plan_tool: false, include_plan_tool: false,
include_apply_patch_tool: false, include_apply_patch_tool: false,
tools_web_search_request: false, tools_web_search_request: false,
responses_originator_header: "codex_cli_rs".to_string(),
preferred_auth_method: AuthMode::ChatGPT, preferred_auth_method: AuthMode::ChatGPT,
use_experimental_streamable_shell_tool: false, use_experimental_streamable_shell_tool: false,
include_view_image_tool: true, include_view_image_tool: true,
@@ -1332,7 +1317,6 @@ model_verbosity = "high"
include_plan_tool: false, include_plan_tool: false,
include_apply_patch_tool: false, include_apply_patch_tool: false,
tools_web_search_request: false, tools_web_search_request: false,
responses_originator_header: "codex_cli_rs".to_string(),
preferred_auth_method: AuthMode::ChatGPT, preferred_auth_method: AuthMode::ChatGPT,
use_experimental_streamable_shell_tool: false, use_experimental_streamable_shell_tool: false,
include_view_image_tool: true, include_view_image_tool: true,
@@ -1390,7 +1374,6 @@ model_verbosity = "high"
include_plan_tool: false, include_plan_tool: false,
include_apply_patch_tool: false, include_apply_patch_tool: false,
tools_web_search_request: false, tools_web_search_request: false,
responses_originator_header: "codex_cli_rs".to_string(),
preferred_auth_method: AuthMode::ChatGPT, preferred_auth_method: AuthMode::ChatGPT,
use_experimental_streamable_shell_tool: false, use_experimental_streamable_shell_tool: false,
include_view_image_tool: true, include_view_image_tool: true,

View File

@@ -1,11 +1,40 @@
pub const DEFAULT_ORIGINATOR: &str = "codex_cli_rs"; use reqwest::header::HeaderValue;
use std::sync::LazyLock;
pub fn get_codex_user_agent(originator: Option<&str>) -> String { pub const CODEX_INTERNAL_ORIGINATOR_OVERRIDE_ENV_VAR: &str = "CODEX_INTERNAL_ORIGINATOR_OVERRIDE";
#[derive(Debug, Clone)]
pub struct Originator {
pub value: String,
pub header_value: HeaderValue,
}
pub static ORIGINATOR: LazyLock<Originator> = LazyLock::new(|| {
let default = "codex_cli_rs";
let value = std::env::var(CODEX_INTERNAL_ORIGINATOR_OVERRIDE_ENV_VAR)
.unwrap_or_else(|_| default.to_string());
match HeaderValue::from_str(&value) {
Ok(header_value) => Originator {
value,
header_value,
},
Err(e) => {
tracing::error!("Unable to turn originator override {value} into header value: {e}");
Originator {
value: default.to_string(),
header_value: HeaderValue::from_static(default),
}
}
}
});
pub fn get_codex_user_agent() -> String {
let build_version = env!("CARGO_PKG_VERSION"); let build_version = env!("CARGO_PKG_VERSION");
let os_info = os_info::get(); let os_info = os_info::get();
format!( format!(
"{}/{build_version} ({} {}; {}) {}", "{}/{build_version} ({} {}; {}) {}",
originator.unwrap_or(DEFAULT_ORIGINATOR), ORIGINATOR.value.as_str(),
os_info.os_type(), os_info.os_type(),
os_info.version(), os_info.version(),
os_info.architecture().unwrap_or("unknown"), os_info.architecture().unwrap_or("unknown"),
@@ -14,25 +43,19 @@ pub fn get_codex_user_agent(originator: Option<&str>) -> String {
} }
/// Create a reqwest client with default `originator` and `User-Agent` headers set. /// Create a reqwest client with default `originator` and `User-Agent` headers set.
pub fn create_client(originator: &str) -> reqwest::Client { pub fn create_client() -> reqwest::Client {
use reqwest::header::HeaderMap; use reqwest::header::HeaderMap;
use reqwest::header::HeaderValue;
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
let originator_value = HeaderValue::from_str(originator) headers.insert("originator", ORIGINATOR.header_value.clone());
.unwrap_or_else(|_| HeaderValue::from_static(DEFAULT_ORIGINATOR)); let ua = get_codex_user_agent();
headers.insert("originator", originator_value);
let ua = get_codex_user_agent(Some(originator));
match reqwest::Client::builder() reqwest::Client::builder()
// Set UA via dedicated helper to avoid header validation pitfalls // Set UA via dedicated helper to avoid header validation pitfalls
.user_agent(ua) .user_agent(ua)
.default_headers(headers) .default_headers(headers)
.build() .build()
{ .unwrap_or_else(|_| reqwest::Client::new())
Ok(client) => client,
Err(_) => reqwest::Client::new(),
}
} }
#[cfg(test)] #[cfg(test)]
@@ -41,7 +64,7 @@ mod tests {
#[test] #[test]
fn test_get_codex_user_agent() { fn test_get_codex_user_agent() {
let user_agent = get_codex_user_agent(None); let user_agent = get_codex_user_agent();
assert!(user_agent.starts_with("codex_cli_rs/")); assert!(user_agent.starts_with("codex_cli_rs/"));
} }
@@ -53,8 +76,7 @@ mod tests {
use wiremock::matchers::method; use wiremock::matchers::method;
use wiremock::matchers::path; use wiremock::matchers::path;
let originator = "test_originator"; let client = create_client();
let client = create_client(originator);
// Spin up a local mock server and capture a request. // Spin up a local mock server and capture a request.
let server = MockServer::start().await; let server = MockServer::start().await;
@@ -82,10 +104,10 @@ mod tests {
let originator_header = headers let originator_header = headers
.get("originator") .get("originator")
.expect("originator header missing"); .expect("originator header missing");
assert_eq!(originator_header.to_str().unwrap(), originator); assert_eq!(originator_header.to_str().unwrap(), "codex_cli_rs");
// User-Agent matches the computed Codex UA for that originator // User-Agent matches the computed Codex UA for that originator
let expected_ua = get_codex_user_agent(Some(originator)); let expected_ua = get_codex_user_agent();
let ua_header = headers let ua_header = headers
.get("user-agent") .get("user-agent")
.expect("user-agent header missing"); .expect("user-agent header missing");
@@ -96,7 +118,7 @@ mod tests {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
fn test_macos() { fn test_macos() {
use regex_lite::Regex; use regex_lite::Regex;
let user_agent = get_codex_user_agent(None); let user_agent = get_codex_user_agent();
let re = Regex::new( let re = Regex::new(
r"^codex_cli_rs/\d+\.\d+\.\d+ \(Mac OS \d+\.\d+\.\d+; (x86_64|arm64)\) (\S+)$", r"^codex_cli_rs/\d+\.\d+\.\d+ \(Mac OS \d+\.\d+\.\d+; (x86_64|arm64)\) (\S+)$",
) )

View File

@@ -371,56 +371,6 @@ async fn includes_base_instructions_override_in_request() {
); );
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn originator_config_override_is_used() {
// Mock server
let server = MockServer::start().await;
let first = ResponseTemplate::new(200)
.insert_header("content-type", "text/event-stream")
.set_body_raw(sse_completed("resp1"), "text/event-stream");
Mock::given(method("POST"))
.and(path("/v1/responses"))
.respond_with(first)
.expect(1)
.mount(&server)
.await;
let model_provider = ModelProviderInfo {
base_url: Some(format!("{}/v1", server.uri())),
..built_in_model_providers()["openai"].clone()
};
let codex_home = TempDir::new().unwrap();
let mut config = load_default_config_for_test(&codex_home);
config.model_provider = model_provider;
config.responses_originator_header = "my_override".to_owned();
let conversation_manager =
ConversationManager::with_auth(CodexAuth::from_api_key("Test API Key"));
let codex = conversation_manager
.new_conversation(config)
.await
.expect("create new conversation")
.conversation;
codex
.submit(Op::UserInput {
items: vec![InputItem::Text {
text: "hello".into(),
}],
})
.await
.unwrap();
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TaskComplete(_))).await;
let request = &server.received_requests().await.unwrap()[0];
let request_originator = request.headers.get("originator").unwrap();
assert_eq!(request_originator.to_str().unwrap(), "my_override");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn chatgpt_auth_sends_correct_request() { async fn chatgpt_auth_sends_correct_request() {
if std::env::var(CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR).is_ok() { if std::env::var(CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR).is_ok() {
@@ -546,15 +496,12 @@ async fn prefers_chatgpt_token_when_config_prefers_chatgpt() {
config.model_provider = model_provider; config.model_provider = model_provider;
config.preferred_auth_method = AuthMode::ChatGPT; config.preferred_auth_method = AuthMode::ChatGPT;
let auth_manager = match CodexAuth::from_codex_home( let auth_manager =
codex_home.path(), match CodexAuth::from_codex_home(codex_home.path(), config.preferred_auth_method) {
config.preferred_auth_method, Ok(Some(auth)) => codex_core::AuthManager::from_auth_for_testing(auth),
&config.responses_originator_header, Ok(None) => panic!("No CodexAuth found in codex_home"),
) { Err(e) => panic!("Failed to load CodexAuth: {e}"),
Ok(Some(auth)) => codex_core::AuthManager::from_auth_for_testing(auth), };
Ok(None) => panic!("No CodexAuth found in codex_home"),
Err(e) => panic!("Failed to load CodexAuth: {e}"),
};
let conversation_manager = ConversationManager::new(auth_manager); let conversation_manager = ConversationManager::new(auth_manager);
let NewConversation { let NewConversation {
conversation: codex, conversation: codex,
@@ -622,15 +569,12 @@ async fn prefers_apikey_when_config_prefers_apikey_even_with_chatgpt_tokens() {
config.model_provider = model_provider; config.model_provider = model_provider;
config.preferred_auth_method = AuthMode::ApiKey; config.preferred_auth_method = AuthMode::ApiKey;
let auth_manager = match CodexAuth::from_codex_home( let auth_manager =
codex_home.path(), match CodexAuth::from_codex_home(codex_home.path(), config.preferred_auth_method) {
config.preferred_auth_method, Ok(Some(auth)) => codex_core::AuthManager::from_auth_for_testing(auth),
&config.responses_originator_header, Ok(None) => panic!("No CodexAuth found in codex_home"),
) { Err(e) => panic!("Failed to load CodexAuth: {e}"),
Ok(Some(auth)) => codex_core::AuthManager::from_auth_for_testing(auth), };
Ok(None) => panic!("No CodexAuth found in codex_home"),
Err(e) => panic!("Failed to load CodexAuth: {e}"),
};
let conversation_manager = ConversationManager::new(auth_manager); let conversation_manager = ConversationManager::new(auth_manager);
let NewConversation { let NewConversation {
conversation: codex, conversation: codex,

View File

@@ -190,7 +190,6 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option<PathBuf>) -> any
let conversation_manager = ConversationManager::new(AuthManager::shared( let conversation_manager = ConversationManager::new(AuthManager::shared(
config.codex_home.clone(), config.codex_home.clone(),
config.preferred_auth_method, config.preferred_auth_method,
config.responses_originator_header.clone(),
)); ));
let NewConversation { let NewConversation {
conversation_id: _, conversation_id: _,

View File

@@ -16,6 +16,7 @@ use base64::Engine;
use chrono::Utc; use chrono::Utc;
use codex_core::auth::AuthDotJson; use codex_core::auth::AuthDotJson;
use codex_core::auth::get_auth_file; use codex_core::auth::get_auth_file;
use codex_core::default_client::ORIGINATOR;
use codex_core::token_data::TokenData; use codex_core::token_data::TokenData;
use codex_core::token_data::parse_id_token; use codex_core::token_data::parse_id_token;
use rand::RngCore; use rand::RngCore;
@@ -35,11 +36,10 @@ pub struct ServerOptions {
pub port: u16, pub port: u16,
pub open_browser: bool, pub open_browser: bool,
pub force_state: Option<String>, pub force_state: Option<String>,
pub originator: String,
} }
impl ServerOptions { impl ServerOptions {
pub fn new(codex_home: PathBuf, client_id: String, originator: String) -> Self { pub fn new(codex_home: PathBuf, client_id: String) -> Self {
Self { Self {
codex_home, codex_home,
client_id: client_id.to_string(), client_id: client_id.to_string(),
@@ -47,7 +47,6 @@ impl ServerOptions {
port: DEFAULT_PORT, port: DEFAULT_PORT,
open_browser: true, open_browser: true,
force_state: None, force_state: None,
originator,
} }
} }
} }
@@ -103,14 +102,7 @@ pub fn run_login_server(opts: ServerOptions) -> io::Result<LoginServer> {
let server = Arc::new(server); let server = Arc::new(server);
let redirect_uri = format!("http://localhost:{actual_port}/auth/callback"); let redirect_uri = format!("http://localhost:{actual_port}/auth/callback");
let auth_url = build_authorize_url( let auth_url = build_authorize_url(&opts.issuer, &opts.client_id, &redirect_uri, &pkce, &state);
&opts.issuer,
&opts.client_id,
&redirect_uri,
&pkce,
&state,
&opts.originator,
);
if opts.open_browser { if opts.open_browser {
let _ = webbrowser::open(&auth_url); let _ = webbrowser::open(&auth_url);
@@ -311,7 +303,6 @@ fn build_authorize_url(
redirect_uri: &str, redirect_uri: &str,
pkce: &PkceCodes, pkce: &PkceCodes,
state: &str, state: &str,
originator: &str,
) -> String { ) -> String {
let query = vec![ let query = vec![
("response_type", "code"), ("response_type", "code"),
@@ -323,7 +314,7 @@ fn build_authorize_url(
("id_token_add_organizations", "true"), ("id_token_add_organizations", "true"),
("codex_cli_simplified_flow", "true"), ("codex_cli_simplified_flow", "true"),
("state", state), ("state", state),
("originator", originator), ("originator", ORIGINATOR.value.as_str()),
]; ];
let qs = query let qs = query
.into_iter() .into_iter()

View File

@@ -102,7 +102,6 @@ async fn end_to_end_login_flow_persists_auth_json() {
port: 0, port: 0,
open_browser: false, open_browser: false,
force_state: Some(state), force_state: Some(state),
originator: "test_originator".to_string(),
}; };
let server = run_login_server(opts).unwrap(); let server = run_login_server(opts).unwrap();
let login_port = server.actual_port; let login_port = server.actual_port;
@@ -161,7 +160,6 @@ async fn creates_missing_codex_home_dir() {
port: 0, port: 0,
open_browser: false, open_browser: false,
force_state: Some(state), force_state: Some(state),
originator: "test_originator".to_string(),
}; };
let server = run_login_server(opts).unwrap(); let server = run_login_server(opts).unwrap();
let login_port = server.actual_port; let login_port = server.actual_port;
@@ -202,7 +200,6 @@ async fn cancels_previous_login_server_when_port_is_in_use() {
port: 0, port: 0,
open_browser: false, open_browser: false,
force_state: Some("cancel_state".to_string()), force_state: Some("cancel_state".to_string()),
originator: "test_originator".to_string(),
}; };
let first_server = run_login_server(first_opts).unwrap(); let first_server = run_login_server(first_opts).unwrap();
@@ -221,7 +218,6 @@ async fn cancels_previous_login_server_when_port_is_in_use() {
port: login_port, port: login_port,
open_browser: false, open_browser: false,
force_state: Some("cancel_state_2".to_string()), force_state: Some("cancel_state_2".to_string()),
originator: "test_originator".to_string(),
}; };
let second_server = run_login_server(second_opts).unwrap(); let second_server = run_login_server(second_opts).unwrap();

View File

@@ -198,11 +198,7 @@ impl CodexMessageProcessor {
let opts = LoginServerOptions { let opts = LoginServerOptions {
open_browser: false, open_browser: false,
..LoginServerOptions::new( ..LoginServerOptions::new(config.codex_home.clone(), CLIENT_ID.to_string())
config.codex_home.clone(),
CLIENT_ID.to_string(),
config.responses_originator_header.clone(),
)
}; };
enum LoginChatGptReply { enum LoginChatGptReply {
@@ -403,7 +399,7 @@ impl CodexMessageProcessor {
} }
async fn get_user_agent(&self, request_id: RequestId) { async fn get_user_agent(&self, request_id: RequestId) {
let user_agent = get_codex_user_agent(Some(&self.config.responses_originator_header)); let user_agent = get_codex_user_agent();
let response = GetUserAgentResponse { user_agent }; let response = GetUserAgentResponse { user_agent };
self.outgoing.send_response(request_id, response).await; self.outgoing.send_response(request_id, response).await;
} }

View File

@@ -54,11 +54,8 @@ 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( let auth_manager =
config.codex_home.clone(), AuthManager::shared(config.codex_home.clone(), config.preferred_auth_method);
config.preferred_auth_method,
config.responses_originator_header.clone(),
);
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

@@ -1,4 +1,3 @@
use codex_core::default_client::DEFAULT_ORIGINATOR;
use codex_core::default_client::get_codex_user_agent; use codex_core::default_client::get_codex_user_agent;
use codex_protocol::mcp_protocol::GetUserAgentResponse; use codex_protocol::mcp_protocol::GetUserAgentResponse;
use mcp_test_support::McpProcess; use mcp_test_support::McpProcess;
@@ -38,7 +37,7 @@ async fn get_user_agent_returns_current_codex_user_agent() {
let received: GetUserAgentResponse = let received: GetUserAgentResponse =
to_response(response).expect("deserialize getUserAgent response"); to_response(response).expect("deserialize getUserAgent response");
let expected = GetUserAgentResponse { let expected = GetUserAgentResponse {
user_agent: get_codex_user_agent(Some(DEFAULT_ORIGINATOR)), user_agent: get_codex_user_agent(),
}; };
assert_eq!(received, expected); assert_eq!(received, expected);

View File

@@ -312,11 +312,7 @@ async fn run_ratatui_app(
.. ..
} = cli; } = cli;
let auth_manager = AuthManager::shared( let auth_manager = AuthManager::shared(config.codex_home.clone(), config.preferred_auth_method);
config.codex_home.clone(),
config.preferred_auth_method,
config.responses_originator_header.clone(),
);
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);
@@ -400,11 +396,7 @@ fn get_login_status(config: &Config) -> LoginStatus {
// Reading the OpenAI API key is an async operation because it may need // Reading the OpenAI API key is an async operation because it may need
// to refresh the token. Block on it. // to refresh the token. Block on it.
let codex_home = config.codex_home.clone(); let codex_home = config.codex_home.clone();
match CodexAuth::from_codex_home( match CodexAuth::from_codex_home(&codex_home, config.preferred_auth_method) {
&codex_home,
config.preferred_auth_method,
&config.responses_originator_header,
) {
Ok(Some(auth)) => LoginStatus::AuthMode(auth.mode), Ok(Some(auth)) => LoginStatus::AuthMode(auth.mode),
Ok(None) => LoginStatus::NotAuthenticated, Ok(None) => LoginStatus::NotAuthenticated,
Err(err) => { Err(err) => {

View File

@@ -2,7 +2,6 @@
use codex_core::AuthManager; use codex_core::AuthManager;
use codex_core::auth::CLIENT_ID; use codex_core::auth::CLIENT_ID;
use codex_core::config::Config;
use codex_login::ServerOptions; use codex_login::ServerOptions;
use codex_login::ShutdownHandle; use codex_login::ShutdownHandle;
use codex_login::run_login_server; use codex_login::run_login_server;
@@ -114,7 +113,6 @@ pub(crate) struct AuthModeWidget {
pub login_status: LoginStatus, pub login_status: LoginStatus,
pub preferred_auth_method: AuthMode, pub preferred_auth_method: AuthMode,
pub auth_manager: Arc<AuthManager>, pub auth_manager: Arc<AuthManager>,
pub config: Config,
} }
impl AuthModeWidget { impl AuthModeWidget {
@@ -316,11 +314,7 @@ impl AuthModeWidget {
} }
self.error = None; self.error = None;
let opts = ServerOptions::new( let opts = ServerOptions::new(self.codex_home.clone(), CLIENT_ID.to_string());
self.codex_home.clone(),
CLIENT_ID.to_string(),
self.config.responses_originator_header.clone(),
);
match run_login_server(opts) { match run_login_server(opts) {
Ok(child) => { Ok(child) => {
let sign_in_state = self.sign_in_state.clone(); let sign_in_state = self.sign_in_state.clone();

View File

@@ -85,7 +85,6 @@ impl OnboardingScreen {
login_status, login_status,
auth_manager, auth_manager,
preferred_auth_method, preferred_auth_method,
config,
})) }))
} }
let is_git_repo = get_git_repo_root(&cwd).is_some(); let is_git_repo = get_git_repo_root(&cwd).is_some();

View File

@@ -24,9 +24,8 @@ pub fn get_upgrade_version(config: &Config) -> Option<String> {
// Refresh the cached latest version in the background so TUI startup // Refresh the cached latest version in the background so TUI startup
// isnt blocked by a network call. The UI reads the previously cached // isnt blocked by a network call. The UI reads the previously cached
// value (if any) for this run; the next run shows the banner if needed. // value (if any) for this run; the next run shows the banner if needed.
let originator = config.responses_originator_header.clone();
tokio::spawn(async move { tokio::spawn(async move {
check_for_update(&version_file, &originator) check_for_update(&version_file)
.await .await
.inspect_err(|e| tracing::error!("Failed to update version: {e}")) .inspect_err(|e| tracing::error!("Failed to update version: {e}"))
}); });
@@ -65,10 +64,10 @@ fn read_version_info(version_file: &Path) -> anyhow::Result<VersionInfo> {
Ok(serde_json::from_str(&contents)?) Ok(serde_json::from_str(&contents)?)
} }
async fn check_for_update(version_file: &Path, originator: &str) -> anyhow::Result<()> { async fn check_for_update(version_file: &Path) -> anyhow::Result<()> {
let ReleaseInfo { let ReleaseInfo {
tag_name: latest_tag_name, tag_name: latest_tag_name,
} = create_client(originator) } = create_client()
.get(LATEST_RELEASE_URL) .get(LATEST_RELEASE_URL)
.send() .send()
.await? .await?