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 {
45 DEFAULT_CACHE_TTL_MINUTES ,
56 markdownToText ,
@@ -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 (
6668 response : Response ,
6769 label : 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}
7574
7675export type FirecrawlSearchParams = {
@@ -220,11 +219,9 @@ async function postFirecrawlJson<T>(
220219 const readJsonPayload = async ( ) : Promise < Record < string , unknown > | null > => {
221220 const candidate = response as Response & { clone ?: ( ) => Response } ;
222221 const jsonResponse = typeof candidate . clone === "function" ? candidate . clone ( ) : response ;
223- if ( typeof jsonResponse . json !== "function" ) {
224- return null ;
225- }
226222 try {
227- const payload = await jsonResponse . json ( ) ;
223+ const body = await readResponseText ( jsonResponse , { maxBytes : 64_000 } ) ;
224+ const payload = JSON . parse ( body . text ) as unknown ;
228225 return 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 } ,
581578 async ( 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+ } ) ;
583583 if ( payloadLocal . success === false ) {
584584 const detail =
585585 typeof payloadLocal . error === "string"
@@ -613,6 +613,7 @@ export const testing = {
613613 assertFirecrawlScrapeTargetAllowed,
614614 parseFirecrawlScrapePayload,
615615 postFirecrawlJson,
616+ readFirecrawlJsonResponse,
616617 resolveEndpoint,
617618 validateFirecrawlBaseUrl,
618619 resolveSearchItems,
0 commit comments