From 19da81579b3fa40bea39fb41ca3379d9da32d83e Mon Sep 17 00:00:00 2001 From: Jim Aldon D'Souza Date: Fri, 2 Oct 2026 10:15:41 -0700 Subject: [PATCH] fix(ui): register tencent in the Add Model provider dropdown (#40924) * fix(ui): register tencent in the Add Model provider dropdown The Add Model provider dropdown is driven by the proxy's /public/providers/fields endpoint, which serves provider_create_fields.json. Tencent was frozen in the test's ADD_MODEL_UNLISTED_PROVIDERS set, so it never appeared in the dropdown. Add a Tencent entry (optional api_base + required api_key, matching TENCENT_API_BASE/TENCENT_API_KEY) and unfreeze it in the backend test. Register Tencent in the UI Providers enum, provider_map, and placeholder map so the dropdown resolves the display name and model placeholder. * fix(tencent): drop test docstring to satisfy comment policy * fix(ui): bundle the Tencent Cloud logo for the provider dropdown --- .../provider_create_fields.json | 28 +++++++++++++++++++ .../public_endpoints/test_public_endpoints.py | 26 ++++++++++++++++- .../public/assets/logos/tencent.svg | 6 ++++ .../components/provider_info_helpers.test.tsx | 9 ++++++ .../src/components/provider_info_helpers.tsx | 5 ++++ 5 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 ui/litellm-dashboard/public/assets/logos/tencent.svg diff --git a/litellm/proxy/public_endpoints/provider_create_fields.json b/litellm/proxy/public_endpoints/provider_create_fields.json index 11d2ff61b95..6e96d6ad0ec 100644 --- a/litellm/proxy/public_endpoints/provider_create_fields.json +++ b/litellm/proxy/public_endpoints/provider_create_fields.json @@ -3161,6 +3161,34 @@ ], "default_model_placeholder": "soniox/stt-async-v5" }, + { + "provider": "Tencent", + "provider_display_name": "Tencent", + "litellm_provider": "tencent", + "credential_fields": [ + { + "key": "api_base", + "label": "API Base", + "placeholder": "https://tokenhub-intl.tencentcloudmaas.com/v1", + "tooltip": null, + "required": false, + "field_type": "text", + "options": null, + "default_value": null + }, + { + "key": "api_key", + "label": "API Key", + "placeholder": null, + "tooltip": null, + "required": true, + "field_type": "password", + "options": null, + "default_value": null + } + ], + "default_model_placeholder": "tencent/deepseek-v4-pro" + }, { "provider": "TEXT_COMPLETION_CODESTRAL", "provider_display_name": "Text-Completion-Codestral", diff --git a/tests/unit/proxy/public_endpoints/test_public_endpoints.py b/tests/unit/proxy/public_endpoints/test_public_endpoints.py index 18839a65d62..be309a67d58 100644 --- a/tests/unit/proxy/public_endpoints/test_public_endpoints.py +++ b/tests/unit/proxy/public_endpoints/test_public_endpoints.py @@ -377,6 +377,31 @@ def test_chatgpt_provider_fields(): assert chatgpt["credential_fields"] == [] +def test_tencent_provider_fields(): + app_instance = FastAPI() + app_instance.include_router(router) + test_client = TestClient(app_instance) + + response = test_client.get("/public/providers/fields") + assert response.status_code == 200 + providers = response.json() + + tencent = next((p for p in providers if p["provider"] == "Tencent"), None) + assert tencent is not None, "Tencent provider entry not found" + + assert tencent["provider_display_name"] == "Tencent" + assert tencent["litellm_provider"] == LlmProviders.TENCENT.value + assert tencent["default_model_placeholder"].startswith("tencent/") + + fields_by_key = {f["key"]: f for f in tencent["credential_fields"]} + + assert fields_by_key["api_key"]["required"] is True + assert fields_by_key["api_key"]["field_type"] == "password" + + assert fields_by_key["api_base"]["field_type"] == "text" + assert fields_by_key["api_base"]["required"] is False + + ADD_MODEL_UNLISTED_PROVIDERS: Final = frozenset( { "a2a", @@ -412,7 +437,6 @@ ADD_MODEL_UNLISTED_PROVIDERS: Final = frozenset( "scaleway", "stability", "synthetic", - "tencent", "tensormesh", "text-completion-inception", "transcribe", diff --git a/ui/litellm-dashboard/public/assets/logos/tencent.svg b/ui/litellm-dashboard/public/assets/logos/tencent.svg new file mode 100644 index 00000000000..ee43c71f4d5 --- /dev/null +++ b/ui/litellm-dashboard/public/assets/logos/tencent.svg @@ -0,0 +1,6 @@ + + Tencent Cloud + + + + diff --git a/ui/litellm-dashboard/src/components/provider_info_helpers.test.tsx b/ui/litellm-dashboard/src/components/provider_info_helpers.test.tsx index 29ce2d4865a..7cfdaf3275d 100644 --- a/ui/litellm-dashboard/src/components/provider_info_helpers.test.tsx +++ b/ui/litellm-dashboard/src/components/provider_info_helpers.test.tsx @@ -209,6 +209,11 @@ describe("provider_info_helpers", () => { const { logo } = getProviderLogoAndName("openai"); expect(logo).toContain("openai_small"); }); + + it("should resolve the Tencent provider to its bundled logo", () => { + const { logo } = getProviderLogoAndName("tencent"); + expect(logo).toContain("tencent"); + }); }); describe("getPlaceholder", () => { @@ -309,6 +314,10 @@ describe("provider_info_helpers", () => { expect(getPlaceholder("CHATGPT")).toBe("chatgpt/gpt-5.4"); }); + it("should return a tencent/ placeholder for the Tencent provider", () => { + expect(getPlaceholder(Providers.Tencent)).toBe("tencent/deepseek-v4-pro"); + }); + it("should return default gpt-3.5-turbo placeholder for unknown provider", () => { expect(getPlaceholder("UnknownProvider" as any)).toBe("gpt-3.5-turbo"); }); diff --git a/ui/litellm-dashboard/src/components/provider_info_helpers.tsx b/ui/litellm-dashboard/src/components/provider_info_helpers.tsx index b0e33338bab..5ea693bea10 100644 --- a/ui/litellm-dashboard/src/components/provider_info_helpers.tsx +++ b/ui/litellm-dashboard/src/components/provider_info_helpers.tsx @@ -55,6 +55,7 @@ import sapLogo from "../../public/assets/logos/sap.png"; import scxAiLogo from "../../public/assets/logos/scx_ai.svg"; import snowflakeLogo from "../../public/assets/logos/snowflake.svg"; import sonioxLogo from "../../public/assets/logos/soniox.svg"; +import tencentLogo from "../../public/assets/logos/tencent.svg"; import togetheraiLogo from "../../public/assets/logos/togetherai.svg"; import topazLogo from "../../public/assets/logos/topaz.svg"; import v0Logo from "../../public/assets/logos/v0.svg"; @@ -167,6 +168,7 @@ export enum Providers { Snowflake = "Snowflake", Soniox = "Soniox", TEXT_COMPLETION_CODESTRAL = "Text-Completion-Codestral", + Tencent = "Tencent", TogetherAI = "TogetherAI", TOPAZ = "Topaz", Triton = "Triton", @@ -286,6 +288,7 @@ export const provider_map: Record = { Snowflake: "snowflake", Soniox: "soniox", TEXT_COMPLETION_CODESTRAL: "text-completion-codestral", + Tencent: "tencent", TogetherAI: "together_ai", TOPAZ: "topaz", Triton: "triton", @@ -384,6 +387,7 @@ export const providerLogoMap: Partial> = { [Providers.SCX_AI]: scxAiLogo.src, [Providers.Snowflake]: snowflakeLogo.src, [Providers.Soniox]: sonioxLogo.src, + [Providers.Tencent]: tencentLogo.src, [Providers.TEXT_COMPLETION_CODESTRAL]: mistralLogo.src, [Providers.TogetherAI]: togetheraiLogo.src, [Providers.TOPAZ]: topazLogo.src, @@ -453,6 +457,7 @@ const providerPlaceholderMap: Partial> = { [Providers.Sail]: "sail/openai/gpt-oss-120b", [Providers.SCX_AI]: "scx-ai/GLM-5.2", [Providers.Snowflake]: "snowflake/mistral-7b", + [Providers.Tencent]: "tencent/deepseek-v4-pro", [Providers.Vertex_AI]: "gemini-pro", [Providers.VolcEngine]: "volcengine/", [Providers.Voyage]: "voyage/",