fix: validate firecrawl numeric options

This commit is contained in:
Peter Steinberger
2026-05-28 19:20:09 -04:00
parent 1211123fe6
commit 3c907250b9
4 changed files with 48 additions and 42 deletions
@@ -2,7 +2,8 @@ import { optionalStringEnum } from "openclaw/plugin-sdk/channel-actions";
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-runtime";
import {
jsonResult,
readNumberParam,
readNonNegativeIntegerParam,
readPositiveIntegerParam,
readStringParam,
} from "openclaw/plugin-sdk/provider-web-search";
import { Type } from "typebox";
@@ -60,11 +61,9 @@ export function createFirecrawlScrapeTool(api: OpenClawPluginApi) {
const url = readStringParam(rawParams, "url", { required: true });
const extractMode =
readStringParam(rawParams, "extractMode") === "text" ? "text" : "markdown";
const maxChars = readNumberParam(rawParams, "maxChars", { positiveInteger: true });
const maxAgeMs = readNumberParam(rawParams, "maxAgeMs", { nonNegativeInteger: true });
const timeoutSeconds = readNumberParam(rawParams, "timeoutSeconds", {
positiveInteger: true,
});
const maxChars = readPositiveIntegerParam(rawParams, "maxChars");
const maxAgeMs = readNonNegativeIntegerParam(rawParams, "maxAgeMs");
const timeoutSeconds = readPositiveIntegerParam(rawParams, "timeoutSeconds");
const proxyRaw = readStringParam(rawParams, "proxy");
const proxy =
proxyRaw === "basic" || proxyRaw === "stealth" || proxyRaw === "auto"
@@ -1,7 +1,7 @@
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-runtime";
import {
jsonResult,
readNumberParam,
readPositiveIntegerParam,
readStringArrayParam,
readStringParam,
} from "openclaw/plugin-sdk/provider-web-search";
@@ -52,10 +52,11 @@ export function createFirecrawlSearchTool(api: OpenClawPluginApi) {
parameters: FirecrawlSearchToolSchema,
execute: async (_toolCallId: string, rawParams: Record<string, unknown>) => {
const query = readStringParam(rawParams, "query", { required: true });
const count = readNumberParam(rawParams, "count", { positiveInteger: true });
const timeoutSeconds = readNumberParam(rawParams, "timeoutSeconds", {
positiveInteger: true,
const count = readPositiveIntegerParam(rawParams, "count", {
max: 10,
message: "count must be an integer from 1 to 10",
});
const timeoutSeconds = readPositiveIntegerParam(rawParams, "timeoutSeconds");
const sources = readStringArrayParam(rawParams, "sources");
const categories = readStringArrayParam(rawParams, "categories");
const scrapeResults = rawParams.scrapeResults === true;
@@ -489,47 +489,52 @@ describe("firecrawl tools", () => {
});
});
it("drops malformed numeric Firecrawl tool options", async () => {
it("rejects malformed numeric Firecrawl search options before dispatch", async () => {
const searchTool = createFirecrawlSearchTool({
config: { env: "test" },
} as never);
await searchTool.execute("call-search", {
query: "web search",
count: 6.5,
timeoutSeconds: Number.POSITIVE_INFINITY,
});
expect(runFirecrawlSearch).toHaveBeenLastCalledWith({
cfg: { env: "test" },
query: "web search",
count: undefined,
timeoutSeconds: undefined,
sources: undefined,
categories: undefined,
scrapeResults: false,
});
await expect(
searchTool.execute("call-search", {
query: "web search",
count: 6.5,
}),
).rejects.toThrow("count must be an integer from 1 to 10");
await expect(
searchTool.execute("call-search-timeout", {
query: "web search",
timeoutSeconds: Number.POSITIVE_INFINITY,
}),
).rejects.toThrow("timeoutSeconds must be a positive integer");
expect(runFirecrawlSearch).not.toHaveBeenCalled();
});
it("rejects malformed numeric Firecrawl scrape options before dispatch", async () => {
const scrapeTool = createFirecrawlScrapeTool({
config: { env: "test" },
} as never);
await scrapeTool.execute("call-scrape", {
url: "https://docs.openclaw.ai",
maxChars: 1500.5,
maxAgeMs: -1,
timeoutSeconds: 22.5,
});
expect(runFirecrawlScrape).toHaveBeenLastCalledWith({
cfg: { env: "test" },
url: "https://docs.openclaw.ai",
extractMode: "markdown",
maxChars: undefined,
onlyMainContent: undefined,
maxAgeMs: undefined,
proxy: undefined,
storeInCache: undefined,
timeoutSeconds: undefined,
});
await expect(
scrapeTool.execute("call-scrape-max-chars", {
url: "https://docs.openclaw.ai",
maxChars: 1500.5,
}),
).rejects.toThrow("maxChars must be a positive integer");
await expect(
scrapeTool.execute("call-scrape-max-age", {
url: "https://docs.openclaw.ai",
maxAgeMs: -1,
}),
).rejects.toThrow("maxAgeMs must be a non-negative integer");
await expect(
scrapeTool.execute("call-scrape-timeout", {
url: "https://docs.openclaw.ai",
timeoutSeconds: 22.5,
}),
).rejects.toThrow("timeoutSeconds must be a positive integer");
expect(runFirecrawlScrape).not.toHaveBeenCalled();
});
it("passes text mode through and ignores invalid proxy values", async () => {