SpyBara
Go Premium

agent-sdk/user-input.md 2026-10-25 23:58 UTC to 2026-10-26 22:57 UTC

No changes for this document in this history range.

2026
Fri 9 23:02 Tue 20 23:00 Thu 22 22:58 Fri 23 22:59 Fri 30 23:01

Gestire approvazioni e input dell'utente

Presenta le richieste di approvazione e le domande di chiarimento di Claude agli utenti, quindi restituisci le loro decisioni all'SDK.

Durante il lavoro su un'attività, Claude a volte ha bisogno di verificare con gli utenti. Potrebbe aver bisogno di autorizzazione prima di eliminare file, oppure potrebbe aver bisogno di chiedere quale database utilizzare per un nuovo progetto. La tua applicazione deve presentare queste richieste agli utenti in modo che Claude possa continuare con il loro input.

Claude richiede input dell'utente in due situazioni: quando ha bisogno di autorizzazione per utilizzare uno strumento (come eliminare file o eseguire comandi), e quando ha domande di chiarimento (tramite lo strumento AskUserQuestion). Entrambi attivano il tuo callback canUseTool, che mette in pausa l'esecuzione fino a quando non restituisci una risposta. Questo è diverso dai normali turni di conversazione in cui Claude finisce e attende il tuo prossimo messaggio.

Per le domande di chiarimento, Claude genera le domande e le opzioni. Il tuo ruolo è presentarle agli utenti e restituire le loro selezioni. Non puoi aggiungere le tue domande a questo flusso; se hai bisogno di chiedere qualcosa agli utenti tu stesso, fallo separatamente nella logica dell'applicazione.

Il callback può rimanere in sospeso indefinitamente. L'esecuzione rimane in pausa fino a quando il callback non restituisce. Se un utente potrebbe impiegare più tempo per rispondere di quanto il tuo processo possa ragionevolmente rimanere in esecuzione, registra un hook PreToolUse che restituisce la decisione defer invece di attendere nel callback, in modo che il processo possa uscire e riprendere in seguito dalla sessione persistente.

Questa guida ti mostra come rilevare ogni tipo di richiesta e rispondere in modo appropriato.

Rilevare quando Claude ha bisogno di input

Passa un callback canUseTool nelle opzioni della query. Il callback si attiva ogni volta che Claude ha bisogno di input dell'utente, ricevendo il nome dello strumento e l'input come argomenti:

from claude_agent_sdk import ClaudeAgentOptions


async def handle_tool_request(tool_name, input_data, context):
    # Chiedi all'utente e restituisci allow o deny
    ...


options = ClaudeAgentOptions(can_use_tool=handle_tool_request)
import type { CanUseTool } from "@anthropic-ai/claude-agent-sdk";

const handleToolRequest: CanUseTool = async (toolName, input, options) => {
  // options includes { signal: AbortSignal, suggestions?: PermissionUpdate[] }
  // Chiedi all'utente qui, poi restituisci allow o deny
  return { behavior: "deny", message: "User declined" };
};

const options = { canUseTool: handleToolRequest };

Il callback si attiva in due casi:

  1. Lo strumento ha bisogno di approvazione: Claude vuole utilizzare uno strumento che non è approvato automaticamente da una regola di autorizzazione o da una modalità di autorizzazione. Controlla tool_name per lo strumento (ad es. "Bash", "Write").
  2. Claude pone una domanda: Claude chiama lo strumento AskUserQuestion. Controlla se tool_name == "AskUserQuestion" per gestirlo diversamente. Se specifichi un array tools, includi AskUserQuestion affinché funzioni. Vedi Gestire domande di chiarimento per i dettagli.

[!WARNING]

Il callback non si attiva mai per gli strumenti approvati automaticamente. Qualsiasi approvazione precedente nel flusso di valutazione delle autorizzazioni, una regola di consentimento o una modalità come acceptEdits o bypassPermissions, risolve la chiamata prima che canUseTool sia consultato. Se elenchi uno strumento direttamente in allowed_tools, un controllo canUseTool per quello strumento viene eseguito solo quando il flusso di valutazione reindirizzi la chiamata a un prompt, come una regola di richiesta o la modalità plan. Per la logica che deve applicarsi a ogni chiamata di strumento, utilizza un hook PreToolUse, che viene eseguito prima del resto del flusso e può consentire, negare o modificare le richieste.

Una regola di consentimento non pre-approva le azioni che nessuna modalità approva automaticamente; vedi Come vengono valutate le autorizzazioni per sapere quali di esse raggiungono il callback e cosa accade in modalità dontAsk e auto.

Puoi anche utilizzare l'hook PermissionRequest per inviare notifiche esterne (Slack, email, push) quando Claude è in attesa di approvazione.

Gestire le richieste di approvazione dello strumento

Una volta passato un callback canUseTool nelle opzioni della query, si attiva quando Claude vuole utilizzare uno strumento che nulla prima nel flusso di autorizzazione ha approvato. In alcune configurazioni, come la modalità dontAsk, Claude Code non lo chiama; l'ultimo passaggio di Come vengono valutate le autorizzazioni le elenca e dice cosa accade alla chiamata invece.

Il tuo callback riceve tre argomenti:

Argomento Descrizione
toolName Il nome dello strumento che Claude vuole utilizzare (ad es. "Bash", "Write", "Edit")
input I parametri che Claude sta passando allo strumento. Il contenuto varia a seconda dello strumento.
options (TS) / context (Python) Contesto aggiuntivo incluso suggestions opzionale (voci PermissionUpdate proposte per evitare di ripetere le domande) e un segnale di annullamento. In TypeScript, signal è un AbortSignal; in Python, il campo signal è riservato per uso futuro. Vedi ToolPermissionContext per Python.

L'oggetto input contiene parametri specifici dello strumento. Esempi comuni:

Strumento Campi di input
Bash command, description, timeout
Write file_path, content
Edit file_path, old_string, new_string
Read file_path, offset, limit

Vedi il riferimento SDK per gli schemi di input completi: Python | TypeScript.

Puoi visualizzare queste informazioni all'utente in modo che possa decidere se consentire o rifiutare l'azione, quindi restituire la risposta appropriata.

L'esempio seguente chiede a Claude di creare ed eliminare un file di test. Quando Claude tenta ogni operazione, il callback stampa la richiesta dello strumento nel terminale e chiede l'approvazione 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:
    # Visualizza la richiesta dello strumento
    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}")

    # Ottieni l'approvazione dell'utente
    response = input("Allow this action? (y/n): ")

    # Restituisci allow o deny in base alla risposta dell'utente
    if response.lower() == "y":
        # Allow: lo strumento viene eseguito con l'input originale (o modificato)
        return PermissionResultAllow(updated_input=input_data)
    else:
        # Deny: lo strumento non viene eseguito, Claude vede il messaggio
        return PermissionResultDeny(message="User denied this action")


# Workaround richiesto: dummy hook mantiene il flusso aperto per 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())
import { query } from "@anthropic-ai/claude-agent-sdk";
import * as readline from "readline";

// Helper per chiedere all'utente l'input nel terminale
function prompt(question: string): Promise<string> {
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
  });
  return new Promise((resolve) =>
    rl.question(question, (answer) => {
      rl.close();
      resolve(answer);
    })
  );
}

for await (const message of query({
  prompt: "Create a test file in /tmp and then delete it",
  options: {
    canUseTool: async (toolName, input) => {
      // Visualizza la richiesta dello strumento
      console.log(`\nTool: ${toolName}`);
      if (toolName === "Bash") {
        console.log(`Command: ${input.command}`);
        if (input.description) console.log(`Description: ${input.description}`);
      } else {
        console.log(`Input: ${JSON.stringify(input, null, 2)}`);
      }

      // Ottieni l'approvazione dell'utente
      const response = await prompt("Allow this action? (y/n): ");

      // Restituisci allow o deny in base alla risposta dell'utente
      if (response.toLowerCase() === "y") {
        // Allow: lo strumento viene eseguito con l'input originale (o modificato)
        return { behavior: "allow", updatedInput: input };
      } else {
        // Deny: lo strumento non viene eseguito, Claude vede il messaggio
        return { behavior: "deny", message: "User denied this action" };
      }
    }
  }
})) {
  if ("result" in message) console.log(message.result);
}

Questo esempio utilizza un flusso s/n in cui qualsiasi input diverso da y viene trattato come un rifiuto. In pratica, potresti creare un'interfaccia utente più ricca che consenta agli utenti di modificare la richiesta, fornire feedback o reindirizzare completamente Claude. Vedi Rispondere alle richieste dello strumento per tutti i modi in cui puoi rispondere.

Rispondere alle richieste dello strumento

Il tuo callback restituisce uno di due tipi di risposta:

Risposta Python TypeScript
Allow PermissionResultAllow(updated_input=...) { behavior: "allow", updatedInput }
Deny PermissionResultDeny(message=...) { behavior: "deny", message }

Quando consenti, lo strumento viene eseguito con l'input che Claude ha richiesto a meno che non restituisca un input modificato, updatedInput in TypeScript o updated_input in Python. Prima della v2.1.207, Claude Code rifiutava un risultato di allow che ometteva updatedInput e negava la chiamata dello strumento con un errore di convalida.

Quando neghi, fornisci un messaggio che spiega il motivo. Claude vede questo messaggio e potrebbe adattare il suo approccio.

Oltre a consentire o negare, puoi modificare l'input dello strumento o fornire contesto che aiuta Claude ad adattare il suo approccio:

  • Approva: consenti l'esecuzione dello strumento come richiesto da Claude
  • Approva con modifiche: modifica l'input prima dell'esecuzione (ad es. sanitizza i percorsi, aggiungi vincoli)
  • Approva e ricorda: ripeti una regola di autorizzazione suggerita in modo che le chiamate corrispondenti saltino il prompt la prossima volta
  • Rifiuta: blocca lo strumento e spiega a Claude il motivo
  • Suggerisci alternativa: blocca ma guida Claude verso ciò che l'utente vuole invece
  • Reindirizza completamente: utilizza input streaming per inviare a Claude un'istruzione completamente nuova

Gli helper ask_user e askUser nei seguenti snippet stanno al posto della tua applicazione stessa per l'interfaccia utente di prompt.

Approva

L'utente approva l'azione così com'è. Passa l'input dal tuo callback invariato e lo strumento viene eseguito esattamente come richiesto da Claude.

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")
canUseTool: async (toolName, input) => {
  console.log(`Claude wants to use ${toolName}`);
  const approved = await askUser("Allow this action?");

  if (approved) {
    return { behavior: "allow", updatedInput: input };
  }
  return { behavior: "deny", message: "User declined" };
};

Approva con modifiche

L'utente approva ma vuole modificare la richiesta prima. Puoi cambiare l'input prima che lo strumento venga eseguito. Claude vede il risultato ma non gli viene detto che hai cambiato qualcosa. Utile per sanitizzare i parametri, aggiungere vincoli o limitare l'accesso.

async def can_use_tool(tool_name, input_data, context):
    if tool_name == "Bash":
        # L'utente ha approvato, ma limita tutti i comandi alla sandbox
        sandboxed_input = {**input_data}
        sandboxed_input["command"] = input_data["command"].replace(
            "/tmp", "/tmp/sandbox"
        )
        return PermissionResultAllow(updated_input=sandboxed_input)
    return PermissionResultAllow(updated_input=input_data)
canUseTool: async (toolName, input) => {
  if (toolName === "Bash") {
    // L'utente ha approvato, ma limita tutti i comandi alla sandbox
    const sandboxedInput = {
      ...input,
      command: input.command.replace("/tmp", "/tmp/sandbox")
    };
    return { behavior: "allow", updatedInput: sandboxedInput };
  }
  return { behavior: "allow", updatedInput: input };
};

Approva e ricorda

L'utente approva e non vuole essere chiesto di nuovo per questo tipo di chiamata. Il terzo argomento del callback contiene suggestions, un array di voci PermissionUpdate già pronte. Ripeti una di queste in updatedPermissions per applicarla. Un suggerimento con la destinazione localSettings scrive la regola in .claude/settings.local.json in modo che le sessioni future saltino il prompt per le chiamate corrispondenti.

In TypeScript, salta la scelta di sempre consentire per una richiesta le cui opzioni portano suppressAlwaysAllowRule: true. L'hint richiede Agent SDK v0.3.268 o successivo, e il context Python non lo contiene.

L'esempio Python richiede claude-agent-sdk 0.1.80 o successivo.

async def can_use_tool(tool_name, input_data, context):
    choice = await ask_user(f"Allow {tool_name}?", ["once", "always", "no"])

    if choice == "always":
        persist = [
            s for s in context.suggestions if s.destination == "localSettings"
        ]
        return PermissionResultAllow(
            updated_input=input_data, updated_permissions=persist
        )
    if choice == "once":
        return PermissionResultAllow(updated_input=input_data)
    return PermissionResultDeny(message="User declined")
canUseTool: async (toolName, input, { suggestions = [] }) => {
  const choice = await askUser(`Allow ${toolName}?`, ["once", "always", "no"]);

  if (choice === "always") {
    const persist = suggestions.filter(
      (s) => s.destination === "localSettings"
    );
    return {
      behavior: "allow",
      updatedInput: input,
      updatedPermissions: persist
    };
  }
  if (choice === "once") {
    return { behavior: "allow", updatedInput: input };
  }
  return { behavior: "deny", message: "User declined" };
};

Rifiuta

L'utente non vuole che questa azione accada. Blocca lo strumento e fornisci un messaggio che spiega il motivo. Claude vede questo messaggio e potrebbe provare un approccio diverso.

async def can_use_tool(tool_name, input_data, context):
    approved = await ask_user(f"Allow {tool_name}?")

    if not approved:
        return PermissionResultDeny(message="User rejected this action")
    return PermissionResultAllow(updated_input=input_data)
canUseTool: async (toolName, input) => {
  const approved = await askUser(`Allow ${toolName}?`);

  if (!approved) {
    return {
      behavior: "deny",
      message: "User rejected this action"
    };
  }
  return { behavior: "allow", updatedInput: input };
};

Suggerisci alternativa

L'utente non vuole questa azione specifica, ma ha un'idea diversa. Blocca lo strumento e includi una guida nel tuo messaggio. Claude leggerà questo e deciderà come procedere in base al tuo feedback.

async def can_use_tool(tool_name, input_data, context):
    if tool_name == "Bash" and "rm" in input_data.get("command", ""):
        # L'utente non vuole eliminare, suggerisci di comprimere invece
        return PermissionResultDeny(
            message="User doesn't want to delete files. They asked if you could compress them into an archive instead."
        )
    return PermissionResultAllow(updated_input=input_data)
canUseTool: async (toolName, input) => {
  if (toolName === "Bash" && input.command.includes("rm")) {
    // L'utente non vuole eliminare, suggerisci di comprimere invece
    return {
      behavior: "deny",
      message:
        "User doesn't want to delete files. They asked if you could compress them into an archive instead."
    };
  }
  return { behavior: "allow", updatedInput: input };
};

Reindirizza completamente

Per un cambio di direzione completo (non solo una spinta), utilizza input streaming per inviare a Claude una nuova istruzione direttamente. Questo bypassa la richiesta dello strumento corrente e dà a Claude istruzioni completamente nuove da seguire.

Gestire domande di chiarimento

Quando Claude ha bisogno di più direzione su un'attività con più approcci validi, chiama lo strumento AskUserQuestion. Questo attiva il tuo callback canUseTool con toolName impostato su AskUserQuestion. L'input contiene le domande di Claude come opzioni a scelta multipla, che visualizzi all'utente e restituisci le sue selezioni.

[!TIP]

Le domande di chiarimento sono particolarmente comuni nella modalità plan, dove Claude esplora la base di codice e pone domande prima di proporre un piano. Questo rende la modalità plan ideale per flussi di lavoro interattivi in cui vuoi che Claude raccolga i requisiti prima di apportare modifiche.

I seguenti passaggi mostrano come gestire le domande di chiarimento:

1. Passa un callback canUseTool

Passa un callback canUseTool nelle opzioni della query. Per impostazione predefinita, AskUserQuestion è disponibile. Se specifichi un array tools per limitare le capacità di Claude (ad esempio, un agente di sola lettura con solo Read, Glob e Grep), includi AskUserQuestion in quell'array. Altrimenti, Claude non sarà in grado di porre domande di chiarimento:

async for message in query(
    prompt="Analyze this codebase",
    options=ClaudeAgentOptions(
        # Includi AskUserQuestion nella tua lista di strumenti
        tools=["Read", "Glob", "Grep", "AskUserQuestion"],
        can_use_tool=can_use_tool,
    ),
):
    print(message)
for await (const message of query({
  prompt: "Analyze this codebase",
  options: {
    // Includi AskUserQuestion nella tua lista di strumenti
    tools: ["Read", "Glob", "Grep", "AskUserQuestion"],
    canUseTool: async (toolName, input) => {
      // Segnaposto che approva ogni chiamata. Il passaggio Rileva AskUserQuestion lo sostituisce.
      return { behavior: "allow", updatedInput: input };
    }
  }
})) {
  console.log(message);
}

2. Rileva AskUserQuestion

Nel tuo callback, controlla se toolName è uguale a AskUserQuestion per gestirlo diversamente da altri strumenti:

async def can_use_tool(tool_name: str, input_data: dict, context):
    if tool_name == "AskUserQuestion":
        # La tua implementazione per raccogliere risposte dall'utente
        return await handle_clarifying_questions(input_data)
    # Gestisci altri strumenti normalmente
    return await prompt_for_approval(tool_name, input_data)
canUseTool: async (toolName, input) => {
  if (toolName === "AskUserQuestion") {
    // La tua implementazione per raccogliere risposte dall'utente
    return handleClarifyingQuestions(input);
  }
  // Gestisci altri strumenti normalmente
  return promptForApproval(toolName, input);
};

3. Analizza l'input della domanda

L'input contiene le domande di Claude in un array questions. Ogni domanda ha una question (il testo da visualizzare), options (le scelte) e multiSelect (se sono consentite più selezioni):

{
  "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
    }
  ]
}

Vedi Formato della domanda per le descrizioni complete dei campi.

4. Raccogli risposte dall'utente

Presenta le domande all'utente e raccogli le sue selezioni. Come lo fai dipende dalla tua applicazione: un prompt del terminale, un modulo web, una finestra di dialogo mobile, ecc.

5. Restituisci le risposte a Claude

Costruisci l'oggetto answers come un record in cui ogni chiave è il testo question e ogni valore è l'label dell'opzione selezionata:

Dall'oggetto domanda Usa come
Campo question (ad es. "How should I format the output?") Chiave
Campo label dell'opzione selezionata (ad es. "Summary") Valore

Per le domande a selezione multipla, passa un array di etichette o uniscile con ", ". Se supporti input di testo libero, utilizza il testo personalizzato dell'utente come valore.

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

Formato della domanda

L'input contiene le domande generate da Claude in un array questions. Ogni domanda ha questi campi:

Campo Descrizione
question Il testo completo della domanda da visualizzare
header Etichetta breve per la domanda (max 12 caratteri)
options Array di 2-4 scelte, ognuna con label e description. TypeScript: opzionalmente preview. Vedi Anteprime delle opzioni.
multiSelect Se true, gli utenti possono selezionare più opzioni

La struttura che il tuo callback riceve:

{
  "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
    }
  ]
}

Anteprime delle opzioni (TypeScript)

toolConfig.askUserQuestion.previewFormat aggiunge un campo preview a ogni opzione in modo che la tua app possa mostrare un mockup visivo insieme all'etichetta. Senza questa impostazione, Claude non genera anteprime e il campo è assente.

previewFormat preview contiene
non impostato (predefinito) Il campo è assente. Claude non genera anteprime.
"markdown" ASCII art e blocchi di codice recintati
"html" Un frammento <div> stilizzato (l'SDK rifiuta <script>, <style> e <!DOCTYPE> prima che il tuo callback venga eseguito)

Il formato si applica a tutte le domande nella sessione. Claude include preview sulle opzioni in cui un confronto visivo aiuta (scelte di layout, schemi di colori) e lo omette dove non lo farebbe (conferme sì/no, scelte solo testo). Controlla undefined prima di eseguire il rendering.

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 è una stringa HTML o undefined
      return { behavior: "allow", updatedInput: input };
    }
  }
})) {
  // ...
}

Un'opzione con un'anteprima 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 della risposta

Restituisci un oggetto answers che mappa il campo question di ogni domanda all'label dell'opzione selezionata:

Campo Descrizione
questions Passa l'array di domande originale (obbligatorio per l'elaborazione dello strumento)
answers Oggetto in cui le chiavi sono il testo della domanda e i valori sono le etichette selezionate
response Risposta facoltativa in testo libero che l'utente ha digitato invece di rispondere alle domande strutturate

Per le domande a selezione multipla, passa un array di etichette o uniscile con ", ". Per input di testo libero per domanda, come un'opzione "Other", inserisci il testo dell'utente in answers[question] come mostrato in Supporta input di testo libero. Imposta response solo quando la tua interfaccia utente consente all'utente di chiudere la scheda della domanda e digitare una risposta generale che non sia una risposta a nessuna domanda specifica. Quando response è impostato, Claude riceve "L'utente ha risposto: …" invece dell'elenco di risposte per domanda.

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

Supporta input di testo libero

Le opzioni predefinite di Claude non copriranno sempre ciò che gli utenti vogliono. Per consentire agli utenti di digitare la propria risposta:

  • Visualizza una scelta "Other" aggiuntiva dopo le opzioni di Claude che accetta input di testo
  • Utilizza il testo personalizzato dell'utente come valore della risposta (non la parola "Other")

Vedi l'esempio completo di seguito per un'implementazione completa.

Esempio completo

Claude pone domande di chiarimento quando ha bisogno di input dell'utente per procedere. Ad esempio, quando gli viene chiesto di aiutare a decidere su uno stack tecnologico per un'app mobile, Claude potrebbe chiedere informazioni su cross-platform vs nativo, preferenze di backend o piattaforme di destinazione. Queste domande aiutano Claude a prendere decisioni che corrispondono alle preferenze dell'utente piuttosto che indovinare.

Questo esempio gestisce quelle domande in un'applicazione terminale. Ecco cosa accade ad ogni passaggio:

  1. Instrada la richiesta: Il callback canUseTool controlla se il nome dello strumento è "AskUserQuestion" e instrada a un gestore dedicato
  2. Visualizza le domande: Il gestore scorre l'array questions e stampa ogni domanda con opzioni numerate
  3. Raccogli input: L'utente può inserire un numero per selezionare un'opzione, o digitare testo libero direttamente (ad es. "jquery", "i don't know")
  4. Mappa le risposte: Il codice controlla se l'input è numerico (utilizza l'etichetta dell'opzione) o testo libero (utilizza il testo direttamente)
  5. Restituisci a Claude: La risposta include sia l'array questions originale che la mappatura answers

Salva la versione TypeScript come ask.ts ed eseguila con npx tsx ask.ts, oppure salva la versione Python come ask.py ed eseguila con python ask.py.

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:
    """Analizza l'input dell'utente come numero(i) di opzione o testo libero."""
    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:
    """Visualizza le domande di Claude e raccogli le risposte dell'utente."""
    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:
    # Instrada AskUserQuestion al nostro gestore di domande
    if tool_name == "AskUserQuestion":
        return await handle_ask_user_question(input_data)
    # Auto-approva altri strumenti per questo esempio
    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",
        },
    }


# Workaround richiesto: dummy hook mantiene il flusso aperto per 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())
import { query } from "@anthropic-ai/claude-agent-sdk";
import type { PermissionResult } from "@anthropic-ai/claude-agent-sdk";
import * as readline from "readline/promises";

// Helper per chiedere all'utente l'input nel terminale
async function prompt(question: string): Promise<string> {
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
  const answer = await rl.question(question);
  rl.close();
  return answer;
}

// Analizza l'input dell'utente come numero(i) di opzione o testo libero
function parseResponse(response: string, options: any[]): string {
  const indices = response.split(",").map((s) => parseInt(s.trim()) - 1);
  const labels = indices
    .filter((i) => !isNaN(i) && i >= 0 && i < options.length)
    .map((i) => options[i].label);
  return labels.length > 0 ? labels.join(", ") : response;
}

// Visualizza le domande di Claude e raccogli le risposte dell'utente
async function handleAskUserQuestion(input: any): Promise<PermissionResult> {
  const answers: Record<string, string> = {};

  for (const q of input.questions) {
    console.log(`\n${q.header}: ${q.question}`);

    const options = q.options;
    options.forEach((opt: any, i: number) => {
      console.log(`  ${i + 1}. ${opt.label} - ${opt.description}`);
    });
    if (q.multiSelect) {
      console.log("  (Enter numbers separated by commas, or type your own answer)");
    } else {
      console.log("  (Enter a number, or type your own answer)");
    }

    const response = (await prompt("Your choice: ")).trim();
    answers[q.question] = parseResponse(response, options);
  }

  // Restituisci le risposte a Claude (deve includere le domande originali)
  return {
    behavior: "allow",
    updatedInput: { questions: input.questions, answers }
  };
}

async function main() {
  for await (const message of query({
    prompt: "Help me decide on the tech stack for a new mobile app",
    options: {
      canUseTool: async (toolName, input) => {
        // Instrada AskUserQuestion al nostro gestore di domande
        if (toolName === "AskUserQuestion") {
          return handleAskUserQuestion(input);
        }
        // Auto-approva altri strumenti per questo esempio
        return { behavior: "allow", updatedInput: input };
      }
    }
  })) {
    if ("result" in message) console.log(message.result);
  }
}

main();

Limitazioni

  • Subagenti: AskUserQuestion non è attualmente disponibile nei subagenti generati tramite lo strumento Agent
  • Limiti delle domande: ogni chiamata AskUserQuestion supporta 1-4 domande con 2-4 opzioni ciascuna

Altri modi per ottenere input dall'utente

Il callback canUseTool e lo strumento AskUserQuestion coprono la maggior parte degli scenari di approvazione e chiarimento, ma l'SDK offre altri modi per ottenere input dagli utenti:

Input streaming

Utilizza input streaming quando hai bisogno di:

  • Interrompere l'agente a metà attività: invia un segnale di annullamento o cambia direzione mentre Claude sta lavorando
  • Fornire contesto aggiuntivo: aggiungi informazioni di cui Claude ha bisogno senza aspettare che le chieda
  • Costruire interfacce di chat: consenti agli utenti di inviare messaggi di follow-up durante operazioni di lunga durata

L'input streaming è ideale per interfacce conversazionali in cui gli utenti interagiscono con l'agente durante l'esecuzione, non solo nei checkpoint di approvazione.

Strumenti personalizzati

Utilizza strumenti personalizzati quando hai bisogno di:

  • Raccogliere input strutturato: costruisci moduli, procedure guidate o flussi di lavoro multi-step che vanno oltre il formato a scelta multipla di AskUserQuestion
  • Integrare sistemi di approvazione esterni: connettiti a piattaforme di ticketing, flusso di lavoro o approvazione esistenti
  • Implementare interazioni specifiche del dominio: crea strumenti personalizzati per le esigenze della tua applicazione, come interfacce di revisione del codice o elenchi di controllo di distribuzione

Gli strumenti personalizzati ti danno il controllo completo sull'interazione, ma richiedono più lavoro di implementazione rispetto all'utilizzo del callback canUseTool integrato.

Risorse correlate