diff --git a/src/agents/tools/web-fetch.ts b/src/agents/tools/web-fetch.ts index c6e19046fde7..bc7f8011c36f 100644 --- a/src/agents/tools/web-fetch.ts +++ b/src/agents/tools/web-fetch.ts @@ -15,7 +15,7 @@ import { extractReadableContent } from "../../web-fetch/content-extractors.runti import { resolveWebProviderConfig } from "../../web/provider-runtime-shared.js"; import { stringEnum } from "../schema/string-enum.js"; import type { AnyAgentTool } from "./common.js"; -import { jsonResult, readNumberParam, readStringParam } from "./common.js"; +import { jsonResult, readPositiveIntegerParam, readStringParam } from "./common.js"; import { extractBasicHtmlContent, htmlToMarkdown, @@ -59,7 +59,7 @@ const WebFetchSchema = Type.Object({ }), ), maxChars: Type.Optional( - Type.Number({ + Type.Integer({ description: "Max chars returned; truncates.", minimum: 100, }), @@ -674,7 +674,7 @@ export function createWebFetchTool(options?: { const params = args as Record; const url = readStringParam(params, "url", { required: true }); const extractMode = readStringParam(params, "extractMode") === "text" ? "text" : "markdown"; - const maxChars = readNumberParam(params, "maxChars", { integer: true }); + const maxChars = readPositiveIntegerParam(params, "maxChars"); const maxCharsCap = resolveFetchMaxCharsCap(executionFetch); const result = await runWebFetch({ url, diff --git a/src/agents/tools/web-tools.fetch.test.ts b/src/agents/tools/web-tools.fetch.test.ts index f48d319b4106..09f926a24857 100644 --- a/src/agents/tools/web-tools.fetch.test.ts +++ b/src/agents/tools/web-tools.fetch.test.ts @@ -534,6 +534,23 @@ describe("web_fetch extraction fallbacks", () => { expect(details.truncated).toBe(true); }); + it("rejects fractional maxChars before fetching", async () => { + const fetchMock = installMockFetch( + (input: RequestInfo | URL) => + Promise.resolve(textResponse("unused", resolveRequestUrl(input))) as Promise, + ); + + const tool = createFetchTool({ firecrawl: { enabled: false } }); + + await expect( + tool?.execute?.("call", { + url: "https://example.com/fractional", + maxChars: 100.5, + }), + ).rejects.toThrow("maxChars must be a positive integer"); + expect(fetchMock).not.toHaveBeenCalled(); + }); + it("strips and truncates HTML from error responses", async () => { const long = "x".repeat(12_000); const html =