A lightweight Python SDK for Claude Code CLI using subprocess
Project description
Claude SDK Lite
A lightweight Python SDK for Claude Code CLI using subprocess.
๐ Table of Contents
- Features
- Installation
- Comparison with Official SDK
- When to Use
- Quick Start
- Migration from Official SDK
- API Documentation
- Message Types
- Examples
- Release Notes
๐ Features
- ๐ชถ Lightweight - Only depends on Pydantic, uses your installed claudecode CLI
- โ Type-safe - Full Pydantic model validation
- ๐ง Complete Coverage - Supports all Claude Code CLI parameters
- ๐ Easy to Use - Simple, sync and async API compatible with official claude-agent-sdk
- ๐ Message Types - Full support for AssistantMessage, TextBlock, and more
- ๐ฏ Event-Driven - Real-time message handling via MessageEventListener callbacks
- ๐ Session-Based - Multi-turn conversations with context retention
๐ฆ Installation
Prerequisites
Install Claude Code CLI:
npm install -g @anthropic-ai/claude-code
Install the SDK
pip install claude-sdk-lite
๐ง Comparison with Official SDK
| Feature | claude-sdk-lite | claude-agent-sdk |
|---|---|---|
| Package Size | ~50KB | ~100MB+ |
| Dependencies | Pydantic only | anyio, anthropic, mcp, ... |
| CLI | User installed | Bundled |
| API | Sync + Async | Async only |
| Message Types | Full support | Full support |
| Use Case | Projects with pre-installed CLI | Standalone deployment |
๐ก When to Use claude-sdk-lite vs Official SDK
Choose claude-sdk-lite if you:
- โ Need sync API support - Use in synchronous contexts without asyncio overhead (official SDK is async-only)
- โ Already have Claude Code CLI installed - Want to avoid downloading bundled CLI (~100MB)
- โ Need multi-turn conversations - Session-based client with event-driven message handling
- โ Care about package size and dependencies - Projects with strict dependency requirements
- โ Don't need custom MCP servers - No need for in-process tools
- โ Don't need hooks system - No need to intercept tool calls
- โ Lightweight deployment - CI/CD, containerized environments
Typical use cases:
# Simple code generation
for message in query(prompt="Write a Python function to parse JSON"):
print(message)
# Batch processing
for prompt in prompts:
response = query_text(prompt=prompt)
process(response)
# Event-driven multi-turn conversations
from claude_sdk_lite import ClaudeClient, DefaultMessageHandler
handler = DefaultMessageHandler()
with ClaudeClient(message_handler=handler) as client:
client.send_request("First question")
handler.wait_for_completion()
client.send_request("Follow-up question") # Same session
handler.wait_for_completion()
# Scripts and automation
result = query_text(prompt="Analyze this code", options=ClaudeOptions(model="haiku"))
Choose official claude-agent-sdk if you:
- ๐ง Need custom MCP servers - Create in-process tools with direct app state access
- ๐ง Need hooks system - Intercept and modify tool calls, implement permission controls
- ๐ง Need advanced tool features - In-process tools with direct app state, tool callbacks
- ๐ง Deploy without pre-installed CLI - Need to distribute standalone application
- ๐ง Need comprehensive error handling - Built-in retry, connection management, flow control
- ๐ง Need tool permission callbacks - Dynamic permission decisions
- ๐ง Need plugin system - Extend SDK functionality
Typical use cases:
# Custom MCP tools (official SDK only)
@tool("greet", "Greet a user", {"name": str})
async def greet(args):
return {"content": [{"type": "text", "text": f"Hello, {args['name']}!"}]}
server = create_sdk_mcp_server("my-tools", tools=[greet])
# Hooks system (official SDK only)
async def check_bash_command(input_data, tool_use_id, context):
if "dangerous" in input_data["tool_input"].get("command", ""):
return {"permissionDecision": "deny"}
return {}
options = ClaudeAgentOptions(
hooks={"PreToolUse": [HookMatcher(matcher="Bash", hooks=[check_bash_command])]}
)
# Advanced tool callbacks (official SDK only)
async with ClaudeSDKClient(options=options) as client:
await client.query("First question")
async for msg in client.receive_response():
print(msg)
await client.query("Follow-up question") # Continue same session
Feature Comparison Table
| Feature | claude-sdk-lite | claude-agent-sdk |
|---|---|---|
| Basic query | โ | โ |
| Sync API | โ | โ |
| Async API | โ | โ |
| Event-driven messages | โ | โ |
| Session-based conversation | โ | โ |
| Custom MCP servers | โ | โ |
| Hooks system | โ | โ |
| Tool permission callbacks | โ | โ |
| Bundled CLI | โ | โ |
| Package size | ~50KB | ~100MB+ |
| Dependencies | 1 (Pydantic) | 3+ |
๐ฏ Quick Start
Basic Usage
from claude_sdk_lite import query, AssistantMessage, TextBlock
for message in query(prompt="What is the capital of France?"):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(block.text)
Async Usage
import asyncio
from claude_sdk_lite import async_query, AssistantMessage, TextBlock
async def main():
async for message in async_query(prompt="What is the capital of France?"):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(block.text)
asyncio.run(main())
Event-Driven Multi-Turn Conversations
The session-based client with event-driven message handling enables real-time message processing and multi-turn conversations:
from claude_sdk_lite import ClaudeClient, DefaultMessageHandler, ClaudeOptions
# Create handler for message callbacks
handler = DefaultMessageHandler()
# Use context manager for automatic cleanup
with ClaudeClient(
message_handler=handler,
options=ClaudeOptions(model="sonnet")
) as client:
# Send first request
client.send_request("What is the capital of France?")
handler.wait_for_completion(timeout=30.0)
# Access all messages from the conversation
for message in handler.get_messages():
print(message)
# Send follow-up in the same session
client.send_request("What about Germany?")
handler.wait_for_completion(timeout=30.0)
# Context is automatically maintained across queries
Custom Message Handlers
Create custom handlers by implementing MessageEventListener:
from claude_sdk_lite import (
ClaudeClient,
MessageEventListener,
AssistantMessage,
TextBlock,
ThinkingBlock,
)
class ChatHandler(MessageEventListener):
def __init__(self):
self.response_text = []
def on_query_start(self, prompt: str):
print(f"\n๐ค Query: {prompt}")
def on_message(self, message):
# Process messages in real-time
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(block.text, end="", flush=True)
self.response_text.append(block.text)
elif isinstance(block, ThinkingBlock):
print("\n๐คฏ Thinking...", end="", flush=True)
def on_query_complete(self, messages):
print("\nโ
Query complete!")
handler = ChatHandler()
with ClaudeClient(message_handler=handler) as client:
client.send_request("Explain recursion in simple terms")
handler.wait_for_completion()
Async Event-Driven Conversations
import asyncio
from claude_sdk_lite import (
AsyncClaudeClient,
AsyncMessageEventListener,
AsyncDefaultMessageHandler,
)
async def chat_example():
# Use default async handler
handler = AsyncDefaultMessageHandler()
async with AsyncClaudeClient(message_handler=handler) as client:
await client.send_request("First question")
await handler.wait_for_completion(timeout=30.0)
# Continue conversation
await client.send_request("Follow-up question")
await handler.wait_for_completion(timeout=30.0)
messages = await handler.get_messages()
print(f"Received {len(messages)} messages")
asyncio.run(chat_example())
Simplified Text Response
from claude_sdk_lite import query_text
response = query_text(prompt="What is 2 + 2?")
print(response) # "2 + 2 equals 4."
With Options
from claude_sdk_lite import query, ClaudeOptions
options = ClaudeOptions(
model="haiku",
system_prompt="You are a helpful math tutor",
max_turns=1,
)
for message in query(
prompt="Explain calculus in simple terms",
options=options
):
print(message)
## ๐ Migration from Official SDK
```python
# Official SDK (async only)
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions
async def main():
options = ClaudeAgentOptions(model="sonnet")
async for message in query(prompt="Hello", options=options):
print(message)
asyncio.run(main())
# claude-sdk-lite - use async_query for async code
import asyncio
from claude_sdk_lite import async_query, ClaudeOptions
async def main():
options = ClaudeOptions(model="sonnet")
async for message in async_query(prompt="Hello", options=options):
print(message)
asyncio.run(main())
# OR use sync API (claude-sdk-lite exclusive!)
from claude_sdk_lite import query, ClaudeOptions
options = ClaudeOptions(model="sonnet")
for message in query(prompt="Hello", options=options):
print(message)
Key difference: Official SDK only supports async API, while claude-sdk-lite supports both sync and async APIs.
๐ API Documentation
Session-Based Client API
ClaudeClient(message_handler, options=None)
Synchronous client for multi-turn conversations with event-driven message handling.
Parameters:
message_handler(MessageEventListener): Required handler for message callbacksoptions(ClaudeOptions | None): Optional configuration
Methods:
connect()- Start the persistent subprocess and listenerdisconnect()- Stop the subprocess and cleanupsend_request(prompt)- Send a query to Claudeinterrupt()- Interrupt the current query
Properties:
is_connected- Check if client is connectedmessage_handler- Get the message handler (read-only)session_id- The session identifierstderr_output- Get captured stderr output
Example:
from claude_sdk_lite import ClaudeClient, DefaultMessageHandler
handler = DefaultMessageHandler()
with ClaudeClient(message_handler=handler) as client:
client.send_request("Hello!")
handler.wait_for_completion(timeout=30.0)
# Continue conversation
client.send_request("Tell me more")
handler.wait_for_completion(timeout=30.0)
AsyncClaudeClient(message_handler, options=None)
Async version of ClaudeClient for async/await patterns.
Example:
import asyncio
from claude_sdk_lite import AsyncClaudeClient, AsyncDefaultMessageHandler
async def main():
handler = AsyncDefaultMessageHandler()
async with AsyncClaudeClient(message_handler=handler) as client:
await client.send_request("Hello!")
await handler.wait_for_completion(timeout=30.0)
asyncio.run(main())
Message Event Listeners
MessageEventListener
Base class for handling message events in real-time.
Callback Methods:
on_message(message)- Called when any message is receivedon_query_start(prompt)- Called when a query startson_query_complete(messages)- Called when a query completeson_stream_start()- Called when streaming startson_stream_end()- Called when streaming endson_error(error)- Called when an error occurs
Example:
from claude_sdk_lite import MessageEventListener
class MyHandler(MessageEventListener):
def on_message(self, message):
print(f"Received: {type(message).__name__}")
def on_query_complete(self, messages):
print(f"Complete! Got {len(messages)} messages")
AsyncMessageEventListener
Async version of MessageEventListener with async callback methods.
DefaultMessageHandler
Default implementation that buffers messages and provides synchronization helpers.
Methods:
get_messages()- Get all buffered messages for current querywait_for_completion(timeout=60.0)- Wait for query to completeis_complete()- Check if current query is complete
AsyncDefaultMessageHandler
Async version of DefaultMessageHandler with async methods.
Simple Query API
query(prompt, options=None)
Query Claude Code (sync version), returning a generator of messages.
Parameters:
prompt(str): The prompt to send to Claudeoptions(ClaudeOptions | None): Optional configuration
Yields:
Message: Messages from the conversation (AssistantMessage, SystemMessage, ResultMessage)
Example:
for message in query(prompt="Hello"):
print(message)
async_query(prompt, options=None)
Query Claude Code (async version), returning an async iterator of messages.
Parameters:
prompt(str): The prompt to send to Claudeoptions(ClaudeOptions | None): Optional configuration
Yields:
Message: Messages from the conversation (AssistantMessage, SystemMessage, ResultMessage)
Example:
async for message in async_query(prompt="Hello"):
print(message)
query_text(prompt, options=None) -> str
Convenience function that returns only the text response.
Parameters:
prompt(str): The prompt to send to Claudeoptions(ClaudeOptions | None): Optional configuration
Returns:
str: The concatenated text response
ClaudeOptions
Configuration options class using Pydantic for validation.
Core Options
ClaudeOptions(
model="sonnet", # Model: sonnet, opus, haiku
agent="custom-agent", # Agent to use
)
System Prompt
ClaudeOptions(
system_prompt="You are a helpful assistant",
append_system_prompt="Always be concise",
)
Tools
ClaudeOptions(
allowed_tools=["Bash(git:*)", "Read", "Edit"],
disallowed_tools=["WebFetch"],
tools=["Bash", "Read", "Write"],
)
Session Management
ClaudeOptions(
continue_conversation=True, # Continue recent conversation
resume="session-id", # Resume specific session
session_id="uuid", # Use specific session ID
)
Permission Mode
ClaudeOptions(
permission_mode="acceptEdits", # Auto-accept file edits
# Other options: default, plan, bypassPermissions, delegate, dontAsk
)
Budget Limits
ClaudeOptions(
max_budget_usd=0.50, # Maximum spend in USD
max_turns=10, # Maximum conversation turns
)
Custom Agents
ClaudeOptions(
agents={
"reviewer": {
"description": "Code reviewer",
"prompt": "You are an expert code reviewer",
"model": "sonnet"
}
},
agent="reviewer"
)
MCP Servers
ClaudeOptions(
mcp_config={
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path"]
}
}
}
)
๐ Message Types
AssistantMessage
Claude's response message with content blocks.
class AssistantMessage(BaseModel):
content: list[ContentBlock] # List of content blocks
model: str # Model used
parent_tool_use_id: str | None
error: str | None
TextBlock
Text content block.
class TextBlock(BaseModel):
text: str
type: str = "text"
ToolUseBlock
Tool usage block.
class ToolUseBlock(BaseModel):
id: str
name: str # Tool name
input: dict[str, Any] # Tool input
type: str = "tool_use"
ResultMessage
Result message with cost and usage information.
class ResultMessage(BaseModel):
subtype: str
duration_ms: int
is_error: bool
num_turns: int
session_id: str
total_cost_usd: float | None
usage: dict[str, Any] | None
result: str | None
๐ Examples
Check out the examples directory for complete working examples:
- simple_chat.py - Interactive chat with custom MessageEventListener
- simple_async_chat.py - Async version with AsyncMessageEventListener
- basic_usage.py - Simple query examples
- basic_async_usage.py - Async query examples
Run examples:
# Sync chat
python examples/simple_chat.py
# Async chat
python examples/simple_async_chat.py
# With debug mode
CLAUDE_SDK_DEBUG=true python examples/simple_chat.py
๐ Release Notes
[0.2.1] - 2025-02-10
๐ New Feature: Echo Mode
Additions
- Echo Mode - New
echo_modeoption inClaudeOptions(default:False)- Echo user input back through
on_messagecallback asUserMessagewithTextBlock - Echo interrupt signals as
UserMessagewithInterruptBlock - Useful for UI applications that need to display user input in message stream
- Ensures proper timing with
on_query_startcallback for message buffering
- Echo user input back through
New Types
InterruptBlock- Content block type for interrupt signal messages
[0.2.0] - 2025-02-10
๐ Major Update: Event-Driven Architecture & Multi-Turn Conversations
New Features
-
Session-Based Clients - Maintain conversation context across multiple queries
ClaudeClient- Synchronous client with persistent subprocessAsyncClaudeClient- Async version for async/await patterns- Automatic session management and context retention
-
Event-Driven Message Handling - Real-time message processing via callbacks
MessageEventListener- Base class for custom message handlersAsyncMessageEventListener- Async version with async callbacksDefaultMessageHandler- Built-in handler with message bufferingAsyncDefaultMessageHandler- Async default handler
Key Benefits
- โ Multi-turn conversations - Send multiple queries in the same session
- โ Real-time streaming - Process messages as they arrive via callbacks
- โ Flexible handlers - Create custom handlers for your use case
- โ Thread-safe - Safe for concurrent operations
- โ Better control - Interrupt queries, track completion, access buffered messages
See examples/ for complete working examples.
[0.1.0] - Initial Release
Basic query functions with full Claude Code CLI parameter support.
๐ License
MIT License - see LICENSE file for details.
๐ Acknowledgments
This SDK is inspired by Anthropic's official claude-agent-sdk and provides a lightweight alternative with compatible API.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file claude_sdk_lite-0.2.1.tar.gz.
File metadata
- Download URL: claude_sdk_lite-0.2.1.tar.gz
- Upload date:
- Size: 72.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5eae4c2c84f487e9c6a3a965530fd541a0c778688e5c7f320ed5125cafd9ed48
|
|
| MD5 |
536820a5828ea6ca73ee81ad419d8dd9
|
|
| BLAKE2b-256 |
a807e05526f6884311aa60ae62bc6a697f0de2d4874e8a021347ff21da2f8960
|
File details
Details for the file claude_sdk_lite-0.2.1-py3-none-any.whl.
File metadata
- Download URL: claude_sdk_lite-0.2.1-py3-none-any.whl
- Upload date:
- Size: 40.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d870a11009d9ab4627c911e52e751be50cc40d393c19c01993989ef1e470bffe
|
|
| MD5 |
e4283405eb5e9b7f24d301a9bdef59d1
|
|
| BLAKE2b-256 |
b01c80c503ab10064927184a579a72d4b34a580bbf82d3c3c5ac6eb3dce5576d
|