Remove core protocol dependency [1/2] (#20324)

## Why

This stack moves `codex-tui` away from the core protocol event surface
and toward app-server API shapes plus TUI-owned local models. This first
PR sets up the lower-risk foundation: it introduces the local model
surface and extracts app-server event routing into focused TUI modules
while preserving the existing behavior for the larger migration in PR2.

This PR is part 1 of a 2-PR stack:

1. Add TUI-owned replacement models and extract app-server event
routing.
2. Move the active TUI flow to app-server notifications and delete
obsolete adapter code.

## What changed

- Added TUI-owned approval, diff, session state, session resume, token
usage, and user-message models.
- Added `app/app_server_event_targets.rs` and `app/app_server_events.rs`
to hold app-server event targeting and dispatch logic outside `app.rs`.
- Updated app/status tests to use the local model layer and added
focused routing coverage.
- Boxed a few large async TUI test futures so this base layer remains
checkable without overflowing the default test stack.

## Verification

- `cargo check -p codex-tui --tests`
This commit is contained in:
Eric Traut
2026-04-30 10:52:19 -07:00
committed by GitHub
Unverified
parent 487716ae74
commit c70cdc108f
11 changed files with 1294 additions and 132 deletions
@@ -0,0 +1,107 @@
//! User-message display models and helpers for the chat widget.
//!
//! The app-server preserves user input as structured chunks, while chat history
//! renders a single prompt row. This module owns that display projection and
//! the small compare key used to suppress duplicate rows for pending steers.
use std::path::PathBuf;
use codex_app_server_protocol::UserInput;
use codex_protocol::user_input::TextElement;
use super::ChatWidget;
use super::append_text_with_rebased_elements;
#[derive(Clone, Debug, PartialEq)]
pub(super) struct UserMessageDisplay {
pub(super) message: String,
pub(super) remote_image_urls: Vec<String>,
pub(super) local_images: Vec<PathBuf>,
pub(super) text_elements: Vec<TextElement>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(super) struct PendingSteerCompareKey {
pub(super) message: String,
pub(super) image_count: usize,
}
impl ChatWidget {
pub(super) fn user_message_display_from_parts(
message: String,
text_elements: Vec<TextElement>,
local_images: Vec<PathBuf>,
remote_image_urls: Vec<String>,
) -> UserMessageDisplay {
UserMessageDisplay {
message,
remote_image_urls,
local_images,
text_elements,
}
}
/// Build the compare key for a submitted pending steer without invoking the
/// expensive request-serialization path. Pending steers only need to match the
/// committed app-server `UserMessage` item emitted after input drains, which
/// preserves flattened text and total image count.
pub(super) fn pending_steer_compare_key_from_items(
items: &[UserInput],
) -> PendingSteerCompareKey {
let mut message = String::new();
let mut image_count = 0;
for item in items {
match item {
UserInput::Text { text, .. } => message.push_str(text),
UserInput::Image { .. } | UserInput::LocalImage { .. } => image_count += 1,
UserInput::Skill { .. } | UserInput::Mention { .. } => {}
}
}
PendingSteerCompareKey {
message,
image_count,
}
}
pub(super) fn user_message_display_from_inputs(items: &[UserInput]) -> UserMessageDisplay {
let mut message = String::new();
let mut remote_image_urls = Vec::new();
let mut local_images = Vec::new();
let mut text_elements = Vec::new();
for item in items {
match item {
UserInput::Text {
text,
text_elements: current_text_elements,
} => append_text_with_rebased_elements(
&mut message,
&mut text_elements,
text,
current_text_elements.iter().map(|element| {
let range = element.byte_range.clone();
TextElement::new(
range.clone().into(),
element
.placeholder()
.or_else(|| text.get(range.start..range.end))
.map(str::to_string),
)
}),
),
UserInput::Image { url } => remote_image_urls.push(url.clone()),
UserInput::LocalImage { path } => local_images.push(path.clone()),
UserInput::Skill { .. } | UserInput::Mention { .. } => {}
}
}
Self::user_message_display_from_parts(
message,
text_elements,
local_images,
remote_image_urls,
)
}
}