Commit 442f3998599f
Changed files (4)
dots
pi
agent
extensions
search
pkgs
my
scripts
bin
dots/pi/agent/extensions/search/backends.ts
@@ -17,9 +17,52 @@ export interface SearchBackend {
isAvailable(): Promise<boolean>;
}
+/**
+ * Brave Search API backend - uses the Brave Search API with an API key.
+ * Primary backend when BRAVE_API_KEY is set.
+ * Free tier: 2,000 queries/month. See https://brave.com/search/api/
+ */
+export class BraveAPIBackend implements SearchBackend {
+ name = "Brave API";
+ private apiKey: string;
+
+ constructor(apiKey: string) {
+ this.apiKey = apiKey;
+ }
+
+ async isAvailable(): Promise<boolean> {
+ return !!this.apiKey;
+ }
+
+ async search(query: string, maxResults: number, signal?: AbortSignal): Promise<SearchResult[]> {
+ const url = `https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=${maxResults}`;
+ const response = await fetch(url, {
+ headers: {
+ "Accept": "application/json",
+ "Accept-Encoding": "gzip",
+ "X-Subscription-Token": this.apiKey,
+ },
+ signal,
+ });
+
+ if (!response.ok) {
+ throw new Error(`Brave API request failed with status ${response.status}`);
+ }
+
+ const data = await response.json();
+ const results: SearchResult[] = (data.web?.results || []).slice(0, maxResults).map((r: any) => ({
+ title: r.title || "(no title)",
+ url: r.url || "",
+ snippet: r.description || "",
+ }));
+
+ return results;
+ }
+}
+
/**
* SearXNG backend - queries a self-hosted SearXNG instance via JSON API.
- * Primary backend for unlimited private searches.
+ * Fallback backend for unlimited private searches.
*/
export class SearXNGBackend implements SearchBackend {
name = "SearXNG";
dots/pi/agent/extensions/search/index.ts
@@ -3,24 +3,27 @@
*
* Provides web search and GitHub code search tools.
* Web search uses multiple backends with automatic fallback:
- * 1. SearXNG (self-hosted, primary)
- * 2. ddgr CLI (DuckDuckGo via CLI)
- * 3. Playwright/Bing (headless Chrome browser search fallback)
+ * 1. Brave Search API (primary, requires BRAVE_API_KEY)
+ * 2. SearXNG (self-hosted fallback)
+ * 3. ddgr CLI (DuckDuckGo via CLI)
* 4. DuckDuckGo Instant Answer API (always available, limited results)
*
* Additional backends available via /search-backend:
+ * - bing: Playwright/Bing (headless Chrome browser search)
* - brave: Playwright/Brave Search (rate-limited after a few queries)
* - mojeek: Playwright/Mojeek (independent engine, no CAPTCHA)
* - ecosia: Playwright/Ecosia (Bing-powered, good results)
*
* Configuration via environment variables:
- * SEARXNG_URL - SearXNG instance URL (default: https://search.sbr.pm)
+ * BRAVE_API_KEY - Brave Search API key (enables brave-api backend)
+ * SEARXNG_URL - SearXNG instance URL (default: https://search.sbr.pm)
*/
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { Text } from "@mariozechner/pi-tui";
import { Type } from "@sinclair/typebox";
import {
+ BraveAPIBackend,
SearXNGBackend,
DuckDuckGoBackend,
DdgrBackend,
@@ -42,7 +45,10 @@ export default function (pi: ExtensionAPI) {
const extensionDir = __dirname;
const execFn = (cmd: string, args: string[], opts?: any) => pi.exec(cmd, args, opts);
+ const braveApiKey = process.env.BRAVE_API_KEY || "";
+
const allBackends: Record<string, SearchBackend> = {
+ "brave-api": new BraveAPIBackend(braveApiKey),
searxng: new SearXNGBackend(searxngUrl),
ddgr: new DdgrBackend(execFn),
bing: new PlaywrightBackend("bing", execFn, extensionDir),
@@ -55,7 +61,10 @@ export default function (pi: ExtensionAPI) {
// Active backends in priority order
// SearXNG first, then Playwright browsers (Bing most reliable, then Mojeek/Ecosia),
// then ddgr CLI, then DDG API as last resort. Brave available via /search-backend.
- let activeBackendNames = ["searxng", "bing", "mojeek", "ecosia", "ddgr", "duckduckgo"];
+ // Brave API first (fast, high quality), then SearXNG fallback, then browser-based fallbacks
+ let activeBackendNames = braveApiKey
+ ? ["brave-api", "searxng", "ddgr", "duckduckgo"]
+ : ["searxng", "bing", "mojeek", "ecosia", "ddgr", "duckduckgo"];
// Helper to get current active backends
const getActiveBackends = (): SearchBackend[] =>
pkgs/my/scripts/bin/pir
@@ -8,4 +8,5 @@ exec pass-run -q \
-e GOOGLE_CLOUD_LOCATION=redhat/google/osp/location \
-e GEMINI_API_KEY=redhat/google/osp/vdeemest-api-key \
-e SYNTHETIC_API_KEY=ai/synthetic.new/api_key \
+ -e BRAVE_API_KEY=ai/brave/api_key \
-- pi "$@"
pkgs/my/scripts/default.nix
@@ -5,7 +5,7 @@
stdenv.mkDerivation {
pname = "vde-scripts";
- version = "0.12";
+ version = "0.13";
src = ./.;