English | 简体中文
DeepSeek Harness plugin that auto-adapts the OpenCode Console (Zen) model catalog.
- GitHub: https://github.com/A42Null/dsh-opencode-zen-plugin
- CNB package registry: https://cnb.cool/A42Null/dsh-opencode-zen-plugin
- npm package:
@a42null/dsh-opencode-zen-plugin(published on npmjs; see Install below)
Paste an OpenCode Console API Key under Settings → Plugins and the Console's chat models become selectable in DSH. Model list, context windows, endpoint routing, and reasoning parameters are handled automatically.
What it does not do: it cannot make *-free models work (they only run inside the OpenCode client), it skips the jev-* SystemOne models (not chat-capable), and paid models still require a funded Console account.
- Zero-config model catalog: fetches
https://opencode.ai/zen/v1/models(unauthenticated) on start and on a timer (default 300s); models appear in the DSH model picker automatically - Automatic endpoint routing (per the official docs table):
gpt-*/grok-*/muse-spark*→ OpenAI Responses API (/zen/v1/responses)claude-*plusqwen3.8-flash/qwen3.7-max/qwen3.7-plus/qwen3.6-plus/qwen3.5-plus→ Anthropic Messages (/zen/v1/messages)gemini-*→ Google GenerateContent (/zen/v1/models/<id>:streamGenerateContent?alt=sse)deepseek/glm/minimax/kimi/qwen3.8-max/big-pickle/*-free→ OpenAI-compatible (/zen/v1/chat/completions)
- Metadata sync: context windows, output budgets, and reasoning tiers come from models.dev with a 7-day disk cache (
~/.opencode-zen/models.dev.json); an embedded static catalog guarantees the picker is never empty offline - Free models hidden by default:
*-freebelongs to OpenCode's in-client free tier; through the Console API it returns403 FreeTierError("free tier can only be used from within OpenCode"), so it is excluded from the picker unless you opt in - Readable billing/permission errors: insufficient funds (402), model access disabled (403), and the free-tier gate (403 FreeTierError) map to distinct error codes with actionable advice instead of a blanket "invalid API key"
- Reasoning passthrough:
off/minimal/low/medium/high/xhigh/max, mapped to each protocol's native field (reasoning_effort,thinking.budget_tokens,thinkingConfig.thinkingBudget,reasoning.effort) - Full harness stream protocol: streamed text / reasoning / tool-call blocks, usage accounting, stop-reason mapping, and error classification (AUTH, QUOTA, FREE_TIER_BLOCKED, MODEL_ACCESS_DISABLED, RATE_LIMIT, INVALID_REQUEST, SERVER, TIMEOUT, TRANSPORT, CONTEXT_WINDOW_EXCEEDED, EMPTY_RESPONSE)
- Tool calls & image input: vision models (claude- / gemini- / deepseek-v4-flash-vision, ...) accept image attachments
- Stream watchdog: aborts after 120s first-byte / 300s idle to avoid hung requests
- UI language follows DSH: the settings card renders in Simplified Chinese or English, matching the interface language
Option 1 is the recommended one: no git, no registry setup. Only option 3 (GitHub source) needs git on the machine.
dsh plugin add @a42null/dsh-opencode-zen-pluginPackage page: https://www.npmjs.com/package/@a42null/dsh-opencode-zen-plugin
Paste this into DSH under Settings → Plugins → Add plugin, or run it on the command line:
dsh plugin add https://github.com/A42Null/dsh-opencode-zen-plugin/releases/latest/download/dsh-opencode-zen-plugin.tgzThe URL always points at the newest release (the asset name carries no version, so it never rots).
dsh plugin add github:A42Null/dsh-opencode-zen-pluginpnpm resolves that repository with git ls-remote. Without git on the machine the install fails with
Command failed: git ls-remote "git+ssh://git@github.com/…" / 'git' is not recognized as an internal or external command —
use option 1 or 2 instead, or install Git for Windows first.
dsh plugin add D:\DSH\插件开发\opencode-zen-dsh-plugin, or install the local package directory via the plugin manager.
The same package is also published to the CNB registry https://cnb.cool/A42Null/dsh-opencode-zen-plugin. The name is scoped, so point that scope at CNB (don't change the default registry — that would affect every other package):
npm config set @a42null:registry https://npm.cnb.cool/A42Null/dsh-opencode-zen-plugin/-/packages/
dsh plugin add @a42null/dsh-opencode-zen-pluginThe same package is also published to GitHub Packages.
GitHub's npm registry requires a token even to read a public package — so treat it as a mirror/archive rather than the primary install route:
npm config set @a42null:registry https://npm.pkg.github.com
npm config set //npm.pkg.github.com/:_authToken <GitHub classic token with read:packages>
dsh plugin add @a42null/dsh-opencode-zen-pluginBoth mirrors only redirect @a42null/*; every other package keeps using your default registry. Undo with npm config delete @a42null:registry.
- Restart DSH (or reload plugins); the plugin registers provider
opencode-zen(override viaproviderIdincordis.patch.yml)
The package ships a dsh.bundle.patch (cordis.patch.yml), so installation writes the registration row into the profile patch automatically, and dsh.client provides the Web settings card.
The DSH settings UI is contributed by the plugin's client half (
lib/client.js): this plugin registers into theplugins.row.configslot (key =<package name>#<row id>), which gives the opencode-zen row under Settings → Plugins a Configure control. With only the host half (lib/index.js) the models work, but no settings UI appears anywhere.
| Field | Description | Default |
|---|---|---|
apiKey |
OpenCode Console API Key — required. Get it at https://opencode.ai/console | empty |
baseUrl |
OpenCode Zen gateway base URL | https://opencode.ai/zen |
includeFreeModels |
Include free (*-free) models — off by default, since they only work inside the OpenCode client |
false |
refreshSeconds |
Catalog auto-refresh interval in seconds (30–86400) | 300 |
Saving takes effect immediately — no DSH restart. As a fallback the key can also come from the OPENCODE_ZEN_API_KEY environment variable (the setting wins).
All endpoints use Authorization: Bearer <apiKey>; Anthropic endpoints also send x-api-key + anthropic-version: 2023-06-01, Google endpoints also send x-goog-api-key. The model-list endpoint is unauthenticated, so models are visible before a key is set — actual calls then fail with a clear AUTH error.
| Path | Content |
|---|---|
~/.opencode-zen/adapter-status.json |
Catalog health snapshot: status / total / updatedAt / lastError |
~/.opencode-zen/models.dev.json |
models.dev metadata cache (7-day TTL, atomic tmp+rename writes) |
~/.opencode-zen/plugin-load.json |
Last plugin-load marker (build / loadedAt / pid) — tells you which code version the running DSH actually loaded |
~/.opencode-zen/errors.log |
Raw adapter failures: time, HTTP status, protocol, model, error code, body excerpt (128 KB cap, auto-cleared) |
Error codes follow DSH's canonical taxonomy:
QUOTAfor an exhausted balance (drives the client's "quota used up" copy and the shell notice),AUTHfor key problems,FREE_TIER_BLOCKEDfor the free-tier gate,MODEL_ACCESS_DISABLEDfor missing model entitlement. The last two surface the plugin's own explanatory text in the UI.
| Symptom | Action |
|---|---|
Install fails with git ls-remote / 'git' is not recognized |
The machine has no git, and github: source installs need it → use option 1 (npmjs) or option 2 (prebuilt tarball), or install Git for Windows first |
| AUTH error | Missing or invalid API Key → paste one under Settings → Plugins → OpenCode Zen → Configure; the model list needs no auth, so models stay visible without a key |
| FREE_TIER_BLOCKED (403 FreeTierError) | A *-free model was selected; the free tier only works inside the OpenCode client → disable "include free models" or use a paid model |
| QUOTA (402 Insufficient account funds) | The key is valid but the Console balance is empty → top up at https://opencode.ai/console |
| MODEL_ACCESS_DISABLED (403 Model access is disabled) | The Console account lacks access to that model → pick a different model |
| RATE_LIMIT (429) | Too many requests → retry later |
| opencode-zen missing from Settings | The client half is not loaded (older install / no restart) → make sure lib/client.js exists and package.json declares dsh.client plus the "./client" export, then restart DSH and reload the page |
| SERVER / TRANSPORT / TIMEOUT | Gateway/network issue → retry later; check lastError in adapter-status.json |
| CONTEXT_WINDOW_EXCEEDED | Input exceeded the model's context window → shorten the session or pick a larger-window model |
| Empty or stale model list | Live-list failures fall back to the embedded static catalog; inspect adapter-status.json |
jev-* reports INVALID_REQUEST |
That family uses the SystemOne endpoint and is not chat-capable; excluded from the catalog |
| After upgrading to the scoped name: the Configure entry disappears / the API key reads as empty | The overlay row in ~/.dsh/profiles/desktop/cordis.patch.yml still names the old package → set its name to @a42null/dsh-opencode-zen-plugin and restart DSH (the API key is preserved) |
Startup fails with client-modules: duplicate factory registration / 1 entry did not activate |
0.3.5 shipped a client module id that no longer matched the package name → upgrade to 0.3.6 or newer; the boot-failure dialog's "disable third-party plugins, back up the profile patch and restart" button is the emergency recovery |
Uninstall/update fails with ERR_PNPM_TARBALL_URL_MISMATCH |
The lockfile's tarball source differs from the active registry → pin the scope: npm config set @a42null:registry https://registry.npmjs.org |
Push a tag that matches the version in package.json and the package is published to npmjs, the CNB registry and GitHub Packages automatically (workflow .github/workflows/publish.yml):
npm version patch --no-git-tag-version # or edit version in package.json manually
git commit -am "chore: release vX.Y.Z"
git tag vX.Y.Z
git push origin main --follow-tagsAll three registries are idempotent (an existing version is skipped, so re-pushing a tag never fails) and independent: one registry failing does not block the other two. The job verifies at the end that the version really is on npmjs.
On https://www.npmjs.com/package/@a42null/dsh-opencode-zen-plugin → Settings → Trusted Publisher:
| Field | Value |
|---|---|
| Publisher | GitHub Actions |
| Organization | A42Null |
| Repository | dsh-opencode-zen-plugin |
| Workflow name | publish.yml |
| Environment | cnb (must match the workflow's environment:; leave blank for any) |
CI then exchanges an OIDC token for publish credentials — no token needed (the workflow already declares permissions: id-token: write). This is npm's own migration target, since it plans to remove bypass-2FA direct publishing in January 2027.
Settings → Environments → cnb → Environment secrets:
| Secret | Required | Notes |
|---|---|---|
CNB_TOKEN |
yes | CNB access token (enable the package-registry scope when creating it) |
GH_TOKEN |
no (recommended) | GitHub classic token / PAT with write:packages, used to publish to GitHub Packages; when absent the built-in GITHUB_TOKEN is used (the repository's Actions workflow permissions must then be read/write) |
npmjs needs no secret at all (it uses OIDC trusted publishing; if the OIDC exchange fails the job prints a configuration checklist and fails). The three registries are independent and idempotent: one failing does not block the other two. The CNB npm username is always cnb (change CNB_USERNAME in the workflow if yours differs). A tag that does not match package.json fails the job, so a wrong version can never be published.
- OpenCode Console models & endpoints: https://opencode.ai/v2/docs/console/models
- OpenCode Console (paid gateway): https://opencode.ai/console