Skip to main content

工具使用前挂钩

onPreToolUse 工具执行 之前 调用挂钩。 使用它可执行以下操作:

在本文中

  • 批准或拒绝工具执行
  • 修改工具参数
  • 添加工具的上下文
  • 取消对话中的工具输出

挂钩签名

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>;
Python
from copilot.session import PreToolUseHookInput, PreToolUseHookOutput
from typing import Callable, Awaitable

PreToolUseHandler = Callable[
    [PreToolUseHookInput, dict[str, str]],
    Awaitable[PreToolUseHookOutput | None]
]
PreToolUseHandler = Callable[
    [PreToolUseHookInput, dict[str, str]],
    Awaitable[PreToolUseHookOutput | None]
]
Go
package main

import copilot "github.com/github/copilot-sdk/go"

type PreToolUseHandler func(
    input copilot.PreToolUseHookInput,
    invocation copilot.HookInvocation,
) (*copilot.PreToolUseHookOutput, error)

func main() {}
type PreToolUseHandler func(
    input PreToolUseHookInput,
    invocation HookInvocation,
) (*PreToolUseHookOutput, error)
.NET
using GitHub.Copilot;

public delegate Task<PreToolUseHookOutput?> PreToolUseHandler(
    PreToolUseHookInput input,
    HookInvocation invocation);
public delegate Task<PreToolUseHookOutput?> PreToolUseHandler(
    PreToolUseHookInput input,
    HookInvocation invocation);
Java
import com.github.copilot.sdk.json.*;

PreToolUseHandler preToolUseHandler;

输入

领域类型Description
timestampnumber触发挂钩时的 Unix 时间戳
cwd字符串当前工作目录
toolName字符串要调用的工具的名称
toolArgs对象传递给工具的参数

输出

返回 nullundefined 允许工具执行,无需更改。 否则,返回包含以下任何字段的对象:

领域类型Description
permissionDecision
"allow"
|
"deny"
|
"ask"
是否允许工具调用
permissionDecisionReason字符串向用户显示的说明(对于拒绝/询问)
modifiedArgs对象传递给工具的已修改参数
additionalContext字符串注入到会话中的额外上下文
suppressOutputboolean如果为 true,工具输出将不会显示在对话中

权限决策

决策Behavior
"allow"工具正常执行
"deny"工具被阻止,原因会显示给用户。
"ask"系统会提示用户批准(交互模式)

示例

允许所有工具(仅限日志记录)

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
package main

import (
    "context"
    "fmt"
    copilot "github.com/github/copilot-sdk/go"
)

func main() {
    client := copilot.NewClient(nil)
    session, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{
        OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
        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
            },
        },
    })
    _ = session
}
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
using GitHub.Copilot;

public static class PreToolUseExample
{
    public static async Task Main()
    {
        await using var client = new CopilotClient();
        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" }
                    );
                },
            },
        });
    }
}
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.sdk.*;
import com.github.copilot.sdk.json.*;
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" };
    },
  },
});

最佳做法

  1. 始终返回一个判定 - 返回null即可允许该工具,但明确返回{ permissionDecision: "allow" }更清晰。

  2. 提供有用的拒绝原因 - 拒绝时,请解释用户理解的原因:

    return {
      permissionDecision: "deny",
      permissionDecisionReason: "Shell commands require approval. Please describe what you want to accomplish.",
    };
    
  3. 请谨慎修改参数 - 确保修改后的参数维护工具的预期架构。

  4. 考虑性能 - 工具调用前钩子会在每次工具调用之前同步运行。 让他们保持速度。

  5. 谨慎使用 suppressOutput - 抑制输出意味着模型看不到结果,这可能会影响聊天质量。

另见