fix(inventory): keep user-defined custom providers in model dedup

The #45954 model-dedup builds `user_models` from every is_user_defined
row, then strips those model IDs from every row where is_aggregator(slug)
is True. But is_aggregator() returns True for *every* `custom:*` slug, and
list_authenticated_providers emits named custom providers with slug
`custom:<name>` and is_user_defined=True. So a user's own custom provider
is treated as an aggregator and filtered against user_models — which holds
exactly its own models (the row helped build that set). Every model is
removed, the row drops to zero, and the provider disappears from the model
picker.

Guard the dedup loop to skip is_user_defined rows: a user's configured
provider is never an aggregator duplicate of itself. Built-in aggregators
(openrouter, etc.) are still deduped as before. Adds a regression test.
This commit is contained in:
cyb0rgk1tty 2026-06-15 23:25:54 +00:00 committed by Teknium
parent f4ef70f6fc
commit b7fa62c530
2 changed files with 40 additions and 0 deletions

View file

@ -178,6 +178,14 @@ def build_models_payload(
user_models.update(m.lower() for m in (row.get("models") or []))
if user_models:
for row in rows:
# A user's own configured provider is never an "aggregator
# duplicate" of itself: user_models is built from these very
# rows, and is_aggregator() reports True for every custom:*
# slug. Without this guard the dedup strips a user-defined
# custom provider's entire model list (all of it lives in
# user_models), emptying its picker row.
if row.get("is_user_defined"):
continue
slug = row.get("slug", "")
if not _is_aggregator(slug):
continue

View file

@ -606,3 +606,35 @@ def test_aggregator_dedup_multiple_user_providers():
assert or_row["models"] == ["model-z"]
assert or_row["total_models"] == 1
def test_aggregator_dedup_does_not_empty_user_defined_custom_provider():
"""A named custom provider has slug ``custom:<name>``, which makes it
*both* ``is_user_defined=True`` *and* ``is_aggregator()==True``
(is_aggregator reports True for every ``custom:*`` slug). The dedup
must skip user-defined rows: their models populate ``user_models``, so
filtering them against that set would strip the row's entire catalog and
hide the provider from the picker. Regression for the #45954 dedup
emptying ``custom:*`` providers (e.g. a local llama.cpp endpoint or an
Anthropic-compatible proxy)."""
rows = [
_user_provider_row("custom:my-proxy", ["my-model-a", "my-model-b"]),
_aggregator_row("openrouter", ["my-model-a", "other/model"]),
]
ctx = _empty_ctx()
with _list_auth_returning(rows):
payload = build_models_payload(ctx)
proxy_row = next(
r for r in payload["providers"] if r["slug"] == "custom:my-proxy"
)
or_row = next(r for r in payload["providers"] if r["slug"] == "openrouter")
# The user's own custom provider keeps all of its models.
assert proxy_row["models"] == ["my-model-a", "my-model-b"]
assert proxy_row["total_models"] == 2
# A genuine aggregator is still deduped against the user's models.
assert "my-model-a" not in or_row["models"]
assert "other/model" in or_row["models"]
assert or_row["total_models"] == 1