DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

type53 /

type53/dsh-openrouter-models

Verified

OpenRouter model catalog and multi-key router for DeepSeek Harness — live catalog, price filters, and one llm-pi-ai route per API key.

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@1fc0b05e

dsh-openrouter-models

English · 简体中文

CI License: MIT Node

An OpenRouter model catalog and multi-key router for DeepSeek Harness. It adds one settings page where you can:

  • browse OpenRouter's live model catalog (hundreds of models, changing with the upstream), search by name / id / vendor, filter by input or output price, toggle free only, and sort by price or context length;
  • keep several OpenRouter API keys as named "accounts", each bound to its own subset of models;
  • get one native model route per account (openrouter-<account>), so each key shows up separately in the model picker and in /model.

The plugin is deliberately self-contained: no runtime dependencies, no build step, and no imports from @deepseek-ai/*. The whole thing is the six files in lib/.

Why another OpenRouter plugin

OpenRouter speaks the OpenAI Chat Completions protocol, which Harness's llm-pi-ai service already drives. So instead of shipping a private streaming adapter, this plugin declares one ordinary llm-pi-ai route per key. You get the native picker, default-model selection, retries, replay handling, and credential resolution for free — and the plugin never handles token streams or speaks to the network on a request path.

Because a route resolves its key from its own credential reference, separate routes are exactly the host's mechanism for several keys against one provider.

Install

Requires DeepSeek Harness 0.1.5-rc.2 or newer and Node.js 20+.

Desktop app — open Settings → Plugins and install dsh-openrouter-models once it is on npm, or quit the app completely (tray included) and run:

# from npm (once the package is published)
dsh plugin --profile desktop add dsh-openrouter-models

# or straight from GitHub — no npm release needed
dsh plugin --profile desktop add github:type53/dsh-openrouter-models

# or from a local checkout you are editing
git clone https://github.com/type53/dsh-openrouter-models.git
dsh plugin --profile desktop add link:/absolute/path/to/dsh-openrouter-models

The CLI refuses to touch the desktop profile while the app is running, so quit it first. For a CLI-only profile (web, headless, or a custom name), use that name instead of desktop.

Restart DSH after installing — the host half mounts only at startup. Then open Settings → OpenRouter 模型 (the panel is Chinese today; see Internationalization).

Usage

  1. Create an account and name it (for example main or backup). The account id is derived from the name and becomes part of the route, so an ASCII name works best; a purely non-ASCII name falls back to account.
  2. Paste the key and save. It is written only to the harness credential store as OPENROUTER_KEY_<ACCOUNT>; it never enters the plugin's JSON file and is never sent back to the page. Press Verify key to check quota.
  3. Pick models from the catalog: select an account, then click models to add or remove them. Filter by name, by an input/output price range in $/M tokens, or with free only; sort by price, context, or name.
  4. Use them from the model picker at the bottom of a conversation, under OpenRouter · <account name>.

A key alone produces no route: llm-pi-ai rejects a hand-declared route with an empty model list. Bind at least one model to an account for it to appear in the picker.

How it maps to DSH

One account becomes one hand-declared llm-pi-ai provider:

llm-pi-ai:
  providers:
    openrouter-main:                    # account id = main
      displayName: OpenRouter · Main
      apiKeyEnv: OPENROUTER_KEY_MAIN    # credential reference, not the key
      api: openai-completions
      baseURL: https://openrouter.ai/api/v1
      compat: { supportsDeveloperRole: false, thinkingFormat: openrouter }
      models: [ ... the models you picked ... ]
  • The route is written into the profile's cordis.patch.yml (the user settings layer), so it takes effect on the next request without a restart. On every boot the plugin re-syncs it from the account file.
  • contextWindow, maxTokens, input modalities, and reasoningEfforts are filled from the live catalog when the route is derived. A copy is stored when you pick a model, so a route built while the catalog is unreachable still declares reasoning support correctly.
  • Removing an account or clearing its models removes only the route this plugin owns; other llm-pi-ai providers are untouched.

Data and security

Data Location
Accounts and their selected models $DSH_HOME/openrouter-models.json (default ~/.dsh/openrouter-models.json), written atomically, safe to inspect, back up, or delete
OpenRouter API keys The harness credential store, reference OPENROUTER_KEY_<ACCOUNT>
Derived routes The profile's cordis.patch.yml
  • Two outbound destinations, both on openrouter.ai: /api/v1/models (public catalog, no credentials attached) and /api/v1/key (only when you press Verify key, with that account's key). No telemetry, no third-party reporting.
  • All host routes are registered on Connection's authenticated /api channel, so they inherit Host/Origin checks and browser-session authentication. A request from an unauthenticated client is rejected with 401; plain webServer routes would not have that fence.
  • Keys are write-only: the state route reports only whether a key is configured, and verification returns only quota metadata.

See SECURITY.md for the disclosure process.

Development

No pnpm install is needed — the tests run on a bare checkout:

node scripts/unit-test.mjs        # pure logic: naming, validation, prices, catalog, route derivation, store
node scripts/host-boot-test.mjs   # host half: stub seams + all five HTTP routes
node scripts/client-boot-test.mjs # browser half: slot registration, filter/sort, render smoke test

npm test runs all three. CI runs them on Node 20, 22, and 24.

Source map:

File Responsibility
lib/index.js Host half: account → llm-pi-ai route reconciliation, the five HTTP routes
lib/config.js Constants, id / credential naming, account validation
lib/store.js Atomic read/write of the account document
lib/catalog.js Catalog fetch, normalization, price arithmetic
lib/routes.js Pure account → llm-pi-ai route mapping
lib/client.js Settings page UI (hand-written __ModuleLoader__ bundle, no build)

Client-only edits in lib/client.js apply on a page refresh: the bundle URL is content-hashed. Host edits need a DSH restart.

Layout note

The settings page is hosted by the platform's settings dialog, which is a fixed 800px panel (188px nav, 24px side padding), leaving roughly 564px of usable width — less in a small window, and a plugin cannot widen it (that CSS belongs to the platform). The layout is designed for that constraint:

  • no fixed control widths; inputs fill their grid cell;
  • buttons and inputs share one 28px height and one font size, so rows align;
  • account settings use a label | control grid with a shared left edge;
  • filters are a caption-over-control grid that wraps to about four columns;
  • accounts stack vertically, one per row;
  • model names and ids wrap (overflow-wrap: anywhere) instead of truncating — OpenRouter ids are long, and the id is what a request actually needs.

Known limitations

  • Filters are not persisted; they reset when you leave the page. Accounts and model selections are persisted.
  • The filtered list is capped at 600 rows to avoid rendering ~500 items at once.
  • Variable-priced models (openrouter/auto and a few other routers) report -1 upstream, meaning "depends on the route chosen". They display as variable, sort last, and match no price range.
  • All models use one protocol: openai-completions. OpenRouter serves that protocol for every model on /api/v1, so this does not reduce availability.
  • Deleting an account, disabling it, or clearing its models removes its openrouter-<id> route (only that one).
  • Account ids must match [a-z][a-z0-9-]* because they become route names and credential references.
  • A key with no models bound produces no route (see Usage).

Internationalization

The settings page currently ships Chinese strings only, matching the Harness UI this plugin was built against. Extracting strings into a locale layer is a welcome contribution; see CONTRIBUTING.md.

License

MIT © 2026 type53

This project is not affiliated with OpenRouter or DeepSeek. OpenRouter and DeepSeek Harness are the property of their respective owners.

—/ 5

No ratings yet

Verified DSH bundle

Commit 1fc0b05ef55a

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout