dsh-model-list
dsh-model-list logo
dsh-model-list is a DeepSeek Harness (DSH) plugin that
browses OpenRouter's model directory (the official openrouter.ai/models catalog) and adds any model —
free variants included — to your DSH model list in one click.
- Model Hub entry (设置 → 模型聚合平台 / Model Hub): the Settings entry hosts one tab per provider
platform (OpenRouter today; the
PLATFORMS registry makes future providers a one-line addition). All
fetch logic runs host-side; the tab is the single entry point.
- Show-in-menu toggle: a slide switch at the very top of the platform page. When on, a
【模型聚合平台 / Model Hub】 entry button appears in the host sidebar's footer action area and opens the
same directory in a large overlay. The preference persists in
localStorage (default off).
- Dialog palette: the overlay and dialogs follow dsh-get-balance — a
rgba(0,0,0,.32)
scrim with blur(12px) saturate(1.2), a color-mix(bg-layer-1 78%) glass panel with a
border-l2 hairline, 14px radius and a 0 16px 48px rgba(0,0,0,.28) shadow, border-l1
dividers, solid button-primary-fill for primary buttons and active tabs / sub-tabs /
filter chips, bg-base inputs, bg-layer-2 cards, and state-* tokens for status colours.
- Official directory, 1:1: the host fetches the official frontend endpoint
https://openrouter.ai/api/frontend/v1/models/find?active=true&fmt=cards&order=newest — once with
variant=standard and once with variant=free (both return one card per model, no pagination) — and
joins each card's weekly token usage from the response's data.analytics. The browser renders the
official filter set client-side: search, sort (weekly usage / newest / context size / input price /
output price / name), input & output modalities, prompt-pricing buckets (Free / <$1 / $1–$2 / $2–$5 /
$5–$10 / >$10 per M tokens), context-length buckets (<8K / 8K–32K / 32K–128K / 128K–1M / >1M),
capabilities (tools / reasoning / structured outputs), series and providers. Cards follow the official
anatomy: author avatar, name, weekly usage, provider, description, and spec chips (context, max output,
input/output price per M tokens, modality arrows, capability badges).
- Full info per card: id (click to copy), provider, quantization, created date, supported parameters,
moderated / forced-reasoning flags, upstream warnings, expandable full description. Layout adapts to
narrow panels and mobile widths.
- "Add to model list" → one-click model config: the dialog takes an OpenRouter API token only when the
platform has no stored credential yet (live-checked via
GET /api/v1/key on demand). Confirming then
- stores the token through the host credentials service (
.credentials.yaml, OPENROUTER_API_KEY),
- merges the model into the OpenRouter route in
settings.yaml (llm-pi-ai namespace — reuses the
existing openrouter / openrouter-free / openrouter.ai route when one exists, preserving its
apiKeyEnv and model entries verbatim; otherwise creates openrouter-free with the
openai-completions protocol and https://openrouter.ai/api/v1), and
- optionally sets it as the default model for new sessions (
agent-default-model namespace).
The model is selectable in the chat composer's model selector immediately — no host restart.
- Robust fetching: every OpenRouter request retries up to 10 times (exponential backoff + jitter);
a failed refresh degrades to the cached list with a stale notice. Results are cached in
$DSH_HOME/dsh-model-list.json (6-hour freshness window).
- Bilingual UI (中文 / English), no hardcoded paths.
中文文档
Commands
| Script | What it does |
|---|
npm run check | tsc -b typecheck (host + client programs) |
npm run build | clean lib/, tsc -b, then tsdown bundles lib/index.js (host, ESM) + lib/client.js (browser, __ModuleLoader__ factory) |
npm run watch | tsdown watch mode |
npm run verify | simulate the host module loader against lib/client.js (factory shape + seed externals) |
npm run test:ops | offline host-logic tests (find/cards normalization, weekly-usage join, route detection, config-write orchestration, store round-trip incl. v1-cache rejection) |
npm run smoke | live smoke test against the real OpenRouter models/find endpoint |
npm run release | check + build + verify, then npm version patch and git push --follow-tags |
npm run publish:npm | check + build + verify, then npm publish --access public |
Installation
# Local development (link the workspace; picks up rebuilds of lib/ automatically)
dsh plugin --profile web add ./dsh-model-list
# Published: npm / tarball / GitHub
dsh plugin --profile web add dsh-model-list
dsh plugin --profile web add github:you/dsh-model-list#<sha>
dsh --profile web --dump-config # verify the layer
dsh --profile web # start (restart required for the host half to load)
Local development dependencies: the host loads lib/index.js through native Node ESM, so
@deepseek-ai/schemastery and @deepseek-ai/dsh-settings must be resolvable from the plugin
directory. Either run pnpm install inside the plugin directory, or use the committed lib/
build (this repository commits lib/, mirroring dsh-jenkins).
Usage
- Open Settings → 模型聚合平台 / Model Hub (or enable 在菜单中显示 / Show in menu at the top of the
page and use the 【模型聚合平台】 button at the bottom of the host sidebar), pick the platform tab (OpenRouter).
- The directory arrives from cache when fresh (hit 刷新 to force a refetch). Use the search box, the
variant dropdown, the output-modality tabs, the sort dropdown (weekly usage / newest / context / prices /
name), and the filter groups (input modalities, discount, context length, prompt & output pricing,
series, supported parameters, providers, model authors) — the official models-page set.
- Click 添加到模型列表 / Add to model list on a card:
- the token field appears only when the platform has no stored credential yet
(otherwise the existing
OPENROUTER_API_KEY is reused and noted in the dialog),
- optionally tick 设为新会话的默认模型,
- confirm — the config is written and the dialog shows the exact route/model to pick
in the composer's model selector.
Adding a platform
src/client/components/FreeModelsSection.tsx: append { id, labelKey } to PLATFORMS and mount the
panel by id.
- Copy
src/client/components/OpenRouterPanel.tsx as a starting point; ops carry platform: '<id>'.
src/host/ops.ts: extend the runOp platform dispatch (fetch/filter per the platform's API, map its
models onto RouteModelProfile, and teach model-config.ts the platform's credential ref and route
naming).
Structure
├── src/host/*.ts # Host half: /dsh-model-list/api route (platform-dispatched ops),
│ # OpenRouter fetch+filter+sort (10 retries), cache store,
│ # model-config writer (settings + credentials services)
├── src/client/*.tsx # Browser half: settings.section shell with platform tabs,
│ # per-platform panel (search, cards), add-to-model-list dialog
├── lib/index.js # Host half build artifact (tsdown, ESM), committed
├── lib/client.js # Browser half build artifact (tsdown → __ModuleLoader__ factory), committed
├── scripts/ # verify-client.mjs, test-ops.mts (offline), smoke-openrouter.mts (live)
├── tsdown.config.ts # node half + client bundle banner wrapper
├── cordis.patch.yml # Bundle patch: plugin row referenced by package name
└── package.json # dsh.bundle + dsh.client(web) manifests + peerDependencies
Notes
- OpenRouter free models still require an account token (daily rate limits apply); the dialog says so.
- The write path is the sanctioned one:
settings.update('llm-pi-ai', …) validates against the
llm-pi-ai schema (a bad profile is refused where it is written) and the adapter re-registers routes
live, so no host restart is needed after adding a model.
- Manual edits to an existing OpenRouter route (custom
apiKeyEnv, extra profile fields) are preserved:
adding a model only rewrites that route's models array (merged by id, same id → replaced).
- The plugin never touches settings/credentials files directly — everything goes through the host
services; its own cache file (
dsh-model-list.json) is the only file it writes.