Forward thread originators from standalone extensions (#33677)

## Why

Standalone web search and image requests need to preserve the trusted,
thread-scoped originator used for billing attribution.

## What changed

- Seed extension data with the resolved thread originator and recognize
  `codex_work_cca` and `chatgpt_cca` service names as originators.
- Forward non-default originators from the web search and image generation
  extensions with the shared originator-header helper.

## Testing

- Cover originator resolution, header validation, and forwarding for web
  search, image generation, and image editing.

GitOrigin-RevId: 165dace8e4d24e4bd92f234bff5ec77a21a7ff6e
This commit is contained in:
charleschen-oai
2026-07-16 19:31:26 +00:00
committed by copyberry
parent 71448a29e7
commit 78ba047bda
16 changed files with 248 additions and 44 deletions
+1 -16
View File
@@ -69,6 +69,7 @@ use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_login::RefreshTokenError;
use codex_login::UnauthorizedRecovery;
use codex_login::default_client::add_originator_header;
use codex_login::default_client::build_default_reqwest_client_for_route;
use codex_otel::SessionTelemetry;
use codex_otel::current_span_w3c_trace_context;
@@ -1884,22 +1885,6 @@ fn build_responses_headers(
headers
}
pub(crate) fn add_originator_header(headers: &mut ApiHeaderMap, originator: &str) {
let default_originator = codex_login::default_client::originator();
if originator == default_originator.value.as_str() {
return;
}
match HeaderValue::from_str(originator) {
Ok(header_value) => {
headers.insert("originator", header_value);
}
Err(err) => {
warn!("ignoring invalid thread originator header value: {err}");
}
}
}
fn add_responses_lite_header(headers: &mut ApiHeaderMap, use_responses_lite: bool) {
if use_responses_lite {
headers.insert(
+1 -1
View File
@@ -1,5 +1,4 @@
use crate::client::ModelClient;
use crate::client::add_originator_header;
use crate::realtime_context::build_realtime_startup_context;
use crate::realtime_context::truncate_realtime_text_to_token_budget;
use crate::realtime_prompt::prepare_realtime_backend_prompt;
@@ -25,6 +24,7 @@ use codex_api::map_api_error;
use codex_config::config_toml::RealtimeWsMode;
use codex_config::config_toml::RealtimeWsVersion;
use codex_login::CodexAuth;
use codex_login::default_client::add_originator_header;
use codex_login::default_client::default_headers;
use codex_login::read_openai_api_key_from_env;
use codex_model_provider_info::ModelProviderInfo;
+3
View File
@@ -583,6 +583,9 @@ impl Session {
roots
}
};
thread_extension_init.insert(codex_extension_api::ThreadOriginator(
session_configuration.originator.clone(),
));
let mcp_thread_init = thread_extension_init.clone();
let thread_extension_data = codex_extension_api::ExtensionData::new_with_init(
thread_id.to_string(),
+7 -1
View File
@@ -204,7 +204,13 @@ pub struct StartThreadOptions {
fn originator_from_service_name(service_name: Option<&str>) -> Option<String> {
let service_name = service_name?.trim();
for originator in ["codex_work_desktop", "codex_work_web", "codex_work_mobile"] {
for originator in [
"codex_work_desktop",
"codex_work_web",
"codex_work_mobile",
"codex_work_cca",
"chatgpt_cca",
] {
if service_name.eq_ignore_ascii_case(originator) {
return Some(originator.to_string());
}
+18
View File
@@ -135,6 +135,24 @@ fn effective_originator_prefers_thread_scoped_sources_before_env_originator() {
Some("inherited_originator"),
"codex_work_mobile",
),
(
Some("codex_work_cca"),
Some("persisted_originator"),
Some("inherited_originator"),
"codex_work_cca",
),
(
Some("chatgpt_cca"),
Some("persisted_originator"),
Some("inherited_originator"),
"chatgpt_cca",
),
(
Some("chatgpt_cca_extra"),
Some("persisted_originator"),
Some("inherited_originator"),
"persisted_originator",
),
(
None,
Some("persisted_originator"),