mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-22 16:25:51 +00:00
style: 完成所有文件的lint
This commit is contained in:
@@ -9,7 +9,11 @@ import type {
|
||||
} from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs'
|
||||
import { getFeatureValue_CACHED_MAY_BE_STALE } from 'src/services/analytics/growthbook.js'
|
||||
import { queryModelWithStreaming } from 'src/services/api/claude.js'
|
||||
import { createTrace, endTrace, isLangfuseEnabled } from 'src/services/langfuse/index.js'
|
||||
import {
|
||||
createTrace,
|
||||
endTrace,
|
||||
isLangfuseEnabled,
|
||||
} from 'src/services/langfuse/index.js'
|
||||
import { getSessionId } from 'src/bootstrap/state.js'
|
||||
import { getAPIProvider } from 'src/utils/model/providers.js'
|
||||
import { createUserMessage } from 'src/utils/messages.js'
|
||||
@@ -18,7 +22,10 @@ import { jsonParse } from 'src/utils/slowOperations.js'
|
||||
import { asSystemPrompt } from 'src/utils/systemPromptType.js'
|
||||
import type { SearchResult, SearchOptions, WebSearchAdapter } from './types.js'
|
||||
|
||||
function makeToolSchema(input: { allowedDomains?: string[]; blockedDomains?: string[] }): BetaWebSearchTool20250305 {
|
||||
function makeToolSchema(input: {
|
||||
allowedDomains?: string[]
|
||||
blockedDomains?: string[]
|
||||
}): BetaWebSearchTool20250305 {
|
||||
return {
|
||||
type: 'web_search_20250305',
|
||||
name: 'web_search',
|
||||
@@ -29,10 +36,7 @@ function makeToolSchema(input: { allowedDomains?: string[]; blockedDomains?: str
|
||||
}
|
||||
|
||||
export class ApiSearchAdapter implements WebSearchAdapter {
|
||||
async search(
|
||||
query: string,
|
||||
options: SearchOptions,
|
||||
): Promise<SearchResult[]> {
|
||||
async search(query: string, options: SearchOptions): Promise<SearchResult[]> {
|
||||
const { signal, onProgress, allowedDomains, blockedDomains } = options
|
||||
|
||||
const userMessage = createUserMessage({
|
||||
@@ -40,7 +44,10 @@ export class ApiSearchAdapter implements WebSearchAdapter {
|
||||
})
|
||||
const toolSchema = makeToolSchema({ allowedDomains, blockedDomains })
|
||||
|
||||
const useHaiku = getFeatureValue_CACHED_MAY_BE_STALE('tengu_plum_vx3', false)
|
||||
const useHaiku = getFeatureValue_CACHED_MAY_BE_STALE(
|
||||
'tengu_plum_vx3',
|
||||
false,
|
||||
)
|
||||
const model = useHaiku ? getSmallFastModel() : getMainLoopModel()
|
||||
const langfuseTrace = isLangfuseEnabled()
|
||||
? createTrace({
|
||||
@@ -71,7 +78,9 @@ export class ApiSearchAdapter implements WebSearchAdapter {
|
||||
isBypassPermissionsModeAvailable: false,
|
||||
}),
|
||||
model,
|
||||
toolChoice: useHaiku ? { type: 'tool' as const, name: 'web_search' } : undefined,
|
||||
toolChoice: useHaiku
|
||||
? { type: 'tool' as const, name: 'web_search' }
|
||||
: undefined,
|
||||
isNonInteractiveSession: false,
|
||||
hasAppendSystemPrompt: false,
|
||||
extraToolSchemas: [toolSchema],
|
||||
@@ -101,8 +110,18 @@ export class ApiSearchAdapter implements WebSearchAdapter {
|
||||
const streamEvt = event as {
|
||||
event?: {
|
||||
type: string
|
||||
content_block?: { type: string; id?: string; tool_use_id?: string; content?: unknown; [key: string]: unknown }
|
||||
delta?: { type: string; partial_json?: string; [key: string]: unknown }
|
||||
content_block?: {
|
||||
type: string
|
||||
id?: string
|
||||
tool_use_id?: string
|
||||
content?: unknown
|
||||
[key: string]: unknown
|
||||
}
|
||||
delta?: {
|
||||
type: string
|
||||
partial_json?: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
@@ -116,7 +135,10 @@ export class ApiSearchAdapter implements WebSearchAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
if (currentToolUseId && streamEvt.event?.type === 'content_block_delta') {
|
||||
if (
|
||||
currentToolUseId &&
|
||||
streamEvt.event?.type === 'content_block_delta'
|
||||
) {
|
||||
const delta = streamEvt.event.delta
|
||||
if (delta?.type === 'input_json_delta' && delta.partial_json) {
|
||||
currentToolUseJson += delta.partial_json
|
||||
@@ -168,14 +190,20 @@ export class ApiSearchAdapter implements WebSearchAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
function extractSearchResults(
|
||||
blocks: BetaContentBlock[],
|
||||
): SearchResult[] {
|
||||
function extractSearchResults(blocks: BetaContentBlock[]): SearchResult[] {
|
||||
const results: SearchResult[] = []
|
||||
|
||||
for (const block of blocks) {
|
||||
if (block.type === 'web_search_tool_result' && Array.isArray(block.content)) {
|
||||
for (const r of block.content as Array<{ title: string; url: string; page_age?: string; type?: string }>) {
|
||||
if (
|
||||
block.type === 'web_search_tool_result' &&
|
||||
Array.isArray(block.content)
|
||||
) {
|
||||
for (const r of block.content as Array<{
|
||||
title: string
|
||||
url: string
|
||||
page_age?: string
|
||||
type?: string
|
||||
}>) {
|
||||
results.push({
|
||||
title: r.title,
|
||||
url: r.url,
|
||||
|
||||
@@ -23,7 +23,8 @@ const BROWSER_HEADERS = {
|
||||
'Accept-Encoding': 'gzip, deflate, br',
|
||||
'Cache-Control': 'no-cache',
|
||||
Pragma: 'no-cache',
|
||||
'Sec-Ch-Ua': '"Microsoft Edge";v="131", "Chromium";v="131", "Not_A Brand";v="24"',
|
||||
'Sec-Ch-Ua':
|
||||
'"Microsoft Edge";v="131", "Chromium";v="131", "Not_A Brand";v="24"',
|
||||
'Sec-Ch-Ua-Mobile': '?0',
|
||||
'Sec-Ch-Ua-Platform': '"macOS"',
|
||||
'Sec-Fetch-Dest': 'document',
|
||||
@@ -34,10 +35,7 @@ const BROWSER_HEADERS = {
|
||||
} as const
|
||||
|
||||
export class BingSearchAdapter implements WebSearchAdapter {
|
||||
async search(
|
||||
query: string,
|
||||
options: SearchOptions,
|
||||
): Promise<SearchResult[]> {
|
||||
async search(query: string, options: SearchOptions): Promise<SearchResult[]> {
|
||||
const { signal, onProgress, allowedDomains, blockedDomains } = options
|
||||
|
||||
if (signal?.aborted) {
|
||||
@@ -50,7 +48,9 @@ export class BingSearchAdapter implements WebSearchAdapter {
|
||||
|
||||
const abortController = new AbortController()
|
||||
if (signal) {
|
||||
signal.addEventListener('abort', () => abortController.abort(), { once: true })
|
||||
signal.addEventListener('abort', () => abortController.abort(), {
|
||||
once: true,
|
||||
})
|
||||
}
|
||||
|
||||
let html: string
|
||||
@@ -76,14 +76,22 @@ export class BingSearchAdapter implements WebSearchAdapter {
|
||||
const rawResults = extractBingResults(html)
|
||||
|
||||
// Client-side domain filtering
|
||||
const results = rawResults.filter((r) => {
|
||||
const results = rawResults.filter(r => {
|
||||
if (!r.url) return false
|
||||
try {
|
||||
const hostname = new URL(r.url).hostname
|
||||
if (allowedDomains?.length && !allowedDomains.some(d => hostname === d || hostname.endsWith('.' + d))) {
|
||||
if (
|
||||
allowedDomains?.length &&
|
||||
!allowedDomains.some(
|
||||
d => hostname === d || hostname.endsWith('.' + d),
|
||||
)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
if (blockedDomains?.length && blockedDomains.some(d => hostname === d || hostname.endsWith('.' + d))) {
|
||||
if (
|
||||
blockedDomains?.length &&
|
||||
blockedDomains.some(d => hostname === d || hostname.endsWith('.' + d))
|
||||
) {
|
||||
return false
|
||||
}
|
||||
} catch {
|
||||
@@ -116,7 +124,8 @@ export function extractBingResults(html: string): SearchResult[] {
|
||||
const block = blockMatch[1]
|
||||
|
||||
// Extract the primary link from <h2><a href="...">...</a></h2>
|
||||
const h2LinkRegex = /<h2[^>]*>\s*<a[^>]+href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/i
|
||||
const h2LinkRegex =
|
||||
/<h2[^>]*>\s*<a[^>]+href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/i
|
||||
const linkMatch = h2LinkRegex.exec(block)
|
||||
if (!linkMatch) continue
|
||||
|
||||
@@ -128,9 +137,7 @@ export function extractBingResults(html: string): SearchResult[] {
|
||||
const url = resolveBingUrl(rawUrl)
|
||||
if (!url) continue
|
||||
|
||||
const title = decodeHtmlEntities(
|
||||
titleHtml.replace(/<[^>]+>/g, '').trim(),
|
||||
)
|
||||
const title = decodeHtmlEntities(titleHtml.replace(/<[^>]+>/g, '').trim())
|
||||
|
||||
// Extract snippet: try b_lineclamp → b_caption <p> → b_caption fallback
|
||||
const snippet = extractSnippet(block)
|
||||
@@ -150,14 +157,16 @@ function extractSnippet(block: string): string | undefined {
|
||||
}
|
||||
|
||||
// 2. Try <p> inside b_caption
|
||||
const captionPRegex = /<div[^>]*class="b_caption[^"]*"[^>]*>[\s\S]*?<p[^>]*>([\s\S]*?)<\/p>/i
|
||||
const captionPRegex =
|
||||
/<div[^>]*class="b_caption[^"]*"[^>]*>[\s\S]*?<p[^>]*>([\s\S]*?)<\/p>/i
|
||||
match = captionPRegex.exec(block)
|
||||
if (match) {
|
||||
return decodeHtmlEntities(match[1].replace(/<[^>]+>/g, '').trim())
|
||||
}
|
||||
|
||||
// 3. Fallback: any text inside b_caption <div>
|
||||
const fallbackRegex = /<div[^>]*class="b_caption[^"]*"[^>]*>([\s\S]*?)<\/div>/i
|
||||
const fallbackRegex =
|
||||
/<div[^>]*class="b_caption[^"]*"[^>]*>([\s\S]*?)<\/div>/i
|
||||
const fallbackMatch = fallbackRegex.exec(block)
|
||||
if (fallbackMatch) {
|
||||
const text = fallbackMatch[1].replace(/<[^>]+>/g, '').trim()
|
||||
|
||||
@@ -9,7 +9,10 @@ import type { SearchResult, SearchOptions, WebSearchAdapter } from './types.js'
|
||||
|
||||
const FETCH_TIMEOUT_MS = 30_000
|
||||
const BRAVE_LLM_CONTEXT_URL = 'https://api.search.brave.com/res/v1/llm/context'
|
||||
const BRAVE_API_KEY_ENV_VARS = ['BRAVE_SEARCH_API_KEY', 'BRAVE_API_KEY'] as const
|
||||
const BRAVE_API_KEY_ENV_VARS = [
|
||||
'BRAVE_SEARCH_API_KEY',
|
||||
'BRAVE_API_KEY',
|
||||
] as const
|
||||
|
||||
interface BraveGroundingResult {
|
||||
title?: string
|
||||
@@ -26,10 +29,7 @@ interface BraveSearchResponse {
|
||||
}
|
||||
|
||||
export class BraveSearchAdapter implements WebSearchAdapter {
|
||||
async search(
|
||||
query: string,
|
||||
options: SearchOptions,
|
||||
): Promise<SearchResult[]> {
|
||||
async search(query: string, options: SearchOptions): Promise<SearchResult[]> {
|
||||
const { signal, onProgress, allowedDomains, blockedDomains } = options
|
||||
|
||||
if (signal?.aborted) {
|
||||
|
||||
@@ -16,10 +16,7 @@ const EXA_MCP_URL = 'https://mcp.exa.ai/mcp'
|
||||
const FETCH_TIMEOUT_MS = 25_000
|
||||
|
||||
export class ExaSearchAdapter implements WebSearchAdapter {
|
||||
async search(
|
||||
query: string,
|
||||
options: SearchOptions,
|
||||
): Promise<SearchResult[]> {
|
||||
async search(query: string, options: SearchOptions): Promise<SearchResult[]> {
|
||||
const { signal, onProgress, allowedDomains, blockedDomains } = options
|
||||
|
||||
if (signal?.aborted) {
|
||||
@@ -30,7 +27,9 @@ export class ExaSearchAdapter implements WebSearchAdapter {
|
||||
|
||||
const abortController = new AbortController()
|
||||
if (signal) {
|
||||
signal.addEventListener('abort', () => abortController.abort(), { once: true })
|
||||
signal.addEventListener('abort', () => abortController.abort(), {
|
||||
once: true,
|
||||
})
|
||||
}
|
||||
|
||||
// Use options to derive search params — matches kilocode websearch.ts defaults
|
||||
@@ -90,14 +89,22 @@ export class ExaSearchAdapter implements WebSearchAdapter {
|
||||
const results = this.parseResults(searchText)
|
||||
|
||||
// Client-side domain filtering
|
||||
const filteredResults = results.filter((r) => {
|
||||
const filteredResults = results.filter(r => {
|
||||
if (!r.url) return false
|
||||
try {
|
||||
const hostname = new URL(r.url).hostname
|
||||
if (allowedDomains?.length && !allowedDomains.some(d => hostname === d || hostname.endsWith('.' + d))) {
|
||||
if (
|
||||
allowedDomains?.length &&
|
||||
!allowedDomains.some(
|
||||
d => hostname === d || hostname.endsWith('.' + d),
|
||||
)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
if (blockedDomains?.length && blockedDomains.some(d => hostname === d || hostname.endsWith('.' + d))) {
|
||||
if (
|
||||
blockedDomains?.length &&
|
||||
blockedDomains.some(d => hostname === d || hostname.endsWith('.' + d))
|
||||
) {
|
||||
return false
|
||||
}
|
||||
} catch {
|
||||
@@ -160,7 +167,9 @@ export class ExaSearchAdapter implements WebSearchAdapter {
|
||||
for (const block of blocks) {
|
||||
const titleMatch = block.match(/^Title:\s*(.+)$/m)
|
||||
const urlMatch = block.match(/^URL:\s*(https?:\/\/[^\s]+)$/m)
|
||||
const contentMatch = block.match(/^Content:\s*([\s\S]+?)(?=\n(?:Title:|URL:|---)|$)/m)
|
||||
const contentMatch = block.match(
|
||||
/^Content:\s*([\s\S]+?)(?=\n(?:Title:|URL:|---)|$)/m,
|
||||
)
|
||||
|
||||
if (urlMatch) {
|
||||
results.push({
|
||||
@@ -173,7 +182,7 @@ export class ExaSearchAdapter implements WebSearchAdapter {
|
||||
|
||||
// Fallback: markdown links
|
||||
if (results.length === 0) {
|
||||
const markdownLinkRegex = /\[([^\]]+)\]\((https?:\/\/[^\)]+)\)/g
|
||||
const markdownLinkRegex = /\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g
|
||||
let match: RegExpExecArray | null
|
||||
while ((match = markdownLinkRegex.exec(text)) !== null) {
|
||||
results.push({
|
||||
|
||||
@@ -41,7 +41,10 @@ export function createAdapter(): WebSearchAdapter {
|
||||
// 3. First-party Anthropic API → api (server-side web search + connector_text)
|
||||
// 4. Fallback → bing
|
||||
const adapterKey =
|
||||
envAdapter === 'api' || envAdapter === 'bing' || envAdapter === 'brave' || envAdapter === 'exa'
|
||||
envAdapter === 'api' ||
|
||||
envAdapter === 'bing' ||
|
||||
envAdapter === 'brave' ||
|
||||
envAdapter === 'exa'
|
||||
? envAdapter
|
||||
: isThirdPartyProvider()
|
||||
? 'bing'
|
||||
|
||||
Reference in New Issue
Block a user