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:
sayan-oai authored and copyberry committed 2026-07-23 00:02:15 +00:00
1 parent 0da13c6c99
commit 0f9fb40fa9
20 files changed
+280 -21

No files matched your search

@@ -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,
}
}