fix(model-picker): OpenAI shows curated models; OpenRouter no longer phantom-shows (#37404)

The model picker now matches `hermes model` for OpenAI, and OpenRouter
stops appearing as authenticated when only OPENAI_API_KEY is set.

- models.py: provider_model_ids() for the default api.openai.com endpoint
  intersects the live /v1/models dump (120+ entries incl. embeddings,
  whisper, tts, dall-e, moderation, legacy chat) with the curated agentic
  list, preserving curated order. Custom OpenAI-compatible endpoints keep
  the live list verbatim so discovery still works.
- providers.py: drop extra_env_vars=("OPENAI_API_KEY",) from the openrouter
  overlay. list_authenticated_providers reads extra_env_vars to decide
  whether a provider is authenticated, so any OpenAI user saw a phantom
  OpenRouter row. Runtime OpenRouter credential resolution still falls back
  to OPENAI_API_KEY (runtime_provider.py), independent of the overlay.
- Regression tests for both paths.
This commit is contained in:
Teknium
2026-06-02 06:31:37 -07:00
committed by GitHub
parent 195c4d2a98
commit afea650e16
3 changed files with 119 additions and 1 deletions
+23
View File
@@ -2106,9 +2106,32 @@ def provider_model_ids(provider: Optional[str], *, force_refresh: bool = False)
if api_key:
base_raw = os.getenv("OPENAI_BASE_URL", "").strip().rstrip("/")
base = base_raw or "https://api.openai.com/v1"
# Custom OpenAI-compatible endpoints (proxies, gateways, self-hosted)
# may serve a small curated catalog — use the live list verbatim so
# discovery works. But the canonical api.openai.com /v1/models dump
# is 120+ entries of embeddings, whisper, tts, dall-e, moderation and
# legacy chat models — none of which belong in the agent model picker.
# For the default endpoint, intersect the live list with our curated
# agentic catalog so ``/model`` matches what ``hermes model`` shows.
is_default_openai = base.rstrip("/") in (
"https://api.openai.com/v1",
"https://api.openai.com",
)
try:
live = fetch_api_models(api_key, base)
if live:
if is_default_openai:
live_lower = {m.lower() for m in live}
curated = list(_PROVIDER_MODELS.get(normalized, []))
# Keep curated order; only surface curated models the
# account actually has access to.
filtered = [m for m in curated if m.lower() in live_lower]
if filtered:
return filtered
# Account serves none of the curated models (rare —
# e.g. org without GPT-5 access). Fall back to curated
# so the picker still offers sane defaults.
return curated or live
return live
except Exception:
pass