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
6 changes: 6 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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`
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)",
Expand Down Expand Up @@ -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

Expand Down
49 changes: 49 additions & 0 deletions test_api_providers.py
Original file line number Diff line number Diff line change
@@ -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()
6 changes: 3 additions & 3 deletions ui/src/components/SettingsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ interface SettingsModalProps {
const PROVIDER_INFO_TEXT: Record<string, string> = {
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.',
}
Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -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"
/>
<Button
Expand Down
6 changes: 3 additions & 3 deletions ui/src/components/views/SettingsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
const PROVIDER_INFO_TEXT: Record<string, string> = {
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.',
}
Expand Down Expand Up @@ -113,7 +113,7 @@ export function SettingsView() {
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 (
Expand Down Expand Up @@ -341,7 +341,7 @@ export function SettingsView() {
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"
/>
<Button
Expand Down