NousResearch/hermes-agent

Model picker filtering and deduplication fixes

Cluster 16970. Data as of

Nothing open on GitHub. Fix merged in #17511 on 29 Apr 2026. Check it covers your case.

The short answer

0open issues
0open PRs
4items grouped
Known?
Yes. 4 related items grouped here, 1 issue already closed.
Fix event
A fix was merged to main in PR #17511. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this resolved, deciding PR #17511. That is separate from the fix event.
Still open
Nothing open on GitHub.

These issues and PRs address bugs in the /model picker related to honoring model_catalog.enabled=false settings and deduplicating rows when custom provider endpoints match built-in models.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #17511 fix(model_switch): dedup /model picker rows when custom provider endpoint matches a built-in (#16970)

    merged to main

    files: hermes_cli/model_switch.py, tests/hermes_cli/test_user_providers_model_switch.py

    Show the diff
    • hermes_cli/model_switch.py
      @@ -1018,6 +1018,37 @@ def list_authenticated_providers(
           results: List[dict] = []
           seen_slugs: set = set()  # lowercase-normalized to catch case variants (#9545)
           seen_mdev_ids: set = set()  # prevent duplicate entries for aliases (e.g. kimi-coding + kimi-coding-cn)
      +    # Effective base URLs of every built-in row we emit (normalized lower+rstrip).
      +    # Section 4 uses this to hide ``custom_providers`` entries that point at the
      +    # same endpoint as a built-in (e.g. a user-defined "my-dashscope" on
      +    # https://coding-intl.dashscope.aliyuncs.com/v1 collides with the built-in
      +    # alibaba-coding-plan row when DASHSCOPE_API_KEY is present). Fixes #16970.
      +    _builtin_endpoints: set = set()
      +
      +    def _norm_url(url: str) -> str:
      +        return str(url or "").strip().rstrip("/").lower()
      +
      +    def _record_builtin_endpoint(slug: str) -> None:
      +        """Record the effective base URL for a built-in provider row.
      +
      +        Prefers the live env-override (e.g. DASHSCOPE_BASE_URL) over the
      +        static inference_base_url so the dedup matches what a user typing
      +        that URL into custom_providers would actually hit."""
      +        try:
      +            from hermes_cli.auth import PROVIDER_REGISTRY as _reg
      +        except Exception:
      +            return
      +        pcfg = _reg.get(slug)
      +        if not pcfg:
      +            return
      +        url = ""
      +        if getattr(pcfg, "base_url_env_var", ""):
      +            url = os.environ.get(pcfg.base_url_env_var, "") or ""
      +        if not url:
      +            url = getattr(pcfg, "inference_base_url", "") or ""
      +        normed = _norm_url(url)
      +        if normed:
      +            _builtin_endpoints.add(normed)
       
           data = fetch_models_dev()
       
      @@ -1124,6 +1155,7 @@ def list_authenticated_providers(
               })
               seen_slugs.add(slug.lower())
               seen_mdev_ids.add(mdev_id)
      +        _record_builtin_endpoint(slug)
       
           # --- 2. Check Hermes-only providers (nous, openai-codex, copilot, opencode-go) ---
           from hermes_cli.providers import HERMES_OVERLAYS
      @@ -1238,6 +1270,7 @@ def list_authenticated_providers(
               })
               seen_slugs.add(pid.lower())
               seen_slugs.add(hermes_slug.lower())
      +        _record_builtin_endpoint(hermes_slug)
       
           # --- 2b. Cross-check canonical provider list ---
           # Catches providers that are in CANONICAL_PROVIDERS but weren't found
      @@ -1317,6 +1350,7 @@ def list_authenticated_providers(
                   "source": "canonical",
               })
               seen_slugs.add(_cp.slug.lower())
      +        _record_builtin_endpoint(_cp.slug)
       
           # --- 3. User-defined endpoints from config ---
           # Track (name, base_url) of what section 3 emits so section 4 can skip
      @@ -1526,6 +1560,15 @@ def list_authenticated_providers(
                   )
                   if _pair_key[0] and _pair_key[1] and _pair_key in _section3_emitted_pairs:
                       continue
      +            # Skip if a built-in row (sections 1/2/2b) already represents this
      +            # endpoint. Fixes #16970: a user-defined "my-dashscope" pointing at
      +            # https://coding-intl.dashscope.aliyuncs.com/v1 duplicates the
      +            # built-in alibaba-coding-plan row whenever DASHSCOPE_API_KEY is
      +            # set. The built-in row carries the curated model list, correct
      +            # auth wiring, and canonical slug — keep it and hide the shadow.
      +            _grp_url_norm = _pair_key[1]
      +            if _grp_url_norm and _grp_url_norm in _builtin_endpoints:
      +                continue
                   results.append({
                       "slug": slug,
                       "name": grp["name"],
    • tests/hermes_cli/test_user_providers_model_switch.py
      @@ -453,6 +453,142 @@ def test_list_authenticated_providers_no_duplicate_labels_across_schemas(monkeyp
           )
       
       
      +def test_list_authenticated_providers_hides_custom_shadowing_builtin_endpoint(monkeypatch):
      +    """#16970: a custom_providers entry whose ``base_url`` matches a built-in
      +    provider's endpoint should be hidden. The built-in row already represents
      +    that endpoint with its canonical slug, curated model list, and auth wiring.
      +
      +    Repro: user sets ``DASHSCOPE_API_KEY`` (triggers the built-in ``alibaba``
      +    row pointing at the static ``inference_base_url``) AND defines a
      +    ``my-alibaba`` custom provider pointing at the same URL. Before the fix,
      +    the picker showed both rows for one endpoint.
      +    """
      +    monkeypatch.setenv("DASHSCOPE_API_KEY", "sk-test")
      +    monkeypatch.setattr(
      +        "agent.models_dev.fetch_models_dev",
      +        lambda: {
      +            "alibaba": {
      +                "name": "Alibaba Cloud (DashScope)",
      +                "env": ["DASHSCOPE_API_KEY"],
      +            }
      +        },
      +    )
      +    monkeypatch.setattr("hermes_cli.providers.HERMES_OVERLAYS", {})
      +
      +    custom_providers = [
      +        {
      +            "name": "my-alibaba",
      +            # Matches PROVIDER_REGISTRY['alibaba'].inference_base_url exactly.
      +            "base_url": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
      +            "api_key": "[redacted:secret-assignment]",
      +            "model": "qwen3.6-plus",
      +            "models": {"qwen3.6-plus": {"context_length": 500000}},
      +        }
      +    ]
      +
      +    providers = list_authenticated_providers(
      +        current_provider="my-alibaba",
      +        user_providers={},
      +        custom_providers=custom_providers,
      +        max_models=50,
      +    )
      +
      +    slugs = [p["slug"] for p in providers]
      +    # Built-in alibaba row should be present.
      +    assert "alibaba" in slugs, (
      +        f"Expected built-in alibaba row, got slugs: {slugs}"
      +    )
      +    # Custom shadow row should be hidden — its base_url matches the built-in's.
      +    assert not any("my-alibaba" in s for s in slugs), (
      +        f"Custom my-alibaba should have been dedup'd against the built-in "
      +        f"alibaba endpoint, got slugs: {slugs}"
      +    )
      +
      +
      +def test_list_authenticated_providers_keeps_custom_with_distinct_endpoint(monkeypatch):
      +    """Dedup must only apply when the endpoint matches a built-in. A custom
      +    provider on a genuinely distinct endpoint stays visible even if a
      +    built-in is also authenticated."""
      +    monkeypatch.setenv("DASHSCOPE_API_KEY", "sk-test")
      +    monkeypatch.setattr(
      +        "agent.models_dev.fetch_models_dev",
      +        lambda: {
      +            "alibaba": {
      +                "name": "Alibaba Cloud (DashScope)",
      +                "env": ["DASHSCOPE_API_KEY"],
      +            }
      +        },
      +    )
      +    monkeypatch.setattr("hermes_cli.providers.HERMES_OVERLAYS", {})
      +
      +    custom_providers = [
      +        {
      +            "name": "my-private-relay",
      +            "base_url": "https://relay.example.internal/v1",
      +            "api_key": "[redacted:secret-assignment]",
      +            "model": "qwen3.6-plus",
      +            "models": {"qwen3.6-plus": {}},
      +        }
      +    ]
      +
      +    providers = list_authenticated_providers(
      +        current_provider="my-private-relay",
      +        user_providers={},
      +        custom_providers=custom_providers,
      +        max_models=50,
      +    )
      +
      +    slugs = [p["slug"] for p in providers]
      +    assert any("my-private-relay" in s for s in slugs), (
      +        f"Custom provider on distinct endpoint must stay visible, got: {slugs}"
      +    )
      +
      +
      +def test_list_authenticated_providers_dedup_honors_base_url_env_override(monkeypatch):
      +    """The dedup must track the EFFECTIVE endpoint — if DASHSCOPE_BASE_URL
      +    overrides the static inference_base_url, a custom provider pointing at
      +    the overridden URL (not the static one) should still be recognized as
      +    a duplicate."""
      +    monkeypatch.setenv("DASHSCOPE_API_KEY", "sk-test")
      +    monkeypatch.setenv(
      +        "DASHSCOPE_BASE_URL",
      +        "https://custom-dashscope.example.com/v1",
      +    )
      +    monkeypatch.setattr(
      +        "agent.models_dev.fetch_models_dev",
      +        lambda: {
      +            "alibaba": {
      +                "name": "Alibaba Cloud (DashScope)",
      +                "env": ["DASHSCOPE_API_KEY"],
      +            }
      +        },
      +    )
      +    monkeypatch.setattr("hermes_cli.providers.HERMES_OVERLAYS", {})
      +
      +    custom_providers = [
      +        {
      +            "name": "my-dashscope-override",
      +            # Same URL as DASHSCOPE_BASE_URL env override above.
      +            "base_url": "https://custom-dashscope.example.com/v1",
      +            "api_key": "[redacted:secret-assignment]",
      +            "model": "qwen3.6-plus",
      +        }
      +    ]
      +
      +    providers = list_authenticated_providers(
      +        current_provider="alibaba",
      +        user_providers={},
      +        custom_providers=custom_providers,
      +        max_models=50,
      +    )
      +
      +    slugs = [p["slug"] for p in providers]
      +    assert not any("my-dashscope-override" in s for s in slugs), (
      +        f"Custom entry matching env-overridden built-in endpoint should be "
      +        f"dedup'd, got: {slugs}"
      +    )
      +
      +
       # =============================================================================
       # Tests for _get_named_custom_provider with providers: dict
       # =============================================================================

2 PRs closed without merging

From the close comments: superseded by another PR 2

  • #16975 fix(cli): respect model_catalog filtering in /model picker · superseded by another PR → #17511
  • #17218 fix(model_switch): honor model_catalog.enabled=false in /model picker (#16970) · superseded by another PR → #17511
Closed issues · 1
  • Closed#16970Bug: Model Picker ignores model_catalog.enabled=false
How this cluster was grouped
  • Link evidence (structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
  • Pairwise link density 0.50: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Merged PRs
#17511
Closed issues
1
Attempts
2 PRs closed without merging
Fix link checked
29 Apr 2026

For your agent

The same facts, typed.