Skip to main content

Hook exécuté après l’utilisation d’un outil

Le onPostToolUse hook est appelé après qu’un outil s’est exécuté avec succès. Utilisez-le pour :

  • Transformer ou filtrer les résultats de l’outil
  • Exécution de l’outil de journalisation pour l’audit
  • Ajouter un contexte en fonction des résultats
  • Supprimer les résultats de la conversation
          **Variante d’échec** : `onPostToolUse` se déclenche uniquement pour les exécutions réussies des outils. Pour observer les appels d’outils **échoués**, enregistrez `onPostToolUseFailure` (`on_post_tool_use_failure` en Python, `OnPostToolUseFailure` en Go/.NET, `on_post_tool_use_failure` en Rust). Le gestionnaire reçoit `{ sessionId, toolName, toolArgs, error, timestamp, workingDirectory }` ( le `error` champ est une chaîne extraite du résultat d’échec de l’outil) et peut revenir `{ additionalContext: string }` pour injecter des conseils supplémentaires pour le modèle (par exemple, des indicateurs de nouvelle tentative). Consultez [l’AUTOTITLE](/copilot/how-tos/copilot-sdk/hooks/hooks-overview) pour obtenir la liste complète.

Signature de point d'interception

TypeScript
import type {
  PostToolUseHookInput,
  HookInvocation,
  PostToolUseHookOutput,
} from "@github/copilot-sdk";
type PostToolUseHandler = (
  input: PostToolUseHookInput,
  invocation: HookInvocation,
) => Promise<PostToolUseHookOutput | null | undefined>;
type PostToolUseHandler = (
  input: PostToolUseHookInput,
  invocation: HookInvocation,
) => Promise<PostToolUseHookOutput | null | undefined>;
Python
from copilot.session import PostToolUseHookInput, PostToolUseHookOutput
from typing import Callable, Awaitable

PostToolUseHandler = Callable[
    [PostToolUseHookInput, dict[str, str]],
    Awaitable[PostToolUseHookOutput | None]
]
PostToolUseHandler = Callable[
    [PostToolUseHookInput, dict[str, str]],
    Awaitable[PostToolUseHookOutput | None]
]
Go
package main

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

type PostToolUseHandler func(
    input copilot.PostToolUseHookInput,
    invocation copilot.HookInvocation,
) (*copilot.PostToolUseHookOutput, error)

func main() {}
type PostToolUseHandler func(
    input PostToolUseHookInput,
    invocation HookInvocation,
) (*PostToolUseHookOutput, error)
.NET
using GitHub.Copilot;

public delegate Task<PostToolUseHookOutput?> PostToolUseHandler(
    PostToolUseHookInput input,
    HookInvocation invocation);
public delegate Task<PostToolUseHookOutput?> PostToolUseHandler(
    PostToolUseHookInput input,
    HookInvocation invocation);
Java
import com.github.copilot.sdk.json.*;

PostToolUseHandler postToolUseHandler;

Input

ChampCatégorieDescription
timestampType d’horodatage du Kit de développement logiciel (SDK)Lorsque le crochet a été déclenché
workingDirectorystringRépertoire de travail actuel
toolNamestringNom de l’outil appelé
toolArgsObjetArguments passés à l’outil
toolResultObjetRésultat retourné par l’outil

Sortie

Retournez null ou undefined pour laisser le résultat inchangé. Sinon, retournez un objet avec l’un de ces champs :

ChampCatégorieDescription
modifiedResultObjetRésultat modifié à utiliser au lieu d’origine
additionalContextstringContexte supplémentaire injecté dans la conversation
suppressOutputbooléenSi la valeur est true, le résultat n’apparaît pas dans la conversation

Exemples

Journaliser tous les résultats de l’outil

TypeScript
const session = await client.createSession({
  hooks: {
    onPostToolUse: async (input, invocation) => {
      console.log(`[${invocation.sessionId}] Tool: ${input.toolName}`);
      console.log(`  Args: ${JSON.stringify(input.toolArgs)}`);
      console.log(`  Result: ${JSON.stringify(input.toolResult)}`);
      return null; // Pass through unchanged
    },
  },
});
Python
from copilot.session import PermissionHandler

async def on_post_tool_use(input_data, invocation):
    print(f"[{invocation['session_id']}] Tool: {input_data['toolName']}")
    print(f"  Args: {input_data['toolArgs']}")
    print(f"  Result: {input_data['toolResult']}")
    return None  # Pass through unchanged

session = await client.create_session(on_permission_request=PermissionHandler.approve_all, hooks={"on_post_tool_use": on_post_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{
            OnPostToolUse: func(input copilot.PostToolUseHookInput, inv copilot.HookInvocation) (*copilot.PostToolUseHookOutput, error) {
                fmt.Printf("[%s] Tool: %s\n", inv.SessionID, input.ToolName)
                fmt.Printf("  Args: %v\n", input.ToolArgs)
                fmt.Printf("  Result: %v\n", input.ToolResult)
                return nil, nil
            },
        },
    })
    _ = session
}
session, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{
    Hooks: &copilot.SessionHooks{
        OnPostToolUse: func(input copilot.PostToolUseHookInput, inv copilot.HookInvocation) (*copilot.PostToolUseHookOutput, error) {
            fmt.Printf("[%s] Tool: %s\n", inv.SessionID, input.ToolName)
            fmt.Printf("  Args: %v\n", input.ToolArgs)
            fmt.Printf("  Result: %v\n", input.ToolResult)
            return nil, nil
        },
    },
})
.NET
using GitHub.Copilot;

public static class PostToolUseExample
{
    public static async Task Main()
    {
        await using var client = new CopilotClient();
        var session = await client.CreateSessionAsync(new SessionConfig
        {
            Hooks = new SessionHooks
            {
                OnPostToolUse = (input, invocation) =>
                {
                    Console.WriteLine($"[{invocation.SessionId}] Tool: {input.ToolName}");
                    Console.WriteLine($"  Args: {input.ToolArgs}");
                    Console.WriteLine($"  Result: {input.ToolResult}");
                    return Task.FromResult<PostToolUseHookOutput?>(null);
                },
            },
        });
    }
}
var session = await client.CreateSessionAsync(new SessionConfig
{
    Hooks = new SessionHooks
    {
        OnPostToolUse = (input, invocation) =>
        {
            Console.WriteLine($"[{invocation.SessionId}] Tool: {input.ToolName}");
            Console.WriteLine($"  Args: {input.ToolArgs}");
            Console.WriteLine($"  Result: {input.ToolResult}");
            return Task.FromResult<PostToolUseHookOutput?>(null);
        },
    },
});
Java
import com.github.copilot.sdk.*;
import com.github.copilot.sdk.json.*;
import java.util.concurrent.CompletableFuture;

var hooks = new SessionHooks()
    .setOnPostToolUse((input, invocation) -> {
        System.out.println("[" + invocation.getSessionId() + "] Tool: " + input.getToolName());
        System.out.println("  Args: " + input.getToolArgs());
        System.out.println("  Result: " + input.getToolResult());
        return CompletableFuture.completedFuture(null);
    });

var session = client.createSession(
    new SessionConfig()
        .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
        .setHooks(hooks)
).get();

Réactez les données sensibles

const SENSITIVE_PATTERNS = [
  /api[_-]?key["\s:=]+["']?[\w-]+["']?/gi,
  /password["\s:=]+["']?[\w-]+["']?/gi,
  /secret["\s:=]+["']?[\w-]+["']?/gi,
];

const session = await client.createSession({
  hooks: {
    onPostToolUse: async (input) => {
      if (typeof input.toolResult === "string") {
        let redacted = input.toolResult;
        for (const pattern of SENSITIVE_PATTERNS) {
          redacted = redacted.replace(pattern, "[REDACTED]");
        }

        if (redacted !== input.toolResult) {
          return { modifiedResult: redacted };
        }
      }
      return null;
    },
  },
});

Tronquer les résultats importants

const MAX_RESULT_LENGTH = 10000;

const session = await client.createSession({
  hooks: {
    onPostToolUse: async (input) => {
      const resultStr = JSON.stringify(input.toolResult);

      if (resultStr.length > MAX_RESULT_LENGTH) {
        return {
          modifiedResult: {
            truncated: true,
            originalLength: resultStr.length,
            content: resultStr.substring(0, MAX_RESULT_LENGTH) + "...",
          },
          additionalContext: `Note: Result was truncated from ${resultStr.length} to ${MAX_RESULT_LENGTH} characters.`,
        };
      }
      return null;
    },
  },
});

Ajouter un contexte en fonction des résultats

const session = await client.createSession({
  hooks: {
    onPostToolUse: async (input) => {
      // If a file read returned an error, add helpful context
      if (input.toolName === "read_file" && input.toolResult?.error) {
        return {
          additionalContext:
            "Tip: If the file doesn't exist, consider creating it or checking the path.",
        };
      }

      // If shell command failed, add debugging hint
      if (input.toolName === "shell" && input.toolResult?.exitCode !== 0) {
        return {
          additionalContext:
            "The command failed. Check if required dependencies are installed.",
        };
      }

      return null;
    },
  },
});

Filtrer les traces de pile d’erreurs

const session = await client.createSession({
  hooks: {
    onPostToolUse: async (input) => {
      if (input.toolResult?.error && input.toolResult?.stack) {
        // Remove internal stack trace details
        return {
          modifiedResult: {
            error: input.toolResult.error,
            // Keep only first 3 lines of stack
            stack: input.toolResult.stack.split("\n").slice(0, 3).join("\n"),
          },
        };
      }
      return null;
    },
  },
});

Piste d’audit pour la conformité

interface AuditEntry {
  timestamp: Date;
  sessionId: string;
  toolName: string;
  args: unknown;
  result: unknown;
  success: boolean;
}

const auditLog: AuditEntry[] = [];

const session = await client.createSession({
  hooks: {
    onPostToolUse: async (input, invocation) => {
      auditLog.push({
        timestamp: input.timestamp,
        sessionId: invocation.sessionId,
        toolName: input.toolName,
        args: input.toolArgs,
        result: input.toolResult,
        success: !input.toolResult?.error,
      });

      // Optionally persist to database/file
      await saveAuditLog(auditLog);

      return null;
    },
  },
});

Supprimer les résultats bruyants

const NOISY_TOOLS = ["list_directory", "search_codebase"];

const session = await client.createSession({
  hooks: {
    onPostToolUse: async (input) => {
      if (NOISY_TOOLS.includes(input.toolName)) {
        // Summarize instead of showing full result
        const items = Array.isArray(input.toolResult)
          ? input.toolResult
          : input.toolResult?.items || [];

        return {
          modifiedResult: {
            summary: `Found ${items.length} items`,
            firstFew: items.slice(0, 5),
          },
        };
      }
      return null;
    },
  },
});

Bonnes pratiques

  1. Retourne null quand aucune modification n’est nécessaire : cela est plus efficace que de renvoyer un objet vide ou le même résultat.

  2. Soyez prudent avec la modification des résultats : la modification des résultats peut affecter la façon dont le modèle interprète la sortie de l’outil. Modifiez uniquement si nécessaire.

  3. Utiliser additionalContext pour les indicateurs : au lieu de modifier les résultats, ajoutez un contexte pour aider le modèle à les interpréter.

  4. Prenez en compte la confidentialité lors de la journalisation : les résultats de l’outil peuvent contenir des données sensibles. Appliquez la rédaction avant la journalisation.

  5. Gardez les hooks rapides - Les hooks après l’outil s’exécutent de manière synchrone. Le traitement lourd doit être effectué de manière asynchrone ou par lot.

Voir aussi