use crate::history_cell::CompositeHistoryCell;use crate::history_cell::HistoryCell;use crate::history_cell::PlainHistoryCell;use crate::history_cell::plain_lines;use crate::history_cell::with_border_with_inner_width;use crate::legacy_core::config::Config;use crate::token_usage::TokenUsage;use crate::token_usage::TokenUsageInfo;use crate::version::CODEX_CLI_VERSION;use chrono::DateTime;use chrono::Local;use codex_app_server_protocol::AskForApproval;use codex_model_provider_info::WireApi;use codex_protocol::ThreadId;use codex_protocol::account::PlanType;use codex_protocol::config_types::ApprovalsReviewer;use codex_protocol::models::ActivePermissionProfile;use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS;use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_READ_ONLY;use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_WORKSPACE;use codex_protocol::models::PermissionProfile;use codex_protocol::openai_models::ReasoningEffort;use codex_utils_absolute_path::AbsolutePathBuf;use codex_utils_sandbox_summary::summarize_permission_profile;use ratatui::prelude::*;use ratatui::style::Stylize;use std::collections::BTreeSet;use std::path::PathBuf;use unicode_width::UnicodeWidthStr;use url::Url;use super::account::StatusAccountDisplay;use super::format::FieldFormatter;use super::format::line_display_width;use super::format::push_label;use super::format::truncate_line_to_width;use super::helpers::compose_account_display;use super::helpers::compose_model_display;use super::helpers::format_directory_display;use super::helpers::format_tokens_compact;use super::rate_limits::RateLimitSnapshotDisplay;use super::rate_limits::StatusRateLimitData;use super::rate_limits::StatusRateLimitRow;use super::rate_limits::StatusRateLimitValue;use super::rate_limits::compose_rate_limit_data;use super::rate_limits::compose_rate_limit_data_many;use super::rate_limits::format_status_limit_summary;use super::rate_limits::render_status_limit_progress_bar;use super::remote_connection::RemoteConnectionStatus;use crate::wrapping::RtOptions;use crate::wrapping::adaptive_wrap_lines;use crate::wrapping::word_wrap_lines;use std::sync::Arc;use std::sync::RwLock;const CHATGPT_USAGE_URL: &str = "https://chatgpt.com/codex/settings/usage";#[derive(Debug, Clone)]struct StatusContextWindowData { percent_remaining: i64, tokens_in_context: i64, window: i64,}#[derive(Debug, Clone)]pub(crate) struct StatusTokenUsageData { total: i64, input: i64, output: i64, context_window: Option<StatusContextWindowData>,}#[derive(Debug)]struct StatusRateLimitState { rate_limits: StatusRateLimitData, refreshing_rate_limits: bool,}#[derive(Debug, Clone)]pub(crate) struct StatusHistoryHandle { rate_limit_state: Arc<RwLock<StatusRateLimitState>>,}impl StatusHistoryHandle { pub(crate) fn finish_rate_limit_refresh( &self, rate_limits: &[RateLimitSnapshotDisplay], now: DateTime<Local>, ) { let rate_limits = if rate_limits.len() <= 1 { compose_rate_limit_data(rate_limits.first(), now) } else { compose_rate_limit_data_many(rate_limits, now) }; #[expect(clippy::expect_used)] let mut state = self .rate_limit_state .write() .expect("status history rate-limit state poisoned"); state.rate_limits = rate_limits; state.refreshing_rate_limits = false; }}#[derive(Debug)]struct StatusHistoryCell { model_name: String, model_details: Vec<String>, directory: PathBuf, permissions: String, agents_summary: Arc<RwLock<String>>, collaboration_mode: Option<String>, model_provider: Option<String>, remote_connection: Option<RemoteConnectionStatus>, show_chatgpt_usage_link: bool, account: Option<StatusAccountDisplay>, thread_name: Option<String>, session_id: Option<String>, forked_from: Option<String>, token_usage: StatusTokenUsageData, rate_limit_state: Arc<RwLock<StatusRateLimitState>>,}#[cfg(test)]#[allow(clippy::too_many_arguments)]pub(crate) fn new_status_output( config: &Config, account_display: Option<&StatusAccountDisplay>, token_info: Option<&TokenUsageInfo>, total_usage: &TokenUsage, session_id: &Option<ThreadId>, thread_name: Option<String>, forked_from: Option<ThreadId>, rate_limits: Option<&RateLimitSnapshotDisplay>, _plan_type: Option<PlanType>, now: DateTime<Local>, model_name: &str, collaboration_mode: Option<&str>, reasoning_effort_override: Option<Option<ReasoningEffort>>,) -> CompositeHistoryCell { let snapshots = rate_limits.map(std::slice::from_ref).unwrap_or_default(); new_status_output_with_rate_limits( config, account_display, token_info, total_usage, session_id, thread_name, forked_from, snapshots, _plan_type, now, model_name, collaboration_mode, reasoning_effort_override, /*refreshing_rate_limits*/ false, )}#[cfg(test)]#[allow(clippy::too_many_arguments)]pub(crate) fn new_status_output_with_rate_limits( config: &Config, account_display: Option<&StatusAccountDisplay>, token_info: Option<&TokenUsageInfo>, total_usage: &TokenUsage, session_id: &Option<ThreadId>, thread_name: Option<String>, forked_from: Option<ThreadId>, rate_limits: &[RateLimitSnapshotDisplay], _plan_type: Option<PlanType>, now: DateTime<Local>, model_name: &str, collaboration_mode: Option<&str>, reasoning_effort_override: Option<Option<ReasoningEffort>>, refreshing_rate_limits: bool,) -> CompositeHistoryCell { new_status_output_with_rate_limits_handle( config, /*runtime_model_provider_base_url*/ None, /*remote_connection*/ None, account_display, token_info, total_usage, session_id, thread_name, forked_from, rate_limits, _plan_type, now, model_name, collaboration_mode, reasoning_effort_override, "<none>".to_string(), refreshing_rate_limits, ) .0}#[allow(clippy::too_many_arguments)]pub(crate) fn new_status_output_with_rate_limits_handle( config: &Config, runtime_model_provider_base_url: Option<&str>, remote_connection: Option<&RemoteConnectionStatus>, account_display: Option<&StatusAccountDisplay>, token_info: Option<&TokenUsageInfo>, total_usage: &TokenUsage, session_id: &Option<ThreadId>, thread_name: Option<String>, forked_from: Option<ThreadId>, rate_limits: &[RateLimitSnapshotDisplay], _plan_type: Option<PlanType>, now: DateTime<Local>, model_name: &str, collaboration_mode: Option<&str>, reasoning_effort_override: Option<Option<ReasoningEffort>>, agents_summary: String, refreshing_rate_limits: bool,) -> (CompositeHistoryCell, StatusHistoryHandle) { let command = PlainHistoryCell::new(vec!["/status".magenta().into()]); let (card, handle) = StatusHistoryCell::new( config, runtime_model_provider_base_url, remote_connection, account_display, token_info, total_usage, session_id, thread_name, forked_from, rate_limits, _plan_type, now, model_name, collaboration_mode, reasoning_effort_override, agents_summary, refreshing_rate_limits, ); ( CompositeHistoryCell::new(vec![Box::new(command), Box::new(card)]), handle, )}impl StatusHistoryCell { #[allow(clippy::too_many_arguments)] fn new( config: &Config, runtime_model_provider_base_url: Option<&str>, remote_connection: Option<&RemoteConnectionStatus>, account_display: Option<&StatusAccountDisplay>, token_info: Option<&TokenUsageInfo>, total_usage: &TokenUsage, session_id: &Option<ThreadId>, thread_name: Option<String>, forked_from: Option<ThreadId>, rate_limits: &[RateLimitSnapshotDisplay], _plan_type: Option<PlanType>, now: DateTime<Local>, model_name: &str, collaboration_mode: Option<&str>, reasoning_effort_override: Option<Option<ReasoningEffort>>, agents_summary: String, refreshing_rate_limits: bool, ) -> (Self, StatusHistoryHandle) { let approval_policy = AskForApproval::from(config.permissions.approval_policy.value()); let permission_profile = config.permissions.effective_permission_profile(); let workspace_roots = config.effective_workspace_roots(); let mut config_entries = vec![ ("workdir", config.cwd.display().to_string()), ("model", model_name.to_string()), ("provider", config.model_provider_id.clone()), ( "approval", config.permissions.approval_policy.value().to_string(), ), ( "sandbox", summarize_permission_profile( &permission_profile, &config.cwd, workspace_roots.as_slice(), ), ), ]; if config.model_provider.wire_api == WireApi::Responses { let effort_value = reasoning_effort_override .unwrap_or_else(|| config.model_reasoning_effort.clone()) .map(|effort| effort.to_string()) .unwrap_or_else(|| "none".to_string()); config_entries.push(("reasoning effort", effort_value)); config_entries.push(( "reasoning summaries", config .model_reasoning_summary .map(|summary| summary.to_string()) .unwrap_or_else(|| "auto".to_string()), )); } let (model_name, model_details) = compose_model_display(model_name, &config_entries); let approval = config_entries .iter() .find(|(k, _)| *k == "approval") .map(|(_, v)| v.clone()) .unwrap_or_else(|| "<unknown>".to_string()); let active_permission_profile = config.permissions.active_permission_profile(); let sandbox = status_permission_summary(&permission_profile, &config.cwd, workspace_roots.as_slice()); let workspace_root_suffix = workspace_root_suffix(workspace_roots.as_slice(), &config.cwd); let approval = status_approval_label(approval_policy, config.approvals_reviewer, &approval); let permissions = status_permissions_label( active_permission_profile.as_ref(), &permission_profile, approval_policy, &sandbox, &approval, workspace_root_suffix.as_deref(), ); let model_provider = format_model_provider(config, runtime_model_provider_base_url); let show_chatgpt_usage_link = config.model_provider.requires_openai_auth; let account = compose_account_display(account_display); let session_id = session_id.as_ref().map(std::string::ToString::to_string); let forked_from = forked_from.map(|id| id.to_string()); let default_usage = TokenUsage::default(); let (context_usage, context_window) = match token_info { Some(info) => (&info.last_token_usage, info.model_context_window), None => (&default_usage, config.model_context_window), }; let context_window = context_window.map(|window| StatusContextWindowData { percent_remaining: context_usage.percent_of_context_window_remaining(window), tokens_in_context: context_usage.tokens_in_context_window(), window, }); let token_usage = StatusTokenUsageData { total: total_usage.blended_total(), input: total_usage.non_cached_input(), output: total_usage.output_tokens, context_window, }; let rate_limits = if rate_limits.len() <= 1 { compose_rate_limit_data(rate_limits.first(), now) } else { compose_rate_limit_data_many(rate_limits, now) }; let rate_limit_state = Arc::new(RwLock::new(StatusRateLimitState { rate_limits, refreshing_rate_limits, })); let agents_summary = Arc::new(RwLock::new(agents_summary)); ( Self { model_name, model_details, directory: config.cwd.to_path_buf(), permissions, collaboration_mode: collaboration_mode.map(ToString::to_string), model_provider, remote_connection: remote_connection.cloned(), show_chatgpt_usage_link, account, thread_name, session_id, forked_from, token_usage, agents_summary, rate_limit_state: rate_limit_state.clone(), }, StatusHistoryHandle { rate_limit_state }, ) } fn token_usage_spans(&self) -> Vec<Span<'static>> { let total_fmt = format_tokens_compact(self.token_usage.total); let input_fmt = format_tokens_compact(self.token_usage.input); let output_fmt = format_tokens_compact(self.token_usage.output); vec![ Span::from(total_fmt), Span::from(" total "), Span::from(" (").dim(), Span::from(input_fmt).dim(), Span::from(" input").dim(), Span::from(" + ").dim(), Span::from(output_fmt).dim(), Span::from(" output").dim(), Span::from(")").dim(), ] } fn context_window_spans(&self) -> Option<Vec<Span<'static>>> { let context = self.token_usage.context_window.as_ref()?; let percent = context.percent_remaining; let used_fmt = format_tokens_compact(context.tokens_in_context); let window_fmt = format_tokens_compact(context.window); Some(vec![ Span::from(format!("{percent}% left")), Span::from(" (").dim(), Span::from(used_fmt).dim(), Span::from(" used / ").dim(), Span::from(window_fmt).dim(), Span::from(")").dim(), ]) } fn rate_limit_lines( &self, state: &StatusRateLimitState, available_inner_width: usize, formatter: &FieldFormatter, ) -> Vec<Line<'static>> { match &state.rate_limits { StatusRateLimitData::Available(rows_data) => { if rows_data.is_empty() { return vec![formatter.line( "Limits", vec![Span::from("not available for this account").dim()], )]; } self.rate_limit_row_lines(rows_data, available_inner_width, formatter) } StatusRateLimitData::Stale(rows_data) => { let mut lines = self.rate_limit_row_lines(rows_data, available_inner_width, formatter); lines.push(formatter.line( "Warning", vec![Span::from(if state.refreshing_rate_limits { "limits may be stale - run /status again shortly." } else { "limits may be stale - start new turn to refresh." }) .dim()], )); lines } StatusRateLimitData::Unavailable => { vec![formatter.line( "Limits", vec![Span::from("not available for this account").dim()], )] } StatusRateLimitData::Missing => { vec![formatter.line( "Limits", vec![Span::from(if state.refreshing_rate_limits { "refresh requested; run /status again shortly." } else { "data not available yet" }) .dim()], )] } } } fn rate_limit_row_lines( &self, rows: &[StatusRateLimitRow], available_inner_width: usize, formatter: &FieldFormatter, ) -> Vec<Line<'static>> { let mut lines = Vec::with_capacity(rows.len().saturating_mul(2)); for row in rows { match &row.value { StatusRateLimitValue::Window { percent_used, resets_at, details, } => { let percent_remaining = (100.0 - percent_used).clamp(0.0, 100.0); let summary = format_status_limit_summary(percent_remaining); let full_value_spans = vec![ Span::from(render_status_limit_progress_bar(percent_remaining)), Span::from(" "), Span::from(summary.clone()), ]; // On narrow terminals, keep the percentage visible rather than // letting the fixed-width progress bar crowd out the reset time. let value_spans = if line_display_width(&Line::from(full_value_spans.clone())) <= formatter.value_width(available_inner_width) { full_value_spans } else { vec![Span::from(summary)] }; let base_spans = formatter.full_spans(row.label.as_str(), value_spans); let base_line = Line::from(base_spans.clone()); if let Some(resets_at) = resets_at.as_ref() { let resets_span = Span::from(format!("(resets {resets_at})")).dim(); let mut inline_spans = base_spans.clone(); inline_spans.push(Span::from(" ").dim()); inline_spans.push(resets_span.clone()); if line_display_width(&Line::from(inline_spans.clone())) <= available_inner_width { lines.push(Line::from(inline_spans)); } else { lines.push(base_line); let reset_text = format!("(resets {resets_at})"); let reset_width = formatter.value_width(available_inner_width).max(1); let wrap_options = textwrap::Options::new(reset_width).break_words(false); // Reset timestamps are the actionable part of this row, so wrap them // onto continuation lines instead of truncating partial times/dates. lines.extend( textwrap::wrap(reset_text.as_str(), wrap_options) .into_iter() .map(|wrapped| { formatter.continuation(vec![ Span::from(wrapped.into_owned()).dim(), ]) }), ); } } else { lines.push(base_line); } if let Some(details) = details { let detail_width = formatter.value_width(available_inner_width).max(1); let wrap_options = textwrap::Options::new(detail_width).break_words(false); lines.extend( textwrap::wrap(details.as_str(), wrap_options) .into_iter() .map(|wrapped| { formatter .continuation(vec![Span::from(wrapped.into_owned()).dim()]) }), ); } } StatusRateLimitValue::Text(text) => { let label = row.label.clone(); let spans = formatter.full_spans(label.as_str(), vec![Span::from(text.clone())]); lines.push(Line::from(spans)); } } } lines } fn collect_rate_limit_labels( &self, state: &StatusRateLimitState, seen: &mut BTreeSet<String>, labels: &mut Vec<String>, ) { match &state.rate_limits { StatusRateLimitData::Available(rows) => { if rows.is_empty() { push_label(labels, seen, "Limits"); } else { for row in rows { push_label(labels, seen, row.label.as_str()); } } } StatusRateLimitData::Stale(rows) => { for row in rows { push_label(labels, seen, row.label.as_str()); } push_label(labels, seen, "Warning"); } StatusRateLimitData::Unavailable => push_label(labels, seen, "Limits"), StatusRateLimitData::Missing => push_label(labels, seen, "Limits"), } }}fn status_permission_summary( permission_profile: &PermissionProfile, cwd: &AbsolutePathBuf, workspace_roots: &[AbsolutePathBuf],) -> String { let summary = summarize_permission_profile(permission_profile, cwd, workspace_roots); if let Some(details) = summary.strip_prefix("read-only") { if details.contains("(network access enabled)") { return "read-only with network access".to_string(); } return "read-only".to_string(); } if let Some(details) = summary.strip_prefix("workspace-write") { if details.contains("(network access enabled)") { return "workspace with network access".to_string(); } return "workspace".to_string(); } if summary == "custom permissions (network access enabled)" { return "custom permissions with network access".to_string(); } summary}fn workspace_root_suffix( workspace_roots: &[AbsolutePathBuf], cwd: &AbsolutePathBuf,) -> Option<String> { let extra_roots = workspace_roots .iter() .filter(|root| *root != cwd) .map(|root| root.to_string_lossy().to_string()) .collect::<Vec<_>>(); if extra_roots.is_empty() { None } else { Some(format!(" [{}]", extra_roots.join(", "))) }}fn status_permissions_label( active_permission_profile: Option<&ActivePermissionProfile>, permission_profile: &PermissionProfile, approval_policy: AskForApproval, sandbox: &str, approval: &str, workspace_root_suffix: Option<&str>,) -> String { let active_id = active_permission_profile.map(|active| active.id.as_str()); match active_id { Some(BUILT_IN_PERMISSION_PROFILE_READ_ONLY) => { let label = if sandbox == "read-only with network access" { "Read Only with network access" } else { "Read Only" }; return format!("{label} ({approval})"); } Some(BUILT_IN_PERMISSION_PROFILE_WORKSPACE) => match sandbox { "workspace" => { return format!( "Workspace{} ({approval})", workspace_root_suffix.unwrap_or("") ); } "workspace with network access" => { return format!( "Workspace with network access{} ({approval})", workspace_root_suffix.unwrap_or("") ); } _ => {} }, Some(BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS) if permission_profile == &PermissionProfile::Disabled => { return if approval_policy == AskForApproval::Never { "Full Access".to_string() } else { format!("No Sandbox ({approval})") }; } Some(id) => { let sandbox = decorate_workspace_sandbox_label(sandbox, workspace_root_suffix); return format!("Profile {id} ({sandbox}, {approval})"); } None => {} } if sandbox == "read-only" { return format!("Read Only ({approval})"); } if approval_policy == AskForApproval::OnRequest && sandbox == "workspace" { return format!( "Workspace{} ({approval})", workspace_root_suffix.unwrap_or("") ); } if approval_policy == AskForApproval::Never && permission_profile == &PermissionProfile::Disabled { return "Full Access".to_string(); } let sandbox = decorate_workspace_sandbox_label(sandbox, workspace_root_suffix); format!("Custom ({sandbox}, {approval})")}fn decorate_workspace_sandbox_label(sandbox: &str, workspace_root_suffix: Option<&str>) -> String { match workspace_root_suffix { Some(suffix) if sandbox.starts_with("workspace") => format!("{sandbox}{suffix}"), _ => sandbox.to_string(), }}fn status_approval_label( approval_policy: AskForApproval, approvals_reviewer: ApprovalsReviewer, approval: &str,) -> String { if approval_policy == AskForApproval::OnRequest { return match approvals_reviewer { ApprovalsReviewer::AutoReview => "Approve for me".to_string(), ApprovalsReviewer::User => "Ask for approval".to_string(), }; } approval.to_string()}impl HistoryCell for StatusHistoryCell { fn display_lines(&self, width: u16) -> Vec<Line<'static>> { let mut lines: Vec<Line<'static>> = Vec::new(); lines.push(Line::from(vec![ Span::from(format!("{}>_ ", FieldFormatter::INDENT)).dim(), Span::from("OpenAI Codex").bold(), Span::from(" ").dim(), Span::from(format!("(v{CODEX_CLI_VERSION})")).dim(), ])); let available_inner_width = usize::from(width.saturating_sub(4)); if available_inner_width == 0 { return Vec::new(); } let account_value = self.account.as_ref().map(|account| match account { StatusAccountDisplay::ChatGpt { email, plan } => match (email, plan) { (Some(email), Some(plan)) => format!("{email} ({plan})"), (Some(email), None) => email.clone(), (None, Some(plan)) => plan.clone(), (None, None) => "ChatGPT".to_string(), }, StatusAccountDisplay::ApiKey => { "API key configured (run codex login to use ChatGPT)".to_string() } }); let mut labels: Vec<String> = vec!["Model", "Directory", "Permissions", "Agents.md"] .into_iter() .map(str::to_string) .collect(); let mut seen: BTreeSet<String> = labels.iter().cloned().collect(); let thread_name = self.thread_name.as_deref().filter(|name| !name.is_empty()); #[expect(clippy::expect_used)] let rate_limit_state = self .rate_limit_state .read() .expect("status history rate-limit state poisoned"); #[expect(clippy::expect_used)] let agents_summary = self .agents_summary .read() .expect("status history agents summary state poisoned") .clone(); if self.model_provider.is_some() { push_label(&mut labels, &mut seen, "Model provider"); } if account_value.is_some() { push_label(&mut labels, &mut seen, "Account"); } if thread_name.is_some() { push_label(&mut labels, &mut seen, "Thread name"); } if self.session_id.is_some() { push_label(&mut labels, &mut seen, "Session"); } if self.session_id.is_some() && self.forked_from.is_some() { push_label(&mut labels, &mut seen, "Forked from"); } if self.collaboration_mode.is_some() { push_label(&mut labels, &mut seen, "Collaboration mode"); } push_label(&mut labels, &mut seen, "Token usage"); if self.token_usage.context_window.is_some() { push_label(&mut labels, &mut seen, "Context window"); } self.collect_rate_limit_labels(&rate_limit_state, &mut seen, &mut labels); let formatter = FieldFormatter::from_labels(labels.iter().map(String::as_str)); let value_width = formatter.value_width(available_inner_width); let note_first_line = Line::from(vec![ Span::from("Visit ").cyan(), CHATGPT_USAGE_URL.cyan().underlined(), Span::from(" for up-to-date").cyan(), ]); let note_second_line = Line::from(vec![ Span::from("information on rate limits and credits").cyan(), ]); let note_lines = adaptive_wrap_lines( [note_first_line, note_second_line], RtOptions::new(available_inner_width), ); lines.push(Line::from(Vec::<Span<'static>>::new())); // The ChatGPT usage page only applies to providers backed by OpenAI auth; // providers like Bedrock manage limits and billing elsewhere. if self.show_chatgpt_usage_link { lines.extend(note_lines); lines.push(Line::from(Vec::<Span<'static>>::new())); } if let Some(remote_connection) = self.remote_connection.as_ref() { let wrapped_remote = word_wrap_lines( [Line::from(vec![ Span::from(remote_connection.address.clone()), Span::from(" (").dim(), Span::from(remote_connection.version.clone()).dim(), Span::from(")").dim(), ])], RtOptions::new(value_width.max(1)), ); let mut wrapped_remote = wrapped_remote.into_iter(); if let Some(first) = wrapped_remote.next() { lines.push(formatter.line("Remote", first.spans)); lines.extend(wrapped_remote.map(|line| formatter.continuation(line.spans))); } lines.push(Line::from(Vec::<Span<'static>>::new())); } let mut model_spans = vec![Span::from(self.model_name.clone())]; if !self.model_details.is_empty() { model_spans.push(Span::from(" (").dim()); model_spans.push(Span::from(self.model_details.join(", ")).dim()); model_spans.push(Span::from(")").dim()); } let directory_value = format_directory_display(&self.directory, Some(value_width)); lines.push(formatter.line("Model", model_spans)); if let Some(model_provider) = self.model_provider.as_ref() { lines.push(formatter.line("Model provider", vec![Span::from(model_provider.clone())])); } lines.push(formatter.line("Directory", vec![Span::from(directory_value)])); lines.push(formatter.line("Permissions", vec![Span::from(self.permissions.clone())])); lines.push(formatter.line("Agents.md", vec![Span::from(agents_summary)])); if let Some(account_value) = account_value { lines.push(formatter.line("Account", vec![Span::from(account_value)])); } if let Some(thread_name) = thread_name { lines.push(formatter.line("Thread name", vec![Span::from(thread_name.to_string())])); } if let Some(collab_mode) = self.collaboration_mode.as_ref() { lines.push(formatter.line("Collaboration mode", vec![Span::from(collab_mode.clone())])); } if let Some(session) = self.session_id.as_ref() { lines.push(formatter.line("Session", vec![Span::from(session.clone())])); } if self.session_id.is_some() && let Some(forked_from) = self.forked_from.as_ref() { lines.push(formatter.line("Forked from", vec![Span::from(forked_from.clone())])); } lines.push(Line::from(Vec::<Span<'static>>::new())); // Hide token usage only for ChatGPT subscribers if !matches!(self.account, Some(StatusAccountDisplay::ChatGpt { .. })) { lines.push(formatter.line("Token usage", self.token_usage_spans())); } if let Some(spans) = self.context_window_spans() { lines.push(formatter.line("Context window", spans)); } lines.extend(self.rate_limit_lines(&rate_limit_state, available_inner_width, &formatter)); let content_width = lines.iter().map(line_display_width).max().unwrap_or(0); let inner_width = content_width.min(available_inner_width); let truncated_lines: Vec<Line<'static>> = lines .into_iter() .map(|line| truncate_line_to_width(line, inner_width)) .collect(); with_border_with_inner_width(truncated_lines, inner_width) } fn raw_lines(&self) -> Vec<Line<'static>> { plain_lines(self.display_lines(u16::MAX)) } fn display_hyperlink_lines( &self, width: u16, ) -> Vec<crate::terminal_hyperlinks::HyperlinkLine> { let mut lines = crate::terminal_hyperlinks::plain_hyperlink_lines(self.display_lines(width)); for line in &mut lines { let visible = line .line .spans .iter() .map(|span| span.content.as_ref()) .collect::<String>(); if let Some(start_byte) = visible.find(CHATGPT_USAGE_URL) { let start = visible[..start_byte].width(); line.hyperlinks .push(crate::terminal_hyperlinks::TerminalHyperlink { columns: start..start + CHATGPT_USAGE_URL.width(), destination: CHATGPT_USAGE_URL.to_string(), }); } } lines } fn transcript_hyperlink_lines( &self, width: u16, ) -> Vec<crate::terminal_hyperlinks::HyperlinkLine> { self.display_hyperlink_lines(width) }}fn format_model_provider(config: &Config, runtime_base_url: Option<&str>) -> Option<String> { let provider = &config.model_provider; let name = provider.name.trim(); let provider_name = if name.is_empty() { config.model_provider_id.as_str() } else { name }; let base_url = runtime_base_url.and_then(sanitize_base_url); let is_default_openai = provider.is_openai() && base_url.is_none(); if is_default_openai { return None; } Some(match base_url { Some(base_url) => format!("{provider_name} - {base_url}"), None => provider_name.to_string(), })}fn sanitize_base_url(raw: &str) -> Option<String> { let trimmed = raw.trim(); if trimmed.is_empty() { return None; } let Ok(mut url) = Url::parse(trimmed) else { return None; }; let _ = url.set_username(""); let _ = url.set_password(None); url.set_query(None); url.set_fragment(None); Some(url.to_string().trim_end_matches('/').to_string()).filter(|value| !value.is_empty())}