diff --git a/codex-rs/app-server/src/request_processors/thread_processor_tests.rs b/codex-rs/app-server/src/request_processors/thread_processor_tests.rs index bf24ad4a68e1..461e1793bb80 100644 --- a/codex-rs/app-server/src/request_processors/thread_processor_tests.rs +++ b/codex-rs/app-server/src/request_processors/thread_processor_tests.rs @@ -689,6 +689,7 @@ mod thread_processor_behavior_tests { websocket_connect_timeout_ms: None, requires_openai_auth: false, supports_websockets: true, + supports_standalone_web_search: false, }; let config_manager = ConfigManager::new( temp_dir.path().to_path_buf(), diff --git a/codex-rs/app-server/tests/suite/v2/web_search.rs b/codex-rs/app-server/tests/suite/v2/web_search.rs index 41450cec7f19..e282c4d78195 100644 --- a/codex-rs/app-server/tests/suite/v2/web_search.rs +++ b/codex-rs/app-server/tests/suite/v2/web_search.rs @@ -43,6 +43,23 @@ const DEFAULT_READ_TIMEOUT: Duration = Duration::from_secs(10); #[tokio::test] async fn standalone_web_search_round_trips_output() -> Result<()> { + assert_standalone_web_search_round_trips_output(WebSearchProvider::ChatGpt).await +} + +#[tokio::test] +async fn standalone_web_search_round_trips_output_for_custom_provider() -> Result<()> { + assert_standalone_web_search_round_trips_output(WebSearchProvider::CustomResponses).await +} + +#[derive(Clone, Copy)] +enum WebSearchProvider { + ChatGpt, + CustomResponses, +} + +async fn assert_standalone_web_search_round_trips_output( + provider: WebSearchProvider, +) -> Result<()> { let call_id = "web-run-1"; let expected_model_id = "model-id-from-search-context"; let search_context = json!({ @@ -57,7 +74,11 @@ async fn standalone_web_search_round_trips_output() -> Result<()> { json!({ "openai/search_context": search_context }).to_string(), )]); let server = responses::start_mock_server().await; - mount_search_response(&server).await; + let search_path = match provider { + WebSearchProvider::ChatGpt => "/api/codex/alpha/search", + WebSearchProvider::CustomResponses => "/v1/alpha/search", + }; + mount_search_response(&server, search_path).await; let response_mock = responses::mount_sse_sequence( &server, @@ -84,24 +105,47 @@ async fn standalone_web_search_round_trips_output() -> Result<()> { .await; let codex_home = TempDir::new()?; - MockResponsesConfig::new(&server.uri()) - .with_model_provider("openai-custom") - .with_provider_name("OpenAI") - .with_provider_base_url(&format!("{}/api/codex", server.uri())) + let config = MockResponsesConfig::new(&server.uri()) .with_root_config(&format!("chatgpt_base_url = \"{}\"", server.uri())) .enable_feature(Feature::StandaloneWebSearch) - .with_provider_config("supports_websockets = false") - .with_provider_config("requires_openai_auth = true") - .write(codex_home.path())?; - write_chatgpt_auth( - codex_home.path(), - ChatGptAuthFixture::new("access-chatgpt"), - AuthCredentialsStoreMode::File, - )?; + .with_provider_config("supports_websockets = false"); + let config = match provider { + WebSearchProvider::ChatGpt => config + .with_model_provider("openai-custom") + .with_provider_name("OpenAI") + .with_provider_base_url(&format!("{}/api/codex", server.uri())) + .with_provider_config("requires_openai_auth = true"), + WebSearchProvider::CustomResponses => config + .with_model_provider("custom-responses") + .with_provider_name("Custom Responses") + .with_provider_base_url(&format!("{}/v1", server.uri())) + .with_provider_config("env_key = \"CUSTOM_RESPONSES_API_KEY\"") + .with_provider_config("supports_standalone_web_search = true") + .with_provider_config("requires_openai_auth = false"), + }; + config.write(codex_home.path())?; + + if matches!(provider, WebSearchProvider::ChatGpt) { + write_chatgpt_auth( + codex_home.path(), + ChatGptAuthFixture::new("access-chatgpt"), + AuthCredentialsStoreMode::File, + )?; + } + + let env_overrides = match provider { + WebSearchProvider::ChatGpt => { + vec![("OPENAI_API_KEY", None), ("CUSTOM_RESPONSES_API_KEY", None)] + } + WebSearchProvider::CustomResponses => vec![ + ("OPENAI_API_KEY", None), + ("CUSTOM_RESPONSES_API_KEY", Some("test-api-key")), + ], + }; let mut mcp = TestAppServer::builder() .with_codex_home(codex_home.path()) - .with_env_overrides(&[("OPENAI_API_KEY", None)]) + .with_env_overrides(&env_overrides) .build_initialized_with_timeout(DEFAULT_READ_TIMEOUT) .await?; @@ -159,7 +203,28 @@ async fn standalone_web_search_round_trips_output() -> Result<()> { "standalone web search should replace hosted web search" ); - let search_request = search_request(&server).await?; + let search_request = search_request(&server, search_path).await?; + let expected_authorization = match provider { + WebSearchProvider::ChatGpt => "Bearer access-chatgpt", + WebSearchProvider::CustomResponses => "Bearer test-api-key", + }; + assert_eq!( + search_request + .headers + .get("authorization") + .context("standalone search should include provider authorization")? + .to_str() + .context("standalone search authorization should be valid ASCII")?, + expected_authorization + ); + if matches!(provider, WebSearchProvider::CustomResponses) { + assert!( + search_request + .headers + .get("x-openai-actor-authorization") + .is_none() + ); + } assert_eq!( search_request .headers @@ -268,7 +333,7 @@ async fn standalone_web_search_round_trips_output() -> Result<()> { drop(mcp); let mut reloaded_mcp = TestAppServer::builder() .with_codex_home(codex_home.path()) - .with_env_overrides(&[("OPENAI_API_KEY", None)]) + .with_env_overrides(&env_overrides) .build_initialized_with_timeout(DEFAULT_READ_TIMEOUT) .await?; let read_req = reloaded_mcp @@ -310,9 +375,9 @@ async fn wait_for_web_search_completed( } } -async fn mount_search_response(server: &MockServer) { +async fn mount_search_response(server: &MockServer, search_path: &str) { Mock::given(method("POST")) - .and(path("/api/codex/alpha/search")) + .and(path(search_path)) .respond_with(ResponseTemplate::new(200).set_body_json(json!({ "encrypted_output": "ciphertext", "output": "Search result", @@ -340,13 +405,13 @@ fn has_hosted_web_search(body: &Value) -> bool { }) } -async fn search_request(server: &MockServer) -> Result { +async fn search_request(server: &MockServer, search_path: &str) -> Result { let requests = server .received_requests() .await .context("failed to fetch received requests")?; requests .into_iter() - .find(|request| request.url.path() == "/api/codex/alpha/search") + .find(|request| request.url.path() == search_path) .context("expected standalone search request") } diff --git a/codex-rs/config/src/thread_config.rs b/codex-rs/config/src/thread_config.rs index 14360c277089..76d76fd59f0e 100644 --- a/codex-rs/config/src/thread_config.rs +++ b/codex-rs/config/src/thread_config.rs @@ -284,6 +284,7 @@ mod tests { wire_api = "responses" requires_openai_auth = false supports_websockets = true + supports_standalone_web_search = true [features] plugins = false @@ -312,6 +313,7 @@ mod tests { websocket_connect_timeout_ms: None, requires_openai_auth: false, supports_websockets: true, + supports_standalone_web_search: true, } } } diff --git a/codex-rs/config/src/thread_config/proto/codex.thread_config.v1.proto b/codex-rs/config/src/thread_config/proto/codex.thread_config.v1.proto index 1efccfd1bfb5..5bcff769864f 100644 --- a/codex-rs/config/src/thread_config/proto/codex.thread_config.v1.proto +++ b/codex-rs/config/src/thread_config/proto/codex.thread_config.v1.proto @@ -48,6 +48,7 @@ message ModelProvider { optional uint64 websocket_connect_timeout_ms = 15; bool requires_openai_auth = 16; bool supports_websockets = 17; + bool supports_standalone_web_search = 18; } message StringMap { diff --git a/codex-rs/config/src/thread_config/proto/codex.thread_config.v1.rs b/codex-rs/config/src/thread_config/proto/codex.thread_config.v1.rs index 30a76bc6b2f4..05b1cf0eca2b 100644 --- a/codex-rs/config/src/thread_config/proto/codex.thread_config.v1.rs +++ b/codex-rs/config/src/thread_config/proto/codex.thread_config.v1.rs @@ -75,6 +75,8 @@ pub struct ModelProvider { pub requires_openai_auth: bool, #[prost(bool, tag = "17")] pub supports_websockets: bool, + #[prost(bool, tag = "18")] + pub supports_standalone_web_search: bool, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct StringMap { diff --git a/codex-rs/config/src/thread_config/remote.rs b/codex-rs/config/src/thread_config/remote.rs index 7a8e4ce88fa6..2a52c3b65715 100644 --- a/codex-rs/config/src/thread_config/remote.rs +++ b/codex-rs/config/src/thread_config/remote.rs @@ -190,6 +190,7 @@ fn model_provider_from_proto( websocket_connect_timeout_ms: provider.websocket_connect_timeout_ms, requires_openai_auth: provider.requires_openai_auth, supports_websockets: provider.supports_websockets, + supports_standalone_web_search: provider.supports_standalone_web_search, }; Ok((id, info)) } @@ -217,6 +218,7 @@ fn model_provider_to_proto( websocket_connect_timeout_ms, requires_openai_auth, supports_websockets, + supports_standalone_web_search, } = provider; proto::ModelProvider { @@ -237,6 +239,7 @@ fn model_provider_to_proto( websocket_connect_timeout_ms, requires_openai_auth, supports_websockets, + supports_standalone_web_search, } } @@ -421,6 +424,21 @@ mod tests { fn model_provider_proto_roundtrips_through_domain_type() { let expected = expected_provider(); let proto = model_provider_to_proto("local", expected.clone()); + assert!(proto.supports_standalone_web_search); + let (id, actual) = model_provider_from_proto(proto).expect("model provider from proto"); + + assert_eq!(id, "local"); + assert_eq!(actual, expected); + } + + #[test] + fn model_provider_proto_defaults_standalone_web_search_to_false() { + let expected = ModelProviderInfo { + supports_standalone_web_search: false, + ..expected_provider() + }; + let proto = model_provider_to_proto("local", expected.clone()); + assert!(!proto.supports_standalone_web_search); let (id, actual) = model_provider_from_proto(proto).expect("model provider from proto"); assert_eq!(id, "local"); @@ -473,6 +491,7 @@ mod tests { websocket_connect_timeout_ms: Some(10_000), requires_openai_auth: false, supports_websockets: true, + supports_standalone_web_search: true, }], features: HashMap::from([ ("plugins".to_string(), false), @@ -536,6 +555,7 @@ mod tests { websocket_connect_timeout_ms: Some(10_000), requires_openai_auth: false, supports_websockets: true, + supports_standalone_web_search: true, aws: None, } } diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index e6ec8d92c227..62b6556ca1d0 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -1736,6 +1736,11 @@ "minimum": 0.0, "type": "integer" }, + "supports_standalone_web_search": { + "default": false, + "description": "Whether this provider supports the standalone web-search endpoint.", + "type": "boolean" + }, "supports_websockets": { "default": false, "description": "Whether this provider supports the Responses API WebSocket transport.", diff --git a/codex-rs/core/src/compact_tests.rs b/codex-rs/core/src/compact_tests.rs index f34d71f96b86..f3f50e021ec1 100644 --- a/codex-rs/core/src/compact_tests.rs +++ b/codex-rs/core/src/compact_tests.rs @@ -284,6 +284,7 @@ fn should_use_remote_compact_task_for_azure_provider() { websocket_connect_timeout_ms: None, requires_openai_auth: false, supports_websockets: false, + supports_standalone_web_search: false, }; assert!(should_use_remote_compact_task(&provider)); diff --git a/codex-rs/core/src/tools/spec_plan.rs b/codex-rs/core/src/tools/spec_plan.rs index e6b69ab2deba..e7bbeaaace7b 100644 --- a/codex-rs/core/src/tools/spec_plan.rs +++ b/codex-rs/core/src/tools/spec_plan.rs @@ -619,6 +619,7 @@ fn add_tool_sources(context: &CoreToolPlanContext<'_>, planned_tools: &mut Plann fn standalone_web_search_enabled(turn_context: &TurnContext) -> bool { namespace_tools_enabled(turn_context) + && turn_context.provider.capabilities().web_search && (turn_context.model_info.use_responses_lite || turn_context .config diff --git a/codex-rs/core/tests/responses_headers.rs b/codex-rs/core/tests/responses_headers.rs index b94f7ed49df4..7343e6703851 100644 --- a/codex-rs/core/tests/responses_headers.rs +++ b/codex-rs/core/tests/responses_headers.rs @@ -88,6 +88,7 @@ async fn responses_stream_includes_subagent_header_on_review() { websocket_connect_timeout_ms: None, requires_openai_auth: false, supports_websockets: false, + supports_standalone_web_search: false, }; let codex_home = TempDir::new().expect("failed to create TempDir"); @@ -223,6 +224,7 @@ async fn responses_stream_includes_subagent_header_on_other() { websocket_connect_timeout_ms: None, requires_openai_auth: false, supports_websockets: false, + supports_standalone_web_search: false, }; let codex_home = TempDir::new().expect("failed to create TempDir"); @@ -339,6 +341,7 @@ async fn responses_respects_model_info_overrides_from_config() { websocket_connect_timeout_ms: None, requires_openai_auth: false, supports_websockets: false, + supports_standalone_web_search: false, }; let codex_home = TempDir::new().expect("failed to create TempDir"); diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index bc0d7a9f79cb..0127de4a9fef 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -1450,6 +1450,7 @@ async fn send_provider_auth_request(server: &MockServer, auth: ModelProviderAuth websocket_connect_timeout_ms: None, requires_openai_auth: false, supports_websockets: false, + supports_standalone_web_search: false, }; send_request_with_provider(provider).await; @@ -3186,6 +3187,7 @@ async fn azure_responses_request_includes_store_and_prefixed_item_ids() { websocket_connect_timeout_ms: None, requires_openai_auth: false, supports_websockets: false, + supports_standalone_web_search: false, }; let codex_home = TempDir::new().unwrap(); @@ -3843,6 +3845,7 @@ async fn azure_overrides_assign_properties_used_for_responses_url() { websocket_connect_timeout_ms: None, requires_openai_auth: false, supports_websockets: false, + supports_standalone_web_search: false, }; // Init session @@ -3932,6 +3935,7 @@ async fn env_var_overrides_loaded_auth() { websocket_connect_timeout_ms: None, requires_openai_auth: false, supports_websockets: false, + supports_standalone_web_search: false, }; // Init session diff --git a/codex-rs/core/tests/suite/client_websockets.rs b/codex-rs/core/tests/suite/client_websockets.rs index a218e721d716..988419dec8f1 100755 --- a/codex-rs/core/tests/suite/client_websockets.rs +++ b/codex-rs/core/tests/suite/client_websockets.rs @@ -2285,6 +2285,7 @@ fn websocket_provider_with_connect_timeout( websocket_connect_timeout_ms, requires_openai_auth: false, supports_websockets: true, + supports_standalone_web_search: false, } } diff --git a/codex-rs/core/tests/suite/responses_lite.rs b/codex-rs/core/tests/suite/responses_lite.rs index 83cc6208be73..40de16c31ede 100644 --- a/codex-rs/core/tests/suite/responses_lite.rs +++ b/codex-rs/core/tests/suite/responses_lite.rs @@ -9,6 +9,7 @@ use codex_extension_api::ExtensionRegistryBuilder; use codex_features::Feature; use codex_image_generation_extension::install as install_image_generation_extension; use codex_login::CodexAuth; +use codex_login::auth::BedrockApiKeyAuth; use codex_protocol::config_types::WebSearchMode; use codex_protocol::models::ImageDetail; use codex_protocol::openai_models::InputModality; @@ -290,6 +291,140 @@ async fn responses_lite_exposes_standalone_tools_for_actor_authorized_provider() Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn responses_lite_exposes_standalone_web_search_for_opted_in_custom_provider() -> Result<()> { + assert_responses_lite_custom_provider_web_search( + WebSearchMode::Live, + /*supports_standalone_web_search*/ true, + /*expect_web_run*/ true, + ) + .await +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn responses_lite_does_not_expose_standalone_web_search_for_custom_provider_by_default() +-> Result<()> { + assert_responses_lite_custom_provider_web_search( + WebSearchMode::Live, + /*supports_standalone_web_search*/ false, + /*expect_web_run*/ false, + ) + .await +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn responses_lite_does_not_expose_disabled_standalone_web_search_for_opted_in_provider() +-> Result<()> { + assert_responses_lite_custom_provider_web_search( + WebSearchMode::Disabled, + /*supports_standalone_web_search*/ true, + /*expect_web_run*/ false, + ) + .await +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn responses_lite_does_not_expose_standalone_web_search_for_opted_in_bedrock_provider() +-> Result<()> { + skip_if_no_network!(Ok(())); + + let server = responses::start_mock_server().await; + let response_mock = responses::mount_sse_once( + &server, + responses::sse(vec![ + responses::ev_response_created("resp-1"), + responses::ev_completed("resp-1"), + ]), + ) + .await; + + let auth = CodexAuth::BedrockApiKey(BedrockApiKeyAuth { + api_key: "dummy".to_string(), + region: "us-east-1".to_string(), + }); + let extensions = responses_extensions(&auth); + let mut builder = test_codex() + .with_auth(auth) + .with_extensions(extensions) + .with_model_info_override("gpt-5.4", |model_info| { + model_info.use_responses_lite = true; + }) + .with_config(|config| { + configure_responses_tools(config); + config.model_provider.name = "Amazon Bedrock".to_string(); + config.model_provider.requires_openai_auth = false; + config.model_provider.http_headers = None; + config.model_provider.supports_standalone_web_search = true; + }); + let test = builder.build(&server).await?; + + test.submit_turn("Use standalone web search").await?; + + let request = response_mock.single_request(); + assert_eq!( + request.header(RESPONSES_LITE_HEADER).as_deref(), + Some("true") + ); + let body = request.body_json(); + assert!(body.get("tools").is_none()); + let tools = additional_tools(&body)?; + assert!(!has_namespaced_tool(tools, "web", "run")); + assert!(!has_hosted_tool(tools, "web_search")); + + Ok(()) +} + +async fn assert_responses_lite_custom_provider_web_search( + web_search_mode: WebSearchMode, + supports_standalone_web_search: bool, + expect_web_run: bool, +) -> Result<()> { + skip_if_no_network!(Ok(())); + + let server = responses::start_mock_server().await; + let response_mock = responses::mount_sse_once( + &server, + responses::sse(vec![ + responses::ev_response_created("resp-1"), + responses::ev_completed("resp-1"), + ]), + ) + .await; + + let auth = CodexAuth::from_api_key("dummy"); + let extensions = responses_extensions(&auth); + let mut builder = test_codex() + .with_auth(auth) + .with_extensions(extensions) + .with_model_info_override("gpt-5.4", |model_info| { + model_info.use_responses_lite = true; + }) + .with_config(move |config| { + configure_responses_tools(config); + assert!(config.web_search_mode.set(web_search_mode).is_ok()); + config.model_provider.name = "custom-responses".to_string(); + config.model_provider.requires_openai_auth = false; + config.model_provider.http_headers = None; + config.model_provider.supports_standalone_web_search = supports_standalone_web_search; + }); + let test = builder.build(&server).await?; + + test.submit_turn("Use standalone web search").await?; + + let request = response_mock.single_request(); + assert_eq!( + request.header(RESPONSES_LITE_HEADER).as_deref(), + Some("true") + ); + let body = request.body_json(); + assert!(body.get("tools").is_none()); + let tools = additional_tools(&body)?; + assert_eq!(has_namespaced_tool(tools, "web", "run"), expect_web_run); + assert!(!has_hosted_tool(tools, "web_search")); + + Ok(()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn responses_lite_compact_request_uses_lite_transport_contract() -> Result<()> { skip_if_no_network!(Ok(())); diff --git a/codex-rs/core/tests/suite/stream_error_allows_next_turn.rs b/codex-rs/core/tests/suite/stream_error_allows_next_turn.rs index 2c86edc53c77..f93a4b66e68f 100644 --- a/codex-rs/core/tests/suite/stream_error_allows_next_turn.rs +++ b/codex-rs/core/tests/suite/stream_error_allows_next_turn.rs @@ -81,6 +81,7 @@ async fn continue_after_stream_error() { websocket_connect_timeout_ms: None, requires_openai_auth: false, supports_websockets: false, + supports_standalone_web_search: false, }; let TestCodex { codex, .. } = test_codex() diff --git a/codex-rs/core/tests/suite/stream_no_completed.rs b/codex-rs/core/tests/suite/stream_no_completed.rs index 6e028279b1b6..207a2754cf9d 100644 --- a/codex-rs/core/tests/suite/stream_no_completed.rs +++ b/codex-rs/core/tests/suite/stream_no_completed.rs @@ -64,6 +64,7 @@ async fn retries_on_early_close() { websocket_connect_timeout_ms: None, requires_openai_auth: false, supports_websockets: false, + supports_standalone_web_search: false, }; let TestCodex { codex, .. } = test_codex() diff --git a/codex-rs/ext/web-search/src/extension.rs b/codex-rs/ext/web-search/src/extension.rs index d59a34649dd0..e269a18a3f24 100644 --- a/codex-rs/ext/web-search/src/extension.rs +++ b/codex-rs/ext/web-search/src/extension.rs @@ -43,7 +43,8 @@ impl From<&Config> for WebSearchExtensionConfig { Self { // Core selects this executor per turn using the feature flag or model metadata. available: (config.model_provider.is_openai() - || config.model_provider.uses_openai_actor_authorization()) + || config.model_provider.uses_openai_actor_authorization() + || config.model_provider.supports_standalone_web_search) && web_search_mode != WebSearchMode::Disabled, provider: config.model_provider.clone(), settings: search_settings(config, web_search_mode), diff --git a/codex-rs/login/src/auth_env_telemetry.rs b/codex-rs/login/src/auth_env_telemetry.rs index 3cec5a4ce3e9..86cbbfd8fea0 100644 --- a/codex-rs/login/src/auth_env_telemetry.rs +++ b/codex-rs/login/src/auth_env_telemetry.rs @@ -76,6 +76,7 @@ mod tests { websocket_connect_timeout_ms: None, requires_openai_auth: false, supports_websockets: false, + supports_standalone_web_search: false, }; let telemetry = diff --git a/codex-rs/model-provider-info/src/lib.rs b/codex-rs/model-provider-info/src/lib.rs index 803d858789c9..82fc5fc2a6bd 100644 --- a/codex-rs/model-provider-info/src/lib.rs +++ b/codex-rs/model-provider-info/src/lib.rs @@ -138,6 +138,9 @@ pub struct ModelProviderInfo { /// Whether this provider supports the Responses API WebSocket transport. #[serde(default)] pub supports_websockets: bool, + /// Whether this provider supports the standalone web-search endpoint. + #[serde(default)] + pub supports_standalone_web_search: bool, } /// AWS SigV4 auth configuration for a model provider. @@ -360,6 +363,7 @@ impl ModelProviderInfo { websocket_connect_timeout_ms: None, requires_openai_auth: true, supports_websockets: true, + supports_standalone_web_search: true, } } @@ -393,6 +397,7 @@ impl ModelProviderInfo { websocket_connect_timeout_ms: None, requires_openai_auth: false, supports_websockets: false, + supports_standalone_web_search: false, } } @@ -540,6 +545,7 @@ pub fn create_oss_provider_with_base_url(base_url: &str, wire_api: WireApi) -> M websocket_connect_timeout_ms: None, requires_openai_auth: false, supports_websockets: false, + supports_standalone_web_search: false, } } diff --git a/codex-rs/model-provider-info/src/model_provider_info_tests.rs b/codex-rs/model-provider-info/src/model_provider_info_tests.rs index cc7f6851f298..354c7fc2b80b 100644 --- a/codex-rs/model-provider-info/src/model_provider_info_tests.rs +++ b/codex-rs/model-provider-info/src/model_provider_info_tests.rs @@ -29,6 +29,7 @@ base_url = "http://localhost:11434/v1" websocket_connect_timeout_ms: None, requires_openai_auth: false, supports_websockets: false, + supports_standalone_web_search: false, }; let provider: ModelProviderInfo = toml::from_str(azure_provider_toml).unwrap(); @@ -63,6 +64,7 @@ query_params = { api-version = "2025-04-01-preview" } websocket_connect_timeout_ms: None, requires_openai_auth: false, supports_websockets: false, + supports_standalone_web_search: false, }; let provider: ModelProviderInfo = toml::from_str(azure_provider_toml).unwrap(); @@ -77,6 +79,7 @@ base_url = "https://example.com" env_key = "API_KEY" http_headers = { "X-Example-Header" = "example-value" } env_http_headers = { "X-Example-Env-Header" = "EXAMPLE_ENV_VAR" } +supports_standalone_web_search = true "#; let expected_provider = ModelProviderInfo { name: "Example".into(), @@ -100,6 +103,7 @@ env_http_headers = { "X-Example-Env-Header" = "EXAMPLE_ENV_VAR" } websocket_connect_timeout_ms: None, requires_openai_auth: false, supports_websockets: false, + supports_standalone_web_search: true, }; let provider: ModelProviderInfo = toml::from_str(azure_provider_toml).unwrap(); @@ -177,6 +181,7 @@ fn test_supports_remote_compaction_for_azure_name() { websocket_connect_timeout_ms: None, requires_openai_auth: false, supports_websockets: false, + supports_standalone_web_search: false, }; assert!(provider.supports_remote_compaction()); @@ -202,6 +207,7 @@ fn test_supports_remote_compaction_for_non_openai_non_azure_provider() { websocket_connect_timeout_ms: None, requires_openai_auth: false, supports_websockets: false, + supports_standalone_web_search: false, }; assert!(!provider.supports_remote_compaction()); @@ -310,6 +316,7 @@ fn test_create_amazon_bedrock_provider() { websocket_connect_timeout_ms: None, requires_openai_auth: false, supports_websockets: false, + supports_standalone_web_search: false, } ); } diff --git a/codex-rs/model-provider/src/provider.rs b/codex-rs/model-provider/src/provider.rs index 756bd641375c..57f950a09eb4 100644 --- a/codex-rs/model-provider/src/provider.rs +++ b/codex-rs/model-provider/src/provider.rs @@ -441,6 +441,7 @@ mod tests { websocket_connect_timeout_ms: None, requires_openai_auth: false, supports_websockets: false, + supports_standalone_web_search: false, } }