deer-flow/docs/superpowers/plans/2026-06-29-community-browserless-web-capture.md
Ryker_Feng a8f950feb6
feat(community): add Browserless web_capture screenshot tool (#3881)
* feat(community): add Browserless web_capture screenshot tool

Add a web_capture tool that renders a page via Browserless /screenshot and
presents it through the artifact system, alongside the existing Browserless
web_fetch provider.

Hardening:
- SSRF guard: reject URLs resolving to private/loopback/link-local (incl. the
  169.254.169.254 cloud-metadata endpoint)/reserved/multicast/unspecified
  addresses; opt out via allow_private_addresses for internal targets.
- Surface a warning when Browserless renders a target page that itself
  responded with a non-2xx/3xx status (X-Response-Code), so an error/anti-bot
  page is not mistaken for valid visual evidence.
- Dedupe colliding output filenames instead of silently overwriting prior
  captures.

Docs: comment out token: $BROWSERLESS_TOKEN in tool examples (an unset $VAR
fails AppConfig startup) and document allow_private_addresses.

* fix(community): format web_capture guard + document local Browserless startup

Address PR #3881 review: fix the lint-backend failure (ruff format on
browserless/tools.py) and add local Browserless startup instructions to
CONFIGURATION.md so reviewers can run the service to try web_fetch/web_capture.
2026-07-01 23:41:58 +08:00

9.3 KiB

Community Browserless Web Capture Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:test-driven-development for code changes. Commit and push steps are intentionally deferred because this local branch must stay uncommitted until user review.

Goal: Add an opt-in community web_capture tool backed by Browserless /screenshot that renders a URL in headless Chrome, saves a PNG/JPEG/WebP screenshot into the current thread outputs directory, and exposes the file as a DeerFlow artifact path.

Architecture: Keep the integration inside the existing deerflow.community.browserless package. BrowserlessClient owns HTTP request construction and binary response handling; tools.py owns DeerFlow runtime/config resolution, output filename generation, file persistence, and artifact-state updates. The feature uses existing thread-data outputs semantics rather than inventing a new artifact channel.

Tech Stack: Python 3.12, LangChain tool decorators, LangGraph Command, httpx.AsyncClient, Browserless REST /screenshot, pytest.


Technical Design

User-Facing Tool Contract

Add a new configured tool:

tools:
  - name: web_capture
    group: web
    use: deerflow.community.browserless.tools:web_capture_tool
    base_url: http://localhost:3032
    timeout_s: 30
    output_format: png
    full_page: true
    viewport_width: 1280
    viewport_height: 720
    # token: $BROWSERLESS_TOKEN
    # wait_for_selector: main
    # wait_for_selector_timeout_ms: 5000
    # wait_for_timeout_ms: 1000
    # best_attempt: true

The model-callable arguments should stay narrow:

async def web_capture_tool(
    runtime: Runtime,
    url: str,
    tool_call_id: Annotated[str, InjectedToolCallId],
    filename: str | None = None,
    full_page: bool | None = None,
    output_format: str | None = None,
    viewport_width: int | None = None,
    viewport_height: int | None = None,
) -> Command:

Behavior:

  • Accept only explicit http:// or https:// URLs.
  • Save the screenshot under runtime.state["thread_data"]["outputs_path"].
  • Return a Command that appends the virtual artifact path /mnt/user-data/outputs/<filename> and a short ToolMessage.
  • Use tool config defaults when optional callable args are omitted.
  • Read Browserless credentials from the configured token value or the BROWSERLESS_TOKEN environment variable.

Browserless API Mapping

Official Browserless docs describe POST /screenshot with JSON body:

{
  "url": "https://example.com/",
  "options": {
    "fullPage": true,
    "type": "png"
  }
}

Browserless returns binary image content with Content-Type: image/png, image/jpeg, or image/webp depending on options.type. Shared request fields such as waitForSelector, waitForTimeout, bestAttempt, gotoOptions, rejectResourceTypes, and rejectRequestPattern are top-level fields.

This PR supports:

  • url
  • options.fullPage
  • options.type
  • options.quality for JPEG/WebP only when configured
  • viewport from config/callable width and height
  • waitForSelector from config
  • waitForTimeout from config
  • bestAttempt from config

This PR intentionally does not support:

  • inline html
  • addScriptTag
  • addStyleTag
  • authenticated profiles
  • arbitrary launch parameters
  • proxy parameters

Those omitted fields are useful, but exposing them directly to an agent increases the chance of unexpected side effects or credential/session leakage. They can be added later behind explicit config if maintainers want them.

File and Artifact Boundary

The tool writes to the host-side outputs path that ThreadDataMiddleware provides, with filesystem writes offloaded through asyncio.to_thread:

thread_data = runtime.state.get("thread_data") or {}
outputs_path = thread_data.get("outputs_path")

The final user-facing artifact path is always:

/mnt/user-data/outputs/<safe-filename>.<ext>

Filename handling:

  • If filename is provided, strip directories and normalize it to a safe stem.
  • If omitted, derive a readable stem from the URL host/path and append a UTC timestamp.
  • Enforce extension from output_format.
  • Use only ASCII letters, digits, dot, dash, and underscore in the final basename.

Error Handling

BrowserlessClient.capture_screenshot() sends token as a query parameter, matching the current Browserless /screenshot documentation, and returns a small result object instead of raising expected API/network errors:

@dataclass(frozen=True)
class BrowserlessScreenshotResult:
    content: bytes
    content_type: str
    target_status_code: str
    target_status: str
    final_url: str

Expected failures return strings beginning with Error: from the client, matching existing fetch_html() behavior:

  • non-200 Browserless response
  • empty image response
  • timeout
  • request error

The tool converts any error string into a ToolMessage and does not mutate artifacts. Unexpected exceptions are logged and returned as Error: ....

Tests

Use TDD with backend/tests/test_browserless_client.py.

Client tests:

  • capture_screenshot posts to /screenshot with url, options.fullPage, options.type, viewport, and wait fields.
  • Token is sent as the Browserless token query parameter.
  • Non-200 status returns an error string with status and response snippet.
  • Empty binary content returns a clear error.

Tool tests:

  • Successful web_capture_tool writes bytes to outputs_path and returns artifact path in Command.update["artifacts"].
  • Tool reads web_capture config block, not web_fetch.
  • Invalid URL is rejected before calling Browserless.
  • Missing runtime thread outputs returns a clear error and writes no file.
  • Unsafe filename is sanitized to a basename under outputs.

Documentation Updates

Update:

  • config.example.yaml: add commented web_capture Browserless block.
  • backend/docs/CONFIGURATION.md: include web_capture in tool list and mention Browserless.
  • frontend/src/content/en/harness/tools.mdx: add Browserless tab for web fetch and a web capture section.
  • frontend/src/content/zh/harness/tools.mdx: mirror the English documentation.
  • scripts/doctor.py: recognize web_capture and browserless token/config status.
  • backend/packages/harness/deerflow/community/browserless/__init__.py: export web_capture_tool.

No README.md update is planned because this is a provider-specific opt-in community tool, and existing provider additions in this area document through config/docs pages.

Execution Tasks

Task 1: Write Failing Client Tests

Files:

  • Modify: backend/tests/test_browserless_client.py

Steps:

  • Add tests for BrowserlessClient.capture_screenshot().
  • Run cd backend && uv run pytest tests/test_browserless_client.py::TestBrowserlessClient -q.
  • Expected red state: AttributeError: 'BrowserlessClient' object has no attribute 'capture_screenshot'.

Task 2: Implement Browserless Screenshot Client

Files:

  • Modify: backend/packages/harness/deerflow/community/browserless/browserless_client.py
  • Modify: backend/tests/test_browserless_client.py

Steps:

  • Add BrowserlessScreenshotResult.
  • Add capture_screenshot() using httpx.AsyncClient.post(f"{base_url}/screenshot", ...).
  • Preserve existing fetch_html() behavior.
  • Run client tests until green.

Task 3: Write Failing Tool Tests

Files:

  • Modify: backend/tests/test_browserless_client.py

Steps:

  • Add tests for web_capture_tool.
  • Use a fake runtime with state={"thread_data": {"outputs_path": str(tmp_path)}}.
  • Patch _get_browserless_client() and _get_tool_config().
  • Run the new tool tests.
  • Expected red state: AttributeError or missing web_capture_tool.

Task 4: Implement web_capture_tool

Files:

  • Modify: backend/packages/harness/deerflow/community/browserless/tools.py
  • Modify: backend/packages/harness/deerflow/community/browserless/__init__.py

Steps:

  • Add helpers for config lookup, URL validation, format validation, filename sanitization, and virtual artifact path generation.
  • Add @tool("web_capture", parse_docstring=True).
  • Write the screenshot bytes to outputs.
  • Return Command(update={"artifacts": [virtual_path], "messages": [ToolMessage(...)]}).
  • Run Browserless tests until green.

Task 5: Update Config, Doctor, and Docs

Files:

  • Modify: config.example.yaml
  • Modify: scripts/doctor.py
  • Modify: backend/docs/CONFIGURATION.md
  • Modify: frontend/src/content/en/harness/tools.mdx
  • Modify: frontend/src/content/zh/harness/tools.mdx

Steps:

  • Add commented config block for web_capture.
  • Extend doctor provider map.
  • Update docs with concise config snippets.
  • Run targeted tests and lint/format checks.

Task 6: Verification and Self-Review

Run:

cd backend && uv run pytest tests/test_browserless_client.py -q
cd backend && uv run ruff check packages/harness/deerflow/community/browserless tests/test_browserless_client.py
cd backend && uv run ruff format --check packages/harness/deerflow/community/browserless tests/test_browserless_client.py

Manual review checklist:

  • No tracked changes outside the scoped files above.
  • No commit created.
  • web_capture reads the web_capture config block.
  • Tool does not expose inline HTML, script injection, style injection, or Browserless profile.
  • Tool writes only under current thread outputs path.
  • Errors do not mutate artifacts.