fix(model-picker): dedup overlapping providers: dict and custom_providers: list entries

When a user's config has the same endpoint in both the providers: dict
(v12+ keyed schema) and custom_providers: list (legacy schema) — which
happens automatically when callers pass the output of
get_compatible_custom_providers() alongside the raw providers dict —
list_authenticated_providers() emitted two picker rows for the same
endpoint: one bare-slug from section 3 and one 'custom:<name>' from
section 4. The slug shapes differed, so seen_slugs dedup never fired,
and users saw the same endpoint twice with identical display labels.

Fix: section 3 records the (display_name, base_url) of each emitted
entry in _section3_emitted_pairs; section 4 skips groups whose
(name, api_url) pair was already emitted. Preserves existing behaviour
for users on either schema alone, and for distinct entries across both.

Test: test_list_authenticated_providers_no_duplicate_labels_across_schemas.
This commit is contained in:
Teknium 2026-04-19 22:04:19 -07:00 committed by Teknium
parent 519faa6e76
commit ca3a0bbc54
2 changed files with 72 additions and 0 deletions

View file

@ -304,6 +304,54 @@ def test_list_authenticated_providers_dedupes_when_user_and_custom_overlap(monke
assert matches[0]["models"] == ["gpt-5.4", "grok-4.20-beta"]
def test_list_authenticated_providers_no_duplicate_labels_across_schemas(monkeypatch):
"""Regression: same endpoint in both ``providers:`` dict AND ``custom_providers:``
list (e.g. via ``get_compatible_custom_providers()``) must not emit two picker
rows with identical display names.
Before the fix, section 3 emitted bare-slug rows ("openrouter") and section 4
emitted ``custom:openrouter`` rows for the same endpoint both labelled
identically, bypassing ``seen_slugs`` dedup because the slug shapes differ.
"""
monkeypatch.setattr("agent.models_dev.fetch_models_dev", lambda: {})
monkeypatch.setattr("hermes_cli.providers.HERMES_OVERLAYS", {})
shared_entries = [
("endpoint-a", "http://a.local/v1"),
("endpoint-b", "http://b.local/v1"),
("endpoint-c", "http://c.local/v1"),
]
user_providers = {
name: {"name": name, "base_url": url, "model": "m1"}
for name, url in shared_entries
}
custom_providers = [
{"name": name, "base_url": url, "model": "m1"}
for name, url in shared_entries
]
providers = list_authenticated_providers(
current_provider="none",
user_providers=user_providers,
custom_providers=custom_providers,
max_models=50,
)
user_rows = [p for p in providers if p.get("source") == "user-config"]
# Expect one row per shared entry — not two.
assert len(user_rows) == len(shared_entries), (
f"Expected {len(shared_entries)} rows, got {len(user_rows)}: "
f"{[(p['slug'], p['name']) for p in user_rows]}"
)
# And zero duplicate display labels.
labels = [p["name"].lower() for p in user_rows]
assert len(labels) == len(set(labels)), (
f"Duplicate labels across picker rows: {labels}"
)
# =============================================================================
# Tests for _get_named_custom_provider with providers: dict
# =============================================================================