Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Generate images via OpenAI, Google, OpenRouter, DashScope, Jimeng, Seedream, and Replicate APIs with batch support.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/providers/minimax.ts
1import path from "node:path";2import { readFile } from "node:fs/promises";34import type { CliArgs } from "../types";56const DEFAULT_MODEL = "image-01";7const MAX_REFERENCE_IMAGE_BYTES = 10 * 1024 * 1024;8const SUPPORTED_ASPECT_RATIOS = new Set(["1:1", "16:9", "4:3", "3:2", "2:3", "3:4", "9:16", "21:9"]);910type MinimaxSubjectReference = {11type: "character";12image_file: string;13};1415type MinimaxRequestBody = {16model: string;17prompt: string;18response_format: "base64";19aspect_ratio?: string;20width?: number;21height?: number;22n?: number;23subject_reference?: MinimaxSubjectReference[];24};2526type MinimaxResponse = {27id?: string;28data?: {29image_urls?: string[];30image_base64?: string[];31};32base_resp?: {33status_code?: number;34status_msg?: string;35};36};3738export function getDefaultModel(): string {39return process.env.MINIMAX_IMAGE_MODEL || DEFAULT_MODEL;40}4142function getApiKey(): string | null {43return process.env.MINIMAX_API_KEY || null;44}4546export function buildMinimaxUrl(): string {47const base = (process.env.MINIMAX_BASE_URL || "https://api.minimax.io").replace(/\/+$/g, "");48return base.endsWith("/v1") ? `${base}/image_generation` : `${base}/v1/image_generation`;49}5051function getMimeType(filename: string): "image/jpeg" | "image/png" {52const ext = path.extname(filename).toLowerCase();53if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";54if (ext === ".png") return "image/png";55throw new Error(56`MiniMax subject_reference only supports JPG, JPEG, or PNG files: ${filename}`57);58}5960export function parsePixelSize(size: string): { width: number; height: number } | null {61const match = size.trim().match(/^(\d+)\s*[xX*]\s*(\d+)$/);62if (!match) return null;6364const width = parseInt(match[1]!, 10);65const height = parseInt(match[2]!, 10);66if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {67return null;68}6970return { width, height };71}7273function validatePixelSize(width: number, height: number): void {74if (width < 512 || width > 2048 || height < 512 || height > 2048) {75throw new Error("MiniMax custom size must keep width and height between 512 and 2048.");76}77if (width % 8 !== 0 || height % 8 !== 0) {78throw new Error("MiniMax custom size requires width and height divisible by 8.");79}80}8182export function validateArgs(model: string, args: CliArgs): void {83if (args.n > 9) {84throw new Error("MiniMax supports at most 9 images per request.");85}8687if (args.aspectRatio && !SUPPORTED_ASPECT_RATIOS.has(args.aspectRatio)) {88throw new Error(89`MiniMax aspect_ratio must be one of: ${Array.from(SUPPORTED_ASPECT_RATIOS).join(", ")}.`90);91}9293if (args.size && !args.aspectRatio) {94if (model !== "image-01") {95throw new Error("MiniMax custom --size is only supported with model image-01. Use --model image-01 or pass --ar instead.");96}97const parsed = parsePixelSize(args.size);98if (!parsed) {99throw new Error("MiniMax --size must be in WxH format, for example 1536x1024.");100}101validatePixelSize(parsed.width, parsed.height);102}103}104105export async function buildSubjectReference(106referenceImages: string[],107): Promise<MinimaxSubjectReference[] | undefined> {108if (referenceImages.length === 0) return undefined;109110const subjectReference: MinimaxSubjectReference[] = [];111for (const refPath of referenceImages) {112const bytes = await readFile(refPath);113if (bytes.length > MAX_REFERENCE_IMAGE_BYTES) {114throw new Error(`MiniMax subject_reference images must be smaller than 10MB: ${refPath}`);115}116117subjectReference.push({118type: "character",119image_file: `data:${getMimeType(refPath)};base64,${bytes.toString("base64")}`,120});121}122123return subjectReference;124}125126export async function buildRequestBody(127prompt: string,128model: string,129args: CliArgs,130): Promise<MinimaxRequestBody> {131validateArgs(model, args);132133const body: MinimaxRequestBody = {134model,135prompt,136response_format: "base64",137};138139if (args.aspectRatio) {140body.aspect_ratio = args.aspectRatio;141} else if (args.size) {142const parsed = parsePixelSize(args.size);143if (!parsed) {144throw new Error("MiniMax --size must be in WxH format, for example 1536x1024.");145}146body.width = parsed.width;147body.height = parsed.height;148}149150if (args.n > 1) {151body.n = args.n;152}153154const subjectReference = await buildSubjectReference(args.referenceImages);155if (subjectReference) {156body.subject_reference = subjectReference;157}158159return body;160}161162async function downloadImage(url: string): Promise<Uint8Array> {163const response = await fetch(url);164if (!response.ok) {165throw new Error(`Failed to download image from MiniMax: ${response.status}`);166}167return new Uint8Array(await response.arrayBuffer());168}169170export async function extractImageFromResponse(result: MinimaxResponse): Promise<Uint8Array> {171const baseResp = result.base_resp;172if (baseResp && baseResp.status_code !== undefined && baseResp.status_code !== 0) {173throw new Error(baseResp.status_msg || `MiniMax API returned status_code=${baseResp.status_code}`);174}175176const base64Image = result.data?.image_base64?.[0];177if (base64Image) {178return Uint8Array.from(Buffer.from(base64Image, "base64"));179}180181const url = result.data?.image_urls?.[0];182if (url) {183return downloadImage(url);184}185186throw new Error("No image data in MiniMax response");187}188189export function getDefaultOutputExtension(): ".jpg" {190return ".jpg";191}192193export async function generateImage(194prompt: string,195model: string,196args: CliArgs197): Promise<Uint8Array> {198const apiKey = getApiKey();199if (!apiKey) {200throw new Error("MINIMAX_API_KEY is required. Get one from https://platform.minimax.io/");201}202203const body = await buildRequestBody(prompt, model, args);204const response = await fetch(buildMinimaxUrl(), {205method: "POST",206headers: {207"Content-Type": "application/json",208Authorization: `Bearer ${apiKey}`,209},210body: JSON.stringify(body),211});212213if (!response.ok) {214const err = await response.text();215throw new Error(`MiniMax API error (${response.status}): ${err}`);216}217218const result = (await response.json()) as MinimaxResponse;219return extractImageFromResponse(result);220}221