Skip to content

feat(llm): add Opencode provider (OpenCode Go and Zen) - #299

Open
Notaloop763 wants to merge 2 commits into
hydropix:mainfrom
Notaloop763:feat/opencode
Open

Notaloop763 wants to merge 2 commits into
hydropix:mainfrom
Notaloop763:feat/opencode

Conversation

@Notaloop763

@Notaloop763 Notaloop763 commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds an Opencode provider for the OpenCode gateway. One OpenCode API key reaches both catalogs:

  • OpenCode Go (/zen/go/v1), the subscription plan
  • OpenCode Zen (/zen/v1), pay-as-you-go from the workspace balance

Model ids follow opencode's own <catalog>/<model> convention, as printed by opencode models. opencode-go/<model> routes to Go and opencode/<model> routes to Zen. A bare id routes to Go, so a missing prefix never spends balance. The API only ever receives the bare id.

API families

The gateway serves each model through one API family. The provider picks the family from the model id, following the Go endpoint table and the routing in opencode's own client and hermes-agent:

Models API Endpoint
gpt-*, grok-*, muse-spark* OpenAI Responses /v1/responses
claude-*, qwen*, union-alpha*, and on Go minimax-* Anthropic Messages /v1/messages
gemini-* (Zen only) Google generateContent /v1/models/<model>:generateContent
everything else (DeepSeek, GLM, Kimi, MiMo, LongCat, ...) chat/completions /v1/chat/completions

Each family gets its native request body, response parsing (text, finish reason, token usage, output-truncation detection) and auth header (Authorization: Bearer, x-api-key or x-goog-api-key). New models that rotate into the catalogs go to chat/completions unless their id matches a prefix above. Only jev-* on Zen (OpenCode's System One API) is not supported; those models are hidden from the model list and rejected with a clear error.

OpenAICompatibleProvider gains small request-URL, auth-header, payload and response-parsing hooks for this. Its own behavior is unchanged.

Details

  • Session header: every request carries a random x-opencode-session id, stable for one translation job, which keeps the gateway's prompt cache warm. Go rejects requests without it. The id is never derived from the machine or the install.
  • Model list: both catalogs are fetched concurrently and shown as "OpenCode Go" / "OpenCode Zen" option groups.
  • Errors: gateway refusals (ModelProtocolUnsupported, FreeTierError, insufficient balance, "model access is disabled") get actionable messages.
  • Wiring: config (OPENCODE_API_KEY, OPENCODE_MODEL, OPENCODE_API_BASE), factory, API routes and settings allow-list, translate/refine/PDF pipelines, CLI, web UI and all 7 locales.
  • Docs: new Opencode section in docs/PROVIDERS.md; the provider is also listed in the README, other docs, .env.example and docker-compose.yml.

Shared changes outside the provider

  • PDF pipeline: the PDF pipeline now receives the NIM API key as well. It was missing, so PDF jobs on NIM failed.
  • Rate limits: in-place sleeps are now capped at MAX_RATE_LIMIT_SLEEP (one hour). A longer Retry-After, such as OpenCode Go's weekly quota, raises RateLimitError, so the pipeline auto-pauses with a checkpoint instead of stalling silently. Shorter throttles still wait in place, as before. This applies to every provider.

Testing

  • Live, OpenCode Go: every model in the Go catalog (38) returned a translation through its family: 23 over chat/completions, 9 over Anthropic Messages (Claude Haiku 5.5, MiniMax, Qwen) and 6 over OpenAI Responses (GPT, Grok, Muse Spark).
  • Live, OpenCode Zen: Messages and Google requests reach the gateway and get the expected billing refusal (no balance on the test workspace). Successful paid Zen calls are covered by mocked tests only.
  • Unit tests: tests/unit/test_opencode_provider.py (routing table, per-family request/response round trips, session header, model list, error hints) and tests/unit/test_opencode_integration.py, plus a rate-limit cap test.

🤖 Generated with Claude Code

@Notaloop763

Copy link
Copy Markdown
Contributor Author

@hydropix, friendly ping: this is ready for review, and CI is green. It builds on frank-lar's #278, which has been inactive since mid-September. Their commits are credited as co-author. On top of that, it adds routing for every OpenCode API family (Responses, Anthropic Messages, Google), the Zen catalog, and live tests against all 38 Go models. If it's merged, #278 can be closed.

@Notaloop763
Notaloop763 force-pushed the feat/opencode branch 2 times, most recently from adfd23d to 32628a6 Compare October 15, 2026 04:47
@hydropix

Copy link
Copy Markdown
Owner

Thanks for the ping and for the work on top of #278, and sorry for the wait. I haven't been able to review this one properly yet, and I'd rather not merge a new provider of this size without going through it, so it won't be in the release going out today. It's next on my list, and I'll get back to you here with feedback.

Notaloop763 and others added 2 commits October 25, 2026 13:05
Add OpencodeProvider, an OpenAI-compatible provider for the OpenCode
gateway. One API key reaches both catalogs, and the model id picks one
using opencode's own "<catalog>/<model>" convention:
opencode-go/<model> goes to Go (subscription) and opencode/<model> goes
to Zen (pay-as-you-go). A bare id goes to Go so a missing prefix never
spends balance. The API only ever receives the bare id; the model setter
normalizes ids assigned later (e.g. by refinement passes) as well.

- Send a random x-opencode-session header, stable for the provider's
  lifetime (one job), on chat, model-listing and context-detection
  requests. Go rejects requests without it. Supported by a new optional
  extra_headers on OpenAICompatibleProvider and ContextDetector.
- Route each model to the API family the gateway serves it through,
  by model-id prefix, as opencode and hermes-agent do: gpt-/grok-/
  muse-spark to OpenAI Responses, claude-/qwen/union-alpha (and
  minimax- on Go) to Anthropic Messages, gemini- on Zen to Google generateContent, and
  everything else to chat/completions. Only System One (jev- on Zen) is
  rejected and hidden from the model list. OpenAICompatibleProvider
  gains request-URL, auth-header, payload and response-parsing hooks
  for this.
- The model list fetches both catalogs concurrently and groups them as
  "OpenCode Go" / "OpenCode Zen"; when both fail, the real cause is
  reported.
- Gateway refusals (ModelProtocolUnsupported, FreeTierError,
  CreditsError / insufficient funds, "model access is disabled") get
  actionable messages via a new _describe_http_error hook.
- Wire the provider through config (OPENCODE_API_KEY, OPENCODE_MODEL,
  OPENCODE_API_BASE), the factory, API routes and settings allow-list,
  translate/refine/PDF pipelines, CLI, web UI and all 7 locales.

Also forward the NIM API key to the PDF pipeline, and cap in-place
rate-limit sleeps at MAX_RATE_LIMIT_SLEEP (one hour): longer
Retry-After waits, such as Go's weekly quota, raise RateLimitError so
the pipeline auto-pauses instead of stalling.

Verified live on Go: all 38 listed models translate, across chat,
Responses (gpt, grok, muse-spark) and Messages (claude, minimax, qwen).
Zen Messages and Google requests reach the gateway and get the expected
billing refusal; paid Zen calls are covered by mocked tests only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: frank-lar <2317872+frank-lar@users.noreply.github.com>
Add an Opencode section to docs/PROVIDERS.md covering the Go and Zen
catalogs, the "<catalog>/<model>" model ids (a bare id routes to Go),
the per-job session header and which model families are supported.
List the provider in the README, CLI, Docker, troubleshooting and
API-key rotation docs, the translator skill, and the .env.example and
docker-compose.yml option lists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: frank-lar <2317872+frank-lar@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants