/** * Pluggable `web_search` backends + HTML→markdown extraction (T1742 · epic * T11456 · SG-TOOLS). * * The `web` toolset's network backend. TWO concerns live here, both built over * an INJECTABLE {@link HttpFetch} so unit tests mock the network and CI never * makes a real request (AC9): * * - **web_search backends** — a {@link WebSearchBackend} interface with two * keyless builtin implementations ({@link duckDuckGoBackend}, * {@link searxngBackend}). NO hard paid-API dependency: backends are resolved * at call time and the tool degrades gracefully to an `unavailable` result * when none answer (AC1). New backends register by satisfying the interface — * the search tool is backend-agnostic. * - **web_extract** — {@link htmlToMarkdown}, a dependency-free HTML→markdown * converter (AC2), plus {@link extractTitle}. * * The pure helpers (parsers, the markdown converter) are exported for direct unit * testing. Import-time side-effect-free. * * @epic T11456 * @task T1742 */ import type { WebExtractResult, WebSearchBackendId, WebSearchHit, WebSearchInput } from '@cleocode/contracts/tools/web-tools'; /** * A minimal HTTP-fetch surface the web tools depend on by INJECTION. Structurally * a subset of the global `fetch`, declared here so the executables can be driven * by a mock in tests (no real network in CI · AC9) and so a future routing of * web egress through a guarded `net` primitive is a one-line swap. */ export type HttpFetch = (url: string, init?: { readonly headers?: Record; readonly signal?: AbortSignal; }) => Promise<{ readonly status: number; text(): Promise; readonly headers: { get(name: string): string | null; }; }>; /** The default {@link HttpFetch} — the platform global `fetch`. */ export declare const defaultHttpFetch: HttpFetch; /** * One pluggable web-search backend. A backend resolves a query into ranked * {@link WebSearchHit}s, or throws (the resolver moves on to the next backend). * `isConfigured()` lets a backend that needs a URL/instance opt OUT of the `auto` * rotation when it has nothing to talk to — keeping the "graceful when * unconfigured" contract (AC1) without a network round-trip. */ export interface WebSearchBackend { /** Canonical backend id. */ readonly id: WebSearchBackendId; /** Whether this backend has everything it needs to run (e.g. an instance URL). */ isConfigured(): boolean; /** * Run the search. * * @param query - The search query. * @param maxResults - Cap on returned hits. * @param http - The injected fetch surface. * @returns Ranked hits. */ search(query: string, maxResults: number, http: HttpFetch): Promise; } /** * Parse DuckDuckGo's keyless HTML endpoint (`html.duckduckgo.com/html/`) into * structured hits. Pure helper (AC9) — exported for direct unit testing against * a captured fixture, so no network is needed. * * @param html - The DuckDuckGo HTML response body. * @param maxResults - Cap on returned hits. * @returns Parsed hits. */ export declare function parseDuckDuckGoHtml(html: string, maxResults: number): WebSearchHit[]; /** * The DuckDuckGo backend — keyless HTML endpoint, always "configured" (no * instance URL needed). The canonical no-API-key default (AC1). */ export declare const duckDuckGoBackend: WebSearchBackend; /** * Parse a SearXNG JSON response (`{ results: [{ title, url, content }] }`) into * hits. Pure helper (AC9). * * @param json - The parsed SearXNG JSON body. * @param maxResults - Cap on returned hits. * @returns Parsed hits. */ export declare function parseSearxngJson(json: unknown, maxResults: number): WebSearchHit[]; /** * Build a SearXNG backend bound to an instance URL (from `SEARXNG_URL` env or * config). When no instance URL is supplied it reports `isConfigured() === false` * so the `auto` resolver skips it — preserving the "graceful when unconfigured" * contract (AC1) with no paid dependency. * * @param instanceUrl - The SearXNG instance base URL (or `undefined`/empty). * @returns A {@link WebSearchBackend}. */ export declare function makeSearxngBackend(instanceUrl: string | undefined): WebSearchBackend; /** * Resolve the candidate backends for a {@link WebSearchInput}, in priority * order. A SearXNG instance (when configured) ranks before DuckDuckGo; an * explicit `input.backend` pins a single backend. The DuckDuckGo backend is * always present (keyless), so the list is never empty — but a pinned backend * that is not configured yields an empty list, which the executable surfaces as * `unavailable` (AC1). * * @param input - The search input. * @param searxngUrl - Optional SearXNG instance URL (config/env). * @returns The ordered candidate backends. */ export declare function resolveSearchBackends(input: WebSearchInput, searxngUrl: string | undefined): WebSearchBackend[]; /** Extract the document `` text, or `''` when absent. Pure (AC9). */ export declare function extractTitle(html: string): string; /** * Convert an HTML document to markdown. Dependency-free (AC2) — a focused, * deterministic transform of the structural tags an agent cares about * (headings, links, lists, code, emphasis, paragraphs); `<script>`/`<style>` * and remaining tags are dropped. NOT a full HTML5 parser — a pragmatic * readability-style extraction that keeps `core` free of a heavy DOM dependency. * * Pure function of its input (no I/O), so it is unit-testable in isolation (AC9). * * @param html - The HTML document / fragment. * @returns A markdown rendering of the body content. */ export declare function htmlToMarkdown(html: string): string; /** * Fetch a URL and convert its HTML body to markdown. The single executable body * for the `web_extract` tool — uses the INJECTED {@link HttpFetch} so CI mocks * the network (AC9). * * @param url - The URL to fetch. * @param http - The injected fetch surface. * @param opts - Timeout + markdown length cap. * @returns The {@link WebExtractResult}. */ export declare function fetchAndExtract(url: string, http: HttpFetch, opts?: { timeoutMs?: number; maxChars?: number; }): Promise<WebExtractResult>; /** * Run a {@link WebSearchInput} against the resolved backends, first-success-wins. * Returns the hits + the answering backend id, or `null` when no backend could * answer (every candidate failed or the list was empty) — the executable maps * `null` onto a graceful `unavailable` result (AC1). * * @param input - The search input. * @param backends - The ordered candidate backends (from {@link resolveSearchBackends}). * @param http - The injected fetch surface. * @returns The hits + backend, or `null` when none answered. */ export declare function runSearch(input: WebSearchInput, backends: readonly WebSearchBackend[], http: HttpFetch): Promise<{ results: WebSearchHit[]; backend: WebSearchBackendId; } | null>; //# sourceMappingURL=web-search-backends.d.ts.map