opencode-py 0.1.1__tar.gz → 0.2.0__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.
Files changed (61) hide show
  1. opencode_py-0.2.0/.editorconfig +18 -0
  2. opencode_py-0.2.0/.gitattributes +36 -0
  3. opencode_py-0.2.0/.github/ISSUE_TEMPLATE/bug_report.yml +29 -0
  4. opencode_py-0.2.0/.github/ISSUE_TEMPLATE/feature_request.yml +26 -0
  5. opencode_py-0.2.0/.github/workflows/test.yml +62 -0
  6. opencode_py-0.2.0/.pre-commit-config.yaml +15 -0
  7. {opencode_py-0.1.1 → opencode_py-0.2.0}/AGENTS.md +60 -12
  8. opencode_py-0.2.0/CHANGELOG.md +22 -0
  9. opencode_py-0.2.0/CODE_OF_CONDUCT.md +63 -0
  10. opencode_py-0.2.0/CONTRIBUTING.md +44 -0
  11. opencode_py-0.2.0/LICENSE +21 -0
  12. {opencode_py-0.1.1 → opencode_py-0.2.0}/PKG-INFO +48 -3
  13. {opencode_py-0.1.1 → opencode_py-0.2.0}/README.md +39 -2
  14. {opencode_py-0.1.1 → opencode_py-0.2.0}/README.ru.md +10 -0
  15. opencode_py-0.2.0/RELEASE.md +52 -0
  16. opencode_py-0.2.0/SECURITY.md +15 -0
  17. opencode_py-0.2.0/demo.py +114 -0
  18. {opencode_py-0.1.1 → opencode_py-0.2.0}/live.py +1 -1
  19. {opencode_py-0.1.1 → opencode_py-0.2.0}/live_async.py +4 -2
  20. {opencode_py-0.1.1 → opencode_py-0.2.0}/live_streaming.py +3 -3
  21. {opencode_py-0.1.1 → opencode_py-0.2.0}/pyproject.toml +30 -1
  22. opencode_py-0.2.0/scripts/check-release.py +95 -0
  23. {opencode_py-0.1.1 → opencode_py-0.2.0}/scripts/check-upstream.py +6 -4
  24. opencode_py-0.2.0/src/opencode/__init__.py +82 -0
  25. opencode_py-0.2.0/src/opencode/_async_client.py +944 -0
  26. {opencode_py-0.1.1 → opencode_py-0.2.0}/src/opencode/_async_opencode.py +23 -23
  27. opencode_py-0.2.0/src/opencode/_async_session.py +171 -0
  28. {opencode_py-0.1.1 → opencode_py-0.2.0}/src/opencode/_binary.py +9 -8
  29. opencode_py-0.2.0/src/opencode/_client.py +906 -0
  30. opencode_py-0.2.0/src/opencode/_errors.py +94 -0
  31. opencode_py-0.2.0/src/opencode/_logs.py +26 -0
  32. {opencode_py-0.1.1 → opencode_py-0.2.0}/src/opencode/_models.py +22 -20
  33. {opencode_py-0.1.1 → opencode_py-0.2.0}/src/opencode/_opencode.py +30 -29
  34. {opencode_py-0.1.1 → opencode_py-0.2.0}/src/opencode/_process.py +1 -1
  35. opencode_py-0.2.0/src/opencode/_response_models.py +242 -0
  36. {opencode_py-0.1.1 → opencode_py-0.2.0}/src/opencode/_server.py +8 -9
  37. {opencode_py-0.1.1 → opencode_py-0.2.0}/src/opencode/_session.py +64 -57
  38. {opencode_py-0.1.1 → opencode_py-0.2.0}/src/opencode/_tools.py +31 -25
  39. opencode_py-0.2.0/src/opencode/_types.py +28 -0
  40. opencode_py-0.2.0/src/opencode/py.typed +1 -0
  41. opencode_py-0.2.0/test_all.py +68 -0
  42. {opencode_py-0.1.1 → opencode_py-0.2.0}/test_live.py +16 -6
  43. opencode_py-0.2.0/tests/docker/Dockerfile +11 -0
  44. opencode_py-0.2.0/tests/docker/test_smoke.py +48 -0
  45. {opencode_py-0.1.1 → opencode_py-0.2.0}/tests/test_async_client.py +29 -13
  46. {opencode_py-0.1.1 → opencode_py-0.2.0}/tests/test_client.py +12 -16
  47. {opencode_py-0.1.1 → opencode_py-0.2.0}/tests/test_opencode.py +28 -13
  48. {opencode_py-0.1.1 → opencode_py-0.2.0}/web/server.py +7 -7
  49. opencode_py-0.1.1/.github/workflows/test.yml +0 -30
  50. opencode_py-0.1.1/demo.py +0 -94
  51. opencode_py-0.1.1/src/opencode/__init__.py +0 -45
  52. opencode_py-0.1.1/src/opencode/_async_client.py +0 -538
  53. opencode_py-0.1.1/src/opencode/_async_session.py +0 -155
  54. opencode_py-0.1.1/src/opencode/_client.py +0 -532
  55. opencode_py-0.1.1/src/opencode/_errors.py +0 -20
  56. opencode_py-0.1.1/test_all.py +0 -56
  57. {opencode_py-0.1.1 → opencode_py-0.2.0}/.github/workflows/publish.yml +0 -0
  58. {opencode_py-0.1.1 → opencode_py-0.2.0}/.gitignore +0 -0
  59. {opencode_py-0.1.1 → opencode_py-0.2.0}/docs/opencode-docs-ru.md +0 -0
  60. {opencode_py-0.1.1 → opencode_py-0.2.0}/src/opencode/__main__.py +0 -0
  61. {opencode_py-0.1.1 → opencode_py-0.2.0}/web/index.html +0 -0
@@ -0,0 +1,18 @@
1
+ root = true
2
+
3
+ [*]
4
+ end_of_line = lf
5
+ insert_final_newline = true
6
+ trim_trailing_whitespace = true
7
+ charset = utf-8
8
+ indent_style = space
9
+ indent_size = 4
10
+
11
+ [*.md]
12
+ trim_trailing_whitespace = false
13
+
14
+ [*.{yml,yaml}]
15
+ indent_size = 2
16
+
17
+ [Makefile]
18
+ indent_style = tab
@@ -0,0 +1,36 @@
1
+ * text=auto eol=lf
2
+
3
+ # Python
4
+ *.py text
5
+ *.pyi text
6
+ *.pyw text
7
+
8
+ # Markdown
9
+ *.md text
10
+ *.mdx text
11
+
12
+ # Config
13
+ *.toml text
14
+ *.ini text
15
+ *.cfg text
16
+ *.yml text
17
+ *.yaml text
18
+ *.json text
19
+
20
+ # Git
21
+ .gitattributes text
22
+ .gitignore text
23
+
24
+ # Batch scripts (Windows needs CRLF)
25
+ *.bat text eol=crlf
26
+ *.cmd text eol=crlf
27
+
28
+ # Shell scripts (must stay LF)
29
+ *.sh text eol=lf
30
+
31
+ # Binaries
32
+ *.png binary
33
+ *.jpg binary
34
+ *.ico binary
35
+ *.exe binary
36
+ *.dll binary
@@ -0,0 +1,29 @@
1
+ name: Bug Report
2
+ description: Report a bug or unexpected behavior
3
+ labels: ["bug"]
4
+
5
+ body:
6
+ - type: textarea
7
+ id: description
8
+ attributes:
9
+ label: Description
10
+ description: What happened?
11
+ validations:
12
+ required: true
13
+
14
+ - type: textarea
15
+ id: reproduction
16
+ attributes:
17
+ label: Steps to reproduce
18
+ description: Minimal code or commands to reproduce.
19
+ render: python
20
+
21
+ - type: textarea
22
+ id: environment
23
+ attributes:
24
+ label: Environment
25
+ description: |
26
+ - OS: [e.g. Windows 11, Ubuntu 22.04]
27
+ - Python version:
28
+ - opencode-py version:
29
+ - opencode version (if relevant):
@@ -0,0 +1,26 @@
1
+ name: Feature Request
2
+ description: Suggest an idea for this project
3
+ labels: ["enhancement"]
4
+
5
+ body:
6
+ - type: textarea
7
+ id: problem
8
+ attributes:
9
+ label: Problem
10
+ description: What problem does this solve?
11
+ validations:
12
+ required: true
13
+
14
+ - type: textarea
15
+ id: solution
16
+ attributes:
17
+ label: Proposed solution
18
+ description: How would you like it to work?
19
+ validations:
20
+ required: true
21
+
22
+ - type: textarea
23
+ id: alternatives
24
+ attributes:
25
+ label: Alternatives
26
+ description: What alternatives have you considered?
@@ -0,0 +1,62 @@
1
+ name: Test
2
+
3
+ on:
4
+ push:
5
+ branches: [master, main]
6
+ pull_request:
7
+ branches: [master, main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ${{ matrix.os }}
12
+ strategy:
13
+ matrix:
14
+ os: [ubuntu-latest, windows-latest]
15
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
16
+
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - name: Set up Python ${{ matrix.python-version }}
21
+ uses: actions/setup-python@v5
22
+ with:
23
+ python-version: ${{ matrix.python-version }}
24
+
25
+ - name: Install dependencies
26
+ run: |
27
+ pip install -e ".[dev]"
28
+
29
+ - name: Lint with ruff
30
+ run: ruff check .
31
+
32
+ - name: Type check with mypy
33
+ run: mypy src/
34
+
35
+ - name: Run unit tests
36
+ run: pytest tests/ -v
37
+
38
+ integration-docker:
39
+ runs-on: ubuntu-latest
40
+ timeout-minutes: 15
41
+ steps:
42
+ - uses: actions/checkout@v4
43
+
44
+ - name: Set up Python 3.12
45
+ uses: actions/setup-python@v5
46
+ with:
47
+ python-version: "3.12"
48
+
49
+ - name: Build wheel
50
+ run: |
51
+ pip install build
52
+ python -m build --wheel
53
+
54
+ - name: Build Docker image
55
+ run: docker build -t opencode-py-test tests/docker/
56
+
57
+ - name: Run smoke test
58
+ run: |
59
+ docker run --rm \
60
+ -v "${{ github.workspace }}/dist:/dist" \
61
+ opencode-py-test \
62
+ bash -c "pip install /dist/*.whl && python test_smoke.py"
@@ -0,0 +1,15 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.5.0
4
+ hooks:
5
+ - id: ruff
6
+ args: [--fix]
7
+ - id: ruff-format
8
+
9
+ - repo: https://github.com/pre-commit/mirrors-mypy
10
+ rev: v1.10.0
11
+ hooks:
12
+ - id: mypy
13
+ args: [src/]
14
+ additional_dependencies: [httpx]
15
+ pass_filenames: false
@@ -4,9 +4,9 @@
4
4
 
5
5
  Python SDK for [Opencode](https://opencode.ai) — a PyPI package (`opencode-py`) that launches an `opencode serve` subprocess and provides both high-level and low-level APIs.
6
6
 
7
- **Current version**: 0.1.0 (unreleased)
7
+ **Current version**: 0.2.0-dev
8
8
  **Python**: >=3.10
9
- **Dependencies**: only `httpx>=0.27.0`
9
+ **Dependencies**: `httpx>=0.27.0`, `pydantic>=2.0.0`, `typing-extensions>=4.6.0`
10
10
  **Build**: hatchling
11
11
 
12
12
  ## Repository
@@ -23,20 +23,24 @@ Python SDK for [Opencode](https://opencode.ai) — a PyPI package (`opencode-py`
23
23
  ```
24
24
  opencode-py/
25
25
  ├── src/opencode/
26
- │ ├── __init__.py # Public API exports
26
+ │ ├── __init__.py # Public API exports, calls setup_logging()
27
27
  │ ├── __main__.py # python -m opencode "question"
28
28
  │ ├── _opencode.py # Opencode class + opencode() convenience fn
29
29
  │ ├── _async_opencode.py # AsyncOpendcode class + async_opencode()
30
- │ ├── _client.py # OpencodeClient — sync REST (528 lines)
31
- │ ├── _async_client.py # AsyncOpendcodeClient — async REST
30
+ │ ├── _client.py # OpencodeClient — sync REST (typed models, retry)
31
+ │ ├── _async_client.py # AsyncOpendcodeClient — async REST (typed models, retry)
32
32
  │ ├── _server.py # OpencodeServer — subprocess lifecycle
33
33
  │ ├── _session.py # Session — sync conversation management
34
34
  │ ├── _async_session.py # AsyncSession — async conversation management
35
35
  │ ├── _binary.py # Binary find in PATH + GitHub download
36
36
  │ ├── _process.py # Cross-platform process termination
37
- │ ├── _models.py # TypedDict types (OutputFormatJsonSchema, etc.)
37
+ │ ├── _models.py # TypedDict types (SessionMessage, etc.)
38
+ │ ├── _response_models.py # Pydantic BaseModel response types (all endpoints)
39
+ │ ├── _types.py # NotGiven sentinel
40
+ │ ├── _logs.py # Logging via OPENCODE_LOG env var
38
41
  │ ├── _tools.py # ToolExecutor — run tools locally with permissions
39
- └── _errors.py # OpencodeError, ApiError, BinaryNotFound
42
+ ├── _errors.py # Typed exception hierarchy (10+ classes)
43
+ │ └── py.typed # PEP 561 marker for type checkers
40
44
  ├── tests/
41
45
  │ ├── test_client.py # 11 unit tests (sync, httpx MockTransport)
42
46
  │ ├── test_async_client.py # 11 unit tests (async, httpx MockTransport)
@@ -90,20 +94,26 @@ fb4b884 feat: initial Python SDK for Opencode
90
94
  - 31/31 unit tests passing (sync + async)
91
95
  - Python 3.10 compatibility (`NotRequired` via `typing_extensions`)
92
96
  - `live.py` (sync), `live_async.py`, `live_streaming.py` — interactive dialog scripts with `atexit` cleanup
97
+ - **Pydantic response models** — `HealthResponse`, `SessionResponse`, `FileContentResponse`, `ProviderResponse`, `V1SessionResponse` and more
98
+ - **Retry logic** — exponential backoff with jitter, retries on 408/409/429/5xx and timeouts
99
+ - **Typed error hierarchy** — `APIStatusError` → `BadRequestError`, `RateLimitError`, `InternalServerError`, etc.
100
+ - **Logging** — `OPENCODE_LOG=debug` enables httpx debug logging
101
+ - **`py.typed` marker** — PEP 561 compliance for type checkers
102
+ - **`copy()`/`with_options()`** — immutable client cloning with overrides
93
103
 
94
104
  ### Known issues to fix
95
105
 
96
106
  1. **Delivery enum mismatch** — npm v1.17.13 uses `"steer"/"queue"`, but upstream `dev` branch source also uses `"steer"/"queue"`. The local clone (`C:\Code\opencode`) has been modified to use `"immediate"/"deferred"` but this is NOT yet upstream. When upstream switches, update `_client.py:delivery="queue"` and `_async_client.py` to `"deferred"`. Run `scripts/check-upstream.py` to monitor.
97
107
 
98
- 2. ~~**Config format** — `Opencode(config={"model": "anthropic/..."})` fails because config expects `provider.{id}.options.apiKey` format.~~ **PARTIALLY RESOLVED**: The free `opencode` provider models work without API keys, but `OPENCODE_CONFIG_CONTENT={"model": "opencode/big-pickle"}` crashes the server with "ServeError" in v1.17.13. The model should be specified in the V1 prompt request body instead of server config. `Opencode` class works by passing `model` per-request, not via server config. **FIXED**: `opencode(model=...)` no longer puts model in server config — passes it per-request.
108
+ 2. ~~**Config format** — `Opencode(config={"model": "anthropic/..."})` fails because config expects `provider.{id}.options.apiKey` format.~~ **FIXED**: `opencode(model=...)` no longer puts model in server config — passes it per-request.
99
109
 
100
- 3. **`v2_session_wait` broken** — `POST /api/session/{sessionID}/wait` returns "Session wait is not available yet" in v1.17.13. **FIXED**: `Session.prompt()` now polls `v2_session_context()` until an assistant message appears (see `_session.py:_poll_response`).
110
+ 3. **`v2_session_wait` broken** — `POST /api/session/{sessionID}/wait` returns "Session wait is not available yet" in v1.17.13. **FIXED**: `Session.prompt()` now polls `v2_session_context()` until an assistant message appears.
101
111
 
102
- 4. ~~**No async support** — `OpencodeClient` is sync-only. `httpx.AsyncClient` not wired up.~~ **DONE**: `AsyncOpendcodeClient`, `AsyncSession`, `AsyncOpendcode`, `async_opencode()` all implemented.
112
+ 4. ~~**No async support** — `OpencodeClient` is sync-only.~~ **DONE**: Full async support.
103
113
 
104
114
  5. **Streaming** — `ask_stream()` reads SSE events via `/event` but delta format may differ between server versions.
105
115
 
106
- 6. ~~**No upstream monitoring** — No script to compare local `openapi.json` with upstream.~~ **DONE**: `scripts/check-upstream.py` fetches upstream, checks delivery enum + structured output.
116
+ 6. ~~**No upstream monitoring**~~ **DONE**: `scripts/check-upstream.py` fetches upstream.
107
117
 
108
118
  ## Architecture
109
119
 
@@ -280,6 +290,44 @@ python web/server.py
280
290
  ### Step G: Upstream monitoring ✅ DONE
281
291
  1. `scripts/check-upstream.py` checks delivery enum + structured output ✅
282
292
 
293
+ ## Next Steps
294
+
295
+ ### Step H: Complete typed model coverage
296
+ 1. Add `cast_to` to remaining client methods (vcs, config, project, etc.)
297
+ 2. Add `with_raw_response` pattern for raw HTTP access
298
+ 3. Consider `model_construct()` for faster deserialization (skip validation)
299
+
300
+ ### Step I: Release v0.2.0
301
+ 1. Bump version in `pyproject.toml` to `0.2.0`
302
+ 2. Publish to PyPI via Trusted Publishing
303
+
304
+ ## Release Process
305
+
306
+ Run `python scripts/check-release.py` to check if a new PyPI release is needed.
307
+ The script compares the version in `pyproject.toml` with the latest git tag
308
+ and reports unreleased commits.
309
+
310
+ **Release checklist:**
311
+ - [ ] `python scripts/check-release.py -v` — confirms release is due
312
+ - [ ] `git log --oneline v0.2.0..HEAD` — review unreleased changes
313
+ - [ ] Version bumped in `pyproject.toml`
314
+ - [ ] CI green on master
315
+ - [ ] `python -m build && twine check dist/*` — wheel is valid
316
+
317
+ **Proactive behavior**:
318
+ - At the start of each session, I will run `python scripts/check-release.py`
319
+ automatically and alert you if a release is due (commits since last tag
320
+ without version bump).
321
+ - **Before every commit**, I will check if the staged changes include anything
322
+ "user-facing" (feat, fix, refactor, or dependency changes — anything beyond
323
+ chore/docs/style). If there has been no version bump since the last tagged
324
+ release, I will warn you and ask whether to bump the version before
325
+ committing.
326
+
327
+ ### Step J: Compare with official SDK
328
+ 1. Evaluate `opencode-ai` (official Stainless SDK) for low-level layer
329
+ 2. Consider using their client as base with our high-level wrapper
330
+
283
331
  ## Style Guide
284
332
 
285
333
  - Keep in one function unless composable/reusable
@@ -288,7 +336,7 @@ python web/server.py
288
336
  - Prefer httpx over urllib/requests (HTTP client), except for `_binary.py` which uses `urllib.request` for download (stdlib, no extra deps)
289
337
  - Method naming: snake_case, category prefix (e.g., `v2_session_*`, `file_*`, `config_*`)
290
338
  - Type hints everywhere
291
- - Avoid `Any` where possible — use `TypedDict` from `_models.py`
339
+ - Avoid `Any` where possible — use Pydantic models from `_response_models.py`
292
340
 
293
341
  ## Commit Convention
294
342
 
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ ## v0.1.1 (2026-07-03)
4
+
5
+ - chore: add keywords to pyproject.toml and .gitattributes
6
+ - docs: add badges to README and MIT license file
7
+ - feat(tests): add Docker smoke test for clean-machine scenario
8
+ - chore: use importlib.metadata for `__version__`
9
+ - chore: bump version to 0.1.1 for author/URLs fix
10
+ - fix(publish): set author to Sergey Kislyakov, fix URLs
11
+
12
+ ## v0.1.0 (2026-06-30)
13
+
14
+ - feat: initial Python SDK for Opencode
15
+ - fix: resolve npm .cmd wrappers to real .exe binary on Windows
16
+ - fix(session): use V1 sync prompt with model support instead of V2
17
+ - feat(sdk): add keep parameter for multi-turn conversations
18
+ - feat(sdk): add auto_tools mode with ToolExecutor and permissions
19
+ - feat(web): add zero-dependency web UI with proxy server
20
+ - feat(async): add AsyncOpendcodeClient, AsyncSession, AsyncOpendcode
21
+ - feat(stream): add live_streaming.py, SSE streaming via ask_stream
22
+ - feat(sdk): add async_opencode, structured output, check-upstream
@@ -0,0 +1,63 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, caste, color, religion, or sexual
10
+ identity and orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open, welcoming,
13
+ diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment:
18
+
19
+ * Demonstrating empathy and kindness toward other people
20
+ * Being respectful of differing opinions, viewpoints, and experiences
21
+ * Giving and gracefully accepting constructive feedback
22
+ * Accepting responsibility and apologizing to those affected by our mistakes,
23
+ and learning from the experience
24
+ * Focusing on what is best not just for us as individuals, but for the overall
25
+ community
26
+
27
+ Examples of unacceptable behavior:
28
+
29
+ * The use of sexualized language or imagery, and sexual attention or advances
30
+ of any kind
31
+ * Trolling, insulting or derogatory comments, and personal or political attacks
32
+ * Public or private harassment
33
+ * Publishing others' private information, such as a physical or email address,
34
+ without their explicit permission
35
+ * Other conduct which could reasonably be considered inappropriate in a
36
+ professional setting
37
+
38
+ ## Enforcement Responsibilities
39
+
40
+ Community leaders are responsible for clarifying and enforcing our standards of
41
+ acceptable behavior and will take appropriate and fair corrective action in
42
+ response to any behavior that they deem inappropriate, threatening, offensive,
43
+ or harmful.
44
+
45
+ ## Scope
46
+
47
+ This Code of Conduct applies within all community spaces, and also applies when
48
+ an individual is officially representing the community in public spaces.
49
+
50
+ ## Enforcement
51
+
52
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
53
+ reported to the community leaders responsible for enforcement at
54
+ **s.kislyakov84@gmail.com**. All complaints will be reviewed and investigated
55
+ promptly and fairly.
56
+
57
+ ## Attribution
58
+
59
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
60
+ version 2.1, available at
61
+ https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.
62
+
63
+ [homepage]: https://www.contributor-covenant.org
@@ -0,0 +1,44 @@
1
+ # Contributing
2
+
3
+ ## Development setup
4
+
5
+ ```bash
6
+ pip install -e ".[dev]"
7
+ ```
8
+
9
+ ## Code style
10
+
11
+ - Type hints everywhere
12
+ - Avoid `Any` where possible — use `TypedDict` from `_models.py`
13
+ - Avoid single-use helper functions
14
+ - Prefer httpx over urllib/requests
15
+ - Method naming: snake_case with category prefix (e.g., `v2_session_*`, `file_*`)
16
+ - No comments unless absolutely necessary
17
+
18
+ ## Testing
19
+
20
+ ```bash
21
+ # Unit tests (no server needed)
22
+ pytest tests/ -v
23
+
24
+ # Live integration test (requires opencode in PATH)
25
+ python test_live.py
26
+ ```
27
+
28
+ ## Commit convention
29
+
30
+ ```
31
+ type(scope): summary
32
+ ```
33
+
34
+ Types: `feat`, `fix`, `docs`, `chore`, `refactor`, `test`
35
+
36
+ ## Pre-commit checks
37
+
38
+ Before committing, run:
39
+
40
+ ```bash
41
+ ruff check .
42
+ mypy src/
43
+ pytest tests/ -v
44
+ ```
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sergey Kislyakov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,32 +1,51 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opencode-py
3
- Version: 0.1.1
3
+ Version: 0.2.0
4
4
  Summary: Python SDK for Opencode — the open source AI coding agent
5
5
  Project-URL: Homepage, https://github.com/skislyakow/opencode-py
6
6
  Project-URL: Repository, https://github.com/skislyakow/opencode-py
7
7
  Project-URL: Documentation, https://github.com/skislyakow/opencode-py
8
+ Project-URL: Changelog, https://github.com/skislyakow/opencode-py/blob/main/CHANGELOG.md
8
9
  Author-email: Sergey Kislyakov <s.kislyakov84@gmail.com>
9
10
  License: MIT
11
+ License-File: LICENSE
12
+ Keywords: ai,coding-agent,llm,opencode,python-sdk,sdk
10
13
  Classifier: Development Status :: 4 - Beta
11
14
  Classifier: Intended Audience :: Developers
12
15
  Classifier: License :: OSI Approved :: MIT License
13
16
  Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
14
18
  Classifier: Programming Language :: Python :: 3.10
15
19
  Classifier: Programming Language :: Python :: 3.11
16
20
  Classifier: Programming Language :: Python :: 3.12
17
21
  Classifier: Programming Language :: Python :: 3.13
18
22
  Requires-Python: >=3.10
19
23
  Requires-Dist: httpx>=0.27.0
24
+ Requires-Dist: pydantic>=2.0.0
25
+ Requires-Dist: typing-extensions>=4.6.0
20
26
  Provides-Extra: dev
21
27
  Requires-Dist: build>=1.0; extra == 'dev'
22
28
  Requires-Dist: httpx>=0.27.0; extra == 'dev'
29
+ Requires-Dist: mypy>=1.10.0; extra == 'dev'
23
30
  Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
24
31
  Requires-Dist: pytest>=8.0; extra == 'dev'
32
+ Requires-Dist: ruff>=0.5.0; extra == 'dev'
25
33
  Requires-Dist: twine>=4.0; extra == 'dev'
26
34
  Description-Content-Type: text/markdown
27
35
 
28
36
  # Opencode Python SDK
29
37
 
38
+ <p align="center">
39
+ <a href="https://pypi.org/project/opencode-py/"><img src="https://img.shields.io/pypi/v/opencode-py" alt="PyPI version"></a>
40
+ <a href="https://pypi.org/project/opencode-py/"><img src="https://img.shields.io/pypi/pyversions/opencode-py" alt="Python versions"></a>
41
+ <a href="https://github.com/skislyakow/opencode-py/blob/main/LICENSE"><img src="https://img.shields.io/pypi/l/opencode-py" alt="License"></a>
42
+ <a href="https://pypi.org/project/opencode-py/"><img src="https://img.shields.io/pypi/dm/opencode-py" alt="Downloads"></a>
43
+ <a href="https://github.com/skislyakow/opencode-py/actions/workflows/test.yml"><img src="https://github.com/skislyakow/opencode-py/actions/workflows/test.yml/badge.svg" alt="Tests"></a>
44
+ <img src="https://img.shields.io/badge/build-hatchling-4051b5" alt="Hatchling">
45
+ <img src="https://img.shields.io/badge/http-httpx-blue" alt="httpx">
46
+ <img src="https://img.shields.io/badge/models-pydantic-E92063" alt="pydantic">
47
+ </p>
48
+
30
49
  Python SDK for [Opencode](https://opencode.ai) — the open source AI coding agent.
31
50
 
32
51
  ```bash
@@ -115,9 +134,35 @@ with Opencode() as ai:
115
134
  diff = ai.client.vcs_diff("HEAD~3")
116
135
  config = ai.client.config_get()
117
136
  session = ai.client.session_create()
118
- ai.client.v2_session_prompt(session["id"], {"text": "Hello"})
137
+ ai.client.v2_session_prompt(session.id, {"text": "Hello"})
119
138
  ```
120
139
 
140
+ All client methods return typed Pydantic models — IDE autocomplete, validation, `.model_dump()`, `.model_dump_json()`.
141
+
142
+ ### Retry & error handling
143
+
144
+ ```python
145
+ from opencode import OpencodeClient, RateLimitError, InternalServerError
146
+
147
+ client = OpencodeClient(max_retries=3) # exponential backoff with jitter
148
+
149
+ try:
150
+ health = client.health()
151
+ print(health.version)
152
+ except RateLimitError:
153
+ print("too many requests — retried but failed")
154
+ except InternalServerError:
155
+ print("server error")
156
+ ```
157
+
158
+ ### Debug logging
159
+
160
+ ```bash
161
+ OPENCODE_LOG=debug python my_script.py
162
+ ```
163
+
164
+ Shows all HTTP requests/responses with timing.
165
+
121
166
  ### Web UI (zero dependencies)
122
167
 
123
168
  ```bash
@@ -184,7 +229,7 @@ from opencode import AsyncOpendcodeClient
184
229
 
185
230
  async with AsyncOpendcodeClient() as client:
186
231
  health = await client.health()
187
- print(health)
232
+ print(health.version) # typed Pydantic model
188
233
  ```
189
234
 
190
235
  ## Development
@@ -1,5 +1,16 @@
1
1
  # Opencode Python SDK
2
2
 
3
+ <p align="center">
4
+ <a href="https://pypi.org/project/opencode-py/"><img src="https://img.shields.io/pypi/v/opencode-py" alt="PyPI version"></a>
5
+ <a href="https://pypi.org/project/opencode-py/"><img src="https://img.shields.io/pypi/pyversions/opencode-py" alt="Python versions"></a>
6
+ <a href="https://github.com/skislyakow/opencode-py/blob/main/LICENSE"><img src="https://img.shields.io/pypi/l/opencode-py" alt="License"></a>
7
+ <a href="https://pypi.org/project/opencode-py/"><img src="https://img.shields.io/pypi/dm/opencode-py" alt="Downloads"></a>
8
+ <a href="https://github.com/skislyakow/opencode-py/actions/workflows/test.yml"><img src="https://github.com/skislyakow/opencode-py/actions/workflows/test.yml/badge.svg" alt="Tests"></a>
9
+ <img src="https://img.shields.io/badge/build-hatchling-4051b5" alt="Hatchling">
10
+ <img src="https://img.shields.io/badge/http-httpx-blue" alt="httpx">
11
+ <img src="https://img.shields.io/badge/models-pydantic-E92063" alt="pydantic">
12
+ </p>
13
+
3
14
  Python SDK for [Opencode](https://opencode.ai) — the open source AI coding agent.
4
15
 
5
16
  ```bash
@@ -88,9 +99,35 @@ with Opencode() as ai:
88
99
  diff = ai.client.vcs_diff("HEAD~3")
89
100
  config = ai.client.config_get()
90
101
  session = ai.client.session_create()
91
- ai.client.v2_session_prompt(session["id"], {"text": "Hello"})
102
+ ai.client.v2_session_prompt(session.id, {"text": "Hello"})
92
103
  ```
93
104
 
105
+ All client methods return typed Pydantic models — IDE autocomplete, validation, `.model_dump()`, `.model_dump_json()`.
106
+
107
+ ### Retry & error handling
108
+
109
+ ```python
110
+ from opencode import OpencodeClient, RateLimitError, InternalServerError
111
+
112
+ client = OpencodeClient(max_retries=3) # exponential backoff with jitter
113
+
114
+ try:
115
+ health = client.health()
116
+ print(health.version)
117
+ except RateLimitError:
118
+ print("too many requests — retried but failed")
119
+ except InternalServerError:
120
+ print("server error")
121
+ ```
122
+
123
+ ### Debug logging
124
+
125
+ ```bash
126
+ OPENCODE_LOG=debug python my_script.py
127
+ ```
128
+
129
+ Shows all HTTP requests/responses with timing.
130
+
94
131
  ### Web UI (zero dependencies)
95
132
 
96
133
  ```bash
@@ -157,7 +194,7 @@ from opencode import AsyncOpendcodeClient
157
194
 
158
195
  async with AsyncOpendcodeClient() as client:
159
196
  health = await client.health()
160
- print(health)
197
+ print(health.version) # typed Pydantic model
161
198
  ```
162
199
 
163
200
  ## Development
@@ -1,5 +1,15 @@
1
1
  # Opencode Python SDK
2
2
 
3
+ <p align="center">
4
+ <a href="https://pypi.org/project/opencode-py/"><img src="https://img.shields.io/pypi/v/opencode-py" alt="PyPI version"></a>
5
+ <a href="https://pypi.org/project/opencode-py/"><img src="https://img.shields.io/pypi/pyversions/opencode-py" alt="Python versions"></a>
6
+ <a href="https://github.com/skislyakow/opencode-py/blob/main/LICENSE"><img src="https://img.shields.io/pypi/l/opencode-py" alt="License"></a>
7
+ <a href="https://pypi.org/project/opencode-py/"><img src="https://img.shields.io/pypi/dm/opencode-py" alt="Downloads"></a>
8
+ <a href="https://github.com/skislyakow/opencode-py/actions/workflows/test.yml"><img src="https://github.com/skislyakow/opencode-py/actions/workflows/test.yml/badge.svg" alt="Tests"></a>
9
+ <img src="https://img.shields.io/badge/build-hatchling-4051b5" alt="Hatchling">
10
+ <img src="https://img.shields.io/badge/http-httpx-blue" alt="httpx">
11
+ </p>
12
+
3
13
  Python SDK для [Opencode](https://opencode.ai) — open source AI coding agent.
4
14
 
5
15
  ```bash