mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Add indexed web search mode (#28489)
## Summary - Add `web_search = "indexed"` alongside `disabled`, `cached`, and `live`. - Use that same resolved mode for both hosted and standalone web search. - For hosted search, send `index_gated_web_access: true` with external web access enabled only when `indexed` is selected. - For standalone search, preserve the existing boolean wire values for existing modes (`cached` maps to `false` and `live` to `true`) and send `"indexed"` only for `indexed`; `disabled` keeps the tool unavailable. - Carry the mode through managed configuration requirements and generated schemas. ## Why Indexed search provides a middle ground between cached-only search and unrestricted live page fetching. Search queries can remain live while direct page fetches are limited to URLs admitted by the server. The existing `web_search` setting remains the single source of truth, so hosted and standalone executors cannot drift into different access modes. Without an explicit `indexed` selection, the existing model-visible tool and request shapes are unchanged. ```toml web_search = "indexed" [features] standalone_web_search = true ``` ## Validation - `just fmt` - `just test -p codex-api` (`126 passed`) - `just test -p codex-web-search-extension` (`7 passed`) - `just test -p codex-core code_mode_can_call_indexed_standalone_web_search` (`1 passed`) - Focused configuration, hosted request, standalone request, and managed-requirement coverage is included in the PR; remaining suites run in CI. The full workspace test suite was not run locally.
This commit is contained in:
committed by
GitHub
Unverified
parent
7951397e4a
commit
3a2712ea14
+1
@@ -20355,6 +20355,7 @@
|
||||
"enum": [
|
||||
"disabled",
|
||||
"cached",
|
||||
"indexed",
|
||||
"live"
|
||||
],
|
||||
"type": "string"
|
||||
|
||||
+1
@@ -18134,6 +18134,7 @@
|
||||
"enum": [
|
||||
"disabled",
|
||||
"cached",
|
||||
"indexed",
|
||||
"live"
|
||||
],
|
||||
"type": "string"
|
||||
|
||||
@@ -809,6 +809,7 @@
|
||||
"enum": [
|
||||
"disabled",
|
||||
"cached",
|
||||
"indexed",
|
||||
"live"
|
||||
],
|
||||
"type": "string"
|
||||
|
||||
+1
@@ -503,6 +503,7 @@
|
||||
"enum": [
|
||||
"disabled",
|
||||
"cached",
|
||||
"indexed",
|
||||
"live"
|
||||
],
|
||||
"type": "string"
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type WebSearchMode = "disabled" | "cached" | "live";
|
||||
export type WebSearchMode = "disabled" | "cached" | "indexed" | "live";
|
||||
|
||||
@@ -55,6 +55,7 @@ mod tests {
|
||||
use crate::provider::RetryConfig;
|
||||
use crate::search::AllowedCaller;
|
||||
use crate::search::ApproximateLocation;
|
||||
use crate::search::ExternalWebAccess;
|
||||
use crate::search::LocationType;
|
||||
use crate::search::OpenOperation;
|
||||
use crate::search::SearchCommands;
|
||||
@@ -193,7 +194,7 @@ mod tests {
|
||||
caption: Some(true),
|
||||
}),
|
||||
allowed_callers: Some(vec![AllowedCaller::Direct]),
|
||||
external_web_access: Some(true),
|
||||
external_web_access: Some(ExternalWebAccess::Boolean(true)),
|
||||
}),
|
||||
max_output_tokens: Some(2500),
|
||||
},
|
||||
|
||||
@@ -81,6 +81,8 @@ pub use crate::requests::Compression;
|
||||
pub use crate::search::AllowedCaller;
|
||||
pub use crate::search::ApproximateLocation;
|
||||
pub use crate::search::ClickOperation;
|
||||
pub use crate::search::ExternalWebAccess;
|
||||
pub use crate::search::ExternalWebAccessMode;
|
||||
pub use crate::search::FinanceAssetType;
|
||||
pub use crate::search::FinanceOperation;
|
||||
pub use crate::search::FindOperation;
|
||||
|
||||
@@ -211,6 +211,21 @@ pub enum SearchResponseLength {
|
||||
Long,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ExternalWebAccessMode {
|
||||
Offline,
|
||||
Indexed,
|
||||
Online,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(untagged)]
|
||||
pub enum ExternalWebAccess {
|
||||
Boolean(bool),
|
||||
Mode(ExternalWebAccessMode),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
|
||||
pub struct SearchSettings {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -224,7 +239,7 @@ pub struct SearchSettings {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub allowed_callers: Option<Vec<AllowedCaller>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub external_web_access: Option<bool>,
|
||||
pub external_web_access: Option<ExternalWebAccess>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
|
||||
@@ -665,10 +665,11 @@ fn is_glob_metacharacter(ch: char) -> bool {
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum WebSearchModeRequirement {
|
||||
Disabled,
|
||||
Cached,
|
||||
Indexed,
|
||||
Live,
|
||||
}
|
||||
|
||||
@@ -677,6 +678,7 @@ impl From<WebSearchMode> for WebSearchModeRequirement {
|
||||
match mode {
|
||||
WebSearchMode::Disabled => WebSearchModeRequirement::Disabled,
|
||||
WebSearchMode::Cached => WebSearchModeRequirement::Cached,
|
||||
WebSearchMode::Indexed => WebSearchModeRequirement::Indexed,
|
||||
WebSearchMode::Live => WebSearchModeRequirement::Live,
|
||||
}
|
||||
}
|
||||
@@ -687,6 +689,7 @@ impl From<WebSearchModeRequirement> for WebSearchMode {
|
||||
match mode {
|
||||
WebSearchModeRequirement::Disabled => WebSearchMode::Disabled,
|
||||
WebSearchModeRequirement::Cached => WebSearchMode::Cached,
|
||||
WebSearchModeRequirement::Indexed => WebSearchMode::Indexed,
|
||||
WebSearchModeRequirement::Live => WebSearchMode::Live,
|
||||
}
|
||||
}
|
||||
@@ -697,6 +700,7 @@ impl fmt::Display for WebSearchModeRequirement {
|
||||
match self {
|
||||
WebSearchModeRequirement::Disabled => write!(f, "disabled"),
|
||||
WebSearchModeRequirement::Cached => write!(f, "cached"),
|
||||
WebSearchModeRequirement::Indexed => write!(f, "indexed"),
|
||||
WebSearchModeRequirement::Live => write!(f, "live"),
|
||||
}
|
||||
}
|
||||
@@ -1345,6 +1349,8 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
|
||||
|
||||
let initial_value = if accepted.contains(&WebSearchModeRequirement::Cached) {
|
||||
WebSearchMode::Cached
|
||||
} else if accepted.contains(&WebSearchModeRequirement::Indexed) {
|
||||
WebSearchMode::Indexed
|
||||
} else if accepted.contains(&WebSearchModeRequirement::Live) {
|
||||
WebSearchMode::Live
|
||||
} else {
|
||||
@@ -2927,6 +2933,34 @@ allowed_approvals_reviewers = ["user"]
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn allowed_web_search_modes_supports_indexed() -> Result<()> {
|
||||
let config: ConfigRequirementsToml = from_str(
|
||||
r#"
|
||||
allowed_web_search_modes = ["indexed"]
|
||||
"#,
|
||||
)?;
|
||||
let requirements: ConfigRequirements = with_unknown_source(config).try_into()?;
|
||||
|
||||
assert_eq!(requirements.web_search_mode.value(), WebSearchMode::Indexed);
|
||||
for mode in [WebSearchMode::Disabled, WebSearchMode::Indexed] {
|
||||
assert!(requirements.web_search_mode.can_set(&mode).is_ok());
|
||||
}
|
||||
for mode in [WebSearchMode::Cached, WebSearchMode::Live] {
|
||||
assert_eq!(
|
||||
requirements.web_search_mode.can_set(&mode),
|
||||
Err(ConstraintError::InvalidValue {
|
||||
field_name: "web_search_mode",
|
||||
candidate: format!("{mode:?}"),
|
||||
allowed: "[Disabled, Indexed]".into(),
|
||||
requirement_source: RequirementSource::Unknown,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn allowed_web_search_modes_allows_disabled() -> Result<()> {
|
||||
let toml_str = r#"
|
||||
|
||||
@@ -409,7 +409,7 @@ pub struct ConfigToml {
|
||||
pub experimental_thread_store: Option<ThreadStoreToml>,
|
||||
pub projects: Option<HashMap<String, ProjectConfig>>,
|
||||
|
||||
/// Controls the web search tool mode: disabled, cached, or live.
|
||||
/// Controls the web search tool mode: disabled, cached, indexed, or live.
|
||||
pub web_search: Option<WebSearchMode>,
|
||||
|
||||
/// Nested tools section for feature toggles
|
||||
|
||||
@@ -4419,6 +4419,7 @@
|
||||
"enum": [
|
||||
"disabled",
|
||||
"cached",
|
||||
"indexed",
|
||||
"live"
|
||||
],
|
||||
"type": "string"
|
||||
@@ -5380,7 +5381,7 @@
|
||||
"$ref": "#/definitions/WebSearchMode"
|
||||
}
|
||||
],
|
||||
"description": "Controls the web search tool mode: disabled, cached, or live."
|
||||
"description": "Controls the web search tool mode: disabled, cached, indexed, or live."
|
||||
},
|
||||
"windows": {
|
||||
"allOf": [
|
||||
|
||||
@@ -5186,6 +5186,14 @@ fn web_search_mode_disabled_overrides_legacy_request() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn web_search_mode_for_turn_preserves_indexed_for_disabled_permissions() {
|
||||
let web_search_mode = Constrained::allow_any(WebSearchMode::Indexed);
|
||||
let mode = resolve_web_search_mode_for_turn(&web_search_mode, &PermissionProfile::Disabled);
|
||||
|
||||
assert_eq!(mode, WebSearchMode::Indexed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn web_search_mode_for_turn_uses_preference_for_read_only() {
|
||||
let web_search_mode = Constrained::allow_any(WebSearchMode::Cached);
|
||||
@@ -5232,6 +5240,31 @@ fn web_search_mode_for_turn_falls_back_when_live_is_disallowed() -> anyhow::Resu
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn web_search_mode_for_turn_does_not_implicitly_select_indexed() -> anyhow::Result<()> {
|
||||
let allowed = [
|
||||
WebSearchMode::Disabled,
|
||||
WebSearchMode::Cached,
|
||||
WebSearchMode::Indexed,
|
||||
];
|
||||
let web_search_mode = Constrained::new(WebSearchMode::Cached, move |candidate| {
|
||||
if allowed.contains(candidate) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ConstraintError::InvalidValue {
|
||||
field_name: "web_search_mode",
|
||||
candidate: format!("{candidate:?}"),
|
||||
allowed: format!("{allowed:?}"),
|
||||
requirement_source: RequirementSource::Unknown,
|
||||
})
|
||||
}
|
||||
})?;
|
||||
let mode = resolve_web_search_mode_for_turn(&web_search_mode, &PermissionProfile::Disabled);
|
||||
|
||||
assert_eq!(mode, WebSearchMode::Cached);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn project_profiles_are_ignored() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
|
||||
@@ -2667,7 +2667,7 @@ pub(crate) fn resolve_web_search_mode_for_turn(
|
||||
let preferred = web_search_mode.value();
|
||||
|
||||
if matches!(permission_profile, PermissionProfile::Disabled)
|
||||
&& preferred != WebSearchMode::Disabled
|
||||
&& !matches!(preferred, WebSearchMode::Disabled | WebSearchMode::Indexed)
|
||||
{
|
||||
for mode in [
|
||||
WebSearchMode::Live,
|
||||
|
||||
@@ -18,11 +18,12 @@ pub fn create_image_generation_tool(output_format: &str) -> ToolSpec {
|
||||
}
|
||||
|
||||
pub fn create_web_search_tool(options: WebSearchToolOptions<'_>) -> Option<ToolSpec> {
|
||||
let external_web_access = match options.web_search_mode {
|
||||
Some(WebSearchMode::Cached) => Some(false),
|
||||
Some(WebSearchMode::Live) => Some(true),
|
||||
Some(WebSearchMode::Disabled) | None => None,
|
||||
}?;
|
||||
let (external_web_access, index_gated_web_access) = match options.web_search_mode {
|
||||
Some(WebSearchMode::Cached) => (false, None),
|
||||
Some(WebSearchMode::Indexed) => (true, Some(true)),
|
||||
Some(WebSearchMode::Live) => (true, None),
|
||||
Some(WebSearchMode::Disabled) | None => return None,
|
||||
};
|
||||
|
||||
let search_content_types = match options.web_search_tool_type {
|
||||
WebSearchToolType::Text => None,
|
||||
@@ -36,6 +37,7 @@ pub fn create_web_search_tool(options: WebSearchToolOptions<'_>) -> Option<ToolS
|
||||
|
||||
Some(ToolSpec::WebSearch {
|
||||
external_web_access: Some(external_web_access),
|
||||
index_gated_web_access,
|
||||
filters: options
|
||||
.web_search_config
|
||||
.and_then(|config| config.filters.clone().map(Into::into)),
|
||||
|
||||
@@ -39,6 +39,7 @@ fn web_search_tool_preserves_configured_options() {
|
||||
}),
|
||||
Some(ToolSpec::WebSearch {
|
||||
external_web_access: Some(true),
|
||||
index_gated_web_access: None,
|
||||
filters: Some(ResponsesApiWebSearchFilters {
|
||||
allowed_domains: Some(vec!["example.com".to_string()]),
|
||||
}),
|
||||
|
||||
@@ -1441,6 +1441,7 @@ async fn hosted_tools_follow_provider_auth_model_and_config_gates() {
|
||||
live_web_search.visible_spec("web_search"),
|
||||
&ToolSpec::WebSearch {
|
||||
external_web_access: Some(true),
|
||||
index_gated_web_access: None,
|
||||
filters: None,
|
||||
user_location: None,
|
||||
search_context_size: None,
|
||||
|
||||
@@ -221,6 +221,19 @@ async fn run_code_mode_turn_with_model_and_config(
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn code_mode_can_call_standalone_web_search() -> Result<()> {
|
||||
assert_code_mode_standalone_web_search(WebSearchMode::Live, serde_json::json!(true)).await
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn code_mode_can_call_indexed_standalone_web_search() -> Result<()> {
|
||||
assert_code_mode_standalone_web_search(WebSearchMode::Indexed, serde_json::json!("indexed"))
|
||||
.await
|
||||
}
|
||||
|
||||
async fn assert_code_mode_standalone_web_search(
|
||||
web_search_mode: WebSearchMode,
|
||||
expected_external_web_access: Value,
|
||||
) -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = responses::start_mock_server().await;
|
||||
@@ -268,7 +281,7 @@ text(result);
|
||||
.with_auth(auth)
|
||||
.with_extensions(Arc::new(extension_builder.build()))
|
||||
.with_model("test-gpt-5.1-codex")
|
||||
.with_config(|config| {
|
||||
.with_config(move |config| {
|
||||
config
|
||||
.features
|
||||
.enable(Feature::CodeMode)
|
||||
@@ -279,7 +292,7 @@ text(result);
|
||||
.expect("standalone web search should be enabled");
|
||||
config
|
||||
.web_search_mode
|
||||
.set(WebSearchMode::Live)
|
||||
.set(web_search_mode)
|
||||
.expect("web search mode should be accepted");
|
||||
});
|
||||
let test = builder.build(&server).await?;
|
||||
@@ -310,7 +323,7 @@ text(result);
|
||||
search_body["settings"],
|
||||
serde_json::json!({
|
||||
"allowed_callers": ["direct"],
|
||||
"external_web_access": true,
|
||||
"external_web_access": expected_external_web_access,
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
|
||||
@@ -92,7 +92,7 @@ async fn emits_deprecation_notice_for_web_search_feature_flag_values() -> anyhow
|
||||
assert_eq!(
|
||||
details.as_deref(),
|
||||
Some(
|
||||
"Set `web_search` to `\"live\"`, `\"cached\"`, or `\"disabled\"` at the top level (or under a profile) in config.toml if you want to override it."
|
||||
"Set `web_search` to `\"live\"`, `\"indexed\"`, `\"cached\"`, or `\"disabled\"` at the top level (or under a profile) in config.toml if you want to override it."
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -274,3 +274,42 @@ location = { country = "US", city = "New York", timezone = "America/New_York" }
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn indexed_web_search_mode_sets_index_gate() {
|
||||
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 home = Arc::new(tempfile::TempDir::new().expect("create codex home"));
|
||||
std::fs::write(home.path().join("config.toml"), r#"web_search = "indexed""#)
|
||||
.expect("write config.toml");
|
||||
|
||||
let mut builder = test_codex().with_model("gpt-5.3-codex").with_home(home);
|
||||
let test = builder
|
||||
.build(&server)
|
||||
.await
|
||||
.expect("create test Codex conversation");
|
||||
|
||||
test.submit_turn_with_permission_profile(
|
||||
"hello indexed web search",
|
||||
PermissionProfile::Disabled,
|
||||
)
|
||||
.await
|
||||
.expect("submit turn");
|
||||
|
||||
let body = resp_mock.single_request().body_json();
|
||||
let tool = find_web_search_tool(&body);
|
||||
assert_eq!(
|
||||
(
|
||||
tool.get("external_web_access").and_then(Value::as_bool),
|
||||
tool.get("index_gated_web_access").and_then(Value::as_bool),
|
||||
),
|
||||
(Some(true), Some(true))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ use std::sync::Arc;
|
||||
|
||||
use codex_api::AllowedCaller;
|
||||
use codex_api::ApproximateLocation;
|
||||
use codex_api::ExternalWebAccess;
|
||||
use codex_api::ExternalWebAccessMode;
|
||||
use codex_api::LocationType;
|
||||
use codex_api::SearchContextSize;
|
||||
use codex_api::SearchFilters;
|
||||
@@ -73,14 +75,19 @@ fn search_settings(config: &Config, web_search_mode: WebSearchMode) -> SearchSet
|
||||
blocked_domains: None,
|
||||
}),
|
||||
allowed_callers: Some(vec![AllowedCaller::Direct]),
|
||||
external_web_access: Some(match web_search_mode {
|
||||
WebSearchMode::Live => true,
|
||||
WebSearchMode::Cached | WebSearchMode::Disabled => false,
|
||||
}),
|
||||
external_web_access: Some(external_web_access_for_mode(web_search_mode)),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn external_web_access_for_mode(web_search_mode: WebSearchMode) -> ExternalWebAccess {
|
||||
match web_search_mode {
|
||||
WebSearchMode::Disabled | WebSearchMode::Cached => ExternalWebAccess::Boolean(false),
|
||||
WebSearchMode::Indexed => ExternalWebAccess::Mode(ExternalWebAccessMode::Indexed),
|
||||
WebSearchMode::Live => ExternalWebAccess::Boolean(true),
|
||||
}
|
||||
}
|
||||
|
||||
impl ThreadLifecycleContributor<Config> for WebSearchExtension {
|
||||
fn on_thread_start<'a>(
|
||||
&'a self,
|
||||
@@ -149,9 +156,32 @@ mod tests {
|
||||
use super::AuthManager;
|
||||
use super::Config;
|
||||
use super::WebSearchExtensionConfig;
|
||||
use super::external_web_access_for_mode;
|
||||
use super::install;
|
||||
use crate::tool::RUN_TOOL_NAME;
|
||||
use crate::tool::WEB_NAMESPACE;
|
||||
use codex_api::ExternalWebAccess;
|
||||
use codex_api::ExternalWebAccessMode;
|
||||
use codex_protocol::config_types::WebSearchMode;
|
||||
|
||||
#[test]
|
||||
fn external_web_access_preserves_legacy_values_until_indexed() {
|
||||
assert_eq!(
|
||||
[
|
||||
WebSearchMode::Disabled,
|
||||
WebSearchMode::Cached,
|
||||
WebSearchMode::Indexed,
|
||||
WebSearchMode::Live,
|
||||
]
|
||||
.map(external_web_access_for_mode),
|
||||
[
|
||||
ExternalWebAccess::Boolean(false),
|
||||
ExternalWebAccess::Boolean(false),
|
||||
ExternalWebAccess::Mode(ExternalWebAccessMode::Indexed),
|
||||
ExternalWebAccess::Boolean(true),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn installed_extension_contributes_web_run_when_enabled() {
|
||||
|
||||
@@ -599,7 +599,7 @@ fn legacy_usage_notice(alias: &str, feature: Feature) -> (String, Option<String>
|
||||
}
|
||||
|
||||
fn web_search_details() -> &'static str {
|
||||
"Set `web_search` to `\"live\"`, `\"cached\"`, or `\"disabled\"` at the top level (or under a profile) in config.toml if you want to override it."
|
||||
"Set `web_search` to `\"live\"`, `\"indexed\"`, `\"cached\"`, or `\"disabled\"` at the top level (or under a profile) in config.toml if you want to override it."
|
||||
}
|
||||
|
||||
/// Keys accepted in `[features]` tables.
|
||||
|
||||
@@ -313,12 +313,13 @@ pub enum MultiAgentMode {
|
||||
#[derive(
|
||||
Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS, Default,
|
||||
)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum WebSearchMode {
|
||||
Disabled,
|
||||
#[default]
|
||||
Cached,
|
||||
Indexed,
|
||||
Live,
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,8 @@ pub enum ToolSpec {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
external_web_access: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
index_gated_web_access: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
filters: Option<ResponsesApiWebSearchFilters>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
user_location: Option<ResponsesApiWebSearchUserLocation>,
|
||||
|
||||
@@ -67,6 +67,7 @@ fn tool_spec_name_covers_all_variants() {
|
||||
assert_eq!(
|
||||
ToolSpec::WebSearch {
|
||||
external_web_access: Some(true),
|
||||
index_gated_web_access: None,
|
||||
filters: None,
|
||||
user_location: None,
|
||||
search_context_size: None,
|
||||
@@ -199,6 +200,7 @@ fn web_search_tool_spec_serializes_expected_wire_shape() {
|
||||
assert_eq!(
|
||||
serde_json::to_value(ToolSpec::WebSearch {
|
||||
external_web_access: Some(true),
|
||||
index_gated_web_access: None,
|
||||
filters: Some(ResponsesApiWebSearchFilters {
|
||||
allowed_domains: Some(vec!["example.com".to_string()]),
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user