21 KiB
21 KiB
web_search
Run one web query through the first available search provider and return LLM-formatted answer, source URLs, and optional citations.
Source
- Entry:
packages/coding-agent/src/web/search/index.ts - Model-facing prompt:
packages/coding-agent/src/prompts/tools/web-search.md - Key collaborators:
packages/coding-agent/src/web/search/provider.ts— lazy provider registry; availability chain.packages/coding-agent/src/web/search/types.ts— unifiedSearchResponse/SearchProviderErrortypes.packages/coding-agent/src/web/search/render.ts— TUI renderer details type.packages/coding-agent/src/web/search/providers/base.ts— provider interface and shared params contract.packages/coding-agent/src/web/search/providers/utils.ts— credential lookup; source normalization.packages/coding-agent/src/web/search/providers/anthropic.ts— Claude web-search provider.packages/coding-agent/src/web/search/providers/brave.ts— Brave Search API adapter.packages/coding-agent/src/web/search/providers/codex.ts— OpenAI Codex SSE adapter.packages/coding-agent/src/web/search/providers/exa.ts— Exa API or MCP adapter.packages/coding-agent/src/web/search/providers/gemini.ts— Gemini grounding SSE adapter.packages/coding-agent/src/web/search/providers/jina.ts— Jina Reader search adapter.packages/coding-agent/src/web/search/providers/kagi.ts— Kagi provider wrapper.packages/coding-agent/src/web/search/providers/kimi.ts— Kimi search adapter.packages/coding-agent/src/web/search/providers/parallel.ts— Parallel provider wrapper.packages/coding-agent/src/web/search/providers/perplexity.ts— Perplexity API / OAuth adapter.packages/coding-agent/src/web/search/providers/searxng.ts— self-hosted SearXNG adapter.packages/coding-agent/src/web/search/providers/synthetic.ts— Synthetic search adapter.packages/coding-agent/src/web/search/providers/tavily.ts— Tavily search adapter.packages/coding-agent/src/web/search/providers/zai.ts— Z.AI remote MCP adapter.packages/coding-agent/src/web/parallel.ts— Parallel search/extract HTTP client.packages/coding-agent/src/web/kagi.ts— Kagi HTTP client.packages/coding-agent/src/tools/index.ts— built-in tool registration and enable flag.
Inputs
| Field | Type | Required | Description |
|---|---|---|---|
query |
string |
Yes | Search query. executeSearch() rewrites any 2020-2029 substring to the current year before dispatch. |
recency |
"day" | "week" | "month" | "year" |
No | Time filter. Only providers that implement it use it. Prompt text says Brave and Perplexity; code also maps it for Tavily and SearXNG. |
limit |
number |
No | Max results to return. Usually becomes the provider request's result-count parameter when num_search_results is absent. |
max_tokens |
number |
No | Passed through as maxOutputTokens / max_tokens only by Anthropic, Gemini, and Perplexity API-key mode. Ignored by the other providers. |
temperature |
number |
No | Passed through only by Anthropic, Gemini, and Perplexity API-key mode. Ignored by the other providers. |
num_search_results |
number |
No | Requested upstream search breadth. For most providers this is the same count used for returned sources. Perplexity is the only adapter that keeps it distinct from limit. |
Outputs
The tool returns a single text content block plus structured details.
content:[{ type: "text", text: string }]details:SearchRenderDetailsfrompackages/coding-agent/src/web/search/render.tsresponse: SearchResponseerror?: string
text is produced by formatForLLM() in packages/coding-agent/src/web/search/index.ts:
- If
response.answerexists, it is emitted first. - If sources exist, a
## Sourcessection follows with a source count, then one entry per source:[n] <title> (<formatted age or published date>)<url>- optional snippet line truncated to 240 chars.
- If citations exist, a
## Citationssection follows with URL/title plus optional cited text truncated to 240 chars. - If related questions exist, a
## Relatedbullet list follows. - If search queries exist, a
Search queries: <n>section follows, capped to the first 3 queries and 120 chars each.
Failure output is not thrown at the tool boundary when at least one provider was attempted. Instead the tool returns:
content[0].text = "Error: ..."details.response.provider = <last attempted provider> | "none"details.error = ...
Streaming: none. WebSearchTool.execute() does not forward its _signal argument into executeSearch(), so provider cancellation is only available to internal callers that place signal inside SearchQueryParams.
Flow
WebSearchTool.execute()inpackages/coding-agent/src/web/search/index.tsdelegates directly toexecuteSearch().executeSearch()chooses a provider list:- if
params.provideris set and not"auto", it loads that provider withgetSearchProvider(); ifisAvailable()returns true, the list is[that provider], otherwise it falls back toresolveProviderChain("auto"). - otherwise it calls
resolveProviderChain()with the module-global preferred provider frompackages/coding-agent/src/web/search/provider.ts.
- if
resolveProviderChain()lazily loads each provider module on demand, checksisAvailable(), and returns only available providers. If a preferred provider is set, it is tried first, then the staticSEARCH_PROVIDER_ORDERexcluding that provider.- If no providers are available,
executeSearch()returnsError: No web search provider configured.withdetails.response.provider = "none". - For each provider in order,
executeSearch()callsprovider.search()with:queryafter year-rewrite,limit,recency,temperature,maxOutputTokens,numSearchResults,systemPromptfrompackages/coding-agent/src/prompts/tools/web-search.md.
- On the first successful
SearchResponse,formatForLLM()renders answer/sources/citations/related/search-queries into one text block and returns it withdetails.response. - If a provider throws,
executeSearch()records the error and tries the next provider. There is no provider-level parallel fan-out; fallback is sequential. - After all candidates fail,
formatProviderError()normalizes the last error:- Anthropic
404becomesAnthropic web search returned 404 (model or endpoint not found). 401/403become<Provider> authorization failed ...except Z.AI, which preserves its raw message.- other
SearchProviderErrors surfaceerror.message.
- Anthropic
- If more than one provider was attempted, the final message is
All web search providers failed (<labels>). Last error: <message>; otherwise it is just the normalized last error.
Modes / Variants
- Provider selection
- Forced provider: internal callers may pass
provider; unavailable forced providers fall back to the auto chain instead of hard-failing (packages/coding-agent/src/web/search/index.ts). This field is not in the model-facing schema. - Preferred provider:
setPreferredSearchProvider()sets a module-global default used byresolveProviderChain().packages/coding-agent/src/sdk.tsandpackages/coding-agent/src/modes/controllers/selector-controller.tswire this from settings. - Auto chain order:
tavily,perplexity,brave,jina,kimi,anthropic,gemini,codex,zai,exa,parallel,kagi,synthetic,searxng(SEARCH_PROVIDER_ORDERinpackages/coding-agent/src/web/search/provider.ts).
- Forced provider: internal callers may pass
- Provider adapters
- Tavily —
packages/coding-agent/src/web/search/providers/tavily.ts- Availability: API key from env or
agent.dbviafindCredential(). - Querying: POST
https://api.tavily.com/search. recencymaps to Tavilytime_range; code explicitly keepstopicat default general scope instead of narrowing to news.limit/num_search_results: adapter usesparams.numSearchResults ?? params.limit, clamped to5..20with default5.- Output:
answer,sources,requestId,authMode: "api_key".
- Availability: API key from env or
- Perplexity —
packages/coding-agent/src/web/search/providers/perplexity.ts- Availability: auth precedence is
PERPLEXITY_COOKIES-> OAuth token inagent.db->PERPLEXITY_API_KEY/PPLX_API_KEY. - OAuth/cookie mode: POSTs to
https://www.perplexity.ai/rest/sse/perplexity_ask, consumes SSE, merges partial events, extracts answer and source URLs, setsauthMode: "oauth". - API-key mode: POSTs to
https://api.perplexity.ai/chat/completionswithmodel: "sonar-pro",search_mode: "web",num_search_results, optionalsearch_recency_filter,max_tokens,temperature. num_search_resultscontrols upstream API breadth only in API-key mode.limitis preserved separately asnum_resultsand slices returnedsourcesafter parsing in both auth modes.- Output may include
answer,sources,citations,usage,model,requestId,authMode.
- Availability: auth precedence is
- Brave —
packages/coding-agent/src/web/search/providers/brave.ts- Availability:
BRAVE_API_KEYonly. - Querying: GET
https://api.search.brave.com/res/v1/web/searchwithcount,extra_snippets=true, andfreshness=pd|pw|pm|pyforrecency. limit/num_search_results:params.numSearchResults ?? params.limit, clamped to1..20, default10.- Output:
sources,requestId.
- Availability:
- Jina —
packages/coding-agent/src/web/search/providers/jina.ts- Availability:
JINA_API_KEYonly. - Querying: GET-like fetch to
https://s.jina.ai/<encoded query>with bearer auth. - Ignores
recency,max_tokens, andtemperature. limit/num_search_results: adapter slices sources toparams.numSearchResults ?? params.limitwhen provided; otherwise returns all payload items.- Output:
sourcesonly.
- Availability:
- Kimi —
packages/coding-agent/src/web/search/providers/kimi.ts- Availability:
MOONSHOT_SEARCH_API_KEY,KIMI_SEARCH_API_KEY,MOONSHOT_API_KEY, oragent.dbcredentials formoonshot/kimi-code. - Querying: POST to
MOONSHOT_SEARCH_BASE_URL/KIMI_SEARCH_BASE_URL/ defaulthttps://api.kimi.com/coding/v1/searchwithtext_query,limit,enable_page_crawling,timeout_seconds: 30. limit/num_search_results:params.numSearchResults ?? params.limit, clamped to1..20, default10.- Output:
sources,requestId.
- Availability:
- Anthropic —
packages/coding-agent/src/web/search/providers/anthropic.ts- Availability:
findAnthropicAuth()from@oh-my-pi/pi-ai. - Querying: Claude Messages API with web-search tool enabled.
max_tokensandtemperaturepass through.limitandnum_search_resultsare collapsed together before dispatch:num_results = params.numSearchResults ?? params.limit.- Output may include
answer,sources,citations,searchQueries,usage.searchRequests,model,requestId.
- Availability:
- Gemini —
packages/coding-agent/src/web/search/providers/gemini.ts- Availability: OAuth credentials in
agent.dbforgoogle-gemini-cliorgoogle-antigravity. - Querying: SSE
streamGenerateContentcall with Google Search grounding enabled. Antigravity auth tries two fallback endpoints and retries401/403/400 invalid authonce after token refresh;429/5xxretry with exponential backoff and server-provided retry delay, capped by a5 * 60 * 1000ms rate-limit budget. max_tokensandtemperaturepass through asgenerationConfig.maxOutputTokens/generationConfig.temperature.limitandnum_search_resultsare collapsed together before dispatch.- Output may include
answer,sources,citations,searchQueries,usage,model.
- Availability: OAuth credentials in
- Codex —
packages/coding-agent/src/web/search/providers/codex.ts- Availability: non-expired OAuth credential for
openai-codexinagent.db. - Querying: SSE POST to
https://chatgpt.com/backend-api/codex/responseswithtool_choice: { type: "web_search" }andsearch_context_size: "high"by default. - Ignores
recency,max_tokens, andtemperaturein this tool path. limitandnum_search_resultsare collapsed together before dispatch.- Output may include
answer,sources,usage,model,requestId. If the streamed response has nourl_citationannotations, the adapter falls back to scraping markdown links and bare URLs from the answer text.
- Availability: non-expired OAuth credential for
- Z.AI —
packages/coding-agent/src/web/search/providers/zai.ts- Availability: env or
agent.dbcredential forzai. - Querying: JSON-RPC
tools/callagainsthttps://api.z.ai/api/mcp/web_search_prime/mcpfor remote MCP toolweb_search_prime. - Fallback chain inside the provider: tries
{query,count}, then{search_query,count}, then{search_query, search_engine:"search-prime", count}when earlier attempts fail with argument-shape errors. limitandnum_search_resultsare collapsed together before dispatch.- Output may include parsed free-text
answer,sources,requestId.
- Availability: env or
- Exa —
packages/coding-agent/src/web/search/providers/exa.ts- Availability: always true unless settings explicitly disable
exa.enabledorexa.enableSearch; the adapter can use public MCP even withoutEXA_API_KEY. - Querying: with
EXA_API_KEY, POSThttps://api.exa.ai/search; otherwise call MCP toolweb_search_exa. limitandnum_search_resultsare collapsed together before dispatch.- Output: synthesized
answerfrom up to 3 result summaries,sources,requestId.
- Availability: always true unless settings explicitly disable
- Parallel —
packages/coding-agent/src/web/search/providers/parallel.ts,packages/coding-agent/src/web/parallel.ts- Availability: env or
agent.dbcredential forparallel. - Querying: POST
https://api.parallel.ai/v1beta/searchwithobjective=query,search_queries=[query],mode:"fast",max_chars_per_result: 10000, beta headersearch-extract-2025-10-10. - There is no provider fan-out here despite the name; the current adapter always sends a one-element
search_queriesarray. limitandnum_search_resultsare collapsed together before dispatch, clamped to1..40, default10.- Output:
sources,requestId.
- Availability: env or
- Kagi —
packages/coding-agent/src/web/search/providers/kagi.ts,packages/coding-agent/src/web/kagi.ts- Availability: env or
agent.dbcredential forkagi. - Querying: GET
https://kagi.com/api/v0/search?q=<query>&limit=<n>withAuthorization: Bot <key>. limitandnum_search_resultsare collapsed together before dispatch, clamped to1..40, default10.- Output:
sources,relatedQuestions,requestId.
- Availability: env or
- Synthetic —
packages/coding-agent/src/web/search/providers/synthetic.ts- Availability: env or
agent.dbcredential forsynthetic. - Querying: POST
https://api.synthetic.new/v2/searchwith{ query }. - Ignores
recency,max_tokens, andtemperature. limitandnum_search_resultsare collapsed together before dispatch.- Output:
sourcesonly.
- Availability: env or
- SearXNG —
packages/coding-agent/src/web/search/providers/searxng.ts- Availability: endpoint from
searxng.endpointsetting orSEARXNG_ENDPOINTenv. - Querying: GET
<endpoint>/search?format=json&q=...; optional settings addcategoriesandlanguage. - Auth precedence: Basic auth (
searxng.basicUsername/searxng.basicPasswordor env equivalents) over bearer token (searxng.token/SEARXNG_TOKEN). Basic credentials are validated for RFC 7617 restrictions. recencymaps totime_range;weekis downgraded tomonthbecause SearXNG does not support week.limitandnum_search_resultsare collapsed together before dispatch, clamped to1..20, default10.- Output:
sources,relatedQuestionsfromsuggestions.
- Availability: endpoint from
- Tavily —
Side Effects
- Network
- Calls one or more external search providers over HTTPS until one succeeds or all fail.
- Provider-specific transports include JSON POST, JSON GET, SSE streaming (Perplexity OAuth/API, Gemini, Codex), and JSON-RPC over HTTP (Z.AI).
- Subprocesses / native bindings
- None.
- Session state (transcript, memory, jobs, checkpoints, registries)
- Uses a module-global provider-instance cache in
packages/coding-agent/src/web/search/provider.ts. - Uses a module-global preferred-provider setting in the same file.
packages/coding-agent/src/tools/index.tsgates tool availability behindsession.settings.get("web_search.enabled").
- Uses a module-global provider-instance cache in
- Background work / cancellation
- Many provider adapters accept
AbortSignal, butWebSearchTool.execute()does not pass its_signalintoexecuteSearch(). Internal callers can still use cancellation by callingrunSearchQuery()/executeSearch()withsignalembedded in params.
- Many provider adapters accept
Limits & Caps
- Provider auto-order length: 14 providers (
SEARCH_PROVIDER_ORDERinpackages/coding-agent/src/web/search/provider.ts). formatForLLM()truncates source snippets and citation text to 240 chars (packages/coding-agent/src/web/search/index.ts).formatForLLM()emits at most 3 search queries, each truncated to 120 chars (packages/coding-agent/src/web/search/index.ts).- Brave result count: default
10, max20(DEFAULT_NUM_RESULTS,MAX_NUM_RESULTSinpackages/coding-agent/src/web/search/providers/brave.ts). - Tavily result count: default
5, max20(packages/coding-agent/src/web/search/providers/tavily.ts). - Kimi result count: default
10, max20; request timeout field fixed to30seconds (packages/coding-agent/src/web/search/providers/kimi.ts). - Parallel result count: default
10, max40; per-result excerpt cap10_000chars (packages/coding-agent/src/web/search/providers/parallel.ts,packages/coding-agent/src/web/parallel.ts). - Kagi result count: default
10, max40(packages/coding-agent/src/web/search/providers/kagi.ts). - SearXNG result count: default
10, max20(packages/coding-agent/src/web/search/providers/searxng.ts). - Perplexity API-key mode defaults:
max_tokens = 8192,temperature = 0.2,num_search_results = 10(packages/coding-agent/src/web/search/providers/perplexity.ts). - Anthropic defaults: model
claude-haiku-4-5,DEFAULT_MAX_TOKENS = 4096when the provider omitsmax_tokens(packages/coding-agent/src/web/search/providers/anthropic.ts). - Gemini retries: up to
3retries per endpoint, base delay1000ms, rate-limit delay budget5 * 60 * 1000ms (packages/coding-agent/src/web/search/providers/gemini.ts).
Errors
- Tool-level no-provider case returns a normal tool result with
Error: No web search provider configured.; it does not throw. - Tool-level all-failed case also returns a normal tool result with
Error: ...; failures are summarized from the last attempted provider. - Provider adapters usually throw
SearchProviderError(provider, message, status)for HTTP or protocol failures. - Availability probes intentionally swallow lookup errors and report
falsein many providers viaisApiKeyAvailable(). - Per-provider notable failures:
- Anthropic: missing credentials throw a plain
Error; a404is remapped to a special final message byformatProviderError(). - Perplexity: missing auth throws a plain
Error; OAuth streamerror_codeevents becomeSearchProviderError("perplexity", ...). - Gemini: auth refresh, endpoint fallback, and retry logic are internal; final exhausted failures surface as
SearchProviderError("gemini", ...). - Codex and Gemini both fail if the HTTP response has no body after a
200. - Z.AI treats malformed SSE/JSON-RPC payloads as provider errors and retries only argument-shape failures across request variants.
- SearXNG
findAuth()can throw configuration errors before any HTTP call if Basic auth fields are incomplete or invalid.
- Anthropic: missing credentials throw a plain
Notes
- The model-facing schema does not expose
provider, but internal callers can force one throughSearchQueryParams. resolveProviderChain()lazily imports provider modules and caches singleton instances. Just asking for labels viagetSearchProviderLabel()does not trigger those imports.- Most providers treat
limitandnum_search_resultsas the same number because adapters passparams.numSearchResults ?? params.limit. Perplexity is the only implementation that preserves both concepts. - The prompt says
recencyis for Brave and Perplexity, but code also implements it for Tavily and SearXNG. - The year rewrite in
executeSearch()is blunt: any2020-2029substring is replaced with the current year. packages/coding-agent/src/config/settings-schema.tsexposes provider preferences forauto,exa,brave,jina,kimi,perplexity,anthropic,zai,tavily,kagi,synthetic,parallel, andsearxng. Gemini and Codex are in the registry and auto chain but not in that settings enum.- Exa availability is optimistic. Unless settings disable it, the provider stays in the chain even without an API key because it can fall back to MCP.