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.

| Enlace | Cuando se activa | Qué puede hacer |
|---|---|---|
| Enlaces de ciclo de vida de sesión | Comienza la sesión (nueva o reanudada) | Insertar contexto, preferencias de carga |
| Enlace enviado de la solicitud de usuario | El usuario envía un mensaje | Reescribir avisos, añadir contexto, filtrar entrada |
| Gancho de transformación de la solicitud al usuario | El entorno de ejecución construye el prompt del modelo. | Inspeccionar o sustituir contenido orientado al modelo |
| Gancho previo al uso de la herramienta | Antes de que se ejecute una herramienta | Permitir, denegar o modificar la llamada |
| Enlace de uso posterior a la herramienta | Después de que una herramienta devuelva el resultado (solo en caso de éxito) | Transformación de resultados, redacción de secretos, auditoría |
| Enlace de uso posterior a la herramienta | Después de que una herramienta devuelva un error | Insertar directrices de reintento, registrar fallos |
| Enlaces de ciclo de vida de sesión | Finaliza la sesión | Limpieza y registro de métricas |
| Gancho de manejo de errores | Se genera un error | Registro personalizado, lógica de reintento, alertas |
Todos los Enlaces 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.
Los Enlaces de repositorio basados en archivos y la configuración del hook de repositorio solo se cargan después de que el espacio de trabajo se considere de confianza. Los Enlaces de usuario permanecen disponibles antes de que se conceda la confianza del espacio de trabajo.
Ganchos de repositorio de directorios adicionales
Los Enlaces del sistema de archivos del repositorio son independientes de los callbacks del SDK descritos en esta guía. Para cargarlos desde directorios adicionales durante la creación o reanudación en frío de la sesión local, proporcione additionalDirectories; La CLI --add-dir también participa de forma predeterminada.
DISABLE_ADDITIONAL_DIRECTORY_HOOKS=true restaura el ámbito solo principal; enableFileHooks puede habilitar o deshabilitar Enlaces del repositorio a través de session.rpc.options.update; los directorios de hooks de SDK explícitos y los callbacks son independientes.
La detección usa la raíz de Git que contiene cada directorio aceptado, o el propio directorio cuando está fuera de un repositorio de Git. Por ejemplo, proporcionar B\packages\core dentro del repositorio B admite B\.github\hooks y la configuración de los Enlaces del repositorio de B, no solo la configuración dentro de B\packages\core.
Advertencia
Proporcione solo directorios de arranque de confianza. Esto autoriza comandos hook y prompts proporcionados por Enlaces de todos los directorios de inicio elegibles sin aprobación adicional por directorio o por hook. Las concesiones de acceso a archivos posteriores no autorizan Enlaces. La directiva administrada existente, los controles de deshabilitación de hook y las reglas de espacio aislado todavía se aplican; no hacen que el contenido de hook malintencionado sea seguro.
Las concesiones de directorio posteriores no agregan orígenes de gancho. Reanudar en frío debe volver a proporcionar los directorios; las concesiones posteriores o las actualizaciones de vía de escape no pueden cambiar la autorización conservada.
Registro de enlaces
El callback hooks permanece separado de los hookDirectories basados en archivos. Para conocer las configuraciones de Enlaces empaquetadas y los recursos adyacentes, consulte Ubicaciones para detectar personalizaciones.
Enlaces declarativos con ámbito de agente
Los agentes personalizados pueden declarar un hooks mapa de eventos en .agent.md frontmatter o en la configuración del agente personalizado del SDK. Estos son comandos declarativos o acciones HTTP, no funciones de devolución de llamada del SDK. Solo se ejecutan mientras se selecciona ese agente, además de los Enlaces de sesión, usuario, repositorio, complemento y directiva administrada aplicables. Cambiar o borrar el agente seleccionado cambia los Enlaces con alcance para la siguiente invocación sin cambiar otras sesiones ni subagentes simultáneos.
---
name: formatter
description: Formats changed files
hooks:
PostToolUse:
- type: command
command: ./scripts/format-changed-files.sh
windows: ./scripts/format-changed-files.ps1
---
Format the files you change.
Se aceptan nombres de eventos en tiempo de ejecución, como postToolUse y alias de VS Code admitidos, como PostToolUse .
command proporciona el comando de shell de reserva; windows, linux y osx lo sobrescriben en la plataforma correspondiente. Los comandos hook son relativos a la raíz del repositorio de la sesión en ejecución, a menos que establezcan un directorio de trabajo explícito. Las cargas útiles de los Enlaces y las decisiones siguen los contratos existentes de los Enlaces del entorno de ejecución.
Cuando un agente local se ejecuta como un subagente, sus acciones de ámbito Stop (agentStop) se ejecutan en SubagentStop en lugar de una parada de turno normal. Una acción explícita SubagentStop en ese agente sigue observando sus propios hijos. Detenga las devoluciones de llamada stop_hook_active: true después de un bloqueo previo. Después de ocho continuaciones de Stop con ámbito consecutivo, el tiempo de ejecución omite más bloques con ámbito y acepta la respuesta final del elemento secundario a menos que un hook del ciclo de vida primario siga bloqueando la finalización.
Los Enlaces de repositorio y de agente heredado requieren la autorización del espacio de trabajo. La detección o selección del agente no otorga confianza. La configuración existente de desactivación de Enlaces, las restricciones empresariales, los permisos de espacio aislado y los tiempos de espera también se aplican a las acciones con ámbito. Los agentes proporcionados por el SDK no pueden tener privilegios de complemento administrado. Las definiciones de agente hospedados en la organización o remota no ejecutan Enlaces con ámbito y el envío Stop con ámbito es compatible con subagentes locales en tiempo de ejecución, no para participantes de AHP externos.
Los nombres de eventos no admitidos se notifican antes de la selección de eventos. Los errores de preparación de acciones afectan solo a su evento declarado; los Enlaces compartidos y de políticas administradas se siguen ejecutando.
Pase un hooks objeto al crear (o reanudar) una sesión. Cada ejemplo siguiente sigue este patrón.
Lenguajes de código navigation
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
},
)
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
},
})
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.CopilotClient;
import com.github.copilot.rpc.*;
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();
}
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.
Caso de uso: 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
Lenguajes de código navigation
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},
)
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
},
},
})
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.rpc.PermissionHandler;
import com.github.copilot.rpc.SessionConfig;
import com.github.copilot.rpc.SessionHooks;
import com.github.copilot.rpc.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();
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: "approve-once" }),
});
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: "approve-once" }),
});
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.
Caso de uso: 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
Lenguajes de código navigation
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,
},
)
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: "approve-once" }),
});
Caso de uso: notificaciones y sonidos
Los enlaces 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
Lenguajes de código navigation
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,
},
)
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: "approve-once" }),
});
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: "approve-once" }),
});
Caso de uso: 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
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" }),
});
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: "approve-once" }),
});
Caso de uso: control de errores y recuperación
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: "approve-once" }),
});
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: "approve-once" }),
});
Caso de uso: 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.
Lenguajes de código navigation
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,
},
)
Combinación de enlaces
Los hooks se integran de manera natural. Un único objeto hooks puede gestionar permisos y auditoría y notificaciones: cada hook cumple su propia función.
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" }),
});
procedimientos recomendados
-
Mantenga los enlaces rápidos. 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
nullcuando 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 devolvernull, 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
additionalContexten lugar demodifiedPromptcuando 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.sessionIdy limpie duranteonSessionEnd.
Reference
Para obtener definiciones de tipo completas, tablas de campos de entrada y salida y ejemplos adicionales para cada enlace, consulte la referencia de API:
- Enlaces de sesión
- Gancho previo al uso de la herramienta
- Enlace de uso posterior a la herramienta
- Enlace enviado de la solicitud de usuario
- Gancho de transformación de la solicitud al usuario
- Enlaces de ciclo de vida de sesión
- Gancho de manejo de errores