// Pi-Search Extension - Registers a single `search` tool. Provider (kagi or // searxng) is chosen via ~/.pi/pi-search/config.json or PI_SEARCH_PROVIDER. import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; import { Type } from "typebox"; import { ConfigError, loadConfig, resolveSettings } from "./src/config.ts"; import { searchKagi } from "./src/providers/kagi.ts"; import { searchSearxng } from "./src/providers/searxng.ts"; import { SearchError, type SearchResult } from "./src/types.ts"; function formatResults(results: SearchResult[]): string { if (results.length === 0) return "(no results)"; return results .map((r) => `## [${r.title}](${r.url})\n> ${r.description}`) .join("\n\n"); } async function runSearch(query: string): Promise { const config = loadConfig(); const settings = resolveSettings(config); switch (settings.provider) { case "kagi": return searchKagi({ query, token: settings.kagiToken ?? "" }); case "searxng": return searchSearxng({ query, baseUrl: settings.searxngBaseUrl ?? "" }); } } export default function (pi: ExtensionAPI) { pi.registerTool({ name: "search", label: "Web Search", description: "Search the web. Returns a markdown list of titles, URLs, and snippets.", promptSnippet: "Search the web for the given query", parameters: Type.Object({ query: Type.String({ description: "Search query text" }), }), async execute(_toolCallId, params) { try { const results = await runSearch(params.query); return { content: [{ type: "text", text: formatResults(results) }], details: { raw: results }, }; } catch (err) { if (err instanceof ConfigError || err instanceof SearchError) { return { content: [{ type: "text", text: `Search error: ${err.message}` }], isError: true, details: { raw: [] as SearchResult[] }, }; } throw err; } }, }); }