Skip to content

Commit e1c2388

Browse files
committed
Refactor image handling in OpenAI image generation and update sample script to use new model
1 parent 36bc1dd commit e1c2388

6 files changed

Lines changed: 24 additions & 51 deletions

File tree

packages/core/src/bufferlike.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,9 @@ export async function resolveBufferLikeAndExt(
7878
* @param mime - Optional MIME type override. If not provided, the MIME type will be inferred from the buffer, or defaults to "application/octet-stream".
7979
* @returns A Blob object constructed from the input data.
8080
*/
81-
export async function BufferToBlob(buffer: Buffer | Uint8Array, mime?: string) {
81+
export async function BufferToBlob(buffer: Buffer | Uint8Array, mime?: string): Promise<Blob> {
8282
const type = await fileTypeFromBuffer(buffer);
83-
return new Blob([buffer], {
83+
return new Blob([buffer as any], {
8484
type: mime || type?.mime || "application/octet-stream",
8585
});
8686
}

packages/core/src/fetch.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,9 @@ export async function createFetch(
126126
const crossFetchWithProxy: typeof fetch = (url, opts) => {
127127
const requestInit = deleteUndefinedValues({ signal, agent, ...(opts || {}) });
128128
dbg(`%s %s`, opts?.method || "GET", url);
129-
return crossFetch(url, requestInit);
129+
dbg(`content-type: %s`, (opts?.headers as any)?.["Content-Type"] as string);
130+
if (opts?.body instanceof FormData) return globalThis.fetch(url, requestInit);
131+
else return crossFetch(url, requestInit);
130132
};
131133

132134
// Return the default fetch if no retry status codes are specified

packages/core/src/fetchtext.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@ export function traceFetchPost(
183183
}
184184
});
185185
httpRequest += `--${boundary}--\n`;
186+
} else if (body === "string") {
187+
httpRequest += "\n" + body;
186188
} else {
187189
httpRequest += "\n" + JSON.stringify(body, null, 2);
188190
}

packages/core/src/llmsdata.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export default {
7878
reasoning_small: "o3-mini",
7979
transcription: "whisper-1",
8080
speech: "tts-1",
81-
image: "dall-e-3",
81+
image: "gpt-image-1",
8282
intent: "gpt-4.1-mini",
8383
},
8484
models: {

packages/core/src/openai.ts

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import { traceFetchPost } from "./fetchtext.js";
3737
import { genaiscriptDebug } from "./debug.js";
3838
import { OpenAIv2ResponsesChatCompletion } from "./openai-responses.js";
3939
import type { LanguageModelInfo, RetryOptions, TranscriptionResult } from "./types.js";
40-
import { resolveBufferLike } from "./bufferlike.js";
40+
import { BufferToBlob, resolveBufferLike } from "./bufferlike.js";
4141
import { getConfigHeaders, OpenAIv1ChatCompletion } from "./openai-chatcompletion.js";
4242

4343
const dbg = genaiscriptDebug("openai");
@@ -336,7 +336,7 @@ export async function OpenAIImageGeneration(
336336

337337
if (isMultipart) {
338338
// Use FormData for image uploads
339-
body = new FormData();
339+
const form = (body = new FormData());
340340

341341
// Add the image file
342342
const imageBuffer = await resolveBufferLike(image);
@@ -346,43 +346,43 @@ export async function OpenAIImageGeneration(
346346
error: serializeError(new Error("Failed to resolve image buffer")),
347347
};
348348
}
349-
body.append("image", new Blob([imageBuffer], { type: "image/png" }), "image.png");
349+
form.append("image", await BufferToBlob(imageBuffer, "image/png"), "image.png");
350350

351351
// Add mask if provided (only for edit mode)
352352
if (mode === "edit" && mask) {
353353
const maskBuffer = await resolveBufferLike(mask);
354354
if (maskBuffer) {
355-
body.append("mask", new Blob([maskBuffer], { type: "image/png" }), "mask.png");
355+
form.append("mask", await BufferToBlob(maskBuffer, "image/png"), "mask.png");
356356
}
357357
}
358358

359359
// Add model
360-
body.append("model", model);
360+
form.append("model", model);
361361

362362
// Add prompt (required for edit mode)
363363
if (mode === "edit") {
364-
body.append("prompt", prompt);
364+
form.append("prompt", prompt);
365365
}
366366

367367
// Add processed parameters
368368
if (shouldIncludeSize) {
369-
body.append("size", processedParams.size);
369+
form.append("size", processedParams.size);
370370
}
371371

372372
if (shouldIncludeQuality) {
373-
body.append("quality", processedParams.quality);
373+
form.append("quality", processedParams.quality);
374374
}
375375

376376
if (shouldIncludeStyle) {
377-
body.append("style", processedParams.style);
377+
form.append("style", processedParams.style);
378378
}
379379

380380
if (shouldIncludeOutputFormat) {
381-
body.append("output_format", processedParams.outputFormat);
381+
form.append("output_format", processedParams.outputFormat);
382382
}
383383

384384
// Always request b64_json for response format
385-
body.append("response_format", "b64_json");
385+
if (isDallE) form.append("response_format", "b64_json");
386386

387387
// Don't set Content-Type header for FormData, let the browser set it with boundary
388388
delete headers["Content-Type"];
@@ -416,9 +416,7 @@ export async function OpenAIImageGeneration(
416416
}
417417

418418
headers["Content-Type"] = "application/json";
419-
body = JSON.stringify(body);
420419
}
421-
422420
dbg("%o", {
423421
mode,
424422
endpoint,
@@ -440,13 +438,12 @@ export async function OpenAIImageGeneration(
440438
const freq = {
441439
method: "POST",
442440
headers,
443-
body,
441+
body: isMultipart ? body : JSON.stringify(body),
444442
};
445443

446444
trace?.itemValue(`url`, `[${url}](${url})`);
447-
if (!isMultipart) {
448-
traceFetchPost(trace, url, freq.headers, JSON.parse(body));
449-
}
445+
446+
traceFetchPost(trace, url, freq.headers, body);
450447

451448
const res = await fetch(url, freq as any);
452449
dbg(`response: %d %s`, res.status, res.statusText);

samples/sample/genaisrc/image-edit-advanced.genai.mjs

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,45 +6,17 @@ script({
66
description: "Demonstrates advanced image editing capabilities with creative transformations",
77
group: "Image Generation"
88
})
9+
const { output } = env
910

1011
// Edit the robot image with specific instructions
1112
const { image } = await generateImage(
1213
"Add a colorful rainbow in the background and make the robot look more cheerful",
1314
{
1415
mode: "edit",
1516
image: "src/robots.jpg",
16-
model: "openai:gpt-image-1",
1717
quality: "high",
1818
size: "square"
1919
}
2020
)
2121

22-
console.log(`🤖 Generated edited robot: ${image.filename}`)
23-
24-
// Edit the mushroom image with artistic style
25-
const { image: landscapeEdit } = await generateImage(
26-
"Transform this into a magical fairy tale scene with glowing lights and enchanted atmosphere",
27-
{
28-
mode: "edit",
29-
image: "src/images/Little Mushroom in the Grass.jpg",
30-
model: "openai:gpt-image-1",
31-
quality: "medium",
32-
size: "landscape"
33-
}
34-
)
35-
36-
console.log(`🍄 Generated magical mushroom scene: ${landscapeEdit.filename}`)
37-
38-
// Edit the galaxy image with sci-fi elements
39-
const { image: galaxyEdit } = await generateImage(
40-
"Add futuristic spaceships and nebula effects to create an epic space battle scene",
41-
{
42-
mode: "edit",
43-
image: "src/vision/galaxy.jpg",
44-
model: "openai:gpt-image-1",
45-
quality: "high",
46-
size: "portrait"
47-
}
48-
)
49-
50-
console.log(`🌌 Generated space battle scene: ${galaxyEdit.filename}`)
22+
output.item(`🤖 Generated edited robot: ${image.filename}`)

0 commit comments

Comments
 (0)