Replaces the built-in llm-pi-ai and llm-deepseek adapters, the model selector and the Models settings page with self-maintained versions, tracks upstream @earendil-works/pi-ai through a safe-zone download with a one-click enable toggle, and adds provider quota and balance lookups plus a provider management page.
Install
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:Ztyss/dsh-llm-provider
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
A dsh (DeepSeek Harness) plugin that takes over
the official pi-ai adapter (llm-pi-ai), the native DeepSeek adapter (llm-deepseek), the model
selector (ui-model-selection) and the Models settings page (ui-settings-models), providing a
self-maintained pi-ai bridge, model selector, quota lookups and provider management UI.
中文 · English
This repository is forked from imchangchang/dsh-llm-provider and maintained independently since. Current version v0.2.3 (installs from main).
Install
dsh plugin --profile web add github:Ztyss/dsh-llm-provider # private repo; git needs credentials (gh auth login)
dsh web # restart required: the plugin tree is assembled at process start
The build output (lib/) ships with the repository, so it works right after install. After
changing source, run npm run build and commit lib/ together with src/ (there is deliberately
no prepare script: pnpm runs it in a temp dir on git installs where tsdown is missing, which
fails the whole install).
Features
pi-ai bridge: enable the latest version
The "Enable latest pi-ai" toggle in the settings page (Provider → pi-ai bridge) runs the
official pi-ai adapter on the newest @earendil-works/pi-ai downloaded from npm, replacing the
DSH-bundled copy — new pi-ai versions and model support arrive without waiting for a dsh release.
- ON = a standing intent: pull
dist-tags.latestof@earendil-works/pi-aifrom the npm registry — sha512 verification → extract → install the dependency closure → probe against the bridge copy's own import requirements — into the safe zone$DSH_HOME/llm-provider-bridge/pi-ai/<version>/(the plugin package can be recursively deleted at any time, so hundreds of MB never live inside it). After a restart the bridge link points at the downloaded copy, replacing the DSH-bundled one; a failed probe falls back automatically, so the switch always has a safety net. - OFF = fall back to the DSH-bundled copy: downloaded files are kept, and flipping back ON costs nothing.
- The toggle preference also lives in the safe zone:
$DSH_HOME/llm-provider-bridge/vendor-status.json— the preference used to be written inside the package asvendor/status.json, and the package manager rebuilds the plugin directory from scratch on every reinstall/upgrade, resetting the switch to OFF. Moved out, a reinstall loses nothing (an in-package legacy file is merged into the safe zone on first read, then removed). - Only the newest downloaded version is kept:
loadBridgecleans older ones after the switch has completed (the running process never steps on a directory being deleted). - The network is touched only while the switch is ON, in two places: the flip itself, and
one check on every startup — no local copy ready, the download starts right away (the UI
enters its downloading state, no second flip needed); a copy ready, npm is still checked and a
newer version downloads automatically (
updateDecisionskips "latest ≤ newest local"). The 60-second floor is crash-loop damping, not a throttle on intent. OFF never touches the network. - The version row tells the truth: current x.y.z (DSH-bundled / npm latest / vendor) — which
copy is in use at a glance. The toggle row only speaks when there is something in progress or
to do: downloading / x.y.z downloaded (restart to apply) / x.y.z updated (restart to apply) /
x.y.z downloaded (restart to fall back to official) / x.y.z downloaded (cannot be enabled) /
x.y.z downloaded (not in use). Whether a restart is pending is derived live by
piAiNeedsRestart(preference + newest ready safe version + the copy currently loaded) — the enable-in-place state (nothing to download) says so too; a pending version that this very check actually downloaded (lastCheck.installedis it — typically the startup auto-update after a restart) says "updated", while a copy that was already on disk says "downloaded"; verification failures live in the detail rows. While downloading, poll every 2s.
Model services settings page
- Bilingual (zh/en): dictionary +
tfinterpolation through dsh's own locale mechanism, following language switches live. - Inline provider editing: display name, endpoint, protocol and credential name are edited right on the card — no edit mode, no separate form. With no changes the card shows no editing affordances at all; as soon as a draft exists, "Save changes / Cancel" appear. Only changed fields are written, clearing a field removes the key. The route id stays read-only.
- The model list is the checklist: expanding "Models (N)" shows checkboxes directly, decoupled
from any edit button. Existing entries are read-only (capability badges + formatted context,
same columns as the display list); configured rows get an ✕ to delete them in one click; only
custom ids not in the catalog need context window / max output and vision / video. Saving goes
through the dedicated route
POST /provider/set-models(server-side validation, native routes rejected), with the declared entry as the base — hand-writtenreasoningEfforts/compatsurvive; "Follow catalog (restore)" deletes the key and returns to the full catalog. - pi-ai bridge card, now a toggle: the "Enable latest pi-ai" switch plus a status line (current version + source / already latest / downloaded, restart pending / failed verification with the reason, persistent); after flipping ON — and during each startup check — it polls every 2s until the download settles.
- Delete confirmation modal: cost list + "Export config (YAML)" backup (secrets excluded).
- Three-state capability badges: supported / explicitly unsupported / unknown are rendered separately; models missing from the catalog get capabilities from the route declaration and adapter self-report (modlens-style synthetic providers).
Console cookie storage
Providers that need a console session (StepFun's Step Plan points, for one) keep their cookies in
$DSH_HOME/llm-provider-bridge/.cookies.yaml — one file, dot-prefixed to match the
.credentials.yaml habit; same schema as credentials (version + flat key table), keys are
credential ref names (e.g. STEPFUN_CONSOLE_COOKIE), so a new provider or cookie is just another
key. Parsing tolerates hand edits, and a bad row never kills a lookup.
- Legacy single-slot session files migrate automatically:
stepfun-console-session.jsonis merged into the new file on first read, then removed. - Seed fingerprint dedup (write convergence): an entry records
seedSha, the fingerprint of the seed credential — later quota lookups for the same credential reuse the stored value instead of writing the static credential back. Previously one lookup meant two writes plus a pointless renewal RPC; now writes happen only on two real changes: repasting a cookie, and renewal rotation. - Fragment seed guard: a seed must contain an
Oasis-Tokensection — a fragment with onlyOasis-Webidcan no longer overwrite a rotated good jar. - First write on a fresh machine: the bridge directory is created when missing.
Model selector
- Full takeover: once the official model selector is disabled, the selection seat (current
model state), the
/modelcommand and the model-catalog state machine are all provided by this plugin. - The
/modelcommand: filter by provider, search models, candidates grouped by provider with quota/balance shown per group. - The
availablecontract: implements the officially requiredavailable(session)for/command contributions — sessions addressed as subagents get no model selection; the host calls it unguarded, so one throw would take the whole/candidate batch down — the implementation never throws and swallows every exception, preferring to show one extra menu entry.
Stability & safety design
- Pre-load integrity check and candidate chain:
src/pi-ai-source.tsverifies the manifest, the entry file and the four subpaths the official bundle actually imports; when broken it prints an executable restore recipe (npm packover the host directory — the plugin never downloads for you).piAiCandidates()= safe zone (when ON, newest → oldest) → legacy vendor tiers → bundled dependency → DSH-bundled, probed in order, first pass wins. - The bridge workspace lives outside the package, in
$DSH_HOME/llm-provider-bridge/: on Node ≥24.15rmSyncfollows junctions and empties their targets, and in this layout there is no link left to follow. Invariant: the installed package contains zero links (enforced bytest/host-safety.mjs). - Archived dependencies resolve by URL, not by link: once DSH NEXT ships the host as
resources/app.asar, the official bundle, its@deepseek-ai/*dependencies and the DSH-bundled pi-ai all live inside the archive. A directory link (Windows junction / POSIX symlink) has its target resolved by the kernel's object manager, and the kernel knows nothing about asar — so any link into the archive dangles andexistsSyncreturns false; startup dies with a singleCannot find package '@deepseek-ai/dsh-launch-environment'(orCannot find package '@earendil-works/pi-ai'for pi-ai) and the whole bridge degrades. When a package root sits inside a.asar, the bridge therefore writes the specifier as the archive entry'sfile://URL instead of linking: that is exactly how the host itself loads the official bundle, so the same path resolves to the same module instance (no secondLlmError/cordis). Both sides of the "use latest pi-ai" switch are covered — OFF selects the DSH-bundled copy inside the archive; the bridge copy is always re-derived from the pristine source plus the current rewrite set, so flipping the switch back and forth lands correctly in both directions. Only when the entry cannot be resolved does it fall back to linking (same error shape as before).
Tests
npm test # build + an 18-step offline chain (no dsh needed; the last step is the patch-condition drift check)
node test/host-safety.mjs # junction-safety regression: run under BOTH node 24.14 and the DSH runtime (≥24.15)
Links
More in this category
Ebony-Vinyl/dsh-our-free-model★ 64128
Adds free model lanes to DeepSeek Harness — DeepSeek V4.1 Flash, Kimi K3 and others — with no login, sign-up or API key, plus a local OpenAI-compatible gateway, fallback routing and a usage dashboard.
dingminhua/dsh-connect-workbuddy★ 1244
Connects locally signed-in WorkBuddy models, CN and international alike, to DSH, reading each model's credit multiplier from the upstream catalog on every refresh, with per-model image input toggles, account switching backed by an auto-failing-over pool, manual daily check-in, and read-only credit overviews shown as compact pills on the card and in the composer.
Alice-Marx/dsh-model-router★ 859
Routes DeepSeek Harness work across configured model APIs and supported vendor CLIs with dependency-aware packages and cost previews.
V1ki/dsh-plugin-subscriptions★ 568
Use ChatGPT (Codex), Claude, and Grok subscriptions as DeepSeek Harness LLM providers, with Settings login, model catalogs, usage, plus image_generate, video_generate, and x_search tools.
Mars-Sea/dsh-commandcode-provider★ 460
Unofficial Command Code LLM provider: registers a `commandcode` route with a live model catalog and reasoning-effort support.
XDTrees/dsh-workbuddy-xdpool★ 447
Merge every locally signed-in WorkBuddy account into DeepSeek Harness as one auto-failing-over model pool (multi-account rotation, live credits, and a model catalog showing per-model credit multipliers and free/night-discount tags).
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.