Media connector
@clankie/media-connector is the versioned, provider-neutral boundary for local media generation.
Schema version 2 (ADR 0085) covers
images, image editing, and video; requests are a discriminated union on kind, so video's duration
and resolution never appear on an image request.
Image adapters cover OpenAI gpt-image-2, Google gemini-3.1-flash-image, and Grok
grok-imagine-image-quality. Video adapters implement VideoGenerationAdapter:
GrokVideoAdapter covers grok-imagine-video-1.5 (prompt, or a first frame), and
GoogleVideoAdapter covers Veo 3.1 (veo-3.1-generate-preview, -fast-, -lite-)
through the Gemini API's predictLongRunning, with a first frame, a last frame and up
to three reference images. A provider such as Kling joins by implementing the same three
steps. Callers provide the
credential and may inject a transport. The package never reads process.env, imports a provider SDK,
publishes an artifact, or grants itself authority.
The call site is ConfiguredMediaGenerator in the clankie service, which owns credential
resolution and where artifacts land. Nothing else constructs these adapters.
Authority and security
media.generate.image and media.generate.video are separate actions, and both create only a
caller-selected local artifact.
Posting a generated artifact is a separate concern with its own boundary: his own reply carrying a picture he just made is decided by the clankie service and the presence schema, never here. See ADR 0085.
Provider responses are untrusted. Adapters validate their response shape, decode the image, write it
with mode 0600 under a MEDIA_ARTIFACT_BYTES_MAX ceiling, and return a validated absolute artifact
path plus SHA-256 and bounded provider metadata. Credentials are constructor inputs and are used only
for the provider request. A rendered video is downloaded from a provider-hosted URL: the host is
checked against the provider's own domain, a redirect is followed only to another of its hosts and
without the credential, and both the declared and actual lengths are bounded.
Product pixel art remains Aseprite-MCP-only in the private clankie-app repository. The connector
refuses .aseprite outputs and paths containing pixel-art, sprite, or atlas asset directories.
const adapter = new OpenAiImageAdapter({ apiKey, fetch: auditedFetch });
const result = await adapter.generate({
schemaVersion: 2,
kind: "image",
prompt: "A friendly robot tending a garden",
size: "1536x1024",
provider: "openai",
model: "gpt-image-2",
outputPath: "/private/artifacts/garden.png",
});
Video is a job rather than a response, so the three steps stay separate primitives and the caller owns how long it is willing to wait:
const video = new GrokVideoAdapter({ apiKey });
let job = await video.start(request); // { requestId, status: "pending" }
while (job.status === "pending") job = await video.poll(job.requestId);
const rendered = await video.retrieve(job, request);
A video request may carry firstFrame, lastFrame and referenceImages as PNG, JPEG
or WebP data URIs; a last frame needs a first. Veo's predictLongRunning takes them as
bytesBase64Encoded plus mimeType, not the inlineData parts generateContent uses. The same image as both frames makes a
loop. Veo takes 4, 6 or 8 seconds (only 8 with a last frame, reference images or 1080p) at 720p or
1080p, 16:9 or 9:16. Its download URL may redirect once to a Google content host;
the redirect is followed without the API key.
Persona self-depiction can supply up to eight bounded referenceImages data URIs.
OpenAI sends them as multipart image[]; Google sends inline data parts. The
current Grok adapter accepts one and explicitly rejects larger sets. The service
chooses only the owner-configured appearance/ references via personaReference, never arbitrary paths
from a model. See persona images.
