Skip to main content

จัดการการอนุมัติและ 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:

Python
async def handle_tool_request(tool_name, input_data, context):
# Prompt ผู้ใช้และส่งคืน allow หรือ deny
...


options = ClaudeAgentOptions(can_use_tool=handle_tool_request)
TypeScript
async function handleToolRequest(toolName, input, options) {
// options รวม { signal: AbortSignal, suggestions?: PermissionUpdate[] }
// Prompt ผู้ใช้และส่งคืน allow หรือ deny
}

const options = { canUseTool: handleToolRequest };

callback จะ fire ในสองกรณี:

  1. Tool ต้องการการอนุมัติ: Claude ต้องการใช้ tool ที่ไม่ได้ auto-approved โดย permission rules หรือ modes ตรวจสอบ tool_name สำหรับ tool (เช่น "Bash", "Write")
  2. Claude ถามคำถาม: Claude เรียก tool AskUserQuestion ตรวจสอบว่า tool_name == "AskUserQuestion" เพื่อจัดการแตกต่างกัน
note

เพื่อ 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")
inputParameters ที่ Claude ส่งไปยัง tool
options (TS) / context (Python)Context เพิ่มเติมรวมถึง suggestions optional และ cancellation signal

object input มี tool-specific parameters ตัวอย่างทั่วไป:

ToolInput fields
Bashcommand, description, timeout
Writefile_path, content
Editfile_path, old_string, new_string
Readfile_path, offset, limit

ตัวอย่างต่อไปนี้ขอให้ Claude สร้างและลบไฟล์ทดสอบ เมื่อ Claude พยายามดำเนินการแต่ละอย่าง callback จะ print tool request ไปยัง terminal และ prompt สำหรับการอนุมัติ y/n:

Python
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())
TypeScript
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);
}
note

ใน Python can_use_tool ต้องการ streaming mode และ hook PreToolUse ที่ส่งคืน {"continue_": True} เพื่อเก็บ stream เปิดไว้ หากไม่มี hook นี้ stream จะปิดก่อนที่จะ invoke permission callback ได้

ตอบสนองต่อ Tool Requests

callback ของคุณส่งคืน response types สองแบบ:

ResponsePythonTypeScript
AllowPermissionResultAllow(updated_input=...){ behavior: "allow", updatedInput }
DenyPermissionResultDeny(message=...){ behavior: "deny", message }

นอกเหนือจากการ allow หรือ deny คุณสามารถแก้ไข input ของ tool หรือให้ context ที่ช่วย Claude ปรับแนวทาง:

Approve with changes - แก้ไข input ก่อนการรัน:

Python
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 กลับเพื่อไม่ถามอีก:

Python
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 แต่แนะนำแนวทางอื่น:

TypeScript
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 ซึ่งคุณแสดงให้ผู้ใช้และส่งคืนการเลือกของพวกเขา

tip

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:

Python
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:

Python
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:

Python
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คำอธิบาย
questionText คำถามแบบเต็มที่จะแสดง
headerLabel สั้นสำหรับคำถาม (สูงสุด 12 ตัวอักษร)
optionsArray ของ 2-4 choices แต่ละอันมี label และ description
multiSelectถ้า true ผู้ใช้สามารถเลือกหลาย options

Option Previews (TypeScript)

toolConfig.askUserQuestion.previewFormat เพิ่ม field preview ให้กับแต่ละ option เพื่อให้ app ของคุณแสดง visual mockup:

previewFormatpreview มี
unset (default)Field ไม่มี Claude ไม่สร้าง previews
"markdown"ASCII art และ fenced code blocks
"html"<div> fragment ที่มี style
TypeScript
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:

Python
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 ที่เกี่ยวข้อง