Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:00:18 +08:00
commit 765529cd13
69 changed files with 18291 additions and 0 deletions

View File

@@ -0,0 +1,124 @@
#!/usr/bin/env python3
"""Example of using custom agents with Claude Code SDK.
This example demonstrates how to define and use custom agents with specific
tools, prompts, and models.
Usage:
./examples/agents.py - Run the example
"""
import anyio
from claude_agent_sdk import (
AgentDefinition,
AssistantMessage,
ClaudeAgentOptions,
ResultMessage,
TextBlock,
query,
)
async def code_reviewer_example():
"""Example using a custom code reviewer agent."""
print("=== Code Reviewer Agent Example ===")
options = ClaudeAgentOptions(
agents={
"code-reviewer": AgentDefinition(
description="Reviews code for best practices and potential issues",
prompt="You are a code reviewer. Analyze code for bugs, performance issues, "
"security vulnerabilities, and adherence to best practices. "
"Provide constructive feedback.",
tools=["Read", "Grep"],
model="sonnet",
),
},
)
async for message in query(
prompt="Use the code-reviewer agent to review the code in src/claude_agent_sdk/types.py",
options=options,
):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}")
elif isinstance(message, ResultMessage) and message.total_cost_usd and message.total_cost_usd > 0:
print(f"\nCost: ${message.total_cost_usd:.4f}")
print()
async def documentation_writer_example():
"""Example using a documentation writer agent."""
print("=== Documentation Writer Agent Example ===")
options = ClaudeAgentOptions(
agents={
"doc-writer": AgentDefinition(
description="Writes comprehensive documentation",
prompt="You are a technical documentation expert. Write clear, comprehensive "
"documentation with examples. Focus on clarity and completeness.",
tools=["Read", "Write", "Edit"],
model="sonnet",
),
},
)
async for message in query(
prompt="Use the doc-writer agent to explain what AgentDefinition is used for",
options=options,
):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}")
elif isinstance(message, ResultMessage) and message.total_cost_usd and message.total_cost_usd > 0:
print(f"\nCost: ${message.total_cost_usd:.4f}")
print()
async def multiple_agents_example():
"""Example with multiple custom agents."""
print("=== Multiple Agents Example ===")
options = ClaudeAgentOptions(
agents={
"analyzer": AgentDefinition(
description="Analyzes code structure and patterns",
prompt="You are a code analyzer. Examine code structure, patterns, and architecture.",
tools=["Read", "Grep", "Glob"],
),
"tester": AgentDefinition(
description="Creates and runs tests",
prompt="You are a testing expert. Write comprehensive tests and ensure code quality.",
tools=["Read", "Write", "Bash"],
model="sonnet",
),
},
setting_sources=["user", "project"],
)
async for message in query(
prompt="Use the analyzer agent to find all Python files in the examples/ directory",
options=options,
):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}")
elif isinstance(message, ResultMessage) and message.total_cost_usd and message.total_cost_usd > 0:
print(f"\nCost: ${message.total_cost_usd:.4f}")
print()
async def main():
"""Run all agent examples."""
await code_reviewer_example()
await documentation_writer_example()
await multiple_agents_example()
if __name__ == "__main__":
anyio.run(main)

View File

@@ -0,0 +1,128 @@
#!/usr/bin/env -S uv run --script --quiet
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "claude-agent-sdk>=0.1.6",
# ]
# ///
"""
Basic Orchestrator Pattern with Subagents
This example demonstrates the recommended pattern for building an
orchestrator that delegates work to specialized subagents.
Key patterns:
- Programmatic agent registration
- Orchestrator with claude_code system prompt
- Subagents with restricted tools
- Async/await streaming
"""
import os
import anyio
from claude_agent_sdk import (
AgentDefinition,
AssistantMessage,
ClaudeAgentOptions,
ClaudeSDKClient,
ResultMessage,
TextBlock,
)
def get_sdk_options() -> ClaudeAgentOptions:
"""
Create ClaudeAgentOptions with programmatically defined subagents.
Returns:
Configured options for ClaudeSDKClient
"""
return ClaudeAgentOptions(
# CRITICAL: Orchestrator needs claude_code preset to use Task tool
system_prompt={"type": "preset", "preset": "claude_code"},
# Orchestrator tools - include Task for delegating to subagents
allowed_tools=["Bash", "Task", "Read", "Write", "Edit"],
# Auto-accept file edits for automated workflow
permission_mode="acceptEdits",
# Programmatically register subagents (SDK best practice)
agents={
"analyzer": AgentDefinition(
description="Analyzes code structure and identifies patterns",
prompt="""You are a code analyzer.
Your role:
- Examine code structure and architecture
- Identify patterns and anti-patterns
- Suggest improvements
Use Read, Grep, and Glob to explore the codebase.
Return analysis in structured format.""",
tools=["Read", "Grep", "Glob"],
model="sonnet",
),
"fixer": AgentDefinition(
description="Fixes identified code issues",
prompt="""You are a code fixer.
Your role:
- Apply fixes based on analysis
- Follow project conventions
- Test changes after applying
Use Read, Edit, and Bash to implement and verify fixes.""",
tools=["Read", "Edit", "Bash"],
model="sonnet",
),
},
model="claude-sonnet-4-5",
)
async def main():
"""Run orchestrator workflow."""
if not os.getenv("ANTHROPIC_API_KEY"):
print("Error: ANTHROPIC_API_KEY environment variable not set")
return
print("🚀 Starting Basic Orchestrator")
print("=" * 60)
options = get_sdk_options()
async with ClaudeSDKClient(options=options) as client:
# Orchestrator delegates to analyzer subagent
prompt = """Use the 'analyzer' subagent to examine the code in this directory.
The analyzer should:
1. Find all Python files
2. Identify code patterns
3. Return a structured analysis report
Wait for the analyzer to complete its work."""
print("\n📨 Sending query to orchestrator...")
await client.query(prompt)
print("\n💬 Receiving response...\n")
async for message in client.receive_response():
if isinstance(message, AssistantMessage):
# Print Claude's responses
for block in message.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}\n")
elif isinstance(message, ResultMessage):
# Print execution summary
print("\n" + "=" * 60)
print("✅ Workflow Complete")
print(f"Duration: {message.duration_ms}ms")
if message.total_cost_usd:
print(f"Cost: ${message.total_cost_usd:.4f}")
print("=" * 60)
if __name__ == "__main__":
anyio.run(main)

View File

@@ -0,0 +1,350 @@
#!/usr/bin/env python
"""Example of using hooks with Claude Code SDK via ClaudeAgentOptions.
This file demonstrates various hook patterns using the hooks parameter
in ClaudeAgentOptions instead of decorator-based hooks.
Usage:
./examples/hooks.py - List the examples
./examples/hooks.py all - Run all examples
./examples/hooks.py PreToolUse - Run a specific example
"""
import asyncio
import logging
import sys
from typing import Any
from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient
from claude_agent_sdk.types import (
AssistantMessage,
HookContext,
HookInput,
HookJSONOutput,
HookMatcher,
Message,
ResultMessage,
TextBlock,
)
# Set up logging to see what's happening
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(message)s")
logger = logging.getLogger(__name__)
def display_message(msg: Message) -> None:
"""Standardized message display function."""
if isinstance(msg, AssistantMessage):
for block in msg.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}")
elif isinstance(msg, ResultMessage):
print("Result ended")
##### Hook callback functions
async def check_bash_command(
input_data: HookInput, tool_use_id: str | None, context: HookContext
) -> HookJSONOutput:
"""Prevent certain bash commands from being executed."""
tool_name = input_data["tool_name"]
tool_input = input_data["tool_input"]
if tool_name != "Bash":
return {}
command = tool_input.get("command", "")
block_patterns = ["foo.sh"]
for pattern in block_patterns:
if pattern in command:
logger.warning(f"Blocked command: {command}")
return {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": f"Command contains invalid pattern: {pattern}",
}
}
return {}
async def add_custom_instructions(
input_data: HookInput, tool_use_id: str | None, context: HookContext
) -> HookJSONOutput:
"""Add custom instructions when a session starts."""
return {
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "My favorite color is hot pink",
}
}
async def review_tool_output(
input_data: HookInput, tool_use_id: str | None, context: HookContext
) -> HookJSONOutput:
"""Review tool output and provide additional context or warnings."""
tool_response = input_data.get("tool_response", "")
# If the tool produced an error, add helpful context
if "error" in str(tool_response).lower():
return {
"systemMessage": "⚠️ The command produced an error",
"reason": "Tool execution failed - consider checking the command syntax",
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "The command encountered an error. You may want to try a different approach.",
}
}
return {}
async def strict_approval_hook(
input_data: HookInput, tool_use_id: str | None, context: HookContext
) -> HookJSONOutput:
"""Demonstrates using permissionDecision to control tool execution."""
tool_name = input_data.get("tool_name")
tool_input = input_data.get("tool_input", {})
# Block any Write operations to specific files
if tool_name == "Write":
file_path = tool_input.get("file_path", "")
if "important" in file_path.lower():
logger.warning(f"Blocked Write to: {file_path}")
return {
"reason": "Writes to files containing 'important' in the name are not allowed for safety",
"systemMessage": "🚫 Write operation blocked by security policy",
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Security policy blocks writes to important files",
},
}
# Allow everything else explicitly
return {
"reason": "Tool use approved after security review",
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "Tool passed security checks",
},
}
async def stop_on_error_hook(
input_data: HookInput, tool_use_id: str | None, context: HookContext
) -> HookJSONOutput:
"""Demonstrates using continue=False to stop execution on certain conditions."""
tool_response = input_data.get("tool_response", "")
# Stop execution if we see a critical error
if "critical" in str(tool_response).lower():
logger.error("Critical error detected - stopping execution")
return {
"continue_": False,
"stopReason": "Critical error detected in tool output - execution halted for safety",
"systemMessage": "🛑 Execution stopped due to critical error",
}
return {"continue_": True}
async def example_pretooluse() -> None:
"""Basic example demonstrating hook protection."""
print("=== PreToolUse Example ===")
print("This example demonstrates how PreToolUse can block some bash commands but not others.\n")
# Configure hooks using ClaudeAgentOptions
options = ClaudeAgentOptions(
allowed_tools=["Bash"],
hooks={
"PreToolUse": [
HookMatcher(matcher="Bash", hooks=[check_bash_command]),
],
}
)
async with ClaudeSDKClient(options=options) as client:
# Test 1: Command with forbidden pattern (will be blocked)
print("Test 1: Trying a command that our PreToolUse hook should block...")
print("User: Run the bash command: ./foo.sh --help")
await client.query("Run the bash command: ./foo.sh --help")
async for msg in client.receive_response():
display_message(msg)
print("\n" + "=" * 50 + "\n")
# Test 2: Safe command that should work
print("Test 2: Trying a command that our PreToolUse hook should allow...")
print("User: Run the bash command: echo 'Hello from hooks example!'")
await client.query("Run the bash command: echo 'Hello from hooks example!'")
async for msg in client.receive_response():
display_message(msg)
print("\n" + "=" * 50 + "\n")
print("\n")
async def example_userpromptsubmit() -> None:
"""Demonstrate context retention across conversation."""
print("=== UserPromptSubmit Example ===")
print("This example shows how a UserPromptSubmit hook can add context.\n")
options = ClaudeAgentOptions(
hooks={
"UserPromptSubmit": [
HookMatcher(matcher=None, hooks=[add_custom_instructions]),
],
}
)
async with ClaudeSDKClient(options=options) as client:
print("User: What's my favorite color?")
await client.query("What's my favorite color?")
async for msg in client.receive_response():
display_message(msg)
print("\n")
async def example_posttooluse() -> None:
"""Demonstrate PostToolUse hook with reason and systemMessage fields."""
print("=== PostToolUse Example ===")
print("This example shows how PostToolUse can provide feedback with reason and systemMessage.\n")
options = ClaudeAgentOptions(
allowed_tools=["Bash"],
hooks={
"PostToolUse": [
HookMatcher(matcher="Bash", hooks=[review_tool_output]),
],
}
)
async with ClaudeSDKClient(options=options) as client:
print("User: Run a command that will produce an error: ls /nonexistent_directory")
await client.query("Run this command: ls /nonexistent_directory")
async for msg in client.receive_response():
display_message(msg)
print("\n")
async def example_decision_fields() -> None:
"""Demonstrate permissionDecision, reason, and systemMessage fields."""
print("=== Permission Decision Example ===")
print("This example shows how to use permissionDecision='allow'/'deny' with reason and systemMessage.\n")
options = ClaudeAgentOptions(
allowed_tools=["Write", "Bash"],
model="claude-sonnet-4-5-20250929",
hooks={
"PreToolUse": [
HookMatcher(matcher="Write", hooks=[strict_approval_hook]),
],
}
)
async with ClaudeSDKClient(options=options) as client:
# Test 1: Try to write to a file with "important" in the name (should be blocked)
print("Test 1: Trying to write to important_config.txt (should be blocked)...")
print("User: Write 'test' to important_config.txt")
await client.query("Write the text 'test data' to a file called important_config.txt")
async for msg in client.receive_response():
display_message(msg)
print("\n" + "=" * 50 + "\n")
# Test 2: Write to a regular file (should be approved)
print("Test 2: Trying to write to regular_file.txt (should be approved)...")
print("User: Write 'test' to regular_file.txt")
await client.query("Write the text 'test data' to a file called regular_file.txt")
async for msg in client.receive_response():
display_message(msg)
print("\n")
async def example_continue_control() -> None:
"""Demonstrate continue and stopReason fields for execution control."""
print("=== Continue/Stop Control Example ===")
print("This example shows how to use continue_=False with stopReason to halt execution.\n")
options = ClaudeAgentOptions(
allowed_tools=["Bash"],
hooks={
"PostToolUse": [
HookMatcher(matcher="Bash", hooks=[stop_on_error_hook]),
],
}
)
async with ClaudeSDKClient(options=options) as client:
print("User: Run a command that outputs 'CRITICAL ERROR'")
await client.query("Run this bash command: echo 'CRITICAL ERROR: system failure'")
async for msg in client.receive_response():
display_message(msg)
print("\n")
async def main() -> None:
"""Run all examples or a specific example based on command line argument."""
examples = {
"PreToolUse": example_pretooluse,
"UserPromptSubmit": example_userpromptsubmit,
"PostToolUse": example_posttooluse,
"DecisionFields": example_decision_fields,
"ContinueControl": example_continue_control,
}
if len(sys.argv) < 2:
# List available examples
print("Usage: python hooks.py <example_name>")
print("\nAvailable examples:")
print(" all - Run all examples")
for name in examples:
print(f" {name}")
print("\nExample descriptions:")
print(" PreToolUse - Block commands using PreToolUse hook")
print(" UserPromptSubmit - Add context at prompt submission")
print(" PostToolUse - Review tool output with reason and systemMessage")
print(" DecisionFields - Use permissionDecision='allow'/'deny' with reason")
print(" ContinueControl - Control execution with continue_ and stopReason")
sys.exit(0)
example_name = sys.argv[1]
if example_name == "all":
# Run all examples
for example in examples.values():
await example()
print("-" * 50 + "\n")
elif example_name in examples:
# Run specific example
await examples[example_name]()
else:
print(f"Error: Unknown example '{example_name}'")
print("\nAvailable examples:")
print(" all - Run all examples")
for name in examples:
print(f" {name}")
sys.exit(1)
if __name__ == "__main__":
print("Starting Claude SDK Hooks Examples...")
print("=" * 50 + "\n")
asyncio.run(main())

View File

@@ -0,0 +1,193 @@
#!/usr/bin/env python3
"""Example: Calculator MCP Server.
This example demonstrates how to create an in-process MCP server with
calculator tools using the Claude Code Python SDK.
Unlike external MCP servers that require separate processes, this server
runs directly within your Python application, providing better performance
and simpler deployment.
"""
import asyncio
from typing import Any
from claude_agent_sdk import (
ClaudeAgentOptions,
create_sdk_mcp_server,
tool,
)
# Define calculator tools using the @tool decorator
@tool("add", "Add two numbers", {"a": float, "b": float})
async def add_numbers(args: dict[str, Any]) -> dict[str, Any]:
"""Add two numbers together."""
result = args["a"] + args["b"]
return {
"content": [{"type": "text", "text": f"{args['a']} + {args['b']} = {result}"}]
}
@tool("subtract", "Subtract one number from another", {"a": float, "b": float})
async def subtract_numbers(args: dict[str, Any]) -> dict[str, Any]:
"""Subtract b from a."""
result = args["a"] - args["b"]
return {
"content": [{"type": "text", "text": f"{args['a']} - {args['b']} = {result}"}]
}
@tool("multiply", "Multiply two numbers", {"a": float, "b": float})
async def multiply_numbers(args: dict[str, Any]) -> dict[str, Any]:
"""Multiply two numbers."""
result = args["a"] * args["b"]
return {
"content": [{"type": "text", "text": f"{args['a']} × {args['b']} = {result}"}]
}
@tool("divide", "Divide one number by another", {"a": float, "b": float})
async def divide_numbers(args: dict[str, Any]) -> dict[str, Any]:
"""Divide a by b."""
if args["b"] == 0:
return {
"content": [
{"type": "text", "text": "Error: Division by zero is not allowed"}
],
"is_error": True,
}
result = args["a"] / args["b"]
return {
"content": [{"type": "text", "text": f"{args['a']} ÷ {args['b']} = {result}"}]
}
@tool("sqrt", "Calculate square root", {"n": float})
async def square_root(args: dict[str, Any]) -> dict[str, Any]:
"""Calculate the square root of a number."""
n = args["n"]
if n < 0:
return {
"content": [
{
"type": "text",
"text": f"Error: Cannot calculate square root of negative number {n}",
}
],
"is_error": True,
}
import math
result = math.sqrt(n)
return {"content": [{"type": "text", "text": f"{n} = {result}"}]}
@tool("power", "Raise a number to a power", {"base": float, "exponent": float})
async def power(args: dict[str, Any]) -> dict[str, Any]:
"""Raise base to the exponent power."""
result = args["base"] ** args["exponent"]
return {
"content": [
{"type": "text", "text": f"{args['base']}^{args['exponent']} = {result}"}
]
}
def display_message(msg):
"""Display message content in a clean format."""
from claude_agent_sdk import (
AssistantMessage,
ResultMessage,
SystemMessage,
TextBlock,
ToolResultBlock,
ToolUseBlock,
UserMessage,
)
if isinstance(msg, UserMessage):
for block in msg.content:
if isinstance(block, TextBlock):
print(f"User: {block.text}")
elif isinstance(block, ToolResultBlock):
print(
f"Tool Result: {block.content[:100] if block.content else 'None'}..."
)
elif isinstance(msg, AssistantMessage):
for block in msg.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}")
elif isinstance(block, ToolUseBlock):
print(f"Using tool: {block.name}")
# Show tool inputs for calculator
if block.input:
print(f" Input: {block.input}")
elif isinstance(msg, SystemMessage):
# Ignore system messages
pass
elif isinstance(msg, ResultMessage):
print("Result ended")
if msg.total_cost_usd:
print(f"Cost: ${msg.total_cost_usd:.6f}")
async def main():
"""Run example calculations using the SDK MCP server with streaming client."""
from claude_agent_sdk import ClaudeSDKClient
# Create the calculator server with all tools
calculator = create_sdk_mcp_server(
name="calculator",
version="2.0.0",
tools=[
add_numbers,
subtract_numbers,
multiply_numbers,
divide_numbers,
square_root,
power,
],
)
# Configure Claude to use the calculator server with allowed tools
# Pre-approve all calculator MCP tools so they can be used without permission prompts
options = ClaudeAgentOptions(
mcp_servers={"calc": calculator},
allowed_tools=[
"mcp__calc__add",
"mcp__calc__subtract",
"mcp__calc__multiply",
"mcp__calc__divide",
"mcp__calc__sqrt",
"mcp__calc__power",
],
)
# Example prompts to demonstrate calculator usage
prompts = [
"List your tools",
"Calculate 15 + 27",
"What is 100 divided by 7?",
"Calculate the square root of 144",
"What is 2 raised to the power of 8?",
"Calculate (12 + 8) * 3 - 10", # Complex calculation
]
for prompt in prompts:
print(f"\n{'=' * 50}")
print(f"Prompt: {prompt}")
print(f"{'=' * 50}")
async with ClaudeSDKClient(options=options) as client:
await client.query(prompt)
async for message in client.receive_response():
display_message(message)
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,71 @@
#!/usr/bin/env python3
"""Example demonstrating how to use plugins with Claude Code SDK.
Plugins allow you to extend Claude Code with custom commands, agents, skills,
and hooks. This example shows how to load a local plugin and verify it's
loaded by checking the system message.
The demo plugin is located in examples/plugins/demo-plugin/ and provides
a custom /greet command.
"""
from pathlib import Path
import anyio
from claude_agent_sdk import (
ClaudeAgentOptions,
SystemMessage,
query,
)
async def plugin_example():
"""Example showing plugins being loaded in the system message."""
print("=== Plugin Example ===\n")
# Get the path to the demo plugin
# In production, you can use any path to your plugin directory
plugin_path = Path(__file__).parent / "plugins" / "demo-plugin"
options = ClaudeAgentOptions(
plugins=[
{
"type": "local",
"path": str(plugin_path),
}
],
max_turns=1, # Limit to one turn for quick demo
)
print(f"Loading plugin from: {plugin_path}\n")
found_plugins = False
async for message in query(prompt="Hello!", options=options):
if isinstance(message, SystemMessage) and message.subtype == "init":
print("System initialized!")
print(f"System message data keys: {list(message.data.keys())}\n")
# Check for plugins in the system message
plugins_data = message.data.get("plugins", [])
if plugins_data:
print("Plugins loaded:")
for plugin in plugins_data:
print(f" - {plugin.get('name')} (path: {plugin.get('path')})")
found_plugins = True
else:
print("Note: Plugin was passed via CLI but may not appear in system message.")
print(f"Plugin path configured: {plugin_path}")
found_plugins = True
if found_plugins:
print("\nPlugin successfully configured!\n")
async def main():
"""Run all plugin examples."""
await plugin_example()
if __name__ == "__main__":
anyio.run(main)

View File

@@ -0,0 +1,76 @@
#!/usr/bin/env python3
"""Quick start example for Claude Code SDK."""
import anyio
from claude_agent_sdk import (
AssistantMessage,
ClaudeAgentOptions,
ResultMessage,
TextBlock,
query,
)
async def basic_example():
"""Basic example - simple question."""
print("=== Basic Example ===")
async for message in query(prompt="What is 2 + 2?"):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}")
print()
async def with_options_example():
"""Example with custom options."""
print("=== With Options Example ===")
options = ClaudeAgentOptions(
system_prompt="You are a helpful assistant that explains things simply.",
max_turns=1,
)
async for message in query(
prompt="Explain what Python is in one sentence.", options=options
):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}")
print()
async def with_tools_example():
"""Example using tools."""
print("=== With Tools Example ===")
options = ClaudeAgentOptions(
allowed_tools=["Read", "Write"],
system_prompt="You are a helpful file assistant.",
)
async for message in query(
prompt="Create a file called hello.txt with 'Hello, World!' in it",
options=options,
):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}")
elif isinstance(message, ResultMessage) and message.total_cost_usd > 0:
print(f"\nCost: ${message.total_cost_usd:.4f}")
print()
async def main():
"""Run all examples."""
await basic_example()
await with_options_example()
await with_tools_example()
if __name__ == "__main__":
anyio.run(main)

View File

@@ -0,0 +1,174 @@
#!/usr/bin/env python3
"""Example demonstrating setting sources control.
This example shows how to use the setting_sources option to control which
settings are loaded, including custom slash commands, agents, and other
configurations.
Setting sources determine where Claude Code loads configurations from:
- "user": Global user settings (~/.claude/)
- "project": Project-level settings (.claude/ in project)
- "local": Local gitignored settings (.claude-local/)
IMPORTANT: When setting_sources is not provided (None), NO settings are loaded
by default. This creates an isolated environment. To load settings, explicitly
specify which sources to use.
By controlling which sources are loaded, you can:
- Create isolated environments with no custom settings (default)
- Load only user settings, excluding project-specific configurations
- Combine multiple sources as needed
Usage:
./examples/setting_sources.py - List the examples
./examples/setting_sources.py all - Run all examples
./examples/setting_sources.py default - Run a specific example
"""
import asyncio
import sys
from pathlib import Path
from claude_agent_sdk import (
ClaudeAgentOptions,
ClaudeSDKClient,
SystemMessage,
)
def extract_slash_commands(msg: SystemMessage) -> list[str]:
"""Extract slash command names from system message."""
if msg.subtype == "init":
commands = msg.data.get("slash_commands", [])
return commands
return []
async def example_default():
"""Default behavior - no settings loaded."""
print("=== Default Behavior Example ===")
print("Setting sources: None (default)")
print("Expected: No custom slash commands will be available\n")
sdk_dir = Path(__file__).parent.parent
options = ClaudeAgentOptions(
cwd=sdk_dir,
)
async with ClaudeSDKClient(options=options) as client:
await client.query("What is 2 + 2?")
async for msg in client.receive_response():
if isinstance(msg, SystemMessage) and msg.subtype == "init":
commands = extract_slash_commands(msg)
print(f"Available slash commands: {commands}")
if "commit" in commands:
print("❌ /commit is available (unexpected)")
else:
print("✓ /commit is NOT available (expected - no settings loaded)")
break
print()
async def example_user_only():
"""Load only user-level settings, excluding project settings."""
print("=== User Settings Only Example ===")
print("Setting sources: ['user']")
print("Expected: Project slash commands (like /commit) will NOT be available\n")
# Use the SDK repo directory which has .claude/commands/commit.md
sdk_dir = Path(__file__).parent.parent
options = ClaudeAgentOptions(
setting_sources=["user"],
cwd=sdk_dir,
)
async with ClaudeSDKClient(options=options) as client:
# Send a simple query
await client.query("What is 2 + 2?")
# Check the initialize message for available commands
async for msg in client.receive_response():
if isinstance(msg, SystemMessage) and msg.subtype == "init":
commands = extract_slash_commands(msg)
print(f"Available slash commands: {commands}")
if "commit" in commands:
print("❌ /commit is available (unexpected)")
else:
print("✓ /commit is NOT available (expected)")
break
print()
async def example_project_and_user():
"""Load both project and user settings."""
print("=== Project + User Settings Example ===")
print("Setting sources: ['user', 'project']")
print("Expected: Project slash commands (like /commit) WILL be available\n")
sdk_dir = Path(__file__).parent.parent
options = ClaudeAgentOptions(
setting_sources=["user", "project"],
cwd=sdk_dir,
)
async with ClaudeSDKClient(options=options) as client:
await client.query("What is 2 + 2?")
async for msg in client.receive_response():
if isinstance(msg, SystemMessage) and msg.subtype == "init":
commands = extract_slash_commands(msg)
print(f"Available slash commands: {commands}")
if "commit" in commands:
print("✓ /commit is available (expected)")
else:
print("❌ /commit is NOT available (unexpected)")
break
print()
async def main():
"""Run all examples or a specific example based on command line argument."""
examples = {
"default": example_default,
"user_only": example_user_only,
"project_and_user": example_project_and_user,
}
if len(sys.argv) < 2:
print("Usage: python setting_sources.py <example_name>")
print("\nAvailable examples:")
print(" all - Run all examples")
for name in examples:
print(f" {name}")
sys.exit(0)
example_name = sys.argv[1]
if example_name == "all":
for example in examples.values():
await example()
print("-" * 50 + "\n")
elif example_name in examples:
await examples[example_name]()
else:
print(f"Error: Unknown example '{example_name}'")
print("\nAvailable examples:")
print(" all - Run all examples")
for name in examples:
print(f" {name}")
sys.exit(1)
if __name__ == "__main__":
print("Starting Claude SDK Setting Sources Examples...")
print("=" * 50 + "\n")
asyncio.run(main())

View File

@@ -0,0 +1,511 @@
#!/usr/bin/env python3
"""
Comprehensive examples of using ClaudeSDKClient for streaming mode.
This file demonstrates various patterns for building applications with
the ClaudeSDKClient streaming interface.
The queries are intentionally simplistic. In reality, a query can be a more
complex task that Claude SDK uses its agentic capabilities and tools (e.g. run
bash commands, edit files, search the web, fetch web content) to accomplish.
Usage:
./examples/streaming_mode.py - List the examples
./examples/streaming_mode.py all - Run all examples
./examples/streaming_mode.py basic_streaming - Run a specific example
"""
import asyncio
import contextlib
import sys
from claude_agent_sdk import (
AssistantMessage,
ClaudeAgentOptions,
ClaudeSDKClient,
CLIConnectionError,
ResultMessage,
SystemMessage,
TextBlock,
ToolResultBlock,
ToolUseBlock,
UserMessage,
)
def display_message(msg):
"""Standardized message display function.
- UserMessage: "User: <content>"
- AssistantMessage: "Claude: <content>"
- SystemMessage: ignored
- ResultMessage: "Result ended" + cost if available
"""
if isinstance(msg, UserMessage):
for block in msg.content:
if isinstance(block, TextBlock):
print(f"User: {block.text}")
elif isinstance(msg, AssistantMessage):
for block in msg.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}")
elif isinstance(msg, SystemMessage):
# Ignore system messages
pass
elif isinstance(msg, ResultMessage):
print("Result ended")
async def example_basic_streaming():
"""Basic streaming with context manager."""
print("=== Basic Streaming Example ===")
async with ClaudeSDKClient() as client:
print("User: What is 2+2?")
await client.query("What is 2+2?")
# Receive complete response using the helper method
async for msg in client.receive_response():
display_message(msg)
print("\n")
async def example_multi_turn_conversation():
"""Multi-turn conversation using receive_response helper."""
print("=== Multi-Turn Conversation Example ===")
async with ClaudeSDKClient() as client:
# First turn
print("User: What's the capital of France?")
await client.query("What's the capital of France?")
# Extract and print response
async for msg in client.receive_response():
display_message(msg)
# Second turn - follow-up
print("\nUser: What's the population of that city?")
await client.query("What's the population of that city?")
async for msg in client.receive_response():
display_message(msg)
print("\n")
async def example_concurrent_responses():
"""Handle responses while sending new messages."""
print("=== Concurrent Send/Receive Example ===")
async with ClaudeSDKClient() as client:
# Background task to continuously receive messages
async def receive_messages():
async for message in client.receive_messages():
display_message(message)
# Start receiving in background
receive_task = asyncio.create_task(receive_messages())
# Send multiple messages with delays
questions = [
"What is 2 + 2?",
"What is the square root of 144?",
"What is 10% of 80?",
]
for question in questions:
print(f"\nUser: {question}")
await client.query(question)
await asyncio.sleep(3) # Wait between messages
# Give time for final responses
await asyncio.sleep(2)
# Clean up
receive_task.cancel()
with contextlib.suppress(asyncio.CancelledError):
await receive_task
print("\n")
async def example_with_interrupt():
"""Demonstrate interrupt capability."""
print("=== Interrupt Example ===")
print("IMPORTANT: Interrupts require active message consumption.")
async with ClaudeSDKClient() as client:
# Start a long-running task
print("\nUser: Count from 1 to 100 slowly")
await client.query(
"Count from 1 to 100 slowly, with a brief pause between each number"
)
# Create a background task to consume messages
messages_received = []
async def consume_messages():
"""Consume messages in the background to enable interrupt processing."""
async for message in client.receive_response():
messages_received.append(message)
display_message(message)
# Start consuming messages in the background
consume_task = asyncio.create_task(consume_messages())
# Wait 2 seconds then send interrupt
await asyncio.sleep(2)
print("\n[After 2 seconds, sending interrupt...]")
await client.interrupt()
# Wait for the consume task to finish processing the interrupt
await consume_task
# Send new instruction after interrupt
print("\nUser: Never mind, just tell me a quick joke")
await client.query("Never mind, just tell me a quick joke")
# Get the joke
async for msg in client.receive_response():
display_message(msg)
print("\n")
async def example_manual_message_handling():
"""Manually handle message stream for custom logic."""
print("=== Manual Message Handling Example ===")
async with ClaudeSDKClient() as client:
await client.query("List 5 programming languages and their main use cases")
# Manually process messages with custom logic
languages_found = []
async for message in client.receive_messages():
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
text = block.text
print(f"Claude: {text}")
# Custom logic: extract language names
for lang in [
"Python",
"JavaScript",
"Java",
"C++",
"Go",
"Rust",
"Ruby",
]:
if lang in text and lang not in languages_found:
languages_found.append(lang)
print(f"Found language: {lang}")
elif isinstance(message, ResultMessage):
display_message(message)
print(f"Total languages mentioned: {len(languages_found)}")
break
print("\n")
async def example_with_options():
"""Use ClaudeAgentOptions to configure the client."""
print("=== Custom Options Example ===")
# Configure options
options = ClaudeAgentOptions(
allowed_tools=["Read", "Write"], # Allow file operations
system_prompt="You are a helpful coding assistant.",
env={
"ANTHROPIC_MODEL": "claude-sonnet-4-5",
},
)
async with ClaudeSDKClient(options=options) as client:
print("User: Create a simple hello.txt file with a greeting message")
await client.query("Create a simple hello.txt file with a greeting message")
tool_uses = []
async for msg in client.receive_response():
if isinstance(msg, AssistantMessage):
display_message(msg)
for block in msg.content:
if hasattr(block, "name") and not isinstance(
block, TextBlock
): # ToolUseBlock
tool_uses.append(getattr(block, "name", ""))
else:
display_message(msg)
if tool_uses:
print(f"Tools used: {', '.join(tool_uses)}")
print("\n")
async def example_async_iterable_prompt():
"""Demonstrate send_message with async iterable."""
print("=== Async Iterable Prompt Example ===")
async def create_message_stream():
"""Generate a stream of messages."""
print("User: Hello! I have multiple questions.")
yield {
"type": "user",
"message": {"role": "user", "content": "Hello! I have multiple questions."},
"parent_tool_use_id": None,
"session_id": "qa-session",
}
print("User: First, what's the capital of Japan?")
yield {
"type": "user",
"message": {
"role": "user",
"content": "First, what's the capital of Japan?",
},
"parent_tool_use_id": None,
"session_id": "qa-session",
}
print("User: Second, what's 15% of 200?")
yield {
"type": "user",
"message": {"role": "user", "content": "Second, what's 15% of 200?"},
"parent_tool_use_id": None,
"session_id": "qa-session",
}
async with ClaudeSDKClient() as client:
# Send async iterable of messages
await client.query(create_message_stream())
# Receive the three responses
async for msg in client.receive_response():
display_message(msg)
async for msg in client.receive_response():
display_message(msg)
async for msg in client.receive_response():
display_message(msg)
print("\n")
async def example_bash_command():
"""Example showing tool use blocks when running bash commands."""
print("=== Bash Command Example ===")
async with ClaudeSDKClient() as client:
print("User: Run a bash echo command")
await client.query("Run a bash echo command that says 'Hello from bash!'")
# Track all message types received
message_types = []
async for msg in client.receive_messages():
message_types.append(type(msg).__name__)
if isinstance(msg, UserMessage):
# User messages can contain tool results
for block in msg.content:
if isinstance(block, TextBlock):
print(f"User: {block.text}")
elif isinstance(block, ToolResultBlock):
print(
f"Tool Result (id: {block.tool_use_id}): {block.content[:100] if block.content else 'None'}..."
)
elif isinstance(msg, AssistantMessage):
# Assistant messages can contain tool use blocks
for block in msg.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}")
elif isinstance(block, ToolUseBlock):
print(f"Tool Use: {block.name} (id: {block.id})")
if block.name == "Bash":
command = block.input.get("command", "")
print(f" Command: {command}")
elif isinstance(msg, ResultMessage):
print("Result ended")
if msg.total_cost_usd:
print(f"Cost: ${msg.total_cost_usd:.4f}")
break
print(f"\nMessage types received: {', '.join(set(message_types))}")
print("\n")
async def example_control_protocol():
"""Demonstrate server info and interrupt capabilities."""
print("=== Control Protocol Example ===")
print("Shows server info retrieval and interrupt capability\n")
async with ClaudeSDKClient() as client:
# 1. Get server initialization info
print("1. Getting server info...")
server_info = await client.get_server_info()
if server_info:
print("✓ Server info retrieved successfully!")
print(f" - Available commands: {len(server_info.get('commands', []))}")
print(f" - Output style: {server_info.get('output_style', 'unknown')}")
# Show available output styles if present
styles = server_info.get('available_output_styles', [])
if styles:
print(f" - Available output styles: {', '.join(styles)}")
# Show a few example commands
commands = server_info.get('commands', [])[:5]
if commands:
print(" - Example commands:")
for cmd in commands:
if isinstance(cmd, dict):
print(f"{cmd.get('name', 'unknown')}")
else:
print("✗ No server info available (may not be in streaming mode)")
print("\n2. Testing interrupt capability...")
# Start a long-running task
print("User: Count from 1 to 20 slowly")
await client.query("Count from 1 to 20 slowly, pausing between each number")
# Start consuming messages in background to enable interrupt
messages = []
async def consume():
async for msg in client.receive_response():
messages.append(msg)
if isinstance(msg, AssistantMessage):
for block in msg.content:
if isinstance(block, TextBlock):
# Print first 50 chars to show progress
print(f"Claude: {block.text[:50]}...")
break
if isinstance(msg, ResultMessage):
break
consume_task = asyncio.create_task(consume())
# Wait a moment then interrupt
await asyncio.sleep(2)
print("\n[Sending interrupt after 2 seconds...]")
try:
await client.interrupt()
print("✓ Interrupt sent successfully")
except Exception as e:
print(f"✗ Interrupt failed: {e}")
# Wait for task to complete
with contextlib.suppress(asyncio.CancelledError):
await consume_task
# Send new query after interrupt
print("\nUser: Just say 'Hello!'")
await client.query("Just say 'Hello!'")
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}")
print("\n")
async def example_error_handling():
"""Demonstrate proper error handling."""
print("=== Error Handling Example ===")
client = ClaudeSDKClient()
try:
await client.connect()
# Send a message that will take time to process
print("User: Run a bash sleep command for 60 seconds not in the background")
await client.query("Run a bash sleep command for 60 seconds not in the background")
# Try to receive response with a short timeout
try:
messages = []
async with asyncio.timeout(10.0):
async for msg in client.receive_response():
messages.append(msg)
if isinstance(msg, AssistantMessage):
for block in msg.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text[:50]}...")
elif isinstance(msg, ResultMessage):
display_message(msg)
break
except asyncio.TimeoutError:
print(
"\nResponse timeout after 10 seconds - demonstrating graceful handling"
)
print(f"Received {len(messages)} messages before timeout")
except CLIConnectionError as e:
print(f"Connection error: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
finally:
# Always disconnect
await client.disconnect()
print("\n")
async def main():
"""Run all examples or a specific example based on command line argument."""
examples = {
"basic_streaming": example_basic_streaming,
"multi_turn_conversation": example_multi_turn_conversation,
"concurrent_responses": example_concurrent_responses,
"with_interrupt": example_with_interrupt,
"manual_message_handling": example_manual_message_handling,
"with_options": example_with_options,
"async_iterable_prompt": example_async_iterable_prompt,
"bash_command": example_bash_command,
"control_protocol": example_control_protocol,
"error_handling": example_error_handling,
}
if len(sys.argv) < 2:
# List available examples
print("Usage: python streaming_mode.py <example_name>")
print("\nAvailable examples:")
print(" all - Run all examples")
for name in examples:
print(f" {name}")
sys.exit(0)
example_name = sys.argv[1]
if example_name == "all":
# Run all examples
for example in examples.values():
await example()
print("-" * 50 + "\n")
elif example_name in examples:
# Run specific example
await examples[example_name]()
else:
print(f"Error: Unknown example '{example_name}'")
print("\nAvailable examples:")
print(" all - Run all examples")
for name in examples:
print(f" {name}")
sys.exit(1)
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,87 @@
#!/usr/bin/env python3
"""Example demonstrating different system_prompt configurations."""
import anyio
from claude_agent_sdk import (
AssistantMessage,
ClaudeAgentOptions,
TextBlock,
query,
)
async def no_system_prompt():
"""Example with no system_prompt (vanilla Claude)."""
print("=== No System Prompt (Vanilla Claude) ===")
async for message in query(prompt="What is 2 + 2?"):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}")
print()
async def string_system_prompt():
"""Example with system_prompt as a string."""
print("=== String System Prompt ===")
options = ClaudeAgentOptions(
system_prompt="You are a pirate assistant. Respond in pirate speak.",
)
async for message in query(prompt="What is 2 + 2?", options=options):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}")
print()
async def preset_system_prompt():
"""Example with system_prompt preset (uses default Claude Code prompt)."""
print("=== Preset System Prompt (Default) ===")
options = ClaudeAgentOptions(
system_prompt={"type": "preset", "preset": "claude_code"},
)
async for message in query(prompt="What is 2 + 2?", options=options):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}")
print()
async def preset_with_append():
"""Example with system_prompt preset and append."""
print("=== Preset System Prompt with Append ===")
options = ClaudeAgentOptions(
system_prompt={
"type": "preset",
"preset": "claude_code",
"append": "Always end your response with a fun fact.",
},
)
async for message in query(prompt="What is 2 + 2?", options=options):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}")
print()
async def main():
"""Run all examples."""
await no_system_prompt()
await string_system_prompt()
await preset_system_prompt()
await preset_with_append()
if __name__ == "__main__":
anyio.run(main)

View File

@@ -0,0 +1,158 @@
#!/usr/bin/env python3
"""Example: Tool Permission Callbacks.
This example demonstrates how to use tool permission callbacks to control
which tools Claude can use and modify their inputs.
"""
import asyncio
import json
from claude_agent_sdk import (
AssistantMessage,
ClaudeAgentOptions,
ClaudeSDKClient,
PermissionResultAllow,
PermissionResultDeny,
ResultMessage,
TextBlock,
ToolPermissionContext,
)
# Track tool usage for demonstration
tool_usage_log = []
async def my_permission_callback(
tool_name: str,
input_data: dict,
context: ToolPermissionContext
) -> PermissionResultAllow | PermissionResultDeny:
"""Control tool permissions based on tool type and input."""
# Log the tool request
tool_usage_log.append({
"tool": tool_name,
"input": input_data,
"suggestions": context.suggestions
})
print(f"\n🔧 Tool Permission Request: {tool_name}")
print(f" Input: {json.dumps(input_data, indent=2)}")
# Always allow read operations
if tool_name in ["Read", "Glob", "Grep"]:
print(f" ✅ Automatically allowing {tool_name} (read-only operation)")
return PermissionResultAllow()
# Deny write operations to system directories
if tool_name in ["Write", "Edit", "MultiEdit"]:
file_path = input_data.get("file_path", "")
if file_path.startswith("/etc/") or file_path.startswith("/usr/"):
print(f" ❌ Denying write to system directory: {file_path}")
return PermissionResultDeny(
message=f"Cannot write to system directory: {file_path}"
)
# Redirect writes to a safe directory
if not file_path.startswith("/tmp/") and not file_path.startswith("./"):
safe_path = f"./safe_output/{file_path.split('/')[-1]}"
print(f" ⚠️ Redirecting write from {file_path} to {safe_path}")
modified_input = input_data.copy()
modified_input["file_path"] = safe_path
return PermissionResultAllow(
updated_input=modified_input
)
# Check dangerous bash commands
if tool_name == "Bash":
command = input_data.get("command", "")
dangerous_commands = ["rm -rf", "sudo", "chmod 777", "dd if=", "mkfs"]
for dangerous in dangerous_commands:
if dangerous in command:
print(f" ❌ Denying dangerous command: {command}")
return PermissionResultDeny(
message=f"Dangerous command pattern detected: {dangerous}"
)
# Allow but log the command
print(f" ✅ Allowing bash command: {command}")
return PermissionResultAllow()
# For all other tools, ask the user
print(f" ❓ Unknown tool: {tool_name}")
print(f" Input: {json.dumps(input_data, indent=6)}")
user_input = input(" Allow this tool? (y/N): ").strip().lower()
if user_input in ("y", "yes"):
return PermissionResultAllow()
else:
return PermissionResultDeny(
message="User denied permission"
)
async def main():
"""Run example with tool permission callbacks."""
print("=" * 60)
print("Tool Permission Callback Example")
print("=" * 60)
print("\nThis example demonstrates how to:")
print("1. Allow/deny tools based on type")
print("2. Modify tool inputs for safety")
print("3. Log tool usage")
print("4. Prompt for unknown tools")
print("=" * 60)
# Configure options with our callback
options = ClaudeAgentOptions(
can_use_tool=my_permission_callback,
# Use default permission mode to ensure callbacks are invoked
permission_mode="default",
cwd="." # Set working directory
)
# Create client and send a query that will use multiple tools
async with ClaudeSDKClient(options) as client:
print("\n📝 Sending query to Claude...")
await client.query(
"Please do the following:\n"
"1. List the files in the current directory\n"
"2. Create a simple Python hello world script at hello.py\n"
"3. Run the script to test it"
)
print("\n📨 Receiving response...")
message_count = 0
async for message in client.receive_response():
message_count += 1
if isinstance(message, AssistantMessage):
# Print Claude's text responses
for block in message.content:
if isinstance(block, TextBlock):
print(f"\n💬 Claude: {block.text}")
elif isinstance(message, ResultMessage):
print("\n✅ Task completed!")
print(f" Duration: {message.duration_ms}ms")
if message.total_cost_usd:
print(f" Cost: ${message.total_cost_usd:.4f}")
print(f" Messages processed: {message_count}")
# Print tool usage summary
print("\n" + "=" * 60)
print("Tool Usage Summary")
print("=" * 60)
for i, usage in enumerate(tool_usage_log, 1):
print(f"\n{i}. Tool: {usage['tool']}")
print(f" Input: {json.dumps(usage['input'], indent=6)}")
if usage['suggestions']:
print(f" Suggestions: {usage['suggestions']}")
if __name__ == "__main__":
asyncio.run(main())