mirror of
https://github.com/openai/codex.git
synced 2026-10-18 17:18:52 +00:00
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:
Generated
+1
@@ -4471,6 +4471,7 @@ dependencies = [
|
||||
"codex-api",
|
||||
"codex-core",
|
||||
"codex-extension-api",
|
||||
"codex-extension-items",
|
||||
"codex-login",
|
||||
"codex-model-provider",
|
||||
"codex-model-provider-info",
|
||||
|
||||
@@ -140,6 +140,7 @@ use codex_app_server_protocol::TurnStatus as AppServerTurnStatus;
|
||||
use codex_app_server_protocol::TurnSteerParams;
|
||||
use codex_app_server_protocol::TurnSteerResponse;
|
||||
use codex_app_server_protocol::UserInput;
|
||||
use codex_app_server_protocol::WebSearchItem;
|
||||
use codex_login::default_client::DEFAULT_ORIGINATOR;
|
||||
use codex_login::default_client::originator;
|
||||
use codex_plugin::AppConnectorId;
|
||||
@@ -4401,11 +4402,11 @@ async fn turn_event_counts_completed_tool_items() {
|
||||
agent_thread_id: "thread-child".to_string(),
|
||||
agent_path: "/root/child".to_string(),
|
||||
},
|
||||
ThreadItem::WebSearch {
|
||||
ThreadItem::WebSearch(WebSearchItem {
|
||||
id: "web-1".to_string(),
|
||||
query: "codex".to_string(),
|
||||
action: None,
|
||||
},
|
||||
}),
|
||||
ThreadItem::ImageGeneration(ImageGenerationItem {
|
||||
id: "image-1".to_string(),
|
||||
status: "completed".to_string(),
|
||||
|
||||
@@ -402,7 +402,7 @@ impl TurnToolCounts {
|
||||
ThreadItem::CollabAgentToolCall { .. } | ThreadItem::SubAgentActivity { .. } => {
|
||||
self.subagent_tool_call += 1;
|
||||
}
|
||||
ThreadItem::WebSearch { .. } => self.web_search += 1,
|
||||
ThreadItem::WebSearch(_) => self.web_search += 1,
|
||||
ThreadItem::ImageGeneration(_) => self.image_generation += 1,
|
||||
ThreadItem::UserMessage { .. }
|
||||
| ThreadItem::HookPrompt { .. }
|
||||
@@ -1732,8 +1732,8 @@ fn tracked_tool_item_id(item: &ThreadItem) -> Option<&str> {
|
||||
| ThreadItem::FileChange { id, .. }
|
||||
| ThreadItem::McpToolCall { id, .. }
|
||||
| ThreadItem::DynamicToolCall { id, .. }
|
||||
| ThreadItem::CollabAgentToolCall { id, .. }
|
||||
| ThreadItem::WebSearch { id, .. } => Some(id),
|
||||
| ThreadItem::CollabAgentToolCall { id, .. } => Some(id),
|
||||
ThreadItem::WebSearch(item) => Some(&item.id),
|
||||
ThreadItem::ImageGeneration(item) => Some(&item.id),
|
||||
ThreadItem::UserMessage { .. }
|
||||
| ThreadItem::HookPrompt { .. }
|
||||
@@ -2027,11 +2027,11 @@ fn tool_item_event(input: ToolItemEventInput<'_>) -> Option<TrackEventRequest> {
|
||||
},
|
||||
))
|
||||
}
|
||||
ThreadItem::WebSearch { id, query, action } => {
|
||||
ThreadItem::WebSearch(item) => {
|
||||
let base = tool_item_base(
|
||||
thread_id,
|
||||
turn_id,
|
||||
id.clone(),
|
||||
item.id.clone(),
|
||||
"web_search".to_string(),
|
||||
ToolItemOutcome {
|
||||
terminal_status: ToolItemTerminalStatus::Completed,
|
||||
@@ -2051,9 +2051,9 @@ fn tool_item_event(input: ToolItemEventInput<'_>) -> Option<TrackEventRequest> {
|
||||
event_type: "codex_web_search_event",
|
||||
event_params: CodexWebSearchEventParams {
|
||||
base,
|
||||
web_search_action: action.as_ref().map(web_search_action_kind),
|
||||
query_present: !query.trim().is_empty(),
|
||||
query_count: web_search_query_count(query, action.as_ref()),
|
||||
web_search_action: item.action.as_ref().map(web_search_action_kind),
|
||||
query_present: !item.query.trim().is_empty(),
|
||||
query_count: web_search_query_count(&item.query, item.action.as_ref()),
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { WebSearchAction } from "./v2/WebSearchAction";
|
||||
|
||||
export type WebSearchItem = { id: string, query: string, action: WebSearchAction | null, };
|
||||
@@ -82,6 +82,7 @@ export type { Tool } from "./Tool";
|
||||
export type { Verbosity } from "./Verbosity";
|
||||
export type { WebSearchAction } from "./WebSearchAction";
|
||||
export type { WebSearchContextSize } from "./WebSearchContextSize";
|
||||
export type { WebSearchItem } from "./WebSearchItem";
|
||||
export type { WebSearchLocation } from "./WebSearchLocation";
|
||||
export type { WebSearchMode } from "./WebSearchMode";
|
||||
export type { WebSearchToolConfig } from "./WebSearchToolConfig";
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { ImageGenerationItem } from "../ImageGenerationItem";
|
||||
import type { LegacyAppPathString } from "../LegacyAppPathString";
|
||||
import type { MessagePhase } from "../MessagePhase";
|
||||
import type { ReasoningEffort } from "../ReasoningEffort";
|
||||
import type { WebSearchItem } from "../WebSearchItem";
|
||||
import type { JsonValue } from "../serde_json/JsonValue";
|
||||
import type { CollabAgentState } from "./CollabAgentState";
|
||||
import type { CollabAgentTool } from "./CollabAgentTool";
|
||||
@@ -24,7 +25,6 @@ import type { MemoryCitation } from "./MemoryCitation";
|
||||
import type { PatchApplyStatus } from "./PatchApplyStatus";
|
||||
import type { SubAgentActivityKind } from "./SubAgentActivityKind";
|
||||
import type { UserInput } from "./UserInput";
|
||||
import type { WebSearchAction } from "./WebSearchAction";
|
||||
|
||||
export type ThreadItem = { "type": "userMessage", id: string, clientId: string | null, content: Array<UserInput>, } | { "type": "hookPrompt", id: string, fragments: Array<HookPromptFragment>, } | { "type": "agentMessage", id: string, text: string, phase: MessagePhase | null, memoryCitation: MemoryCitation | null, } | { "type": "plan", id: string, text: string, } | { "type": "reasoning", id: string, summary: Array<string>, content: Array<string>, } | { "type": "commandExecution", id: string,
|
||||
/**
|
||||
@@ -105,4 +105,4 @@ reasoningEffort: ReasoningEffort | null,
|
||||
/**
|
||||
* Last known status of the target agents, when available.
|
||||
*/
|
||||
agentsStates: { [key in string]?: CollabAgentState }, } | { "type": "subAgentActivity", id: string, kind: SubAgentActivityKind, agentThreadId: string, agentPath: string, } | { "type": "webSearch", id: string, query: string, action: WebSearchAction | null, } | { "type": "imageView", id: string, path: LegacyAppPathString, } | { "type": "sleep", id: string, durationMs: number, } | { "type": "imageGeneration" } & ImageGenerationItem | { "type": "enteredReviewMode", id: string, review: string, } | { "type": "exitedReviewMode", id: string, review: string, } | { "type": "contextCompaction", id: string, };
|
||||
agentsStates: { [key in string]?: CollabAgentState }, } | { "type": "subAgentActivity", id: string, kind: SubAgentActivityKind, agentThreadId: string, agentPath: string, } | { "type": "webSearch" } & WebSearchItem | { "type": "imageView", id: string, path: LegacyAppPathString, } | { "type": "sleep", id: string, durationMs: number, } | { "type": "imageGeneration" } & ImageGenerationItem | { "type": "enteredReviewMode", id: string, review: string, } | { "type": "exitedReviewMode", id: string, review: string, } | { "type": "contextCompaction", id: string, };
|
||||
|
||||
@@ -21,7 +21,10 @@ use crate::protocol::v2::TurnError;
|
||||
use crate::protocol::v2::TurnItemsView;
|
||||
use crate::protocol::v2::TurnStatus;
|
||||
use crate::protocol::v2::UserInput;
|
||||
#[cfg(test)]
|
||||
use crate::protocol::v2::WebSearchAction;
|
||||
use crate::protocol::v2::WebSearchItem;
|
||||
use crate::protocol::v2::web_search_action_from_core;
|
||||
use codex_extension_items::image_generation::ImageGenerationItem;
|
||||
use codex_protocol::items::parse_hook_prompt_message;
|
||||
use codex_protocol::models::MessagePhase;
|
||||
@@ -613,20 +616,20 @@ impl ThreadHistoryBuilder {
|
||||
}
|
||||
|
||||
fn handle_web_search_begin(&mut self, payload: &WebSearchBeginEvent) {
|
||||
let item = ThreadItem::WebSearch {
|
||||
let item = ThreadItem::WebSearch(WebSearchItem {
|
||||
id: payload.call_id.clone(),
|
||||
query: String::new(),
|
||||
action: None,
|
||||
};
|
||||
});
|
||||
self.upsert_item_in_current_turn(item);
|
||||
}
|
||||
|
||||
fn handle_web_search_end(&mut self, payload: &WebSearchEndEvent) {
|
||||
let item = ThreadItem::WebSearch {
|
||||
let item = ThreadItem::WebSearch(WebSearchItem {
|
||||
id: payload.call_id.clone(),
|
||||
query: payload.query.clone(),
|
||||
action: Some(WebSearchAction::from(payload.action.clone())),
|
||||
};
|
||||
action: Some(web_search_action_from_core(payload.action.clone())),
|
||||
});
|
||||
self.upsert_item_in_current_turn(item);
|
||||
}
|
||||
|
||||
@@ -2549,14 +2552,14 @@ mod tests {
|
||||
assert_eq!(turns[0].items.len(), 4);
|
||||
assert_eq!(
|
||||
turns[0].items[1],
|
||||
ThreadItem::WebSearch {
|
||||
ThreadItem::WebSearch(WebSearchItem {
|
||||
id: "search-1".into(),
|
||||
query: "codex".into(),
|
||||
action: Some(WebSearchAction::Search {
|
||||
query: Some("codex".into()),
|
||||
queries: None,
|
||||
}),
|
||||
}
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
turns[0].items[2],
|
||||
@@ -3982,14 +3985,14 @@ mod tests {
|
||||
ThreadHistoryChangeSet {
|
||||
changed_items: vec![ThreadHistoryItemChange {
|
||||
turn_id: "rollout-0".into(),
|
||||
item: ThreadItem::WebSearch {
|
||||
item: ThreadItem::WebSearch(WebSearchItem {
|
||||
id: "search-1".into(),
|
||||
query: "codex".into(),
|
||||
action: Some(WebSearchAction::Search {
|
||||
query: Some("codex".into()),
|
||||
queries: None,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
}],
|
||||
changed_turns: Vec::new(),
|
||||
removed_turn_ids: Vec::new(),
|
||||
@@ -4117,14 +4120,14 @@ mod tests {
|
||||
ThreadHistoryChangeSet {
|
||||
changed_items: vec![ThreadHistoryItemChange {
|
||||
turn_id: "rollout-0".into(),
|
||||
item: ThreadItem::WebSearch {
|
||||
item: ThreadItem::WebSearch(WebSearchItem {
|
||||
id: "search-1".into(),
|
||||
query: "codex".into(),
|
||||
action: Some(WebSearchAction::Search {
|
||||
query: Some("codex".into()),
|
||||
queries: None,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
}],
|
||||
changed_turns: vec![ThreadHistoryTurnChange {
|
||||
turn_id: "rollout-0".into(),
|
||||
|
||||
@@ -13,6 +13,8 @@ use crate::protocol::item_builders::convert_patch_changes;
|
||||
use codex_experimental_api_macros::ExperimentalApi;
|
||||
use codex_extension_items::ExtensionItem;
|
||||
pub use codex_extension_items::image_generation::ImageGenerationItem;
|
||||
pub use codex_extension_items::web_search::WebSearchAction;
|
||||
pub use codex_extension_items::web_search::WebSearchItem;
|
||||
use codex_protocol::approvals::GuardianAssessmentAction as CoreGuardianAssessmentAction;
|
||||
use codex_protocol::approvals::GuardianAssessmentDecisionSource as CoreGuardianAssessmentDecisionSource;
|
||||
use codex_protocol::approvals::GuardianCommandSource as CoreGuardianCommandSource;
|
||||
@@ -358,13 +360,7 @@ pub enum ThreadItem {
|
||||
agent_thread_id: String,
|
||||
agent_path: String,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
WebSearch {
|
||||
id: String,
|
||||
query: String,
|
||||
action: Option<WebSearchAction>,
|
||||
},
|
||||
WebSearch(WebSearchItem),
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
ImageView {
|
||||
@@ -432,12 +428,12 @@ impl ThreadItem {
|
||||
| ThreadItem::DynamicToolCall { id, .. }
|
||||
| ThreadItem::CollabAgentToolCall { id, .. }
|
||||
| ThreadItem::SubAgentActivity { id, .. }
|
||||
| ThreadItem::WebSearch { id, .. }
|
||||
| ThreadItem::ImageView { id, .. }
|
||||
| ThreadItem::Sleep { id, .. }
|
||||
| ThreadItem::EnteredReviewMode { id, .. }
|
||||
| ThreadItem::ExitedReviewMode { id, .. }
|
||||
| ThreadItem::ContextCompaction { id, .. } => id,
|
||||
ThreadItem::WebSearch(item) => &item.id,
|
||||
ThreadItem::ImageGeneration(item) => &item.id,
|
||||
}
|
||||
}
|
||||
@@ -786,40 +782,20 @@ impl TryFrom<GuardianApprovalReviewAction> for CoreGuardianAssessmentAction {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[ts(tag = "type", rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum WebSearchAction {
|
||||
Search {
|
||||
query: Option<String>,
|
||||
queries: Option<Vec<String>>,
|
||||
},
|
||||
OpenPage {
|
||||
url: Option<String>,
|
||||
},
|
||||
FindInPage {
|
||||
url: Option<String>,
|
||||
pattern: Option<String>,
|
||||
},
|
||||
#[serde(other)]
|
||||
Other,
|
||||
}
|
||||
|
||||
impl From<codex_protocol::models::WebSearchAction> for WebSearchAction {
|
||||
fn from(value: codex_protocol::models::WebSearchAction) -> Self {
|
||||
match value {
|
||||
codex_protocol::models::WebSearchAction::Search { query, queries } => {
|
||||
WebSearchAction::Search { query, queries }
|
||||
}
|
||||
codex_protocol::models::WebSearchAction::OpenPage { url } => {
|
||||
WebSearchAction::OpenPage { url }
|
||||
}
|
||||
codex_protocol::models::WebSearchAction::FindInPage { url, pattern } => {
|
||||
WebSearchAction::FindInPage { url, pattern }
|
||||
}
|
||||
codex_protocol::models::WebSearchAction::Other => WebSearchAction::Other,
|
||||
pub(crate) fn web_search_action_from_core(
|
||||
value: codex_protocol::models::WebSearchAction,
|
||||
) -> WebSearchAction {
|
||||
match value {
|
||||
codex_protocol::models::WebSearchAction::Search { query, queries } => {
|
||||
WebSearchAction::Search { query, queries }
|
||||
}
|
||||
codex_protocol::models::WebSearchAction::OpenPage { url } => {
|
||||
WebSearchAction::OpenPage { url }
|
||||
}
|
||||
codex_protocol::models::WebSearchAction::FindInPage { url, pattern } => {
|
||||
WebSearchAction::FindInPage { url, pattern }
|
||||
}
|
||||
codex_protocol::models::WebSearchAction::Other => WebSearchAction::Other,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -921,11 +897,11 @@ impl From<CoreTurnItem> for ThreadItem {
|
||||
agent_thread_id: activity.agent_thread_id.to_string(),
|
||||
agent_path: String::from(activity.agent_path),
|
||||
},
|
||||
CoreTurnItem::WebSearch(search) => ThreadItem::WebSearch {
|
||||
CoreTurnItem::WebSearch(search) => ThreadItem::WebSearch(WebSearchItem {
|
||||
id: search.id,
|
||||
query: search.query,
|
||||
action: Some(WebSearchAction::from(search.action)),
|
||||
},
|
||||
action: Some(web_search_action_from_core(search.action)),
|
||||
}),
|
||||
CoreTurnItem::ImageView(image) => ThreadItem::ImageView {
|
||||
id: image.id,
|
||||
path: image.path.into(),
|
||||
@@ -936,6 +912,7 @@ impl From<CoreTurnItem> for ThreadItem {
|
||||
},
|
||||
CoreTurnItem::Extension(extension) => match extension {
|
||||
ExtensionItem::ImageGeneration(item) => ThreadItem::ImageGeneration(item),
|
||||
ExtensionItem::WebSearch(item) => ThreadItem::WebSearch(item),
|
||||
},
|
||||
CoreTurnItem::ImageGeneration(image) => {
|
||||
ThreadItem::ImageGeneration(ImageGenerationItem {
|
||||
|
||||
@@ -19,7 +19,7 @@ use codex_protocol::items::ReasoningItem;
|
||||
use codex_protocol::items::SubAgentActivityItem;
|
||||
use codex_protocol::items::TurnItem;
|
||||
use codex_protocol::items::UserMessageItem;
|
||||
use codex_protocol::items::WebSearchItem;
|
||||
use codex_protocol::items::WebSearchItem as CoreWebSearchItem;
|
||||
use codex_protocol::mcp::CallToolResult;
|
||||
use codex_protocol::mcp::McpServerInfo;
|
||||
use codex_protocol::memory_citation::MemoryCitation as CoreMemoryCitation;
|
||||
@@ -2770,7 +2770,7 @@ fn core_turn_item_into_thread_item_converts_supported_variants() {
|
||||
}
|
||||
);
|
||||
|
||||
let search_item = TurnItem::WebSearch(WebSearchItem {
|
||||
let search_item = TurnItem::WebSearch(CoreWebSearchItem {
|
||||
id: "search-1".to_string(),
|
||||
query: "docs".to_string(),
|
||||
action: CoreWebSearchAction::Search {
|
||||
@@ -2779,16 +2779,24 @@ fn core_turn_item_into_thread_item_converts_supported_variants() {
|
||||
},
|
||||
});
|
||||
|
||||
let expected_search_item = WebSearchItem {
|
||||
id: "search-1".to_string(),
|
||||
query: "docs".to_string(),
|
||||
action: Some(WebSearchAction::Search {
|
||||
query: Some("docs".to_string()),
|
||||
queries: None,
|
||||
}),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
ThreadItem::from(search_item),
|
||||
ThreadItem::WebSearch {
|
||||
id: "search-1".to_string(),
|
||||
query: "docs".to_string(),
|
||||
action: Some(WebSearchAction::Search {
|
||||
query: Some("docs".to_string()),
|
||||
queries: None,
|
||||
}),
|
||||
}
|
||||
ThreadItem::WebSearch(expected_search_item.clone())
|
||||
);
|
||||
assert_eq!(
|
||||
ThreadItem::from(TurnItem::Extension(
|
||||
codex_extension_items::ExtensionItem::WebSearch(expected_search_item.clone()),
|
||||
)),
|
||||
ThreadItem::WebSearch(expected_search_item)
|
||||
);
|
||||
|
||||
let image_view_item = TurnItem::ImageView(ImageViewItem {
|
||||
|
||||
@@ -20,6 +20,7 @@ use codex_app_server_protocol::TurnStartParams;
|
||||
use codex_app_server_protocol::TurnStartResponse;
|
||||
use codex_app_server_protocol::UserInput as V2UserInput;
|
||||
use codex_app_server_protocol::WebSearchAction;
|
||||
use codex_app_server_protocol::WebSearchItem;
|
||||
use codex_config::types::AuthCredentialsStoreMode;
|
||||
use core_test_support::responses;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -183,20 +184,20 @@ async fn standalone_web_search_round_trips_output() -> Result<()> {
|
||||
);
|
||||
assert_eq!(
|
||||
started.item,
|
||||
ThreadItem::WebSearch {
|
||||
ThreadItem::WebSearch(WebSearchItem {
|
||||
id: call_id.to_string(),
|
||||
query: String::new(),
|
||||
action: Some(WebSearchAction::Other),
|
||||
}
|
||||
action: None,
|
||||
})
|
||||
);
|
||||
let expected_completed_item = ThreadItem::WebSearch {
|
||||
let expected_completed_item = ThreadItem::WebSearch(WebSearchItem {
|
||||
id: call_id.to_string(),
|
||||
query: "standalone web search".to_string(),
|
||||
action: Some(WebSearchAction::Search {
|
||||
query: Some("standalone web search".to_string()),
|
||||
queries: None,
|
||||
}),
|
||||
};
|
||||
});
|
||||
assert_eq!(completed.item, expected_completed_item);
|
||||
|
||||
drop(mcp);
|
||||
@@ -223,7 +224,7 @@ async fn standalone_web_search_round_trips_output() -> Result<()> {
|
||||
.turns
|
||||
.iter()
|
||||
.flat_map(|turn| &turn.items)
|
||||
.filter(|item| matches!(item, ThreadItem::WebSearch { .. }))
|
||||
.filter(|item| matches!(item, ThreadItem::WebSearch(_)))
|
||||
.collect();
|
||||
assert_eq!(persisted_web_searches, vec![&expected_completed_item]);
|
||||
|
||||
@@ -240,7 +241,7 @@ async fn wait_for_web_search_started(mcp: &mut TestAppServer) -> Result<ItemStar
|
||||
.params
|
||||
.context("item/started notification should include params")?,
|
||||
)?;
|
||||
if matches!(&started.item, ThreadItem::WebSearch { .. }) {
|
||||
if matches!(&started.item, ThreadItem::WebSearch(_)) {
|
||||
return Ok(started);
|
||||
}
|
||||
}
|
||||
@@ -258,7 +259,7 @@ async fn wait_for_web_search_completed(
|
||||
.params
|
||||
.context("item/completed notification should include params")?,
|
||||
)?;
|
||||
if matches!(&completed.item, ThreadItem::WebSearch { .. }) {
|
||||
if matches!(&completed.item, ThreadItem::WebSearch(_)) {
|
||||
return Ok(completed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,6 @@ use codex_tools::TurnItemEmitter;
|
||||
use crate::sandboxing::SandboxPermissions;
|
||||
use crate::session::session::Session;
|
||||
use crate::session::turn_context::TurnContext;
|
||||
use crate::stream_events_utils::TurnItemContributorPolicy;
|
||||
use crate::stream_events_utils::finalize_turn_item;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::apply_granted_turn_permissions;
|
||||
@@ -87,13 +85,11 @@ impl TurnItemEmitter for CoreTurnItemEmitter {
|
||||
let (Some(session), Some(turn)) = (self.session.upgrade(), self.turn.upgrade()) else {
|
||||
return;
|
||||
};
|
||||
let (item, legacy_events) = match item {
|
||||
ExtensionTurnItem::WebSearch(item) => (TurnItem::WebSearch(item), Vec::new()),
|
||||
ExtensionTurnItem::Extension {
|
||||
item,
|
||||
legacy_events,
|
||||
} => (TurnItem::Extension(item), legacy_events),
|
||||
};
|
||||
let ExtensionTurnItem {
|
||||
item,
|
||||
legacy_events,
|
||||
} = item;
|
||||
let item = TurnItem::Extension(item);
|
||||
session.emit_turn_item_started(turn.as_ref(), &item).await;
|
||||
emit_legacy_events(session.as_ref(), turn.as_ref(), legacy_events).await;
|
||||
})
|
||||
@@ -104,25 +100,11 @@ impl TurnItemEmitter for CoreTurnItemEmitter {
|
||||
let (Some(session), Some(turn)) = (self.session.upgrade(), self.turn.upgrade()) else {
|
||||
return;
|
||||
};
|
||||
let (item, legacy_events) = match item {
|
||||
ExtensionTurnItem::Extension {
|
||||
item,
|
||||
legacy_events,
|
||||
} => (TurnItem::Extension(item), legacy_events),
|
||||
ExtensionTurnItem::WebSearch(item) => {
|
||||
let mut item = TurnItem::WebSearch(item);
|
||||
finalize_turn_item(
|
||||
session.as_ref(),
|
||||
turn.as_ref(),
|
||||
TurnItemContributorPolicy::Run(turn.extension_data.as_ref()),
|
||||
&mut item,
|
||||
turn.collaboration_mode.mode
|
||||
== codex_protocol::config_types::ModeKind::Plan,
|
||||
)
|
||||
.await;
|
||||
(item, Vec::new())
|
||||
}
|
||||
};
|
||||
let ExtensionTurnItem {
|
||||
item,
|
||||
legacy_events,
|
||||
} = item;
|
||||
let item = TurnItem::Extension(item);
|
||||
session.emit_turn_item_completed(turn.as_ref(), item).await;
|
||||
emit_legacy_events(session.as_ref(), turn.as_ref(), legacy_events).await;
|
||||
})
|
||||
@@ -181,11 +163,10 @@ mod tests {
|
||||
|
||||
use codex_extension_items::ExtensionItem;
|
||||
use codex_extension_items::image_generation::ImageGenerationItem;
|
||||
use codex_extension_items::web_search::WebSearchItem;
|
||||
use codex_protocol::items::TurnItem;
|
||||
use codex_protocol::items::WebSearchItem;
|
||||
use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::models::WebSearchAction;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::ImageGenerationBeginEvent;
|
||||
use codex_protocol::protocol::ImageGenerationEndEvent;
|
||||
@@ -274,16 +255,16 @@ mod tests {
|
||||
&self,
|
||||
call: codex_tools::ToolCall,
|
||||
) -> Result<Box<dyn codex_tools::ToolOutput>, codex_tools::FunctionCallError> {
|
||||
let item = ExtensionTurnItem::WebSearch(WebSearchItem {
|
||||
id: call.call_id.clone(),
|
||||
query: "rust trait object".to_string(),
|
||||
action: WebSearchAction::Search {
|
||||
query: Some("rust trait object".to_string()),
|
||||
queries: None,
|
||||
},
|
||||
});
|
||||
call.turn_item_emitter.emit_started(item.clone()).await;
|
||||
call.turn_item_emitter.emit_completed(item).await;
|
||||
call.turn_item_emitter
|
||||
.emit_started(ExtensionTurnItem {
|
||||
item: ExtensionItem::WebSearch(WebSearchItem {
|
||||
id: call.call_id.clone(),
|
||||
query: String::new(),
|
||||
action: None,
|
||||
}),
|
||||
legacy_events: Vec::new(),
|
||||
})
|
||||
.await;
|
||||
*self.captured_call.lock().await = Some(call);
|
||||
Ok(
|
||||
Box::new(codex_tools::JsonToolOutput::new(json!({ "ok": true })))
|
||||
@@ -420,39 +401,17 @@ mod tests {
|
||||
let EventMsg::ItemStarted(started) = started.msg else {
|
||||
panic!("expected item started event");
|
||||
};
|
||||
let TurnItem::WebSearch(started_item) = started.item else {
|
||||
panic!("expected web search item");
|
||||
let TurnItem::Extension(ExtensionItem::WebSearch(started_item)) = started.item else {
|
||||
panic!("expected extension web search item");
|
||||
};
|
||||
let begin = rx.recv().await.expect("legacy web search begin event");
|
||||
let EventMsg::WebSearchBegin(begin) = begin.msg else {
|
||||
panic!("expected legacy web search begin event");
|
||||
};
|
||||
let completed = rx.recv().await.expect("item completed event");
|
||||
let EventMsg::ItemCompleted(completed) = completed.msg else {
|
||||
panic!("expected item completed event");
|
||||
};
|
||||
let TurnItem::WebSearch(completed_item) = completed.item else {
|
||||
panic!("expected web search item");
|
||||
};
|
||||
let end = rx.recv().await.expect("legacy web search end event");
|
||||
let EventMsg::WebSearchEnd(end) = end.msg else {
|
||||
panic!("expected legacy web search end event");
|
||||
};
|
||||
|
||||
let expected = WebSearchItem {
|
||||
id: "call-extension".to_string(),
|
||||
query: "rust trait object".to_string(),
|
||||
action: WebSearchAction::Search {
|
||||
query: Some("rust trait object".to_string()),
|
||||
queries: None,
|
||||
},
|
||||
};
|
||||
assert_eq!(started_item, expected);
|
||||
assert_eq!(completed_item, expected);
|
||||
assert_eq!(begin.call_id, expected.id);
|
||||
assert_eq!(end.call_id, expected.id);
|
||||
assert_eq!(end.query, expected.query);
|
||||
assert_eq!(end.action, expected.action);
|
||||
assert_eq!(
|
||||
started_item,
|
||||
WebSearchItem {
|
||||
id: "call-extension".to_string(),
|
||||
query: String::new(),
|
||||
action: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -484,7 +443,7 @@ mod tests {
|
||||
});
|
||||
codex_tools::TurnItemEmitter::emit_started(
|
||||
&emitter,
|
||||
ExtensionTurnItem::Extension {
|
||||
ExtensionTurnItem {
|
||||
item: expected_started_item.clone(),
|
||||
legacy_events: vec![EventMsg::ImageGenerationBegin(ImageGenerationBeginEvent {
|
||||
call_id: "call-image".to_string(),
|
||||
@@ -494,7 +453,7 @@ mod tests {
|
||||
.await;
|
||||
codex_tools::TurnItemEmitter::emit_completed(
|
||||
&emitter,
|
||||
ExtensionTurnItem::Extension {
|
||||
ExtensionTurnItem {
|
||||
item: expected_completed_item.clone(),
|
||||
legacy_events: vec![EventMsg::ImageGenerationEnd(ImageGenerationEndEvent {
|
||||
call_id: "call-image".to_string(),
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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")),
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -101,7 +101,7 @@ fn legacy_end_event(item: &ImageGenerationItem) -> EventMsg {
|
||||
}
|
||||
|
||||
fn extension_turn_item(item: ImageGenerationItem, legacy_event: EventMsg) -> ExtensionTurnItem {
|
||||
ExtensionTurnItem::Extension {
|
||||
ExtensionTurnItem {
|
||||
item: ExtensionItem::ImageGeneration(item),
|
||||
legacy_events: vec![legacy_event],
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use serde::Serialize;
|
||||
use ts_rs::TS;
|
||||
|
||||
pub mod image_generation;
|
||||
pub mod web_search;
|
||||
|
||||
/// Canonical extension-owned turn item carried through core lifecycle events.
|
||||
///
|
||||
@@ -34,6 +35,9 @@ pub enum ExtensionItem {
|
||||
#[serde(rename = "image_gen.generation")]
|
||||
#[ts(rename = "image_gen.generation")]
|
||||
ImageGeneration(image_generation::ImageGenerationItem),
|
||||
#[serde(rename = "web.search")]
|
||||
#[ts(rename = "web.search")]
|
||||
WebSearch(web_search::WebSearchItem),
|
||||
}
|
||||
|
||||
impl ExtensionItem {
|
||||
@@ -42,6 +46,7 @@ impl ExtensionItem {
|
||||
pub fn id(&self) -> &str {
|
||||
match self {
|
||||
Self::ImageGeneration(item) => &item.id,
|
||||
Self::WebSearch(item) => &item.id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ use serde_json::json;
|
||||
|
||||
use super::ExtensionItem;
|
||||
use super::image_generation::ImageGenerationItem;
|
||||
use super::web_search::WebSearchAction;
|
||||
use super::web_search::WebSearchItem;
|
||||
|
||||
fn completed_image_generation_item() -> ExtensionItem {
|
||||
ExtensionItem::ImageGeneration(ImageGenerationItem {
|
||||
@@ -35,6 +37,37 @@ fn image_generation_item_preserves_stable_wire_shape() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn web_search_item_preserves_stable_wire_shape() {
|
||||
let item = ExtensionItem::WebSearch(WebSearchItem {
|
||||
id: "search-1".to_string(),
|
||||
query: "docs".to_string(),
|
||||
action: Some(WebSearchAction::Search {
|
||||
query: Some("docs".to_string()),
|
||||
queries: None,
|
||||
}),
|
||||
});
|
||||
let value = serde_json::to_value(&item).expect("serialize extension item");
|
||||
|
||||
assert_eq!(
|
||||
value,
|
||||
json!({
|
||||
"kind": "web.search",
|
||||
"id": "search-1",
|
||||
"query": "docs",
|
||||
"action": {
|
||||
"type": "search",
|
||||
"query": "docs",
|
||||
"queries": null,
|
||||
},
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_value::<ExtensionItem>(value).expect("deserialize extension item"),
|
||||
item
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_extension_kind_is_rejected() {
|
||||
let value = json!({
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use ts_rs::TS;
|
||||
|
||||
// Standalone web-search item owned by the web extension. This is also the
|
||||
// field-level representation exposed by app-server; core and rollout
|
||||
// persistence only carry it inside an ExtensionItem envelope.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
pub struct WebSearchItem {
|
||||
pub id: String,
|
||||
pub query: String,
|
||||
pub action: Option<WebSearchAction>,
|
||||
}
|
||||
|
||||
// App-server-facing description of the action performed by standalone web search.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema, PartialEq)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[ts(tag = "type", rename_all = "camelCase")]
|
||||
// Keep app-server's existing v2 TS path. The root WebSearchAction name is
|
||||
// already used by the snake_case Responses API action type.
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum WebSearchAction {
|
||||
Search {
|
||||
query: Option<String>,
|
||||
queries: Option<Vec<String>>,
|
||||
},
|
||||
OpenPage {
|
||||
url: Option<String>,
|
||||
},
|
||||
FindInPage {
|
||||
url: Option<String>,
|
||||
pattern: Option<String>,
|
||||
},
|
||||
#[serde(other)]
|
||||
Other,
|
||||
}
|
||||
@@ -16,6 +16,7 @@ workspace = true
|
||||
codex-api = { workspace = true }
|
||||
codex-core = { workspace = true }
|
||||
codex-extension-api = { workspace = true }
|
||||
codex-extension-items = { workspace = true }
|
||||
codex-login = { workspace = true }
|
||||
codex-model-provider = { workspace = true }
|
||||
codex-model-provider-info = { workspace = true }
|
||||
|
||||
@@ -14,10 +14,15 @@ use codex_extension_api::ToolName;
|
||||
use codex_extension_api::ToolOutput;
|
||||
use codex_extension_api::ToolSpec;
|
||||
use codex_extension_api::parse_tool_input_schema_without_compaction;
|
||||
use codex_extension_items::ExtensionItem;
|
||||
use codex_extension_items::web_search::WebSearchAction;
|
||||
use codex_extension_items::web_search::WebSearchItem;
|
||||
use codex_login::default_client::build_reqwest_client;
|
||||
use codex_model_provider::SharedModelProvider;
|
||||
use codex_protocol::items::WebSearchItem;
|
||||
use codex_protocol::models::WebSearchAction;
|
||||
use codex_protocol::models::WebSearchAction as CoreWebSearchAction;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::WebSearchBeginEvent;
|
||||
use codex_protocol::protocol::WebSearchEndEvent;
|
||||
use codex_tools::ResponsesApiNamespace;
|
||||
use codex_tools::ResponsesApiNamespaceTool;
|
||||
use codex_tools::ToolExposure;
|
||||
@@ -109,14 +114,47 @@ impl WebSearchTool {
|
||||
),
|
||||
};
|
||||
call.turn_item_emitter
|
||||
.emit_started(web_search_item(&call.call_id, WebSearchAction::Other))
|
||||
.emit_started(extension_turn_item(
|
||||
WebSearchItem {
|
||||
id: call.call_id.clone(),
|
||||
query: String::new(),
|
||||
action: None,
|
||||
},
|
||||
EventMsg::WebSearchBegin(WebSearchBeginEvent {
|
||||
call_id: call.call_id.clone(),
|
||||
}),
|
||||
))
|
||||
.await;
|
||||
let response = client
|
||||
.search(&request, HeaderMap::new())
|
||||
.await
|
||||
.map_err(|err| FunctionCallError::Fatal(err.to_string()))?;
|
||||
let legacy_action = match &command_action {
|
||||
WebSearchAction::Search { query, queries } => CoreWebSearchAction::Search {
|
||||
query: query.clone(),
|
||||
queries: queries.clone(),
|
||||
},
|
||||
WebSearchAction::OpenPage { url } => CoreWebSearchAction::OpenPage { url: url.clone() },
|
||||
WebSearchAction::FindInPage { url, pattern } => CoreWebSearchAction::FindInPage {
|
||||
url: url.clone(),
|
||||
pattern: pattern.clone(),
|
||||
},
|
||||
WebSearchAction::Other => CoreWebSearchAction::Other,
|
||||
};
|
||||
let query = web_search_action_detail(&legacy_action);
|
||||
call.turn_item_emitter
|
||||
.emit_completed(web_search_item(&call.call_id, command_action))
|
||||
.emit_completed(extension_turn_item(
|
||||
WebSearchItem {
|
||||
id: call.call_id.clone(),
|
||||
query: query.clone(),
|
||||
action: Some(command_action),
|
||||
},
|
||||
EventMsg::WebSearchEnd(WebSearchEndEvent {
|
||||
call_id: call.call_id.clone(),
|
||||
query,
|
||||
action: legacy_action,
|
||||
}),
|
||||
))
|
||||
.await;
|
||||
|
||||
Ok(Box::new(SearchOutput::new(response.output)))
|
||||
@@ -180,18 +218,17 @@ fn literal_url(ref_id: &str) -> Option<String> {
|
||||
Url::parse(ref_id).is_ok().then(|| ref_id.to_string())
|
||||
}
|
||||
|
||||
fn web_search_item(call_id: &str, action: WebSearchAction) -> ExtensionTurnItem {
|
||||
ExtensionTurnItem::WebSearch(WebSearchItem {
|
||||
id: call_id.to_string(),
|
||||
query: web_search_action_detail(&action),
|
||||
action,
|
||||
})
|
||||
fn extension_turn_item(item: WebSearchItem, legacy_event: EventMsg) -> ExtensionTurnItem {
|
||||
ExtensionTurnItem {
|
||||
item: ExtensionItem::WebSearch(item),
|
||||
legacy_events: vec![legacy_event],
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use codex_api::SearchCommands;
|
||||
use codex_protocol::models::WebSearchAction;
|
||||
use codex_extension_items::web_search::WebSearchAction;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::command_action;
|
||||
|
||||
@@ -47,13 +47,17 @@ pub enum TurnItem {
|
||||
DynamicToolCall(DynamicToolCallItem),
|
||||
CollabAgentToolCall(CollabAgentToolCallItem),
|
||||
SubAgentActivity(SubAgentActivityItem),
|
||||
/// Hosted Responses API web-search item handled directly by core.
|
||||
///
|
||||
/// Standalone web search uses Self::Extension instead because its display
|
||||
/// schema is owned by the web-search extension.
|
||||
WebSearch(WebSearchItem),
|
||||
ImageView(ImageViewItem),
|
||||
Sleep(SleepItem),
|
||||
/// Item whose schema and lifecycle details are owned by an extension.
|
||||
///
|
||||
/// Standalone image generation uses this path. App-server wraps the same
|
||||
/// typed item in its public image-generation variant.
|
||||
/// Standalone image generation and web search use this path. App-server
|
||||
/// wraps the same typed items in their public variants.
|
||||
Extension(ExtensionItem),
|
||||
/// Hosted Responses API image-generation item handled directly by core.
|
||||
///
|
||||
|
||||
@@ -4,7 +4,6 @@ use crate::ToolPayload;
|
||||
use codex_extension_items::ExtensionItem;
|
||||
use codex_file_system::ExecutorFileSystem;
|
||||
use codex_file_system::FileSystemSandboxContext;
|
||||
use codex_protocol::items::WebSearchItem;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
@@ -36,8 +35,7 @@ pub type TurnItemEmissionFuture<'a> = Pin<Box<dyn Future<Output = ()> + Send + '
|
||||
|
||||
/// Visible turn items that an extension may publish into the host lifecycle.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ExtensionTurnItem {
|
||||
WebSearch(WebSearchItem),
|
||||
pub struct ExtensionTurnItem {
|
||||
/// Canonical extension item plus compatibility events derived by its owner.
|
||||
///
|
||||
/// Core intentionally does not inspect extension-owned payloads, so it
|
||||
@@ -45,10 +43,8 @@ pub enum ExtensionTurnItem {
|
||||
/// event first, then these extension-provided events. Core also skips
|
||||
/// global turn-item contributors here so extensions cannot mutate items
|
||||
/// owned by other extensions.
|
||||
Extension {
|
||||
item: ExtensionItem,
|
||||
legacy_events: Vec<EventMsg>,
|
||||
},
|
||||
pub item: ExtensionItem,
|
||||
pub legacy_events: Vec<EventMsg>,
|
||||
}
|
||||
|
||||
/// Host-provided capability for extension tools to emit visible turn items.
|
||||
|
||||
@@ -178,8 +178,8 @@ fn activity_summary(item: &ThreadItem) -> Option<String> {
|
||||
};
|
||||
return bounded_summary(&format!("{action} {agent_path}"));
|
||||
}
|
||||
ThreadItem::WebSearch { query, .. } => {
|
||||
return bounded_summary(&format!("Web search: {query}"));
|
||||
ThreadItem::WebSearch(item) => {
|
||||
return bounded_summary(&format!("Web search: {}", item.query));
|
||||
}
|
||||
ThreadItem::ImageView { path, .. } => {
|
||||
let path = path.render_for_ui();
|
||||
|
||||
@@ -290,8 +290,8 @@ impl ChatWidget {
|
||||
self.on_patch_apply_begin(file_update_changes_to_display(changes));
|
||||
}
|
||||
item @ ThreadItem::McpToolCall { .. } => self.on_mcp_tool_call_started(item),
|
||||
ThreadItem::WebSearch { id, .. } => {
|
||||
self.on_web_search_begin(id);
|
||||
ThreadItem::WebSearch(item) => {
|
||||
self.on_web_search_begin(item.id);
|
||||
}
|
||||
ThreadItem::ImageGeneration(_) => {
|
||||
self.on_image_generation_begin();
|
||||
|
||||
@@ -139,12 +139,13 @@ impl ChatWidget {
|
||||
..
|
||||
} => self.on_mcp_tool_call_started(item),
|
||||
item @ ThreadItem::McpToolCall { .. } => self.on_mcp_tool_call_completed(item),
|
||||
ThreadItem::WebSearch { id, query, action } => {
|
||||
self.on_web_search_begin(id.clone());
|
||||
ThreadItem::WebSearch(item) => {
|
||||
self.on_web_search_begin(item.id.clone());
|
||||
self.on_web_search_end(
|
||||
id,
|
||||
query,
|
||||
action.unwrap_or(codex_app_server_protocol::WebSearchAction::Other),
|
||||
item.id,
|
||||
item.query,
|
||||
item.action
|
||||
.unwrap_or(codex_app_server_protocol::WebSearchAction::Other),
|
||||
);
|
||||
}
|
||||
ThreadItem::ImageView { id: _, path } => {
|
||||
|
||||
@@ -199,8 +199,8 @@ fn fallback_transcript_cell(item: &ThreadItem) -> Option<PlainHistoryCell> {
|
||||
} => {
|
||||
vec![sub_agent_activity_summary(*kind, agent_path).dim().into()]
|
||||
}
|
||||
ThreadItem::WebSearch { query, .. } => {
|
||||
vec![vec!["web search: ".dim(), query.clone().into()].into()]
|
||||
ThreadItem::WebSearch(item) => {
|
||||
vec![vec!["web search: ".dim(), item.query.clone().into()].into()]
|
||||
}
|
||||
ThreadItem::ImageView { path, .. } => {
|
||||
let path = path.render_for_ui();
|
||||
|
||||
Reference in New Issue
Block a user