- ツールの実行を承認または拒否する
- ツールの引数を変更する
- ツールのコンテキストを追加する
- 会話からのツール出力を抑制する
フック署名
コード言語 navigation
TypeScript
type PreToolUseHandler = (
input: PreToolUseHookInput,
invocation: HookInvocation
) => Promise<PreToolUseHookOutput | null | undefined>;
Python
PreToolUseHandler = Callable[
[PreToolUseHookInput, dict[str, str]],
Awaitable[PreToolUseHookOutput | None]
]
Go
type PreToolUseHandler func(
input PreToolUseHookInput,
invocation HookInvocation,
) (*PreToolUseHookOutput, error)
.NET
public delegate Task<PreToolUseHookOutput?> PreToolUseHandler(
PreToolUseHookInput input,
HookInvocation invocation);
Java
@FunctionalInterface
public interface PreToolUseHandler {
CompletableFuture<PreToolUseHookOutput> handle(
PreToolUseHookInput input,
HookInvocation invocation);
}
入力
| フィールド | タイプ | Description |
|---|---|---|
timestamp | 数値 | フックがトリガーされたときの Unix タイムスタンプ |
cwd | 文字列 | 現在の作業ディレクトリ |
toolName | 文字列 | 呼び出されるツールの名前 |
toolArgs | オブジェクト | ツールに渡される引数 |
アウトプット
nullまたはundefinedを返して、変更なしでツールを実行できるようにします。 それ以外の場合は、次のいずれかのフィールドを持つオブジェクトを返します。
| フィールド | タイプ | Description |
|---|---|---|
permission | ||
"allow" | ||
| | | ||
"deny" | ||
| | | ||
"ask" | ||
| ツール呼び出しを許可するかどうか | ||
permission | 文字列 | ユーザーに表示される説明 (deny/ask) |
modifiedArgs | オブジェクト | ツールに渡す引数を変更しました |
additionalContext | 文字列 | 会話に挿入された追加のコンテキスト |
suppressOutput | boolean | true、ツールの出力は会話に表示されません |
アクセス許可の決定
| 決定 | Behavior |
|---|---|
"allow" | ツールが正常に実行される |
"deny" | ツールがブロックされ、その理由がユーザーに表示される |
"ask" | ユーザーに承認を求めるメッセージが表示される (対話型モード) |
信頼できるカスタム ツールのアクセス許可プロンプトをスキップする
プロンプトを表示せずに安全に実行できるカスタム ツールを定義する場合は、ツール定義に 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}` }),
});
例
すべてのツールを許可する (ログ記録のみ)
コード言語 navigation
TypeScript
const session = await client.createSession({
hooks: {
onPreToolUse: async (input, invocation) => {
console.log(`[${invocation.sessionId}] Calling ${input.toolName}`);
console.log(` Args: ${JSON.stringify(input.toolArgs)}`);
return { permissionDecision: "allow" };
},
},
});
Python
from copilot.session import PermissionHandler
async def on_pre_tool_use(input_data, invocation):
print(f"[{invocation['session_id']}] Calling {input_data['toolName']}")
print(f" Args: {input_data['toolArgs']}")
return {"permissionDecision": "allow"}
session = await client.create_session(on_permission_request=PermissionHandler.approve_all, hooks={"on_pre_tool_use": on_pre_tool_use})
Go
session, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{
Hooks: &copilot.SessionHooks{
OnPreToolUse: func(input copilot.PreToolUseHookInput, inv copilot.HookInvocation) (*copilot.PreToolUseHookOutput, error) {
fmt.Printf("[%s] Calling %s\n", inv.SessionID, input.ToolName)
fmt.Printf(" Args: %v\n", input.ToolArgs)
return &copilot.PreToolUseHookOutput{
PermissionDecision: "allow",
}, nil
},
},
})
.NET
var session = await client.CreateSessionAsync(new SessionConfig
{
Hooks = new SessionHooks
{
OnPreToolUse = (input, invocation) =>
{
Console.WriteLine($"[{invocation.SessionId}] Calling {input.ToolName}");
Console.WriteLine($" Args: {input.ToolArgs}");
return Task.FromResult<PreToolUseHookOutput?>(
new PreToolUseHookOutput { PermissionDecision = "allow" }
);
},
},
});
Java
import com.github.copilot.*;
import com.github.copilot.rpc.*;
import java.util.concurrent.CompletableFuture;
var hooks = new SessionHooks()
.setOnPreToolUse((input, invocation) -> {
System.out.println("[" + invocation.getSessionId() + "] Calling " + input.getToolName());
System.out.println(" Args: " + input.getToolArgs());
return CompletableFuture.completedFuture(PreToolUseHookOutput.allow());
});
var session = client.createSession(
new SessionConfig()
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
.setHooks(hooks)
).get();
特定のツールをブロックする
const BLOCKED_TOOLS = ["shell", "bash", "write_file", "delete_file"];
const session = await client.createSession({
hooks: {
onPreToolUse: async (input) => {
if (BLOCKED_TOOLS.includes(input.toolName)) {
return {
permissionDecision: "deny",
permissionDecisionReason: `Tool '${input.toolName}' is not permitted in this environment`,
};
}
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" };
},
},
});
特定のディレクトリへのファイル アクセスを制限する
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 VERBOSE_TOOLS = ["list_directory", "search_files"];
const session = await client.createSession({
hooks: {
onPreToolUse: async (input) => {
return {
permissionDecision: "allow",
suppressOutput: VERBOSE_TOOLS.includes(input.toolName),
};
},
},
});
ツールに基づいてコンテキストを追加する
const session = await client.createSession({
hooks: {
onPreToolUse: async (input) => {
if (input.toolName === "query_database") {
return {
permissionDecision: "allow",
additionalContext: "Remember: This database uses PostgreSQL syntax. Always use parameterized queries.",
};
}
return { permissionDecision: "allow" };
},
},
});
ベスト プラクティス
-
常に決定を返す -
nullを返すことはツールを許可しますが、{ permissionDecision: "allow" }で明示的に行う方が明確です。 -
拒否に役立つ理由を提供する - 拒否する場合は、ユーザーが理解する理由を説明します。
return { permissionDecision: "deny", permissionDecisionReason: "Shell commands require approval. Please describe what you want to accomplish.", }; -
引数の変更には注意してください 。 変更された引数で、ツールに必要なスキーマが維持されていることを確認してください。
-
パフォーマンスを考慮する - ツール前フックは、各ツール呼び出しの前に同期的に実行されます。 高速状態を維持してください。
-
**
suppressOutput慎重に使用**する - 出力を抑制することは、モデルに結果が表示されないことを意味し、会話の品質に影響を与える可能性があります。