Skip to main content

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 the opencode CLI 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 serve instance over REST
  • Subprocess mode — spawns opencode acp locally 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 containing TextBlock and/or ToolUseBlock
  • ResultMessage — Final message with usage stats, cost, and session info
  • SystemMessage — Internal events (init, tool results, thoughts)
  • TextBlock — Text content from the LLM
  • ToolUseBlock — Tool invocation with name and input
  • HookMatcher — 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

  1. SDKClient creates an HTTP session against opencode serve
  2. query() sends the user prompt via POST /session/{id}/message
  3. receive_response() yields typed messages: SystemMessage (init, step events), AssistantMessage (LLM text/tool calls), and ResultMessage (cost, session ID, turn count)
  4. 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

opencode_agent_sdk-0.2.0.tar.gz (19.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

opencode_agent_sdk-0.2.0-py3-none-any.whl (17.9 kB view details)

Uploaded Python 3

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

Hashes for opencode_agent_sdk-0.2.0.tar.gz
Algorithm Hash digest
SHA256 ea249ec0985502d738c9d79060b15dfdd4a4aae2cc608a9d3a656d1a9244a9cf
MD5 7e28735799ff1348ef0440e1a8a82739
BLAKE2b-256 c99148a87ed80099b609c015e9952fad2002c8b1e4caaaad574d7d9a84cbd995

See more details on using hashes here.

File details

Details for the file opencode_agent_sdk-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for opencode_agent_sdk-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1d3d209b0f5877ad726a5aeacab3fa850a2af9d7f0e15f85606d44950100e309
MD5 045b8c10b3f4cb63df1d25cc9f9d5487
BLAKE2b-256 51cb3f531f52bca4dc99051858e067b055f8434913f2f6d60a952bc14b878896

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page