Skip to main content

opencode-python-sdk

CI PyPI Python

Typed Python client for the OpenCode 2.x server API.

Unofficial, community-maintained. This package is not affiliated with or endorsed by the OpenCode team. If the OpenCode maintainers want to take it over, we are happy to transfer this repository and the opencode-python-sdk name on PyPI. Open an issue and we will hand it over.

  • Complete. All 142 operations of the OpenCode 2.1 API, sync and async, with typed models for every request, response and event (96 event types).
  • Generated from the source of truth. The contract is exported from the same Effect HttpApi the official TypeScript client is built from, so method names and request shapes match it exactly.
  • Safe to run in production. Retries only when a request cannot have changed server state, bounded backoff that honors Retry-After, typed errors for every declared server error, and timeouts on everything.
  • Forward compatible. New fields, enum values, union variants and event types from newer servers are kept, not rejected, so an older client keeps working after a server upgrade.
  • Batteries included. Starts and stops opencode serve for you, auto-paginates lists, and streams events.

Install

pip install opencode-python-sdk

The package imports as opencode. Requires Python 3.10 or newer. To start a server from Python, install the OpenCode CLI too.

Quick start

from opencode import OpencodeServer

with OpencodeServer(cwd="/path/to/repo") as server, server.client() as client:
    session = client.session.create(title="Review")
    client.session.prompt(session.id, text="Summarize the open TODOs in this repository")
    client.session.wait(session.id)

    for message in client.message.list(session.id):
        print(message.type)

To connect to a server that is already running:

from opencode import Opencode

client = Opencode("http://127.0.0.1:4096", password="...")
print(client.server.info().version)

base_url and password default to the OPENCODE_BASE_URL and OPENCODE_PASSWORD environment variables. The password variable is the same one the OpenCode CLI reads.

Async

AsyncOpencode has the same methods. It works with asyncio and trio.

import asyncio

from opencode import AsyncOpencode


async def main() -> None:
    async with AsyncOpencode("http://127.0.0.1:4096", password="...") as client:
        session = await client.session.create(title="Async")
        await client.session.prompt(session.id, text="Hello")

        async for message in client.message.list(session.id):
            print(message.type)


asyncio.run(main())

Calling the API

Methods mirror the official TypeScript client: client.<group>.<operation>(), with names in snake_case. Path parameters are positional; everything else is keyword-only.

client.session.get("ses_...")
client.session.prompt("ses_...", text="Fix the failing test", resume=True)
client.session.revert.stage("ses_...", message_id="msg_...")
client.mcp.list()

The full list is in api.md.

Optional and null arguments

The server distinguishes a field that is absent from a field set to null. Optional arguments default to NOT_GIVEN and are left out of the request; passing None sends null:

client.session.update("ses_...", title="New title")  # sends {"title": "New title"}
client.session.list(parent_id=None)  # only root sessions: parentID=null

Models

Responses are pydantic models. Attributes are snake_case; the wire names stay available as aliases, so a model validates server JSON and serializes back to it unchanged.

from opencode.types import ModelRef

session = client.session.create(model=ModelRef(id="claude-sonnet-5-5", provider_id="anthropic"))
session.project_id  # attribute access
session.to_dict()  # {"id": "...", "projectID": "...", ...}

Unions are discriminated, so isinstance and match narrow them:

from opencode.types import SessionMessageAssistant, SessionMessageUser

for message in client.message.list(session.id):
    match message:
        case SessionMessageUser(text=text):
            print("user:", text)
        case SessionMessageAssistant():
            print("assistant replied")
        case _:
            pass  # includes variants added by newer servers

Project directory

Most endpoints take a location. Set a default for the client once:

client = Opencode("http://127.0.0.1:4096", directory="/path/to/repo")
client.agent.list()  # uses /path/to/repo
client.agent.list(location={"directory": "/other"})  # per call

Pagination

List endpoints with cursors return a page. Iterating it fetches the following pages as needed.

for session in client.session.list(limit=50):
    print(session.title)

page = client.session.list(limit=50)
page.data  # this page only
page.has_next_page()
next_page = page.next_page()

Async list methods return a paginator: await it for the first page, or async for over every item.

Events

with client.event.subscribe() as events:
    for event in events:
        print(event.type)

Events are typed (SessionCreatedEvent, SessionTextDeltaEvent, and so on). Event types this client version does not know arrive as UnknownEvent with all their fields kept. Streams apply only the connect timeout by default, since they stay open between events.

Errors

Every error derives from opencode.OpencodeError. Errors the server declares are raised as their own class, with a typed body:

import opencode

try:
    client.session.get("ses_missing")
except opencode.SessionNotFoundError as error:
    print(error.error.session_id, error.status_code)
except opencode.NotFoundError:
    ...  # any other 404
except opencode.APIConnectionError:
    ...  # server unreachable
Status Class
400 BadRequestError
401 AuthenticationError
403 PermissionDeniedError
404 NotFoundError
409 ConflictError
413 ContentTooLargeError
429 RateLimitError
5xx InternalServerError
No response APIConnectionError, APITimeoutError
Unexpected body APIResponseValidationError

Retries and timeouts

The client retries twice by default, with exponential backoff and jitter, and honors Retry-After up to 60 seconds.

  • GET, PUT and DELETE are retried on connection errors and on 408, 429, 500, 502, 503 and 504.
  • POST and PATCH are retried only when the request cannot have run: when the connection could not be opened, or on 429 and 503. A prompt is never sent twice because a response was slow.
client = Opencode(max_retries=5, timeout=30.0)
client.with_options(timeout=None).session.wait(session.id)  # wait as long as it takes
client.session.get("ses_...", timeout=5.0)  # per call

The default timeout is 10 minutes with a 10 second connect timeout.

Running a server

OpencodeServer runs opencode serve as a child process on a free local port with a random password:

from opencode import OpencodeServer

server = OpencodeServer(
    cwd="/path/to/repo",
    isolated=True,  # ignore project opencode.json, AGENTS.md and Claude Code files
    config={"model": "anthropic/claude-sonnet-5-5"},  # passed as OPENCODE_CONFIG_CONTENT
)
with server:
    client = server.client()
    ...

The server runs in --stdio mode, so it also exits when the Python process dies. AsyncOpencodeServer is the async equivalent.

Advanced

import httpx

from opencode import Opencode

client = Opencode(
    "https://opencode.internal.example.com",
    http_client=httpx.Client(proxy="http://proxy:8080", verify="/etc/ssl/ca.pem"),
    default_headers={"X-Team": "platform"},
)

client.session.create(title="t", extra_headers={"X-Trace": "1"}, extra_body={"future": True})

Logs go to the opencode logger. logging.getLogger("opencode").setLevel(logging.DEBUG) shows every request and retry; credentials are never logged.

Versioning

opencode-python-sdk (this package) OpenCode server
0.1.x 2.1.x

opencode.API_VERSION is the server version the client was generated from. The package follows semantic versioning and releases automatically on every merge to main; until 1.0, minor versions may contain breaking changes. See the releases page.

How it is built

@opencode/protocol (npm)  ──bun codegen/extract──▶  spec/contract.json  ──python -m codegen──▶  src/opencode
  1. codegen/extract/extract.ts loads ClientApi from the published @opencode/protocol package and exports every endpoint, its inputs, outputs and declared errors, and every schema as JSON Schema.
  2. python -m codegen turns the contract into pydantic models (opencode/types), resources (opencode/resources), error classes (opencode/_errors.py) and api.md.
  3. The transport, retries, streaming, pagination and server launcher are written by hand.

Generated code is committed. CI regenerates it and fails if anything changed. See CONTRIBUTING.md.

License

MIT

Metadata

Release files for opencode-python-sdk 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for opencode-python-sdk 0.1.0
File Size Uploaded Provenance
opencode_python_sdk-0.1.0.tar.gz 99.0 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for opencode-python-sdk 0.1.0
File Interpreter ABI Platform
opencode_python_sdk-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 218.0 kB

Release files / opencode_python_sdk-0.1.0.tar.gz

Download URL opencode_python_sdk-0.1.0.tar.gz
Size 99.0 kB
Tags Source
SHA-256 checksum
How to use checksums
91171a34d576f0c295705ccf601b260337e48dc41bbd6525268f12ae9f5573d2
BLAKE2b-256 checksum
How to use checksums
5c72b7e16d7af6ff318659b6c4a1f5ba3c332875e3da24f59e2c824ce8c4f54d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

Release files / opencode_python_sdk-0.1.0-py3-none-any.whl

Download URL opencode_python_sdk-0.1.0-py3-none-any.whl
Size 119.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
197c967e0299fb512890f72213fc2ba351784bbe5f58e5ab1fab5a14dcbba2df
BLAKE2b-256 checksum
How to use checksums
2529394e7ccdf1db7be3ddc500e26a0f3d287fb82fca3599184f5ad784f1fb01
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

Security for opencode-python-sdk 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for opencode_python_sdk-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page