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/agnes.ts
1import { readFile } from "node:fs/promises";2import path from "node:path";3import type { CliArgs } from "../types";45const DEFAULT_MODEL = "agnes-image-2.1-flash";6const DEFAULT_BASE_URL = "https://apihub.agnes-ai.com/v1";7const DEFAULT_SIZE = "1024x1024";89type AgnesResponse = {10created?: number;11data: Array<{ url?: string; b64_json?: string }>;12};1314export function getDefaultModel(): string {15return process.env.AGNES_IMAGE_MODEL || DEFAULT_MODEL;16}1718function getApiKey(): string {19const key = process.env.AGNES_API_KEY;20if (!key) {21throw new Error("AGNES_API_KEY is required. Get one from https://apihub.agnes-ai.com.");22}23return key;24}2526function getBaseUrl(): string {27return (process.env.AGNES_BASE_URL || DEFAULT_BASE_URL).replace(/\/+$/, "");28}2930export function parseAspectRatio(ar: string): { width: number; height: number } | null {31const match = ar.match(/^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?)$/);32if (!match) return null;33const w = parseFloat(match[1]!);34const h = parseFloat(match[2]!);35if (w <= 0 || h <= 0) return null;36return { width: w, height: h };37}3839export function snapDim(n: number): number {40return Math.max(32, Math.round(n / 32) * 32);41}4243export function resolveSize(args: Pick<CliArgs, "size" | "aspectRatio">): string {44if (args.size) return args.size;4546if (args.aspectRatio) {47const parsed = parseAspectRatio(args.aspectRatio);48if (parsed) {49if (parsed.width === 1 && parsed.height === 1) return "1024x1024";50const maxEdge = 2048;51const scale = Math.max(1, Math.floor(maxEdge / Math.max(parsed.width, parsed.height)));52const width = parsed.width * scale;53const height = parsed.height * scale;54return `${snapDim(width)}x${snapDim(height)}`;55}56}5758return DEFAULT_SIZE;59}6061function isRemoteUrl(refPath: string): boolean {62return /^https?:\/\//i.test(refPath);63}6465export async function resolveReferenceImages(66referenceImages: string[]67): Promise<string[]> {68const result: string[] = [];69for (const refPath of referenceImages) {70if (isRemoteUrl(refPath)) {71result.push(refPath);72continue;73}74const bytes = await readFile(refPath);75const ext = path.extname(refPath).toLowerCase();76let mime = "image/png";77if (ext === ".jpg" || ext === ".jpeg") mime = "image/jpeg";78else if (ext === ".webp") mime = "image/webp";79else if (ext === ".gif") mime = "image/gif";80const b64 = Buffer.from(bytes).toString("base64");81result.push(`data:${mime};base64,${b64}`);82}83return result;84}8586export function validateArgs(_model: string, args: CliArgs): void {87if (args.n > 1) {88throw new Error("Agnes image generation currently returns a single image per request. Set --n 1 or omit --n.");89}90}9192export function getDefaultOutputExtension(_model: string, args: CliArgs): string {93return args.responseFormat === "url" ? ".txt" : ".png";94}9596export function buildRequestBody(97prompt: string,98model: string,99args: Pick<CliArgs, "size" | "aspectRatio" | "referenceImages">100): Record<string, unknown> {101const body: Record<string, unknown> = {102model,103prompt,104size: resolveSize(args),105};106107if (args.referenceImages.length > 0) {108body.image = args.referenceImages;109}110111body.extra_body = { response_format: "url" };112113return body;114}115116export async function extractImageFromResponse(result: AgnesResponse): Promise<Uint8Array> {117const img = result.data[0];118119if (img?.b64_json) {120return Uint8Array.from(Buffer.from(img.b64_json, "base64"));121}122123if (img?.url) {124const imgRes = await fetch(img.url);125if (!imgRes.ok) throw new Error(`Failed to download image from Agnes: ${imgRes.status}`);126return new Uint8Array(await imgRes.arrayBuffer());127}128129throw new Error("No image in Agnes response");130}131132export async function generateImage(133prompt: string,134model: string,135args: CliArgs136): Promise<Uint8Array> {137const apiKey = getApiKey();138const baseUrl = getBaseUrl();139140const referenceImages = await resolveReferenceImages(args.referenceImages);141142const body = buildRequestBody(prompt, model, { ...args, referenceImages });143144const controller = new AbortController();145const timeout = setTimeout(() => controller.abort(), 120_000);146147try {148const res = await fetch(`${baseUrl}/images/generations`, {149method: "POST",150headers: {151"Content-Type": "application/json",152Authorization: `Bearer ${apiKey}`,153},154body: JSON.stringify(body),155signal: controller.signal,156});157158if (!res.ok) {159const err = await res.text();160throw new Error(`Agnes API error (${res.status}): ${err}`);161}162163const result = (await res.json()) as AgnesResponse;164165if (args.responseFormat === "url") {166const url = result.data[0]?.url;167if (!url) throw new Error("No URL in Agnes response");168return new Uint8Array(Buffer.from(url, "utf-8"));169}170171return extractImageFromResponse(result);172} finally {173clearTimeout(timeout);174}175}176