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 uniqueÉchanges multiples 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 le chat❌ Nouvelle session à chaque fois✅ Maintient la conversation
Cas d’usageTâches ponctuellesConversations continues

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

Idéal pour :
  • Questions ponctuelles où vous n’avez pas besoin de l’historique de conversation
  • Tâches indépendantes qui ne nécessitent pas de contexte des échanges précédents
  • Scripts d’automatisation simples
  • Quand vous voulez un nouveau départ à 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 - Construire 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 à 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 comme chaîne ou itérable asynchrone pour le mode streaming
optionsClaudeAgentOptions | NoneObjet de configuration optionnel (par défaut ClaudeAgentOptions() si None)

Retour

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="Vous êtes un développeur Python expert",
        permission_mode='acceptEdits',
        cwd="/home/user/project"
    )

    async for message in query(
        prompt="Créez un serveur web Python",
        options=options
    ):
        print(message)


asyncio.run(main())

tool()

Décorateur pour définir des outils MCP avec sécurité de type.
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 par l’humain 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 de schéma JSON (pour validation complexe) :
    {
        "type": "object",
        "properties": {
            "text": {"type": "string"},
            "count": {"type": "integer", "minimum": 0}
        },
        "required": ["text"]
    }
    

Retour

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

Exemple

from claude_agent_sdk import tool
from typing import Any

@tool("greet", "Saluer un utilisateur", {"name": str})
async def greet(args: dict[str, Any]) -> dict[str, Any]:
    return {
        "content": [{
            "type": "text",
            "text": f"Bonjour, {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ètreTypeDé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

Retour

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

Exemple

from claude_agent_sdk import tool, create_sdk_mcp_server

@tool("add", "Additionner deux nombres", {"a": float, "b": float})
async def add(args):
    return {
        "content": [{
            "type": "text",
            "text": f"Somme : {args['a'] + args['b']}"
        }]
    }

@tool("multiply", "Multiplier deux nombres", {"a": float, "b": float})
async def multiply(args):
    return {
        "content": [{
            "type": "text",
            "text": f"Produit : {args['a'] * args['b']}"
        }]
    }

calculator = create_sdk_mcp_server(
    name="calculator",
    version="2.0.0",
    tools=[add, multiply]  # Passer les fonctions décorées
)

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

Classes

ClaudeSDKClient

Maintient une session de conversation à travers 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 à travers plusieurs appels query()
  • Même conversation : Claude se souvient des messages précédents dans la session
  • Support d’interruption : 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 et hooks personnalisés : 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 requête en mode streaming
receive_messages()Recevoir tous les messages de Claude comme un itérateur asynchrone
receive_response()Recevoir les messages jusqu’à et y compris un ResultMessage
interrupt()Envoyer un signal d’interruption (fonctionne seulement en mode streaming)
disconnect()Se déconnecter de Claude

Support du gestionnaire de contexte

Le client peut être utilisé comme un gestionnaire de contexte asynchrone pour la gestion automatique de la connexion :
async with ClaudeSDKClient() as client:
    await client.query("Bonjour Claude")
    async for message in client.receive_response():
        print(message)
Important : Lors de l’itération sur les messages, évitez d’utiliser break pour sortir prématurément 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 dont vous avez besoin.

Exemple - Continuer une conversation

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

async def main():
    async with ClaudeSDKClient() as client:
        # Première question
        await client.query("Quelle est la capitale de la France ?")
        
        # Traiter la réponse
        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}")
        
        # Question de suivi - Claude se souvient du contexte précédent
        await client.query("Quelle est la population de cette ville ?")
        
        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}")
        
        # Autre suivi - toujours dans la même conversation
        await client.query("Quels sont quelques monuments célèbres là-bas ?")
        
        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():
    """Générer des messages dynamiquement."""
    yield {"type": "text", "text": "Analysez les données suivantes :"}
    await asyncio.sleep(0.5)
    yield {"type": "text", "text": "Température : 25°C"}
    await asyncio.sleep(0.5)
    yield {"type": "text", "text": "Humidité : 60%"}
    await asyncio.sleep(0.5)
    yield {"type": "text", "text": "Quels modèles voyez-vous ?"}

async def main():
    async with ClaudeSDKClient() as client:
        # Diffuser l'entrée vers Claude
        await client.query(message_stream())
        
        # Traiter la réponse
        async for message in client.receive_response():
            print(message)
        
        # Suivi dans la même session
        await client.query("Devrions-nous être préoccupés par ces lectures ?")
        
        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:
        # Démarrer une tâche de longue durée
        await client.query("Comptez de 1 à 100 lentement")
        
        # Laisser tourner un peu
        await asyncio.sleep(2)
        
        # Interrompre la tâche
        await client.interrupt()
        print("Tâche interrompue !")
        
        # Envoyer une nouvelle commande
        await client.query("Dites juste bonjour à la place")
        
        async for message in client.receive_response():
            # Traiter la nouvelle réponse
            pass

asyncio.run(interruptible_task())

Exemple - Contrôle de permission avancé

from claude_agent_sdk import (
    ClaudeSDKClient,
    ClaudeAgentOptions
)

async def custom_permission_handler(
    tool_name: str,
    input_data: dict,
    context: dict
):
    """Logique personnalisée pour les permissions d'outils."""

    # Bloquer les écritures dans les répertoires système
    if tool_name == "Write" and input_data.get("file_path", "").startswith("/system/"):
        return {
            "behavior": "deny",
            "message": "Écriture dans le répertoire système non autorisée",
            "interrupt": True
        }

    # Rediriger les opérations de fichiers sensibles
    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}
        }

    # Autoriser tout le reste
    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("Mettez à jour le fichier de configuration système")
        
        async for message in client.receive_response():
            # Utilisera le chemin sandbox à la place
            print(message)

asyncio.run(main())

Types

SdkMcpTool

Définition pour un outil SDK MCP 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 par l’humain
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

Dataclass de configuration pour les requêtes Claude Code.
@dataclass
class ClaudeAgentOptions:
    allowed_tools: list[str] = field(default_factory=list)
    max_thinking_tokens: int = 8000
    system_prompt: str | None = None
    mcp_servers: dict[str, McpServerConfig] | str | Path = field(default_factory=dict)
    permission_mode: PermissionMode | None = None
    continue_conversation: bool = False
    resume: str | None = None
    fork_session: bool = False
    max_turns: int | None = None
    disallowed_tools: list[str] = field(default_factory=list)
    model: str | None = None
    permission_prompt_tool_name: str | None = None
    cwd: str | Path | None = None
    settings: str | None = None
    add_dirs: list[str | Path] = field(default_factory=list)
    env: dict[str, str] = field(default_factory=dict)
    extra_args: dict[str, str | None] = field(default_factory=dict)
PropriétéTypeDéfautDescription
allowed_toolslist[str][]Liste des noms d’outils autorisés
max_thinking_tokensint8000Tokens maximum pour le processus de réflexion
system_promptstr | NoneNoneConfiguration du prompt système. Passez une chaîne pour un prompt personnalisé, ou utilisez le format prédéfini pour le prompt système de Claude Code
mcp_serversdict[str, McpServerConfig] | str | Path{}Configurations de serveur MCP ou chemin vers le fichier de configuration
permission_modePermissionMode | NoneNoneMode de permission pour l’utilisation d’outils
continue_conversationboolFalseContinuer la conversation la plus récente
resumestr | NoneNoneID de session à reprendre
fork_sessionboolFalseLors de la reprise avec resume, bifurquer vers un nouvel ID de session au lieu de continuer la session originale
max_turnsint | NoneNoneTours de conversation maximum
disallowed_toolslist[str][]Liste des noms d’outils non autorisés
modelstr | NoneNoneModèle Claude à utiliser
permission_prompt_tool_namestr | NoneNoneNom d’outil MCP pour les prompts de permission
cwdstr | Path | NoneNoneRépertoire de travail actuel
settingsstr | NoneNoneChemin vers le fichier de paramètres
add_dirslist[str | Path][]Répertoires supplémentaires auxquels Claude peut accéder
extra_argsdict[str, str | None]{}Arguments CLI supplémentaires à passer directement au CLI
can_use_toolCanUseTool | NoneNoneFonction de rappel de permission d’outil
hooksdict[HookEvent, list[HookMatcher]] | NoneNoneConfigurations de hook pour intercepter les événements
agentsdict[str, AgentDefinition] | NoneNoneSous-agents définis programmatiquement
setting_sourceslist[SettingSource] | NoneNone (aucun paramètre)Contrôler quels paramètres du système de fichiers le SDK charge. Quand omis, aucun paramètre n’est chargé

SettingSource

Contrôle quelles sources de configuration basées sur le système de fichiers le SDK charge les paramètres.
SettingSource = Literal["user", "project", "local"]
ValeurDescriptionEmplacement
"user"Paramètres utilisateur globaux~/.claude/settings.json
"project"Paramètres de projet partagés (contrôlés par 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 aucun paramètre du système de fichiers. Cela fournit une isolation pour les applications SDK.

Pourquoi utiliser setting_sources ?

Charger tous les paramètres du système de fichiers (comportement hérité) :
# Charger tous les paramètres comme le SDK v0.0.x le faisait
from claude_agent_sdk import query, ClaudeAgentOptions

async for message in query(
    prompt="Analysez ce code",
    options=ClaudeAgentOptions(
        setting_sources=["user", "project", "local"]  # Charger tous les paramètres
    )
):
    print(message)
Charger seulement des sources de paramètres spécifiques :
# Charger seulement les paramètres de projet, ignorer utilisateur et local
async for message in query(
    prompt="Exécuter les vérifications CI",
    options=ClaudeAgentOptions(
        setting_sources=["project"]  # Seulement .claude/settings.json
    )
):
    print(message)
Environnements de test et CI :
# Assurer un comportement cohérent en CI en excluant les paramètres locaux
async for message in query(
    prompt="Exécuter les tests",
    options=ClaudeAgentOptions(
        setting_sources=["project"],  # Seulement les paramètres partagés d'équipe
        permission_mode="bypassPermissions"
    )
):
    print(message)
Applications SDK uniquement :
# Définir tout programmatiquement (comportement par défaut)
# Aucune dépendance du système de fichiers - setting_sources par défaut à None
async for message in query(
    prompt="Examiner cette PR",
    options=ClaudeAgentOptions(
        # setting_sources=None est le défaut, pas besoin de spécifier
        agents={ /* ... */ },
        mcp_servers={ /* ... */ },
        allowed_tools=["Read", "Grep", "Glob"]
    )
):
    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 programmatiquement.
@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 de modèle pour cet agent. Si omis, utilise le modèle principal

PermissionMode

Modes de permission pour contrôler l’exécution d’outils.
PermissionMode = Literal[
    "default",           # Comportement de permission standard
    "acceptEdits",       # Auto-accepter les modifications de fichiers
    "plan",              # Mode planification - pas d'exécution
    "bypassPermissions"  # Contourner toutes les vérifications de permission (utiliser avec prudence)
]

McpSdkServerConfig

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

McpServerConfig

Type union pour les configurations de serveur MCP.
McpServerConfig = McpStdioServerConfig | McpSSEServerConfig | McpHttpServerConfig | McpSdkServerConfig

McpStdioServerConfig

class McpStdioServerConfig(TypedDict):
    type: NotRequired[Literal["stdio"]]  # Optionnel pour la compatibilité descendante
    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]]

Types de messages

Message

Type 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 assistant avec 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 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’erreurs

ClaudeSDKError

Classe d’exception de base pour toutes les erreurs SDK.
class ClaudeSDKError(Exception):
    """Erreur de base pour 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 introuvable", cli_path: str | None = None):
        """
        Args:
            message: Message d'erreur (défaut: "Claude Code introuvable")
            cli_path: Chemin optionnel vers le CLI qui n'a pas été trouvé
        """

CLIConnectionError

Levée quand la connexion à Claude Code échoue.
class CLIConnectionError(ClaudeSDKError):
    """Échec de connexion à 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: La ligne qui a échoué à analyser
            original_error: L'exception de décodage JSON originale
        """
        self.line = line
        self.original_error = original_error

Types de hooks

HookEvent

Types d’événements de hook supportés. Notez qu’en raison de limitations de configuration, le SDK Python ne supporte pas les hooks SessionStart, SessionEnd et Notification.
HookEvent = Literal[
    "PreToolUse",      # Appelé avant l'exécution d'outil
    "PostToolUse",     # Appelé après l'exécution d'outil
    "UserPromptSubmit", # Appelé quand l'utilisateur soumet un prompt
    "Stop",            # Appelé lors de l'arrêt de l'exécution
    "SubagentStop",    # Appelé quand un sous-agent s'arrête
    "PreCompact"       # Appelé avant la compaction de message
]

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 de hook avec 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 passées aux rappels de hook.
@dataclass
class HookContext:
    signal: Any | None = None  # Futur : support du signal d'abandon

HookMatcher

Configuration pour faire correspondre les hooks à des événements ou outils spécifiques.
@dataclass
class HookMatcher:
    matcher: str | None = None        # Nom d'outil ou motif à faire correspondre (ex: "Bash", "Write|Edit")
    hooks: list[HookCallback] = field(default_factory=list)  # Liste des rappels à exécuter

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]:
    """Valider et potentiellement bloquer les commandes bash dangereuses."""
    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': 'Commande dangereuse bloquée'
                }
            }
    return {}

async def log_tool_use(
    input_data: dict[str, Any],
    tool_use_id: str | None,
    context: HookContext
) -> dict[str, Any]:
    """Enregistrer toute utilisation d'outil pour audit."""
    print(f"Outil utilisé : {input_data.get('tool_name')}")
    return {}

options = ClaudeAgentOptions(
    hooks={
        'PreToolUse': [
            HookMatcher(matcher='Bash', hooks=[validate_bash_command]),
            HookMatcher(hooks=[log_tool_use])  # S'applique à tous les outils
        ],
        'PostToolUse': [
            HookMatcher(hooks=[log_tool_use])
        ]
    }
)

async for message in query(
    prompt="Analysez cette base de code",
    options=options
):
    print(message)

Types d’entrée/sortie d’outils

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 ceux-ci comme types, ils représentent la structure des entrées et sorties d’outils dans les messages.

Task

Nom d’outil : Task Entrée :
{
    "description": str,      # Une description courte (3-5 mots) de la tâche
    "prompt": str,           # La tâche pour l'agent à effectuer
    "subagent_type": str     # Le type d'agent spécialisé à utiliser
}
Sortie :
{
    "result": str,                    # Résultat final du sous-agent
    "usage": dict | None,             # Statistiques d'utilisation de tokens
    "total_cost_usd": float | None,  # Coût total en USD
    "duration_ms": int | None         # Durée d'exécution en millisecondes
}

Bash

Nom d’outil : Bash Entrée :
{
    "command": str,                  # La commande à exécuter
    "timeout": int | None,           # Timeout optionnel en millisecondes (max 600000)
    "description": str | None,       # Description claire et concise (5-10 mots)
    "run_in_background": bool | None # Définir à true pour exécuter en arrière-plan
}
Sortie :
{
    "output": str,              # Sortie combinée stdout et stderr
    "exitCode": int,            # Code de sortie de la commande
    "killed": bool | None,      # Si la commande a été tuée à cause du timeout
    "shellId": str | None       # ID de shell pour les processus en arrière-plan
}

Edit

Nom d’outil : Edit Entrée :
{
    "file_path": str,           # Le chemin absolu vers le fichier à modifier
    "old_string": str,          # Le texte à remplacer
    "new_string": str,          # Le texte par lequel le remplacer
    "replace_all": bool | None  # Remplacer toutes les occurrences (défaut False)
}
Sortie :
{
    "message": str,      # Message de confirmation
    "replacements": int, # Nombre de remplacements effectués
    "file_path": str     # Chemin de fichier qui a été édité
}

MultiEdit

Nom d’outil : MultiEdit Entrée :
{
    "file_path": str,     # Le chemin absolu vers le fichier à modifier
    "edits": [            # Tableau d'opérations d'édition
        {
            "old_string": str,          # Le texte à remplacer
            "new_string": str,          # Le texte par lequel le remplacer
            "replace_all": bool | None  # Remplacer toutes les occurrences
        }
    ]
}
Sortie :
{
    "message": str,       # Message de succès
    "edits_applied": int, # Nombre total d'éditions appliquées
    "file_path": str      # Chemin de fichier qui a été édité
}

Read

Nom d’outil : Read Entrée :
{
    "file_path": str,       # Le chemin absolu vers le fichier à lire
    "offset": int | None,   # Le numéro de ligne à partir duquel commencer la lecture
    "limit": int | None     # Le nombre de lignes à lire
}
Sortie (Fichiers texte) :
{
    "content": str,         # Contenu du fichier avec numéros de ligne
    "total_lines": int,     # Nombre total de lignes dans le fichier
    "lines_returned": int   # Lignes réellement retournées
}
Sortie (Images) :
{
    "image": str,       # Données d'image encodées en base64
    "mime_type": str,   # Type MIME de l'image
    "file_size": int    # Taille du fichier en octets
}

Write

Nom d’outil : Write Entrée :
{
    "file_path": str,  # Le chemin absolu vers le fichier à écrire
    "content": str     # Le contenu à écrire dans le fichier
}
Sortie :
{
    "message": str,        # Message de succès
    "bytes_written": int,  # Nombre d'octets écrits
    "file_path": str       # Chemin de fichier qui a été écrit
}

Glob

Nom d’outil : Glob Entrée :
{
    "pattern": str,       # Le motif glob pour faire correspondre les fichiers
    "path": str | None    # Le répertoire dans lequel chercher (par défaut cwd)
}
Sortie :
{
    "matches": list[str],  # Tableau des chemins de fichiers correspondants
    "count": int,          # Nombre de correspondances trouvées
    "search_path": str     # Répertoire de recherche utilisé
}

Grep

Nom d’outil : Grep Entrée :
{
    "pattern": str,                    # Le motif d'expression régulière
    "path": str | None,                # Fichier ou répertoire dans lequel chercher
    "glob": str | None,                # Motif glob pour filtrer les fichiers
    "type": str | None,                # Type de fichier à chercher
    "output_mode": str | None,         # "content", "files_with_matches", ou "count"
    "-i": bool | None,                 # Recherche insensible à la casse
    "-n": bool | None,                 # Afficher les numéros de ligne
    "-B": int | None,                  # Lignes à afficher avant chaque correspondance
    "-A": int | None,                  # Lignes à afficher après chaque correspondance
    "-C": int | None,                  # Lignes à afficher avant et après
    "head_limit": int | None,          # Limiter la sortie aux N premières lignes/entrées
    "multiline": bool | None           # Activer le mode multiligne
}
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],  # Fichiers contenant des correspondances
    "count": int         # Nombre de fichiers avec correspondances
}

NotebookEdit

Nom d’outil : NotebookEdit Entrée :
{
    "notebook_path": str,                     # Chemin absolu vers le notebook Jupyter
    "cell_id": str | None,                    # L'ID de la cellule à éditer
    "new_source": str,                        # La nouvelle source pour la cellule
    "cell_type": "code" | "markdown" | None,  # Le type de la cellule
    "edit_mode": "replace" | "insert" | "delete" | None  # Type d'opération d'édition
}
Sortie :
{
    "message": str, # Message de succès
    "edit_type": "replaced" | "inserted" | "deleted",  # Type d'édition effectuée
    "cell_id": str | None,                       # ID de cellule qui a été affectée
    "total_cells": int                           # Total de cellules dans le notebook après édition
}

WebFetch

Nom d’outil : WebFetch Entrée :
{
    "url": str,     # L'URL pour récupérer le contenu
    "prompt": str   # Le prompt à exécuter sur le contenu récupéré
}
Sortie :
{
    "response": str,           # Réponse du modèle IA au prompt
    "url": str,                # URL qui a été récupérée
    "final_url": str | None,   # URL finale après redirections
    "status_code": int | None  # Code de statut HTTP
}

WebSearch

Nom d’outil : WebSearch Entrée :
{
    "query": str,                        # La requête de recherche à utiliser
    "allowed_domains": list[str] | None, # Inclure seulement les résultats de ces domaines
    "blocked_domains": list[str] | None  # Ne jamais inclure les résultats de ces domaines
}
Sortie :
{
    "results": [
        {
            "title": str,
            "url": str,
            "snippet": str,
            "metadata": dict | None
        }
    ],
    "total_results": int,
    "query": str
}

TodoWrite

Nom d’outil : TodoWrite Entrée :
{
    "todos": [
        {
            "content": str, # La description de la tâche
            "status": "pending" | "in_progress" | "completed",  # Statut de la tâche
            "activeForm": str                            # Forme active de la description
        }
    ]
}
Sortie :
{
    "message": str,  # Message de succès
    "stats": {
        "total": int,
        "pending": int,
        "in_progress": int,
        "completed": int
    }
}

BashOutput

Nom d’outil : BashOutput Entrée :
{
    "bash_id": str,       # L'ID du shell en arrière-plan
    "filter": str | None  # Regex optionnel pour filtrer les lignes de sortie
}
Sortie :
{
    "output": str, # Nouvelle sortie depuis la dernière vérification
    "status": "running" | "completed" | "failed",       # Statut actuel du shell
    "exitCode": int | None # Code de sortie quand terminé
}

KillBash

Nom d’outil : KillBash Entrée :
{
    "shell_id": str  # L'ID du shell en arrière-plan à tuer
}
Sortie :
{
    "message": str,  # Message de succès
    "shell_id": str  # ID du shell tué
}

ExitPlanMode

Nom d’outil : ExitPlanMode Entrée :
{
    "plan": str  # Le plan à exécuter par l'utilisateur pour approbation
}
Sortie :
{
    "message": str,          # Message de confirmation
    "approved": bool | None  # Si l'utilisateur a approuvé le plan
}

ListMcpResources

Nom d’outil : ListMcpResources Entrée :
{
    "server": str | None  # Nom de serveur optionnel pour filtrer les ressources
}
Sortie :
{
    "resources": [
        {
            "uri": str,
            "name": str,
            "description": str | None,
            "mimeType": str | None,
            "server": str
        }
    ],
    "total": int
}

ReadMcpResource

Nom d’outil : ReadMcpResource Entrée :
{
    "server": str,  # Le nom du serveur MCP
    "uri": str      # L'URI de ressource à lire
}
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, ClaudeCodeOptions, AssistantMessage, TextBlock
import asyncio

class ConversationSession:
    """Maintient une session de conversation unique avec Claude."""
    
    def __init__(self, options: ClaudeCodeOptions = None):
        self.client = ClaudeSDKClient(options)
        self.turn_count = 0
    
    async def start(self):
        await self.client.connect()
        print("Démarrage de la session de conversation. Claude se souviendra du contexte.")
        print("Commandes : 'exit' pour quitter, 'interrupt' pour arrêter la tâche actuelle, 'new' pour nouvelle session")
        
        while True:
            user_input = input(f"\n[Tour {self.turn_count + 1}] Vous : ")
            
            if user_input.lower() == 'exit':
                break
            elif user_input.lower() == 'interrupt':
                await self.client.interrupt()
                print("Tâche interrompue !")
                continue
            elif user_input.lower() == 'new':
                # Déconnecter et reconnecter pour une session fraîche
                await self.client.disconnect()
                await self.client.connect()
                self.turn_count = 0
                print("Nouvelle session de conversation démarrée (contexte précédent effacé)")
                continue
            
            # Envoyer message - Claude se souvient de tous les messages précédents dans cette session
            await self.client.query(user_input)
            self.turn_count += 1
            
            # Traiter la réponse
            print(f"[Tour {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()  # Nouvelle ligne après la réponse
        
        await self.client.disconnect()
        print(f"Conversation terminée après {self.turn_count} tours.")

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

# Exemple de conversation :
# Tour 1 - Vous : "Créez un fichier appelé hello.py"
# Tour 1 - Claude : "Je vais créer un fichier hello.py pour vous..."
# Tour 2 - Vous : "Qu'y a-t-il dans ce fichier ?"  
# Tour 2 - Claude : "Le fichier hello.py que je viens de créer contient..." (se souvient !)
# Tour 3 - Vous : "Ajoutez-y une fonction main"
# Tour 3 - Claude : "Je vais ajouter une fonction main à hello.py..." (sait quel fichier !)

asyncio.run(main())

Utiliser les hooks pour la modification de 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]:
    """Enregistrer toute utilisation d'outil avant l'exécution."""
    tool_name = input_data.get('tool_name', 'inconnu')
    print(f"[PRE-OUTIL] Sur le point d'utiliser : {tool_name}")

    # Vous pouvez modifier ou bloquer l'exécution d'outil ici
    if tool_name == "Bash" and "rm -rf" in str(input_data.get('tool_input', {})):
        return {
            'hookSpecificOutput': {
                'hookEventName': 'PreToolUse',
                'permissionDecision': 'deny',
                'permissionDecisionReason': 'Commande dangereuse bloquée'
            }
        }
    return {}

async def post_tool_logger(
    input_data: dict[str, Any],
    tool_use_id: str | None,
    context: HookContext
) -> dict[str, Any]:
    """Enregistrer les résultats après l'exécution d'outil."""
    tool_name = input_data.get('tool_name', 'inconnu')
    print(f"[POST-OUTIL] Terminé : {tool_name}")
    return {}

async def user_prompt_modifier(
    input_data: dict[str, Any],
    tool_use_id: str | None,
    context: HookContext
) -> dict[str, Any]:
    """Ajouter du contexte aux prompts utilisateur."""
    original_prompt = input_data.get('prompt', '')

    # Ajouter un horodatage à tous les 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("Listez les fichiers dans le répertoire actuel")
        
        async for message in client.receive_response():
            # Les hooks enregistreront automatiquement l'utilisation d'outils
            pass

asyncio.run(main())

Surveillance de 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(
            "Créez 5 fichiers Python avec différents algorithmes de tri"
        )
        
        # Surveiller la progression en temps réel
        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"🔨 Création : {file_path}")
                    elif isinstance(block, ToolResultBlock):
                        print(f"✅ Exécution d'outil terminée")
                    elif isinstance(block, TextBlock):
                        print(f"💭 Claude dit : {block.text[:100]}...")
            
            # Vérifier si nous avons reçu le résultat final
            if hasattr(message, 'subtype') and message.subtype in ['success', 'error']:
                print(f"\n🎯 Tâche terminée !")
                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="Créez une structure de projet Python avec setup.py",
        options=options
    ):
        if isinstance(message, AssistantMessage):
            for block in message.content:
                if isinstance(block, ToolUseBlock):
                    print(f"Utilisation d'outil : {block.name}")

asyncio.run(create_project())

Gestion d’erreurs

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

try:
    async for message in query(prompt="Bonjour"):
        print(message)
except CLINotFoundError:
    print("Veuillez installer Claude Code : npm install -g @anthropic-ai/claude-code")
except ProcessError as e:
    print(f"Processus échoué avec code de sortie : {e.exit_code}")
except CLIJSONDecodeError as e:
    print(f"Échec d'analyse de la réponse : {e}")

Mode streaming avec client

from claude_agent_sdk import ClaudeSDKClient
import asyncio

async def interactive_session():
    async with ClaudeSDKClient() as client:
        # Envoyer message initial
        await client.query("Quel temps fait-il ?")
        
        # Traiter les réponses
        async for msg in client.receive_response():
            print(msg)
        
        # Envoyer suivi
        await client.query("Dites-moi en plus à ce sujet")
        
        # Traiter la réponse de suivi
        async for msg in client.receive_response():
            print(msg)

asyncio.run(interactive_session())

Utiliser des 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

# Définir des outils personnalisés avec le décorateur @tool
@tool("calculate", "Effectuer des calculs mathématiques", {"expression": str})
async def calculate(args: dict[str, Any]) -> dict[str, Any]:
    try:
        result = eval(args["expression"], {"__builtins__": {}})
        return {
            "content": [{
                "type": "text",
                "text": f"Résultat : {result}"
            }]
        }
    except Exception as e:
        return {
            "content": [{
                "type": "text",
                "text": f"Erreur : {str(e)}"
            }],
            "is_error": True
        }

@tool("get_time", "Obtenir l'heure actuelle", {})
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"Heure actuelle : {current_time}"
        }]
    }

async def main():
    # Créer un serveur SDK MCP avec des outils personnalisés
    my_server = create_sdk_mcp_server(
        name="utilities",
        version="1.0.0",
        tools=[calculate, get_time]
    )

    # Configurer les options avec le serveur
    options = ClaudeAgentOptions(
        mcp_servers={"utils": my_server},
        allowed_tools=[
            "mcp__utils__calculate",
            "mcp__utils__get_time"
        ]
    )
    
    # Utiliser ClaudeSDKClient pour l'utilisation interactive d'outils
    async with ClaudeSDKClient(options=options) as client:
        await client.query("Combien font 123 * 456 ?")
        
        # Traiter la réponse de calcul
        async for message in client.receive_response():
            if isinstance(message, AssistantMessage):
                for block in message.content:
                    if isinstance(block, TextBlock):
                        print(f"Calcul : {block.text}")
        
        # Suivi avec requête d'heure
        await client.query("Quelle heure est-il maintenant ?")
        
        async for message in client.receive_response():
            if isinstance(message, AssistantMessage):
                for block in message.content:
                    if isinstance(block, TextBlock):
                        print(f"Heure : {block.text}")

asyncio.run(main())

Voir aussi