Установка

pip install claude-agent-sdk

Выбор между query() и ClaudeSDKClient

Python SDK предоставляет два способа взаимодействия с Claude Code:

Краткое сравнение

Функцияquery()ClaudeSDKClient
СессияСоздает новую сессию каждый разПереиспользует ту же сессию
РазговорОдиночный обменМножественные обмены в том же контексте
СоединениеУправляется автоматическиРучное управление
Потоковый ввод✅ Поддерживается✅ Поддерживается
Прерывания❌ Не поддерживается✅ Поддерживается
Хуки❌ Не поддерживается✅ Поддерживается
Пользовательские инструменты❌ Не поддерживается✅ Поддерживается
Продолжение чата❌ Новая сессия каждый раз✅ Поддерживает разговор
Случай использованияРазовые задачиНепрерывные разговоры

Когда использовать query() (Новая сессия каждый раз)

Лучше всего для:
  • Разовых вопросов, где не нужна история разговора
  • Независимых задач, которые не требуют контекста из предыдущих обменов
  • Простых скриптов автоматизации
  • Когда вы хотите свежий старт каждый раз

Когда использовать ClaudeSDKClient (Непрерывный разговор)

Лучше всего для:
  • Продолжения разговоров - Когда нужно, чтобы Claude запомнил контекст
  • Дополнительных вопросов - Построение на основе предыдущих ответов
  • Интерактивных приложений - Интерфейсы чата, REPL
  • Логики, управляемой ответами - Когда следующее действие зависит от ответа Claude
  • Управления сессией - Явное управление жизненным циклом разговора

Функции

query()

Создает новую сессию для каждого взаимодействия с Claude Code. Возвращает асинхронный итератор, который выдает сообщения по мере их поступления. Каждый вызов query() начинается заново без памяти о предыдущих взаимодействиях.
async def query(
    *,
    prompt: str | AsyncIterable[dict[str, Any]],
    options: ClaudeAgentOptions | None = None
) -> AsyncIterator[Message]

Параметры

ПараметрТипОписание
promptstr | AsyncIterable[dict]Входной промпт как строка или асинхронный итерируемый объект для потокового режима
optionsClaudeAgentOptions | NoneНеобязательный объект конфигурации (по умолчанию ClaudeAgentOptions() если None)

Возвращает

Возвращает AsyncIterator[Message], который выдает сообщения из разговора.

Пример - С опциями


import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions

async def main():
    options = ClaudeAgentOptions(
        system_prompt="Вы эксперт-разработчик Python",
        permission_mode='acceptEdits',
        cwd="/home/user/project"
    )

    async for message in query(
        prompt="Создайте веб-сервер Python",
        options=options
    ):
        print(message)


asyncio.run(main())

tool()

Декоратор для определения MCP инструментов с типобезопасностью.
def tool(
    name: str,
    description: str,
    input_schema: type | dict[str, Any]
) -> Callable[[Callable[[Any], Awaitable[dict[str, Any]]]], SdkMcpTool[Any]]

Параметры

ПараметрТипОписание
namestrУникальный идентификатор для инструмента
descriptionstrЧеловекочитаемое описание того, что делает инструмент
input_schematype | dict[str, Any]Схема, определяющая входные параметры инструмента (см. ниже)

Опции схемы ввода

  1. Простое сопоставление типов (рекомендуется):
    {"text": str, "count": int, "enabled": bool}
    
  2. Формат JSON Schema (для сложной валидации):
    {
        "type": "object",
        "properties": {
            "text": {"type": "string"},
            "count": {"type": "integer", "minimum": 0}
        },
        "required": ["text"]
    }
    

Возвращает

Функцию-декоратор, которая оборачивает реализацию инструмента и возвращает экземпляр SdkMcpTool.

Пример

from claude_agent_sdk import tool
from typing import Any

@tool("greet", "Поприветствовать пользователя", {"name": str})
async def greet(args: dict[str, Any]) -> dict[str, Any]:
    return {
        "content": [{
            "type": "text",
            "text": f"Привет, {args['name']}!"
        }]
    }

create_sdk_mcp_server()

Создать внутрипроцессный MCP сервер, который работает внутри вашего Python приложения.
def create_sdk_mcp_server(
    name: str,
    version: str = "1.0.0",
    tools: list[SdkMcpTool[Any]] | None = None
) -> McpSdkServerConfig

Параметры

ПараметрТипПо умолчаниюОписание
namestr-Уникальный идентификатор для сервера
versionstr"1.0.0"Строка версии сервера
toolslist[SdkMcpTool[Any]] | NoneNoneСписок функций инструментов, созданных с декоратором @tool

Возвращает

Возвращает объект McpSdkServerConfig, который можно передать в ClaudeAgentOptions.mcp_servers.

Пример

from claude_agent_sdk import tool, create_sdk_mcp_server

@tool("add", "Сложить два числа", {"a": float, "b": float})
async def add(args):
    return {
        "content": [{
            "type": "text",
            "text": f"Сумма: {args['a'] + args['b']}"
        }]
    }

@tool("multiply", "Умножить два числа", {"a": float, "b": float})
async def multiply(args):
    return {
        "content": [{
            "type": "text",
            "text": f"Произведение: {args['a'] * args['b']}"
        }]
    }

calculator = create_sdk_mcp_server(
    name="calculator",
    version="2.0.0",
    tools=[add, multiply]  # Передать декорированные функции
)

# Использовать с Claude
options = ClaudeAgentOptions(
    mcp_servers={"calc": calculator},
    allowed_tools=["mcp__calc__add", "mcp__calc__multiply"]
)

Классы

ClaudeSDKClient

Поддерживает сессию разговора через множественные обмены. Это Python эквивалент того, как функция query() TypeScript SDK работает внутренне - она создает объект клиента, который может продолжать разговоры.

Ключевые функции

  • Непрерывность сессии: Поддерживает контекст разговора через множественные вызовы query()
  • Тот же разговор: Claude запоминает предыдущие сообщения в сессии
  • Поддержка прерываний: Может остановить Claude в середине выполнения
  • Явный жизненный цикл: Вы контролируете, когда сессия начинается и заканчивается
  • Поток, управляемый ответами: Может реагировать на ответы и отправлять продолжения
  • Пользовательские инструменты и хуки: Поддерживает пользовательские инструменты (созданные с декоратором @tool) и хуки
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

Методы

МетодОписание
__init__(options)Инициализировать клиент с необязательной конфигурацией
connect(prompt)Подключиться к Claude с необязательным начальным промптом или потоком сообщений
query(prompt, session_id)Отправить новый запрос в потоковом режиме
receive_messages()Получить все сообщения от Claude как асинхронный итератор
receive_response()Получить сообщения до и включая ResultMessage
interrupt()Отправить сигнал прерывания (работает только в потоковом режиме)
disconnect()Отключиться от Claude

Поддержка менеджера контекста

Клиент может использоваться как асинхронный менеджер контекста для автоматического управления соединением:
async with ClaudeSDKClient() as client:
    await client.query("Привет Claude")
    async for message in client.receive_response():
        print(message)
Важно: При итерации по сообщениям избегайте использования break для раннего выхода, так как это может вызвать проблемы с очисткой asyncio. Вместо этого позвольте итерации завершиться естественно или используйте флаги для отслеживания того, когда вы нашли то, что нужно.

Пример - Продолжение разговора

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

async def main():
    async with ClaudeSDKClient() as client:
        # Первый вопрос
        await client.query("Какая столица Франции?")
        
        # Обработать ответ
        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}")
        
        # Дополнительный вопрос - Claude помнит предыдущий контекст
        await client.query("Какое население этого города?")
        
        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}")
        
        # Еще один дополнительный вопрос - все еще в том же разговоре
        await client.query("Какие там есть знаменитые достопримечательности?")
        
        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())

Пример - Потоковый ввод с ClaudeSDKClient

import asyncio
from claude_agent_sdk import ClaudeSDKClient

async def message_stream():
    """Генерировать сообщения динамически."""
    yield {"type": "text", "text": "Проанализируйте следующие данные:"}
    await asyncio.sleep(0.5)
    yield {"type": "text", "text": "Температура: 25°C"}
    await asyncio.sleep(0.5)
    yield {"type": "text", "text": "Влажность: 60%"}
    await asyncio.sleep(0.5)
    yield {"type": "text", "text": "Какие закономерности вы видите?"}

async def main():
    async with ClaudeSDKClient() as client:
        # Потоковый ввод в Claude
        await client.query(message_stream())
        
        # Обработать ответ
        async for message in client.receive_response():
            print(message)
        
        # Продолжение в той же сессии
        await client.query("Должны ли мы беспокоиться об этих показаниях?")
        
        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:
        # Начать долго выполняющуюся задачу
        await client.query("Считайте от 1 до 100 медленно")
        
        # Дать ей поработать немного
        await asyncio.sleep(2)
        
        # Прервать задачу
        await client.interrupt()
        print("Задача прервана!")
        
        # Отправить новую команду
        await client.query("Просто скажите привет вместо этого")
        
        async for message in client.receive_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
):
    """Пользовательская логика для разрешений инструментов."""

    # Блокировать записи в системные директории
    if tool_name == "Write" and input_data.get("file_path", "").startswith("/system/"):
        return {
            "behavior": "deny",
            "message": "Запись в системную директорию не разрешена",
            "interrupt": True
        }

    # Перенаправить чувствительные файловые операции
    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}
        }

    # Разрешить все остальное
    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("Обновите системный конфигурационный файл")
        
        async for message in client.receive_response():
            # Будет использовать путь песочницы вместо этого
            print(message)

asyncio.run(main())

Типы

SdkMcpTool

Определение для SDK MCP инструмента, созданного с декоратором @tool.
@dataclass
class SdkMcpTool(Generic[T]):
    name: str
    description: str
    input_schema: type[T] | dict[str, Any]
    handler: Callable[[T], Awaitable[dict[str, Any]]]
СвойствоТипОписание
namestrУникальный идентификатор для инструмента
descriptionstrЧеловекочитаемое описание
input_schematype[T] | dict[str, Any]Схема для валидации ввода
handlerCallable[[T], Awaitable[dict[str, Any]]]Асинхронная функция, которая обрабатывает выполнение инструмента

ClaudeAgentOptions

Dataclass конфигурации для запросов 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)
СвойствоТипПо умолчаниюОписание
allowed_toolslist[str][]Список разрешенных имен инструментов
max_thinking_tokensint8000Максимальное количество токенов для процесса размышления
system_promptstr | NoneNoneКонфигурация системного промпта. Передайте строку для пользовательского промпта или используйте предустановленный формат для системного промпта Claude Code
mcp_serversdict[str, McpServerConfig] | str | Path{}Конфигурации MCP серверов или путь к конфигурационному файлу
permission_modePermissionMode | NoneNoneРежим разрешений для использования инструментов
continue_conversationboolFalseПродолжить самый последний разговор
resumestr | NoneNoneID сессии для возобновления
fork_sessionboolFalseПри возобновлении с resume, разветвить на новый ID сессии вместо продолжения оригинальной сессии
max_turnsint | NoneNoneМаксимальное количество ходов разговора
disallowed_toolslist[str][]Список запрещенных имен инструментов
modelstr | NoneNoneМодель Claude для использования
permission_prompt_tool_namestr | NoneNoneИмя MCP инструмента для промптов разрешений
cwdstr | Path | NoneNoneТекущая рабочая директория
settingsstr | NoneNoneПуть к файлу настроек
add_dirslist[str | Path][]Дополнительные директории, к которым Claude может получить доступ
extra_argsdict[str, str | None]{}Дополнительные аргументы CLI для передачи напрямую в CLI
can_use_toolCanUseTool | NoneNoneФункция обратного вызова разрешений инструмента
hooksdict[HookEvent, list[HookMatcher]] | NoneNoneКонф��гурации хуков для перехвата событий
agentsdict[str, AgentDefinition] | NoneNoneПрограммно определенные субагенты
setting_sourceslist[SettingSource] | NoneNone (без настроек)Контролировать, какие настройки файловой системы загружать. Когда опущено, настройки не загружаются

SettingSource

Контролирует, какие источники конфигурации на основе файловой системы SDK загружает настройки из.
SettingSource = Literal["user", "project", "local"]
ЗначениеОписаниеМестоположение
"user"Глобальные пользовательские настройки~/.claude/settings.json
"project"Общие настройки проекта (под контролем версий).claude/settings.json
"local"Локальные настройки проекта (игнорируемые git).claude/settings.local.json

Поведение по умолчанию

Когда setting_sources опущено или None, SDK не загружает никаких настроек файловой системы. Это обеспечивает изоляцию для SDK приложений.

Зачем использовать setting_sources?

Загрузить все настройки файловой системы (устаревшее поведение):
# Загрузить все настройки как делал SDK v0.0.x
from claude_agent_sdk import query, ClaudeAgentOptions

async for message in query(
    prompt="Проанализируйте этот код",
    options=ClaudeAgentOptions(
        setting_sources=["user", "project", "local"]  # Загрузить все настройки
    )
):
    print(message)
Загрузить только определенные источники настроек:
# Загрузить только настройки проекта, игнорировать пользовательские и локальные
async for message in query(
    prompt="Запустить CI проверки",
    options=ClaudeAgentOptions(
        setting_sources=["project"]  # Только .claude/settings.json
    )
):
    print(message)
Среды тестирования и CI:
# Обеспечить согласованное поведение в CI, исключив локальные настройки
async for message in query(
    prompt="Запустить тесты",
    options=ClaudeAgentOptions(
        setting_sources=["project"],  # Только общие настройки команды
        permission_mode="bypassPermissions"
    )
):
    print(message)
Приложения только SDK:
# Определить все программно (поведение по умолчанию)
# Никаких зависимостей файловой системы - setting_sources по умолчанию None
async for message in query(
    prompt="Просмотреть этот PR",
    options=ClaudeAgentOptions(
        # setting_sources=None по умолчанию, не нужно указывать
        agents={ /* ... */ },
        mcp_servers={ /* ... */ },
        allowed_tools=["Read", "Grep", "Glob"]
    )
):
    print(message)

Приоритет настроек

Когда загружается несколько источников, настройки объединяются с этим приоритетом (от высшего к низшему):
  1. Локальные настройки (.claude/settings.local.json)
  2. Настройки проекта (.claude/settings.json)
  3. Пользовательские настройки (~/.claude/settings.json)
Программные опции (как agents, allowed_tools) всегда переопределяют настройки файловой системы.

AgentDefinition

Конфигурация для субагента, определенного программно.
@dataclass
class AgentDefinition:
    description: str
    prompt: str
    tools: list[str] | None = None
    model: Literal["sonnet", "opus", "haiku", "inherit"] | None = None
ПолеОбязательноОписание
descriptionДаОписание на естественном языке о том, когда использовать этого агента
toolsНетМассив разрешенных имен инструментов. Если опущено, наследует все инструменты
promptДаСистемный промпт агента
modelНетПереопределение модели для этого агента. Если опущено, использует основную модель

PermissionMode

Режимы разрешений для контроля выполнения инструментов.
PermissionMode = Literal[
    "default",           # Стандартное поведение разрешений
    "acceptEdits",       # Автоматически принимать редактирования файлов
    "plan",              # Режим планирования - без выполнения
    "bypassPermissions"  # Обойти все проверки разрешений (используйте с осторожностью)
]

McpSdkServerConfig

Конфигурация для SDK MCP серверов, созданных с create_sdk_mcp_server().
class McpSdkServerConfig(TypedDict):
    type: Literal["sdk"]
    name: str
    instance: Any  # Экземпляр MCP сервера

McpServerConfig

Тип объединения для конфигураций MCP серверов.
McpServerConfig = McpStdioServerConfig | McpSSEServerConfig | McpHttpServerConfig | McpSdkServerConfig

McpStdioServerConfig

class McpStdioServerConfig(TypedDict):
    type: NotRequired[Literal["stdio"]]  # Необязательно для обратной совместимости
    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]]

Типы сообщений

Message

Тип объединения всех возможных сообщений.
Message = UserMessage | AssistantMessage | SystemMessage | ResultMessage

UserMessage

Сообщение пользовательского ввода.
@dataclass
class UserMessage:
    content: str | list[ContentBlock]

AssistantMessage

Сообщение ответа ассистента с блоками содержимого.
@dataclass
class AssistantMessage:
    content: list[ContentBlock]
    model: str

SystemMessage

Системное сообщение с метаданными.
@dataclass
class SystemMessage:
    subtype: str
    data: dict[str, Any]

ResultMessage

Финальное сообщение результата с информацией о стоимости и использовании.
@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

Типы блоков содержимого

ContentBlock

Тип объединения всех блоков содержимого.
ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock | ToolResultBlock

TextBlock

Блок текстового содержимого.
@dataclass
class TextBlock:
    text: str

ThinkingBlock

Блок содержимого размышления (для моделей с возможностью размышления).
@dataclass
class ThinkingBlock:
    thinking: str
    signature: str

ToolUseBlock

Блок запроса использования инструмента.
@dataclass
class ToolUseBlock:
    id: str
    name: str
    input: dict[str, Any]

ToolResultBlock

Блок результата выполнения инструмента.
@dataclass
class ToolResultBlock:
    tool_use_id: str
    content: str | list[dict[str, Any]] | None = None
    is_error: bool | None = None

Типы ошибок

ClaudeSDKError

Базовый класс исключения для всех ошибок SDK.
class ClaudeSDKError(Exception):
    """Базовая ошибка для Claude SDK."""

CLINotFoundError

Возникает, когда Claude Code CLI не установлен или не найден.
class CLINotFoundError(CLIConnectionError):
    def __init__(self, message: str = "Claude Code не найден", cli_path: str | None = None):
        """
        Args:
            message: Сообщение об ошибке (по умолчанию: "Claude Code не найден")
            cli_path: Необязательный путь к CLI, который не был найден
        """

CLIConnectionError

Возникает, когда подключение к Claude Code не удается.
class CLIConnectionError(ClaudeSDKError):
    """Не удалось подключиться к Claude Code."""

ProcessError

Возникает, когда процесс Claude Code не удается.
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

Возникает, когда парсинг JSON не удается.
class CLIJSONDecodeError(ClaudeSDKError):
    def __init__(self, line: str, original_error: Exception):
        """
        Args:
            line: Строка, которую не удалось распарсить
            original_error: Оригинальное исключение декодирования JSON
        """
        self.line = line
        self.original_error = original_error

Типы хуков

HookEvent

Поддерживаемые типы событий хуков. Обратите внимание, что из-за ограничений настройки Python SDK не поддерживает хуки SessionStart, SessionEnd и Notification.
HookEvent = Literal[
    "PreToolUse",      # Вызывается перед выполнением инструмента
    "PostToolUse",     # Вызывается после выполнения инструмента
    "UserPromptSubmit", # Вызывается когда пользователь отправляет промпт
    "Stop",            # Вызывается при остановке выполнения
    "SubagentStop",    # Вызывается когда субагент останавливается
    "PreCompact"       # Вызывается перед сжатием сообщений
]

HookCallback

Определение типа для функций обратного вызова хуков.
HookCallback = Callable[
    [dict[str, Any], str | None, HookContext],
    Awaitable[dict[str, Any]]
]
Параметры:
  • input_data: Входные данные, специфичные для хука (см. документацию хуков)
  • tool_use_id: Необязательный идентификатор использования инструмента (для хуков, связанных с инструментами)
  • context: Контекст хука с дополнительной информацией
Возвращает словарь, который может содержать:
  • decision: "block" для блокировки действия
  • systemMessage: Системное сообщение для добавления в транскрипт
  • hookSpecificOutput: Выходные данные, специфичные для хука

HookContext

Контекстная информация, передаваемая в обратные вызовы хуков.
@dataclass
class HookContext:
    signal: Any | None = None  # Будущее: поддержка сигнала прерывания

HookMatcher

Конфигурация для сопоставления хуков с определенными событиями или инструментами.
@dataclass
class HookMatcher:
    matcher: str | None = None        # Имя инструмента или шаблон для сопоставления (например, "Bash", "Write|Edit")
    hooks: list[HookCallback] = field(default_factory=list)  # Список обратных вызовов для выполнения

Пример использования хуков

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]:
    """Валидировать и потенциально блокировать опасные bash команды."""
    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': 'Опасная команда заблокирована'
                }
            }
    return {}

async def log_tool_use(
    input_data: dict[str, Any],
    tool_use_id: str | None,
    context: HookContext
) -> dict[str, Any]:
    """Логировать все использование инструментов для аудита."""
    print(f"Инструмент использован: {input_data.get('tool_name')}")
    return {}

options = ClaudeAgentOptions(
    hooks={
        'PreToolUse': [
            HookMatcher(matcher='Bash', hooks=[validate_bash_command]),
            HookMatcher(hooks=[log_tool_use])  # Применяется ко всем инструментам
        ],
        'PostToolUse': [
            HookMatcher(hooks=[log_tool_use])
        ]
    }
)

async for message in query(
    prompt="Проанализируйте эту кодовую базу",
    options=options
):
    print(message)

Типы ввода/вывода инструментов

Документация схем ввода/вывода для всех встроенных инструментов Claude Code. Хотя Python SDK не экспортирует их как типы, они представляют структуру входов и выходов инструментов в сообщениях.

Task

Имя инструмента: Task Ввод:
{
    "description": str,      # Краткое (3-5 слов) описание задачи
    "prompt": str,           # Задача для выполнения агентом
    "subagent_type": str     # Тип специализированного агента для использования
}
Вывод:
{
    "result": str,                    # Финальный результат от субагента
    "usage": dict | None,             # Статистика использования токенов
    "total_cost_usd": float | None,  # Общая стоимость в USD
    "duration_ms": int | None         # Длительность выполнения в миллисекундах
}

Bash

Имя инструмента: Bash Ввод:
{
    "command": str,                  # Команда для выполнения
    "timeout": int | None,           # Необязательный таймаут в миллисекундах (макс 600000)
    "description": str | None,       # Четкое, краткое описание (5-10 слов)
    "run_in_background": bool | None # Установить в true для запуска в фоне
}
Вывод:
{
    "output": str,              # Объединенный вывод stdout и stderr
    "exitCode": int,            # Код выхода команды
    "killed": bool | None,      # Была ли команда убита из-за таймаута
    "shellId": str | None       # ID оболочки для фоновых процессов
}

Edit

Имя инструмента: Edit Ввод:
{
    "file_path": str,           # Абсолютный путь к файлу для изменения
    "old_string": str,          # Текст для замены
    "new_string": str,          # Текст для замены на него
    "replace_all": bool | None  # Заменить все вхождения (по умолчанию False)
}
Вывод:
{
    "message": str,      # Сообщение подтверждения
    "replacements": int, # Количество сделанных замен
    "file_path": str     # Путь к файлу, который был отредактирован
}

MultiEdit

Имя инструмента: MultiEdit Ввод:
{
    "file_path": str,     # Абсолютный путь к файлу для изменения
    "edits": [            # Массив операций редактирования
        {
            "old_string": str,          # Текст для замены
            "new_string": str,          # Текст для замены на него
            "replace_all": bool | None  # Заменить все вхождения
        }
    ]
}
Вывод:
{
    "message": str,       # Сообщение об успехе
    "edits_applied": int, # Общее количество примененных правок
    "file_path": str      # Путь к файлу, который был отредактирован
}

Read

Имя инструмента: Read Ввод:
{
    "file_path": str,       # Абсолютный путь к файлу для чтения
    "offset": int | None,   # Номер строки для начала чтения с
    "limit": int | None     # Количество строк для чтения
}
Вывод (Текстовые файлы):
{
    "content": str,         # Содержимое файла с номерами строк
    "total_lines": int,     # Общее количество строк в файле
    "lines_returned": int   # Строки фактически возвращенные
}
Вывод (Изображения):
{
    "image": str,       # Данные изображения в кодировке Base64
    "mime_type": str,   # MIME тип изображения
    "file_size": int    # Размер файла в байтах
}

Write

Имя инструмента: Write Ввод:
{
    "file_path": str,  # Абсолютный путь к файлу для записи
    "content": str     # Содержимое для записи в файл
}
Вывод:
{
    "message": str,        # Сообщение об успехе
    "bytes_written": int,  # Количество записанных байт
    "file_path": str       # Путь к файлу, который был записан
}

Glob

Имя инструмента: Glob Ввод:
{
    "pattern": str,       # Шаблон glob для сопоставления файлов
    "path": str | None    # Директория для поиска в (по умолчанию cwd)
}
Вывод:
{
    "matches": list[str],  # Массив совпадающих путей файлов
    "count": int,          # Количество найденных совпадений
    "search_path": str     # Используемая директория поиска
}

Grep

Имя инструмента: Grep Ввод:
{
    "pattern": str,                    # Шаблон регулярного выражения
    "path": str | None,                # Файл или директория для поиска в
    "glob": str | None,                # Шаблон glob для фильтрации файлов
    "type": str | None,                # Тип файла для поиска
    "output_mode": str | None,         # "content", "files_with_matches", или "count"
    "-i": bool | None,                 # Поиск без учета регистра
    "-n": bool | None,                 # Показать номера строк
    "-B": int | None,                  # Строки для показа перед каждым совпадением
    "-A": int | None,                  # Строки для показа после каждого совпадения
    "-C": int | None,                  # Строки для показа до и после
    "head_limit": int | None,          # Ограничить вывод первыми N строками/записями
    "multiline": bool | None           # Включить многострочный режим
}
Вывод (режим content):
{
    "matches": [
        {
            "file": str,
            "line_number": int | None,
            "line": str,
            "before_context": list[str] | None,
            "after_context": list[str] | None
        }
    ],
    "total_matches": int
}
Вывод (режим files_with_matches):
{
    "files": list[str],  # Файлы, содержащие совпадения
    "count": int         # Количество файлов с совпадениями
}

NotebookEdit

Имя инструмента: NotebookEdit Ввод:
{
    "notebook_path": str,                     # Абсолютный путь к блокноту Jupyter
    "cell_id": str | None,                    # ID ячейки для редактирования
    "new_source": str,                        # Новый источник для ячейки
    "cell_type": "code" | "markdown" | None,  # Тип ячейки
    "edit_mode": "replace" | "insert" | "delete" | None  # Тип операции редактирования
}
Вывод:
{
    "message": str, # Сообщение об успехе
    "edit_type": "replaced" | "inserted" | "deleted",  # Тип выполненного редактирования
    "cell_id": str | None,                       # ID ячейки, которая была затронута
    "total_cells": int                           # Общее количество ячеек в блокноте после редактирования
}

WebFetch

Имя инструмента: WebFetch Ввод:
{
    "url": str,     # URL для получения содержимого из
    "prompt": str   # Промпт для запуска на полученном содержимом
}
Вывод:
{
    "response": str,           # Ответ AI модели на промпт
    "url": str,                # URL, который был получен
    "final_url": str | None,   # Финальный URL после перенаправлений
    "status_code": int | None  # HTTP код состояния
}

WebSearch

Имя инструмента: WebSearch Ввод:
{
    "query": str,                        # Поисковый запрос для использования
    "allowed_domains": list[str] | None, # Включать результаты только из этих доменов
    "blocked_domains": list[str] | None  # Никогда не включать результаты из этих доменов
}
Вывод:
{
    "results": [
        {
            "title": str,
            "url": str,
            "snippet": str,
            "metadata": dict | None
        }
    ],
    "total_results": int,
    "query": str
}

TodoWrite

Имя инструмента: TodoWrite Ввод:
{
    "todos": [
        {
            "content": str, # Описание задачи
            "status": "pending" | "in_progress" | "completed",  # Статус задачи
            "activeForm": str                            # Активная форма описания
        }
    ]
}
Вывод:
{
    "message": str,  # Сообщение об успехе
    "stats": {
        "total": int,
        "pending": int,
        "in_progress": int,
        "completed": int
    }
}

BashOutput

Имя инструмента: BashOutput Ввод:
{
    "bash_id": str,       # ID фоновой оболочки
    "filter": str | None  # Необязательное регулярное выражение для фильтрации строк вывода
}
Вывод:
{
    "output": str, # Новый вывод с последней проверки
    "status": "running" | "completed" | "failed",       # Текущий статус оболочки
    "exitCode": int | None # Код выхода при завершении
}

KillBash

Имя инструмента: KillBash Ввод:
{
    "shell_id": str  # ID фоновой оболочки для убийства
}
Вывод:
{
    "message": str,  # Сообщение об успехе
    "shell_id": str  # ID убитой оболочки
}

ExitPlanMode

Имя инструмента: ExitPlanMode Ввод:
{
    "plan": str  # План для запуска пользователем для одобрения
}
Вывод:
{
    "message": str,          # Сообщение подтверждения
    "approved": bool | None  # Одобрил ли пользователь план
}

ListMcpResources

Имя инструмента: ListMcpResources Ввод:
{
    "server": str | None  # Необязательное имя сервера для фильтрации ресурсов по
}
Вывод:
{
    "resources": [
        {
            "uri": str,
            "name": str,
            "description": str | None,
            "mimeType": str | None,
            "server": str
        }
    ],
    "total": int
}

ReadMcpResource

Имя инструмента: ReadMcpResource Ввод:
{
    "server": str,  # Имя MCP сервера
    "uri": str      # URI ресурса для чтения
}
Вывод:
{
    "contents": [
        {
            "uri": str,
            "mimeType": str | None,
            "text": str | None,
            "blob": str | None
        }
    ],
    "server": str
}

Расширенные функции с ClaudeSDKClient

Построение интерфейса непрерывного разговора

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

class ConversationSession:
    """Поддерживает одну сессию разговора с Claude."""
    
    def __init__(self, options: ClaudeCodeOptions = None):
        self.client = ClaudeSDKClient(options)
        self.turn_count = 0
    
    async def start(self):
        await self.client.connect()
        print("Начинаем сессию разговора. Claude будет помнить контекст.")
        print("Команды: 'exit' для выхода, 'interrupt' для остановки текущей задачи, 'new' для новой сессии")
        
        while True:
            user_input = input(f"\n[Ход {self.turn_count + 1}] Вы: ")
            
            if user_input.lower() == 'exit':
                break
            elif user_input.lower() == 'interrupt':
                await self.client.interrupt()
                print("Задача прервана!")
                continue
            elif user_input.lower() == 'new':
                # Отключиться и переподключиться для свежей сессии
                await self.client.disconnect()
                await self.client.connect()
                self.turn_count = 0
                print("Начата новая сессия разговора (предыдущий контекст очищен)")
                continue
            
            # Отправить сообщение - Claude помнит все предыдущие сообщения в этой сессии
            await self.client.query(user_input)
            self.turn_count += 1
            
            # Обработать ответ
            print(f"[Ход {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()  # Новая строка после ответа
        
        await self.client.disconnect()
        print(f"Разговор закончен после {self.turn_count} ходов.")

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

# Пример разговора:
# Ход 1 - Вы: "Создайте файл под названием hello.py"
# Ход 1 - Claude: "Я создам файл hello.py для вас..."
# Ход 2 - Вы: "Что в этом файле?"  
# Ход 2 - Claude: "Файл hello.py, который я только что создал, содержит..." (помнит!)
# Ход 3 - Вы: "Добавьте к нему главную функцию"
# Ход 3 - Claude: "Я добавлю главную функцию к hello.py..." (знает какой файл!)

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]:
    """Логировать все использование инструментов перед выполнением."""
    tool_name = input_data.get('tool_name', 'unknown')
    print(f"[PRE-TOOL] Собираюсь использовать: {tool_name}")

    # Вы можете изменить или заблокировать выполнение инструмента здесь
    if tool_name == "Bash" and "rm -rf" in str(input_data.get('tool_input', {})):
        return {
            'hookSpecificOutput': {
                'hookEventName': 'PreToolUse',
                'permissionDecision': 'deny',
                'permissionDecisionReason': 'Опасная команда заблокирована'
            }
        }
    return {}

async def post_tool_logger(
    input_data: dict[str, Any],
    tool_use_id: str | None,
    context: HookContext
) -> dict[str, Any]:
    """Логировать результаты после выполнения инструмента."""
    tool_name = input_data.get('tool_name', 'unknown')
    print(f"[POST-TOOL] Завершено: {tool_name}")
    return {}

async def user_prompt_modifier(
    input_data: dict[str, Any],
    tool_use_id: str | None,
    context: HookContext
) -> dict[str, Any]:
    """Добавить контекст к пользовательским промптам."""
    original_prompt = input_data.get('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("Перечислите файлы в текущей директории")
        
        async for message in client.receive_response():
            # Хуки автоматически будут логировать использование инструментов
            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(
            "Создайте 5 файлов Python с различными алгоритмами сортировки"
        )
        
        # Мониторить прогресс в реальном времени
        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"🔨 Создаю: {file_path}")
                    elif isinstance(block, ToolResultBlock):
                        print(f"✅ Выполнение инструмента завершено")
                    elif isinstance(block, TextBlock):
                        print(f"💭 Claude говорит: {block.text[:100]}...")
            
            # Проверить, получили ли мы финальный результат
            if hasattr(message, 'subtype') and message.subtype in ['success', 'error']:
                print(f"\n🎯 Задача завершена!")
                break

asyncio.run(monitor_progress())

Примеры использования

Базовые файловые операции (используя 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="Создайте структуру проекта Python с setup.py",
        options=options
    ):
        if isinstance(message, AssistantMessage):
            for block in message.content:
                if isinstance(block, ToolUseBlock):
                    print(f"Использую инструмент: {block.name}")

asyncio.run(create_project())

Обработка ошибок

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

try:
    async for message in query(prompt="Привет"):
        print(message)
except CLINotFoundError:
    print("Пожалуйста, установите Claude Code: npm install -g @anthropic-ai/claude-code")
except ProcessError as e:
    print(f"Процесс не удался с кодом выхода: {e.exit_code}")
except CLIJSONDecodeError as e:
    print(f"Не удалось распарсить ответ: {e}")

Потоковый режим с клиентом

from claude_agent_sdk import ClaudeSDKClient
import asyncio

async def interactive_session():
    async with ClaudeSDKClient() as client:
        # Отправить начальное сообщение
        await client.query("Какая погода?")
        
        # Обработать ответы
        async for msg in client.receive_response():
            print(msg)
        
        # Отправить продолжение
        await client.query("Расскажите мне больше об этом")
        
        # Обработать ответ на продолжение
        async for msg in client.receive_response():
            print(msg)

asyncio.run(interactive_session())

Использование пользовательских инструментов с ClaudeSDKClient

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

# Определить пользовательские инструменты с декоратором @tool
@tool("calculate", "Выполнить математические вычисления", {"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}"
            }]
        }
    except Exception as e:
        return {
            "content": [{
                "type": "text",
                "text": f"Ошибка: {str(e)}"
            }],
            "is_error": True
        }

@tool("get_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}"
        }]
    }

async def main():
    # Создать SDK MCP сервер с пользовательскими инструментами
    my_server = create_sdk_mcp_server(
        name="utilities",
        version="1.0.0",
        tools=[calculate, get_time]
    )

    # Настроить опции с сервером
    options = ClaudeAgentOptions(
        mcp_servers={"utils": my_server},
        allowed_tools=[
            "mcp__utils__calculate",
            "mcp__utils__get_time"
        ]
    )
    
    # Использовать ClaudeSDKClient для интерактивного использования инструментов
    async with ClaudeSDKClient(options=options) as client:
        await client.query("Сколько будет 123 * 456?")
        
        # Обработать ответ вычисления
        async for message in client.receive_response():
            if isinstance(message, AssistantMessage):
                for block in message.content:
                    if isinstance(block, TextBlock):
                        print(f"Вычисление: {block.text}")
        
        # Продолжить запросом времени
        await client.query("Который сейчас час?")
        
        async for message in client.receive_response():
            if isinstance(message, AssistantMessage):
                for block in message.content:
                    if isinstance(block, TextBlock):
                        print(f"Время: {block.text}")

asyncio.run(main())

См. также