Initial commit
This commit is contained in:
557
skills/claude-agent-sdk/SKILL.md
Normal file
557
skills/claude-agent-sdk/SKILL.md
Normal file
@@ -0,0 +1,557 @@
|
||||
---
|
||||
name: claude-agent-sdk
|
||||
description: This skill should be used when building applications with the Claude Agent SDK (Python). Use for creating orchestrators with subagents, configuring agents programmatically, setting up hooks and permissions, and following SDK best practices. Trigger when implementing agentic workflows, multi-agent systems, or SDK-based automation.
|
||||
---
|
||||
|
||||
# Claude Agent SDK
|
||||
|
||||
Build production-ready applications using the Claude Agent SDK for Python.
|
||||
|
||||
**SDK Version:** This skill targets `claude-agent-sdk>=0.1.6` (Python)
|
||||
|
||||
## Overview
|
||||
|
||||
This skill provides patterns, examples, and best practices for building SDK applications that orchestrate Claude agents.
|
||||
|
||||
## Quick Start
|
||||
|
||||
Copy the template and customize:
|
||||
|
||||
```bash
|
||||
cp assets/sdk-template.py my-app.py
|
||||
# Edit my-app.py - customize agents and workflow
|
||||
chmod +x my-app.py
|
||||
./my-app.py
|
||||
```
|
||||
|
||||
The template includes proper uv script headers, agent definitions, and async patterns.
|
||||
|
||||
## Choosing Between query() and ClaudeSDKClient
|
||||
|
||||
The SDK provides two ways to interact with Claude: the `query()` function for simple one-shot tasks, and `ClaudeSDKClient` for continuous conversations.
|
||||
|
||||
### Quick Comparison
|
||||
|
||||
| Feature | `query()` | `ClaudeSDKClient` |
|
||||
|---------|-----------|-------------------|
|
||||
| **Conversation memory** | No - each call is independent | Yes - maintains context across queries |
|
||||
| **Use case** | One-off tasks, single questions | Multi-turn conversations, complex workflows |
|
||||
| **Complexity** | Simple - one function call | More setup - context manager pattern |
|
||||
| **Hooks support** | No | Yes |
|
||||
| **Custom tools** | No | Yes |
|
||||
| **Interrupts** | No | Yes - can interrupt ongoing operations |
|
||||
| **Session control** | New session each time | Single persistent session |
|
||||
|
||||
> **Important:** Hooks and custom tools (SDK MCP servers) are **only supported with `ClaudeSDKClient`**, not with `query()`. If you need hooks or custom tools, you must use `ClaudeSDKClient`.
|
||||
>
|
||||
> **Note on Async Runtimes:** The SDK works with both `asyncio` and `anyio`. The official SDK examples prefer `anyio.run()` for better async library compatibility, but `asyncio.run()` works equally well. Use whichever fits your project's async runtime.
|
||||
|
||||
### When to Use query()
|
||||
|
||||
Use `query()` for simple, independent tasks where you don't need conversation history:
|
||||
|
||||
```python
|
||||
import anyio # or: import asyncio
|
||||
from claude_agent_sdk import query, ClaudeAgentOptions
|
||||
|
||||
async def analyze_file():
|
||||
"""One-shot file analysis - no conversation needed."""
|
||||
options = ClaudeAgentOptions(
|
||||
system_prompt="You are a code analyzer",
|
||||
allowed_tools=["Read", "Grep", "Glob"],
|
||||
permission_mode="acceptEdits"
|
||||
)
|
||||
|
||||
async for message in query(
|
||||
prompt="Analyze /path/to/file.py for bugs",
|
||||
options=options
|
||||
):
|
||||
print(message)
|
||||
|
||||
anyio.run(analyze_file) # or: asyncio.run(analyze_file())
|
||||
```
|
||||
|
||||
**Best for:**
|
||||
|
||||
- Single analysis tasks
|
||||
- Independent file operations
|
||||
- Quick questions without follow-up
|
||||
- Scripts that run once and exit
|
||||
|
||||
**Key limitation:** Each `query()` call creates a new session with no memory of previous calls.
|
||||
|
||||
### When to Use ClaudeSDKClient
|
||||
|
||||
Use `ClaudeSDKClient` when you need conversation context across multiple interactions:
|
||||
|
||||
```python
|
||||
import anyio # or: import asyncio
|
||||
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, AssistantMessage, TextBlock
|
||||
|
||||
async def interactive_debugging():
|
||||
"""Multi-turn debugging conversation with context."""
|
||||
options = ClaudeAgentOptions(
|
||||
system_prompt="You are a debugging assistant",
|
||||
allowed_tools=["Read", "Grep", "Bash"],
|
||||
permission_mode="acceptEdits"
|
||||
)
|
||||
|
||||
async with ClaudeSDKClient(options=options) as client:
|
||||
# First query
|
||||
await client.query("Find all TODO comments in /path/to/project")
|
||||
async for message in client.receive_response():
|
||||
if isinstance(message, AssistantMessage):
|
||||
for block in message.content:
|
||||
if isinstance(block, TextBlock):
|
||||
print(f"Claude: {block.text}")
|
||||
|
||||
# Follow-up - Claude remembers the TODOs found above
|
||||
await client.query("Now prioritize them by complexity")
|
||||
async for message in client.receive_response():
|
||||
if isinstance(message, AssistantMessage):
|
||||
for block in message.content:
|
||||
if isinstance(block, TextBlock):
|
||||
print(f"Claude: {block.text}")
|
||||
|
||||
# Another follow-up - still in same conversation
|
||||
await client.query("Create a plan to address the top 3")
|
||||
async for message in client.receive_response():
|
||||
if isinstance(message, AssistantMessage):
|
||||
for block in message.content:
|
||||
if isinstance(block, TextBlock):
|
||||
print(f"Claude: {block.text}")
|
||||
|
||||
anyio.run(interactive_debugging) # or: asyncio.run(interactive_debugging())
|
||||
```
|
||||
|
||||
**Best for:**
|
||||
|
||||
- Multi-turn conversations
|
||||
- Interactive workflows
|
||||
- Tasks requiring context from previous responses
|
||||
- Applications with interrupt capability
|
||||
- Orchestrators managing complex workflows
|
||||
|
||||
**Key advantage:** Claude remembers all previous queries and responses in the session.
|
||||
|
||||
**See:** `examples/streaming_mode.py` - Comprehensive ClaudeSDKClient examples with all patterns
|
||||
|
||||
### Advanced: Interrupts with ClaudeSDKClient
|
||||
|
||||
Only `ClaudeSDKClient` supports interrupting ongoing operations:
|
||||
|
||||
```python
|
||||
import anyio # or: import asyncio
|
||||
from claude_agent_sdk import ClaudeSDKClient
|
||||
|
||||
async def interruptible_task():
|
||||
async with ClaudeSDKClient() as client:
|
||||
await client.query("Run a long analysis on /large/codebase")
|
||||
|
||||
# Start processing in background
|
||||
async with anyio.create_task_group() as tg:
|
||||
tg.start_soon(process_messages, client)
|
||||
|
||||
# Simulate user interrupt after 5 seconds
|
||||
await anyio.sleep(5)
|
||||
await client.interrupt()
|
||||
|
||||
async def process_messages(client):
|
||||
async for message in client.receive_response():
|
||||
print(message)
|
||||
|
||||
anyio.run(interruptible_task) # or: asyncio.run(interruptible_task())
|
||||
```
|
||||
|
||||
### Quick Decision Guide
|
||||
|
||||
**Use `query()` if:**
|
||||
|
||||
- Task is self-contained
|
||||
- No follow-up questions needed
|
||||
- Each execution is independent
|
||||
- Simpler code is preferred
|
||||
|
||||
**Use `ClaudeSDKClient` if:**
|
||||
|
||||
- Need conversation memory
|
||||
- Building interactive workflows
|
||||
- Require interrupt capability
|
||||
- Managing complex multi-step processes
|
||||
- Working with orchestrators and subagents
|
||||
|
||||
## Core Patterns
|
||||
|
||||
### 1. Orchestrator with Subagents
|
||||
|
||||
Define a main orchestrator that delegates work to specialized subagents.
|
||||
|
||||
**Critical requirements:**
|
||||
|
||||
- Orchestrator must use `system_prompt={"type": "preset", "preset": "claude_code"}` (provides Task tool knowledge)
|
||||
- Register agents programmatically via `agents={}` parameter (SDK best practice)
|
||||
- Orchestrator must include `"Task"` in `allowed_tools`
|
||||
- Match agent names exactly between definition and usage
|
||||
|
||||
**Example:**
|
||||
|
||||
```python
|
||||
from claude_agent_sdk import AgentDefinition, ClaudeAgentOptions
|
||||
|
||||
options = ClaudeAgentOptions(
|
||||
system_prompt={"type": "preset", "preset": "claude_code"}, # REQUIRED for orchestrators
|
||||
allowed_tools=["Bash", "Task", "Read", "Write"],
|
||||
agents={
|
||||
"analyzer": AgentDefinition(
|
||||
description="Analyzes code structure and patterns",
|
||||
prompt="You are a code analyzer...",
|
||||
tools=["Read", "Grep", "Glob"],
|
||||
model="sonnet"
|
||||
),
|
||||
"fixer": AgentDefinition(
|
||||
description="Fixes identified issues",
|
||||
prompt="You are a code fixer...",
|
||||
tools=["Read", "Edit", "Bash"],
|
||||
model="sonnet"
|
||||
)
|
||||
},
|
||||
permission_mode="acceptEdits",
|
||||
model="claude-sonnet-4-5"
|
||||
)
|
||||
```
|
||||
|
||||
**See:**
|
||||
|
||||
- `references/agent-patterns.md` - Complete agent definition patterns
|
||||
- `examples/agents.py` - Official SDK agent examples with different agent types
|
||||
|
||||
### 2. System Prompt Configuration
|
||||
|
||||
Choose the appropriate system prompt pattern:
|
||||
|
||||
```python
|
||||
# Orchestrator (use claude_code preset) - dict format (official examples prefer this)
|
||||
system_prompt={"type": "preset", "preset": "claude_code"}
|
||||
|
||||
# Shorthand format (equivalent, but less explicit)
|
||||
system_prompt="claude_code"
|
||||
|
||||
# Custom behavior
|
||||
system_prompt="You are a Python expert..."
|
||||
|
||||
# Extend preset with additional instructions
|
||||
system_prompt={
|
||||
"type": "preset",
|
||||
"preset": "claude_code",
|
||||
"append": "Additional domain-specific instructions"
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** The shorthand `system_prompt="claude_code"` is equivalent to `{"type": "preset", "preset": "claude_code"}`. Both are valid. Official examples prefer the dict format for explicitness.
|
||||
|
||||
**See:**
|
||||
|
||||
- `references/system-prompts.md` - Complete system prompt documentation
|
||||
- `examples/system_prompt.py` - Official SDK system prompt examples
|
||||
|
||||
### 3. Tool Restrictions
|
||||
|
||||
Limit subagent tools to minimum needed:
|
||||
|
||||
```python
|
||||
# Read-only analyzer
|
||||
tools=["Read", "Grep", "Glob"]
|
||||
|
||||
# Code modifier
|
||||
tools=["Read", "Edit", "Bash"]
|
||||
|
||||
# Test runner
|
||||
tools=["Bash", "Read"]
|
||||
```
|
||||
|
||||
**See:** `references/agent-patterns.md` for common tool combinations
|
||||
|
||||
### 4. Hooks
|
||||
|
||||
Intercept SDK events to control behavior:
|
||||
|
||||
```python
|
||||
from claude_agent_sdk import HookMatcher
|
||||
|
||||
options = ClaudeAgentOptions(
|
||||
hooks={
|
||||
"PreToolUse": [
|
||||
HookMatcher(matcher="Bash", hooks=[check_bash_command])
|
||||
],
|
||||
"PostToolUse": [
|
||||
HookMatcher(matcher="Bash", hooks=[review_output])
|
||||
]
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
**See:**
|
||||
|
||||
- `references/hooks-guide.md` - Complete hook patterns documentation
|
||||
- `examples/hooks.py` - Official SDK hook examples with all hook types
|
||||
|
||||
### 5. Permission Callbacks
|
||||
|
||||
Fine-grained control over tool usage:
|
||||
|
||||
```python
|
||||
async def permission_callback(tool_name, input_data, context):
|
||||
# Allow read operations
|
||||
if tool_name in ["Read", "Grep", "Glob"]:
|
||||
return PermissionResultAllow()
|
||||
|
||||
# Block dangerous commands
|
||||
if tool_name == "Bash" and "rm -rf" in input_data.get("command", ""):
|
||||
return PermissionResultDeny(message="Dangerous command")
|
||||
|
||||
return PermissionResultAllow()
|
||||
|
||||
options = ClaudeAgentOptions(
|
||||
can_use_tool=permission_callback,
|
||||
permission_mode="default"
|
||||
)
|
||||
```
|
||||
|
||||
**See:**
|
||||
|
||||
- `references/tool-permissions.md` - Complete permission patterns and decision guide
|
||||
- `examples/tool_permission_callback.py` - Official SDK permission callback example
|
||||
|
||||
## Workflow Templates
|
||||
|
||||
### Building an Orchestrator
|
||||
|
||||
Follow these steps to build an effective orchestrator:
|
||||
|
||||
**1. Define agent purposes**
|
||||
|
||||
- What specialized tasks need delegation?
|
||||
- What tools does each agent need?
|
||||
- What constraints should apply?
|
||||
|
||||
**2. Create agent definitions**
|
||||
|
||||
```python
|
||||
agents={
|
||||
"agent-name": AgentDefinition(
|
||||
description="When to use this agent",
|
||||
prompt="Agent's role and behavior",
|
||||
tools=["Tool1", "Tool2"],
|
||||
model="sonnet"
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**3. Configure orchestrator**
|
||||
|
||||
```python
|
||||
options = ClaudeAgentOptions(
|
||||
system_prompt={"type": "preset", "preset": "claude_code"}, # CRITICAL
|
||||
allowed_tools=["Bash", "Task", "Read", "Write"],
|
||||
agents=agents,
|
||||
permission_mode="acceptEdits"
|
||||
)
|
||||
```
|
||||
|
||||
**4. Implement workflow**
|
||||
|
||||
```python
|
||||
async with ClaudeSDKClient(options=options) as client:
|
||||
await client.query("Use 'agent-name' to perform task")
|
||||
|
||||
async for message in client.receive_response():
|
||||
# Process responses
|
||||
pass
|
||||
```
|
||||
|
||||
**See:** `examples/basic-orchestrator.py` for complete working example
|
||||
|
||||
### Loading Agents from Files
|
||||
|
||||
While programmatic registration is recommended, agent content can be stored in markdown files:
|
||||
|
||||
```python
|
||||
import yaml
|
||||
|
||||
def load_agent_definition(path: str) -> AgentDefinition:
|
||||
"""Load agent from markdown file with YAML frontmatter."""
|
||||
with open(path) as f:
|
||||
content = f.read()
|
||||
|
||||
parts = content.split("---")
|
||||
frontmatter = yaml.safe_load(parts[1])
|
||||
prompt = parts[2].strip()
|
||||
|
||||
# Parse tools (comma-separated string or array)
|
||||
tools = frontmatter.get("tools", [])
|
||||
if isinstance(tools, str):
|
||||
tools = [t.strip() for t in tools.split(",")]
|
||||
|
||||
return AgentDefinition(
|
||||
description=frontmatter["description"],
|
||||
prompt=prompt,
|
||||
tools=tools,
|
||||
model=frontmatter.get("model", "inherit")
|
||||
)
|
||||
|
||||
# Load and register programmatically
|
||||
agent = load_agent_definition(".claude/agents/my-agent.md")
|
||||
options = ClaudeAgentOptions(agents={"my-agent": agent})
|
||||
```
|
||||
|
||||
**See:** `references/agent-patterns.md` for complete loading pattern
|
||||
|
||||
## Common Anti-Patterns
|
||||
|
||||
Avoid these common mistakes:
|
||||
|
||||
**❌ Missing orchestrator system prompt**
|
||||
|
||||
```python
|
||||
# Orchestrator won't know how to use Task tool
|
||||
options = ClaudeAgentOptions(agents={...})
|
||||
```
|
||||
|
||||
**✅ Correct orchestrator configuration**
|
||||
|
||||
```python
|
||||
options = ClaudeAgentOptions(
|
||||
system_prompt="claude_code",
|
||||
agents={...}
|
||||
)
|
||||
```
|
||||
|
||||
**❌ Mismatched agent names**
|
||||
|
||||
```python
|
||||
agents={"investigator": AgentDefinition(...)}
|
||||
await client.query("Use 'markdown-investigator'...") # Wrong name
|
||||
```
|
||||
|
||||
**✅ Exact name matching**
|
||||
|
||||
```python
|
||||
agents={"investigator": AgentDefinition(...)}
|
||||
await client.query("Use 'investigator'...") # Matches
|
||||
```
|
||||
|
||||
**❌ Tool/prompt mismatch**
|
||||
|
||||
```python
|
||||
system_prompt="Fix bugs you find"
|
||||
allowed_tools=["Read", "Grep"] # Can't fix, only read
|
||||
```
|
||||
|
||||
**✅ Aligned tools and behavior**
|
||||
|
||||
```python
|
||||
system_prompt="Analyze code for bugs"
|
||||
allowed_tools=["Read", "Grep", "Glob"]
|
||||
```
|
||||
|
||||
**See:** `references/best-practices.md` for complete anti-patterns list
|
||||
|
||||
## Resources
|
||||
|
||||
### references/
|
||||
|
||||
In-depth documentation loaded as needed:
|
||||
|
||||
- `api-reference.md` - Complete Python SDK API reference (types, functions, examples)
|
||||
- `agent-patterns.md` - Agent definition patterns, tool restrictions, best practices
|
||||
- `subagents.md` - Comprehensive subagent patterns and SDK integration
|
||||
- `system-prompts.md` - System prompt configuration (preset, custom, append)
|
||||
- `hooks-guide.md` - Hook patterns for all hook types with examples
|
||||
- `tool-permissions.md` - Permission callback patterns and examples
|
||||
- `best-practices.md` - SDK best practices, anti-patterns, debugging tips
|
||||
- `custom-tools.md` - Creating custom tools with SDK MCP servers (Python-only)
|
||||
- `sessions.md` - Session management and resumption patterns (Python-only)
|
||||
- `skills.md` - Using Agent Skills with the SDK (Python-only)
|
||||
- `slash-commands.md` - Slash commands and custom command creation (Python-only)
|
||||
|
||||
### examples/
|
||||
|
||||
Ready-to-run code examples from official SDK:
|
||||
|
||||
**Getting Started:**
|
||||
|
||||
- `quick_start.py` - Basic query() usage and message handling (start here!)
|
||||
- `basic-orchestrator.py` - Complete orchestrator with analyzer and fixer subagents
|
||||
|
||||
**Core Patterns:**
|
||||
|
||||
- `agents.py` - Programmatic agent definitions with different agent types
|
||||
- `hooks.py` - Comprehensive hook patterns (PreToolUse, PostToolUse, UserPromptSubmit, etc.)
|
||||
- `system_prompt.py` - System prompt patterns (preset, custom, append)
|
||||
- `streaming_mode.py` - Complete ClaudeSDKClient patterns with multi-turn conversations
|
||||
|
||||
**Advanced Features:**
|
||||
|
||||
- `mcp_calculator.py` - Custom tools with SDK MCP server (in-process tool server)
|
||||
- `tool_permission_callback.py` - Permission callbacks with logging and control
|
||||
- `setting_sources.py` - Settings isolation and loading (user/project/local)
|
||||
- `plugin_example.py` - Using plugins with the SDK (relevant for plugin marketplace!)
|
||||
|
||||
### assets/
|
||||
|
||||
Templates and validation tools:
|
||||
|
||||
- `sdk-template.py` - Project template with uv script headers and agent structure
|
||||
- `sdk-validation-checklist.md` - Comprehensive checklist for validating SDK applications against best practices
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when:
|
||||
|
||||
- Creating new Claude Agent SDK applications
|
||||
- Building orchestrators with multiple subagents
|
||||
- Implementing programmatic agent definitions
|
||||
- Configuring hooks or permission callbacks
|
||||
- Validating/reviewing SDK code (use `assets/sdk-validation-checklist.md`)
|
||||
- Migrating from filesystem agent discovery to programmatic registration
|
||||
- Debugging SDK applications (agent not found, Task tool not working)
|
||||
- Following SDK best practices
|
||||
|
||||
Do not use for:
|
||||
|
||||
- Claude Code slash commands or skills (different system)
|
||||
- Direct API usage without SDK
|
||||
- Non-Python implementations (TypeScript SDK has different patterns)
|
||||
|
||||
## Next Steps
|
||||
|
||||
### For Beginners
|
||||
|
||||
1. Start with `examples/quick_start.py` - Learn basic query() usage
|
||||
2. Try `assets/sdk-template.py` - Template for new projects
|
||||
3. Review `examples/basic-orchestrator.py` - See orchestrator pattern
|
||||
|
||||
### For Intermediate Users
|
||||
|
||||
1. Explore core patterns:
|
||||
- `examples/agents.py` - Agent definitions
|
||||
- `examples/system_prompt.py` - System prompt patterns
|
||||
- `examples/streaming_mode.py` - Multi-turn conversations
|
||||
- `examples/hooks.py` - Hook patterns
|
||||
|
||||
### For Advanced Users
|
||||
|
||||
1. Study advanced features:
|
||||
- `examples/tool_permission_callback.py` - Permission control
|
||||
- `examples/mcp_calculator.py` - Custom tools
|
||||
- `examples/setting_sources.py` - Settings management
|
||||
- `examples/plugin_example.py` - Plugin integration
|
||||
|
||||
### Validation & Quality
|
||||
|
||||
1. Validate your code with `assets/sdk-validation-checklist.md`
|
||||
2. Review against best practices in `references/best-practices.md`
|
||||
|
||||
### Reference Documentation
|
||||
|
||||
1. Consult `references/` as needed for detailed patterns
|
||||
96
skills/claude-agent-sdk/assets/sdk-template.py
Executable file
96
skills/claude-agent-sdk/assets/sdk-template.py
Executable file
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env -S uv run --script --quiet
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = [
|
||||
# "claude-agent-sdk>=0.1.6",
|
||||
# ]
|
||||
# ///
|
||||
"""
|
||||
Claude Agent SDK Project Template
|
||||
|
||||
This template provides a starting point for building SDK applications.
|
||||
|
||||
Usage:
|
||||
1. Copy this file to your project
|
||||
2. Customize the agent definitions
|
||||
3. Update the prompt and workflow
|
||||
4. Run: ./your-script.py
|
||||
|
||||
Note: Uses anyio for async runtime (official SDK examples use anyio).
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
import anyio # Official SDK examples use anyio
|
||||
from claude_agent_sdk import (
|
||||
AgentDefinition,
|
||||
AssistantMessage,
|
||||
ClaudeAgentOptions,
|
||||
ClaudeSDKClient,
|
||||
ResultMessage,
|
||||
TextBlock,
|
||||
)
|
||||
|
||||
|
||||
def get_sdk_options() -> ClaudeAgentOptions:
|
||||
"""Configure SDK options with agents."""
|
||||
return ClaudeAgentOptions(
|
||||
# Use claude_code preset for orchestrators
|
||||
system_prompt={"type": "preset", "preset": "claude_code"},
|
||||
# Allow orchestrator to use Task tool for delegation
|
||||
allowed_tools=["Bash", "Task", "Read", "Write"],
|
||||
# Permission mode: "default", "acceptEdits", or "rejectEdits"
|
||||
permission_mode="acceptEdits",
|
||||
# Define subagents programmatically
|
||||
agents={
|
||||
"example-agent": AgentDefinition(
|
||||
description="Replace with agent purpose/when to use",
|
||||
prompt="Replace with agent's system prompt and instructions",
|
||||
tools=["Read", "Grep"], # Limit to needed tools
|
||||
model="sonnet", # or "opus", "haiku", "inherit"
|
||||
),
|
||||
},
|
||||
model="claude-sonnet-4-5",
|
||||
)
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main workflow."""
|
||||
# Verify API key is set
|
||||
if not os.getenv("ANTHROPIC_API_KEY"):
|
||||
print("Error: ANTHROPIC_API_KEY environment variable not set")
|
||||
return
|
||||
|
||||
print("🚀 Starting SDK Application")
|
||||
print("=" * 60)
|
||||
|
||||
# Get SDK configuration
|
||||
options = get_sdk_options()
|
||||
|
||||
# Create client and send query
|
||||
async with ClaudeSDKClient(options=options) as client:
|
||||
# Replace with your actual prompt
|
||||
prompt = "Your task description here"
|
||||
|
||||
print(f"\n📨 Query: {prompt}\n")
|
||||
await client.query(prompt)
|
||||
|
||||
# Stream responses
|
||||
async for message in client.receive_response():
|
||||
if isinstance(message, AssistantMessage):
|
||||
for block in message.content:
|
||||
if isinstance(block, TextBlock):
|
||||
print(f"Claude: {block.text}\n")
|
||||
|
||||
elif isinstance(message, ResultMessage):
|
||||
print("\n" + "=" * 60)
|
||||
print("✅ Complete")
|
||||
if message.duration_ms:
|
||||
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) # Official SDK examples use anyio.run()
|
||||
362
skills/claude-agent-sdk/assets/sdk-validation-checklist.md
Normal file
362
skills/claude-agent-sdk/assets/sdk-validation-checklist.md
Normal file
@@ -0,0 +1,362 @@
|
||||
# Claude Agent SDK Validation Checklist
|
||||
|
||||
This checklist helps validate SDK applications against official patterns and best practices from the claude-agent-sdk skill documentation.
|
||||
|
||||
## Quick Validation
|
||||
|
||||
Use this checklist when:
|
||||
|
||||
- Creating new SDK applications
|
||||
- Reviewing SDK code
|
||||
- Debugging SDK issues
|
||||
- Ensuring alignment with best practices
|
||||
|
||||
---
|
||||
|
||||
## 1. Imports & Dependencies
|
||||
|
||||
### Required Checks
|
||||
|
||||
- [ ] **Async runtime import**
|
||||
- Uses `import anyio` (official SDK examples use anyio)
|
||||
- Comment reflects official preference: `# Official SDK examples use anyio`
|
||||
- **Reference:** Official examples consistently use anyio
|
||||
|
||||
- [ ] **Claude SDK imports are accurate**
|
||||
- `ClaudeAgentOptions` for configuration
|
||||
- `ClaudeSDKClient` for continuous conversations OR `query` for one-shot tasks
|
||||
- `AgentDefinition` if using programmatic agents
|
||||
- Message types: `AssistantMessage`, `ResultMessage`, `TextBlock`
|
||||
- Permission types if using callbacks: `PermissionResultAllow`, `PermissionResultDeny`
|
||||
- **Reference:** `references/api-reference.md`
|
||||
|
||||
- [ ] **UV script headers (if applicable)**
|
||||
- Uses `#!/usr/bin/env -S uv run --script --quiet`
|
||||
- Has PEP 723 dependencies block with `claude-agent-sdk>=0.1.6`
|
||||
- **Reference:** `assets/sdk-template.py` lines 1-7
|
||||
|
||||
---
|
||||
|
||||
## 2. Async Runtime
|
||||
|
||||
### Required Checks
|
||||
|
||||
- [ ] **Runtime execution is correct**
|
||||
- Uses `anyio.run(main)` (official SDK examples use anyio.run())
|
||||
- Comment reflects official preference: `# Official SDK examples use anyio.run()`
|
||||
- **Reference:** Official examples consistently use anyio.run()
|
||||
|
||||
- [ ] **Async/await patterns are correct**
|
||||
- Functions marked as `async def`
|
||||
- Uses `await` for SDK calls
|
||||
- Uses `async for` for message streaming
|
||||
- Uses `async with` for ClaudeSDKClient context manager
|
||||
- **Reference:** `references/best-practices.md` lines 82-94
|
||||
|
||||
---
|
||||
|
||||
## 3. Choosing query() vs ClaudeSDKClient
|
||||
|
||||
### Required Checks
|
||||
|
||||
- [ ] **Correct approach for use case**
|
||||
- `query()`: One-shot tasks, no conversation memory
|
||||
- `ClaudeSDKClient`: Multi-turn conversations, context retention
|
||||
- **Reference:** SKILL.md lines 29-44
|
||||
|
||||
- [ ] **Hooks/Custom tools only with ClaudeSDKClient**
|
||||
- NOT using hooks with `query()` (not supported)
|
||||
- NOT using custom tools with `query()` (not supported)
|
||||
- **Reference:** SKILL.md line 45 (important warning)
|
||||
|
||||
---
|
||||
|
||||
## 4. Orchestrator Configuration
|
||||
|
||||
### Required Checks
|
||||
|
||||
- [ ] **System prompt is set correctly**
|
||||
- Uses `system_prompt={"type": "preset", "preset": "claude_code"}` for orchestrators
|
||||
(official examples use dict format)
|
||||
- OR uses `system_prompt="claude_code"` (string shorthand, equivalent but less explicit)
|
||||
- Custom prompts only for non-orchestrators
|
||||
- **Reference:** Official examples use dict format, SKILL.md lines 226-242,
|
||||
`references/system-prompts.md`
|
||||
|
||||
- [ ] **Task tool is included**
|
||||
- `allowed_tools` includes `"Task"` for orchestrators
|
||||
- Orchestrators cannot delegate without Task tool
|
||||
- **Reference:** SKILL.md line 39, `references/best-practices.md` lines 72-80
|
||||
|
||||
- [ ] **Agent definitions are programmatic**
|
||||
- Agents defined in `agents={}` parameter (preferred)
|
||||
- Clear `description` (when to use agent)
|
||||
- Specific `prompt` (agent instructions)
|
||||
- Minimal `tools` list (principle of least privilege)
|
||||
- **Reference:** SKILL.md lines 195-217, `references/agent-patterns.md`
|
||||
|
||||
---
|
||||
|
||||
## 5. Agent Definitions
|
||||
|
||||
### Required Checks
|
||||
|
||||
- [ ] **Agent definition structure is correct**
|
||||
|
||||
```python
|
||||
AgentDefinition(
|
||||
description="...", # When to use this agent
|
||||
prompt="...", # Agent instructions
|
||||
tools=[...], # Minimal tool set
|
||||
model="sonnet" # or "opus", "haiku", "inherit"
|
||||
)
|
||||
```
|
||||
|
||||
- **Reference:** SKILL.md lines 195-217
|
||||
|
||||
- [ ] **Agent names match references**
|
||||
- Names in `agents={}` match Task tool usage
|
||||
- No naming mismatches between definition and invocation
|
||||
- **Reference:** `references/best-practices.md` lines 43-52
|
||||
|
||||
- [ ] **Tools are restricted to minimum needed**
|
||||
- Read-only agents: `["Read", "Grep", "Glob"]`
|
||||
- Code modifiers: `["Read", "Edit", "Bash"]`
|
||||
- No excessive tool permissions
|
||||
- **Reference:** SKILL.md lines 248-256, `references/best-practices.md` lines 54-71
|
||||
|
||||
---
|
||||
|
||||
## 6. Permission Control
|
||||
|
||||
### Required Checks
|
||||
|
||||
- [ ] **Permission strategy is appropriate**
|
||||
- Simple use case → `permission_mode` only
|
||||
- Complex logic → `can_use_tool` callback
|
||||
- **Reference:** `references/tool-permissions.md` lines 13-114
|
||||
|
||||
- [ ] **Permission mode is valid**
|
||||
- One of: `"acceptEdits"`, `"rejectEdits"`, `"plan"`, `"bypassPermissions"`, `"default"`
|
||||
- Appropriate for use case (e.g., CI/CD uses `"acceptEdits"`)
|
||||
- **Reference:** `references/tool-permissions.md` lines 64-70
|
||||
|
||||
- [ ] **Permission callback (if used) is correct**
|
||||
- Signature: `async def(tool_name, input_data, context) -> PermissionResultAllow | PermissionResultDeny`
|
||||
- Returns early for unmatched tools
|
||||
- Uses `.get()` for safe input_data access
|
||||
- Clear denial messages
|
||||
- **Reference:** `references/tool-permissions.md` lines 120-344, `examples/tool_permission_callback.py`
|
||||
|
||||
---
|
||||
|
||||
## 7. Hooks (if used)
|
||||
|
||||
### Required Checks
|
||||
|
||||
- [ ] **Hooks ONLY used with ClaudeSDKClient**
|
||||
- NOT using hooks with `query()` function
|
||||
- **Reference:** SKILL.md line 45 (critical warning)
|
||||
|
||||
- [ ] **Hook types are supported**
|
||||
- Using ONLY: `PreToolUse`, `PostToolUse`, `UserPromptSubmit`, `Stop`, `SubagentStop`, `PreCompact`
|
||||
- NOT using unsupported: `SessionStart`, `SessionEnd`, `Notification`
|
||||
- **Reference:** `references/hooks-guide.md` line 14 (important warning)
|
||||
|
||||
- [ ] **Hook signature is correct**
|
||||
- `async def(input_data, tool_use_id, context) -> HookJSONOutput`
|
||||
- Returns empty `{}` when hook doesn't apply
|
||||
- Uses `HookMatcher` for tool filtering
|
||||
- **Reference:** `references/hooks-guide.md` lines 46-68, `examples/hooks.py`
|
||||
|
||||
- [ ] **Hook output structure is valid**
|
||||
- Includes `hookEventName` in `hookSpecificOutput`
|
||||
- PreToolUse: includes `permissionDecision` ("allow" or "deny")
|
||||
- Includes clear `reason` and `systemMessage` fields
|
||||
- **Reference:** `references/hooks-guide.md` lines 70-144
|
||||
|
||||
---
|
||||
|
||||
## 8. ClaudeSDKClient Usage
|
||||
|
||||
### Required Checks
|
||||
|
||||
- [ ] **Context manager pattern is used**
|
||||
|
||||
```python
|
||||
async with ClaudeSDKClient(options=options) as client:
|
||||
await client.query(...)
|
||||
async for message in client.receive_response():
|
||||
...
|
||||
```
|
||||
|
||||
- **Reference:** SKILL.md lines 88-124
|
||||
|
||||
- [ ] **Query → receive_response flow**
|
||||
- Calls `await client.query(prompt)` first
|
||||
- Then iterates `async for message in client.receive_response()`
|
||||
- Does NOT interleave queries and receives incorrectly
|
||||
- **Reference:** `examples/streaming_mode.py`
|
||||
|
||||
- [ ] **Interrupts (if used) are correct**
|
||||
- Uses `await client.interrupt()` to stop execution
|
||||
- Only available with ClaudeSDKClient (not query())
|
||||
- **Reference:** SKILL.md lines 139-162
|
||||
|
||||
---
|
||||
|
||||
## 9. Message Handling
|
||||
|
||||
### Required Checks
|
||||
|
||||
- [ ] **Message types are checked correctly**
|
||||
|
||||
```python
|
||||
if isinstance(message, AssistantMessage):
|
||||
for block in message.content:
|
||||
if isinstance(block, TextBlock):
|
||||
print(block.text)
|
||||
elif isinstance(message, ResultMessage):
|
||||
# Handle completion
|
||||
```
|
||||
|
||||
- **Reference:** SKILL.md lines 77-91, `examples/streaming_mode.py`
|
||||
|
||||
- [ ] **TextBlock extraction is correct**
|
||||
- Iterates through `message.content`
|
||||
- Checks `isinstance(block, TextBlock)` before accessing `.text`
|
||||
- **Reference:** `references/best-practices.md` lines 95-113
|
||||
|
||||
- [ ] **ResultMessage handling**
|
||||
- Checks for `message.duration_ms`, `message.total_cost_usd`
|
||||
- Uses optional access (fields may be None)
|
||||
- **Reference:** `assets/sdk-template.py` lines 86-93
|
||||
|
||||
---
|
||||
|
||||
## 10. Error Handling
|
||||
|
||||
### Required Checks
|
||||
|
||||
- [ ] **API key validation**
|
||||
- Checks `os.getenv("ANTHROPIC_API_KEY")` before SDK calls
|
||||
- Provides clear error message if missing
|
||||
- **Reference:** `assets/sdk-template.py` lines 58-63
|
||||
|
||||
- [ ] **Safe dictionary access**
|
||||
- Uses `.get()` for input_data, tool_response fields
|
||||
- Handles missing/None values gracefully
|
||||
- **Reference:** `references/tool-permissions.md` lines 297-344
|
||||
|
||||
- [ ] **Async exception handling**
|
||||
- Try/except blocks for critical sections
|
||||
- Proper cleanup in exception cases
|
||||
- **Reference:** `references/best-practices.md`
|
||||
|
||||
---
|
||||
|
||||
## 11. Settings & Configuration
|
||||
|
||||
### Required Checks
|
||||
|
||||
- [ ] **setting_sources is configured (if needed)**
|
||||
- Default behavior: NO settings loaded (isolated environment)
|
||||
- Explicitly set to load: `["user"]`, `["project"]`, `["local"]`, or combinations like `["user", "project"]`
|
||||
- Understands isolation vs loading tradeoff
|
||||
- **Reference:** `examples/setting_sources.py` (official example shows user, project, local options)
|
||||
|
||||
- [ ] **Model selection is appropriate**
|
||||
- Orchestrator: `"claude-sonnet-4-5"` (simplified, official examples prefer this)
|
||||
or `"claude-sonnet-4-5-20250929"` (dated version)
|
||||
- Subagents: `"sonnet"`, `"opus"`, `"haiku"`, or `"inherit"`
|
||||
- **Reference:** Official examples use `claude-sonnet-4-5`, SKILL.md line 51,
|
||||
`references/agent-patterns.md`
|
||||
|
||||
- [ ] **Budget limits (if needed)**
|
||||
- Uses `max_budget_usd` for cost control
|
||||
- Appropriate for CI/CD and automated workflows
|
||||
- **Reference:** `examples/max_budget_usd.py`
|
||||
|
||||
---
|
||||
|
||||
## 12. Best Practices Compliance
|
||||
|
||||
### Required Checks
|
||||
|
||||
- [ ] **Follows DRY principle**
|
||||
- Options extracted to function (e.g., `get_sdk_options()`)
|
||||
- Reusable patterns not duplicated
|
||||
- **Reference:** `assets/sdk-template.py` lines 33-55
|
||||
|
||||
- [ ] **Clear comments and documentation**
|
||||
- Docstrings for functions
|
||||
- Inline comments for complex logic
|
||||
- Usage notes in module docstring
|
||||
- **Reference:** `assets/sdk-template.py` lines 8-17
|
||||
|
||||
- [ ] **Type hints are used**
|
||||
- Function return types specified
|
||||
- Parameter types for clarity
|
||||
- **Reference:** `assets/sdk-template.py` line 36
|
||||
|
||||
- [ ] **No anti-patterns**
|
||||
- Not using agents for simple tasks (use query() instead)
|
||||
- Not giving excessive tool permissions
|
||||
- Not bypassing permissions without reason
|
||||
- **Reference:** `references/best-practices.md`, skill SKILL.md
|
||||
|
||||
---
|
||||
|
||||
## Validation Summary Template
|
||||
|
||||
After reviewing, fill out this summary:
|
||||
|
||||
### ✅ Passed Checks
|
||||
|
||||
- [ ] Imports & Dependencies
|
||||
- [ ] Async Runtime
|
||||
- [ ] Orchestrator Configuration
|
||||
- [ ] Agent Definitions
|
||||
- [ ] Permission Control
|
||||
- [ ] Message Handling
|
||||
- [ ] Best Practices
|
||||
|
||||
### ❌ Issues Found
|
||||
|
||||
- Issue 1: [Description]
|
||||
- Issue 2: [Description]
|
||||
|
||||
### 🔧 Fixes Required
|
||||
|
||||
1. [Specific fix with line reference]
|
||||
2. [Specific fix with line reference]
|
||||
|
||||
### 📊 Overall Assessment
|
||||
|
||||
- **Accuracy:** [%]
|
||||
- **Alignment with docs:** [High/Medium/Low]
|
||||
- **Production ready:** [Yes/No]
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference Links
|
||||
|
||||
**Core Documentation:**
|
||||
|
||||
- Main skill: `SKILL.md`
|
||||
- API reference: `references/api-reference.md`
|
||||
- Best practices: `references/best-practices.md`
|
||||
|
||||
**Pattern Guides:**
|
||||
|
||||
- Agent patterns: `references/agent-patterns.md`
|
||||
- Hooks: `references/hooks-guide.md`
|
||||
- Permissions: `references/tool-permissions.md`
|
||||
- System prompts: `references/system-prompts.md`
|
||||
- Subagents: `references/subagents.md`
|
||||
|
||||
**Examples:**
|
||||
|
||||
- Quick start: `examples/quick_start.py`
|
||||
- Template: `assets/sdk-template.py`
|
||||
- Complete examples: `examples/*.py`
|
||||
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())
|
||||
191
skills/claude-agent-sdk/references/agent-patterns.md
Normal file
191
skills/claude-agent-sdk/references/agent-patterns.md
Normal file
@@ -0,0 +1,191 @@
|
||||
# Agent and Subagent Definition Patterns
|
||||
|
||||
This guide covers how to define agents and subagents programmatically using the Claude Agent SDK.
|
||||
|
||||
## Core Concepts
|
||||
|
||||
**AgentDefinition** - Defines a specialized agent with specific tools, prompts, and model configuration.
|
||||
|
||||
**Programmatic Definition** - SDK best practice is to define agents programmatically using the `agents` parameter in `ClaudeAgentOptions`, not filesystem auto-discovery.
|
||||
|
||||
## Basic Agent Definition
|
||||
|
||||
```python
|
||||
from claude_agent_sdk import AgentDefinition, ClaudeAgentOptions
|
||||
|
||||
options = ClaudeAgentOptions(
|
||||
agents={
|
||||
"agent-name": AgentDefinition(
|
||||
description="When to use this agent",
|
||||
prompt="System prompt defining role and behavior",
|
||||
tools=["Read", "Grep", "Glob"],
|
||||
model="sonnet" # or "opus", "haiku", "inherit"
|
||||
)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## AgentDefinition Fields
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `description` | `str` | Yes | Natural language description of when to use this agent |
|
||||
| `prompt` | `str` | Yes | Agent's system prompt defining role and behavior |
|
||||
| `tools` | `list[str]` | No | Array of allowed tool names. If omitted, inherits all tools |
|
||||
| `model` | `str` | No | Model override: "sonnet", "opus", "haiku", or "inherit" |
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Read-Only Analyzer Agent
|
||||
|
||||
For code review, architecture analysis, or documentation review:
|
||||
|
||||
```python
|
||||
"code-reviewer": AgentDefinition(
|
||||
description="Reviews code for best practices, security, and performance",
|
||||
prompt="""You are a code reviewer. Analyze code for:
|
||||
- Security vulnerabilities
|
||||
- Performance issues
|
||||
- Best practice adherence
|
||||
- Potential bugs
|
||||
|
||||
Provide constructive, specific feedback.""",
|
||||
tools=["Read", "Grep", "Glob"],
|
||||
model="sonnet"
|
||||
)
|
||||
```
|
||||
|
||||
### Code Modification Agent
|
||||
|
||||
For implementing features or fixing bugs:
|
||||
|
||||
```python
|
||||
"code-writer": AgentDefinition(
|
||||
description="Implements features and fixes bugs",
|
||||
prompt="""You are a code implementation specialist.
|
||||
Write clean, tested, well-documented code.
|
||||
Follow project conventions and best practices.""",
|
||||
tools=["Read", "Write", "Edit", "Grep", "Glob"],
|
||||
model="sonnet"
|
||||
)
|
||||
```
|
||||
|
||||
### Test Execution Agent
|
||||
|
||||
For running tests and analyzing results:
|
||||
|
||||
```python
|
||||
"test-runner": AgentDefinition(
|
||||
description="Runs tests and analyzes results",
|
||||
prompt="""You are a testing specialist.
|
||||
Execute tests, analyze failures, and provide clear diagnostics.
|
||||
Focus on actionable feedback.""",
|
||||
tools=["Bash", "Read", "Grep"],
|
||||
model="sonnet"
|
||||
)
|
||||
```
|
||||
|
||||
### Multiple Agents Pattern
|
||||
|
||||
Orchestrator with specialized subagents:
|
||||
|
||||
```python
|
||||
options = ClaudeAgentOptions(
|
||||
system_prompt="claude_code", # Orchestrator needs Task tool knowledge
|
||||
allowed_tools=["Bash", "Task", "Read", "Write"],
|
||||
agents={
|
||||
"analyzer": AgentDefinition(
|
||||
description="Analyzes code structure and patterns",
|
||||
prompt="You are a code analyzer. Examine structure, patterns, and architecture.",
|
||||
tools=["Read", "Grep", "Glob"]
|
||||
),
|
||||
"fixer": AgentDefinition(
|
||||
description="Fixes identified issues",
|
||||
prompt="You are a code fixer. Apply fixes based on analysis results.",
|
||||
tools=["Read", "Edit", "Bash"],
|
||||
model="sonnet"
|
||||
)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## Loading Agent Definitions from Files
|
||||
|
||||
While programmatic definition is recommended, you can still store agent prompts in markdown files:
|
||||
|
||||
```python
|
||||
import yaml
|
||||
|
||||
def load_agent_definition(path: str) -> AgentDefinition:
|
||||
"""Load agent definition from markdown file with YAML frontmatter."""
|
||||
with open(path) as f:
|
||||
content = f.read()
|
||||
|
||||
parts = content.split("---")
|
||||
frontmatter = yaml.safe_load(parts[1])
|
||||
system_prompt = parts[2].strip()
|
||||
|
||||
# Parse tools (can be comma-separated string or array)
|
||||
tools_value = frontmatter.get("tools", [])
|
||||
if isinstance(tools_value, str):
|
||||
tools = [t.strip() for t in tools_value.split(",")]
|
||||
else:
|
||||
tools = tools_value
|
||||
|
||||
return AgentDefinition(
|
||||
description=frontmatter["description"],
|
||||
prompt=system_prompt,
|
||||
tools=tools,
|
||||
model=frontmatter.get("model", "inherit")
|
||||
)
|
||||
|
||||
# Load and register programmatically
|
||||
investigator = load_agent_definition(".claude/agents/investigator.md")
|
||||
|
||||
options = ClaudeAgentOptions(
|
||||
agents={"investigator": investigator}
|
||||
)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use programmatic registration** - Define agents via `agents` parameter, not filesystem auto-discovery
|
||||
2. **Set orchestrator system_prompt** - Main agent needs `system_prompt="claude_code"` to use Task tool
|
||||
3. **Specific descriptions** - Agent descriptions determine when they're used
|
||||
4. **Restrict tools** - Limit agent tools to minimum needed for safety and clarity
|
||||
5. **Match agent names** - Ensure agent names in `agents={}` match what orchestrator references
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
❌ **Relying on filesystem auto-discovery**
|
||||
|
||||
```python
|
||||
# SDK will auto-discover .claude/agents/*.md but this is NOT recommended
|
||||
options = ClaudeAgentOptions() # Missing explicit agents={}
|
||||
```
|
||||
|
||||
✅ **Programmatic registration**
|
||||
|
||||
```python
|
||||
options = ClaudeAgentOptions(
|
||||
agents={"agent-name": AgentDefinition(...)}
|
||||
)
|
||||
```
|
||||
|
||||
❌ **Missing orchestrator system prompt**
|
||||
|
||||
```python
|
||||
options = ClaudeAgentOptions(
|
||||
agents={...}
|
||||
# Missing system_prompt="claude_code"
|
||||
)
|
||||
```
|
||||
|
||||
✅ **Proper orchestrator configuration**
|
||||
|
||||
```python
|
||||
options = ClaudeAgentOptions(
|
||||
system_prompt="claude_code", # Orchestrator knows how to use Task tool
|
||||
agents={...}
|
||||
)
|
||||
```
|
||||
1848
skills/claude-agent-sdk/references/api-reference.md
Normal file
1848
skills/claude-agent-sdk/references/api-reference.md
Normal file
File diff suppressed because it is too large
Load Diff
406
skills/claude-agent-sdk/references/best-practices.md
Normal file
406
skills/claude-agent-sdk/references/best-practices.md
Normal file
@@ -0,0 +1,406 @@
|
||||
# Claude Agent SDK Best Practices
|
||||
|
||||
This guide captures best practices and common patterns for building effective SDK applications.
|
||||
|
||||
## Agent Definition
|
||||
|
||||
### ✅ Use Programmatic Registration
|
||||
|
||||
**Recommended:** Define agents via `agents` parameter
|
||||
|
||||
```python
|
||||
options = ClaudeAgentOptions(
|
||||
agents={
|
||||
"investigator": AgentDefinition(
|
||||
description="Analyzes errors autonomously",
|
||||
prompt="You are an error investigator...",
|
||||
tools=["Read", "Grep", "Glob", "Bash"]
|
||||
)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
**Not Recommended:** Relying on filesystem auto-discovery
|
||||
|
||||
```python
|
||||
# SDK can auto-discover .claude/agents/*.md
|
||||
# but programmatic registration is clearer and more maintainable
|
||||
options = ClaudeAgentOptions()
|
||||
```
|
||||
|
||||
### ✅ Set Orchestrator System Prompt
|
||||
|
||||
**Critical:** Orchestrators must use `system_prompt="claude_code"`
|
||||
|
||||
```python
|
||||
options = ClaudeAgentOptions(
|
||||
system_prompt="claude_code", # Knows how to use Task tool
|
||||
allowed_tools=["Bash", "Task", "Read", "Write"],
|
||||
agents={...}
|
||||
)
|
||||
```
|
||||
|
||||
**Why:** The claude_code preset includes knowledge of the Task tool for delegating to subagents.
|
||||
|
||||
### ✅ Match Agent Names
|
||||
|
||||
Ensure agent names in `agents={}` match references in prompts:
|
||||
|
||||
```python
|
||||
# Define agent
|
||||
options = ClaudeAgentOptions(
|
||||
agents={"markdown-investigator": AgentDefinition(...)}
|
||||
)
|
||||
|
||||
# Reference in prompt
|
||||
await client.query("Use the 'markdown-investigator' subagent...")
|
||||
```
|
||||
|
||||
## Tool Configuration
|
||||
|
||||
### ✅ Restrict Subagent Tools
|
||||
|
||||
Limit subagent tools to minimum needed:
|
||||
|
||||
```python
|
||||
# Read-only analyzer
|
||||
"analyzer": AgentDefinition(
|
||||
tools=["Read", "Grep", "Glob"]
|
||||
)
|
||||
|
||||
# Code modifier
|
||||
"fixer": AgentDefinition(
|
||||
tools=["Read", "Edit", "Bash"]
|
||||
)
|
||||
```
|
||||
|
||||
### ✅ Give Orchestrator Task Tool
|
||||
|
||||
Orchestrators need Task tool to delegate:
|
||||
|
||||
```python
|
||||
options = ClaudeAgentOptions(
|
||||
allowed_tools=["Bash", "Task", "Read", "Write"], # Include Task
|
||||
agents={...}
|
||||
)
|
||||
```
|
||||
|
||||
## Async/Await Patterns
|
||||
|
||||
### ✅ Use async with for Streaming
|
||||
|
||||
```python
|
||||
async with ClaudeSDKClient(options=options) as client:
|
||||
await client.query(prompt)
|
||||
|
||||
async for message in client.receive_response():
|
||||
if isinstance(message, AssistantMessage):
|
||||
# Process messages
|
||||
pass
|
||||
```
|
||||
|
||||
### ✅ Handle Multiple Message Types
|
||||
|
||||
```python
|
||||
async for message in client.receive_response():
|
||||
if isinstance(message, AssistantMessage):
|
||||
for block in message.content:
|
||||
if isinstance(block, TextBlock):
|
||||
text = block.text
|
||||
|
||||
elif isinstance(message, ResultMessage):
|
||||
print(f"Cost: ${message.total_cost_usd:.4f}")
|
||||
print(f"Duration: {message.duration_ms}ms")
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### ✅ Validate Agent Responses
|
||||
|
||||
Don't assume agents return expected format:
|
||||
|
||||
```python
|
||||
investigation_report = None
|
||||
|
||||
async for message in client.receive_response():
|
||||
if isinstance(message, AssistantMessage):
|
||||
for block in message.content:
|
||||
if isinstance(block, TextBlock):
|
||||
# Try to extract JSON
|
||||
try:
|
||||
investigation_report = json.loads(block.text)
|
||||
except json.JSONDecodeError:
|
||||
# Handle non-JSON response
|
||||
continue
|
||||
|
||||
if not investigation_report:
|
||||
raise RuntimeError("Agent did not return valid report")
|
||||
```
|
||||
|
||||
### ✅ Use uv Script Headers
|
||||
|
||||
For standalone SDK scripts, use uv inline script metadata:
|
||||
|
||||
```python
|
||||
#!/usr/bin/env -S uv run --script --quiet
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = [
|
||||
# "claude-agent-sdk>=0.1.6",
|
||||
# ]
|
||||
# ///
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
### ✅ Organize Agent Definitions
|
||||
|
||||
Option 1: Store in markdown files, load programmatically
|
||||
|
||||
```text
|
||||
project/
|
||||
├── .claude/
|
||||
│ └── agents/
|
||||
│ ├── investigator.md
|
||||
│ └── fixer.md
|
||||
├── main.py
|
||||
```
|
||||
|
||||
```python
|
||||
def load_agent_definition(path: str) -> AgentDefinition:
|
||||
# Parse frontmatter and content
|
||||
# Return AgentDefinition
|
||||
|
||||
investigator = load_agent_definition(".claude/agents/investigator.md")
|
||||
options = ClaudeAgentOptions(agents={"investigator": investigator})
|
||||
```
|
||||
|
||||
Option 2: Define inline
|
||||
|
||||
```python
|
||||
options = ClaudeAgentOptions(
|
||||
agents={
|
||||
"investigator": AgentDefinition(
|
||||
description="...",
|
||||
prompt="...",
|
||||
tools=[...]
|
||||
)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## Permission Management
|
||||
|
||||
### ✅ Choose Appropriate Permission Mode
|
||||
|
||||
```python
|
||||
# Automated workflows (auto-approve edits)
|
||||
options = ClaudeAgentOptions(
|
||||
permission_mode="acceptEdits"
|
||||
)
|
||||
|
||||
# Interactive development (ask for approval)
|
||||
options = ClaudeAgentOptions(
|
||||
permission_mode="default",
|
||||
can_use_tool=permission_callback
|
||||
)
|
||||
|
||||
# Read-only mode (use tool restrictions)
|
||||
options = ClaudeAgentOptions(
|
||||
allowed_tools=["Read", "Grep", "Glob"] # Only read tools
|
||||
)
|
||||
```
|
||||
|
||||
### ✅ Use Hooks for Complex Logic
|
||||
|
||||
Prefer hooks over permission callbacks for:
|
||||
|
||||
- Adding context
|
||||
- Reviewing outputs
|
||||
- Stopping on errors
|
||||
|
||||
```python
|
||||
options = ClaudeAgentOptions(
|
||||
hooks={
|
||||
"PreToolUse": [
|
||||
HookMatcher(matcher="Bash", hooks=[check_command])
|
||||
],
|
||||
"PostToolUse": [
|
||||
HookMatcher(matcher="Bash", hooks=[review_output])
|
||||
]
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## Common Anti-Patterns
|
||||
|
||||
### ❌ Missing System Prompt on Orchestrator
|
||||
|
||||
```python
|
||||
# Orchestrator won't know how to use Task tool
|
||||
options = ClaudeAgentOptions(
|
||||
agents={...}
|
||||
# Missing system_prompt="claude_code"
|
||||
)
|
||||
```
|
||||
|
||||
### ❌ Tool/Prompt Mismatch
|
||||
|
||||
```python
|
||||
# Tells agent to modify files but only allows read tools
|
||||
options = ClaudeAgentOptions(
|
||||
system_prompt="Fix any bugs you find",
|
||||
allowed_tools=["Read", "Grep"] # Can't actually fix
|
||||
)
|
||||
```
|
||||
|
||||
### ❌ Assuming Agent Output Format
|
||||
|
||||
```python
|
||||
# Assumes agent returns JSON
|
||||
json_data = json.loads(message.content[0].text) # May crash
|
||||
```
|
||||
|
||||
### ❌ Not Validating Agent Names
|
||||
|
||||
```python
|
||||
# Define as "investigator" but reference as "markdown-investigator"
|
||||
options = ClaudeAgentOptions(
|
||||
agents={"investigator": AgentDefinition(...)}
|
||||
)
|
||||
await client.query("Use 'markdown-investigator'...") # Won't work
|
||||
```
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### ✅ Use Appropriate Models
|
||||
|
||||
```python
|
||||
# Fast, cheap tasks
|
||||
"simple-agent": AgentDefinition(model="haiku", ...)
|
||||
|
||||
# Complex reasoning
|
||||
"complex-agent": AgentDefinition(model="sonnet", ...)
|
||||
|
||||
# Inherit from main agent
|
||||
"helper-agent": AgentDefinition(model="inherit", ...)
|
||||
```
|
||||
|
||||
### ✅ Set Budget Limits
|
||||
|
||||
```python
|
||||
options = ClaudeAgentOptions(
|
||||
max_budget_usd=1.00 # Stop after $1
|
||||
)
|
||||
```
|
||||
|
||||
### ✅ Limit Turns for Simple Tasks
|
||||
|
||||
```python
|
||||
options = ClaudeAgentOptions(
|
||||
max_turns=3 # Prevent infinite loops
|
||||
)
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### ✅ Validate Agent Definitions
|
||||
|
||||
```python
|
||||
def test_agent_configuration():
|
||||
"""Ensure agent definitions are valid."""
|
||||
options = get_sdk_options()
|
||||
|
||||
# Check orchestrator has claude_code preset
|
||||
# Note: Can be string "claude_code" or dict {"type": "preset", "preset": "claude_code"}
|
||||
assert options.system_prompt in ("claude_code", {"type": "preset", "preset": "claude_code"})
|
||||
|
||||
# Check orchestrator has Task tool
|
||||
assert "Task" in options.allowed_tools
|
||||
|
||||
# Check agents are registered
|
||||
assert "investigator" in options.agents
|
||||
assert "fixer" in options.agents
|
||||
```
|
||||
|
||||
### ✅ Test Tool Restrictions
|
||||
|
||||
```python
|
||||
def test_subagent_tools():
|
||||
"""Ensure subagents have correct tools."""
|
||||
options = get_sdk_options()
|
||||
|
||||
investigator = options.agents["investigator"]
|
||||
assert "Read" in investigator.tools
|
||||
assert "Write" not in investigator.tools # Read-only
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
### ✅ Document Agent Purposes
|
||||
|
||||
```python
|
||||
options = ClaudeAgentOptions(
|
||||
agents={
|
||||
"investigator": AgentDefinition(
|
||||
# Clear, specific description
|
||||
description=(
|
||||
"Autonomous analyzer that determines if markdown errors "
|
||||
"are fixable or false positives"
|
||||
),
|
||||
prompt="...",
|
||||
tools=[...]
|
||||
)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### ✅ Document Workflow
|
||||
|
||||
```python
|
||||
"""
|
||||
Intelligent Markdown Linting Orchestrator
|
||||
|
||||
Architecture:
|
||||
- Orchestrator (main): Strategic coordination
|
||||
- Investigator subagent: Autonomous error analysis
|
||||
- Fixer subagent: Execute fixes with context
|
||||
|
||||
Workflow:
|
||||
1. Discovery: Run linter, parse output
|
||||
2. Triage: Classify errors (simple vs ambiguous)
|
||||
3. Investigation: Investigator analyzes ambiguous errors
|
||||
4. Fixing: Fixer applies fixes based on investigation
|
||||
5. Verification: Re-run linter to confirm fixes
|
||||
"""
|
||||
```
|
||||
|
||||
## Debugging
|
||||
|
||||
### ✅ Log Agent Communication
|
||||
|
||||
```python
|
||||
all_response_text = []
|
||||
|
||||
async for message in client.receive_response():
|
||||
if isinstance(message, AssistantMessage):
|
||||
for block in message.content:
|
||||
if isinstance(block, TextBlock):
|
||||
all_response_text.append(block.text)
|
||||
print(f"Agent: {block.text}")
|
||||
|
||||
# Save full transcript for debugging
|
||||
with open("debug_transcript.txt", "w") as f:
|
||||
f.write("\n\n".join(all_response_text))
|
||||
```
|
||||
|
||||
### ✅ Track Costs
|
||||
|
||||
```python
|
||||
async for message in client.receive_response():
|
||||
if isinstance(message, ResultMessage):
|
||||
if message.total_cost_usd:
|
||||
print(f"Total cost: ${message.total_cost_usd:.4f}")
|
||||
if message.duration_ms:
|
||||
print(f"Duration: {message.duration_ms}ms")
|
||||
```
|
||||
715
skills/claude-agent-sdk/references/custom-tools.md
Normal file
715
skills/claude-agent-sdk/references/custom-tools.md
Normal file
@@ -0,0 +1,715 @@
|
||||
# Custom Tools
|
||||
|
||||
> Build and integrate custom tools to extend Claude Agent SDK functionality
|
||||
|
||||
Custom tools allow you to extend Claude Code's capabilities with your own
|
||||
functionality through in-process MCP servers, enabling Claude to interact with
|
||||
external services, APIs, or perform specialized operations.
|
||||
|
||||
## Creating Custom Tools
|
||||
|
||||
Use the `createSdkMcpServer` and `tool` helper functions to define type-safe custom tools:
|
||||
|
||||
```typescript
|
||||
const customServer = createSdkMcpServer({
|
||||
name: "my-custom-tools",
|
||||
version: "1.0.0",
|
||||
tools: [
|
||||
tool(
|
||||
"get_weather",
|
||||
"Get current weather for a location",
|
||||
{
|
||||
location: z.string().describe("City name or coordinates"),
|
||||
units: z.enum(["celsius", "fahrenheit"]).default("celsius").describe("Temperature units")
|
||||
},
|
||||
async (args) => {
|
||||
// Call weather API
|
||||
const response = await fetch(
|
||||
`https://api.weather.com/v1/current?q=${args.location}&units=${args.units}`
|
||||
);
|
||||
const data = await response.json();
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Temperature: ${data.temp}°\nConditions: ${data.conditions}\nHumidity: ${data.humidity}%`
|
||||
}]
|
||||
};
|
||||
}
|
||||
)
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
```python
|
||||
from claude_agent_sdk import tool, create_sdk_mcp_server, ClaudeSDKClient, ClaudeAgentOptions
|
||||
from typing import Any
|
||||
import aiohttp
|
||||
|
||||
# Define a custom tool using the @tool decorator
|
||||
@tool("get_weather", "Get current weather for a location", {"location": str, "units": str})
|
||||
async def get_weather(args: dict[str, Any]) -> dict[str, Any]:
|
||||
# Call weather API
|
||||
units = args.get('units', 'celsius')
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(
|
||||
f"https://api.weather.com/v1/current?q={args['location']}&units={units}"
|
||||
) as response:
|
||||
data = await response.json()
|
||||
|
||||
return {
|
||||
"content": [{
|
||||
"type": "text",
|
||||
"text": f"Temperature: {data['temp']}°\nConditions: {data['conditions']}\nHumidity: {data['humidity']}%"
|
||||
}]
|
||||
}
|
||||
|
||||
# Create an SDK MCP server with the custom tool
|
||||
custom_server = create_sdk_mcp_server(
|
||||
name="my-custom-tools",
|
||||
version="1.0.0",
|
||||
tools=[get_weather] # Pass the decorated function
|
||||
)
|
||||
```
|
||||
|
||||
## Using Custom Tools
|
||||
|
||||
Pass the custom server to the `query` function via the `mcpServers` option as a dictionary/object.
|
||||
|
||||
**Important:** Custom MCP tools require streaming input mode. You must use an
|
||||
async generator/iterable for the `prompt` parameter - a simple string will not
|
||||
work with MCP servers.
|
||||
|
||||
### Tool Name Format
|
||||
|
||||
When MCP tools are exposed to Claude, their names follow a specific format:
|
||||
|
||||
* Pattern: `mcp__{server_name}__{tool_name}`
|
||||
* Example: A tool named `get_weather` in server `my-custom-tools` becomes `mcp__my-custom-tools__get_weather`
|
||||
|
||||
### Configuring Allowed Tools
|
||||
|
||||
You can control which tools Claude can use via the `allowedTools` option:
|
||||
|
||||
```typescript
|
||||
async function* generateMessages() {
|
||||
yield {
|
||||
type: "user",
|
||||
message: {
|
||||
content: "What's the weather in San Francisco?"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
for await (const message of query({
|
||||
prompt: generateMessages(), // Use async generator for streaming input
|
||||
options: {
|
||||
mcpServers: {
|
||||
"my-custom-tools": customServer // Pass as object/dictionary, not array
|
||||
},
|
||||
// Optionally specify which tools Claude can use
|
||||
allowedTools: [
|
||||
"mcp__my-custom-tools__get_weather", // Allow the weather tool
|
||||
// Add other tools as needed
|
||||
],
|
||||
maxTurns: 3
|
||||
}
|
||||
})) {
|
||||
if (message.type === "result" && message.subtype === "success") {
|
||||
console.log(message.result);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```python
|
||||
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions
|
||||
import asyncio
|
||||
|
||||
# Use the custom tools with Claude
|
||||
options = ClaudeAgentOptions(
|
||||
mcp_servers={"my-custom-tools": custom_server},
|
||||
allowed_tools=[
|
||||
"mcp__my-custom-tools__get_weather", # Allow the weather tool
|
||||
# Add other tools as needed
|
||||
]
|
||||
)
|
||||
|
||||
async def main():
|
||||
async with ClaudeSDKClient(options=options) as client:
|
||||
await client.query("What's the weather in San Francisco?")
|
||||
|
||||
# Extract and print response
|
||||
async for msg in client.receive_response():
|
||||
print(msg)
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## Multiple Tools Example
|
||||
|
||||
When your MCP server has multiple tools, you can selectively allow them:
|
||||
|
||||
```typescript
|
||||
const multiToolServer = createSdkMcpServer({
|
||||
name: "utilities",
|
||||
version: "1.0.0",
|
||||
tools: [
|
||||
tool("calculate", "Perform calculations", { /* ... */ }, async (args) => { /* ... */ }),
|
||||
tool("translate", "Translate text", { /* ... */ }, async (args) => { /* ... */ }),
|
||||
tool("search_web", "Search the web", { /* ... */ }, async (args) => { /* ... */ })
|
||||
]
|
||||
});
|
||||
|
||||
// Allow only specific tools with streaming input
|
||||
async function* generateMessages() {
|
||||
yield {
|
||||
type: "user",
|
||||
message: {
|
||||
content: "Calculate 5 + 3 and translate 'hello' to Spanish"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
for await (const message of query({
|
||||
prompt: generateMessages(), // Use async generator for streaming input
|
||||
options: {
|
||||
mcpServers: {
|
||||
utilities: multiToolServer
|
||||
},
|
||||
allowedTools: [
|
||||
"mcp__utilities__calculate", // Allow calculator
|
||||
"mcp__utilities__translate", // Allow translator
|
||||
// "mcp__utilities__search_web" is NOT allowed
|
||||
]
|
||||
}
|
||||
})) {
|
||||
// Process messages
|
||||
}
|
||||
```
|
||||
|
||||
```python
|
||||
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, tool, create_sdk_mcp_server
|
||||
from typing import Any
|
||||
import asyncio
|
||||
|
||||
# Define multiple tools using the @tool decorator
|
||||
@tool("calculate", "Perform calculations", {"expression": str})
|
||||
async def calculate(args: dict[str, Any]) -> dict[str, Any]:
|
||||
result = eval(args["expression"]) # Use safe eval in production
|
||||
return {"content": [{"type": "text", "text": f"Result: {result}"}]}
|
||||
|
||||
@tool("translate", "Translate text", {"text": str, "target_lang": str})
|
||||
async def translate(args: dict[str, Any]) -> dict[str, Any]:
|
||||
# Translation logic here
|
||||
return {"content": [{"type": "text", "text": f"Translated: {args['text']}"}]}
|
||||
|
||||
@tool("search_web", "Search the web", {"query": str})
|
||||
async def search_web(args: dict[str, Any]) -> dict[str, Any]:
|
||||
# Search logic here
|
||||
return {"content": [{"type": "text", "text": f"Search results for: {args['query']}"}]}
|
||||
|
||||
multi_tool_server = create_sdk_mcp_server(
|
||||
name="utilities",
|
||||
version="1.0.0",
|
||||
tools=[calculate, translate, search_web] # Pass decorated functions
|
||||
)
|
||||
|
||||
# Allow only specific tools with streaming input
|
||||
async def message_generator():
|
||||
yield {
|
||||
"type": "user",
|
||||
"message": {
|
||||
"role": "user",
|
||||
"content": "Calculate 5 + 3 and translate 'hello' to Spanish"
|
||||
}
|
||||
}
|
||||
|
||||
async for message in query(
|
||||
prompt=message_generator(), # Use async generator for streaming input
|
||||
options=ClaudeAgentOptions(
|
||||
mcp_servers={"utilities": multi_tool_server},
|
||||
allowed_tools=[
|
||||
"mcp__utilities__calculate", # Allow calculator
|
||||
"mcp__utilities__translate", # Allow translator
|
||||
# "mcp__utilities__search_web" is NOT allowed
|
||||
]
|
||||
)
|
||||
):
|
||||
if hasattr(message, 'result'):
|
||||
print(message.result)
|
||||
```
|
||||
|
||||
## Type Safety with Python
|
||||
|
||||
The `@tool` decorator supports various schema definition approaches for type safety:
|
||||
|
||||
```typescript
|
||||
tool(
|
||||
"process_data",
|
||||
"Process structured data with type safety",
|
||||
{
|
||||
// Zod schema defines both runtime validation and TypeScript types
|
||||
data: z.object({
|
||||
name: z.string(),
|
||||
age: z.number().min(0).max(150),
|
||||
email: z.string().email(),
|
||||
preferences: z.array(z.string()).optional()
|
||||
}),
|
||||
format: z.enum(["json", "csv", "xml"]).default("json")
|
||||
},
|
||||
async (args) => {
|
||||
// args is fully typed based on the schema
|
||||
// TypeScript knows: args.data.name is string, args.data.age is number, etc.
|
||||
console.log(`Processing ${args.data.name}'s data as ${args.format}`);
|
||||
|
||||
// Your processing logic here
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Processed data for ${args.data.name}`
|
||||
}]
|
||||
};
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
```python
|
||||
from typing import Any
|
||||
|
||||
# Simple type mapping - recommended for most cases
|
||||
@tool(
|
||||
"process_data",
|
||||
"Process structured data with type safety",
|
||||
{
|
||||
"name": str,
|
||||
"age": int,
|
||||
"email": str,
|
||||
"preferences": list # Optional parameters can be handled in the function
|
||||
}
|
||||
)
|
||||
async def process_data(args: dict[str, Any]) -> dict[str, Any]:
|
||||
# Access arguments with type hints for IDE support
|
||||
name = args["name"]
|
||||
age = args["age"]
|
||||
email = args["email"]
|
||||
preferences = args.get("preferences", [])
|
||||
|
||||
print(f"Processing {name}'s data (age: {age})")
|
||||
|
||||
return {
|
||||
"content": [{
|
||||
"type": "text",
|
||||
"text": f"Processed data for {name}"
|
||||
}]
|
||||
}
|
||||
|
||||
# For more complex schemas, you can use JSON Schema format
|
||||
@tool(
|
||||
"advanced_process",
|
||||
"Process data with advanced validation",
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"age": {"type": "integer", "minimum": 0, "maximum": 150},
|
||||
"email": {"type": "string", "format": "email"},
|
||||
"format": {"type": "string", "enum": ["json", "csv", "xml"], "default": "json"}
|
||||
},
|
||||
"required": ["name", "age", "email"]
|
||||
}
|
||||
)
|
||||
async def advanced_process(args: dict[str, Any]) -> dict[str, Any]:
|
||||
# Process with advanced schema validation
|
||||
return {
|
||||
"content": [{
|
||||
"type": "text",
|
||||
"text": f"Advanced processing for {args['name']}"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
Handle errors gracefully to provide meaningful feedback:
|
||||
|
||||
```typescript
|
||||
tool(
|
||||
"fetch_data",
|
||||
"Fetch data from an API",
|
||||
{
|
||||
endpoint: z.string().url().describe("API endpoint URL")
|
||||
},
|
||||
async (args) => {
|
||||
try {
|
||||
const response = await fetch(args.endpoint);
|
||||
|
||||
if (!response.ok) {
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `API error: ${response.status} ${response.statusText}`
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(data, null, 2)
|
||||
}]
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Failed to fetch data: ${error.message}`
|
||||
}]
|
||||
};
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
```python
|
||||
import json
|
||||
import aiohttp
|
||||
from typing import Any
|
||||
|
||||
@tool(
|
||||
"fetch_data",
|
||||
"Fetch data from an API",
|
||||
{"endpoint": str} # Simple schema
|
||||
)
|
||||
async def fetch_data(args: dict[str, Any]) -> dict[str, Any]:
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(args["endpoint"]) as response:
|
||||
if response.status != 200:
|
||||
return {
|
||||
"content": [{
|
||||
"type": "text",
|
||||
"text": f"API error: {response.status} {response.reason}"
|
||||
}]
|
||||
}
|
||||
|
||||
data = await response.json()
|
||||
return {
|
||||
"content": [{
|
||||
"type": "text",
|
||||
"text": json.dumps(data, indent=2)
|
||||
}]
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"content": [{
|
||||
"type": "text",
|
||||
"text": f"Failed to fetch data: {str(e)}"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
## Example Tools
|
||||
|
||||
### Database Query Tool
|
||||
|
||||
```typescript
|
||||
const databaseServer = createSdkMcpServer({
|
||||
name: "database-tools",
|
||||
version: "1.0.0",
|
||||
tools: [
|
||||
tool(
|
||||
"query_database",
|
||||
"Execute a database query",
|
||||
{
|
||||
query: z.string().describe("SQL query to execute"),
|
||||
params: z.array(z.any()).optional().describe("Query parameters")
|
||||
},
|
||||
async (args) => {
|
||||
const results = await db.query(args.query, args.params || []);
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Found ${results.length} rows:\n${JSON.stringify(results, null, 2)}`
|
||||
}]
|
||||
};
|
||||
}
|
||||
)
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
```python
|
||||
from typing import Any
|
||||
import json
|
||||
|
||||
@tool(
|
||||
"query_database",
|
||||
"Execute a database query",
|
||||
{"query": str, "params": list} # Simple schema with list type
|
||||
)
|
||||
async def query_database(args: dict[str, Any]) -> dict[str, Any]:
|
||||
results = await db.query(args["query"], args.get("params", []))
|
||||
return {
|
||||
"content": [{
|
||||
"type": "text",
|
||||
"text": f"Found {len(results)} rows:\n{json.dumps(results, indent=2)}"
|
||||
}]
|
||||
}
|
||||
|
||||
database_server = create_sdk_mcp_server(
|
||||
name="database-tools",
|
||||
version="1.0.0",
|
||||
tools=[query_database] # Pass the decorated function
|
||||
)
|
||||
```
|
||||
|
||||
### API Gateway Tool
|
||||
|
||||
```typescript
|
||||
const apiGatewayServer = createSdkMcpServer({
|
||||
name: "api-gateway",
|
||||
version: "1.0.0",
|
||||
tools: [
|
||||
tool(
|
||||
"api_request",
|
||||
"Make authenticated API requests to external services",
|
||||
{
|
||||
service: z.enum(["stripe", "github", "openai", "slack"]).describe("Service to call"),
|
||||
endpoint: z.string().describe("API endpoint path"),
|
||||
method: z.enum(["GET", "POST", "PUT", "DELETE"]).describe("HTTP method"),
|
||||
body: z.record(z.any()).optional().describe("Request body"),
|
||||
query: z.record(z.string()).optional().describe("Query parameters")
|
||||
},
|
||||
async (args) => {
|
||||
const config = {
|
||||
stripe: { baseUrl: "<https://api.stripe.com/v1>", key: process.env.STRIPE_KEY },
|
||||
github: { baseUrl: "<https://api.github.com>", key: process.env.GITHUB_TOKEN },
|
||||
openai: { baseUrl: "<https://api.openai.com/v1>", key: process.env.OPENAI_KEY },
|
||||
slack: { baseUrl: "<https://slack.com/api>", key: process.env.SLACK_TOKEN }
|
||||
};
|
||||
|
||||
const { baseUrl, key } = config[args.service];
|
||||
const url = new URL(`${baseUrl}${args.endpoint}`);
|
||||
|
||||
if (args.query) {
|
||||
Object.entries(args.query).forEach(([k, v]) => url.searchParams.set(k, v));
|
||||
}
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: args.method,
|
||||
headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
|
||||
body: args.body ? JSON.stringify(args.body) : undefined
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(data, null, 2)
|
||||
}]
|
||||
};
|
||||
}
|
||||
)
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
```python
|
||||
import os
|
||||
import json
|
||||
import aiohttp
|
||||
from typing import Any
|
||||
|
||||
# For complex schemas with enums, use JSON Schema format
|
||||
@tool(
|
||||
"api_request",
|
||||
"Make authenticated API requests to external services",
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"service": {"type": "string", "enum": ["stripe", "github", "openai", "slack"]},
|
||||
"endpoint": {"type": "string"},
|
||||
"method": {"type": "string", "enum": ["GET", "POST", "PUT", "DELETE"]},
|
||||
"body": {"type": "object"},
|
||||
"query": {"type": "object"}
|
||||
},
|
||||
"required": ["service", "endpoint", "method"]
|
||||
}
|
||||
)
|
||||
async def api_request(args: dict[str, Any]) -> dict[str, Any]:
|
||||
config = {
|
||||
"stripe": {"base_url": "<https://api.stripe.com/v1>", "key": os.environ["STRIPE_KEY"]},
|
||||
"github": {"base_url": "<https://api.github.com>", "key": os.environ["GITHUB_TOKEN"]},
|
||||
"openai": {"base_url": "<https://api.openai.com/v1>", "key": os.environ["OPENAI_KEY"]},
|
||||
"slack": {"base_url": "<https://slack.com/api>", "key": os.environ["SLACK_TOKEN"]}
|
||||
}
|
||||
|
||||
service_config = config[args["service"]]
|
||||
url = f"{service_config['base_url']}{args['endpoint']}"
|
||||
|
||||
if args.get("query"):
|
||||
params = "&".join([f"{k}={v}" for k, v in args["query"].items()])
|
||||
url += f"?{params}"
|
||||
|
||||
headers = {"Authorization": f"Bearer {service_config['key']}", "Content-Type": "application/json"}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.request(
|
||||
args["method"], url, headers=headers, json=args.get("body")
|
||||
) as response:
|
||||
data = await response.json()
|
||||
return {
|
||||
"content": [{
|
||||
"type": "text",
|
||||
"text": json.dumps(data, indent=2)
|
||||
}]
|
||||
}
|
||||
|
||||
api_gateway_server = create_sdk_mcp_server(
|
||||
name="api-gateway",
|
||||
version="1.0.0",
|
||||
tools=[api_request] # Pass the decorated function
|
||||
)
|
||||
```
|
||||
|
||||
## Calculator Tool
|
||||
|
||||
```typescript
|
||||
const calculatorServer = createSdkMcpServer({
|
||||
name: "calculator",
|
||||
version: "1.0.0",
|
||||
tools: [
|
||||
tool(
|
||||
"calculate",
|
||||
"Perform mathematical calculations",
|
||||
{
|
||||
expression: z.string().describe("Mathematical expression to evaluate"),
|
||||
precision: z.number().optional().default(2).describe("Decimal precision")
|
||||
},
|
||||
async (args) => {
|
||||
try {
|
||||
// Use a safe math evaluation library in production
|
||||
const result = eval(args.expression); // Example only!
|
||||
const formatted = Number(result).toFixed(args.precision);
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `${args.expression} = ${formatted}`
|
||||
}]
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Error: Invalid expression - ${error.message}`
|
||||
}]
|
||||
};
|
||||
}
|
||||
}
|
||||
),
|
||||
tool(
|
||||
"compound_interest",
|
||||
"Calculate compound interest for an investment",
|
||||
{
|
||||
principal: z.number().positive().describe("Initial investment amount"),
|
||||
rate: z.number().describe("Annual interest rate (as decimal, e.g., 0.05 for 5%)"),
|
||||
time: z.number().positive().describe("Investment period in years"),
|
||||
n: z.number().positive().default(12).describe("Compounding frequency per year")
|
||||
},
|
||||
async (args) => {
|
||||
const amount = args.principal * Math.pow(1 + args.rate / args.n, args.n * args.time);
|
||||
const interest = amount - args.principal;
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Investment Analysis:\n` +
|
||||
`Principal: $${args.principal.toFixed(2)}\n` +
|
||||
`Rate: ${(args.rate * 100).toFixed(2)}%\n` +
|
||||
`Time: ${args.time} years\n` +
|
||||
`Compounding: ${args.n} times per year\n\n` +
|
||||
`Final Amount: $${amount.toFixed(2)}\n` +
|
||||
`Interest Earned: $${interest.toFixed(2)}\n` +
|
||||
`Return: ${((interest / args.principal) * 100).toFixed(2)}%`
|
||||
}]
|
||||
};
|
||||
}
|
||||
)
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
```python
|
||||
import math
|
||||
from typing import Any
|
||||
|
||||
@tool(
|
||||
"calculate",
|
||||
"Perform mathematical calculations",
|
||||
{"expression": str, "precision": int} # Simple schema
|
||||
)
|
||||
async def calculate(args: dict[str, Any]) -> dict[str, Any]:
|
||||
try:
|
||||
# Use a safe math evaluation library in production
|
||||
result = eval(args["expression"], {"__builtins__": {}})
|
||||
precision = args.get("precision", 2)
|
||||
formatted = round(result, precision)
|
||||
|
||||
return {
|
||||
"content": [{
|
||||
"type": "text",
|
||||
"text": f"{args['expression']} = {formatted}"
|
||||
}]
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"content": [{
|
||||
"type": "text",
|
||||
"text": f"Error: Invalid expression - {str(e)}"
|
||||
}]
|
||||
}
|
||||
|
||||
@tool(
|
||||
"compound_interest",
|
||||
"Calculate compound interest for an investment",
|
||||
{"principal": float, "rate": float, "time": float, "n": int}
|
||||
)
|
||||
async def compound_interest(args: dict[str, Any]) -> dict[str, Any]:
|
||||
principal = args["principal"]
|
||||
rate = args["rate"]
|
||||
time = args["time"]
|
||||
n = args.get("n", 12)
|
||||
|
||||
amount = principal * (1 + rate / n) ** (n * time)
|
||||
interest = amount - principal
|
||||
|
||||
return {
|
||||
"content": [{
|
||||
"type": "text",
|
||||
"text": f"""Investment Analysis:
|
||||
Principal: ${principal:.2f}
|
||||
Rate: {rate * 100:.2f}%
|
||||
Time: {time} years
|
||||
Compounding: {n} times per year
|
||||
|
||||
Final Amount: ${amount:.2f}
|
||||
Interest Earned: ${interest:.2f}
|
||||
Return: {(interest / principal) * 100:.2f}%"""
|
||||
}]
|
||||
}
|
||||
|
||||
calculator_server = create_sdk_mcp_server(
|
||||
name="calculator",
|
||||
version="1.0.0",
|
||||
tools=[calculate, compound_interest] # Pass decorated functions
|
||||
)
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
|
||||
* [TypeScript SDK Reference](/en/api/agent-sdk/typescript)
|
||||
* [Python SDK Reference](/en/api/agent-sdk/python)
|
||||
* [MCP Documentation](https://modelcontextprotocol.io)
|
||||
* [SDK Overview](/en/api/agent-sdk/overview)
|
||||
374
skills/claude-agent-sdk/references/hooks-guide.md
Normal file
374
skills/claude-agent-sdk/references/hooks-guide.md
Normal file
@@ -0,0 +1,374 @@
|
||||
# Hook Patterns and Configuration
|
||||
|
||||
This guide covers hook patterns for intercepting and modifying Claude Agent SDK behavior.
|
||||
|
||||
## Overview
|
||||
|
||||
Hooks allow you to intercept SDK events and modify behavior at key points in execution. Common uses:
|
||||
|
||||
- Control tool execution (approve/deny/modify)
|
||||
- Add context to prompts
|
||||
- Review tool outputs
|
||||
- Stop execution on errors
|
||||
- Log activity
|
||||
|
||||
> **⚠️ IMPORTANT:** The Python SDK does **NOT** support `SessionStart`, `SessionEnd`, or `Notification` hooks due to setup limitations. Only the 6 hook types listed below are supported. Attempting to use unsupported hooks will result in them never firing.
|
||||
|
||||
## Hook Types
|
||||
|
||||
| Hook | When It Fires | Common Uses |
|
||||
|------|---------------|-------------|
|
||||
| `PreToolUse` | Before tool execution | Approve/deny/modify tool calls |
|
||||
| `PostToolUse` | After tool execution | Review output, add context, stop on errors |
|
||||
| `UserPromptSubmit` | Before processing user prompt | Add context, modify prompt |
|
||||
| `Stop` | When execution stops | Cleanup, final logging |
|
||||
| `SubagentStop` | When a subagent stops | Capture subagent results, cleanup |
|
||||
| `PreCompact` | Before message compaction | Review/modify messages before compaction |
|
||||
|
||||
## Hook Configuration
|
||||
|
||||
Hooks are configured via the `hooks` parameter in `ClaudeAgentOptions`:
|
||||
|
||||
```python
|
||||
from claude_agent_sdk import ClaudeAgentOptions, HookMatcher
|
||||
|
||||
options = ClaudeAgentOptions(
|
||||
hooks={
|
||||
"PreToolUse": [
|
||||
HookMatcher(matcher="Bash", hooks=[check_bash_command]),
|
||||
],
|
||||
"PostToolUse": [
|
||||
HookMatcher(matcher="Bash", hooks=[review_output]),
|
||||
],
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## Hook Function Signature
|
||||
|
||||
All hooks have the same signature:
|
||||
|
||||
```python
|
||||
from claude_agent_sdk.types import HookInput, HookContext, HookJSONOutput
|
||||
|
||||
async def hook_function(
|
||||
input_data: HookInput,
|
||||
tool_use_id: str | None,
|
||||
context: HookContext
|
||||
) -> HookJSONOutput:
|
||||
"""
|
||||
Args:
|
||||
input_data: Hook-specific data (tool_name, tool_input, etc.)
|
||||
tool_use_id: Unique ID for this tool use (if applicable)
|
||||
context: Additional context about the execution
|
||||
|
||||
Returns:
|
||||
HookJSONOutput: Dict with hook-specific fields
|
||||
"""
|
||||
return {} # Empty dict = no action
|
||||
```
|
||||
|
||||
## HookJSONOutput Fields
|
||||
|
||||
| Field | Type | Use Case | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `continue_` | `bool` | Stop execution | Set to `False` to halt execution |
|
||||
| `stopReason` | `str` | Stop execution | Explanation for why execution stopped |
|
||||
| `reason` | `str` | Logging/debugging | Explanation of hook decision |
|
||||
| `systemMessage` | `str` | User feedback | Message shown to user |
|
||||
| `hookSpecificOutput` | `dict` | Hook-specific data | Additional hook-specific fields |
|
||||
|
||||
### PreToolUse Hook-Specific Fields
|
||||
|
||||
```python
|
||||
{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "allow" | "deny", # Control tool execution
|
||||
"permissionDecisionReason": "Explanation for decision",
|
||||
"modifiedInput": {...} # Optional: Modified tool input
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### PostToolUse Hook-Specific Fields
|
||||
|
||||
```python
|
||||
{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PostToolUse",
|
||||
"additionalContext": "Extra context based on tool output"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### UserPromptSubmit Hook-Specific Fields
|
||||
|
||||
```python
|
||||
{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "UserPromptSubmit",
|
||||
"updatedPrompt": "Modified prompt text"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Stop Hook-Specific Fields
|
||||
|
||||
```python
|
||||
{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "Stop"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### SubagentStop Hook-Specific Fields
|
||||
|
||||
```python
|
||||
{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "SubagentStop"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### PreCompact Hook-Specific Fields
|
||||
|
||||
```python
|
||||
{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreCompact",
|
||||
"additionalContext": "Context to preserve during compaction"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### 1. Block Dangerous Commands (PreToolUse)
|
||||
|
||||
```python
|
||||
async def check_bash_command(
|
||||
input_data: HookInput,
|
||||
tool_use_id: str | None,
|
||||
context: HookContext
|
||||
) -> HookJSONOutput:
|
||||
"""Prevent dangerous bash commands."""
|
||||
if input_data["tool_name"] != "Bash":
|
||||
return {}
|
||||
|
||||
command = input_data["tool_input"].get("command", "")
|
||||
dangerous_patterns = ["rm -rf", "sudo", "chmod 777", "dd if="]
|
||||
|
||||
for pattern in dangerous_patterns:
|
||||
if pattern in command:
|
||||
return {
|
||||
"reason": f"Blocked dangerous command pattern: {pattern}",
|
||||
"systemMessage": f"🚫 Blocked: {pattern}",
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "deny",
|
||||
"permissionDecisionReason": f"Command contains: {pattern}"
|
||||
}
|
||||
}
|
||||
|
||||
return {} # Allow by default
|
||||
```
|
||||
|
||||
### 2. Review Tool Output (PostToolUse)
|
||||
|
||||
```python
|
||||
async def review_tool_output(
|
||||
input_data: HookInput,
|
||||
tool_use_id: str | None,
|
||||
context: HookContext
|
||||
) -> HookJSONOutput:
|
||||
"""Add context based on tool output."""
|
||||
tool_response = input_data.get("tool_response", "")
|
||||
|
||||
if "error" in str(tool_response).lower():
|
||||
return {
|
||||
"systemMessage": "⚠️ Command produced an error",
|
||||
"reason": "Tool execution failed",
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PostToolUse",
|
||||
"additionalContext": "Consider checking command syntax or permissions."
|
||||
}
|
||||
}
|
||||
|
||||
return {}
|
||||
```
|
||||
|
||||
### 4. Stop on Critical Errors (PostToolUse)
|
||||
|
||||
```python
|
||||
async def stop_on_critical_error(
|
||||
input_data: HookInput,
|
||||
tool_use_id: str | None,
|
||||
context: HookContext
|
||||
) -> HookJSONOutput:
|
||||
"""Halt execution on critical errors."""
|
||||
tool_response = input_data.get("tool_response", "")
|
||||
|
||||
if "critical" in str(tool_response).lower():
|
||||
return {
|
||||
"continue_": False,
|
||||
"stopReason": "Critical error detected - halting for safety",
|
||||
"systemMessage": "🛑 Execution stopped: critical error"
|
||||
}
|
||||
|
||||
return {"continue_": True}
|
||||
```
|
||||
|
||||
### 5. Redirect File Writes (PreToolUse)
|
||||
|
||||
```python
|
||||
async def safe_file_writes(
|
||||
input_data: HookInput,
|
||||
tool_use_id: str | None,
|
||||
context: HookContext
|
||||
) -> HookJSONOutput:
|
||||
"""Redirect writes to safe directory."""
|
||||
if input_data["tool_name"] not in ["Write", "Edit"]:
|
||||
return {}
|
||||
|
||||
file_path = input_data["tool_input"].get("file_path", "")
|
||||
|
||||
# Block writes to system directories
|
||||
if file_path.startswith("/etc/") or file_path.startswith("/usr/"):
|
||||
return {
|
||||
"reason": f"Blocked write to system directory: {file_path}",
|
||||
"systemMessage": "🚫 Cannot write to system directories",
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "deny",
|
||||
"permissionDecisionReason": "System directory protection"
|
||||
}
|
||||
}
|
||||
|
||||
# Redirect to safe directory
|
||||
if not file_path.startswith("./safe/"):
|
||||
safe_path = f"./safe/{file_path.split('/')[-1]}"
|
||||
modified_input = input_data["tool_input"].copy()
|
||||
modified_input["file_path"] = safe_path
|
||||
|
||||
return {
|
||||
"reason": f"Redirected write from {file_path} to {safe_path}",
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "allow",
|
||||
"permissionDecisionReason": "Redirected to safe directory",
|
||||
"modifiedInput": modified_input
|
||||
}
|
||||
}
|
||||
|
||||
return {}
|
||||
```
|
||||
|
||||
## Hook Matcher Patterns
|
||||
|
||||
`HookMatcher` determines when hooks fire:
|
||||
|
||||
```python
|
||||
# Match specific tool
|
||||
HookMatcher(matcher="Bash", hooks=[check_bash])
|
||||
|
||||
# Match all tools
|
||||
HookMatcher(matcher=None, hooks=[log_all_tools])
|
||||
|
||||
# Multiple hooks for same matcher
|
||||
HookMatcher(
|
||||
matcher="Write",
|
||||
hooks=[check_permissions, log_write, backup_file]
|
||||
)
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
```python
|
||||
from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient, HookMatcher
|
||||
|
||||
async def block_dangerous_bash(input_data, tool_use_id, context):
|
||||
if input_data["tool_name"] != "Bash":
|
||||
return {}
|
||||
|
||||
command = input_data["tool_input"].get("command", "")
|
||||
if "rm -rf" in command:
|
||||
return {
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "deny",
|
||||
"permissionDecisionReason": "Dangerous rm -rf detected"
|
||||
}
|
||||
}
|
||||
return {}
|
||||
|
||||
options = ClaudeAgentOptions(
|
||||
allowed_tools=["Bash", "Read", "Write"],
|
||||
hooks={
|
||||
"PreToolUse": [
|
||||
HookMatcher(matcher="Bash", hooks=[block_dangerous_bash])
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
async with ClaudeSDKClient(options=options) as client:
|
||||
await client.query("Run a safe bash command")
|
||||
async for message in client.receive_response():
|
||||
# Process messages
|
||||
pass
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Return early** - Return empty dict `{}` when hook doesn't apply
|
||||
2. **Be specific** - Clear `reason` and `systemMessage` fields help debugging
|
||||
3. **Use matchers** - Filter hooks to relevant tools with `matcher` parameter
|
||||
4. **Chain hooks** - Multiple hooks can process same event
|
||||
5. **Handle errors** - Hooks should be defensive and handle missing data
|
||||
6. **Log decisions** - Use `reason` field to explain hook decisions
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
❌ **Blocking without explanation**
|
||||
|
||||
```python
|
||||
return {
|
||||
"hookSpecificOutput": {
|
||||
"permissionDecision": "deny"
|
||||
# Missing permissionDecisionReason
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
✅ **Clear explanations**
|
||||
|
||||
```python
|
||||
return {
|
||||
"reason": "Command contains dangerous pattern: rm -rf",
|
||||
"systemMessage": "🚫 Blocked dangerous command",
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "deny",
|
||||
"permissionDecisionReason": "Safety: rm -rf detected"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
❌ **Ignoring tool_name in PreToolUse**
|
||||
|
||||
```python
|
||||
# This will fire for ALL tools
|
||||
async def hook(input_data, tool_use_id, context):
|
||||
command = input_data["tool_input"]["command"] # Crashes on non-Bash tools
|
||||
```
|
||||
|
||||
✅ **Filter by tool_name**
|
||||
|
||||
```python
|
||||
async def hook(input_data, tool_use_id, context):
|
||||
if input_data["tool_name"] != "Bash":
|
||||
return {}
|
||||
command = input_data["tool_input"].get("command", "")
|
||||
```
|
||||
247
skills/claude-agent-sdk/references/sessions.md
Normal file
247
skills/claude-agent-sdk/references/sessions.md
Normal file
@@ -0,0 +1,247 @@
|
||||
# Session Management
|
||||
|
||||
> Understanding how the Claude Agent SDK handles sessions and session resumption
|
||||
|
||||
## Session Management
|
||||
|
||||
The Claude Agent SDK provides session management capabilities for handling conversation state and resumption. Sessions allow you to continue conversations across multiple interactions while maintaining full context.
|
||||
|
||||
## How Sessions Work
|
||||
|
||||
When you start a new query, the SDK automatically creates a session and returns a session ID in the initial system message. You can capture this ID to resume the session later.
|
||||
|
||||
### Getting the Session ID
|
||||
|
||||
options: { options: {
|
||||
model: "claude-sonnet-4-5"
|
||||
}
|
||||
})
|
||||
|
||||
for await (const message of response) {
|
||||
// The first message is a system init message with the session ID
|
||||
|
||||
```python
|
||||
if (message.type === 'system' && message.subtype === 'init') { if (message.type === 'system' && message.subtype === 'init') {
|
||||
sessionId = message.session_id
|
||||
console.log(`Session started with ID: ${sessionId}`)
|
||||
// You can save this ID for later resumption
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
// Process other messages... // Process other messages...
|
||||
console.log(message)
|
||||
}
|
||||
|
||||
// Later, you can use the saved sessionId to resume
|
||||
if (sessionId) {
|
||||
const resumedResponse = query({
|
||||
|
||||
```python
|
||||
prompt: "Continue where we left off",
|
||||
options: {
|
||||
resume: sessionId
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
```python Python theme={null}
|
||||
from claude_agent_sdk import query, ClaudeAgentOptions
|
||||
|
||||
session_id = None
|
||||
|
||||
async for message in query(
|
||||
prompt="Help me build a web application",
|
||||
```
|
||||
|
||||
```python
|
||||
options=ClaudeAgentOptions( options=ClaudeAgentOptions(
|
||||
model="claude-sonnet-4-5"
|
||||
)
|
||||
):
|
||||
|
||||
## The first message is a system init message with the session ID
|
||||
```
|
||||
|
||||
```python
|
||||
if hasattr(message, 'subtype') and message.subtype == 'init': if hasattr(message, 'subtype') and message.subtype == 'init':
|
||||
session_id = message.data.get('session_id')
|
||||
print(f"Session started with ID: {session_id}")
|
||||
|
||||
# You can save this ID for later resumption
|
||||
|
||||
```
|
||||
|
||||
## Process other messages... # Process other messages
|
||||
|
||||
print(message)
|
||||
|
||||
## Later, you can use the saved session_id to resume
|
||||
|
||||
if session_id:
|
||||
async for message in query(
|
||||
|
||||
```python
|
||||
prompt="Continue where we left off", prompt="Continue where we left off",
|
||||
options=ClaudeAgentOptions(
|
||||
resume=session_id
|
||||
)
|
||||
):
|
||||
print(message)
|
||||
```
|
||||
|
||||
## Resuming Sessions
|
||||
|
||||
The SDK supports resuming sessions from previous conversation states, enabling continuous development workflows. Use the `resume` option with a session ID to continue a previous conversation.
|
||||
|
||||
options: { options: {
|
||||
resume: "session-xyz", // Session ID from previous conversation
|
||||
model: "claude-sonnet-4-5",
|
||||
allowedTools: ["Read", "Edit", "Write", "Glob", "Grep", "Bash"]
|
||||
}
|
||||
})
|
||||
|
||||
// The conversation continues with full context from the previous session
|
||||
for await (const message of response) {
|
||||
console.log(message)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```python Python theme={null}
|
||||
from claude_agent_sdk import query, ClaudeAgentOptions
|
||||
|
||||
## Resume a previous session using its ID
|
||||
|
||||
async for message in query(
|
||||
prompt="Continue implementing the authentication system from where we left off",
|
||||
```
|
||||
|
||||
```python
|
||||
options=ClaudeAgentOptions( options=ClaudeAgentOptions(
|
||||
resume="session-xyz", # Session ID from previous conversation
|
||||
model="claude-sonnet-4-5",
|
||||
allowed_tools=["Read", "Edit", "Write", "Glob", "Grep", "Bash"]
|
||||
)
|
||||
):
|
||||
print(message)
|
||||
|
||||
## The conversation continues with full context from the previous session
|
||||
```
|
||||
|
||||
The SDK automatically handles loading the conversation history and context when you resume a session, allowing Claude to continue exactly where it left off.
|
||||
|
||||
## Forking Sessions
|
||||
|
||||
When resuming a session, you can choose to either continue the original session or fork it into a new branch. By default, resuming continues the original session. Use the `forkSession` option (TypeScript) or `fork_session` option (Python) to create a new session ID that starts from the resumed state.
|
||||
|
||||
### When to Fork a Session
|
||||
|
||||
Forking is useful when you want to:
|
||||
|
||||
* Explore different approaches from the same starting point
|
||||
* Create multiple conversation branches without modifying the original
|
||||
* Test changes without affecting the original session history
|
||||
* Maintain separate conversation paths for different experiments
|
||||
|
||||
### Forking vs Continuing
|
||||
|
||||
| Behavior | `forkSession: false` (default) | `forkSession: true` |
|
||||
| -------------------- | ------------------------------ | ------------------------------------ |
|
||||
| **Session ID** | Same as original | New session ID generated |
|
||||
| **History** | Appends to original session | Creates new branch from resume point |
|
||||
| **Original Session** | Modified | Preserved unchanged |
|
||||
| **Use Case** | Continue linear conversation | Branch to explore alternatives |
|
||||
|
||||
### Example: Forking a Session
|
||||
|
||||
options: { model: "claude-sonnet-4-5" } options: { model: "claude-sonnet-4-5" }
|
||||
})
|
||||
|
||||
for await (const message of response) {
|
||||
if (message.type === 'system' && message.subtype === 'init') {
|
||||
|
||||
```python
|
||||
sessionId = message.session_id sessionId = message.session_id
|
||||
console.log(`Original session: ${sessionId}`)
|
||||
}
|
||||
|
||||
// Fork the session to try a different approach
|
||||
const forkedResponse = query({
|
||||
prompt: "Now let's redesign this as a GraphQL API instead",
|
||||
```python
|
||||
options: { options: {
|
||||
resume: sessionId,
|
||||
forkSession: true, // Creates a new session ID
|
||||
model: "claude-sonnet-4-5"
|
||||
}
|
||||
})
|
||||
|
||||
for await (const message of forkedResponse) {
|
||||
if (message.type === 'system' && message.subtype === 'init') {
|
||||
```python
|
||||
console.log(`Forked session: ${message.session_id}`) console.log(`Forked session: ${message.session_id}`)
|
||||
// This will be a different session ID
|
||||
}
|
||||
|
||||
// The original session remains unchanged and can still be resumed
|
||||
const originalContinued = query({
|
||||
prompt: "Add authentication to the REST API",
|
||||
```python
|
||||
options: { options: {
|
||||
resume: sessionId,
|
||||
forkSession: false, // Continue original session (default)
|
||||
model: "claude-sonnet-4-5"
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
```python Python theme={null}
|
||||
from claude_agent_sdk import query, ClaudeAgentOptions
|
||||
|
||||
## First, capture the session ID
|
||||
|
||||
session_id = None
|
||||
|
||||
async for message in query(
|
||||
prompt="Help me design a REST API",
|
||||
```python
|
||||
options=ClaudeAgentOptions(model="claude-sonnet-4-5") options=ClaudeAgentOptions(model="claude-sonnet-4-5")
|
||||
):
|
||||
if hasattr(message, 'subtype') and message.subtype == 'init':
|
||||
```python
|
||||
session_id = message.data.get('session_id') session_id = message.data.get('session_id')
|
||||
print(f"Original session: {session_id}")
|
||||
|
||||
## Fork the session to try a different approach
|
||||
|
||||
async for message in query(
|
||||
prompt="Now let's redesign this as a GraphQL API instead",
|
||||
```python
|
||||
options=ClaudeAgentOptions( options=ClaudeAgentOptions(
|
||||
resume=session_id,
|
||||
fork_session=True, # Creates a new session ID
|
||||
model="claude-sonnet-4-5"
|
||||
)
|
||||
):
|
||||
if hasattr(message, 'subtype') and message.subtype == 'init':
|
||||
```python
|
||||
forked_id = message.data.get('session_id') forked_id = message.data.get('session_id')
|
||||
print(f"Forked session: {forked_id}")
|
||||
|
||||
## This will be a different session ID
|
||||
|
||||
## The original session remains unchanged and can still be resumed
|
||||
|
||||
async for message in query(
|
||||
prompt="Add authentication to the REST API",
|
||||
```python
|
||||
options=ClaudeAgentOptions( options=ClaudeAgentOptions(
|
||||
resume=session_id,
|
||||
fork_session=False, # Continue original session (default)
|
||||
model="claude-sonnet-4-5"
|
||||
)
|
||||
):
|
||||
print(message)
|
||||
```
|
||||
286
skills/claude-agent-sdk/references/skills.md
Normal file
286
skills/claude-agent-sdk/references/skills.md
Normal file
@@ -0,0 +1,286 @@
|
||||
# Agent Skills in the SDK
|
||||
|
||||
> Extend Claude with specialized capabilities using Agent Skills in the Claude Agent SDK
|
||||
|
||||
## Overview
|
||||
|
||||
Agent Skills extend Claude with specialized capabilities that Claude autonomously invokes when relevant. Skills are packaged as `SKILL.md` files containing instructions, descriptions, and optional supporting resources.
|
||||
|
||||
For comprehensive information about Skills, including benefits, architecture, and authoring guidelines, see the [Agent Skills overview](/en/docs/agents-and-tools/agent-skills/overview).
|
||||
|
||||
## How Skills Work with the SDK
|
||||
|
||||
When using the Claude Agent SDK, Skills are:
|
||||
|
||||
1. **Defined as filesystem artifacts**: Created as `SKILL.md` files in specific directories (`.claude/skills/`)
|
||||
2. **Loaded from filesystem**: Skills are loaded from configured filesystem locations. You must specify `settingSources` (TypeScript) or `setting_sources` (Python) to load Skills from the filesystem
|
||||
3. **Automatically discovered**: Once filesystem settings are loaded, Skill metadata is discovered at startup from user and project directories; full content loaded when triggered
|
||||
4. **Model-invoked**: Claude autonomously chooses when to use them based on context
|
||||
5. **Enabled via allowed\_tools**: Add `"Skill"` to your `allowed_tools` to enable Skills
|
||||
|
||||
Unlike subagents (which can be defined programmatically), Skills must be created as filesystem artifacts. The SDK does not provide a programmatic API for registering Skills.
|
||||
|
||||
**Default behavior**: By default, the SDK does not load any filesystem settings. To use Skills, you must explicitly configure `settingSources: ['user', 'project']` (TypeScript) or `setting_sources=["user", "project"]` (Python) in your options.
|
||||
|
||||
## Using Skills with the SDK
|
||||
|
||||
To use Skills with the SDK, you need to:
|
||||
|
||||
1. Include `"Skill"` in your `allowed_tools` configuration
|
||||
2. Configure `settingSources`/`setting_sources` to load Skills from the filesystem
|
||||
|
||||
Once configured, Claude automatically discovers Skills from the specified directories and invokes them when relevant to the user's request.
|
||||
|
||||
```python Python theme={null}
|
||||
import asyncio
|
||||
from claude_agent_sdk import query, ClaudeAgentOptions
|
||||
|
||||
async def main():
|
||||
options = ClaudeAgentOptions(
|
||||
```
|
||||
|
||||
```python
|
||||
cwd="/path/to/project", # Project with .claude/skills/ cwd="/path/to/project", # Project with .claude/skills/
|
||||
setting_sources=["user", "project"], # Load Skills from filesystem
|
||||
allowed_tools=["Skill", "Read", "Write", "Bash"] # Enable Skill tool
|
||||
)
|
||||
|
||||
```
|
||||
|
||||
```python
|
||||
async for message in query( async for message in query(
|
||||
prompt="Help me process this PDF document",
|
||||
options=options
|
||||
):
|
||||
print(message)
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
options: { options: {
|
||||
cwd: "/path/to/project", // Project with .claude/skills/
|
||||
settingSources: ["user", "project"], // Load Skills from filesystem
|
||||
allowedTools: ["Skill", "Read", "Write", "Bash"] // Enable Skill tool
|
||||
}
|
||||
})) {
|
||||
console.log(message);
|
||||
}
|
||||
|
||||
```text
|
||||
|
||||
## Skill Locations
|
||||
|
||||
Skills are loaded from filesystem directories based on your `settingSources`/`setting_sources` configuration:
|
||||
|
||||
* **Project Skills** (`.claude/skills/`): Shared with your team via git - loaded when `setting_sources` includes `"project"`
|
||||
* **User Skills** (`~/.claude/skills/`): Personal Skills across all projects - loaded when `setting_sources` includes `"user"`
|
||||
* **Plugin Skills**: Bundled with installed Claude Code plugins
|
||||
|
||||
## Creating Skills
|
||||
|
||||
Skills are defined as directories containing a `SKILL.md` file with YAML frontmatter and Markdown content. The `description` field determines when Claude invokes your Skill.
|
||||
|
||||
**Example directory structure**:
|
||||
|
||||
```bash theme={null}
|
||||
.claude/skills/processing-pdfs/
|
||||
└── SKILL.md
|
||||
```
|
||||
|
||||
For complete guidance on creating Skills, including SKILL.md structure, multi-file Skills, and examples, see:
|
||||
|
||||
* [Agent Skills in Claude Code](/en/docs/claude-code/skills): Complete guide with examples
|
||||
* [Agent Skills Best Practices](/en/docs/agents-and-tools/agent-skills/best-practices): Authoring guidelines and naming conventions
|
||||
|
||||
## Tool Restrictions
|
||||
|
||||
The `allowed-tools` frontmatter field in SKILL.md is only supported when using Claude Code CLI directly. **It does not apply when using Skills through the SDK**.
|
||||
|
||||
When using the SDK, control tool access through the main `allowedTools` option in your query configuration.
|
||||
|
||||
To restrict tools for Skills in SDK applications, use the `allowedTools` option:
|
||||
|
||||
Import statements from the first example are assumed in the following code snippets.
|
||||
|
||||
```python Python theme={null}
|
||||
options = ClaudeAgentOptions(
|
||||
setting_sources=["user", "project"], # Load Skills from filesystem
|
||||
```
|
||||
|
||||
```python
|
||||
allowed_tools=["Skill", "Read", "Grep", "Glob"] # Restricted toolset allowed_tools=["Skill", "Read", "Grep", "Glob"] # Restricted toolset
|
||||
)
|
||||
|
||||
async for message in query(
|
||||
prompt="Analyze the codebase structure",
|
||||
```python
|
||||
options=options options=options
|
||||
):
|
||||
print(message)
|
||||
```
|
||||
|
||||
options: { options: {
|
||||
settingSources: ["user", "project"], // Load Skills from filesystem
|
||||
allowedTools: ["Skill", "Read", "Grep", "Glob"] // Restricted toolset
|
||||
}
|
||||
})) {
|
||||
console.log(message);
|
||||
}
|
||||
|
||||
```text
|
||||
|
||||
## Discovering Available Skills
|
||||
|
||||
To see which Skills are available in your SDK application, simply ask Claude:
|
||||
|
||||
```python Python theme={null}
|
||||
options = ClaudeAgentOptions(
|
||||
setting_sources=["user", "project"], # Load Skills from filesystem
|
||||
```python
|
||||
allowed_tools=["Skill"] allowed_tools=["Skill"]
|
||||
)
|
||||
|
||||
async for message in query(
|
||||
prompt="What Skills are available?",
|
||||
```python
|
||||
options=options options=options
|
||||
):
|
||||
print(message)
|
||||
```
|
||||
|
||||
options: { options: {
|
||||
settingSources: ["user", "project"], // Load Skills from filesystem
|
||||
allowedTools: ["Skill"]
|
||||
}
|
||||
})) {
|
||||
console.log(message);
|
||||
}
|
||||
|
||||
```text
|
||||
|
||||
Claude will list the available Skills based on your current working directory and installed plugins.
|
||||
|
||||
## Testing Skills
|
||||
|
||||
Test Skills by asking questions that match their descriptions:
|
||||
|
||||
```python Python theme={null}
|
||||
options = ClaudeAgentOptions(
|
||||
cwd="/path/to/project",
|
||||
```python
|
||||
setting_sources=["user", "project"], # Load Skills from filesystem setting_sources=["user", "project"], # Load Skills from filesystem
|
||||
allowed_tools=["Skill", "Read", "Bash"]
|
||||
)
|
||||
|
||||
async for message in query(
|
||||
prompt="Extract text from invoice.pdf",
|
||||
```python
|
||||
options=options options=options
|
||||
):
|
||||
print(message)
|
||||
```
|
||||
|
||||
options: { options: {
|
||||
cwd: "/path/to/project",
|
||||
settingSources: ["user", "project"], // Load Skills from filesystem
|
||||
allowedTools: ["Skill", "Read", "Bash"]
|
||||
}
|
||||
})) {
|
||||
console.log(message);
|
||||
}
|
||||
|
||||
```text
|
||||
|
||||
Claude automatically invokes the relevant Skill if the description matches your request.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Skills Not Found
|
||||
|
||||
**Check settingSources configuration**: Skills are only loaded when you explicitly configure `settingSources`/`setting_sources`. This is the most common issue:
|
||||
|
||||
```python Python theme={null}
|
||||
|
||||
## Wrong - Skills won't be loaded
|
||||
|
||||
options = ClaudeAgentOptions(
|
||||
allowed_tools=["Skill"]
|
||||
)
|
||||
|
||||
## Correct - Skills will be loaded
|
||||
|
||||
options = ClaudeAgentOptions(
|
||||
setting_sources=["user", "project"], # Required to load Skills
|
||||
```python
|
||||
allowed_tools=["Skill"] allowed_tools=["Skill"]
|
||||
)
|
||||
```
|
||||
|
||||
allowedTools: ["Skill"] allowedTools: ["Skill"]
|
||||
};
|
||||
|
||||
```text
|
||||
|
||||
For more details on `settingSources`/`setting_sources`, see the [TypeScript SDK reference](/en/api/agent-sdk/typescript#settingsource) or [Python SDK reference](/en/api/agent-sdk/python#settingsource).
|
||||
|
||||
**Check working directory**: The SDK loads Skills relative to the `cwd` option. Ensure it points to a directory containing `.claude/skills/`:
|
||||
|
||||
```python Python theme={null}
|
||||
|
||||
## Ensure your cwd points to the directory containing .claude/skills/
|
||||
|
||||
options = ClaudeAgentOptions(
|
||||
cwd="/path/to/project", # Must contain .claude/skills/
|
||||
```python
|
||||
setting_sources=["user", "project"], # Required to load Skills setting_sources=["user", "project"], # Required to load Skills
|
||||
allowed_tools=["Skill"]
|
||||
)
|
||||
```
|
||||
|
||||
settingSources: ["user", "project"], // Required to load Skills settingSources: ["user", "project"], // Required to load Skills
|
||||
allowedTools: ["Skill"]
|
||||
};
|
||||
|
||||
```text
|
||||
|
||||
See the "Using Skills with the SDK" section above for the complete pattern.
|
||||
|
||||
**Verify filesystem location**:
|
||||
|
||||
```bash theme={null}
|
||||
|
||||
## Check project Skills
|
||||
|
||||
ls .claude/skills/*/SKILL.md
|
||||
|
||||
## Check personal Skills
|
||||
|
||||
ls ~/.claude/skills/*/SKILL.md
|
||||
```
|
||||
|
||||
### Skill Not Being Used
|
||||
|
||||
**Check the Skill tool is enabled**: Confirm `"Skill"` is in your `allowedTools`.
|
||||
|
||||
**Check the description**: Ensure it's specific and includes relevant keywords. See [Agent Skills Best Practices](/en/docs/agents-and-tools/agent-skills/best-practices#writing-effective-descriptions) for guidance on writing effective descriptions.
|
||||
|
||||
### Additional Troubleshooting
|
||||
|
||||
For general Skills troubleshooting (YAML syntax, debugging, etc.), see the [Claude Code Skills troubleshooting section](/en/docs/claude-code/skills#troubleshooting).
|
||||
|
||||
## Related Documentation
|
||||
|
||||
### Skills Guides
|
||||
|
||||
* [Agent Skills in Claude Code](/en/docs/claude-code/skills): Complete Skills guide with creation, examples, and troubleshooting
|
||||
* [Agent Skills Overview](/en/docs/agents-and-tools/agent-skills/overview): Conceptual overview, benefits, and architecture
|
||||
* [Agent Skills Best Practices](/en/docs/agents-and-tools/agent-skills/best-practices): Authoring guidelines for effective Skills
|
||||
* [Agent Skills Cookbook](https://github.com/anthropics/claude-cookbooks/tree/main/skills): Example Skills and templates
|
||||
|
||||
### SDK Resources
|
||||
|
||||
* [Subagents in the SDK](/en/api/agent-sdk/subagents): Similar filesystem-based agents with programmatic options
|
||||
* [Slash Commands in the SDK](/en/api/agent-sdk/slash-commands): User-invoked commands
|
||||
* [SDK Overview](/en/api/agent-sdk/overview): General SDK concepts
|
||||
* [TypeScript SDK Reference](/en/api/agent-sdk/typescript): Complete API documentation
|
||||
* [Python SDK Reference](/en/api/agent-sdk/python): Complete API documentation
|
||||
465
skills/claude-agent-sdk/references/slash-commands.md
Normal file
465
skills/claude-agent-sdk/references/slash-commands.md
Normal file
@@ -0,0 +1,465 @@
|
||||
# Slash Commands in the SDK
|
||||
|
||||
> Learn how to use slash commands to control Claude Code sessions through the SDK
|
||||
|
||||
Slash commands provide a way to control Claude Code sessions with special commands that start with `/`. These commands can be sent through the SDK to perform actions like clearing conversation history, compacting messages, or getting help.
|
||||
|
||||
## Discovering Available Slash Commands
|
||||
|
||||
The Claude Agent SDK provides information about available slash commands in the system initialization message. Access this information when your session starts:
|
||||
|
||||
options: { maxTurns: 1 } options: { maxTurns: 1 }
|
||||
})) {
|
||||
if (message.type === "system" && message.subtype === "init") {
|
||||
|
||||
```python
|
||||
console.log("Available slash commands:", message.slash_commands); console.log("Available slash commands:", message.slash_commands);
|
||||
// Example output: ["/compact", "/clear", "/help"]
|
||||
}
|
||||
```
|
||||
|
||||
```python Python theme={null}
|
||||
import asyncio
|
||||
from claude_agent_sdk import query
|
||||
|
||||
async def main():
|
||||
async for message in query(
|
||||
```
|
||||
|
||||
```python
|
||||
prompt="Hello Claude", prompt="Hello Claude",
|
||||
options={"max_turns": 1}
|
||||
):
|
||||
if message.type == "system" and message.subtype == "init":
|
||||
print("Available slash commands:", message.slash_commands)
|
||||
|
||||
# Example output: ["/compact", "/clear", "/help"]
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## Sending Slash Commands
|
||||
|
||||
Send slash commands by including them in your prompt string, just like regular text:
|
||||
|
||||
options: { maxTurns: 1 } options: { maxTurns: 1 }
|
||||
})) {
|
||||
if (message.type === "result") {
|
||||
|
||||
```python
|
||||
console.log("Command executed:", message.result); console.log("Command executed:", message.result);
|
||||
}
|
||||
```
|
||||
|
||||
```python Python theme={null}
|
||||
import asyncio
|
||||
from claude_agent_sdk import query
|
||||
|
||||
async def main():
|
||||
|
||||
# Send a slash command
|
||||
```
|
||||
|
||||
```python
|
||||
async for message in query( async for message in query(
|
||||
prompt="/compact",
|
||||
options={"max_turns": 1}
|
||||
):
|
||||
if message.type == "result":
|
||||
print("Command executed:", message.result)
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## Common Slash Commands
|
||||
|
||||
### `/compact` - Compact Conversation History
|
||||
|
||||
The `/compact` command reduces the size of your conversation history by summarizing older messages while preserving important context:
|
||||
|
||||
options: { maxTurns: 1 } options: { maxTurns: 1 }
|
||||
})) {
|
||||
if (message.type === "system" && message.subtype === "compact_boundary") {
|
||||
|
||||
```python
|
||||
console.log("Compaction completed"); console.log("Compaction completed");
|
||||
console.log("Pre-compaction tokens:", message.compact_metadata.pre_tokens);
|
||||
console.log("Trigger:", message.compact_metadata.trigger);
|
||||
}
|
||||
```
|
||||
|
||||
```python Python theme={null}
|
||||
import asyncio
|
||||
from claude_agent_sdk import query
|
||||
|
||||
async def main():
|
||||
async for message in query(
|
||||
```
|
||||
|
||||
```python
|
||||
prompt="/compact", prompt="/compact",
|
||||
options={"max_turns": 1}
|
||||
):
|
||||
if (message.type == "system" and
|
||||
message.subtype == "compact_boundary"):
|
||||
print("Compaction completed")
|
||||
print("Pre-compaction tokens:",
|
||||
message.compact_metadata.pre_tokens)
|
||||
print("Trigger:", message.compact_metadata.trigger)
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
### `/clear` - Clear Conversation
|
||||
|
||||
The `/clear` command starts a fresh conversation by clearing all previous history:
|
||||
|
||||
options: { maxTurns: 1 } options: { maxTurns: 1 }
|
||||
})) {
|
||||
if (message.type === "system" && message.subtype === "init") {
|
||||
|
||||
```python
|
||||
console.log("Conversation cleared, new session started"); console.log("Conversation cleared, new session started");
|
||||
console.log("Session ID:", message.session_id);
|
||||
}
|
||||
```
|
||||
|
||||
```python Python theme={null}
|
||||
import asyncio
|
||||
from claude_agent_sdk import query
|
||||
|
||||
async def main():
|
||||
|
||||
# Clear conversation and start fresh
|
||||
```python
|
||||
async for message in query( async for message in query(
|
||||
prompt="/clear",
|
||||
options={"max_turns": 1}
|
||||
):
|
||||
if message.type == "system" and message.subtype == "init":
|
||||
print("Conversation cleared, new session started")
|
||||
print("Session ID:", message.session_id)
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## Creating Custom Slash Commands
|
||||
|
||||
In addition to using built-in slash commands, you can create your own custom commands that are available through the SDK. Custom commands are defined as markdown files in specific directories, similar to how subagents are configured.
|
||||
|
||||
### File Locations
|
||||
|
||||
Custom slash commands are stored in designated directories based on their scope:
|
||||
|
||||
* **Project commands**: `.claude/commands/` - Available only in the current project
|
||||
* **Personal commands**: `~/.claude/commands/` - Available across all your projects
|
||||
|
||||
### File Format
|
||||
|
||||
Each custom command is a markdown file where:
|
||||
|
||||
* The filename (without `.md` extension) becomes the command name
|
||||
* The file content defines what the command does
|
||||
* Optional YAML frontmatter provides configuration
|
||||
|
||||
#### Basic Example
|
||||
|
||||
Create `.claude/commands/refactor.md`:
|
||||
|
||||
```markdown theme={null}
|
||||
Refactor the selected code to improve readability and maintainability.
|
||||
Focus on clean code principles and best practices.
|
||||
```
|
||||
|
||||
This creates the `/refactor` command that you can use through the SDK.
|
||||
|
||||
#### With Frontmatter
|
||||
|
||||
Create `.claude/commands/security-check.md`:
|
||||
|
||||
```markdown theme={null}
|
||||
---
|
||||
allowed-tools: Read, Grep, Glob
|
||||
description: Run security vulnerability scan
|
||||
model: claude-sonnet-4-5
|
||||
---
|
||||
|
||||
Analyze the codebase for security vulnerabilities including:
|
||||
|
||||
- SQL injection risks
|
||||
- XSS vulnerabilities
|
||||
- Exposed credentials
|
||||
- Insecure configurations
|
||||
```
|
||||
|
||||
### Using Custom Commands in the SDK
|
||||
|
||||
Once defined in the filesystem, custom commands are automatically available through the SDK:
|
||||
|
||||
options: { maxTurns: 3 } options: { maxTurns: 3 }
|
||||
})) {
|
||||
if (message.type === "assistant") {
|
||||
|
||||
```python
|
||||
console.log("Refactoring suggestions:", message.message); console.log("Refactoring suggestions:", message.message);
|
||||
}
|
||||
|
||||
// Custom commands appear in the slash_commands list
|
||||
for await (const message of query({
|
||||
prompt: "Hello",
|
||||
```python
|
||||
options: { maxTurns: 1 } options: { maxTurns: 1 }
|
||||
})) {
|
||||
if (message.type === "system" && message.subtype === "init") {
|
||||
```
|
||||
|
||||
// Will include both built-in and custom commands // Will include both built-in and custom commands
|
||||
console.log("Available commands:", message.slash_commands);
|
||||
// Example: ["/compact", "/clear", "/help", "/refactor", "/security-check"]
|
||||
}
|
||||
|
||||
```text
|
||||
|
||||
```python Python theme={null}
|
||||
import asyncio
|
||||
from claude_agent_sdk import query
|
||||
|
||||
async def main():
|
||||
|
||||
# Use a custom command
|
||||
```python
|
||||
async for message in query( async for message in query(
|
||||
prompt="/refactor src/auth/login.py",
|
||||
options={"max_turns": 3}
|
||||
):
|
||||
if message.type == "assistant":
|
||||
print("Refactoring suggestions:", message.message)
|
||||
|
||||
# Custom commands appear in the slash_commands list
|
||||
async for message in query(
|
||||
prompt="Hello",
|
||||
options={"max_turns": 1}
|
||||
):
|
||||
if message.type == "system" and message.subtype == "init":
|
||||
# Will include both built-in and custom commands
|
||||
print("Available commands:", message.slash_commands)
|
||||
# Example: ["/compact", "/clear", "/help", "/refactor", "/security-check"]
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Arguments and Placeholders
|
||||
|
||||
Custom commands support dynamic arguments using placeholders:
|
||||
|
||||
Create `.claude/commands/fix-issue.md`:
|
||||
|
||||
```markdown theme={null}
|
||||
---
|
||||
argument-hint: [issue-number] [priority]
|
||||
description: Fix a GitHub issue
|
||||
---
|
||||
|
||||
Fix issue #$1 with priority $2.
|
||||
Check the issue description and implement the necessary changes.
|
||||
```
|
||||
|
||||
Use in SDK:
|
||||
|
||||
options: { maxTurns: 5 } options: { maxTurns: 5 }
|
||||
})) {
|
||||
// Command will process with $1="123" and $2="high"
|
||||
|
||||
```python
|
||||
if (message.type === "result") { if (message.type === "result") {
|
||||
console.log("Issue fixed:", message.result);
|
||||
}
|
||||
```
|
||||
|
||||
```python Python theme={null}
|
||||
import asyncio
|
||||
from claude_agent_sdk import query
|
||||
|
||||
async def main():
|
||||
|
||||
# Pass arguments to custom command
|
||||
```python
|
||||
async for message in query( async for message in query(
|
||||
prompt="/fix-issue 123 high",
|
||||
options={"max_turns": 5}
|
||||
):
|
||||
|
||||
# Command will process with $1="123" and $2="high"
|
||||
|
||||
if message.type == "result":
|
||||
print("Issue fixed:", message.result)
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## Bash Command Execution
|
||||
|
||||
Custom commands can execute bash commands and include their output:
|
||||
|
||||
Create `.claude/commands/git-commit.md`:
|
||||
|
||||
```markdown theme={null}
|
||||
---
|
||||
allowed-tools: Bash(git add:*), Bash(git status:*), Bash(git commit:*)
|
||||
description: Create a git commit
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
- Current status: !`git status`
|
||||
- Current diff: !`git diff HEAD`
|
||||
|
||||
## Task
|
||||
|
||||
Create a git commit with appropriate message based on the changes.
|
||||
```
|
||||
|
||||
### File References
|
||||
|
||||
Include file contents using the `@` prefix:
|
||||
|
||||
Create `.claude/commands/review-config.md`:
|
||||
|
||||
```markdown theme={null}
|
||||
---
|
||||
description: Review configuration files
|
||||
---
|
||||
|
||||
Review the following configuration files for issues:
|
||||
|
||||
- Package config: @package.json
|
||||
- TypeScript config: @tsconfig.json
|
||||
- Environment config: @.env
|
||||
|
||||
Check for security issues, outdated dependencies, and misconfigurations.
|
||||
```
|
||||
|
||||
### Organization with Namespacing
|
||||
|
||||
Organize commands in subdirectories for better structure:
|
||||
|
||||
```bash theme={null}
|
||||
.claude/commands/
|
||||
├── frontend/
|
||||
│ ├── component.md # Creates /component (project:frontend)
|
||||
│ └── style-check.md # Creates /style-check (project:frontend)
|
||||
├── backend/
|
||||
│ ├── api-test.md # Creates /api-test (project:backend)
|
||||
│ └── db-migrate.md # Creates /db-migrate (project:backend)
|
||||
└── review.md # Creates /review (project)
|
||||
```
|
||||
|
||||
The subdirectory appears in the command description but doesn't affect the command name itself.
|
||||
|
||||
### Practical Examples
|
||||
|
||||
#### Code Review Command
|
||||
|
||||
Create `.claude/commands/code-review.md`:
|
||||
|
||||
```markdown theme={null}
|
||||
---
|
||||
allowed-tools: Read, Grep, Glob, Bash(git diff:*)
|
||||
description: Comprehensive code review
|
||||
---
|
||||
|
||||
## Changed Files
|
||||
|
||||
!`git diff --name-only HEAD~1`
|
||||
|
||||
## Detailed Changes
|
||||
|
||||
!`git diff HEAD~1`
|
||||
|
||||
## Review Checklist
|
||||
|
||||
Review the above changes for:
|
||||
|
||||
1. Code quality and readability
|
||||
2. Security vulnerabilities
|
||||
3. Performance implications
|
||||
4. Test coverage
|
||||
5. Documentation completeness
|
||||
|
||||
Provide specific, actionable feedback organized by priority.
|
||||
```
|
||||
|
||||
#### Test Runner Command
|
||||
|
||||
Create `.claude/commands/test.md`:
|
||||
|
||||
```markdown theme={null}
|
||||
---
|
||||
allowed-tools: Bash, Read, Edit
|
||||
argument-hint: [test-pattern]
|
||||
description: Run tests with optional pattern
|
||||
---
|
||||
|
||||
Run tests matching pattern: $ARGUMENTS
|
||||
|
||||
1. Detect the test framework (Jest, pytest, etc.)
|
||||
2. Run tests with the provided pattern
|
||||
3. If tests fail, analyze and fix them
|
||||
4. Re-run to verify fixes
|
||||
```
|
||||
|
||||
Use these commands through the SDK:
|
||||
|
||||
options: { maxTurns: 3 } options: { maxTurns: 3 }
|
||||
})) {
|
||||
// Process review feedback
|
||||
}
|
||||
|
||||
// Run specific tests
|
||||
for await (const message of query({
|
||||
prompt: "/test auth",
|
||||
|
||||
```python
|
||||
options: { maxTurns: 5 } options: { maxTurns: 5 }
|
||||
})) {
|
||||
// Handle test results
|
||||
}
|
||||
```
|
||||
|
||||
```python Python theme={null}
|
||||
import asyncio
|
||||
from claude_agent_sdk import query
|
||||
|
||||
async def main():
|
||||
|
||||
# Run code review
|
||||
```python
|
||||
async for message in query( async for message in query(
|
||||
prompt="/code-review",
|
||||
options={"max_turns": 3}
|
||||
):
|
||||
|
||||
# Process review feedback
|
||||
|
||||
pass
|
||||
|
||||
# Run specific tests
|
||||
async for message in query(
|
||||
prompt="/test auth",
|
||||
options={"max_turns": 5}
|
||||
):
|
||||
# Handle test results
|
||||
pass
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
- [Slash Commands](/en/docs/claude-code/slash-commands) - Complete slash command documentation
|
||||
- [Subagents in the SDK](/en/api/agent-sdk/subagents) - Similar filesystem-based configuration for subagents
|
||||
- [TypeScript SDK reference](/en/docs/claude-code/typescript-sdk-reference) - Complete API documentation
|
||||
- [SDK overview](/en/api/agent-sdk/overview) - General SDK concepts
|
||||
- [CLI reference](/en/docs/claude-code/cli-reference) - Command-line interface
|
||||
302
skills/claude-agent-sdk/references/subagents.md
Normal file
302
skills/claude-agent-sdk/references/subagents.md
Normal file
@@ -0,0 +1,302 @@
|
||||
# Subagents in the SDK
|
||||
|
||||
> Working with subagents in the Claude Agent SDK
|
||||
|
||||
Subagents in the Claude Agent SDK are specialized AIs that are orchestrated by the main agent.
|
||||
Use subagents for context management and parallelization.
|
||||
|
||||
This guide explains how to define and use subagents in the SDK using the `agents` parameter.
|
||||
|
||||
## Overview
|
||||
|
||||
Subagents can be defined in two ways when using the SDK:
|
||||
|
||||
1. **Programmatically** - Using the `agents` parameter in your `query()` options (recommended for SDK applications)
|
||||
2. **Filesystem-based** - Placing markdown files with YAML frontmatter in designated directories (`.claude/agents/`)
|
||||
|
||||
This guide primarily focuses on the programmatic approach using the `agents` parameter, which provides a more integrated development experience for SDK applications.
|
||||
|
||||
## Benefits of Using Subagents
|
||||
|
||||
### Context Management
|
||||
|
||||
Subagents maintain separate context from the main agent, preventing information overload and keeping interactions focused. This isolation ensures that specialized tasks don't pollute the main conversation context with irrelevant details.
|
||||
|
||||
**Example**: A `research-assistant` subagent can explore dozens of files and documentation pages without cluttering the main conversation with all the intermediate search results - only returning the relevant findings.
|
||||
|
||||
### Parallelization
|
||||
|
||||
Multiple subagents can run concurrently, dramatically speeding up complex workflows.
|
||||
|
||||
**Example**: During a code review, you can run `style-checker`, `security-scanner`, and `test-coverage` subagents simultaneously, reducing review time from minutes to seconds.
|
||||
|
||||
### Specialized Instructions and Knowledge
|
||||
|
||||
Each subagent can have tailored system prompts with specific expertise, best practices, and constraints.
|
||||
|
||||
**Example**: A `database-migration` subagent can have detailed knowledge about SQL best practices, rollback strategies, and data integrity checks that would be unnecessary noise in the main agent's instructions.
|
||||
|
||||
### Tool Restrictions
|
||||
|
||||
Subagents can be limited to specific tools, reducing the risk of unintended actions.
|
||||
|
||||
**Example**: A `doc-reviewer` subagent might only have access to Read and Grep tools, ensuring it can analyze but never accidentally modify your documentation files.
|
||||
|
||||
## Creating Subagents
|
||||
|
||||
### Programmatic Definition (Recommended)
|
||||
|
||||
Define subagents directly in your code using the `agents` parameter:
|
||||
|
||||
```python
|
||||
import anyio
|
||||
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition
|
||||
|
||||
async def main():
|
||||
options = ClaudeAgentOptions(
|
||||
agents={
|
||||
"code-reviewer": AgentDefinition(
|
||||
description="Expert code review specialist. Use for quality, security, and maintainability reviews.",
|
||||
prompt="""You are a code review specialist with expertise in security, performance, and best practices.
|
||||
|
||||
When reviewing code:
|
||||
- Identify security vulnerabilities
|
||||
- Check for performance issues
|
||||
- Verify adherence to coding standards
|
||||
- Suggest specific improvements
|
||||
|
||||
Be thorough but concise in your feedback.""",
|
||||
tools=["Read", "Grep", "Glob"],
|
||||
model="sonnet"
|
||||
),
|
||||
"test-runner": AgentDefinition(
|
||||
description="Runs and analyzes test suites. Use for test execution and coverage analysis.",
|
||||
prompt="""You are a test execution specialist. Run tests and provide clear analysis of results.
|
||||
|
||||
Focus on:
|
||||
- Running test commands
|
||||
- Analyzing test output
|
||||
- Identifying failing tests
|
||||
- Suggesting fixes for failures""",
|
||||
tools=["Bash", "Read", "Grep"]
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
async for message in query(
|
||||
prompt="Review the authentication module for security issues",
|
||||
options=options
|
||||
):
|
||||
print(message)
|
||||
|
||||
anyio.run(main)
|
||||
```
|
||||
|
||||
### AgentDefinition Configuration
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| :------------ | :------------------------------------------- | :------- | :--------------------------------------------------------------- |
|
||||
| `description` | `string` | Yes | Natural language description of when to use this agent |
|
||||
| `prompt` | `string` | Yes | The agent's system prompt defining its role and behavior |
|
||||
| `tools` | `string[]` | No | Array of allowed tool names. If omitted, inherits all tools |
|
||||
| `model` | `'sonnet' \| 'opus' \| 'haiku' \| 'inherit'` | No | Model override for this agent. Defaults to main model if omitted |
|
||||
|
||||
### Filesystem-Based Definition (Alternative)
|
||||
|
||||
You can also define subagents as markdown files in specific directories:
|
||||
|
||||
* **Project-level**: `.claude/agents/*.md` - Available only in the current project
|
||||
* **User-level**: `~/.claude/agents/*.md` - Available across all projects
|
||||
|
||||
Each subagent is a markdown file with YAML frontmatter:
|
||||
|
||||
```markdown theme={null}
|
||||
---
|
||||
name: code-reviewer
|
||||
description: Expert code review specialist. Use for quality, security, and maintainability reviews.
|
||||
tools: Read, Grep, Glob, Bash
|
||||
---
|
||||
|
||||
Your subagent's system prompt goes here. This defines the subagent's
|
||||
role, capabilities, and approach to solving problems.
|
||||
```
|
||||
|
||||
**Note:** Programmatically defined agents (via the `agents` parameter) take precedence over filesystem-based agents with the same name.
|
||||
|
||||
## How the SDK Uses Subagents
|
||||
|
||||
When using the Claude Agent SDK, subagents can be defined programmatically or loaded from the filesystem. Claude will:
|
||||
|
||||
1. **Load programmatic agents** from the `agents` parameter in your options
|
||||
2. **Auto-detect filesystem agents** from `.claude/agents/` directories (if not overridden)
|
||||
3. **Invoke them automatically** based on task matching and the agent's `description`
|
||||
4. **Use their specialized prompts** and tool restrictions
|
||||
5. **Maintain separate context** for each subagent invocation
|
||||
|
||||
Programmatically defined agents (via `agents` parameter) take precedence over filesystem-based agents with the same name.
|
||||
|
||||
## Example Subagents
|
||||
|
||||
For comprehensive examples of subagents including code reviewers, test runners, debuggers, and security auditors, see the [main Subagents guide](/en/docs/claude-code/sub-agents#example-subagents). The guide includes detailed configurations and best practices for creating effective subagents.
|
||||
|
||||
## SDK Integration Patterns
|
||||
|
||||
### Automatic Invocation
|
||||
|
||||
The SDK will automatically invoke appropriate subagents based on the task context. Ensure your agent's `description` field clearly indicates when it should be used:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition
|
||||
|
||||
async def main():
|
||||
options = ClaudeAgentOptions(
|
||||
agents={
|
||||
"performance-optimizer": AgentDefinition(
|
||||
description="Use PROACTIVELY when code changes might impact performance. MUST BE USED for optimization tasks.",
|
||||
prompt="You are a performance optimization specialist...",
|
||||
tools=["Read", "Edit", "Bash", "Grep"],
|
||||
model="sonnet"
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
async for message in query(
|
||||
prompt="Optimize the database queries in the API layer",
|
||||
options=options
|
||||
):
|
||||
# Process messages
|
||||
pass
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
### Explicit Invocation
|
||||
|
||||
Users can request specific subagents in their prompts:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition
|
||||
|
||||
async def main():
|
||||
options = ClaudeAgentOptions(
|
||||
agents={
|
||||
"code-reviewer": AgentDefinition(
|
||||
description="Expert code review specialist",
|
||||
prompt="You are a security-focused code reviewer...",
|
||||
tools=["Read", "Grep", "Glob"]
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
async for message in query(
|
||||
prompt="Use the code-reviewer agent to check the authentication module",
|
||||
options=options
|
||||
):
|
||||
# Process messages
|
||||
pass
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
### Dynamic Agent Configuration
|
||||
|
||||
You can dynamically configure agents based on your application's needs:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from typing import Literal
|
||||
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition
|
||||
|
||||
def create_security_agent(
|
||||
security_level: Literal["basic", "strict"]
|
||||
) -> AgentDefinition:
|
||||
"""Create a security agent with configurable strictness."""
|
||||
strictness = "strict" if security_level == "strict" else "balanced"
|
||||
|
||||
return AgentDefinition(
|
||||
description="Security code reviewer",
|
||||
prompt=f"You are a {strictness} security reviewer...",
|
||||
tools=["Read", "Grep", "Glob"],
|
||||
model="opus" if security_level == "strict" else "sonnet"
|
||||
)
|
||||
|
||||
async def main():
|
||||
options = ClaudeAgentOptions(
|
||||
agents={
|
||||
"security-reviewer": create_security_agent("strict")
|
||||
}
|
||||
)
|
||||
|
||||
async for message in query(
|
||||
prompt="Review this PR for security issues",
|
||||
options=options
|
||||
):
|
||||
# Process messages
|
||||
pass
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## Tool Restrictions
|
||||
|
||||
Subagents can have restricted tool access via the `tools` field:
|
||||
|
||||
* **Omit the field** - Agent inherits all available tools (default)
|
||||
* **Specify tools** - Agent can only use listed tools
|
||||
|
||||
Example of a read-only analysis agent:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition
|
||||
|
||||
async def main():
|
||||
options = ClaudeAgentOptions(
|
||||
agents={
|
||||
"code-analyzer": AgentDefinition(
|
||||
description="Static code analysis and architecture review",
|
||||
prompt="""You are a code architecture analyst. Analyze code structure,
|
||||
identify patterns, and suggest improvements without making changes.""",
|
||||
tools=["Read", "Grep", "Glob"] # No write or execute permissions
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
async for message in query(
|
||||
prompt="Analyze the architecture of this codebase",
|
||||
options=options
|
||||
):
|
||||
# Process messages
|
||||
pass
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
### Common Tool Combinations
|
||||
|
||||
**Read-only agents** (analysis, review):
|
||||
|
||||
```python
|
||||
tools=["Read", "Grep", "Glob"]
|
||||
```
|
||||
|
||||
**Test execution agents**:
|
||||
|
||||
```python
|
||||
tools=["Bash", "Read", "Grep"]
|
||||
```
|
||||
|
||||
**Code modification agents**:
|
||||
|
||||
```python
|
||||
tools=["Read", "Edit", "Write", "Grep", "Glob"]
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
|
||||
* [Main Subagents Guide](/en/docs/claude-code/sub-agents) - Comprehensive subagent documentation
|
||||
* [SDK Overview](/en/api/agent-sdk/overview) - Overview of Claude Agent SDK
|
||||
* [Settings](/en/docs/claude-code/settings) - Configuration file reference
|
||||
* [Slash Commands](/en/docs/claude-code/slash-commands) - Custom command creation
|
||||
258
skills/claude-agent-sdk/references/system-prompts.md
Normal file
258
skills/claude-agent-sdk/references/system-prompts.md
Normal file
@@ -0,0 +1,258 @@
|
||||
# System Prompt Configuration Patterns
|
||||
|
||||
This guide covers different ways to configure system prompts in the Claude Agent SDK.
|
||||
|
||||
## Overview
|
||||
|
||||
System prompts define Claude's role, behavior, and capabilities. The SDK supports multiple configuration patterns.
|
||||
|
||||
## Configuration Types
|
||||
|
||||
### 1. No System Prompt (Vanilla Claude)
|
||||
|
||||
Claude operates without additional instructions:
|
||||
|
||||
```python
|
||||
from claude_agent_sdk import query
|
||||
|
||||
async for message in query(prompt="What is 2 + 2?"):
|
||||
# Process messages
|
||||
pass
|
||||
```
|
||||
|
||||
**Use when:** You want vanilla Claude behavior without specialized instructions.
|
||||
|
||||
### 2. String System Prompt
|
||||
|
||||
Custom instructions as a simple string:
|
||||
|
||||
```python
|
||||
from claude_agent_sdk import ClaudeAgentOptions, query
|
||||
|
||||
options = ClaudeAgentOptions(
|
||||
system_prompt="You are a helpful Python expert. Explain concepts simply."
|
||||
)
|
||||
|
||||
async for message in query(prompt="Explain async/await", options=options):
|
||||
pass
|
||||
```
|
||||
|
||||
**Use when:** You need custom behavior for a specific task or domain.
|
||||
|
||||
### 3. Preset System Prompt
|
||||
|
||||
Use the official Claude Code preset:
|
||||
|
||||
```python
|
||||
options = ClaudeAgentOptions(
|
||||
system_prompt={"type": "preset", "preset": "claude_code"}
|
||||
)
|
||||
```
|
||||
|
||||
The `"claude_code"` preset includes:
|
||||
|
||||
- Tool usage patterns (Bash, Read, Write, Edit, etc.)
|
||||
- Best practices for code modification
|
||||
- Knowledge of the Task tool for delegating to subagents
|
||||
- Git commit and PR workflows
|
||||
- Security guidelines
|
||||
|
||||
**Use when:** Building SDK applications that orchestrate subagents or use file tools.
|
||||
|
||||
### 4. Preset with Append
|
||||
|
||||
Extend the Claude Code preset with additional instructions:
|
||||
|
||||
```python
|
||||
options = ClaudeAgentOptions(
|
||||
system_prompt={
|
||||
"type": "preset",
|
||||
"preset": "claude_code",
|
||||
"append": "Always explain your reasoning before implementing changes."
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
**Use when:** You need Claude Code behavior plus domain-specific instructions.
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Orchestrator Agent
|
||||
|
||||
Main agent that delegates to subagents:
|
||||
|
||||
```python
|
||||
options = ClaudeAgentOptions(
|
||||
system_prompt="claude_code", # Knows how to use Task tool
|
||||
allowed_tools=["Bash", "Task", "Read", "Write"],
|
||||
agents={
|
||||
"subagent-1": AgentDefinition(...),
|
||||
"subagent-2": AgentDefinition(...)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
**Critical:** Orchestrators must use `system_prompt="claude_code"` to understand how to delegate to subagents.
|
||||
|
||||
### Domain Expert
|
||||
|
||||
Specialized behavior for specific tasks:
|
||||
|
||||
```python
|
||||
options = ClaudeAgentOptions(
|
||||
system_prompt={
|
||||
"type": "preset",
|
||||
"preset": "claude_code",
|
||||
"append": """You are a security auditor for Python applications.
|
||||
|
||||
Focus on:
|
||||
- SQL injection vulnerabilities
|
||||
- Command injection risks
|
||||
- Authentication/authorization flaws
|
||||
- Secrets management issues
|
||||
|
||||
Provide specific, actionable security recommendations."""
|
||||
},
|
||||
allowed_tools=["Read", "Grep", "Glob"]
|
||||
)
|
||||
```
|
||||
|
||||
### Constrained Agent
|
||||
|
||||
Agent with specific behavioral constraints:
|
||||
|
||||
```python
|
||||
options = ClaudeAgentOptions(
|
||||
system_prompt="""You are a read-only code analyzer.
|
||||
|
||||
IMPORTANT:
|
||||
- Never modify files
|
||||
- Never execute code
|
||||
- Only analyze and report findings
|
||||
|
||||
Provide detailed analysis with file/line references.""",
|
||||
allowed_tools=["Read", "Grep", "Glob"]
|
||||
)
|
||||
```
|
||||
|
||||
## Shorthand vs Dict Format
|
||||
|
||||
The SDK accepts both shorthand and dictionary formats for the `claude_code` preset:
|
||||
|
||||
```python
|
||||
# Dict format (official examples prefer this)
|
||||
system_prompt={"type": "preset", "preset": "claude_code"}
|
||||
|
||||
# Shorthand format (equivalent, but less explicit)
|
||||
system_prompt="claude_code"
|
||||
|
||||
# With append (dict only)
|
||||
system_prompt={
|
||||
"type": "preset",
|
||||
"preset": "claude_code",
|
||||
"append": "Additional instructions here"
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** The shorthand `system_prompt="claude_code"` is a convenience that's equivalent to the full dict format. Both are valid and produce identical behavior. Official examples prefer the dict format for explicitness, but shorthand works fine for simple cases.
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use preset for orchestrators** - Orchestrators need `system_prompt="claude_code"` for Task tool knowledge
|
||||
2. **Use append for specialization** - Extend preset with domain-specific instructions
|
||||
3. **Match tools to prompt** - System prompt should align with allowed_tools
|
||||
4. **Be specific** - Clear, specific instructions produce better results
|
||||
5. **Test variations** - Experiment with different prompts for your use case
|
||||
|
||||
## Examples
|
||||
|
||||
### File Processing Agent
|
||||
|
||||
```python
|
||||
options = ClaudeAgentOptions(
|
||||
system_prompt={
|
||||
"type": "preset",
|
||||
"preset": "claude_code",
|
||||
"append": """Process CSV files with these requirements:
|
||||
- Validate data types
|
||||
- Handle missing values
|
||||
- Generate summary statistics
|
||||
- Create visualizations using matplotlib"""
|
||||
},
|
||||
allowed_tools=["Read", "Write", "Bash"]
|
||||
)
|
||||
```
|
||||
|
||||
### Documentation Generator
|
||||
|
||||
```python
|
||||
options = ClaudeAgentOptions(
|
||||
system_prompt="""You are a technical documentation specialist.
|
||||
|
||||
Generate clear, comprehensive documentation with:
|
||||
- Overview and purpose
|
||||
- API reference with types
|
||||
- Usage examples
|
||||
- Common pitfalls and troubleshooting
|
||||
|
||||
Use Google-style Python docstrings.""",
|
||||
allowed_tools=["Read", "Write", "Edit", "Grep"]
|
||||
)
|
||||
```
|
||||
|
||||
### Test Writer
|
||||
|
||||
```python
|
||||
options = ClaudeAgentOptions(
|
||||
system_prompt={
|
||||
"type": "preset",
|
||||
"preset": "claude_code",
|
||||
"append": """Write pytest-based tests following these patterns:
|
||||
- Use fixtures for setup/teardown
|
||||
- Parametrize tests when appropriate
|
||||
- Include edge cases and error conditions
|
||||
- Aim for >90% code coverage"""
|
||||
},
|
||||
allowed_tools=["Read", "Write", "Bash"]
|
||||
)
|
||||
```
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
❌ **Orchestrator without claude_code preset**
|
||||
|
||||
```python
|
||||
# Orchestrator won't know how to use Task tool
|
||||
options = ClaudeAgentOptions(
|
||||
agents={...}
|
||||
# Missing system_prompt="claude_code"
|
||||
)
|
||||
```
|
||||
|
||||
✅ **Proper orchestrator configuration**
|
||||
|
||||
```python
|
||||
options = ClaudeAgentOptions(
|
||||
system_prompt="claude_code",
|
||||
agents={...}
|
||||
)
|
||||
```
|
||||
|
||||
❌ **Conflicting instructions**
|
||||
|
||||
```python
|
||||
# Tells agent to modify files but only allows read tools
|
||||
options = ClaudeAgentOptions(
|
||||
system_prompt="Fix any bugs you find",
|
||||
allowed_tools=["Read", "Grep"] # Can't actually fix anything
|
||||
)
|
||||
```
|
||||
|
||||
✅ **Aligned tools and instructions**
|
||||
|
||||
```python
|
||||
options = ClaudeAgentOptions(
|
||||
system_prompt="Analyze code for bugs and report findings",
|
||||
allowed_tools=["Read", "Grep", "Glob"]
|
||||
)
|
||||
```
|
||||
460
skills/claude-agent-sdk/references/tool-permissions.md
Normal file
460
skills/claude-agent-sdk/references/tool-permissions.md
Normal file
@@ -0,0 +1,460 @@
|
||||
# Tool Permission Callbacks
|
||||
|
||||
This guide covers tool permission callbacks for fine-grained control over tool usage.
|
||||
|
||||
## Overview
|
||||
|
||||
Tool permission callbacks allow you to:
|
||||
|
||||
- Approve or deny tool usage
|
||||
- Modify tool inputs before execution
|
||||
- Implement complex permission logic
|
||||
- Log tool usage
|
||||
|
||||
## Choosing Your Permission Strategy
|
||||
|
||||
The SDK provides two ways to control tool permissions: **permission modes** (simple) and **permission callbacks** (advanced).
|
||||
|
||||
### Quick Decision Guide
|
||||
|
||||
**Use `permission_mode` when:**
|
||||
|
||||
- You have simple, consistent permission policies
|
||||
- You want to auto-approve/deny all file edits
|
||||
- You don't need conditional logic
|
||||
- You want minimal code
|
||||
|
||||
**Use `can_use_tool` callback when:**
|
||||
|
||||
- You need conditional approval logic
|
||||
- You want to modify tool inputs before execution
|
||||
- You need to block specific commands or patterns
|
||||
- You want to log tool usage
|
||||
- You need fine-grained control per tool
|
||||
|
||||
### Permission Mode Options
|
||||
|
||||
```python
|
||||
from claude_agent_sdk import ClaudeAgentOptions
|
||||
|
||||
# Option 1: Auto-accept file edits (for automated workflows)
|
||||
options = ClaudeAgentOptions(
|
||||
permission_mode="acceptEdits",
|
||||
allowed_tools=["Read", "Write", "Edit", "Bash"]
|
||||
)
|
||||
|
||||
# Option 2: Require approval for edits (read-only automation)
|
||||
options = ClaudeAgentOptions(
|
||||
permission_mode="rejectEdits",
|
||||
allowed_tools=["Read", "Grep", "Glob"]
|
||||
)
|
||||
|
||||
# Option 3: Plan mode (no execution, just planning)
|
||||
options = ClaudeAgentOptions(
|
||||
permission_mode="plan",
|
||||
allowed_tools=["Read", "Write", "Bash"]
|
||||
)
|
||||
|
||||
# Option 4: Bypass all permissions (use with extreme caution)
|
||||
options = ClaudeAgentOptions(
|
||||
permission_mode="bypassPermissions",
|
||||
allowed_tools=["Read", "Write", "Bash"]
|
||||
)
|
||||
```
|
||||
|
||||
### When to Use Each Mode
|
||||
|
||||
| Mode | Behavior | Best For |
|
||||
|------|----------|----------|
|
||||
| `"acceptEdits"` | Auto-approves file edits (Write, Edit, etc.) | CI/CD pipelines, automated refactoring, code generation |
|
||||
| `"rejectEdits"` | Auto-rejects file edits, allows reads | Analysis tasks, read-only auditing, code review |
|
||||
| `"plan"` | No execution, planning only | Previewing changes, cost estimation, planning phase |
|
||||
| `"bypassPermissions"` | Bypasses all permission checks | Testing, trusted environments only (⚠️ dangerous) |
|
||||
| `"default"` | Uses `can_use_tool` callback if provided | Custom permission logic (see below) |
|
||||
|
||||
### Combining Mode + Callback
|
||||
|
||||
You can use both together - the mode provides baseline behavior and the callback adds custom logic:
|
||||
|
||||
```python
|
||||
async def custom_permissions(tool_name, input_data, context):
|
||||
"""Custom logic on top of permission mode."""
|
||||
# Block dangerous bash commands even if acceptEdits is set
|
||||
if tool_name == "Bash":
|
||||
command = input_data.get("command", "")
|
||||
if "rm -rf /" in command:
|
||||
return PermissionResultDeny(message="Dangerous command blocked")
|
||||
|
||||
return PermissionResultAllow()
|
||||
|
||||
options = ClaudeAgentOptions(
|
||||
permission_mode="acceptEdits", # Auto-approve file edits
|
||||
can_use_tool=custom_permissions, # But add custom bash validation
|
||||
allowed_tools=["Read", "Write", "Edit", "Bash"]
|
||||
)
|
||||
```
|
||||
|
||||
### Simple Use Cases: Just Use permission_mode
|
||||
|
||||
```python
|
||||
# Example 1: Automated code generation (accept all edits)
|
||||
options = ClaudeAgentOptions(
|
||||
permission_mode="acceptEdits",
|
||||
allowed_tools=["Read", "Write", "Edit"]
|
||||
)
|
||||
|
||||
# Example 2: Code analysis (read-only)
|
||||
options = ClaudeAgentOptions(
|
||||
permission_mode="rejectEdits",
|
||||
allowed_tools=["Read", "Grep", "Glob"]
|
||||
)
|
||||
|
||||
# Example 3: Planning phase (no execution)
|
||||
options = ClaudeAgentOptions(
|
||||
permission_mode="plan",
|
||||
allowed_tools=["Read", "Write", "Bash"]
|
||||
)
|
||||
```
|
||||
|
||||
## Permission Callbacks (Advanced)
|
||||
|
||||
For complex permission logic, use the `can_use_tool` callback.
|
||||
|
||||
## Callback Signature
|
||||
|
||||
```python
|
||||
from claude_agent_sdk import (
|
||||
PermissionResultAllow,
|
||||
PermissionResultDeny,
|
||||
ToolPermissionContext
|
||||
)
|
||||
|
||||
async def permission_callback(
|
||||
tool_name: str,
|
||||
input_data: dict,
|
||||
context: ToolPermissionContext
|
||||
) -> PermissionResultAllow | PermissionResultDeny:
|
||||
"""
|
||||
Args:
|
||||
tool_name: Name of the tool being used
|
||||
input_data: Tool input parameters
|
||||
context: Additional context (suggestions, etc.)
|
||||
|
||||
Returns:
|
||||
PermissionResultAllow or PermissionResultDeny
|
||||
"""
|
||||
pass
|
||||
```
|
||||
|
||||
## Permission Results
|
||||
|
||||
### Allow
|
||||
|
||||
```python
|
||||
# Simple allow
|
||||
return PermissionResultAllow()
|
||||
|
||||
# Allow with modified input
|
||||
return PermissionResultAllow(
|
||||
updated_input={"file_path": "/safe/output.txt"}
|
||||
)
|
||||
```
|
||||
|
||||
### Deny
|
||||
|
||||
```python
|
||||
return PermissionResultDeny(
|
||||
message="Cannot write to system directories"
|
||||
)
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### 1. Allow Read-Only Tools
|
||||
|
||||
```python
|
||||
async def permission_callback(
|
||||
tool_name: str,
|
||||
input_data: dict,
|
||||
context: ToolPermissionContext
|
||||
) -> PermissionResultAllow | PermissionResultDeny:
|
||||
"""Auto-allow read-only operations."""
|
||||
|
||||
# Always allow read operations
|
||||
if tool_name in ["Read", "Glob", "Grep"]:
|
||||
return PermissionResultAllow()
|
||||
|
||||
# Deny or ask for other tools
|
||||
return PermissionResultDeny(
|
||||
message=f"Tool {tool_name} requires approval"
|
||||
)
|
||||
```
|
||||
|
||||
### 2. Block Dangerous Commands
|
||||
|
||||
```python
|
||||
async def permission_callback(
|
||||
tool_name: str,
|
||||
input_data: dict,
|
||||
context: ToolPermissionContext
|
||||
) -> PermissionResultAllow | PermissionResultDeny:
|
||||
"""Block dangerous bash commands."""
|
||||
|
||||
if tool_name == "Bash":
|
||||
command = input_data.get("command", "")
|
||||
dangerous = ["rm -rf", "sudo", "chmod 777", "dd if=", "mkfs"]
|
||||
|
||||
for pattern in dangerous:
|
||||
if pattern in command:
|
||||
return PermissionResultDeny(
|
||||
message=f"Dangerous command pattern: {pattern}"
|
||||
)
|
||||
|
||||
return PermissionResultAllow()
|
||||
```
|
||||
|
||||
### 3. Redirect File Writes
|
||||
|
||||
```python
|
||||
async def permission_callback(
|
||||
tool_name: str,
|
||||
input_data: dict,
|
||||
context: ToolPermissionContext
|
||||
) -> PermissionResultAllow | PermissionResultDeny:
|
||||
"""Redirect writes to safe directory."""
|
||||
|
||||
if tool_name in ["Write", "Edit", "MultiEdit"]:
|
||||
file_path = input_data.get("file_path", "")
|
||||
|
||||
# Block system directories
|
||||
if file_path.startswith("/etc/") or file_path.startswith("/usr/"):
|
||||
return PermissionResultDeny(
|
||||
message=f"Cannot write to system directory: {file_path}"
|
||||
)
|
||||
|
||||
# Redirect to safe directory
|
||||
if not file_path.startswith("./safe/"):
|
||||
safe_path = f"./safe/{file_path.split('/')[-1]}"
|
||||
modified_input = input_data.copy()
|
||||
modified_input["file_path"] = safe_path
|
||||
|
||||
return PermissionResultAllow(
|
||||
updated_input=modified_input
|
||||
)
|
||||
|
||||
return PermissionResultAllow()
|
||||
```
|
||||
|
||||
### 4. Log Tool Usage
|
||||
|
||||
```python
|
||||
tool_usage_log = []
|
||||
|
||||
async def permission_callback(
|
||||
tool_name: str,
|
||||
input_data: dict,
|
||||
context: ToolPermissionContext
|
||||
) -> PermissionResultAllow | PermissionResultDeny:
|
||||
"""Log all tool usage."""
|
||||
|
||||
# Log the request
|
||||
tool_usage_log.append({
|
||||
"tool": tool_name,
|
||||
"input": input_data,
|
||||
"suggestions": context.suggestions
|
||||
})
|
||||
|
||||
print(f"Tool: {tool_name}")
|
||||
print(f"Input: {input_data}")
|
||||
|
||||
return PermissionResultAllow()
|
||||
```
|
||||
|
||||
### 5. Interactive Approval
|
||||
|
||||
```python
|
||||
async def permission_callback(
|
||||
tool_name: str,
|
||||
input_data: dict,
|
||||
context: ToolPermissionContext
|
||||
) -> PermissionResultAllow | PermissionResultDeny:
|
||||
"""Ask user for permission on unknown tools."""
|
||||
|
||||
# Auto-allow safe tools
|
||||
if tool_name in ["Read", "Grep", "Glob"]:
|
||||
return PermissionResultAllow()
|
||||
|
||||
# Auto-deny dangerous tools
|
||||
if tool_name == "Bash":
|
||||
command = input_data.get("command", "")
|
||||
if "rm -rf" in command:
|
||||
return PermissionResultDeny(message="Dangerous command")
|
||||
|
||||
# Ask user for other tools
|
||||
print(f"\nTool: {tool_name}")
|
||||
print(f"Input: {input_data}")
|
||||
user_input = input("Allow? (y/N): ").strip().lower()
|
||||
|
||||
if user_input in ("y", "yes"):
|
||||
return PermissionResultAllow()
|
||||
else:
|
||||
return PermissionResultDeny(message="User denied permission")
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Set the callback in `ClaudeAgentOptions`:
|
||||
|
||||
```python
|
||||
from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient
|
||||
|
||||
options = ClaudeAgentOptions(
|
||||
can_use_tool=permission_callback,
|
||||
permission_mode="default", # Ensure callbacks are invoked
|
||||
cwd="."
|
||||
)
|
||||
|
||||
async with ClaudeSDKClient(options) as client:
|
||||
await client.query("List files and create hello.py")
|
||||
async for message in client.receive_response():
|
||||
# Process messages
|
||||
pass
|
||||
```
|
||||
|
||||
## Permission Modes
|
||||
|
||||
| Mode | Behavior | Use Case |
|
||||
|------|----------|----------|
|
||||
| `"default"` | Invokes callback for every tool | Fine-grained control |
|
||||
| `"acceptEdits"` | Auto-approves file edits | Automated workflows |
|
||||
| `"rejectEdits"` | Auto-rejects file edits | Read-only mode |
|
||||
|
||||
## Complete Example
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from claude_agent_sdk import (
|
||||
ClaudeAgentOptions,
|
||||
ClaudeSDKClient,
|
||||
PermissionResultAllow,
|
||||
PermissionResultDeny,
|
||||
ToolPermissionContext,
|
||||
)
|
||||
|
||||
# Track usage
|
||||
tool_log = []
|
||||
|
||||
async def safe_permission_callback(
|
||||
tool_name: str,
|
||||
input_data: dict,
|
||||
context: ToolPermissionContext
|
||||
) -> PermissionResultAllow | PermissionResultDeny:
|
||||
"""Safe permission callback with logging."""
|
||||
|
||||
# Log usage
|
||||
tool_log.append({"tool": tool_name, "input": input_data})
|
||||
|
||||
# Always allow read operations
|
||||
if tool_name in ["Read", "Glob", "Grep"]:
|
||||
print(f"✅ Auto-allow: {tool_name}")
|
||||
return PermissionResultAllow()
|
||||
|
||||
# Check writes to system directories
|
||||
if tool_name in ["Write", "Edit"]:
|
||||
file_path = input_data.get("file_path", "")
|
||||
if file_path.startswith("/etc/"):
|
||||
print(f"❌ Blocked: write to {file_path}")
|
||||
return PermissionResultDeny(
|
||||
message=f"Cannot write to system directory"
|
||||
)
|
||||
|
||||
# Check dangerous bash commands
|
||||
if tool_name == "Bash":
|
||||
command = input_data.get("command", "")
|
||||
if "rm -rf" in command or "sudo" in command:
|
||||
print(f"❌ Blocked: dangerous command")
|
||||
return PermissionResultDeny(
|
||||
message="Dangerous command pattern detected"
|
||||
)
|
||||
|
||||
print(f"✅ Allowed: {tool_name}")
|
||||
return PermissionResultAllow()
|
||||
|
||||
async def main():
|
||||
options = ClaudeAgentOptions(
|
||||
can_use_tool=safe_permission_callback,
|
||||
permission_mode="default",
|
||||
allowed_tools=["Read", "Write", "Bash"]
|
||||
)
|
||||
|
||||
async with ClaudeSDKClient(options) as client:
|
||||
await client.query("List files and create hello.py")
|
||||
|
||||
async for message in client.receive_response():
|
||||
# Process messages
|
||||
pass
|
||||
|
||||
# Print usage summary
|
||||
print("\nTool Usage Summary:")
|
||||
for entry in tool_log:
|
||||
print(f" {entry['tool']}: {entry['input']}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Return early** - Check tool_name first and return quickly for unmatched tools
|
||||
2. **Be defensive** - Use `.get()` to safely access input_data fields
|
||||
3. **Log decisions** - Track what was allowed/denied for debugging
|
||||
4. **Clear messages** - Denial messages should explain why
|
||||
5. **Test thoroughly** - Verify callback logic with different tool types
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
❌ **Assuming input structure**
|
||||
|
||||
```python
|
||||
# Crashes if command key doesn't exist
|
||||
command = input_data["command"]
|
||||
```
|
||||
|
||||
✅ **Safe access**
|
||||
|
||||
```python
|
||||
command = input_data.get("command", "")
|
||||
```
|
||||
|
||||
❌ **Silent denials**
|
||||
|
||||
```python
|
||||
return PermissionResultDeny() # No message
|
||||
```
|
||||
|
||||
✅ **Informative denials**
|
||||
|
||||
```python
|
||||
return PermissionResultDeny(
|
||||
message="Cannot write to system directories for safety"
|
||||
)
|
||||
```
|
||||
|
||||
❌ **Checking all tools for Bash-specific logic**
|
||||
|
||||
```python
|
||||
# This crashes on non-Bash tools
|
||||
async def callback(tool_name, input_data, context):
|
||||
command = input_data["command"] # Only Bash has "command"
|
||||
```
|
||||
|
||||
✅ **Filter by tool_name first**
|
||||
|
||||
```python
|
||||
async def callback(tool_name, input_data, context):
|
||||
if tool_name != "Bash":
|
||||
return PermissionResultAllow()
|
||||
|
||||
command = input_data.get("command", "")
|
||||
# Now safe to check command
|
||||
```
|
||||
Reference in New Issue
Block a user