opencode-python-sdk 0.2.0__py3-none-any.whl → 0.2.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.
- opencode/__init__.py +14 -0
- opencode/_aiohttp.py +44 -0
- opencode/_base_client.py +237 -11
- opencode/_client.py +115 -3
- opencode/_logs.py +29 -0
- opencode/_pagination.py +14 -0
- opencode/_response.py +316 -0
- opencode/_service.py +1018 -0
- opencode/resources/agent.py +63 -0
- opencode/resources/command.py +59 -0
- opencode/resources/config.py +93 -20
- opencode/resources/credential.py +149 -56
- opencode/resources/debug.py +156 -20
- opencode/resources/event.py +59 -0
- opencode/resources/experimental.py +210 -36
- opencode/resources/file.py +71 -0
- opencode/resources/form.py +59 -0
- opencode/resources/generate.py +59 -0
- opencode/resources/integration.py +538 -128
- opencode/resources/location.py +87 -18
- opencode/resources/mcp.py +252 -82
- opencode/resources/message.py +59 -0
- opencode/resources/migration.py +126 -0
- opencode/resources/model.py +63 -0
- opencode/resources/permission.py +272 -38
- opencode/resources/plugin.py +95 -22
- opencode/resources/project.py +63 -0
- opencode/resources/provider.py +63 -0
- opencode/resources/pty.py +172 -20
- opencode/resources/reference.py +59 -0
- opencode/resources/rpc.py +59 -0
- opencode/resources/server.py +67 -0
- opencode/resources/session.py +1159 -405
- opencode/resources/shell.py +101 -20
- opencode/resources/skill.py +59 -0
- opencode/resources/vcs.py +172 -20
- opencode/resources/voice.py +59 -0
- opencode/resources/websearch.py +63 -0
- opencode/resources/worktree.py +123 -40
- opencode/service.py +50 -0
- {opencode_python_sdk-0.2.0.dist-info → opencode_python_sdk-0.2.3.dist-info}/METADATA +111 -2
- opencode_python_sdk-0.2.3.dist-info/RECORD +67 -0
- opencode_python_sdk-0.2.0.dist-info/RECORD +0 -62
- {opencode_python_sdk-0.2.0.dist-info → opencode_python_sdk-0.2.3.dist-info}/WHEEL +0 -0
- {opencode_python_sdk-0.2.0.dist-info → opencode_python_sdk-0.2.3.dist-info}/licenses/LICENSE +0 -0
opencode/_response.py
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
"""Raw HTTP response access for generated methods.
|
|
2
|
+
|
|
3
|
+
``client.session.with_raw_response.get(...)`` calls the same method, but returns an :class:`APIResponse` with the
|
|
4
|
+
status, headers and request instead of the parsed value; :meth:`APIResponse.parse` gives the value the plain call
|
|
5
|
+
would have returned. ``with_streaming_response`` does the same without reading the body, inside a context manager::
|
|
6
|
+
|
|
7
|
+
with client.file.with_streaming_response.content(...) as response:
|
|
8
|
+
for chunk in response.iter_bytes():
|
|
9
|
+
...
|
|
10
|
+
|
|
11
|
+
The generated methods are unchanged: a context variable tells the transport to hand back the response instead of
|
|
12
|
+
parsing it.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import contextvars
|
|
18
|
+
import functools
|
|
19
|
+
import json
|
|
20
|
+
from typing import TYPE_CHECKING, Any, Generic, Literal, TypeVar, cast, overload
|
|
21
|
+
|
|
22
|
+
from typing_extensions import ParamSpec, override
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from collections.abc import AsyncIterator, Awaitable, Callable, Iterator
|
|
26
|
+
from types import TracebackType
|
|
27
|
+
|
|
28
|
+
import httpx
|
|
29
|
+
|
|
30
|
+
from ._pagination import AsyncCursorPage, AsyncPaginator
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"APIResponse",
|
|
34
|
+
"AsyncAPIResponse",
|
|
35
|
+
"AsyncResponseContextManager",
|
|
36
|
+
"ResponseContextManager",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
_P = ParamSpec("_P")
|
|
40
|
+
_R = TypeVar("_R")
|
|
41
|
+
_T = TypeVar("_T")
|
|
42
|
+
|
|
43
|
+
RawMode = Literal["raw", "stream"]
|
|
44
|
+
|
|
45
|
+
_raw_mode: contextvars.ContextVar[RawMode | None] = contextvars.ContextVar("opencode_raw_mode", default=None)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def raw_mode() -> RawMode | None:
|
|
49
|
+
"""The raw-response mode of the current call, or ``None`` for a normal call."""
|
|
50
|
+
return _raw_mode.get()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class _BaseAPIResponse(Generic[_R]):
|
|
54
|
+
http_response: httpx.Response
|
|
55
|
+
"""The underlying ``httpx.Response``."""
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self, response: httpx.Response, parser: Callable[[httpx.Response], Any], *, event_stream: bool
|
|
59
|
+
) -> None:
|
|
60
|
+
self.http_response = response
|
|
61
|
+
self._parser = parser
|
|
62
|
+
self._event_stream = event_stream
|
|
63
|
+
self._parsed: Any = _UNSET
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def headers(self) -> httpx.Headers:
|
|
67
|
+
"""Response headers."""
|
|
68
|
+
return self.http_response.headers
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def status_code(self) -> int:
|
|
72
|
+
"""HTTP status code."""
|
|
73
|
+
return self.http_response.status_code
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def request(self) -> httpx.Request:
|
|
77
|
+
"""The request that was sent (the last attempt, when it was retried)."""
|
|
78
|
+
return self.http_response.request
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def url(self) -> httpx.URL:
|
|
82
|
+
"""The request URL."""
|
|
83
|
+
return self.http_response.url
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def method(self) -> str:
|
|
87
|
+
"""The request method."""
|
|
88
|
+
return self.http_response.request.method
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def elapsed(self) -> Any:
|
|
92
|
+
"""Time from sending the request to receiving the response headers."""
|
|
93
|
+
return self.http_response.elapsed
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def is_closed(self) -> bool:
|
|
97
|
+
"""Whether the connection has been released."""
|
|
98
|
+
return self.http_response.is_closed
|
|
99
|
+
|
|
100
|
+
@override
|
|
101
|
+
def __repr__(self) -> str:
|
|
102
|
+
return f"<{type(self).__name__} [{self.status_code} {self.http_response.reason_phrase}]>"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class _Unset:
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
_UNSET = _Unset()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class APIResponse(_BaseAPIResponse[_R]):
|
|
113
|
+
"""An HTTP response from a sync ``with_raw_response`` / ``with_streaming_response`` call."""
|
|
114
|
+
|
|
115
|
+
def parse(self) -> _R:
|
|
116
|
+
"""Returns the value the plain method call would have returned, reading the body first if needed.
|
|
117
|
+
|
|
118
|
+
For event-stream endpoints this is the :class:`~opencode.Stream`, which consumes the body as you iterate.
|
|
119
|
+
"""
|
|
120
|
+
if isinstance(self._parsed, _Unset):
|
|
121
|
+
if not self._event_stream:
|
|
122
|
+
self.read()
|
|
123
|
+
self._parsed = self._parser(self.http_response)
|
|
124
|
+
return cast("_R", self._parsed)
|
|
125
|
+
|
|
126
|
+
def read(self) -> bytes:
|
|
127
|
+
"""Reads and returns the whole body."""
|
|
128
|
+
return self.http_response.read()
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def content(self) -> bytes:
|
|
132
|
+
"""The whole body, read on first access."""
|
|
133
|
+
return self.read()
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def text(self) -> str:
|
|
137
|
+
"""The body decoded as text."""
|
|
138
|
+
self.read()
|
|
139
|
+
return self.http_response.text
|
|
140
|
+
|
|
141
|
+
def json(self) -> Any:
|
|
142
|
+
"""The body decoded as JSON."""
|
|
143
|
+
return json.loads(self.read())
|
|
144
|
+
|
|
145
|
+
def iter_bytes(self, chunk_size: int | None = None) -> Iterator[bytes]:
|
|
146
|
+
"""Iterates over the body in byte chunks, decoding any content encoding."""
|
|
147
|
+
yield from self.http_response.iter_bytes(chunk_size)
|
|
148
|
+
|
|
149
|
+
def iter_text(self, chunk_size: int | None = None) -> Iterator[str]:
|
|
150
|
+
"""Iterates over the body as decoded text chunks."""
|
|
151
|
+
yield from self.http_response.iter_text(chunk_size)
|
|
152
|
+
|
|
153
|
+
def iter_lines(self) -> Iterator[str]:
|
|
154
|
+
"""Iterates over the body line by line."""
|
|
155
|
+
yield from self.http_response.iter_lines()
|
|
156
|
+
|
|
157
|
+
def close(self) -> None:
|
|
158
|
+
"""Releases the connection without reading the rest of the body."""
|
|
159
|
+
self.http_response.close()
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class AsyncAPIResponse(_BaseAPIResponse[_R]):
|
|
163
|
+
"""An HTTP response from an async ``with_raw_response`` / ``with_streaming_response`` call."""
|
|
164
|
+
|
|
165
|
+
async def parse(self) -> _R:
|
|
166
|
+
"""Returns the value the plain method call would have returned, reading the body first if needed.
|
|
167
|
+
|
|
168
|
+
For event-stream endpoints this is the :class:`~opencode.AsyncStream`, which consumes the body as you
|
|
169
|
+
iterate.
|
|
170
|
+
"""
|
|
171
|
+
if isinstance(self._parsed, _Unset):
|
|
172
|
+
if not self._event_stream:
|
|
173
|
+
await self.read()
|
|
174
|
+
self._parsed = self._parser(self.http_response)
|
|
175
|
+
return cast("_R", self._parsed)
|
|
176
|
+
|
|
177
|
+
async def read(self) -> bytes:
|
|
178
|
+
"""Reads and returns the whole body."""
|
|
179
|
+
return await self.http_response.aread()
|
|
180
|
+
|
|
181
|
+
async def text(self) -> str:
|
|
182
|
+
"""The body decoded as text."""
|
|
183
|
+
await self.read()
|
|
184
|
+
return self.http_response.text
|
|
185
|
+
|
|
186
|
+
async def json(self) -> Any:
|
|
187
|
+
"""The body decoded as JSON."""
|
|
188
|
+
return json.loads(await self.read())
|
|
189
|
+
|
|
190
|
+
async def iter_bytes(self, chunk_size: int | None = None) -> AsyncIterator[bytes]:
|
|
191
|
+
"""Iterates over the body in byte chunks, decoding any content encoding."""
|
|
192
|
+
async for chunk in self.http_response.aiter_bytes(chunk_size):
|
|
193
|
+
yield chunk
|
|
194
|
+
|
|
195
|
+
async def iter_text(self, chunk_size: int | None = None) -> AsyncIterator[str]:
|
|
196
|
+
"""Iterates over the body as decoded text chunks."""
|
|
197
|
+
async for chunk in self.http_response.aiter_text(chunk_size):
|
|
198
|
+
yield chunk
|
|
199
|
+
|
|
200
|
+
async def iter_lines(self) -> AsyncIterator[str]:
|
|
201
|
+
"""Iterates over the body line by line."""
|
|
202
|
+
async for line in self.http_response.aiter_lines():
|
|
203
|
+
yield line
|
|
204
|
+
|
|
205
|
+
async def close(self) -> None:
|
|
206
|
+
"""Releases the connection without reading the rest of the body."""
|
|
207
|
+
await self.http_response.aclose()
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class ResponseContextManager(Generic[_R]):
|
|
211
|
+
"""Sends the request on ``__enter__`` and closes the response on ``__exit__``."""
|
|
212
|
+
|
|
213
|
+
def __init__(self, send: Callable[[], APIResponse[_R]]) -> None:
|
|
214
|
+
self._send = send
|
|
215
|
+
self._response: APIResponse[_R] | None = None
|
|
216
|
+
|
|
217
|
+
def __enter__(self) -> APIResponse[_R]:
|
|
218
|
+
self._response = self._send()
|
|
219
|
+
return self._response
|
|
220
|
+
|
|
221
|
+
def __exit__(
|
|
222
|
+
self, exc_type: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None
|
|
223
|
+
) -> None:
|
|
224
|
+
if self._response is not None:
|
|
225
|
+
self._response.close()
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class AsyncResponseContextManager(Generic[_R]):
|
|
229
|
+
"""Sends the request on ``__aenter__`` and closes the response on ``__aexit__``."""
|
|
230
|
+
|
|
231
|
+
def __init__(self, send: Callable[[], Awaitable[AsyncAPIResponse[_R]]]) -> None:
|
|
232
|
+
self._send = send
|
|
233
|
+
self._response: AsyncAPIResponse[_R] | None = None
|
|
234
|
+
|
|
235
|
+
async def __aenter__(self) -> AsyncAPIResponse[_R]:
|
|
236
|
+
self._response = await self._send()
|
|
237
|
+
return self._response
|
|
238
|
+
|
|
239
|
+
async def __aexit__(
|
|
240
|
+
self, exc_type: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None
|
|
241
|
+
) -> None:
|
|
242
|
+
if self._response is not None:
|
|
243
|
+
await self._response.close()
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _call_sync(mode: RawMode, func: Callable[..., Any], args: Any, kwargs: Any) -> Any:
|
|
247
|
+
token = _raw_mode.set(mode)
|
|
248
|
+
try:
|
|
249
|
+
return func(*args, **kwargs)
|
|
250
|
+
finally:
|
|
251
|
+
_raw_mode.reset(token)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
async def _call_async(mode: RawMode, func: Callable[..., Any], args: Any, kwargs: Any) -> Any:
|
|
255
|
+
token = _raw_mode.set(mode)
|
|
256
|
+
try:
|
|
257
|
+
# Async list methods are plain functions; in raw mode they return a coroutine for the first page.
|
|
258
|
+
return await func(*args, **kwargs)
|
|
259
|
+
finally:
|
|
260
|
+
_raw_mode.reset(token)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def to_raw_response_wrapper(func: Callable[_P, _R]) -> Callable[_P, APIResponse[_R]]:
|
|
264
|
+
"""Wraps a sync generated method so it returns an :class:`APIResponse`."""
|
|
265
|
+
|
|
266
|
+
@functools.wraps(func)
|
|
267
|
+
def wrapped(*args: _P.args, **kwargs: _P.kwargs) -> APIResponse[_R]:
|
|
268
|
+
return cast("APIResponse[_R]", _call_sync("raw", func, args, kwargs))
|
|
269
|
+
|
|
270
|
+
return wrapped
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def to_streamed_response_wrapper(func: Callable[_P, _R]) -> Callable[_P, ResponseContextManager[_R]]:
|
|
274
|
+
"""Wraps a sync generated method so it returns a context manager over an unread :class:`APIResponse`."""
|
|
275
|
+
|
|
276
|
+
@functools.wraps(func)
|
|
277
|
+
def wrapped(*args: _P.args, **kwargs: _P.kwargs) -> ResponseContextManager[_R]:
|
|
278
|
+
return ResponseContextManager(lambda: cast("APIResponse[_R]", _call_sync("stream", func, args, kwargs)))
|
|
279
|
+
|
|
280
|
+
return wrapped
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
@overload
|
|
284
|
+
def async_to_raw_response_wrapper(
|
|
285
|
+
func: Callable[_P, AsyncPaginator[_T]],
|
|
286
|
+
) -> Callable[_P, Awaitable[AsyncAPIResponse[AsyncCursorPage[_T]]]]: ...
|
|
287
|
+
@overload
|
|
288
|
+
def async_to_raw_response_wrapper(
|
|
289
|
+
func: Callable[_P, Awaitable[_R]],
|
|
290
|
+
) -> Callable[_P, Awaitable[AsyncAPIResponse[_R]]]: ...
|
|
291
|
+
def async_to_raw_response_wrapper(func: Callable[_P, Any]) -> Callable[_P, Awaitable[AsyncAPIResponse[Any]]]:
|
|
292
|
+
"""Wraps an async generated method so it returns an :class:`AsyncAPIResponse`."""
|
|
293
|
+
|
|
294
|
+
@functools.wraps(func)
|
|
295
|
+
async def wrapped(*args: _P.args, **kwargs: _P.kwargs) -> AsyncAPIResponse[Any]:
|
|
296
|
+
return cast("AsyncAPIResponse[Any]", await _call_async("raw", func, args, kwargs))
|
|
297
|
+
|
|
298
|
+
return wrapped
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
@overload
|
|
302
|
+
def async_to_streamed_response_wrapper(
|
|
303
|
+
func: Callable[_P, AsyncPaginator[_T]],
|
|
304
|
+
) -> Callable[_P, AsyncResponseContextManager[AsyncCursorPage[_T]]]: ...
|
|
305
|
+
@overload
|
|
306
|
+
def async_to_streamed_response_wrapper(
|
|
307
|
+
func: Callable[_P, Awaitable[_R]],
|
|
308
|
+
) -> Callable[_P, AsyncResponseContextManager[_R]]: ...
|
|
309
|
+
def async_to_streamed_response_wrapper(func: Callable[_P, Any]) -> Callable[_P, AsyncResponseContextManager[Any]]:
|
|
310
|
+
"""Wraps an async generated method so it returns an async context manager over an unread response."""
|
|
311
|
+
|
|
312
|
+
@functools.wraps(func)
|
|
313
|
+
def wrapped(*args: _P.args, **kwargs: _P.kwargs) -> AsyncResponseContextManager[Any]:
|
|
314
|
+
return AsyncResponseContextManager(lambda: _call_async("stream", func, args, kwargs))
|
|
315
|
+
|
|
316
|
+
return wrapped
|