From d7fca5794db7dcc4312ca5516760bc5e52f56a6f Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 28 May 2026 19:35:50 -0400 Subject: [PATCH] fix: validate image numeric options --- src/agents/tools/image-generate-tool.test.ts | 8 +++--- src/agents/tools/image-generate-tool.ts | 28 ++++++++++++-------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/agents/tools/image-generate-tool.test.ts b/src/agents/tools/image-generate-tool.test.ts index 759d3bad848..668dcacee2d 100644 --- a/src/agents/tools/image-generate-tool.test.ts +++ b/src/agents/tools/image-generate-tool.test.ts @@ -1300,7 +1300,7 @@ describe("createImageGenerateTool", () => { expect(details.outputFormat).toBe("jpeg"); }); - it("rejects malformed OpenAI output compression", async () => { + it.each([60.5, "60px", null])("rejects malformed OpenAI output compression %s", async (value) => { const generateImage = vi.spyOn(imageGenerationRuntime, "generateImage").mockResolvedValue({ provider: "openai", model: "gpt-image-2", @@ -1327,7 +1327,7 @@ describe("createImageGenerateTool", () => { prompt: "Cheap preview", outputFormat: "jpeg", openai: { - outputCompression: 60.5, + outputCompression: value, }, }), ).rejects.toThrow("openai.outputCompression must be between 0 and 100"); @@ -1501,7 +1501,7 @@ describe("createImageGenerateTool", () => { ); }); - it("rejects fractional image counts", async () => { + it.each([2.5, "2cats", null])("rejects malformed image count %s", async (count) => { const generateImage = vi.spyOn(imageGenerationRuntime, "generateImage").mockResolvedValue({ provider: "google", model: "gemini-3.1-flash-image-preview", @@ -1526,7 +1526,7 @@ describe("createImageGenerateTool", () => { await expect( tool.execute("call-fractional-count", { prompt: "A cat wearing sunglasses", - count: 2.5, + count, }), ).rejects.toThrow("count must be between 1 and 4"); expect(generateImage).not.toHaveBeenCalled(); diff --git a/src/agents/tools/image-generate-tool.ts b/src/agents/tools/image-generate-tool.ts index d6ea4a2b538..b30091f04a5 100644 --- a/src/agents/tools/image-generate-tool.ts +++ b/src/agents/tools/image-generate-tool.ts @@ -45,7 +45,12 @@ import { recordRecentMediaGenerationTaskStartForSession, } from "../media-generation-task-status-shared.js"; import { optionalStringEnum } from "../schema/string-enum.js"; -import { ToolInputError, readNumberParam, readStringParam } from "./common.js"; +import { + ToolInputError, + readNonNegativeIntegerParam, + readPositiveIntegerParam, + readStringParam, +} from "./common.js"; import { completeImageGenerationTaskRun, createImageGenerationTaskRun, @@ -236,11 +241,13 @@ function resolveAction(args: Record): "generate" | "list" | "st } function resolveRequestedCount(args: Record): number { - const count = readNumberParam(args, "count", { positiveInteger: true }); + if (readSnakeCaseParamRaw(args, "count") === null) { + throw new ToolInputError(`count must be between 1 and ${MAX_COUNT}`); + } + const count = readPositiveIntegerParam(args, "count", { + message: `count must be between 1 and ${MAX_COUNT}`, + }); if (count === undefined) { - if (readSnakeCaseParamRaw(args, "count") !== undefined) { - throw new ToolInputError(`count must be between 1 and ${MAX_COUNT}`); - } return DEFAULT_COUNT; } if (count < 1 || count > MAX_COUNT) { @@ -343,14 +350,13 @@ function normalizeOpenAIOptions(args: Record): ImageGenerationO const raw = readRecordParam(args, "openai"); const background = normalizeOpenAIBackground(readStringParam(raw, "background")); const moderation = normalizeOpenAIModeration(readStringParam(raw, "moderation")); - const outputCompression = readNumberParam(raw, "outputCompression", { nonNegativeInteger: true }); - const user = readStringParam(raw, "user"); - if ( - outputCompression === undefined && - readSnakeCaseParamRaw(raw, "outputCompression") !== undefined - ) { + if (readSnakeCaseParamRaw(raw, "outputCompression") === null) { throw new ToolInputError("openai.outputCompression must be between 0 and 100"); } + const outputCompression = readNonNegativeIntegerParam(raw, "outputCompression", { + message: "openai.outputCompression must be between 0 and 100", + }); + const user = readStringParam(raw, "user"); if (outputCompression !== undefined && (outputCompression < 0 || outputCompression > 100)) { throw new ToolInputError("openai.outputCompression must be between 0 and 100"); }