dsh-agnes-image
No description
- Stars
- 0
- Language
- JavaScript
- Created
- Oct 6, 2026
- Updated
- Oct 6, 2026
Introduction
English · 简体中文
dsh-agnes-image
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 get | What it changes |
|---|---|
| Three modes, one schema | image omitted → text-to-image; one reference → image-to-image; several → multi-image composition. The model never has to pick a tool. |
| Files, not URLs | Bytes 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 path | Pinned IPv4 on this plugin's sockets only — same request lands in ~9s instead of timing out at 20s+ (measured, see below). |
| Loss-tolerant by design | Bounded retry on connect-level failures only; HTTP statuses and aborts are never retried. |
| Hot-rotating credentials | API key resolved per call from 4 sources — rotate the key and the very next call picks it up, no restart. |
| Zero runtime dependencies | Only node:https + the harness SDK. Nothing else to install, nothing else to break. |
| Readable failures | Every 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.
| Parameter | Required | Description |
|---|---|---|
prompt | yes | What to draw or how to edit. Recommended structure: subject + scene + style + lighting + composition + quality. |
size | Resolution tier 1K / 2K / 3K / 4K (default 2K). | |
ratio | Aspect ratio 1:1 3:4 4:3 16:9 9:16 2:3 3:2 21:9 (default 1:1). | |
image | Reference images (public https URL or data: URI). One = image-to-image, several = composition. Omit for text-to-image. | |
model | Model id (default agnes-image-2.5-flash). | |
output_path | Destination, relative to the session workspace. Default agnes-images/<timestamp>-<prompt-slug>.png. |
Measured output dimensions (match the official docs):
| ratio | 1K | 2K |
|---|---|---|
1:1 | 1024×1024 | 2048×2048 |
16:9 | 1312×736 | 2624×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:
| Approach | Result |
|---|---|
Default (autoSelectFamily + AAAA) | Intermittent 20s+ all-address ETIMEDOUT, empty AggregateError |
Custom lookup pinning family 4 | Still reaches IPv6 (not honored on the multi-address path) — unreliable |
family: 4 + autoSelectFamily: false | Stable success in ~9s |
The middle row is the trap: pinning
family: 4through a customlookupstill produced IPv6 addresses in the error — it is not applied on theautoSelectFamilypath. 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 strategy | Measured |
|---|---|
| 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