Preface

When building agents using the DeepSeek Harness (DSH) Web GUI, there are three scenarios involving images that have always been difficult to handle:

  1. The model wants you to choose an image from several candidates (e.g., selecting a cover for a novel), but the options tab cannot display images; the model can only print out the path for you to open the file yourself;
  2. The model generates an image and wants to mix it with the text in the reply body, but there is no channel for this either;
  3. You send an image into the chat, and the text-only model adapter immediately reports UNSUPPORTED_CONTENT, interrupting the turn.

dsh-plugin-image-tools is a DSH plugin designed for these three scenarios: image selection cards, embedded images in replies, and receiving images by blind models. These three tools handle their respective tasks independently, with all zero-token local rendering implemented purely as a plugin without modifying the core package.

What is this

dsh-plugin-image-tools is maintained by Pasumao under the MIT license. It runs on the Web GUI platform (where dsh.client.platform in package.json is web). The current version is 0.6.6.

Why implemented as a plugin instead of modifying the core: The browser-side consumes question/requested frames using strict Zod schema parsing, stripping unknown fields from option objects; the assistant message content is generated by model text and does not have a channel for carrying structured image blocks. Images cannot be directly stuffed into the option / content fields. The plugin’s solution is: the server normalizes image bytes into a memory registry and serves them directly via a custom web route; the client part registers entries in the conversation.composer slot chain, responsible for rendering and enhancement.

Code and documentation are generated with the assistance of AI, all reviewed manually and verified on real machines (npm run smoke).

Installation and Enablement

npm installation (recommended):

dsh plugin --profile web add dsh-plugin-image-tools

Or install from GitHub source:

dsh plugin --profile web add github:Pasumao/dsh-plugin-image-tools

After installation, restart dsh (launcher) and refresh the browser page. The package comes with a cordis.patch.yml mount line, automatically applied via dsh.profile.bundles, no need to manually modify configuration. The plugin does not read environment variables, requires no API Key / token, and does not write configuration files; it is ready to use out of the box. Pure text questions without images are automatically passed to the native UI, without affecting each other.

Three Tools

The three tools uniformly support three image source forms: local paths (relative to the session workspace or absolute paths), http(s) URLs (fetched by the server and stored), and base64 data URIs. Single image limit is 20 MiB, supporting only PNG / JPEG / WebP / GIF (checked by magic number; errors reported if declared format does not match).

ask_user_choice: Picking Images in Options

The model passes in a questions array; each option can include an image (choose one of path / url / data). Pure images, pure text, and mixed text/image options can be mixed in the same question. Supports multi-question pagination, single/multi-select, custom answers, and skipping; appending (Recommended) or (推荐) to the label displays a recommended tag. Clicking the thumbnail opens a Lightbox with the full image; exit via Esc, clicking the overlay, or the close button. The selection card is rendered by the Web GUI, and the answer protocol is consistent with the native one:

{
  "questions": [
    {
      "id": "cover",
      "question": "选一张封面图",
      "header": "封面选择",
      "options": [
        { "label": "深海鲸鱼 (Recommended)", "image": { "path": "novel/assets/covers/whale.png" } },
        { "label": "星空", "image": { "url": "https://example.com/stars.png" } },
        { "label": "手绘风",
          "image": { "data": "data:image/png;base64,iVBORw0KGgo..." } },
        { "label": "都不选,我自己说", "description": "选这个可以在下方输入自定义答案" }
      ],
      "multi_select": false
    }
  ]
}
// 返回{ "answers": [ { "id": "cover", "selected": ["深海鲸鱼 (Recommended)"] } ] }

show_images: Displaying Images in Reply Body

When calling, pass an images array (1-9 images at a time, each can include a caption). The tool returns a markdown image fragment with an absolute URL. The model pastes the fragment line-by-line into the reply body as-is, and the images appear with the text:

// 调用 show_images
{
  "images": [
    { "image": { "path": "novel/assets/covers/whale.png" }, "caption": "深海鲸鱼封面" },
    { "image": { "url": "https://example.com/stars.png" }, "caption": "星空" }
  ]
}
// 返回:{ "markdown": ["![深海鲸鱼封面](http://127.0.0.1:3080/dsh-plugin-image-tools/show/<id>/0)", "![星空](http://127.0.0.1:3080/dsh-plugin-image-tools/show/<id>/1)"] }

The client plugin performs progressive enhancement on these images: rounded corners, hover tooltips, click-to-enlarge, and fallback on load failure.

save_received_images: Receiving Images for Blind Models and Saving to Files

When a user sends an image into the chat, the agent/pre-step listener registered by the plugin rewrites the image blocks in the messages entering the LLM step as the text placeholder dshimg:<attachmentId>. The text-only adapter no longer reports UNSUPPORTED_CONTENT for image blocks, and the turn proceeds normally; the placeholder is replaced by an expandable image echo in the user’s bubble by the client enhancer.

After the model sees the placeholder, it calls save_received_images to save the images as workspace files by attachmentId, with the default directory received/; the file name prioritizes the safe filename provided by the attachment, otherwise generated as image-<n>-<timestamp>.<ext>. Subsequently, the file/command tools can be used to analyze the images (dimensions, pixels, hash, etc.).

Limitations and Notes

  • Image bytes are stored only in process memory: selection card images are released immediately upon answering/cancelling the question; embedded images and attachment echoes rely on a 30-minute TTL cleanup.
  • Image route URLs are content-addressed, with the response header sending Cache-Control: private, max-age=2592000, immutable (30-day browser cache); after server cleanup, refreshing the page can still hit the local cache.
  • The image route is a regular HTTP route on the same origin (same trust level as GUI), without extra authentication.
  • The markdown URL for embedded images is an absolute address; if the GUI is accessed via a reverse proxy or changed port, image addresses in historical messages may become invalid.
  • Compatibility: Tested on DSH 0.1.2-rc.1 (adapted to the new selection card protocol from 0.6.5, testing with built-in react/react-dom from 0.6.6), depends on client services slots / locale.
  • The plugin runs with the current dsh process permissions; it is recommended to review the source code and license (MIT) before installation.

Conclusion

dsh-plugin-image-tools covers the three tasks of “selecting images, viewing images, and sending images” in the Web GUI using three tools, with zero-token local rendering, without modifying the core package, and without requiring configuration. If you are working on workflows involving images in DSH (cover selection, image display, sending images to text models for analysis), you can install and try it directly.

  • Community Plugin Directory (independent site, no official affiliation with DeepSeek / Hypothesis): https://www.skillhub.cn/plugins/Pasumao/dsh-plugin-image-tools
  • GitHub Repository: https://github.com/Pasumao/dsh-plugin-image-tools