diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 0e155fdc0..8dc2276b7 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -2939,6 +2939,7 @@ mod tests { app.chat_widget.current_model(), event, is_first, + None, )) as Arc }; diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 078fbb978..eb2d1ad96 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -815,6 +815,9 @@ impl ChatWidget { &model_for_header, event, self.show_welcome_banner, + self.auth_manager + .auth_cached() + .and_then(|auth| auth.account_plan_type()), ); self.apply_session_info_cell(session_info_cell); diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index 934c9506f..eb1de452a 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -46,6 +46,7 @@ use codex_core::protocol::McpInvocation; use codex_core::protocol::SessionConfiguredEvent; use codex_core::web_search::web_search_detail; use codex_otel::RuntimeMetricsSummary; +use codex_protocol::account::PlanType; use codex_protocol::models::WebSearchAction; use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig; use codex_protocol::plan_tool::PlanItemArg; @@ -943,6 +944,7 @@ pub(crate) fn new_session_info( requested_model: &str, event: SessionConfiguredEvent, is_first_event: bool, + auth_plan: Option, ) -> SessionInfoCell { let SessionConfiguredEvent { model, @@ -995,7 +997,7 @@ pub(crate) fn new_session_info( parts.push(Box::new(PlainHistoryCell { lines: help_lines })); } else { if config.show_tooltips - && let Some(tooltips) = tooltips::random_tooltip().map(TooltipHistoryCell::new) + && let Some(tooltips) = tooltips::get_tooltip(auth_plan).map(TooltipHistoryCell::new) { parts.push(Box::new(tooltips)); } diff --git a/codex-rs/tui/src/tooltips.rs b/codex-rs/tui/src/tooltips.rs index 1b59eb4f8..b59a478a4 100644 --- a/codex-rs/tui/src/tooltips.rs +++ b/codex-rs/tui/src/tooltips.rs @@ -1,9 +1,18 @@ use codex_core::features::FEATURES; +use codex_protocol::account::PlanType; use lazy_static::lazy_static; use rand::Rng; const ANNOUNCEMENT_TIP_URL: &str = "https://raw.githubusercontent.com/openai/codex/main/announcement_tip.toml"; + +const PAID_TOOLTIP: &str = + "*New* Try the **Codex App** with 2x rate limits until *April 2nd*. https://chatgpt.com/codex"; +const OTHER_TOOLTIP: &str = + "*New* Build faster with the **Codex App**. Try it now. https://chatgpt.com/codex"; +const FREE_GO_TOOLTIP: &str = + "*New* Codex is included in your plan for free through *March 2nd* – let’s build together."; + const RAW_TOOLTIPS: &str = include_str!("../tooltips.txt"); lazy_static! { @@ -28,11 +37,30 @@ fn experimental_tooltips() -> Vec<&'static str> { } /// Pick a random tooltip to show to the user when starting Codex. -pub(crate) fn random_tooltip() -> Option { +pub(crate) fn get_tooltip(plan: Option) -> Option { + let mut rng = rand::rng(); + + // Leave small chance for a random tooltip to be shown. + if rng.random_ratio(8, 10) { + match plan { + Some(PlanType::Plus) + | Some(PlanType::Business) + | Some(PlanType::Team) + | Some(PlanType::Enterprise) + | Some(PlanType::Pro) => { + return Some(PAID_TOOLTIP.to_string()); + } + Some(PlanType::Go) | Some(PlanType::Free) => { + return Some(FREE_GO_TOOLTIP.to_string()); + } + _ => return Some(OTHER_TOOLTIP.to_string()), + } + } + if let Some(announcement) = announcement::fetch_announcement_tip() { return Some(announcement); } - let mut rng = rand::rng(); + pick_tooltip(&mut rng).map(str::to_string) }