opencode-python-sdk 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. opencode_python_sdk-0.1.0/.gitignore +16 -0
  2. opencode_python_sdk-0.1.0/CHANGELOG.md +9 -0
  3. opencode_python_sdk-0.1.0/LICENSE +21 -0
  4. opencode_python_sdk-0.1.0/PKG-INFO +317 -0
  5. opencode_python_sdk-0.1.0/README.md +282 -0
  6. opencode_python_sdk-0.1.0/api.md +372 -0
  7. opencode_python_sdk-0.1.0/pyproject.toml +149 -0
  8. opencode_python_sdk-0.1.0/src/opencode/.generated.json +39 -0
  9. opencode_python_sdk-0.1.0/src/opencode/__init__.py +156 -0
  10. opencode_python_sdk-0.1.0/src/opencode/_base_client.py +518 -0
  11. opencode_python_sdk-0.1.0/src/opencode/_client.py +167 -0
  12. opencode_python_sdk-0.1.0/src/opencode/_constants.py +27 -0
  13. opencode_python_sdk-0.1.0/src/opencode/_errors.py +547 -0
  14. opencode_python_sdk-0.1.0/src/opencode/_exceptions.py +249 -0
  15. opencode_python_sdk-0.1.0/src/opencode/_models.py +54 -0
  16. opencode_python_sdk-0.1.0/src/opencode/_pagination.py +192 -0
  17. opencode_python_sdk-0.1.0/src/opencode/_qs.py +95 -0
  18. opencode_python_sdk-0.1.0/src/opencode/_resource.py +42 -0
  19. opencode_python_sdk-0.1.0/src/opencode/_server.py +327 -0
  20. opencode_python_sdk-0.1.0/src/opencode/_streaming.py +227 -0
  21. opencode_python_sdk-0.1.0/src/opencode/_types.py +77 -0
  22. opencode_python_sdk-0.1.0/src/opencode/_unions.py +46 -0
  23. opencode_python_sdk-0.1.0/src/opencode/_version.py +8 -0
  24. opencode_python_sdk-0.1.0/src/opencode/py.typed +0 -0
  25. opencode_python_sdk-0.1.0/src/opencode/resources/__init__.py +364 -0
  26. opencode_python_sdk-0.1.0/src/opencode/resources/agent.py +177 -0
  27. opencode_python_sdk-0.1.0/src/opencode/resources/command.py +98 -0
  28. opencode_python_sdk-0.1.0/src/opencode/resources/config.py +238 -0
  29. opencode_python_sdk-0.1.0/src/opencode/resources/credential.py +382 -0
  30. opencode_python_sdk-0.1.0/src/opencode/resources/debug.py +174 -0
  31. opencode_python_sdk-0.1.0/src/opencode/resources/event.py +84 -0
  32. opencode_python_sdk-0.1.0/src/opencode/resources/experimental.py +760 -0
  33. opencode_python_sdk-0.1.0/src/opencode/resources/file.py +353 -0
  34. opencode_python_sdk-0.1.0/src/opencode/resources/form.py +98 -0
  35. opencode_python_sdk-0.1.0/src/opencode/resources/generate.py +108 -0
  36. opencode_python_sdk-0.1.0/src/opencode/resources/integration.py +1023 -0
  37. opencode_python_sdk-0.1.0/src/opencode/resources/location.py +160 -0
  38. opencode_python_sdk-0.1.0/src/opencode/resources/mcp.py +488 -0
  39. opencode_python_sdk-0.1.0/src/opencode/resources/message.py +151 -0
  40. opencode_python_sdk-0.1.0/src/opencode/resources/migration.py +118 -0
  41. opencode_python_sdk-0.1.0/src/opencode/resources/model.py +175 -0
  42. opencode_python_sdk-0.1.0/src/opencode/resources/permission.py +698 -0
  43. opencode_python_sdk-0.1.0/src/opencode/resources/plugin.py +248 -0
  44. opencode_python_sdk-0.1.0/src/opencode/resources/project.py +185 -0
  45. opencode_python_sdk-0.1.0/src/opencode/resources/provider.py +181 -0
  46. opencode_python_sdk-0.1.0/src/opencode/resources/pty.py +517 -0
  47. opencode_python_sdk-0.1.0/src/opencode/resources/reference.py +98 -0
  48. opencode_python_sdk-0.1.0/src/opencode/resources/rpc.py +111 -0
  49. opencode_python_sdk-0.1.0/src/opencode/resources/server.py +228 -0
  50. opencode_python_sdk-0.1.0/src/opencode/resources/session.py +3553 -0
  51. opencode_python_sdk-0.1.0/src/opencode/resources/shell.py +410 -0
  52. opencode_python_sdk-0.1.0/src/opencode/resources/skill.py +98 -0
  53. opencode_python_sdk-0.1.0/src/opencode/resources/vcs.py +496 -0
  54. opencode_python_sdk-0.1.0/src/opencode/resources/voice.py +111 -0
  55. opencode_python_sdk-0.1.0/src/opencode/resources/websearch.py +182 -0
  56. opencode_python_sdk-0.1.0/src/opencode/resources/worktree.py +334 -0
  57. opencode_python_sdk-0.1.0/src/opencode/types/__init__.py +1462 -0
  58. opencode_python_sdk-0.1.0/src/opencode/types/_generated.py +8540 -0
  59. opencode_python_sdk-0.1.0/tests/__init__.py +0 -0
  60. opencode_python_sdk-0.1.0/tests/conftest.py +45 -0
  61. opencode_python_sdk-0.1.0/tests/test_async_client.py +51 -0
  62. opencode_python_sdk-0.1.0/tests/test_client.py +251 -0
  63. opencode_python_sdk-0.1.0/tests/test_encoding.py +56 -0
  64. opencode_python_sdk-0.1.0/tests/test_live.py +89 -0
  65. opencode_python_sdk-0.1.0/tests/test_models.py +68 -0
  66. opencode_python_sdk-0.1.0/tests/test_pagination.py +57 -0
  67. opencode_python_sdk-0.1.0/tests/test_streaming.py +78 -0
@@ -0,0 +1,16 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .coverage
8
+ coverage.xml
9
+ htmlcov/
10
+ .pytest_cache/
11
+ .mypy_cache/
12
+ .ruff_cache/
13
+ .pyright/
14
+ codegen/extract/node_modules/
15
+ codegen/extract/.cache/
16
+ .DS_Store
@@ -0,0 +1,9 @@
1
+ # Changelog
2
+
3
+ Releases are published automatically on every merge to `main`. Release notes for each version are on the
4
+ [GitHub releases page](https://github.com/shrijayan/opencode-python-sdk/releases).
5
+
6
+ ## 0.1.0
7
+
8
+ First release, generated from OpenCode 2.1.2: sync and async clients for all 142 operations, typed models and
9
+ events, typed errors, pagination, event streams, retries, and the `OpencodeServer` launcher.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shrijayan Rajendran and contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,317 @@
1
+ Metadata-Version: 2.5
2
+ Name: opencode-python-sdk
3
+ Version: 0.1.0
4
+ Summary: Typed Python client for the OpenCode 2.x server API
5
+ Project-URL: Homepage, https://github.com/shrijayan/opencode-python-sdk
6
+ Project-URL: Documentation, https://github.com/shrijayan/opencode-python-sdk#readme
7
+ Project-URL: Repository, https://github.com/shrijayan/opencode-python-sdk
8
+ Project-URL: Issues, https://github.com/shrijayan/opencode-python-sdk/issues
9
+ Project-URL: Changelog, https://github.com/shrijayan/opencode-python-sdk/blob/main/CHANGELOG.md
10
+ Author: Shrijayan Rajendran
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: agents,ai,client,coding-agent,opencode,sdk
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Framework :: AsyncIO
16
+ Classifier: Framework :: Pydantic :: 2
17
+ Classifier: Framework :: Trio
18
+ Classifier: Intended Audience :: Developers
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Programming Language :: Python :: 3
21
+ Classifier: Programming Language :: Python :: 3 :: Only
22
+ Classifier: Programming Language :: Python :: 3.10
23
+ Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Programming Language :: Python :: 3.12
25
+ Classifier: Programming Language :: Python :: 3.13
26
+ Classifier: Programming Language :: Python :: 3.14
27
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
28
+ Classifier: Typing :: Typed
29
+ Requires-Python: >=3.10
30
+ Requires-Dist: anyio<5,>=4.4
31
+ Requires-Dist: httpx<1,>=0.27
32
+ Requires-Dist: pydantic<3,>=2.12
33
+ Requires-Dist: typing-extensions>=4.12
34
+ Description-Content-Type: text/markdown
35
+
36
+ # opencode-python-sdk
37
+
38
+ [![CI](https://github.com/shrijayan/opencode-python-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/shrijayan/opencode-python-sdk/actions/workflows/ci.yml)
39
+ [![PyPI](https://img.shields.io/pypi/v/opencode-python-sdk.svg)](https://pypi.org/project/opencode-python-sdk/)
40
+ [![Python](https://img.shields.io/pypi/pyversions/opencode-python-sdk.svg)](https://pypi.org/project/opencode-python-sdk/)
41
+
42
+ Typed Python client for the [OpenCode](https://github.com/anomalyco/opencode) 2.x server API.
43
+
44
+ > **Unofficial, community-maintained.** This package is not affiliated with or endorsed by the OpenCode team.
45
+ > If the OpenCode maintainers want to take it over, we are happy to transfer this repository and the `opencode-python-sdk`
46
+ > name on PyPI. Open an issue and we will hand it over.
47
+
48
+ - **Complete.** All 142 operations of the OpenCode 2.1 API, sync and async, with typed models for every request,
49
+ response and event (96 event types).
50
+ - **Generated from the source of truth.** The contract is exported from the same Effect `HttpApi` the official
51
+ TypeScript client is built from, so method names and request shapes match it exactly.
52
+ - **Safe to run in production.** Retries only when a request cannot have changed server state, bounded backoff that
53
+ honors `Retry-After`, typed errors for every declared server error, and timeouts on everything.
54
+ - **Forward compatible.** New fields, enum values, union variants and event types from newer servers are kept, not
55
+ rejected, so an older client keeps working after a server upgrade.
56
+ - **Batteries included.** Starts and stops `opencode serve` for you, auto-paginates lists, and streams events.
57
+
58
+ ## Install
59
+
60
+ ```bash
61
+ pip install opencode-python-sdk
62
+ ```
63
+
64
+ The package imports as `opencode`. Requires Python 3.10 or newer. To start a server from Python, install the [OpenCode CLI](https://opencode.ai) too.
65
+
66
+ ## Quick start
67
+
68
+ ```python
69
+ from opencode import OpencodeServer
70
+
71
+ with OpencodeServer(cwd="/path/to/repo") as server, server.client() as client:
72
+ session = client.session.create(title="Review")
73
+ client.session.prompt(session.id, text="Summarize the open TODOs in this repository")
74
+ client.session.wait(session.id)
75
+
76
+ for message in client.message.list(session.id):
77
+ print(message.type)
78
+ ```
79
+
80
+ To connect to a server that is already running:
81
+
82
+ ```python
83
+ from opencode import Opencode
84
+
85
+ client = Opencode("http://127.0.0.1:4096", password="...")
86
+ print(client.server.info().version)
87
+ ```
88
+
89
+ `base_url` and `password` default to the `OPENCODE_BASE_URL` and `OPENCODE_PASSWORD` environment variables. The
90
+ password variable is the same one the OpenCode CLI reads.
91
+
92
+ ## Async
93
+
94
+ `AsyncOpencode` has the same methods. It works with asyncio and trio.
95
+
96
+ ```python
97
+ import asyncio
98
+
99
+ from opencode import AsyncOpencode
100
+
101
+
102
+ async def main() -> None:
103
+ async with AsyncOpencode("http://127.0.0.1:4096", password="...") as client:
104
+ session = await client.session.create(title="Async")
105
+ await client.session.prompt(session.id, text="Hello")
106
+
107
+ async for message in client.message.list(session.id):
108
+ print(message.type)
109
+
110
+
111
+ asyncio.run(main())
112
+ ```
113
+
114
+ ## Calling the API
115
+
116
+ Methods mirror the official TypeScript client: `client.<group>.<operation>()`, with names in `snake_case`. Path
117
+ parameters are positional; everything else is keyword-only.
118
+
119
+ ```python
120
+ client.session.get("ses_...")
121
+ client.session.prompt("ses_...", text="Fix the failing test", resume=True)
122
+ client.session.revert.stage("ses_...", message_id="msg_...")
123
+ client.mcp.list()
124
+ ```
125
+
126
+ The full list is in [api.md](api.md).
127
+
128
+ ### Optional and null arguments
129
+
130
+ The server distinguishes a field that is absent from a field set to `null`. Optional arguments default to
131
+ `NOT_GIVEN` and are left out of the request; passing `None` sends `null`:
132
+
133
+ ```python
134
+ client.session.update("ses_...", title="New title") # sends {"title": "New title"}
135
+ client.session.list(parent_id=None) # only root sessions: parentID=null
136
+ ```
137
+
138
+ ### Models
139
+
140
+ Responses are [pydantic](https://docs.pydantic.dev) models. Attributes are `snake_case`; the wire names stay
141
+ available as aliases, so a model validates server JSON and serializes back to it unchanged.
142
+
143
+ ```python
144
+ from opencode.types import ModelRef
145
+
146
+ session = client.session.create(model=ModelRef(id="claude-sonnet-5-5", provider_id="anthropic"))
147
+ session.project_id # attribute access
148
+ session.to_dict() # {"id": "...", "projectID": "...", ...}
149
+ ```
150
+
151
+ Unions are discriminated, so `isinstance` and `match` narrow them:
152
+
153
+ ```python
154
+ from opencode.types import SessionMessageAssistant, SessionMessageUser
155
+
156
+ for message in client.message.list(session.id):
157
+ match message:
158
+ case SessionMessageUser(text=text):
159
+ print("user:", text)
160
+ case SessionMessageAssistant():
161
+ print("assistant replied")
162
+ case _:
163
+ pass # includes variants added by newer servers
164
+ ```
165
+
166
+ ### Project directory
167
+
168
+ Most endpoints take a `location`. Set a default for the client once:
169
+
170
+ ```python
171
+ client = Opencode("http://127.0.0.1:4096", directory="/path/to/repo")
172
+ client.agent.list() # uses /path/to/repo
173
+ client.agent.list(location={"directory": "/other"}) # per call
174
+ ```
175
+
176
+ ## Pagination
177
+
178
+ List endpoints with cursors return a page. Iterating it fetches the following pages as needed.
179
+
180
+ ```python
181
+ for session in client.session.list(limit=50):
182
+ print(session.title)
183
+
184
+ page = client.session.list(limit=50)
185
+ page.data # this page only
186
+ page.has_next_page()
187
+ next_page = page.next_page()
188
+ ```
189
+
190
+ Async list methods return a paginator: `await` it for the first page, or `async for` over every item.
191
+
192
+ ## Events
193
+
194
+ ```python
195
+ with client.event.subscribe() as events:
196
+ for event in events:
197
+ print(event.type)
198
+ ```
199
+
200
+ Events are typed (`SessionCreatedEvent`, `SessionTextDeltaEvent`, and so on). Event types this client version does
201
+ not know arrive as `UnknownEvent` with all their fields kept. Streams apply only the connect timeout by default, since
202
+ they stay open between events.
203
+
204
+ ## Errors
205
+
206
+ Every error derives from `opencode.OpencodeError`. Errors the server declares are raised as their own class, with a
207
+ typed body:
208
+
209
+ ```python
210
+ import opencode
211
+
212
+ try:
213
+ client.session.get("ses_missing")
214
+ except opencode.SessionNotFoundError as error:
215
+ print(error.error.session_id, error.status_code)
216
+ except opencode.NotFoundError:
217
+ ... # any other 404
218
+ except opencode.APIConnectionError:
219
+ ... # server unreachable
220
+ ```
221
+
222
+ | Status | Class |
223
+ | --- | --- |
224
+ | 400 | `BadRequestError` |
225
+ | 401 | `AuthenticationError` |
226
+ | 403 | `PermissionDeniedError` |
227
+ | 404 | `NotFoundError` |
228
+ | 409 | `ConflictError` |
229
+ | 413 | `ContentTooLargeError` |
230
+ | 429 | `RateLimitError` |
231
+ | 5xx | `InternalServerError` |
232
+ | No response | `APIConnectionError`, `APITimeoutError` |
233
+ | Unexpected body | `APIResponseValidationError` |
234
+
235
+ ## Retries and timeouts
236
+
237
+ The client retries twice by default, with exponential backoff and jitter, and honors `Retry-After` up to 60 seconds.
238
+
239
+ - `GET`, `PUT` and `DELETE` are retried on connection errors and on 408, 429, 500, 502, 503 and 504.
240
+ - `POST` and `PATCH` are retried only when the request cannot have run: when the connection could not be opened, or
241
+ on 429 and 503. A prompt is never sent twice because a response was slow.
242
+
243
+ ```python
244
+ client = Opencode(max_retries=5, timeout=30.0)
245
+ client.with_options(timeout=None).session.wait(session.id) # wait as long as it takes
246
+ client.session.get("ses_...", timeout=5.0) # per call
247
+ ```
248
+
249
+ The default timeout is 10 minutes with a 10 second connect timeout.
250
+
251
+ ## Running a server
252
+
253
+ `OpencodeServer` runs `opencode serve` as a child process on a free local port with a random password:
254
+
255
+ ```python
256
+ from opencode import OpencodeServer
257
+
258
+ server = OpencodeServer(
259
+ cwd="/path/to/repo",
260
+ isolated=True, # ignore project opencode.json, AGENTS.md and Claude Code files
261
+ config={"model": "anthropic/claude-sonnet-5-5"}, # passed as OPENCODE_CONFIG_CONTENT
262
+ )
263
+ with server:
264
+ client = server.client()
265
+ ...
266
+ ```
267
+
268
+ The server runs in `--stdio` mode, so it also exits when the Python process dies. `AsyncOpencodeServer` is the
269
+ async equivalent.
270
+
271
+ ## Advanced
272
+
273
+ ```python
274
+ import httpx
275
+
276
+ from opencode import Opencode
277
+
278
+ client = Opencode(
279
+ "https://opencode.internal.example.com",
280
+ http_client=httpx.Client(proxy="http://proxy:8080", verify="/etc/ssl/ca.pem"),
281
+ default_headers={"X-Team": "platform"},
282
+ )
283
+
284
+ client.session.create(title="t", extra_headers={"X-Trace": "1"}, extra_body={"future": True})
285
+ ```
286
+
287
+ Logs go to the `opencode` logger. `logging.getLogger("opencode").setLevel(logging.DEBUG)` shows every request and
288
+ retry; credentials are never logged.
289
+
290
+ ## Versioning
291
+
292
+ | `opencode-python-sdk` (this package) | OpenCode server |
293
+ | --- | --- |
294
+ | 0.1.x | 2.1.x |
295
+
296
+ `opencode.API_VERSION` is the server version the client was generated from. The package follows semantic
297
+ versioning and releases automatically on every merge to `main`; until 1.0, minor versions may contain breaking
298
+ changes. See the [releases page](https://github.com/shrijayan/opencode-python-sdk/releases).
299
+
300
+ ## How it is built
301
+
302
+ ```
303
+ @opencode/protocol (npm) ──bun codegen/extract──▶ spec/contract.json ──python -m codegen──▶ src/opencode
304
+ ```
305
+
306
+ 1. `codegen/extract/extract.ts` loads `ClientApi` from the published `@opencode/protocol` package and exports
307
+ every endpoint, its inputs, outputs and declared errors, and every schema as JSON Schema.
308
+ 2. `python -m codegen` turns the contract into pydantic models (`opencode/types`), resources
309
+ (`opencode/resources`), error classes (`opencode/_errors.py`) and [api.md](api.md).
310
+ 3. The transport, retries, streaming, pagination and server launcher are written by hand.
311
+
312
+ Generated code is committed. CI regenerates it and fails if anything changed. See
313
+ [CONTRIBUTING.md](CONTRIBUTING.md).
314
+
315
+ ## License
316
+
317
+ MIT
@@ -0,0 +1,282 @@
1
+ # opencode-python-sdk
2
+
3
+ [![CI](https://github.com/shrijayan/opencode-python-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/shrijayan/opencode-python-sdk/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/opencode-python-sdk.svg)](https://pypi.org/project/opencode-python-sdk/)
5
+ [![Python](https://img.shields.io/pypi/pyversions/opencode-python-sdk.svg)](https://pypi.org/project/opencode-python-sdk/)
6
+
7
+ Typed Python client for the [OpenCode](https://github.com/anomalyco/opencode) 2.x server API.
8
+
9
+ > **Unofficial, community-maintained.** This package is not affiliated with or endorsed by the OpenCode team.
10
+ > If the OpenCode maintainers want to take it over, we are happy to transfer this repository and the `opencode-python-sdk`
11
+ > name on PyPI. Open an issue and we will hand it over.
12
+
13
+ - **Complete.** All 142 operations of the OpenCode 2.1 API, sync and async, with typed models for every request,
14
+ response and event (96 event types).
15
+ - **Generated from the source of truth.** The contract is exported from the same Effect `HttpApi` the official
16
+ TypeScript client is built from, so method names and request shapes match it exactly.
17
+ - **Safe to run in production.** Retries only when a request cannot have changed server state, bounded backoff that
18
+ honors `Retry-After`, typed errors for every declared server error, and timeouts on everything.
19
+ - **Forward compatible.** New fields, enum values, union variants and event types from newer servers are kept, not
20
+ rejected, so an older client keeps working after a server upgrade.
21
+ - **Batteries included.** Starts and stops `opencode serve` for you, auto-paginates lists, and streams events.
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ pip install opencode-python-sdk
27
+ ```
28
+
29
+ The package imports as `opencode`. Requires Python 3.10 or newer. To start a server from Python, install the [OpenCode CLI](https://opencode.ai) too.
30
+
31
+ ## Quick start
32
+
33
+ ```python
34
+ from opencode import OpencodeServer
35
+
36
+ with OpencodeServer(cwd="/path/to/repo") as server, server.client() as client:
37
+ session = client.session.create(title="Review")
38
+ client.session.prompt(session.id, text="Summarize the open TODOs in this repository")
39
+ client.session.wait(session.id)
40
+
41
+ for message in client.message.list(session.id):
42
+ print(message.type)
43
+ ```
44
+
45
+ To connect to a server that is already running:
46
+
47
+ ```python
48
+ from opencode import Opencode
49
+
50
+ client = Opencode("http://127.0.0.1:4096", password="...")
51
+ print(client.server.info().version)
52
+ ```
53
+
54
+ `base_url` and `password` default to the `OPENCODE_BASE_URL` and `OPENCODE_PASSWORD` environment variables. The
55
+ password variable is the same one the OpenCode CLI reads.
56
+
57
+ ## Async
58
+
59
+ `AsyncOpencode` has the same methods. It works with asyncio and trio.
60
+
61
+ ```python
62
+ import asyncio
63
+
64
+ from opencode import AsyncOpencode
65
+
66
+
67
+ async def main() -> None:
68
+ async with AsyncOpencode("http://127.0.0.1:4096", password="...") as client:
69
+ session = await client.session.create(title="Async")
70
+ await client.session.prompt(session.id, text="Hello")
71
+
72
+ async for message in client.message.list(session.id):
73
+ print(message.type)
74
+
75
+
76
+ asyncio.run(main())
77
+ ```
78
+
79
+ ## Calling the API
80
+
81
+ Methods mirror the official TypeScript client: `client.<group>.<operation>()`, with names in `snake_case`. Path
82
+ parameters are positional; everything else is keyword-only.
83
+
84
+ ```python
85
+ client.session.get("ses_...")
86
+ client.session.prompt("ses_...", text="Fix the failing test", resume=True)
87
+ client.session.revert.stage("ses_...", message_id="msg_...")
88
+ client.mcp.list()
89
+ ```
90
+
91
+ The full list is in [api.md](api.md).
92
+
93
+ ### Optional and null arguments
94
+
95
+ The server distinguishes a field that is absent from a field set to `null`. Optional arguments default to
96
+ `NOT_GIVEN` and are left out of the request; passing `None` sends `null`:
97
+
98
+ ```python
99
+ client.session.update("ses_...", title="New title") # sends {"title": "New title"}
100
+ client.session.list(parent_id=None) # only root sessions: parentID=null
101
+ ```
102
+
103
+ ### Models
104
+
105
+ Responses are [pydantic](https://docs.pydantic.dev) models. Attributes are `snake_case`; the wire names stay
106
+ available as aliases, so a model validates server JSON and serializes back to it unchanged.
107
+
108
+ ```python
109
+ from opencode.types import ModelRef
110
+
111
+ session = client.session.create(model=ModelRef(id="claude-sonnet-5-5", provider_id="anthropic"))
112
+ session.project_id # attribute access
113
+ session.to_dict() # {"id": "...", "projectID": "...", ...}
114
+ ```
115
+
116
+ Unions are discriminated, so `isinstance` and `match` narrow them:
117
+
118
+ ```python
119
+ from opencode.types import SessionMessageAssistant, SessionMessageUser
120
+
121
+ for message in client.message.list(session.id):
122
+ match message:
123
+ case SessionMessageUser(text=text):
124
+ print("user:", text)
125
+ case SessionMessageAssistant():
126
+ print("assistant replied")
127
+ case _:
128
+ pass # includes variants added by newer servers
129
+ ```
130
+
131
+ ### Project directory
132
+
133
+ Most endpoints take a `location`. Set a default for the client once:
134
+
135
+ ```python
136
+ client = Opencode("http://127.0.0.1:4096", directory="/path/to/repo")
137
+ client.agent.list() # uses /path/to/repo
138
+ client.agent.list(location={"directory": "/other"}) # per call
139
+ ```
140
+
141
+ ## Pagination
142
+
143
+ List endpoints with cursors return a page. Iterating it fetches the following pages as needed.
144
+
145
+ ```python
146
+ for session in client.session.list(limit=50):
147
+ print(session.title)
148
+
149
+ page = client.session.list(limit=50)
150
+ page.data # this page only
151
+ page.has_next_page()
152
+ next_page = page.next_page()
153
+ ```
154
+
155
+ Async list methods return a paginator: `await` it for the first page, or `async for` over every item.
156
+
157
+ ## Events
158
+
159
+ ```python
160
+ with client.event.subscribe() as events:
161
+ for event in events:
162
+ print(event.type)
163
+ ```
164
+
165
+ Events are typed (`SessionCreatedEvent`, `SessionTextDeltaEvent`, and so on). Event types this client version does
166
+ not know arrive as `UnknownEvent` with all their fields kept. Streams apply only the connect timeout by default, since
167
+ they stay open between events.
168
+
169
+ ## Errors
170
+
171
+ Every error derives from `opencode.OpencodeError`. Errors the server declares are raised as their own class, with a
172
+ typed body:
173
+
174
+ ```python
175
+ import opencode
176
+
177
+ try:
178
+ client.session.get("ses_missing")
179
+ except opencode.SessionNotFoundError as error:
180
+ print(error.error.session_id, error.status_code)
181
+ except opencode.NotFoundError:
182
+ ... # any other 404
183
+ except opencode.APIConnectionError:
184
+ ... # server unreachable
185
+ ```
186
+
187
+ | Status | Class |
188
+ | --- | --- |
189
+ | 400 | `BadRequestError` |
190
+ | 401 | `AuthenticationError` |
191
+ | 403 | `PermissionDeniedError` |
192
+ | 404 | `NotFoundError` |
193
+ | 409 | `ConflictError` |
194
+ | 413 | `ContentTooLargeError` |
195
+ | 429 | `RateLimitError` |
196
+ | 5xx | `InternalServerError` |
197
+ | No response | `APIConnectionError`, `APITimeoutError` |
198
+ | Unexpected body | `APIResponseValidationError` |
199
+
200
+ ## Retries and timeouts
201
+
202
+ The client retries twice by default, with exponential backoff and jitter, and honors `Retry-After` up to 60 seconds.
203
+
204
+ - `GET`, `PUT` and `DELETE` are retried on connection errors and on 408, 429, 500, 502, 503 and 504.
205
+ - `POST` and `PATCH` are retried only when the request cannot have run: when the connection could not be opened, or
206
+ on 429 and 503. A prompt is never sent twice because a response was slow.
207
+
208
+ ```python
209
+ client = Opencode(max_retries=5, timeout=30.0)
210
+ client.with_options(timeout=None).session.wait(session.id) # wait as long as it takes
211
+ client.session.get("ses_...", timeout=5.0) # per call
212
+ ```
213
+
214
+ The default timeout is 10 minutes with a 10 second connect timeout.
215
+
216
+ ## Running a server
217
+
218
+ `OpencodeServer` runs `opencode serve` as a child process on a free local port with a random password:
219
+
220
+ ```python
221
+ from opencode import OpencodeServer
222
+
223
+ server = OpencodeServer(
224
+ cwd="/path/to/repo",
225
+ isolated=True, # ignore project opencode.json, AGENTS.md and Claude Code files
226
+ config={"model": "anthropic/claude-sonnet-5-5"}, # passed as OPENCODE_CONFIG_CONTENT
227
+ )
228
+ with server:
229
+ client = server.client()
230
+ ...
231
+ ```
232
+
233
+ The server runs in `--stdio` mode, so it also exits when the Python process dies. `AsyncOpencodeServer` is the
234
+ async equivalent.
235
+
236
+ ## Advanced
237
+
238
+ ```python
239
+ import httpx
240
+
241
+ from opencode import Opencode
242
+
243
+ client = Opencode(
244
+ "https://opencode.internal.example.com",
245
+ http_client=httpx.Client(proxy="http://proxy:8080", verify="/etc/ssl/ca.pem"),
246
+ default_headers={"X-Team": "platform"},
247
+ )
248
+
249
+ client.session.create(title="t", extra_headers={"X-Trace": "1"}, extra_body={"future": True})
250
+ ```
251
+
252
+ Logs go to the `opencode` logger. `logging.getLogger("opencode").setLevel(logging.DEBUG)` shows every request and
253
+ retry; credentials are never logged.
254
+
255
+ ## Versioning
256
+
257
+ | `opencode-python-sdk` (this package) | OpenCode server |
258
+ | --- | --- |
259
+ | 0.1.x | 2.1.x |
260
+
261
+ `opencode.API_VERSION` is the server version the client was generated from. The package follows semantic
262
+ versioning and releases automatically on every merge to `main`; until 1.0, minor versions may contain breaking
263
+ changes. See the [releases page](https://github.com/shrijayan/opencode-python-sdk/releases).
264
+
265
+ ## How it is built
266
+
267
+ ```
268
+ @opencode/protocol (npm) ──bun codegen/extract──▶ spec/contract.json ──python -m codegen──▶ src/opencode
269
+ ```
270
+
271
+ 1. `codegen/extract/extract.ts` loads `ClientApi` from the published `@opencode/protocol` package and exports
272
+ every endpoint, its inputs, outputs and declared errors, and every schema as JSON Schema.
273
+ 2. `python -m codegen` turns the contract into pydantic models (`opencode/types`), resources
274
+ (`opencode/resources`), error classes (`opencode/_errors.py`) and [api.md](api.md).
275
+ 3. The transport, retries, streaming, pagination and server launcher are written by hand.
276
+
277
+ Generated code is committed. CI regenerates it and fails if anything changed. See
278
+ [CONTRIBUTING.md](CONTRIBUTING.md).
279
+
280
+ ## License
281
+
282
+ MIT