opencode-runtime 0.4.0__tar.gz → 0.5.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.
- {opencode_runtime-0.4.0 → opencode_runtime-0.5.0}/.github/workflows/ci.yml +3 -1
- {opencode_runtime-0.4.0 → opencode_runtime-0.5.0}/.gitignore +1 -1
- {opencode_runtime-0.4.0 → opencode_runtime-0.5.0}/CHANGELOG.md +24 -1
- opencode_runtime-0.5.0/PKG-INFO +126 -0
- opencode_runtime-0.5.0/README.md +93 -0
- opencode_runtime-0.5.0/docs/cli.md +120 -0
- opencode_runtime-0.5.0/docs/http-client.md +96 -0
- opencode_runtime-0.5.0/docs/opencode-config.md +94 -0
- opencode_runtime-0.5.0/docs/sessions.md +102 -0
- opencode_runtime-0.5.0/docs/streaming.md +150 -0
- opencode_runtime-0.5.0/docs/users-and-workspaces.md +125 -0
- {opencode_runtime-0.4.0 → opencode_runtime-0.5.0}/pyproject.toml +2 -2
- {opencode_runtime-0.4.0 → opencode_runtime-0.5.0}/src/opencode_runtime/__init__.py +2 -2
- {opencode_runtime-0.4.0 → opencode_runtime-0.5.0}/src/opencode_runtime/cli.py +151 -23
- {opencode_runtime-0.4.0 → opencode_runtime-0.5.0}/src/opencode_runtime/client.py +4 -7
- {opencode_runtime-0.4.0 → opencode_runtime-0.5.0}/src/opencode_runtime/event.py +3 -7
- {opencode_runtime-0.4.0 → opencode_runtime-0.5.0}/src/opencode_runtime/exceptions.py +8 -0
- opencode_runtime-0.5.0/src/opencode_runtime/registry.py +188 -0
- {opencode_runtime-0.4.0 → opencode_runtime-0.5.0}/src/opencode_runtime/response.py +1 -1
- {opencode_runtime-0.4.0 → opencode_runtime-0.5.0}/src/opencode_runtime/runtime.py +12 -8
- opencode_runtime-0.5.0/src/opencode_runtime/schema.py +79 -0
- opencode_runtime-0.5.0/src/opencode_runtime/server.py +549 -0
- {opencode_runtime-0.4.0 → opencode_runtime-0.5.0}/src/opencode_runtime/session.py +6 -3
- {opencode_runtime-0.4.0 → opencode_runtime-0.5.0}/tests/test_cli.py +19 -37
- {opencode_runtime-0.4.0 → opencode_runtime-0.5.0}/tests/test_client.py +1 -2
- {opencode_runtime-0.4.0 → opencode_runtime-0.5.0}/tests/test_multi_tenant.py +2 -19
- opencode_runtime-0.5.0/tests/test_registry.py +239 -0
- opencode_runtime-0.5.0/tests/test_runtime.py +114 -0
- opencode_runtime-0.5.0/tests/test_schema.py +106 -0
- {opencode_runtime-0.4.0 → opencode_runtime-0.5.0}/tests/test_server.py +134 -2
- {opencode_runtime-0.4.0 → opencode_runtime-0.5.0}/tests/test_session.py +1 -2
- opencode_runtime-0.4.0/PKG-INFO +0 -299
- opencode_runtime-0.4.0/README.md +0 -266
- opencode_runtime-0.4.0/src/opencode_runtime/registry.py +0 -84
- opencode_runtime-0.4.0/src/opencode_runtime/server.py +0 -394
- opencode_runtime-0.4.0/tests/test_registry.py +0 -159
- opencode_runtime-0.4.0/tests/test_runtime.py +0 -152
- {opencode_runtime-0.4.0 → opencode_runtime-0.5.0}/.github/workflows/publish.yml +0 -0
- {opencode_runtime-0.4.0 → opencode_runtime-0.5.0}/CONTRIBUTING.md +0 -0
- {opencode_runtime-0.4.0 → opencode_runtime-0.5.0}/LICENSE +0 -0
- {opencode_runtime-0.4.0 → opencode_runtime-0.5.0}/src/opencode_runtime/py.typed +0 -0
- {opencode_runtime-0.4.0 → opencode_runtime-0.5.0}/tests/test.py +0 -0
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [
|
|
3
|
+
## [0.5.0]
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- SQLite-backed server registry at `~/.opencode-runtime/servers/registry.db` — persistent, atomic, supports concurrent claims
|
|
7
|
+
- `ServerState` enum (STARTING, RUNNING, STOPPING, FAILED) for type-safe state tracking
|
|
8
|
+
- Health probes: process liveness check + HTTP health endpoint verification; display status shows running/starting/unhealthy/stale/failed
|
|
9
|
+
- `inspect` command: detailed server info (PID, port, uptime, idle time, runtime version, health)
|
|
10
|
+
- Server metadata: track `runtime_version` and `last_used_at` timestamp
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Registry moved from PID files to SQLite — no API changes, same registry operations
|
|
14
|
+
- All state tracking uses `ServerState` enum instead of string literals (type safety)
|
|
15
|
+
- Server startup now lazy — contexts don't auto-create default server, start on first `session()` call
|
|
16
|
+
- `runtime.close()` now stops only servers that `OpenCodeRuntime` instance itself started; servers it merely attached to (already running, started by another process or the CLI) are left alone (safe for multi-process deployments)
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- one `OpenCodeRuntime` exiting would terminate servers created by other processes or CLI — now scoped to servers it actually started
|
|
20
|
+
|
|
21
|
+
## [0.4.1] - 2026-07-09
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- `server.py`: set `OPENCODE_CONFIG` env var instead of the unrecognised `OPENCODE_CONFIG_HOME` — without this fix the `config` dict (model, permissions, etc.) was never read by the opencode binary, silently ignored every time
|
|
25
|
+
- `client.py`: SSE stream now terminates only on `session.idle` or `session.error`; previously `session.status {type: "idle"}` caused the generator to exit early, dropping any events that followed — including `permission.asked`
|
|
26
|
+
- `session.py`: `ask()` now propagates the real server error message from `event.raw["properties"]["error"]`; previously it always raised the generic fallback `"unknown error from opencode server"` because `event.text` is always `None` for `session.error` events
|
|
4
27
|
|
|
5
28
|
## [0.4.0] - 2026-07-07
|
|
6
29
|
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: opencode-runtime
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Embed OpenCode in your Python application.
|
|
5
|
+
Project-URL: Homepage, https://github.com/ashish16052/opencode-runtime
|
|
6
|
+
Project-URL: Repository, https://github.com/ashish16052/opencode-runtime
|
|
7
|
+
Project-URL: Issues, https://github.com/ashish16052/opencode-runtime/issues
|
|
8
|
+
Author: Ashish Mohite
|
|
9
|
+
License-Expression: Apache-2.0
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: AI coding agent,agent runtime,lifecycle management,multi-user,opencode,workspace isolation
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: httpx
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: build; extra == 'dev'
|
|
25
|
+
Requires-Dist: hatchling; extra == 'dev'
|
|
26
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest-asyncio; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest-timeout; extra == 'dev'
|
|
30
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
31
|
+
Requires-Dist: twine; extra == 'dev'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# opencode-runtime
|
|
35
|
+
|
|
36
|
+
**Embed OpenCode in your Python application.**
|
|
37
|
+
|
|
38
|
+
OpenCode is a great CLI coding agent. But to embed it in a product, SaaS
|
|
39
|
+
backend, or multi-user automation system, you need more than a CLI process.
|
|
40
|
+
|
|
41
|
+
opencode-runtime turns OpenCode into a managed application runtime for
|
|
42
|
+
Python. It uses OpenCode as the agent harness and adds what you need to
|
|
43
|
+
run it as part of an application: lifecycle management, per-user
|
|
44
|
+
workspaces, reusable sessions, health checks, streaming output, and
|
|
45
|
+
multi-server orchestration.
|
|
46
|
+
|
|
47
|
+
## Install
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
pip install opencode-runtime
|
|
51
|
+
npm install -g opencode-ai # opencode must be on PATH
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Use
|
|
55
|
+
|
|
56
|
+
### Your first session
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from opencode_runtime import OpenCodeRuntime
|
|
60
|
+
|
|
61
|
+
async with OpenCodeRuntime() as r:
|
|
62
|
+
session = await r.session()
|
|
63
|
+
response = await session.ask("Explain this repo")
|
|
64
|
+
print(response.text)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### One server per user, automatically
|
|
68
|
+
|
|
69
|
+
Every `(workspace, user_id)` pair gets its own isolated server, workspace, and conversation history — started on first use, reused after:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
async with OpenCodeRuntime(runtime_dir=".opencode-runtime") as r:
|
|
73
|
+
s1 = await r.session(workspace="org_a", user_id="u_1")
|
|
74
|
+
s2 = await r.session(workspace="org_b", user_id="u_2")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Stream every token as it arrives
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
async for event in session.stream("Review this PR"):
|
|
81
|
+
if event.type == "message.part.delta" and event.text:
|
|
82
|
+
print(event.text, end="", flush=True)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Inspect
|
|
86
|
+
|
|
87
|
+
Servers started from Python are visible and manageable from the terminal:
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
opencode-runtime ps
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
ID PID PORT STATUS UPTIME WORKSPACE USER PROJECT
|
|
95
|
+
───────────────────────────────────────────────────────────────────────────────────
|
|
96
|
+
39dce5beb4debfaa 12051 58409 ● running 5m org_a u_1 ~/Developer/myproject
|
|
97
|
+
81fa29acb3e9210f 12088 58411 ● running 3m org_b u_2 ~/Developer/myproject
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
```sh
|
|
101
|
+
opencode-runtime health 39dce5beb4debfaa
|
|
102
|
+
opencode-runtime inspect 39dce5beb4debfaa
|
|
103
|
+
opencode-runtime stop-all
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Guides
|
|
107
|
+
|
|
108
|
+
- [OpenCode config](docs/opencode-config.md) — models, permissions, agents, skills
|
|
109
|
+
- [Sessions](docs/sessions.md) — continuation, resume across restarts
|
|
110
|
+
- [Users & workspaces](docs/users-and-workspaces.md) — multi-tenant isolation
|
|
111
|
+
- [Streaming](docs/streaming.md) — event types, tool calls, permissions, cost
|
|
112
|
+
- [CLI](docs/cli.md) — manage your fleet from the terminal
|
|
113
|
+
- [HTTP client](docs/http-client.md) — direct server access
|
|
114
|
+
|
|
115
|
+
## Requirements
|
|
116
|
+
|
|
117
|
+
- Python 3.10+
|
|
118
|
+
- `opencode` 1.0+ on PATH
|
|
119
|
+
|
|
120
|
+
## Contributing
|
|
121
|
+
|
|
122
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
Apache 2.0
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# opencode-runtime
|
|
2
|
+
|
|
3
|
+
**Embed OpenCode in your Python application.**
|
|
4
|
+
|
|
5
|
+
OpenCode is a great CLI coding agent. But to embed it in a product, SaaS
|
|
6
|
+
backend, or multi-user automation system, you need more than a CLI process.
|
|
7
|
+
|
|
8
|
+
opencode-runtime turns OpenCode into a managed application runtime for
|
|
9
|
+
Python. It uses OpenCode as the agent harness and adds what you need to
|
|
10
|
+
run it as part of an application: lifecycle management, per-user
|
|
11
|
+
workspaces, reusable sessions, health checks, streaming output, and
|
|
12
|
+
multi-server orchestration.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
pip install opencode-runtime
|
|
18
|
+
npm install -g opencode-ai # opencode must be on PATH
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Use
|
|
22
|
+
|
|
23
|
+
### Your first session
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from opencode_runtime import OpenCodeRuntime
|
|
27
|
+
|
|
28
|
+
async with OpenCodeRuntime() as r:
|
|
29
|
+
session = await r.session()
|
|
30
|
+
response = await session.ask("Explain this repo")
|
|
31
|
+
print(response.text)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### One server per user, automatically
|
|
35
|
+
|
|
36
|
+
Every `(workspace, user_id)` pair gets its own isolated server, workspace, and conversation history — started on first use, reused after:
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
async with OpenCodeRuntime(runtime_dir=".opencode-runtime") as r:
|
|
40
|
+
s1 = await r.session(workspace="org_a", user_id="u_1")
|
|
41
|
+
s2 = await r.session(workspace="org_b", user_id="u_2")
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Stream every token as it arrives
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
async for event in session.stream("Review this PR"):
|
|
48
|
+
if event.type == "message.part.delta" and event.text:
|
|
49
|
+
print(event.text, end="", flush=True)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Inspect
|
|
53
|
+
|
|
54
|
+
Servers started from Python are visible and manageable from the terminal:
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
opencode-runtime ps
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
ID PID PORT STATUS UPTIME WORKSPACE USER PROJECT
|
|
62
|
+
───────────────────────────────────────────────────────────────────────────────────
|
|
63
|
+
39dce5beb4debfaa 12051 58409 ● running 5m org_a u_1 ~/Developer/myproject
|
|
64
|
+
81fa29acb3e9210f 12088 58411 ● running 3m org_b u_2 ~/Developer/myproject
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
opencode-runtime health 39dce5beb4debfaa
|
|
69
|
+
opencode-runtime inspect 39dce5beb4debfaa
|
|
70
|
+
opencode-runtime stop-all
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Guides
|
|
74
|
+
|
|
75
|
+
- [OpenCode config](docs/opencode-config.md) — models, permissions, agents, skills
|
|
76
|
+
- [Sessions](docs/sessions.md) — continuation, resume across restarts
|
|
77
|
+
- [Users & workspaces](docs/users-and-workspaces.md) — multi-tenant isolation
|
|
78
|
+
- [Streaming](docs/streaming.md) — event types, tool calls, permissions, cost
|
|
79
|
+
- [CLI](docs/cli.md) — manage your fleet from the terminal
|
|
80
|
+
- [HTTP client](docs/http-client.md) — direct server access
|
|
81
|
+
|
|
82
|
+
## Requirements
|
|
83
|
+
|
|
84
|
+
- Python 3.10+
|
|
85
|
+
- `opencode` 1.0+ on PATH
|
|
86
|
+
|
|
87
|
+
## Contributing
|
|
88
|
+
|
|
89
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
Apache 2.0
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# CLI
|
|
2
|
+
|
|
3
|
+
Every server your Python app starts is also visible and controllable from the terminal — same registry, no separate daemon. The CLI is how you operate your OpenCode fleet in production: inspect running servers, health-check tenants, drain a workspace, or kill everything before a deploy.
|
|
4
|
+
|
|
5
|
+
Think of it as `kubectl` for your OpenCode fleet.
|
|
6
|
+
|
|
7
|
+
## See everything that's running
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
opencode-runtime ps
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
ID PID PORT STATUS UPTIME WORKSPACE USER PROJECT
|
|
15
|
+
───────────────────────────────────────────────────────────────────────────────────
|
|
16
|
+
39dce5beb4debfaa 12051 58409 ● running 5m org_a u_1 ~/Developer/myproject
|
|
17
|
+
81fa29acb3e9210f 12088 58411 ● running 3m org_b u_2 ~/Developer/myproject
|
|
18
|
+
c3f2a1d9e8b74f05 13204 58413 ● running 1h org_c u_3 ~/Developer/myproject
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Every server your Python app has started — across all users and workspaces — is visible here. PID, port, uptime, which tenant, which user, which project. No guessing, no digging through logs.
|
|
22
|
+
|
|
23
|
+
## Health check a specific server
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
opencode-runtime health 39dce5beb4debfaa
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Hit the health endpoint of any server by ID. Use this in a monitoring script, a deployment check, or just to verify a server is responsive after a spike in traffic.
|
|
30
|
+
|
|
31
|
+
Pipe it into your alerting:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
opencode-runtime health 39dce5beb4debfaa || pagerduty-alert "opencode server down"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Inspect a server in detail
|
|
38
|
+
|
|
39
|
+
`health` tells you if a server is up; `inspect` tells you everything else — uptime, idle time, runtime version, log file location:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
opencode-runtime inspect 39dce5beb4debfaa
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
ID 39dce5beb4debfaa
|
|
47
|
+
Status ● running
|
|
48
|
+
Project ~/Developer/myproject
|
|
49
|
+
Workspace org_a
|
|
50
|
+
User u_1
|
|
51
|
+
PID 12051
|
|
52
|
+
Port 58409
|
|
53
|
+
Uptime 5m 12s
|
|
54
|
+
Last used 30s ago
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Start a server manually
|
|
58
|
+
|
|
59
|
+
Spin up a server outside of Python — useful for pre-warming tenants before they hit your API, or for running one-off tasks from the terminal:
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
opencode-runtime serve --workspace acme --user-id alice
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The server registers in the same shared registry your Python app uses. When your app later calls `r.session(workspace="acme", user_id="alice")`, it reuses the already-running server instead of starting a new one.
|
|
66
|
+
|
|
67
|
+
## Drain a tenant
|
|
68
|
+
|
|
69
|
+
A tenant is causing runaway resource usage. Stop their server without touching any other tenant:
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
# find the server
|
|
73
|
+
opencode-runtime ps | grep acme
|
|
74
|
+
|
|
75
|
+
# stop just that one
|
|
76
|
+
opencode-runtime stop 39dce5beb4debfaa
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Next request from that tenant will cold-start a fresh server — clean slate, no lingering state.
|
|
80
|
+
|
|
81
|
+
## Emergency stop
|
|
82
|
+
|
|
83
|
+
Deploy gone wrong. A bug is spawning servers faster than expected. Kill everything instantly:
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
opencode-runtime stop-all
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
All servers stopped, all ports freed. Your app will restart them on next use.
|
|
90
|
+
|
|
91
|
+
## Use as a sidecar in production
|
|
92
|
+
|
|
93
|
+
The CLI reads from the same registry as the Python library — no separate daemon, no IPC. Run it alongside your application in any environment:
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
# in one terminal — your app
|
|
97
|
+
python app.py
|
|
98
|
+
|
|
99
|
+
# in another — live monitoring
|
|
100
|
+
watch -n 5 opencode-runtime ps
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Or wire it into your infra tooling. The `ps` output is structured enough to parse, `health` exits non-zero on failure, and `stop`/`stop-all` are safe to call from scripts or runbooks.
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
# runbook: drain all servers before a deploy
|
|
107
|
+
opencode-runtime stop-all
|
|
108
|
+
deploy.sh
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Command reference
|
|
112
|
+
|
|
113
|
+
| Command | Description |
|
|
114
|
+
|---|---|
|
|
115
|
+
| `opencode-runtime ps` | List all running servers with ID, PID, port, status, uptime, workspace, user, project |
|
|
116
|
+
| `opencode-runtime serve` | Start a background server. Accepts `--workspace`, `--user-id` |
|
|
117
|
+
| `opencode-runtime health <id>` | Health check a server by ID. Exits non-zero if unhealthy |
|
|
118
|
+
| `opencode-runtime inspect <id>` | Show detailed info for a server: uptime, idle time, runtime version, log file |
|
|
119
|
+
| `opencode-runtime stop <id>` | Stop a specific server by ID |
|
|
120
|
+
| `opencode-runtime stop-all` | Stop all running servers |
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Raw client
|
|
2
|
+
|
|
3
|
+
`session.raw_client` gives you direct access to every OpenCode server endpoint — anything not covered by `ask()` and `stream()`. It exposes two methods: `get(path)` and `post(path, body)`, both return parsed JSON.
|
|
4
|
+
|
|
5
|
+
The examples below follow a single story: a code review backend that receives a PR, runs an agent review, tracks progress, stores the result, and handles rollback.
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
from opencode_runtime import OpenCodeRuntime
|
|
9
|
+
|
|
10
|
+
async with OpenCodeRuntime(runtime_dir=".opencode-runtime") as r:
|
|
11
|
+
session = await r.session(workspace="acme", user_id="alice")
|
|
12
|
+
client = session.raw_client
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## 1. Pick the right agent
|
|
16
|
+
|
|
17
|
+
Before running the review, confirm which agents are available and pick the one best suited for the task:
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
agents = await client.get("/agent")
|
|
21
|
+
review_agents = [a for a in agents if "review" in a["name"].lower()]
|
|
22
|
+
|
|
23
|
+
# fall back to "plan" (read-only, no edits) if no dedicated reviewer
|
|
24
|
+
agent = review_agents[0]["name"] if review_agents else "plan"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 2. Run the review
|
|
28
|
+
|
|
29
|
+
Use the chosen agent for this specific call:
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
response = await session.ask(
|
|
33
|
+
"Review this PR for security issues, logic bugs, and test coverage gaps",
|
|
34
|
+
agent=agent,
|
|
35
|
+
)
|
|
36
|
+
print(response.text)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 3. Track todos
|
|
40
|
+
|
|
41
|
+
For complex PRs, the agent creates a todo list as it works through the review. Poll it to show progress in your UI:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
todos = await client.get(f"/session/{session.session_id}/todo")
|
|
45
|
+
for todo in todos:
|
|
46
|
+
icon = {"completed": "✓", "in_progress": "⟳", "pending": "○", "cancelled": "✗"}.get(todo["status"], "?")
|
|
47
|
+
print(f" {icon} {todo['content']}")
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 4. Store the full conversation
|
|
51
|
+
|
|
52
|
+
After the review, fetch the full message history to persist in your database — useful for audit trails, displaying review threads in your UI, or feeding into downstream pipelines:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
messages = await client.get(f"/session/{session.session_id}/message")
|
|
56
|
+
for entry in messages:
|
|
57
|
+
info = entry["info"]
|
|
58
|
+
role = info["role"]
|
|
59
|
+
cost = info.get("cost", 0) if role == "assistant" else 0
|
|
60
|
+
text = next((p["text"] for p in entry["parts"] if p["type"] == "text"), "")
|
|
61
|
+
db.insert(session_id=session.session_id, role=role, text=text, cost=cost)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## 5. Revert if the agent made unwanted changes
|
|
65
|
+
|
|
66
|
+
If the agent edited files as part of the review and you want to roll them back:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
messages = await client.get(f"/session/{session.session_id}/message")
|
|
70
|
+
|
|
71
|
+
# revert to just before the last assistant turn
|
|
72
|
+
message_id = messages[-2]["info"]["id"]
|
|
73
|
+
await client.post(
|
|
74
|
+
f"/session/{session.session_id}/revert",
|
|
75
|
+
{"messageID": message_id},
|
|
76
|
+
)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## 6. Report cost
|
|
80
|
+
|
|
81
|
+
At the end of the review, read the final cost from the last assistant message for billing or quota tracking:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
messages = await client.get(f"/session/{session.session_id}/message")
|
|
85
|
+
assistant_messages = [e for e in messages if e["info"]["role"] == "assistant"]
|
|
86
|
+
|
|
87
|
+
if assistant_messages:
|
|
88
|
+
last = assistant_messages[-1]["info"]
|
|
89
|
+
cost = last.get("cost", 0)
|
|
90
|
+
tokens = last.get("tokens", {})
|
|
91
|
+
print(f"Review cost: ${cost:.5f} ({tokens.get('input', 0)} in / {tokens.get('output', 0)} out)")
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Full endpoint reference
|
|
95
|
+
|
|
96
|
+
See the [OpenCode server docs](https://opencode.ai/docs/server) for all endpoints and the full type definitions in [types.gen.ts](https://github.com/anomalyco/opencode/blob/dev/packages/sdk/js/src/gen/types.gen.ts).
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Configuration & materials
|
|
2
|
+
|
|
3
|
+
## Config
|
|
4
|
+
|
|
5
|
+
Pass any valid `opencode.json` keys as a dict. opencode-runtime writes this as the server's config before starting. **`runtime_dir` must be set for config to take effect** — without it no config file is written.
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
from opencode_runtime import OpenCodeRuntime
|
|
9
|
+
|
|
10
|
+
async with OpenCodeRuntime(
|
|
11
|
+
runtime_dir=".opencode-runtime",
|
|
12
|
+
config={
|
|
13
|
+
"model": "anthropic/claude-sonnet-4-5",
|
|
14
|
+
"permission": {"bash": "deny"},
|
|
15
|
+
},
|
|
16
|
+
) as r:
|
|
17
|
+
session = await r.session()
|
|
18
|
+
response = await session.ask("Analyse the architecture")
|
|
19
|
+
print(response.text)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
See the [OpenCode config reference](https://opencode.ai/docs/config/) for all available keys.
|
|
23
|
+
|
|
24
|
+
## Materials
|
|
25
|
+
|
|
26
|
+
Pass a directory of OpenCode-native files and they are copied into the server before it starts. This is how you bring custom instructions, agents, and skills to a managed server.
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from opencode_runtime import OpenCodeRuntime
|
|
30
|
+
|
|
31
|
+
async with OpenCodeRuntime(
|
|
32
|
+
runtime_dir=".opencode-runtime",
|
|
33
|
+
materials="./opencode-materials",
|
|
34
|
+
) as r:
|
|
35
|
+
session = await r.session()
|
|
36
|
+
response = await session.ask("Follow the instructions in AGENTS.md")
|
|
37
|
+
print(response.text)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
What can go in a materials directory:
|
|
41
|
+
|
|
42
|
+
| File / path | Purpose | Docs |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `opencode.json` | Server config (model, permissions, MCP servers, …) | [Config](https://opencode.ai/docs/config/) |
|
|
45
|
+
| `AGENTS.md` | Rules and instructions included in every session | [Rules](https://opencode.ai/docs/rules/) |
|
|
46
|
+
| `.opencode/agents/` | Custom agent definitions (markdown format) | [Agents](https://opencode.ai/docs/agents/#markdown) |
|
|
47
|
+
| `.opencode/skills/` | Reusable skill definitions | [Agent Skills](https://opencode.ai/docs/skills/) |
|
|
48
|
+
|
|
49
|
+
## project_dir
|
|
50
|
+
|
|
51
|
+
The directory OpenCode runs against — the working directory of the subprocess. Defaults to `.` (wherever your Python process is running).
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
async with OpenCodeRuntime(project_dir="/path/to/repo") as r:
|
|
55
|
+
session = await r.session()
|
|
56
|
+
response = await session.ask("What does this project do?")
|
|
57
|
+
print(response.text)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**When to use:** when your Python backend runs from a different location than the repo you want OpenCode to work on — for example, a service that checks out customer repos to a temp directory and points OpenCode at each one.
|
|
61
|
+
|
|
62
|
+
## runtime_dir
|
|
63
|
+
|
|
64
|
+
When set, each server gets its own isolated `HOME` and config file (`opencode.json`) under `runtime_dir/servers/<key>/`. Without it, OpenCode uses your real user environment and any `config` or `materials` passed to the runtime are ignored.
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
async with OpenCodeRuntime(runtime_dir=".opencode-runtime") as r:
|
|
68
|
+
session = await r.session()
|
|
69
|
+
response = await session.ask("What does this project do?")
|
|
70
|
+
print(response.text)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**When to use:** any multi-user or production setup. Without `runtime_dir`, all servers share your real home directory — meaning shared OpenCode config, shared history, and API keys from your personal environment. Setting it gives every server a clean, isolated slate.
|
|
74
|
+
|
|
75
|
+
## Session-level overrides
|
|
76
|
+
|
|
77
|
+
`config`, `materials`, and `env` can all be overridden per session. Session config is shallow-merged with runtime config; session materials replace runtime materials; session env is merged with runtime env. Session keys win in all cases.
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
async with OpenCodeRuntime(
|
|
81
|
+
config={"model": "anthropic/claude-sonnet-4-5"},
|
|
82
|
+
materials="./base-materials",
|
|
83
|
+
env={"API_KEY": "default"},
|
|
84
|
+
) as r:
|
|
85
|
+
session = await r.session(
|
|
86
|
+
config={"model": "anthropic/claude-opus-4-5"},
|
|
87
|
+
materials="./org-a-materials",
|
|
88
|
+
env={"API_KEY": "org-a-key"},
|
|
89
|
+
)
|
|
90
|
+
response = await session.ask("Analyse the architecture")
|
|
91
|
+
print(response.text)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Note: `config` and `materials` affect server identity — different values produce a separate server process. `env` does not; if the server is already running, a different `env` passed to `session()` has no effect.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Sessions
|
|
2
|
+
|
|
3
|
+
## Single ask
|
|
4
|
+
|
|
5
|
+
The simplest usage — send a message, get a response:
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
from opencode_runtime import OpenCodeRuntime
|
|
9
|
+
|
|
10
|
+
async with OpenCodeRuntime() as r:
|
|
11
|
+
session = await r.session()
|
|
12
|
+
response = await session.ask("What does this project do?")
|
|
13
|
+
print(response.text)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`response.text` is the full assistant reply. `response.raw` contains all raw events if you need them (tool calls, thinking, status updates).
|
|
17
|
+
|
|
18
|
+
## Multi-turn conversation
|
|
19
|
+
|
|
20
|
+
Multiple `ask()` calls on the same session continue the same conversation — OpenCode keeps the full history server-side:
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
async with OpenCodeRuntime() as r:
|
|
24
|
+
session = await r.session()
|
|
25
|
+
await session.ask("Explain this repo's architecture")
|
|
26
|
+
await session.ask("Which file handles authentication?") # has full context
|
|
27
|
+
response = await session.ask("What would you change about it?")
|
|
28
|
+
print(response.text)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
To start a fresh independent conversation, get a new session:
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
async with OpenCodeRuntime() as r:
|
|
35
|
+
session_a = await r.session() # conversation A
|
|
36
|
+
session_b = await r.session() # conversation B — isolated, no shared history
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Resume across restarts
|
|
40
|
+
|
|
41
|
+
`session_id` is set after the first `ask()` or `stream()` call. Persist it to resume the conversation later:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
# First run
|
|
45
|
+
async with OpenCodeRuntime() as r:
|
|
46
|
+
session = await r.session()
|
|
47
|
+
await session.ask("Explain this repo")
|
|
48
|
+
saved_id = session.session_id # store in your DB
|
|
49
|
+
|
|
50
|
+
# Later — picks up the same conversation
|
|
51
|
+
async with OpenCodeRuntime() as r:
|
|
52
|
+
session = await r.session(session_id=saved_id)
|
|
53
|
+
response = await session.ask("Where did we get to?")
|
|
54
|
+
print(response.text)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Per-message overrides
|
|
58
|
+
|
|
59
|
+
`ask()` and `stream()` accept per-message overrides for model, agent, tools, and system prompt. These apply to that message only — they don't change the session's config:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
async with OpenCodeRuntime() as r:
|
|
63
|
+
session = await r.session()
|
|
64
|
+
|
|
65
|
+
# use a specific agent for one task
|
|
66
|
+
review = await session.ask(
|
|
67
|
+
"Review this PR for security issues",
|
|
68
|
+
agent="security-auditor",
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# override model for a single expensive call
|
|
72
|
+
deep = await session.ask(
|
|
73
|
+
"Redesign the auth module",
|
|
74
|
+
model="anthropic/claude-opus-4-5",
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# disable bash for one call
|
|
78
|
+
safe = await session.ask(
|
|
79
|
+
"Summarise the test coverage",
|
|
80
|
+
tools={"bash": False},
|
|
81
|
+
)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Abort
|
|
85
|
+
|
|
86
|
+
Cancel a running session mid-flight:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
import asyncio
|
|
90
|
+
from opencode_runtime import OpenCodeRuntime
|
|
91
|
+
|
|
92
|
+
async with OpenCodeRuntime() as r:
|
|
93
|
+
session = await r.session()
|
|
94
|
+
|
|
95
|
+
async def run():
|
|
96
|
+
await session.ask("Refactor the entire codebase")
|
|
97
|
+
|
|
98
|
+
task = asyncio.create_task(run())
|
|
99
|
+
await asyncio.sleep(5)
|
|
100
|
+
await session.abort() # cancels the in-progress session server-side
|
|
101
|
+
task.cancel()
|
|
102
|
+
```
|