mirror of
https://github.com/openai/codex.git
synced 2026-11-05 20:10:33 +00:00
Use structured service tiers in core model metadata
This commit is contained in:
@@ -26,7 +26,7 @@ use codex_protocol::config_types::ModeKind;
|
||||
use codex_protocol::config_types::Personality;
|
||||
use codex_protocol::config_types::ReasoningSummary;
|
||||
use codex_protocol::config_types::SandboxMode as CoreSandboxMode;
|
||||
use codex_protocol::config_types::ServiceTier;
|
||||
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;
|
||||
@@ -121,6 +121,47 @@ use serde_with::serde_as;
|
||||
use thiserror::Error;
|
||||
use ts_rs::TS;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[ts(export)]
|
||||
pub enum ServiceTier {
|
||||
Fast,
|
||||
Flex,
|
||||
}
|
||||
|
||||
impl ServiceTier {
|
||||
pub fn fast() -> Self {
|
||||
Self::Fast
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ServiceTier> for CoreServiceTier {
|
||||
fn from(value: ServiceTier) -> Self {
|
||||
value.to_core()
|
||||
}
|
||||
}
|
||||
|
||||
// Macro to declare a camelCased API v2 enum mirroring a core enum which
|
||||
// tends to use either snake_case or kebab-case.
|
||||
macro_rules! v2_enum_from_core {
|
||||
|
||||
@@ -143,6 +143,7 @@ use codex_app_server_protocol::SendAddCreditsNudgeEmailParams;
|
||||
use codex_app_server_protocol::SendAddCreditsNudgeEmailResponse;
|
||||
use codex_app_server_protocol::ServerNotification;
|
||||
use codex_app_server_protocol::ServerRequestResolvedNotification;
|
||||
use codex_app_server_protocol::ServiceTier;
|
||||
use codex_app_server_protocol::SkillSummary;
|
||||
use codex_app_server_protocol::SkillsConfigWriteParams;
|
||||
use codex_app_server_protocol::SkillsConfigWriteResponse;
|
||||
@@ -2538,7 +2539,7 @@ impl CodexMessageProcessor {
|
||||
let mut typesafe_overrides = self.build_thread_config_overrides(
|
||||
model,
|
||||
model_provider,
|
||||
service_tier,
|
||||
Self::resolve_service_tier_override(service_tier),
|
||||
cwd,
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
@@ -2919,7 +2920,7 @@ impl CodexMessageProcessor {
|
||||
thread: thread.clone(),
|
||||
model: config_snapshot.model,
|
||||
model_provider: config_snapshot.model_provider_id,
|
||||
service_tier: config_snapshot.service_tier,
|
||||
service_tier: config_snapshot.service_tier.as_ref().and_then(ServiceTier::from_core),
|
||||
cwd: config_snapshot.cwd,
|
||||
instruction_sources,
|
||||
approval_policy: config_snapshot.approval_policy.into(),
|
||||
@@ -3019,6 +3020,12 @@ impl CodexMessageProcessor {
|
||||
Ok(environment_selections)
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
async fn thread_archive(&self, request_id: ConnectionRequestId, params: ThreadArchiveParams) {
|
||||
let _thread_list_state_permit = match self.acquire_thread_list_state_permit().await {
|
||||
Ok(permit) => permit,
|
||||
@@ -4381,7 +4388,7 @@ impl CodexMessageProcessor {
|
||||
let mut typesafe_overrides = self.build_thread_config_overrides(
|
||||
model,
|
||||
model_provider,
|
||||
service_tier,
|
||||
Self::resolve_service_tier_override(service_tier),
|
||||
cwd,
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
@@ -4499,7 +4506,10 @@ impl CodexMessageProcessor {
|
||||
thread,
|
||||
model: session_configured.model,
|
||||
model_provider: session_configured.model_provider_id,
|
||||
service_tier: session_configured.service_tier,
|
||||
service_tier: session_configured
|
||||
.service_tier
|
||||
.as_ref()
|
||||
.and_then(ServiceTier::from_core),
|
||||
cwd: session_configured.cwd,
|
||||
instruction_sources,
|
||||
approval_policy: session_configured.approval_policy.into(),
|
||||
@@ -5013,7 +5023,7 @@ impl CodexMessageProcessor {
|
||||
let mut typesafe_overrides = self.build_thread_config_overrides(
|
||||
model,
|
||||
model_provider,
|
||||
service_tier,
|
||||
Self::resolve_service_tier_override(service_tier),
|
||||
cwd,
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
@@ -5134,7 +5144,10 @@ impl CodexMessageProcessor {
|
||||
thread: thread.clone(),
|
||||
model: session_configured.model,
|
||||
model_provider: session_configured.model_provider_id,
|
||||
service_tier: session_configured.service_tier,
|
||||
service_tier: session_configured
|
||||
.service_tier
|
||||
.as_ref()
|
||||
.and_then(ServiceTier::from_core),
|
||||
cwd: session_configured.cwd,
|
||||
instruction_sources,
|
||||
approval_policy: session_configured.approval_policy.into(),
|
||||
@@ -6769,7 +6782,7 @@ impl CodexMessageProcessor {
|
||||
let model = params.model;
|
||||
let effort = params.effort.map(Some);
|
||||
let summary = params.summary;
|
||||
let service_tier = params.service_tier;
|
||||
let service_tier = Self::resolve_service_tier_override(params.service_tier);
|
||||
let personality = params.personality;
|
||||
|
||||
// If any overrides are provided, validate them synchronously so the
|
||||
@@ -8492,7 +8505,7 @@ async fn handle_pending_thread_resume_request(
|
||||
thread,
|
||||
model,
|
||||
model_provider: model_provider_id,
|
||||
service_tier,
|
||||
service_tier: service_tier.as_ref().and_then(ServiceTier::from_core),
|
||||
cwd,
|
||||
instruction_sources,
|
||||
approval_policy: approval_policy.into(),
|
||||
@@ -8662,11 +8675,15 @@ fn collect_resume_override_mismatches(
|
||||
));
|
||||
}
|
||||
if let Some(requested_service_tier) = request.service_tier.as_ref()
|
||||
&& requested_service_tier != &config_snapshot.service_tier
|
||||
&& requested_service_tier.map(ServiceTier::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);
|
||||
mismatch_details.push(format!(
|
||||
"service_tier requested={requested_service_tier:?} active={:?}",
|
||||
config_snapshot.service_tier
|
||||
"service_tier requested={requested_service_tier:?} active={active_service_tier:?}"
|
||||
));
|
||||
}
|
||||
if let Some(requested_cwd) = request.cwd.as_deref() {
|
||||
@@ -10048,6 +10065,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::openai_models::ReasoningEffort;
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
@@ -10528,7 +10546,7 @@ mod tests {
|
||||
path: None,
|
||||
model: None,
|
||||
model_provider: None,
|
||||
service_tier: Some(Some(codex_protocol::config_types::ServiceTier::Fast)),
|
||||
service_tier: Some(Some(ServiceTier::fast())),
|
||||
cwd: None,
|
||||
approval_policy: None,
|
||||
approvals_reviewer: None,
|
||||
@@ -10544,7 +10562,7 @@ mod tests {
|
||||
let config_snapshot = ThreadConfigSnapshot {
|
||||
model: "gpt-5".to_string(),
|
||||
model_provider_id: "openai".to_string(),
|
||||
service_tier: Some(codex_protocol::config_types::ServiceTier::Flex),
|
||||
service_tier: Some(CoreServiceTier::flex()),
|
||||
approval_policy: codex_protocol::protocol::AskForApproval::OnRequest,
|
||||
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer::User,
|
||||
permission_profile: codex_protocol::models::PermissionProfile::Disabled,
|
||||
|
||||
@@ -6,6 +6,7 @@ use codex_app_server_protocol::ReasoningEffortOption;
|
||||
use codex_core::ThreadManager;
|
||||
use codex_models_manager::manager::RefreshStrategy;
|
||||
use codex_protocol::openai_models::ModelPreset;
|
||||
use codex_protocol::openai_models::ModelServiceTier as CoreModelServiceTier;
|
||||
use codex_protocol::openai_models::ReasoningEffortPreset;
|
||||
|
||||
pub async fn supported_models(
|
||||
@@ -22,6 +23,7 @@ pub async fn supported_models(
|
||||
}
|
||||
|
||||
fn model_from_preset(preset: ModelPreset) -> Model {
|
||||
let additional_speed_tiers = legacy_additional_speed_tiers(&preset.service_tiers);
|
||||
Model {
|
||||
id: preset.id.to_string(),
|
||||
model: preset.model.to_string(),
|
||||
@@ -42,11 +44,18 @@ fn model_from_preset(preset: ModelPreset) -> Model {
|
||||
default_reasoning_effort: preset.default_reasoning_effort,
|
||||
input_modalities: preset.input_modalities,
|
||||
supports_personality: preset.supports_personality,
|
||||
additional_speed_tiers: preset.additional_speed_tiers,
|
||||
additional_speed_tiers,
|
||||
is_default: preset.is_default,
|
||||
}
|
||||
}
|
||||
|
||||
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()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn reasoning_efforts_from_preset(
|
||||
efforts: Vec<ReasoningEffortPreset>,
|
||||
) -> Vec<ReasoningEffortOption> {
|
||||
|
||||
@@ -28,7 +28,7 @@ fn preset_to_info(preset: &ModelPreset, priority: i32) -> ModelInfo {
|
||||
},
|
||||
supported_in_api: preset.supported_in_api,
|
||||
priority,
|
||||
additional_speed_tiers: preset.additional_speed_tiers.clone(),
|
||||
service_tiers: preset.service_tiers.clone(),
|
||||
upgrade: preset.upgrade.as_ref().map(Into::into),
|
||||
base_instructions: "base instructions".to_string(),
|
||||
model_messages: None,
|
||||
|
||||
@@ -13,6 +13,7 @@ use codex_app_server_protocol::ModelUpgradeInfo;
|
||||
use codex_app_server_protocol::ReasoningEffortOption;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
use codex_protocol::openai_models::ModelPreset;
|
||||
use codex_protocol::openai_models::ModelServiceTier as CoreModelServiceTier;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::TempDir;
|
||||
use tokio::time::timeout;
|
||||
@@ -50,11 +51,18 @@ fn model_from_preset(preset: &ModelPreset) -> Model {
|
||||
// cache report `supports_personality = false`.
|
||||
// todo(sayan): fix, maybe make roundtrip use ModelInfo only
|
||||
supports_personality: false,
|
||||
additional_speed_tiers: preset.additional_speed_tiers.clone(),
|
||||
additional_speed_tiers: legacy_additional_speed_tiers(&preset.service_tiers),
|
||||
is_default: preset.is_default,
|
||||
}
|
||||
}
|
||||
|
||||
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()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn expected_visible_models() -> Vec<Model> {
|
||||
// Filter by supported_in_api to support testing with both ChatGPT and non-ChatGPT auth modes.
|
||||
let mut presets = ModelPreset::filter_by_auth(
|
||||
|
||||
@@ -14,6 +14,7 @@ use codex_app_server_protocol::McpServerStatusUpdatedNotification;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
use codex_app_server_protocol::SandboxMode;
|
||||
use codex_app_server_protocol::ServerNotification;
|
||||
use codex_app_server_protocol::ServiceTier;
|
||||
use codex_app_server_protocol::ThreadStartParams;
|
||||
use codex_app_server_protocol::ThreadStartResponse;
|
||||
use codex_app_server_protocol::ThreadStartedNotification;
|
||||
@@ -26,7 +27,6 @@ use codex_core::config::set_project_trust_level;
|
||||
use codex_exec_server::LOCAL_FS;
|
||||
use codex_git_utils::resolve_root_git_project_for_trust;
|
||||
use codex_login::REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR;
|
||||
use codex_protocol::config_types::ServiceTier;
|
||||
use codex_protocol::config_types::TrustLevel;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ async fn models_client_hits_models_endpoint() {
|
||||
visibility: ModelVisibility::List,
|
||||
supported_in_api: true,
|
||||
priority: 1,
|
||||
additional_speed_tiers: Vec::new(),
|
||||
service_tiers: Vec::new(),
|
||||
upgrade: None,
|
||||
base_instructions: "base instructions".to_string(),
|
||||
model_messages: None,
|
||||
|
||||
@@ -2151,10 +2151,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"ServiceTier": {
|
||||
"enum": [
|
||||
"fast",
|
||||
"flex"
|
||||
],
|
||||
"description": "String-backed service tier identifier. Known values include `priority` and `flex`, but other backend-provided ids are allowed.",
|
||||
"type": "string"
|
||||
},
|
||||
"ShellEnvironmentPolicyInherit": {
|
||||
@@ -4497,4 +4494,4 @@
|
||||
},
|
||||
"title": "ConfigToml",
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -889,11 +889,7 @@ impl ModelClientSession {
|
||||
store: provider.is_azure_responses_endpoint(),
|
||||
stream: true,
|
||||
include,
|
||||
service_tier: match service_tier {
|
||||
Some(ServiceTier::Fast) => Some("priority".to_string()),
|
||||
Some(service_tier) => Some(service_tier.to_string()),
|
||||
None => None,
|
||||
},
|
||||
service_tier: service_tier.map(|service_tier| service_tier.to_string()),
|
||||
prompt_cache_key,
|
||||
text,
|
||||
client_metadata: Some(HashMap::from([(
|
||||
|
||||
@@ -155,7 +155,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(ServiceTier::flex().to_string()),
|
||||
text: None,
|
||||
client_metadata: None,
|
||||
};
|
||||
|
||||
@@ -518,7 +518,7 @@ async fn drain_to_completed(
|
||||
&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,
|
||||
// Rollout tracing currently models remote compaction only; local compaction streams
|
||||
// are left untraced until the reducer has a first-class local compaction lifecycle.
|
||||
|
||||
@@ -2659,12 +2659,10 @@ impl Config {
|
||||
None => config_profile.service_tier.or(cfg.service_tier),
|
||||
};
|
||||
let service_tier = match service_tier {
|
||||
Some(ServiceTier::Fast) if features.enabled(Feature::FastMode) => {
|
||||
Some(ServiceTier::Fast)
|
||||
Some(service_tier) if service_tier.is_priority() && !features.enabled(Feature::FastMode) => {
|
||||
None
|
||||
}
|
||||
Some(ServiceTier::Fast) => None,
|
||||
Some(ServiceTier::Flex) => Some(ServiceTier::Flex),
|
||||
None => None,
|
||||
other => other,
|
||||
};
|
||||
|
||||
let compact_prompt = compact_prompt.or(cfg.compact_prompt).and_then(|value| {
|
||||
|
||||
@@ -585,7 +585,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),
|
||||
@@ -788,7 +788,7 @@ fn get_service_tier(
|
||||
|
||||
account_plan_type
|
||||
.is_some_and(is_enterprise_default_service_tier_plan)
|
||||
.then_some(ServiceTier::Fast)
|
||||
.then(ServiceTier::priority)
|
||||
}
|
||||
|
||||
fn is_enterprise_default_service_tier_plan(plan_type: AccountPlanType) -> bool {
|
||||
|
||||
@@ -132,7 +132,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(),
|
||||
@@ -177,7 +177,7 @@ impl SessionConfiguration {
|
||||
if let Some(summary) = updates.reasoning_summary {
|
||||
next_configuration.model_reasoning_summary = Some(summary);
|
||||
}
|
||||
if let Some(service_tier) = updates.service_tier {
|
||||
if let Some(service_tier) = updates.service_tier.clone() {
|
||||
next_configuration.service_tier = service_tier;
|
||||
}
|
||||
if let Some(personality) = updates.personality {
|
||||
|
||||
@@ -2801,7 +2801,7 @@ fn get_service_tier_defaults_enterprise_accounts_to_fast() {
|
||||
Some(AccountPlanType::Enterprise),
|
||||
/*fast_mode_enabled*/ true,
|
||||
),
|
||||
Some(ServiceTier::Fast)
|
||||
Some(ServiceTier::priority())
|
||||
);
|
||||
assert_eq!(
|
||||
get_service_tier(
|
||||
@@ -2810,7 +2810,7 @@ fn get_service_tier_defaults_enterprise_accounts_to_fast() {
|
||||
Some(AccountPlanType::EnterpriseCbpUsageBased),
|
||||
/*fast_mode_enabled*/ true,
|
||||
),
|
||||
Some(ServiceTier::Fast)
|
||||
Some(ServiceTier::priority())
|
||||
);
|
||||
assert_eq!(
|
||||
get_service_tier(
|
||||
@@ -2819,7 +2819,7 @@ fn get_service_tier_defaults_enterprise_accounts_to_fast() {
|
||||
Some(AccountPlanType::Business),
|
||||
/*fast_mode_enabled*/ true,
|
||||
),
|
||||
Some(ServiceTier::Fast)
|
||||
Some(ServiceTier::priority())
|
||||
);
|
||||
assert_eq!(
|
||||
get_service_tier(
|
||||
@@ -2828,7 +2828,7 @@ fn get_service_tier_defaults_enterprise_accounts_to_fast() {
|
||||
Some(AccountPlanType::Team),
|
||||
/*fast_mode_enabled*/ true,
|
||||
),
|
||||
Some(ServiceTier::Fast)
|
||||
Some(ServiceTier::priority())
|
||||
);
|
||||
assert_eq!(
|
||||
get_service_tier(
|
||||
@@ -2837,7 +2837,7 @@ fn get_service_tier_defaults_enterprise_accounts_to_fast() {
|
||||
Some(AccountPlanType::SelfServeBusinessUsageBased),
|
||||
/*fast_mode_enabled*/ true,
|
||||
),
|
||||
Some(ServiceTier::Fast)
|
||||
Some(ServiceTier::priority())
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -697,7 +697,7 @@ 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.clone(),
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
approvals_reviewer: turn_context.config.approvals_reviewer,
|
||||
sandbox_network_access: turn_context.network_sandbox_policy().is_enabled(),
|
||||
@@ -1839,7 +1839,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,
|
||||
)
|
||||
|
||||
@@ -397,7 +397,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 =
|
||||
|
||||
@@ -269,7 +269,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::Fast))
|
||||
test.submit_turn_with_service_tier("hello", Some(ServiceTier::priority()))
|
||||
.await?;
|
||||
|
||||
assert_eq!(server.handshakes().len(), 1);
|
||||
@@ -313,7 +313,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::Fast);
|
||||
config.service_tier = Some(ServiceTier::priority());
|
||||
});
|
||||
let test = builder.build_with_websocket_server(&server).await?;
|
||||
|
||||
@@ -385,7 +385,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::Fast))
|
||||
test.submit_turn_with_service_tier("first", Some(ServiceTier::priority()))
|
||||
.await?;
|
||||
test.submit_turn_with_service_tier("second", /*service_tier*/ None)
|
||||
.await?;
|
||||
|
||||
@@ -105,7 +105,7 @@ fn test_model_info(
|
||||
used_fallback_model_metadata: false,
|
||||
supports_search_tool: false,
|
||||
priority: 1,
|
||||
additional_speed_tiers: Vec::new(),
|
||||
service_tiers: Vec::new(),
|
||||
upgrade: None,
|
||||
base_instructions: "base instructions".to_string(),
|
||||
model_messages: None,
|
||||
@@ -297,7 +297,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::Fast))
|
||||
test.submit_turn_with_service_tier("fast turn", Some(ServiceTier::priority()))
|
||||
.await?;
|
||||
test.submit_turn_with_service_tier("standard turn", /*service_tier*/ None)
|
||||
.await?;
|
||||
@@ -323,7 +323,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(ServiceTier::flex()))
|
||||
.await?;
|
||||
|
||||
let request = resp_mock.single_request();
|
||||
@@ -817,7 +817,7 @@ async fn model_switch_to_smaller_model_updates_token_context_window() -> Result<
|
||||
used_fallback_model_metadata: false,
|
||||
supports_search_tool: false,
|
||||
priority: 1,
|
||||
additional_speed_tiers: Vec::new(),
|
||||
service_tiers: Vec::new(),
|
||||
upgrade: None,
|
||||
base_instructions: "base instructions".to_string(),
|
||||
model_messages: None,
|
||||
|
||||
@@ -340,7 +340,7 @@ fn test_remote_model(slug: &str, priority: i32) -> ModelInfo {
|
||||
visibility: ModelVisibility::List,
|
||||
supported_in_api: true,
|
||||
priority,
|
||||
additional_speed_tiers: Vec::new(),
|
||||
service_tiers: Vec::new(),
|
||||
upgrade: None,
|
||||
base_instructions: "base instructions".to_string(),
|
||||
model_messages: None,
|
||||
|
||||
@@ -578,7 +578,7 @@ async fn remote_model_friendly_personality_instructions_with_feature() -> anyhow
|
||||
visibility: ModelVisibility::List,
|
||||
supported_in_api: true,
|
||||
priority: 1,
|
||||
additional_speed_tiers: Vec::new(),
|
||||
service_tiers: Vec::new(),
|
||||
upgrade: None,
|
||||
base_instructions: "base instructions".to_string(),
|
||||
model_messages: Some(ModelMessages {
|
||||
@@ -686,7 +686,7 @@ async fn user_turn_personality_remote_model_template_includes_update_message() -
|
||||
visibility: ModelVisibility::List,
|
||||
supported_in_api: true,
|
||||
priority: 1,
|
||||
additional_speed_tiers: Vec::new(),
|
||||
service_tiers: Vec::new(),
|
||||
upgrade: None,
|
||||
base_instructions: "base instructions".to_string(),
|
||||
model_messages: Some(ModelMessages {
|
||||
|
||||
@@ -532,7 +532,7 @@ async fn remote_models_remote_model_uses_unified_exec() -> Result<()> {
|
||||
used_fallback_model_metadata: false,
|
||||
supports_search_tool: false,
|
||||
priority: 1,
|
||||
additional_speed_tiers: Vec::new(),
|
||||
service_tiers: Vec::new(),
|
||||
upgrade: None,
|
||||
base_instructions: "base instructions".to_string(),
|
||||
model_messages: None,
|
||||
@@ -788,7 +788,7 @@ async fn remote_models_apply_remote_base_instructions() -> Result<()> {
|
||||
used_fallback_model_metadata: false,
|
||||
supports_search_tool: false,
|
||||
priority: 1,
|
||||
additional_speed_tiers: Vec::new(),
|
||||
service_tiers: Vec::new(),
|
||||
upgrade: None,
|
||||
base_instructions: remote_base.to_string(),
|
||||
model_messages: None,
|
||||
@@ -1278,7 +1278,7 @@ fn test_remote_model_with_policy(
|
||||
used_fallback_model_metadata: false,
|
||||
supports_search_tool: false,
|
||||
priority,
|
||||
additional_speed_tiers: Vec::new(),
|
||||
service_tiers: Vec::new(),
|
||||
upgrade: None,
|
||||
base_instructions: "base instructions".to_string(),
|
||||
model_messages: None,
|
||||
|
||||
@@ -1282,7 +1282,7 @@ async fn stdio_image_responses_are_sanitized_for_text_only_model() -> anyhow::Re
|
||||
visibility: ModelVisibility::List,
|
||||
supported_in_api: true,
|
||||
priority: 1,
|
||||
additional_speed_tiers: Vec::new(),
|
||||
service_tiers: Vec::new(),
|
||||
upgrade: None,
|
||||
base_instructions: "base instructions".to_string(),
|
||||
model_messages: None,
|
||||
|
||||
@@ -66,7 +66,7 @@ fn test_model_info(
|
||||
used_fallback_model_metadata: false,
|
||||
supports_search_tool: false,
|
||||
priority: 1,
|
||||
additional_speed_tiers: Vec::new(),
|
||||
service_tiers: Vec::new(),
|
||||
upgrade: None,
|
||||
base_instructions: "base instructions".to_string(),
|
||||
model_messages: None,
|
||||
|
||||
@@ -1084,7 +1084,7 @@ async fn view_image_tool_returns_unsupported_message_for_text_only_model() -> an
|
||||
used_fallback_model_metadata: false,
|
||||
supports_search_tool: false,
|
||||
priority: 1,
|
||||
additional_speed_tiers: Vec::new(),
|
||||
service_tiers: Vec::new(),
|
||||
upgrade: None,
|
||||
base_instructions: "base instructions".to_string(),
|
||||
model_messages: None,
|
||||
|
||||
@@ -253,14 +253,14 @@ async fn memories_startup_phase1_uses_live_thread_service_tier() -> anyhow::Resu
|
||||
model: None,
|
||||
effort: None,
|
||||
summary: None,
|
||||
service_tier: Some(Some(ServiceTier::Fast)),
|
||||
service_tier: Some(Some(ServiceTier::priority())),
|
||||
collaboration_mode: None,
|
||||
personality: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
let config_snapshot = wait_for_service_tier(&test, Some(ServiceTier::Fast)).await?;
|
||||
assert_eq!(config_snapshot.service_tier, Some(ServiceTier::Fast));
|
||||
let config_snapshot = wait_for_service_tier(&test, Some(ServiceTier::priority())).await?;
|
||||
assert_eq!(config_snapshot.service_tier, Some(ServiceTier::priority()));
|
||||
|
||||
let context = crate::runtime::MemoryStartupContext::new(
|
||||
Arc::clone(&test.thread_manager),
|
||||
@@ -277,7 +277,7 @@ async fn memories_startup_phase1_uses_live_thread_service_tier() -> anyhow::Resu
|
||||
ReasoningEffort::Low,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(request_context.service_tier, Some(ServiceTier::Fast));
|
||||
assert_eq!(request_context.service_tier, Some(ServiceTier::priority()));
|
||||
|
||||
shutdown_test_codex(&test).await?;
|
||||
Ok(())
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
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::openai_models::ApplyPatchToolType;
|
||||
use codex_protocol::openai_models::ConfigShellToolType;
|
||||
use codex_protocol::openai_models::InputModality;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_protocol::openai_models::ModelServiceTier;
|
||||
use codex_protocol::openai_models::ModelVisibility;
|
||||
use codex_protocol::openai_models::ModelsResponse;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
@@ -46,7 +48,11 @@ fn gpt_5_4_cmb_bedrock_model(priority: i32) -> ModelInfo {
|
||||
visibility: ModelVisibility::List,
|
||||
supported_in_api: true,
|
||||
priority,
|
||||
additional_speed_tiers: vec!["fast".to_string()],
|
||||
service_tiers: vec![ModelServiceTier {
|
||||
id: ServiceTier::priority(),
|
||||
name: "Fast".to_string(),
|
||||
description: "Fast tier".to_string(),
|
||||
}],
|
||||
availability_nux: None,
|
||||
upgrade: None,
|
||||
base_instructions: BASE_INSTRUCTIONS.to_string(),
|
||||
@@ -86,7 +92,7 @@ fn bedrock_oss_model(slug: &str, display_name: &str, priority: i32) -> ModelInfo
|
||||
visibility: ModelVisibility::List,
|
||||
supported_in_api: true,
|
||||
priority,
|
||||
additional_speed_tiers: Vec::new(),
|
||||
service_tiers: Vec::new(),
|
||||
availability_nux: None,
|
||||
upgrade: None,
|
||||
base_instructions: BASE_INSTRUCTIONS.to_string(),
|
||||
|
||||
@@ -82,8 +82,12 @@
|
||||
"team"
|
||||
],
|
||||
"supports_search_tool": true,
|
||||
"additional_speed_tiers": [
|
||||
"fast"
|
||||
"service_tiers": [
|
||||
{
|
||||
"id": "priority",
|
||||
"name": "Fast",
|
||||
"description": "Fast tier"
|
||||
}
|
||||
],
|
||||
"supports_reasoning_summaries": true
|
||||
},
|
||||
@@ -164,8 +168,12 @@
|
||||
"team"
|
||||
],
|
||||
"supports_search_tool": true,
|
||||
"additional_speed_tiers": [
|
||||
"fast"
|
||||
"service_tiers": [
|
||||
{
|
||||
"id": "priority",
|
||||
"name": "Fast",
|
||||
"description": "Fast tier"
|
||||
}
|
||||
],
|
||||
"supports_reasoning_summaries": true
|
||||
},
|
||||
@@ -249,7 +257,7 @@
|
||||
"team"
|
||||
],
|
||||
"supports_search_tool": true,
|
||||
"additional_speed_tiers": [],
|
||||
"service_tiers": [],
|
||||
"supports_reasoning_summaries": true
|
||||
},
|
||||
{
|
||||
@@ -332,7 +340,7 @@
|
||||
"team"
|
||||
],
|
||||
"supports_search_tool": true,
|
||||
"additional_speed_tiers": [],
|
||||
"service_tiers": [],
|
||||
"supports_reasoning_summaries": true
|
||||
},
|
||||
{
|
||||
@@ -411,7 +419,7 @@
|
||||
"team"
|
||||
],
|
||||
"supports_search_tool": true,
|
||||
"additional_speed_tiers": [],
|
||||
"service_tiers": [],
|
||||
"supports_reasoning_summaries": true
|
||||
},
|
||||
{
|
||||
@@ -491,7 +499,7 @@
|
||||
"team"
|
||||
],
|
||||
"supports_search_tool": true,
|
||||
"additional_speed_tiers": [],
|
||||
"service_tiers": [],
|
||||
"supports_reasoning_summaries": true
|
||||
}
|
||||
]
|
||||
|
||||
@@ -75,7 +75,7 @@ pub fn model_info_from_slug(slug: &str) -> ModelInfo {
|
||||
visibility: ModelVisibility::None,
|
||||
supported_in_api: true,
|
||||
priority: 99,
|
||||
additional_speed_tiers: Vec::new(),
|
||||
service_tiers: Vec::new(),
|
||||
availability_nux: None,
|
||||
upgrade: None,
|
||||
base_instructions: BASE_INSTRUCTIONS.to_string(),
|
||||
|
||||
@@ -347,12 +347,88 @@ impl From<WebSearchToolConfig> for WebSearchConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
pub enum ServiceTier {
|
||||
Fast,
|
||||
Flex,
|
||||
pub const SERVICE_TIER_PRIORITY: &str = "priority";
|
||||
pub const SERVICE_TIER_FLEX: &str = "flex";
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, TS)]
|
||||
#[serde(transparent)]
|
||||
#[ts(type = "string")]
|
||||
pub struct ServiceTier(String);
|
||||
|
||||
impl ServiceTier {
|
||||
pub fn new(id: impl Into<String>) -> Self {
|
||||
Self(id.into())
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
|
||||
pub fn priority() -> Self {
|
||||
Self::new(SERVICE_TIER_PRIORITY)
|
||||
}
|
||||
|
||||
pub fn flex() -> Self {
|
||||
Self::new(SERVICE_TIER_FLEX)
|
||||
}
|
||||
|
||||
pub fn is_priority(&self) -> bool {
|
||||
self.as_str() == SERVICE_TIER_PRIORITY
|
||||
}
|
||||
|
||||
pub fn is_flex(&self) -> bool {
|
||||
self.as_str() == SERVICE_TIER_FLEX
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for ServiceTier {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for ServiceTier {
|
||||
fn from(value: &str) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for ServiceTier {
|
||||
fn from(value: String) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ServiceTier> for String {
|
||||
fn from(value: ServiceTier) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ServiceTier {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl JsonSchema for ServiceTier {
|
||||
fn schema_name() -> String {
|
||||
"ServiceTier".to_string()
|
||||
}
|
||||
|
||||
fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
|
||||
Schema::Object(SchemaObject {
|
||||
instance_type: Some(InstanceType::String.into()),
|
||||
metadata: Some(Box::new(Metadata {
|
||||
description: Some(
|
||||
"String-backed service tier identifier. Known values include `priority` and `flex`, but other backend-provided ids are allowed."
|
||||
.to_string(),
|
||||
),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS)]
|
||||
|
||||
@@ -17,10 +17,10 @@ use ts_rs::TS;
|
||||
|
||||
use crate::config_types::Personality;
|
||||
use crate::config_types::ReasoningSummary;
|
||||
use crate::config_types::ServiceTier;
|
||||
use crate::config_types::Verbosity;
|
||||
|
||||
const PERSONALITY_PLACEHOLDER: &str = "{{ personality }}";
|
||||
pub const SPEED_TIER_FAST: &str = "fast";
|
||||
|
||||
/// See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning
|
||||
#[derive(
|
||||
@@ -115,6 +115,13 @@ pub struct ModelAvailabilityNux {
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema, PartialEq, Eq)]
|
||||
pub struct ModelServiceTier {
|
||||
pub id: ServiceTier,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
/// Metadata describing a Codex-supported model.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema, PartialEq)]
|
||||
pub struct ModelPreset {
|
||||
@@ -133,9 +140,9 @@ pub struct ModelPreset {
|
||||
/// Whether this model supports personality-specific instructions.
|
||||
#[serde(default)]
|
||||
pub supports_personality: bool,
|
||||
/// Additional speed tiers this model can run with beyond the standard path.
|
||||
/// User-selectable service tiers supported by this model.
|
||||
#[serde(default)]
|
||||
pub additional_speed_tiers: Vec<String>,
|
||||
pub service_tiers: Vec<ModelServiceTier>,
|
||||
/// Whether this is the default model for new users.
|
||||
pub is_default: bool,
|
||||
/// recommended upgrade model
|
||||
@@ -257,7 +264,7 @@ pub struct ModelInfo {
|
||||
pub supported_in_api: bool,
|
||||
pub priority: i32,
|
||||
#[serde(default)]
|
||||
pub additional_speed_tiers: Vec<String>,
|
||||
pub service_tiers: Vec<ModelServiceTier>,
|
||||
pub availability_nux: Option<ModelAvailabilityNux>,
|
||||
pub upgrade: Option<ModelInfoUpgrade>,
|
||||
pub base_instructions: String,
|
||||
@@ -441,7 +448,7 @@ impl From<ModelInfo> for ModelPreset {
|
||||
.unwrap_or(ReasoningEffort::None),
|
||||
supported_reasoning_efforts: info.supported_reasoning_levels.clone(),
|
||||
supports_personality,
|
||||
additional_speed_tiers: info.additional_speed_tiers,
|
||||
service_tiers: info.service_tiers,
|
||||
is_default: false, // default is the highest priority available model
|
||||
upgrade: info.upgrade.as_ref().map(|upgrade| ModelUpgrade {
|
||||
id: upgrade.model.clone(),
|
||||
@@ -463,10 +470,16 @@ impl From<ModelInfo> for ModelPreset {
|
||||
}
|
||||
|
||||
impl ModelPreset {
|
||||
pub fn supports_fast_mode(&self) -> bool {
|
||||
self.additional_speed_tiers
|
||||
pub fn supports_service_tier(&self, service_tier: &ServiceTier) -> bool {
|
||||
self.service_tiers
|
||||
.iter()
|
||||
.any(|tier| tier == SPEED_TIER_FAST)
|
||||
.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.
|
||||
@@ -547,7 +560,7 @@ mod tests {
|
||||
visibility: ModelVisibility::List,
|
||||
supported_in_api: true,
|
||||
priority: 1,
|
||||
additional_speed_tiers: Vec::new(),
|
||||
service_tiers: Vec::new(),
|
||||
availability_nux: None,
|
||||
upgrade: None,
|
||||
base_instructions: "base".to_string(),
|
||||
@@ -817,7 +830,11 @@ mod tests {
|
||||
availability_nux: Some(ModelAvailabilityNux {
|
||||
message: "Try Spark.".to_string(),
|
||||
}),
|
||||
additional_speed_tiers: vec![SPEED_TIER_FAST.to_string()],
|
||||
service_tiers: vec![ModelServiceTier {
|
||||
id: ServiceTier::priority(),
|
||||
name: "Fast".to_string(),
|
||||
description: "Fast tier".to_string(),
|
||||
}],
|
||||
..test_model(/*spec*/ None)
|
||||
});
|
||||
|
||||
@@ -827,6 +844,6 @@ mod tests {
|
||||
message: "Try Spark.".to_string(),
|
||||
})
|
||||
);
|
||||
assert!(preset.supports_fast_mode());
|
||||
assert!(preset.supports_service_tier(&ServiceTier::priority()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ fn model_preset(id: &str, show_in_picker: bool) -> ModelPreset {
|
||||
description: "Balanced".to_string(),
|
||||
}],
|
||||
supports_personality: false,
|
||||
additional_speed_tiers: Vec::new(),
|
||||
service_tiers: Vec::new(),
|
||||
is_default: false,
|
||||
upgrade: None,
|
||||
show_in_picker,
|
||||
|
||||
Reference in New Issue
Block a user