1> ## Documentation Index
2> Fetch the complete documentation index at: https://code.claude.com/docs/llms.txt
3> Use this file to discover all available pages before exploring further.
4
5# Gérer les approbations et les entrées utilisateur
6
7> Présentez les demandes d'approbation et les questions de clarification de Claude aux utilisateurs, puis renvoyez leurs décisions au SDK.
8
9Lors du travail sur une tâche, Claude a parfois besoin de vérifier auprès des utilisateurs. Il peut avoir besoin d'une permission avant de supprimer des fichiers, ou avoir besoin de demander quelle base de données utiliser pour un nouveau projet. Votre application doit présenter ces demandes aux utilisateurs afin que Claude puisse continuer avec leurs entrées.
10
11Claude demande une entrée utilisateur dans deux situations : lorsqu'il a besoin d'une **permission pour utiliser un outil** (comme supprimer des fichiers ou exécuter des commandes), et lorsqu'il a des **questions de clarification** (via l'outil `AskUserQuestion`). Les deux déclenchent votre callback `canUseTool`, qui met en pause l'exécution jusqu'à ce que vous retourniez une réponse. C'est différent des tours de conversation normaux où Claude termine et attend votre prochain message.
12
13Pour les questions de clarification, Claude génère les questions et les options. Votre rôle est de les présenter aux utilisateurs et de retourner leurs sélections. Vous ne pouvez pas ajouter vos propres questions à ce flux ; si vous avez besoin de poser une question aux utilisateurs vous-même, faites-le séparément dans votre logique d'application.
14
15Le callback peut rester en attente indéfiniment. L'exécution reste en pause jusqu'à ce que votre callback retourne, et le SDK n'annule l'attente que lorsque la requête elle-même est annulée. Si un utilisateur pourrait prendre plus de temps pour répondre que votre processus ne peut raisonnablement rester en cours d'exécution, le SDK TypeScript supporte le [hook `defer` decision](/fr/hooks#defer-a-tool-call-for-later), qui permet au processus de quitter et de reprendre plus tard à partir de la session persistante ; cette option n'est pas disponible dans le SDK Python.
16
17Ce guide vous montre comment détecter chaque type de demande et répondre de manière appropriée.
18
19## Détecter quand Claude a besoin d'une entrée
20
21Passez un callback `canUseTool` dans vos options de requête. Le callback se déclenche chaque fois que Claude a besoin d'une entrée utilisateur, en recevant le nom de l'outil et l'entrée comme arguments :
22
23<CodeGroup>
24 ```python Python theme={null}
25 async def handle_tool_request(tool_name, input_data, context):
26 # Inviter l'utilisateur et retourner allow ou deny
27 ...
28
29
30 options = ClaudeAgentOptions(can_use_tool=handle_tool_request)
31 ```
32
33 ```typescript TypeScript theme={null}
34 async function handleToolRequest(toolName, input, options) {
35 // options includes { signal: AbortSignal, suggestions?: PermissionUpdate[] }
36 // Inviter l'utilisateur et retourner allow ou deny
37 }
38
39 const options = { canUseTool: handleToolRequest };
40 ```
41</CodeGroup>
42
43Le callback se déclenche dans deux cas :
44
451. **L'outil a besoin d'approbation** : Claude veut utiliser un outil qui n'est pas auto-approuvé par les [règles de permission](/fr/agent-sdk/permissions) ou les modes. Vérifiez `tool_name` pour l'outil (par exemple, `"Bash"`, `"Write"`).
462. **Claude pose une question** : Claude appelle l'outil `AskUserQuestion`. Vérifiez si `tool_name == "AskUserQuestion"` pour le gérer différemment. Si vous spécifiez un tableau `tools`, incluez `AskUserQuestion` pour que cela fonctionne. Voir [Gérer les questions de clarification](#handle-clarifying-questions) pour plus de détails.
47
48<Note>
49 Pour autoriser ou refuser automatiquement les outils sans inviter les utilisateurs, utilisez plutôt les [hooks](/fr/agent-sdk/hooks). Les hooks s'exécutent avant `canUseTool` et peuvent autoriser, refuser ou modifier les demandes en fonction de votre propre logique. Vous pouvez également utiliser le [hook `PermissionRequest`](/fr/agent-sdk/hooks#available-hooks) pour envoyer des notifications externes (Slack, email, push) lorsque Claude attend une approbation.
50</Note>
51
52## Gérer les demandes d'approbation d'outil
53
54Une fois que vous avez passé un callback `canUseTool` dans vos options de requête, il se déclenche lorsque Claude veut utiliser un outil qui n'est pas auto-approuvé. Votre callback reçoit trois arguments :
55
56| Argument | Description |
57| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
58| `toolName` | Le nom de l'outil que Claude veut utiliser (par exemple, `"Bash"`, `"Write"`, `"Edit"`) |
59| `input` | Les paramètres que Claude passe à l'outil. Le contenu varie selon l'outil. |
60| `options` (TS) / `context` (Python) | Contexte supplémentaire incluant des `suggestions` optionnelles (entrées `PermissionUpdate` proposées pour éviter de re-inviter) et un signal d'annulation. En TypeScript, `signal` est un `AbortSignal` ; en Python, le champ signal est réservé pour une utilisation future. Voir [`ToolPermissionContext`](/fr/agent-sdk/python#toolpermissioncontext) pour Python. |
61
62L'objet `input` contient des paramètres spécifiques à l'outil. Exemples courants :
63
64| Outil | Champs d'entrée |
65| ------- | --------------------------------------- |
66| `Bash` | `command`, `description`, `timeout` |
67| `Write` | `file_path`, `content` |
68| `Edit` | `file_path`, `old_string`, `new_string` |
69| `Read` | `file_path`, `offset`, `limit` |
70
71Voir la référence du SDK pour les schémas d'entrée complets : [Python](/fr/agent-sdk/python#tool-input%2Foutput-types) | [TypeScript](/fr/agent-sdk/typescript#tool-input-types).
72
73Vous pouvez afficher ces informations à l'utilisateur afin qu'il puisse décider d'autoriser ou de rejeter l'action, puis retourner la réponse appropriée.
74
75L'exemple suivant demande à Claude de créer et de supprimer un fichier de test. Lorsque Claude tente chaque opération, le callback imprime la demande d'outil au terminal et invite à une approbation y/n.
76
77<CodeGroup>
78 ```python Python theme={null}
79 import asyncio
80
81 from claude_agent_sdk import ClaudeAgentOptions, ResultMessage, query
82 from claude_agent_sdk.types import (
83 HookMatcher,
84 PermissionResultAllow,
85 PermissionResultDeny,
86 ToolPermissionContext,
87 )
88
89
90 async def can_use_tool(
91 tool_name: str, input_data: dict, context: ToolPermissionContext
92 ) -> PermissionResultAllow | PermissionResultDeny:
93 # Afficher la demande d'outil
94 print(f"\nTool: {tool_name}")
95 if tool_name == "Bash":
96 print(f"Command: {input_data.get('command')}")
97 if input_data.get("description"):
98 print(f"Description: {input_data.get('description')}")
99 else:
100 print(f"Input: {input_data}")
101
102 # Obtenir l'approbation de l'utilisateur
103 response = input("Allow this action? (y/n): ")
104
105 # Retourner allow ou deny en fonction de la réponse de l'utilisateur
106 if response.lower() == "y":
107 # Allow: l'outil s'exécute avec l'entrée originale (ou modifiée)
108 return PermissionResultAllow(updated_input=input_data)
109 else:
110 # Deny: l'outil ne s'exécute pas, Claude voit le message
111 return PermissionResultDeny(message="User denied this action")
112
113
114 # Contournement requis : un hook factice garde le flux ouvert pour can_use_tool
115 async def dummy_hook(input_data, tool_use_id, context):
116 return {"continue_": True}
117
118
119 async def prompt_stream():
120 yield {
121 "type": "user",
122 "message": {
123 "role": "user",
124 "content": "Create a test file in /tmp and then delete it",
125 },
126 }
127
128
129 async def main():
130 async for message in query(
131 prompt=prompt_stream(),
132 options=ClaudeAgentOptions(
133 can_use_tool=can_use_tool,
134 hooks={"PreToolUse": [HookMatcher(matcher=None, hooks=[dummy_hook])]},
135 ),
136 ):
137 if isinstance(message, ResultMessage) and message.subtype == "success":
138 print(message.result)
139
140
141 asyncio.run(main())
142 ```
143
144 ```typescript TypeScript theme={null}
145 import { query } from "@anthropic-ai/claude-agent-sdk";
146 import * as readline from "readline";
147
148 // Helper to prompt user for input in the terminal
149 function prompt(question: string): Promise<string> {
150 const rl = readline.createInterface({
151 input: process.stdin,
152 output: process.stdout
153 });
154 return new Promise((resolve) =>
155 rl.question(question, (answer) => {
156 rl.close();
157 resolve(answer);
158 })
159 );
160 }
161
162 for await (const message of query({
163 prompt: "Create a test file in /tmp and then delete it",
164 options: {
165 canUseTool: async (toolName, input) => {
166 // Afficher la demande d'outil
167 console.log(`\nTool: ${toolName}`);
168 if (toolName === "Bash") {
169 console.log(`Command: ${input.command}`);
170 if (input.description) console.log(`Description: ${input.description}`);
171 } else {
172 console.log(`Input: ${JSON.stringify(input, null, 2)}`);
173 }
174
175 // Obtenir l'approbation de l'utilisateur
176 const response = await prompt("Allow this action? (y/n): ");
177
178 // Retourner allow ou deny en fonction de la réponse de l'utilisateur
179 if (response.toLowerCase() === "y") {
180 // Allow: l'outil s'exécute avec l'entrée originale (ou modifiée)
181 return { behavior: "allow", updatedInput: input };
182 } else {
183 // Deny: l'outil ne s'exécute pas, Claude voit le message
184 return { behavior: "deny", message: "User denied this action" };
185 }
186 }
187 }
188 })) {
189 if ("result" in message) console.log(message.result);
190 }
191 ```
192</CodeGroup>
193
194<Note>
195 En Python, `can_use_tool` nécessite le [mode streaming](/fr/agent-sdk/streaming-vs-single-mode) et un hook `PreToolUse` qui retourne `{"continue_": True}` pour garder le flux ouvert. Sans ce hook, le flux se ferme avant que le callback de permission puisse être invoqué.
196</Note>
197
198Cet exemple utilise un flux y/n où toute entrée autre que `y` est traitée comme un refus. En pratique, vous pourriez construire une interface utilisateur plus riche qui permet aux utilisateurs de modifier la demande, de fournir des commentaires, ou de rediriger Claude entièrement. Voir [Répondre aux demandes d'outil](#respond-to-tool-requests) pour tous les moyens de répondre.
199
200### Répondre aux demandes d'outil
201
202Votre callback retourne l'un de deux types de réponse :
203
204| Réponse | Python | TypeScript |
205| --------- | ------------------------------------------ | ------------------------------------- |
206| **Allow** | `PermissionResultAllow(updated_input=...)` | `{ behavior: "allow", updatedInput }` |
207| **Deny** | `PermissionResultDeny(message=...)` | `{ behavior: "deny", message }` |
208
209Lors de l'autorisation, passez l'entrée de l'outil (originale ou modifiée). Lors du refus, fournissez un message expliquant pourquoi. Claude voit ce message et peut ajuster son approche.
210
211<CodeGroup>
212 ```python Python theme={null}
213 from claude_agent_sdk.types import PermissionResultAllow, PermissionResultDeny
214
215 # Autoriser l'outil à s'exécuter
216 return PermissionResultAllow(updated_input=input_data)
217
218 # Bloquer l'outil
219 return PermissionResultDeny(message="User rejected this action")
220 ```
221
222 ```typescript TypeScript theme={null}
223 // Autoriser l'outil à s'exécuter
224 return { behavior: "allow", updatedInput: input };
225
226 // Bloquer l'outil
227 return { behavior: "deny", message: "User rejected this action" };
228 ```
229</CodeGroup>
230
231Au-delà de l'autorisation ou du refus, vous pouvez modifier l'entrée de l'outil ou fournir un contexte qui aide Claude à ajuster son approche :
232
233* **Approuver** : laisser l'outil s'exécuter comme Claude l'a demandé
234* **Approuver avec des modifications** : modifier l'entrée avant l'exécution (par exemple, nettoyer les chemins, ajouter des contraintes)
235* **Rejeter** : bloquer l'outil et dire à Claude pourquoi
236* **Suggérer une alternative** : bloquer mais guider Claude vers ce que l'utilisateur veut à la place
237* **Rediriger entièrement** : utiliser [streaming input](/fr/agent-sdk/streaming-vs-single-mode) pour envoyer à Claude une instruction complètement nouvelle
238
239<Tabs>
240 <Tab title="Approuver">
241 L'utilisateur approuve l'action telle quelle. Passez l'`input` de votre callback inchangée et l'outil s'exécute exactement comme Claude l'a demandé.
242
243 <CodeGroup>
244 ```python Python theme={null}
245 async def can_use_tool(tool_name, input_data, context):
246 print(f"Claude wants to use {tool_name}")
247 approved = await ask_user("Allow this action?")
248
249 if approved:
250 return PermissionResultAllow(updated_input=input_data)
251 return PermissionResultDeny(message="User declined")
252 ```
253
254 ```typescript TypeScript theme={null}
255 canUseTool: async (toolName, input) => {
256 console.log(`Claude wants to use ${toolName}`);
257 const approved = await askUser("Allow this action?");
258
259 if (approved) {
260 return { behavior: "allow", updatedInput: input };
261 }
262 return { behavior: "deny", message: "User declined" };
263 };
264 ```
265 </CodeGroup>
266 </Tab>
267
268 <Tab title="Approuver avec des modifications">
269 L'utilisateur approuve mais veut d'abord modifier la demande. Vous pouvez modifier l'entrée avant l'exécution de l'outil. Claude voit le résultat mais n'est pas informé que vous avez changé quelque chose. Utile pour nettoyer les paramètres, ajouter des contraintes, ou limiter l'accès.
270
271 <CodeGroup>
272 ```python Python theme={null}
273 async def can_use_tool(tool_name, input_data, context):
274 if tool_name == "Bash":
275 # L'utilisateur a approuvé, mais limiter toutes les commandes au sandbox
276 sandboxed_input = {**input_data}
277 sandboxed_input["command"] = input_data["command"].replace(
278 "/tmp", "/tmp/sandbox"
279 )
280 return PermissionResultAllow(updated_input=sandboxed_input)
281 return PermissionResultAllow(updated_input=input_data)
282 ```
283
284 ```typescript TypeScript theme={null}
285 canUseTool: async (toolName, input) => {
286 if (toolName === "Bash") {
287 // L'utilisateur a approuvé, mais limiter toutes les commandes au sandbox
288 const sandboxedInput = {
289 ...input,
290 command: input.command.replace("/tmp", "/tmp/sandbox")
291 };
292 return { behavior: "allow", updatedInput: sandboxedInput };
293 }
294 return { behavior: "allow", updatedInput: input };
295 };
296 ```
297 </CodeGroup>
298 </Tab>
299
300 <Tab title="Rejeter">
301 L'utilisateur ne veut pas que cette action se produise. Bloquez l'outil et fournissez un message expliquant pourquoi. Claude voit ce message et peut essayer une approche différente.
302
303 <CodeGroup>
304 ```python Python theme={null}
305 async def can_use_tool(tool_name, input_data, context):
306 approved = await ask_user(f"Allow {tool_name}?")
307
308 if not approved:
309 return PermissionResultDeny(message="User rejected this action")
310 return PermissionResultAllow(updated_input=input_data)
311 ```
312
313 ```typescript TypeScript theme={null}
314 canUseTool: async (toolName, input) => {
315 const approved = await askUser(`Allow ${toolName}?`);
316
317 if (!approved) {
318 return {
319 behavior: "deny",
320 message: "User rejected this action"
321 };
322 }
323 return { behavior: "allow", updatedInput: input };
324 };
325 ```
326 </CodeGroup>
327 </Tab>
328
329 <Tab title="Suggérer une alternative">
330 L'utilisateur ne veut pas cette action spécifique, mais a une idée différente. Bloquez l'outil et incluez des conseils dans votre message. Claude lira ceci et décidera comment procéder en fonction de vos commentaires.
331
332 <CodeGroup>
333 ```python Python theme={null}
334 async def can_use_tool(tool_name, input_data, context):
335 if tool_name == "Bash" and "rm" in input_data.get("command", ""):
336 # L'utilisateur ne veut pas supprimer, suggérer d'archiver à la place
337 return PermissionResultDeny(
338 message="User doesn't want to delete files. They asked if you could compress them into an archive instead."
339 )
340 return PermissionResultAllow(updated_input=input_data)
341 ```
342
343 ```typescript TypeScript theme={null}
344 canUseTool: async (toolName, input) => {
345 if (toolName === "Bash" && input.command.includes("rm")) {
346 // L'utilisateur ne veut pas supprimer, suggérer d'archiver à la place
347 return {
348 behavior: "deny",
349 message:
350 "User doesn't want to delete files. They asked if you could compress them into an archive instead."
351 };
352 }
353 return { behavior: "allow", updatedInput: input };
354 };
355 ```
356 </CodeGroup>
357 </Tab>
358
359 <Tab title="Rediriger entièrement">
360 Pour un changement de direction complet (pas seulement une nudge), utilisez [streaming input](/fr/agent-sdk/streaming-vs-single-mode) pour envoyer à Claude une nouvelle instruction directement. Cela contourne la demande d'outil actuelle et donne à Claude des instructions entièrement nouvelles à suivre.
361 </Tab>
362</Tabs>
363
364## Gérer les questions de clarification
365
366Lorsque Claude a besoin de plus de direction sur une tâche avec plusieurs approches valides, il appelle l'outil `AskUserQuestion`. Cela déclenche votre callback `canUseTool` avec `toolName` défini sur `AskUserQuestion`. L'entrée contient les questions de Claude sous forme d'options à choix multiples, que vous affichez à l'utilisateur et retournez ses sélections.
367
368<Tip>
369 Les questions de clarification sont particulièrement courantes en [mode `plan`](/fr/agent-sdk/permissions#plan-mode-plan), où Claude explore la base de code et pose des questions avant de proposer un plan. Cela rend le mode plan idéal pour les flux de travail interactifs où vous voulez que Claude rassemble les exigences avant de faire des modifications.
370</Tip>
371
372Les étapes suivantes montrent comment gérer les questions de clarification :
373
374<Steps>
375 <Step title="Passer un callback canUseTool">
376 Passez un callback `canUseTool` dans vos options de requête. Par défaut, `AskUserQuestion` est disponible. Si vous spécifiez un tableau `tools` pour restreindre les capacités de Claude (par exemple, un agent en lecture seule avec seulement `Read`, `Glob`, et `Grep`), incluez `AskUserQuestion` dans ce tableau. Sinon, Claude ne pourra pas poser de questions de clarification :
377
378 <CodeGroup>
379 ```python Python theme={null}
380 async for message in query(
381 prompt="Analyze this codebase",
382 options=ClaudeAgentOptions(
383 # Inclure AskUserQuestion dans votre liste d'outils
384 tools=["Read", "Glob", "Grep", "AskUserQuestion"],
385 can_use_tool=can_use_tool,
386 ),
387 ):
388 print(message)
389 ```
390
391 ```typescript TypeScript theme={null}
392 for await (const message of query({
393 prompt: "Analyze this codebase",
394 options: {
395 // Inclure AskUserQuestion dans votre liste d'outils
396 tools: ["Read", "Glob", "Grep", "AskUserQuestion"],
397 canUseTool: async (toolName, input) => {
398 // Gérer les questions de clarification ici
399 }
400 }
401 })) {
402 console.log(message);
403 }
404 ```
405 </CodeGroup>
406 </Step>
407
408 <Step title="Détecter AskUserQuestion">
409 Dans votre callback, vérifiez si `toolName` est égal à `AskUserQuestion` pour le gérer différemment des autres outils :
410
411 <CodeGroup>
412 ```python Python theme={null}
413 async def can_use_tool(tool_name: str, input_data: dict, context):
414 if tool_name == "AskUserQuestion":
415 # Votre implémentation pour collecter les réponses de l'utilisateur
416 return await handle_clarifying_questions(input_data)
417 # Gérer les autres outils normalement
418 return await prompt_for_approval(tool_name, input_data)
419 ```
420
421 ```typescript TypeScript theme={null}
422 canUseTool: async (toolName, input) => {
423 if (toolName === "AskUserQuestion") {
424 // Votre implémentation pour collecter les réponses de l'utilisateur
425 return handleClarifyingQuestions(input);
426 }
427 // Gérer les autres outils normalement
428 return promptForApproval(toolName, input);
429 };
430 ```
431 </CodeGroup>
432 </Step>
433
434 <Step title="Analyser l'entrée de la question">
435 L'entrée contient les questions de Claude dans un tableau `questions`. Chaque question a une `question` (le texte à afficher), des `options` (les choix), et `multiSelect` (si plusieurs sélections sont autorisées) :
436
437 ```json theme={null}
438 {
439 "questions": [
440 {
441 "question": "How should I format the output?",
442 "header": "Format",
443 "options": [
444 { "label": "Summary", "description": "Brief overview" },
445 { "label": "Detailed", "description": "Full explanation" }
446 ],
447 "multiSelect": false
448 },
449 {
450 "question": "Which sections should I include?",
451 "header": "Sections",
452 "options": [
453 { "label": "Introduction", "description": "Opening context" },
454 { "label": "Conclusion", "description": "Final summary" }
455 ],
456 "multiSelect": true
457 }
458 ]
459 }
460 ```
461
462 Voir [Format de question](#question-format) pour les descriptions complètes des champs.
463 </Step>
464
465 <Step title="Collecter les réponses de l'utilisateur">
466 Présentez les questions à l'utilisateur et collectez ses sélections. La façon dont vous le faites dépend de votre application : une invite de terminal, un formulaire web, un dialogue mobile, etc.
467 </Step>
468
469 <Step title="Retourner les réponses à Claude">
470 Construisez l'objet `answers` comme un enregistrement où chaque clé est le texte de `question` et chaque valeur est le `label` de l'option sélectionnée :
471
472 | De l'objet question | Utiliser comme |
473 | ------------------------------------------------------------------- | -------------- |
474 | Champ `question` (par exemple, `"How should I format the output?"`) | Clé |
475 | Champ `label` de l'option sélectionnée (par exemple, `"Summary"`) | Valeur |
476
477 Pour les questions multi-sélection, passez un tableau de labels ou joignez-les avec `", "`. Si vous [supportez l'entrée de texte libre](#support-free-text-input), utilisez le texte personnalisé de l'utilisateur comme valeur.
478
479 <CodeGroup>
480 ```python Python theme={null}
481 return PermissionResultAllow(
482 updated_input={
483 "questions": input_data.get("questions", []),
484 "answers": {
485 "How should I format the output?": "Summary",
486 "Which sections should I include?": ["Introduction", "Conclusion"],
487 },
488 }
489 )
490 ```
491
492 ```typescript TypeScript theme={null}
493 return {
494 behavior: "allow",
495 updatedInput: {
496 questions: input.questions,
497 answers: {
498 "How should I format the output?": "Summary",
499 "Which sections should I include?": "Introduction, Conclusion"
500 }
501 }
502 };
503 ```
504 </CodeGroup>
505 </Step>
506</Steps>
507
508### Format de question
509
510L'entrée contient les questions générées par Claude dans un tableau `questions`. Chaque question a ces champs :
511
512| Champ | Description |
513| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
514| `question` | Le texte complet de la question à afficher |
515| `header` | Étiquette courte pour la question (max 12 caractères) |
516| `options` | Tableau de 2-4 choix, chacun avec `label` et `description`. TypeScript : optionnellement `preview` (voir [ci-dessous](#option-previews-type-script)) |
517| `multiSelect` | Si `true`, les utilisateurs peuvent sélectionner plusieurs options |
518
519La structure que votre callback reçoit :
520
521```json theme={null}
522{
523 "questions": [
524 {
525 "question": "How should I format the output?",
526 "header": "Format",
527 "options": [
528 { "label": "Summary", "description": "Brief overview of key points" },
529 { "label": "Detailed", "description": "Full explanation with examples" }
530 ],
531 "multiSelect": false
532 }
533 ]
534}
535```
536
537#### Aperçus d'options (TypeScript)
538
539`toolConfig.askUserQuestion.previewFormat` ajoute un champ `preview` à chaque option afin que votre application puisse afficher une maquette visuelle à côté du label. Sans ce paramètre, Claude ne génère pas d'aperçus et le champ est absent.
540
541| `previewFormat` | `preview` contient |
542| :---------------------- | :------------------------------------------------------------------------------------------------------------------------ |
543| non défini (par défaut) | Le champ est absent. Claude ne génère pas d'aperçus. |
544| `"markdown"` | Art ASCII et blocs de code clôturés |
545| `"html"` | Un fragment `<div>` stylisé (le SDK rejette `<script>`, `<style>`, et `<!DOCTYPE>` avant que votre callback ne s'exécute) |
546
547Le format s'applique à toutes les questions de la session. Claude inclut `preview` sur les options où une comparaison visuelle aide (choix de mise en page, schémas de couleurs) et l'omet où ce ne serait pas le cas (confirmations oui/non, choix texte uniquement). Vérifiez `undefined` avant de rendre.
548
549```typescript theme={null}
550import { query } from "@anthropic-ai/claude-agent-sdk";
551
552for await (const message of query({
553 prompt: "Help me choose a card layout",
554 options: {
555 toolConfig: {
556 askUserQuestion: { previewFormat: "html" }
557 },
558 canUseTool: async (toolName, input) => {
559 // input.questions[].options[].preview is an HTML string or undefined
560 return { behavior: "allow", updatedInput: input };
561 }
562 }
563})) {
564 // ...
565}
566```
567
568Une option avec un aperçu HTML :
569
570```json theme={null}
571{
572 "label": "Compact",
573 "description": "Title and metric value only",
574 "preview": "<div style=\"padding:12px;border:1px solid #ddd;border-radius:8px\"><div style=\"font-size:12px;color:#666\">Active users</div><div style=\"font-size:28px;font-weight:600\">1,284</div></div>"
575}
576```
577
578### Format de réponse
579
580Retournez un objet `answers` mappant le champ `question` de chaque question au `label` de l'option sélectionnée :
581
582| Champ | Description |
583| ----------- | ------------------------------------------------------------------------------------------ |
584| `questions` | Passez le tableau de questions original (requis pour le traitement de l'outil) |
585| `answers` | Objet où les clés sont le texte de la question et les valeurs sont les labels sélectionnés |
586
587Pour les questions multi-sélection, passez un tableau de labels ou joignez-les avec `", "`. Pour l'entrée de texte libre, utilisez le texte personnalisé de l'utilisateur directement.
588
589```json theme={null}
590{
591 "questions": [
592 // ...
593 ],
594 "answers": {
595 "How should I format the output?": "Summary",
596 "Which sections should I include?": ["Introduction", "Conclusion"]
597 }
598}
599```
600
601#### Supporter l'entrée de texte libre
602
603Les options prédéfinies de Claude ne couvriront pas toujours ce que les utilisateurs veulent. Pour permettre aux utilisateurs de taper leur propre réponse :
604
605* Affichez un choix supplémentaire « Autre » après les options de Claude qui accepte l'entrée de texte
606* Utilisez le texte personnalisé de l'utilisateur comme valeur de réponse (pas le mot « Autre »)
607
608Voir l'[exemple complet](#complete-example) ci-dessous pour une implémentation complète.
609
610### Exemple complet
611
612Claude pose des questions de clarification lorsqu'il a besoin d'une entrée utilisateur pour continuer. Par exemple, lorsqu'on lui demande d'aider à décider d'une pile technologique pour une application mobile, Claude pourrait poser des questions sur cross-platform vs native, les préférences de backend, ou les plates-formes cibles. Ces questions aident Claude à prendre des décisions qui correspondent aux préférences de l'utilisateur plutôt que de deviner.
613
614Cet exemple gère ces questions dans une application de terminal. Voici ce qui se passe à chaque étape :
615
6161. **Router la demande** : Le callback `canUseTool` vérifie si le nom de l'outil est `"AskUserQuestion"` et route vers un gestionnaire dédié
6172. **Afficher les questions** : Le gestionnaire boucle à travers le tableau `questions` et imprime chaque question avec des options numérotées
6183. **Collecter l'entrée** : L'utilisateur peut entrer un numéro pour sélectionner une option, ou taper du texte libre directement (par exemple, « jquery », « je ne sais pas »)
6194. **Mapper les réponses** : Le code vérifie si l'entrée est numérique (utilise le label de l'option) ou du texte libre (utilise le texte directement)
6205. **Retourner à Claude** : La réponse inclut à la fois le tableau `questions` original et le mapping `answers`
621
622<CodeGroup>
623 ```python Python theme={null}
624 import asyncio
625
626 from claude_agent_sdk import ClaudeAgentOptions, ResultMessage, query
627 from claude_agent_sdk.types import HookMatcher, PermissionResultAllow
628
629
630 def parse_response(response: str, options: list) -> str:
631 """Analyser l'entrée utilisateur comme numéro(s) d'option ou texte libre."""
632 try:
633 indices = [int(s.strip()) - 1 for s in response.split(",")]
634 labels = [options[i]["label"] for i in indices if 0 <= i < len(options)]
635 return ", ".join(labels) if labels else response
636 except ValueError:
637 return response
638
639
640 async def handle_ask_user_question(input_data: dict) -> PermissionResultAllow:
641 """Afficher les questions de Claude et collecter les réponses de l'utilisateur."""
642 answers = {}
643
644 for q in input_data.get("questions", []):
645 print(f"\n{q['header']}: {q['question']}")
646
647 options = q["options"]
648 for i, opt in enumerate(options):
649 print(f" {i + 1}. {opt['label']} - {opt['description']}")
650 if q.get("multiSelect"):
651 print(" (Enter numbers separated by commas, or type your own answer)")
652 else:
653 print(" (Enter a number, or type your own answer)")
654
655 response = input("Your choice: ").strip()
656 answers[q["question"]] = parse_response(response, options)
657
658 return PermissionResultAllow(
659 updated_input={
660 "questions": input_data.get("questions", []),
661 "answers": answers,
662 }
663 )
664
665
666 async def can_use_tool(
667 tool_name: str, input_data: dict, context
668 ) -> PermissionResultAllow:
669 # Router AskUserQuestion vers notre gestionnaire de questions
670 if tool_name == "AskUserQuestion":
671 return await handle_ask_user_question(input_data)
672 # Auto-approuver les autres outils pour cet exemple
673 return PermissionResultAllow(updated_input=input_data)
674
675
676 async def prompt_stream():
677 yield {
678 "type": "user",
679 "message": {
680 "role": "user",
681 "content": "Help me decide on the tech stack for a new mobile app",
682 },
683 }
684
685
686 # Contournement requis : un hook factice garde le flux ouvert pour can_use_tool
687 async def dummy_hook(input_data, tool_use_id, context):
688 return {"continue_": True}
689
690
691 async def main():
692 async for message in query(
693 prompt=prompt_stream(),
694 options=ClaudeAgentOptions(
695 can_use_tool=can_use_tool,
696 hooks={"PreToolUse": [HookMatcher(matcher=None, hooks=[dummy_hook])]},
697 ),
698 ):
699 if isinstance(message, ResultMessage) and message.subtype == "success":
700 print(message.result)
701
702
703 asyncio.run(main())
704 ```
705
706 ```typescript TypeScript theme={null}
707 import { query } from "@anthropic-ai/claude-agent-sdk";
708 import * as readline from "readline/promises";
709
710 // Helper to prompt user for input in the terminal
711 async function prompt(question: string): Promise<string> {
712 const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
713 const answer = await rl.question(question);
714 rl.close();
715 return answer;
716 }
717
718 // Parse user input as option number(s) or free text
719 function parseResponse(response: string, options: any[]): string {
720 const indices = response.split(",").map((s) => parseInt(s.trim()) - 1);
721 const labels = indices
722 .filter((i) => !isNaN(i) && i >= 0 && i < options.length)
723 .map((i) => options[i].label);
724 return labels.length > 0 ? labels.join(", ") : response;
725 }
726
727 // Display Claude's questions and collect user answers
728 async function handleAskUserQuestion(input: any) {
729 const answers: Record<string, string> = {};
730
731 for (const q of input.questions) {
732 console.log(`\n${q.header}: ${q.question}`);
733
734 const options = q.options;
735 options.forEach((opt: any, i: number) => {
736 console.log(` ${i + 1}. ${opt.label} - ${opt.description}`);
737 });
738 if (q.multiSelect) {
739 console.log(" (Enter numbers separated by commas, or type your own answer)");
740 } else {
741 console.log(" (Enter a number, or type your own answer)");
742 }
743
744 const response = (await prompt("Your choice: ")).trim();
745 answers[q.question] = parseResponse(response, options);
746 }
747
748 // Return the answers to Claude (must include original questions)
749 return {
750 behavior: "allow",
751 updatedInput: { questions: input.questions, answers }
752 };
753 }
754
755 async function main() {
756 for await (const message of query({
757 prompt: "Help me decide on the tech stack for a new mobile app",
758 options: {
759 canUseTool: async (toolName, input) => {
760 // Router AskUserQuestion vers notre gestionnaire de questions
761 if (toolName === "AskUserQuestion") {
762 return handleAskUserQuestion(input);
763 }
764 // Auto-approuver les autres outils pour cet exemple
765 return { behavior: "allow", updatedInput: input };
766 }
767 }
768 })) {
769 if ("result" in message) console.log(message.result);
770 }
771 }
772
773 main();
774 ```
775</CodeGroup>
776
777## Limitations
778
779* **Subagents** : `AskUserQuestion` n'est actuellement pas disponible dans les subagents générés via l'outil Agent
780* **Limites de questions** : chaque appel `AskUserQuestion` supporte 1-4 questions avec 2-4 options chacune
781
782## Autres façons d'obtenir une entrée utilisateur
783
784Le callback `canUseTool` et l'outil `AskUserQuestion` couvrent la plupart des scénarios d'approbation et de clarification, mais le SDK offre d'autres façons d'obtenir une entrée des utilisateurs :
785
786### Streaming input
787
788Utilisez [streaming input](/fr/agent-sdk/streaming-vs-single-mode) lorsque vous avez besoin de :
789
790* **Interrompre l'agent en cours de tâche** : envoyer un signal d'annulation ou changer de direction pendant que Claude travaille
791* **Fournir un contexte supplémentaire** : ajouter des informations dont Claude a besoin sans attendre qu'il les demande
792* **Construire des interfaces de chat** : permettre aux utilisateurs d'envoyer des messages de suivi pendant les opérations longues
793
794Streaming input est idéal pour les interfaces conversationnelles où les utilisateurs interagissent avec l'agent tout au long de l'exécution, pas seulement aux points d'approbation.
795
796### Outils personnalisés
797
798Utilisez [outils personnalisés](/fr/agent-sdk/custom-tools) lorsque vous avez besoin de :
799
800* **Collecter une entrée structurée** : construire des formulaires, des assistants, ou des flux de travail multi-étapes qui vont au-delà du format à choix multiples de `AskUserQuestion`
801* **Intégrer des systèmes d'approbation externes** : se connecter à des plates-formes de ticketing, de flux de travail, ou d'approbation existantes
802* **Implémenter des interactions spécifiques au domaine** : créer des outils adaptés aux besoins de votre application, comme des interfaces d'examen de code ou des listes de contrôle de déploiement
803
804Les outils personnalisés vous donnent un contrôle total sur l'interaction, mais nécessitent plus de travail d'implémentation que d'utiliser le callback `canUseTool` intégré.
805
806## Ressources connexes
807
808* [Configurer les permissions](/fr/agent-sdk/permissions) : configurer les modes et règles de permission
809* [Contrôler l'exécution avec les hooks](/fr/agent-sdk/hooks) : exécuter du code personnalisé à des points clés du cycle de vie de l'agent
810* [Référence du SDK TypeScript](/fr/agent-sdk/typescript#canusetool) : documentation complète de l'API canUseTool