资讯动态

GitHub Copilot SDK 会话 Hooks 完全指南:拦截、定制与扩展 Copilot Agent 生命周期

发布时间:2026/9/15 18:10:01 来源:尧图企业网站定制
GitHub Copilot SDK 会话 Hooks 完全指南拦截、定制与扩展 Copilot Agent 生命周期【免费下载链接】copilot-sdkMulti-platform SDK for integrating GitHub Copilot Agent into apps and services项目地址: https://gitcode.com/GitHub_Trending/co/copilot-sdkHooks钩子是 GitHub Copilot SDK 提供的核心扩展机制允许开发者拦截并定制 Copilot 会话在对话生命周期关键节点上的行为。本文以官方 Hooks 总览文档 为骨架结合 Node.js/TypeScript、Python、Go、.NET、Java、Rust 六种语言的 SDK 源码实现系统讲解九大内置 Hook 的触发时机、输入输出契约、权限决策模型与实战模式帮助你完成工具调用管控、结果转换、上下文注入、错误处理与合规审计等场景的落地实现。Session Hooks 是什么Hooks 允许你在 Copilot 会话对话生命周期的关键节点拦截并定制其行为官方文档将其适用场景归纳为五大类控制工具执行Control tool execution批准、拒绝或修改工具调用转换结果Transform results在工具输出被模型处理之前进行改写注入上下文Add context在会话开始时追加额外信息处理错误Handle errors实现自定义错误处理逻辑审计与日志Audit and log跟踪全部交互以满足合规要求。从 SDK 实现上看Hooks 本质上是一个由会话运行时Copilot CLI 侧驱动的回调分发机制当某个生命周期事件发生时运行时通过 JSON-RPC 把事件载荷分发给 SDK 注册的处理函数处理函数返回的结构化结果再被回传给运行时从而影响后续行为。以 Go SDK 为例go/session.go#L746 中的handleHooksInvoke方法根据hookType如preToolUse、postToolUse、userPromptSubmitted等反序列化输入并分发给对应处理器——这正是所有 Hook 在底层共享的统一调用链。可用 Hooks 一览下表完整列出官方提供的全部会话 Hook 及其触发时机与典型用途Hook触发时机典型用途onPreToolUse工具执行前权限控制、参数校验onPostToolUse工具成功执行后结果转换、日志记录onPostToolUseFailure工具执行失败后注入重试指引、记录失败onUserPromptSubmitted用户发送消息时提示词修改、过滤onUserPromptTransformed运行时完成提示词转换后检视或替换模型侧内容onSessionStart会话开始时注入上下文、配置会话onSessionEnd会话结束时资源清理、统计分析onErrorOccurred错误发生时自定义错误处理onAgentStop顶层 Agent 自然停止时校验完成度或请求追加一轮这九类 Hook 在 SDK 中统一收敛为会话配置对象。以 Go SDK 为例go/types.go#L1052-L1064 中的SessionHooks结构体完整声明了上述全部处理器字段并且额外包含OnPreMCPToolCallMCP 工具调用前置钩子用于控制 MCP 请求的_meta元数据见 nodejs/src/types.ts#L1463-L1482供集成 MCP 服务器的场景使用。快速开始五种语言注册 HooksHooks 的注册方式在各语言 SDK 中保持一致在创建会话时把处理器函数集合作为hooks配置传入。下面是官方 Quick Start 在五种语言中的完整写法。Node.js / TypeScriptimport { CopilotClient } from github/copilot-sdk; const client new CopilotClient(); const session await client.createSession({ hooks: { onPreToolUse: async (input) { console.log(Tool called: ${input.toolName}); // Allow all tools return { permissionDecision: allow }; }, onPostToolUse: async (input) { console.log(Tool result: ${JSON.stringify(input.toolResult)}); return null; // No modifications }, onSessionStart: async (input) { return { additionalContext: User prefers concise answers. }; }, }, });Pythonfrom copilot import CopilotClient from copilot.session import PermissionHandler async def main(): client CopilotClient() await client.start() async def on_pre_tool_use(input_data, invocation): print(fTool called: {input_data[toolName]}) return {permissionDecision: allow} async def on_post_tool_use(input_data, invocation): print(fTool result: {input_data[toolResult]}) return None async def on_session_start(input_data, invocation): return {additionalContext: User prefers concise answers.} session await client.create_session(on_permission_requestPermissionHandler.approve_all, hooks{ on_pre_tool_use: on_pre_tool_use, on_post_tool_use: on_post_tool_use, on_session_start: on_session_start, })注意 Python SDK 中 Hook 名称采用下划线风格on_pre_tool_use且演示代码通过on_permission_requestPermissionHandler.approve_all显式放行所有权限请求——这是因为 Hooks 只负责在工具调用前后介入而权限确认请求本身由独立的权限处理器负责详见下文权限决策小节。Gopackage main import ( context fmt copilot github.com/github/copilot-sdk/go ) func main() { client : copilot.NewClient(nil) session, _ : client.CreateSession(context.Background(), copilot.SessionConfig{ Hooks: copilot.SessionHooks{ OnPreToolUse: func(input copilot.PreToolUseHookInput, inv copilot.HookInvocation) (*copilot.PreToolUseHookOutput, error) { fmt.Printf(Tool called: %s\n, input.ToolName) return copilot.PreToolUseHookOutput{ PermissionDecision: allow, }, nil }, OnPostToolUse: func(input copilot.PostToolUseHookInput, inv copilot.HookInvocation) (*copilot.PostToolUseHookOutput, error) { fmt.Printf(Tool result: %v\n, input.ToolResult) return nil, nil }, OnSessionStart: func(input copilot.SessionStartHookInput, inv copilot.HookInvocation) (*copilot.SessionStartHookOutput, error) { return copilot.SessionStartHookOutput{ AdditionalContext: User prefers concise answers., }, nil }, }, }) _ session }.NETusing GitHub.Copilot; var client new CopilotClient(); var session await client.CreateSessionAsync(new SessionConfig { Hooks new SessionHooks { OnPreToolUse (input, invocation) { Console.WriteLine($Tool called: {input.ToolName}); return Task.FromResultPreToolUseHookOutput?( new PreToolUseHookOutput { PermissionDecision allow } ); }, OnPostToolUse (input, invocation) { Console.WriteLine($Tool result: {input.ToolResult}); return Task.FromResultPostToolUseHookOutput?(null); }, OnSessionStart (input, invocation) { return Task.FromResultSessionStartHookOutput?( new SessionStartHookOutput { AdditionalContext User prefers concise answers. } ); }, }, });Javaimport com.github.copilot.*; import com.github.copilot.rpc.*; import java.util.concurrent.CompletableFuture; try (var client new CopilotClient()) { client.start().get(); var hooks new SessionHooks() .setOnPreToolUse((input, invocation) - { System.out.println(Tool called: input.getToolName()); return CompletableFuture.completedFuture(PreToolUseHookOutput.allow()); }) .setOnPostToolUse((input, invocation) - { System.out.println(Tool result: input.getToolResult()); return CompletableFuture.completedFuture(null); }) .setOnSessionStart((input, invocation) - { return CompletableFuture.completedFuture( new SessionStartHookOutput(User prefers concise answers., null) ); }); var session client.createSession( new SessionConfig() .setHooks(hooks) .setOnPermissionRequest(PermissionHandler.APPROVE_ALL) ).get(); }此外Rust SDK 的用法也值得参考它要求实现SessionHookstrait例如impl SessionHooks for MyHooks并覆写on_user_prompt_transformed等异步方法再通过SessionConfig::default().with_hooks(Arc::new(MyHooks))挂载到会话见 docs/hooks/user-prompt-transformed.md。Hook 调用上下文Invocation Context每个 Hook 处理器都会收到一个invocation参数携带当前会话的上下文信息字段类型说明sessionIdstring当前会话的 ID在 Go SDK 中该上下文被建模为HookInvocation结构体位于 go/types.go#L1047-L1050其唯一字段就是SessionID。会话 SDK 在每次分发 Hook 时都会构造它——go/session.go#L753-L755 中handleHooksInvoke方法创建HookInvocation{SessionID: s.SessionID}再连同反序列化后的输入一起传给处理器。这个sessionId是 Hook 之间共享状态、实现会话级逻辑的关键桥梁例如在onSessionStart里记录开始时间并以sessionId为键存入 Map再在onSessionEnd里读取并计算会话时长见 docs/hooks/session-lifecycle.md 的指标统计示例。另外值得注意在 Node.js SDK 中Hook 输入本身BaseHookInput也带有sessionId、timestamp、workingDirectory字段nodejs/src/types.ts#L1424-L1431。对子 Agentsub-agent触发的 Hook 而言输入中的sessionId与invocation.sessionId可能不同——前者是触发事件的运行时子会话 ID后者是注册 Hook 时的顶层会话 ID。深入解析各 Hook下面结合各 Hook 的详细文档与源码契约逐一展开其输入、输出与适用场景。onPreToolUse工具执行前的权限与参数管控onPreToolUse在工具执行之前被调用官方定位的用途包括批准/拒绝工具执行、修改工具参数、为工具追加上下文、以及抑制工具输出进入对话。输入字段docs/hooks/pre-tool-use.md字段类型说明timestampnumberHook 触发时的 Unix 时间戳cwdstring当前工作目录toolNamestring被调用的工具名称toolArgsobject传给工具的参数从源码看Go SDK 中PreToolUseHookInput的 JSON 字段名与文档略有映射差异SessionID对应sessionIdWorkingDirectory在线上载荷中使用cwd作为 JSON tag而Timestamp通过自定义MarshalJSON以 Unix 毫秒整数输出go/types.go#L617-L633。这意味着各语言 SDK 内部负责把运行时的协议载荷翻译为各自的强类型结构开发者只需面对文档化的字段语义。输出字段返回null或undefined表示原样放行否则可返回以下任意组合字段类型说明permissionDecisionallow|deny|ask是否允许该工具调用permissionDecisionReasonstring展示给用户的说明deny/ask 时使用modifiedArgsobject传给工具的修改后参数additionalContextstring注入对话的额外上下文suppressOutputboolean为 true 时工具输出不进入对话在 nodejs/src/types.ts#L1444-L1450 中PreToolUseHookOutput的 TypeScript 定义与上表完全一致五个字段均为可选且permissionDecision被枚举约束为allow | deny | ask。权限决策模型决策行为allow工具正常执行deny工具被拦截理由展示给用户ask提示用户批准交互模式需要澄清的是Hooks 的permissionDecision与 SDK 的权限请求/批准机制如 Python 的PermissionHandler.approve_all、Go 的copilot.PermissionHandler.ApproveAll是两套独立通道。前者是会话生命周期钩子对每一次工具调用的策略裁决后者处理的是运行时发起的交互式权限确认请求。官方文档明确指出如果你自定义的工具本身输入已被应用约束、无需逐次提示可以直接在工具定义上设置skipPermission: true而需要逐次策略检查或参数校验时才使用onPreToolUse。const getWeather defineTool(get_weather, { description: Get weather for a location., parameters: { type: object, properties: { location: { type: string } }, required: [location], }, skipPermission: true, handler: async ({ location }) ({ forecast: Sunny in ${location} }), });实战限制文件访问目录const ALLOWED_DIRECTORIES [/home/user/projects, /tmp]; const session await client.createSession({ hooks: { onPreToolUse: async (input) { if (input.toolName read_file || input.toolName write_file) { const args input.toolArgs as { path: string }; const isAllowed ALLOWED_DIRECTORIES.some(dir args.path.startsWith(dir) ); if (!isAllowed) { return { permissionDecision: deny, permissionDecisionReason: Access to ${args.path} is not permitted. Allowed directories: ${ALLOWED_DIRECTORIES.join(, )}, }; } } return { permissionDecision: allow }; }, }, });实战修改工具参数注入默认超时const session await client.createSession({ hooks: { onPreToolUse: async (input) { // Add a default timeout to all shell commands if (input.toolName shell input.toolArgs) { const args input.toolArgs as { command: string; timeout?: number }; return { permissionDecision: allow, modifiedArgs: { ...args, timeout: args.timeout ?? 30000, // Default 30s timeout }, }; } return { permissionDecision: allow }; }, }, });该文档还提供了抑制冗长输出suppressOutput适合list_directory、search_files类工具、按工具注入上下文如query_database时提示必须使用参数化查询等更多模式完整示例见 docs/hooks/pre-tool-use.md。onPostToolUse工具结果转换与审计onPostToolUse在工具成功执行之后被调用官方定位的用途包括转换/过滤工具结果、记录执行日志用于审计、基于结果追加上下文、抑制结果进入对话。输入字段docs/hooks/post-tool-use.md字段类型说明timestampSDK 时间戳类型Hook 触发时间workingDirectorystring当前工作目录toolNamestring被调用的工具名toolArgsobject传给工具的参数toolResultobject工具返回的结果输出字段返回null/undefined表示原样透传否则可返回字段类型说明modifiedResultobject替换原始结果的修改后结果additionalContextstring注入对话的额外上下文suppressOutputboolean为 true 时结果不进入对话Go SDK 中的PostToolUseHookInputgo/types.go#L661-L669与PostToolUseHookOutputgo/types.go#L694-L699与上表一一对应ToolResult使用any类型承载任意结构的工具结果。失败变体Failure VariantonPostToolUse只在工具成功执行后触发。要观测失败的调用需注册onPostToolUseFailurePython 为on_post_tool_use_failureGo/.NET 为OnPostToolUseFailureRust 为on_post_tool_use_failure。该处理器收到的载荷为{ sessionId, toolName, toolArgs, error, timestamp, workingDirectory }其中error字段是从工具失败结果中提取的字符串它允许返回{ additionalContext: string }来注入额外指引例如重试提示。Go SDK 中对应类型为PostToolUseFailureHookInputgo/types.go#L704-L717 的注释明确说明CLI 会从工具结果中提取失败消息作为Error字段传递而不是传递完整的结果对象。实战脱敏敏感数据const SENSITIVE_PATTERNS [ /api[_-]?key[\s:][]?[\w-][]?/gi, /password[\s:][]?[\w-][]?/gi, /secret[\s:][]?[\w-][]?/gi, ]; const session await client.createSession({ hooks: { onPostToolUse: async (input) { if (typeof input.toolResult string) { let redacted input.toolResult; for (const pattern of SENSITIVE_PATTERNS) { redacted redacted.replace(pattern, [REDACTED]); } if (redacted ! input.toolResult) { return { modifiedResult: redacted }; } } return null; }, }, });实战截断超长结果const MAX_RESULT_LENGTH 10000; const session await client.createSession({ hooks: { onPostToolUse: async (input) { const resultStr JSON.stringify(input.toolResult); if (resultStr.length MAX_RESULT_LENGTH) { return { modifiedResult: { truncated: true, originalLength: resultStr.length, content: resultStr.substring(0, MAX_RESULT_LENGTH) ..., }, additionalContext: Note: Result was truncated from ${resultStr.length} to ${MAX_RESULT_LENGTH} characters., }; } return null; }, }, });该文档还包含合规审计追踪AuditEntry记录时间戳、sessionId、工具名、参数、结果、成功标志并落库、过滤错误堆栈、基于结果追加调试提示如 shell 退出码非 0 时提示检查依赖是否安装等完整模式见 docs/hooks/post-tool-use.md。onUserPromptSubmitted用户消息入口拦截onUserPromptSubmitted在用户提交消息时触发用于修改/增强提示词、在处理前注入上下文、过滤或校验用户输入、实现提示词模板。输入字段docs/hooks/user-prompt-submitted.mdtimestampUnix 时间戳、cwd工作目录、prompt用户提交的原始提示词。输出字段docs/hooks/user-prompt-submitted.md字段类型说明modifiedPromptstring替换原始提示词的修改后文本additionalContextstring追加到对话的额外上下文suppressOutputboolean为 true 时抑制助手响应输出典型用法包括记录全部用户消息日志模式、把短指令扩写为结构化模板、过滤敏感词或拦截不合规输入。五种语言的记录用户提示词示例完整代码见 docs/hooks/user-prompt-submitted.md。onUserPromptTransformed模型侧最终提示词替换onUserPromptTransformed在运行时为提示词追加生成上下文之后、内容持久化到会话历史或发送给模型之前执行。它与onUserPromptSubmitted的关键区别在于输入中的prompt是经过userPromptSubmitted处理后的用户提示词而transformedPrompt还包含了运行时生成的上下文例如current_datetime这类占位符展开内容。输入字段docs/hooks/user-prompt-transformed.mdsessionId、timestamp、cwd/workingDirectory、prompt经过 userPromptSubmitted 后的内容、transformedPrompt经过运行时转换的模型侧内容。输出不返回值则保持转换后内容不变返回modifiedTransformedPrompt则替换将写入会话历史并发送给模型的内容。该替换结果会作为用户消息内容持久化因此恢复resume的会话会按修改后的内容原样重放。五种语言的调用示例如对transformedPrompt执行脱敏见 docs/hooks/user-prompt-transformed.md。会话生命周期onSessionStart / onSessionEnd / onAgentStoponSessionStart会话开始新建或恢复时触发用于初始化上下文、动态配置会话。其输入字段docs/hooks/session-lifecycle.md包括字段类型说明timestampnumberHook 触发时间cwdstring当前工作目录sourcestartup|resume|new会话的启动方式initialPromptstring | undefined启动时提供的初始提示词输出字段为additionalContext会话开始追加的上下文和modifiedConfig覆盖会话配置。典型模式检测input.source resume时加载上次会话状态并注入会话已恢复上次主题为…的上下文或根据工作目录探测项目类型后注入项目信息完整代码见 docs/hooks/session-lifecycle.md。onSessionEnd会话结束时触发用于资源清理、指标统计、状态保存。输入字段docs/hooks/session-lifecycle.md为timestamp、cwd、reason、finalMessage、error其中结束原因reason的取值如下原因说明complete会话正常完成error会话因错误结束abort会话被用户或代码中止timeout会话超时user_exit用户显式结束会话输出字段为suppressOutput抑制最终输出、cleanupActions要执行的清理动作列表、sessionSummary用于日志/分析的会话摘要。官方建议不要假设会话总是干净结束要覆盖错误与中止等所有结束原因且清理逻辑应具备幂等性——因为进程崩溃时onSessionEnd可能根本不会被调用。onAgentStoponAgentStop在顶层 Agent 自然结束一轮对话时运行它与onSessionEnd是两回事此时会话仍然活跃Hook 可以请求追加一轮 Agent 交互。各语言的处理器名称为onAgentStopNode.js/Python/Rust、OnAgentStopGo/.NET、setOnAgentStopJava。其输入成员按语言命名约定分别为stopReason/StopReason/stop_reason/getStopReason()停止原因如end_turn、transcriptPath磁盘会话记录路径、stopHookActive是否已因本 Hook 强制续轮。返回空表示放行 Agent 停止返回 block 决策则入队一条新用户消息并继续例如{ decision: block, reason: Run the final validation and fix any failures. }官方特别提示应使用stopHookActive成员避免对已经因本 Hook 续轮的 Agent 反复阻塞且运行时也会对连续 block 决策设置上限docs/hooks/session-lifecycle.md。onErrorOccurred错误处理与恢复策略onErrorOccurred在会话执行过程中出错时触发用于自定义错误日志、追踪错误模式、向用户呈现友好错误消息、对关键错误告警。输入字段docs/hooks/error-handling.md字段类型说明timestampnumber错误发生时间cwdstring当前工作目录errorstring错误消息errorContextstring错误发生位置model_call、tool_execution、system、user_inputrecoverableboolean错误是否可能被恢复输出字段docs/hooks/error-handling.md字段类型说明suppressOutputboolean为 true 时不向用户展示错误输出errorHandlingstring处理方式retry、skip或abortretryCountnumber当errorHandling为retry时的重试次数userNotificationstring展示给用户的自定义消息值得注意的平台差异Java SDK 不提供onErrorOccurredHook官方文档建议改用EventErrorPolicy与EventErrorHandler组合——例如session.setEventErrorPolicy(EventErrorPolicy.SUPPRESS_AND_LOG_ERRORS)配合session.setEventErrorHandler((event, ex) - ...)实现等效的抑制并记录错误能力docs/hooks/error-handling.md。五种语言的错误日志示例完整代码见 docs/hooks/error-handling.md。常见模式Common Patterns官方文档归纳了三个高频组合模式下面逐一展开TypeScript 示例。模式一记录全部工具调用在onPreToolUse与onPostToolUse中成对输出带时间戳的调用与结果日志const session await client.createSession({ hooks: { onPreToolUse: async (input) { console.log([${new Date().toISOString()}] Tool: ${input.toolName}, Args: ${JSON.stringify(input.toolArgs)}); return { permissionDecision: allow }; }, onPostToolUse: async (input) { console.log([${new Date().toISOString()}] Result: ${JSON.stringify(input.toolResult)}); return null; }, }, });模式二拦截危险工具维护一个黑名单命中即deny并附带清晰的拒绝理由const BLOCKED_TOOLS [shell, bash, exec]; const session await client.createSession({ hooks: { onPreToolUse: async (input) { if (BLOCKED_TOOLS.includes(input.toolName)) { return { permissionDecision: deny, permissionDecisionReason: Shell access is not permitted, }; } return { permissionDecision: allow }; }, }, });模式三注入用户上下文在会话开始时异步加载用户偏好转换为additionalContext注入const session await client.createSession({ hooks: { onSessionStart: async () { const userPrefs await loadUserPreferences(); return { additionalContext: User preferences: ${JSON.stringify(userPrefs)}, }; }, }, });最佳实践综合官方文档与 SDK 实现使用 Hooks 时应遵循以下准则始终显式返回决策onPreToolUse返回null虽然默认放行但显式返回{ permissionDecision: allow }更清晰、更易维护拒绝时提供有帮助的理由permissionDecisionReason应解释为什么被拒例如Shell 命令需要审批请描述你想完成的目标谨慎修改参数与结果modifiedArgs必须保持工具期望的 schemamodifiedResult会影响模型对工具输出的解读仅在必要时修改能用additionalContext引导就优先用它保持 Hook 轻快Pre-tool 与 Post-tool Hooks 在每次工具调用前/后同步运行耗时处理应异步化或批量处理onSessionStart期间用户在等待会话就绪同样要快审慎使用suppressOutput抑制输出意味着模型看不到该结果可能影响对话质量日志注意隐私工具结果可能含敏感数据落库前先脱敏清理逻辑幂等onSessionEnd可能因进程崩溃而不触发资源清理要可重复执行善用skipPermission可信的、输入已被应用约束的自有工具可直接skipPermission: true把onPreToolUse留给真正需要逐次策略判断的场景。运行时如何分发 Hook源码视角从 Go SDK 的实现可以清晰看到整个 Hook 机制的运行脉络go/session.go#L731-L796会话创建时通过registerHooks把SessionHooks存入会话对象内部以互斥锁保护运行时Copilot CLI在生命周期事件发生时通过 JSON-RPC 以hookTyperawInput调用handleHooksInvoke该方法根据hookType分发到对应的处理器例如preToolUse反序列化PreToolUseHookInput后调用hooks.OnPreToolUse(input, invocation)若对应处理器未注册则直接返回nil放行语义处理器的返回结构如PreToolUseHookOutput经序列化回传运行时由运行时决定工具的放行、拒绝或参数替换。这套运行时发事件 → SDK 反序列化 → 用户回调 → 结构化返回的协议正是 nodejs/src/types.ts 与 go/types.go 中所有*HookInput/*HookOutput类型存在的意义也保证了跨语言 SDK 的行为一致性。相关文档导航Hooks 总览本文主题Pre-Tool Use Hook控制工具执行权限Post-Tool Use Hook转换工具结果User Prompt Submitted Hook修改用户提示词User Prompt Transformed Hook替换模型侧提示词Session Lifecycle Hooks会话开始与结束Error Handling Hook自定义错误处理调试指南MCP 调试指南快速开始含自定义工具步骤SDK 集成概览【免费下载链接】copilot-sdkMulti-platform SDK for integrating GitHub Copilot Agent into apps and services项目地址: https://gitcode.com/GitHub_Trending/co/copilot-sdk创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价