fix(opencode): strip /v1 from base_url on mid-session /model switch to Anthropic-routed models (#11286)
PR #4918 fixed the double-/v1 bug at fresh agent init by stripping the trailing /v1 from OpenCode base URLs when api_mode is anthropic_messages (so the Anthropic SDK's own /v1/messages doesn't land on /v1/v1/messages). The same logic was missing from the /model mid-session switch path. Repro: start a session on opencode-go with GLM-5 (or any chat_completions model), then `/model minimax-m2.7`. switch_model() correctly sets api_mode=anthropic_messages via opencode_model_api_mode(), but base_url passes through as https://opencode.ai/zen/go/v1. The Anthropic SDK then POSTs to https://opencode.ai/zen/go/v1/v1/messages, which returns the OpenCode website 404 HTML page (title 'Not Found | opencode'). Same bug affects `/model claude-sonnet-4-6` on opencode-zen. Verified upstream: POST /v1/messages returns clean JSON 401 with x-api-key auth (route works), while POST /v1/v1/messages returns the exact HTML 404 users reported. Fix mirrors runtime_provider.resolve_runtime_provider: - hermes_cli/model_switch.py::switch_model() strips /v1 after the OpenCode api_mode override when the resolved mode is anthropic_messages. - run_agent.py::AIAgent.switch_model() applies the same strip as defense-in-depth, so any direct caller can't reintroduce the double-/v1. Tests: 9 new regression tests in tests/hermes_cli/test_model_switch_opencode_anthropic.py covering minimax on opencode-go, claude on opencode-zen, chat_completions (GLM/Kimi/Gemini) keeping /v1 intact, codex_responses (GPT) keeping /v1 intact, trailing-slash handling, and the agent-level defense-in-depth.
This commit is contained in:
@@ -727,6 +727,22 @@ def switch_model(
|
||||
if not api_mode:
|
||||
api_mode = determine_api_mode(target_provider, base_url)
|
||||
|
||||
# OpenCode base URLs end with /v1 for OpenAI-compatible models, but the
|
||||
# Anthropic SDK prepends its own /v1/messages to the base_url. Strip the
|
||||
# trailing /v1 so the SDK constructs the correct path (e.g.
|
||||
# https://opencode.ai/zen/go/v1/messages instead of .../v1/v1/messages).
|
||||
# Mirrors the same logic in hermes_cli.runtime_provider.resolve_runtime_provider;
|
||||
# without it, /model switches into an anthropic_messages-routed OpenCode
|
||||
# model (e.g. `/model minimax-m2.7` on opencode-go, `/model claude-sonnet-4-6`
|
||||
# on opencode-zen) hit a double /v1 and returned OpenCode's website 404 page.
|
||||
if (
|
||||
api_mode == "anthropic_messages"
|
||||
and target_provider in {"opencode-zen", "opencode-go"}
|
||||
and isinstance(base_url, str)
|
||||
and base_url
|
||||
):
|
||||
base_url = re.sub(r"/v1/?$", "", base_url)
|
||||
|
||||
# --- Get capabilities (legacy) ---
|
||||
capabilities = get_model_capabilities(target_provider, new_model)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user