mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-22 16:25:51 +00:00
feat: 工具层及 mcp 大重构 (#252)
* feat: 第一版大重构 * fix: 修复类型问题 * chore: 更新版本到 1.3.2 * Add brave as alternative WebSearchTool * fix: 修正顺序 * fix: 修复对穷鬼模式的 auto dream 和 session memory 越过 * feat: 穷鬼模式去除 session-summary * feat: 创建 builtin-tools 包,搬运所有工具实现 将 src/tools/ 下的全部 60 个工具目录迁移至 packages/builtin-tools/src/tools/, 内部导入路径已更新为 src/ alias 模式。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: 更新 src/ 中所有工具引用至 builtin-tools 包,删除 src/tools/ - src/tools.ts 及 178 个 src/ 文件的 import 路径从 ./tools/ 改为 builtin-tools/tools/ - 删除 src/tools/ 整个目录(已迁移至 packages/builtin-tools/) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: 添加 builtin-tools 路径别名至 tsconfig,更新 bun.lock - tsconfig.json 新增 builtin-tools/* 和 builtin-tools 路径映射 - 新增 packages/builtin-tools/src 至 include Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: 为 builtin-tools、mcp-client、agent-tools 添加 @claude-code-best 作用域前缀 所有包名及 import 路径统一添加 @claude-code-best/ 前缀: - builtin-tools → @claude-code-best/builtin-tools - mcp-client → @claude-code-best/mcp-client - agent-tools → @claude-code-best/agent-tools Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: 修复 node 环境没有 bun 的问题 --------- Co-authored-by: Eric-Guo <eric.guocz@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* API-based search adapter — delegates to Anthropic's server-side
|
||||
* web_search_20250305 tool via a secondary API call.
|
||||
*/
|
||||
|
||||
import type {
|
||||
BetaContentBlock,
|
||||
BetaWebSearchTool20250305,
|
||||
} 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 { createUserMessage } from 'src/utils/messages.js'
|
||||
import { getMainLoopModel, getSmallFastModel } from 'src/utils/model/model.js'
|
||||
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 {
|
||||
return {
|
||||
type: 'web_search_20250305',
|
||||
name: 'web_search',
|
||||
allowed_domains: input.allowedDomains,
|
||||
blocked_domains: input.blockedDomains,
|
||||
max_uses: 8,
|
||||
}
|
||||
}
|
||||
|
||||
export class ApiSearchAdapter implements WebSearchAdapter {
|
||||
async search(
|
||||
query: string,
|
||||
options: SearchOptions,
|
||||
): Promise<SearchResult[]> {
|
||||
const { signal, onProgress, allowedDomains, blockedDomains } = options
|
||||
|
||||
const userMessage = createUserMessage({
|
||||
content: 'Perform a web search for the query: ' + query,
|
||||
})
|
||||
const toolSchema = makeToolSchema({ allowedDomains, blockedDomains })
|
||||
|
||||
const useHaiku = getFeatureValue_CACHED_MAY_BE_STALE('tengu_plum_vx3', false)
|
||||
|
||||
const queryStream = queryModelWithStreaming({
|
||||
messages: [userMessage],
|
||||
systemPrompt: asSystemPrompt([
|
||||
'You are an assistant for performing a web search tool use',
|
||||
]),
|
||||
thinkingConfig: useHaiku
|
||||
? { type: 'disabled' as const }
|
||||
: { type: 'enabled' as const, budgetTokens: 10000 },
|
||||
tools: [],
|
||||
signal: signal ?? new AbortController().signal,
|
||||
options: {
|
||||
getToolPermissionContext: async () => ({
|
||||
mode: 'default' as const,
|
||||
additionalWorkingDirectories: new Map(),
|
||||
alwaysAllowRules: {},
|
||||
alwaysDenyRules: {},
|
||||
alwaysAskRules: {},
|
||||
isBypassPermissionsModeAvailable: false,
|
||||
}),
|
||||
model: useHaiku ? getSmallFastModel() : getMainLoopModel(),
|
||||
toolChoice: useHaiku ? { type: 'tool' as const, name: 'web_search' } : undefined,
|
||||
isNonInteractiveSession: false,
|
||||
hasAppendSystemPrompt: false,
|
||||
extraToolSchemas: [toolSchema],
|
||||
querySource: 'web_search_tool' as const,
|
||||
agents: [],
|
||||
mcpTools: [],
|
||||
agentId: undefined,
|
||||
effortValue: undefined,
|
||||
},
|
||||
})
|
||||
|
||||
const allContentBlocks: BetaContentBlock[] = []
|
||||
let currentToolUseId: string | null = null
|
||||
let currentToolUseJson = ''
|
||||
const toolUseQueries = new Map<string, string>()
|
||||
let progressCounter = 0
|
||||
|
||||
for await (const event of queryStream) {
|
||||
if (event.type === 'assistant') {
|
||||
const msg = event as { message: { content: BetaContentBlock[] } }
|
||||
allContentBlocks.push(...msg.message.content)
|
||||
continue
|
||||
}
|
||||
|
||||
if (event.type === 'stream_event') {
|
||||
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 }
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
|
||||
if (streamEvt.event?.type === 'content_block_start') {
|
||||
const contentBlock = streamEvt.event.content_block
|
||||
if (contentBlock && contentBlock.type === 'server_tool_use') {
|
||||
currentToolUseId = contentBlock.id as string
|
||||
currentToolUseJson = ''
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
try {
|
||||
const queryMatch = currentToolUseJson.match(
|
||||
/"query"\s*:\s*"((?:[^"\\]|\\.)*)"/,
|
||||
)
|
||||
if (queryMatch && queryMatch[1]) {
|
||||
const parsedQuery = jsonParse('"' + queryMatch[1] + '"')
|
||||
if (
|
||||
!toolUseQueries.has(currentToolUseId) ||
|
||||
toolUseQueries.get(currentToolUseId) !== parsedQuery
|
||||
) {
|
||||
toolUseQueries.set(currentToolUseId, parsedQuery)
|
||||
progressCounter++
|
||||
onProgress?.({
|
||||
type: 'query_update',
|
||||
query: parsedQuery,
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore parsing errors for partial JSON
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (streamEvt.event?.type === 'content_block_start') {
|
||||
const contentBlock = streamEvt.event.content_block
|
||||
if (contentBlock && contentBlock.type === 'web_search_tool_result') {
|
||||
const toolUseId = contentBlock.tool_use_id as string
|
||||
const actualQuery = toolUseQueries.get(toolUseId) || query
|
||||
const content = contentBlock.content
|
||||
progressCounter++
|
||||
onProgress?.({
|
||||
type: 'search_results_received',
|
||||
resultCount: Array.isArray(content) ? content.length : 0,
|
||||
query: actualQuery,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract SearchResult[] from content blocks
|
||||
return extractSearchResults(allContentBlocks)
|
||||
}
|
||||
}
|
||||
|
||||
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 }>) {
|
||||
results.push({
|
||||
title: r.title,
|
||||
url: r.url,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
Reference in New Issue
Block a user