mirror of
https://github.com/langchain-ai/deepagents.git
synced 2026-07-21 17:25:26 -04:00
fix(code): hide "Recent" section during onboarding model selection (#4198)
Onboarding model selection no longer shows a spurious "Recent" model on a fresh install. --- On a fresh install, the onboarding "Choose a Recommended Model" screen showed a "Recent" entry for a model the user never picked. The startup default-fallback resolution auto-detects a provider and persists it to the recent-models MRU (`save_recent_model` / `touch_recent_model`), which the curated onboarding picker then surfaced as "Recent". The fix skips loading recents in curated (onboarding) mode, so the section only appears once a user has genuinely switched models. Made by [Open SWE](https://openswe.vercel.app/agents/1188b11f-a113-c648-0ba7-b0a91b2f5c0c) --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
This commit is contained in:
@@ -481,6 +481,12 @@ class ModelSelectorScreen(ModalScreen[tuple[str, str] | None]):
|
||||
return i
|
||||
return 0
|
||||
|
||||
def _initial_selected_index(self) -> int:
|
||||
"""Return the default highlighted row for the current selector mode."""
|
||||
if self._curated:
|
||||
return 0
|
||||
return self._find_current_model_index()
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
"""Compose the screen layout.
|
||||
|
||||
@@ -533,6 +539,7 @@ class ModelSelectorScreen(ModalScreen[tuple[str, str] | None]):
|
||||
cli_override: dict[str, Any] | None,
|
||||
*,
|
||||
include_uninstalled: bool = True,
|
||||
include_recent: bool = True,
|
||||
) -> _ModelData:
|
||||
"""Gather model discovery data synchronously.
|
||||
|
||||
@@ -547,6 +554,12 @@ class ModelSelectorScreen(ModalScreen[tuple[str, str] | None]):
|
||||
install-required rows; (2) the provider is installed but its
|
||||
upstream profiles omit the model, added as normal selectable
|
||||
rows.
|
||||
include_recent: When `True`, load the recent-models MRU so the
|
||||
pinned "Recent" section can render. Onboarding sets this
|
||||
`False`: first-run users have never picked a model, and the
|
||||
startup default-fallback resolution writes its auto-detected
|
||||
pick into the MRU, which would otherwise surface as a bogus
|
||||
"Recent" entry the user never chose.
|
||||
|
||||
Returns:
|
||||
A `_ModelData` bundle of the discovered models, default spec,
|
||||
@@ -612,7 +625,7 @@ class ModelSelectorScreen(ModalScreen[tuple[str, str] | None]):
|
||||
all_models.extend(uninstalled_recommended)
|
||||
|
||||
profiles = get_model_profiles(cli_override=cli_override)
|
||||
recent_specs = load_recent_models()
|
||||
recent_specs = load_recent_models() if include_recent else []
|
||||
return _ModelData(
|
||||
all_models,
|
||||
config.default_model,
|
||||
@@ -701,6 +714,7 @@ class ModelSelectorScreen(ModalScreen[tuple[str, str] | None]):
|
||||
self._load_model_data,
|
||||
self._cli_profile_override,
|
||||
include_uninstalled=True,
|
||||
include_recent=not self._curated,
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("Failed to load model data for /model selector")
|
||||
@@ -728,7 +742,7 @@ class ModelSelectorScreen(ModalScreen[tuple[str, str] | None]):
|
||||
self._install_extras = data.install_extras
|
||||
self._all_models = self._apply_subset(self._unfiltered_models)
|
||||
self._filtered_models = list(self._all_models)
|
||||
self._selected_index = self._find_current_model_index()
|
||||
self._selected_index = self._initial_selected_index()
|
||||
self._loaded = True
|
||||
|
||||
# Re-apply any filter text the user typed while data was loading
|
||||
@@ -816,7 +830,7 @@ class ModelSelectorScreen(ModalScreen[tuple[str, str] | None]):
|
||||
query = self._filter_text.strip()
|
||||
if not query:
|
||||
self._filtered_models = list(self._all_models)
|
||||
self._selected_index = self._find_current_model_index()
|
||||
self._selected_index = self._initial_selected_index()
|
||||
return
|
||||
|
||||
tokens = query.split()
|
||||
@@ -837,7 +851,7 @@ class ModelSelectorScreen(ModalScreen[tuple[str, str] | None]):
|
||||
exc_info=True,
|
||||
)
|
||||
self._filtered_models = list(search_models)
|
||||
self._selected_index = self._find_current_model_index()
|
||||
self._selected_index = self._initial_selected_index()
|
||||
return
|
||||
|
||||
self._filtered_models = [
|
||||
|
||||
@@ -626,6 +626,47 @@ class TestRecentModelsSection:
|
||||
specs = [spec for spec, _ in screen._filtered_models]
|
||||
assert "anthropic:claude-sonnet-4-5" in specs
|
||||
|
||||
async def test_recent_section_hidden_during_onboarding(
|
||||
self, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""Curated onboarding never shows Recent, even if the MRU is populated.
|
||||
|
||||
Guards the `include_recent` gating in `_load_model_data` (see its
|
||||
docstring for why the startup auto-detected fallback must not surface
|
||||
as a "Recent" entry the user never chose).
|
||||
"""
|
||||
from deepagents_code.widgets import model_selector
|
||||
|
||||
recent_called = False
|
||||
|
||||
def _tracked_load_recent_models() -> list[str]:
|
||||
nonlocal recent_called
|
||||
recent_called = True
|
||||
# Deliberately a recommended model so it survives curated filtering:
|
||||
# on a revert it would reach the rendered "Recent" header, keeping
|
||||
# the header assertion below an effective regression guard.
|
||||
return ["anthropic:claude-opus-4-7"]
|
||||
|
||||
monkeypatch.setattr(
|
||||
model_selector,
|
||||
"load_recent_models",
|
||||
_tracked_load_recent_models,
|
||||
)
|
||||
|
||||
app = ModelSelectorTestApp()
|
||||
async with app.run_test() as pilot:
|
||||
screen = ModelSelectorScreen(curated=True)
|
||||
app.push_screen(screen)
|
||||
await pilot.pause()
|
||||
|
||||
assert recent_called is False
|
||||
assert screen._recent_specs == []
|
||||
headers = [
|
||||
str(h.content)
|
||||
for h in screen.query(".model-provider-header").results(Static)
|
||||
]
|
||||
assert not any("Recent" in h for h in headers)
|
||||
|
||||
async def test_recent_section_hidden_during_filter(
|
||||
self, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
@@ -1345,6 +1386,21 @@ class TestCuratedModelSelection:
|
||||
("openai:gpt-5.3-codex", "openai"),
|
||||
]
|
||||
|
||||
def test_curated_initial_selection_starts_at_top(self) -> None:
|
||||
"""Onboarding should highlight the first model, not the current one."""
|
||||
screen = ModelSelectorScreen(
|
||||
current_model="claude-opus-4-7",
|
||||
current_provider="anthropic",
|
||||
curated=True,
|
||||
)
|
||||
screen._filtered_models = [
|
||||
("openai:gpt-5.5", "openai"),
|
||||
("anthropic:claude-opus-4-7", "anthropic"),
|
||||
]
|
||||
|
||||
assert screen._find_current_model_index() == 1
|
||||
assert screen._initial_selected_index() == 0
|
||||
|
||||
|
||||
class TestFormatOptionLabel:
|
||||
"""Tests for _format_option_label."""
|
||||
@@ -1860,8 +1916,10 @@ class TestModelSelectorInstallRouting:
|
||||
_cli_override: dict[str, Any] | None,
|
||||
*,
|
||||
include_uninstalled: bool = True,
|
||||
include_recent: bool = True,
|
||||
) -> model_selector._ModelData:
|
||||
captured["include_uninstalled"] = include_uninstalled
|
||||
captured["include_recent"] = include_recent
|
||||
return model_selector._ModelData(
|
||||
[("baseten:zai-org/GLM-5.2", "baseten")],
|
||||
None,
|
||||
@@ -1888,6 +1946,10 @@ class TestModelSelectorInstallRouting:
|
||||
await pilot.pause()
|
||||
|
||||
assert captured["include_uninstalled"] is True
|
||||
# Curated onboarding must skip the recent-models MRU at the call site,
|
||||
# independent of the rendering-level guard in
|
||||
# test_recent_section_hidden_during_onboarding.
|
||||
assert captured["include_recent"] is False
|
||||
|
||||
async def test_load_model_data_surfaces_uninstalled_recommended(
|
||||
self, monkeypatch: pytest.MonkeyPatch
|
||||
|
||||
Reference in New Issue
Block a user