← Back to home@Lijianan-123

dsh-agnes-image

No description

Stars
0
Language
JavaScript
Created
Oct 6, 2026
Updated
Oct 6, 2026
GitHub repo

Introduction

English · 简体中文

dsh-agnes-image banner

dsh-agnes-image

license node runtime deps dsh profiles stars

One tool, three modes. A DeepSeek Harness plugin that gives your agent Agnes Image 2.5 Flash (agnes-image-2.5-flash): text-to-image, image-to-image, and multi-image composition — with the result saved as a real PNG inside the session workspace, ready to show, present, or re-inspect with read_image.

Underneath the one-tool surface is the part most image plugins skip: the network engineering that makes a lossy endpoint behave like a reliable one. Everything in Engineering deep-dive was measured, not guessed.

Why this plugin

What you getWhat it changes
Three modes, one schemaimage omitted → text-to-image; one reference → image-to-image; several → multi-image composition. The model never has to pick a tool.
Files, not URLsBytes are requested inline (base64) and written into the session workspace. The agent gets a path it can render, diff, or feed back in as a reference.
Deterministic connect pathPinned IPv4 on this plugin's sockets only — same request lands in ~9s instead of timing out at 20s+ (measured, see below).
Loss-tolerant by designBounded retry on connect-level failures only; HTTP statuses and aborts are never retried.
Hot-rotating credentialsAPI key resolved per call from 4 sources — rotate the key and the very next call picks it up, no restart.
Zero runtime dependenciesOnly node:https + the harness SDK. Nothing else to install, nothing else to break.
Readable failuresEvery error — including a message-less AggregateError — is normalized into one actionable line naming the attempt count.

Install

Requires Node.js 22+ and a DeepSeek Harness profile (web, desktop, or headless).

# from GitHub
dsh plugin --profile web add github:Lijianan-123/dsh-agnes-image

# or from a local checkout
git clone https://github.com/Lijianan-123/dsh-agnes-image.git
dsh plugin --profile web add ./dsh-agnes-image

Restart the harness after installing. Then set the API key (any one of):

# 1. environment variable
export AGNES_API_KEY=sk-...

# 2. harness credential store:  ~/.dsh/.credentials.yaml → refs.AGONES_API_KEY
# 3. key file:                  ~/.dsh/agnes-image.json  → {"apiKey": "sk-..."}

Use it

Once installed, just ask in natural language:

Generate a 16:9 hero image for my landing page: a glass-and-steel medical device on a dark background, soft rim light, product photography style.

Take this UI screenshot and this logo, compose them into a clean App Store preview card.

ParameterRequiredDescription
promptyesWhat to draw or how to edit. Recommended structure: subject + scene + style + lighting + composition + quality.
sizeResolution tier 1K / 2K / 3K / 4K (default 2K).
ratioAspect ratio 1:1 3:4 4:3 16:9 9:16 2:3 3:2 21:9 (default 1:1).
imageReference images (public https URL or data: URI). One = image-to-image, several = composition. Omit for text-to-image.
modelModel id (default agnes-image-2.5-flash).
output_pathDestination, relative to the session workspace. Default agnes-images/<timestamp>-<prompt-slug>.png.

Measured output dimensions (match the official docs):

ratio1K2K
1:11024×10242048×2048
16:91312×7362624×1472

Configuration

All keys are optional, applied over these defaults by the Loader:

{
  "apiKey": "",               // inline key; wins over every other source
  "credentialRef": "AGONES_API_KEY",
  "endpoint": "https://apihub.agnes-ai.com/v1/images/generations",
  "model": "agnes-image-2.5-flash",
  "defaultSize": "2K",
  "defaultRatio": "1:1",
  "outputDir": "",            // empty → <session cwd>/agnes-images
  "timeoutMs": 300000,
  "attempts": 3               // total attempts on connect-level failures
}

API key resolution order (re-resolved on every call, never cached): plugin config → harness credential store → environment (AGONES_API_KEY / AGNES_API_KEY) → key file. Key rotation reaches the very next call with no restart.

Engineering deep-dive

Four field-tested details are baked into ~470 lines of dependency-free code. Each one cost a real debugging session — the measurements below are why this plugin is reliable where a naive fetch wrapper is not.

1. Pin IPv4 and disable happy-eyeballs — on our sockets only

The endpoint resolves to Cloudflare (104.18.18.62 / 104.18.19.62) and advertises unroutable AAAA records. Node's default autoSelectFamily runs a happy-eyeballs race: it burns the IPv6 attempts first and ends in a 20s+ all-address ETIMEDOUT, surfaced as an AggregateError with an empty message — the model literally sees a bare Error:.

The fix is family: 4 + autoSelectFamily: false, collapsing the connect to one deterministic IPv4 path without touching process-level DNS:

ApproachResult
Default (autoSelectFamily + AAAA)Intermittent 20s+ all-address ETIMEDOUT, empty AggregateError
Custom lookup pinning family 4Still reaches IPv6 (not honored on the multi-address path) — unreliable
family: 4 + autoSelectFamily: falseStable success in ~9s

The middle row is the trap: pinning family: 4 through a custom lookup still produced IPv6 addresses in the error — it is not applied on the autoSelectFamily path. Don't conclude from a single success.

2. The route is genuinely lossy — retry, but only connect-level failures

An identical request can time out on every address one minute and answer normally the next. The plugin retries with bounded backoff (600/1200 ms, 3 attempts by default) and only when no HTTP response was ever received. AbortError and any HTTP status are never retried; every thrown failure is normalized into one line that names how many attempts were actually made.

3. Fetch the image in one hop, not two (a deliberate trade-off)

The API returns an image URL by default, requiring a second network round-trip to a different host (platform-outputs.agnes-ai.space). In testing, generations succeeded and then died on the download hop, wasting the generation.

So the plugin asks for the bytes inline in the generation response: return_base64: true for text-to-image, extra_body.response_format: "b64_json" for reference-image work. One request, one host, no download to strand. The URL path remains only as a fallback.

Fetch strategyMeasured
URL + download (two hops)58.3s — and the download was aborted once mid-test
Inline base64 (one hop)16.6s

4. response_format must never sit at the top level

The API requires it inside extra_body (same for extra_body.image on reference work). A top-level response_format — or tags: ["img2img"] for image-to-image — is silently wrong. The payload is constructed per mode accordingly.

Verify

An offline end-to-end smoke test imports the plugin, captures the tool through a fake cordis context, executes a real generation, and checks the bytes on disk:

node scripts/smoke.mjs <plugin-dir> <workspace-dir>
# registered tool: agnes_image
# elapsed: 16600 ms
# PASS: byte count matches disk

Known boundary: router-standard preset gating

The router-standard preset uses tools.restrict for progressive disclosure (whitelist = stage tools ∪ META_TOOLS ∩ GLOBAL_SAFE). agnes_image is not in that list, so under that preset it is hidden in stages 0–2 and unlocked at stage 3 (verification/delivery). Built-in presets (standard / minimal / ptc / cordis) and no-preset sessions are unaffected.

Compatibility

  • @deepseek-ai/dsh-tools >=0.1.5-rc.1 <0.2.0-0 || >=0.2.0-rc.1 <0.3.0-0, @deepseek-ai/schemastery ^3.18.2 — all declared as ranges, no hard-pinned harness version.
  • Node.js 22+; built-in modules only beyond the harness SDK.

Like it?

If this plugin saved you a debugging session — or the deep-dive taught you something — a ⭐ is much appreciated. Issues and PRs welcome.

License

MIT © 2026 Jianan Li