ca06b87689
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.
121 lines
4.9 KiB
Python
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"
|