mirror of
https://github.com/openai/codex.git
synced 2026-04-30 19:32:04 +03: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:
@@ -1,3 +1,4 @@
|
||||
use crate::models::WebSearchAction;
|
||||
use crate::protocol::AgentMessageEvent;
|
||||
use crate::protocol::AgentReasoningEvent;
|
||||
use crate::protocol::AgentReasoningRawContentEvent;
|
||||
@@ -49,10 +50,11 @@ pub struct ReasoningItem {
|
||||
pub raw_content: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema)]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema, PartialEq)]
|
||||
pub struct WebSearchItem {
|
||||
pub id: String,
|
||||
pub query: String,
|
||||
pub action: WebSearchAction,
|
||||
}
|
||||
|
||||
impl UserMessageItem {
|
||||
@@ -181,6 +183,7 @@ impl WebSearchItem {
|
||||
EventMsg::WebSearchEnd(WebSearchEndEvent {
|
||||
call_id: self.id.clone(),
|
||||
query: self.query.clone(),
|
||||
action: self.action.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +157,9 @@ pub enum ResponseItem {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
status: Option<String>,
|
||||
action: WebSearchAction,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
action: Option<WebSearchAction>,
|
||||
},
|
||||
// Generated by the harness but considered exactly as a model response.
|
||||
GhostSnapshot {
|
||||
@@ -1034,10 +1036,12 @@ mod tests {
|
||||
"query": "weather seattle"
|
||||
}
|
||||
}"#,
|
||||
WebSearchAction::Search {
|
||||
None,
|
||||
Some(WebSearchAction::Search {
|
||||
query: Some("weather seattle".into()),
|
||||
},
|
||||
}),
|
||||
Some("completed".into()),
|
||||
true,
|
||||
),
|
||||
(
|
||||
r#"{
|
||||
@@ -1048,10 +1052,12 @@ mod tests {
|
||||
"url": "https://example.com"
|
||||
}
|
||||
}"#,
|
||||
WebSearchAction::OpenPage {
|
||||
None,
|
||||
Some(WebSearchAction::OpenPage {
|
||||
url: Some("https://example.com".into()),
|
||||
},
|
||||
}),
|
||||
Some("open".into()),
|
||||
true,
|
||||
),
|
||||
(
|
||||
r#"{
|
||||
@@ -1063,26 +1069,43 @@ mod tests {
|
||||
"pattern": "installation"
|
||||
}
|
||||
}"#,
|
||||
WebSearchAction::FindInPage {
|
||||
None,
|
||||
Some(WebSearchAction::FindInPage {
|
||||
url: Some("https://example.com/docs".into()),
|
||||
pattern: Some("installation".into()),
|
||||
},
|
||||
}),
|
||||
Some("in_progress".into()),
|
||||
true,
|
||||
),
|
||||
(
|
||||
r#"{
|
||||
"type": "web_search_call",
|
||||
"status": "in_progress",
|
||||
"id": "ws_partial"
|
||||
}"#,
|
||||
Some("ws_partial".into()),
|
||||
None,
|
||||
Some("in_progress".into()),
|
||||
false,
|
||||
),
|
||||
];
|
||||
|
||||
for (json_literal, expected_action, expected_status) in cases {
|
||||
for (json_literal, expected_id, expected_action, expected_status, expect_roundtrip) in cases
|
||||
{
|
||||
let parsed: ResponseItem = serde_json::from_str(json_literal)?;
|
||||
let expected = ResponseItem::WebSearchCall {
|
||||
id: None,
|
||||
id: expected_id.clone(),
|
||||
status: expected_status.clone(),
|
||||
action: expected_action.clone(),
|
||||
};
|
||||
assert_eq!(parsed, expected);
|
||||
|
||||
let serialized = serde_json::to_value(&parsed)?;
|
||||
let original_value: serde_json::Value = serde_json::from_str(json_literal)?;
|
||||
assert_eq!(serialized, original_value);
|
||||
let mut expected_serialized: serde_json::Value = serde_json::from_str(json_literal)?;
|
||||
if !expect_roundtrip && let Some(obj) = expected_serialized.as_object_mut() {
|
||||
obj.remove("id");
|
||||
}
|
||||
assert_eq!(serialized, expected_serialized);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -24,6 +24,7 @@ use crate::message_history::HistoryEntry;
|
||||
use crate::models::BaseInstructions;
|
||||
use crate::models::ContentItem;
|
||||
use crate::models::ResponseItem;
|
||||
use crate::models::WebSearchAction;
|
||||
use crate::num_format::format_with_separators;
|
||||
use crate::openai_models::ReasoningEffort as ReasoningEffortConfig;
|
||||
use crate::parse_command::ParsedCommand;
|
||||
@@ -1041,6 +1042,7 @@ impl HasLegacyEvent for ReasoningRawContentDeltaEvent {
|
||||
impl HasLegacyEvent for EventMsg {
|
||||
fn as_legacy_events(&self, show_raw_agent_reasoning: bool) -> Vec<EventMsg> {
|
||||
match self {
|
||||
EventMsg::ItemStarted(event) => event.as_legacy_events(show_raw_agent_reasoning),
|
||||
EventMsg::ItemCompleted(event) => event.as_legacy_events(show_raw_agent_reasoning),
|
||||
EventMsg::AgentMessageContentDelta(event) => {
|
||||
event.as_legacy_events(show_raw_agent_reasoning)
|
||||
@@ -1402,6 +1404,7 @@ pub struct WebSearchBeginEvent {
|
||||
pub struct WebSearchEndEvent {
|
||||
pub call_id: String,
|
||||
pub query: String,
|
||||
pub action: WebSearchAction,
|
||||
}
|
||||
|
||||
// Conversation kept for backward compatibility.
|
||||
@@ -2375,6 +2378,9 @@ mod tests {
|
||||
item: TurnItem::WebSearch(WebSearchItem {
|
||||
id: "search-1".into(),
|
||||
query: "find docs".into(),
|
||||
action: WebSearchAction::Search {
|
||||
query: Some("find docs".into()),
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user