/** * Search engine backends for web-search-pro. Each engine is a plain object * with { id, label, available(), search(query, count, signal) }. Routing, * caching, and persistence live in router.ts. * @module web-search-pro/engines */ import type { WebSearchSource, WebRuntime } from '@deepseek-ai/dsh-web'; import { runCli } from './util.ts'; import type { BrowserService } from './browser-service.ts'; import { type BrowserMethod } from './browser-access.ts'; import type { CustomPlatformSpec } from './config.ts'; import { type ExaSearchRequest } from './exa-client.ts'; import { EngineError } from './engine-error.ts'; export interface SearchOutcome { /** Provider-generated answer/summary text, when any. */ content?: string; sources: WebSearchSource[]; /** Id of the concrete backend that answered (a platform provider with a fallback chain names the leg that ran). */ via?: string; /** The backend id of the platform chain that answered (`bili`, `browser-opencli`), for display and history. */ backend?: string; /** Short notes for the caller: backends skipped on the way, results dropped. */ notes?: string[]; } export interface Engine { id: string; label: string; /** Backend id in a platform chain (`xhs`, `browser-opencli`); absent for plain engines. */ backend?: string; /** Cheap local availability check; must not do network I/O. */ available(): boolean; /** Browser-service method this engine depends on (dsh-browser is optional). */ needsBrowser?: BrowserMethod; search(query: string, count: number, signal?: AbortSignal, options?: EngineSearchOptions): Promise; } export interface EngineSearchOptions { exa?: Omit; /** Bocha native request fields compiled from hard constraints (pipeline/compile.ts). */ bocha?: { freshness?: string; include?: string[]; exclude?: string[]; }; /** Lower bound of the publication date (ISO 8601), compiled from a hard time_window; the engine maps it to its native filter. */ since?: string; /** Domain lists compiled from hard site / exclude_site constraints (keyed sources that take them as request fields). */ sites?: { include?: string[]; exclude?: string[]; }; /** Task language (`zh` / `en`) for engines with per-language editions or zones (Wikipedia, AnySearch); absent = detect from the query. */ lang?: 'zh' | 'en'; browser?: { authProfile?: string; rulePack?: string; }; /** Feed URL of the `rss` platform (it has no fixed endpoint). */ url?: string; } export { EngineError }; /** One metered request of a non-model provider (Bocha search): counted in the usage ledger, tokens n/a, price unknown. */ export interface UsageRecorder { record(entry: { provider: string; protocol: string; requests: number; note?: string; }): void; } export interface EngineDeps { web?: WebRuntime; exaApiKey?: string; jinaApiKey?: string; /** Bocha web-search key (config bochaApiKey / credentials ref / $BOCHA_SEARCH_API_KEY, then the Jev key of the same account). */ bochaApiKey?: string; /** Bocha endpoint base, default https://api.bochaai.com. */ bochaBaseUrl?: string; /** Ask Bocha for its longer per-page summary (default true). */ bochaSummary?: boolean; /** Records requests of metered non-model providers in the usage ledger (best effort, never throws). */ usage?: UsageRecorder; /** Test seam: replaces the global fetch of API clients that accept one. */ fetchImpl?: typeof fetch; /** Test seam: replaces the DNS lookup of the SSRF check. */ lookup?: (hostname: string) => Promise<{ address: string; family?: number; }[]>; /** API keys of the keyed sources by route id (`tavily`, `brave`, ...): config literal -> credentials ref -> environment (router). */ sourceKeys?: Readonly>; /** Base URL overrides of the keyed sources by route id (settings `keyedSources..baseUrl`). */ sourceBaseUrls?: Readonly>; /** Self-hosted SearXNG instance (settings `searxngUrl`); no default public instance exists. */ searxngUrl?: string; /** Contact address appended to the User-Agent of OpenAlex requests (settings `openalexMailto`). */ openalexMailto?: string; /** Optional free keys of the anonymous APIs (credentials / environment); they raise limits, nothing needs them. */ openalexApiKey?: string; semanticScholarApiKey?: string; anysearchApiKey?: string; /** GitHub API token (config githubToken / $GITHUB_TOKEN / $GH_TOKEN). */ githubToken?: string; enableCli: boolean; opencliEnabled: boolean; agentReachEnabled: boolean; allowProxyFakeIp: boolean; /** Browser service (dsh-browser, optional) for Playwright platform search + bundled opencli; resolved per call. */ browser?: BrowserService; /** Per-platform selector overrides (settings.yaml `platformRules`). */ platformRules?: Record; /** User-defined custom platforms (settings.yaml `customPlatforms`). */ customPlatforms?: Record; /** True when this call originates from the ctx.web provider (avoid seam recursion). */ skipSeam: boolean; } export declare function seamEngine(deps: EngineDeps): Engine; /** Parse mcporter's human-readable Exa response into the router's native source shape. */ export declare function parseMcporterExaSearch(output: string, count: number): WebSearchSource[]; export declare function exaEngine(deps: EngineDeps): Engine; /** * Parse DDG html.duckduckgo.com result HTML into sources. * * Two passes on purpose: a single regex combining the result anchor with an * *optional* snippet group behind a lazy bridge silently never captures * snippets (the optional group backtracks to an empty match before the lazy * bridge is allowed to expand). Slicing each block first, then extracting the * snippet inside the block, avoids that trap entirely. */ export declare function parseDdgHtml(html: string, count?: number): WebSearchSource[]; export declare function ddgEngine(allowProxyFakeIp?: boolean): Engine; export declare function bingEngine(allowProxyFakeIp?: boolean): Engine; /** Parse RSS/Atom XML into sources (used by bing engine and rss platform). */ export declare function parseRss(xml: string, count?: number): WebSearchSource[]; export declare function jinaSearchEngine(deps: EngineDeps): Engine; export declare function githubEngine(deps: EngineDeps): Engine; export declare function githubCodeEngine(deps: EngineDeps): Engine; export declare function githubIssuesEngine(deps: EngineDeps): Engine; export declare function bilibiliEngine(deps: EngineDeps): Engine; /** Exact argv contract supported by public-clis/bilibili-cli v0.6.2+ (the `bili` spec). */ export declare function biliSearchArgs(query: string, count: number): string[]; /** Parse and validate bili-cli's versioned JSON envelope (the `bili` spec). */ export declare function parseBilibiliSearchOutput(output: string): WebSearchSource[]; export declare function v2exEngine(allowProxyFakeIp?: boolean): Engine; export declare function youtubeEngine(deps: EngineDeps, cli?: typeof runCli): Engine; /** Platforms the dsh-browser OpenCLI bridge has a site adapter for (platform -> adapter name). */ export declare const OPENCLI_PLATFORMS: Record; export declare function opencliEngine(platform: string, deps: EngineDeps): Engine; /** * The legacy `agentreach-twitter` engine, now a thin view of the `twitter` CLI spec (the platform chain uses the spec * directly; this keeps the old engine id and its install message). Credentials are checked by `available()`. */ export declare function agentReachEngine(platform: string, deps: EngineDeps): Engine; export declare function arxivEngine(allowProxyFakeIp?: boolean): Engine; export declare function pubmedEngine(allowProxyFakeIp?: boolean): Engine; export declare function customPlatformEngine(id: string, spec: CustomPlatformSpec, deps: EngineDeps): Engine; export declare function playwrightPlatformEngine(platform: string, deps: EngineDeps): Engine; export declare function rssEngine(url: string, allowProxyFakeIp?: boolean): Engine;