Skip to content
dsh.fish
Bundle

@jcy2387/dsh-models-input-modalities

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

Source
DamonBao
stars
1 stars
License
MIT
Updated
Updated 2 days ago

Readme

# DSH Models Input Modalities

[![CI](https://github.com/DamonBao/dsh-models-input-modalities/actions/workflows/ci.yml/badge.svg)](https://github.com/DamonBao/dsh-models-input-modalities/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![Node](https://img.shields.io/badge/node-%5E22.19%20%7C%7C%20%3E%3D24-green.svg)](#development)
[![pnpm](https://img.shields.io/badge/pnpm-11-orange.svg)](#development)

English | [简体中文](README.zh.md)

A [DeepSeek Harness](https://github.com/deepseek-ai/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 model** — *Provider default*, *Text only*, or *Text and image*.
- **Exact adapter semantics** — *Text 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:**

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

**From a local checkout (development):**

```sh
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`.

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

Individual commands:

```sh
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`](.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`](.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](https://docs.npmjs.com/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.2` → `alpha`, `0.1.1-rc.1` → `rc`). 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:

```sh
# 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

```text
.
├─ 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](LICENSE) © jcy2387

Install

dsh plugin --profile web add github:DamonBao/dsh-models-input-modalities

Profile: web

  • This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source