mirror of
https://github.com/openai/codex.git
synced 2026-02-01 22:47:52 +00:00
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:
@@ -32,6 +32,7 @@ use codex_core::protocol::TurnCompleteEvent;
|
||||
use codex_core::protocol::TurnDiffEvent;
|
||||
use codex_core::protocol::WarningEvent;
|
||||
use codex_core::protocol::WebSearchEndEvent;
|
||||
use codex_core::web_search::web_search_detail;
|
||||
use codex_protocol::num_format::format_with_separators;
|
||||
use owo_colors::OwoColorize;
|
||||
use owo_colors::Style;
|
||||
@@ -370,8 +371,20 @@ impl EventProcessor for EventProcessorWithHumanOutput {
|
||||
}
|
||||
}
|
||||
}
|
||||
EventMsg::WebSearchEnd(WebSearchEndEvent { call_id: _, query }) => {
|
||||
ts_msg!(self, "🌐 Searched: {query}");
|
||||
EventMsg::WebSearchBegin(_) => {
|
||||
ts_msg!(self, "🌐 Searching the web...");
|
||||
}
|
||||
EventMsg::WebSearchEnd(WebSearchEndEvent {
|
||||
call_id: _,
|
||||
query,
|
||||
action,
|
||||
}) => {
|
||||
let detail = web_search_detail(Some(&action), &query);
|
||||
if detail.is_empty() {
|
||||
ts_msg!(self, "🌐 Searched the web");
|
||||
} else {
|
||||
ts_msg!(self, "🌐 Searched: {detail}");
|
||||
}
|
||||
}
|
||||
EventMsg::PatchApplyBegin(PatchApplyBeginEvent {
|
||||
call_id,
|
||||
@@ -737,8 +750,7 @@ impl EventProcessor for EventProcessorWithHumanOutput {
|
||||
);
|
||||
}
|
||||
EventMsg::ShutdownComplete => return CodexStatus::Shutdown,
|
||||
EventMsg::WebSearchBegin(_)
|
||||
| EventMsg::ExecApprovalRequest(_)
|
||||
EventMsg::ExecApprovalRequest(_)
|
||||
| EventMsg::ApplyPatchApprovalRequest(_)
|
||||
| EventMsg::TerminalInteraction(_)
|
||||
| EventMsg::ExecCommandOutputDelta(_)
|
||||
|
||||
@@ -49,6 +49,7 @@ use codex_core::protocol::CollabCloseBeginEvent;
|
||||
use codex_core::protocol::CollabCloseEndEvent;
|
||||
use codex_core::protocol::CollabWaitingBeginEvent;
|
||||
use codex_core::protocol::CollabWaitingEndEvent;
|
||||
use codex_protocol::models::WebSearchAction;
|
||||
use codex_protocol::plan_tool::StepStatus;
|
||||
use codex_protocol::plan_tool::UpdatePlanArgs;
|
||||
use serde_json::Value as JsonValue;
|
||||
@@ -66,6 +67,7 @@ pub struct EventProcessorWithJsonOutput {
|
||||
last_total_token_usage: Option<codex_core::protocol::TokenUsage>,
|
||||
running_mcp_tool_calls: HashMap<String, RunningMcpToolCall>,
|
||||
running_collab_tool_calls: HashMap<String, RunningCollabToolCall>,
|
||||
running_web_search_calls: HashMap<String, String>,
|
||||
last_critical_error: Option<ThreadErrorEvent>,
|
||||
}
|
||||
|
||||
@@ -107,6 +109,7 @@ impl EventProcessorWithJsonOutput {
|
||||
last_total_token_usage: None,
|
||||
running_mcp_tool_calls: HashMap::new(),
|
||||
running_collab_tool_calls: HashMap::new(),
|
||||
running_web_search_calls: HashMap::new(),
|
||||
last_critical_error: None,
|
||||
}
|
||||
}
|
||||
@@ -138,7 +141,7 @@ impl EventProcessorWithJsonOutput {
|
||||
protocol::EventMsg::CollabCloseEnd(ev) => self.handle_collab_close_end(ev),
|
||||
protocol::EventMsg::PatchApplyBegin(ev) => self.handle_patch_apply_begin(ev),
|
||||
protocol::EventMsg::PatchApplyEnd(ev) => self.handle_patch_apply_end(ev),
|
||||
protocol::EventMsg::WebSearchBegin(_) => Vec::new(),
|
||||
protocol::EventMsg::WebSearchBegin(ev) => self.handle_web_search_begin(ev),
|
||||
protocol::EventMsg::WebSearchEnd(ev) => self.handle_web_search_end(ev),
|
||||
protocol::EventMsg::TokenCount(ev) => {
|
||||
if let Some(info) = &ev.info {
|
||||
@@ -195,11 +198,36 @@ impl EventProcessorWithJsonOutput {
|
||||
})]
|
||||
}
|
||||
|
||||
fn handle_web_search_end(&self, ev: &protocol::WebSearchEndEvent) -> Vec<ThreadEvent> {
|
||||
fn handle_web_search_begin(&mut self, ev: &protocol::WebSearchBeginEvent) -> Vec<ThreadEvent> {
|
||||
if self.running_web_search_calls.contains_key(&ev.call_id) {
|
||||
return Vec::new();
|
||||
}
|
||||
let item_id = self.get_next_item_id();
|
||||
self.running_web_search_calls
|
||||
.insert(ev.call_id.clone(), item_id.clone());
|
||||
let item = ThreadItem {
|
||||
id: self.get_next_item_id(),
|
||||
id: item_id,
|
||||
details: ThreadItemDetails::WebSearch(WebSearchItem {
|
||||
id: ev.call_id.clone(),
|
||||
query: String::new(),
|
||||
action: WebSearchAction::Other,
|
||||
}),
|
||||
};
|
||||
|
||||
vec![ThreadEvent::ItemStarted(ItemStartedEvent { item })]
|
||||
}
|
||||
|
||||
fn handle_web_search_end(&mut self, ev: &protocol::WebSearchEndEvent) -> Vec<ThreadEvent> {
|
||||
let item_id = self
|
||||
.running_web_search_calls
|
||||
.remove(&ev.call_id)
|
||||
.unwrap_or_else(|| self.get_next_item_id());
|
||||
let item = ThreadItem {
|
||||
id: item_id,
|
||||
details: ThreadItemDetails::WebSearch(WebSearchItem {
|
||||
id: ev.call_id.clone(),
|
||||
query: ev.query.clone(),
|
||||
action: ev.action.clone(),
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use codex_protocol::models::WebSearchAction;
|
||||
use mcp_types::ContentBlock as McpContentBlock;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
@@ -280,7 +281,9 @@ pub struct McpToolCallItem {
|
||||
/// A web search request.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS)]
|
||||
pub struct WebSearchItem {
|
||||
pub id: String,
|
||||
pub query: String,
|
||||
pub action: WebSearchAction,
|
||||
}
|
||||
|
||||
/// An error notification.
|
||||
|
||||
@@ -20,6 +20,7 @@ use codex_core::protocol::PatchApplyEndEvent;
|
||||
use codex_core::protocol::SandboxPolicy;
|
||||
use codex_core::protocol::SessionConfiguredEvent;
|
||||
use codex_core::protocol::WarningEvent;
|
||||
use codex_core::protocol::WebSearchBeginEvent;
|
||||
use codex_core::protocol::WebSearchEndEvent;
|
||||
use codex_exec::event_processor_with_jsonl_output::EventProcessorWithJsonOutput;
|
||||
use codex_exec::exec_events::AgentMessageItem;
|
||||
@@ -54,6 +55,7 @@ use codex_exec::exec_events::TurnStartedEvent;
|
||||
use codex_exec::exec_events::Usage;
|
||||
use codex_exec::exec_events::WebSearchItem;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::models::WebSearchAction;
|
||||
use codex_protocol::plan_tool::PlanItemArg;
|
||||
use codex_protocol::plan_tool::StepStatus;
|
||||
use codex_protocol::plan_tool::UpdatePlanArgs;
|
||||
@@ -124,11 +126,15 @@ fn task_started_produces_turn_started_event() {
|
||||
fn web_search_end_emits_item_completed() {
|
||||
let mut ep = EventProcessorWithJsonOutput::new(None);
|
||||
let query = "rust async await".to_string();
|
||||
let action = WebSearchAction::Search {
|
||||
query: Some(query.clone()),
|
||||
};
|
||||
let out = ep.collect_thread_events(&event(
|
||||
"w1",
|
||||
EventMsg::WebSearchEnd(WebSearchEndEvent {
|
||||
call_id: "call-123".to_string(),
|
||||
query: query.clone(),
|
||||
action: action.clone(),
|
||||
}),
|
||||
));
|
||||
|
||||
@@ -137,12 +143,82 @@ fn web_search_end_emits_item_completed() {
|
||||
vec![ThreadEvent::ItemCompleted(ItemCompletedEvent {
|
||||
item: ThreadItem {
|
||||
id: "item_0".to_string(),
|
||||
details: ThreadItemDetails::WebSearch(WebSearchItem { query }),
|
||||
details: ThreadItemDetails::WebSearch(WebSearchItem {
|
||||
id: "call-123".to_string(),
|
||||
query,
|
||||
action,
|
||||
}),
|
||||
},
|
||||
})]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn web_search_begin_emits_item_started() {
|
||||
let mut ep = EventProcessorWithJsonOutput::new(None);
|
||||
let out = ep.collect_thread_events(&event(
|
||||
"w0",
|
||||
EventMsg::WebSearchBegin(WebSearchBeginEvent {
|
||||
call_id: "call-0".to_string(),
|
||||
}),
|
||||
));
|
||||
|
||||
assert_eq!(out.len(), 1);
|
||||
let ThreadEvent::ItemStarted(ItemStartedEvent { item }) = &out[0] else {
|
||||
panic!("expected ItemStarted");
|
||||
};
|
||||
assert!(item.id.starts_with("item_"));
|
||||
assert_eq!(
|
||||
item.details,
|
||||
ThreadItemDetails::WebSearch(WebSearchItem {
|
||||
id: "call-0".to_string(),
|
||||
query: String::new(),
|
||||
action: WebSearchAction::Other,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn web_search_begin_then_end_reuses_item_id() {
|
||||
let mut ep = EventProcessorWithJsonOutput::new(None);
|
||||
let begin = ep.collect_thread_events(&event(
|
||||
"w0",
|
||||
EventMsg::WebSearchBegin(WebSearchBeginEvent {
|
||||
call_id: "call-1".to_string(),
|
||||
}),
|
||||
));
|
||||
let ThreadEvent::ItemStarted(ItemStartedEvent { item: started_item }) = &begin[0] else {
|
||||
panic!("expected ItemStarted");
|
||||
};
|
||||
let action = WebSearchAction::Search {
|
||||
query: Some("rust async await".to_string()),
|
||||
};
|
||||
let end = ep.collect_thread_events(&event(
|
||||
"w1",
|
||||
EventMsg::WebSearchEnd(WebSearchEndEvent {
|
||||
call_id: "call-1".to_string(),
|
||||
query: "rust async await".to_string(),
|
||||
action: action.clone(),
|
||||
}),
|
||||
));
|
||||
let ThreadEvent::ItemCompleted(ItemCompletedEvent {
|
||||
item: completed_item,
|
||||
}) = &end[0]
|
||||
else {
|
||||
panic!("expected ItemCompleted");
|
||||
};
|
||||
|
||||
assert_eq!(completed_item.id, started_item.id);
|
||||
assert_eq!(
|
||||
completed_item.details,
|
||||
ThreadItemDetails::WebSearch(WebSearchItem {
|
||||
id: "call-1".to_string(),
|
||||
query: "rust async await".to_string(),
|
||||
action,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plan_update_emits_todo_list_started_updated_and_completed() {
|
||||
let mut ep = EventProcessorWithJsonOutput::new(None);
|
||||
|
||||
Reference in New Issue
Block a user