Overview
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.

| Gancho | Quando é acionado | O que você pode fazer |
|---|---|---|
| Ganchos de ciclo de vida de sessão | A sessão começa (nova ou retomada) | Contexto de injeção, preferências de carga |
| Gancho enviado pelo prompt do usuário | O usuário envia uma mensagem | Reescrever comandos, adicionar contexto, filtrar entrada |
| Gancho para uso antes da ferramenta | Antes que uma ferramenta seja executada | Permitir/negar/modificar a chamada |
| Gancho de uso pós-ferramenta | Depois que uma ferramenta retorna (apenas em caso de sucesso) | Transformar resultados, redigir segredos, auditoria |
| Gancho de uso pós-ferramenta | Depois que uma ferramenta retorna uma falha | Inserir orientações para nova tentativa, registrar falhas |
| Ganchos de ciclo de vida de sessão | Término da sessão | Limpar, registrar métricas |
| Gancho de tratamento de erros | 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: "approve-once" }),
});
from copilot import CopilotClient, PermissionDecisionApproveOnce
client = CopilotClient()
await client.start()
session = await client.create_session(
on_permission_request=lambda req, inv: PermissionDecisionApproveOnce(),
hooks={
"on_session_start": on_session_start,
"on_pre_tool_use": on_pre_tool_use,
"on_post_tool_use": on_post_tool_use,
# ... add only the hooks you need
},
)
package main
import (
"context"
copilot "github.com/github/copilot-sdk/go"
"github.com/github/copilot-sdk/go/rpc"
)
func onSessionStart(input copilot.SessionStartHookInput, inv copilot.HookInvocation) (*copilot.SessionStartHookOutput, error) {
return nil, nil
}
func onPreToolUse(input copilot.PreToolUseHookInput, inv copilot.HookInvocation) (*copilot.PreToolUseHookOutput, error) {
return nil, nil
}
func onPostToolUse(input copilot.PostToolUseHookInput, inv copilot.HookInvocation) (*copilot.PostToolUseHookOutput, error) {
return nil, nil
}
func main() {
ctx := context.Background()
client := copilot.NewClient(nil)
session, err := client.CreateSession(ctx, &copilot.SessionConfig{
Hooks: &copilot.SessionHooks{
OnSessionStart: onSessionStart,
OnPreToolUse: onPreToolUse,
OnPostToolUse: onPostToolUse,
},
OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) {
return &rpc.PermissionDecisionApproveOnce{}, nil
},
})
_ = session
_ = err
}
client := copilot.NewClient(nil)
session, err := client.CreateSession(ctx, &copilot.SessionConfig{
Hooks: &copilot.SessionHooks{
OnSessionStart: onSessionStart,
OnPreToolUse: onPreToolUse,
OnPostToolUse: onPostToolUse,
// ... add only the hooks you need
},
OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) {
return &rpc.PermissionDecisionApproveOnce{}, nil
},
})
using GitHub.Copilot;
using GitHub.Copilot.Rpc;
public static class HooksExample
{
static Task<SessionStartHookOutput?> onSessionStart(SessionStartHookInput input, HookInvocation invocation) =>
Task.FromResult<SessionStartHookOutput?>(null);
static Task<PreToolUseHookOutput?> onPreToolUse(PreToolUseHookInput input, HookInvocation invocation) =>
Task.FromResult<PreToolUseHookOutput?>(null);
static Task<PostToolUseHookOutput?> onPostToolUse(PostToolUseHookInput input, HookInvocation invocation) =>
Task.FromResult<PostToolUseHookOutput?>(null);
public static async Task Main()
{
var client = new CopilotClient();
var session = await client.CreateSessionAsync(new SessionConfig
{
Hooks = new SessionHooks
{
OnSessionStart = onSessionStart,
OnPreToolUse = onPreToolUse,
OnPostToolUse = onPostToolUse,
},
OnPermissionRequest = (req, inv) =>
Task.FromResult(PermissionDecision.ApproveOnce()),
});
}
}
var client = new CopilotClient();
var session = await client.CreateSessionAsync(new SessionConfig
{
Hooks = new SessionHooks
{
OnSessionStart = onSessionStart,
OnPreToolUse = onPreToolUse,
OnPostToolUse = onPostToolUse,
// ... add only the hooks you need
},
OnPermissionRequest = (req, inv) =>
Task.FromResult(PermissionDecision.ApproveOnce()),
});
import com.github.copilot.sdk.CopilotClient;
import com.github.copilot.sdk.events.*;
import com.github.copilot.sdk.json.*;
import java.util.concurrent.CompletableFuture;
try (var client = new CopilotClient()) {
client.start().get();
var hooks = new SessionHooks()
.setOnSessionStart((input, inv) -> CompletableFuture.completedFuture(null))
.setOnPreToolUse((input, inv) -> CompletableFuture.completedFuture(null))
.setOnPostToolUse((input, inv) -> CompletableFuture.completedFuture(null));
// ... add only the hooks you need
var session = client.createSession(
new SessionConfig()
.setHooks(hooks)
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
).get();
}
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.
Caso de uso: 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: "approve-once" }),
});
from copilot import PermissionDecisionApproveOnce
READ_ONLY_TOOLS = ["read_file", "glob", "grep", "view"]
async def on_pre_tool_use(input_data, invocation):
if input_data["toolName"] not in READ_ONLY_TOOLS:
return {
"permissionDecision": "deny",
"permissionDecisionReason":
f'Only read-only tools are allowed. "{input_data["toolName"]}" was blocked.',
}
return {"permissionDecision": "allow"}
session = await client.create_session(
on_permission_request=lambda req, inv: PermissionDecisionApproveOnce(),
hooks={"on_pre_tool_use": on_pre_tool_use},
)
package main
import (
"context"
"fmt"
copilot "github.com/github/copilot-sdk/go"
"github.com/github/copilot-sdk/go/rpc"
)
func main() {
ctx := context.Background()
client := copilot.NewClient(nil)
readOnlyTools := map[string]bool{"read_file": true, "glob": true, "grep": true, "view": true}
session, _ := client.CreateSession(ctx, &copilot.SessionConfig{
Hooks: &copilot.SessionHooks{
OnPreToolUse: func(input copilot.PreToolUseHookInput, inv copilot.HookInvocation) (*copilot.PreToolUseHookOutput, error) {
if !readOnlyTools[input.ToolName] {
return &copilot.PreToolUseHookOutput{
PermissionDecision: "deny",
PermissionDecisionReason: fmt.Sprintf("Only read-only tools are allowed. %q was blocked.", input.ToolName),
}, nil
}
return &copilot.PreToolUseHookOutput{PermissionDecision: "allow"}, nil
},
},
OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) {
return &rpc.PermissionDecisionApproveOnce{}, nil
},
})
_ = session
}
readOnlyTools := map[string]bool{"read_file": true, "glob": true, "grep": true, "view": true}
session, _ := client.CreateSession(ctx, &copilot.SessionConfig{
Hooks: &copilot.SessionHooks{
OnPreToolUse: func(input copilot.PreToolUseHookInput, inv copilot.HookInvocation) (*copilot.PreToolUseHookOutput, error) {
if !readOnlyTools[input.ToolName] {
return &copilot.PreToolUseHookOutput{
PermissionDecision: "deny",
PermissionDecisionReason: fmt.Sprintf("Only read-only tools are allowed. %q was blocked.", input.ToolName),
}, nil
}
return &copilot.PreToolUseHookOutput{PermissionDecision: "allow"}, nil
},
},
})
using GitHub.Copilot;
using GitHub.Copilot.Rpc;
public static class PermissionControlExample
{
public static async Task Main()
{
await using var client = new CopilotClient();
var readOnlyTools = new HashSet<string> { "read_file", "glob", "grep", "view" };
var session = await client.CreateSessionAsync(new SessionConfig
{
Hooks = new SessionHooks
{
OnPreToolUse = (input, invocation) =>
{
if (!readOnlyTools.Contains(input.ToolName))
{
return Task.FromResult<PreToolUseHookOutput?>(new PreToolUseHookOutput
{
PermissionDecision = "deny",
PermissionDecisionReason = $"Only read-only tools are allowed. \"{input.ToolName}\" was blocked.",
});
}
return Task.FromResult<PreToolUseHookOutput?>(
new PreToolUseHookOutput { PermissionDecision = "allow" });
},
},
OnPermissionRequest = (req, inv) =>
Task.FromResult(PermissionDecision.ApproveOnce()),
});
}
}
var readOnlyTools = new HashSet<string> { "read_file", "glob", "grep", "view" };
var session = await client.CreateSessionAsync(new SessionConfig
{
Hooks = new SessionHooks
{
OnPreToolUse = (input, invocation) =>
{
if (!readOnlyTools.Contains(input.ToolName))
{
return Task.FromResult<PreToolUseHookOutput?>(new PreToolUseHookOutput
{
PermissionDecision = "deny",
PermissionDecisionReason = $"Only read-only tools are allowed. \"{input.ToolName}\" was blocked.",
});
}
return Task.FromResult<PreToolUseHookOutput?>(
new PreToolUseHookOutput { PermissionDecision = "allow" });
},
},
});
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import com.github.copilot.sdk.PermissionHandler;
import com.github.copilot.sdk.SessionConfig;
import com.github.copilot.sdk.SessionHooks;
import com.github.copilot.sdk.json.PreToolUseHookOutput;
var readOnlyTools = Set.of("read_file", "glob", "grep", "view");
var hooks = new SessionHooks()
.setOnPreToolUse((input, invocation) -> {
if (!readOnlyTools.contains(input.getToolName())) {
return CompletableFuture.completedFuture(
PreToolUseHookOutput.deny(
"Only read-only tools are allowed. \"" + input.getToolName() + "\" was blocked.")
);
}
return CompletableFuture.completedFuture(PreToolUseHookOutput.allow());
});
var session = client.createSession(
new SessionConfig()
.setHooks(hooks)
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
).get();
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: "approve-once" }),
});
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: "approve-once" }),
});
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.
Caso de uso: 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: Date;
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: "approve-once" }),
});
import json, aiofiles
from copilot import PermissionDecisionApproveOnce
audit_log = []
async def on_session_start(input_data, invocation):
audit_log.append({
"timestamp": input_data["timestamp"].isoformat(),
"session_id": invocation["session_id"],
"event": "session_start",
})
return None
async def on_user_prompt_submitted(input_data, invocation):
audit_log.append({
"timestamp": input_data["timestamp"].isoformat(),
"session_id": invocation["session_id"],
"event": "user_prompt",
"prompt": input_data["prompt"],
})
return None
async def on_pre_tool_use(input_data, invocation):
audit_log.append({
"timestamp": input_data["timestamp"].isoformat(),
"session_id": invocation["session_id"],
"event": "tool_call",
"tool_name": input_data["toolName"],
"tool_args": input_data["toolArgs"],
})
return {"permissionDecision": "allow"}
async def on_post_tool_use(input_data, invocation):
audit_log.append({
"timestamp": input_data["timestamp"].isoformat(),
"session_id": invocation["session_id"],
"event": "tool_result",
"tool_name": input_data["toolName"],
"tool_result": input_data["toolResult"],
})
return None
async def on_session_end(input_data, invocation):
audit_log.append({
"timestamp": input_data["timestamp"].isoformat(),
"session_id": invocation["session_id"],
"event": "session_end",
})
async with aiofiles.open(f"audit-{invocation['session_id']}.json", "w") as f:
await f.write(json.dumps(audit_log, indent=2))
return None
session = await client.create_session(
on_permission_request=lambda req, inv: PermissionDecisionApproveOnce(),
hooks={
"on_session_start": on_session_start,
"on_user_prompt_submitted": on_user_prompt_submitted,
"on_pre_tool_use": on_pre_tool_use,
"on_post_tool_use": on_post_tool_use,
"on_session_end": on_session_end,
},
)
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: "approve-once" }),
});
Caso de uso: notificações e sons
Os ganchos são acionados no processo do aplicativo, para que você possa disparar qualquer efeito colateral: 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: "approve-once" }),
});
import subprocess
from copilot import PermissionDecisionApproveOnce
async def on_session_end(input_data, invocation):
sid = invocation["session_id"][:8]
reason = input_data["reason"]
subprocess.Popen([
"notify-send", "Copilot Session Complete",
f"Session {sid} finished ({reason}).",
])
return None
async def on_error_occurred(input_data, invocation):
subprocess.Popen([
"notify-send", "Copilot Error",
input_data["error"][:200],
])
return None
session = await client.create_session(
on_permission_request=lambda req, inv: PermissionDecisionApproveOnce(),
hooks={
"on_session_end": on_session_end,
"on_error_occurred": on_error_occurred,
},
)
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: "approve-once" }),
});
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: "approve-once" }),
});
Caso de uso: 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
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}`,
`Working directory: ${input.workingDirectory}`,
].join("\n"),
};
},
},
onPermissionRequest: async () => ({ kind: "approve-once" }),
});
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: "approve-once" }),
});
Caso de uso: 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: "approve-once" }),
});
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: "approve-once" }),
});
Caso de uso: 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: Date; 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.getTime() - m.start.getTime()) / 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: "approve-once" }),
});
from copilot import PermissionDecisionApproveOnce
session_metrics = {}
async def on_session_start(input_data, invocation):
session_metrics[invocation["session_id"]] = {
"start": input_data["timestamp"],
"tool_calls": 0,
"prompts": 0,
}
return None
async def on_user_prompt_submitted(input_data, invocation):
session_metrics[invocation["session_id"]]["prompts"] += 1
return None
async def on_pre_tool_use(input_data, invocation):
session_metrics[invocation["session_id"]]["tool_calls"] += 1
return {"permissionDecision": "allow"}
async def on_session_end(input_data, invocation):
m = session_metrics.pop(invocation["session_id"])
duration = (input_data["timestamp"] - m["start"]).total_seconds()
sid = invocation["session_id"][:8]
print(
f"Session {sid}: {duration:.1f}s, {m['prompts']} prompts, "
f"{m['tool_calls']} tool calls, ended: {input_data['reason']}"
)
return None
session = await client.create_session(
on_permission_request=lambda req, inv: PermissionDecisionApproveOnce(),
hooks={
"on_session_start": on_session_start,
"on_user_prompt_submitted": on_user_prompt_submitted,
"on_pre_tool_use": on_pre_tool_use,
"on_session_end": on_session_end,
},
)
Combinando ganchos
Ganchos compõem naturalmente. Um único objeto hooks pode lidar com permissões e auditoria e notificações — cada mecanismo cumpre sua própria função.
const session = await client.createSession({
hooks: {
onSessionStart: async (input) => {
console.log(`[audit] session started in ${input.workingDirectory}`);
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: "approve-once" }),
});
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
nullquando 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 retornarnull, 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
additionalContextem vez demodifiedPromptquando 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.sessionIde faça a limpeza emonSessionEnd.
Referências
Para obter definições de tipo completo, tabelas de campo de entrada/saída e exemplos adicionais para cada gancho, consulte a referência da API:
- Ganchos de sessão
- Gancho para uso antes da ferramenta
- Gancho de uso pós-ferramenta
- Gancho enviado pelo prompt do usuário
- Ganchos de ciclo de vida de sessão
- Gancho de tratamento de erros