จัดการการอนุมัติและ Input จากผู้ใช้
แสดง approval requests และ clarifying questions ของ Claude ให้ผู้ใช้ จากนั้นส่งคืนการตัดสินใจของพวกเขาไปยัง SDK
ขณะทำงาน Claude บางครั้งต้องตรวจสอบกับผู้ใช้ อาจต้องการ permission ก่อนลบไฟล์ หรือต้องถามว่าจะใช้ database ใดสำหรับ project ใหม่ application ของคุณต้องแสดง requests เหล่านี้ให้ผู้ใช้เพื่อให้ Claude สามารถดำเนินต่อด้วย input ของพวกเขา
Claude ขอ input ของผู้ใช้ในสองสถานการณ์: เมื่อต้องการ permission ในการใช้ tool (เช่น การลบไฟล์หรือรัน commands) และเมื่อมี clarifying questions (ผ่าน tool AskUserQuestion) ทั้งสองกรณีจะ trigger callback canUseTool ของคุณ ซึ่งหยุดการทำงานจนกว่าคุณจะส่งคืน response
callback สามารถ pending ได้ไม่จำกัด การทำงานจะหยุดจนกว่า callback ของคุณจะส่งคืน และ SDK จะยกเลิกการรอเมื่อ query เองถูกยกเลิก หากผู้ใช้อาจใช้เวลานานกว่าที่ process ของคุณสามารถรออยู่ได้อย่างสมเหตุสมผล ให้ส่งคืน hook decision defer
ตรวจจับเมื่อ Claude ต้องการ Input
ส่ง callback canUseTool ใน query options ของคุณ callback จะ fire เมื่อ Claude ต้องการ user input รับชื่อ tool และ input เป็น arguments:
async def handle_tool_request(tool_name, input_data, context):
# Prompt ผู้ใช้และส่งคืน allow หรือ deny
...
options = ClaudeAgentOptions(can_use_tool=handle_tool_request)
async function handleToolRequest(toolName, input, options) {
// options รวม { signal: AbortSignal, suggestions?: PermissionUpdate[] }
// Prompt ผู้ใช้และส่งคืน allow หรือ deny
}
const options = { canUseTool: handleToolRequest };
callback จะ fire ในสองกรณี:
- Tool ต้องการการอนุมัติ: Claude ต้องการใช้ tool ที่ไม่ได้ auto-approved โดย permission rules หรือ modes ตรวจสอบ
tool_nameสำหรับ tool (เช่น"Bash","Write") - Claude ถามคำถาม: Claude เรียก tool
AskUserQuestionตรวจสอบว่าtool_name == "AskUserQuestion"เพื่อจัดการแตกต่างกัน
เพื่อ allow หรือ deny tools โดยอัตโนมัติโดยไม่ prompt ผู้ใช้ ให้ใช้ hooks แทน Hooks รัน callback canUseTool และสามารถ allow, deny หรือแก้ไข requests ตาม logic ของคุณเองได้
จัดการ Tool Approval Requests
เมื่อคุณส่ง callback canUseTool ใน query options แล้ว มันจะ fire เมื่อ Claude ต้องการใช้ tool ที่ไม่ได้ auto-approved callback ของคุณรับสาม arguments:
| Argument | คำอธิบาย |
|---|---|
toolName | ชื่อของ tool ที่ Claude ต้องการใช้ (เช่น "Bash", "Write", "Edit") |
input | Parameters ที่ Claude ส่งไปยัง tool |
options (TS) / context (Python) | Context เพิ่มเติมรวมถึง suggestions optional และ cancellation signal |
object input มี tool-specific parameters ตัวอย่างทั่วไป:
| Tool | Input fields |
|---|---|
Bash | command, description, timeout |
Write | file_path, content |
Edit | file_path, old_string, new_string |
Read | file_path, offset, limit |
ตัวอย่างต่อไปนี้ขอให้ Claude สร้างและลบไฟล์ทดสอบ เมื่อ Claude พยายามดำเนินการแต่ละอย่าง callback จะ print tool request ไปยัง terminal และ prompt สำหรับการอนุมัติ y/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:
# แสดง 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}")
# รับการอนุมัติจากผู้ใช้
response = input("Allow this action? (y/n): ")
# ส่งคืน allow หรือ deny ตาม response ของผู้ใช้
if response.lower() == "y":
return PermissionResultAllow(updated_input=input_data)
else:
return PermissionResultDeny(message="User denied this action")
# Workaround ที่จำเป็น: dummy hook เก็บ stream เปิดไว้สำหรับ 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 เพื่อ prompt ผู้ใช้สำหรับ input ใน 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) => {
// แสดง 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)}`);
}
// รับการอนุมัติจากผู้ใช้
const response = await prompt("Allow this action? (y/n): ");
// ส่งคืน allow หรือ deny ตาม response ของผู้ใช้
if (response.toLowerCase() === "y") {
return { behavior: "allow", updatedInput: input };
} else {
return { behavior: "deny", message: "User denied this action" };
}
}
}
})) {
if ("result" in message) console.log(message.result);
}
ใน Python can_use_tool ต้องการ streaming mode และ hook PreToolUse ที่ส่งคืน {"continue_": True} เพื่อเก็บ stream เปิดไว้ หากไม่มี hook นี้ stream จะปิดก่อนที่จะ invoke permission callback ได้
ตอบสนองต่อ Tool Requests
callback ของคุณส่งคืน response types สองแบบ:
| Response | Python | TypeScript |
|---|---|---|
| Allow | PermissionResultAllow(updated_input=...) | { behavior: "allow", updatedInput } |
| Deny | PermissionResultDeny(message=...) | { behavior: "deny", message } |
นอกเหนือจากการ allow หรือ deny คุณสามารถแก้ไข input ของ tool หรือให้ context ที่ช่วย Claude ปรับแนวทาง:
Approve with changes - แก้ไข input ก่อนการรัน:
async def can_use_tool(tool_name, input_data, context):
if tool_name == "Bash":
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)
Approve and remember - echo suggestion กลับเพื่อไม่ถามอีก:
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")
Suggest alternative - block แต่แนะนำแนวทางอื่น:
canUseTool: async (toolName, input) => {
if (toolName === "Bash" && input.command.includes("rm")) {
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 };
};
จัดการ Clarifying Questions
เมื่อ Claude ต้องการทิศทางเพิ่มเติมในงานที่มีหลายแนวทางที่ถูกต้อง มันจะเรียก tool AskUserQuestion สิ่งนี้ trigger callback canUseTool ของคุณโดยตั้ง toolName เป็น AskUserQuestion input มีคำถามของ Claude เป็น multiple-choice options ซึ่งคุณแสดงให้ผู้ใช้และส่งคืนการเลือกของพวกเขา
Clarifying questions พบบ่อยเป็นพิเศษใน plan mode ซึ่ง Claude สำรวจ codebase และถามคำถามก่อนเสนอแผน ทำให้ plan mode เหมาะสำหรับ interactive workflows ที่คุณต้องการให้ Claude รวบรวม requirements ก่อนทำการเปลี่ยนแปลง
ขั้นตอนการจัดการ Clarifying Questions
ขั้นที่ 1: ส่ง callback canUseTool ใน query options และรวม AskUserQuestion ใน tools list หากคุณจำกัด Claude's tools:
async for message in query(
prompt="Analyze this codebase",
options=ClaudeAgentOptions(
tools=["Read", "Glob", "Grep", "AskUserQuestion"],
can_use_tool=can_use_tool,
),
):
print(message)
ขั้นที่ 2: ตรวจจับ AskUserQuestion ใน callback:
async def can_use_tool(tool_name: str, input_data: dict, context):
if tool_name == "AskUserQuestion":
return await handle_clarifying_questions(input_data)
return await prompt_for_approval(tool_name, input_data)
ขั้นที่ 3: Parse question input - input มีคำถามของ Claude ใน array questions:
{
"questions": [
{
"question": "How should I format the output?",
"header": "Format",
"options": [
{ "label": "Summary", "description": "Brief overview" },
{ "label": "Detailed", "description": "Full explanation" }
],
"multiSelect": false
}
]
}
ขั้นที่ 4: รวบรวมคำตอบจากผู้ใช้และ return ไปยัง Claude:
return PermissionResultAllow(
updated_input={
"questions": input_data.get("questions", []),
"answers": {
"How should I format the output?": "Summary",
"Which sections should I include?": ["Introduction", "Conclusion"],
},
}
)
Question Format
| Field | คำอธิบาย |
|---|---|
question | Text คำถามแบบเต็มที่จะแสดง |
header | Label สั้นสำหรับคำถาม (สูงสุด 12 ตัวอักษร) |
options | Array ของ 2-4 choices แต่ละอันมี label และ description |
multiSelect | ถ้า true ผู้ใช้สามารถเลือกหลาย options |
Option Previews (TypeScript)
toolConfig.askUserQuestion.previewFormat เพิ่ม field preview ให้กับแต่ละ option เพื่อให้ app ของคุณแสดง visual mockup:
previewFormat | preview มี |
|---|---|
| unset (default) | Field ไม่มี Claude ไม่สร้าง previews |
"markdown" | ASCII art และ fenced code blocks |
"html" | <div> fragment ที่มี style |
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 เป็น HTML string หรือ undefined
return { behavior: "allow", updatedInput: input };
}
}
})) {
// ...
}
ตัวอย่างครบถ้วน
ตัวอย่างนี้จัดการ clarifying questions ใน terminal application:
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 เป็น option number(s) หรือ 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:
"""แสดงคำถามของ Claude และรวบรวมคำตอบจากผู้ใช้"""
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:
if tool_name == "AskUserQuestion":
return await handle_ask_user_question(input_data)
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",
},
}
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())
ข้อจำกัด
- Subagents:
AskUserQuestionไม่มีให้ใช้ใน subagents ที่ spawn ผ่าน Agent tool ในปัจจุบัน - Question limits: แต่ละการเรียก
AskUserQuestionรองรับ 1-4 คำถาม แต่ละคำถามมี 2-4 options
วิธีอื่นในการรับ Input จากผู้ใช้
Streaming Input
ใช้ streaming input เมื่อต้องการ:
- Interrupt agent กลางงาน: ส่ง cancel signal หรือเปลี่ยนทิศทางขณะที่ Claude กำลังทำงาน
- ให้ context เพิ่มเติม: เพิ่มข้อมูลที่ Claude ต้องการโดยไม่รอให้มันถาม
- สร้าง chat interfaces: ให้ผู้ใช้ส่ง follow-up messages ระหว่าง long-running operations
Custom Tools
ใช้ custom tools เมื่อต้องการ:
- รวบรวม structured input: สร้าง forms, wizards หรือ multi-step workflows ที่เกินขอบเขต multiple-choice format ของ
AskUserQuestion - Integrate external approval systems: เชื่อมต่อกับ ticketing, workflow หรือ approval platforms ที่มีอยู่แล้ว
- Implement domain-specific interactions: สร้าง tools ที่ tailored กับความต้องการของ application ของคุณ
Resources ที่เกี่ยวข้อง
- Configure permissions: ตั้งค่า permission modes และ rules
- Control execution with hooks: รัน custom code ที่จุดสำคัญใน agent lifecycle
- TypeScript SDK reference: เอกสาร API canUseTool แบบเต็ม