Files
codex/codex-rs/tui/src/service_tier_resolution.rs
T
Shijie Rao 370b13afc9 Honor client-resolved service tier defaults (#23537)
## Why

Model catalog responses can now advertise a nullable
`default_service_tier` for each model. Codex needs to preserve three
distinct states all the way from config/app-server inputs to inference:

- no explicit service tier, so the client may apply the current model
catalog default when FastMode is enabled
- explicit `default`, meaning the user intentionally wants standard
routing
- explicit catalog tier ids such as `priority`, `flex`, or future tiers

Keeping those states distinct prevents the UI from showing one tier
while core sends another, especially after model switches or app-server
`thread/start` / `turn/start` updates.

## What Changed

- Plumbed `default_service_tier` through model catalog protocol types,
app-server model responses, generated schemas, model cache fixtures, and
provider/model-manager conversions.
- Added the request-only `default` service tier sentinel and normalized
legacy config spelling so `fast` in `config.toml` still materializes as
the runtime/request id `priority`.
- Moved catalog default resolution to the TUI/client side, including
recomputing the effective service tier when model/FastMode-dependent
surfaces change.
- Updated app-server thread lifecycle config construction so
`serviceTier: null` preserves explicit standard-routing intent by
mapping to `default` instead of internal `None`.
- Kept core responsible for validating explicit tiers against the
current model and stripping `default` before `/v1/responses`, without
applying catalog defaults itself.

## Validation

- `CARGO_INCREMENTAL=0 cargo build -p codex-cli`
- `CARGO_INCREMENTAL=0 cargo test -p codex-app-server model_list`
- `cargo test -p codex-tui service_tier`
- `cargo test -p codex-protocol service_tier_for_request`
- `cargo test -p codex-core get_service_tier`
- `RUST_MIN_STACK=8388608 CARGO_INCREMENTAL=0 cargo test -p codex-core
service_tier`
2026-05-20 15:57:50 -07:00

65 lines
1.9 KiB
Rust

use crate::legacy_core::config::Config;
use codex_features::Feature;
use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;
use codex_protocol::openai_models::ModelPreset;
pub(crate) fn configured_service_tier(config: &Config) -> Option<String> {
config.service_tier.clone().or_else(|| {
(config.notices.fast_default_opt_out == Some(true))
.then(|| SERVICE_TIER_DEFAULT_REQUEST_VALUE.to_string())
})
}
pub(crate) fn effective_service_tier(
config: &Config,
model: &str,
models: &[ModelPreset],
) -> Option<String> {
if !config.features.enabled(Feature::FastMode) {
return None;
}
let configured = configured_service_tier(config);
let Some(preset) = models.iter().find(|preset| preset.model == model) else {
return configured;
};
match configured.as_deref() {
Some(service_tier) if service_tier == SERVICE_TIER_DEFAULT_REQUEST_VALUE => configured,
Some(service_tier) if model_supports_service_tier(preset, service_tier) => configured,
Some(_) => None,
None => preset
.default_service_tier
.clone()
.filter(|service_tier| model_supports_service_tier(preset, service_tier)),
}
}
pub(crate) fn service_tier_update_for_core(
config: &Config,
model: &str,
models: &[ModelPreset],
) -> Option<Option<String>> {
if !config.features.enabled(Feature::FastMode) {
return None;
}
let effective = effective_service_tier(config, model, models);
if let Some(service_tier) = effective {
return Some(Some(service_tier));
}
if !models.iter().any(|preset| preset.model == model) {
return None;
}
Some(Some(SERVICE_TIER_DEFAULT_REQUEST_VALUE.to_string()))
}
pub(crate) fn model_supports_service_tier(model: &ModelPreset, service_tier: &str) -> bool {
model
.service_tiers
.iter()
.any(|tier| tier.id == service_tier)
}