Install
Inside DeepSeek Harness, with dsh-market
dsh plugin --profile web add dshmarket
Or from the command line
dsh plugin --profile web add "https://github.com/itchenshi/dsh-gateway-models/releases/download/v0.2.0/dsh-gateway-models-0.2.0.tgz"
Installing runs third-party code with your own permissions — it can read your files, use your credentials and reach the network. Review the source first, and pin a commit (github:owner/repo#sha) when you can.
Screenshots
README
One of the plugins bundled with DSH Ready GUI — the GUI ships all four, ready to tick. Each one also installs standalone into any DSH host (see "Install and go" below). GUI: https://github.com/itchenshi/dsh-ready-gui
Renamed on 2026-09-25: this was
dsh-opencode-go-path. It no longer covers only OpenCode Go — it also declares Command Code's protocol and endpoint and syncs its model list from the provider's catalog — so it now carries a vendor-neutral name. The patch layer's row id staysopencode-go(deliberately decoupled from the package name), so your enable/disable choice is not lost, and GitHub redirects the old repository name, so existing installs keep working.
One plugin for gateway routes (OpenCode Go + Command Code): install it, tick it, done — no hand-editing config, no command line, and no typing the API address every time.
What it fixes for you
| The problem you hit | What the plugin does |
|---|---|
deepseek-v4.1-flash is missing from the model list, and adding it fails with needs an api |
Declares the route's wire protocol, so models the catalog doesn't describe work too |
| Newly released models never show up in the catalog | Detects the route's catalog at engine startup, adds the DeepSeek V4.1 models and puts them first |
| The model is there but requests fail and saving it is refused | Supplies the route baseURL (a model outside the catalog has nowhere to point without it) |
Multi-turn calls fail with 400 MissingSessionID |
Attaches a per-conversation x-opencode-session header to OpenCode requests |
Multi-turn calls fail with 400 reasoning_content must be passed back |
Supplies the DeepSeek thinking-protocol declaration a catalog-unknown model is missing |
| No Command Code model exists in DSH at all, and each one you add asks for the API address | The patch layer declares the commandcode route's protocol and its endpoint, so you supply only the key; at startup the full model list is filled in from the provider's public catalog |
How the Command Code half works
Command Code is not in the engine's shipped pi-ai catalog (40 providers, none of them Command Code), so
nothing about it can be inferred: every entry needs a route-level api and baseURL, and the model ids
need a source — which is exactly why adding one meant typing the API address again. The plugin supplies
both ends:
- The endpoint:
cordis.patch.ymldeclaresapi+baseURLforcommandcode. The engine resolves an entry's endpoint asroute.baseURL ?? catalogModel.baseUrl ?? providerBaseUrl, and the model page probes withdraft.baseURL ?? fallback.baseURL— so while the route carries the address, you never type it. - The models: at startup it reads the provider's own public catalog
(
GET .../provider/v1/models, no credential required, 81 models, each carryingcontext_length) and completes the list.
Filling is add-only: entries you already have (including names and limits you edited) are kept verbatim, and missing ones are appended, so your own ordering survives. A second pass writes nothing (idempotent).
This route's catalog belongs to the provider, so the job here is completeness — not hoisting one model to the front the way the OpenCode Go half does.
One route name covers every plan: commandcode
The plan lives on the account behind the API key — not in the route name, and not in the URL. One
host, api.commandcode.ai, serves every tier; the key reports its own planId from
/alpha/billing/subscriptions (individual-go / individual-goat / individual-pro /
individual-max-10x / individual-max-20x …), and the credit windows come back sized for that tier.
So the default route is named commandcode, with no tier suffix: the tier is not what the route
decides, and calling it commandcode-goat would claim otherwise. Upgrading the plan is a key swap —
the route name and the config never change.
Older names keep working: the plugin recognises Command Code routes by their endpoint
(api.commandcode.ai), never by name. So an existing commandcode-goat, or commandcode-pro /
commandcode-max / the community provider's commandcode, is provisioned just the same. For names the
patch does not cover, the plugin also writes the api and baseURL such a route cannot resolve on its
own — the engine refuses to store models on a route whose protocol is unresolvable, and Command Code has
no catalog entry to resolve it from.
If a route's endpoint is nowhere in the config (say you proxy it through your own gateway), name its id
explicitly in the row's commandcodeProviders.
Install and go
Using DSH Ready GUI (recommended): the plugin ships inside the GUI. Open the GUI → Settings → Third-party plugins → tick Gateway routes, then restart the engine as prompted.
Any other DSH host (
dsh web, the CLI) — either route works:# Recommended: install straight from GitHub (recorded in your profile, updatable) dsh plugin --profile web add github:itchenshi/dsh-gateway-models # Fallback: this release's tarball (for when the git protocol fails but HTTPS works) dsh plugin --profile web add https://github.com/itchenshi/dsh-gateway-models/releases/download/v0.2.0/dsh-gateway-models-0.2.0.tgzBoth land the full repository contents (including
cordis.patch.yml); no extra configuration is needed afterwards.
Not on npm yet: sign-up is unreachable (
www.npmjs.comanswers with a Cloudflare challenge), so nothing can be published. Use one of the two routes above; publishing resumes once sign-up works.
How to confirm it worked: the first entry in the session model selector is
DeepSeek V4.1 Flash, and $DSH_HOME/settings.yaml lists deepseek-v4.1-flash first under
llm-pi-ai.providers.opencode-go (the plugin also adds baseURL: https://opencode.ai/zen/go/v1 if you
never set one).
What it actually does
1. Declares the route protocol (config layer)
The engine resolves a model's api in this order:
route.api ?? the model's api in the catalog ?? the api shared by all models in the catalog
The opencode-go catalog spans three protocols (anthropic-messages / openai-completions /
openai-responses), so the third fallback cannot be inferred; a model outside the catalog (such as
deepseek-v4.1-flash) has no second fallback either. The engine then reports needs an api, and the
model page's strict validation refuses to save it.
Through cordis.patch.yml the plugin adds providers.opencode-go.api: openai-completions to the
llm-pi-ai row — the protocol OpenCode Go's OpenAI-compatible endpoint speaks.
2. Detects the catalog, adds the models, puts them first (runtime)
Once the llm-pi-ai settings namespace is ready, the plugin:
- Detects the route's models through the engine's own
llm.discoverModels(). For a route pi-ai ships a catalog for, that channel answers the catalog with no network request; the plugin passes nobaseURLand no credential on purpose, so a route the catalog does not describe fails before reaching the network. Nothing detected means nothing is guessed. - Completes and hoists:
- you configured
models→ the DeepSeek V4.1 models go to the front, your own entries stay in place and in order; - you configured none → the list is seeded from the detected catalog ("V4.1 first + every catalog model"). The engine treats a non-empty configured list as the provider's complete set, so writing only our models would replace the whole catalog — the seed has to come from the catalog, and nothing is lost;
- "is the model already listed" is decided by our own ids, not the
deepseek-v4.1prefix, so a future upstream sibling cannot displace the Flash we know.
- you configured
- Supplies an endpoint when the route has none: a catalog-unknown model resolves its endpoint as
route.baseURL ?? the model's baseUrl in the catalog ?? the provider-level baseUrl, and opencode-go has no provider-levelbaseUrl— so without one the model can neither be sent nor stored (needs a baseURL). The plugin addshttps://opencode.ai/zen/go/v1only when the route has none; a route you pointed somewhere else is left untouched. A hand-written config that already has the right order but no endpoint is repaired too. - Is idempotent: right shape, endpoint present → no write, no event, no loop.
3. Attaches the session header
OpenCode's relay pins requests carrying the same x-opencode-session to the same upstream backend,
keeping the prompt cache warm across one conversation (fixes 400 MissingSessionID).
Safe by default: every DSH session derives a random opaque UUID, and the internal session ID is
never sent to a third party. mode: 'session-id' is an explicit opt-in for people who fully trust
the upstream.
4. Supplies the thinking protocol a catalog-unknown model is missing (fixes 400 reasoning_content)
deepseek-v4.1-flash is not in the installed catalog, so the engine treats it as a plain model: it
neither asks for thinking nor replays reasoning_content. pi-ai detects its DeepSeek compatibility
from the provider name or host, and the OpenCode Go relay matches neither — so replayed history
omits reasoning_content and the upstream answers 400
(The reasoning_content in the thinking mode must be passed back to the API). Every multi-turn
conversation on that model fails.
The plugin supplies the declaration the catalog would have provided (byte-for-byte what the same
route's deepseek-v4-flash / -pro entries declare): compat.requiresReasoningContentOnAssistantMessages,
thinkingFormat: deepseek, and reasoningEfforts. It fills gaps rather than overwriting: a name,
limits or compat switch you changed is kept, so an entry you already added is upgraded in place instead
of needing to be deleted and re-added.
Configuration (the cordis.patch.yml row config, all optional)
| Key | Description |
|---|---|
providers |
Route names to attach the session header to; default ['opencode', 'opencode-go'] |
commandcodeProviders |
Extra Command Code route ids. Normally unnecessary — routes on api.commandcode.ai are recognised by their endpoint. Use it only when a route's endpoint is not visible in the config |
mode |
'uuid' (default, safe) or 'session-id' (explicit opt-in; sends the internal session ID) |
debug |
true logs every streaming call that received the header |
debugFile |
Path to append JSON lines to; only paths under $DSH_HOME/logs or the system temp directory take effect; the logged session is a one-way SHA-256 hash |
Permissions and boundaries (for marketplaces that scan statically)
- Runtime dependencies: none. Node built-ins only.
- Outbound network: one public GET, carrying no credential. The only request the plugin makes is
GET https://api.commandcode.ai/provider/v1/models, used to complete the Command Code model list. That endpoint is public and needs no key, and the request sends no Authorization header. Everything else is local: the OpenCode Go half calls the engine's ownllm.discoverModels()(which answers the shipped catalog without touching the network), and the header is added by wrappingglobalThis.fetch. - Files: none by default. Only an explicitly configured
debugFileis ever written, and only under$DSH_HOME/logsor the system temp directory. - Local routes: none. No page half, no HTTP route.
- Credentials / commands / native artifacts / lifecycle scripts: none. The plugin never reads an
API key — it only writes route configuration; the engine resolves the key itself from
apiKeyEnv. - Write gate: the Command Code half touches settings only for routes you configured yourself (a
user-layer entry, e.g. after adding the key). The patch layer declares the default route for everyone
purely to supply its endpoint, which says nothing about whether you use it, so 81 models are never
pushed into the settings of someone who does not. Which routes count is decided by their endpoint
(
api.commandcode.ai), not by their name. - Failure boundary: it depends on the public contracts of the engine's assembly layer, the
settingsservice and thellmevents — not on an engine version. If those change, the engine reports the plugin as failed to load instead of failing silently. Writes go through thesettingsservice and re-run the engine's strict validation; a rejected write is refused as a whole (with the reason logged) rather than leaving half a config.
Development
node --check lib/index.js
npm test # local behaviour tests (no network, no engine)
Add an entry to V4_1_MODELS to support another V4.1 model. planRouteUpdate() /
withV41ModelsFirst() / withV41Defaults() / isV41() decide "what to write", "how to hoist", "how to
fill gaps" and "is it a v4.1 model", and all four are unit-tested.
License
MIT
Comments
Comments live in GitHub Discussions. Sign in with GitHub to post or react.