From 4c54667b408ea7c5d262f1a4171ca414a8f5be88 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Tue, 27 Oct 2026 16:54:35 +0000 Subject: [PATCH] Add profile and provider status line items (#57516) ## What changed Add selectable `profile` and `provider` items to the TUI status line, including setup previews and theme colors. Render them as `profile: ` and `provider: `, omitting the profile when unset and the provider until a thread is active. Accept `model-provider` as an alias for `provider`. ## Testing Extend status line tests to cover omitted values, the active thread's provider, provider updates, and a selected profile. Update the setup popup snapshot to include both items. GitOrigin-RevId: e7cbb7fb1e7017cfbd6ee71e1df7d0ddb1836ed4 --- .../tui/src/bottom_pane/status_line_setup.rs | 16 ++++++++++++++- .../tui/src/bottom_pane/status_line_style.rs | 8 +++++--- .../src/bottom_pane/status_surface_preview.rs | 6 ++++++ ...tatus_line_setup_popup_hardcoded_only.snap | 16 +++++++-------- .../tui/src/chatwidget/status_surfaces.rs | 12 +++++++++++ .../tui/src/chatwidget/tests/app_server.rs | 20 +++++++++++++++++++ .../tests/status_surface_previews.rs | 2 ++ 7 files changed, 68 insertions(+), 12 deletions(-) diff --git a/codex-rs/tui/src/bottom_pane/status_line_setup.rs b/codex-rs/tui/src/bottom_pane/status_line_setup.rs index 51bf78d468..c4ca02c6dd 100644 --- a/codex-rs/tui/src/bottom_pane/status_line_setup.rs +++ b/codex-rs/tui/src/bottom_pane/status_line_setup.rs @@ -9,7 +9,8 @@ //! //! # Available Status Line Items //! -//! - Model information (name, reasoning level) +//! - Model information (name, reasoning level, provider) +//! - Named configuration profile //! - Directory paths (current dir, project root) //! - Machine hostname //! - Git information (branch name) @@ -158,6 +159,13 @@ pub(crate) enum StatusLineItem { /// Latest checklist task progress from `update_plan` (if available). TaskProgress, + + /// Named config profile selected for this invocation. + Profile, + + /// Model provider ID for the active thread. + #[strum(to_string = "provider", serialize = "model-provider")] + Provider, } impl StatusLineItem { @@ -167,6 +175,10 @@ impl StatusLineItem { StatusLineItem::ModelName => "Current model name", StatusLineItem::ModelWithReasoning => "Current model name with reasoning level", StatusLineItem::Reasoning => "Current reasoning level", + StatusLineItem::Profile => { + "Config profile selected with --profile (omitted when unset)" + } + StatusLineItem::Provider => "Model provider for the active thread", StatusLineItem::CurrentDir => "Current working directory", StatusLineItem::ProjectRoot => "Project name (omitted when unavailable)", StatusLineItem::Hostname => "Current machine hostname (omitted when unavailable)", @@ -227,6 +239,8 @@ impl StatusLineItem { StatusLineItem::ModelName => StatusSurfacePreviewItem::Model, StatusLineItem::ModelWithReasoning => StatusSurfacePreviewItem::ModelWithReasoning, StatusLineItem::Reasoning => StatusSurfacePreviewItem::Reasoning, + StatusLineItem::Profile => StatusSurfacePreviewItem::Profile, + StatusLineItem::Provider => StatusSurfacePreviewItem::Provider, StatusLineItem::CurrentDir => StatusSurfacePreviewItem::CurrentDir, StatusLineItem::ProjectRoot => StatusSurfacePreviewItem::ProjectRoot, StatusLineItem::Hostname => StatusSurfacePreviewItem::Hostname, diff --git a/codex-rs/tui/src/bottom_pane/status_line_style.rs b/codex-rs/tui/src/bottom_pane/status_line_style.rs index 6ef9003c58..6041a24105 100644 --- a/codex-rs/tui/src/bottom_pane/status_line_style.rs +++ b/codex-rs/tui/src/bottom_pane/status_line_style.rs @@ -38,6 +38,7 @@ impl StatusLineAccent { match item { StatusLineItem::ModelName | StatusLineItem::ModelWithReasoning + | StatusLineItem::Provider | StatusLineItem::Reasoning => Self::Model, StatusLineItem::CurrentDir | StatusLineItem::ProjectRoot => Self::Path, StatusLineItem::GitBranch @@ -53,9 +54,10 @@ impl StatusLineAccent { | StatusLineItem::ThreadCredits | StatusLineItem::EstimatedThreadCost => Self::Usage, StatusLineItem::FiveHourLimit | StatusLineItem::WeeklyLimit => Self::Limit, - StatusLineItem::CodexVersion | StatusLineItem::Hostname | StatusLineItem::SessionId => { - Self::Metadata - } + StatusLineItem::CodexVersion + | StatusLineItem::Hostname + | StatusLineItem::SessionId + | StatusLineItem::Profile => Self::Metadata, StatusLineItem::FastMode | StatusLineItem::Daybreak | StatusLineItem::RawOutput => { Self::Mode } diff --git a/codex-rs/tui/src/bottom_pane/status_surface_preview.rs b/codex-rs/tui/src/bottom_pane/status_surface_preview.rs index 5dc02dfd86..d940ecb404 100644 --- a/codex-rs/tui/src/bottom_pane/status_surface_preview.rs +++ b/codex-rs/tui/src/bottom_pane/status_surface_preview.rs @@ -40,6 +40,8 @@ pub(crate) enum StatusSurfacePreviewItem { Model, ModelWithReasoning, Reasoning, + Profile, + Provider, TaskProgress, } @@ -78,6 +80,8 @@ impl StatusSurfacePreviewItem { StatusSurfacePreviewItem::Model => "gpt-5.2-codex", StatusSurfacePreviewItem::ModelWithReasoning => "gpt-5.2-codex medium", StatusSurfacePreviewItem::Reasoning => "medium", + StatusSurfacePreviewItem::Profile => "profile: ", + StatusSurfacePreviewItem::Provider => "provider: ", StatusSurfacePreviewItem::TaskProgress => "Tasks 0/0", } } @@ -116,6 +120,8 @@ impl StatusSurfacePreviewItem { Self::Model, Self::ModelWithReasoning, Self::Reasoning, + Self::Profile, + Self::Provider, Self::TaskProgress, ] .into_iter() diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_line_setup_popup_hardcoded_only.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_line_setup_popup_hardcoded_only.snap index 770ed596bf..7848ac4868 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_line_setup_popup_hardcoded_only.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_line_setup_popup_hardcoded_only.snap @@ -7,14 +7,14 @@ expression: status_line_popup_snapshot(&mut chat) Type to search -› [x] Use theme colors Apply colors from the active /theme +› [x] Use theme colors Apply colors from the active /theme ─────────────────────── - [x] project-name Project name (omitted when unavailable) - [x] git-branch Current Git branch (omitted when unavailable) - [x] thread-title Current thread title, or thread identifier when unnamed - [ ] model Current model name - [ ] model-with-reasoning Current model name with reasoning level - [ ] reasoning Current reasoning level + [x] project-name Project name (omitted when unavailable) + [x] git-branch Current Git branch (omitted when unavailable) + [x] thread-title Current thread title, or thread identifier when unnamed + [x] profile Config profile selected with --profile (omitted when unset) + [x] provider Model provider for the active thread + [ ] model Current model name ↓ - my-project · feat/awesome-feature · thread title + my-project · feat/awesome-feature · thread title · profile: · provider: space toggle · ←/→ reorder · enter save · esc cancel diff --git a/codex-rs/tui/src/chatwidget/status_surfaces.rs b/codex-rs/tui/src/chatwidget/status_surfaces.rs index 86c54919fe..997c3a2be6 100644 --- a/codex-rs/tui/src/chatwidget/status_surfaces.rs +++ b/codex-rs/tui/src/chatwidget/status_surfaces.rs @@ -731,6 +731,16 @@ impl ChatWidget { StatusLineItem::ModelName => Some(self.model_display_name().to_string()), StatusLineItem::ModelWithReasoning => Some(self.model_with_reasoning_display_name()), StatusLineItem::Reasoning => Some(self.reasoning_display_name()), + StatusLineItem::Profile => { + let layer = self.config.config_layer_stack.get_active_user_layer()?; + let ConfigLayerSource::User { profile, .. } = &layer.name else { + return None; + }; + profile.as_ref().map(|name| format!("profile: {name}")) + } + StatusLineItem::Provider => self + .thread_id + .map(|_| format!("provider: {}", self.config.model_provider_id)), StatusLineItem::CurrentDir => { Some(format_directory_display( self.status_line_cwd(), @@ -887,6 +897,8 @@ impl ChatWidget { StatusSurfacePreviewItem::Model => StatusLineItem::ModelName, StatusSurfacePreviewItem::ModelWithReasoning => StatusLineItem::ModelWithReasoning, StatusSurfacePreviewItem::Reasoning => StatusLineItem::Reasoning, + StatusSurfacePreviewItem::Profile => StatusLineItem::Profile, + StatusSurfacePreviewItem::Provider => StatusLineItem::Provider, }; self.status_line_value_for_item(status_line_item) } diff --git a/codex-rs/tui/src/chatwidget/tests/app_server.rs b/codex-rs/tui/src/chatwidget/tests/app_server.rs index 25fe42e718..c6dc2d4c6b 100644 --- a/codex-rs/tui/src/chatwidget/tests/app_server.rs +++ b/codex-rs/tui/src/chatwidget/tests/app_server.rs @@ -107,6 +107,9 @@ async fn session_header_uses_catalog_display_name_without_changing_model() { async fn session_and_settings_sync_server_provider_id() { let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.2")).await; chat.config.model_provider.base_url = Some("https://local-provider.example/v1".to_string()); + chat.local_settings.tui.status_line = Some(vec!["profile".into(), "provider".into()]); + chat.refresh_status_line(); + assert_eq!(status_line_text(&chat), None); let status = |chat: &mut ChatWidget| { chat.add_status_output( /*refreshing_rate_limits*/ false, /*request_id*/ None, @@ -138,6 +141,10 @@ async fn session_and_settings_sync_server_provider_id() { assert!(displayed.contains("Model provider:") && displayed.contains("server-ollama")); assert!(!displayed.contains("local-provider.example")); assert!(!displayed.contains("ChatGPT workspace:")); + assert_eq!( + status_line_text(&chat).as_deref(), + Some("provider: server-ollama") + ); let mut other = configured_thread_session(ThreadId::new()); other.model_provider_id = "server-bedrock".to_string(); @@ -149,6 +156,15 @@ async fn session_and_settings_sync_server_provider_id() { chat.handle_thread_session_quiet(first); assert_eq!(chat.config.model_provider_id, "server-ollama"); + chat.config.config_layer_stack = chat + .config + .config_layer_stack + .with_user_config_profile( + &chat.config.codex_home.join("work.config.toml"), + Some(&"work".parse().unwrap()), + toml::Table::new().into(), + ) + .unwrap(); let mut updated = thread_settings_for_test("gpt-5.4", first_id); updated.thread_settings.model_provider = "server-updated".to_string(); chat.handle_server_notification( @@ -156,6 +172,10 @@ async fn session_and_settings_sync_server_provider_id() { /*replay_kind*/ None, ); assert!(status(&mut chat).contains("server-updated")); + insta::assert_snapshot!( + status_line_text(&chat).unwrap(), + @"profile: work · provider: server-updated" + ); } fn start_safety_buffering_test_turn( diff --git a/codex-rs/tui/src/chatwidget/tests/status_surface_previews.rs b/codex-rs/tui/src/chatwidget/tests/status_surface_previews.rs index a05f180af2..81d948749a 100644 --- a/codex-rs/tui/src/chatwidget/tests/status_surface_previews.rs +++ b/codex-rs/tui/src/chatwidget/tests/status_surface_previews.rs @@ -219,6 +219,8 @@ async fn status_line_setup_popup_hardcoded_only_snapshot() { "project-name".to_string(), "git-branch".to_string(), "thread-title".to_string(), + "profile".to_string(), + "provider".to_string(), ]); assert_chatwidget_snapshot!(