Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/llm_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"):
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2191,6 +2191,8 @@ <h2 style="display:flex;align-items:center;gap:8px;"><svg width="14" height="14"
<option value="https://openrouter.ai/api/v1" data-logo="openrouter">OpenRouter</option>
<option value="https://ollama.com/api" data-logo="ollama">Ollama Cloud</option>
<option value="https://api.groq.com/openai/v1" data-logo="groq">Groq</option>
<option value="https://api.siliconflow.cn/v1" data-logo="siliconflow">SiliconFlow (CN)</option>
<option value="https://api.siliconflow.com/v1" data-logo="siliconflow">SiliconFlow (Global)</option>
<option value="https://api.mistral.ai/v1" data-logo="mistral">Mistral</option>
<option value="https://api.together.xyz/v1" data-logo="together">Together AI</option>
<option value="https://api.fireworks.ai/inference/v1" data-logo="fireworks">Fireworks AI</option>
Expand Down
6 changes: 6 additions & 0 deletions static/js/providers.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ const _PROVIDERS = [
[/deepseek/i,
'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.748 4.482c-.254-.124-.364.113-.512.234-.051.039-.094.09-.137.136-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.156-.708-.311-.955-.65-.172-.241-.219-.51-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.093.172.187.129.323-.082.28-.18.552-.266.833-.055.179-.137.217-.329.14a5.526 5.526 0 01-1.736-1.18c-.857-.828-1.631-1.742-2.597-2.458a11.365 11.365 0 00-.689-.471c-.985-.957.13-1.743.388-1.836.27-.098.093-.432-.779-.428-.872.004-1.67.295-2.687.684a3.055 3.055 0 01-.465.137 9.597 9.597 0 00-2.883-.102c-1.885.21-3.39 1.102-4.497 2.623C.082 8.606-.231 10.684.152 12.85c.403 2.284 1.569 4.175 3.36 5.653 1.858 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.133-.284 4.994-1.86.47.234.962.327 1.78.397.63.059 1.236-.03 1.705-.128.735-.156.684-.837.419-.961-2.155-1.004-1.682-.595-2.113-.926 1.096-1.296 2.746-2.642 3.392-7.003.05-.347.007-.565 0-.845-.004-.17.035-.237.23-.256a4.173 4.173 0 001.545-.475c1.396-.763 1.96-2.015 2.093-3.517.02-.23-.004-.467-.247-.588zM11.581 18c-2.089-1.642-3.102-2.183-3.52-2.16-.392.024-.321.471-.235.763.09.288.207.486.371.739.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.167-1.361-.802-2.5-1.86-3.301-3.307-.774-1.393-1.224-2.887-1.298-4.482-.02-.386.093-.522.477-.592a4.696 4.696 0 011.529-.039c2.132.312 3.946 1.265 5.468 2.774.868.86 1.525 1.887 2.202 2.891.72 1.066 1.494 2.082 2.48 2.914.348.292.625.514.891.677-.802.09-2.14.11-3.054-.614zm1-6.44a.306.306 0 01.415-.287.302.302 0 01.2.288.306.306 0 01-.31.307.303.303 0 01-.304-.308zm3.11 1.596c-.2.081-.399.151-.59.16a1.245 1.245 0 01-.798-.254c-.274-.23-.47-.358-.552-.758a1.73 1.73 0 01.016-.588c.07-.327-.008-.537-.239-.727-.187-.156-.426-.199-.688-.199a.559.559 0 01-.254-.078c-.11-.054-.2-.19-.114-.358.028-.054.16-.186.192-.21.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.391.451.462.576.685.914.176.265.336.537.445.848.067.195-.019.354-.25.452z"/></svg>'],

// SiliconFlow — OpenAI-compatible MaaS endpoints
[/siliconflow/i,
'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M4 5.5A2.5 2.5 0 0 1 6.5 3H18v3H7v4h9.5a2.5 2.5 0 0 1 0 5H8v6H4V5.5Zm4 7.5h8.25a.75.75 0 0 0 0-1.5H8V13Zm10-10h2v18h-4v-3h2V3Z"/></svg>'],

// xAI — Grok (stylized X)
[/x-ai|xai|grok/i,
'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z"/></svg>'],
Expand Down Expand Up @@ -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"],
Expand Down
5 changes: 5 additions & 0 deletions tests/test_admin_device_flow_static.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
8 changes: 8 additions & 0 deletions tests/test_provider_detection_builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
6 changes: 6 additions & 0 deletions tests/test_provider_detection_detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
20 changes: 20 additions & 0 deletions tests/test_providers_mixtral_logo_js.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading