The philosophy of DeepSeek Harness (DSH) is that “everything is a plugin.” In multimodal conversations, clients typically calculate image budgets in bytes or pixels, while upstream models (such as vLLM) often limit input by image count (--limit-mm-per-prompt.image). A byte budget cannot prevent 400 errors caused by too many images, and DSH’s compact compression replays the same history, causing repeated failures. dsh-image-guard is intended to solve this problem.

Core Features

This plugin runs at the first layer of DSH’s fetch chain and mainly handles two stages: “before sending a request” and “error retry.”

  • Keep the latest N images, replacing the rest with markers: The plugin keeps the latest keepRecent images and replaces the remaining images with recognizable markers (including filename, path, or fingerprint). Trimmed images are not silently lost; the model or agent can retrieve them again when needed.
  • Parse the upstream limit: When the upstream returns HTTP 400 because of image count, the plugin parses the limit N from the error message At most N image(s) and retries with N−1 images; once the limit has been learned, subsequent requests in the same process are sent directly with N−1 images, rather than triggering 400 errors again.
  • Save visual tokens: Each trimmed image saves about 930 tokens net (observed limit 972 − marker overhead about 40). The settings page displays the cumulative savings in real time.
  • Do not modify conversation history: It only modifies the request body that is about to be sent (a copy made via structuredClone), so prefix caching remains valid; disabling the plugin restores the original behavior.
  • Always stay at the head of the fetch chain: The plugin intercepts assignments to globalThis.fetch, and uses a 200 ms watchdog plus in-chain probes as a fallback to ensure it remains ahead of dsh-net-proxy, avoiding bypass issues in proxy mode.

Installation and Enablement

This plugin is not currently published to npm and is not listed in any plugin marketplace directory, so it cannot be found in the DSH Desktop GUI plugin marketplace. It must be installed manually.

Command-line profile

dsh plugin --profile <name> add github:mafeis/dsh-image-guard

Note: The desktop profile is held by Electron, and the launcher rejects plugin management requests for it.

Manual installation in DSH Desktop

DSH Desktop loads a profile named desktop, located at ~/.dsh/profiles/desktop/. The manifest must be edited manually and dependencies installed.

  1. Edit ~/.dsh/profiles/desktop/package.json and add the following to dependencies:
    "dsh-image-guard": "github:mafeis/dsh-image-guard"
  1. Append "dsh-image-guard" to the dsh.profile.bundles array in the same file (if the field does not exist, create it under the dsh key):
    "dsh": {
      "profile": {
        "bundles": ["dsh-image-guard"]
      }
    }
  1. After installing the dependencies, restart DSH Desktop:
    cd ~/.dsh/profiles/desktop && pnpm install --prefer-offline --ignore-scripts

Configuration and Debugging

The configuration file is located at ~/.dsh/image-guard.json. Changes support hot reloading, and the settings can also be modified in the settings page or via the /_dsh/image-guard route.

Common configuration options

Field Default Description
enabled true Master switch; when disabled, requests pass through completely
keepRecent 12 Number of latest images to keep; the rest are replaced with markers
maxRetries 3 Number of fallback retries after a 400 error
learnLimit true Whether to parse the upstream limit from the 400 response
dryRun false Observe only; do not modify any request
markerLang empty Marker language; leave empty to follow the host UI language; zh / en are fixed values
placeholder empty Marker template; if empty, use the default template of the active language
pathMode basename full / relative / basename / none; determines how paths are displayed
tokensPerImage 972 Visual tokens per image used to estimate savings
verbose true Whether to output logs

Debugging steps

  1. Observe before enabling: Turn on dryRun on the settings page. At this point, only the images that would be trimmed are recorded, and no request is modified.
  2. Check chain integration: Settings → Image Guard → Diagnostics. It should show that the guard is connected to the fetch chain and that the chain probe count continues to increase.
  3. Check status: chatPosts in ~/.dsh/image-guard-status.json begins to grow.

Marker Mechanism

Trimmed images are replaced with markers. Depending on the image source, the marker content differs:

  • With a path (default): [Image omitted #1 · 01-race-start.png · If needed, search by filename in the workspace and read it]
  • Attachment metadata only: [Image omitted #1 · 01-race-start.png · Inline image sha256:bf23bcd3 (image/png 1280x720 618 KiB) · No original file path; cannot be retrieved]
  • Remote URL: [Image omitted #1 · race.png · https://…/race.png · If needed, you can revisit this URL]

Known Limitations

  • Controls image count only: It does not compress or resize images; byte budgets are out of scope.
  • Initial 400 error: The limit can only be learned from a 400 response, so the first request will still trigger one 400 error.
  • Historical images only: The latest N images are sent as-is (including bytes).
  • Inline images cannot be restored: Inline images without a path cannot be retrieved; the marker states this explicitly.
  • Depends on the current request shape: It depends on DSH’s current request shape; requests that do not match the expected shape are passed through.

Conclusion

dsh-image-guard solves multimodal conversation interruptions caused by the mismatch between DSH’s client-side byte budget and the upstream’s image-count limit by intercepting the request chain. By parsing errors and dynamically adjusting the number of images, it maintains conversation continuity.

Directory page: dsh-image-guard
GitHub: mafeis/dsh-image-guard