ClineToolSet:工具定義與 schema 轉換
職責
ClineToolSet 是 Cline 把「工具 (tool)」這個概念在三個層面統一管理的中樞。第一層是「註冊」:每個工具按模型家族 (ModelFamily) 註冊一份 ClineToolSpec,Generic 家族兜底(register:19);第二層是「選擇」:getEnabledTools 按 variant 聲明的 tool id 列表 + 上下文要求(contextRequirements)過濾出當前該啟用的工具(getEnabledTools:87);第三層是「轉換」:getNativeConverter 按 providerId 選不同的轉換器,把 ClineToolSpec 翻譯成 Anthropic Tool / OpenAI ChatCompletionTool / Google FunctionDeclaration(getNativeConverter:151).
它的輸出有兩條路:一條是「文字路徑」,工具 schema 被拼成 <tool_name>...<param>...</param></tool_name> 風格的 XML 段塞進系統提示,適用於不支援 native tool calling 的模型(PromptBuilder.tool:146);另一條是「原生路徑」,getNativeTools 把轉換後的 schema 陣列作為 createMessage 的 tools 參數傳給 ApiHandler(getNativeTools:170).
設計動機
- 雙 schema 出口:同一份
ClineToolSpec既能轉成 XML 文字又能轉成原生 tool schema,因為 Cline 支援的模型裡有的支援 native function calling(Anthropic / OpenAI / Gemini),有的不支援(很多本地模型),後者只能靠在系統提示裡塞 XML 讓模型按格式輸出。 - variant fallback 鏈:
getToolByNameWithFallback先找精確 family → 再找 GENERIC → 再遍歷所有 variant(fallback:48),保證某個工具只在 next-gen 註冊了但 generic 沒註冊時也能被 generic variant 用上。 - contextRequirements 動態過濾:工具可以聲明
contextRequirements: (ctx) => boolean,比如browser_action要求supportsBrowserUse=true,上下文不滿足就過濾掉(ctx filter:101),比硬編碼 variant 列表更靈活。 - 動態 subagent 工具:
getDynamicSubagentToolSpecs在執行時根據AgentConfigLoader載入的 subagent 設定生成USE_SUBAGENTS工具的多個變體(dynamic subagent:108),每個 subagent 一個名字,所以工具集不是靜態的。 - MCP 工具統一進 toolset:
mcpToolToClineToolSpec把 MCP server 暴露的 tools 轉成ClineToolSpec格式,id 都是use_mcp_tool,name 是serverUid__mcp__toolName(mcp name:238),MCP 工具和內建工具用同一套機制管理。 - name 長度兜底:MCP 工具 name 超過 64 字元直接 skip 不註冊(
length check:243),因為 provider API 會拒絕過長的 tool name。
關鍵檔案
ClineToolSet class:8— 持有variants: Map<ModelFamily, Set<ClineToolSet>>靜態註冊表。register:19— 靜態方法,建立實例並按 family 存進 Map,同 id 去重。getToolByNameWithFallback:48— 三級查找:精確 family → GENERIC → 全表掃描。getToolsForVariantWithFallback:73— 批次按 id 列表解析,去重。getEnabledTools:87— variant.tools 列表 + contextRequirements 過濾。getDynamicSubagentToolSpecs:108— subagent 開啟且不是 subagent 執行時,從 AgentConfigLoader 動態生成 USE_SUBAGENTS 變體。getEnabledToolSpecs:136— 合併靜態工具和動態 subagent 工具,subagent 動態生成時移除靜態 USE_SUBAGENTS。getNativeConverter:151— providerId 選轉換器:anthropic/bedrock/minimax → inputSchema,openai 相容 → functionDefinition,gemini → functionDeclarations。getNativeTools:170— 僅當 variant.labels.use_native_tools === 1 且 context.enableNativeToolCalls 時才回傳原生 schema 陣列。mcpToolToClineToolSpec:198— 把 MCP server 的 inputSchema 翻譯成 ClineToolSpec.parameters,保留 enum / format 等額外欄位。ClineDefaultTool enum:8— 所有內建工具 id 的列舉:execute_command / read_file / write_to_file / replace_in_file / search_files 等。ClineToolSpec:10— 工具規格類型:id / name / description / parameters / contextRequirements。toolSpecFunctionDefinition:52— 轉 OpenAI ChatCompletionTool,帶strict: false、additionalProperties: false。toolSpecInputSchema:145— 轉 Anthropic Tool,欄位名是input_schema不是parameters。toolSpecFunctionDeclarations:243— 轉 Google FunctionDeclaration,type 用 STRING / NUMBER / BOOLEAN 等大寫常數。registerClineToolSets:32— 啟動時把所有工具 variants 一次性註冊進 ClineToolSet。
資料流
工具從「註冊」到「最終被 ApiHandler 使用」要經過四步:註冊 → variant 聲明 → 啟用過濾 → schema 轉換。註冊階段在 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)
})
}每個工具檔案匯出 _variants 陣列,裡面是針對不同 ModelFamily 寫的 ClineToolSpec(allToolVariants:34). 註冊後,variant 自己聲明要用哪些工具 id:
// 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,
)generic variant 列了 18 個工具 id(generic tools:58),getEnabledTools 拿這個列表去 getToolByNameWithFallback 逐個解析,再過 contextRequirements 過濾。最後如果要走原生 tool calling,轉換器把 ClineToolSpec 轉成對應 provider 的 schema:
// 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
}注意 replacer 函式會替換 / / 等模板變數(replacer:404),所以工具的 description 裡可以寫這些占位符,執行時按 context 填充。
邊界與失敗
- 工具重複註冊:同一 family + 同 id 不會重複加進 Set,程式碼裡顯式
some(t => t.config.id === config.id)檢查(dedup:25). - MCP 工具 name 過長:
mcpToolName.length > 64直接 skip(length check:243),不在工具集裡出現,模型就呼叫不到這個 MCP 工具。 - variant 沒聲明 tools:
getEnabledTools在variant.tools為空時回傳空陣列(empty tools:89),意味著該 variant 走純文字對話,不帶任何工具。 - subagent 巢狀:
getDynamicSubagentToolSpecs在context.isSubagentRun為 true 時直接回傳空(subagent guard:109),避免 subagent 再生成 subagent 工具,防止無限遞迴。 - use_native_tools label 未設:
getNativeTools預設variant.labels.use_native_tools !== 1時回傳 undefined(label gate:175),即使 context.enableNativeToolCalls 也不生效,variant 必須顯式開啟。 - converter 拋錯:
toolSpecInputSchema等轉換器在tool.contextRequirements不滿足時拋"Tool X does not meet context requirements"(contextRequirements throw:55),理論上getEnabledTools已經過濾過,這裡是兜底。 - MCP server.disabled:MCP 工具獲取時先
filter(s => s.disabled !== true)(disabled filter:183),停用的 server 的工具完全不進 toolset。
小結
ClineToolSet 是工具的「中央註冊表 + 選擇器 + schema 翻譯器」三合一,關鍵設計是同一份 ClineToolSpec 能同時走「文字 XML 段」和「原生 tool schema」兩條出口,讓 Cline 既能服務支援 native function calling 的模型,也能服務只能輸出文字的本地模型。MCP 工具被翻譯成和內建工具一樣的格式塞進同一套管理,subagent 工具按執行時設定動態生成,這些都讓 toolset 不是靜態表而是動態構造的。
- 工具提示怎麼拼成 XML 段:
/prompts/prompt-builder - plan / act 模式各自能用的工具差異:
/prompts/mode - handler 怎麼消費 tools 參數:
/providers/anthropic