fix: handle all web_search actions and in progress invocations (#9960)

### Summary
- Parse all `web_search` tool actions (`search`, `find_in_page`,
`open_page`).
- Previously we only parsed + displayed `search`, which made the TUI
appear to pause when the other actions were being used.
- Show in progress `web_search` calls as `Searching the web`
  - Previously we only showed completed tool calls

<img width="308" height="149" alt="image"
src="https://github.com/user-attachments/assets/90a4e8ff-b06a-48ff-a282-b57b31121845"
/>

### Tests
Added + updated tests, tested locally

### Follow ups
Update VSCode extension to display these as well
This commit is contained in:
sayan-oai
2026-01-26 19:33:48 -08:00
committed by GitHub
parent 998e88b12a
commit 86adf53235
18 changed files with 462 additions and 62 deletions

View File

@@ -43,6 +43,8 @@ use codex_core::protocol::FileChange;
use codex_core::protocol::McpAuthStatus;
use codex_core::protocol::McpInvocation;
use codex_core::protocol::SessionConfiguredEvent;
use codex_core::web_search::web_search_detail;
use codex_protocol::models::WebSearchAction;
use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;
use codex_protocol::plan_tool::PlanItemArg;
use codex_protocol::plan_tool::StepStatus;
@@ -1342,9 +1344,89 @@ pub(crate) fn new_active_mcp_tool_call(
McpToolCallCell::new(call_id, invocation, animations_enabled)
}
pub(crate) fn new_web_search_call(query: String) -> PrefixedWrappedHistoryCell {
let text: Text<'static> = Line::from(vec!["Searched".bold(), " ".into(), query.into()]).into();
PrefixedWrappedHistoryCell::new(text, "".dim(), " ")
fn web_search_header(completed: bool) -> &'static str {
if completed {
"Searched"
} else {
"Searching the web"
}
}
#[derive(Debug)]
pub(crate) struct WebSearchCell {
call_id: String,
query: String,
action: Option<WebSearchAction>,
start_time: Instant,
completed: bool,
animations_enabled: bool,
}
impl WebSearchCell {
pub(crate) fn new(
call_id: String,
query: String,
action: Option<WebSearchAction>,
animations_enabled: bool,
) -> Self {
Self {
call_id,
query,
action,
start_time: Instant::now(),
completed: false,
animations_enabled,
}
}
pub(crate) fn call_id(&self) -> &str {
&self.call_id
}
pub(crate) fn update(&mut self, action: WebSearchAction, query: String) {
self.action = Some(action);
self.query = query;
}
pub(crate) fn complete(&mut self) {
self.completed = true;
}
}
impl HistoryCell for WebSearchCell {
fn display_lines(&self, width: u16) -> Vec<Line<'static>> {
let bullet = if self.completed {
"".dim()
} else {
spinner(Some(self.start_time), self.animations_enabled)
};
let header = web_search_header(self.completed);
let detail = web_search_detail(self.action.as_ref(), &self.query);
let text: Text<'static> = if detail.is_empty() {
Line::from(vec![header.bold()]).into()
} else {
Line::from(vec![header.bold(), " ".into(), detail.into()]).into()
};
PrefixedWrappedHistoryCell::new(text, vec![bullet, " ".into()], " ").display_lines(width)
}
}
pub(crate) fn new_active_web_search_call(
call_id: String,
query: String,
animations_enabled: bool,
) -> WebSearchCell {
WebSearchCell::new(call_id, query, None, animations_enabled)
}
pub(crate) fn new_web_search_call(
call_id: String,
query: String,
action: WebSearchAction,
) -> WebSearchCell {
let mut cell = WebSearchCell::new(call_id, query, Some(action), false);
cell.complete();
cell
}
/// If the first content is an image, return a new cell with the image.
@@ -1837,6 +1919,7 @@ mod tests {
use codex_core::config::types::McpServerConfig;
use codex_core::config::types::McpServerTransportConfig;
use codex_core::protocol::McpAuthStatus;
use codex_protocol::models::WebSearchAction;
use codex_protocol::parse_command::ParsedCommand;
use dirs::home_dir;
use pretty_assertions::assert_eq;
@@ -2060,8 +2143,12 @@ mod tests {
#[test]
fn web_search_history_cell_snapshot() {
let query =
"example search query with several generic words to exercise wrapping".to_string();
let cell = new_web_search_call(
"example search query with several generic words to exercise wrapping".to_string(),
"call-1".to_string(),
query.clone(),
WebSearchAction::Search { query: Some(query) },
);
let rendered = render_lines(&cell.display_lines(64)).join("\n");
@@ -2070,8 +2157,12 @@ mod tests {
#[test]
fn web_search_history_cell_wraps_with_indented_continuation() {
let query =
"example search query with several generic words to exercise wrapping".to_string();
let cell = new_web_search_call(
"example search query with several generic words to exercise wrapping".to_string(),
"call-1".to_string(),
query.clone(),
WebSearchAction::Search { query: Some(query) },
);
let rendered = render_lines(&cell.display_lines(64));
@@ -2086,7 +2177,12 @@ mod tests {
#[test]
fn web_search_history_cell_short_query_does_not_wrap() {
let cell = new_web_search_call("short query".to_string());
let query = "short query".to_string();
let cell = new_web_search_call(
"call-1".to_string(),
query.clone(),
WebSearchAction::Search { query: Some(query) },
);
let rendered = render_lines(&cell.display_lines(64));
assert_eq!(rendered, vec!["• Searched short query".to_string()]);
@@ -2094,8 +2190,12 @@ mod tests {
#[test]
fn web_search_history_cell_transcript_snapshot() {
let query =
"example search query with several generic words to exercise wrapping".to_string();
let cell = new_web_search_call(
"example search query with several generic words to exercise wrapping".to_string(),
"call-1".to_string(),
query.clone(),
WebSearchAction::Search { query: Some(query) },
);
let rendered = render_lines(&cell.transcript_lines(64)).join("\n");