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>;

Input

ПолеТипDescription
timestampНомерВременная метка Unix, когда срабатывал крюк
cwdструнаТекущий рабочий справочник
toolNameструнаНазвание используемого инструмента
toolArgsobjectАргументы, переданные средству

Output

Вернуть null или undefined позволить инструменту выполняться без изменений. В противном случае верните объект с любым из следующих полей:

ПолеТипDescription
permissionDecision
"allow"
|
"deny"
|
"ask"
Разрешать ли вызов инструмента
permissionDecisionReasonструнаОбъяснение показано пользователю (для отказа/спроса)
modifiedArgsobjectМодифицированные аргументы для передачи инструменту
additionalContextструнаДополнительный контекст в разговор
suppressOutputbooleanЕсли это правда, то результат инструмента не будет отображаться в разговоре

Решения по разрешению

Решение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. Будьте осторожны с модификацией аргументов — убедитесь, что модифицированные arg сохраняют ожидаемую схему для инструмента.

  4. Учитывайте производительность — пре-инструментальные хуки выполняются синхронно перед каждым вызовом инструмента. Держите их быстрыми.

  5. Используйте suppressOutput разумно — подавление выхода означает, что модель не увидит результат, что может повлиять на качество разговора.

См. также