opencode-python-sdk 0.1.0__tar.gz → 0.1.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.
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/.gitignore +4 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/PKG-INFO +28 -2
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/README.md +24 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/pyproject.toml +20 -5
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/__init__.py +4 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/_exceptions.py +22 -0
- opencode_python_sdk-0.1.2/src/opencode/_pty.py +586 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/_server.py +8 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/agent.py +14 -14
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/command.py +6 -6
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/config.py +18 -18
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/credential.py +22 -22
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/debug.py +10 -8
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/event.py +4 -4
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/experimental.py +77 -74
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/file.py +28 -28
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/form.py +6 -6
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/generate.py +8 -8
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/integration.py +102 -94
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/location.py +12 -12
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/mcp.py +44 -42
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/message.py +10 -10
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/migration.py +6 -4
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/model.py +16 -16
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/permission.py +68 -64
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/plugin.py +20 -20
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/project.py +14 -14
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/provider.py +18 -18
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/pty.py +51 -48
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/reference.py +6 -6
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/rpc.py +10 -10
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/server.py +12 -12
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/session.py +378 -366
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/shell.py +34 -34
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/skill.py +6 -6
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/vcs.py +48 -46
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/voice.py +8 -8
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/websearch.py +16 -16
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/worktree.py +30 -30
- opencode_python_sdk-0.1.2/tests/test_docs_coverage.py +256 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/tests/test_live.py +16 -0
- opencode_python_sdk-0.1.2/tests/test_pty.py +236 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/CHANGELOG.md +0 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/LICENSE +0 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/api.md +0 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/.generated.json +0 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/_base_client.py +0 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/_client.py +0 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/_constants.py +0 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/_errors.py +0 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/_models.py +0 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/_pagination.py +0 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/_qs.py +0 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/_resource.py +0 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/_streaming.py +0 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/_types.py +0 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/_unions.py +0 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/_version.py +0 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/py.typed +0 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/resources/__init__.py +0 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/types/__init__.py +0 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/src/opencode/types/_generated.py +0 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/tests/__init__.py +0 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/tests/conftest.py +0 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/tests/test_async_client.py +0 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/tests/test_client.py +0 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/tests/test_encoding.py +0 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/tests/test_models.py +0 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/tests/test_pagination.py +0 -0
- {opencode_python_sdk-0.1.0 → opencode_python_sdk-0.1.2}/tests/test_streaming.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: opencode-python-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.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
|
-
Project-URL: Documentation, https://github.
|
|
6
|
+
Project-URL: Documentation, https://shrijayan.github.io/opencode-python-sdk/
|
|
7
7
|
Project-URL: Repository, https://github.com/shrijayan/opencode-python-sdk
|
|
8
8
|
Project-URL: Issues, https://github.com/shrijayan/opencode-python-sdk/issues
|
|
9
9
|
Project-URL: Changelog, https://github.com/shrijayan/opencode-python-sdk/blob/main/CHANGELOG.md
|
|
@@ -31,16 +31,21 @@ Requires-Dist: anyio<5,>=4.4
|
|
|
31
31
|
Requires-Dist: httpx<1,>=0.27
|
|
32
32
|
Requires-Dist: pydantic<3,>=2.12
|
|
33
33
|
Requires-Dist: typing-extensions>=4.12
|
|
34
|
+
Provides-Extra: pty
|
|
35
|
+
Requires-Dist: websockets<17,>=14; extra == 'pty'
|
|
34
36
|
Description-Content-Type: text/markdown
|
|
35
37
|
|
|
36
38
|
# opencode-python-sdk
|
|
37
39
|
|
|
38
40
|
[](https://github.com/shrijayan/opencode-python-sdk/actions/workflows/ci.yml)
|
|
39
41
|
[](https://pypi.org/project/opencode-python-sdk/)
|
|
42
|
+
[](https://shrijayan.github.io/opencode-python-sdk/)
|
|
40
43
|
[](https://pypi.org/project/opencode-python-sdk/)
|
|
41
44
|
|
|
42
45
|
Typed Python client for the [OpenCode](https://github.com/anomalyco/opencode) 2.x server API.
|
|
43
46
|
|
|
47
|
+
**Documentation: <https://shrijayan.github.io/opencode-python-sdk/>**
|
|
48
|
+
|
|
44
49
|
> **Unofficial, community-maintained.** This package is not affiliated with or endorsed by the OpenCode team.
|
|
45
50
|
> If the OpenCode maintainers want to take it over, we are happy to transfer this repository and the `opencode-python-sdk`
|
|
46
51
|
> name on PyPI. Open an issue and we will hand it over.
|
|
@@ -201,6 +206,27 @@ Events are typed (`SessionCreatedEvent`, `SessionTextDeltaEvent`, and so on). Ev
|
|
|
201
206
|
not know arrive as `UnknownEvent` with all their fields kept. Streams apply only the connect timeout by default, since
|
|
202
207
|
they stay open between events.
|
|
203
208
|
|
|
209
|
+
## Terminals
|
|
210
|
+
|
|
211
|
+
`client.pty.attach()` opens an interactive connection to a PTY over a WebSocket. It needs the `pty` extra:
|
|
212
|
+
`pip install "opencode-python-sdk[pty]"`.
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
pty = client.pty.create(command="/bin/sh").data
|
|
216
|
+
with client.pty.attach(pty.id) as term:
|
|
217
|
+
term.resize(120, 40)
|
|
218
|
+
term.send("echo hi\n")
|
|
219
|
+
for chunk in term: # output bytes, until the shell exits
|
|
220
|
+
print(chunk.decode(errors="replace"), end="")
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
`recv(timeout=...)` reads one chunk and returns `None` once the terminal has ended. `term.cursor` holds the output
|
|
224
|
+
position to pass as `cursor=` when reattaching; `cursor=-1` skips the replay of earlier output. Persistent terminals
|
|
225
|
+
work the same way through `client.experimental.persistent_pty.attach(pty_id, role=..., takeover=...)`. The async
|
|
226
|
+
client returns `AsyncPtyConnection` (asyncio only): `async with await client.pty.attach(...) as term`. A connection
|
|
227
|
+
the server refuses or closes abnormally raises `PtyConnectionError`. See
|
|
228
|
+
[`examples/terminal.py`](examples/terminal.py) for a full TTY bridge.
|
|
229
|
+
|
|
204
230
|
## Errors
|
|
205
231
|
|
|
206
232
|
Every error derives from `opencode.OpencodeError`. Errors the server declares are raised as their own class, with a
|
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/shrijayan/opencode-python-sdk/actions/workflows/ci.yml)
|
|
4
4
|
[](https://pypi.org/project/opencode-python-sdk/)
|
|
5
|
+
[](https://shrijayan.github.io/opencode-python-sdk/)
|
|
5
6
|
[](https://pypi.org/project/opencode-python-sdk/)
|
|
6
7
|
|
|
7
8
|
Typed Python client for the [OpenCode](https://github.com/anomalyco/opencode) 2.x server API.
|
|
8
9
|
|
|
10
|
+
**Documentation: <https://shrijayan.github.io/opencode-python-sdk/>**
|
|
11
|
+
|
|
9
12
|
> **Unofficial, community-maintained.** This package is not affiliated with or endorsed by the OpenCode team.
|
|
10
13
|
> If the OpenCode maintainers want to take it over, we are happy to transfer this repository and the `opencode-python-sdk`
|
|
11
14
|
> name on PyPI. Open an issue and we will hand it over.
|
|
@@ -166,6 +169,27 @@ Events are typed (`SessionCreatedEvent`, `SessionTextDeltaEvent`, and so on). Ev
|
|
|
166
169
|
not know arrive as `UnknownEvent` with all their fields kept. Streams apply only the connect timeout by default, since
|
|
167
170
|
they stay open between events.
|
|
168
171
|
|
|
172
|
+
## Terminals
|
|
173
|
+
|
|
174
|
+
`client.pty.attach()` opens an interactive connection to a PTY over a WebSocket. It needs the `pty` extra:
|
|
175
|
+
`pip install "opencode-python-sdk[pty]"`.
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
pty = client.pty.create(command="/bin/sh").data
|
|
179
|
+
with client.pty.attach(pty.id) as term:
|
|
180
|
+
term.resize(120, 40)
|
|
181
|
+
term.send("echo hi\n")
|
|
182
|
+
for chunk in term: # output bytes, until the shell exits
|
|
183
|
+
print(chunk.decode(errors="replace"), end="")
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
`recv(timeout=...)` reads one chunk and returns `None` once the terminal has ended. `term.cursor` holds the output
|
|
187
|
+
position to pass as `cursor=` when reattaching; `cursor=-1` skips the replay of earlier output. Persistent terminals
|
|
188
|
+
work the same way through `client.experimental.persistent_pty.attach(pty_id, role=..., takeover=...)`. The async
|
|
189
|
+
client returns `AsyncPtyConnection` (asyncio only): `async with await client.pty.attach(...) as term`. A connection
|
|
190
|
+
the server refuses or closes abnormally raises `PtyConnectionError`. See
|
|
191
|
+
[`examples/terminal.py`](examples/terminal.py) for a full TTY bridge.
|
|
192
|
+
|
|
169
193
|
## Errors
|
|
170
194
|
|
|
171
195
|
Every error derives from `opencode.OpencodeError`. Errors the server declares are raised as their own class, with a
|
|
@@ -34,11 +34,16 @@ dependencies = [
|
|
|
34
34
|
|
|
35
35
|
[project.urls]
|
|
36
36
|
Homepage = "https://github.com/shrijayan/opencode-python-sdk"
|
|
37
|
-
Documentation = "https://github.
|
|
37
|
+
Documentation = "https://shrijayan.github.io/opencode-python-sdk/"
|
|
38
38
|
Repository = "https://github.com/shrijayan/opencode-python-sdk"
|
|
39
39
|
Issues = "https://github.com/shrijayan/opencode-python-sdk/issues"
|
|
40
40
|
Changelog = "https://github.com/shrijayan/opencode-python-sdk/blob/main/CHANGELOG.md"
|
|
41
41
|
|
|
42
|
+
[project.optional-dependencies]
|
|
43
|
+
pty = [
|
|
44
|
+
"websockets>=14,<17",
|
|
45
|
+
]
|
|
46
|
+
|
|
42
47
|
[dependency-groups]
|
|
43
48
|
dev = [
|
|
44
49
|
"mypy>=1.18",
|
|
@@ -48,6 +53,11 @@ dev = [
|
|
|
48
53
|
"respx>=0.22",
|
|
49
54
|
"ruff>=0.14",
|
|
50
55
|
"trio>=0.31",
|
|
56
|
+
"websockets>=14",
|
|
57
|
+
]
|
|
58
|
+
docs = [
|
|
59
|
+
"mkdocs-material[imaging]>=9.6",
|
|
60
|
+
"mkdocstrings[python]>=0.30",
|
|
51
61
|
]
|
|
52
62
|
|
|
53
63
|
[build-system]
|
|
@@ -93,6 +103,7 @@ src = ["src", "tests", "codegen"]
|
|
|
93
103
|
select = [
|
|
94
104
|
"B", # bugbear
|
|
95
105
|
"C4", # comprehensions
|
|
106
|
+
"D1", # missing docstrings (public modules only; tests/test_docs_coverage.py covers the private runtime modules)
|
|
96
107
|
"E", # pycodestyle errors
|
|
97
108
|
"F", # pyflakes
|
|
98
109
|
"FA", # future annotations
|
|
@@ -107,6 +118,8 @@ select = [
|
|
|
107
118
|
"W", # pycodestyle warnings
|
|
108
119
|
]
|
|
109
120
|
ignore = [
|
|
121
|
+
"D105", # magic methods
|
|
122
|
+
"D107", # __init__: arguments are documented on the class
|
|
110
123
|
"E501", # line length is handled by the formatter
|
|
111
124
|
"RUF022", # __all__ keeps a readable, grouped order
|
|
112
125
|
]
|
|
@@ -116,10 +129,12 @@ ignore = [
|
|
|
116
129
|
"src/opencode/types/**" = ["E741", "TC"]
|
|
117
130
|
"src/opencode/resources/**" = ["TC"]
|
|
118
131
|
"src/opencode/_errors.py" = ["TC"]
|
|
119
|
-
|
|
120
|
-
"
|
|
121
|
-
"
|
|
122
|
-
"
|
|
132
|
+
# D1 applies to the published package only.
|
|
133
|
+
"codegen/**" = ["T20", "D1"]
|
|
134
|
+
"scripts/**" = ["T20", "D1"]
|
|
135
|
+
"examples/**" = ["T20", "D1"]
|
|
136
|
+
"tests/**" = ["E501", "TC", "D1"]
|
|
137
|
+
"docs/**" = ["D1"]
|
|
123
138
|
|
|
124
139
|
[tool.ruff.lint.isort]
|
|
125
140
|
known-first-party = ["opencode", "codegen"]
|
|
@@ -75,6 +75,7 @@ from ._exceptions import (
|
|
|
75
75
|
)
|
|
76
76
|
from ._models import BaseModel
|
|
77
77
|
from ._pagination import AsyncCursorPage, AsyncPaginator, SyncCursorPage
|
|
78
|
+
from ._pty import AsyncPtyConnection, PtyConnection, PtyConnectionError
|
|
78
79
|
from ._server import AsyncOpencodeServer, OpencodeServer, ServerStartError
|
|
79
80
|
from ._streaming import AsyncStream, Stream
|
|
80
81
|
from ._types import NOT_GIVEN, NotGiven
|
|
@@ -97,6 +98,7 @@ __all__ = [
|
|
|
97
98
|
"AsyncOpencode",
|
|
98
99
|
"AsyncOpencodeServer",
|
|
99
100
|
"AsyncPaginator",
|
|
101
|
+
"AsyncPtyConnection",
|
|
100
102
|
"AsyncStream",
|
|
101
103
|
"AuthenticationError",
|
|
102
104
|
"BadRequestError",
|
|
@@ -111,6 +113,8 @@ __all__ = [
|
|
|
111
113
|
"OpencodeError",
|
|
112
114
|
"OpencodeServer",
|
|
113
115
|
"PermissionDeniedError",
|
|
116
|
+
"PtyConnection",
|
|
117
|
+
"PtyConnectionError",
|
|
114
118
|
"RateLimitError",
|
|
115
119
|
"ServerStartError",
|
|
116
120
|
"ServiceUnavailableError",
|
|
@@ -147,47 +147,69 @@ class APIStatusError(APIError):
|
|
|
147
147
|
|
|
148
148
|
|
|
149
149
|
class BadRequestError(APIStatusError):
|
|
150
|
+
"""The server rejected the request as malformed (400)."""
|
|
151
|
+
|
|
150
152
|
status = 400
|
|
151
153
|
|
|
152
154
|
|
|
153
155
|
class AuthenticationError(APIStatusError):
|
|
156
|
+
"""The password was missing or wrong (401)."""
|
|
157
|
+
|
|
154
158
|
status = 401
|
|
155
159
|
|
|
156
160
|
|
|
157
161
|
class PermissionDeniedError(APIStatusError):
|
|
162
|
+
"""The request is not allowed (403)."""
|
|
163
|
+
|
|
158
164
|
status = 403
|
|
159
165
|
|
|
160
166
|
|
|
161
167
|
class NotFoundError(APIStatusError):
|
|
168
|
+
"""The requested resource does not exist (404)."""
|
|
169
|
+
|
|
162
170
|
status = 404
|
|
163
171
|
|
|
164
172
|
|
|
165
173
|
class ConflictError(APIStatusError):
|
|
174
|
+
"""The request conflicts with the current state, for example a busy session (409)."""
|
|
175
|
+
|
|
166
176
|
status = 409
|
|
167
177
|
tag = "ConflictError"
|
|
168
178
|
|
|
169
179
|
|
|
170
180
|
class ContentTooLargeError(APIStatusError):
|
|
181
|
+
"""The request body is too large (413)."""
|
|
182
|
+
|
|
171
183
|
status = 413
|
|
172
184
|
|
|
173
185
|
|
|
174
186
|
class UnprocessableEntityError(APIStatusError):
|
|
187
|
+
"""The request was well formed but could not be processed (422)."""
|
|
188
|
+
|
|
175
189
|
status = 422
|
|
176
190
|
|
|
177
191
|
|
|
178
192
|
class RateLimitError(APIStatusError):
|
|
193
|
+
"""Too many requests (429). Retried automatically, honoring ``Retry-After``."""
|
|
194
|
+
|
|
179
195
|
status = 429
|
|
180
196
|
|
|
181
197
|
|
|
182
198
|
class InternalServerError(APIStatusError):
|
|
199
|
+
"""The server failed (500, and the base class for every 5xx)."""
|
|
200
|
+
|
|
183
201
|
status = 500
|
|
184
202
|
|
|
185
203
|
|
|
186
204
|
class NotImplementedServerError(InternalServerError):
|
|
205
|
+
"""The server does not implement this endpoint (501)."""
|
|
206
|
+
|
|
187
207
|
status = 501
|
|
188
208
|
|
|
189
209
|
|
|
190
210
|
class ServiceUnavailableError(InternalServerError):
|
|
211
|
+
"""The server is temporarily unavailable (503). Retried automatically."""
|
|
212
|
+
|
|
191
213
|
status = 503
|
|
192
214
|
tag = "ServiceUnavailableError"
|
|
193
215
|
|