[codex] Generalize service tier slash commands (#21745)

## Why

`/fast` was wired as a one-off slash command even though model metadata
now exposes service tiers as catalog data. That meant adding another
tier, such as a slower/cheaper tier, would require more hardcoded TUI
plumbing instead of letting the model catalog drive the available
commands.

This change makes service-tier commands data-driven: each advertised
`service_tiers` entry becomes a `/name` command using the catalog
description, while the request path sends the tier `id` only when the
selected model supports it.

## What Changed

- Removed the hardcoded `/fast` slash-command variant and introduced
dynamic service-tier command items in the composer and command popup.
- Added toggle behavior for service-tier commands: invoking `/name`
selects that tier, and invoking it again clears the selection.
- Preserved the existing Fast-mode keybinding/status affordances by
resolving the current model tier whose name is `fast`, while still
sending the tier request value such as `priority`.
- Persisted service-tier selections as raw request strings so non-fast
tiers can round-trip through config.
- Updated the Bedrock catalog entry to advertise fast support through
`service_tiers` with `id: "priority"` and `name: "fast"`.
- Added defensive filtering in core so unsupported selected service
tiers are omitted from `/responses` requests.

## Validation

- Added/updated coverage for dynamic service-tier slash command lookup,
popup descriptions, composer dispatch, TUI fast toggling, and
unsupported-tier omission in core request construction.
- Local tests were not run per request.

---------

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Ahmed Ibrahim
2026-05-08 20:09:51 +03:00
committed by GitHub
Unverified
parent 47f1d7b40b
commit 7c0e54bf59
24 changed files with 919 additions and 412 deletions
+2
View File
@@ -711,6 +711,8 @@ impl ModelClient {
prompt.output_schema_strict,
);
let prompt_cache_key = Some(self.state.thread_id.to_string());
let service_tier =
service_tier.filter(|service_tier| model_info.supports_service_tier(service_tier));
let request = ResponsesApiRequest {
model: model_info.slug.clone(),
instructions: instructions.clone(),
+5 -4
View File
@@ -7,7 +7,6 @@ use codex_config::types::SessionPickerViewMode;
use codex_config::types::ToolSuggestDisabledTool;
use codex_features::FEATURES;
use codex_protocol::config_types::Personality;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::config_types::TrustLevel;
use codex_protocol::openai_models::ReasoningEffort;
use std::collections::BTreeMap;
@@ -33,7 +32,7 @@ pub enum ConfigEdit {
effort: Option<ReasoningEffort>,
},
/// Update the service tier preference for future turns.
SetServiceTier { service_tier: Option<ServiceTier> },
SetServiceTier { service_tier: Option<String> },
/// Update the active (or default) model personality.
SetModelPersonality { personality: Option<Personality> },
/// Toggle the acknowledgement flag under `[notice]`.
@@ -536,7 +535,9 @@ impl ConfigDocument {
}),
ConfigEdit::SetServiceTier { service_tier } => Ok(self.write_profile_value(
&["service_tier"],
service_tier.map(|service_tier| value(service_tier.to_string())),
service_tier
.as_ref()
.map(|service_tier| value(service_tier.clone())),
)),
ConfigEdit::SetModelPersonality { personality } => Ok(self.write_profile_value(
&["personality"],
@@ -1114,7 +1115,7 @@ impl ConfigEditsBuilder {
self
}
pub fn set_service_tier(mut self, service_tier: Option<ServiceTier>) -> Self {
pub fn set_service_tier(mut self, service_tier: Option<String>) -> Self {
self.edits.push(ConfigEdit::SetServiceTier { service_tier });
self
}
@@ -521,6 +521,10 @@ impl Session {
&per_turn_config.agent_roles,
));
let mut per_turn_config = per_turn_config;
per_turn_config.service_tier = per_turn_config
.service_tier
.filter(|service_tier| model_info.supports_service_tier(service_tier));
let per_turn_config = Arc::new(per_turn_config);
let turn_metadata_state = Arc::new(TurnMetadataState::new(
session_id.to_string(),