Default Fast service tier for eligible ChatGPT plans (#19053)

## Why

Enterprise and business-like ChatGPT plans should get Codex's Fast
service tier by default when the user or caller has not made an explicit
service-tier choice. At the same time, callers need a durable way to
choose standard routing without adding a new persisted `standard`
service tier value. This keeps existing config compatibility while
letting core own the managed default policy.

## What changed

- Resolve the effective service tier in core at session creation:
explicit `fast` or `flex` wins, explicit null/clear or
`[notice].fast_default_opt_out = true` resolves to standard routing, and
otherwise eligible ChatGPT plans resolve to Fast when FastMode is
enabled.
- Add `[notice].fast_default_opt_out` as the persisted opt-out marker
for managed Fast defaults.
- Treat app-server/TUI `service_tier: null` as an explicit
standard/clear choice by preserving that intent through config loading.
- Update TUI rendering to use core's effective service tier for startup
and status surfaces while still keeping `config.service_tier` as the
explicit configured choice.
- Update `/fast off` to clear `service_tier`, persist the opt-out
marker, and send explicit standard for subsequent turns.

## Verification

- Added unit coverage for config override/notice handling, service-tier
resolution, runtime null clearing, and `/fast off` turn propagation.
- `cargo build -p codex-cli`

Full test suite was not run locally per author request.
This commit is contained in:
Shijie Rao
2026-04-22 21:54:44 -07:00
committed by GitHub
parent 082fc4f632
commit 02170996e6
16 changed files with 289 additions and 33 deletions
+14 -4
View File
@@ -2031,14 +2031,24 @@ impl Config {
let forced_login_method = cfg.forced_login_method;
let model = model.or(config_profile.model).or(cfg.model);
let service_tier = service_tier_override
.unwrap_or_else(|| config_profile.service_tier.or(cfg.service_tier));
let mut notices = cfg.notice.unwrap_or_default();
let service_tier = match service_tier_override {
Some(Some(service_tier)) => Some(service_tier),
Some(None) => {
// Preserve explicit standard/clear intent after the nested override
// collapses into `Config.service_tier = None`.
notices.fast_default_opt_out = Some(true);
None
}
None => config_profile.service_tier.or(cfg.service_tier),
};
let service_tier = match service_tier {
Some(ServiceTier::Fast) if features.enabled(Feature::FastMode) => {
Some(ServiceTier::Fast)
}
Some(ServiceTier::Fast) => None,
Some(ServiceTier::Flex) => Some(ServiceTier::Flex),
_ => None,
None => None,
};
let compact_prompt = compact_prompt.or(cfg.compact_prompt).and_then(|value| {
@@ -2414,7 +2424,7 @@ impl Config {
active_profile: active_profile_name,
active_project,
windows_wsl_setup_acknowledged: cfg.windows_wsl_setup_acknowledged.unwrap_or(false),
notices: cfg.notice.unwrap_or_default(),
notices,
check_for_update_on_startup,
disable_paste_burst: cfg.disable_paste_burst.unwrap_or(false),
analytics_enabled: config_profile