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
10 changes: 9 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
# ANTHROPIC_DEFAULT_HAIKU_MODEL=claude-3-5-haiku@20241022

# ===================
# Alternative API Providers (Azure, GLM, Ollama, Kimi, Custom)
# Alternative API Providers (Azure, GLM, Ollama, Kimi, Gemini, Custom)
# ===================
# Configure via Settings UI (recommended) or set env vars below.
# When both are set, env vars take precedence.
Expand Down Expand Up @@ -61,3 +61,11 @@
# ANTHROPIC_DEFAULT_OPUS_MODEL=kimi-k2.5
# ANTHROPIC_DEFAULT_SONNET_MODEL=kimi-k2.5
# ANTHROPIC_DEFAULT_HAIKU_MODEL=kimi-k2.5
#
# Google Gemini (via an Anthropic-compatible LiteLLM proxy):
# GEMINI_API_KEY=... litellm --model gemini/gemini-2.5-pro --alias gemini-2.5-pro --port 4000
# ANTHROPIC_BASE_URL=http://localhost:4000
# ANTHROPIC_AUTH_TOKEN=your-litellm-master-key
# ANTHROPIC_DEFAULT_OPUS_MODEL=gemini-2.5-pro
# ANTHROPIC_DEFAULT_SONNET_MODEL=gemini-2.5-pro
# ANTHROPIC_DEFAULT_HAIKU_MODEL=gemini-2.5-pro
9 changes: 7 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -418,18 +418,23 @@ Run coding agents via Google Cloud Vertex AI:

**Note:** Use `@` instead of `-` in model names for Vertex AI.

### Alternative API Providers (GLM, Ollama, Kimi, Custom)
### Alternative API Providers (GLM, Ollama, Kimi, Gemini, Custom)

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.

**Available providers:** Claude (default), GLM (Zhipu AI), Ollama (local models), Kimi (Moonshot), Custom
**Available providers:** Claude (default), GLM (Zhipu AI), Ollama (local models), Kimi (Moonshot), Gemini (Google, via LiteLLM), Custom

**Ollama notes:**
- Requires Ollama v0.14.0+ with Anthropic API compatibility
- Install: https://ollama.com → `ollama serve` → `ollama pull qwen3-coder`
- Recommended models: `qwen3-coder`, `deepseek-coder-v2`, `codellama`
- Performance depends on local hardware (GPU recommended)

**Gemini notes:**
- Google has no Anthropic Messages-compatible endpoint, so Gemini goes through an Anthropic-compatible gateway (LiteLLM proxy, default `http://localhost:4000`)
- Start: `GEMINI_API_KEY=... litellm --model gemini/gemini-2.5-pro --alias gemini-2.5-pro --port 4000`
- Auth token is sent as `ANTHROPIC_AUTH_TOKEN` (the LiteLLM master key, if set); base URL and model are editable in the UI

## Claude Code Integration

**Slash commands** (`.claude/commands/`):
Expand Down
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,14 +332,24 @@ When test progress increases, the agent sends:
}
```

### Alternative API Providers (GLM, Ollama, Kimi, Custom)
### Alternative API Providers (GLM, Ollama, Kimi, Gemini, Custom)

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.

Available providers: **Claude** (default), **GLM** (Zhipu AI), **Ollama** (local models), **Kimi** (Moonshot), **Custom**
Available providers: **Claude** (default), **GLM** (Zhipu AI), **Ollama** (local models), **Kimi** (Moonshot), **Gemini** (Google, via LiteLLM), **Custom**

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.

For Google Gemini, run an Anthropic-compatible gateway such as [LiteLLM](https://docs.litellm.ai/) in front of the Gemini API (Google does not offer an Anthropic Messages endpoint, and the agents run on the Claude Agent SDK):

```bash
pip install 'litellm[proxy]'
export GEMINI_API_KEY=your-gemini-api-key # from aistudio.google.com
litellm --model gemini/gemini-2.5-pro --alias gemini-2.5-pro --port 4000
```

Then select "Google Gemini (via LiteLLM)" in the Settings UI. The base URL defaults to `http://localhost:4000`; enter the LiteLLM master key as the API key if you configured one. Pick a listed model or type any model name your gateway serves (e.g. a `model_list` entry in a LiteLLM `config.yaml`). Tool-use quality and context handling depend on the model and gateway, so results may differ from Claude.

### Using Vertex AI

Add these variables to your `.env` file to run agents via Google Cloud Vertex AI:
Expand Down
14 changes: 14 additions & 0 deletions registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,20 @@ def get_all_settings() -> dict[str, str]:
],
"default_model": "qwen3-coder",
},
"gemini": {
# Google does not expose an Anthropic Messages-compatible endpoint, so Gemini is
# reached through an Anthropic-compatible gateway such as LiteLLM (default port 4000)
# that translates /v1/messages requests to the Gemini API.
"name": "Google Gemini (via LiteLLM)",
"base_url": "http://localhost:4000",
"requires_auth": True,
"auth_env_var": "ANTHROPIC_AUTH_TOKEN",
"models": [
{"id": "gemini-2.5-pro", "name": "Gemini 2.5 Pro"},
{"id": "gemini-2.5-flash", "name": "Gemini 2.5 Flash"},
],
"default_model": "gemini-2.5-pro",
},
"custom": {
"name": "Custom Provider",
"base_url": "",
Expand Down
44 changes: 44 additions & 0 deletions test_api_providers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""Tests for API provider presets and the SDK environment they produce."""

import pytest

import registry
from registry import API_PROVIDERS, get_effective_sdk_env


@pytest.mark.parametrize("provider_id", list(API_PROVIDERS))
def test_provider_definitions_are_well_formed(provider_id):
provider = API_PROVIDERS[provider_id]
assert provider["name"]
model_ids = [m["id"] for m in provider["models"]]
if provider["default_model"]:
assert provider["default_model"] in model_ids


def test_gemini_provider_uses_gateway_defaults(monkeypatch):
monkeypatch.setattr(registry, "get_all_settings", lambda: {"api_provider": "gemini"})

env = get_effective_sdk_env()

assert env["ANTHROPIC_BASE_URL"] == "http://localhost:4000"
assert env["ANTHROPIC_DEFAULT_OPUS_MODEL"] == "gemini-2.5-pro"
assert env["ANTHROPIC_DEFAULT_SONNET_MODEL"] == "gemini-2.5-pro"
assert env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] == "gemini-2.5-pro"
# Ambient Anthropic/Vertex credentials must not leak into the gateway session
assert env["ANTHROPIC_API_KEY"] == ""
assert env["CLAUDE_CODE_USE_VERTEX"] == ""


def test_gemini_provider_honours_user_overrides(monkeypatch):
monkeypatch.setattr(registry, "get_all_settings", lambda: {
"api_provider": "gemini",
"api_base_url": "https://litellm.internal.example.com",
"api_auth_token": "sk-litellm-master",
"api_model": "gemini-2.5-flash",
})

env = get_effective_sdk_env()

assert env["ANTHROPIC_BASE_URL"] == "https://litellm.internal.example.com"
assert env["ANTHROPIC_AUTH_TOKEN"] == "sk-litellm-master"
assert env["ANTHROPIC_DEFAULT_SONNET_MODEL"] == "gemini-2.5-flash"
7 changes: 4 additions & 3 deletions ui/src/components/SettingsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const PROVIDER_INFO_TEXT: Record<string, string> = {
kimi: 'Get an API key at kimi.com',
glm: 'Get an API key at open.bigmodel.cn',
ollama: 'Run models locally. Install from ollama.com',
gemini: 'Runs through an Anthropic-compatible gateway such as LiteLLM. The gateway holds your Gemini key (aistudio.google.com); enter the gateway key below, if any.',
custom: 'Connect to any OpenAI-compatible API endpoint.',
}

Expand Down Expand Up @@ -116,8 +117,8 @@ 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 showCustomModelInput = currentProvider === 'custom' || currentProvider === 'ollama'
const showBaseUrlField = currentProvider === 'custom' || currentProvider === 'azure' || currentProvider === 'gemini'
const showCustomModelInput = currentProvider === 'custom' || currentProvider === 'ollama' || currentProvider === 'gemini'

return (
<Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
Expand Down Expand Up @@ -332,7 +333,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 === 'gemini' ? 'http://localhost:4000' : 'https://api.example.com/v1'}
className="flex-1 py-1.5 px-3 text-sm border rounded-md bg-background"
/>
<Button
Expand Down
7 changes: 4 additions & 3 deletions ui/src/components/views/SettingsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const PROVIDER_INFO_TEXT: Record<string, string> = {
kimi: 'Get an API key at kimi.com',
glm: 'Get an API key at open.bigmodel.cn',
ollama: 'Run models locally. Install from ollama.com',
gemini: 'Runs through an Anthropic-compatible gateway such as LiteLLM. The gateway holds your Gemini key (aistudio.google.com); enter the gateway key below, if any.',
custom: 'Connect to any OpenAI-compatible API endpoint.',
}

Expand Down Expand Up @@ -113,8 +114,8 @@ 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 showCustomModelInput = currentProvider === 'custom' || currentProvider === 'ollama'
const showBaseUrlField = currentProvider === 'custom' || currentProvider === 'azure' || currentProvider === 'gemini'
const showCustomModelInput = currentProvider === 'custom' || currentProvider === 'ollama' || currentProvider === 'gemini'

return (
<div className="flex-1 overflow-y-auto">
Expand Down Expand Up @@ -341,7 +342,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 === 'gemini' ? 'http://localhost:4000' : 'https://api.example.com/v1'}
className="flex-1 py-1.5 px-3 text-sm border rounded-md bg-background"
/>
<Button
Expand Down