Files
hermes-agent/tests/agent/test_opencode_free_provider.py
T
Teknium ca06b87689 feat: opencode-free is fully keyless — no env var, no account, anonymous wire
Reworks the salvaged OpenCode Free provider to match the tier's real
auth contract (verified live 2026-08-21): the Zen relay serves free
models ANONYMOUSLY and 401s any unrecognized bearer, so the provider now
declares no credentials at all and routes every model through the shared
keyless machinery from the Ox Alpha fix (empty Authorization default
header overriding the SDK bearer).

On top of the salvaged base:
- auth.py: no api_key_env_vars; drop the keyed-auth special case
- runtime_provider.py: restore the plain fail-closed path (opencode-free
  never reaches it — the keyless runtime resolves first)
- models.py: opencode-free joins the opencode family (prefix stripping,
  Zen endpoint routing incl. muse->responses); keyless predicate extended
  with unsuffixed free slugs (big-pickle); free runtime pins EVERY
  opencode-free model keyless; curated catalog replaces the models.dev
  cost==0 filter (it lags reality: deepseek-v4-flash-free stayed 'free'
  there after its promo ended and the relay began 401ing it — delisted)
- agent_runtime_helpers.py: replace the httpx transport-sharing auth-strip
  wrapper with the shared header policy (no proxy-mount loss)
- model_setup_flows.py: skip the API-key prompt for opencode-free
- plugin profile: keyless headers, no env vars
- .env.example + providers.md: keyless docs (no OPENCODE_FREE_API_KEY)
- tests rewritten to the keyless contract, incl. catalog-membership
  invariant (every curated model must satisfy the keyless predicate)

E2E: full AIAgent turns with zero keys complete on x-preview-f-free via
provider opencode-free and alias 'free', incl. a real terminal tool
round-trip; muse routes to /v1/responses; picker lists 8 keyless models.
2026-08-21 00:24:32 -07:00

121 lines
4.9 KiB
Python

"""Tests for OpenCode Free provider — registration, keyless contract, aliases.
The provider is KEYLESS: OpenCode's free tier is served anonymously and
rejects any unrecognized Authorization bearer with 401, so the provider
declares no env vars and every request goes out with an empty Authorization
header (see hermes_cli.models.opencode_zen_free_runtime).
"""
import os
from unittest.mock import patch
class TestOpenCodeFreeProviderRegistration:
"""Verify the opencode-free provider registers correctly."""
def test_provider_is_registered(self):
from providers import get_provider_profile
profile = get_provider_profile("opencode-free")
assert profile is not None
assert profile.name == "opencode-free"
def test_provider_has_correct_base_url(self):
from providers import get_provider_profile
profile = get_provider_profile("opencode-free")
assert profile.base_url == "https://opencode.ai/zen/v1"
def test_provider_is_keyless(self):
"""No env vars declared — the free tier requires no credential."""
from providers import get_provider_profile
profile = get_provider_profile("opencode-free")
assert profile.env_vars == ()
def test_provider_headers_override_sdk_bearer(self):
"""The profile's default headers blank Authorization so the SDK's
Bearer never reaches the wire (the free tier 401s unknown bearers)."""
from providers import get_provider_profile
profile = get_provider_profile("opencode-free")
assert profile.default_headers.get("Authorization") == ""
def test_provider_uses_chat_completions_mode(self):
from providers import get_provider_profile
profile = get_provider_profile("opencode-free")
assert profile.api_mode == "chat_completions"
class TestOpenCodeFreeAliases:
"""Verify alias resolution for the opencode-free provider."""
def test_alias_free(self):
from providers import get_provider_profile
profile = get_provider_profile("free")
assert profile is not None
assert profile.name == "opencode-free"
def test_alias_opencode_free(self):
from providers import get_provider_profile
profile = get_provider_profile("opencode_free")
assert profile is not None
assert profile.name == "opencode-free"
class TestOpenCodeFreeAuthAlias:
"""Verify the hardcoded alias in auth.py resolve_provider()."""
def test_resolve_provider_free_alias(self):
from hermes_cli.auth import resolve_provider
# "free" should resolve to "opencode-free" without any credential
result = resolve_provider("free")
assert result == "opencode-free"
class TestOpenCodeFreeModelLists:
"""Curated keyless model lists exist and stay in sync."""
def test_fallback_models_exist(self):
from hermes_cli.setup import _DEFAULT_PROVIDER_MODELS
assert "opencode-free" in _DEFAULT_PROVIDER_MODELS
def test_setup_list_matches_curated_catalog(self):
"""setup.py sample list must be a subset of the curated catalog
(behavior contract, not a frozen snapshot)."""
from hermes_cli.models import _PROVIDER_MODELS
from hermes_cli.setup import _DEFAULT_PROVIDER_MODELS
curated = set(_PROVIDER_MODELS["opencode-free"])
assert set(_DEFAULT_PROVIDER_MODELS["opencode-free"]) <= curated
def test_every_curated_model_is_keyless(self):
"""Every model in the opencode-free catalog must satisfy the keyless
predicate — a paid slug here would route with no auth and 401."""
from hermes_cli.models import _PROVIDER_MODELS, is_opencode_zen_free_model
for mid in _PROVIDER_MODELS["opencode-free"]:
assert is_opencode_zen_free_model(mid), mid
def test_ox_alpha_is_listed(self):
from hermes_cli.models import _PROVIDER_MODELS
assert "x-preview-f-free" in _PROVIDER_MODELS["opencode-free"]
class TestOpenCodeFreeRuntimeKeyless:
"""The runtime resolver pins every opencode-free model keyless."""
def test_free_provider_any_model_routes_keyless(self):
from hermes_cli.models import (
OPENCODE_ZEN_FREE_KEYLESS_PLACEHOLDER,
opencode_zen_free_runtime,
)
rt = opencode_zen_free_runtime("opencode-free", "big-pickle")
assert rt is not None
assert rt["api_key"] == OPENCODE_ZEN_FREE_KEYLESS_PLACEHOLDER
assert rt["base_url"] == "https://opencode.ai/zen/v1"
assert rt["default_headers"]["Authorization"] == ""
def test_free_provider_muse_routes_responses(self):
"""opencode-free inherits Zen's per-model endpoint routing."""
from hermes_cli.models import opencode_zen_free_runtime
rt = opencode_zen_free_runtime(
"opencode-free", "muse-spark-1.2-contributor-free"
)
assert rt is not None
assert rt["api_mode"] == "codex_responses"