opencode-py 0.2.1__py3-none-any.whl → 0.2.2__py3-none-any.whl
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.
- opencode_py-0.2.2.dist-info/METADATA +491 -0
- {opencode_py-0.2.1.dist-info → opencode_py-0.2.2.dist-info}/RECORD +5 -5
- opencode_py-0.2.1.dist-info/METADATA +0 -246
- {opencode_py-0.2.1.dist-info → opencode_py-0.2.2.dist-info}/WHEEL +0 -0
- {opencode_py-0.2.1.dist-info → opencode_py-0.2.2.dist-info}/entry_points.txt +0 -0
- {opencode_py-0.2.1.dist-info → opencode_py-0.2.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: opencode-py
|
|
3
|
+
Version: 0.2.2
|
|
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
|
+
## CLI
|
|
56
|
+
|
|
57
|
+
After installation, the `opencode-py` command is available **system-wide** from any directory:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
opencode-py "What is the capital of France?" # one-shot prompt
|
|
61
|
+
echo "What is the capital of France?" | opencode-py # via pipe
|
|
62
|
+
opencode-py --help # show all options
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
All CLI flags:
|
|
66
|
+
|
|
67
|
+
| Flag | Description |
|
|
68
|
+
|------|-------------|
|
|
69
|
+
| `prompt` (positional) | Prompt text or read from stdin |
|
|
70
|
+
| `--model` / `-m` | Model name (e.g. `opencode/big-pickle`) |
|
|
71
|
+
| `--keep` / `-k` | Keep session alive between calls |
|
|
72
|
+
| `--auto-tools` | Enable agentic tool execution |
|
|
73
|
+
| `--directory` / `-d` | Working directory |
|
|
74
|
+
| `--port` / `-p` | Server port (default: 4096) |
|
|
75
|
+
|
|
76
|
+
You can also use `python -m opencode`:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
python -m opencode "Explain dependency injection"
|
|
80
|
+
python -m opencode --model "opencode/big-pickle" "Hello"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Client library reference
|
|
84
|
+
|
|
85
|
+
### One-shot (spawns server, asks, cleans up)
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from opencode import opencode
|
|
89
|
+
|
|
90
|
+
answer = opencode("What is the capital of France?")
|
|
91
|
+
print(answer)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Context manager (recommended)
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from opencode import Opencode
|
|
98
|
+
|
|
99
|
+
with Opencode() as ai:
|
|
100
|
+
answer = ai.ask("Explain dependency injection")
|
|
101
|
+
print(answer)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Streaming
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
with Opencode() as ai:
|
|
108
|
+
for chunk in ai.ask_stream("Write a Python function"):
|
|
109
|
+
print(chunk, end="")
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Conversations
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
with Opencode() as ai:
|
|
116
|
+
session = ai.create_session()
|
|
117
|
+
msg1 = session.prompt("Suggest a project name")
|
|
118
|
+
print(f"AI: {msg1}")
|
|
119
|
+
msg2 = session.prompt("Now write a tagline for it")
|
|
120
|
+
print(f"AI: {msg2}")
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Session methods
|
|
124
|
+
|
|
125
|
+
Every `Session` object provides additional methods:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
with Opencode() as ai:
|
|
129
|
+
session = ai.create_session()
|
|
130
|
+
session.prompt("Hello")
|
|
131
|
+
|
|
132
|
+
# Get conversation history
|
|
133
|
+
ctx = session.context() # list of all messages
|
|
134
|
+
msgs = session.messages() # paginated message list
|
|
135
|
+
|
|
136
|
+
# Control
|
|
137
|
+
session.abort() # abort current generation
|
|
138
|
+
session.compact() # compact conversation
|
|
139
|
+
session.fork() # fork into new session
|
|
140
|
+
|
|
141
|
+
# Inspect
|
|
142
|
+
session.diff() # file changes made by AI
|
|
143
|
+
session.todo() # remaining TODOs
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Multi-turn (keep mode)
|
|
147
|
+
|
|
148
|
+
Reuses server and session across calls:
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from opencode import opencode
|
|
152
|
+
|
|
153
|
+
r1 = opencode("My name is Alice", keep=True)
|
|
154
|
+
r2 = opencode("What's my name?", keep=True) # remembers conversation
|
|
155
|
+
r3 = opencode("That's all", keep=False) # closes server
|
|
156
|
+
|
|
157
|
+
# Also accepts: model, format, port, directory, config, agent
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Auto-tools (agentic tool execution)
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
r = opencode("Create a file called hello.txt", auto_tools=True)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Available tools: `bash`, `write`, `edit`, `read`, `glob`, `grep`.
|
|
167
|
+
|
|
168
|
+
By default `bash` asks for permission in the console, all others run without prompting.
|
|
169
|
+
|
|
170
|
+
Custom permissions via `Session.ask()`:
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
from opencode import Opencode, ToolExecutor
|
|
174
|
+
|
|
175
|
+
with Opencode() as ai:
|
|
176
|
+
session = ai.create_session()
|
|
177
|
+
msg = session.ask(
|
|
178
|
+
"Write test.py with print('hello')",
|
|
179
|
+
tool_executor=ToolExecutor(
|
|
180
|
+
permissions={"write": "allow"},
|
|
181
|
+
workdir="/path/to/sandbox", # restrict file operations
|
|
182
|
+
),
|
|
183
|
+
max_tool_rounds=25, # safety limit
|
|
184
|
+
quiet=True, # suppress tool logs
|
|
185
|
+
)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The first AI response in `ask()` enters plan mode — the SDK auto-confirms with
|
|
189
|
+
`"Exit plan mode and proceed"` to make the model execute tools immediately.
|
|
190
|
+
|
|
191
|
+
### Low-level client (any endpoint)
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
with Opencode() as ai:
|
|
195
|
+
content = ai.client.file_read("src/main.py")
|
|
196
|
+
diff = ai.client.vcs_diff("HEAD~3")
|
|
197
|
+
config = ai.client.config_get()
|
|
198
|
+
session = ai.client.session_create()
|
|
199
|
+
ai.client.v2_session_prompt(session.id, {"text": "Hello"})
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
All client methods return typed Pydantic models — IDE autocomplete,
|
|
203
|
+
`.model_dump()`, `.model_dump_json()`.
|
|
204
|
+
|
|
205
|
+
#### Connecting to an existing server
|
|
206
|
+
|
|
207
|
+
Skip subprocess management by pointing at a running `opencode serve`:
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
from opencode import OpencodeClient
|
|
211
|
+
|
|
212
|
+
client = OpencodeClient(base_url="http://127.0.0.1:4096", directory=".")
|
|
213
|
+
health = client.health()
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
from opencode import AsyncOpendcodeClient
|
|
218
|
+
|
|
219
|
+
async with AsyncOpendcodeClient(base_url="http://127.0.0.1:4096") as client:
|
|
220
|
+
health = await client.health()
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
#### Cloning a client
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
client2 = client.copy(base_url="http://other:4096", timeout=60.0)
|
|
227
|
+
|
|
228
|
+
# Or via with_options:
|
|
229
|
+
faster = client.with_options(timeout=10.0, max_retries=0)
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Retry & error handling
|
|
233
|
+
|
|
234
|
+
Typed exception hierarchy. All errors are importable from `opencode`:
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
from opencode import OpencodeClient, RateLimitError, InternalServerError
|
|
238
|
+
|
|
239
|
+
client = OpencodeClient(max_retries=3) # exponential backoff with jitter
|
|
240
|
+
|
|
241
|
+
try:
|
|
242
|
+
health = client.health()
|
|
243
|
+
print(health.version)
|
|
244
|
+
except RateLimitError:
|
|
245
|
+
print("too many requests — retried but failed")
|
|
246
|
+
except InternalServerError:
|
|
247
|
+
print("server error")
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Full error class hierarchy:
|
|
251
|
+
|
|
252
|
+
| Class | HTTP status | When raised |
|
|
253
|
+
|-------|-------------|-------------|
|
|
254
|
+
| `OpencodeError` | — | Base for all SDK errors |
|
|
255
|
+
| `APIConnectionError` | — | Network / connection failure |
|
|
256
|
+
| `APITimeoutError` | — | Request timed out |
|
|
257
|
+
| `APIResponseValidationError` | — | Response doesn't match schema |
|
|
258
|
+
| `APIStatusError` | 4xx/5xx | Base for HTTP error responses |
|
|
259
|
+
| `BadRequestError` | 400 | Malformed request |
|
|
260
|
+
| `AuthenticationError` | 401 | Invalid or missing API key |
|
|
261
|
+
| `PermissionDeniedError` | 403 | Access denied |
|
|
262
|
+
| `NotFoundError` | 404 | Resource not found |
|
|
263
|
+
| `ConflictError` | 409 | Resource conflict |
|
|
264
|
+
| `UnprocessableEntityError` | 422 | Validation error in request body |
|
|
265
|
+
| `RateLimitError` | 429 | Rate limit exceeded |
|
|
266
|
+
| `InternalServerError` | 500+ | Server-side error |
|
|
267
|
+
| `BinaryNotFoundError` | — | `opencode` binary not on PATH |
|
|
268
|
+
| `ServerStartupTimeoutError` | — | Server didn't start in time |
|
|
269
|
+
|
|
270
|
+
Retry policy: 408, 409, 429, 5xx and timeouts are retried with exponential
|
|
271
|
+
backoff + jitter. `Retry-After` and `retry-after-ms` headers are respected.
|
|
272
|
+
|
|
273
|
+
### Structured output
|
|
274
|
+
|
|
275
|
+
```python
|
|
276
|
+
with Opencode(model="anthropic/claude-sonnet-4") as ai:
|
|
277
|
+
result = ai.ask(
|
|
278
|
+
"Generate a user profile",
|
|
279
|
+
format={
|
|
280
|
+
"type": "json_schema",
|
|
281
|
+
"schema": {
|
|
282
|
+
"type": "object",
|
|
283
|
+
"properties": {
|
|
284
|
+
"name": {"type": "string"},
|
|
285
|
+
"age": {"type": "integer"},
|
|
286
|
+
},
|
|
287
|
+
"required": ["name", "age"],
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
)
|
|
291
|
+
# result is a JSON string matching the schema
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Works with `opencode()`, `async_opencode()`, `Session.prompt()`, and `Session.ask()`.
|
|
295
|
+
|
|
296
|
+
Requires a model that supports `tool_choice="required"` (Claude, GPT-4).
|
|
297
|
+
The free `opencode/big-pickle` (DeepSeek) does NOT support this.
|
|
298
|
+
|
|
299
|
+
### Debug logging
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
# Linux / macOS (bash/zsh)
|
|
303
|
+
OPENCODE_LOG=debug python my_script.py
|
|
304
|
+
|
|
305
|
+
# Windows (PowerShell)
|
|
306
|
+
$env:OPENCODE_LOG="debug"; python my_script.py
|
|
307
|
+
|
|
308
|
+
# Windows (cmd)
|
|
309
|
+
set OPENCODE_LOG=debug && python my_script.py
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Shows all HTTP requests/responses with timing.
|
|
313
|
+
|
|
314
|
+
### Web UI (zero dependencies)
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
python web/server.py
|
|
318
|
+
# → open http://127.0.0.1:3000
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Built-in HTTP server + proxy to `opencode serve` — no extra dependencies.
|
|
322
|
+
|
|
323
|
+
### Interactive dialog
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
python live.py
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Multi-turn dialog with `keep=True`, server cleaned up on exit via `atexit`.
|
|
330
|
+
|
|
331
|
+
### ToolExecutor reference
|
|
332
|
+
|
|
333
|
+
```python
|
|
334
|
+
from opencode import ToolExecutor
|
|
335
|
+
|
|
336
|
+
# Default permissions:
|
|
337
|
+
# bash → "ask" (prompts in console)
|
|
338
|
+
# write → "allow"
|
|
339
|
+
# edit → "allow"
|
|
340
|
+
# read → "allow"
|
|
341
|
+
# glob → "allow"
|
|
342
|
+
# grep → "allow"
|
|
343
|
+
|
|
344
|
+
executor = ToolExecutor(
|
|
345
|
+
permissions={
|
|
346
|
+
"bash": "allow", # always allow
|
|
347
|
+
"write": "deny", # always deny
|
|
348
|
+
"grep": "ask", # ask each time
|
|
349
|
+
},
|
|
350
|
+
workdir="/path/to/sandbox", # restrict file operations here
|
|
351
|
+
confirm=lambda name, inp: name != "bash", # custom confirm function
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
# Use with Session.ask():
|
|
355
|
+
session.ask("Create a project", tool_executor=executor)
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Binary management
|
|
359
|
+
|
|
360
|
+
When `opencode` is not on PATH, the SDK auto-downloads it to
|
|
361
|
+
`~/.opencode/bin/opencode`.
|
|
362
|
+
|
|
363
|
+
Resolution order:
|
|
364
|
+
1. `PATH` — `shutil.which("opencode")`
|
|
365
|
+
2. `~/.opencode/bin/opencode` — previously downloaded copy
|
|
366
|
+
3. GitHub releases — download for current platform
|
|
367
|
+
|
|
368
|
+
Supported platforms: `win32-x64`, `win32-arm64`, `darwin-x64`, `darwin-arm64`,
|
|
369
|
+
`linux-x64`, `linux-arm64`.
|
|
370
|
+
|
|
371
|
+
Override the binary path directly:
|
|
372
|
+
|
|
373
|
+
```python
|
|
374
|
+
with Opencode(opencode_binary="/custom/path/opencode") as ai:
|
|
375
|
+
...
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### OpencodeServer (low-level server control)
|
|
379
|
+
|
|
380
|
+
```python
|
|
381
|
+
from opencode import OpencodeServer, create_opencode_server
|
|
382
|
+
|
|
383
|
+
server = create_opencode_server(
|
|
384
|
+
port=4096,
|
|
385
|
+
hostname="127.0.0.1",
|
|
386
|
+
timeout=30.0,
|
|
387
|
+
config={"model": "opencode/big-pickle"},
|
|
388
|
+
opencode_binary="/path/to/opencode",
|
|
389
|
+
)
|
|
390
|
+
print(server.url) # "http://127.0.0.1:4096"
|
|
391
|
+
|
|
392
|
+
# Later:
|
|
393
|
+
server.close() # kills the subprocess
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
## Configuration reference
|
|
397
|
+
|
|
398
|
+
All parameters for `Opendcode()` / `AsyncOpendcode()`:
|
|
399
|
+
|
|
400
|
+
| Parameter | Default | Description |
|
|
401
|
+
|-----------|---------|-------------|
|
|
402
|
+
| `model` | `None` | Model name, e.g. `"opencode/big-pickle"` or `"provider/model"` |
|
|
403
|
+
| `hostname` | `"127.0.0.1"` | Bind address for the server |
|
|
404
|
+
| `port` | `4096` | Port for the server |
|
|
405
|
+
| `directory` | `None` | Working directory passed to all API calls |
|
|
406
|
+
| `workspace` | `None` | Workspace directory for the session |
|
|
407
|
+
| `server_timeout` | `30.0` | Seconds to wait for server startup |
|
|
408
|
+
| `client_timeout` | `300.0` | Seconds before HTTP request timeout |
|
|
409
|
+
| `config` | `None` | Server config dict (see opencode docs) |
|
|
410
|
+
| `opencode_binary` | `None` | Path to opencode binary (auto-downloaded if not set) |
|
|
411
|
+
|
|
412
|
+
All parameters are keyword-only.
|
|
413
|
+
|
|
414
|
+
## Async API
|
|
415
|
+
|
|
416
|
+
### Basic
|
|
417
|
+
|
|
418
|
+
```python
|
|
419
|
+
import asyncio
|
|
420
|
+
from opencode import AsyncOpendcode
|
|
421
|
+
|
|
422
|
+
async def main():
|
|
423
|
+
async with AsyncOpendcode() as ai:
|
|
424
|
+
answer = await ai.ask("Explain async/await in Python")
|
|
425
|
+
print(answer)
|
|
426
|
+
|
|
427
|
+
asyncio.run(main())
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### Async streaming
|
|
431
|
+
|
|
432
|
+
```python
|
|
433
|
+
async with AsyncOpendcode() as ai:
|
|
434
|
+
async for chunk in ai.ask_stream("Write a poem"):
|
|
435
|
+
print(chunk, end="")
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### Async conversations
|
|
439
|
+
|
|
440
|
+
```python
|
|
441
|
+
async with AsyncOpendcode() as ai:
|
|
442
|
+
session = await ai.create_session()
|
|
443
|
+
msg1 = await session.prompt("Suggest a project name")
|
|
444
|
+
msg2 = await session.prompt("Now write a tagline for it")
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### Async low-level client
|
|
448
|
+
|
|
449
|
+
```python
|
|
450
|
+
from opencode import AsyncOpendcodeClient
|
|
451
|
+
|
|
452
|
+
async with AsyncOpendcodeClient() as client:
|
|
453
|
+
health = await client.health()
|
|
454
|
+
print(health.version) # typed Pydantic model
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### Async convenience function
|
|
458
|
+
|
|
459
|
+
```python
|
|
460
|
+
from opencode import async_opencode
|
|
461
|
+
|
|
462
|
+
result = await async_opencode("Hello", keep=True)
|
|
463
|
+
result2 = await async_opencode("Still there?", keep=True)
|
|
464
|
+
result3 = await async_opencode("Bye")
|
|
465
|
+
|
|
466
|
+
# Also accepts: model, format, port, directory, config, agent, auto_tools
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
## OpenAPI response models
|
|
470
|
+
|
|
471
|
+
```python
|
|
472
|
+
from opencode._response_models import HealthResponse, SessionResponse, FileContentResponse
|
|
473
|
+
|
|
474
|
+
# These are Pydantic BaseModel classes with:
|
|
475
|
+
# .model_dump() -> dict
|
|
476
|
+
# .model_dump_json() -> str
|
|
477
|
+
# .model_validate(dict) -> classmethod
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
## Development
|
|
481
|
+
|
|
482
|
+
```bash
|
|
483
|
+
# Install in editable mode
|
|
484
|
+
pip install -e ".[dev]"
|
|
485
|
+
|
|
486
|
+
# Run tests
|
|
487
|
+
pytest
|
|
488
|
+
|
|
489
|
+
# Build
|
|
490
|
+
python -m build --wheel
|
|
491
|
+
```
|
|
@@ -16,8 +16,8 @@ opencode/_session.py,sha256=WA9O36a9jIQPVWbiRa_Qq--Q2_sQgfZY-qriFYuIEUk,6273
|
|
|
16
16
|
opencode/_tools.py,sha256=pXIcGiuBLHZJrhFoIr02PD5rXRZ_kmEfH_yCDL-4kCw,6186
|
|
17
17
|
opencode/_types.py,sha256=abvR9AgIDy1Ao4udciG4bm5EVrS1a7iI8UjTnfp_4F0,513
|
|
18
18
|
opencode/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
19
|
-
opencode_py-0.2.
|
|
20
|
-
opencode_py-0.2.
|
|
21
|
-
opencode_py-0.2.
|
|
22
|
-
opencode_py-0.2.
|
|
23
|
-
opencode_py-0.2.
|
|
19
|
+
opencode_py-0.2.2.dist-info/METADATA,sha256=4mIasdpNlyz1UpBxDf81T0Dl2_lslSzKGPeDqaCX1tw,14097
|
|
20
|
+
opencode_py-0.2.2.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
21
|
+
opencode_py-0.2.2.dist-info/entry_points.txt,sha256=uFnHHKDTlnLNw7YZ7oV7aAOQZKKmhet5DMAkGKkoSU4,55
|
|
22
|
+
opencode_py-0.2.2.dist-info/licenses/LICENSE,sha256=ELTf0xrIX83aTz71tsvSJmHlanAsDLGOc3trjTn1exA,1073
|
|
23
|
+
opencode_py-0.2.2.dist-info/RECORD,,
|
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: opencode-py
|
|
3
|
-
Version: 0.2.1
|
|
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
|
-
## Quick start
|
|
56
|
-
|
|
57
|
-
### One-shot (spawns server, asks, cleans up)
|
|
58
|
-
|
|
59
|
-
```python
|
|
60
|
-
from opencode import opencode
|
|
61
|
-
|
|
62
|
-
answer = opencode("What is the capital of France?")
|
|
63
|
-
print(answer)
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### Context manager (recommended)
|
|
67
|
-
|
|
68
|
-
```python
|
|
69
|
-
from opencode import Opencode
|
|
70
|
-
|
|
71
|
-
with Opencode() as ai:
|
|
72
|
-
answer = ai.ask("Explain dependency injection")
|
|
73
|
-
print(answer)
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
### Streaming
|
|
77
|
-
|
|
78
|
-
```python
|
|
79
|
-
with Opencode() as ai:
|
|
80
|
-
for chunk in ai.ask_stream("Write a Python function"):
|
|
81
|
-
print(chunk, end="")
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
### Conversations
|
|
85
|
-
|
|
86
|
-
```python
|
|
87
|
-
with Opencode() as ai:
|
|
88
|
-
session = ai.create_session()
|
|
89
|
-
msg1 = session.prompt("Suggest a project name")
|
|
90
|
-
print(f"AI: {msg1}")
|
|
91
|
-
msg2 = session.prompt("Now write a tagline for it")
|
|
92
|
-
print(f"AI: {msg2}")
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
### Multi-turn (keep mode)
|
|
96
|
-
|
|
97
|
-
```python
|
|
98
|
-
from opencode import opencode
|
|
99
|
-
|
|
100
|
-
# keep=True — server and session stay alive between calls
|
|
101
|
-
r1 = opencode("My name is Alice", keep=True)
|
|
102
|
-
r2 = opencode("What's my name?", keep=True) # remembers the conversation
|
|
103
|
-
r3 = opencode("That's all", keep=False) # keep=False closes the server
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
### Auto-tools (agentic tool execution)
|
|
107
|
-
|
|
108
|
-
```python
|
|
109
|
-
r = opencode("Create a file called hello.txt", auto_tools=True)
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
Available tools: `bash`, `write`, `edit`, `read`, `glob`, `grep`.
|
|
113
|
-
|
|
114
|
-
By default, `bash` asks for permission in the console, all others run without prompting.
|
|
115
|
-
|
|
116
|
-
Custom permissions via `Session.ask()`:
|
|
117
|
-
|
|
118
|
-
```python
|
|
119
|
-
from opencode import Opencode, ToolExecutor
|
|
120
|
-
|
|
121
|
-
with Opencode() as ai:
|
|
122
|
-
session = ai.create_session()
|
|
123
|
-
msg = session.ask(
|
|
124
|
-
"Write test.py with print('hello')",
|
|
125
|
-
tool_executor=ToolExecutor(permissions={"write": "allow"}),
|
|
126
|
-
)
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
### Low-level API (any endpoint)
|
|
130
|
-
|
|
131
|
-
```python
|
|
132
|
-
with Opencode() as ai:
|
|
133
|
-
content = ai.client.file_read("src/main.py")
|
|
134
|
-
diff = ai.client.vcs_diff("HEAD~3")
|
|
135
|
-
config = ai.client.config_get()
|
|
136
|
-
session = ai.client.session_create()
|
|
137
|
-
ai.client.v2_session_prompt(session.id, {"text": "Hello"})
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
All client methods return typed Pydantic models — IDE autocomplete, validation, `.model_dump()`, `.model_dump_json()`.
|
|
141
|
-
|
|
142
|
-
### Retry & error handling
|
|
143
|
-
|
|
144
|
-
```python
|
|
145
|
-
from opencode import OpencodeClient, RateLimitError, InternalServerError
|
|
146
|
-
|
|
147
|
-
client = OpencodeClient(max_retries=3) # exponential backoff with jitter
|
|
148
|
-
|
|
149
|
-
try:
|
|
150
|
-
health = client.health()
|
|
151
|
-
print(health.version)
|
|
152
|
-
except RateLimitError:
|
|
153
|
-
print("too many requests — retried but failed")
|
|
154
|
-
except InternalServerError:
|
|
155
|
-
print("server error")
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
### Debug logging
|
|
159
|
-
|
|
160
|
-
```bash
|
|
161
|
-
OPENCODE_LOG=debug python my_script.py
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
Shows all HTTP requests/responses with timing.
|
|
165
|
-
|
|
166
|
-
### Web UI (zero dependencies)
|
|
167
|
-
|
|
168
|
-
```bash
|
|
169
|
-
python web/server.py
|
|
170
|
-
# → open http://127.0.0.1:3000
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
Built-in HTTP server + proxy to `opencode serve` — no extra dependencies.
|
|
174
|
-
|
|
175
|
-
### Interactive dialog
|
|
176
|
-
|
|
177
|
-
```bash
|
|
178
|
-
python live.py
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
Multi-turn dialog with `keep=True`, server cleaned up on exit via `atexit`.
|
|
182
|
-
|
|
183
|
-
### Configuration
|
|
184
|
-
|
|
185
|
-
```python
|
|
186
|
-
with Opencode(
|
|
187
|
-
model="claude-sonnet-4-20250514",
|
|
188
|
-
directory="/path/to/project",
|
|
189
|
-
port=4096,
|
|
190
|
-
) as ai:
|
|
191
|
-
...
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
## Async API
|
|
195
|
-
|
|
196
|
-
```python
|
|
197
|
-
import asyncio
|
|
198
|
-
from opencode import AsyncOpendcode
|
|
199
|
-
|
|
200
|
-
async def main():
|
|
201
|
-
async with AsyncOpendcode() as ai:
|
|
202
|
-
answer = await ai.ask("Explain async/await in Python")
|
|
203
|
-
print(answer)
|
|
204
|
-
|
|
205
|
-
asyncio.run(main())
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
### Async streaming
|
|
209
|
-
|
|
210
|
-
```python
|
|
211
|
-
async with AsyncOpendcode() as ai:
|
|
212
|
-
async for chunk in ai.ask_stream("Write a poem"):
|
|
213
|
-
print(chunk, end="")
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
### Async conversations
|
|
217
|
-
|
|
218
|
-
```python
|
|
219
|
-
async with AsyncOpendcode() as ai:
|
|
220
|
-
session = await ai.create_session()
|
|
221
|
-
msg1 = await session.prompt("Suggest a project name")
|
|
222
|
-
msg2 = await session.prompt("Now write a tagline for it")
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
### Async low-level client
|
|
226
|
-
|
|
227
|
-
```python
|
|
228
|
-
from opencode import AsyncOpendcodeClient
|
|
229
|
-
|
|
230
|
-
async with AsyncOpendcodeClient() as client:
|
|
231
|
-
health = await client.health()
|
|
232
|
-
print(health.version) # typed Pydantic model
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
## Development
|
|
236
|
-
|
|
237
|
-
```bash
|
|
238
|
-
# Install in editable mode
|
|
239
|
-
pip install -e ".[dev]"
|
|
240
|
-
|
|
241
|
-
# Run tests
|
|
242
|
-
pytest
|
|
243
|
-
|
|
244
|
-
# Build
|
|
245
|
-
python -m build --wheel
|
|
246
|
-
```
|
|
File without changes
|
|
File without changes
|
|
File without changes
|