diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a9e0d8..6542d42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ English | [中文](./CHANGELOG.zh.md) +## 1.110.0 - 2026-04-21 + +### Features +- `baoyu-imagine`: add `gpt-image-2` support for OpenAI image generation and edits, make it the default OpenAI model, and document the official size/quality mapping, custom-size constraints, and Azure deployment guidance + ## 1.109.0 - 2026-04-21 ### Features diff --git a/CHANGELOG.zh.md b/CHANGELOG.zh.md index 7c2d9ef..4e6230f 100644 --- a/CHANGELOG.zh.md +++ b/CHANGELOG.zh.md @@ -2,6 +2,11 @@ [English](./CHANGELOG.md) | 中文 +## 1.110.0 - 2026-04-21 + +### 新功能 +- `baoyu-imagine`:新增 `gpt-image-2` 支持,用于 OpenAI 图像生成与编辑;将其设为默认 OpenAI 模型,并补齐官方尺寸/质量映射、自定义尺寸约束与 Azure 部署说明 + ## 1.109.0 - 2026-04-21 ### 新功能 diff --git a/README.md b/README.md index 18863a0..435affd 100644 --- a/README.md +++ b/README.md @@ -714,7 +714,7 @@ AI-powered generation backends. #### baoyu-imagine -AI SDK-based image generation using OpenAI, Azure OpenAI, Google, OpenRouter, DashScope (Aliyun Tongyi Wanxiang), MiniMax, Jimeng (即梦), Seedream (豆包), and Replicate APIs. Supports text-to-image, reference images, aspect ratios, custom sizes, batch generation, and quality presets. +AI SDK-based image generation using OpenAI GPT Image 2, Azure OpenAI, Google, OpenRouter, DashScope (Aliyun Tongyi Wanxiang), MiniMax, Jimeng (即梦), Seedream (豆包), and Replicate APIs. Supports text-to-image, reference images, aspect ratios, custom sizes, batch generation, and quality presets. ```bash # Basic generation (auto-detect provider) @@ -727,10 +727,10 @@ AI SDK-based image generation using OpenAI, Azure OpenAI, Google, OpenRouter, Da /baoyu-imagine --prompt "A banner" --image banner.png --quality 2k # Specific provider -/baoyu-imagine --prompt "A cat" --image cat.png --provider openai +/baoyu-imagine --prompt "A cat" --image cat.png --provider openai --model gpt-image-2 # Azure OpenAI (model = deployment name) -/baoyu-imagine --prompt "A cat" --image cat.png --provider azure --model gpt-image-1.5 +/baoyu-imagine --prompt "A cat" --image cat.png --provider azure --model gpt-image-2 # OpenRouter /baoyu-imagine --prompt "A cat" --image cat.png --provider openrouter @@ -786,7 +786,7 @@ AI SDK-based image generation using OpenAI, Azure OpenAI, Google, OpenRouter, Da | `--provider` | `google`, `openai`, `azure`, `openrouter`, `dashscope`, `zai`, `minimax`, `jimeng`, `seedream`, or `replicate` | | `--model`, `-m` | Model ID or deployment name. Azure uses deployment name; OpenRouter uses full model IDs; Z.AI uses `glm-image`; MiniMax uses `image-01` / `image-01-live` | | `--ar` | Aspect ratio (e.g., `16:9`, `1:1`, `4:3`) | -| `--size` | Size (e.g., `1024x1024`) | +| `--size` | Size (e.g., `1024x1024`; `gpt-image-2` accepts valid custom sizes up to 3840px max edge) | | `--quality` | `normal` or `2k` (default: `2k`) | | `--imageSize` | `1K`, `2K`, or `4K` for Google/OpenRouter | | `--imageApiDialect` | `openai-native` or `ratio-metadata` for OpenAI-compatible gateways | @@ -810,9 +810,9 @@ AI SDK-based image generation using OpenAI, Azure OpenAI, Google, OpenRouter, Da | `JIMENG_ACCESS_KEY_ID` | Jimeng Volcengine access key | - | | `JIMENG_SECRET_ACCESS_KEY` | Jimeng Volcengine secret key | - | | `ARK_API_KEY` | Seedream Volcengine ARK API key | - | -| `OPENAI_IMAGE_MODEL` | OpenAI model | `gpt-image-1.5` | +| `OPENAI_IMAGE_MODEL` | OpenAI model | `gpt-image-2` | | `AZURE_OPENAI_DEPLOYMENT` | Azure default deployment name | - | -| `AZURE_OPENAI_IMAGE_MODEL` | Backward-compatible Azure deployment/model alias | `gpt-image-1.5` | +| `AZURE_OPENAI_IMAGE_MODEL` | Backward-compatible Azure deployment/model alias | `gpt-image-2` | | `OPENROUTER_IMAGE_MODEL` | OpenRouter model | `google/gemini-3.1-flash-image-preview` | | `GOOGLE_IMAGE_MODEL` | Google model | `gemini-3-pro-image-preview` | | `DASHSCOPE_IMAGE_MODEL` | DashScope model | `qwen-image-2.0-pro` | @@ -1132,14 +1132,14 @@ mkdir -p ~/.baoyu-skills cat > ~/.baoyu-skills/.env << 'EOF' # OpenAI OPENAI_API_KEY=sk-xxx -OPENAI_IMAGE_MODEL=gpt-image-1.5 +OPENAI_IMAGE_MODEL=gpt-image-2 # OPENAI_BASE_URL=https://api.openai.com/v1 # OPENAI_IMAGE_USE_CHAT=false # Azure OpenAI AZURE_OPENAI_API_KEY=xxx AZURE_OPENAI_BASE_URL=https://your-resource.openai.azure.com -AZURE_OPENAI_DEPLOYMENT=gpt-image-1.5 +AZURE_OPENAI_DEPLOYMENT=gpt-image-2 # AZURE_API_VERSION=2025-04-01-preview # OpenRouter diff --git a/README.zh.md b/README.zh.md index 95b7201..d1c84af 100644 --- a/README.zh.md +++ b/README.zh.md @@ -714,7 +714,7 @@ AI 驱动的生成后端。 #### baoyu-imagine -基于 AI SDK 的图像生成,支持 OpenAI、Azure OpenAI、Google、OpenRouter、DashScope(阿里通义万相)、MiniMax、即梦(Jimeng)、豆包(Seedream)和 Replicate API。支持文生图、参考图、宽高比、自定义尺寸、批量生成和质量预设。 +基于 AI SDK 的图像生成,支持 OpenAI GPT Image 2、Azure OpenAI、Google、OpenRouter、DashScope(阿里通义万相)、MiniMax、即梦(Jimeng)、豆包(Seedream)和 Replicate API。支持文生图、参考图、宽高比、自定义尺寸、批量生成和质量预设。 ```bash # 基础生成(自动检测服务商) @@ -727,10 +727,10 @@ AI 驱动的生成后端。 /baoyu-imagine --prompt "横幅图" --image banner.png --quality 2k # 指定服务商 -/baoyu-imagine --prompt "一只猫" --image cat.png --provider openai +/baoyu-imagine --prompt "一只猫" --image cat.png --provider openai --model gpt-image-2 # Azure OpenAI(model 为部署名称) -/baoyu-imagine --prompt "一只猫" --image cat.png --provider azure --model gpt-image-1.5 +/baoyu-imagine --prompt "一只猫" --image cat.png --provider azure --model gpt-image-2 # OpenRouter /baoyu-imagine --prompt "一只猫" --image cat.png --provider openrouter @@ -786,7 +786,7 @@ AI 驱动的生成后端。 | `--provider` | `google`、`openai`、`azure`、`openrouter`、`dashscope`、`zai`、`minimax`、`jimeng`、`seedream` 或 `replicate` | | `--model`, `-m` | 模型 ID 或部署名。Azure 使用部署名;OpenRouter 使用完整模型 ID;Z.AI 使用 `glm-image`;MiniMax 使用 `image-01` / `image-01-live` | | `--ar` | 宽高比(如 `16:9`、`1:1`、`4:3`) | -| `--size` | 尺寸(如 `1024x1024`) | +| `--size` | 尺寸(如 `1024x1024`;`gpt-image-2` 支持最长边不超过 3840px 的有效自定义尺寸) | | `--quality` | `normal` 或 `2k`(默认:`2k`) | | `--imageSize` | Google/OpenRouter 使用的 `1K`、`2K`、`4K` | | `--imageApiDialect` | OpenAI 兼容网关的图像 API 方言(`openai-native` 或 `ratio-metadata`) | @@ -810,9 +810,9 @@ AI 驱动的生成后端。 | `JIMENG_ACCESS_KEY_ID` | 即梦火山引擎 Access Key | - | | `JIMENG_SECRET_ACCESS_KEY` | 即梦火山引擎 Secret Key | - | | `ARK_API_KEY` | 豆包火山引擎 ARK API 密钥 | - | -| `OPENAI_IMAGE_MODEL` | OpenAI 模型 | `gpt-image-1.5` | +| `OPENAI_IMAGE_MODEL` | OpenAI 模型 | `gpt-image-2` | | `AZURE_OPENAI_DEPLOYMENT` | Azure 默认部署名 | - | -| `AZURE_OPENAI_IMAGE_MODEL` | 兼容旧配置的 Azure 部署/模型别名 | `gpt-image-1.5` | +| `AZURE_OPENAI_IMAGE_MODEL` | 兼容旧配置的 Azure 部署/模型别名 | `gpt-image-2` | | `OPENROUTER_IMAGE_MODEL` | OpenRouter 模型 | `google/gemini-3.1-flash-image-preview` | | `GOOGLE_IMAGE_MODEL` | Google 模型 | `gemini-3-pro-image-preview` | | `DASHSCOPE_IMAGE_MODEL` | DashScope 模型 | `qwen-image-2.0-pro` | @@ -1132,14 +1132,14 @@ mkdir -p ~/.baoyu-skills cat > ~/.baoyu-skills/.env << 'EOF' # OpenAI OPENAI_API_KEY=sk-xxx -OPENAI_IMAGE_MODEL=gpt-image-1.5 +OPENAI_IMAGE_MODEL=gpt-image-2 # OPENAI_BASE_URL=https://api.openai.com/v1 # OPENAI_IMAGE_USE_CHAT=false # Azure OpenAI AZURE_OPENAI_API_KEY=xxx AZURE_OPENAI_BASE_URL=https://your-resource.openai.azure.com -AZURE_OPENAI_DEPLOYMENT=gpt-image-1.5 +AZURE_OPENAI_DEPLOYMENT=gpt-image-2 # AZURE_API_VERSION=2025-04-01-preview # OpenRouter diff --git a/skills/baoyu-imagine/SKILL.md b/skills/baoyu-imagine/SKILL.md index 9e300ea..06c01e8 100644 --- a/skills/baoyu-imagine/SKILL.md +++ b/skills/baoyu-imagine/SKILL.md @@ -1,7 +1,7 @@ --- name: baoyu-imagine -description: AI image generation with OpenAI, Azure OpenAI, Google, OpenRouter, DashScope, Z.AI GLM-Image, MiniMax, Jimeng, Seedream and Replicate APIs. Supports text-to-image, reference images, aspect ratios, and batch generation from saved prompt files. Sequential by default; use batch parallel generation when the user already has multiple prompts or wants stable multi-image throughput. Use when user asks to generate, create, or draw images. -version: 1.57.0 +description: AI image generation with OpenAI GPT Image 2, Azure OpenAI, Google, OpenRouter, DashScope, Z.AI GLM-Image, MiniMax, Jimeng, Seedream and Replicate APIs. Supports text-to-image, reference images, aspect ratios, and batch generation from saved prompt files. Sequential by default; use batch parallel generation when the user already has multiple prompts or wants stable multi-image throughput. Use when user asks to generate, create, or draw images. +version: 1.58.0 metadata: openclaw: homepage: https://github.com/JimLiu/baoyu-skills#baoyu-imagine @@ -13,7 +13,7 @@ metadata: # Image Generation (AI SDK) -Official API-based image generation. Supports OpenAI, Azure OpenAI, Google, OpenRouter, DashScope (阿里通义万象), Z.AI GLM-Image, MiniMax, Jimeng (即梦), Seedream (豆包) and Replicate. +Official API-based image generation. Supports OpenAI GPT Image 2, Azure OpenAI, Google, OpenRouter, DashScope (阿里通义万象), Z.AI GLM-Image, MiniMax, Jimeng (即梦), Seedream (豆包) and Replicate. ## User Input Tools @@ -68,6 +68,9 @@ ${BUN_X} {baseDir}/scripts/main.ts --prompt "Make blue" --image out.png --ref so # Specific provider ${BUN_X} {baseDir}/scripts/main.ts --prompt "A cat" --image out.png --provider dashscope --model qwen-image-2.0-pro +# OpenAI GPT Image 2 +${BUN_X} {baseDir}/scripts/main.ts --prompt "A cat" --image out.png --provider openai --model gpt-image-2 + # Batch mode ${BUN_X} {baseDir}/scripts/main.ts --batchfile batch.json --jobs 4 ``` @@ -84,7 +87,7 @@ ${BUN_X} {baseDir}/scripts/main.ts --batchfile batch.json --jobs 4 | `--provider google\|openai\|azure\|openrouter\|dashscope\|zai\|minimax\|jimeng\|seedream\|replicate` | Force provider (default: auto-detect) | | `--model `, `-m` | Model ID — see provider references for defaults and allowed values | | `--ar ` | Aspect ratio (`16:9`, `1:1`, `4:3`, …) | -| `--size ` | Explicit size (e.g., `1024x1024`) | +| `--size ` | Explicit size (e.g., `1024x1024`; for `gpt-image-2`, width/height must be multiples of 16, max edge 3840px, ratio no wider than 3:1) | | `--quality normal\|2k` | Quality preset (default: `2k`) | | `--imageSize 1K\|2K\|4K` | Image size for Google/OpenRouter (default: from quality) | | `--imageApiDialect openai-native\|ratio-metadata` | OpenAI-compatible endpoint dialect — use `ratio-metadata` for gateways that expect aspect-ratio `size` plus `metadata.resolution` | @@ -128,7 +131,9 @@ Priority (highest → lowest) applies to every provider: 3. Env var `_IMAGE_MODEL` 4. Built-in default -For Azure, `--model` / `default_model.azure` is the Azure deployment name. `AZURE_OPENAI_DEPLOYMENT` is the preferred env var; `AZURE_OPENAI_IMAGE_MODEL` is kept as a backward-compatible alias. +For OpenAI, the built-in default is `gpt-image-2`. `gpt-image-1.5`, `gpt-image-1`, and GPT Image snapshots remain selectable with `--model` or `OPENAI_IMAGE_MODEL`. + +For Azure, `--model` / `default_model.azure` is the Azure deployment name. `AZURE_OPENAI_DEPLOYMENT` is the preferred env var; `AZURE_OPENAI_IMAGE_MODEL` is kept as a backward-compatible alias. If your Azure deployment is named after the underlying model, use `gpt-image-2`; otherwise use the exact custom deployment name. EXTEND.md overrides env vars: if EXTEND.md sets `default_model.google: "gemini-3-pro-image-preview"` and the env var sets `GOOGLE_IMAGE_MODEL=gemini-3.1-flash-image-preview`, EXTEND.md wins. @@ -169,17 +174,19 @@ Each provider has its own quirks (model families, size rules, ref support, limit | Preset | Google imageSize | OpenAI size | OpenRouter size | Replicate resolution | Use case | |--------|------------------|-------------|-----------------|----------------------|----------| -| `normal` | 1K | 1024px | 1K | 1K | Quick previews | -| `2k` (default) | 2K | 2048px | 2K | 2K | Covers, illustrations, infographics | +| `normal` | 1K | 1024px target | 1K | 1K | Quick previews | +| `2k` (default) | 2K | 2048px target | 2K | 2K | Covers, illustrations, infographics | Google/OpenRouter `imageSize` can be overridden with `--imageSize 1K|2K|4K`. +For OpenAI native `gpt-image-2`, `normal` maps to `quality=medium` and a low-latency valid size near the requested aspect ratio; `2k` maps to `quality=high` and 2048px-class sizes such as `2048x2048`, `2048x1152`, or `1152x2048`. Use explicit `--size` for valid custom or 4K outputs, e.g. `3840x2160`. + ## Aspect Ratios Supported: `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `2.35:1`. - Google multimodal: `imageConfig.aspectRatio` -- OpenAI: closest supported size +- OpenAI: `gpt-image-2` uses the closest valid custom size for the requested ratio; older GPT Image and DALL·E models use their closest supported fixed size - OpenRouter: `imageGenerationOptions.aspect_ratio`; if only `--size ` is given, the ratio is inferred - Replicate: behavior is model-specific — `google/nano-banana*` uses `aspect_ratio`, `bytedance/seedream-*` uses documented Replicate ratios, Wan 2.7 maps `--ar` to a concrete `size` - MiniMax: official `aspect_ratio` values; if `--size ` is given without `--ar`, sends `width`/`height` for `image-01` diff --git a/skills/baoyu-imagine/references/config/first-time-setup.md b/skills/baoyu-imagine/references/config/first-time-setup.md index 0d73811..d92222a 100644 --- a/skills/baoyu-imagine/references/config/first-time-setup.md +++ b/skills/baoyu-imagine/references/config/first-time-setup.md @@ -46,7 +46,7 @@ options: - label: "Google (Recommended)" description: "Gemini multimodal - high quality, reference images, flexible sizes" - label: "OpenAI" - description: "GPT Image - consistent quality, reliable output" + description: "GPT Image 2 - latest OpenAI image model, reference-image workflows" - label: "Azure OpenAI" description: "Azure-hosted GPT Image deployments with resource-specific routing" - label: "OpenRouter" @@ -101,10 +101,12 @@ Only show if user selected Azure OpenAI. header: "Azure Deploy" question: "Default Azure image deployment name?" options: - - label: "gpt-image-1.5 (Recommended)" - description: "Best default if your Azure deployment uses the same name" - - label: "gpt-image-1" + - label: "gpt-image-2 (Recommended)" + description: "Use if your Azure deployment uses the GPT Image 2 model name" + - label: "gpt-image-1.5" description: "Previous GPT Image deployment name" + - label: "gpt-image-1" + description: "Earlier GPT Image deployment name" ``` ### Question 2d: Default MiniMax Model @@ -214,10 +216,12 @@ options: header: "OpenAI Model" question: "Choose a default OpenAI image generation model?" options: - - label: "gpt-image-1.5 (Recommended)" - description: "Latest GPT Image model, high quality" + - label: "gpt-image-2 (Recommended)" + description: "Latest GPT Image model, flexible sizes up to 4K, high-fidelity image inputs" + - label: "gpt-image-1.5" + description: "Previous GPT Image model" - label: "gpt-image-1" - description: "Previous generation GPT Image model" + description: "Earlier GPT Image model" ``` ### Azure Deployment Selection @@ -226,8 +230,10 @@ options: header: "Azure Deploy" question: "Choose a default Azure image deployment name?" options: - - label: "gpt-image-1.5 (Recommended)" - description: "Use when your Azure deployment name matches the GPT-image-1.5 model" + - label: "gpt-image-2 (Recommended)" + description: "Use when your Azure deployment name matches the GPT Image 2 model" + - label: "gpt-image-1.5" + description: "Use when your Azure deployment name matches the GPT Image 1.5 model" - label: "gpt-image-1" description: "Use when your Azure deployment name matches GPT-image-1" ``` diff --git a/skills/baoyu-imagine/references/config/preferences-schema.md b/skills/baoyu-imagine/references/config/preferences-schema.md index 617ac76..8f86b60 100644 --- a/skills/baoyu-imagine/references/config/preferences-schema.md +++ b/skills/baoyu-imagine/references/config/preferences-schema.md @@ -23,8 +23,8 @@ default_image_api_dialect: null # openai-native|ratio-metadata|null (OpenAI-com default_model: google: null # e.g., "gemini-3-pro-image-preview", "gemini-3.1-flash-image-preview" - openai: null # e.g., "gpt-image-1.5", "gpt-image-1" - azure: null # Azure deployment name, e.g., "gpt-image-1.5" or "image-prod" + openai: null # e.g., "gpt-image-2", "gpt-image-1.5", "gpt-image-1" + azure: null # Azure deployment name, e.g., "gpt-image-2" or "image-prod" openrouter: null # e.g., "google/gemini-3.1-flash-image-preview" dashscope: null # e.g., "qwen-image-2.0-pro" zai: null # e.g., "glm-image" @@ -106,8 +106,8 @@ default_image_size: 2K default_image_api_dialect: null default_model: google: "gemini-3-pro-image-preview" - openai: "gpt-image-1.5" - azure: "gpt-image-1.5" + openai: "gpt-image-2" + azure: "gpt-image-2" openrouter: "google/gemini-3.1-flash-image-preview" dashscope: "qwen-image-2.0-pro" zai: "glm-image" diff --git a/skills/baoyu-imagine/references/usage-examples.md b/skills/baoyu-imagine/references/usage-examples.md index 06a96fe..0bfb29d 100644 --- a/skills/baoyu-imagine/references/usage-examples.md +++ b/skills/baoyu-imagine/references/usage-examples.md @@ -25,10 +25,13 @@ ${BUN_X} {baseDir}/scripts/main.ts --prompt "Make blue" --image out.png --ref so ```bash # OpenAI -${BUN_X} {baseDir}/scripts/main.ts --prompt "A cat" --image out.png --provider openai +${BUN_X} {baseDir}/scripts/main.ts --prompt "A cat" --image out.png --provider openai --model gpt-image-2 # Azure OpenAI (model = deployment name) -${BUN_X} {baseDir}/scripts/main.ts --prompt "A cat" --image out.png --provider azure --model gpt-image-1.5 +${BUN_X} {baseDir}/scripts/main.ts --prompt "A cat" --image out.png --provider azure --model gpt-image-2 + +# OpenAI GPT Image 2 custom 4K size +${BUN_X} {baseDir}/scripts/main.ts --prompt "A cinematic landscape" --image out.png --provider openai --model gpt-image-2 --size 3840x2160 # Google with explicit model ${BUN_X} {baseDir}/scripts/main.ts --prompt "Make blue" --image out.png --provider google --model gemini-3-pro-image-preview --ref source.png diff --git a/skills/baoyu-imagine/scripts/main.test.ts b/skills/baoyu-imagine/scripts/main.test.ts index 6a8bbf4..8213bb9 100644 --- a/skills/baoyu-imagine/scripts/main.test.ts +++ b/skills/baoyu-imagine/scripts/main.test.ts @@ -133,7 +133,7 @@ default_image_size: 2K default_image_api_dialect: ratio-metadata default_model: google: gemini-3-pro-image-preview - openai: gpt-image-1.5 + openai: gpt-image-2 zai: glm-image azure: image-prod minimax: image-01 @@ -165,7 +165,7 @@ batch: assert.equal(config.default_image_size, "2K"); assert.equal(config.default_image_api_dialect, "ratio-metadata"); assert.equal(config.default_model?.google, "gemini-3-pro-image-preview"); - assert.equal(config.default_model?.openai, "gpt-image-1.5"); + assert.equal(config.default_model?.openai, "gpt-image-2"); assert.equal(config.default_model?.zai, "glm-image"); assert.equal(config.default_model?.azure, "image-prod"); assert.equal(config.default_model?.minimax, "image-01"); diff --git a/skills/baoyu-imagine/scripts/main.ts b/skills/baoyu-imagine/scripts/main.ts index a101bb0..ea3bf4c 100644 --- a/skills/baoyu-imagine/scripts/main.ts +++ b/skills/baoyu-imagine/scripts/main.ts @@ -124,7 +124,7 @@ Environment variables: JIMENG_ACCESS_KEY_ID Jimeng Access Key ID JIMENG_SECRET_ACCESS_KEY Jimeng Secret Access Key ARK_API_KEY Seedream/Ark API key - OPENAI_IMAGE_MODEL Default OpenAI model (gpt-image-1.5) + OPENAI_IMAGE_MODEL Default OpenAI model (gpt-image-2) OPENROUTER_IMAGE_MODEL Default OpenRouter model (google/gemini-3.1-flash-image-preview) GOOGLE_IMAGE_MODEL Default Google model (gemini-3-pro-image-preview) DASHSCOPE_IMAGE_MODEL Default DashScope model (qwen-image-2.0-pro) @@ -151,7 +151,7 @@ Environment variables: AZURE_OPENAI_BASE_URL Azure OpenAI resource or deployment endpoint AZURE_OPENAI_DEPLOYMENT Default Azure deployment name AZURE_API_VERSION Azure API version (default: 2025-04-01-preview) - AZURE_OPENAI_IMAGE_MODEL Backward-compatible Azure deployment/model alias (defaults to gpt-image-1.5) + AZURE_OPENAI_IMAGE_MODEL Backward-compatible Azure deployment/model alias (defaults to gpt-image-2) SEEDREAM_BASE_URL Custom Seedream endpoint BAOYU_IMAGE_GEN_MAX_WORKERS Override batch worker cap BAOYU_IMAGE_GEN__CONCURRENCY Override provider concurrency diff --git a/skills/baoyu-imagine/scripts/providers/azure.ts b/skills/baoyu-imagine/scripts/providers/azure.ts index ab39917..72b004d 100644 --- a/skills/baoyu-imagine/scripts/providers/azure.ts +++ b/skills/baoyu-imagine/scripts/providers/azure.ts @@ -46,7 +46,7 @@ export function getDefaultModel(): string { } } - return process.env.AZURE_OPENAI_IMAGE_MODEL || "gpt-image-1.5"; + return process.env.AZURE_OPENAI_IMAGE_MODEL || "gpt-image-2"; } function getEndpoint(): AzureEndpoint { diff --git a/skills/baoyu-imagine/scripts/providers/openai.test.ts b/skills/baoyu-imagine/scripts/providers/openai.test.ts index b6b44f8..9d09bbc 100644 --- a/skills/baoyu-imagine/scripts/providers/openai.test.ts +++ b/skills/baoyu-imagine/scripts/providers/openai.test.ts @@ -1,9 +1,11 @@ import assert from "node:assert/strict"; import test from "node:test"; +import type { CliArgs } from "../types.ts"; import { buildOpenAIGenerationsBody, extractImageFromResponse, + getDefaultModel, getOpenAIAspectRatio, getOpenAIImageApiDialect, getOpenAIResolution, @@ -13,9 +15,33 @@ import { inferAspectRatioFromSize, inferResolutionFromSize, parseAspectRatio, + validateArgs, } from "./openai.ts"; +function makeArgs(overrides: Partial = {}): CliArgs { + return { + prompt: null, + promptFiles: [], + imagePath: null, + provider: null, + model: null, + aspectRatio: null, + size: null, + quality: "2k", + imageSize: null, + imageApiDialect: null, + referenceImages: [], + n: 1, + batchFile: null, + jobs: null, + json: false, + help: false, + ...overrides, + }; +} + test("OpenAI aspect-ratio parsing and size selection match model families", () => { + assert.equal(getDefaultModel(), "gpt-image-2"); assert.deepEqual(parseAspectRatio("16:9"), { width: 16, height: 9 }); assert.equal(parseAspectRatio("wide"), null); assert.equal(parseAspectRatio("0:1"), null); @@ -25,6 +51,10 @@ test("OpenAI aspect-ratio parsing and size selection match model families", () = assert.equal(getOpenAISize("dall-e-2", "16:9", "2k"), "1024x1024"); assert.equal(getOpenAISize("gpt-image-1.5", "16:9", "2k"), "1536x1024"); assert.equal(getOpenAISize("gpt-image-1.5", "4:3", "2k"), "1024x1024"); + assert.equal(getOpenAISize("gpt-image-2", "16:9", "2k"), "2048x1152"); + assert.equal(getOpenAISize("gpt-image-2", "9:16", "2k"), "1152x2048"); + assert.equal(getOpenAISize("gpt-image-2", "4:3", "2k"), "2048x1536"); + assert.equal(getOpenAISize("gpt-image-2", "2.35:1", "normal"), "1248x528"); assert.equal(inferAspectRatioFromSize("1536x1024"), "3:2"); assert.equal(inferResolutionFromSize("1536x1024"), "2K"); assert.equal(getOpenAIAspectRatio({ aspectRatio: null, size: "2048x1152" }), "16:9"); @@ -37,7 +67,7 @@ test("OpenAI aspect-ratio parsing and size selection match model families", () = test("OpenAI generations body switches between native and ratio-metadata dialects", () => { assert.deepEqual( - buildOpenAIGenerationsBody("Draw a skyline", "gpt-image-1.5", { + buildOpenAIGenerationsBody("Draw a skyline", "gpt-image-2", { aspectRatio: "16:9", size: null, quality: "2k", @@ -45,9 +75,10 @@ test("OpenAI generations body switches between native and ratio-metadata dialect imageApiDialect: null, }), { - model: "gpt-image-1.5", + model: "gpt-image-2", prompt: "Draw a skyline", - size: "1536x1024", + size: "2048x1152", + quality: "high", }, ); @@ -90,6 +121,28 @@ test("OpenAI generations body switches between native and ratio-metadata dialect ); }); +test("OpenAI validates gpt-image-2 custom size constraints", () => { + assert.doesNotThrow(() => + validateArgs("gpt-image-2", makeArgs({ size: "3840x2160" })), + ); + assert.doesNotThrow(() => + validateArgs("gpt-image-2-2026-04-21", makeArgs({ aspectRatio: "2.35:1" })), + ); + + assert.throws( + () => validateArgs("gpt-image-2", makeArgs({ size: "1024x576" })), + /total pixels/, + ); + assert.throws( + () => validateArgs("gpt-image-2", makeArgs({ size: "1025x1024" })), + /multiples of 16px/, + ); + assert.throws( + () => validateArgs("gpt-image-2", makeArgs({ aspectRatio: "4:1" })), + /must not exceed 3:1/, + ); +}); + test("OpenAI mime-type detection covers supported reference image extensions", () => { assert.equal(getMimeType("frame.png"), "image/png"); assert.equal(getMimeType("frame.jpg"), "image/jpeg"); diff --git a/skills/baoyu-imagine/scripts/providers/openai.ts b/skills/baoyu-imagine/scripts/providers/openai.ts index 2777682..fbd5cd3 100644 --- a/skills/baoyu-imagine/scripts/providers/openai.ts +++ b/skills/baoyu-imagine/scripts/providers/openai.ts @@ -3,7 +3,7 @@ import { readFile } from "node:fs/promises"; import type { CliArgs, OpenAIImageApiDialect } from "../types"; export function getDefaultModel(): string { - return process.env.OPENAI_IMAGE_MODEL || "gpt-image-1.5"; + return process.env.OPENAI_IMAGE_MODEL || "gpt-image-2"; } type OpenAIImageResponse = { data: Array<{ url?: string; b64_json?: string }> }; @@ -25,6 +25,55 @@ type SizeMapping = { type OpenAIGenerationsBody = Record; +function isGptImageModel(model: string): boolean { + return model.includes("gpt-image"); +} + +function isGptImage2Model(model: string): boolean { + return model.includes("gpt-image-2"); +} + +function roundToMultiple(value: number, multiple: number): number { + return Math.max(multiple, Math.round(value / multiple) * multiple); +} + +function buildGptImage2SizeFromAspectRatio( + ar: string | null, + quality: CliArgs["quality"], +): string { + const parsed = ar ? parseAspectRatio(ar) : null; + const ratio = parsed ? parsed.width / parsed.height : 1; + + if (!parsed || Math.abs(ratio - 1) < 0.1) { + const edge = quality === "2k" ? 2048 : 1024; + return `${edge}x${edge}`; + } + + const targetLongEdge = quality === "2k" ? 2048 : 1024; + let width: number; + let height: number; + + if (ratio > 1) { + width = targetLongEdge; + height = roundToMultiple(width / ratio, 16); + } else { + height = targetLongEdge; + width = roundToMultiple(height * ratio, 16); + } + + while (width * height < 655_360) { + if (ratio > 1) { + width += 16; + height = roundToMultiple(width / ratio, 16); + } else { + height += 16; + width = roundToMultiple(height * ratio, 16); + } + } + + return `${width}x${height}`; +} + export function getOpenAISize( model: string, ar: string | null, @@ -37,6 +86,10 @@ export function getOpenAISize( return "1024x1024"; } + if (isGptImage2Model(model)) { + return buildGptImage2SizeFromAspectRatio(ar, quality); + } + const sizes: SizeMapping = isDalle3 ? { square: "1024x1024", @@ -127,6 +180,18 @@ export function getOpenAIResolution( return args.quality === "normal" ? "1K" : "2K"; } +function getOpenAIQuality(model: string, quality: CliArgs["quality"]): "standard" | "hd" | "medium" | "high" | null { + if (model.includes("dall-e-3")) { + return quality === "2k" ? "hd" : "standard"; + } + + if (isGptImageModel(model)) { + return quality === "2k" ? "high" : "medium"; + } + + return null; +} + export function getOrientationFromAspectRatio(ar: string): "landscape" | "portrait" | null { const parsed = parseAspectRatio(ar); if (!parsed) return null; @@ -163,13 +228,53 @@ export function buildOpenAIGenerationsBody( size: args.size || getOpenAISize(model, args.aspectRatio, args.quality), }; - if (model.includes("dall-e-3")) { - body.quality = args.quality === "2k" ? "hd" : "standard"; + const quality = getOpenAIQuality(model, args.quality); + if (quality) { + body.quality = quality; } return body; } +export function validateArgs(model: string, args: CliArgs): void { + if (!isGptImage2Model(model)) return; + + if (args.aspectRatio && !args.size) { + const parsed = parseAspectRatio(args.aspectRatio); + if (!parsed) { + throw new Error(`Invalid gpt-image-2 aspect ratio: ${args.aspectRatio}`); + } + const ratio = parsed.width / parsed.height; + if (Math.max(ratio, 1 / ratio) > 3) { + throw new Error("gpt-image-2 aspect ratio must not exceed 3:1."); + } + } + + if (!args.size) return; + + const parsedSize = parsePixelSize(args.size); + if (!parsedSize) { + throw new Error(`Invalid gpt-image-2 --size: ${args.size}. Expected x.`); + } + + const { width, height } = parsedSize; + const totalPixels = width * height; + const ratio = Math.max(width, height) / Math.min(width, height); + + if (Math.max(width, height) > 3840) { + throw new Error("gpt-image-2 --size maximum edge length must be 3840px or less."); + } + if (width % 16 !== 0 || height % 16 !== 0) { + throw new Error("gpt-image-2 --size width and height must both be multiples of 16px."); + } + if (ratio > 3) { + throw new Error("gpt-image-2 --size long edge to short edge ratio must not exceed 3:1."); + } + if (totalPixels < 655_360 || totalPixels > 8_294_400) { + throw new Error("gpt-image-2 --size total pixels must be between 655,360 and 8,294,400."); + } +} + export async function generateImage( prompt: string, model: string, @@ -198,7 +303,7 @@ export async function generateImage( } if (model.includes("dall-e-2") || model.includes("dall-e-3")) { throw new Error( - "Reference images with OpenAI in this skill require GPT Image models. Use --model gpt-image-1.5 (or another gpt-image model)." + "Reference images with OpenAI in this skill require GPT Image models. Use --model gpt-image-2 (or another gpt-image model)." ); } const size = args.size || getOpenAISize(model, args.aspectRatio, args.quality); @@ -283,8 +388,9 @@ async function generateWithOpenAIEdits( form.append("prompt", prompt); form.append("size", size); - if (model.includes("gpt-image")) { - form.append("quality", quality === "2k" ? "high" : "medium"); + const openAIQuality = getOpenAIQuality(model, quality); + if (openAIQuality && openAIQuality !== "standard" && openAIQuality !== "hd") { + form.append("quality", openAIQuality); } for (const refPath of referenceImages) {