Skip to main content

Arbeiten mit Hooks

Verwenden Sie Hooks, um das Verhalten Ihrer Copilot SDK Sitzungen anzupassen.

Wer kann dieses Feature verwenden?

GitHub Copilot SDK ist mit allen Copilot Tarifen verfügbar.

Hinweis

          Copilot SDK ist zurzeit in Technische Preview. Funktionalität und Verfügbarkeit können geändert werden.

Hooks ermöglichen es Ihnen, benutzerdefinierte Logik in jede Phase einer Copilot SDK Sitzung zu integrieren – von dem Moment an, in dem sie gestartet wird, bis hin zu jedem Benutzereingabeaufforderungs- und Toolaufruf bis zum Ende. Sie können Hooks verwenden, um Berechtigungen, Überwachung, Benachrichtigungen und mehr zu implementieren, ohne das Grundlegende Agent-Verhalten zu ändern.

Übersicht

Ein Hook ist ein Callback, den Sie beim Erstellen einer Sitzung nur einmal registrieren. Das SDK ruft sie an einem gut definierten Punkt im Unterhaltungslebenszyklus auf, übergibt kontextbezogene Eingaben und akzeptiert optional die Ausgabe, die das Verhalten der Sitzung ändert. Ein detailliertes Sequenzdiagramm des Sitzungsflusses finden Sie im github/copilot-sdk Repository.

HookWenn es ausgelöst wirdIhre Möglichkeiten
onSessionStartSitzung beginnt (neu oder fortgesetzt)Kontext einfügen, Einstellungen laden
onUserPromptSubmittedDer Benutzer sendet eine Nachricht.Umschreiben von Eingabeaufforderungen, Hinzufügen von Kontext, Filtereingaben
onPreToolUseBevor ein Tool ausgeführt wirdZulassen, Verweigern oder Ändern des Anrufs
onPostToolUseNach der Rückgabe eines ToolsTransformieren von Ergebnissen, Geheimnisse redigieren, auditieren
onSessionEndSitzung endetBereinigen, Aufzeichnen von Metriken
onErrorOccurredEin Fehler wird ausgelöst.Benutzerdefinierte Protokollierung, Wiederholungslogik, Warnungen

Alle Hooks sind optional – registrieren Sie nur die, die Sie benötigen. Indem null (oder die entsprechende Sprache) von einem Hook zurückgegeben wird, wird dem SDK angezeigt, dass es mit dem Standardverhalten fortfahren soll.

Registrieren von Hooks

Übergeben Sie ein hooks Objekt, wenn Sie eine Sitzung erstellen (oder fortsetzen). Jedes beispiel unten folgt diesem Muster.

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

Beispiele in Python, Go und .NET finden Sie im github/copilot-sdk Repository.

Tipp

Jeder Hook-Handler empfängt einen invocation Parameter mit dem sessionId, der nützlich ist, um Protokolle zu korrelieren und den Zustand pro Sitzung zu verwalten.

Berechtigungsverwaltung

Dient onPreToolUse zum Erstellen einer Berechtigungsschicht, die entscheidet, welche Tools der Agent ausführen kann, welche Argumente zulässig sind und ob der Benutzer vor der Ausführung aufgefordert werden soll.

Zulassungsliste für einen sicheren Satz von Tools

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

Beispiele in Python, Go und .NET finden Sie im github/copilot-sdk Repository.

Einschränken des Dateizugriffs auf bestimmte Verzeichnisse

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

Benutzer vor destruktiven Vorgängen fragen

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

Durch Rückgabe von "ask" wird die Entscheidung zur Laufzeit dem Benutzer überlassen – nützlich für destruktive Aktionen, bei denen menschliches Eingreifen erforderlich ist.

Überwachung und Compliance

Kombinieren Sie onPreToolUse, onPostToolUseund die Sitzungslebenszyklus-Hooks, um einen vollständigen Überwachungspfad zu erstellen, der jede Aktion erfasst, die der Agent ausführt.

Strukturiertes Überwachungsprotokoll

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

Ein Beispiel in Python finden Sie im github/copilot-sdk Repository.

Geheime Daten aus Toolergebnissen schwärzen

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

Benachrichtigungen

Hooks werden im Prozess Ihrer Anwendung ausgelöst, sodass Sie alle Nebeneffekte wie Desktopbenachrichtigungen, Sounds, Slack-Nachrichten oder Webhook-Aufrufe auslösen können.

Desktopbenachrichtigung für Sitzungsereignisse

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

Ein Beispiel in Python finden Sie im github/copilot-sdk Repository.

Einen Ton abspielen, wenn ein Tool fertig ist

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

Bei Fehlern auf Slack posten

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

Prompt-Anreicherung

Verwenden Sie onSessionStart und onUserPromptSubmitted, um den Kontext automatisch einzufügen, damit Benutzer sich nicht wiederholen müssen.

Einfügen von Projektmetadaten beim Sitzungsstart

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

Erweitern von Kurzbefehlen in Eingabeaufforderungen

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

Fehlerbehandlung und Wiederherstellung

Der onErrorOccurred Hook gibt Ihnen die Möglichkeit, auf Fehler zu reagieren – sei es, erneut zu versuchen, einen Menschen zu benachrichtigen oder auf ordnungsgemäße Weise herunterzufahren.

Wiederholen von vorübergehenden Modellfehlern

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

Benutzerfreundliche Fehlermeldungen

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

Metriken für Sitzungen

Verfolgen Sie, wie lange Sitzungen ausgeführt werden, wie viele Tools aufgerufen werden und warum Sitzungen enden – nützlich für Dashboards und Kostenüberwachung.

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

Ein Beispiel in Python finden Sie im github/copilot-sdk Repository.

Kombinieren von Haken

Hooks verfassen natürlich. Ein einzelnes hooks Objekt kann Berechtigungen, Überwachung und Benachrichtigungen verarbeiten – jeder Hook übernimmt seinen eigenen Auftrag.

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

Bewährte Methoden

  •         **Halten Sie Haken fest.** Jeder Hook wird in-line ausgeführt – langsame Hooks verzögern die Unterhaltung. Entladen Sie nach Möglichkeit schwere Arbeit (Datenbankschreibvorgänge, HTTP-Aufrufe) in eine Hintergrundwarteschlange.
    
  •         **Kehren Sie zurück `null` , wenn Sie nichts ändern müssen.** Dadurch wird das SDK aufgefordert, mit den Standardwerten fortzufahren und unnötige Objektzuordnungen zu vermeiden.
    
  •         **Seien Sie mit Berechtigungsentscheidungen explizit.** Die Rückgabe von `{ permissionDecision: "allow" }` ist klarer als die von `null`, obwohl beide das Tool zulassen.
    
  •         **Schlucken Sie keine kritischen Fehler.** Es ist in Ordnung, wiederherstellbare Toolfehler zu unterdrücken, aber nicht wiederherstellbare immer zu protokollieren oder darauf hinzuweisen.
    
  •         **Verwenden Sie `additionalContext` statt `modifiedPrompt` nach Möglichkeit.** Durch das Anfügen des Kontexts wird die ursprüngliche Absicht des Benutzers beibehalten, während das Modell weiterhin unterstützt wird.
    
  •         **Bereichsstatus nach Sitzungs-ID.** Wenn Sie Daten pro Sitzung nachverfolgen, schlüsseln Sie diese auf `invocation.sessionId` und bereinigen in `onSessionEnd`.
    

Weiterführende Lektüre

Weitere Informationen finden Sie in der Hooks-Referenz im github/copilot-sdk Repository.