Skip to main content

Utilisation de crochets

Utilisez des hooks pour personnaliser le comportement de vos Kit de développement logiciel (SDK) Copilot sessions.

Qui peut utiliser cette fonctionnalité ?

SDK GitHub Copilot est disponible dans tous les forfaits Copilot.

Remarque

          Kit de développement logiciel (SDK) Copilot est actuellement en préversion technique. Les fonctionnalités et la disponibilité sont susceptibles de changer.

Les hooks vous permettent de connecter une logique personnalisée à chaque étape d’une Kit de développement logiciel (SDK) Copilot session, à partir du moment où elle démarre, par le biais de chaque invite utilisateur et appel d’outil, jusqu’au moment où elle se termine. Vous pouvez utiliser des hooks pour implémenter des autorisations, des audits, des notifications et bien plus encore sans modifier le comportement de l’agent principal.

Aperçu

Un hook est un rappel que vous inscrivez une fois lors de la création d’une session. Le Kit de développement logiciel (SDK) l’appelle à un point bien défini dans le cycle de vie de la conversation, transmet une entrée contextuelle et accepte éventuellement la sortie qui modifie le comportement de la session. Pour obtenir un diagramme de séquence détaillé du flux de session, consultez le github/copilot-sdk référentiel.

HookQuand il se déclencheCe que vous pouvez faire
onSessionStartLa session commence (nouvelle ou reprise)Injecter le contexte, les préférences de chargement
onUserPromptSubmittedL’utilisateur envoie un messageRéécrire les invites, ajouter du contexte, filtrer les entrées
onPreToolUseAvant qu'un outil ne s'exécuteAutoriser, refuser ou modifier l’appel
onPostToolUseUne fois qu’un outil est retournéTransformation des résultats, rédiger des secrets, auditer
onSessionEndFin de sessionNettoyer, enregistrer les métriques
onErrorOccurredUne erreur est généréeJournalisation personnalisée, logique de nouvelle tentative, alertes

Tous les crochets sont facultatifs : inscrivez uniquement ceux dont vous avez besoin. Le renvoi null (ou l’équivalent du langage) à partir de n’importe quel hook indique au SDK de continuer avec le comportement par défaut.

Enregistrement de points d'extension

Transmettez un hooks objet lorsque vous créez (ou reprenez) une session. Chaque exemple ci-dessous suit ce modèle.

import { CopilotClient } from "@github/copilot-sdk";

const client = new CopilotClient();
await client.start();

const session = await client.createSession({
    hooks: {
        onSessionStart: async (input, invocation) => { /* ... */ },
        onPreToolUse:   async (input, invocation) => { /* ... */ },
        onPostToolUse:  async (input, invocation) => { /* ... */ },
        // ... add only the hooks you need
    },
    onPermissionRequest: async () => ({ kind: "approved" }),
});

Pour obtenir des exemples dans Python, Go et .NET, consultez le github/copilot-sdk référentiel.

Conseil

Chaque gestionnaire de hooks reçoit un invocation paramètre contenant le sessionId, ce qui est utile pour la corrélation des journaux et le maintien de l'état par session.

Contrôle d’autorisation

Permet onPreToolUse de générer une couche d’autorisation qui détermine les outils que l’agent peut exécuter, quels arguments sont autorisés et si l’utilisateur doit être invité avant l’exécution.

Liste verte d’un ensemble sécurisé d’outils

const READ_ONLY_TOOLS = ["read_file", "glob", "grep", "view"];

const session = await client.createSession({
    hooks: {
        onPreToolUse: async (input) => {
            if (!READ_ONLY_TOOLS.includes(input.toolName)) {
                return {
                    permissionDecision: "deny",
                    permissionDecisionReason:
                        `Only read-only tools are allowed. "${input.toolName}" was blocked.`,
                };
            }
            return { permissionDecision: "allow" };
        },
    },
    onPermissionRequest: async () => ({ kind: "approved" }),
});

Pour obtenir des exemples dans Python, Go et .NET, consultez le github/copilot-sdk référentiel.

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

const ALLOWED_DIRS = ["/home/user/projects", "/tmp"];

const session = await client.createSession({
    hooks: {
        onPreToolUse: async (input) => {
            if (["read_file", "write_file", "edit"].includes(input.toolName)) {
                const filePath = (input.toolArgs as { path: string }).path;
                const allowed = ALLOWED_DIRS.some((dir) => filePath.startsWith(dir));

                if (!allowed) {
                    return {
                        permissionDecision: "deny",
                        permissionDecisionReason:
                            `Access to "${filePath}" is outside the allowed directories.`,
                    };
                }
            }
            return { permissionDecision: "allow" };
        },
    },
    onPermissionRequest: async () => ({ kind: "approved" }),
});

Demander à l’utilisateur avant les opérations destructrices

const DESTRUCTIVE_TOOLS = ["delete_file", "shell", "bash"];

const session = await client.createSession({
    hooks: {
        onPreToolUse: async (input) => {
            if (DESTRUCTIVE_TOOLS.includes(input.toolName)) {
                return { permissionDecision: "ask" };
            }
            return { permissionDecision: "allow" };
        },
    },
    onPermissionRequest: async () => ({ kind: "approved" }),
});

Renvoyer "ask" la décision à l’utilisateur au moment de l’exécution : utile pour les actions destructrices où vous souhaitez un humain dans la boucle.

Audit et conformité

Combinez onPreToolUse, onPostToolUse et les crochets de cycle de vie de session pour générer une piste d’audit exhaustive qui enregistre chaque action effectuée par l’agent.

Journal d’audit structuré

interface AuditEntry {
    timestamp: number;
    sessionId: string;
    event: string;
    toolName?: string;
    toolArgs?: unknown;
    toolResult?: unknown;
    prompt?: string;
}

const auditLog: AuditEntry[] = [];

const session = await client.createSession({
    hooks: {
        onSessionStart: async (input, invocation) => {
            auditLog.push({
                timestamp: input.timestamp,
                sessionId: invocation.sessionId,
                event: "session_start",
            });
            return null;
        },
        onUserPromptSubmitted: async (input, invocation) => {
            auditLog.push({
                timestamp: input.timestamp,
                sessionId: invocation.sessionId,
                event: "user_prompt",
                prompt: input.prompt,
            });
            return null;
        },
        onPreToolUse: async (input, invocation) => {
            auditLog.push({
                timestamp: input.timestamp,
                sessionId: invocation.sessionId,
                event: "tool_call",
                toolName: input.toolName,
                toolArgs: input.toolArgs,
            });
            return { permissionDecision: "allow" };
        },
        onPostToolUse: async (input, invocation) => {
            auditLog.push({
                timestamp: input.timestamp,
                sessionId: invocation.sessionId,
                event: "tool_result",
                toolName: input.toolName,
                toolResult: input.toolResult,
            });
            return null;
        },
        onSessionEnd: async (input, invocation) => {
            auditLog.push({
                timestamp: input.timestamp,
                sessionId: invocation.sessionId,
                event: "session_end",
            });

            // Persist the log — swap this with your own storage backend
            await fs.promises.writeFile(
                `audit-${invocation.sessionId}.json`,
                JSON.stringify(auditLog, null, 2),
            );
            return null;
        },
    },
    onPermissionRequest: async () => ({ kind: "approved" }),
});

Pour obtenir un exemple dans Python, consultez le github/copilot-sdk référentiel.

Réactez les secrets à partir des résultats de l’outil

const SECRET_PATTERNS = [
    /(?:api[_-]?key|token|secret|password)\s*[:=]\s*["']?[\w\-\.]+["']?/gi,
];

const session = await client.createSession({
    hooks: {
        onPostToolUse: async (input) => {
            if (typeof input.toolResult !== "string") return null;

            let redacted = input.toolResult;
            for (const pattern of SECRET_PATTERNS) {
                redacted = redacted.replace(pattern, "[REDACTED]");
            }

            return redacted !== input.toolResult
                ? { modifiedResult: redacted }
                : null;
        },
    },
    onPermissionRequest: async () => ({ kind: "approved" }),
});

Notifications

Les hooks se déclenchent dans le processus de votre application, ce qui vous permet de déclencher n’importe quel effet secondaire tel que les notifications de bureau, les sons, les messages Slack ou les appels webhook.

Notification de bureau sur les événements de session

import notifier from "node-notifier"; // npm install node-notifier

const session = await client.createSession({
    hooks: {
        onSessionEnd: async (input, invocation) => {
            notifier.notify({
                title: "Copilot Session Complete",
                message: `Session ${invocation.sessionId.slice(0, 8)} finished (${input.reason}).`,
            });
            return null;
        },
        onErrorOccurred: async (input) => {
            notifier.notify({
                title: "Copilot Error",
                message: input.error.slice(0, 200),
            });
            return null;
        },
    },
    onPermissionRequest: async () => ({ kind: "approved" }),
});

Pour obtenir un exemple dans Python, consultez le github/copilot-sdk référentiel.

Jouer un son lors de la fin d'un outil

import { exec } from "node:child_process";

const session = await client.createSession({
    hooks: {
        onPostToolUse: async (input) => {
            // macOS: play a system sound after every tool call
            exec("afplay /System/Library/Sounds/Pop.aiff");
            return null;
        },
        onErrorOccurred: async () => {
            exec("afplay /System/Library/Sounds/Basso.aiff");
            return null;
        },
    },
    onPermissionRequest: async () => ({ kind: "approved" }),
});

Publier sur Slack en cas d'erreurs

const SLACK_WEBHOOK_URL = process.env.SLACK_WEBHOOK_URL!;

const session = await client.createSession({
    hooks: {
        onErrorOccurred: async (input, invocation) => {
            if (!input.recoverable) {
                await fetch(SLACK_WEBHOOK_URL, {
                    method: "POST",
                    headers: { "Content-Type": "application/json" },
                    body: JSON.stringify({
                        text: `🚨 Unrecoverable error in session \`${invocation.sessionId.slice(0, 8)}\`:\n\`\`\`${input.error}\`\`\``,
                    }),
                });
            }
            return null;
        },
    },
    onPermissionRequest: async () => ({ kind: "approved" }),
});

Enrichissement à l’invite

Utilisez onSessionStart et onUserPromptSubmitted injectez automatiquement le contexte afin que les utilisateurs n’aient pas à se répéter.

Injecter des métadonnées de projet au début de la session

import * as fs from "node:fs";

const session = await client.createSession({
    hooks: {
        onSessionStart: async (input) => {
            const pkg = JSON.parse(
                await fs.promises.readFile("package.json", "utf-8"),
            );
            return {
                additionalContext: [
                    `Project: ${pkg.name} v${pkg.version}`,
                    `Node: ${process.version}`,
                    `CWD: ${input.cwd}`,
                ].join("\n"),
            };
        },
    },
    onPermissionRequest: async () => ({ kind: "approved" }),
});

Développer des commandes abrégées dans les requêtes

const SHORTCUTS: Record<string, string> = {
    "/fix":      "Find and fix all errors in the current file",
    "/test":     "Write comprehensive unit tests for this code",
    "/explain":  "Explain this code in detail",
    "/refactor": "Refactor this code to improve readability",
};

const session = await client.createSession({
    hooks: {
        onUserPromptSubmitted: async (input) => {
            for (const [shortcut, expansion] of Object.entries(SHORTCUTS)) {
                if (input.prompt.startsWith(shortcut)) {
                    const rest = input.prompt.slice(shortcut.length).trim();
                    return { modifiedPrompt: rest ? `${expansion}: ${rest}` : expansion };
                }
            }
            return null;
        },
    },
    onPermissionRequest: async () => ({ kind: "approved" }),
});

Gestion et récupération des erreurs

Le onErrorOccurred crochet vous donne la possibilité de réagir aux défaillances, qu'il s'agisse de réessayer, d'avertir un humain ou d'arrêter de manière élégante.

Reessayer les erreurs de modèle transitoires

const session = await client.createSession({
    hooks: {
        onErrorOccurred: async (input) => {
            if (input.errorContext === "model_call" && input.recoverable) {
                return {
                    errorHandling: "retry",
                    retryCount: 3,
                    userNotification: "Temporary model issue—retrying…",
                };
            }
            return null;
        },
    },
    onPermissionRequest: async () => ({ kind: "approved" }),
});

Messages d’erreur conviviaux

const FRIENDLY_MESSAGES: Record<string, string> = {
    model_call:      "The AI model is temporarily unavailable. Please try again.",
    tool_execution:  "A tool encountered an error. Check inputs and try again.",
    system:          "A system error occurred. Please try again later.",
};

const session = await client.createSession({
    hooks: {
        onErrorOccurred: async (input) => {
            return {
                userNotification: FRIENDLY_MESSAGES[input.errorContext] ?? input.error,
            };
        },
    },
    onPermissionRequest: async () => ({ kind: "approved" }),
});

Métriques de session

Suivez la durée d’exécution des sessions, le nombre d’outils appelés et la raison pour laquelle les sessions se terminent, utiles pour les tableaux de bord et la surveillance des coûts.

const metrics = new Map<string, { start: number; toolCalls: number; prompts: number }>();

const session = await client.createSession({
    hooks: {
        onSessionStart: async (input, invocation) => {
            metrics.set(invocation.sessionId, {
                start: input.timestamp,
                toolCalls: 0,
                prompts: 0,
            });
            return null;
        },
        onUserPromptSubmitted: async (_input, invocation) => {
            metrics.get(invocation.sessionId)!.prompts++;
            return null;
        },
        onPreToolUse: async (_input, invocation) => {
            metrics.get(invocation.sessionId)!.toolCalls++;
            return { permissionDecision: "allow" };
        },
        onSessionEnd: async (input, invocation) => {
            const m = metrics.get(invocation.sessionId)!;
            const durationSec = (input.timestamp - m.start) / 1000;

            console.log(
                `Session ${invocation.sessionId.slice(0, 8)}: ` +
                `${durationSec.toFixed(1)}s, ${m.prompts} prompts, ` +
                `${m.toolCalls} tool calls, ended: ${input.reason}`,
            );

            metrics.delete(invocation.sessionId);
            return null;
        },
    },
    onPermissionRequest: async () => ({ kind: "approved" }),
});

Pour obtenir un exemple dans Python, consultez le github/copilot-sdk référentiel.

Combinaison de crochets

Les hooks se composent naturellement. Un seul hooks objet peut gérer les autorisations, l’audit et les notifications : chaque hook effectue son propre travail.

const session = await client.createSession({
    hooks: {
        onSessionStart: async (input) => {
            console.log(`[audit] session started in ${input.cwd}`);
            return { additionalContext: "Project uses TypeScript and Vitest." };
        },
        onPreToolUse: async (input) => {
            console.log(`[audit] tool requested: ${input.toolName}`);
            if (input.toolName === "shell") {
                return { permissionDecision: "ask" };
            }
            return { permissionDecision: "allow" };
        },
        onPostToolUse: async (input) => {
            console.log(`[audit] tool completed: ${input.toolName}`);
            return null;
        },
        onErrorOccurred: async (input) => {
            console.error(`[alert] ${input.errorContext}: ${input.error}`);
            return null;
        },
        onSessionEnd: async (input, invocation) => {
            console.log(`[audit] session ${invocation.sessionId.slice(0, 8)} ended: ${input.reason}`);
            return null;
        },
    },
    onPermissionRequest: async () => ({ kind: "approved" }),
});

Bonnes pratiques

  •         **Gardez les crochets bien serrés.** Chaque hook s’exécute en ligne, les hooks lents retardent la conversation. Déchargez le travail lourd (écritures de base de données, appels HTTP) dans une file d’attente en arrière-plan si possible.
    
  •         **Retournez `null` quand vous n’avez rien à changer.** Cela indique au Kit de développement logiciel (SDK) de continuer avec les valeurs par défaut et d’éviter l’allocation inutile d’objets.
    
  •         **Soyez explicite avec les décisions d’autorisation.** Le retour `{ permissionDecision: "allow" }` est plus clair que le retour `null`, même si les deux autorisent l’outil.
    
  •         **N’avalez pas les erreurs critiques.** Il est judicieux de supprimer les erreurs d’outil récupérables, mais toujours journaliser ou alerter sur des erreurs irrécupérables.
    
  •         **Utilisez `additionalContext` plutôt `modifiedPrompt` que si possible.** L’ajout de contexte conserve l’intention d’origine de l’utilisateur tout en guidant le modèle.
    
  •         **État du scope par ID de session.** Si vous effectuez le suivi des données par session, utilisez `invocation.sessionId` comme clé et nettoyez dans `onSessionEnd`.
    

Lectures complémentaires

Pour plus d’informations, consultez la référence hooks dans le github/copilot-sdk référentiel.