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# 处理批准和用户输入
6
7> 向用户显示 Claude 的批准请求和澄清问题,然后将他们的决定返回给 SDK。
8
9在处理任务时,Claude 有时需要与用户进行沟通。它可能需要在删除文件前获得许可,或需要询问为新项目使用哪个数据库。您的应用程序需要向用户显示这些请求,以便 Claude 可以继续使用他们的输入。
10
11Claude 在两种情况下请求用户输入:当它需要**使用工具的权限**(如删除文件或运行命令)时,以及当它有**澄清问题**(通过 `AskUserQuestion` 工具)时。两者都会触发您的 `canUseTool` 回调,该回调会暂停执行,直到您返回响应。这与普通对话轮次不同,在普通对话轮次中 Claude 完成后等待您的下一条消息。
12
13对于澄清问题,Claude 生成问题和选项。您的角色是向用户呈现这些问题,并返回他们的选择。您不能向此流程添加自己的问题;如果您需要自己询问用户某些内容,请在应用程序逻辑中单独进行。
14
15回调可以无限期地保持待处理状态。执行保持暂停状态,直到您的回调返回,SDK 仅在查询本身被取消时才取消等待。如果用户可能需要比您的进程能够合理保持运行的时间更长的时间来响应,TypeScript SDK 支持 [`defer` hook 决定](/zh-CN/hooks#defer-a-tool-call-for-later),它允许进程退出并稍后从持久化会话恢复;此选项在 Python SDK 中不可用。
16
17本指南向您展示如何检测每种类型的请求并做出适当的响应。
18
19## 检测 Claude 何时需要输入
20
21在您的查询选项中传递 `canUseTool` 回调。每当 Claude 需要用户输入时,回调就会触发,接收工具名称和输入作为参数:
22
23<CodeGroup>
24 ```python Python theme={null}
25 async def handle_tool_request(tool_name, input_data, context):
26 # 提示用户并返回允许或拒绝
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 // 提示用户并返回允许或拒绝
37 }
38
39 const options = { canUseTool: handleToolRequest };
40 ```
41</CodeGroup>
42
43回调在两种情况下触发:
44
451. **工具需要批准**:Claude 想要使用不被[权限规则](/zh-CN/agent-sdk/permissions)或模式自动批准的工具。检查 `tool_name` 以获取工具(例如 `"Bash"`、`"Write"`)。
462. **Claude 提出问题**:Claude 调用 `AskUserQuestion` 工具。检查 `tool_name == "AskUserQuestion"` 以不同方式处理它。如果您指定 `tools` 数组,请包含 `AskUserQuestion` 以使其工作。有关详细信息,请参阅[处理澄清问题](#handle-clarifying-questions)。
47
48<Note>
49 要自动允许或拒绝工具而不提示用户,请改用 [hooks](/zh-CN/agent-sdk/hooks)。Hooks 在 `canUseTool` 之前执行,可以根据您自己的逻辑允许、拒绝或修改请求。您还可以使用 [`PermissionRequest` hook](/zh-CN/agent-sdk/hooks#available-hooks) 在 Claude 等待批准时发送外部通知(Slack、电子邮件、推送)。
50</Note>
51
52## 处理工具批准请求
53
54一旦您在查询选项中传递了 `canUseTool` 回调,当 Claude 想要使用不被自动批准的工具时,它就会触发。您的回调接收三个参数:
55
56| 参数 | 描述 |
57| ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
58| `toolName` | Claude 想要使用的工具的名称(例如 `"Bash"`、`"Write"`、`"Edit"`) |
59| `input` | Claude 传递给工具的参数。内容因工具而异。 |
60| `options` (TS) / `context` (Python) | 附加上下文,包括可选的 `suggestions`(建议的 `PermissionUpdate` 条目以避免重新提示)和取消信号。在 TypeScript 中,`signal` 是 `AbortSignal`;在 Python 中,信号字段保留供将来使用。有关 Python,请参阅 [`ToolPermissionContext`](/zh-CN/agent-sdk/python#toolpermissioncontext)。 |
61
62`input` 对象包含工具特定的参数。常见示例:
63
64| 工具 | 输入字段 |
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
71有关完整的输入架构,请参阅 SDK 参考:[Python](/zh-CN/agent-sdk/python#tool-input%2Foutput-types) | [TypeScript](/zh-CN/agent-sdk/typescript#tool-input-types)。
72
73您可以向用户显示此信息,以便他们可以决定是否允许或拒绝该操作,然后返回适当的响应。
74
75以下示例要求 Claude 创建和删除测试文件。当 Claude 尝试每个操作时,回调会将工具请求打印到终端并提示进行 y/n 批准。
76
77<CodeGroup>
78 ```python Python theme={null}
79 import asyncio
80
81 from claude_agent_sdk import ClaudeAgentOptions, ResultMessage, query
82 from claude_agent_sdk.types import (
83 HookMatcher,
84 PermissionResultAllow,
85 PermissionResultDeny,
86 ToolPermissionContext,
87 )
88
89
90 async def can_use_tool(
91 tool_name: str, input_data: dict, context: ToolPermissionContext
92 ) -> PermissionResultAllow | PermissionResultDeny:
93 # 显示工具请求
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 # 获取用户批准
103 response = input("Allow this action? (y/n): ")
104
105 # 根据用户的响应返回允许或拒绝
106 if response.lower() == "y":
107 # 允许:工具使用原始(或修改的)输入执行
108 return PermissionResultAllow(updated_input=input_data)
109 else:
110 # 拒绝:工具不执行,Claude 看到该消息
111 return PermissionResultDeny(message="User denied this action")
112
113
114 # 必需的解决方法:虚拟 hook 保持流打开以供 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 // 帮助程序在终端中提示用户输入
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 // 显示工具请求
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 // 获取用户批准
176 const response = await prompt("Allow this action? (y/n): ");
177
178 // 根据用户的响应返回允许或拒绝
179 if (response.toLowerCase() === "y") {
180 // 允许:工具使用原始(或修改的)输入执行
181 return { behavior: "allow", updatedInput: input };
182 } else {
183 // 拒绝:工具不执行,Claude 看到该消息
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 在 Python 中,`can_use_tool` 需要[流模式](/zh-CN/agent-sdk/streaming-vs-single-mode)和返回 `{"continue_": True}` 的 `PreToolUse` hook 以保持流打开。没有此 hook,流会在权限回调被调用之前关闭。
196</Note>
197
198此示例使用 y/n 流,其中除 `y` 之外的任何输入都被视为拒绝。在实践中,您可能会构建一个更丰富的 UI,让用户修改请求、提供反馈或完全重定向 Claude。有关所有响应方式,请参阅[响应工具请求](#respond-to-tool-requests)。
199
200### 响应工具请求
201
202您的回调返回两种响应类型之一:
203
204| 响应 | Python | TypeScript |
205| ------ | ------------------------------------------ | ------------------------------------- |
206| **允许** | `PermissionResultAllow(updated_input=...)` | `{ behavior: "allow", updatedInput }` |
207| **拒绝** | `PermissionResultDeny(message=...)` | `{ behavior: "deny", message }` |
208
209允许时,传递工具输入(原始或修改的)。拒绝时,提供说明原因的消息。Claude 会看到此消息并可能调整其方法。
210
211<CodeGroup>
212 ```python Python theme={null}
213 from claude_agent_sdk.types import PermissionResultAllow, PermissionResultDeny
214
215 # 允许工具执行
216 return PermissionResultAllow(updated_input=input_data)
217
218 # 阻止工具
219 return PermissionResultDeny(message="User rejected this action")
220 ```
221
222 ```typescript TypeScript theme={null}
223 // 允许工具执行
224 return { behavior: "allow", updatedInput: input };
225
226 // 阻止工具
227 return { behavior: "deny", message: "User rejected this action" };
228 ```
229</CodeGroup>
230
231除了允许或拒绝之外,您还可以修改工具的输入或提供帮助 Claude 调整其方法的上下文:
232
233* **批准**:让工具按 Claude 请求的方式执行
234* **批准并进行更改**:在执行前修改输入(例如,清理路径、添加约束)
235* **拒绝**:阻止工具并告诉 Claude 原因
236* **建议替代方案**:阻止但指导 Claude 朝向用户想要的方向
237* **完全重定向**:使用[流输入](/zh-CN/agent-sdk/streaming-vs-single-mode)向 Claude 发送全新指令
238
239<Tabs>
240 <Tab title="批准">
241 用户按原样批准该操作。从您的回调中传递 `input` 不变,工具完全按 Claude 请求的方式执行。
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="批准并进行更改">
269 用户批准但想先修改请求。您可以在工具执行前更改输入。Claude 会看到结果,但不会被告知您更改了任何内容。对于清理参数、添加约束或限制访问范围很有用。
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 # 用户批准,但将所有命令限制在沙箱中
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 // 用户批准,但将所有命令限制在沙箱中
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="拒绝">
301 用户不希望发生此操作。阻止工具并提供说明原因的消息。Claude 会看到此消息并可能尝试不同的方法。
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="建议替代方案">
330 用户不想要此特定操作,但有不同的想法。阻止工具并在您的消息中包含指导。Claude 将阅读此内容并根据您的反馈决定如何继续。
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 # 用户不想删除,建议改为存档
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 // 用户不想删除,建议改为存档
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="完全重定向">
360 对于完全改变方向(不仅仅是轻推),使用[流输入](/zh-CN/agent-sdk/streaming-vs-single-mode)向 Claude 发送新指令。这绕过当前工具请求并为 Claude 提供全新指令来遵循。
361 </Tab>
362</Tabs>
363
364## 处理澄清问题
365
366当 Claude 需要在具有多个有效方法的任务上获得更多指导时,它会调用 `AskUserQuestion` 工具。这会触发您的 `canUseTool` 回调,其中 `toolName` 设置为 `AskUserQuestion`。输入包含 Claude 的问题作为多选选项,您向用户显示这些问题并返回他们的选择。
367
368<Tip>
369 澄清问题在 [`plan` 模式](/zh-CN/agent-sdk/permissions#plan-mode-plan)中特别常见,其中 Claude 探索代码库并在提出计划前提出问题。这使 plan 模式非常适合交互式工作流,您希望 Claude 在进行更改前收集需求。
370</Tip>
371
372以下步骤显示如何处理澄清问题:
373
374<Steps>
375 <Step title="传递 canUseTool 回调">
376 在您的查询选项中传递 `canUseTool` 回调。默认情况下,`AskUserQuestion` 可用。如果您指定 `tools` 数组来限制 Claude 的功能(例如,仅具有 `Read`、`Glob` 和 `Grep` 的只读代理),请在该数组中包含 `AskUserQuestion`。否则,Claude 将无法提出澄清问题:
377
378 <CodeGroup>
379 ```python Python theme={null}
380 async for message in query(
381 prompt="Analyze this codebase",
382 options=ClaudeAgentOptions(
383 # 在您的工具列表中包含 AskUserQuestion
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 // 在您的工具列表中包含 AskUserQuestion
396 tools: ["Read", "Glob", "Grep", "AskUserQuestion"],
397 canUseTool: async (toolName, input) => {
398 // 在此处处理澄清问题
399 }
400 }
401 })) {
402 console.log(message);
403 }
404 ```
405 </CodeGroup>
406 </Step>
407
408 <Step title="检测 AskUserQuestion">
409 在您的回调中,检查 `toolName` 是否等于 `AskUserQuestion` 以不同方式处理它与其他工具:
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 # 您从用户收集答案的实现
416 return await handle_clarifying_questions(input_data)
417 # 正常处理其他工具
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 // 您从用户收集答案的实现
425 return handleClarifyingQuestions(input);
426 }
427 // 正常处理其他工具
428 return promptForApproval(toolName, input);
429 };
430 ```
431 </CodeGroup>
432 </Step>
433
434 <Step title="解析问题输入">
435 输入包含 Claude 在 `questions` 数组中的问题。每个问题都有 `question`(要显示的文本)、`options`(选择)和 `multiSelect`(是否允许多个选择):
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 有关完整字段描述,请参阅[问题格式](#question-format)。
463 </Step>
464
465 <Step title="从用户收集答案">
466 向用户呈现问题并收集他们的选择。您如何执行此操作取决于您的应用程序:终端提示、Web 表单、移动对话框等。
467 </Step>
468
469 <Step title="将答案返回给 Claude">
470 将 `answers` 对象构建为记录,其中每个键是 `question` 文本,每个值是所选选项的 `label`:
471
472 | 来自问题对象 | 用作 |
473 | ----------------------------------------------------- | -- |
474 | `question` 字段(例如 `"How should I format the output?"`) | 键 |
475 | 所选选项的 `label` 字段(例如 `"Summary"`) | 值 |
476
477 对于多选问题,传递标签数组或用 `", "` 连接它们。如果您[支持自由文本输入](#support-free-text-input),使用用户的自定义文本作为值。
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### 问题格式
509
510输入包含 Claude 在 `questions` 数组中生成的问题。每个问题都有这些字段:
511
512| 字段 | 描述 |
513| ------------- | ------------------------------------------------------------------------------------------------------ |
514| `question` | 要显示的完整问题文本 |
515| `header` | 问题的短标签(最多 12 个字符) |
516| `options` | 2-4 个选择的数组,每个都有 `label` 和 `description`。TypeScript:可选 `preview`(请参阅[下文](#option-previews-type-script)) |
517| `multiSelect` | 如果为 `true`,用户可以选择多个选项 |
518
519您的回调接收的结构:
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#### 选项预览 (TypeScript)
538
539`toolConfig.askUserQuestion.previewFormat` 向每个选项添加 `preview` 字段,以便您的应用可以在标签旁显示视觉模型。没有此设置,Claude 不会生成预览,该字段不存在。
540
541| `previewFormat` | `preview` 包含 |
542| :-------------- | :----------------------------------------------------------------- |
543| 未设置(默认) | 字段不存在。Claude 不会生成预览。 |
544| `"markdown"` | ASCII 艺术和围栏代码块 |
545| `"html"` | 样式的 `<div>` 片段(SDK 在您的回调运行前拒绝 `<script>`、`<style>` 和 `<!DOCTYPE>`) |
546
547该格式适用于会话中的所有问题。Claude 在视觉比较有帮助的选项上包含 `preview`(布局选择、配色方案),并在不会的地方省略它(是/否确认、仅文本选择)。在呈现前检查 `undefined`。
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 是 HTML 字符串或 undefined
560 return { behavior: "allow", updatedInput: input };
561 }
562 }
563})) {
564 // ...
565}
566```
567
568带有 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### 响应格式
579
580返回 `answers` 对象,将每个问题的 `question` 字段映射到所选选项的 `label`:
581
582| 字段 | 描述 |
583| ----------- | ------------------ |
584| `questions` | 传递原始问题数组(工具处理需要) |
585| `answers` | 对象,其中键是问题文本,值是所选标签 |
586
587对于多选问题,传递标签数组或用 `", "` 连接它们。对于自由文本输入,直接使用用户的自定义文本。
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#### 支持自由文本输入
602
603Claude 的预定义选项并不总是涵盖用户想要的内容。要让用户输入自己的答案:
604
605* 在 Claude 的选项后显示额外的"其他"选择,接受文本输入
606* 使用用户的自定义文本作为答案值(不是单词"其他")
607
608有关完整实现,请参阅下面的[完整示例](#complete-example)。
609
610### 完整示例
611
612当 Claude 需要用户输入来继续时,它会提出澄清问题。例如,当被要求帮助为移动应用程序决定技术栈时,Claude 可能会询问跨平台与原生、后端偏好或目标平台。这些问题帮助 Claude 做出与用户偏好相匹配的决定,而不是猜测。
613
614此示例在终端应用程序中处理这些问题。以下是每个步骤发生的情况:
615
6161. **路由请求**:`canUseTool` 回调检查工具名称是否为 `"AskUserQuestion"` 并路由到专用处理程序
6172. **显示问题**:处理程序循环遍历 `questions` 数组并打印每个问题及编号选项
6183. **收集输入**:用户可以输入数字来选择选项,或直接输入自由文本(例如"jquery"、"i don't know")
6194. **映射答案**:代码检查输入是数字(使用选项的标签)还是自由文本(使用文本直接)
6205. **返回给 Claude**:响应包括原始 `questions` 数组和 `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 """将用户输入解析为选项编号或自由文本。"""
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 """显示 Claude 的问题并收集用户答案。"""
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 # 将 AskUserQuestion 路由到我们的问题处理程序
670 if tool_name == "AskUserQuestion":
671 return await handle_ask_user_question(input_data)
672 # 为此示例自动批准其他工具
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 # 必需的解决方法:虚拟 hook 保持流打开以供 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 // 帮助程序在终端中提示用户输入
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 // 将用户输入解析为选项编号或自由文本
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 // 显示 Claude 的问题并收集用户答案
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 // 将答案返回给 Claude(必须包括原始问题)
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 // 将 AskUserQuestion 路由到我们的问题处理程序
761 if (toolName === "AskUserQuestion") {
762 return handleAskUserQuestion(input);
763 }
764 // 为此示例自动批准其他工具
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## 限制
778
779* **子代理**:`AskUserQuestion` 目前在通过 Agent 工具生成的子代理中不可用
780* **问题限制**:每个 `AskUserQuestion` 调用支持 1-4 个问题,每个 2-4 个选项
781
782## 获取用户输入的其他方式
783
784`canUseTool` 回调和 `AskUserQuestion` 工具涵盖了大多数批准和澄清场景,但 SDK 提供了其他从用户获取输入的方式:
785
786### 流输入
787
788当您需要以下情况时,使用[流输入](/zh-CN/agent-sdk/streaming-vs-single-mode):
789
790* **在任务中断代理**:在 Claude 工作时发送取消信号或改变方向
791* **提供额外上下文**:添加 Claude 需要的信息而无需等待它提出问题
792* **构建聊天界面**:让用户在长时间运行的操作期间发送后续消息
793
794流输入非常适合对话式 UI,用户在整个执行过程中与代理交互,而不仅仅在批准检查点。
795
796### 自定义工具
797
798当您需要以下情况时,使用[自定义工具](/zh-CN/agent-sdk/custom-tools):
799
800* **收集结构化输入**:构建超越 `AskUserQuestion` 多选格式的表单、向导或多步工作流
801* **集成外部批准系统**:连接到现有的票务、工作流或批准平台
802* **实现特定领域的交互**:创建针对您的应用程序需求定制的工具,如代码审查界面或部署清单
803
804自定义工具让您完全控制交互,但需要比使用内置 `canUseTool` 回调更多的实现工作。
805
806## 相关资源
807
808* [配置权限](/zh-CN/agent-sdk/permissions):设置权限模式和规则
809* [使用 hooks 控制执行](/zh-CN/agent-sdk/hooks):在代理生命周期的关键点运行自定义代码
810* [TypeScript SDK 参考](/zh-CN/agent-sdk/typescript#canusetool):完整的 canUseTool API 文档