opencode-python-sdk 0.1.2__py3-none-any.whl → 0.2.0__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 +62 -1
- opencode/_base_client.py +24 -3
- opencode/_client.py +90 -2
- opencode/_events.py +732 -0
- opencode/_rpc.py +489 -0
- opencode/_streaming.py +22 -1
- 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/testing.py +508 -0
- {opencode_python_sdk-0.1.2.dist-info → opencode_python_sdk-0.2.0.dist-info}/METADATA +126 -1
- opencode_python_sdk-0.2.0.dist-info/RECORD +62 -0
- opencode_python_sdk-0.1.2.dist-info/RECORD +0 -56
- {opencode_python_sdk-0.1.2.dist-info → opencode_python_sdk-0.2.0.dist-info}/WHEEL +0 -0
- {opencode_python_sdk-0.1.2.dist-info → opencode_python_sdk-0.2.0.dist-info}/licenses/LICENSE +0 -0
opencode/__init__.py
CHANGED
|
@@ -14,7 +14,8 @@ Quick start::
|
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
-
from
|
|
17
|
+
from typing import TYPE_CHECKING, Any
|
|
18
|
+
|
|
18
19
|
from ._client import AsyncOpencode, Opencode
|
|
19
20
|
|
|
20
21
|
# <generated:error-imports>
|
|
@@ -81,6 +82,36 @@ from ._streaming import AsyncStream, Stream
|
|
|
81
82
|
from ._types import NOT_GIVEN, NotGiven
|
|
82
83
|
from ._version import __version__
|
|
83
84
|
|
|
85
|
+
# isort: split
|
|
86
|
+
# Hand-written helpers, kept apart from the generated error-import block above.
|
|
87
|
+
from ._events import (
|
|
88
|
+
SUBSCRIBER_CAPACITY,
|
|
89
|
+
AsyncEventHub,
|
|
90
|
+
AsyncSubscription,
|
|
91
|
+
EventHub,
|
|
92
|
+
EventOverflowError,
|
|
93
|
+
Reconnect,
|
|
94
|
+
Subscription,
|
|
95
|
+
session_id_of,
|
|
96
|
+
)
|
|
97
|
+
from ._rpc import (
|
|
98
|
+
AsyncRpcClient,
|
|
99
|
+
AsyncRpcEvents,
|
|
100
|
+
RpcCallError,
|
|
101
|
+
RpcCallInternalError,
|
|
102
|
+
RpcClient,
|
|
103
|
+
RpcDefinition,
|
|
104
|
+
RpcEventDefinition,
|
|
105
|
+
RpcEventPayload,
|
|
106
|
+
RpcEvents,
|
|
107
|
+
RpcMethod,
|
|
108
|
+
rpc_event,
|
|
109
|
+
rpc_method,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
if TYPE_CHECKING:
|
|
113
|
+
from . import types
|
|
114
|
+
|
|
84
115
|
# <generated:api-version>
|
|
85
116
|
API_VERSION = "2.1.2"
|
|
86
117
|
"""The OpenCode server version this client was generated from."""
|
|
@@ -123,6 +154,27 @@ __all__ = [
|
|
|
123
154
|
"UnprocessableEntityError",
|
|
124
155
|
"__version__",
|
|
125
156
|
"types",
|
|
157
|
+
# Shared event stream and typed plugin RPC
|
|
158
|
+
"SUBSCRIBER_CAPACITY",
|
|
159
|
+
"AsyncEventHub",
|
|
160
|
+
"AsyncRpcClient",
|
|
161
|
+
"AsyncRpcEvents",
|
|
162
|
+
"AsyncSubscription",
|
|
163
|
+
"EventHub",
|
|
164
|
+
"EventOverflowError",
|
|
165
|
+
"Reconnect",
|
|
166
|
+
"RpcCallError",
|
|
167
|
+
"RpcCallInternalError",
|
|
168
|
+
"RpcClient",
|
|
169
|
+
"RpcDefinition",
|
|
170
|
+
"RpcEventDefinition",
|
|
171
|
+
"RpcEventPayload",
|
|
172
|
+
"RpcEvents",
|
|
173
|
+
"RpcMethod",
|
|
174
|
+
"Subscription",
|
|
175
|
+
"rpc_event",
|
|
176
|
+
"rpc_method",
|
|
177
|
+
"session_id_of",
|
|
126
178
|
# <generated:error-exports>
|
|
127
179
|
"AgentNotFoundError",
|
|
128
180
|
"CommandExecutionError",
|
|
@@ -158,3 +210,12 @@ __all__ = [
|
|
|
158
210
|
"WorktreeError",
|
|
159
211
|
# </generated:error-exports>
|
|
160
212
|
]
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def __getattr__(name: str) -> Any:
|
|
216
|
+
# `opencode.types` builds several hundred models, so it is imported on first use rather than with the package.
|
|
217
|
+
if name == "types":
|
|
218
|
+
import importlib
|
|
219
|
+
|
|
220
|
+
return importlib.import_module(".types", __name__)
|
|
221
|
+
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,22 +1,30 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from functools import cached_property
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
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
|
|
|
12
15
|
if TYPE_CHECKING:
|
|
13
16
|
import os
|
|
14
|
-
from collections.abc import Mapping
|
|
17
|
+
from collections.abc import Iterable, Mapping
|
|
15
18
|
|
|
16
19
|
import httpx
|
|
17
20
|
|
|
21
|
+
from .testing import FakeServer
|
|
22
|
+
|
|
18
23
|
__all__ = ["AsyncOpencode", "Opencode"]
|
|
19
24
|
|
|
25
|
+
FAKE_BASE_URL = "http://opencode.fake"
|
|
26
|
+
"""Base URL of clients made by ``Opencode.fake()``."""
|
|
27
|
+
|
|
20
28
|
|
|
21
29
|
class Opencode(SyncAPIClient, SyncResources):
|
|
22
30
|
"""Synchronous client for an OpenCode server.
|
|
@@ -35,6 +43,8 @@ class Opencode(SyncAPIClient, SyncResources):
|
|
|
35
43
|
default_query: Query parameters sent with every request.
|
|
36
44
|
http_client: A preconfigured ``httpx.Client`` (proxies, custom transports, event hooks). The caller keeps
|
|
37
45
|
ownership and must close it.
|
|
46
|
+
transport: An ``httpx`` transport for the client's own connection pool, such as
|
|
47
|
+
:class:`opencode.testing.FakeServer`. Cannot be combined with ``http_client``.
|
|
38
48
|
|
|
39
49
|
Example::
|
|
40
50
|
|
|
@@ -57,6 +67,7 @@ class Opencode(SyncAPIClient, SyncResources):
|
|
|
57
67
|
default_headers: Mapping[str, str] | None = None,
|
|
58
68
|
default_query: Mapping[str, object] | None = None,
|
|
59
69
|
http_client: httpx.Client | None = None,
|
|
70
|
+
transport: httpx.BaseTransport | None = None,
|
|
60
71
|
) -> None:
|
|
61
72
|
super().__init__(
|
|
62
73
|
base_url=base_url,
|
|
@@ -68,8 +79,46 @@ class Opencode(SyncAPIClient, SyncResources):
|
|
|
68
79
|
default_headers=default_headers,
|
|
69
80
|
default_query=default_query,
|
|
70
81
|
http_client=http_client,
|
|
82
|
+
transport=transport,
|
|
71
83
|
)
|
|
72
84
|
|
|
85
|
+
@cached_property
|
|
86
|
+
def events(self) -> EventHub:
|
|
87
|
+
"""The shared event stream: one connection for every subscriber. See :class:`opencode.EventHub`."""
|
|
88
|
+
return EventHub(self)
|
|
89
|
+
|
|
90
|
+
@cached_property
|
|
91
|
+
def rpc(self) -> TypedRpcResource:
|
|
92
|
+
"""Plugin RPC calls: ``client.rpc.call(...)``, or ``client.rpc(Definition)`` for typed access."""
|
|
93
|
+
return TypedRpcResource(self)
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
def fake(cls, server: FakeServer | None = None, **kwargs: Any) -> Self:
|
|
97
|
+
"""Returns a client wired to an in-process :class:`opencode.testing.FakeServer`, for tests.
|
|
98
|
+
|
|
99
|
+
Pass your own ``server`` to configure replies and inspect requests; other arguments go to the constructor.
|
|
100
|
+
"""
|
|
101
|
+
from .testing import FakeServer
|
|
102
|
+
|
|
103
|
+
kwargs.setdefault("password", "")
|
|
104
|
+
return cls(FAKE_BASE_URL, transport=server if server is not None else FakeServer(), **kwargs)
|
|
105
|
+
|
|
106
|
+
def wait_until_ready(
|
|
107
|
+
self, *, agents: Iterable[str] = (), mcp_servers: Iterable[str] = (), timeout: float = 60
|
|
108
|
+
) -> None:
|
|
109
|
+
"""Blocks until the server answers, the named ``agents`` are listed and the named ``mcp_servers`` are
|
|
110
|
+
connected.
|
|
111
|
+
|
|
112
|
+
Agents load asynchronously after the server starts, so call this before creating sessions on a freshly
|
|
113
|
+
booted server. Connection errors and 503s while the server boots count as not ready yet.
|
|
114
|
+
|
|
115
|
+
Raises:
|
|
116
|
+
TimeoutError: Something was still missing after ``timeout`` seconds. The message names it.
|
|
117
|
+
"""
|
|
118
|
+
from .lib._ready import wait_until_ready
|
|
119
|
+
|
|
120
|
+
wait_until_ready(self, agents=agents, mcp_servers=mcp_servers, timeout=timeout)
|
|
121
|
+
|
|
73
122
|
def with_options(
|
|
74
123
|
self,
|
|
75
124
|
*,
|
|
@@ -125,6 +174,7 @@ class AsyncOpencode(AsyncAPIClient, AsyncResources):
|
|
|
125
174
|
default_headers: Mapping[str, str] | None = None,
|
|
126
175
|
default_query: Mapping[str, object] | None = None,
|
|
127
176
|
http_client: httpx.AsyncClient | None = None,
|
|
177
|
+
transport: httpx.AsyncBaseTransport | None = None,
|
|
128
178
|
) -> None:
|
|
129
179
|
super().__init__(
|
|
130
180
|
base_url=base_url,
|
|
@@ -136,8 +186,46 @@ class AsyncOpencode(AsyncAPIClient, AsyncResources):
|
|
|
136
186
|
default_headers=default_headers,
|
|
137
187
|
default_query=default_query,
|
|
138
188
|
http_client=http_client,
|
|
189
|
+
transport=transport,
|
|
139
190
|
)
|
|
140
191
|
|
|
192
|
+
@cached_property
|
|
193
|
+
def events(self) -> AsyncEventHub:
|
|
194
|
+
"""The shared event stream: one connection for every subscriber. See :class:`opencode.AsyncEventHub`."""
|
|
195
|
+
return AsyncEventHub(self)
|
|
196
|
+
|
|
197
|
+
@cached_property
|
|
198
|
+
def rpc(self) -> AsyncTypedRpcResource:
|
|
199
|
+
"""Plugin RPC calls: ``client.rpc.call(...)``, or ``client.rpc(Definition)`` for typed access."""
|
|
200
|
+
return AsyncTypedRpcResource(self)
|
|
201
|
+
|
|
202
|
+
@classmethod
|
|
203
|
+
def fake(cls, server: FakeServer | None = None, **kwargs: Any) -> Self:
|
|
204
|
+
"""Returns a client wired to an in-process :class:`opencode.testing.FakeServer`, for tests.
|
|
205
|
+
|
|
206
|
+
Pass your own ``server`` to configure replies and inspect requests; other arguments go to the constructor.
|
|
207
|
+
"""
|
|
208
|
+
from .testing import FakeServer
|
|
209
|
+
|
|
210
|
+
kwargs.setdefault("password", "")
|
|
211
|
+
return cls(FAKE_BASE_URL, transport=server if server is not None else FakeServer(), **kwargs)
|
|
212
|
+
|
|
213
|
+
async def wait_until_ready(
|
|
214
|
+
self, *, agents: Iterable[str] = (), mcp_servers: Iterable[str] = (), timeout: float = 60
|
|
215
|
+
) -> None:
|
|
216
|
+
"""Blocks until the server answers, the named ``agents`` are listed and the named ``mcp_servers`` are
|
|
217
|
+
connected.
|
|
218
|
+
|
|
219
|
+
Agents load asynchronously after the server starts, so call this before creating sessions on a freshly
|
|
220
|
+
booted server. Connection errors and 503s while the server boots count as not ready yet.
|
|
221
|
+
|
|
222
|
+
Raises:
|
|
223
|
+
TimeoutError: Something was still missing after ``timeout`` seconds. The message names it.
|
|
224
|
+
"""
|
|
225
|
+
from .lib._ready import async_wait_until_ready
|
|
226
|
+
|
|
227
|
+
await async_wait_until_ready(self, agents=agents, mcp_servers=mcp_servers, timeout=timeout)
|
|
228
|
+
|
|
141
229
|
def with_options(
|
|
142
230
|
self,
|
|
143
231
|
*,
|