Skip to main content

ツール使用前のフック

onPreToolUse フックは、ツールを実行する前に呼び出されます。 これは次の目的で使用されます。

  • ツールの実行を承認または拒否する
  • ツールの引数を変更する
  • ツールのコンテキストを追加する
  • 会話からのツール出力を抑制する

フック署名

コード言語 navigation

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

入力

フィールドタイプDescription
timestamp数値フックがトリガーされたときの Unix タイムスタンプ
cwd文字列現在の作業ディレクトリ
toolName文字列呼び出されるツールの名前
toolArgsオブジェクトツールに渡される引数

アウトプット

nullまたはundefinedを返して、変更なしでツールを実行できるようにします。 それ以外の場合は、次のいずれかのフィールドを持つオブジェクトを返します。

フィールドタイプDescription
permissionDecision
"allow"
|
"deny"
|
"ask"
ツール呼び出しを許可するかどうか
permissionDecisionReason文字列ユーザーに表示される説明 (deny/ask)
modifiedArgsオブジェクトツールに渡す引数を変更しました
additionalContext文字列会話に挿入された追加のコンテキスト
suppressOutputbooleantrue、ツールの出力は会話に表示されません

アクセス許可の決定

決定Behavior
"allow"ツールが正常に実行される
"deny"ツールがブロックされ、その理由がユーザーに表示される
"ask"ユーザーに承認を求めるメッセージが表示される (対話型モード)

すべてのツールを許可する (ログ記録のみ)

コード言語 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" };
    },
  },
});

特定のツールをブロックする

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

ベスト プラクティス

  1. 常に決定を返す - null を返すことはツールを許可しますが、 { permissionDecision: "allow" } で明示的に行う方が明確です。

  2. 拒否に役立つ理由を提供する - 拒否する場合は、ユーザーが理解する理由を説明します。

    return {
      permissionDecision: "deny",
      permissionDecisionReason: "Shell commands require approval. Please describe what you want to accomplish.",
    };
    
  3. 引数の変更には注意してください 。 変更された引数で、ツールに必要なスキーマが維持されていることを確認してください。

  4. パフォーマンスを考慮する - ツール前フックは、各ツール呼び出しの前に同期的に実行されます。 高速状態を維持してください。

  5. ** suppressOutput慎重に使用**する - 出力を抑制することは、モデルに結果が表示されないことを意味し、会話の品質に影響を与える可能性があります。

こちらも参照ください