工具接口设计
gemini-cli 的 ServerTool 接口
gemini-cli 定义了统一的工具接口 ServerTool。
源码位置:packages/core/src/tools/tool.ts
typescript
interface ServerTool {
// 工具名称
name: string
// 工具定义(JSON Schema)
schema: FunctionDeclaration
// 执行工具
execute(
params: Record<string, unknown>,
signal?: AbortSignal
): Promise<ToolResult>
// 是否需要用户确认(可选)
shouldConfirmExecute?(
params: Record<string, unknown>,
signal?: AbortSignal
): Promise<ToolCallConfirmationDetails | false>
}核心方法
execute
执行工具的核心方法。
typescript
async execute(params: { file_path: string }): Promise<ToolResult> {
try {
const content = await fs.readFile(params.file_path, 'utf-8')
return {
content, // 返回给 LLM 的内容
metadata: { // 可选的元数据
size: content.length
}
}
} catch (error) {
return {
error: `读取失败: ${error.message}`
}
}
}返回值 ToolResult:
content:成功时返回的内容(字符串)error:失败时返回的错误信息metadata:可选的元数据
shouldConfirmExecute
用于危险操作的确认机制。
typescript
async shouldConfirmExecute(params: { command: string }) {
// 只读命令不需要确认
if (params.command.startsWith('ls ') || params.command.startsWith('cat ')) {
return false
}
// 危险命令需要确认
return {
title: '执行 Shell 命令',
description: `即将执行: ${params.command}`,
risk: 'high'
}
}返回 false 表示不需要确认,返回确认详情对象则需要用户确认。
完整工具示例
只读工具(read_file)
typescript
export const readFileTool: ServerTool = {
name: 'read_file',
schema: {
name: 'read_file',
description: '读取指定文件的内容。用于查看代码、配置文件或文档。',
parameters: {
type: 'object',
properties: {
file_path: {
type: 'string',
description: '文件的绝对路径'
},
encoding: {
type: 'string',
description: '文件编码,默认 utf-8',
enum: ['utf-8', 'ascii', 'base64']
}
},
required: ['file_path']
}
},
async execute(params: { file_path: string; encoding?: string }) {
const encoding = params.encoding || 'utf-8'
try {
const content = await fs.readFile(params.file_path, encoding)
return { content }
} catch (error) {
if (error.code === 'ENOENT') {
return { error: `文件不存在: ${params.file_path}` }
}
return { error: `读取失败: ${error.message}` }
}
}
// 只读操作不需要确认
// 不实现 shouldConfirmExecute
}写入工具(write_file)
typescript
export const writeFileTool: ServerTool = {
name: 'write_file',
schema: {
name: 'write_file',
description: '创建或覆盖文件。用于创建新文件或完全重写现有文件。',
parameters: {
type: 'object',
properties: {
file_path: {
type: 'string',
description: '文件的绝对路径'
},
content: {
type: 'string',
description: '要写入的内容'
}
},
required: ['file_path', 'content']
}
},
async execute(params: { file_path: string; content: string }) {
try {
await fs.writeFile(params.file_path, params.content, 'utf-8')
return { content: `文件已写入: ${params.file_path}` }
} catch (error) {
return { error: `写入失败: ${error.message}` }
}
},
// 写入操作需要确认
async shouldConfirmExecute(params: { file_path: string; content: string }) {
return {
title: '写入文件',
description: `将创建/覆盖文件: ${params.file_path}`,
preview: params.content.slice(0, 500), // 预览内容
risk: 'medium'
}
}
}Shell 工具
typescript
export const shellTool: ServerTool = {
name: 'shell',
schema: {
name: 'shell',
description: '执行 shell 命令。用于运行构建、测试、git 等命令。',
parameters: {
type: 'object',
properties: {
command: {
type: 'string',
description: '要执行的命令'
},
cwd: {
type: 'string',
description: '工作目录(可选)'
}
},
required: ['command']
}
},
async execute(params: { command: string; cwd?: string }) {
try {
const { stdout, stderr } = await execAsync(params.command, {
cwd: params.cwd,
timeout: 60000
})
return {
content: stdout || stderr || '命令执行完成(无输出)'
}
} catch (error) {
return {
error: `命令执行失败: ${error.message}\n${error.stderr || ''}`
}
}
},
async shouldConfirmExecute(params: { command: string }) {
const safeCommands = ['ls', 'pwd', 'cat', 'head', 'tail', 'echo', 'which']
const firstWord = params.command.split(' ')[0]
if (safeCommands.includes(firstWord)) {
return false // 安全命令不需要确认
}
return {
title: '执行命令',
description: params.command,
risk: params.command.includes('rm ') ? 'high' : 'medium'
}
}
}设计原则
1. 单一职责
每个工具只做一件事:
read_file只读文件write_file只写文件edit_file只编辑文件
2. 清晰的错误处理
typescript
async execute(params) {
// 参数校验
if (!params.path) {
return { error: '缺少必填参数: path' }
}
try {
// 执行操作
} catch (error) {
// 返回有意义的错误信息
return { error: `操作失败: ${error.message}` }
}
}3. 返回有用的信息
LLM 需要知道操作结果来决定下一步:
typescript
// 好
return { content: '文件已创建: /path/to/file.ts' }
// 不好
return { content: 'ok' }4. 支持取消
通过 AbortSignal 支持取消长时间操作:
typescript
async execute(params, signal?: AbortSignal) {
if (signal?.aborted) {
return { error: '操作已取消' }
}
// 长时间操作中定期检查
for (const item of items) {
if (signal?.aborted) {
return { error: '操作已取消' }
}
await process(item)
}
}工具注册
gemini-cli 在启动时注册所有工具:
typescript
// 简化的工具注册逻辑
const tools: ServerTool[] = [
readFileTool,
writeFileTool,
editFileTool,
shellTool,
grepTool,
globTool,
// ...
]
// 转换为 LLM 需要的格式
const functionDeclarations = tools.map(t => t.schema)小结
ServerTool接口定义了工具的统一结构execute方法执行具体操作shouldConfirmExecute控制是否需要用户确认- 设计工具时注意单一职责、清晰错误、有用的返回信息
下一步
了解了工具接口设计,接下来看 gemini-cli 有哪些内置工具:内置工具分析 →