From 946624c3d058ac8f12cd16551577fe95152c8ed8 Mon Sep 17 00:00:00 2001 From: mohamed zwanski Date: Thu, 22 Oct 2026 21:26:04 +0100 Subject: [PATCH] Add Google Gemini as an API provider via Anthropic-compatible gateway (#94) Adds a 'gemini' preset to API_PROVIDERS that points the Claude Agent SDK at an Anthropic-compatible gateway (LiteLLM, default localhost:4000) which translates requests to the Gemini API. The settings UI exposes an editable base URL and custom model name for it. Documents setup in README, CLAUDE.md and .env.example, and adds provider env tests. Co-Authored-By: Claude Opus 5.5 --- .env.example | 10 +++++- CLAUDE.md | 9 +++-- README.md | 14 ++++++-- registry.py | 14 ++++++++ test_api_providers.py | 44 ++++++++++++++++++++++++ ui/src/components/SettingsModal.tsx | 7 ++-- ui/src/components/views/SettingsView.tsx | 7 ++-- 7 files changed, 94 insertions(+), 11 deletions(-) create mode 100644 test_api_providers.py diff --git a/.env.example b/.env.example index ed163bacf..2050a128f 100644 --- a/.env.example +++ b/.env.example @@ -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. @@ -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 diff --git a/CLAUDE.md b/CLAUDE.md index 6f59910be..11a8b41b0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -418,11 +418,11 @@ 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 @@ -430,6 +430,11 @@ Alternative providers are configured via the **Settings UI** (gear icon > API Pr - 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/`): diff --git a/README.md b/README.md index 0ad71c941..009002fdb 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/registry.py b/registry.py index 6255d431d..422d0f732 100644 --- a/registry.py +++ b/registry.py @@ -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": "", diff --git a/test_api_providers.py b/test_api_providers.py new file mode 100644 index 000000000..c8b8ce6dc --- /dev/null +++ b/test_api_providers.py @@ -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" diff --git a/ui/src/components/SettingsModal.tsx b/ui/src/components/SettingsModal.tsx index 5605f4e63..3d79195f4 100644 --- a/ui/src/components/SettingsModal.tsx +++ b/ui/src/components/SettingsModal.tsx @@ -25,6 +25,7 @@ const PROVIDER_INFO_TEXT: Record = { 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.', } @@ -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 ( !open && onClose()}> @@ -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" />