Compare commits

...

1 Commits

Author SHA1 Message Date
Mark Steinbrick
9c8bf8bea4 [codex-analytics] include user agent in default headers 2026-04-14 14:15:04 -07:00
2 changed files with 36 additions and 7 deletions

View File

@@ -53,6 +53,7 @@ use tracing_test::traced_test;
const MODEL: &str = "gpt-5.2-codex";
const OPENAI_BETA_HEADER: &str = "OpenAI-Beta";
const USER_AGENT_HEADER: &str = "user-agent";
const WS_V2_BETA_HEADER_VALUE: &str = "responses_websockets=2026-02-06";
const X_CLIENT_REQUEST_ID_HEADER: &str = "x-client-request-id";
const TEST_INSTALLATION_ID: &str = "11111111-1111-4111-8111-111111111111";
@@ -126,6 +127,10 @@ async fn responses_websocket_streams_request() {
handshake.header(X_CLIENT_REQUEST_ID_HEADER),
Some(harness.conversation_id.to_string())
);
assert_eq!(
handshake.header(USER_AGENT_HEADER),
Some(codex_login::default_client::get_codex_user_agent())
);
assert_eq!(
body["client_metadata"]["x-codex-installation-id"].as_str(),
Some(TEST_INSTALLATION_ID)
@@ -197,6 +202,10 @@ 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())
);
let connection = server.single_connection();
assert_eq!(connection.len(), 2);
@@ -282,7 +291,12 @@ async fn responses_websocket_preconnect_reuses_connection() {
stream_until_complete(&mut client_session, &harness, &prompt).await;
assert_eq!(server.handshakes().len(), 1);
assert_eq!(server.single_connection().len(), 1);
assert_eq!(
server.single_handshake().header(USER_AGENT_HEADER),
Some(codex_login::default_client::get_codex_user_agent())
);
let connection = server.single_connection();
assert_eq!(connection.len(), 1);
server.shutdown().await;
}
@@ -315,6 +329,10 @@ async fn responses_websocket_request_prewarm_reuses_connection() {
stream_until_complete(&mut client_session, &harness, &prompt).await;
assert_eq!(server.handshakes().len(), 1);
assert_eq!(
server.single_handshake().header(USER_AGENT_HEADER),
Some(codex_login::default_client::get_codex_user_agent())
);
let connection = server.single_connection();
assert_eq!(connection.len(), 2);
let warmup = connection
@@ -1144,6 +1162,18 @@ async fn responses_websocket_connection_limit_error_reconnects_and_completes() {
let total_websocket_requests: usize = server.connections().iter().map(Vec::len).sum();
assert_eq!(total_websocket_requests, 2);
let handshake_user_agents: Vec<_> = server
.handshakes()
.iter()
.map(|handshake| handshake.header(USER_AGENT_HEADER))
.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()),
]
);
server.shutdown().await;
}

View File

@@ -11,6 +11,7 @@ use codex_client::build_reqwest_client_with_custom_ca;
use codex_terminal_detection::user_agent;
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;
@@ -210,12 +211,7 @@ 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 ua = get_codex_user_agent();
let mut builder = reqwest::Client::builder()
// Set UA via dedicated helper to avoid header validation pitfalls
.user_agent(ua)
.default_headers(default_headers());
let mut builder = reqwest::Client::builder().default_headers(default_headers());
if is_sandboxed() {
builder = builder.no_proxy();
}
@@ -226,6 +222,9 @@ pub fn try_build_reqwest_client() -> Result<reqwest::Client, BuildCustomCaTransp
pub fn default_headers() -> HeaderMap {
let mut headers = HeaderMap::new();
headers.insert("originator", originator().header_value);
if let Ok(user_agent) = HeaderValue::from_str(&get_codex_user_agent()) {
headers.insert(USER_AGENT, user_agent);
}
if let Ok(guard) = REQUIREMENTS_RESIDENCY.read()
&& let Some(requirement) = guard.as_ref()
&& !headers.contains_key(RESIDENCY_HEADER_NAME)