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# Gestionar aprobaciones e entrada de usuario
6
7> Presente las solicitudes de aprobación y preguntas aclaratorias de Claude a los usuarios, luego devuelva sus decisiones al SDK.
8
9Mientras trabaja en una tarea, Claude a veces necesita consultar con los usuarios. Podría necesitar permiso antes de eliminar archivos, o necesitar preguntar qué base de datos usar para un nuevo proyecto. Su aplicación necesita presentar estas solicitudes a los usuarios para que Claude pueda continuar con su entrada.
10
11Claude solicita entrada del usuario en dos situaciones: cuando necesita **permiso para usar una herramienta** (como eliminar archivos o ejecutar comandos), y cuando tiene **preguntas aclaratorias** (a través de la herramienta `AskUserQuestion`). Ambas activan su callback `canUseTool`, que pausa la ejecución hasta que devuelva una respuesta. Esto es diferente de los turnos de conversación normales donde Claude termina y espera su próximo mensaje.
12
13Para preguntas aclaratorias, Claude genera las preguntas y opciones. Su función es presentarlas a los usuarios y devolver sus selecciones. No puede agregar sus propias preguntas a este flujo; si necesita preguntarle algo a los usuarios usted mismo, hágalo por separado en la lógica de su aplicación.
14
15El callback puede permanecer pendiente indefinidamente. La ejecución permanece pausada hasta que su callback regrese, y el SDK solo cancela la espera cuando la consulta misma se cancela. Si un usuario podría tardar más en responder de lo que su proceso puede razonablemente mantenerse ejecutando, el SDK de TypeScript admite el [`defer` hook decision](/es/hooks#defer-a-tool-call-for-later), que permite que el proceso salga y se reanude más tarde desde la sesión persistida; esta opción no está disponible en el SDK de Python.
16
17Esta guía le muestra cómo detectar cada tipo de solicitud y responder apropiadamente.
18
19## Detectar cuándo Claude necesita entrada
20
21Pase un callback `canUseTool` en sus opciones de consulta. El callback se activa cada vez que Claude necesita entrada del usuario, recibiendo el nombre de la herramienta y la entrada como argumentos:
22
23<CodeGroup>
24 ```python Python theme={null}
25 async def handle_tool_request(tool_name, input_data, context):
26 # Solicitar al usuario y devolver permitir o denegar
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 // Solicitar al usuario y devolver permitir o denegar
37 }
38
39 const options = { canUseTool: handleToolRequest };
40 ```
41</CodeGroup>
42
43El callback se activa en dos casos:
44
451. **La herramienta necesita aprobación**: Claude quiere usar una herramienta que no está aprobada automáticamente por [reglas de permisos](/es/agent-sdk/permissions) o modos. Verifique `tool_name` para la herramienta (por ejemplo, `"Bash"`, `"Write"`).
462. **Claude hace una pregunta**: Claude llama a la herramienta `AskUserQuestion`. Verifique si `tool_name == "AskUserQuestion"` para manejarlo de manera diferente. Si especifica un array `tools`, incluya `AskUserQuestion` para que esto funcione. Vea [Manejar preguntas aclaratorias](#handle-clarifying-questions) para más detalles.
47
48<Note>
49 Para permitir o denegar automáticamente herramientas sin solicitar a los usuarios, use [hooks](/es/agent-sdk/hooks) en su lugar. Los hooks se ejecutan antes de `canUseTool` y pueden permitir, denegar o modificar solicitudes según su propia lógica. También puede usar el [`PermissionRequest` hook](/es/agent-sdk/hooks#available-hooks) para enviar notificaciones externas (Slack, correo electrónico, push) cuando Claude está esperando aprobación.
50</Note>
51
52## Manejar solicitudes de aprobación de herramientas
53
54Una vez que haya pasado un callback `canUseTool` en sus opciones de consulta, se activa cuando Claude quiere usar una herramienta que no está aprobada automáticamente. Su callback recibe tres argumentos:
55
56| Argumento | Descripción |
57| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
58| `toolName` | El nombre de la herramienta que Claude quiere usar (por ejemplo, `"Bash"`, `"Write"`, `"Edit"`) |
59| `input` | Los parámetros que Claude está pasando a la herramienta. El contenido varía según la herramienta. |
60| `options` (TS) / `context` (Python) | Contexto adicional incluyendo `suggestions` opcional (entradas `PermissionUpdate` propuestas para evitar re-solicitar) y una señal de cancelación. En TypeScript, `signal` es un `AbortSignal`; en Python, el campo de señal está reservado para uso futuro. Vea [`ToolPermissionContext`](/es/agent-sdk/python#toolpermissioncontext) para Python. |
61
62El objeto `input` contiene parámetros específicos de la herramienta. Ejemplos comunes:
63
64| Herramienta | Campos de entrada |
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
71Vea la referencia del SDK para esquemas de entrada completos: [Python](/es/agent-sdk/python#tool-input%2Foutput-types) | [TypeScript](/es/agent-sdk/typescript#tool-input-types).
72
73Puede mostrar esta información al usuario para que pueda decidir si permitir o rechazar la acción, luego devolver la respuesta apropiada.
74
75El siguiente ejemplo le pide a Claude que cree y elimine un archivo de prueba. Cuando Claude intenta cada operación, el callback imprime la solicitud de herramienta en la terminal y solicita aprobación s/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 # Mostrar la solicitud de herramienta
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 # Obtener aprobación del usuario
103 response = input("Allow this action? (y/n): ")
104
105 # Devolver permitir o denegar según la respuesta del usuario
106 if response.lower() == "y":
107 # Permitir: la herramienta se ejecuta con la entrada original (o modificada)
108 return PermissionResultAllow(updated_input=input_data)
109 else:
110 # Denegar: la herramienta no se ejecuta, Claude ve el mensaje
111 return PermissionResultDeny(message="User denied this action")
112
113
114 # Solución requerida: hook ficticio mantiene el flujo abierto para 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 para solicitar entrada del usuario en la 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 // Mostrar la solicitud de herramienta
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 // Obtener aprobación del usuario
176 const response = await prompt("Allow this action? (y/n): ");
177
178 // Devolver permitir o denegar según la respuesta del usuario
179 if (response.toLowerCase() === "y") {
180 // Permitir: la herramienta se ejecuta con la entrada original (o modificada)
181 return { behavior: "allow", updatedInput: input };
182 } else {
183 // Denegar: la herramienta no se ejecuta, Claude ve el mensaje
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` requiere [modo de flujo](/es/agent-sdk/streaming-vs-single-mode) y un hook `PreToolUse` que devuelva `{"continue_": True}` para mantener el flujo abierto. Sin este hook, el flujo se cierra antes de que se pueda invocar el callback de permiso.
196</Note>
197
198Este ejemplo usa un flujo s/n donde cualquier entrada que no sea `y` se trata como una denegación. En la práctica, podría construir una interfaz de usuario más rica que permita a los usuarios modificar la solicitud, proporcionar retroalimentación o redirigir a Claude completamente. Vea [Responder a solicitudes de herramientas](#respond-to-tool-requests) para todas las formas en que puede responder.
199
200### Responder a solicitudes de herramientas
201
202Su callback devuelve uno de dos tipos de respuesta:
203
204| Respuesta | Python | TypeScript |
205| ------------ | ------------------------------------------ | ------------------------------------- |
206| **Permitir** | `PermissionResultAllow(updated_input=...)` | `{ behavior: "allow", updatedInput }` |
207| **Denegar** | `PermissionResultDeny(message=...)` | `{ behavior: "deny", message }` |
208
209Al permitir, pase la entrada de la herramienta (original o modificada). Al denegar, proporcione un mensaje explicando por qué. Claude ve este mensaje y puede ajustar su enfoque.
210
211<CodeGroup>
212 ```python Python theme={null}
213 from claude_agent_sdk.types import PermissionResultAllow, PermissionResultDeny
214
215 # Permitir que la herramienta se ejecute
216 return PermissionResultAllow(updated_input=input_data)
217
218 # Bloquear la herramienta
219 return PermissionResultDeny(message="User rejected this action")
220 ```
221
222 ```typescript TypeScript theme={null}
223 // Permitir que la herramienta se ejecute
224 return { behavior: "allow", updatedInput: input };
225
226 // Bloquear la herramienta
227 return { behavior: "deny", message: "User rejected this action" };
228 ```
229</CodeGroup>
230
231Más allá de permitir o denegar, puede modificar la entrada de la herramienta o proporcionar contexto que ayude a Claude a ajustar su enfoque:
232
233* **Aprobar**: permitir que la herramienta se ejecute como Claude solicitó
234* **Aprobar con cambios**: modificar la entrada antes de la ejecución (por ejemplo, desinfectar rutas, agregar restricciones)
235* **Rechazar**: bloquear la herramienta y decirle a Claude por qué
236* **Sugerir alternativa**: bloquear pero guiar a Claude hacia lo que el usuario quiere en su lugar
237* **Redirigir completamente**: usar [entrada de flujo](/es/agent-sdk/streaming-vs-single-mode) para enviar a Claude una instrucción completamente nueva
238
239<Tabs>
240 <Tab title="Aprobar">
241 El usuario aprueba la acción tal como está. Pase la `input` de su callback sin cambios y la herramienta se ejecuta exactamente como Claude solicitó.
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="Aprobar con cambios">
269 El usuario aprueba pero quiere modificar la solicitud primero. Puede cambiar la entrada antes de que la herramienta se ejecute. Claude ve el resultado pero no se le dice que cambió nada. Útil para desinfectar parámetros, agregar restricciones o limitar el acceso.
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 # Usuario aprobó, pero limita todos los comandos a 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 // Usuario aprobó, pero limita todos los comandos a 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="Rechazar">
301 El usuario no quiere que esta acción suceda. Bloquee la herramienta y proporcione un mensaje explicando por qué. Claude ve este mensaje y puede intentar un enfoque diferente.
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="Sugerir alternativa">
330 El usuario no quiere esta acción específica, pero tiene una idea diferente. Bloquee la herramienta e incluya orientación en su mensaje. Claude leerá esto y decidirá cómo proceder según su retroalimentación.
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 # El usuario no quiere eliminar, sugiera archivar en su lugar
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 // El usuario no quiere eliminar, sugiera archivar en su lugar
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="Redirigir completamente">
360 Para un cambio de dirección completo (no solo un empujón), use [entrada de flujo](/es/agent-sdk/streaming-vs-single-mode) para enviar a Claude una nueva instrucción directamente. Esto evita la solicitud de herramienta actual y le da a Claude instrucciones completamente nuevas para seguir.
361 </Tab>
362</Tabs>
363
364## Manejar preguntas aclaratorias
365
366Cuando Claude necesita más dirección en una tarea con múltiples enfoques válidos, llama a la herramienta `AskUserQuestion`. Esto activa su callback `canUseTool` con `toolName` establecido en `AskUserQuestion`. La entrada contiene las preguntas de Claude como opciones de opción múltiple, que muestra al usuario y devuelve sus selecciones.
367
368<Tip>
369 Las preguntas aclaratorias son especialmente comunes en [`plan` mode](/es/agent-sdk/permissions#plan-mode-plan), donde Claude explora la base de código y hace preguntas antes de proponer un plan. Esto hace que el modo plan sea ideal para flujos de trabajo interactivos donde desea que Claude recopile requisitos antes de hacer cambios.
370</Tip>
371
372Los siguientes pasos muestran cómo manejar preguntas aclaratorias:
373
374<Steps>
375 <Step title="Pasar un callback canUseTool">
376 Pase un callback `canUseTool` en sus opciones de consulta. De forma predeterminada, `AskUserQuestion` está disponible. Si especifica un array `tools` para restringir las capacidades de Claude (por ejemplo, un agente de solo lectura con solo `Read`, `Glob` y `Grep`), incluya `AskUserQuestion` en ese array. De lo contrario, Claude no podrá hacer preguntas aclaratorias:
377
378 <CodeGroup>
379 ```python Python theme={null}
380 async for message in query(
381 prompt="Analyze this codebase",
382 options=ClaudeAgentOptions(
383 # Incluya AskUserQuestion en su lista de herramientas
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 // Incluya AskUserQuestion en su lista de herramientas
396 tools: ["Read", "Glob", "Grep", "AskUserQuestion"],
397 canUseTool: async (toolName, input) => {
398 // Manejar preguntas aclaratorias aquí
399 }
400 }
401 })) {
402 console.log(message);
403 }
404 ```
405 </CodeGroup>
406 </Step>
407
408 <Step title="Detectar AskUserQuestion">
409 En su callback, verifique si `toolName` es igual a `AskUserQuestion` para manejarlo de manera diferente a otras herramientas:
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 # Su implementación para recopilar respuestas del usuario
416 return await handle_clarifying_questions(input_data)
417 # Manejar otras herramientas normalmente
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 // Su implementación para recopilar respuestas del usuario
425 return handleClarifyingQuestions(input);
426 }
427 // Manejar otras herramientas normalmente
428 return promptForApproval(toolName, input);
429 };
430 ```
431 </CodeGroup>
432 </Step>
433
434 <Step title="Analizar la entrada de la pregunta">
435 La entrada contiene las preguntas de Claude en un array `questions`. Cada pregunta tiene una `question` (el texto a mostrar), `options` (las opciones) y `multiSelect` (si se permiten múltiples selecciones):
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 Vea [Formato de pregunta](#question-format) para descripciones completas de campos.
463 </Step>
464
465 <Step title="Recopilar respuestas del usuario">
466 Presente las preguntas al usuario y recopile sus selecciones. Cómo lo hace depende de su aplicación: un indicador de terminal, un formulario web, un diálogo móvil, etc.
467 </Step>
468
469 <Step title="Devolver respuestas a Claude">
470 Construya el objeto `answers` como un registro donde cada clave es el texto `question` y cada valor es la `label` de la opción seleccionada:
471
472 | Del objeto de pregunta | Usar como |
473 | ------------------------------------------------------------------- | --------- |
474 | Campo `question` (por ejemplo, `"How should I format the output?"`) | Clave |
475 | Campo `label` de la opción seleccionada (por ejemplo, `"Summary"`) | Valor |
476
477 Para preguntas de selección múltiple, pase un array de etiquetas o únalas con `", "`. Si [admite entrada de texto libre](#support-free-text-input), use el texto personalizado del usuario como valor.
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### Formato de pregunta
509
510La entrada contiene las preguntas generadas por Claude en un array `questions`. Cada pregunta tiene estos campos:
511
512| Campo | Descripción |
513| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
514| `question` | El texto completo de la pregunta a mostrar |
515| `header` | Etiqueta corta para la pregunta (máximo 12 caracteres) |
516| `options` | Array de 2-4 opciones, cada una con `label` y `description`. TypeScript: opcionalmente `preview` (vea [abajo](#option-previews-type-script)) |
517| `multiSelect` | Si es `true`, los usuarios pueden seleccionar múltiples opciones |
518
519La estructura que su callback recibe:
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#### Vistas previas de opciones (TypeScript)
538
539`toolConfig.askUserQuestion.previewFormat` agrega un campo `preview` a cada opción para que su aplicación pueda mostrar una maqueta visual junto a la etiqueta. Sin esta configuración, Claude no genera vistas previas y el campo está ausente.
540
541| `previewFormat` | `preview` contiene |
542| :------------------------------ | :------------------------------------------------------------------------------------------------------------------------ |
543| sin establecer (predeterminado) | El campo está ausente. Claude no genera vistas previas. |
544| `"markdown"` | Arte ASCII y bloques de código cercados |
545| `"html"` | Un fragmento `<div>` con estilo (el SDK rechaza `<script>`, `<style>` y `<!DOCTYPE>` antes de que su callback se ejecute) |
546
547El formato se aplica a todas las preguntas en la sesión. Claude incluye `preview` en opciones donde una comparación visual ayuda (opciones de diseño, esquemas de color) y la omite donde no lo haría (confirmaciones sí/no, opciones de solo texto). Verifique `undefined` antes de renderizar.
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 es una cadena HTML o undefined
560 return { behavior: "allow", updatedInput: input };
561 }
562 }
563})) {
564 // ...
565}
566```
567
568Una opción con una vista previa 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### Formato de respuesta
579
580Devuelva un objeto `answers` que asigne cada campo `question` de la pregunta a la `label` de la opción seleccionada:
581
582| Campo | Descripción |
583| ----------- | --------------------------------------------------------------------------------------- |
584| `questions` | Pase el array de preguntas original (requerido para el procesamiento de herramientas) |
585| `answers` | Objeto donde las claves son texto de pregunta y los valores son etiquetas seleccionadas |
586
587Para preguntas de selección múltiple, pase un array de etiquetas o únalas con `", "`. Para entrada de texto libre, use el texto personalizado del usuario directamente.
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#### Admitir entrada de texto libre
602
603Las opciones predefinidas de Claude no siempre cubrirán lo que los usuarios quieren. Para permitir que los usuarios escriban su propia respuesta:
604
605* Muestre una opción "Otro" adicional después de las opciones de Claude que acepte entrada de texto
606* Use el texto personalizado del usuario como valor de respuesta (no la palabra "Otro")
607
608Vea el [ejemplo completo](#complete-example) a continuación para una implementación completa.
609
610### Ejemplo completo
611
612Claude hace preguntas aclaratorias cuando necesita entrada del usuario para proceder. Por ejemplo, cuando se le pide que ayude a decidir sobre una pila de tecnología para una aplicación móvil, Claude podría preguntar sobre multiplataforma vs nativo, preferencias de backend o plataformas objetivo. Estas preguntas ayudan a Claude a tomar decisiones que coincidan con las preferencias del usuario en lugar de adivinar.
613
614Este ejemplo maneja esas preguntas en una aplicación de terminal. Esto es lo que sucede en cada paso:
615
6161. **Enrutar la solicitud**: El callback `canUseTool` verifica si el nombre de la herramienta es `"AskUserQuestion"` y enruta a un manejador dedicado
6172. **Mostrar preguntas**: El manejador recorre el array `questions` e imprime cada pregunta con opciones numeradas
6183. **Recopilar entrada**: El usuario puede ingresar un número para seleccionar una opción, o escribir texto libre directamente (por ejemplo, "jquery", "i don't know")
6194. **Asignar respuestas**: El código verifica si la entrada es numérica (usa la etiqueta de la opción) o texto libre (usa el texto directamente)
6205. **Devolver a Claude**: La respuesta incluye tanto el array `questions` original como el mapeo `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 """Analizar la entrada del usuario como número(s) de opción o texto 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 """Mostrar las preguntas de Claude y recopilar respuestas del usuario."""
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 # Enrutar AskUserQuestion a nuestro manejador de preguntas
670 if tool_name == "AskUserQuestion":
671 return await handle_ask_user_question(input_data)
672 # Auto-aprobar otras herramientas para este ejemplo
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 # Solución requerida: hook ficticio mantiene el flujo abierto para 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 para solicitar entrada del usuario en la 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 // Analizar la entrada del usuario como número(s) de opción o texto libre
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 // Mostrar las preguntas de Claude y recopilar respuestas del usuario
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 // Devolver las respuestas a Claude (debe incluir preguntas originales)
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 // Enrutar AskUserQuestion a nuestro manejador de preguntas
761 if (toolName === "AskUserQuestion") {
762 return handleAskUserQuestion(input);
763 }
764 // Auto-aprobar otras herramientas para este ejemplo
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## Limitaciones
778
779* **Subagentes**: `AskUserQuestion` no está disponible actualmente en subagentes generados a través de la herramienta Agent
780* **Límites de preguntas**: cada llamada `AskUserQuestion` admite 1-4 preguntas con 2-4 opciones cada una
781
782## Otras formas de obtener entrada del usuario
783
784El callback `canUseTool` y la herramienta `AskUserQuestion` cubren la mayoría de escenarios de aprobación y aclaración, pero el SDK ofrece otras formas de obtener entrada de los usuarios:
785
786### Entrada de flujo
787
788Use [entrada de flujo](/es/agent-sdk/streaming-vs-single-mode) cuando necesite:
789
790* **Interrumpir el agente a mitad de tarea**: enviar una señal de cancelación o cambiar de dirección mientras Claude está trabajando
791* **Proporcionar contexto adicional**: agregar información que Claude necesita sin esperar a que la solicite
792* **Construir interfaces de chat**: permitir que los usuarios envíen mensajes de seguimiento durante operaciones de larga duración
793
794La entrada de flujo es ideal para interfaces conversacionales donde los usuarios interactúan con el agente durante toda la ejecución, no solo en puntos de aprobación.
795
796### Herramientas personalizadas
797
798Use [herramientas personalizadas](/es/agent-sdk/custom-tools) cuando necesite:
799
800* **Recopilar entrada estructurada**: construir formularios, asistentes o flujos de trabajo de varios pasos que vayan más allá del formato de opción múltiple de `AskUserQuestion`
801* **Integrar sistemas de aprobación externos**: conectarse a plataformas de tickets, flujo de trabajo o aprobación existentes
802* **Implementar interacciones específicas del dominio**: crear herramientas adaptadas a las necesidades de su aplicación, como interfaces de revisión de código o listas de verificación de implementación
803
804Las herramientas personalizadas le dan control total sobre la interacción, pero requieren más trabajo de implementación que usar el callback `canUseTool` integrado.
805
806## Recursos relacionados
807
808* [Configurar permisos](/es/agent-sdk/permissions): configurar modos y reglas de permisos
809* [Controlar la ejecución con hooks](/es/agent-sdk/hooks): ejecutar código personalizado en puntos clave del ciclo de vida del agente
810* [Referencia del SDK de TypeScript](/es/agent-sdk/typescript#canusetool): documentación completa de la API canUseTool