Skip to main content

Hook exécuté avant l’utilisation d’un outil

Le onPreToolUse hook est appelé avant l’exécution d’un outil. Utilisez-le pour :

Dans cet article

  • Approuver ou refuser l’exécution de l’outil
  • Modifier les arguments de l’outil
  • Ajouter un contexte pour l’outil
  • Supprimer la sortie de l’outil de la conversation

Signature du hook

Langages de code 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

ChampCatégorieDescription
timestampNuméroHorodatage Unix lorsque le hook a été déclenché
cwdstringRépertoire de travail actuel
toolNamestringNom de l’outil appelé
toolArgsObjetArguments passés à l’outil

Sortie

Retournez null ou undefined autorisez l’outil à s’exécuter sans aucune modification. Sinon, retournez un objet avec l’un de ces champs :

ChampCatégorieDescription
permissionDecision
"allow"
|
"deny"
|
"ask"
Si l’on doit autoriser l’appel de l’outil
permissionDecisionReasonstringExplication présentée à l’utilisateur (pour refuser/demander)
modifiedArgsObjetArguments modifiés pour passer à l’outil
additionalContextstringContexte supplémentaire injecté dans la conversation
suppressOutputbooléenSi c’est vrai, la sortie de l’outil n’apparaît pas dans la conversation

Décisions d’autorisation

DécisionBehavior
"allow"L’outil s’exécute normalement
"deny"L’outil est bloqué, raison indiquée à l’utilisateur
"ask"L’utilisateur est invité à approuver (mode interactif)

Exemples

Autoriser tous les outils (journalisation uniquement)

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

Bloquer des outils spécifiques

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

Modifier les arguments de l’outil

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

Restreindre l’accès aux fichiers à des répertoires spécifiques

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

Réduire la sortie verbeuse de l’outil

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

Ajouter un contexte basé sur l’outil

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

Bonnes pratiques

  1. Retournez toujours une décision : renvoyer null autorise l’outil, mais utiliser explicitement { permissionDecision: "allow" } est plus clair.

  2. Fournir des raisons de refus utiles : lorsque vous refusez, expliquez pourquoi les utilisateurs comprennent :

    return {
      permissionDecision: "deny",
      permissionDecisionReason: "Shell commands require approval. Please describe what you want to accomplish.",
    };
    
  3. Soyez prudent lors de la modification des arguments - Assurez-vous que les arguments modifiés conservent le schéma attendu pour l’outil.

  4. Tenez compte des performances - Les hooks de pré-outil s’exécutent de manière synchrone avant chaque appel d’outil. Gardez-les rapidement.

  5. Utiliser suppressOutput judicieusement : la suppression de la sortie signifie que le modèle ne verra pas le résultat, ce qui peut affecter la qualité de la conversation.

Voir aussi