Installation

pip install claude-agent-sdk

Choisir entre query() et ClaudeSDKClient

Le SDK Python fournit deux façons d’interagir avec Claude Code :

Comparaison rapide

Fonctionnalitéquery()ClaudeSDKClient
SessionCrée une nouvelle session à chaque foisRéutilise la même session
ConversationÉchange uniquePlusieurs échanges dans le même contexte
ConnexionGérée automatiquementContrôle manuel
Entrée en streaming✅ Supportée✅ Supportée
Interruptions❌ Non supportées✅ Supportées
Hooks❌ Non supportés✅ Supportés
Outils personnalisés❌ Non supportés✅ Supportés
Continuer la conversation❌ Nouvelle session à chaque fois✅ Maintient la conversation
Cas d’usageTâches ponctuellesConversations continues

Quand utiliser query() (Nouvelle session à chaque fois)

Idéal pour :
  • Les questions ponctuelles où vous n’avez pas besoin d’historique de conversation
  • Les tâches indépendantes qui ne nécessitent pas de contexte des échanges précédents
  • Les scripts d’automatisation simples
  • Quand vous voulez un départ frais à chaque fois

Quand utiliser ClaudeSDKClient (Conversation continue)

Idéal pour :
  • Continuer les conversations - Quand vous avez besoin que Claude se souvienne du contexte
  • Questions de suivi - S’appuyer sur les réponses précédentes
  • Applications interactives - Interfaces de chat, REPLs
  • Logique basée sur les réponses - Quand l’action suivante dépend de la réponse de Claude
  • Contrôle de session - Gérer explicitement le cycle de vie de la conversation

Fonctions

query()

Crée une nouvelle session pour chaque interaction avec Claude Code. Retourne un itérateur asynchrone qui produit des messages au fur et à mesure qu’ils arrivent. Chaque appel à query() commence de zéro sans mémoire des interactions précédentes.
async def query(
    *,
    prompt: str | AsyncIterable[dict[str, Any]],
    options: ClaudeAgentOptions | None = None
) -> AsyncIterator[Message]

Paramètres

ParamètreTypeDescription
promptstr | AsyncIterable[dict]Le prompt d’entrée sous forme de chaîne ou d’itérable asynchrone pour le mode streaming
optionsClaudeAgentOptions | NoneObjet de configuration optionnel (par défaut ClaudeAgentOptions() si None)

Retours

Retourne un AsyncIterator[Message] qui produit des messages de la conversation.

Exemple - Avec options


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

Décorateur pour définir les outils MCP avec la sécurité des types.
def tool(
    name: str,
    description: str,
    input_schema: type | dict[str, Any]
) -> Callable[[Callable[[Any], Awaitable[dict[str, Any]]]], SdkMcpTool[Any]]

Paramètres

ParamètreTypeDescription
namestrIdentifiant unique pour l’outil
descriptionstrDescription lisible de ce que fait l’outil
input_schematype | dict[str, Any]Schéma définissant les paramètres d’entrée de l’outil (voir ci-dessous)

Options de schéma d’entrée

  1. Mappage de type simple (recommandé) :
    {"text": str, "count": int, "enabled": bool}
    
  2. Format JSON Schema (pour la validation complexe) :
    {
        "type": "object",
        "properties": {
            "text": {"type": "string"},
            "count": {"type": "integer", "minimum": 0}
        },
        "required": ["text"]
    }
    

Retours

Une fonction décorateur qui enveloppe l’implémentation de l’outil et retourne une instance SdkMcpTool.

Exemple

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

Créer un serveur MCP en processus qui s’exécute dans votre application Python.
def create_sdk_mcp_server(
    name: str,
    version: str = "1.0.0",
    tools: list[SdkMcpTool[Any]] | None = None
) -> McpSdkServerConfig

Paramètres

ParamètreTypePar défautDescription
namestr-Identifiant unique pour le serveur
versionstr"1.0.0"Chaîne de version du serveur
toolslist[SdkMcpTool[Any]] | NoneNoneListe des fonctions d’outils créées avec le décorateur @tool

Retours

Retourne un objet McpSdkServerConfig qui peut être passé à ClaudeAgentOptions.mcp_servers.

Exemple

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

Maintient une session de conversation sur plusieurs échanges. C’est l’équivalent Python de la façon dont la fonction query() du SDK TypeScript fonctionne en interne - elle crée un objet client qui peut continuer les conversations.

Fonctionnalités clés

  • Continuité de session : Maintient le contexte de conversation sur plusieurs appels query()
  • Même conversation : Claude se souvient des messages précédents dans la session
  • Support des interruptions : Peut arrêter Claude en cours d’exécution
  • Cycle de vie explicite : Vous contrôlez quand la session commence et se termine
  • Flux basé sur les réponses : Peut réagir aux réponses et envoyer des suivis
  • Outils personnalisés et hooks : Supporte les outils personnalisés (créés avec le décorateur @tool) et les 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éthodes

MéthodeDescription
__init__(options)Initialiser le client avec une configuration optionnelle
connect(prompt)Se connecter à Claude avec un prompt initial optionnel ou un flux de messages
query(prompt, session_id)Envoyer une nouvelle demande en mode streaming
receive_messages()Recevoir tous les messages de Claude sous forme d’itérateur asynchrone
receive_response()Recevoir les messages jusqu’à et y compris un ResultMessage
interrupt()Envoyer un signal d’interruption (fonctionne uniquement en mode streaming)
disconnect()Se déconnecter de Claude

Support du gestionnaire de contexte

Le client peut être utilisé comme gestionnaire de contexte asynchrone pour la gestion automatique de la connexion :
async with ClaudeSDKClient() as client:
    await client.query("Hello Claude")
    async for message in client.receive_response():
        print(message)
Important : Lors de l’itération sur les messages, évitez d’utiliser break pour quitter tôt car cela peut causer des problèmes de nettoyage asyncio. À la place, laissez l’itération se terminer naturellement ou utilisez des drapeaux pour suivre quand vous avez trouvé ce que vous cherchiez.

Exemple - Continuer une conversation

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

Exemple - Entrée en streaming avec 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())

Exemple - Utiliser les interruptions

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

Exemple - Contrôle des permissions avancé

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

Types

SdkMcpTool

Définition pour un outil MCP SDK créé avec le décorateur @tool.
@dataclass
class SdkMcpTool(Generic[T]):
    name: str
    description: str
    input_schema: type[T] | dict[str, Any]
    handler: Callable[[T], Awaitable[dict[str, Any]]]
PropriétéTypeDescription
namestrIdentifiant unique pour l’outil
descriptionstrDescription lisible
input_schematype[T] | dict[str, Any]Schéma pour la validation d’entrée
handlerCallable[[T], Awaitable[dict[str, Any]]]Fonction asynchrone qui gère l’exécution de l’outil

ClaudeAgentOptions

Classe de configuration pour les requêtes 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
PropriétéTypePar défautDescription
allowed_toolslist[str][]Liste des noms d’outils autorisés
system_promptstr | SystemPromptPreset | NoneNoneConfiguration du prompt système. Passez une chaîne pour un prompt personnalisé, ou utilisez {"type": "preset", "preset": "claude_code"} pour le prompt système de Claude Code. Ajoutez "append" pour étendre le preset
mcp_serversdict[str, McpServerConfig] | str | Path{}Configurations du serveur MCP ou chemin vers le fichier de configuration
permission_modePermissionMode | NoneNoneMode de permission pour l’utilisation des outils
continue_conversationboolFalseContinuer la conversation la plus récente
resumestr | NoneNoneID de session à reprendre
max_turnsint | NoneNoneNombre maximum de tours de conversation
disallowed_toolslist[str][]Liste des noms d’outils non autorisés
modelstr | NoneNoneModèle Claude à utiliser
permission_prompt_tool_namestr | NoneNoneNom de l’outil MCP pour les prompts de permission
cwdstr | Path | NoneNoneRépertoire de travail courant
settingsstr | NoneNoneChemin vers le fichier de paramètres
add_dirslist[str | Path][]Répertoires supplémentaires auxquels Claude peut accéder
envdict[str, str]{}Variables d’environnement
extra_argsdict[str, str | None]{}Arguments CLI supplémentaires à passer directement à la CLI
max_buffer_sizeint | NoneNoneNombre maximum d’octets lors de la mise en mémoire tampon de la sortie CLI stdout
debug_stderrAnysys.stderrDéprécié - Objet de type fichier pour la sortie de débogage. Utilisez plutôt le rappel stderr
stderrCallable[[str], None] | NoneNoneFonction de rappel pour la sortie stderr de la CLI
can_use_toolCanUseTool | NoneNoneFonction de rappel de permission d’outil
hooksdict[HookEvent, list[HookMatcher]] | NoneNoneConfigurations de hook pour intercepter les événements
userstr | NoneNoneIdentifiant utilisateur
include_partial_messagesboolFalseInclure les événements de streaming de messages partiels
fork_sessionboolFalseLors de la reprise avec resume, bifurquer vers un nouvel ID de session au lieu de continuer la session d’origine
agentsdict[str, AgentDefinition] | NoneNoneSous-agents définis par programmation
pluginslist[SdkPluginConfig][]Charger les plugins personnalisés à partir de chemins locaux. Voir Plugins pour plus de détails
setting_sourceslist[SettingSource] | NoneNone (pas de paramètres)Contrôler les paramètres du système de fichiers à charger. Lorsqu’omis, aucun paramètre n’est chargé. Remarque : Doit inclure "project" pour charger les fichiers CLAUDE.md

SystemPromptPreset

Configuration pour utiliser le prompt système preset de Claude Code avec des ajouts optionnels.
class SystemPromptPreset(TypedDict):
    type: Literal["preset"]
    preset: Literal["claude_code"]
    append: NotRequired[str]
ChampRequisDescription
typeOuiDoit être "preset" pour utiliser un prompt système preset
presetOuiDoit être "claude_code" pour utiliser le prompt système de Claude Code
appendNonInstructions supplémentaires à ajouter au prompt système preset

SettingSource

Contrôle les sources de configuration basées sur le système de fichiers que le SDK charge.
SettingSource = Literal["user", "project", "local"]
ValeurDescriptionEmplacement
"user"Paramètres utilisateur globaux~/.claude/settings.json
"project"Paramètres de projet partagés (contrôle de version).claude/settings.json
"local"Paramètres de projet locaux (gitignorés).claude/settings.local.json

Comportement par défaut

Quand setting_sources est omis ou None, le SDK ne charge pas les paramètres du système de fichiers. Cela fournit l’isolation pour les applications SDK.

Pourquoi utiliser setting_sources ?

Charger tous les paramètres du système de fichiers (comportement hérité) :
# 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)
Charger uniquement des sources de paramètres spécifiques :
# 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)
Environnements de test et 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)
Applications SDK uniquement :
# 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)
Chargement des instructions de projet 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)

Précédence des paramètres

Quand plusieurs sources sont chargées, les paramètres sont fusionnés avec cette précédence (du plus élevé au plus bas) :
  1. Paramètres locaux (.claude/settings.local.json)
  2. Paramètres de projet (.claude/settings.json)
  3. Paramètres utilisateur (~/.claude/settings.json)
Les options programmatiques (comme agents, allowed_tools) remplacent toujours les paramètres du système de fichiers.

AgentDefinition

Configuration pour un sous-agent défini par programmation.
@dataclass
class AgentDefinition:
    description: str
    prompt: str
    tools: list[str] | None = None
    model: Literal["sonnet", "opus", "haiku", "inherit"] | None = None
ChampRequisDescription
descriptionOuiDescription en langage naturel de quand utiliser cet agent
toolsNonTableau des noms d’outils autorisés. Si omis, hérite de tous les outils
promptOuiLe prompt système de l’agent
modelNonRemplacement du modèle pour cet agent. Si omis, utilise le modèle principal

PermissionMode

Modes de permission pour contrôler l’exécution des outils.
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

Configuration pour les serveurs MCP SDK créés avec create_sdk_mcp_server().
class McpSdkServerConfig(TypedDict):
    type: Literal["sdk"]
    name: str
    instance: Any  # MCP Server instance

McpServerConfig

Type d’union pour les configurations du serveur 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

Configuration pour charger les plugins dans le SDK.
class SdkPluginConfig(TypedDict):
    type: Literal["local"]
    path: str
ChampTypeDescription
typeLiteral["local"]Doit être "local" (seuls les plugins locaux sont actuellement supportés)
pathstrChemin absolu ou relatif vers le répertoire du plugin
Exemple :
plugins=[
    {"type": "local", "path": "./my-plugin"},
    {"type": "local", "path": "/absolute/path/to/plugin"}
]
Pour des informations complètes sur la création et l’utilisation de plugins, voir Plugins.

Types de messages

Message

Type d’union de tous les messages possibles.
Message = UserMessage | AssistantMessage | SystemMessage | ResultMessage

UserMessage

Message d’entrée utilisateur.
@dataclass
class UserMessage:
    content: str | list[ContentBlock]

AssistantMessage

Message de réponse de l’assistant avec des blocs de contenu.
@dataclass
class AssistantMessage:
    content: list[ContentBlock]
    model: str

SystemMessage

Message système avec métadonnées.
@dataclass
class SystemMessage:
    subtype: str
    data: dict[str, Any]

ResultMessage

Message de résultat final avec informations de coût et d’utilisation.
@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

Types de blocs de contenu

ContentBlock

Type d’union de tous les blocs de contenu.
ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock | ToolResultBlock

TextBlock

Bloc de contenu texte.
@dataclass
class TextBlock:
    text: str

ThinkingBlock

Bloc de contenu de réflexion (pour les modèles avec capacité de réflexion).
@dataclass
class ThinkingBlock:
    thinking: str
    signature: str

ToolUseBlock

Bloc de demande d’utilisation d’outil.
@dataclass
class ToolUseBlock:
    id: str
    name: str
    input: dict[str, Any]

ToolResultBlock

Bloc de résultat d’exécution d’outil.
@dataclass
class ToolResultBlock:
    tool_use_id: str
    content: str | list[dict[str, Any]] | None = None
    is_error: bool | None = None

Types d’erreur

ClaudeSDKError

Classe d’exception de base pour toutes les erreurs SDK.
class ClaudeSDKError(Exception):
    """Base error for Claude SDK."""

CLINotFoundError

Levée quand Claude Code CLI n’est pas installé ou introuvable.
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

Levée quand la connexion à Claude Code échoue.
class CLIConnectionError(ClaudeSDKError):
    """Failed to connect to Claude Code."""

ProcessError

Levée quand le processus Claude Code échoue.
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

Levée quand l’analyse JSON échoue.
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

Types de hook

HookEvent

Types d’événements de hook supportés. Notez que en raison des limitations de configuration, le SDK Python ne supporte pas les hooks SessionStart, SessionEnd et 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

Définition de type pour les fonctions de rappel de hook.
HookCallback = Callable[
    [dict[str, Any], str | None, HookContext],
    Awaitable[dict[str, Any]]
]
Paramètres :
  • input_data : Données d’entrée spécifiques au hook (voir documentation des hooks)
  • tool_use_id : Identifiant d’utilisation d’outil optionnel (pour les hooks liés aux outils)
  • context : Contexte du hook avec des informations supplémentaires
Retourne un dictionnaire qui peut contenir :
  • decision : "block" pour bloquer l’action
  • systemMessage : Message système à ajouter à la transcription
  • hookSpecificOutput : Données de sortie spécifiques au hook

HookContext

Informations de contexte transmises aux rappels de hook.
@dataclass
class HookContext:
    signal: Any | None = None  # Future: abort signal support

HookMatcher

Configuration pour faire correspondre les hooks à des événements ou des outils spécifiques.
@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

Exemple d’utilisation 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)

Types d’entrée/sortie d’outil

Documentation des schémas d’entrée/sortie pour tous les outils Claude Code intégrés. Bien que le SDK Python n’exporte pas ces types, ils représentent la structure des entrées et sorties d’outils dans les messages.

Task

Nom de l’outil : Task Entrée :
{
    "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
}
Sortie :
{
    "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

Nom de l’outil : Bash Entrée :
{
    "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
}
Sortie :
{
    "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

Nom de l’outil : Edit Entrée :
{
    "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)
}
Sortie :
{
    "message": str,      # Confirmation message
    "replacements": int, # Number of replacements made
    "file_path": str     # File path that was edited
}

Read

Nom de l’outil : Read Entrée :
{
    "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
}
Sortie (fichiers texte) :
{
    "content": str,         # File contents with line numbers
    "total_lines": int,     # Total number of lines in file
    "lines_returned": int   # Lines actually returned
}
Sortie (images) :
{
    "image": str,       # Base64 encoded image data
    "mime_type": str,   # Image MIME type
    "file_size": int    # File size in bytes
}

Write

Nom de l’outil : Write Entrée :
{
    "file_path": str,  # The absolute path to the file to write
    "content": str     # The content to write to the file
}
Sortie :
{
    "message": str,        # Success message
    "bytes_written": int,  # Number of bytes written
    "file_path": str       # File path that was written
}

Glob

Nom de l’outil : Glob Entrée :
{
    "pattern": str,       # The glob pattern to match files against
    "path": str | None    # The directory to search in (defaults to cwd)
}
Sortie :
{
    "matches": list[str],  # Array of matching file paths
    "count": int,          # Number of matches found
    "search_path": str     # Search directory used
}

Grep

Nom de l’outil : Grep Entrée :
{
    "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
}
Sortie (mode contenu) :
{
    "matches": [
        {
            "file": str,
            "line_number": int | None,
            "line": str,
            "before_context": list[str] | None,
            "after_context": list[str] | None
        }
    ],
    "total_matches": int
}
Sortie (mode files_with_matches) :
{
    "files": list[str],  # Files containing matches
    "count": int         # Number of files with matches
}

NotebookEdit

Nom de l’outil : NotebookEdit Entrée :
{
    "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
}
Sortie :
{
    "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

Nom de l’outil : WebFetch Entrée :
{
    "url": str,     # The URL to fetch content from
    "prompt": str   # The prompt to run on the fetched content
}
Sortie :
{
    "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

Nom de l’outil : WebSearch Entrée :
{
    "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
}
Sortie :
{
    "results": [
        {
            "title": str,
            "url": str,
            "snippet": str,
            "metadata": dict | None
        }
    ],
    "total_results": int,
    "query": str
}

TodoWrite

Nom de l’outil : TodoWrite Entrée :
{
    "todos": [
        {
            "content": str, # The task description
            "status": "pending" | "in_progress" | "completed",  # Task status
            "activeForm": str                            # Active form of the description
        }
    ]
}
Sortie :
{
    "message": str,  # Success message
    "stats": {
        "total": int,
        "pending": int,
        "in_progress": int,
        "completed": int
    }
}

BashOutput

Nom de l’outil : BashOutput Entrée :
{
    "bash_id": str,       # The ID of the background shell
    "filter": str | None  # Optional regex to filter output lines
}
Sortie :
{
    "output": str, # New output since last check
    "status": "running" | "completed" | "failed",       # Current shell status
    "exitCode": int | None # Exit code when completed
}

KillBash

Nom de l’outil : KillBash Entrée :
{
    "shell_id": str  # The ID of the background shell to kill
}
Sortie :
{
    "message": str,  # Success message
    "shell_id": str  # ID of the killed shell
}

ExitPlanMode

Nom de l’outil : ExitPlanMode Entrée :
{
    "plan": str  # The plan to run by the user for approval
}
Sortie :
{
    "message": str,          # Confirmation message
    "approved": bool | None  # Whether user approved the plan
}

ListMcpResources

Nom de l’outil : ListMcpResources Entrée :
{
    "server": str | None  # Optional server name to filter resources by
}
Sortie :
{
    "resources": [
        {
            "uri": str,
            "name": str,
            "description": str | None,
            "mimeType": str | None,
            "server": str
        }
    ],
    "total": int
}

ReadMcpResource

Nom de l’outil : ReadMcpResource Entrée :
{
    "server": str,  # The MCP server name
    "uri": str      # The resource URI to read
}
Sortie :
{
    "contents": [
        {
            "uri": str,
            "mimeType": str | None,
            "text": str | None,
            "blob": str | None
        }
    ],
    "server": str
}

Fonctionnalités avancées avec ClaudeSDKClient

Construire une interface de conversation continue

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

Utiliser les hooks pour la modification du comportement

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

Surveillance de la progression en temps réel

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

Exemple d’utilisation

Opérations de fichiers de base (utilisant 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())

Gestion des erreurs

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

try:
    async for message in query(prompt="Hello"):
        print(message)
except CLINotFoundError:
    print("Please install Claude Code: npm install -g @anthropic-ai/claude-code")
except ProcessError as e:
    print(f"Process failed with exit code: {e.exit_code}")
except CLIJSONDecodeError as e:
    print(f"Failed to parse response: {e}")

Mode streaming avec client

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

Utiliser les outils personnalisés avec 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())

Voir aussi