diff --git a/.env.example b/.env.example
index c426100..e29bec3 100644
--- a/.env.example
+++ b/.env.example
@@ -7,3 +7,15 @@
# - false: Browser opens a visible window (useful for debugging)
# Defaults to 'false' if not specified
# PLAYWRIGHT_HEADLESS=false
+
+# GLM/Alternative API Configuration (Optional)
+# To use Zhipu AI's GLM models instead of Claude, uncomment and set these variables.
+# This only affects AutoCoder - your global Claude Code settings remain unchanged.
+# Get an API key at: https://z.ai/subscribe
+#
+# ANTHROPIC_BASE_URL=https://api.z.ai/api/anthropic
+# ANTHROPIC_AUTH_TOKEN=your-zhipu-api-key
+# API_TIMEOUT_MS=3000000
+# ANTHROPIC_DEFAULT_SONNET_MODEL=glm-4.7
+# ANTHROPIC_DEFAULT_OPUS_MODEL=glm-4.7
+# ANTHROPIC_DEFAULT_HAIKU_MODEL=glm-4.5-air
diff --git a/README.md b/README.md
index 9af8172..3ed7f15 100644
--- a/README.md
+++ b/README.md
@@ -290,18 +290,18 @@ When test progress increases, the agent sends:
### Using GLM Models (Alternative to Claude)
-To use Zhipu AI's GLM models instead of Claude, create a settings file at `~/.claude/settings.json`:
+To use Zhipu AI's GLM models instead of Claude, add these variables to your `.env` file in the AutoCoder directory:
-```json
-{
- "env": {
- "ANTHROPIC_BASE_URL": "https://api.z.ai/api/anthropic",
- "ANTHROPIC_AUTH_TOKEN": "your-zhipu-api-key"
- }
-}
+```bash
+ANTHROPIC_BASE_URL=https://api.z.ai/api/anthropic
+ANTHROPIC_AUTH_TOKEN=your-zhipu-api-key
+API_TIMEOUT_MS=3000000
+ANTHROPIC_DEFAULT_SONNET_MODEL=glm-4.7
+ANTHROPIC_DEFAULT_OPUS_MODEL=glm-4.7
+ANTHROPIC_DEFAULT_HAIKU_MODEL=glm-4.5-air
```
-This routes Claude Code requests through Zhipu's Claude-compatible API, allowing you to use GLM-4.7 and other models while keeping all Claude Code features (MCP servers, hooks, permissions).
+This routes AutoCoder's API requests through Zhipu's Claude-compatible API, allowing you to use GLM-4.7 and other models. **This only affects AutoCoder** - your global Claude Code settings remain unchanged.
Get an API key at: https://z.ai/subscribe
diff --git a/client.py b/client.py
index f232a0a..fdf6d54 100644
--- a/client.py
+++ b/client.py
@@ -25,6 +25,18 @@ load_dotenv()
# When False, browser window is visible (default - useful for monitoring agent progress)
DEFAULT_PLAYWRIGHT_HEADLESS = False
+# Environment variables to pass through to Claude CLI for API configuration
+# These allow using alternative API endpoints (e.g., GLM via z.ai) without
+# affecting the user's global Claude Code settings
+API_ENV_VARS = [
+ "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
+]
+
def get_playwright_headless() -> bool:
"""
@@ -205,6 +217,21 @@ def create_client(project_dir: Path, model: str, yolo_mode: bool = False):
"args": playwright_args,
}
+ # Build environment overrides for API endpoint configuration
+ # These override system env vars for the Claude CLI subprocess,
+ # allowing AutoCoder to use alternative APIs (e.g., GLM) without
+ # affecting the user's global Claude Code settings
+ sdk_env = {}
+ for var in API_ENV_VARS:
+ value = os.getenv(var)
+ if value:
+ sdk_env[var] = value
+
+ if sdk_env:
+ print(f" - API overrides: {', '.join(sdk_env.keys())}")
+ if "ANTHROPIC_BASE_URL" in sdk_env:
+ print(f" - GLM Mode: Using {sdk_env['ANTHROPIC_BASE_URL']}")
+
return ClaudeSDKClient(
options=ClaudeAgentOptions(
model=model,
@@ -222,5 +249,6 @@ def create_client(project_dir: Path, model: str, yolo_mode: bool = False):
max_turns=1000,
cwd=str(project_dir.resolve()),
settings=str(settings_file.resolve()), # Use absolute path
+ env=sdk_env, # Pass API configuration overrides to CLI subprocess
)
)
diff --git a/server/main.py b/server/main.py
index 8be2a50..9340315 100644
--- a/server/main.py
+++ b/server/main.py
@@ -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
diff --git a/server/routers/settings.py b/server/routers/settings.py
index 18362ee..78d6ff8 100644
--- a/server/routers/settings.py
+++ b/server/routers/settings.py
@@ -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(),
)
diff --git a/server/schemas.py b/server/schemas.py
index 72d6bf4..e9b9c31 100644
--- a/server/schemas.py
+++ b/server/schemas.py
@@ -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):
diff --git a/server/services/assistant_chat_session.py b/server/services/assistant_chat_session.py
index 9e067f1..cf18241 100755
--- a/server/services/assistant_chat_session.py
+++ b/server/services/assistant_chat_session.py
@@ -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__()
diff --git a/server/services/expand_chat_session.py b/server/services/expand_chat_session.py
index b187804..71e56bb 100644
--- a/server/services/expand_chat_session.py
+++ b/server/services/expand_chat_session.py
@@ -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__()
diff --git a/server/services/spec_chat_session.py b/server/services/spec_chat_session.py
index b3b4e1c..e073a4e 100644
--- a/server/services/spec_chat_session.py
+++ b/server/services/spec_chat_session.py
@@ -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
diff --git a/ui/src/App.tsx b/ui/src/App.tsx
index 4a33b9e..50b0297 100644
--- a/ui/src/App.tsx
+++ b/ui/src/App.tsx
@@ -1,6 +1,6 @@
import { useState, useEffect, useCallback } from 'react'
import { useQueryClient } from '@tanstack/react-query'
-import { useProjects, useFeatures, useAgentStatus } from './hooks/useProjects'
+import { useProjects, useFeatures, useAgentStatus, useSettings } from './hooks/useProjects'
import { useProjectWebSocket } from './hooks/useWebSocket'
import { useFeatureSound } from './hooks/useFeatureSound'
import { useCelebration } from './hooks/useCelebration'
@@ -46,6 +46,7 @@ function App() {
const queryClient = useQueryClient()
const { data: projects, isLoading: projectsLoading } = useProjects()
const { data: features } = useFeatures(selectedProject)
+ const { data: settings } = useSettings()
useAgentStatus(selectedProject) // Keep polling for status updates
const wsState = useProjectWebSocket(selectedProject)
@@ -210,6 +211,16 @@ function App() {
>
+
+ {/* GLM Mode Badge */}
+ {settings?.glm_mode && (
+
+ GLM
+
+ )}
>
)}
diff --git a/ui/src/hooks/useProjects.ts b/ui/src/hooks/useProjects.ts
index 6a1098f..d6081a7 100644
--- a/ui/src/hooks/useProjects.ts
+++ b/ui/src/hooks/useProjects.ts
@@ -217,6 +217,7 @@ const DEFAULT_MODELS: ModelsResponse = {
const DEFAULT_SETTINGS: Settings = {
yolo_mode: false,
model: 'claude-opus-4-5-20251101',
+ glm_mode: false,
}
export function useAvailableModels() {
diff --git a/ui/src/lib/types.ts b/ui/src/lib/types.ts
index cceb704..0851617 100644
--- a/ui/src/lib/types.ts
+++ b/ui/src/lib/types.ts
@@ -387,6 +387,7 @@ export interface ModelsResponse {
export interface Settings {
yolo_mode: boolean
model: string
+ glm_mode: boolean
}
export interface SettingsUpdate {