mirror of
https://github.com/openai/codex.git
synced 2026-10-18 17:18:52 +00:00
Allow custom providers to opt into standalone web search (#34846)
## What changed - Add the `supports_standalone_web_search` model-provider setting, defaulting to `false`, and preserve it in remote thread configuration. - Enable the standalone `web.run` tool for opted-in custom Responses providers when web search is enabled and the runtime provider supports it. - Send standalone search requests through the custom provider's endpoint and authentication. ## Testing - Cover opt-in, default-off, disabled-search, and unsupported-provider behavior. - Verify custom-provider search request routing and authorization through the app server. GitOrigin-RevId: 7c5f96b0ce924ad2b9715c45bfc635e89fc39cff
This commit is contained in:
@@ -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(),
|
||||
|
||||
@@ -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<wiremock::Request> {
|
||||
async fn search_request(server: &MockServer, search_path: &str) -> Result<wiremock::Request> {
|
||||
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")
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(()));
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -441,6 +441,7 @@ mod tests {
|
||||
websocket_connect_timeout_ms: None,
|
||||
requires_openai_auth: false,
|
||||
supports_websockets: false,
|
||||
supports_standalone_web_search: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user