mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
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 b11e4bff01.
---------
Co-authored-by: airlin <airlin@airlins-Mac-mini.local>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
@@ -310,7 +310,8 @@ ComfyUI support 1.
|
||||
transparent outputs require `outputFormat` `png` or `webp` and a
|
||||
transparency-capable OpenAI image model. OpenClaw routes default
|
||||
`gpt-image-2` transparent-background requests to `gpt-image-1.5`.
|
||||
`openai.outputCompression` applies to JPEG/WebP outputs.
|
||||
`openai.outputCompression` applies to JPEG/WebP outputs and is ignored
|
||||
for PNG outputs.
|
||||
|
||||
The top-level `background` hint is provider-neutral and currently maps
|
||||
to the same OpenAI `background` request field when the OpenAI provider
|
||||
|
||||
@@ -605,6 +605,28 @@ describe("openai image generation provider", () => {
|
||||
expect(result.images[0]?.fileName).toBe("image-1.jpg");
|
||||
});
|
||||
|
||||
it("omits output compression for PNG direct generations", async () => {
|
||||
mockGeneratedPngResponse();
|
||||
|
||||
const provider = buildOpenAIImageGenerationProvider();
|
||||
await provider.generateImage({
|
||||
provider: "openai",
|
||||
model: "gpt-image-2",
|
||||
prompt: "Transparent PNG",
|
||||
cfg: {},
|
||||
outputFormat: "png",
|
||||
providerOptions: {
|
||||
openai: {
|
||||
outputCompression: 60,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const body = jsonRequestCall().body as Record<string, unknown>;
|
||||
expect(body.output_format).toBe("png");
|
||||
expect(body.output_compression).toBeUndefined();
|
||||
});
|
||||
|
||||
it("routes transparent default-model requests to the OpenAI image model that supports alpha", async () => {
|
||||
mockGeneratedPngResponse();
|
||||
|
||||
@@ -1080,6 +1102,30 @@ describe("openai image generation provider", () => {
|
||||
expect(result.model).toBe("gpt-image-1.5");
|
||||
});
|
||||
|
||||
it("omits output compression for PNG Codex image requests", async () => {
|
||||
mockCodexAuthOnly();
|
||||
mockCodexImageStream({ imageData: "codex-png-image" });
|
||||
|
||||
const provider = buildOpenAIImageGenerationProvider();
|
||||
await provider.generateImage({
|
||||
provider: "openai",
|
||||
model: "gpt-image-2",
|
||||
prompt: "Draw a transparent Codex badge",
|
||||
cfg: {},
|
||||
authStore: { version: 1, profiles: {} },
|
||||
outputFormat: "png",
|
||||
providerOptions: {
|
||||
openai: {
|
||||
outputCompression: 55,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const body = jsonRequestCall().body as { tools?: Array<Record<string, unknown>> };
|
||||
expect(body.tools?.[0]?.output_format).toBe("png");
|
||||
expect(body.tools?.[0]?.output_compression).toBeUndefined();
|
||||
});
|
||||
|
||||
it("uses configured Codex OAuth directly instead of probing an available OpenAI API key", async () => {
|
||||
resolveApiKeyForProviderMock.mockImplementation(async (params?: { provider?: string }) => {
|
||||
if (params?.provider === "openai") {
|
||||
|
||||
@@ -183,20 +183,33 @@ function resolveOutputMime(outputFormat?: ImageGenerationOutputFormat): {
|
||||
return { mimeType: DEFAULT_OUTPUT_MIME, extension: DEFAULT_OUTPUT_EXTENSION };
|
||||
}
|
||||
|
||||
type OpenAIImageRequest = Parameters<ImageGenerationProvider["generateImage"]>[0];
|
||||
type OpenAIImageOptions = NonNullable<OpenAIImageRequest["providerOptions"]>["openai"];
|
||||
|
||||
function resolveOpenAIImageOutputCompression(
|
||||
req: OpenAIImageRequest,
|
||||
openai: OpenAIImageOptions,
|
||||
): number | undefined {
|
||||
if (openai?.outputCompression === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const outputFormat = req.outputFormat ?? "png";
|
||||
return outputFormat === "jpeg" || outputFormat === "webp" ? openai.outputCompression : undefined;
|
||||
}
|
||||
|
||||
function appendOpenAIImageOptions(
|
||||
target: Record<string, unknown> | FormData,
|
||||
req: Parameters<ImageGenerationProvider["generateImage"]>[0],
|
||||
): void {
|
||||
const openai = req.providerOptions?.openai;
|
||||
const background = openai?.background ?? req.background;
|
||||
const outputCompression = resolveOpenAIImageOutputCompression(req, openai);
|
||||
const entries: Record<string, unknown> = {
|
||||
...(req.quality !== undefined ? { quality: req.quality } : {}),
|
||||
...(req.outputFormat !== undefined ? { output_format: req.outputFormat } : {}),
|
||||
...(background !== undefined ? { background } : {}),
|
||||
...(openai?.moderation !== undefined ? { moderation: openai.moderation } : {}),
|
||||
...(openai?.outputCompression !== undefined
|
||||
? { output_compression: openai.outputCompression }
|
||||
: {}),
|
||||
...(outputCompression !== undefined ? { output_compression: outputCompression } : {}),
|
||||
...(openai?.user !== undefined ? { user: openai.user } : {}),
|
||||
};
|
||||
for (const [key, value] of Object.entries(entries)) {
|
||||
@@ -637,6 +650,7 @@ async function generateOpenAICodexImage(params: {
|
||||
const timeoutMs = resolveOpenAIImageTimeoutMs(req.timeoutMs);
|
||||
const openai = req.providerOptions?.openai;
|
||||
const background = openai?.background ?? req.background;
|
||||
const outputCompression = resolveOpenAIImageOutputCompression(req, openai);
|
||||
headers.set("Content-Type", "application/json");
|
||||
const content: Array<Record<string, unknown>> = [
|
||||
{ type: "input_text", text: req.prompt },
|
||||
@@ -668,9 +682,7 @@ async function generateOpenAICodexImage(params: {
|
||||
...(req.quality !== undefined ? { quality: req.quality } : {}),
|
||||
...(req.outputFormat !== undefined ? { output_format: req.outputFormat } : {}),
|
||||
...(background !== undefined ? { background } : {}),
|
||||
...(openai?.outputCompression !== undefined
|
||||
? { output_compression: openai.outputCompression }
|
||||
: {}),
|
||||
...(outputCompression !== undefined ? { output_compression: outputCompression } : {}),
|
||||
},
|
||||
],
|
||||
tool_choice: { type: "image_generation" },
|
||||
|
||||
Reference in New Issue
Block a user