Four chat_completions profiles (kimi-coding, deepseek, opencode-go's Kimi K2 and DeepSeek branches, actual) each hand-rolled the same extra_body.thinking / top-level reasoning_effort translation, and the copies had already drifted in small ways (kimi's `.get("enabled", True)`, deepseek's separate effort parsing). agent.reasoning_effort.thinking_toggle_extras is now the single implementation: the Moonshot default emits effort XOR toggle (both is an HTTP 400), and always_emit_toggle=True covers DeepSeek's contract where the toggle must ride on every request to dodge the reasoning_content echo trap. actual keeps its two contract-specific lines (reasoning_config None -> nothing; effort "none" -> disabled toggle plus reasoning_effort="none", which the relay accepts as a real level) and delegates the rest. ox_alpha_reasoning_extras moves alongside so opencode-free imports it like any other helper instead of reaching into the zen plugin's module through sys.modules and swallowing every exception into ({}, {}) - a failure there previously silently dropped the user's effort setting. No wire behavior changes; tests/plugins/model_providers/test_thinking_toggle_parity.py pins the XOR invariant across the matrix and zen/free parity.
49 lines
2.0 KiB
Python
49 lines
2.0 KiB
Python
"""OpenCode Free provider profile: the free tier on the Zen relay (https://opencode.ai/zen/v1).
|
|
|
|
KEYLESS: the relay serves free-tier models anonymously and 401s any bearer it
|
|
doesn't recognize, so this provider never sends a credential (the runtime
|
|
resolver pins the keyless placeholder and an empty Authorization header; see
|
|
hermes_cli.models.opencode_zen_free_runtime). Select via ``/model free``.
|
|
"""
|
|
|
|
from typing import Any
|
|
|
|
from agent.reasoning_effort import ox_alpha_reasoning_extras
|
|
from hermes_cli import __version__ as _HERMES_VERSION
|
|
from providers import register_provider
|
|
from providers.base import ProviderProfile
|
|
|
|
|
|
class OpenCodeFreeProfile(ProviderProfile):
|
|
"""OpenCode Free — keyless, with Ox Alpha reasoning controls.
|
|
|
|
Ox Alpha (x-preview-f-free) is also reachable via opencode-zen with the same wire
|
|
contract; both profiles call ``agent.reasoning_effort.ox_alpha_reasoning_extras``.
|
|
"""
|
|
|
|
def build_api_kwargs_extras(
|
|
self, *, reasoning_config: dict | None = None, model: str | None = None, **context
|
|
) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
return ox_alpha_reasoning_extras(reasoning_config, model)
|
|
|
|
|
|
opencode_free = OpenCodeFreeProfile(
|
|
name="opencode-free", aliases=("free", "opencode_free"),
|
|
env_vars=(), # keyless — nothing to configure
|
|
base_url="https://opencode.ai/zen/v1", display_name="OpenCode Free",
|
|
description="OpenCode free models — keyless, no account needed",
|
|
# Attribution headers (same values as opencode-zen/go) plus the empty Authorization
|
|
# override that keeps the SDK's "Bearer <placeholder>" off the wire (free tier 401s it).
|
|
default_headers={
|
|
"Authorization": "",
|
|
"HTTP-Referer": "https://hermes-agent.nousresearch.com",
|
|
"X-Title": "Hermes Agent",
|
|
"User-Agent": f"HermesAgent/{_HERMES_VERSION}",
|
|
},
|
|
# laguna is the fastest non-UA-gated free model; big-pickle 429s every
|
|
# client except the opencode CLI's own User-Agent.
|
|
default_aux_model="laguna-s-2.1-free",
|
|
)
|
|
|
|
register_provider(opencode_free)
|