pip install claude-agent-sdkquery() dan ClaudeSDKClientPython SDK menyediakan dua cara untuk berinteraksi dengan Claude Code:
| Fitur | query() | ClaudeSDKClient |
|---|---|---|
| Sesi | Membuat sesi baru setiap kali | Menggunakan kembali sesi yang sama |
| Percakapan | Pertukaran tunggal | Pertukaran berganda dalam konteks yang sama |
| Koneksi | Dikelola secara otomatis | Kontrol manual |
| Input Streaming | ✅ Didukung | ✅ Didukung |
| Interupsi | ❌ Tidak didukung | ✅ Didukung |
| Hooks | ❌ Tidak didukung | ✅ Didukung |
| Alat Kustom | ❌ Tidak didukung | ✅ Didukung |
| Lanjutkan Chat | ❌ Sesi baru setiap kali | ✅ Mempertahankan percakapan |
| Kasus Penggunaan | Tugas sekali jalan | Percakapan berkelanjutan |
query() (Sesi Baru Setiap Kali)Terbaik untuk:
ClaudeSDKClient (Percakapan Berkelanjutan)Terbaik untuk:
query()Membuat sesi baru untuk setiap interaksi dengan Claude Code. Mengembalikan iterator async yang menghasilkan pesan saat tiba. Setiap panggilan ke query() dimulai segar tanpa memori interaksi sebelumnya.
async def query(
*,
prompt: str | AsyncIterable[dict[str, Any]],
options: ClaudeAgentOptions | None = None
) -> AsyncIterator[Message]| Parameter | Tipe | Deskripsi |
|---|---|---|
prompt | str | AsyncIterable[dict] | Prompt input sebagai string atau async iterable untuk mode streaming |
options | ClaudeAgentOptions | None | Objek konfigurasi opsional (default ke ClaudeAgentOptions() jika None) |
Mengembalikan AsyncIterator[Message] yang menghasilkan pesan dari percakapan.
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions
async def main():
options = ClaudeAgentOptions(
system_prompt="You are an expert Python developer",
permission_mode='acceptEdits',
cwd="/home/user/project"
)
async for message in query(
prompt="Create a Python web server",
options=options
):
print(message)
asyncio.run(main())tool()Dekorator untuk mendefinisikan alat MCP dengan keamanan tipe.
def tool(
name: str,
description: str,
input_schema: type | dict[str, Any]
) -> Callable[[Callable[[Any], Awaitable[dict[str, Any]]]], SdkMcpTool[Any]]| Parameter | Tipe | Deskripsi |
|---|---|---|
name | str | Pengidentifikasi unik untuk alat |
description | str | Deskripsi yang dapat dibaca manusia tentang apa yang dilakukan alat |
input_schema | type | dict[str, Any] | Skema yang mendefinisikan parameter input alat (lihat di bawah) |
Pemetaan tipe sederhana (direkomendasikan):
{"text": str, "count": int, "enabled": bool}Format JSON Schema (untuk validasi kompleks):
{
"type": "object",
"properties": {
"text": {"type": "string"},
"count": {"type": "integer", "minimum": 0}
},
"required": ["text"]
}Fungsi dekorator yang membungkus implementasi alat dan mengembalikan instance SdkMcpTool.
from claude_agent_sdk import tool
from typing import Any
@tool("greet", "Greet a user", {"name": str})
async def greet(args: dict[str, Any]) -> dict[str, Any]:
return {
"content": [{
"type": "text",
"text": f"Hello, {args['name']}!"
}]
}create_sdk_mcp_server()Buat server MCP dalam proses yang berjalan dalam aplikasi Python Anda.
def create_sdk_mcp_server(
name: str,
version: str = "1.0.0",
tools: list[SdkMcpTool[Any]] | None = None
) -> McpSdkServerConfig| Parameter | Tipe | Default | Deskripsi |
|---|---|---|---|
name | str | - | Pengidentifikasi unik untuk server |
version | str | "1.0.0" | String versi server |
tools | list[SdkMcpTool[Any]] | None | None | Daftar fungsi alat yang dibuat dengan dekorator @tool |
Mengembalikan objek McpSdkServerConfig yang dapat diteruskan ke ClaudeAgentOptions.mcp_servers.
from claude_agent_sdk import tool, create_sdk_mcp_server
@tool("add", "Add two numbers", {"a": float, "b": float})
async def add(args):
return {
"content": [{
"type": "text",
"text": f"Sum: {args['a'] + args['b']}"
}]
}
@tool("multiply", "Multiply two numbers", {"a": float, "b": float})
async def multiply(args):
return {
"content": [{
"type": "text",
"text": f"Product: {args['a'] * args['b']}"
}]
}
calculator = create_sdk_mcp_server(
name="calculator",
version="2.0.0",
tools=[add, multiply] # Pass decorated functions
)
# Use with Claude
options = ClaudeAgentOptions(
mcp_servers={"calc": calculator},
allowed_tools=["mcp__calc__add", "mcp__calc__multiply"]
)ClaudeSDKClientMempertahankan sesi percakapan di seluruh pertukaran berganda. Ini adalah setara Python dari cara fungsi query() SDK TypeScript bekerja secara internal - ini membuat objek klien yang dapat melanjutkan percakapan.
query() berganda@tool) dan hooksclass ClaudeSDKClient:
def __init__(self, options: ClaudeAgentOptions | None = None)
async def connect(self, prompt: str | AsyncIterable[dict] | None = None) -> None
async def query(self, prompt: str | AsyncIterable[dict], session_id: str = "default") -> None
async def receive_messages(self) -> AsyncIterator[Message]
async def receive_response(self) -> AsyncIterator[Message]
async def interrupt(self) -> None
async def rewind_files(self, user_message_uuid: str) -> None
async def disconnect(self) -> None| Metode | Deskripsi |
|---|---|
__init__(options) | Inisialisasi klien dengan konfigurasi opsional |
connect(prompt) | Terhubung ke Claude dengan prompt awal opsional atau aliran pesan |
query(prompt, session_id) | Kirim permintaan baru dalam mode streaming |
receive_messages() | Terima semua pesan dari Claude sebagai iterator async |
receive_response() | Terima pesan hingga dan termasuk ResultMessage |
interrupt() | Kirim sinyal interupsi (hanya berfungsi dalam mode streaming) |
rewind_files(user_message_uuid) | Pulihkan file ke keadaan mereka pada pesan pengguna yang ditentukan. Memerlukan enable_file_checkpointing=True. Lihat File checkpointing |
disconnect() | Putuskan sambungan dari Claude |
Klien dapat digunakan sebagai async context manager untuk manajemen koneksi otomatis:
async with ClaudeSDKClient() as client:
await client.query("Hello Claude")
async for message in client.receive_response():
print(message)Penting: Saat mengulangi pesan, hindari menggunakan
breakuntuk keluar lebih awal karena ini dapat menyebabkan masalah pembersihan asyncio. Sebaliknya, biarkan iterasi selesai secara alami atau gunakan flag untuk melacak kapan Anda menemukan apa yang Anda butuhkan.
import asyncio
from claude_agent_sdk import ClaudeSDKClient, AssistantMessage, TextBlock, ResultMessage
async def main():
async with ClaudeSDKClient() as client:
# First question
await client.query("What's the capital of France?")
# Process response
async for message in client.receive_response():
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}")
# Follow-up question - Claude remembers the previous context
await client.query("What's the population of that city?")
async for message in client.receive_response():
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}")
# Another follow-up - still in the same conversation
await client.query("What are some famous landmarks there?")
async for message in client.receive_response():
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}")
asyncio.run(main())import asyncio
from claude_agent_sdk import ClaudeSDKClient
async def message_stream():
"""Generate messages dynamically."""
yield {"type": "text", "text": "Analyze the following data:"}
await asyncio.sleep(0.5)
yield {"type": "text", "text": "Temperature: 25°C"}
await asyncio.sleep(0.5)
yield {"type": "text", "text": "Humidity: 60%"}
await asyncio.sleep(0.5)
yield {"type": "text", "text": "What patterns do you see?"}
async def main():
async with ClaudeSDKClient() as client:
# Stream input to Claude
await client.query(message_stream())
# Process response
async for message in client.receive_response():
print(message)
# Follow-up in same session
await client.query("Should we be concerned about these readings?")
async for message in client.receive_response():
print(message)
asyncio.run(main())import asyncio
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions
async def interruptible_task():
options = ClaudeAgentOptions(
allowed_tools=["Bash"],
permission_mode="acceptEdits"
)
async with ClaudeSDKClient(options=options) as client:
# Start a long-running task
await client.query("Count from 1 to 100 slowly")
# Let it run for a bit
await asyncio.sleep(2)
# Interrupt the task
await client.interrupt()
print("Task interrupted!")
# Send a new command
await client.query("Just say hello instead")
async for message in client.receive_response():
# Process the new response
pass
asyncio.run(interruptible_task())from claude_agent_sdk import (
ClaudeSDKClient,
ClaudeAgentOptions
)
async def custom_permission_handler(
tool_name: str,
input_data: dict,
context: dict
):
"""Custom logic for tool permissions."""
# Block writes to system directories
if tool_name == "Write" and input_data.get("file_path", "").startswith("/system/"):
return {
"behavior": "deny",
"message": "System directory write not allowed",
"interrupt": True
}
# Redirect sensitive file operations
if tool_name in ["Write", "Edit"] and "config" in input_data.get("file_path", ""):
safe_path = f"./sandbox/{input_data['file_path']}"
return {
"behavior": "allow",
"updatedInput": {**input_data, "file_path": safe_path}
}
# Allow everything else
return {
"behavior": "allow",
"updatedInput": input_data
}
async def main():
options = ClaudeAgentOptions(
can_use_tool=custom_permission_handler,
allowed_tools=["Read", "Write", "Edit"]
)
async with ClaudeSDKClient(options=options) as client:
await client.query("Update the system config file")
async for message in client.receive_response():
# Will use sandbox path instead
print(message)
asyncio.run(main())SdkMcpToolDefinisi untuk alat MCP SDK yang dibuat dengan dekorator @tool.
@dataclass
class SdkMcpTool(Generic[T]):
name: str
description: str
input_schema: type[T] | dict[str, Any]
handler: Callable[[T], Awaitable[dict[str, Any]]]| Properti | Tipe | Deskripsi |
|---|---|---|
name | str | Pengidentifikasi unik untuk alat |
description | str | Deskripsi yang dapat dibaca manusia |
input_schema | type[T] | dict[str, Any] | Skema untuk validasi input |
handler | Callable[[T], Awaitable[dict[str, Any]]] | Fungsi async yang menangani eksekusi alat |
ClaudeAgentOptionsDataclass konfigurasi untuk kueri Claude Code.
@dataclass
class ClaudeAgentOptions:
allowed_tools: list[str] = field(default_factory=list)
system_prompt: str | SystemPromptPreset | None = None
mcp_servers: dict[str, McpServerConfig] | str | Path = field(default_factory=dict)
permission_mode: PermissionMode | None = None
continue_conversation: bool = False
resume: str | None = None
max_turns: int | None = None
disallowed_tools: list[str] = field(default_factory=list)
model: str | None = None
output_format: OutputFormat | None = None
permission_prompt_tool_name: str | None = None
cwd: str | Path | None = None
settings: str | None = None
add_dirs: list[str | Path] = field(default_factory=list)
env: dict[str, str] = field(default_factory=dict)
extra_args: dict[str, str | None] = field(default_factory=dict)
max_buffer_size: int | None = None
debug_stderr: Any = sys.stderr # Deprecated
stderr: Callable[[str], None] | None = None
can_use_tool: CanUseTool | None = None
hooks: dict[HookEvent, list[HookMatcher]] | None = None
user: str | None = None
include_partial_messages: bool = False
fork_session: bool = False
agents: dict[str, AgentDefinition] | None = None
setting_sources: list[SettingSource] | None = None| Properti | Tipe | Default | Deskripsi |
|---|---|---|---|
allowed_tools | list[str] | [] | Daftar nama alat yang diizinkan |
system_prompt | str | SystemPromptPreset | None | None | Konfigurasi prompt sistem. Lewatkan string untuk prompt kustom, atau gunakan {"type": "preset", "preset": "claude_code"} untuk prompt sistem Claude Code. Tambahkan "append" untuk memperluas preset |
mcp_servers | dict[str, McpServerConfig] | str | Path | {} | Konfigurasi server MCP atau jalur ke file konfigurasi |
permission_mode | PermissionMode | None | None | Mode izin untuk penggunaan alat |
continue_conversation | bool | False | Lanjutkan percakapan paling baru |
resume | str | None | None | ID sesi untuk dilanjutkan |
max_turns | int | None | None | Putaran percakapan maksimum |
disallowed_tools | list[str] | [] | Daftar nama alat yang tidak diizinkan |
enable_file_checkpointing | bool | False | Aktifkan pelacakan perubahan file untuk rewinding. Lihat File checkpointing |
model | str | None | None | Model Claude yang akan digunakan |
output_format | OutputFormat | None | None | Tentukan format output untuk hasil agen. Lihat Structured outputs untuk detail |
permission_prompt_tool_name | str | None | None | Nama alat MCP untuk prompt izin |
cwd | str | Path | None | None | Direktori kerja saat ini |
settings | str | None | None | Jalur ke file pengaturan |
add_dirs | list[str | Path] | [] | Direktori tambahan yang dapat diakses Claude |
env | dict[str, str] | {} | Variabel lingkungan |
extra_args | dict[str, str | None] | {} | Argumen CLI tambahan untuk diteruskan langsung ke CLI |
max_buffer_size | int | None | None | Byte maksimum saat membuffer stdout CLI |
debug_stderr | Any | sys.stderr | Deprecated - Objek seperti file untuk output debug. Gunakan callback stderr sebagai gantinya |
stderr | Callable[[str], None] | None | None | Fungsi callback untuk output stderr dari CLI |
can_use_tool | CanUseTool | None | None | Fungsi callback izin alat |
hooks | dict[HookEvent, list[HookMatcher]] | None | None | Konfigurasi hook untuk mengintersepsi acara |
user | str | None | None | Pengidentifikasi pengguna |
include_partial_messages | bool | False | Sertakan acara streaming pesan parsial |
fork_session | bool | False | Saat melanjutkan dengan resume, fork ke ID sesi baru alih-alih melanjutkan sesi asli |
agents | dict[str, AgentDefinition] | None | None | Subagen yang didefinisikan secara terprogram |
plugins | list[SdkPluginConfig] | [] | Muat plugin kustom dari jalur lokal. Lihat Plugins untuk detail |
sandbox | SandboxSettings | None | None | Konfigurasi perilaku sandbox secara terprogram. Lihat Sandbox settings untuk detail |
setting_sources | list[SettingSource] | None | None (no settings) | Kontrol pengaturan filesystem mana yang akan dimuat. Saat dihilangkan, tidak ada pengaturan yang dimuat. Catatan: Harus menyertakan "project" untuk memuat file CLAUDE.md |
OutputFormatKonfigurasi untuk validasi output terstruktur.
class OutputFormat(TypedDict):
type: Literal["json_schema"]
schema: dict[str, Any]| Bidang | Diperlukan | Deskripsi |
|---|---|---|
type | Ya | Harus "json_schema" untuk validasi JSON Schema |
schema | Ya | Definisi JSON Schema untuk validasi output |
SystemPromptPresetKonfigurasi untuk menggunakan prompt sistem preset Claude Code dengan penambahan opsional.
class SystemPromptPreset(TypedDict):
type: Literal["preset"]
preset: Literal["claude_code"]
append: NotRequired[str]| Bidang | Diperlukan | Deskripsi |
|---|---|---|
type | Ya | Harus "preset" untuk menggunakan prompt sistem preset |
preset | Ya | Harus "claude_code" untuk menggunakan prompt sistem Claude Code |
append | Tidak | Instruksi tambahan untuk ditambahkan ke prompt sistem preset |
SettingSourceMengontrol sumber konfigurasi berbasis filesystem mana yang dimuat pengaturan SDK.
SettingSource = Literal["user", "project", "local"]| Nilai | Deskripsi | Lokasi |
|---|---|---|
"user" | Pengaturan pengguna global | ~/.claude/settings.json |
"project" | Pengaturan proyek bersama (dikontrol versi) | .claude/settings.json |
"local" | Pengaturan proyek lokal (gitignored) | .claude/settings.local.json |
Ketika setting_sources dihilangkan atau None, SDK tidak memuat pengaturan filesystem apa pun. Ini memberikan isolasi untuk aplikasi SDK.
Muat semua pengaturan filesystem (perilaku warisan):
# Load all settings like SDK v0.0.x did
from claude_agent_sdk import query, ClaudeAgentOptions
async for message in query(
prompt="Analyze this code",
options=ClaudeAgentOptions(
setting_sources=["user", "project", "local"] # Load all settings
)
):
print(message)Muat hanya sumber pengaturan tertentu:
# Load only project settings, ignore user and local
async for message in query(
prompt="Run CI checks",
options=ClaudeAgentOptions(
setting_sources=["project"] # Only .claude/settings.json
)
):
print(message)Lingkungan pengujian dan CI:
# Ensure consistent behavior in CI by excluding local settings
async for message in query(
prompt="Run tests",
options=ClaudeAgentOptions(
setting_sources=["project"], # Only team-shared settings
permission_mode="bypassPermissions"
)
):
print(message)Aplikasi hanya SDK:
# Define everything programmatically (default behavior)
# No filesystem dependencies - setting_sources defaults to None
async for message in query(
prompt="Review this PR",
options=ClaudeAgentOptions(
# setting_sources=None is the default, no need to specify
agents={ /* ... */ },
mcp_servers={ /* ... */ },
allowed_tools=["Read", "Grep", "Glob"]
)
):
print(message)Memuat instruksi proyek CLAUDE.md:
# Load project settings to include CLAUDE.md files
async for message in query(
prompt="Add a new feature following project conventions",
options=ClaudeAgentOptions(
system_prompt={
"type": "preset",
"preset": "claude_code" # Use Claude Code's system prompt
},
setting_sources=["project"], # Required to load CLAUDE.md from project
allowed_tools=["Read", "Write", "Edit"]
)
):
print(message)Ketika beberapa sumber dimuat, pengaturan digabungkan dengan preseden ini (tertinggi ke terendah):
.claude/settings.local.json).claude/settings.json)~/.claude/settings.json)Opsi terprogram (seperti agents, allowed_tools) selalu mengganti pengaturan filesystem.
AgentDefinitionKonfigurasi untuk subagen yang didefinisikan secara terprogram.
@dataclass
class AgentDefinition:
description: str
prompt: str
tools: list[str] | None = None
model: Literal["sonnet", "opus", "haiku", "inherit"] | None = None| Bidang | Diperlukan | Deskripsi |
|---|---|---|
description | Ya | Deskripsi bahasa alami tentang kapan menggunakan agen ini |
tools | Tidak | Array nama alat yang diizinkan. Jika dihilangkan, mewarisi semua alat |
prompt | Ya | Prompt sistem agen |
model | Tidak | Penggantian model untuk agen ini. Jika dihilangkan, menggunakan model utama |
PermissionModeMode izin untuk mengontrol eksekusi alat.
PermissionMode = Literal[
"default", # Standard permission behavior
"acceptEdits", # Auto-accept file edits
"plan", # Planning mode - no execution
"bypassPermissions" # Bypass all permission checks (use with caution)
]McpSdkServerConfigKonfigurasi untuk server MCP SDK yang dibuat dengan create_sdk_mcp_server().
class McpSdkServerConfig(TypedDict):
type: Literal["sdk"]
name: str
instance: Any # MCP Server instanceMcpServerConfigTipe union untuk konfigurasi server MCP.
McpServerConfig = McpStdioServerConfig | McpSSEServerConfig | McpHttpServerConfig | McpSdkServerConfigMcpStdioServerConfigclass McpStdioServerConfig(TypedDict):
type: NotRequired[Literal["stdio"]] # Optional for backwards compatibility
command: str
args: NotRequired[list[str]]
env: NotRequired[dict[str, str]]McpSSEServerConfigclass McpSSEServerConfig(TypedDict):
type: Literal["sse"]
url: str
headers: NotRequired[dict[str, str]]McpHttpServerConfigclass McpHttpServerConfig(TypedDict):
type: Literal["http"]
url: str
headers: NotRequired[dict[str, str]]SdkPluginConfigKonfigurasi untuk memuat plugin dalam SDK.
class SdkPluginConfig(TypedDict):
type: Literal["local"]
path: str| Bidang | Tipe | Deskripsi |
|---|---|---|
type | Literal["local"] | Harus "local" (hanya plugin lokal yang didukung saat ini) |
path | str | Jalur absolut atau relatif ke direktori plugin |
Contoh:
plugins=[
{"type": "local", "path": "./my-plugin"},
{"type": "local", "path": "/absolute/path/to/plugin"}
]Untuk informasi lengkap tentang membuat dan menggunakan plugin, lihat Plugins.
MessageTipe union dari semua pesan yang mungkin.
Message = UserMessage | AssistantMessage | SystemMessage | ResultMessageUserMessagePesan input pengguna.
@dataclass
class UserMessage:
content: str | list[ContentBlock]AssistantMessagePesan respons asisten dengan blok konten.
@dataclass
class AssistantMessage:
content: list[ContentBlock]
model: strSystemMessagePesan sistem dengan metadata.
@dataclass
class SystemMessage:
subtype: str
data: dict[str, Any]ResultMessagePesan hasil akhir dengan informasi biaya dan penggunaan.
@dataclass
class ResultMessage:
subtype: str
duration_ms: int
duration_api_ms: int
is_error: bool
num_turns: int
session_id: str
total_cost_usd: float | None = None
usage: dict[str, Any] | None = None
result: str | None = NoneContentBlockJenis union dari semua blok konten.
ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock | ToolResultBlockTextBlockBlok konten teks.
@dataclass
class TextBlock:
text: strThinkingBlockBlok konten pemikiran (untuk model dengan kemampuan pemikiran).
@dataclass
class ThinkingBlock:
thinking: str
signature: strToolUseBlockBlok permintaan penggunaan alat.
@dataclass
class ToolUseBlock:
id: str
name: str
input: dict[str, Any]ToolResultBlockBlok hasil eksekusi alat.
@dataclass
class ToolResultBlock:
tool_use_id: str
content: str | list[dict[str, Any]] | None = None
is_error: bool | None = NoneClaudeSDKErrorKelas pengecualian dasar untuk semua kesalahan SDK.
class ClaudeSDKError(Exception):
"""Base error for Claude SDK."""CLINotFoundErrorDimunculkan ketika Claude Code CLI tidak terinstal atau tidak ditemukan.
class CLINotFoundError(CLIConnectionError):
def __init__(self, message: str = "Claude Code not found", cli_path: str | None = None):
"""
Args:
message: Error message (default: "Claude Code not found")
cli_path: Optional path to the CLI that was not found
"""CLIConnectionErrorDimunculkan ketika koneksi ke Claude Code gagal.
class CLIConnectionError(ClaudeSDKError):
"""Failed to connect to Claude Code."""ProcessErrorDimunculkan ketika proses Claude Code gagal.
class ProcessError(ClaudeSDKError):
def __init__(self, message: str, exit_code: int | None = None, stderr: str | None = None):
self.exit_code = exit_code
self.stderr = stderrCLIJSONDecodeErrorDimunculkan ketika penguraian JSON gagal.
class CLIJSONDecodeError(ClaudeSDKError):
def __init__(self, line: str, original_error: Exception):
"""
Args:
line: The line that failed to parse
original_error: The original JSON decode exception
"""
self.line = line
self.original_error = original_errorUntuk panduan komprehensif tentang penggunaan hook dengan contoh dan pola umum, lihat panduan Hook.
HookEventJenis peristiwa hook yang didukung. Perhatikan bahwa karena keterbatasan pengaturan, Python SDK tidak mendukung hook SessionStart, SessionEnd, dan Notification.
HookEvent = Literal[
"PreToolUse", # Called before tool execution
"PostToolUse", # Called after tool execution
"UserPromptSubmit", # Called when user submits a prompt
"Stop", # Called when stopping execution
"SubagentStop", # Called when a subagent stops
"PreCompact" # Called before message compaction
]HookCallbackDefinisi tipe untuk fungsi callback hook.
HookCallback = Callable[
[dict[str, Any], str | None, HookContext],
Awaitable[dict[str, Any]]
]Parameter:
input_data: Data input spesifik hook (lihat panduan Hook)tool_use_id: Pengidentifikasi penggunaan alat opsional (untuk hook terkait alat)context: Konteks hook dengan informasi tambahanMengembalikan kamus yang mungkin berisi:
decision: "block" untuk memblokir tindakansystemMessage: Pesan sistem untuk ditambahkan ke transkriphookSpecificOutput: Data output spesifik hookHookContextInformasi konteks yang diteruskan ke callback hook.
@dataclass
class HookContext:
signal: Any | None = None # Future: abort signal supportHookMatcherKonfigurasi untuk mencocokkan hook ke peristiwa atau alat tertentu.
@dataclass
class HookMatcher:
matcher: str | None = None # Tool name or pattern to match (e.g., "Bash", "Write|Edit")
hooks: list[HookCallback] = field(default_factory=list) # List of callbacks to execute
timeout: float | None = None # Timeout in seconds for all hooks in this matcher (default: 60)Contoh ini mendaftarkan dua hook: satu yang memblokir perintah bash berbahaya seperti rm -rf /, dan yang lain yang mencatat semua penggunaan alat untuk audit. Hook keamanan hanya berjalan pada perintah Bash (melalui matcher), sementara hook logging berjalan pada semua alat.
from claude_agent_sdk import query, ClaudeAgentOptions, HookMatcher, HookContext
from typing import Any
async def validate_bash_command(
input_data: dict[str, Any],
tool_use_id: str | None,
context: HookContext
) -> dict[str, Any]:
"""Validate and potentially block dangerous bash commands."""
if input_data['tool_name'] == 'Bash':
command = input_data['tool_input'].get('command', '')
if 'rm -rf /' in command:
return {
'hookSpecificOutput': {
'hookEventName': 'PreToolUse',
'permissionDecision': 'deny',
'permissionDecisionReason': 'Dangerous command blocked'
}
}
return {}
async def log_tool_use(
input_data: dict[str, Any],
tool_use_id: str | None,
context: HookContext
) -> dict[str, Any]:
"""Log all tool usage for auditing."""
print(f"Tool used: {input_data.get('tool_name')}")
return {}
options = ClaudeAgentOptions(
hooks={
'PreToolUse': [
HookMatcher(matcher='Bash', hooks=[validate_bash_command], timeout=120), # 2 min for validation
HookMatcher(hooks=[log_tool_use]) # Applies to all tools (default 60s timeout)
],
'PostToolUse': [
HookMatcher(hooks=[log_tool_use])
]
}
)
async for message in query(
prompt="Analyze this codebase",
options=options
):
print(message)Dokumentasi skema input/output untuk semua alat Claude Code bawaan. Meskipun Python SDK tidak mengekspor ini sebagai tipe, mereka mewakili struktur input dan output alat dalam pesan.
Nama alat: Task
Input:
{
"description": str, # A short (3-5 word) description of the task
"prompt": str, # The task for the agent to perform
"subagent_type": str # The type of specialized agent to use
}Output:
{
"result": str, # Final result from the subagent
"usage": dict | None, # Token usage statistics
"total_cost_usd": float | None, # Total cost in USD
"duration_ms": int | None # Execution duration in milliseconds
}Nama alat: Bash
Input:
{
"command": str, # The command to execute
"timeout": int | None, # Optional timeout in milliseconds (max 600000)
"description": str | None, # Clear, concise description (5-10 words)
"run_in_background": bool | None # Set to true to run in background
}Output:
{
"output": str, # Combined stdout and stderr output
"exitCode": int, # Exit code of the command
"killed": bool | None, # Whether command was killed due to timeout
"shellId": str | None # Shell ID for background processes
}Nama alat: Edit
Input:
{
"file_path": str, # The absolute path to the file to modify
"old_string": str, # The text to replace
"new_string": str, # The text to replace it with
"replace_all": bool | None # Replace all occurrences (default False)
}Output:
{
"message": str, # Confirmation message
"replacements": int, # Number of replacements made
"file_path": str # File path that was edited
}Nama alat: Read
Input:
{
"file_path": str, # The absolute path to the file to read
"offset": int | None, # The line number to start reading from
"limit": int | None # The number of lines to read
}Output (File teks):
{
"content": str, # File contents with line numbers
"total_lines": int, # Total number of lines in file
"lines_returned": int # Lines actually returned
}Output (Gambar):
{
"image": str, # Base64 encoded image data
"mime_type": str, # Image MIME type
"file_size": int # File size in bytes
}Nama alat: Write
Input:
{
"file_path": str, # The absolute path to the file to write
"content": str # The content to write to the file
}Output:
{
"message": str, # Success message
"bytes_written": int, # Number of bytes written
"file_path": str # File path that was written
}Nama alat: Glob
Input:
{
"pattern": str, # The glob pattern to match files against
"path": str | None # The directory to search in (defaults to cwd)
}Output:
{
"matches": list[str], # Array of matching file paths
"count": int, # Number of matches found
"search_path": str # Search directory used
}Nama alat: Grep
Input:
{
"pattern": str, # The regular expression pattern
"path": str | None, # File or directory to search in
"glob": str | None, # Glob pattern to filter files
"type": str | None, # File type to search
"output_mode": str | None, # "content", "files_with_matches", or "count"
"-i": bool | None, # Case insensitive search
"-n": bool | None, # Show line numbers
"-B": int | None, # Lines to show before each match
"-A": int | None, # Lines to show after each match
"-C": int | None, # Lines to show before and after
"head_limit": int | None, # Limit output to first N lines/entries
"multiline": bool | None # Enable multiline mode
}Output (mode konten):
{
"matches": [
{
"file": str,
"line_number": int | None,
"line": str,
"before_context": list[str] | None,
"after_context": list[str] | None
}
],
"total_matches": int
}Output (mode files_with_matches):
{
"files": list[str], # Files containing matches
"count": int # Number of files with matches
}Nama alat: NotebookEdit
Input:
{
"notebook_path": str, # Absolute path to the Jupyter notebook
"cell_id": str | None, # The ID of the cell to edit
"new_source": str, # The new source for the cell
"cell_type": "code" | "markdown" | None, # The type of the cell
"edit_mode": "replace" | "insert" | "delete" | None # Edit operation type
}Output:
{
"message": str, # Success message
"edit_type": "replaced" | "inserted" | "deleted", # Type of edit performed
"cell_id": str | None, # Cell ID that was affected
"total_cells": int # Total cells in notebook after edit
}Nama alat: WebFetch
Input:
{
"url": str, # The URL to fetch content from
"prompt": str # The prompt to run on the fetched content
}Output:
{
"response": str, # AI model's response to the prompt
"url": str, # URL that was fetched
"final_url": str | None, # Final URL after redirects
"status_code": int | None # HTTP status code
}Nama alat: WebSearch
Input:
{
"query": str, # The search query to use
"allowed_domains": list[str] | None, # Only include results from these domains
"blocked_domains": list[str] | None # Never include results from these domains
}Output:
{
"results": [
{
"title": str,
"url": str,
"snippet": str,
"metadata": dict | None
}
],
"total_results": int,
"query": str
}Nama alat: TodoWrite
Input:
{
"todos": [
{
"content": str, # The task description
"status": "pending" | "in_progress" | "completed", # Task status
"activeForm": str # Active form of the description
}
]
}Output:
{
"message": str, # Success message
"stats": {
"total": int,
"pending": int,
"in_progress": int,
"completed": int
}
}Nama alat: BashOutput
Input:
{
"bash_id": str, # The ID of the background shell
"filter": str | None # Optional regex to filter output lines
}Output:
{
"output": str, # New output since last check
"status": "running" | "completed" | "failed", # Current shell status
"exitCode": int | None # Exit code when completed
}Nama alat: KillBash
Input:
{
"shell_id": str # The ID of the background shell to kill
}Output:
{
"message": str, # Success message
"shell_id": str # ID of the killed shell
}Nama alat: ExitPlanMode
Input:
{
"plan": str # The plan to run by the user for approval
}Output:
{
"message": str, # Confirmation message
"approved": bool | None # Whether user approved the plan
}Nama alat: ListMcpResources
Input:
{
"server": str | None # Optional server name to filter resources by
}Output:
{
"resources": [
{
"uri": str,
"name": str,
"description": str | None,
"mimeType": str | None,
"server": str
}
],
"total": int
}Nama alat: ReadMcpResource
Input:
{
"server": str, # The MCP server name
"uri": str # The resource URI to read
}Output:
{
"contents": [
{
"uri": str,
"mimeType": str | None,
"text": str | None,
"blob": str | None
}
],
"server": str
}from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, AssistantMessage, TextBlock
import asyncio
class ConversationSession:
"""Maintains a single conversation session with Claude."""
def __init__(self, options: ClaudeAgentOptions = None):
self.client = ClaudeSDKClient(options)
self.turn_count = 0
async def start(self):
await self.client.connect()
print("Starting conversation session. Claude will remember context.")
print("Commands: 'exit' to quit, 'interrupt' to stop current task, 'new' for new session")
while True:
user_input = input(f"\n[Turn {self.turn_count + 1}] You: ")
if user_input.lower() == 'exit':
break
elif user_input.lower() == 'interrupt':
await self.client.interrupt()
print("Task interrupted!")
continue
elif user_input.lower() == 'new':
# Disconnect and reconnect for a fresh session
await self.client.disconnect()
await self.client.connect()
self.turn_count = 0
print("Started new conversation session (previous context cleared)")
continue
# Send message - Claude remembers all previous messages in this session
await self.client.query(user_input)
self.turn_count += 1
# Process response
print(f"[Turn {self.turn_count}] Claude: ", end="")
async for message in self.client.receive_response():
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(block.text, end="")
print() # New line after response
await self.client.disconnect()
print(f"Conversation ended after {self.turn_count} turns.")
async def main():
options = ClaudeAgentOptions(
allowed_tools=["Read", "Write", "Bash"],
permission_mode="acceptEdits"
)
session = ConversationSession(options)
await session.start()
# Example conversation:
# Turn 1 - You: "Create a file called hello.py"
# Turn 1 - Claude: "I'll create a hello.py file for you..."
# Turn 2 - You: "What's in that file?"
# Turn 2 - Claude: "The hello.py file I just created contains..." (remembers!)
# Turn 3 - You: "Add a main function to it"
# Turn 3 - Claude: "I'll add a main function to hello.py..." (knows which file!)
asyncio.run(main())from claude_agent_sdk import (
ClaudeSDKClient,
ClaudeAgentOptions,
HookMatcher,
HookContext
)
import asyncio
from typing import Any
async def pre_tool_logger(
input_data: dict[str, Any],
tool_use_id: str | None,
context: HookContext
) -> dict[str, Any]:
"""Log all tool usage before execution."""
tool_name = input_data.get('tool_name', 'unknown')
print(f"[PRE-TOOL] About to use: {tool_name}")
# You can modify or block the tool execution here
if tool_name == "Bash" and "rm -rf" in str(input_data.get('tool_input', {})):
return {
'hookSpecificOutput': {
'hookEventName': 'PreToolUse',
'permissionDecision': 'deny',
'permissionDecisionReason': 'Dangerous command blocked'
}
}
return {}
async def post_tool_logger(
input_data: dict[str, Any],
tool_use_id: str | None,
context: HookContext
) -> dict[str, Any]:
"""Log results after tool execution."""
tool_name = input_data.get('tool_name', 'unknown')
print(f"[POST-TOOL] Completed: {tool_name}")
return {}
async def user_prompt_modifier(
input_data: dict[str, Any],
tool_use_id: str | None,
context: HookContext
) -> dict[str, Any]:
"""Add context to user prompts."""
original_prompt = input_data.get('prompt', '')
# Add timestamp to all prompts
from datetime import datetime
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
return {
'hookSpecificOutput': {
'hookEventName': 'UserPromptSubmit',
'updatedPrompt': f"[{timestamp}] {original_prompt}"
}
}
async def main():
options = ClaudeAgentOptions(
hooks={
'PreToolUse': [
HookMatcher(hooks=[pre_tool_logger]),
HookMatcher(matcher='Bash', hooks=[pre_tool_logger])
],
'PostToolUse': [
HookMatcher(hooks=[post_tool_logger])
],
'UserPromptSubmit': [
HookMatcher(hooks=[user_prompt_modifier])
]
},
allowed_tools=["Read", "Write", "Bash"]
)
async with ClaudeSDKClient(options=options) as client:
await client.query("List files in current directory")
async for message in client.receive_response():
# Hooks will automatically log tool usage
pass
asyncio.run(main())from claude_agent_sdk import (
ClaudeSDKClient,
ClaudeAgentOptions,
AssistantMessage,
ToolUseBlock,
ToolResultBlock,
TextBlock
)
import asyncio
async def monitor_progress():
options = ClaudeAgentOptions(
allowed_tools=["Write", "Bash"],
permission_mode="acceptEdits"
)
async with ClaudeSDKClient(options=options) as client:
await client.query(
"Create 5 Python files with different sorting algorithms"
)
# Monitor progress in real-time
files_created = []
async for message in client.receive_messages():
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, ToolUseBlock):
if block.name == "Write":
file_path = block.input.get("file_path", "")
print(f"🔨 Creating: {file_path}")
elif isinstance(block, ToolResultBlock):
print(f"✅ Completed tool execution")
elif isinstance(block, TextBlock):
print(f"💭 Claude says: {block.text[:100]}...")
# Check if we've received the final result
if hasattr(message, 'subtype') and message.subtype in ['success', 'error']:
print(f"\n🎯 Task completed!")
break
asyncio.run(monitor_progress())from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, ToolUseBlock
import asyncio
async def create_project():
options = ClaudeAgentOptions(
allowed_tools=["Read", "Write", "Bash"],
permission_mode='acceptEdits',
cwd="/home/user/project"
)
async for message in query(
prompt="Create a Python project structure with setup.py",
options=options
):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, ToolUseBlock):
print(f"Using tool: {block.name}")
asyncio.run(create_project())from claude_agent_sdk import (
query,
CLINotFoundError,
ProcessError,
CLIJSONDecodeError
)
try:
async for message in query(prompt="Hello"):
print(message)
except CLINotFoundError:
print("Please install Claude Code: npm install -g @anthropic-ai/claude-code")
except ProcessError as e:
print(f"Process failed with exit code: {e.exit_code}")
except CLIJSONDecodeError as e:
print(f"Failed to parse response: {e}")from claude_agent_sdk import ClaudeSDKClient
import asyncio
async def interactive_session():
async with ClaudeSDKClient() as client:
# Send initial message
await client.query("What's the weather like?")
# Process responses
async for msg in client.receive_response():
print(msg)
# Send follow-up
await client.query("Tell me more about that")
# Process follow-up response
async for msg in client.receive_response():
print(msg)
asyncio.run(interactive_session())from claude_agent_sdk import (
ClaudeSDKClient,
ClaudeAgentOptions,
tool,
create_sdk_mcp_server,
AssistantMessage,
TextBlock
)
import asyncio
from typing import Any
# Define custom tools with @tool decorator
@tool("calculate", "Perform mathematical calculations", {"expression": str})
async def calculate(args: dict[str, Any]) -> dict[str, Any]:
try:
result = eval(args["expression"], {"__builtins__": {}})
return {
"content": [{
"type": "text",
"text": f"Result: {result}"
}]
}
except Exception as e:
return {
"content": [{
"type": "text",
"text": f"Error: {str(e)}"
}],
"is_error": True
}
@tool("get_time", "Get current time", {})
async def get_time(args: dict[str, Any]) -> dict[str, Any]:
from datetime import datetime
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
return {
"content": [{
"type": "text",
"text": f"Current time: {current_time}"
}]
}
async def main():
# Create SDK MCP server with custom tools
my_server = create_sdk_mcp_server(
name="utilities",
version="1.0.0",
tools=[calculate, get_time]
)
# Configure options with the server
options = ClaudeAgentOptions(
mcp_servers={"utils": my_server},
allowed_tools=[
"mcp__utils__calculate",
"mcp__utils__get_time"
]
)
# Use ClaudeSDKClient for interactive tool usage
async with ClaudeSDKClient(options=options) as client:
await client.query("What's 123 * 456?")
# Process calculation response
async for message in client.receive_response():
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(f"Calculation: {block.text}")
# Follow up with time query
await client.query("What time is it now?")
async for message in client.receive_response():
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(f"Time: {block.text}")
asyncio.run(main())SandboxSettingsKonfigurasi untuk perilaku sandbox. Gunakan ini untuk mengaktifkan sandboxing perintah dan mengonfigurasi pembatasan jaringan secara terprogram.
class SandboxSettings(TypedDict, total=False):
enabled: bool
autoAllowBashIfSandboxed: bool
excludedCommands: list[str]
allowUnsandboxedCommands: bool
network: SandboxNetworkConfig
ignoreViolations: SandboxIgnoreViolations
enableWeakerNestedSandbox: bool| Properti | Tipe | Default | Deskripsi |
|---|---|---|---|
enabled | bool | False | Aktifkan mode sandbox untuk eksekusi perintah |
autoAllowBashIfSandboxed | bool | False | Persetujuan otomatis perintah bash ketika sandbox diaktifkan |
excludedCommands | list[str] | [] | Perintah yang selalu melewati pembatasan sandbox (misalnya, ["docker"]). Ini berjalan tanpa sandbox secara otomatis tanpa keterlibatan model |
allowUnsandboxedCommands | bool | False | Izinkan model untuk meminta menjalankan perintah di luar sandbox. Ketika True, model dapat mengatur dangerouslyDisableSandbox dalam input alat, yang kembali ke sistem izin |
network | SandboxNetworkConfig | None | Konfigurasi sandbox spesifik jaringan |
ignoreViolations | SandboxIgnoreViolations | None | Konfigurasi pelanggaran sandbox mana yang akan diabaikan |
enableWeakerNestedSandbox | bool | False | Aktifkan sandbox bersarang yang lebih lemah untuk kompatibilitas |
Pembatasan akses filesystem dan jaringan TIDAK dikonfigurasi melalui pengaturan sandbox. Sebaliknya, mereka berasal dari aturan izin:
Gunakan pengaturan sandbox untuk sandboxing eksekusi perintah, dan aturan izin untuk kontrol akses filesystem dan jaringan.
from claude_agent_sdk import query, ClaudeAgentOptions, SandboxSettings
sandbox_settings: SandboxSettings = {
"enabled": True,
"autoAllowBashIfSandboxed": True,
"excludedCommands": ["docker"],
"network": {
"allowLocalBinding": True,
"allowUnixSockets": ["/var/run/docker.sock"]
}
}
async for message in query(
prompt="Build and test my project",
options=ClaudeAgentOptions(sandbox=sandbox_settings)
):
print(message)SandboxNetworkConfigKonfigurasi spesifik jaringan untuk mode sandbox.
class SandboxNetworkConfig(TypedDict, total=False):
allowLocalBinding: bool
allowUnixSockets: list[str]
allowAllUnixSockets: bool
httpProxyPort: int
socksProxyPort: int| Properti | Tipe | Default | Deskripsi |
|---|---|---|---|
allowLocalBinding | bool | False | Izinkan proses untuk mengikat ke port lokal (misalnya, untuk server dev) |
allowUnixSockets | list[str] | [] | Jalur soket Unix yang dapat diakses oleh proses (misalnya, soket Docker) |
allowAllUnixSockets | bool | False | Izinkan akses ke semua soket Unix |
httpProxyPort | int | None | Port proxy HTTP untuk permintaan jaringan |
socksProxyPort | int | None | Port proxy SOCKS untuk permintaan jaringan |
SandboxIgnoreViolationsKonfigurasi untuk mengabaikan pelanggaran sandbox tertentu.
class SandboxIgnoreViolations(TypedDict, total=False):
file: list[str]
network: list[str]| Properti | Tipe | Default | Deskripsi |
|---|---|---|---|
file | list[str] | [] | Pola jalur file untuk mengabaikan pelanggaran |
network | list[str] | [] | Pola jaringan untuk mengabaikan pelanggaran |
Ketika allowUnsandboxedCommands diaktifkan, model dapat meminta untuk menjalankan perintah di luar sandbox dengan mengatur dangerouslyDisableSandbox: True dalam input alat. Permintaan ini kembali ke sistem izin yang ada, artinya handler can_use_tool Anda akan dipanggil, memungkinkan Anda menerapkan logika otorisasi kustom.
excludedCommands vs allowUnsandboxedCommands:
excludedCommands: Daftar statis perintah yang selalu melewati sandbox secara otomatis (misalnya, ["docker"]). Model tidak memiliki kontrol atas ini.allowUnsandboxedCommands: Memungkinkan model memutuskan pada waktu runtime apakah akan meminta eksekusi tanpa sandbox dengan mengatur dangerouslyDisableSandbox: True dalam input alat.from claude_agent_sdk import query, ClaudeAgentOptions
async def can_use_tool(tool: str, input: dict) -> bool:
# Check if the model is requesting to bypass the sandbox
if tool == "Bash" and input.get("dangerouslyDisableSandbox"):
# The model wants to run this command outside the sandbox
print(f"Unsandboxed command requested: {input.get('command')}")
# Return True to allow, False to deny
return is_command_authorized(input.get("command"))
return True
async def main():
async for message in query(
prompt="Deploy my application",
options=ClaudeAgentOptions(
sandbox={
"enabled": True,
"allowUnsandboxedCommands": True # Model can request unsandboxed execution
},
permission_mode="default",
can_use_tool=can_use_tool
)
):
print(message)Pola ini memungkinkan Anda untuk:
Perintah yang berjalan dengan dangerouslyDisableSandbox: True memiliki akses sistem penuh. Pastikan handler can_use_tool Anda memvalidasi permintaan ini dengan hati-hati.