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-TW/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-TW/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-TW/agent-sdk/hooks)。Hooks 在 `canUseTool` 之前執行,可以根據您自己的邏輯允許、拒絕或修改請求。您也可以使用 [`PermissionRequest` hook](/zh-TW/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-TW/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-TW/agent-sdk/python#tool-input%2Foutput-types) | [TypeScript](/zh-TW/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-TW/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-TW/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-TW/agent-sdk/streaming-vs-single-mode)向 Claude 直接發送新指令。這會繞過目前的工具請求,並為 Claude 提供全新的指令來遵循。
361 </Tab>
362</Tabs>
363
364## 處理澄清問題
365
366當 Claude 需要在具有多個有效方法的任務上獲得更多方向時,它會呼叫 `AskUserQuestion` 工具。這會使用 `toolName` 設定為 `AskUserQuestion` 的方式觸發您的 `canUseTool` 回呼。輸入包含 Claude 的問題作為多選選項,您將其顯示給使用者並返回他們的選擇。
367
368<Tip>
369 澄清問題在 [`plan` 模式](/zh-TW/agent-sdk/permissions#plan-mode-plan)中特別常見,Claude 在其中探索程式碼庫並在提出計畫前提出問題。這使得計畫模式非常適合互動式工作流程,您希望 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 輸入在 `questions` 陣列中包含 Claude 的問題。每個問題都有 `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 向使用者呈現問題並收集他們的選擇。您如何執行此操作取決於您的應用程式:終端機提示、網路表單、行動對話框等。
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輸入在 `questions` 陣列中包含 Claude 生成的問題。每個問題都有這些欄位:
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-TW/agent-sdk/streaming-vs-single-mode):
789
790* **在任務中途中斷代理**:在 Claude 工作時發送取消信號或改變方向
791* **提供額外上下文**:添加 Claude 需要的資訊,無需等待它詢問
792* **構建聊天介面**:讓使用者在長時間運行的操作期間發送後續訊息
793
794串流輸入非常適合對話式 UI,使用者在整個執行過程中與代理互動,而不僅僅在批准檢查點。
795
796### 自訂工具
797
798當您需要以下情況時,使用[自訂工具](/zh-TW/agent-sdk/custom-tools):
799
800* **收集結構化輸入**:構建超越 `AskUserQuestion` 多選格式的表單、精靈或多步驟工作流程
801* **整合外部批准系統**:連接到現有的票務、工作流程或批准平台
802* **實現特定領域的互動**:創建針對您應用程式需求的工具,例如程式碼審查介面或部署檢查清單
803
804自訂工具讓您完全控制互動,但需要比使用內建 `canUseTool` 回呼更多的實現工作。
805
806## 相關資源
807
808* [配置權限](/zh-TW/agent-sdk/permissions):設定權限模式和規則
809* [使用 hooks 控制執行](/zh-TW/agent-sdk/hooks):在代理生命週期的關鍵點執行自訂程式碼
810* [TypeScript SDK 參考](/zh-TW/agent-sdk/typescript#canusetool):完整 canUseTool API 文件