opencode-python-sdk 0.0.1.dev1__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.
Files changed (62) hide show
  1. opencode/.generated.json +39 -0
  2. opencode/__init__.py +221 -0
  3. opencode/_base_client.py +539 -0
  4. opencode/_client.py +255 -0
  5. opencode/_constants.py +27 -0
  6. opencode/_errors.py +547 -0
  7. opencode/_events.py +732 -0
  8. opencode/_exceptions.py +271 -0
  9. opencode/_models.py +54 -0
  10. opencode/_pagination.py +192 -0
  11. opencode/_pty.py +586 -0
  12. opencode/_qs.py +95 -0
  13. opencode/_resource.py +42 -0
  14. opencode/_rpc.py +489 -0
  15. opencode/_server.py +335 -0
  16. opencode/_streaming.py +248 -0
  17. opencode/_types.py +77 -0
  18. opencode/_unions.py +46 -0
  19. opencode/_version.py +8 -0
  20. opencode/lib/__init__.py +5 -0
  21. opencode/lib/_ready.py +91 -0
  22. opencode/lib/_session.py +231 -0
  23. opencode/py.typed +0 -0
  24. opencode/resources/__init__.py +364 -0
  25. opencode/resources/agent.py +179 -0
  26. opencode/resources/command.py +100 -0
  27. opencode/resources/config.py +240 -0
  28. opencode/resources/credential.py +384 -0
  29. opencode/resources/debug.py +178 -0
  30. opencode/resources/event.py +88 -0
  31. opencode/resources/experimental.py +765 -0
  32. opencode/resources/file.py +355 -0
  33. opencode/resources/form.py +100 -0
  34. opencode/resources/generate.py +110 -0
  35. opencode/resources/integration.py +1033 -0
  36. opencode/resources/location.py +162 -0
  37. opencode/resources/mcp.py +492 -0
  38. opencode/resources/message.py +152 -0
  39. opencode/resources/migration.py +118 -0
  40. opencode/resources/model.py +177 -0
  41. opencode/resources/permission.py +704 -0
  42. opencode/resources/plugin.py +250 -0
  43. opencode/resources/project.py +187 -0
  44. opencode/resources/provider.py +183 -0
  45. opencode/resources/pty.py +522 -0
  46. opencode/resources/reference.py +100 -0
  47. opencode/resources/rpc.py +113 -0
  48. opencode/resources/server.py +230 -0
  49. opencode/resources/session.py +3567 -0
  50. opencode/resources/shell.py +412 -0
  51. opencode/resources/skill.py +100 -0
  52. opencode/resources/vcs.py +500 -0
  53. opencode/resources/voice.py +113 -0
  54. opencode/resources/websearch.py +184 -0
  55. opencode/resources/worktree.py +336 -0
  56. opencode/testing.py +508 -0
  57. opencode/types/__init__.py +1462 -0
  58. opencode/types/_generated.py +8540 -0
  59. opencode_python_sdk-0.0.1.dev1.dist-info/METADATA +468 -0
  60. opencode_python_sdk-0.0.1.dev1.dist-info/RECORD +62 -0
  61. opencode_python_sdk-0.0.1.dev1.dist-info/WHEEL +4 -0
  62. opencode_python_sdk-0.0.1.dev1.dist-info/licenses/LICENSE +21 -0
opencode/_resource.py ADDED
@@ -0,0 +1,42 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from ._types import NotGiven
6
+
7
+ if TYPE_CHECKING:
8
+ from ._client import AsyncOpencode, Opencode
9
+
10
+ __all__ = ["AsyncAPIResource", "SyncAPIResource"]
11
+
12
+
13
+ class SyncAPIResource:
14
+ """Base class for generated resource groups of the sync client."""
15
+
16
+ _client: Opencode
17
+
18
+ def __init__(self, client: Opencode) -> None:
19
+ self._client = client
20
+
21
+ def _location(self, location: object) -> object:
22
+ """Falls back to the client's ``directory`` for endpoints that take a ``location``."""
23
+ return _default_location(location, self._client.directory)
24
+
25
+
26
+ class AsyncAPIResource:
27
+ """Base class for generated resource groups of the async client."""
28
+
29
+ _client: AsyncOpencode
30
+
31
+ def __init__(self, client: AsyncOpencode) -> None:
32
+ self._client = client
33
+
34
+ def _location(self, location: object) -> object:
35
+ """Falls back to the client's ``directory`` for endpoints that take a ``location``."""
36
+ return _default_location(location, self._client.directory)
37
+
38
+
39
+ def _default_location(location: object, directory: str | None) -> object:
40
+ if isinstance(location, NotGiven) and directory is not None:
41
+ return {"directory": directory}
42
+ return location
opencode/_rpc.py ADDED
@@ -0,0 +1,489 @@
1
+ """Typed calls to plugin RPCs.
2
+
3
+ A plugin registers an RPC with an id, methods and events. Declare the same shape once as a class, then call it
4
+ through ``client.rpc(Definition)``, the counterpart of ``client.rpc(definition)`` in the official TypeScript client::
5
+
6
+ from pydantic import BaseModel
7
+ from opencode import RpcDefinition, rpc_event, rpc_method
8
+
9
+
10
+ class AddInput(BaseModel):
11
+ amount: int
12
+
13
+
14
+ class Total(BaseModel):
15
+ total: int
16
+
17
+
18
+ class Counter(RpcDefinition, id="counter"):
19
+ add = rpc_method(AddInput, Total)
20
+ reset = rpc_method(None, Total)
21
+ changed = rpc_event(Total)
22
+
23
+
24
+ counter = client.rpc(Counter)
25
+ print(counter.call(Counter.add, AddInput(amount=2)).total)
26
+ with counter.subscribe(Counter.changed) as changes:
27
+ for change in changes:
28
+ print(change.data.total)
29
+
30
+ Inputs are validated and serialized, and outputs and event data validated, with pydantic, so any type pydantic
31
+ accepts works: models, dataclasses, ``TypedDict``, ``list[int]``. RPC failures raise :class:`RpcCallError`.
32
+ """
33
+
34
+ from __future__ import annotations
35
+
36
+ from dataclasses import dataclass
37
+ from typing import TYPE_CHECKING, Any, ClassVar, Generic, TypeVar, cast, overload
38
+
39
+ import pydantic
40
+ from typing_extensions import Self
41
+
42
+ from ._base_client import type_adapter
43
+ from ._errors import RpcError, RpcInternalError
44
+ from ._exceptions import OpencodeError, validation_summary
45
+ from ._types import NOT_GIVEN, NotGiven
46
+ from .resources.rpc import AsyncRpcResource, RpcResource
47
+
48
+ if TYPE_CHECKING:
49
+ from collections.abc import Mapping
50
+ from types import TracebackType
51
+
52
+ from ._client import AsyncOpencode, Opencode
53
+ from ._events import AsyncSubscription, Subscription
54
+ from ._types import Headers, Timeout
55
+ from .types import Location, RpcEvent
56
+
57
+ __all__ = [
58
+ "AsyncRpcClient",
59
+ "AsyncRpcEvents",
60
+ "RpcCallError",
61
+ "RpcCallInternalError",
62
+ "RpcClient",
63
+ "RpcDefinition",
64
+ "RpcEventDefinition",
65
+ "RpcEventPayload",
66
+ "RpcEvents",
67
+ "RpcMethod",
68
+ "rpc_event",
69
+ "rpc_method",
70
+ ]
71
+
72
+ _D = TypeVar("_D", bound="RpcDefinition")
73
+ _I = TypeVar("_I")
74
+ _O = TypeVar("_O")
75
+ _T = TypeVar("_T")
76
+
77
+
78
+ class RpcCallError(OpencodeError):
79
+ """A plugin RPC method failed (the server answered with ``RpcError``).
80
+
81
+ Attributes:
82
+ rpc_id: The RPC that was called.
83
+ method: The method that was called.
84
+ type: The error type the plugin reported, for example ``"not_found"``.
85
+ message: The error message.
86
+ data: Error details, validated against the method's ``errors`` declaration when it names this type.
87
+ """
88
+
89
+ def __init__(self, rpc_id: str, method: str, type: str, message: str, data: object = None) -> None:
90
+ super().__init__(f"{rpc_id}.{method} failed with {type}: {message}")
91
+ self.rpc_id = rpc_id
92
+ self.method = method
93
+ self.type = type
94
+ self.message = message
95
+ self.data = data
96
+
97
+
98
+ class RpcCallInternalError(RpcCallError):
99
+ """The plugin RPC crashed or returned output that does not match its declaration.
100
+
101
+ ``type`` is ``"rpc.internal"`` or ``"rpc.invalid_output"``. Output this client cannot validate against the
102
+ method's declared output type is also reported as ``"rpc.invalid_output"``.
103
+ """
104
+
105
+
106
+ class RpcMethod(Generic[_I, _O]):
107
+ """One method of an :class:`RpcDefinition`. Create it with :func:`rpc_method`."""
108
+
109
+ name: str
110
+
111
+ def __init__(self, input: Any, output: Any, *, name: str | None, errors: Mapping[str, Any] | None) -> None:
112
+ self.input = input
113
+ self.output = output
114
+ self.errors = dict(errors or {})
115
+ if name is not None:
116
+ self.name = name
117
+
118
+ def __set_name__(self, owner: type, name: str) -> None:
119
+ if not hasattr(self, "name"):
120
+ self.name = name
121
+
122
+ def encode(self, value: _I) -> object:
123
+ """Validates ``value`` against the input type and returns its JSON form."""
124
+ if self.input is None:
125
+ return NOT_GIVEN if value is None else value
126
+ adapter = type_adapter(self.input)
127
+ return adapter.dump_python(adapter.validate_python(value), mode="json", by_alias=True)
128
+
129
+ def decode(self, rpc_id: str, value: object) -> _O:
130
+ """Validates a method's output."""
131
+ if self.output is None:
132
+ return cast("_O", None)
133
+ try:
134
+ return cast("_O", type_adapter(self.output).validate_python(value))
135
+ except pydantic.ValidationError as exc:
136
+ raise RpcCallInternalError(rpc_id, self.name, "rpc.invalid_output", validation_summary(exc), value) from exc
137
+
138
+ def error(self, rpc_id: str, exc: RpcError | RpcInternalError) -> RpcCallError:
139
+ """Maps a transport error to :class:`RpcCallError`, decoding declared error data."""
140
+ body = exc.error
141
+ data: object = body.data
142
+ if body.type in self.errors and data is not None:
143
+ try:
144
+ data = type_adapter(self.errors[body.type]).validate_python(data)
145
+ except pydantic.ValidationError:
146
+ data = body.data
147
+ cls = RpcCallInternalError if isinstance(exc, RpcInternalError) else RpcCallError
148
+ return cls(rpc_id, self.name, body.type, body.message, data)
149
+
150
+
151
+ class RpcEventDefinition(Generic[_T]):
152
+ """One event of an :class:`RpcDefinition`. Create it with :func:`rpc_event`."""
153
+
154
+ name: str
155
+
156
+ def __init__(self, data: Any, *, name: str | None) -> None:
157
+ self.data = data
158
+ if name is not None:
159
+ self.name = name
160
+
161
+ def __set_name__(self, owner: type, name: str) -> None:
162
+ if not hasattr(self, "name"):
163
+ self.name = name
164
+
165
+ def parse(self, event: RpcEvent) -> RpcEventPayload[_T]:
166
+ """Validates the data of a raw RPC event of this kind.
167
+
168
+ Raises:
169
+ pydantic.ValidationError: The data does not match the declared type.
170
+ """
171
+ data = cast("_T", type_adapter(self.data).validate_python(event.data))
172
+ return RpcEventPayload(name=self.name, data=data, event=event)
173
+
174
+
175
+ @overload
176
+ def rpc_method(
177
+ input: None, output: type[_O], *, name: str | None = None, errors: Mapping[str, Any] | None = None
178
+ ) -> RpcMethod[None, _O]: ...
179
+ @overload
180
+ def rpc_method(
181
+ input: type[_I], output: type[_O], *, name: str | None = None, errors: Mapping[str, Any] | None = None
182
+ ) -> RpcMethod[_I, _O]: ...
183
+ def rpc_method(
184
+ input: Any, output: Any, *, name: str | None = None, errors: Mapping[str, Any] | None = None
185
+ ) -> RpcMethod[Any, Any]:
186
+ """Declares an RPC method on an :class:`RpcDefinition`.
187
+
188
+ Args:
189
+ input: The input type, or ``None`` for a method without input.
190
+ output: The output type. Use ``type(None)`` for a method that returns nothing.
191
+ name: The method name on the server. Defaults to the attribute name.
192
+ errors: Data types of the errors the method declares, by error type. Matching error data is validated
193
+ into these types on :attr:`RpcCallError.data`.
194
+ """
195
+ return RpcMethod(input, output, name=name, errors=errors)
196
+
197
+
198
+ def rpc_event(data: type[_T], *, name: str | None = None) -> RpcEventDefinition[_T]:
199
+ """Declares an RPC event on an :class:`RpcDefinition`.
200
+
201
+ Args:
202
+ data: The type of the event's ``data``.
203
+ name: The event name on the server. Defaults to the attribute name.
204
+ """
205
+ return RpcEventDefinition(data, name=name)
206
+
207
+
208
+ class RpcDefinition:
209
+ """Base class for plugin RPC declarations. Pass the RPC id as a class keyword::
210
+
211
+ class Counter(RpcDefinition, id="counter"):
212
+ add = rpc_method(AddInput, Total)
213
+ changed = rpc_event(Total)
214
+
215
+ The class is only a declaration; bind it to a client with ``client.rpc(Counter)``.
216
+ """
217
+
218
+ id: ClassVar[str]
219
+ methods: ClassVar[dict[str, RpcMethod[Any, Any]]]
220
+ events: ClassVar[dict[str, RpcEventDefinition[Any]]]
221
+
222
+ def __init_subclass__(cls, *, id: str | None = None, **kwargs: Any) -> None:
223
+ super().__init_subclass__(**kwargs)
224
+ if id is not None:
225
+ cls.id = id
226
+ elif not hasattr(cls, "id"):
227
+ raise TypeError(f"{cls.__name__} needs an RPC id: class {cls.__name__}(RpcDefinition, id='...')")
228
+ methods: dict[str, RpcMethod[Any, Any]] = {}
229
+ events: dict[str, RpcEventDefinition[Any]] = {}
230
+ for base in reversed(cls.__mro__):
231
+ for value in vars(base).values():
232
+ if isinstance(value, RpcMethod):
233
+ methods[value.name] = value
234
+ elif isinstance(value, RpcEventDefinition):
235
+ events[value.name] = value
236
+ cls.methods = methods
237
+ cls.events = events
238
+
239
+ @classmethod
240
+ def event_type(cls, event: RpcEventDefinition[Any] | str) -> str:
241
+ """The wire ``type`` of an event of this RPC: ``rpc.<id>.<name>``."""
242
+ return f"rpc.{cls.id}.{event if isinstance(event, str) else event.name}"
243
+
244
+
245
+ @dataclass(frozen=True)
246
+ class RpcEventPayload(Generic[_T]):
247
+ """A plugin RPC event with validated data.
248
+
249
+ Attributes:
250
+ name: The event name, without the ``rpc.<id>.`` prefix.
251
+ data: The validated event data.
252
+ event: The raw event, with its ``id``, ``created`` time and ``location``.
253
+ """
254
+
255
+ name: str
256
+ data: _T
257
+ event: RpcEvent
258
+
259
+
260
+ def _check_method(definition: type[RpcDefinition], method: RpcMethod[Any, Any]) -> None:
261
+ if not any(known is method for known in definition.methods.values()):
262
+ raise ValueError(f"{getattr(method, 'name', method)!r} is not a method of {definition.__name__}")
263
+
264
+
265
+ def _check_event(definition: type[RpcDefinition], event: RpcEventDefinition[Any]) -> None:
266
+ if not any(known is event for known in definition.events.values()):
267
+ raise ValueError(f"{getattr(event, 'name', event)!r} is not an event of {definition.__name__}")
268
+
269
+
270
+ class RpcClient(Generic[_D]):
271
+ """A plugin RPC bound to a synchronous client. Get one with ``client.rpc(Definition)``."""
272
+
273
+ def __init__(self, client: Opencode, definition: type[_D]) -> None:
274
+ self._client = client
275
+ self.definition = definition
276
+
277
+ @overload
278
+ def call(
279
+ self,
280
+ method: RpcMethod[None, _O],
281
+ input: None = None,
282
+ *,
283
+ location: Location | NotGiven | None = NOT_GIVEN,
284
+ extra_headers: Headers | None = None,
285
+ timeout: Timeout | NotGiven = NOT_GIVEN,
286
+ ) -> _O: ...
287
+ @overload
288
+ def call(
289
+ self,
290
+ method: RpcMethod[_I, _O],
291
+ input: _I,
292
+ *,
293
+ location: Location | NotGiven | None = NOT_GIVEN,
294
+ extra_headers: Headers | None = None,
295
+ timeout: Timeout | NotGiven = NOT_GIVEN,
296
+ ) -> _O: ...
297
+ def call(
298
+ self,
299
+ method: RpcMethod[Any, Any],
300
+ input: Any = None,
301
+ *,
302
+ location: Location | NotGiven | None = NOT_GIVEN,
303
+ extra_headers: Headers | None = None,
304
+ timeout: Timeout | NotGiven = NOT_GIVEN,
305
+ ) -> Any:
306
+ """Calls a method of this RPC and returns its validated output.
307
+
308
+ Raises:
309
+ RpcCallError: The method failed.
310
+ RpcCallInternalError: The method crashed or returned invalid output.
311
+ """
312
+ _check_method(self.definition, method)
313
+ rpc_id = self.definition.id
314
+ try:
315
+ result = self._client.rpc.call(
316
+ rpc_id,
317
+ method.name,
318
+ input=method.encode(input),
319
+ location=location,
320
+ extra_headers=extra_headers,
321
+ timeout=timeout,
322
+ )
323
+ except (RpcError, RpcInternalError) as exc:
324
+ raise method.error(rpc_id, exc) from exc
325
+ return method.decode(rpc_id, result.output)
326
+
327
+ def events(self) -> Subscription[RpcEvent]:
328
+ """Subscribes to every event of this RPC, as raw events, on the client's shared event stream."""
329
+ from .types import RpcEvent
330
+
331
+ prefix = f"rpc.{self.definition.id}."
332
+ return self._client.events.of_type(RpcEvent).where(lambda event: event.type.startswith(prefix))
333
+
334
+ def subscribe(self, event: RpcEventDefinition[_T]) -> RpcEvents[_T]:
335
+ """Subscribes to one event of this RPC, with validated data, on the client's shared event stream."""
336
+ from .types import RpcEvent
337
+
338
+ _check_event(self.definition, event)
339
+ wire = self.definition.event_type(event)
340
+ return RpcEvents(self._client.events.of_type(RpcEvent).where(lambda raw: raw.type == wire), event)
341
+
342
+
343
+ class AsyncRpcClient(Generic[_D]):
344
+ """A plugin RPC bound to an asynchronous client. Get one with ``client.rpc(Definition)``."""
345
+
346
+ def __init__(self, client: AsyncOpencode, definition: type[_D]) -> None:
347
+ self._client = client
348
+ self.definition = definition
349
+
350
+ @overload
351
+ async def call(
352
+ self,
353
+ method: RpcMethod[None, _O],
354
+ input: None = None,
355
+ *,
356
+ location: Location | NotGiven | None = NOT_GIVEN,
357
+ extra_headers: Headers | None = None,
358
+ timeout: Timeout | NotGiven = NOT_GIVEN,
359
+ ) -> _O: ...
360
+ @overload
361
+ async def call(
362
+ self,
363
+ method: RpcMethod[_I, _O],
364
+ input: _I,
365
+ *,
366
+ location: Location | NotGiven | None = NOT_GIVEN,
367
+ extra_headers: Headers | None = None,
368
+ timeout: Timeout | NotGiven = NOT_GIVEN,
369
+ ) -> _O: ...
370
+ async def call(
371
+ self,
372
+ method: RpcMethod[Any, Any],
373
+ input: Any = None,
374
+ *,
375
+ location: Location | NotGiven | None = NOT_GIVEN,
376
+ extra_headers: Headers | None = None,
377
+ timeout: Timeout | NotGiven = NOT_GIVEN,
378
+ ) -> Any:
379
+ """Calls a method of this RPC and returns its validated output.
380
+
381
+ Raises:
382
+ RpcCallError: The method failed.
383
+ RpcCallInternalError: The method crashed or returned invalid output.
384
+ """
385
+ _check_method(self.definition, method)
386
+ rpc_id = self.definition.id
387
+ try:
388
+ result = await self._client.rpc.call(
389
+ rpc_id,
390
+ method.name,
391
+ input=method.encode(input),
392
+ location=location,
393
+ extra_headers=extra_headers,
394
+ timeout=timeout,
395
+ )
396
+ except (RpcError, RpcInternalError) as exc:
397
+ raise method.error(rpc_id, exc) from exc
398
+ return method.decode(rpc_id, result.output)
399
+
400
+ def events(self) -> AsyncSubscription[RpcEvent]:
401
+ """Subscribes to every event of this RPC, as raw events, on the client's shared event stream."""
402
+ from .types import RpcEvent
403
+
404
+ prefix = f"rpc.{self.definition.id}."
405
+ return self._client.events.of_type(RpcEvent).where(lambda event: event.type.startswith(prefix))
406
+
407
+ def subscribe(self, event: RpcEventDefinition[_T]) -> AsyncRpcEvents[_T]:
408
+ """Subscribes to one event of this RPC, with validated data, on the client's shared event stream."""
409
+ from .types import RpcEvent
410
+
411
+ _check_event(self.definition, event)
412
+ wire = self.definition.event_type(event)
413
+ return AsyncRpcEvents(self._client.events.of_type(RpcEvent).where(lambda raw: raw.type == wire), event)
414
+
415
+
416
+ class RpcEvents(Generic[_T]):
417
+ """Events of one kind from a plugin RPC. Iterate with ``for``; close it, or use ``with``, to leave."""
418
+
419
+ def __init__(self, source: Subscription[RpcEvent], event: RpcEventDefinition[_T]) -> None:
420
+ self._source = source
421
+ self._event = event
422
+
423
+ def __iter__(self) -> Self:
424
+ return self
425
+
426
+ def __next__(self) -> RpcEventPayload[_T]:
427
+ return self._event.parse(next(self._source))
428
+
429
+ def get(self, timeout: float | None = None) -> RpcEventPayload[_T]:
430
+ """Returns the next event, waiting up to ``timeout`` seconds. See :meth:`Subscription.get`."""
431
+ return self._event.parse(self._source.get(timeout))
432
+
433
+ def close(self) -> None:
434
+ """Leaves the shared event stream."""
435
+ self._source.close()
436
+
437
+ def __enter__(self) -> Self:
438
+ return self
439
+
440
+ def __exit__(
441
+ self, exc_type: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None
442
+ ) -> None:
443
+ self.close()
444
+
445
+
446
+ class AsyncRpcEvents(Generic[_T]):
447
+ """Events of one kind from a plugin RPC. Iterate with ``async for``; close it, or use ``async with``."""
448
+
449
+ def __init__(self, source: AsyncSubscription[RpcEvent], event: RpcEventDefinition[_T]) -> None:
450
+ self._source = source
451
+ self._event = event
452
+
453
+ def __aiter__(self) -> Self:
454
+ return self
455
+
456
+ async def __anext__(self) -> RpcEventPayload[_T]:
457
+ return self._event.parse(await self._source.__anext__())
458
+
459
+ async def get(self) -> RpcEventPayload[_T]:
460
+ """Returns the next event. See :meth:`AsyncSubscription.get`."""
461
+ return self._event.parse(await self._source.get())
462
+
463
+ async def close(self) -> None:
464
+ """Leaves the shared event stream."""
465
+ await self._source.close()
466
+
467
+ async def __aenter__(self) -> Self:
468
+ return self
469
+
470
+ async def __aexit__(
471
+ self, exc_type: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None
472
+ ) -> None:
473
+ await self.close()
474
+
475
+
476
+ class TypedRpcResource(RpcResource):
477
+ """``client.rpc``: the generated ``call`` plus ``client.rpc(Definition)`` for typed access."""
478
+
479
+ def __call__(self, definition: type[_D]) -> RpcClient[_D]:
480
+ """Binds a plugin RPC declaration to this client."""
481
+ return RpcClient(self._client, definition)
482
+
483
+
484
+ class AsyncTypedRpcResource(AsyncRpcResource):
485
+ """``client.rpc``: the generated ``call`` plus ``client.rpc(Definition)`` for typed access."""
486
+
487
+ def __call__(self, definition: type[_D]) -> AsyncRpcClient[_D]:
488
+ """Binds a plugin RPC declaration to this client."""
489
+ return AsyncRpcClient(self._client, definition)