dsh-model-list
OpenRouter free-models browser & one-click model config for DeepSeek Harness (DSH) — newest-first, local search, platform tabs. DSH 免费模型浏览器:按最新排序、本地搜索、一键添加到模型列表
- Stars
- 0
- Language
- JavaScript
- Created
- Aug 27, 2026
- Updated
- Aug 27, 2026
Introduction
dsh-free-model
dsh-free-model is a DeepSeek Harness (DSH) plugin that browses OpenRouter's free models and adds any of them to your DSH model list in one click.
- Platform tabs: the Settings entry hosts one tab per provider platform (OpenRouter today; the
PLATFORMSregistry makes future providers a one-line addition). All fetch/filter/sort logic runs host-side; the tab is the single entry point. - Free-model directory: fetches
https://openrouter.ai/api/v1/modelsfrom the host, keeps only models priced 0 (prompt & completion) or flagged:free, and sorts them newest first (by OpenRoutercreated) so newly launched models surface on top. Results are cached in$DSH_HOME/dsh-free-model.json(6-hour freshness window; a failed refresh degrades to the cached list with a stale notice). - Full info per card: name, id (click to copy), created date, context window, max output, modalities, tokenizer, pricing, supported parameters, moderated / forced-reasoning flags, expandable full description — plus a local second-stage search (id / name / description / vendor). Layout adapts to narrow panels and mobile widths (stacked cards, single-column grids, full-screen sheet dialog).
- "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/keyon 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-ainamespace — reuses the existingopenrouter/openrouter-free/ openrouter.ai route when one exists, preserving itsapiKeyEnvand model entries verbatim; otherwise createsopenrouter-freewith theopenai-completionsprotocol andhttps://openrouter.ai/api/v1), and - optionally sets it as the default model for new sessions (
agent-default-modelnamespace). The model is selectable in the chat composer's model selector immediately — no host restart.
- stores the token through the host credentials service (
- Robust fetching: every OpenRouter request retries up to 10 times (exponential backoff + jitter).
- 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 (free filter, sorting, route detection, config-write orchestration, store round-trip) |
npm run smoke | live smoke test against the real OpenRouter /models 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-free-model
# Published: npm / tarball / GitHub
dsh plugin --profile web add dsh-free-model
dsh plugin --profile web add github:you/dsh-free-model#<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.jsthrough native Node ESM, so@deepseek-ai/schemasteryand@deepseek-ai/dsh-settingsmust be resolvable from the plugin directory. Either runpnpm installinside the plugin directory, or use the committedlib/build (this repository commitslib/, mirroring dsh-jenkins).
Usage
- Open Settings → 免费模型 / Free Models, pick the platform tab (OpenRouter).
- The list arrives newest-first (served from cache when fresh; hit 刷新 to force a refetch).
- Type in the search box to narrow the list locally.
- Click 添加到模型列表 / Add to model list on a model:
- the token field appears only when the platform has no stored credential yet
(otherwise the existing
OPENROUTER_API_KEYis 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.
- the token field appears only when the platform has no stored credential yet
(otherwise the existing
Adding a platform
src/client/components/FreeModelsSection.tsx: append{ id, labelKey }toPLATFORMSand mount the panel by id.- Copy
src/client/components/OpenRouterPanel.tsxas a starting point; ops carryplatform: '<id>'. src/host/ops.ts: extend therunOpplatform dispatch (fetch/filter per the platform's API, map its models ontoRouteModelProfile, and teachmodel-config.tsthe platform's credential ref and route naming).
Structure
├── src/host/*.ts # Host half: /dsh-free-model/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'smodelsarray (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-free-model.json) is the only file it writes.