ClineToolSet : définition des outils et conversion de schéma
Responsabilités
ClineToolSet est le hub par lequel Cline unifie la notion d'« outil » (tool) à trois niveaux. Premier niveau, « enregistrement » : chaque outil est enregistré, par famille de modèles (ModelFamily), sous forme d'une ClineToolSpec, la famille Generic servant de filet de secours (register:19). Deuxième niveau, « sélection » : getEnabledTools filtre, à partir de la liste d'ids d'outils déclarée par la variante et des exigences de contexte (contextRequirements), les outils à activer pour le contexte courant (getEnabledTools:87). Troisième niveau, « conversion » : getNativeConverter choisit un convertisseur selon providerId et traduit la ClineToolSpec en Anthropic Tool, OpenAI ChatCompletionTool ou Google FunctionDeclaration (getNativeConverter:151).
Sa sortie suit deux chemins : la « voie texte », où le schéma de l'outil est assemblé en section XML <tool_name>...<param>...</param></tool_name> injectée dans le system prompt, destinée aux modèles qui ne supportent pas l'appel d'outil natif (PromptBuilder.tool:146) ; et la « voie native », où getNativeTools fournit le tableau de schémas convertis comme paramètre tools de createMessage à l'ApiHandler (getNativeTools:170).
Motivation de conception
- Double sortie de schéma : une même
ClineToolSpecpeut se convertir à la fois en XML texte et en schéma d'outil natif, car parmi les modèles supportés par Cline certains acceptent le function calling natif (Anthropic / OpenAI / Gemini), d'autres non (notamment des modèles locaux), et ces derniers ne peuvent qu'être guidés par injection d'un XML dans le system prompt pour forcer un format de sortie. - chaîne de repli par variante :
getToolByNameWithFallbackcherche d'abord la famille exacte, puis GENERIC, puis parcourt toutes les variantes (fallback:48), pour qu'un outil enregistré uniquement en next-gen puisse tout de même être utilisé par la variante generic. - filtrage dynamique par contextRequirements : un outil peut déclarer
contextRequirements: (ctx) => boolean; par exemplebrowser_actionexigesupportsBrowserUse=true, faute de quoi il est filtré (ctx filter:101), plus souple que de coder en dur une liste de variantes. - outils subagent dynamiques :
getDynamicSubagentToolSpecsgénère, au runtime, plusieurs variantes de l'outilUSE_SUBAGENTSà partir des configurations de subagent chargées parAgentConfigLoader(dynamic subagent:108), une par nom de subagent — la toolset n'est donc pas statique. - outils MCP intégrés au toolset :
mcpToolToClineToolSpecconvertit les tools exposés par un serveur MCP enClineToolSpec, tous avec l'iduse_mcp_toolet un name au formatserverUid__mcp__toolName(mcp name:238). Outils MCP et outils internes sont gérés par le même mécanisme. - garde-fou sur la longueur du name : si le name d'un outil MCP dépasse 64 caractères, il est purement ignoré et non enregistré (
length check:243), car les API des providers refusent les noms d'outil trop longs.
Fichiers clés
ClineToolSet class:8— détientvariants: Map<ModelFamily, Set<ClineToolSet>>, registre statique.register:19— méthode static ; crée une instance, la stocke par famille, déduplique par id.getToolByNameWithFallback:48— recherche à trois niveaux : famille exacte → GENERIC → parcours complet.getToolsForVariantWithFallback:73— résolution en lot par liste d'ids, avec déduplication.getEnabledTools:87— filtrage par listevariant.tools+ contextRequirements.getDynamicSubagentToolSpecs:108— si subagents activés et qu'on n'est pas dans un run subagent, génère dynamiquement les variantes USE_SUBAGENTS depuis AgentConfigLoader.getEnabledToolSpecs:136— fusionne outils statiques et outils subagent dynamiques ; retire le USE_SUBAGENTS statique quand le dynamique est généré.getNativeConverter:151— sélection du convertisseur par providerId : anthropic/bedrock/minimax → inputSchema, compatible openai → functionDefinition, gemini → functionDeclarations.getNativeTools:170— ne renvoie le tableau de schémas natifs que sivariant.labels.use_native_tools === 1et quecontext.enableNativeToolCallsest vrai.mcpToolToClineToolSpec:198— traduit l'inputSchema d'un serveur MCP en ClineToolSpec.parameters, en préservant les champs supplémentaires comme enum / format.ClineDefaultTool enum:8— enumération de tous les ids d'outils internes : execute_command / read_file / write_to_file / replace_in_file / search_files, etc.ClineToolSpec:10— type de spec d'outil : id / name / description / parameters / contextRequirements.toolSpecFunctionDefinition:52— conversion en OpenAI ChatCompletionTool, avecstrict: falseetadditionalProperties: false.toolSpecInputSchema:145— conversion en Anthropic Tool, le champ s'appelleinput_schemaet nonparameters.toolSpecFunctionDeclarations:243— conversion en Google FunctionDeclaration, le type utilise des constantes majuscules STRING / NUMBER / BOOLEAN.registerClineToolSets:32— au démarrage, enregistre toutes les variantes d'outils en une fois dans ClineToolSet.
Flux de données
De l'enregistrement à l'utilisation finale par l'ApiHandler, l'outil traverse quatre étapes : enregistrement → déclaration par variante → filtrage d'activation → conversion de schéma. La phase d'enregistrement se déclenche à la construction de PromptRegistry :
// apps/vscode/src/core/prompts/system-prompt/tools/init.ts
export function registerClineToolSets(): void {
const allToolVariants = [
...access_mcp_resource_variants,
...act_mode_respond_variants,
// ...所有 22 个工具的 variants 数组
...apply_patch_variants,
]
allToolVariants.forEach((v) => {
ClineToolSet.register(v)
})
}Chaque fichier d'outil exporte un tableau _variants contenant des ClineToolSpec écrits pour différentes ModelFamily (allToolVariants:34). Une fois enregistrés, chaque variante déclare elle-même les ids d'outils qu'elle veut utiliser :
// apps/vscode/src/core/prompts/system-prompt/variants/generic/config.ts
.tools(
ClineDefaultTool.BASH,
ClineDefaultTool.FILE_READ,
ClineDefaultTool.FILE_NEW,
ClineDefaultTool.FILE_EDIT,
ClineDefaultTool.SEARCH,
ClineDefaultTool.LIST_FILES,
ClineDefaultTool.LIST_CODE_DEF,
ClineDefaultTool.BROWSER,
ClineDefaultTool.MCP_USE,
ClineDefaultTool.MCP_ACCESS,
ClineDefaultTool.ASK,
ClineDefaultTool.ATTEMPT,
ClineDefaultTool.PLAN_MODE,
ClineDefaultTool.MCP_DOCS,
ClineDefaultTool.TODO,
ClineDefaultTool.GENERATE_EXPLANATION,
ClineDefaultTool.USE_SKILL,
ClineDefaultTool.USE_SUBAGENTS,
)La variante generic liste 18 ids d'outils (generic tools:58). getEnabledTools prend cette liste, la résout via getToolByNameWithFallback pour chaque entrée, puis applique le filtre contextRequirements. Si l'on doit utiliser l'appel d'outil natif, un convertisseur traduit la ClineToolSpec dans le schéma attendu par le provider :
// apps/vscode/src/core/prompts/system-prompt/spec.ts
export function toolSpecInputSchema(tool: ClineToolSpec, context: SystemPromptContext): AnthropicTool {
// ...
const toolInputSchema: AnthropicTool = {
name: tool.name,
description: replacer(tool.description, context),
input_schema: {
type: "object",
properties,
required,
},
}
return toolInputSchema
}À noter : la fonction replacer substitue les variables de template / / (replacer:404), ce qui permet d'écrire ces placeholders dans la description des outils, remplis au runtime à partir du context.
Limites et échecs
- enregistrement en doublon d'outil : une même combinaison famille + id n'est pas ajoutée deux fois dans le Set, avec une vérification explicite
some(t => t.config.id === config.id)(dedup:25). - name d'outil MCP trop long : si
mcpToolName.length > 64, l'outil est ignoré (length check:243), absent de la toolset, et donc inaccessible au modèle. - variante sans outils déclarés :
getEnabledToolsrenvoie un tableau vide quandvariant.toolsest vide (empty tools:89), signifiant que la variante converse en texte pur, sans aucun outil. - imbrication de subagents :
getDynamicSubagentToolSpecsrenvoie directement un tableau vide quandcontext.isSubagentRunest true (subagent guard:109), empêchant un subagent de générer à son tour des outils subagent et évitant toute récursion infinie. - label use_native_tools non défini : par défaut, si
variant.labels.use_native_tools !== 1,getNativeToolsrenvoie undefined (label gate:175), même sicontext.enableNativeToolCallsest vrai — il faut que la variante l'active explicitement. - convertisseur qui lève : les convertisseurs comme
toolSpecInputSchemalèvent"Tool X does not meet context requirements"sitool.contextRequirementsn'est pas satisfait (contextRequirements throw:55). En théoriegetEnabledToolsa déjà filtré ; c'est un filet de sécurité. - MCP server.disabled : à la récupération des outils MCP, on filtre d'abord par
filter(s => s.disabled !== true)(disabled filter:183). Les outils d'un serveur désactivé n'entrent jamais dans la toolset.
Résumé
ClineToolSet est à la fois « registre central + sélecteur + traducteur de schéma » pour les outils. Le point clé du design est qu'une même ClineToolSpec peut emprunter deux sorties à la fois : « section XML texte » et « schéma d'outil natif », ce qui permet à Cline de servir aussi bien les modèles qui supportent le function calling natif que ceux qui ne savent produire que du texte. Les outils MCP sont convertis au même format que les outils internes et gérés par le même mécanisme ; les outils subagent sont générés dynamiquement selon la configuration runtime. La toolset n'est donc pas une table statique mais une construction dynamique.
- Comment les prompts d'outils deviennent une section XML :
/prompts/prompt-builder - Différences d'outils entre modes plan / act :
/prompts/mode - Comment le handler consomme le paramètre tools :
/providers/anthropic
Voir la documentation officielle : documentation Cline · README