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/azure.ts
1import path from "node:path";2import { readFile } from "node:fs/promises";3import type { CliArgs } from "../types";4import { getOpenAISize, extractImageFromResponse } from "./openai.ts";56type OpenAIImageResponse = { data: Array<{ url?: string; b64_json?: string }> };7type AzureEndpoint = {8resourceBaseURL: string;9deployment: string | null;10};1112const DEFAULT_AZURE_API_VERSION = "2025-04-01-preview";13const AZURE_EDIT_IMAGE_EXTENSIONS = new Set([".png", ".jpg", ".jpeg"]);1415export function parseAzureBaseURL(url: string): AzureEndpoint {16const parsed = new URL(url);17const trimmedPath = parsed.pathname.replace(/\/+$/, "");18const deploymentMatch = trimmedPath.match(/^(.*?)(?:\/openai)?\/deployments\/([^/]+)$/);1920if (deploymentMatch) {21parsed.pathname = `${deploymentMatch[1] || ""}/openai`;22return {23resourceBaseURL: parsed.toString().replace(/\/+$/, ""),24deployment: decodeURIComponent(deploymentMatch[2]!),25};26}2728parsed.pathname = trimmedPath.endsWith("/openai") ? trimmedPath : `${trimmedPath}/openai`;29return {30resourceBaseURL: parsed.toString().replace(/\/+$/, ""),31deployment: null,32};33}3435export function getDefaultModel(): string {36const explicitDeployment = process.env.AZURE_OPENAI_DEPLOYMENT?.trim();37if (explicitDeployment) return explicitDeployment;3839const baseURL = process.env.AZURE_OPENAI_BASE_URL;40if (baseURL) {41try {42const { deployment } = parseAzureBaseURL(baseURL);43if (deployment) return deployment;44} catch {45// Ignore invalid URLs here so the required-env check can raise the user-facing error later.46}47}4849return process.env.AZURE_OPENAI_IMAGE_MODEL || "gpt-image-1.5";50}5152function getEndpoint(): AzureEndpoint {53const url = process.env.AZURE_OPENAI_BASE_URL;54if (!url) {55throw new Error(56"AZURE_OPENAI_BASE_URL is required. Set it to your Azure resource or deployment endpoint, e.g.: https://your-resource.openai.azure.com or https://your-resource.openai.azure.com/openai/deployments/your-deployment"57);58}59return parseAzureBaseURL(url);60}6162function getApiKey(): string {63const key = process.env.AZURE_OPENAI_API_KEY;64if (!key) {65throw new Error(66"AZURE_OPENAI_API_KEY is required. Get it from Azure Portal → your OpenAI resource → Keys and Endpoint."67);68}69return key;70}7172function getApiVersion(): string {73return process.env.AZURE_API_VERSION || DEFAULT_AZURE_API_VERSION;74}7576function getDeployment(model: string): string {77const deployment = model.trim();78if (!deployment) {79throw new Error(80"Azure deployment name is required. Use --model <deployment>, AZURE_OPENAI_DEPLOYMENT, AZURE_OPENAI_IMAGE_MODEL, or embed the deployment in AZURE_OPENAI_BASE_URL."81);82}83return deployment;84}8586function buildURL(deployment: string, pathSuffix: string): string {87const { resourceBaseURL } = getEndpoint();88return `${resourceBaseURL}/deployments/${encodeURIComponent(deployment)}${pathSuffix}?api-version=${getApiVersion()}`;89}9091function authHeaders(): Record<string, string> {92return { "api-key": getApiKey() };93}9495function getAzureQuality(quality: CliArgs["quality"]): "medium" | "high" {96return quality === "2k" ? "high" : "medium";97}9899export function validateArgs(_model: string, args: CliArgs): void {100for (const refPath of args.referenceImages) {101const ext = path.extname(refPath).toLowerCase();102if (!AZURE_EDIT_IMAGE_EXTENSIONS.has(ext)) {103throw new Error(104`Azure OpenAI reference images must be PNG or JPG/JPEG. Unsupported file: ${refPath}`105);106}107}108}109110export async function generateImage(111prompt: string,112model: string,113args: CliArgs114): Promise<Uint8Array> {115const deployment = getDeployment(model);116const size = args.size || getOpenAISize(model, args.aspectRatio, args.quality);117118if (args.referenceImages.length > 0) {119return generateWithAzureEdits(prompt, deployment, size, args.referenceImages, args.quality);120}121122return generateWithAzureGenerations(prompt, deployment, size, args.quality);123}124125async function generateWithAzureGenerations(126prompt: string,127deployment: string,128size: string,129quality: CliArgs["quality"]130): Promise<Uint8Array> {131const body: Record<string, any> = {132prompt,133size,134n: 1,135quality: getAzureQuality(quality),136};137138const res = await fetch(buildURL(deployment, "/images/generations"), {139method: "POST",140headers: {141"Content-Type": "application/json",142...authHeaders(),143},144body: JSON.stringify(body),145});146147if (!res.ok) {148const err = await res.text();149throw new Error(`Azure OpenAI API error: ${err}`);150}151152const result = (await res.json()) as OpenAIImageResponse;153return extractImageFromResponse(result);154}155156async function generateWithAzureEdits(157prompt: string,158deployment: string,159size: string,160referenceImages: string[],161quality: CliArgs["quality"]162): Promise<Uint8Array> {163const form = new FormData();164form.append("prompt", prompt);165form.append("size", size);166form.append("n", "1");167form.append("quality", getAzureQuality(quality));168169for (const refPath of referenceImages) {170const bytes = await readFile(refPath);171const filename = path.basename(refPath);172const mimeType = path.extname(filename).toLowerCase() === ".png" ? "image/png" : "image/jpeg";173const blob = new Blob([bytes], { type: mimeType });174form.append("image[]", blob, filename);175}176177const res = await fetch(buildURL(deployment, "/images/edits"), {178method: "POST",179headers: {180...authHeaders(),181},182body: form,183});184185if (!res.ok) {186const err = await res.text();187throw new Error(`Azure OpenAI edits API error: ${err}`);188}189190const result = (await res.json()) as OpenAIImageResponse;191return extractImageFromResponse(result);192}193