opencode-py 0.2.1__tar.gz → 0.3.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 (59) hide show
  1. opencode_py-0.3.0/CHANGELOG.md +62 -0
  2. opencode_py-0.3.0/PKG-INFO +502 -0
  3. opencode_py-0.3.0/README.md +467 -0
  4. opencode_py-0.3.0/README.ru.md +466 -0
  5. {opencode_py-0.2.1 → opencode_py-0.3.0}/RELEASE.md +6 -2
  6. opencode_py-0.3.0/VERIFY.md +84 -0
  7. {opencode_py-0.2.1 → opencode_py-0.3.0}/demo.py +12 -34
  8. {opencode_py-0.2.1 → opencode_py-0.3.0}/pyproject.toml +4 -1
  9. {opencode_py-0.2.1 → opencode_py-0.3.0}/scripts/check-release.py +95 -95
  10. {opencode_py-0.2.1 → opencode_py-0.3.0}/scripts/check-upstream.py +19 -8
  11. {opencode_py-0.2.1 → opencode_py-0.3.0}/src/opencode/_async_client.py +159 -194
  12. {opencode_py-0.2.1 → opencode_py-0.3.0}/src/opencode/_client.py +145 -162
  13. {opencode_py-0.2.1 → opencode_py-0.3.0}/src/opencode/_errors.py +94 -94
  14. opencode_py-0.3.0/src/opencode/_response_models.py +494 -0
  15. {opencode_py-0.2.1 → opencode_py-0.3.0}/tests/docker/test_smoke.py +3 -1
  16. {opencode_py-0.2.1 → opencode_py-0.3.0}/tests/test_async_client.py +4 -1
  17. {opencode_py-0.2.1 → opencode_py-0.3.0}/tests/test_client.py +9 -2
  18. {opencode_py-0.2.1 → opencode_py-0.3.0}/web/index.html +199 -199
  19. opencode_py-0.2.1/CHANGELOG.md +0 -22
  20. opencode_py-0.2.1/PKG-INFO +0 -246
  21. opencode_py-0.2.1/README.md +0 -211
  22. opencode_py-0.2.1/README.ru.md +0 -184
  23. opencode_py-0.2.1/src/opencode/_response_models.py +0 -242
  24. {opencode_py-0.2.1 → opencode_py-0.3.0}/.editorconfig +0 -0
  25. {opencode_py-0.2.1 → opencode_py-0.3.0}/.gitattributes +0 -0
  26. {opencode_py-0.2.1 → opencode_py-0.3.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  27. {opencode_py-0.2.1 → opencode_py-0.3.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  28. {opencode_py-0.2.1 → opencode_py-0.3.0}/.github/workflows/publish.yml +0 -0
  29. {opencode_py-0.2.1 → opencode_py-0.3.0}/.github/workflows/test.yml +0 -0
  30. {opencode_py-0.2.1 → opencode_py-0.3.0}/.gitignore +0 -0
  31. {opencode_py-0.2.1 → opencode_py-0.3.0}/.pre-commit-config.yaml +0 -0
  32. {opencode_py-0.2.1 → opencode_py-0.3.0}/AGENTS.md +0 -0
  33. {opencode_py-0.2.1 → opencode_py-0.3.0}/CODE_OF_CONDUCT.md +0 -0
  34. {opencode_py-0.2.1 → opencode_py-0.3.0}/CONTRIBUTING.md +0 -0
  35. {opencode_py-0.2.1 → opencode_py-0.3.0}/LICENSE +0 -0
  36. {opencode_py-0.2.1 → opencode_py-0.3.0}/SECURITY.md +0 -0
  37. {opencode_py-0.2.1 → opencode_py-0.3.0}/docs/opencode-docs-ru.md +0 -0
  38. {opencode_py-0.2.1 → opencode_py-0.3.0}/live.py +0 -0
  39. {opencode_py-0.2.1 → opencode_py-0.3.0}/live_async.py +0 -0
  40. {opencode_py-0.2.1 → opencode_py-0.3.0}/live_streaming.py +0 -0
  41. {opencode_py-0.2.1 → opencode_py-0.3.0}/src/opencode/__init__.py +0 -0
  42. {opencode_py-0.2.1 → opencode_py-0.3.0}/src/opencode/__main__.py +0 -0
  43. {opencode_py-0.2.1 → opencode_py-0.3.0}/src/opencode/_async_opencode.py +0 -0
  44. {opencode_py-0.2.1 → opencode_py-0.3.0}/src/opencode/_async_session.py +0 -0
  45. {opencode_py-0.2.1 → opencode_py-0.3.0}/src/opencode/_binary.py +0 -0
  46. {opencode_py-0.2.1 → opencode_py-0.3.0}/src/opencode/_logs.py +0 -0
  47. {opencode_py-0.2.1 → opencode_py-0.3.0}/src/opencode/_models.py +0 -0
  48. {opencode_py-0.2.1 → opencode_py-0.3.0}/src/opencode/_opencode.py +0 -0
  49. {opencode_py-0.2.1 → opencode_py-0.3.0}/src/opencode/_process.py +0 -0
  50. {opencode_py-0.2.1 → opencode_py-0.3.0}/src/opencode/_server.py +0 -0
  51. {opencode_py-0.2.1 → opencode_py-0.3.0}/src/opencode/_session.py +0 -0
  52. {opencode_py-0.2.1 → opencode_py-0.3.0}/src/opencode/_tools.py +0 -0
  53. {opencode_py-0.2.1 → opencode_py-0.3.0}/src/opencode/_types.py +0 -0
  54. {opencode_py-0.2.1 → opencode_py-0.3.0}/src/opencode/py.typed +0 -0
  55. {opencode_py-0.2.1 → opencode_py-0.3.0}/test_all.py +0 -0
  56. {opencode_py-0.2.1 → opencode_py-0.3.0}/test_live.py +0 -0
  57. {opencode_py-0.2.1 → opencode_py-0.3.0}/tests/docker/Dockerfile +0 -0
  58. {opencode_py-0.2.1 → opencode_py-0.3.0}/tests/test_opencode.py +0 -0
  59. {opencode_py-0.2.1 → opencode_py-0.3.0}/web/server.py +0 -0
@@ -0,0 +1,62 @@
1
+ # Changelog
2
+
3
+ ## v0.3.0 (2026-07-05)
4
+
5
+ - feat(client): add typed Pydantic response models (`cast_to`) to all ~75 client methods
6
+ - feat(client): add 30+ response model classes (AgentResponse, CommandResponse, ConfigResponse, FileNode, FindMatch, VcsInfo, PtyResponse, WorktreeResponse, WorkspaceResponse, etc.)
7
+ - feat(client): `_construct_type` handles generic `list[X]` via `get_origin/get_args` and `{"data": [...]}` response format
8
+ - fix(client): `AgentResponse.permission` changed from `dict` to `Any` (server returns list)
9
+ - fix(scripts): resolve mypy `no-any-return` in check-upstream.py
10
+ - fix(docs): correct web UI port in VERIFY.md (3000, not 8000)
11
+ - fix(tests): import SessionMessage from `_models`, not `_session`
12
+ - chore(lint): suppress N815 camelCase warnings for `_response_models.py`
13
+ - chore(docs): add VERIFY.md release checklist
14
+
15
+ ## v0.2.2 (2026-07-04)
16
+
17
+ - docs: comprehensive README documentation in EN and RU (CLI flags, structured output, session methods, error hierarchy, ToolExecutor, binary management, OpencodeServer, config reference, async API, response models)
18
+ - fix(demo): Pydantic model compatibility
19
+ - fix(scripts): mypy errors in check-upstream.py
20
+ - docs(readme): clarify binary auto-download behavior (NOT system-wide, NOT in PATH)
21
+
22
+ ## v0.2.1 (2026-07-04)
23
+
24
+ - fix: rename entry point to `opencode-py` to avoid conflict with the real `opencode` binary
25
+
26
+ ## v0.2.0 (2026-07-03)
27
+
28
+ - feat: Pydantic response models (HealthResponse, SessionResponse, FileContentResponse, V1SessionResponse)
29
+ - feat: retry logic with exponential backoff and jitter
30
+ - feat: typed error hierarchy (15+ classes)
31
+ - feat: logging via OPENCODE_LOG env var
32
+ - feat: async full support (AsyncOpendcodeClient, AsyncSession, AsyncOpendcode)
33
+ - feat: streaming (ask_stream sync + async)
34
+ - feat: auto_tools mode with ToolExecutor and permissions
35
+ - feat: web UI with proxy server (zero dependencies)
36
+ - feat: structured output (format parameter)
37
+ - feat: OpencodeServer lifecycle management
38
+ - feat: binary auto-download (PATH → ~/.opencode/bin/ → GitHub)
39
+ - feat: check-upstream.py script for monitoring openapi.json changes
40
+ - feat: `.copy()` / `.with_options()` for immutable client cloning
41
+ - feat: py.typed marker for PEP 561 compliance
42
+
43
+ ## v0.1.1 (2026-07-03)
44
+
45
+ - chore: add keywords to pyproject.toml and .gitattributes
46
+ - docs: add badges to README and MIT license file
47
+ - feat(tests): add Docker smoke test for clean-machine scenario
48
+ - chore: use importlib.metadata for `__version__`
49
+ - chore: bump version to 0.1.1 for author/URLs fix
50
+ - fix(publish): set author to Sergey Kislyakov, fix URLs
51
+
52
+ ## v0.1.0 (2026-06-30)
53
+
54
+ - feat: initial Python SDK for Opencode
55
+ - fix: resolve npm .cmd wrappers to real .exe binary on Windows
56
+ - fix(session): use V1 sync prompt with model support instead of V2
57
+ - feat(sdk): add keep parameter for multi-turn conversations
58
+ - feat(sdk): add auto_tools mode with ToolExecutor and permissions
59
+ - feat(web): add zero-dependency web UI with proxy server
60
+ - feat(async): add AsyncOpendcodeClient, AsyncSession, AsyncOpendcode
61
+ - feat(stream): add live_streaming.py, SSE streaming via ask_stream
62
+ - feat(sdk): add async_opencode, structured output, check-upstream
@@ -0,0 +1,502 @@
1
+ Metadata-Version: 2.4
2
+ Name: opencode-py
3
+ Version: 0.3.0
4
+ Summary: Python SDK for Opencode — the open source AI coding agent
5
+ Project-URL: Homepage, https://github.com/skislyakow/opencode-py
6
+ Project-URL: Repository, https://github.com/skislyakow/opencode-py
7
+ Project-URL: Documentation, https://github.com/skislyakow/opencode-py
8
+ Project-URL: Changelog, https://github.com/skislyakow/opencode-py/blob/main/CHANGELOG.md
9
+ Author-email: Sergey Kislyakov <s.kislyakov84@gmail.com>
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: ai,coding-agent,llm,opencode,python-sdk,sdk
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: httpx>=0.27.0
24
+ Requires-Dist: pydantic>=2.0.0
25
+ Requires-Dist: typing-extensions>=4.6.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: build>=1.0; extra == 'dev'
28
+ Requires-Dist: httpx>=0.27.0; extra == 'dev'
29
+ Requires-Dist: mypy>=1.10.0; extra == 'dev'
30
+ Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
31
+ Requires-Dist: pytest>=8.0; extra == 'dev'
32
+ Requires-Dist: ruff>=0.5.0; extra == 'dev'
33
+ Requires-Dist: twine>=4.0; extra == 'dev'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # Opencode Python SDK
37
+
38
+ <p align="center">
39
+ <a href="https://pypi.org/project/opencode-py/"><img src="https://img.shields.io/pypi/v/opencode-py" alt="PyPI version"></a>
40
+ <a href="https://pypi.org/project/opencode-py/"><img src="https://img.shields.io/pypi/pyversions/opencode-py" alt="Python versions"></a>
41
+ <a href="https://github.com/skislyakow/opencode-py/blob/main/LICENSE"><img src="https://img.shields.io/pypi/l/opencode-py" alt="License"></a>
42
+ <a href="https://pypi.org/project/opencode-py/"><img src="https://img.shields.io/pypi/dm/opencode-py" alt="Downloads"></a>
43
+ <a href="https://github.com/skislyakow/opencode-py/actions/workflows/test.yml"><img src="https://github.com/skislyakow/opencode-py/actions/workflows/test.yml/badge.svg" alt="Tests"></a>
44
+ <img src="https://img.shields.io/badge/build-hatchling-4051b5" alt="Hatchling">
45
+ <img src="https://img.shields.io/badge/http-httpx-blue" alt="httpx">
46
+ <img src="https://img.shields.io/badge/models-pydantic-E92063" alt="pydantic">
47
+ </p>
48
+
49
+ Python SDK for [Opencode](https://opencode.ai) — the open source AI coding agent.
50
+
51
+ ```bash
52
+ pip install opencode-py
53
+ ```
54
+
55
+ **Do I need Opencode pre-installed?** No. The SDK automatically downloads the
56
+ `opencode` binary for your OS (Windows/macOS/Linux, x64/arm64) on first use to
57
+ `~/.opencode/bin/`. The binary is only used internally by the SDK — it is NOT
58
+ added to PATH, NOT registered system-wide, and NOT shown in the Start Menu.
59
+
60
+ **What if I install the official Opencode later?** If you install Opencode
61
+ via `npm install -g opencode-ai` or another method, the SDK will use the
62
+ PATH version instead — no conflict.
63
+
64
+ *See [Binary management](#binary-management) for details.*
65
+
66
+ ## CLI
67
+
68
+ After installation, the `opencode-py` command is available **system-wide** from any directory:
69
+
70
+ ```bash
71
+ opencode-py "What is the capital of France?" # one-shot prompt
72
+ echo "What is the capital of France?" | opencode-py # via pipe
73
+ opencode-py --help # show all options
74
+ ```
75
+
76
+ All CLI flags:
77
+
78
+ | Flag | Description |
79
+ |------|-------------|
80
+ | `prompt` (positional) | Prompt text or read from stdin |
81
+ | `--model` / `-m` | Model name (e.g. `opencode/big-pickle`) |
82
+ | `--keep` / `-k` | Keep session alive between calls |
83
+ | `--auto-tools` | Enable agentic tool execution |
84
+ | `--directory` / `-d` | Working directory |
85
+ | `--port` / `-p` | Server port (default: 4096) |
86
+
87
+ You can also use `python -m opencode`:
88
+
89
+ ```bash
90
+ python -m opencode "Explain dependency injection"
91
+ python -m opencode --model "opencode/big-pickle" "Hello"
92
+ ```
93
+
94
+ ## Client library reference
95
+
96
+ ### One-shot (spawns server, asks, cleans up)
97
+
98
+ ```python
99
+ from opencode import opencode
100
+
101
+ answer = opencode("What is the capital of France?")
102
+ print(answer)
103
+ ```
104
+
105
+ ### Context manager (recommended)
106
+
107
+ ```python
108
+ from opencode import Opencode
109
+
110
+ with Opencode() as ai:
111
+ answer = ai.ask("Explain dependency injection")
112
+ print(answer)
113
+ ```
114
+
115
+ ### Streaming
116
+
117
+ ```python
118
+ with Opencode() as ai:
119
+ for chunk in ai.ask_stream("Write a Python function"):
120
+ print(chunk, end="")
121
+ ```
122
+
123
+ ### Conversations
124
+
125
+ ```python
126
+ with Opencode() as ai:
127
+ session = ai.create_session()
128
+ msg1 = session.prompt("Suggest a project name")
129
+ print(f"AI: {msg1}")
130
+ msg2 = session.prompt("Now write a tagline for it")
131
+ print(f"AI: {msg2}")
132
+ ```
133
+
134
+ ### Session methods
135
+
136
+ Every `Session` object provides additional methods:
137
+
138
+ ```python
139
+ with Opencode() as ai:
140
+ session = ai.create_session()
141
+ session.prompt("Hello")
142
+
143
+ # Get conversation history
144
+ ctx = session.context() # list of all messages
145
+ msgs = session.messages() # paginated message list
146
+
147
+ # Control
148
+ session.abort() # abort current generation
149
+ session.compact() # compact conversation
150
+ session.fork() # fork into new session
151
+
152
+ # Inspect
153
+ session.diff() # file changes made by AI
154
+ session.todo() # remaining TODOs
155
+ ```
156
+
157
+ ### Multi-turn (keep mode)
158
+
159
+ Reuses server and session across calls:
160
+
161
+ ```python
162
+ from opencode import opencode
163
+
164
+ r1 = opencode("My name is Alice", keep=True)
165
+ r2 = opencode("What's my name?", keep=True) # remembers conversation
166
+ r3 = opencode("That's all", keep=False) # closes server
167
+
168
+ # Also accepts: model, format, port, directory, config, agent
169
+ ```
170
+
171
+ ### Auto-tools (agentic tool execution)
172
+
173
+ ```python
174
+ r = opencode("Create a file called hello.txt", auto_tools=True)
175
+ ```
176
+
177
+ Available tools: `bash`, `write`, `edit`, `read`, `glob`, `grep`.
178
+
179
+ By default `bash` asks for permission in the console, all others run without prompting.
180
+
181
+ Custom permissions via `Session.ask()`:
182
+
183
+ ```python
184
+ from opencode import Opencode, ToolExecutor
185
+
186
+ with Opencode() as ai:
187
+ session = ai.create_session()
188
+ msg = session.ask(
189
+ "Write test.py with print('hello')",
190
+ tool_executor=ToolExecutor(
191
+ permissions={"write": "allow"},
192
+ workdir="/path/to/sandbox", # restrict file operations
193
+ ),
194
+ max_tool_rounds=25, # safety limit
195
+ quiet=True, # suppress tool logs
196
+ )
197
+ ```
198
+
199
+ The first AI response in `ask()` enters plan mode — the SDK auto-confirms with
200
+ `"Exit plan mode and proceed"` to make the model execute tools immediately.
201
+
202
+ ### Low-level client (any endpoint)
203
+
204
+ ```python
205
+ with Opencode() as ai:
206
+ content = ai.client.file_read("src/main.py")
207
+ diff = ai.client.vcs_diff("HEAD~3")
208
+ config = ai.client.config_get()
209
+ session = ai.client.session_create()
210
+ ai.client.v2_session_prompt(session.id, {"text": "Hello"})
211
+ ```
212
+
213
+ All client methods return typed Pydantic models — IDE autocomplete,
214
+ `.model_dump()`, `.model_dump_json()`.
215
+
216
+ #### Connecting to an existing server
217
+
218
+ Skip subprocess management by pointing at a running `opencode serve`:
219
+
220
+ ```python
221
+ from opencode import OpencodeClient
222
+
223
+ client = OpencodeClient(base_url="http://127.0.0.1:4096", directory=".")
224
+ health = client.health()
225
+ ```
226
+
227
+ ```python
228
+ from opencode import AsyncOpendcodeClient
229
+
230
+ async with AsyncOpendcodeClient(base_url="http://127.0.0.1:4096") as client:
231
+ health = await client.health()
232
+ ```
233
+
234
+ #### Cloning a client
235
+
236
+ ```python
237
+ client2 = client.copy(base_url="http://other:4096", timeout=60.0)
238
+
239
+ # Or via with_options:
240
+ faster = client.with_options(timeout=10.0, max_retries=0)
241
+ ```
242
+
243
+ ### Retry & error handling
244
+
245
+ Typed exception hierarchy. All errors are importable from `opencode`:
246
+
247
+ ```python
248
+ from opencode import OpencodeClient, RateLimitError, InternalServerError
249
+
250
+ client = OpencodeClient(max_retries=3) # exponential backoff with jitter
251
+
252
+ try:
253
+ health = client.health()
254
+ print(health.version)
255
+ except RateLimitError:
256
+ print("too many requests — retried but failed")
257
+ except InternalServerError:
258
+ print("server error")
259
+ ```
260
+
261
+ Full error class hierarchy:
262
+
263
+ | Class | HTTP status | When raised |
264
+ |-------|-------------|-------------|
265
+ | `OpencodeError` | — | Base for all SDK errors |
266
+ | `APIConnectionError` | — | Network / connection failure |
267
+ | `APITimeoutError` | — | Request timed out |
268
+ | `APIResponseValidationError` | — | Response doesn't match schema |
269
+ | `APIStatusError` | 4xx/5xx | Base for HTTP error responses |
270
+ | `BadRequestError` | 400 | Malformed request |
271
+ | `AuthenticationError` | 401 | Invalid or missing API key |
272
+ | `PermissionDeniedError` | 403 | Access denied |
273
+ | `NotFoundError` | 404 | Resource not found |
274
+ | `ConflictError` | 409 | Resource conflict |
275
+ | `UnprocessableEntityError` | 422 | Validation error in request body |
276
+ | `RateLimitError` | 429 | Rate limit exceeded |
277
+ | `InternalServerError` | 500+ | Server-side error |
278
+ | `BinaryNotFoundError` | — | `opencode` binary not on PATH |
279
+ | `ServerStartupTimeoutError` | — | Server didn't start in time |
280
+
281
+ Retry policy: 408, 409, 429, 5xx and timeouts are retried with exponential
282
+ backoff + jitter. `Retry-After` and `retry-after-ms` headers are respected.
283
+
284
+ ### Structured output
285
+
286
+ ```python
287
+ with Opencode(model="anthropic/claude-sonnet-4") as ai:
288
+ result = ai.ask(
289
+ "Generate a user profile",
290
+ format={
291
+ "type": "json_schema",
292
+ "schema": {
293
+ "type": "object",
294
+ "properties": {
295
+ "name": {"type": "string"},
296
+ "age": {"type": "integer"},
297
+ },
298
+ "required": ["name", "age"],
299
+ },
300
+ },
301
+ )
302
+ # result is a JSON string matching the schema
303
+ ```
304
+
305
+ Works with `opencode()`, `async_opencode()`, `Session.prompt()`, and `Session.ask()`.
306
+
307
+ Requires a model that supports `tool_choice="required"` (Claude, GPT-4).
308
+ The free `opencode/big-pickle` (DeepSeek) does NOT support this.
309
+
310
+ ### Debug logging
311
+
312
+ ```bash
313
+ # Linux / macOS (bash/zsh)
314
+ OPENCODE_LOG=debug python my_script.py
315
+
316
+ # Windows (PowerShell)
317
+ $env:OPENCODE_LOG="debug"; python my_script.py
318
+
319
+ # Windows (cmd)
320
+ set OPENCODE_LOG=debug && python my_script.py
321
+ ```
322
+
323
+ Shows all HTTP requests/responses with timing.
324
+
325
+ ### Web UI (zero dependencies)
326
+
327
+ ```bash
328
+ python web/server.py
329
+ # → open http://127.0.0.1:3000
330
+ ```
331
+
332
+ Built-in HTTP server + proxy to `opencode serve` — no extra dependencies.
333
+
334
+ ### Interactive dialog
335
+
336
+ ```bash
337
+ python live.py
338
+ ```
339
+
340
+ Multi-turn dialog with `keep=True`, server cleaned up on exit via `atexit`.
341
+
342
+ ### ToolExecutor reference
343
+
344
+ ```python
345
+ from opencode import ToolExecutor
346
+
347
+ # Default permissions:
348
+ # bash → "ask" (prompts in console)
349
+ # write → "allow"
350
+ # edit → "allow"
351
+ # read → "allow"
352
+ # glob → "allow"
353
+ # grep → "allow"
354
+
355
+ executor = ToolExecutor(
356
+ permissions={
357
+ "bash": "allow", # always allow
358
+ "write": "deny", # always deny
359
+ "grep": "ask", # ask each time
360
+ },
361
+ workdir="/path/to/sandbox", # restrict file operations here
362
+ confirm=lambda name, inp: name != "bash", # custom confirm function
363
+ )
364
+
365
+ # Use with Session.ask():
366
+ session.ask("Create a project", tool_executor=executor)
367
+ ```
368
+
369
+ ### Binary management
370
+
371
+ When `opencode` is not on PATH, the SDK auto-downloads it to
372
+ `~/.opencode/bin/opencode`.
373
+
374
+ Resolution order:
375
+ 1. `PATH` — `shutil.which("opencode")`
376
+ 2. `~/.opencode/bin/opencode` — previously downloaded copy
377
+ 3. GitHub releases — download for current platform
378
+
379
+ Supported platforms: `win32-x64`, `win32-arm64`, `darwin-x64`, `darwin-arm64`,
380
+ `linux-x64`, `linux-arm64`.
381
+
382
+ Override the binary path directly:
383
+
384
+ ```python
385
+ with Opencode(opencode_binary="/custom/path/opencode") as ai:
386
+ ...
387
+ ```
388
+
389
+ ### OpencodeServer (low-level server control)
390
+
391
+ ```python
392
+ from opencode import OpencodeServer, create_opencode_server
393
+
394
+ server = create_opencode_server(
395
+ port=4096,
396
+ hostname="127.0.0.1",
397
+ timeout=30.0,
398
+ config={"model": "opencode/big-pickle"},
399
+ opencode_binary="/path/to/opencode",
400
+ )
401
+ print(server.url) # "http://127.0.0.1:4096"
402
+
403
+ # Later:
404
+ server.close() # kills the subprocess
405
+ ```
406
+
407
+ ## Configuration reference
408
+
409
+ All parameters for `Opendcode()` / `AsyncOpendcode()`:
410
+
411
+ | Parameter | Default | Description |
412
+ |-----------|---------|-------------|
413
+ | `model` | `None` | Model name, e.g. `"opencode/big-pickle"` or `"provider/model"` |
414
+ | `hostname` | `"127.0.0.1"` | Bind address for the server |
415
+ | `port` | `4096` | Port for the server |
416
+ | `directory` | `None` | Working directory passed to all API calls |
417
+ | `workspace` | `None` | Workspace directory for the session |
418
+ | `server_timeout` | `30.0` | Seconds to wait for server startup |
419
+ | `client_timeout` | `300.0` | Seconds before HTTP request timeout |
420
+ | `config` | `None` | Server config dict (see opencode docs) |
421
+ | `opencode_binary` | `None` | Path to opencode binary (auto-downloaded if not set) |
422
+
423
+ All parameters are keyword-only.
424
+
425
+ ## Async API
426
+
427
+ ### Basic
428
+
429
+ ```python
430
+ import asyncio
431
+ from opencode import AsyncOpendcode
432
+
433
+ async def main():
434
+ async with AsyncOpendcode() as ai:
435
+ answer = await ai.ask("Explain async/await in Python")
436
+ print(answer)
437
+
438
+ asyncio.run(main())
439
+ ```
440
+
441
+ ### Async streaming
442
+
443
+ ```python
444
+ async with AsyncOpendcode() as ai:
445
+ async for chunk in ai.ask_stream("Write a poem"):
446
+ print(chunk, end="")
447
+ ```
448
+
449
+ ### Async conversations
450
+
451
+ ```python
452
+ async with AsyncOpendcode() as ai:
453
+ session = await ai.create_session()
454
+ msg1 = await session.prompt("Suggest a project name")
455
+ msg2 = await session.prompt("Now write a tagline for it")
456
+ ```
457
+
458
+ ### Async low-level client
459
+
460
+ ```python
461
+ from opencode import AsyncOpendcodeClient
462
+
463
+ async with AsyncOpendcodeClient() as client:
464
+ health = await client.health()
465
+ print(health.version) # typed Pydantic model
466
+ ```
467
+
468
+ ### Async convenience function
469
+
470
+ ```python
471
+ from opencode import async_opencode
472
+
473
+ result = await async_opencode("Hello", keep=True)
474
+ result2 = await async_opencode("Still there?", keep=True)
475
+ result3 = await async_opencode("Bye")
476
+
477
+ # Also accepts: model, format, port, directory, config, agent, auto_tools
478
+ ```
479
+
480
+ ## OpenAPI response models
481
+
482
+ ```python
483
+ from opencode._response_models import HealthResponse, SessionResponse, FileContentResponse
484
+
485
+ # These are Pydantic BaseModel classes with:
486
+ # .model_dump() -> dict
487
+ # .model_dump_json() -> str
488
+ # .model_validate(dict) -> classmethod
489
+ ```
490
+
491
+ ## Development
492
+
493
+ ```bash
494
+ # Install in editable mode
495
+ pip install -e ".[dev]"
496
+
497
+ # Run tests
498
+ pytest
499
+
500
+ # Build
501
+ python -m build --wheel
502
+ ```