opencode-python-sdk 0.2.0__tar.gz → 0.2.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 (93) hide show
  1. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/CHANGELOG.md +9 -0
  2. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/PKG-INFO +111 -2
  3. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/README.md +108 -1
  4. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/pyproject.toml +8 -2
  5. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/__init__.py +14 -0
  6. opencode_python_sdk-0.2.3/src/opencode/_aiohttp.py +44 -0
  7. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/_base_client.py +237 -11
  8. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/_client.py +115 -3
  9. opencode_python_sdk-0.2.3/src/opencode/_logs.py +29 -0
  10. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/_pagination.py +14 -0
  11. opencode_python_sdk-0.2.3/src/opencode/_response.py +316 -0
  12. opencode_python_sdk-0.2.3/src/opencode/_service.py +1018 -0
  13. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/resources/agent.py +63 -0
  14. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/resources/command.py +59 -0
  15. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/resources/config.py +93 -20
  16. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/resources/credential.py +149 -56
  17. opencode_python_sdk-0.2.3/src/opencode/resources/debug.py +314 -0
  18. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/resources/event.py +59 -0
  19. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/resources/experimental.py +210 -36
  20. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/resources/file.py +71 -0
  21. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/resources/form.py +59 -0
  22. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/resources/generate.py +59 -0
  23. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/resources/integration.py +538 -128
  24. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/resources/location.py +87 -18
  25. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/resources/mcp.py +252 -82
  26. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/resources/message.py +59 -0
  27. opencode_python_sdk-0.2.3/src/opencode/resources/migration.py +244 -0
  28. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/resources/model.py +63 -0
  29. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/resources/permission.py +272 -38
  30. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/resources/plugin.py +95 -22
  31. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/resources/project.py +63 -0
  32. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/resources/provider.py +63 -0
  33. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/resources/pty.py +172 -20
  34. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/resources/reference.py +59 -0
  35. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/resources/rpc.py +59 -0
  36. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/resources/server.py +67 -0
  37. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/resources/session.py +1159 -405
  38. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/resources/shell.py +101 -20
  39. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/resources/skill.py +59 -0
  40. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/resources/vcs.py +172 -20
  41. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/resources/voice.py +59 -0
  42. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/resources/websearch.py +63 -0
  43. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/resources/worktree.py +123 -40
  44. opencode_python_sdk-0.2.3/src/opencode/service.py +50 -0
  45. opencode_python_sdk-0.2.3/tests/fake_service.py +77 -0
  46. opencode_python_sdk-0.2.3/tests/test_aiohttp.py +131 -0
  47. opencode_python_sdk-0.2.3/tests/test_logs.py +45 -0
  48. opencode_python_sdk-0.2.3/tests/test_response.py +155 -0
  49. opencode_python_sdk-0.2.3/tests/test_service.py +288 -0
  50. opencode_python_sdk-0.2.3/tests/test_service_live.py +67 -0
  51. opencode_python_sdk-0.2.3/tests/test_with_options.py +76 -0
  52. opencode_python_sdk-0.2.0/src/opencode/resources/debug.py +0 -178
  53. opencode_python_sdk-0.2.0/src/opencode/resources/migration.py +0 -118
  54. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/.gitignore +0 -0
  55. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/LICENSE +0 -0
  56. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/api.md +0 -0
  57. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/.generated.json +0 -0
  58. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/_constants.py +0 -0
  59. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/_errors.py +0 -0
  60. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/_events.py +0 -0
  61. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/_exceptions.py +0 -0
  62. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/_models.py +0 -0
  63. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/_pty.py +0 -0
  64. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/_qs.py +0 -0
  65. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/_resource.py +0 -0
  66. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/_rpc.py +0 -0
  67. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/_server.py +0 -0
  68. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/_streaming.py +0 -0
  69. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/_types.py +0 -0
  70. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/_unions.py +0 -0
  71. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/_version.py +0 -0
  72. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/lib/__init__.py +0 -0
  73. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/lib/_ready.py +0 -0
  74. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/lib/_session.py +0 -0
  75. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/py.typed +0 -0
  76. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/resources/__init__.py +0 -0
  77. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/testing.py +0 -0
  78. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/types/__init__.py +0 -0
  79. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/src/opencode/types/_generated.py +0 -0
  80. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/tests/__init__.py +0 -0
  81. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/tests/conftest.py +0 -0
  82. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/tests/test_async_client.py +0 -0
  83. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/tests/test_client.py +0 -0
  84. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/tests/test_docs_coverage.py +0 -0
  85. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/tests/test_encoding.py +0 -0
  86. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/tests/test_events.py +0 -0
  87. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/tests/test_helpers.py +0 -0
  88. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/tests/test_live.py +0 -0
  89. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/tests/test_models.py +0 -0
  90. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/tests/test_pagination.py +0 -0
  91. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/tests/test_pty.py +0 -0
  92. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/tests/test_rpc.py +0 -0
  93. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.3}/tests/test_streaming.py +0 -0
@@ -11,6 +11,15 @@ Releases are published automatically on every merge to `main`. Release notes for
11
11
  - `client.rpc(Definition)`: typed plugin RPC calls and event subscriptions declared with `RpcDefinition`,
12
12
  `rpc_method` and `rpc_event`; RPC failures raise `RpcCallError` / `RpcCallInternalError`.
13
13
  - `Stream.on_activity` and `Stream.interrupt()`.
14
+ - `client.copy(...)`, an alias of `client.with_options(...)`; documented that copies share the connection pool
15
+ and do not close it.
16
+ - `OPENCODE_LOG=debug|info` sends the `opencode` logger to stderr at that level.
17
+ - `DefaultAioHttpClient`: an optional aiohttp transport for `AsyncOpencode`, installed with the `aiohttp` extra
18
+ (`pip install 'opencode-python-sdk[aiohttp]'`) and passed as `http_client=`.
19
+ - `.with_raw_response` and `.with_streaming_response` on every resource (sync and async): return an
20
+ `APIResponse` / `AsyncAPIResponse` with `headers`, `status_code`, `http_response`, `request`, `text`, `json()`,
21
+ `iter_bytes()` / `iter_lines()` and `parse()` for the usual typed value.
22
+ - `client.get` / `post` / `put` / `patch` / `delete` for endpoints the client has no method for.
14
23
 
15
24
  ## 0.1.0
16
25
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: opencode-python-sdk
3
- Version: 0.2.0
3
+ Version: 0.2.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: aiohttp
35
+ Requires-Dist: httpx-aiohttp<0.3,>=0.1.9; extra == 'aiohttp'
34
36
  Provides-Extra: pty
35
37
  Requires-Dist: websockets<17,>=14; extra == 'pty'
36
38
  Description-Content-Type: text/markdown
@@ -116,6 +118,27 @@ async def main() -> None:
116
118
  asyncio.run(main())
117
119
  ```
118
120
 
121
+ ### aiohttp transport
122
+
123
+ For high-concurrency asyncio code you can swap httpx's networking for [aiohttp](https://docs.aiohttp.org). Install
124
+ the extra and pass `DefaultAioHttpClient` as `http_client`:
125
+
126
+ ```bash
127
+ pip install 'opencode-python-sdk[aiohttp]'
128
+ ```
129
+
130
+ ```python
131
+ from opencode import AsyncOpencode, DefaultAioHttpClient
132
+
133
+
134
+ async def main() -> None:
135
+ async with DefaultAioHttpClient() as http, AsyncOpencode("http://127.0.0.1:4096", http_client=http) as client:
136
+ info = await client.server.info()
137
+ ```
138
+
139
+ Retries, timeouts and event streams work the same. aiohttp runs on asyncio only, not trio. The client does not close
140
+ an `http_client` you pass in, so close it yourself as above.
141
+
119
142
  ## Calling the API
120
143
 
121
144
  Methods mirror the official TypeScript client: `client.<group>.<operation>()`, with names in `snake_case`. Path
@@ -350,6 +373,15 @@ client.session.get("ses_...", timeout=5.0) # per call
350
373
 
351
374
  The default timeout is 10 minutes with a 10 second connect timeout.
352
375
 
376
+ `with_options` (alias `copy`) returns a client with some options changed: `timeout`, `max_retries`, `base_url`,
377
+ `password`, `directory`, `default_headers` and `default_query` (headers and query are merged over the current
378
+ ones). The copy shares the original's connection pool, and closing it leaves the pool open.
379
+
380
+ ```python
381
+ client.with_options(max_retries=0).session.create() # no retries for this call
382
+ scoped = client.with_options(directory="/other/repo", default_headers={"X-Trace": "1"})
383
+ ```
384
+
353
385
  ## Running an agent headless
354
386
 
355
387
  `client.session.run` does the usual "prompt an agent and wait" round trip in one call: it creates a session (or
@@ -419,6 +451,27 @@ with server:
419
451
  The server runs in `--stdio` mode, so it also exits when the Python process dies. `AsyncOpencodeServer` is the
420
452
  async equivalent.
421
453
 
454
+ ### The background service
455
+
456
+ OpenCode can also run one shared background server per user (`opencode serve --service`), which registers itself in
457
+ `$XDG_STATE_HOME/opencode/service.json`. `Opencode.from_service()` connects to it, starting it first if it is not
458
+ running:
459
+
460
+ ```python
461
+ from opencode import Opencode, service
462
+
463
+ with Opencode.from_service(version="2.1.2") as client: # replaces a service running another version
464
+ print(client.server.info().version)
465
+
466
+ endpoint = service.discover() # an Endpoint, or None; never starts anything
467
+ service.stop(pty="handoff") # hand persistent terminals to the next service instead of closing them
468
+ ```
469
+
470
+ `service.ensure()` returns the `Endpoint` (URL and credentials) without creating a client, and `service.headers()`
471
+ gives its `Authorization` header for other HTTP clients. Concurrent callers are safe: they race to start the service
472
+ and all get the one that won. `await AsyncOpencode.from_service()`, `service.async_discover()`,
473
+ `service.async_ensure()` and `service.async_stop()` are the async equivalents.
474
+
422
475
  ## Advanced
423
476
 
424
477
  ```python
@@ -436,7 +489,63 @@ client.session.create(title="t", extra_headers={"X-Trace": "1"}, extra_body={"fu
436
489
  ```
437
490
 
438
491
  Logs go to the `opencode` logger. `logging.getLogger("opencode").setLevel(logging.DEBUG)` shows every request and
439
- retry; credentials are never logged.
492
+ retry; credentials are never logged. Set `OPENCODE_LOG=debug` (or `info`) to have the SDK attach a stderr handler at
493
+ that level on import, without touching your own logging config.
494
+
495
+ ### Accessing raw response data
496
+
497
+ Prefix any method with `.with_raw_response` to get the HTTP response (headers, status, request) instead of the
498
+ parsed value. `.parse()` returns what the plain call would have returned:
499
+
500
+ ```python
501
+ response = client.session.with_raw_response.get("ses_...")
502
+ print(response.status_code, response.headers.get("content-type"))
503
+ session = response.parse() # SessionInfo
504
+ ```
505
+
506
+ `.with_raw_response` reads the body before returning. `.with_streaming_response` does not: it is a context manager
507
+ that sends the request on entry and closes the connection on exit, so you can read large bodies incrementally with
508
+ `iter_bytes()`, `iter_text()` or `iter_lines()`, or still call `parse()`:
509
+
510
+ ```python
511
+ with client.file.with_streaming_response.read("big.log") as response:
512
+ for line in response.iter_lines():
513
+ ...
514
+ ```
515
+
516
+ Both exist on every resource and sub-resource of both clients (`async with` and `await response.parse()` on
517
+ `AsyncOpencode`). `APIResponse` also has `.http_response`, `.request`, `.text`, `.json()` and `.read()`.
518
+
519
+ - **List methods** give the response of the first page; `parse()` returns a `SyncCursorPage` / `AsyncCursorPage`
520
+ that fetches the following pages as usual.
521
+ - **Event streams** are never read eagerly: `parse()` returns the `Stream` / `AsyncStream`, which reads events as
522
+ you iterate. Close the stream (or the response) when done.
523
+ - Error statuses still raise `APIStatusError` subclasses; the response is on the exception as `error.response`.
524
+ - Hand-written helpers such as `client.session.run()` and `client.pty.attach()` are not wrapped.
525
+
526
+ ### Making custom/undocumented requests
527
+
528
+ Undocumented parameters go through `extra_headers`, `extra_query` and `extra_body` on every method (see above). For
529
+ an endpoint the client has no method for, `client.get`, `post`, `put`, `patch` and `delete` send a request relative
530
+ to `base_url` with the client's authentication, retries and typed errors, and return the decoded JSON:
531
+
532
+ ```python
533
+ data = client.post("/api/new-endpoint", body={"key": "value"}, query={"directory": "/repo"})
534
+
535
+ # Validate into a type, e.g. a response wrapped in {"data": ...}:
536
+ from opencode.types import SessionInfo
537
+
538
+ payload = client.get("/api/session/ses_...", cast_to=dict[str, SessionInfo])
539
+ ```
540
+
541
+ ### Determining the installed version
542
+
543
+ ```python
544
+ import opencode
545
+
546
+ print(opencode.__version__) # this package
547
+ print(opencode.API_VERSION) # the OpenCode server version it was generated from
548
+ ```
440
549
 
441
550
  ## Versioning
442
551
 
@@ -79,6 +79,27 @@ async def main() -> None:
79
79
  asyncio.run(main())
80
80
  ```
81
81
 
82
+ ### aiohttp transport
83
+
84
+ For high-concurrency asyncio code you can swap httpx's networking for [aiohttp](https://docs.aiohttp.org). Install
85
+ the extra and pass `DefaultAioHttpClient` as `http_client`:
86
+
87
+ ```bash
88
+ pip install 'opencode-python-sdk[aiohttp]'
89
+ ```
90
+
91
+ ```python
92
+ from opencode import AsyncOpencode, DefaultAioHttpClient
93
+
94
+
95
+ async def main() -> None:
96
+ async with DefaultAioHttpClient() as http, AsyncOpencode("http://127.0.0.1:4096", http_client=http) as client:
97
+ info = await client.server.info()
98
+ ```
99
+
100
+ Retries, timeouts and event streams work the same. aiohttp runs on asyncio only, not trio. The client does not close
101
+ an `http_client` you pass in, so close it yourself as above.
102
+
82
103
  ## Calling the API
83
104
 
84
105
  Methods mirror the official TypeScript client: `client.<group>.<operation>()`, with names in `snake_case`. Path
@@ -313,6 +334,15 @@ client.session.get("ses_...", timeout=5.0) # per call
313
334
 
314
335
  The default timeout is 10 minutes with a 10 second connect timeout.
315
336
 
337
+ `with_options` (alias `copy`) returns a client with some options changed: `timeout`, `max_retries`, `base_url`,
338
+ `password`, `directory`, `default_headers` and `default_query` (headers and query are merged over the current
339
+ ones). The copy shares the original's connection pool, and closing it leaves the pool open.
340
+
341
+ ```python
342
+ client.with_options(max_retries=0).session.create() # no retries for this call
343
+ scoped = client.with_options(directory="/other/repo", default_headers={"X-Trace": "1"})
344
+ ```
345
+
316
346
  ## Running an agent headless
317
347
 
318
348
  `client.session.run` does the usual "prompt an agent and wait" round trip in one call: it creates a session (or
@@ -382,6 +412,27 @@ with server:
382
412
  The server runs in `--stdio` mode, so it also exits when the Python process dies. `AsyncOpencodeServer` is the
383
413
  async equivalent.
384
414
 
415
+ ### The background service
416
+
417
+ OpenCode can also run one shared background server per user (`opencode serve --service`), which registers itself in
418
+ `$XDG_STATE_HOME/opencode/service.json`. `Opencode.from_service()` connects to it, starting it first if it is not
419
+ running:
420
+
421
+ ```python
422
+ from opencode import Opencode, service
423
+
424
+ with Opencode.from_service(version="2.1.2") as client: # replaces a service running another version
425
+ print(client.server.info().version)
426
+
427
+ endpoint = service.discover() # an Endpoint, or None; never starts anything
428
+ service.stop(pty="handoff") # hand persistent terminals to the next service instead of closing them
429
+ ```
430
+
431
+ `service.ensure()` returns the `Endpoint` (URL and credentials) without creating a client, and `service.headers()`
432
+ gives its `Authorization` header for other HTTP clients. Concurrent callers are safe: they race to start the service
433
+ and all get the one that won. `await AsyncOpencode.from_service()`, `service.async_discover()`,
434
+ `service.async_ensure()` and `service.async_stop()` are the async equivalents.
435
+
385
436
  ## Advanced
386
437
 
387
438
  ```python
@@ -399,7 +450,63 @@ client.session.create(title="t", extra_headers={"X-Trace": "1"}, extra_body={"fu
399
450
  ```
400
451
 
401
452
  Logs go to the `opencode` logger. `logging.getLogger("opencode").setLevel(logging.DEBUG)` shows every request and
402
- retry; credentials are never logged.
453
+ retry; credentials are never logged. Set `OPENCODE_LOG=debug` (or `info`) to have the SDK attach a stderr handler at
454
+ that level on import, without touching your own logging config.
455
+
456
+ ### Accessing raw response data
457
+
458
+ Prefix any method with `.with_raw_response` to get the HTTP response (headers, status, request) instead of the
459
+ parsed value. `.parse()` returns what the plain call would have returned:
460
+
461
+ ```python
462
+ response = client.session.with_raw_response.get("ses_...")
463
+ print(response.status_code, response.headers.get("content-type"))
464
+ session = response.parse() # SessionInfo
465
+ ```
466
+
467
+ `.with_raw_response` reads the body before returning. `.with_streaming_response` does not: it is a context manager
468
+ that sends the request on entry and closes the connection on exit, so you can read large bodies incrementally with
469
+ `iter_bytes()`, `iter_text()` or `iter_lines()`, or still call `parse()`:
470
+
471
+ ```python
472
+ with client.file.with_streaming_response.read("big.log") as response:
473
+ for line in response.iter_lines():
474
+ ...
475
+ ```
476
+
477
+ Both exist on every resource and sub-resource of both clients (`async with` and `await response.parse()` on
478
+ `AsyncOpencode`). `APIResponse` also has `.http_response`, `.request`, `.text`, `.json()` and `.read()`.
479
+
480
+ - **List methods** give the response of the first page; `parse()` returns a `SyncCursorPage` / `AsyncCursorPage`
481
+ that fetches the following pages as usual.
482
+ - **Event streams** are never read eagerly: `parse()` returns the `Stream` / `AsyncStream`, which reads events as
483
+ you iterate. Close the stream (or the response) when done.
484
+ - Error statuses still raise `APIStatusError` subclasses; the response is on the exception as `error.response`.
485
+ - Hand-written helpers such as `client.session.run()` and `client.pty.attach()` are not wrapped.
486
+
487
+ ### Making custom/undocumented requests
488
+
489
+ Undocumented parameters go through `extra_headers`, `extra_query` and `extra_body` on every method (see above). For
490
+ an endpoint the client has no method for, `client.get`, `post`, `put`, `patch` and `delete` send a request relative
491
+ to `base_url` with the client's authentication, retries and typed errors, and return the decoded JSON:
492
+
493
+ ```python
494
+ data = client.post("/api/new-endpoint", body={"key": "value"}, query={"directory": "/repo"})
495
+
496
+ # Validate into a type, e.g. a response wrapped in {"data": ...}:
497
+ from opencode.types import SessionInfo
498
+
499
+ payload = client.get("/api/session/ses_...", cast_to=dict[str, SessionInfo])
500
+ ```
501
+
502
+ ### Determining the installed version
503
+
504
+ ```python
505
+ import opencode
506
+
507
+ print(opencode.__version__) # this package
508
+ print(opencode.API_VERSION) # the OpenCode server version it was generated from
509
+ ```
403
510
 
404
511
  ## Versioning
405
512
 
@@ -43,21 +43,27 @@ Changelog = "https://github.com/shrijayan/opencode-python-sdk/blob/main/CHANGELO
43
43
  pty = [
44
44
  "websockets>=14,<17",
45
45
  ]
46
+ aiohttp = [
47
+ "httpx-aiohttp>=0.1.9,<0.3",
48
+ ]
46
49
 
47
50
  [dependency-groups]
48
51
  dev = [
49
52
  "mypy>=1.18",
50
53
  "pyright>=1.1.408",
51
54
  "pytest>=8.4",
55
+ "httpx-aiohttp>=0.1.9",
52
56
  "pytest-cov>=7",
53
57
  "respx>=0.22",
54
58
  "ruff>=0.14",
55
59
  "trio>=0.31",
56
60
  "websockets>=14",
57
61
  ]
62
+ # Pillow (via [imaging]) only ships patched releases for Python 3.11+, so docs builds need 3.11+.
58
63
  docs = [
59
- "mkdocs-material[imaging]>=9.6",
60
- "mkdocstrings[python]>=0.30",
64
+ "mkdocs-material[imaging]>=9.6; python_version >= '3.11'",
65
+ "mkdocstrings[python]>=0.30; python_version >= '3.11'",
66
+ "pillow>=13; python_version >= '3.11'",
61
67
  ]
62
68
 
63
69
  [build-system]
@@ -16,6 +16,8 @@ from __future__ import annotations
16
16
 
17
17
  from typing import TYPE_CHECKING, Any
18
18
 
19
+ from . import service
20
+ from ._aiohttp import DefaultAioHttpClient
19
21
  from ._client import AsyncOpencode, Opencode
20
22
 
21
23
  # <generated:error-imports>
@@ -74,10 +76,13 @@ from ._exceptions import (
74
76
  ServiceUnavailableError,
75
77
  UnprocessableEntityError,
76
78
  )
79
+ from ._logs import setup_logging as _setup_logging
77
80
  from ._models import BaseModel
78
81
  from ._pagination import AsyncCursorPage, AsyncPaginator, SyncCursorPage
79
82
  from ._pty import AsyncPtyConnection, PtyConnection, PtyConnectionError
83
+ from ._response import APIResponse, AsyncAPIResponse
80
84
  from ._server import AsyncOpencodeServer, OpencodeServer, ServerStartError
85
+ from ._service import Endpoint, ServiceError
81
86
  from ._streaming import AsyncStream, Stream
82
87
  from ._types import NOT_GIVEN, NotGiven
83
88
  from ._version import __version__
@@ -121,10 +126,12 @@ __all__ = [
121
126
  "API_VERSION",
122
127
  "NOT_GIVEN",
123
128
  "APIConnectionError",
129
+ "APIResponse",
124
130
  "APIError",
125
131
  "APIResponseValidationError",
126
132
  "APIStatusError",
127
133
  "APITimeoutError",
134
+ "AsyncAPIResponse",
128
135
  "AsyncCursorPage",
129
136
  "AsyncOpencode",
130
137
  "AsyncOpencodeServer",
@@ -136,6 +143,8 @@ __all__ = [
136
143
  "BaseModel",
137
144
  "ConflictError",
138
145
  "ContentTooLargeError",
146
+ "DefaultAioHttpClient",
147
+ "Endpoint",
139
148
  "InternalServerError",
140
149
  "NotFoundError",
141
150
  "NotGiven",
@@ -148,11 +157,13 @@ __all__ = [
148
157
  "PtyConnectionError",
149
158
  "RateLimitError",
150
159
  "ServerStartError",
160
+ "ServiceError",
151
161
  "ServiceUnavailableError",
152
162
  "Stream",
153
163
  "SyncCursorPage",
154
164
  "UnprocessableEntityError",
155
165
  "__version__",
166
+ "service",
156
167
  "types",
157
168
  # Shared event stream and typed plugin RPC
158
169
  "SUBSCRIBER_CAPACITY",
@@ -219,3 +230,6 @@ def __getattr__(name: str) -> Any:
219
230
 
220
231
  return importlib.import_module(".types", __name__)
221
232
  raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
233
+
234
+
235
+ _setup_logging()
@@ -0,0 +1,44 @@
1
+ """Optional aiohttp-backed HTTP client for `AsyncOpencode`.
2
+
3
+ Install the extra with ``pip install 'opencode-python-sdk[aiohttp]'`` and pass the client in::
4
+
5
+ async with AsyncOpencode(http_client=DefaultAioHttpClient()) as client:
6
+ ...
7
+
8
+ The SDK still speaks httpx: ``httpx-aiohttp`` swaps the transport underneath, so retries, timeouts and event
9
+ streams behave the same. aiohttp only runs on asyncio, so this client does not work under trio.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from typing import TYPE_CHECKING, Any
15
+
16
+ import httpx
17
+
18
+ from . import _constants as constants
19
+
20
+ if TYPE_CHECKING:
21
+ _Base = httpx.AsyncClient
22
+ else:
23
+ try:
24
+ from httpx_aiohttp import HttpxAiohttpClient as _Base
25
+ except ImportError: # pragma: no cover - only without the extra
26
+ _Base = httpx.AsyncClient
27
+
28
+
29
+ class DefaultAioHttpClient(_Base):
30
+ """aiohttp-backed `httpx.AsyncClient` with the SDK's default connection limits and redirect handling.
31
+
32
+ Pass it to `AsyncOpencode` as ``http_client``. Keyword arguments go to `httpx.AsyncClient`. The SDK does not
33
+ close a client it was given, so close it yourself (``async with`` it) when you are done. Creating one without
34
+ the ``aiohttp`` extra installed raises `RuntimeError`.
35
+ """
36
+
37
+ def __init__(self, **kwargs: Any) -> None:
38
+ if _Base is httpx.AsyncClient:
39
+ raise RuntimeError(
40
+ "DefaultAioHttpClient needs the aiohttp extra: pip install 'opencode-python-sdk[aiohttp]'"
41
+ )
42
+ kwargs.setdefault("limits", constants.DEFAULT_CONNECTION_LIMITS)
43
+ kwargs.setdefault("follow_redirects", True)
44
+ super().__init__(**kwargs)