mirror of
https://github.com/Alishahryar1/free-claude-code.git
synced 2026-06-02 06:13:46 +02:00
Add OpenCode Go subscription gateway provider (#505)
## Summary Adds support for the **OpenCode Go** subscription gateway at `opencode.ai/zen/go/v1`, as requested in #504. OpenCode Go exposes the same OpenAI-compatible Chat Completions API as OpenCode Zen, so the implementation reuses `OpenCodeProvider` with a configurable `provider_name` parameter — avoiding code duplication. ### Changes - **Provider**: `OpenCodeProvider` now accepts `provider_name` (defaults to `"OPENCODE"` for backward compatibility) - **Catalog**: New `opencode_go` descriptor with correct base URL, credential, and capabilities - **Registry**: `_create_opencode_go` factory that passes `provider_name="OPENCODE_GO"` - **Settings**: `opencode_go_api_key` and `opencode_go_proxy` fields - **Admin UI**: OpenCode Go API key, proxy, and smoke model config fields - **API services**: `opencode_go` added to OpenAI Chat Completions upstream IDs - **Smoke config**: Default smoke model `opencode_go/gpt-5.3-codex` - **Tests**: New test for base URL, provider name, and API key; existing tests updated ## Test plan - [x] `test_opencode_go_provider_config_uses_correct_base_url_and_name` — passes - [x] `test_create_provider_instantiates_each_builtin` — covers opencode_go - [x] `test_provider_and_platform_registries_include_advertised_builtins` — covers opencode_go - [x] `uv run ruff format`, `ruff check`, `ty check`, `pytest` — pass locally on Python 3.14 Closes #504. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: Alishahryar1 <alishahryar2@gmail.com>
This commit is contained in:
+7
-1
@@ -22,6 +22,10 @@ WAFER_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=""
|
||||
|
||||
@@ -44,7 +48,7 @@ OLLAMA_BASE_URL="http://localhost:11434"
|
||||
|
||||
# All Claude model requests are mapped to these models, plain model is fallback
|
||||
# Format: provider_type/model/name
|
||||
# Valid providers: "nvidia_nim" | "open_router" | "deepseek" | "lmstudio" | "llamacpp" | "ollama" | "kimi" | "wafer" | "opencode" | "zai" | "fireworks"
|
||||
# Valid providers: "nvidia_nim" | "open_router" | "deepseek" | "lmstudio" | "llamacpp" | "ollama" | "kimi" | "wafer" | "opencode" | "opencode_go" | "zai" | "fireworks"
|
||||
MODEL_OPUS=
|
||||
MODEL_SONNET=
|
||||
MODEL_HAIKU=
|
||||
@@ -62,6 +66,7 @@ FCC_SMOKE_MODEL_OLLAMA=
|
||||
FCC_SMOKE_MODEL_KIMI=
|
||||
FCC_SMOKE_MODEL_WAFER=
|
||||
FCC_SMOKE_MODEL_OPENCODE=
|
||||
FCC_SMOKE_MODEL_OPENCODE_GO=
|
||||
FCC_SMOKE_MODEL_ZAI=
|
||||
FCC_SMOKE_MODEL_FIREWORKS=
|
||||
FCC_SMOKE_NIM_MODELS=
|
||||
@@ -88,6 +93,7 @@ LLAMACPP_PROXY=""
|
||||
KIMI_PROXY=""
|
||||
WAFER_PROXY=""
|
||||
OPENCODE_PROXY=""
|
||||
OPENCODE_GO_PROXY=""
|
||||
ZAI_PROXY=""
|
||||
FIREWORKS_PROXY=""
|
||||
|
||||
|
||||
@@ -167,6 +167,15 @@ FIELDS: tuple[ConfigFieldSpec, ...] = (
|
||||
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.",
|
||||
),
|
||||
ConfigFieldSpec(
|
||||
"ZAI_API_KEY",
|
||||
"Z.ai API Key",
|
||||
@@ -269,6 +278,15 @@ FIELDS: tuple[ConfigFieldSpec, ...] = (
|
||||
secret=True,
|
||||
advanced=True,
|
||||
),
|
||||
ConfigFieldSpec(
|
||||
"OPENCODE_GO_PROXY",
|
||||
"OpenCode Go Proxy",
|
||||
"providers",
|
||||
"secret",
|
||||
settings_attr="opencode_go_proxy",
|
||||
secret=True,
|
||||
advanced=True,
|
||||
),
|
||||
ConfigFieldSpec(
|
||||
"ZAI_PROXY",
|
||||
"Z.ai Proxy",
|
||||
@@ -729,6 +747,12 @@ FIELDS: tuple[ConfigFieldSpec, ...] = (
|
||||
"smoke",
|
||||
advanced=True,
|
||||
),
|
||||
ConfigFieldSpec(
|
||||
"FCC_SMOKE_MODEL_OPENCODE_GO",
|
||||
"Smoke OpenCode Go Model",
|
||||
"smoke",
|
||||
advanced=True,
|
||||
),
|
||||
ConfigFieldSpec(
|
||||
"FCC_SMOKE_MODEL_ZAI",
|
||||
"Smoke Z.ai Model",
|
||||
|
||||
@@ -68,6 +68,7 @@ function providerName(providerId) {
|
||||
kimi: "Kimi",
|
||||
wafer: "Wafer",
|
||||
opencode: "OpenCode Zen",
|
||||
opencode_go: "OpenCode Go",
|
||||
zai: "Z.ai",
|
||||
};
|
||||
if (names[providerId]) return names[providerId];
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ TokenCounter = Callable[[list[Any], str | list[Any] | None, list[Any] | None], i
|
||||
ProviderGetter = Callable[[str], BaseProvider]
|
||||
|
||||
# Providers that use ``/chat/completions`` + Anthropic-to-OpenAI conversion (not native Messages).
|
||||
_OPENAI_CHAT_UPSTREAM_IDS = frozenset({"nvidia_nim", "opencode", "zai"})
|
||||
_OPENAI_CHAT_UPSTREAM_IDS = frozenset({"nvidia_nim", "opencode", "opencode_go", "zai"})
|
||||
|
||||
|
||||
def anthropic_sse_streaming_response(
|
||||
|
||||
@@ -25,6 +25,7 @@ LMSTUDIO_DEFAULT_BASE = "http://localhost:1234/v1"
|
||||
LLAMACPP_DEFAULT_BASE = "http://localhost:8080/v1"
|
||||
OLLAMA_DEFAULT_BASE = "http://localhost:11434"
|
||||
OPENCODE_DEFAULT_BASE = "https://opencode.ai/zen/v1"
|
||||
OPENCODE_GO_DEFAULT_BASE = "https://opencode.ai/zen/go/v1"
|
||||
ZAI_DEFAULT_BASE = "https://api.z.ai/api/coding/paas/v4"
|
||||
|
||||
|
||||
@@ -137,6 +138,16 @@ PROVIDER_CATALOG: dict[str, ProviderDescriptor] = {
|
||||
proxy_attr="opencode_proxy",
|
||||
capabilities=("chat", "streaming", "tools", "thinking", "rate_limit"),
|
||||
),
|
||||
"opencode_go": ProviderDescriptor(
|
||||
provider_id="opencode_go",
|
||||
transport_type="openai_chat",
|
||||
credential_env="OPENCODE_GO_API_KEY",
|
||||
credential_url="https://opencode.ai/auth",
|
||||
credential_attr="opencode_go_api_key",
|
||||
default_base_url=OPENCODE_GO_DEFAULT_BASE,
|
||||
proxy_attr="opencode_go_proxy",
|
||||
capabilities=("chat", "streaming", "tools", "thinking", "rate_limit"),
|
||||
),
|
||||
"zai": ProviderDescriptor(
|
||||
provider_id="zai",
|
||||
transport_type="openai_chat",
|
||||
|
||||
@@ -122,6 +122,9 @@ class Settings(BaseSettings):
|
||||
# ==================== OpenCode Zen Config ====================
|
||||
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")
|
||||
|
||||
@@ -180,6 +183,7 @@ class Settings(BaseSettings):
|
||||
kimi_proxy: str = Field(default="", validation_alias="KIMI_PROXY")
|
||||
wafer_proxy: str = Field(default="", validation_alias="WAFER_PROXY")
|
||||
opencode_proxy: str = Field(default="", validation_alias="OPENCODE_PROXY")
|
||||
opencode_go_proxy: str = Field(default="", validation_alias="OPENCODE_GO_PROXY")
|
||||
zai_proxy: str = Field(default="", validation_alias="ZAI_PROXY")
|
||||
fireworks_proxy: str = Field(default="", validation_alias="FIREWORKS_PROXY")
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ from config.provider_catalog import (
|
||||
NVIDIA_NIM_DEFAULT_BASE,
|
||||
OLLAMA_DEFAULT_BASE,
|
||||
OPENCODE_DEFAULT_BASE,
|
||||
OPENCODE_GO_DEFAULT_BASE,
|
||||
OPENROUTER_DEFAULT_BASE,
|
||||
WAFER_DEFAULT_BASE,
|
||||
ZAI_DEFAULT_BASE,
|
||||
@@ -23,6 +24,7 @@ __all__ = (
|
||||
"NVIDIA_NIM_DEFAULT_BASE",
|
||||
"OLLAMA_DEFAULT_BASE",
|
||||
"OPENCODE_DEFAULT_BASE",
|
||||
"OPENCODE_GO_DEFAULT_BASE",
|
||||
"OPENROUTER_DEFAULT_BASE",
|
||||
"WAFER_DEFAULT_BASE",
|
||||
"ZAI_DEFAULT_BASE",
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
"""OpenCode Zen provider exports."""
|
||||
|
||||
from providers.defaults import OPENCODE_DEFAULT_BASE
|
||||
from providers.defaults import OPENCODE_DEFAULT_BASE, OPENCODE_GO_DEFAULT_BASE
|
||||
|
||||
from .client import OpenCodeProvider
|
||||
|
||||
__all__ = [
|
||||
"OPENCODE_DEFAULT_BASE",
|
||||
"OPENCODE_GO_DEFAULT_BASE",
|
||||
"OpenCodeProvider",
|
||||
]
|
||||
|
||||
@@ -14,10 +14,10 @@ from .request import build_request_body
|
||||
class OpenCodeProvider(OpenAIChatTransport):
|
||||
"""OpenCode Zen provider using ``https://opencode.ai/zen/v1/chat/completions``."""
|
||||
|
||||
def __init__(self, config: ProviderConfig):
|
||||
def __init__(self, config: ProviderConfig, provider_name: str = "OPENCODE"):
|
||||
super().__init__(
|
||||
config,
|
||||
provider_name="OPENCODE",
|
||||
provider_name=provider_name,
|
||||
base_url=config.base_url or OPENCODE_DEFAULT_BASE,
|
||||
api_key=config.api_key,
|
||||
)
|
||||
|
||||
@@ -86,6 +86,12 @@ def _create_opencode(config: ProviderConfig, _settings: Settings) -> BaseProvide
|
||||
return OpenCodeProvider(config)
|
||||
|
||||
|
||||
def _create_opencode_go(config: ProviderConfig, _settings: Settings) -> BaseProvider:
|
||||
from providers.opencode import OpenCodeProvider
|
||||
|
||||
return OpenCodeProvider(config, provider_name="OPENCODE_GO")
|
||||
|
||||
|
||||
def _create_zai(config: ProviderConfig, _settings: Settings) -> BaseProvider:
|
||||
from providers.zai import ZaiProvider
|
||||
|
||||
@@ -108,6 +114,7 @@ PROVIDER_FACTORIES: dict[str, ProviderFactory] = {
|
||||
"kimi": _create_kimi,
|
||||
"wafer": _create_wafer,
|
||||
"opencode": _create_opencode,
|
||||
"opencode_go": _create_opencode_go,
|
||||
"zai": _create_zai,
|
||||
"fireworks": _create_fireworks,
|
||||
}
|
||||
|
||||
@@ -50,6 +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",
|
||||
"zai": "zai/glm-5.1",
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ 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"
|
||||
@@ -46,6 +47,7 @@ def _make_mock_settings(**overrides):
|
||||
mock.kimi_proxy = ""
|
||||
mock.wafer_proxy = ""
|
||||
mock.opencode_proxy = ""
|
||||
mock.opencode_go_proxy = ""
|
||||
mock.zai_proxy = ""
|
||||
mock.nim = NimSettings()
|
||||
mock.http_read_timeout = 300.0
|
||||
|
||||
@@ -78,6 +78,7 @@ def test_provider_and_platform_registries_include_advertised_builtins() -> None:
|
||||
"ollama": OllamaProvider,
|
||||
"wafer": WaferProvider,
|
||||
"opencode": OpenCodeProvider,
|
||||
"opencode_go": OpenCodeProvider,
|
||||
"zai": ZaiProvider,
|
||||
}
|
||||
for provider_class in provider_classes.values():
|
||||
|
||||
@@ -28,6 +28,7 @@ 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": "",
|
||||
|
||||
@@ -34,6 +34,7 @@ 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(
|
||||
@@ -46,6 +47,7 @@ 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,
|
||||
)
|
||||
|
||||
@@ -34,6 +34,7 @@ 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"
|
||||
@@ -45,6 +46,7 @@ def _make_settings(**overrides):
|
||||
mock.kimi_proxy = ""
|
||||
mock.wafer_proxy = ""
|
||||
mock.opencode_proxy = ""
|
||||
mock.opencode_go_proxy = ""
|
||||
mock.zai_proxy = ""
|
||||
mock.provider_rate_limit = 40
|
||||
mock.provider_rate_window = 60
|
||||
@@ -109,6 +111,16 @@ def test_zai_provider_config_ignores_stale_base_url_setting():
|
||||
assert config.base_url == ZAI_DEFAULT_BASE
|
||||
|
||||
|
||||
def test_opencode_go_provider_config_uses_correct_base_url_and_name():
|
||||
with patch("httpx.AsyncClient"):
|
||||
provider = create_provider("opencode_go", _make_settings())
|
||||
|
||||
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"
|
||||
|
||||
|
||||
def test_create_provider_uses_native_openrouter_by_default():
|
||||
with patch("httpx.AsyncClient"):
|
||||
provider = create_provider("open_router", _make_settings())
|
||||
@@ -126,6 +138,7 @@ def test_create_provider_instantiates_each_builtin():
|
||||
"ollama": OllamaProvider,
|
||||
"wafer": WaferProvider,
|
||||
"opencode": OpenCodeProvider,
|
||||
"opencode_go": OpenCodeProvider,
|
||||
"zai": ZaiProvider,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user