1017a56274
The Zen relay serves *-free models (x-preview-f-free / Ox Alpha) ONLY
anonymously: any Authorization bearer it doesn't recognize is a 401
'Invalid API key' — including our no-key-required placeholder and valid
OpenCode GO subscription keys. The Go relay doesn't serve the free tier
at all ('Model x is not supported'). So the free model failed for every
Hermes user: keyless setups got the placeholder bearer, and OpenCode
subscribers sent a Go key to a relay that rejects it.
Fix (class-wide for all 8 current *-free Zen slugs, not just Ox Alpha):
- hermes_cli/models.py: is_opencode_zen_free_model / opencode_zen_free_runtime
/ opencode_zen_free_headers — one shared policy: free slugs pin to the
Zen relay with a keyless placeholder and an empty Authorization header
that overrides the OpenAI SDK's 'Bearer <key>'.
- runtime_provider.py: free slugs route through the keyless runtime before
the credential-pool/explicit/api_key paths (no key required; Go
selections heal to Zen). Paid models still fail closed without a key.
- agent_init.py + auxiliary_client.py: the placeholder key swaps in the
empty-Authorization headers at both client-build chokepoints.
Verified live (2026-08-21): anonymous chat/completions 200 incl. tools,
streaming, parallel; bad bearer 401; full E2E AIAgent turn with a real
terminal tool round-trip completes keyless under both opencode-zen and
opencode-go providers. Sabotage run: routing tests fail without the fix.
125 lines
5.0 KiB
Python
125 lines
5.0 KiB
Python
"""OpenCode Zen free-tier keyless routing (x-preview-f-free / "Ox Alpha").
|
|
|
|
The Zen relay serves ``*-free`` models ANONYMOUSLY: a request with no
|
|
Authorization header succeeds, while any non-empty bearer the relay doesn't
|
|
recognize — including our historical "no-key-required" placeholder and valid
|
|
OpenCode GO subscription keys — is rejected with 401 "Invalid API key".
|
|
The Go relay doesn't serve the free tier at all ("Model x is not supported").
|
|
|
|
These tests pin the keyless routing added for the community report where the
|
|
free Ox Alpha model failed under an OpenCode subscription:
|
|
|
|
1. ``is_opencode_zen_free_model`` recognizes free slugs (bare + prefixed).
|
|
2. ``opencode_zen_free_runtime`` pins free slugs to the Zen relay with the
|
|
keyless placeholder + empty-Authorization headers, for BOTH family
|
|
providers (Go selections heal to Zen).
|
|
3. ``resolve_runtime_provider`` routes free slugs keylessly with no
|
|
OPENCODE_* credential present, and still fails closed for paid models.
|
|
4. The keyless placeholder never reaches the wire: client default_headers
|
|
carry ``Authorization: ""`` overriding the SDK bearer.
|
|
"""
|
|
|
|
import os
|
|
from unittest import mock
|
|
|
|
import pytest
|
|
|
|
from hermes_cli.models import (
|
|
OPENCODE_ZEN_FREE_KEYLESS_PLACEHOLDER,
|
|
is_opencode_zen_free_model,
|
|
opencode_zen_free_headers,
|
|
opencode_zen_free_runtime,
|
|
)
|
|
|
|
|
|
class TestFreeSlugDetection:
|
|
def test_bare_free_slug(self):
|
|
assert is_opencode_zen_free_model("x-preview-f-free")
|
|
|
|
def test_provider_prefixed_slug(self):
|
|
assert is_opencode_zen_free_model("opencode-zen/x-preview-f-free")
|
|
|
|
def test_other_free_tier_slugs(self):
|
|
for slug in (
|
|
"hy3-free",
|
|
"laguna-s-2.1-free",
|
|
"mimo-v2.5-free",
|
|
"nemotron-3-ultra-free",
|
|
):
|
|
assert is_opencode_zen_free_model(slug), slug
|
|
|
|
def test_paid_models_not_free(self):
|
|
for slug in ("claude-sonnet-5", "glm-5.2", "kimi-k3", "gpt-5.6-sol"):
|
|
assert not is_opencode_zen_free_model(slug), slug
|
|
|
|
def test_empty_and_none(self):
|
|
assert not is_opencode_zen_free_model("")
|
|
assert not is_opencode_zen_free_model(None)
|
|
|
|
def test_freedom_like_names_not_swept(self):
|
|
# suffix match must be exact "-free", not substring "free"
|
|
assert not is_opencode_zen_free_model("freeform-1")
|
|
assert not is_opencode_zen_free_model("model-freedom")
|
|
|
|
|
|
class TestFreeRuntime:
|
|
def test_zen_provider_free_model(self):
|
|
rt = opencode_zen_free_runtime("opencode-zen", "x-preview-f-free")
|
|
assert rt is not None
|
|
assert rt["base_url"] == "https://opencode.ai/zen/v1"
|
|
assert rt["api_key"] == OPENCODE_ZEN_FREE_KEYLESS_PLACEHOLDER
|
|
assert rt["api_mode"] == "chat_completions"
|
|
assert rt["default_headers"]["Authorization"] == ""
|
|
|
|
def test_go_provider_heals_to_zen(self):
|
|
# Free slugs only exist on the Zen relay; a Go selection must be
|
|
# routed to Zen (the Go relay rejects the model outright).
|
|
rt = opencode_zen_free_runtime("opencode-go", "x-preview-f-free")
|
|
assert rt is not None
|
|
assert rt["base_url"] == "https://opencode.ai/zen/v1"
|
|
|
|
def test_paid_model_returns_none(self):
|
|
assert opencode_zen_free_runtime("opencode-zen", "claude-sonnet-5") is None
|
|
|
|
def test_non_opencode_provider_returns_none(self):
|
|
assert opencode_zen_free_runtime("openrouter", "x-preview-f-free") is None
|
|
assert opencode_zen_free_runtime(None, "x-preview-f-free") is None
|
|
|
|
def test_headers_override_sdk_bearer(self):
|
|
headers = opencode_zen_free_headers()
|
|
assert headers["Authorization"] == ""
|
|
assert headers["X-Title"] == "Hermes Agent"
|
|
|
|
|
|
class TestRuntimeProviderKeylessRouting:
|
|
@pytest.fixture(autouse=True)
|
|
def _no_opencode_creds(self, monkeypatch):
|
|
for var in ("OPENCODE_ZEN_API_KEY", "OPENCODE_GO_API_KEY"):
|
|
monkeypatch.delenv(var, raising=False)
|
|
|
|
def _resolve(self, provider, model):
|
|
from hermes_cli.runtime_provider import resolve_runtime_provider
|
|
|
|
with mock.patch(
|
|
"hermes_cli.runtime_provider._get_model_config",
|
|
return_value={"provider": provider, "model": model, "default": model},
|
|
):
|
|
return resolve_runtime_provider(requested=provider, target_model=model)
|
|
|
|
def test_zen_free_model_resolves_keyless(self):
|
|
rt = self._resolve("opencode-zen", "x-preview-f-free")
|
|
assert rt["api_key"] == OPENCODE_ZEN_FREE_KEYLESS_PLACEHOLDER
|
|
assert rt["base_url"] == "https://opencode.ai/zen/v1"
|
|
assert rt["api_mode"] == "chat_completions"
|
|
|
|
def test_go_free_model_resolves_keyless_on_zen(self):
|
|
rt = self._resolve("opencode-go", "x-preview-f-free")
|
|
assert rt["api_key"] == OPENCODE_ZEN_FREE_KEYLESS_PLACEHOLDER
|
|
assert rt["base_url"] == "https://opencode.ai/zen/v1"
|
|
|
|
def test_paid_model_still_fails_closed_without_key(self):
|
|
from hermes_cli.auth import AuthError
|
|
|
|
with pytest.raises(AuthError):
|
|
self._resolve("opencode-zen", "claude-sonnet-5")
|