Commit ac6bd0e52616
2026-02-16 15:09:31
1 parent
fcfe418
Changed files (1)
src
pi
tools
src/pi/tools/websearch.ts
@@ -1,11 +1,15 @@
/**
- * Web Search Tool - Search the web using SearXNG or DuckDuckGo
+ * Web Search Tool - Search the web using SearXNG, ddgr, or DuckDuckGo API
*
- * Prefers SearXNG if SEARXNG_URL is set, falls back to DuckDuckGo
+ * Tries in order: SearXNG → ddgr → DuckDuckGo API
*/
import { Type } from "@sinclair/typebox";
import type { AgentTool } from "@mariozechner/pi-agent-core";
+import { exec } from "child_process";
+import { promisify } from "util";
+
+const execAsync = promisify(exec);
interface SearchResult {
title: string;
@@ -13,6 +17,52 @@ interface SearchResult {
snippet: string;
}
+/**
+ * Search using ddgr (DuckDuckGo command-line tool)
+ */
+async function searchDdgr(query: string, maxResults: number = 5): Promise<SearchResult[]> {
+ if (!query.trim()) {
+ return [];
+ }
+
+ try {
+ // Use ddgr with JSON output
+ const { stdout } = await execAsync(
+ `ddgr --json -n ${maxResults} ${JSON.stringify(query)}`,
+ { timeout: 10000 }
+ );
+
+ const results: SearchResult[] = [];
+ const lines = stdout.trim().split('\n');
+
+ for (const line of lines) {
+ if (!line.trim()) continue;
+
+ try {
+ const data = JSON.parse(line);
+ if (data.url && data.title) {
+ results.push({
+ title: data.title,
+ url: data.url,
+ snippet: data.abstract || data.title,
+ });
+ }
+ } catch (e) {
+ // Skip invalid JSON lines
+ }
+ }
+
+ return results;
+ } catch (error) {
+ // ddgr not available or failed
+ if (process.env.DANEEL_DEBUG) {
+ const msg = error instanceof Error ? error.message : String(error);
+ console.warn(`[ddgr] Unavailable (${msg})`);
+ }
+ return [];
+ }
+}
+
/**
* Search using SearXNG instance
*/
@@ -134,7 +184,7 @@ async function searchDuckDuckGo(query: string, maxResults: number = 5): Promise<
export const webSearchTool: AgentTool = {
name: "web_search",
label: "Web Search",
- description: "Search the web for current information using SearXNG (if available) or DuckDuckGo. Returns relevant web pages with titles, URLs, and snippets.",
+ description: "Search the web for current information. Uses SearXNG, ddgr, or DuckDuckGo API (in order of preference). Returns relevant web pages with titles, URLs, and snippets.",
parameters: Type.Object({
query: Type.String({ description: "The search query" }),
maxResults: Type.Optional(Type.Number({
@@ -158,10 +208,11 @@ export const webSearchTool: AgentTool = {
};
}
- // Try SearXNG first if configured
+ // Try search engines in order of preference
let results: SearchResult[] = [];
- let searchEngine = "DuckDuckGo";
+ let searchEngine = "none";
+ // 1. Try SearXNG if configured
if (process.env.SEARXNG_URL) {
results = await searchSearXNG(query, maxResults);
if (results.length > 0) {
@@ -169,18 +220,29 @@ export const webSearchTool: AgentTool = {
}
}
- // Fall back to DuckDuckGo if SearXNG failed or not configured
+ // 2. Try ddgr (DuckDuckGo CLI)
+ if (results.length === 0) {
+ results = await searchDdgr(query, maxResults);
+ if (results.length > 0) {
+ searchEngine = "ddgr";
+ }
+ }
+
+ // 3. Fall back to DuckDuckGo API (limited)
if (results.length === 0) {
results = await searchDuckDuckGo(query, maxResults);
+ if (results.length > 0) {
+ searchEngine = "DuckDuckGo API";
+ }
}
if (results.length === 0) {
let message = `No results found for: "${query}"`;
- // If SearXNG is configured but we're using DuckDuckGo, explain why
- if (process.env.SEARXNG_URL && searchEngine === "DuckDuckGo") {
- message += `\n\nNote: SearXNG (${process.env.SEARXNG_URL}) is currently unavailable. The DuckDuckGo fallback has limited coverage for general queries. Please try a more specific search or wait for SearXNG to be available.`;
- } else if (searchEngine === "DuckDuckGo") {
+ // Provide helpful context based on what failed
+ if (process.env.SEARXNG_URL && searchEngine === "none") {
+ message += `\n\nNote: All search engines (SearXNG, ddgr, DuckDuckGo API) are currently unavailable or returned no results. Please try again later or with a different query.`;
+ } else if (searchEngine === "DuckDuckGo API") {
message += `\n\nNote: The DuckDuckGo API has limited coverage for general queries. Try a more specific search term.`;
}