diff --git a/.env.example b/.env.example index 79bdeb9..668843a 100644 --- a/.env.example +++ b/.env.example @@ -18,14 +18,10 @@ KIMI_API_KEY="" WAFER_API_KEY="" -# OpenCode Zen Config (OpenAI-compatible Chat Completions at opencode.ai/zen/v1) +# OpenCode Zen (opencode.ai/zen/v1) and OpenCode Go (opencode.ai/zen/go/v1) share OPENCODE_API_KEY OPENCODE_API_KEY="" -# OpenCode Go Config (OpenAI-compatible Chat Completions at opencode.ai/zen/go/v1) -OPENCODE_GO_API_KEY="" - - # Z.ai Config (Anthropic-compatible Messages at api.z.ai/api/anthropic) ZAI_API_KEY="" diff --git a/README.md b/README.md index 26a17f1..2107cc4 100644 --- a/README.md +++ b/README.md @@ -207,7 +207,7 @@ In the Admin UI, keep or update `OLLAMA_BASE_URL`, then set `MODEL` to the same Get an API key at [opencode.ai/auth](https://opencode.ai/auth). -In the Admin UI, paste it into `OPENCODE_API_KEY`, then set `MODEL` to an OpenCode Zen model slug such as `opencode/gpt-5.3-codex`. +In the Admin UI, paste it into `OPENCODE_API_KEY`, then set `MODEL` to an OpenCode Zen model slug such as `opencode/gpt-5.3-codex`. The same `OPENCODE_API_KEY` powers **OpenCode Go** (below); use `opencode_go/` slugs there. OpenCode Zen is a curated model gateway that provides access to models from Anthropic, OpenAI, Google, DeepSeek, and more through a single API key and OpenAI-compatible endpoint at `https://opencode.ai/zen/v1`. @@ -224,15 +224,15 @@ Browse available models at [opencode.ai](https://opencode.ai). ### 10. [OpenCode Go](https://opencode.ai/) -Get an API key at [opencode.ai/auth](https://opencode.ai/auth). +Get an API key at [opencode.ai/auth](https://opencode.ai/auth) (same as OpenCode Zen). -In the Admin UI, paste it into `OPENCODE_GO_API_KEY`, then set `MODEL` to an OpenCode Go model slug such as `opencode_go/gpt-5.3-codex`. +In the Admin UI, use `OPENCODE_API_KEY`, then set `MODEL` to an OpenCode Go model slug such as `opencode_go/minimax-m2.7`. -OpenCode Go is a separate subscription gateway from OpenCode Zen. It uses an OpenAI-compatible endpoint at `https://opencode.ai/zen/go/v1` and its own API key (`OPENCODE_GO_API_KEY`), distinct from `OPENCODE_API_KEY`. +OpenCode Go is a subscription gateway with its own curated catalog and OpenAI-compatible endpoint at `https://opencode.ai/zen/go/v1`. It shares the **same OpenCode API key** as Zen; only the slug prefix (`opencode_go/` vs `opencode/`) and upstream path differ. Popular examples: -- `opencode_go/gpt-5.3-codex` +- `opencode_go/minimax-m2.7` Browse available models at [opencode.ai](https://opencode.ai). diff --git a/api/admin_config.py b/api/admin_config.py index 5ac6c2b..ef64a4a 100644 --- a/api/admin_config.py +++ b/api/admin_config.py @@ -160,21 +160,15 @@ FIELDS: tuple[ConfigFieldSpec, ...] = ( ), ConfigFieldSpec( "OPENCODE_API_KEY", - "OpenCode Zen API Key", + "OpenCode API Key", "providers", "secret", settings_attr="opencode_api_key", secret=True, - description="OpenCode Zen curated model gateway at opencode.ai.", - ), - ConfigFieldSpec( - "OPENCODE_GO_API_KEY", - "OpenCode Go API Key", - "providers", - "secret", - settings_attr="opencode_go_api_key", - secret=True, - description="OpenCode Go subscription gateway at opencode.ai.", + description=( + "OpenCode Zen curated gateway (opencode.ai/zen/v1) and OpenCode Go subscription " + "gateway (opencode.ai/zen/go/v1); single key from opencode.ai/auth." + ), ), ConfigFieldSpec( "ZAI_API_KEY", diff --git a/config/provider_catalog.py b/config/provider_catalog.py index 988a202..0a77db6 100644 --- a/config/provider_catalog.py +++ b/config/provider_catalog.py @@ -141,9 +141,9 @@ PROVIDER_CATALOG: dict[str, ProviderDescriptor] = { "opencode_go": ProviderDescriptor( provider_id="opencode_go", transport_type="openai_chat", - credential_env="OPENCODE_GO_API_KEY", + credential_env="OPENCODE_API_KEY", credential_url="https://opencode.ai/auth", - credential_attr="opencode_go_api_key", + credential_attr="opencode_api_key", default_base_url=OPENCODE_GO_DEFAULT_BASE, proxy_attr="opencode_go_proxy", capabilities=("chat", "streaming", "tools", "thinking", "rate_limit"), diff --git a/config/settings.py b/config/settings.py index e6fc704..37e7479 100644 --- a/config/settings.py +++ b/config/settings.py @@ -119,12 +119,10 @@ class Settings(BaseSettings): # ==================== Wafer Config ==================== wafer_api_key: str = Field(default="", validation_alias="WAFER_API_KEY") - # ==================== OpenCode Zen Config ==================== + # ==================== OpenCode Zen / OpenCode Go ==================== + # Same key from opencode.ai/auth; zen uses prefix ``opencode/``, Go uses ``opencode_go/``. opencode_api_key: str = Field(default="", validation_alias="OPENCODE_API_KEY") - # ==================== OpenCode Go Config ==================== - opencode_go_api_key: str = Field(default="", validation_alias="OPENCODE_GO_API_KEY") - # ==================== Z.ai Config ==================== zai_api_key: str = Field(default="", validation_alias="ZAI_API_KEY") diff --git a/smoke/lib/config.py b/smoke/lib/config.py index 861a8d6..dd568d5 100644 --- a/smoke/lib/config.py +++ b/smoke/lib/config.py @@ -50,7 +50,7 @@ PROVIDER_SMOKE_DEFAULT_MODELS: dict[str, str] = { "ollama": "ollama/llama3.1", "wafer": "wafer/DeepSeek-V4-Pro", "opencode": "opencode/gpt-5.3-codex", - "opencode_go": "opencode_go/gpt-5.3-codex", + "opencode_go": "opencode_go/minimax-m2.7", "zai": "zai/glm-5.1", } diff --git a/tests/api/test_dependencies.py b/tests/api/test_dependencies.py index b7711aa..b7566de 100644 --- a/tests/api/test_dependencies.py +++ b/tests/api/test_dependencies.py @@ -38,7 +38,6 @@ def _make_mock_settings(**overrides): mock.deepseek_api_key = "test_deepseek_key" mock.wafer_api_key = "test_wafer_key" mock.opencode_api_key = "test_opencode_key" - mock.opencode_go_api_key = "test_opencode_go_key" mock.zai_api_key = "test_zai_key" mock.lm_studio_base_url = "http://localhost:1234/v1" mock.ollama_base_url = "http://localhost:11434" diff --git a/tests/contracts/test_smoke_config.py b/tests/contracts/test_smoke_config.py index f9ff22d..b54c6ea 100644 --- a/tests/contracts/test_smoke_config.py +++ b/tests/contracts/test_smoke_config.py @@ -28,7 +28,6 @@ def _settings(**overrides): "deepseek_api_key": "", "wafer_api_key": "", "opencode_api_key": "", - "opencode_go_api_key": "", "zai_api_key": "", "lm_studio_base_url": "", "llamacpp_base_url": "", diff --git a/tests/providers/test_model_validation.py b/tests/providers/test_model_validation.py index 32055c1..2a3d781 100644 --- a/tests/providers/test_model_validation.py +++ b/tests/providers/test_model_validation.py @@ -34,7 +34,6 @@ def _settings( deepseek_api_key: str = "", wafer_api_key: str = "", opencode_api_key: str = "", - opencode_go_api_key: str = "", zai_api_key: str = "", ) -> Settings: return Settings.model_construct( @@ -47,7 +46,6 @@ def _settings( deepseek_api_key=deepseek_api_key, wafer_api_key=wafer_api_key, opencode_api_key=opencode_api_key, - opencode_go_api_key=opencode_go_api_key, zai_api_key=zai_api_key, log_api_error_tracebacks=False, ) diff --git a/tests/providers/test_registry.py b/tests/providers/test_registry.py index ff9ab41..6999c47 100644 --- a/tests/providers/test_registry.py +++ b/tests/providers/test_registry.py @@ -5,7 +5,7 @@ from unittest.mock import AsyncMock, MagicMock, patch import pytest from config.nim import NimSettings -from config.provider_catalog import ZAI_DEFAULT_BASE +from config.provider_catalog import PROVIDER_CATALOG, ZAI_DEFAULT_BASE from config.provider_ids import SUPPORTED_PROVIDER_IDS from providers.deepseek import DeepSeekProvider from providers.exceptions import UnknownProviderTypeError @@ -34,7 +34,6 @@ def _make_settings(**overrides): mock.deepseek_api_key = "test_deepseek_key" mock.wafer_api_key = "test_wafer_key" mock.opencode_api_key = "test_opencode_key" - mock.opencode_go_api_key = "test_opencode_go_key" mock.zai_api_key = "test_zai_key" mock.lm_studio_base_url = "http://localhost:1234/v1" mock.llamacpp_base_url = "http://localhost:8080/v1" @@ -118,7 +117,23 @@ def test_opencode_go_provider_config_uses_correct_base_url_and_name(): assert isinstance(provider, OpenCodeProvider) assert provider._base_url == "https://opencode.ai/zen/go/v1" assert provider._provider_name == "OPENCODE_GO" - assert provider._api_key == "test_opencode_go_key" + assert provider._api_key == "test_opencode_key" + + +def test_opencode_go_catalog_uses_opencode_api_key() -> None: + desc = PROVIDER_CATALOG["opencode_go"] + + assert desc.credential_env == "OPENCODE_API_KEY" + assert desc.credential_attr == "opencode_api_key" + + +def test_build_provider_config_opencode_go_uses_opencode_api_key() -> None: + descriptor = PROVIDER_CATALOG["opencode_go"] + settings = _make_settings(opencode_api_key="shared-opencode-token") + + config = build_provider_config(descriptor, settings) + + assert config.api_key == "shared-opencode-token" def test_create_provider_uses_native_openrouter_by_default():