2- Use string service tiers in session protocol (#20971)

## Summary
- break service tier session/op/app-server protocol fields from the
closed enum to string tier ids
- send the service tier string directly through model requests, prewarm,
compaction, memories, and TUI/app-server turn starts
- regenerate app-server protocol JSON/TypeScript schemas, removing the
standalone ServiceTier TS enum

## Verification
- just fmt
- cargo check -p codex-core -p codex-app-server -p codex-tui
- just write-app-server-schema

---------

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Ahmed Ibrahim
2026-05-06 18:00:21 +03:00
committed by GitHub
Unverified
parent ebd9ec05b4
commit be1d3cff93
65 changed files with 403 additions and 570 deletions
+5 -2
View File
@@ -109,7 +109,10 @@ 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;
lock_config.service_tier = sc
.service_tier
.as_deref()
.and_then(codex_protocol::config_types::ServiceTier::from_request_value);
lock_config.instructions = Some(sc.base_instructions.clone());
lock_config.developer_instructions = sc.developer_instructions.clone();
lock_config.compact_prompt = sc.compact_prompt.clone();
@@ -280,7 +283,7 @@ mod tests {
sc.base_instructions = "catalog instructions".to_string();
sc.developer_instructions = Some("catalog developer instructions".to_string());
sc.compact_prompt = Some("catalog compact prompt".to_string());
sc.service_tier = Some(codex_protocol::config_types::ServiceTier::Flex);
sc.service_tier = Some("flex".to_string());
let lockfile = sc.to_config_lockfile_toml().expect("lock should serialize");
let lock = &lockfile.config;
+4 -4
View File
@@ -588,7 +588,7 @@ impl Codex {
.auth_cached()
.and_then(|auth| auth.account_plan_type());
let service_tier = get_service_tier(
config.service_tier,
config.service_tier.clone(),
config.notices.fast_default_opt_out.unwrap_or(false),
account_plan_type,
config.features.enabled(Feature::FastMode),
@@ -787,18 +787,18 @@ impl Codex {
}
fn get_service_tier(
configured_service_tier: Option<ServiceTier>,
configured_service_tier: Option<String>,
fast_default_opt_out: bool,
account_plan_type: Option<AccountPlanType>,
fast_mode_enabled: bool,
) -> Option<ServiceTier> {
) -> Option<String> {
if configured_service_tier.is_some() || fast_default_opt_out || !fast_mode_enabled {
return configured_service_tier;
}
account_plan_type
.is_some_and(is_enterprise_default_service_tier_plan)
.then_some(ServiceTier::Fast)
.then_some(ServiceTier::Fast.request_value().to_string())
}
fn is_enterprise_default_service_tier_plan(plan_type: AccountPlanType) -> bool {
+14 -6
View File
@@ -1,6 +1,7 @@
use super::*;
use crate::goals::GoalRuntimeState;
use codex_protocol::SessionId;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::permissions::FileSystemPath;
use codex_protocol::permissions::FileSystemSpecialPath;
use codex_protocol::protocol::ThreadSource;
@@ -42,7 +43,7 @@ pub(crate) struct SessionConfiguration {
pub(super) collaboration_mode: CollaborationMode,
pub(super) model_reasoning_summary: Option<ReasoningSummaryConfig>,
pub(super) service_tier: Option<ServiceTier>,
pub(super) service_tier: Option<String>,
/// Developer instructions that supplement the base instructions.
pub(super) developer_instructions: Option<String>,
@@ -136,7 +137,7 @@ impl SessionConfiguration {
ThreadConfigSnapshot {
model: self.collaboration_mode.model().to_string(),
model_provider_id: self.original_config_do_not_use.model_provider_id.clone(),
service_tier: self.service_tier,
service_tier: self.service_tier.clone(),
approval_policy: self.approval_policy.value(),
approvals_reviewer: self.approvals_reviewer,
permission_profile: self.permission_profile(),
@@ -182,8 +183,15 @@ impl SessionConfiguration {
if let Some(summary) = updates.reasoning_summary {
next_configuration.model_reasoning_summary = Some(summary);
}
if let Some(service_tier) = updates.service_tier {
next_configuration.service_tier = service_tier;
if let Some(service_tier) = updates.service_tier.clone() {
// TODO(aibrahim): Remove once v2 clients no longer send the legacy
// "fast" service tier value.
next_configuration.service_tier = service_tier.map(|service_tier| {
ServiceTier::from_request_value(&service_tier)
.map_or(service_tier, |service_tier| {
service_tier.request_value().to_string()
})
});
}
if let Some(personality) = updates.personality {
next_configuration.personality = Some(personality);
@@ -309,7 +317,7 @@ pub(crate) struct SessionSettingsUpdate {
pub(crate) windows_sandbox_level: Option<WindowsSandboxLevel>,
pub(crate) collaboration_mode: Option<CollaborationMode>,
pub(crate) reasoning_summary: Option<ReasoningSummaryConfig>,
pub(crate) service_tier: Option<Option<ServiceTier>>,
pub(crate) service_tier: Option<Option<String>>,
pub(crate) final_output_json_schema: Option<Option<Value>>,
/// Turn-local environment override. `None` inherits the sticky thread
/// environments stored on `SessionConfiguration`; `Some([])` explicitly
@@ -905,7 +913,7 @@ impl Session {
thread_name: session_configuration.thread_name.clone(),
model: session_configuration.collaboration_mode.model().to_string(),
model_provider_id: config.model_provider_id.clone(),
service_tier: session_configuration.service_tier,
service_tier: session_configuration.service_tier.clone(),
approval_policy: session_configuration.approval_policy.value(),
approvals_reviewer: session_configuration.approvals_reviewer,
permission_profile: session_configuration.permission_profile(),
+22 -5
View File
@@ -2891,7 +2891,7 @@ fn get_service_tier_defaults_enterprise_accounts_to_fast() {
Some(AccountPlanType::Enterprise),
/*fast_mode_enabled*/ true,
),
Some(ServiceTier::Fast)
Some(ServiceTier::Fast.request_value().to_string())
);
assert_eq!(
get_service_tier(
@@ -2900,7 +2900,7 @@ fn get_service_tier_defaults_enterprise_accounts_to_fast() {
Some(AccountPlanType::EnterpriseCbpUsageBased),
/*fast_mode_enabled*/ true,
),
Some(ServiceTier::Fast)
Some(ServiceTier::Fast.request_value().to_string())
);
assert_eq!(
get_service_tier(
@@ -2909,7 +2909,7 @@ fn get_service_tier_defaults_enterprise_accounts_to_fast() {
Some(AccountPlanType::Business),
/*fast_mode_enabled*/ true,
),
Some(ServiceTier::Fast)
Some(ServiceTier::Fast.request_value().to_string())
);
assert_eq!(
get_service_tier(
@@ -2918,7 +2918,7 @@ fn get_service_tier_defaults_enterprise_accounts_to_fast() {
Some(AccountPlanType::Team),
/*fast_mode_enabled*/ true,
),
Some(ServiceTier::Fast)
Some(ServiceTier::Fast.request_value().to_string())
);
assert_eq!(
get_service_tier(
@@ -2927,7 +2927,7 @@ fn get_service_tier_defaults_enterprise_accounts_to_fast() {
Some(AccountPlanType::SelfServeBusinessUsageBased),
/*fast_mode_enabled*/ true,
),
Some(ServiceTier::Fast)
Some(ServiceTier::Fast.request_value().to_string())
);
}
@@ -2980,6 +2980,23 @@ async fn session_settings_null_service_tier_update_clears_service_tier() {
assert_eq!(updated.service_tier, None);
}
#[tokio::test]
async fn session_settings_legacy_fast_service_tier_update_uses_priority_request_value() {
let session_configuration = make_session_configuration_for_tests().await;
let updated = session_configuration
.apply(&SessionSettingsUpdate {
service_tier: Some(Some("fast".to_string())),
..Default::default()
})
.expect("legacy fast service tier update should apply");
assert_eq!(
updated.service_tier,
Some(ServiceTier::Fast.request_value().to_string())
);
}
pub(crate) async fn make_session_configuration_for_tests() -> SessionConfiguration {
let codex_home = tempfile::tempdir().expect("create temp dir");
let config = build_test_config(codex_home.path()).await;
+7 -2
View File
@@ -73,6 +73,7 @@ use codex_hooks::HookEventAfterAgent;
use codex_hooks::HookPayload;
use codex_hooks::HookResult;
use codex_protocol::config_types::ModeKind;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::error::CodexErr;
use codex_protocol::error::Result as CodexResult;
use codex_protocol::items::PlanItem;
@@ -693,7 +694,11 @@ async fn track_turn_resolved_config_analytics(
permission_profile_cwd: turn_context.cwd.to_path_buf(),
reasoning_effort: turn_context.reasoning_effort,
reasoning_summary: Some(turn_context.reasoning_summary),
service_tier: turn_context.config.service_tier,
service_tier: turn_context
.config
.service_tier
.as_deref()
.and_then(ServiceTier::from_request_value),
approval_policy: turn_context.approval_policy.value(),
approvals_reviewer: turn_context.config.approvals_reviewer,
sandbox_network_access: turn_context.network_sandbox_policy().is_enabled(),
@@ -1858,7 +1863,7 @@ async fn try_run_sampling_request(
&turn_context.session_telemetry,
turn_context.reasoning_effort,
turn_context.reasoning_summary,
turn_context.config.service_tier,
turn_context.config.service_tier.clone(),
turn_metadata_header,
&inference_trace,
)
+1 -1
View File
@@ -415,7 +415,7 @@ impl Session {
per_turn_config.model_reasoning_effort =
session_configuration.collaboration_mode.reasoning_effort();
per_turn_config.model_reasoning_summary = session_configuration.model_reasoning_summary;
per_turn_config.service_tier = session_configuration.service_tier;
per_turn_config.service_tier = session_configuration.service_tier.clone();
per_turn_config.personality = session_configuration.personality;
per_turn_config.approvals_reviewer = session_configuration.approvals_reviewer;
per_turn_config.permissions.permission_profile =