fix(#2338): prevent known model + Auto effort from falling through to auto row
The whale-route fallback in the picker constructor used show_custom_model_row as the gate for selecting the 'auto' vs custom row, but a known DeepSeek model (e.g. v4-pro) paired with ReasoningEffort::Auto would not match any whale route yet still have show_custom_model_row=false — silently landing on the auto row and replacing the explicit model with 'auto' on apply. Key the fallback on whether the initial model is actually 'auto' instead. When a whale-route fallback selects the custom row, ensure show_custom_model_row is set to true so the row is visible in the picker UI. Also: - Add regression test: known-model + Auto effort must not fall to auto row. - Clean up picker_auto_model_forces_auto_effort_on_apply: remove manual mutations of selected_model_idx / selected_effort_idx which whale-route mode never reads. - Rename Porpoise → Beluga per #2016, which excludes porpoises from the user-facing whale pool.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
//!
|
||||
//! For DeepSeek providers the picker shows whale-sized routes — model + effort
|
||||
//! combinations sorted largest → fastest with friendly whale-species labels
|
||||
//! (Blue Whale, Fin Whale, …, Porpoise). A single ↑/↓ selection sets both
|
||||
//! (Blue Whale, Fin Whale, …, Beluga). A single ↑/↓ selection sets both
|
||||
//! model and effort at once. The "auto" option is always available; custom
|
||||
//! (unrecognised) model ids appear as a separate row.
|
||||
//!
|
||||
@@ -110,23 +110,30 @@ impl ModelPickerView {
|
||||
|
||||
// When showing whale routes, find the matching route by position in the array
|
||||
// (not by sort_order, which happens to match today but is semantically wrong).
|
||||
let selected_route_idx = if show_whale_routes {
|
||||
WHALE_ROUTES
|
||||
let (selected_route_idx, show_custom_model_row) = if show_whale_routes {
|
||||
let idx = WHALE_ROUTES
|
||||
.iter()
|
||||
.position(|r| {
|
||||
r.model.eq_ignore_ascii_case(&initial_model) && r.effort == normalized
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
// No matching whale route — fall back to "auto" (standard model)
|
||||
// or the custom row (unrecognized model).
|
||||
if show_custom_model_row {
|
||||
WHALE_ROUTES.len() + 1 // custom model row
|
||||
} else {
|
||||
// No matching whale route — key the fallback on whether the
|
||||
// current model is actually "auto", not on show_custom_model_row.
|
||||
// Otherwise a known DeepSeek model (e.g. v4-pro) paired with
|
||||
// ReasoningEffort::Auto silently falls through to the "auto" row
|
||||
// and replaces the explicit model on apply.
|
||||
if initial_model.eq_ignore_ascii_case("auto") {
|
||||
WHALE_ROUTES.len() // "auto" row
|
||||
} else {
|
||||
WHALE_ROUTES.len() + 1 // custom model row
|
||||
}
|
||||
})
|
||||
});
|
||||
// When the whale-route fallback selected the custom row, ensure it is
|
||||
// visible so the user can see their current model in the picker.
|
||||
let show_custom = show_custom_model_row || idx == WHALE_ROUTES.len() + 1;
|
||||
(idx, show_custom)
|
||||
} else {
|
||||
0
|
||||
(0, show_custom_model_row)
|
||||
};
|
||||
|
||||
Self {
|
||||
@@ -621,12 +628,7 @@ mod tests {
|
||||
app.auto_model = true;
|
||||
app.reasoning_effort = ReasoningEffort::Off;
|
||||
|
||||
let mut view = ModelPickerView::new(&app);
|
||||
view.selected_model_idx = 0;
|
||||
view.selected_effort_idx = PICKER_EFFORTS
|
||||
.iter()
|
||||
.position(|effort| *effort == ReasoningEffort::Max)
|
||||
.expect("max effort row");
|
||||
let view = ModelPickerView::new(&app);
|
||||
|
||||
assert_eq!(view.resolved_model(), "auto");
|
||||
assert_eq!(view.resolved_effort(), ReasoningEffort::Auto);
|
||||
@@ -747,6 +749,24 @@ mod tests {
|
||||
assert_eq!(view.resolved_effort(), ReasoningEffort::Max);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn whale_routes_known_model_auto_effort_does_not_fall_to_auto() {
|
||||
// Regression: a known DeepSeek model paired with ReasoningEffort::Auto
|
||||
// must NOT fall through to the "auto" row — that would silently replace
|
||||
// the explicit model with "auto" on apply.
|
||||
let (mut app, _lock) = create_test_app();
|
||||
app.model = "deepseek-v4-pro".to_string();
|
||||
app.auto_model = false;
|
||||
app.reasoning_effort = ReasoningEffort::Auto;
|
||||
let view = ModelPickerView::new(&app);
|
||||
// Should fall to custom row (WHALE_ROUTES.len() + 1), not auto row.
|
||||
assert_eq!(view.selected_route_idx, WHALE_ROUTES.len() + 1);
|
||||
assert_eq!(view.resolved_model(), "deepseek-v4-pro");
|
||||
assert_eq!(view.resolved_effort(), ReasoningEffort::Auto);
|
||||
// The custom row must be visible so the user sees their current model.
|
||||
assert!(view.show_custom_model_row);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn whale_routes_auto_effort_maps_to_fallback_row() {
|
||||
let (mut app, _lock) = create_test_app();
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
//! 3. Sperm Whale — Pro + no thinking
|
||||
//! 4. Humpback — Flash + max thinking
|
||||
//! 5. Minke Whale — Flash + high thinking
|
||||
//! 6. Porpoise — Flash + no thinking (smallest, fastest)
|
||||
//! 6. Beluga — Flash + no thinking (smallest, fastest)
|
||||
//!
|
||||
//! Unknown or non-DeepSeek models fall back to the raw model id without
|
||||
//! fake whale labeling.
|
||||
@@ -80,7 +80,7 @@ pub const WHALE_ROUTES: &[WhaleRoute] = &[
|
||||
description: "Fast model, moderate reasoning — tool execution, read-only scouting",
|
||||
},
|
||||
WhaleRoute {
|
||||
label: "Porpoise",
|
||||
label: "Beluga",
|
||||
model: "deepseek-v4-flash",
|
||||
effort: ReasoningEffort::Off,
|
||||
sort_order: 5,
|
||||
@@ -135,10 +135,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lookup_porpoise_for_flash_off() {
|
||||
fn lookup_beluga_for_flash_off() {
|
||||
let route = WhaleRoute::for_model_effort("deepseek-v4-flash", ReasoningEffort::Off)
|
||||
.expect("porpoise route exists");
|
||||
assert_eq!(route.label, "Porpoise");
|
||||
.expect("beluga route exists");
|
||||
assert_eq!(route.label, "Beluga");
|
||||
assert_eq!(route.sort_order, 5);
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ mod tests {
|
||||
#[test]
|
||||
fn by_sort_order_finds_correct_routes() {
|
||||
assert_eq!(WhaleRoute::by_sort_order(0).unwrap().label, "Blue Whale");
|
||||
assert_eq!(WhaleRoute::by_sort_order(5).unwrap().label, "Porpoise");
|
||||
assert_eq!(WhaleRoute::by_sort_order(5).unwrap().label, "Beluga");
|
||||
assert!(WhaleRoute::by_sort_order(99).is_none());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user