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 authored and copyberry committed 2026-07-16 19:31:26 +00:00
1 parent 71448a29e7
commit 78ba047bda
16 files changed
+248 -44

No files matched your search

+20
View File
@@ -127,6 +127,26 @@ pub fn originator() -> Originator {
get_originator_value(/*provided*/ None)
}
/// Adds a valid, non-default thread originator override to request headers.
///
/// The default client already supplies the process originator. Thread-scoped callers should use
/// this helper to override that value only when the thread originator differs.
pub fn add_originator_header(headers: &mut HeaderMap, originator_value: &str) {
let default_originator = originator();
if originator_value == default_originator.value.as_str() {
return;
}
match HeaderValue::from_str(originator_value) {
Ok(header_value) => {
headers.insert("originator", header_value);
}
Err(err) => {
tracing::warn!("ignoring invalid thread originator header value: {err}");
}
}
}
pub fn is_first_party_originator(originator_value: &str) -> bool {
originator_value == DEFAULT_ORIGINATOR
|| originator_value == "codex-tui"
@@ -67,6 +67,54 @@ fn is_first_party_chat_originator_matches_known_values() {
assert_eq!(is_first_party_chat_originator("codex_vscode"), false);
}
#[test]
fn add_originator_header_inserts_non_default_originator() {
let default_originator = originator();
let thread_originator = if default_originator.value == "chatgpt_cca" {
"codex_work_cca"
} else {
"chatgpt_cca"
};
let mut headers = HeaderMap::new();
add_originator_header(&mut headers, thread_originator);
assert_eq!(
headers
.get("originator")
.and_then(|value| value.to_str().ok()),
Some(thread_originator)
);
}
#[test]
fn add_originator_header_preserves_provider_default() {
let default_originator = originator();
let mut headers = HeaderMap::new();
headers.insert(
"originator",
HeaderValue::from_static("provider-originator"),
);
add_originator_header(&mut headers, &default_originator.value);
assert_eq!(
headers
.get("originator")
.and_then(|value| value.to_str().ok()),
Some("provider-originator")
);
}
#[test]
fn add_originator_header_omits_invalid_originator() {
let mut headers = HeaderMap::new();
add_originator_header(&mut headers, "invalid\noriginator");
assert!(headers.is_empty());
}
#[tokio::test]
async fn test_create_client_sets_default_headers() {
skip_if_no_network!();