DeepSeek Harness Plugin

Ztyss/dsh-llm-provider

Stars ★ 1 Category Models & Providers Added 2026-09-23

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.latest of @earendil-works/pi-ai from 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 as vendor/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: loadBridge cleans 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 (updateDecision skips "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.installed is 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 + tf interpolation 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-written reasoningEfforts / compat survive; "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.json is 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-Token section — a fragment with only Oasis-Webid can 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 /model command and the model-catalog state machine are all provided by this plugin.
  • The /model command: filter by provider, search models, candidates grouped by provider with quota/balance shown per group.
  • The available contract: implements the officially required available(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.ts verifies the manifest, the entry file and the four subpaths the official bundle actually imports; when broken it prints an executable restore recipe (npm pack over 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.15 rmSync follows junctions and empties their targets, and in this layout there is no link left to follow. Invariant: the installed package contains zero links (enforced by test/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 and existsSync returns false; startup dies with a single Cannot find package '@deepseek-ai/dsh-launch-environment' (or Cannot 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's file:// 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 second LlmError / 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)

Content from the project README on GitHub ↗

Links

More in this category

View the whole category →

Community comments

Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.