Vision & Multimodal

dsh-echelon

Abnerluo123/dsh-echelon · ★ 2

Abnerluo123

Echelon for DeepSeek Harness: name a few tiers, and each one becomes an ordered chain of real models with retries, failover, reasoning-level translation, context-window routing and vision routing.

License
MIT
Added
2026-10-30
Maintenance
active
Install kind
npm
npm
dsh-echelon
Manifest details
Manifest status
Declared
Manifest path
package.json#dsh
Manifest checked
2026-10-30T09:38:16.042Z
Lifecycle scripts
None found

Static manifest checks report declarations only; they are not compatibility or security review.

安装

dsh plugin --profile web add 'dsh-echelon@1.3.0'

从 npm 安装预构建包

For install and environment setup, plus plugin development tutorials, see DSH community docs ↗

README

Source README · body: en · Read Chinese README

Table of contents
  1. What it solves
  2. Three things that matter
  3. Models of different capability can back each other up
  4. A cleaner picker
  5. Compatible with existing plugins
  6. Quick start
  7. Advanced
  8. How the timeout is decided
  9. How the inputs behave
  10. How it differs from other tools
  11. Keywords
  12. Other install methods
  13. Known limitations
  14. Development
  15. License

Tier-based model routing for DeepSeek Harness. Collapse a dozen models into a few names you choose; behind each name is an ordered chain of real models — called in order, retried, failed over automatically.

version license

中文

dsh plugin --profile desktop add dsh-echelon

Configure tab

What it solves

The DSH model picker lists every model you can reach. With a few providers that becomes a dozen entries of differing names and capabilities, so each new conversation starts with "which one this time?".

Echelon replaces that with naming: define a few tiers (daily, deep, free…) and the picker shows only those names.

Three things that matter

Models of different capability can back each other up

This is the least obvious part. Mixing unequal models in one chain hits three obstacles, and each one turns "automatic failover" into silent degradation:

Obstacle What Echelon does
Reasoning levels named differently (medium / max / not declared) One vocabulary off / low / middle / high / xhigh, translated per member at dispatch; a level that cannot land falls back to the member's own default
Different context windows (1M beside 300K) Advertises the largest window, then picks a member that fits this request
Different vision support (multimodal beside text-only) An image request skips members that cannot see

So the strong and the cheap model can share one chain: small requests go cheap, large ones land on the big window, image requests land on the one that can see. You never have to know which happened.

Request log

One request in this log triggered all three: an image skipped the vision-less member, a large context skipped the small-window member, then a rate-limited call succeeded on retry.

A cleaner picker

Hide the real models, leave your tiers. The default is "show everything" — hiding is something you choose after a tier works. Hiding lifts automatically when no tier can dispatch, so the menu can never become an empty list that locks you out of settings.

Compatible with existing plugins

A member can be another plugin's route, because they are all just provider/model strings: dsh-our-free-model lanes, dsh-model-fusion's auto, anything that registers a provider.

No DSH patching. Echelon registers one provider route and wraps one public read-only method to implement hiding.

Quick start

dsh plugin --profile desktop add dsh-echelon

Requires DSH ≥ 0.2.0-rc.1 and Node ≥ 22.14.0. Restart dsh afterwards and reload the page.

  1. Configure → New tier, give it a name.
  2. Add models in priority order — that order is the failover order.
  3. Once it answers, switch Settings → What the model picker shows to "Tiers only".

A fresh install shows everything, so none of your models disappear.

Advanced

  • Per-member controls: retries, timeout, reasoning effort, context window, max output, vision (force it for a model that supports images but does not declare them).
  • Timeout measures progress, not traffic: a lane that only sends keepalives, empty deltas or usage rows is still dead and still fails over; reasoning counts as progress. 3–60s, default 6s on a fresh install, switchable off. DSH retries a failed turn itself, and a retry usually beats waiting.
  • Tier defaults: one timeout and retry count for the whole chain, used by members that do not set their own.
  • Default level for new chats: follow the model, or pin one of the five levels.
  • Reasoning mapping switch: on means one five-level vocabulary in the picker, translated onto each member at dispatch (nearest published level, or the model's default when it publishes none); off shows each model's own levels and forwards them as written. The per-member control in the editor always offers only that model's own levels — it pins one member, so it must not name a level that member cannot honour.
  • Image routing: an image-bearing request prefers a member that can see. With Settings → Advanced → Vision compatibility on, only images inside the window keep the whole chain on vision members (the last five messages by default, or a number of minutes); outside it, text-only members are candidates again and receive [图片] as text, leaving them to infer the content from the conversation. Members that can see still get the real image. Off by default, which is the earlier behaviour: any image still in the conversation requires vision.
  • Request log: each turn shows tier → real model, how the level was translated (xhigh → max, or not sent at all), every member tried, why each was skipped, duration and tokens. Written to disk, so a restart does not erase it; 128 turns kept by default, switchable off.
  • Context shorthand: 1m, 256k, 128k or plain digits — no counting zeros. k=1000, m=1000000 (decimal, so what the field shows is what it means).
  • Saves apply immediately: no restart, no page reload. One button top-right: save-and-reload when there are edits, plain reload when there are none.
  • Settings file: $DSH_HOME/model-router/settings.json, hand-editable (and "128k" is accepted there too); writes go through a temp file and a rename.

How the timeout is decided

The panel shows one line; the detail lives here.

The watchdog measures progress, not traffic. The difference matters: many gateways keep a stalled stream warm with heartbeats, empty deltas or usage-only rows, and a rule of "any data arrived" lets such an attempt run forever while the screen stays blank.

Counts as progress: a text-delta / reasoning-delta with non-whitespace text (one character is enough), a tool call with a name or non-empty arguments, a block boundary, the finish chunk. Does not count: empty strings, whitespace-only deltas, usage-only rows, unknown heartbeat types.

One budget covers all three places a member can stall:

Stage Before Now
resolveModelInfo no deadline (a hang left the log empty) 8s, then degrades to "capabilities unknown"
prepareCall no deadline the member's own budget
streaming only when nothing at all arrived same budget, judged by progress

On timeout the member's request is cancelled, and cleanup is bounded to one second — otherwise the cleanup itself blocks on the same read, which is how an 8-second budget produced a 277-second attempt.

On false positives: a model that thinks keeps emitting reasoning deltas and is never cut off, and a model waiting on a local command has already finished its call with a tool-call block — so a ten-minute local task is unaffected.

How the inputs behave

  • Nothing is validated or corrected while typing: you can clear "90" to "9", to empty, and retype.
  • Normalisation happens once, on blur or Enter: below the minimum becomes the minimum, above the maximum becomes the maximum, empty means "unset".
  • No stepper arrows; arrow keys and the wheel cannot change a value.

How it differs from other tools

Echelon is a DSH plugin, not a standalone model gateway — that difference decides what it is good for:

Echelon LiteLLM / One-API / New-API / OpenRouter
Form plugin inside DSH standalone service exposing an OpenAI-compatible endpoint
Reach only inside DSH any OpenAI-compatible client (Cursor, Cline, Cherry Studio, Chatbox…)
Setup install into DSH run a service, configure ports and keys
Routes by tier + context size + image + effort level usually model name, weights, quota, rate limits
Answers "which model should DSH use" "one entry point for every client"

So if you want a single OpenAI-compatible entry point for Cursor, Cline, Cherry Studio or Chatbox, LiteLLM or One-API is the right tool — Echelon does not expose an external endpoint and cannot do that.

If you want to stop memorising a dozen model names inside DSH, with automatic failover that also respects context size and images, that is what Echelon is for. The two can coexist: register LiteLLM as a DSH provider and let Echelon put it in the same tier as your free lanes.

Keywords

English: model router, model cascading, sequential fallback, automatic failover, multi-provider LLM, provider routing, rate-limit handling (HTTP 429), request retry, reasoning-effort mapping, context-window routing, vision / multimodal routing, DeepSeek Harness plugin

中文:模型路由 / 模型聚合 / 挡位 / 按序降级 / 接口兜底 / 失败自动切换 / 渠道自动切换 / 防限流 / 429 处理 / 免费模型聚合 / 多供应商 LLM / 思考档位映射 / 上下文窗口路由 / 视觉能力路由

Other install methods

Method Command
npm (recommended) dsh plugin --profile desktop add dsh-echelon
Pin a version append #v1.9.0
GitHub source dsh plugin --profile desktop add github:Abnerluo123/dsh-echelon
Plugin market Plugins → Market, search Echelon

Replace desktop with your profile name (dsh profile list).

Verify: dsh plugin --profile desktop list should contain dsh-echelon, and Echelon appears in settings.

Uninstall: dsh plugin --profile desktop remove dsh-echelon. Settings stay in settings.json; delete that file to remove them completely.

Known limitations

  • Hiding wraps llm.listModels: DSH has no catalog-filter hook, so code that reads an adapter directly is not filtered.
  • The request log keeps a bounded history: 128 turns by default, written to disk so a restart does not clear it. It is a debugging view, not an audit trail; the oldest turns are dropped past the limit.
  • An undeclared vision capability is unknown: still a candidate for image requests, but never preferred.
  • ABORTED and CONTEXT_WINDOW_EXCEEDED never fail over: you cancelled the first; the second would not fit elsewhere either.
  • The internal route id remains model-router: the rebrand did not change it, so saved sessions and settings keep working.

Development

node --test test/               # 219 tests, no network needed
node scripts/verify-profile.mjs # can the current profile load this plugin?

test/integration.test.js uses the real LlmRuntime, when the DSH app packages are present locally, to exercise registration, dispatch, hiding and failover.

File Responsibility
index.js Host entry: register the route, catalog filter, settings API
src/adapter.js Chain dispatch, streaming, retries, failover, log writes
src/config.js Normalization, chain expansion, context and modality filters
src/effort.js Effort vocabulary and per-member translation
src/tokens.js Request size estimation and image detection
src/catalog.js Catalog filter (hiding) and the unfiltered read
client.js Settings page and request-log view

License

MIT