Skip to content

feat(web): firecrawl provider with crawl4ai fallback - #168

Open
ChronodeAi wants to merge 2 commits into
jordan-gibbs:mainfrom
ChronodeAi:feat/firecrawl-provider
Open

ChronodeAi wants to merge 2 commits into
jordan-gibbs:mainfrom
ChronodeAi:feat/firecrawl-provider

Conversation

@ChronodeAi

@ChronodeAi ChronodeAi commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Adds an opt-in firecrawl web provider ([web] provider = "firecrawl"). It uses the httpx dependency the core already has, so there is no new dependency. Defaults and every existing provider are unchanged.

What it does

  • fetch calls Firecrawl v2 /scrape and returns markdown, rawHtml (for DOI extraction), images (as media) and the screenshot, which it downloads through safe_get.
  • fetch_many submits one /batch/scrape job, polls it until it finishes, and follows next pages.
  • search calls /search with scrapeOptions set, so each result carries its page's markdown.
  • Without FIRECRAWL_API_KEY, fetch and search use Firecrawl's keyless tier, and batches go one URL at a time.

Fallback to crawl4ai. The crawl4ai fallback is built with the vault's profile, magic and settings. Firecrawl cannot use a local login profile, so the following go to crawl4ai:

  • the visible-browser lane
  • allow_private_hosts hosts, which are never sent to Firecrawl
  • Firecrawl API errors (402, 429, network)
  • pages where the target answered 401/403/407/429/5xx, or that look like a login wall or bot-detection page

A 404/410 is raised and not retried. If the fallback also fails on a page Firecrawl blocked on, the Firecrawl result is returned, so the existing junk gate and escalation queue still run.

Invariants kept

Provenance. fetch_provider now comes from served_by(prov, result). That is the name of the provider that actually served the page (crawl4ai for a handed-off URL). For every other provider it equals prov.name, as before.

Tests. 18 offline tests in tests/test_web/test_firecrawl_provider.py use httpx.MockTransport, a fake fallback provider and stubbed DNS. The full suite passes (1737). A live smoke test against the keyless tier covered fetch (Wikipedia: 92k chars plus screenshot), search, and fetch-batch. In the batch, a 403 URL was handed to crawl4ai, matching crawl4ai's own batch behaviour.

Setup wizard. hyperresearch setup step 1 is now a menu listing crawl4ai, firecrawl and builtin. A vault on any other provider also gets that provider as an option, so re-running setup doesn't replace exa or tavily without asking. Choosing firecrawl reports whether FIRECRAWL_API_KEY is set. It also keeps the browser-profile step, stealth and the Chromium install, because the crawl4ai fallback uses them. Re-running setup defaults to the vault's current provider. Covered by 4 wizard tests with scripted prompts.

Opt-in [web] provider = "firecrawl" (FIRECRAWL_API_KEY; keyless tier without it). Fetch, batch fetch via one Firecrawl batch job, and search. crawl4ai serves the visible-browser lane, allow_private_hosts hosts, Firecrawl API errors, and pages Firecrawl was blocked on. PDFs use the shared PDF lane first. skipTlsVerification follows browser_verify_tls. fetch_provider now records the provider that served the page.
Step 1 is now a menu: crawl4ai, firecrawl, builtin (plus the vault's current provider if it is none of those). Choosing firecrawl reports whether FIRECRAWL_API_KEY is set and keeps the browser-profile step, stealth and Chromium install for the crawl4ai fallback. Re-running setup defaults to the vault's current provider.

@jordan-gibbs jordan-gibbs left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this. The provider code looks good with no new dependency, sensible batch scrape use, and rawHtml for DOI extraction. Could you split it so it's easier to take?

  1. Keep the provider on its own without the setup-wizard rewrite in cli/setup.py, since that lists Firecrawl as option 2 for every user and steers people toward one vendor.
  2. Put the served_by provenance in its own small PR since that's useful on its own.
  3. Make the keyless tier an explicit opt-in. Sending URLs to Firecrawl shouldn't happen just because the provider is selected without a key.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants