Skip to content

Tool Handlers:工具處理器全圖

源码版本v4.0.10

職責

Handler 是每個工具的具體實作。一個 handler 類別對應一種工具行為:讀檔案、寫檔案、跑命令、調 MCP、起 subagent 等等。它們都實作 IToolHandler 介面 (IToolHandler:34),提供 nameexecutegetDescription 三件套。需要支援流式 partial block 的額外實作 IPartialBlockHandler,兩個都實作的標記為 IFullyManagedTool

Cline 的工具清單定義在 ClineDefaultTool 列舉裡 (ClineDefaultTool:8)。目前有 27 個列舉值,從 ask_followup_questionuse_subagents。這些名字直接對應 LLM 看到的 tool_use block 的 name 欄位。列舉下面有一行 toolUseNames = Object.values(ClineDefaultTool) (toolUseNames:40) 把列舉拍平成陣列,ToolExecutor 建構時遍歷這個陣列完成全量註冊。

handler 檔案都在 apps/vscode/src/core/task/tools/handlers/ 目錄下,一個工具一個檔案,類別名形如 XxxToolHandler。所有 handler 共享一個 TaskConfig 上下文物件,裡面有 cwd、taskState、messageState、api、各種 service 和 callback,handler 透過它存取所有外部依賴 (asToolConfig:135)。

設計動機

  • 一工具一檔案:每個 handler 一個檔案,職責邊界清晰。新增工具只加檔案不動現有程式碼,刪工具也只刪一個檔案。
  • 介面分層而非繼承:IToolHandler 是最小契約,IPartialBlockHandler 是流式附加能力,IFullyManagedTool 是「全套」標記 (IFullyManagedTool:44)。組合優於繼承,handler 按需挑介面實作。
  • Handler 不持有狀態:Handler 實例被 Coordinator 快取,所有狀態都從 TaskConfig 傳進來。同一 handler 實例可能被多次 execute 呼叫,不能存任務級狀態在自己欄位裡。
  • TaskConfig 一次建構多處用:ToolExecutor 建構 asToolConfig() 時把所有依賴收集成一個物件 (asToolConfig:135),handler 拿到 config 就能存取 mcpHub、browserSession、diffViewProvider、clineIgnoreController 等服務,不用自己注入。
  • ClineDefaultTool 列舉驅動註冊:toolUseNames 自動從列舉生成,註冊迴圈不用手維護工具清單 (for of toolUseNames:204)。新增列舉值就自動進入註冊迴圈,前提是 Coordinator 的 toolHandlersMap 也有對應工廠。
  • SharedToolHandler 復用實作:多個名字共享一個實作時 (write_to_file / replace_in_file / new_rule 三個共用 WriteToFileToolHandler),用包裝類改名 (SharedToolHandler:52),避免寫三個一樣的類別。

關鍵檔案

資料流

新增工具的流程是「寫 handler 類別 → 在 map 裡登記一行 → 列舉加一項」。map 登記的樣子:

typescript
// apps/vscode/src/core/task/tools/ToolExecutorCoordinator.ts
private readonly toolHandlersMap: Record<ClineDefaultTool, (v: ToolValidator) => IToolHandler | undefined> = {
    [ClineDefaultTool.ASK]: (_v: ToolValidator) => new AskFollowupQuestionToolHandler(),
    [ClineDefaultTool.ATTEMPT]: (_v: ToolValidator) => new AttemptCompletionHandler(),
    [ClineDefaultTool.BASH]: (v: ToolValidator) => new ExecuteCommandToolHandler(v),
    [ClineDefaultTool.FILE_EDIT]: (v: ToolValidator) =>
        new SharedToolHandler(ClineDefaultTool.FILE_EDIT, new WriteToFileToolHandler(v)),
    [ClineDefaultTool.FILE_READ]: (v: ToolValidator) => new ReadFileToolHandler(v),
    [ClineDefaultTool.FILE_NEW]: (v: ToolValidator) => new WriteToFileToolHandler(v),
    // ...
    [ClineDefaultTool.TODO]: (_v: ToolValidator) => undefined,
}

工廠函式的參數是 ToolValidator,需要校驗路徑的 handler (read/write/search/list) 會接住它,不需要的 handler (ask/attempt/browser/mcp 等) 用 _v 占位。回傳 undefined 的只有 TODO 一項,表示這個工具名 Coordinator 不接管 (todo undefined:97)。

Handler 實際執行時拿到的 config 長這樣:

typescript
// apps/vscode/src/core/task/ToolExecutor.ts
const config: TaskConfig = {
    taskId: this.taskId,
    ulid: this.ulid,
    mode: this.stateManager.getGlobalSettingsKey("mode"),
    cwd: this.cwd,
    workspaceManager: this.workspaceManager,
    taskState: this.taskState,
    messageState: this.messageStateHandler,
    api: this.api,
    autoApprover: this.autoApprover,
    services: {
        mcpHub: this.mcpHub,
        browserSession: this.browserSession,
        diffViewProvider: this.diffViewProvider,
        fileContextTracker: this.fileContextTracker,
        clineIgnoreController: this.clineIgnoreController,
        commandPermissionController: this.commandPermissionController,
        contextManager: this.contextManager,
        stateManager: this.stateManager,
    },
    callbacks: {
        say: this.say,
        ask: this.ask,
        shouldAutoApproveTool: this.shouldAutoApproveTool.bind(this),
        shouldAutoApproveToolWithPath: this.shouldAutoApproveToolWithPath.bind(this),
        // ...
    },
    coordinator: this.coordinator,
}

註解裡有個警告:handler 可以讀 config 欄位,但不能改 config 自身欄位 (比如 config.browserSession = ... 不會改 ToolExecutor 的實例變數) (config warning:134)。需要換 browser session 時要走 applyLatestBrowserSettings 這種專門入口。config.coordinator 把協調器自己也傳進去,handler 在執行中可以再調其他工具 (subagent 工具就靠這個)。

邊界與失敗

  • 缺工廠的工具名靜默跳過:toolHandlersMapRecord<ClineDefaultTool, ...>,TS 強制每個列舉值都填一項。但工廠可以回傳 undefined,目前只有 TODO 這麼做 (todo undefined:97)。registerByName 看到 undefined 直接跳過,等效於「不註冊」。
  • Handler 共享但要保持無狀態:WriteToFileToolHandlerwrite_to_filereplace_in_filenew_rule 三個名字共享一個實例 (file_edit shared:83)。如果 handler 在實例欄位裡存任務級狀態,三個名字會互相污染。所以所有狀態都從 TaskConfig 傳。
  • asToolConfig 每次重新建構:每次 ToolExecutor.execute 進來都 asToolConfig() 拼一個新 config (asToolConfig call:321)。但 config 裡參考的 service 實例是同一個,handler 改 service 的狀態會跨呼叫生效。
  • Partial handler 不 push tool result:handlePartialBlock 註解明確說「We don't push tool results in partial blocks」(no partial result:521)。partial 階段只更新 UI,真正的 tool result 在 complete block 處理時才 push。
  • handler 錯誤被 ToolExecutor 兜住:ToolExecutor.execute 用 try/catch 包住 coordinator.execute (error catch:373)。handler 拋任何錯都被 handleError 捕獲,轉成 formatResponse.toolError push 進對話,不會讓 Task 崩潰。
  • 新增工具三處必改:加工具要改三處:ClineDefaultTool 列舉加值、toolHandlersMap 加工廠、新 handler 檔案。toolUseNames 自動從列舉生成不用改,但 getSystemPrompt 裡工具描述也得加,否則模型不知道這個工具存在。

小結

Handler 是 Cline 工具體系的末端,每個工具一個檔案,實作 IToolHandler 介面。註冊靠 ClineDefaultTool 列舉 + toolHandlersMap 工廠表驅動,新增工具三處改動。要看路由本身怎麼找 handler,轉 /tools/coordinator;要看執行前的參數和權限檢查,轉 /tools/validator;要看工具被誰呼叫,轉 /agent-loop/present-assistant-message

對照官方資料:Cline 文件 · README