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