Summary
This is a concrete reproduction of the issue described in #526 — model ID mismatch when using a custom OpenAI-compatible upstream (OpenCode Go) via OPENROUTER_BASE_URL.
The router sends slash-form model IDs (deepseek/deepseek-v4-flash) in the request body, but OpenCode Go expects bare names (deepseek-v4-flash). There is no env-var mechanism to remap model names at runtime.
Steps to reproduce
1. Start the router
2. Point OpenRouter at OpenCode Go
Add to .env (docker compose auto-reads this):
OPENROUTER_API_KEY=sk-your-opencode-go-key
OPENROUTER_BASE_URL=https://opencode.ai/zen/go/v1
Then recreate:
docker compose up -d --force-recreate router-server
3. Create a router API key
Login at http://localhost:8080/ui/login (password: admin), then create a key via Settings → API Keys.
4. Send a test request
curl -s -N "http://localhost:8080/v1/chat/completions" \
-H "Content-Type: application/json" \
-H "x-api-key: rk_YOUR_KEY" \
-d '{"model":"deepseek/deepseek-v4-flash","messages":[{"role":"user","content":"say hello in one word"}]}'
5. Actual result
{"type":"error","error":{"type":"ModelError","message":"Model deepseek/deepseek-v4-pro is not supported"}}
The error format ({"type":"error",...}) confirms the request reached OpenCode Go. It was rejected because the model name sent was the router's public slash-form name instead of OpenCode Go's bare name.
Root cause analysis
The OpenRouter client (openaicompat.Client) sends the body's "model" field verbatim — no modelIDMap rewriting happens for OpenRouter:
| Provider |
Has modelIDMap? |
Constructor in main.go |
| OpenRouter |
No (public slug = upstream ID) |
NewClient(key, url) |
| DeepInfra |
Yes |
NewClientWithModelIDMap(key, url, upstreamIDsForProvider("deepinfra")) |
| Fireworks |
Yes |
NewClientWithModelIDMap(key, url, upstreamIDsForProvider("fireworks")) |
| Bedrock |
Yes |
NewClientWithModelIDMap(key, url, upstreamIDsForProvider("bedrock")) |
The modelIDMap rewrites the body's "model" field at request time in rewriteModelField() (internal/providers/openaicompat/client.go:119). For OpenRouter this is a no-op because the map is nil.
OpenCode Go's /v1/models returns bare names (deepseek-v4-flash, deepseek-v4-pro, kimi-k2.6, etc.) but the router catalog uses slash-form IDs (deepseek/deepseek-v4-flash, moonshotai/kimi-k2.6, z-ai/glm-5.2, etc.).
The existing rewriteModelField infrastructure is correct — it just needs an env-var hook so self-hosters can supply runtime mappings.
Proposed fix direction
An env var (e.g. ROUTER_MODEL_ID_MAP) — comma-separated from=to pairs — parsed at boot and merged into the OpenRouter client's modelIDMap. Example:
ROUTER_MODEL_ID_MAP="deepseek/deepseek-v4-flash=deepseek-v4-flash,deepseek/deepseek-v4-pro=deepseek-v4-pro,moonshotai/kimi-k2.6=kimi-k2.6,xiaomi/mimo-v2.5-pro=mimo-v2.5-pro,z-ai/glm-5.2=glm-5.2"
Environment
- Router: main branch, docker compose stack
- Upstream: OpenCode Go (
https://opencode.ai/zen/go/v1)
Summary
This is a concrete reproduction of the issue described in #526 — model ID mismatch when using a custom OpenAI-compatible upstream (OpenCode Go) via
OPENROUTER_BASE_URL.The router sends slash-form model IDs (
deepseek/deepseek-v4-flash) in the request body, but OpenCode Go expects bare names (deepseek-v4-flash). There is no env-var mechanism to remap model names at runtime.Steps to reproduce
1. Start the router
2. Point OpenRouter at OpenCode Go
Add to
.env(docker compose auto-reads this):Then recreate:
3. Create a router API key
Login at
http://localhost:8080/ui/login(password:admin), then create a key via Settings → API Keys.4. Send a test request
5. Actual result
{"type":"error","error":{"type":"ModelError","message":"Model deepseek/deepseek-v4-pro is not supported"}}The error format (
{"type":"error",...}) confirms the request reached OpenCode Go. It was rejected because the model name sent was the router's public slash-form name instead of OpenCode Go's bare name.Root cause analysis
The OpenRouter client (
openaicompat.Client) sends the body's"model"field verbatim — nomodelIDMaprewriting happens for OpenRouter:NewClient(key, url)NewClientWithModelIDMap(key, url, upstreamIDsForProvider("deepinfra"))NewClientWithModelIDMap(key, url, upstreamIDsForProvider("fireworks"))NewClientWithModelIDMap(key, url, upstreamIDsForProvider("bedrock"))The
modelIDMaprewrites the body's"model"field at request time inrewriteModelField()(internal/providers/openaicompat/client.go:119). For OpenRouter this is a no-op because the map is nil.OpenCode Go's
/v1/modelsreturns bare names (deepseek-v4-flash,deepseek-v4-pro,kimi-k2.6, etc.) but the router catalog uses slash-form IDs (deepseek/deepseek-v4-flash,moonshotai/kimi-k2.6,z-ai/glm-5.2, etc.).The existing
rewriteModelFieldinfrastructure is correct — it just needs an env-var hook so self-hosters can supply runtime mappings.Proposed fix direction
An env var (e.g.
ROUTER_MODEL_ID_MAP) — comma-separatedfrom=topairs — parsed at boot and merged into the OpenRouter client's modelIDMap. Example:ROUTER_MODEL_ID_MAP="deepseek/deepseek-v4-flash=deepseek-v4-flash,deepseek/deepseek-v4-pro=deepseek-v4-pro,moonshotai/kimi-k2.6=kimi-k2.6,xiaomi/mimo-v2.5-pro=mimo-v2.5-pro,z-ai/glm-5.2=glm-5.2"Environment
https://opencode.ai/zen/go/v1)