Route OpenRouter requests through a configured provider list and quantization cap, injected as provider.only/order with allow_fallbacks, plus provider.quantizations.
Install
# from npm (prebuilt)
dsh plugin --profile web add dsh-openrouter-providers
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:MoRanYue/dsh-openrouter-providers
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
中文 | English
A DeepSeek Harness plugin: fill in the provider list and quantization cap that OpenRouter requests should use, and the plugin injects them as provider.only / provider.order / provider.quantizations routing parameters into every OpenRouter model request. The plugin owns its configuration and writes it to $DSH_HOME/openrouter-providers.json (~/.dsh/openrouter-providers.json when DSH_HOME is unset); it is restored after a restart.
Compatible DSH versions: 0.1.5-rc.1 through 0.2.1-alpha.1 (engines.dsh: >=0.1.5-rc.1 <0.3.0). This plugin imports no @deepseek-ai/dsh-* package at all — its only peer is @deepseek-ai/cordis — so neither the 0.1.7 nor the 0.2.x breaking changes reach it; the 0.2 major-version crossing was re-verified item by item on a real 0.2.0-rc.2 and 0.2.1-alpha.1 (config round-trip, llm/stream reroute, real OpenRouter routing, Plugins-page title and icon).
Why the settings service is no longer used: DSH
0.1.7removed settings namespace registration (settings.register/SettingsScope/watch) and now enumerates only.volatile()fields on profile entry Configs. A plugin built on the old API fails silently on 0.1.7:applyreturns early, so neither the HTTP route nor thellm/streamreroute is registered, the settings page reports "Cannot read the current state (Host half unavailable)", and request injection never takes effect. This plugin owns its configuration document instead and treats the old namespace as an optional, capability-detected enhancement (below), so behavior is identical on old and new hosts.
Configuration entry points (dual-stack): DSH 0.1.6-alpha.2 moved plugin configuration out of Settings onto the new sidebar Plugins page and retired settings.plugin.item. This plugin registers both slots and lets the host's declarations decide which one applies (the undeclared one simply never fires — no error either way):
| Host | Slot | Where |
|---|---|---|
DSH ≥ 0.1.6-alpha.2 |
plugins.bundle.config (keyed by package name) |
Sidebar Plugins → this bundle's page → configuration form |
DSH < 0.1.6-alpha.2 |
settings.plugin.item |
Settings → Plugins → Plugin configuration → collapsible card |
On old hosts that card is dispatched per settings namespace the Host serves, so the plugin probes for settings.register and registers an empty pass-through namespace only when it exists — purely so the card can be dispatched; it carries no values. From 0.1.7 on, that API is gone, the namespace is skipped, and nothing else is affected.
Features
- Configuration form: fill in provider slugs (one per line), pick the routing mode, and pick the quantization cap:
- Only these providers → the request body carries
provider: { only: [...], allow_fallbacks: false } - Try in order → injects
provider: { order: [...], allow_fallbacks: true } - Quantization cap → injects
provider: { quantizations: ['int4' | 'int8' | ...] }(optional, unrestricted by default; valid values in OpenRouter Quantization) - The whole feature has a master switch; saving writes the plugin-owned configuration file (
$DSH_HOME/openrouter-providers.json). On the new Plugins page only a save writes and leaving the page drops staged edits; the legacy card additionally offers a Discard button and an unsaved badge.
- Only these providers → the request body carries
- Localized UI: copy comes from the plugin's
openrouter-providerslocale namespace (zh/en); switching the language re-renders the UI live with no page reload. On hosts without the locale service it falls back to Chinese copy. - Display name and icon on the Plugins page:
meta.title/meta.descriptionin the packagedlocale/zh.jsonandlocale/en.jsonprovide the localized display name (OpenRouter Providersin English, 「OpenRouter 提供商列表」in Chinese), so the plugin list shows a readable name instead of the bare package namedsh-openrouter-providers;icon: "./icon.svg"inpackage.jsonsupplies the card and row artwork (three fading rounded bars, suggesting a provider list narrowing step by step). Both follow the DSH interface language. Note thatexportsmust include"./locale/*.json": "./locale/*.json", otherwise Node's exports resolution throwsERR_PACKAGE_PATH_NOT_EXPORTEDand the display name silently falls back to the package name;iconmust be a relative path with a.svg/.png/.jpg/.jpeg/.webpextension, inside the manifest directory and at most 256 KiB — a violation is reported rather than ignored, but only the icon is lost and the name still applies. - Request injection: the plugin listens on the
llm/streamwaterfall — when the requested provider route isopenrouter(enabled, with a non-empty list or a quantization cap), the request is rerouted to the plugin's own chat-completions adapter, which builds the request body and injects theproviderfield;reasoning.effort(off/low/medium/high/max, all valid OpenRouter values) passes through unchanged. The session log and UI still showopenrouter. - Credentials: reuses the existing
OPENROUTER_API_KEY(resolved through thecredentialsservice, matchingllm-pi-ai'sapiKeyEnv). - App attribution: requests carry
HTTP-Referer: https://github.com/deepseek-ai/deepseek-harness,X-OpenRouter-Title: DeepSeek Harness OpenRouter, andX-OpenRouter-Categories: cli-agent, so OpenRouter's UI and leaderboards showDeepSeek Harness OpenRouterinstead of Unknown (OpenRouter App Attribution).
Installation (bundle mount)
The plugin mounts as a bundle package, like other plugins (dshmarket, dsh-recall-plugin, …): its cordis.patch.yml insert row is merged into the profile composition automatically.
Option A: install from npm (recommended)
In the profile directory (for example ~/.dsh/profiles/web/):
pnpm add dsh-openrouter-providers@latest
Then add the package to the profile package.json's dsh.profile.bundles list:
"dsh": { "profile": { "bundles": [ ..., "dsh-openrouter-providers" ] } }
Restart dsh web. The bundle coordinator merges the package's cordis.patch.yml (insert row: id: openrouter-providers) on its own.
Option B: install from GitHub
pnpm add dsh-openrouter-providers@github:MoRanYue/dsh-openrouter-providers
The remaining steps are the same as Option A (add to dsh.profile.bundles and restart).
Option C: local path
pnpm add dsh-openrouter-providers@file:D:\\path\\to\\dsh-openrouter-providers
# then add the package name to dsh.profile.bundles and restart
Manual mount (without the bundle)
Append to ~/.dsh/profiles/web/cordis.patch.yml:
- insert:
- id: openrouter-providers
name: 'dsh-openrouter-providers'
Then add the package to the profile package.json's dependencies and dsh.profile.bundles, run pnpm install, and restart.
Usage
- Make sure the model's provider route is
openrouter(pick a model under OpenRouter in the model selector). - Open the configuration entry for your host (see the table above: sidebar Plugins → this bundle's page on DSH ≥
0.1.6-alpha.2, or Settings → Plugins → Plugin configuration on older hosts), fill in provider slugs (such asDeepInfra,Together), choose the routing mode and quantization cap, and save. - Every later OpenRouter request carries the injected provider routing parameters.
Provider slug format: see the OpenRouter Provider Routing docs (
order/onlyfields).
How it works (brief)
- Transport: the dynamic plugin environment has no built-in
fetch, so the adapter spawns anode -echild process through thesubprocessservice to perform HTTP + SSE streaming parsing (text/reasoning/tool-call deltas, usage,[DONE], error classification such as AUTH/RATE_LIMIT/INVALID_REQUEST/SERVER). - State persistence: the plugin owns its configuration and writes
$DSH_HOME/openrouter-providers.json(~/.dsh/…whenDSH_HOMEis unset), independent of the settings service. On first start, while the file still holds shipped defaults, the plugin tries each legacy location once, in order:<workspaceRoot>/.dsh-plugins/openrouter-providers.json(v1.0.4 and earlier), then$DSH_HOME/settings.yaml.imported(the renamed settings document 0.1.7 left behind, whose section for this plugin could not be imported because it declares no Config). The first source that holds values wins, and an existing configuration is never overwritten afterwards. - Client communication: the configuration card reads and writes state over the HTTP API
GET/POST /api/openrouter-providers/state(registered on the Host half throughwebServer). The client now shows the status code and response excerpt for a non-2xx response instead of reporting every failure as "Host half unavailable". - Adapter contract: the plugin registers a plain object adapter (not an
LlmAdaptersubclass), so it has no base-class defaults to fall back on and must implement every member the host calls. Besidesstream,LlmRuntimecallsproviderInfo/providerRetryPolicy/imageRequestPricing/listModels/resolveModel/prepareCall— omit any one and that call site throwsTypeError: adapter.<name> is not a function. This plugin'slistModels()returns an empty array, so the synthetic route never appears in the model catalog (it is reachable only internally, fromllm/stream);imageRequestPricing()returnsundefined, leaving the token meter and spill policy on their own neutral estimates.
Limitations
- Image input is not supported (a model request containing images returns
UNSUPPORTED_CONTENT). - In
onlymodeallow_fallbacks=false: the request fails when none of the listed providers is available (OpenRouter behavior).
License
MIT
Links
More in this category
V1ki/dsh-plugin-subscriptions★ 422
Use ChatGPT (Codex), Claude, and Grok subscriptions as DeepSeek Harness LLM providers, with Settings login, model catalogs, usage, plus image_generate, video_generate, and x_search tools.
Mars-Sea/dsh-commandcode-provider★ 370
Unofficial Command Code LLM provider: registers a `commandcode` route with a live model catalog and reasoning-effort support.
corrinehu/dsh-workbuddy-connect★ 303
Brings the models in the WorkBuddy desktop app straight into DeepSeek Harness — zero configuration in the DSH chat.
cv-superding/dsh-deepseek-web-login★ 230
Adds a deepseek-web provider that uses chat.deepseek.com web models in DSH, with browser login capture, PoW request signing, SSE streaming, and prompting-based tool calls.
FishBottle7/opencode2dsh★ 143
Exposes OpenCode Zen free models to DeepSeek Harness, with no API key.
WSL043/dsh-codex-subscription★ 142
ChatGPT OAuth provider for Codex models with subscription web search, quota and safe reset, image tools, Fast mode, and model-aware context; no API key or Codex CLI.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.