diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 11138fdc8a9..b9265d10549 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1000,6 +1000,7 @@ dependencies = [ "codex-login", "codex-protocol", "codex-rmcp-client", + "codex-utils-absolute-path", "codex-utils-json-to-toml", "core_test_support", "mcp-types", diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 05362b204ed..310a7a595b6 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -209,13 +209,24 @@ v2_enum_from_core!( ); #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] -#[serde(rename_all = "camelCase")] +#[serde(tag = "type", rename_all = "camelCase")] +#[ts(tag = "type")] #[ts(export_to = "v2/")] -pub enum ConfigLayerName { - Mdm, - System, +pub enum ConfigLayerSource { + /// Managed preferences layer delivered by MDM (macOS only). + #[serde(rename_all = "camelCase")] + #[ts(rename_all = "camelCase")] + Mdm { domain: String, key: String }, + /// Managed config layer from a file (usually `managed_config.toml`). + #[serde(rename_all = "camelCase")] + #[ts(rename_all = "camelCase")] + System { file: AbsolutePathBuf }, + /// Session-layer overrides supplied via `-c`/`--config`. SessionFlags, - User, + /// User config layer from a file (usually `config.toml`). + #[serde(rename_all = "camelCase")] + #[ts(rename_all = "camelCase")] + User { file: AbsolutePathBuf }, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)] @@ -288,8 +299,7 @@ pub struct Config { #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ConfigLayerMetadata { - pub name: ConfigLayerName, - pub source: String, + pub name: ConfigLayerSource, pub version: String, } @@ -297,8 +307,7 @@ pub struct ConfigLayerMetadata { #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ConfigLayer { - pub name: ConfigLayerName, - pub source: String, + pub name: ConfigLayerSource, pub version: String, pub config: JsonValue, } diff --git a/codex-rs/app-server/Cargo.toml b/codex-rs/app-server/Cargo.toml index 3695ffa1f10..ca7fac30ced 100644 --- a/codex-rs/app-server/Cargo.toml +++ b/codex-rs/app-server/Cargo.toml @@ -27,6 +27,7 @@ codex-protocol = { workspace = true } codex-app-server-protocol = { workspace = true } codex-feedback = { workspace = true } codex-rmcp-client = { workspace = true } +codex-utils-absolute-path = { workspace = true } codex-utils-json-to-toml = { workspace = true } chrono = { workspace = true } serde = { workspace = true, features = ["derive"] } diff --git a/codex-rs/app-server/tests/suite/user_agent.rs b/codex-rs/app-server/tests/suite/user_agent.rs index 52ba6e56aa5..5ed6cafdeeb 100644 --- a/codex-rs/app-server/tests/suite/user_agent.rs +++ b/codex-rs/app-server/tests/suite/user_agent.rs @@ -25,12 +25,13 @@ async fn get_user_agent_returns_current_codex_user_agent() -> Result<()> { .await??; let os_info = os_info::get(); + let originator = codex_core::default_client::originator().value.as_str(); + let os_type = os_info.os_type(); + let os_version = os_info.version(); + let architecture = os_info.architecture().unwrap_or("unknown"); + let terminal_ua = codex_core::terminal::user_agent(); let user_agent = format!( - "codex_cli_rs/0.0.0 ({} {}; {}) {} (codex-app-server-tests; 0.1.0)", - os_info.os_type(), - os_info.version(), - os_info.architecture().unwrap_or("unknown"), - codex_core::terminal::user_agent() + "{originator}/0.0.0 ({os_type} {os_version}; {architecture}) {terminal_ua} (codex-app-server-tests; 0.1.0)" ); let received: GetUserAgentResponse = to_response(response)?; diff --git a/codex-rs/app-server/tests/suite/v2/config_rpc.rs b/codex-rs/app-server/tests/suite/v2/config_rpc.rs index 199d2f88e64..d26f36fa3f4 100644 --- a/codex-rs/app-server/tests/suite/v2/config_rpc.rs +++ b/codex-rs/app-server/tests/suite/v2/config_rpc.rs @@ -6,7 +6,7 @@ use app_test_support::to_response; use codex_app_server_protocol::AskForApproval; use codex_app_server_protocol::ConfigBatchWriteParams; use codex_app_server_protocol::ConfigEdit; -use codex_app_server_protocol::ConfigLayerName; +use codex_app_server_protocol::ConfigLayerSource; use codex_app_server_protocol::ConfigReadParams; use codex_app_server_protocol::ConfigReadResponse; use codex_app_server_protocol::ConfigValueWriteParams; @@ -18,6 +18,7 @@ use codex_app_server_protocol::RequestId; use codex_app_server_protocol::SandboxMode; use codex_app_server_protocol::ToolsV2; use codex_app_server_protocol::WriteStatus; +use codex_utils_absolute_path::AbsolutePathBuf; use pretty_assertions::assert_eq; use serde_json::json; use tempfile::TempDir; @@ -42,6 +43,8 @@ model = "gpt-user" sandbox_mode = "workspace-write" "#, )?; + let codex_home_path = codex_home.path().canonicalize()?; + let user_file = AbsolutePathBuf::try_from(codex_home_path.join("config.toml"))?; let mut mcp = McpProcess::new(codex_home.path()).await?; timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; @@ -65,12 +68,14 @@ sandbox_mode = "workspace-write" assert_eq!(config.model.as_deref(), Some("gpt-user")); assert_eq!( origins.get("model").expect("origin").name, - ConfigLayerName::User + ConfigLayerSource::User { + file: user_file.clone(), + } ); let layers = layers.expect("layers present"); assert_eq!(layers.len(), 2); - assert_eq!(layers[0].name, ConfigLayerName::SessionFlags); - assert_eq!(layers[1].name, ConfigLayerName::User); + assert_eq!(layers[0].name, ConfigLayerSource::SessionFlags); + assert_eq!(layers[1].name, ConfigLayerSource::User { file: user_file }); Ok(()) } @@ -88,6 +93,8 @@ web_search = true view_image = false "#, )?; + let codex_home_path = codex_home.path().canonicalize()?; + let user_file = AbsolutePathBuf::try_from(codex_home_path.join("config.toml"))?; let mut mcp = McpProcess::new(codex_home.path()).await?; timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; @@ -118,17 +125,21 @@ view_image = false ); assert_eq!( origins.get("tools.web_search").expect("origin").name, - ConfigLayerName::User + ConfigLayerSource::User { + file: user_file.clone(), + } ); assert_eq!( origins.get("tools.view_image").expect("origin").name, - ConfigLayerName::User + ConfigLayerSource::User { + file: user_file.clone(), + } ); let layers = layers.expect("layers present"); assert_eq!(layers.len(), 2); - assert_eq!(layers[0].name, ConfigLayerName::SessionFlags); - assert_eq!(layers[1].name, ConfigLayerName::User); + assert_eq!(layers[0].name, ConfigLayerSource::SessionFlags); + assert_eq!(layers[1].name, ConfigLayerSource::User { file: user_file }); Ok(()) } @@ -153,8 +164,11 @@ network_access = true serde_json::json!(user_dir) ), )?; + let codex_home_path = codex_home.path().canonicalize()?; + let user_file = AbsolutePathBuf::try_from(codex_home_path.join("config.toml"))?; let managed_path = codex_home.path().join("managed_config.toml"); + let managed_file = AbsolutePathBuf::try_from(managed_path.clone())?; std::fs::write( &managed_path, format!( @@ -197,19 +211,25 @@ writable_roots = [{}] assert_eq!(config.model.as_deref(), Some("gpt-system")); assert_eq!( origins.get("model").expect("origin").name, - ConfigLayerName::System + ConfigLayerSource::System { + file: managed_file.clone(), + } ); assert_eq!(config.approval_policy, Some(AskForApproval::Never)); assert_eq!( origins.get("approval_policy").expect("origin").name, - ConfigLayerName::System + ConfigLayerSource::System { + file: managed_file.clone(), + } ); assert_eq!(config.sandbox_mode, Some(SandboxMode::WorkspaceWrite)); assert_eq!( origins.get("sandbox_mode").expect("origin").name, - ConfigLayerName::User + ConfigLayerSource::User { + file: user_file.clone(), + } ); let sandbox = config @@ -222,7 +242,9 @@ writable_roots = [{}] .get("sandbox_workspace_write.writable_roots.0") .expect("origin") .name, - ConfigLayerName::System + ConfigLayerSource::System { + file: managed_file.clone(), + } ); assert!(sandbox.network_access); @@ -231,14 +253,19 @@ writable_roots = [{}] .get("sandbox_workspace_write.network_access") .expect("origin") .name, - ConfigLayerName::User + ConfigLayerSource::User { + file: user_file.clone(), + } ); let layers = layers.expect("layers present"); assert_eq!(layers.len(), 3); - assert_eq!(layers[0].name, ConfigLayerName::System); - assert_eq!(layers[1].name, ConfigLayerName::SessionFlags); - assert_eq!(layers[2].name, ConfigLayerName::User); + assert_eq!( + layers[0].name, + ConfigLayerSource::System { file: managed_file } + ); + assert_eq!(layers[1].name, ConfigLayerSource::SessionFlags); + assert_eq!(layers[2].name, ConfigLayerSource::User { file: user_file }); Ok(()) } diff --git a/codex-rs/core/src/config/service.rs b/codex-rs/core/src/config/service.rs index 393d637f314..59bd1285829 100644 --- a/codex-rs/core/src/config/service.rs +++ b/codex-rs/core/src/config/service.rs @@ -10,7 +10,7 @@ use crate::config_loader::merge_toml_values; use codex_app_server_protocol::Config as ApiConfig; use codex_app_server_protocol::ConfigBatchWriteParams; use codex_app_server_protocol::ConfigLayerMetadata; -use codex_app_server_protocol::ConfigLayerName; +use codex_app_server_protocol::ConfigLayerSource; use codex_app_server_protocol::ConfigReadParams; use codex_app_server_protocol::ConfigReadResponse; use codex_app_server_protocol::ConfigValueWriteParams; @@ -497,12 +497,12 @@ fn value_at_path<'a>(root: &'a TomlValue, segments: &[String]) -> Option<&'a Tom Some(current) } -fn override_message(layer: &ConfigLayerName) -> String { +fn override_message(layer: &ConfigLayerSource) -> String { match layer { - ConfigLayerName::Mdm => "Overridden by managed policy (mdm)".to_string(), - ConfigLayerName::System => "Overridden by managed config (system)".to_string(), - ConfigLayerName::SessionFlags => "Overridden by session flags".to_string(), - ConfigLayerName::User => "Overridden by user config".to_string(), + ConfigLayerSource::Mdm { .. } => "Overridden by managed policy (mdm)".to_string(), + ConfigLayerSource::System { .. } => "Overridden by managed config (system)".to_string(), + ConfigLayerSource::SessionFlags => "Overridden by session flags".to_string(), + ConfigLayerSource::User { .. } => "Overridden by user config".to_string(), } } @@ -576,6 +576,7 @@ mod tests { use super::*; use anyhow::Result; use codex_app_server_protocol::AskForApproval; + use codex_utils_absolute_path::AbsolutePathBuf; use pretty_assertions::assert_eq; use tempfile::tempdir; @@ -677,16 +678,19 @@ remote_compaction = true #[tokio::test] async fn read_includes_origins_and_layers() { let tmp = tempdir().expect("tempdir"); - std::fs::write(tmp.path().join(CONFIG_TOML_FILE), "model = \"user\"").unwrap(); + let user_path = tmp.path().join(CONFIG_TOML_FILE); + std::fs::write(&user_path, "model = \"user\"").unwrap(); + let user_file = AbsolutePathBuf::try_from(user_path.clone()).expect("user file"); let managed_path = tmp.path().join("managed_config.toml"); std::fs::write(&managed_path, "approval_policy = \"never\"").unwrap(); + let managed_file = AbsolutePathBuf::try_from(managed_path.clone()).expect("managed file"); let service = ConfigService::with_overrides( tmp.path().to_path_buf(), vec![], LoaderOverrides { - managed_config_path: Some(managed_path), + managed_config_path: Some(managed_path.clone()), #[cfg(target_os = "macos")] managed_preferences_base64: None, }, @@ -707,12 +711,20 @@ remote_compaction = true .get("approval_policy") .expect("origin") .name, - ConfigLayerName::System + ConfigLayerSource::System { + file: managed_file.clone(), + } ); let layers = response.layers.expect("layers present"); - assert_eq!(layers.first().unwrap().name, ConfigLayerName::System); - assert_eq!(layers.get(1).unwrap().name, ConfigLayerName::SessionFlags); - assert_eq!(layers.last().unwrap().name, ConfigLayerName::User); + assert_eq!( + layers.first().unwrap().name, + ConfigLayerSource::System { file: managed_file } + ); + assert_eq!(layers.get(1).unwrap().name, ConfigLayerSource::SessionFlags); + assert_eq!( + layers.last().unwrap().name, + ConfigLayerSource::User { file: user_file } + ); } #[tokio::test] @@ -726,12 +738,13 @@ remote_compaction = true let managed_path = tmp.path().join("managed_config.toml"); std::fs::write(&managed_path, "approval_policy = \"never\"").unwrap(); + let managed_file = AbsolutePathBuf::try_from(managed_path.clone()).expect("managed file"); let service = ConfigService::with_overrides( tmp.path().to_path_buf(), vec![], LoaderOverrides { - managed_config_path: Some(managed_path), + managed_config_path: Some(managed_path.clone()), #[cfg(target_os = "macos")] managed_preferences_base64: None, }, @@ -764,7 +777,7 @@ remote_compaction = true .get("approval_policy") .expect("origin") .name, - ConfigLayerName::System + ConfigLayerSource::System { file: managed_file } ); assert_eq!(result.status, WriteStatus::Ok); assert!(result.overridden_metadata.is_none()); @@ -773,7 +786,8 @@ remote_compaction = true #[tokio::test] async fn version_conflict_rejected() { let tmp = tempdir().expect("tempdir"); - std::fs::write(tmp.path().join(CONFIG_TOML_FILE), "model = \"user\"").unwrap(); + let user_path = tmp.path().join(CONFIG_TOML_FILE); + std::fs::write(&user_path, "model = \"user\"").unwrap(); let service = ConfigService::new(tmp.path().to_path_buf(), vec![]); let error = service @@ -830,7 +844,7 @@ remote_compaction = true tmp.path().to_path_buf(), vec![], LoaderOverrides { - managed_config_path: Some(managed_path), + managed_config_path: Some(managed_path.clone()), #[cfg(target_os = "macos")] managed_preferences_base64: None, }, @@ -860,10 +874,13 @@ remote_compaction = true #[tokio::test] async fn read_reports_managed_overrides_user_and_session_flags() { let tmp = tempdir().expect("tempdir"); - std::fs::write(tmp.path().join(CONFIG_TOML_FILE), "model = \"user\"").unwrap(); + let user_path = tmp.path().join(CONFIG_TOML_FILE); + std::fs::write(&user_path, "model = \"user\"").unwrap(); + let user_file = AbsolutePathBuf::try_from(user_path.clone()).expect("user file"); let managed_path = tmp.path().join("managed_config.toml"); std::fs::write(&managed_path, "model = \"system\"").unwrap(); + let managed_file = AbsolutePathBuf::try_from(managed_path.clone()).expect("managed file"); let cli_overrides = vec![( "model".to_string(), @@ -874,7 +891,7 @@ remote_compaction = true tmp.path().to_path_buf(), cli_overrides, LoaderOverrides { - managed_config_path: Some(managed_path), + managed_config_path: Some(managed_path.clone()), #[cfg(target_os = "macos")] managed_preferences_base64: None, }, @@ -890,12 +907,20 @@ remote_compaction = true assert_eq!(response.config.model.as_deref(), Some("system")); assert_eq!( response.origins.get("model").expect("origin").name, - ConfigLayerName::System + ConfigLayerSource::System { + file: managed_file.clone(), + } ); let layers = response.layers.expect("layers"); - assert_eq!(layers.first().unwrap().name, ConfigLayerName::System); - assert_eq!(layers.get(1).unwrap().name, ConfigLayerName::SessionFlags); - assert_eq!(layers.get(2).unwrap().name, ConfigLayerName::User); + assert_eq!( + layers.first().unwrap().name, + ConfigLayerSource::System { file: managed_file } + ); + assert_eq!(layers.get(1).unwrap().name, ConfigLayerSource::SessionFlags); + assert_eq!( + layers.get(2).unwrap().name, + ConfigLayerSource::User { file: user_file } + ); } #[tokio::test] @@ -905,12 +930,13 @@ remote_compaction = true let managed_path = tmp.path().join("managed_config.toml"); std::fs::write(&managed_path, "approval_policy = \"never\"").unwrap(); + let managed_file = AbsolutePathBuf::try_from(managed_path.clone()).expect("managed file"); let service = ConfigService::with_overrides( tmp.path().to_path_buf(), vec![], LoaderOverrides { - managed_config_path: Some(managed_path), + managed_config_path: Some(managed_path.clone()), #[cfg(target_os = "macos")] managed_preferences_base64: None, }, @@ -929,7 +955,10 @@ remote_compaction = true assert_eq!(result.status, WriteStatus::OkOverridden); let overridden = result.overridden_metadata.expect("overridden metadata"); - assert_eq!(overridden.overriding_layer.name, ConfigLayerName::System); + assert_eq!( + overridden.overriding_layer.name, + ConfigLayerSource::System { file: managed_file } + ); assert_eq!(overridden.effective_value, serde_json::json!("never")); } diff --git a/codex-rs/core/src/config_loader/README.md b/codex-rs/core/src/config_loader/README.md index da427c45446..9df656951ca 100644 --- a/codex-rs/core/src/config_loader/README.md +++ b/codex-rs/core/src/config_loader/README.md @@ -16,7 +16,7 @@ Exported from `codex_core::config_loader`: - `origins() -> HashMap` - `layers_high_to_low() -> Vec` - `with_user_config(user_config) -> ConfigLayerStack` -- `ConfigLayerEntry` (one layer’s `{name, source, config, version}`) +- `ConfigLayerEntry` (one layer’s `{name, config, version}`; `name` carries source metadata) - `LoaderOverrides` (test/override hooks for managed config sources) - `merge_toml_values(base, overlay)` (public helper used elsewhere) @@ -61,4 +61,3 @@ Implementation is split by concern: - `merge.rs`: recursive TOML merge. - `fingerprint.rs`: stable per-layer hashing and per-key origins traversal. - `macos.rs`: managed preferences integration (macOS only). - diff --git a/codex-rs/core/src/config_loader/mod.rs b/codex-rs/core/src/config_loader/mod.rs index c7c522008d9..2c3634ea04b 100644 --- a/codex-rs/core/src/config_loader/mod.rs +++ b/codex-rs/core/src/config_loader/mod.rs @@ -9,10 +9,10 @@ mod state; mod tests; use crate::config::CONFIG_TOML_FILE; -use codex_app_server_protocol::ConfigLayerName; +use codex_app_server_protocol::ConfigLayerSource; +use codex_utils_absolute_path::AbsolutePathBuf; use std::io; use std::path::Path; -use std::path::PathBuf; use toml::Value as TomlValue; pub use merge::merge_toml_values; @@ -20,8 +20,8 @@ pub use state::ConfigLayerEntry; pub use state::ConfigLayerStack; pub use state::LoaderOverrides; -const SESSION_FLAGS_SOURCE: &str = "--config"; -const MDM_SOURCE: &str = "com.openai.codex/config_toml_base64"; +const MDM_PREFERENCES_DOMAIN: &str = "com.openai.codex"; +const MDM_PREFERENCES_KEY: &str = "config_toml_base64"; /// Configuration layering pipeline (top overrides bottom): /// @@ -51,24 +51,32 @@ pub async fn load_config_layers_state( .unwrap_or_else(|| layer_io::managed_config_default_path(codex_home)); let layers = layer_io::load_config_layers_internal(codex_home, overrides).await?; - let cli_overrides = overrides::build_cli_overrides_layer(cli_overrides); + let cli_overrides_layer = overrides::build_cli_overrides_layer(cli_overrides); + let user_file = AbsolutePathBuf::from_absolute_path(codex_home.join(CONFIG_TOML_FILE))?; + + let system = match layers.managed_config { + Some(cfg) => { + let system_file = AbsolutePathBuf::from_absolute_path(managed_config_path.clone())?; + Some(ConfigLayerEntry::new( + ConfigLayerSource::System { file: system_file }, + cfg, + )) + } + None => None, + }; Ok(ConfigLayerStack { - user: ConfigLayerEntry::new( - ConfigLayerName::User, - codex_home.join(CONFIG_TOML_FILE), - layers.base, - ), - session_flags: ConfigLayerEntry::new( - ConfigLayerName::SessionFlags, - PathBuf::from(SESSION_FLAGS_SOURCE), - cli_overrides, - ), - system: layers.managed_config.map(|cfg| { - ConfigLayerEntry::new(ConfigLayerName::System, managed_config_path.clone(), cfg) + user: ConfigLayerEntry::new(ConfigLayerSource::User { file: user_file }, layers.base), + session_flags: ConfigLayerEntry::new(ConfigLayerSource::SessionFlags, cli_overrides_layer), + system, + mdm: layers.managed_preferences.map(|cfg| { + ConfigLayerEntry::new( + ConfigLayerSource::Mdm { + domain: MDM_PREFERENCES_DOMAIN.to_string(), + key: MDM_PREFERENCES_KEY.to_string(), + }, + cfg, + ) }), - mdm: layers - .managed_preferences - .map(|cfg| ConfigLayerEntry::new(ConfigLayerName::Mdm, PathBuf::from(MDM_SOURCE), cfg)), }) } diff --git a/codex-rs/core/src/config_loader/state.rs b/codex-rs/core/src/config_loader/state.rs index e23cc243ce7..4ae235b796a 100644 --- a/codex-rs/core/src/config_loader/state.rs +++ b/codex-rs/core/src/config_loader/state.rs @@ -3,7 +3,7 @@ use super::fingerprint::version_for_toml; use super::merge::merge_toml_values; use codex_app_server_protocol::ConfigLayer; use codex_app_server_protocol::ConfigLayerMetadata; -use codex_app_server_protocol::ConfigLayerName; +use codex_app_server_protocol::ConfigLayerSource; use serde_json::Value as JsonValue; use std::collections::HashMap; use std::path::PathBuf; @@ -18,18 +18,16 @@ pub struct LoaderOverrides { #[derive(Debug, Clone)] pub struct ConfigLayerEntry { - pub name: ConfigLayerName, - pub source: PathBuf, + pub name: ConfigLayerSource, pub config: TomlValue, pub version: String, } impl ConfigLayerEntry { - pub fn new(name: ConfigLayerName, source: PathBuf, config: TomlValue) -> Self { + pub fn new(name: ConfigLayerSource, config: TomlValue) -> Self { let version = version_for_toml(&config); Self { name, - source, config, version, } @@ -38,7 +36,6 @@ impl ConfigLayerEntry { pub fn metadata(&self) -> ConfigLayerMetadata { ConfigLayerMetadata { name: self.name.clone(), - source: self.source.display().to_string(), version: self.version.clone(), } } @@ -46,7 +43,6 @@ impl ConfigLayerEntry { pub fn as_layer(&self) -> ConfigLayer { ConfigLayer { name: self.name.clone(), - source: self.source.display().to_string(), version: self.version.clone(), config: serde_json::to_value(&self.config).unwrap_or(JsonValue::Null), } @@ -64,11 +60,7 @@ pub struct ConfigLayerStack { impl ConfigLayerStack { pub fn with_user_config(&self, user_config: TomlValue) -> Self { Self { - user: ConfigLayerEntry::new( - self.user.name.clone(), - self.user.source.clone(), - user_config, - ), + user: ConfigLayerEntry::new(self.user.name.clone(), user_config), session_flags: self.session_flags.clone(), system: self.system.clone(), mdm: self.mdm.clone(), diff --git a/codex-rs/core/src/default_client.rs b/codex-rs/core/src/default_client.rs index 2ea512b935e..3cd882489d0 100644 --- a/codex-rs/core/src/default_client.rs +++ b/codex-rs/core/src/default_client.rs @@ -163,7 +163,9 @@ mod tests { #[test] fn test_get_codex_user_agent() { let user_agent = get_codex_user_agent(); - assert!(user_agent.starts_with("codex_cli_rs/")); + let originator = originator().value.as_str(); + let prefix = format!("{originator}/"); + assert!(user_agent.starts_with(&prefix)); } #[tokio::test] @@ -204,7 +206,7 @@ mod tests { let originator_header = headers .get("originator") .expect("originator header missing"); - assert_eq!(originator_header.to_str().unwrap(), "codex_cli_rs"); + assert_eq!(originator_header.to_str().unwrap(), originator().value); // User-Agent matches the computed Codex UA for that originator let expected_ua = get_codex_user_agent(); @@ -241,9 +243,10 @@ mod tests { fn test_macos() { use regex_lite::Regex; let user_agent = get_codex_user_agent(); - let re = Regex::new( - r"^codex_cli_rs/\d+\.\d+\.\d+ \(Mac OS \d+\.\d+\.\d+; (x86_64|arm64)\) (\S+)$", - ) + let originator = regex_lite::escape(originator().value.as_str()); + let re = Regex::new(&format!( + r"^{originator}/\d+\.\d+\.\d+ \(Mac OS \d+\.\d+\.\d+; (x86_64|arm64)\) (\S+)$" + )) .unwrap(); assert!(re.is_match(&user_agent)); } diff --git a/codex-rs/exec/tests/suite/originator.rs b/codex-rs/exec/tests/suite/originator.rs index cf0954447af..9b57c2c92f4 100644 --- a/codex-rs/exec/tests/suite/originator.rs +++ b/codex-rs/exec/tests/suite/originator.rs @@ -1,6 +1,7 @@ #![cfg(not(target_os = "windows"))] #![allow(clippy::expect_used, clippy::unwrap_used)] +use codex_core::default_client::CODEX_INTERNAL_ORIGINATOR_OVERRIDE_ENV_VAR; use core_test_support::responses; use core_test_support::test_codex_exec::test_codex_exec; use wiremock::matchers::header; @@ -20,6 +21,7 @@ async fn send_codex_exec_originator() -> anyhow::Result<()> { responses::mount_sse_once_match(&server, header("Originator", "codex_exec"), body).await; test.cmd_with_server(&server) + .env_remove(CODEX_INTERNAL_ORIGINATOR_OVERRIDE_ENV_VAR) .arg("--skip-git-repo-check") .arg("tell me something") .assert()