From 35152cdae94fa1e5af508822692fa260ff32b9d0 Mon Sep 17 00:00:00 2001 From: zhanghuiying <20434979+UNHNQ@users.noreply.github.com> Date: Thu, 16 Jul 2026 11:07:56 +0800 Subject: [PATCH] feat: add SiliconFlow CN and Global providers --- src/llm_core.py | 4 ++++ static/index.html | 2 ++ static/js/providers.js | 6 ++++++ tests/test_admin_device_flow_static.py | 5 +++++ tests/test_provider_detection_builders.py | 8 ++++++++ tests/test_provider_detection_detect.py | 6 ++++++ tests/test_providers_mixtral_logo_js.py | 20 ++++++++++++++++++++ 7 files changed, 51 insertions(+) diff --git a/src/llm_core.py b/src/llm_core.py index af1958f16f..2f0e5ce5c5 100644 --- a/src/llm_core.py +++ b/src/llm_core.py @@ -865,6 +865,8 @@ def _detect_provider(url: str) -> str: return "openrouter" if _host_match(url, "groq.com"): return "groq" + if _host_match(url, "siliconflow.cn") or _host_match(url, "siliconflow.com"): + return "siliconflow" if _host_match(url, "nvidia.com"): return "nvidia" if _host_match(url, "moonshot.ai") or _host_match(url, "moonshot.cn"): @@ -1005,6 +1007,8 @@ def _provider_label(url: str) -> str: if _host_match(url, "opencode.ai/zen/go"): return "OpenCode Go" if _host_match(url, "opencode.ai/zen"): return "OpenCode Zen" if _host_match(url, "groq.com"): return "Groq" + if _host_match(url, "siliconflow.cn"): return "SiliconFlow (CN)" + if _host_match(url, "siliconflow.com"): return "SiliconFlow (Global)" from src.chatgpt_subscription import is_chatgpt_subscription_base if is_chatgpt_subscription_base(url): return "ChatGPT Subscription" from src.copilot import is_copilot_base diff --git a/static/index.html b/static/index.html index cb30e84899..d05b0cc20c 100644 --- a/static/index.html +++ b/static/index.html @@ -2191,6 +2191,8 @@

OpenRouter + + diff --git a/static/js/providers.js b/static/js/providers.js index 54556faebc..5efbf21471 100644 --- a/static/js/providers.js +++ b/static/js/providers.js @@ -52,6 +52,10 @@ const _PROVIDERS = [ [/deepseek/i, ''], + // SiliconFlow — OpenAI-compatible MaaS endpoints + [/siliconflow/i, + ''], + // xAI — Grok (stylized X) [/x-ai|xai|grok/i, ''], @@ -113,6 +117,8 @@ const _ENDPOINT_LABELS = [ [/(^|\.)(generativelanguage|aiplatform)\.googleapis\.com$/i, "Google"], [/(^|\.)bedrock[\w.-]*\.amazonaws\.com$/i, "AWS Bedrock"], [/(^|\.)deepseek\.com$/i, "DeepSeek"], + [/(^|\.)siliconflow\.cn$/i, "SiliconFlow (CN)"], + [/(^|\.)siliconflow\.com$/i, "SiliconFlow (Global)"], [/(^|\.)mistral\.ai$/i, "Mistral"], [/(^|\.)groq\.com$/i, "Groq"], [/(^|\.)together\.(ai|xyz)$/i, "Together"], diff --git a/tests/test_admin_device_flow_static.py b/tests/test_admin_device_flow_static.py index 94f8373405..2cc392df3a 100644 --- a/tests/test_admin_device_flow_static.py +++ b/tests/test_admin_device_flow_static.py @@ -20,6 +20,11 @@ def test_copilot_and_chatgpt_subscription_are_dropdown_device_auth_options(): assert 'id="adm-deviceAuthStatus"' in _INDEX +def test_siliconflow_cn_and_global_are_dropdown_provider_options(): + assert 'value="https://api.siliconflow.cn/v1" data-logo="siliconflow">SiliconFlow (CN)' in _INDEX + assert 'value="https://api.siliconflow.com/v1" data-logo="siliconflow">SiliconFlow (Global)' in _INDEX + + def test_provider_selection_is_inert_and_add_button_starts_device_flow(): change_block = _between(_ADMIN, "provider.addEventListener('change'", "urlInput.addEventListener('input'") add_block = _between(_ADMIN, "el('adm-epAddBtn').addEventListener('click'", "async function _startProviderDeviceAuth") diff --git a/tests/test_provider_detection_builders.py b/tests/test_provider_detection_builders.py index 82ed8bd2ca..8e9211fe46 100644 --- a/tests/test_provider_detection_builders.py +++ b/tests/test_provider_detection_builders.py @@ -57,6 +57,14 @@ def test_lookalike_ollama_models_is_openai(self): assert llm_core._detect_provider("https://notollama.com") == "openai" assert build_models_url("https://notollama.com") == "https://notollama.com/models" + def test_siliconflow_cn_models_preserves_v1_base(self): + assert llm_core._detect_provider("https://api.siliconflow.cn/v1") == "siliconflow" + assert build_models_url("https://api.siliconflow.cn/v1") == "https://api.siliconflow.cn/v1/models" + + def test_siliconflow_global_models_preserves_v1_base(self): + assert llm_core._detect_provider("https://api.siliconflow.com/v1") == "siliconflow" + assert build_models_url("https://api.siliconflow.com/v1") == "https://api.siliconflow.com/v1/models" + class TestBuildersLocalAndDockerEndpoints: """Local and docker endpoints must keep working after the hostname change: diff --git a/tests/test_provider_detection_detect.py b/tests/test_provider_detection_detect.py index 8731a00d5b..c58d5260bb 100644 --- a/tests/test_provider_detection_detect.py +++ b/tests/test_provider_detection_detect.py @@ -24,6 +24,12 @@ def test_groq_openai_compat_path(self): # Groq's base carries an /openai/v1 path; detection must still see the host. assert llm_core._detect_provider("https://api.groq.com/openai/v1") == "groq" + def test_siliconflow_cn(self): + assert llm_core._detect_provider("https://api.siliconflow.cn/v1") == "siliconflow" + + def test_siliconflow_global(self): + assert llm_core._detect_provider("https://api.siliconflow.com/v1") == "siliconflow" + def test_ollama_native_unchanged(self): assert llm_core._detect_provider("https://ollama.com/api") == "ollama" diff --git a/tests/test_providers_mixtral_logo_js.py b/tests/test_providers_mixtral_logo_js.py index 6e60446710..7dc7708869 100644 --- a/tests/test_providers_mixtral_logo_js.py +++ b/tests/test_providers_mixtral_logo_js.py @@ -26,11 +26,31 @@ def _has_logo(model): return json.loads(p.stdout.strip()) +def _provider_label(endpoint_url): + js = ( + f"import {{ providerLabel }} from '{_HELPER.as_posix()}';" + f"console.log(JSON.stringify(providerLabel({json.dumps(endpoint_url)})));" + ) + p = subprocess.run(["node", "--input-type=module"], input=js, capture_output=True, text=True, cwd=str(_REPO), timeout=30) + assert p.returncode == 0, p.stderr + return json.loads(p.stdout.strip()) + + def test_mixtral_ministral_get_a_logo(): assert _has_logo("mixtral-8x7b") is True assert _has_logo("ministral-8b") is True assert _has_logo("mistral-large-latest") is True +def test_siliconflow_gets_a_logo(): + assert _has_logo("siliconflow") is True + assert _has_logo("https://api.siliconflow.cn/v1") is True + + +def test_siliconflow_endpoint_labels(): + assert _provider_label("https://api.siliconflow.cn/v1") == "SiliconFlow (CN)" + assert _provider_label("https://api.siliconflow.com/v1") == "SiliconFlow (Global)" + + def test_unknown_vendor_has_no_logo(): assert _has_logo("totally-unknown-model-xyz") is False