# OpenCode Backend (`oc-zen` / `oc-go`)

## Why a custom provider at all

pi-ai v0.75+ ships built-in `opencode` and `opencode-go` providers, but their
catalog routes several models to the **wrong wire protocol**, so requests fail
with 400 errors. Known misroutes fixed by this package:

| Model            | Tier | Built-in routes to  | Correct route        |
| ---------------- | ---- | ------------------- | -------------------- |
| Qwen3.x family   | Go   | `/chat/completions` | `/messages` (claude) |
| MiniMax M2.5/2.7 | Go   | `/chat/completions` | `/messages` (claude) |
| MiniMax M3       | Go   | `/chat/completions` | `/messages` (claude) |

## Endpoints

|                      | Zen                                 | Go                                     |
| -------------------- | ----------------------------------- | -------------------------------------- |
| Base (OpenAI/Google) | `https://opencode.ai/zen/v1`        | `https://opencode.ai/zen/go/v1`        |
| Base (Anthropic SDK) | `https://opencode.ai/zen`           | `https://opencode.ai/zen/go`           |
| Models list          | `https://opencode.ai/zen/v1/models` | `https://opencode.ai/zen/go/v1/models` |
| Auth                 | `OPENCODE_API_KEY`                  | `OPENCODE_API_KEY` (same key)          |

Auth header format follows the protocol: `Authorization: Bearer <key>` for
OpenAI-style transports, `x-api-key: <key>` for the Anthropic transport.

## Model → protocol routing

The routing table (`src/backends/opencode/catalog.ts`) pins each model:

| Protocol               | Path                | Model families                                                      |
| ---------------------- | ------------------- | ------------------------------------------------------------------- |
| `openai-responses`     | `/responses`        | GPT-5.x, Grok                                                       |
| `anthropic-messages`   | `/messages`         | Claude, Qwen, MiniMax M2.x/M3 (Go)                                  |
| `openai-completions`   | `/chat/completions` | DeepSeek, GLM, Kimi, MiMo, MiniMax M3 (Zen), big-pickle, free tiers |
| `google-generative-ai` | `/models/{id}`      | Gemini                                                              |

Unknown/live ids fall back to pattern matching (`getProtocolForModel`):
`claude*` → anthropic, `gemini*` → google, `gpt-5*` → responses, `qwen*` →
anthropic, `minimax*` → go:anthropic / zen:openai-completions, otherwise
openai-completions.

The catalog was cross-checked against OmniRoute's opencode registries: the Go
model list (44 models including effort aliases) matches **1:1**.

## Effort tiers

Go models expose effort-tier aliases (registered as separate models, expanded
at request time by `rewriteEffortTier`):

- `deepseek-v4-pro{-low,-medium,-high,-max}`
- `deepseek-v4-flash{-high,-max}`
- `grok-4.5{-low,-medium,-high}`
- `qwen3.6-plus{-high,-max}`, `qwen3.7-plus{-high,-max}`, `qwen3.7-max{-high,-max}`
- `glm-5.2{-high,-max}`, `kimi-k3{-max}`, `mimo-v2.5{-high,-max}`
- `hy3{-none,-low,-high}` (+ `hy3-preview`)

## Gotcha hooks

Every request passes through `composeOnPayload(protocol, model.id)`. These only
run because models are registered with the `OPENCODE_CUSTOM_API` marker (see
[Architecture – design decisions](ARCHITECTURE.md)), which makes pi route
streaming through our `streamSimple` instead of pi-ai's directly. The real
protocol is re-derived from the catalog at stream time.

1. `stripClientMetadata` — upstream 400 `Extra inputs are not permitted, field: 'client_metadata'`
2. `limitTools` (max 128) — upstream rejects oversized tool arrays
3. `rewriteEffortTier` — alias → canonical id + `reasoning_effort` injection
4. `injectReasoningContent` — thinking models (DeepSeek, Kimi, MiniMax, MiMo,
   big-pickle) need `reasoning_content` echoed on assistant messages
   (openai-completions transport only)
5. `stripBooleanReasoning` — some upstreams reject boolean `reasoning` fields

## Model discovery & refresh

- Static catalogs (`ZEN_MODELS` 43 / `GO_MODELS` 44) are the offline baseline
  with full metadata (name, pricing, context window, protocol pin).
- `refreshModels` (see [CACHING.md](CACHING.md)) fetches the live `/models`
  list when the user opens `/model`, merges it with the baseline, and hands it
  to pi's model-store cache. New upstream models appear automatically with
  pattern-matched protocol + generic metadata (cost shows $0 until added to
  the static catalog — see pricing note below).

## Pricing

`ZEN_PRICING` / `GO_PRICING` (src/pricing.ts) hold per-token USD rates
including `cacheRead` / `cacheWrite`. Models missing from the tables display
zero cost in pi — **this does not mean the request is free**; check
[OpenCode pricing](https://opencode.ai) for real rates.

## Auth setup

See the README "Authentication" section. Remember: auth entries are keyed by
**provider id**, so add both `oc-zen` and `oc-go` (or set `OPENCODE_API_KEY`).
