{"version":3,"file":"firecrawl.mjs","names":[],"sources":["../../src/providers/firecrawl.ts"],"sourcesContent":["import type {\n  SearchResult,\n  SearchRequestOptions,\n  SearchResponse,\n  ReadResult,\n  ReadOptions,\n  ProviderConfig,\n} from \"../core/types.ts\";\nimport { Provider } from \"../core/provider.ts\";\nimport { FIRECRAWL_MAX_RESULTS } from \"../core/providers.ts\";\nimport { AuthError, WebError, normalizeError } from \"../core/errors.ts\";\n\ninterface FirecrawlSearchResult {\n  readonly title: string;\n  readonly url: string;\n  readonly markdown?: string;\n}\n\ninterface FirecrawlWebResult extends FirecrawlSearchResult {\n  readonly description: string;\n}\n\ninterface FirecrawlNewsResult extends FirecrawlSearchResult {\n  readonly snippet: string;\n  readonly date?: string;\n  readonly imageUrl?: string;\n}\n\ninterface FirecrawlImageResult {\n  readonly title: string;\n  readonly url: string;\n  readonly imageUrl: string;\n  readonly imageWidth?: number;\n  readonly imageHeight?: number;\n}\n\ninterface FirecrawlSearchResponse {\n  readonly success: boolean;\n  readonly data?: {\n    readonly web?: readonly FirecrawlWebResult[];\n    readonly news?: readonly FirecrawlNewsResult[];\n    readonly images?: readonly FirecrawlImageResult[];\n  };\n  readonly id?: string;\n  readonly warning?: string | null;\n  readonly creditsUsed?: number;\n}\n\ninterface FirecrawlScrapeResponse {\n  readonly success: boolean;\n  readonly data?: {\n    readonly markdown?: string;\n    readonly html?: string;\n    readonly metadata?: {\n      readonly title?: string;\n      readonly description?: string;\n      readonly sourceURL?: string;\n      readonly language?: string;\n      readonly keywords?: string;\n      readonly ogImage?: string;\n      readonly [key: string]: unknown;\n    };\n    readonly links?: readonly string[];\n    readonly warning?: string;\n  };\n}\n\nfunction clampMaxResults(max?: number): number {\n  return Math.min(Math.max(max ?? 10, 1), FIRECRAWL_MAX_RESULTS);\n}\n\nexport class FirecrawlProvider extends Provider {\n  static readonly providerName = \"firecrawl\";\n  static readonly defaultBaseURL = \"https://api.firecrawl.dev\";\n\n  private readonly apiKey: string;\n\n  constructor(config: Readonly<ProviderConfig>) {\n    super(config, FirecrawlProvider);\n    if (!config.apiKey) {\n      throw new AuthError(\"Missing API key for Firecrawl. Set FIRECRAWL_API_KEY\", \"firecrawl\");\n    }\n\n    this.apiKey = config.apiKey;\n  }\n\n  private authHeaders(): Record<string, string> {\n    return { Authorization: `Bearer ${this.apiKey}` };\n  }\n\n  async search(query: string, options?: SearchRequestOptions): Promise<SearchResult[]> {\n    const response = await this.searchDetailed(query, options);\n    return response.results;\n  }\n\n  async searchDetailed(query: string, options?: SearchRequestOptions): Promise<SearchResponse> {\n    try {\n      const response = await this.client.postJSON<FirecrawlSearchResponse>(\n        `${this.baseURL}/v2/search`,\n        searchBody(query, options),\n        this.authHeaders(),\n        options?.signal,\n      );\n      return mapSearchResponse(response, options?.maxResults);\n    } catch (error) {\n      throw normalizeError(error, \"firecrawl\");\n    }\n  }\n\n  async read(url: string, options?: Readonly<ReadOptions>): Promise<ReadResult> {\n    if (options?.maxTokens !== undefined) {\n      throw new WebError(\"Firecrawl does not support the maxTokens read option\");\n    }\n\n    try {\n      const response = await this.client.postJSON<FirecrawlScrapeResponse>(\n        `${this.baseURL}/v2/scrape`,\n        scrapeBody(url, options),\n        this.authHeaders(),\n        options?.signal,\n      );\n      return mapScrapeResponse(url, response);\n    } catch (error) {\n      throw normalizeError(error, \"firecrawl\");\n    }\n  }\n}\n\nfunction searchBody(query: string, options?: SearchRequestOptions): Record<string, unknown> {\n  validateCategoryCombination(options?.categories);\n  return {\n    query,\n    limit: clampMaxResults(options?.maxResults),\n    highlights: options?.highlights ?? true,\n    ...domainFilters(options),\n    ...sourceAndCategoryFilters(options),\n  };\n}\n\nfunction domainFilters(options?: SearchRequestOptions): Record<string, unknown> {\n  return {\n    ...(options?.includeDomains?.length ? { includeDomains: options.includeDomains } : {}),\n    ...(options?.excludeDomains?.length ? { excludeDomains: options.excludeDomains } : {}),\n  };\n}\n\nfunction sourceAndCategoryFilters(options?: SearchRequestOptions): Record<string, unknown> {\n  return {\n    ...(options?.sources?.length ? { sources: options.sources } : {}),\n    ...(options?.categories?.length ? { categories: options.categories } : {}),\n  };\n}\n\nfunction validateCategoryCombination(categories?: readonly string[]): void {\n  if (\n    categories?.includes(\"developer\") &&\n    categories.some((category) => category !== \"developer\")\n  ) {\n    throw new WebError('Firecrawl category \"developer\" cannot be combined with other categories');\n  }\n}\n\nfunction mapSearchResponse(\n  response: Readonly<FirecrawlSearchResponse>,\n  maxResults?: number,\n): SearchResponse {\n  if (!response.success) throw new Error(\"Firecrawl search failed\");\n\n  const web = response.data?.web ?? [];\n  const news = response.data?.news ?? [];\n  const images = response.data?.images ?? [];\n  const metadata = searchMetadata(response);\n  return {\n    results: [\n      ...web.map(mapWebResult),\n      ...news.map(mapNewsResult),\n      ...images.map(mapImageResult),\n    ].slice(0, clampMaxResults(maxResults)),\n    ...(Object.keys(metadata).length === 0 ? {} : { metadata }),\n  };\n}\n\nfunction searchMetadata(response: Readonly<FirecrawlSearchResponse>): Record<string, unknown> {\n  return {\n    ...(response.id === undefined ? {} : { id: response.id }),\n    ...(response.warning === undefined || response.warning === null\n      ? {}\n      : { warning: response.warning }),\n    ...(response.creditsUsed === undefined ? {} : { creditsUsed: response.creditsUsed }),\n  };\n}\n\nfunction scrapeBody(url: string, options?: Readonly<ReadOptions>): Record<string, unknown> {\n  return {\n    url,\n    formats: [normalizeFormat(options?.format)],\n    onlyMainContent: true,\n    ...selectorFilters(options),\n    ...(options?.timeout ? { timeout: options.timeout * 1000 } : {}),\n    ...(options?.noCache ? { maxAge: 0 } : {}),\n  };\n}\n\nfunction selectorFilters(options?: Readonly<ReadOptions>): Record<string, unknown> {\n  return {\n    ...(options?.targetSelector ? { includeTags: [options.targetSelector] } : {}),\n    ...(options?.removeSelector ? { excludeTags: [options.removeSelector] } : {}),\n  };\n}\n\nfunction mapScrapeResponse(url: string, response: Readonly<FirecrawlScrapeResponse>): ReadResult {\n  if (!response.success) throw new Error(\"Firecrawl scrape failed\");\n\n  const data = response.data ?? {};\n  return {\n    url,\n    title: data.metadata?.title,\n    description: data.metadata?.description,\n    content: data.markdown ?? data.html ?? \"\",\n    html: data.html,\n    links: data.links ? [...data.links] : undefined,\n    image: data.metadata?.ogImage,\n    metadata: data.metadata,\n  };\n}\n\nfunction normalizeFormat(format?: string): \"markdown\" | \"html\" {\n  if (format === \"html\") return \"html\";\n  return \"markdown\";\n}\n\nfunction mapWebResult(result: FirecrawlWebResult): SearchResult {\n  return {\n    url: result.url,\n    title: result.title,\n    snippet: result.description,\n    text: result.markdown,\n  };\n}\n\nfunction mapNewsResult(result: FirecrawlNewsResult): SearchResult {\n  return {\n    url: result.url,\n    title: result.title,\n    snippet: result.snippet,\n    publishedDate: result.date,\n    image: result.imageUrl,\n    text: result.markdown,\n  };\n}\n\nfunction mapImageResult(result: FirecrawlImageResult): SearchResult {\n  return {\n    url: result.url,\n    title: result.title,\n    snippet: \"\",\n    image: result.imageUrl,\n    metadata: { imageWidth: result.imageWidth, imageHeight: result.imageHeight },\n  };\n}\n"],"mappings":"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"}