From e837ce9e02e7dbe88702cdce33d727c089c0d246 Mon Sep 17 00:00:00 2001 From: Andrei Eternal Date: Mon, 16 Mar 2026 17:33:54 -0700 Subject: [PATCH] tui_app_server: handle hook prompt items --- .../app-server/src/bespoke_event_handling.rs | 1 - .../core/src/contextual_user_message_tests.rs | 4 +- codex-rs/protocol/src/items.rs | 11 +- .../src/app/app_server_adapter.rs | 1 + .../tui_app_server/src/app_server_session.rs | 115 ++++++++++++++++++ 5 files changed, 124 insertions(+), 8 deletions(-) diff --git a/codex-rs/app-server/src/bespoke_event_handling.rs b/codex-rs/app-server/src/bespoke_event_handling.rs index ad4cd2125c..339f90d0da 100644 --- a/codex-rs/app-server/src/bespoke_event_handling.rs +++ b/codex-rs/app-server/src/bespoke_event_handling.rs @@ -111,7 +111,6 @@ use codex_protocol::ThreadId; use codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem as CoreDynamicToolCallOutputContentItem; use codex_protocol::dynamic_tools::DynamicToolResponse as CoreDynamicToolResponse; use codex_protocol::items::parse_hook_prompt_message; -use codex_protocol::models::PermissionProfile as CorePermissionProfile; use codex_protocol::plan_tool::UpdatePlanArgs; use codex_protocol::protocol::ApplyPatchApprovalRequestEvent; use codex_protocol::protocol::CodexErrorInfo as CoreCodexErrorInfo; diff --git a/codex-rs/core/src/contextual_user_message_tests.rs b/codex-rs/core/src/contextual_user_message_tests.rs index 6fe2a2df21..7a4d377968 100644 --- a/codex-rs/core/src/contextual_user_message_tests.rs +++ b/codex-rs/core/src/contextual_user_message_tests.rs @@ -85,8 +85,8 @@ fn detects_hook_prompt_fragment_and_roundtrips_escaping() { let ContentItem::InputText { text } = content_item else { panic!("expected input text content item"); }; - let parsed = parse_visible_hook_prompt_message(None, content.as_slice()) - .expect("visible hook prompt"); + let parsed = + parse_visible_hook_prompt_message(None, content.as_slice()).expect("visible hook prompt"); assert_eq!( parsed.fragments, vec![HookPromptFragment { diff --git a/codex-rs/protocol/src/items.rs b/codex-rs/protocol/src/items.rs index 2a8f54aee5..ed1eaa1489 100644 --- a/codex-rs/protocol/src/items.rs +++ b/codex-rs/protocol/src/items.rs @@ -241,10 +241,8 @@ pub fn build_hook_prompt_message(fragments: &[HookPromptFragment]) -> Option>(); @@ -342,7 +340,10 @@ fn parse_hook_prompt_hook_run_ids(open_tag: &str) -> Option> { parse_hook_prompt_attribute(open_tag, HOOK_PROMPT_RUN_IDS_ATTR) { let hook_run_ids = serde_json::from_str::>(&encoded_hook_run_ids).ok()?; - if hook_run_ids.iter().any(|hook_run_id| hook_run_id.trim().is_empty()) { + if hook_run_ids + .iter() + .any(|hook_run_id| hook_run_id.trim().is_empty()) + { return None; } return Some(hook_run_ids); diff --git a/codex-rs/tui_app_server/src/app/app_server_adapter.rs b/codex-rs/tui_app_server/src/app/app_server_adapter.rs index 0fff49fd20..29657bffdb 100644 --- a/codex-rs/tui_app_server/src/app/app_server_adapter.rs +++ b/codex-rs/tui_app_server/src/app/app_server_adapter.rs @@ -615,6 +615,7 @@ fn thread_item_to_core(item: &ThreadItem) -> Option { | ThreadItem::McpToolCall { .. } | ThreadItem::DynamicToolCall { .. } | ThreadItem::CollabAgentToolCall { .. } + | ThreadItem::HookPrompt { .. } | ThreadItem::ImageView { .. } | ThreadItem::EnteredReviewMode { .. } | ThreadItem::ExitedReviewMode { .. } => { diff --git a/codex-rs/tui_app_server/src/app_server_session.rs b/codex-rs/tui_app_server/src/app_server_session.rs index 514005193b..daf4ef5926 100644 --- a/codex-rs/tui_app_server/src/app_server_session.rs +++ b/codex-rs/tui_app_server/src/app_server_session.rs @@ -987,6 +987,121 @@ fn session_configured_from_thread_response( }) } +fn thread_initial_messages( + thread_id: &ThreadId, + turns: &[codex_app_server_protocol::Turn], + show_raw_agent_reasoning: bool, +) -> Option> { + let events: Vec = turns + .iter() + .flat_map(|turn| turn_initial_messages(thread_id, turn, show_raw_agent_reasoning)) + .collect(); + (!events.is_empty()).then_some(events) +} + +fn turn_initial_messages( + thread_id: &ThreadId, + turn: &codex_app_server_protocol::Turn, + show_raw_agent_reasoning: bool, +) -> Vec { + turn.items + .iter() + .cloned() + .filter_map(app_server_thread_item_to_core) + .flat_map(|item| match item { + TurnItem::UserMessage(item) => vec![item.as_legacy_event()], + TurnItem::Plan(item) => vec![EventMsg::ItemCompleted(ItemCompletedEvent { + thread_id: *thread_id, + turn_id: turn.id.clone(), + item: TurnItem::Plan(item), + })], + item => item.as_legacy_events(show_raw_agent_reasoning), + }) + .collect() +} + +fn app_server_thread_item_to_core(item: codex_app_server_protocol::ThreadItem) -> Option { + match item { + codex_app_server_protocol::ThreadItem::UserMessage { id, content } => { + Some(TurnItem::UserMessage(UserMessageItem { + id, + content: content + .into_iter() + .map(codex_app_server_protocol::UserInput::into_core) + .collect(), + })) + } + codex_app_server_protocol::ThreadItem::AgentMessage { id, text, phase } => { + Some(TurnItem::AgentMessage(AgentMessageItem { + id, + content: vec![AgentMessageContent::Text { text }], + phase, + })) + } + codex_app_server_protocol::ThreadItem::Plan { id, text } => { + Some(TurnItem::Plan(PlanItem { id, text })) + } + codex_app_server_protocol::ThreadItem::Reasoning { + id, + summary, + content, + } => Some(TurnItem::Reasoning(ReasoningItem { + id, + summary_text: summary, + raw_content: content, + })), + codex_app_server_protocol::ThreadItem::WebSearch { id, query, action } => { + Some(TurnItem::WebSearch(WebSearchItem { + id, + query, + action: app_server_web_search_action_to_core(action?)?, + })) + } + codex_app_server_protocol::ThreadItem::ImageGeneration { + id, + status, + revised_prompt, + result, + } => Some(TurnItem::ImageGeneration(ImageGenerationItem { + id, + status, + revised_prompt, + result, + saved_path: None, + })), + codex_app_server_protocol::ThreadItem::ContextCompaction { id } => { + Some(TurnItem::ContextCompaction(ContextCompactionItem { id })) + } + codex_app_server_protocol::ThreadItem::CommandExecution { .. } + | codex_app_server_protocol::ThreadItem::FileChange { .. } + | codex_app_server_protocol::ThreadItem::McpToolCall { .. } + | codex_app_server_protocol::ThreadItem::DynamicToolCall { .. } + | codex_app_server_protocol::ThreadItem::CollabAgentToolCall { .. } + | codex_app_server_protocol::ThreadItem::HookPrompt { .. } + | codex_app_server_protocol::ThreadItem::ImageView { .. } + | codex_app_server_protocol::ThreadItem::EnteredReviewMode { .. } + | codex_app_server_protocol::ThreadItem::ExitedReviewMode { .. } => None, + } +} + +fn app_server_web_search_action_to_core( + action: codex_app_server_protocol::WebSearchAction, +) -> Option { + match action { + codex_app_server_protocol::WebSearchAction::Search { query, queries } => { + Some(codex_protocol::models::WebSearchAction::Search { query, queries }) + } + codex_app_server_protocol::WebSearchAction::OpenPage { url } => { + Some(codex_protocol::models::WebSearchAction::OpenPage { url }) + } + codex_app_server_protocol::WebSearchAction::FindInPage { url, pattern } => { + Some(codex_protocol::models::WebSearchAction::FindInPage { url, pattern }) + } + codex_app_server_protocol::WebSearchAction::Other => { + Some(codex_protocol::models::WebSearchAction::Other) + } + } +} fn app_server_rate_limit_snapshots_to_core( response: GetAccountRateLimitsResponse, ) -> Vec {