opencode-python-sdk 0.1.1__py3-none-any.whl → 0.1.3__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.
@@ -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
@@ -1,16 +1,19 @@
1
1
  opencode/.generated.json,sha256=QNCFazAYhcwsGf0l4A2EeSpm2HOM7TRBE648Zyoeq_s,1383
2
- opencode/__init__.py,sha256=UGraxBEfh90WBSAQ7ccwxJsXE5AxYDDTyKlcXhqzykc,4106
2
+ opencode/__init__.py,sha256=-jyJAzJw0GVbe4ZgtdAIiblDo2Xl5EZWk_22hOqmD2Y,5243
3
3
  opencode/_base_client.py,sha256=3BwhIXL-cFGoRUuwDzTeFH21O7pihUR5CryOlS1Wylo,20108
4
- opencode/_client.py,sha256=ip-UhWyHTViMnqgX3jhUmCkscahFtXnIWM2Jd2zn61I,6723
4
+ opencode/_client.py,sha256=3zY2lGLi_9FBpbG1UIWmklgSn86sJJZv0Wf7Z7sWNDk,7687
5
5
  opencode/_constants.py,sha256=-B_D8RHElL4mYN__WVf4bag4D12dJHbL69_EMfi7bXo,1132
6
6
  opencode/_errors.py,sha256=dWjsyGV0aDBKsvJrGNKGJn41Yue66yBBwGTT2pTV7E8,16431
7
+ opencode/_events.py,sha256=KwVvFMyPEBy-wopfbTcu7nDWwgo0zqSdWpuTmTl3dFc,30099
7
8
  opencode/_exceptions.py,sha256=kqQiaSM6zbfyiXXddwapIymW7J61ZlAa767Yko5Y9EI,8720
8
9
  opencode/_models.py,sha256=2esvD2CmslOHCbuLNkN0sGDcgMAqeoMmB7hZMPzo1uA,1988
9
10
  opencode/_pagination.py,sha256=uvB-0IwfkGqbYIYiUW_ax_oAE-ldZJSWHtjluVcZlNk,7052
11
+ opencode/_pty.py,sha256=wdqBF7K40ikzas8i203pem2iov8nbgYDC-RqH6ZX41E,23429
10
12
  opencode/_qs.py,sha256=8EWbLzd-iLComngGRyO95f_z76j4tUeBihYJdHI5NP8,3643
11
13
  opencode/_resource.py,sha256=ovuG85mWcuXNTu5m6H9FKPN2rrD5o52oRFAGBHimPyg,1263
14
+ opencode/_rpc.py,sha256=s3E_6Abfbt4nRysaK-J0RFt5xx8ZCdolRJCJad2UayI,17578
12
15
  opencode/_server.py,sha256=O-yVWU-v7HDJuVav1xXTil96BtvvbYMsHm1zLhneWKk,12849
13
- opencode/_streaming.py,sha256=OJJNoFdvx6degaVnnQNhv9augigImoYjGzEdnI3FVkk,7905
16
+ opencode/_streaming.py,sha256=TfnfZaNucjLrpfOB9m1i_sRtjYoACztTflavrhwvnDE,8861
14
17
  opencode/_types.py,sha256=Z8bYxzpZhPRtisvPUv9R5g4oBlC23iXR70fFzReTOc0,2194
15
18
  opencode/_unions.py,sha256=dW0dTmP2QK2lL6RhyK2YsjD2yNbO8Ah6pk-i5ySW3lo,1518
16
19
  opencode/_version.py,sha256=zNErHu8z_g6ScOeYcO8QAiktg036FVPaUiIIZefeoLU,259
@@ -22,7 +25,7 @@ opencode/resources/config.py,sha256=pnRi7pG8mJqO0Xp6dnh0a8V767Dy5Zvbr8wvsaBqaIc,
22
25
  opencode/resources/credential.py,sha256=6X-IGP8uN4WVVhCFoLPWnMjfQ6dW25wms1R3YIFNggA,12018
23
26
  opencode/resources/debug.py,sha256=gdKIJ1jSsD6mPaI_Qd3pmE5J5URIyTWZAppfulejf-c,5483
24
27
  opencode/resources/event.py,sha256=UntmSEmFRWjSIjWvP-OkXfc-S1cpflwHyp0E2ZYGCBk,2694
25
- opencode/resources/experimental.py,sha256=JdRajS5r4k7AjEX9Xp_Y2iGKndSKdDsJZK1nq-PM0kU,27961
28
+ opencode/resources/experimental.py,sha256=qjaMkBfI7aOwLWeQWlXmVtYwM1VIilry27kyCVcC1B0,28058
26
29
  opencode/resources/file.py,sha256=leXPuTdE44BUrpIqDZ3MAAjSgv8_aKtyDROKsDZuVSE,12943
27
30
  opencode/resources/form.py,sha256=ToPVrHbTlyUZyNrMaH3bkcfqEjvIF1UlL7CQHT2VjyE,2967
28
31
  opencode/resources/generate.py,sha256=koElRVSJtRrP6yok3hsYOJon8MMinku8IB8zCS0MImM,3755
@@ -36,7 +39,7 @@ opencode/resources/permission.py,sha256=4J-eysN90zWIAdrXbzXfR4FvPlsyIgwvXPmRMI46
36
39
  opencode/resources/plugin.py,sha256=CaZCUCdU4iAxgkZhYr6QntPmdAcJuBlip55q3q5rplw,8159
37
40
  opencode/resources/project.py,sha256=BJkgHswUoE2UZkWYQzTg1KyT8L5l5WNlJhlBdVZUMso,6114
38
41
  opencode/resources/provider.py,sha256=H7hdaJZ1ToyEGoSr13qUcAV-Rd1bd-pUSi6GJlY8B7A,6134
39
- opencode/resources/pty.py,sha256=gjXxuppy8R70PaN0x-ixv7X7s-U2XPER6Us4t4Getco,17480
42
+ opencode/resources/pty.py,sha256=ttlB4iwI-QyppsEClPrsdkvEtLFsrxOF5-B1iH-4QY0,17537
40
43
  opencode/resources/reference.py,sha256=7Y9pPZYVoIaN8k3qwYY8-h54vBUdxI6lSBgJYmowq3I,3089
41
44
  opencode/resources/rpc.py,sha256=AExEcrxgJrreOqav4k_edNcrg3Uqa9p7lilnvxVMqfo,3630
42
45
  opencode/resources/server.py,sha256=tNuofw5_BMSCGTalkUvOc5--hn43mSVBU0HANK-L8zo,6932
@@ -49,7 +52,7 @@ opencode/resources/websearch.py,sha256=xr1vaS6dwPsSZJUtVSkRtqtzZuoP9xg_cr1bBpeKo
49
52
  opencode/resources/worktree.py,sha256=mohf9rIm4PPJTMbJmieygQkaC8rc7LgF0GO4u-2D9OU,10950
50
53
  opencode/types/__init__.py,sha256=5JEAe89RPSwJw1yz0kufBxtEJgRsMpZXj7KTbzGX45A,60935
51
54
  opencode/types/_generated.py,sha256=c3zBAfVpZGJ9TX4FqrMZASmoxRntel_QzVuP85BOJwU,235015
52
- opencode_python_sdk-0.1.1.dist-info/METADATA,sha256=i_ELBqCkCafo6tyKdVzcETFFjRDhmDhZi_X8jPJDsQ0,11502
53
- opencode_python_sdk-0.1.1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
54
- opencode_python_sdk-0.1.1.dist-info/licenses/LICENSE,sha256=SbiOG1an_-gZ0gwGLS0tsbUnMHA7tYX22FgP1EqzrH4,1093
55
- opencode_python_sdk-0.1.1.dist-info/RECORD,,
55
+ opencode_python_sdk-0.1.3.dist-info/METADATA,sha256=8bH3l7_tYHkst05cMXdAxJCvpbYHZWxSLKKXR1ItkHE,15746
56
+ opencode_python_sdk-0.1.3.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
57
+ opencode_python_sdk-0.1.3.dist-info/licenses/LICENSE,sha256=SbiOG1an_-gZ0gwGLS0tsbUnMHA7tYX22FgP1EqzrH4,1093
58
+ opencode_python_sdk-0.1.3.dist-info/RECORD,,