mirror of
https://github.com/openai/codex.git
synced 2026-10-22 17:58:46 +00:00
Move TUI tier helpers out of widget types
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
|
||||
use super::resize_reflow::trailing_run_start;
|
||||
use super::*;
|
||||
use crate::chatwidget::service_tiers::service_tier_display_name;
|
||||
|
||||
const SHUTDOWN_FIRST_EXIT_TIMEOUT: Duration = Duration::from_secs(/*secs*/ 2);
|
||||
|
||||
@@ -1265,7 +1266,8 @@ impl App {
|
||||
let mut message = match service_tier.as_ref() {
|
||||
Some(service_tier) => format!(
|
||||
"Service tier set to {}",
|
||||
self.chat_widget.service_tier_display_name(
|
||||
service_tier_display_name(
|
||||
&self.chat_widget,
|
||||
self.chat_widget.current_model(),
|
||||
service_tier,
|
||||
)
|
||||
|
||||
@@ -72,6 +72,8 @@ use codex_protocol::config_types::CollaborationMode;
|
||||
use codex_protocol::config_types::CollaborationModeMask;
|
||||
use codex_protocol::config_types::ModeKind;
|
||||
use codex_protocol::config_types::Settings;
|
||||
use codex_protocol::config_types::priority_service_tier;
|
||||
use codex_protocol::models::AdditionalPermissionProfile as CoreAdditionalPermissionProfile;
|
||||
use codex_protocol::models::FileSystemPermissions;
|
||||
use codex_protocol::models::NetworkPermissions;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
@@ -3703,7 +3705,7 @@ async fn clear_ui_header_shows_fast_status_for_fast_capable_models() {
|
||||
app.chat_widget
|
||||
.set_reasoning_effort(Some(ReasoningEffortConfig::XHigh));
|
||||
app.chat_widget
|
||||
.set_service_tier(Some(codex_protocol::config_types::ServiceTier::priority()));
|
||||
.set_service_tier(Some(priority_service_tier()));
|
||||
set_chatgpt_auth(&mut app.chat_widget);
|
||||
set_fast_mode_test_catalog(&mut app.chat_widget);
|
||||
|
||||
@@ -4323,14 +4325,11 @@ fn active_turn_steer_race_extracts_actual_turn_id_from_mismatch() {
|
||||
async fn fresh_session_config_uses_current_service_tier() {
|
||||
let mut app = make_test_app().await;
|
||||
app.chat_widget
|
||||
.set_service_tier(Some(codex_protocol::config_types::ServiceTier::priority()));
|
||||
.set_service_tier(Some(priority_service_tier()));
|
||||
|
||||
let config = app.fresh_session_config();
|
||||
|
||||
assert_eq!(
|
||||
config.service_tier,
|
||||
Some(codex_protocol::config_types::ServiceTier::priority())
|
||||
);
|
||||
assert_eq!(config.service_tier, Some(priority_service_tier()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -185,6 +185,10 @@ use super::slash_commands;
|
||||
use super::slash_commands::BuiltinCommandFlags;
|
||||
use super::slash_commands::ServiceTierCommand;
|
||||
use super::slash_commands::SlashCommandAction;
|
||||
use super::slash_commands::command_as_builtin;
|
||||
use super::slash_commands::command_available_during_task;
|
||||
use super::slash_commands::command_name;
|
||||
use super::slash_commands::command_supports_inline_args;
|
||||
use crate::bottom_pane::paste_burst::FlushResult;
|
||||
use crate::bottom_pane::prompt_args::parse_slash_name;
|
||||
use crate::key_hint::KeyBindingListExt;
|
||||
@@ -1741,19 +1745,19 @@ impl ChatComposer {
|
||||
popup.on_composer_text_change(first_line.to_string());
|
||||
let selected_cmd = popup.selected_item();
|
||||
if let Some(cmd) = selected_cmd {
|
||||
if cmd.as_builtin() == Some(SlashCommand::Skills) {
|
||||
if command_as_builtin(&cmd) == Some(SlashCommand::Skills) {
|
||||
self.stage_selected_slash_command_history(&cmd);
|
||||
self.textarea.set_text_clearing_elements("");
|
||||
self.is_bash_mode = false;
|
||||
return (InputResult::Command(cmd), true);
|
||||
}
|
||||
|
||||
let selected_command_text = format!("/{}", cmd.command());
|
||||
let selected_command_text = format!("/{}", command_name(&cmd));
|
||||
let starts_with_cmd =
|
||||
first_line.trim_start().starts_with(&selected_command_text);
|
||||
if !starts_with_cmd {
|
||||
self.textarea
|
||||
.set_text_clearing_elements(&format!("/{} ", cmd.command()));
|
||||
.set_text_clearing_elements(&format!("/{} ", command_name(&cmd)));
|
||||
if !self.textarea.text().is_empty() {
|
||||
self.textarea.set_cursor(self.textarea.text().len());
|
||||
}
|
||||
@@ -1777,10 +1781,10 @@ impl ChatComposer {
|
||||
if let Some(cmd) = popup.selected_item() {
|
||||
let starts_with_cmd = first_line
|
||||
.trim_start()
|
||||
.starts_with(&format!("/{}", cmd.command()));
|
||||
.starts_with(&format!("/{}", command_name(&cmd)));
|
||||
if !starts_with_cmd {
|
||||
self.textarea
|
||||
.set_text_clearing_elements(&format!("/{} ", cmd.command()));
|
||||
.set_text_clearing_elements(&format!("/{} ", command_name(&cmd)));
|
||||
self.is_bash_mode = false;
|
||||
}
|
||||
if !self.textarea.text().is_empty() {
|
||||
@@ -2856,7 +2860,7 @@ impl ChatComposer {
|
||||
&self.service_tier_commands,
|
||||
)?;
|
||||
|
||||
if !cmd.supports_inline_args() {
|
||||
if !command_supports_inline_args(&cmd) {
|
||||
return None;
|
||||
}
|
||||
if self.reject_slash_command_if_unavailable(&cmd) {
|
||||
@@ -2905,12 +2909,12 @@ impl ChatComposer {
|
||||
}
|
||||
|
||||
fn reject_slash_command_if_unavailable(&self, cmd: &SlashCommandAction) -> bool {
|
||||
if !self.is_task_running || cmd.available_during_task() {
|
||||
if !self.is_task_running || command_available_during_task(cmd) {
|
||||
return false;
|
||||
}
|
||||
let message = format!(
|
||||
"'/{}' is disabled while a task is in progress.",
|
||||
cmd.command()
|
||||
command_name(cmd)
|
||||
);
|
||||
self.app_event_tx.send(AppEvent::InsertHistoryCell(Box::new(
|
||||
history_cell::new_error_event(message),
|
||||
@@ -2950,7 +2954,7 @@ impl ChatComposer {
|
||||
/// Popup filtering text can be partial, so recording the selected command avoids recalling
|
||||
/// `/di` after the user actually accepted `/diff`.
|
||||
fn stage_selected_slash_command_history(&mut self, cmd: &SlashCommandAction) {
|
||||
self.stage_slash_command_history_text(format!("/{}", cmd.command()));
|
||||
self.stage_slash_command_history_text(format!("/{}", command_name(cmd)));
|
||||
}
|
||||
|
||||
/// Store the provided command text and the current composer adornments in the pending slot.
|
||||
@@ -7451,7 +7455,7 @@ mod tests {
|
||||
|
||||
match &composer.active_popup {
|
||||
ActivePopup::Command(popup) => match popup.selected_item() {
|
||||
Some(command) => assert_eq!(command.command(), "model"),
|
||||
Some(command) => assert_eq!(command_name(&command), "model"),
|
||||
None => panic!("no selected command for '/mo'"),
|
||||
},
|
||||
_ => panic!("slash popup not active after typing '/mo'"),
|
||||
@@ -7501,7 +7505,7 @@ mod tests {
|
||||
|
||||
match &composer.active_popup {
|
||||
ActivePopup::Command(popup) => match popup.selected_item() {
|
||||
Some(command) => assert_eq!(command.command(), "resume"),
|
||||
Some(command) => assert_eq!(command_name(&command), "resume"),
|
||||
None => panic!("no selected command for '/res'"),
|
||||
},
|
||||
_ => panic!("slash popup not active after typing '/res'"),
|
||||
@@ -7560,7 +7564,7 @@ mod tests {
|
||||
// Command result (not submit literal text) and clear its textarea.
|
||||
match result {
|
||||
InputResult::Command(cmd) => {
|
||||
assert_eq!(cmd.command(), "init");
|
||||
assert_eq!(command_name(&cmd), "init");
|
||||
}
|
||||
InputResult::CommandWithArgs(_, _, _) => {
|
||||
panic!("expected command dispatch without args for '/init'")
|
||||
@@ -7637,7 +7641,7 @@ mod tests {
|
||||
composer.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
|
||||
match result {
|
||||
InputResult::Command(cmd) => {
|
||||
assert_eq!(cmd.command(), "diff");
|
||||
assert_eq!(command_name(&cmd), "diff");
|
||||
}
|
||||
_ => panic!("expected Command result for '/diff'"),
|
||||
}
|
||||
@@ -8007,7 +8011,7 @@ mod tests {
|
||||
let (result, _needs_redraw) =
|
||||
composer.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
|
||||
match result {
|
||||
InputResult::Command(cmd) => assert_eq!(cmd.command(), "diff"),
|
||||
InputResult::Command(cmd) => assert_eq!(command_name(&cmd), "diff"),
|
||||
InputResult::CommandWithArgs(_, _, _) => {
|
||||
panic!("expected command dispatch without args for '/diff'")
|
||||
}
|
||||
@@ -8200,7 +8204,7 @@ mod tests {
|
||||
|
||||
match result {
|
||||
InputResult::Command(cmd) => {
|
||||
assert_eq!(cmd.command(), "mention");
|
||||
assert_eq!(command_name(&cmd), "mention");
|
||||
}
|
||||
InputResult::CommandWithArgs(_, _, _) => {
|
||||
panic!("expected command dispatch without args for '/mention'")
|
||||
@@ -8244,7 +8248,7 @@ mod tests {
|
||||
|
||||
match result {
|
||||
InputResult::CommandWithArgs(cmd, args, text_elements) => {
|
||||
assert_eq!(cmd.command(), "plan");
|
||||
assert_eq!(command_name(&cmd), "plan");
|
||||
assert_eq!(args, placeholder);
|
||||
assert_eq!(text_elements.len(), 1);
|
||||
assert_eq!(
|
||||
@@ -9923,7 +9927,7 @@ mod tests {
|
||||
|
||||
match result {
|
||||
InputResult::CommandWithArgs(cmd, args, text_elements) => {
|
||||
assert_eq!(cmd.command(), "plan");
|
||||
assert_eq!(command_name(&cmd), "plan");
|
||||
assert_eq!(args, "investigate this");
|
||||
assert!(text_elements.is_empty());
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ use super::selection_popup_common::render_rows_with_col_width_mode;
|
||||
use super::slash_commands;
|
||||
use super::slash_commands::ServiceTierCommand;
|
||||
use super::slash_commands::SlashCommandAction;
|
||||
use super::slash_commands::command_as_builtin;
|
||||
use super::slash_commands::command_description;
|
||||
use super::slash_commands::command_name;
|
||||
use crate::render::Insets;
|
||||
use crate::render::RectExt;
|
||||
use crate::slash_command::SlashCommand;
|
||||
@@ -73,8 +76,8 @@ impl CommandPopup {
|
||||
// Keep command availability in sync with the composer.
|
||||
let commands = slash_commands::commands_for_input(flags.into(), service_tier_commands)
|
||||
.into_iter()
|
||||
.filter(|command| !command.command().starts_with("debug"))
|
||||
.filter(|command| command.as_builtin() != Some(SlashCommand::Apps))
|
||||
.filter(|command| !command_name(command).starts_with("debug"))
|
||||
.filter(|command| command_as_builtin(command) != Some(SlashCommand::Apps))
|
||||
.collect();
|
||||
Self {
|
||||
command_filter: String::new(),
|
||||
@@ -135,8 +138,7 @@ impl CommandPopup {
|
||||
let mut out: Vec<(CommandItem, Option<Vec<usize>>)> = Vec::new();
|
||||
if filter.is_empty() {
|
||||
for command in self.commands.iter() {
|
||||
if command
|
||||
.as_builtin()
|
||||
if command_as_builtin(command)
|
||||
.is_some_and(|builtin| ALIAS_COMMANDS.contains(&builtin))
|
||||
{
|
||||
continue;
|
||||
@@ -174,7 +176,7 @@ impl CommandPopup {
|
||||
};
|
||||
|
||||
for command in self.commands.iter() {
|
||||
push_match(command.clone(), command.command(), None, 0);
|
||||
push_match(command.clone(), command_name(command), None, 0);
|
||||
}
|
||||
|
||||
out.extend(exact);
|
||||
@@ -193,8 +195,8 @@ impl CommandPopup {
|
||||
matches
|
||||
.into_iter()
|
||||
.map(|(item, indices)| {
|
||||
let name = format!("/{}", item.command());
|
||||
let description = item.description().to_string();
|
||||
let name = format!("/{}", command_name(&item));
|
||||
let description = command_description(&item).to_string();
|
||||
GenericDisplayRow {
|
||||
name,
|
||||
name_prefix_spans: Vec::new(),
|
||||
@@ -266,7 +268,7 @@ mod tests {
|
||||
// Access the filtered list via the selected command and ensure that
|
||||
// one of the matches is the new "init" command.
|
||||
let matches = popup.filtered_items();
|
||||
let has_init = matches.iter().any(|item| item.command() == "init");
|
||||
let has_init = matches.iter().any(|item| command_name(item) == "init");
|
||||
assert!(
|
||||
has_init,
|
||||
"expected '/init' to appear among filtered commands"
|
||||
@@ -282,7 +284,7 @@ mod tests {
|
||||
// command by default.
|
||||
let selected = popup.selected_item();
|
||||
match selected {
|
||||
Some(command) => assert_eq!(command.command(), "init"),
|
||||
Some(command) => assert_eq!(command_name(&command), "init"),
|
||||
None => panic!("expected a selected command for exact match"),
|
||||
}
|
||||
}
|
||||
@@ -293,7 +295,7 @@ mod tests {
|
||||
popup.on_composer_text_change("/mo".to_string());
|
||||
let matches = popup.filtered_items();
|
||||
match matches.first() {
|
||||
Some(command) => assert_eq!(command.command(), "model"),
|
||||
Some(command) => assert_eq!(command_name(command), "model"),
|
||||
None => panic!("expected at least one match for '/mo'"),
|
||||
}
|
||||
}
|
||||
@@ -303,12 +305,20 @@ mod tests {
|
||||
let mut popup = CommandPopup::new(CommandPopupFlags::default(), &[]);
|
||||
popup.on_composer_text_change("/m".to_string());
|
||||
|
||||
let cmds: Vec<&str> = popup
|
||||
let cmds: Vec<String> = popup
|
||||
.filtered_items()
|
||||
.into_iter()
|
||||
.map(|item| item.command())
|
||||
.map(|item| command_name(&item).to_string())
|
||||
.collect();
|
||||
assert_eq!(cmds, vec!["model", "memories", "mention", "mcp"]);
|
||||
assert_eq!(
|
||||
cmds,
|
||||
vec![
|
||||
"model".to_string(),
|
||||
"memories".to_string(),
|
||||
"mention".to_string(),
|
||||
"mcp".to_string(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -316,13 +326,13 @@ mod tests {
|
||||
let mut popup = CommandPopup::new(CommandPopupFlags::default(), &[]);
|
||||
popup.on_composer_text_change("/ac".to_string());
|
||||
|
||||
let cmds: Vec<&str> = popup
|
||||
let cmds: Vec<String> = popup
|
||||
.filtered_items()
|
||||
.into_iter()
|
||||
.map(|item| item.command())
|
||||
.map(|item| command_name(&item).to_string())
|
||||
.collect();
|
||||
assert!(
|
||||
!cmds.contains(&"compact"),
|
||||
!cmds.iter().any(|command| command == "compact"),
|
||||
"expected prefix search for '/ac' to exclude 'compact', got {cmds:?}"
|
||||
);
|
||||
}
|
||||
@@ -344,17 +354,17 @@ mod tests {
|
||||
let mut popup = CommandPopup::new(CommandPopupFlags::default(), &[]);
|
||||
popup.on_composer_text_change("/".to_string());
|
||||
|
||||
let cmds: Vec<&str> = popup
|
||||
let cmds: Vec<String> = popup
|
||||
.filtered_items()
|
||||
.into_iter()
|
||||
.map(|item| item.command())
|
||||
.map(|item| command_name(&item).to_string())
|
||||
.collect();
|
||||
assert!(
|
||||
!cmds.contains(&"collab"),
|
||||
!cmds.iter().any(|command| command == "collab"),
|
||||
"expected '/collab' to be hidden when collaboration modes are disabled, got {cmds:?}"
|
||||
);
|
||||
assert!(
|
||||
!cmds.contains(&"plan"),
|
||||
!cmds.iter().any(|command| command == "plan"),
|
||||
"expected '/plan' to be hidden when collaboration modes are disabled, got {cmds:?}"
|
||||
);
|
||||
}
|
||||
@@ -379,7 +389,7 @@ mod tests {
|
||||
popup.on_composer_text_change("/collab".to_string());
|
||||
|
||||
match popup.selected_item() {
|
||||
Some(command) => assert_eq!(command.command(), "collab"),
|
||||
Some(command) => assert_eq!(command_name(&command), "collab"),
|
||||
other => panic!("expected collab to be selected for exact match, got {other:?}"),
|
||||
}
|
||||
}
|
||||
@@ -404,7 +414,7 @@ mod tests {
|
||||
popup.on_composer_text_change("/plan".to_string());
|
||||
|
||||
match popup.selected_item() {
|
||||
Some(command) => assert_eq!(command.command(), "plan"),
|
||||
Some(command) => assert_eq!(command_name(&command), "plan"),
|
||||
other => panic!("expected plan to be selected for exact match, got {other:?}"),
|
||||
}
|
||||
}
|
||||
@@ -428,13 +438,13 @@ mod tests {
|
||||
);
|
||||
popup.on_composer_text_change("/pers".to_string());
|
||||
|
||||
let cmds: Vec<&str> = popup
|
||||
let cmds: Vec<String> = popup
|
||||
.filtered_items()
|
||||
.into_iter()
|
||||
.map(|item| item.command())
|
||||
.map(|item| command_name(&item).to_string())
|
||||
.collect();
|
||||
assert!(
|
||||
!cmds.contains(&"personality"),
|
||||
!cmds.iter().any(|command| command == "personality"),
|
||||
"expected '/personality' to be hidden when disabled, got {cmds:?}"
|
||||
);
|
||||
}
|
||||
@@ -459,7 +469,7 @@ mod tests {
|
||||
popup.on_composer_text_change("/personality".to_string());
|
||||
|
||||
match popup.selected_item() {
|
||||
Some(command) => assert_eq!(command.command(), "personality"),
|
||||
Some(command) => assert_eq!(command_name(&command), "personality"),
|
||||
other => panic!("expected personality to be selected for exact match, got {other:?}"),
|
||||
}
|
||||
}
|
||||
@@ -483,14 +493,14 @@ mod tests {
|
||||
);
|
||||
popup.on_composer_text_change("/aud".to_string());
|
||||
|
||||
let cmds: Vec<&str> = popup
|
||||
let cmds: Vec<String> = popup
|
||||
.filtered_items()
|
||||
.into_iter()
|
||||
.map(|item| item.command())
|
||||
.map(|item| command_name(&item).to_string())
|
||||
.collect();
|
||||
|
||||
assert!(
|
||||
!cmds.contains(&"settings"),
|
||||
!cmds.iter().any(|command| command == "settings"),
|
||||
"expected '/settings' to be hidden when audio device selection is disabled, got {cmds:?}"
|
||||
);
|
||||
}
|
||||
@@ -498,10 +508,10 @@ mod tests {
|
||||
#[test]
|
||||
fn debug_commands_are_hidden_from_popup() {
|
||||
let popup = CommandPopup::new(CommandPopupFlags::default(), &[]);
|
||||
let cmds: Vec<&str> = popup
|
||||
let cmds: Vec<String> = popup
|
||||
.filtered_items()
|
||||
.into_iter()
|
||||
.map(|item| item.command())
|
||||
.map(|item| command_name(&item).to_string())
|
||||
.collect();
|
||||
|
||||
assert!(
|
||||
|
||||
@@ -7,6 +7,7 @@ use std::collections::HashSet;
|
||||
use std::str::FromStr;
|
||||
|
||||
use codex_protocol::config_types::ServiceTier;
|
||||
use codex_protocol::config_types::priority_service_tier;
|
||||
use codex_protocol::openai_models::ModelPreset;
|
||||
use codex_protocol::openai_models::ModelServiceTier;
|
||||
use codex_utils_fuzzy_match::fuzzy_match;
|
||||
@@ -36,14 +37,15 @@ pub(crate) struct ServiceTierCommand {
|
||||
pub(crate) description: String,
|
||||
}
|
||||
|
||||
impl ServiceTierCommand {
|
||||
fn from_model_service_tier(service_tier: &ModelServiceTier, command: String) -> Self {
|
||||
Self {
|
||||
service_tier: service_tier.id.clone(),
|
||||
command,
|
||||
name: service_tier.name.clone(),
|
||||
description: service_tier.description.clone(),
|
||||
}
|
||||
fn service_tier_command_from_model_tier(
|
||||
service_tier: &ModelServiceTier,
|
||||
command: String,
|
||||
) -> ServiceTierCommand {
|
||||
ServiceTierCommand {
|
||||
service_tier: service_tier.id.clone(),
|
||||
command,
|
||||
name: service_tier.name.clone(),
|
||||
description: service_tier.description.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,47 +55,45 @@ pub(crate) enum SlashCommandAction {
|
||||
ServiceTier(ServiceTierCommand),
|
||||
}
|
||||
|
||||
impl SlashCommandAction {
|
||||
pub(crate) fn command(&self) -> &str {
|
||||
match self {
|
||||
Self::Builtin(command) => command.command(),
|
||||
Self::ServiceTier(command) => command.command.as_str(),
|
||||
}
|
||||
pub(crate) fn command_name(action: &SlashCommandAction) -> &str {
|
||||
match action {
|
||||
SlashCommandAction::Builtin(command) => command.command(),
|
||||
SlashCommandAction::ServiceTier(command) => command.command.as_str(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn description(&self) -> &str {
|
||||
match self {
|
||||
Self::Builtin(command) => command.description(),
|
||||
Self::ServiceTier(command) => command.description.as_str(),
|
||||
}
|
||||
pub(crate) fn command_description(action: &SlashCommandAction) -> &str {
|
||||
match action {
|
||||
SlashCommandAction::Builtin(command) => command.description(),
|
||||
SlashCommandAction::ServiceTier(command) => command.description.as_str(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn supports_inline_args(&self) -> bool {
|
||||
match self {
|
||||
Self::Builtin(command) => command.supports_inline_args(),
|
||||
Self::ServiceTier(_) => true,
|
||||
}
|
||||
pub(crate) fn command_supports_inline_args(action: &SlashCommandAction) -> bool {
|
||||
match action {
|
||||
SlashCommandAction::Builtin(command) => command.supports_inline_args(),
|
||||
SlashCommandAction::ServiceTier(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn available_in_side_conversation(&self) -> bool {
|
||||
match self {
|
||||
Self::Builtin(command) => command.available_in_side_conversation(),
|
||||
Self::ServiceTier(_) => false,
|
||||
}
|
||||
pub(crate) fn command_available_in_side_conversation(action: &SlashCommandAction) -> bool {
|
||||
match action {
|
||||
SlashCommandAction::Builtin(command) => command.available_in_side_conversation(),
|
||||
SlashCommandAction::ServiceTier(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn available_during_task(&self) -> bool {
|
||||
match self {
|
||||
Self::Builtin(command) => command.available_during_task(),
|
||||
Self::ServiceTier(_) => false,
|
||||
}
|
||||
pub(crate) fn command_available_during_task(action: &SlashCommandAction) -> bool {
|
||||
match action {
|
||||
SlashCommandAction::Builtin(command) => command.available_during_task(),
|
||||
SlashCommandAction::ServiceTier(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn as_builtin(&self) -> Option<SlashCommand> {
|
||||
match self {
|
||||
Self::Builtin(command) => Some(*command),
|
||||
Self::ServiceTier(_) => None,
|
||||
}
|
||||
pub(crate) fn command_as_builtin(action: &SlashCommandAction) -> Option<SlashCommand> {
|
||||
match action {
|
||||
SlashCommandAction::Builtin(command) => Some(*command),
|
||||
SlashCommandAction::ServiceTier(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ pub(crate) fn has_command_prefix(
|
||||
) -> bool {
|
||||
commands_for_input(flags, service_tier_commands)
|
||||
.into_iter()
|
||||
.any(|command| fuzzy_match(command.command(), name).is_some())
|
||||
.any(|command| fuzzy_match(command_name(&command), name).is_some())
|
||||
}
|
||||
|
||||
pub(crate) fn service_tier_commands_for_model(model: &ModelPreset) -> Vec<ServiceTierCommand> {
|
||||
@@ -191,10 +191,7 @@ pub(crate) fn service_tier_commands_for_model(model: &ModelPreset) -> Vec<Servic
|
||||
continue;
|
||||
};
|
||||
reserved_names.insert(command.clone());
|
||||
commands.push(ServiceTierCommand::from_model_service_tier(
|
||||
service_tier,
|
||||
command,
|
||||
));
|
||||
commands.push(service_tier_command_from_model_tier(service_tier, command));
|
||||
}
|
||||
commands
|
||||
}
|
||||
@@ -294,7 +291,7 @@ mod tests {
|
||||
#[test]
|
||||
fn service_tier_command_is_hidden_when_disabled() {
|
||||
let service_tier_commands = vec![ServiceTierCommand {
|
||||
service_tier: ServiceTier::priority(),
|
||||
service_tier: priority_service_tier(),
|
||||
command: "fast".to_string(),
|
||||
name: "Fast".to_string(),
|
||||
description: "Fast tier".to_string(),
|
||||
@@ -343,7 +340,7 @@ mod tests {
|
||||
&[],
|
||||
)
|
||||
.into_iter()
|
||||
.map(|command| command.command().to_string())
|
||||
.map(|command| command_name(&command).to_string())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(
|
||||
@@ -385,12 +382,12 @@ mod tests {
|
||||
supports_personality: false,
|
||||
service_tiers: vec![
|
||||
ModelServiceTier {
|
||||
id: ServiceTier::priority(),
|
||||
id: priority_service_tier(),
|
||||
name: "Fast".to_string(),
|
||||
description: "Fast tier".to_string(),
|
||||
},
|
||||
ModelServiceTier {
|
||||
id: ServiceTier::new("express"),
|
||||
id: ServiceTier::from("express"),
|
||||
name: "Model".to_string(),
|
||||
description: "Express tier".to_string(),
|
||||
},
|
||||
@@ -409,13 +406,13 @@ mod tests {
|
||||
commands,
|
||||
vec![
|
||||
ServiceTierCommand {
|
||||
service_tier: ServiceTier::priority(),
|
||||
service_tier: priority_service_tier(),
|
||||
command: "fast".to_string(),
|
||||
name: "Fast".to_string(),
|
||||
description: "Fast tier".to_string(),
|
||||
},
|
||||
ServiceTierCommand {
|
||||
service_tier: ServiceTier::new("express"),
|
||||
service_tier: ServiceTier::from("express"),
|
||||
command: "express".to_string(),
|
||||
name: "Model".to_string(),
|
||||
description: "Express tier".to_string(),
|
||||
|
||||
@@ -57,7 +57,6 @@ use crate::bottom_pane::StatusSurfacePreviewData;
|
||||
use crate::bottom_pane::StatusSurfacePreviewItem;
|
||||
use crate::bottom_pane::TerminalTitleItem;
|
||||
use crate::bottom_pane::TerminalTitleSetupView;
|
||||
use crate::bottom_pane::slash_commands::ServiceTierCommand;
|
||||
use crate::diff_model::FileChange;
|
||||
use crate::legacy_core::DEFAULT_AGENTS_MD_FILENAME;
|
||||
use crate::legacy_core::config::Config;
|
||||
@@ -155,6 +154,7 @@ use codex_protocol::config_types::ServiceTier;
|
||||
use codex_protocol::config_types::Settings;
|
||||
#[cfg(target_os = "windows")]
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::config_types::is_priority_service_tier;
|
||||
use codex_protocol::items::AgentMessageContent;
|
||||
use codex_protocol::items::AgentMessageItem;
|
||||
use codex_protocol::models::MessagePhase;
|
||||
@@ -343,6 +343,10 @@ use self::plan_implementation::PLAN_IMPLEMENTATION_TITLE;
|
||||
mod realtime;
|
||||
use self::realtime::RealtimeConversationUiState;
|
||||
mod reasoning_shortcuts;
|
||||
pub(crate) mod service_tiers;
|
||||
use self::service_tiers::available_service_tier_commands;
|
||||
use self::service_tiers::model_preset;
|
||||
use self::service_tiers::model_supports_fast_mode;
|
||||
mod side;
|
||||
mod status_surfaces;
|
||||
use self::status_surfaces::CachedProjectRootName;
|
||||
@@ -9263,8 +9267,8 @@ impl ChatWidget {
|
||||
model: &str,
|
||||
service_tier: Option<ServiceTier>,
|
||||
) -> bool {
|
||||
self.model_supports_fast_mode(model)
|
||||
&& service_tier.as_ref().is_some_and(ServiceTier::is_priority)
|
||||
model_supports_fast_mode(self, model)
|
||||
&& service_tier.as_ref().is_some_and(is_priority_service_tier)
|
||||
&& self.has_chatgpt_account
|
||||
}
|
||||
|
||||
@@ -9359,7 +9363,7 @@ impl ChatWidget {
|
||||
|
||||
fn sync_service_tier_commands(&mut self) {
|
||||
self.bottom_pane
|
||||
.set_service_tier_commands(self.available_service_tier_commands());
|
||||
.set_service_tier_commands(available_service_tier_commands(self));
|
||||
}
|
||||
|
||||
fn sync_personality_command_enabled(&mut self) {
|
||||
@@ -9377,64 +9381,18 @@ impl ChatWidget {
|
||||
.set_goal_command_enabled(self.config.features.enabled(Feature::Goals));
|
||||
}
|
||||
|
||||
fn model_preset(&self, model: &str) -> Option<ModelPreset> {
|
||||
self.model_catalog
|
||||
.try_list_models()
|
||||
.ok()
|
||||
.and_then(|models| models.into_iter().find(|preset| preset.model == model))
|
||||
}
|
||||
|
||||
fn available_service_tier_commands(&self) -> Vec<ServiceTierCommand> {
|
||||
self.model_preset(self.current_model())
|
||||
.map(|model| {
|
||||
crate::bottom_pane::slash_commands::service_tier_commands_for_model(&model)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub(crate) fn service_tier_display_name(
|
||||
&self,
|
||||
model: &str,
|
||||
service_tier: &ServiceTier,
|
||||
) -> String {
|
||||
self.model_preset(model)
|
||||
.and_then(|preset| {
|
||||
preset
|
||||
.service_tier(service_tier)
|
||||
.map(|tier| tier.name.clone())
|
||||
})
|
||||
.unwrap_or_else(|| service_tier.to_string())
|
||||
}
|
||||
|
||||
pub(crate) fn current_service_tier_name(&self) -> Option<String> {
|
||||
self.current_service_tier()
|
||||
.map(|tier| self.service_tier_display_name(self.current_model(), &tier))
|
||||
}
|
||||
|
||||
pub(crate) fn current_service_tier_status_label(&self) -> String {
|
||||
self.current_service_tier_name()
|
||||
.map(|name| format!("Tier {name}"))
|
||||
.unwrap_or_else(|| "Tier default".to_string())
|
||||
}
|
||||
|
||||
fn current_model_supports_personality(&self) -> bool {
|
||||
self.model_preset(self.current_model())
|
||||
model_preset(self, self.current_model())
|
||||
.map(|preset| preset.supports_personality)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn model_supports_fast_mode(&self, model: &str) -> bool {
|
||||
self.model_preset(model)
|
||||
.map(|preset| preset.supports_service_tier(&ServiceTier::priority()))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Return whether the effective model currently advertises image-input support.
|
||||
///
|
||||
/// We intentionally default to `true` when model metadata cannot be read so transient catalog
|
||||
/// failures do not hard-block user input in the UI.
|
||||
fn current_model_supports_images(&self) -> bool {
|
||||
self.model_preset(self.current_model())
|
||||
model_preset(self, self.current_model())
|
||||
.map(|preset| preset.input_modalities.contains(&InputModality::Image))
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
use codex_protocol::config_types::ServiceTier;
|
||||
use codex_protocol::config_types::priority_service_tier;
|
||||
use codex_protocol::openai_models::ModelPreset;
|
||||
use codex_protocol::openai_models::model_service_tier;
|
||||
use codex_protocol::openai_models::model_supports_service_tier;
|
||||
|
||||
use super::ChatWidget;
|
||||
use crate::bottom_pane::slash_commands::ServiceTierCommand;
|
||||
use crate::bottom_pane::slash_commands::service_tier_commands_for_model;
|
||||
|
||||
pub(super) fn model_preset(chat: &ChatWidget, model: &str) -> Option<ModelPreset> {
|
||||
chat.model_catalog
|
||||
.try_list_models()
|
||||
.ok()
|
||||
.and_then(|models| models.into_iter().find(|preset| preset.model == model))
|
||||
}
|
||||
|
||||
pub(crate) fn available_service_tier_commands(chat: &ChatWidget) -> Vec<ServiceTierCommand> {
|
||||
model_preset(chat, chat.current_model())
|
||||
.map(|model| service_tier_commands_for_model(&model))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub(crate) fn service_tier_display_name(
|
||||
chat: &ChatWidget,
|
||||
model: &str,
|
||||
service_tier: &ServiceTier,
|
||||
) -> String {
|
||||
model_preset(chat, model)
|
||||
.and_then(|preset| model_service_tier(&preset, service_tier).map(|tier| tier.name.clone()))
|
||||
.unwrap_or_else(|| service_tier.to_string())
|
||||
}
|
||||
|
||||
pub(crate) fn current_service_tier_name(chat: &ChatWidget) -> Option<String> {
|
||||
chat.current_service_tier()
|
||||
.map(|tier| service_tier_display_name(chat, chat.current_model(), &tier))
|
||||
}
|
||||
|
||||
pub(crate) fn current_service_tier_status_label(chat: &ChatWidget) -> String {
|
||||
current_service_tier_name(chat)
|
||||
.map(|name| format!("Tier {name}"))
|
||||
.unwrap_or_else(|| "Tier default".to_string())
|
||||
}
|
||||
|
||||
pub(crate) fn model_supports_fast_mode(chat: &ChatWidget, model: &str) -> bool {
|
||||
model_preset(chat, model)
|
||||
.map(|preset| model_supports_service_tier(&preset, &priority_service_tier()))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
@@ -5,11 +5,17 @@
|
||||
//! dispatch step and records the staged entry once the command has been handled, so
|
||||
//! slash-command recall follows the same submitted-input rule as ordinary text.
|
||||
|
||||
use super::service_tiers::available_service_tier_commands;
|
||||
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::SlashCommandAction;
|
||||
use crate::bottom_pane::slash_commands::command_as_builtin;
|
||||
use crate::bottom_pane::slash_commands::command_available_during_task;
|
||||
use crate::bottom_pane::slash_commands::command_available_in_side_conversation;
|
||||
use crate::bottom_pane::slash_commands::command_name;
|
||||
use crate::bottom_pane::slash_commands::command_supports_inline_args;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum SlashCommandDispatchSource {
|
||||
@@ -40,7 +46,7 @@ impl ChatWidget {
|
||||
/// that staged entry after dispatch so slash-command recall follows the same "submitted input"
|
||||
/// rule as normal text.
|
||||
pub(super) fn handle_slash_command_dispatch(&mut self, cmd: SlashCommandAction) {
|
||||
let is_goal = cmd.as_builtin() == Some(SlashCommand::Goal);
|
||||
let is_goal = command_as_builtin(&cmd) == Some(SlashCommand::Goal);
|
||||
self.dispatch_command(cmd);
|
||||
if is_goal {
|
||||
self.bottom_pane.drain_pending_submission_state();
|
||||
@@ -112,10 +118,10 @@ impl ChatWidget {
|
||||
if !self.ensure_side_command_allowed_outside_review(&cmd) {
|
||||
return;
|
||||
}
|
||||
if !cmd.available_during_task() && self.bottom_pane.is_task_running() {
|
||||
if !command_available_during_task(&cmd) && self.bottom_pane.is_task_running() {
|
||||
let message = format!(
|
||||
"'/{}' is disabled while a task is in progress.",
|
||||
cmd.command()
|
||||
command_name(&cmd)
|
||||
);
|
||||
self.add_to_history(history_cell::new_error_event(message));
|
||||
self.bottom_pane.drain_pending_submission_state();
|
||||
@@ -458,14 +464,14 @@ impl ChatWidget {
|
||||
if !self.ensure_side_command_allowed_outside_review(&cmd) {
|
||||
return;
|
||||
}
|
||||
if !cmd.supports_inline_args() {
|
||||
if !command_supports_inline_args(&cmd) {
|
||||
self.dispatch_command(cmd);
|
||||
return;
|
||||
}
|
||||
if !cmd.available_during_task() && self.bottom_pane.is_task_running() {
|
||||
if !command_available_during_task(&cmd) && self.bottom_pane.is_task_running() {
|
||||
let message = format!(
|
||||
"'/{}' is disabled while a task is in progress.",
|
||||
cmd.command()
|
||||
command_name(&cmd)
|
||||
);
|
||||
self.add_to_history(history_cell::new_error_event(message));
|
||||
self.request_redraw();
|
||||
@@ -548,7 +554,7 @@ impl ChatWidget {
|
||||
source,
|
||||
} = prepared;
|
||||
let trimmed = args.trim();
|
||||
let is_goal_command = cmd.as_builtin() == Some(SlashCommand::Goal);
|
||||
let is_goal_command = command_as_builtin(&cmd) == Some(SlashCommand::Goal);
|
||||
match cmd {
|
||||
SlashCommandAction::ServiceTier(command) => {
|
||||
match trimmed.to_ascii_lowercase().as_str() {
|
||||
@@ -769,7 +775,7 @@ impl ChatWidget {
|
||||
let Some(cmd) = slash_commands::find_command(
|
||||
name,
|
||||
self.builtin_command_flags(),
|
||||
&self.available_service_tier_commands(),
|
||||
&available_service_tier_commands(self),
|
||||
) else {
|
||||
self.add_info_message(
|
||||
format!(
|
||||
@@ -785,7 +791,7 @@ impl ChatWidget {
|
||||
return self.queued_command_drain_result(cmd);
|
||||
}
|
||||
|
||||
if !cmd.supports_inline_args() {
|
||||
if !command_supports_inline_args(&cmd) {
|
||||
self.submit_user_message(UserMessage {
|
||||
text,
|
||||
local_images,
|
||||
@@ -930,19 +936,19 @@ impl ChatWidget {
|
||||
&mut self,
|
||||
cmd: &SlashCommandAction,
|
||||
) -> bool {
|
||||
if !self.active_side_conversation || cmd.available_in_side_conversation() {
|
||||
if !self.active_side_conversation || command_available_in_side_conversation(cmd) {
|
||||
return true;
|
||||
}
|
||||
self.add_error_message(format!(
|
||||
"'/{}' is unavailable in side conversations. {SIDE_SLASH_COMMAND_UNAVAILABLE_HINT}",
|
||||
cmd.command()
|
||||
command_name(cmd)
|
||||
));
|
||||
self.bottom_pane.drain_pending_submission_state();
|
||||
false
|
||||
}
|
||||
|
||||
fn ensure_side_command_allowed_outside_review(&mut self, cmd: &SlashCommandAction) -> bool {
|
||||
if cmd.as_builtin() != Some(SlashCommand::Side) || !self.is_review_mode {
|
||||
if command_as_builtin(cmd) != Some(SlashCommand::Side) || !self.is_review_mode {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
//! Keeping this logic in a focused submodule makes the additive title/status
|
||||
//! behavior easier to review without paging through the rest of `chatwidget.rs`.
|
||||
|
||||
use super::service_tiers::current_service_tier_name;
|
||||
use super::service_tiers::current_service_tier_status_label;
|
||||
use super::*;
|
||||
use crate::bottom_pane::status_line_from_segments;
|
||||
use crate::status::format_tokens_compact;
|
||||
@@ -557,7 +559,7 @@ impl ChatWidget {
|
||||
format_tokens_compact(self.status_line_total_usage().output_tokens)
|
||||
)),
|
||||
StatusLineItem::SessionId => self.thread_id.map(|id| id.to_string()),
|
||||
StatusLineItem::FastMode => Some(self.current_service_tier_status_label()),
|
||||
StatusLineItem::FastMode => Some(current_service_tier_status_label(self)),
|
||||
StatusLineItem::ThreadTitle => self.thread_name.as_ref().and_then(|name| {
|
||||
let trimmed = name.trim();
|
||||
(!trimmed.is_empty()).then(|| trimmed.to_string())
|
||||
@@ -671,8 +673,7 @@ impl ChatWidget {
|
||||
|
||||
fn model_with_reasoning_display_name(&self) -> String {
|
||||
let label = Self::status_line_reasoning_effort_label(self.effective_reasoning_effort());
|
||||
let service_tier_label = self
|
||||
.current_service_tier_name()
|
||||
let service_tier_label = current_service_tier_name(self)
|
||||
.map(|name| format!(" {}", name.to_ascii_lowercase()))
|
||||
.unwrap_or_default();
|
||||
format!("{} {label}{service_tier_label}", self.model_display_name())
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::*;
|
||||
use crate::bottom_pane::slash_commands::ServiceTierCommand;
|
||||
use crate::bottom_pane::slash_commands::SlashCommandAction;
|
||||
use codex_protocol::config_types::priority_service_tier;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
fn complete_turn_with_message(chat: &mut ChatWidget, turn_id: &str, message: Option<&str>) {
|
||||
@@ -31,7 +32,7 @@ fn queue_composer_text_with_tab(chat: &mut ChatWidget, text: &str) {
|
||||
|
||||
fn fast_service_tier_command() -> SlashCommandAction {
|
||||
SlashCommandAction::ServiceTier(ServiceTierCommand {
|
||||
service_tier: ServiceTier::priority(),
|
||||
service_tier: priority_service_tier(),
|
||||
command: "fast".to_string(),
|
||||
name: "Fast".to_string(),
|
||||
description: "Fast tier".to_string(),
|
||||
@@ -1708,13 +1709,14 @@ async fn fast_slash_command_updates_and_persists_local_service_tier() {
|
||||
chat.dispatch_command(fast_service_tier_command());
|
||||
|
||||
let events = std::iter::from_fn(|| rx.try_recv().ok()).collect::<Vec<_>>();
|
||||
let priority_tier = priority_service_tier();
|
||||
assert!(
|
||||
events.iter().any(|event| matches!(
|
||||
event,
|
||||
AppEvent::CodexOp(AppCommand::OverrideTurnContext {
|
||||
service_tier: Some(Some(ServiceTier::priority())),
|
||||
service_tier: Some(Some(tier)),
|
||||
..
|
||||
})
|
||||
}) if tier == &priority_tier
|
||||
)),
|
||||
"expected fast-mode override app event; events: {events:?}"
|
||||
);
|
||||
@@ -1722,8 +1724,8 @@ async fn fast_slash_command_updates_and_persists_local_service_tier() {
|
||||
events.iter().any(|event| matches!(
|
||||
event,
|
||||
AppEvent::PersistServiceTierSelection {
|
||||
service_tier: Some(ServiceTier::priority()),
|
||||
}
|
||||
service_tier: Some(tier),
|
||||
} if tier == &priority_tier
|
||||
)),
|
||||
"expected fast-mode persistence app event; events: {events:?}"
|
||||
);
|
||||
@@ -1748,9 +1750,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::priority())),
|
||||
service_tier: Some(Some(tier)),
|
||||
..
|
||||
} => {}
|
||||
} if tier == priority_service_tier() => {}
|
||||
other => panic!("expected Op::UserTurn with fast service tier, got {other:?}"),
|
||||
}
|
||||
}
|
||||
@@ -1769,13 +1771,14 @@ async fn queued_fast_slash_applies_before_next_queued_message() {
|
||||
complete_turn_with_message(&mut chat, "turn-1", Some("done"));
|
||||
|
||||
let events = std::iter::from_fn(|| rx.try_recv().ok()).collect::<Vec<_>>();
|
||||
let priority_tier = priority_service_tier();
|
||||
assert!(
|
||||
events.iter().any(|event| matches!(
|
||||
event,
|
||||
AppEvent::CodexOp(AppCommand::OverrideTurnContext {
|
||||
service_tier: Some(Some(ServiceTier::priority())),
|
||||
service_tier: Some(Some(tier)),
|
||||
..
|
||||
})
|
||||
}) if tier == &priority_tier
|
||||
)),
|
||||
"expected queued /fast to update service tier before next turn; events: {events:?}"
|
||||
);
|
||||
@@ -1783,9 +1786,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::priority())),
|
||||
service_tier: Some(Some(tier)),
|
||||
..
|
||||
} => assert_eq!(
|
||||
} if tier == priority_service_tier() => assert_eq!(
|
||||
items,
|
||||
vec![UserInput::Text {
|
||||
text: "hello after fast".to_string(),
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use super::*;
|
||||
use crate::bottom_pane::goal_status_indicator_line;
|
||||
use codex_protocol::config_types::priority_service_tier;
|
||||
use codex_protocol::openai_models::model_supports_service_tier;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
/// Receiving a token usage update without usage clears the context indicator.
|
||||
@@ -1016,14 +1018,20 @@ async fn commentary_completion_restores_status_indicator_before_exec_begin() {
|
||||
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_service_tier(&ServiceTier::priority()));
|
||||
chat.set_service_tier(Some(ServiceTier::priority()));
|
||||
assert!(model_supports_service_tier(
|
||||
&get_available_model(&chat, "gpt-5.4"),
|
||||
&priority_service_tier()
|
||||
));
|
||||
chat.set_service_tier(Some(priority_service_tier()));
|
||||
|
||||
assert!(!chat.should_show_fast_status(chat.current_model(), chat.current_service_tier(),));
|
||||
|
||||
set_chatgpt_auth(&mut chat);
|
||||
set_fast_mode_test_catalog(&mut chat);
|
||||
assert!(get_available_model(&chat, "gpt-5.4").supports_service_tier(&ServiceTier::priority()));
|
||||
assert!(model_supports_service_tier(
|
||||
&get_available_model(&chat, "gpt-5.4"),
|
||||
&priority_service_tier()
|
||||
));
|
||||
|
||||
assert!(chat.should_show_fast_status(chat.current_model(), chat.current_service_tier(),));
|
||||
}
|
||||
@@ -1032,17 +1040,17 @@ async fn fast_status_indicator_requires_chatgpt_auth() {
|
||||
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_service_tier(&ServiceTier::priority())
|
||||
);
|
||||
chat.set_service_tier(Some(ServiceTier::priority()));
|
||||
assert!(!model_supports_service_tier(
|
||||
&get_available_model(&chat, "gpt-5.3-codex"),
|
||||
&priority_service_tier()
|
||||
));
|
||||
chat.set_service_tier(Some(priority_service_tier()));
|
||||
set_chatgpt_auth(&mut chat);
|
||||
set_fast_mode_test_catalog(&mut chat);
|
||||
assert!(
|
||||
!get_available_model(&chat, "gpt-5.3-codex")
|
||||
.supports_service_tier(&ServiceTier::priority())
|
||||
);
|
||||
assert!(!model_supports_service_tier(
|
||||
&get_available_model(&chat, "gpt-5.3-codex"),
|
||||
&priority_service_tier()
|
||||
));
|
||||
|
||||
assert!(!chat.should_show_fast_status(chat.current_model(), chat.current_service_tier(),));
|
||||
}
|
||||
@@ -1051,10 +1059,16 @@ async fn fast_status_indicator_is_hidden_for_models_without_fast_support() {
|
||||
async fn fast_status_indicator_is_hidden_when_fast_mode_is_off() {
|
||||
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_service_tier(&ServiceTier::priority()));
|
||||
assert!(model_supports_service_tier(
|
||||
&get_available_model(&chat, "gpt-5.4"),
|
||||
&priority_service_tier()
|
||||
));
|
||||
set_chatgpt_auth(&mut chat);
|
||||
set_fast_mode_test_catalog(&mut chat);
|
||||
assert!(get_available_model(&chat, "gpt-5.4").supports_service_tier(&ServiceTier::priority()));
|
||||
assert!(model_supports_service_tier(
|
||||
&get_available_model(&chat, "gpt-5.4"),
|
||||
&priority_service_tier()
|
||||
));
|
||||
|
||||
assert!(!chat.should_show_fast_status(chat.current_model(), chat.current_service_tier(),));
|
||||
}
|
||||
@@ -1371,7 +1385,7 @@ async fn status_line_fast_mode_renders_on_and_off() {
|
||||
chat.refresh_status_line();
|
||||
assert_eq!(status_line_text(&chat), Some("Tier default".to_string()));
|
||||
|
||||
chat.set_service_tier(Some(ServiceTier::priority()));
|
||||
chat.set_service_tier(Some(priority_service_tier()));
|
||||
chat.refresh_status_line();
|
||||
assert_eq!(status_line_text(&chat), Some("Tier Fast".to_string()));
|
||||
}
|
||||
@@ -1384,7 +1398,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::priority()));
|
||||
chat.set_service_tier(Some(priority_service_tier()));
|
||||
chat.refresh_status_line();
|
||||
|
||||
let width = 80;
|
||||
@@ -1403,7 +1417,10 @@ async fn status_line_fast_mode_footer_snapshot() {
|
||||
async fn status_line_model_with_reasoning_includes_fast_for_fast_capable_models() {
|
||||
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_service_tier(&ServiceTier::priority()));
|
||||
assert!(model_supports_service_tier(
|
||||
&get_available_model(&chat, "gpt-5.4"),
|
||||
&priority_service_tier()
|
||||
));
|
||||
chat.config.cwd = test_project_path().abs();
|
||||
chat.config.tui_status_line = Some(vec![
|
||||
"model-with-reasoning".to_string(),
|
||||
@@ -1411,10 +1428,13 @@ 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::priority()));
|
||||
chat.set_service_tier(Some(priority_service_tier()));
|
||||
set_chatgpt_auth(&mut chat);
|
||||
set_fast_mode_test_catalog(&mut chat);
|
||||
assert!(get_available_model(&chat, "gpt-5.4").supports_service_tier(&ServiceTier::priority()));
|
||||
assert!(model_supports_service_tier(
|
||||
&get_available_model(&chat, "gpt-5.4"),
|
||||
&priority_service_tier()
|
||||
));
|
||||
chat.refresh_status_line();
|
||||
let test_cwd = test_path_display("/tmp/project");
|
||||
|
||||
@@ -1550,7 +1570,10 @@ async fn status_line_model_with_reasoning_fast_footer_snapshot() {
|
||||
|
||||
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_service_tier(&ServiceTier::priority()));
|
||||
assert!(model_supports_service_tier(
|
||||
&get_available_model(&chat, "gpt-5.4"),
|
||||
&priority_service_tier()
|
||||
));
|
||||
chat.show_welcome_banner = false;
|
||||
chat.config.cwd = test_project_path().abs();
|
||||
chat.config.tui_status_line = Some(vec![
|
||||
@@ -1559,10 +1582,13 @@ 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::priority()));
|
||||
chat.set_service_tier(Some(priority_service_tier()));
|
||||
set_chatgpt_auth(&mut chat);
|
||||
set_fast_mode_test_catalog(&mut chat);
|
||||
assert!(get_available_model(&chat, "gpt-5.4").supports_service_tier(&ServiceTier::priority()));
|
||||
assert!(model_supports_service_tier(
|
||||
&get_available_model(&chat, "gpt-5.4"),
|
||||
&priority_service_tier()
|
||||
));
|
||||
chat.refresh_status_line();
|
||||
|
||||
let width = 80;
|
||||
@@ -1584,7 +1610,10 @@ async fn status_line_model_with_reasoning_context_remaining_footer_snapshot() {
|
||||
|
||||
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_service_tier(&ServiceTier::priority()));
|
||||
assert!(model_supports_service_tier(
|
||||
&get_available_model(&chat, "gpt-5.4"),
|
||||
&priority_service_tier()
|
||||
));
|
||||
chat.show_welcome_banner = false;
|
||||
chat.config.cwd = test_project_path().abs();
|
||||
chat.config.tui_status_line = Some(vec![
|
||||
@@ -1593,10 +1622,13 @@ 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::priority()));
|
||||
chat.set_service_tier(Some(priority_service_tier()));
|
||||
set_chatgpt_auth(&mut chat);
|
||||
set_fast_mode_test_catalog(&mut chat);
|
||||
assert!(get_available_model(&chat, "gpt-5.4").supports_service_tier(&ServiceTier::priority()));
|
||||
assert!(model_supports_service_tier(
|
||||
&get_available_model(&chat, "gpt-5.4"),
|
||||
&priority_service_tier()
|
||||
));
|
||||
chat.refresh_status_line();
|
||||
|
||||
let width = 80;
|
||||
|
||||
Reference in New Issue
Block a user