[tui] rotate paid promo tips to include fast mode (#13438)

- rotate the paid-plan startup promo slot 50/50 between the existing
Codex App promo and a new Fast mode promo
- keep the Fast mode call to action platform-neutral so Windows can show
the same tip
- add a focused unit test to ensure the paid promo pool actually rotates

---------

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
pash-openai
2026-03-04 20:06:44 -08:00
committed by GitHub
parent 394e538640
commit 3284bde48e
2 changed files with 60 additions and 9 deletions

View File

@@ -44,6 +44,7 @@ use codex_core::plugins::PluginsManager;
use codex_core::web_search::web_search_detail;
use codex_otel::RuntimeMetricsSummary;
use codex_protocol::account::PlanType;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::mcp::Resource;
use codex_protocol::mcp::ResourceTemplate;
use codex_protocol::models::WebSearchAction;
@@ -1099,7 +1100,12 @@ pub(crate) fn new_session_info(
} else {
if config.show_tooltips
&& let Some(tooltips) = tooltip_override
.or_else(|| tooltips::get_tooltip(auth_plan))
.or_else(|| {
tooltips::get_tooltip(
auth_plan,
matches!(config.service_tier, Some(ServiceTier::Fast)),
)
})
.map(TooltipHistoryCell::new)
{
parts.push(Box::new(tooltips));

View File

@@ -7,9 +7,12 @@ const ANNOUNCEMENT_TIP_URL: &str =
"https://raw.githubusercontent.com/openai/codex/main/announcement_tip.toml";
const IS_MACOS: bool = cfg!(target_os = "macos");
const IS_WINDOWS: bool = cfg!(target_os = "windows");
const PAID_TOOLTIP: &str = "*New* Try the **Codex App** with 2x rate limits until *April 2nd*. Run 'codex app' or visit https://chatgpt.com/codex?app-landing-page=true";
const PAID_TOOLTIP_WINDOWS: &str = "*New* Try the **Codex App**, now available on **Windows**, with 2x rate limits until *April 2nd*. Run 'codex app' or visit https://chatgpt.com/codex?app-landing-page=true";
const PAID_TOOLTIP_NON_MAC: &str = "*New* 2x rate limits until *April 2nd*.";
const FAST_TOOLTIP: &str = "*New* Use **/fast** to enable our fastest inference at 2X plan usage.";
const OTHER_TOOLTIP: &str = "*New* Build faster with the **Codex App**. Run 'codex app' or visit https://chatgpt.com/codex?app-landing-page=true";
const OTHER_TOOLTIP_NON_MAC: &str = "*New* Build faster with Codex.";
const FREE_GO_TOOLTIP: &str =
@@ -25,7 +28,7 @@ lazy_static! {
if line.is_empty() || line.starts_with('#') {
return false;
}
if !IS_MACOS && line.contains("codex app") {
if !IS_MACOS && !IS_WINDOWS && line.contains("codex app") {
return false;
}
true
@@ -47,7 +50,7 @@ fn experimental_tooltips() -> Vec<&'static str> {
}
/// Pick a random tooltip to show to the user when starting Codex.
pub(crate) fn get_tooltip(plan: Option<PlanType>) -> Option<String> {
pub(crate) fn get_tooltip(plan: Option<PlanType>, fast_mode_enabled: bool) -> Option<String> {
let mut rng = rand::rng();
if let Some(announcement) = announcement::fetch_announcement_tip() {
@@ -62,12 +65,7 @@ pub(crate) fn get_tooltip(plan: Option<PlanType>) -> Option<String> {
| Some(PlanType::Team)
| Some(PlanType::Enterprise)
| Some(PlanType::Pro) => {
let tooltip = if IS_MACOS {
PAID_TOOLTIP
} else {
PAID_TOOLTIP_NON_MAC
};
return Some(tooltip.to_string());
return Some(pick_paid_tooltip(&mut rng, fast_mode_enabled).to_string());
}
Some(PlanType::Go) | Some(PlanType::Free) => {
return Some(FREE_GO_TOOLTIP.to_string());
@@ -86,6 +84,28 @@ pub(crate) fn get_tooltip(plan: Option<PlanType>) -> Option<String> {
pick_tooltip(&mut rng).map(str::to_string)
}
fn paid_app_tooltip() -> &'static str {
if IS_MACOS {
PAID_TOOLTIP
} else if IS_WINDOWS {
PAID_TOOLTIP_WINDOWS
} else {
PAID_TOOLTIP_NON_MAC
}
}
/// Paid users spend most startup sessions in a dedicated promo slot rather than the
/// generic random tip pool. Keep this business logic explicit: we currently split
/// that slot between the app promo and Fast mode, but suppress the Fast promo once
/// the user already has Fast mode enabled.
fn pick_paid_tooltip<R: Rng + ?Sized>(rng: &mut R, fast_mode_enabled: bool) -> &'static str {
if fast_mode_enabled || rng.random_bool(0.5) {
paid_app_tooltip()
} else {
FAST_TOOLTIP
}
}
fn pick_tooltip<R: Rng + ?Sized>(rng: &mut R) -> Option<&'static str> {
if ALL_TOOLTIPS.is_empty() {
None
@@ -264,6 +284,31 @@ mod tests {
assert_eq!(expected, pick_tooltip(&mut rng));
}
#[test]
fn paid_tooltip_pool_rotates_between_promos() {
let mut seen = std::collections::BTreeSet::new();
for seed in 0..32 {
let mut rng = StdRng::seed_from_u64(seed);
seen.insert(pick_paid_tooltip(&mut rng, false));
}
let expected = std::collections::BTreeSet::from([paid_app_tooltip(), FAST_TOOLTIP]);
assert_eq!(seen, expected);
}
#[test]
fn paid_tooltip_pool_skips_fast_when_fast_mode_is_enabled() {
let mut seen = std::collections::BTreeSet::new();
for seed in 0..8 {
let mut rng = StdRng::seed_from_u64(seed);
seen.insert(pick_paid_tooltip(&mut rng, true));
}
let expected = std::collections::BTreeSet::from([paid_app_tooltip()]);
assert_eq!(seen, expected);
assert!(!seen.contains(&FAST_TOOLTIP));
}
#[test]
fn announcement_tip_toml_picks_last_matching() {
let toml = r#"