opencode-python-sdk 0.1.3__py3-none-any.whl → 0.2.2__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.
- opencode/__init__.py +19 -1
- opencode/_base_client.py +24 -3
- opencode/_client.py +173 -2
- opencode/_events.py +5 -2
- opencode/_rpc.py +9 -2
- opencode/_service.py +1018 -0
- opencode/lib/__init__.py +5 -0
- opencode/lib/_ready.py +91 -0
- opencode/lib/_session.py +231 -0
- opencode/resources/agent.py +13 -11
- opencode/resources/command.py +9 -7
- opencode/resources/config.py +12 -10
- opencode/resources/credential.py +13 -11
- opencode/resources/debug.py +9 -7
- opencode/resources/event.py +9 -5
- opencode/resources/experimental.py +31 -29
- opencode/resources/file.py +15 -13
- opencode/resources/form.py +9 -7
- opencode/resources/generate.py +9 -7
- opencode/resources/integration.py +28 -26
- opencode/resources/location.py +9 -7
- opencode/resources/mcp.py +13 -11
- opencode/resources/message.py +9 -8
- opencode/resources/migration.py +10 -12
- opencode/resources/model.py +12 -10
- opencode/resources/permission.py +29 -27
- opencode/resources/plugin.py +12 -10
- opencode/resources/project.py +14 -12
- opencode/resources/provider.py +13 -11
- opencode/resources/pty.py +23 -21
- opencode/resources/reference.py +9 -7
- opencode/resources/rpc.py +9 -7
- opencode/resources/server.py +14 -12
- opencode/resources/session.py +92 -90
- opencode/resources/shell.py +19 -17
- opencode/resources/skill.py +9 -7
- opencode/resources/vcs.py +22 -20
- opencode/resources/voice.py +9 -7
- opencode/resources/websearch.py +13 -11
- opencode/resources/worktree.py +13 -11
- opencode/service.py +50 -0
- opencode/testing.py +508 -0
- {opencode_python_sdk-0.1.3.dist-info → opencode_python_sdk-0.2.2.dist-info}/METADATA +71 -1
- opencode_python_sdk-0.2.2.dist-info/RECORD +64 -0
- opencode_python_sdk-0.1.3.dist-info/RECORD +0 -58
- {opencode_python_sdk-0.1.3.dist-info → opencode_python_sdk-0.2.2.dist-info}/WHEEL +0 -0
- {opencode_python_sdk-0.1.3.dist-info → opencode_python_sdk-0.2.2.dist-info}/licenses/LICENSE +0 -0
opencode/__init__.py
CHANGED
|
@@ -14,7 +14,9 @@ Quick start::
|
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
-
from
|
|
17
|
+
from typing import TYPE_CHECKING, Any
|
|
18
|
+
|
|
19
|
+
from . import service
|
|
18
20
|
from ._client import AsyncOpencode, Opencode
|
|
19
21
|
|
|
20
22
|
# <generated:error-imports>
|
|
@@ -77,6 +79,7 @@ from ._models import BaseModel
|
|
|
77
79
|
from ._pagination import AsyncCursorPage, AsyncPaginator, SyncCursorPage
|
|
78
80
|
from ._pty import AsyncPtyConnection, PtyConnection, PtyConnectionError
|
|
79
81
|
from ._server import AsyncOpencodeServer, OpencodeServer, ServerStartError
|
|
82
|
+
from ._service import Endpoint, ServiceError
|
|
80
83
|
from ._streaming import AsyncStream, Stream
|
|
81
84
|
from ._types import NOT_GIVEN, NotGiven
|
|
82
85
|
from ._version import __version__
|
|
@@ -108,6 +111,9 @@ from ._rpc import (
|
|
|
108
111
|
rpc_method,
|
|
109
112
|
)
|
|
110
113
|
|
|
114
|
+
if TYPE_CHECKING:
|
|
115
|
+
from . import types
|
|
116
|
+
|
|
111
117
|
# <generated:api-version>
|
|
112
118
|
API_VERSION = "2.1.2"
|
|
113
119
|
"""The OpenCode server version this client was generated from."""
|
|
@@ -132,6 +138,7 @@ __all__ = [
|
|
|
132
138
|
"BaseModel",
|
|
133
139
|
"ConflictError",
|
|
134
140
|
"ContentTooLargeError",
|
|
141
|
+
"Endpoint",
|
|
135
142
|
"InternalServerError",
|
|
136
143
|
"NotFoundError",
|
|
137
144
|
"NotGiven",
|
|
@@ -144,11 +151,13 @@ __all__ = [
|
|
|
144
151
|
"PtyConnectionError",
|
|
145
152
|
"RateLimitError",
|
|
146
153
|
"ServerStartError",
|
|
154
|
+
"ServiceError",
|
|
147
155
|
"ServiceUnavailableError",
|
|
148
156
|
"Stream",
|
|
149
157
|
"SyncCursorPage",
|
|
150
158
|
"UnprocessableEntityError",
|
|
151
159
|
"__version__",
|
|
160
|
+
"service",
|
|
152
161
|
"types",
|
|
153
162
|
# Shared event stream and typed plugin RPC
|
|
154
163
|
"SUBSCRIBER_CAPACITY",
|
|
@@ -206,3 +215,12 @@ __all__ = [
|
|
|
206
215
|
"WorktreeError",
|
|
207
216
|
# </generated:error-exports>
|
|
208
217
|
]
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def __getattr__(name: str) -> Any:
|
|
221
|
+
# `opencode.types` builds several hundred models, so it is imported on first use rather than with the package.
|
|
222
|
+
if name == "types":
|
|
223
|
+
import importlib
|
|
224
|
+
|
|
225
|
+
return importlib.import_module(".types", __name__)
|
|
226
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
opencode/_base_client.py
CHANGED
|
@@ -7,6 +7,7 @@ transient failures, and turns responses into typed values or errors.
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import base64
|
|
10
|
+
import builtins
|
|
10
11
|
import email.utils
|
|
11
12
|
import functools
|
|
12
13
|
import json
|
|
@@ -60,8 +61,18 @@ def _cached_adapter(tp: Any) -> pydantic.TypeAdapter[Any]:
|
|
|
60
61
|
return pydantic.TypeAdapter(tp)
|
|
61
62
|
|
|
62
63
|
|
|
64
|
+
@functools.cache
|
|
65
|
+
def _resolve_type(expr: str) -> Any:
|
|
66
|
+
# Generated resources name their response types as strings, so `import opencode` does not build every model.
|
|
67
|
+
from . import types
|
|
68
|
+
|
|
69
|
+
return eval(expr, {**vars(types), "builtins": builtins}) # expressions come from the generator
|
|
70
|
+
|
|
71
|
+
|
|
63
72
|
def type_adapter(tp: Any) -> pydantic.TypeAdapter[Any]:
|
|
64
|
-
"""Returns a cached ``TypeAdapter`` for a response type."""
|
|
73
|
+
"""Returns a cached ``TypeAdapter`` for a response type, or for a type expression over ``opencode.types``."""
|
|
74
|
+
if isinstance(tp, str):
|
|
75
|
+
tp = _resolve_type(tp)
|
|
65
76
|
try:
|
|
66
77
|
return _cached_adapter(tp)
|
|
67
78
|
except TypeError: # unhashable type expression
|
|
@@ -307,6 +318,7 @@ class SyncAPIClient(_BaseClient):
|
|
|
307
318
|
default_headers: Mapping[str, str] | None,
|
|
308
319
|
default_query: Mapping[str, object] | None,
|
|
309
320
|
http_client: httpx.Client | None,
|
|
321
|
+
transport: httpx.BaseTransport | None = None,
|
|
310
322
|
) -> None:
|
|
311
323
|
super().__init__(
|
|
312
324
|
base_url=base_url,
|
|
@@ -319,7 +331,11 @@ class SyncAPIClient(_BaseClient):
|
|
|
319
331
|
default_query=default_query,
|
|
320
332
|
)
|
|
321
333
|
self._owns_http = http_client is None
|
|
322
|
-
|
|
334
|
+
if http_client is not None and transport is not None:
|
|
335
|
+
raise ValueError("Pass either http_client or transport, not both")
|
|
336
|
+
self._http = http_client or httpx.Client(
|
|
337
|
+
limits=constants.DEFAULT_CONNECTION_LIMITS, follow_redirects=True, transport=transport
|
|
338
|
+
)
|
|
323
339
|
|
|
324
340
|
def close(self) -> None:
|
|
325
341
|
"""Closes the connection pool, unless it was passed in by the caller."""
|
|
@@ -421,6 +437,7 @@ class AsyncAPIClient(_BaseClient):
|
|
|
421
437
|
default_headers: Mapping[str, str] | None,
|
|
422
438
|
default_query: Mapping[str, object] | None,
|
|
423
439
|
http_client: httpx.AsyncClient | None,
|
|
440
|
+
transport: httpx.AsyncBaseTransport | None = None,
|
|
424
441
|
) -> None:
|
|
425
442
|
super().__init__(
|
|
426
443
|
base_url=base_url,
|
|
@@ -433,7 +450,11 @@ class AsyncAPIClient(_BaseClient):
|
|
|
433
450
|
default_query=default_query,
|
|
434
451
|
)
|
|
435
452
|
self._owns_http = http_client is None
|
|
436
|
-
|
|
453
|
+
if http_client is not None and transport is not None:
|
|
454
|
+
raise ValueError("Pass either http_client or transport, not both")
|
|
455
|
+
self._http = http_client or httpx.AsyncClient(
|
|
456
|
+
limits=constants.DEFAULT_CONNECTION_LIMITS, follow_redirects=True, transport=transport
|
|
457
|
+
)
|
|
437
458
|
|
|
438
459
|
async def close(self) -> None:
|
|
439
460
|
"""Closes the connection pool, unless it was passed in by the caller."""
|
opencode/_client.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from functools import cached_property
|
|
4
|
-
from typing import TYPE_CHECKING
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
5
5
|
|
|
6
6
|
from typing_extensions import Self
|
|
7
7
|
|
|
@@ -9,17 +9,31 @@ 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 Mapping
|
|
26
|
+
from collections.abc import Callable, Iterable, Mapping, Sequence
|
|
18
27
|
|
|
19
28
|
import httpx
|
|
20
29
|
|
|
30
|
+
from .testing import FakeServer
|
|
31
|
+
|
|
21
32
|
__all__ = ["AsyncOpencode", "Opencode"]
|
|
22
33
|
|
|
34
|
+
FAKE_BASE_URL = "http://opencode.fake"
|
|
35
|
+
"""Base URL of clients made by ``Opencode.fake()``."""
|
|
36
|
+
|
|
23
37
|
|
|
24
38
|
class Opencode(SyncAPIClient, SyncResources):
|
|
25
39
|
"""Synchronous client for an OpenCode server.
|
|
@@ -38,6 +52,8 @@ class Opencode(SyncAPIClient, SyncResources):
|
|
|
38
52
|
default_query: Query parameters sent with every request.
|
|
39
53
|
http_client: A preconfigured ``httpx.Client`` (proxies, custom transports, event hooks). The caller keeps
|
|
40
54
|
ownership and must close it.
|
|
55
|
+
transport: An ``httpx`` transport for the client's own connection pool, such as
|
|
56
|
+
:class:`opencode.testing.FakeServer`. Cannot be combined with ``http_client``.
|
|
41
57
|
|
|
42
58
|
Example::
|
|
43
59
|
|
|
@@ -60,6 +76,7 @@ class Opencode(SyncAPIClient, SyncResources):
|
|
|
60
76
|
default_headers: Mapping[str, str] | None = None,
|
|
61
77
|
default_query: Mapping[str, object] | None = None,
|
|
62
78
|
http_client: httpx.Client | None = None,
|
|
79
|
+
transport: httpx.BaseTransport | None = None,
|
|
63
80
|
) -> None:
|
|
64
81
|
super().__init__(
|
|
65
82
|
base_url=base_url,
|
|
@@ -71,6 +88,68 @@ class Opencode(SyncAPIClient, SyncResources):
|
|
|
71
88
|
default_headers=default_headers,
|
|
72
89
|
default_query=default_query,
|
|
73
90
|
http_client=http_client,
|
|
91
|
+
transport=transport,
|
|
92
|
+
)
|
|
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,
|
|
74
153
|
)
|
|
75
154
|
|
|
76
155
|
@cached_property
|
|
@@ -83,6 +162,33 @@ class Opencode(SyncAPIClient, SyncResources):
|
|
|
83
162
|
"""Plugin RPC calls: ``client.rpc.call(...)``, or ``client.rpc(Definition)`` for typed access."""
|
|
84
163
|
return TypedRpcResource(self)
|
|
85
164
|
|
|
165
|
+
@classmethod
|
|
166
|
+
def fake(cls, server: FakeServer | None = None, **kwargs: Any) -> Self:
|
|
167
|
+
"""Returns a client wired to an in-process :class:`opencode.testing.FakeServer`, for tests.
|
|
168
|
+
|
|
169
|
+
Pass your own ``server`` to configure replies and inspect requests; other arguments go to the constructor.
|
|
170
|
+
"""
|
|
171
|
+
from .testing import FakeServer
|
|
172
|
+
|
|
173
|
+
kwargs.setdefault("password", "")
|
|
174
|
+
return cls(FAKE_BASE_URL, transport=server if server is not None else FakeServer(), **kwargs)
|
|
175
|
+
|
|
176
|
+
def wait_until_ready(
|
|
177
|
+
self, *, agents: Iterable[str] = (), mcp_servers: Iterable[str] = (), timeout: float = 60
|
|
178
|
+
) -> None:
|
|
179
|
+
"""Blocks until the server answers, the named ``agents`` are listed and the named ``mcp_servers`` are
|
|
180
|
+
connected.
|
|
181
|
+
|
|
182
|
+
Agents load asynchronously after the server starts, so call this before creating sessions on a freshly
|
|
183
|
+
booted server. Connection errors and 503s while the server boots count as not ready yet.
|
|
184
|
+
|
|
185
|
+
Raises:
|
|
186
|
+
TimeoutError: Something was still missing after ``timeout`` seconds. The message names it.
|
|
187
|
+
"""
|
|
188
|
+
from .lib._ready import wait_until_ready
|
|
189
|
+
|
|
190
|
+
wait_until_ready(self, agents=agents, mcp_servers=mcp_servers, timeout=timeout)
|
|
191
|
+
|
|
86
192
|
def with_options(
|
|
87
193
|
self,
|
|
88
194
|
*,
|
|
@@ -138,6 +244,7 @@ class AsyncOpencode(AsyncAPIClient, AsyncResources):
|
|
|
138
244
|
default_headers: Mapping[str, str] | None = None,
|
|
139
245
|
default_query: Mapping[str, object] | None = None,
|
|
140
246
|
http_client: httpx.AsyncClient | None = None,
|
|
247
|
+
transport: httpx.AsyncBaseTransport | None = None,
|
|
141
248
|
) -> None:
|
|
142
249
|
super().__init__(
|
|
143
250
|
base_url=base_url,
|
|
@@ -149,6 +256,43 @@ class AsyncOpencode(AsyncAPIClient, AsyncResources):
|
|
|
149
256
|
default_headers=default_headers,
|
|
150
257
|
default_query=default_query,
|
|
151
258
|
http_client=http_client,
|
|
259
|
+
transport=transport,
|
|
260
|
+
)
|
|
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,
|
|
152
296
|
)
|
|
153
297
|
|
|
154
298
|
@cached_property
|
|
@@ -161,6 +305,33 @@ class AsyncOpencode(AsyncAPIClient, AsyncResources):
|
|
|
161
305
|
"""Plugin RPC calls: ``client.rpc.call(...)``, or ``client.rpc(Definition)`` for typed access."""
|
|
162
306
|
return AsyncTypedRpcResource(self)
|
|
163
307
|
|
|
308
|
+
@classmethod
|
|
309
|
+
def fake(cls, server: FakeServer | None = None, **kwargs: Any) -> Self:
|
|
310
|
+
"""Returns a client wired to an in-process :class:`opencode.testing.FakeServer`, for tests.
|
|
311
|
+
|
|
312
|
+
Pass your own ``server`` to configure replies and inspect requests; other arguments go to the constructor.
|
|
313
|
+
"""
|
|
314
|
+
from .testing import FakeServer
|
|
315
|
+
|
|
316
|
+
kwargs.setdefault("password", "")
|
|
317
|
+
return cls(FAKE_BASE_URL, transport=server if server is not None else FakeServer(), **kwargs)
|
|
318
|
+
|
|
319
|
+
async def wait_until_ready(
|
|
320
|
+
self, *, agents: Iterable[str] = (), mcp_servers: Iterable[str] = (), timeout: float = 60
|
|
321
|
+
) -> None:
|
|
322
|
+
"""Blocks until the server answers, the named ``agents`` are listed and the named ``mcp_servers`` are
|
|
323
|
+
connected.
|
|
324
|
+
|
|
325
|
+
Agents load asynchronously after the server starts, so call this before creating sessions on a freshly
|
|
326
|
+
booted server. Connection errors and 503s while the server boots count as not ready yet.
|
|
327
|
+
|
|
328
|
+
Raises:
|
|
329
|
+
TimeoutError: Something was still missing after ``timeout`` seconds. The message names it.
|
|
330
|
+
"""
|
|
331
|
+
from .lib._ready import async_wait_until_ready
|
|
332
|
+
|
|
333
|
+
await async_wait_until_ready(self, agents=agents, mcp_servers=mcp_servers, timeout=timeout)
|
|
334
|
+
|
|
164
335
|
def with_options(
|
|
165
336
|
self,
|
|
166
337
|
*,
|
opencode/_events.py
CHANGED
|
@@ -31,7 +31,6 @@ import anyio
|
|
|
31
31
|
from typing_extensions import Self
|
|
32
32
|
|
|
33
33
|
from ._exceptions import APIConnectionError, APIStatusError, OpencodeError
|
|
34
|
-
from .types import ServerConnectedEvent
|
|
35
34
|
|
|
36
35
|
if TYPE_CHECKING:
|
|
37
36
|
from collections.abc import AsyncIterator, Awaitable, Callable, Iterator
|
|
@@ -39,7 +38,7 @@ if TYPE_CHECKING:
|
|
|
39
38
|
|
|
40
39
|
from ._client import AsyncOpencode, Opencode
|
|
41
40
|
from ._streaming import AsyncStream, Stream
|
|
42
|
-
from .types import Event
|
|
41
|
+
from .types import Event, ServerConnectedEvent
|
|
43
42
|
|
|
44
43
|
__all__ = [
|
|
45
44
|
"SUBSCRIBER_CAPACITY",
|
|
@@ -302,6 +301,8 @@ class EventHub:
|
|
|
302
301
|
|
|
303
302
|
def _read(self, connection: _SyncConnection) -> None:
|
|
304
303
|
"""Reads one connection until it ends, setting ``connection.delivered`` once an event arrives."""
|
|
304
|
+
from .types import ServerConnectedEvent
|
|
305
|
+
|
|
305
306
|
stream = self._client.event.subscribe()
|
|
306
307
|
with self._lock:
|
|
307
308
|
connection.stream = stream
|
|
@@ -605,6 +606,8 @@ class AsyncEventHub:
|
|
|
605
606
|
|
|
606
607
|
async def _read(self, connection: _AsyncConnection) -> None:
|
|
607
608
|
"""Reads one connection until it ends, setting ``connection.delivered`` once an event arrives."""
|
|
609
|
+
from .types import ServerConnectedEvent
|
|
610
|
+
|
|
608
611
|
stream: AsyncStream[Event] = await self._client.event.subscribe()
|
|
609
612
|
try:
|
|
610
613
|
stream.on_activity = connection.activity
|
opencode/_rpc.py
CHANGED
|
@@ -44,7 +44,6 @@ from ._errors import RpcError, RpcInternalError
|
|
|
44
44
|
from ._exceptions import OpencodeError, validation_summary
|
|
45
45
|
from ._types import NOT_GIVEN, NotGiven
|
|
46
46
|
from .resources.rpc import AsyncRpcResource, RpcResource
|
|
47
|
-
from .types import RpcEvent
|
|
48
47
|
|
|
49
48
|
if TYPE_CHECKING:
|
|
50
49
|
from collections.abc import Mapping
|
|
@@ -53,7 +52,7 @@ if TYPE_CHECKING:
|
|
|
53
52
|
from ._client import AsyncOpencode, Opencode
|
|
54
53
|
from ._events import AsyncSubscription, Subscription
|
|
55
54
|
from ._types import Headers, Timeout
|
|
56
|
-
from .types import Location
|
|
55
|
+
from .types import Location, RpcEvent
|
|
57
56
|
|
|
58
57
|
__all__ = [
|
|
59
58
|
"AsyncRpcClient",
|
|
@@ -327,11 +326,15 @@ class RpcClient(Generic[_D]):
|
|
|
327
326
|
|
|
328
327
|
def events(self) -> Subscription[RpcEvent]:
|
|
329
328
|
"""Subscribes to every event of this RPC, as raw events, on the client's shared event stream."""
|
|
329
|
+
from .types import RpcEvent
|
|
330
|
+
|
|
330
331
|
prefix = f"rpc.{self.definition.id}."
|
|
331
332
|
return self._client.events.of_type(RpcEvent).where(lambda event: event.type.startswith(prefix))
|
|
332
333
|
|
|
333
334
|
def subscribe(self, event: RpcEventDefinition[_T]) -> RpcEvents[_T]:
|
|
334
335
|
"""Subscribes to one event of this RPC, with validated data, on the client's shared event stream."""
|
|
336
|
+
from .types import RpcEvent
|
|
337
|
+
|
|
335
338
|
_check_event(self.definition, event)
|
|
336
339
|
wire = self.definition.event_type(event)
|
|
337
340
|
return RpcEvents(self._client.events.of_type(RpcEvent).where(lambda raw: raw.type == wire), event)
|
|
@@ -396,11 +399,15 @@ class AsyncRpcClient(Generic[_D]):
|
|
|
396
399
|
|
|
397
400
|
def events(self) -> AsyncSubscription[RpcEvent]:
|
|
398
401
|
"""Subscribes to every event of this RPC, as raw events, on the client's shared event stream."""
|
|
402
|
+
from .types import RpcEvent
|
|
403
|
+
|
|
399
404
|
prefix = f"rpc.{self.definition.id}."
|
|
400
405
|
return self._client.events.of_type(RpcEvent).where(lambda event: event.type.startswith(prefix))
|
|
401
406
|
|
|
402
407
|
def subscribe(self, event: RpcEventDefinition[_T]) -> AsyncRpcEvents[_T]:
|
|
403
408
|
"""Subscribes to one event of this RPC, with validated data, on the client's shared event stream."""
|
|
409
|
+
from .types import RpcEvent
|
|
410
|
+
|
|
404
411
|
_check_event(self.definition, event)
|
|
405
412
|
wire = self.definition.event_type(event)
|
|
406
413
|
return AsyncRpcEvents(self._client.events.of_type(RpcEvent).where(lambda raw: raw.type == wire), event)
|