fix: validate image numeric options

This commit is contained in:
Peter Steinberger
2026-05-28 19:35:50 -04:00
parent 4c49ca75d9
commit d7fca5794d
2 changed files with 21 additions and 15 deletions

View File

@@ -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();

View File

@@ -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<string, unknown>): "generate" | "list" | "st
}
function resolveRequestedCount(args: Record<string, unknown>): 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<string, unknown>): 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");
}