mirror of
https://github.com/openai/codex.git
synced 2026-03-05 21:45:28 +03:00
[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:
@@ -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));
|
||||
|
||||
@@ -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#"
|
||||
|
||||
Reference in New Issue
Block a user