dsh-plugin-image-gen
No description
- Stars
- 0
- Language
- TypeScript
- Created
- Aug 27, 2026
- Updated
- Aug 27, 2026
Introduction
@dopilot/dsh-plugin-image-gen
DeepSeek Harness plugin that exposes an OpenAI-compatible generate_image tool. It sends one synchronous POST <baseURL>/images/generations request, writes every returned image to the current session workspace, and—when available—stores an attachment reference so an image-capable conversation model can see the result.
The package is ESM-only and requires Node.js 22.19 or newer. It intentionally has no default export; its Cordis entry points are the named exports name, inject, Config, and apply.
Installation
Install the public bundle into the profile you use:
dsh plugin --profile web add @dopilot/dsh-plugin-image-gen
The package inserts a dormant dopilot-image-gen row into that profile. Configure it by patching the same id in the profile's cordis.patch.yml; config is replaced whole rather than deep-merged, so restate every key you need. Remove both the dependency and bundle layer with dsh plugin --profile web remove @dopilot/dsh-plugin-image-gen.
For source development, use npm ci, npm run typecheck, npm test, and npm run build. The DSH rc.2 subpackages are published under prerelease versions whose npm latest tags may still point at older builds. This repository pins the tested development versions and declares exact 0.1.1-rc.2 DSH peers.
Compatibility
| Plugin | Supported DSH |
|---|---|
0.1.x | 0.1.1-rc.2 |
Configuration
In the target profile's cordis.patch.yml, override the bundle row by id:
- id: dopilot-image-gen
config:
providers:
openai:
baseURL: https://api.openai.com/v1
apiKeyEnv: OPENAI_IMAGE_API_KEY
defaultModel: gpt-image-1
models: [gpt-image-1]
responseFormat: png
timeoutMs: 120000
gateway:
baseURL: https://images.example.com/openai/v1
apiKeyEnv: CUSTOM_IMAGE_API_KEY
defaultModel: organization/image-model
responseFormat: webp
timeoutMs: 180000
defaultProvider: openai
maxImagesPerCall: 4
outputDir: generated-images
maxResponseBytes: 134217728
maxImageBytes: 20971520
maxTotalImageBytes: 83886080
maxErrorChars: 500
toolTimeoutGraceMs: 30000
apiKeyEnv is a credential reference name, not a secret value. It is resolved through the DSH credentials service for every call and is never cached. Omit it for a gateway that needs no Authorization header.
| Field | Meaning and default |
|---|---|
providers | Optional route map. Missing or empty keeps the plugin dormant and registers no tool. |
providers.*.baseURL | Absolute HTTP(S) URL without credentials, query, or fragment. Trailing slashes are removed. |
providers.*.apiKeyEnv | Optional credential reference using POSIX shell-identifier syntax. |
providers.*.defaultModel | Required non-empty default image model. |
providers.*.models | Optional model allowlist; omitted or empty means unrestricted. |
providers.*.responseFormat | Actual returned encoding: png (default), jpeg, or webp. The plugin does not transcode provider output. |
providers.*.timeoutMs | Provider request deadline; default 120000. |
defaultProvider | Required with multiple providers; inferred with exactly one. |
maxImagesPerCall | 1–10; default 4. |
outputDir | Safe normalized relative path; default generated-images. |
maxResponseBytes | Streaming HTTP body limit; default 128 MiB. |
maxImageBytes | Per decoded image limit; default 20 MiB. |
maxTotalImageBytes | Per-call decoded-image limit; default 80 MiB. |
maxErrorChars | Provider/attachment error excerpt, 64–4000 Unicode code points; default 500. |
toolTimeoutGraceMs | Cooperative tool deadline margin above the slowest provider; default 30000 ms. |
Missing or empty providers is an intentional dormant state: DSH starts normally, the plugin logs how to configure the profile, and generate_image is not registered. Once providers is non-empty, invalid values fail during plugin loading. The combined slowest provider timeout and tool grace may not exceed 2_147_483_647 ms. Byte limits must satisfy maxImageBytes <= maxTotalImageBytes <= maxResponseBytes.
Tool contract
generate_image accepts:
prompt(required string): the generation instructions.output_paths(required string array): one path per requested image; its length is the APInvalue.provider,model, andsize(optional strings): route/model overrides and a size matching\d{3,4}x\d{3,4}.
Every path must already be normalized with /, be unique, and name a direct child of the configured outputDir. Filenames use ASCII letters, digits, _, or -, followed by .png, .jpg/.jpeg, or .webp as required by the provider's responseFormat. Absolute paths, backslashes, dot segments, nested arbitrary directories, Windows device names, and existing files are rejected. Files are created with exclusive wx semantics and are never overwritten.
Example model call:
{
"prompt": "A red circle centered on a clean white background",
"provider": "openai",
"size": "1024x1024",
"output_paths": ["generated-images/red-circle-1.png"]
}
A successful canonical result records the provider/model, the original on-disk encoding, byte count and dimensions, plus any attachment facts returned by the attachment service. Attachment metadata is a separate fact set because the service may normalize the stored copy. The text result always reports the on-disk facts and paths. An image block is added only when the current conversation route explicitly advertises image input and a complete attachment reference exists.
Local smoke test
From a source checkout:
cd /path/to/dsh-plugin-image-gen
npm ci
npm run build
cd ~/dsh/deepseek-harness-master
pnpm dsh plugin --profile web add /path/to/dsh-plugin-image-gen
export OPENAI_IMAGE_API_KEY='<real key>'
export IMAGE_API_MODEL='gpt-image-1'
pnpm dsh web --patch /path/to/dsh-plugin-image-gen/test/smoke.overlay.yml
For an installed DSH, use the same sequence with dsh in place of pnpm dsh. The plugin ... add step is required; --patch alone neither installs the package nor activates its bundle. Installation must not emit the “bundle-less dependency” warning. Before configuring providers, the profile must still boot with the plugin dormant and no generate_image schema.
In the Web UI, select a local workspace, create a session, and ask:
Generate one image of a red circle on white background. Save it as generated-images/red-circle-smoke-1.png.
Verify all of the following:
generate_imageappears in the tool list, and the pending call displays the planned path.- The successful call creates the exact PNG in the session workspace. The final “产出文件” row lists the same path and opens it.
- The generic tool card does not show an inline thumbnail; that is intentionally deferred.
- Reusing the same
output_pathsfails and leaves the original bytes unchanged. - A text-only conversation model completes without
UNSUPPORTED_CONTENT; it receives text but no image block. - With default base composition, Node filesystem output alone does not trigger an approval dialog. A custom
tools/pre-executepolicy may still returnask; follow that deployment policy and record the difference. - If practical, generate a 2048×2048 image. The rc.2 defaults do not reject it merely for being 2048 px. Any byte/pixel/deployment/storage rejection must appear in the result warning while the original file still lands on disk.
Before setting a real key, run an independent failure smoke. The result must name OPENAI_IMAGE_API_KEY as unconfigured and must not reveal any credential value. This does not replace the successful-key smoke.
Model Experience
Tool schema and result content
What the model sees
The complete tool name, description, parameter schema, configured outputDir, default provider, and each provider's declared format enter the model's system prompt. A successful result always includes a text envelope listing the durable workspace paths and original file facts. Image blocks appear only when the current conversation provider/model route explicitly supports image input and attachment storage succeeded; otherwise the model receives an explanatory warning and disk paths only.
Token effect
The tool schema consumes system-prompt tokens whenever the plugin is active. Each call adds a compact text summary to the conversation. Provider base64, API keys, prompts, and binary bytes are never copied into the canonical value or text result. An eligible image block contributes the image representation selected by the active LLM adapter.
KV Cache effect
The schema is deterministic for a fixed plugin configuration. Changing provider routes, formats, outputDir, or the default provider and then reloading/HMR-registering the plugin changes model-visible schema text, so the affected system-prompt prefix and its KV cache can no longer be reused. Ordinary calls do not mutate the schema.
Known Limitations and Deferred Work
- Only synchronous OpenAI-compatible
POST /images/generationswithb64_jsonis supported. There are no video, asynchronous polling/job, image edit, or variation APIs. - There is no settings-page UI card and no chat-card thumbnail. A future browser client plugin would need to render a keyed tool view and load the attachment through the host RPC.
- DoPilot payload integration is intentionally deferred; this package and smoke overlay are standalone development artifacts.
- Attachment storage is optional and has independent deployment limits. The rc.2 attachment-local defaults admit source images up to 20 MiB, 8192 px per dimension, 64 million pixels, 20 images and 200 MiB per batch; saved copies may be normalized to at most 2048 px and 4 MiB. A valid 2048×2048 source is not rejected merely for that dimension. Attachment rejection degrades to disk-only output.
- Attachment metadata describes the normalized stored copy, so its encoding, bytes, width, and height may differ from the original disk file. The optional
nameand, after scaling,originalDimensionsare persisted and reconstructed exactly from the returned reference. attachments.saveImages()has noAbortSignal. Cancellation and the cooperativeTOOL_TIMEOUTcan only be observed after that operation settles, so attachment storage can delay timeout convergence.- Binary files are written through Node's local filesystem because rc.2
dsh-fsexposes no binary writer. This supports only a trusted local host, not remote or sandbox filesystem abstractions. Parentlstatchecks plus exclusive creation reduce accidents but do not eliminate malicious local-process TOCTOU races. - Direct Node filesystem writes bypass
ctx.fs, sofs/write-intentapprovals andfs/observedpolicy telemetry cannot see them. Default base composition does not ask solely because of these writes, while a customtools/pre-executepolicy can still require approval for the tool call. - A provider's configured
responseFormatmust match its actual returned bytes and the requested path extension. The plugin rejects mismatches and does not transcode.