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
keepRecentimages 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 ofdsh-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.
- Edit
~/.dsh/profiles/desktop/package.jsonand add the following todependencies:
"dsh-image-guard": "github:mafeis/dsh-image-guard"
- Append
"dsh-image-guard"to thedsh.profile.bundlesarray in the same file (if the field does not exist, create it under thedshkey):
"dsh": {
"profile": {
"bundles": ["dsh-image-guard"]
}
}
- 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¶
- Observe before enabling: Turn on
dryRunon the settings page. At this point, only the images that would be trimmed are recorded, and no request is modified. - 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.
- Check status:
chatPostsin~/.dsh/image-guard-status.jsonbegins 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