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# Menangani persetujuan dan input pengguna
6
7> Tampilkan permintaan persetujuan Claude dan pertanyaan klarifikasi kepada pengguna, kemudian kembalikan keputusan mereka ke SDK.
8
9Saat mengerjakan tugas, Claude kadang-kadang perlu berkonsultasi dengan pengguna. Mungkin perlu izin sebelum menghapus file, atau perlu menanyakan database mana yang akan digunakan untuk proyek baru. Aplikasi Anda perlu menampilkan permintaan ini kepada pengguna sehingga Claude dapat melanjutkan dengan input mereka.
10
11Claude meminta input pengguna dalam dua situasi: ketika membutuhkan **izin untuk menggunakan alat** (seperti menghapus file atau menjalankan perintah), dan ketika memiliki **pertanyaan klarifikasi** (melalui alat `AskUserQuestion`). Keduanya memicu callback `canUseTool` Anda, yang menghentikan eksekusi sampai Anda mengembalikan respons. Ini berbeda dari putaran percakapan normal di mana Claude selesai dan menunggu pesan berikutnya Anda.
12
13Untuk pertanyaan klarifikasi, Claude menghasilkan pertanyaan dan opsi. Peran Anda adalah menyajikannya kepada pengguna dan mengembalikan pilihan mereka. Anda tidak dapat menambahkan pertanyaan Anda sendiri ke alur ini; jika Anda perlu menanyakan sesuatu kepada pengguna, lakukan itu secara terpisah dalam logika aplikasi Anda.
14
15Callback dapat tetap tertunda tanpa batas waktu. Eksekusi tetap dijeda sampai callback Anda kembali, dan SDK hanya membatalkan tunggu ketika kueri itu sendiri dibatalkan. Jika pengguna mungkin membutuhkan waktu lebih lama untuk merespons daripada yang dapat ditahan proses Anda, TypeScript SDK mendukung [hook `defer`](/id/hooks#defer-a-tool-call-for-later), yang memungkinkan proses keluar dan dilanjutkan nanti dari sesi yang disimpan; opsi ini tidak tersedia di Python SDK.
16
17Panduan ini menunjukkan cara mendeteksi setiap jenis permintaan dan merespons dengan tepat.
18
19## Deteksi ketika Claude membutuhkan input
20
21Berikan callback `canUseTool` dalam opsi kueri Anda. Callback dipicu setiap kali Claude membutuhkan input pengguna, menerima nama alat dan input sebagai argumen:
22
23<CodeGroup>
24 ```python Python theme={null}
25 async def handle_tool_request(tool_name, input_data, context):
26 # Minta pengguna dan kembalikan izin atau tolak
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 // Minta pengguna dan kembalikan izin atau tolak
37 }
38
39 const options = { canUseTool: handleToolRequest };
40 ```
41</CodeGroup>
42
43Callback dipicu dalam dua kasus:
44
451. **Alat membutuhkan persetujuan**: Claude ingin menggunakan alat yang tidak disetujui secara otomatis oleh [aturan izin](/id/agent-sdk/permissions) atau mode. Periksa `tool_name` untuk alat (misalnya, `"Bash"`, `"Write"`).
462. **Claude mengajukan pertanyaan**: Claude memanggil alat `AskUserQuestion`. Periksa apakah `tool_name == "AskUserQuestion"` untuk menanganinya secara berbeda. Jika Anda menentukan array `tools`, sertakan `AskUserQuestion` agar ini berfungsi. Lihat [Menangani pertanyaan klarifikasi](#handle-clarifying-questions) untuk detail.
47
48<Note>
49 Untuk secara otomatis mengizinkan atau menolak alat tanpa meminta pengguna, gunakan [hooks](/id/agent-sdk/hooks) sebagai gantinya. Hooks dijalankan sebelum `canUseTool` dan dapat mengizinkan, menolak, atau memodifikasi permintaan berdasarkan logika Anda sendiri. Anda juga dapat menggunakan [hook `PermissionRequest`](/id/agent-sdk/hooks#available-hooks) untuk mengirim notifikasi eksternal (Slack, email, push) ketika Claude menunggu persetujuan.
50</Note>
51
52## Menangani permintaan persetujuan alat
53
54Setelah Anda melewatkan callback `canUseTool` dalam opsi kueri Anda, callback dipicu ketika Claude ingin menggunakan alat yang tidak disetujui secara otomatis. Callback Anda menerima tiga argumen:
55
56| Argumen | Deskripsi |
57| ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
58| `toolName` | Nama alat yang ingin digunakan Claude (misalnya, `"Bash"`, `"Write"`, `"Edit"`) |
59| `input` | Parameter yang diteruskan Claude ke alat. Isi bervariasi menurut alat. |
60| `options` (TS) / `context` (Python) | Konteks tambahan termasuk `suggestions` opsional (entri `PermissionUpdate` yang diusulkan untuk menghindari permintaan ulang) dan sinyal pembatalan. Di TypeScript, `signal` adalah `AbortSignal`; di Python, bidang sinyal dicadangkan untuk penggunaan di masa depan. Lihat [`ToolPermissionContext`](/id/agent-sdk/python#toolpermissioncontext) untuk Python. |
61
62Objek `input` berisi parameter khusus alat. Contoh umum:
63
64| Alat | Bidang input |
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
71Lihat referensi SDK untuk skema input lengkap: [Python](/id/agent-sdk/python#tool-input%2Foutput-types) | [TypeScript](/id/agent-sdk/typescript#tool-input-types).
72
73Anda dapat menampilkan informasi ini kepada pengguna sehingga mereka dapat memutuskan apakah akan mengizinkan atau menolak tindakan, kemudian kembalikan respons yang sesuai.
74
75Contoh berikut meminta Claude untuk membuat dan menghapus file uji. Ketika Claude mencoba setiap operasi, callback mencetak permintaan alat ke terminal dan meminta persetujuan 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 # Tampilkan permintaan alat
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 # Dapatkan persetujuan pengguna
103 response = input("Allow this action? (y/n): ")
104
105 # Kembalikan izin atau tolak berdasarkan respons pengguna
106 if response.lower() == "y":
107 # Izinkan: alat dijalankan dengan input asli (atau dimodifikasi)
108 return PermissionResultAllow(updated_input=input_data)
109 else:
110 # Tolak: alat tidak dijalankan, Claude melihat pesan
111 return PermissionResultDeny(message="User denied this action")
112
113
114 # Solusi yang diperlukan: hook dummy menjaga aliran tetap terbuka untuk 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 // Helper untuk meminta input pengguna di terminal
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 // Tampilkan permintaan alat
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 // Dapatkan persetujuan pengguna
176 const response = await prompt("Allow this action? (y/n): ");
177
178 // Kembalikan izin atau tolak berdasarkan respons pengguna
179 if (response.toLowerCase() === "y") {
180 // Izinkan: alat dijalankan dengan input asli (atau dimodifikasi)
181 return { behavior: "allow", updatedInput: input };
182 } else {
183 // Tolak: alat tidak dijalankan, Claude melihat pesan
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 Di Python, `can_use_tool` memerlukan [mode streaming](/id/agent-sdk/streaming-vs-single-mode) dan hook `PreToolUse` yang mengembalikan `{"continue_": True}` untuk menjaga aliran tetap terbuka. Tanpa hook ini, aliran ditutup sebelum callback izin dapat dipanggil.
196</Note>
197
198Contoh ini menggunakan alur `y/n` di mana input apa pun selain `y` diperlakukan sebagai penolakan. Dalam praktik, Anda mungkin membangun UI yang lebih kaya yang memungkinkan pengguna memodifikasi permintaan, memberikan umpan balik, atau mengarahkan Claude sepenuhnya. Lihat [Merespons permintaan alat](#respond-to-tool-requests) untuk semua cara Anda dapat merespons.
199
200### Merespons permintaan alat
201
202Callback Anda mengembalikan salah satu dari dua jenis respons:
203
204| Respons | Python | TypeScript |
205| ----------- | ------------------------------------------ | ------------------------------------- |
206| **Izinkan** | `PermissionResultAllow(updated_input=...)` | `{ behavior: "allow", updatedInput }` |
207| **Tolak** | `PermissionResultDeny(message=...)` | `{ behavior: "deny", message }` |
208
209Saat mengizinkan, berikan input alat (asli atau dimodifikasi). Saat menolak, berikan pesan yang menjelaskan alasannya. Claude melihat pesan ini dan mungkin menyesuaikan pendekatannya.
210
211<CodeGroup>
212 ```python Python theme={null}
213 from claude_agent_sdk.types import PermissionResultAllow, PermissionResultDeny
214
215 # Izinkan alat untuk dijalankan
216 return PermissionResultAllow(updated_input=input_data)
217
218 # Blokir alat
219 return PermissionResultDeny(message="User rejected this action")
220 ```
221
222 ```typescript TypeScript theme={null}
223 // Izinkan alat untuk dijalankan
224 return { behavior: "allow", updatedInput: input };
225
226 // Blokir alat
227 return { behavior: "deny", message: "User rejected this action" };
228 ```
229</CodeGroup>
230
231Selain mengizinkan atau menolak, Anda dapat memodifikasi input alat atau memberikan konteks yang membantu Claude menyesuaikan pendekatannya:
232
233* **Setujui**: biarkan alat dijalankan seperti yang diminta Claude
234* **Setujui dengan perubahan**: modifikasi input sebelum eksekusi (misalnya, sanitasi jalur, tambahkan batasan)
235* **Tolak**: blokir alat dan beri tahu Claude mengapa
236* **Sarankan alternatif**: blokir tetapi arahkan Claude ke arah yang diinginkan pengguna
237* **Alihkan sepenuhnya**: gunakan [input streaming](/id/agent-sdk/streaming-vs-single-mode) untuk mengirim Claude instruksi yang sepenuhnya baru
238
239<Tabs>
240 <Tab title="Setujui">
241 Pengguna menyetujui tindakan apa adanya. Teruskan `input` dari callback Anda tanpa perubahan dan alat dijalankan persis seperti yang diminta 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="Setujui dengan perubahan">
269 Pengguna menyetujui tetapi ingin memodifikasi permintaan terlebih dahulu. Anda dapat mengubah input sebelum alat dijalankan. Claude melihat hasilnya tetapi tidak diberitahu Anda mengubah apa pun. Berguna untuk sanitasi parameter, menambahkan batasan, atau membatasi akses.
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 # Pengguna menyetujui, tetapi batasi semua perintah ke sandbox
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 // Pengguna menyetujui, tetapi batasi semua perintah ke sandbox
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="Tolak">
301 Pengguna tidak menginginkan tindakan ini terjadi. Blokir alat dan berikan pesan yang menjelaskan alasannya. Claude melihat pesan ini dan mungkin mencoba pendekatan yang berbeda.
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="Sarankan alternatif">
330 Pengguna tidak menginginkan tindakan spesifik ini, tetapi memiliki ide yang berbeda. Blokir alat dan sertakan panduan dalam pesan Anda. Claude akan membaca ini dan memutuskan cara melanjutkan berdasarkan umpan balik Anda.
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 # Pengguna tidak ingin menghapus, sarankan pengarsipan sebagai gantinya
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 // Pengguna tidak ingin menghapus, sarankan pengarsipan sebagai gantinya
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="Alihkan sepenuhnya">
360 Untuk perubahan arah yang lengkap (bukan hanya dorongan), gunakan [input streaming](/id/agent-sdk/streaming-vs-single-mode) untuk mengirim Claude instruksi baru secara langsung. Ini melewati permintaan alat saat ini dan memberikan Claude instruksi yang sepenuhnya baru untuk diikuti.
361 </Tab>
362</Tabs>
363
364## Menangani pertanyaan klarifikasi
365
366Ketika Claude membutuhkan lebih banyak arahan tentang tugas dengan beberapa pendekatan yang valid, Claude memanggil alat `AskUserQuestion`. Ini memicu callback `canUseTool` Anda dengan `toolName` diatur ke `AskUserQuestion`. Input berisi pertanyaan Claude sebagai opsi pilihan ganda, yang Anda tampilkan kepada pengguna dan kembalikan pilihan mereka.
367
368<Tip>
369 Pertanyaan klarifikasi sangat umum dalam [mode `plan`](/id/agent-sdk/permissions#plan-mode-plan), di mana Claude menjelajahi basis kode dan mengajukan pertanyaan sebelum mengusulkan rencana. Ini membuat mode plan ideal untuk alur kerja interaktif di mana Anda ingin Claude mengumpulkan persyaratan sebelum membuat perubahan.
370</Tip>
371
372Langkah-langkah berikut menunjukkan cara menangani pertanyaan klarifikasi:
373
374<Steps>
375 <Step title="Berikan callback canUseTool">
376 Berikan callback `canUseTool` dalam opsi kueri Anda. Secara default, `AskUserQuestion` tersedia. Jika Anda menentukan array `tools` untuk membatasi kemampuan Claude (misalnya, agen read-only dengan hanya `Read`, `Glob`, dan `Grep`), sertakan `AskUserQuestion` dalam array itu. Jika tidak, Claude tidak akan dapat mengajukan pertanyaan klarifikasi:
377
378 <CodeGroup>
379 ```python Python theme={null}
380 async for message in query(
381 prompt="Analyze this codebase",
382 options=ClaudeAgentOptions(
383 # Sertakan AskUserQuestion dalam daftar alat Anda
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 // Sertakan AskUserQuestion dalam daftar alat Anda
396 tools: ["Read", "Glob", "Grep", "AskUserQuestion"],
397 canUseTool: async (toolName, input) => {
398 // Tangani pertanyaan klarifikasi di sini
399 }
400 }
401 })) {
402 console.log(message);
403 }
404 ```
405 </CodeGroup>
406 </Step>
407
408 <Step title="Deteksi AskUserQuestion">
409 Dalam callback Anda, periksa apakah `toolName` sama dengan `AskUserQuestion` untuk menanganinya secara berbeda dari alat lain:
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 # Implementasi Anda untuk mengumpulkan jawaban dari pengguna
416 return await handle_clarifying_questions(input_data)
417 # Tangani alat lain secara normal
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 // Implementasi Anda untuk mengumpulkan jawaban dari pengguna
425 return handleClarifyingQuestions(input);
426 }
427 // Tangani alat lain secara normal
428 return promptForApproval(toolName, input);
429 };
430 ```
431 </CodeGroup>
432 </Step>
433
434 <Step title="Analisis input pertanyaan">
435 Input berisi pertanyaan Claude dalam array `questions`. Setiap pertanyaan memiliki `question` (teks untuk ditampilkan), `options` (pilihan), dan `multiSelect` (apakah beberapa pilihan diizinkan):
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 Lihat [Format pertanyaan](#question-format) untuk deskripsi bidang lengkap.
463 </Step>
464
465 <Step title="Kumpulkan jawaban dari pengguna">
466 Presentasikan pertanyaan kepada pengguna dan kumpulkan pilihan mereka. Cara Anda melakukan ini tergantung pada aplikasi Anda: prompt terminal, formulir web, dialog seluler, dll.
467 </Step>
468
469 <Step title="Kembalikan jawaban ke Claude">
470 Bangun objek `answers` sebagai catatan di mana setiap kunci adalah teks `question` dan setiap nilai adalah `label` opsi yang dipilih:
471
472 | Dari objek pertanyaan | Gunakan sebagai |
473 | ----------------------------------------------------------------- | --------------- |
474 | Bidang `question` (misalnya, `"How should I format the output?"`) | Kunci |
475 | Bidang `label` opsi yang dipilih (misalnya, `"Summary"`) | Nilai |
476
477 Untuk pertanyaan multi-pilih, berikan array label atau gabungkan dengan `", "`. Jika Anda [mendukung input teks bebas](#support-free-text-input), gunakan teks kustom pengguna sebagai nilai.
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### Format pertanyaan
509
510Input berisi pertanyaan yang dihasilkan Claude dalam array `questions`. Setiap pertanyaan memiliki bidang-bidang ini:
511
512| Bidang | Deskripsi |
513| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
514| `question` | Teks pertanyaan lengkap untuk ditampilkan |
515| `header` | Label pendek untuk pertanyaan (maks 12 karakter) |
516| `options` | Array 2-4 pilihan, masing-masing dengan `label` dan `description`. TypeScript: secara opsional `preview` (lihat [di bawah](#option-previews-type-script)) |
517| `multiSelect` | Jika `true`, pengguna dapat memilih beberapa opsi |
518
519Struktur yang diterima callback Anda:
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#### Pratinjau opsi (TypeScript)
538
539`toolConfig.askUserQuestion.previewFormat` menambahkan bidang `preview` ke setiap opsi sehingga aplikasi Anda dapat menampilkan mockup visual di samping label. Tanpa pengaturan ini, Claude tidak menghasilkan pratinjau dan bidang tidak ada.
540
541| `previewFormat` | `preview` berisi |
542| :--------------------- | :----------------------------------------------------------------------------------------------------------- |
543| tidak diatur (default) | Bidang tidak ada. Claude tidak menghasilkan pratinjau. |
544| `"markdown"` | ASCII art dan blok kode yang dibatasi |
545| `"html"` | Fragmen `<div>` bergaya (SDK menolak `<script>`, `<style>`, dan `<!DOCTYPE>` sebelum callback Anda berjalan) |
546
547Format berlaku untuk semua pertanyaan dalam sesi. Claude menyertakan `preview` pada opsi di mana perbandingan visual membantu (pilihan tata letak, skema warna) dan menghilangkannya di mana tidak akan membantu (konfirmasi ya/tidak, pilihan hanya teks). Periksa `undefined` sebelum merender.
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 adalah string HTML atau undefined
560 return { behavior: "allow", updatedInput: input };
561 }
562 }
563})) {
564 // ...
565}
566```
567
568Opsi dengan pratinjau 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### Format respons
579
580Kembalikan objek `answers` yang memetakan bidang `question` setiap pertanyaan ke `label` opsi yang dipilih:
581
582| Bidang | Deskripsi |
583| ----------- | ------------------------------------------------------------------------------ |
584| `questions` | Teruskan array pertanyaan asli (diperlukan untuk pemrosesan alat) |
585| `answers` | Objek di mana kunci adalah teks pertanyaan dan nilai adalah label yang dipilih |
586
587Untuk pertanyaan multi-pilih, berikan array label atau gabungkan dengan `", "`. Untuk input teks bebas, gunakan teks kustom pengguna secara langsung.
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#### Dukung input teks bebas
602
603Opsi yang telah ditentukan Claude tidak akan selalu mencakup apa yang diinginkan pengguna. Untuk memungkinkan pengguna mengetik jawaban mereka sendiri:
604
605* Tampilkan pilihan "Other" tambahan setelah opsi Claude yang menerima input teks
606* Gunakan teks kustom pengguna sebagai nilai jawaban (bukan kata "Other")
607
608Lihat [contoh lengkap](#complete-example) di bawah untuk implementasi lengkap.
609
610### Contoh lengkap
611
612Claude mengajukan pertanyaan klarifikasi ketika membutuhkan input pengguna untuk melanjutkan. Misalnya, ketika diminta membantu memutuskan tech stack untuk aplikasi seluler, Claude mungkin menanyakan tentang cross-platform vs native, preferensi backend, atau platform target. Pertanyaan-pertanyaan ini membantu Claude membuat keputusan yang sesuai dengan preferensi pengguna daripada menebak.
613
614Contoh ini menangani pertanyaan-pertanyaan tersebut dalam aplikasi terminal. Berikut yang terjadi di setiap langkah:
615
6161. **Arahkan permintaan**: Callback `canUseTool` memeriksa apakah nama alat adalah `"AskUserQuestion"` dan mengarahkan ke handler khusus
6172. **Tampilkan pertanyaan**: Handler melakukan loop melalui array `questions` dan mencetak setiap pertanyaan dengan opsi bernomor
6183. **Kumpulkan input**: Pengguna dapat memasukkan angka untuk memilih opsi, atau mengetik teks bebas langsung (misalnya, "jquery", "i don't know")
6194. **Peta jawaban**: Kode memeriksa apakah input adalah numerik (menggunakan label opsi) atau teks bebas (menggunakan teks langsung)
6205. **Kembalikan ke Claude**: Respons mencakup array `questions` asli dan pemetaan `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 """Analisis input pengguna sebagai nomor opsi atau teks bebas."""
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 """Tampilkan pertanyaan Claude dan kumpulkan jawaban pengguna."""
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 # Arahkan AskUserQuestion ke handler pertanyaan kami
670 if tool_name == "AskUserQuestion":
671 return await handle_ask_user_question(input_data)
672 # Setujui secara otomatis alat lain untuk contoh ini
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 # Solusi yang diperlukan: hook dummy menjaga aliran tetap terbuka untuk 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 // Helper untuk meminta input pengguna di terminal
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 // Analisis input pengguna sebagai nomor opsi atau teks bebas
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 // Tampilkan pertanyaan Claude dan kumpulkan jawaban pengguna
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 // Kembalikan jawaban ke Claude (harus menyertakan pertanyaan asli)
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 // Arahkan AskUserQuestion ke handler pertanyaan kami
761 if (toolName === "AskUserQuestion") {
762 return handleAskUserQuestion(input);
763 }
764 // Setujui secara otomatis alat lain untuk contoh ini
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## Keterbatasan
778
779* **Subagents**: `AskUserQuestion` saat ini tidak tersedia di subagents yang dihasilkan melalui alat Agent
780* **Batas pertanyaan**: setiap panggilan `AskUserQuestion` mendukung 1-4 pertanyaan dengan 2-4 opsi masing-masing
781
782## Cara lain untuk mendapatkan input pengguna
783
784Callback `canUseTool` dan alat `AskUserQuestion` mencakup sebagian besar skenario persetujuan dan klarifikasi, tetapi SDK menawarkan cara lain untuk mendapatkan input dari pengguna:
785
786### Input streaming
787
788Gunakan [input streaming](/id/agent-sdk/streaming-vs-single-mode) ketika Anda perlu:
789
790* **Mengganggu agen di tengah-tugas**: kirim sinyal pembatalan atau ubah arah saat Claude sedang bekerja
791* **Memberikan konteks tambahan**: tambahkan informasi yang dibutuhkan Claude tanpa menunggu untuk ditanya
792* **Membangun antarmuka obrolan**: biarkan pengguna mengirim pesan lanjutan selama operasi yang berjalan lama
793
794Input streaming ideal untuk UI percakapan di mana pengguna berinteraksi dengan agen sepanjang eksekusi, bukan hanya di titik persetujuan.
795
796### Alat kustom
797
798Gunakan [alat kustom](/id/agent-sdk/custom-tools) ketika Anda perlu:
799
800* **Mengumpulkan input terstruktur**: bangun formulir, wizard, atau alur kerja multi-langkah yang melampaui format pilihan ganda `AskUserQuestion`
801* **Mengintegrasikan sistem persetujuan eksternal**: terhubung ke platform tiket, alur kerja, atau persetujuan yang ada
802* **Menerapkan interaksi khusus domain**: buat alat yang disesuaikan dengan kebutuhan aplikasi Anda, seperti antarmuka tinjauan kode atau daftar periksa penyebaran
803
804Alat kustom memberi Anda kontrol penuh atas interaksi, tetapi memerlukan lebih banyak pekerjaan implementasi daripada menggunakan callback `canUseTool` bawaan.
805
806## Sumber daya terkait
807
808* [Konfigurasi izin](/id/agent-sdk/permissions): atur mode dan aturan izin
809* [Kontrol eksekusi dengan hooks](/id/agent-sdk/hooks): jalankan kode kustom di titik-titik kunci dalam siklus hidup agen
810* [Referensi TypeScript SDK](/id/agent-sdk/typescript#canusetool): dokumentasi API canUseTool lengkap