When a slides deck needs images, delegate to the Assets app over A2A so generations are grounded in the user's brand library — never call an image-generation API directly from slides.
Scanned 9/3/2026
Install to Claude Code
npx -y skills add BuilderIO/agent-native --skill image-generation-via-a2a --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Image Generation Via A2a?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/builderio-image-generation-via-a2a)More formats (shields.io, HTML) on the badges page.
---
name: image-generation-via-a2a
description: When a slides deck needs images, delegate to the Assets app over A2A so generations are grounded in the user's brand library — never call an image-generation API directly from slides.
---
# Image generation via A2A
Slides never calls an image-generation API itself when Assets is reachable.
Assets owns brand libraries, presets, provenance, and the generation audit log,
so every improvement there has to reach decks for free.
Both entry points share one delegation helper,
`server/lib/assets-image-delegation.ts`:
1. **`generate-image-api`** — the action the agent and the editor's image panel
use. It delegates to Assets, then returns `source: "assets-a2a"` with the
Assets reply verbatim (plus `url` when a `previewUrl`/`downloadUrl` was
parseable).
2. **`pnpm action generate-image`** — the CLI script, for local runs that want
files on disk. Same delegation, prints the reply verbatim.
The helper resolves Assets through agent discovery (target `assets`, which also
matches `images`), so a workspace with both apps mounted needs no configuration.
`IMAGES_A2A_URL` / `IMAGES_A2A_KEY` remain as overrides for standalone deploys
that point at a remote Assets instance.
## Outcomes
The helper distinguishes four outcomes, because "Assets did not return an
image" has very different right answers:
| Outcome | Meaning | What happens |
| --- | --- | --- |
| `delegated` | Task completed | Use the reply's `previewUrl` |
| `rejected` | Task ended `failed`/`canceled`/`input-required` | Surface the reason; do NOT silently generate locally |
| `pending` | Caller-side timeout; the Assets run is still going and owns a `taskId` | Tell the user to check Assets; generating again would duplicate the run |
| `unavailable` | Assets could not be resolved or reached at all | Local fallback |
A `delegated` reply can still say the generation is a draft pending approval.
That means the user may draft in that brand kit but not save into it: the image
is real and usable in the deck, and only the copy kept in Assets is waiting on a
kit editor. Use the image, pass that along, and do not retry or fall back
locally.
Only `unavailable` falls through to the local Gemini/OpenAI providers under
`server/handlers/image-providers/`, so a slides-only deploy still works. That
output is **not** brand-grounded and the action says so: it returns
`source: "slides-fallback"` with a `fallbackReason`. Report that honestly
rather than presenting a fallback image as a library generation.
There is no direct Builder.io image-generation path in slides. If a user wants
Builder-managed generation, that belongs in Assets, behind the same delegation.
## Calling explicitly from the agent
Call the action with the destination so Assets can ground the generation:
```
generate-image-api { prompt, deckId, slideId, slideContent }
```
For direct insertion, add `insertIntoSlide: true`. This requires both IDs and
only returns `inserted: true` after Slides writes the transformed HTML through
`update-slide` and re-reads it through `get-deck` with `compact=false` to find
the image source.
Never say the image was added based on `url`, `previewUrl`, or a completed
Assets reply alone. For preview-only variations, leave `insertIntoSlide` false;
after choosing one, use `update-slide` and verify the persisted source with
`get-deck` with `compact=false` before claiming insertion.
Do **not** reach for the generic `call-agent` tool to ask Assets for an image.
It talks to the same app, so it looks equivalent, but it skips the slide
grounding, the completed-vs-failed task handling, and the ready-to-render
preview markdown this action returns — which is how image results end up in
chat as bare links instead of visible images.
Slides owns the semantic job of the image: its slide role, audience, crop, and
must-preserve content. Assets owns library and preset selection, style anchors,
generation settings, and provenance. Include the active design system's
image-style guidance in the prompt context, but do not ask Assets to invent a
competing brand direction.
Use the returned `previewUrl` for previews. Do not drop it into slide HTML
without the verified insertion workflow above.
## Showing the result in chat
Always show a generated image as an inline markdown image:
```md

```
The chat renders `![]()` as a real image but `[]()` as a bare link, so a
plain link (or a "View the photo" / "Open preview" link) leaves the user with
nothing to look at. Use the action's `url` field, or the `previewUrl` from the
Assets reply when the action could not parse one. This applies whether the
request came from the editor's image panel or from a plain chat message like
"generate a photo of a monstera".
The delegation message already instructs Assets to mark generations with
`source: "a2a"` and `callerAppId: "slides"` when it calls `generate-image-batch`
or `refine-image`. That keeps the Assets audit log useful for design review.
## Multi-slide image generation
Do not fire parallel `add-slide` calls into the same deck. Keep deck writes
sequential: add one slide, wait for the result, then add the next slide. If a
single slide needs several image variants, the image-generation action may
request multiple variants internally, but the deck write itself should remain a
single `add-slide` or `update-slide` call.
## Iteration
When the user gives feedback ("make slide 3's hero darker, more navy"), ask Assets to run `refine-image` with the previous `assetId` (extracted from the `previewUrl` returned earlier) plus the new feedback. Replace only the slide-3 `<img src="...">` with the new URL. Do **not** delete the prior asset — it stays in the library so the user can pick which version to keep.
## Cross-app reply parsing
The Assets reply comes back as plain text. Assets (per its `a2a-assets` skill)
includes `assetId`, `runId`, `previewUrl`, `downloadUrl`, and `embedPath` exactly
as returned by its actions. `extractAssetUrl` pulls the first `previewUrl` /
`downloadUrl` out of that reply; when it finds none, the action returns the reply
without a `url`.
If parsing the reply fails, surface "I couldn't parse the Assets agent's
response" to the user rather than guessing at URLs.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!