Skip to main content

工具使用后挂钩

onPostToolUse 钩子会在工具成功执行被调用。 使用它可执行以下操作:

在本文中

  • 转换或筛选工具结果
  • 记录工具执行以用于审计
  • 基于结果添加上下文
  • 隐藏对话的结果
          **失败变体** — `onPostToolUse` 仅会在工具成功执行时触发。 若要查看**失败的**工具调用,请注册`onPostToolUseFailure`(在 Python 中为`on_post_tool_use_failure`,在 Go/.NET 中为`OnPostToolUseFailure`,在 Rust 中为`on_post_tool_use_failure`)。 处理程序接收 `{ sessionId, toolName, toolArgs, error, timestamp, workingDirectory }` - `error` 字段是从工具的失败结果中提取的字符串, 并可能返回 `{ additionalContext: string }` 为模型注入额外的指导(例如重试提示)。 有关完整列表,请参阅 [AUTOTITLE](/copilot/how-tos/copilot-sdk/hooks/hooks-overview) 。

挂钩签名

代码语言 navigation

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

输入

领域类型Description
timestampSDK 时间戳类型触发钩子时
workingDirectory字符串当前工作目录
toolName字符串已调用的工具的名称
toolArgs对象传递给工具的参数
toolResult对象工具返回的结果

输出

返回 nullundefined 传递结果不变。 否则,返回包含以下任何字段的对象:

领域类型Description
modifiedResult对象要使用的修改结果,而不是原始结果
additionalContext字符串向对话注入额外上下文
suppressOutputboolean如果为 true,则结果不会显示在对话中

示例

记录所有工具结果

代码语言 navigation

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

对敏感数据进行修订

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

最佳做法

  1. 无需更改时返回 null - 这比返回空对象或相同的结果更有效。

  2. 谨慎修改结果 - 更改结果可能会影响模型对工具输出的解释方式。 仅在必要时进行修改。

  3. 使用 additionalContext 提供提示 - 不要修改结果,而是添加上下文以帮助模型理解这些结果。

  4. 日志记录时考虑隐私 - 工具结果可能包含敏感数据。 在记录前应用编辑。

  5.           **保持钩子快速** - 后置工具钩子会同步运行。 应该以异步或批量的方式进行繁重的处理。
    

另见