Appearance
Copilot SDK Hooks 让你在 Agent 执行的关键节点(会话启动/结束、用户输入、工具调用)注入自定义代码。Hook 可以允许/拒绝/修改操作,返回 null 则保持默认行为。保持 Hook 快速执行,避免阻塞 Agent 工作流。
GitHub Copilot SDK Hooks:在 Agent 关键节点注入自定义逻辑
什么是 Hooks
Hooks 是在 Agent 执行流程的特定节点运行的回调函数,可以:
- 权限控制:拦截并决定是否允许工具调用
- 审计日志:记录 Agent 的每个动作
- 上下文注入:在用户输入后自动补充背景信息
- 结果修改:过滤或处理工具调用结果
- 错误处理:捕获错误并决定如何响应
注册 Hooks
typescript
import { createSession } from '@github/copilot-sdk'
const session = await createSession({
hooks: {
onSessionStart: async (context) => { /* ... */ },
onUserPromptSubmitted: async (context) => { /* ... */ },
onPreToolUse: async (context) => { /* ... */ },
onPostToolUse: async (context) => { /* ... */ },
onError: async (context) => { /* ... */ },
onSessionEnd: async (context) => { /* ... */ },
}
})所有 Hooks 都是可选的,返回 null 时保持默认行为。
Hook 类型详解
onSessionStart — 会话启动
typescript
hooks: {
onSessionStart: async (context) => {
console.log(`会话 ${context.sessionId} 已启动`)
// 记录启动时间、初始化资源
return null // 返回 null 继续默认行为
}
}onUserPromptSubmitted — 用户输入后
适合注入额外上下文,或修改用户输入:
typescript
hooks: {
onUserPromptSubmitted: async (context) => {
// 自动追加系统上下文
return {
additionalContext: `当前时间: ${new Date().toISOString()}, 用户: ${context.userId}`
}
// 返回 null 则不修改
}
}onPreToolUse — 工具调用前(权限控制关键点)
typescript
hooks: {
onPreToolUse: async (context) => {
const { toolName, arguments: args } = context
// 禁止写入生产数据库
if (toolName === 'execute_sql' && args.database === 'production') {
return { action: 'deny', reason: '禁止直接操作生产数据库' }
}
// 需要用户确认的高危操作
if (toolName === 'delete_files') {
return { action: 'ask', message: `确认要删除这些文件吗?${args.paths.join(', ')}` }
}
// 审计日志
auditLog.record({ event: 'tool_called', tool: toolName, args })
return { action: 'allow' } // 或 return null
}
}返回值说明:
{ action: 'allow' }或null:允许执行{ action: 'deny', reason: '...' }:拒绝,向 AI 返回错误信息{ action: 'ask', message: '...' }:暂停,请求用户确认
onPostToolUse — 工具调用后(结果处理)
typescript
hooks: {
onPostToolUse: async (context) => {
const { toolName, result } = context
// 过滤敏感信息
if (toolName === 'read_file' && result.content?.includes('SECRET_KEY')) {
return {
modifiedResult: { ...result, content: '[已过滤敏感内容]' }
}
}
// 记录工具执行耗时
metrics.record({ tool: toolName, duration: context.durationMs })
return null // 不修改结果
}
}onError — 错误处理
typescript
hooks: {
onError: async (context) => {
const { error, toolName } = context
// 记录错误
errorTracker.capture(error, { tool: toolName, sessionId: context.sessionId })
// 返回替代结果,避免 Agent 卡住
if (error.code === 'TIMEOUT') {
return { fallbackResult: '工具执行超时,请稍后重试' }
}
return null // 让 Agent 自己处理错误
}
}onSessionEnd — 会话结束
typescript
hooks: {
onSessionEnd: async (context) => {
// 清理资源、记录会话统计
await cleanup(context.sessionId)
metrics.recordSession({
id: context.sessionId,
duration: context.durationMs,
toolCallCount: context.toolCallCount
})
return null
}
}最佳实践
1. 保持 Hook 快速:Hook 是同步拦截点,耗时的 Hook 会直接阻塞 Agent。如果需要异步操作(如写日志),使用非阻塞方式:
typescript
onPreToolUse: async (context) => {
// 非阻塞日志(不 await)
auditLog.record({ tool: context.toolName }).catch(console.error)
return null
}2. 没有要改的就返回 null:不需要修改行为时,返回 null 比返回 { action: 'allow' } 更清晰。
3. 权限检查优先:onPreToolUse 是最关键的安全点,至少在这里做权限检查。
常见问题
Q: Hook 抛出异常会怎样?
A: Hook 异常会传播到 Agent,可能导致工具调用失败或会话中断。建议在 Hook 内部 try-catch,优雅处理内部错误。
Q: Hook 可以修改用户发送的 Prompt 内容吗?
A: onUserPromptSubmitted 可以注入额外上下文,但直接修改用户原始消息需要谨慎——可能导致行为不可预期,建议只做追加,不做替换。
Q: onPreToolUse 返回 'ask' 后,用户在哪里确认?
A: 应用层需要监听对应事件,向用户展示确认界面,然后通过 session.respondToPermissionRequest() 方法传回用户的决定。