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 tunggalPertukaran ganda dalam konteks yang sama
KoneksiDikelola secara otomatisKontrol 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 PenggunaanTugas sekali jalanPercakapan berkelanjutan

Kapan Menggunakan query() (Sesi Baru Setiap Kali)

Terbaik untuk:
  • Pertanyaan sekali jalan di mana Anda tidak memerlukan riwayat percakapan
  • Tugas independen yang tidak memerlukan konteks dari pertukaran sebelumnya
  • Skrip otomasi sederhana
  • Ketika Anda menginginkan awal yang segar setiap kali

Kapan Menggunakan ClaudeSDKClient (Percakapan Berkelanjutan)

Terbaik untuk:
  • Melanjutkan percakapan - Ketika Anda memerlukan Claude untuk mengingat konteks
  • Pertanyaan lanjutan - Membangun berdasarkan respons sebelumnya
  • Aplikasi interaktif - Antarmuka obrolan, REPL
  • Logika berbasis respons - Ketika tindakan berikutnya 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 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

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

Pengembalian

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="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

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

Opsi Input Schema

  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"]
    }
    

Pengembalian

Fungsi dekorator yang membungkus implementasi alat dan mengembalikan instance SdkMcpTool.

Contoh

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

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

Pengembalian

Mengembalikan objek McpSdkServerConfig yang dapat diteruskan ke ClaudeAgentOptions.mcp_servers.

Contoh

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"]
)

Kelas

ClaudeSDKClient

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

Fitur Utama

  • Kontinuitas Sesi: Mempertahankan konteks percakapan di seluruh panggilan query() ganda
  • Percakapan 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 Berbasis Respons: Dapat bereaksi terhadap respons dan mengirim tindak lanjut
  • Alat Kustom & Hooks: Mendukung alat kustom (dibuat dengan dekorator @tool) dan hooks
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 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 bekerja dalam mode streaming)
disconnect()Putuskan koneksi dari Claude

Dukungan Context Manager

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 melakukan iterasi atas pesan, hindari menggunakan break untuk keluar lebih awal karena ini dapat menyebabkan masalah pembersihan asyncio. Sebaliknya, 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:
        # 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())

Contoh - Input streaming dengan ClaudeSDKClient

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())

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:
        # 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())

Contoh - Kontrol izin tingkat lanjut

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())

Tipe

SdkMcpTool

Definisi 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]]]
PropertiTipeDeskripsi
namestrPengidentifikasi unik untuk alat
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 alat

ClaudeAgentOptions

Dataclass 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
    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
PropertiTipeDefaultDeskripsi
allowed_toolslist[str][]Daftar nama alat yang diizinkan
system_promptstr | SystemPromptPreset | NoneNoneKonfigurasi prompt sistem. Teruskan string untuk prompt kustom, atau gunakan {"type": "preset", "preset": "claude_code"} untuk prompt sistem Claude Code. Tambahkan "append" untuk memperluas preset
mcp_serversdict[str, McpServerConfig] | str | Path{}Konfigurasi server MCP atau jalur ke file konfigurasi
permission_modePermissionMode | NoneNoneMode izin untuk penggunaan alat
continue_conversationboolFalseLanjutkan percakapan terbaru
resumestr | NoneNoneID sesi untuk dilanjutkan
max_turnsint | NoneNoneJumlah maksimal putaran percakapan
disallowed_toolslist[str][]Daftar nama alat yang tidak diizinkan
modelstr | NoneNoneModel Claude yang digunakan
permission_prompt_tool_namestr | NoneNoneNama alat MCP untuk prompt izin
cwdstr | Path | NoneNoneDirektori kerja saat ini
settingsstr | NoneNoneJalur ke file pengaturan
add_dirslist[str | Path][]Direktori tambahan yang dapat diakses Claude
envdict[str, str]{}Variabel lingkungan
extra_argsdict[str, str | None]{}Argumen CLI tambahan untuk diteruskan langsung ke CLI
max_buffer_sizeint | NoneNoneJumlah byte maksimal saat membuffer stdout CLI
debug_stderrAnysys.stderrTidak digunakan lagi - Objek seperti file untuk output debug. Gunakan callback stderr sebagai gantinya
stderrCallable[[str], None] | NoneNoneFungsi callback untuk output stderr dari CLI
can_use_toolCanUseTool | NoneNoneFungsi callback izin alat
hooksdict[HookEvent, list[HookMatcher]] | NoneNoneKonfigurasi hook untuk mengintersepsi acara
userstr | NoneNonePengidentifikasi pengguna
include_partial_messagesboolFalseSertakan acara streaming pesan parsial
fork_sessionboolFalseSaat melanjutkan dengan resume, fork ke ID sesi baru alih-alih melanjutkan sesi asli
agentsdict[str, AgentDefinition] | NoneNoneSubagen yang didefinisikan secara terprogram
pluginslist[SdkPluginConfig][]Muat plugin kustom dari jalur lokal. Lihat Plugins untuk detail
setting_sourceslist[SettingSource] | NoneNone (tidak ada pengaturan)Kontrol sumber pengaturan sistem file mana yang akan dimuat. Jika dihilangkan, tidak ada pengaturan yang dimuat. Catatan: Harus menyertakan "project" untuk memuat file CLAUDE.md

SystemPromptPreset

Konfigurasi untuk menggunakan prompt sistem preset Claude Code dengan penambahan opsional.
class SystemPromptPreset(TypedDict):
    type: Literal["preset"]
    preset: Literal["claude_code"]
    append: NotRequired[str]
BidangDiperlukanDeskripsi
typeYaHarus "preset" untuk menggunakan prompt sistem preset
presetYaHarus "claude_code" untuk menggunakan prompt sistem Claude Code
appendTidakInstruksi tambahan untuk ditambahkan ke prompt sistem preset

SettingSource

Mengontrol sumber konfigurasi berbasis sistem file mana yang dimuat pengaturan SDK.
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 sistem file apa pun. Ini memberikan isolasi untuk aplikasi SDK.

Mengapa menggunakan setting_sources?

Muat semua pengaturan sistem file (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)

Preseden pengaturan

Ketika beberapa sumber dimuat, pengaturan digabungkan dengan preseden ini (tertinggi ke terendah):
  1. Pengaturan lokal (.claude/settings.local.json)
  2. Pengaturan proyek (.claude/settings.json)
  3. Pengaturan pengguna (~/.claude/settings.json)
Opsi terprogram (seperti agents, allowed_tools) selalu menggantikan pengaturan sistem file.

AgentDefinition

Konfigurasi 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
BidangDiperlukanDeskripsi
descriptionYaDeskripsi bahasa alami tentang kapan menggunakan agen ini
toolsTidakArray nama alat yang diizinkan. Jika dihilangkan, mewarisi semua alat
promptYaPrompt sistem agen
modelTidakPenggantian model untuk agen ini. Jika dihilangkan, menggunakan model utama

PermissionMode

Mode 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)
]

McpSdkServerConfig

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

McpServerConfig

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

McpStdioServerConfig

class McpStdioServerConfig(TypedDict):
    type: NotRequired[Literal["stdio"]]  # Optional for backwards compatibility
    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]]

SdkPluginConfig

Konfigurasi untuk memuat plugin di SDK.
class SdkPluginConfig(TypedDict):
    type: Literal["local"]
    path: str
BidangTipeDeskripsi
typeLiteral["local"]Harus "local" (hanya plugin lokal yang didukung saat ini)
pathstrJalur 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.

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 pemikiran (untuk model dengan kemampuan pemikiran).
@dataclass
class ThinkingBlock:
    thinking: str
    signature: str

ToolUseBlock

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

ToolResultBlock

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

Tipe Kesalahan

ClaudeSDKError

Kelas pengecualian dasar untuk semua kesalahan SDK.
class ClaudeSDKError(Exception):
    """Base error for Claude SDK."""

CLINotFoundError

Dimunculkan ketika Claude Code CLI tidak diinstal 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
        """

CLIConnectionError

Dimunculkan ketika koneksi ke Claude Code gagal.
class CLIConnectionError(ClaudeSDKError):
    """Failed to connect to 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 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_error

Tipe Hook

HookEvent

Tipe acara hook yang didukung. Perhatikan bahwa karena keterbatasan penyiapan, 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
]

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: Pengidentifikasi penggunaan alat opsional (untuk hook terkait alat)
  • context: Konteks hook dengan informasi tambahan
Mengembalikan kamus 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  # Future: abort signal support

HookMatcher

Konfigurasi untuk mencocokkan hook ke acara 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

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]:
    """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]),
            HookMatcher(hooks=[log_tool_use])  # Applies to all tools
        ],
        'PostToolUse': [
            HookMatcher(hooks=[log_tool_use])
        ]
    }
)

async for message in query(
    prompt="Analyze this codebase",
    options=options
):
    print(message)

Tipe Input/Output Alat

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.

Task

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
}

Bash

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
}

Edit

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
}

Read

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
}

Write

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
}

Glob

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
}

Grep

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
}

NotebookEdit

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
}

WebFetch

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
}

WebSearch

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
}

TodoWrite

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
    }
}

BashOutput

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
}

KillBash

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
}

ExitPlanMode

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
}

ListMcpResources

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
}

ReadMcpResource

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
}

Fitur Lanjutan dengan ClaudeSDKClient

Membangun Antarmuka Percakapan Berkelanjutan

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())

Menggunakan Hooks 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 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())

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(
            "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())

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="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())

Penanganan kesalahan

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}")

Mode streaming dengan klien

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())

Menggunakan alat kustom dengan ClaudeSDKClient

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())

Lihat juga