Initial commit
This commit is contained in:
124
skills/claude-agent-sdk/examples/agents.py
Executable file
124
skills/claude-agent-sdk/examples/agents.py
Executable 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)
|
||||
128
skills/claude-agent-sdk/examples/basic-orchestrator.py
Executable file
128
skills/claude-agent-sdk/examples/basic-orchestrator.py
Executable 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)
|
||||
350
skills/claude-agent-sdk/examples/hooks.py
Executable file
350
skills/claude-agent-sdk/examples/hooks.py
Executable 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())
|
||||
193
skills/claude-agent-sdk/examples/mcp_calculator.py
Executable file
193
skills/claude-agent-sdk/examples/mcp_calculator.py
Executable 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())
|
||||
71
skills/claude-agent-sdk/examples/plugin_example.py
Executable file
71
skills/claude-agent-sdk/examples/plugin_example.py
Executable 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)
|
||||
76
skills/claude-agent-sdk/examples/quick_start.py
Executable file
76
skills/claude-agent-sdk/examples/quick_start.py
Executable 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)
|
||||
174
skills/claude-agent-sdk/examples/setting_sources.py
Executable file
174
skills/claude-agent-sdk/examples/setting_sources.py
Executable 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())
|
||||
511
skills/claude-agent-sdk/examples/streaming_mode.py
Executable file
511
skills/claude-agent-sdk/examples/streaming_mode.py
Executable 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())
|
||||
87
skills/claude-agent-sdk/examples/system_prompt.py
Executable file
87
skills/claude-agent-sdk/examples/system_prompt.py
Executable 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)
|
||||
158
skills/claude-agent-sdk/examples/tool_permission_callback.py
Executable file
158
skills/claude-agent-sdk/examples/tool_permission_callback.py
Executable 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())
|
||||
Reference in New Issue
Block a user