/** * `web_search` — keyless-by-default web search tool for the Agent. * * Companion to `web_fetch` (see web-fetch.ts): where `web_fetch` retrieves a * *known* URL, `web_search` *discovers* URLs from a query — "search the web", * "find the official docs for X", "look up this error message". * * Design (mirrors web-fetch.ts): * - Zero hard dependency beyond global `fetch` (Node 18+). * - Pluggable backend: keyless **Bing** by default (reachable without a * proxy in regions where DuckDuckGo is not, e.g. mainland China); * keyless **DuckDuckGo** as fallback or by explicit `provider` choice; * **Brave** when an API key is supplied; or a host-injected `search` * function (e.g. a multi-engine backplane). Tool name + `query` input * stay stable so consumers work regardless of backend. * - Safe-by-default: per-call timeout, result cap, fixed provider host * (the model's query is URL-encoded into a constant host — no SSRF surface). * - Returns a compact, source-linked result list for the LLM to act on * (typically followed by a `web_fetch` on the most relevant result). * - **Reliability note**: Keyless backends (Bing, DuckDuckGo HTML/Lite) are * increasingly blocked by anti-bot measures. For reliable search, configure * an API key: **BOCHA_API_KEY** for mainland China, or **BRAVE_API_KEY** * for international access. Without an API key, search may fail if the * backend is blocked; set `fallback: false` in options to fail fast when * the primary backend is unavailable. * * Intentionally **not**: * - A crawler or browser — follow up with `web_fetch` to read a result. * - A ranking engine — it returns the provider's order verbatim. */ import type { Tool } from '../core/tools/tool-types.js'; import { type BrowserSearchPageSnapshot } from './browser-tools.js'; export interface WebSearchResult { title: string; url: string; snippet: string; date?: string; /** Structured provenance used to guide safe, efficient follow-up behavior. */ resultKind?: 'web' | 'rss-news'; /** Publisher/feed name when the backend can identify it. */ sourceName?: string; /** Publisher homepage or canonical source URL when supplied by the feed. */ sourceUrl?: string; } /** Anonymous Exa hosted MCP backend. The hosted service supplies a bounded * fallback key for the basic search/fetch tools, so no user API key is needed. * It is used only for fresh-news evidence and always remains optional. */ export declare function createAnonymousExaMcpSearch(): WebSearchBackend; export interface WebSearchBackendOptions { maxResults: number; timeoutMs: number; signal?: AbortSignal; region?: string; userAgent: string; recency?: 'day' | 'week' | 'month' | 'year'; } /** A pluggable search backend. Receives the raw query, returns ranked results. */ export type WebSearchBackend = (query: string, opts: WebSearchBackendOptions) => Promise; /** * Bounded retry policy for transient/recoverable backend failures * (rate-limit, timeout, upstream/anti-bot). Each backend in the fallback chain * is retried independently before the chain moves on to the next backend. * @beta */ export interface WebSearchRetryOptions { /** Max attempts per backend (≥1). Default 2 (i.e. 1 retry). */ maxAttempts?: number; /** Base backoff delay in ms; grows exponentially with jitter, capped. Default 400. */ baseDelayMs?: number; /** * Injectable sleep, primarily for tests. Must reject (or resolve fast) when * `signal` aborts. Default: an abort-aware `setTimeout`. */ sleep?: (ms: number, signal?: AbortSignal) => Promise; } export interface WebSearchOptions { /** * Custom backend. Takes precedence over `provider`. Use this to route to a * proprietary search API or a multi-engine backplane. When set, the keyless * fallback chain is bypassed entirely (the host owns routing). */ search?: WebSearchBackend; /** Built-in provider when `search` is not supplied. Default: `bing`. */ provider?: 'bing' | 'duckduckgo' | 'brave' | 'bocha' | 'exa'; /** API key for providers that need one (brave). Falls back to `BRAVE_API_KEY`. */ apiKey?: string; /** API key for the Bocha search backend. Falls back to `BOCHA_API_KEY`. */ bochaApiKey?: string; /** API key for the Exa search backend. Falls back to `EXA_API_KEY`. */ exaApiKey?: string; /** Default max results (capped at 20). Default 8. */ maxResults?: number; /** Per-call timeout in ms. Default 15 000. */ timeoutMs?: number; /** Region / locale hint, e.g. `zh-CN` (Bing `mkt` / Brave) or `wt-wt` (DDG). */ region?: string; /** * Recency filter: restrict results to the given time range. * Passed to keyless backends (Bing, DDG, Baidu) as their native filter parameter. */ recency?: 'day' | 'week' | 'month' | 'year'; /** Custom User-Agent. */ userAgent?: string; /** * Per-backend retry-with-backoff for recoverable failures. Default 2 attempts. * @beta */ retry?: WebSearchRetryOptions; /** * Keyless provider fallback chain. When true (default), a blocked/failed * primary backend falls through to the next available keyless endpoint * (Bing → DuckDuckGo HTML → DuckDuckGo Lite; Brave is prepended automatically * when an API key is present). Set false to use only the single resolved * backend. Ignored when a custom `search` backend is supplied. * @beta */ fallback?: boolean; /** Browser-backed read-only fallback for search pages. Set false to disable. */ browserSearch?: WebSearchBackend | false; } export interface BrowserSearchBackendOptions { browse?: (url: string, opts: WebSearchBackendOptions) => Promise; } export declare function buildSearchQueryVariants(query: string): string[]; export declare function createBrowserSearchBackend(options?: BrowserSearchBackendOptions): WebSearchBackend; /** Keyless DuckDuckGo HTML-endpoint backend. */ export declare function duckDuckGoSearch(query: string, opts: WebSearchBackendOptions): Promise; /** * Given DuckDuckGo's HTML response body that yielded zero parsed results, decide * whether the backend is blocked/broken (anti-bot/anomaly page, or no result markup * at all) vs a genuinely empty result set. Exported for testing. */ export declare function duckDuckGoResponseLooksBlocked(text: string): boolean; /** * Keyless DuckDuckGo **Lite**-endpoint backend. The Lite surface (a minimal * table-based page) frequently succeeds when the main html endpoint serves an * anti-bot/anomaly page, so it serves as the keyless fallback for * {@link duckDuckGoSearch}. Same redirect-unwrapping and blocked-page detection. */ export declare function duckDuckGoLiteSearch(query: string, opts: WebSearchBackendOptions): Promise; /** * Keyless Bing web-search backend (GET `www.bing.com/search`). Default primary: * unlike the DuckDuckGo endpoints it is directly reachable from networks where * duckduckgo.com is blocked (e.g. mainland China), and it serves parseable * `b_algo` result markup to a plain HTTP client. Same blocked-page honesty * contract as the DuckDuckGo backends. * @beta */ export declare function bingSearch(query: string, opts: WebSearchBackendOptions): Promise; /** * Given Bing's HTML response body that yielded zero parsed results, decide * whether the backend is blocked/broken (captcha page, or no result markup at * all) vs a genuinely empty result set (`b_no` marker). Exported for testing. */ export declare function bingResponseLooksBlocked(text: string): boolean; /** * Keyless Baidu web-search backend (GET `www.baidu.com/s`). Useful for CJK * queries where Baidu's index is strongest. Parses organic result blocks, * filters ads (tuiguang / promoted), and extracts dates from c-color-gray spans. * @beta */ export declare function baiduSearch(query: string, opts: WebSearchBackendOptions): Promise; /** * Given Baidu's HTML response body that yielded zero parsed results, decide * whether the backend is blocked/broken (verification page, or no result markup) * vs a genuinely empty result set. Exported for testing. */ export declare function baiduResponseLooksBlocked(text: string): boolean; /** Brave Search API backend (requires an API key). */ export declare function createBraveSearch(apiKey: string): WebSearchBackend; /** Bocha (博查) Search API backend (requires an API key). */ export declare function createBochaSearch(apiKey: string): WebSearchBackend; /** Exa Search API backend (requires an API key). */ export declare function createExaSearch(apiKey: string): WebSearchBackend; interface NamedBackend { name: string; backend: WebSearchBackend; } interface ResolvedRetry { maxAttempts: number; baseDelayMs: number; sleep: (ms: number, signal?: AbortSignal) => Promise; } /** * Resolve the ordered backend chain. A custom `search` backend bypasses the * chain (host owns routing). Otherwise: Brave is used (and, with fallback on, * prepended) whenever an API key is available; the keyless Bing, DuckDuckGo * HTML, and DuckDuckGo Lite endpoints provide a no-key fallback. Selecting * `provider: 'brave'` without a key still fails fast at construction. * * When `isCjk` is true, the Baidu keyless backend is inserted after Bing * (before DuckDuckGo), since Baidu's index is strongest for CJK queries. * * If no API keys are configured and fallback is enabled, logs a warning that * keyless backends are increasingly blocked by anti-bot measures and may fail. * @beta Exported for testing. */ export declare function resolveBackendChain(opts: WebSearchOptions, isCjk?: boolean): NamedBackend[]; /** * Parallel-race fallback: start backends one by one with a grace window * (RACE_PRIMARY_GRACE_MS). If the first backend hasn't returned non-empty * results within the window, the next backend is also launched. The moment * any backend returns non-empty results, all in-flight backends are aborted * via `raceController` and the winner is returned. If all backends fail or * return empty, falls through with the same contract as before. */ export declare function searchWithFallback(chain: NamedBackend[], query: string, opts: WebSearchBackendOptions, retry: ResolvedRetry, raceGraceMs?: number, policy?: { acceptResults?: (results: WebSearchResult[]) => boolean; }): Promise; export declare function diversifyNewsResults(rows: WebSearchResult[], recency: WebSearchBackendOptions['recency'], now?: Date): WebSearchResult[]; /** Run all evidence sources concurrently within a shared latency budget. */ export declare function searchAllWithBudget(chain: NamedBackend[], query: string, opts: WebSearchBackendOptions, retry: ResolvedRetry, budgetMs: number): Promise; interface PreprocessedQuery { query: string; region?: string; siteHint?: string; siteDomains?: string[]; } export declare function inferSearchRecency(query: string): 'day' | 'week' | undefined; /** * Preprocess a raw LLM search query before passing it to a backend: * - Detect CJK characters and auto-set `region` to `zh-CN` (improves Bing recall * for Chinese queries — without `mkt=zh-CN`, Bing often returns Western results). * - Strip `site:` operators and `OR`/`AND` boolean syntax that keyless HTML * backends (Bing, DuckDuckGo) do not support reliably — they cause empty * results or timeouts. Extract the `site:` domain as a hint for the LLM. */ /** @beta Exported for testing. */ export declare function preprocessQuery(rawQuery: string, region?: string): PreprocessedQuery; /** Normalize domain list inputs (strings may include paths or schemes). */ export declare function normalizeDomainFilterList(raw: unknown): string[]; /** * Apply allow/block domain filters to search results. * allowed wins as a whitelist when non-empty; blocked always removes matches. * @internal exported for tests */ export declare function applyDomainFilters(results: WebSearchResult[], allowed: string[], blocked: string[]): WebSearchResult[]; export declare function createWebSearchTool(opts?: WebSearchOptions): Tool<{ query: string; max_results?: number; recency?: 'day' | 'week' | 'month' | 'year'; /** Parallel multi-angle sub-queries (max 5). Merged into one result list. */ query_keyword_groups?: string[]; /** Only keep results whose host matches these domains (whitelist). */ allowed_domains?: string[]; /** Drop results whose host matches these domains. */ blocked_domains?: string[]; }>; export {}; //# sourceMappingURL=web-search.d.ts.map