Skip to content

feat: whale-size route taxonomy for model + thinking-effort picker#2338

Merged
Hmbown merged 5 commits into
Hmbown:mainfrom
encyc:feat/whale-routes-2026
May 31, 2026
Merged

feat: whale-size route taxonomy for model + thinking-effort picker#2338
Hmbown merged 5 commits into
Hmbown:mainfrom
encyc:feat/whale-routes-2026

Conversation

@encyc

@encyc encyc commented May 29, 2026

Copy link
Copy Markdown
Contributor

Refs #2026. Related: #1676.

Summary

Adds a central whale-route taxonomy that maps each (model, reasoning_effort) pair to a friendly whale-species label, sorted from largest/deepest to smallest/fastest.

For DeepSeek providers, the /model picker now shows a single-column whale-route list instead of the two-column Model | Thinking layout. Pass-through providers retain the classic layout.

Routes (largest → fastest)

Route Model Effort Description
Blue Whale v4-pro max Architecture, debugging, security reviews
Fin Whale v4-pro high Complex tasks, multi-file refactors
Sperm Whale v4-pro off Straightforward code generation
Humpback v4-flash max Lightweight analysis, first-pass reviews
Minke Whale v4-flash high Tool execution, read-only scouting
Porpoise v4-flash off Lookups, searches, simple edits

New module: whale_routes.rs

  • WhaleRoute struct with label, model, effort, sort_order, hint, description
  • WHALE_ROUTES const array (6 routes)
  • for_model_effort() — look up route by model + effort
  • by_sort_order() — look up by sort index
  • 8 unit tests covering ordering, lookup, case-insensitivity, uniqueness

Model picker changes

  • show_whale_routes flag activates for DeepSeek providers
  • Single-column route list with whale labels and hints
  • Fallback row for "auto" and custom model ids
  • Tab key is a no-op in whale-route mode
  • 10 updated/new unit tests

Files

crates/tui/src/tui/whale_routes.rs  | +196  (new)
crates/tui/src/tui/mod.rs           |   +1
crates/tui/src/tui/model_picker.rs  | +287 / -48

3 files, +436 −48

Greptile Summary

This PR introduces a whale-route taxonomy that maps (model, reasoning_effort) pairs to friendly whale-species labels (Blue Whale → Beluga) and wires them into the /model picker for DeepSeek providers, replacing the two-column model/effort layout with a single-column route list. Pass-through providers retain the classic layout.

  • whale_routes.rs defines the 6-entry WHALE_ROUTES const, WhaleRoute struct, and two lookup helpers (for_model_effort, by_sort_order), all covered by 9 unit tests.
  • model_picker.rs adds show_whale_routes / selected_route_idx fields, routes selection logic, render helpers, and 10 updated/new tests; fallback rows handle "auto" and unrecognised custom model IDs correctly, and selected_route_idx is initialised from .position() (array index) rather than sort_order, avoiding a future fragility.
  • The PR description table names the 6th route "Porpoise", but the module docs and code consistently use "Beluga" — the code is self-consistent, the PR description has a stale label.

Confidence Score: 5/5

Safe to merge — the three compile-blocking and logic issues from the previous review round are all resolved.

The impl-block placement, popup-height type cast, and array-index-vs-sort_order initialisation flagged in earlier rounds are all correctly addressed. Fallback-row logic is consistent across new(), resolved_whale_model/effort, whale_route_row_count, and render. The 19 total tests cover the key paths well.

No files require special attention.

Important Files Changed

Filename Overview
crates/tui/src/tui/whale_routes.rs New module defining the WhaleRoute taxonomy: 6 canonical (model, effort) routes with labels, hints, descriptions, and utility lookup methods; 9 unit tests covering ordering, case-insensitive lookup, uniqueness, and edge cases.
crates/tui/src/tui/model_picker.rs Adds show_whale_routes / selected_route_idx fields, splits render into render_whale_routes and render_classic helpers in the correct impl block, uses .position() for index init, and correctly sizes the popup. Navigation, fallback rows, and tests are all consistent.
crates/tui/src/tui/mod.rs Single-line module registration; no issues.

Reviews (5): Last reviewed commit: "fix(#2338): prevent known model + Auto e..." | Re-trigger Greptile

…mbown#2026)

Adds a central whale-route taxonomy that maps each (model,
reasoning_effort) pair to a friendly whale-species label sorted from
largest/deepest to smallest/fastest:

  Blue Whale   — Pro + max thinking
  Fin Whale    — Pro + high thinking
  Sperm Whale  — Pro + no thinking
  Humpback     — Flash + max thinking
  Minke Whale  — Flash + high thinking
  Porpoise     — Flash + no thinking

For DeepSeek providers the /model picker now shows a single-column
whale-route list instead of the two-column Model|Thinking layout.
Each route sets both model and effort at once. Pass-through providers
retain the classic layout.

New whale_routes module:
- WhaleRoute struct with label, model, effort, sort_order, hint, description
- WHALE_ROUTES const array (6 routes)
- for_model_effort() and by_sort_order() lookups
- 8 unit tests

Model picker changes:
- show_whale_routes flag activates on DeepSeek providers
- Selected route maps to model + effort simultaneously
- Fallback row for "auto" and custom models
- Updated test suite for whale-route behavior (7 new/updated tests)

Refs: Hmbown#1676, Hmbown#2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces 'whale routes' for DeepSeek providers in the model picker modal, mapping model and thinking-effort combinations to friendly whale-species labels sorted from largest to fastest. For other providers, the picker falls back to the classic two-column layout. Feedback on the changes highlights a compilation error in model_picker.rs due to a type mismatch in popup_height calculation, as well as a correctness bug in resolved_whale_model() where selecting the fallback row can incorrectly resolve to the initial model instead of 'auto'.

Comment thread crates/tui/src/tui/model_picker.rs Outdated
fn render_whale_routes(&self, area: Rect, buf: &mut Buffer) {
let popup_width = 62.min(area.width.saturating_sub(4)).max(44);
let row_count = self.whale_route_row_count();
let popup_height = (row_count + 4).min(area.height.saturating_sub(4)).max(8) as u16;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This line will fail to compile because row_count + 4 is of type usize while area.height.saturating_sub(4) is of type u16. In Rust, the .min() method requires both arguments to be of the same type. Casting the u16 value to usize resolves this type mismatch.

Suggested change
let popup_height = (row_count + 4).min(area.height.saturating_sub(4)).max(8) as u16;
let popup_height = (row_count + 4).min(area.height.saturating_sub(4) as usize).max(8) as u16;

Comment on lines +168 to +175
fn resolved_whale_model(&self) -> String {
if self.selected_route_idx < WHALE_ROUTES.len() {
WHALE_ROUTES[self.selected_route_idx].model.to_string()
} else {
// Past the last whale route: "auto" or custom.
self.initial_model.clone()
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

There is a correctness bug when resolving the model from the fallback row. If initial_model is a standard model (e.g., "deepseek-v4-pro") and the user selects the fallback row (which is rendered as "auto — select per turn" because show_custom_model_row is false), resolved_whale_model() incorrectly returns self.initial_model.clone() (i.e., "deepseek-v4-pro") instead of "auto". Checking self.show_custom_model_row ensures that we correctly resolve to "auto" when the fallback row represents the auto-select option.

Suggested change
fn resolved_whale_model(&self) -> String {
if self.selected_route_idx < WHALE_ROUTES.len() {
WHALE_ROUTES[self.selected_route_idx].model.to_string()
} else {
// Past the last whale route: "auto" or custom.
self.initial_model.clone()
}
}
fn resolved_whale_model(&self) -> String {
if self.selected_route_idx < WHALE_ROUTES.len() {
WHALE_ROUTES[self.selected_route_idx].model.to_string()
} else if self.show_custom_model_row {
self.initial_model.clone()
} else {
"auto".to_string()
}
}

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5ddac40909

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +171 to +173
} else {
// Past the last whale route: "auto" or custom.
self.initial_model.clone()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Return auto for the standard fallback row

When the current model is one of the standard whale routes, the picker appends a fallback row that is rendered as auto — select per turn, but selecting that row reaches this branch and returns initial_model instead of auto. In that scenario, choosing the displayed auto row applies the current model/effort unchanged rather than enabling per-turn auto selection; only the custom-model fallback should preserve initial_model.

Useful? React with 👍 / 👎.

Comment thread crates/tui/src/tui/model_picker.rs Outdated
#[must_use]
pub fn new(app: &App) -> Self {
let hide_deepseek_models = crate::config::provider_passes_model_through(app.api_provider);
let show_whale_routes = !hide_deepseek_models;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Limit whale routes to official DeepSeek providers

For non-pass-through compatible providers such as NVIDIA NIM, OpenRouter, Novita, Fireworks, SGLang, and vLLM, provider_passes_model_through is also false, so this enables whale-route mode even though those providers use provider-specific model IDs elsewhere (for example deepseek-ai/deepseek-v4-pro for NIM). In that context the current provider-specific model is treated as a custom fallback, and selecting any whale route applies the bare DeepSeek ID directly, bypassing the provider remapping used by /model <id> and config normalization.

Useful? React with 👍 / 👎.

Comment thread crates/tui/src/tui/model_picker.rs Outdated
Comment on lines +412 to +416
// Fallback row: "auto" if not set, otherwise the current custom model.
let fallback_label = if self.initial_model == "auto" {
"auto — select per turn".to_string()
} else if self.show_custom_model_row {
format!("{} — custom", self.initial_model)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep auto selectable for custom models

When the current model is an unrecognized/custom DeepSeek ID, the single fallback row is rendered as the custom model instead of adding both the custom row and the documented auto option. That means a user on a snapshot/custom model can no longer switch back to per-turn auto from /model, whereas the previous two-column picker always kept the auto row alongside the custom model.

Useful? React with 👍 / 👎.

Comment thread crates/tui/src/tui/model_picker.rs Outdated
Comment thread crates/tui/src/tui/model_picker.rs Outdated
Comment thread crates/tui/src/tui/model_picker.rs
… array index

- Fix usize/u16 type mismatch in popup_height (row_count as u16 + 4)
- Fallback rows: always show "auto" first, then custom model if applicable
- Limit whale routes to official DeepSeek/DeepSeekCN providers only
- Use array position (not sort_order) for selected_route_idx lookup
- Update tests for new fallback row indices
Comment thread crates/tui/src/tui/model_picker.rs
encyc added 2 commits May 29, 2026 10:22
These two methods were accidentally placed inside the ModalView trait
implementation block, which caused E0407 compilation errors on CI.
They are now in a separate impl ModelPickerView block.
…fort

- model_picker.rs no longer directly references WhaleRoute (uses
  WHALE_ROUTES.iter().position() instead of WhaleRoute::for_model_effort)
- whale_routes.rs: for_model_effort is only used in tests; add
  #[allow(dead_code)] to suppress -D warnings in release builds
@encyc

encyc commented May 29, 2026

Copy link
Copy Markdown
Contributor Author

CI status: macOS ✅, ubuntu ✅, lint ✅. The Windows failure is a pre-existing flaky test in composer_history::tests::append_history_dispatched_does_not_block_the_caller — a writer-thread timing race on Windows CI runners, unrelated to this PR's changes (model_picker.rs, whale_routes.rs).

@Hmbown

Hmbown commented May 31, 2026

Copy link
Copy Markdown
Owner

haha I like this - I am working on something similar with a whale pod mode - will use this to help!

@Hmbown

Hmbown commented May 31, 2026

Copy link
Copy Markdown
Owner

Still like the whale-route idea. I am leaving this open because review found one real model-picker bug: a known DeepSeek model paired with ReasoningEffort::Auto can fall through to the auto model row and silently replace the explicit model when the picker is applied.

Please key that fallback on whether the model is actually auto, add the regression around known-model + Auto effort, and clean up the test that mutates fields whale-route mode never reads. After that, the whale taxonomy slice should be much closer.

@Hmbown

Hmbown commented May 31, 2026

Copy link
Copy Markdown
Owner

Thanks @encyc, I still like this direction a lot. I tested the merged PR locally against current origin/main: the whale route and model picker tests pass, and the Windows CI failure is in an unrelated composer-history timing test.

I would hold merge for one correctness fix: a known DeepSeek model paired with ReasoningEffort::Auto currently falls through to the auto row, so applying the picker can silently replace the explicit model with "auto". Please key that fallback on whether the initial model is actually auto, add a regression for known-model + Auto effort, and clean up the test that mutates fields whale-route mode never reads.

One small taxonomy note too: #2016 excludes porpoises from the user-facing whale pool, so the final Flash/off tier should use a small whale name from the shared taxonomy instead of Porpoise. After that, this looks harvestable as the model-picker slice; I would keep #2026 open for receipts/footer/agent-card integration.

…ugh 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 Hmbown#2016, which excludes porpoises from the
  user-facing whale pool.
@encyc

encyc commented May 31, 2026

Copy link
Copy Markdown
Contributor Author

@Hmbown thanks for the careful review. Pushed fixes for all three points:

1. Auto effort fallthrough bug (the real one)
The fallback in new() was keyed on show_custom_model_row, but a known DeepSeek model (e.g. v4-pro) paired with ReasoningEffort::Auto would have show_custom_model_row = false (the model IS in PICKER_MODELS) yet not match any whale route. It silently landed on the "auto" row and replaced the explicit model.

Fixed by keying the fallback on initial_model.eq_ignore_ascii_case("auto") — only when the model is actually "auto" does it fall to the auto row. When the whale-route fallback selects the custom row, show_custom_model_row is now also set to true so the row is visible in the picker.

Regression test added: whale_routes_known_model_auto_effort_does_not_fall_to_auto.

2. Cleaned up test
picker_auto_model_forces_auto_effort_on_apply was mutating selected_model_idx / selected_effort_idx — fields whale-route mode never reads. Removed those lines; the test still passes because the constructor selects the correct route.

3. Porpoise → Beluga
Per #2016 excluding porpoises from the user-facing whale pool, renamed to Beluga at all sites (constant, tests, doc comments). Beluga is a proper toothed whale in the shared taxonomy.

All 28 model_picker / whale_routes tests pass locally.

@Hmbown Hmbown merged commit eb55cfe into Hmbown:main May 31, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants