refactor: make Settings UI the single source of truth for API provider

Remove legacy env-var-based provider/mode detection that caused misleading
UI badges (e.g., GLM badge showing when Settings was set to Claude).

Key changes:
- Remove _is_glm_mode() and _is_ollama_mode() env-var sniffing functions
  from server/routers/settings.py; derive glm_mode/ollama_mode purely from
  the api_provider setting
- Remove `import os` from settings router (no longer needed)
- Update schema comments to reflect settings-based derivation
- Remove "(configured via .env)" from badge tooltips in App.tsx
- Remove Kimi/GLM/Ollama/Playwright-headless sections from .env.example;
  add note pointing to Settings UI
- Update CLAUDE.md and README.md documentation to reference Settings UI
  for alternative provider configuration
- Update model IDs from claude-opus-4-5-20251101 to claude-opus-4-6
  across registry, client, chat sessions, tests, and UI defaults
- Add LEGACY_MODEL_MAP with auto-migration in get_all_settings()
- Show model ID subtitle in SettingsModal model selector
- Add Vertex passthrough test for claude-opus-4-6 (no date suffix)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Auto
2026-02-06 09:23:06 +02:00
parent c0aaac241c
commit a52f191a54
15 changed files with 96 additions and 163 deletions

View File

@@ -9,11 +9,6 @@
# - webkit: Safari engine # - webkit: Safari engine
# - msedge: Microsoft Edge # - msedge: Microsoft Edge
# PLAYWRIGHT_BROWSER=firefox # PLAYWRIGHT_BROWSER=firefox
#
# PLAYWRIGHT_HEADLESS: Run browser without visible window
# - true: Browser runs in background, saves CPU (default)
# - false: Browser opens a visible window (useful for debugging)
# PLAYWRIGHT_HEADLESS=true
# Extra Read Paths (Optional) # Extra Read Paths (Optional)
# Comma-separated list of absolute paths for read-only access to external directories. # Comma-separated list of absolute paths for read-only access to external directories.
@@ -25,56 +20,17 @@
# Google Cloud Vertex AI Configuration (Optional) # Google Cloud Vertex AI Configuration (Optional)
# To use Claude via Vertex AI on Google Cloud Platform, uncomment and set these variables. # To use Claude via Vertex AI on Google Cloud Platform, uncomment and set these variables.
# Requires: gcloud CLI installed and authenticated (run: gcloud auth application-default login) # Requires: gcloud CLI installed and authenticated (run: gcloud auth application-default login)
# Note: Use @ instead of - in model names (e.g., claude-opus-4-5@20251101) # Note: Use @ instead of - in model names for date-suffixed models (e.g., claude-sonnet-4-5@20250929)
# #
# CLAUDE_CODE_USE_VERTEX=1 # CLAUDE_CODE_USE_VERTEX=1
# CLOUD_ML_REGION=us-east5 # CLOUD_ML_REGION=us-east5
# ANTHROPIC_VERTEX_PROJECT_ID=your-gcp-project-id # ANTHROPIC_VERTEX_PROJECT_ID=your-gcp-project-id
# ANTHROPIC_DEFAULT_OPUS_MODEL=claude-opus-4-5@20251101 # ANTHROPIC_DEFAULT_OPUS_MODEL=claude-opus-4-6
# ANTHROPIC_DEFAULT_SONNET_MODEL=claude-sonnet-4-5@20250929 # ANTHROPIC_DEFAULT_SONNET_MODEL=claude-sonnet-4-5@20250929
# ANTHROPIC_DEFAULT_HAIKU_MODEL=claude-3-5-haiku@20241022 # ANTHROPIC_DEFAULT_HAIKU_MODEL=claude-3-5-haiku@20241022
# =================== # ===================
# Alternative API Providers # Alternative API Providers (GLM, Ollama, Kimi, Custom)
# =================== # ===================
# NOTE: These env vars are the legacy way to configure providers. # Configure alternative providers via the Settings UI (gear icon > API Provider).
# The recommended way is to use the Settings UI (API Provider section). # The Settings UI is the recommended way to switch providers and models.
# UI settings take precedence when api_provider != "claude".
# Kimi K2.5 (Moonshot) Configuration (Optional)
# Get an API key at: https://kimi.com
#
# ANTHROPIC_BASE_URL=https://api.kimi.com/coding/
# ANTHROPIC_API_KEY=your-kimi-api-key
# ANTHROPIC_DEFAULT_SONNET_MODEL=kimi-k2.5
# ANTHROPIC_DEFAULT_OPUS_MODEL=kimi-k2.5
# ANTHROPIC_DEFAULT_HAIKU_MODEL=kimi-k2.5
# GLM/Alternative API Configuration (Optional)
# To use Zhipu AI's GLM models instead of Claude, uncomment and set these variables.
# This only affects AutoForge - 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
# Ollama Local Model Configuration (Optional)
# To use local models via Ollama instead of Claude, uncomment and set these variables.
# Requires Ollama v0.14.0+ with Anthropic API compatibility.
# See: https://ollama.com/blog/claude
#
# ANTHROPIC_BASE_URL=http://localhost:11434
# ANTHROPIC_AUTH_TOKEN=ollama
# API_TIMEOUT_MS=3000000
# ANTHROPIC_DEFAULT_SONNET_MODEL=qwen3-coder
# ANTHROPIC_DEFAULT_OPUS_MODEL=qwen3-coder
# ANTHROPIC_DEFAULT_HAIKU_MODEL=qwen3-coder
#
# Model recommendations:
# - For best results, use a capable coding model like qwen3-coder or deepseek-coder-v2
# - You can use the same model for all tiers, or different models per tier
# - Larger models (70B+) work best for Opus tier, smaller (7B-20B) for Haiku

View File

@@ -408,44 +408,23 @@ Run coding agents via Google Cloud Vertex AI:
CLAUDE_CODE_USE_VERTEX=1 CLAUDE_CODE_USE_VERTEX=1
CLOUD_ML_REGION=us-east5 CLOUD_ML_REGION=us-east5
ANTHROPIC_VERTEX_PROJECT_ID=your-gcp-project-id ANTHROPIC_VERTEX_PROJECT_ID=your-gcp-project-id
ANTHROPIC_DEFAULT_OPUS_MODEL=claude-opus-4-5@20251101 ANTHROPIC_DEFAULT_OPUS_MODEL=claude-opus-4-6
ANTHROPIC_DEFAULT_SONNET_MODEL=claude-sonnet-4-5@20250929 ANTHROPIC_DEFAULT_SONNET_MODEL=claude-sonnet-4-5@20250929
ANTHROPIC_DEFAULT_HAIKU_MODEL=claude-3-5-haiku@20241022 ANTHROPIC_DEFAULT_HAIKU_MODEL=claude-3-5-haiku@20241022
``` ```
**Note:** Use `@` instead of `-` in model names for Vertex AI. **Note:** Use `@` instead of `-` in model names for Vertex AI.
### Ollama Local Models (Optional) ### Alternative API Providers (GLM, Ollama, Kimi, Custom)
Run coding agents using local models via Ollama v0.14.0+: Alternative providers are configured via the **Settings UI** (gear icon > API Provider section). Select a provider, set the base URL, auth token, and model — no `.env` changes needed.
1. Install Ollama: https://ollama.com **Available providers:** Claude (default), GLM (Zhipu AI), Ollama (local models), Kimi (Moonshot), Custom
2. Start Ollama: `ollama serve`
3. Pull a coding model: `ollama pull qwen3-coder`
4. Configure `.env`:
```
ANTHROPIC_BASE_URL=http://localhost:11434
ANTHROPIC_AUTH_TOKEN=ollama
API_TIMEOUT_MS=3000000
ANTHROPIC_DEFAULT_SONNET_MODEL=qwen3-coder
ANTHROPIC_DEFAULT_OPUS_MODEL=qwen3-coder
ANTHROPIC_DEFAULT_HAIKU_MODEL=qwen3-coder
```
5. Run AutoForge normally - it will use your local Ollama models
**Recommended coding models:** **Ollama notes:**
- `qwen3-coder` - Good balance of speed and capability - Requires Ollama v0.14.0+ with Anthropic API compatibility
- `deepseek-coder-v2` - Strong coding performance - Install: https://ollama.com → `ollama serve` → `ollama pull qwen3-coder`
- `codellama` - Meta's code-focused model - Recommended models: `qwen3-coder`, `deepseek-coder-v2`, `codellama`
**Model tier mapping:**
- Use the same model for all tiers, or map different models per capability level
- Larger models (70B+) work best for Opus tier
- Smaller models (7B-20B) work well for Haiku tier
**Known limitations:**
- Smaller context windows than Claude (model-dependent)
- Extended context beta disabled (not supported by Ollama)
- Performance depends on local hardware (GPU recommended) - Performance depends on local hardware (GPU recommended)
## Claude Code Integration ## Claude Code Integration

View File

@@ -326,37 +326,13 @@ When test progress increases, the agent sends:
} }
``` ```
### Using GLM Models (Alternative to Claude) ### Alternative API Providers (GLM, Ollama, Kimi, Custom)
Add these variables to your `.env` file to use Zhipu AI's GLM models: Alternative providers are configured via the **Settings UI** (gear icon > API Provider). Select your provider, set the base URL, auth token, and model directly in the UI — no `.env` changes needed.
```bash Available providers: **Claude** (default), **GLM** (Zhipu AI), **Ollama** (local models), **Kimi** (Moonshot), **Custom**
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 AutoForge's API requests through Zhipu's Claude-compatible API, allowing you to use GLM-4.7 and other models. **This only affects AutoForge** - your global Claude Code settings remain unchanged. 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.
Get an API key at: https://z.ai/subscribe
### Using Ollama Local Models
Add these variables to your `.env` file to run agents with local models via Ollama v0.14.0+:
```bash
ANTHROPIC_BASE_URL=http://localhost:11434
ANTHROPIC_AUTH_TOKEN=ollama
API_TIMEOUT_MS=3000000
ANTHROPIC_DEFAULT_SONNET_MODEL=qwen3-coder
ANTHROPIC_DEFAULT_OPUS_MODEL=qwen3-coder
ANTHROPIC_DEFAULT_HAIKU_MODEL=qwen3-coder
```
See the [CLAUDE.md](CLAUDE.md) for recommended models and known limitations.
### Using Vertex AI ### Using Vertex AI
@@ -366,7 +342,7 @@ Add these variables to your `.env` file to run agents via Google Cloud Vertex AI
CLAUDE_CODE_USE_VERTEX=1 CLAUDE_CODE_USE_VERTEX=1
CLOUD_ML_REGION=us-east5 CLOUD_ML_REGION=us-east5
ANTHROPIC_VERTEX_PROJECT_ID=your-gcp-project-id ANTHROPIC_VERTEX_PROJECT_ID=your-gcp-project-id
ANTHROPIC_DEFAULT_OPUS_MODEL=claude-opus-4-5@20251101 ANTHROPIC_DEFAULT_OPUS_MODEL=claude-opus-4-6
ANTHROPIC_DEFAULT_SONNET_MODEL=claude-sonnet-4-5@20250929 ANTHROPIC_DEFAULT_SONNET_MODEL=claude-sonnet-4-5@20250929
ANTHROPIC_DEFAULT_HAIKU_MODEL=claude-3-5-haiku@20241022 ANTHROPIC_DEFAULT_HAIKU_MODEL=claude-3-5-haiku@20241022
``` ```

View File

@@ -46,8 +46,9 @@ def convert_model_for_vertex(model: str) -> str:
""" """
Convert model name format for Vertex AI compatibility. Convert model name format for Vertex AI compatibility.
Vertex AI uses @ to separate model name from version (e.g., claude-opus-4-5@20251101) Vertex AI uses @ to separate model name from version (e.g., claude-sonnet-4-5@20250929)
while the Anthropic API uses - (e.g., claude-opus-4-5-20251101). while the Anthropic API uses - (e.g., claude-sonnet-4-5-20250929).
Models without a date suffix (e.g., claude-opus-4-6) pass through unchanged.
Args: Args:
model: Model name in Anthropic format (with hyphens) model: Model name in Anthropic format (with hyphens)
@@ -61,7 +62,7 @@ def convert_model_for_vertex(model: str) -> str:
return model return model
# Pattern: claude-{name}-{version}-{date} -> claude-{name}-{version}@{date} # Pattern: claude-{name}-{version}-{date} -> claude-{name}-{version}@{date}
# Example: claude-opus-4-5-20251101 -> claude-opus-4-5@20251101 # Example: claude-sonnet-4-5-20250929 -> claude-sonnet-4-5@20250929
# The date is always 8 digits at the end # The date is always 8 digits at the end
match = re.match(r'^(claude-.+)-(\d{8})$', model) match = re.match(r'^(claude-.+)-(\d{8})$', model)
if match: if match:

View File

@@ -46,10 +46,16 @@ def _migrate_registry_dir() -> None:
# Available models with display names # Available models with display names
# To add a new model: add an entry here with {"id": "model-id", "name": "Display Name"} # To add a new model: add an entry here with {"id": "model-id", "name": "Display Name"}
AVAILABLE_MODELS = [ AVAILABLE_MODELS = [
{"id": "claude-opus-4-5-20251101", "name": "Claude Opus 4.5"}, {"id": "claude-opus-4-6", "name": "Claude Opus"},
{"id": "claude-sonnet-4-5-20250929", "name": "Claude Sonnet 4.5"}, {"id": "claude-sonnet-4-5-20250929", "name": "Claude Sonnet"},
] ]
# Map legacy model IDs to their current replacements.
# Used by get_all_settings() to auto-migrate stale values on first read after upgrade.
LEGACY_MODEL_MAP = {
"claude-opus-4-5-20251101": "claude-opus-4-6",
}
# List of valid model IDs (derived from AVAILABLE_MODELS) # List of valid model IDs (derived from AVAILABLE_MODELS)
VALID_MODELS = [m["id"] for m in AVAILABLE_MODELS] VALID_MODELS = [m["id"] for m in AVAILABLE_MODELS]
@@ -59,7 +65,7 @@ VALID_MODELS = [m["id"] for m in AVAILABLE_MODELS]
_env_default_model = os.getenv("ANTHROPIC_DEFAULT_OPUS_MODEL") _env_default_model = os.getenv("ANTHROPIC_DEFAULT_OPUS_MODEL")
if _env_default_model is not None: if _env_default_model is not None:
_env_default_model = _env_default_model.strip() _env_default_model = _env_default_model.strip()
DEFAULT_MODEL = _env_default_model or "claude-opus-4-5-20251101" DEFAULT_MODEL = _env_default_model or "claude-opus-4-6"
# Ensure env-provided DEFAULT_MODEL is in VALID_MODELS for validation consistency # Ensure env-provided DEFAULT_MODEL is in VALID_MODELS for validation consistency
# (idempotent: only adds if missing, doesn't alter AVAILABLE_MODELS semantics) # (idempotent: only adds if missing, doesn't alter AVAILABLE_MODELS semantics)
@@ -598,6 +604,9 @@ def get_all_settings() -> dict[str, str]:
""" """
Get all settings as a dictionary. Get all settings as a dictionary.
Automatically migrates legacy model IDs (e.g. claude-opus-4-5-20251101 -> claude-opus-4-6)
on first read after upgrade. This is a one-time silent migration.
Returns: Returns:
Dictionary mapping setting keys to values. Dictionary mapping setting keys to values.
""" """
@@ -606,7 +615,26 @@ def get_all_settings() -> dict[str, str]:
session = SessionLocal() session = SessionLocal()
try: try:
settings = session.query(Settings).all() settings = session.query(Settings).all()
return {s.key: s.value for s in settings} result = {s.key: s.value for s in settings}
# Auto-migrate legacy model IDs
migrated = False
for key in ("model", "api_model"):
old_id = result.get(key)
if old_id and old_id in LEGACY_MODEL_MAP:
new_id = LEGACY_MODEL_MAP[old_id]
setting = session.query(Settings).filter(Settings.key == key).first()
if setting:
setting.value = new_id
setting.updated_at = datetime.now()
result[key] = new_id
migrated = True
logger.info("Migrated setting '%s': %s -> %s", key, old_id, new_id)
if migrated:
session.commit()
return result
finally: finally:
session.close() session.close()
except Exception as e: except Exception as e:
@@ -624,10 +652,10 @@ API_PROVIDERS: dict[str, dict[str, Any]] = {
"base_url": None, "base_url": None,
"requires_auth": False, "requires_auth": False,
"models": [ "models": [
{"id": "claude-opus-4-5-20251101", "name": "Claude Opus 4.5"}, {"id": "claude-opus-4-6", "name": "Claude Opus"},
{"id": "claude-sonnet-4-5-20250929", "name": "Claude Sonnet 4.5"}, {"id": "claude-sonnet-4-5-20250929", "name": "Claude Sonnet"},
], ],
"default_model": "claude-opus-4-5-20251101", "default_model": "claude-opus-4-6",
}, },
"kimi": { "kimi": {
"name": "Kimi K2.5 (Moonshot)", "name": "Kimi K2.5 (Moonshot)",

View File

@@ -7,7 +7,6 @@ Settings are stored in the registry database and shared across all projects.
""" """
import mimetypes import mimetypes
import os
import sys import sys
from fastapi import APIRouter from fastapi import APIRouter
@@ -39,19 +38,6 @@ def _parse_yolo_mode(value: str | None) -> bool:
return (value or "false").lower() == "true" return (value or "false").lower() == "true"
def _is_glm_mode() -> bool:
"""Check if GLM API is configured via environment variables."""
base_url = os.getenv("ANTHROPIC_BASE_URL", "")
# GLM mode is when ANTHROPIC_BASE_URL is set but NOT pointing to Ollama
return bool(base_url) and not _is_ollama_mode()
def _is_ollama_mode() -> bool:
"""Check if Ollama API is configured via environment variables."""
base_url = os.getenv("ANTHROPIC_BASE_URL", "")
return "localhost:11434" in base_url or "127.0.0.1:11434" in base_url
@router.get("/providers", response_model=ProvidersResponse) @router.get("/providers", response_model=ProvidersResponse)
async def get_available_providers(): async def get_available_providers():
"""Get list of available API providers.""" """Get list of available API providers."""
@@ -116,9 +102,8 @@ async def get_settings():
api_provider = all_settings.get("api_provider", "claude") api_provider = all_settings.get("api_provider", "claude")
# Compute glm_mode / ollama_mode from api_provider for backward compat glm_mode = api_provider == "glm"
glm_mode = api_provider == "glm" or _is_glm_mode() ollama_mode = api_provider == "ollama"
ollama_mode = api_provider == "ollama" or _is_ollama_mode()
return SettingsResponse( return SettingsResponse(
yolo_mode=_parse_yolo_mode(all_settings.get("yolo_mode")), yolo_mode=_parse_yolo_mode(all_settings.get("yolo_mode")),
@@ -181,8 +166,8 @@ async def update_settings(update: SettingsUpdate):
# Return updated settings # Return updated settings
all_settings = get_all_settings() all_settings = get_all_settings()
api_provider = all_settings.get("api_provider", "claude") api_provider = all_settings.get("api_provider", "claude")
glm_mode = api_provider == "glm" or _is_glm_mode() glm_mode = api_provider == "glm"
ollama_mode = api_provider == "ollama" or _is_ollama_mode() ollama_mode = api_provider == "ollama"
return SettingsResponse( return SettingsResponse(
yolo_mode=_parse_yolo_mode(all_settings.get("yolo_mode")), yolo_mode=_parse_yolo_mode(all_settings.get("yolo_mode")),

View File

@@ -411,8 +411,8 @@ class SettingsResponse(BaseModel):
"""Response schema for global settings.""" """Response schema for global settings."""
yolo_mode: bool = False yolo_mode: bool = False
model: str = DEFAULT_MODEL model: str = DEFAULT_MODEL
glm_mode: bool = False # True if GLM API is configured via .env glm_mode: bool = False # True when api_provider is "glm"
ollama_mode: bool = False # True if Ollama API is configured via .env ollama_mode: bool = False # True when api_provider is "ollama"
testing_agent_ratio: int = 1 # Regression testing agents (0-3) testing_agent_ratio: int = 1 # Regression testing agents (0-3)
playwright_headless: bool = True playwright_headless: bool = True
batch_size: int = 3 # Features per coding agent batch (1-3) batch_size: int = 3 # Features per coding agent batch (1-3)

View File

@@ -157,7 +157,7 @@ class AssistantChatSession:
""" """
Manages a read-only assistant conversation for a project. Manages a read-only assistant conversation for a project.
Uses Claude Opus 4.5 with only read-only tools enabled. Uses Claude Opus with only read-only tools enabled.
Persists conversation history to SQLite. Persists conversation history to SQLite.
""" """
@@ -258,11 +258,11 @@ class AssistantChatSession:
system_cli = shutil.which("claude") system_cli = shutil.which("claude")
# Build environment overrides for API configuration # Build environment overrides for API configuration
from registry import get_effective_sdk_env from registry import DEFAULT_MODEL, get_effective_sdk_env
sdk_env = get_effective_sdk_env() sdk_env = get_effective_sdk_env()
# Determine model from SDK env (provider-aware) or fallback to env/default # Determine model from SDK env (provider-aware) or fallback to env/default
model = sdk_env.get("ANTHROPIC_DEFAULT_OPUS_MODEL") or os.getenv("ANTHROPIC_DEFAULT_OPUS_MODEL", "claude-opus-4-5-20251101") model = sdk_env.get("ANTHROPIC_DEFAULT_OPUS_MODEL") or os.getenv("ANTHROPIC_DEFAULT_OPUS_MODEL", DEFAULT_MODEL)
try: try:
logger.info("Creating ClaudeSDKClient...") logger.info("Creating ClaudeSDKClient...")

View File

@@ -154,11 +154,11 @@ class ExpandChatSession:
system_prompt = skill_content.replace("$ARGUMENTS", project_path) system_prompt = skill_content.replace("$ARGUMENTS", project_path)
# Build environment overrides for API configuration # Build environment overrides for API configuration
from registry import get_effective_sdk_env from registry import DEFAULT_MODEL, get_effective_sdk_env
sdk_env = get_effective_sdk_env() sdk_env = get_effective_sdk_env()
# Determine model from SDK env (provider-aware) or fallback to env/default # Determine model from SDK env (provider-aware) or fallback to env/default
model = sdk_env.get("ANTHROPIC_DEFAULT_OPUS_MODEL") or os.getenv("ANTHROPIC_DEFAULT_OPUS_MODEL", "claude-opus-4-5-20251101") model = sdk_env.get("ANTHROPIC_DEFAULT_OPUS_MODEL") or os.getenv("ANTHROPIC_DEFAULT_OPUS_MODEL", DEFAULT_MODEL)
# Build MCP servers config for feature creation # Build MCP servers config for feature creation
mcp_servers = { mcp_servers = {

View File

@@ -346,7 +346,7 @@ class AgentProcessManager:
Args: Args:
yolo_mode: If True, run in YOLO mode (skip testing agents) yolo_mode: If True, run in YOLO mode (skip testing agents)
model: Model to use (e.g., claude-opus-4-5-20251101) model: Model to use (e.g., claude-opus-4-6)
parallel_mode: DEPRECATED - ignored, always uses unified orchestrator parallel_mode: DEPRECATED - ignored, always uses unified orchestrator
max_concurrency: Max concurrent coding agents (1-5, default 1) max_concurrency: Max concurrent coding agents (1-5, default 1)
testing_agent_ratio: Number of regression testing agents (0-3, default 1) testing_agent_ratio: Number of regression testing agents (0-3, default 1)

View File

@@ -140,11 +140,11 @@ class SpecChatSession:
system_cli = shutil.which("claude") system_cli = shutil.which("claude")
# Build environment overrides for API configuration # Build environment overrides for API configuration
from registry import get_effective_sdk_env from registry import DEFAULT_MODEL, get_effective_sdk_env
sdk_env = get_effective_sdk_env() sdk_env = get_effective_sdk_env()
# Determine model from SDK env (provider-aware) or fallback to env/default # Determine model from SDK env (provider-aware) or fallback to env/default
model = sdk_env.get("ANTHROPIC_DEFAULT_OPUS_MODEL") or os.getenv("ANTHROPIC_DEFAULT_OPUS_MODEL", "claude-opus-4-5-20251101") model = sdk_env.get("ANTHROPIC_DEFAULT_OPUS_MODEL") or os.getenv("ANTHROPIC_DEFAULT_OPUS_MODEL", DEFAULT_MODEL)
try: try:
self.client = ClaudeSDKClient( self.client = ClaudeSDKClient(

View File

@@ -40,15 +40,15 @@ class TestConvertModelForVertex(unittest.TestCase):
def test_returns_model_unchanged_when_vertex_disabled(self): def test_returns_model_unchanged_when_vertex_disabled(self):
os.environ.pop("CLAUDE_CODE_USE_VERTEX", None) os.environ.pop("CLAUDE_CODE_USE_VERTEX", None)
self.assertEqual( self.assertEqual(
convert_model_for_vertex("claude-opus-4-5-20251101"), convert_model_for_vertex("claude-opus-4-6"),
"claude-opus-4-5-20251101", "claude-opus-4-6",
) )
def test_returns_model_unchanged_when_vertex_set_to_zero(self): def test_returns_model_unchanged_when_vertex_set_to_zero(self):
os.environ["CLAUDE_CODE_USE_VERTEX"] = "0" os.environ["CLAUDE_CODE_USE_VERTEX"] = "0"
self.assertEqual( self.assertEqual(
convert_model_for_vertex("claude-opus-4-5-20251101"), convert_model_for_vertex("claude-opus-4-6"),
"claude-opus-4-5-20251101", "claude-opus-4-6",
) )
def test_returns_model_unchanged_when_vertex_set_to_empty(self): def test_returns_model_unchanged_when_vertex_set_to_empty(self):
@@ -60,13 +60,20 @@ class TestConvertModelForVertex(unittest.TestCase):
# --- Vertex AI enabled: standard conversions --- # --- Vertex AI enabled: standard conversions ---
def test_converts_opus_model(self): def test_converts_legacy_opus_model(self):
os.environ["CLAUDE_CODE_USE_VERTEX"] = "1" os.environ["CLAUDE_CODE_USE_VERTEX"] = "1"
self.assertEqual( self.assertEqual(
convert_model_for_vertex("claude-opus-4-5-20251101"), convert_model_for_vertex("claude-opus-4-5-20251101"),
"claude-opus-4-5@20251101", "claude-opus-4-5@20251101",
) )
def test_opus_4_6_passthrough_on_vertex(self):
os.environ["CLAUDE_CODE_USE_VERTEX"] = "1"
self.assertEqual(
convert_model_for_vertex("claude-opus-4-6"),
"claude-opus-4-6",
)
def test_converts_sonnet_model(self): def test_converts_sonnet_model(self):
os.environ["CLAUDE_CODE_USE_VERTEX"] = "1" os.environ["CLAUDE_CODE_USE_VERTEX"] = "1"
self.assertEqual( self.assertEqual(
@@ -86,8 +93,8 @@ class TestConvertModelForVertex(unittest.TestCase):
def test_already_vertex_format_unchanged(self): def test_already_vertex_format_unchanged(self):
os.environ["CLAUDE_CODE_USE_VERTEX"] = "1" os.environ["CLAUDE_CODE_USE_VERTEX"] = "1"
self.assertEqual( self.assertEqual(
convert_model_for_vertex("claude-opus-4-5@20251101"), convert_model_for_vertex("claude-sonnet-4-5@20250929"),
"claude-opus-4-5@20251101", "claude-sonnet-4-5@20250929",
) )
def test_non_claude_model_unchanged(self): def test_non_claude_model_unchanged(self):
@@ -100,8 +107,8 @@ class TestConvertModelForVertex(unittest.TestCase):
def test_model_without_date_suffix_unchanged(self): def test_model_without_date_suffix_unchanged(self):
os.environ["CLAUDE_CODE_USE_VERTEX"] = "1" os.environ["CLAUDE_CODE_USE_VERTEX"] = "1"
self.assertEqual( self.assertEqual(
convert_model_for_vertex("claude-opus-4-5"), convert_model_for_vertex("claude-opus-4-6"),
"claude-opus-4-5", "claude-opus-4-6",
) )
def test_empty_string_unchanged(self): def test_empty_string_unchanged(self):

View File

@@ -319,7 +319,7 @@ function App() {
{settings?.ollama_mode && ( {settings?.ollama_mode && (
<div <div
className="flex items-center gap-1.5 px-2 py-1 bg-card rounded border-2 border-border shadow-sm" className="flex items-center gap-1.5 px-2 py-1 bg-card rounded border-2 border-border shadow-sm"
title="Using Ollama local models (configured via .env)" title="Using Ollama local models"
> >
<img src="/ollama.png" alt="Ollama" className="w-5 h-5" /> <img src="/ollama.png" alt="Ollama" className="w-5 h-5" />
<span className="text-xs font-bold text-foreground">Ollama</span> <span className="text-xs font-bold text-foreground">Ollama</span>
@@ -330,7 +330,7 @@ function App() {
{settings?.glm_mode && ( {settings?.glm_mode && (
<Badge <Badge
className="bg-purple-500 text-white hover:bg-purple-600" className="bg-purple-500 text-white hover:bg-purple-600"
title="Using GLM API (configured via .env)" title="Using GLM API"
> >
GLM GLM
</Badge> </Badge>

View File

@@ -325,7 +325,8 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
: 'bg-background text-foreground hover:bg-muted' : 'bg-background text-foreground hover:bg-muted'
} ${isSaving ? 'opacity-50 cursor-not-allowed' : ''}`} } ${isSaving ? 'opacity-50 cursor-not-allowed' : ''}`}
> >
{model.name} <span className="block">{model.name}</span>
<span className="block text-xs opacity-60">{model.id}</span>
</button> </button>
))} ))}
</div> </div>

View File

@@ -254,15 +254,15 @@ export function useValidatePath() {
// Default models response for placeholder (until API responds) // Default models response for placeholder (until API responds)
const DEFAULT_MODELS: ModelsResponse = { const DEFAULT_MODELS: ModelsResponse = {
models: [ models: [
{ id: 'claude-opus-4-5-20251101', name: 'Claude Opus 4.5' }, { id: 'claude-opus-4-6', name: 'Claude Opus' },
{ id: 'claude-sonnet-4-5-20250929', name: 'Claude Sonnet 4.5' }, { id: 'claude-sonnet-4-5-20250929', name: 'Claude Sonnet' },
], ],
default: 'claude-opus-4-5-20251101', default: 'claude-opus-4-6',
} }
const DEFAULT_SETTINGS: Settings = { const DEFAULT_SETTINGS: Settings = {
yolo_mode: false, yolo_mode: false,
model: 'claude-opus-4-5-20251101', model: 'claude-opus-4-6',
glm_mode: false, glm_mode: false,
ollama_mode: false, ollama_mode: false,
testing_agent_ratio: 1, testing_agent_ratio: 1,
@@ -276,7 +276,7 @@ const DEFAULT_SETTINGS: Settings = {
const DEFAULT_PROVIDERS: ProvidersResponse = { const DEFAULT_PROVIDERS: ProvidersResponse = {
providers: [ providers: [
{ id: 'claude', name: 'Claude (Anthropic)', base_url: null, models: DEFAULT_MODELS.models, default_model: 'claude-opus-4-5-20251101', requires_auth: false }, { id: 'claude', name: 'Claude (Anthropic)', base_url: null, models: DEFAULT_MODELS.models, default_model: 'claude-opus-4-6', requires_auth: false },
], ],
current: 'claude', current: 'claude',
} }