Skip to content
Moku
@moku-labs/ai pages

image

Task plugin for still images. Owns the image contract and the one-off facade app.image.*.

On this page

What it does

image is the task plugin for still images. It owns the contract every image provider implements: ImageFile, ImageRequest, ImageResult, ImageOutput and ImageHandler. It exposes a small typed facade, app.image.*, for one-off generation, cost estimates and provider discovery.

The plugin talks to no AI service. Provider plugins register a handler with registry under the task "image". The facade resolves the handler by name and calls it. The request is passed through unchanged.

Providers that serve this task today:

ProviderPlugin
codexcodex
falfal
arkark

How it works

The registry carries handlers as unknown. This plugin checks the shape at run time and casts once, at its own resolve site. A malformed registration fails with a clear error, never a crash.

Groups. A provider that makes several consistent images in one call returns all of them in images, each an ImageOutput { image, mimeType }, and the first one again as image and mimeType. Single-image code keeps reading image. The runner stores every entry of images as the item's ordered outputs. Export writes <label>.<ext>, <label>-2.<ext> and so on. A $ref to the item is the first image. app.image.generate() passes the result through unchanged.

The plugin is stateless. No lifecycle hooks, no events, no packs.

Config

pluginConfigs.image.

OptionTypeDefaultDescription
defaultProviderstring"codex"Provider the facade uses when a call names none.
import { createApp } from "@moku-labs/ai";

const app = createApp({
  pluginConfigs: { image: { defaultProvider: "fal" } }
});

API

Mounted at app.image, reachable with ctx.require(imagePlugin).

MethodSignatureDescription
generate(request: ImageRequest, opts?: { provider?: string; signal?: AbortSignal }) => Promise<ImageResult>One-off generation. Direct, not journaled.
estimate(request: ImageRequest, opts?: { provider?: string }) => { usd: number }Cost estimate without a call. Synchronous.
providers() => string[]Registered image providers, in registration order.

An unknown provider, or a registered value that fails the shape check, throws:

[ai] No image provider named "<name>" is registered.
  Available: <comma list or "none">.

ImageRequest

FieldTypeRequiredDescription
promptstringyesWhat to draw.
negativestringnoThings to avoid.
modelstringnoProvider-scoped model id.
aspectstringnoAspect ratio, e.g. "9:16". Providers default to "9:16".
refsImageFile[]noReference images: a character sheet, a location plate, an earlier keyframe.
paramsRecord<string, unknown>noProvider params, passed through.

ImageFile is { path: string; mimeType: string; hash: string }. In a build file the runner resolves { $ref: id } and { $file: path } into that shape.

ImageResult

FieldTypeDescription
imageUint8ArrayThe image bytes.
mimeTypestringMIME type of image.
imagesImageOutput[]?Every image of a group, in order. images[0] is image. Absent for a single image.
costUsdnumberActual cost, USD.
metaRecord<string, unknown>?Metadata only.

Example

The durable path is a build file. The second item takes the first as a reference:

keyframes.moku.yaml
version: 1
name: keyframes
items:
  - id: hero-sheet
    task: image
    provider: fal
    input:
      prompt: "character sheet, young courier, three views"
      aspect: "1:1"
  - id: s01-frame
    task: image
    provider: fal
    input:
      prompt: "the courier at a patisserie at night"
      aspect: "9:16"
      refs: [{ $ref: hero-sheet }]

The facade is the direct path. Nothing is journaled:

draw.ts
import { app } from "./app";

await app.start();

const sheet = { path: "art/hero-sheet.png", mimeType: "image/png", hash: "a".repeat(64) };

console.log(app.image.estimate({ prompt: "a patisserie at night" }, { provider: "fal" }));

const { image, images, costUsd } = await app.image.generate(
  { prompt: "the courier at a patisserie at night", aspect: "9:16", refs: [sheet] },
  { provider: "fal" }
);
await Bun.write("patisserie.png", image);
console.log(costUsd, images?.length ?? 1);

console.log(app.image.providers()); // ["codex", "fal", "ark"]
await app.stop();
  • generate is not journaled. No resume, no cost ledger entry. Build files go through runner.
  • refs must be real files. The facade does not resolve $ref or $file. Pass { path, mimeType, hash }.
  • A group is still one image. Read images when a provider returns several; image is always the first.

Events

Emits

None.

Listens to

None.

Depends on

  • registry: resolve("image", provider) and providers("image").
  • sprite: cuts a stored image into a transparent sprite.
  • runner: the durable path for the same handlers.
  • codex, fal, ark: the providers.