fix: surface actionable error when DuckDuckGo web search is rate-limited (#834)
Non-Anthropic / non-codex providers (minimax, kimi, generic OpenAI-compatible) fell through to the DDG adapter when no paid search key was configured. DDG's scraper is blocked on most IPs, so web_search surfaced an opaque "anomaly in the request" error. Catch that response in the DDG provider and rethrow with the exact env vars that would unblock the tool, or the option to switch to a native-search provider. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,23 @@
|
||||
import type { SearchInput, SearchProvider } from './types.js'
|
||||
import { applyDomainFilters, type ProviderOutput } from './types.js'
|
||||
|
||||
// DuckDuckGo's HTML scraper aggressively blocks datacenter / repeat IPs with
|
||||
// an "anomaly in the request" response. When that happens we surface an
|
||||
// actionable error instead of the opaque scraper message so users know how
|
||||
// to configure a working backend.
|
||||
const DDG_ANOMALY_HINT =
|
||||
'DuckDuckGo scraping is rate-limited from this network. ' +
|
||||
'Configure a search backend with one of: ' +
|
||||
'FIRECRAWL_API_KEY, TAVILY_API_KEY, EXA_API_KEY, YOU_API_KEY, ' +
|
||||
'JINA_API_KEY, BING_API_KEY, MOJEEK_API_KEY, LINKUP_API_KEY — ' +
|
||||
'or use an Anthropic / Vertex / Foundry provider for native web search.'
|
||||
|
||||
function isAnomalyError(message: string): boolean {
|
||||
return /anomaly in the request|likely making requests too quickly/i.test(
|
||||
message,
|
||||
)
|
||||
}
|
||||
|
||||
export const duckduckgoProvider: SearchProvider = {
|
||||
name: 'duckduckgo',
|
||||
|
||||
@@ -20,7 +37,16 @@ export const duckduckgoProvider: SearchProvider = {
|
||||
}
|
||||
if (signal?.aborted) throw new DOMException('Aborted', 'AbortError')
|
||||
// TODO: duck-duck-scrape doesn't accept AbortSignal — can't cancel in-flight searches
|
||||
const response = await search(input.query, { safeSearch: SafeSearchType.STRICT })
|
||||
let response: Awaited<ReturnType<typeof search>>
|
||||
try {
|
||||
response = await search(input.query, { safeSearch: SafeSearchType.STRICT })
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err)
|
||||
if (isAnomalyError(msg)) {
|
||||
throw new Error(DDG_ANOMALY_HINT)
|
||||
}
|
||||
throw err
|
||||
}
|
||||
|
||||
const hits = applyDomainFilters(
|
||||
response.results.map(r => ({
|
||||
|
||||
Reference in New Issue
Block a user