Docs/Integrations
Integrations

Image Agent

Conversational image workspace in the official Nuxt app: what it does, what to configure, and how a developer connects it.

Last updated Sep 29, 2026

Image Agent is the conversational image workspace shipped with apps/web-nuxt. A signed-in user describes a new image or a change to an existing one. A dialogue model decides whether to answer, ask a clarifying question, or call an image model. Finished images stay in the conversation and can be edited again.

The workspace lives in Node10 (node10-ai-service). Image generation goes through AI Provider Service. Text chat on /dashboard/ai-chat does not create images. AI Playground sends one generation request with the fields the user filled in. Image Agent keeps the conversation, the run, and the image versions, and continues a run after a refresh.

What the user can do

  • Talk through a brief, then generate or edit.
  • Attach images by upload, drag and drop, a public HTTPS link, or the material library.
  • Mark a reference and ask for a change against that mark.
  • Pick an image model, aspect ratio, and the settings that model requires.
  • Stop a run. A failed image operation follows the Provider refund rules. Dialogue credits already settled stay settled.
  • Reopen a conversation and see the same messages, references, versions, and progress.

Each accepted run may call generate_image, edit_image, and inspect_images. generate_image starts a new image. edit_image changes or combines images already in the conversation. inspect_images loads a closer look before the next edit. One run is capped by the Admin policy: model steps, image operations, credits, and time.

How one message runs

  1. The browser calls the Nuxt BFF at /api/image-agent/* with the user's API key (Authorization: Bearer sk-...). The browser never receives the service key.
  2. Node10 stores the conversation and the turn, then starts a Cloudflare Workflow.
  3. The dialogue model runs on the AI Gateway already configured for Node10.
  4. When the model asks for an image, the workflow calls Provider POST /generate with an idempotency key tied to that run and attempt.
  5. Provider bills image credits through Node1, applies content moderation when a moderation config is active, and returns the image URL.
  6. The page reads GET /api/image-agent/turns/:id/events and restores the same run after a reload.

Closing the tab does not create a second paid submission for work the workflow already accepted.

Where it appears

Piece Location
User page /dashboard/image-agent in apps/web-nuxt
Sidebar entry imageAgent.enabled in apps/web-nuxt/app/app.config.ts
Browser API apps/web-nuxt/server/api/image-agent/[...].ts
Admin AI → Image Agent
Image tasks Providers → Generation Tasks. A task whose client namespace is image-agent links to that run's dialogue charges

The Astro, SvelteKit, and TanStack templates do not include this workspace. Copying those apps does not add the page. Set imageAgent.enabled to false in a Nuxt copy to hide the sidebar item. The route remains in the app.

The page requires a signed-in user with an API key. Guests are sent through the normal sign-in flow.

Enable it

Image Agent stays unavailable until the Admin runtime checks are configured and Accept new runs is on. GET /api/image-agent/capabilities returns available: false until then. Existing runs keep the limits they were accepted with.

Node10

Deploy node10-ai-service with:

Binding or secret Role
IMAGE_AGENT_WORKFLOW Cloudflare Workflow that owns the run
IMAGE_AGENT_CREDENTIAL_KEY 64-character hex secret. Node10 uses it to encrypt the user API key stored with an in-flight run. Keep it a secret, not a plain var
AI, or a gateway API key and base URL Dialogue model upstream
PROVIDER1_SERVICE or PROVIDER_URL Image generation and image moderation
IMAGE_AGENT_MEDIA Private R2 bucket for user references and annotation images. Do not attach a public domain
IMAGE_AGENT_MEDIA_PUBLIC_URL HTTPS base of the signed media route, for example https://media.example.com/image-agent-media. May carry a path prefix; no query or hash
IMAGE_AGENT_MEDIA_DEDICATED_HOST Set to true when the host of IMAGE_AGENT_MEDIA_PUBLIC_URL is dedicated to Node10 media. That host then serves only signed media and returns 404 for every other path, so none of Node10's API is exposed there
IMAGE_AGENT_MEDIA_SIGNING_KEY 64-character hex secret. Node10 uses it to sign the expiring machine URLs served under IMAGE_AGENT_MEDIA_PUBLIC_URL. Keep it a secret, not a plain var
IMAGE_AGENT_LEGACY_MEDIA Optional R2 bucket holding media written before the private bucket existed. Bind it to keep serving old storage='legacy' rows, or omit it to retire them
IMAGE_AGENT_MEDIA_QUEUE Queue that downloads, stores, and reviews new media in the background, plus its dead-letter queue (zship-image-agent-media-dlq)

IMAGE_AGENT_MEDIA_PUBLIC_URL does not serve the R2 bucket directly. It is the base of an HMAC-signed route on Node10, so that host and path must be routed to node10-ai-service — the official wrangler config puts it on a dedicated custom domain (media.zship.ai, base https://media.zship.ai/image-agent-media). With IMAGE_AGENT_MEDIA_DEDICATED_HOST set to true, that host serves only signed media and returns 404 for every other path, so none of Node10's API is exposed there. A template user points their own host at node10-ai-service and sets both IMAGE_AGENT_MEDIA_PUBLIC_URL and IMAGE_AGENT_MEDIA_DEDICATED_HOST to match. Browsers never use the signed route: they read user media through the same-origin session path GET /api/image-agent/media/:id/content, which is authenticated by the login cookie and never expires. Machines — the dialogue model upstream, the image provider, and the moderation adapter — receive signed URLs minted at the point the request leaves Node10, with at most a seven-day expiry. Signed URLs are never stored on a turn, run, or operation.

The Worker cron reconciles runs that are still open and re-enqueues media that stayed pending. Leave that trigger in place.

Admin → AI → Image Agent

  1. Choose an enabled dialogue model with valid pricing. A paid model needs a credit ceiling of at least 1. A free model may use a lower ceiling.
  2. Set the ceilings. Defaults are 6 model steps (2–8), 2 image operations (1–3), 100 credits (up to 1000), and 1800 seconds (60–1800).
  3. Set inference if you need to. Output tokens default to 1600, between 256 and 8192. Reasoning effort defaults to the model default.
  4. Confirm the runtime row: workflow, credential encryption, model upstream, image service, and media.
  5. Run the model probe and the media probe. The model probe spends a small dialogue charge.
  6. Turn on Accept new runs and save.

Image models

In Providers, add an API key for each image provider you want users to see. The picker lists image models from providers that have a key. Video and audio models stay out of this picker.

image_delivery is optional. When that provider advertises durable delivery, the run stores the delivered object and uses that URL. When it does not, the run keeps the provider's HTTPS image URL and the model still appears in the picker.

Credits

Two ledgers apply to one message:

Charge Where it is recorded
Dialogue tokens Node10, on the Image Agent run as dialogue credits paid. AI Gateway logs use source image_agent
Image generation Provider task credits, the same ledger as a direct Playground generation

The credit ceiling is the budget for that run. It is not an error message. If the user cannot cover the next dialogue or image call, the run waits or stops under that ceiling. A failed image submission follows Provider's refund path. Retry an Image Agent image task from the conversation, not from the Provider retry button.

Reference images

Attaching an image returns immediately. POST /media/import and POST /media/upload record a pending row and hand the download, storage, and content review to IMAGE_AGENT_MEDIA_QUEUE. The workspace shows the attachment in a processing state, polls GET /media/:id, and keeps Send disabled until every attachment is ready. A failed attachment shows its reason and can be removed; typing is never blocked.

Uploads accept PNG, JPEG, WebP, GIF, and AVIF, up to 10 MiB each. A pasted link is imported when it is public HTTPS: no username, password, or port, and not a local, internal, or raw-IP host. Each redirect is checked the same way, up to three hops. The file must decode as one of those image types. Imported bytes belong to that user and project, and live in the private IMAGE_AGENT_MEDIA bucket.

Content moderation

Image Agent does not have a separate moderation switch. A Provider POST /generate from a run uses the same content-moderation config as any other generation for that app_key, or the * fallback when the project has no active row.

  • No active moderation config means the image request is not reviewed.
  • An active config reviews the configured prompt fields before the image provider is called. A deny decision blocks the image.
  • If the moderation API times out or returns an error, Fail open lets generation continue. Turn that off to stop the image call when moderation cannot decide.
  • A flagged result still blocks when Block flagged prompts is on.
  • The dialogue model call itself is not sent through content moderation.
  • This path does not use Turnstile.

Reference media has its own review. While a queued import or upload is processed, Node10 sends the staged image to Provider's image moderation endpoint with a short-lived signed URL. An enabled config needs an Image moderation API URL on the moderation row; its Image moderation credits are charged to the user when review is performed. Without those two fields the media review is skipped and the attachment becomes ready after it is stored. A denied or blocked attachment never becomes usable as a reference.

Setup steps and provider adapters are in AI Provider Service. Use a moderation API key, not the image provider's generation key.

Call it from your own UI

The Nuxt workspace is the supported client. A custom UI should keep the same boundary: your server holds the service credentials, and the browser only sends the signed-in user's sk- key to your BFF.

Official BFF routes, relative to /api/image-agent:

Method Path Purpose
GET /capabilities Whether new runs are accepted, the dialogue model, and the ceilings
GET /conversations This user's conversations
POST /conversations Create one. Body: id, title
GET /conversations/:id Conversation plus turns
POST /conversations/:id/update Rename, or { "archived": true } after the active run has stopped
POST /media/import Body: { "url": "https://..." }. Returns { id, state, url } right away
POST /media/upload Raw image bytes with an image Content-Type. Same response shape
GET /media/:id Submission state: pending, ready, failed, or blocked
GET /media/:id/content Bytes for an image owned by this user. sk- clients use the API key; browsers may use the login cookie instead
POST /turns Save the user message. See the body below
POST /turns/:id/generate Start the workflow. Optional body: { "limits": { "maxCredits": 20, "maxOperations": 1 } }
GET /turns/:id/events?after=0 Incremental events. after is the last event id you have stored
GET /turns/:id/status Current turn, including workflow progress
POST /turns/:id/cancel Stop new work. Accepted image work still settles
POST /turns/:id/resume Continue a run that is waiting on the user or on credits

Create the turn before generate:

{
  "id": "turn-uuid",
  "conversation_id": "conversation-uuid",
  "prompt": "Replace the background with a night street",
  "references": [],
  "reference_ids": [],
  "annotations": [],
  "mode": "agent",
  "execution": "workflow",
  "request": {
    "provider": "your-provider-key",
    "model": "your-image-model"
  },
  "agent_options": {
    "promptField": "prompt",
    "referenceField": "image_urls",
    "referenceArray": true,
    "maxReferences": 4
  }
}

request is the image model's public parameter object. Do not send credentials, callback URLs, or idempotency fields in it. agent_options must match that model's prompt field and reference field. The official workspace fills both from the Provider catalog; a custom client has to do the same mapping.

useImageAgent in apps/web-nuxt is the reference for conversation restore, event polling, and credit refresh. Prefer that composable over a second client.

Local preview

In nuxt dev only, open /dashboard/image-agent?mock=1&case=edits. Other case names cover an empty thread, a pending run, insufficient credits, cancellation, and a completed delivery. A production build ignores mock.

When a run will not start

What you see What to check
Sidebar item missing imageAgent.enabled in the Nuxt app config
Capabilities say unavailable Admin: dialogue model, pricing, Accept new runs, and every runtime dependency
No image models in the picker Provider API keys, and that the model is an image model
Import link fails Public HTTPS, a real image, at most 10 MiB, no local or raw-IP host
Attachment stays in processing IMAGE_AGENT_MEDIA_QUEUE producer/consumer, the DLQ, and IMAGE_AGENT_MEDIA_SIGNING_KEY
Image never starts, no Provider task Workflow binding, credential secret, and the user's credit balance
Provider task exists, dialogue charge is elsewhere Open the task's dialogue-charge link, or AI → Image Agent and the run id