dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
Advanced provider configuration UI for DeepSeek Harness (DSH): headers, User-Agent, retry policy, timeouts, vision, reasoning and compatibility — without editing settings.yaml by hand.
Advanced Provider Settings is a WebUI plugin for DeepSeek Harness (DSH) that turns the
hidden half of an OpenAI-compatible provider into something you can see and edit. Instead of
hand-editing the profile YAML (~/.dsh/profiles/<profile>/cordis.patch.yml), you get a real control
panel — on its own settings page and on every provider card — and it is built around the model:
pick one, then give it its own input
modalities, reasoning-effort mapping and compatibility flags (and, on a route served by the installed
catalog, its own name, context window and output-token cap). Everything that can only apply to the
provider as a whole — request Headers, User-Agent control, Retry Policy, Timeout and
transport, Vision image budgets, Reasoning thinking levels, route-level Compatibility
flags — sits in provider-level cards that each say why they are not on the model. All of it is
written through DeepSeek Harness's own revision-fenced settings transport, so your YAML keeps its
comments and every field you never touched stays exactly as it was.
It exists above all for the case DSH serves worst out of the box: pointing Harness at a third-party DeepSeek endpoint — a cloud gateway, a corporate proxy, a self-hosted vLLM or llama.cpp behind nginx — where nothing in the URL tells the adapter that the models behind it think in DeepSeek's dialect. Compatibility is detected from the provider id and the base URL, so such a route silently gets OpenAI-shaped defaults: the thinking level you pick is shaped in a way the endpoint does not understand, the output cap is spelled with the wrong field name, and a request field your vendor does not implement is sent anyway. This plugin lets you declare the dialect per route and per model, set the thinking ladder and its token budgets, and see what will leave the machine before a request fails halfway through a session.
Status:
v0.4.8. Verified against DeepSeek Harness0.2.0-rc.2,0.2.1-alpha.4and0.2.1-rc.2(none changes what this plugin reads).
DeepSeek Harness exposes a large, well-designed provider schema, and its Models page deliberately edits only the common fields: endpoint, credential, model list. The rest — the fields that decide whether a flaky gateway succeeds on the third try, whether a corporate proxy accepts your client, whether an image-heavy prompt is rejected before it is sent, and whether a DeepSeek-compatible endpoint ever receives the thinking level you asked for — live in that YAML and nowhere else.
That last one is the sharpest case, because it fails without saying anything. thinkingFormat,
supportsReasoningEffort and maxTokensField are detected from the base URL, and only a URL
containing deepseek.com is detected as DeepSeek. Every other host that serves DeepSeek models —
Ark, a cloud aggregator, your own vLLM behind a reverse proxy — is treated as OpenAI itself: the
thinking parameter is built in OpenAI's shape, max_completion_tokens is sent where the endpoint
wants max_tokens, and store is sent to servers that reject it. Nothing errors at configuration
time; the request simply does not do what the level you selected said it would.
This plugin surfaces those fields without touching a single line of Harness source, without
patching anything in node_modules, and without owning a second copy of your configuration. It
adds UI through the Models page's declared extension slots, and it reads and writes the
llm-pi-ai settings namespace through the framework's public settings API.
off, minimal, low, medium,
high, xhigh, max — as a route default and as a per-model map that decides the exact
string each level becomes on the wire, plus the per-level token budgets.thinkingFormat: deepseek, reasoning_effort
support, the max_tokens spelling, DeepSeek-style reasoning replay and the fields your gateway
rejects — all switchable per route and per model, because URL detection will not do it for you.models list is
edited in place; a route serving the installed catalog is edited through modelOverrides.<id>.Configuration has two levels: a model setting is edited one model at a time, and a provider
setting applies to every model on the route. That split is not a UI preference — it is the Harness
schema. A model entry created by the route's own models list accepts only input,
reasoningEfforts and compat; a route that serves the installed catalog is configured per model
through the schema's other channel, modelOverrides.<id>, which accepts six fields. Everything else
— headers, retry, network, image byte budgets, cache retention — exists only at route level, and a
model entry that sets it is rejected by name.
| Area | Level | What you get |
|---|---|---|
| Per model | Model | For the model you select: what input it accepts (text / image), the thinking level → wire value map it actually sends, and its compat overrides — each flag showing the route value it falls back to. On a catalog route the same editor also covers the model's display name, context window and max output tokens, because nothing else edits that channel. |
| Headers | Provider / global | Per-provider and global request headers, with validation, secret masking and duplicate detection. |
| User-Agent | Global | Presets (Chrome, Safari, Firefox, opencode, Codex CLI, Claude CLI) plus free text, for gateways that whitelist clients. |
| Retry Policy | Provider | Harness default / Conservative / Aggressive presets, or a custom policy: mode, max retries, retryable codes, initial delay, max delay, jitter. |
| Network | Provider | Transport (sse, websocket, websocket-cached, auto), request timeout, stream idle timeout, WebSocket connect timeout, cache retention. |
| Vision | Provider | The fallback input type for models that declare none, max image bytes per request, pixel budget, max bytes per image — with human units (MiB) instead of raw byte counts. |
| Reasoning | Provider | The route's default thinking level (off … max) and the per-level token budgets. What each level becomes for a given model is set under Per model. |
| Compatibility | Provider | All 26 compat flags, filtered to the ones your route's protocol actually reads — including the thinking dialect (openai, deepseek, openrouter, qwen, chat-template, …) and the field that carries a thinking budget. The model-level half lives under Per model and is filtered harder, because a mismatch there is a hard error in Harness. |
| Test Provider | Provider | Interrogates the endpoint's model list using the headers currently in the form, saved or not — the fastest way to prove a whitelist header works. |
| Effective configuration | Provider | A preview of exactly what the provider will send, layer by layer, with sensitive values masked. |
| Diagnostics | Global | Read-only compatibility report you can paste into a bug report, plus one-click import from the retired dsh-custom-provider-settings plugin. |
Requires DeepSeek Harness 0.1.7–0.2.1 or a compatible build, and Node.js 20+.
Both profiles run the same artifact — one package, no build on your machine. What differs is who is allowed to write to the profile.
web profiledsh plugin manages this profile, and both sources work:
# Straight from the repository — no build step
dsh plugin --profile web add github:misswell/dsh-advanced-provider-settings
# From a GitHub release tarball (a fixed, content-hashed artifact)
dsh plugin --profile web add https://github.com/misswell/dsh-advanced-provider-settings/releases/download/v0.4.0/dsh-advanced-provider-settings-0.4.0.tgz
Both work without a build on your machine. lib/ is committed on purpose: dsh plugin runs
no build step, and pnpm 10+ blocks a git dependency's prepare script, so a plugin that needs
building must ship its output. The package is not published to npm, so a bare-name install is
not offered.
A profile created by the DSH CLI is a pnpm workspace root, and pnpm refuses a first dependency at the root:
ERR_PNPM_ADDING_TO_ROOT. Add once to that profile's.npmrcand the install succeeds —ignore-workspace-root-check=true. The failure names the file it wants.
Then restart the web UI:
dsh web
desktop profileThe app owns this profile, and the CLI refuses to touch it:
$ dsh plugin --profile desktop add github:misswell/dsh-advanced-provider-settings
error: profile "desktop" is managed exclusively by the Electron application
Install it from inside the app instead: open Plugin Marketplace and install
misswell/dsh-advanced-provider-settings, then restart the app. The app writes exactly what the CLI
writes for a web profile — a dependency in ~/.dsh/profiles/desktop/package.json and an entry in
that manifest's dsh.profile.bundles list, the entry that makes the plugin a profile layer rather
than a plain dependency. (If the app has no Plugin Marketplace page yet, install
dsh-plugin-marketplace first; the marketplace is itself a plugin.)
If an install ends with
spawn pnpm ENOENT, the app was launched with a minimal PATH — what a Finder- or Dock-launched macOS app inherits. Make a pnpm that matches the profile's store visible to GUI apps (launchctl setenv PATH …, then relaunch) and install again.
dsh plugin --profile web remove dsh-advanced-provider-settings
In the desktop app, remove it on the same Plugin Marketplace page.
Removing the package removes the UI, and with it this plugin's own settings namespace: on 0.1.7 a
namespace is a profile entry's Config, so dropping the entry drops the global header list and the
UI preferences stored under it. Your provider configuration is untouched — the advanced fields
keep working, because they were written to Harness's own llm-pi-ai entry, which this plugin never
owned. Both live in the active profile's user layer, ~/.dsh/profiles/<profile>/cordis.patch.yml; to
clear the fields this plugin set by hand, delete the providers.<id> keys under the llm-pi-ai entry
(or use Reset all advanced settings on each provider card first, which shows what is about to go).
2 items, Always retrying, high, Custom, Default — so you can see
where configuration lives, and how much, without opening anything, and expand only the card you
came for.models
list is edited in place; a route served by the installed catalog is edited through
modelOverrides.<id>, and the card says so — with the picker listing the catalog's live models and
naming the layer that failed if the listing could not be read. An override whose model has since
left the catalog still appears, marked, so it can be cleared rather than silently kept.compat, so headers, retry, network,
image byte budgets and cache retention appear at provider level only — and the model editor states
that boundary at its foot instead of offering buttons that would be rejected on save. Name, context
window and max output tokens are editable on a catalog override but deliberately left to the Models
page on a listed model: one path, one writer.Inherited from the route: Supported, so you never have to switch
back to the provider-level list to compare.2 min, 5 min, 10 MiB. A value that is off by a
factor of 1000 stops looking plausible. The pixel budget is the one row that restates its own value,
because turning it into ≈ 1448 × 1448 pixels says something the box cannot.xhigh and max are accepted by the
schema but folded to high before the request leaves, and the row says so rather than offering a
granularity that does not exist. A model that does not reason at all — reasoningEfforts: false, a
model-level value with no provider-level equivalent — is declarable from the same editor, and
Inherit clears it again.4 retries, 500 ms, doubling, capped at 8 s is a shape, and it is easier to
sanity-check as one.Inherit is not the same as Off, because it leaves the decision to the adapter.Everything about reasoning in Harness is two-layered, and the two layers answer different questions: what level do we want and what does this endpoint call it. This plugin exposes both.
off → minimal → low → medium → high → xhigh → max. The last two are accepted by the
schema but folded onto the high budget before the request leaves, so only high ever needs a
budget of its own; the Reasoning card says this in words next to the pills. Budgets exist for
minimal, low, medium and high (Harness defaults: 1024, 2048, 8192, 16384), and a
budget is clamped to the room the request leaves for an answer — a 32000-token budget on a
request whose output cap is 8000 becomes 8000.
A budget reaches the wire only if the route says which field carries it: pick Budget field name
(thinking_token_budget, thinking_budget, thinking_budget_tokens) or the alias switch
in the Compatibility card, because no adapter can guess it. Servers that express the budget inside
their chat template instead are served by the chat-template dialects.
Reasoning → Thinking level sets the level this route uses when a request does not name one. It is
a route-wide default, and Harness validates it against the model that actually receives the request:
a level a model pins as unsupported is refused with UNSUPPORTED_REASONING_EFFORT rather than
downgraded. On a route that mixes thinking and non-thinking models, leave it on Inherit and let the
per-model maps decide (see behaviour 4).
Per model → Model level mapping is one box per level, and the box is the exact string that level
sends. Each box is followed by the state that level is in — sends low, Not supported, Inherit,
sends nothing — because a blank box cannot say it on its own: a level left blank is pinned
unsupported for that model, so choosing it is refused by name instead of being silently dropped,
while leaving every box blank keeps whatever the catalog declares. The consequences are worth
stating precisely:
| What you leave in the map | What it means |
|---|---|
low: low, high: high | Those levels exist for this model and send exactly those strings. |
| a level left blank | Pinned unsupported: the level is hidden from the model's effort list and a request naming it fails with a named error. |
off left empty (the Offered, sends nothing switch on) | off stays selectable instead of being pinned unsupported. The dialect then expresses "do not think" its own way. |
off given a string | off is offered and sends that string as the thinking parameter. |
reasoningEfforts: false (No reasoning) | The model is declared non-reasoning: Harness reports no reasoning capability at all, and asks for nothing. Model level only — the schema has no provider-level equivalent. |
The map is not free-form: Harness refuses a map that is empty, that offers no level beyond off,
that maps anything but off to nothing, or that contains an empty string. The editor's grid cannot
produce those shapes, and the draft check names one if a hand-edited YAML has it.
The chosen level goes through your compat switches, which is where "third-party DeepSeek" is won or
lost. With thinkingFormat: deepseek and supportsReasoningEffort: true, a request asking for
high carries thinking: {type: "enabled"} together with reasoning_effort: "high"; a request
asking for nothing carries thinking: {type: "disabled"}, the dialect's way of saying so. With the
default openai format the same level becomes a bare reasoning_effort, and "no thinking" is the
absence of any reasoning field — which is why a DeepSeek-compatible endpoint that requires an
explicit disabled marker must be told which dialect it speaks.
The situation. An OpenAI-compatible endpoint serves DeepSeek models: deepseek-reasoner thinks,
deepseek-chat does not. Its host is a cloud gateway or a corporate proxy — nothing like
api.deepseek.com — so Harness cannot recognise what is behind it.
Why it needs configuring. Compatibility is detected from the provider id and the base URL. An unrecognised URL is treated as OpenAI itself, which is wrong in five specific ways here:
| Assumed for an unrecognised URL | What a DeepSeek-compatible endpoint expects |
|---|---|
thinkingFormat: openai — a bare reasoning_effort | thinkingFormat: deepseek — an explicit thinking marker plus reasoning_effort |
maxTokensField: max_completion_tokens | max_tokens, the spelling DeepSeek's own API and most mirrors use |
supportsStore: true — sends store | DeepSeek's own API does not take store — Harness already treats it as non-standard — and most mirrors follow |
supportsDeveloperRole: true — sends instructions as developer | DeepSeek-style servers expect instructions as system |
requiresReasoningContentOnAssistantMessages: false | servers that replay DeepSeek turns expect an empty reasoning_content alongside them |
Step 1 — declare the route on the Models page. Identity, endpoint, credential and the model list belong to DSH's own page; the plugin deliberately does not edit them.
# Settings → Models
displayName: Third-party DeepSeek
apiKeyEnv: THIRDPARTY_DEEPSEEK_API_KEY # the name of a credential, never the key
api: openai-completions
baseURL: https://llm-gateway.example.com/v1
models:
- id: deepseek-reasoner
name: DeepSeek R1
contextWindow: 131072
maxTokens: 65536
- id: deepseek-chat
name: DeepSeek V3
Because this route declares a models list, that list replaces the installed catalog and the
per-model channel is those entries themselves — the Per model card edits them in place, addressed by
position.
Step 2 — Compatibility, at route level. The mismatches above, as six switches:
reasoning_effort → Supportedmax_tokens, legacystore → Not supporteddeveloper role → Not supportedStep 3 — Reasoning, at route level. Leave Thinking level on Inherit here: the two models do not share a ladder, and a route default a model cannot take is refused at request time. Set the budgets instead — they are route-wide and safe:
2048, Medium 8192, High 32768Step 4 — Per model. Select deepseek-reasoner and fill the mapping grid:
| Level | Box | Meaning |
|---|---|---|
| Off | Offered, sends nothing switch on | off stays selectable; the dialect expresses it as thinking: {type: "disabled"} |
| Low | low | sends reasoning_effort: low |
| Medium | medium | sends reasoning_effort: medium |
| High | high | sends |