From 1128be8b36040f7dfbc572e1c0b8a7fa695e14bb Mon Sep 17 00:00:00 2001 From: mohamed zwanski Date: Thu, 22 Oct 2026 08:36:49 +0100 Subject: [PATCH] Improve GLM 4.x provider support (#10) - Add GLM 4.6 and GLM 4.5 to the GLM model list - Apply Z.ai's recommended API_TIMEOUT_MS=3000000 by default for GLM (new per-provider default_timeout_ms, overridable via api_timeout_ms) - Make the GLM base URL editable in Settings so mainland China accounts can use https://open.bigmodel.cn/api/anthropic - Fix the GLM hint text, which pointed at open.bigmodel.cn while the default endpoint is api.z.ai - Document GLM setup and add tests for provider env construction Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 6 +++ README.md | 2 + registry.py | 7 +++- test_api_providers.py | 49 ++++++++++++++++++++++++ ui/src/components/SettingsModal.tsx | 6 +-- ui/src/components/views/SettingsView.tsx | 6 +-- 6 files changed, 69 insertions(+), 7 deletions(-) create mode 100644 test_api_providers.py diff --git a/CLAUDE.md b/CLAUDE.md index 6f59910be..579da815d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -120,6 +120,7 @@ python test_security_integration.py # Integration tests (9 tests) python -m pytest test_client.py # Client tests (20 tests) python -m pytest test_dependency_resolver.py # Dependency resolver tests (12 tests) python -m pytest test_rate_limit_utils.py # Rate limit tests (22 tests) +python -m pytest test_api_providers.py # API provider env tests ``` ### React UI @@ -424,6 +425,11 @@ Alternative providers are configured via the **Settings UI** (gear icon > API Pr **Available providers:** Claude (default), GLM (Zhipu AI), Ollama (local models), Kimi (Moonshot), Custom +**GLM notes:** +- Auth via `ANTHROPIC_AUTH_TOKEN`; base URL defaults to `https://api.z.ai/api/anthropic` (editable, e.g. `https://open.bigmodel.cn/api/anthropic` for mainland China) +- Models: `glm-5`, `glm-4.7` (default), `glm-4.6`, `glm-4.5`, `glm-4.5-air` +- `API_TIMEOUT_MS` defaults to 3000000 via the provider's `default_timeout_ms` in `registry.py` + **Ollama notes:** - Requires Ollama v0.14.0+ with Anthropic API compatibility - Install: https://ollama.com → `ollama serve` → `ollama pull qwen3-coder` diff --git a/README.md b/README.md index 0ad71c941..9337b7b78 100644 --- a/README.md +++ b/README.md @@ -338,6 +338,8 @@ Alternative providers are configured via the **Settings UI** (gear icon > API Pr Available providers: **Claude** (default), **GLM** (Zhipu AI), **Ollama** (local models), **Kimi** (Moonshot), **Custom** +**GLM (Zhipu AI / Z.ai):** pick "GLM" in Settings, paste your Z.ai API key (e.g. from the GLM Coding Plan), and choose a model (GLM 5, GLM 4.7, 4.6, 4.5, 4.5 Air, or type any other model ID). The base URL defaults to `https://api.z.ai/api/anthropic`; mainland China accounts should change it to `https://open.bigmodel.cn/api/anthropic`. AutoForge applies Z.ai's recommended `API_TIMEOUT_MS=3000000` automatically so long planning turns don't time out. + For Ollama, install [Ollama v0.14.0+](https://ollama.com), run `ollama serve`, and pull a coding model (e.g., `ollama pull qwen3-coder`). Then select "Ollama" in the Settings UI. ### Using Vertex AI diff --git a/registry.py b/registry.py index 6255d431d..5757e5f68 100644 --- a/registry.py +++ b/registry.py @@ -801,9 +801,14 @@ def get_all_settings() -> dict[str, str]: "models": [ {"id": "glm-5", "name": "GLM 5"}, {"id": "glm-4.7", "name": "GLM 4.7"}, + {"id": "glm-4.6", "name": "GLM 4.6"}, + {"id": "glm-4.5", "name": "GLM 4.5"}, {"id": "glm-4.5-air", "name": "GLM 4.5 Air"}, ], "default_model": "glm-4.7", + # Z.ai recommends a long timeout for its Anthropic-compatible endpoint; + # long planning turns otherwise hit the CLI's default request timeout. + "default_timeout_ms": "3000000", }, "azure": { "name": "Azure Anthropic (Claude)", @@ -907,7 +912,7 @@ def get_effective_sdk_env() -> dict[str, str]: sdk_env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = model # Timeout - timeout = all_settings.get("api_timeout_ms") + timeout = all_settings.get("api_timeout_ms") or provider.get("default_timeout_ms") if timeout: sdk_env["API_TIMEOUT_MS"] = timeout diff --git a/test_api_providers.py b/test_api_providers.py new file mode 100644 index 000000000..849b082f4 --- /dev/null +++ b/test_api_providers.py @@ -0,0 +1,49 @@ +"""Tests for alternative API provider env construction (registry.get_effective_sdk_env).""" + +import pytest + +import registry + + +@pytest.fixture +def settings(monkeypatch): + store: dict[str, str] = {} + monkeypatch.setattr(registry, "get_all_settings", lambda: dict(store)) + return store + + +def test_glm_defaults(settings): + settings.update({"api_provider": "glm", "api_auth_token": "zai-key"}) + env = registry.get_effective_sdk_env() + + assert env["ANTHROPIC_BASE_URL"] == "https://api.z.ai/api/anthropic" + assert env["ANTHROPIC_AUTH_TOKEN"] == "zai-key" + assert env["ANTHROPIC_API_KEY"] == "" + assert env["API_TIMEOUT_MS"] == "3000000" + for tier in ("OPUS", "SONNET", "HAIKU"): + assert env[f"ANTHROPIC_DEFAULT_{tier}_MODEL"] == "glm-4.7" + + +def test_glm_overrides(settings): + settings.update({ + "api_provider": "glm", + "api_auth_token": "key", + "api_base_url": "https://open.bigmodel.cn/api/anthropic", + "api_model": "glm-4.6", + "api_timeout_ms": "600000", + }) + env = registry.get_effective_sdk_env() + + assert env["ANTHROPIC_BASE_URL"] == "https://open.bigmodel.cn/api/anthropic" + assert env["ANTHROPIC_DEFAULT_SONNET_MODEL"] == "glm-4.6" + assert env["API_TIMEOUT_MS"] == "600000" + + +def test_glm_lists_4x_models(): + ids = {m["id"] for m in registry.API_PROVIDERS["glm"]["models"]} + assert {"glm-4.7", "glm-4.6", "glm-4.5", "glm-4.5-air"} <= ids + + +def test_providers_without_default_timeout_leave_it_unset(settings): + settings.update({"api_provider": "kimi", "api_auth_token": "k"}) + assert "API_TIMEOUT_MS" not in registry.get_effective_sdk_env() diff --git a/ui/src/components/SettingsModal.tsx b/ui/src/components/SettingsModal.tsx index 5605f4e63..bff35cd88 100644 --- a/ui/src/components/SettingsModal.tsx +++ b/ui/src/components/SettingsModal.tsx @@ -23,7 +23,7 @@ interface SettingsModalProps { const PROVIDER_INFO_TEXT: Record = { claude: 'Default provider. Uses Claude CLI credentials. API key auth is recommended.', kimi: 'Get an API key at kimi.com', - glm: 'Get an API key at open.bigmodel.cn', + glm: 'Get an API key at z.ai (GLM Coding Plan). Mainland China accounts: change the Base URL to https://open.bigmodel.cn/api/anthropic', ollama: 'Run models locally. Install from ollama.com', custom: 'Connect to any OpenAI-compatible API endpoint.', } @@ -116,7 +116,7 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) { const currentProviderInfo: ProviderInfo | undefined = providers.find(p => p.id === currentProvider) const isAlternativeProvider = currentProvider !== 'claude' const showAuthField = isAlternativeProvider && currentProviderInfo?.requires_auth - const showBaseUrlField = currentProvider === 'custom' || currentProvider === 'azure' + const showBaseUrlField = currentProvider === 'custom' || currentProvider === 'azure' || currentProvider === 'glm' const showCustomModelInput = currentProvider === 'custom' || currentProvider === 'ollama' return ( @@ -332,7 +332,7 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) { type="text" value={customBaseUrlInput} onChange={(e) => setCustomBaseUrlInput(e.target.value)} - placeholder={currentProvider === 'azure' ? 'https://your-resource.services.ai.azure.com/anthropic' : 'https://api.example.com/v1'} + placeholder={currentProvider === 'azure' ? 'https://your-resource.services.ai.azure.com/anthropic' : currentProvider === 'glm' ? 'https://api.z.ai/api/anthropic' : 'https://api.example.com/v1'} className="flex-1 py-1.5 px-3 text-sm border rounded-md bg-background" />