Search Docs
Loading...
Skip to content

Agentic Resize

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.

12 mins
estimated time
Download
GitHub

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#

Terminal
git clone https://github.com/imgly/starterkit-codesign-agentic-resize-ts-web.git
git clone https://github.com/imgly/starterkit-codesign-agentic-resize-ts-web.git

What 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 server

Read src/runner/resize.ts after your first run; How It Works below explains it.

Step 2: Install Dependencies#

Terminal
cd starterkit-codesign-agentic-resize-ts-web
npm install
cd starterkit-codesign-agentic-resize-ts-web
npm install

Every npm run … command on this page works as pnpm run … or yarn … too.

Step 3: Set Your Keys#

Put both templates into one .env:

Terminal window
cat .env.template src/hosting/cloud/.env.template > .env

Then fill in the variables that are empty in the templates; leave TOKEN_ISSUERS empty on your machine:

.env
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=me

Your 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#

Terminal window
docker compose -f src/hosting/cloud/compose.yaml --env-file .env up -d
curl 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:

Terminal
npx vite-node src/hosting/cloud/runner/serve.ts
npx vite-node src/hosting/cloud/runner/serve.ts

It 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:

Terminal
npm run dev
npm run dev

Vite 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:

  1. 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;
  2. loads the original into it;
  3. 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;
  4. exports the .imgly and the PNG as bytes (Uint8Array);
  5. 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 /runs to 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: and blob: 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:

  1. Your sign-in. The kit has none of its own; plug yours in at startSession() in shared/src/page/session.ts. Its getToken returns 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.

  2. The agent runner, at the address you build into VITE_RUNNER_URL. npm run build:runner bundles it into dist/runner/serve.mjs; run that with node from dist/runner/, where the bundled sample design is, or add --profile runner to the docker compose command to start it in a Node container beside the executors. It holds IMGLY_GATEWAY_API_KEY and checks each token against TOKEN_ISSUERS, the issuers your sign-in writes into a token’s iss claim, comma-separated:

    TOKEN_ISSUERS=https://auth.example.com

    It fetches each issuer’s public keys from <issuer>/.well-known/jwks.json (or JWKS_URL) and accepts tokens signed with RS256, the RSA signature most hosted sign-ins use. It answers only the page origins in ALLOWED_ORIGINS. RUNNER_HOST and RUNNER_PORT set where it listens. Leave RUNNER_DEV_USER unset: the agent runner refuses it unless it listens on a loopback address, and refuses it beside TOKEN_ISSUERS.

  3. 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_URLS lists them, and only the agent runner may reach them.

  4. The page: npm run build:site builds it into dist/, with the VITE_ variables set at build time, for any static host. Its VITE_CESDK_LICENSE is 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 for EXECUTOR_TOKEN. Compose names the variable it is missing. Pass --env-file .env from the kit’s folder.
  • /health does not answer. Run docker compose -f src/hosting/cloud/compose.yaml logs executor-1.

The Agent Runner Does Not Start#

  • The runner needs …: set them in .env names the variables it is missing: EXECUTOR_URLS, IMGLY_GATEWAY_API_KEY, or ALLOWED_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: set VITE_CESDK_LICENSE in .env and restart the page’s dev server; Vite reads .env only 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, set RUNNER_DEV_USER; deployed, check that your sign-in’s issuer is in TOKEN_ISSUERS. A request that fails without an answer means the agent runner is not running, or the page’s origin is missing from ALLOWED_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#