Skip to content

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() 方法传回用户的决定。