From 980ecf943cbe289fb9f93af4eb3321ac762d5542 Mon Sep 17 00:00:00 2001 From: Chaitanya Laxman Date: Mon, 7 Sep 2026 11:17:40 +0400 Subject: [PATCH 1/3] fix(ui): render Z.AI API Key and API Base on the credential form The Add Credential provider list comes from the UI enum, so Z.AI (Zhipu AI) already appears. GET /public/providers/fields reads provider_create_fields.json, which had 117 rows and none with litellm_provider=zai, so the form rendered no fields. Add Model uses that same catalog and never listed Z.AI. Add the missing catalog row (required api_key, optional api_base) and a regression on TestZAIDashboardRegistration. Revert-check: the new test fails with assert 0 == 1 when the row is removed. --- .../provider_create_fields.json | 28 +++++++++++++++++++ .../llms/zai/test_zai_provider.py | 26 +++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/litellm/proxy/public_endpoints/provider_create_fields.json b/litellm/proxy/public_endpoints/provider_create_fields.json index 66f8c2ea36f..d6edcd734b4 100644 --- a/litellm/proxy/public_endpoints/provider_create_fields.json +++ b/litellm/proxy/public_endpoints/provider_create_fields.json @@ -3414,6 +3414,34 @@ ], "default_model_placeholder": "gpt-3.5-turbo" }, + { + "provider": "ZAI", + "provider_display_name": "Z.AI (Zhipu AI)", + "litellm_provider": "zai", + "credential_fields": [ + { + "key": "api_base", + "label": "API Base", + "placeholder": "https://api.z.ai/api/paas/v4", + "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": "zai/glm-4.5" + }, { "provider": "CURSOR", "provider_display_name": "Cursor", diff --git a/tests/test_litellm/llms/zai/test_zai_provider.py b/tests/test_litellm/llms/zai/test_zai_provider.py index 38ddac8d510..0c8413c46b1 100644 --- a/tests/test_litellm/llms/zai/test_zai_provider.py +++ b/tests/test_litellm/llms/zai/test_zai_provider.py @@ -172,3 +172,29 @@ def test_zai_sync_completion(respx_mock, zai_response, monkeypatch): assert response.choices[0].message.content == "Hello! How can I help you today?" assert response.usage.total_tokens == 25 + + +class TestZAIDashboardRegistration: + @staticmethod + def _provider_create_fields(): + from pathlib import Path + + path = Path(litellm.__file__).parent / "proxy" / "public_endpoints" / "provider_create_fields.json" + with open(path) as f: + return json.load(f) + + def test_zai_is_selectable_in_the_add_model_form(self): + entries = [e for e in self._provider_create_fields() if e["litellm_provider"] == "zai"] + assert len(entries) == 1, "zai must appear exactly once in provider_create_fields.json" + + entry = entries[0] + assert entry["provider"] == "ZAI" + assert entry["provider_display_name"] == "Z.AI (Zhipu AI)" + assert entry["default_model_placeholder"].startswith("zai/") + + fields = {f["key"]: f for f in entry["credential_fields"]} + assert fields["api_key"]["required"] is True + assert fields["api_key"]["field_type"] == "password" + assert fields["api_base"]["required"] is False + assert fields["api_base"]["field_type"] == "text" + assert fields["api_base"]["placeholder"] == "https://api.z.ai/api/paas/v4" From ab0e3f7695c85cc3bbffebd967d93641a9ad596d Mon Sep 17 00:00:00 2001 From: Chaitanya Laxman Date: Mon, 7 Sep 2026 20:46:29 +0400 Subject: [PATCH 2/3] test(ui): hit GET /public/providers/fields for the Z.AI catalog row Greptile flagged the JSON parse test as structure-only. The new test calls the same public endpoint the Add Credential form uses. --- .../llms/zai/test_zai_provider.py | 27 ---------------- .../public_endpoints/test_public_endpoints.py | 31 +++++++++++++++++++ 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/tests/test_litellm/llms/zai/test_zai_provider.py b/tests/test_litellm/llms/zai/test_zai_provider.py index 0c8413c46b1..5139c0cc1d4 100644 --- a/tests/test_litellm/llms/zai/test_zai_provider.py +++ b/tests/test_litellm/llms/zai/test_zai_provider.py @@ -2,7 +2,6 @@ Tests for Z.AI (Zhipu AI) provider - GLM models """ -import json import math import pytest @@ -172,29 +171,3 @@ def test_zai_sync_completion(respx_mock, zai_response, monkeypatch): assert response.choices[0].message.content == "Hello! How can I help you today?" assert response.usage.total_tokens == 25 - - -class TestZAIDashboardRegistration: - @staticmethod - def _provider_create_fields(): - from pathlib import Path - - path = Path(litellm.__file__).parent / "proxy" / "public_endpoints" / "provider_create_fields.json" - with open(path) as f: - return json.load(f) - - def test_zai_is_selectable_in_the_add_model_form(self): - entries = [e for e in self._provider_create_fields() if e["litellm_provider"] == "zai"] - assert len(entries) == 1, "zai must appear exactly once in provider_create_fields.json" - - entry = entries[0] - assert entry["provider"] == "ZAI" - assert entry["provider_display_name"] == "Z.AI (Zhipu AI)" - assert entry["default_model_placeholder"].startswith("zai/") - - fields = {f["key"]: f for f in entry["credential_fields"]} - assert fields["api_key"]["required"] is True - assert fields["api_key"]["field_type"] == "password" - assert fields["api_base"]["required"] is False - assert fields["api_base"]["field_type"] == "text" - assert fields["api_base"]["placeholder"] == "https://api.z.ai/api/paas/v4" diff --git a/tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py b/tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py index 4a19ad3541c..0fb2380d445 100644 --- a/tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py +++ b/tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py @@ -327,6 +327,37 @@ def test_cognition_provider_fields(): assert fields_by_key["api_base"]["required"] is False +def test_zai_provider_fields(): + """Z.AI must be selectable in the Add Credential flow (#39310). + + The form is driven by GET /public/providers/fields. Without a ZAI row the + UI shows only Credential Name and Provider, so the key cannot be saved. + """ + 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() + + zai = next((p for p in providers if p["provider"] == "ZAI"), None) + assert zai is not None, "Z.AI provider entry not found" + + assert zai["provider_display_name"] == "Z.AI (Zhipu AI)" + assert zai["litellm_provider"] == "zai" + assert zai["default_model_placeholder"].startswith("zai/") + + fields_by_key = {f["key"]: f for f in zai["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"]["required"] is False + assert fields_by_key["api_base"]["field_type"] == "text" + assert fields_by_key["api_base"]["placeholder"] == "https://api.z.ai/api/paas/v4" + + def test_google_ai_studio_provider_fields_expose_api_base(): """The Google AI Studio (gemini) credential form must let admins set a custom api_base so they can point at a Gemini-compatible gateway (e.g. a self-hosted From 5154602d35f45b69a6f26ab23e9820470df68b2a Mon Sep 17 00:00:00 2001 From: Chaitanya Laxman Date: Mon, 7 Sep 2026 21:03:10 +0400 Subject: [PATCH 3/3] test(ui): restore unused json import in zai provider tests --- tests/test_litellm/llms/zai/test_zai_provider.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_litellm/llms/zai/test_zai_provider.py b/tests/test_litellm/llms/zai/test_zai_provider.py index 5139c0cc1d4..38ddac8d510 100644 --- a/tests/test_litellm/llms/zai/test_zai_provider.py +++ b/tests/test_litellm/llms/zai/test_zai_provider.py @@ -2,6 +2,7 @@ Tests for Z.AI (Zhipu AI) provider - GLM models """ +import json import math import pytest