openai-image-gen: validate and normalize --output-format (#36648)

* openai-image-gen: validate and normalize output format

* Skills/openai-image-gen: cover output-format edge cases

* Changelog: note openai image output format validation

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
Mark Zhang
2026-03-07 00:14:30 +08:00
committed by GitHub
parent f392b81e95
commit 81f22ae109
3 changed files with 110 additions and 29 deletions

View File

@@ -199,6 +199,7 @@ Docs: https://docs.openclaw.ai
- Telegram/Discord media upload caps: make outbound uploads honor channel `mediaMaxMb` config, raise Telegram's default media cap to 100MB, and remove MIME fallback limits that kept some Telegram uploads at 16MB. Thanks @vincentkoc.
- Skills/nano-banana-pro resolution override: respect explicit `--resolution` values during image editing and only auto-detect output size from input images when the flag is omitted. (#36880) Thanks @shuofengzhang and @vincentkoc.
- Skills/openai-image-gen CLI validation: validate `--background` and `--style` inputs early, normalize supported values, and warn when those flags are ignored for incompatible models. (#36762) Thanks @shuofengzhang and @vincentkoc.
- Skills/openai-image-gen output formats: validate `--output-format` values early, normalize aliases like `jpg -> jpeg`, and warn when the flag is ignored for incompatible models. (#36648) Thanks @shuofengzhang and @vincentkoc.
- WhatsApp media upload caps: make outbound media sends and auto-replies honor `channels.whatsapp.mediaMaxMb` with per-account overrides so inbound and outbound limits use the same channel config. Thanks @vincentkoc.
## 2026.3.2

View File

@@ -9,6 +9,7 @@ import re
import sys
import urllib.error
import urllib.request
from collections.abc import Callable
from html import escape as html_escape
from pathlib import Path
@@ -75,46 +76,82 @@ def get_model_defaults(model: str) -> tuple[str, str]:
return ("1024x1024", "high")
def normalize_background(model: str, background: str) -> str:
"""Validate --background for GPT image models."""
value = background.strip().lower()
def normalize_optional_flag(
*,
model: str,
raw_value: str,
flag_name: str,
supported: Callable[[str], bool],
allowed: set[str],
allowed_text: str,
unsupported_message: str,
aliases: dict[str, str] | None = None,
) -> str:
"""Normalize a string flag, warn when unsupported, and reject invalid values."""
value = raw_value.strip().lower()
if not value:
return ""
if not model.startswith("gpt-image"):
print(
f"Warning: --background is only supported for gpt-image models; ignoring for '{model}'.",
file=sys.stderr,
)
if not supported(model):
print(unsupported_message.format(model=model), file=sys.stderr)
return ""
allowed = {"transparent", "opaque", "auto"}
if aliases:
value = aliases.get(value, value)
if value not in allowed:
raise ValueError(
f"Invalid --background '{background}'. Allowed values: transparent, opaque, auto."
f"Invalid --{flag_name} '{raw_value}'. Allowed values: {allowed_text}."
)
return value
def normalize_background(model: str, background: str) -> str:
"""Validate --background for GPT image models."""
return normalize_optional_flag(
model=model,
raw_value=background,
flag_name="background",
supported=lambda candidate: candidate.startswith("gpt-image"),
allowed={"transparent", "opaque", "auto"},
allowed_text="transparent, opaque, auto",
unsupported_message=(
"Warning: --background is only supported for gpt-image models; "
"ignoring for '{model}'."
),
)
def normalize_style(model: str, style: str) -> str:
"""Validate --style for dall-e-3."""
value = style.strip().lower()
if not value:
return ""
return normalize_optional_flag(
model=model,
raw_value=style,
flag_name="style",
supported=lambda candidate: candidate == "dall-e-3",
allowed={"vivid", "natural"},
allowed_text="vivid, natural",
unsupported_message=(
"Warning: --style is only supported for dall-e-3; ignoring for '{model}'."
),
)
if model != "dall-e-3":
print(
f"Warning: --style is only supported for dall-e-3; ignoring for '{model}'.",
file=sys.stderr,
)
return ""
allowed = {"vivid", "natural"}
if value not in allowed:
raise ValueError(
f"Invalid --style '{style}'. Allowed values for dall-e-3: vivid, natural."
)
return value
def normalize_output_format(model: str, output_format: str) -> str:
"""Normalize output format for GPT image models and validate allowed values."""
return normalize_optional_flag(
model=model,
raw_value=output_format,
flag_name="output-format",
supported=lambda candidate: candidate.startswith("gpt-image"),
allowed={"png", "jpeg", "webp"},
allowed_text="png, jpeg, webp",
unsupported_message=(
"Warning: --output-format is only supported for gpt-image models; "
"ignoring for '{model}'."
),
aliases={"jpg": "jpeg"},
)
def request_images(
@@ -239,13 +276,14 @@ def main() -> int:
try:
normalized_background = normalize_background(args.model, args.background)
normalized_style = normalize_style(args.model, args.style)
normalized_output_format = normalize_output_format(args.model, args.output_format)
except ValueError as e:
print(str(e), file=sys.stderr)
return 2
# Determine file extension based on output format
if args.model.startswith("gpt-image") and args.output_format:
file_ext = args.output_format
if args.model.startswith("gpt-image") and normalized_output_format:
file_ext = normalized_output_format
else:
file_ext = "png"
@@ -259,7 +297,7 @@ def main() -> int:
size,
quality,
normalized_background,
args.output_format,
normalized_output_format,
normalized_style,
)
data = res.get("data", [{}])[0]

View File

@@ -4,7 +4,12 @@ import tempfile
from pathlib import Path
import pytest
from gen import normalize_background, normalize_style, write_gallery
from gen import (
normalize_background,
normalize_output_format,
normalize_style,
write_gallery,
)
def test_normalize_background_allows_empty_for_non_gpt_models():
@@ -55,6 +60,43 @@ def test_normalize_style_rejects_invalid_values():
normalize_style("dall-e-3", "cinematic")
def test_normalize_output_format_allows_empty_for_non_gpt_models():
assert normalize_output_format("dall-e-3", "jpeg") == ""
def test_normalize_output_format_allows_empty_for_gpt_models():
assert normalize_output_format("gpt-image-1", "") == ""
assert normalize_output_format("gpt-image-1", " ") == ""
def test_normalize_output_format_warns_when_model_does_not_support_flag(capsys):
assert normalize_output_format("dall-e-3", "jpeg") == ""
captured = capsys.readouterr()
assert "--output-format is only supported for gpt-image models" in captured.err
def test_normalize_output_format_normalizes_case_for_supported_values():
assert normalize_output_format("gpt-image-1", "PNG") == "png"
assert normalize_output_format("gpt-image-1", "WEBP") == "webp"
def test_normalize_output_format_strips_whitespace_for_supported_values():
assert normalize_output_format("gpt-image-1", " png ") == "png"
def test_normalize_output_format_keeps_supported_values():
assert normalize_output_format("gpt-image-1", "png") == "png"
assert normalize_output_format("gpt-image-1", "jpeg") == "jpeg"
assert normalize_output_format("gpt-image-1", "webp") == "webp"
def test_normalize_output_format_normalizes_jpg_alias():
assert normalize_output_format("gpt-image-1", "jpg") == "jpeg"
def test_normalize_output_format_rejects_invalid_values():
with pytest.raises(ValueError, match="Invalid --output-format"):
normalize_output_format("gpt-image-1", "svg")
def test_write_gallery_escapes_prompt_xss():
with tempfile.TemporaryDirectory() as tmpdir:
out = Path(tmpdir)