multillm-claude
Claude Agent SDK provider for multillm.
This provider enables autonomous agents that can use tools and perform multi-step tasks.
Installation
pip install multillm-claude
Configuration
Choose one of the following methods (priority: direct config > env var > config file):
Option 1: Claude CLI OAuth (recommended)
claude login
Option 2: Config file
Create ~/.config/multillm/providers/claude.json:
{
"api_key": "sk-ant-..."
}
Or for OAuth token:
{
"oauth_token": "sk-ant-oat01-..."
}
Set permissions:
chmod 600 ~/.config/multillm/providers/claude.json
Option 3: Environment variables
# API Key
export ANTHROPIC_API_KEY=sk-ant-...
# Or OAuth Token
export CLAUDE_CODE_OAUTH_TOKEN=your-token
Option 4: Direct config
client = multillm.Client({
"claude": {"api_key": "sk-ant-..."}
})
Config File Fields
| Field | Required | Description |
|---|---|---|
api_key |
No* | Your Anthropic API key |
oauth_token |
No* | Your Claude OAuth token |
*One of api_key or oauth_token must be provided, unless using claude login.
Usage
import asyncio
import multillm
async def main():
client = multillm.Client()
# Simple query with tools
answer = await client.single(
"claude/default",
"What Python version is installed?",
allowed_tools=["Bash"],
permission_mode="acceptEdits",
)
print(answer)
# Streaming agent events
options = multillm.AgentOptions(
system_prompt="You are a helpful assistant.",
max_turns=10,
allowed_tools=["Read", "Write", "Bash"],
permission_mode="acceptEdits",
)
async for msg in client.run("claude/default", "Create hello.py", options):
if msg.type == "text":
print(msg.content, end="")
elif msg.type == "tool_use":
print(f"\n[{msg.tool_name}]", end="")
asyncio.run(main())
Model Selection
Specify the model after the provider prefix:
claude/default- Uses the default modelclaude/claude-sonnet-4-20250514- Claude Sonnetclaude/claude-opus-4-20250514- Claude Opus
Tool Support
Supported Tools
These tools work correctly with the Claude Agent SDK provider:
| Tool | Description | Permission Required |
|---|---|---|
Bash |
Execute bash commands | Yes |
Read |
Read files from filesystem | No |
Write |
Create or overwrite files | Yes |
Edit |
Edit existing files | Yes |
Glob |
Find files by pattern | No |
Grep |
Search file contents | No |
Task |
Launch sub-agents | Varies |
WebFetch |
Fetch web content | No |
WebSearch |
Search the web | No |
NotebookEdit |
Edit Jupyter notebooks | Yes |
KillShell |
Kill background shells | Yes |
EnterPlanMode |
Enter planning mode | No |
ExitPlanMode |
Exit planning mode | No |
Interactive Tools
| Tool | CLI Support | Notes |
|---|---|---|
AskUserQuestion |
✅ Auto-converts | CLI automatically uses custom ask_user tool |
| Custom tools | ✅ Full support | Provide Tool objects with interactive handlers |
About Interactive Tools with Claude:
The Claude Agent SDK's built-in AskUserQuestion runs in a subprocess and can't access our terminal's stdin/stdout for interactive prompting. To solve this, multillm-cli automatically provides a custom interactive tool when you request AskUserQuestion.
What happens:
When you use --allowed-tools AskUserQuestion, the CLI:
1. Removes the built-in AskUserQuestion (which doesn't work interactively)
2. Adds a custom ask_user tool with an interactive handler
3. The agent uses this tool instead, with full interactive support
Usage:
# Request AskUserQuestion - CLI auto-provides working alternative
multillm -m claude/default \
-p "Ask me about my preferences and create a summary" \
--allowed-tools AskUserQuestion \
--permission-mode acceptEdits
What you'll see:
ℹ️ Using custom 'ask_user' tool instead of AskUserQuestion for interactive prompting
======================================================================
❓ QUESTION FROM ASSISTANT
======================================================================
What is your favorite programming language?
Suggested options:
1. Python
2. JavaScript
3. Rust
Your answer: 1
======================================================================
Programmatic usage:
For programmatic use, provide your own ask_user tool with an interactive handler:
import multillm
# Define interactive ask_user tool
ask_user_tool = multillm.Tool(
name="ask_user",
description="Ask the user a question",
parameters={
"type": "object",
"properties": {
"question": {"type": "string"},
"options": {"type": "array", "items": {"type": "string"}}
},
"required": ["question"]
},
handler=lambda args: {
"answer": input(f"\n{args['question']}\nYour answer: ")
}
)
# Use with Claude
async for msg in client.run(
"claude/default",
"Ask me questions",
options=multillm.AgentOptions(max_turns=10),
tools=[ask_user_tool] # Provide custom tool
):
if msg.type == "text":
print(msg.content)
Why not use the built-in AskUserQuestion?
The SDK's built-in AskUserQuestion is designed for Claude Code CLI's interactive mode where the subprocess has special stdin/stdout handling. In multillm, this doesn't work because:
- The SDK subprocess can't access our terminal
- We can't intercept and respond to built-in tool calls
- The tool returns an error instead of prompting
Solution: Use custom tools (which the CLI provides automatically!)
Comparison:
| Approach | Works? | How |
|---|---|---|
--allowed-tools AskUserQuestion |
✅ Yes | CLI auto-converts to custom tool |
Custom ask_user Tool |
✅ Yes | Provide Tool object with handler |
| SDK built-in (direct) | ❌ No | Subprocess can't access stdin/stdout |
Chat provider ask_user |
✅ Yes | Via agentwrap |
Agent Options
options = multillm.AgentOptions(
system_prompt="...", # System prompt
max_turns=10, # Maximum agent turns
allowed_tools=["Read", "Bash"], # Tools the agent can use
permission_mode="acceptEdits", # Permission handling
working_directory="/path", # Working directory
)
Message Types
When streaming with client.run():
| Type | Description |
|---|---|
text |
Text content from the agent |
tool_use |
Agent is using a tool |
tool_result |
Result from a tool |
result |
Final result |
system |
System messages |
Debugging
Enable Debug Mode
Set the MULTILLM_DEBUG environment variable to see detailed error information and SDK options:
export MULTILLM_DEBUG=1
python your_script.py
This will show: - Detailed error messages with full stderr/stdout - SDK configuration options - Full Python tracebacks
Common Issues
Error: "Command failed with exit code 1"
The provider now captures and displays all available error information to stderr. Look for:
======================================================================
CLAUDE AGENT SDK ERROR
======================================================================
Error: <detailed error message>
Error Details:
stderr: <actual error from subprocess>
stdout: <subprocess output>
exit_code: 1
======================================================================
Authentication Errors:
# Check authentication status
claude login --check
# Re-authenticate if needed
claude login
Permission Errors:
Always specify permission_mode when using tools:
result = await client.single(
"claude/default",
"List files",
allowed_tools=["Bash"],
permission_mode="acceptEdits" # Required!
)
Full Debugging Guide
See DEBUGGING.md for comprehensive debugging information, including: - How to read error messages - Common issues and solutions - Debug mode usage - Testing error handling - Reporting bugs
