Python SDK for Claude Code enabling developers to build AI-powered applications and agents with support for custom tools, hooks, and bidirectional interactive conversations
Strongly-typed message system with discriminated unions for all message types and content blocks. Provides type safety for message handling, pattern matching, and content extraction.
Union type representing all possible message types.
Message = UserMessage | AssistantMessage | SystemMessage | ResultMessage | StreamEventUser-originated message in the conversation.
@dataclass
class UserMessage:
content: str | list[ContentBlock]
uuid: str | None = None
parent_tool_use_id: str | None = None
tool_use_result: dict[str, Any] | None = NoneFields:
content: Message content as string or list of content blocksuuid: Message UUID (only present with replay-user-messages flag)parent_tool_use_id: Parent tool use identifier for tool resultstool_use_result: Tool execution result if this is a tool responseUsage:
from claude_agent_sdk import UserMessage, TextBlock
# String content
user_msg = UserMessage(
content="Hello Claude",
uuid="msg-123"
)
# Block content
user_msg = UserMessage(
content=[TextBlock(text="Hello Claude")],
uuid="msg-123"
)
# Tool result message
user_msg = UserMessage(
content="Tool completed successfully",
parent_tool_use_id="tool-456",
tool_use_result={"status": "success"}
)Claude's response message containing content blocks.
@dataclass
class AssistantMessage:
content: list[ContentBlock]
model: str
parent_tool_use_id: str | None = None
error: AssistantMessageError | None = NoneFields:
content: List of content blocks (TextBlock, ThinkingBlock, ToolUseBlock, ToolResultBlock)model: Model identifier that generated this messageparent_tool_use_id: Parent tool use identifier if nestederror: Error type if message represents an errorError Types:
AssistantMessageError = Literal[
"authentication_failed",
"billing_error",
"rate_limit",
"invalid_request",
"server_error",
"unknown"
]"authentication_failed" - API key or authentication credentials are invalid"billing_error" - Account has billing issues or insufficient credits"rate_limit" - API rate limit has been exceeded"invalid_request" - Request parameters are invalid or malformed"server_error" - Anthropic API server encountered an error"unknown" - An unclassified error occurredUsage:
from claude_agent_sdk import AssistantMessage, TextBlock, ToolUseBlock
# Process assistant messages
async for message in query(prompt="Hello"):
if isinstance(message, AssistantMessage):
print(f"Model: {message.model}")
for block in message.content:
if isinstance(block, TextBlock):
print(f"Text: {block.text}")
elif isinstance(block, ToolUseBlock):
print(f"Tool: {block.name}, Input: {block.input}")
if message.error:
print(f"Error: {message.error}")System-level message with metadata.
@dataclass
class SystemMessage:
subtype: str
data: dict[str, Any]Fields:
subtype: Message subtype identifierdata: Message data dictionaryUsage:
from claude_agent_sdk import SystemMessage
async for message in query(prompt="Hello"):
if isinstance(message, SystemMessage):
print(f"System {message.subtype}: {message.data}")Final result message with cost and usage metrics.
@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
structured_output: Any = NoneFields:
subtype: Result subtypeduration_ms: Total duration in millisecondsduration_api_ms: API call duration in millisecondsis_error: Whether result represents an errornum_turns: Number of conversation turnssession_id: Session identifiertotal_cost_usd: Total cost in USD (if available)usage: Usage metrics dictionary with keys like input_tokens, output_tokens, total_tokens, and potentially cache_creation_input_tokens, cache_read_input_tokens for prompt cachingresult: Result text (if any)structured_output: Structured output if requested via output_formatUsage:
from claude_agent_sdk import ResultMessage
async for message in query(prompt="Calculate 2+2"):
if isinstance(message, ResultMessage):
print(f"Completed in {message.duration_ms}ms")
print(f"Turns: {message.num_turns}")
print(f"Session: {message.session_id}")
if message.total_cost_usd:
print(f"Cost: ${message.total_cost_usd:.4f}")
if message.usage:
print(f"Tokens: {message.usage.get('total_tokens', 0)}")
if message.is_error:
print(f"Error result: {message.result}")
if message.structured_output:
print(f"Structured: {message.structured_output}")Partial message update during streaming (when include_partial_messages=True).
@dataclass
class StreamEvent:
uuid: str
session_id: str
event: dict[str, Any]
parent_tool_use_id: str | None = NoneFields:
uuid: Event UUIDsession_id: Session identifierevent: Raw Anthropic API stream eventparent_tool_use_id: Parent tool use identifier if nestedUsage:
from claude_agent_sdk import query, ClaudeAgentOptions, StreamEvent
options = ClaudeAgentOptions(
include_partial_messages=True
)
async for message in query(prompt="Tell me a story", options=options):
if isinstance(message, StreamEvent):
event_type = message.event.get("type")
print(f"Stream event: {event_type}")
if event_type == "content_block_delta":
delta = message.event.get("delta", {})
if delta.get("type") == "text_delta":
print(delta.get("text", ""), end="", flush=True)Union type representing all content block types.
ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock | ToolResultBlockText content block.
@dataclass
class TextBlock:
text: strUsage:
from claude_agent_sdk import AssistantMessage, TextBlock
async for message in query(prompt="Hello"):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(f"Claude says: {block.text}")Extended thinking content block (requires extended thinking model feature).
@dataclass
class ThinkingBlock:
thinking: str
signature: strFields:
thinking: Thinking contentsignature: Thinking signatureUsage:
from claude_agent_sdk import AssistantMessage, ThinkingBlock
async for message in query(prompt="Solve this problem"):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, ThinkingBlock):
print(f"Claude's thinking: {block.thinking}")
print(f"Signature: {block.signature}")Tool invocation block indicating Claude wants to use a tool.
@dataclass
class ToolUseBlock:
id: str
name: str
input: dict[str, Any]Fields:
id: Unique tool use identifiername: Tool nameinput: Tool input parametersUsage:
from claude_agent_sdk import AssistantMessage, ToolUseBlock
async for message in query(
prompt="Read the file config.json",
options=ClaudeAgentOptions(allowed_tools=["Read"])
):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, ToolUseBlock):
print(f"Tool: {block.name}")
print(f"ID: {block.id}")
print(f"Input: {block.input}")
if block.name == "Read":
file_path = block.input.get("file_path")
print(f"Reading: {file_path}")Tool execution result block.
@dataclass
class ToolResultBlock:
tool_use_id: str
content: str | list[dict[str, Any]] | None = None
is_error: bool | None = NoneFields:
tool_use_id: Tool use identifier this result corresponds tocontent: Result content as string, list of content dicts, or Noneis_error: Whether this result represents an errorUsage:
from claude_agent_sdk import UserMessage, ToolResultBlock
async for message in query(prompt="Read config.json"):
if isinstance(message, UserMessage):
if isinstance(message.content, list):
for block in message.content:
if isinstance(block, ToolResultBlock):
print(f"Tool result for: {block.tool_use_id}")
print(f"Content: {block.content}")
if block.is_error:
print("Tool execution failed")Use isinstance checks for type-safe message handling:
from claude_agent_sdk import (
query,
AssistantMessage,
UserMessage,
SystemMessage,
ResultMessage,
StreamEvent,
TextBlock,
ToolUseBlock,
ToolResultBlock
)
async for message in query(prompt="Hello"):
match message:
case AssistantMessage():
for block in message.content:
match block:
case TextBlock(text=text):
print(f"Claude: {text}")
case ToolUseBlock(name=name, input=input):
print(f"Using tool {name}: {input}")
case UserMessage(content=content):
print(f"User: {content}")
case SystemMessage(subtype=subtype, data=data):
print(f"System {subtype}: {data}")
case ResultMessage() as result:
print(f"Completed: {result.duration_ms}ms, Cost: ${result.total_cost_usd}")
case StreamEvent():
print(f"Stream event: {message.event}")Extract all text from assistant messages:
from claude_agent_sdk import AssistantMessage, TextBlock
async def get_assistant_text(prompt: str) -> list[str]:
texts = []
async for message in query(prompt=prompt):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
texts.append(block.text)
return texts
texts = await get_assistant_text("What is 2+2?")
print(" ".join(texts))Track all tools used in a conversation:
from claude_agent_sdk import AssistantMessage, ToolUseBlock
async def track_tools(prompt: str) -> list[dict]:
tools_used = []
async for message in query(prompt=prompt):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, ToolUseBlock):
tools_used.append({
"id": block.id,
"name": block.name,
"input": block.input
})
return tools_used
tools = await track_tools("Read all Python files and summarize them")
for tool in tools:
print(f"Used {tool['name']}: {tool['input']}")import anyio
from claude_agent_sdk import (
query,
ClaudeAgentOptions,
AssistantMessage,
UserMessage,
ResultMessage,
TextBlock,
ToolUseBlock,
ToolResultBlock
)
async def process_conversation(prompt: str):
options = ClaudeAgentOptions(
allowed_tools=["Read", "Write"],
permission_mode="acceptEdits"
)
async for message in query(prompt=prompt, options=options):
if isinstance(message, AssistantMessage):
print(f"\n[Assistant - {message.model}]")
if message.error:
print(f"ERROR: {message.error}")
continue
for block in message.content:
if isinstance(block, TextBlock):
print(block.text)
elif isinstance(block, ToolUseBlock):
print(f"\n→ Using tool: {block.name}")
print(f" ID: {block.id}")
print(f" Input: {block.input}")
elif isinstance(message, UserMessage):
if isinstance(message.content, str):
print(f"\n[User] {message.content}")
elif isinstance(message.content, list):
for block in message.content:
if isinstance(block, ToolResultBlock):
status = "ERROR" if block.is_error else "SUCCESS"
print(f"\n[Tool Result - {status}]")
print(f" For: {block.tool_use_id}")
if block.content:
print(f" Content: {block.content}")
elif isinstance(message, ResultMessage):
print(f"\n[Result]")
print(f" Duration: {message.duration_ms}ms")
print(f" Turns: {message.num_turns}")
print(f" Session: {message.session_id}")
if message.total_cost_usd:
print(f" Cost: ${message.total_cost_usd:.4f}")
if message.is_error:
print(f" Error: {message.result}")
anyio.run(lambda: process_conversation("Analyze the codebase"))Install with Tessl CLI
npx tessl i tessl/pypi-claude-agent-sdk@0.1.3