CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-claude-agent-sdk

Python SDK for programmatically interacting with Claude Code, enabling AI-powered automation workflows with support for bidirectional conversations, custom in-process tools, hooks, and fine-grained permission control

Quality

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

The risk profile of this skill

This plugin was archived by the owner on Jun 3, 2026

Reason: Retiring all tiles created prior to the transition to plugin support

Overview
Eval results
Files

client.mddocs/

Interactive Client

The ClaudeSDKClient class provides full control over conversation flow with support for bidirectional communication, streaming, interrupts, and dynamic message sending. It's ideal for chat interfaces, interactive debugging, and multi-turn conversations.

Capabilities

ClaudeSDKClient Class

Client for bidirectional, interactive conversations with Claude Code. Maintains conversation state and allows sending messages at any time based on responses.

class ClaudeSDKClient:
    """
    Client for bidirectional, interactive conversations with Claude Code.

    This client provides full control over the conversation flow with support
    for streaming, interrupts, and dynamic message sending. For simple one-shot
    queries, consider using the query() function instead.

    Key features:
    - Bidirectional: Send and receive messages at any time
    - Stateful: Maintains conversation context across messages
    - Interactive: Send follow-ups based on responses
    - Control flow: Support for interrupts and session management

    When to use ClaudeSDKClient:
    - Building chat interfaces or conversational UIs
    - Interactive debugging or exploration sessions
    - Multi-turn conversations with context
    - When you need to react to Claude's responses
    - Real-time applications with user input
    - When you need interrupt capabilities

    When to use query() instead:
    - Simple one-off questions
    - Batch processing of prompts
    - Fire-and-forget automation scripts
    - When all inputs are known upfront
    - Stateless operations

    Caveat: As of v0.0.20, you cannot use a ClaudeSDKClient instance across
    different async runtime contexts (e.g., different trio nurseries or asyncio
    task groups). The client internally maintains a persistent anyio task group
    for reading messages that remains active from connect() until disconnect().
    This means you must complete all operations with the client within the same
    async context where it was connected.
    """

    def __init__(
        self,
        options: ClaudeAgentOptions | None = None,
        transport: Transport | None = None,
    ):
        """
        Initialize Claude SDK client.

        Args:
            options: Optional configuration (ClaudeAgentOptions instance)
            transport: Optional custom transport implementation
        """

    async def connect(
        self, prompt: str | AsyncIterable[dict[str, Any]] | None = None
    ) -> None:
        """
        Connect to Claude with a prompt or message stream.

        Args:
            prompt: Optional initial prompt (string or AsyncIterable of message dicts)
                   If None, connects without sending initial messages (for interactive use)
        """

    async def receive_messages(self) -> AsyncIterator[Message]:
        """
        Receive all messages from Claude.

        Yields:
            Message objects (UserMessage, AssistantMessage, SystemMessage,
            ResultMessage, or StreamEvent)
        """

    async def query(
        self, prompt: str | AsyncIterable[dict[str, Any]], session_id: str = "default"
    ) -> None:
        """
        Send a new request in streaming mode.

        Args:
            prompt: Either a string message or an async iterable of message dictionaries
            session_id: Session identifier for the conversation (default: "default")
        """

    async def interrupt(self) -> None:
        """
        Send interrupt signal (only works with streaming mode).

        Interrupts the current Claude operation, stopping generation and
        tool execution.
        """

    async def set_permission_mode(self, mode: str) -> None:
        """
        Change permission mode during conversation (only works with streaming mode).

        Args:
            mode: The permission mode to set. Valid options:
                - 'default': CLI prompts for dangerous tools
                - 'acceptEdits': Auto-accept file edits
                - 'bypassPermissions': Allow all tools (use with caution)
        """

    async def set_model(self, model: str | None = None) -> None:
        """
        Change the AI model during conversation (only works with streaming mode).

        Args:
            model: The model to use, or None to use default. Examples:
                - 'claude-sonnet-4-5'
                - 'claude-opus-4-1-20250805'
                - 'claude-opus-4-20250514'
        """

    async def get_server_info(self) -> dict[str, Any] | None:
        """
        Get server initialization info including available commands and output styles.

        Returns initialization information from the Claude Code server including:
        - Available commands (slash commands, system commands, etc.)
        - Current and available output styles
        - Server capabilities

        Returns:
            Dictionary with server info, or None if not in streaming mode
        """

    async def receive_response(self) -> AsyncIterator[Message]:
        """
        Receive messages from Claude until and including a ResultMessage.

        This async iterator yields all messages in sequence and automatically terminates
        after yielding a ResultMessage (which indicates the response is complete).
        It's a convenience method over receive_messages() for single-response workflows.

        Stopping Behavior:
        - Yields each message as it's received
        - Terminates immediately after yielding a ResultMessage
        - The ResultMessage IS included in the yielded messages
        - If no ResultMessage is received, the iterator continues indefinitely

        Yields:
            Message objects (each message received until ResultMessage)

        Note:
            To collect all messages: messages = [msg async for msg in client.receive_response()]
            The final message in the list will always be a ResultMessage.
        """

    async def disconnect(self) -> None:
        """
        Disconnect from Claude.

        Closes the connection and cleans up resources.
        """

    async def __aenter__(self) -> "ClaudeSDKClient":
        """
        Enter async context - automatically connects with empty stream for interactive use.

        Returns:
            Self (the ClaudeSDKClient instance)
        """

    async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> bool:
        """
        Exit async context - always disconnects.

        Returns:
            False (does not suppress exceptions)
        """

Usage Examples

Basic Interactive Session

import anyio
from claude_agent_sdk import ClaudeSDKClient, AssistantMessage, TextBlock

async def main():
    async with ClaudeSDKClient() as client:
        await client.query("What is the capital of France?")

        async for msg in client.receive_response():
            if isinstance(msg, AssistantMessage):
                for block in msg.content:
                    if isinstance(block, TextBlock):
                        print(f"Claude: {block.text}")

anyio.run(main)

Multi-Turn Conversation

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

async def main():
    async with ClaudeSDKClient() as client:
        # First question
        await client.query("What is Python?")
        async for msg in client.receive_response():
            if isinstance(msg, AssistantMessage):
                for block in msg.content:
                    if isinstance(block, TextBlock):
                        print(f"Claude: {block.text}")

        # Follow-up question
        await client.query("Can you show me a simple Python example?")
        async for msg in client.receive_response():
            if isinstance(msg, AssistantMessage):
                for block in msg.content:
                    if isinstance(block, TextBlock):
                        print(f"Claude: {block.text}")

anyio.run(main)

Using with Options

import anyio
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions

async def main():
    options = ClaudeAgentOptions(
        system_prompt="You are a helpful Python expert",
        allowed_tools=["Read", "Bash"],
        permission_mode='default',
        cwd="/home/user/project"
    )

    async with ClaudeSDKClient(options=options) as client:
        await client.query("Analyze the Python files in this directory")

        async for msg in client.receive_response():
            print(msg)

anyio.run(main)

Interrupting Execution

import anyio
from claude_agent_sdk import ClaudeSDKClient, AssistantMessage

async def main():
    async with ClaudeSDKClient() as client:
        await client.query("Generate 1000 lines of code")

        # Interrupt after a short delay
        async def interrupt_after_delay():
            await anyio.sleep(2)
            await client.interrupt()

        async with anyio.create_task_group() as tg:
            tg.start_soon(interrupt_after_delay)

            async for msg in client.receive_messages():
                if isinstance(msg, AssistantMessage):
                    print("Received message before interrupt")

anyio.run(main)

Dynamic Permission Mode

import anyio
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions

async def main():
    options = ClaudeAgentOptions(
        allowed_tools=["Read", "Write", "Edit"],
        permission_mode='default'  # Start with default (ask for permissions)
    )

    async with ClaudeSDKClient(options=options) as client:
        # Review mode - ask for permissions
        await client.query("Help me analyze this codebase")
        async for msg in client.receive_response():
            print(msg)

        # Implementation mode - auto-accept edits
        await client.set_permission_mode('acceptEdits')
        await client.query("Now implement the fix we discussed")
        async for msg in client.receive_response():
            print(msg)

anyio.run(main)

Switching Models

import anyio
from claude_agent_sdk import ClaudeSDKClient

async def main():
    async with ClaudeSDKClient() as client:
        # Start with default model
        await client.query("Explain quantum computing briefly")
        async for msg in client.receive_response():
            print(msg)

        # Switch to a specific model for detailed implementation
        await client.set_model('claude-sonnet-4-5')
        await client.query("Now implement a quantum circuit simulator")
        async for msg in client.receive_response():
            print(msg)

anyio.run(main)

Getting Server Info

import anyio
from claude_agent_sdk import ClaudeSDKClient

async def main():
    async with ClaudeSDKClient() as client:
        info = await client.get_server_info()
        if info:
            print(f"Available commands: {len(info.get('commands', []))}")
            print(f"Output style: {info.get('output_style', 'default')}")
            print(f"Server capabilities: {info.get('capabilities', {})}")

anyio.run(main)

Manual Connection Management

import anyio
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions

async def main():
    options = ClaudeAgentOptions(
        allowed_tools=["Read", "Write"]
    )

    client = ClaudeSDKClient(options=options)

    try:
        # Manual connect
        await client.connect()

        await client.query("Hello Claude")
        async for msg in client.receive_response():
            print(msg)

    finally:
        # Manual disconnect
        await client.disconnect()

anyio.run(main)

Collecting All Messages

import anyio
from claude_agent_sdk import ClaudeSDKClient, ResultMessage

async def main():
    async with ClaudeSDKClient() as client:
        await client.query("What is 2 + 2?")

        # Collect all messages into a list
        messages = [msg async for msg in client.receive_response()]

        # Last message is always ResultMessage
        result = messages[-1]
        if isinstance(result, ResultMessage):
            print(f"Total cost: ${result.total_cost_usd:.4f}")
            print(f"Number of turns: {result.num_turns}")
            print(f"Duration: {result.duration_ms}ms")

anyio.run(main)

Streaming with AsyncIterable Prompts

import anyio
from claude_agent_sdk import ClaudeSDKClient

async def main():
    async def message_stream():
        yield {"type": "user", "message": {"role": "user", "content": "First question"}, "session_id": "1"}
        await anyio.sleep(1)
        yield {"type": "user", "message": {"role": "user", "content": "Follow up"}, "session_id": "1"}

    async with ClaudeSDKClient() as client:
        await client.query(message_stream())

        async for msg in client.receive_messages():
            print(msg)

anyio.run(main)

Using Custom Transport

import anyio
from claude_agent_sdk import ClaudeSDKClient, Transport

class MyCustomTransport(Transport):
    # Implement custom transport logic
    async def connect(self) -> None:
        pass

    async def write(self, data: str) -> None:
        pass

    def read_messages(self):
        pass

    async def close(self) -> None:
        pass

    def is_ready(self) -> bool:
        return True

    async def end_input(self) -> None:
        pass

async def main():
    transport = MyCustomTransport()
    client = ClaudeSDKClient(transport=transport)

    async with client:
        await client.query("Hello")
        async for msg in client.receive_response():
            print(msg)

anyio.run(main)

Error Handling

import anyio
from claude_agent_sdk import (
    ClaudeSDKClient,
    ClaudeSDKError,
    CLINotFoundError,
    CLIConnectionError
)

async def main():
    try:
        async with ClaudeSDKClient() as client:
            await client.query("Hello Claude")
            async for msg in client.receive_response():
                print(msg)
    except CLINotFoundError:
        print("Please install Claude Code")
    except CLIConnectionError as e:
        print(f"Connection error: {e}")
    except ClaudeSDKError as e:
        print(f"SDK error: {e}")

anyio.run(main)

docs

agent-definitions.md

agents.md

client.md

configuration-options.md

content-blocks.md

core-query-interface.md

custom-tools.md

error-handling.md

errors.md

hook-system.md

hooks.md

index.md

mcp-config.md

mcp-server-configuration.md

messages-and-content.md

messages.md

options.md

permission-control.md

permissions.md

query.md

transport.md

COMPLETION_SUMMARY.md

tile.json