Skip to content

Commit d075111

Browse files
AAirLinairlinsteipete
authored
Guard OpenAI image compression for PNG outputs (#85776)
* Guard OpenAI image compression for PNG outputs * Fix OpenAI image compression type narrowing * docs(changelog): note OpenAI PNG compression fix * Revert "docs(changelog): note OpenAI PNG compression fix" This reverts commit b11e4bf. --------- Co-authored-by: airlin <[email protected]> Co-authored-by: Peter Steinberger <[email protected]>
1 parent 1d1a7c2 commit d075111

3 files changed

Lines changed: 66 additions & 7 deletions

File tree

docs/tools/image-generation.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,8 @@ ComfyUI support 1.
310310
transparent outputs require `outputFormat` `png` or `webp` and a
311311
transparency-capable OpenAI image model. OpenClaw routes default
312312
`gpt-image-2` transparent-background requests to `gpt-image-1.5`.
313-
`openai.outputCompression` applies to JPEG/WebP outputs.
313+
`openai.outputCompression` applies to JPEG/WebP outputs and is ignored
314+
for PNG outputs.
314315

315316
The top-level `background` hint is provider-neutral and currently maps
316317
to the same OpenAI `background` request field when the OpenAI provider

extensions/openai/image-generation-provider.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,28 @@ describe("openai image generation provider", () => {
605605
expect(result.images[0]?.fileName).toBe("image-1.jpg");
606606
});
607607

608+
it("omits output compression for PNG direct generations", async () => {
609+
mockGeneratedPngResponse();
610+
611+
const provider = buildOpenAIImageGenerationProvider();
612+
await provider.generateImage({
613+
provider: "openai",
614+
model: "gpt-image-2",
615+
prompt: "Transparent PNG",
616+
cfg: {},
617+
outputFormat: "png",
618+
providerOptions: {
619+
openai: {
620+
outputCompression: 60,
621+
},
622+
},
623+
});
624+
625+
const body = jsonRequestCall().body as Record<string, unknown>;
626+
expect(body.output_format).toBe("png");
627+
expect(body.output_compression).toBeUndefined();
628+
});
629+
608630
it("routes transparent default-model requests to the OpenAI image model that supports alpha", async () => {
609631
mockGeneratedPngResponse();
610632

@@ -1080,6 +1102,30 @@ describe("openai image generation provider", () => {
10801102
expect(result.model).toBe("gpt-image-1.5");
10811103
});
10821104

1105+
it("omits output compression for PNG Codex image requests", async () => {
1106+
mockCodexAuthOnly();
1107+
mockCodexImageStream({ imageData: "codex-png-image" });
1108+
1109+
const provider = buildOpenAIImageGenerationProvider();
1110+
await provider.generateImage({
1111+
provider: "openai",
1112+
model: "gpt-image-2",
1113+
prompt: "Draw a transparent Codex badge",
1114+
cfg: {},
1115+
authStore: { version: 1, profiles: {} },
1116+
outputFormat: "png",
1117+
providerOptions: {
1118+
openai: {
1119+
outputCompression: 55,
1120+
},
1121+
},
1122+
});
1123+
1124+
const body = jsonRequestCall().body as { tools?: Array<Record<string, unknown>> };
1125+
expect(body.tools?.[0]?.output_format).toBe("png");
1126+
expect(body.tools?.[0]?.output_compression).toBeUndefined();
1127+
});
1128+
10831129
it("uses configured Codex OAuth directly instead of probing an available OpenAI API key", async () => {
10841130
resolveApiKeyForProviderMock.mockImplementation(async (params?: { provider?: string }) => {
10851131
if (params?.provider === "openai") {

extensions/openai/image-generation-provider.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -183,20 +183,33 @@ function resolveOutputMime(outputFormat?: ImageGenerationOutputFormat): {
183183
return { mimeType: DEFAULT_OUTPUT_MIME, extension: DEFAULT_OUTPUT_EXTENSION };
184184
}
185185

186+
type OpenAIImageRequest = Parameters<ImageGenerationProvider["generateImage"]>[0];
187+
type OpenAIImageOptions = NonNullable<OpenAIImageRequest["providerOptions"]>["openai"];
188+
189+
function resolveOpenAIImageOutputCompression(
190+
req: OpenAIImageRequest,
191+
openai: OpenAIImageOptions,
192+
): number | undefined {
193+
if (openai?.outputCompression === undefined) {
194+
return undefined;
195+
}
196+
const outputFormat = req.outputFormat ?? "png";
197+
return outputFormat === "jpeg" || outputFormat === "webp" ? openai.outputCompression : undefined;
198+
}
199+
186200
function appendOpenAIImageOptions(
187201
target: Record<string, unknown> | FormData,
188202
req: Parameters<ImageGenerationProvider["generateImage"]>[0],
189203
): void {
190204
const openai = req.providerOptions?.openai;
191205
const background = openai?.background ?? req.background;
206+
const outputCompression = resolveOpenAIImageOutputCompression(req, openai);
192207
const entries: Record<string, unknown> = {
193208
...(req.quality !== undefined ? { quality: req.quality } : {}),
194209
...(req.outputFormat !== undefined ? { output_format: req.outputFormat } : {}),
195210
...(background !== undefined ? { background } : {}),
196211
...(openai?.moderation !== undefined ? { moderation: openai.moderation } : {}),
197-
...(openai?.outputCompression !== undefined
198-
? { output_compression: openai.outputCompression }
199-
: {}),
212+
...(outputCompression !== undefined ? { output_compression: outputCompression } : {}),
200213
...(openai?.user !== undefined ? { user: openai.user } : {}),
201214
};
202215
for (const [key, value] of Object.entries(entries)) {
@@ -637,6 +650,7 @@ async function generateOpenAICodexImage(params: {
637650
const timeoutMs = resolveOpenAIImageTimeoutMs(req.timeoutMs);
638651
const openai = req.providerOptions?.openai;
639652
const background = openai?.background ?? req.background;
653+
const outputCompression = resolveOpenAIImageOutputCompression(req, openai);
640654
headers.set("Content-Type", "application/json");
641655
const content: Array<Record<string, unknown>> = [
642656
{ type: "input_text", text: req.prompt },
@@ -668,9 +682,7 @@ async function generateOpenAICodexImage(params: {
668682
...(req.quality !== undefined ? { quality: req.quality } : {}),
669683
...(req.outputFormat !== undefined ? { output_format: req.outputFormat } : {}),
670684
...(background !== undefined ? { background } : {}),
671-
...(openai?.outputCompression !== undefined
672-
? { output_compression: openai.outputCompression }
673-
: {}),
685+
...(outputCompression !== undefined ? { output_compression: outputCompression } : {}),
674686
},
675687
],
676688
tool_choice: { type: "image_generation" },

0 commit comments

Comments
 (0)