Persist service tier ids compatibly

This commit is contained in:
Ahmed Ibrahim
2026-05-03 07:24:43 +03:00
parent 330517a4c6
commit f0129540fe
9 changed files with 239 additions and 12 deletions
+7 -1
View File
@@ -304,7 +304,13 @@ pub struct ConfigToml {
/// Optionally specify a personality for the model
pub personality: Option<Personality>,
/// Optional explicit service tier preference for new turns (`fast` or `flex`).
/// Optional explicit service tier id preference for new turns.
///
/// Takes precedence over deprecated `service_tier`.
pub service_tier_id: Option<ServiceTier>,
/// Deprecated legacy service tier preference for new turns (`fast` or `flex`).
#[deprecated(note = "use service_tier_id instead")]
pub service_tier: Option<ServiceTier>,
/// Base URL for requests to ChatGPT (as opposed to the OpenAI API).
+6 -1
View File
@@ -23,7 +23,12 @@ use codex_protocol::protocol::AskForApproval;
#[schemars(deny_unknown_fields)]
pub struct ConfigProfile {
pub model: Option<String>,
/// Optional explicit service tier preference for new turns (`fast` or `flex`).
/// Optional explicit service tier id preference for new turns.
///
/// Takes precedence over deprecated `service_tier`.
pub service_tier_id: Option<ServiceTier>,
/// Deprecated legacy service tier preference for new turns (`fast` or `flex`).
#[deprecated(note = "use service_tier_id instead")]
pub service_tier: Option<ServiceTier>,
/// The key in the `model_providers` map identifying the
/// [`ModelProviderInfo`] to use.
+21 -3
View File
@@ -663,7 +663,16 @@
"$ref": "#/definitions/ServiceTier"
}
],
"description": "Optional explicit service tier preference for new turns (`fast` or `flex`)."
"deprecated": true,
"description": "Deprecated legacy service tier preference for new turns (`fast` or `flex`)."
},
"service_tier_id": {
"allOf": [
{
"$ref": "#/definitions/ServiceTier"
}
],
"description": "Optional explicit service tier id preference for new turns.\n\nTakes precedence over deprecated `service_tier`."
},
"tools": {
"$ref": "#/definitions/ToolsToml"
@@ -4391,7 +4400,16 @@
"$ref": "#/definitions/ServiceTier"
}
],
"description": "Optional explicit service tier preference for new turns (`fast` or `flex`)."
"deprecated": true,
"description": "Deprecated legacy service tier preference for new turns (`fast` or `flex`)."
},
"service_tier_id": {
"allOf": [
{
"$ref": "#/definitions/ServiceTier"
}
],
"description": "Optional explicit service tier id preference for new turns.\n\nTakes precedence over deprecated `service_tier`."
},
"shell_environment_policy": {
"allOf": [
@@ -4494,4 +4512,4 @@
},
"title": "ConfigToml",
"type": "object"
}
}
+53
View File
@@ -60,6 +60,9 @@ use codex_model_provider_info::LMSTUDIO_OSS_PROVIDER_ID;
use codex_model_provider_info::OLLAMA_OSS_PROVIDER_ID;
use codex_model_provider_info::WireApi;
use codex_models_manager::bundled_models_response;
use codex_protocol::config_types::SERVICE_TIER_FAST_LEGACY;
use codex_protocol::config_types::SERVICE_TIER_PRIORITY;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::models::ActivePermissionProfile;
use codex_protocol::models::ActivePermissionProfileModification;
use codex_protocol::models::ManagedFileSystemPermissions;
@@ -6506,6 +6509,56 @@ async fn explicit_null_service_tier_override_sets_fast_default_opt_out() -> std:
Ok(())
}
#[tokio::test]
async fn service_tier_id_takes_precedence_over_deprecated_service_tier() -> std::io::Result<()> {
let fixture = create_test_fixture()?;
let mut cfg = fixture.cfg.clone();
cfg.service_tier_id = Some(ServiceTier::from("premium"));
#[allow(deprecated)]
{
cfg.service_tier = Some(ServiceTier::from(SERVICE_TIER_FAST_LEGACY));
}
let config = Config::load_from_base_config_with_overrides(
cfg,
ConfigOverrides {
cwd: Some(fixture.cwd_path()),
..Default::default()
},
fixture.codex_home(),
)
.await?;
assert_eq!(config.service_tier, Some(ServiceTier::from("premium")));
Ok(())
}
#[tokio::test]
async fn deprecated_fast_service_tier_maps_to_priority_id() -> std::io::Result<()> {
let fixture = create_test_fixture()?;
let mut cfg = fixture.cfg.clone();
#[allow(deprecated)]
{
cfg.service_tier = Some(ServiceTier::from(SERVICE_TIER_FAST_LEGACY));
}
let config = Config::load_from_base_config_with_overrides(
cfg,
ConfigOverrides {
cwd: Some(fixture.cwd_path()),
..Default::default()
},
fixture.codex_home(),
)
.await?;
assert_eq!(
config.service_tier,
Some(ServiceTier::from(SERVICE_TIER_PRIORITY))
);
Ok(())
}
#[tokio::test]
async fn fast_default_opt_out_notice_config_is_respected() -> std::io::Result<()> {
let fixture = create_test_fixture()?;
+40 -6
View File
@@ -6,6 +6,9 @@ use codex_config::types::McpServerConfig;
use codex_config::types::ToolSuggestDisabledTool;
use codex_features::FEATURES;
use codex_protocol::config_types::Personality;
use codex_protocol::config_types::SERVICE_TIER_FAST_LEGACY;
use codex_protocol::config_types::SERVICE_TIER_FLEX;
use codex_protocol::config_types::SERVICE_TIER_PRIORITY;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::config_types::TrustLevel;
use codex_protocol::openai_models::ReasoningEffort;
@@ -525,12 +528,43 @@ impl ConfigDocument {
);
mutated
}),
ConfigEdit::SetServiceTier { service_tier } => Ok(self.write_profile_value(
&["service_tier"],
service_tier
.as_ref()
.map(|service_tier| value(service_tier.to_string())),
)),
ConfigEdit::SetServiceTier { service_tier } => {
let mut mutated = false;
match service_tier.as_ref().map(AsRef::as_ref) {
Some(service_tier_id) if service_tier_id == SERVICE_TIER_PRIORITY => {
mutated |= self.write_profile_value(
&["service_tier_id"],
Some(value(SERVICE_TIER_PRIORITY)),
);
mutated |= self.write_profile_value(
&["service_tier"],
Some(value(SERVICE_TIER_FAST_LEGACY)),
);
}
Some(service_tier_id) if service_tier_id == SERVICE_TIER_FLEX => {
mutated |= self.write_profile_value(
&["service_tier_id"],
Some(value(SERVICE_TIER_FLEX)),
);
mutated |= self
.write_profile_value(&["service_tier"], Some(value(SERVICE_TIER_FLEX)));
}
Some(_) => {
mutated |= self.write_profile_value(
&["service_tier_id"],
service_tier
.as_ref()
.map(|service_tier| value(service_tier.to_string())),
);
mutated |= self.write_profile_value(&["service_tier"], None);
}
None => {
mutated |= self.write_profile_value(&["service_tier_id"], None);
mutated |= self.write_profile_value(&["service_tier"], None);
}
}
Ok(mutated)
}
ConfigEdit::SetModelPersonality { personality } => Ok(self.write_profile_value(
&["personality"],
personality.map(|personality| value(personality.to_string())),
+86
View File
@@ -2,6 +2,9 @@ use super::*;
use codex_config::types::AppToolApproval;
use codex_config::types::McpServerToolConfig;
use codex_config::types::McpServerTransportConfig;
use codex_protocol::config_types::SERVICE_TIER_FLEX;
use codex_protocol::config_types::SERVICE_TIER_PRIORITY;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::openai_models::ReasoningEffort;
use pretty_assertions::assert_eq;
#[cfg(unix)]
@@ -31,6 +34,89 @@ model_reasoning_effort = "high"
assert_eq!(contents, expected);
}
#[test]
fn set_service_tier_writes_legacy_fast_for_priority_id() {
let tmp = tempdir().expect("tmpdir");
let codex_home = tmp.path();
std::fs::write(
codex_home.join(CONFIG_TOML_FILE),
r#"service_tier_id = "premium"
"#,
)
.expect("seed config");
ConfigEditsBuilder::new(codex_home)
.set_service_tier(Some(SERVICE_TIER_PRIORITY.into()))
.apply_blocking()
.expect("persist");
let contents = std::fs::read_to_string(codex_home.join(CONFIG_TOML_FILE)).expect("read config");
let expected = r#"service_tier_id = "priority"
service_tier = "fast"
"#;
assert_eq!(contents, expected);
}
#[test]
fn set_service_tier_writes_both_keys_for_flex_id() {
let tmp = tempdir().expect("tmpdir");
let codex_home = tmp.path();
ConfigEditsBuilder::new(codex_home)
.set_service_tier(Some(SERVICE_TIER_FLEX.into()))
.apply_blocking()
.expect("persist");
let contents = std::fs::read_to_string(codex_home.join(CONFIG_TOML_FILE)).expect("read config");
let expected = r#"service_tier_id = "flex"
service_tier = "flex"
"#;
assert_eq!(contents, expected);
}
#[test]
fn set_service_tier_writes_service_tier_id_for_arbitrary_id() {
let tmp = tempdir().expect("tmpdir");
let codex_home = tmp.path();
std::fs::write(
codex_home.join(CONFIG_TOML_FILE),
r#"service_tier = "fast"
"#,
)
.expect("seed config");
ConfigEditsBuilder::new(codex_home)
.set_service_tier(Some(ServiceTier::from("premium")))
.apply_blocking()
.expect("persist");
let contents = std::fs::read_to_string(codex_home.join(CONFIG_TOML_FILE)).expect("read config");
let expected = r#"service_tier_id = "premium"
"#;
assert_eq!(contents, expected);
}
#[test]
fn clear_service_tier_removes_legacy_and_id_keys() {
let tmp = tempdir().expect("tmpdir");
let codex_home = tmp.path();
std::fs::write(
codex_home.join(CONFIG_TOML_FILE),
r#"service_tier_id = "premium"
service_tier = "flex"
"#,
)
.expect("seed config");
ConfigEditsBuilder::new(codex_home)
.set_service_tier(None)
.apply_blocking()
.expect("persist");
let contents = std::fs::read_to_string(codex_home.join(CONFIG_TOML_FILE)).expect("read config");
assert_eq!(contents, "");
}
#[test]
fn builder_with_edits_applies_custom_paths() {
let tmp = tempdir().expect("tmpdir");
+22 -1
View File
@@ -81,6 +81,7 @@ use codex_protocol::config_types::AltScreenMode;
use codex_protocol::config_types::ForcedLoginMethod;
use codex_protocol::config_types::Personality;
use codex_protocol::config_types::ReasoningSummary;
use codex_protocol::config_types::SERVICE_TIER_FAST_LEGACY;
use codex_protocol::config_types::SERVICE_TIER_PRIORITY;
use codex_protocol::config_types::SandboxMode;
use codex_protocol::config_types::ServiceTier;
@@ -1991,6 +1992,14 @@ pub(crate) fn resolve_web_search_mode_for_turn(
WebSearchMode::Disabled
}
fn normalize_deprecated_service_tier(service_tier: ServiceTier) -> ServiceTier {
if service_tier.as_ref() == SERVICE_TIER_FAST_LEGACY {
SERVICE_TIER_PRIORITY.into()
} else {
service_tier
}
}
impl Config {
#[cfg(test)]
async fn load_from_base_config_with_overrides(
@@ -2649,6 +2658,14 @@ impl Config {
let model = model.or(config_profile.model).or(cfg.model);
let mut notices = cfg.notice.unwrap_or_default();
#[allow(deprecated)]
let deprecated_profile_service_tier = config_profile
.service_tier
.clone()
.map(normalize_deprecated_service_tier);
#[allow(deprecated)]
let deprecated_config_service_tier =
cfg.service_tier.clone().map(normalize_deprecated_service_tier);
let service_tier = match service_tier_override {
Some(Some(service_tier)) => Some(service_tier),
Some(None) => {
@@ -2657,7 +2674,11 @@ impl Config {
notices.fast_default_opt_out = Some(true);
None
}
None => config_profile.service_tier.or(cfg.service_tier),
None => config_profile
.service_tier_id
.or(cfg.service_tier_id)
.or(deprecated_profile_service_tier)
.or(deprecated_config_service_tier),
};
let service_tier = match service_tier {
Some(service_tier)
+3
View File
@@ -349,6 +349,9 @@ impl From<WebSearchToolConfig> for WebSearchConfig {
pub const SERVICE_TIER_PRIORITY: &str = "priority";
pub const SERVICE_TIER_FLEX: &str = "flex";
/// Legacy config value accepted by older clients. New clients map this to
/// `priority` when reading deprecated `service_tier`.
pub const SERVICE_TIER_FAST_LEGACY: &str = "fast";
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, TS)]
#[serde(transparent)]
+1
View File
@@ -17,6 +17,7 @@ use ts_rs::TS;
use crate::config_types::Personality;
use crate::config_types::ReasoningSummary;
#[cfg(test)]
use crate::config_types::SERVICE_TIER_PRIORITY;
use crate::config_types::ServiceTier;
use crate::config_types::Verbosity;