| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; |
| import { CodexExecutor } from "../../open-sse/executors/codex.js"; |
| import * as proxyFetchModule from "../../open-sse/utils/proxyFetch.js"; |
|
|
| const IMAGE_1MB_BYTES = 1024 * 1024; |
| const REMOTE_URL = "https://example.com/big.jpg"; |
| const DATA_URI = "data:image/png;base64,iVBORw0KGgo="; |
|
|
| function makeImageBuffer(sizeBytes) { |
| const buf = new Uint8Array(sizeBytes); |
| for (let i = 0; i < sizeBytes; i++) buf[i] = i & 0xff; |
| return buf.buffer; |
| } |
|
|
| function mockImageFetch(sizeBytes, mimeType = "image/jpeg") { |
| return { |
| ok: true, |
| headers: { get: (k) => (k === "Content-Type" ? mimeType : null) }, |
| arrayBuffer: async () => makeImageBuffer(sizeBytes), |
| }; |
| } |
|
|
| describe("CodexExecutor image handling", () => { |
| let originalFetch; |
|
|
| beforeEach(() => { |
| originalFetch = global.fetch; |
| }); |
|
|
| afterEach(() => { |
| global.fetch = originalFetch; |
| vi.restoreAllMocks(); |
| }); |
|
|
| it("fetches 1MB remote image and inlines it as base64 data URI", async () => { |
| global.fetch = vi.fn(async () => mockImageFetch(IMAGE_1MB_BYTES)); |
|
|
| const executor = new CodexExecutor(); |
| const body = { |
| input: [ |
| { |
| role: "user", |
| content: [ |
| { type: "input_text", text: "describe this" }, |
| { type: "image_url", image_url: { url: REMOTE_URL, detail: "high" } }, |
| ], |
| }, |
| ], |
| }; |
|
|
| await executor.prefetchImages(body); |
|
|
| const imgBlock = body.input[0].content.find((c) => c.type === "input_image"); |
| expect(imgBlock, "input_image block must be present after prefetch").toBeDefined(); |
| expect(imgBlock.image_url.startsWith("data:image/jpeg;base64,")).toBe(true); |
| expect(imgBlock.detail).toBe("high"); |
|
|
| const base64Payload = imgBlock.image_url.split(",")[1]; |
| const decodedLen = Buffer.from(base64Payload, "base64").length; |
| expect(decodedLen).toBe(IMAGE_1MB_BYTES); |
| expect(global.fetch).toHaveBeenCalledTimes(1); |
| }); |
|
|
| it("passes through existing data URIs without calling fetch", async () => { |
| global.fetch = vi.fn(); |
|
|
| const executor = new CodexExecutor(); |
| const body = { |
| input: [ |
| { |
| role: "user", |
| content: [{ type: "image_url", image_url: { url: DATA_URI } }], |
| }, |
| ], |
| }; |
|
|
| await executor.prefetchImages(body); |
|
|
| const imgBlock = body.input[0].content.find((c) => c.type === "input_image"); |
| expect(imgBlock.image_url).toBe(DATA_URI); |
| expect(global.fetch).not.toHaveBeenCalled(); |
| }); |
|
|
| it("falls back to original URL when remote fetch fails", async () => { |
| global.fetch = vi.fn(async () => { throw new Error("network down"); }); |
|
|
| const executor = new CodexExecutor(); |
| const body = { |
| input: [ |
| { |
| role: "user", |
| content: [{ type: "image_url", image_url: { url: REMOTE_URL } }], |
| }, |
| ], |
| }; |
|
|
| await executor.prefetchImages(body); |
|
|
| const imgBlock = body.input[0].content.find((c) => c.type === "input_image"); |
| expect(imgBlock.image_url).toBe(REMOTE_URL); |
| }); |
|
|
| it("execute() prefetches images before sending to upstream", async () => { |
| global.fetch = vi.fn(async () => mockImageFetch(IMAGE_1MB_BYTES)); |
|
|
| let capturedBodyString = null; |
| vi.spyOn(proxyFetchModule, "proxyAwareFetch").mockImplementation(async (url, init) => { |
| capturedBodyString = init.body; |
| return { ok: true, status: 200, headers: new Map() }; |
| }); |
|
|
| const executor = new CodexExecutor(); |
| const body = { |
| input: [ |
| { |
| role: "user", |
| content: [{ type: "image_url", image_url: { url: REMOTE_URL } }], |
| }, |
| ], |
| }; |
|
|
| await executor.execute({ |
| model: "gpt-5.3-codex", |
| body, |
| stream: true, |
| credentials: { accessToken: "test" }, |
| }); |
|
|
| expect(capturedBodyString).toBeTypeOf("string"); |
| expect(capturedBodyString).not.toBe("{}"); |
| const parsed = JSON.parse(capturedBodyString); |
| const imgBlock = parsed.input[0].content.find((c) => c.type === "input_image"); |
| expect(imgBlock.image_url.startsWith("data:image/jpeg;base64,")).toBe(true); |
| }); |
| }); |
|
|