core: migrate standalone web search to extension-owned turn items (#31525)

## Description

This PR migrates standalone web search onto the extension-owned
turn-item path introduced in #31283.

Standalone web search now emits `ExtensionItem::WebSearch` through
generic `TurnItem::Extension`, while app-server still exposes the
existing typed `ThreadItem::WebSearch` JSON shape. Hosted Responses API
web search stays on core-owned `TurnItem::WebSearch`.

## What changed

- Added `web_search::WebSearchItem` and `WebSearchAction` to
`codex-extension-items` under the stable `web.search` kind.
- Collapsed `ExtensionTurnItem` to generic `{ item, legacy_events }` now
that no typed extension special cases remain.
- Kept the existing `WebSearchBegin` / `WebSearchEnd` compatibility
events and canonical-first ordering.
- Updated app-server projection/history and generated TypeScript; the
app-server JSON schema is unchanged.
This commit is contained in:
Owen Lin
2026-07-08 09:40:00 -07:00
committed by GitHub
parent e212cc95b0
commit a219b6fdb4
26 changed files with 279 additions and 209 deletions

View File

@@ -81,8 +81,8 @@ impl EventProcessorWithHumanOutput {
"started".style(self.dimmed)
);
}
ThreadItem::WebSearch { query, .. } => {
eprintln!("{} {}", "web search:".style(self.bold), query);
ThreadItem::WebSearch(item) => {
eprintln!("{} {}", "web search:".style(self.bold), item.query);
}
ThreadItem::FileChange { .. } => {
eprintln!("{}", "apply patch".style(self.bold));
@@ -196,8 +196,8 @@ impl EventProcessorWithHumanOutput {
eprintln!("{}", error.message.style(self.red));
}
}
ThreadItem::WebSearch { query, .. } => {
eprintln!("{} {}", "web search:".style(self.bold), query);
ThreadItem::WebSearch(item) => {
eprintln!("{} {}", "web search:".style(self.bold), item.query);
}
ThreadItem::ContextCompaction { .. } => {
eprintln!("{}", "context compacted".style(self.dimmed));

View File

@@ -293,16 +293,12 @@ impl EventProcessorWithJsonOutput {
},
}),
}),
ThreadItem::WebSearch {
id: raw_id,
query,
action,
} => Some(ExecThreadItem {
ThreadItem::WebSearch(item) => Some(ExecThreadItem {
id: make_id(),
details: ThreadItemDetails::WebSearch(WebSearchItem {
id: raw_id,
query,
action: match action {
id: item.id,
query: item.query,
action: match item.action {
Some(action) => serde_json::from_value(
serde_json::to_value(action).unwrap_or_else(|_| json!("other")),
)

View File

@@ -27,6 +27,7 @@ use codex_app_server_protocol::TurnPlanUpdatedNotification;
use codex_app_server_protocol::TurnStartedNotification;
use codex_app_server_protocol::TurnStatus;
use codex_app_server_protocol::WebSearchAction as ApiWebSearchAction;
use codex_app_server_protocol::WebSearchItem as ApiWebSearchItem;
use codex_protocol::SessionId;
use codex_protocol::ThreadId;
use codex_protocol::models::PermissionProfile;
@@ -362,14 +363,14 @@ fn web_search_completion_preserves_query_and_action() {
let collected = processor.collect_thread_events(ServerNotification::ItemCompleted(
ItemCompletedNotification {
item: ThreadItem::WebSearch {
item: ThreadItem::WebSearch(ApiWebSearchItem {
id: "search-1".to_string(),
query: "rust async await".to_string(),
action: Some(ApiWebSearchAction::Search {
query: Some("rust async await".to_string()),
queries: None,
}),
},
}),
thread_id: "thread-1".to_string(),
turn_id: "turn-1".to_string(),
completed_at_ms: 0,
@@ -403,11 +404,11 @@ fn web_search_start_and_completion_reuse_item_id() {
let started =
processor.collect_thread_events(ServerNotification::ItemStarted(ItemStartedNotification {
item: ThreadItem::WebSearch {
item: ThreadItem::WebSearch(ApiWebSearchItem {
id: "search-1".to_string(),
query: String::new(),
action: None,
},
}),
thread_id: "thread-1".to_string(),
turn_id: "turn-1".to_string(),
started_at_ms: 0,
@@ -415,14 +416,14 @@ fn web_search_start_and_completion_reuse_item_id() {
let completed = processor.collect_thread_events(ServerNotification::ItemCompleted(
ItemCompletedNotification {
item: ThreadItem::WebSearch {
item: ThreadItem::WebSearch(ApiWebSearchItem {
id: "search-1".to_string(),
query: "rust async await".to_string(),
action: Some(ApiWebSearchAction::Search {
query: Some("rust async await".to_string()),
queries: None,
}),
},
}),
thread_id: "thread-1".to_string(),
turn_id: "turn-1".to_string(),
completed_at_ms: 0,