{"meta":{"title":"Utilisation de crochets","intro":"Les hooks vous permettent de connecter une logique personnalisée à chaque étape d’une session Copilot, à partir du moment où elle démarre, par le biais de chaque invite utilisateur et appel d’outil, jusqu’au moment où elle se termine. Ce guide décrit les cas d’usage pratiques afin de pouvoir expédier des autorisations, des audits, des notifications et bien plus encore sans modifier le comportement de l’agent principal.","product":"GitHub Copilot","breadcrumbs":[{"href":"/fr/copilot","title":"GitHub Copilot"},{"href":"/fr/copilot/how-tos","title":"Procédures"},{"href":"/fr/copilot/how-tos/copilot-sdk","title":"Kit de développement logiciel (SDK) Copilot"},{"href":"/fr/copilot/how-tos/copilot-sdk/features","title":"Fonctionnalités"},{"href":"/fr/copilot/how-tos/copilot-sdk/features/hooks","title":"Hooks"}],"documentType":"article"},"body":"# Utilisation de crochets\n\nLes hooks vous permettent de connecter une logique personnalisée à chaque étape d’une session Copilot, à partir du moment où elle démarre, par le biais de chaque invite utilisateur et appel d’outil, jusqu’au moment où elle se termine. Ce guide décrit les cas d’usage pratiques afin de pouvoir expédier des autorisations, des audits, des notifications et bien plus encore sans modifier le comportement de l’agent principal.\n\n<!-- markdownlint-disable GHD046 GHD005 -->\n\n<!-- Suppressed: GHD046 (outdated release terminology), GHD005 (hardcoded data variable) -->\n\n## Overview\n\nUn hook est un rappel que vous inscrivez une fois lors de la création d’une session. Le Kit de développement logiciel (SDK) l’appelle à un point bien défini dans le cycle de vie de la conversation, transmet une entrée contextuelle et accepte éventuellement la sortie qui modifie le comportement de la session.\n\n![Diagramme : Organigramme montrant le processus décrit.](/assets/images/help/copilot/copilot-sdk/features-hooks-diagram-0.png)\n\n| Hook                                                                                                                  | Quand il se déclenche                                   | Ce que vous pouvez faire                                               |\n| --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------- |\n| [Hooks du cycle de vie de la session](/fr/copilot/how-tos/copilot-sdk/hooks/session-lifecycle#session-start)          | La session commence (nouvelle ou reprise)               | Injecter le contexte, les préférences de chargement                    |\n| [Hook déclenché à la soumission d’un prompt utilisateur](/fr/copilot/how-tos/copilot-sdk/hooks/user-prompt-submitted) | L’utilisateur envoie un message                         | Réécrire les invites, ajouter du contexte, filtrer les entrées         |\n| [Hook exécuté avant l’utilisation d’un outil](/fr/copilot/how-tos/copilot-sdk/hooks/pre-tool-use)                     | Avant qu'un outil ne s'exécute                          | Autoriser/ refuser / modifier l’appel                                  |\n| [Hook exécuté après l’utilisation d’un outil](/fr/copilot/how-tos/copilot-sdk/hooks/post-tool-use)                    | Une fois qu’un outil est retourné (réussite uniquement) | Transformation des résultats, rédiger des secrets, auditer             |\n| [Hook exécuté après l’utilisation d’un outil](/fr/copilot/how-tos/copilot-sdk/hooks/post-tool-use#failure-variant)    | Après qu’un outil a renvoyé une erreur                  | Ajouter des instructions de nouvelle tentative, journaliser les échecs |\n| [Hooks du cycle de vie de la session](/fr/copilot/how-tos/copilot-sdk/hooks/session-lifecycle#session-end)            | Fin de session                                          | Nettoyer, enregistrer les métriques                                    |\n| [Hook de gestion des erreurs](/fr/copilot/how-tos/copilot-sdk/hooks/error-handling)                                   | Une erreur est générée                                  | Journalisation personnalisée, logique de nouvelle tentative, alertes   |\n\nTous les crochets sont **facultatifs** : inscrivez uniquement ceux dont vous avez besoin. Le renvoi `null` (ou l’équivalent du langage) à partir de n’importe quel hook indique au SDK de continuer avec le comportement par défaut.\n\n## Enregistrement de points d'extension\n\nTransmettez un `hooks` objet lorsque vous créez (ou reprenez) une session. Chaque exemple ci-dessous suit ce modèle.\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\n```typescript\nimport { CopilotClient } from \"@github/copilot-sdk\";\n\nconst client = new CopilotClient();\nawait client.start();\n\nconst session = await client.createSession({\n  hooks: {\n    onSessionStart: async (input, invocation) => {\n      /* ... */\n    },\n    onPreToolUse: async (input, invocation) => {\n      /* ... */\n    },\n    onPostToolUse: async (input, invocation) => {\n      /* ... */\n    },\n    // ... add only the hooks you need\n  },\n  onPermissionRequest: async () => ({ kind: \"approve-once\" }),\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\n```python\nfrom copilot import CopilotClient, PermissionDecisionApproveOnce\n\nclient = CopilotClient()\nawait client.start()\n\nsession = await client.create_session(\n    on_permission_request=lambda req, inv: PermissionDecisionApproveOnce(),\n    hooks={\n        \"on_session_start\": on_session_start,\n        \"on_pre_tool_use\":  on_pre_tool_use,\n        \"on_post_tool_use\": on_post_tool_use,\n        # ... add only the hooks you need\n    },\n)\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"go\" data-label=\"Go\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Go</div>\n\n```golang\npackage main\n\nimport (\n    \"context\"\n    copilot \"github.com/github/copilot-sdk/go\"\n    \"github.com/github/copilot-sdk/go/rpc\"\n)\n\nfunc onSessionStart(input copilot.SessionStartHookInput, inv copilot.HookInvocation) (*copilot.SessionStartHookOutput, error) {\n    return nil, nil\n}\n\nfunc onPreToolUse(input copilot.PreToolUseHookInput, inv copilot.HookInvocation) (*copilot.PreToolUseHookOutput, error) {\n    return nil, nil\n}\n\nfunc onPostToolUse(input copilot.PostToolUseHookInput, inv copilot.HookInvocation) (*copilot.PostToolUseHookOutput, error) {\n    return nil, nil\n}\n\nfunc main() {\n    ctx := context.Background()\n    client := copilot.NewClient(nil)\n\n    session, err := client.CreateSession(ctx, &copilot.SessionConfig{\n        Hooks: &copilot.SessionHooks{\n            OnSessionStart: onSessionStart,\n            OnPreToolUse:   onPreToolUse,\n            OnPostToolUse:  onPostToolUse,\n        },\n        OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) {\n            return &rpc.PermissionDecisionApproveOnce{}, nil\n        },\n    })\n    _ = session\n    _ = err\n}\n```\n\n```golang\nclient := copilot.NewClient(nil)\n\nsession, err := client.CreateSession(ctx, &copilot.SessionConfig{\n    Hooks: &copilot.SessionHooks{\n        OnSessionStart: onSessionStart,\n        OnPreToolUse:   onPreToolUse,\n        OnPostToolUse:  onPostToolUse,\n        // ... add only the hooks you need\n    },\n    OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) {\n        return &rpc.PermissionDecisionApproveOnce{}, nil\n    },\n})\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"dotnet\" data-label=\".NET\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">.NET</div>\n\n```csharp\nusing GitHub.Copilot;\nusing GitHub.Copilot.Rpc;\n\npublic static class HooksExample\n{\n    static Task<SessionStartHookOutput?> onSessionStart(SessionStartHookInput input, HookInvocation invocation) =>\n        Task.FromResult<SessionStartHookOutput?>(null);\n    static Task<PreToolUseHookOutput?> onPreToolUse(PreToolUseHookInput input, HookInvocation invocation) =>\n        Task.FromResult<PreToolUseHookOutput?>(null);\n    static Task<PostToolUseHookOutput?> onPostToolUse(PostToolUseHookInput input, HookInvocation invocation) =>\n        Task.FromResult<PostToolUseHookOutput?>(null);\n\n    public static async Task Main()\n    {\n        var client = new CopilotClient();\n\n        var session = await client.CreateSessionAsync(new SessionConfig\n        {\n            Hooks = new SessionHooks\n            {\n                OnSessionStart = onSessionStart,\n                OnPreToolUse   = onPreToolUse,\n                OnPostToolUse  = onPostToolUse,\n            },\n            OnPermissionRequest = (req, inv) =>\n                Task.FromResult(PermissionDecision.ApproveOnce()),\n        });\n    }\n}\n```\n\n```csharp\nvar client = new CopilotClient();\n\nvar session = await client.CreateSessionAsync(new SessionConfig\n{\n    Hooks = new SessionHooks\n    {\n        OnSessionStart = onSessionStart,\n        OnPreToolUse   = onPreToolUse,\n        OnPostToolUse  = onPostToolUse,\n        // ... add only the hooks you need\n    },\n    OnPermissionRequest = (req, inv) =>\n        Task.FromResult(PermissionDecision.ApproveOnce()),\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"java\" data-label=\"Java\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Java</div>\n\n```java\nimport com.github.copilot.CopilotClient;\nimport com.github.copilot.rpc.*;\nimport java.util.concurrent.CompletableFuture;\n\ntry (var client = new CopilotClient()) {\n    client.start().get();\n\n    var hooks = new SessionHooks()\n        .setOnSessionStart((input, inv) -> CompletableFuture.completedFuture(null))\n        .setOnPreToolUse((input, inv) -> CompletableFuture.completedFuture(null))\n        .setOnPostToolUse((input, inv) -> CompletableFuture.completedFuture(null));\n        // ... add only the hooks you need\n\n    var session = client.createSession(\n        new SessionConfig()\n            .setHooks(hooks)\n            .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)\n    ).get();\n}\n```\n\n</div>\n\n</div>\n\n> \\[!TIP]\n> Chaque gestionnaire de hooks reçoit un `invocation` paramètre contenant le `sessionId`, ce qui est utile pour la corrélation des journaux et le maintien de l'état par session.\n\n## Cas d’usage : contrôle d’autorisation\n\nPermet `onPreToolUse` de générer une couche d’autorisation qui détermine les outils que l’agent peut exécuter, quels arguments sont autorisés et si l’utilisateur doit être invité avant l’exécution.\n\n### Liste verte d’un ensemble sécurisé d’outils\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\n```typescript\nconst READ_ONLY_TOOLS = [\"read_file\", \"glob\", \"grep\", \"view\"];\n\nconst session = await client.createSession({\n  hooks: {\n    onPreToolUse: async (input) => {\n      if (!READ_ONLY_TOOLS.includes(input.toolName)) {\n        return {\n          permissionDecision: \"deny\",\n          permissionDecisionReason: `Only read-only tools are allowed. \"${input.toolName}\" was blocked.`,\n        };\n      }\n      return { permissionDecision: \"allow\" };\n    },\n  },\n  onPermissionRequest: async () => ({ kind: \"approve-once\" }),\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\n```python\nfrom copilot import PermissionDecisionApproveOnce\n\nREAD_ONLY_TOOLS = [\"read_file\", \"glob\", \"grep\", \"view\"]\n\nasync def on_pre_tool_use(input_data, invocation):\n    if input_data[\"toolName\"] not in READ_ONLY_TOOLS:\n        return {\n            \"permissionDecision\": \"deny\",\n            \"permissionDecisionReason\":\n                f'Only read-only tools are allowed. \"{input_data[\"toolName\"]}\" was blocked.',\n        }\n    return {\"permissionDecision\": \"allow\"}\n\nsession = await client.create_session(\n    on_permission_request=lambda req, inv: PermissionDecisionApproveOnce(),\n    hooks={\"on_pre_tool_use\": on_pre_tool_use},\n)\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"go\" data-label=\"Go\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Go</div>\n\n```golang\npackage main\n\nimport (\n    \"context\"\n    \"fmt\"\n    copilot \"github.com/github/copilot-sdk/go\"\n    \"github.com/github/copilot-sdk/go/rpc\"\n)\n\nfunc main() {\n    ctx := context.Background()\n    client := copilot.NewClient(nil)\n\n    readOnlyTools := map[string]bool{\"read_file\": true, \"glob\": true, \"grep\": true, \"view\": true}\n\n    session, _ := client.CreateSession(ctx, &copilot.SessionConfig{\n        Hooks: &copilot.SessionHooks{\n            OnPreToolUse: func(input copilot.PreToolUseHookInput, inv copilot.HookInvocation) (*copilot.PreToolUseHookOutput, error) {\n                if !readOnlyTools[input.ToolName] {\n                    return &copilot.PreToolUseHookOutput{\n                        PermissionDecision:       \"deny\",\n                        PermissionDecisionReason: fmt.Sprintf(\"Only read-only tools are allowed. %q was blocked.\", input.ToolName),\n                    }, nil\n                }\n                return &copilot.PreToolUseHookOutput{PermissionDecision: \"allow\"}, nil\n            },\n        },\n        OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) {\n            return &rpc.PermissionDecisionApproveOnce{}, nil\n        },\n    })\n    _ = session\n}\n```\n\n```golang\nreadOnlyTools := map[string]bool{\"read_file\": true, \"glob\": true, \"grep\": true, \"view\": true}\n\nsession, _ := client.CreateSession(ctx, &copilot.SessionConfig{\n    Hooks: &copilot.SessionHooks{\n        OnPreToolUse: func(input copilot.PreToolUseHookInput, inv copilot.HookInvocation) (*copilot.PreToolUseHookOutput, error) {\n            if !readOnlyTools[input.ToolName] {\n                return &copilot.PreToolUseHookOutput{\n                    PermissionDecision:       \"deny\",\n                    PermissionDecisionReason: fmt.Sprintf(\"Only read-only tools are allowed. %q was blocked.\", input.ToolName),\n                }, nil\n            }\n            return &copilot.PreToolUseHookOutput{PermissionDecision: \"allow\"}, nil\n        },\n    },\n})\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"dotnet\" data-label=\".NET\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">.NET</div>\n\n```csharp\nusing GitHub.Copilot;\nusing GitHub.Copilot.Rpc;\n\npublic static class PermissionControlExample\n{\n    public static async Task Main()\n    {\n        await using var client = new CopilotClient();\n\n        var readOnlyTools = new HashSet<string> { \"read_file\", \"glob\", \"grep\", \"view\" };\n\n        var session = await client.CreateSessionAsync(new SessionConfig\n        {\n            Hooks = new SessionHooks\n            {\n                OnPreToolUse = (input, invocation) =>\n                {\n                    if (!readOnlyTools.Contains(input.ToolName))\n                    {\n                        return Task.FromResult<PreToolUseHookOutput?>(new PreToolUseHookOutput\n                        {\n                            PermissionDecision = \"deny\",\n                            PermissionDecisionReason = $\"Only read-only tools are allowed. \\\"{input.ToolName}\\\" was blocked.\",\n                        });\n                    }\n                    return Task.FromResult<PreToolUseHookOutput?>(\n                        new PreToolUseHookOutput { PermissionDecision = \"allow\" });\n                },\n            },\n            OnPermissionRequest = (req, inv) =>\n                Task.FromResult(PermissionDecision.ApproveOnce()),\n        });\n    }\n}\n```\n\n```csharp\nvar readOnlyTools = new HashSet<string> { \"read_file\", \"glob\", \"grep\", \"view\" };\n\nvar session = await client.CreateSessionAsync(new SessionConfig\n{\n    Hooks = new SessionHooks\n    {\n        OnPreToolUse = (input, invocation) =>\n        {\n            if (!readOnlyTools.Contains(input.ToolName))\n            {\n                return Task.FromResult<PreToolUseHookOutput?>(new PreToolUseHookOutput\n                {\n                    PermissionDecision = \"deny\",\n                    PermissionDecisionReason = $\"Only read-only tools are allowed. \\\"{input.ToolName}\\\" was blocked.\",\n                });\n            }\n            return Task.FromResult<PreToolUseHookOutput?>(\n                new PreToolUseHookOutput { PermissionDecision = \"allow\" });\n        },\n    },\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"java\" data-label=\"Java\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Java</div>\n\n<!-- docs-validate: skip -->\n\n```java\nimport java.util.Set;\nimport java.util.concurrent.CompletableFuture;\n\nimport com.github.copilot.rpc.PermissionHandler;\nimport com.github.copilot.rpc.SessionConfig;\nimport com.github.copilot.rpc.SessionHooks;\nimport com.github.copilot.rpc.PreToolUseHookOutput;\nvar readOnlyTools = Set.of(\"read_file\", \"glob\", \"grep\", \"view\");\n\nvar hooks = new SessionHooks()\n    .setOnPreToolUse((input, invocation) -> {\n        if (!readOnlyTools.contains(input.getToolName())) {\n            return CompletableFuture.completedFuture(\n                PreToolUseHookOutput.deny(\n                    \"Only read-only tools are allowed. \\\"\" + input.getToolName() + \"\\\" was blocked.\")\n            );\n        }\n        return CompletableFuture.completedFuture(PreToolUseHookOutput.allow());\n    });\n\nvar session = client.createSession(\n    new SessionConfig()\n        .setHooks(hooks)\n        .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)\n).get();\n```\n\n</div>\n\n</div>\n\n### Restreindre l’accès aux fichiers à des répertoires spécifiques\n\n```typescript\nconst ALLOWED_DIRS = [\"/home/user/projects\", \"/tmp\"];\n\nconst session = await client.createSession({\n  hooks: {\n    onPreToolUse: async (input) => {\n      if ([\"read_file\", \"write_file\", \"edit\"].includes(input.toolName)) {\n        const filePath = (input.toolArgs as { path: string }).path;\n        const allowed = ALLOWED_DIRS.some((dir) => filePath.startsWith(dir));\n\n        if (!allowed) {\n          return {\n            permissionDecision: \"deny\",\n            permissionDecisionReason: `Access to \"${filePath}\" is outside the allowed directories.`,\n          };\n        }\n      }\n      return { permissionDecision: \"allow\" };\n    },\n  },\n  onPermissionRequest: async () => ({ kind: \"approve-once\" }),\n});\n```\n\n### Demander à l’utilisateur avant les opérations destructrices\n\n```typescript\nconst DESTRUCTIVE_TOOLS = [\"delete_file\", \"shell\", \"bash\"];\n\nconst session = await client.createSession({\n  hooks: {\n    onPreToolUse: async (input) => {\n      if (DESTRUCTIVE_TOOLS.includes(input.toolName)) {\n        return { permissionDecision: \"ask\" };\n      }\n      return { permissionDecision: \"allow\" };\n    },\n  },\n  onPermissionRequest: async () => ({ kind: \"approve-once\" }),\n});\n```\n\nRenvoyer `\"ask\"` la décision à l’utilisateur au moment de l’exécution : utile pour les actions destructrices où vous souhaitez un humain dans la boucle.\n\n## Cas d’usage : audit et conformité\n\nCombinez `onPreToolUse`, `onPostToolUse` et les crochets de cycle de vie de session pour générer une piste d’audit exhaustive qui enregistre chaque action effectuée par l’agent.\n\n### Journal d’audit structuré\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\n```typescript\ninterface AuditEntry {\n  timestamp: Date;\n  sessionId: string;\n  event: string;\n  toolName?: string;\n  toolArgs?: unknown;\n  toolResult?: unknown;\n  prompt?: string;\n}\n\nconst auditLog: AuditEntry[] = [];\n\nconst session = await client.createSession({\n  hooks: {\n    onSessionStart: async (input, invocation) => {\n      auditLog.push({\n        timestamp: input.timestamp,\n        sessionId: invocation.sessionId,\n        event: \"session_start\",\n      });\n      return null;\n    },\n    onUserPromptSubmitted: async (input, invocation) => {\n      auditLog.push({\n        timestamp: input.timestamp,\n        sessionId: invocation.sessionId,\n        event: \"user_prompt\",\n        prompt: input.prompt,\n      });\n      return null;\n    },\n    onPreToolUse: async (input, invocation) => {\n      auditLog.push({\n        timestamp: input.timestamp,\n        sessionId: invocation.sessionId,\n        event: \"tool_call\",\n        toolName: input.toolName,\n        toolArgs: input.toolArgs,\n      });\n      return { permissionDecision: \"allow\" };\n    },\n    onPostToolUse: async (input, invocation) => {\n      auditLog.push({\n        timestamp: input.timestamp,\n        sessionId: invocation.sessionId,\n        event: \"tool_result\",\n        toolName: input.toolName,\n        toolResult: input.toolResult,\n      });\n      return null;\n    },\n    onSessionEnd: async (input, invocation) => {\n      auditLog.push({\n        timestamp: input.timestamp,\n        sessionId: invocation.sessionId,\n        event: \"session_end\",\n      });\n\n      // Persist the log — swap this with your own storage backend\n      await fs.promises.writeFile(\n        `audit-${invocation.sessionId}.json`,\n        JSON.stringify(auditLog, null, 2),\n      );\n      return null;\n    },\n  },\n  onPermissionRequest: async () => ({ kind: \"approve-once\" }),\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\n<!-- docs-validate: skip -->\n\n```python\nimport json, aiofiles\nfrom copilot import PermissionDecisionApproveOnce\n\naudit_log = []\n\nasync def on_session_start(input_data, invocation):\n    audit_log.append({\n        \"timestamp\": input_data[\"timestamp\"].isoformat(),\n        \"session_id\": invocation[\"session_id\"],\n        \"event\": \"session_start\",\n    })\n    return None\n\nasync def on_user_prompt_submitted(input_data, invocation):\n    audit_log.append({\n        \"timestamp\": input_data[\"timestamp\"].isoformat(),\n        \"session_id\": invocation[\"session_id\"],\n        \"event\": \"user_prompt\",\n        \"prompt\": input_data[\"prompt\"],\n    })\n    return None\n\nasync def on_pre_tool_use(input_data, invocation):\n    audit_log.append({\n        \"timestamp\": input_data[\"timestamp\"].isoformat(),\n        \"session_id\": invocation[\"session_id\"],\n        \"event\": \"tool_call\",\n        \"tool_name\": input_data[\"toolName\"],\n        \"tool_args\": input_data[\"toolArgs\"],\n    })\n    return {\"permissionDecision\": \"allow\"}\n\nasync def on_post_tool_use(input_data, invocation):\n    audit_log.append({\n        \"timestamp\": input_data[\"timestamp\"].isoformat(),\n        \"session_id\": invocation[\"session_id\"],\n        \"event\": \"tool_result\",\n        \"tool_name\": input_data[\"toolName\"],\n        \"tool_result\": input_data[\"toolResult\"],\n    })\n    return None\n\nasync def on_session_end(input_data, invocation):\n    audit_log.append({\n        \"timestamp\": input_data[\"timestamp\"].isoformat(),\n        \"session_id\": invocation[\"session_id\"],\n        \"event\": \"session_end\",\n    })\n    async with aiofiles.open(f\"audit-{invocation['session_id']}.json\", \"w\") as f:\n        await f.write(json.dumps(audit_log, indent=2))\n    return None\n\nsession = await client.create_session(\n    on_permission_request=lambda req, inv: PermissionDecisionApproveOnce(),\n    hooks={\n        \"on_session_start\": on_session_start,\n        \"on_user_prompt_submitted\": on_user_prompt_submitted,\n        \"on_pre_tool_use\": on_pre_tool_use,\n        \"on_post_tool_use\": on_post_tool_use,\n        \"on_session_end\": on_session_end,\n    },\n)\n```\n\n</div>\n\n</div>\n\n### Réactez les secrets à partir des résultats de l’outil\n\n```typescript\nconst SECRET_PATTERNS = [\n  /(?:api[_-]?key|token|secret|password)\\s*[:=]\\s*[\"']?[\\w\\-\\.]+[\"']?/gi,\n];\n\nconst session = await client.createSession({\n  hooks: {\n    onPostToolUse: async (input) => {\n      if (typeof input.toolResult !== \"string\") return null;\n\n      let redacted = input.toolResult;\n      for (const pattern of SECRET_PATTERNS) {\n        redacted = redacted.replace(pattern, \"[REDACTED]\");\n      }\n\n      return redacted !== input.toolResult\n        ? { modifiedResult: redacted }\n        : null;\n    },\n  },\n  onPermissionRequest: async () => ({ kind: \"approve-once\" }),\n});\n```\n\n## Cas d’usage : notifications et sons\n\nLes hooks se déclenchent dans le processus de votre application, ce qui vous permet de déclencher n’importe quel effet secondaire : notifications de bureau, sons, messages Slack ou appels webhook.\n\n### Notification de bureau sur les événements de session\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\n```typescript\nimport notifier from \"node-notifier\"; // npm install node-notifier\n\nconst session = await client.createSession({\n  hooks: {\n    onSessionEnd: async (input, invocation) => {\n      notifier.notify({\n        title: \"Copilot Session Complete\",\n        message: `Session ${invocation.sessionId.slice(0, 8)} finished (${input.reason}).`,\n      });\n      return null;\n    },\n    onErrorOccurred: async (input) => {\n      notifier.notify({\n        title: \"Copilot Error\",\n        message: input.error.slice(0, 200),\n      });\n      return null;\n    },\n  },\n  onPermissionRequest: async () => ({ kind: \"approve-once\" }),\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\n```python\nimport subprocess\nfrom copilot import PermissionDecisionApproveOnce\n\nasync def on_session_end(input_data, invocation):\n    sid = invocation[\"session_id\"][:8]\n    reason = input_data[\"reason\"]\n    subprocess.Popen([\n        \"notify-send\", \"Copilot Session Complete\",\n        f\"Session {sid} finished ({reason}).\",\n    ])\n    return None\n\nasync def on_error_occurred(input_data, invocation):\n    subprocess.Popen([\n        \"notify-send\", \"Copilot Error\",\n        input_data[\"error\"][:200],\n    ])\n    return None\n\nsession = await client.create_session(\n    on_permission_request=lambda req, inv: PermissionDecisionApproveOnce(),\n    hooks={\n        \"on_session_end\": on_session_end,\n        \"on_error_occurred\": on_error_occurred,\n    },\n)\n```\n\n</div>\n\n</div>\n\n### Jouer un son lors de la fin d'un outil\n\n```typescript\nimport { exec } from \"node:child_process\";\n\nconst session = await client.createSession({\n  hooks: {\n    onPostToolUse: async (input) => {\n      // macOS: play a system sound after every tool call\n      exec(\"afplay /System/Library/Sounds/Pop.aiff\");\n      return null;\n    },\n    onErrorOccurred: async () => {\n      exec(\"afplay /System/Library/Sounds/Basso.aiff\");\n      return null;\n    },\n  },\n  onPermissionRequest: async () => ({ kind: \"approve-once\" }),\n});\n```\n\n### Publier sur Slack en cas d'erreurs\n\n```typescript\nconst SLACK_WEBHOOK_URL = process.env.SLACK_WEBHOOK_URL!;\n\nconst session = await client.createSession({\n  hooks: {\n    onErrorOccurred: async (input, invocation) => {\n      if (!input.recoverable) {\n        await fetch(SLACK_WEBHOOK_URL, {\n          method: \"POST\",\n          headers: { \"Content-Type\": \"application/json\" },\n          body: JSON.stringify({\n            text: `🚨 Unrecoverable error in session \\`${invocation.sessionId.slice(0, 8)}\\`:\\n\\`\\`\\`${input.error}\\`\\`\\``,\n          }),\n        });\n      }\n      return null;\n    },\n  },\n  onPermissionRequest: async () => ({ kind: \"approve-once\" }),\n});\n```\n\n## Cas d’usage : enrichissement rapide\n\nUtilisez `onSessionStart` et `onUserPromptSubmitted` injectez automatiquement le contexte afin que les utilisateurs n’aient pas à se répéter.\n\n### Injecter des métadonnées de projet au début de la session\n\n```typescript\nconst session = await client.createSession({\n  hooks: {\n    onSessionStart: async (input) => {\n      const pkg = JSON.parse(\n        await fs.promises.readFile(\"package.json\", \"utf-8\"),\n      );\n      return {\n        additionalContext: [\n          `Project: ${pkg.name} v${pkg.version}`,\n          `Node: ${process.version}`,\n          `Working directory: ${input.workingDirectory}`,\n        ].join(\"\\n\"),\n      };\n    },\n  },\n  onPermissionRequest: async () => ({ kind: \"approve-once\" }),\n});\n```\n\n### Développer des commandes abrégées dans les requêtes\n\n```typescript\nconst SHORTCUTS: Record<string, string> = {\n  \"/fix\": \"Find and fix all errors in the current file\",\n  \"/test\": \"Write comprehensive unit tests for this code\",\n  \"/explain\": \"Explain this code in detail\",\n  \"/refactor\": \"Refactor this code to improve readability\",\n};\n\nconst session = await client.createSession({\n  hooks: {\n    onUserPromptSubmitted: async (input) => {\n      for (const [shortcut, expansion] of Object.entries(SHORTCUTS)) {\n        if (input.prompt.startsWith(shortcut)) {\n          const rest = input.prompt.slice(shortcut.length).trim();\n          return { modifiedPrompt: rest ? `${expansion}: ${rest}` : expansion };\n        }\n      }\n      return null;\n    },\n  },\n  onPermissionRequest: async () => ({ kind: \"approve-once\" }),\n});\n```\n\n## Cas d’usage : gestion des erreurs et récupération\n\nLe `onErrorOccurred` crochet vous donne la possibilité de réagir aux défaillances, qu'il s'agisse de réessayer, d'avertir un humain ou d'arrêter de manière élégante.\n\n### Reessayer les erreurs de modèle transitoires\n\n```typescript\nconst session = await client.createSession({\n  hooks: {\n    onErrorOccurred: async (input) => {\n      if (input.errorContext === \"model_call\" && input.recoverable) {\n        return {\n          errorHandling: \"retry\",\n          retryCount: 3,\n          userNotification: \"Temporary model issue — retrying…\",\n        };\n      }\n      return null;\n    },\n  },\n  onPermissionRequest: async () => ({ kind: \"approve-once\" }),\n});\n```\n\n### Messages d’erreur conviviaux\n\n```typescript\nconst FRIENDLY_MESSAGES: Record<string, string> = {\n  model_call: \"The AI model is temporarily unavailable. Please try again.\",\n  tool_execution: \"A tool encountered an error. Check inputs and try again.\",\n  system: \"A system error occurred. Please try again later.\",\n};\n\nconst session = await client.createSession({\n  hooks: {\n    onErrorOccurred: async (input) => {\n      return {\n        userNotification: FRIENDLY_MESSAGES[input.errorContext] ?? input.error,\n      };\n    },\n  },\n  onPermissionRequest: async () => ({ kind: \"approve-once\" }),\n});\n```\n\n## Cas d’usage : métriques de session\n\nSuivez la durée d’exécution des sessions, le nombre d’outils appelés et la raison pour laquelle les sessions se terminent, utiles pour les tableaux de bord et la surveillance des coûts.\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\n```typescript\nconst metrics = new Map<\n  string,\n  { start: Date; toolCalls: number; prompts: number }\n>();\n\nconst session = await client.createSession({\n  hooks: {\n    onSessionStart: async (input, invocation) => {\n      metrics.set(invocation.sessionId, {\n        start: input.timestamp,\n        toolCalls: 0,\n        prompts: 0,\n      });\n      return null;\n    },\n    onUserPromptSubmitted: async (_input, invocation) => {\n      metrics.get(invocation.sessionId)!.prompts++;\n      return null;\n    },\n    onPreToolUse: async (_input, invocation) => {\n      metrics.get(invocation.sessionId)!.toolCalls++;\n      return { permissionDecision: \"allow\" };\n    },\n    onSessionEnd: async (input, invocation) => {\n      const m = metrics.get(invocation.sessionId)!;\n      const durationSec =\n        (input.timestamp.getTime() - m.start.getTime()) / 1000;\n\n      console.log(\n        `Session ${invocation.sessionId.slice(0, 8)}: ` +\n          `${durationSec.toFixed(1)}s, ${m.prompts} prompts, ` +\n          `${m.toolCalls} tool calls, ended: ${input.reason}`,\n      );\n\n      metrics.delete(invocation.sessionId);\n      return null;\n    },\n  },\n  onPermissionRequest: async () => ({ kind: \"approve-once\" }),\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\n```python\nfrom copilot import PermissionDecisionApproveOnce\n\nsession_metrics = {}\n\nasync def on_session_start(input_data, invocation):\n    session_metrics[invocation[\"session_id\"]] = {\n        \"start\": input_data[\"timestamp\"],\n        \"tool_calls\": 0,\n        \"prompts\": 0,\n    }\n    return None\n\nasync def on_user_prompt_submitted(input_data, invocation):\n    session_metrics[invocation[\"session_id\"]][\"prompts\"] += 1\n    return None\n\nasync def on_pre_tool_use(input_data, invocation):\n    session_metrics[invocation[\"session_id\"]][\"tool_calls\"] += 1\n    return {\"permissionDecision\": \"allow\"}\n\nasync def on_session_end(input_data, invocation):\n    m = session_metrics.pop(invocation[\"session_id\"])\n    duration = (input_data[\"timestamp\"] - m[\"start\"]).total_seconds()\n    sid = invocation[\"session_id\"][:8]\n    print(\n        f\"Session {sid}: {duration:.1f}s, {m['prompts']} prompts, \"\n        f\"{m['tool_calls']} tool calls, ended: {input_data['reason']}\"\n    )\n    return None\n\nsession = await client.create_session(\n    on_permission_request=lambda req, inv: PermissionDecisionApproveOnce(),\n    hooks={\n        \"on_session_start\": on_session_start,\n        \"on_user_prompt_submitted\": on_user_prompt_submitted,\n        \"on_pre_tool_use\": on_pre_tool_use,\n        \"on_session_end\": on_session_end,\n    },\n)\n```\n\n</div>\n\n</div>\n\n## Combinaison de crochets\n\nLes hooks se composent naturellement. Un seul `hooks` objet peut gérer les autorisations **et** l’audit **et** les notifications : chaque hook effectue son propre travail.\n\n```typescript\nconst session = await client.createSession({\n  hooks: {\n    onSessionStart: async (input) => {\n      console.log(`[audit] session started in ${input.workingDirectory}`);\n      return { additionalContext: \"Project uses TypeScript and Vitest.\" };\n    },\n    onPreToolUse: async (input) => {\n      console.log(`[audit] tool requested: ${input.toolName}`);\n      if (input.toolName === \"shell\") {\n        return { permissionDecision: \"ask\" };\n      }\n      return { permissionDecision: \"allow\" };\n    },\n    onPostToolUse: async (input) => {\n      console.log(`[audit] tool completed: ${input.toolName}`);\n      return null;\n    },\n    onErrorOccurred: async (input) => {\n      console.error(`[alert] ${input.errorContext}: ${input.error}`);\n      return null;\n    },\n    onSessionEnd: async (input, invocation) => {\n      console.log(\n        `[audit] session ${invocation.sessionId.slice(0, 8)} ended: ${input.reason}`,\n      );\n      return null;\n    },\n  },\n  onPermissionRequest: async () => ({ kind: \"approve-once\" }),\n});\n```\n\n## Bonnes pratiques\n\n1. **Gardez les crochets bien serrés.** Chaque hook s’exécute en ligne, les hooks lents retardent la conversation. Déchargez le travail lourd (écritures de base de données, appels HTTP) dans une file d’attente en arrière-plan si possible.\n\n2. **Retournez `null` quand vous n’avez rien à changer.** Cela indique au Kit de développement logiciel (SDK) de continuer avec les valeurs par défaut et d’éviter l’allocation inutile d’objets.\n\n3. **Soyez explicite avec les décisions d’autorisation.** Le retour `{ permissionDecision: \"allow\" }` est plus clair que le retour `null`, même si les deux autorisent l’outil.\n\n4. **N’avalez pas les erreurs critiques.** Il est judicieux de supprimer les erreurs d’outil récupérables, mais toujours journaliser ou alerter sur des erreurs irrécupérables.\n\n5. **Utilisez `additionalContext` plutôt `modifiedPrompt` que si possible.** L’ajout de contexte conserve l’intention d’origine de l’utilisateur tout en guidant le modèle.\n\n6. **État du scope par ID de session.** Si vous effectuez le suivi des données par session, utilisez `invocation.sessionId` comme clé et nettoyez dans `onSessionEnd`.\n\n## Reference\n\nPour obtenir des définitions de type complètes, des tables de champs d’entrée/sortie et des exemples supplémentaires pour chaque hook, consultez la référence de l’API :\n\n* [Hooks de session](/fr/copilot/how-tos/copilot-sdk/hooks/hooks-overview)\n* [Hook exécuté avant l’utilisation d’un outil](/fr/copilot/how-tos/copilot-sdk/hooks/pre-tool-use)\n* [Hook exécuté après l’utilisation d’un outil](/fr/copilot/how-tos/copilot-sdk/hooks/post-tool-use)\n* [Hook déclenché à la soumission d’un prompt utilisateur](/fr/copilot/how-tos/copilot-sdk/hooks/user-prompt-submitted)\n* [Hooks du cycle de vie de la session](/fr/copilot/how-tos/copilot-sdk/hooks/session-lifecycle)\n* [Hook de gestion des erreurs](/fr/copilot/how-tos/copilot-sdk/hooks/error-handling)\n\n## Voir aussi\n\n* [Créez votre première application avec Copilot](/fr/copilot/how-tos/copilot-sdk/getting-started)\n* [Agents personnalisés et orchestration de sous-agents](/fr/copilot/how-tos/copilot-sdk/features/custom-agents)\n* [Événements de session de streaming](/fr/copilot/how-tos/copilot-sdk/features/streaming-events)\n* [Guide de débogage](/fr/copilot/how-tos/copilot-sdk/troubleshooting/debugging)"}