mirror of
https://github.com/leonvanzyl/autocoder.git
synced 2026-03-16 18:33:08 +00:00
fix: propagate alternative API provider settings to agent subprocesses
When users configured GLM/Ollama/Kimi via the Settings UI, agents still used Claude because conflicting env vars leaked through subprocess env. Root cause: get_effective_sdk_env() set ANTHROPIC_AUTH_TOKEN for GLM but didn't clear ANTHROPIC_API_KEY, which leaked from os.environ. The CLI prioritized the wrong credential. Changes: - registry.py: Clear conflicting auth vars (API_KEY vs AUTH_TOKEN) and Vertex AI vars when building env for alternative providers - client.py: Replace manual os.getenv() loop with get_effective_sdk_env() so agent SDK reads provider settings from the database - autonomous_agent_demo.py: Apply UI-configured provider settings to process env so CLI-launched agents also respect Settings UI config - start.py: Pass --model from settings when launching agent subprocess - server/schemas.py: Allow non-Claude model names when an alternative provider is configured (prevents 422 errors for glm-4.7, etc.) - .env.example: Document env vars for GLM, Ollama, and Kimi providers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
24
.env.example
24
.env.example
@@ -32,5 +32,25 @@
|
||||
# ===================
|
||||
# Alternative API Providers (GLM, Ollama, Kimi, Custom)
|
||||
# ===================
|
||||
# Configure alternative providers via the Settings UI (gear icon > API Provider).
|
||||
# The Settings UI is the recommended way to switch providers and models.
|
||||
# Configure via Settings UI (recommended) or set env vars below.
|
||||
# When both are set, env vars take precedence.
|
||||
#
|
||||
# GLM (Zhipu AI):
|
||||
# ANTHROPIC_BASE_URL=https://api.z.ai/api/anthropic
|
||||
# ANTHROPIC_AUTH_TOKEN=your-glm-api-key
|
||||
# ANTHROPIC_DEFAULT_OPUS_MODEL=glm-4.7
|
||||
# ANTHROPIC_DEFAULT_SONNET_MODEL=glm-4.7
|
||||
# ANTHROPIC_DEFAULT_HAIKU_MODEL=glm-4.7
|
||||
#
|
||||
# Ollama (Local):
|
||||
# ANTHROPIC_BASE_URL=http://localhost:11434
|
||||
# ANTHROPIC_DEFAULT_OPUS_MODEL=qwen3-coder
|
||||
# ANTHROPIC_DEFAULT_SONNET_MODEL=qwen3-coder
|
||||
# ANTHROPIC_DEFAULT_HAIKU_MODEL=qwen3-coder
|
||||
#
|
||||
# Kimi (Moonshot):
|
||||
# ANTHROPIC_BASE_URL=https://api.kimi.com/coding/
|
||||
# ANTHROPIC_API_KEY=your-kimi-api-key
|
||||
# ANTHROPIC_DEFAULT_OPUS_MODEL=kimi-k2.5
|
||||
# ANTHROPIC_DEFAULT_SONNET_MODEL=kimi-k2.5
|
||||
# ANTHROPIC_DEFAULT_HAIKU_MODEL=kimi-k2.5
|
||||
|
||||
@@ -44,8 +44,10 @@ from dotenv import load_dotenv
|
||||
# IMPORTANT: Must be called BEFORE importing other modules that read env vars at load time
|
||||
load_dotenv()
|
||||
|
||||
import os
|
||||
|
||||
from agent import run_autonomous_agent
|
||||
from registry import DEFAULT_MODEL, get_project_path
|
||||
from registry import DEFAULT_MODEL, get_effective_sdk_env, get_project_path
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
@@ -195,6 +197,14 @@ def main() -> None:
|
||||
# Note: Authentication is handled by start.bat/start.sh before this script runs.
|
||||
# The Claude SDK auto-detects credentials from ~/.claude/.credentials.json
|
||||
|
||||
# Apply UI-configured provider settings to this process's environment.
|
||||
# This ensures CLI-launched agents respect Settings UI provider config (GLM, Ollama, etc.).
|
||||
# Uses setdefault so explicit env vars / .env file take precedence.
|
||||
sdk_overrides = get_effective_sdk_env()
|
||||
for key, value in sdk_overrides.items():
|
||||
if value: # Only set non-empty values (empty values are used to clear conflicts)
|
||||
os.environ.setdefault(key, value)
|
||||
|
||||
# Handle deprecated --parallel flag
|
||||
if args.parallel is not None:
|
||||
print("WARNING: --parallel is deprecated. Use --concurrency instead.", flush=True)
|
||||
|
||||
14
client.py
14
client.py
@@ -16,7 +16,6 @@ from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient
|
||||
from claude_agent_sdk.types import HookContext, HookInput, HookMatcher, SyncHookJSONOutput
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from env_constants import API_ENV_VARS
|
||||
from security import SENSITIVE_DIRECTORIES, bash_security_hook
|
||||
|
||||
# Load environment variables from .env file if present
|
||||
@@ -450,14 +449,11 @@ def create_client(
|
||||
}
|
||||
|
||||
# Build environment overrides for API endpoint configuration
|
||||
# These override system env vars for the Claude CLI subprocess,
|
||||
# allowing AutoForge 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
|
||||
# Uses get_effective_sdk_env() which reads provider settings from the database,
|
||||
# ensuring UI-configured alternative providers (GLM, Ollama, Kimi, Custom) propagate
|
||||
# correctly to the Claude CLI subprocess
|
||||
from registry import get_effective_sdk_env
|
||||
sdk_env = get_effective_sdk_env()
|
||||
|
||||
# Detect alternative API mode (Ollama, GLM, or Vertex AI)
|
||||
base_url = sdk_env.get("ANTHROPIC_BASE_URL", "")
|
||||
|
||||
18
registry.py
18
registry.py
@@ -731,7 +731,22 @@ def get_effective_sdk_env() -> dict[str, str]:
|
||||
sdk_env[var] = value
|
||||
return sdk_env
|
||||
|
||||
sdk_env = {}
|
||||
sdk_env: dict[str, str] = {}
|
||||
|
||||
# Explicitly clear credentials that could leak from the server process env.
|
||||
# For providers using ANTHROPIC_AUTH_TOKEN (GLM, Custom), clear ANTHROPIC_API_KEY.
|
||||
# For providers using ANTHROPIC_API_KEY (Kimi), clear ANTHROPIC_AUTH_TOKEN.
|
||||
# This prevents the Claude CLI from using the wrong credentials.
|
||||
auth_env_var = provider.get("auth_env_var", "ANTHROPIC_AUTH_TOKEN")
|
||||
if auth_env_var == "ANTHROPIC_AUTH_TOKEN":
|
||||
sdk_env["ANTHROPIC_API_KEY"] = ""
|
||||
elif auth_env_var == "ANTHROPIC_API_KEY":
|
||||
sdk_env["ANTHROPIC_AUTH_TOKEN"] = ""
|
||||
|
||||
# Clear Vertex AI vars when using non-Vertex alternative providers
|
||||
sdk_env["CLAUDE_CODE_USE_VERTEX"] = ""
|
||||
sdk_env["CLOUD_ML_REGION"] = ""
|
||||
sdk_env["ANTHROPIC_VERTEX_PROJECT_ID"] = ""
|
||||
|
||||
# Base URL
|
||||
base_url = all_settings.get("api_base_url") or provider.get("base_url")
|
||||
@@ -741,7 +756,6 @@ def get_effective_sdk_env() -> dict[str, str]:
|
||||
# Auth token
|
||||
auth_token = all_settings.get("api_auth_token")
|
||||
if auth_token:
|
||||
auth_env_var = provider.get("auth_env_var", "ANTHROPIC_AUTH_TOKEN")
|
||||
sdk_env[auth_env_var] = auth_token
|
||||
|
||||
# Model - set all three tier overrides to the same model
|
||||
|
||||
@@ -190,9 +190,12 @@ class AgentStartRequest(BaseModel):
|
||||
@field_validator('model')
|
||||
@classmethod
|
||||
def validate_model(cls, v: str | None) -> str | None:
|
||||
"""Validate model is in the allowed list."""
|
||||
"""Validate model is in the allowed list (Claude) or allow any model for alternative providers."""
|
||||
if v is not None and v not in VALID_MODELS:
|
||||
raise ValueError(f"Invalid model. Must be one of: {VALID_MODELS}")
|
||||
from registry import get_all_settings
|
||||
settings = get_all_settings()
|
||||
if settings.get("api_provider", "claude") == "claude":
|
||||
raise ValueError(f"Invalid model. Must be one of: {VALID_MODELS}")
|
||||
return v
|
||||
|
||||
@field_validator('max_concurrency')
|
||||
@@ -571,9 +574,12 @@ class ScheduleCreate(BaseModel):
|
||||
@field_validator('model')
|
||||
@classmethod
|
||||
def validate_model(cls, v: str | None) -> str | None:
|
||||
"""Validate model is in the allowed list."""
|
||||
"""Validate model is in the allowed list (Claude) or allow any model for alternative providers."""
|
||||
if v is not None and v not in VALID_MODELS:
|
||||
raise ValueError(f"Invalid model. Must be one of: {VALID_MODELS}")
|
||||
from registry import get_all_settings
|
||||
settings = get_all_settings()
|
||||
if settings.get("api_provider", "claude") == "claude":
|
||||
raise ValueError(f"Invalid model. Must be one of: {VALID_MODELS}")
|
||||
return v
|
||||
|
||||
|
||||
@@ -593,9 +599,12 @@ class ScheduleUpdate(BaseModel):
|
||||
@field_validator('model')
|
||||
@classmethod
|
||||
def validate_model(cls, v: str | None) -> str | None:
|
||||
"""Validate model is in the allowed list."""
|
||||
"""Validate model is in the allowed list (Claude) or allow any model for alternative providers."""
|
||||
if v is not None and v not in VALID_MODELS:
|
||||
raise ValueError(f"Invalid model. Must be one of: {VALID_MODELS}")
|
||||
from registry import get_all_settings
|
||||
settings = get_all_settings()
|
||||
if settings.get("api_provider", "claude") == "claude":
|
||||
raise ValueError(f"Invalid model. Must be one of: {VALID_MODELS}")
|
||||
return v
|
||||
|
||||
|
||||
|
||||
7
start.py
7
start.py
@@ -390,8 +390,11 @@ def run_agent(project_name: str, project_dir: Path) -> None:
|
||||
print(f"Location: {project_dir}")
|
||||
print("-" * 50)
|
||||
|
||||
# Build the command - pass absolute path
|
||||
cmd = [sys.executable, "autonomous_agent_demo.py", "--project-dir", str(project_dir.resolve())]
|
||||
# Build the command - pass absolute path and model from settings
|
||||
from registry import DEFAULT_MODEL, get_all_settings
|
||||
settings = get_all_settings()
|
||||
model = settings.get("api_model") or settings.get("model", DEFAULT_MODEL)
|
||||
cmd = [sys.executable, "autonomous_agent_demo.py", "--project-dir", str(project_dir.resolve()), "--model", model]
|
||||
|
||||
# Run the agent with stderr capture to detect auth errors
|
||||
# stdout goes directly to terminal for real-time output
|
||||
|
||||
Reference in New Issue
Block a user