Observação
SDK do Copilot está atualmente em versão prévia técnica. A funcionalidade e a disponibilidade estão sujeitas a alterações.
Os ganchos permitem que você conecte a lógica personalizada em cada estágio de uma SDK do Copilot sessão , desde o momento em que ela começa, por meio de cada prompt de usuário e chamada de ferramenta, até o momento em que ela termina. Você pode usar ganchos para implementar permissões, auditoria, notificações e muito mais sem modificar o comportamento do agente principal.
Visão geral
Um gancho é um retorno de chamada que você registra uma vez ao criar uma sessão. O SDK o invoca em um ponto bem definido no ciclo de vida da conversa, passa a entrada contextual e, opcionalmente, aceita a saída que modifica o comportamento da sessão. Para obter um diagrama de sequência detalhado do fluxo de sessão, consulte o github/copilot-sdk repositório.
| Gancho | Quando é acionado | O que você pode fazer |
|---|---|---|
onSessionStart | A sessão começa (nova ou retomada) | Contexto de injeção, preferências de carga |
onUserPromptSubmitted | O usuário envia uma mensagem | Reescrever comandos, adicionar contexto, filtrar entrada |
onPreToolUse | Antes que uma ferramenta seja executada | Permitir, negar ou modificar a chamada |
onPostToolUse | Depois que uma ferramenta é retornada | Transformar resultados, redigir segredos, auditoria |
onSessionEnd | Término da sessão | Limpar, registrar métricas |
onErrorOccurred | Um erro é gerado | Registro personalizado, lógica de repetição, alertas |
Todos os ganchos são opcionais: registre apenas aqueles de que você precisa. Retornar null (ou o equivalente de idioma) de qualquer hook informa ao SDK para continuar com o comportamento padrão.
Registrando ganchos
Passe um hooks objeto ao criar (ou retomar) uma sessão. Cada exemplo abaixo segue esse padrão.
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 obter exemplos em Python, Go e .NET, consulte o github/copilot-sdk repositório.
Dica
Cada manipulador de gancho recebe um invocation parâmetro que contém o sessionId, que é útil para correlacionar logs e manter o estado por sessão.
Controle de permissão
Use onPreToolUse para criar uma camada de permissão que decida quais ferramentas o agente pode executar, quais argumentos são permitidos e se o usuário deve ser solicitado antes da execução.
Lista de permissões de um conjunto seguro de ferramentas
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 obter exemplos em Python, Go e .NET, consulte o github/copilot-sdk repositório.
Restringir o acesso a arquivos a diretórios 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" }),
});
Pergunte ao usuário antes das operações destrutivas
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" }),
});
Retornar "ask" delega a decisão ao usuário em tempo de execução — útil para ações destrutivas nas quais você deseja ter um humano no processo.
Auditoria e conformidade
Combine onPreToolUse, onPostToolUse e os ganchos de ciclo de vida da sessão para criar uma trilha de auditoria completa que registra todas as ações executadas pelo agente.
Log de auditoria estruturado
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 obter um exemplo em Python, consulte o github/copilot-sdk repositório.
Redigir segredos dos resultados da ferramenta
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" }),
});
Notificações
Os ganchos são acionados no processo do seu aplicativo, de modo que você possa disparar qualquer efeito secundário, tais como notificações da área de trabalho, sons, mensagens do Slack ou chamadas de webhook.
Notificação de desktop em eventos de sessão
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 obter um exemplo em Python, consulte o github/copilot-sdk repositório.
Reproduzir um som quando uma ferramenta concluir o trabalho
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" }),
});
Publicar no Slack em caso de erros
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" }),
});
Enriquecimento de prompt
Use onSessionStart e onUserPromptSubmitted injete automaticamente o contexto para que os usuários não precisem se repetir.
Injetar metadados do projeto no início da sessão
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" }),
});
Expandir comandos de abreviatura em prompts
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" }),
});
Tratamento e recuperação de erros
O onErrorOccurred gancho lhe dá a chance de reagir a falhas, quer isso signifique tentar novamente, notificar um humano ou desligar normalmente.
Repetir erros de modelo transitórios
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" }),
});
Mensagens de erro amigáveis
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 sessão
Acompanhe por quanto tempo as sessões são executadas, quantas ferramentas são invocadas e por que as sessões terminam, úteis para dashboards e monitoramento de custos.
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 obter um exemplo em Python, consulte o github/copilot-sdk repositório.
Combinando ganchos
Ganchos compõem naturalmente. Um único hooks objeto pode manipular permissões, auditoria e notificações. Cada gancho faz seu próprio trabalho.
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" }),
});
Práticas recomendadas
-
**Mantenha os ganchos fixos.** Cada gancho é executado em linha — ganchos lentos causam contra-tempos na conversa. Descarregar trabalho pesado (gravações de banco de dados, chamadas HTTP) em uma fila em segundo plano quando possível. -
**Retorne `null` quando você não tiver nada para mudar.** Informa o SDK para prosseguir com as configurações padrão e evita a alocação desnecessária de objetos. -
**Seja explícito com decisões de permissão.** Retornar `{ permissionDecision: "allow" }` é mais claro do que retornar `null`, mesmo que ambos permitam o uso da ferramenta. -
**Não engole erros críticos.** É bom suprimir erros de ferramenta recuperáveis, mas sempre registrar ou alertar sobre erros irrecuperáveis. -
**Use `additionalContext` em vez de `modifiedPrompt` quando possível.** O acréscimo de contexto preserva a intenção original do usuário enquanto ainda orienta o modelo. -
**Estado do escopo por ID da sessão.** Se você acompanhar os dados por sessão, defina como chave `invocation.sessionId` e faça a limpeza em `onSessionEnd`.
Leitura adicional
Para obter mais informações, consulte a Referência de Hooks no repositório github/copilot-sdk.