From 40e2f8d9f0df6bfceccd16ce27df561395beadd1 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Tue, 17 Mar 2026 02:02:43 -0700 Subject: [PATCH] feat(provider): add OpenCode Zen and OpenCode Go providers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for OpenCode Zen (pay-as-you-go, 35+ curated models) and OpenCode Go ($10/month subscription, open models) as first-class providers. Both are OpenAI-compatible endpoints resolved via the generic api_key provider flow — no custom adapter needed. Files changed: - hermes_cli/auth.py — ProviderConfig entries + aliases - hermes_cli/config.py — OPENCODE_ZEN/GO API key env vars - hermes_cli/models.py — model catalogs, labels, aliases, provider order - hermes_cli/main.py — provider labels, menu entries, model flow dispatch - hermes_cli/setup.py — setup wizard branches (idx 10, 11) - agent/model_metadata.py — context lengths for all OpenCode models - agent/auxiliary_client.py — default aux models - .env.example — documentation Co-authored-by: DevAgarwal2 --- .env.example | 16 +++++++++ agent/auxiliary_client.py | 2 ++ agent/model_metadata.py | 36 ++++++++++++++++++++ hermes_cli/auth.py | 18 ++++++++++ hermes_cli/config.py | 32 ++++++++++++++++++ hermes_cli/main.py | 6 +++- hermes_cli/models.py | 49 +++++++++++++++++++++++++++ hermes_cli/setup.py | 70 ++++++++++++++++++++++++++++++++++++++- 8 files changed, 227 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index fb215afb..638610e4 100644 --- a/.env.example +++ b/.env.example @@ -45,6 +45,22 @@ MINIMAX_API_KEY= MINIMAX_CN_API_KEY= # MINIMAX_CN_BASE_URL=https://api.minimaxi.com/v1 # Override default base URL +# ============================================================================= +# LLM PROVIDER (OpenCode Zen) +# ============================================================================= +# OpenCode Zen provides curated, tested models (GPT, Claude, Gemini, MiniMax, GLM, Kimi) +# Pay-as-you-go pricing. Get your key at: https://opencode.ai/auth +OPENCODE_ZEN_API_KEY= +# OPENCODE_ZEN_BASE_URL=https://opencode.ai/zen/v1 # Override default base URL + +# ============================================================================= +# LLM PROVIDER (OpenCode Go) +# ============================================================================= +# OpenCode Go provides access to open models (GLM-5, Kimi K2.5, MiniMax M2.5) +# $10/month subscription. Get your key at: https://opencode.ai/auth +OPENCODE_GO_API_KEY= +# OPENCODE_GO_BASE_URL=https://opencode.ai/zen/go/v1 # Override default base URL + # ============================================================================= # TOOL API KEYS # ============================================================================= diff --git a/agent/auxiliary_client.py b/agent/auxiliary_client.py index cf740bc8..17fab37f 100644 --- a/agent/auxiliary_client.py +++ b/agent/auxiliary_client.py @@ -58,6 +58,8 @@ _API_KEY_PROVIDER_AUX_MODELS: Dict[str, str] = { "minimax-cn": "MiniMax-M2.5-highspeed", "anthropic": "claude-haiku-4-5-20251001", "ai-gateway": "google/gemini-3-flash", + "opencode-zen": "gemini-3-flash", + "opencode-go": "glm-5", } # OpenRouter app attribution headers diff --git a/agent/model_metadata.py b/agent/model_metadata.py index 755bc81b..ae7abb56 100644 --- a/agent/model_metadata.py +++ b/agent/model_metadata.py @@ -80,6 +80,42 @@ DEFAULT_CONTEXT_LENGTHS = { "MiniMax-M2.5": 204800, "MiniMax-M2.5-highspeed": 204800, "MiniMax-M2.1": 204800, + # OpenCode Zen models + "gpt-5.4-pro": 128000, + "gpt-5.4": 128000, + "gpt-5.3-codex": 128000, + "gpt-5.3-codex-spark": 128000, + "gpt-5.2": 128000, + "gpt-5.2-codex": 128000, + "gpt-5.1": 128000, + "gpt-5.1-codex": 128000, + "gpt-5.1-codex-max": 128000, + "gpt-5.1-codex-mini": 128000, + "gpt-5": 128000, + "gpt-5-codex": 128000, + "gpt-5-nano": 128000, + "claude-opus-4-6": 200000, + "claude-opus-4-5": 200000, + "claude-opus-4-1": 200000, + "claude-sonnet-4-6": 200000, + "claude-sonnet-4-5": 200000, + "claude-sonnet-4": 200000, + "claude-haiku-4-5": 200000, + "claude-3-5-haiku": 200000, + "gemini-3.1-pro": 1048576, + "gemini-3-pro": 1048576, + "gemini-3-flash": 1048576, + "minimax-m2.5": 204800, + "minimax-m2.5-free": 204800, + "minimax-m2.1": 204800, + "glm-5": 202752, + "glm-4.7": 202752, + "glm-4.6": 202752, + "kimi-k2.5": 262144, + "kimi-k2-thinking": 262144, + "kimi-k2": 262144, + "qwen3-coder": 32768, + "big-pickle": 128000, } diff --git a/hermes_cli/auth.py b/hermes_cli/auth.py index c5d20082..9dc50e2e 100644 --- a/hermes_cli/auth.py +++ b/hermes_cli/auth.py @@ -163,6 +163,22 @@ PROVIDER_REGISTRY: Dict[str, ProviderConfig] = { api_key_env_vars=("AI_GATEWAY_API_KEY",), base_url_env_var="AI_GATEWAY_BASE_URL", ), + "opencode-zen": ProviderConfig( + id="opencode-zen", + name="OpenCode Zen", + auth_type="api_key", + inference_base_url="https://opencode.ai/zen/v1", + api_key_env_vars=("OPENCODE_ZEN_API_KEY",), + base_url_env_var="OPENCODE_ZEN_BASE_URL", + ), + "opencode-go": ProviderConfig( + id="opencode-go", + name="OpenCode Go", + auth_type="api_key", + inference_base_url="https://opencode.ai/zen/go/v1", + api_key_env_vars=("OPENCODE_GO_API_KEY",), + base_url_env_var="OPENCODE_GO_BASE_URL", + ), } @@ -541,6 +557,8 @@ def resolve_provider( "minimax-china": "minimax-cn", "minimax_cn": "minimax-cn", "claude": "anthropic", "claude-code": "anthropic", "aigateway": "ai-gateway", "vercel": "ai-gateway", "vercel-ai-gateway": "ai-gateway", + "opencode": "opencode-zen", "zen": "opencode-zen", + "go": "opencode-go", "opencode-go-sub": "opencode-go", } normalized = _PROVIDER_ALIASES.get(normalized, normalized) diff --git a/hermes_cli/config.py b/hermes_cli/config.py index e1a20ac5..672cff8f 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -485,6 +485,38 @@ OPTIONAL_ENV_VARS = { "password": False, "category": "provider", }, + "OPENCODE_ZEN_API_KEY": { + "description": "OpenCode Zen API key (pay-as-you-go access to curated models)", + "prompt": "OpenCode Zen API key", + "url": "https://opencode.ai/auth", + "password": True, + "category": "provider", + "advanced": True, + }, + "OPENCODE_ZEN_BASE_URL": { + "description": "OpenCode Zen base URL override", + "prompt": "OpenCode Zen base URL (leave empty for default)", + "url": None, + "password": False, + "category": "provider", + "advanced": True, + }, + "OPENCODE_GO_API_KEY": { + "description": "OpenCode Go API key ($10/month subscription for open models)", + "prompt": "OpenCode Go API key", + "url": "https://opencode.ai/auth", + "password": True, + "category": "provider", + "advanced": True, + }, + "OPENCODE_GO_BASE_URL": { + "description": "OpenCode Go base URL override", + "prompt": "OpenCode Go base URL (leave empty for default)", + "url": None, + "password": False, + "category": "provider", + "advanced": True, + }, # ── Tool API keys ── "FIRECRAWL_API_KEY": { diff --git a/hermes_cli/main.py b/hermes_cli/main.py index 4e1b060f..266a4cee 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -768,6 +768,8 @@ def cmd_model(args): "kimi-coding": "Kimi / Moonshot", "minimax": "MiniMax", "minimax-cn": "MiniMax (China)", + "opencode-zen": "OpenCode Zen", + "opencode-go": "OpenCode Go", "ai-gateway": "AI Gateway", "custom": "Custom endpoint", } @@ -788,6 +790,8 @@ def cmd_model(args): ("kimi-coding", "Kimi / Moonshot (Moonshot AI direct API)"), ("minimax", "MiniMax (global direct API)"), ("minimax-cn", "MiniMax China (domestic direct API)"), + ("opencode-zen", "OpenCode Zen (35+ curated models, pay-as-you-go)"), + ("opencode-go", "OpenCode Go (open models, $10/month subscription)"), ("ai-gateway", "AI Gateway (Vercel — 200+ models, pay-per-use)"), ] @@ -857,7 +861,7 @@ def cmd_model(args): _model_flow_anthropic(config, current_model) elif selected_provider == "kimi-coding": _model_flow_kimi(config, current_model) - elif selected_provider in ("zai", "minimax", "minimax-cn", "ai-gateway"): + elif selected_provider in ("zai", "minimax", "minimax-cn", "opencode-zen", "opencode-go", "ai-gateway"): _model_flow_api_key_provider(config, selected_provider, current_model) diff --git a/hermes_cli/models.py b/hermes_cli/models.py index bd346376..0d905205 100644 --- a/hermes_cli/models.py +++ b/hermes_cli/models.py @@ -83,6 +83,48 @@ _PROVIDER_MODELS: dict[str, list[str]] = { "deepseek-chat", "deepseek-reasoner", ], + "opencode-zen": [ + "gpt-5.4-pro", + "gpt-5.4", + "gpt-5.3-codex", + "gpt-5.3-codex-spark", + "gpt-5.2", + "gpt-5.2-codex", + "gpt-5.1", + "gpt-5.1-codex", + "gpt-5.1-codex-max", + "gpt-5.1-codex-mini", + "gpt-5", + "gpt-5-codex", + "gpt-5-nano", + "claude-opus-4-6", + "claude-opus-4-5", + "claude-opus-4-1", + "claude-sonnet-4-6", + "claude-sonnet-4-5", + "claude-sonnet-4", + "claude-haiku-4-5", + "claude-3-5-haiku", + "gemini-3.1-pro", + "gemini-3-pro", + "gemini-3-flash", + "minimax-m2.5", + "minimax-m2.5-free", + "minimax-m2.1", + "glm-5", + "glm-4.7", + "glm-4.6", + "kimi-k2.5", + "kimi-k2-thinking", + "kimi-k2", + "qwen3-coder", + "big-pickle", + ], + "opencode-go": [ + "glm-5", + "kimi-k2.5", + "minimax-m2.5", + ], "ai-gateway": [ "anthropic/claude-opus-4.6", "anthropic/claude-sonnet-4.6", @@ -109,6 +151,8 @@ _PROVIDER_LABELS = { "minimax-cn": "MiniMax (China)", "anthropic": "Anthropic", "deepseek": "DeepSeek", + "opencode-zen": "OpenCode Zen", + "opencode-go": "OpenCode Go", "ai-gateway": "AI Gateway", "custom": "Custom endpoint", } @@ -125,6 +169,10 @@ _PROVIDER_ALIASES = { "claude": "anthropic", "claude-code": "anthropic", "deep-seek": "deepseek", + "opencode": "opencode-zen", + "zen": "opencode-zen", + "go": "opencode-go", + "opencode-go-sub": "opencode-go", "aigateway": "ai-gateway", "vercel": "ai-gateway", "vercel-ai-gateway": "ai-gateway", @@ -162,6 +210,7 @@ def list_available_providers() -> list[dict[str, str]]: _PROVIDER_ORDER = [ "openrouter", "nous", "openai-codex", "zai", "kimi-coding", "minimax", "minimax-cn", "anthropic", + "opencode-zen", "opencode-go", "ai-gateway", "deepseek", "custom", ] # Build reverse alias map diff --git a/hermes_cli/setup.py b/hermes_cli/setup.py index c567dc70..d09b49b9 100644 --- a/hermes_cli/setup.py +++ b/hermes_cli/setup.py @@ -726,6 +726,8 @@ def setup_model_provider(config: dict): "MiniMax China (mainland China endpoint)", "Anthropic (Claude models — API key or Claude Code subscription)", "AI Gateway (Vercel — 200+ models, pay-per-use)", + "OpenCode Zen (35+ curated models, pay-as-you-go)", + "OpenCode Go (open models, $10/month subscription)", ] if keep_label: provider_choices.append(keep_label) @@ -1266,7 +1268,73 @@ def setup_model_provider(config: dict): _update_config_for_provider("ai-gateway", pconfig.inference_base_url, default_model="anthropic/claude-opus-4.6") _set_model_provider(config, "ai-gateway", pconfig.inference_base_url) - # else: provider_idx == 10 (Keep current) — only shown when a provider already exists + elif provider_idx == 10: # OpenCode Zen + selected_provider = "opencode-zen" + print() + print_header("OpenCode Zen API Key") + pconfig = PROVIDER_REGISTRY["opencode-zen"] + print_info(f"Provider: {pconfig.name}") + print_info(f"Base URL: {pconfig.inference_base_url}") + print_info("Get your API key at: https://opencode.ai/auth") + print() + + existing_key = get_env_value("OPENCODE_ZEN_API_KEY") + if existing_key: + print_info(f"Current: {existing_key[:8]}... (configured)") + if prompt_yes_no("Update API key?", False): + api_key = prompt_text("OpenCode Zen API key", password=True) + if api_key: + save_env_value("OPENCODE_ZEN_API_KEY", api_key) + print_success("OpenCode Zen API key updated") + else: + api_key = prompt_text("OpenCode Zen API key", password=True) + if api_key: + save_env_value("OPENCODE_ZEN_API_KEY", api_key) + print_success("OpenCode Zen API key saved") + else: + print_warning("Skipped - agent won't work without an API key") + + # Clear custom endpoint vars if switching + if existing_custom: + save_env_value("OPENAI_BASE_URL", "") + save_env_value("OPENAI_API_KEY", "") + _set_model_provider(config, "opencode-zen", pconfig.inference_base_url) + selected_base_url = pconfig.inference_base_url + + elif provider_idx == 11: # OpenCode Go + selected_provider = "opencode-go" + print() + print_header("OpenCode Go API Key") + pconfig = PROVIDER_REGISTRY["opencode-go"] + print_info(f"Provider: {pconfig.name}") + print_info(f"Base URL: {pconfig.inference_base_url}") + print_info("Get your API key at: https://opencode.ai/auth") + print() + + existing_key = get_env_value("OPENCODE_GO_API_KEY") + if existing_key: + print_info(f"Current: {existing_key[:8]}... (configured)") + if prompt_yes_no("Update API key?", False): + api_key = prompt_text("OpenCode Go API key", password=True) + if api_key: + save_env_value("OPENCODE_GO_API_KEY", api_key) + print_success("OpenCode Go API key updated") + else: + api_key = prompt_text("OpenCode Go API key", password=True) + if api_key: + save_env_value("OPENCODE_GO_API_KEY", api_key) + print_success("OpenCode Go API key saved") + else: + print_warning("Skipped - agent won't work without an API key") + + # Clear custom endpoint vars if switching + if existing_custom: + save_env_value("OPENAI_BASE_URL", "") + save_env_value("OPENAI_API_KEY", "") + _set_model_provider(config, "opencode-go", pconfig.inference_base_url) + selected_base_url = pconfig.inference_base_url + + # else: provider_idx == 12 (Keep current) — only shown when a provider already exists # Normalize "keep current" to an explicit provider so downstream logic # doesn't fall back to the generic OpenRouter/static-model path. if selected_provider is None: