Skip to main content

Trabajar con enlaces

Usa hooks para personalizar el comportamiento de las SDK de Copilot sesiones.

¿Quién puede utilizar esta característica?

SDK de GitHub Copilot está disponible con todos los Copilot planes.

Nota:

          SDK de Copilot actualmente está en Versión preliminar técnica. La funcionalidad y la disponibilidad están sujetas a cambios.

Las Hooks permiten conectar lógica personalizada en cada etapa de una SDK de Copilot sesión, desde el momento en que comienza, a través de cada solicitud del usuario y llamada de herramienta, hasta el momento en que finaliza. Puede usar enlaces para implementar permisos, auditorías, notificaciones y mucho más sin modificar el comportamiento del agente principal.

Visión general

Un gancho es un callback que se registra una vez al crear una sesión. El SDK lo invoca en un punto bien definido en el ciclo de vida de la conversación, pasa la entrada contextual y, opcionalmente, acepta la salida que modifica el comportamiento de la sesión. Para obtener un diagrama de secuencia detallado del flujo de sesión, consulte el github/copilot-sdk repositorio.

EnlaceCuando se activaQué puede hacer
onSessionStartComienza la sesión (nueva o reanudada)Insertar contexto, preferencias de carga
onUserPromptSubmittedEl usuario envía un mensajeReescribir avisos, añadir contexto, filtrar entrada
onPreToolUseAntes de que se ejecute una herramientaPermitir, denegar o modificar la llamada
onPostToolUseDespués de que se devuelva una herramientaTransformación de resultados, redacción de secretos, auditoría
onSessionEndFinaliza la sesiónLimpieza y registro de métricas
onErrorOccurredSe genera un errorRegistro personalizado, lógica de reintento, alertas

Todos los hooks son opcionales: registre solo los que necesite. Al retornar null (o el equivalente en el idioma) desde cualquier enlace, se indica al SDK que continúe con el comportamiento predeterminado.

Registro de enlaces

Pase un hooks objeto al crear (o reanudar) una sesión. Cada ejemplo siguiente sigue este patrón.

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

Para obtener ejemplos en Python, Go y .NET, consulte el github/copilot-sdk repositorio.

Sugerencia

Cada controlador de enlace recibe un invocation parámetro que contiene el sessionId, lo cual resulta útil para correlacionar registros y mantener el estado por sesión.

Control de permisos

Use onPreToolUse para crear una capa de permisos que decida qué herramientas puede ejecutar el agente, qué argumentos se permiten y si se debe solicitar al usuario antes de la ejecución.

Permitir una lista segura de herramientas

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

Para obtener ejemplos en Python, Go y .NET, consulte el github/copilot-sdk repositorio.

Restricción del acceso a archivos a directorios específicos

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

Preguntar al usuario antes de las operaciones destructivas

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

Al devolver "ask", se delega la decisión al usuario en tiempo de ejecución, lo cual resulta útil para acciones destructivas en las que se desea la intervención directa del usuario.

Auditoría y cumplimiento

Combine onPreToolUse, onPostToolUsey los enlaces del ciclo de vida de la sesión para crear una pista de auditoría completa que registre todas las acciones que realiza el agente.

Registro de auditoría estructurado

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

Para obtener un ejemplo en Python, consulte el github/copilot-sdk repositorio.

Redactar secretos en los resultados de la herramienta

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

Notificaciones

Los ganchos se activan en el proceso de su aplicación para que pueda desencadenar cualquier efecto secundario, como notificaciones de escritorio, sonidos, mensajes de Slack o llamadas de webhook.

Notificación de escritorio en eventos de sesión

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

Para obtener un ejemplo en Python, consulte el github/copilot-sdk repositorio.

Reproducir un sonido cuando una herramienta finaliza

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

Enviar a Slack sobre errores

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

Enriquecimiento de mensajes

Use onSessionStart y onUserPromptSubmitted para insertar automáticamente el contexto para que los usuarios no tengan que repetirse.

Inserción de metadatos del proyecto en el inicio de sesión

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

Expansión de comandos abreviados en mensajes

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

Control y recuperación de errores

El onErrorOccurred enlace le da la oportunidad de reaccionar a los errores, ya sea que eso significa reintentar, notificar a un humano o cerrar correctamente.

Reintentar errores de modelo transitorio

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

Mensajes de error amigables

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étricas de sesión

Realice un seguimiento de cuánto tiempo las sesiones se ejecutan, cuántas herramientas se invocan y por qué las sesiones finalizan, lo cual es útil para paneles de control y monitoreo de costos.

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

Para obtener un ejemplo en Python, consulte el github/copilot-sdk repositorio.

Combinación de enlaces

Los hooks se integran de manera natural. Un único hooks objeto puede controlar los permisos, la auditoría y las notificaciones; cada enlace realiza su propio trabajo.

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

procedimientos recomendados

  •         **Mantenga los ganchos seguros.** Cada enlace se ejecuta en línea: los enlaces lentos retrasan la conversación. Descargue trabajos pesados (escrituras en bases de datos, llamadas HTTP) a una cola en segundo plano cuando sea posible.
    
  •         **Vuelva `null` cuando no tenga nada que cambiar.** Esto indica al SDK que continúe con los valores predeterminados y evite la asignación de objetos innecesaria.
    
  •         **Sea explícito con las decisiones de permisos.** Devolver `{ permissionDecision: "allow" }` es más claro que devolver `null`, aunque ambos permiten la herramienta.
    
  •         **No trague errores críticos.** Está bien suprimir los errores de herramientas recuperables, pero siempre registre o alerte sobre los que no son recuperables.
    
  •         **Use `additionalContext` en lugar de `modifiedPrompt` cuando sea posible.** Agregar contexto conserva la intención original del usuario mientras sigue guiando el modelo.
    
  •         **Estado de ámbito por identificador de sesión.** Si realiza un seguimiento de los datos por sesión, úselo como clave `invocation.sessionId` y limpie durante `onSessionEnd`.
    

Lectura adicional

Para obtener más información, consulte la referencia de enlaces en el github/copilot-sdk repositorio.