注意
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 时间戳 |
cwd | string | 当前工作目录 |
toolName | string | 被调用的工具名称 |
toolArgs | object | 传递给工具的参数 |
toolResult | object | 工具返回的结果 |
输出
返回 null 或 undefined 可让结果保持不变。否则,返回包含以下任意字段的对象。
| 字段 | 类型 | 描述 |
|---|---|---|
modifiedResult | object | 用于替代原始结果的修改后结果 |
additionalContext | string | 注入对话的额外上下文 |
suppressOutput | boolean | 若为 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提供提示。 与其修改结果,不如添加上下文帮助模型解释它们。 - 记录时考虑隐私。 工具结果可能包含敏感数据。记录前请先进行脱敏处理。
- 保持钩子执行快速。 后工具钩子同步运行。繁重的处理应异步或批量完成。