English | 简体中文
An external DeepSeek Harness web client plugin: a Settings page that configures
per-model thinking levels (reasoning efforts) for third-party (pi-ai)
providers. It writes the same llm-pi-ai.providers.<route>.models[].reasoningEfforts
(and route-level reasoning) fields the llm-pi-ai adapter reads, so the
composer's 「推理等级」 picker and route defaults pick the values up with no other
change.
Why an external plugin: the built-in Models settings form deliberately does
not expose reasoning effort (it is a per-model capability), and adding a field
to the built-in ui-settings-models package would be overwritten by the next
official release. This package ships as an installable bundle that never
touches repository source, so official updates cannot clobber it.
A new Settings section, 「模型思考等级 / Model reasoning」, placed after the
built-in Models page. For each third-party provider that carries an explicit
models list you can:
- set the route default thinking level (
providers.<route>.reasoning), and - per model, choose inherit / non-reasoning (
false) / reasoning with a level set (reasoningEfforts), ticking the canonical levelsoff minimal low medium high xhigh max.
The three mode choices sit side by side with hover explanations (tooltips), and an Apply to all models button copies the current model's thinking declaration (levels + wire spellings) to every model on the route at once. Empty states guide you when there is no provider yet, no editable provider, or a provider with no models.
Each selected level has a wire-spelling field (defaults to the level name).
Change it to remap what that level sends — e.g. max → ultra for a model that
calls its top level Ultra, or high → turbo. off can either send nothing
(default) or a custom value of its own. This is the supported way to adapt a
model's thinking vocabulary without waiting for an adapter update.
⚠️ DSH does not support inventing new level names. The pi-ai schema pinsreasoningEffortskeys to the seven levels above (z.dict(..., z.union(levels))) and resolution only reads those keys, so a bareultra:key is rejected at write and ignored at request time. "Ultra" is expressed by remapping an existing level's wire spelling (max: ultra), not by adding anultrakey.
When no third-party provider is configured yet, the page shows a friendly placeholder card (instead of a dead empty dropdown) prompting you to add a custom provider first, pointing at Settings → Models → Add a custom provider. It distinguishes "no providers at all" from "providers without a custom models list", and shows loading / unavailable hints while the settings document loads.
The write path uses the official settings.mutate RPC with revision fencing, so
a concurrent change is refused rather than silently overwritten.
Prerequisites: a DeepSeek Harness install with the dsh CLI, plus pnpm (the dsh plugin command runs pnpm under the hood). This is an installable bundle — it is loaded by dsh, not imported as a library.
The package is published to npm as dsh-model-reasoning:
dsh plugin --profile web add dsh-model-reasoningThis installs the prebuilt bundle and appends it to the web profile. Then restart dsh web and open Settings → 模型思考等级 / Model reasoning.
dsh plugin --profile web add github:karoc/dsh-model-reasoning#<sha>A git install runs the package's prepare script to build the bundle. pnpm ≥ 10 requires allowlisting that build once — copy the exact package key pnpm prints into the profile's pnpm-workspace.yaml under allowBuilds, then re-run add (see docs/user/develop/basic/publish.md in the DSH repo).
Bump to the newest release with pnpm update (or re-add to pick up a newer git ref):
dsh plugin --profile web update dsh-model-reasoning
# or, if the dependency spec is pinned: dsh plugin --profile web add dsh-model-reasoningThen restart dsh web so the new client bundle loads.
dsh plugin --profile web remove dsh-model-reasoningThis removes both the dependency and its bundle layer from the web profile. Restart dsh web for the section to disappear.
cordis.patch.yml # bundle layer: mounts the row that the client-modules
# service discovers (dsh.client manifest)
package.json # dsh.bundle (patch) + dsh.client (web) + exports["./client"]
tsdown.config.ts # self-contained build: node half + module-table client bundle
src/index.ts # host apply (no-op)
src/client/index.ts # client apply: settingsScope.bind(llm-pi-ai) + register settings.section
src/client/ReasoningSection.tsx # the settings page (route → model → effort editor)
src/client/styles.ts # design-token styles (--dsw-alias-*) + injection
src/client/locales.ts # en/zh copy
pnpm install
pnpm bundle # emits lib/index.js + lib/client.js
pnpm release:check # release gate: docs/changelog/tag/tree/build/registry must all pass
pnpm publish # runs the gate (prepack/prepublishOnly), then postpublish verifies the live releaseThe bundle leaves the platform packages (react, @deepseek-ai/cordis,
@deepseek-ai/dsh-client-*) external — they resolve at runtime from the loader's
module table; everything else is inlined.
- Only routes that carry an explicit
modelslist are enumerable here (the installed catalog is not reachable from the client). Catalog-only providers keep their levels from the installed catalog and use the composer picker. - Wire spellings default to the level name; to rename a level on the wire (e.g.
max: ultra) editsettings.yamlfor that model. - Section nav icon is shell-assigned, not plugin-assigned. The built-in
ui-settings-generalSettingsRoot.tsxnavIcon(id)maps known ids (models,agent-presets,plugins) and falls back to a gear for every other id — including this section'smodel-reasoning. Thesettings.sectionregistration has no icon field, so an external plugin cannot set it without patching the shell. When DSH exposes a per-section icon (e.g. an icon option on the registration), useIconThinkOutline16fromdsh-client-ui-primitivesfor this section.
See CONTRIBUTING.md (development + release checklist) and CHANGELOG.md for version history.