

















11// Firecrawl plugin module implements firecrawl client behavior.
22import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
3+import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http";
34import {
45DEFAULT_CACHE_TTL_MINUTES,
56markdownToText,
@@ -41,6 +42,7 @@ const SCRAPE_CACHE = new Map<
4142>();
4243const DEFAULT_SEARCH_COUNT = 5;
4344const DEFAULT_SCRAPE_MAX_CHARS = 50_000;
45+const FIRECRAWL_SCRAPE_RESPONSE_MAX_BYTES = 64 * 1024 * 1024;
4446const ALLOWED_FIRECRAWL_HOSTS = new Set(["api.firecrawl.dev"]);
4547const FIRECRAWL_SELF_HOSTED_PRIVATE_ERROR =
4648"Firecrawl custom baseUrl must target a private or internal self-hosted endpoint.";
@@ -65,12 +67,9 @@ type FirecrawlSearchItem = {
6567async function readFirecrawlJsonResponse(
6668response: Response,
6769label: string,
70+opts?: { maxBytes?: number },
6871): Promise<Record<string, unknown>> {
69-try {
70-return (await response.json()) as Record<string, unknown>;
71-} catch (cause) {
72-throw new Error(`${label}: malformed JSON response`, { cause });
73-}
72+return await readProviderJsonResponse<Record<string, unknown>>(response, label, opts);
7473}
75747675export type FirecrawlSearchParams = {
@@ -220,11 +219,9 @@ async function postFirecrawlJson<T>(
220219const readJsonPayload = async (): Promise<Record<string, unknown> | null> => {
221220const candidate = response as Response & { clone?: () => Response };
222221const jsonResponse = typeof candidate.clone === "function" ? candidate.clone() : response;
223-if (typeof jsonResponse.json !== "function") {
224-return null;
225-}
226222try {
227-const payload = await jsonResponse.json();
223+const body = await readResponseText(jsonResponse, { maxBytes: 64_000 });
224+const payload = JSON.parse(body.text) as unknown;
228225return payload && typeof payload === "object" && !Array.isArray(payload)
229226 ? (payload as Record<string, unknown>)
230227 : null;
@@ -579,7 +576,10 @@ export async function runFirecrawlScrape(
579576},
580577},
581578async (response) => {
582-const payloadLocal = await readFirecrawlJsonResponse(response, "Firecrawl fetch failed");
579+const payloadLocal = await readFirecrawlJsonResponse(response, "Firecrawl fetch failed", {
580+// Scrape can legitimately return page bodies before maxChars truncates parsed output.
581+maxBytes: FIRECRAWL_SCRAPE_RESPONSE_MAX_BYTES,
582+});
583583if (payloadLocal.success === false) {
584584const detail =
585585typeof payloadLocal.error === "string"
@@ -613,6 +613,7 @@ export const testing = {
613613 assertFirecrawlScrapeTargetAllowed,
614614 parseFirecrawlScrapePayload,
615615 postFirecrawlJson,
616+ readFirecrawlJsonResponse,
616617 resolveEndpoint,
617618 validateFirecrawlBaseUrl,
618619 resolveSearchItems,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。