Add complete Korean-language technical documentation analyzing Claude Code CLI internals, organized in a leveled guide structure (Level 1-3 + Appendix). Level 1 (입문): architecture overview, request lifecycle, key concepts Level 2 (시스템): QueryEngine, Tool system, Command system, Permission system, Agent coordinator, UI/Ink components Level 3 (심화): MCP/LSP integration, IDE bridge, Plugin/Skill system, Context compression, OAuth/Auth, Telemetry Appendix: Korean-English glossary, file map, design patterns Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
26 KiB
Tool 시스템: 도구 레지스트리 & 실행 파이프라인
1. 개요
Tool 시스템은 Claude Code의 핵심 확장 메커니즘이다. LLM(Large Language Model, 대규모 언어 모델)이 텍스트 생성 외에 실제 작업을 수행할 수 있도록 40개 이상의 도구를 등록, 검증, 실행하는 파이프라인 전체를 관할한다.
역할 요약:
- 등록: 빌트인(built-in, 내장) 도구와 MCP(Model Context Protocol) 도구를 단일 풀(pool)로 조합
- 검증: Zod v4 스키마 기반 입력 유효성 검사 및 권한 확인
- 실행:
ToolUseContext를 의존성 주입(dependency injection) 컨테이너로 활용하여 각 도구에 실행 환경 전달 - 결과 처리: 결과 포맷팅, 대용량 결과의 디스크 저장, UI 렌더링
파일 구조:
| 파일 | 역할 |
|---|---|
src/Tool.ts |
핵심 타입 및 인터페이스 정의 |
src/tools.ts |
도구 레지스트리 및 조합 로직 |
src/tools/*/ |
개별 도구 구현체 (40개 이상) |
2. 아키텍처 다이어그램
graph TD
A[LLM tool_use 블록 반환] --> B[toolMatchesName으로 도구 탐색]
B --> C{도구 발견?}
C -- 아니오 --> D[에러 반환]
C -- 예 --> E[Zod 스키마 입력 검증]
E --> F{validateInput 통과?}
F -- 실패 --> G[ValidationResult 에러 반환]
F -- 통과 --> H[checkPermissions 호출]
H --> I{권한 결과}
I -- deny --> J[거부 메시지 반환]
I -- ask --> K[사용자에게 확인 요청]
I -- allow --> L[tool.call 실행]
K -- 승인 --> L
K -- 거부 --> J
L --> M[ToolResult 반환]
M --> N{결과 크기 > maxResultSizeChars?}
N -- 예 --> O[디스크에 저장, 프리뷰 반환]
N -- 아니오 --> P[mapToolResultToToolResultBlockParam]
O --> P
P --> Q[렌더링 및 메시지 히스토리 추가]
subgraph "도구 레지스트리 (tools.ts)"
R[getAllBaseTools] --> S[getTools]
S --> T[filterToolsByDenyRules]
T --> U[assembleToolPool]
U --> V[MCP 도구 병합]
end
subgraph "개별 도구 구현체 (src/tools/*/)"
W[BashTool]
X[FileReadTool]
Y[AgentTool]
Z[기타 40+ 도구]
end
3. 핵심 타입 및 인터페이스
src/Tool.ts는 792줄 규모의 타입 전용 파일로, 런타임 코드를 최소화하고 타입 시스템을 통해 도구 계약(contract)을 강제한다.
3.1 ToolInputJSONSchema
export type ToolInputJSONSchema = {
[x: string]: unknown
type: 'object'
properties?: {
[x: string]: unknown
}
}
MCP 도구처럼 Zod 스키마 대신 JSON Schema를 직접 사용하는 도구를 위한 타입이다. 빌트인 도구는 inputSchema: Input(Zod 기반)을 사용하고, MCP 도구는 inputJSONSchema를 사용하는 두 경로가 공존한다.
3.2 ToolPermissionContext
export type ToolPermissionContext = DeepImmutable<{
mode: PermissionMode
additionalWorkingDirectories: Map<string, AdditionalWorkingDirectory>
alwaysAllowRules: ToolPermissionRulesBySource
alwaysDenyRules: ToolPermissionRulesBySource
alwaysAskRules: ToolPermissionRulesBySource
isBypassPermissionsModeAvailable: boolean
isAutoModeAvailable?: boolean
strippedDangerousRules?: ToolPermissionRulesBySource
shouldAvoidPermissionPrompts?: boolean
awaitAutomatedChecksBeforeDialog?: boolean
prePlanMode?: PermissionMode
}>
DeepImmutable<T>로 감싸져 있어 런타임 중 권한 컨텍스트가 변경되지 않음을 타입 수준에서 보장한다. alwaysAllowRules, alwaysDenyRules, alwaysAskRules는 각각 소스(source)별로 분류된 규칙 맵으로, .claude/settings.json의 훅(hook) 설정이 여기에 반영된다.
3.3 ToolUseContext
전체 시스템에서 가장 복잡한 타입이다. 모든 tool.call()에 전달되는 의존성 주입 컨테이너 역할을 한다.
주요 필드 분류:
| 카테고리 | 필드 | 설명 |
|---|---|---|
| 설정 | options.tools, options.commands, options.mainLoopModel |
런타임 설정 |
| 상태 관리 | getAppState(), setAppState() |
React 상태 접근자 |
| 세션 인프라 | setAppStateForTasks |
서브에이전트에서도 루트 스토어에 접근 가능 |
| UI 콜백 | setToolJSX, addNotification, appendSystemMessage |
UI 업데이트 함수 |
| 중단 제어 | abortController |
도구 실행 취소 신호 |
| 파일 캐시 | readFileState: FileStateCache |
LRU 캐시 기반 파일 상태 |
| 컨텍스트 추적 | queryTracking, toolDecisions |
체인 추적 및 결정 기록 |
| 에이전트 식별 | agentId, agentType |
서브에이전트 구분 |
| 권한 관련 | localDenialTracking, requireCanUseTool |
비동기 서브에이전트용 거부 추적 |
| 프롬프트 캐시 | renderedSystemPrompt, contentReplacementState |
캐시 공유 최적화 |
setAppStateForTasks는 일반 setAppState와 다르게 비동기 서브에이전트에서도 루트 AppState에 접근할 수 있도록 설계된 점이 특징이다. 일반 setAppState는 비동기 에이전트에서 no-op(무작동)으로 작동하여 격리를 유지하지만, 세션 스코프 인프라(예: 백그라운드 태스크 등록)는 루트까지 전파되어야 하기 때문이다.
3.4 ValidationResult
export type ValidationResult =
| { result: true }
| { result: false; message: string; errorCode: number }
도구 실행 전 사전 검증(pre-flight validation) 결과 타입이다. 실패 시 message가 LLM에게 반환되어 모델이 입력을 수정할 수 있도록 안내한다.
3.5 Tool 인터페이스
export type Tool<
Input extends AnyObject = AnyObject,
Output = unknown,
P extends ToolProgressData = ToolProgressData,
> = {
name: string
aliases?: string[]
inputSchema: Input
inputJSONSchema?: ToolInputJSONSchema
maxResultSizeChars: number
call(args, context, canUseTool, parentMessage, onProgress?): Promise<ToolResult<Output>>
description(input, options): Promise<string>
checkPermissions(input, context): Promise<PermissionResult>
validateInput?(input, context): Promise<ValidationResult>
isConcurrencySafe(input): boolean
isReadOnly(input): boolean
isDestructive?(input): boolean
// ... UI 렌더링 메서드 다수
}
제네릭 파라미터 3개가 입력 스키마 타입(Input), 출력 타입(Output), 진행 상태 타입(P)을 각각 고정한다. UI 렌더링 관련 메서드(renderToolResultMessage, renderToolUseMessage 등)가 도구 인터페이스에 직접 포함된 점이 이 설계의 특징으로, 도구 로직과 렌더링 로직이 같은 모듈 내에 공존한다.
3.6 buildTool 팩토리 함수
export function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D> {
return {
...TOOL_DEFAULTS,
userFacingName: () => def.name,
...def,
} as BuiltTool<D>
}
모든 도구는 buildTool을 통해 생성되며, 다음 기본값이 주입된다:
| 메서드 | 기본값 | 설계 원칙 |
|---|---|---|
isEnabled |
() => true |
명시적 비활성화 없이는 활성 |
isConcurrencySafe |
() => false |
실패 안전(fail-closed): 기본적으로 동시 실행 불가 |
isReadOnly |
() => false |
기본적으로 쓰기 작업으로 간주 |
isDestructive |
() => false |
기본적으로 비파괴적 |
checkPermissions |
allow 반환 |
일반 권한 시스템에 위임 |
toAutoClassifierInput |
() => '' |
보안 분류기에서 제외 |
userFacingName |
() => def.name |
도구명을 그대로 표시 |
BuiltTool<D> 타입은 타입 수준 스프레드(spread)를 통해 런타임 {...TOOL_DEFAULTS, ...def} 의미론을 정확히 반영한다.
3.7 Tools 타입
export type Tools = readonly Tool[]
Tool[] 대신 이 타입을 사용함으로써 코드베이스 전체에서 도구 집합이 조합, 전달, 필터링되는 지점을 타입 시스템으로 추적할 수 있다.
4. Tool 레지스트리 (tools.ts)
src/tools.ts는 도구 등록 방식을 세 가지 패턴으로 분류한다.
4.1 정적 임포트 (Static Import)
핵심 도구들은 ES 모듈 정적 임포트로 등록된다:
import { AgentTool } from './tools/AgentTool/AgentTool.js'
import { BashTool } from './tools/BashTool/BashTool.js'
import { FileEditTool } from './tools/FileEditTool/FileEditTool.js'
// ...
번들러(bundler)가 빌드 시점에 포함 여부를 결정할 수 있어 트리 셰이킹(tree shaking)이 적용된다.
4.2 조건부 require() — 기능 플래그 기반
bun:bundle의 feature() 함수를 활용한 dead code elimination(데드 코드 제거) 패턴이다:
import { feature } from 'bun:bundle'
const SleepTool = feature('PROACTIVE') || feature('KAIROS')
? require('./tools/SleepTool/SleepTool.js').SleepTool
: null
const cronTools = feature('AGENT_TRIGGERS')
? [
require('./tools/ScheduleCronTool/CronCreateTool.js').CronCreateTool,
// ...
]
: []
feature() 플래그가 false인 경우, 번들러는 해당 require() 브랜치 전체를 빌드 산출물에서 제거한다. 이는 프로덕션 번들 크기를 최소화하는 동시에 기능별 점진적 배포를 가능하게 한다.
4.3 조건부 require() — 환경 변수 기반
USER_TYPE 환경 변수로 Ant(내부 Anthropic) 전용 도구를 분리한다:
const REPLTool =
process.env.USER_TYPE === 'ant'
? require('./tools/REPLTool/REPLTool.js').REPLTool
: null
이 패턴을 사용하는 도구: REPLTool, SuggestBackgroundPRTool, ConfigTool, TungstenTool.
4.4 지연 require() — 순환 의존성 해결
TeamCreateTool, TeamDeleteTool, SendMessageTool은 순환 의존성(circular dependency)을 해결하기 위해 지연 로딩(lazy loading)된다:
// Lazy require to break circular dependency: tools.ts -> TeamCreateTool/TeamDeleteTool -> ... -> tools.ts
const getTeamCreateTool = () =>
require('./tools/TeamCreateTool/TeamCreateTool.js')
.TeamCreateTool as typeof import('./tools/TeamCreateTool/TeamCreateTool.js').TeamCreateTool
이 팀 도구들은 tools.ts를 임포트하는 모듈 체인에 속하므로, 정적 임포트로 처리하면 모듈 초기화 시점에 순환이 발생한다. 지연 함수로 감싸면 실제 호출 시점까지 로딩을 미룰 수 있다.
4.5 getAllBaseTools() 함수
전체 도구 목록의 단일 진실 공급원(single source of truth)이다:
export function getAllBaseTools(): Tools {
return [
AgentTool,
TaskOutputTool,
BashTool,
...(hasEmbeddedSearchTools() ? [] : [GlobTool, GrepTool]),
// ...
...(isTodoV2Enabled()
? [TaskCreateTool, TaskGetTool, TaskUpdateTool, TaskListTool]
: []),
// ...
...(isToolSearchEnabledOptimistic() ? [ToolSearchTool] : []),
]
}
주석에 따르면 이 목록은 Statsig 시스템 프롬프트 캐싱 설정(claude_code_global_system_caching)과 동기화를 유지해야 한다. 도구 순서가 바뀌면 프롬프트 캐시 키가 무효화될 수 있기 때문이다.
4.6 getTools() 함수
권한 컨텍스트를 적용하여 실제 사용 가능한 도구 목록을 반환한다:
export const getTools = (permissionContext: ToolPermissionContext): Tools => {
// 1. CLAUDE_CODE_SIMPLE 환경 변수: Bash/Read/Edit만 반환
if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) { ... }
// 2. 특수 도구 제외 (ListMcpResources 등 별도 경로로 추가)
const tools = getAllBaseTools().filter(tool => !specialTools.has(tool.name))
// 3. Deny 규칙으로 도구 필터링
let allowedTools = filterToolsByDenyRules(tools, permissionContext)
// 4. REPL 모드: 원시 도구 숨김 (REPL 내부 VM에서만 접근 가능)
if (isReplModeEnabled()) { ... }
// 5. isEnabled() 검사
return allowedTools.filter((_, i) => isEnabled[i])
}
4.7 assembleToolPool() 함수
빌트인 도구와 MCP 도구를 하나의 풀로 결합하는 최종 조합 함수다:
export function assembleToolPool(
permissionContext: ToolPermissionContext,
mcpTools: Tools,
): Tools {
const builtInTools = getTools(permissionContext)
const allowedMcpTools = filterToolsByDenyRules(mcpTools, permissionContext)
// 빌트인 도구를 앞쪽 연속 블록으로 유지하여 프롬프트 캐시 안정성 확보
const byName = (a: Tool, b: Tool) => a.name.localeCompare(b.name)
return uniqBy(
[...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)),
'name',
)
}
정렬 시 빌트인 도구와 MCP 도구를 별도 파티션으로 유지하는 이유는 프롬프트 캐시 안정성(prompt cache stability) 때문이다. 서버 측 캐시 정책이 빌트인 도구 블록 끝에 캐시 브레이크포인트(cache breakpoint)를 두므로, 두 파티션을 평탄하게 섞으면 MCP 도구가 빌트인 목록 사이에 끼어들어 캐시 키가 무효화된다.
5. Tool 실행 파이프라인
LLM이 tool_use 블록을 반환한 시점부터 결과가 대화 히스토리에 기록되기까지의 전체 흐름이다.
단계 1: 도구 탐색
export function toolMatchesName(
tool: { name: string; aliases?: string[] },
name: string,
): boolean {
return tool.name === name || (tool.aliases?.includes(name) ?? false)
}
export function findToolByName(tools: Tools, name: string): Tool | undefined {
return tools.find(t => toolMatchesName(t, name))
}
aliases 필드는 도구명이 변경될 때 하위 호환성(backward compatibility)을 유지하기 위한 장치다. 구 도구명으로 호출된 LLM 요청도 새 도구로 라우팅된다.
단계 2: 입력 검증
Zod v4 스키마로 입력을 파싱한다. 파싱 실패 시 구조화된 에러 메시지가 LLM에 반환되어 모델이 입력을 수정하도록 유도한다.
옵셔널 메서드 validateInput이 구현된 경우, Zod 파싱 통과 후 추가적인 도구별 검증이 실행된다:
validateInput?(
input: z.infer<Input>,
context: ToolUseContext,
): Promise<ValidationResult>
단계 3: 권한 확인
checkPermissions(
input: z.infer<Input>,
context: ToolUseContext,
): Promise<PermissionResult>
PermissionResult의 behavior 필드에 따라 실행 경로가 분기된다:
| behavior | 동작 |
|---|---|
allow |
즉시 실행 |
deny |
거부 메시지 반환, LLM에 전달 |
ask |
사용자에게 확인 UI 표시 |
buildTool의 기본 구현은 allow를 반환하여 일반 권한 시스템(permissions.ts)에 위임한다. 도구별 특수 로직이 필요한 경우에만 이 메서드를 오버라이드한다.
단계 4: 실행
call(
args: z.infer<Input>,
context: ToolUseContext,
canUseTool: CanUseToolFn,
parentMessage: AssistantMessage,
onProgress?: ToolCallProgress<P>,
): Promise<ToolResult<Output>>
ToolUseContext는 도구가 필요로 하는 모든 의존성(앱 상태, 파일 캐시, 중단 컨트롤러, UI 콜백 등)을 담은 컨테이너로 전달된다. onProgress 콜백을 통해 실행 중 진행 상태를 스트리밍할 수 있다.
단계 5: 결과 처리
export type ToolResult<T> = {
data: T
newMessages?: (UserMessage | AssistantMessage | AttachmentMessage | SystemMessage)[]
contextModifier?: (context: ToolUseContext) => ToolUseContext
mcpMeta?: { _meta?: Record<string, unknown>; structuredContent?: Record<string, unknown> }
}
contextModifier는 동시 실행에 안전하지 않은 도구(isConcurrencySafe = false)에서만 사용 가능하며, 도구 결과에 따라 ToolUseContext를 변경할 수 있다. 예를 들어 EnterPlanModeTool은 이를 통해 권한 모드를 전환한다.
단계 6: 결과 직렬화 및 저장
maxResultSizeChars: number
mapToolResultToToolResultBlockParam(
content: Output,
toolUseID: string,
): ToolResultBlockParam
결과 크기가 maxResultSizeChars를 초과하면 디스크에 저장하고 LLM에는 파일 경로와 프리뷰만 전달한다. FileReadTool의 경우 이 값이 Infinity로 설정되어 있는데, 파일 내용을 디스크에 다시 저장하면 Read → 파일 → Read 순환이 발생하고 이미 자체적으로 크기 제한을 적용하기 때문이다.
단계 7: 에러 처리
도구는 에러 UI를 커스터마이즈할 수 있다:
renderToolUseErrorMessage?(
result: ToolResultBlockParam['content'],
options: { ... },
): React.ReactNode
이 메서드가 없으면 <FallbackToolUseErrorMessage />가 사용된다. 파일 탐색 도구처럼 "파일을 찾을 수 없습니다" 같은 도구별 에러 메시지가 필요한 경우에만 구현한다.
6. Tool 카테고리 분석
src/tools/ 디렉토리의 모든 도구를 카테고리별로 분류한다.
파일 조작
| 도구 | 설명 | 읽기 전용 |
|---|---|---|
FileReadTool |
파일 읽기, 이미지/PDF/노트북 지원 | O |
FileEditTool |
파일 일부 편집 (문자열 대체) | X |
FileWriteTool |
파일 생성 및 전체 덮어쓰기 | X |
GlobTool |
파일 패턴 탐색 | O |
GrepTool |
파일 내용 패턴 검색 (ripgrep 래핑) | O |
NotebookEditTool |
Jupyter 노트북 셀 편집 | X |
GlobTool과 GrepTool은 hasEmbeddedSearchTools()가 true인 경우(Ant 내부 빌드) 제외된다. Ant 빌드에는 bfs/ugrep이 Bun 바이너리에 내장되어 있고, Bash 내에서 find/grep 명령이 이 빠른 도구로 앨리어스(alias)되기 때문이다.
실행
| 도구 | 설명 |
|---|---|
BashTool |
셸 명령 실행 (샌드박스 지원) |
REPLTool |
격리된 VM 환경에서 코드 실행 (Ant 전용) |
PowerShellTool |
Windows PowerShell 명령 실행 |
BashTool은 파이프라인 구성 명령을 분석하여 검색/읽기/목록 조회 명령인지 판단하고 UI에서 접을 수 있게(collapsible) 표시한다. 이를 위해 BASH_SEARCH_COMMANDS, BASH_READ_COMMANDS, BASH_LIST_COMMANDS, BASH_SEMANTIC_NEUTRAL_COMMANDS 집합을 사용한다.
에이전트
| 도구 | 설명 |
|---|---|
AgentTool |
서브에이전트 생성 및 실행 |
SendMessageTool |
팀 내 에이전트 간 메시지 전송 |
TeamCreateTool |
에이전트 팀 생성 |
TeamDeleteTool |
에이전트 팀 해체 |
AgentTool은 시스템에서 가장 복잡한 도구로, 로컬/원격 에이전트 실행, 워크트리(worktree) 생성, 포크(fork) 서브에이전트, 코디네이터 모드 등 다수의 실행 경로를 포함한다. AgentTool의 임포트 목록만 80줄에 달한다.
태스크 관리
| 도구 | 설명 |
|---|---|
TaskCreateTool |
새 태스크 생성 |
TaskGetTool |
태스크 상태 조회 |
TaskListTool |
태스크 목록 조회 |
TaskUpdateTool |
태스크 업데이트 |
TaskOutputTool |
태스크 출력 스트리밍 |
TaskStopTool |
태스크 중단 |
태스크 관련 도구 6개는 isTodoV2Enabled() 플래그로 일괄 활성화/비활성화된다.
MCP / LSP
| 도구 | 설명 |
|---|---|
MCPTool |
MCP 서버 도구 래퍼 (동적) |
LSPTool |
Language Server Protocol 통합 |
McpAuthTool |
MCP 인증 처리 |
ListMcpResourcesTool |
MCP 서버 리소스 목록 |
ReadMcpResourceTool |
MCP 리소스 내용 읽기 |
ListMcpResourcesTool과 ReadMcpResourceTool은 getTools()에서 specialTools 집합으로 분리되어 별도 경로로 추가된다. LSPTool은 ENABLE_LSP_TOOL 환경 변수로 명시적으로 활성화해야 한다.
워크플로우 제어
| 도구 | 설명 |
|---|---|
EnterPlanModeTool |
Plan 모드 진입 (쓰기 작업 차단) |
ExitPlanModeV2Tool |
Plan 모드 종료 |
EnterWorktreeTool |
Git 워크트리 모드 진입 |
ExitWorktreeTool |
Git 워크트리 모드 종료 |
SkillTool |
스킬 파일 실행 |
ToolSearchTool |
지연 로드된 도구 검색 |
EnterWorktreeTool과 ExitWorktreeTool은 isWorktreeModeEnabled() 조건으로 등록된다.
사용자 상호작용
| 도구 | 설명 |
|---|---|
AskUserQuestionTool |
사용자에게 질문 및 응답 수집 |
BriefTool |
간략한 상태 업데이트 표시 |
ConfigTool |
설정 읽기/쓰기 (Ant 전용) |
TodoWriteTool |
할 일 목록 업데이트 |
SleepTool |
실행 일시 중지 (PROACTIVE/KAIROS) |
TodoWriteTool은 renderToolResultMessage를 구현하지 않는 도구의 예시다. 이 도구는 결과를 투명 패널(todo panel)로 업데이트하므로 대화 트랜스크립트(transcript)에 별도 결과를 출력하지 않는다.
웹 접근
| 도구 | 설명 |
|---|---|
WebFetchTool |
URL에서 HTML/텍스트 가져오기 |
WebSearchTool |
웹 검색 실행 |
기타 (실험적/내부)
| 도구 | 설명 | 조건 |
|---|---|---|
SyntheticOutputTool |
합성 출력 생성 | 항상 특수 처리 |
RemoteTriggerTool |
원격 트리거 실행 | AGENT_TRIGGERS_REMOTE 플래그 |
ScheduleCronTool 계열 |
크론 작업 관리 | AGENT_TRIGGERS 플래그 |
SleepTool |
에이전트 슬립 | PROACTIVE 또는 KAIROS 플래그 |
TungstenTool |
내부 도구 | Ant 전용 |
TestingPermissionTool |
권한 테스트 | NODE_ENV === 'test' |
7. 주요 설계 결정
7.1 Zod v4 기반 스키마 검증
빌드 시점과 런타임 모두에서 타입 안전성을 보장하기 위해 Zod v4를 채택했다. z.infer<Input>으로 입력 타입을 자동 추론하므로 별도의 타입 정의가 불필요하다. 또한 Zod 스키마는 Anthropic API가 요구하는 JSON Schema로 직렬화되어 LLM에 도구 명세로 전달된다.
lazySchema 패턴이 다수의 도구에서 사용된다:
// FileReadTool.tsx에서
import { lazySchema } from '../../utils/lazySchema.js'
이는 스키마 생성 비용이 높거나 순환 의존성이 있는 경우, 실제 사용 시점까지 스키마 평가를 지연시키는 패턴이다.
7.2 조건부 도구 로딩과 번들 크기 최적화
bun:bundle의 feature() 함수는 Bun의 번들러가 이해하는 특수 API다. feature('FLAG_NAME')이 false로 평가되는 빌드에서는 해당 require() 브랜치가 번들에서 완전히 제거된다. 이를 통해:
- 프로덕션 빌드 크기 최소화
- 미완성 기능의 코드가 배포되지 않음 보장
- A/B 테스트 및 단계적 기능 롤아웃 지원
런타임 기능 플래그(isEnvTruthy, process.env.USER_TYPE)와 빌드 타임 플래그(feature())를 구분하여 사용하는 점이 특징이다.
7.3 ToolUseContext의 의존성 주입 패턴
모든 도구가 단일 context: ToolUseContext 인수를 통해 필요한 의존성에 접근한다. 이 패턴의 장점:
- 테스트 용이성: 컨텍스트 객체를 모킹(mocking)하여 도구를 격리 테스트 가능
- 확장성: 새 의존성을 추가할 때 함수 시그니처를 변경하지 않고 컨텍스트에 필드만 추가
- 서브에이전트 격리:
setAppState가 서브에이전트에서 no-op으로 작동하도록 컨텍스트를 교체하여 격리 달성
단점으로는 컨텍스트 타입이 매우 커진다는 점이 있다 — ToolUseContext는 약 50개의 필드를 포함한다.
7.4 프롬프트 캐시 안정성 설계
assembleToolPool이 빌트인/MCP 도구를 별도 파티션으로 정렬하는 이유는 Anthropic API의 프롬프트 캐싱 메커니즘과 직접 연관된다. 서버 측 캐시 정책은 빌트인 도구 목록 끝에 캐시 브레이크포인트를 배치하는데, 도구 순서가 변경되면 이 브레이크포인트 이후의 모든 토큰이 캐시 미스(cache miss)가 된다. 이 설계를 통해 MCP 서버 연결/해제가 빈번해도 빌트인 도구 캐시는 유지된다.
7.5 shouldDefer / alwaysLoad — 지연 도구 로딩
도구 수가 많아지면 LLM에 전달되는 프롬프트 크기가 크게 증가한다. shouldDefer: true인 도구는 초기 프롬프트에서 스키마가 생략되고(defer_loading: true 플래그로 API에 전달), ToolSearchTool을 통해 필요 시 온디맨드(on-demand)로 스키마를 가져올 수 있다:
readonly shouldDefer?: boolean
readonly alwaysLoad?: boolean // 지연 도구 중에서도 항상 초기 로드
이를 통해 40개 이상의 도구를 보유하면서도 초기 컨텍스트 토큰 소비를 최소화한다.
8. 실제 도구 구현 패턴
BashTool 구현 패턴
// BashTool.tsx (단순화)
export const BashTool = buildTool({
name: BASH_TOOL_NAME,
// 1. Zod 스키마 정의
inputSchema: lazySchema(() => z.object({
command: z.string(),
timeout: z.number().optional(),
})),
// 2. 핵심 실행 로직
async call(args, context, canUseTool, parentMessage, onProgress) {
// ... 실행 로직
},
// 3. 검색/읽기 명령 판별
isSearchOrReadCommand(input) {
return isSearchOrReadBashCommand(input.command)
},
// 4. 읽기 전용 여부 (기본값 false 사용)
isReadOnly: () => false,
})
FileReadTool 구현 패턴
FileReadTool.tsx는 빌드 시점에 수십 개의 유틸리티를 임포트하는 복잡한 도구다. PDF, 이미지, Jupyter 노트북, 일반 텍스트 등 다양한 파일 형식을 처리하며, maxResultSizeChars: Infinity를 통해 결과가 디스크에 저장되지 않도록 보장한다.
내비게이션
- 이전: QueryEngine
- 다음: 권한 시스템
- 상위: 목차