Allow string service tiers in config TOML (#21697)

## Why

`service_tier` in `config.toml` and profile config was still modeled as
an enum, which blocked newer or experimental service tier IDs even
though the runtime paths already carry string IDs.

This change makes the TOML-facing config accept string service tier IDs
directly while keeping the legacy `fast` alias behavior by normalizing
it to the request value `priority`.

## What Changed

- change the TOML-facing `service_tier` fields in global and profile
config to `Option<String>`
- keep config-load normalization so legacy `fast` still resolves to
`priority`
- persist resolved service tier strings directly in config locks so
arbitrary IDs round-trip cleanly
- regenerate the config schema and add config coverage for arbitrary
string IDs plus legacy `fast` normalization

## Verification

- added config tests for arbitrary string service tiers and legacy
`fast` normalization
- ran `just write-config-schema`
- CI

---------

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Ahmed Ibrahim
2026-05-08 15:15:00 +03:00
committed by GitHub
co-authored by Codex
parent d9feaffffb
commit 317213fd33
6 changed files with 60 additions and 33 deletions
+48
View File
@@ -7297,6 +7297,54 @@ async fn legacy_fast_service_tier_override_uses_priority_request_value() -> std:
Ok(())
}
#[tokio::test]
async fn config_toml_service_tier_accepts_arbitrary_string() -> std::io::Result<()> {
let mut fixture = create_test_fixture()?;
fixture.cfg.service_tier = Some("experimental-tier-id".to_string());
let cwd = fixture.cwd_path();
let codex_home = fixture.codex_home();
let config = Config::load_from_base_config_with_overrides(
fixture.cfg,
ConfigOverrides {
cwd: Some(cwd),
..Default::default()
},
codex_home,
)
.await?;
assert_eq!(
config.service_tier,
Some("experimental-tier-id".to_string())
);
Ok(())
}
#[tokio::test]
async fn config_toml_legacy_fast_service_tier_uses_priority_request_value() -> std::io::Result<()> {
let mut fixture = create_test_fixture()?;
fixture.cfg.service_tier = Some("fast".to_string());
let cwd = fixture.cwd_path();
let codex_home = fixture.codex_home();
let config = Config::load_from_base_config_with_overrides(
fixture.cfg,
ConfigOverrides {
cwd: Some(cwd),
..Default::default()
},
codex_home,
)
.await?;
assert_eq!(
config.service_tier,
Some(ServiceTier::Fast.request_value().to_string())
);
Ok(())
}
#[tokio::test]
async fn fast_default_opt_out_notice_config_is_respected() -> std::io::Result<()> {
let fixture = create_test_fixture()?;
+1 -4
View File
@@ -2734,10 +2734,7 @@ impl Config {
notices.fast_default_opt_out = Some(true);
None
}
None => config_profile
.service_tier
.or(cfg.service_tier)
.map(|service_tier| service_tier.request_value().to_string()),
None => config_profile.service_tier.or(cfg.service_tier),
};
let service_tier = service_tier.and_then(|service_tier| {
match ServiceTier::from_request_value(&service_tier) {
+1 -4
View File
@@ -109,10 +109,7 @@ fn save_session_resolved_fields(sc: &SessionConfiguration, lock_config: &mut Con
lock_config.model = Some(sc.collaboration_mode.model().to_string());
lock_config.model_reasoning_effort = sc.collaboration_mode.reasoning_effort();
lock_config.model_reasoning_summary = sc.model_reasoning_summary;
lock_config.service_tier = sc
.service_tier
.as_deref()
.and_then(codex_protocol::config_types::ServiceTier::from_request_value);
lock_config.service_tier = sc.service_tier.clone();
lock_config.instructions = Some(sc.base_instructions.clone());
lock_config.developer_instructions = sc.developer_instructions.clone();
lock_config.compact_prompt = sc.compact_prompt.clone();