OpenRouter provider that reads the live /models list, so new models appear without a harness release: models sorted by name with input/output prices, per-model reasoning levels, image input, reasoning_details replay, Anthropic prompt caching, and actual spend per session including subagents from OpenRouter usage.cost, shown under the Web UI composer and by /cost.
Install
# from npm (prebuilt)
dsh plugin --profile web add dsh-openrouter-live
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:Boomerangz/dsh-openrouter-live
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
An OpenRouter provider for DeepSeek Harness (dsh) that reads OpenRouter's live model list, so a model shows up in the picker as soon as OpenRouter serves it.
The harness's built-in openrouter route lists models from a catalog bundled with each release. A model released after that snapshot, such as deepseek/deepseek-v4.1-flash, does not appear in the picker until the next harness release. This plugin adds a second route, OpenRouter (live). It reads OpenRouter's /models endpoint and takes each model's metadata from there.
What it does
Live model list. It lists every OpenRouter model that accepts tools, sorted by name, so models from one vendor stay together. Each name ends with the input/output price per million tokens. The list is refreshed every 10 minutes. If a refresh fails, the previous list stays in use.

Per-model metadata. The context window and output cap come from OpenRouter. The reasoning levels come from the model's own
reasoning.supported_efforts, so the composer offers only levels the model accepts, including Off when reasoning is optional.Image input. It inlines images for models whose
/modelsentry listsimageinput. It follows the harness image budget: images are downscaled, and the oldest images are offloaded when a request grows too large.Reasoning continuity. It sends
reasoning_detailsback on later requests, including signed and encrypted entries. This lets models such as Claude and DeepSeek keep their reasoning across tool calls.Prompt caching.
- Anthropic models get cache breakpoints on the system prompt and the newest message. In a live check, a second request read 7209 of 7223 prompt tokens from cache.
- Other providers cache automatically. The
x-session-idheader keeps each conversation on one upstream provider, so its cache stays warm.
Classified failures. Errors map to the harness failure codes, so the harness retry plugin retries only what is worth retrying:
- Retried: rate limits, with OpenRouter's
Retry-Afterhonored; upstream 5xx errors; idle streams; truncated streams; empty responses. - Not retried: bad keys, exhausted credits, and context overflow.
- Retried: rate limits, with OpenRouter's
Actual spend, including subagents. The plugin records the charge OpenRouter reports for every response (
usage.cost), so these are real charges, not token estimates. Each charge is attributed to the session that made the request and to every session it was delegated from. Type/costin the CLI or Web UI:OpenRouter spend in this session: $0.0022 (5 requests) this agent: $0.0011 (3 requests) subagents: $0.0011 (2 requests in 1 session) All recorded sessions: $0.0022 (5 requests).In the Web UI the session total is always visible under the composer, next to the built-in statistics and context meter. It updates during a turn, including subagent charges, and its tooltip splits the total between the agent and its subagents. It is hidden in sessions with no OpenRouter charges.

Charges are appended to
~/.dsh/openrouter-live/costs.jsonl, one JSON line per response, so totals survive restarts. Each line carries the OpenRouter generation id, so any charge can be matched against your OpenRouter activity page.Shared credential. By default the route reads the same
OPENROUTER_API_KEYcredential as the built-inopenrouterroute, so a key you already stored works without extra setup.
Requirements
- DeepSeek Harness
0.1.6-alpha.2through0.1.7. It has been tested on0.1.6-alpha.2and0.1.7-alpha.2. The harness API is pre-stable, so later harness versions are added to the peer range after they are tested. - Node.js
^22.19 || >=24. - An OpenRouter API key.
Install
From npm:
dsh plugin --profile web add dsh-openrouter-live
From a local checkout:
git clone https://github.com/Boomerangz/dsh-openrouter-live.git
cd dsh-openrouter-live
npm install
npm pack --pack-destination dist # runs typecheck and tests first
dsh plugin --profile web add "$PWD/dist/dsh-openrouter-live-0.2.0.tgz"
[!IMPORTANT] Install the packed
.tgz, not the directory. When you add a directory, pnpm links it, Node resolves the link to its real path outside the profile, and the harness packages (@deepseek-ai/*) can't be found there. The whole profile then fails to boot.
Restart the profile after installing. OpenRouter (live) then appears in the model picker. If you haven't stored a key yet, choose one of these:
- Enter it on the built-in OpenRouter card under Settings → Models. It is stored as
OPENROUTER_API_KEY, which both routes read. - Export
OPENROUTER_API_KEYbefore launchingdsh.
To choose it as the default model, pick it in the composer's model picker. You can also set it in ~/.dsh/settings.yaml:
agent-default-model:
provider: openrouter-live
model: deepseek/deepseek-v4.1-flash
Configuration
Every field is optional. Set fields in the profile's patch layer, ~/.dsh/profiles/<profile>/cordis.patch.yml, under the plugin's entry id openrouter-live:
- id: openrouter-live
config:
models: ['deepseek/*', 'anthropic/claude-*', 'openai/gpt-5*']
extraBody:
provider:
sort: throughput
| Field | Default | Meaning |
|---|---|---|
models |
[] |
Glob patterns over model ids. An empty list lists every model. Ids outside the filter still work when typed. |
toolsOnly |
true |
List only models whose OpenRouter entry accepts tools. Agent turns need tools. |
priceInName |
true |
Append input/output prices per 1M tokens to model names, such as DeepSeek: DeepSeek V4.1 Flash ($0.15/$0.60), because the pickers show names but not descriptions. The price then also appears in the composer's model button. |
apiKeyEnv |
OPENROUTER_API_KEY |
Credential reference: a credential-store entry or environment variable. |
provider |
openrouter-live |
Route id. Give each instance its own id to run several side by side, for example one with a *:free filter. |
displayName |
OpenRouter (live) |
Provider name in the picker. |
baseURL |
https://openrouter.ai/api/v1 |
API root. |
modelListTtlMs |
600000 |
How long one fetched model list is reused. |
maxTokens |
32768 |
Output cap for requests that set none. A lower model limit wins. |
defaultContextWindow |
262144 |
Context size assumed for an id the list does not describe. |
streamIdleTimeoutMs |
300000 |
Longest silence inside a response stream before failing with TIMEOUT. |
maxRequestImageBytes |
20971520 |
Base64 image budget per request. Older images are offloaded beyond it. |
requestImagePixelBudget |
4194304 |
Pixel budget of one request image. |
requestImageMaxBytes |
1048576 |
Encoded-byte target of one request image. |
appTitle / appUrl |
DeepSeek Harness / unset |
OpenRouter app attribution headers (X-OpenRouter-Title, HTTP-Referer). |
extraBody |
{} |
Fields merged into every request body, such as provider routing. Fields the plugin sets take precedence. |
costLedgerPath |
$DSH_HOME/openrouter-live/costs.jsonl |
Spend ledger file. An empty string keeps spend in memory for the current process only. |
costCommand |
cost |
Slash command that reports spend. Leave it empty on a second instance that shares the ledger, so the command is registered once. |
costIndicator |
true |
Serve the Web UI spend indicator's data route (/openrouter-live/spend). Disable it on a second instance, which would otherwise claim the same route. |
retryPolicy |
harness default | The harness's provider retry policy for this route: {mode: normal, maxRetries, retryableCodes, backoff} or {mode: always}. |
How it maps onto OpenRouter
| Harness | OpenRouter chat completions |
|---|---|
| System prompt, user and assistant text | system, user and assistant messages |
| Tool definitions and tool calls | tools (function type) and tool_calls |
| Tool results | tool messages. Images returned by a tool follow in a user message, because tool messages carry text only. |
| Images | image_url data URLs, each preceded by the harness's image handle line |
| Reasoning effort | reasoning.effort |
| Reasoning output | The reasoning stream becomes reasoning blocks. reasoning_details is kept for replay. |
| Usage | prompt_tokens is split into uncached input, cache reads and cache writes. Reasoning tokens are reported separately. |
Development
npm install
npm run check # tsc --checkJs (strict) + node:test suite
OPENROUTER_API_KEY=... npm run smoke # live checks against OpenRouter; costs a few cents
The plugin is plain ESM JavaScript with JSDoc types, so no build step is needed to run it. npm pack generates the .d.ts files in types/. The test suite includes a composition test that boots the real LlmRuntime from @deepseek-ai/dsh-llm in a Cordis context and streams through it.
Known limitations
Spend covers only requests through this plugin's routes. The built-in
openrouteranddeepseek-officialroutes are not counted.A charge is recorded when OpenRouter reports it at the end of a response. A request that is cancelled or fails mid-stream may still be billed by OpenRouter, but it does not appear in
/cost.The Web UI indicator reads a route that answers only same-origin requests to a loopback address (
127.0.0.1,localhost), because the harness web server does not authenticate plugin routes. When the Web UI is opened over the network, the indicator stays hidden;/coststill works./costneeds the harness command service, which the CLI and Web profiles mount. Headless runs still write the ledger.Only OpenRouter's text output is used. Image generation and audio models are listed only when they also accept tools, and their non-text output is ignored.
Prices in the picker are OpenRouter's base prices. Time-of-day price overrides are not shown.
Explicit cache breakpoints are sent only for
anthropic/*models. Every other model relies on its provider's automatic caching.A model that reasons but publishes no
supported_effortsgets no reasoning selector, so OpenRouter's default applies.
License
MIT
Links
More in this category
V1ki/dsh-plugin-subscriptions★ 379
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★ 318
Unofficial Command Code LLM provider: registers a `commandcode` route with a live model catalog and reasoning-effort support.
corrinehu/dsh-workbuddy-connect★ 186
Brings the models in the WorkBuddy desktop app straight into DeepSeek Harness — zero configuration in the DSH chat.
volcengine/ark-cli#ark-plan-api★ 137
Registers Ark Agent Plan, Coding Plan and postpaid model routes in the native DSH model picker.
cv-superding/dsh-deepseek-web-login★ 130
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.
franksong2702/dsh-codex-connect★ 118
Connect ChatGPT OAuth and OpenAI Codex models to DeepSeek Harness, with opt-in search and image tools.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.