From 86603f25508206dd655d03ca661aa45a72833c1a Mon Sep 17 00:00:00 2001 From: iiitiansaurabh <79242161+genius-0963@users.noreply.github.com> Date: Sat, 13 Sep 2025 17:33:11 +0530 Subject: [PATCH] fix: support both string and object formats for sources parameter - Updated firecrawl_search tool schema to accept both string format (["web"]) and object format ([{"type": "web"}]) - Added normalization logic to convert string formats to object format internally - Updated documentation to show both supported formats - Fixes HTTP 422 error when using string format for sources parameter - Maintains backward compatibility with existing implementations Resolves issue where requests with sources: ["web"] would fail with: HTTP error 422: {"detail":[{"type":"model_attributes_type","loc":["body","sources",0],"msg":"Input should be a valid dictionary or object to extract fields from","input":"web"}]} --- src/index.ts | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index 9dbc713..babe638 100644 --- a/src/index.ts +++ b/src/index.ts @@ -298,7 +298,7 @@ Search the web and optionally extract content from search results. This is the m **Not recommended for:** When you need to search the filesystem. When you already know which website to scrape (use scrape); when you need comprehensive coverage of a single website (use map or crawl. **Common mistakes:** Using crawl or map for open-ended questions (use search instead). **Prompt Example:** "Find the latest research papers on AI published in 2023." -**Sources:** web, images, news, default to web unless needed images or news. +**Sources:** web, images, news, default to web unless needed images or news. Can be provided as strings (e.g., ["web"]) or objects (e.g., [{"type": "web"}]). **Scrape Options:** Only use scrapeOptions when you think it is absolutely necessary. When you do so default to a lower limit to avoid timeouts, 5 or lower. **Usage Example without formats:** \`\`\`json @@ -334,6 +334,16 @@ Search the web and optionally extract content from search results. This is the m } } \`\`\` +**Alternative sources format (also supported):** +\`\`\`json +{ + "sources": [ + {"type": "web"}, + {"type": "images"}, + {"type": "news"} + ] +} +\`\`\` **Returns:** Array of search results (with optional scraped content). `, parameters: z.object({ @@ -343,7 +353,12 @@ Search the web and optionally extract content from search results. This is the m filter: z.string().optional(), location: z.string().optional(), sources: z - .array(z.object({ type: z.enum(['web', 'images', 'news']) })) + .array( + z.union([ + z.enum(['web', 'images', 'news']), + z.object({ type: z.enum(['web', 'images', 'news']) }) + ]) + ) .optional(), scrapeOptions: scrapeParamsSchema.omit({ url: true }).partial().optional(), }), @@ -352,8 +367,21 @@ Search the web and optionally extract content from search results. This is the m { session, log }: { session?: SessionData; log: Logger } ): Promise => { const client = getClient(session); - const { query, ...opts } = args as Record; - const cleaned = removeEmptyTopLevel(opts as Record); + const { query, sources, ...opts } = args as Record; + + // Normalize sources to object format if they are strings + let normalizedSources = sources; + if (Array.isArray(sources)) { + normalizedSources = sources.map(source => + typeof source === 'string' ? { type: source } : source + ); + } + + const cleaned = removeEmptyTopLevel({ + ...opts, + ...(normalizedSources ? { sources: normalizedSources } : {}) + } as Record); + log.info('Searching', { query: String(query) }); const res = await client.search(query as string, { ...(cleaned as any),