メモ
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 リポジトリを参照してください。
入力
| フィールド | タイプ | 説明 |
|---|---|---|
timestamp | 数値 | フックがトリガーされたときの Unix タイムスタンプ |
cwd | 文字列 | 現在の作業ディレクトリ |
toolName | 文字列 | 呼び出されたツールの名前 |
toolArgs | オブジェクト | ツールに渡された引数 |
toolResult | オブジェクト | ツールによって返される結果 |
アウトプット
結果を変更せずに渡す null または undefined を返します。 それ以外の場合は、次のいずれかのフィールドを持つオブジェクトを返します。
| フィールド | タイプ | 説明 |
|---|---|---|
modifiedResult | オブジェクト | 元の結果ではなく使用するように変更された結果 |
additionalContext | 文字列 | 会話に挿入された追加のコンテキスト |
suppressOutput | ブーリアン | 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 リポジトリを参照してください。
機密データを編集する
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` を使用します。** 結果を変更する代わりに、モデルが結果を解釈するのに役立つコンテキストを追加します。 -
**ログ記録時のプライバシーを考慮してください。** ツールの結果に機密データが含まれている場合があります。 ログ記録の前に編集を適用します。 -
**フックをしっかりと固定してください。** ツール後フックは同期的に実行されます。 大量の処理は、非同期またはバッチ処理で行う必要があります。
詳細については、次を参照してください。
-
[AUTOTITLE](/copilot/how-tos/copilot-sdk/use-hooks/quickstart) -
[AUTOTITLE](/copilot/how-tos/copilot-sdk/use-hooks/pre-tool-use) -
[AUTOTITLE](/copilot/how-tos/copilot-sdk/use-hooks/error-handling)