Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
claude-code tools.ts
17 kB · 389 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390// biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reorderedimport { toolMatchesName, type Tool, type Tools } from './Tool.js'import { AgentTool } from './tools/AgentTool/AgentTool.js'import { SkillTool } from './tools/SkillTool/SkillTool.js'import { BashTool } from './tools/BashTool/BashTool.js'import { FileEditTool } from './tools/FileEditTool/FileEditTool.js'import { FileReadTool } from './tools/FileReadTool/FileReadTool.js'import { FileWriteTool } from './tools/FileWriteTool/FileWriteTool.js'import { GlobTool } from './tools/GlobTool/GlobTool.js'import { NotebookEditTool } from './tools/NotebookEditTool/NotebookEditTool.js'import { WebFetchTool } from './tools/WebFetchTool/WebFetchTool.js'import { TaskStopTool } from './tools/TaskStopTool/TaskStopTool.js'import { BriefTool } from './tools/BriefTool/BriefTool.js'// Dead code elimination: conditional import for ant-only tools/* eslint-disable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */const REPLTool = process.env.USER_TYPE === 'ant' ? require('./tools/REPLTool/REPLTool.js').REPLTool : nullconst SuggestBackgroundPRTool = process.env.USER_TYPE === 'ant' ? require('./tools/SuggestBackgroundPRTool/SuggestBackgroundPRTool.js') .SuggestBackgroundPRTool : nullconst SleepTool = feature('PROACTIVE') || feature('KAIROS') ? require('./tools/SleepTool/SleepTool.js').SleepTool : nullconst cronTools = feature('AGENT_TRIGGERS') ? [ require('./tools/ScheduleCronTool/CronCreateTool.js').CronCreateTool, require('./tools/ScheduleCronTool/CronDeleteTool.js').CronDeleteTool, require('./tools/ScheduleCronTool/CronListTool.js').CronListTool, ] : []const RemoteTriggerTool = feature('AGENT_TRIGGERS_REMOTE') ? require('./tools/RemoteTriggerTool/RemoteTriggerTool.js').RemoteTriggerTool : nullconst MonitorTool = feature('MONITOR_TOOL') ? require('./tools/MonitorTool/MonitorTool.js').MonitorTool : nullconst SendUserFileTool = feature('KAIROS') ? require('./tools/SendUserFileTool/SendUserFileTool.js').SendUserFileTool : nullconst PushNotificationTool = feature('KAIROS') || feature('KAIROS_PUSH_NOTIFICATION') ? require('./tools/PushNotificationTool/PushNotificationTool.js') .PushNotificationTool : nullconst SubscribePRTool = feature('KAIROS_GITHUB_WEBHOOKS') ? require('./tools/SubscribePRTool/SubscribePRTool.js').SubscribePRTool : null/* eslint-enable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */import { TaskOutputTool } from './tools/TaskOutputTool/TaskOutputTool.js'import { WebSearchTool } from './tools/WebSearchTool/WebSearchTool.js'import { TodoWriteTool } from './tools/TodoWriteTool/TodoWriteTool.js'import { ExitPlanModeV2Tool } from './tools/ExitPlanModeTool/ExitPlanModeV2Tool.js'import { TestingPermissionTool } from './tools/testing/TestingPermissionTool.js'import { GrepTool } from './tools/GrepTool/GrepTool.js'import { TungstenTool } from './tools/TungstenTool/TungstenTool.js'// Lazy require to break circular dependency: tools.ts -> TeamCreateTool/TeamDeleteTool -> ... -> tools.ts/* eslint-disable @typescript-eslint/no-require-imports */const getTeamCreateTool = () => require('./tools/TeamCreateTool/TeamCreateTool.js') .TeamCreateTool as typeof import('./tools/TeamCreateTool/TeamCreateTool.js').TeamCreateToolconst getTeamDeleteTool = () => require('./tools/TeamDeleteTool/TeamDeleteTool.js') .TeamDeleteTool as typeof import('./tools/TeamDeleteTool/TeamDeleteTool.js').TeamDeleteToolconst getSendMessageTool = () => require('./tools/SendMessageTool/SendMessageTool.js') .SendMessageTool as typeof import('./tools/SendMessageTool/SendMessageTool.js').SendMessageTool/* eslint-enable @typescript-eslint/no-require-imports */import { AskUserQuestionTool } from './tools/AskUserQuestionTool/AskUserQuestionTool.js'import { LSPTool } from './tools/LSPTool/LSPTool.js'import { ListMcpResourcesTool } from './tools/ListMcpResourcesTool/ListMcpResourcesTool.js'import { ReadMcpResourceTool } from './tools/ReadMcpResourceTool/ReadMcpResourceTool.js'import { ToolSearchTool } from './tools/ToolSearchTool/ToolSearchTool.js'import { EnterPlanModeTool } from './tools/EnterPlanModeTool/EnterPlanModeTool.js'import { EnterWorktreeTool } from './tools/EnterWorktreeTool/EnterWorktreeTool.js'import { ExitWorktreeTool } from './tools/ExitWorktreeTool/ExitWorktreeTool.js'import { ConfigTool } from './tools/ConfigTool/ConfigTool.js'import { TaskCreateTool } from './tools/TaskCreateTool/TaskCreateTool.js'import { TaskGetTool } from './tools/TaskGetTool/TaskGetTool.js'import { TaskUpdateTool } from './tools/TaskUpdateTool/TaskUpdateTool.js'import { TaskListTool } from './tools/TaskListTool/TaskListTool.js'import uniqBy from 'lodash-es/uniqBy.js'import { isToolSearchEnabledOptimistic } from './utils/toolSearch.js'import { isTodoV2Enabled } from './utils/tasks.js'// Dead code elimination: conditional import for CLAUDE_CODE_VERIFY_PLAN/* eslint-disable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */const VerifyPlanExecutionTool = process.env.CLAUDE_CODE_VERIFY_PLAN === 'true' ? require('./tools/VerifyPlanExecutionTool/VerifyPlanExecutionTool.js') .VerifyPlanExecutionTool : null/* eslint-enable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */import { SYNTHETIC_OUTPUT_TOOL_NAME } from './tools/SyntheticOutputTool/SyntheticOutputTool.js'export { ALL_AGENT_DISALLOWED_TOOLS, CUSTOM_AGENT_DISALLOWED_TOOLS, ASYNC_AGENT_ALLOWED_TOOLS, COORDINATOR_MODE_ALLOWED_TOOLS,} from './constants/tools.js'import { feature } from 'bun:bundle'// Dead code elimination: conditional import for OVERFLOW_TEST_TOOL/* eslint-disable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */const OverflowTestTool = feature('OVERFLOW_TEST_TOOL') ? require('./tools/OverflowTestTool/OverflowTestTool.js').OverflowTestTool : nullconst CtxInspectTool = feature('CONTEXT_COLLAPSE') ? require('./tools/CtxInspectTool/CtxInspectTool.js').CtxInspectTool : nullconst TerminalCaptureTool = feature('TERMINAL_PANEL') ? require('./tools/TerminalCaptureTool/TerminalCaptureTool.js') .TerminalCaptureTool : nullconst WebBrowserTool = feature('WEB_BROWSER_TOOL') ? require('./tools/WebBrowserTool/WebBrowserTool.js').WebBrowserTool : nullconst coordinatorModeModule = feature('COORDINATOR_MODE') ? (require('./coordinator/coordinatorMode.js') as typeof import('./coordinator/coordinatorMode.js')) : nullconst SnipTool = feature('HISTORY_SNIP') ? require('./tools/SnipTool/SnipTool.js').SnipTool : nullconst ListPeersTool = feature('UDS_INBOX') ? require('./tools/ListPeersTool/ListPeersTool.js').ListPeersTool : nullconst WorkflowTool = feature('WORKFLOW_SCRIPTS') ? (() => { require('./tools/WorkflowTool/bundled/index.js').initBundledWorkflows() return require('./tools/WorkflowTool/WorkflowTool.js').WorkflowTool })() : null/* eslint-enable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */import type { ToolPermissionContext } from './Tool.js'import { getDenyRuleForTool } from './utils/permissions/permissions.js'import { hasEmbeddedSearchTools } from './utils/embeddedTools.js'import { isEnvTruthy } from './utils/envUtils.js'import { isPowerShellToolEnabled } from './utils/shell/shellToolUtils.js'import { isAgentSwarmsEnabled } from './utils/agentSwarmsEnabled.js'import { isWorktreeModeEnabled } from './utils/worktreeModeEnabled.js'import { REPL_TOOL_NAME, REPL_ONLY_TOOLS, isReplModeEnabled,} from './tools/REPLTool/constants.js'export { REPL_ONLY_TOOLS }/* eslint-disable @typescript-eslint/no-require-imports */const getPowerShellTool = () => { if (!isPowerShellToolEnabled()) return null return ( require('./tools/PowerShellTool/PowerShellTool.js') as typeof import('./tools/PowerShellTool/PowerShellTool.js') ).PowerShellTool}/* eslint-enable @typescript-eslint/no-require-imports */
/** * Predefined tool presets that can be used with --tools flag */export const TOOL_PRESETS = ['default'] as const
export type ToolPreset = (typeof TOOL_PRESETS)[number]
export function parseToolPreset(preset: string): ToolPreset | null { const presetString = preset.toLowerCase() if (!TOOL_PRESETS.includes(presetString as ToolPreset)) { return null } return presetString as ToolPreset}
/** * Get the list of tool names for a given preset * Filters out tools that are disabled via isEnabled() check * @param preset The preset name * @returns Array of tool names */export function getToolsForDefaultPreset(): string[] { const tools = getAllBaseTools() const isEnabled = tools.map(tool => tool.isEnabled()) return tools.filter((_, i) => isEnabled[i]).map(tool => tool.name)}
/** * Get the complete exhaustive list of all tools that could be available * in the current environment (respecting process.env flags). * This is the source of truth for ALL tools. *//** * NOTE: This MUST stay in sync with https://console.statsig.com/4aF3Ewatb6xPVpCwxb5nA3/dynamic_configs/claude_code_global_system_caching, in order to cache the system prompt across users. */export function getAllBaseTools(): Tools { return [ AgentTool, TaskOutputTool, BashTool, // Ant-native builds have bfs/ugrep embedded in the bun binary (same ARGV0 // trick as ripgrep). When available, find/grep in Claude's shell are aliased // to these fast tools, so the dedicated Glob/Grep tools are unnecessary. ...(hasEmbeddedSearchTools() ? [] : [GlobTool, GrepTool]), ExitPlanModeV2Tool, FileReadTool, FileEditTool, FileWriteTool, NotebookEditTool, WebFetchTool, TodoWriteTool, WebSearchTool, TaskStopTool, AskUserQuestionTool, SkillTool, EnterPlanModeTool, ...(process.env.USER_TYPE === 'ant' ? [ConfigTool] : []), ...(process.env.USER_TYPE === 'ant' ? [TungstenTool] : []), ...(SuggestBackgroundPRTool ? [SuggestBackgroundPRTool] : []), ...(WebBrowserTool ? [WebBrowserTool] : []), ...(isTodoV2Enabled() ? [TaskCreateTool, TaskGetTool, TaskUpdateTool, TaskListTool] : []), ...(OverflowTestTool ? [OverflowTestTool] : []), ...(CtxInspectTool ? [CtxInspectTool] : []), ...(TerminalCaptureTool ? [TerminalCaptureTool] : []), ...(isEnvTruthy(process.env.ENABLE_LSP_TOOL) ? [LSPTool] : []), ...(isWorktreeModeEnabled() ? [EnterWorktreeTool, ExitWorktreeTool] : []), getSendMessageTool(), ...(ListPeersTool ? [ListPeersTool] : []), ...(isAgentSwarmsEnabled() ? [getTeamCreateTool(), getTeamDeleteTool()] : []), ...(VerifyPlanExecutionTool ? [VerifyPlanExecutionTool] : []), ...(process.env.USER_TYPE === 'ant' && REPLTool ? [REPLTool] : []), ...(WorkflowTool ? [WorkflowTool] : []), ...(SleepTool ? [SleepTool] : []), ...cronTools, ...(RemoteTriggerTool ? [RemoteTriggerTool] : []), ...(MonitorTool ? [MonitorTool] : []), BriefTool, ...(SendUserFileTool ? [SendUserFileTool] : []), ...(PushNotificationTool ? [PushNotificationTool] : []), ...(SubscribePRTool ? [SubscribePRTool] : []), ...(getPowerShellTool() ? [getPowerShellTool()] : []), ...(SnipTool ? [SnipTool] : []), ...(process.env.NODE_ENV === 'test' ? [TestingPermissionTool] : []), ListMcpResourcesTool, ReadMcpResourceTool, // Include ToolSearchTool when tool search might be enabled (optimistic check) // The actual decision to defer tools happens at request time in claude.ts ...(isToolSearchEnabledOptimistic() ? [ToolSearchTool] : []), ]}
/** * Filters out tools that are blanket-denied by the permission context. * A tool is filtered out if there's a deny rule matching its name with no * ruleContent (i.e., a blanket deny for that tool). * * Uses the same matcher as the runtime permission check (step 1a), so MCP * server-prefix rules like `mcp__server` strip all tools from that server * before the model sees them — not just at call time. */export function filterToolsByDenyRules< T extends { name: string mcpInfo?: { serverName: string; toolName: string } },>(tools: readonly T[], permissionContext: ToolPermissionContext): T[] { return tools.filter(tool => !getDenyRuleForTool(permissionContext, tool))}
export const getTools = (permissionContext: ToolPermissionContext): Tools => { // Simple mode: only Bash, Read, and Edit tools if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) { // --bare + REPL mode: REPL wraps Bash/Read/Edit/etc inside the VM, so // return REPL instead of the raw primitives. Matches the non-bare path // below which also hides REPL_ONLY_TOOLS when REPL is enabled. if (isReplModeEnabled() && REPLTool) { const replSimple: Tool[] = [REPLTool] if ( feature('COORDINATOR_MODE') && coordinatorModeModule?.isCoordinatorMode() ) { replSimple.push(TaskStopTool, getSendMessageTool()) } return filterToolsByDenyRules(replSimple, permissionContext) } const simpleTools: Tool[] = [BashTool, FileReadTool, FileEditTool] // When coordinator mode is also active, include AgentTool and TaskStopTool // so the coordinator gets Task+TaskStop (via useMergedTools filtering) and // workers get Bash/Read/Edit (via filterToolsForAgent filtering). if ( feature('COORDINATOR_MODE') && coordinatorModeModule?.isCoordinatorMode() ) { simpleTools.push(AgentTool, TaskStopTool, getSendMessageTool()) } return filterToolsByDenyRules(simpleTools, permissionContext) }
// Get all base tools and filter out special tools that get added conditionally const specialTools = new Set([ ListMcpResourcesTool.name, ReadMcpResourceTool.name, SYNTHETIC_OUTPUT_TOOL_NAME, ])
const tools = getAllBaseTools().filter(tool => !specialTools.has(tool.name))
// Filter out tools that are denied by the deny rules let allowedTools = filterToolsByDenyRules(tools, permissionContext)
// When REPL mode is enabled, hide primitive tools from direct use. // They're still accessible inside REPL via the VM context. if (isReplModeEnabled()) { const replEnabled = allowedTools.some(tool => toolMatchesName(tool, REPL_TOOL_NAME), ) if (replEnabled) { allowedTools = allowedTools.filter( tool => !REPL_ONLY_TOOLS.has(tool.name), ) } }
const isEnabled = allowedTools.map(_ => _.isEnabled()) return allowedTools.filter((_, i) => isEnabled[i])}
/** * Assemble the full tool pool for a given permission context and MCP tools. * * This is the single source of truth for combining built-in tools with MCP tools. * Both REPL.tsx (via useMergedTools hook) and runAgent.ts (for coordinator workers) * use this function to ensure consistent tool pool assembly. * * The function: * 1. Gets built-in tools via getTools() (respects mode filtering) * 2. Filters MCP tools by deny rules * 3. Deduplicates by tool name (built-in tools take precedence) * * @param permissionContext - Permission context for filtering built-in tools * @param mcpTools - MCP tools from appState.mcp.tools * @returns Combined, deduplicated array of built-in and MCP tools */export function assembleToolPool( permissionContext: ToolPermissionContext, mcpTools: Tools,): Tools { const builtInTools = getTools(permissionContext)
// Filter out MCP tools that are in the deny list const allowedMcpTools = filterToolsByDenyRules(mcpTools, permissionContext)
// Sort each partition for prompt-cache stability, keeping built-ins as a // contiguous prefix. The server's claude_code_system_cache_policy places a // global cache breakpoint after the last prefix-matched built-in tool; a flat // sort would interleave MCP tools into built-ins and invalidate all downstream // cache keys whenever an MCP tool sorts between existing built-ins. uniqBy // preserves insertion order, so built-ins win on name conflict. // Avoid Array.toSorted (Node 20+) — we support Node 18. builtInTools is // readonly so copy-then-sort; allowedMcpTools is a fresh .filter() result. const byName = (a: Tool, b: Tool) => a.name.localeCompare(b.name) return uniqBy( [...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)), 'name', )}
/** * Get all tools including both built-in tools and MCP tools. * * This is the preferred function when you need the complete tools list for: * - Tool search threshold calculations (isToolSearchEnabled) * - Token counting that includes MCP tools * - Any context where MCP tools should be considered * * Use getTools() only when you specifically need just built-in tools. * * @param permissionContext - Permission context for filtering built-in tools * @param mcpTools - MCP tools from appState.mcp.tools * @returns Combined array of built-in and MCP tools */export function getMergedTools( permissionContext: ToolPermissionContext, mcpTools: Tools,): Tools { const builtInTools = getTools(permissionContext) return [...builtInTools, ...mcpTools]}