mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[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:
@@ -0,0 +1,146 @@
|
||||
//! Service-tier selection and model-catalog helpers for `ChatWidget`.
|
||||
|
||||
use super::ChatWidget;
|
||||
use crate::app_command::AppCommand;
|
||||
use crate::app_event::AppEvent;
|
||||
use crate::bottom_pane::slash_commands::ServiceTierCommand;
|
||||
use codex_features::Feature;
|
||||
use codex_protocol::config_types::ServiceTier;
|
||||
use codex_protocol::openai_models::SPEED_TIER_FAST;
|
||||
|
||||
impl ChatWidget {
|
||||
pub(crate) fn set_service_tier(&mut self, service_tier: Option<String>) {
|
||||
self.config.service_tier = service_tier.clone();
|
||||
self.effective_service_tier = service_tier;
|
||||
self.refresh_model_dependent_surfaces();
|
||||
}
|
||||
|
||||
pub(crate) fn current_service_tier(&self) -> Option<&str> {
|
||||
self.effective_service_tier.as_deref()
|
||||
}
|
||||
|
||||
pub(crate) fn configured_service_tier(&self) -> Option<String> {
|
||||
self.config.service_tier.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn fast_default_opt_out(&self) -> Option<bool> {
|
||||
self.config.notices.fast_default_opt_out
|
||||
}
|
||||
|
||||
pub(crate) fn should_show_fast_status(&self, model: &str, service_tier: Option<&str>) -> bool {
|
||||
service_tier.is_some_and(|service_tier| {
|
||||
service_tier == ServiceTier::Fast.request_value()
|
||||
&& self.model_supports_service_tier(model, service_tier)
|
||||
}) && self.has_chatgpt_account
|
||||
}
|
||||
|
||||
pub(super) fn fast_mode_enabled(&self) -> bool {
|
||||
self.config.features.enabled(Feature::FastMode)
|
||||
}
|
||||
|
||||
pub(crate) fn can_toggle_fast_mode_from_keybinding(&self) -> bool {
|
||||
self.fast_mode_enabled()
|
||||
&& self.current_model_fast_service_tier().is_some()
|
||||
&& !self.is_user_turn_pending_or_running()
|
||||
&& self.bottom_pane.no_modal_or_popup_active()
|
||||
}
|
||||
|
||||
pub(crate) fn toggle_fast_mode_from_ui(&mut self) {
|
||||
let Some(fast_tier) = self.current_model_fast_service_tier() else {
|
||||
return;
|
||||
};
|
||||
let next_tier = if self.current_service_tier() == Some(fast_tier.id.as_str()) {
|
||||
None
|
||||
} else {
|
||||
Some(fast_tier.id)
|
||||
};
|
||||
self.set_service_tier_selection(next_tier);
|
||||
}
|
||||
|
||||
pub(crate) fn toggle_service_tier_from_ui(&mut self, command: ServiceTierCommand) {
|
||||
let next_tier = if self.current_service_tier() == Some(command.id.as_str()) {
|
||||
None
|
||||
} else {
|
||||
Some(command.id)
|
||||
};
|
||||
self.set_service_tier_selection(next_tier);
|
||||
}
|
||||
|
||||
pub(super) fn sync_service_tier_commands(&mut self) {
|
||||
self.bottom_pane
|
||||
.set_service_tier_commands_enabled(self.fast_mode_enabled());
|
||||
self.bottom_pane
|
||||
.set_service_tier_commands(self.current_model_service_tier_commands());
|
||||
}
|
||||
|
||||
pub(super) fn current_model_service_tier_commands(&self) -> Vec<ServiceTierCommand> {
|
||||
let model = self.current_model();
|
||||
self.model_catalog
|
||||
.try_list_models()
|
||||
.ok()
|
||||
.and_then(|models| {
|
||||
models
|
||||
.into_iter()
|
||||
.find(|preset| preset.model == model)
|
||||
.map(|preset| {
|
||||
preset
|
||||
.service_tiers
|
||||
.into_iter()
|
||||
.map(|tier| ServiceTierCommand {
|
||||
id: tier.id,
|
||||
name: tier.name,
|
||||
description: tier.description,
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn set_service_tier_selection(&mut self, service_tier: Option<String>) {
|
||||
if service_tier.is_none() {
|
||||
self.config.notices.fast_default_opt_out = Some(true);
|
||||
}
|
||||
self.set_service_tier(service_tier.clone());
|
||||
self.app_event_tx
|
||||
.send(AppEvent::CodexOp(AppCommand::override_turn_context(
|
||||
/*cwd*/ None,
|
||||
/*approval_policy*/ None,
|
||||
/*approvals_reviewer*/ None,
|
||||
/*permission_profile*/ None,
|
||||
/*windows_sandbox_level*/ None,
|
||||
/*model*/ None,
|
||||
/*effort*/ None,
|
||||
/*summary*/ None,
|
||||
Some(service_tier.clone()),
|
||||
/*collaboration_mode*/ None,
|
||||
/*personality*/ None,
|
||||
)));
|
||||
self.app_event_tx
|
||||
.send(AppEvent::PersistServiceTierSelection { service_tier });
|
||||
}
|
||||
|
||||
fn model_supports_service_tier(&self, model: &str, service_tier: &str) -> bool {
|
||||
self.model_catalog
|
||||
.try_list_models()
|
||||
.ok()
|
||||
.and_then(|models| {
|
||||
models
|
||||
.into_iter()
|
||||
.find(|preset| preset.model == model)
|
||||
.map(|preset| {
|
||||
preset
|
||||
.service_tiers
|
||||
.iter()
|
||||
.any(|tier| tier.id == service_tier)
|
||||
})
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn current_model_fast_service_tier(&self) -> Option<ServiceTierCommand> {
|
||||
self.current_model_service_tier_commands()
|
||||
.into_iter()
|
||||
.find(|tier| tier.name.eq_ignore_ascii_case(SPEED_TIER_FAST))
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,10 @@ use super::goal_validation::GoalObjectiveValidationSource;
|
||||
use super::*;
|
||||
use crate::app_event::ThreadGoalSetMode;
|
||||
use crate::bottom_pane::prompt_args::parse_slash_name;
|
||||
use crate::bottom_pane::slash_commands;
|
||||
use crate::bottom_pane::slash_commands::BuiltinCommandFlags;
|
||||
use crate::bottom_pane::slash_commands::ServiceTierCommand;
|
||||
use crate::bottom_pane::slash_commands::SlashCommandItem;
|
||||
use crate::bottom_pane::slash_commands::find_slash_command;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum SlashCommandDispatchSource {
|
||||
@@ -48,6 +51,20 @@ impl ChatWidget {
|
||||
self.bottom_pane.record_pending_slash_command_history();
|
||||
}
|
||||
|
||||
pub(super) fn handle_service_tier_command_dispatch(&mut self, command: ServiceTierCommand) {
|
||||
if self.active_side_conversation {
|
||||
self.add_error_message(format!(
|
||||
"'/{}' is unavailable in side conversations. {SIDE_SLASH_COMMAND_UNAVAILABLE_HINT}",
|
||||
command.name
|
||||
));
|
||||
self.bottom_pane.drain_pending_submission_state();
|
||||
self.bottom_pane.record_pending_slash_command_history();
|
||||
return;
|
||||
}
|
||||
self.toggle_service_tier_from_ui(command);
|
||||
self.bottom_pane.record_pending_slash_command_history();
|
||||
}
|
||||
|
||||
/// Dispatch an inline slash command and record its staged local-history entry.
|
||||
///
|
||||
/// Inline command arguments may later be prepared through the normal submission pipeline, but
|
||||
@@ -184,9 +201,6 @@ impl ChatWidget {
|
||||
SlashCommand::Model => {
|
||||
self.open_model_popup();
|
||||
}
|
||||
SlashCommand::Fast => {
|
||||
self.toggle_fast_mode_from_ui();
|
||||
}
|
||||
SlashCommand::Realtime => {
|
||||
if !self.realtime_conversation_enabled() {
|
||||
return;
|
||||
@@ -572,27 +586,6 @@ impl ChatWidget {
|
||||
} = prepared;
|
||||
let trimmed = args.trim();
|
||||
match cmd {
|
||||
SlashCommand::Fast => {
|
||||
match trimmed.to_ascii_lowercase().as_str() {
|
||||
"on" => self.set_service_tier_selection(Some(ServiceTier::Fast)),
|
||||
"off" => self.set_service_tier_selection(/*service_tier*/ None),
|
||||
"status" => {
|
||||
let status =
|
||||
if matches!(self.current_service_tier(), Some(ServiceTier::Fast)) {
|
||||
"on"
|
||||
} else {
|
||||
"off"
|
||||
};
|
||||
self.add_info_message(
|
||||
format!("Fast mode is {status}."),
|
||||
/*hint*/ None,
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
self.add_error_message("Usage: /fast [on|off|status]".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
SlashCommand::Ide => {
|
||||
self.handle_ide_command_args(trimmed);
|
||||
}
|
||||
@@ -813,7 +806,9 @@ impl ChatWidget {
|
||||
return QueueDrain::Stop;
|
||||
}
|
||||
|
||||
let Some(cmd) = slash_commands::find_builtin_command(name, self.builtin_command_flags())
|
||||
let service_tier_commands = self.current_model_service_tier_commands();
|
||||
let Some(command) =
|
||||
find_slash_command(name, self.builtin_command_flags(), &service_tier_commands)
|
||||
else {
|
||||
self.add_info_message(
|
||||
format!(
|
||||
@@ -825,11 +820,19 @@ impl ChatWidget {
|
||||
};
|
||||
|
||||
if rest.is_empty() {
|
||||
self.dispatch_command(cmd);
|
||||
return self.queued_command_drain_result(cmd);
|
||||
return match command {
|
||||
SlashCommandItem::Builtin(cmd) => {
|
||||
self.dispatch_command(cmd);
|
||||
self.queued_command_drain_result(cmd)
|
||||
}
|
||||
SlashCommandItem::ServiceTier(command) => {
|
||||
self.handle_service_tier_command_dispatch(command);
|
||||
QueueDrain::Continue
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if !cmd.supports_inline_args() {
|
||||
if !command.supports_inline_args() {
|
||||
self.submit_user_message(UserMessage {
|
||||
text,
|
||||
local_images,
|
||||
@@ -839,6 +842,16 @@ impl ChatWidget {
|
||||
});
|
||||
return QueueDrain::Stop;
|
||||
}
|
||||
let SlashCommandItem::Builtin(cmd) = command else {
|
||||
self.submit_user_message(UserMessage {
|
||||
text,
|
||||
local_images,
|
||||
remote_image_urls,
|
||||
text_elements,
|
||||
mention_bindings,
|
||||
});
|
||||
return QueueDrain::Stop;
|
||||
};
|
||||
|
||||
let trimmed_start = rest.trim_start();
|
||||
let leading_trimmed = rest.len().saturating_sub(trimmed_start.len());
|
||||
@@ -867,7 +880,7 @@ impl ChatWidget {
|
||||
self.queued_command_drain_result(cmd)
|
||||
}
|
||||
|
||||
fn builtin_command_flags(&self) -> slash_commands::BuiltinCommandFlags {
|
||||
fn builtin_command_flags(&self) -> BuiltinCommandFlags {
|
||||
#[cfg(target_os = "windows")]
|
||||
let allow_elevate_sandbox = {
|
||||
let windows_sandbox_level = WindowsSandboxLevel::from_config(&self.config);
|
||||
@@ -876,12 +889,12 @@ impl ChatWidget {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let allow_elevate_sandbox = false;
|
||||
|
||||
slash_commands::BuiltinCommandFlags {
|
||||
BuiltinCommandFlags {
|
||||
collaboration_modes_enabled: self.collaboration_modes_enabled(),
|
||||
connectors_enabled: self.connectors_enabled(),
|
||||
plugins_command_enabled: self.config.features.enabled(Feature::Plugins),
|
||||
goal_command_enabled: self.config.features.enabled(Feature::Goals),
|
||||
fast_command_enabled: self.fast_mode_enabled(),
|
||||
service_tier_commands_enabled: self.fast_mode_enabled(),
|
||||
personality_command_enabled: self.config.features.enabled(Feature::Personality),
|
||||
realtime_conversation_enabled: self.realtime_conversation_enabled(),
|
||||
audio_device_selection_enabled: self.realtime_audio_device_selection_enabled(),
|
||||
@@ -895,8 +908,7 @@ impl ChatWidget {
|
||||
return QueueDrain::Stop;
|
||||
}
|
||||
match cmd {
|
||||
SlashCommand::Fast
|
||||
| SlashCommand::Ide
|
||||
SlashCommand::Ide
|
||||
| SlashCommand::Status
|
||||
| SlashCommand::DebugConfig
|
||||
| SlashCommand::Ps
|
||||
|
||||
@@ -10,6 +10,7 @@ use crate::legacy_core::config::Config;
|
||||
use crate::status::format_tokens_compact;
|
||||
use codex_app_server_protocol::AskForApproval;
|
||||
use codex_protocol::config_types::ApprovalsReviewer;
|
||||
use codex_protocol::config_types::ServiceTier;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_utils_sandbox_summary::summarize_permission_profile;
|
||||
|
||||
@@ -648,7 +649,7 @@ impl ChatWidget {
|
||||
)),
|
||||
StatusLineItem::SessionId => self.thread_id.map(|id| id.to_string()),
|
||||
StatusLineItem::FastMode => Some(
|
||||
if matches!(self.current_service_tier(), Some(ServiceTier::Fast)) {
|
||||
if self.current_service_tier() == Some(ServiceTier::Fast.request_value()) {
|
||||
"Fast on".to_string()
|
||||
} else {
|
||||
"Fast off".to_string()
|
||||
@@ -779,13 +780,18 @@ impl ChatWidget {
|
||||
|
||||
fn model_with_reasoning_display_name(&self) -> String {
|
||||
let label = Self::status_line_reasoning_effort_label(self.effective_reasoning_effort());
|
||||
let fast_label =
|
||||
if self.should_show_fast_status(self.current_model(), self.current_service_tier()) {
|
||||
" fast"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
format!("{} {label}{fast_label}", self.model_display_name())
|
||||
let service_tier_label = self
|
||||
.current_service_tier()
|
||||
.and_then(|service_tier| {
|
||||
self.current_model_service_tier_commands()
|
||||
.into_iter()
|
||||
.find(|tier| tier.id == service_tier)
|
||||
.map(|tier| tier.name)
|
||||
})
|
||||
.filter(|_| self.has_chatgpt_account)
|
||||
.map(|tier| format!(" {tier}"))
|
||||
.unwrap_or_default();
|
||||
format!("{} {label}{service_tier_label}", self.model_display_name())
|
||||
}
|
||||
|
||||
/// Computes the compact runtime status label used by word-based status items.
|
||||
|
||||
@@ -182,10 +182,7 @@ 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
|
||||
.as_deref()
|
||||
.and_then(ServiceTier::from_request_value);
|
||||
let effective_service_tier = cfg.service_tier.clone();
|
||||
let mut widget = ChatWidget {
|
||||
app_event_tx,
|
||||
codex_op_target: super::CodexOpTarget::Direct(op_tx),
|
||||
@@ -391,8 +388,12 @@ pub(crate) fn set_chatgpt_auth(chat: &mut ChatWidget) {
|
||||
}
|
||||
|
||||
fn test_model_info(slug: &str, priority: i32, supports_fast_mode: bool) -> ModelInfo {
|
||||
let additional_speed_tiers = if supports_fast_mode {
|
||||
vec![codex_protocol::openai_models::SPEED_TIER_FAST]
|
||||
let service_tiers = if supports_fast_mode {
|
||||
vec![json!({
|
||||
"id": ServiceTier::Fast.request_value(),
|
||||
"name": "fast",
|
||||
"description": "Fastest inference with increased plan usage"
|
||||
})]
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
@@ -406,7 +407,8 @@ fn test_model_info(slug: &str, priority: i32, supports_fast_mode: bool) -> Model
|
||||
"visibility": "list",
|
||||
"supported_in_api": true,
|
||||
"priority": priority,
|
||||
"additional_speed_tiers": additional_speed_tiers,
|
||||
"additional_speed_tiers": [],
|
||||
"service_tiers": service_tiers,
|
||||
"availability_nux": null,
|
||||
"upgrade": null,
|
||||
"base_instructions": "base instructions",
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
use super::*;
|
||||
use crate::bottom_pane::slash_commands::ServiceTierCommand;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
fn fast_tier_command() -> ServiceTierCommand {
|
||||
ServiceTierCommand {
|
||||
id: ServiceTier::Fast.request_value().to_string(),
|
||||
name: "fast".to_string(),
|
||||
description: "Fastest inference with increased plan usage".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn complete_turn_with_message(chat: &mut ChatWidget, turn_id: &str, message: Option<&str>) {
|
||||
if let Some(message) = message {
|
||||
complete_assistant_message(
|
||||
@@ -1023,9 +1032,8 @@ async fn slash_rename_without_existing_thread_name_starts_empty() {
|
||||
#[tokio::test]
|
||||
async fn usage_error_slash_command_is_available_from_local_recall() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.3-codex")).await;
|
||||
chat.set_feature_enabled(Feature::FastMode, /*enabled*/ true);
|
||||
|
||||
submit_composer_text(&mut chat, "/fast maybe");
|
||||
submit_composer_text(&mut chat, "/raw maybe");
|
||||
|
||||
assert_eq!(chat.bottom_pane.composer_text(), "");
|
||||
|
||||
@@ -1036,10 +1044,10 @@ async fn usage_error_slash_command_is_available_from_local_recall() {
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
assert!(
|
||||
rendered.contains("Usage: /fast [on|off|status]"),
|
||||
rendered.contains("Usage: /raw [on|off]"),
|
||||
"expected usage message, got: {rendered:?}"
|
||||
);
|
||||
assert_eq!(recall_latest_after_clearing(&mut chat), "/fast maybe");
|
||||
assert_eq!(recall_latest_after_clearing(&mut chat), "/raw maybe");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -1811,10 +1819,11 @@ async fn slash_rollout_handles_missing_path() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn fast_slash_command_updates_and_persists_local_service_tier() {
|
||||
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(Some("gpt-5.3-codex")).await;
|
||||
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
|
||||
set_fast_mode_test_catalog(&mut chat);
|
||||
chat.set_feature_enabled(Feature::FastMode, /*enabled*/ true);
|
||||
|
||||
chat.dispatch_command(SlashCommand::Fast);
|
||||
chat.handle_service_tier_command_dispatch(fast_tier_command());
|
||||
|
||||
let events = std::iter::from_fn(|| rx.try_recv().ok()).collect::<Vec<_>>();
|
||||
assert!(
|
||||
@@ -1831,8 +1840,9 @@ async fn fast_slash_command_updates_and_persists_local_service_tier() {
|
||||
events.iter().any(|event| matches!(
|
||||
event,
|
||||
AppEvent::PersistServiceTierSelection {
|
||||
service_tier: Some(ServiceTier::Fast),
|
||||
service_tier: Some(service_tier),
|
||||
}
|
||||
if service_tier == ServiceTier::Fast.request_value()
|
||||
)),
|
||||
"expected fast-mode persistence app event; events: {events:?}"
|
||||
);
|
||||
@@ -1842,7 +1852,8 @@ async fn fast_slash_command_updates_and_persists_local_service_tier() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn fast_keybinding_toggle_uses_same_events_as_fast_slash_command() {
|
||||
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(Some("gpt-5.3-codex")).await;
|
||||
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
|
||||
set_fast_mode_test_catalog(&mut chat);
|
||||
chat.set_feature_enabled(Feature::FastMode, /*enabled*/ true);
|
||||
|
||||
chat.toggle_fast_mode_from_ui();
|
||||
@@ -1862,8 +1873,9 @@ async fn fast_keybinding_toggle_uses_same_events_as_fast_slash_command() {
|
||||
events.iter().any(|event| matches!(
|
||||
event,
|
||||
AppEvent::PersistServiceTierSelection {
|
||||
service_tier: Some(ServiceTier::Fast),
|
||||
service_tier: Some(service_tier),
|
||||
}
|
||||
if service_tier == ServiceTier::Fast.request_value()
|
||||
)),
|
||||
"expected fast-mode persistence app event; events: {events:?}"
|
||||
);
|
||||
@@ -1873,7 +1885,8 @@ async fn fast_keybinding_toggle_uses_same_events_as_fast_slash_command() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn fast_keybinding_toggle_requires_feature_and_idle_surface() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.3-codex")).await;
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
|
||||
set_fast_mode_test_catalog(&mut chat);
|
||||
chat.set_feature_enabled(Feature::FastMode, /*enabled*/ false);
|
||||
|
||||
assert!(!chat.can_toggle_fast_mode_from_keybinding());
|
||||
@@ -1887,12 +1900,13 @@ async fn fast_keybinding_toggle_requires_feature_and_idle_surface() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn user_turn_carries_service_tier_after_fast_toggle() {
|
||||
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(Some("gpt-5.3-codex")).await;
|
||||
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
|
||||
chat.thread_id = Some(ThreadId::new());
|
||||
set_chatgpt_auth(&mut chat);
|
||||
set_fast_mode_test_catalog(&mut chat);
|
||||
chat.set_feature_enabled(Feature::FastMode, /*enabled*/ true);
|
||||
|
||||
chat.dispatch_command(SlashCommand::Fast);
|
||||
chat.handle_service_tier_command_dispatch(fast_tier_command());
|
||||
|
||||
let _events = std::iter::from_fn(|| rx.try_recv().ok()).collect::<Vec<_>>();
|
||||
|
||||
@@ -1911,13 +1925,14 @@ async fn user_turn_carries_service_tier_after_fast_toggle() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn queued_fast_slash_applies_before_next_queued_message() {
|
||||
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(Some("gpt-5.3-codex")).await;
|
||||
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
|
||||
chat.thread_id = Some(ThreadId::new());
|
||||
set_chatgpt_auth(&mut chat);
|
||||
set_fast_mode_test_catalog(&mut chat);
|
||||
chat.set_feature_enabled(Feature::FastMode, /*enabled*/ true);
|
||||
handle_turn_started(&mut chat, "turn-1");
|
||||
|
||||
queue_composer_text_with_tab(&mut chat, "/fast on");
|
||||
queue_composer_text_with_tab(&mut chat, "/fast");
|
||||
queue_composer_text_with_tab(&mut chat, "hello after fast");
|
||||
|
||||
complete_turn_with_message(&mut chat, "turn-1", Some("done"));
|
||||
@@ -1952,15 +1967,16 @@ async fn queued_fast_slash_applies_before_next_queued_message() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn user_turn_sends_standard_override_after_fast_is_turned_off() {
|
||||
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(Some("gpt-5.3-codex")).await;
|
||||
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
|
||||
chat.thread_id = Some(ThreadId::new());
|
||||
set_chatgpt_auth(&mut chat);
|
||||
set_fast_mode_test_catalog(&mut chat);
|
||||
chat.set_feature_enabled(Feature::FastMode, /*enabled*/ true);
|
||||
|
||||
chat.dispatch_command(SlashCommand::Fast);
|
||||
chat.handle_service_tier_command_dispatch(fast_tier_command());
|
||||
let _events = std::iter::from_fn(|| rx.try_recv().ok()).collect::<Vec<_>>();
|
||||
|
||||
chat.dispatch_command_with_args(SlashCommand::Fast, "off".to_string(), Vec::new());
|
||||
chat.handle_service_tier_command_dispatch(fast_tier_command());
|
||||
let events = std::iter::from_fn(|| rx.try_recv().ok()).collect::<Vec<_>>();
|
||||
assert!(
|
||||
events.iter().any(|event| matches!(
|
||||
|
||||
@@ -1124,7 +1124,7 @@ async fn fast_status_indicator_requires_chatgpt_auth() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
|
||||
set_fast_mode_test_catalog(&mut chat);
|
||||
assert!(get_available_model(&chat, "gpt-5.4").supports_fast_mode());
|
||||
chat.set_service_tier(Some(ServiceTier::Fast));
|
||||
chat.set_service_tier(Some(ServiceTier::Fast.request_value().to_string()));
|
||||
|
||||
assert!(!chat.should_show_fast_status(chat.current_model(), chat.current_service_tier(),));
|
||||
|
||||
@@ -1140,7 +1140,7 @@ async fn fast_status_indicator_is_hidden_for_models_without_fast_support() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.3-codex")).await;
|
||||
set_fast_mode_test_catalog(&mut chat);
|
||||
assert!(!get_available_model(&chat, "gpt-5.3-codex").supports_fast_mode());
|
||||
chat.set_service_tier(Some(ServiceTier::Fast));
|
||||
chat.set_service_tier(Some(ServiceTier::Fast.request_value().to_string()));
|
||||
set_chatgpt_auth(&mut chat);
|
||||
set_fast_mode_test_catalog(&mut chat);
|
||||
assert!(!get_available_model(&chat, "gpt-5.3-codex").supports_fast_mode());
|
||||
@@ -1533,7 +1533,7 @@ async fn status_line_fast_mode_renders_on_and_off() {
|
||||
chat.refresh_status_line();
|
||||
assert_eq!(status_line_text(&chat), Some("Fast off".to_string()));
|
||||
|
||||
chat.set_service_tier(Some(ServiceTier::Fast));
|
||||
chat.set_service_tier(Some(ServiceTier::Fast.request_value().to_string()));
|
||||
chat.refresh_status_line();
|
||||
assert_eq!(status_line_text(&chat), Some("Fast on".to_string()));
|
||||
}
|
||||
@@ -1546,7 +1546,7 @@ async fn status_line_fast_mode_footer_snapshot() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
chat.show_welcome_banner = false;
|
||||
chat.config.tui_status_line = Some(vec!["fast-mode".to_string()]);
|
||||
chat.set_service_tier(Some(ServiceTier::Fast));
|
||||
chat.set_service_tier(Some(ServiceTier::Fast.request_value().to_string()));
|
||||
chat.refresh_status_line();
|
||||
|
||||
let width = 80;
|
||||
@@ -1573,7 +1573,7 @@ async fn status_line_model_with_reasoning_includes_fast_for_fast_capable_models(
|
||||
"current-dir".to_string(),
|
||||
]);
|
||||
chat.set_reasoning_effort(Some(ReasoningEffortConfig::XHigh));
|
||||
chat.set_service_tier(Some(ServiceTier::Fast));
|
||||
chat.set_service_tier(Some(ServiceTier::Fast.request_value().to_string()));
|
||||
set_chatgpt_auth(&mut chat);
|
||||
set_fast_mode_test_catalog(&mut chat);
|
||||
assert!(get_available_model(&chat, "gpt-5.4").supports_fast_mode());
|
||||
@@ -1721,7 +1721,7 @@ async fn status_line_model_with_reasoning_fast_footer_snapshot() {
|
||||
"current-dir".to_string(),
|
||||
]);
|
||||
chat.set_reasoning_effort(Some(ReasoningEffortConfig::XHigh));
|
||||
chat.set_service_tier(Some(ServiceTier::Fast));
|
||||
chat.set_service_tier(Some(ServiceTier::Fast.request_value().to_string()));
|
||||
set_chatgpt_auth(&mut chat);
|
||||
set_fast_mode_test_catalog(&mut chat);
|
||||
assert!(get_available_model(&chat, "gpt-5.4").supports_fast_mode());
|
||||
@@ -1755,7 +1755,7 @@ async fn status_line_model_with_reasoning_context_remaining_footer_snapshot() {
|
||||
"current-dir".to_string(),
|
||||
]);
|
||||
chat.set_reasoning_effort(Some(ReasoningEffortConfig::XHigh));
|
||||
chat.set_service_tier(Some(ServiceTier::Fast));
|
||||
chat.set_service_tier(Some(ServiceTier::Fast.request_value().to_string()));
|
||||
set_chatgpt_auth(&mut chat);
|
||||
set_fast_mode_test_catalog(&mut chat);
|
||||
assert!(get_available_model(&chat, "gpt-5.4").supports_fast_mode());
|
||||
|
||||
Reference in New Issue
Block a user