From 6017b738b1ab87cee16192e99cb597e2fc095da0 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Fri, 6 Mar 2026 22:49:38 -0500 Subject: [PATCH] Web: add HEIC media regression and doc fix (#38294) * Web: add HEIC media normalization regression * Docs: list HEIC input_image MIME types * Update src/web/media.test.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- docs/gateway/openresponses-http-api.md | 2 +- src/web/media.test.ts | 29 ++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/docs/gateway/openresponses-http-api.md b/docs/gateway/openresponses-http-api.md index b5b4045ac62..8b490b30632 100644 --- a/docs/gateway/openresponses-http-api.md +++ b/docs/gateway/openresponses-http-api.md @@ -161,7 +161,7 @@ Supports base64 or URL sources: } ``` -Allowed MIME types (current): `image/jpeg`, `image/png`, `image/gif`, `image/webp`. +Allowed MIME types (current): `image/jpeg`, `image/png`, `image/gif`, `image/webp`, `image/heic`, `image/heif`. Max size (current): 10MB. ## Files (`input_file`) diff --git a/src/web/media.test.ts b/src/web/media.test.ts index d91ed4b7d66..9db06e3024a 100644 --- a/src/web/media.test.ts +++ b/src/web/media.test.ts @@ -16,6 +16,17 @@ import { optimizeImageToJpeg, } from "./media.js"; +const convertHeicToJpegMock = vi.fn(); + +vi.mock("../media/image-ops.js", async () => { + const actual = + await vi.importActual("../media/image-ops.js"); + return { + ...actual, + convertHeicToJpeg: (...args: unknown[]) => convertHeicToJpegMock(...args), + }; +}); + let fixtureRoot = ""; let fixtureFileCount = 0; let largeJpegBuffer: Buffer; @@ -23,6 +34,7 @@ let largeJpegFile = ""; let tinyPngBuffer: Buffer; let tinyPngFile = ""; let tinyPngWrongExtFile = ""; +let fakeHeicFile = ""; let alphaPngBuffer: Buffer; let alphaPngFile = ""; let fallbackPngBuffer: Buffer; @@ -76,6 +88,7 @@ beforeAll(async () => { .toBuffer(); tinyPngFile = await writeTempFile(tinyPngBuffer, ".png"); tinyPngWrongExtFile = await writeTempFile(tinyPngBuffer, ".bin"); + fakeHeicFile = await writeTempFile(Buffer.from("fake-heic"), ".heic"); alphaPngBuffer = await sharp({ create: { width: 64, @@ -178,6 +191,22 @@ describe("web media loading", () => { expect(result.contentType).toBe("image/jpeg"); }); + it("normalizes HEIC local files to JPEG output", async () => { + convertHeicToJpegMock.mockResolvedValueOnce(tinyPngBuffer); + + const result = await loadWebMedia(fakeHeicFile, 1024 * 1024); + + expect(convertHeicToJpegMock).toHaveBeenCalledTimes(1); + expect(result.kind).toBe("image"); + expect(result.contentType).toBe("image/jpeg"); + expect(result.fileName).toBe(path.basename(fakeHeicFile, ".heic") + ".jpg"); + expect(result.buffer.length).toBeGreaterThan(0); + expect(result.buffer.equals(tinyPngBuffer)).toBe(false); + // Confirm the output is actually JPEG (magic bytes 0xFF 0xD8) + expect(result.buffer[0]).toBe(0xff); + expect(result.buffer[1]).toBe(0xd8); + }); + it("includes URL + status in fetch errors", async () => { const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValueOnce({ ok: false,