fix(cli): non-zero /model counts for native OpenAI and direct API rows

This commit is contained in:
XieNBi 2026-04-24 05:41:01 +08:00 committed by Teknium
parent 7f26cea390
commit 4a51ab61eb
3 changed files with 84 additions and 0 deletions

View file

@ -1240,6 +1240,15 @@ def list_authenticated_providers(
if m and m not in models_list: if m and m not in models_list:
models_list.append(m) models_list.append(m)
# Official OpenAI API rows in providers: often have base_url but no
# explicit models: dict — avoid a misleading zero count in /model.
if not models_list:
url_lower = str(api_url).strip().lower()
if "api.openai.com" in url_lower:
fb = curated.get("openai") or []
if fb:
models_list = list(fb)
# Try to probe /v1/models if URL is set (but don't block on it) # Try to probe /v1/models if URL is set (but don't block on it)
# For now just show what we know from config # For now just show what we know from config
results.append({ results.append({

View file

@ -142,6 +142,18 @@ _PROVIDER_MODELS: dict[str, list[str]] = {
"openai/gpt-5.4-pro", "openai/gpt-5.4-pro",
"openai/gpt-5.4-nano", "openai/gpt-5.4-nano",
], ],
# Native OpenAI Chat Completions (api.openai.com). Used by /model counts and
# provider_model_ids fallback when /v1/models is unavailable.
"openai": [
"gpt-5.4",
"gpt-5.4-mini",
"gpt-5-mini",
"gpt-5.3-codex",
"gpt-5.2-codex",
"gpt-4.1",
"gpt-4o",
"gpt-4o-mini",
],
"openai-codex": _codex_curated_models(), "openai-codex": _codex_curated_models(),
"copilot-acp": [ "copilot-acp": [
"copilot-acp", "copilot-acp",
@ -1748,6 +1760,17 @@ def provider_model_ids(provider: Optional[str], *, force_refresh: bool = False)
live = fetch_ollama_cloud_models(force_refresh=force_refresh) live = fetch_ollama_cloud_models(force_refresh=force_refresh)
if live: if live:
return live return live
if normalized == "openai":
api_key = os.getenv("OPENAI_API_KEY", "").strip()
if api_key:
base_raw = os.getenv("OPENAI_BASE_URL", "").strip().rstrip("/")
base = base_raw or "https://api.openai.com/v1"
try:
live = fetch_api_models(api_key, base)
if live:
return live
except Exception:
pass
if normalized == "custom": if normalized == "custom":
base_url = _get_custom_base_url() base_url = _get_custom_base_url()
if base_url: if base_url:

View file

@ -197,6 +197,58 @@ def test_list_authenticated_providers_dict_models_dedupe_with_default(monkeypatc
assert user_prov["models"].count("model-a") == 1 assert user_prov["models"].count("model-a") == 1
def test_openai_native_curated_catalog_is_non_empty():
"""Regression: built-in openai must have a static catalog for picker totals."""
from hermes_cli.models import _PROVIDER_MODELS
assert _PROVIDER_MODELS.get("openai")
assert len(_PROVIDER_MODELS["openai"]) >= 4
def test_list_authenticated_providers_openai_built_in_nonzero_total(monkeypatch):
"""Built-in openai row must not report total_models=0 when creds exist."""
monkeypatch.setenv("OPENAI_API_KEY", "sk-test")
monkeypatch.setattr(
"agent.models_dev.fetch_models_dev",
lambda: {"openai": {"env": ["OPENAI_API_KEY"]}},
)
monkeypatch.setattr("hermes_cli.providers.HERMES_OVERLAYS", {})
providers = list_authenticated_providers(
current_provider="",
current_base_url="",
user_providers={},
custom_providers=[],
max_models=50,
)
row = next((p for p in providers if p.get("slug") == "openai"), None)
assert row is not None
assert row["total_models"] > 0
def test_list_authenticated_providers_user_openai_official_url_fallback(monkeypatch):
"""User providers: api.openai.com with no models list uses native curated fallback."""
monkeypatch.setattr("agent.models_dev.fetch_models_dev", lambda: {})
monkeypatch.setattr("hermes_cli.providers.HERMES_OVERLAYS", {})
user_providers = {
"openai-direct": {
"name": "OpenAI Direct",
"api": "https://api.openai.com/v1",
}
}
providers = list_authenticated_providers(
current_provider="",
current_base_url="",
user_providers=user_providers,
custom_providers=[],
max_models=50,
)
row = next((p for p in providers if p.get("slug") == "openai-direct"), None)
assert row is not None
assert row["total_models"] > 0
def test_list_authenticated_providers_fallback_to_default_only(monkeypatch): def test_list_authenticated_providers_fallback_to_default_only(monkeypatch):
"""When no models array is provided, should fall back to default_model.""" """When no models array is provided, should fall back to default_model."""
monkeypatch.setattr("agent.models_dev.fetch_models_dev", lambda: {}) monkeypatch.setattr("agent.models_dev.fetch_models_dev", lambda: {})