跳至主要内容

工具使用后 Hook

使用 onPostToolUse 钩子来转换工具结果、记录工具执行情况,并在 Copilot SDK 中工具运行后添加上下文。

谁可以使用此功能?

GitHub Copilot SDK 在所有 Copilot 计划中均可使用。

注意

Copilot SDK 目前处于公开预览阶段。功能和可用性可能会更改。

在工具执行之后调用 onPostToolUse 钩子。用于

  • 转换或过滤工具结果
  • 记录工具执行以进行审计
  • 根据结果添加上下文
  • 在对话中隐藏结果

钩子签名

import type { PostToolUseHookInput, HookInvocation, PostToolUseHookOutput } from "@github/copilot-sdk";
type PostToolUseHandler = (
  input: PostToolUseHookInput,
  invocation: HookInvocation
) => Promise<PostToolUseHookOutput | null | undefined>;

有关 Python、Go 和 .NET 的钩子签名,请参阅 github/copilot-sdk 仓库。Java 请参阅 github/copilot-sdk-java 仓库

输入

字段类型描述
时间戳number钩子触发时的 Unix 时间戳
cwdstring当前工作目录
toolNamestring被调用的工具名称
toolArgsobject传递给工具的参数
toolResultobject工具返回的结果

输出

返回 nullundefined 可让结果保持不变。否则,返回包含以下任意字段的对象。

字段类型描述
modifiedResultobject用于替代原始结果的修改后结果
additionalContextstring注入对话的额外上下文
suppressOutputboolean若为 true,结果将不会出现在对话中

示例

记录所有工具结果

const session = await client.createSession({
  hooks: {
    onPostToolUse: async (input, invocation) => {
      console.log(
        `[${invocation.sessionId}] `
        + `Tool: ${input.toolName}`
      );
      console.log(
        `  Args: ${JSON.stringify(input.toolArgs)}`
      );
      console.log(
        `  Result: `
        + `${JSON.stringify(input.toolResult)}`
      );
      return null; // Pass through unchanged
    },
  },
});

有关 Python、Go 和 .NET 的示例,请参阅 github/copilot-sdk 仓库。Java 请参阅 github/copilot-sdk-java 仓库

脱敏敏感数据

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;
    },
  },
});

基于结果添加上下文

const session = await client.createSession({
  hooks: {
    onPostToolUse: async (input) => {
      // If a file read returned an error,
      // add helpful context
      if (
        input.toolName === "read_file"
        && input.toolResult?.error
      ) {
        return {
          additionalContext:
            "Tip: If the file doesn't exist, "
            + "consider creating it or "
            + "checking the path.",
        };
      }

      // If shell command failed,
      // add debugging hint
      if (
        input.toolName === "shell"
        && input.toolResult?.exitCode !== 0
      ) {
        return {
          additionalContext:
            "The command failed. Check if "
            + "required dependencies are installed.",
        };
      }

      return null;
    },
  },
});

过滤错误堆栈跟踪

const session = await client.createSession({
  hooks: {
    onPostToolUse: async (input) => {
      if (input.toolResult?.error && input.toolResult?.stack) {
        // Remove internal stack trace details
        return {
          modifiedResult: {
            error: input.toolResult.error,
            // Keep only first 3 lines of stack
            stack: input.toolResult.stack.split("\n").slice(0, 3).join("\n"),
          },
        };
      }
      return null;
    },
  },
});

合规审计轨迹

interface AuditEntry {
  timestamp: number;
  sessionId: string;
  toolName: string;
  args: unknown;
  result: unknown;
  success: boolean;
}

const auditLog: AuditEntry[] = [];

const session = await client.createSession({
  hooks: {
    onPostToolUse: async (input, invocation) => {
      auditLog.push({
        timestamp: input.timestamp,
        sessionId: invocation.sessionId,
        toolName: input.toolName,
        args: input.toolArgs,
        result: input.toolResult,
        success: !input.toolResult?.error,
      });

      // Optionally persist to database/file
      await saveAuditLog(auditLog);

      return null;
    },
  },
});

抑制噪声结果

const NOISY_TOOLS = ["list_directory", "search_codebase"];

const session = await client.createSession({
  hooks: {
    onPostToolUse: async (input) => {
      if (NOISY_TOOLS.includes(input.toolName)) {
        // Summarize instead of showing full result
        const items = Array.isArray(input.toolResult) 
          ? input.toolResult 
          : input.toolResult?.items || [];
        
        return {
          modifiedResult: {
            summary: `Found ${items.length} items`,
            firstFew: items.slice(0, 5),
          },
        };
      }
      return null;
    },
  },
});

最佳实践

  • 当不需要更改时返回 null 这比返回空对象或相同结果更高效。
  • 请谨慎修改结果。 更改结果可能影响模型对工具输出的解释。仅在必要时进行修改。
  • 使用 additionalContext 提供提示。 与其修改结果,不如添加上下文帮助模型解释它们。
  • 记录时考虑隐私。 工具结果可能包含敏感数据。记录前请先进行脱敏处理。
  • 保持钩子执行快速。 后工具钩子同步运行。繁重的处理应异步或批量完成。

延伸阅读

© . This site is unofficial and not affiliated with GitHub, Inc.