$ cat node-template.py

W

Web Search

// Search the public web (via Brave Search) and return a ranked list of result URLs (no snippets — deliberate, to avoid SERP-snippet hallucination). The output is a URL list; reading the content behind those URLs is a separate concern handled downstream. Pairs with: `Fetch URL as Markdown`.

Input
Integration
#web#search#brave#research
template.py
1"""2Web Search34Thin shim over the gais.web SDK namespace. Sends a query to Brave Search via5the gais-web sidecar service and returns a ranked list of URLs (no snippets).6Workflow authors typically pair this with the Fetch URL as Markdown node to7build research-agent loops.8"""910from __future__ import annotations1112import json13import sys14import traceback15from typing import Any1617from gais import Gais181920def main() -> None:21    try:22        envelope = json.loads(sys.stdin.read() or "{}")23        inputs: dict[str, Any] = (24            envelope.get("inputs", {}) if isinstance(envelope, dict) else {}25        )2627        query = inputs.get("query")28        num_results = inputs.get("num_results", 10)29        country_raw = inputs.get("country", "any")3031        if not query:32            raise ValueError("Required input 'query' not provided")3334        # Normalize the "Any" sentinel (and any legacy empty-string default)35        # to None so the SDK omits the country param. The sentinel is "any"36        # rather than "" because Radix Select.Item rejects empty-string37        # values (reserved for clearing the selection).38        country = country_raw if country_raw and country_raw != "any" else None3940        print(41            f"[web-search] query={query!r} num_results={num_results} country={country!r}",42            file=sys.stderr,43        )4445        result = Gais.web.search(46            query=query,47            num_results=num_results,48            country=country,49        )5051        urls = result.metadata.get("urls", [])52        result_count = result.metadata.get("result_count", len(urls) if isinstance(urls, list) else 0)53        rate_limited = result.metadata.get("rate_limited", False)5455        print(56            f"[web-search] done result_count={result_count} rate_limited={rate_limited}",57            file=sys.stderr,58        )5960        json.dump(61            {62                "urls": urls,63                "result_count": result_count,64                "rate_limited": rate_limited,65            },66            sys.stdout,67        )68    except Exception as e:69        error = {70            "error": str(e),71            "errorType": type(e).__name__,72            "traceback": traceback.format_exc(),73        }74        print(json.dumps(error), file=sys.stderr)75        sys.exit(1)767778if __name__ == "__main__":79    main()80

$ git log --oneline

v1.0.5
HEAD
2026-05-25
v1.0.42026-05-25
v1.0.32026-05-22
v1.0.22026-05-21
v1.0.12026-05-21
v1.0.02026-05-21