Back to home@DamonBao

dsh-models-input-modalities

DeepSeek Harness Web plugin: per-model input-modality selector (text/image) on the Models settings page for third-party (pi-ai) providers.

Stars
1
Language
TypeScript
Created
Sep 9, 2026
Updated
Sep 9, 2026
GitHub repo

Introduction

DSH Models Input Modalities

CI License: MIT Node pnpm

English | 简体中文

A DeepSeek Harness (DSH) Web client plugin that adds an Input modalities fold to every third-party (pi-ai) provider card on the Settings → Models page, declaring per model which inputs it accepts (whether images are allowed) — exactly the field the page's own forms do not expose. Built against DSH 0.1.5-alpha.1 (peer range >=0.1.5-alpha.1 <0.2.0).

The providers themselves (provider ID, API base URL, protocol, API key, model list) are still created and edited entirely through the Models page forms; this plugin pre-writes no provider configuration.

What it does

  • A three-way choice per modelProvider default, Text only, or Text and image.
  • Exact adapter semanticsText only writes input: [text], Text and image writes input: [text, image], and Provider default removes the field so the row inherits the installed catalog's modalities, then the route's defaultInput.
  • Revision-fenced writes — the whole models array is written back under the revision read when the fold was opened, with the same array semantics and conflict handling as the Models page's own cards: a concurrent edit surfaces a conflict notice and reloads instead of silently clobbering.
  • Field preservation — every other field of every row survives verbatim, including fields this card never shows.
  • Follows the page live — add or remove a model in the catalog above and the modality rows follow it in place: the fold listens for the pushed settings/document-updated invalidation of its own namespace, so nothing has to be closed and reopened. A commit that outruns a running read is caught by revision comparison and re-read until the fold is level. An unsaved draft is never overwritten, not even by a reopen — a change that lands mid-draft parks until the draft settles: revert the edits and the fold re-reads silently, or save and the revision fence refuses the raced write and reloads behind a conflict notice.
  • Localized UI — English and Chinese follow the Web UI's locale.

Usage

  1. Install (see below) and restart dsh web.
  2. Settings → Models: create your gateway provider with Add custom provider as usual, or open an existing one.
  3. An Input modalities fold appears under every third-party provider card; expand it and choose one of the three states per model.
  4. Click Save. The claim lands in the user layer of $DSH_HOME/settings.yaml; the adapter picks it up on its next request — no restart required.

How it works

The plugin registers its component into the settings.models.provider-card extension seat exposed by the Models page (key llm-pi-ai, i.e. the cards of the whole pi-ai adapter family). On first expansion the fold reads the provider's stored models rows through the settings Remote, edits them locally, and writes the whole array back under the revision captured at read time — the same array semantics and conflict handling as the page's own cards (a concurrent edit prompts a conflict notice and a reload). All fields other than input are preserved verbatim in every row.

The fold also subscribes to the Host's forwarded settings/document-updated event and filters it to llm-pi-ai, so the page's own model-list writes reach it without a remount. An open, clean fold re-reads silently, and keeps re-reading until its data is level with the newest announced revision; a closed one parks the notice and re-reads on the next expansion; one holding an unsaved draft parks the notice until the draft settles, so a reopen never costs edits. The card's own write is recognised by the revision it just committed and does not echo back as a refresh.

Installation

Prerequisites: DeepSeek Harness (dsh) >=0.1.5-alpha.1 <0.2.0 with the web profile.

From npm:

dsh plugin --profile web add @jcy2387/dsh-models-input-modalities
dsh web

From a local checkout (development):

git clone https://github.com/DamonBao/dsh-models-input-modalities.git
cd dsh-models-input-modalities
pnpm install && pnpm run build
dsh plugin --profile web add link:$PWD
  • link: references this directory directly; after a change run pnpm run build and restart dsh. Without the link: prefix the install is a copy — update it with dsh plugin --profile web update.
  • Uninstall: dsh plugin --profile web remove @jcy2387/dsh-models-input-modalities.

Development

Requirements: Node.js ^22.19.0 || >=24.0.0 and pnpm 11.7.

pnpm install
pnpm run check        # typecheck + test + build + publint, same as CI

Individual commands:

pnpm run typecheck    # host + client faces
pnpm test             # vitest suites over the pure row helpers
pnpm run build        # tsc d.ts + tsdown (lib/index.js & lib/client.cjs)

The client artifact is a self-contained bundle: React, Cordis, ui-slots, and ui-primitives are supplied by the Web shell, CSS Modules are inlined, and every other @deepseek-ai/* package is a type-only import (enforced by a build-time purity check).

CI (.github/workflows/ci.yml) runs the same gates on Node 22.22.0 and 24.x, verifies on tag pushes that the release tag matches the package version, audits the packed tarball's file list, and runs a consumer smoke test that installs the tarball into a scratch project (resolving the published peer ranges against the real registry) and imports every Node-side entry point.

Release

Publishing is automated by the Release workflow (.github/workflows/release.yml), which runs whenever a GitHub Release is published. It requires the release tag to equal the package version (an optional v prefix is stripped), re-runs the full quality gates, packs the tarball, and publishes to npm with provenance via OIDC trusted publishing — no long-lived NPM_TOKEN secret is involved.

One-time setup: configure trusted publishing on npmjs.com for @jcy2387/dsh-models-input-modalities, authorizing repository DamonBao/dsh-models-input-modalities with workflow release.yml (no environment).

The dist-tag follows the GitHub Release's pre-release flag: a full release (checkbox unchecked) publishes under latest — including rc versions — while a pre-release publishes under the channel tag derived from the version (0.1.1-alpha.2alpha, 0.1.1-rc.1rc). The workflow is idempotent — a version that already exists on npm is skipped, so a re-run after a partial failure republishes only what is missing.

A typical release:

# bump the version in package.json, then:
pnpm run check
VERSION="$(node -p "require('./package.json').version")"
git commit -am "release: $VERSION"
git tag "$VERSION"
git push origin main --tags

Then create and publish a GitHub Release for that tag.

Dependabot checks GitHub Actions dependencies weekly. npm version updates are intentionally not enabled for Dependabot: it regenerates pnpm-lock.yaml without the workspace overrides, so its PRs cannot pass pnpm install --frozen-lockfile — bump dependencies manually with pnpm update.

Repository layout

.
├─ src/
│  ├─ index.ts          # Host half: an intentionally empty apply (browser-only plugin)
│  ├─ image-input.ts    # pure row helpers for the per-model input claim
│  └─ client/           # Web half: the input-modality fold (controller, card, locales)
├─ tests/               # vitest suites over the pure row helpers
├─ build/               # tsdown preset for the self-contained client bundle
├─ .github/workflows/ci.yml       # validate + tarball audit + consumer smoke
├─ .github/workflows/release.yml  # npm publish on GitHub Release
├─ cordis.patch.yml
└─ README.md / README.zh.md

Known limitations

  • Dormant (not yet configured) provider cards do not render the fold; a freshly created custom provider appears after it is saved.
  • Route-level defaultInput and modelOverrides for built-in provider-catalog models are out of this plugin's scope — set them directly in $DSH_HOME/settings.yaml.
  • In read-only settings deployments the fold is visible but cannot save.

License

MIT © jcy2387