add WebSearchMode enum (#9216)

### What
Add `WebSearchMode` enum (disabled, cached live, defaults to cached) to
config + V2 protocol. This enum takes precedence over legacy flags:
`web_search_cached`, `web_search_request`, and `tools.web_search`.

Keep `--search` as live.

### Tests
Added tests
This commit is contained in:
sayan-oai
2026-01-14 12:51:42 -08:00
committed by GitHub
parent 27da8a68d3
commit 5e426ac270
22 changed files with 302 additions and 72 deletions

View File

@@ -1,5 +1,6 @@
#![allow(clippy::unwrap_used)]
use codex_protocol::config_types::WebSearchMode;
use core_test_support::load_sse_fixture_with_id;
use core_test_support::responses;
use core_test_support::responses::start_mock_server;
@@ -32,7 +33,10 @@ async fn collect_tool_identifiers_for_model(model: &str) -> Vec<String> {
let sse = sse_completed(model);
let resp_mock = responses::mount_sse_once(&server, sse).await;
let mut builder = test_codex().with_model(model);
let mut builder = test_codex()
.with_model(model)
// Keep tool expectations stable when the default web_search mode changes.
.with_config(|config| config.web_search_mode = WebSearchMode::Cached);
let test = builder
.build(&server)
.await
@@ -58,6 +62,7 @@ async fn model_selects_expected_tools() {
"list_mcp_resource_templates".to_string(),
"read_mcp_resource".to_string(),
"update_plan".to_string(),
"web_search".to_string(),
"view_image".to_string()
],
"codex-mini-latest should expose the local shell tool",
@@ -73,6 +78,7 @@ async fn model_selects_expected_tools() {
"read_mcp_resource".to_string(),
"update_plan".to_string(),
"apply_patch".to_string(),
"web_search".to_string(),
"view_image".to_string()
],
"gpt-5-codex should expose the apply_patch tool",
@@ -88,6 +94,7 @@ async fn model_selects_expected_tools() {
"read_mcp_resource".to_string(),
"update_plan".to_string(),
"apply_patch".to_string(),
"web_search".to_string(),
"view_image".to_string()
],
"gpt-5.1-codex should expose the apply_patch tool",
@@ -102,6 +109,7 @@ async fn model_selects_expected_tools() {
"list_mcp_resource_templates".to_string(),
"read_mcp_resource".to_string(),
"update_plan".to_string(),
"web_search".to_string(),
"view_image".to_string()
],
"gpt-5 should expose the apply_patch tool",
@@ -117,6 +125,7 @@ async fn model_selects_expected_tools() {
"read_mcp_resource".to_string(),
"update_plan".to_string(),
"apply_patch".to_string(),
"web_search".to_string(),
"view_image".to_string()
],
"gpt-5.1 should expose the apply_patch tool",
@@ -132,6 +141,7 @@ async fn model_selects_expected_tools() {
"read_mcp_resource".to_string(),
"update_plan".to_string(),
"apply_patch".to_string(),
"web_search".to_string(),
"view_image".to_string()
],
"exp-5.1 should expose the apply_patch tool",

View File

@@ -11,6 +11,7 @@ use codex_core::protocol::SandboxPolicy;
use codex_core::protocol_config_types::ReasoningSummary;
use codex_core::shell::Shell;
use codex_core::shell::default_user_shell;
use codex_protocol::config_types::WebSearchMode;
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::user_input::UserInput;
use codex_utils_absolute_path::AbsolutePathBuf;
@@ -52,7 +53,13 @@ fn assert_tool_names(body: &serde_json::Value, expected_names: &[&str]) {
.as_array()
.unwrap()
.iter()
.map(|t| t["name"].as_str().unwrap().to_string())
.map(|t| {
t.get("name")
.and_then(|value| value.as_str())
.or_else(|| t.get("type").and_then(|value| value.as_str()))
.unwrap()
.to_string()
})
.collect::<Vec<_>>(),
expected_names
);
@@ -80,6 +87,8 @@ async fn prompt_tools_are_consistent_across_requests() -> anyhow::Result<()> {
.with_config(|config| {
config.user_instructions = Some("be consistent and helpful".to_string());
config.model = Some("gpt-5.1-codex-max".to_string());
// Keep tool expectations stable when the default web_search mode changes.
config.web_search_mode = WebSearchMode::Cached;
})
.build(&server)
.await?;
@@ -122,6 +131,7 @@ async fn prompt_tools_are_consistent_across_requests() -> anyhow::Result<()> {
"read_mcp_resource",
"update_plan",
"apply_patch",
"web_search",
"view_image",
];
let body0 = req1.single_request().body_json();

View File

@@ -1,6 +1,7 @@
#![allow(clippy::unwrap_used)]
use codex_core::features::Feature;
use codex_protocol::config_types::WebSearchMode;
use core_test_support::load_sse_fixture_with_id;
use core_test_support::responses;
use core_test_support::responses::start_mock_server;
@@ -24,7 +25,7 @@ fn find_web_search_tool(body: &Value) -> &Value {
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn web_search_cached_sets_external_web_access_false_in_request_body() {
async fn web_search_mode_cached_sets_external_web_access_false_in_request_body() {
skip_if_no_network!();
let server = start_mock_server().await;
@@ -34,7 +35,7 @@ async fn web_search_cached_sets_external_web_access_false_in_request_body() {
let mut builder = test_codex()
.with_model("gpt-5-codex")
.with_config(|config| {
config.features.enable(Feature::WebSearchCached);
config.web_search_mode = WebSearchMode::Cached;
});
let test = builder
.build(&server)
@@ -50,12 +51,12 @@ async fn web_search_cached_sets_external_web_access_false_in_request_body() {
assert_eq!(
tool.get("external_web_access").and_then(Value::as_bool),
Some(false),
"web_search_cached should force external_web_access=false"
"web_search cached mode should force external_web_access=false"
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn web_search_cached_takes_precedence_over_web_search_request_in_request_body() {
async fn web_search_mode_takes_precedence_over_legacy_flags_in_request_body() {
skip_if_no_network!();
let server = start_mock_server().await;
@@ -66,7 +67,7 @@ async fn web_search_cached_takes_precedence_over_web_search_request_in_request_b
.with_model("gpt-5-codex")
.with_config(|config| {
config.features.enable(Feature::WebSearchRequest);
config.features.enable(Feature::WebSearchCached);
config.web_search_mode = WebSearchMode::Cached;
});
let test = builder
.build(&server)
@@ -82,6 +83,6 @@ async fn web_search_cached_takes_precedence_over_web_search_request_in_request_b
assert_eq!(
tool.get("external_web_access").and_then(Value::as_bool),
Some(false),
"web_search_cached should win over web_search_request"
"web_search mode should win over legacy web_search_request"
);
}