fix: validate image caps

This commit is contained in:
Peter Steinberger
2026-05-28 20:02:51 -04:00
parent f9f4c4959b
commit 9119e8d99c
2 changed files with 46 additions and 10 deletions
+36 -2
View File
@@ -1696,8 +1696,8 @@ describe("image tool implicit imageModel config", () => {
items: { type: "string" },
},
model: { type: "string" },
maxBytesMb: { type: "number" },
maxImages: { type: "number" },
maxBytesMb: { type: "number", exclusiveMinimum: 0 },
maxImages: { type: "integer", minimum: 1 },
},
});
});
@@ -2343,6 +2343,40 @@ describe("image tool MiniMax VLM routing", () => {
expect(tooManyDetails?.max).toBe(1);
});
it("rejects invalid image cap values before loading images", async () => {
const { fetch, tool } = await createMinimaxVlmFixture({ status_code: 0, status_msg: "" });
await expect(
tool.execute("t1", {
prompt: "Compare these images.",
image: `data:image/png;base64,${ONE_PIXEL_PNG_B64}`,
maxImages: 1.5,
}),
).rejects.toThrow("maxImages must be a positive integer");
await expect(
tool.execute("t2", {
prompt: "Compare these images.",
image: `data:image/png;base64,${ONE_PIXEL_PNG_B64}`,
maxBytesMb: 0,
}),
).rejects.toThrow("maxBytesMb must be greater than 0");
expect(fetch).not.toHaveBeenCalled();
});
it("accepts string image caps through shared numeric readers", async () => {
const { fetch, tool } = await createMinimaxVlmFixture({ status_code: 0, status_msg: "" });
await tool.execute("t1", {
prompt: "Describe this image.",
image: `data:image/png;base64,${ONE_PIXEL_PNG_B64}`,
maxImages: "1",
maxBytesMb: "1",
});
expect(fetch).toHaveBeenCalledTimes(1);
});
it("surfaces MiniMax API errors from /v1/coding_plan/vlm", async () => {
const { tool } = await createMinimaxVlmFixture({ status_code: 1004, status_msg: "bad key" });
+10 -8
View File
@@ -45,6 +45,8 @@ import {
resolveImageFallbackCandidates,
resolveImageFallbackDefaultProvider,
} from "../model-fallback.js";
import { optionalFiniteNumberSchema, optionalPositiveIntegerSchema } from "../schema/typebox.js";
import { readFiniteNumberParam, readPositiveIntegerParam } from "./common.js";
import {
coerceImageAssistantText,
coerceImageModelConfig,
@@ -761,8 +763,8 @@ export function createImageTool(options?: {
}),
),
model: Type.Optional(Type.String()),
maxBytesMb: Type.Optional(Type.Number()),
maxImages: Type.Optional(Type.Number()),
maxBytesMb: optionalFiniteNumberSchema({ exclusiveMinimum: 0 }),
maxImages: optionalPositiveIntegerSchema(),
}),
execute: async (_toolCallId, args) => {
const record = args && typeof args === "object" ? (args as Record<string, unknown>) : {};
@@ -794,11 +796,7 @@ export function createImageTool(options?: {
}
// MARK: - Enforce max images cap
const maxImagesRaw = typeof record.maxImages === "number" ? record.maxImages : undefined;
const maxImages =
typeof maxImagesRaw === "number" && Number.isFinite(maxImagesRaw) && maxImagesRaw > 0
? Math.floor(maxImagesRaw)
: DEFAULT_MAX_IMAGES;
const maxImages = readPositiveIntegerParam(record, "maxImages") ?? DEFAULT_MAX_IMAGES;
if (imageInputs.length > maxImages) {
return {
content: [
@@ -815,7 +813,11 @@ export function createImageTool(options?: {
record,
DEFAULT_PROMPT,
);
const maxBytesMb = typeof record.maxBytesMb === "number" ? record.maxBytesMb : undefined;
const maxBytesMb = readFiniteNumberParam(record, "maxBytesMb", {
min: 0,
minExclusive: true,
message: "maxBytesMb must be greater than 0",
});
const maxBytes = pickMaxBytes(options?.config, maxBytesMb);
const imageModelConfig =
resolvedImageModelConfig ??