Open-source Agent SDK backed by OpenCode ACP (drop-in replacement for claude_agent_sdk)
Project description
OpenCode Agent SDK for Python
Python SDK for building agents backed by OpenCode. Drop-in replacement for claude_agent_sdk with support for any LLM provider (Anthropic, OpenAI, xAI, etc.).
Installation
pip install opencode-agent-sdk
Prerequisites:
- Python 3.10+
- An OpenCode server (
opencode serve) or theopencodeCLI installed locally
Quick Start
import asyncio
from opencode_agent_sdk import SDKClient, AgentOptions, AssistantMessage, TextBlock
async def main():
client = SDKClient(options=AgentOptions(
model="claude-haiku-4-5",
server_url="http://localhost:54321",
))
await client.connect()
await client.query("What is 2 + 2?")
async for message in client.receive_response():
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(block.text)
await client.disconnect()
asyncio.run(main())
SDKClient
SDKClient supports bidirectional conversations with an LLM via OpenCode. It works in two transport modes:
- HTTP mode — communicates with a running
opencode serveinstance over REST - Subprocess mode — spawns
opencode acplocally over stdio JSON-RPC
HTTP Mode (recommended)
Start the server, then connect:
docker compose up -d # starts opencode serve on port 54321
from opencode_agent_sdk import SDKClient, AgentOptions
client = SDKClient(options=AgentOptions(
model="claude-haiku-4-5",
server_url="http://localhost:54321",
system_prompt="You are a helpful assistant",
))
await client.connect()
await client.query("Hello!")
async for msg in client.receive_response():
print(msg)
await client.disconnect()
Subprocess Mode
When server_url is not set, the SDK spawns opencode acp as a child process:
client = SDKClient(options=AgentOptions(
cwd="/path/to/project",
model="claude-haiku-4-5",
))
Resuming Sessions
options = AgentOptions(
resume="session-id-from-previous-run",
server_url="http://localhost:54321",
)
AgentOptions
| Field | Type | Default | Description |
|---|---|---|---|
cwd |
str |
"." |
Working directory |
model |
str |
"" |
Model identifier (e.g. "claude-haiku-4-5") |
provider_id |
str |
"anthropic" |
Provider identifier |
system_prompt |
str |
"" |
System prompt for the LLM |
server_url |
str |
"" |
OpenCode server URL; enables HTTP mode when set |
mcp_servers |
dict |
{} |
MCP server configurations |
allowed_tools |
list[str] |
[] |
Tools the agent is allowed to use |
permission_mode |
str |
"" |
Permission mode for tool execution |
hooks |
dict |
{} |
Hook matchers keyed by event type |
max_turns |
int |
100 |
Maximum conversation turns |
resume |
str | None |
None |
Session ID to resume |
Custom Tools (MCP Servers)
Define tools as Python functions and expose them as in-process MCP servers:
from opencode_agent_sdk import tool, create_sdk_mcp_server, SDKClient, AgentOptions
@tool("greet", "Greet a user", {"type": "object", "properties": {"name": {"type": "string"}}})
def greet_user(args):
return {"content": [{"type": "text", "text": f"Hello, {args['name']}!"}]}
server = create_sdk_mcp_server("my-tools", tools=[greet_user])
client = SDKClient(options=AgentOptions(
mcp_servers={"my-tools": server},
allowed_tools=["mcp__my-tools__greet"],
server_url="http://localhost:54321",
))
You can mix in-process SDK servers with external MCP servers:
options = AgentOptions(
mcp_servers={
"internal": sdk_server, # In-process SDK server
"external": { # External stdio server
"command": "external-server",
"args": ["--port", "8080"],
},
}
)
Hooks
Hooks let you intercept and control tool execution. They run deterministically at specific points in the agent loop.
from opencode_agent_sdk import SDKClient, AgentOptions, HookMatcher
async def check_bash_command(input_data, tool_use_id, context):
tool_input = input_data["tool_input"]
command = tool_input.get("command", "")
if "rm -rf" in command:
return {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Destructive command blocked",
}
}
return {}
options = AgentOptions(
allowed_tools=["Bash"],
hooks={
"PreToolUse": [
HookMatcher(matcher="Bash", hooks=[check_bash_command]),
],
},
server_url="http://localhost:54321",
)
client = SDKClient(options=options)
await client.connect()
await client.query("Run: echo hello")
async for msg in client.receive_response():
print(msg)
await client.disconnect()
Hook event types: "PreToolUse", "Stop"
Types
See src/opencode_agent_sdk/types.py for complete type definitions:
AssistantMessage— LLM response containingTextBlockand/orToolUseBlockResultMessage— Final message with usage stats, cost, and session infoSystemMessage— Internal events (init, tool results, thoughts)TextBlock— Text content from the LLMToolUseBlock— Tool invocation with name and inputHookMatcher— Matches tool names to hook functions
Error Handling
from opencode_agent_sdk._errors import ProcessError
try:
await client.connect()
except ProcessError as e:
print(f"Failed with exit code: {e.exit_code}")
Migrating from claude_agent_sdk
This SDK mirrors the claude_agent_sdk API. Migration requires renaming imports:
# Before (claude_agent_sdk)
from claude_agent_sdk import (
ClaudeAgentOptions, ClaudeSDKClient, AssistantMessage,
ResultMessage, SystemMessage, TextBlock, ToolUseBlock, HookMatcher,
)
from claude_agent_sdk._errors import ProcessError
# After (opencode_agent_sdk)
from opencode_agent_sdk import (
AgentOptions, SDKClient, AssistantMessage,
ResultMessage, SystemMessage, TextBlock, ToolUseBlock, HookMatcher,
)
from opencode_agent_sdk._errors import ProcessError
All method calls, message types, hooks, and tool decorators stay the same. Only the class names change:
| claude_agent_sdk | opencode_agent_sdk |
|---|---|
ClaudeSDKClient |
SDKClient |
ClaudeAgentOptions |
AgentOptions |
Demo: End-to-End Walkthrough
A full working demo that connects to opencode serve, sends a prompt to clone a GitHub repo, and streams the LLM response back through the SDK.
1. Configure API keys
Create a .env file in the project root with your provider key:
ANTHROPIC_API_KEY=sk-ant-...
2. Start the server
docker compose up -d opencode
This builds and starts the opencode serve container on port 54321.
3. Install dependencies
uv sync
4. Run the E2E demo
uv run python scripts/e2e_test.py
Expected output
============================================================
E2E Test: Clone repo & explain project
============================================================
Server: http://127.0.0.1:54321
[*] Connecting ...
[*] Connected.
[>] Prompt:
Clone the repo https://github.com/dingkwang/opencode-agent-sdk-python and then
explain what the project does. Give a concise summary of its purpose,
architecture, and key components.
------------------------------------------------------------
[system:init]
[system:step_start]
[assistant]
## opencode-agent-sdk-python
### Purpose
An open-source Python SDK that serves as a drop-in replacement for Anthropic's
proprietary `claude_agent_sdk`. It delegates all LLM work to OpenCode — an
open-source headless server that supports any provider ...
...
============================================================
[result] session = ses_...
cost = $0.024723
turns = 1
is_error = False
============================================================
[*] Message counts: {'system': 2, 'assistant': 1, 'result': 1}
[*] E2E test complete.
What's happening
SDKClientcreates an HTTP session againstopencode servequery()sends the user prompt viaPOST /session/{id}/messagereceive_response()yields typed messages:SystemMessage(init, step events),AssistantMessage(LLM text/tool calls), andResultMessage(cost, session ID, turn count)disconnect()cleans up the session
Customizing the demo
Set a custom server URL via environment variable:
OPENCODE_SERVER_URL=http://your-host:54321 uv run python scripts/e2e_test.py
Running with Docker
# Start opencode serve
docker compose up -d
# Run the integration test
docker compose run --rm test
The Docker setup uses opencode-ai v1.2.6 and exposes the REST API on port 54321. Pass provider API keys via .env (e.g. ANTHROPIC_API_KEY).
Development
# Install dependencies
uv sync
# Run tests
uv run pytest
# Run demo against a running opencode serve
uv run python scripts/opencode_ai_demo.py
# Interactive multi-turn chat
uv run python scripts/chat.py
License
MIT
Project details
Release history Release notifications | RSS feed
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 opencode_agent_sdk-0.2.0.tar.gz.
File metadata
- Download URL: opencode_agent_sdk-0.2.0.tar.gz
- Upload date:
- Size: 19.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.16
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ea249ec0985502d738c9d79060b15dfdd4a4aae2cc608a9d3a656d1a9244a9cf
|
|
| MD5 |
7e28735799ff1348ef0440e1a8a82739
|
|
| BLAKE2b-256 |
c99148a87ed80099b609c015e9952fad2002c8b1e4caaaad574d7d9a84cbd995
|
File details
Details for the file opencode_agent_sdk-0.2.0-py3-none-any.whl.
File metadata
- Download URL: opencode_agent_sdk-0.2.0-py3-none-any.whl
- Upload date:
- Size: 17.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.16
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1d3d209b0f5877ad726a5aeacab3fa850a2af9d7f0e15f85606d44950100e309
|
|
| MD5 |
045b8c10b3f4cb63df1d25cc9f9d5487
|
|
| BLAKE2b-256 |
51cb3f531f52bca4dc99051858e067b055f8434913f2f6d60a952bc14b878896
|