Move TUI on top of app server (parallel code) (#14717)

This PR replicates the `tui` code directory and creates a temporary
parallel `tui_app_server` directory. It also implements a new feature
flag `tui_app_server` to select between the two tui implementations.

Once the new app-server-based TUI is stabilized, we'll delete the old
`tui` directory and feature flag.
This commit is contained in:
Eric Traut
2026-03-16 10:49:19 -06:00
committed by GitHub
parent c04a0a7454
commit db89b73a9c
1109 changed files with 134253 additions and 17 deletions
@@ -0,0 +1,45 @@
use std::future::Future;
use crate::Cli;
use codex_core::config::Config;
use codex_core::config::ConfigOverrides;
use codex_core::features::Feature;
pub(crate) fn app_server_tui_config_inputs(
cli: &Cli,
) -> std::io::Result<(Vec<(String, toml::Value)>, ConfigOverrides)> {
let mut raw_overrides = cli.config_overrides.raw_overrides.clone();
if cli.web_search {
raw_overrides.push("web_search=\"live\"".to_string());
}
let cli_kv_overrides = codex_utils_cli::CliConfigOverrides { raw_overrides }
.parse_overrides()
.map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidInput, err))?;
let config_overrides = ConfigOverrides {
cwd: cli.cwd.clone(),
config_profile: cli.config_profile.clone(),
..Default::default()
};
Ok((cli_kv_overrides, config_overrides))
}
pub(crate) async fn should_use_app_server_tui_with<F, Fut>(
cli: &Cli,
load_config: F,
) -> std::io::Result<bool>
where
F: FnOnce(Vec<(String, toml::Value)>, ConfigOverrides) -> Fut,
Fut: Future<Output = std::io::Result<Config>>,
{
let (cli_kv_overrides, config_overrides) = app_server_tui_config_inputs(cli)?;
let config = load_config(cli_kv_overrides, config_overrides).await?;
Ok(config.features.enabled(Feature::TuiAppServer))
}
pub async fn should_use_app_server_tui(cli: &Cli) -> std::io::Result<bool> {
should_use_app_server_tui_with(cli, Config::load_with_cli_overrides_and_harness_overrides).await
}
+3
View File
@@ -70,6 +70,7 @@ mod app;
mod app_backtrack;
mod app_event;
mod app_event_sender;
mod app_server_tui_dispatch;
mod ascii_animation;
#[cfg(all(not(target_os = "linux"), feature = "voice-input"))]
mod audio_device;
@@ -229,6 +230,7 @@ pub mod test_backend;
use crate::onboarding::onboarding_screen::OnboardingScreenArgs;
use crate::onboarding::onboarding_screen::run_onboarding_app;
use crate::tui::Tui;
pub use app_server_tui_dispatch::should_use_app_server_tui;
pub use cli::Cli;
use codex_arg0::Arg0DispatchPaths;
pub use markdown_render::render_markdown_text;
@@ -1288,6 +1290,7 @@ mod tests {
use codex_protocol::protocol::SessionMetaLine;
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::TurnContextItem;
use pretty_assertions::assert_eq;
use serial_test::serial;
use tempfile::TempDir;
+81 -6
View File
@@ -1,8 +1,11 @@
use clap::Parser;
use codex_arg0::Arg0DispatchPaths;
use codex_arg0::arg0_dispatch_or_else;
use codex_tui::AppExitInfo;
use codex_tui::Cli;
use codex_tui::ExitReason;
use codex_tui::run_main;
use codex_tui::update_action::UpdateAction;
use codex_utils_cli::CliConfigOverrides;
#[derive(Parser, Debug)]
@@ -14,6 +17,65 @@ struct TopCli {
inner: Cli,
}
fn into_app_server_cli(cli: Cli) -> codex_tui_app_server::Cli {
codex_tui_app_server::Cli {
prompt: cli.prompt,
images: cli.images,
resume_picker: cli.resume_picker,
resume_last: cli.resume_last,
resume_session_id: cli.resume_session_id,
resume_show_all: cli.resume_show_all,
fork_picker: cli.fork_picker,
fork_last: cli.fork_last,
fork_session_id: cli.fork_session_id,
fork_show_all: cli.fork_show_all,
model: cli.model,
oss: cli.oss,
oss_provider: cli.oss_provider,
config_profile: cli.config_profile,
sandbox_mode: cli.sandbox_mode,
approval_policy: cli.approval_policy,
full_auto: cli.full_auto,
dangerously_bypass_approvals_and_sandbox: cli.dangerously_bypass_approvals_and_sandbox,
cwd: cli.cwd,
web_search: cli.web_search,
add_dir: cli.add_dir,
no_alt_screen: cli.no_alt_screen,
config_overrides: cli.config_overrides,
}
}
fn into_legacy_update_action(
action: codex_tui_app_server::update_action::UpdateAction,
) -> UpdateAction {
match action {
codex_tui_app_server::update_action::UpdateAction::NpmGlobalLatest => {
UpdateAction::NpmGlobalLatest
}
codex_tui_app_server::update_action::UpdateAction::BunGlobalLatest => {
UpdateAction::BunGlobalLatest
}
codex_tui_app_server::update_action::UpdateAction::BrewUpgrade => UpdateAction::BrewUpgrade,
}
}
fn into_legacy_exit_reason(reason: codex_tui_app_server::ExitReason) -> ExitReason {
match reason {
codex_tui_app_server::ExitReason::UserRequested => ExitReason::UserRequested,
codex_tui_app_server::ExitReason::Fatal(message) => ExitReason::Fatal(message),
}
}
fn into_legacy_exit_info(exit_info: codex_tui_app_server::AppExitInfo) -> AppExitInfo {
AppExitInfo {
token_usage: exit_info.token_usage,
thread_id: exit_info.thread_id,
thread_name: exit_info.thread_name,
update_action: exit_info.update_action.map(into_legacy_update_action),
exit_reason: into_legacy_exit_reason(exit_info.exit_reason),
}
}
fn main() -> anyhow::Result<()> {
arg0_dispatch_or_else(|arg0_paths: Arg0DispatchPaths| async move {
let top_cli = TopCli::parse();
@@ -22,12 +84,25 @@ fn main() -> anyhow::Result<()> {
.config_overrides
.raw_overrides
.splice(0..0, top_cli.config_overrides.raw_overrides);
let exit_info = run_main(
inner,
arg0_paths,
codex_core::config_loader::LoaderOverrides::default(),
)
.await?;
let use_app_server_tui = codex_tui::should_use_app_server_tui(&inner).await?;
let exit_info = if use_app_server_tui {
into_legacy_exit_info(
codex_tui_app_server::run_main(
into_app_server_cli(inner),
arg0_paths,
codex_core::config_loader::LoaderOverrides::default(),
None,
)
.await?,
)
} else {
run_main(
inner,
arg0_paths,
codex_core::config_loader::LoaderOverrides::default(),
)
.await?
};
let token_usage = exit_info.token_usage;
if !token_usage.is_zero() {
println!(