mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
fix(model): collapse kimi alias/canonical to one /model picker row
A single Kimi credential surfaced two rows in the `/model` picker — the bare alias `kimi` (PROVIDER_TO_MODELS_DEV pass) and the canonical `kimi-coding` (CANONICAL_PROVIDERS cross-check, section 2b) — both backed by the same `kimi-for-coding` provider. `kimi`, `moonshot` and the canonical `kimi-coding` all map to one models.dev id (`kimi-for-coding`). The seen_mdev_ids guard collapses them to the first key in section 1, but that key is the bare alias, so 2b re-emits the canonical name as a second row. Emit the row under the canonical Hermes slug instead: resolve the alias via _PROVIDER_ALIASES (`kimi` -> `kimi-coding`) before appending, so 2b's seen_slugs check collapses the pair. This matches the picker's other alias rows (copilot, gemini) and the overlay slug-resolution contract, and keeps the surviving row resolvable to the real provider. A defensive seen_slugs guard prevents emitting a duplicate canonical row. Distinct providers keep their own row: `kimi-coding-cn` has its own KIMI_CN_API_KEY and is still emitted by section 2b. Regression tests assert the single-key case yields one `kimi-coding` row (fails on clean main, which shows both `kimi` and `kimi-coding`) and that the China endpoint is preserved. Fixes #49439
This commit is contained in:
parent
3133af8215
commit
b99e1e3bf6
2 changed files with 123 additions and 2 deletions
|
|
@ -1849,6 +1849,7 @@ def list_authenticated_providers(
|
|||
|
||||
# --- 1. Check Hermes-mapped providers ---
|
||||
from hermes_cli.models import _AGGREGATOR_PROVIDERS as _AGG_PROVIDERS
|
||||
from hermes_cli.models import _PROVIDER_ALIASES as _CANON_ALIASES
|
||||
from hermes_cli.providers import ALIASES as _PROVIDER_ALIAS_TABLE
|
||||
for hermes_id, mdev_id in PROVIDER_TO_MODELS_DEV.items():
|
||||
# Skip vendor names that are merely aliases routing through an
|
||||
|
|
@ -1940,14 +1941,34 @@ def list_authenticated_providers(
|
|||
else:
|
||||
top = model_ids[:max_models] if max_models is not None else model_ids
|
||||
|
||||
slug = hermes_id
|
||||
# Emit under the CANONICAL Hermes slug, not the bare alias. A single
|
||||
# credential can be reachable under several PROVIDER_TO_MODELS_DEV keys
|
||||
# that all share one models.dev id (e.g. "kimi", "moonshot" and the
|
||||
# canonical "kimi-coding" all map to "kimi-for-coding"). The
|
||||
# seen_mdev_ids guard above already collapses them to the first key —
|
||||
# but that first key is usually the bare alias ("kimi"), so emitting it
|
||||
# verbatim leaves section 2b free to re-emit the canonical "kimi-coding"
|
||||
# from CANONICAL_PROVIDERS: one key, two picker rows (#49439). Resolving
|
||||
# to the canonical slug here lets 2b's seen_slugs check collapse the
|
||||
# pair, matches the picker's other alias rows (copilot, gemini, …), and
|
||||
# keeps the row resolvable to the real provider.
|
||||
slug = _CANON_ALIASES.get(hermes_id.lower(), hermes_id)
|
||||
if slug.lower() in seen_slugs:
|
||||
# Canonical already emitted by an earlier alias in this pass; don't
|
||||
# add a second row for the same provider.
|
||||
seen_mdev_ids.add(mdev_id)
|
||||
continue
|
||||
pinfo = _mdev_pinfo(mdev_id)
|
||||
display_name = pinfo.name if pinfo else mdev_id
|
||||
|
||||
results.append({
|
||||
"slug": slug,
|
||||
"name": display_name,
|
||||
"is_current": slug == current_provider or mdev_id == current_provider,
|
||||
"is_current": (
|
||||
slug == current_provider
|
||||
or hermes_id == current_provider
|
||||
or mdev_id == current_provider
|
||||
),
|
||||
"is_user_defined": False,
|
||||
"models": top,
|
||||
"total_models": total,
|
||||
|
|
|
|||
|
|
@ -299,3 +299,103 @@ def test_current_custom_endpoint_passthrough_marks_current_row(monkeypatch):
|
|||
assert row["slug"] == "custom:ollama"
|
||||
assert row["is_current"] is True
|
||||
assert row["models"] == ["glm-5.1", "qwen3"]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# list_authenticated_providers: alias/canonical de-dup for Kimi (#49439)
|
||||
# ---------------------------------------------------------------------------
|
||||
#
|
||||
# A single Kimi credential used to surface TWO picker rows: the alias slug
|
||||
# "kimi" (emitted by the PROVIDER_TO_MODELS_DEV pass) plus its canonical
|
||||
# "kimi-coding" (re-emitted by the CANONICAL_PROVIDERS cross-check pass),
|
||||
# both backed by the same kimi-for-coding models.dev provider. The picker
|
||||
# must list each authenticated credential exactly once, under the CANONICAL
|
||||
# slug ("kimi-coding") — matching list_authenticated_providers' other alias
|
||||
# rows and the overlay slug-resolution contract (see
|
||||
# test_overlay_slug_resolution.py).
|
||||
|
||||
|
||||
def _stub_kimi_discovery(monkeypatch, *, canonical):
|
||||
"""Isolate list_authenticated_providers to the Kimi alias family.
|
||||
|
||||
Restricts the models.dev map / catalog / overlays / canonical list to
|
||||
just the Kimi entries and stubs the model-id fetch so discovery stays
|
||||
offline and deterministic. ``canonical`` is the CANONICAL_PROVIDERS list
|
||||
the 2b cross-check pass should iterate.
|
||||
"""
|
||||
import agent.models_dev as md
|
||||
import hermes_cli.models as hm
|
||||
|
||||
kimi_map = {
|
||||
"kimi": "kimi-for-coding",
|
||||
"kimi-coding": "kimi-for-coding",
|
||||
"moonshot": "kimi-for-coding",
|
||||
"kimi-coding-cn": "kimi-for-coding",
|
||||
}
|
||||
monkeypatch.setattr(md, "PROVIDER_TO_MODELS_DEV", kimi_map)
|
||||
monkeypatch.setattr(
|
||||
md, "fetch_models_dev",
|
||||
lambda *a, **k: {
|
||||
"kimi-for-coding": {"name": "Kimi For Coding", "env": ["KIMI_API_KEY"]},
|
||||
},
|
||||
)
|
||||
|
||||
class _PInfo:
|
||||
name = "Kimi For Coding"
|
||||
|
||||
monkeypatch.setattr(md, "get_provider_info", lambda _pid: _PInfo())
|
||||
monkeypatch.setattr("hermes_cli.providers.HERMES_OVERLAYS", {})
|
||||
monkeypatch.setattr(hm, "CANONICAL_PROVIDERS", canonical)
|
||||
monkeypatch.setattr(hm, "cached_provider_model_ids",
|
||||
lambda *a, **k: ["kimi-k2.6", "kimi-k2.5"])
|
||||
monkeypatch.setattr(hm, "clear_provider_models_cache", lambda *a, **k: None)
|
||||
|
||||
|
||||
def test_single_kimi_credential_yields_one_canonical_row(monkeypatch):
|
||||
"""One Kimi key yields a single row under the canonical 'kimi-coding' slug."""
|
||||
import hermes_cli.models as hm
|
||||
|
||||
_stub_kimi_discovery(
|
||||
monkeypatch,
|
||||
canonical=[hm.ProviderEntry("kimi-coding", "Kimi / Kimi Coding Plan", "desc")],
|
||||
)
|
||||
monkeypatch.setenv("KIMI_API_KEY", "sk-test-kimi")
|
||||
|
||||
rows = model_switch.list_authenticated_providers(max_models=10)
|
||||
slugs = [r["slug"] for r in rows]
|
||||
|
||||
# Exactly one Kimi / kimi-for-coding-backed row, under the canonical slug —
|
||||
# not both the alias ("kimi") and its canonical ("kimi-coding").
|
||||
kimi_rows = [s for s in slugs if s in {"kimi", "kimi-coding"}]
|
||||
assert kimi_rows == ["kimi-coding"], (
|
||||
f"expected a single canonical Kimi row, got: {slugs}"
|
||||
)
|
||||
assert slugs.count("kimi-coding") == 1
|
||||
assert "kimi" not in slugs
|
||||
|
||||
|
||||
def test_distinct_kimi_china_credential_still_listed(monkeypatch):
|
||||
"""A separate China (kimi-coding-cn) credential remains its own row.
|
||||
|
||||
Negative-control guard: the de-dup must collapse only the alias/canonical
|
||||
pair that share a credential, not legitimately distinct providers.
|
||||
"""
|
||||
import hermes_cli.models as hm
|
||||
|
||||
_stub_kimi_discovery(
|
||||
monkeypatch,
|
||||
canonical=[
|
||||
hm.ProviderEntry("kimi-coding", "Kimi / Kimi Coding Plan", "desc"),
|
||||
hm.ProviderEntry("kimi-coding-cn", "Kimi / Moonshot (China)", "desc"),
|
||||
],
|
||||
)
|
||||
monkeypatch.setenv("KIMI_API_KEY", "sk-test-kimi")
|
||||
monkeypatch.setenv("KIMI_CN_API_KEY", "sk-test-kimi-cn")
|
||||
|
||||
rows = model_switch.list_authenticated_providers(max_models=10)
|
||||
slugs = [r["slug"] for r in rows]
|
||||
|
||||
assert "kimi-coding" in slugs # canonical global row
|
||||
assert slugs.count("kimi-coding") == 1
|
||||
assert "kimi" not in slugs # alias collapsed into the canonical row
|
||||
assert "kimi-coding-cn" in slugs # distinct China endpoint preserved
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue