opencode-python-sdk 0.2.0__tar.gz → 0.2.2__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 (84) hide show
  1. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/PKG-INFO +22 -1
  2. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/README.md +21 -0
  3. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/pyproject.toml +4 -2
  4. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/__init__.py +5 -0
  5. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/_client.py +107 -1
  6. opencode_python_sdk-0.2.2/src/opencode/_service.py +1018 -0
  7. opencode_python_sdk-0.2.2/src/opencode/service.py +50 -0
  8. opencode_python_sdk-0.2.2/tests/fake_service.py +77 -0
  9. opencode_python_sdk-0.2.2/tests/test_service.py +288 -0
  10. opencode_python_sdk-0.2.2/tests/test_service_live.py +67 -0
  11. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/.gitignore +0 -0
  12. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/CHANGELOG.md +0 -0
  13. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/LICENSE +0 -0
  14. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/api.md +0 -0
  15. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/.generated.json +0 -0
  16. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/_base_client.py +0 -0
  17. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/_constants.py +0 -0
  18. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/_errors.py +0 -0
  19. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/_events.py +0 -0
  20. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/_exceptions.py +0 -0
  21. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/_models.py +0 -0
  22. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/_pagination.py +0 -0
  23. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/_pty.py +0 -0
  24. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/_qs.py +0 -0
  25. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/_resource.py +0 -0
  26. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/_rpc.py +0 -0
  27. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/_server.py +0 -0
  28. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/_streaming.py +0 -0
  29. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/_types.py +0 -0
  30. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/_unions.py +0 -0
  31. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/_version.py +0 -0
  32. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/lib/__init__.py +0 -0
  33. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/lib/_ready.py +0 -0
  34. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/lib/_session.py +0 -0
  35. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/py.typed +0 -0
  36. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/__init__.py +0 -0
  37. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/agent.py +0 -0
  38. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/command.py +0 -0
  39. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/config.py +0 -0
  40. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/credential.py +0 -0
  41. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/debug.py +0 -0
  42. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/event.py +0 -0
  43. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/experimental.py +0 -0
  44. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/file.py +0 -0
  45. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/form.py +0 -0
  46. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/generate.py +0 -0
  47. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/integration.py +0 -0
  48. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/location.py +0 -0
  49. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/mcp.py +0 -0
  50. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/message.py +0 -0
  51. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/migration.py +0 -0
  52. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/model.py +0 -0
  53. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/permission.py +0 -0
  54. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/plugin.py +0 -0
  55. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/project.py +0 -0
  56. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/provider.py +0 -0
  57. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/pty.py +0 -0
  58. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/reference.py +0 -0
  59. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/rpc.py +0 -0
  60. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/server.py +0 -0
  61. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/session.py +0 -0
  62. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/shell.py +0 -0
  63. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/skill.py +0 -0
  64. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/vcs.py +0 -0
  65. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/voice.py +0 -0
  66. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/websearch.py +0 -0
  67. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/resources/worktree.py +0 -0
  68. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/testing.py +0 -0
  69. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/types/__init__.py +0 -0
  70. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/src/opencode/types/_generated.py +0 -0
  71. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/tests/__init__.py +0 -0
  72. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/tests/conftest.py +0 -0
  73. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/tests/test_async_client.py +0 -0
  74. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/tests/test_client.py +0 -0
  75. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/tests/test_docs_coverage.py +0 -0
  76. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/tests/test_encoding.py +0 -0
  77. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/tests/test_events.py +0 -0
  78. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/tests/test_helpers.py +0 -0
  79. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/tests/test_live.py +0 -0
  80. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/tests/test_models.py +0 -0
  81. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/tests/test_pagination.py +0 -0
  82. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/tests/test_pty.py +0 -0
  83. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/tests/test_rpc.py +0 -0
  84. {opencode_python_sdk-0.2.0 → opencode_python_sdk-0.2.2}/tests/test_streaming.py +0 -0
@@ -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.2
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/
@@ -419,6 +419,27 @@ with server:
419
419
  The server runs in `--stdio` mode, so it also exits when the Python process dies. `AsyncOpencodeServer` is the
420
420
  async equivalent.
421
421
 
422
+ ### The background service
423
+
424
+ OpenCode can also run one shared background server per user (`opencode serve --service`), which registers itself in
425
+ `$XDG_STATE_HOME/opencode/service.json`. `Opencode.from_service()` connects to it, starting it first if it is not
426
+ running:
427
+
428
+ ```python
429
+ from opencode import Opencode, service
430
+
431
+ with Opencode.from_service(version="2.1.2") as client: # replaces a service running another version
432
+ print(client.server.info().version)
433
+
434
+ endpoint = service.discover() # an Endpoint, or None; never starts anything
435
+ service.stop(pty="handoff") # hand persistent terminals to the next service instead of closing them
436
+ ```
437
+
438
+ `service.ensure()` returns the `Endpoint` (URL and credentials) without creating a client, and `service.headers()`
439
+ gives its `Authorization` header for other HTTP clients. Concurrent callers are safe: they race to start the service
440
+ and all get the one that won. `await AsyncOpencode.from_service()`, `service.async_discover()`,
441
+ `service.async_ensure()` and `service.async_stop()` are the async equivalents.
442
+
422
443
  ## Advanced
423
444
 
424
445
  ```python
@@ -382,6 +382,27 @@ with server:
382
382
  The server runs in `--stdio` mode, so it also exits when the Python process dies. `AsyncOpencodeServer` is the
383
383
  async equivalent.
384
384
 
385
+ ### The background service
386
+
387
+ OpenCode can also run one shared background server per user (`opencode serve --service`), which registers itself in
388
+ `$XDG_STATE_HOME/opencode/service.json`. `Opencode.from_service()` connects to it, starting it first if it is not
389
+ running:
390
+
391
+ ```python
392
+ from opencode import Opencode, service
393
+
394
+ with Opencode.from_service(version="2.1.2") as client: # replaces a service running another version
395
+ print(client.server.info().version)
396
+
397
+ endpoint = service.discover() # an Endpoint, or None; never starts anything
398
+ service.stop(pty="handoff") # hand persistent terminals to the next service instead of closing them
399
+ ```
400
+
401
+ `service.ensure()` returns the `Endpoint` (URL and credentials) without creating a client, and `service.headers()`
402
+ gives its `Authorization` header for other HTTP clients. Concurrent callers are safe: they race to start the service
403
+ and all get the one that won. `await AsyncOpencode.from_service()`, `service.async_discover()`,
404
+ `service.async_ensure()` and `service.async_stop()` are the async equivalents.
405
+
385
406
  ## Advanced
386
407
 
387
408
  ```python
@@ -55,9 +55,11 @@ dev = [
55
55
  "trio>=0.31",
56
56
  "websockets>=14",
57
57
  ]
58
+ # Pillow (via [imaging]) only ships patched releases for Python 3.11+, so docs builds need 3.11+.
58
59
  docs = [
59
- "mkdocs-material[imaging]>=9.6",
60
- "mkdocstrings[python]>=0.30",
60
+ "mkdocs-material[imaging]>=9.6; python_version >= '3.11'",
61
+ "mkdocstrings[python]>=0.30; python_version >= '3.11'",
62
+ "pillow>=13; python_version >= '3.11'",
61
63
  ]
62
64
 
63
65
  [build-system]
@@ -16,6 +16,7 @@ from __future__ import annotations
16
16
 
17
17
  from typing import TYPE_CHECKING, Any
18
18
 
19
+ from . import service
19
20
  from ._client import AsyncOpencode, Opencode
20
21
 
21
22
  # <generated:error-imports>
@@ -78,6 +79,7 @@ from ._models import BaseModel
78
79
  from ._pagination import AsyncCursorPage, AsyncPaginator, SyncCursorPage
79
80
  from ._pty import AsyncPtyConnection, PtyConnection, PtyConnectionError
80
81
  from ._server import AsyncOpencodeServer, OpencodeServer, ServerStartError
82
+ from ._service import Endpoint, ServiceError
81
83
  from ._streaming import AsyncStream, Stream
82
84
  from ._types import NOT_GIVEN, NotGiven
83
85
  from ._version import __version__
@@ -136,6 +138,7 @@ __all__ = [
136
138
  "BaseModel",
137
139
  "ConflictError",
138
140
  "ContentTooLargeError",
141
+ "Endpoint",
139
142
  "InternalServerError",
140
143
  "NotFoundError",
141
144
  "NotGiven",
@@ -148,11 +151,13 @@ __all__ = [
148
151
  "PtyConnectionError",
149
152
  "RateLimitError",
150
153
  "ServerStartError",
154
+ "ServiceError",
151
155
  "ServiceUnavailableError",
152
156
  "Stream",
153
157
  "SyncCursorPage",
154
158
  "UnprocessableEntityError",
155
159
  "__version__",
160
+ "service",
156
161
  "types",
157
162
  # Shared event stream and typed plugin RPC
158
163
  "SUBSCRIBER_CAPACITY",
@@ -9,12 +9,21 @@ from ._base_client import AsyncAPIClient, SyncAPIClient
9
9
  from ._constants import DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT, DEFAULT_USERNAME
10
10
  from ._events import AsyncEventHub, EventHub
11
11
  from ._rpc import AsyncTypedRpcResource, TypedRpcResource
12
+ from ._service import (
13
+ EnsureReason,
14
+ ServiceError,
15
+ Version,
16
+ async_discover,
17
+ async_ensure,
18
+ discover as discover_service,
19
+ ensure as ensure_service,
20
+ )
12
21
  from ._types import NOT_GIVEN, NotGiven, Timeout
13
22
  from .resources import AsyncResources, SyncResources
14
23
 
15
24
  if TYPE_CHECKING:
16
25
  import os
17
- from collections.abc import Iterable, Mapping
26
+ from collections.abc import Callable, Iterable, Mapping, Sequence
18
27
 
19
28
  import httpx
20
29
 
@@ -82,6 +91,67 @@ class Opencode(SyncAPIClient, SyncResources):
82
91
  transport=transport,
83
92
  )
84
93
 
94
+ @classmethod
95
+ def from_service(
96
+ cls,
97
+ *,
98
+ start: bool = True,
99
+ file: str | os.PathLike[str] | None = None,
100
+ version: Version | None = None,
101
+ command: Sequence[str] | None = None,
102
+ env: Mapping[str, str] | None = None,
103
+ on_start: Callable[[EnsureReason, str | None], None] | None = None,
104
+ directory: str | os.PathLike[str] | None = None,
105
+ timeout: Timeout = DEFAULT_TIMEOUT,
106
+ max_retries: int = DEFAULT_MAX_RETRIES,
107
+ default_headers: Mapping[str, str] | None = None,
108
+ default_query: Mapping[str, object] | None = None,
109
+ ) -> Self:
110
+ """Connect to the local background service, starting ``opencode serve --service`` if needed.
111
+
112
+ Uses :func:`opencode.service.ensure`, or :func:`opencode.service.discover` when ``start`` is false, and
113
+ sends the service's password with every request.
114
+
115
+ Args:
116
+ start: Start or replace the service when no matching one is running. When false, raise instead.
117
+ file: Registration file. Defaults to ``$XDG_STATE_HOME/opencode/service.json``.
118
+ version: Required exact version, or a predicate on the service version.
119
+ command: Service command and arguments, used when starting it.
120
+ env: Variables added to the environment of a started service.
121
+ on_start: Called once before a service process is started.
122
+ directory: Default project directory, as in the constructor.
123
+ timeout: Default request timeout, as in the constructor.
124
+ max_retries: Retries for failed requests, as in the constructor.
125
+ default_headers: Headers sent with every request.
126
+ default_query: Query parameters sent with every request.
127
+
128
+ Raises:
129
+ ServiceError: No matching service is running and ``start`` is false, or it could not be started.
130
+
131
+ Example::
132
+
133
+ with Opencode.from_service() as client:
134
+ print(client.server.info().version)
135
+ """
136
+ if start:
137
+ endpoint = ensure_service(file, version, command=command, env=env, on_start=on_start)
138
+ else:
139
+ found = discover_service(file, version)
140
+ if found is None:
141
+ raise ServiceError("No matching OpenCode background service is running")
142
+ endpoint = found
143
+ return cls(
144
+ endpoint.url,
145
+ # An empty password sends no credentials instead of falling back to OPENCODE_PASSWORD.
146
+ password="" if endpoint.auth is None else endpoint.auth.password,
147
+ username=DEFAULT_USERNAME if endpoint.auth is None else endpoint.auth.username,
148
+ directory=directory,
149
+ timeout=timeout,
150
+ max_retries=max_retries,
151
+ default_headers=default_headers,
152
+ default_query=default_query,
153
+ )
154
+
85
155
  @cached_property
86
156
  def events(self) -> EventHub:
87
157
  """The shared event stream: one connection for every subscriber. See :class:`opencode.EventHub`."""
@@ -189,6 +259,42 @@ class AsyncOpencode(AsyncAPIClient, AsyncResources):
189
259
  transport=transport,
190
260
  )
191
261
 
262
+ @classmethod
263
+ async def from_service(
264
+ cls,
265
+ *,
266
+ start: bool = True,
267
+ file: str | os.PathLike[str] | None = None,
268
+ version: Version | None = None,
269
+ command: Sequence[str] | None = None,
270
+ env: Mapping[str, str] | None = None,
271
+ on_start: Callable[[EnsureReason, str | None], None] | None = None,
272
+ directory: str | os.PathLike[str] | None = None,
273
+ timeout: Timeout = DEFAULT_TIMEOUT,
274
+ max_retries: int = DEFAULT_MAX_RETRIES,
275
+ default_headers: Mapping[str, str] | None = None,
276
+ default_query: Mapping[str, object] | None = None,
277
+ ) -> Self:
278
+ """Async version of :meth:`Opencode.from_service`. Await it: ``client = await AsyncOpencode.from_service()``."""
279
+ if start:
280
+ endpoint = await async_ensure(file, version, command=command, env=env, on_start=on_start)
281
+ else:
282
+ found = await async_discover(file, version)
283
+ if found is None:
284
+ raise ServiceError("No matching OpenCode background service is running")
285
+ endpoint = found
286
+ return cls(
287
+ endpoint.url,
288
+ # An empty password sends no credentials instead of falling back to OPENCODE_PASSWORD.
289
+ password="" if endpoint.auth is None else endpoint.auth.password,
290
+ username=DEFAULT_USERNAME if endpoint.auth is None else endpoint.auth.username,
291
+ directory=directory,
292
+ timeout=timeout,
293
+ max_retries=max_retries,
294
+ default_headers=default_headers,
295
+ default_query=default_query,
296
+ )
297
+
192
298
  @cached_property
193
299
  def events(self) -> AsyncEventHub:
194
300
  """The shared event stream: one connection for every subscriber. See :class:`opencode.AsyncEventHub`."""