mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-23 00:35:51 +00:00
feat: 重构 WebSearch/WebFetch,新增 Tavily 适配器及 /web-tools 面板
- WebSearch: 默认 Tavily,适配器优先级 WEB_SEARCH_ADAPTER > settings.webSearchAdapter > tavily - WebFetch: 支持 Tavily /extract 返回 Markdown,移除 domain blacklist 远程检查 - 新增 /web-tools 命令面板(Search/Fetch 双 Tab + 二级配置菜单) - 新增 settings 字段: webSearchAdapter, webFetchAdapter, tavilyEndpointUrl, braveApiKey, exaApiKey, exaEndpointUrl, webFetchHttpTimeoutMs - 适配器联动: Tavily/Exa 从 settings 读取 endpoint 和 API key Co-Authored-By: deepseek-v4-pro <deepseek-ai@claude-code-best.win>
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Tavily-based search adapter — calls the Tavily Search API
|
||||
* (https://tavily.claude-code-best.win) and maps results to
|
||||
* the unified SearchResult format.
|
||||
*/
|
||||
|
||||
import axios from 'axios'
|
||||
import { AbortError } from 'src/utils/errors.js'
|
||||
import { getSettings_DEPRECATED } from 'src/utils/settings/settings.js'
|
||||
import type { SearchResult, SearchOptions, WebSearchAdapter } from './types.js'
|
||||
|
||||
const DEFAULT_TAVILY_SEARCH_URL = 'https://tavily.claude-code-best.win/search'
|
||||
const FETCH_TIMEOUT_MS = 30_000
|
||||
|
||||
interface TavilySearchHit {
|
||||
title: string
|
||||
url: string
|
||||
content: string
|
||||
score: number
|
||||
}
|
||||
|
||||
interface TavilySearchResponse {
|
||||
results: TavilySearchHit[]
|
||||
}
|
||||
|
||||
export class TavilySearchAdapter implements WebSearchAdapter {
|
||||
async search(query: string, options: SearchOptions): Promise<SearchResult[]> {
|
||||
const { signal, onProgress, allowedDomains, blockedDomains } = options
|
||||
|
||||
if (signal?.aborted) {
|
||||
throw new AbortError()
|
||||
}
|
||||
|
||||
onProgress?.({ type: 'query_update', query })
|
||||
|
||||
const abortController = new AbortController()
|
||||
if (signal) {
|
||||
signal.addEventListener('abort', () => abortController.abort(), {
|
||||
once: true,
|
||||
})
|
||||
}
|
||||
|
||||
const settings = getSettings_DEPRECATED() as Record<string, unknown> & {
|
||||
tavilyEndpointUrl?: string
|
||||
}
|
||||
const searchUrl = settings.tavilyEndpointUrl || DEFAULT_TAVILY_SEARCH_URL
|
||||
|
||||
try {
|
||||
const response = await axios.post<{
|
||||
query: string
|
||||
results: TavilySearchHit[]
|
||||
}>(
|
||||
searchUrl,
|
||||
{
|
||||
query,
|
||||
search_depth: 'basic',
|
||||
max_results: options.numResults ?? 8,
|
||||
include_domains: allowedDomains ?? [],
|
||||
exclude_domains: blockedDomains ?? [],
|
||||
},
|
||||
{
|
||||
signal: abortController.signal,
|
||||
timeout: FETCH_TIMEOUT_MS,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
},
|
||||
)
|
||||
|
||||
if (abortController.signal.aborted) {
|
||||
throw new AbortError()
|
||||
}
|
||||
|
||||
const results: SearchResult[] = (response.data.results ?? []).map(
|
||||
(hit: TavilySearchHit) => ({
|
||||
title: hit.title,
|
||||
url: hit.url,
|
||||
snippet: hit.content,
|
||||
}),
|
||||
)
|
||||
|
||||
onProgress?.({
|
||||
type: 'search_results_received',
|
||||
resultCount: results.length,
|
||||
query,
|
||||
})
|
||||
|
||||
return results
|
||||
} catch (e) {
|
||||
if (axios.isCancel(e) || abortController.signal.aborted) {
|
||||
throw new AbortError()
|
||||
}
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user