Skip to main content

Крючок для использования до использования инструмента

onPreToolUse Крюк вызывается до запуска инструмента. Он используется для следующих задач:

  • Одобрение или отказ в выполнении инструмента
  • Изменение аргументов инструментов
  • Добавьте контекст для инструмента
  • Подавление инструмента из разговора

Сигнатура крюка

Языки кода navigation

TypeScript
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"Пользователю предлагается одобрить (интерактивный режим)

Пропуск запросов разрешений для доверенных пользовательских инструментов

Если вы определяете настраиваемое средство, безопасное для запуска без запроса, задайте для 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" };
    },
  },
});

Инструменты, специфичные для блоков

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 разумно — подавление выхода означает, что модель не увидит результат, что может повлиять на качество разговора.

См. также