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:
Winston Howes authored and GitHub committed 2026-06-19 05:35:57 -07:00
1 parent 7951397e4a
commit 3a2712ea14
24 files changed
+204 -23

No files matched your search

+33
View File
@@ -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()?;
+1 -1
View File
@@ -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,