Appearance
onPostToolUse 在工具执行完成后触发,可以过滤结果中的敏感信息、转换结果格式、向 AI 追加上下文,或完全抑制结果不让 AI 看到。返回 null 保持原始结果,修改时通过 modifiedResult 返回新结果。
GitHub Copilot SDK onPostToolUse Hook:处理和过滤工具执行结果
基本结构
typescript
const session = await createSession({
hooks: {
onPostToolUse: async (context) => {
const { toolName, result, arguments: args, durationMs } = context
// 处理逻辑
return null // 不修改,保持原始结果
}
}
})过滤敏感数据
工具返回结果中可能包含不应让 AI 看到的敏感信息:
typescript
onPostToolUse: async ({ toolName, result }) => {
if (toolName === 'read_file') {
const content = result.content as string
if (!content) return null
// 脱敏:替换 API Key、密码等
const sanitized = content
.replace(/([A-Z_]*(KEY|SECRET|TOKEN|PASSWORD|PASS)[A-Z_]*\s*=\s*)[^\n]*/gi, '$1[REDACTED]')
.replace(/"password"\s*:\s*"[^"]*"/gi, '"password": "[REDACTED]"')
if (sanitized !== content) {
return { modifiedResult: { ...result, content: sanitized } }
}
}
return null
}转换结果格式
把原始结果转换成对 AI 更友好的格式:
typescript
onPostToolUse: async ({ toolName, result }) => {
if (toolName === 'execute_sql') {
const rows = result.rows as object[]
// 把 JSON 数组转换成 Markdown 表格,AI 更容易理解
if (rows?.length > 0) {
const headers = Object.keys(rows[0])
const table = [
`| ${headers.join(' | ')} |`,
`| ${headers.map(() => '---').join(' | ')} |`,
...rows.map(row => `| ${headers.map(h => (row as any)[h]).join(' | ')} |`)
].join('\n')
return { modifiedResult: { ...result, content: table } }
}
}
return null
}追加额外上下文
工具执行后,向 AI 补充相关背景信息:
typescript
onPostToolUse: async ({ toolName, result }) => {
if (toolName === 'read_file' && result.path?.endsWith('.ts')) {
return {
modifiedResult: result,
additionalContext: '注意:这是 TypeScript 文件,遵循项目的严格模式设置,禁止使用 any 类型。'
}
}
return null
}记录执行指标
typescript
onPostToolUse: async ({ toolName, result, durationMs }) => {
// 记录工具执行耗时
metrics.histogram('tool.duration', durationMs, { tool: toolName })
// 记录成功/失败
metrics.increment('tool.calls', 1, {
tool: toolName,
success: result.error ? 'false' : 'true'
})
return null // 不修改结果
}抑制结果(不让 AI 看到)
某些工具的结果太冗长或包含不相关信息,可以完全抑制:
typescript
onPostToolUse: async ({ toolName, result }) => {
if (toolName === 'list_directory') {
// 如果结果超过 50 个条目,只返回摘要
const items = result.items as string[]
if (items?.length > 50) {
return {
modifiedResult: {
...result,
items: items.slice(0, 10),
content: `目录共 ${items.length} 个文件,仅显示前 10 个。使用搜索工具查找具体文件。`
}
}
}
}
return null
}注意事项
修改结果会改变 AI 的判断:AI 基于工具结果决定下一步,过度修改可能导致 AI 做出错误决定。只做必要的脱敏和格式转换。
不要在 Hook 中做耗时操作:onPostToolUse 也会阻塞对话流程,耗时操作应该异步处理。
记录日志时注意敏感信息:先脱敏再记录,避免把工具返回的原始敏感数据写入日志。
常见问题
Q: 修改了结果后,会影响工具调用的缓存吗?
A: 持久化会话的工具结果缓存存储的是修改后的结果(即 modifiedResult)。恢复会话时,AI 看到的也是修改后的版本。
Q: result 的结构是固定的吗?
A: 不是。result 的结构取决于工具实现,不同工具的 result 格式不同。建议用 TypeScript 的类型守卫(type guard)安全访问字段,避免运行时报错。
Q: onPostToolUse 能阻止结果被写入会话历史吗?
A: 可以返回 { suppress: true } 完全阻止工具结果进入会话对话历史,AI 将不会看到这次工具调用的结果。慎用,可能导致 AI 困惑。