diff --git a/extensions/firecrawl/src/firecrawl-scrape-tool.ts b/extensions/firecrawl/src/firecrawl-scrape-tool.ts index 7efffb5cc7c5..00bd20ce1948 100644 --- a/extensions/firecrawl/src/firecrawl-scrape-tool.ts +++ b/extensions/firecrawl/src/firecrawl-scrape-tool.ts @@ -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" diff --git a/extensions/firecrawl/src/firecrawl-search-tool.ts b/extensions/firecrawl/src/firecrawl-search-tool.ts index a64c09f3e48f..e23850f2b434 100644 --- a/extensions/firecrawl/src/firecrawl-search-tool.ts +++ b/extensions/firecrawl/src/firecrawl-search-tool.ts @@ -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) => { 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; diff --git a/extensions/firecrawl/src/firecrawl-tools.test.ts b/extensions/firecrawl/src/firecrawl-tools.test.ts index 8c75a0f5d698..bb1b5a3b5497 100644 --- a/extensions/firecrawl/src/firecrawl-tools.test.ts +++ b/extensions/firecrawl/src/firecrawl-tools.test.ts @@ -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 () => { diff --git a/src/plugin-sdk/provider-web-search.ts b/src/plugin-sdk/provider-web-search.ts index 9f5a42a03fe1..12dceaa00d8d 100644 --- a/src/plugin-sdk/provider-web-search.ts +++ b/src/plugin-sdk/provider-web-search.ts @@ -9,6 +9,7 @@ import type { } from "../plugins/types.js"; export { jsonResult, + readNonNegativeIntegerParam, readNumberParam, readPositiveIntegerParam, readStringArrayParam,