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` ํ
๊ฒฐ์ ](/ko/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๊ฐ [๊ถํ ๊ท์น](/ko/agent-sdk/permissions) ๋๋ ๋ชจ๋์ ์ํด ์๋ ์น์ธ๋์ง ์์ ๋๊ตฌ๋ฅผ ์ฌ์ฉํ๋ ค๊ณ ํฉ๋๋ค. ๋๊ตฌ์ ๋ํด `tool_name`์ ํ์ธํฉ๋๋ค(์: `"Bash"`, `"Write"`).
462. **Claude๊ฐ ์ง๋ฌธํจ**: Claude๊ฐ `AskUserQuestion` ๋๊ตฌ๋ฅผ ํธ์ถํฉ๋๋ค. `tool_name == "AskUserQuestion"`์ ํ์ธํ์ฌ ๋ค๋ฅด๊ฒ ์ฒ๋ฆฌํฉ๋๋ค. `tools` ๋ฐฐ์ด์ ์ง์ ํ๋ ๊ฒฝ์ฐ ์ด๊ฒ์ด ์๋ํ๋ ค๋ฉด `AskUserQuestion`์ ํฌํจํ์ญ์์ค. ์์ธํ ๋ด์ฉ์ [๋ช
ํํ ์ง๋ฌธ ์ฒ๋ฆฌ](#๋ช
ํํ-์ง๋ฌธ-์ฒ๋ฆฌ)๋ฅผ ์ฐธ์กฐํ์ญ์์ค.
47
48<Note>
49 ์ฌ์ฉ์์๊ฒ ํ๋กฌํํธํ์ง ์๊ณ ๋๊ตฌ๋ฅผ ์๋์ผ๋ก ํ์ฉํ๊ฑฐ๋ ๊ฑฐ๋ถํ๋ ค๋ฉด [ํ
](/ko/agent-sdk/hooks)์ ๋์ ์ฌ์ฉํ์ญ์์ค. ํ
์ `canUseTool` ์ ์ ์คํ๋๋ฉฐ ์์ ์ ๋ก์ง์ ๋ฐ๋ผ ์์ฒญ์ ํ์ฉ, ๊ฑฐ๋ถ ๋๋ ์์ ํ ์ ์์ต๋๋ค. [`PermissionRequest` ํ
](/ko/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`](/ko/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](/ko/agent-sdk/python#tool-input%2Foutput-types) | [TypeScript](/ko/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 # ํ์ ํด๊ฒฐ ๋ฐฉ๋ฒ: ๋๋ฏธ ํ
์ด 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`์ [์คํธ๋ฆฌ๋ฐ ๋ชจ๋](/ko/agent-sdk/streaming-vs-single-mode)์ ์คํธ๋ฆผ์ ์ด์ด ๋๊ธฐ ์ํด `{"continue_": True}`๋ฅผ ๋ฐํํ๋ `PreToolUse` ํ
์ด ํ์ํฉ๋๋ค. ์ด ํ
์ด ์์ผ๋ฉด ๊ถํ ์ฝ๋ฐฑ์ด ํธ์ถ๋๊ธฐ ์ ์ ์คํธ๋ฆผ์ด ๋ซํ๋๋ค.
196</Note>
197
198์ด ์์ ๋ `y` ์ด์ธ์ ๋ชจ๋ ์
๋ ฅ์ด ๊ฑฐ๋ถ๋ก ์ฒ๋ฆฌ๋๋ y/n ํ๋ฆ์ ์ฌ์ฉํฉ๋๋ค. ์ค์ ๋ก๋ ์ฌ์ฉ์๊ฐ ์์ฒญ์ ์์ ํ๊ฑฐ๋, ํผ๋๋ฐฑ์ ์ ๊ณตํ๊ฑฐ๋, Claude๋ฅผ ์์ ํ ๋ฆฌ๋๋ ์
ํ ์ ์๋ ๋ ํ๋ถํ UI๋ฅผ ๊ตฌ์ถํ ์ ์์ต๋๋ค. ์๋ตํ ์ ์๋ ๋ชจ๋ ๋ฐฉ๋ฒ์ [๋๊ตฌ ์์ฒญ์ ์๋ต](#๋๊ตฌ-์์ฒญ์-์๋ต)์ ์ฐธ์กฐํ์ญ์์ค.
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* **์์ ํ ๋ฆฌ๋๋ ์
**: [์คํธ๋ฆฌ๋ฐ ์
๋ ฅ](/ko/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 ๋ฐฉํฅ์ ์์ ํ ๋ณ๊ฒฝ(๋จ์ํ ๋ฐ์ด๋ถ์ด๊ธฐ๊ฐ ์๋)์ ๊ฒฝ์ฐ [์คํธ๋ฆฌ๋ฐ ์
๋ ฅ](/ko/agent-sdk/streaming-vs-single-mode)์ ์ฌ์ฉํ์ฌ Claude์ ์๋ก์ด ์ง์๋ฅผ ์ง์ ๋ณด๋
๋๋ค. ์ด๋ ํ์ฌ ๋๊ตฌ ์์ฒญ์ ์ฐํํ๊ณ Claude์ ์์ ํ ์๋ก์ด ์ง์๋ฅผ ๋ฐ๋ฅด๋๋ก ํฉ๋๋ค.
361 </Tab>
362</Tabs>
363
364## ๋ช
ํํ ์ง๋ฌธ ์ฒ๋ฆฌ
365
366Claude๊ฐ ์ฌ๋ฌ ์ ํจํ ์ ๊ทผ ๋ฐฉ์์ด ์๋ ์์
์ ๋ํด ๋ ๋ง์ ๋ฐฉํฅ์ด ํ์ํ ๋ `AskUserQuestion` ๋๊ตฌ๋ฅผ ํธ์ถํฉ๋๋ค. ์ด๋ `toolName`์ด `AskUserQuestion`์ผ๋ก ์ค์ ๋ `canUseTool` ์ฝ๋ฐฑ์ ํธ๋ฆฌ๊ฑฐํฉ๋๋ค. ์
๋ ฅ์๋ Claude์ ์ง๋ฌธ์ด ๊ฐ๊ด์ ์ต์
์ผ๋ก ํฌํจ๋์ด ์์ผ๋ฉฐ, ์ด๋ฅผ ์ฌ์ฉ์์๊ฒ ํ์ํ๊ณ ์ ํ ์ฌํญ์ ๋ฐํํฉ๋๋ค.
367
368<Tip>
369 ๋ช
ํํ ์ง๋ฌธ์ ํนํ [`plan` ๋ชจ๋](/ko/agent-sdk/permissions#plan-mode-plan)์์ ํํ๋ฉฐ, Claude๊ฐ ์ฝ๋๋ฒ ์ด์ค๋ฅผ ํ์ํ๊ณ ๊ณํ์ ์ ์ํ๊ธฐ ์ ์ ์ง๋ฌธํฉ๋๋ค. ์ด๋ ๊ณํ ๋ชจ๋๋ฅผ Claude๊ฐ ๋ณ๊ฒฝํ๊ธฐ ์ ์ ์๊ตฌ ์ฌํญ์ ์์งํ๊ธฐ๋ฅผ ์ํ๋ ๋ํํ ์ํฌํ๋ก์ฐ์ ์ด์์ ์ผ๋ก ๋ง๋ญ๋๋ค.
370</Tip>
371
372๋ค์ ๋จ๊ณ๋ ๋ช
ํํ ์ง๋ฌธ์ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋๋ค.
373
374<Steps>
375 <Step title="canUseTool ์ฝ๋ฐฑ ์ ๋ฌ">
376 ์ฟผ๋ฆฌ ์ต์
์ `canUseTool` ์ฝ๋ฐฑ์ ์ ๋ฌํฉ๋๋ค. ๊ธฐ๋ณธ์ ์ผ๋ก `AskUserQuestion`์ ์ฌ์ฉํ ์ ์์ต๋๋ค. Claude์ ๊ธฐ๋ฅ์ ์ ํํ๊ธฐ ์ํด `tools` ๋ฐฐ์ด์ ์ง์ ํ๋ ๊ฒฝ์ฐ(์: `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 ์ ์ฒด ํ๋ ์ค๋ช
์ [์ง๋ฌธ ํ์](#์ง๋ฌธ-ํ์)์ ์ฐธ์กฐํ์ญ์์ค.
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 ๋ค์ค ์ ํ ์ง๋ฌธ์ ๊ฒฝ์ฐ ๋ ์ด๋ธ ๋ฐฐ์ด์ ์ ๋ฌํ๊ฑฐ๋ `", "`๋ก ์กฐ์ธํฉ๋๋ค. [์์ ํ
์คํธ ์
๋ ฅ์ ์ง์](#์์ -ํ
์คํธ-์
๋ ฅ-์ง์)ํ๋ ๊ฒฝ์ฐ ์ฌ์ฉ์์ ์ฌ์ฉ์ ์ ์ ํ
์คํธ๋ฅผ ๊ฐ์ผ๋ก ์ฌ์ฉํฉ๋๋ค.
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` | ๊ฐ๊ฐ `label` ๋ฐ `description`์ด ์๋ 2-4๊ฐ ์ ํ ์ฌํญ์ ๋ฐฐ์ด์
๋๋ค. 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
568HTML ๋ฏธ๋ฆฌ๋ณด๊ธฐ๊ฐ ์๋ ์ต์
:
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๊ฐ ์ง๋ฌธ์ `question` ํ๋๋ฅผ ์ ํ๋ ์ต์
์ `label`์ ๋งคํํ๋ `answers` ๊ฐ์ฒด๋ฅผ ๋ฐํํฉ๋๋ค.
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์ ์ต์
ํ์ ์ถ๊ฐ "Other" ์ ํ์ ํ์ํ์ฌ ํ
์คํธ ์
๋ ฅ์ ํ์ฉํฉ๋๋ค.
606* ์ฌ์ฉ์์ ์ฌ์ฉ์ ์ ์ ํ
์คํธ๋ฅผ ๋ต๋ณ ๊ฐ์ผ๋ก ์ฌ์ฉํฉ๋๋ค("Other"๋ผ๋ ๋จ์ด๊ฐ ์๋).
607
608์ ์ฒด ๊ตฌํ์ ์๋์ [์์ ํ ์์ ](#์์ ํ-์์ )๋ฅผ ์ฐธ์กฐํ์ญ์์ค.
609
610### ์์ ํ ์์
611
612Claude๋ ์งํํ๊ธฐ ์ํด ์ฌ์ฉ์ ์
๋ ฅ์ด ํ์ํ ๋ ๋ช
ํํ ์ง๋ฌธ์ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด ๋ชจ๋ฐ์ผ ์ฑ์ ๊ธฐ์ ์คํ์ ๊ฒฐ์ ํ๋ ๋ฐ ๋์์ ๋ฌ๋ผ๋ ์์ฒญ์ ๋ฐ์ผ๋ฉด 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 # ํ์ ํด๊ฒฐ ๋ฐฉ๋ฒ: ๋๋ฏธ ํ
์ด 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` ํธ์ถ์ ๊ฐ๊ฐ 2-4๊ฐ ์ต์
์ด ์๋ 1-4๊ฐ ์ง๋ฌธ์ ์ง์ํฉ๋๋ค.
781
782## ์ฌ์ฉ์ ์
๋ ฅ์ ์ป๋ ๋ค๋ฅธ ๋ฐฉ๋ฒ
783
784`canUseTool` ์ฝ๋ฐฑ๊ณผ `AskUserQuestion` ๋๊ตฌ๋ ๋๋ถ๋ถ์ ์น์ธ ๋ฐ ๋ช
ํํ ์๋๋ฆฌ์ค๋ฅผ ๋ค๋ฃจ์ง๋ง, SDK๋ ์ฌ์ฉ์๋ก๋ถํฐ ์
๋ ฅ์ ์ป๋ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ ์ ๊ณตํฉ๋๋ค.
785
786### ์คํธ๋ฆฌ๋ฐ ์
๋ ฅ
787
788๋ค์์ด ํ์ํ ๋ [์คํธ๋ฆฌ๋ฐ ์
๋ ฅ](/ko/agent-sdk/streaming-vs-single-mode)์ ์ฌ์ฉํ์ญ์์ค.
789
790* **์์ด์ ํธ ์ค๊ฐ์ ์ค๋จ**: Claude๊ฐ ์์
์ค์ผ ๋ ์ทจ์ ์ ํธ๋ฅผ ๋ณด๋ด๊ฑฐ๋ ๋ฐฉํฅ์ ๋ณ๊ฒฝํฉ๋๋ค.
791* **์ถ๊ฐ ์ปจํ
์คํธ ์ ๊ณต**: Claude๊ฐ ๋ฌผ์ด๋ณผ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ์ง ์๊ณ ํ์ํ ์ ๋ณด๋ฅผ ์ถ๊ฐํฉ๋๋ค.
792* **์ฑํ
์ธํฐํ์ด์ค ๊ตฌ์ถ**: ์ฅ์๊ฐ ์คํ๋๋ ์์
์ค์ ์ฌ์ฉ์๊ฐ ํ์ ๋ฉ์์ง๋ฅผ ๋ณด๋ผ ์ ์์ต๋๋ค.
793
794์คํธ๋ฆฌ๋ฐ ์
๋ ฅ์ ์ฌ์ฉ์๊ฐ ์น์ธ ์ฒดํฌํฌ์ธํธ์์๋ง์ด ์๋๋ผ ์คํ ์ ์ฒด์์ ์์ด์ ํธ์ ์ํธ ์์ฉํ๋ ๋ํํ UI์ ์ด์์ ์
๋๋ค.
795
796### ์ฌ์ฉ์ ์ ์ ๋๊ตฌ
797
798๋ค์์ด ํ์ํ ๋ [์ฌ์ฉ์ ์ ์ ๋๊ตฌ](/ko/agent-sdk/custom-tools)๋ฅผ ์ฌ์ฉํ์ญ์์ค.
799
800* **๊ตฌ์กฐํ๋ ์
๋ ฅ ์์ง**: `AskUserQuestion`์ ๊ฐ๊ด์ ํ์์ ๋์ด์๋ ์์, ๋ง๋ฒ์ฌ ๋๋ ๋ค๋จ๊ณ ์ํฌํ๋ก์ฐ๋ฅผ ๊ตฌ์ถํฉ๋๋ค.
801* **์ธ๋ถ ์น์ธ ์์คํ
ํตํฉ**: ๊ธฐ์กด ํฐ์ผํ
, ์ํฌํ๋ก์ฐ ๋๋ ์น์ธ ํ๋ซํผ์ ์ฐ๊ฒฐํฉ๋๋ค.
802* **๋๋ฉ์ธ๋ณ ์ํธ ์์ฉ ๊ตฌํ**: ์ฝ๋ ๊ฒํ ์ธํฐํ์ด์ค ๋๋ ๋ฐฐํฌ ์ฒดํฌ๋ฆฌ์คํธ์ ๊ฐ์ด ์ ํ๋ฆฌ์ผ์ด์
์ ํ์์ ๋ง๋ ๋๊ตฌ๋ฅผ ๋ง๋ญ๋๋ค.
803
804์ฌ์ฉ์ ์ ์ ๋๊ตฌ๋ ์ํธ ์์ฉ์ ์์ ํ ์ ์ดํ ์ ์์ง๋ง ๊ธฐ๋ณธ ์ ๊ณต `canUseTool` ์ฝ๋ฐฑ์ ์ฌ์ฉํ๋ ๊ฒ๋ณด๋ค ๋ ๋ง์ ๊ตฌํ ์์
์ด ํ์ํฉ๋๋ค.
805
806## ๊ด๋ จ ๋ฆฌ์์ค
807
808* [๊ถํ ๊ตฌ์ฑ](/ko/agent-sdk/permissions): ๊ถํ ๋ชจ๋ ๋ฐ ๊ท์น ์ค์
809* [ํ
์ผ๋ก ์คํ ์ ์ด](/ko/agent-sdk/hooks): ์์ด์ ํธ ์๋ช
์ฃผ๊ธฐ์ ์ฃผ์ ์ง์ ์์ ์ฌ์ฉ์ ์ ์ ์ฝ๋ ์คํ
810* [TypeScript SDK ์ฐธ์กฐ](/ko/agent-sdk/typescript#canusetool): ์ ์ฒด canUseTool API ๋ฌธ์