Add opt-out for rate limit model nudge (#6433)
## Summary - add a `hide_rate_limit_model_nudge` notice flag plus config edit plumbing so the rate limit reminder preference is persisted and documented - extend the chat widget prompt with a "never show again" option, and wire new app events so selecting it hides future nudges immediately and writes the config - add unit coverage and refresh the snapshot for the three-option prompt ## Testing - `just fmt` - `just fix -p codex-tui` - `just fix -p codex-core` - `cargo test -p codex-tui` - `cargo test -p codex-core` *(fails at `exec::tests::kill_child_process_group_kills_grandchildren_on_timeout`: grandchild process still alive)* ------ [Codex Task](https://chatgpt.com/codex/tasks/task_i_6910d7f407748321b2661fc355416994)
This commit is contained in:
@@ -25,6 +25,8 @@ pub enum ConfigEdit {
|
||||
SetNoticeHideFullAccessWarning(bool),
|
||||
/// Toggle the Windows world-writable directories warning acknowledgement flag.
|
||||
SetNoticeHideWorldWritableWarning(bool),
|
||||
/// Toggle the rate limit model nudge acknowledgement flag.
|
||||
SetNoticeHideRateLimitModelNudge(bool),
|
||||
/// Toggle the Windows onboarding acknowledgement flag.
|
||||
SetWindowsWslSetupAcknowledged(bool),
|
||||
/// Replace the entire `[mcp_servers]` table.
|
||||
@@ -246,6 +248,11 @@ impl ConfigDocument {
|
||||
&[Notice::TABLE_KEY, "hide_world_writable_warning"],
|
||||
value(*acknowledged),
|
||||
)),
|
||||
ConfigEdit::SetNoticeHideRateLimitModelNudge(acknowledged) => Ok(self.write_value(
|
||||
Scope::Global,
|
||||
&[Notice::TABLE_KEY, "hide_rate_limit_model_nudge"],
|
||||
value(*acknowledged),
|
||||
)),
|
||||
ConfigEdit::SetWindowsWslSetupAcknowledged(acknowledged) => Ok(self.write_value(
|
||||
Scope::Global,
|
||||
&["windows_wsl_setup_acknowledged"],
|
||||
@@ -486,6 +493,12 @@ impl ConfigEditsBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_hide_rate_limit_model_nudge(mut self, acknowledged: bool) -> Self {
|
||||
self.edits
|
||||
.push(ConfigEdit::SetNoticeHideRateLimitModelNudge(acknowledged));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_windows_wsl_setup_acknowledged(mut self, acknowledged: bool) -> Self {
|
||||
self.edits
|
||||
.push(ConfigEdit::SetWindowsWslSetupAcknowledged(acknowledged));
|
||||
@@ -733,6 +746,34 @@ hide_full_access_warning = true
|
||||
assert_eq!(contents, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blocking_set_hide_rate_limit_model_nudge_preserves_table() {
|
||||
let tmp = tempdir().expect("tmpdir");
|
||||
let codex_home = tmp.path();
|
||||
std::fs::write(
|
||||
codex_home.join(CONFIG_TOML_FILE),
|
||||
r#"[notice]
|
||||
existing = "value"
|
||||
"#,
|
||||
)
|
||||
.expect("seed");
|
||||
|
||||
apply_blocking(
|
||||
codex_home,
|
||||
None,
|
||||
&[ConfigEdit::SetNoticeHideRateLimitModelNudge(true)],
|
||||
)
|
||||
.expect("persist");
|
||||
|
||||
let contents =
|
||||
std::fs::read_to_string(codex_home.join(CONFIG_TOML_FILE)).expect("read config");
|
||||
let expected = r#"[notice]
|
||||
existing = "value"
|
||||
hide_rate_limit_model_nudge = true
|
||||
"#;
|
||||
assert_eq!(contents, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blocking_replace_mcp_servers_round_trips() {
|
||||
let tmp = tempdir().expect("tmpdir");
|
||||
|
||||
@@ -360,6 +360,8 @@ pub struct Notice {
|
||||
pub hide_full_access_warning: Option<bool>,
|
||||
/// Tracks whether the user has acknowledged the Windows world-writable directories warning.
|
||||
pub hide_world_writable_warning: Option<bool>,
|
||||
/// Tracks whether the user opted out of the rate limit model switch reminder.
|
||||
pub hide_rate_limit_model_nudge: Option<bool>,
|
||||
}
|
||||
|
||||
impl Notice {
|
||||
|
||||
@@ -499,6 +499,9 @@ impl App {
|
||||
self.chat_widget
|
||||
.set_world_writable_warning_acknowledged(ack);
|
||||
}
|
||||
AppEvent::UpdateRateLimitSwitchPromptHidden(hidden) => {
|
||||
self.chat_widget.set_rate_limit_switch_prompt_hidden(hidden);
|
||||
}
|
||||
AppEvent::PersistFullAccessWarningAcknowledged => {
|
||||
if let Err(err) = ConfigEditsBuilder::new(&self.config.codex_home)
|
||||
.set_hide_full_access_warning(true)
|
||||
@@ -529,6 +532,21 @@ impl App {
|
||||
));
|
||||
}
|
||||
}
|
||||
AppEvent::PersistRateLimitSwitchPromptHidden => {
|
||||
if let Err(err) = ConfigEditsBuilder::new(&self.config.codex_home)
|
||||
.set_hide_rate_limit_model_nudge(true)
|
||||
.apply()
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
error = %err,
|
||||
"failed to persist rate limit switch prompt preference"
|
||||
);
|
||||
self.chat_widget.add_error_message(format!(
|
||||
"Failed to save rate limit reminder preference: {err}"
|
||||
));
|
||||
}
|
||||
}
|
||||
AppEvent::OpenApprovalsPopup => {
|
||||
self.chat_widget.open_approvals_popup();
|
||||
}
|
||||
|
||||
@@ -104,6 +104,9 @@ pub(crate) enum AppEvent {
|
||||
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
||||
UpdateWorldWritableWarningAcknowledged(bool),
|
||||
|
||||
/// Update whether the rate limit switch prompt has been acknowledged for the session.
|
||||
UpdateRateLimitSwitchPromptHidden(bool),
|
||||
|
||||
/// Persist the acknowledgement flag for the full access warning prompt.
|
||||
PersistFullAccessWarningAcknowledged,
|
||||
|
||||
@@ -111,6 +114,9 @@ pub(crate) enum AppEvent {
|
||||
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
||||
PersistWorldWritableWarningAcknowledged,
|
||||
|
||||
/// Persist the acknowledgement flag for the rate limit switch prompt.
|
||||
PersistRateLimitSwitchPromptHidden,
|
||||
|
||||
/// Skip the next world-writable scan (one-shot) after a user-confirmed continue.
|
||||
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
||||
SkipNextWorldWritableScan,
|
||||
|
||||
@@ -522,6 +522,7 @@ impl ChatWidget {
|
||||
.unwrap_or(false);
|
||||
|
||||
if high_usage
|
||||
&& !self.rate_limit_switch_prompt_hidden()
|
||||
&& self.config.model != NUDGE_MODEL_SLUG
|
||||
&& !matches!(
|
||||
self.rate_limit_switch_prompt,
|
||||
@@ -1710,7 +1711,18 @@ impl ChatWidget {
|
||||
.find(|preset| preset.model == NUDGE_MODEL_SLUG)
|
||||
}
|
||||
|
||||
fn rate_limit_switch_prompt_hidden(&self) -> bool {
|
||||
self.config
|
||||
.notices
|
||||
.hide_rate_limit_model_nudge
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn maybe_show_pending_rate_limit_prompt(&mut self) {
|
||||
if self.rate_limit_switch_prompt_hidden() {
|
||||
self.rate_limit_switch_prompt = RateLimitSwitchPromptState::Idle;
|
||||
return;
|
||||
}
|
||||
if !matches!(
|
||||
self.rate_limit_switch_prompt,
|
||||
RateLimitSwitchPromptState::Pending
|
||||
@@ -1744,6 +1756,10 @@ impl ChatWidget {
|
||||
})];
|
||||
|
||||
let keep_actions: Vec<SelectionAction> = Vec::new();
|
||||
let never_actions: Vec<SelectionAction> = vec![Box::new(|tx| {
|
||||
tx.send(AppEvent::UpdateRateLimitSwitchPromptHidden(true));
|
||||
tx.send(AppEvent::PersistRateLimitSwitchPromptHidden);
|
||||
})];
|
||||
let description = if preset.description.is_empty() {
|
||||
Some("Uses fewer credits for upcoming turns.".to_string())
|
||||
} else {
|
||||
@@ -1769,6 +1785,17 @@ impl ChatWidget {
|
||||
dismiss_on_select: true,
|
||||
..Default::default()
|
||||
},
|
||||
SelectionItem {
|
||||
name: "Keep current model (never show again)".to_string(),
|
||||
description: Some(
|
||||
"Hide future rate limit reminders about switching models.".to_string(),
|
||||
),
|
||||
selected_description: None,
|
||||
is_current: false,
|
||||
actions: never_actions,
|
||||
dismiss_on_select: true,
|
||||
..Default::default()
|
||||
},
|
||||
];
|
||||
|
||||
self.bottom_pane.show_selection_view(SelectionViewParams {
|
||||
@@ -2386,6 +2413,13 @@ impl ChatWidget {
|
||||
self.config.notices.hide_world_writable_warning = Some(acknowledged);
|
||||
}
|
||||
|
||||
pub(crate) fn set_rate_limit_switch_prompt_hidden(&mut self, hidden: bool) {
|
||||
self.config.notices.hide_rate_limit_model_nudge = Some(hidden);
|
||||
if hidden {
|
||||
self.rate_limit_switch_prompt = RateLimitSwitchPromptState::Idle;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
||||
pub(crate) fn world_writable_warning_hidden(&self) -> bool {
|
||||
self.config
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
---
|
||||
source: tui/src/chatwidget/tests.rs
|
||||
assertion_line: 500
|
||||
expression: popup
|
||||
---
|
||||
Approaching rate limits
|
||||
Switch to gpt-5-codex-mini for lower credit usage?
|
||||
|
||||
› 1. Switch to gpt-5-codex-mini Optimized for codex. Cheaper, faster, but
|
||||
less capable.
|
||||
› 1. Switch to gpt-5-codex-mini Optimized for codex. Cheaper,
|
||||
faster, but less capable.
|
||||
2. Keep current model
|
||||
3. Keep current model (never show again) Hide future rate limit reminders
|
||||
about switching models.
|
||||
|
||||
Press enter to confirm or esc to go back
|
||||
|
||||
@@ -448,6 +448,22 @@ fn rate_limit_switch_prompt_shows_once_per_session() {
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rate_limit_switch_prompt_respects_hidden_notice() {
|
||||
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
|
||||
let (mut chat, _, _) = make_chatwidget_manual();
|
||||
chat.config.model = "gpt-5".to_string();
|
||||
chat.auth_manager = AuthManager::from_auth_for_testing(auth);
|
||||
chat.config.notices.hide_rate_limit_model_nudge = Some(true);
|
||||
|
||||
chat.on_rate_limit_snapshot(Some(snapshot(95.0)));
|
||||
|
||||
assert!(matches!(
|
||||
chat.rate_limit_switch_prompt,
|
||||
RateLimitSwitchPromptState::Idle
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rate_limit_switch_prompt_defers_until_task_complete() {
|
||||
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
|
||||
|
||||
@@ -159,6 +159,7 @@ windows_wsl_setup_acknowledged = false
|
||||
# In-product notices (mostly set automatically by Codex).
|
||||
[notice]
|
||||
# hide_full_access_warning = true
|
||||
# hide_rate_limit_model_nudge = true
|
||||
|
||||
################################################################################
|
||||
# Authentication & Login
|
||||
|
||||
Reference in New Issue
Block a user