참고
코필로트 SDK가 현재 기술 미리 보기에 있습니다. 기능 및 가용성은 변경될 수 있습니다.
후크를 사용하면 세션의 코필로트 SDK 모든 단계에서, 시작되는 순간부터 각 사용자 프롬프트 및 도구 호출을 통하여 종료되는 순간까지 사용자 정의 로직을 연결할 수 있습니다. 후크를 사용하여 핵심 에이전트 동작을 수정하지 않고 권한, 감사, 알림 등을 구현할 수 있습니다.
개요
후크는 세션을 만들 때 한 번 등록하는 콜백입니다. SDK는 대화 수명 주기의 잘 정의된 지점에서 호출하고, 상황별 입력을 전달하며, 필요에 따라 세션의 동작을 수정하는 출력을 수락합니다. 세션 흐름의 자세한 시퀀스 다이어그램은 리포지토리를github/copilot-sdk 참조하세요.
| 후크 | 실행 시 | 수행 가능한 작업 |
|---|---|---|
onSessionStart | 세션 시작(신규 또는 다시 시작) | 컨텍스트 삽입, 부하 기본 설정 |
onUserPromptSubmitted | 사용자가 메시지를 보냅니다. | 프롬프트 다시 작성, 컨텍스트 추가, 입력 필터링 |
onPreToolUse | 도구를 실행하기 전에 | 호출 허용, 거부 또는 수정 |
onPostToolUse | 도구가 반환된 후 | 결과 변환, 기밀 편집, 감사 |
onSessionEnd | 세션 종료 | 클린업, 메트릭 기록하기 |
onErrorOccurred | 오류가 발생합니다. | 사용자 지정 로깅, 다시 시도 논리, 경고 |
모든 후크는 선택 사항이며 필요한 후크만 등록합니다.
null (또는 해당 언어의 동등한 표현)을 후크에서 반환하면 SDK가 기본 동작을 계속합니다.
후크 등록
`hooks` 세션을 만들거나 다시 시작할 때 개체를 전달합니다. 아래의 모든 예제는 이 패턴을 따릅니다.
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" }),
});
Python, Go 및 .NET의 예제는 리포지토리를github/copilot-sdk 참조하세요.
팁
모든 후크 처리기는 세션별 상태를 유지하고 로그의 상관 관계를 지정하는 데 유용한 sessionId을 포함하는 매개 변수 invocation를 수신합니다.
권한 제어
에이전트가 실행할 수 있는 도구, 허용되는 인수 및 실행 전에 사용자에게 메시지가 표시되어야 하는지 여부를 결정하는 권한 계층을 빌드하는 데 사용합니다 onPreToolUse .
"안전한 도구 집합을 허용 목록으로 등록"
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" }),
});
Python, Go 및 .NET의 예제는 리포지토리를github/copilot-sdk 참조하세요.
특정 디렉터리에 대한 파일 액세스 제한
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" }),
});
파괴적인 작업 전에 사용자에게 문의
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" }),
});
반환하면 "ask" 런타임에 사용자에게 결정을 위임합니다. 이는 사람이 관여해야 하는 파괴적인 작업에서 유용합니다.
감사 및 규정 준수
`onPreToolUse`, `onPostToolUse` 및 세션 수명 주기 후크를 결합하여 에이전트가 수행하는 모든 작업을 기록하는 완전한 감사 추적을 작성합니다.
구조적 감사 로그
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" }),
});
Python의 예제는 리포지토리를github/copilot-sdk 참조하세요.
도구 결과에서 비밀 항목 제거
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" }),
});
알림
애플리케이션 프로세스에서 후크가 실행되므로 데스크톱 알림, 소리, Slack 메시지 또는 웹후크 호출과 같은 효과를 발생시킬 수 있습니다.
세션 이벤트에 대한 데스크톱 알림
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" }),
});
Python의 예제는 리포지토리를github/copilot-sdk 참조하세요.
도구가 작업을 마치면 소리를 재생
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" }),
});
오류 발생 후 Slack에 게시
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" }),
});
프롬프트 보강
`onSessionStart` 및 `onUserPromptSubmitted`을 사용하여 자동으로 컨텍스트를 삽입하면 사용자가 스스로 반복할 필요가 없습니다.
세션 시작 시 프로젝트 메타데이터 삽입
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" }),
});
프롬프트에서 줄임 명령어를 확장하기
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" }),
});
오류 처리 및 복구
`onErrorOccurred` 후크는 오류에 대응할 수 있는 기회를 제공합니다. 여기에는 다시 시도하거나, 사람에게 알리거나, 정상적으로 종료하는 등의 방법이 포함됩니다.
일시적인 모델 오류 다시 시도
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" }),
});
친숙한 오류 메시지
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" }),
});
세션 메트릭
세션 실행 시간, 호출되는 도구 수 및 세션이 종료되는 이유를 추적하여 대시보드 및 비용 모니터링에 유용합니다.
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" }),
});
Python의 예제는 리포지토리를github/copilot-sdk 참조하세요.
후크 결합
후크는 자연스럽게 조합됩니다. 단일 hooks 개체는 사용 권한, 감사 및 알림을 처리할 수 있습니다. 각 후크는 자체 작업을 수행합니다.
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" }),
});
모범 사례
-
**후크가 빠르게 작동하도록 유지합니다.** 모든 후크는 인라인으로 실행되며 느린 후크는 대화를 지연합니다. 가능하면 백그라운드 큐에 많은 작업(데이터베이스 쓰기, HTTP 호출)을 오프로드합니다. -
**변경할 내용이 없으면 반환 `null` 합니다.** 이렇게 하면 SDK에서 기본값을 계속 진행하도록 지시하고 불필요한 개체 할당을 방지합니다. -
**사용 권한 결정에 명시적이어야 합니다.** 도구를 허용하더라도 `{ permissionDecision: "allow" }` 반환은 `null` 반환보다 더 명확합니다. -
**중요한 오류를 삼켜도 안됩니다.** 복구 가능한 도구 오류를 표시하지 않는 것은 괜찮지만, 복구할 수 없는 오류에 대해 항상 로그 또는 경고를 표시합니다. -
**가능한 경우 대신 `additionalContext` 사용합니다`modifiedPrompt`.** 컨텍스트를 추가하면 모델을 계속 안내하는 동안 사용자의 원래 의도가 유지됩니다. -
**세션 ID별 범위 상태입니다.** 세션별 데이터를 추적하는 경우 `invocation.sessionId`에 키를 설정하고 `onSessionEnd`에서 정리합니다.
추가 읽기
자세한 내용은 리포지토리의 후크 참조 를 github/copilot-sdk 참조하세요.