chore: rm web-search-eligible header (#10660)

default-enablement of web_search is now client-side, no need to send
eligibility headers to backend.

Tested locally, headers no longer sent.

will wait for corresponding backend change to deploy before merging
This commit is contained in:
sayan-oai 2026-02-05 11:48:34 -08:00 committed by GitHub
parent 901d5b8fd6
commit 5fdf6f5efa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 12 additions and 144 deletions

View file

@ -4,9 +4,9 @@
//! configuration and state needed to talk to a provider (auth, provider selection, conversation id,
//! and feature-gated request behavior).
//!
//! Per-turn settings (model selection, reasoning controls, telemetry context, web search
//! eligibility, and turn metadata) are passed explicitly to streaming and unary methods so that the
//! turn lifetime is visible at the call site.
//! Per-turn settings (model selection, reasoning controls, telemetry context, and turn metadata)
//! are passed explicitly to streaming and unary methods so that the turn lifetime is visible at the
//! call site.
//!
//! A [`ModelClientSession`] is created per turn and is used to stream one or more Responses API
//! requests during that turn. It caches a Responses WebSocket connection (opened lazily) and
@ -82,7 +82,6 @@ use crate::model_provider_info::ModelProviderInfo;
use crate::model_provider_info::WireApi;
use crate::tools::spec::create_tools_json_for_responses_api;
pub const WEB_SEARCH_ELIGIBLE_HEADER: &str = "x-oai-web-search-eligible";
pub const X_CODEX_TURN_STATE_HEADER: &str = "x-codex-turn-state";
pub const X_CODEX_TURN_METADATA_HEADER: &str = "x-codex-turn-metadata";
pub const X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER: &str =
@ -115,9 +114,8 @@ struct ModelClientState {
/// WebSocket fallback is session-scoped: once a turn activates the HTTP fallback, subsequent turns
/// will also use HTTP for the remainder of the session.
///
/// Turn-scoped settings (model selection, reasoning controls, telemetry context, web search
/// eligibility, and turn metadata) are passed explicitly to the relevant methods to keep turn
/// lifetime visible at the call site.
/// Turn-scoped settings (model selection, reasoning controls, telemetry context, and turn metadata)
/// are passed explicitly to the relevant methods to keep turn lifetime visible at the call site.
///
/// This type is cheap to clone.
#[derive(Debug, Clone)]
@ -349,7 +347,6 @@ impl ModelClientSession {
model_info: &ModelInfo,
effort: Option<ReasoningEffortConfig>,
summary: ReasoningSummaryConfig,
web_search_eligible: bool,
turn_metadata_header: Option<&str>,
compression: Compression,
) -> ApiResponsesOptions {
@ -404,7 +401,6 @@ impl ModelClientSession {
session_source: Some(self.client.state.session_source.clone()),
extra_headers: build_responses_headers(
self.client.state.beta_features_header.as_deref(),
web_search_eligible,
Some(&self.turn_state),
turn_metadata_header.as_ref(),
),
@ -529,7 +525,6 @@ impl ModelClientSession {
otel_manager: &OtelManager,
effort: Option<ReasoningEffortConfig>,
summary: ReasoningSummaryConfig,
web_search_eligible: bool,
turn_metadata_header: Option<&str>,
) -> Result<ResponseStream> {
if let Some(path) = &*CODEX_RS_SSE_FIXTURE {
@ -571,7 +566,6 @@ impl ModelClientSession {
model_info,
effort,
summary,
web_search_eligible,
turn_metadata_header,
compression,
);
@ -604,7 +598,6 @@ impl ModelClientSession {
otel_manager: &OtelManager,
effort: Option<ReasoningEffortConfig>,
summary: ReasoningSummaryConfig,
web_search_eligible: bool,
turn_metadata_header: Option<&str>,
) -> Result<ResponseStream> {
let auth_manager = self.client.state.auth_manager.clone();
@ -631,7 +624,6 @@ impl ModelClientSession {
model_info,
effort,
summary,
web_search_eligible,
turn_metadata_header,
compression,
);
@ -687,9 +679,9 @@ impl ModelClientSession {
/// Streams a single model request within the current turn.
///
/// The caller is responsible for passing per-turn settings explicitly (model selection,
/// reasoning settings, telemetry context, web search eligibility, and turn metadata). This
/// method will prefer the Responses WebSocket transport when enabled and healthy, and will
/// fall back to the HTTP Responses API transport otherwise.
/// reasoning settings, telemetry context, and turn metadata). This method will prefer the
/// Responses WebSocket transport when enabled and healthy, and will fall back to the HTTP
/// Responses API transport otherwise.
pub async fn stream(
&mut self,
prompt: &Prompt,
@ -697,7 +689,6 @@ impl ModelClientSession {
otel_manager: &OtelManager,
effort: Option<ReasoningEffortConfig>,
summary: ReasoningSummaryConfig,
web_search_eligible: bool,
turn_metadata_header: Option<&str>,
) -> Result<ResponseStream> {
let wire_api = self.client.state.provider.wire_api;
@ -713,7 +704,6 @@ impl ModelClientSession {
otel_manager,
effort,
summary,
web_search_eligible,
turn_metadata_header,
)
.await
@ -724,7 +714,6 @@ impl ModelClientSession {
otel_manager,
effort,
summary,
web_search_eligible,
turn_metadata_header,
)
.await
@ -772,12 +761,10 @@ fn build_api_prompt(prompt: &Prompt, instructions: String, tools_json: Vec<Value
/// These headers implement Codex-specific conventions:
///
/// - `x-codex-beta-features`: comma-separated beta feature keys enabled for the session.
/// - `x-oai-web-search-eligible`: whether this turn is allowed to use web search.
/// - `x-codex-turn-state`: sticky routing token captured earlier in the turn.
/// - `x-codex-turn-metadata`: optional per-turn metadata for observability.
fn build_responses_headers(
beta_features_header: Option<&str>,
web_search_eligible: bool,
turn_state: Option<&Arc<OnceLock<String>>>,
turn_metadata_header: Option<&HeaderValue>,
) -> ApiHeaderMap {
@ -788,10 +775,6 @@ fn build_responses_headers(
{
headers.insert("x-codex-beta-features", header_value);
}
headers.insert(
WEB_SEARCH_ELIGIBLE_HEADER,
HeaderValue::from_static(if web_search_eligible { "true" } else { "false" }),
);
if let Some(turn_state) = turn_state
&& let Some(state) = turn_state.get()
&& let Ok(header_value) = HeaderValue::from_str(state)

View file

@ -4525,10 +4525,6 @@ async fn try_run_sampling_request(
);
sess.persist_rollout_items(&[rollout_item]).await;
let web_search_eligible = !matches!(
turn_context.config.web_search_mode,
Some(WebSearchMode::Disabled)
);
let mut stream = client_session
.stream(
prompt,
@ -4536,7 +4532,6 @@ async fn try_run_sampling_request(
&turn_context.otel_manager,
turn_context.reasoning_effort,
turn_context.reasoning_summary,
web_search_eligible,
turn_metadata_header,
)
.instrument(trace_span!("stream_request"))

View file

@ -20,7 +20,6 @@ use crate::truncate::TruncationPolicy;
use crate::truncate::approx_token_count;
use crate::truncate::truncate_text;
use crate::util::backoff;
use codex_protocol::config_types::WebSearchMode;
use codex_protocol::items::ContextCompactionItem;
use codex_protocol::items::TurnItem;
use codex_protocol::models::ContentItem;
@ -352,10 +351,6 @@ async fn drain_to_completed(
turn_metadata_header: Option<&str>,
prompt: &Prompt,
) -> CodexResult<()> {
let web_search_eligible = !matches!(
turn_context.config.web_search_mode,
Some(WebSearchMode::Disabled)
);
let mut stream = client_session
.stream(
prompt,
@ -363,7 +358,6 @@ async fn drain_to_completed(
&turn_context.otel_manager,
turn_context.reasoning_effort,
turn_context.reasoning_summary,
web_search_eligible,
turn_metadata_header,
)
.await?;

View file

@ -130,7 +130,6 @@ mod user_shell_command;
pub mod util;
pub use apply_patch::CODEX_APPLY_PATCH_ARG1;
pub use client::WEB_SEARCH_ELIGIBLE_HEADER;
pub use client::X_CODEX_TURN_METADATA_HEADER;
pub use command_safety::is_dangerous_command;
pub use command_safety::is_safe_command;

View file

@ -9,14 +9,12 @@ use codex_core::ModelProviderInfo;
use codex_core::Prompt;
use codex_core::ResponseEvent;
use codex_core::ResponseItem;
use codex_core::WEB_SEARCH_ELIGIBLE_HEADER;
use codex_core::WireApi;
use codex_core::models_manager::manager::ModelsManager;
use codex_otel::OtelManager;
use codex_otel::TelemetryAuthMode;
use codex_protocol::ThreadId;
use codex_protocol::config_types::ReasoningSummary;
use codex_protocol::config_types::WebSearchMode;
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::SubAgentSource;
use core_test_support::load_default_config_for_test;
@ -87,7 +85,6 @@ async fn responses_stream_includes_subagent_header_on_review() {
session_source.clone(),
);
let web_search_eligible = !matches!(config.web_search_mode, Some(WebSearchMode::Disabled));
let client = ModelClient::new(
None,
conversation_id,
@ -113,15 +110,7 @@ async fn responses_stream_includes_subagent_header_on_review() {
}];
let mut stream = client_session
.stream(
&prompt,
&model_info,
&otel_manager,
effort,
summary,
web_search_eligible,
None,
)
.stream(&prompt, &model_info, &otel_manager, effort, summary, None)
.await
.expect("stream failed");
while let Some(event) = stream.next().await {
@ -198,7 +187,6 @@ async fn responses_stream_includes_subagent_header_on_other() {
session_source.clone(),
);
let web_search_eligible = !matches!(config.web_search_mode, Some(WebSearchMode::Disabled));
let client = ModelClient::new(
None,
conversation_id,
@ -224,15 +212,7 @@ async fn responses_stream_includes_subagent_header_on_other() {
}];
let mut stream = client_session
.stream(
&prompt,
&model_info,
&otel_manager,
effort,
summary,
web_search_eligible,
None,
)
.stream(&prompt, &model_info, &otel_manager, effort, summary, None)
.await
.expect("stream failed");
while let Some(event) = stream.next().await {
@ -248,66 +228,6 @@ async fn responses_stream_includes_subagent_header_on_other() {
);
}
#[tokio::test]
async fn responses_stream_includes_web_search_eligible_header_true_by_default() {
core_test_support::skip_if_no_network!();
let server = responses::start_mock_server().await;
let response_body = responses::sse(vec![
responses::ev_response_created("resp-1"),
responses::ev_completed("resp-1"),
]);
let request_recorder = responses::mount_sse_once_match(
&server,
header(WEB_SEARCH_ELIGIBLE_HEADER, "true"),
response_body,
)
.await;
let test = test_codex().build(&server).await.expect("build test codex");
test.submit_turn("hello").await.expect("submit test prompt");
let request = request_recorder.single_request();
assert_eq!(
request.header(WEB_SEARCH_ELIGIBLE_HEADER).as_deref(),
Some("true")
);
}
#[tokio::test]
async fn responses_stream_includes_web_search_eligible_header_false_when_disabled() {
core_test_support::skip_if_no_network!();
let server = responses::start_mock_server().await;
let response_body = responses::sse(vec![
responses::ev_response_created("resp-1"),
responses::ev_completed("resp-1"),
]);
let request_recorder = responses::mount_sse_once_match(
&server,
header(WEB_SEARCH_ELIGIBLE_HEADER, "false"),
response_body,
)
.await;
let test = test_codex()
.with_config(|config| {
config.web_search_mode = Some(WebSearchMode::Disabled);
})
.build(&server)
.await
.expect("build test codex");
test.submit_turn("hello").await.expect("submit test prompt");
let request = request_recorder.single_request();
assert_eq!(
request.header(WEB_SEARCH_ELIGIBLE_HEADER).as_deref(),
Some("false")
);
}
#[tokio::test]
async fn responses_respects_model_info_overrides_from_config() {
core_test_support::skip_if_no_network!();
@ -368,7 +288,6 @@ async fn responses_respects_model_info_overrides_from_config() {
session_source.clone(),
);
let web_search_eligible = !matches!(config.web_search_mode, Some(WebSearchMode::Disabled));
let client = ModelClient::new(
None,
conversation_id,
@ -394,15 +313,7 @@ async fn responses_respects_model_info_overrides_from_config() {
}];
let mut stream = client_session
.stream(
&prompt,
&model_info,
&otel_manager,
effort,
summary,
web_search_eligible,
None,
)
.stream(&prompt, &model_info, &otel_manager, effort, summary, None)
.await
.expect("stream failed");
while let Some(event) = stream.next().await {

View file

@ -1340,15 +1340,7 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() {
});
let mut stream = client_session
.stream(
&prompt,
&model_info,
&otel_manager,
effort,
summary,
true,
None,
)
.stream(&prompt, &model_info, &otel_manager, effort, summary, None)
.await
.expect("responses stream to start");

View file

@ -47,7 +47,6 @@ struct WebsocketTestHarness {
model_info: ModelInfo,
effort: Option<ReasoningEffortConfig>,
summary: ReasoningSummary,
web_search_eligible: bool,
otel_manager: OtelManager,
}
@ -198,7 +197,6 @@ async fn responses_websocket_emits_reasoning_included_event() {
&harness.otel_manager,
harness.effort,
harness.summary,
harness.web_search_eligible,
None,
)
.await
@ -269,7 +267,6 @@ async fn responses_websocket_emits_rate_limit_events() {
&harness.otel_manager,
harness.effort,
harness.summary,
harness.web_search_eligible,
None,
)
.await
@ -455,7 +452,6 @@ async fn websocket_harness_with_runtime_metrics(
.with_metrics(metrics);
let effort = None;
let summary = ReasoningSummary::Auto;
let web_search_eligible = true;
let client = ModelClient::new(
None,
conversation_id,
@ -474,7 +470,6 @@ async fn websocket_harness_with_runtime_metrics(
model_info,
effort,
summary,
web_search_eligible,
otel_manager,
}
}
@ -491,7 +486,6 @@ async fn stream_until_complete(
&harness.otel_manager,
harness.effort,
harness.summary,
harness.web_search_eligible,
None,
)
.await