Skip to content

Tool Handlers: Gesamtbild der Werkzeug-Handler

源码版本v4.0.10

Verantwortung

Ein Handler ist die konkrete Implementierung eines Werkzeugs. Eine Handler-Klasse entspricht einer Werkzeugverhalten: Datei lesen, Datei schreiben, Befehl ausführen, MCP aufrufen, Subagent starten usw. Sie alle implementieren das Interface IToolHandler (IToolHandler:34) und bieten das Dreigestirn name, execute, getDescription. Wer gestreamte Partial-Blöcke unterstützen will, implementiert zusätzlich IPartialBlockHandler; wer beides implementiert, wird als IFullyManagedTool markiert.

Die Werkzeugliste von Cline ist im ClineDefaultTool-Enum definiert (ClineDefaultTool:8). Es umfasst derzeit 27 Werte, von ask_followup_question bis use_subagents. Diese Namen entsprechen direkt dem Feld name des tool_use-Blocks, den das LLM sieht. Unter dem Enum steht eine Zeile toolUseNames = Object.values(ClineDefaultTool) (toolUseNames:40), die das Enum in ein Array flachklopft; der ToolExecutor durchläuft dieses Array beim Konstruieren und registriert alle Werkzeuge.

Die Handler-Dateien liegen alle unter apps/vscode/src/core/task/tools/handlers/, ein Werkzeug pro Datei, mit Klassennamen der Form XxxToolHandler. Alle Handler teilen sich ein TaskConfig-Kontextobjekt, das cwd, taskState, messageState, api, diverse Dienste und Callbacks enthält; darüber greift ein Handler auf sämtliche äusseren Abhängigkeiten zu (asToolConfig:135).

Entwurfsmotivation

  • Ein Werkzeug, eine Datei: Pro Handler eine Datei; die Verantwortungsgrenzen sind klar. Ein neues Werkzeug bedeutet nur eine zusätzliche Datei, ohne bestehenden Code zu berühren; ein entferntes Werkzeug bedeutet nur eine gelöschte Datei.
  • Interface-Schichtung statt Vererbung: IToolHandler ist der minimale Vertrag, IPartialBlockHandler die optionale Streaming-Fähigkeit, IFullyManagedTool die «vollständige» Markierung (IFullyManagedTool:44). Komposition vor Vererbung; Handler implementieren nur die Interfaces, die sie brauchen.
  • Handler halten keinen Zustand: Handler-Instanzen werden vom Coordinator gecacht; der gesamte Zustand kommt über TaskConfig hinein. Dieselbe Handler-Instanz kann mehrfach mit execute aufgerufen werden und darf keinen Task-bezogenen Zustand in eigenen Feldern ablegen.
  • TaskConfig einmal konstruiert, vielerorts genutzt: Der ToolExecutor sammelt beim Aufruf von asToolConfig() alle Abhängigkeiten in einem Objekt (asToolConfig:135); der Handler greift über config auf mcpHub, browserSession, diffViewProvider, clineIgnoreController und weitere Dienste zu, ohne selbst zu injizieren.
  • ClineDefaultTool-Enum treibt Registrierung: toolUseNames wird automatisch aus dem Enum erzeugt; die Registrierungsschleife muss die Werkzeugliste nicht per Hand pflegen (for of toolUseNames:204). Ein neuer Enum-Wert rückt automatisch in die Registrierungsschleife, vorausgesetzt die toolHandlersMap des Coordinator enthält eine entsprechende Fabrik.
  • SharedToolHandler zur Implementierungswiederverwendung: Wenn mehrere Namen eine Implementierung teilen (write_to_file / replace_in_file / new_rule teilen sich WriteToFileToolHandler), hüllt eine Wrapper-Klasse ein und überschreibt den Namen (SharedToolHandler:52), um drei identische Klassen zu vermeiden.

Schlüsseldateien

  • ClineDefaultTool enum:8 — Enum mit 27 Werkzeugnamen; hierher stammt der Werkzeugname, den das LLM sieht.
  • toolUseNames:40 — Aus dem Enum flachgeklopftes Array; treibt die vollständige Registrierung im ToolExecutor.
  • toolHandlersMap:79 — Mapping von 27 Enum-Werten auf Handler-Fabriken; hier wird angepasst, wenn ein neues Werkzeug dazukommt.
  • registerToolHandlers:201 — Durchläuft beim Konstruieren toolUseNames und registriert vollständig.
  • asToolConfig:135 — Konstruiert vor jeder Werkzeugausführung eine TaskConfig, die alle Abhängigkeiten für den Handler bündelt.
  • WriteToFileToolHandler:26 — Implementiert IFullyManagedTool, unterstützt Partial + Complete und wird von drei Werkzeugnamen geteilt: write_to_file, replace_in_file, new_rule.
  • ReadFileToolHandler:142 — Implementierung von read_file; erhält im Konstruktor den ToolValidator für die Pfadprüfung.
  • ExecuteCommandToolHandler:58 — Implementierung von execute_command; Bash-Befehlsausführung plus Berechtigungsprüfung.
  • AttemptCompletionHandler:39 — Implementierung von attempt_completion; implementiert nur IToolHandler + IPartialBlockHandler und markiert den Task-Abschluss.
  • UseMcpToolHandler:14 — Implementierung von use_mcp_tool; sämtliche MCP-Werkzeugaufrufe laufen hier zusammen.
  • UseSubagentsToolHandler:49 — Implementierung von use_subagents; dient auch dynamischen Subagent-Werkzeugnamen (via SharedToolHandler-Wrapper).
  • ListFilesToolHandler partial approval:48 — Ruft bereits in der Partial-Block-Phase shouldAutoApproveToolWithPath auf, um den UI-Pfad festzulegen.

Datenfluss

Der Ablauf beim Hinzufügen eines Werkzeugs lautet: «Handler-Klasse schreiben → eine Zeile in der Map registrieren → Enum um einen Wert erweitern». So sieht ein Map-Eintrag aus:

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,
}

Parameter der Fabrikfunktion ist der ToolValidator. Handler, die Pfade prüfen müssen (read/write/search/list), nehmen ihn entgegen; die übrigen (ask/attempt/browser/mcp usw.) verwenden _v als Platzhalter. Nur TODO liefert undefined und zeigt damit, dass dieser Werkzeugname nicht vom Coordinator verwaltet wird (todo undefined:97).

Das config-Objekt, das ein Handler zur Ausführung erhält, sieht so aus:

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,
}

Ein Kommentar warnt: Handler dürfen config-Felder lesen, aber nicht das config-Objekt selbst verändern (etwa config.browserSession = ... ändert nicht die Instanzvariable des ToolExecutor) (config warning:134). Soll die Browser-Session getauscht werden, ist ein eigener Einstieg wie applyLatestBrowserSettings zu nutzen. config.coordinator reicht den Coordinator selbst an den Handler weiter, sodass ein Handler während der Ausführung weitere Werkzeuge aufrufen kann (darauf setzt das Subagent-Werkzeug).

Grenzen und Fehler

  • Werkzeugnamen ohne Fabrik werden still übersprungen: toolHandlersMap ist ein Record<ClineDefaultTool, ...>, TypeScript erzwingt für jeden Enum-Wert einen Eintrag. Die Fabrik darf jedoch undefined zurückgeben; derzeit tut das nur TODO (todo undefined:97). registerByName überspringt bei undefined die Registrierung; das entspricht «nicht registriert».
  • Handler werden geteilt, müssen aber zustandslos bleiben: WriteToFileToolHandler wird von drei Namen (write_to_file, replace_in_file, new_rule) als gemeinsame Instanz geteilt (file_edit shared:83). Würde ein Handler Task-bezogenen Zustand in Instanzfeldern ablegen, würden sich die drei Namen gegenseitig verschmutzen. Deshalb kommt der gesamte Zustand über TaskConfig herein.
  • asToolConfig wird jedes Mal neu konstruiert: Bei jedem Eintreten von ToolExecutor.execute wird per asToolConfig() ein neues config zusammengebaut (asToolConfig call:321). Die im config referenzierten Dienst-Instanzen sind jedoch dieselben; ändert ein Handler den Zustand eines Dienstes, wirkt das über Aufrufe hinweg.
  • Partial-Handler pushed kein Tool-Result: Der Kommentar zu handlePartialBlock hält explizit fest: «We don't push tool results in partial blocks» (no partial result:521). In der Partial-Phase wird nur das UI aktualisiert; das echte tool-result wird erst bei der Behandlung des Complete-Blocks gepusht.
  • Fehler des Handlers werden vom ToolExecutor aufgefangen: ToolExecutor.execute umschliesst coordinator.execute mit try/catch (error catch:373). Jede vom Handler geworfene Fehler wird von handleError gefangen, in ein formatResponse.toolError umgewandelt und in die Konversation gepusht, ohne die Task zum Absturz zu bringen.
  • Neues Werkzeug erfordert drei Änderungen: Ein neues Werkzeug bedingt drei Anpassungen: einen Wert im Enum ClineDefaultTool, eine Fabrik in toolHandlersMap und eine neue Handler-Datei. toolUseNames wird automatisch aus dem Enum erzeugt und muss nicht angefasst werden; allerdings muss auch in getSystemPrompt eine Werkzeugbeschreibung ergänzt werden, sonst weiss das Modell vom Werkzeug nichts.

Zusammenfassung

Handler sind das Ende von Clines Werkzeugsystem: ein Werkzeug pro Datei, das das Interface IToolHandler implementiert. Die Registrierung wird durch das ClineDefaultTool-Enum und die toolHandlersMap-Fabriktabelle getrieben; ein neues Werkzeug bedeutet drei Änderungen. Wie das Routing den Handler findet, steht in /tools/coordinator; für die Parameter- und Berechtigungsprüfung vor der Ausführung siehe /tools/validator; wer die Werkzeuge aufruft, siehe /agent-loop/present-assistant-message.

Siehe offizielle Dokumentation: Cline-Dokumentation · README