Instalação

pip install claude-agent-sdk

Escolhendo entre query() e ClaudeSDKClient

O SDK Python fornece duas maneiras de interagir com Claude Code:

Comparação Rápida

Recursoquery()ClaudeSDKClient
SessãoCria nova sessão a cada vezReutiliza a mesma sessão
ConversaTroca únicaMúltiplas trocas no mesmo contexto
ConexãoGerenciada automaticamenteControle manual
Entrada em Streaming✅ Suportado✅ Suportado
Interrupções❌ Não suportado✅ Suportado
Hooks❌ Não suportado✅ Suportado
Ferramentas Personalizadas❌ Não suportado✅ Suportado
Continuar Chat❌ Nova sessão a cada vez✅ Mantém conversa
Caso de UsoTarefas pontuaisConversas contínuas

Quando Usar query() (Nova Sessão a Cada Vez)

Melhor para:
  • Perguntas pontuais onde você não precisa do histórico de conversa
  • Tarefas independentes que não requerem contexto de trocas anteriores
  • Scripts de automação simples
  • Quando você quer um novo começo a cada vez

Quando Usar ClaudeSDKClient (Conversa Contínua)

Melhor para:
  • Continuando conversas - Quando você precisa que Claude se lembre do contexto
  • Perguntas de acompanhamento - Construindo sobre respostas anteriores
  • Aplicações interativas - Interfaces de chat, REPLs
  • Lógica orientada por resposta - Quando a próxima ação depende da resposta de Claude
  • Controle de sessão - Gerenciando o ciclo de vida da conversa explicitamente

Funções

query()

Cria uma nova sessão para cada interação com Claude Code. Retorna um iterador assíncrono que produz mensagens conforme chegam. Cada chamada para query() começa do zero sem memória de interações anteriores.
async def query(
    *,
    prompt: str | AsyncIterable[dict[str, Any]],
    options: ClaudeAgentOptions | None = None
) -> AsyncIterator[Message]

Parâmetros

ParâmetroTipoDescrição
promptstr | AsyncIterable[dict]O prompt de entrada como uma string ou iterável assíncrono para modo de streaming
optionsClaudeAgentOptions | NoneObjeto de configuração opcional (padrão para ClaudeAgentOptions() se None)

Retorna

Retorna um AsyncIterator[Message] que produz mensagens da conversa.

Exemplo - Com opções


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

Decorador para definir ferramentas MCP com segurança de tipo.
def tool(
    name: str,
    description: str,
    input_schema: type | dict[str, Any]
) -> Callable[[Callable[[Any], Awaitable[dict[str, Any]]]], SdkMcpTool[Any]]

Parâmetros

ParâmetroTipoDescrição
namestrIdentificador único para a ferramenta
descriptionstrDescrição legível por humanos do que a ferramenta faz
input_schematype | dict[str, Any]Schema definindo os parâmetros de entrada da ferramenta (veja abaixo)

Opções de Schema de Entrada

  1. Mapeamento de tipo simples (recomendado):
    {"text": str, "count": int, "enabled": bool}
    
  2. Formato JSON Schema (para validação complexa):
    {
        "type": "object",
        "properties": {
            "text": {"type": "string"},
            "count": {"type": "integer", "minimum": 0}
        },
        "required": ["text"]
    }
    

Retorna

Uma função decoradora que envolve a implementação da ferramenta e retorna uma instância SdkMcpTool.

Exemplo

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

Cria um servidor MCP em processo que é executado dentro de sua aplicação Python.
def create_sdk_mcp_server(
    name: str,
    version: str = "1.0.0",
    tools: list[SdkMcpTool[Any]] | None = None
) -> McpSdkServerConfig

Parâmetros

ParâmetroTipoPadrãoDescrição
namestr-Identificador único para o servidor
versionstr"1.0.0"String de versão do servidor
toolslist[SdkMcpTool[Any]] | NoneNoneLista de funções de ferramenta criadas com decorador @tool

Retorna

Retorna um objeto McpSdkServerConfig que pode ser passado para ClaudeAgentOptions.mcp_servers.

Exemplo

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

Classes

ClaudeSDKClient

Mantém uma sessão de conversa através de múltiplas trocas. Este é o equivalente Python de como a função query() do SDK TypeScript funciona internamente - cria um objeto cliente que pode continuar conversas.

Recursos Principais

  • Continuidade de Sessão: Mantém contexto de conversa através de múltiplas chamadas query()
  • Mesma Conversa: Claude se lembra de mensagens anteriores na sessão
  • Suporte a Interrupção: Pode parar Claude no meio da execução
  • Ciclo de Vida Explícito: Você controla quando a sessão começa e termina
  • Fluxo Orientado por Resposta: Pode reagir a respostas e enviar acompanhamentos
  • Ferramentas Personalizadas e Hooks: Suporta ferramentas personalizadas (criadas com decorador @tool) e 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

Métodos

MétodoDescrição
__init__(options)Inicializa o cliente com configuração opcional
connect(prompt)Conecta a Claude com um prompt inicial opcional ou fluxo de mensagem
query(prompt, session_id)Envia uma nova solicitação em modo de streaming
receive_messages()Recebe todas as mensagens de Claude como um iterador assíncrono
receive_response()Recebe mensagens até e incluindo uma ResultMessage
interrupt()Envia sinal de interrupção (funciona apenas em modo de streaming)
disconnect()Desconecta de Claude

Suporte a Gerenciador de Contexto

O cliente pode ser usado como um gerenciador de contexto assíncrono para gerenciamento automático de conexão:
async with ClaudeSDKClient() as client:
    await client.query("Hello Claude")
    async for message in client.receive_response():
        print(message)
Importante: Ao iterar sobre mensagens, evite usar break para sair cedo, pois isso pode causar problemas de limpeza do asyncio. Em vez disso, deixe a iteração completar naturalmente ou use sinalizadores para rastrear quando você encontrou o que precisa.

Exemplo - Continuando uma conversa

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

Exemplo - Entrada em streaming com 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())

Exemplo - Usando interrupções

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

Exemplo - Controle de permissão avançado

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

Tipos

SdkMcpTool

Definição para uma ferramenta SDK MCP criada com o decorador @tool.
@dataclass
class SdkMcpTool(Generic[T]):
    name: str
    description: str
    input_schema: type[T] | dict[str, Any]
    handler: Callable[[T], Awaitable[dict[str, Any]]]
PropriedadeTipoDescrição
namestrIdentificador único para a ferramenta
descriptionstrDescrição legível por humanos
input_schematype[T] | dict[str, Any]Schema para validação de entrada
handlerCallable[[T], Awaitable[dict[str, Any]]]Função assíncrona que manipula a execução da ferramenta

ClaudeAgentOptions

Dataclass de configuração para consultas 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
PropriedadeTipoPadrãoDescrição
allowed_toolslist[str][]Lista de nomes de ferramentas permitidas
system_promptstr | SystemPromptPreset | NoneNoneConfiguração de prompt do sistema. Passe uma string para prompt personalizado, ou use {"type": "preset", "preset": "claude_code"} para o prompt do sistema Claude Code. Adicione "append" para estender o preset
mcp_serversdict[str, McpServerConfig] | str | Path{}Configurações de servidor MCP ou caminho para arquivo de configuração
permission_modePermissionMode | NoneNoneModo de permissão para uso de ferramentas
continue_conversationboolFalseContinuar a conversa mais recente
resumestr | NoneNoneID de sessão para retomar
max_turnsint | NoneNoneMáximo de turnos de conversa
disallowed_toolslist[str][]Lista de nomes de ferramentas não permitidas
modelstr | NoneNoneModelo Claude a usar
permission_prompt_tool_namestr | NoneNoneNome da ferramenta MCP para prompts de permissão
cwdstr | Path | NoneNoneDiretório de trabalho atual
settingsstr | NoneNoneCaminho para arquivo de configurações
add_dirslist[str | Path][]Diretórios adicionais que Claude pode acessar
envdict[str, str]{}Variáveis de ambiente
extra_argsdict[str, str | None]{}Argumentos CLI adicionais para passar diretamente para a CLI
max_buffer_sizeint | NoneNoneMáximo de bytes ao fazer buffer da saída stdout da CLI
debug_stderrAnysys.stderrDescontinuado - Objeto semelhante a arquivo para saída de depuração. Use callback stderr em vez disso
stderrCallable[[str], None] | NoneNoneFunção de callback para saída stderr da CLI
can_use_toolCanUseTool | NoneNoneFunção de callback de permissão de ferramenta
hooksdict[HookEvent, list[HookMatcher]] | NoneNoneConfigurações de hook para interceptar eventos
userstr | NoneNoneIdentificador de usuário
include_partial_messagesboolFalseIncluir eventos de streaming de mensagens parciais
fork_sessionboolFalseAo retomar com resume, fazer fork para um novo ID de sessão em vez de continuar a sessão original
agentsdict[str, AgentDefinition] | NoneNoneSubagentes definidos programaticamente
pluginslist[SdkPluginConfig][]Carregar plugins personalizados de caminhos locais. Veja Plugins para detalhes
setting_sourceslist[SettingSource] | NoneNone (sem configurações)Controlar quais configurações do sistema de arquivos carregar. Quando omitido, nenhuma configuração é carregada. Nota: Deve incluir "project" para carregar arquivos CLAUDE.md

SystemPromptPreset

Configuração para usar o prompt do sistema preset Claude Code com adições opcionais.
class SystemPromptPreset(TypedDict):
    type: Literal["preset"]
    preset: Literal["claude_code"]
    append: NotRequired[str]
CampoObrigatórioDescrição
typeSimDeve ser "preset" para usar um prompt do sistema preset
presetSimDeve ser "claude_code" para usar o prompt do sistema Claude Code
appendNãoInstruções adicionais para anexar ao prompt do sistema preset

SettingSource

Controla quais fontes de configuração baseadas em sistema de arquivos o SDK carrega configurações.
SettingSource = Literal["user", "project", "local"]
ValorDescriçãoLocalização
"user"Configurações globais do usuário~/.claude/settings.json
"project"Configurações de projeto compartilhadas (controladas por versão).claude/settings.json
"local"Configurações de projeto local (gitignored).claude/settings.local.json

Comportamento padrão

Quando setting_sources é omitido ou None, o SDK não carrega nenhuma configuração do sistema de arquivos. Isso fornece isolamento para aplicações SDK.

Por que usar setting_sources?

Carregar todas as configurações do sistema de arquivos (comportamento legado):
# 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)
Carregar apenas fontes de configuração específicas:
# 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)
Ambientes de teste e 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)
Aplicações apenas 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)
Carregando instruções de projeto 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)

Precedência de configurações

Quando múltiplas fontes são carregadas, as configurações são mescladas com esta precedência (maior para menor):
  1. Configurações locais (.claude/settings.local.json)
  2. Configurações de projeto (.claude/settings.json)
  3. Configurações de usuário (~/.claude/settings.json)
Opções programáticas (como agents, allowed_tools) sempre substituem configurações do sistema de arquivos.

AgentDefinition

Configuração para um subagente definido programaticamente.
@dataclass
class AgentDefinition:
    description: str
    prompt: str
    tools: list[str] | None = None
    model: Literal["sonnet", "opus", "haiku", "inherit"] | None = None
CampoObrigatórioDescrição
descriptionSimDescrição em linguagem natural de quando usar este agente
toolsNãoArray de nomes de ferramentas permitidas. Se omitido, herda todas as ferramentas
promptSimO prompt do sistema do agente
modelNãoSubstituição de modelo para este agente. Se omitido, usa o modelo principal

PermissionMode

Modos de permissão para controlar a execução de ferramentas.
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

Configuração para servidores MCP SDK criados com create_sdk_mcp_server().
class McpSdkServerConfig(TypedDict):
    type: Literal["sdk"]
    name: str
    instance: Any  # MCP Server instance

McpServerConfig

Tipo de união para configurações de servidor 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

Configuração para carregar plugins no SDK.
class SdkPluginConfig(TypedDict):
    type: Literal["local"]
    path: str
CampoTipoDescrição
typeLiteral["local"]Deve ser "local" (apenas plugins locais atualmente suportados)
pathstrCaminho absoluto ou relativo para o diretório do plugin
Exemplo:
plugins=[
    {"type": "local", "path": "./my-plugin"},
    {"type": "local", "path": "/absolute/path/to/plugin"}
]
Para informações completas sobre criação e uso de plugins, veja Plugins.

Tipos de Mensagem

Message

Tipo de união de todas as mensagens possíveis.
Message = UserMessage | AssistantMessage | SystemMessage | ResultMessage

UserMessage

Mensagem de entrada do usuário.
@dataclass
class UserMessage:
    content: str | list[ContentBlock]

AssistantMessage

Mensagem de resposta do assistente com blocos de conteúdo.
@dataclass
class AssistantMessage:
    content: list[ContentBlock]
    model: str

SystemMessage

Mensagem do sistema com metadados.
@dataclass
class SystemMessage:
    subtype: str
    data: dict[str, Any]

ResultMessage

Mensagem de resultado final com informações de custo e uso.
@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

Tipos de Bloco de Conteúdo

ContentBlock

Tipo de união de todos os blocos de conteúdo.
ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock | ToolResultBlock

TextBlock

Bloco de conteúdo de texto.
@dataclass
class TextBlock:
    text: str

ThinkingBlock

Bloco de conteúdo de pensamento (para modelos com capacidade de pensamento).
@dataclass
class ThinkingBlock:
    thinking: str
    signature: str

ToolUseBlock

Bloco de solicitação de uso de ferramenta.
@dataclass
class ToolUseBlock:
    id: str
    name: str
    input: dict[str, Any]

ToolResultBlock

Bloco de resultado de execução de ferramenta.
@dataclass
class ToolResultBlock:
    tool_use_id: str
    content: str | list[dict[str, Any]] | None = None
    is_error: bool | None = None

Tipos de Erro

ClaudeSDKError

Classe de exceção base para todos os erros do SDK.
class ClaudeSDKError(Exception):
    """Base error for Claude SDK."""

CLINotFoundError

Levantado quando Claude Code CLI não está instalado ou não é encontrado.
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

Levantado quando a conexão com Claude Code falha.
class CLIConnectionError(ClaudeSDKError):
    """Failed to connect to Claude Code."""

ProcessError

Levantado quando o processo Claude Code falha.
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

Levantado quando a análise JSON falha.
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

Tipos de Hook

HookEvent

Tipos de eventos de hook suportados. Observe que devido a limitações de configuração, o SDK Python não suporta hooks SessionStart, SessionEnd e 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

Definição de tipo para funções de callback de hook.
HookCallback = Callable[
    [dict[str, Any], str | None, HookContext],
    Awaitable[dict[str, Any]]
]
Parâmetros:
  • input_data: Dados de entrada específicos do hook (veja documentação de hook)
  • tool_use_id: Identificador de uso de ferramenta opcional (para hooks relacionados a ferramentas)
  • context: Contexto de hook com informações adicionais
Retorna um dicionário que pode conter:
  • decision: "block" para bloquear a ação
  • systemMessage: Mensagem do sistema para adicionar à transcrição
  • hookSpecificOutput: Dados de saída específicos do hook

HookContext

Informações de contexto passadas para callbacks de hook.
@dataclass
class HookContext:
    signal: Any | None = None  # Future: abort signal support

HookMatcher

Configuração para corresponder hooks a eventos ou ferramentas específicas.
@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

Exemplo de Uso de 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)

Tipos de Entrada/Saída de Ferramenta

Documentação de schemas de entrada/saída para todas as ferramentas Claude Code integradas. Embora o SDK Python não exporte estes como tipos, eles representam a estrutura de entradas e saídas de ferramentas em mensagens.

Task

Nome da ferramenta: Task Entrada:
{
    "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
}
Saída:
{
    "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

Nome da ferramenta: Bash Entrada:
{
    "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
}
Saída:
{
    "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

Nome da ferramenta: Edit Entrada:
{
    "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)
}
Saída:
{
    "message": str,      # Confirmation message
    "replacements": int, # Number of replacements made
    "file_path": str     # File path that was edited
}

Read

Nome da ferramenta: Read Entrada:
{
    "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
}
Saída (Arquivos de texto):
{
    "content": str,         # File contents with line numbers
    "total_lines": int,     # Total number of lines in file
    "lines_returned": int   # Lines actually returned
}
Saída (Imagens):
{
    "image": str,       # Base64 encoded image data
    "mime_type": str,   # Image MIME type
    "file_size": int    # File size in bytes
}

Write

Nome da ferramenta: Write Entrada:
{
    "file_path": str,  # The absolute path to the file to write
    "content": str     # The content to write to the file
}
Saída:
{
    "message": str,        # Success message
    "bytes_written": int,  # Number of bytes written
    "file_path": str       # File path that was written
}

Glob

Nome da ferramenta: Glob Entrada:
{
    "pattern": str,       # The glob pattern to match files against
    "path": str | None    # The directory to search in (defaults to cwd)
}
Saída:
{
    "matches": list[str],  # Array of matching file paths
    "count": int,          # Number of matches found
    "search_path": str     # Search directory used
}

Grep

Nome da ferramenta: Grep Entrada:
{
    "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
}
Saída (modo content):
{
    "matches": [
        {
            "file": str,
            "line_number": int | None,
            "line": str,
            "before_context": list[str] | None,
            "after_context": list[str] | None
        }
    ],
    "total_matches": int
}
Saída (modo files_with_matches):
{
    "files": list[str],  # Files containing matches
    "count": int         # Number of files with matches
}

NotebookEdit

Nome da ferramenta: NotebookEdit Entrada:
{
    "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
}
Saída:
{
    "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

Nome da ferramenta: WebFetch Entrada:
{
    "url": str,     # The URL to fetch content from
    "prompt": str   # The prompt to run on the fetched content
}
Saída:
{
    "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

Nome da ferramenta: WebSearch Entrada:
{
    "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
}
Saída:
{
    "results": [
        {
            "title": str,
            "url": str,
            "snippet": str,
            "metadata": dict | None
        }
    ],
    "total_results": int,
    "query": str
}

TodoWrite

Nome da ferramenta: TodoWrite Entrada:
{
    "todos": [
        {
            "content": str, # The task description
            "status": "pending" | "in_progress" | "completed",  # Task status
            "activeForm": str                            # Active form of the description
        }
    ]
}
Saída:
{
    "message": str,  # Success message
    "stats": {
        "total": int,
        "pending": int,
        "in_progress": int,
        "completed": int
    }
}

BashOutput

Nome da ferramenta: BashOutput Entrada:
{
    "bash_id": str,       # The ID of the background shell
    "filter": str | None  # Optional regex to filter output lines
}
Saída:
{
    "output": str, # New output since last check
    "status": "running" | "completed" | "failed",       # Current shell status
    "exitCode": int | None # Exit code when completed
}

KillBash

Nome da ferramenta: KillBash Entrada:
{
    "shell_id": str  # The ID of the background shell to kill
}
Saída:
{
    "message": str,  # Success message
    "shell_id": str  # ID of the killed shell
}

ExitPlanMode

Nome da ferramenta: ExitPlanMode Entrada:
{
    "plan": str  # The plan to run by the user for approval
}
Saída:
{
    "message": str,          # Confirmation message
    "approved": bool | None  # Whether user approved the plan
}

ListMcpResources

Nome da ferramenta: ListMcpResources Entrada:
{
    "server": str | None  # Optional server name to filter resources by
}
Saída:
{
    "resources": [
        {
            "uri": str,
            "name": str,
            "description": str | None,
            "mimeType": str | None,
            "server": str
        }
    ],
    "total": int
}

ReadMcpResource

Nome da ferramenta: ReadMcpResource Entrada:
{
    "server": str,  # The MCP server name
    "uri": str      # The resource URI to read
}
Saída:
{
    "contents": [
        {
            "uri": str,
            "mimeType": str | None,
            "text": str | None,
            "blob": str | None
        }
    ],
    "server": str
}

Recursos Avançados com ClaudeSDKClient

Construindo uma Interface de Conversa Contínua

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

Usando Hooks para Modificação de Comportamento

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

Monitoramento de Progresso em Tempo Real

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

Exemplo de Uso

Operações básicas de arquivo (usando 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())

Tratamento de erros

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

Modo de streaming com cliente

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

Usando ferramentas personalizadas com 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())

Veja também