feat: add GLM/alternative API support via environment variables

Add support for using alternative API endpoints (like Zhipu AI's GLM models)
without affecting the user's global Claude Code settings. Configuration is
done via AutoCoder's .env file.

Changes:
- Add API_ENV_VARS constant and pass through ClaudeAgentOptions.env parameter
  in client.py and all server service files (spec, expand, assistant sessions)
- Add glm_mode to settings API response to indicate when GLM is configured
- Add purple "GLM" badge in UI header when GLM mode is active
- Update setup status to accept GLM credentials as valid authentication
- Update .env.example with GLM configuration documentation
- Update README.md with AutoCoder-scoped GLM setup instructions

Supported environment variables:
- ANTHROPIC_BASE_URL: Custom API endpoint (e.g., https://api.z.ai/api/anthropic)
- ANTHROPIC_AUTH_TOKEN: API authentication token
- API_TIMEOUT_MS: Request timeout in milliseconds
- ANTHROPIC_DEFAULT_SONNET_MODEL: Model override for Sonnet
- ANTHROPIC_DEFAULT_OPUS_MODEL: Model override for Opus
- ANTHROPIC_DEFAULT_HAIKU_MODEL: Model override for Haiku

This approach routes API requests through the alternative endpoint while
keeping all Claude Code features (MCP servers, hooks, permissions) intact.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Auto
2026-01-12 12:25:13 +02:00
parent a7f8c3aa8d
commit f31ea403ea
12 changed files with 123 additions and 11 deletions

View File

@@ -6,6 +6,7 @@ Main entry point for the Autonomous Coding UI server.
Provides REST API, WebSocket, and static file serving.
"""
import os
import shutil
from contextlib import asynccontextmanager
from pathlib import Path
@@ -148,7 +149,11 @@ async def setup_status():
# Note: CLI no longer stores credentials in ~/.claude/.credentials.json
# The existence of ~/.claude indicates the CLI has been configured
claude_dir = Path.home() / ".claude"
credentials = claude_dir.exists() and claude_dir.is_dir()
has_claude_config = claude_dir.exists() and claude_dir.is_dir()
# If GLM mode is configured via .env, we have alternative credentials
glm_configured = bool(os.getenv("ANTHROPIC_BASE_URL") and os.getenv("ANTHROPIC_AUTH_TOKEN"))
credentials = has_claude_config or glm_configured
# Check for Node.js and npm
node = shutil.which("node") is not None

View File

@@ -6,6 +6,7 @@ API endpoints for global settings management.
Settings are stored in the registry database and shared across all projects.
"""
import os
import sys
from pathlib import Path
@@ -33,6 +34,11 @@ def _parse_yolo_mode(value: str | None) -> bool:
return (value or "false").lower() == "true"
def _is_glm_mode() -> bool:
"""Check if GLM API is configured via environment variables."""
return bool(os.getenv("ANTHROPIC_BASE_URL"))
@router.get("/models", response_model=ModelsResponse)
async def get_available_models():
"""Get list of available models.
@@ -54,6 +60,7 @@ async def get_settings():
return SettingsResponse(
yolo_mode=_parse_yolo_mode(all_settings.get("yolo_mode")),
model=all_settings.get("model", DEFAULT_MODEL),
glm_mode=_is_glm_mode(),
)
@@ -71,4 +78,5 @@ async def update_settings(update: SettingsUpdate):
return SettingsResponse(
yolo_mode=_parse_yolo_mode(all_settings.get("yolo_mode")),
model=all_settings.get("model", DEFAULT_MODEL),
glm_mode=_is_glm_mode(),
)

View File

@@ -289,6 +289,7 @@ class SettingsResponse(BaseModel):
"""Response schema for global settings."""
yolo_mode: bool = False
model: str = DEFAULT_MODEL
glm_mode: bool = False # True if GLM API is configured via .env
class ModelsResponse(BaseModel):

View File

@@ -33,6 +33,16 @@ logger = logging.getLogger(__name__)
# Root directory of the project
ROOT_DIR = Path(__file__).parent.parent.parent
# Environment variables to pass through to Claude CLI for API configuration
API_ENV_VARS = [
"ANTHROPIC_BASE_URL",
"ANTHROPIC_AUTH_TOKEN",
"API_TIMEOUT_MS",
"ANTHROPIC_DEFAULT_SONNET_MODEL",
"ANTHROPIC_DEFAULT_OPUS_MODEL",
"ANTHROPIC_DEFAULT_HAIKU_MODEL",
]
# Read-only feature MCP tools
READONLY_FEATURE_MCP_TOOLS = [
"mcp__features__feature_get_stats",
@@ -234,6 +244,9 @@ class AssistantChatSession:
# Use system Claude CLI
system_cli = shutil.which("claude")
# Build environment overrides for API configuration
sdk_env = {var: os.getenv(var) for var in API_ENV_VARS if os.getenv(var)}
try:
self.client = ClaudeSDKClient(
options=ClaudeAgentOptions(
@@ -246,6 +259,7 @@ class AssistantChatSession:
max_turns=100,
cwd=str(self.project_dir.resolve()),
settings=str(settings_file.resolve()),
env=sdk_env,
)
)
await self.client.__aenter__()

View File

@@ -9,6 +9,7 @@ Uses the expand-project.md skill to help users add features to existing projects
import asyncio
import json
import logging
import os
import re
import shutil
import threading
@@ -27,6 +28,16 @@ load_dotenv()
logger = logging.getLogger(__name__)
# Environment variables to pass through to Claude CLI for API configuration
API_ENV_VARS = [
"ANTHROPIC_BASE_URL",
"ANTHROPIC_AUTH_TOKEN",
"API_TIMEOUT_MS",
"ANTHROPIC_DEFAULT_SONNET_MODEL",
"ANTHROPIC_DEFAULT_OPUS_MODEL",
"ANTHROPIC_DEFAULT_HAIKU_MODEL",
]
async def _make_multimodal_message(content_blocks: list[dict]) -> AsyncGenerator[dict, None]:
"""
@@ -153,6 +164,9 @@ class ExpandChatSession:
project_path = str(self.project_dir.resolve())
system_prompt = skill_content.replace("$ARGUMENTS", project_path)
# Build environment overrides for API configuration
sdk_env = {var: os.getenv(var) for var in API_ENV_VARS if os.getenv(var)}
# Create Claude SDK client
try:
self.client = ClaudeSDKClient(
@@ -168,6 +182,7 @@ class ExpandChatSession:
max_turns=100,
cwd=str(self.project_dir.resolve()),
settings=str(settings_file.resolve()),
env=sdk_env,
)
)
await self.client.__aenter__()

View File

@@ -8,6 +8,7 @@ Uses the create-spec.md skill to guide users through app spec creation.
import json
import logging
import os
import shutil
import threading
from datetime import datetime
@@ -24,6 +25,16 @@ load_dotenv()
logger = logging.getLogger(__name__)
# Environment variables to pass through to Claude CLI for API configuration
API_ENV_VARS = [
"ANTHROPIC_BASE_URL",
"ANTHROPIC_AUTH_TOKEN",
"API_TIMEOUT_MS",
"ANTHROPIC_DEFAULT_SONNET_MODEL",
"ANTHROPIC_DEFAULT_OPUS_MODEL",
"ANTHROPIC_DEFAULT_HAIKU_MODEL",
]
async def _make_multimodal_message(content_blocks: list[dict]) -> AsyncGenerator[dict, None]:
"""
@@ -147,6 +158,10 @@ class SpecChatSession:
# Use Opus for best quality spec generation
# Use system Claude CLI to avoid bundled Bun runtime crash (exit code 3) on Windows
system_cli = shutil.which("claude")
# Build environment overrides for API configuration
sdk_env = {var: os.getenv(var) for var in API_ENV_VARS if os.getenv(var)}
try:
self.client = ClaudeSDKClient(
options=ClaudeAgentOptions(
@@ -163,6 +178,7 @@ class SpecChatSession:
max_turns=100,
cwd=str(self.project_dir.resolve()),
settings=str(settings_file.resolve()),
env=sdk_env,
)
)
# Enter the async context and track it