SpyBara
Go Premium

agent-sdk/user-input.md 2026-05-11 23:00 UTC to 2026-05-12 22:57 UTC

47 added, 1 removed.

2026
Sun 31 06:39 Sat 30 06:23 Fri 29 06:38 Thu 28 06:37 Wed 27 06:42 Tue 26 06:33 Sun 24 06:25 Sat 23 06:18 Fri 22 06:33 Thu 21 06:36 Wed 20 06:35 Tue 19 06:34 Mon 18 23:59 Sun 17 01:01 Fri 15 22:58 Thu 14 17:02 Wed 13 23:01 Tue 12 22:57 Mon 11 23:00 Sun 10 23:03 Sat 9 04:57 Fri 8 22:00 Thu 7 22:59 Tue 5 23:00 Mon 4 22:58 Sat 2 18:14 Fri 1 18:19

Gestionar aprobaciones e entrada de usuario

Presente las solicitudes de aprobación y preguntas aclaratorias de Claude a los usuarios, luego devuelva sus decisiones al SDK.

Mientras 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.

Claude 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.

Para 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.

El 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, devuelva la decisión del hook defer, que permite que el proceso salga y se reanude más tarde desde la sesión persistida.

Esta guía le muestra cómo detectar cada tipo de solicitud y responder apropiadamente.

Detectar cuándo Claude necesita entrada

Pase 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:

async def handle_tool_request(tool_name, input_data, context):
# Solicitar al usuario y devolver permitir o denegar
...


options = ClaudeAgentOptions(can_use_tool=handle_tool_request)

El callback se activa en dos casos:

  1. La herramienta necesita aprobación: Claude quiere usar una herramienta que no está aprobada automáticamente por reglas de permisos o modos. Verifique tool_name para la herramienta (por ejemplo, "Bash", "Write").
  2. 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 para más detalles.

Manejar solicitudes de aprobación de herramientas

Una 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:

Argumento Descripción
toolName El nombre de la herramienta que Claude quiere usar (por ejemplo, "Bash", "Write", "Edit")
input Los parámetros que Claude está pasando a la herramienta. El contenido varía según la herramienta.
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 para Python.

El objeto input contiene parámetros específicos de la herramienta. Ejemplos comunes:

Herramienta Campos de entrada
Bash command, description, timeout
Write file_path, content
Edit file_path, old_string, new_string
Read file_path, offset, limit

Vea la referencia del SDK para esquemas de entrada completos: Python | TypeScript.

Puede mostrar esta información al usuario para que pueda decidir si permitir o rechazar la acción, luego devolver la respuesta apropiada.

El 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.

import asyncio

from claude_agent_sdk import ClaudeAgentOptions, ResultMessage, query
from claude_agent_sdk.types import (
HookMatcher,
PermissionResultAllow,
PermissionResultDeny,
ToolPermissionContext,
)


async def can_use_tool(
tool_name: str, input_data: dict, context: ToolPermissionContext
) -> PermissionResultAllow | PermissionResultDeny:
# Mostrar la solicitud de herramienta
print(f"\nTool: {tool_name}")
if tool_name == "Bash":
print(f"Command: {input_data.get('command')}")
if input_data.get("description"):
print(f"Description: {input_data.get('description')}")
else:
print(f"Input: {input_data}")

# Obtener aprobación del usuario
response = input("Allow this action? (y/n): ")

# Devolver permitir o denegar según la respuesta del usuario
if response.lower() == "y":
# Permitir: la herramienta se ejecuta con la entrada original (o modificada)
return PermissionResultAllow(updated_input=input_data)
else:
# Denegar: la herramienta no se ejecuta, Claude ve el mensaje
return PermissionResultDeny(message="User denied this action")


# Solución requerida: hook ficticio mantiene el flujo abierto para can_use_tool
async def dummy_hook(input_data, tool_use_id, context):
return {"continue_": True}


async def prompt_stream():
yield {
"type": "user",
"message": {
"role": "user",
"content": "Create a test file in /tmp and then delete it",
},
}


async def main():
async for message in query(
prompt=prompt_stream(),
options=ClaudeAgentOptions(
can_use_tool=can_use_tool,
hooks={"PreToolUse": [HookMatcher(matcher=None, hooks=[dummy_hook])]},
),
):
if isinstance(message, ResultMessage) and message.subtype == "success":
print(message.result)


asyncio.run(main())

Este 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 para todas las formas en que puede responder.

Responder a solicitudes de herramientas

Su callback devuelve uno de dos tipos de respuesta:

Respuesta Python TypeScript
Permitir PermissionResultAllow(updated_input=...) { behavior: "allow", updatedInput }
Denegar PermissionResultDeny(message=...) { behavior: "deny", message }

Al 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.

from claude_agent_sdk.types import PermissionResultAllow, PermissionResultDeny

# Permitir que la herramienta se ejecute
return PermissionResultAllow(updated_input=input_data)

# Bloquear la herramienta
return PermissionResultDeny(message="User rejected this action")

Más allá de permitir o denegar, puede modificar la entrada de la herramienta o proporcionar contexto que ayude a Claude a ajustar su enfoque:

  • Aprobar: permitir que la herramienta se ejecute como Claude solicitó
  • Aprobar con cambios: modificar la entrada antes de la ejecución (por ejemplo, desinfectar rutas, agregar restricciones)
  • Aprobar y recordar: devolver una regla de permiso sugerida para que las llamadas coincidentes omitan el aviso la próxima vez
  • Rechazar: bloquear la herramienta y decirle a Claude por qué
  • Sugerir alternativa: bloquear pero guiar a Claude hacia lo que el usuario quiere en su lugar
  • Redirigir completamente: usar entrada de flujo para enviar a Claude una instrucción completamente nueva

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ó.

async def can_use_tool(tool_name, input_data, context):
print(f"Claude wants to use {tool_name}")
approved = await ask_user("Allow this action?")

if approved:
return PermissionResultAllow(updated_input=input_data)
return PermissionResultDeny(message="User declined")

Manejar preguntas aclaratorias

Cuando 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.

Los siguientes pasos muestran cómo manejar preguntas aclaratorias:

1

Pasar un callback canUseTool

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:

async for message in query(
prompt="Analyze this codebase",
options=ClaudeAgentOptions(
# Incluya AskUserQuestion en su lista de herramientas
tools=["Read", "Glob", "Grep", "AskUserQuestion"],
can_use_tool=can_use_tool,
),
):
print(message)
2

Detectar AskUserQuestion

En su callback, verifique si toolName es igual a AskUserQuestion para manejarlo de manera diferente a otras herramientas:

async def can_use_tool(tool_name: str, input_data: dict, context):
if tool_name == "AskUserQuestion":
# Su implementación para recopilar respuestas del usuario
return await handle_clarifying_questions(input_data)
# Manejar otras herramientas normalmente
return await prompt_for_approval(tool_name, input_data)
3

Analizar la entrada de la pregunta

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):

{
"questions": [
{
"question": "How should I format the output?",
"header": "Format",
"options": [
{ "label": "Summary", "description": "Brief overview" },
{ "label": "Detailed", "description": "Full explanation" }
],
"multiSelect": false
},
{
"question": "Which sections should I include?",
"header": "Sections",
"options": [
{ "label": "Introduction", "description": "Opening context" },
{ "label": "Conclusion", "description": "Final summary" }
],
"multiSelect": true
}
]
}

Vea Formato de pregunta para descripciones completas de campos.

4

Recopilar respuestas del usuario

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.

5

Devolver respuestas a Claude

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:

Del objeto de pregunta Usar como
Campo question (por ejemplo, "How should I format the output?") Clave
Campo label de la opción seleccionada (por ejemplo, "Summary") Valor

Para preguntas de selección múltiple, pase un array de etiquetas o únalas con ", ". Si admite entrada de texto libre, use el texto personalizado del usuario como valor.

return PermissionResultAllow(
updated_input={
"questions": input_data.get("questions", []),
"answers": {
"How should I format the output?": "Summary",
"Which sections should I include?": ["Introduction", "Conclusion"],
},
}
)

Formato de pregunta

La entrada contiene las preguntas generadas por Claude en un array questions. Cada pregunta tiene estos campos:

Campo Descripción
question El texto completo de la pregunta a mostrar
header Etiqueta corta para la pregunta (máximo 12 caracteres)
options Array de 2-4 opciones, cada una con label y description. TypeScript: opcionalmente preview (vea abajo)
multiSelect Si es true, los usuarios pueden seleccionar múltiples opciones

La estructura que su callback recibe:

{
  "questions": [
    {
      "question": "How should I format the output?",
      "header": "Format",
      "options": [
        { "label": "Summary", "description": "Brief overview of key points" },
        { "label": "Detailed", "description": "Full explanation with examples" }
      ],
      "multiSelect": false
    }
  ]
}

Vistas previas de opciones (TypeScript)

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.

previewFormat preview contiene
sin establecer (predeterminado) El campo está ausente. Claude no genera vistas previas.
"markdown" Arte ASCII y bloques de código cercados
"html" Un fragmento <div> con estilo (el SDK rechaza <script>, <style> y <!DOCTYPE> antes de que su callback se ejecute)

El 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.

import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "Help me choose a card layout",
  options: {
    toolConfig: {
      askUserQuestion: { previewFormat: "html" }
    },
    canUseTool: async (toolName, input) => {
      // input.questions[].options[].preview es una cadena HTML o undefined
      return { behavior: "allow", updatedInput: input };
    }
  }
})) {
  // ...
}

Una opción con una vista previa HTML:

{
  "label": "Compact",
  "description": "Title and metric value only",
  "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>"
}

Formato de respuesta

Devuelva un objeto answers que asigne cada campo question de la pregunta a la label de la opción seleccionada:

Campo Descripción
questions Pase el array de preguntas original (requerido para el procesamiento de herramientas)
answers Objeto donde las claves son texto de pregunta y los valores son etiquetas seleccionadas

Para 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.

{
  "questions": [
    // ...
  ],
  "answers": {
    "How should I format the output?": "Summary",
    "Which sections should I include?": ["Introduction", "Conclusion"]
  }
}

Admitir entrada de texto libre

Las opciones predefinidas de Claude no siempre cubrirán lo que los usuarios quieren. Para permitir que los usuarios escriban su propia respuesta:

  • Muestre una opción "Otro" adicional después de las opciones de Claude que acepte entrada de texto
  • Use el texto personalizado del usuario como valor de respuesta (no la palabra "Otro")

Vea el ejemplo completo a continuación para una implementación completa.

Ejemplo completo

Claude 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.

Este ejemplo maneja esas preguntas en una aplicación de terminal. Esto es lo que sucede en cada paso:

  1. Enrutar la solicitud: El callback canUseTool verifica si el nombre de la herramienta es "AskUserQuestion" y enruta a un manejador dedicado
  2. Mostrar preguntas: El manejador recorre el array questions e imprime cada pregunta con opciones numeradas
  3. 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")
  4. 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)
  5. Devolver a Claude: La respuesta incluye tanto el array questions original como el mapeo answers
import asyncio

from claude_agent_sdk import ClaudeAgentOptions, ResultMessage, query
from claude_agent_sdk.types import HookMatcher, PermissionResultAllow


def parse_response(response: str, options: list) -> str:
"""Analizar la entrada del usuario como número(s) de opción o texto libre."""
try:
indices = [int(s.strip()) - 1 for s in response.split(",")]
labels = [options[i]["label"] for i in indices if 0 <= i < len(options)]
return ", ".join(labels) if labels else response
except ValueError:
return response


async def handle_ask_user_question(input_data: dict) -> PermissionResultAllow:
"""Mostrar las preguntas de Claude y recopilar respuestas del usuario."""
answers = {}

for q in input_data.get("questions", []):
print(f"\n{q['header']}: {q['question']}")

options = q["options"]
for i, opt in enumerate(options):
print(f"  {i + 1}. {opt['label']} - {opt['description']}")
if q.get("multiSelect"):
print("  (Enter numbers separated by commas, or type your own answer)")
else:
print("  (Enter a number, or type your own answer)")

response = input("Your choice: ").strip()
answers[q["question"]] = parse_response(response, options)

return PermissionResultAllow(
updated_input={
"questions": input_data.get("questions", []),
"answers": answers,
}
)


async def can_use_tool(
tool_name: str, input_data: dict, context
) -> PermissionResultAllow:
# Enrutar AskUserQuestion a nuestro manejador de preguntas
if tool_name == "AskUserQuestion":
return await handle_ask_user_question(input_data)
# Auto-aprobar otras herramientas para este ejemplo
return PermissionResultAllow(updated_input=input_data)


async def prompt_stream():
yield {
"type": "user",
"message": {
"role": "user",
"content": "Help me decide on the tech stack for a new mobile app",
},
}


# Solución requerida: hook ficticio mantiene el flujo abierto para can_use_tool
async def dummy_hook(input_data, tool_use_id, context):
return {"continue_": True}


async def main():
async for message in query(
prompt=prompt_stream(),
options=ClaudeAgentOptions(
can_use_tool=can_use_tool,
hooks={"PreToolUse": [HookMatcher(matcher=None, hooks=[dummy_hook])]},
),
):
if isinstance(message, ResultMessage) and message.subtype == "success":
print(message.result)


asyncio.run(main())

Limitaciones

  • Subagentes: AskUserQuestion no está disponible actualmente en subagentes generados a través de la herramienta Agent
  • Límites de preguntas: cada llamada AskUserQuestion admite 1-4 preguntas con 2-4 opciones cada una

Otras formas de obtener entrada del usuario

El 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:

Entrada de flujo

Use entrada de flujo cuando necesite:

  • Interrumpir el agente a mitad de tarea: enviar una señal de cancelación o cambiar de dirección mientras Claude está trabajando
  • Proporcionar contexto adicional: agregar información que Claude necesita sin esperar a que la solicite
  • Construir interfaces de chat: permitir que los usuarios envíen mensajes de seguimiento durante operaciones de larga duración

La 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.

Herramientas personalizadas

Use herramientas personalizadas cuando necesite:

  • 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
  • Integrar sistemas de aprobación externos: conectarse a plataformas de tickets, flujo de trabajo o aprobación existentes
  • 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

Las herramientas personalizadas le dan control total sobre la interacción, pero requieren más trabajo de implementación que usar el callback canUseTool integrado.

Recursos relacionados