{/* TRANSLATED — 已翻译为中文 */}

> ## 文档索引
> 在此获取完整文档索引：https://code.claude.com/docs/llms.txt
> 使用此文件发现所有可用页面，然后再进一步探索。

# 处理审批和用户输入

> 将 Claude 的审批请求和澄清问题展示给用户，然后将用户的决定返回给 SDK。

在处理任务时，Claude 有时需要与用户沟通。它可能在删除文件前需要权限，或者需要询问新项目使用哪个数据库。你的应用需要将这些请求展示给用户，以便 Claude 能够根据用户的输入继续工作。

Claude 在两种情况下请求用户输入：当它需要**使用工具的权限**时（如删除文件或运行命令），以及当它有**澄清问题**时（通过 `AskUserQuestion` 工具）。两者都会触发你的 `canUseTool` 回调，该回调会暂停执行直到你返回响应。这与正常的对话轮次不同，在正常轮次中 Claude 完成后等待你的下一条消息。

对于澄清问题，Claude 生成问题和选项。你的角色是将其展示给用户并返回他们的选择。你不能在此流程中添加自己的问题；如果你需要自己向用户提问，请在应用逻辑中单独进行。

回调可以无限期地保持挂起状态。执行会暂停直到你的回调返回，SDK 只有在查询本身被取消时才会取消等待。如果用户可能需要比你的进程合理运行时间更长的时间来响应，请返回 [`defer` 钩子决策](/en/hooks#defer-a-tool-call-for-later)，这允许进程退出并稍后从持久化的会话恢复。

本指南展示如何检测每种类型的请求并做出适当的响应。

## 检测 Claude 何时需要输入

在查询选项中传递 `canUseTool` 回调。每当 Claude 需要用户输入时，回调就会触发，接收工具名称和输入作为参数：

<CodeGroup>
  ```python Python theme={null}
  async def handle_tool_request(tool_name, input_data, context):
      # Prompt user and return allow or deny
      ...


  options = ClaudeAgentOptions(can_use_tool=handle_tool_request)
  ```

  ```typescript TypeScript theme={null}
  async function handleToolRequest(toolName, input, options) {
    // options includes { signal: AbortSignal, suggestions?: PermissionUpdate[] }
    // Prompt user and return allow or deny
  }

  const options = { canUseTool: handleToolRequest };
  ```
</CodeGroup>

回调在两种情况下触发：

1. **工具需要审批**：Claude 想要使用一个未被[权限规则](/en/agent-sdk/permissions)或模式自动批准的工具。检查 `tool_name` 获取工具名称（如 `"Bash"`、`"Write"`）。
2. **Claude 提问**：Claude 调用 `AskUserQuestion` 工具。检查 `tool_name == "AskUserQuestion"` 以不同方式处理。如果你指定了 `tools` 数组，请包含 `AskUserQuestion` 才能使此功能生效。详见[处理澄清问题](#handle-clarifying-questions)。

<Note>
  要自动允许或拒绝工具而无需提示用户，请使用[钩子](/en/agent-sdk/hooks)代替。钩子在 `canUseTool` 之前执行，可以根据你自己的逻辑允许、拒绝或修改请求。你还可以使用 [`PermissionRequest` 钩子](/en/agent-sdk/hooks#available-hooks)在 Claude 等待审批时发送外部通知（Slack、邮件、推送）。
</Note>

## 处理工具审批请求

一旦你在查询选项中传递了 `canUseTool` 回调，当 Claude 想要使用未自动批准的工具时它就会触发。你的回调接收三个参数：

| 参数                            | 描述                                                                                                                                                                                                                                                                                                                           |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `toolName`                          | Claude 想要使用的工具名称（如 `"Bash"`、`"Write"`、`"Edit"`）                                                                                                                                                                                                                                                        |
| `input`                             | Claude 传递给工具的参数。内容因工具而异。                                                                                                                                                                                                                                                                  |
| `options`（TS）/ `context`（Python） | 附加上下文，包括可选的 `suggestions`（建议的 `PermissionUpdate` 条目，用于避免重复提示）和取消信号。在 TypeScript 中，`signal` 是 `AbortSignal`；在 Python 中，signal 字段保留供将来使用。详见 Python 的 [`ToolPermissionContext`](/en/agent-sdk/python#toolpermissioncontext)。 |

`input` 对象包含工具特定的参数。常见示例：

| 工具    | 输入字段                            |
| ------- | --------------------------------------- |
| `Bash`  | `command`、`description`、`timeout`     |
| `Write` | `file_path`、`content`                  |
| `Edit`  | `file_path`、`old_string`、`new_string` |
| `Read`  | `file_path`、`offset`、`limit`          |

完整的输入模式请参阅 SDK 参考：[Python](/en/agent-sdk/python#tool-input%2Foutput-types) | [TypeScript](/en/agent-sdk/typescript#tool-input-types)。

你可以将这些信息展示给用户，让他们决定是允许还是拒绝操作，然后返回适当的响应。

以下示例要求 Claude 创建并删除一个测试文件。当 Claude 尝试每个操作时，回调将工具请求打印到终端并提示 y/n 审批。

<CodeGroup>
  ```python Python theme={null}
  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:
      # Display the tool request
      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}")

      # Get user approval
      response = input("Allow this action? (y/n): ")

      # Return allow or deny based on user's response
      if response.lower() == "y":
          # Allow: tool executes with the original (or modified) input
          return PermissionResultAllow(updated_input=input_data)
      else:
          # Deny: tool doesn't execute, Claude sees the message
          return PermissionResultDeny(message="User denied this action")


  # Required workaround: dummy hook keeps the stream open for 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())
  ```

  ```typescript TypeScript theme={null}
  import { query } from "@anthropic-ai/claude-agent-sdk";
  import * as readline from "readline";

  // Helper to prompt user for input in the terminal
  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) => {
        // Display the tool request
        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)}`);
        }

        // Get user approval
        const response = await prompt("Allow this action? (y/n): ");

        // Return allow or deny based on user's response
        if (response.toLowerCase() === "y") {
          // Allow: tool executes with the original (or modified) input
          return { behavior: "allow", updatedInput: input };
        } else {
          // Deny: tool doesn't execute, Claude sees the message
          return { behavior: "deny", message: "User denied this action" };
        }
      }
    }
  })) {
    if ("result" in message) console.log(message.result);
  }
  ```
</CodeGroup>

<Note>
  在 Python 中，`can_use_tool` 需要[流式模式](/en/agent-sdk/streaming-vs-single-mode)和一个返回 `{"continue_": True}` 的 `PreToolUse` 钩子来保持流打开。没有此钩子，流会在权限回调被调用之前关闭。
</Note>

此示例使用 `y/n` 流程，任何非 `y` 的输入都被视为拒绝。在实践中，你可能会构建更丰富的 UI，让用户修改请求、提供反馈或完全重定向 Claude。有关所有响应方式，请参阅[响应工具请求](#respond-to-tool-requests)。

### 响应工具请求

你的回调返回以下两种响应类型之一：

| 响应  | Python                                     | TypeScript                            |
| --------- | ------------------------------------------ | ------------------------------------- |
| **允许** | `PermissionResultAllow(updated_input=...)` | `{ behavior: "allow", updatedInput }` |
| **拒绝**  | `PermissionResultDeny(message=...)`        | `{ behavior: "deny", message }`       |

允许时，传递工具输入（原始或修改后的）。拒绝时，提供解释原因的消息。Claude 会看到此消息并可能调整其方法。

<CodeGroup>
  ```python Python theme={null}
  from claude_agent_sdk.types import PermissionResultAllow, PermissionResultDeny

  # Allow the tool to execute
  return PermissionResultAllow(updated_input=input_data)

  # Block the tool
  return PermissionResultDeny(message="User rejected this action")
  ```

  ```typescript TypeScript theme={null}
  // Allow the tool to execute
  return { behavior: "allow", updatedInput: input };

  // Block the tool
  return { behavior: "deny", message: "User rejected this action" };
  ```
</CodeGroup>

除了允许或拒绝，你还可以修改工具的输入或提供帮助 Claude 调整方法的上下文：

* **批准**：让工具按 Claude 的请求执行
* **批准并修改**：在执行前修改输入（如清理路径、添加约束）
* **批准并记住**：回显建议的权限规则，以便匹配的调用下次跳过提示
* **拒绝**：阻止工具并告诉 Claude 原因
* **建议替代方案**：阻止但引导 Claude 转向用户想要的方向
* **完全重定向**：使用[流式输入](/en/agent-sdk/streaming-vs-single-mode)直接向 Claude 发送全新的指令

<Tabs>
  <Tab title="批准">
    用户批准该操作原样执行。原样传递回调中的 `input`，工具将按 Claude 的请求精确执行。

    <CodeGroup>
      ```python Python theme={null}
      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")
      ```

      ```typescript TypeScript theme={null}
      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" };
      };
      ```
    </CodeGroup>
  </Tab>

  <Tab title="批准并修改">
    用户批准但希望先修改请求。你可以在工具执行前更改输入。Claude 看到结果但不会被告知你做了更改。适用于清理参数、添加约束或限制访问范围。

    <CodeGroup>
      ```python Python theme={null}
      async def can_use_tool(tool_name, input_data, context):
          if tool_name == "Bash":
              # User approved, but scope all commands to 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)
      ```

      ```typescript TypeScript theme={null}
      canUseTool: async (toolName, input) => {
        if (toolName === "Bash") {
          // User approved, but scope all commands to sandbox
          const sandboxedInput = {
            ...input,
            command: input.command.replace("/tmp", "/tmp/sandbox")
          };
          return { behavior: "allow", updatedInput: sandboxedInput };
        }
        return { behavior: "allow", updatedInput: input };
      };
      ```
    </CodeGroup>
  </Tab>

  <Tab title="批准并记住">
    用户批准且不想再被问及此类调用。第三个回调参数携带 `suggestions`，即一组现成的 [`PermissionUpdate`](/en/agent-sdk/typescript#permissionupdate) 条目。在 `updatedPermissions` 中回显一个以应用它。目标为 `localSettings` 的建议会将规则写入 `.claude/settings.local.json`，以便未来的会话跳过匹配调用的提示。

    Python 示例需要 `claude-agent-sdk` 0.1.80 或更高版本。

    <CodeGroup>
      ```python Python theme={null}
      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")
      ```

      ```typescript TypeScript theme={null}
      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" };
      };
      ```
    </CodeGroup>
  </Tab>

  <Tab title="拒绝">
    用户不希望执行此操作。阻止工具并提供解释原因的消息。Claude 会看到此消息并可能尝试不同的方法。

    <CodeGroup>
      ```python Python theme={null}
      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)
      ```

      ```typescript TypeScript theme={null}
      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 };
      };
      ```
    </CodeGroup>
  </Tab>

  <Tab title="建议替代方案">
    用户不想要这个特定操作，但有其他想法。阻止工具并在消息中包含指导。Claude 会读取此内容并根据你的反馈决定如何继续。

    <CodeGroup>
      ```python Python theme={null}
      async def can_use_tool(tool_name, input_data, context):
          if tool_name == "Bash" and "rm" in input_data.get("command", ""):
              # User doesn't want to delete, suggest archiving instead
              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)
      ```

      ```typescript TypeScript theme={null}
      canUseTool: async (toolName, input) => {
        if (toolName === "Bash" && input.command.includes("rm")) {
          // User doesn't want to delete, suggest archiving instead
          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 };
      };
      ```
    </CodeGroup>
  </Tab>

  <Tab title="完全重定向">
    对于完全改变方向（不仅仅是轻微调整），使用[流式输入](/en/agent-sdk/streaming-vs-single-mode)直接向 Claude 发送新指令。这会绕过当前的工具请求，并给 Claude 一个全新的指令来执行。
  </Tab>
</Tabs>

## 处理澄清问题

当 Claude 需要关于有多种有效方法的任务的更多指导时，它会调用 `AskUserQuestion` 工具。这会触发你的 `canUseTool` 回调，`toolName` 设为 `AskUserQuestion`。输入包含 Claude 的问题作为多选选项，你将其展示给用户并返回他们的选择。

<Tip>
  澄清问题在 [`plan` 模式](/en/agent-sdk/permissions#plan-mode-plan)中尤为常见，Claude 会在提出计划之前探索代码库并提问。这使得 plan 模式非常适合你希望 Claude 在进行更改之前收集需求的交互式工作流。
</Tip>

以下步骤展示如何处理澄清问题：

<Steps>
  <Step title="传递 canUseTool 回调">
    在查询选项中传递 `canUseTool` 回调。默认情况下，`AskUserQuestion` 是可用的。如果你指定了 `tools` 数组来限制 Claude 的能力（例如，只有 `Read`、`Glob` 和 `Grep` 的只读智能体），请在该数组中包含 `AskUserQuestion`。否则，Claude 将无法提出澄清问题：

    <CodeGroup>
      ```python Python theme={null}
      async for message in query(
          prompt="Analyze this codebase",
          options=ClaudeAgentOptions(
              # Include AskUserQuestion in your tools list
              tools=["Read", "Glob", "Grep", "AskUserQuestion"],
              can_use_tool=can_use_tool,
          ),
      ):
          print(message)
      ```

      ```typescript TypeScript theme={null}
      for await (const message of query({
        prompt: "Analyze this codebase",
        options: {
          // Include AskUserQuestion in your tools list
          tools: ["Read", "Glob", "Grep", "AskUserQuestion"],
          canUseTool: async (toolName, input) => {
            // Handle clarifying questions here
          }
        }
      })) {
        console.log(message);
      }
      ```
    </CodeGroup>
  </Step>

  <Step title="检测 AskUserQuestion">
    在回调中，检查 `toolName` 是否等于 `AskUserQuestion`，以便与其他工具区别处理：

    <CodeGroup>
      ```python Python theme={null}
      async def can_use_tool(tool_name: str, input_data: dict, context):
          if tool_name == "AskUserQuestion":
              # Your implementation to collect answers from the user
              return await handle_clarifying_questions(input_data)
          # Handle other tools normally
          return await prompt_for_approval(tool_name, input_data)
      ```

      ```typescript TypeScript theme={null}
      canUseTool: async (toolName, input) => {
        if (toolName === "AskUserQuestion") {
          // Your implementation to collect answers from the user
          return handleClarifyingQuestions(input);
        }
        // Handle other tools normally
        return promptForApproval(toolName, input);
      };
      ```
    </CodeGroup>
  </Step>

  <Step title="解析问题输入">
    输入包含 Claude 的问题，位于 `questions` 数组中。每个问题有 `question`（要显示的文本）、`options`（选项）和 `multiSelect`（是否允许多选）：

    ```json theme={null}
    {
      "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
        }
      ]
    }
    ```

    完整字段描述请参阅[问题格式](#question-format)。
  </Step>

  <Step title="收集用户的答案">
    向用户展示问题并收集他们的选择。具体方式取决于你的应用：终端提示、网页表单、移动对话框等。
  </Step>

  <Step title="将答案返回给 Claude">
    构建 `answers` 对象，它是一个记录，其中每个键是 `question` 文本，每个值是所选选项的 `label`：

    | 来自问题对象                                     | 用作 |
    | ------------------------------------------------------------ | ------ |
    | `question` 字段（如 `"How should I format the output?"`） | 键    |
    | 所选选项的 `label` 字段（如 `"Summary"`）          | 值  |

    对于多选问题，传递标签数组或用 `", "` 连接。如果你[支持自由文本输入](#support-free-text-input)，使用用户的自定义文本作为值。

    <CodeGroup>
      ```python Python theme={null}
      return PermissionResultAllow(
          updated_input={
              "questions": input_data.get("questions", []),
              "answers": {
                  "How should I format the output?": "Summary",
                  "Which sections should I include?": ["Introduction", "Conclusion"],
              },
          }
      )
      ```

      ```typescript TypeScript theme={null}
      return {
        behavior: "allow",
        updatedInput: {
          questions: input.questions,
          answers: {
            "How should I format the output?": "Summary",
            "Which sections should I include?": "Introduction, Conclusion"
          }
        }
      };
      ```
    </CodeGroup>
  </Step>
</Steps>

### 问题格式

输入包含 Claude 生成的问题，位于 `questions` 数组中。每个问题有以下字段：

| 字段         | 描述                                                                                                                             |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `question`    | 要显示的完整问题文本                                                                                                       |
| `header`      | 问题的简短标签（最多 12 个字符）                                                                                        |
| `options`     | 2-4 个选项的数组，每个选项有 `label` 和 `description`。TypeScript：可选 `preview`（见[下文](#option-previews-type-script)） |
| `multiSelect` | 如果为 `true`，用户可以选择多个选项                                                                                            |

你的回调接收的结构：

```json theme={null}
{
  "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
    }
  ]
}
```

#### 选项预览（TypeScript）

`toolConfig.askUserQuestion.previewFormat` 为每个选项添加 `preview` 字段，以便你的应用可以在标签旁边显示视觉预览。不设置此项时，Claude 不会生成预览，该字段不存在。

| `previewFormat` | `preview` 包含                                                                                            |
| :-------------- | :------------------------------------------------------------------------------------------------------------ |
| 未设置（默认） | 字段不存在。Claude 不生成预览。                                                           |
| `"markdown"`    | ASCII 艺术和围栏代码块                                                                              |
| `"html"`        | 带样式的 `<div>` 片段（SDK 在回调运行前拒绝 `<script>`、`<style>` 和 `<!DOCTYPE>`） |

该格式适用于会话中的所有问题。Claude 在视觉比较有帮助的选项（布局选择、配色方案）上包含 `preview`，在不需要的选项（是/否确认、纯文本选择）上省略它。渲染前请检查是否为 `undefined`。

```typescript theme={null}
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 is an HTML string or undefined
      return { behavior: "allow", updatedInput: input };
    }
  }
})) {
  // ...
}
```

带有 HTML 预览的选项：

```json theme={null}
{
  "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>"
}
```

### 响应格式

返回一个 `answers` 对象，将每个问题的 `question` 字段映射到所选选项的 `label`：

| 字段       | 描述                                                              |
| ----------- | ------------------------------------------------------------------------ |
| `questions` | 透传原始问题数组（工具处理所必需） |
| `answers`   | 对象，键为问题文本，值为所选标签       |

对于多选问题，传递标签数组或用 `", "` 连接。对于自由文本输入，直接使用用户的自定义文本。

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

#### 支持自由文本输入

Claude 的预定义选项并不总是能满足用户的需求。要让用户输入自己的答案：

* 在 Claude 的选项之后显示一个额外的"其他"选项，接受文本输入
* 使用用户的自定义文本作为答案值（而不是"其他"这个词）

完整的实现请参见下方的[完整示例](#complete-example)。

### 完整示例

当 Claude 需要用户输入才能继续时，它会提出澄清问题。例如，当被要求帮助决定移动应用的技术栈时，Claude 可能会询问跨平台 vs 原生、后端偏好或目标平台。这些问题帮助 Claude 做出符合用户偏好的决定，而不是猜测。

此示例在终端应用中处理这些问题。以下是每个步骤发生的情况：

1. **路由请求**：`canUseTool` 回调检查工具名称是否为 `"AskUserQuestion"` 并路由到专用处理器
2. **显示问题**：处理器循环遍历 `questions` 数组并打印每个问题及编号选项
3. **收集输入**：用户可以输入数字选择选项，或直接输入自由文本（如 "jquery"、"i don't know"）
4. **映射答案**：代码检查输入是数字（使用选项的 label）还是自由文本（直接使用文本）
5. **返回给 Claude**：响应包含原始 `questions` 数组和 `answers` 映射

<CodeGroup>
  ```python Python theme={null}
  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:
      """Parse user input as option number(s) or free text."""
      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:
      """Display Claude's questions and collect user answers."""
      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:
      # Route AskUserQuestion to our question handler
      if tool_name == "AskUserQuestion":
          return await handle_ask_user_question(input_data)
      # Auto-approve other tools for this example
      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",
          },
      }


  # Required workaround: dummy hook keeps the stream open for 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())
  ```

  ```typescript TypeScript theme={null}
  import { query } from "@anthropic-ai/claude-agent-sdk";
  import * as readline from "readline/promises";

  // Helper to prompt user for input in the terminal
  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;
  }

  // Parse user input as option number(s) or free text
  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;
  }

  // Display Claude's questions and collect user answers
  async function handleAskUserQuestion(input: any) {
    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);
    }

    // Return the answers to Claude (must include original questions)
    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) => {
          // Route AskUserQuestion to our question handler
          if (toolName === "AskUserQuestion") {
            return handleAskUserQuestion(input);
          }
          // Auto-approve other tools for this example
          return { behavior: "allow", updatedInput: input };
        }
      }
    })) {
      if ("result" in message) console.log(message.result);
    }
  }

  main();
  ```
</CodeGroup>

## 限制

* **子智能体**：`AskUserQuestion` 目前在通过 Agent 工具生成的子智能体中不可用
* **问题限制**：每次 `AskUserQuestion` 调用支持 1-4 个问题，每个问题有 2-4 个选项

## 获取用户输入的其他方式

`canUseTool` 回调和 `AskUserQuestion` 工具涵盖了大多数审批和澄清场景，但 SDK 还提供了其他获取用户输入的方式：

### 流式输入

在以下情况下使用[流式输入](/en/agent-sdk/streaming-vs-single-mode)：

* **在任务中途中断智能体**：在 Claude 工作时发送取消信号或改变方向
* **提供额外上下文**：在 Claude 提问之前添加它需要的信息
* **构建聊天界面**：让用户在长时间运行的操作期间发送后续消息

流式输入非常适合对话式 UI，用户在整个执行过程中与智能体交互，而不仅仅在审批检查点。

### 自定义工具

在以下情况下使用[自定义工具](/en/agent-sdk/custom-tools)：

* **收集结构化输入**：构建超越 `AskUserQuestion` 多选格式的表单、向导或多步骤工作流
* **集成外部审批系统**：连接到现有的工单、工作流或审批平台
* **实现特定领域的交互**：创建针对你的应用需求定制的工具，如代码审查界面或部署检查清单

自定义工具给你完全的交互控制权，但比使用内置的 `canUseTool` 回调需要更多的实现工作。

## 相关资源

* [配置权限](/en/agent-sdk/permissions)：设置权限模式和规则
* [使用钩子控制执行](/en/agent-sdk/hooks)：在智能体生命周期的关键点运行自定义代码
* [TypeScript SDK 参考](/en/agent-sdk/typescript#canusetool)：完整的 canUseTool API 文档
