provider-opencode-go
v0.0.6OpenCode Go Chat Completions provider worker; implements provider::opencode_go::stream and provider::opencode_go::refresh_models behind llm-router.
- macOS: arm64
- Linux: arm64 · armv7 · x64
- Windows: arm64 · x64
exact versions are immutable; binary and bundle artifacts are digest-pinned.
readme
open as markdownprovider-opencode-go
OpenCode Go Chat Completions provider worker behind llm-router.
Implements the provider protocol from
tech-specs/2026-06-agentic/llm-router.md: provider::opencode_go::stream
(SSE chunks → AssistantMessageEvent frames into a router-owned channel),
provider::opencode_go::refresh_models (live GET /v1/models id list
enriched from a hardcoded curated metadata table → router::models::reconcile),
provider::opencode_go::count_tokens (local tiktoken prompt-token estimate,
the same surface the other providers expose behind router::count_tokens),
and provider::opencode_go::abort (cancels an in-flight upstream request).
There is no embedding surface — the OpenCode Go API is Chat Completions only.
Install with iii trigger compose::add worker=provider-opencode-go; the worker takes no
per-worker config — credentials and endpoint live in the llm-router
configuration entry (providers.opencode_go.api_key, default endpoint
https://opencode.ai/zen/go/v1/chat/completions), exactly like
provider-openai.
Behavior
- Registration: self-declares via
router::provider::registerwith backoff until acked, and re-declares on therouter::readytrigger type. The model slice is populated from live discovery and the declaration carriescredential_env_var: OPENCODE_GO_API_KEY. - Transport: the upstream endpoint is
https://opencode.ai/zen/go/v1/chat/completions(overridable viaapi_url); Chat Completions wire format only. - Identity binding: the router returns a
registration_tokenon first registration; it is persisted in state (scopeprovider-opencode-go, keyregistration_token) and presented on every laterregister/resolve/reconcile. If that state is lost the router rejects re-registration — the operator must clear the binding on the router side. - Credentials: resolved per request via
router::provider::resolve(config slice →OPENCODE_GO_API_KEYenv on the router → none). The key is sent asAuthorization: Bearer. - Liveness:
pingat least every 30s of upstream silence; a failed channel write (caller gone /router::abort) drops the SSE receiver and aborts the in-flight HTTP request. - Errors: 401/403 →
auth_expired, 429 →rate_limited,context_length_exceeded→context_overflow, 5xx/network →transient, other 4xx →permanent. No transport retries here — the router owns retry policy. - Model metadata: discovery fetches the live id list from
GET /v1/models(the API carries no capability data) and enriches each id from a hardcoded curated table (src/curated.rs) covering the maintainer's OpenCode Go subscription catalog — the 24opencode-goentries on models.dev (fetched 2026-08-03) plushy3-preview, which models.dev does not list and which keeps conservative defaults — context windows, reasoning support/effort levels, tool-call and structured-output capability. Ids the table does not know keep conservative defaults (128K context, no thinking, tools on). Same pattern as provider-openai. - Reasoning:
thinking_levelmaps to the upstreamreasoning_effortwhen the model's curated effort list accepts the level (e.g.grok-4.5acceptslow/medium/high,deepseek-v4-flashacceptshigh/max); models that reason without published effort levels, and unknown ids, stream without the field. When the upstream emitsreasoning_contentdeltas they are relayed as thinking blocks; models that never emit them stream text only. - Structured output: a
response_formatwith a schema maps to strictjson_schemamode; without one,json_objectmode (the caller must mention "JSON" in the prompt per OpenAI-compatible API rules). - Prompt caching: upstream-managed;
prompt_tokens_details.cached_tokenslands onusage.cache_readwhen the API reports it.
Tests
cargo test --lib --test schemas # unit (pure modules + TCP stubs) + golden schema checks
III_ENGINE_BIN=$(which iii) cargo test --test integration -- --test-threads=1The integration suite spawns a real engine, the real router (path dep), this provider, and a local stub upstream — no external API calls anywhere.
Running
The binary takes the standard worker CLI flags: --url (engine WebSocket,
default ws://127.0.0.1:49134, falls back to the III_URL environment
variable), --manifest (print the registry manifest and exit), and
--config (accepted but ignored with a warning — provider config comes
from the llm-router configuration entry).
Credits
Originally contributed by @faramirezs.