opencode-python-sdk 0.1.1__tar.gz → 0.1.3__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 (75) hide show
  1. opencode_python_sdk-0.1.3/CHANGELOG.md +18 -0
  2. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/PKG-INFO +100 -1
  3. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/README.md +97 -0
  4. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/pyproject.toml +6 -0
  5. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/__init__.py +52 -0
  6. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/_client.py +23 -0
  7. opencode_python_sdk-0.1.3/src/opencode/_events.py +729 -0
  8. opencode_python_sdk-0.1.3/src/opencode/_pty.py +586 -0
  9. opencode_python_sdk-0.1.3/src/opencode/_rpc.py +482 -0
  10. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/_streaming.py +22 -1
  11. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/experimental.py +3 -2
  12. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/pty.py +3 -2
  13. opencode_python_sdk-0.1.3/tests/test_events.py +396 -0
  14. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/tests/test_live.py +65 -1
  15. opencode_python_sdk-0.1.3/tests/test_pty.py +236 -0
  16. opencode_python_sdk-0.1.3/tests/test_rpc.py +154 -0
  17. opencode_python_sdk-0.1.1/CHANGELOG.md +0 -9
  18. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/.gitignore +0 -0
  19. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/LICENSE +0 -0
  20. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/api.md +0 -0
  21. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/.generated.json +0 -0
  22. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/_base_client.py +0 -0
  23. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/_constants.py +0 -0
  24. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/_errors.py +0 -0
  25. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/_exceptions.py +0 -0
  26. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/_models.py +0 -0
  27. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/_pagination.py +0 -0
  28. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/_qs.py +0 -0
  29. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/_resource.py +0 -0
  30. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/_server.py +0 -0
  31. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/_types.py +0 -0
  32. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/_unions.py +0 -0
  33. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/_version.py +0 -0
  34. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/py.typed +0 -0
  35. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/__init__.py +0 -0
  36. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/agent.py +0 -0
  37. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/command.py +0 -0
  38. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/config.py +0 -0
  39. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/credential.py +0 -0
  40. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/debug.py +0 -0
  41. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/event.py +0 -0
  42. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/file.py +0 -0
  43. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/form.py +0 -0
  44. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/generate.py +0 -0
  45. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/integration.py +0 -0
  46. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/location.py +0 -0
  47. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/mcp.py +0 -0
  48. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/message.py +0 -0
  49. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/migration.py +0 -0
  50. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/model.py +0 -0
  51. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/permission.py +0 -0
  52. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/plugin.py +0 -0
  53. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/project.py +0 -0
  54. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/provider.py +0 -0
  55. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/reference.py +0 -0
  56. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/rpc.py +0 -0
  57. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/server.py +0 -0
  58. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/session.py +0 -0
  59. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/shell.py +0 -0
  60. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/skill.py +0 -0
  61. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/vcs.py +0 -0
  62. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/voice.py +0 -0
  63. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/websearch.py +0 -0
  64. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/resources/worktree.py +0 -0
  65. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/types/__init__.py +0 -0
  66. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/src/opencode/types/_generated.py +0 -0
  67. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/tests/__init__.py +0 -0
  68. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/tests/conftest.py +0 -0
  69. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/tests/test_async_client.py +0 -0
  70. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/tests/test_client.py +0 -0
  71. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/tests/test_docs_coverage.py +0 -0
  72. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/tests/test_encoding.py +0 -0
  73. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/tests/test_models.py +0 -0
  74. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/tests/test_pagination.py +0 -0
  75. {opencode_python_sdk-0.1.1 → opencode_python_sdk-0.1.3}/tests/test_streaming.py +0 -0
@@ -0,0 +1,18 @@
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
+ ## Unreleased
7
+
8
+ - `client.events`: a shared event stream with one connection for all subscribers, bounded per-subscriber queues,
9
+ `server.connected` replay, `of_type` / `for_session` / `where` filters, `on_activity` keepalive hook, cancellation,
10
+ and opt-in reconnection with backoff (`EventHub(client, reconnect=True)`).
11
+ - `client.rpc(Definition)`: typed plugin RPC calls and event subscriptions declared with `RpcDefinition`,
12
+ `rpc_method` and `rpc_event`; RPC failures raise `RpcCallError` / `RpcCallInternalError`.
13
+ - `Stream.on_activity` and `Stream.interrupt()`.
14
+
15
+ ## 0.1.0
16
+
17
+ First release, generated from OpenCode 2.1.2: sync and async clients for all 142 operations, typed models and
18
+ events, typed errors, pagination, event streams, retries, and the `OpencodeServer` launcher.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: opencode-python-sdk
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: Typed Python client for the OpenCode 2.x server API
5
5
  Project-URL: Homepage, https://github.com/shrijayan/opencode-python-sdk
6
6
  Project-URL: Documentation, https://shrijayan.github.io/opencode-python-sdk/
@@ -31,6 +31,8 @@ Requires-Dist: anyio<5,>=4.4
31
31
  Requires-Dist: httpx<1,>=0.27
32
32
  Requires-Dist: pydantic<3,>=2.12
33
33
  Requires-Dist: typing-extensions>=4.12
34
+ Provides-Extra: pty
35
+ Requires-Dist: websockets<17,>=14; extra == 'pty'
34
36
  Description-Content-Type: text/markdown
35
37
 
36
38
  # opencode-python-sdk
@@ -204,6 +206,103 @@ Events are typed (`SessionCreatedEvent`, `SessionTextDeltaEvent`, and so on). Ev
204
206
  not know arrive as `UnknownEvent` with all their fields kept. Streams apply only the connect timeout by default, since
205
207
  they stay open between events.
206
208
 
209
+ ### Shared event stream
210
+
211
+ `client.events` shares one connection among every subscriber, like `client.event` in the TypeScript client. The
212
+ connection opens with the first subscriber and closes when the last one leaves. Each subscriber has its own queue of
213
+ up to 4096 events; one that falls further behind fails with `EventOverflowError` without affecting the others. The
214
+ latest `server.connected` event is replayed to subscribers that join later.
215
+
216
+ ```python
217
+ from opencode.types import SessionIdleEvent
218
+
219
+ with client.events.of_type(SessionIdleEvent).for_session(session.id) as idle:
220
+ client.session.prompt(session.id, text="Hello")
221
+ event = idle.get(timeout=300) # typed as SessionIdleEvent
222
+ ```
223
+
224
+ - Filters chain: `of_type(*classes)` narrows the yielded type, `for_session(id)` keeps one session's events, and
225
+ `where(predicate)` takes any test. Entering the `with` block joins the stream, so nothing published after that is
226
+ missed.
227
+ - `subscribe(on_activity=callback)` calls `callback` for every chunk received, keepalive comments included, as a
228
+ liveness signal.
229
+ - `close()` (or leaving the `with` block) ends the subscription, also from another thread. In async code, cancel a wait
230
+ with `anyio.fail_after`, `asyncio.timeout` or a trio cancel scope; the subscription stays usable.
231
+ - Reconnection is opt-in: `EventHub(client, reconnect=True)` (or `AsyncEventHub`, or a `Reconnect(...)` policy) reopens
232
+ the connection with exponential backoff after connection errors, 5xx and 429. Subscribers see `server.connected`
233
+ once. **The stream is volatile**: events published while disconnected are lost, so re-read state through the API
234
+ after a reconnect when it matters.
235
+
236
+ The async hub works the same way under asyncio and trio:
237
+
238
+ ```python
239
+ async with client.events.for_session(session.id) as events:
240
+ async for event in events:
241
+ print(event.type)
242
+ ```
243
+
244
+ `client.event.subscribe()` still opens a dedicated connection.
245
+
246
+ ### Plugin RPC
247
+
248
+ Declare a plugin RPC once as a class, then call it with typed inputs and outputs:
249
+
250
+ ```python
251
+ from pydantic import BaseModel
252
+ from opencode import RpcCallError, RpcDefinition, rpc_event, rpc_method
253
+
254
+
255
+ class AddInput(BaseModel):
256
+ amount: int
257
+
258
+
259
+ class Total(BaseModel):
260
+ total: int
261
+
262
+
263
+ class Counter(RpcDefinition, id="counter"):
264
+ add = rpc_method(AddInput, Total)
265
+ reset = rpc_method(None, Total)
266
+ changed = rpc_event(Total)
267
+
268
+
269
+ counter = client.rpc(Counter)
270
+ try:
271
+ print(counter.call(Counter.add, AddInput(amount=2)).total)
272
+ except RpcCallError as error:
273
+ print(error.type, error.message, error.data)
274
+
275
+ with counter.subscribe(Counter.changed) as changes: # only rpc.counter.changed, with validated data
276
+ for change in changes:
277
+ print(change.data.total)
278
+ ```
279
+
280
+ Any type pydantic validates works for inputs, outputs and event data. `rpc_method(..., errors={"not_found": Model})`
281
+ validates the `data` of declared errors. Failures raise `RpcCallError`; crashes and outputs that do not match the
282
+ declared type raise `RpcCallInternalError`. `counter.events()` yields every raw event of the RPC. The untyped
283
+ `client.rpc.call(rpc_id, method, input=...)` is still available.
284
+
285
+ ## Terminals
286
+
287
+ `client.pty.attach()` opens an interactive connection to a PTY over a WebSocket. It needs the `pty` extra:
288
+ `pip install "opencode-python-sdk[pty]"`.
289
+
290
+ ```python
291
+ pty = client.pty.create(command="/bin/sh").data
292
+ with client.pty.attach(pty.id) as term:
293
+ term.resize(120, 40)
294
+ term.send("echo hi\n")
295
+ for chunk in term: # output bytes, until the shell exits
296
+ print(chunk.decode(errors="replace"), end="")
297
+ ```
298
+
299
+ `recv(timeout=...)` reads one chunk and returns `None` once the terminal has ended. `term.cursor` holds the output
300
+ position to pass as `cursor=` when reattaching; `cursor=-1` skips the replay of earlier output. Persistent terminals
301
+ work the same way through `client.experimental.persistent_pty.attach(pty_id, role=..., takeover=...)`. The async
302
+ client returns `AsyncPtyConnection` (asyncio only): `async with await client.pty.attach(...) as term`. A connection
303
+ the server refuses or closes abnormally raises `PtyConnectionError`. See
304
+ [`examples/terminal.py`](examples/terminal.py) for a full TTY bridge.
305
+
207
306
  ## Errors
208
307
 
209
308
  Every error derives from `opencode.OpencodeError`. Errors the server declares are raised as their own class, with a
@@ -169,6 +169,103 @@ Events are typed (`SessionCreatedEvent`, `SessionTextDeltaEvent`, and so on). Ev
169
169
  not know arrive as `UnknownEvent` with all their fields kept. Streams apply only the connect timeout by default, since
170
170
  they stay open between events.
171
171
 
172
+ ### Shared event stream
173
+
174
+ `client.events` shares one connection among every subscriber, like `client.event` in the TypeScript client. The
175
+ connection opens with the first subscriber and closes when the last one leaves. Each subscriber has its own queue of
176
+ up to 4096 events; one that falls further behind fails with `EventOverflowError` without affecting the others. The
177
+ latest `server.connected` event is replayed to subscribers that join later.
178
+
179
+ ```python
180
+ from opencode.types import SessionIdleEvent
181
+
182
+ with client.events.of_type(SessionIdleEvent).for_session(session.id) as idle:
183
+ client.session.prompt(session.id, text="Hello")
184
+ event = idle.get(timeout=300) # typed as SessionIdleEvent
185
+ ```
186
+
187
+ - Filters chain: `of_type(*classes)` narrows the yielded type, `for_session(id)` keeps one session's events, and
188
+ `where(predicate)` takes any test. Entering the `with` block joins the stream, so nothing published after that is
189
+ missed.
190
+ - `subscribe(on_activity=callback)` calls `callback` for every chunk received, keepalive comments included, as a
191
+ liveness signal.
192
+ - `close()` (or leaving the `with` block) ends the subscription, also from another thread. In async code, cancel a wait
193
+ with `anyio.fail_after`, `asyncio.timeout` or a trio cancel scope; the subscription stays usable.
194
+ - Reconnection is opt-in: `EventHub(client, reconnect=True)` (or `AsyncEventHub`, or a `Reconnect(...)` policy) reopens
195
+ the connection with exponential backoff after connection errors, 5xx and 429. Subscribers see `server.connected`
196
+ once. **The stream is volatile**: events published while disconnected are lost, so re-read state through the API
197
+ after a reconnect when it matters.
198
+
199
+ The async hub works the same way under asyncio and trio:
200
+
201
+ ```python
202
+ async with client.events.for_session(session.id) as events:
203
+ async for event in events:
204
+ print(event.type)
205
+ ```
206
+
207
+ `client.event.subscribe()` still opens a dedicated connection.
208
+
209
+ ### Plugin RPC
210
+
211
+ Declare a plugin RPC once as a class, then call it with typed inputs and outputs:
212
+
213
+ ```python
214
+ from pydantic import BaseModel
215
+ from opencode import RpcCallError, RpcDefinition, rpc_event, rpc_method
216
+
217
+
218
+ class AddInput(BaseModel):
219
+ amount: int
220
+
221
+
222
+ class Total(BaseModel):
223
+ total: int
224
+
225
+
226
+ class Counter(RpcDefinition, id="counter"):
227
+ add = rpc_method(AddInput, Total)
228
+ reset = rpc_method(None, Total)
229
+ changed = rpc_event(Total)
230
+
231
+
232
+ counter = client.rpc(Counter)
233
+ try:
234
+ print(counter.call(Counter.add, AddInput(amount=2)).total)
235
+ except RpcCallError as error:
236
+ print(error.type, error.message, error.data)
237
+
238
+ with counter.subscribe(Counter.changed) as changes: # only rpc.counter.changed, with validated data
239
+ for change in changes:
240
+ print(change.data.total)
241
+ ```
242
+
243
+ Any type pydantic validates works for inputs, outputs and event data. `rpc_method(..., errors={"not_found": Model})`
244
+ validates the `data` of declared errors. Failures raise `RpcCallError`; crashes and outputs that do not match the
245
+ declared type raise `RpcCallInternalError`. `counter.events()` yields every raw event of the RPC. The untyped
246
+ `client.rpc.call(rpc_id, method, input=...)` is still available.
247
+
248
+ ## Terminals
249
+
250
+ `client.pty.attach()` opens an interactive connection to a PTY over a WebSocket. It needs the `pty` extra:
251
+ `pip install "opencode-python-sdk[pty]"`.
252
+
253
+ ```python
254
+ pty = client.pty.create(command="/bin/sh").data
255
+ with client.pty.attach(pty.id) as term:
256
+ term.resize(120, 40)
257
+ term.send("echo hi\n")
258
+ for chunk in term: # output bytes, until the shell exits
259
+ print(chunk.decode(errors="replace"), end="")
260
+ ```
261
+
262
+ `recv(timeout=...)` reads one chunk and returns `None` once the terminal has ended. `term.cursor` holds the output
263
+ position to pass as `cursor=` when reattaching; `cursor=-1` skips the replay of earlier output. Persistent terminals
264
+ work the same way through `client.experimental.persistent_pty.attach(pty_id, role=..., takeover=...)`. The async
265
+ client returns `AsyncPtyConnection` (asyncio only): `async with await client.pty.attach(...) as term`. A connection
266
+ the server refuses or closes abnormally raises `PtyConnectionError`. See
267
+ [`examples/terminal.py`](examples/terminal.py) for a full TTY bridge.
268
+
172
269
  ## Errors
173
270
 
174
271
  Every error derives from `opencode.OpencodeError`. Errors the server declares are raised as their own class, with a
@@ -39,6 +39,11 @@ Repository = "https://github.com/shrijayan/opencode-python-sdk"
39
39
  Issues = "https://github.com/shrijayan/opencode-python-sdk/issues"
40
40
  Changelog = "https://github.com/shrijayan/opencode-python-sdk/blob/main/CHANGELOG.md"
41
41
 
42
+ [project.optional-dependencies]
43
+ pty = [
44
+ "websockets>=14,<17",
45
+ ]
46
+
42
47
  [dependency-groups]
43
48
  dev = [
44
49
  "mypy>=1.18",
@@ -48,6 +53,7 @@ dev = [
48
53
  "respx>=0.22",
49
54
  "ruff>=0.14",
50
55
  "trio>=0.31",
56
+ "websockets>=14",
51
57
  ]
52
58
  docs = [
53
59
  "mkdocs-material[imaging]>=9.6",
@@ -75,11 +75,39 @@ from ._exceptions import (
75
75
  )
76
76
  from ._models import BaseModel
77
77
  from ._pagination import AsyncCursorPage, AsyncPaginator, SyncCursorPage
78
+ from ._pty import AsyncPtyConnection, PtyConnection, PtyConnectionError
78
79
  from ._server import AsyncOpencodeServer, OpencodeServer, ServerStartError
79
80
  from ._streaming import AsyncStream, Stream
80
81
  from ._types import NOT_GIVEN, NotGiven
81
82
  from ._version import __version__
82
83
 
84
+ # isort: split
85
+ # Hand-written helpers, kept apart from the generated error-import block above.
86
+ from ._events import (
87
+ SUBSCRIBER_CAPACITY,
88
+ AsyncEventHub,
89
+ AsyncSubscription,
90
+ EventHub,
91
+ EventOverflowError,
92
+ Reconnect,
93
+ Subscription,
94
+ session_id_of,
95
+ )
96
+ from ._rpc import (
97
+ AsyncRpcClient,
98
+ AsyncRpcEvents,
99
+ RpcCallError,
100
+ RpcCallInternalError,
101
+ RpcClient,
102
+ RpcDefinition,
103
+ RpcEventDefinition,
104
+ RpcEventPayload,
105
+ RpcEvents,
106
+ RpcMethod,
107
+ rpc_event,
108
+ rpc_method,
109
+ )
110
+
83
111
  # <generated:api-version>
84
112
  API_VERSION = "2.1.2"
85
113
  """The OpenCode server version this client was generated from."""
@@ -97,6 +125,7 @@ __all__ = [
97
125
  "AsyncOpencode",
98
126
  "AsyncOpencodeServer",
99
127
  "AsyncPaginator",
128
+ "AsyncPtyConnection",
100
129
  "AsyncStream",
101
130
  "AuthenticationError",
102
131
  "BadRequestError",
@@ -111,6 +140,8 @@ __all__ = [
111
140
  "OpencodeError",
112
141
  "OpencodeServer",
113
142
  "PermissionDeniedError",
143
+ "PtyConnection",
144
+ "PtyConnectionError",
114
145
  "RateLimitError",
115
146
  "ServerStartError",
116
147
  "ServiceUnavailableError",
@@ -119,6 +150,27 @@ __all__ = [
119
150
  "UnprocessableEntityError",
120
151
  "__version__",
121
152
  "types",
153
+ # Shared event stream and typed plugin RPC
154
+ "SUBSCRIBER_CAPACITY",
155
+ "AsyncEventHub",
156
+ "AsyncRpcClient",
157
+ "AsyncRpcEvents",
158
+ "AsyncSubscription",
159
+ "EventHub",
160
+ "EventOverflowError",
161
+ "Reconnect",
162
+ "RpcCallError",
163
+ "RpcCallInternalError",
164
+ "RpcClient",
165
+ "RpcDefinition",
166
+ "RpcEventDefinition",
167
+ "RpcEventPayload",
168
+ "RpcEvents",
169
+ "RpcMethod",
170
+ "Subscription",
171
+ "rpc_event",
172
+ "rpc_method",
173
+ "session_id_of",
122
174
  # <generated:error-exports>
123
175
  "AgentNotFoundError",
124
176
  "CommandExecutionError",
@@ -1,11 +1,14 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from functools import cached_property
3
4
  from typing import TYPE_CHECKING
4
5
 
5
6
  from typing_extensions import Self
6
7
 
7
8
  from ._base_client import AsyncAPIClient, SyncAPIClient
8
9
  from ._constants import DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT, DEFAULT_USERNAME
10
+ from ._events import AsyncEventHub, EventHub
11
+ from ._rpc import AsyncTypedRpcResource, TypedRpcResource
9
12
  from ._types import NOT_GIVEN, NotGiven, Timeout
10
13
  from .resources import AsyncResources, SyncResources
11
14
 
@@ -70,6 +73,16 @@ class Opencode(SyncAPIClient, SyncResources):
70
73
  http_client=http_client,
71
74
  )
72
75
 
76
+ @cached_property
77
+ def events(self) -> EventHub:
78
+ """The shared event stream: one connection for every subscriber. See :class:`opencode.EventHub`."""
79
+ return EventHub(self)
80
+
81
+ @cached_property
82
+ def rpc(self) -> TypedRpcResource:
83
+ """Plugin RPC calls: ``client.rpc.call(...)``, or ``client.rpc(Definition)`` for typed access."""
84
+ return TypedRpcResource(self)
85
+
73
86
  def with_options(
74
87
  self,
75
88
  *,
@@ -138,6 +151,16 @@ class AsyncOpencode(AsyncAPIClient, AsyncResources):
138
151
  http_client=http_client,
139
152
  )
140
153
 
154
+ @cached_property
155
+ def events(self) -> AsyncEventHub:
156
+ """The shared event stream: one connection for every subscriber. See :class:`opencode.AsyncEventHub`."""
157
+ return AsyncEventHub(self)
158
+
159
+ @cached_property
160
+ def rpc(self) -> AsyncTypedRpcResource:
161
+ """Plugin RPC calls: ``client.rpc.call(...)``, or ``client.rpc(Definition)`` for typed access."""
162
+ return AsyncTypedRpcResource(self)
163
+
141
164
  def with_options(
142
165
  self,
143
166
  *,