Compare commits

...

1 Commits

Author SHA1 Message Date
Owen Lin
caaae96609 Make Codex HTTP identity explicit 2026-05-19 13:54:51 -07:00
49 changed files with 476 additions and 303 deletions

View File

@@ -124,7 +124,7 @@ use codex_app_server_protocol::TurnSteerParams;
use codex_app_server_protocol::TurnSteerResponse;
use codex_app_server_protocol::UserInput;
use codex_login::default_client::DEFAULT_ORIGINATOR;
use codex_login::default_client::originator;
use codex_login::default_client::Originator;
use codex_plugin::AppConnectorId;
use codex_plugin::PluginCapabilitySummary;
use codex_plugin::PluginId;
@@ -965,7 +965,7 @@ fn app_mentioned_event_serializes_expected_shape() {
"thread_id": "thread-1",
"turn_id": "turn-1",
"app_name": "Calendar",
"product_client_id": originator().value,
"product_client_id": Originator::process_default().value().to_string(),
"invoke_type": "explicit",
"model_slug": "gpt-5"
}
@@ -1003,7 +1003,7 @@ fn app_used_event_serializes_expected_shape() {
"thread_id": "thread-2",
"turn_id": "turn-2",
"app_name": "Google Drive",
"product_client_id": originator().value,
"product_client_id": Originator::process_default().value().to_string(),
"invoke_type": "implicit",
"model_slug": "gpt-5"
}
@@ -2774,7 +2774,7 @@ fn plugin_used_event_serializes_expected_shape() {
"has_skills": true,
"mcp_server_count": 2,
"connector_ids": ["calendar", "drive"],
"product_client_id": originator().value,
"product_client_id": Originator::process_default().value().to_string(),
"thread_id": "thread-3",
"turn_id": "turn-3",
"model_slug": "gpt-5"
@@ -2803,7 +2803,7 @@ fn plugin_management_event_serializes_expected_shape() {
"has_skills": true,
"mcp_server_count": 2,
"connector_ids": ["calendar", "drive"],
"product_client_id": originator().value
"product_client_id": Originator::process_default().value().to_string()
}
})
);
@@ -3009,7 +3009,7 @@ async fn reducer_ingests_skill_invoked_fact() {
"skill_id": expected_skill_id,
"skill_name": "doc",
"event_params": {
"product_client_id": originator().value,
"product_client_id": Originator::process_default().value().to_string(),
"skill_scope": "user",
"plugin_id": null,
"repo_url": null,
@@ -3170,7 +3170,7 @@ async fn reducer_ingests_plugin_state_changed_fact() {
"has_skills": true,
"mcp_server_count": 2,
"connector_ids": ["calendar", "drive"],
"product_client_id": originator().value
"product_client_id": Originator::process_default().value().to_string()
}
}])
);

View File

@@ -31,6 +31,7 @@ use codex_app_server_protocol::ServerRequest;
use codex_app_server_protocol::ServerResponse;
use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_login::default_client::Originator;
use codex_login::default_client::create_client;
use codex_plugin::PluginTelemetryMetadata;
use codex_protocol::request_permissions::RequestPermissionsResponse;
@@ -440,7 +441,8 @@ async fn send_track_events_request(auth: &CodexAuth, url: &str, events: Vec<Trac
let payload = TrackEventsRequest { events };
let response = create_client()
let originator = Originator::process_default();
let response = create_client(&originator)
.post(url)
.timeout(ANALYTICS_EVENTS_TIMEOUT)
.headers(codex_model_provider::auth_provider_from_auth(auth).to_auth_headers())

View File

@@ -22,7 +22,7 @@ use crate::facts::TurnSubmissionType;
use crate::now_unix_millis;
use codex_app_server_protocol::CodexErrorInfo;
use codex_app_server_protocol::CommandExecutionSource;
use codex_login::default_client::originator;
use codex_login::default_client::Originator;
use codex_plugin::PluginTelemetryMetadata;
use codex_protocol::approvals::NetworkApprovalProtocol;
use codex_protocol::models::AdditionalPermissionProfile;
@@ -890,7 +890,7 @@ pub(crate) fn codex_app_metadata(
thread_id: Some(tracking.thread_id.clone()),
turn_id: Some(tracking.turn_id.clone()),
app_name: app.app_name,
product_client_id: Some(originator().value),
product_client_id: Some(Originator::process_default().value().to_string()),
invoke_type: app.invocation_type,
model_slug: Some(tracking.model_slug.clone()),
}
@@ -920,7 +920,7 @@ pub(crate) fn codex_plugin_metadata(plugin: PluginTelemetryMetadata) -> CodexPlu
.map(|connector_id| connector_id.0)
.collect()
}),
product_client_id: Some(originator().value),
product_client_id: Some(Originator::process_default().value().to_string()),
}
}

View File

@@ -111,7 +111,7 @@ use codex_app_server_protocol::UserInput;
use codex_app_server_protocol::WebSearchAction;
use codex_git_utils::collect_git_info;
use codex_git_utils::get_git_repo_root;
use codex_login::default_client::originator;
use codex_login::default_client::Originator;
use codex_protocol::config_types::ModeKind;
use codex_protocol::config_types::Personality;
use codex_protocol::config_types::ReasoningSummary;
@@ -680,7 +680,7 @@ impl AnalyticsReducer {
turn_id: Some(tracking.turn_id.clone()),
invoke_type: Some(invocation.invocation_type),
model_slug: Some(tracking.model_slug.clone()),
product_client_id: Some(originator().value),
product_client_id: Some(Originator::process_default().value().to_string()),
repo_url,
skill_scope: Some(skill_scope.to_string()),
plugin_id: invocation.plugin_id,

View File

@@ -3,6 +3,7 @@ use super::protocol::EnrollRemoteServerResponse;
use super::protocol::RemoteControlTarget;
use axum::http::HeaderMap;
use codex_api::SharedAuthProvider;
use codex_login::default_client::Originator;
use codex_login::default_client::build_reqwest_client;
use codex_state::RemoteControlEnrollmentRecord;
use codex_state::StateRuntime;
@@ -204,7 +205,8 @@ pub(super) async fn enroll_remote_control_server(
app_server_version: env!("CARGO_PKG_VERSION"),
installation_id: installation_id.to_string(),
};
let client = build_reqwest_client();
let originator = Originator::process_default();
let client = build_reqwest_client(&originator);
let mut auth_headers = HeaderMap::new();
auth.auth_provider.add_auth_headers(&mut auth_headers);
let http_request = client

View File

@@ -1,20 +1,15 @@
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use axum::http::HeaderValue;
use codex_analytics::AppServerRpcTransport;
use codex_login::default_client::SetOriginatorError;
use codex_login::default_client::USER_AGENT_SUFFIX;
use codex_login::default_client::Originator;
use codex_login::default_client::get_codex_user_agent;
use codex_login::default_client::set_default_client_residency_requirement;
use codex_login::default_client::set_default_originator;
use super::*;
use crate::message_processor::ConnectionSessionState;
use crate::message_processor::InitializedConnectionSessionState;
const NON_ORIGINATING_CLIENT_NAMES: &[&str] = &["codex_app_server_daemon", "codex-backend"];
#[derive(Clone)]
pub(crate) struct InitializeRequestProcessor {
outgoing: Arc<OutgoingMessageSender>,
@@ -83,23 +78,19 @@ impl InitializeRequestProcessor {
title: _title,
version,
} = params.client_info;
// Validate before committing; set_default_originator validates while
// mutating process-global metadata.
if HeaderValue::from_str(&name).is_err() {
return Err(invalid_request(format!(
"Invalid clientInfo.name: '{name}'. Must be a valid HTTP header value."
)));
}
let originator = name.clone();
let user_agent_suffix = format!("{name}; {version}");
let mutates_global_identity = !NON_ORIGINATING_CLIENT_NAMES.contains(&name.as_str());
let originator = Originator::from_app_server_client(name.clone(), version.clone())
.map_err(|_| {
invalid_request(format!(
"Invalid clientInfo.name: '{name}'. Must be a valid HTTP header value."
))
})?;
let codex_home = self.config.codex_home.clone();
if session
.initialize(InitializedConnectionSessionState {
experimental_api_enabled,
opted_out_notification_methods: opt_out_notification_methods.into_iter().collect(),
app_server_client_name: name.clone(),
client_version: version,
client_version: version.clone(),
request_attestation,
})
.is_err()
@@ -107,37 +98,15 @@ impl InitializeRequestProcessor {
return Err(invalid_request("Already initialized"));
}
if mutates_global_identity {
// Only real client initialization may mutate process-global client metadata.
if let Err(error) = set_default_originator(originator.clone()) {
match error {
SetOriginatorError::InvalidHeaderValue => {
tracing::warn!(
client_info_name = %name,
"validated clientInfo.name was rejected while setting originator"
);
}
SetOriginatorError::AlreadyInitialized => {
// No-op. This is expected to happen if the originator is already set via env var.
// TODO(owen): Once we remove support for CODEX_INTERNAL_ORIGINATOR_OVERRIDE,
// this will be an unexpected state and we can return a JSON-RPC error indicating
// internal server error.
}
}
}
}
self.analytics_events_client.track_initialize(
connection_id.0,
analytics_initialize_params,
originator,
name,
self.rpc_transport,
);
set_default_client_residency_requirement(self.config.enforce_residency.value());
if mutates_global_identity && let Ok(mut suffix) = USER_AGENT_SUFFIX.lock() {
*suffix = Some(user_agent_suffix);
}
let user_agent = get_codex_user_agent();
let user_agent = get_codex_user_agent(&originator);
let response = InitializeResponse {
user_agent,
codex_home,

View File

@@ -63,7 +63,7 @@ async fn initialize_uses_client_info_name_as_originator() -> Result<()> {
}
#[tokio::test]
async fn initialize_probe_does_not_override_originator() -> Result<()> {
async fn initialize_probe_uses_connection_scoped_originator() -> Result<()> {
let responses = Vec::new();
let server = create_mock_responses_server_sequence_unchecked(responses).await;
let codex_home = TempDir::new()?;
@@ -85,12 +85,12 @@ async fn initialize_probe_does_not_override_originator() -> Result<()> {
};
let InitializeResponse { user_agent, .. } = to_response::<InitializeResponse>(response)?;
assert!(user_agent.starts_with("codex_cli_rs/"));
assert!(user_agent.starts_with("codex_app_server_daemon/"));
Ok(())
}
#[tokio::test]
async fn initialize_codex_backend_does_not_override_originator() -> Result<()> {
async fn initialize_codex_backend_uses_connection_scoped_originator() -> Result<()> {
let responses = Vec::new();
let server = create_mock_responses_server_sequence_unchecked(responses).await;
let codex_home = TempDir::new()?;
@@ -112,12 +112,12 @@ async fn initialize_codex_backend_does_not_override_originator() -> Result<()> {
};
let InitializeResponse { user_agent, .. } = to_response::<InitializeResponse>(response)?;
assert!(user_agent.starts_with("codex_cli_rs/"));
assert!(user_agent.starts_with("codex-backend/"));
Ok(())
}
#[tokio::test]
async fn initialize_respects_originator_override_env_var() -> Result<()> {
async fn initialize_ignores_process_originator_override_env_var() -> Result<()> {
let responses = Vec::new();
let server = create_mock_responses_server_sequence_unchecked(responses).await;
let codex_home = TempDir::new()?;
@@ -152,7 +152,7 @@ async fn initialize_respects_originator_override_env_var() -> Result<()> {
platform_os,
} = to_response::<InitializeResponse>(response)?;
assert!(user_agent.starts_with("codex_originator_via_env_var/"));
assert!(user_agent.starts_with("codex_vscode/"));
assert_eq!(response_codex_home, expected_codex_home);
assert_eq!(platform_family, std::env::consts::FAMILY);
assert_eq!(platform_os, std::env::consts::OS);

View File

@@ -9,6 +9,7 @@ use codex_api::SharedAuthProvider;
use codex_client::build_reqwest_client_with_custom_ca;
use codex_client::with_chatgpt_cloudflare_cookie_store;
use codex_login::CodexAuth;
use codex_login::default_client::Originator;
use codex_login::default_client::get_codex_user_agent;
use codex_protocol::account::PlanType as AccountPlanType;
use codex_protocol::protocol::CreditsSnapshot;
@@ -170,8 +171,9 @@ impl Client {
}
pub fn from_auth(base_url: impl Into<String>, auth: &CodexAuth) -> Result<Self> {
let originator = Originator::process_default();
Ok(Self::new(base_url)?
.with_user_agent(get_codex_user_agent())
.with_user_agent(get_codex_user_agent(&originator))
.with_auth_provider(codex_model_provider::auth_provider_from_auth(auth)))
}

View File

@@ -1,5 +1,6 @@
use codex_core::config::Config;
use codex_login::AuthManager;
use codex_login::default_client::Originator;
use codex_login::default_client::create_client;
use anyhow::Context;
@@ -39,7 +40,8 @@ pub(crate) async fn chatgpt_get_request_with_timeout<T: DeserializeOwned>(
);
// Make direct HTTP request to ChatGPT backend API with the token
let client = create_client();
let originator = Originator::process_default();
let client = create_client(&originator);
let url = format!(
"{}/{}",
chatgpt_base_url.trim_end_matches('/'),

View File

@@ -20,7 +20,7 @@ pub use codex_core::connectors::with_app_enabled_state;
use codex_core_plugins::PluginsManager;
use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_login::default_client::originator;
use codex_login::default_client::Originator;
use codex_plugin::AppConnectorId;
const DIRECTORY_CONNECTORS_TIMEOUT: Duration = Duration::from_secs(60);
@@ -85,10 +85,8 @@ pub async fn list_cached_all_connectors(config: &Config) -> Option<Vec<AppInfo>>
.into_iter()
.map(|connector_id| connector_id.0),
);
Some(filter_disallowed_connectors(
connectors,
originator().value.as_str(),
))
let originator = Originator::process_default();
Some(filter_disallowed_connectors(connectors, originator.value()))
}
pub async fn list_all_connectors_with_options(
@@ -121,10 +119,8 @@ pub async fn list_all_connectors_with_options(
.into_iter()
.map(|connector_id| connector_id.0),
);
Ok(filter_disallowed_connectors(
connectors,
originator().value.as_str(),
))
let originator = Originator::process_default();
Ok(filter_disallowed_connectors(connectors, originator.value()))
}
fn connector_directory_cache_context(
@@ -165,7 +161,8 @@ pub fn connectors_for_plugin_apps(
.iter()
.map(|connector_id| connector_id.0.clone()),
);
filter_disallowed_connectors(connectors, originator().value.as_str())
let originator = Originator::process_default();
filter_disallowed_connectors(connectors, originator.value())
.into_iter()
.filter(|connector| plugin_app_ids.contains(connector.id.as_str()))
.collect()
@@ -189,7 +186,8 @@ pub fn merge_connectors_with_accessible(
accessible_connectors
};
let merged = merge_connectors(connectors, accessible_connectors);
filter_disallowed_connectors(merged, originator().value.as_str())
let originator = Originator::process_default();
filter_disallowed_connectors(merged, originator.value())
}
#[cfg(test)]

View File

@@ -47,6 +47,7 @@ use codex_login::CODEX_ACCESS_TOKEN_ENV_VAR;
use codex_login::CODEX_API_KEY_ENV_VAR;
use codex_login::CodexAuth;
use codex_login::OPENAI_API_KEY_ENV_VAR;
use codex_login::default_client::Originator;
use codex_login::default_client::build_reqwest_client;
use codex_login::default_client::default_headers;
use codex_login::load_auth_dot_json;
@@ -2164,12 +2165,13 @@ async fn websocket_reachability_check(
OPENAI_BETA_HEADER,
HeaderValue::from_static(RESPONSES_WEBSOCKETS_V2_BETA_HEADER_VALUE),
);
let originator = Originator::process_default();
let client = ResponsesWebsocketClient::new(api_provider, api_auth);
match tokio::time::timeout(
provider.websocket_connect_timeout(),
client.probe_handshake(
extra_headers,
default_headers(),
default_headers(&originator),
WEBSOCKET_IMMEDIATE_CLOSE_GRACE,
),
)
@@ -2657,7 +2659,8 @@ async fn mcp_http_probe_url_with_timeout(url: &str, timeout: Duration) -> Result
}
async fn http_probe_url_with_timeout(url: &str, timeout: Duration) -> Result<String, String> {
let response = build_reqwest_client()
let originator = Originator::process_default();
let response = build_reqwest_client(&originator)
.head(url)
.timeout(timeout)
.send()
@@ -2683,7 +2686,8 @@ async fn http_get_probe_url_with_timeout(url: &str, timeout: Duration) -> Result
}
async fn http_get_probe_status_with_timeout(url: &str, timeout: Duration) -> Result<u16, String> {
let response = build_reqwest_client()
let originator = Originator::process_default();
let response = build_reqwest_client(&originator)
.get(url)
.timeout(timeout)
.send()

View File

@@ -12,6 +12,7 @@ use chrono::Utc;
use codex_cloud_tasks_client::TaskStatus;
use codex_git_utils::current_branch_name;
use codex_git_utils::default_branch_name;
use codex_login::default_client::Originator;
use codex_login::default_client::get_codex_user_agent;
use owo_colors::OwoColorize;
use owo_colors::Stream;
@@ -28,7 +29,6 @@ use tracing::info;
use tracing_subscriber::EnvFilter;
use util::append_error_log;
use util::format_relative_time;
use util::set_user_agent_suffix;
struct ApplyJob {
task_id: codex_cloud_tasks_client::TaskId,
@@ -49,8 +49,6 @@ async fn init_backend(user_agent_suffix: &str) -> anyhow::Result<BackendContext>
let base_url = std::env::var("CODEX_CLOUD_TASKS_BASE_URL")
.unwrap_or_else(|_| "https://chatgpt.com/backend-api".to_string());
set_user_agent_suffix(user_agent_suffix);
#[cfg(debug_assertions)]
if use_mock {
return Ok(BackendContext {
@@ -59,7 +57,9 @@ async fn init_backend(user_agent_suffix: &str) -> anyhow::Result<BackendContext>
});
}
let ua = get_codex_user_agent();
let originator = Originator::for_process(user_agent_suffix.to_string())
.expect("cloud task originator should be a valid header value");
let ua = get_codex_user_agent(&originator);
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"
@@ -799,7 +799,10 @@ pub async fn run_main(cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> an
append_error_log(format!(
"startup: wham_force_internal={} ua={}",
force_internal,
get_codex_user_agent()
get_codex_user_agent(
&Originator::for_process("codex_cloud_tasks_tui".to_string())
.expect("codex_cloud_tasks_tui should be a valid originator header value")
)
));
// Non-blocking initial load so the in-box spinner can animate
app.status = "Loading tasks…".to_string();

View File

@@ -5,12 +5,7 @@ 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_login::default_client::USER_AGENT_SUFFIX.lock() {
guard.replace(suffix.to_string());
}
}
use codex_login::default_client::Originator;
pub fn append_error_log(message: impl AsRef<str>) {
let ts = Utc::now().to_rfc3339();
@@ -61,8 +56,9 @@ pub async fn build_chatgpt_headers() -> HeaderMap {
use reqwest::header::HeaderValue;
use reqwest::header::USER_AGENT;
set_user_agent_suffix("codex_cloud_tasks_tui");
let ua = codex_login::default_client::get_codex_user_agent();
let originator = Originator::for_process("codex_cloud_tasks_tui".to_string())
.expect("codex_cloud_tasks_tui should be a valid originator header value");
let ua = codex_login::default_client::get_codex_user_agent(&originator);
let mut headers = HeaderMap::new();
headers.insert(
USER_AGENT,

View File

@@ -50,7 +50,7 @@ pub use codex_extension_api::empty_extension_registry;
pub use codex_features::Feature;
pub use codex_features::Features;
pub use codex_login::AuthManager;
pub use codex_login::default_client::set_default_originator;
pub use codex_login::default_client::Originator;
pub use codex_model_provider_info::OPENAI_PROVIDER_ID;
pub use codex_model_provider_info::built_in_model_providers;
pub use codex_models_manager::manager::RefreshStrategy;

View File

@@ -7,6 +7,7 @@ use codex_app_server_protocol::PluginInstallPolicy;
use codex_app_server_protocol::PluginInterface;
use codex_app_server_protocol::SkillInterface;
use codex_login::CodexAuth;
use codex_login::default_client::Originator;
use codex_login::default_client::build_reqwest_client;
use codex_plugin::PluginId;
use codex_utils_absolute_path::AbsolutePathBuf;
@@ -23,6 +24,11 @@ use url::Url;
mod remote_installed_plugin_sync;
mod share;
fn build_process_reqwest_client() -> reqwest::Client {
let originator = Originator::process_default();
build_reqwest_client(&originator)
}
pub use remote_installed_plugin_sync::RemoteInstalledPluginBundleSyncError;
pub use remote_installed_plugin_sync::RemoteInstalledPluginBundleSyncOutcome;
pub use remote_installed_plugin_sync::RemotePluginCacheMutationGuard;
@@ -782,7 +788,7 @@ pub async fn fetch_remote_plugin_skill_detail(
}
let url = remote_plugin_skill_detail_url(config, plugin_id, skill_name)?;
let client = build_reqwest_client();
let client = build_process_reqwest_client();
let request = authenticated_request(client.get(&url), auth)?;
let response: RemotePluginSkillDetailResponse = send_and_decode(request, &url).await?;
if response.plugin_id != plugin_id {
@@ -883,7 +889,7 @@ pub async fn install_remote_plugin(
let base_url = config.chatgpt_base_url.trim_end_matches('/');
let url = format!("{base_url}/ps/plugins/{plugin_id}/install");
let client = build_reqwest_client();
let client = build_process_reqwest_client();
let request = authenticated_request(client.post(&url), auth)?;
let response: RemotePluginMutationResponse = send_and_decode(request, &url).await?;
if response.id != plugin_id {
@@ -919,7 +925,7 @@ pub async fn uninstall_remote_plugin(
let base_url = config.chatgpt_base_url.trim_end_matches('/');
let url = format!("{base_url}/plugins/{plugin_id}/uninstall");
let client = build_reqwest_client();
let client = build_process_reqwest_client();
let request = authenticated_request(client.post(&url), auth)?;
let response: RemotePluginMutationResponse = send_and_decode(request, &url).await?;
if response.id != plugin_id {
@@ -1252,7 +1258,7 @@ async fn get_remote_plugin_list_page(
) -> Result<RemotePluginListResponse, RemotePluginCatalogError> {
let base_url = config.chatgpt_base_url.trim_end_matches('/');
let url = format!("{base_url}/ps/plugins/list");
let client = build_reqwest_client();
let client = build_process_reqwest_client();
let mut request = authenticated_request(client.get(&url), auth)?;
request = request.query(&[("scope", scope.api_value())]);
request = request.query(&[("limit", REMOTE_PLUGIN_LIST_PAGE_LIMIT)]);
@@ -1269,7 +1275,7 @@ async fn get_remote_shared_workspace_plugins_page(
) -> Result<RemotePluginListResponse, RemotePluginCatalogError> {
let base_url = config.chatgpt_base_url.trim_end_matches('/');
let url = format!("{base_url}/ps/plugins/workspace/shared");
let client = build_reqwest_client();
let client = build_process_reqwest_client();
let mut request = authenticated_request(client.get(&url), auth)?;
request = request.query(&[("limit", REMOTE_PLUGIN_LIST_PAGE_LIMIT)]);
if let Some(page_token) = page_token {
@@ -1287,7 +1293,7 @@ async fn get_remote_plugin_installed_page(
) -> Result<RemotePluginInstalledResponse, RemotePluginCatalogError> {
let base_url = config.chatgpt_base_url.trim_end_matches('/');
let url = format!("{base_url}/ps/plugins/installed");
let client = build_reqwest_client();
let client = build_process_reqwest_client();
let mut request = authenticated_request(client.get(&url), auth)?;
request = request.query(&[("scope", scope.api_value())]);
if include_download_urls {
@@ -1307,7 +1313,7 @@ async fn fetch_plugin_detail(
) -> Result<RemotePluginDirectoryItem, RemotePluginCatalogError> {
let base_url = config.chatgpt_base_url.trim_end_matches('/');
let url = format!("{base_url}/ps/plugins/{plugin_id}");
let client = build_reqwest_client();
let client = build_process_reqwest_client();
let mut request = authenticated_request(client.get(&url), auth)?;
if include_download_urls {
request = request.query(&[("includeDownloadUrls", true)]);

View File

@@ -1,6 +1,5 @@
use super::*;
use codex_login::CodexAuth;
use codex_login::default_client::build_reqwest_client;
use codex_utils_absolute_path::AbsolutePathBuf;
use flate2::Compression;
use flate2::write::GzEncoder;
@@ -282,7 +281,7 @@ pub async fn delete_remote_plugin_share(
let auth = ensure_chatgpt_auth(auth)?;
let base_url = config.chatgpt_base_url.trim_end_matches('/');
let url = format!("{base_url}/public/plugins/workspace/{remote_plugin_id}");
let client = build_reqwest_client();
let client = build_process_reqwest_client();
let request = authenticated_request(client.delete(&url), auth)?;
send_and_expect_status(request, &url, &[StatusCode::NO_CONTENT]).await?;
if let Err(err) = local_paths::remove_plugin_share_local_path(codex_home, remote_plugin_id) {
@@ -315,7 +314,7 @@ pub async fn update_remote_plugin_share_targets(
.unwrap_or_default();
let base_url = config.chatgpt_base_url.trim_end_matches('/');
let url = format!("{base_url}/ps/plugins/{remote_plugin_id}/shares");
let client = build_reqwest_client();
let client = build_process_reqwest_client();
let request = authenticated_request(client.put(&url), auth)?.json(
&RemotePluginShareUpdateTargetsRequest {
discoverability,
@@ -381,7 +380,7 @@ async fn get_created_workspace_plugins_page(
) -> Result<RemotePluginListResponse, RemotePluginCatalogError> {
let base_url = config.chatgpt_base_url.trim_end_matches('/');
let url = format!("{base_url}/ps/plugins/workspace/created");
let client = build_reqwest_client();
let client = build_process_reqwest_client();
let mut request = authenticated_request(client.get(&url), auth)?;
request = request.query(&[("limit", REMOTE_PLUGIN_LIST_PAGE_LIMIT)]);
if let Some(page_token) = page_token {
@@ -399,7 +398,7 @@ async fn create_workspace_plugin_upload(
) -> Result<RemoteWorkspacePluginUploadUrlResponse, RemotePluginCatalogError> {
let base_url = config.chatgpt_base_url.trim_end_matches('/');
let url = format!("{base_url}/public/plugins/workspace/upload-url");
let client = build_reqwest_client();
let client = build_process_reqwest_client();
let request = authenticated_request(client.post(&url), auth)?.json(
&RemoteWorkspacePluginUploadUrlRequest {
filename,
@@ -415,7 +414,7 @@ async fn put_workspace_plugin_upload(
upload_url: &str,
archive_bytes: Vec<u8>,
) -> Result<(), RemotePluginCatalogError> {
let client = build_reqwest_client();
let client = build_process_reqwest_client();
let request = client
.put(upload_url)
.timeout(REMOTE_PLUGIN_CATALOG_TIMEOUT)
@@ -453,7 +452,7 @@ async fn finalize_workspace_plugin_upload(
} else {
format!("{base_url}/public/plugins/workspace")
};
let client = build_reqwest_client();
let client = build_process_reqwest_client();
let request = authenticated_request(client.post(&url), auth)?.json(&body);
send_and_decode(request, &url).await
}

View File

@@ -2,6 +2,7 @@ use crate::store::PluginInstallResult;
use crate::store::PluginStore;
use crate::store::PluginStoreError;
use crate::store::validate_plugin_version_segment;
use codex_login::default_client::Originator;
use codex_login::default_client::build_reqwest_client;
use codex_plugin::PluginId;
use codex_plugin::PluginIdError;
@@ -29,6 +30,11 @@ const REMOTE_PLUGIN_INSTALL_STAGING_DIR: &str = "plugins/.remote-plugin-install-
const TEST_ALLOW_LOOPBACK_HTTP_REMOTE_PLUGIN_BUNDLES_ENV: &str =
"CODEX_TEST_ALLOW_HTTP_REMOTE_PLUGIN_BUNDLE_DOWNLOADS";
fn build_process_reqwest_client() -> reqwest::Client {
let originator = Originator::process_default();
build_reqwest_client(&originator)
}
#[derive(Debug, Clone)]
pub struct ValidatedRemotePluginBundle {
pub plugin_id: PluginId,
@@ -263,7 +269,7 @@ async fn download_remote_plugin_bundle_with_limit(
bundle_download_url: &str,
max_bytes: u64,
) -> Result<Vec<u8>, RemotePluginBundleInstallError> {
let client = build_reqwest_client();
let client = build_process_reqwest_client();
let response = client
.get(bundle_download_url)
.timeout(REMOTE_PLUGIN_BUNDLE_DOWNLOAD_TIMEOUT)

View File

@@ -1,5 +1,6 @@
use crate::remote::RemotePluginServiceConfig;
use codex_login::CodexAuth;
use codex_login::default_client::Originator;
use codex_login::default_client::build_reqwest_client;
use codex_protocol::protocol::Product;
use serde::Deserialize;
@@ -11,6 +12,11 @@ const REMOTE_PLUGIN_FETCH_TIMEOUT: Duration = Duration::from_secs(30);
const REMOTE_FEATURED_PLUGIN_FETCH_TIMEOUT: Duration = Duration::from_secs(10);
const REMOTE_PLUGIN_MUTATION_TIMEOUT: Duration = Duration::from_secs(30);
fn build_process_reqwest_client() -> reqwest::Client {
let originator = Originator::process_default();
build_reqwest_client(&originator)
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct RemotePluginStatusSummary {
pub name: String,
@@ -129,7 +135,7 @@ pub async fn fetch_remote_plugin_status(
let base_url = config.chatgpt_base_url.trim_end_matches('/');
let url = format!("{base_url}/plugins/list");
let client = build_reqwest_client();
let client = build_process_reqwest_client();
let request = client
.get(&url)
.timeout(REMOTE_PLUGIN_FETCH_TIMEOUT)
@@ -161,7 +167,7 @@ pub async fn fetch_remote_featured_plugin_ids(
) -> Result<Vec<String>, RemotePluginFetchError> {
let base_url = config.chatgpt_base_url.trim_end_matches('/');
let url = format!("{base_url}/plugins/featured");
let client = build_reqwest_client();
let client = build_process_reqwest_client();
let mut request = client
.get(&url)
.query(&[(
@@ -236,7 +242,7 @@ async fn post_remote_plugin_mutation(
) -> Result<RemotePluginMutationResponse, RemotePluginMutationError> {
let auth = ensure_codex_backend_auth(auth)?;
let url = remote_plugin_mutation_url(config, plugin_id, action)?;
let client = build_reqwest_client();
let client = build_process_reqwest_client();
let request = client
.post(url.clone())
.timeout(REMOTE_PLUGIN_MUTATION_TIMEOUT)

View File

@@ -13,6 +13,7 @@ use tempfile::TempDir;
use tracing::warn;
use zip::ZipArchive;
use codex_login::default_client::Originator;
use codex_login::default_client::build_reqwest_client;
const GITHUB_API_BASE_URL: &str = "https://api.github.com";
@@ -31,6 +32,11 @@ const CURATED_PLUGINS_BACKUP_ARCHIVE_TIMEOUT: Duration = Duration::from_secs(30)
// Keep this comfortably above a normal sync attempt so we do not race another Codex process.
const CURATED_PLUGINS_STALE_TEMP_DIR_MAX_AGE: Duration = Duration::from_secs(10 * 60);
fn build_process_reqwest_client() -> Client {
let originator = Originator::process_default();
build_reqwest_client(&originator)
}
#[derive(Debug, Deserialize)]
struct GitHubRepositorySummary {
default_branch: String,
@@ -590,7 +596,7 @@ fn ensure_git_success(output: &Output, context: &str) -> Result<(), String> {
async fn fetch_curated_repo_remote_sha(api_base_url: &str) -> Result<String, String> {
let api_base_url = api_base_url.trim_end_matches('/');
let repo_url = format!("{api_base_url}/repos/{OPENAI_PLUGINS_OWNER}/{OPENAI_PLUGINS_REPO}");
let client = build_reqwest_client();
let client = build_process_reqwest_client();
let repo_body = fetch_github_text(&client, &repo_url, "get curated plugins repository").await?;
let repo_summary: GitHubRepositorySummary =
serde_json::from_str(&repo_body).map_err(|err| {
@@ -624,14 +630,14 @@ async fn fetch_curated_repo_zipball(
let api_base_url = api_base_url.trim_end_matches('/');
let repo_url = format!("{api_base_url}/repos/{OPENAI_PLUGINS_OWNER}/{OPENAI_PLUGINS_REPO}");
let zipball_url = format!("{repo_url}/zipball/{remote_sha}");
let client = build_reqwest_client();
let client = build_process_reqwest_client();
fetch_github_bytes(&client, &zipball_url, "download curated plugins archive").await
}
async fn fetch_curated_repo_backup_archive_zip(
backup_archive_api_url: &str,
) -> Result<Vec<u8>, String> {
let client = build_reqwest_client();
let client = build_process_reqwest_client();
let export_body = fetch_public_text(
&client,
backup_archive_api_url,

View File

@@ -7,6 +7,7 @@ use std::path::PathBuf;
use std::time::Duration;
use codex_login::CodexAuth;
use codex_login::default_client::Originator;
use codex_login::default_client::build_reqwest_client;
const REMOTE_SKILLS_API_TIMEOUT: Duration = Duration::from_secs(30);
@@ -107,7 +108,8 @@ pub async fn list_remote_skills(
query_params.push(("enabled", enabled));
}
let client = build_reqwest_client();
let originator = Originator::process_default();
let client = build_reqwest_client(&originator);
let request = client
.get(&url)
.timeout(REMOTE_SKILLS_API_TIMEOUT)
@@ -146,7 +148,8 @@ pub async fn export_remote_skill(
) -> Result<RemoteSkillDownloadResult> {
let auth = ensure_codex_backend_auth(auth)?;
let client = build_reqwest_client();
let originator = Originator::process_default();
let client = build_reqwest_client(&originator);
let base_url = chatgpt_base_url.trim_end_matches('/');
let url = format!("{base_url}/hazelnuts/{skill_id}/export");
let request = client

View File

@@ -9,6 +9,7 @@ use crate::compact::content_items_to_text;
use crate::event_mapping::is_contextual_user_message_content;
use crate::session::session::Session;
use crate::session::turn_context::TurnContext;
use codex_login::default_client::Originator;
use codex_login::default_client::build_reqwest_client;
use codex_protocol::models::MessagePhase;
use codex_protocol::models::ResponseItem;
@@ -128,7 +129,8 @@ pub(crate) async fn monitor_action(
};
let body =
build_arc_monitor_request(sess, turn_context, action, protection_client_callsite).await;
let client = build_reqwest_client();
let originator = Originator::process_default();
let client = build_reqwest_client(&originator);
let mut request = client.post(&url).timeout(ARC_MONITOR_TIMEOUT).json(&body);
if let Some(token) = env_token {
request = request.bearer_auth(token);

View File

@@ -66,7 +66,9 @@ use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_login::RefreshTokenError;
use codex_login::UnauthorizedRecovery;
use codex_login::default_client::Originator;
use codex_login::default_client::build_reqwest_client;
use codex_login::default_client::default_headers;
use codex_otel::SessionTelemetry;
use codex_otel::current_span_w3c_trace_context;
@@ -151,6 +153,16 @@ const MEMORIES_SUMMARIZE_ENDPOINT: &str = "/memories/trace_summarize";
pub(crate) const WEBSOCKET_CONNECT_TIMEOUT: Duration =
Duration::from_millis(DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS);
fn process_reqwest_client() -> reqwest::Client {
let originator = Originator::process_default();
build_reqwest_client(&originator)
}
fn process_default_headers() -> ApiHeaderMap {
let originator = Originator::process_default();
default_headers(&originator)
}
pub(crate) struct CompactConversationRequestSettings {
pub(crate) effort: Option<ReasoningEffortConfig>,
pub(crate) summary: ReasoningSummaryConfig,
@@ -442,7 +454,7 @@ impl ModelClient {
return Ok(Vec::new());
}
let client_setup = self.current_client_setup().await?;
let transport = ReqwestTransport::new(build_reqwest_client());
let transport = ReqwestTransport::new(process_reqwest_client());
let request_telemetry = Self::build_request_telemetry(
session_telemetry,
AuthRequestTelemetryContext::new(
@@ -530,7 +542,7 @@ impl ModelClient {
sideband_headers.extend(sideband_websocket_auth_headers(
client_setup.api_auth.as_ref(),
));
let transport = ReqwestTransport::new(build_reqwest_client());
let transport = ReqwestTransport::new(process_reqwest_client());
let response =
ApiRealtimeCallClient::new(transport, client_setup.api_provider, client_setup.api_auth)
.create_with_session_and_headers(sdp, session_config, extra_headers)
@@ -561,7 +573,7 @@ impl ModelClient {
}
let client_setup = self.current_client_setup().await?;
let transport = ReqwestTransport::new(build_reqwest_client());
let transport = ReqwestTransport::new(process_reqwest_client());
let request_telemetry = Self::build_request_telemetry(
session_telemetry,
AuthRequestTelemetryContext::new(
@@ -823,7 +835,7 @@ impl ModelClient {
websocket_connect_timeout,
ApiWebSocketResponsesClient::new(api_provider, api_auth).connect(
headers,
codex_login::default_client::default_headers(),
process_default_headers(),
turn_state,
Some(websocket_telemetry),
),
@@ -1225,7 +1237,7 @@ impl ModelClientSession {
let mut pending_retry = PendingUnauthorizedRetry::default();
loop {
let client_setup = self.client.current_client_setup().await?;
let transport = ReqwestTransport::new(build_reqwest_client());
let transport = ReqwestTransport::new(process_reqwest_client());
let request_auth_context = AuthRequestTelemetryContext::new(
client_setup.auth.as_ref().map(CodexAuth::auth_mode),
client_setup.api_auth.as_ref(),

View File

@@ -32,7 +32,7 @@ use codex_core_plugins::PluginsManager;
use codex_features::Feature;
use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_login::default_client::originator;
use codex_login::default_client::Originator;
use codex_mcp::CODEX_APPS_MCP_SERVER_NAME;
use codex_mcp::McpConnectionManager;
use codex_mcp::McpRuntimeEnvironment;
@@ -124,7 +124,7 @@ pub(crate) async fn list_tool_suggest_discoverable_tools_with_auth(
directory_connectors,
accessible_connectors,
&connector_ids,
originator().value.as_str(),
Originator::process_default().value(),
)
.into_iter()
.map(DiscoverableTool::from);
@@ -153,7 +153,7 @@ pub async fn list_cached_accessible_connectors_from_mcp_tools(
read_cached_accessible_connectors(&cache_key).map(|connectors| {
codex_connectors::filter::filter_disallowed_connectors(
connectors,
originator().value.as_str(),
Originator::process_default().value(),
)
})
}
@@ -170,7 +170,7 @@ pub(crate) fn refresh_accessible_connectors_cache_from_mcp_tools(
let cache_key = accessible_connectors_cache_key(config, auth);
let accessible_connectors = codex_connectors::filter::filter_disallowed_connectors(
accessible_connectors_from_mcp_tools(mcp_tools),
originator().value.as_str(),
Originator::process_default().value(),
);
write_cached_accessible_connectors(cache_key, &accessible_connectors);
}
@@ -232,7 +232,7 @@ pub async fn list_accessible_connectors_from_mcp_tools_with_environment_manager(
{
let cached_connectors = codex_connectors::filter::filter_disallowed_connectors(
cached_connectors,
originator().value.as_str(),
Originator::process_default().value(),
);
let cached_connectors = with_app_plugin_sources(cached_connectors, &tool_plugin_provenance);
return Ok(AccessibleConnectorsStatus {
@@ -341,7 +341,7 @@ pub async fn list_accessible_connectors_from_mcp_tools_with_environment_manager(
let accessible_connectors = codex_connectors::filter::filter_disallowed_connectors(
accessible_connectors_from_mcp_tools(&tools),
originator().value.as_str(),
Originator::process_default().value(),
);
if codex_apps_ready || !accessible_connectors.is_empty() {
write_cached_accessible_connectors(cache_key, &accessible_connectors);

View File

@@ -5,8 +5,8 @@ use codex_config::ConfigEditsBuilder;
use codex_config::McpServerConfig;
use codex_config::McpServerTransportConfig;
use codex_config::load_global_mcp_servers;
use codex_login::default_client::Originator;
use codex_login::default_client::is_first_party_originator;
use codex_login::default_client::originator;
use codex_protocol::request_user_input::RequestUserInputArgs;
use codex_protocol::request_user_input::RequestUserInputQuestion;
use codex_protocol::request_user_input::RequestUserInputQuestionOption;
@@ -38,8 +38,8 @@ pub(crate) async fn maybe_prompt_and_install_mcp_dependencies(
mentioned_skills: &[SkillMetadata],
elicitation_reviewer: Option<ElicitationReviewerHandle>,
) {
let originator_value = originator().value;
if !is_first_party_originator(originator_value.as_str()) {
let originator = Originator::process_default();
if !is_first_party_originator(originator.value()) {
// Only support first-party clients for now.
return;
}

View File

@@ -2,7 +2,7 @@ use crate::config::Config;
use codex_config::types::OtelExporterKind as Kind;
use codex_config::types::OtelHttpProtocol as Protocol;
use codex_features::Feature;
use codex_login::default_client::originator;
use codex_login::default_client::Originator;
use codex_otel::OtelExporter;
use codex_otel::OtelHttpProtocol;
use codex_otel::OtelProvider;
@@ -76,8 +76,8 @@ pub fn build_provider(
OtelExporter::None
};
let originator = originator();
let service_name = service_name_override.unwrap_or(originator.value.as_str());
let originator = Originator::process_default();
let service_name = service_name_override.unwrap_or(originator.value());
let runtime_metrics = config.features.enabled(Feature::RuntimeMetrics);
OtelProvider::from(&OtelSettings {

View File

@@ -24,6 +24,7 @@ use codex_app_server_protocol::AuthMode;
use codex_config::config_toml::RealtimeWsMode;
use codex_config::config_toml::RealtimeWsVersion;
use codex_login::CodexAuth;
use codex_login::default_client::Originator;
use codex_login::default_client::default_headers;
use codex_login::read_openai_api_key_from_env;
use codex_model_provider_info::ModelProviderInfo;
@@ -74,6 +75,11 @@ const REALTIME_V2_STEER_ACKNOWLEDGEMENT: &str =
const REALTIME_ACTIVE_RESPONSE_ERROR_PREFIX: &str =
"Conversation already has an active response in progress:";
fn process_default_headers() -> HeaderMap {
let originator = Originator::process_default();
default_headers(&originator)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum RealtimeConversationEnd {
Requested,
@@ -333,7 +339,7 @@ impl RealtimeConversationManager {
.connect(
session_config,
extra_headers.unwrap_or_default(),
default_headers(),
process_default_headers(),
)
.await
.map_err(map_api_error)?;
@@ -1054,7 +1060,7 @@ fn spawn_webrtc_sideband_input_task(input: RealtimeWebrtcSidebandInputTask) -> J
session_config,
&call_id,
sideband_headers,
default_headers(),
process_default_headers(),
)
.await
{

View File

@@ -59,7 +59,6 @@ use codex_hooks::HooksConfig;
use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_login::auth_env_telemetry::collect_auth_env_telemetry;
use codex_login::default_client::originator;
use codex_mcp::McpConnectionManager;
use codex_mcp::McpRuntimeEnvironment;
use codex_mcp::codex_apps_tools_cache_key;

View File

@@ -2,6 +2,7 @@ use super::input_queue::InputQueue;
use super::*;
use crate::goals::GoalRuntimeState;
use crate::state::ActiveTurn;
use codex_login::default_client::Originator;
use codex_protocol::SessionId;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::permissions::FileSystemPath;
@@ -670,7 +671,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 = originator().value;
let originator = Originator::process_default().value().to_string();
let terminal_type = user_agent();
let session_model = session_configuration.collaboration_mode.model().to_string();
let auth_env_telemetry = collect_auth_env_telemetry(

View File

@@ -6,7 +6,7 @@ use codex_config::types::WindowsSandboxModeToml;
use codex_features::Feature;
use codex_features::Features;
use codex_features::FeaturesToml;
use codex_login::default_client::originator;
use codex_login::default_client::Originator;
use codex_otel::sanitize_metric_tag_value;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::protocol::SandboxPolicy;
@@ -283,7 +283,8 @@ pub struct WindowsSandboxSetupRequest {
pub async fn run_windows_sandbox_setup(request: WindowsSandboxSetupRequest) -> anyhow::Result<()> {
let start = Instant::now();
let mode = request.mode;
let originator_tag = sanitize_metric_tag_value(originator().value.as_str());
let originator = Originator::process_default();
let originator_tag = sanitize_metric_tag_value(originator.value());
let result = run_windows_sandbox_setup_and_persist(request).await;
match result {

View File

@@ -11,7 +11,7 @@ use codex_extension_api::empty_extension_registry;
use codex_features::Feature;
use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_login::default_client::originator;
use codex_login::default_client::Originator;
use codex_model_provider_info::ModelProviderInfo;
use codex_model_provider_info::WireApi;
use codex_model_provider_info::built_in_model_providers;
@@ -781,7 +781,7 @@ async fn includes_session_id_thread_id_and_model_headers_in_request() {
assert_eq!(request_session_id, expected_session_id.to_string());
assert_eq!(request_thread_id, thread_id_string.as_str());
assert_eq!(request_originator, originator().value);
assert_eq!(request_originator, Originator::process_default().value());
assert_eq!(request_authorization, "Bearer Test API Key");
assert_eq!(
request_body["prompt_cache_key"].as_str(),
@@ -1052,7 +1052,7 @@ async fn chatgpt_auth_sends_correct_request() {
assert_eq!(request_session_id, expected_session_id.to_string());
assert_eq!(request_thread_id, expected_thread_id.to_string());
assert_eq!(request_originator, originator().value);
assert_eq!(request_originator, Originator::process_default().value());
assert_eq!(request_authorization, "Bearer Access Token");
assert_eq!(request_chatgpt_account_id, "account_id");
assert_eq!(

View File

@@ -8,6 +8,7 @@ use codex_core::ResponseEvent;
use codex_core::X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER;
use codex_features::Feature;
use codex_login::CodexAuth;
use codex_login::default_client::Originator;
use codex_model_provider_info::ModelProviderInfo;
use codex_model_provider_info::WireApi;
use codex_otel::MetricsClient;
@@ -61,6 +62,11 @@ const TEST_INSTALLATION_ID: &str = "11111111-1111-4111-8111-111111111111";
const X_CODEX_WS_STREAM_REQUEST_START_MS_CLIENT_METADATA_KEY: &str =
"x-codex-ws-stream-request-start-ms";
fn default_user_agent() -> String {
let originator = Originator::process_default();
codex_login::default_client::get_codex_user_agent(&originator)
}
fn assert_request_trace_matches(body: &serde_json::Value, expected_trace: &W3cTraceContext) {
let client_metadata = body["client_metadata"]
.as_object()
@@ -141,7 +147,7 @@ async fn responses_websocket_streams_request() {
);
assert_eq!(
handshake.header(USER_AGENT_HEADER),
Some(codex_login::default_client::get_codex_user_agent())
Some(default_user_agent())
);
assert_eq!(
body["client_metadata"]["x-codex-installation-id"].as_str(),
@@ -385,7 +391,7 @@ async fn responses_websocket_reuses_connection_with_per_turn_trace_payloads() {
assert_eq!(server.handshakes().len(), 1);
assert_eq!(
server.single_handshake().header(USER_AGENT_HEADER),
Some(codex_login::default_client::get_codex_user_agent())
Some(default_user_agent())
);
let connection = server.single_connection();
assert_eq!(connection.len(), 2);
@@ -474,7 +480,7 @@ async fn responses_websocket_preconnect_reuses_connection() {
assert_eq!(server.handshakes().len(), 1);
assert_eq!(
server.single_handshake().header(USER_AGENT_HEADER),
Some(codex_login::default_client::get_codex_user_agent())
Some(default_user_agent())
);
let connection = server.single_connection();
assert_eq!(connection.len(), 1);
@@ -512,7 +518,7 @@ async fn responses_websocket_request_prewarm_reuses_connection() {
assert_eq!(server.handshakes().len(), 1);
assert_eq!(
server.single_handshake().header(USER_AGENT_HEADER),
Some(codex_login::default_client::get_codex_user_agent())
Some(default_user_agent())
);
let connection = server.single_connection();
assert_eq!(connection.len(), 2);
@@ -1359,10 +1365,7 @@ async fn responses_websocket_connection_limit_error_reconnects_and_completes() {
.collect();
assert_eq!(
handshake_user_agents,
vec![
Some(codex_login::default_client::get_codex_user_agent()),
Some(codex_login::default_client::get_codex_user_agent()),
]
vec![Some(default_user_agent()), Some(default_user_agent()),]
);
server.shutdown().await;

View File

@@ -429,7 +429,11 @@ async fn explicit_plugin_mentions_track_plugin_used_analytics() -> Result<()> {
);
assert_eq!(
event["event_params"]["product_client_id"],
serde_json::json!(codex_login::default_client::originator().value)
serde_json::json!(
codex_login::default_client::Originator::process_default()
.value()
.to_string()
)
);
assert_eq!(event["event_params"]["model_slug"], "gpt-5.2");
assert!(event["event_params"]["thread_id"].as_str().is_some());

View File

@@ -72,7 +72,6 @@ use codex_feedback::CodexFeedback;
use codex_git_utils::get_git_repo_root;
use codex_login::AuthConfig;
use codex_login::default_client::set_default_client_residency_requirement;
use codex_login::default_client::set_default_originator;
use codex_login::enforce_login_restrictions;
use codex_model_provider_info::LMSTUDIO_OSS_PROVIDER_ID;
use codex_model_provider_info::OLLAMA_OSS_PROVIDER_ID;
@@ -236,10 +235,6 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
eprintln!("{message}");
}
if let Err(err) = set_default_originator("codex_exec".to_string()) {
tracing::warn!(?err, "Failed to set codex exec originator override {err:?}");
}
let Cli {
command,
strict_config,

View File

@@ -3,6 +3,7 @@ use codex_agent_identity::register_agent_task;
use codex_protocol::account::PlanType as AccountPlanType;
use std::env;
use crate::default_client::Originator;
use crate::default_client::build_reqwest_client;
use super::storage::AgentIdentityAuthRecord;
@@ -19,8 +20,9 @@ pub struct AgentIdentityAuth {
impl AgentIdentityAuth {
pub async fn load(record: AgentIdentityAuthRecord) -> std::io::Result<Self> {
let agent_identity_authapi_base_url = agent_identity_authapi_base_url();
let originator = Originator::process_default();
let process_task_id = register_agent_task(
&build_reqwest_client(),
&build_reqwest_client(&originator),
&agent_identity_authapi_base_url,
key(&record),
)

View File

@@ -14,79 +14,111 @@ use reqwest::header::HeaderMap;
use reqwest::header::HeaderValue;
use reqwest::header::USER_AGENT;
use std::sync::LazyLock;
use std::sync::Mutex;
use std::sync::RwLock;
/// Set this to add a suffix to the User-Agent string.
///
/// It is not ideal that we're using a global singleton for this.
/// This is primarily designed to differentiate MCP clients from each other.
/// Because there can only be one MCP server per process, it should be safe for this to be a global static.
/// However, future users of this should use this with caution as a result.
/// In addition, we want to be confident that this value is used for ALL clients and doing that requires a
/// lot of wiring and it's easy to miss code paths by doing so.
/// See https://github.com/openai/codex/pull/3388/files for an example of what that would look like.
/// Finally, we want to make sure this is set for ALL mcp clients without needing to know a special env var
/// or having to set data that they already specified in the mcp initialize request somewhere else.
///
/// A space is automatically added between the suffix and the rest of the User-Agent string.
/// The full user agent string is returned from the mcp initialize response.
/// Parenthesis will be added by Codex. This should only specify what goes inside of the parenthesis.
pub static USER_AGENT_SUFFIX: LazyLock<Mutex<Option<String>>> = LazyLock::new(|| Mutex::new(None));
pub const DEFAULT_ORIGINATOR: &str = "codex_cli_rs";
pub const CODEX_INTERNAL_ORIGINATOR_OVERRIDE_ENV_VAR: &str = "CODEX_INTERNAL_ORIGINATOR_OVERRIDE";
pub const RESIDENCY_HEADER_NAME: &str = "x-openai-internal-codex-residency";
pub use codex_config::ResidencyRequirement;
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Originator {
pub value: String,
pub header_value: HeaderValue,
}
static ORIGINATOR: LazyLock<RwLock<Option<Originator>>> = LazyLock::new(|| RwLock::new(None));
static REQUIREMENTS_RESIDENCY: LazyLock<RwLock<Option<ResidencyRequirement>>> =
LazyLock::new(|| RwLock::new(None));
#[derive(Debug)]
pub enum SetOriginatorError {
InvalidHeaderValue,
AlreadyInitialized,
kind: OriginatorKind,
}
fn get_originator_value(provided: Option<String>) -> Originator {
let value = std::env::var(CODEX_INTERNAL_ORIGINATOR_OVERRIDE_ENV_VAR)
.ok()
.or(provided)
.unwrap_or(DEFAULT_ORIGINATOR.to_string());
#[derive(Debug, Clone, PartialEq, Eq)]
enum OriginatorKind {
Process { value: String },
AppServerClient { client: AppServerClient },
}
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_ORIGINATOR.to_string(),
header_value: HeaderValue::from_static(DEFAULT_ORIGINATOR),
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AppServerClient {
name: String,
version: String,
}
impl Originator {
pub fn process_default() -> Self {
let value = std::env::var(CODEX_INTERNAL_ORIGINATOR_OVERRIDE_ENV_VAR)
.unwrap_or_else(|_| DEFAULT_ORIGINATOR.to_string());
match Self::for_process(value.clone()) {
Ok(originator) => originator,
Err(e) => {
tracing::error!(
"Unable to turn originator override {value} into header value: {e}"
);
Self::for_process(DEFAULT_ORIGINATOR.to_string())
.expect("default originator should be a valid HTTP header value")
}
}
}
pub fn for_process(value: String) -> Result<Self, InvalidOriginator> {
validate_originator_value(&value)?;
Ok(Self {
kind: OriginatorKind::Process { value },
})
}
pub fn from_app_server_client(
name: String,
version: String,
) -> Result<Self, InvalidOriginator> {
validate_originator_value(&name)?;
Ok(Self {
kind: OriginatorKind::AppServerClient {
client: AppServerClient { name, version },
},
})
}
pub fn value(&self) -> &str {
match &self.kind {
OriginatorKind::Process { value } => value,
OriginatorKind::AppServerClient { client } => client.name(),
}
}
pub fn app_server_client(&self) -> Option<&AppServerClient> {
match &self.kind {
OriginatorKind::Process { .. } => None,
OriginatorKind::AppServerClient { client } => Some(client),
}
}
}
pub fn set_default_originator(value: String) -> Result<(), SetOriginatorError> {
if HeaderValue::from_str(&value).is_err() {
return Err(SetOriginatorError::InvalidHeaderValue);
impl AppServerClient {
pub fn name(&self) -> &str {
&self.name
}
let originator = get_originator_value(Some(value));
let Ok(mut guard) = ORIGINATOR.write() else {
return Err(SetOriginatorError::AlreadyInitialized);
};
if guard.is_some() {
return Err(SetOriginatorError::AlreadyInitialized);
pub fn version(&self) -> &str {
&self.version
}
*guard = Some(originator);
}
static REQUIREMENTS_RESIDENCY: LazyLock<RwLock<Option<ResidencyRequirement>>> =
LazyLock::new(|| RwLock::new(None));
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InvalidOriginator {
InvalidHeaderValue,
}
impl std::fmt::Display for InvalidOriginator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidHeaderValue => f.write_str("invalid HTTP header value"),
}
}
}
impl std::error::Error for InvalidOriginator {}
fn validate_originator_value(value: &str) -> Result<(), InvalidOriginator> {
HeaderValue::from_str(value).map_err(|_| InvalidOriginator::InvalidHeaderValue)?;
Ok(())
}
@@ -98,27 +130,6 @@ pub fn set_default_client_residency_requirement(enforce_residency: Option<Reside
*guard = enforce_residency;
}
pub fn originator() -> Originator {
if let Ok(guard) = ORIGINATOR.read()
&& let Some(originator) = guard.as_ref()
{
return originator.clone();
}
if std::env::var(CODEX_INTERNAL_ORIGINATOR_OVERRIDE_ENV_VAR).is_ok() {
let originator = get_originator_value(/*provided*/ None);
if let Ok(mut guard) = ORIGINATOR.write() {
match guard.as_ref() {
Some(originator) => return originator.clone(),
None => *guard = Some(originator.clone()),
}
}
return originator;
}
get_originator_value(/*provided*/ None)
}
pub fn is_first_party_originator(originator_value: &str) -> bool {
originator_value == DEFAULT_ORIGINATOR
|| originator_value == "codex-tui"
@@ -130,23 +141,18 @@ pub fn is_first_party_chat_originator(originator_value: &str) -> bool {
originator_value == "codex_atlas" || originator_value == "codex_chatgpt_desktop"
}
pub fn get_codex_user_agent() -> String {
pub fn get_codex_user_agent(originator: &Originator) -> String {
let build_version = env!("CARGO_PKG_VERSION");
let os_info = os_info::get();
let originator = originator();
let prefix = format!(
"{}/{build_version} ({} {}; {}) {}",
originator.value.as_str(),
originator.value(),
os_info.os_type(),
os_info.version(),
os_info.architecture().unwrap_or("unknown"),
user_agent()
);
let suffix = USER_AGENT_SUFFIX
.lock()
.ok()
.and_then(|guard| guard.clone());
let suffix = suffix
let suffix = user_agent_suffix(originator)
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
@@ -184,13 +190,19 @@ fn sanitize_user_agent(candidate: String, fallback: &str) -> String {
tracing::warn!(
"Falling back to default Codex originator because base user agent string is invalid"
);
originator().value
DEFAULT_ORIGINATOR.to_string()
}
}
fn user_agent_suffix(originator: &Originator) -> Option<String> {
originator
.app_server_client()
.map(|client| format!("{}; {}", client.name(), client.version()))
}
/// Create an HTTP client with default `originator` and `User-Agent` headers set.
pub fn create_client() -> CodexHttpClient {
let inner = build_reqwest_client();
pub fn create_client(originator: &Originator) -> CodexHttpClient {
let inner = build_reqwest_client(originator);
CodexHttpClient::new(inner)
}
@@ -200,8 +212,12 @@ pub fn create_client() -> CodexHttpClient {
/// policy, then layers in shared custom CA handling from `CODEX_CA_CERTIFICATE` /
/// `SSL_CERT_FILE`. The function remains infallible for compatibility with existing call sites, so
/// a custom-CA or builder failure is logged and falls back to `reqwest::Client::new()`.
pub fn build_reqwest_client() -> reqwest::Client {
try_build_reqwest_client().unwrap_or_else(|error| {
pub fn build_reqwest_client(originator: &Originator) -> reqwest::Client {
build_reqwest_client_with_headers(default_headers(originator))
}
fn build_reqwest_client_with_headers(headers: HeaderMap) -> reqwest::Client {
try_build_reqwest_client_with_headers(headers).unwrap_or_else(|error| {
tracing::warn!(error = %error, "failed to build default reqwest client");
with_chatgpt_cloudflare_cookie_store(reqwest::Client::builder())
.build()
@@ -219,8 +235,16 @@ pub fn build_reqwest_client() -> reqwest::Client {
///
/// Callers that need a structured CA-loading failure instead of the legacy logged fallback can use
/// this method directly.
pub fn try_build_reqwest_client() -> Result<reqwest::Client, BuildCustomCaTransportError> {
let mut builder = reqwest::Client::builder().default_headers(default_headers());
pub fn try_build_reqwest_client(
originator: &Originator,
) -> Result<reqwest::Client, BuildCustomCaTransportError> {
try_build_reqwest_client_with_headers(default_headers(originator))
}
fn try_build_reqwest_client_with_headers(
headers: HeaderMap,
) -> Result<reqwest::Client, BuildCustomCaTransportError> {
let mut builder = reqwest::Client::builder().default_headers(headers);
if is_sandboxed() {
builder = builder.no_proxy();
}
@@ -229,10 +253,13 @@ pub fn try_build_reqwest_client() -> Result<reqwest::Client, BuildCustomCaTransp
build_reqwest_client_with_custom_ca(builder)
}
pub fn default_headers() -> HeaderMap {
pub fn default_headers(originator: &Originator) -> HeaderMap {
let mut headers = HeaderMap::new();
headers.insert("originator", originator().header_value);
if let Ok(user_agent) = HeaderValue::from_str(&get_codex_user_agent()) {
let originator_header = HeaderValue::from_str(originator.value())
.expect("originator should have been validated as a header value");
headers.insert("originator", originator_header);
let user_agent = get_codex_user_agent(originator);
if let Ok(user_agent) = HeaderValue::from_str(&user_agent) {
headers.insert(USER_AGENT, user_agent);
}
if let Ok(guard) = REQUIREMENTS_RESIDENCY.read()

View File

@@ -2,12 +2,13 @@ use super::sanitize_user_agent;
use super::*;
use core_test_support::skip_if_no_network;
use pretty_assertions::assert_eq;
use serial_test::serial;
#[test]
fn test_get_codex_user_agent() {
let user_agent = get_codex_user_agent();
let originator = originator().value;
let prefix = format!("{originator}/");
let originator = Originator::process_default();
let user_agent = get_codex_user_agent(&originator);
let prefix = format!("{}/", originator.value());
assert!(user_agent.starts_with(&prefix));
}
@@ -44,7 +45,8 @@ async fn test_create_client_sets_default_headers() {
use wiremock::matchers::method;
use wiremock::matchers::path;
let client = create_client();
let originator = Originator::process_default();
let client = create_client(&originator);
// Spin up a local mock server and capture a request.
let server = MockServer::start().await;
@@ -72,10 +74,10 @@ async fn test_create_client_sets_default_headers() {
let originator_header = headers
.get("originator")
.expect("originator header missing");
assert_eq!(originator_header.to_str().unwrap(), originator().value);
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();
let expected_ua = get_codex_user_agent(&originator);
let ua_header = headers
.get("user-agent")
.expect("user-agent header missing");
@@ -89,6 +91,94 @@ async fn test_create_client_sets_default_headers() {
set_default_client_residency_requirement(/*enforce_residency*/ None);
}
#[test]
fn app_server_originator_builds_explicit_headers() {
let originator =
Originator::from_app_server_client("codex_ios".to_string(), "1.2.3".to_string())
.expect("originator should be valid");
let headers = default_headers(&originator);
assert_eq!(
headers
.get("originator")
.expect("originator header missing")
.to_str()
.expect("originator should be valid"),
"codex_ios"
);
assert_eq!(
originator.app_server_client().map(AppServerClient::name),
Some("codex_ios")
);
assert_eq!(
originator.app_server_client().map(AppServerClient::version),
Some("1.2.3")
);
assert!(
headers
.get("user-agent")
.expect("user-agent header missing")
.to_str()
.expect("user-agent should be valid")
.starts_with("codex_ios/")
);
assert!(get_codex_user_agent(&originator).contains("(codex_ios; 1.2.3)"));
}
#[test]
fn process_originator_does_not_add_user_agent_suffix() {
let originator =
Originator::for_process("codex_exec".to_string()).expect("originator should be valid");
assert_eq!(originator.value(), "codex_exec");
assert_eq!(originator.app_server_client(), None);
assert!(!get_codex_user_agent(&originator).contains("(codex_exec"));
}
#[test]
#[serial(originator_env)]
fn process_default_reads_originator_override() {
let _guard = EnvVarGuard::set(CODEX_INTERNAL_ORIGINATOR_OVERRIDE_ENV_VAR, "codex_override");
let originator = Originator::process_default();
assert_eq!(originator.value(), "codex_override");
}
#[test]
#[serial(originator_env)]
fn app_server_originator_ignores_originator_override() {
let _guard = EnvVarGuard::set(CODEX_INTERNAL_ORIGINATOR_OVERRIDE_ENV_VAR, "codex_override");
let originator =
Originator::from_app_server_client("codex_ios".to_string(), "1.2.3".to_string())
.expect("originator should be valid");
assert_eq!(originator.value(), "codex_ios");
}
#[test]
#[serial(originator_env)]
fn invalid_process_default_override_falls_back() {
let _guard = EnvVarGuard::set(CODEX_INTERNAL_ORIGINATOR_OVERRIDE_ENV_VAR, "bad\rvalue");
let originator = Originator::process_default();
assert_eq!(originator.value(), DEFAULT_ORIGINATOR);
}
#[test]
fn invalid_originator_values_are_rejected() {
assert_eq!(
Originator::for_process("bad\rvalue".to_string()),
Err(InvalidOriginator::InvalidHeaderValue)
);
assert_eq!(
Originator::from_app_server_client("bad\rvalue".to_string(), "1.2.3".to_string()),
Err(InvalidOriginator::InvalidHeaderValue)
);
}
#[test]
fn test_invalid_suffix_is_sanitized() {
let prefix = "codex_cli_rs/0.0.0";
@@ -115,11 +205,38 @@ fn test_invalid_suffix_is_sanitized2() {
#[cfg(target_os = "macos")]
fn test_macos() {
use regex_lite::Regex;
let user_agent = get_codex_user_agent();
let originator = regex_lite::escape(originator().value.as_str());
let originator = Originator::process_default();
let user_agent = get_codex_user_agent(&originator);
let originator = regex_lite::escape(originator.value());
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));
}
struct EnvVarGuard {
key: &'static str,
original: Option<std::ffi::OsString>,
}
impl EnvVarGuard {
fn set(key: &'static str, value: &str) -> Self {
let original = std::env::var_os(key);
unsafe {
std::env::set_var(key, value);
}
Self { key, original }
}
}
impl Drop for EnvVarGuard {
fn drop(&mut self) {
unsafe {
match &self.original {
Some(value) => std::env::set_var(self.key, value),
None => std::env::remove_var(self.key),
}
}
}
}

View File

@@ -31,6 +31,7 @@ pub use crate::auth::storage::AuthDotJson;
use crate::auth::storage::AuthStorageBackend;
use crate::auth::storage::create_auth_storage;
use crate::auth::util::try_parse_error_message;
use crate::default_client::Originator;
use crate::default_client::build_reqwest_client;
use crate::default_client::create_client;
use crate::token_data::TokenData;
@@ -203,7 +204,8 @@ impl CodexAuth {
chatgpt_base_url: Option<&str>,
) -> std::io::Result<Self> {
let auth_mode = auth_dot_json.resolved_mode();
let client = create_client();
let originator = Originator::process_default();
let client = create_client(&originator);
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."));
@@ -423,7 +425,8 @@ impl CodexAuth {
agent_identity: None,
};
let client = create_client();
let originator = Originator::process_default();
let client = create_client(&originator);
let state = ChatgptAuthState {
auth_dot_json: Arc::new(Mutex::new(Some(auth_dot_json))),
client,
@@ -493,7 +496,8 @@ async fn verified_agent_identity_record(
chatgpt_base_url: &str,
) -> std::io::Result<AgentIdentityAuthRecord> {
AgentIdentityAuthRecord::from_agent_identity_jwt(jwt)?;
let jwks = fetch_agent_identity_jwks(&build_reqwest_client(), chatgpt_base_url)
let originator = Originator::process_default();
let jwks = fetch_agent_identity_jwks(&build_reqwest_client(&originator), chatgpt_base_url)
.await
.map_err(std::io::Error::other)?;
let claims = decode_agent_identity_jwt(jwt, Some(&jwks)).map_err(std::io::Error::other)?;

View File

@@ -17,6 +17,7 @@ use super::manager::REVOKE_TOKEN_URL;
use super::manager::REVOKE_TOKEN_URL_OVERRIDE_ENV_VAR;
use super::storage::AuthDotJson;
use super::util::try_parse_error_message;
use crate::default_client::Originator;
use crate::default_client::create_client;
use crate::token_data::TokenData;
@@ -59,7 +60,8 @@ pub(crate) async fn revoke_auth_tokens(
return Ok(());
};
let client = create_client();
let originator = Originator::process_default();
let client = create_client(&originator);
let endpoint = revoke_token_endpoint();
revoke_oauth_token(&client, endpoint.as_str(), token, kind, REVOKE_HTTP_TIMEOUT).await
}

View File

@@ -29,7 +29,7 @@ use crate::auth::load_auth_dot_json;
use crate::auth::revoke_auth_tokens;
use crate::auth::save_auth;
use crate::auth::should_revoke_auth_tokens;
use crate::default_client::originator;
use crate::default_client::Originator;
use crate::pkce::PkceCodes;
use crate::pkce::generate_pkce;
use crate::token_data::TokenData;
@@ -505,7 +505,10 @@ fn build_authorize_url(
("id_token_add_organizations".to_string(), "true".to_string()),
("codex_cli_simplified_flow".to_string(), "true".to_string()),
("state".to_string(), state.to_string()),
("originator".to_string(), originator().value),
(
"originator".to_string(),
Originator::process_default().value().to_string(),
),
];
if let Some(workspace_ids) = forced_chatgpt_workspace_ids {
query.push(("allowed_workspace_id".to_string(), workspace_ids.join(",")));

View File

@@ -8,7 +8,7 @@ use codex_core::config::Config;
use codex_exec_server::EnvironmentManager;
use codex_extension_api::empty_extension_registry;
use codex_login::AuthManager;
use codex_login::default_client::USER_AGENT_SUFFIX;
use codex_login::default_client::Originator;
use codex_login::default_client::get_codex_user_agent;
use codex_protocol::ThreadId;
use codex_protocol::protocol::SessionSource;
@@ -210,14 +210,6 @@ impl MessageProcessor {
return;
}
let client_info = params.client_info;
let name = client_info.name;
let version = client_info.version;
let user_agent_suffix = format!("{name}; {version}");
if let Ok(mut suffix) = USER_AGENT_SUFFIX.lock() {
*suffix = Some(user_agent_suffix);
}
let server_info = Implementation {
name: "codex-mcp-server".to_string(),
title: Some("Codex".to_string()),
@@ -244,7 +236,11 @@ impl MessageProcessor {
}
};
if let serde_json::Value::Object(ref mut obj) = server_info_value {
obj.insert("user_agent".to_string(), json!(get_codex_user_agent()));
let originator = Originator::process_default();
obj.insert(
"user_agent".to_string(),
json!(get_codex_user_agent(&originator)),
);
}
let mut result_value = match serde_json::to_value(InitializeResult {

View File

@@ -11,7 +11,6 @@ use tokio::process::ChildStdout;
use anyhow::Context;
use codex_mcp_server::CodexToolCallParam;
use codex_terminal_detection::user_agent;
use pretty_assertions::assert_eq;
use rmcp::model::CallToolRequestParams;
@@ -149,16 +148,8 @@ impl McpProcess {
.await?;
let initialized = self.read_jsonrpc_message().await?;
let os_info = os_info::get();
let build_version = env!("CARGO_PKG_VERSION");
let originator = codex_login::default_client::originator().value;
let user_agent = format!(
"{originator}/{build_version} ({} {}; {}) {} (elicitation test; 0.0.0)",
os_info.os_type(),
os_info.version(),
os_info.architecture().unwrap_or("unknown"),
user_agent()
);
let originator = codex_login::default_client::Originator::process_default();
let user_agent = codex_login::default_client::get_codex_user_agent(&originator);
let JsonRpcMessage::Response(JsonRpcResponse {
jsonrpc,
id,

View File

@@ -12,7 +12,7 @@ use codex_features::Feature;
use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_login::auth_env_telemetry::collect_auth_env_telemetry;
use codex_login::default_client::originator;
use codex_login::default_client::Originator;
use codex_otel::SessionTelemetry;
use codex_otel::TelemetryAuthMode;
use codex_protocol::SessionId;
@@ -86,6 +86,7 @@ impl MemoryStartupContext {
let account_id = auth.and_then(CodexAuth::get_account_id);
let account_email = auth.and_then(CodexAuth::get_account_email);
let model = config.model.as_deref().unwrap_or("unknown");
let originator = Originator::process_default();
let auth_env_telemetry = collect_auth_env_telemetry(
&config.model_provider,
auth_manager.codex_api_key_env_enabled(),
@@ -97,7 +98,7 @@ impl MemoryStartupContext {
account_id,
account_email,
auth_mode,
originator().value,
originator.value().to_string(),
config.otel.log_user_prompt,
user_agent(),
source,

View File

@@ -14,6 +14,7 @@ use codex_login::AuthEnvTelemetry;
use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_login::collect_auth_env_telemetry;
use codex_login::default_client::Originator;
use codex_login::default_client::build_reqwest_client;
use codex_model_provider_info::ModelProviderInfo;
use codex_models_manager::manager::ModelsEndpointClient;
@@ -88,7 +89,8 @@ impl ModelsEndpointClient for OpenAiModelsEndpoint {
let auth_mode = auth.as_ref().map(CodexAuth::auth_mode);
let api_provider = self.provider_info.to_api_provider(auth_mode)?;
let api_auth = resolve_provider_auth(auth.as_ref(), &self.provider_info)?;
let transport = ReqwestTransport::new(build_reqwest_client());
let originator = Originator::process_default();
let transport = ReqwestTransport::new(build_reqwest_client(&originator));
let auth_telemetry = auth_header_telemetry(api_auth.as_ref());
let request_telemetry: Arc<dyn RequestTelemetry> = Arc::new(ModelsRequestTelemetry {
auth_mode: auth_mode.map(|mode| TelemetryAuthMode::from(mode).to_string()),

View File

@@ -44,7 +44,7 @@ use super::list::parse_timestamp_uuid_from_filename;
use super::metadata;
use super::session_index::find_thread_names_by_ids;
use crate::config::RolloutConfigView;
use crate::default_client::originator;
use crate::default_client::Originator;
use crate::state_db;
use crate::state_db::StateDbHandle;
use codex_git_utils::collect_git_info;
@@ -675,7 +675,7 @@ impl RolloutRecorder {
forked_from_id,
timestamp,
cwd: config.cwd().to_path_buf(),
originator: originator().value,
originator: Originator::process_default().value().to_string(),
cli_version: env!("CARGO_PKG_VERSION").to_string(),
agent_nickname: source.get_nickname(),
agent_role: source.get_agent_role(),

View File

@@ -59,7 +59,6 @@ use codex_core_api::find_codex_home;
use codex_core_api::init_state_db;
use codex_core_api::item_event_to_server_notification;
use codex_core_api::resolve_installation_id;
use codex_core_api::set_default_originator;
use codex_core_api::thread_store_from_config;
#[derive(Debug, Parser)]
@@ -82,10 +81,6 @@ fn main() -> anyhow::Result<()> {
}
async fn run_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
if let Err(err) = set_default_originator("codex_thread_manager_sample".to_string()) {
tracing::warn!("failed to set originator: {err:?}");
}
let args = Args::parse();
let prompt = if args.prompt.is_empty() {
if std::io::stdin().is_terminal() {

View File

@@ -744,7 +744,9 @@ impl App {
/*account_id*/ None,
bootstrap.account_email.clone(),
auth_mode,
codex_login::default_client::originator().value,
codex_login::default_client::Originator::process_default()
.value()
.to_string(),
config.otel.log_user_prompt,
user_agent(),
serde_json::from_value(serde_json::json!("cli"))

View File

@@ -44,7 +44,7 @@ use codex_config::format_config_error_with_source;
use codex_exec_server::EnvironmentManager;
use codex_exec_server::ExecServerRuntimePaths;
use codex_login::AuthConfig;
use codex_login::default_client::originator;
use codex_login::default_client::Originator;
use codex_login::default_client::set_default_client_residency_requirement;
use codex_login::enforce_login_restrictions;
use codex_protocol::ThreadId;
@@ -977,7 +977,7 @@ pub async fn run_main(
)
.await;
let otel_originator = originator().value;
let otel_originator = Originator::process_default().value().to_string();
let otel = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
crate::legacy_core::otel_init::build_provider(
&config,

View File

@@ -11,6 +11,7 @@ use crate::update_versions::is_source_build_version;
use chrono::DateTime;
use chrono::Duration;
use chrono::Utc;
use codex_login::default_client::Originator;
use codex_login::default_client::create_client;
use serde::Deserialize;
use serde::Serialize;
@@ -85,9 +86,10 @@ fn read_version_info(version_file: &Path) -> anyhow::Result<VersionInfo> {
}
async fn check_for_update(version_file: &Path, action: Option<UpdateAction>) -> anyhow::Result<()> {
let originator = Originator::process_default();
let latest_version = match action {
Some(UpdateAction::BrewUpgrade) => {
let HomebrewCaskInfo { version } = create_client()
let HomebrewCaskInfo { version } = create_client(&originator)
.get(HOMEBREW_CASK_API_URL)
.send()
.await?
@@ -98,7 +100,7 @@ async fn check_for_update(version_file: &Path, action: Option<UpdateAction>) ->
}
Some(UpdateAction::NpmGlobalLatest) | Some(UpdateAction::BunGlobalLatest) => {
let latest_version = fetch_latest_github_release_version().await?;
let package_info = create_client()
let package_info = create_client(&originator)
.get(npm_registry::PACKAGE_URL)
.send()
.await?
@@ -130,9 +132,10 @@ async fn check_for_update(version_file: &Path, action: Option<UpdateAction>) ->
}
async fn fetch_latest_github_release_version() -> anyhow::Result<String> {
let originator = Originator::process_default();
let ReleaseInfo {
tag_name: latest_tag_name,
} = create_client()
} = create_client(&originator)
.get(LATEST_RELEASE_URL)
.send()
.await?

View File

@@ -1,5 +1,5 @@
use codex_login::default_client::Originator;
use codex_login::default_client::is_first_party_chat_originator;
use codex_login::default_client::originator;
const DISALLOWED_CONNECTOR_IDS: &[&str] = &[
"asdk_app_6938a94a61d881918ef32cb999ff937c",
@@ -13,7 +13,8 @@ const FIRST_PARTY_CHAT_DISALLOWED_CONNECTOR_IDS: &[&str] =
&["connector_0f9c9d4592e54d0a9a12b3f44a1e2010"];
pub fn is_connector_id_allowed(connector_id: &str) -> bool {
is_connector_id_allowed_for_originator(connector_id, originator().value.as_str())
let originator = Originator::process_default();
is_connector_id_allowed_for_originator(connector_id, originator.value())
}
fn is_connector_id_allowed_for_originator(connector_id: &str, originator_value: &str) -> bool {