mirror of
https://github.com/sanbuphy/claude-code-source-code.git
synced 2026-04-03 19:44:58 +08:00
v2.1.88 反编译源码
从 npm 包 @anthropic-ai/claude-code 2.1.88 版本提取的反编译源码 包含 src/ 目录下的 TypeScript 源文件及 vendor/ 原生模块源码 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
792
src/Tool.ts
Normal file
792
src/Tool.ts
Normal file
@@ -0,0 +1,792 @@
|
||||
import type {
|
||||
ToolResultBlockParam,
|
||||
ToolUseBlockParam,
|
||||
} from '@anthropic-ai/sdk/resources/index.mjs'
|
||||
import type {
|
||||
ElicitRequestURLParams,
|
||||
ElicitResult,
|
||||
} from '@modelcontextprotocol/sdk/types.js'
|
||||
import type { UUID } from 'crypto'
|
||||
import type { z } from 'zod/v4'
|
||||
import type { Command } from './commands.js'
|
||||
import type { CanUseToolFn } from './hooks/useCanUseTool.js'
|
||||
import type { ThinkingConfig } from './utils/thinking.js'
|
||||
|
||||
export type ToolInputJSONSchema = {
|
||||
[x: string]: unknown
|
||||
type: 'object'
|
||||
properties?: {
|
||||
[x: string]: unknown
|
||||
}
|
||||
}
|
||||
|
||||
import type { Notification } from './context/notifications.js'
|
||||
import type {
|
||||
MCPServerConnection,
|
||||
ServerResource,
|
||||
} from './services/mcp/types.js'
|
||||
import type {
|
||||
AgentDefinition,
|
||||
AgentDefinitionsResult,
|
||||
} from './tools/AgentTool/loadAgentsDir.js'
|
||||
import type {
|
||||
AssistantMessage,
|
||||
AttachmentMessage,
|
||||
Message,
|
||||
ProgressMessage,
|
||||
SystemLocalCommandMessage,
|
||||
SystemMessage,
|
||||
UserMessage,
|
||||
} from './types/message.js'
|
||||
// Import permission types from centralized location to break import cycles
|
||||
// Import PermissionResult from centralized location to break import cycles
|
||||
import type {
|
||||
AdditionalWorkingDirectory,
|
||||
PermissionMode,
|
||||
PermissionResult,
|
||||
} from './types/permissions.js'
|
||||
// Import tool progress types from centralized location to break import cycles
|
||||
import type {
|
||||
AgentToolProgress,
|
||||
BashProgress,
|
||||
MCPProgress,
|
||||
REPLToolProgress,
|
||||
SkillToolProgress,
|
||||
TaskOutputProgress,
|
||||
ToolProgressData,
|
||||
WebSearchProgress,
|
||||
} from './types/tools.js'
|
||||
import type { FileStateCache } from './utils/fileStateCache.js'
|
||||
import type { DenialTrackingState } from './utils/permissions/denialTracking.js'
|
||||
import type { SystemPrompt } from './utils/systemPromptType.js'
|
||||
import type { ContentReplacementState } from './utils/toolResultStorage.js'
|
||||
|
||||
// Re-export progress types for backwards compatibility
|
||||
export type {
|
||||
AgentToolProgress,
|
||||
BashProgress,
|
||||
MCPProgress,
|
||||
REPLToolProgress,
|
||||
SkillToolProgress,
|
||||
TaskOutputProgress,
|
||||
WebSearchProgress,
|
||||
}
|
||||
|
||||
import type { SpinnerMode } from './components/Spinner.js'
|
||||
import type { QuerySource } from './constants/querySource.js'
|
||||
import type { SDKStatus } from './entrypoints/agentSdkTypes.js'
|
||||
import type { AppState } from './state/AppState.js'
|
||||
import type {
|
||||
HookProgress,
|
||||
PromptRequest,
|
||||
PromptResponse,
|
||||
} from './types/hooks.js'
|
||||
import type { AgentId } from './types/ids.js'
|
||||
import type { DeepImmutable } from './types/utils.js'
|
||||
import type { AttributionState } from './utils/commitAttribution.js'
|
||||
import type { FileHistoryState } from './utils/fileHistory.js'
|
||||
import type { Theme, ThemeName } from './utils/theme.js'
|
||||
|
||||
export type QueryChainTracking = {
|
||||
chainId: string
|
||||
depth: number
|
||||
}
|
||||
|
||||
export type ValidationResult =
|
||||
| { result: true }
|
||||
| {
|
||||
result: false
|
||||
message: string
|
||||
errorCode: number
|
||||
}
|
||||
|
||||
export type SetToolJSXFn = (
|
||||
args: {
|
||||
jsx: React.ReactNode | null
|
||||
shouldHidePromptInput: boolean
|
||||
shouldContinueAnimation?: true
|
||||
showSpinner?: boolean
|
||||
isLocalJSXCommand?: boolean
|
||||
isImmediate?: boolean
|
||||
/** Set to true to clear a local JSX command (e.g., from its onDone callback) */
|
||||
clearLocalJSX?: boolean
|
||||
} | null,
|
||||
) => void
|
||||
|
||||
// Import tool permission types from centralized location to break import cycles
|
||||
import type { ToolPermissionRulesBySource } from './types/permissions.js'
|
||||
|
||||
// Re-export for backwards compatibility
|
||||
export type { ToolPermissionRulesBySource }
|
||||
|
||||
// Apply DeepImmutable to the imported type
|
||||
export type ToolPermissionContext = DeepImmutable<{
|
||||
mode: PermissionMode
|
||||
additionalWorkingDirectories: Map<string, AdditionalWorkingDirectory>
|
||||
alwaysAllowRules: ToolPermissionRulesBySource
|
||||
alwaysDenyRules: ToolPermissionRulesBySource
|
||||
alwaysAskRules: ToolPermissionRulesBySource
|
||||
isBypassPermissionsModeAvailable: boolean
|
||||
isAutoModeAvailable?: boolean
|
||||
strippedDangerousRules?: ToolPermissionRulesBySource
|
||||
/** When true, permission prompts are auto-denied (e.g., background agents that can't show UI) */
|
||||
shouldAvoidPermissionPrompts?: boolean
|
||||
/** When true, automated checks (classifier, hooks) are awaited before showing the permission dialog (coordinator workers) */
|
||||
awaitAutomatedChecksBeforeDialog?: boolean
|
||||
/** Stores the permission mode before model-initiated plan mode entry, so it can be restored on exit */
|
||||
prePlanMode?: PermissionMode
|
||||
}>
|
||||
|
||||
export const getEmptyToolPermissionContext: () => ToolPermissionContext =
|
||||
() => ({
|
||||
mode: 'default',
|
||||
additionalWorkingDirectories: new Map(),
|
||||
alwaysAllowRules: {},
|
||||
alwaysDenyRules: {},
|
||||
alwaysAskRules: {},
|
||||
isBypassPermissionsModeAvailable: false,
|
||||
})
|
||||
|
||||
export type CompactProgressEvent =
|
||||
| {
|
||||
type: 'hooks_start'
|
||||
hookType: 'pre_compact' | 'post_compact' | 'session_start'
|
||||
}
|
||||
| { type: 'compact_start' }
|
||||
| { type: 'compact_end' }
|
||||
|
||||
export type ToolUseContext = {
|
||||
options: {
|
||||
commands: Command[]
|
||||
debug: boolean
|
||||
mainLoopModel: string
|
||||
tools: Tools
|
||||
verbose: boolean
|
||||
thinkingConfig: ThinkingConfig
|
||||
mcpClients: MCPServerConnection[]
|
||||
mcpResources: Record<string, ServerResource[]>
|
||||
isNonInteractiveSession: boolean
|
||||
agentDefinitions: AgentDefinitionsResult
|
||||
maxBudgetUsd?: number
|
||||
/** Custom system prompt that replaces the default system prompt */
|
||||
customSystemPrompt?: string
|
||||
/** Additional system prompt appended after the main system prompt */
|
||||
appendSystemPrompt?: string
|
||||
/** Override querySource for analytics tracking */
|
||||
querySource?: QuerySource
|
||||
/** Optional callback to get the latest tools (e.g., after MCP servers connect mid-query) */
|
||||
refreshTools?: () => Tools
|
||||
}
|
||||
abortController: AbortController
|
||||
readFileState: FileStateCache
|
||||
getAppState(): AppState
|
||||
setAppState(f: (prev: AppState) => AppState): void
|
||||
/**
|
||||
* Always-shared setAppState for session-scoped infrastructure (background
|
||||
* tasks, session hooks). Unlike setAppState, which is no-op for async agents
|
||||
* (see createSubagentContext), this always reaches the root store so agents
|
||||
* at any nesting depth can register/clean up infrastructure that outlives
|
||||
* a single turn. Only set by createSubagentContext; main-thread contexts
|
||||
* fall back to setAppState.
|
||||
*/
|
||||
setAppStateForTasks?: (f: (prev: AppState) => AppState) => void
|
||||
/**
|
||||
* Optional handler for URL elicitations triggered by tool call errors (-32042).
|
||||
* In print/SDK mode, this delegates to structuredIO.handleElicitation.
|
||||
* In REPL mode, this is undefined and the queue-based UI path is used.
|
||||
*/
|
||||
handleElicitation?: (
|
||||
serverName: string,
|
||||
params: ElicitRequestURLParams,
|
||||
signal: AbortSignal,
|
||||
) => Promise<ElicitResult>
|
||||
setToolJSX?: SetToolJSXFn
|
||||
addNotification?: (notif: Notification) => void
|
||||
/** Append a UI-only system message to the REPL message list. Stripped at the
|
||||
* normalizeMessagesForAPI boundary — the Exclude<> makes that type-enforced. */
|
||||
appendSystemMessage?: (
|
||||
msg: Exclude<SystemMessage, SystemLocalCommandMessage>,
|
||||
) => void
|
||||
/** Send an OS-level notification (iTerm2, Kitty, Ghostty, bell, etc.) */
|
||||
sendOSNotification?: (opts: {
|
||||
message: string
|
||||
notificationType: string
|
||||
}) => void
|
||||
nestedMemoryAttachmentTriggers?: Set<string>
|
||||
/**
|
||||
* CLAUDE.md paths already injected as nested_memory attachments this
|
||||
* session. Dedup for memoryFilesToAttachments — readFileState is an LRU
|
||||
* that evicts entries in busy sessions, so its .has() check alone can
|
||||
* re-inject the same CLAUDE.md dozens of times.
|
||||
*/
|
||||
loadedNestedMemoryPaths?: Set<string>
|
||||
dynamicSkillDirTriggers?: Set<string>
|
||||
/** Skill names surfaced via skill_discovery this session. Telemetry only (feeds was_discovered). */
|
||||
discoveredSkillNames?: Set<string>
|
||||
userModified?: boolean
|
||||
setInProgressToolUseIDs: (f: (prev: Set<string>) => Set<string>) => void
|
||||
/** Only wired in interactive (REPL) contexts; SDK/QueryEngine don't set this. */
|
||||
setHasInterruptibleToolInProgress?: (v: boolean) => void
|
||||
setResponseLength: (f: (prev: number) => number) => void
|
||||
/** Ant-only: push a new API metrics entry for OTPS tracking.
|
||||
* Called by subagent streaming when a new API request starts. */
|
||||
pushApiMetricsEntry?: (ttftMs: number) => void
|
||||
setStreamMode?: (mode: SpinnerMode) => void
|
||||
onCompactProgress?: (event: CompactProgressEvent) => void
|
||||
setSDKStatus?: (status: SDKStatus) => void
|
||||
openMessageSelector?: () => void
|
||||
updateFileHistoryState: (
|
||||
updater: (prev: FileHistoryState) => FileHistoryState,
|
||||
) => void
|
||||
updateAttributionState: (
|
||||
updater: (prev: AttributionState) => AttributionState,
|
||||
) => void
|
||||
setConversationId?: (id: UUID) => void
|
||||
agentId?: AgentId // Only set for subagents; use getSessionId() for session ID. Hooks use this to distinguish subagent calls.
|
||||
agentType?: string // Subagent type name. For the main thread's --agent type, hooks fall back to getMainThreadAgentType().
|
||||
/** When true, canUseTool must always be called even when hooks auto-approve.
|
||||
* Used by speculation for overlay file path rewriting. */
|
||||
requireCanUseTool?: boolean
|
||||
messages: Message[]
|
||||
fileReadingLimits?: {
|
||||
maxTokens?: number
|
||||
maxSizeBytes?: number
|
||||
}
|
||||
globLimits?: {
|
||||
maxResults?: number
|
||||
}
|
||||
toolDecisions?: Map<
|
||||
string,
|
||||
{
|
||||
source: string
|
||||
decision: 'accept' | 'reject'
|
||||
timestamp: number
|
||||
}
|
||||
>
|
||||
queryTracking?: QueryChainTracking
|
||||
/** Callback factory for requesting interactive prompts from the user.
|
||||
* Returns a prompt callback bound to the given source name.
|
||||
* Only available in interactive (REPL) contexts. */
|
||||
requestPrompt?: (
|
||||
sourceName: string,
|
||||
toolInputSummary?: string | null,
|
||||
) => (request: PromptRequest) => Promise<PromptResponse>
|
||||
toolUseId?: string
|
||||
criticalSystemReminder_EXPERIMENTAL?: string
|
||||
/** When true, preserve toolUseResult on messages even for subagents.
|
||||
* Used by in-process teammates whose transcripts are viewable by the user. */
|
||||
preserveToolUseResults?: boolean
|
||||
/** Local denial tracking state for async subagents whose setAppState is a
|
||||
* no-op. Without this, the denial counter never accumulates and the
|
||||
* fallback-to-prompting threshold is never reached. Mutable — the
|
||||
* permissions code updates it in place. */
|
||||
localDenialTracking?: DenialTrackingState
|
||||
/**
|
||||
* Per-conversation-thread content replacement state for the tool result
|
||||
* budget. When present, query.ts applies the aggregate tool result budget.
|
||||
* Main thread: REPL provisions once (never resets — stale UUID keys
|
||||
* are inert). Subagents: createSubagentContext clones the parent's state
|
||||
* by default (cache-sharing forks need identical decisions), or
|
||||
* resumeAgentBackground threads one reconstructed from sidechain records.
|
||||
*/
|
||||
contentReplacementState?: ContentReplacementState
|
||||
/**
|
||||
* Parent's rendered system prompt bytes, frozen at turn start.
|
||||
* Used by fork subagents to share the parent's prompt cache — re-calling
|
||||
* getSystemPrompt() at fork-spawn time can diverge (GrowthBook cold→warm)
|
||||
* and bust the cache. See forkSubagent.ts.
|
||||
*/
|
||||
renderedSystemPrompt?: SystemPrompt
|
||||
}
|
||||
|
||||
// Re-export ToolProgressData from centralized location
|
||||
export type { ToolProgressData }
|
||||
|
||||
export type Progress = ToolProgressData | HookProgress
|
||||
|
||||
export type ToolProgress<P extends ToolProgressData> = {
|
||||
toolUseID: string
|
||||
data: P
|
||||
}
|
||||
|
||||
export function filterToolProgressMessages(
|
||||
progressMessagesForMessage: ProgressMessage[],
|
||||
): ProgressMessage<ToolProgressData>[] {
|
||||
return progressMessagesForMessage.filter(
|
||||
(msg): msg is ProgressMessage<ToolProgressData> =>
|
||||
msg.data?.type !== 'hook_progress',
|
||||
)
|
||||
}
|
||||
|
||||
export type ToolResult<T> = {
|
||||
data: T
|
||||
newMessages?: (
|
||||
| UserMessage
|
||||
| AssistantMessage
|
||||
| AttachmentMessage
|
||||
| SystemMessage
|
||||
)[]
|
||||
// contextModifier is only honored for tools that aren't concurrency safe.
|
||||
contextModifier?: (context: ToolUseContext) => ToolUseContext
|
||||
/** MCP protocol metadata (structuredContent, _meta) to pass through to SDK consumers */
|
||||
mcpMeta?: {
|
||||
_meta?: Record<string, unknown>
|
||||
structuredContent?: Record<string, unknown>
|
||||
}
|
||||
}
|
||||
|
||||
export type ToolCallProgress<P extends ToolProgressData = ToolProgressData> = (
|
||||
progress: ToolProgress<P>,
|
||||
) => void
|
||||
|
||||
// Type for any schema that outputs an object with string keys
|
||||
export type AnyObject = z.ZodType<{ [key: string]: unknown }>
|
||||
|
||||
/**
|
||||
* Checks if a tool matches the given name (primary name or alias).
|
||||
*/
|
||||
export function toolMatchesName(
|
||||
tool: { name: string; aliases?: string[] },
|
||||
name: string,
|
||||
): boolean {
|
||||
return tool.name === name || (tool.aliases?.includes(name) ?? false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a tool by name or alias from a list of tools.
|
||||
*/
|
||||
export function findToolByName(tools: Tools, name: string): Tool | undefined {
|
||||
return tools.find(t => toolMatchesName(t, name))
|
||||
}
|
||||
|
||||
export type Tool<
|
||||
Input extends AnyObject = AnyObject,
|
||||
Output = unknown,
|
||||
P extends ToolProgressData = ToolProgressData,
|
||||
> = {
|
||||
/**
|
||||
* Optional aliases for backwards compatibility when a tool is renamed.
|
||||
* The tool can be looked up by any of these names in addition to its primary name.
|
||||
*/
|
||||
aliases?: string[]
|
||||
/**
|
||||
* One-line capability phrase used by ToolSearch for keyword matching.
|
||||
* Helps the model find this tool via keyword search when it's deferred.
|
||||
* 3–10 words, no trailing period.
|
||||
* Prefer terms not already in the tool name (e.g. 'jupyter' for NotebookEdit).
|
||||
*/
|
||||
searchHint?: string
|
||||
call(
|
||||
args: z.infer<Input>,
|
||||
context: ToolUseContext,
|
||||
canUseTool: CanUseToolFn,
|
||||
parentMessage: AssistantMessage,
|
||||
onProgress?: ToolCallProgress<P>,
|
||||
): Promise<ToolResult<Output>>
|
||||
description(
|
||||
input: z.infer<Input>,
|
||||
options: {
|
||||
isNonInteractiveSession: boolean
|
||||
toolPermissionContext: ToolPermissionContext
|
||||
tools: Tools
|
||||
},
|
||||
): Promise<string>
|
||||
readonly inputSchema: Input
|
||||
// Type for MCP tools that can specify their input schema directly in JSON Schema format
|
||||
// rather than converting from Zod schema
|
||||
readonly inputJSONSchema?: ToolInputJSONSchema
|
||||
// Optional because TungstenTool doesn't define this. TODO: Make it required.
|
||||
// When we do that, we can also go through and make this a bit more type-safe.
|
||||
outputSchema?: z.ZodType<unknown>
|
||||
inputsEquivalent?(a: z.infer<Input>, b: z.infer<Input>): boolean
|
||||
isConcurrencySafe(input: z.infer<Input>): boolean
|
||||
isEnabled(): boolean
|
||||
isReadOnly(input: z.infer<Input>): boolean
|
||||
/** Defaults to false. Only set when the tool performs irreversible operations (delete, overwrite, send). */
|
||||
isDestructive?(input: z.infer<Input>): boolean
|
||||
/**
|
||||
* What should happen when the user submits a new message while this tool
|
||||
* is running.
|
||||
*
|
||||
* - `'cancel'` — stop the tool and discard its result
|
||||
* - `'block'` — keep running; the new message waits
|
||||
*
|
||||
* Defaults to `'block'` when not implemented.
|
||||
*/
|
||||
interruptBehavior?(): 'cancel' | 'block'
|
||||
/**
|
||||
* Returns information about whether this tool use is a search or read operation
|
||||
* that should be collapsed into a condensed display in the UI. Examples include
|
||||
* file searching (Grep, Glob), file reading (Read), and bash commands like find,
|
||||
* grep, wc, etc.
|
||||
*
|
||||
* Returns an object indicating whether the operation is a search or read operation:
|
||||
* - `isSearch: true` for search operations (grep, find, glob patterns)
|
||||
* - `isRead: true` for read operations (cat, head, tail, file read)
|
||||
* - `isList: true` for directory-listing operations (ls, tree, du)
|
||||
* - All can be false if the operation shouldn't be collapsed
|
||||
*/
|
||||
isSearchOrReadCommand?(input: z.infer<Input>): {
|
||||
isSearch: boolean
|
||||
isRead: boolean
|
||||
isList?: boolean
|
||||
}
|
||||
isOpenWorld?(input: z.infer<Input>): boolean
|
||||
requiresUserInteraction?(): boolean
|
||||
isMcp?: boolean
|
||||
isLsp?: boolean
|
||||
/**
|
||||
* When true, this tool is deferred (sent with defer_loading: true) and requires
|
||||
* ToolSearch to be used before it can be called.
|
||||
*/
|
||||
readonly shouldDefer?: boolean
|
||||
/**
|
||||
* When true, this tool is never deferred — its full schema appears in the
|
||||
* initial prompt even when ToolSearch is enabled. For MCP tools, set via
|
||||
* `_meta['anthropic/alwaysLoad']`. Use for tools the model must see on
|
||||
* turn 1 without a ToolSearch round-trip.
|
||||
*/
|
||||
readonly alwaysLoad?: boolean
|
||||
/**
|
||||
* For MCP tools: the server and tool names as received from the MCP server (unnormalized).
|
||||
* Present on all MCP tools regardless of whether `name` is prefixed (mcp__server__tool)
|
||||
* or unprefixed (CLAUDE_AGENT_SDK_MCP_NO_PREFIX mode).
|
||||
*/
|
||||
mcpInfo?: { serverName: string; toolName: string }
|
||||
readonly name: string
|
||||
/**
|
||||
* Maximum size in characters for tool result before it gets persisted to disk.
|
||||
* When exceeded, the result is saved to a file and Claude receives a preview
|
||||
* with the file path instead of the full content.
|
||||
*
|
||||
* Set to Infinity for tools whose output must never be persisted (e.g. Read,
|
||||
* where persisting creates a circular Read→file→Read loop and the tool
|
||||
* already self-bounds via its own limits).
|
||||
*/
|
||||
maxResultSizeChars: number
|
||||
/**
|
||||
* When true, enables strict mode for this tool, which causes the API to
|
||||
* more strictly adhere to tool instructions and parameter schemas.
|
||||
* Only applied when the tengu_tool_pear is enabled.
|
||||
*/
|
||||
readonly strict?: boolean
|
||||
|
||||
/**
|
||||
* Called on copies of tool_use input before observers see it (SDK stream,
|
||||
* transcript, canUseTool, PreToolUse/PostToolUse hooks). Mutate in place
|
||||
* to add legacy/derived fields. Must be idempotent. The original API-bound
|
||||
* input is never mutated (preserves prompt cache). Not re-applied when a
|
||||
* hook/permission returns a fresh updatedInput — those own their shape.
|
||||
*/
|
||||
backfillObservableInput?(input: Record<string, unknown>): void
|
||||
|
||||
/**
|
||||
* Determines if this tool is allowed to run with this input in the current context.
|
||||
* It informs the model of why the tool use failed, and does not directly display any UI.
|
||||
* @param input
|
||||
* @param context
|
||||
*/
|
||||
validateInput?(
|
||||
input: z.infer<Input>,
|
||||
context: ToolUseContext,
|
||||
): Promise<ValidationResult>
|
||||
|
||||
/**
|
||||
* Determines if the user is asked for permission. Only called after validateInput() passes.
|
||||
* General permission logic is in permissions.ts. This method contains tool-specific logic.
|
||||
* @param input
|
||||
* @param context
|
||||
*/
|
||||
checkPermissions(
|
||||
input: z.infer<Input>,
|
||||
context: ToolUseContext,
|
||||
): Promise<PermissionResult>
|
||||
|
||||
// Optional method for tools that operate on a file path
|
||||
getPath?(input: z.infer<Input>): string
|
||||
|
||||
/**
|
||||
* Prepare a matcher for hook `if` conditions (permission-rule patterns like
|
||||
* "git *" from "Bash(git *)"). Called once per hook-input pair; any
|
||||
* expensive parsing happens here. Returns a closure that is called per
|
||||
* hook pattern. If not implemented, only tool-name-level matching works.
|
||||
*/
|
||||
preparePermissionMatcher?(
|
||||
input: z.infer<Input>,
|
||||
): Promise<(pattern: string) => boolean>
|
||||
|
||||
prompt(options: {
|
||||
getToolPermissionContext: () => Promise<ToolPermissionContext>
|
||||
tools: Tools
|
||||
agents: AgentDefinition[]
|
||||
allowedAgentTypes?: string[]
|
||||
}): Promise<string>
|
||||
userFacingName(input: Partial<z.infer<Input>> | undefined): string
|
||||
userFacingNameBackgroundColor?(
|
||||
input: Partial<z.infer<Input>> | undefined,
|
||||
): keyof Theme | undefined
|
||||
/**
|
||||
* Transparent wrappers (e.g. REPL) delegate all rendering to their progress
|
||||
* handler, which emits native-looking blocks for each inner tool call.
|
||||
* The wrapper itself shows nothing.
|
||||
*/
|
||||
isTransparentWrapper?(): boolean
|
||||
/**
|
||||
* Returns a short string summary of this tool use for display in compact views.
|
||||
* @param input The tool input
|
||||
* @returns A short string summary, or null to not display
|
||||
*/
|
||||
getToolUseSummary?(input: Partial<z.infer<Input>> | undefined): string | null
|
||||
/**
|
||||
* Returns a human-readable present-tense activity description for spinner display.
|
||||
* Example: "Reading src/foo.ts", "Running bun test", "Searching for pattern"
|
||||
* @param input The tool input
|
||||
* @returns Activity description string, or null to fall back to tool name
|
||||
*/
|
||||
getActivityDescription?(
|
||||
input: Partial<z.infer<Input>> | undefined,
|
||||
): string | null
|
||||
/**
|
||||
* Returns a compact representation of this tool use for the auto-mode
|
||||
* security classifier. Examples: `ls -la` for Bash, `/tmp/x: new content`
|
||||
* for Edit. Return '' to skip this tool in the classifier transcript
|
||||
* (e.g. tools with no security relevance). May return an object to avoid
|
||||
* double-encoding when the caller JSON-wraps the value.
|
||||
*/
|
||||
toAutoClassifierInput(input: z.infer<Input>): unknown
|
||||
mapToolResultToToolResultBlockParam(
|
||||
content: Output,
|
||||
toolUseID: string,
|
||||
): ToolResultBlockParam
|
||||
/**
|
||||
* Optional. When omitted, the tool result renders nothing (same as returning
|
||||
* null). Omit for tools whose results are surfaced elsewhere (e.g., TodoWrite
|
||||
* updates the todo panel, not the transcript).
|
||||
*/
|
||||
renderToolResultMessage?(
|
||||
content: Output,
|
||||
progressMessagesForMessage: ProgressMessage<P>[],
|
||||
options: {
|
||||
style?: 'condensed'
|
||||
theme: ThemeName
|
||||
tools: Tools
|
||||
verbose: boolean
|
||||
isTranscriptMode?: boolean
|
||||
isBriefOnly?: boolean
|
||||
/** Original tool_use input, when available. Useful for compact result
|
||||
* summaries that reference what was requested (e.g. "Sent to #foo"). */
|
||||
input?: unknown
|
||||
},
|
||||
): React.ReactNode
|
||||
/**
|
||||
* Flattened text of what renderToolResultMessage shows IN TRANSCRIPT
|
||||
* MODE (verbose=true, isTranscriptMode=true). For transcript search
|
||||
* indexing: the index counts occurrences in this string, the highlight
|
||||
* overlay scans the actual screen buffer. For count ≡ highlight, this
|
||||
* must return the text that ends up visible — not the model-facing
|
||||
* serialization from mapToolResultToToolResultBlockParam (which adds
|
||||
* system-reminders, persisted-output wrappers).
|
||||
*
|
||||
* Chrome can be skipped (under-count is fine). "Found 3 files in 12ms"
|
||||
* isn't worth indexing. Phantoms are not fine — text that's claimed
|
||||
* here but doesn't render is a count≠highlight bug.
|
||||
*
|
||||
* Optional: omitted → field-name heuristic in transcriptSearch.ts.
|
||||
* Drift caught by test/utils/transcriptSearch.renderFidelity.test.tsx
|
||||
* which renders sample outputs and flags text that's indexed-but-not-
|
||||
* rendered (phantom) or rendered-but-not-indexed (under-count warning).
|
||||
*/
|
||||
extractSearchText?(out: Output): string
|
||||
/**
|
||||
* Render the tool use message. Note that `input` is partial because we render
|
||||
* the message as soon as possible, possibly before tool parameters have fully
|
||||
* streamed in.
|
||||
*/
|
||||
renderToolUseMessage(
|
||||
input: Partial<z.infer<Input>>,
|
||||
options: { theme: ThemeName; verbose: boolean; commands?: Command[] },
|
||||
): React.ReactNode
|
||||
/**
|
||||
* Returns true when the non-verbose rendering of this output is truncated
|
||||
* (i.e., clicking to expand would reveal more content). Gates
|
||||
* click-to-expand in fullscreen — only messages where verbose actually
|
||||
* shows more get a hover/click affordance. Unset means never truncated.
|
||||
*/
|
||||
isResultTruncated?(output: Output): boolean
|
||||
/**
|
||||
* Renders an optional tag to display after the tool use message.
|
||||
* Used for additional metadata like timeout, model, resume ID, etc.
|
||||
* Returns null to not display anything.
|
||||
*/
|
||||
renderToolUseTag?(input: Partial<z.infer<Input>>): React.ReactNode
|
||||
/**
|
||||
* Optional. When omitted, no progress UI is shown while the tool runs.
|
||||
*/
|
||||
renderToolUseProgressMessage?(
|
||||
progressMessagesForMessage: ProgressMessage<P>[],
|
||||
options: {
|
||||
tools: Tools
|
||||
verbose: boolean
|
||||
terminalSize?: { columns: number; rows: number }
|
||||
inProgressToolCallCount?: number
|
||||
isTranscriptMode?: boolean
|
||||
},
|
||||
): React.ReactNode
|
||||
renderToolUseQueuedMessage?(): React.ReactNode
|
||||
/**
|
||||
* Optional. When omitted, falls back to <FallbackToolUseRejectedMessage />.
|
||||
* Only define this for tools that need custom rejection UI (e.g., file edits
|
||||
* that show the rejected diff).
|
||||
*/
|
||||
renderToolUseRejectedMessage?(
|
||||
input: z.infer<Input>,
|
||||
options: {
|
||||
columns: number
|
||||
messages: Message[]
|
||||
style?: 'condensed'
|
||||
theme: ThemeName
|
||||
tools: Tools
|
||||
verbose: boolean
|
||||
progressMessagesForMessage: ProgressMessage<P>[]
|
||||
isTranscriptMode?: boolean
|
||||
},
|
||||
): React.ReactNode
|
||||
/**
|
||||
* Optional. When omitted, falls back to <FallbackToolUseErrorMessage />.
|
||||
* Only define this for tools that need custom error UI (e.g., search tools
|
||||
* that show "File not found" instead of the raw error).
|
||||
*/
|
||||
renderToolUseErrorMessage?(
|
||||
result: ToolResultBlockParam['content'],
|
||||
options: {
|
||||
progressMessagesForMessage: ProgressMessage<P>[]
|
||||
tools: Tools
|
||||
verbose: boolean
|
||||
isTranscriptMode?: boolean
|
||||
},
|
||||
): React.ReactNode
|
||||
|
||||
/**
|
||||
* Renders multiple parallel instances of this tool as a group.
|
||||
* @returns React node to render, or null to fall back to individual rendering
|
||||
*/
|
||||
/**
|
||||
* Renders multiple tool uses as a group (non-verbose mode only).
|
||||
* In verbose mode, individual tool uses render at their original positions.
|
||||
* @returns React node to render, or null to fall back to individual rendering
|
||||
*/
|
||||
renderGroupedToolUse?(
|
||||
toolUses: Array<{
|
||||
param: ToolUseBlockParam
|
||||
isResolved: boolean
|
||||
isError: boolean
|
||||
isInProgress: boolean
|
||||
progressMessages: ProgressMessage<P>[]
|
||||
result?: {
|
||||
param: ToolResultBlockParam
|
||||
output: unknown
|
||||
}
|
||||
}>,
|
||||
options: {
|
||||
shouldAnimate: boolean
|
||||
tools: Tools
|
||||
},
|
||||
): React.ReactNode | null
|
||||
}
|
||||
|
||||
/**
|
||||
* A collection of tools. Use this type instead of `Tool[]` to make it easier
|
||||
* to track where tool sets are assembled, passed, and filtered across the codebase.
|
||||
*/
|
||||
export type Tools = readonly Tool[]
|
||||
|
||||
/**
|
||||
* Methods that `buildTool` supplies a default for. A `ToolDef` may omit these;
|
||||
* the resulting `Tool` always has them.
|
||||
*/
|
||||
type DefaultableToolKeys =
|
||||
| 'isEnabled'
|
||||
| 'isConcurrencySafe'
|
||||
| 'isReadOnly'
|
||||
| 'isDestructive'
|
||||
| 'checkPermissions'
|
||||
| 'toAutoClassifierInput'
|
||||
| 'userFacingName'
|
||||
|
||||
/**
|
||||
* Tool definition accepted by `buildTool`. Same shape as `Tool` but with the
|
||||
* defaultable methods optional — `buildTool` fills them in so callers always
|
||||
* see a complete `Tool`.
|
||||
*/
|
||||
export type ToolDef<
|
||||
Input extends AnyObject = AnyObject,
|
||||
Output = unknown,
|
||||
P extends ToolProgressData = ToolProgressData,
|
||||
> = Omit<Tool<Input, Output, P>, DefaultableToolKeys> &
|
||||
Partial<Pick<Tool<Input, Output, P>, DefaultableToolKeys>>
|
||||
|
||||
/**
|
||||
* Type-level spread mirroring `{ ...TOOL_DEFAULTS, ...def }`. For each
|
||||
* defaultable key: if D provides it (required), D's type wins; if D omits
|
||||
* it or has it optional (inherited from Partial<> in the constraint), the
|
||||
* default fills in. All other keys come from D verbatim — preserving arity,
|
||||
* optional presence, and literal types exactly as `satisfies Tool` did.
|
||||
*/
|
||||
type BuiltTool<D> = Omit<D, DefaultableToolKeys> & {
|
||||
[K in DefaultableToolKeys]-?: K extends keyof D
|
||||
? undefined extends D[K]
|
||||
? ToolDefaults[K]
|
||||
: D[K]
|
||||
: ToolDefaults[K]
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a complete `Tool` from a partial definition, filling in safe defaults
|
||||
* for the commonly-stubbed methods. All tool exports should go through this so
|
||||
* that defaults live in one place and callers never need `?.() ?? default`.
|
||||
*
|
||||
* Defaults (fail-closed where it matters):
|
||||
* - `isEnabled` → `true`
|
||||
* - `isConcurrencySafe` → `false` (assume not safe)
|
||||
* - `isReadOnly` → `false` (assume writes)
|
||||
* - `isDestructive` → `false`
|
||||
* - `checkPermissions` → `{ behavior: 'allow', updatedInput }` (defer to general permission system)
|
||||
* - `toAutoClassifierInput` → `''` (skip classifier — security-relevant tools must override)
|
||||
* - `userFacingName` → `name`
|
||||
*/
|
||||
const TOOL_DEFAULTS = {
|
||||
isEnabled: () => true,
|
||||
isConcurrencySafe: (_input?: unknown) => false,
|
||||
isReadOnly: (_input?: unknown) => false,
|
||||
isDestructive: (_input?: unknown) => false,
|
||||
checkPermissions: (
|
||||
input: { [key: string]: unknown },
|
||||
_ctx?: ToolUseContext,
|
||||
): Promise<PermissionResult> =>
|
||||
Promise.resolve({ behavior: 'allow', updatedInput: input }),
|
||||
toAutoClassifierInput: (_input?: unknown) => '',
|
||||
userFacingName: (_input?: unknown) => '',
|
||||
}
|
||||
|
||||
// The defaults type is the ACTUAL shape of TOOL_DEFAULTS (optional params so
|
||||
// both 0-arg and full-arg call sites type-check — stubs varied in arity and
|
||||
// tests relied on that), not the interface's strict signatures.
|
||||
type ToolDefaults = typeof TOOL_DEFAULTS
|
||||
|
||||
// D infers the concrete object-literal type from the call site. The
|
||||
// constraint provides contextual typing for method parameters; `any` in
|
||||
// constraint position is structural and never leaks into the return type.
|
||||
// BuiltTool<D> mirrors runtime `{...TOOL_DEFAULTS, ...def}` at the type level.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type AnyToolDef = ToolDef<any, any, any>
|
||||
|
||||
export function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D> {
|
||||
// The runtime spread is straightforward; the `as` bridges the gap between
|
||||
// the structural-any constraint and the precise BuiltTool<D> return. The
|
||||
// type semantics are proven by the 0-error typecheck across all 60+ tools.
|
||||
return {
|
||||
...TOOL_DEFAULTS,
|
||||
userFacingName: () => def.name,
|
||||
...def,
|
||||
} as BuiltTool<D>
|
||||
}
|
||||
Reference in New Issue
Block a user