fix(gateway): show MoA presets in model picker

This commit is contained in:
dodo-reach 2026-06-27 10:05:53 +02:00 committed by Teknium
parent 789f8b7dc2
commit ed54469d06
4 changed files with 97 additions and 0 deletions

View file

@ -1206,6 +1206,7 @@ class GatewaySlashCommandsMixin:
user_providers=user_provs,
custom_providers=custom_provs,
max_models=50,
include_moa=True,
)
except Exception:
providers = []

View file

@ -2277,6 +2277,39 @@ def list_authenticated_providers(
return results
def _prepend_moa_picker_provider(providers: List[dict], current_provider: str = "") -> List[dict]:
"""Add the virtual MoA provider row used by interactive model pickers.
``list_authenticated_providers()`` only returns real/auth-backed providers.
The CLI model inventory adds MoA separately so named presets appear next to
normal providers; gateway pickers call ``list_picker_providers()`` directly,
so they need the same virtual row here.
"""
try:
from hermes_cli.config import load_config
from hermes_cli.moa_config import normalize_moa_config
cfg = normalize_moa_config(load_config().get("moa") or {})
models = list(cfg.get("presets", {}).keys())
if not models:
return providers
moa_row = {
"slug": "moa",
"name": "Mixture of Agents",
"is_current": (current_provider or "").lower() == "moa",
"is_user_defined": False,
"models": models,
"total_models": len(models),
"source": "virtual",
"authenticated": True,
"auth_type": "virtual",
"warning": "Aggregator acts as the selected model; references provide analysis before each call.",
}
return [moa_row] + [p for p in providers if str(p.get("slug", "")).lower() != "moa"]
except Exception:
return providers
def list_picker_providers(
current_provider: str = "",
current_base_url: str = "",
@ -2284,6 +2317,7 @@ def list_picker_providers(
custom_providers: list | None = None,
max_models: int | None = None,
current_model: str = "",
include_moa: bool = False,
) -> List[dict]:
"""Interactive-picker variant of :func:`list_authenticated_providers`.
@ -2314,6 +2348,8 @@ def list_picker_providers(
max_models=max_models,
current_model=current_model,
)
if include_moa:
providers = _prepend_moa_picker_provider(providers, current_provider=current_provider)
filtered: List[dict] = []
for p in providers:

View file

@ -166,3 +166,29 @@ async def test_picker_path_offloads_list_picker_providers(_isolated_config, monk
"list_picker_providers must be dispatched via asyncio.to_thread "
"(it was called inline on the event loop instead)"
)
@pytest.mark.asyncio
async def test_picker_path_requests_moa_presets(_isolated_config, monkeypatch):
"""Gateway /model pickers must opt into the virtual MoA preset provider."""
captured = {}
def _fake_list_picker_providers(**kwargs):
captured.update(kwargs)
return [{"slug": "moa", "name": "Mixture of Agents", "is_current": False,
"models": ["battle", "smart"], "total_models": 2}]
monkeypatch.setattr(
"hermes_cli.model_switch.list_picker_providers",
_fake_list_picker_providers,
)
runner = _make_runner()
runner.adapters = {Platform.TELEGRAM: _FakePickerAdapter()}
monkeypatch.setattr(runner, "_thread_metadata_for_source", lambda *a, **k: None, raising=False)
monkeypatch.setattr(runner, "_reply_anchor_for_event", lambda *a, **k: None, raising=False)
result = await runner._handle_model_command(_make_event())
assert result is None
assert captured["include_moa"] is True

View file

@ -109,6 +109,40 @@ def test_non_openrouter_rows_passed_through_unchanged(monkeypatch):
assert result[1]["models"] == ["gemini-3-flash-preview"]
def test_include_moa_adds_virtual_provider_with_named_presets(monkeypatch):
"""Gateway pickers opt into a virtual MoA provider so presets are tappable."""
base = [_make_provider("minimax", models=["MiniMax-M3"])]
moa_config = {
"moa": {
"default_preset": "battle",
"presets": {
"battle": {"enabled": True},
"smart": {"enabled": True},
},
}
}
monkeypatch.setattr(model_switch, "list_authenticated_providers",
lambda **kw: list(base))
monkeypatch.setattr("hermes_cli.config.load_config", lambda: moa_config)
monkeypatch.setattr("hermes_cli.models.fetch_openrouter_models",
lambda *a, **kw: pytest.fail("should not be called"))
result = model_switch.list_picker_providers(
current_provider="moa",
max_models=50,
include_moa=True,
)
assert [p["slug"] for p in result] == ["moa", "minimax"]
moa = result[0]
assert moa["name"] == "Mixture of Agents"
assert moa["is_current"] is True
assert moa["source"] == "virtual"
assert moa["models"] == ["battle", "smart"]
assert moa["total_models"] == 2
def test_empty_models_row_dropped(monkeypatch):
"""Built-in provider with an empty ``models`` list is dropped."""
base = [