dsh-model-reasoning-levels
Manifest validPer-model reasoning effort levels inside DeepSeek Harness Settings -> Models, written to that model's reasoningEfforts in cordis.patch.yml.
dsh-model-reasoning-levels
Per-model reasoning effort levels inside DeepSeek Harness's Settings → Models. English | 简体中文
Model Reasoning Levels — the reasoning-effort control each model entry is
missing, written straight into that model's reasoningEfforts in the profile's
cordis.patch.yml.
The shipped Models page edits a model's id, display name, context window,
max-output tokens and input types, and deliberately edits no reasoning
effort: effort is a per-model capability while that editor is per-provider.
This plugin adds the missing control inside each model entry — directly
under the id/name line, or under the expanded context-window/input-types area —
and converts the selection into the model's reasoningEfforts in the profile's
cordis.patch.yml.
What it contributes
| Surface | What it does |
|---|---|
settings.models.provider-card (keyed llm-pi-ai) | reaches each pi-ai provider card, discovers its model rows, and portals a per-model effort editor into every model entry — hand-declared routes included |
Locale namespace dsh-model-reasoning-levels | English and Simplified Chinese copy |
scripts/reasoning-efforts.mjs | validates a selection and prints the reasoningEfforts YAML and the settings path op it becomes |
Browser half only: the host half (exports["."]) exists so the Loader row can
carry the browser half (exports["./client"]). No route, no service, no Config.
What it looks like
Settings → Models → Baidu Team → Customized settings → Models
┌─────────────────────────────────────────────────────────────┐
│ [deepseek-v4.1-flash] [DeepSeek-V4.1-Flash] ⌄ 🗑 │
│ Reasoning effort levels Inherited │
│ [Off] [Minimal] [Low] [Medium] [High] [XHigh] [Max] │
│ Low wire value: low High wire value: high │
│ [Inherit catalog] [Non-reasoning] [Apply] { off: null, … } │
└─────────────────────────────────────────────────────────────┘

The control inside one model entry: the seven level chips, one wire-value field
per selected thinking level, Inherit catalog / Non-reasoning / Apply,
and the exact reasoningEfforts value the current selection becomes.
How the control reaches the row
The shipped page declares no seat inside a model row, and its ModelRow is
an internal component. The only sanctioned row-level change is shadowing the
whole settings.section models entry — a shadowed copy of the page. This plugin takes
the narrower path instead: it owns the provider-card seat, then discovers each
model row structurally.
- The row's id input carries an
aria-labelbuilt from the shipped page's ownsettings.modelslocale namespace (Model ID 1,模型 ID 1, …). The plugin binds that namespace and matches the label, so discovery follows the active language and never depends on a hashed CSS class name. - It appends one container per
modelEntryand portals a React editor into it, so the control lives inside the model entry and disappears with the row. - A
MutationObserverre-runs discovery when the fold opens, a row is added or removed, or the page re-renders. A shipped row restructure would need the selector updated.
Saving, and the page's own Apply
The editor writes through the settings transport (settings.mutate path ops);
the host persists the result. The shipped page keeps its own draft of the
model list and its own Apply writes the whole models array for a hand-declared
route, which can restore the reasoningEfforts value the card loaded at open.
To keep the editor's value authoritative, the row remembers the declaration the user applied and, if a later settings change reverts it, writes it again — up to three times, so a refusal is reported instead of retried forever. Applying the effort and then using the card's Apply for another field therefore converges on the effort value you chose.
For a catalog route the editor uses the narrow
providers.<route>.modelOverrides.<id>.reasoningEfforts path, which the page's
model editor does not touch — unless the page materializes a models array (by
editing the model list), which the adapter refuses beside modelOverrides. The
row's re-apply covers that case while the card is open.
The level vocabulary is closed and ordered
off, minimal, low, medium, high, xhigh, max — exactly the harness's own
THINKING_LEVELS, in escalation order. The editor always emits the map in that
order, whatever order the chips were clicked. off means "do not think"; the
other six are thinking levels.
The conversion rules (they are the adapter's)
Each chosen level becomes a key whose value is the spelling dispatch sends, so a
gateway with its own vocabulary can be renamed (max: ultra). The plugin
enforces @deepseek-ai/dsh-llm-pi-ai's resolveModelReasoning rules before the
write:
| Selection | Result |
|---|---|
off, low, high, max with wire values | { off: null, low: 'low', high: 'high', max: 'max' } |
off alone | refused — a model that cannot think uses false |
| nothing selected | refused — omit the field to inherit |
a non-off level with no wire value | refused — it needs the value dispatch sends |
| an empty-string wire value | refused |
| a level outside the seven | refused |
| the "Non-reasoning" form | reasoningEfforts: false |
| the "Inherit catalog" form | the field is unset, restoring inheritance |
A declared map replaces the model's capability set: levels not declared are
not offered. That is why off must be present in the map when the user keeps
it, and why "off only" is false rather than { off: null }.
Where the value lands in cordis.patch.yml
The editor writes settings path ops; the host persists them into the profile's
cordis.patch.yml. reasoningEfforts sits beside the fields the shipped row
already edits:
- id: llm-pi-ai
name: "@deepseek-ai/dsh-llm-pi-ai"
config:
providers:
baidu-team:
models:
- id: deepseek-v4.1-flash
reasoningEfforts: { off: null, low: 'low', high: 'high', max: 'max' }
contextWindow: 1000000
maxTokens: 256000
input: [text, image]
name: DeepSeek-V4.1-Flash
The expanded spelling is equivalent and easier to hand-edit:
reasoningEfforts:
off:
low: 'low'
high: 'high'
max: 'max'
A model that cannot think, and one that keeps the installed catalog capability:
- id: acme-plain
reasoningEfforts: false # no selector beyond off
- id: acme-catalog
contextWindow: 128000 # reasoningEfforts omitted -> inherits
The two homes, and why the editor picks between them
llm-pi-ai carries a model entry in one of two mutually exclusive shapes:
| Home | Shape | The editor's write |
|---|---|---|
providers.<route>.models[] | list, entries carry id | replace-by-value: the whole array |
providers.<route>.modelOverrides.<id> | dict keyed by model id | one narrow path: reasoningEfforts only |
The adapter refuses modelOverrides beside a models list, on a hand-declared
route, and for an id the installed catalog does not describe. So the editor uses
models when the route configures one (or is hand-declared), and the minimal
modelOverrides path when the catalog describes the route and no models list
exists. For models, the complete array is materialized so one edited entry
never deletes the rest of the catalog; a narrow modelOverrides write pins
nothing else on the entry.
The editor reads the list the way the page does — the user layer when it owns
models, else the composition base, else the schema default — and loads the
installed catalog through llm/discoverModels (no endpoint I/O for a catalog
provider) when neither declares a list. It never quotes the resolved value
into a write.
Requirements
- DeepSeek Harness
0.2.0-rc.2— the plugin declares it inengines.dshand pins the@deepseek-ai/dsh-*peers to that line. - Node.js >= 22.17 and pnpm 11.8.0 to build from source.
- A profile you can write to. On the desktop app the install path is Settings → Plugins (see Install).
Install
This repository ships TypeScript source only — lib/ is gitignored — so build
the bundle before installing it:
git clone https://github.com/icrefin/dsh-model-reasoning-levels.git
cd dsh-model-reasoning-levels
pnpm install
pnpm build # tsc -> lib/, tsdown -> lib/client.js
sh scripts/install.sh # packs dist/ and prints the install path
sh scripts/install.sh --profile <profile> # or pack and install into that profile
scripts/install.sh packs dist/dsh-model-reasoning-levels-<version>.tgz and
prints what to do with it. With --profile <profile> it installs the tarball
into a CLI-managed profile instead of printing instructions.
The desktop profile is owned by the Electron application, so install the tarball
from Settings → Plugins (a plugin-manager tool works too); dsh plugin add
refuses that profile. Adding a bundle to a running profile needs a restart;
the client half additionally needs the page reloaded.
The development suite runs the same checks:
pnpm check # typecheck both halves, build, then vitest
pnpm yaml check --levels off,low,high,max --wire low=low,high=high,max=ultra
pnpm check typechecks the host and the browser halves separately, builds the
bundle and runs the suite — 50 tests in 5 files.
Usage
- Open Settings → Models and expand a pi-ai provider card (for example the
hand-declared
baidu-teamroute, or a catalog route such as OpenAI). - Under Customized settings → Models, every model row now carries its own
Reasoning effort levels control. Pick the levels the model offers; each
non-
offlevel gets a wire-value field, prefilled with the level id. - Use Inherit catalog to omit
reasoningEfforts, or Non-reasoning for a model that cannot think. - Apply. The control writes one settings mutation and reloads; the small code chip shows the exact YAML value.
llm-deepseek routes have no per-model field — their effort is route-scoped —
so this editor renders only for the llm-pi-ai adapter family.
The CLI: preview the YAML and the op
node scripts/reasoning-efforts.mjs check --levels off,low,high,max --wire low=low,high=high,max=ultra
node scripts/reasoning-efforts.mjs yaml --route baidu-team --model deepseek-v4.1-flash \
--levels off,low,high,max --wire low=low,high=high,max=max
node scripts/reasoning-efforts.mjs ops --route acme --model acme-think --overrides \
--levels off,high --wire high=high
node scripts/reasoning-efforts.mjs none --model acme-plain
pnpm yaml is the same script. --json prints machine-readable output. Exit
code 1 means the selection is invalid and the reason goes to stderr.
Limits
- The
llm-pi-aiadapter family only.llm-deepseekroutes carry no per-model effort field, so the editor renders nothing for them. - Row discovery is structural. It follows the shipped page's own localized
Model ID <n>label; a shipped row restructure would need the selector updated, and is what theMutationObserverre-runs on. - The plugin writes no YAML. It sends settings path ops and the host persists them. A read-only settings document leaves the control disabled, and a refused or conflicting write is reported in the row instead of retried.
- Re-applying is bounded. The row restores an applied value at most three times before it reports the conflict.
- Catalog routes need the host's catalog. Rows for a route that configures
no
modelslist come fromllm/discoverModels; without it, only the routes that declare amodelslist can be edited.
FAQ
The value I applied disappeared when I used the page's own Apply.
The shipped page keeps its own draft of the model list, and its Apply writes the
whole models array for a hand-declared route — restoring the reasoningEfforts
value the card loaded when it opened. The row notices and writes your value
again, up to three times; if it reports the conflict instead, reload the card
and apply once more.
A level the model used to offer is gone from its selector. A declared map is the model's whole capability set: levels the map does not list are not offered. Add the level back here, or choose Inherit catalog to drop the declaration and keep the installed catalog entry's capability.
Why is off on its own refused?
A model that can do nothing but "do not think" is a non-reasoning model, and the
adapter spells that reasoningEfforts: false. Use Non-reasoning. Inside a
map, off means "send nothing when this level is asked for", so it belongs
beside at least one thinking level.
The control does not appear in a model row.
Check the route: only llm-pi-ai cards get it. If the row's id field is empty,
save the model first — an editor with no model id has nothing to address, and
says so.
License
MIT — see LICENSE.
If this plugin saves you from hand-editing one more YAML patch, a ⭐ star helps other DSH users find it.
An independent plugin for DeepSeek Harness — not affiliated with DeepSeek.
Comments
Loading…
Similar plugins
by HaoyueQin
Reasoning-effort editing for third-party models in DeepSeek Harness: per-model thinking levels with a knowledge base + protocol inference, edited inside the official Models page card.
★ 48
↓ 1k/wk
MIT
TypeScript
Sep 30, 2026
dsh plugin --profile web add dsh-better-reasoning-effortby chr003
Separate per-session model and reasoning effort controls for DeepSeek Harness subagents.
★ 0
MIT
JavaScript
Sep 7, 2026
dsh plugin --profile web add dsh-subagent-model-policy✦
No screenshots
by aerince
Add models.dev reasoning levels to unconfigured third-party DeepSeek Harness models.
★ 3
MIT
JavaScript
Aug 15, 2026
dsh plugin --profile web add dsh-models-dev-reasoningby Mu-scorpio
Configures provider and model reasoning-effort mappings for DeepSeek Harness, with a grouped model picker and composer slider.
★ 4
↓ 688/wk
MIT
TypeScript
Sep 15, 2026
dsh plugin --profile web add dsh-reasoning-effortby Neptune810
Flash-only reasoning-effort routing for DeepSeek Harness, setting the DeepSeek flash model reasoning effort per step. The model never changes; effort max is opt-in.
★ 0
MIT
JavaScript
Sep 29, 2026
dsh plugin --profile web add @neptune810/dsh-model-routerby TTTPOB
DeepSeek Harness plugin with per-task model and reasoning-effort selection
★ 0
MIT
TypeScript
Aug 22, 2026
dsh plugin --profile web add dsh-task-models