Move service tier helpers out of data types

This commit is contained in:
Ahmed Ibrahim
2026-05-03 07:24:16 +03:00
parent e9a3e91f3c
commit 7ee21cc692
15 changed files with 109 additions and 94 deletions
+17 -23
View File
@@ -30,6 +30,10 @@ use codex_protocol::config_types::ServiceTier as CoreServiceTier;
use codex_protocol::config_types::Verbosity;
use codex_protocol::config_types::WebSearchMode;
use codex_protocol::config_types::WebSearchToolConfig;
use codex_protocol::config_types::flex_service_tier;
use codex_protocol::config_types::is_flex_service_tier;
use codex_protocol::config_types::is_priority_service_tier;
use codex_protocol::config_types::priority_service_tier;
use codex_protocol::items::AgentMessageContent as CoreAgentMessageContent;
use codex_protocol::items::TurnItem as CoreTurnItem;
use codex_protocol::mcp::CallToolResult as CoreMcpCallToolResult;
@@ -129,36 +133,26 @@ pub enum ServiceTier {
Flex,
}
impl ServiceTier {
pub fn fast() -> Self {
Self::Fast
pub fn service_tier_to_core(service_tier: ServiceTier) -> CoreServiceTier {
match service_tier {
ServiceTier::Fast => priority_service_tier(),
ServiceTier::Flex => flex_service_tier(),
}
}
pub fn flex() -> Self {
Self::Flex
}
pub fn to_core(self) -> CoreServiceTier {
match self {
Self::Fast => CoreServiceTier::priority(),
Self::Flex => CoreServiceTier::flex(),
}
}
pub fn from_core(service_tier: &CoreServiceTier) -> Option<Self> {
if service_tier.is_priority() {
Some(Self::Fast)
} else if service_tier.is_flex() {
Some(Self::Flex)
} else {
None
}
pub fn service_tier_from_core(service_tier: &CoreServiceTier) -> Option<ServiceTier> {
if is_priority_service_tier(service_tier) {
Some(ServiceTier::Fast)
} else if is_flex_service_tier(service_tier) {
Some(ServiceTier::Flex)
} else {
None
}
}
impl From<ServiceTier> for CoreServiceTier {
fn from(value: ServiceTier) -> Self {
value.to_core()
service_tier_to_core(value)
}
}
@@ -241,6 +241,8 @@ use codex_app_server_protocol::WindowsSandboxSetupMode;
use codex_app_server_protocol::WindowsSandboxSetupStartParams;
use codex_app_server_protocol::WindowsSandboxSetupStartResponse;
use codex_app_server_protocol::build_turns_from_rollout_items;
use codex_app_server_protocol::service_tier_from_core;
use codex_app_server_protocol::service_tier_to_core;
use codex_arg0::Arg0DispatchPaths;
use codex_backend_client::AddCreditsNudgeCreditType as BackendAddCreditsNudgeCreditType;
use codex_backend_client::Client as BackendClient;
@@ -2920,7 +2922,10 @@ impl CodexMessageProcessor {
thread: thread.clone(),
model: config_snapshot.model,
model_provider: config_snapshot.model_provider_id,
service_tier: config_snapshot.service_tier.as_ref().and_then(ServiceTier::from_core),
service_tier: config_snapshot
.service_tier
.as_ref()
.and_then(service_tier_from_core),
cwd: config_snapshot.cwd,
instruction_sources,
approval_policy: config_snapshot.approval_policy.into(),
@@ -3023,7 +3028,7 @@ impl CodexMessageProcessor {
fn resolve_service_tier_override(
service_tier: Option<Option<ServiceTier>>,
) -> Option<Option<codex_protocol::config_types::ServiceTier>> {
service_tier.map(|service_tier| service_tier.map(ServiceTier::to_core))
service_tier.map(|service_tier| service_tier.map(service_tier_to_core))
}
async fn thread_archive(&self, request_id: ConnectionRequestId, params: ThreadArchiveParams) {
@@ -4509,7 +4514,7 @@ impl CodexMessageProcessor {
service_tier: session_configured
.service_tier
.as_ref()
.and_then(ServiceTier::from_core),
.and_then(service_tier_from_core),
cwd: session_configured.cwd,
instruction_sources,
approval_policy: session_configured.approval_policy.into(),
@@ -5147,7 +5152,7 @@ impl CodexMessageProcessor {
service_tier: session_configured
.service_tier
.as_ref()
.and_then(ServiceTier::from_core),
.and_then(service_tier_from_core),
cwd: session_configured.cwd,
instruction_sources,
approval_policy: session_configured.approval_policy.into(),
@@ -8505,7 +8510,7 @@ async fn handle_pending_thread_resume_request(
thread,
model,
model_provider: model_provider_id,
service_tier: service_tier.as_ref().and_then(ServiceTier::from_core),
service_tier: service_tier.as_ref().and_then(service_tier_from_core),
cwd,
instruction_sources,
approval_policy: approval_policy.into(),
@@ -8675,13 +8680,13 @@ fn collect_resume_override_mismatches(
));
}
if let Some(requested_service_tier) = request.service_tier.as_ref()
&& requested_service_tier.map(ServiceTier::to_core).as_ref()
&& requested_service_tier.map(service_tier_to_core).as_ref()
!= config_snapshot.service_tier.as_ref()
{
let active_service_tier = config_snapshot
.service_tier
.as_ref()
.and_then(ServiceTier::from_core);
.and_then(service_tier_from_core);
mismatch_details.push(format!(
"service_tier requested={requested_service_tier:?} active={active_service_tier:?}"
));
@@ -10065,7 +10070,7 @@ mod tests {
use codex_model_provider_info::ModelProviderInfo;
use codex_model_provider_info::WireApi;
use codex_protocol::ThreadId;
use codex_protocol::config_types::ServiceTier as CoreServiceTier;
use codex_protocol::config_types::flex_service_tier;
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::permissions::FileSystemAccessMode;
use codex_protocol::permissions::FileSystemPath;
@@ -10546,7 +10551,7 @@ mod tests {
path: None,
model: None,
model_provider: None,
service_tier: Some(Some(ServiceTier::fast())),
service_tier: Some(Some(ServiceTier::Fast)),
cwd: None,
approval_policy: None,
approvals_reviewer: None,
@@ -10562,7 +10567,7 @@ mod tests {
let config_snapshot = ThreadConfigSnapshot {
model: "gpt-5".to_string(),
model_provider_id: "openai".to_string(),
service_tier: Some(CoreServiceTier::flex()),
service_tier: Some(flex_service_tier()),
approval_policy: codex_protocol::protocol::AskForApproval::OnRequest,
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer::User,
permission_profile: codex_protocol::models::PermissionProfile::Disabled,
+2 -1
View File
@@ -5,6 +5,7 @@ use codex_app_server_protocol::ModelUpgradeInfo;
use codex_app_server_protocol::ReasoningEffortOption;
use codex_core::ThreadManager;
use codex_models_manager::manager::RefreshStrategy;
use codex_protocol::config_types::is_priority_service_tier;
use codex_protocol::openai_models::ModelPreset;
use codex_protocol::openai_models::ModelServiceTier as CoreModelServiceTier;
use codex_protocol::openai_models::ReasoningEffortPreset;
@@ -52,7 +53,7 @@ fn model_from_preset(preset: ModelPreset) -> Model {
fn legacy_additional_speed_tiers(service_tiers: &[CoreModelServiceTier]) -> Vec<String> {
service_tiers
.iter()
.filter_map(|tier| tier.id.is_priority().then_some("fast".to_string()))
.filter_map(|tier| is_priority_service_tier(&tier.id).then_some("fast".to_string()))
.collect()
}
@@ -12,6 +12,7 @@ use codex_app_server_protocol::ModelListResponse;
use codex_app_server_protocol::ModelUpgradeInfo;
use codex_app_server_protocol::ReasoningEffortOption;
use codex_app_server_protocol::RequestId;
use codex_protocol::config_types::is_priority_service_tier;
use codex_protocol::openai_models::ModelPreset;
use codex_protocol::openai_models::ModelServiceTier as CoreModelServiceTier;
use pretty_assertions::assert_eq;
@@ -59,7 +60,7 @@ fn model_from_preset(preset: &ModelPreset) -> Model {
fn legacy_additional_speed_tiers(service_tiers: &[CoreModelServiceTier]) -> Vec<String> {
service_tiers
.iter()
.filter_map(|tier| tier.id.is_priority().then_some("fast".to_string()))
.filter_map(|tier| is_priority_service_tier(&tier.id).then_some("fast".to_string()))
.collect()
}
@@ -340,7 +340,7 @@ async fn thread_start_accepts_flex_service_tier() -> Result<()> {
let req_id = mcp
.send_thread_start_request(ThreadStartParams {
service_tier: Some(Some(ServiceTier::flex())),
service_tier: Some(Some(ServiceTier::Flex)),
..Default::default()
})
.await?;
@@ -352,7 +352,7 @@ async fn thread_start_accepts_flex_service_tier() -> Result<()> {
.await??;
let ThreadStartResponse { service_tier, .. } = to_response::<ThreadStartResponse>(resp)?;
assert_eq!(service_tier, Some(ServiceTier::flex()));
assert_eq!(service_tier, Some(ServiceTier::Flex));
Ok(())
}
+2 -1
View File
@@ -3,6 +3,7 @@ use codex_api::ResponsesApiRequest;
use codex_api::TextControls;
use codex_api::create_text_param_for_request;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::config_types::flex_service_tier;
use codex_protocol::models::FunctionCallOutputPayload;
use pretty_assertions::assert_eq;
@@ -155,7 +156,7 @@ fn serializes_flex_service_tier_when_set() {
stream: true,
include: vec![],
prompt_cache_key: None,
service_tier: Some(ServiceTier::flex().to_string()),
service_tier: Some(flex_service_tier().to_string()),
text: None,
client_metadata: None,
};
+5 -1
View File
@@ -89,6 +89,7 @@ use codex_protocol::config_types::Verbosity;
use codex_protocol::config_types::WebSearchConfig;
use codex_protocol::config_types::WebSearchMode;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::config_types::is_priority_service_tier;
use codex_protocol::models::ActivePermissionProfile;
use codex_protocol::models::ActivePermissionProfileModification;
use codex_protocol::models::PermissionProfile;
@@ -2659,7 +2660,10 @@ impl Config {
None => config_profile.service_tier.or(cfg.service_tier),
};
let service_tier = match service_tier {
Some(service_tier) if service_tier.is_priority() && !features.enabled(Feature::FastMode) => {
Some(service_tier)
if is_priority_service_tier(&service_tier)
&& !features.enabled(Feature::FastMode) =>
{
None
}
other => other,
+2 -1
View File
@@ -314,6 +314,7 @@ 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::config_types::priority_service_tier;
use codex_protocol::models::ContentItem;
use codex_protocol::models::ResponseInputItem;
use codex_protocol::models::ResponseItem;
@@ -788,7 +789,7 @@ fn get_service_tier(
account_plan_type
.is_some_and(is_enterprise_default_service_tier_plan)
.then(ServiceTier::priority)
.then(priority_service_tier)
}
fn is_enterprise_default_service_tier_plan(plan_type: AccountPlanType) -> bool {
+6 -5
View File
@@ -34,6 +34,7 @@ use codex_protocol::ThreadId;
use codex_protocol::account::PlanType as AccountPlanType;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::config_types::TrustLevel;
use codex_protocol::config_types::priority_service_tier;
use codex_protocol::exec_output::ExecToolCallOutput;
use codex_protocol::models::FileSystemPermissions;
use codex_protocol::models::FunctionCallOutputBody;
@@ -2801,7 +2802,7 @@ fn get_service_tier_defaults_enterprise_accounts_to_fast() {
Some(AccountPlanType::Enterprise),
/*fast_mode_enabled*/ true,
),
Some(ServiceTier::priority())
Some(priority_service_tier())
);
assert_eq!(
get_service_tier(
@@ -2810,7 +2811,7 @@ fn get_service_tier_defaults_enterprise_accounts_to_fast() {
Some(AccountPlanType::EnterpriseCbpUsageBased),
/*fast_mode_enabled*/ true,
),
Some(ServiceTier::priority())
Some(priority_service_tier())
);
assert_eq!(
get_service_tier(
@@ -2819,7 +2820,7 @@ fn get_service_tier_defaults_enterprise_accounts_to_fast() {
Some(AccountPlanType::Business),
/*fast_mode_enabled*/ true,
),
Some(ServiceTier::priority())
Some(priority_service_tier())
);
assert_eq!(
get_service_tier(
@@ -2828,7 +2829,7 @@ fn get_service_tier_defaults_enterprise_accounts_to_fast() {
Some(AccountPlanType::Team),
/*fast_mode_enabled*/ true,
),
Some(ServiceTier::priority())
Some(priority_service_tier())
);
assert_eq!(
get_service_tier(
@@ -2837,7 +2838,7 @@ fn get_service_tier_defaults_enterprise_accounts_to_fast() {
Some(AccountPlanType::SelfServeBusinessUsageBased),
/*fast_mode_enabled*/ true,
),
Some(ServiceTier::priority())
Some(priority_service_tier())
);
}
+4 -3
View File
@@ -1,6 +1,7 @@
use anyhow::Result;
use codex_features::Feature;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::config_types::priority_service_tier;
use core_test_support::responses::WebSocketConnectionConfig;
use core_test_support::responses::ev_assistant_message;
use core_test_support::responses::ev_completed;
@@ -269,7 +270,7 @@ async fn websocket_v2_first_turn_uses_updated_fast_tier_after_startup_prewarm()
assert_eq!(warmup["generate"].as_bool(), Some(false));
assert_eq!(warmup.get("service_tier"), None);
test.submit_turn_with_service_tier("hello", Some(ServiceTier::priority()))
test.submit_turn_with_service_tier("hello", Some(priority_service_tier()))
.await?;
assert_eq!(server.handshakes().len(), 1);
@@ -313,7 +314,7 @@ async fn websocket_v2_first_turn_drops_fast_tier_after_startup_prewarm() -> Resu
.features
.enable(Feature::ResponsesWebsocketsV2)
.expect("test config should allow feature update");
config.service_tier = Some(ServiceTier::priority());
config.service_tier = Some(priority_service_tier());
});
let test = builder.build_with_websocket_server(&server).await?;
@@ -385,7 +386,7 @@ async fn websocket_v2_next_turn_uses_updated_service_tier() -> Result<()> {
assert_eq!(warmup["generate"].as_bool(), Some(false));
assert_eq!(warmup.get("service_tier"), None);
test.submit_turn_with_service_tier("first", Some(ServiceTier::priority()))
test.submit_turn_with_service_tier("first", Some(priority_service_tier()))
.await?;
test.submit_turn_with_service_tier("second", /*service_tier*/ None)
.await?;
+4 -2
View File
@@ -5,6 +5,8 @@ use codex_login::CodexAuth;
use codex_models_manager::manager::RefreshStrategy;
use codex_protocol::config_types::ReasoningSummary;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::config_types::flex_service_tier;
use codex_protocol::config_types::priority_service_tier;
use codex_protocol::models::PermissionProfile;
use codex_protocol::openai_models::ConfigShellToolType;
use codex_protocol::openai_models::InputModality;
@@ -297,7 +299,7 @@ async fn service_tier_change_is_applied_on_next_http_turn() -> Result<()> {
let test = test_codex().build(&server).await?;
test.submit_turn_with_service_tier("fast turn", Some(ServiceTier::priority()))
test.submit_turn_with_service_tier("fast turn", Some(priority_service_tier()))
.await?;
test.submit_turn_with_service_tier("standard turn", /*service_tier*/ None)
.await?;
@@ -323,7 +325,7 @@ async fn flex_service_tier_is_applied_to_http_turn() -> Result<()> {
let test = test_codex().build(&server).await?;
test.submit_turn_with_service_tier("flex turn", Some(ServiceTier::flex()))
test.submit_turn_with_service_tier("flex turn", Some(flex_service_tier()))
.await?;
let request = resp_mock.single_request();
+5 -4
View File
@@ -4,6 +4,7 @@ use codex_git_utils::diff_since_latest_init;
use codex_git_utils::reset_git_repository;
use codex_protocol::ThreadId;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::config_types::priority_service_tier;
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::Op;
@@ -253,14 +254,14 @@ async fn memories_startup_phase1_uses_live_thread_service_tier() -> anyhow::Resu
model: None,
effort: None,
summary: None,
service_tier: Some(Some(ServiceTier::priority())),
service_tier: Some(Some(priority_service_tier())),
collaboration_mode: None,
personality: None,
})
.await?;
let config_snapshot = wait_for_service_tier(&test, Some(ServiceTier::priority())).await?;
assert_eq!(config_snapshot.service_tier, Some(ServiceTier::priority()));
let config_snapshot = wait_for_service_tier(&test, Some(priority_service_tier())).await?;
assert_eq!(config_snapshot.service_tier, Some(priority_service_tier()));
let context = crate::runtime::MemoryStartupContext::new(
Arc::clone(&test.thread_manager),
@@ -277,7 +278,7 @@ async fn memories_startup_phase1_uses_live_thread_service_tier() -> anyhow::Resu
ReasoningEffort::Low,
)
.await;
assert_eq!(request_context.service_tier, Some(ServiceTier::priority()));
assert_eq!(request_context.service_tier, Some(priority_service_tier()));
shutdown_test_codex(&test).await?;
Ok(())
@@ -1,7 +1,7 @@
use codex_models_manager::model_info::BASE_INSTRUCTIONS;
use codex_protocol::config_types::ReasoningSummary;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::config_types::Verbosity;
use codex_protocol::config_types::priority_service_tier;
use codex_protocol::openai_models::ApplyPatchToolType;
use codex_protocol::openai_models::ConfigShellToolType;
use codex_protocol::openai_models::InputModality;
@@ -49,7 +49,7 @@ fn gpt_5_4_cmb_bedrock_model(priority: i32) -> ModelInfo {
supported_in_api: true,
priority,
service_tiers: vec![ModelServiceTier {
id: ServiceTier::priority(),
id: priority_service_tier(),
name: "Fast".to_string(),
description: "Fast tier".to_string(),
}],
+18 -24
View File
@@ -355,47 +355,41 @@ pub const SERVICE_TIER_FLEX: &str = "flex";
#[ts(type = "string")]
pub struct ServiceTier(String);
impl ServiceTier {
pub fn new(id: impl Into<String>) -> Self {
Self(id.into())
}
pub fn priority_service_tier() -> ServiceTier {
ServiceTier::from(SERVICE_TIER_PRIORITY)
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
pub fn flex_service_tier() -> ServiceTier {
ServiceTier::from(SERVICE_TIER_FLEX)
}
pub fn priority() -> Self {
Self::new(SERVICE_TIER_PRIORITY)
}
pub fn service_tier_id(service_tier: &ServiceTier) -> &str {
service_tier.as_ref()
}
pub fn flex() -> Self {
Self::new(SERVICE_TIER_FLEX)
}
pub fn is_priority_service_tier(service_tier: &ServiceTier) -> bool {
service_tier_id(service_tier) == SERVICE_TIER_PRIORITY
}
pub fn is_priority(&self) -> bool {
self.as_str() == SERVICE_TIER_PRIORITY
}
pub fn is_flex(&self) -> bool {
self.as_str() == SERVICE_TIER_FLEX
}
pub fn is_flex_service_tier(service_tier: &ServiceTier) -> bool {
service_tier_id(service_tier) == SERVICE_TIER_FLEX
}
impl AsRef<str> for ServiceTier {
fn as_ref(&self) -> &str {
self.as_str()
self.0.as_str()
}
}
impl From<&str> for ServiceTier {
fn from(value: &str) -> Self {
Self::new(value)
Self(value.to_string())
}
}
impl From<String> for ServiceTier {
fn from(value: String) -> Self {
Self::new(value)
Self(value)
}
}
@@ -407,7 +401,7 @@ impl From<ServiceTier> for String {
impl std::fmt::Display for ServiceTier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
f.write_str(service_tier_id(self))
}
}
+23 -14
View File
@@ -19,6 +19,7 @@ use crate::config_types::Personality;
use crate::config_types::ReasoningSummary;
use crate::config_types::ServiceTier;
use crate::config_types::Verbosity;
use crate::config_types::priority_service_tier;
const PERSONALITY_PLACEHOLDER: &str = "{{ personality }}";
@@ -469,19 +470,24 @@ impl From<ModelInfo> for ModelPreset {
}
}
pub fn model_supports_service_tier(model: &ModelPreset, service_tier: &ServiceTier) -> bool {
model
.service_tiers
.iter()
.any(|tier| tier.id == *service_tier)
}
pub fn model_service_tier<'a>(
model: &'a ModelPreset,
service_tier: &ServiceTier,
) -> Option<&'a ModelServiceTier> {
model
.service_tiers
.iter()
.find(|tier| tier.id == *service_tier)
}
impl ModelPreset {
pub fn supports_service_tier(&self, service_tier: &ServiceTier) -> bool {
self.service_tiers
.iter()
.any(|tier| tier.id == *service_tier)
}
pub fn service_tier(&self, service_tier: &ServiceTier) -> Option<&ModelServiceTier> {
self.service_tiers
.iter()
.find(|tier| tier.id == *service_tier)
}
/// Filter models based on authentication mode.
///
/// In ChatGPT mode, all models are visible. Otherwise, only API-supported models are shown.
@@ -831,7 +837,7 @@ mod tests {
message: "Try Spark.".to_string(),
}),
service_tiers: vec![ModelServiceTier {
id: ServiceTier::priority(),
id: priority_service_tier(),
name: "Fast".to_string(),
description: "Fast tier".to_string(),
}],
@@ -844,6 +850,9 @@ mod tests {
message: "Try Spark.".to_string(),
})
);
assert!(preset.supports_service_tier(&ServiceTier::priority()));
assert!(model_supports_service_tier(
&preset,
&priority_service_tier()
));
}
}