Turn one design into every canvas size your users need: an agent re-composes the design for each size. Run the kit on your machine, deploy it, and replace its parts to build your own.
What It Does#
You pick a design and some target canvases. For each canvas, an agent adapts
the layout: it re-derives margins, type sizes, and spacing for the new shape,
keeps the palette, the type, and every element, and lets full-bleed backgrounds
cover the new canvas. It never stretches the design to fit. For each canvas you
get a PNG and a design file (.imgly, with its images inside) of CE.SDK,
IMG.LY’s design engine and editor.
- The design is one of four bundled samples (a coffee ad and a poster of a
fictional roastery, an eyewear sale post, and a podcast banner), or an
.imgly, PNG, JPEG, or WebP file you upload. - The canvases are CE.SDK’s page and crop presets, plus a custom size.
- Each result opens in CE.SDK’s editor, where you can change it by hand.
Start from this kit if your users want one design in many formats: social media posts, banners, and print sizes. It has the shape of a resize API in your product. Three things run on your machine:
- The page, which a person opens in the browser to pick the design and the sizes, and to see the results.
- The agent runner, your Node server that runs the resize job and its agent loop: it calls the model, hands it CoDesign’s design tools, and runs each tool the model asks for. The page sends it each job.
- The executors, Docker containers from IMG.LY’s image
imgly/codesign-executor. Each holds CE.SDK’s engine and runs the code the model writes to change a design, apart from your keys ( Security).
The model runs through IMG.LY’s AI gateway, a service that forwards model calls to the model provider and charges them to your IMG.LY account.
Pre-requisites#
- Docker with Docker Compose, which starts several containers from one file, for the executors – Download
- Node.js v22+ with npm, pnpm, or yarn – Download
- A desktop browser. The kit’s page is tested in desktop Chrome.
- A CE.SDK license for the editor the page opens on a result, where a
person changes a design by hand. The agent’s engine is the executor, which
takes the CoDesign key below. A CE.SDK trial key works on
localhost. - A CoDesign license key, for the model calls and the exports. Contact sales to get a CoDesign license key.
Get Started#
Step 1: Clone the Repository#
git clone https://github.com/imgly/starterkit-codesign-agentic-resize-ts-web.gitgit clone https://github.com/imgly/starterkit-codesign-agentic-resize-ts-web.gitnpx degit imgly/starterkit-codesign-agentic-resize-ts-web starterkit-codesign-agentic-resize-ts-webnpx degit imgly/starterkit-codesign-agentic-resize-ts-web starterkit-codesign-agentic-resize-ts-webWhat is inside, a Vite project (Vite is a web dev server and build tool):
starterkit-codesign-agentic-resize-ts-web/├── src/│ ├── runner/ # The job: per canvas, re-compose and export (resize.ts)│ ├── page/ # The page: sample designs, target canvases, results│ ├── hosting/cloud/│ │ ├── runner/ # The agent runner's server, and the page's client for it│ │ ├── compose.yaml # Three executors behind nginx; the agent runner only with --profile runner│ │ └── .env.template # The variables of the agent runner and Compose│ └── protocol.ts # The messages between the page and the agent runner├── shared/ # Code the CoDesign starter kits share: page session, agent loop, executor pool, editor modal├── samples/ # The sample designs the page offers to resize├── .env.template # The page's variables, and the agent runner's gateway key├── LICENSE.md # IMG.LY's license, the same as the CoDesign SDK's├── index.html├── package.json└── vite.config.ts # The page's dev serverRead src/runner/resize.ts after your first run; How It Works below
explains it.
Step 2: Install Dependencies#
cd starterkit-codesign-agentic-resize-ts-web
npm installcd starterkit-codesign-agentic-resize-ts-web
npm installcd starterkit-codesign-agentic-resize-ts-web
pnpm installcd starterkit-codesign-agentic-resize-ts-web
pnpm installcd starterkit-codesign-agentic-resize-ts-web
yarncd starterkit-codesign-agentic-resize-ts-web
yarnEvery npm run … command on this page works as pnpm run … or yarn … too.
Step 3: Set Your Keys#
Put both templates into one .env:
cat .env.template src/hosting/cloud/.env.template > .envThen fill in the variables that are empty in the templates; leave
TOKEN_ISSUERS empty on your machine:
VITE_CESDK_LICENSE=<your CE.SDK license or trial key>IMGLY_GATEWAY_API_KEY=<your CoDesign license key>IMGLY_CESDK_LICENSE=<your CoDesign license key>EXECUTOR_TOKEN=<a secret of your own; openssl rand -hex 32 makes one>RUNNER_DEV_USER=meYour CoDesign license key goes in twice, as IMGLY_GATEWAY_API_KEY and
IMGLY_CESDK_LICENSE, and neither reaches the page. VITE_CESDK_LICENSE is a
different key, the CE.SDK license of the editor. Vite sends every variable
whose name starts with VITE_ to the page, where anyone can read it, so keep
only that license and the agent runner’s address there.
| Variable | Read by | What it is |
|---|---|---|
VITE_CESDK_LICENSE |
the page | The CE.SDK license of the editor. |
VITE_RUNNER_URL |
the page | The agent runner’s address. Default http://localhost:8793. |
VITE_MODEL |
the page | Optional: the model the page starts with. |
IMGLY_GATEWAY_API_KEY |
the agent runner | Your CoDesign license key. The agent runner sends it with every model call to the AI gateway. |
EXECUTOR_URLS |
the agent runner | The executors, as the agent runner reaches them through nginx, the web server in the Compose file. Default the three on http://localhost:8791. |
RUNNER_DEV_USER |
the agent runner | On your machine only: any id. Every request to the agent runner is that user, in place of a sign-in. |
ALLOWED_ORIGINS |
the agent runner | The page origins that may call it, comma-separated. Default http://localhost:5191. |
IMGLY_CESDK_LICENSE |
Compose | Your CoDesign license key again, under the executor image’s name for it. The executors start with it and charge every export to it. It is not the CE.SDK license above. |
EXECUTOR_TOKEN |
Compose | The token nginx adds to every request to the executors, so only the agent runner reaches them. |
src/hosting/cloud/.env.template also describes the variables of a deployed
agent runner (Deploy It).
Step 4: Start the Executors#
docker compose -f src/hosting/cloud/compose.yaml --env-file .env up -dcurl http://localhost:8791/executor-1/health# {"result":{"sessions":0,...,"accepting":true,...}}The first start downloads the images. curl asks the first executor, through
nginx, whether it is ready, and prints a line like the comment above. If it
does not answer, run
docker compose -f src/hosting/cloud/compose.yaml logs executor-1: the
executor stops at start when the license key is missing or cannot be
activated, or when it cannot reach api.img.ly.
Step 5: Start the Agent Runner#
In a terminal of its own, from the kit’s folder:
npx vite-node src/hosting/cloud/runner/serve.tsnpx vite-node src/hosting/cloud/runner/serve.tspnpm exec vite-node src/hosting/cloud/runner/serve.tspnpm exec vite-node src/hosting/cloud/runner/serve.tsyarn vite-node src/hosting/cloud/runner/serve.tsyarn vite-node src/hosting/cloud/runner/serve.tsIt prints Runner: http://127.0.0.1:8793 and keeps running.
curl http://127.0.0.1:8793/health answers {}.
Step 6: Start the Page#
In a second terminal, from the kit’s folder:
npm run devnpm run devpnpm run devpnpm run devyarn devyarn devVite prints the page’s address, http://localhost:5191/. Open it in desktop
Chrome. The page opens on a coffee ad with two sizes picked. Press Resize.
Each size is a design job, which takes around 3 minutes, depending on the
model and how hard the brief is. Each canvas appears as it lands, with its
picture and three buttons: Download (the .imgly file), PNG, and Edit, which
opens it in the editor.
The Resize button stays off until the design to resize is shown: a bundled sample’s picture, or the executor’s preview of a file you uploaded.
To stop, press Ctrl+C in both terminals and run
docker compose -f src/hosting/cloud/compose.yaml down.
What It Costs#
Model calls are charged to the IMG.LY account behind IMGLY_GATEWAY_API_KEY,
and each resized canvas’s PNG to the key the executors run with; the .imgly
files and the previews are free
( What It Costs).
How It Works#
The Job#
The job is one file, src/runner/resize.ts. For each canvas, it:
- prepares the tools for a new design in the run’s conversation, your id for one job, which pins every design of a run to one executor, so the run’s canvases share one engine and no other run’s code;
- loads the original into it;
- hands the tools to the agent, with a prompt that carries the design’s version, which the model passes back with each edit so an edit on an outdated design is refused;
- exports the
.imglyand the PNG as bytes (Uint8Array); - calls
release(), which ends this design’s engine session at once and frees its memory, instead of after a few minutes; the executor stays with the run for the next canvas.
The handbook, the guide the SDK serves the model during a session, covers
resizing, and codesign.instructions tells the model to read it first.
executors says where the engine runs: IMG.LY’s image in your containers
( remoteExecutors).
googleFonts() gives the model IMG.LY’s Google Fonts catalog. gateway: false
leaves out image generation, because a resize makes nothing new; the model
calls are the agent loop’s own and still run through the gateway.
The code below is a reference: it type-checks against the SDK. runAgent is
yours to write: it runs your agent loop with the tools, the instructions, and
the prompt, and returns when the model is done.
// One conversation per run: every canvas of it opens on one executor.const conversation = crypto.randomUUID();const results = [];for (const format of formats) { const codesign = prepareCoDesign({ conversation, executors, plugins: [googleFonts()], gateway: false // no image generation: resizing makes nothing new }); try { // A new design, with the original loaded into it. const { version } = await codesign.load(source); const notes = await runAgent({ tools: codesign.tools, instructions: codesign.instructions, prompt: `Resize the loaded design to ${format.width}×${format.height} px (${format.label}). It is at version ${version}.` }); // The .imgly is free; the PNG is a charged export. const archive = await codesign.export('imgly'); const png = await codesign.export('png'); results.push({ ...format, archive, png, notes }); } finally { // Ends this design's engine session; the executor stays the run's. await codesign.release(); }}return results;The whole file
import { googleFonts, prepareCoDesign, type CoDesignTool, type Executors} from '@imgly/codesign-sdk';
export interface TargetFormat { label: string; width: number; height: number;}
// Yours to write: one agent turn over these tools, in any agent framework.async function runAgent(opts: { tools: readonly CoDesignTool[]; instructions: string; prompt: string;}): Promise<string> { throw new Error('yours to write');}
export async function resizeDesign( executors: Executors, source: Uint8Array | URL, formats: readonly TargetFormat[]) { // One conversation per run: every canvas of it opens on one executor. const conversation = crypto.randomUUID(); const results = []; for (const format of formats) { const codesign = prepareCoDesign({ conversation, executors, plugins: [googleFonts()], gateway: false // no image generation: resizing makes nothing new }); try { // A new design, with the original loaded into it. const { version } = await codesign.load(source); const notes = await runAgent({ tools: codesign.tools, instructions: codesign.instructions, prompt: `Resize the loaded design to ${format.width}×${format.height} px (${format.label}). It is at version ${version}.` }); // The .imgly is free; the PNG is a charged export. const archive = await codesign.export('imgly'); const png = await codesign.export('png'); results.push({ ...format, archive, png, notes }); } finally { // Ends this design's engine session; the executor stays the run's. await codesign.release(); } } return results;}The Agent Runner#
- One request per job. The page sends a resize, or a preview of a design
you uploaded, as
POST /runsto the agent runner, and reads the answer as a stream of JSON lines: each step of the agent, each file, each canvas as it lands, and the result last. A file’s bytes travel as base64. - Nothing is kept. The results come back to the page as bytes and are
shown as
data:andblob:URLs, so a reload starts over; an edit in the editor is kept in the page too. To keep results, have the agent runner write them to a store of yours and send its URLs instead. - A closed page does not stop a run. The agent runner finishes the run, and its PNGs are charged, but nobody receives them.
The Executors#
Each executor serves one conversation at a time, so that no other
conversation gets an engine that ran this one’s code. A resize run is one
conversation, and each preview of a design you uploaded is another, so the
Compose file starts three executors. A fourth conversation waits up to 60
seconds, then fails with an error that says so. An executor exits about 3½
minutes after its conversation’s last call (3 idle minutes, then 30 seconds),
and Compose starts it again empty. For more, add services to compose.yaml
and their URLs to EXECUTOR_URLS.
Deploy It#
Deployed, each piece runs on your infrastructure:
-
Your sign-in. The kit has none of its own; plug yours in at
startSession()inshared/src/page/session.ts. ItsgetTokenreturns your sign-in’s short-lived JSON Web Token (JWT), a fresh one on each call, and the page sends it with each request to the agent runner. The token only says who calls: the agent runner’s key pays the model, not the person. -
The agent runner, at the address you build into
VITE_RUNNER_URL.npm run build:runnerbundles it intodist/runner/serve.mjs; run that withnodefromdist/runner/, where the bundled sample design is, or add--profile runnerto thedocker composecommand to start it in a Node container beside the executors. It holdsIMGLY_GATEWAY_API_KEYand checks each token againstTOKEN_ISSUERS, the issuers your sign-in writes into a token’sissclaim, comma-separated:TOKEN_ISSUERS=https://auth.example.comIt fetches each issuer’s public keys from
<issuer>/.well-known/jwks.json(orJWKS_URL) and accepts tokens signed with RS256, the RSA signature most hosted sign-ins use. It answers only the page origins inALLOWED_ORIGINS.RUNNER_HOSTandRUNNER_PORTset where it listens. LeaveRUNNER_DEV_USERunset: the agent runner refuses it unless it listens on a loopback address, and refuses it besideTOKEN_ISSUERS. -
The executors, IMG.LY’s image, one container per conversation, with their network closed to all but the hosts they need ( Host the Executor).
EXECUTOR_URLSlists them, and only the agent runner may reach them. -
The page:
npm run build:sitebuilds it intodist/, with theVITE_variables set at build time, for any static host. ItsVITE_CESDK_LICENSEis a CE.SDK license for your page’s domain.
What to Replace#
| Piece | In the kit | In your product |
|---|---|---|
| The agent runner | the kit’s own, in a Node server, on the AI gateway with your license key | your server, in any agent framework, on the same gateway |
| The tools | codesign.tools |
the same |
| The executor | IMG.LY’s image, three containers in Compose | IMG.LY’s image in your containers, one per conversation |
| The storage | none: results come back to the page as bytes | your bucket |
| Sign-in | none of its own: startSession() and RUNNER_DEV_USER |
your sign-in, at startSession() and in TOKEN_ISSUERS |
To run the same job as an HTTP API for production, with stored images, guardrails, and a secured endpoint, follow Generate Designs.
Troubleshooting#
The Executors Do Not Start#
required variable IMGLY_CESDK_LICENSE is missing a value: set IMGLY_CESDK_LICENSE in .env, or the same forEXECUTOR_TOKEN. Compose names the variable it is missing. Pass--env-file .envfrom the kit’s folder./healthdoes not answer. Rundocker compose -f src/hosting/cloud/compose.yaml logs executor-1.
The Agent Runner Does Not Start#
The runner needs …: set them in .envnames the variables it is missing:EXECUTOR_URLS,IMGLY_GATEWAY_API_KEY, orALLOWED_ORIGINS.The runner needs TOKEN_ISSUERS or RUNNER_DEV_USER to know who calls it.Set one of them, not both.
The Page Shows an Error#
No CE.SDK license: setVITE_CESDK_LICENSEin.envand restart the page’s dev server; Vite reads.envonly at start.- The page names
VITE_RUNNER_URL: set it and restart the page’s dev server. the runner at … answered 401: the agent runner did not accept the page’s token. On your machine, setRUNNER_DEV_USER; deployed, check that your sign-in’s issuer is inTOKEN_ISSUERS. A request that fails without an answer means the agent runner is not running, or the page’s origin is missing fromALLOWED_ORIGINS: the agent runner answers no other.
The Resize Button Stays Off#
- The design to resize is not shown yet. For a file you upload, the button turns on once the executor has rendered its preview, because a resize of a design the engine cannot open would fail for every canvas. A file it cannot open is named under the design with the reason; a preview that never appears means the page cannot reach the agent runner, or the agent runner its executors (see The Page Shows an Error and A Run Waits, Then Fails).
A Run Waits, Then Fails#
- All three executors serve another conversation. The error says so. One is free again about 3½ minutes after its conversation’s last call; or add executors (see The Executors).
Exports Are Refused#
- The executors charge exports to
IMGLY_CESDK_LICENSE. With a key that does not include CoDesign, every PNG is refused with a message that says why. For what each message means, see Exports Are Refused in Troubleshooting.
Next Steps#
- Generate Designs — run the same job as an HTTP API on your server
- Host the Executor — run the executor image in production
- Agentic Design Manager — start from a design platform with an assistant instead