1. 工具系统概述
ToolManager 是 Agent 与外部世界交互的桥梁。它位于 packages/agent-core/src/agent/tool/,是 Agent 类的核心成员之一。在一个 turn 的 loop 中,LLM 返回 tool_use 停止原因后,loop 会查询 ToolManager 获取可执行的工具集合,完成工具调用的分配与执行。
工具的来源有三种:
| 来源 | ToolSource |
说明 |
|---|---|---|
| 内置工具 | 'builtin' |
框架自带的核心工具,如文件读写、Shell 执行、Web 搜索等。由 initializeBuiltinTools() 按依赖注入动态构建 |
| 用户自定义工具 | 'user' |
通过 registerUserTool() 注册的外部工具,支持 inline 和 deferred 两种披露模式 |
| MCP 工具 | 'mcp' |
通过 Model Context Protocol 从外部 MCP 服务器动态发现和注册的工具,使用 mcp__serverName__toolName 命名空间 |
无论是哪种来源,所有工具都遵循统一的抽象接口 --- BuiltinTool<Input> 即 ExecutableTool<Input>,这使得 loop 层可以无差别地调度任何工具。
设计哲学: ToolManager 不区分工具的来源 --- 内置、用户、MCP 工具在 loop 层面都是等价的
ExecutableTool条目。来源标记仅用于 UI 展示和权限策略的差异化处理。
2. Tool 接口设计
2.1 ExecutableTool 核心契约
每个可执行工具必须提供三个基础字段和一个核心方法:
| 字段/方法 | 类型 | 说明 |
|---|---|---|
name |
string |
工具的唯一标识名称,LLM 通过此名称发起调用 |
description |
string |
工具的功能描述,会出现在 system prompt 的工具列表中 |
parameters |
Record |
参数 的 JSON Schema(draft-7),描述 LLM 应如何构造调用参数 |
resolveExecution(args) |
(args: Input) => ToolExecution |
核心方法:接收 LLM 传来的参数,返回 ToolExecution 对象 |
2.2 ToolExecution --- 执行前的准备
resolveExecution() 返回的 ToolExecution 对象是工具执行的关键中介,它包含:
accesses: 声明该工具需要的资源访问(如文件路径)。权限系统据此做冲突检测和序列化。approvalRule: 权限规则的匹配模式,用于权限策略的决策链。display: UI 展示信息,如{ kind: 'file_io', operation: 'read', path }。execute(context): 实际的异步执行函数,接收turnId、toolCallId、signal等上下文。
2.3 参数 JSON Schema 的生成
工具使用 Zod schema 定义输入类型,然后通过 toInputJsonSchema() 转换为 LLM 可理解的 JSON Schema:
Typescript
import { z } from 'zod';
import { toInputJsonSchema } from '../../support/input-schema';
export const ReadInputSchema = z.object({
path: z.string().describe('Path to a text file...'),
line_offset: z.number().int().min(1).optional()
.describe('The line number to start reading from...'),
n_lines: z.number().int().positive().optional()
.describe('The number of lines to read...'),
});
// 转换为 draft-7 JSON Schema,使用 io: 'input' 视图
const parameters: Record<string, unknown> = toInputJsonSchema(ReadInputSchema);
关键细节:toInputJsonSchema 使用 zod 的 io: 'input' 模式确保带 .default() 的字段保持可选。同时自动为所有 object 节点添加 additionalProperties: false,防止拼写错误的参数被静默忽略。
2.4 序列化为 LLM function calling 格式
当 loop 构建 LLM 请求时,ToolManager.loopTools getter 返回当前可用的 ExecutableTool[]。Kosong 层的 generate() 会提取每个工具的 name、description、parameters 三个字段,序列化为 LLM provider 所需的 function calling 格式(OpenAI 兼容的 tools 数组)。
3. 内置工具全景
内置工具在 initializeBuiltinTools() 中按依赖注入动态构建。并非所有工具在每个 session 都可用 --- 它们的注册取决于 Agent 的配置和能力(如是否启用 cron、是否有 subagent host、是否支持图片/视频等)。
3.1 文件操作
| 工具名 | 类别 | 核心能力 |
|---|---|---|
Read |
文件 | 读取文本文件,支持行偏移、负偏移(从尾部读)、行数限制(最多 1000 行)。自动探测文件类型,对图片/视频提示使用 ReadMediaFile。内部有 100KB 字节上限和 2000 字符/行截断 |
Write |
文件 | 写入或覆盖文件。通过 Kaos 抽象层执行实际 I/O |
Edit |
文件 | 精确字符串替换编辑 --- 使用 old_string → new_string 模式进行替换。old_string 必须在文件中唯一匹配,支持 replace_all 全局替换 |
Glob |
文件 | 基于 glob pattern 的文件匹配搜索。按修改时间排序返回结果,支持分页(limit/offset) |
Grep |
文件 | 基于ripgrep 的内容搜索。支持正则表达式、文件类型过滤、上下文行(-A/-B/-C)、multiline 模式、大小写不敏感搜索 |
ReadMediaFile |
文件 | 读取图片和视频文件。支持图片压缩(WebP 解码)、多模态输入。仅当模型支持 image_in 或 video_in 时注册 |
3.2 Shell
| 工具名 | 类别 | 核心能力 |
|---|---|---|
Bash |
Shell | 执行 shell 命令。Windows 上使用 Git Bash,Linux/macOS 使用系统 bash。• 沙箱模式 :通过 Kaos 抽象层执行,而非直接调用 node:child_process• 后台执行 :run_in_background: true 将命令转为后台任务• 超时管理 :前台默认 60s(最大 5min),后台默认 10min(最大 24h),支持 disable_timeout• 用户中断 :支持通过 AbortController 取消运行中的命令(TUI 中 Esc/Ctrl+C)• 自动后台 :前台命令超时时可自动转为后台(bashAutoBackgroundOnTimeout) |
Kaos 抽象层: Bash 工具的核心强化在于 Kaos --- 一个跨平台的进程执行抽象。它提供了
exec、execWithEnv、spawn等统一接口,使得沙箱模式的实现可以独立于操作系统。非沙箱模式下,Kaos 仍然提供进程生命周期管理(stderr 关闭后 SIGTERM → grace → SIGKILL 两阶段杀死)。
3.3 Web
| 工具名 | 类别 | 核心能力 |
|---|---|---|
WebSearch |
Web | 网络搜索。依赖 toolServices.webSearcher 由 provider 注入。 支持多种搜索渠道(Moonshot provider 有专门实现) |
FetchURL |
Web | 网页抓取并处理为可读内容。内置 15 分钟自我清理缓存:相同 URL 在 15 分钟内重复请求直接返回缓存结果,减少重复抓取 |
3.4 协作
| 工具名 | 类别 | 核心能力 |
|---|---|---|
Agent |
协作 | 生成子 Agent(subagent)。传入 prompt 和 description,子 Agent 在隔离的上下文中执行任务。支持后台运行和超时控制 |
AgentSwarm |
协作 | 并行多 Agent 协调。在 swarm 模式下允许多个子 Agent 并行处理任务 |
AskUserQuestion |
协作 | 向用户提问。仅当 agent.rpc?.requestQuestion 可用时注册 |
Skill |
协作 | 调用已注册的 Skill。仅当有可调用 Skill 时注册 |
3.5 状态管理
| 工具名 | 类别 | 核心能力 |
|---|---|---|
TodoList |
状态 | 待办事项管理。基于 ToolStore 的键值存储,支持创建、更新、完成、删除任务项 |
TaskList |
后台 | 列出所有后台任务及其状态(运行中/已完成/已失败) |
TaskOutput |
后台 | 获取指定后台任务的输出。支持流式读取和过滤 |
TaskStop |
后台 | 停止指定的后台任务 |
3.6 定时任务
| 工具名 | 类别 | 核心能力 |
|---|---|---|
CronCreate |
定时 | 创建定时任务。支持一次性(recurring: false)和重复执行。任务持久化到 sessionDir/cron/.json |
CronList |
定时 | 列出当前 session 的所有定时任务及其下次执行时间 |
CronDelete |
定时 | 删除指定的定时任务 |
定时任务的生命周期: 任务绑定到 session --- 恢复同一 session 时会重新加载已持久化的 cron 任务。停机期间错过的触发会被合并为单次投递(
coalescedCount)。任务不会跨 session 继承。所有触发时间都经过 jitter 随机化处理,避免精确时间点的竞态问题。
3.7 规划与目标
| 工具名 | 类别 | 核心能力 |
|---|---|---|
EnterPlanMode |
规划 | 进入规划模式。在此模式下 Agent 专注于分析和规划,限制某些工具的执行 |
ExitPlanMode |
规划 | 退出规划模式,恢复正常执行流程 |
CreateGoal |
目标 | 创建高层次的执行目标。仅 main agent 可用(agent.type === 'main') |
GetGoal |
目标 | 获取目标的状态和进度。仅 main agent 可用 |
UpdateGoal |
目标 | 更新目标信息。仅 main agent 可用 |
SetGoalBudget |
目标 | 为目标设置 token 或其他资源预算。仅 main agent 可用 |
3.8 工具发现 --- select_tools
| 工具名 | 类别 | 核心能力 |
|---|---|---|
SelectTools |
发现 | 渐进式工具披露 的核心。LLM 按需加载 MCP 工具和 deferred 模式的用户工具。 工具 schema 从实时注册表读取并作为 role: 'system' 消息注入上下文,下一个 step 即可直接调用。 |
4. 工具注册与发现
4.1 注册表结构
ToolManager 内部维护了三张核心注册表:
typescript
// 内置工具 --- 由 initializeBuiltinTools() 按 Agent 能力动态构建
protected builtinTools: Map<string, BuiltinTool> = new Map();
// 用户自定义工具 --- 通过 registerUserTool() 添加
protected readonly userTools: Map<string, ExecutableTool> = new Map();
// MCP 工具 --- 通过 registerMcpServer() 添加,附带服务器来源信息
protected readonly mcpTools: Map<string, McpToolEntry> = new Map();
// 按服务器名称索引 MCP 工具
protected readonly mcpToolsByServer: Map<string, string[]> = new Map();
此外,还有两个辅助集合用于工具可见性控制:
enabledTools: 一个Set<string>,存储当前启用的内置和用户工具名称。由配置文件的 profile 或setActiveTools()管理。mcpAccessPatterns: glob 模式数组(如mcp__*、mcp__github__*),控制哪些 MCP 工具对当前 session 可见。
4.2 工具按类别组织
从模块结构来看,工具按功能类别在源码中组织:
tools/builtin/file/--- 文件操作类(read, write, edit, glob, grep, read-media)tools/builtin/shell/--- Shell 类(bash)tools/builtin/web/--- Web 类(web-search, fetch-url)tools/builtin/collaboration/--- 协作文类(agent, agent-swarm, ask-user, skill-tool)tools/builtin/state/--- 状态类(todo-list)tools/builtin/planning/--- 规划类(enter-plan-mode, exit-plan-mode)tools/builtin/goal/--- 目标类(create-goal, get-goal, update-goal, set-goal-budget)tools/background/--- 后台任务类(task-list, task-output, task-stop)tools/cron/--- 定时任务类(cron-create, cron-list, cron-delete)tools/builtin/select-tools.ts--- 工具发现类
4.3 渐进式工具披露(Progressive Disclosure)
当工具集非常大时(特别是连接了多个 MCP 服务器后,可能有上百个工具),将所有工具的完整 schema 放入 system prompt 会显著增加 token 消耗。渐进式工具披露解决了这个问题:
- 核心内置工具始终在顶层
tools[]中 - MCP 工具和标记为
deferred的用户工具仅以名称列表的形式在 system context 中宣布 - LLM 需要时调用
select_tools按名称加载具体的工具 schema - 加载后的工具成为可调用的执行条目,但其 schema 作为历史消息存储而非重复出现在每个请求的
tools[]中 - compaction 发生时,已被折叠的 schema 消息被丢弃,LLM 需重新选择仍需要的工具
Typescript
// ToolManager.loopTools getter 中的披露逻辑
const disclosure = this.progressiveDisclosure;
const enabledMcpNames = [...this.mcpTools.keys()]
.filter((name) => this.isMcpToolEnabled(name));
// 披露模式开启时只暴露已加载的动态工具
const loadedSet = disclosure ? this.loadedDynamicToolNames() : undefined;
const mcpNames = loadedSet === undefined
? enabledMcpNames
: enabledMcpNames.filter((name) => loadedSet.has(name));
// select_tools 仅在披露模式下暴露
const selectToolsName = disclosure ? [b.SELECT_TOOLS_TOOL_NAME] : [];
4.4 性能考量
在大型工具集场景下(如连接了 GitHub、Slack、数据库等多个 MCP 服务器),工具 schema 的 prompt token 消耗是一个关键约束。以一个平均包含 500 tokens schema 描述的工具为例,100 个工具将消耗约 50,000 tokens 的 context 空间。渐进式披露将这一成本分散到按需加载的时机,显著降低了每个 turn 的固定开销。
5. MCP 工具集成
5.1 MCP 概述
MCP(Model Context Protocol)是一个开放协议,允许 AI 应用通过标准化接口连接到外部数据源和工具。在 kimi-code 中,MCP 集成由 packages/agent-core/src/mcp/ 模块实现。
5.2 工具命名空间
MCP 工具使用三段式命名解决多服务器间的冲突:
Typescript
// 命名格式:mcp__serverName__toolName
// 各部分经过 sanitize 处理(非 ASCII 字符替换为 _,连续 _ 折叠)
export function qualifyMcpToolName(serverName: string, toolName: string): string {
const full = `mcp__${sanitizeMcpNamePart(serverName)}__${sanitizeMcpNamePart(toolName)}`;
if (full.length <= 64) return full; // 大多数 LLM provider 限制工具名为 64 字符
// 超出时使用 FNV-1a 8 字符哈希后缀进行确定性截断
const hash = stableHash8(full);
const head = full.slice(0, 64 - hash.length - 1);
return `${head}_${hash}`;
}
5.3 MCP 客户端架构
- 连接管理:
McpConnectionManager管理所有 MCP 服务器的生命周期 --- 连接、重连、断开、状态变更通知 - OAuth 认证: 当服务器需要认证时,自动注入
mcp__auth__<serverName>合成工具,引导用户完成 OAuth 流程 - 状态监听:
ToolManager.attachMcpTools()订阅McpConnectionManager.onStatusChange,当服务器状态变更时动态注册/注销工具
5.4 MCP 工具的发现与注册
完整的注册流程如下:
kotlin
// 当 MCP 服务器连接成功时
private registerConnectedMcpServer(mcp: McpConnectionManager, entry: McpServerEntry): void {
const resolved = mcp.resolved(entry.name);
// 1. 注册工具的 ExecutableTool 包装
const result = this.registerMcpServer(
entry.name,
resolved.client,
resolved.tools,
resolved.enabledNames, // 配置中指定的工具白名单
);
// 2. 记录工具发现事件(可观察性)
this.recordMcpToolsDiscovered(
entry.name,
resolved.rawTools, // 服务器返回的原始工具列表
resolved.enabledNames,
result.collisions, // 命名冲突信息
);
// 3. 通知 UI 更新
this.agent.emitEvent({
type: 'tool.list.updated',
reason: 'mcp.connected',
serverName: entry.name,
});
}
每个 MCP 工具被包装为一个 ExecutableTool,其 execute 闭包内部通过 client.callTool(toolName, args, signal) 调用远程 MCP 服务器:
typescript
const wrapped: ExecutableTool = {
name: qualified,
description: tool.description,
parameters: tool.parameters,
resolveExecution: (args) => ({
approvalRule: qualified,
execute: async (context) => {
const result = await client.callTool(
tool.name,
(args ?? {}) as Record<string, unknown>,
context.signal,
);
// 将 MCP 结果转换为 ExecutableToolResult
return mcpResultToExecutableOutput(result, qualified, {
originalsDir: this.agent.mediaOriginalsDir,
maxImageEdgePx: this.agent.imageLimits?.maxEdgePx(),
});
},
}),
};
5.5 命名冲突处理
当多个 MCP 服务器提供同名工具时,系统记录冲突信息(McpToolCollision)并以先注册者为准。冲突信息通过事件和日志传递给用户,帮助诊断配置问题。
5.6 MCP 资源暴露
除了工具,MCP 服务器还可以暴露 Resources --- 静态或动态的数据源。通过 ListMcpResources 和 ReadMcpResource 两个 deferred tool(按需加载的工具),Agent 可以列出和读取 MCP 资源的内容。
6. 工具执行流程
一个完整的工具调用从 LLM 的 tool_use 响应到结果返回,经历以下阶段:
1
解析与参数提取
loop 从 LLM 响应中提取 ToolCall(包含 name 和 arguments)。参数是 JSON 字符串,由 loop 层的 preflight 进行 JSON 解析。
2
参数验证(Zod Schema)
解析后的参数通过工具的 Zod schema 进行验证。loop 层的 tool-call.ts 在执行前使用 AJV(JSON Schema validator)对参数进行校验。未通过验证的参数会返回错误。
3
权限检查
调用 PermissionManager 的策略链。每个工具通过 resolveExecution() 返回的 approvalRule 和 accesses 参与权限决策。策略按优先级依次评估:模式策略(auto/yolo/manual)→ 路径访问策略 → 用户配置规则 → 默认/fallback 策略。
4
并发冲突检测(ToolAccesses)
ToolAccesses 声明了工具所需的资源(如文件路径)。同一批次中,如果两个工具声明了冲突的资源访问(如同一文件的读写),loop 会序列化它们的执行。
5
执行沙箱(Kaos)
工具通过 execute(context) 执行实际操作。文件 I/O 通过 Kaos 抽象层进行,确保跨平台兼容性和沙箱安全。Bash 命令通过 Kaos 的进程执行接口运行。
6
结果格式化
工具返回 ExecutableToolResult,包含 output(LLM 可见的内容)、isError(是否错误)、note(model-only 侧通道元数据)、message(用户可见提示)等字段。
7
执行时间统计
loop 记录每个工具的执行耗时,用于 UI 展示和性能监控。
typescript
// ExecutableToolResult 的定义
export interface ExecutableToolSuccessResult {
readonly output: ExecutableToolOutput; // LLM 可见的输出
readonly isError?: false | undefined;
readonly stopTurn?: boolean | undefined; // 是否停止当前 turn
readonly message?: string | undefined; // 用户侧提示
readonly note?: string | undefined; // model-only 元数据
}
export interface ExecutableToolErrorResult {
readonly output: ExecutableToolOutput;
readonly isError: true;
readonly telemetry?: Record<string, unknown>;
}
export type ExecutableToolResult =
| ExecutableToolSuccessResult
| ExecutableToolErrorResult;
result-builder 工具类: Bash 等复杂工具使用
ToolResultBuilder辅助构建结果。它支持流式输出拼接(stdout/stderr流式更新回调)、结构化渲染(renderAs{Text,Structured,Error}()),以及内容截断保护(默认上限 30,000 字符)。
7. 扩展:添加自定义工具
7.1 通过配置添加用户工具
用户工具通过 ToolManager.registerUserTool() 注册:
kotlin
// ToolManager.registerUserTool() 的实现
registerUserTool(input: UserToolRegistration): void {
const { name, description, parameters } = input;
const tool: ExecutableTool = {
name,
description,
parameters,
resolveExecution: (args) => ({
approvalRule: name,
execute: async (context) => {
// 通过 RPC 将调用转发到外部处理
return this.agent.rpc!.toolCall!(
{
turnId: Number(context.turnId),
toolCallId: context.toolCallId,
args,
},
{ signal: context.signal },
);
},
}),
};
this.userTools.set(name, tool);
if (input.disclosure === 'deferred') {
this.deferredUserTools.add(name); // 渐进式披露
}
this.enabledTools.add(name);
}
用户工具支持两种披露模式:
inline(默认): 工具始终出现在顶层tools[]中,LLM 可以直接调用deferred: 工具仅以名称形式宣布,LLM 需通过select_tools按需加载 schema
7.2 通过插件系统添加工具
插件(Plugin)是一种更高级的扩展机制。插件可以在 Agent 初始化时通过 injection/plugin-session-start.ts 注入 session 启动逻辑,其中包括注册自定义工具。插件注册的工具遵循与内置工具相同的 ExecutableTool 接口。
7.3 工具开发最佳实践
| 原则 | 说明 |
|---|---|
| 幂等性 | 工具的 execute 应尽可能幂等。LLM 可能因为各种原因重试同一个工具调用 |
| 超时处理 | 长时间运行的操作应支持通过 context.signal(AbortSignal)取消,并在取消后做适当清理 |
| 错误处理 | 返回明确的 ExecutableToolErrorResult 而非抛出未捕获异常。错误信息应包含足够的上下文帮助 LLM 自我修正 |
| 描述质量 | description 字段应清晰描述工具的功能和使用场景。这是 LLM 决定何时使用该工具的唯一信息来源 |
| 参数验证 | 使用 Zod schema 定义输入,通过 toInputJsonSchema() 生成 JSON Schema。在每个字段上使用 .describe() 提供清晰说明 |
| 权限声明 | 在 resolveExecution() 中正确声明 accesses 和 approvalRule,确保权限系统能正确决策 |
| 结果结构 | 输出应结构化且易于 LLM 理解。使用 note 字段传递 model-only 元数据,使用 message 字段传递用户可见的消息 |
| 披露策略 | 不常用的工具建议使用 deferred 模式,减少 prompt token 消耗 |
总结
kimi-code 的工具系统是一个设计精良的抽象层。它通过统一的 ExecutableTool 接口屏蔽了三种工具来源的差异,通过 Zod schema + JSON Schema 的双重验证保证了参数安全,通过 Kaos 抽象层实现了跨平台的执行隔离,通过渐进式工具披露解决了大量工具时的 prompt token 消耗问题。
ToolManager 不是简单的工具列表管理器 --- 它是工具生命周期(注册、发现、披露、执行、注销)、权限决策的前置协调、MCP 动态集成的统一编排中心。理解它的架构,是深入掌握 kimi-code 关键的一步。