@agent-forge/plugin-image-generation
First-party image generation capability plugin for Agent Forge. Registers the
image_generate and image_edit tools (full-control hosts) against the
host-configured provider's image endpoint (OpenAI-compatible request shapes
by default, with a Grok Imagine edit adapter):
- Credentials (
apiKey+baseUrl) are resolved at runtime through the publicCredentialAPI(api.credentials.resolve({ providerId })); the plugin keeps no credentials of its own. provider/model/baseUrlcome from the session-selectedgenerate_imagetarget (/modelcapability menu); the call cannot override them. The first adapter isopenai-images.capabilityConfig.generate_image.optionsmay provide defaultsize,qualityandn; explicit tool parameters win.- Hosts without a capability-selection service fall back to the legacy capability-slot chain (settings slot, env, provider inference). New hosts use the session selection exclusively.
- Generated and edited image files are written under the workspace
generated-images/directory, named and typed by their actual format (magic-byte sniff: png/jpg/webp/gif; unknown falls back to png); the tool result returns their workspace-relative paths only (see "Images never ride the conversation" below) — the model reads a returned file to view it. - Cancellation cascades through
api.sessionAbortSignal()and the invocation signal; requests time out after 180s and error messages never echo the API key or request headers. - A 401/403 from the images API is reported as
credentials_insufficient(notimage_request_failed): the message names the rejected key and points at the provider-specificauth.jsonentry. A single gateway key often has text capability but no image capability, so register the dedicated image key for the image provider instead of reusing the main one. - Image edits use the provider's
/images/editsendpoint. The plugin selects the request protocol from the model ID:grok-imagine-imageandgrok-imagine-image-*use the Grok JSON data-URL shape; other models use OpenAI multipart. Provider credentials and endpoint configuration remain host supplied.
Configuring an image provider
Register the model in models.json, then select it in /model → 生图 (or set
settings.defaultCapabilities.generate_image). The settings.json slot below is the
legacy fallback used only by hosts without a capability-selection service:
{
"capabilities": {
"imageGeneration": { "provider": "myimg", "model": "gpt-image-1" }
}
}
models.json registers the provider and its image model (capabilities
marks image-capable models; the adapter selects the image protocol):
{
"providers": {
"myimg": {
"baseUrl": "https://img.example.com/v1",
"api": "openai-completions",
"models": [
{
"id": "gpt-image-1",
"capabilities": ["generate_image"],
"capabilityConfig": { "generate_image": { "adapter": "openai-images" } }
}
]
}
}
}
Image generation consumes only baseUrl, the provider credential, and the
model id; chat-model fields such as contextWindow/maxTokens do not affect
image requests and can be omitted (declaring them only changes the
--list-models display).
Save the provider key with /login (it is stored in auth.json); models.json
rejects an apiKey field at load. The plugin resolves the key and baseUrl
through the host's credential API: the stored key lives in auth.json, and
models.json is not a key source — --api-key/runtime-injected and
provider-registered keys are resolved too.
With only the models.json registration (no capabilities settings), the
plugin auto-uses the single registered image provider (inference layers 4/5).
Registering an image model in models.json implies that provider really has
an /images/generations endpoint; chat models never take part
in the inference. The slot travels to the plugin through the host's
assembly-time configOverride injection (api.config.imageGeneration +
api.config.imageModelCatalog snapshot) — the plugin never reads host
settings files itself.
Proxy support
Every outbound request (API call and provider-CDN image download) honors the
standard proxy environment variables — HTTPS_PROXY, HTTP_PROXY, NO_PROXY
(both cases work, same convention as curl/git/npm) — via undici's
EnvHttpProxyAgent, injected per request. Node's fetch ignores these
variables by default, so if your machine reaches the internet only through a
VPN/proxy client, export its local HTTP port before running the host, e.g.:
export HTTPS_PROXY=http://127.0.0.1:7897
export NO_PROXY=api.shenwenai.com # optional: keep the gateway direct
OS-level proxy settings (macOS network settings, Windows registry) are not read; export the port manually. The global dispatcher is never replaced — only this plugin's requests are affected.
Notes:
HTTP(S)_PROXYmust point at an http proxy (most VPN clients expose a mixed http/socks port; use that port with anhttp://URL).socks5://URLs are rejected with an explicit error — undici speaks http proxies only.ALL_PROXYis not read (undici limitation); setHTTP_PROXY/HTTPS_PROXY.- The host itself already honors these variables for model traffic through
its own global dispatcher (http-dispatcher's
EnvHttpProxyAgent); this plugin's wrapper exists so image traffic also works in hosts that do not install that global dispatcher (embedded hosts), and to reject socks URLs with an actionable message. - Enterprise proxies that re-sign TLS require
NODE_EXTRA_CA_CERTSwith the proxy's root certificate.
Images never ride the conversation (read on demand)
Image payloads are never inlined into the conversation — they would be
re-sent to the model on every later turn. The tool result carries conclusions
only: {files, model, usage} plus a note telling the model to read a file.
When the model actually needs to look at what it produced (e.g. to revise it),
it reads the file — the host read tool serves images as attachments and
auto-resizes them, which is both cheaper and more precise than carrying raw
payloads in history. To let your conversation model see images at all, declare
"capabilities": ["text", "visual_image"] for it in the provider's models.json
entry (the host drops image parts for text-only models with a readable note).