Back to home@tuofangzhe

dsh-plugin-image-gen

No description

Stars
0
Language
TypeScript
Created
Aug 27, 2026
Updated
Aug 27, 2026
GitHub repo

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

PluginSupported DSH
0.1.x0.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.

FieldMeaning and default
providersOptional route map. Missing or empty keeps the plugin dormant and registers no tool.
providers.*.baseURLAbsolute HTTP(S) URL without credentials, query, or fragment. Trailing slashes are removed.
providers.*.apiKeyEnvOptional credential reference using POSIX shell-identifier syntax.
providers.*.defaultModelRequired non-empty default image model.
providers.*.modelsOptional model allowlist; omitted or empty means unrestricted.
providers.*.responseFormatActual returned encoding: png (default), jpeg, or webp. The plugin does not transcode provider output.
providers.*.timeoutMsProvider request deadline; default 120000.
defaultProviderRequired with multiple providers; inferred with exactly one.
maxImagesPerCall110; default 4.
outputDirSafe normalized relative path; default generated-images.
maxResponseBytesStreaming HTTP body limit; default 128 MiB.
maxImageBytesPer decoded image limit; default 20 MiB.
maxTotalImageBytesPer-call decoded-image limit; default 80 MiB.
maxErrorCharsProvider/attachment error excerpt, 64–4000 Unicode code points; default 500.
toolTimeoutGraceMsCooperative 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 API n value.
  • provider, model, and size (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_image appears 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_paths fails 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-execute policy may still return ask; 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/generations with b64_json is 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 name and, after scaling, originalDimensions are persisted and reconstructed exactly from the returned reference.
  • attachments.saveImages() has no AbortSignal. Cancellation and the cooperative TOOL_TIMEOUT can 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-fs exposes no binary writer. This supports only a trusted local host, not remote or sandbox filesystem abstractions. Parent lstat checks plus exclusive creation reduce accidents but do not eliminate malicious local-process TOCTOU races.
  • Direct Node filesystem writes bypass ctx.fs, so fs/write-intent approvals and fs/observed policy telemetry cannot see them. Default base composition does not ask solely because of these writes, while a custom tools/pre-execute policy can still require approval for the tool call.
  • A provider's configured responseFormat must match its actual returned bytes and the requested path extension. The plugin rejects mismatches and does not transcode.