Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -5455,7 +5455,12 @@ def _compute_model_picker_viewport(
scroll_offset = max(0, min(scroll_offset, n - visible))
return scroll_offset, visible

def _apply_model_switch_result(self, result, persist_global: bool) -> None:
def _apply_model_switch_result(
self,
result,
persist_global: bool,
custom_providers: list | None = None,
) -> None:
if not result.success:
_cprint(f" βœ— {result.error_message}")
return
Expand Down Expand Up @@ -5507,7 +5512,12 @@ def _apply_model_switch_result(self, result, persist_global: bool) -> None:
base_url=result.base_url or self.base_url or "",
api_key=result.api_key or self.api_key or "",
model_info=mi,
config_context_length=getattr(self.agent, "_config_context_length", None) if self.agent else None,
custom_providers=custom_providers,
config_context_length=(
getattr(self.agent, "_config_context_length", None)
if self.agent
else None
),
)
if ctx:
_cprint(f" Context: {ctx:,} tokens")
Expand Down Expand Up @@ -5595,7 +5605,11 @@ def _handle_model_picker_selection(self, persist_global: bool = False) -> None:
custom_providers=state.get("custom_provs"),
)
self._close_model_picker()
self._apply_model_switch_result(result, persist_global)
self._apply_model_switch_result(
result,
persist_global,
custom_providers=state.get("custom_provs"),
)
return
self._close_model_picker()

Expand Down Expand Up @@ -5734,7 +5748,12 @@ def _handle_model_switch(self, cmd_original: str):
base_url=result.base_url or self.base_url or "",
api_key=result.api_key or self.api_key or "",
model_info=mi,
config_context_length=getattr(self.agent, "_config_context_length", None) if self.agent else None,
custom_providers=custom_provs,
config_context_length=(
getattr(self.agent, "_config_context_length", None)
if self.agent
else None
),
)
if ctx:
_cprint(f" Context: {ctx:,} tokens")
Expand Down
20 changes: 20 additions & 0 deletions hermes_cli/model_switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,26 @@ def resolve_display_context_length(
Prefer the provider-aware value; fall back to ``model_info.context_window``
only if the resolver returns nothing.
"""
if (
config_context_length is not None
and isinstance(config_context_length, int)
and config_context_length > 0
):
return config_context_length

if custom_providers and base_url and model:
try:
from hermes_cli.config import get_custom_provider_context_length
ctx = get_custom_provider_context_length(
model=model,
base_url=base_url,
custom_providers=custom_providers,
)
if ctx:
return int(ctx)
except Exception:
pass

try:
from agent.model_metadata import get_model_context_length
ctx = get_model_context_length(
Expand Down
41 changes: 39 additions & 2 deletions tests/hermes_cli/test_apply_model_switch_result_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,19 @@ class _StubCLI:
_pending_model_switch_note = ""


def _run_display(monkeypatch, result):
def _run_display(monkeypatch, result, custom_providers=None):
import cli as cli_mod

captured: list[str] = []
monkeypatch.setattr(cli_mod, "_cprint", lambda s, *a, **k: captured.append(str(s)))
# Avoid writing to ~/.hermes/config.yaml during the test.
monkeypatch.setattr(cli_mod, "save_config_value", lambda *a, **k: None)
cli_mod.HermesCLI._apply_model_switch_result(_StubCLI(), result, False)
cli_mod.HermesCLI._apply_model_switch_result(
_StubCLI(),
result,
False,
custom_providers=custom_providers,
)
return captured


Expand Down Expand Up @@ -150,3 +155,35 @@ def test_picker_path_falls_back_to_model_info_when_resolver_empty(monkeypatch):
assert "1,050,000" in ctx_line, (
f"resolver-empty path should fall back to ModelInfo, got: {ctx_line!r}"
)


def test_picker_path_honors_custom_provider_context(monkeypatch):
"""Picker confirmation must pass custom_providers into the shared resolver."""
result = ModelSwitchResult(
success=True,
new_model="local-model",
target_provider="custom:local",
provider_changed=True,
api_key="",
base_url="http://localhost:1234/v1",
api_mode="chat_completions",
warning_message="",
provider_label="Local",
resolved_via_alias=False,
capabilities=None,
model_info=_FakeModelInfo(),
is_global=False,
)
custom_providers = [
{
"name": "Local",
"base_url": "http://localhost:1234/v1",
"models": {"local-model": {"context_length": 900_000}},
}
]

lines = _run_display(monkeypatch, result, custom_providers=custom_providers)

ctx_line = next((l for l in lines if "Context:" in l), "")
assert "900,000" in ctx_line
assert "1,050,000" not in ctx_line
31 changes: 31 additions & 0 deletions tests/hermes_cli/test_model_switch_context_display.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,17 @@ def test_prefers_resolver_even_when_model_info_has_larger_value(self):
)
assert ctx == 128_000

def test_config_context_length_overrides_resolver_and_model_info(self):
fake_mi = _FakeModelInfo(2_000_000)
ctx = resolve_display_context_length(
"custom-model",
"custom",
base_url="https://example.invalid/v1",
model_info=fake_mi,
config_context_length=750_000,
)
assert ctx == 750_000

def test_custom_providers_override_honored(self):
"""Regression for #15779: /model switch onto a custom provider must
surface the configured per-model context_length, not the 128K/256K
Expand Down Expand Up @@ -146,3 +157,23 @@ def test_custom_providers_trailing_slash_insensitive(self):
custom_providers=custom_provs,
)
assert ctx == 400_000

def test_non_matching_custom_provider_does_not_override(self):
fake_mi = _FakeModelInfo(1_050_000)
custom_provs = [
{
"base_url": "https://example.invalid/v1",
"models": {"m": {"context_length": 400_000}},
}
]
with patch(
"agent.model_metadata.get_model_context_length", return_value=256_000
):
ctx = resolve_display_context_length(
"m",
"custom",
base_url="https://other.invalid/v1",
model_info=fake_mi,
custom_providers=custom_provs,
)
assert ctx == 256_000
Loading