Analyzed web search tool commit and config.
UnderstandExamined Codex tool specifications and enums.
UnderstandConfirmed only web search is server-side tool.
Verifyhave a look at this commit: https://github.com/mitsuhiko/pi-mono/commit/2b7e67873a88ef1ef76c40c988b1b218f4c55c14
Anthropic server-side web-search tool docs
The web search tool gives Claude direct access to real-time web content, allowing it to answer questions with up-to-date information beyond its knowledge cutoff. Claude automatically cites sources from search results as part of its answer.
Please reach out through our [feedback form](https://forms.gle/sWjBtsrNEY2oKGuE8) to share your experience with the web search tool.Web search is available on:
claude-sonnet-4-5-20250929)claude-sonnet-4-20250514)claude-3-7-sonnet-20250219)claude-haiku-4-5-20251001)claude-3-5-haiku-latest)claude-opus-4-5-20251101)claude-opus-4-1-20250805)claude-opus-4-20250514)When you add the web search tool to your API request:
Provide the web search tool in your API request:
```bash Shell curl https://api.anthropic.com/v1/messages \ --header "x-api-key: $ANTHROPIC_API_KEY" \ --header "anthropic-version: 2023-06-01" \ --header "content-type: application/json" \ --data '{ "model": "claude-sonnet-4-5", "max_tokens": 1024, "messages": [ { "role": "user", "content": "What is the weather in NYC?" } ], "tools": [{ "type": "web_search_20250305", "name": "web_search", "max_uses": 5 }] }' ```import anthropic
client = anthropic.Anthropic()
response = client.messages.create( model="claude-sonnet-4-5", max_tokens=[REDACTED] messages=[ { "role": "user", "content": "What's the weather in NYC?" } ], tools=[{ "type": "web_search_20250305", "name": "web_search", "max_uses": 5 }])print(response)import { Anthropic } from '@anthropic-ai/sdk';
const anthropic = new Anthropic();
async function main() { const response = await anthropic.messages.create({ model: "claude-sonnet-4-5", max_tokens: 1024, messages: [ { role: "user", content: "What's the weather in NYC?" } ], tools: [{ type: "web_search_20250305", name: "web_search", max_uses: 5 }] });
console.log(response);}
main().catch(console.error);The web search tool supports the following parameters:
{ "type": "web_search_20250305", "name": "web_search",
// Optional: Limit the number of searches per request "max_uses": 5,
// Optional: Only include results from these domains "allowed_domains": ["example.com", "trusteddomain.org"],
// Optional: Never include results from these domains "blocked_domains": ["untrustedsource.com"],
// Optional: Localize search results "user_location": { "type": "approximate", "city": "San Francisco", "region": "California", "country": "US", "timezone": "America/Los_Angeles" }}The max_uses parameter limits the number of searches performed. If Claude attempts more searches than allowed, the web_search_tool_result will be an error with the max_uses_exceeded error code.
When using domain filters:
example.com instead of https://example.com)example.com covers docs.example.com)docs.example.com returns only results from that subdomain, not from example.com or api.example.com)example.com/blog matches example.com/blog/post-1)allowed_domains or blocked_domains, but not both in the same request.Wildcard support:
*) is allowed per domain entry, and it must appear after the domain part (in the path)example.com/*, example.com/*/articles*.example.com, ex*.com, example.com/*/news/*Invalid domain formats will return an invalid_tool_input tool error.
The user_location parameter allows you to localize search results based on a user's location.
type: The type of location (must be approximate)city: The city nameregion: The region or statecountry: The countrytimezone: The IANA timezone ID.Here's an example response structure:
{ "role": "assistant", "content": [ // 1. Claude's decision to search { "type": "text", "text": "I'll search for when Claude Shannon was born." }, // 2. The search query used { "type": "server_tool_use", "id": "srvtoolu_01WYG3ziw53XMcoyKL4XcZmE", "name": "web_search", "input": { "query": "claude shannon birth date" } }, // 3. Search results { "type": "web_search_tool_result", "tool_use_id": "srvtoolu_01WYG3ziw53XMcoyKL4XcZmE", "content": [ { "type": "web_search_result", "url": "https://en.wikipedia.org/wiki/Claude_Shannon", "title": "Claude Shannon - Wikipedia", "encrypted_content": "EqgfCioIARgBIiQ3YTAwMjY1Mi1mZjM5LTQ1NGUtODgxNC1kNjNjNTk1ZWI3Y...", "page_age": "April 30, 2025" } ] }, { "text": "Based on the search results, ", "type": "text" }, // 4. Claude's response with citations { "text": "Claude Shannon was born on April 30, 1916, in Petoskey, Michigan", "type": "text", "citations": [ { "type": "web_search_result_location", "url": "https://en.wikipedia.org/wiki/Claude_Shannon", "title": "Claude Shannon - Wikipedia", "encrypted_index": "Eo8BCioIAhgBIiQyYjQ0OWJmZi1lNm..", "cited_text": "Claude Elwood Shannon (April 30, 1916 – February 24, 2001) was an American mathematician, electrical engineer, computer scientist, cryptographer and i..." } ] } ], "id": "msg_a930390d3a", "usage": { "input_tokens": 6039, "output_tokens": 931, "server_tool_use": { "web_search_requests": 1 } }, "stop_reason": "end_turn"}Search results include:
url: The URL of the source pagetitle: The title of the source pagepage_age: When the site was last updatedencrypted_content: Encrypted content that must be passed back in multi-turn conversations for citationsCitations are always enabled for web search, and each web_search_result_location includes:
url: The URL of the cited sourcetitle: The title of the cited sourceencrypted_index: A reference that must be passed back for multi-turn conversations.cited_text: Up to 150 characters of the cited contentThe web search citation fields cited_text, title, and url do not count towards input or output token usage.
When the web search tool encounters an error (such as hitting rate limits), the Claude API still returns a 200 (success) response. The error is represented within the response body using the following structure:
{ "type": "web_search_tool_result", "tool_use_id": "servertoolu_a93jad", "content": { "type": "web_search_tool_result_error", "error_code": "max_uses_exceeded" }}These are the possible error codes:
too_many_requests: Rate limit exceededinvalid_input: Invalid search query parametermax_uses_exceeded: Maximum web search tool uses exceededquery_too_long: Query exceeds maximum lengthunavailable: An internal error occurredpause_turn stop reasonThe response may include a pause_turn stop reason, which indicates that the API paused a long-running turn. You may provide the response back as-is in a subsequent request to let Claude continue its turn, or modify the content if you wish to interrupt the conversation.
Web search works with prompt caching. To enable prompt caching, add at least one cache_control breakpoint in your request. The system will automatically cache up until the last web_search_tool_result block when executing the tool.
For multi-turn conversations, set a cache_control breakpoint on or after the last web_search_tool_result block to reuse cached content.
For example, to use prompt caching with web search for a multi-turn conversation:
```python import anthropicclient = anthropic.Anthropic()
messages = [ { "role": "user", "content": "What's the current weather in San Francisco today?" } ]
response1 = client.messages.create( model="claude-sonnet-4-5", max_tokens=[REDACTED] messages=messages, tools=[{ "type": "web_search_20250305", "name": "web_search", "user_location": { "type": "approximate", "city": "San Francisco", "region": "California", "country": "US", "timezone": "America/Los_Angeles" } }] )
messages.append({ "role": "assistant", "content": response1.content })
messages.append({ "role": "user", "content": "Should I expect rain later this week?", "cache_control": {"type": "ephemeral"} # Cache up to this point })
response2 = client.messages.create( model="claude-sonnet-4-5", max_tokens=[REDACTED] messages=messages, tools=[{ "type": "web_search_20250305", "name": "web_search", "user_location": { "type": "approximate", "city": "San Francisco", "region": "California", "country": "US", "timezone": "America/Los_Angeles" } }] )
print(f"Cache read tokens: {response2.usage.get('cache_read_input_tokens', 0)}")
</CodeGroup>
## Streaming
With streaming enabled, you'll receive search events as part of the stream. There will be a pause while the search executes:
```javascriptevent: message_startdata: {"type": "message_start", "message": {"id": "msg_abc123", "type": "message"}}
event: content_block_startdata: {"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}}
// Claude's decision to search
event: content_block_startdata: {"type": "content_block_start", "index": 1, "content_block": {"type": "server_tool_use", "id": "srvtoolu_xyz789", "name": "web_search"}}
// Search query streamedevent: content_block_deltadata: {"type": "content_block_delta", "index": 1, "delta": {"type": "input_json_delta", "partial_json": "{\"query\":\"latest quantum computing breakthroughs 2025\"}"}}
// Pause while search executes
// Search results streamedevent: content_block_startdata: {"type": "content_block_start", "index": 2, "content_block": {"type": "web_search_tool_result", "tool_use_id": "srvtoolu_xyz789", "content": [{"type": "web_search_result", "title": "Quantum Computing Breakthroughs in 2025", "url": "https://example.com"}]}}
// Claude's response with citations (omitted in this example)You can include the web search tool in the Messages Batches API. Web search tool calls through the Messages Batches API are priced the same as those in regular Messages API requests.
Web search usage is charged in addition to token usage:
"usage": { "input_tokens": 105, "output_tokens": 6039, "cache_read_input_tokens": 7123, "cache_creation_input_tokens": 7345, "server_tool_use": { "web_search_requests": 1 }}Web search is available on the Claude API for ** +424 lines0 per 1,000 searches**, plus standard token costs for search-generated content. Web search results retrieved throughout a conversation are counted as input tokens, in search iterations executed during a single turn and in subsequent conversation turns.
Each web search counts as one use, regardless of the number of results returned. If an error occurs during web search, the web search will not be billed.
clone the openai/codex repo and see how they do websearch
i propose we formalize this properly. passing a tool called web_search sucks. this should be encoded via StreamOptions (and hence SimplifiedStreamOptions) in some way. providers are free to ignore it. we might want to add additional server-side tools in the future.
that's for "how to enable this in both pi-ai and pi-coding-agent". the next issue is how we combine this with tools specified by the provider. it appears for anthropic, we add a web_search tool if enabled. this can clash with user provided tools, so user provided tools need to be renamed in that case before being passed to anthropic. we already do some kind of renaming in the anthropic provider (for the anthropic endpoint). when sending to the llm endpoint the server side web search must be called web_search, while any user provided tool with name web_search must be called user_web_search. when persisting to the pi-ai context, we need to rename the server side tool calls (and results if they contain the tool name) from web_search to something that uniquely identifies it as a server side web_search. when the context is later serialized for the provider again, we need to do the name swapping again for old tool calls/results.
finally, we also need to deal with the tool result. from what i can gather in the anthropic docs, they return a content signature along with other shit. i suppose since tool results are free form anyways for the most part, that's fine. however, coding-agent @packages/coding-agent/src/modes/interactive/components/tool-execution.ts may have to accomodate that somehow?
Planning repo cloning and commit inspection
Preparing to clone the relevant repos as the user requested and inspect the specified commit to analyze their websearch implementation before reporting findings.
Deciding to clone both repos for commit inspection
Planning to clone both repos into temporary directories to examine the commit directly using git commands, ensuring compliance by using bash for file operations and read tool for file contents as required.
>lsAGENTS.md
biome.json
CONTRIBUTING.md
LICENSE
node_modules
package-lock.json
package.json
packages
pi-mono.code-workspace
pi-test.sh
README.md
scripts
test.sh
thisisa
todo.md
tsconfig.base.json
tsconfig.jsonStarting codebase exploration
>mkdir -p /tmp/pi-review && cd /tmp/pi-review && git clone https://github.com/openai/codex.gitCloning into 'codex'...Searching Codex for web search references
>cd /tmp/pi-review/codex && rg -n "web_search|websearch|search"codex-rs/core/models.json:383: "base_instructions": "You are Codex, based on GPT-5. You are running as a coding agent in the Codex CLI on a user's computer.\n\n## General\n\n- When searching for text or files, prefer using `rg` or `rg --files` respectively because `rg` is much faster than alternatives like `grep`. (If the `rg` command is not found, then use alternatives.)\n\n## Editing constraints\n\n- Default to ASCII when editing or creating files. Only introduce non-ASCII or other Unicode characters when there is a clear justification and the file already uses them.\n- Add succinct code comments that explain what is going on if code is not self-explanatory. You should not add comments like \"Assigns the value to the variable\", but a brief comment might be useful ahead of a complex code block that the user would otherwise have to spend time parsing out. Usage of these comments should be rare.\n- Try to use apply_patch for single file edits, but it is fine to explore other options to make the edit if it does not work well. Do not use apply_patch for changes that are auto-generated (i.e. generating package.json or running a lint or format command like gofmt) or when scripting is more efficient (such as search and replacing a string across a codebase).\n- You may be in a dirty git worktree.\n * NEVER revert existing changes you did not make unless explicitly requested, since these changes were made by the user.\n * If asked to make a commit or code edits and there are unrelated changes to your work or changes that you didn't make in those files, don't revert those changes.\n * If the changes are in files you've touched recently, you should read carefully and understand how you can work with the changes rather than reverting them.\n * If the changes are in unrelated files, just ignore them and don't revert them.\n- Do not amend a commit unless explicitly requested to do so.\n- While you are working, you might notice unexpected changes that you didn't make. If this happens, STOP IMMEDIATELY and ask the user how they would like to proceed.\n- **NEVER** use destructive commands like `git reset --hard` or `git checkout --` unless specifically requested or approved by the user.\n\n## Plan tool\n\nWhen using the planning tool:\n- Skip using the planning tool for straightforward tasks (roughly the easiest 25%).\n- Do not make single-step plans.\n- When you made a plan, update it after having performed one of the sub-tasks that you shared on the plan.\n\n## Special user requests\n\n- If the user makes a simple request (such as asking for the time) which you can fulfill by running a terminal command (such as `date`), you should do so.\n- If the user asks for a \"review\", default to a code review mindset: prioritise identifying bugs, risks, behavioural regressions, and missing tests. Findings must be the primary focus of the response - keep summaries or overviews brief and only after enumerating the issues. Present findings first (ordered by severity with file/line references), follow with open questions or assumptions, and offer a change-summary only as a secondary detail. If no findings are discovered, state that explicitly and mention any residual risks or testing gaps.\n\n## Presenting your work and final message\n\nYou are producing plain text that will later be styled by the CLI. Follow these rules exactly. Formatting should make results easy to scan, but not feel mechanical. Use judgment to decide how much structure adds value.\n\n- Default: be very concise; friendly coding teammate tone.\n- Ask only when needed; suggest ideas; mirror the user's style.\n- For substantial work, summarize clearly; follow final‑answer formatting.\n- Skip heavy formatting for simple confirmations.\n- Don't dump large files you've written; reference paths only.\n- No \"save/copy this file\" - User is on the same machine.\n- Offer logical next steps (tests, commits, build) briefly; add verify steps if you couldn't do something.\n- For code changes:\n * Lead with a quick explanation of the change, and then give more details on the context covering where and why a change was made. Do not start this explanation with \"summary\", just jump right in.\n * If there are natural next steps the user may want to take, suggest them at the end of your response. Do not make suggestions if there are no natural next steps.\n * When suggesting multiple options, use numeric lists for the suggestions so the user can quickly respond with a single number.\n- The user does not command execution outputs. When asked to show the output of a command (e.g. `git show`), relay the important details in your answer or summarize the key lines so the user understands the result.\n\n### Final answer structure and style guidelines\n\n- Plain text; CLI handles styling. Use structure only when it helps scanability.\n- Headers: optional; short Title Case (1-3 words) wrapped in **…**; no blank line before the first bullet; add only if they truly help.\n- Bullets: use - ; merge related points; keep to one line when possible; 4–6 per list ordered by importance; keep phrasing consistent.\n- Monospace: backticks for commands/paths/env vars/code ids and inline examples; use for literal keyword bullets; never combine with **.\n- Code samples or multi-line snippets should be wrapped in fenced code blocks; include an info string as often as possible.\n- Structure: group related bullets; order sections general → specific → supporting; for subsections, start with a bolded keyword bullet, then items; match complexity to the task.\n- Tone: collaborative, concise, factual; present tense, active voice; self‑contained; no \"above/below\"; parallel wording.\n- Don'ts: no nested bullets/hierarchies; no ANSI codes; don't cram unrelated keywords; keep keyword lists short—wrap/reformat if long; avoid naming formatting styles in answers.\n- Adaptation: code explanations → precise, structured with code refs; simple tasks → lead with outcome; big changes → logical walkthrough + rationale + next actions; casual one-offs → plain sentences, no headers/bullets.\n- File References: When referencing files in your response, make sure to include the relevant start line and always follow the below rules:\n * Use inline code to make file paths clickable.\n * Each reference should have a stand alone path. Even if it's the same file.\n * Accepted: absolute, workspace‑relative, a/ or b/ diff prefixes, or bare filename/suffix.\n * Line/column (1‑based, optional): :line[:column] or #Lline[Ccolumn] (column defaults to 1).\n * Do not use URIs like file://, vscode://, or https://.\n * Do not provide range of lines\n * Examples: src/app.ts, src/app.ts:42, b/server/index.js#L10, C:\\repo\\project\\main.rs:12:5\n",
codex-rs/core/src/parse_command.rs:166: fn supports_searching_for_navigate_to_route() -> anyhow::Result<()> {
codex-rs/core/src/parse_command.rs:235: fn rg_files_with_matches_flags_are_search() {
codex-rs/core/src/parse_command.rs:599: fn grep_files_with_matches_flags_are_search() {
codex-rs/core/src/parse_command.rs:887: fn supports_sed_n_then_nl_as_search() {
codex-rs/core/src/parse_command.rs:888: // Ensure `sed -n '<range>' <file> | nl -ba` is summarized as a search for that file.
codex-rs/core/src/parse_command.rs:1143: fn ag_ack_pt_files_with_matches_flags_are_search() {
codex-rs/core/src/parse_command.rs:1830: "--search-path",
codex-rs/core/src/parse_command.rs:2290: "--file-search-regex",
codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__binary_size_ideal_response.snap:30: │ file-search linux-sandbox login mcp-client mcp-server mcp-types ollama
codex-rs/core/tests/suite/grep_files.rs:40: let search_dir = test.cwd.path().join("src");
codex-rs/core/tests/suite/grep_files.rs:41: std::fs::create_dir_all(&search_dir)?;
codex-rs/core/tests/suite/grep_files.rs:42: let alpha = search_dir.join("alpha.rs");
codex-rs/core/tests/suite/grep_files.rs:43: let beta = search_dir.join("beta.rs");
codex-rs/core/tests/suite/grep_files.rs:44: let gamma = search_dir.join("gamma.txt");
codex-rs/core/tests/suite/grep_files.rs:52: "path": search_dir.to_string_lossy(),
codex-rs/core/tests/suite/grep_files.rs:98: let search_dir = test.cwd.path().join("logs");
codex-rs/core/tests/suite/grep_files.rs:99: std::fs::create_dir_all(&search_dir)?;
codex-rs/core/tests/suite/grep_files.rs:100: std::fs::write(search_dir.join("output.txt"), "no hits here")?;
codex-rs/core/tests/suite/grep_files.rs:105: "path": search_dir.to_string_lossy(),
codex-rs/core/tests/suite/grep_files.rs:112: test.submit_turn("search again").await?;
codex-rs/Cargo.toml:25: "file-search",
codex-rs/Cargo.toml:84:codex-file-search = { path = "file-search" }
codex-rs/rmcp-client/src/program_resolver.rs:34:/// uses the `which` crate to search the `PATH` environment variable and find
codex-rs/rmcp-client/src/program_resolver.rs:46: // Extract PATH from environment for search locations
codex-rs/rmcp-client/src/program_resolver.rs:47: let search_path = env.get("PATH");
codex-rs/rmcp-client/src/program_resolver.rs:50: match which::which_in(&program, search_path, &cwd) {
codex-rs/core/gpt_5_2_prompt.md:128:- Use `git log` and `git blame` to search the history of the codebase if additional context is required.
codex-rs/core/gpt_5_2_prompt.md:250:- When searching for text or files, prefer using `rg` or `rg --files` respectively because `rg` is much faster than alternatives like `grep`. (If the `rg` command is not found, then use alternatives.)
codex-rs/core/Cargo.toml:34:codex-file-search = { workspace = true }
codex-rs/core/tests/suite/prompt_caching.rs:94: // Keep tool expectations stable when the default web_search mode changes.
codex-rs/core/tests/suite/prompt_caching.rs:95: config.web_search_mode = Some(WebSearchMode::Cached);
codex-rs/core/tests/suite/prompt_caching.rs:142: "web_search",
codex-rs/core/gpt_5_codex_prompt.md:5:- When searching for text or files, prefer using `rg` or `rg --files` respectively because `rg` is much faster than alternatives like `grep`. (If the `rg` command is not found, then use alternatives.)
codex-rs/core/gpt_5_codex_prompt.md:11:- Try to use apply_patch for single file edits, but it is fine to explore other options to make the edit if it does not work well. Do not use apply_patch for changes that are auto-generated (i.e. generating package.json or running a lint or format command like gofmt) or when scripting is more efficient (such as search and replacing a string across a codebase).
codex-rs/core/tests/suite/client.rs:1215: id: Some("web-search-id".into()),
codex-rs/core/tests/suite/client.rs:1278: assert_eq!(body["input"][2]["id"].as_str(), Some("web-search-id"));
codex-rs/core/src/features/legacy.rs:33: legacy_key: "web_search",
codex-rs/core/src/features/legacy.rs:57: pub tools_web_search: Option<bool>,
codex-rs/core/src/features/legacy.rs:83: self.tools_web_search,
codex-rs/core/src/features/legacy.rs:84: "tools.web_search",
codex-rs/core/src/client_common.rs:172: // TODO: Understand why we get an error on web_search although the API docs say it's supported.
codex-rs/core/src/client_common.rs:173: // https://platform.openai.com/docs/guides/tools-web-search?api-mode=responses#:~:text=%7B%20type%3A%20%22web_search%22%20%7D%2C
codex-rs/core/src/client_common.rs:174: // The `external_web_access` field determines whether the web search is over cached or live content.
codex-rs/core/src/client_common.rs:175: // https://platform.openai.com/docs/guides/tools-web-search#live-internet-access
codex-rs/core/src/client_common.rs:176: #[serde(rename = "web_search")]
codex-rs/core/src/client_common.rs:190: ToolSpec::WebSearch { .. } => "web_search",
codex-rs/core/src/codex.rs:108:use crate::config::resolve_web_search_mode_for_turn;
codex-rs/core/src/codex.rs:636: per_turn_config.web_search_mode = Some(resolve_web_search_mode_for_turn(
codex-rs/core/src/codex.rs:637: per_turn_config.web_search_mode,
codex-rs/core/src/codex.rs:682: web_search_mode: per_turn_config.web_search_mode,
codex-rs/core/src/codex.rs:3157: // For reviews, disable web_search and view_image regardless of global settings.
codex-rs/core/src/codex.rs:3162: let review_web_search_mode = WebSearchMode::Disabled;
codex-rs/core/src/codex.rs:3166: web_search_mode: Some(review_web_search_mode),
codex-rs/core/src/codex.rs:3178: per_turn_config.web_search_mode = Some(review_web_search_mode);
codex-rs/core/src/lib.rs:76:pub mod web_search;
codex-rs/tui/src/file_search.rs:1://! Session-based orchestration for `@` file searches.
codex-rs/tui/src/file_search.rs:5://! `codex-file-search` session for the current search root, updates the query
codex-rs/tui/src/file_search.rs:8:use codex_file_search as file_search;
codex-rs/tui/src/file_search.rs:22: search_dir: PathBuf,
codex-rs/tui/src/file_search.rs:28: session: Option<file_search::FileSearchSession>,
codex-rs/tui/src/file_search.rs:33: pub fn new(search_dir: PathBuf, tx: AppEventSender) -> Self {
codex-rs/tui/src/file_search.rs:40: search_dir,
codex-rs/tui/src/file_search.rs:76: let session = file_search::create_session(
codex-rs/tui/src/file_search.rs:77: &self.search_dir,
codex-rs/tui/src/file_search.rs:78: file_search::SessionOptions {
codex-rs/tui/src/file_search.rs:90: tracing::warn!("file search session failed to start: {err}");
codex-rs/tui/src/file_search.rs:104: fn send_snapshot(&self, snapshot: &file_search::FileSearchSnapshot) {
codex-rs/tui/src/file_search.rs:122:impl file_search::SessionReporter for TuiSessionReporter {
codex-rs/tui/src/file_search.rs:123: fn on_update(&self, snapshot: &file_search::FileSearchSnapshot) {
codex-rs/tui/src/history_cell.rs:47:use codex_core::web_search::web_search_detail;
codex-rs/tui/src/history_cell.rs:1383:fn web_search_header(completed: bool) -> &'static str {
codex-rs/tui/src/history_cell.rs:1439: let header = web_search_header(self.completed);
codex-rs/tui/src/history_cell.rs:1440: let detail = web_search_detail(self.action.as_ref(), &self.query);
codex-rs/tui/src/history_cell.rs:1450:pub(crate) fn new_active_web_search_call(
codex-rs/tui/src/history_cell.rs:1458:pub(crate) fn new_web_search_call(
codex-rs/tui/src/history_cell.rs:2137: recent_chunks: vec!["searching...".to_string()],
codex-rs/tui/src/history_cell.rs:2295: fn web_search_history_cell_snapshot() {
codex-rs/tui/src/history_cell.rs:2297: "example search query with several generic words to exercise wrapping".to_string();
codex-rs/tui/src/history_cell.rs:2298: let cell = new_web_search_call(
codex-rs/tui/src/history_cell.rs:2309: fn web_search_history_cell_wraps_with_indented_continuation() {
codex-rs/tui/src/history_cell.rs:2311: "example search query with several generic words to exercise wrapping".to_string();
codex-rs/tui/src/history_cell.rs:2312: let cell = new_web_search_call(
codex-rs/tui/src/history_cell.rs:2322: "• Searched example search query with several generic words to".to_string(),
codex-rs/tui/src/history_cell.rs:2329: fn web_search_history_cell_short_query_does_not_wrap() {
codex-rs/tui/src/history_cell.rs:2331: let cell = new_web_search_call(
codex-rs/tui/src/history_cell.rs:2342: fn web_search_history_cell_transcript_snapshot() {
codex-rs/tui/src/history_cell.rs:2344: "example search query with several generic words to exercise wrapping".to_string();
codex-rs/tui/src/history_cell.rs:2345: let cell = new_web_search_call(
codex-rs/tui/src/history_cell.rs:2358: server: "search".into(),
codex-rs/tui/src/history_cell.rs:2375: server: "search".into(),
codex-rs/tui/src/history_cell.rs:2464: server: "search".into(),
codex-rs/tui/src/history_cell.rs:2486: server: "search".into(),
codex-rs/core/src/context_manager/history.rs:310:/// tool calls, tool outputs, shell calls, and web-search calls).
codex-rs/tui/src/app_event.rs:17:use codex_file_search::FileMatch;
codex-rs/tui/src/app_event.rs:82: /// Kick off an asynchronous file search for the given query (text after
codex-rs/tui/src/app_event.rs:83: /// the `@`). Previous searches may be cancelled by the app layer so there
codex-rs/tui/src/app_event.rs:84: /// is at most one in-flight search.
codex-rs/tui/src/app_event.rs:87: /// Result of a completed asynchronous file search. The `query` echoes the
codex-rs/tui/src/app_event.rs:88: /// original search term so the UI can decide whether the results are
codex-rs/tui/Cargo.toml:42:codex-file-search = { workspace = true }
codex-rs/tui/src/lib.rs:74:mod file_search;
codex-rs/tui/src/lib.rs:147: // Map the legacy --search flag to the canonical web_search mode.
codex-rs/tui/src/lib.rs:148: if cli.web_search {
codex-rs/tui/src/lib.rs:151: .push("web_search=\"live\"".to_string());
codex-rs/tui/src/bottom_pane/file_search_popup.rs:1:use codex_file_search::FileMatch;
codex-rs/tui/src/bottom_pane/file_search_popup.rs:14:/// Visual state for the file-search popup.
codex-rs/tui/src/bottom_pane/file_search_popup.rs:19: /// a search is still in-flight.
codex-rs/tui/src/bottom_pane/file_search_popup.rs:23: /// Cached matches; paths relative to the search dir.
codex-rs/tui/src/bottom_pane/file_search_popup.rs:101: // yet (e.g. initial search or query with no results) reserve a single
codex-rs/tui/src/bottom_pane/file_search_popup.rs:104: // remains stable while a newer search is in-flight.
codex-rs/tui/src/bottom_pane/skill_popup.rs:25: pub(crate) search_terms: Vec<String>,
codex-rs/tui/src/bottom_pane/skill_popup.rs:130: for term in &mention.search_terms {
codex-rs/tui/src/snapshots/codex_tui__history_cell__tests__completed_mcp_tool_call_multiple_outputs_snapshot.snap:7: └ search.find_docs({"query":"ratatui
codex-rs/core/tests/suite/web_search_cached.rs:19:fn find_web_search_tool(body: &Value) -> &Value {
codex-rs/core/tests/suite/web_search_cached.rs:24: .find(|tool| tool.get("type").and_then(Value::as_str) == Some("web_search"))
codex-rs/core/tests/suite/web_search_cached.rs:25: .expect("tools should include a web_search tool")
codex-rs/core/tests/suite/web_search_cached.rs:29:async fn web_search_mode_cached_sets_external_web_access_false() {
codex-rs/core/tests/suite/web_search_cached.rs:39: config.web_search_mode = Some(WebSearchMode::Cached);
codex-rs/core/tests/suite/web_search_cached.rs:46: test.submit_turn("hello cached web search")
codex-rs/core/tests/suite/web_search_cached.rs:51: let tool = find_web_search_tool(&body);
codex-rs/core/tests/suite/web_search_cached.rs:55: "web_search cached mode should force external_web_access=false"
codex-rs/core/tests/suite/web_search_cached.rs:60:async fn web_search_mode_takes_precedence_over_legacy_flags() {
codex-rs/core/tests/suite/web_search_cached.rs:71: config.web_search_mode = Some(WebSearchMode::Cached);
codex-rs/core/tests/suite/web_search_cached.rs:83: let tool = find_web_search_tool(&body);
codex-rs/core/tests/suite/web_search_cached.rs:87: "web_search mode should win over legacy web_search_request"
codex-rs/core/tests/suite/web_search_cached.rs:92:async fn web_search_mode_defaults_to_cached_when_unset() {
codex-rs/core/tests/suite/web_search_cached.rs:102: config.web_search_mode = None;
codex-rs/core/tests/suite/web_search_cached.rs:111: test.submit_turn_with_policy("hello default cached web search", SandboxPolicy::ReadOnly)
codex-rs/core/tests/suite/web_search_cached.rs:116: let tool = find_web_search_tool(&body);
codex-rs/core/tests/suite/web_search_cached.rs:120: "default web_search should be cached when unset"
codex-rs/core/tests/suite/web_search_cached.rs:125:async fn web_search_mode_updates_between_turns_with_sandbox_policy() {
codex-rs/core/tests/suite/web_search_cached.rs:138: config.web_search_mode = None;
codex-rs/core/tests/suite/web_search_cached.rs:158: let first_tool = find_web_search_tool(&first_body);
codex-rs/core/tests/suite/web_search_cached.rs:164: "read-only policy should default web_search to cached"
codex-rs/core/tests/suite/web_search_cached.rs:168: let second_tool = find_web_search_tool(&second_body);
codex-rs/core/tests/suite/web_search_cached.rs:174: "danger-full-access policy should default web_search to live"
codex-rs/tui/src/snapshots/codex_tui__history_cell__tests__web_search_history_cell_snapshot.snap:5:• Searched example search query with several generic words to
codex-rs/tui/src/bottom_pane/mod.rs:32:use codex_file_search::FileMatch;
codex-rs/tui/src/bottom_pane/mod.rs:60:mod file_search_popup;
codex-rs/tui/src/bottom_pane/mod.rs:766: pub(crate) fn on_file_search_result(&mut self, query: String, matches: Vec<FileMatch>) {
codex-rs/tui/src/bottom_pane/mod.rs:767: self.composer.on_file_search_result(query, matches);
codex-rs/tui/src/bottom_pane/chat_composer.rs:6://! - Routing keys to the active popup (slash commands, file search, skill/apps mentions).
codex-rs/tui/src/bottom_pane/chat_composer.rs:106:use super::file_search_popup::FileSearchPopup;
codex-rs/tui/src/bottom_pane/chat_composer.rs:162:use codex_file_search::FileMatch;
codex-rs/tui/src/bottom_pane/chat_composer.rs:869: /// Integrate results from an asynchronous file search.
codex-rs/tui/src/bottom_pane/chat_composer.rs:870: pub(crate) fn on_file_search_result(&mut self, query: String, matches: Vec<FileMatch>) {
codex-rs/tui/src/bottom_pane/chat_composer.rs:952: /// Return true if either the slash-command popup or the file-search popup is active.
codex-rs/tui/src/bottom_pane/chat_composer.rs:1244: /// Handle key events when file search popup is visible.
codex-rs/tui/src/bottom_pane/chat_composer.rs:2492: self.sync_file_search_popup(token);
codex-rs/tui/src/bottom_pane/chat_composer.rs:2586: // file-search popup over the slash popup so users can insert a file path
codex-rs/tui/src/bottom_pane/chat_composer.rs:2629: /// Synchronize `self.file_search_popup` with the current text in the textarea.
codex-rs/tui/src/bottom_pane/chat_composer.rs:2631: fn sync_file_search_popup(&mut self, query: String) {
codex-rs/tui/src/bottom_pane/chat_composer.rs:2704: let search_terms = if display_name == skill.name {
codex-rs/tui/src/bottom_pane/chat_composer.rs:2713: search_terms,
codex-rs/tui/src/bottom_pane/chat_composer.rs:2729: let search_terms = vec![display_name.clone(), connector.id.clone(), slug.clone()];
codex-rs/tui/src/bottom_pane/chat_composer.rs:2735: search_terms,
codex-rs/core/src/config/mod.rs:314: /// Explicit or feature-derived web search mode.
codex-rs/core/src/config/mod.rs:315: pub web_search_mode: Option<WebSearchMode>,
codex-rs/core/src/config/mod.rs:919: /// Controls the web search tool mode: disabled, cached, or live.
codex-rs/core/src/config/mod.rs:920: pub web_search: Option<WebSearchMode>,
codex-rs/core/src/config/mod.rs:944: /// Markers used to detect the project root when searching parent
codex-rs/core/src/config/mod.rs:1032: #[serde(default, alias = "web_search_request")]
codex-rs/core/src/config/mod.rs:1033: pub web_search: Option<bool>,
codex-rs/core/src/config/mod.rs:1052: web_search: tools_toml.web_search,
codex-rs/core/src/config/mod.rs:1195: pub tools_web_search_request: Option<bool>,
codex-rs/core/src/config/mod.rs:1229:/// Resolve the web search mode from explicit config and feature flags.
codex-rs/core/src/config/mod.rs:1230:fn resolve_web_search_mode(
codex-rs/core/src/config/mod.rs:1235: if let Some(mode) = config_profile.web_search.or(config_toml.web_search) {
codex-rs/core/src/config/mod.rs:1247:pub(crate) fn resolve_web_search_mode_for_turn(
codex-rs/core/src/config/mod.rs:1298: tools_web_search_request: override_tools_web_search_request,
codex-rs/core/src/config/mod.rs:1323: web_search_request: override_tools_web_search_request,
codex-rs/core/src/config/mod.rs:1382: let web_search_mode = resolve_web_search_mode(&cfg, &config_profile, &features);
codex-rs/core/src/config/mod.rs:1600: web_search_mode,
codex-rs/core/src/config/mod.rs:2308: fn web_search_mode_defaults_to_none_if_unset() {
codex-rs/core/src/config/mod.rs:2313: assert_eq!(resolve_web_search_mode(&cfg, &profile, &features), None);
codex-rs/core/src/config/mod.rs:2317: fn web_search_mode_prefers_profile_over_legacy_flags() {
codex-rs/core/src/config/mod.rs:2320: web_search: Some(WebSearchMode::Live),
codex-rs/core/src/config/mod.rs:2327: resolve_web_search_mode(&cfg, &profile, &features),
codex-rs/core/src/config/mod.rs:2333: fn web_search_mode_disabled_overrides_legacy_request() {
codex-rs/core/src/config/mod.rs:2335: web_search: Some(WebSearchMode::Disabled),
codex-rs/core/src/config/mod.rs:2343: resolve_web_search_mode(&cfg, &profile, &features),
codex-rs/core/src/config/mod.rs:2349: fn web_search_mode_for_turn_defaults_to_cached_when_unset() {
codex-rs/core/src/config/mod.rs:2350: let mode = resolve_web_search_mode_for_turn(None, &SandboxPolicy::ReadOnly);
codex-rs/core/src/config/mod.rs:2356: fn web_search_mode_for_turn_defaults_to_live_for_danger_full_access() {
codex-rs/core/src/config/mod.rs:2357: let mode = resolve_web_search_mode_for_turn(None, &SandboxPolicy::DangerFullAccess);
codex-rs/core/src/config/mod.rs:2363: fn web_search_mode_for_turn_prefers_explicit_value() {
codex-rs/core/src/config/mod.rs:2364: let mode = resolve_web_search_mode_for_turn(
codex-rs/core/src/config/mod.rs:2379: tools_web_search: Some(false),
codex-rs/core/src/config/mod.rs:3691: // Make it look like a Git repo so it does not search for AGENTS.md in
codex-rs/core/src/config/mod.rs:3808: web_search_mode: None,
codex-rs/core/src/config/mod.rs:3892: web_search_mode: None,
codex-rs/core/src/config/mod.rs:3991: web_search_mode: None,
codex-rs/core/src/config/mod.rs:4076: web_search_mode: None,
codex-rs/tui/src/snapshots/codex_tui__resume_picker__tests__resume_picker_screen.snap:7:Type to search
codex-rs/tui/src/bottom_pane/list_selection_view.rs:45: pub search_value: Option<String>,
codex-rs/tui/src/bottom_pane/list_selection_view.rs:55: pub is_searchable: bool,
codex-rs/tui/src/bottom_pane/list_selection_view.rs:56: pub search_placeholder: Option<String>,
codex-rs/tui/src/bottom_pane/list_selection_view.rs:69: is_searchable: false,
codex-rs/tui/src/bottom_pane/list_selection_view.rs:70: search_placeholder: None,
codex-rs/tui/src/bottom_pane/list_selection_view.rs:84: is_searchable: bool,
codex-rs/tui/src/bottom_pane/list_selection_view.rs:85: search_query: String,
codex-rs/tui/src/bottom_pane/list_selection_view.rs:86: search_placeholder: Option<String>,
codex-rs/tui/src/bottom_pane/list_selection_view.rs:112: is_searchable: params.is_searchable,
codex-rs/tui/src/bottom_pane/list_selection_view.rs:113: search_query: String::new(),
codex-rs/tui/src/bottom_pane/list_selection_view.rs:114: search_placeholder: if params.is_searchable {
codex-rs/tui/src/bottom_pane/list_selection_view.rs:115: params.search_placeholder
codex-rs/tui/src/bottom_pane/list_selection_view.rs:142: (!self.is_searchable)
codex-rs/tui/src/bottom_pane/list_selection_view.rs:148: if self.is_searchable && !self.search_query.is_empty() {
codex-rs/tui/src/bottom_pane/list_selection_view.rs:149: let query_lower = self.search_query.to_lowercase();
codex-rs/tui/src/bottom_pane/list_selection_view.rs:154: item.search_value
codex-rs/tui/src/bottom_pane/list_selection_view.rs:204: let wrap_prefix = if self.is_searchable {
codex-rs/tui/src/bottom_pane/list_selection_view.rs:205: // The number keys don't work when search is enabled (since we let the
codex-rs/tui/src/bottom_pane/list_selection_view.rs:206: // numbers be used for the search query).
codex-rs/tui/src/bottom_pane/list_selection_view.rs:276: pub(crate) fn set_search_query(&mut self, query: String) {
codex-rs/tui/src/bottom_pane/list_selection_view.rs:277: self.search_query = query;
codex-rs/tui/src/bottom_pane/list_selection_view.rs:347: } if !self.is_searchable => self.move_up(),
codex-rs/tui/src/bottom_pane/list_selection_view.rs:366: } if !self.is_searchable => self.move_down(),
codex-rs/tui/src/bottom_pane/list_selection_view.rs:370: } if self.is_searchable => {
codex-rs/tui/src/bottom_pane/list_selection_view.rs:371: self.search_query.pop();
codex-rs/tui/src/bottom_pane/list_selection_view.rs:383: } if self.is_searchable
codex-rs/tui/src/bottom_pane/list_selection_view.rs:387: self.search_query.push(c);
codex-rs/tui/src/bottom_pane/list_selection_view.rs:394: } if !self.is_searchable
codex-rs/tui/src/bottom_pane/list_selection_view.rs:447: if self.is_searchable {
codex-rs/tui/src/bottom_pane/list_selection_view.rs:492: let [header_area, _, search_area, list_area] = Layout::vertical([
codex-rs/tui/src/bottom_pane/list_selection_view.rs:495: Constraint::Length(if self.is_searchable { 1 } else { 0 }),
codex-rs/tui/src/bottom_pane/list_selection_view.rs:512: if self.is_searchable {
codex-rs/tui/src/bottom_pane/list_selection_view.rs:513: Line::from(self.search_query.clone()).render(search_area, buf);
codex-rs/tui/src/bottom_pane/list_selection_view.rs:514: let query_span: Span<'static> = if self.search_query.is_empty() {
codex-rs/tui/src/bottom_pane/list_selection_view.rs:515: self.search_placeholder
codex-rs/tui/src/bottom_pane/list_selection_view.rs:520: self.search_query.clone().into()
codex-rs/tui/src/bottom_pane/list_selection_view.rs:522: Line::from(query_span).render(search_area, buf);
codex-rs/tui/src/bottom_pane/list_selection_view.rs:698: fn renders_search_query_line_when_enabled() {
codex-rs/tui/src/bottom_pane/list_selection_view.rs:713: is_searchable: true,
codex-rs/tui/src/bottom_pane/list_selection_view.rs:714: search_placeholder: Some("Type to search branches".to_string()),
codex-rs/tui/src/bottom_pane/list_selection_view.rs:719: view.set_search_query("filters".to_string());
codex-rs/tui/src/bottom_pane/list_selection_view.rs:724: "expected search query line to include rendered query, got {lines:?}"
codex-rs/tui/src/bottom_pane/command_popup.rs:170: // Support both search styles:
codex-rs/tui/src/bottom_pane/command_popup.rs:443: "expected prefix search for '/ac' to exclude 'compact', got {cmds:?}"
codex-rs/tui/src/snapshots/codex_tui__history_cell__tests__ps_output_long_command_snapshot.snap:10: ↳ searching...
codex-rs/tui/src/bottom_pane/skills_toggle_view.rs:34:const SEARCH_PLACEHOLDER: &str = "Type to search skills";
codex-rs/tui/src/bottom_pane/skills_toggle_view.rs:52: search_query: String,
codex-rs/tui/src/bottom_pane/skills_toggle_view.rs:71: search_query: String::new(),
codex-rs/tui/src/bottom_pane/skills_toggle_view.rs:93: let filter = self.search_query.trim();
codex-rs/tui/src/bottom_pane/skills_toggle_view.rs:239: self.search_query.pop();
codex-rs/tui/src/bottom_pane/skills_toggle_view.rs:264: self.search_query.push(c);
codex-rs/tui/src/bottom_pane/skills_toggle_view.rs:311: let [header_area, _, search_area, list_area] = Layout::vertical([
codex-rs/tui/src/bottom_pane/skills_toggle_view.rs:321: // Render the search prompt as two lines to mimic the composer.
codex-rs/tui/src/bottom_pane/skills_toggle_view.rs:322: if search_area.height >= 2 {
codex-rs/tui/src/bottom_pane/skills_toggle_view.rs:324: Layout::vertical([Constraint::Length(1), Constraint::Length(1)]).areas(search_area);
codex-rs/tui/src/bottom_pane/skills_toggle_view.rs:326: let line = if self.search_query.is_empty() {
codex-rs/tui/src/bottom_pane/skills_toggle_view.rs:331: self.search_query.clone().into(),
codex-rs/tui/src/bottom_pane/skills_toggle_view.rs:335: } else if search_area.height > 0 {
codex-rs/tui/src/bottom_pane/skills_toggle_view.rs:336: let query_span = if self.search_query.is_empty() {
codex-rs/tui/src/bottom_pane/skills_toggle_view.rs:339: self.search_query.clone().into()
codex-rs/tui/src/bottom_pane/skills_toggle_view.rs:341: Line::from(query_span).render(search_area, buf);
codex-rs/core/src/config/profile.rs:39: pub tools_web_search: Option<bool>,
codex-rs/core/src/config/profile.rs:41: pub web_search: Option<WebSearchMode>,
codex-rs/tui/src/snapshots/codex_tui__history_cell__tests__active_mcp_tool_call_snapshot.snap:6:• Calling search.find_docs({"query":"ratatui styling","limit":3})
codex-rs/tui/src/snapshots/codex_tui__history_cell__tests__web_search_history_cell_transcript_snapshot.snap:5:• Searched example search query with several generic words to
codex-rs/tui/src/snapshots/codex_tui__history_cell__tests__completed_mcp_tool_call_success_snapshot.snap:6:• Called search.find_docs({"query":"ratatui styling","limit":3})
codex-rs/tui/src/snapshots/codex_tui__history_cell__tests__completed_mcp_tool_call_error_snapshot.snap:6:• Called search.find_docs({"query":"ratatui styling","limit":3})
codex-rs/core/tests/suite/rollout_list_find.rs:27: // Minimal first line: session_meta with the id so content search can find it
codex-rs/core/src/tools/handlers/grep_files.rs:72: let search_path = turn.resolve_path(args.path.clone());
codex-rs/core/src/tools/handlers/grep_files.rs:74: verify_path_exists(&search_path).await?;
codex-rs/core/src/tools/handlers/grep_files.rs:84: let search_results =
codex-rs/core/src/tools/handlers/grep_files.rs:85: run_rg_search(pattern, include.as_deref(), &search_path, limit, &turn.cwd).await?;
codex-rs/core/src/tools/handlers/grep_files.rs:87: if search_results.is_empty() {
codex-rs/core/src/tools/handlers/grep_files.rs:95: content: search_results.join("\n"),
codex-rs/core/src/tools/handlers/grep_files.rs:110:async fn run_rg_search(
codex-rs/core/src/tools/handlers/grep_files.rs:113: search_path: &Path,
codex-rs/core/src/tools/handlers/grep_files.rs:130: command.arg("--").arg(search_path);
codex-rs/core/src/tools/handlers/grep_files.rs:201: async fn run_search_returns_results() -> anyhow::Result<()> {
codex-rs/core/src/tools/handlers/grep_files.rs:211: let results = run_rg_search("alpha", None, dir, 10, dir).await?;
codex-rs/core/src/tools/handlers/grep_files.rs:219: async fn run_search_with_glob_filter() -> anyhow::Result<()> {
codex-rs/core/src/tools/handlers/grep_files.rs:228: let results = run_rg_search("alpha", Some("*.rs"), dir, 10, dir).await?;
codex-rs/core/src/tools/handlers/grep_files.rs:235: async fn run_search_respects_limit() -> anyhow::Result<()> {
codex-rs/core/src/tools/handlers/grep_files.rs:245: let results = run_rg_search("alpha", None, dir, 2, dir).await?;
codex-rs/core/src/tools/handlers/grep_files.rs:251: async fn run_search_handles_no_matches() -> anyhow::Result<()> {
codex-rs/core/src/tools/handlers/grep_files.rs:259: let results = run_rg_search("alpha", None, dir, 5, dir).await?;
codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__skills_toggle_view__tests__skills_toggle_basic.snap:10: Type to search skills
codex-rs/core/src/client.rs:70:pub const WEB_SEARCH_ELIGIBLE_HEADER: &str = "x-oai-web-search-eligible";
codex-rs/core/src/client.rs:706: if matches!(config.web_search_mode, Some(WebSearchMode::Disabled)) {
codex-rs/core/src/tools/spec.rs:30: pub web_search_mode: Option<WebSearchMode>,
codex-rs/core/src/tools/spec.rs:40: pub(crate) web_search_mode: Option<WebSearchMode>,
codex-rs/core/src/tools/spec.rs:48: web_search_mode,
codex-rs/core/src/tools/spec.rs:83: web_search_mode: *web_search_mode,
codex-rs/core/src/tools/spec.rs:732: description: Some("Regular expression pattern to search for.".to_string()),
codex-rs/core/src/tools/spec.rs:739: "Optional glob that limits which files are searched (e.g. \"*.rs\" or \
codex-rs/core/src/tools/spec.rs:749: "Directory or file path to search. Defaults to the session's working directory."
codex-rs/core/src/tools/spec.rs:951: description: "Lists resources provided by MCP servers. Resources allow servers to share data that provides context to language models, such as files, database schemas, or application-specific information. Prefer resources over web search when possible.".to_string(),
codex-rs/core/src/tools/spec.rs:985: description: "Lists resource templates provided by MCP servers. Parameterized resource templates allow servers to share data that takes parameters and provides context to language models, such as files, database schemas, or application-specific information. Prefer resource templates over web search when possible.".to_string(),
codex-rs/core/src/tools/spec.rs:1383: match config.web_search_mode {
codex-rs/core/src/tools/spec.rs:1464: ToolSpec::WebSearch { .. } => "web_search",
codex-rs/core/src/tools/spec.rs:1547: fn test_full_toolset_specs_for_gpt5_codex_unified_exec_web_search() {
codex-rs/core/src/tools/spec.rs:1556: web_search_mode: Some(WebSearchMode::Live),
codex-rs/core/src/tools/spec.rs:1620: web_search_mode: Some(WebSearchMode::Cached),
codex-rs/core/src/tools/spec.rs:1638: web_search_mode: Some(WebSearchMode::Cached),
codex-rs/core/src/tools/spec.rs:1650: web_search_mode: Some(WebSearchMode::Cached),
codex-rs/core/src/tools/spec.rs:1659: web_search_mode: Option<WebSearchMode>,
codex-rs/core/src/tools/spec.rs:1667: web_search_mode,
codex-rs/core/src/tools/spec.rs:1675: fn web_search_mode_cached_sets_external_web_access_false() {
codex-rs/core/src/tools/spec.rs:1683: web_search_mode: Some(WebSearchMode::Cached),
codex-rs/core/src/tools/spec.rs:1687: let tool = find_tool(&tools, "web_search");
codex-rs/core/src/tools/spec.rs:1697: fn web_search_mode_live_sets_external_web_access_true() {
codex-rs/core/src/tools/spec.rs:1705: web_search_mode: Some(WebSearchMode::Live),
codex-rs/core/src/tools/spec.rs:1709: let tool = find_tool(&tools, "web_search");
codex-rs/core/src/tools/spec.rs:1734: "web_search",
codex-rs/core/src/tools/spec.rs:1756: "web_search",
codex-rs/core/src/tools/spec.rs:1763: fn test_build_specs_gpt5_codex_unified_exec_web_search() {
codex-rs/core/src/tools/spec.rs:1780: "web_search",
codex-rs/core/src/tools/spec.rs:1787: fn test_build_specs_gpt51_codex_unified_exec_web_search() {
codex-rs/core/src/tools/spec.rs:1804: "web_search",
codex-rs/core/src/tools/spec.rs:1825: "web_search",
codex-rs/core/src/tools/spec.rs:1847: "web_search",
codex-rs/core/src/tools/spec.rs:1868: "web_search",
codex-rs/core/src/tools/spec.rs:1890: "web_search",
codex-rs/core/src/tools/spec.rs:1913: "web_search",
codex-rs/core/src/tools/spec.rs:1920: fn test_codex_mini_unified_exec_web_search() {
codex-rs/core/src/tools/spec.rs:1936: "web_search",
codex-rs/core/src/tools/spec.rs:1951: web_search_mode: Some(WebSearchMode::Live),
codex-rs/core/src/tools/spec.rs:1973: web_search_mode: Some(WebSearchMode::Cached),
codex-rs/core/src/tools/spec.rs:1992: web_search_mode: Some(WebSearchMode::Cached),
codex-rs/core/src/tools/spec.rs:2023: web_search_mode: Some(WebSearchMode::Live),
codex-rs/core/src/tools/spec.rs:2119: web_search_mode: Some(WebSearchMode::Cached),
codex-rs/core/src/tools/spec.rs:2196: web_search_mode: Some(WebSearchMode::Cached),
codex-rs/core/src/tools/spec.rs:2202: "dash/search".to_string(),
codex-rs/core/src/tools/spec.rs:2204: name: "search".to_string(),
codex-rs/core/src/tools/spec.rs:2208: "description": "search query"
codex-rs/core/src/tools/spec.rs:2224: let tool = find_tool(&tools, "dash/search");
codex-rs/core/src/tools/spec.rs:2228: name: "dash/search".to_string(),
codex-rs/core/src/tools/spec.rs:2233: description: Some("search query".to_string())
codex-rs/core/src/tools/spec.rs:2254: web_search_mode: Some(WebSearchMode::Cached),
codex-rs/core/src/tools/spec.rs:2309: web_search_mode: Some(WebSearchMode::Cached),
codex-rs/core/src/tools/spec.rs:2366: web_search_mode: Some(WebSearchMode::Cached),
codex-rs/core/src/tools/spec.rs:2479: web_search_mode: Some(WebSearchMode::Cached),
[Showing lines 2-338 of 338 (50.0KB limit). Full output: /var/folders/49/l4171l3n219_xwq30fmyzr8w0000gn/T/pi-bash-baa9a33ff2e5acd6.log]Listing source files
I'm listing files in codex-rs/core/src to locate web search-related modules for deeper inspection.
>cd /tmp/pi-review/codex && ls codex-rs/core/srcagent
api_bridge.rs
apply_patch.rs
auth
auth.rs
bash.rs
bin
client_common.rs
client.rs
codex_delegate.rs
codex_thread.rs
codex.rs
command_safety
compact_remote.rs
compact.rs
config
config_loader
connectors.rs
context_manager
custom_prompts.rs
default_client.rs
env.rs
environment_context.rs
error.rs
event_mapping.rs
exec_env.rs
exec_policy.rs
exec.rs
features
features.rs
flags.rs
function_tool.rs
git_info.rs
instructions
landlock.rs
lib.rs
mcp
mcp_connection_manager.rs
mcp_tool_call.rs
mentions.rs
message_history.rs
model_provider_info.rs
models_manager
otel_init.rs
parse_command.rs
path_utils.rs
powershell.rs
project_doc.rs
proposed_plan_parser.rs
review_format.rs
review_prompts.rs
rollout
safety.rs
sandboxing
seatbelt_base_policy.sbpl
seatbelt_network_policy.sbpl
seatbelt.rs
session_prefix.rs
shell_snapshot.rs
shell.rs
skills
spawn.rs
state
state_db.rs
stream_events_utils.rs
tagged_block_parser.rs
tasks
terminal.rs
text_encoding.rs
thread_manager.rs
token_data.rs
tools
transport_manager.rs
truncate.rs
turn_diff_tracker.rs
unified_exec
user_notification.rs
user_shell_command.rs
util.rs
web_search.rs
windows_sandbox.rsChecking the web_search.rs file
Planning config inspection and repo cloning
I'm preparing to locate how web_search_mode is enabled by inspecting config files like config/mod.rs and tools/spec.rs, planning to use targeted partial reads with offsets. I'll clone the repo to access commit diffs and relevant files safely.
>cd /tmp/pi-review && git clone https://github.com/mitsuhiko/pi-mono.git mitsu-pi-monoCloning into 'mitsu-pi-mono'...Showing latest commit
I'm displaying the details of the most recent commit to review changes and context.
>cd /tmp/pi-review/mitsu-pi-mono && git show 2b7e67873a88ef1ef76c40c988b1b218f4c55c14commit 2b7e67873a88ef1ef76c40c988b1b218f4c55c14
Author: Armin Ronacher <[REDACTED]>
Date: Fri Jan 30 20:05:54 2026 +0100
Search slop
diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md
index bda066d3..6a0510c9 100644
--- a/packages/ai/CHANGELOG.md
+++ b/packages/ai/CHANGELOG.md
@@ -2,6 +2,10 @@
## [Unreleased]
+### Added
+
+- Added web search server tool support for Anthropic and OpenAI Codex Responses when enabling the `web_search` tool.
+
## [0.50.5] - 2026-01-30
## [0.50.4] - 2026-01-30
diff --git a/packages/ai/src/index.ts b/packages/ai/src/index.ts
index 46e1c470..d21bfc52 100644
--- a/packages/ai/src/index.ts
+++ b/packages/ai/src/index.ts
@@ -17,3 +17,4 @@ export * from "./utils/oauth/index.js";
export * from "./utils/overflow.js";
export * from "./utils/typebox-helpers.js";
export * from "./utils/validation.js";
+export * from "./utils/web-search.js";
diff --git a/packages/ai/src/providers/anthropic.ts b/packages/ai/src/providers/anthropic.ts
index a52f7322..e3366bc1 100644
--- a/packages/ai/src/providers/anthropic.ts
+++ b/packages/ai/src/providers/anthropic.ts
@@ -26,6 +26,7 @@ import type {
import { AssistantMessageEventStream } from "../utils/event-stream.js";
import { parseStreamingJson } from "../utils/json-parse.js";
import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
+import { isWebSearchTool } from "../utils/web-search.js";
import { adjustMaxTokensForThinking, buildBaseOptions } from "./simple-options.js";
import { transformMessages } from "./transform-messages.js";
@@ -685,13 +686,23 @@ function convertMessages(
return params;
}
-function convertTools(tools: Tool[], isOAuthToken: boolean): Anthropic.Messages.Tool[] {
+function convertTools(tools: Tool[], isOAuthToken: boolean): Anthropic.Messages.ToolUnion[] {
if (!tools) return [];
- return tools.map((tool) => {
+ const converted: Anthropic.Messages.ToolUnion[] = [];
+
+ for (const tool of tools) {
+ if (isWebSearchTool(tool)) {
+ converted.push({
+ name: "web_search",
+ type: "web_search_20250305",
+ });
+ continue;
+ }
+
const jsonSchema = tool.parameters as any; // TypeBox already generates JSON Schema
- return {
+ converted.push({
name: isOAuthToken ? toClaudeCodeName(tool.name) : tool.name,
description: tool.description,
input_schema: {
@@ -699,8 +710,10 @@ function convertTools(tools: Tool[], isOAuthToken: boolean): Anthropic.Messages.
properties: jsonSchema.properties || {},
required: jsonSchema.required || [],
},
- };
- });
+ });
+ }
+
+ return converted;
}
function mapStopReason(reason: Anthropic.Messages.StopReason | string): StopReason {
diff --git a/packages/ai/src/providers/openai-codex-responses.ts b/packages/ai/src/providers/openai-codex-responses.ts
index 38565b49..63982708 100644
--- a/packages/ai/src/providers/openai-codex-responses.ts
+++ b/packages/ai/src/providers/openai-codex-responses.ts
@@ -19,6 +19,7 @@ import type {
StreamOptions,
} from "../types.js";
import { AssistantMessageEventStream } from "../utils/event-stream.js";
+import { hasWebSearchTool } from "../utils/web-search.js";
import { convertResponsesMessages, convertResponsesTools, processResponsesStream } from "./openai-responses-shared.js";
import { buildBaseOptions, clampReasoning } from "./simple-options.js";
@@ -134,7 +135,15 @@ export const streamOpenAICodexResponses: StreamFunction<"openai-codex-responses"
const accountId = extractAccountId(apiKey);
const body = buildRequestBody(model, context, options);
options?.onPayload?.(body);
- const headers = buildHeaders(model.headers, options?.headers, accountId, apiKey, options?.sessionId);
+ const webSearchEnabled = hasWebSearchTool(context.tools);
+ const headers = buildHeaders(
+ model.headers,
+ options?.headers,
+ accountId,
+ apiKey,
+ options?.sessionId,
+ webSearchEnabled,
+ );
const bodyJson = JSON.stringify(body);
// Fetch with retry logic for rate limits and transient errors
@@ -426,13 +435,15 @@ function buildHeaders(
additionalHeaders: Record<string, string> | undefined,
accountId: string,
token: string,
- sessionId?: string,
+ sessionId: string | undefined,
+ webSearchEnabled: boolean,
): Headers {
const headers = new Headers(initHeaders);
headers.set("Authorization", `Bearer ${token}`);
headers.set("chatgpt-account-id", accountId);
headers.set("OpenAI-Beta", "responses=experimental");
headers.set("originator", "pi");
+ headers.set("x-oai-web-search-eligible", webSearchEnabled ? "true" : "false");
const userAgent = _os ? `pi (${_os.platform()} ${_os.release()}; ${_os.arch()})` : "pi (browser)";
headers.set("User-Agent", userAgent);
headers.set("accept", "text/event-stream");
diff --git a/packages/ai/src/providers/openai-responses-shared.ts b/packages/ai/src/providers/openai-responses-shared.ts
index 4642e4e8..c8c1ea01 100644
--- a/packages/ai/src/providers/openai-responses-shared.ts
+++ b/packages/ai/src/providers/openai-responses-shared.ts
@@ -28,6 +28,7 @@ import type {
import type { AssistantMessageEventStream } from "../utils/event-stream.js";
import { parseStreamingJson } from "../utils/json-parse.js";
import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
+import { isWebSearchTool } from "../utils/web-search.js";
import { transformMessages } from "./transform-messages.js";
// =============================================================================
@@ -245,13 +246,30 @@ export function convertResponsesMessages<TApi extends Api>(
export function convertResponsesTools(tools: Tool[], options?: ConvertResponsesToolsOptions): OpenAITool[] {
const strict = options?.strict === undefined ? false : options.strict;
- return tools.map((tool) => ({
- type: "function",
- name: tool.name,
- description: tool.description,
- parameters: tool.parameters as any, // TypeBox already generates JSON Schema
- strict,
- }));
+ const converted: OpenAITool[] = [];
+ let webSearchEnabled = false;
+
+ for (const tool of tools) {
+ if (isWebSearchTool(tool)) {
+ webSearchEnabled = true;
+ continue;
+ }
+ converted.push({
+ type: "function",
+ name: tool.name,
+ description: tool.description,
+ parameters: tool.parameters as any, // TypeBox already generates JSON Schema
+ strict,
+ });
+ }
+
+ if (webSearchEnabled) {
+ converted.push({
+ type: "web_search",
+ });
+ }
+
+ return converted;
}
// =============================================================================
diff --git a/packages/ai/src/utils/web-search.ts b/packages/ai/src/utils/web-search.ts
new file mode 100644
index 00000000..b78a76ce
--- /dev/null
+++ b/packages/ai/src/utils/web-search.ts
@@ -0,0 +1,22 @@
+import type { Tool } from "../types.js";
+
+export const WEB_SEARCH_TOOL_NAME = "web_search";
+export const WEB_SEARCH_TOOL_DESCRIPTION = "Enable server-side web search for supported models.";
+
+type ToolSchema = {
+ properties?: Record<string, unknown>;
+} | null;
+
+export function isWebSearchTool(tool: Tool): boolean {
+ if (tool.name !== WEB_SEARCH_TOOL_NAME) return false;
+ if (tool.description !== WEB_SEARCH_TOOL_DESCRIPTION) return false;
+ const schema = tool.parameters as ToolSchema;
+ const properties = schema?.properties;
+ if (!properties) return true;
+ return Object.keys(properties).length === 0;
+}
+
+export function hasWebSearchTool(tools: Tool[] | undefined): boolean {
+ if (!tools) return false;
+ return tools.some(isWebSearchTool);
+}
diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md
index 21a27d48..9f16ecc4 100644
--- a/packages/coding-agent/CHANGELOG.md
+++ b/packages/coding-agent/CHANGELOG.md
@@ -5,6 +5,7 @@
### Added
- Added `ctx.getSystemPrompt()` to extension context for accessing the current effective system prompt ([#1098](https://github.com/badlogic/pi-mono/pull/1098) by [@kaofelix](https://github.com/kaofelix))
+- Added `web_search` as a built-in tool for enabling provider-side web search on supported models.
## [0.50.5] - 2026-01-30
diff --git a/packages/coding-agent/src/cli/args.ts b/packages/coding-agent/src/cli/args.ts
index 181731bf..76ce16ac 100644
--- a/packages/coding-agent/src/cli/args.ts
+++ b/packages/coding-agent/src/cli/args.ts
@@ -203,7 +203,7 @@ ${chalk.bold("Options:")}
Supports globs (anthropic/*, *sonnet*) and fuzzy matching
--no-tools Disable all built-in tools
--tools <tools> Comma-separated list of tools to enable (default: read,bash,edit,write)
- Available: read, bash, edit, write, grep, find, ls
+ Available: read, bash, edit, write, grep, find, ls, web_search
--thinking <level> Set thinking level: off, minimal, low, medium, high, xhigh
--extension, -e <path> Load an extension file (can be used multiple times)
--no-extensions Disable extension discovery (explicit -e paths still work)
@@ -290,12 +290,13 @@ ${chalk.bold("Environment Variables:")}
PI_SHARE_VIEWER_URL - Base URL for /share command (default: https://buildwithpi.ai/session/)
${chalk.bold("Available Tools (default: read, bash, edit, write):")}
- read - Read file contents
- bash - Execute bash commands
- edit - Edit files with find/replace
- write - Write files (creates/overwrites)
- grep - Search file contents (read-only, off by default)
- find - Find files by glob pattern (read-only, off by default)
- ls - List directory contents (read-only, off by default)
+ read - Read file contents
+ bash - Execute bash commands
+ edit - Edit files with find/replace
+ write - Write files (creates/overwrites)
+ grep - Search file contents (read-only, off by default)
+ find - Find files by glob pattern (read-only, off by default)
+ ls - List directory contents (read-only, off by default)
+ web_search - Enable server-side web search (off by default)
`);
}
diff --git a/packages/coding-agent/src/core/sdk.ts b/packages/coding-agent/src/core/sdk.ts
index 2f8c9959..8228006b 100644
--- a/packages/coding-agent/src/core/sdk.ts
+++ b/packages/coding-agent/src/core/sdk.ts
@@ -26,6 +26,7 @@ import {
createLsTool,
createReadOnlyTools,
createReadTool,
+ createWebSearchTool,
createWriteTool,
editTool,
findTool,
@@ -35,6 +36,7 @@ import {
readTool,
type Tool,
type ToolName,
+ webSearchTool,
writeTool,
} from "./tools/index.js";
@@ -103,6 +105,7 @@ export {
grepTool,
findTool,
lsTool,
+ webSearchTool,
codingTools,
readOnlyTools,
allTools as allBuiltInTools,
@@ -116,6 +119,7 @@ export {
createGrepTool,
createFindTool,
createLsTool,
+ createWebSearchTool,
};
// Helper Functions
diff --git a/packages/coding-agent/src/core/system-prompt.ts b/packages/coding-agent/src/core/system-prompt.ts
index 368ac739..eb614c48 100644
--- a/packages/coding-agent/src/core/system-prompt.ts
+++ b/packages/coding-agent/src/core/system-prompt.ts
@@ -14,6 +14,7 @@ const toolDescriptions: Record<string, string> = {
grep: "Search file contents for patterns (respects .gitignore)",
find: "Find files by glob pattern (respects .gitignore)",
ls: "List directory contents",
+ web_search: "Enable server-side web search for supported models",
};
export interface BuildSystemPromptOptions {
diff --git a/packages/coding-agent/src/core/tools/index.ts b/packages/coding-agent/src/core/tools/index.ts
index 26328116..fdb857bc 100644
--- a/packages/coding-agent/src/core/tools/index.ts
+++ b/packages/coding-agent/src/core/tools/index.ts
@@ -26,6 +26,7 @@ export {
truncateLine,
truncateTail,
} from "./truncate.js";
+export { createWebSearchTool, webSearchTool } from "./web-search.js";
export { createWriteTool, type WriteOperations, type WriteToolOptions, writeTool } from "./write.js";
import type { AgentTool } from "@mariozechner/pi-agent-core";
@@ -35,6 +36,7 @@ import { createFindTool, findTool } from "./find.js";
import { createGrepTool, grepTool } from "./grep.js";
import { createLsTool, lsTool } from "./ls.js";
import { createReadTool, type ReadToolOptions, readTool } from "./read.js";
+import { createWebSearchTool, webSearchTool } from "./web-search.js";
import { createWriteTool, writeTool } from "./write.js";
/** Tool type (AgentTool from pi-ai) */
@@ -55,6 +57,7 @@ export const allTools = {
grep: grepTool,
find: findTool,
ls: lsTool,
+ web_search: webSearchTool,
};
export type ToolName = keyof typeof allTools;
@@ -97,5 +100,6 @@ export function createAllTools(cwd: string, options?: ToolsOptions): Record<Tool
grep: createGrepTool(cwd),
find: createFindTool(cwd),
ls: createLsTool(cwd),
+ web_search: createWebSearchTool(),
};
}
diff --git a/packages/coding-agent/src/core/tools/web-search.ts b/packages/coding-agent/src/core/tools/web-search.ts
new file mode 100644
index 00000000..a5edc277
--- /dev/null
+++ b/packages/coding-agent/src/core/tools/web-search.ts
@@ -0,0 +1,19 @@
+import type { AgentTool } from "@mariozechner/pi-agent-core";
+import { WEB_SEARCH_TOOL_DESCRIPTION, WEB_SEARCH_TOOL_NAME } from "@mariozechner/pi-ai";
+import { Type } from "@sinclair/typebox";
+
+const webSearchSchema = Type.Object({}, { additionalProperties: false });
+
+export function createWebSearchTool(): AgentTool<typeof webSearchSchema> {
+ return {
+ name: WEB_SEARCH_TOOL_NAME,
+ label: WEB_SEARCH_TOOL_NAME,
+ description: WEB_SEARCH_TOOL_DESCRIPTION,
+ parameters: webSearchSchema,
+ execute: async () => {
+ throw new Error("Web search is handled by the model provider and cannot be executed locally.");
+ },
+ };
+}
+
+export const webSearchTool = createWebSearchTool();
diff --git a/packages/coding-agent/src/modes/interactive/components/daxnuts.ts b/packages/coding-agent/src/modes/interactive/components/daxnuts.ts
index 3e6f12b1..54170c27 100644
--- a/packages/coding-agent/src/modes/interactive/components/daxnuts.ts
+++ b/packages/coding-agent/src/modes/interactive/components/daxnuts.ts
@@ -46,7 +46,7 @@ function buildImage(): string[] {
for (let x = 0; x < WIDTH; x++) {
const top = pixels[row][x];
const bottom = pixels[row + 1]?.[x] ?? top;
- line += rgb(bottom[0], bottom[1], bottom[2]) + rgb(top[0], top[1], top[2], true) + "▄";
+ line += `${rgb(bottom[0], bottom[1], bottom[2])}${rgb(top[0], top[1], top[2], true)}▄`;
}
line += RESET;
lines.push(line);
diff --git a/packages/tui/test/bug-regression-isimageline-startswith-bug.test.ts b/packages/tui/test/bug-regression-isimageline-startswith-bug.test.ts
index 2ab162ac..ff15d18f 100644
--- a/packages/tui/test/bug-regression-isimageline-startswith-bug.test.ts
+++ b/packages/tui/test/bug-regression-isimageline-startswith-bug.test.ts
@@ -76,7 +76,7 @@ describe("Bug regression: isImageLine() crash with image escape sequences", () =
"Suffix text \x1b_Ga=T,data...\x1b\\ suffix",
"Middle \x1b_Ga=T,data...\x1b\\ more text",
// Very long line (simulating 300KB+ crash scenario)
- "Text before " + "\x1b_Ga=T,f=100" + "A".repeat(300000) + " text after",
+ `Text before \x1b_Ga=T,f=100${"A".repeat(300000)} text after`,
];
for (const line of scenarios) {
@@ -93,7 +93,7 @@ describe("Bug regression: isImageLine() crash with image escape sequences", () =
"Suffix text \x1b]1337;File=inline=1:data==\x07 suffix",
"Middle \x1b]1337;File=inline=1:data==\x07 more text",
// Very long line (simulating 304KB crash scenario)
- "Text before " + "\x1b]1337;File=size=800,600;inline=1:" + "B".repeat(300000) + " text after",
+ `Text before \x1b]1337;File=size=800,600;inline=1:${"B".repeat(300000)} text after`,
];
for (const line of scenarios) {Planning targeted code inspection
I'm focusing on the web_search tool usage in the codebase, pinpointing relevant sections in core/src/tools/spec.rs by searching for WebSearchMode to read those segments selectively.
>cd /tmp/pi-review/codex && rg -n "WebSearchMode|web_search" codex-rs/core/src/tools/spec.rs13:use codex_protocol::config_types::WebSearchMode;
30: pub web_search_mode: Option<WebSearchMode>,
40: pub(crate) web_search_mode: Option<WebSearchMode>,
48: web_search_mode,
83: web_search_mode: *web_search_mode,
1383: match config.web_search_mode {
1384: Some(WebSearchMode::Cached) => {
1389: Some(WebSearchMode::Live) => {
1394: Some(WebSearchMode::Disabled) | None => {}
1464: ToolSpec::WebSearch { .. } => "web_search",
1547: fn test_full_toolset_specs_for_gpt5_codex_unified_exec_web_search() {
1556: web_search_mode: Some(WebSearchMode::Live),
1620: web_search_mode: Some(WebSearchMode::Cached),
1638: web_search_mode: Some(WebSearchMode::Cached),
1650: web_search_mode: Some(WebSearchMode::Cached),
1659: web_search_mode: Option<WebSearchMode>,
1667: web_search_mode,
1675: fn web_search_mode_cached_sets_external_web_access_false() {
1683: web_search_mode: Some(WebSearchMode::Cached),
1687: let tool = find_tool(&tools, "web_search");
1697: fn web_search_mode_live_sets_external_web_access_true() {
1705: web_search_mode: Some(WebSearchMode::Live),
1709: let tool = find_tool(&tools, "web_search");
1725: Some(WebSearchMode::Cached),
1734: "web_search",
1747: Some(WebSearchMode::Cached),
1756: "web_search",
1763: fn test_build_specs_gpt5_codex_unified_exec_web_search() {
1770: Some(WebSearchMode::Live),
1780: "web_search",
1787: fn test_build_specs_gpt51_codex_unified_exec_web_search() {
1794: Some(WebSearchMode::Live),
1804: "web_search",
1817: Some(WebSearchMode::Cached),
1825: "web_search",
1838: Some(WebSearchMode::Cached),
1847: "web_search",
1860: Some(WebSearchMode::Cached),
1868: "web_search",
1881: Some(WebSearchMode::Cached),
1890: "web_search",
1903: Some(WebSearchMode::Cached),
1913: "web_search",
1920: fn test_codex_mini_unified_exec_web_search() {
1927: Some(WebSearchMode::Live),
1936: "web_search",
1951: web_search_mode: Some(WebSearchMode::Live),
1973: web_search_mode: Some(WebSearchMode::Cached),
1992: web_search_mode: Some(WebSearchMode::Cached),
2023: web_search_mode: Some(WebSearchMode::Live),
2119: web_search_mode: Some(WebSearchMode::Cached),
2196: web_search_mode: Some(WebSearchMode::Cached),
2254: web_search_mode: Some(WebSearchMode::Cached),
2309: web_search_mode: Some(WebSearchMode::Cached),
2366: web_search_mode: Some(WebSearchMode::Cached),
2479: web_search_mode: Some(WebSearchMode::Cached),Planning partial file read
I'm preparing to read the tools/spec file starting at offset 1360.
Locating web_search_mode config
I'm searching for web_search_mode definitions in the config module to understand how it's set up and whether it needs separation for the CLI. I'll inspect the relevant block around line 900.
Locating web search mode config
I'm checking around line 1230 in config/mod.rs to understand the web search mode setup, using a read with offset to pinpoint relevant details.
Investigating web_search usage in client_common.rs
I'm checking the client_common.rs file around the web_search comment to understand its role and whether it fits the OpenAI tools spec or client context.
Identifying web_search config and headers
I'm noting that the tool spec includes web_search and there's a config named web_search_mode plus a header x-oai-web-search-eligible, potentially handled in the client code around codex-rs/core/src/client.rs. Next, I'll explore that file to confirm usage and integration points.
Analyzing web search integration
I'm assessing the recent commit adding the web_search tool and how the codex repo implements web search via WebSearchMode and tool specs. The user proposes formalizing this in StreamOptions and renaming; I'll prepare an analysis of the current commit and codex approach to provide informed feedback and suggest next steps.
Analyzing tool injection and renaming
Codex seems to merge the web_search tool by injecting it into the tool spec list without renaming, also setting a header for eligibility; the pi-mono commit introduces a new web_search tool and proposes StreamOptions changes. Next step is verifying naming and injection details in the current repo for clear analysis and referencing relevant files.
Planning to review tool execution code
The commit adds the web_search tool using a special name and flags like isWebSearchTool; next, I'll check the coding-agent's tool execution code to clarify how it runs tools, focusing on the specified file.
Checking existing Anthropics provider rename
I'm inspecting the current Anthropics provider code to see if there's an existing renaming approach for OAuth or similar tools that informs how to handle the server-side web-search tool.
Proposing web search integration approach
Planning to formalize web search support by adding a webSearch option in stream settings to control provider injection. Also considering name mapping to avoid user tool conflicts, with internal renaming and context/message transformations to handle tool calls and results consistently. Next, I'll analyze existing transformMessages in the AI providers for integration details.
Planning line numbering approach
I'll use rg -n to find relevant code sections with line numbers for citations since reading files directly doesn't show line numbers and using nl may be disallowed; this ensures proper line references for commit and codex details.
Confirming line number references via rg
I'll gather exact line numbers for key tokens in codex and mitsu files using rg -n to provide precise file and line citations for tool specs, config, client code, and commit additions, ensuring all references are supported by explicit line info.
>cd /tmp/pi-review/mitsu-pi-mono && rg -n "WEB_SEARCH_TOOL_NAME|web_search_20250305|web_search" packages/ai/src/utils/web-search.ts packages/ai/src/providers/anthropic.ts packages/ai/src/providers/openai-responses-shared.ts packages/ai/src/providers/openai-codex-responses.tsrg: packages/ai/src/utils/web-search.ts: No such file or directory (os error 2)
Command exited with code 2Investigating missing file in repo
I'm verifying whether the file exists in the target commit or branch since cloning after the commit should include it, considering possible branch differences affecting file presence.
>cd /tmp/pi-review/mitsu-pi-mono && ls packages/ai/src/utilsevent-stream.ts
json-parse.ts
oauth
overflow.ts
sanitize-unicode.ts
typebox-helpers.ts
validation.tsClarifying file reading via git show
I'm verifying that using git show <commit>:path to read committed files is distinct from using cat or sed and aligns with allowed commands since it accesses file content directly from Git objects rather than the working tree.
Planning referencing strategy for websearch commit
I'm deciding to summarize how Codex handles websearch via config and tool spec without full file reading, using rg -n to get precise line numbers for key patterns instead of reading committed files directly.
>cd /tmp/pi-review/codex && rg -n "WebSearchMode::Cached" codex-rs/core/src/tools/spec.rs1384: Some(WebSearchMode::Cached) => {
1620: web_search_mode: Some(WebSearchMode::Cached),
1638: web_search_mode: Some(WebSearchMode::Cached),
1650: web_search_mode: Some(WebSearchMode::Cached),
1683: web_search_mode: Some(WebSearchMode::Cached),
1725: Some(WebSearchMode::Cached),
1747: Some(WebSearchMode::Cached),
1817: Some(WebSearchMode::Cached),
1838: Some(WebSearchMode::Cached),
1860: Some(WebSearchMode::Cached),
1881: Some(WebSearchMode::Cached),
1903: Some(WebSearchMode::Cached),
1973: web_search_mode: Some(WebSearchMode::Cached),
1992: web_search_mode: Some(WebSearchMode::Cached),
2119: web_search_mode: Some(WebSearchMode::Cached),
2196: web_search_mode: Some(WebSearchMode::Cached),
2254: web_search_mode: Some(WebSearchMode::Cached),
2309: web_search_mode: Some(WebSearchMode::Cached),
2366: web_search_mode: Some(WebSearchMode::Cached),
2479: web_search_mode: Some(WebSearchMode::Cached),Locating WebSearchMode and ToolSpec lines
I'm identifying exact line numbers for WebSearchMode handling and ToolSpec definition to reference them precisely in codex-rs/core source files.
>cd /tmp/pi-review/codex && rg -n "WebSearch" codex-rs/core/src/client_common.rs177: WebSearch {
190: ToolSpec::WebSearch { .. } => "web_search",Searching header usage
I'm searching for the line number of the x-oai-web-search-eligible header in client.rs to locate its definition or usage.
>cd /tmp/pi-review/codex && rg -n "WEB_SEARCH_ELIGIBLE_HEADER|web_search" codex-rs/core/src/client.rs70:pub const WEB_SEARCH_ELIGIBLE_HEADER: &str = "x-oai-web-search-eligible";
704: WEB_SEARCH_ELIGIBLE_HEADER,
706: if matches!(config.web_search_mode, Some(WebSearchMode::Disabled)) {Locating config web_search
I'm searching for the web_search config in config/mod.rs using line numbers to pinpoint its definition.
>cd /tmp/pi-review/codex && rg -n "web_search" codex-rs/core/src/config/mod.rs315: pub web_search_mode: Option<WebSearchMode>,
920: pub web_search: Option<WebSearchMode>,
1032: #[serde(default, alias = "web_search_request")]
1033: pub web_search: Option<bool>,
1052: web_search: tools_toml.web_search,
1195: pub tools_web_search_request: Option<bool>,
1230:fn resolve_web_search_mode(
1235: if let Some(mode) = config_profile.web_search.or(config_toml.web_search) {
1247:pub(crate) fn resolve_web_search_mode_for_turn(
1298: tools_web_search_request: override_tools_web_search_request,
1323: web_search_request: override_tools_web_search_request,
1382: let web_search_mode = resolve_web_search_mode(&cfg, &config_profile, &features);
1600: web_search_mode,
2308: fn web_search_mode_defaults_to_none_if_unset() {
2313: assert_eq!(resolve_web_search_mode(&cfg, &profile, &features), None);
2317: fn web_search_mode_prefers_profile_over_legacy_flags() {
2320: web_search: Some(WebSearchMode::Live),
2327: resolve_web_search_mode(&cfg, &profile, &features),
2333: fn web_search_mode_disabled_overrides_legacy_request() {
2335: web_search: Some(WebSearchMode::Disabled),
2343: resolve_web_search_mode(&cfg, &profile, &features),
2349: fn web_search_mode_for_turn_defaults_to_cached_when_unset() {
2350: let mode = resolve_web_search_mode_for_turn(None, &SandboxPolicy::ReadOnly);
2356: fn web_search_mode_for_turn_defaults_to_live_for_danger_full_access() {
2357: let mode = resolve_web_search_mode_for_turn(None, &SandboxPolicy::DangerFullAccess);
2363: fn web_search_mode_for_turn_prefers_explicit_value() {
2364: let mode = resolve_web_search_mode_for_turn(
2379: tools_web_search: Some(false),
3808: web_search_mode: None,
3892: web_search_mode: None,
3991: web_search_mode: None,
4076: web_search_mode: None,Planning git checkout for file inspection
I'll checkout the specific commit in the mitsu repo worktree to access the needed files, then use the read tool to inspect them since the files aren't in the current tree.
>cd /tmp/pi-review/mitsu-pi-mono && git checkout 2b7e67873a88ef1ef76c40c988b1b218f4c55c14Note: switching to '2b7e67873a88ef1ef76c40c988b1b218f4c55c14'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
HEAD is now at 2b7e6787 Search slopPlanning line number extraction
I'll rely on ripgrep in mitsu to obtain line numbers from relevant files instead of reading the file contents directly.
>cd /tmp/pi-review/mitsu-pi-mono && rg -n "WEB_SEARCH_TOOL_NAME|WEB_SEARCH_TOOL_DESCRIPTION|web_search_20250305" packages/ai/src/utils/web-search.ts packages/ai/src/providers/anthropic.tspackages/ai/src/utils/web-search.ts:3:export const WEB_SEARCH_TOOL_NAME = "web_search";
packages/ai/src/utils/web-search.ts:4:export const WEB_SEARCH_TOOL_DESCRIPTION = "Enable server-side web search for supported models.";
packages/ai/src/utils/web-search.ts:11: if (tool.name !== WEB_SEARCH_TOOL_NAME) return false;
packages/ai/src/utils/web-search.ts:12: if (tool.description !== WEB_SEARCH_TOOL_DESCRIPTION) return false;
packages/ai/src/providers/anthropic.ts:698: type: "web_search_20250305",Planning line number extraction
I'll use ripgrep to get line numbers for OpenAI Codex and OpenAI web search responses to aid precise referencing.
>cd /tmp/pi-review/mitsu-pi-mono && rg -n "web_search" packages/ai/src/providers/openai-responses-shared.ts packages/ai/src/providers/openai-codex-responses.tspackages/ai/src/providers/openai-responses-shared.ts:268: type: "web_search",Locating buildHeaders usage
I'm searching for line numbers of buildHeaders in openai-codex-responses, focusing on x-oai-web-search-eligible to pinpoint its exact location for reference.
>cd /tmp/pi-review/mitsu-pi-mono && rg -n "web_search" packages/ai/src/providers/openai-codex-responses.ts(no output)
Command exited with code 1Searching for web search usage patterns
>cd /tmp/pi-review/mitsu-pi-mono && rg -n "webSearch" packages/ai/src/providers/openai-codex-responses.ts138: const webSearchEnabled = hasWebSearchTool(context.tools);
145: webSearchEnabled,
439: webSearchEnabled: boolean,
446: headers.set("x-oai-web-search-eligible", webSearchEnabled ? "true" : "false");Proposing formal web-search tool integration
Planning to formalize web-search tool support by adding a typed serverTools.webSearch option in StreamOptions with provider-specific fields and rename rules, plus internal naming to avoid collisions and convenience mappings from CLI tool flags.
Summarizing web-search tool handling and formalization proposal
Reviewed OpenAI Codex and mitsu commit approaches: Codex treats web search as a config-driven non-function tool with WebSearchMode and sets a request header, while mitsu adds a dummy web_search tool detected by providers. Proposed formalization adds serverTools.webSearch in StreamOptions with provider-specific fields and internal renaming (__server_web_search) to avoid collisions. Noted Anthropic’s server tool responses use content blocks like server_tool_use and web_search_tool_result which need new parsing to fully support. Suggested coding-agent treat server tool results as generic tool output without local execution. Included references to relevant source files and lines for context and invite clarification or next steps.
Findings from codex and the commit, plus how this maps to your proposal.
Codex web search shape
web_search that resolves to a WebSearchMode enum. It is not modeled as a user tool. See codex-rs/core/src/config/mod.rs:920 and codex-rs/core/src/config/mod.rs:1230.ToolSpec::WebSearch with optional external_web_access. That decides cached versus live search. See codex-rs/core/src/client_common.rs:177 and codex-rs/core/src/tools/spec.rs:1383.x-oai-web-search-eligible based on the resolved mode. See codex-rs/core/src/client.rs:70 and codex-rs/core/src/client.rs:704.What the commit does
/tmp/pi-review/mitsu-pi-mono/packages/ai/src/utils/web-search.ts:3.type: "web_search_20250305" when that tool is present. See /tmp/pi-review/mitsu-pi-mono/packages/ai/src/providers/anthropic.ts:698.{ type: "web_search" }. See /tmp/pi-review/mitsu-pi-mono/packages/ai/src/providers/openai-responses-shared.ts:268.x-oai-web-search-eligible based on that tool. See /tmp/pi-review/mitsu-pi-mono/packages/ai/src/providers/openai-codex-responses.ts:446.Proposal fit and gaps
StreamOptions.webSearch or StreamOptions.serverTools would be a cleaner API than a fake tool. I suggest a provider agnostic shape like:
webSearch?: { enabled: boolean; mode?: "cached" | "live"; maxUses?; allowedDomains?; blockedDomains?; userLocation? }mode to external_web_access and still send the eligibility header. Anthropic can ignore mode and map the rest to web_search_20250305.web_search. Any user tool called web_search has to be renamed in the outbound payload and then mapped back when persisting and replaying context.
server_web_search and map to web_search only at the provider boundary. That prevents collisions and reduces special casing.convertMessages and the streaming event mapping.server_tool_use and web_search_tool_result content blocks. The current Anthropic stream handler only processes tool_use. Without changes, the web search call and result may not roundtrip into ToolCall and ToolResult messages. That is a functional gap that needs addressing when you formalize this.packages/coding-agent/src/modes/interactive/components/tool-execution.ts currently renders tool outputs generically. If you store server web search results as standard tool results, it will display them fine. If you keep them as assistant content blocks, no change is needed. Consider a custom renderer if you want to hide encrypted fields or show citations, but it is optional.If you want, I can draft a concrete API shape for StreamOptions and a mapping plan for Anthropic and OpenAI.
does the codex codebase have any other server side tool support?
Clarifying server-side tools
Only web_search is a server-defined tool in codex; other tools like local_shell and view_image are local/function tools. The references are in the code's ToolSpec enum, where WebSearch is the sole non-function server tool.
I only see web search as a server-side tool in codex. The only non function, non local tool in the tool spec enum is ToolSpec::WebSearch, and the request headers include x-oai-web-search-eligible. Everything else is either function tools or local shell tooling. See codex-rs/core/src/client_common.rs:171 and codex-rs/core/src/tools/spec.rs:1383.
import anthropic
client = anthropic.Anthropic()
response = client.messages.create( model="claude-sonnet-4-5", max_tokens=[REDACTED] messages=[ { "role": "user", "content": "What's the weather in NYC?" } ], tools=[{ "type": "web_search_20250305", "name": "web_search", "max_uses": 5 }])print(response)import { Anthropic } from '@anthropic-ai/sdk';
const anthropic = new Anthropic();
async function main() { const response = await anthropic.messages.create({ model: "claude-sonnet-4-5", max_tokens: 1024, messages: [ { role: "user", content: "What's the weather in NYC?" } ], tools: [{ type: "web_search_20250305", name: "web_search", max_uses: 5 }] });
console.log(response);}
main().catch(console.error);{ "type": "web_search_20250305", "name": "web_search",
// Optional: Limit the number of searches per request "max_uses": 5,
// Optional: Only include results from these domains "allowed_domains": ["example.com", "trusteddomain.org"],
// Optional: Never include results from these domains "blocked_domains": ["untrustedsource.com"],
// Optional: Localize search results "user_location": { "type": "approximate", "city": "San Francisco", "region": "California", "country": "US", "timezone": "America/Los_Angeles" }}{ "role": "assistant", "content": [ // 1. Claude's decision to search { "type": "text", "text": "I'll search for when Claude Shannon was born." }, // 2. The search query used { "type": "server_tool_use", "id": "srvtoolu_01WYG3ziw53XMcoyKL4XcZmE", "name": "web_search", "input": { "query": "claude shannon birth date" } }, // 3. Search results { "type": "web_search_tool_result", "tool_use_id": "srvtoolu_01WYG3ziw53XMcoyKL4XcZmE", "content": [ { "type": "web_search_result", "url": "https://en.wikipedia.org/wiki/Claude_Shannon", "title": "Claude Shannon - Wikipedia", "encrypted_content": "EqgfCioIARgBIiQ3YTAwMjY1Mi1mZjM5LTQ1NGUtODgxNC1kNjNjNTk1ZWI3Y...", "page_age": "April 30, 2025" } ] }, { "text": "Based on the search results, ", "type": "text" }, // 4. Claude's response with citations { "text": "Claude Shannon was born on April 30, 1916, in Petoskey, Michigan", "type": "text", "citations": [ { "type": "web_search_result_location", "url": "https://en.wikipedia.org/wiki/Claude_Shannon", "title": "Claude Shannon - Wikipedia", "encrypted_index": "Eo8BCioIAhgBIiQyYjQ0OWJmZi1lNm..", "cited_text": "Claude Elwood Shannon (April 30, 1916 – February 24, 2001) was an American mathematician, electrical engineer, computer scientist, cryptographer and i..." } ] } ], "id": "msg_a930390d3a", "usage": { "input_tokens": 6039, "output_tokens": 931, "server_tool_use": { "web_search_requests": 1 } }, "stop_reason": "end_turn"}{ "type": "web_search_tool_result", "tool_use_id": "servertoolu_a93jad", "content": { "type": "web_search_tool_result_error", "error_code": "max_uses_exceeded" }}
</CodeGroup>
## Streaming
With streaming enabled, you'll receive search events as part of the stream. There will be a pause while the search executes:
```javascriptevent: message_startdata: {"type": "message_start", "message": {"id": "msg_abc123", "type": "message"}}
event: content_block_startdata: {"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}}
// Claude's decision to search
event: content_block_startdata: {"type": "content_block_start", "index": 1, "content_block": {"type": "server_tool_use", "id": "srvtoolu_xyz789", "name": "web_search"}}
// Search query streamedevent: content_block_deltadata: {"type": "content_block_delta", "index": 1, "delta": {"type": "input_json_delta", "partial_json": "{\"query\":\"latest quantum computing breakthroughs 2025\"}"}}
// Pause while search executes
// Search results streamedevent: content_block_startdata: {"type": "content_block_start", "index": 2, "content_block": {"type": "web_search_tool_result", "tool_use_id": "srvtoolu_xyz789", "content": [{"type": "web_search_result", "title": "Quantum Computing Breakthroughs in 2025", "url": "https://example.com"}]}}
// Claude's response with citations (omitted in this example)"usage": { "input_tokens": 105, "output_tokens": 6039, "cache_read_input_tokens": 7123, "cache_creation_input_tokens": 7345, "server_tool_use": { "web_search_requests": 1 }}