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
+2 -1
View File
@@ -1263,7 +1263,8 @@ impl App {
AppEvent::PersistServiceTierSelection { service_tier } => {
self.refresh_status_line();
let profile = self.active_profile.as_deref();
self.config.service_tier = service_tier;
self.config.service_tier =
service_tier.map(|service_tier| service_tier.request_value().to_string());
let mut edits = ConfigEditsBuilder::new(&self.config.codex_home)
.with_profile(profile)
.set_service_tier(service_tier);
+4 -1
View File
@@ -617,7 +617,10 @@ impl App {
pub(super) fn fresh_session_config(&self) -> Config {
let mut config = self.config.clone();
config.service_tier = self.chat_widget.configured_service_tier();
config.service_tier = self
.chat_widget
.configured_service_tier()
.map(|service_tier| service_tier.request_value().to_string());
config.notices.fast_default_opt_out = self.chat_widget.fast_default_opt_out();
config
}
+5 -1
View File
@@ -4496,7 +4496,11 @@ async fn fresh_session_config_uses_current_service_tier() {
assert_eq!(
config.service_tier,
Some(codex_protocol::config_types::ServiceTier::Fast)
Some(
codex_protocol::config_types::ServiceTier::Fast
.request_value()
.to_string()
)
);
}
+1 -1
View File
@@ -623,7 +623,7 @@ impl App {
model.to_string(),
*effort,
*summary,
*service_tier,
service_tier.clone(),
collaboration_mode.clone(),
*personality,
final_output_json_schema.clone(),
+4 -1
View File
@@ -63,7 +63,10 @@ impl App {
thread_name: None,
model: self.chat_widget.current_model().to_string(),
model_provider_id: self.config.model_provider_id.clone(),
service_tier: self.chat_widget.current_service_tier(),
service_tier: self
.chat_widget
.current_service_tier()
.map(|service_tier| service_tier.request_value().to_string()),
approval_policy: AskForApproval::from(
self.config.permissions.approval_policy.value(),
),
+4 -5
View File
@@ -15,7 +15,6 @@ use codex_protocol::approvals::GuardianAssessmentEvent;
use codex_protocol::config_types::CollaborationMode;
use codex_protocol::config_types::Personality;
use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::PermissionProfile;
use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;
@@ -46,7 +45,7 @@ pub(crate) enum AppCommand {
model: String,
effort: Option<ReasoningEffortConfig>,
summary: Option<ReasoningSummaryConfig>,
service_tier: Option<Option<ServiceTier>>,
service_tier: Option<Option<String>>,
final_output_json_schema: Option<Value>,
collaboration_mode: Option<CollaborationMode>,
personality: Option<Personality>,
@@ -60,7 +59,7 @@ pub(crate) enum AppCommand {
model: Option<String>,
effort: Option<Option<ReasoningEffortConfig>>,
summary: Option<ReasoningSummaryConfig>,
service_tier: Option<Option<ServiceTier>>,
service_tier: Option<Option<String>>,
collaboration_mode: Option<CollaborationMode>,
personality: Option<Personality>,
},
@@ -154,7 +153,7 @@ impl AppCommand {
model: String,
effort: Option<ReasoningEffortConfig>,
summary: Option<ReasoningSummaryConfig>,
service_tier: Option<Option<ServiceTier>>,
service_tier: Option<Option<String>>,
final_output_json_schema: Option<Value>,
collaboration_mode: Option<CollaborationMode>,
personality: Option<Personality>,
@@ -185,7 +184,7 @@ impl AppCommand {
model: Option<String>,
effort: Option<Option<ReasoningEffortConfig>>,
summary: Option<ReasoningSummaryConfig>,
service_tier: Option<Option<ServiceTier>>,
service_tier: Option<Option<String>>,
collaboration_mode: Option<CollaborationMode>,
personality: Option<Personality>,
) -> Self {
+5 -5
View File
@@ -531,7 +531,7 @@ impl AppServerSession {
model: String,
effort: Option<codex_protocol::openai_models::ReasoningEffort>,
summary: Option<codex_protocol::config_types::ReasoningSummary>,
service_tier: Option<Option<codex_protocol::config_types::ServiceTier>>,
service_tier: Option<Option<String>>,
collaboration_mode: Option<codex_protocol::config_types::CollaborationMode>,
personality: Option<codex_protocol::config_types::Personality>,
output_schema: Option<serde_json::Value>,
@@ -1344,7 +1344,7 @@ async fn thread_session_state_from_thread_start_response(
response.thread.path.clone(),
response.model.clone(),
response.model_provider.clone(),
response.service_tier,
response.service_tier.clone(),
response.approval_policy,
response.approvals_reviewer.to_core(),
permission_profile,
@@ -1376,7 +1376,7 @@ async fn thread_session_state_from_thread_resume_response(
response.thread.path.clone(),
response.model.clone(),
response.model_provider.clone(),
response.service_tier,
response.service_tier.clone(),
response.approval_policy,
response.approvals_reviewer.to_core(),
permission_profile,
@@ -1408,7 +1408,7 @@ async fn thread_session_state_from_thread_fork_response(
response.thread.path.clone(),
response.model.clone(),
response.model_provider.clone(),
response.service_tier,
response.service_tier.clone(),
response.approval_policy,
response.approvals_reviewer.to_core(),
permission_profile,
@@ -1450,7 +1450,7 @@ async fn thread_session_state_from_thread_response(
rollout_path: Option<PathBuf>,
model: String,
model_provider_id: String,
service_tier: Option<codex_protocol::config_types::ServiceTier>,
service_tier: Option<String>,
approval_policy: AskForApproval,
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer,
permission_profile: PermissionProfile,
+17 -7
View File
@@ -2066,7 +2066,10 @@ impl ChatWidget {
self.current_rollout_path = session.rollout_path.clone();
self.current_cwd = Some(session.cwd.to_path_buf());
self.config.cwd = session.cwd.clone();
self.effective_service_tier = session.service_tier;
self.effective_service_tier = session
.service_tier
.as_deref()
.and_then(ServiceTier::from_request_value);
if let Err(err) = self
.config
.permissions
@@ -2115,7 +2118,7 @@ impl ChatWidget {
if display == SessionConfiguredDisplay::Normal {
let startup_tooltip_override = self.startup_tooltip_override.take();
let show_fast_status =
self.should_show_fast_status(&model_for_header, session.service_tier);
self.should_show_fast_status(&model_for_header, self.effective_service_tier);
let session_info_cell = history_cell::new_session_info(
&self.config,
&model_for_header,
@@ -4880,7 +4883,10 @@ impl ChatWidget {
let active_cell = Some(Self::placeholder_session_header_cell(&config));
let current_cwd = Some(config.cwd.to_path_buf());
let effective_service_tier = config.service_tier;
let effective_service_tier = config
.service_tier
.as_deref()
.and_then(ServiceTier::from_request_value);
let current_terminal_info = terminal_info();
let runtime_keymap = RuntimeKeymap::from_config(&config.tui_keymap).ok();
let default_keymap = RuntimeKeymap::defaults();
@@ -5865,7 +5871,7 @@ impl ChatWidget {
.personality
.filter(|_| self.config.features.enabled(Feature::Personality))
.filter(|_| self.current_model_supports_personality());
let service_tier = match self.config.service_tier {
let service_tier = match self.config.service_tier.clone() {
Some(service_tier) => Some(Some(service_tier)),
None if self.config.notices.fast_default_opt_out == Some(true) => Some(None),
None => None,
@@ -9277,7 +9283,8 @@ impl ChatWidget {
/// Set Fast mode in the widget's config copy.
pub(crate) fn set_service_tier(&mut self, service_tier: Option<ServiceTier>) {
self.config.service_tier = service_tier;
self.config.service_tier =
service_tier.map(|service_tier| service_tier.request_value().to_string());
self.effective_service_tier = service_tier;
}
@@ -9286,7 +9293,10 @@ impl ChatWidget {
}
pub(crate) fn configured_service_tier(&self) -> Option<ServiceTier> {
self.config.service_tier
self.config
.service_tier
.as_deref()
.and_then(ServiceTier::from_request_value)
}
pub(crate) fn fast_default_opt_out(&self) -> Option<bool> {
@@ -9393,7 +9403,7 @@ impl ChatWidget {
/*model*/ None,
/*effort*/ None,
/*summary*/ None,
Some(service_tier),
Some(service_tier.map(|service_tier| service_tier.request_value().to_string())),
/*collaboration_mode*/ None,
/*personality*/ None,
)));
+4 -1
View File
@@ -182,7 +182,10 @@ pub(super) async fn make_chatwidget_manual(
};
let current_collaboration_mode = base_mode;
let active_collaboration_mask = collaboration_modes::default_mask(model_catalog.as_ref());
let effective_service_tier = cfg.service_tier;
let effective_service_tier = cfg
.service_tier
.as_deref()
.and_then(ServiceTier::from_request_value);
let mut widget = ChatWidget {
app_event_tx,
codex_op_target: super::CodexOpTarget::Direct(op_tx),
@@ -1803,9 +1803,9 @@ async fn fast_slash_command_updates_and_persists_local_service_tier() {
events.iter().any(|event| matches!(
event,
AppEvent::CodexOp(Op::OverrideTurnContext {
service_tier: Some(Some(ServiceTier::Fast)),
service_tier: Some(Some(service_tier)),
..
})
}) if service_tier == ServiceTier::Fast.request_value()
)),
"expected fast-mode override app event; events: {events:?}"
);
@@ -1834,9 +1834,9 @@ async fn fast_keybinding_toggle_uses_same_events_as_fast_slash_command() {
events.iter().any(|event| matches!(
event,
AppEvent::CodexOp(Op::OverrideTurnContext {
service_tier: Some(Some(ServiceTier::Fast)),
service_tier: Some(Some(service_tier)),
..
})
}) if service_tier == ServiceTier::Fast.request_value()
)),
"expected fast-mode override app event; events: {events:?}"
);
@@ -1884,9 +1884,9 @@ async fn user_turn_carries_service_tier_after_fast_toggle() {
match next_submit_op(&mut op_rx) {
Op::UserTurn {
service_tier: Some(Some(ServiceTier::Fast)),
service_tier: Some(Some(service_tier)),
..
} => {}
} if service_tier == ServiceTier::Fast.request_value() => {}
other => panic!("expected Op::UserTurn with fast service tier, got {other:?}"),
}
}
@@ -1909,9 +1909,9 @@ async fn queued_fast_slash_applies_before_next_queued_message() {
events.iter().any(|event| matches!(
event,
AppEvent::CodexOp(Op::OverrideTurnContext {
service_tier: Some(Some(ServiceTier::Fast)),
service_tier: Some(Some(service_tier)),
..
})
}) if service_tier == ServiceTier::Fast.request_value()
)),
"expected queued /fast to update service tier before next turn; events: {events:?}"
);
@@ -1919,9 +1919,9 @@ async fn queued_fast_slash_applies_before_next_queued_message() {
match next_submit_op(&mut op_rx) {
Op::UserTurn {
items,
service_tier: Some(Some(ServiceTier::Fast)),
service_tier: Some(Some(service_tier)),
..
} => assert_eq!(
} if service_tier == ServiceTier::Fast.request_value() => assert_eq!(
items,
vec![UserInput::Text {
text: "hello after fast".to_string(),
+1 -1
View File
@@ -25,7 +25,7 @@ pub(crate) struct ThreadSessionState {
pub(crate) thread_name: Option<String>,
pub(crate) model: String,
pub(crate) model_provider_id: String,
pub(crate) service_tier: Option<codex_protocol::config_types::ServiceTier>,
pub(crate) service_tier: Option<String>,
pub(crate) approval_policy: AskForApproval,
pub(crate) approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer,
/// Canonical active permissions for this session. Legacy app-server