diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index dbb812c768..d0734ae85d 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -408,6 +408,7 @@ dependencies = [ "base64 0.22.1", "chrono", "codex-app-server-protocol", + "codex-client", "codex-core", "codex-protocol", "codex-utils-cargo-bin", @@ -1425,6 +1426,7 @@ dependencies = [ "codex-arg0", "codex-backend-client", "codex-chatgpt", + "codex-client", "codex-cloud-requirements", "codex-core", "codex-exec-server", @@ -1596,6 +1598,34 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "codex-auth" +version = "0.0.0" +dependencies = [ + "anyhow", + "async-trait", + "base64 0.22.1", + "chrono", + "codex-app-server-protocol", + "codex-client", + "codex-keyring-store", + "codex-otel", + "codex-protocol", + "keyring", + "once_cell", + "pretty_assertions", + "reqwest", + "schemars 0.8.22", + "serde", + "serde_json", + "serial_test", + "sha2", + "tempfile", + "thiserror 2.0.18", + "tokio", + "tracing", +] + [[package]] name = "codex-backend-client" version = "0.0.0" @@ -1626,6 +1656,7 @@ version = "0.0.0" dependencies = [ "anyhow", "clap", + "codex-client", "codex-connectors", "codex-core", "codex-git", @@ -1692,6 +1723,8 @@ version = "0.0.0" dependencies = [ "async-trait", "bytes", + "codex-config", + "codex-terminal", "codex-utils-cargo-bin", "codex-utils-rustls-provider", "eventsource-stream", @@ -1699,8 +1732,10 @@ dependencies = [ "http 1.4.0", "opentelemetry", "opentelemetry_sdk", + "os_info", "pretty_assertions", "rand 0.9.2", + "regex-lite", "reqwest", "rustls", "rustls-native-certs", @@ -1713,6 +1748,7 @@ dependencies = [ "tracing", "tracing-opentelemetry", "tracing-subscriber", + "wiremock", "zstd", ] @@ -1841,6 +1877,7 @@ dependencies = [ "codex-arg0", "codex-artifacts", "codex-async-utils", + "codex-auth", "codex-client", "codex-config", "codex-connectors", @@ -1895,7 +1932,6 @@ dependencies = [ "openssl-sys", "opentelemetry", "opentelemetry_sdk", - "os_info", "predicates", "pretty_assertions", "rand 0.9.2", @@ -1960,6 +1996,7 @@ dependencies = [ "codex-app-server-protocol", "codex-apply-patch", "codex-arg0", + "codex-client", "codex-cloud-requirements", "codex-core", "codex-feedback", @@ -2200,10 +2237,10 @@ version = "0.0.0" dependencies = [ "anyhow", "codex-arg0", + "codex-client", "codex-core", "codex-protocol", "codex-shell-command", - "codex-terminal", "codex-utils-cli", "codex-utils-json-to-toml", "core_test_support", @@ -5826,8 +5863,10 @@ name = "mcp_test_support" version = "0.0.0" dependencies = [ "anyhow", + "codex-client", "codex-core", "codex-mcp-server", + "codex-terminal", "codex-utils-cargo-bin", "core_test_support", "os_info", diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index 71c8facfe2..42e42045d1 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -10,6 +10,7 @@ members = [ "debug-client", "apply-patch", "arg0", + "auth", "feedback", "codex-backend-openapi-models", "cloud-requirements", @@ -97,6 +98,7 @@ codex-app-server-test-client = { path = "app-server-test-client" } codex-apply-patch = { path = "apply-patch" } codex-arg0 = { path = "arg0" } codex-async-utils = { path = "async-utils" } +codex-auth = { path = "auth" } codex-backend-client = { path = "backend-client" } codex-chatgpt = { path = "chatgpt" } codex-cli = { path = "cli" } diff --git a/codex-rs/app-server/Cargo.toml b/codex-rs/app-server/Cargo.toml index 9391050491..f070a8b1b9 100644 --- a/codex-rs/app-server/Cargo.toml +++ b/codex-rs/app-server/Cargo.toml @@ -30,6 +30,7 @@ axum = { workspace = true, default-features = false, features = [ "ws", ] } codex-arg0 = { workspace = true } +codex-client = { workspace = true } codex-cloud-requirements = { workspace = true } codex-core = { workspace = true } codex-exec-server = { workspace = true } diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index b6ece83ddf..f10806d5f5 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -176,6 +176,7 @@ use codex_app_server_protocol::build_turns_from_rollout_items; use codex_arg0::Arg0DispatchPaths; use codex_backend_client::Client as BackendClient; use codex_chatgpt::connectors; +use codex_client::set_default_client_residency_requirement; use codex_cloud_requirements::cloud_requirements_loader; use codex_core::AuthManager; use codex_core::CodexAuth; @@ -201,7 +202,6 @@ use codex_core::config::types::McpServerTransportConfig; use codex_core::config_loader::CloudRequirementsLoadError; use codex_core::config_loader::CloudRequirementsLoadErrorCode; use codex_core::config_loader::CloudRequirementsLoader; -use codex_core::default_client::set_default_client_residency_requirement; use codex_core::error::CodexErr; use codex_core::error::Result as CodexResult; use codex_core::exec::ExecExpiration; diff --git a/codex-rs/app-server/src/message_processor.rs b/codex-rs/app-server/src/message_processor.rs index 3804c4f9b4..4aec2cd166 100644 --- a/codex-rs/app-server/src/message_processor.rs +++ b/codex-rs/app-server/src/message_processor.rs @@ -47,6 +47,11 @@ use codex_app_server_protocol::ServerNotification; use codex_app_server_protocol::ServerRequestPayload; use codex_app_server_protocol::experimental_required_message; use codex_arg0::Arg0DispatchPaths; +use codex_client::SetOriginatorError; +use codex_client::USER_AGENT_SUFFIX; +use codex_client::get_codex_user_agent; +use codex_client::set_default_client_residency_requirement; +use codex_client::set_default_originator; use codex_core::AnalyticsEventsClient; use codex_core::AuthManager; use codex_core::ThreadManager; @@ -57,11 +62,6 @@ use codex_core::auth::ExternalAuthTokens; use codex_core::config::Config; use codex_core::config_loader::CloudRequirementsLoader; use codex_core::config_loader::LoaderOverrides; -use codex_core::default_client::SetOriginatorError; -use codex_core::default_client::USER_AGENT_SUFFIX; -use codex_core::default_client::get_codex_user_agent; -use codex_core::default_client::set_default_client_residency_requirement; -use codex_core::default_client::set_default_originator; use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig; use codex_feedback::CodexFeedback; use codex_protocol::ThreadId; diff --git a/codex-rs/app-server/tests/common/Cargo.toml b/codex-rs/app-server/tests/common/Cargo.toml index de58509f0d..9421596a6d 100644 --- a/codex-rs/app-server/tests/common/Cargo.toml +++ b/codex-rs/app-server/tests/common/Cargo.toml @@ -12,6 +12,7 @@ anyhow = { workspace = true } base64 = { workspace = true } chrono = { workspace = true } codex-app-server-protocol = { workspace = true } +codex-client = { workspace = true } codex-core = { workspace = true } codex-protocol = { workspace = true } codex-utils-cargo-bin = { workspace = true } diff --git a/codex-rs/app-server/tests/common/mcp_process.rs b/codex-rs/app-server/tests/common/mcp_process.rs index 1a132ccee1..02a4786309 100644 --- a/codex-rs/app-server/tests/common/mcp_process.rs +++ b/codex-rs/app-server/tests/common/mcp_process.rs @@ -77,7 +77,7 @@ use codex_app_server_protocol::TurnInterruptParams; use codex_app_server_protocol::TurnStartParams; use codex_app_server_protocol::TurnSteerParams; use codex_app_server_protocol::WindowsSandboxSetupStartParams; -use codex_core::default_client::CODEX_INTERNAL_ORIGINATOR_OVERRIDE_ENV_VAR; +use codex_client::CODEX_INTERNAL_ORIGINATOR_OVERRIDE_ENV_VAR; use tokio::process::Command; pub struct McpProcess { diff --git a/codex-rs/auth/BUILD.bazel b/codex-rs/auth/BUILD.bazel new file mode 100644 index 0000000000..25f224fdf3 --- /dev/null +++ b/codex-rs/auth/BUILD.bazel @@ -0,0 +1,7 @@ +load("//:defs.bzl", "codex_rust_crate") + +codex_rust_crate( + name = "auth", + crate_name = "codex_auth", + crate_srcs = glob(["*.rs"]), +) diff --git a/codex-rs/auth/Cargo.toml b/codex-rs/auth/Cargo.toml new file mode 100644 index 0000000000..3cdb9c2ca1 --- /dev/null +++ b/codex-rs/auth/Cargo.toml @@ -0,0 +1,39 @@ +[package] +edition.workspace = true +license.workspace = true +name = "codex-auth" +version.workspace = true + +[lib] +doctest = false +name = "codex_auth" +path = "lib.rs" + +[lints] +workspace = true + +[dependencies] +async-trait = { workspace = true } +base64 = { workspace = true } +chrono = { workspace = true, features = ["serde"] } +codex-app-server-protocol = { workspace = true } +codex-client = { workspace = true } +codex-keyring-store = { workspace = true } +codex-otel = { workspace = true } +codex-protocol = { workspace = true } +once_cell = { workspace = true } +reqwest = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +sha2 = { workspace = true } +thiserror = { workspace = true } +tracing = { workspace = true } + +[dev-dependencies] +anyhow = { workspace = true } +keyring = { workspace = true } +pretty_assertions = { workspace = true } +serial_test = { workspace = true } +tempfile = { workspace = true } +tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } diff --git a/codex-rs/core/src/auth.rs b/codex-rs/auth/auth.rs similarity index 97% rename from codex-rs/core/src/auth.rs rename to codex-rs/auth/auth.rs index 90f0dcfdaf..742c96d5c5 100644 --- a/codex-rs/core/src/auth.rs +++ b/codex-rs/auth/auth.rs @@ -1,5 +1,3 @@ -mod storage; - use async_trait::async_trait; use chrono::Utc; use reqwest::StatusCode; @@ -19,13 +17,12 @@ use codex_app_server_protocol::AuthMode as ApiAuthMode; use codex_otel::TelemetryAuthMode; use codex_protocol::config_types::ForcedLoginMethod; -pub use crate::auth::storage::AuthCredentialsStoreMode; -pub use crate::auth::storage::AuthDotJson; -use crate::auth::storage::AuthStorageBackend; -use crate::auth::storage::create_auth_storage; -use crate::config::Config; use crate::error::RefreshTokenFailedError; use crate::error::RefreshTokenFailedReason; +pub use crate::storage::AuthCredentialsStoreMode; +pub use crate::storage::AuthDotJson; +use crate::storage::AuthStorageBackend; +use crate::storage::create_auth_storage; use crate::token_data::KnownPlan as InternalKnownPlan; use crate::token_data::PlanType as InternalPlanType; use crate::token_data::TokenData; @@ -161,14 +158,14 @@ impl CodexAuth { codex_home: &Path, auth_dot_json: AuthDotJson, auth_credentials_store_mode: AuthCredentialsStoreMode, - client: CodexHttpClient, ) -> std::io::Result { let auth_mode = auth_dot_json.resolved_mode(); + let client = codex_client::create_client(); if auth_mode == ApiAuthMode::ApiKey { let Some(api_key) = auth_dot_json.openai_api_key.as_deref() else { return Err(std::io::Error::other("API key auth is missing a key.")); }; - return Ok(CodexAuth::from_api_key_with_client(api_key, client)); + return Ok(CodexAuth::from_api_key(api_key)); } let storage_mode = auth_dot_json.storage_mode(auth_credentials_store_mode); @@ -189,7 +186,6 @@ impl CodexAuth { } } - /// Loads the available auth information from auth storage. pub fn from_auth_storage( codex_home: &Path, auth_credentials_store_mode: AuthCredentialsStoreMode, @@ -335,7 +331,7 @@ impl CodexAuth { last_refresh: Some(Utc::now()), }; - let client = crate::default_client::create_client(); + let client = codex_client::create_client(); let state = ChatgptAuthState { auth_dot_json: Arc::new(Mutex::new(Some(auth_dot_json))), client, @@ -344,15 +340,11 @@ impl CodexAuth { Self::Chatgpt(ChatgptAuth { state, storage }) } - fn from_api_key_with_client(api_key: &str, _client: CodexHttpClient) -> Self { + pub fn from_api_key(api_key: &str) -> Self { Self::ApiKey(ApiKeyAuth { api_key: api_key.to_owned(), }) } - - pub fn from_api_key(api_key: &str) -> Self { - Self::from_api_key_with_client(api_key, crate::default_client::create_client()) - } } impl ChatgptAuth { @@ -458,11 +450,19 @@ pub fn load_auth_dot_json( storage.load() } -pub fn enforce_login_restrictions(config: &Config) -> std::io::Result<()> { +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AuthConfig { + pub codex_home: PathBuf, + pub auth_credentials_store_mode: AuthCredentialsStoreMode, + pub forced_login_method: Option, + pub forced_chatgpt_workspace_id: Option, +} + +pub fn enforce_login_restrictions(config: &AuthConfig) -> std::io::Result<()> { let Some(auth) = load_auth( &config.codex_home, /*enable_codex_api_key_env*/ true, - config.cli_auth_credentials_store_mode, + config.auth_credentials_store_mode, )? else { return Ok(()); @@ -486,7 +486,7 @@ pub fn enforce_login_restrictions(config: &Config) -> std::io::Result<()> { return logout_with_message( &config.codex_home, message, - config.cli_auth_credentials_store_mode, + config.auth_credentials_store_mode, ); } } @@ -504,7 +504,7 @@ pub fn enforce_login_restrictions(config: &Config) -> std::io::Result<()> { format!( "Failed to load ChatGPT credentials while enforcing workspace restrictions: {err}. Logging out." ), - config.cli_auth_credentials_store_mode, + config.auth_credentials_store_mode, ); } }; @@ -523,7 +523,7 @@ pub fn enforce_login_restrictions(config: &Config) -> std::io::Result<()> { return logout_with_message( &config.codex_home, message, - config.cli_auth_credentials_store_mode, + config.auth_credentials_store_mode, ); } } @@ -564,17 +564,12 @@ fn load_auth( auth_credentials_store_mode: AuthCredentialsStoreMode, ) -> std::io::Result> { let build_auth = |auth_dot_json: AuthDotJson, storage_mode| { - let client = crate::default_client::create_client(); - CodexAuth::from_auth_dot_json(codex_home, auth_dot_json, storage_mode, client) + CodexAuth::from_auth_dot_json(codex_home, auth_dot_json, storage_mode) }; // API key via env var takes precedence over any other auth method. 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, - ))); + return Ok(Some(CodexAuth::from_api_key(api_key.as_str()))); } // External ChatGPT auth tokens live in the in-memory (ephemeral) store. Always check this @@ -1077,7 +1072,7 @@ impl AuthManager { } /// Create an AuthManager with a specific CodexAuth, for testing only. - pub(crate) fn from_auth_for_testing(auth: CodexAuth) -> Arc { + pub fn from_auth_for_testing(auth: CodexAuth) -> Arc { let cached = CachedAuth { auth: Some(auth), external_refresher: None, @@ -1093,10 +1088,7 @@ impl AuthManager { } /// Create an AuthManager with a specific CodexAuth and codex home, for testing only. - pub(crate) fn from_auth_for_testing_with_home( - auth: CodexAuth, - codex_home: PathBuf, - ) -> Arc { + pub fn from_auth_for_testing_with_home(auth: CodexAuth, codex_home: PathBuf) -> Arc { let cached = CachedAuth { auth: Some(auth), external_refresher: None, diff --git a/codex-rs/core/src/auth_tests.rs b/codex-rs/auth/auth_tests.rs similarity index 96% rename from codex-rs/core/src/auth_tests.rs rename to codex-rs/auth/auth_tests.rs index 3bc5eb6c78..7e2272d2e3 100644 --- a/codex-rs/core/src/auth_tests.rs +++ b/codex-rs/auth/auth_tests.rs @@ -1,8 +1,6 @@ use super::*; -use crate::auth::storage::FileAuthStorage; -use crate::auth::storage::get_auth_file; -use crate::config::Config; -use crate::config::ConfigBuilder; +use crate::storage::FileAuthStorage; +use crate::storage::get_auth_file; use crate::token_data::IdTokenInfo; use crate::token_data::KnownPlan as InternalKnownPlan; use crate::token_data::PlanType as InternalPlanType; @@ -260,15 +258,13 @@ async fn build_config( codex_home: &Path, forced_login_method: Option, forced_chatgpt_workspace_id: Option, -) -> Config { - let mut config = ConfigBuilder::default() - .codex_home(codex_home.to_path_buf()) - .build() - .await - .expect("config should load"); - config.forced_login_method = forced_login_method; - config.forced_chatgpt_workspace_id = forced_chatgpt_workspace_id; - config +) -> AuthConfig { + AuthConfig { + codex_home: codex_home.to_path_buf(), + auth_credentials_store_mode: AuthCredentialsStoreMode::File, + forced_login_method, + forced_chatgpt_workspace_id, + } } /// Use sparingly. diff --git a/codex-rs/auth/error.rs b/codex-rs/auth/error.rs new file mode 100644 index 0000000000..fcbd4c7093 --- /dev/null +++ b/codex-rs/auth/error.rs @@ -0,0 +1,25 @@ +use thiserror::Error; + +#[derive(Debug, Clone, PartialEq, Eq, Error)] +#[error("{message}")] +pub struct RefreshTokenFailedError { + pub reason: RefreshTokenFailedReason, + pub message: String, +} + +impl RefreshTokenFailedError { + pub fn new(reason: RefreshTokenFailedReason, message: impl Into) -> Self { + Self { + reason, + message: message.into(), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RefreshTokenFailedReason { + Expired, + Exhausted, + Revoked, + Other, +} diff --git a/codex-rs/auth/lib.rs b/codex-rs/auth/lib.rs new file mode 100644 index 0000000000..87b43e4f4e --- /dev/null +++ b/codex-rs/auth/lib.rs @@ -0,0 +1,10 @@ +pub mod error; +mod storage; +pub mod token_data; +mod util; + +mod auth; + +pub use auth::*; +pub use error::RefreshTokenFailedError; +pub use error::RefreshTokenFailedReason; diff --git a/codex-rs/core/src/auth/storage.rs b/codex-rs/auth/storage.rs similarity index 100% rename from codex-rs/core/src/auth/storage.rs rename to codex-rs/auth/storage.rs diff --git a/codex-rs/core/src/auth/storage_tests.rs b/codex-rs/auth/storage_tests.rs similarity index 100% rename from codex-rs/core/src/auth/storage_tests.rs rename to codex-rs/auth/storage_tests.rs diff --git a/codex-rs/core/src/token_data.rs b/codex-rs/auth/token_data.rs similarity index 96% rename from codex-rs/core/src/token_data.rs rename to codex-rs/auth/token_data.rs index 5952d5940d..304bf765f4 100644 --- a/codex-rs/core/src/token_data.rs +++ b/codex-rs/auth/token_data.rs @@ -27,7 +27,7 @@ pub struct IdTokenInfo { /// The ChatGPT subscription plan type /// (e.g., "free", "plus", "pro", "business", "enterprise", "edu"). /// (Note: values may vary by backend.) - pub(crate) chatgpt_plan_type: Option, + pub chatgpt_plan_type: Option, /// ChatGPT user identifier associated with the token, if present. pub chatgpt_user_id: Option, /// Organization/workspace identifier associated with the token, if present. @@ -55,13 +55,13 @@ impl IdTokenInfo { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] -pub(crate) enum PlanType { +pub enum PlanType { Known(KnownPlan), Unknown(String), } impl PlanType { - pub(crate) fn from_raw_value(raw: &str) -> Self { + pub fn from_raw_value(raw: &str) -> Self { match raw.to_ascii_lowercase().as_str() { "free" => Self::Known(KnownPlan::Free), "go" => Self::Known(KnownPlan::Go), @@ -78,7 +78,7 @@ impl PlanType { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] -pub(crate) enum KnownPlan { +pub enum KnownPlan { Free, Go, Plus, diff --git a/codex-rs/core/src/token_data_tests.rs b/codex-rs/auth/token_data_tests.rs similarity index 100% rename from codex-rs/core/src/token_data_tests.rs rename to codex-rs/auth/token_data_tests.rs diff --git a/codex-rs/auth/util.rs b/codex-rs/auth/util.rs new file mode 100644 index 0000000000..18592f9fb7 --- /dev/null +++ b/codex-rs/auth/util.rs @@ -0,0 +1,16 @@ +use tracing::debug; + +pub(crate) fn try_parse_error_message(text: &str) -> String { + debug!("Parsing server error response: {}", text); + let json = serde_json::from_str::(text).unwrap_or_default(); + if let Some(error) = json.get("error") + && let Some(message) = error.get("message") + && let Some(message_str) = message.as_str() + { + return message_str.to_string(); + } + if text.is_empty() { + return "Unknown error".to_string(); + } + text.to_string() +} diff --git a/codex-rs/backend-client/src/client.rs b/codex-rs/backend-client/src/client.rs index e7fe145987..514ac330b5 100644 --- a/codex-rs/backend-client/src/client.rs +++ b/codex-rs/backend-client/src/client.rs @@ -5,8 +5,8 @@ use crate::types::RateLimitStatusPayload; use crate::types::TurnAttemptsSiblingTurnsResponse; use anyhow::Result; use codex_client::build_reqwest_client_with_custom_ca; +use codex_client::get_codex_user_agent; use codex_core::auth::CodexAuth; -use codex_core::default_client::get_codex_user_agent; use codex_protocol::account::PlanType as AccountPlanType; use codex_protocol::protocol::CreditsSnapshot; use codex_protocol::protocol::RateLimitSnapshot; diff --git a/codex-rs/chatgpt/Cargo.toml b/codex-rs/chatgpt/Cargo.toml index 17a6f97ad3..0eeace876d 100644 --- a/codex-rs/chatgpt/Cargo.toml +++ b/codex-rs/chatgpt/Cargo.toml @@ -10,6 +10,7 @@ workspace = true [dependencies] anyhow = { workspace = true } clap = { workspace = true, features = ["derive"] } +codex-client = { workspace = true } codex-connectors = { workspace = true } codex-core = { workspace = true } codex-utils-cli = { workspace = true } diff --git a/codex-rs/chatgpt/src/chatgpt_client.rs b/codex-rs/chatgpt/src/chatgpt_client.rs index 5d02bdac7f..eb2830a94a 100644 --- a/codex-rs/chatgpt/src/chatgpt_client.rs +++ b/codex-rs/chatgpt/src/chatgpt_client.rs @@ -1,5 +1,5 @@ +use codex_client::create_client; use codex_core::config::Config; -use codex_core::default_client::create_client; use crate::chatgpt_token::get_chatgpt_token_data; use crate::chatgpt_token::init_chatgpt_token_from_auth; diff --git a/codex-rs/cloud-tasks/src/lib.rs b/codex-rs/cloud-tasks/src/lib.rs index c6abc80c10..590931ec28 100644 --- a/codex-rs/cloud-tasks/src/lib.rs +++ b/codex-rs/cloud-tasks/src/lib.rs @@ -9,6 +9,7 @@ pub use cli::Cli; use anyhow::anyhow; use chrono::Utc; +use codex_client::get_codex_user_agent; use codex_cloud_tasks_client::TaskStatus; use owo_colors::OwoColorize; use owo_colors::Stream; @@ -54,7 +55,7 @@ async fn init_backend(user_agent_suffix: &str) -> anyhow::Result }); } - let ua = codex_core::default_client::get_codex_user_agent(); + let ua = get_codex_user_agent(); let mut http = codex_cloud_tasks_client::HttpClient::new(base_url.clone())?.with_user_agent(ua); let style = if base_url.contains("/backend-api") { "wham" @@ -800,7 +801,7 @@ pub async fn run_main(cli: Cli, _codex_linux_sandbox_exe: Option) -> an append_error_log(format!( "startup: wham_force_internal={} ua={}", force_internal, - codex_core::default_client::get_codex_user_agent() + get_codex_user_agent() )); // Non-blocking initial load so the in-box spinner can animate app.status = "Loading tasks…".to_string(); diff --git a/codex-rs/cloud-tasks/src/util.rs b/codex-rs/cloud-tasks/src/util.rs index 0dcf18432a..ddb678fceb 100644 --- a/codex-rs/cloud-tasks/src/util.rs +++ b/codex-rs/cloud-tasks/src/util.rs @@ -2,13 +2,15 @@ use base64::Engine as _; use chrono::DateTime; use chrono::Local; use chrono::Utc; +use codex_client::USER_AGENT_SUFFIX; +use codex_client::get_codex_user_agent; use reqwest::header::HeaderMap; use codex_core::config::Config; use codex_login::AuthManager; pub fn set_user_agent_suffix(suffix: &str) { - if let Ok(mut guard) = codex_core::default_client::USER_AGENT_SUFFIX.lock() { + if let Ok(mut guard) = USER_AGENT_SUFFIX.lock() { guard.replace(suffix.to_string()); } } @@ -78,7 +80,7 @@ pub async fn build_chatgpt_headers() -> HeaderMap { use reqwest::header::USER_AGENT; set_user_agent_suffix("codex_cloud_tasks_tui"); - let ua = codex_core::default_client::get_codex_user_agent(); + let ua = get_codex_user_agent(); let mut headers = HeaderMap::new(); headers.insert( USER_AGENT, diff --git a/codex-rs/codex-client/Cargo.toml b/codex-rs/codex-client/Cargo.toml index 2ef31ac826..5b0c09d033 100644 --- a/codex-rs/codex-client/Cargo.toml +++ b/codex-rs/codex-client/Cargo.toml @@ -7,10 +7,14 @@ version.workspace = true [dependencies] async-trait = { workspace = true } bytes = { workspace = true } +codex-config = { workspace = true } +codex-terminal = { workspace = true } +codex-utils-rustls-provider = { workspace = true } eventsource-stream = { workspace = true } futures = { workspace = true } http = { workspace = true } opentelemetry = { workspace = true } +os_info = { workspace = true } rand = { workspace = true } reqwest = { workspace = true, features = ["json", "stream"] } rustls = { workspace = true } @@ -19,10 +23,9 @@ rustls-pki-types = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } thiserror = { workspace = true } -tokio = { workspace = true, features = ["macros", "rt", "time", "sync"] } +tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread", "sync", "time"] } tracing = { workspace = true } tracing-opentelemetry = { workspace = true } -codex-utils-rustls-provider = { workspace = true } zstd = { workspace = true } [lints] @@ -32,5 +35,7 @@ workspace = true codex-utils-cargo-bin = { workspace = true } opentelemetry_sdk = { workspace = true } pretty_assertions = { workspace = true } +regex-lite = { workspace = true } tempfile = { workspace = true } tracing-subscriber = { workspace = true } +wiremock = { workspace = true } diff --git a/codex-rs/core/src/default_client.rs b/codex-rs/codex-client/src/create_client.rs similarity index 95% rename from codex-rs/core/src/default_client.rs rename to codex-rs/codex-client/src/create_client.rs index 6b88ae9a9f..756b20120a 100644 --- a/codex-rs/core/src/default_client.rs +++ b/codex-rs/codex-client/src/create_client.rs @@ -1,9 +1,7 @@ -use crate::config_loader::ResidencyRequirement; -use crate::spawn::CODEX_SANDBOX_ENV_VAR; -use codex_client::BuildCustomCaTransportError; -use codex_client::CodexHttpClient; -pub use codex_client::CodexRequestBuilder; -use codex_client::build_reqwest_client_with_custom_ca; +use crate::custom_ca::BuildCustomCaTransportError; +use crate::custom_ca::build_reqwest_client_with_custom_ca; +use crate::default_client::CodexHttpClient; +use codex_config::ResidencyRequirement; use codex_terminal::user_agent; use reqwest::header::HeaderMap; use reqwest::header::HeaderValue; @@ -152,7 +150,7 @@ pub fn get_codex_user_agent() -> String { /// Invalid characters are replaced with an underscore. /// /// If the user agent fails to parse, it falls back to fallback and then to ORIGINATOR. -fn sanitize_user_agent(candidate: String, fallback: &str) -> String { +pub fn sanitize_user_agent(candidate: String, fallback: &str) -> String { if HeaderValue::from_str(candidate.as_str()).is_ok() { return candidate; } @@ -232,9 +230,9 @@ pub fn default_headers() -> HeaderMap { } fn is_sandboxed() -> bool { - std::env::var(CODEX_SANDBOX_ENV_VAR).as_deref() == Ok("seatbelt") + std::env::var("CODEX_SANDBOX").as_deref() == Ok("seatbelt") } #[cfg(test)] -#[path = "default_client_tests.rs"] +#[path = "create_client_tests.rs"] mod tests; diff --git a/codex-rs/core/src/default_client_tests.rs b/codex-rs/codex-client/src/create_client_tests.rs similarity index 91% rename from codex-rs/core/src/default_client_tests.rs rename to codex-rs/codex-client/src/create_client_tests.rs index 44d5e2c3c9..7ebe69c42e 100644 --- a/codex-rs/core/src/default_client_tests.rs +++ b/codex-rs/codex-client/src/create_client_tests.rs @@ -1,7 +1,18 @@ use super::*; -use core_test_support::skip_if_no_network; +use codex_config::ResidencyRequirement; use pretty_assertions::assert_eq; +macro_rules! skip_if_no_network { + () => { + if std::env::var("CODEX_SANDBOX_NETWORK_DISABLED").is_ok() { + println!( + "Skipping test because it cannot execute when network is disabled in a Codex sandbox." + ); + return; + } + }; +} + #[test] fn test_get_codex_user_agent() { let user_agent = get_codex_user_agent(); diff --git a/codex-rs/codex-client/src/lib.rs b/codex-rs/codex-client/src/lib.rs index 93dd81506f..db63c1f7f5 100644 --- a/codex-rs/codex-client/src/lib.rs +++ b/codex-rs/codex-client/src/lib.rs @@ -1,3 +1,4 @@ +mod create_client; mod custom_ca; mod default_client; mod error; @@ -7,6 +8,23 @@ mod sse; mod telemetry; mod transport; +pub use crate::create_client::CODEX_INTERNAL_ORIGINATOR_OVERRIDE_ENV_VAR; +pub use crate::create_client::DEFAULT_ORIGINATOR; +pub use crate::create_client::Originator; +pub use crate::create_client::RESIDENCY_HEADER_NAME; +pub use crate::create_client::SetOriginatorError; +pub use crate::create_client::USER_AGENT_SUFFIX; +pub use crate::create_client::build_reqwest_client; +pub use crate::create_client::create_client; +pub use crate::create_client::default_headers; +pub use crate::create_client::get_codex_user_agent; +pub use crate::create_client::is_first_party_chat_originator; +pub use crate::create_client::is_first_party_originator; +pub use crate::create_client::originator; +pub use crate::create_client::sanitize_user_agent; +pub use crate::create_client::set_default_client_residency_requirement; +pub use crate::create_client::set_default_originator; +pub use crate::create_client::try_build_reqwest_client; pub use crate::custom_ca::BuildCustomCaTransportError; /// Test-only subprocess hook for custom CA coverage. /// diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index 9f0269537a..bb9899aaf3 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -31,6 +31,7 @@ codex-api = { workspace = true } codex-app-server-protocol = { workspace = true } codex-apply-patch = { workspace = true } codex-async-utils = { workspace = true } +codex-auth = { workspace = true } codex-client = { workspace = true } codex-connectors = { workspace = true } codex-config = { workspace = true } @@ -74,7 +75,6 @@ keyring = { workspace = true, features = ["crypto-rust"] } libc = { workspace = true } notify = { workspace = true } once_cell = { workspace = true } -os_info = { workspace = true } rand = { workspace = true } regex-lite = { workspace = true } reqwest = { workspace = true, features = ["json", "stream"] } diff --git a/codex-rs/core/src/analytics_client.rs b/codex-rs/core/src/analytics_client.rs index bfb90bc4da..0987a3a46f 100644 --- a/codex-rs/core/src/analytics_client.rs +++ b/codex-rs/core/src/analytics_client.rs @@ -1,9 +1,10 @@ use crate::AuthManager; use crate::config::Config; -use crate::default_client::create_client; use crate::git_info::collect_git_info; use crate::git_info::get_git_repo_root; use crate::plugins::PluginTelemetryMetadata; +use codex_client::create_client; +use codex_client::originator; use codex_protocol::protocol::SkillScope; use serde::Serialize; use sha1::Digest; @@ -525,7 +526,7 @@ async fn send_track_skill_invocations(auth_manager: &AuthManager, job: TrackSkil thread_id: Some(tracking.thread_id.clone()), invoke_type: Some(invocation.invocation_type), model_slug: Some(tracking.model_slug.clone()), - product_client_id: Some(crate::default_client::originator().value), + product_client_id: Some(originator().value), repo_url, skill_scope: Some(skill_scope.to_string()), }, @@ -629,7 +630,7 @@ fn codex_app_metadata(tracking: &TrackEventsContext, app: AppInvocation) -> Code thread_id: Some(tracking.thread_id.clone()), turn_id: Some(tracking.turn_id.clone()), app_name: app.app_name, - product_client_id: Some(crate::default_client::originator().value), + product_client_id: Some(originator().value), invoke_type: app.invocation_type, model_slug: Some(tracking.model_slug.clone()), } @@ -654,7 +655,7 @@ fn codex_plugin_metadata(plugin: PluginTelemetryMetadata) -> CodexPluginMetadata .map(|connector_id| connector_id.0) .collect() }), - product_client_id: Some(crate::default_client::originator().value), + product_client_id: Some(originator().value), } } diff --git a/codex-rs/core/src/analytics_client_tests.rs b/codex-rs/core/src/analytics_client_tests.rs index a038aae047..cc79d6567e 100644 --- a/codex-rs/core/src/analytics_client_tests.rs +++ b/codex-rs/core/src/analytics_client_tests.rs @@ -109,7 +109,7 @@ fn app_mentioned_event_serializes_expected_shape() { "thread_id": "thread-1", "turn_id": "turn-1", "app_name": "Calendar", - "product_client_id": crate::default_client::originator().value, + "product_client_id": originator().value, "invoke_type": "explicit", "model_slug": "gpt-5" } @@ -147,7 +147,7 @@ fn app_used_event_serializes_expected_shape() { "thread_id": "thread-2", "turn_id": "turn-2", "app_name": "Google Drive", - "product_client_id": crate::default_client::originator().value, + "product_client_id": originator().value, "invoke_type": "implicit", "model_slug": "gpt-5" } @@ -210,7 +210,7 @@ fn plugin_used_event_serializes_expected_shape() { "has_skills": true, "mcp_server_count": 2, "connector_ids": ["calendar", "drive"], - "product_client_id": crate::default_client::originator().value, + "product_client_id": originator().value, "thread_id": "thread-3", "turn_id": "turn-3", "model_slug": "gpt-5" @@ -239,7 +239,7 @@ fn plugin_management_event_serializes_expected_shape() { "has_skills": true, "mcp_server_count": 2, "connector_ids": ["calendar", "drive"], - "product_client_id": crate::default_client::originator().value + "product_client_id": originator().value } }) ); diff --git a/codex-rs/core/src/arc_monitor.rs b/codex-rs/core/src/arc_monitor.rs index c704faafc0..0e87e20435 100644 --- a/codex-rs/core/src/arc_monitor.rs +++ b/codex-rs/core/src/arc_monitor.rs @@ -8,8 +8,8 @@ use tracing::warn; use crate::codex::Session; use crate::codex::TurnContext; use crate::compact::content_items_to_text; -use crate::default_client::build_reqwest_client; use crate::event_mapping::is_contextual_user_message_content; +use codex_client::build_reqwest_client; use codex_protocol::models::MessagePhase; use codex_protocol::models::ResponseItem; diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index ba71033c3b..3ebd884cc1 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -97,7 +97,6 @@ use crate::auth::RefreshTokenError; use crate::client_common::Prompt; use crate::client_common::ResponseEvent; use crate::client_common::ResponseStream; -use crate::default_client::build_reqwest_client; use crate::error::CodexErr; use crate::error::Result; use crate::flags::CODEX_RS_SSE_FIXTURE; @@ -111,6 +110,8 @@ use crate::tools::spec::create_tools_json_for_responses_api; use crate::util::FeedbackRequestTags; use crate::util::emit_feedback_auth_recovery_tags; use crate::util::emit_feedback_request_tags_with_auth_env; +use codex_client::build_reqwest_client; +use codex_client::default_headers; pub const OPENAI_BETA_HEADER: &str = "OpenAI-Beta"; pub const X_CODEX_TURN_STATE_HEADER: &str = "x-codex-turn-state"; @@ -568,7 +569,7 @@ impl ModelClient { websocket_connect_timeout, ApiWebSocketResponsesClient::new(api_provider, api_auth).connect( headers, - crate::default_client::default_headers(), + default_headers(), turn_state, Some(websocket_telemetry), ), diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 62a22e5712..cdd8f6852d 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -58,6 +58,7 @@ use chrono::Local; use chrono::Utc; use codex_app_server_protocol::McpServerElicitationRequest; use codex_app_server_protocol::McpServerElicitationRequestParams; +use codex_client::originator; use codex_exec_server::Environment; use codex_hooks::HookEvent; use codex_hooks::HookEventAfterAgent; @@ -1580,7 +1581,7 @@ impl Session { let auth_mode = auth.map(CodexAuth::auth_mode).map(TelemetryAuthMode::from); let account_id = auth.and_then(CodexAuth::get_account_id); let account_email = auth.and_then(CodexAuth::get_account_email); - let originator = crate::default_client::originator().value; + let originator = originator().value; let terminal_type = user_agent(); let session_model = session_configuration.collaboration_mode.model().to_string(); let auth_env_telemetry = collect_auth_env_telemetry( diff --git a/codex-rs/core/src/connectors.rs b/codex-rs/core/src/connectors.rs index fdd5cfb59e..1556f10b38 100644 --- a/codex-rs/core/src/connectors.rs +++ b/codex-rs/core/src/connectors.rs @@ -30,9 +30,6 @@ use crate::config::types::AppToolApproval; use crate::config::types::AppsConfigToml; use crate::config::types::ToolSuggestDiscoverableType; use crate::config_loader::AppsRequirementsToml; -use crate::default_client::create_client; -use crate::default_client::is_first_party_chat_originator; -use crate::default_client::originator; use crate::features::Feature; use crate::mcp::CODEX_APPS_MCP_SERVER_NAME; use crate::mcp::McpManager; @@ -47,6 +44,9 @@ use crate::plugins::list_tool_suggest_discoverable_plugins; use crate::token_data::TokenData; use crate::tools::discoverable::DiscoverablePluginInfo; use crate::tools::discoverable::DiscoverableTool; +use codex_client::create_client; +use codex_client::is_first_party_chat_originator; +use codex_client::originator; pub use codex_connectors::CONNECTORS_CACHE_TTL; const CONNECTORS_READY_TIMEOUT_ON_EMPTY_TOOLS: Duration = Duration::from_secs(30); diff --git a/codex-rs/core/src/error.rs b/codex-rs/core/src/error.rs index e8e86defc2..130259a49b 100644 --- a/codex-rs/core/src/error.rs +++ b/codex-rs/core/src/error.rs @@ -9,6 +9,8 @@ use chrono::Datelike; use chrono::Local; use chrono::Utc; use codex_async_utils::CancelErr; +pub use codex_auth::RefreshTokenFailedError; +pub use codex_auth::RefreshTokenFailedReason; use codex_protocol::ThreadId; use codex_protocol::protocol::CodexErrorInfo; use codex_protocol::protocol::ErrorEvent; @@ -261,30 +263,6 @@ impl std::fmt::Display for ResponseStreamFailed { } } -#[derive(Debug, Clone, PartialEq, Eq, Error)] -#[error("{message}")] -pub struct RefreshTokenFailedError { - pub reason: RefreshTokenFailedReason, - pub message: String, -} - -impl RefreshTokenFailedError { - pub fn new(reason: RefreshTokenFailedReason, message: impl Into) -> Self { - Self { - reason, - message: message.into(), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum RefreshTokenFailedReason { - Expired, - Exhausted, - Revoked, - Other, -} - #[derive(Debug)] pub struct UnexpectedResponseError { pub status: StatusCode, diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index 6d519f488f..c2f3faac23 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -10,7 +10,7 @@ pub mod api_bridge; mod apply_patch; mod apps; mod arc_monitor; -pub mod auth; +pub use codex_auth as auth; mod auth_env_telemetry; mod client; mod client_common; @@ -76,7 +76,7 @@ mod shell_detect; mod stream_events_utils; pub mod test_support; mod text_encoding; -pub mod token_data; +pub use codex_auth::token_data; mod truncate; mod unified_exec; pub mod windows_sandbox; @@ -110,7 +110,6 @@ pub type CodexConversation = CodexThread; pub use analytics_client::AnalyticsEventsClient; pub use auth::AuthManager; pub use auth::CodexAuth; -pub mod default_client; pub mod project_doc; mod rollout; pub(crate) mod safety; diff --git a/codex-rs/core/src/mcp/skill_dependencies.rs b/codex-rs/core/src/mcp/skill_dependencies.rs index 4e00c2eca5..cc02b11860 100644 --- a/codex-rs/core/src/mcp/skill_dependencies.rs +++ b/codex-rs/core/src/mcp/skill_dependencies.rs @@ -22,11 +22,11 @@ use crate::config::edit::ConfigEditsBuilder; use crate::config::load_global_mcp_servers; use crate::config::types::McpServerConfig; use crate::config::types::McpServerTransportConfig; -use crate::default_client::is_first_party_originator; -use crate::default_client::originator; use crate::features::Feature; use crate::skills::SkillMetadata; use crate::skills::model::SkillToolDependency; +use codex_client::is_first_party_originator; +use codex_client::originator; const SKILL_MCP_DEPENDENCY_PROMPT_ID: &str = "skill_mcp_dependency_install"; const MCP_DEPENDENCY_OPTION_INSTALL: &str = "Install"; diff --git a/codex-rs/core/src/models_manager/manager.rs b/codex-rs/core/src/models_manager/manager.rs index 29a1a85767..40f1cbb37b 100644 --- a/codex-rs/core/src/models_manager/manager.rs +++ b/codex-rs/core/src/models_manager/manager.rs @@ -7,7 +7,6 @@ use crate::auth::CodexAuth; use crate::auth_env_telemetry::AuthEnvTelemetry; use crate::auth_env_telemetry::collect_auth_env_telemetry; use crate::config::Config; -use crate::default_client::build_reqwest_client; use crate::error::CodexErr; use crate::error::Result as CoreResult; use crate::model_provider_info::ModelProviderInfo; @@ -22,6 +21,7 @@ use codex_api::ModelsClient; use codex_api::RequestTelemetry; use codex_api::ReqwestTransport; use codex_api::TransportError; +use codex_client::build_reqwest_client; use codex_otel::TelemetryAuthMode; use codex_protocol::config_types::CollaborationModeMask; use codex_protocol::openai_models::ModelInfo; diff --git a/codex-rs/core/src/otel_init.rs b/codex-rs/core/src/otel_init.rs index 74e30ef822..0902763637 100644 --- a/codex-rs/core/src/otel_init.rs +++ b/codex-rs/core/src/otel_init.rs @@ -1,8 +1,8 @@ use crate::config::Config; use crate::config::types::OtelExporterKind as Kind; use crate::config::types::OtelHttpProtocol as Protocol; -use crate::default_client::originator; use crate::features::Feature; +use codex_client::originator; use codex_otel::OtelProvider; use codex_otel::config::OtelExporter; use codex_otel::config::OtelHttpProtocol; diff --git a/codex-rs/core/src/plugins/curated_repo.rs b/codex-rs/core/src/plugins/curated_repo.rs index 3307f28ffc..644077dc52 100644 --- a/codex-rs/core/src/plugins/curated_repo.rs +++ b/codex-rs/core/src/plugins/curated_repo.rs @@ -1,4 +1,4 @@ -use crate::default_client::build_reqwest_client; +use codex_client::build_reqwest_client; use reqwest::Client; use serde::Deserialize; use std::fs; diff --git a/codex-rs/core/src/plugins/remote.rs b/codex-rs/core/src/plugins/remote.rs index 898767e35f..5bec6dcc58 100644 --- a/codex-rs/core/src/plugins/remote.rs +++ b/codex-rs/core/src/plugins/remote.rs @@ -1,6 +1,6 @@ use crate::auth::CodexAuth; use crate::config::Config; -use crate::default_client::build_reqwest_client; +use codex_client::build_reqwest_client; use serde::Deserialize; use std::time::Duration; use url::Url; diff --git a/codex-rs/core/src/realtime_conversation.rs b/codex-rs/core/src/realtime_conversation.rs index 1ddd72d0fd..da5d88cc9d 100644 --- a/codex-rs/core/src/realtime_conversation.rs +++ b/codex-rs/core/src/realtime_conversation.rs @@ -4,7 +4,6 @@ use crate::auth::read_openai_api_key_from_env; use crate::codex::Session; use crate::config::RealtimeWsMode; use crate::config::RealtimeWsVersion; -use crate::default_client::default_headers; use crate::error::CodexErr; use crate::error::Result as CodexResult; use crate::realtime_context::build_realtime_startup_context; @@ -22,6 +21,7 @@ use codex_api::RealtimeSessionMode; use codex_api::RealtimeWebsocketClient; use codex_api::endpoint::realtime_websocket::RealtimeWebsocketEvents; use codex_api::endpoint::realtime_websocket::RealtimeWebsocketWriter; +use codex_client::default_headers; use codex_protocol::protocol::CodexErrorInfo; use codex_protocol::protocol::ConversationAudioParams; use codex_protocol::protocol::ConversationStartParams; diff --git a/codex-rs/core/src/rollout/recorder.rs b/codex-rs/core/src/rollout/recorder.rs index 002269d59e..41e0a3401d 100644 --- a/codex-rs/core/src/rollout/recorder.rs +++ b/codex-rs/core/src/rollout/recorder.rs @@ -40,13 +40,13 @@ use super::metadata; use super::policy::EventPersistenceMode; use super::policy::is_persisted_response_item; use crate::config::Config; -use crate::default_client::originator; use crate::git_info::collect_git_info; use crate::path_utils; use crate::state_db; use crate::state_db::StateDbHandle; use crate::truncate::TruncationPolicy; use crate::truncate::truncate_text; +use codex_client::originator; use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::InitialHistory; use codex_protocol::protocol::ResumedHistory; diff --git a/codex-rs/core/src/skills/remote.rs b/codex-rs/core/src/skills/remote.rs index 165c450635..79c0e83d4a 100644 --- a/codex-rs/core/src/skills/remote.rs +++ b/codex-rs/core/src/skills/remote.rs @@ -8,7 +8,7 @@ use std::time::Duration; use crate::auth::CodexAuth; use crate::config::Config; -use crate::default_client::build_reqwest_client; +use codex_client::build_reqwest_client; const REMOTE_SKILLS_API_TIMEOUT: Duration = Duration::from_secs(30); diff --git a/codex-rs/core/src/util.rs b/codex-rs/core/src/util.rs index 1dbd6a84fc..09892cca57 100644 --- a/codex-rs/core/src/util.rs +++ b/codex-rs/core/src/util.rs @@ -217,21 +217,6 @@ pub(crate) fn error_or_panic(message: impl std::string::ToString) { } } -pub(crate) fn try_parse_error_message(text: &str) -> String { - debug!("Parsing server error response: {}", text); - let json = serde_json::from_str::(text).unwrap_or_default(); - if let Some(error) = json.get("error") - && let Some(message) = error.get("message") - && let Some(message_str) = message.as_str() - { - return message_str.to_string(); - } - if text.is_empty() { - return "Unknown error".to_string(); - } - text.to_string() -} - pub fn resolve_path(base: &Path, path: &PathBuf) -> PathBuf { if path.is_absolute() { path.clone() diff --git a/codex-rs/core/src/windows_sandbox.rs b/codex-rs/core/src/windows_sandbox.rs index 79f5c2f1ed..8990505826 100644 --- a/codex-rs/core/src/windows_sandbox.rs +++ b/codex-rs/core/src/windows_sandbox.rs @@ -3,11 +3,11 @@ use crate::config::ConfigToml; use crate::config::edit::ConfigEditsBuilder; use crate::config::profile::ConfigProfile; use crate::config::types::WindowsSandboxModeToml; -use crate::default_client::originator; use crate::features::Feature; use crate::features::Features; use crate::features::FeaturesToml; use crate::protocol::SandboxPolicy; +use codex_client::originator; use codex_otel::sanitize_metric_tag_value; use codex_protocol::config_types::WindowsSandboxLevel; use std::collections::BTreeMap; diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index 35dee3fa70..16f5cd8ad7 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -1,3 +1,4 @@ +use codex_client::originator; use codex_core::CodexAuth; use codex_core::ModelClient; use codex_core::ModelProviderInfo; @@ -8,7 +9,6 @@ use codex_core::ThreadManager; use codex_core::WireApi; use codex_core::auth::AuthCredentialsStoreMode; use codex_core::built_in_model_providers; -use codex_core::default_client::originator; use codex_core::error::CodexErr; use codex_core::features::Feature; use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig; diff --git a/codex-rs/core/tests/suite/plugins.rs b/codex-rs/core/tests/suite/plugins.rs index 0eba6e3234..8a9076f893 100644 --- a/codex-rs/core/tests/suite/plugins.rs +++ b/codex-rs/core/tests/suite/plugins.rs @@ -6,6 +6,7 @@ use std::time::Duration; use std::time::Instant; use anyhow::Result; +use codex_client::originator; use codex_core::CodexAuth; use codex_core::features::Feature; use codex_protocol::protocol::EventMsg; @@ -394,7 +395,7 @@ async fn explicit_plugin_mentions_track_plugin_used_analytics() -> Result<()> { ); assert_eq!( event["event_params"]["product_client_id"], - serde_json::json!(codex_core::default_client::originator().value) + serde_json::json!(originator().value) ); assert_eq!(event["event_params"]["model_slug"], "gpt-5"); assert!(event["event_params"]["thread_id"].as_str().is_some()); diff --git a/codex-rs/exec-server/src/server/filesystem.rs b/codex-rs/exec-server/src/server/filesystem.rs index bc3d22a4da..a263bb1fee 100644 --- a/codex-rs/exec-server/src/server/filesystem.rs +++ b/codex-rs/exec-server/src/server/filesystem.rs @@ -36,7 +36,7 @@ pub(crate) struct ExecServerFileSystem { impl Default for ExecServerFileSystem { fn default() -> Self { Self { - file_system: Arc::new(Environment.get_filesystem()), + file_system: Arc::new(Environment::default().get_filesystem()), } } } diff --git a/codex-rs/exec/Cargo.toml b/codex-rs/exec/Cargo.toml index 3785235606..0c429ab4ec 100644 --- a/codex-rs/exec/Cargo.toml +++ b/codex-rs/exec/Cargo.toml @@ -21,6 +21,7 @@ clap = { workspace = true, features = ["derive"] } codex-arg0 = { workspace = true } codex-app-server-client = { workspace = true } codex-app-server-protocol = { workspace = true } +codex-client = { workspace = true } codex-cloud-requirements = { workspace = true } codex-core = { workspace = true } codex-feedback = { workspace = true } diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index d27cec1f57..96a5171fbc 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -45,6 +45,7 @@ use codex_cloud_requirements::cloud_requirements_loader; use codex_core::AuthManager; use codex_core::LMSTUDIO_OSS_PROVIDER_ID; use codex_core::OLLAMA_OSS_PROVIDER_ID; +use codex_core::auth::AuthConfig; use codex_core::auth::enforce_login_restrictions; use codex_core::check_execpolicy_for_warnings; use codex_core::config::Config; @@ -98,8 +99,8 @@ use uuid::Uuid; use crate::cli::Command as ExecCommand; use crate::event_processor::CodexStatus; use crate::event_processor::EventProcessor; -use codex_core::default_client::set_default_client_residency_requirement; -use codex_core::default_client::set_default_originator; +use codex_client::set_default_client_residency_requirement; +use codex_client::set_default_originator; use codex_core::find_thread_path_by_id_str; use codex_core::find_thread_path_by_name_str; @@ -381,7 +382,12 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result set_default_client_residency_requirement(config.enforce_residency.value()); - if let Err(err) = enforce_login_restrictions(&config) { + if let Err(err) = enforce_login_restrictions(&AuthConfig { + codex_home: config.codex_home.clone(), + auth_credentials_store_mode: config.cli_auth_credentials_store_mode, + forced_login_method: config.forced_login_method, + forced_chatgpt_workspace_id: config.forced_chatgpt_workspace_id.clone(), + }) { eprintln!("{err}"); std::process::exit(1); } diff --git a/codex-rs/exec/tests/suite/originator.rs b/codex-rs/exec/tests/suite/originator.rs index 9b57c2c92f..43006b53f4 100644 --- a/codex-rs/exec/tests/suite/originator.rs +++ b/codex-rs/exec/tests/suite/originator.rs @@ -1,7 +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 codex_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; diff --git a/codex-rs/login/src/server.rs b/codex-rs/login/src/server.rs index a51e038dc1..96b3759078 100644 --- a/codex-rs/login/src/server.rs +++ b/codex-rs/login/src/server.rs @@ -29,10 +29,10 @@ use base64::Engine; use chrono::Utc; use codex_app_server_protocol::AuthMode; use codex_client::build_reqwest_client_with_custom_ca; +use codex_client::originator; use codex_core::auth::AuthCredentialsStoreMode; use codex_core::auth::AuthDotJson; use codex_core::auth::save_auth; -use codex_core::default_client::originator; use codex_core::token_data::TokenData; use codex_core::token_data::parse_chatgpt_jwt_claims; use rand::RngCore; diff --git a/codex-rs/mcp-server/Cargo.toml b/codex-rs/mcp-server/Cargo.toml index 9158a7b66e..b5baac7f1d 100644 --- a/codex-rs/mcp-server/Cargo.toml +++ b/codex-rs/mcp-server/Cargo.toml @@ -18,6 +18,7 @@ workspace = true [dependencies] anyhow = { workspace = true } codex-arg0 = { workspace = true } +codex-client = { workspace = true } codex-core = { workspace = true } codex-protocol = { workspace = true } codex-shell-command = { workspace = true } @@ -40,7 +41,6 @@ tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] } [dev-dependencies] core_test_support = { workspace = true } -codex-terminal = { workspace = true } mcp_test_support = { workspace = true } os_info = { workspace = true } pretty_assertions = { workspace = true } diff --git a/codex-rs/mcp-server/src/message_processor.rs b/codex-rs/mcp-server/src/message_processor.rs index ee57b7038c..671e18c5ab 100644 --- a/codex-rs/mcp-server/src/message_processor.rs +++ b/codex-rs/mcp-server/src/message_processor.rs @@ -1,11 +1,11 @@ use std::collections::HashMap; use codex_arg0::Arg0DispatchPaths; +use codex_client::USER_AGENT_SUFFIX; +use codex_client::get_codex_user_agent; use codex_core::AuthManager; use codex_core::ThreadManager; use codex_core::config::Config; -use codex_core::default_client::USER_AGENT_SUFFIX; -use codex_core::default_client::get_codex_user_agent; use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig; use codex_protocol::ThreadId; use codex_protocol::protocol::SessionSource; diff --git a/codex-rs/mcp-server/tests/common/Cargo.toml b/codex-rs/mcp-server/tests/common/Cargo.toml index 1dec2d09ac..fec95a0945 100644 --- a/codex-rs/mcp-server/tests/common/Cargo.toml +++ b/codex-rs/mcp-server/tests/common/Cargo.toml @@ -9,8 +9,10 @@ path = "lib.rs" [dependencies] anyhow = { workspace = true } +codex-client = { workspace = true } codex-core = { workspace = true } codex-mcp-server = { workspace = true } +codex-terminal = { workspace = true } codex-utils-cargo-bin = { workspace = true } rmcp = { workspace = true } os_info = { workspace = true } diff --git a/codex-rs/mcp-server/tests/common/mcp_process.rs b/codex-rs/mcp-server/tests/common/mcp_process.rs index 5ff9e1788c..47a78ae8bf 100644 --- a/codex-rs/mcp-server/tests/common/mcp_process.rs +++ b/codex-rs/mcp-server/tests/common/mcp_process.rs @@ -10,6 +10,7 @@ use tokio::process::ChildStdin; use tokio::process::ChildStdout; use anyhow::Context; +use codex_client::originator; use codex_mcp_server::CodexToolCallParam; use codex_terminal::user_agent; @@ -151,7 +152,7 @@ impl McpProcess { let initialized = self.read_jsonrpc_message().await?; let os_info = os_info::get(); let build_version = env!("CARGO_PKG_VERSION"); - let originator = codex_core::default_client::originator().value; + let originator = originator().value; let user_agent = format!( "{originator}/{build_version} ({} {}; {}) {} (elicitation test; 0.0.0)", os_info.os_type(), diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 98f2679ded..3e4df002dc 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -40,6 +40,7 @@ use crate::update_action::UpdateAction; use crate::version::CODEX_CLI_VERSION; use codex_ansi_escape::ansi_escape_line; use codex_app_server_protocol::ConfigLayerSource; +use codex_client::originator; use codex_core::AuthManager; use codex_core::CodexAuth; use codex_core::ThreadManager; @@ -2078,7 +2079,7 @@ impl App { auth_ref.and_then(CodexAuth::get_account_id), auth_ref.and_then(CodexAuth::get_account_email), auth_mode, - codex_core::default_client::originator().value, + originator().value, config.otel.log_user_prompt, user_agent(), SessionSource::Cli, diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index 2d75eb29de..617dca9443 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -7,12 +7,14 @@ use additional_dirs::add_dir_warning_message; use app::App; pub use app::AppExitInfo; pub use app::ExitReason; +use codex_client::set_default_client_residency_requirement; use codex_cloud_requirements::cloud_requirements_loader; use codex_core::AuthManager; use codex_core::CodexAuth; use codex_core::INTERACTIVE_SESSION_SOURCES; use codex_core::RolloutRecorder; use codex_core::ThreadSortKey; +use codex_core::auth::AuthConfig; use codex_core::auth::AuthMode; use codex_core::auth::enforce_login_restrictions; use codex_core::check_execpolicy_for_warnings; @@ -26,7 +28,6 @@ use codex_core::config_loader::CloudRequirementsLoader; use codex_core::config_loader::ConfigLoadError; use codex_core::config_loader::LoaderOverrides; use codex_core::config_loader::format_config_error_with_source; -use codex_core::default_client::set_default_client_residency_requirement; use codex_core::find_thread_path_by_id_str; use codex_core::find_thread_path_by_name_str; use codex_core::format_exec_policy_error_with_source; @@ -454,7 +455,12 @@ pub async fn run_main( } #[allow(clippy::print_stderr)] - if let Err(err) = enforce_login_restrictions(&config) { + if let Err(err) = enforce_login_restrictions(&AuthConfig { + codex_home: config.codex_home.clone(), + auth_credentials_store_mode: config.cli_auth_credentials_store_mode, + forced_login_method: config.forced_login_method, + forced_chatgpt_workspace_id: config.forced_chatgpt_workspace_id.clone(), + }) { eprintln!("{err}"); std::process::exit(1); } diff --git a/codex-rs/tui/src/updates.rs b/codex-rs/tui/src/updates.rs index 58d025a4a1..124aa730c0 100644 --- a/codex-rs/tui/src/updates.rs +++ b/codex-rs/tui/src/updates.rs @@ -5,8 +5,8 @@ use crate::update_action::UpdateAction; use chrono::DateTime; use chrono::Duration; use chrono::Utc; +use codex_client::create_client; use codex_core::config::Config; -use codex_core::default_client::create_client; use serde::Deserialize; use serde::Serialize; use std::path::Path; diff --git a/codex-rs/tui/src/voice.rs b/codex-rs/tui/src/voice.rs index 510010c303..13e15da623 100644 --- a/codex-rs/tui/src/voice.rs +++ b/codex-rs/tui/src/voice.rs @@ -4,10 +4,10 @@ use crate::audio_device::preferred_input_config; use crate::audio_device::select_configured_input_device_and_config; use base64::Engine; use codex_client::build_reqwest_client_with_custom_ca; +use codex_client::get_codex_user_agent; use codex_core::auth::AuthCredentialsStoreMode; use codex_core::config::Config; use codex_core::config::find_codex_home; -use codex_core::default_client::get_codex_user_agent; use codex_login::AuthMode; use codex_login::CodexAuth; use codex_protocol::protocol::ConversationAudioParams; diff --git a/codex-rs/tui_app_server/src/app.rs b/codex-rs/tui_app_server/src/app.rs index b404a6d73d..2e25dcd20b 100644 --- a/codex-rs/tui_app_server/src/app.rs +++ b/codex-rs/tui_app_server/src/app.rs @@ -60,6 +60,7 @@ use codex_app_server_protocol::SkillsListResponse; use codex_app_server_protocol::ThreadRollbackResponse; use codex_app_server_protocol::Turn; use codex_app_server_protocol::TurnStatus; +use codex_client::originator; use codex_core::config::Config; use codex_core::config::ConfigBuilder; use codex_core::config::ConfigOverrides; @@ -2964,7 +2965,7 @@ impl App { /*account_id*/ None, bootstrap.account_email.clone(), auth_mode, - codex_core::default_client::originator().value, + originator().value, config.otel.log_user_prompt, user_agent(), SessionSource::Cli, diff --git a/codex-rs/tui_app_server/src/lib.rs b/codex-rs/tui_app_server/src/lib.rs index 7fdfc86b1e..9f031dc48a 100644 --- a/codex-rs/tui_app_server/src/lib.rs +++ b/codex-rs/tui_app_server/src/lib.rs @@ -20,7 +20,9 @@ use codex_app_server_protocol::Thread as AppServerThread; use codex_app_server_protocol::ThreadListParams; use codex_app_server_protocol::ThreadSortKey as AppServerThreadSortKey; use codex_app_server_protocol::ThreadSourceKind; +use codex_client::set_default_client_residency_requirement; use codex_cloud_requirements::cloud_requirements_loader_for_storage; +use codex_core::auth::AuthConfig; use codex_core::auth::enforce_login_restrictions; use codex_core::check_execpolicy_for_warnings; use codex_core::config::Config; @@ -33,7 +35,6 @@ use codex_core::config_loader::CloudRequirementsLoader; use codex_core::config_loader::ConfigLoadError; use codex_core::config_loader::LoaderOverrides; use codex_core::config_loader::format_config_error_with_source; -use codex_core::default_client::set_default_client_residency_requirement; use codex_core::format_exec_policy_error_with_source; use codex_core::path_utils; use codex_core::read_session_meta_line; @@ -777,7 +778,12 @@ pub async fn run_main( if matches!(app_server_target, AppServerTarget::Embedded) { #[allow(clippy::print_stderr)] - if let Err(err) = enforce_login_restrictions(&config) { + if let Err(err) = enforce_login_restrictions(&AuthConfig { + codex_home: config.codex_home.clone(), + auth_credentials_store_mode: config.cli_auth_credentials_store_mode, + forced_login_method: config.forced_login_method, + forced_chatgpt_workspace_id: config.forced_chatgpt_workspace_id.clone(), + }) { eprintln!("{err}"); std::process::exit(1); } diff --git a/codex-rs/tui_app_server/src/updates.rs b/codex-rs/tui_app_server/src/updates.rs index 58d025a4a1..124aa730c0 100644 --- a/codex-rs/tui_app_server/src/updates.rs +++ b/codex-rs/tui_app_server/src/updates.rs @@ -5,8 +5,8 @@ use crate::update_action::UpdateAction; use chrono::DateTime; use chrono::Duration; use chrono::Utc; +use codex_client::create_client; use codex_core::config::Config; -use codex_core::default_client::create_client; use serde::Deserialize; use serde::Serialize; use std::path::Path; diff --git a/codex-rs/tui_app_server/src/voice.rs b/codex-rs/tui_app_server/src/voice.rs index 6758eff4d6..07402cd2a9 100644 --- a/codex-rs/tui_app_server/src/voice.rs +++ b/codex-rs/tui_app_server/src/voice.rs @@ -2,10 +2,10 @@ use crate::app_event::AppEvent; use crate::app_event_sender::AppEventSender; use base64::Engine; use codex_client::build_reqwest_client_with_custom_ca; +use codex_client::get_codex_user_agent; use codex_core::auth::AuthCredentialsStoreMode; use codex_core::config::Config; use codex_core::config::find_codex_home; -use codex_core::default_client::get_codex_user_agent; use codex_login::AuthMode; use codex_login::CodexAuth; use codex_protocol::protocol::ConversationAudioParams;