import assert from "node:assert/strict"; import test from "node:test"; import type { CliArgs } from "../types.ts"; import { buildContent, buildRequestBody, extractImageFromResponse, getAspectRatio, getImageSize, validateArgs, } from "./openrouter.ts"; const GEMINI_MODEL = "google/gemini-3.1-flash-image-preview"; const GEMINI_25_MODEL = "google/gemini-2.5-flash-image"; const GPT_5_IMAGE_MODEL = "openai/gpt-5-image"; const OPENROUTER_AUTO_MODEL = "openrouter/auto"; const FLUX_MODEL = "black-forest-labs/flux.2-pro"; function makeArgs(overrides: Partial = {}): CliArgs { return { prompt: null, promptFiles: [], imagePath: null, provider: null, model: null, aspectRatio: null, size: null, quality: null, imageSize: null, referenceImages: [], n: 1, batchFile: null, jobs: null, json: false, help: false, ...overrides, }; } test("OpenRouter request body uses image_config and string content for text-only prompts", () => { const args = makeArgs({ aspectRatio: "16:9", quality: "2k" }); const body = buildRequestBody("hello", GEMINI_MODEL, args, []); assert.deepEqual(body.image_config, { image_size: "2K", aspect_ratio: "16:9", }); assert.deepEqual(body.provider, { require_parameters: true, }); assert.deepEqual(body.modalities, ["image", "text"]); assert.equal(body.stream, false); assert.equal(body.messages[0].content, "hello"); }); test("OpenRouter request body keeps text+image modalities for current text+image models", () => { for (const model of [GEMINI_MODEL, GEMINI_25_MODEL, GPT_5_IMAGE_MODEL, OPENROUTER_AUTO_MODEL]) { const body = buildRequestBody("hello", model, makeArgs({ quality: "2k" }), []); assert.deepEqual(body.image_config, { image_size: "2K", }); assert.deepEqual(body.provider, { require_parameters: true, }); assert.deepEqual(body.modalities, ["image", "text"]); assert.equal(body.messages[0].content, "hello"); } }); test("OpenRouter request body uses image-only modalities for image-only models under CLI defaults", () => { const body = buildRequestBody("hello", FLUX_MODEL, makeArgs({ quality: "2k" }), []); assert.deepEqual(body.image_config, { image_size: "2K", }); assert.deepEqual(body.provider, { require_parameters: true, }); assert.deepEqual(body.modalities, ["image"]); assert.equal(body.stream, false); assert.equal(body.messages[0].content, "hello"); }); test("OpenRouter helper omits image_config when no size or quality is passed", () => { const body = buildRequestBody("hello", FLUX_MODEL, makeArgs(), []); assert.equal(body.image_config, undefined); assert.equal(body.provider, undefined); assert.deepEqual(body.modalities, ["image"]); assert.equal(body.stream, false); assert.equal(body.messages[0].content, "hello"); }); test("OpenRouter request body keeps multimodal array content when references are provided", () => { const content = buildContent("hello", ["data:image/png;base64,abc"]); assert.ok(Array.isArray(content)); assert.deepEqual(content[0], { type: "text", text: "hello" }); assert.deepEqual(content[1], { type: "image_url", image_url: { url: "data:image/png;base64,abc" }, }); }); test("OpenRouter size and aspect helpers infer supported values", () => { assert.equal(getImageSize(makeArgs()), null); assert.equal(getImageSize(makeArgs({ quality: "normal" })), "1K"); assert.equal(getImageSize(makeArgs({ size: "2048x1024" })), "2K"); assert.equal(getAspectRatio(GEMINI_MODEL, makeArgs({ size: "1600x900" })), "16:9"); assert.equal(getAspectRatio(GEMINI_MODEL, makeArgs({ size: "1024x4096" })), "1:4"); assert.equal(getAspectRatio(GEMINI_25_MODEL, makeArgs({ size: "1600x900" })), "16:9"); assert.equal(getAspectRatio(FLUX_MODEL, makeArgs({ size: "1024x4096" })), null); }); test("OpenRouter validates explicit aspect ratios and inferred size ratios against model support", () => { assert.doesNotThrow(() => validateArgs(GEMINI_MODEL, makeArgs({ aspectRatio: "1:4" })), ); assert.doesNotThrow(() => validateArgs(GEMINI_MODEL, makeArgs({ size: "1024x4096" })), ); assert.throws( () => validateArgs(GEMINI_25_MODEL, makeArgs({ aspectRatio: "1:4" })), /does not support aspect ratio 1:4/, ); assert.throws( () => validateArgs(FLUX_MODEL, makeArgs({ aspectRatio: "1:4" })), /does not support aspect ratio 1:4/, ); assert.throws( () => validateArgs(GEMINI_MODEL, makeArgs({ size: "2048x1024" })), /does not support size 2048x1024 \(aspect ratio 2:1\)/, ); }); test("OpenRouter response extraction supports inline image data and finish_reason errors", async () => { const bytes = await extractImageFromResponse({ choices: [ { message: { images: [ { image_url: { url: `data:image/png;base64,${Buffer.from("hello").toString("base64")}`, }, }, ], }, }, ], }); assert.equal(Buffer.from(bytes).toString("utf8"), "hello"); await assert.rejects( () => extractImageFromResponse({ choices: [ { finish_reason: "error", native_finish_reason: "MALFORMED_FUNCTION_CALL", message: { content: null }, }, ], }), /finish_reason=MALFORMED_FUNCTION_CALL/, ); });