tui_app_server: handle hook prompt items

This commit is contained in:
Andrei Eternal
2026-03-16 17:33:54 -07:00
parent 9ad2b001f0
commit e837ce9e02
5 changed files with 124 additions and 8 deletions

View File

@@ -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;

View File

@@ -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 {

View File

@@ -241,10 +241,8 @@ pub fn build_hook_prompt_message(fragments: &[HookPromptFragment]) -> Option<Res
let content = fragments
.iter()
.filter(|fragment| !fragment.hook_run_ids.is_empty())
.map(|fragment| {
ContentItem::InputText {
text: serialize_hook_prompt_fragment(&fragment.text, &fragment.hook_run_ids),
}
.map(|fragment| ContentItem::InputText {
text: serialize_hook_prompt_fragment(&fragment.text, &fragment.hook_run_ids),
})
.collect::<Vec<_>>();
@@ -342,7 +340,10 @@ fn parse_hook_prompt_hook_run_ids(open_tag: &str) -> Option<Vec<String>> {
parse_hook_prompt_attribute(open_tag, HOOK_PROMPT_RUN_IDS_ATTR)
{
let hook_run_ids = serde_json::from_str::<Vec<String>>(&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);

View File

@@ -615,6 +615,7 @@ fn thread_item_to_core(item: &ThreadItem) -> Option<TurnItem> {
| ThreadItem::McpToolCall { .. }
| ThreadItem::DynamicToolCall { .. }
| ThreadItem::CollabAgentToolCall { .. }
| ThreadItem::HookPrompt { .. }
| ThreadItem::ImageView { .. }
| ThreadItem::EnteredReviewMode { .. }
| ThreadItem::ExitedReviewMode { .. } => {

View File

@@ -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<Vec<EventMsg>> {
let events: Vec<EventMsg> = 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<EventMsg> {
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<TurnItem> {
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<codex_protocol::models::WebSearchAction> {
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<RateLimitSnapshot> {