Instalasi

pip install claude-agent-sdk

Memilih Antara query() dan ClaudeSDKClient

Python SDK menyediakan dua cara untuk berinteraksi dengan Claude Code:

Perbandingan Cepat

Fiturquery()ClaudeSDKClient
SesiMembuat sesi baru setiap kaliMenggunakan kembali sesi yang sama
PercakapanPertukaran tunggalBeberapa pertukaran dalam konteks yang sama
KoneksiDikelola secara otomatisKontrol manual
Input Streaming✅ Didukung✅ Didukung
Interupsi❌ Tidak didukung✅ Didukung
Hook❌ Tidak didukung✅ Didukung
Tool Kustom❌ Tidak didukung✅ Didukung
Lanjutkan Chat❌ Sesi baru setiap kali✅ Mempertahankan percakapan
Kasus PenggunaanTugas sekali pakaiPercakapan berkelanjutan

Kapan Menggunakan query() (Sesi Baru Setiap Kali)

Terbaik untuk:
  • Pertanyaan sekali pakai di mana Anda tidak memerlukan riwayat percakapan
  • Tugas independen yang tidak memerlukan konteks dari pertukaran sebelumnya
  • Skrip otomatisasi sederhana
  • Ketika Anda ingin memulai dari awal setiap kali

Kapan Menggunakan ClaudeSDKClient (Percakapan Berkelanjutan)

Terbaik untuk:
  • Melanjutkan percakapan - Ketika Anda perlu Claude mengingat konteks
  • Pertanyaan lanjutan - Membangun dari respons sebelumnya
  • Aplikasi interaktif - Antarmuka chat, REPL
  • Logika yang didorong respons - Ketika tindakan selanjutnya bergantung pada respons Claude
  • Kontrol sesi - Mengelola siklus hidup percakapan secara eksplisit

Fungsi

query()

Membuat sesi baru untuk setiap interaksi dengan Claude Code. Mengembalikan iterator async yang menghasilkan pesan saat mereka tiba. Setiap panggilan ke query() dimulai segar tanpa memori dari interaksi sebelumnya.
async def query(
    *,
    prompt: str | AsyncIterable[dict[str, Any]],
    options: ClaudeAgentOptions | None = None
) -> AsyncIterator[Message]

Parameter

ParameterTipeDeskripsi
promptstr | AsyncIterable[dict]Prompt input sebagai string atau async iterable untuk mode streaming
optionsClaudeAgentOptions | NoneObjek konfigurasi opsional (default ke ClaudeAgentOptions() jika None)

Mengembalikan

Mengembalikan AsyncIterator[Message] yang menghasilkan pesan dari percakapan.

Contoh - Dengan opsi


import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions

async def main():
    options = ClaudeAgentOptions(
        system_prompt="Anda adalah pengembang Python ahli",
        permission_mode='acceptEdits',
        cwd="/home/user/project"
    )

    async for message in query(
        prompt="Buat server web Python",
        options=options
    ):
        print(message)


asyncio.run(main())

tool()

Dekorator untuk mendefinisikan tool 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

ParameterTipeDeskripsi
namestrPengenal unik untuk tool
descriptionstrDeskripsi yang dapat dibaca manusia tentang apa yang dilakukan tool
input_schematype | dict[str, Any]Skema yang mendefinisikan parameter input tool (lihat di bawah)

Opsi Skema Input

  1. Pemetaan tipe sederhana (direkomendasikan):
    {"text": str, "count": int, "enabled": bool}
    
  2. Format JSON Schema (untuk validasi kompleks):
    {
        "type": "object",
        "properties": {
            "text": {"type": "string"},
            "count": {"type": "integer", "minimum": 0}
        },
        "required": ["text"]
    }
    

Mengembalikan

Fungsi dekorator yang membungkus implementasi tool dan mengembalikan instance SdkMcpTool.

Contoh

from claude_agent_sdk import tool
from typing import Any

@tool("greet", "Sapa pengguna", {"name": str})
async def greet(args: dict[str, Any]) -> dict[str, Any]:
    return {
        "content": [{
            "type": "text",
            "text": f"Halo, {args['name']}!"
        }]
    }

create_sdk_mcp_server()

Membuat server MCP dalam proses yang berjalan di dalam aplikasi Python Anda.
def create_sdk_mcp_server(
    name: str,
    version: str = "1.0.0",
    tools: list[SdkMcpTool[Any]] | None = None
) -> McpSdkServerConfig

Parameter

ParameterTipeDefaultDeskripsi
namestr-Pengenal unik untuk server
versionstr"1.0.0"String versi server
toolslist[SdkMcpTool[Any]] | NoneNoneDaftar fungsi tool yang dibuat dengan dekorator @tool

Mengembalikan

Mengembalikan objek McpSdkServerConfig yang dapat diteruskan ke ClaudeAgentOptions.mcp_servers.

Contoh

from claude_agent_sdk import tool, create_sdk_mcp_server

@tool("add", "Tambahkan dua angka", {"a": float, "b": float})
async def add(args):
    return {
        "content": [{
            "type": "text",
            "text": f"Jumlah: {args['a'] + args['b']}"
        }]
    }

@tool("multiply", "Kalikan dua angka", {"a": float, "b": float})
async def multiply(args):
    return {
        "content": [{
            "type": "text",
            "text": f"Hasil kali: {args['a'] * args['b']}"
        }]
    }

calculator = create_sdk_mcp_server(
    name="calculator",
    version="2.0.0",
    tools=[add, multiply]  # Teruskan fungsi yang didekorasi
)

# Gunakan dengan Claude
options = ClaudeAgentOptions(
    mcp_servers={"calc": calculator},
    allowed_tools=["mcp__calc__add", "mcp__calc__multiply"]
)

Kelas

ClaudeSDKClient

Mempertahankan sesi percakapan di beberapa pertukaran. Ini adalah setara Python dari bagaimana fungsi query() TypeScript SDK bekerja secara internal - ia membuat objek klien yang dapat melanjutkan percakapan.

Fitur Utama

  • Kontinuitas Sesi: Mempertahankan konteks percakapan di beberapa panggilan query()
  • Percakapan yang Sama: Claude mengingat pesan sebelumnya dalam sesi
  • Dukungan Interupsi: Dapat menghentikan Claude di tengah eksekusi
  • Siklus Hidup Eksplisit: Anda mengontrol kapan sesi dimulai dan berakhir
  • Alur yang Didorong Respons: Dapat bereaksi terhadap respons dan mengirim tindak lanjut
  • Tool & Hook Kustom: Mendukung tool kustom (dibuat dengan dekorator @tool) dan hook
class 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 disconnect(self) -> None

Metode

MetodeDeskripsi
__init__(options)Inisialisasi klien dengan konfigurasi opsional
connect(prompt)Terhubung ke Claude dengan prompt awal opsional atau stream 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 bekerja dalam mode streaming)
disconnect()Putuskan koneksi dari Claude

Dukungan Context Manager

Klien dapat digunakan sebagai context manager async untuk manajemen koneksi otomatis:
async with ClaudeSDKClient() as client:
    await client.query("Halo Claude")
    async for message in client.receive_response():
        print(message)
Penting: Saat melakukan iterasi pesan, hindari menggunakan break untuk keluar lebih awal karena ini dapat menyebabkan masalah pembersihan asyncio. Sebagai gantinya, biarkan iterasi selesai secara alami atau gunakan flag untuk melacak kapan Anda telah menemukan apa yang Anda butuhkan.

Contoh - Melanjutkan percakapan

import asyncio
from claude_agent_sdk import ClaudeSDKClient, AssistantMessage, TextBlock, ResultMessage

async def main():
    async with ClaudeSDKClient() as client:
        # Pertanyaan pertama
        await client.query("Apa ibu kota Prancis?")
        
        # Proses respons
        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}")
        
        # Pertanyaan lanjutan - Claude mengingat konteks sebelumnya
        await client.query("Berapa populasi kota itu?")
        
        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}")
        
        # Tindak lanjut lain - masih dalam percakapan yang sama
        await client.query("Apa saja landmark terkenal di sana?")
        
        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())

Contoh - Input streaming dengan ClaudeSDKClient

import asyncio
from claude_agent_sdk import ClaudeSDKClient

async def message_stream():
    """Hasilkan pesan secara dinamis."""
    yield {"type": "text", "text": "Analisis data berikut:"}
    await asyncio.sleep(0.5)
    yield {"type": "text", "text": "Suhu: 25°C"}
    await asyncio.sleep(0.5)
    yield {"type": "text", "text": "Kelembaban: 60%"}
    await asyncio.sleep(0.5)
    yield {"type": "text", "text": "Pola apa yang Anda lihat?"}

async def main():
    async with ClaudeSDKClient() as client:
        # Stream input ke Claude
        await client.query(message_stream())
        
        # Proses respons
        async for message in client.receive_response():
            print(message)
        
        # Tindak lanjut dalam sesi yang sama
        await client.query("Haruskah kita khawatir tentang pembacaan ini?")
        
        async for message in client.receive_response():
            print(message)

asyncio.run(main())

Contoh - Menggunakan interupsi

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:
        # Mulai tugas yang berjalan lama
        await client.query("Hitung dari 1 sampai 100 dengan lambat")
        
        # Biarkan berjalan sebentar
        await asyncio.sleep(2)
        
        # Interupsi tugas
        await client.interrupt()
        print("Tugas diinterupsi!")
        
        # Kirim perintah baru
        await client.query("Katakan halo saja")
        
        async for message in client.receive_response():
            # Proses respons baru
            pass

asyncio.run(interruptible_task())

Contoh - Kontrol izin lanjutan

from claude_agent_sdk import (
    ClaudeSDKClient,
    ClaudeAgentOptions
)

async def custom_permission_handler(
    tool_name: str,
    input_data: dict,
    context: dict
):
    """Logika kustom untuk izin tool."""

    # Blokir penulisan ke direktori sistem
    if tool_name == "Write" and input_data.get("file_path", "").startswith("/system/"):
        return {
            "behavior": "deny",
            "message": "Penulisan direktori sistem tidak diizinkan",
            "interrupt": True
        }

    # Alihkan operasi file sensitif
    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}
        }

    # Izinkan semua yang lain
    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("Perbarui file konfigurasi sistem")
        
        async for message in client.receive_response():
            # Akan menggunakan path sandbox sebagai gantinya
            print(message)

asyncio.run(main())

Tipe

SdkMcpTool

Definisi untuk tool SDK MCP 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]]]
PropertiTipeDeskripsi
namestrPengenal unik untuk tool
descriptionstrDeskripsi yang dapat dibaca manusia
input_schematype[T] | dict[str, Any]Skema untuk validasi input
handlerCallable[[T], Awaitable[dict[str, Any]]]Fungsi async yang menangani eksekusi tool

ClaudeAgentOptions

Dataclass konfigurasi untuk query Claude Code.
@dataclass
class ClaudeAgentOptions:
    allowed_tools: list[str] = field(default_factory=list)
    max_thinking_tokens: int = 8000
    system_prompt: str | 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
    fork_session: bool = False
    max_turns: int | None = None
    disallowed_tools: list[str] = field(default_factory=list)
    model: str | 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)
PropertiTipeDefaultDeskripsi
allowed_toolslist[str][]Daftar nama tool yang diizinkan
max_thinking_tokensint8000Token maksimum untuk proses berpikir
system_promptstr | NoneNoneKonfigurasi system prompt. Berikan string untuk prompt kustom, atau gunakan format preset untuk system prompt Claude Code
mcp_serversdict[str, McpServerConfig] | str | Path{}Konfigurasi server MCP atau path ke file konfigurasi
permission_modePermissionMode | NoneNoneMode izin untuk penggunaan tool
continue_conversationboolFalseLanjutkan percakapan terbaru
resumestr | NoneNoneID sesi untuk dilanjutkan
fork_sessionboolFalseSaat melanjutkan dengan resume, fork ke ID sesi baru alih-alih melanjutkan sesi asli
max_turnsint | NoneNoneGiliran percakapan maksimum
disallowed_toolslist[str][]Daftar nama tool yang tidak diizinkan
modelstr | NoneNoneModel Claude yang akan digunakan
permission_prompt_tool_namestr | NoneNoneNama tool MCP untuk prompt izin
cwdstr | Path | NoneNoneDirektori kerja saat ini
settingsstr | NoneNonePath ke file pengaturan
add_dirslist[str | Path][]Direktori tambahan yang dapat diakses Claude
extra_argsdict[str, str | None]{}Argumen CLI tambahan untuk diteruskan langsung ke CLI
can_use_toolCanUseTool | NoneNoneFungsi callback izin tool
hooksdict[HookEvent, list[HookMatcher]] | NoneNoneKonfigurasi hook untuk mencegat event
agentsdict[str, AgentDefinition] | NoneNoneSubagen yang didefinisikan secara programatis
setting_sourceslist[SettingSource] | NoneNone (tidak ada pengaturan)Kontrol sumber pengaturan filesystem mana yang dimuat SDK. Jika dihilangkan, tidak ada pengaturan yang dimuat

SettingSource

Mengontrol sumber konfigurasi berbasis filesystem mana yang dimuat SDK untuk pengaturan.
SettingSource = Literal["user", "project", "local"]
NilaiDeskripsiLokasi
"user"Pengaturan pengguna global~/.claude/settings.json
"project"Pengaturan proyek bersama (dikontrol versi).claude/settings.json
"local"Pengaturan proyek lokal (diabaikan git).claude/settings.local.json

Perilaku default

Ketika setting_sources dihilangkan atau None, SDK tidak memuat pengaturan filesystem apa pun. Ini memberikan isolasi untuk aplikasi SDK.

Mengapa menggunakan setting_sources?

Muat semua pengaturan filesystem (perilaku legacy):
# Muat semua pengaturan seperti SDK v0.0.x
from claude_agent_sdk import query, ClaudeAgentOptions

async for message in query(
    prompt="Analisis kode ini",
    options=ClaudeAgentOptions(
        setting_sources=["user", "project", "local"]  # Muat semua pengaturan
    )
):
    print(message)
Muat hanya sumber pengaturan tertentu:
# Muat hanya pengaturan proyek, abaikan pengguna dan lokal
async for message in query(
    prompt="Jalankan pemeriksaan CI",
    options=ClaudeAgentOptions(
        setting_sources=["project"]  # Hanya .claude/settings.json
    )
):
    print(message)
Lingkungan pengujian dan CI:
# Pastikan perilaku konsisten di CI dengan mengecualikan pengaturan lokal
async for message in query(
    prompt="Jalankan tes",
    options=ClaudeAgentOptions(
        setting_sources=["project"],  # Hanya pengaturan yang dibagikan tim
        permission_mode="bypassPermissions"
    )
):
    print(message)
Aplikasi khusus SDK:
# Definisikan semuanya secara programatis (perilaku default)
# Tidak ada ketergantungan filesystem - setting_sources default ke None
async for message in query(
    prompt="Tinjau PR ini",
    options=ClaudeAgentOptions(
        # setting_sources=None adalah default, tidak perlu ditentukan
        agents={ /* ... */ },
        mcp_servers={ /* ... */ },
        allowed_tools=["Read", "Grep", "Glob"]
    )
):
    print(message)

Prioritas pengaturan

Ketika beberapa sumber dimuat, pengaturan digabungkan dengan prioritas ini (tertinggi ke terendah):
  1. Pengaturan lokal (.claude/settings.local.json)
  2. Pengaturan proyek (.claude/settings.json)
  3. Pengaturan pengguna (~/.claude/settings.json)
Opsi programatis (seperti agents, allowed_tools) selalu menimpa pengaturan filesystem.

AgentDefinition

Konfigurasi untuk subagen yang didefinisikan secara programatis.
@dataclass
class AgentDefinition:
    description: str
    prompt: str
    tools: list[str] | None = None
    model: Literal["sonnet", "opus", "haiku", "inherit"] | None = None
FieldWajibDeskripsi
descriptionYaDeskripsi bahasa alami tentang kapan menggunakan agen ini
toolsTidakArray nama tool yang diizinkan. Jika dihilangkan, mewarisi semua tool
promptYaSystem prompt agen
modelTidakOverride model untuk agen ini. Jika dihilangkan, menggunakan model utama

PermissionMode

Mode izin untuk mengontrol eksekusi tool.
PermissionMode = Literal[
    "default",           # Perilaku izin standar
    "acceptEdits",       # Auto-terima edit file
    "plan",              # Mode perencanaan - tidak ada eksekusi
    "bypassPermissions"  # Lewati semua pemeriksaan izin (gunakan dengan hati-hati)
]

McpSdkServerConfig

Konfigurasi untuk server SDK MCP yang dibuat dengan create_sdk_mcp_server().
class McpSdkServerConfig(TypedDict):
    type: Literal["sdk"]
    name: str
    instance: Any  # Instance MCP Server

McpServerConfig

Tipe union untuk konfigurasi server MCP.
McpServerConfig = McpStdioServerConfig | McpSSEServerConfig | McpHttpServerConfig | McpSdkServerConfig

McpStdioServerConfig

class McpStdioServerConfig(TypedDict):
    type: NotRequired[Literal["stdio"]]  # Opsional untuk kompatibilitas mundur
    command: str
    args: NotRequired[list[str]]
    env: NotRequired[dict[str, str]]

McpSSEServerConfig

class McpSSEServerConfig(TypedDict):
    type: Literal["sse"]
    url: str
    headers: NotRequired[dict[str, str]]

McpHttpServerConfig

class McpHttpServerConfig(TypedDict):
    type: Literal["http"]
    url: str
    headers: NotRequired[dict[str, str]]

Tipe Pesan

Message

Tipe union dari semua pesan yang mungkin.
Message = UserMessage | AssistantMessage | SystemMessage | ResultMessage

UserMessage

Pesan input pengguna.
@dataclass
class UserMessage:
    content: str | list[ContentBlock]

AssistantMessage

Pesan respons asisten dengan blok konten.
@dataclass
class AssistantMessage:
    content: list[ContentBlock]
    model: str

SystemMessage

Pesan sistem dengan metadata.
@dataclass
class SystemMessage:
    subtype: str
    data: dict[str, Any]

ResultMessage

Pesan 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 = None

Tipe Blok Konten

ContentBlock

Tipe union dari semua blok konten.
ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock | ToolResultBlock

TextBlock

Blok konten teks.
@dataclass
class TextBlock:
    text: str

ThinkingBlock

Blok konten berpikir (untuk model dengan kemampuan berpikir).
@dataclass
class ThinkingBlock:
    thinking: str
    signature: str

ToolUseBlock

Blok permintaan penggunaan tool.
@dataclass
class ToolUseBlock:
    id: str
    name: str
    input: dict[str, Any]

ToolResultBlock

Blok hasil eksekusi tool.
@dataclass
class ToolResultBlock:
    tool_use_id: str
    content: str | list[dict[str, Any]] | None = None
    is_error: bool | None = None

Tipe Error

ClaudeSDKError

Kelas exception dasar untuk semua error SDK.
class ClaudeSDKError(Exception):
    """Error dasar untuk Claude SDK."""

CLINotFoundError

Dimunculkan ketika Claude Code CLI tidak terinstal atau tidak ditemukan.
class CLINotFoundError(CLIConnectionError):
    def __init__(self, message: str = "Claude Code tidak ditemukan", cli_path: str | None = None):
        """
        Args:
            message: Pesan error (default: "Claude Code tidak ditemukan")
            cli_path: Path opsional ke CLI yang tidak ditemukan
        """

CLIConnectionError

Dimunculkan ketika koneksi ke Claude Code gagal.
class CLIConnectionError(ClaudeSDKError):
    """Gagal terhubung ke Claude Code."""

ProcessError

Dimunculkan 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 = stderr

CLIJSONDecodeError

Dimunculkan ketika parsing JSON gagal.
class CLIJSONDecodeError(ClaudeSDKError):
    def __init__(self, line: str, original_error: Exception):
        """
        Args:
            line: Baris yang gagal diparse
            original_error: Exception decode JSON asli
        """
        self.line = line
        self.original_error = original_error

Tipe Hook

HookEvent

Tipe event hook yang didukung. Perhatikan bahwa karena keterbatasan setup, Python SDK tidak mendukung hook SessionStart, SessionEnd, dan Notification.
HookEvent = Literal[
    "PreToolUse",      # Dipanggil sebelum eksekusi tool
    "PostToolUse",     # Dipanggil setelah eksekusi tool
    "UserPromptSubmit", # Dipanggil ketika pengguna mengirim prompt
    "Stop",            # Dipanggil ketika menghentikan eksekusi
    "SubagentStop",    # Dipanggil ketika subagen berhenti
    "PreCompact"       # Dipanggil sebelum kompaksi pesan
]

HookCallback

Definisi 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 dokumentasi hook)
  • tool_use_id: Pengenal penggunaan tool opsional (untuk hook terkait tool)
  • context: Konteks hook dengan informasi tambahan
Mengembalikan dictionary yang mungkin berisi:
  • decision: "block" untuk memblokir tindakan
  • systemMessage: Pesan sistem untuk ditambahkan ke transkrip
  • hookSpecificOutput: Data output spesifik hook

HookContext

Informasi konteks yang diteruskan ke callback hook.
@dataclass
class HookContext:
    signal: Any | None = None  # Masa depan: dukungan sinyal abort

HookMatcher

Konfigurasi untuk mencocokkan hook ke event atau tool tertentu.
@dataclass
class HookMatcher:
    matcher: str | None = None        # Nama tool atau pola untuk dicocokkan (mis., "Bash", "Write|Edit")
    hooks: list[HookCallback] = field(default_factory=list)  # Daftar callback untuk dieksekusi

Contoh Penggunaan Hook

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]:
    """Validasi dan berpotensi memblokir perintah bash berbahaya."""
    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': 'Perintah berbahaya diblokir'
                }
            }
    return {}

async def log_tool_use(
    input_data: dict[str, Any],
    tool_use_id: str | None,
    context: HookContext
) -> dict[str, Any]:
    """Log semua penggunaan tool untuk audit."""
    print(f"Tool digunakan: {input_data.get('tool_name')}")
    return {}

options = ClaudeAgentOptions(
    hooks={
        'PreToolUse': [
            HookMatcher(matcher='Bash', hooks=[validate_bash_command]),
            HookMatcher(hooks=[log_tool_use])  # Berlaku untuk semua tool
        ],
        'PostToolUse': [
            HookMatcher(hooks=[log_tool_use])
        ]
    }
)

async for message in query(
    prompt="Analisis codebase ini",
    options=options
):
    print(message)

Tipe Input/Output Tool

Dokumentasi skema input/output untuk semua tool Claude Code bawaan. Meskipun Python SDK tidak mengekspor ini sebagai tipe, mereka mewakili struktur input dan output tool dalam pesan.

Task

Nama tool: Task Input:
{
    "description": str,      # Deskripsi singkat (3-5 kata) dari tugas
    "prompt": str,           # Tugas untuk dilakukan agen
    "subagent_type": str     # Jenis agen khusus yang akan digunakan
}
Output:
{
    "result": str,                    # Hasil akhir dari subagen
    "usage": dict | None,             # Statistik penggunaan token
    "total_cost_usd": float | None,  # Total biaya dalam USD
    "duration_ms": int | None         # Durasi eksekusi dalam milidetik
}

Bash

Nama tool: Bash Input:
{
    "command": str,                  # Perintah yang akan dieksekusi
    "timeout": int | None,           # Timeout opsional dalam milidetik (maks 600000)
    "description": str | None,       # Deskripsi yang jelas dan ringkas (5-10 kata)
    "run_in_background": bool | None # Set ke true untuk berjalan di latar belakang
}
Output:
{
    "output": str,              # Output stdout dan stderr gabungan
    "exitCode": int,            # Kode keluar dari perintah
    "killed": bool | None,      # Apakah perintah dibunuh karena timeout
    "shellId": str | None       # ID shell untuk proses latar belakang
}

Edit

Nama tool: Edit Input:
{
    "file_path": str,           # Path absolut ke file yang akan dimodifikasi
    "old_string": str,          # Teks yang akan diganti
    "new_string": str,          # Teks pengganti
    "replace_all": bool | None  # Ganti semua kemunculan (default False)
}
Output:
{
    "message": str,      # Pesan konfirmasi
    "replacements": int, # Jumlah penggantian yang dibuat
    "file_path": str     # Path file yang diedit
}

MultiEdit

Nama tool: MultiEdit Input:
{
    "file_path": str,     # Path absolut ke file yang akan dimodifikasi
    "edits": [            # Array operasi edit
        {
            "old_string": str,          # Teks yang akan diganti
            "new_string": str,          # Teks pengganti
            "replace_all": bool | None  # Ganti semua kemunculan
        }
    ]
}
Output:
{
    "message": str,       # Pesan sukses
    "edits_applied": int, # Total jumlah edit yang diterapkan
    "file_path": str      # Path file yang diedit
}

Read

Nama tool: Read Input:
{
    "file_path": str,       # Path absolut ke file yang akan dibaca
    "offset": int | None,   # Nomor baris untuk mulai membaca
    "limit": int | None     # Jumlah baris yang akan dibaca
}
Output (File teks):
{
    "content": str,         # Konten file dengan nomor baris
    "total_lines": int,     # Total jumlah baris dalam file
    "lines_returned": int   # Baris yang benar-benar dikembalikan
}
Output (Gambar):
{
    "image": str,       # Data gambar yang dikodekan base64
    "mime_type": str,   # Tipe MIME gambar
    "file_size": int    # Ukuran file dalam byte
}

Write

Nama tool: Write Input:
{
    "file_path": str,  # Path absolut ke file yang akan ditulis
    "content": str     # Konten yang akan ditulis ke file
}
Output:
{
    "message": str,        # Pesan sukses
    "bytes_written": int,  # Jumlah byte yang ditulis
    "file_path": str       # Path file yang ditulis
}

Glob

Nama tool: Glob Input:
{
    "pattern": str,       # Pola glob untuk mencocokkan file
    "path": str | None    # Direktori untuk dicari (default ke cwd)
}
Output:
{
    "matches": list[str],  # Array path file yang cocok
    "count": int,          # Jumlah kecocokan yang ditemukan
    "search_path": str     # Direktori pencarian yang digunakan
}

Grep

Nama tool: Grep Input:
{
    "pattern": str,                    # Pola regular expression
    "path": str | None,                # File atau direktori untuk dicari
    "glob": str | None,                # Pola glob untuk memfilter file
    "type": str | None,                # Jenis file untuk dicari
    "output_mode": str | None,         # "content", "files_with_matches", atau "count"
    "-i": bool | None,                 # Pencarian tidak peka huruf besar-kecil
    "-n": bool | None,                 # Tampilkan nomor baris
    "-B": int | None,                  # Baris untuk ditampilkan sebelum setiap kecocokan
    "-A": int | None,                  # Baris untuk ditampilkan setelah setiap kecocokan
    "-C": int | None,                  # Baris untuk ditampilkan sebelum dan sesudah
    "head_limit": int | None,          # Batasi output ke N baris/entri pertama
    "multiline": bool | None           # Aktifkan mode multiline
}
Output (mode content):
{
    "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],  # File yang berisi kecocokan
    "count": int         # Jumlah file dengan kecocokan
}

NotebookEdit

Nama tool: NotebookEdit Input:
{
    "notebook_path": str,                     # Path absolut ke notebook Jupyter
    "cell_id": str | None,                    # ID sel yang akan diedit
    "new_source": str,                        # Sumber baru untuk sel
    "cell_type": "code" | "markdown" | None,  # Jenis sel
    "edit_mode": "replace" | "insert" | "delete" | None  # Jenis operasi edit
}
Output:
{
    "message": str, # Pesan sukses
    "edit_type": "replaced" | "inserted" | "deleted",  # Jenis edit yang dilakukan
    "cell_id": str | None,                       # ID sel yang terpengaruh
    "total_cells": int                           # Total sel dalam notebook setelah edit
}

WebFetch

Nama tool: WebFetch Input:
{
    "url": str,     # URL untuk mengambil konten
    "prompt": str   # Prompt untuk dijalankan pada konten yang diambil
}
Output:
{
    "response": str,           # Respons model AI terhadap prompt
    "url": str,                # URL yang diambil
    "final_url": str | None,   # URL akhir setelah redirect
    "status_code": int | None  # Kode status HTTP
}

WebSearch

Nama tool: WebSearch Input:
{
    "query": str,                        # Query pencarian yang akan digunakan
    "allowed_domains": list[str] | None, # Hanya sertakan hasil dari domain ini
    "blocked_domains": list[str] | None  # Jangan pernah sertakan hasil dari domain ini
}
Output:
{
    "results": [
        {
            "title": str,
            "url": str,
            "snippet": str,
            "metadata": dict | None
        }
    ],
    "total_results": int,
    "query": str
}

TodoWrite

Nama tool: TodoWrite Input:
{
    "todos": [
        {
            "content": str, # Deskripsi tugas
            "status": "pending" | "in_progress" | "completed",  # Status tugas
            "activeForm": str                            # Bentuk aktif dari deskripsi
        }
    ]
}
Output:
{
    "message": str,  # Pesan sukses
    "stats": {
        "total": int,
        "pending": int,
        "in_progress": int,
        "completed": int
    }
}

BashOutput

Nama tool: BashOutput Input:
{
    "bash_id": str,       # ID shell latar belakang
    "filter": str | None  # Regex opsional untuk memfilter baris output
}
Output:
{
    "output": str, # Output baru sejak pemeriksaan terakhir
    "status": "running" | "completed" | "failed",       # Status shell saat ini
    "exitCode": int | None # Kode keluar ketika selesai
}

KillBash

Nama tool: KillBash Input:
{
    "shell_id": str  # ID shell latar belakang yang akan dibunuh
}
Output:
{
    "message": str,  # Pesan sukses
    "shell_id": str  # ID shell yang dibunuh
}

ExitPlan Mode

Nama tool: ExitPlanMode Input:
{
    "plan": str  # Rencana yang akan dijalankan oleh pengguna untuk persetujuan
}
Output:
{
    "message": str,          # Pesan konfirmasi
    "approved": bool | None  # Apakah pengguna menyetujui rencana
}

ListMcpResources

Nama tool: ListMcpResources Input:
{
    "server": str | None  # Nama server opsional untuk memfilter sumber daya
}
Output:
{
    "resources": [
        {
            "uri": str,
            "name": str,
            "description": str | None,
            "mimeType": str | None,
            "server": str
        }
    ],
    "total": int
}

ReadMcpResource

Nama tool: ReadMcpResource Input:
{
    "server": str,  # Nama server MCP
    "uri": str      # URI sumber daya yang akan dibaca
}
Output:
{
    "contents": [
        {
            "uri": str,
            "mimeType": str | None,
            "text": str | None,
            "blob": str | None
        }
    ],
    "server": str
}

Fitur Lanjutan dengan ClaudeSDKClient

Membangun Antarmuka Percakapan Berkelanjutan

from claude_agent_sdk import ClaudeSDKClient, ClaudeCodeOptions, AssistantMessage, TextBlock
import asyncio

class ConversationSession:
    """Mempertahankan sesi percakapan tunggal dengan Claude."""
    
    def __init__(self, options: ClaudeCodeOptions = None):
        self.client = ClaudeSDKClient(options)
        self.turn_count = 0
    
    async def start(self):
        await self.client.connect()
        print("Memulai sesi percakapan. Claude akan mengingat konteks.")
        print("Perintah: 'exit' untuk keluar, 'interrupt' untuk menghentikan tugas saat ini, 'new' untuk sesi baru")
        
        while True:
            user_input = input(f"\n[Giliran {self.turn_count + 1}] Anda: ")
            
            if user_input.lower() == 'exit':
                break
            elif user_input.lower() == 'interrupt':
                await self.client.interrupt()
                print("Tugas diinterupsi!")
                continue
            elif user_input.lower() == 'new':
                # Putuskan koneksi dan sambung kembali untuk sesi segar
                await self.client.disconnect()
                await self.client.connect()
                self.turn_count = 0
                print("Memulai sesi percakapan baru (konteks sebelumnya dihapus)")
                continue
            
            # Kirim pesan - Claude mengingat semua pesan sebelumnya dalam sesi ini
            await self.client.query(user_input)
            self.turn_count += 1
            
            # Proses respons
            print(f"[Giliran {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()  # Baris baru setelah respons
        
        await self.client.disconnect()
        print(f"Percakapan berakhir setelah {self.turn_count} giliran.")

async def main():
    options = ClaudeCodeOptions(
        allowed_tools=["Read", "Write", "Bash"],
        permission_mode="acceptEdits"
    )
    session = ConversationSession(options)
    await session.start()

# Contoh percakapan:
# Giliran 1 - Anda: "Buat file bernama hello.py"
# Giliran 1 - Claude: "Saya akan membuat file hello.py untuk Anda..."
# Giliran 2 - Anda: "Apa isi file itu?"  
# Giliran 2 - Claude: "File hello.py yang baru saya buat berisi..." (mengingat!)
# Giliran 3 - Anda: "Tambahkan fungsi main ke dalamnya"
# Giliran 3 - Claude: "Saya akan menambahkan fungsi main ke hello.py..." (tahu file mana!)

asyncio.run(main())

Menggunakan Hook untuk Modifikasi Perilaku

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 semua penggunaan tool sebelum eksekusi."""
    tool_name = input_data.get('tool_name', 'unknown')
    print(f"[PRE-TOOL] Akan menggunakan: {tool_name}")

    # Anda dapat memodifikasi atau memblokir eksekusi tool di sini
    if tool_name == "Bash" and "rm -rf" in str(input_data.get('tool_input', {})):
        return {
            'hookSpecificOutput': {
                'hookEventName': 'PreToolUse',
                'permissionDecision': 'deny',
                'permissionDecisionReason': 'Perintah berbahaya diblokir'
            }
        }
    return {}

async def post_tool_logger(
    input_data: dict[str, Any],
    tool_use_id: str | None,
    context: HookContext
) -> dict[str, Any]:
    """Log hasil setelah eksekusi tool."""
    tool_name = input_data.get('tool_name', 'unknown')
    print(f"[POST-TOOL] Selesai: {tool_name}")
    return {}

async def user_prompt_modifier(
    input_data: dict[str, Any],
    tool_use_id: str | None,
    context: HookContext
) -> dict[str, Any]:
    """Tambahkan konteks ke prompt pengguna."""
    original_prompt = input_data.get('prompt', '')

    # Tambahkan timestamp ke semua prompt
    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("Daftar file di direktori saat ini")
        
        async for message in client.receive_response():
            # Hook akan secara otomatis mencatat penggunaan tool
            pass

asyncio.run(main())

Pemantauan Kemajuan Real-time

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(
            "Buat 5 file Python dengan algoritma sorting yang berbeda"
        )
        
        # Pantau kemajuan secara 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"🔨 Membuat: {file_path}")
                    elif isinstance(block, ToolResultBlock):
                        print(f"✅ Eksekusi tool selesai")
                    elif isinstance(block, TextBlock):
                        print(f"💭 Claude berkata: {block.text[:100]}...")
            
            # Periksa apakah kita telah menerima hasil akhir
            if hasattr(message, 'subtype') and message.subtype in ['success', 'error']:
                print(f"\n🎯 Tugas selesai!")
                break

asyncio.run(monitor_progress())

Contoh Penggunaan

Operasi file dasar (menggunakan query)

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="Buat struktur proyek Python dengan setup.py",
        options=options
    ):
        if isinstance(message, AssistantMessage):
            for block in message.content:
                if isinstance(block, ToolUseBlock):
                    print(f"Menggunakan tool: {block.name}")

asyncio.run(create_project())

Penanganan error

from claude_agent_sdk import (
    query,
    CLINotFoundError,
    ProcessError,
    CLIJSONDecodeError
)

try:
    async for message in query(prompt="Halo"):
        print(message)
except CLINotFoundError:
    print("Silakan instal Claude Code: npm install -g @anthropic-ai/claude-code")
except ProcessError as e:
    print(f"Proses gagal dengan kode keluar: {e.exit_code}")
except CLIJSONDecodeError as e:
    print(f"Gagal mem-parse respons: {e}")

Mode streaming dengan klien

from claude_agent_sdk import ClaudeSDKClient
import asyncio

async def interactive_session():
    async with ClaudeSDKClient() as client:
        # Kirim pesan awal
        await client.query("Bagaimana cuacanya?")
        
        # Proses respons
        async for msg in client.receive_response():
            print(msg)
        
        # Kirim tindak lanjut
        await client.query("Ceritakan lebih banyak tentang itu")
        
        # Proses respons tindak lanjut
        async for msg in client.receive_response():
            print(msg)

asyncio.run(interactive_session())

Menggunakan tool kustom dengan ClaudeSDKClient

from claude_agent_sdk import (
    ClaudeSDKClient,
    ClaudeAgentOptions,
    tool,
    create_sdk_mcp_server,
    AssistantMessage,
    TextBlock
)
import asyncio
from typing import Any

# Definisikan tool kustom dengan dekorator @tool
@tool("calculate", "Lakukan kalkulasi matematika", {"expression": str})
async def calculate(args: dict[str, Any]) -> dict[str, Any]:
    try:
        result = eval(args["expression"], {"__builtins__": {}})
        return {
            "content": [{
                "type": "text",
                "text": f"Hasil: {result}"
            }]
        }
    except Exception as e:
        return {
            "content": [{
                "type": "text",
                "text": f"Error: {str(e)}"
            }],
            "is_error": True
        }

@tool("get_time", "Dapatkan waktu saat ini", {})
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"Waktu saat ini: {current_time}"
        }]
    }

async def main():
    # Buat server SDK MCP dengan tool kustom
    my_server = create_sdk_mcp_server(
        name="utilities",
        version="1.0.0",
        tools=[calculate, get_time]
    )

    # Konfigurasi opsi dengan server
    options = ClaudeAgentOptions(
        mcp_servers={"utils": my_server},
        allowed_tools=[
            "mcp__utils__calculate",
            "mcp__utils__get_time"
        ]
    )
    
    # Gunakan ClaudeSDKClient untuk penggunaan tool interaktif
    async with ClaudeSDKClient(options=options) as client:
        await client.query("Berapa 123 * 456?")
        
        # Proses respons kalkulasi
        async for message in client.receive_response():
            if isinstance(message, AssistantMessage):
                for block in message.content:
                    if isinstance(block, TextBlock):
                        print(f"Kalkulasi: {block.text}")
        
        # Tindak lanjut dengan query waktu
        await client.query("Jam berapa sekarang?")
        
        async for message in client.receive_response():
            if isinstance(message, AssistantMessage):
                for block in message.content:
                    if isinstance(block, TextBlock):
                        print(f"Waktu: {block.text}")

asyncio.run(main())

Lihat juga