# 工具使用前挂钩

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

<!-- markdownlint-disable GHD046 GHD005 -->

<!-- Suppressed: GHD046 (outdated release terminology), GHD005 (hardcoded data variable) -->

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

## 挂钩签名

<div class="ghd-codetabs">
<div class="ghd-codetab" data-lang="typescript" data-label="TypeScript"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">TypeScript</div>

```typescript
type PreToolUseHandler = (
  input: PreToolUseHookInput,
  invocation: HookInvocation
) => Promise<PreToolUseHookOutput | null | undefined>;
```

</div>

<div class="ghd-codetab" data-lang="python" data-label="Python"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Python</div>

```python
PreToolUseHandler = Callable[
    [PreToolUseHookInput, dict[str, str]],
    Awaitable[PreToolUseHookOutput | None]
]
```

</div>

<div class="ghd-codetab" data-lang="go" data-label="Go"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Go</div>

```golang
type PreToolUseHandler func(
    input PreToolUseHookInput,
    invocation HookInvocation,
) (*PreToolUseHookOutput, error)
```

</div>

<div class="ghd-codetab" data-lang="dotnet" data-label=".NET"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">.NET</div>

```csharp
public delegate Task<PreToolUseHookOutput?> PreToolUseHandler(
    PreToolUseHookInput input,
    HookInvocation invocation);
```

</div>

<div class="ghd-codetab" data-lang="java" data-label="Java"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Java</div>

```java
@FunctionalInterface
public interface PreToolUseHandler {
    CompletableFuture<PreToolUseHookOutput> handle(
        PreToolUseHookInput input,
        HookInvocation invocation);
}
```

</div>

</div>

## 输入

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

## 输出

返回 `null` 或 `undefined` 允许工具执行，无需更改。 否则，返回包含以下任何字段的对象：

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

### 权限决策

| 决策        | Behavior        |
| --------- | --------------- |
| `"allow"` | 工具正常执行          |
| `"deny"`  | 工具被阻止，原因会显示给用户。 |
| `"ask"`   | 系统会提示用户批准（交互模式） |

### 跳过受信任的自定义工具的权限提示

如果你定义了一个无需提示即可安全运行的自定义工具，请在工具定义中设置 `skipPermission: true`。 将其用于受信任的应用自有工具，这些工具的输入已由应用程序加以约束；当需要进行逐次调用的策略检查或参数验证时，请使用 `onPreToolUse`。

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

## 示例

### 允许所有工具（仅限日志记录）

<div class="ghd-codetabs">
<div class="ghd-codetab" data-lang="typescript" data-label="TypeScript"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">TypeScript</div>

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

</div>

<div class="ghd-codetab" data-lang="python" data-label="Python"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Python</div>

```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})
```

</div>

<div class="ghd-codetab" data-lang="go" data-label="Go"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Go</div>

```golang
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
        },
    },
})
```

</div>

<div class="ghd-codetab" data-lang="dotnet" data-label=".NET"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">.NET</div>

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

</div>

<div class="ghd-codetab" data-lang="java" data-label="Java"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Java</div>

<!-- docs-validate: skip -->

```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();
```

</div>

</div>

### 阻止特定工具

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

### 修改工具参数

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

### 限制对特定目录的文件访问

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

### 抑制冗长工具输出

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

### 根据工具添加上下文

```typescript
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. **提供有用的拒绝原因** - 拒绝时，请解释用户理解的原因：

   ```typescript
   return {
     permissionDecision: "deny",
     permissionDecisionReason: "Shell commands require approval. Please describe what you want to accomplish.",
   };
   ```

3. **请谨慎修改参数** - 确保修改后的参数维护工具的预期架构。

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

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

## 另见

* [使用挂钩](/zh/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/hooks)
* [工具使用后挂钩](/zh/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/hooks/post-tool-use)
* [调试指南](/zh/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/troubleshooting/debugging)