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: <name>` and `provider: <id>`, 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
This commit is contained in:
Eric Traut
2026-10-27 17:08:08 +00:00
committed by copyberry
parent 77f0cbfdd5
commit 4c54667b40
7 changed files with 68 additions and 12 deletions
@@ -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,
@@ -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
}
@@ -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: <name>",
StatusSurfacePreviewItem::Provider => "provider: <id>",
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()
@@ -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: <name> · provider: <id>
space toggle · ←/→ reorder · enter save · esc cancel
@@ -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)
}
@@ -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(
@@ -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!(