Compare commits

...

3 Commits

Author SHA1 Message Date
Sayan Sisodiya
bcb5b8f703 fix 2026-03-04 19:12:11 -08:00
Sayan Sisodiya
3472be1138 update comment 2026-03-04 19:05:25 -08:00
Sayan Sisodiya
11a3fb1d0f fflag for web_search with images 2026-03-04 19:05:11 -08:00
5 changed files with 113 additions and 0 deletions

View File

@@ -461,6 +461,9 @@
"web_search_cached": {
"type": "boolean"
},
"web_search_image_support": {
"type": "boolean"
},
"web_search_request": {
"type": "boolean"
}
@@ -1852,6 +1855,9 @@
"web_search_cached": {
"type": "boolean"
},
"web_search_image_support": {
"type": "boolean"
},
"web_search_request": {
"type": "boolean"
}

View File

@@ -176,6 +176,8 @@ pub(crate) mod tools {
WebSearch {
#[serde(skip_serializing_if = "Option::is_none")]
external_web_access: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
search_content_types: Option<Vec<String>>,
},
#[serde(rename = "custom")]
Freeform(FreeformTool),

View File

@@ -163,6 +163,8 @@ pub enum Feature {
ResponsesWebsockets,
/// Enable Responses API websocket v2 mode.
ResponsesWebsocketsV2,
/// Allow Codex web_search to request image results.
WebSearchImageSupport,
}
impl Feature {
@@ -753,6 +755,12 @@ pub const FEATURES: &[FeatureSpec] = &[
stage: Stage::UnderDevelopment,
default_enabled: false,
},
FeatureSpec {
id: Feature::WebSearchImageSupport,
key: "web_search_image_support",
stage: Stage::UnderDevelopment,
default_enabled: false,
},
];
/// Push a warning event if any under-development features are enabled.

View File

@@ -36,6 +36,8 @@ use std::collections::HashMap;
const SEARCH_TOOL_BM25_DESCRIPTION_TEMPLATE: &str =
include_str!("../../templates/search_tool/tool_description.md");
const WEB_SEARCH_CONTENT_TYPES: [&str; 2] = ["text", "image"];
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum ShellCommandBackendConfig {
Classic,
@@ -49,6 +51,7 @@ pub(crate) struct ToolsConfig {
pub allow_login_shell: bool,
pub apply_patch_tool_type: Option<ApplyPatchToolType>,
pub web_search_mode: Option<WebSearchMode>,
pub web_search_image_support: bool,
pub image_gen_tool: bool,
pub agent_roles: BTreeMap<String, AgentRoleConfig>,
pub search_tool: bool,
@@ -93,6 +96,7 @@ impl ToolsConfig {
features.enabled(Feature::ImageGeneration) && supports_image_generation(model_info);
let include_agent_jobs = include_collab_tools && features.enabled(Feature::Sqlite);
let request_permission_enabled = features.enabled(Feature::RequestPermissions);
let include_web_search_image_support = features.enabled(Feature::WebSearchImageSupport);
let shell_command_backend =
if features.enabled(Feature::ShellTool) && features.enabled(Feature::ShellZshFork) {
ShellCommandBackendConfig::ZshFork
@@ -140,6 +144,7 @@ impl ToolsConfig {
allow_login_shell: true,
apply_patch_tool_type,
web_search_mode: *web_search_mode,
web_search_image_support: include_web_search_image_support,
image_gen_tool: include_image_gen_tool,
agent_roles: BTreeMap::new(),
search_tool: include_search_tool,
@@ -167,6 +172,15 @@ impl ToolsConfig {
}
}
fn web_search_content_types(web_search_image_support: bool) -> Option<Vec<String>> {
web_search_image_support.then(|| {
WEB_SEARCH_CONTENT_TYPES
.into_iter()
.map(str::to_string)
.collect()
})
}
fn supports_image_generation(model_info: &ModelInfo) -> bool {
model_info.input_modalities.contains(&InputModality::Image)
}
@@ -1881,11 +1895,13 @@ pub(crate) fn build_specs(
Some(WebSearchMode::Cached) => {
builder.push_spec(ToolSpec::WebSearch {
external_web_access: Some(false),
search_content_types: web_search_content_types(config.web_search_image_support),
});
}
Some(WebSearchMode::Live) => {
builder.push_spec(ToolSpec::WebSearch {
external_web_access: Some(true),
search_content_types: web_search_content_types(config.web_search_image_support),
});
}
Some(WebSearchMode::Disabled) | None => {}
@@ -2172,6 +2188,7 @@ mod tests {
create_apply_patch_freeform_tool(),
ToolSpec::WebSearch {
external_web_access: Some(true),
search_content_types: None,
},
create_view_image_tool(),
] {
@@ -2486,6 +2503,7 @@ mod tests {
tool.spec,
ToolSpec::WebSearch {
external_web_access: Some(false),
search_content_types: None,
}
);
}
@@ -2510,6 +2528,38 @@ mod tests {
tool.spec,
ToolSpec::WebSearch {
external_web_access: Some(true),
search_content_types: None,
}
);
}
#[test]
fn web_search_image_support_sets_search_content_types_when_web_search_enabled() {
let config = test_config();
let model_info =
ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
let mut features = Features::with_defaults();
features.enable(Feature::WebSearchImageSupport);
let tools_config = ToolsConfig::new(&ToolsConfigParams {
model_info: &model_info,
features: &features,
web_search_mode: Some(WebSearchMode::Cached),
session_source: SessionSource::Cli,
});
let (tools, _) = build_specs(&tools_config, None, None, &[]).build();
let tool = find_tool(&tools, "web_search");
assert_eq!(
tool.spec,
ToolSpec::WebSearch {
external_web_access: Some(false),
search_content_types: Some(
WEB_SEARCH_CONTENT_TYPES
.into_iter()
.map(str::to_string)
.collect(),
),
}
);
}

View File

@@ -223,3 +223,50 @@ async fn web_search_mode_updates_between_turns_with_sandbox_policy() {
"danger-full-access policy should default web_search to live"
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn web_search_image_support_feature_sets_content_types() {
skip_if_no_network!();
let server = start_mock_server().await;
let sse = responses::sse(vec![
responses::ev_response_created("resp-1"),
responses::ev_completed("resp-1"),
]);
let resp_mock = responses::mount_sse_once(&server, sse).await;
let mut builder = test_codex()
.with_model("gpt-5-codex")
.with_config(|config| {
config
.features
.enable(Feature::WebSearchImageSupport)
.expect("test feature should satisfy constraints");
config
.web_search_mode
.set(WebSearchMode::Cached)
.expect("test web_search_mode should satisfy constraints");
});
let test = builder
.build(&server)
.await
.expect("create test Codex conversation");
test.submit_turn_with_policy(
"hello image web search",
SandboxPolicy::new_read_only_policy(),
)
.await
.expect("submit turn");
let body = resp_mock.single_request().body_json();
let tool = find_web_search_tool(&body);
assert_eq!(
tool.get("search_content_types").and_then(Value::as_array),
Some(&vec![
Value::String("text".to_string()),
Value::String("image".to_string()),
]),
"web_search image support should request both text and image results"
);
}