From 8d2c31eb6a014505331aa877103c8f9885fbd208 Mon Sep 17 00:00:00 2001 From: chengyongru Date: Wed, 1 Jul 2026 10:44:56 +0800 Subject: [PATCH] refactor(webui): derive provider model catalog kind --- nanobot/providers/registry.py | 2 + nanobot/webui/settings_api.py | 65 +++++--------------------------- tests/webui/test_settings_api.py | 8 ++++ 3 files changed, 20 insertions(+), 55 deletions(-) diff --git a/nanobot/providers/registry.py b/nanobot/providers/registry.py index 2e34d4f4..f932f098 100644 --- a/nanobot/providers/registry.py +++ b/nanobot/providers/registry.py @@ -32,6 +32,7 @@ class ProviderSpec: keywords: tuple[str, ...] # model-name keywords for matching (lowercase) env_key: str # env var for API key, e.g. "DASHSCOPE_API_KEY" display_name: str = "" # shown in `nanobot status` + model_catalog: str = "auto" # WebUI model-list source # which provider implementation to use # "openai_compat" | "anthropic" | "azure_openai" | "openai_codex" | "github_copilot" | "bedrock" @@ -221,6 +222,7 @@ PROVIDERS: tuple[ProviderSpec, ...] = ( keywords=("skywork", "skyclaw", "apifree"), env_key="SKYWORK_API_KEY", display_name="Skywork", + model_catalog="official", backend="openai_compat", env_extras=(("APIFREE_API_KEY", "{api_key}"),), is_gateway=True, diff --git a/nanobot/webui/settings_api.py b/nanobot/webui/settings_api.py index 5b1f9bb4..5d4d98b5 100644 --- a/nanobot/webui/settings_api.py +++ b/nanobot/webui/settings_api.py @@ -99,47 +99,6 @@ _CONTEXT_WINDOW_TOKEN_OPTIONS = {65_536, 200_000, 262_144} _MODEL_CONFIGURATION_SLUG_RE = re.compile(r"[^a-z0-9_-]+") _ENV_REF_RE = re.compile(r"\$\{([A-Za-z_][A-Za-z0-9_]*)\}") -_MODEL_LIST_UNSUPPORTED_BACKENDS = { - "anthropic", - "azure_openai", - "bedrock", - "github_copilot", - "openai_codex", -} - -_MODEL_LIST_CATALOG_PROVIDERS = { - "aihubmix", - "byteplus", - "byteplus_coding_plan", - "huggingface", - "novita", - "openrouter", - "siliconflow", - "volcengine", - "volcengine_coding_plan", -} - -_MODEL_LIST_OFFICIAL_PROVIDERS = { - "ant_ling", - "dashscope", - "deepseek", - "gemini", - "groq", - "longcat", - "minimax", - "minimax_anthropic", - "mistral", - "moonshot", - "nvidia", - "openai", - "qianfan", - "skywork", - "stepfun", - "xiaomi_mimo", - "zhipu", -} - - class WebUISettingsError(ValueError): """User-facing settings validation failure.""" @@ -394,10 +353,13 @@ def _provider_settings_row( def _model_catalog_kind(spec: Any) -> str: - if spec.name in _MODEL_LIST_CATALOG_PROVIDERS: - return "catalog" - if spec.name in _MODEL_LIST_OFFICIAL_PROVIDERS: - return "official" + catalog = getattr(spec, "model_catalog", "auto") + if catalog != "auto": + return catalog + if spec.is_transcription_only or spec.is_oauth: + return "unsupported" + if spec.backend != "openai_compat" and spec.name != "minimax_anthropic": + return "unsupported" if spec.is_local: return "local" if spec.is_direct: @@ -490,27 +452,20 @@ def provider_models_payload(query: QueryParams) -> dict[str, Any]: raise WebUISettingsError("unknown provider") spec, provider_key, provider_config = resolved_provider + catalog_kind = _model_catalog_kind(spec) base_payload: dict[str, Any] = { "provider": provider_key, "label": spec.label, - "catalog_kind": _model_catalog_kind(spec), + "catalog_kind": catalog_kind, "models": [], "model_count": 0, "message": None, "fetched_at": time.time(), } - if ( - spec.is_transcription_only - or ( - spec.backend in _MODEL_LIST_UNSUPPORTED_BACKENDS - and spec.name != "minimax_anthropic" - ) - or spec.is_oauth - ): + if catalog_kind == "unsupported": return { **base_payload, "status": "unsupported", - "catalog_kind": "unsupported", "message": "Model list is not available for this provider. Type a model ID manually.", } diff --git a/tests/webui/test_settings_api.py b/tests/webui/test_settings_api.py index f772ce5a..b6fd99a8 100644 --- a/tests/webui/test_settings_api.py +++ b/tests/webui/test_settings_api.py @@ -11,6 +11,7 @@ from nanobot.config.schema import Config, ModelPresetConfig from nanobot.providers.registry import find_by_name from nanobot.webui.settings_api import ( WebUISettingsError, + _model_catalog_kind, _oauth_provider_status, create_model_configuration, login_oauth_provider, @@ -998,9 +999,16 @@ def test_provider_models_payload_requires_gateway_key( payload = provider_models_payload({"provider": ["openrouter"]}) assert payload["status"] == "not_configured" + assert payload["catalog_kind"] == "catalog" assert payload["models"] == [] +def test_model_catalog_kind_uses_provider_spec_metadata() -> None: + assert _model_catalog_kind(find_by_name("skywork")) == "official" + assert _model_catalog_kind(find_by_name("anthropic")) == "unsupported" + assert _model_catalog_kind(find_by_name("openrouter")) == "catalog" + + def test_create_model_configuration_accepts_configured_oauth_provider( tmp_path, monkeypatch: pytest.MonkeyPatch,