Explorar el Código

ignore: python sdk (#2779)

Co-authored-by: Aiden Cline <[email protected]>
Kevin King hace 3 meses
padre
commit
0e60f66604
Se han modificado 100 ficheros con 9594 adiciones y 2 borrados
  1. 71 0
      .github/publish-python-sdk.yml
  2. 1 1
      packages/plugin/package.json
  3. 1 1
      packages/sdk/js/package.json
  4. 22 0
      packages/sdk/python/.gitignore
  5. 82 0
      packages/sdk/python/README.md
  6. 19 0
      packages/sdk/python/docs/generation.md
  7. 11 0
      packages/sdk/python/docs/index.md
  8. 27 0
      packages/sdk/python/docs/installation.md
  9. 24 0
      packages/sdk/python/docs/publishing.md
  10. 22 0
      packages/sdk/python/docs/quickstart.md
  11. 15 0
      packages/sdk/python/docs/testing.md
  12. 21 0
      packages/sdk/python/docs/usage/configuration.md
  13. 22 0
      packages/sdk/python/docs/usage/files_projects.md
  14. 18 0
      packages/sdk/python/docs/usage/sessions.md
  15. 29 0
      packages/sdk/python/docs/usage/streaming.md
  16. 19 0
      packages/sdk/python/examples/basic_usage.py
  17. 6 0
      packages/sdk/python/examples/file_status.py
  18. 4 0
      packages/sdk/python/examples/session_list.py
  19. 29 0
      packages/sdk/python/mkdocs.yml
  20. 5 0
      packages/sdk/python/openapi-python-client.yaml
  21. 56 0
      packages/sdk/python/pyproject.toml
  22. 210 0
      packages/sdk/python/scripts/generate.py
  23. 68 0
      packages/sdk/python/scripts/publish.py
  24. 14 0
      packages/sdk/python/src/opencode_ai/__init__.py
  25. 1 0
      packages/sdk/python/src/opencode_ai/api/__init__.py
  26. 1 0
      packages/sdk/python/src/opencode_ai/api/default/__init__.py
  27. 160 0
      packages/sdk/python/src/opencode_ai/api/default/app_agents.py
  28. 164 0
      packages/sdk/python/src/opencode_ai/api/default/command_list.py
  29. 155 0
      packages/sdk/python/src/opencode_ai/api/default/config_get.py
  30. 159 0
      packages/sdk/python/src/opencode_ai/api/default/config_providers.py
  31. 447 0
      packages/sdk/python/src/opencode_ai/api/default/event_subscribe.py
  32. 160 0
      packages/sdk/python/src/opencode_ai/api/default/file_status.py
  33. 155 0
      packages/sdk/python/src/opencode_ai/api/default/path_get.py
  34. 155 0
      packages/sdk/python/src/opencode_ai/api/default/project_current.py
  35. 164 0
      packages/sdk/python/src/opencode_ai/api/default/project_list.py
  36. 164 0
      packages/sdk/python/src/opencode_ai/api/default/session_list.py
  37. 164 0
      packages/sdk/python/src/opencode_ai/api/default/tool_ids.py
  38. 153 0
      packages/sdk/python/src/opencode_ai/api/default/tui_clear_prompt.py
  39. 153 0
      packages/sdk/python/src/opencode_ai/api/default/tui_open_help.py
  40. 153 0
      packages/sdk/python/src/opencode_ai/api/default/tui_open_models.py
  41. 153 0
      packages/sdk/python/src/opencode_ai/api/default/tui_open_sessions.py
  42. 153 0
      packages/sdk/python/src/opencode_ai/api/default/tui_open_themes.py
  43. 153 0
      packages/sdk/python/src/opencode_ai/api/default/tui_submit_prompt.py
  44. 268 0
      packages/sdk/python/src/opencode_ai/client.py
  45. 16 0
      packages/sdk/python/src/opencode_ai/errors.py
  46. 186 0
      packages/sdk/python/src/opencode_ai/extras.py
  47. 367 0
      packages/sdk/python/src/opencode_ai/models/__init__.py
  48. 180 0
      packages/sdk/python/src/opencode_ai/models/agent.py
  49. 173 0
      packages/sdk/python/src/opencode_ai/models/agent_config.py
  50. 155 0
      packages/sdk/python/src/opencode_ai/models/agent_config_permission.py
  51. 74 0
      packages/sdk/python/src/opencode_ai/models/agent_config_permission_bash_type_1.py
  52. 44 0
      packages/sdk/python/src/opencode_ai/models/agent_config_tools.py
  53. 67 0
      packages/sdk/python/src/opencode_ai/models/agent_model.py
  54. 44 0
      packages/sdk/python/src/opencode_ai/models/agent_options.py
  55. 117 0
      packages/sdk/python/src/opencode_ai/models/agent_part.py
  56. 102 0
      packages/sdk/python/src/opencode_ai/models/agent_part_input.py
  57. 75 0
      packages/sdk/python/src/opencode_ai/models/agent_part_input_source.py
  58. 75 0
      packages/sdk/python/src/opencode_ai/models/agent_part_source.py
  59. 120 0
      packages/sdk/python/src/opencode_ai/models/agent_permission.py
  60. 74 0
      packages/sdk/python/src/opencode_ai/models/agent_permission_bash.py
  61. 44 0
      packages/sdk/python/src/opencode_ai/models/agent_tools.py
  62. 69 0
      packages/sdk/python/src/opencode_ai/models/api_auth.py
  63. 228 0
      packages/sdk/python/src/opencode_ai/models/assistant_message.py
  64. 67 0
      packages/sdk/python/src/opencode_ai/models/assistant_message_path.py
  65. 70 0
      packages/sdk/python/src/opencode_ai/models/assistant_message_time.py
  66. 89 0
      packages/sdk/python/src/opencode_ai/models/assistant_message_tokens.py
  67. 67 0
      packages/sdk/python/src/opencode_ai/models/assistant_message_tokens_cache.py
  68. 105 0
      packages/sdk/python/src/opencode_ai/models/command.py
  69. 411 0
      packages/sdk/python/src/opencode_ai/models/config.py
  70. 113 0
      packages/sdk/python/src/opencode_ai/models/config_agent.py
  71. 57 0
      packages/sdk/python/src/opencode_ai/models/config_command.py
  72. 97 0
      packages/sdk/python/src/opencode_ai/models/config_command_additional_property.py
  73. 81 0
      packages/sdk/python/src/opencode_ai/models/config_experimental.py
  74. 93 0
      packages/sdk/python/src/opencode_ai/models/config_experimental_hook.py
  75. 73 0
      packages/sdk/python/src/opencode_ai/models/config_experimental_hook_file_edited.py
  76. 87 0
      packages/sdk/python/src/opencode_ai/models/config_experimental_hook_file_edited_additional_property_item.py
  77. 44 0
      packages/sdk/python/src/opencode_ai/models/config_experimental_hook_file_edited_additional_property_item_environment.py
  78. 87 0
      packages/sdk/python/src/opencode_ai/models/config_experimental_hook_session_completed_item.py
  79. 44 0
      packages/sdk/python/src/opencode_ai/models/config_experimental_hook_session_completed_item_environment.py
  80. 57 0
      packages/sdk/python/src/opencode_ai/models/config_formatter.py
  81. 105 0
      packages/sdk/python/src/opencode_ai/models/config_formatter_additional_property.py
  82. 44 0
      packages/sdk/python/src/opencode_ai/models/config_formatter_additional_property_environment.py
  83. 86 0
      packages/sdk/python/src/opencode_ai/models/config_lsp.py
  84. 59 0
      packages/sdk/python/src/opencode_ai/models/config_lsp_additional_property_type_0.py
  85. 125 0
      packages/sdk/python/src/opencode_ai/models/config_lsp_additional_property_type_1.py
  86. 44 0
      packages/sdk/python/src/opencode_ai/models/config_lsp_additional_property_type_1_env.py
  87. 44 0
      packages/sdk/python/src/opencode_ai/models/config_lsp_additional_property_type_1_initialization.py
  88. 82 0
      packages/sdk/python/src/opencode_ai/models/config_mcp.py
  89. 97 0
      packages/sdk/python/src/opencode_ai/models/config_mode.py
  90. 155 0
      packages/sdk/python/src/opencode_ai/models/config_permission.py
  91. 74 0
      packages/sdk/python/src/opencode_ai/models/config_permission_bash_type_1.py
  92. 57 0
      packages/sdk/python/src/opencode_ai/models/config_provider.py
  93. 118 0
      packages/sdk/python/src/opencode_ai/models/config_provider_additional_property.py
  94. 63 0
      packages/sdk/python/src/opencode_ai/models/config_provider_additional_property_models.py
  95. 214 0
      packages/sdk/python/src/opencode_ai/models/config_provider_additional_property_models_additional_property.py
  96. 87 0
      packages/sdk/python/src/opencode_ai/models/config_provider_additional_property_models_additional_property_cost.py
  97. 67 0
      packages/sdk/python/src/opencode_ai/models/config_provider_additional_property_models_additional_property_limit.py
  98. 44 0
      packages/sdk/python/src/opencode_ai/models/config_provider_additional_property_models_additional_property_options.py
  99. 59 0
      packages/sdk/python/src/opencode_ai/models/config_provider_additional_property_models_additional_property_provider.py
  100. 87 0
      packages/sdk/python/src/opencode_ai/models/config_provider_additional_property_options.py

+ 71 - 0
.github/publish-python-sdk.yml

@@ -0,0 +1,71 @@
+#
+# This file is intentionally in the wrong dir, will move and add later....
+#
+
+# name: publish-python-sdk
+
+# on:
+#   release:
+#     types: [published]
+#   workflow_dispatch:
+
+# jobs:
+#   publish:
+#     runs-on: ubuntu-latest
+#     permissions:
+#       contents: read
+#     steps:
+#       - name: Checkout repository
+#         uses: actions/checkout@v4
+
+#       - name: Setup Bun
+#         uses: oven-sh/setup-bun@v1
+#         with:
+#           bun-version: 1.2.21
+
+#       - name: Install dependencies (JS/Bun)
+#         run: bun install
+
+#       - name: Install uv
+#         shell: bash
+#         run: curl -LsSf https://astral.sh/uv/install.sh | sh
+
+#       - name: Generate Python SDK from OpenAPI (CLI)
+#         shell: bash
+#         run: |
+#           ~/.local/bin/uv run --project packages/sdk/python python packages/sdk/python/scripts/generate.py --source cli
+
+#       - name: Sync Python dependencies
+#         shell: bash
+#         run: |
+#           ~/.local/bin/uv sync --dev --project packages/sdk/python
+
+#       - name: Set version from release tag
+#         shell: bash
+#         run: |
+#           TAG="${GITHUB_REF_NAME:-}"
+#           if [ -z "$TAG" ]; then
+#             TAG="$(git describe --tags --abbrev=0 || echo 0.0.0)"
+#           fi
+#           echo "Using version: $TAG"
+#           VERSION="$TAG" ~/.local/bin/uv run --project packages/sdk/python python - <<'PY'
+# import os, re, pathlib
+# root = pathlib.Path('packages/sdk/python')
+# pt = (root / 'pyproject.toml').read_text()
+# version = os.environ.get('VERSION','0.0.0').lstrip('v')
+# pt = re.sub(r'(?m)^(version\s*=\s*")[^"]+("\s*)$', f"\\1{version}\\2", pt)
+# (root / 'pyproject.toml').write_text(pt)
+# # Also update generator config override for consistency
+# cfgp = root / 'openapi-python-client.yaml'
+# if cfgp.exists():
+#     cfg = cfgp.read_text()
+#     cfg = re.sub(r'(?m)^(package_version_override:\s*)\S+$', f"\\1{version}", cfg)
+#     cfgp.write_text(cfg)
+# PY
+
+#       - name: Build and publish to PyPI
+#         env:
+#           PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
+#         shell: bash
+#         run: |
+#           ~/.local/bin/uv run --project packages/sdk/python python packages/sdk/python/scripts/publish.py

+ 1 - 1
packages/plugin/package.json

@@ -24,4 +24,4 @@
     "typescript": "catalog:",
     "@typescript/native-preview": "catalog:"
   }
-}
+}

+ 1 - 1
packages/sdk/js/package.json

@@ -26,4 +26,4 @@
   "publishConfig": {
     "directory": "dist"
   }
-}
+}

+ 22 - 0
packages/sdk/python/.gitignore

@@ -0,0 +1,22 @@
+__pycache__/
+*.py[cod]
+*.egg-info/
+.build/
+build/
+dist/
+.coverage
+htmlcov/
+.mypy_cache/
+.pytest_cache/
+.ruff_cache/
+.venv/
+.conda/
+.env
+.DS_Store
+openapi.json
+site/
+
+
+# IDE
+.vscode/
+.idea/

+ 82 - 0
packages/sdk/python/README.md

@@ -0,0 +1,82 @@
+# Opencode Python SDK
+
+This package provides a Python SDK for the Opencode API. It is generated using openapi-python-client (not Stainless).
+
+
+Documentation
+- Full docs: see `mkdocs` site under `packages/sdk/python/docs/`
+- Preview locally:
+```bash
+uv run --project packages/sdk/python mkdocs serve -f packages/sdk/python/mkdocs.yml
+```
+
+Badges
+- PyPI: https://img.shields.io/pypi/v/opencode-ai?style=flat-square
+
+Requirements
+- Python 3.8+
+- uv (recommended) -> https://docs.astral.sh/uv/
+- openapi-python-client (invoked via `uvx`)
+
+Install uv
+```bash
+# macOS/Linux
+curl -LsSf https://astral.sh/uv/install.sh | sh
+```
+
+Set up the environment (from this directory)
+```bash
+uv sync --dev
+```
+
+Generate client code (from CLI-generated spec)
+```bash
+# From repository root OR from this directory
+uv run python packages/sdk/python/scripts/generate.py --source cli
+```
+
+Alternatively, fetch spec from a running server
+```bash
+uv run python packages/sdk/python/scripts/generate.py --source server --server-url http://localhost:4096/doc
+```
+
+This will:
+1) Produce an OpenAPI spec from the local CLI or a running server
+2) Run openapi-python-client (via `uvx`) to generate client code
+3) Copy the generated Python package into src/opencode_ai
+
+Usage (after generation)
+```python
+from opencode_ai import OpenCodeClient
+
+client = OpenCodeClient(base_url="http://localhost:4096")
+print(client.get_config())
+
+# See examples/basic_usage.py for more details
+
+# Streaming events (sync)
+for event in client.subscribe_events():
+    print(event)
+    break
+
+# Error handling and retries
+# Set retries>0 to enable exponential backoff for transient errors like 429/5xx
+client = OpenCodeClient(retries=2, backoff_factor=0.1)
+
+# Async usage example
+# uv run --project packages/sdk/python python - <<'PY'
+# import asyncio
+# from opencode_ai import OpenCodeClient
+# async def main():
+#     client = OpenCodeClient()
+#     async for event in client.subscribe_events_async():
+#         print(event)
+#         break
+# asyncio.run(main())
+# PY
+```
+
+Notes
+- We intentionally do not use Stainless for the Python SDK.
+- The generator targets OpenAPI 3.1 emitted by the opencode server at /doc.
+- See scripts/generate.py for details and customization points.

+ 19 - 0
packages/sdk/python/docs/generation.md

@@ -0,0 +1,19 @@
+# Generation workflow
+
+The SDK is generated from the Opencode server's OpenAPI 3.1 spec.
+
+Two source modes are supported:
+- CLI (default): runs `bun dev generate` to emit the OpenAPI JSON
+- Server: fetches `http://localhost:4096/doc` from a running server
+
+Generator command
+```bash
+# From repo root
+uv run --project packages/sdk/python python packages/sdk/python/scripts/generate.py --source cli
+# Or
+uv run --project packages/sdk/python python packages/sdk/python/scripts/generate.py --source server --server-url http://localhost:4096/doc
+```
+
+Post-generation
+- The generator injects `extras.py` (OpenCodeClient) and patches `__init__.py` to export it
+- Code is formatted with `ruff` (imports) and `black`

+ 11 - 0
packages/sdk/python/docs/index.md

@@ -0,0 +1,11 @@
+# Opencode Python SDK
+
+The official Python client for the Opencode API, generated from the OpenAPI spec and extended with ergonomic helpers.
+
+Highlights
+- Provider-agnostic client generated from OpenAPI 3.1
+- Thin convenience wrapper (OpenCodeClient) for common tasks
+- Sync and async SSE streaming for live event feeds
+- First-class uv support for development
+
+If you're new, start with Quickstart or Installation in the navigation.

+ 27 - 0
packages/sdk/python/docs/installation.md

@@ -0,0 +1,27 @@
+# Installation
+
+Requirements
+- Python 3.8+
+- uv (recommended) -> https://docs.astral.sh/uv/
+
+Install uv
+```bash
+curl -LsSf https://astral.sh/uv/install.sh | sh
+```
+
+Project setup
+```bash
+# From repo root or this directory
+uv sync --dev --project packages/sdk/python
+```
+
+Using pip (alternative)
+```bash
+pip install opencode-ai
+```
+
+Preview docs locally
+```bash
+# From repo root
+uv run --project packages/sdk/python mkdocs serve -f packages/sdk/python/mkdocs.yml
+```

+ 24 - 0
packages/sdk/python/docs/publishing.md

@@ -0,0 +1,24 @@
+# Publishing (maintainers)
+
+Automated publishing runs on GitHub Releases.
+
+Workflow
+- Create a new Release (the tag value becomes the package version)
+- The `publish-python-sdk` workflow will:
+  - Generate the SDK from OpenAPI (CLI path)
+  - Set the version in `pyproject.toml` and generator config
+  - Build wheel/sdist and upload to PyPI
+
+Prerequisites
+- Repository secret: `PYPI_API_TOKEN`
+
+Manual publish
+```bash
+# TestPyPI
+REPOSITORY=testpypi PYPI_TOKEN=$TEST_PYPI_API_TOKEN \
+uv run --project packages/sdk/python python packages/sdk/python/scripts/publish.py
+
+# PyPI
+REPOSITORY=pypi PYPI_TOKEN=$PYPI_API_TOKEN \
+uv run --project packages/sdk/python python packages/sdk/python/scripts/publish.py
+```

+ 22 - 0
packages/sdk/python/docs/quickstart.md

@@ -0,0 +1,22 @@
+# Quickstart
+
+Create a client and make your first calls.
+
+```python
+from opencode_ai import OpenCodeClient
+
+client = OpenCodeClient(base_url="http://localhost:4096")
+
+# List projects
+for p in client.list_projects() or []:
+    print(p.id, p.directory)
+
+# Get path info
+path = client.get_path()
+print(path.directory)
+
+# Stream events (sync)
+for event in client.subscribe_events():
+    print(event)
+    break
+```

+ 15 - 0
packages/sdk/python/docs/testing.md

@@ -0,0 +1,15 @@
+# Testing
+
+Run unit, mock, and integration tests.
+
+```bash
+# Sync dev dependencies
+uv sync --dev --project packages/sdk/python
+
+# Run tests
+uv run --project packages/sdk/python pytest -q
+```
+
+Notes
+- Integration test starts a headless opencode server via Bun in a subprocess
+- SSE behavior is validated using real streaming from the server

+ 21 - 0
packages/sdk/python/docs/usage/configuration.md

@@ -0,0 +1,21 @@
+# Configuration
+
+OpenCodeClient accepts common options for auth, timeouts, and retries.
+
+```python
+from opencode_ai import OpenCodeClient
+
+client = OpenCodeClient(
+    base_url="http://localhost:4096",
+    token="pypi-or-other-token",
+    auth_header_name="Authorization",
+    auth_prefix="Bearer",
+    timeout=30.0,  # seconds
+    retries=2,
+    backoff_factor=0.2,  # exponential backoff
+)
+```
+
+- Auth: sets the header `{auth_header_name}: {auth_prefix} {token}` when `token` is provided
+- Retries: retry on transient httpx.RequestError and 429/5xx
+- Timeouts: passed to httpx.Timeout

+ 22 - 0
packages/sdk/python/docs/usage/files_projects.md

@@ -0,0 +1,22 @@
+# Files & Projects
+
+Access file status and project information.
+
+```python
+from opencode_ai import OpenCodeClient
+
+client = OpenCodeClient()
+
+# Projects
+for p in client.list_projects() or []:
+    print(p.id, p.directory)
+
+# Current path
+pinfo = client.get_path()
+print(pinfo.directory)
+
+# File status
+files = client.file_status() or []
+for f in files:
+    print(f.path, f.type)
+```

+ 18 - 0
packages/sdk/python/docs/usage/sessions.md

@@ -0,0 +1,18 @@
+# Sessions
+
+List sessions and inspect them. The wrapper exposes a convenience method while the generated API remains available under `opencode_ai.api.default`.
+
+```python
+from opencode_ai import OpenCodeClient
+from opencode_ai.api.default import session_list as generated
+
+client = OpenCodeClient()
+
+# Wrapper
+sessions = client.list_sessions() or []
+
+# Generated function
+sessions2 = generated.sync(client=client.client)
+
+print(len(sessions), len(sessions2))
+```

+ 29 - 0
packages/sdk/python/docs/usage/streaming.md

@@ -0,0 +1,29 @@
+# Streaming (SSE)
+
+Subscribe to the event stream. The wrapper provides both sync and async interfaces.
+
+```python
+from opencode_ai import OpenCodeClient
+
+client = OpenCodeClient()
+
+# Sync streaming
+for event in client.subscribe_events():
+    print(event)
+    break
+```
+
+Async variant:
+
+```python
+import asyncio
+from opencode_ai import OpenCodeClient
+
+async def main():
+    client = OpenCodeClient()
+    async for event in client.subscribe_events_async():
+        print(event)
+        break
+
+asyncio.run(main())
+```

+ 19 - 0
packages/sdk/python/examples/basic_usage.py

@@ -0,0 +1,19 @@
+# Basic usage example (placeholder)
+# After generating the client, this should reflect actual client entrypoints.
+
+try:
+    from opencode_ai import client  # type: ignore
+except Exception:  # pragma: no cover
+    client = None
+
+
+def main() -> None:
+    if client is None:
+        print("Client not generated yet. Run the generator first:")
+        print("    uv run python packages/sdk/python/scripts/generate.py")
+        return
+    print("Replace this with real example code once the client is generated.")
+
+
+if __name__ == "__main__":
+    main()

+ 6 - 0
packages/sdk/python/examples/file_status.py

@@ -0,0 +1,6 @@
+from opencode_ai import OpenCodeClient
+
+client = OpenCodeClient()
+files = client.file_status() or []
+for f in files:
+    print(f.path, f.type)

+ 4 - 0
packages/sdk/python/examples/session_list.py

@@ -0,0 +1,4 @@
+from opencode_ai import OpenCodeClient
+
+client = OpenCodeClient()
+print([s.id for s in client.list_sessions() or []])

+ 29 - 0
packages/sdk/python/mkdocs.yml

@@ -0,0 +1,29 @@
+site_name: Opencode Python SDK
+site_description: Official Python SDK for the Opencode API
+site_url: https://opencode.ai
+repo_url: https://github.com/sst/opencode
+repo_name: sst/opencode
+edit_uri: ''
+theme:
+  name: material
+  features:
+    - navigation.tabs
+    - navigation.sections
+    - content.code.copy
+markdown_extensions:
+  - admonition
+  - codehilite
+  - toc:
+      permalink: true
+nav:
+  - Overview: index.md
+  - Installation: installation.md
+  - Quickstart: quickstart.md
+  - Usage:
+      - Configuration: usage/configuration.md
+      - Sessions: usage/sessions.md
+      - Files & Projects: usage/files_projects.md
+      - Streaming (SSE): usage/streaming.md
+  - Generation: generation.md
+  - Testing: testing.md
+  - Publishing (maintainers): publishing.md

+ 5 - 0
packages/sdk/python/openapi-python-client.yaml

@@ -0,0 +1,5 @@
+# Configuration for openapi-python-client
+# Ensures consistent project and package names and version when generating.
+project_name_override: opencode-ai
+package_name_override: opencode_ai
+package_version_override: 0.1.0

+ 56 - 0
packages/sdk/python/pyproject.toml

@@ -0,0 +1,56 @@
+[build-system]
+requires = ["hatchling>=1.17.0"]
+build-backend = "hatchling.build"
+
+[project]
+name = "opencode-ai"
+version = "0.1.0"
+description = "Python client for the Opencode API (generated via openapi-python-client)"
+readme = "README.md"
+requires-python = ">=3.8"
+license = {text = "MIT"}
+authors = [
+  { name = "Opencode Authors", email = "[email protected]" }
+]
+dependencies = [
+  "httpx>=0.27.0",
+  "pydantic>=2.0.0",
+  "python-dateutil>=2.8.2"
+]
+
+[project.urls]
+Homepage = "https://opencode.ai"
+Repository = "https://github.com/sst/opencode"
+
+[tool.uv]
+# Development-time dependencies installed with `uv sync --dev`
+dev-dependencies = [
+  "openapi-python-client",
+  "black",
+  "isort",
+  "ruff",
+  "pytest",
+  "pytest-asyncio",
+  "sseclient-py",
+  "build",
+  "twine",
+  "mkdocs",
+  "mkdocs-material",
+]
+
+[tool.black]
+line-length = 120
+target-version = ["py38", "py39", "py310", "py311", "py312"]
+
+[tool.isort]
+profile = "black"
+line_length = 120
+
+[tool.ruff]
+line-length = 120
+select = ["E", "F", "I", "UP"]
+ignore = []
+
+[tool.pytest.ini_options]
+addopts = "-q"
+pythonpath = ["src"]

+ 210 - 0
packages/sdk/python/scripts/generate.py

@@ -0,0 +1,210 @@
+#!/usr/bin/env python3
+"""
+Generate the Opencode Python SDK using openapi-python-client and place it under src/opencode_ai.
+
+Steps:
+- Generate OpenAPI JSON from the local CLI (bun dev generate)
+- Run openapi-python-client (via `uvx` if available, else fallback to PATH)
+- Copy the generated module into src/opencode_ai
+
+Requires:
+- Bun installed (for `bun dev generate`)
+- uv installed (recommended) to run `uvx openapi-python-client`
+"""
+from __future__ import annotations
+
+import argparse
+import json
+import shutil
+import subprocess
+import sys
+from pathlib import Path
+from urllib.request import urlopen
+
+
+def run(cmd: list[str], cwd: Path | None = None) -> subprocess.CompletedProcess:
+    print("$", " ".join(cmd))
+    return subprocess.run(cmd, cwd=str(cwd) if cwd else None, check=True, capture_output=True, text=True)
+
+
+def find_repo_root(start: Path) -> Path:
+    p = start
+    for _ in range(10):
+        if (p / ".git").exists() or (p / "sst.config.ts").exists():
+            return p
+        if p.parent == p:
+            break
+        p = p.parent
+    # Fallback: assume 4 levels up from scripts/
+    return start.parents[4]
+
+
+def write_json(path: Path, content: str) -> None:
+    # Validate JSON before writing
+    json.loads(content)
+    path.write_text(content)
+
+
+def main() -> int:
+    parser = argparse.ArgumentParser(description="Generate the Opencode Python SDK from OpenAPI spec.")
+    parser.add_argument(
+        "--source", choices=["cli", "server"], default="cli", help="Where to fetch the OpenAPI spec from"
+    )
+    parser.add_argument(
+        "--server-url",
+        default="http://localhost:4096/doc",
+        help="OpenAPI document URL when --source=server",
+    )
+    parser.add_argument(
+        "--out-spec",
+        default=None,
+        help="Output path for the OpenAPI spec (defaults to packages/sdk/python/openapi.json)",
+    )
+    parser.add_argument(
+        "--only-spec",
+        action="store_true",
+        help="Only fetch and write the OpenAPI spec without generating the client",
+    )
+    args = parser.parse_args()
+
+    script_dir = Path(__file__).resolve().parent
+    sdk_dir = script_dir.parent
+    repo_root = find_repo_root(script_dir)
+    opencode_dir = repo_root / "packages" / "opencode"
+
+    openapi_json = Path(args.out_spec) if args.out_spec else (sdk_dir / "openapi.json")
+    build_dir = sdk_dir / ".build"
+    out_pkg_dir = sdk_dir / "src" / "opencode_ai"
+
+    build_dir.mkdir(parents=True, exist_ok=True)
+    (sdk_dir / "src").mkdir(parents=True, exist_ok=True)
+
+    # 1) Obtain OpenAPI spec
+    if args.source == "server":
+        print(f"Fetching OpenAPI spec from {args.server_url} ...")
+        try:
+            with urlopen(args.server_url) as resp:
+                if resp.status != 200:
+                    print(f"ERROR: GET {args.server_url} -> HTTP {resp.status}", file=sys.stderr)
+                    return 1
+                text = resp.read().decode("utf-8")
+        except Exception as e:
+            print(f"ERROR: Failed to fetch from server: {e}", file=sys.stderr)
+            return 1
+        try:
+            write_json(openapi_json, text)
+        except json.JSONDecodeError as je:
+            print("ERROR: Response from server was not valid JSON:", file=sys.stderr)
+            print(str(je), file=sys.stderr)
+            return 1
+        print(f"Wrote OpenAPI spec to {openapi_json}")
+    else:
+        print("Generating OpenAPI spec via 'bun dev generate' ...")
+        try:
+            proc = run(["bun", "dev", "generate"], cwd=opencode_dir)
+        except subprocess.CalledProcessError as e:
+            print(e.stdout)
+            print(e.stderr, file=sys.stderr)
+            print(
+                "ERROR: Failed to run 'bun dev generate'. Ensure Bun is installed and available in PATH.",
+                file=sys.stderr,
+            )
+            return 1
+        try:
+            write_json(openapi_json, proc.stdout)
+        except json.JSONDecodeError as je:
+            print("ERROR: Output from 'bun dev generate' was not valid JSON:", file=sys.stderr)
+            print(str(je), file=sys.stderr)
+            return 1
+        print(f"Wrote OpenAPI spec to {openapi_json}")
+
+    if args.only_spec:
+        print("Spec written; skipping client generation (--only-spec).")
+        return 0
+
+    # 2) Run openapi-python-client
+    print("Running openapi-python-client generate ...")
+    # Prefer uvx if available
+    use_uvx = shutil.which("uvx") is not None
+    cmd = (["uvx", "openapi-python-client", "generate"] if use_uvx else ["openapi-python-client", "generate"]) + [
+        "--path",
+        str(openapi_json),
+        "--output-path",
+        str(build_dir),
+        "--overwrite",
+        "--config",
+        str(sdk_dir / "openapi-python-client.yaml"),
+    ]
+
+    try:
+        run(cmd, cwd=sdk_dir)
+    except subprocess.CalledProcessError as e:
+        print(e.stdout)
+        print(e.stderr, file=sys.stderr)
+        print(
+            "ERROR: Failed to run openapi-python-client. Install uv and try again: curl -LsSf https://astral.sh/uv/install.sh | sh",
+            file=sys.stderr,
+        )
+        return 1
+
+    # 3) Locate generated module directory and copy to src/opencode_ai
+    generated_module: Path | None = None
+    for candidate in build_dir.rglob("__init__.py"):
+        if candidate.parent.name.startswith("."):
+            continue
+        siblings = {p.name for p in candidate.parent.glob("*.py")}
+        if "client.py" in siblings or "api_client.py" in siblings:
+            generated_module = candidate.parent
+            break
+
+    if not generated_module:
+        print("ERROR: Could not locate generated module directory in .build", file=sys.stderr)
+        return 1
+
+    print(f"Found generated module at {generated_module}")
+
+    # Clean target then copy
+    if out_pkg_dir.exists():
+        shutil.rmtree(out_pkg_dir)
+    shutil.copytree(generated_module, out_pkg_dir)
+
+    # Inject local extras from template if present
+    extras_template = sdk_dir / "templates" / "extras.py"
+    if extras_template.exists():
+        (out_pkg_dir / "extras.py").write_text(extras_template.read_text())
+
+    # Patch __init__ to export OpenCodeClient if present
+    init_path = out_pkg_dir / "__init__.py"
+    if init_path.exists() and (out_pkg_dir / "extras.py").exists():
+        init_text = (
+            '"""A client library for accessing opencode\n\n'
+            "This package is generated by openapi-python-client.\n"
+            "A thin convenience wrapper `OpenCodeClient` is also provided.\n"
+            '"""\n\n'
+            "from .client import AuthenticatedClient, Client\n"
+            "from .extras import OpenCodeClient\n\n"
+            "__all__ = (\n"
+            '    "AuthenticatedClient",\n'
+            '    "Client",\n'
+            '    "OpenCodeClient",\n'
+            ")\n"
+        )
+        init_path.write_text(init_text)
+
+    print(f"Copied generated client to {out_pkg_dir}")
+
+    # 4) Format generated code
+    try:
+        run(["uv", "run", "--project", str(sdk_dir), "ruff", "check", "--select", "I", "--fix", str(out_pkg_dir)])
+        run(["uv", "run", "--project", str(sdk_dir), "black", str(out_pkg_dir)])
+    except subprocess.CalledProcessError as e:
+        print("WARNING: formatting failed; continuing", file=sys.stderr)
+        print(e.stdout)
+        print(e.stderr, file=sys.stderr)
+
+    print("Done.")
+    return 0
+
+
+if __name__ == "__main__":
+    raise SystemExit(main())

+ 68 - 0
packages/sdk/python/scripts/publish.py

@@ -0,0 +1,68 @@
+#!/usr/bin/env python3
+"""
+Python SDK publishing helper.
+
+- Builds sdist and wheel using `python -m build` into dist/
+- Uploads using twine. Configure either TestPyPI or PyPI via environment:
+
+Environment variables:
+  REPOSITORY   : "pypi" (default) or "testpypi"
+  PYPI_TOKEN   : API token (e.g., pypi-XXXX). For TestPyPI, use the TestPyPI token.
+
+Examples:
+  REPOSITORY=testpypi PYPI_TOKEN=${{TEST_PYPI_API_TOKEN}} uv run --project packages/sdk/python python packages/sdk/python/scripts/publish.py
+"""
+from __future__ import annotations
+
+import os
+import subprocess
+from pathlib import Path
+
+
+def run(cmd: list[str], cwd: Path | None = None) -> None:
+    print("$", " ".join(cmd))
+    subprocess.run(cmd, cwd=str(cwd) if cwd else None, check=True)
+
+
+def main() -> int:
+    sdk_dir = Path(__file__).resolve().parent.parent
+    repo = os.environ.get("REPOSITORY", "pypi").strip()
+    token = os.environ.get("PYPI_TOKEN")
+    if not token:
+        print("ERROR: PYPI_TOKEN not set", flush=True)
+        return 1
+
+    dist = sdk_dir / "dist"
+    if dist.exists():
+        for f in dist.iterdir():
+            f.unlink()
+
+    # Build
+    run(["python", "-m", "build"], cwd=sdk_dir)
+
+    # Upload
+    repo_url = {
+        "pypi": "https://upload.pypi.org/legacy/",
+        "testpypi": "https://test.pypi.org/legacy/",
+    }.get(repo, repo)
+
+    env = os.environ.copy()
+    env["TWINE_USERNAME"] = "__token__"
+    env["TWINE_PASSWORD"] = token
+
+    print(f"Uploading to {repo_url}")
+    subprocess.run(
+        ["python", "-m", "twine", "check", "dist/*"], cwd=sdk_dir, check=True
+    )
+    subprocess.run(
+        ["python", "-m", "twine", "upload", "--repository-url", repo_url, "dist/*"],
+        cwd=sdk_dir,
+        check=True,
+        env=env,
+    )
+    print("Publish complete")
+    return 0
+
+
+if __name__ == "__main__":
+    raise SystemExit(main())

+ 14 - 0
packages/sdk/python/src/opencode_ai/__init__.py

@@ -0,0 +1,14 @@
+"""A client library for accessing opencode
+
+This package is generated by openapi-python-client.
+A thin convenience wrapper `OpenCodeClient` is also provided.
+"""
+
+from .client import AuthenticatedClient, Client
+from .extras import OpenCodeClient
+
+__all__ = (
+    "AuthenticatedClient",
+    "Client",
+    "OpenCodeClient",
+)

+ 1 - 0
packages/sdk/python/src/opencode_ai/api/__init__.py

@@ -0,0 +1 @@
+"""Contains methods for accessing the API"""

+ 1 - 0
packages/sdk/python/src/opencode_ai/api/default/__init__.py

@@ -0,0 +1 @@
+"""Contains endpoint functions for accessing the API"""

+ 160 - 0
packages/sdk/python/src/opencode_ai/api/default/app_agents.py

@@ -0,0 +1,160 @@
+from http import HTTPStatus
+from typing import Any, Optional, Union
+
+import httpx
+
+from ... import errors
+from ...client import AuthenticatedClient, Client
+from ...models.agent import Agent
+from ...types import UNSET, Response, Unset
+
+
+def _get_kwargs(
+    *,
+    directory: Union[Unset, str] = UNSET,
+) -> dict[str, Any]:
+    params: dict[str, Any] = {}
+
+    params["directory"] = directory
+
+    params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
+
+    _kwargs: dict[str, Any] = {
+        "method": "get",
+        "url": "/agent",
+        "params": params,
+    }
+
+    return _kwargs
+
+
+def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[list["Agent"]]:
+    if response.status_code == 200:
+        response_200 = []
+        _response_200 = response.json()
+        for response_200_item_data in _response_200:
+            response_200_item = Agent.from_dict(response_200_item_data)
+
+            response_200.append(response_200_item)
+
+        return response_200
+
+    if client.raise_on_unexpected_status:
+        raise errors.UnexpectedStatus(response.status_code, response.content)
+    else:
+        return None
+
+
+def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[list["Agent"]]:
+    return Response(
+        status_code=HTTPStatus(response.status_code),
+        content=response.content,
+        headers=response.headers,
+        parsed=_parse_response(client=client, response=response),
+    )
+
+
+def sync_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[list["Agent"]]:
+    """List all agents
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[list['Agent']]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = client.get_httpx_client().request(
+        **kwargs,
+    )
+
+    return _build_response(client=client, response=response)
+
+
+def sync(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[list["Agent"]]:
+    """List all agents
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        list['Agent']
+    """
+
+    return sync_detailed(
+        client=client,
+        directory=directory,
+    ).parsed
+
+
+async def asyncio_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[list["Agent"]]:
+    """List all agents
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[list['Agent']]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = await client.get_async_httpx_client().request(**kwargs)
+
+    return _build_response(client=client, response=response)
+
+
+async def asyncio(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[list["Agent"]]:
+    """List all agents
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        list['Agent']
+    """
+
+    return (
+        await asyncio_detailed(
+            client=client,
+            directory=directory,
+        )
+    ).parsed

+ 164 - 0
packages/sdk/python/src/opencode_ai/api/default/command_list.py

@@ -0,0 +1,164 @@
+from http import HTTPStatus
+from typing import Any, Optional, Union
+
+import httpx
+
+from ... import errors
+from ...client import AuthenticatedClient, Client
+from ...models.command import Command
+from ...types import UNSET, Response, Unset
+
+
+def _get_kwargs(
+    *,
+    directory: Union[Unset, str] = UNSET,
+) -> dict[str, Any]:
+    params: dict[str, Any] = {}
+
+    params["directory"] = directory
+
+    params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
+
+    _kwargs: dict[str, Any] = {
+        "method": "get",
+        "url": "/command",
+        "params": params,
+    }
+
+    return _kwargs
+
+
+def _parse_response(
+    *, client: Union[AuthenticatedClient, Client], response: httpx.Response
+) -> Optional[list["Command"]]:
+    if response.status_code == 200:
+        response_200 = []
+        _response_200 = response.json()
+        for response_200_item_data in _response_200:
+            response_200_item = Command.from_dict(response_200_item_data)
+
+            response_200.append(response_200_item)
+
+        return response_200
+
+    if client.raise_on_unexpected_status:
+        raise errors.UnexpectedStatus(response.status_code, response.content)
+    else:
+        return None
+
+
+def _build_response(
+    *, client: Union[AuthenticatedClient, Client], response: httpx.Response
+) -> Response[list["Command"]]:
+    return Response(
+        status_code=HTTPStatus(response.status_code),
+        content=response.content,
+        headers=response.headers,
+        parsed=_parse_response(client=client, response=response),
+    )
+
+
+def sync_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[list["Command"]]:
+    """List all commands
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[list['Command']]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = client.get_httpx_client().request(
+        **kwargs,
+    )
+
+    return _build_response(client=client, response=response)
+
+
+def sync(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[list["Command"]]:
+    """List all commands
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        list['Command']
+    """
+
+    return sync_detailed(
+        client=client,
+        directory=directory,
+    ).parsed
+
+
+async def asyncio_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[list["Command"]]:
+    """List all commands
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[list['Command']]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = await client.get_async_httpx_client().request(**kwargs)
+
+    return _build_response(client=client, response=response)
+
+
+async def asyncio(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[list["Command"]]:
+    """List all commands
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        list['Command']
+    """
+
+    return (
+        await asyncio_detailed(
+            client=client,
+            directory=directory,
+        )
+    ).parsed

+ 155 - 0
packages/sdk/python/src/opencode_ai/api/default/config_get.py

@@ -0,0 +1,155 @@
+from http import HTTPStatus
+from typing import Any, Optional, Union
+
+import httpx
+
+from ... import errors
+from ...client import AuthenticatedClient, Client
+from ...models.config import Config
+from ...types import UNSET, Response, Unset
+
+
+def _get_kwargs(
+    *,
+    directory: Union[Unset, str] = UNSET,
+) -> dict[str, Any]:
+    params: dict[str, Any] = {}
+
+    params["directory"] = directory
+
+    params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
+
+    _kwargs: dict[str, Any] = {
+        "method": "get",
+        "url": "/config",
+        "params": params,
+    }
+
+    return _kwargs
+
+
+def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Config]:
+    if response.status_code == 200:
+        response_200 = Config.from_dict(response.json())
+
+        return response_200
+
+    if client.raise_on_unexpected_status:
+        raise errors.UnexpectedStatus(response.status_code, response.content)
+    else:
+        return None
+
+
+def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Config]:
+    return Response(
+        status_code=HTTPStatus(response.status_code),
+        content=response.content,
+        headers=response.headers,
+        parsed=_parse_response(client=client, response=response),
+    )
+
+
+def sync_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[Config]:
+    """Get config info
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[Config]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = client.get_httpx_client().request(
+        **kwargs,
+    )
+
+    return _build_response(client=client, response=response)
+
+
+def sync(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[Config]:
+    """Get config info
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Config
+    """
+
+    return sync_detailed(
+        client=client,
+        directory=directory,
+    ).parsed
+
+
+async def asyncio_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[Config]:
+    """Get config info
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[Config]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = await client.get_async_httpx_client().request(**kwargs)
+
+    return _build_response(client=client, response=response)
+
+
+async def asyncio(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[Config]:
+    """Get config info
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Config
+    """
+
+    return (
+        await asyncio_detailed(
+            client=client,
+            directory=directory,
+        )
+    ).parsed

+ 159 - 0
packages/sdk/python/src/opencode_ai/api/default/config_providers.py

@@ -0,0 +1,159 @@
+from http import HTTPStatus
+from typing import Any, Optional, Union
+
+import httpx
+
+from ... import errors
+from ...client import AuthenticatedClient, Client
+from ...models.config_providers_response_200 import ConfigProvidersResponse200
+from ...types import UNSET, Response, Unset
+
+
+def _get_kwargs(
+    *,
+    directory: Union[Unset, str] = UNSET,
+) -> dict[str, Any]:
+    params: dict[str, Any] = {}
+
+    params["directory"] = directory
+
+    params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
+
+    _kwargs: dict[str, Any] = {
+        "method": "get",
+        "url": "/config/providers",
+        "params": params,
+    }
+
+    return _kwargs
+
+
+def _parse_response(
+    *, client: Union[AuthenticatedClient, Client], response: httpx.Response
+) -> Optional[ConfigProvidersResponse200]:
+    if response.status_code == 200:
+        response_200 = ConfigProvidersResponse200.from_dict(response.json())
+
+        return response_200
+
+    if client.raise_on_unexpected_status:
+        raise errors.UnexpectedStatus(response.status_code, response.content)
+    else:
+        return None
+
+
+def _build_response(
+    *, client: Union[AuthenticatedClient, Client], response: httpx.Response
+) -> Response[ConfigProvidersResponse200]:
+    return Response(
+        status_code=HTTPStatus(response.status_code),
+        content=response.content,
+        headers=response.headers,
+        parsed=_parse_response(client=client, response=response),
+    )
+
+
+def sync_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[ConfigProvidersResponse200]:
+    """List all providers
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[ConfigProvidersResponse200]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = client.get_httpx_client().request(
+        **kwargs,
+    )
+
+    return _build_response(client=client, response=response)
+
+
+def sync(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[ConfigProvidersResponse200]:
+    """List all providers
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        ConfigProvidersResponse200
+    """
+
+    return sync_detailed(
+        client=client,
+        directory=directory,
+    ).parsed
+
+
+async def asyncio_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[ConfigProvidersResponse200]:
+    """List all providers
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[ConfigProvidersResponse200]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = await client.get_async_httpx_client().request(**kwargs)
+
+    return _build_response(client=client, response=response)
+
+
+async def asyncio(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[ConfigProvidersResponse200]:
+    """List all providers
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        ConfigProvidersResponse200
+    """
+
+    return (
+        await asyncio_detailed(
+            client=client,
+            directory=directory,
+        )
+    ).parsed

+ 447 - 0
packages/sdk/python/src/opencode_ai/api/default/event_subscribe.py

@@ -0,0 +1,447 @@
+from http import HTTPStatus
+from typing import Any, Optional, Union
+
+import httpx
+
+from ... import errors
+from ...client import AuthenticatedClient, Client
+from ...models.event_file_edited import EventFileEdited
+from ...models.event_file_watcher_updated import EventFileWatcherUpdated
+from ...models.event_ide_installed import EventIdeInstalled
+from ...models.event_installation_updated import EventInstallationUpdated
+from ...models.event_lsp_client_diagnostics import EventLspClientDiagnostics
+from ...models.event_message_part_removed import EventMessagePartRemoved
+from ...models.event_message_part_updated import EventMessagePartUpdated
+from ...models.event_message_removed import EventMessageRemoved
+from ...models.event_message_updated import EventMessageUpdated
+from ...models.event_permission_replied import EventPermissionReplied
+from ...models.event_permission_updated import EventPermissionUpdated
+from ...models.event_server_connected import EventServerConnected
+from ...models.event_session_compacted import EventSessionCompacted
+from ...models.event_session_deleted import EventSessionDeleted
+from ...models.event_session_error import EventSessionError
+from ...models.event_session_idle import EventSessionIdle
+from ...models.event_session_updated import EventSessionUpdated
+from ...types import UNSET, Response, Unset
+
+
+def _get_kwargs(
+    *,
+    directory: Union[Unset, str] = UNSET,
+) -> dict[str, Any]:
+    params: dict[str, Any] = {}
+
+    params["directory"] = directory
+
+    params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
+
+    _kwargs: dict[str, Any] = {
+        "method": "get",
+        "url": "/event",
+        "params": params,
+    }
+
+    return _kwargs
+
+
+def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[
+    Union[
+        "EventFileEdited",
+        "EventFileWatcherUpdated",
+        "EventIdeInstalled",
+        "EventInstallationUpdated",
+        "EventLspClientDiagnostics",
+        "EventMessagePartRemoved",
+        "EventMessagePartUpdated",
+        "EventMessageRemoved",
+        "EventMessageUpdated",
+        "EventPermissionReplied",
+        "EventPermissionUpdated",
+        "EventServerConnected",
+        "EventSessionCompacted",
+        "EventSessionDeleted",
+        "EventSessionError",
+        "EventSessionIdle",
+        "EventSessionUpdated",
+    ]
+]:
+    if response.status_code == 200:
+
+        def _parse_response_200(
+            data: object,
+        ) -> Union[
+            "EventFileEdited",
+            "EventFileWatcherUpdated",
+            "EventIdeInstalled",
+            "EventInstallationUpdated",
+            "EventLspClientDiagnostics",
+            "EventMessagePartRemoved",
+            "EventMessagePartUpdated",
+            "EventMessageRemoved",
+            "EventMessageUpdated",
+            "EventPermissionReplied",
+            "EventPermissionUpdated",
+            "EventServerConnected",
+            "EventSessionCompacted",
+            "EventSessionDeleted",
+            "EventSessionError",
+            "EventSessionIdle",
+            "EventSessionUpdated",
+        ]:
+            try:
+                if not isinstance(data, dict):
+                    raise TypeError()
+                componentsschemas_event_type_0 = EventInstallationUpdated.from_dict(data)
+
+                return componentsschemas_event_type_0
+            except:  # noqa: E722
+                pass
+            try:
+                if not isinstance(data, dict):
+                    raise TypeError()
+                componentsschemas_event_type_1 = EventLspClientDiagnostics.from_dict(data)
+
+                return componentsschemas_event_type_1
+            except:  # noqa: E722
+                pass
+            try:
+                if not isinstance(data, dict):
+                    raise TypeError()
+                componentsschemas_event_type_2 = EventMessageUpdated.from_dict(data)
+
+                return componentsschemas_event_type_2
+            except:  # noqa: E722
+                pass
+            try:
+                if not isinstance(data, dict):
+                    raise TypeError()
+                componentsschemas_event_type_3 = EventMessageRemoved.from_dict(data)
+
+                return componentsschemas_event_type_3
+            except:  # noqa: E722
+                pass
+            try:
+                if not isinstance(data, dict):
+                    raise TypeError()
+                componentsschemas_event_type_4 = EventMessagePartUpdated.from_dict(data)
+
+                return componentsschemas_event_type_4
+            except:  # noqa: E722
+                pass
+            try:
+                if not isinstance(data, dict):
+                    raise TypeError()
+                componentsschemas_event_type_5 = EventMessagePartRemoved.from_dict(data)
+
+                return componentsschemas_event_type_5
+            except:  # noqa: E722
+                pass
+            try:
+                if not isinstance(data, dict):
+                    raise TypeError()
+                componentsschemas_event_type_6 = EventSessionCompacted.from_dict(data)
+
+                return componentsschemas_event_type_6
+            except:  # noqa: E722
+                pass
+            try:
+                if not isinstance(data, dict):
+                    raise TypeError()
+                componentsschemas_event_type_7 = EventPermissionUpdated.from_dict(data)
+
+                return componentsschemas_event_type_7
+            except:  # noqa: E722
+                pass
+            try:
+                if not isinstance(data, dict):
+                    raise TypeError()
+                componentsschemas_event_type_8 = EventPermissionReplied.from_dict(data)
+
+                return componentsschemas_event_type_8
+            except:  # noqa: E722
+                pass
+            try:
+                if not isinstance(data, dict):
+                    raise TypeError()
+                componentsschemas_event_type_9 = EventFileEdited.from_dict(data)
+
+                return componentsschemas_event_type_9
+            except:  # noqa: E722
+                pass
+            try:
+                if not isinstance(data, dict):
+                    raise TypeError()
+                componentsschemas_event_type_10 = EventSessionIdle.from_dict(data)
+
+                return componentsschemas_event_type_10
+            except:  # noqa: E722
+                pass
+            try:
+                if not isinstance(data, dict):
+                    raise TypeError()
+                componentsschemas_event_type_11 = EventSessionUpdated.from_dict(data)
+
+                return componentsschemas_event_type_11
+            except:  # noqa: E722
+                pass
+            try:
+                if not isinstance(data, dict):
+                    raise TypeError()
+                componentsschemas_event_type_12 = EventSessionDeleted.from_dict(data)
+
+                return componentsschemas_event_type_12
+            except:  # noqa: E722
+                pass
+            try:
+                if not isinstance(data, dict):
+                    raise TypeError()
+                componentsschemas_event_type_13 = EventSessionError.from_dict(data)
+
+                return componentsschemas_event_type_13
+            except:  # noqa: E722
+                pass
+            try:
+                if not isinstance(data, dict):
+                    raise TypeError()
+                componentsschemas_event_type_14 = EventFileWatcherUpdated.from_dict(data)
+
+                return componentsschemas_event_type_14
+            except:  # noqa: E722
+                pass
+            try:
+                if not isinstance(data, dict):
+                    raise TypeError()
+                componentsschemas_event_type_15 = EventServerConnected.from_dict(data)
+
+                return componentsschemas_event_type_15
+            except:  # noqa: E722
+                pass
+            if not isinstance(data, dict):
+                raise TypeError()
+            componentsschemas_event_type_16 = EventIdeInstalled.from_dict(data)
+
+            return componentsschemas_event_type_16
+
+        response_200 = _parse_response_200(response.text)
+
+        return response_200
+
+    if client.raise_on_unexpected_status:
+        raise errors.UnexpectedStatus(response.status_code, response.content)
+    else:
+        return None
+
+
+def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[
+    Union[
+        "EventFileEdited",
+        "EventFileWatcherUpdated",
+        "EventIdeInstalled",
+        "EventInstallationUpdated",
+        "EventLspClientDiagnostics",
+        "EventMessagePartRemoved",
+        "EventMessagePartUpdated",
+        "EventMessageRemoved",
+        "EventMessageUpdated",
+        "EventPermissionReplied",
+        "EventPermissionUpdated",
+        "EventServerConnected",
+        "EventSessionCompacted",
+        "EventSessionDeleted",
+        "EventSessionError",
+        "EventSessionIdle",
+        "EventSessionUpdated",
+    ]
+]:
+    return Response(
+        status_code=HTTPStatus(response.status_code),
+        content=response.content,
+        headers=response.headers,
+        parsed=_parse_response(client=client, response=response),
+    )
+
+
+def sync_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[
+    Union[
+        "EventFileEdited",
+        "EventFileWatcherUpdated",
+        "EventIdeInstalled",
+        "EventInstallationUpdated",
+        "EventLspClientDiagnostics",
+        "EventMessagePartRemoved",
+        "EventMessagePartUpdated",
+        "EventMessageRemoved",
+        "EventMessageUpdated",
+        "EventPermissionReplied",
+        "EventPermissionUpdated",
+        "EventServerConnected",
+        "EventSessionCompacted",
+        "EventSessionDeleted",
+        "EventSessionError",
+        "EventSessionIdle",
+        "EventSessionUpdated",
+    ]
+]:
+    """Get events
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[Union['EventFileEdited', 'EventFileWatcherUpdated', 'EventIdeInstalled', 'EventInstallationUpdated', 'EventLspClientDiagnostics', 'EventMessagePartRemoved', 'EventMessagePartUpdated', 'EventMessageRemoved', 'EventMessageUpdated', 'EventPermissionReplied', 'EventPermissionUpdated', 'EventServerConnected', 'EventSessionCompacted', 'EventSessionDeleted', 'EventSessionError', 'EventSessionIdle', 'EventSessionUpdated']]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = client.get_httpx_client().request(
+        **kwargs,
+    )
+
+    return _build_response(client=client, response=response)
+
+
+def sync(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[
+    Union[
+        "EventFileEdited",
+        "EventFileWatcherUpdated",
+        "EventIdeInstalled",
+        "EventInstallationUpdated",
+        "EventLspClientDiagnostics",
+        "EventMessagePartRemoved",
+        "EventMessagePartUpdated",
+        "EventMessageRemoved",
+        "EventMessageUpdated",
+        "EventPermissionReplied",
+        "EventPermissionUpdated",
+        "EventServerConnected",
+        "EventSessionCompacted",
+        "EventSessionDeleted",
+        "EventSessionError",
+        "EventSessionIdle",
+        "EventSessionUpdated",
+    ]
+]:
+    """Get events
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Union['EventFileEdited', 'EventFileWatcherUpdated', 'EventIdeInstalled', 'EventInstallationUpdated', 'EventLspClientDiagnostics', 'EventMessagePartRemoved', 'EventMessagePartUpdated', 'EventMessageRemoved', 'EventMessageUpdated', 'EventPermissionReplied', 'EventPermissionUpdated', 'EventServerConnected', 'EventSessionCompacted', 'EventSessionDeleted', 'EventSessionError', 'EventSessionIdle', 'EventSessionUpdated']
+    """
+
+    return sync_detailed(
+        client=client,
+        directory=directory,
+    ).parsed
+
+
+async def asyncio_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[
+    Union[
+        "EventFileEdited",
+        "EventFileWatcherUpdated",
+        "EventIdeInstalled",
+        "EventInstallationUpdated",
+        "EventLspClientDiagnostics",
+        "EventMessagePartRemoved",
+        "EventMessagePartUpdated",
+        "EventMessageRemoved",
+        "EventMessageUpdated",
+        "EventPermissionReplied",
+        "EventPermissionUpdated",
+        "EventServerConnected",
+        "EventSessionCompacted",
+        "EventSessionDeleted",
+        "EventSessionError",
+        "EventSessionIdle",
+        "EventSessionUpdated",
+    ]
+]:
+    """Get events
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[Union['EventFileEdited', 'EventFileWatcherUpdated', 'EventIdeInstalled', 'EventInstallationUpdated', 'EventLspClientDiagnostics', 'EventMessagePartRemoved', 'EventMessagePartUpdated', 'EventMessageRemoved', 'EventMessageUpdated', 'EventPermissionReplied', 'EventPermissionUpdated', 'EventServerConnected', 'EventSessionCompacted', 'EventSessionDeleted', 'EventSessionError', 'EventSessionIdle', 'EventSessionUpdated']]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = await client.get_async_httpx_client().request(**kwargs)
+
+    return _build_response(client=client, response=response)
+
+
+async def asyncio(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[
+    Union[
+        "EventFileEdited",
+        "EventFileWatcherUpdated",
+        "EventIdeInstalled",
+        "EventInstallationUpdated",
+        "EventLspClientDiagnostics",
+        "EventMessagePartRemoved",
+        "EventMessagePartUpdated",
+        "EventMessageRemoved",
+        "EventMessageUpdated",
+        "EventPermissionReplied",
+        "EventPermissionUpdated",
+        "EventServerConnected",
+        "EventSessionCompacted",
+        "EventSessionDeleted",
+        "EventSessionError",
+        "EventSessionIdle",
+        "EventSessionUpdated",
+    ]
+]:
+    """Get events
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Union['EventFileEdited', 'EventFileWatcherUpdated', 'EventIdeInstalled', 'EventInstallationUpdated', 'EventLspClientDiagnostics', 'EventMessagePartRemoved', 'EventMessagePartUpdated', 'EventMessageRemoved', 'EventMessageUpdated', 'EventPermissionReplied', 'EventPermissionUpdated', 'EventServerConnected', 'EventSessionCompacted', 'EventSessionDeleted', 'EventSessionError', 'EventSessionIdle', 'EventSessionUpdated']
+    """
+
+    return (
+        await asyncio_detailed(
+            client=client,
+            directory=directory,
+        )
+    ).parsed

+ 160 - 0
packages/sdk/python/src/opencode_ai/api/default/file_status.py

@@ -0,0 +1,160 @@
+from http import HTTPStatus
+from typing import Any, Optional, Union
+
+import httpx
+
+from ... import errors
+from ...client import AuthenticatedClient, Client
+from ...models.file import File
+from ...types import UNSET, Response, Unset
+
+
+def _get_kwargs(
+    *,
+    directory: Union[Unset, str] = UNSET,
+) -> dict[str, Any]:
+    params: dict[str, Any] = {}
+
+    params["directory"] = directory
+
+    params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
+
+    _kwargs: dict[str, Any] = {
+        "method": "get",
+        "url": "/file/status",
+        "params": params,
+    }
+
+    return _kwargs
+
+
+def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[list["File"]]:
+    if response.status_code == 200:
+        response_200 = []
+        _response_200 = response.json()
+        for response_200_item_data in _response_200:
+            response_200_item = File.from_dict(response_200_item_data)
+
+            response_200.append(response_200_item)
+
+        return response_200
+
+    if client.raise_on_unexpected_status:
+        raise errors.UnexpectedStatus(response.status_code, response.content)
+    else:
+        return None
+
+
+def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[list["File"]]:
+    return Response(
+        status_code=HTTPStatus(response.status_code),
+        content=response.content,
+        headers=response.headers,
+        parsed=_parse_response(client=client, response=response),
+    )
+
+
+def sync_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[list["File"]]:
+    """Get file status
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[list['File']]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = client.get_httpx_client().request(
+        **kwargs,
+    )
+
+    return _build_response(client=client, response=response)
+
+
+def sync(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[list["File"]]:
+    """Get file status
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        list['File']
+    """
+
+    return sync_detailed(
+        client=client,
+        directory=directory,
+    ).parsed
+
+
+async def asyncio_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[list["File"]]:
+    """Get file status
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[list['File']]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = await client.get_async_httpx_client().request(**kwargs)
+
+    return _build_response(client=client, response=response)
+
+
+async def asyncio(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[list["File"]]:
+    """Get file status
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        list['File']
+    """
+
+    return (
+        await asyncio_detailed(
+            client=client,
+            directory=directory,
+        )
+    ).parsed

+ 155 - 0
packages/sdk/python/src/opencode_ai/api/default/path_get.py

@@ -0,0 +1,155 @@
+from http import HTTPStatus
+from typing import Any, Optional, Union
+
+import httpx
+
+from ... import errors
+from ...client import AuthenticatedClient, Client
+from ...models.path import Path
+from ...types import UNSET, Response, Unset
+
+
+def _get_kwargs(
+    *,
+    directory: Union[Unset, str] = UNSET,
+) -> dict[str, Any]:
+    params: dict[str, Any] = {}
+
+    params["directory"] = directory
+
+    params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
+
+    _kwargs: dict[str, Any] = {
+        "method": "get",
+        "url": "/path",
+        "params": params,
+    }
+
+    return _kwargs
+
+
+def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Path]:
+    if response.status_code == 200:
+        response_200 = Path.from_dict(response.json())
+
+        return response_200
+
+    if client.raise_on_unexpected_status:
+        raise errors.UnexpectedStatus(response.status_code, response.content)
+    else:
+        return None
+
+
+def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Path]:
+    return Response(
+        status_code=HTTPStatus(response.status_code),
+        content=response.content,
+        headers=response.headers,
+        parsed=_parse_response(client=client, response=response),
+    )
+
+
+def sync_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[Path]:
+    """Get the current path
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[Path]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = client.get_httpx_client().request(
+        **kwargs,
+    )
+
+    return _build_response(client=client, response=response)
+
+
+def sync(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[Path]:
+    """Get the current path
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Path
+    """
+
+    return sync_detailed(
+        client=client,
+        directory=directory,
+    ).parsed
+
+
+async def asyncio_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[Path]:
+    """Get the current path
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[Path]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = await client.get_async_httpx_client().request(**kwargs)
+
+    return _build_response(client=client, response=response)
+
+
+async def asyncio(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[Path]:
+    """Get the current path
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Path
+    """
+
+    return (
+        await asyncio_detailed(
+            client=client,
+            directory=directory,
+        )
+    ).parsed

+ 155 - 0
packages/sdk/python/src/opencode_ai/api/default/project_current.py

@@ -0,0 +1,155 @@
+from http import HTTPStatus
+from typing import Any, Optional, Union
+
+import httpx
+
+from ... import errors
+from ...client import AuthenticatedClient, Client
+from ...models.project import Project
+from ...types import UNSET, Response, Unset
+
+
+def _get_kwargs(
+    *,
+    directory: Union[Unset, str] = UNSET,
+) -> dict[str, Any]:
+    params: dict[str, Any] = {}
+
+    params["directory"] = directory
+
+    params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
+
+    _kwargs: dict[str, Any] = {
+        "method": "get",
+        "url": "/project/current",
+        "params": params,
+    }
+
+    return _kwargs
+
+
+def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Project]:
+    if response.status_code == 200:
+        response_200 = Project.from_dict(response.json())
+
+        return response_200
+
+    if client.raise_on_unexpected_status:
+        raise errors.UnexpectedStatus(response.status_code, response.content)
+    else:
+        return None
+
+
+def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Project]:
+    return Response(
+        status_code=HTTPStatus(response.status_code),
+        content=response.content,
+        headers=response.headers,
+        parsed=_parse_response(client=client, response=response),
+    )
+
+
+def sync_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[Project]:
+    """Get the current project
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[Project]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = client.get_httpx_client().request(
+        **kwargs,
+    )
+
+    return _build_response(client=client, response=response)
+
+
+def sync(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[Project]:
+    """Get the current project
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Project
+    """
+
+    return sync_detailed(
+        client=client,
+        directory=directory,
+    ).parsed
+
+
+async def asyncio_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[Project]:
+    """Get the current project
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[Project]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = await client.get_async_httpx_client().request(**kwargs)
+
+    return _build_response(client=client, response=response)
+
+
+async def asyncio(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[Project]:
+    """Get the current project
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Project
+    """
+
+    return (
+        await asyncio_detailed(
+            client=client,
+            directory=directory,
+        )
+    ).parsed

+ 164 - 0
packages/sdk/python/src/opencode_ai/api/default/project_list.py

@@ -0,0 +1,164 @@
+from http import HTTPStatus
+from typing import Any, Optional, Union
+
+import httpx
+
+from ... import errors
+from ...client import AuthenticatedClient, Client
+from ...models.project import Project
+from ...types import UNSET, Response, Unset
+
+
+def _get_kwargs(
+    *,
+    directory: Union[Unset, str] = UNSET,
+) -> dict[str, Any]:
+    params: dict[str, Any] = {}
+
+    params["directory"] = directory
+
+    params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
+
+    _kwargs: dict[str, Any] = {
+        "method": "get",
+        "url": "/project",
+        "params": params,
+    }
+
+    return _kwargs
+
+
+def _parse_response(
+    *, client: Union[AuthenticatedClient, Client], response: httpx.Response
+) -> Optional[list["Project"]]:
+    if response.status_code == 200:
+        response_200 = []
+        _response_200 = response.json()
+        for response_200_item_data in _response_200:
+            response_200_item = Project.from_dict(response_200_item_data)
+
+            response_200.append(response_200_item)
+
+        return response_200
+
+    if client.raise_on_unexpected_status:
+        raise errors.UnexpectedStatus(response.status_code, response.content)
+    else:
+        return None
+
+
+def _build_response(
+    *, client: Union[AuthenticatedClient, Client], response: httpx.Response
+) -> Response[list["Project"]]:
+    return Response(
+        status_code=HTTPStatus(response.status_code),
+        content=response.content,
+        headers=response.headers,
+        parsed=_parse_response(client=client, response=response),
+    )
+
+
+def sync_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[list["Project"]]:
+    """List all projects
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[list['Project']]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = client.get_httpx_client().request(
+        **kwargs,
+    )
+
+    return _build_response(client=client, response=response)
+
+
+def sync(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[list["Project"]]:
+    """List all projects
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        list['Project']
+    """
+
+    return sync_detailed(
+        client=client,
+        directory=directory,
+    ).parsed
+
+
+async def asyncio_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[list["Project"]]:
+    """List all projects
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[list['Project']]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = await client.get_async_httpx_client().request(**kwargs)
+
+    return _build_response(client=client, response=response)
+
+
+async def asyncio(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[list["Project"]]:
+    """List all projects
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        list['Project']
+    """
+
+    return (
+        await asyncio_detailed(
+            client=client,
+            directory=directory,
+        )
+    ).parsed

+ 164 - 0
packages/sdk/python/src/opencode_ai/api/default/session_list.py

@@ -0,0 +1,164 @@
+from http import HTTPStatus
+from typing import Any, Optional, Union
+
+import httpx
+
+from ... import errors
+from ...client import AuthenticatedClient, Client
+from ...models.session import Session
+from ...types import UNSET, Response, Unset
+
+
+def _get_kwargs(
+    *,
+    directory: Union[Unset, str] = UNSET,
+) -> dict[str, Any]:
+    params: dict[str, Any] = {}
+
+    params["directory"] = directory
+
+    params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
+
+    _kwargs: dict[str, Any] = {
+        "method": "get",
+        "url": "/session",
+        "params": params,
+    }
+
+    return _kwargs
+
+
+def _parse_response(
+    *, client: Union[AuthenticatedClient, Client], response: httpx.Response
+) -> Optional[list["Session"]]:
+    if response.status_code == 200:
+        response_200 = []
+        _response_200 = response.json()
+        for response_200_item_data in _response_200:
+            response_200_item = Session.from_dict(response_200_item_data)
+
+            response_200.append(response_200_item)
+
+        return response_200
+
+    if client.raise_on_unexpected_status:
+        raise errors.UnexpectedStatus(response.status_code, response.content)
+    else:
+        return None
+
+
+def _build_response(
+    *, client: Union[AuthenticatedClient, Client], response: httpx.Response
+) -> Response[list["Session"]]:
+    return Response(
+        status_code=HTTPStatus(response.status_code),
+        content=response.content,
+        headers=response.headers,
+        parsed=_parse_response(client=client, response=response),
+    )
+
+
+def sync_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[list["Session"]]:
+    """List all sessions
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[list['Session']]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = client.get_httpx_client().request(
+        **kwargs,
+    )
+
+    return _build_response(client=client, response=response)
+
+
+def sync(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[list["Session"]]:
+    """List all sessions
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        list['Session']
+    """
+
+    return sync_detailed(
+        client=client,
+        directory=directory,
+    ).parsed
+
+
+async def asyncio_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[list["Session"]]:
+    """List all sessions
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[list['Session']]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = await client.get_async_httpx_client().request(**kwargs)
+
+    return _build_response(client=client, response=response)
+
+
+async def asyncio(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[list["Session"]]:
+    """List all sessions
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        list['Session']
+    """
+
+    return (
+        await asyncio_detailed(
+            client=client,
+            directory=directory,
+        )
+    ).parsed

+ 164 - 0
packages/sdk/python/src/opencode_ai/api/default/tool_ids.py

@@ -0,0 +1,164 @@
+from http import HTTPStatus
+from typing import Any, Optional, Union, cast
+
+import httpx
+
+from ... import errors
+from ...client import AuthenticatedClient, Client
+from ...models.error import Error
+from ...types import UNSET, Response, Unset
+
+
+def _get_kwargs(
+    *,
+    directory: Union[Unset, str] = UNSET,
+) -> dict[str, Any]:
+    params: dict[str, Any] = {}
+
+    params["directory"] = directory
+
+    params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
+
+    _kwargs: dict[str, Any] = {
+        "method": "get",
+        "url": "/experimental/tool/ids",
+        "params": params,
+    }
+
+    return _kwargs
+
+
+def _parse_response(
+    *, client: Union[AuthenticatedClient, Client], response: httpx.Response
+) -> Optional[Union[Error, list[str]]]:
+    if response.status_code == 200:
+        response_200 = cast(list[str], response.json())
+
+        return response_200
+
+    if response.status_code == 400:
+        response_400 = Error.from_dict(response.json())
+
+        return response_400
+
+    if client.raise_on_unexpected_status:
+        raise errors.UnexpectedStatus(response.status_code, response.content)
+    else:
+        return None
+
+
+def _build_response(
+    *, client: Union[AuthenticatedClient, Client], response: httpx.Response
+) -> Response[Union[Error, list[str]]]:
+    return Response(
+        status_code=HTTPStatus(response.status_code),
+        content=response.content,
+        headers=response.headers,
+        parsed=_parse_response(client=client, response=response),
+    )
+
+
+def sync_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[Union[Error, list[str]]]:
+    """List all tool IDs (including built-in and dynamically registered)
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[Union[Error, list[str]]]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = client.get_httpx_client().request(
+        **kwargs,
+    )
+
+    return _build_response(client=client, response=response)
+
+
+def sync(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[Union[Error, list[str]]]:
+    """List all tool IDs (including built-in and dynamically registered)
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Union[Error, list[str]]
+    """
+
+    return sync_detailed(
+        client=client,
+        directory=directory,
+    ).parsed
+
+
+async def asyncio_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[Union[Error, list[str]]]:
+    """List all tool IDs (including built-in and dynamically registered)
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[Union[Error, list[str]]]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = await client.get_async_httpx_client().request(**kwargs)
+
+    return _build_response(client=client, response=response)
+
+
+async def asyncio(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[Union[Error, list[str]]]:
+    """List all tool IDs (including built-in and dynamically registered)
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Union[Error, list[str]]
+    """
+
+    return (
+        await asyncio_detailed(
+            client=client,
+            directory=directory,
+        )
+    ).parsed

+ 153 - 0
packages/sdk/python/src/opencode_ai/api/default/tui_clear_prompt.py

@@ -0,0 +1,153 @@
+from http import HTTPStatus
+from typing import Any, Optional, Union, cast
+
+import httpx
+
+from ... import errors
+from ...client import AuthenticatedClient, Client
+from ...types import UNSET, Response, Unset
+
+
+def _get_kwargs(
+    *,
+    directory: Union[Unset, str] = UNSET,
+) -> dict[str, Any]:
+    params: dict[str, Any] = {}
+
+    params["directory"] = directory
+
+    params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
+
+    _kwargs: dict[str, Any] = {
+        "method": "post",
+        "url": "/tui/clear-prompt",
+        "params": params,
+    }
+
+    return _kwargs
+
+
+def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[bool]:
+    if response.status_code == 200:
+        response_200 = cast(bool, response.json())
+        return response_200
+
+    if client.raise_on_unexpected_status:
+        raise errors.UnexpectedStatus(response.status_code, response.content)
+    else:
+        return None
+
+
+def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[bool]:
+    return Response(
+        status_code=HTTPStatus(response.status_code),
+        content=response.content,
+        headers=response.headers,
+        parsed=_parse_response(client=client, response=response),
+    )
+
+
+def sync_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[bool]:
+    """Clear the prompt
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[bool]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = client.get_httpx_client().request(
+        **kwargs,
+    )
+
+    return _build_response(client=client, response=response)
+
+
+def sync(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[bool]:
+    """Clear the prompt
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        bool
+    """
+
+    return sync_detailed(
+        client=client,
+        directory=directory,
+    ).parsed
+
+
+async def asyncio_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[bool]:
+    """Clear the prompt
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[bool]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = await client.get_async_httpx_client().request(**kwargs)
+
+    return _build_response(client=client, response=response)
+
+
+async def asyncio(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[bool]:
+    """Clear the prompt
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        bool
+    """
+
+    return (
+        await asyncio_detailed(
+            client=client,
+            directory=directory,
+        )
+    ).parsed

+ 153 - 0
packages/sdk/python/src/opencode_ai/api/default/tui_open_help.py

@@ -0,0 +1,153 @@
+from http import HTTPStatus
+from typing import Any, Optional, Union, cast
+
+import httpx
+
+from ... import errors
+from ...client import AuthenticatedClient, Client
+from ...types import UNSET, Response, Unset
+
+
+def _get_kwargs(
+    *,
+    directory: Union[Unset, str] = UNSET,
+) -> dict[str, Any]:
+    params: dict[str, Any] = {}
+
+    params["directory"] = directory
+
+    params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
+
+    _kwargs: dict[str, Any] = {
+        "method": "post",
+        "url": "/tui/open-help",
+        "params": params,
+    }
+
+    return _kwargs
+
+
+def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[bool]:
+    if response.status_code == 200:
+        response_200 = cast(bool, response.json())
+        return response_200
+
+    if client.raise_on_unexpected_status:
+        raise errors.UnexpectedStatus(response.status_code, response.content)
+    else:
+        return None
+
+
+def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[bool]:
+    return Response(
+        status_code=HTTPStatus(response.status_code),
+        content=response.content,
+        headers=response.headers,
+        parsed=_parse_response(client=client, response=response),
+    )
+
+
+def sync_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[bool]:
+    """Open the help dialog
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[bool]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = client.get_httpx_client().request(
+        **kwargs,
+    )
+
+    return _build_response(client=client, response=response)
+
+
+def sync(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[bool]:
+    """Open the help dialog
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        bool
+    """
+
+    return sync_detailed(
+        client=client,
+        directory=directory,
+    ).parsed
+
+
+async def asyncio_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[bool]:
+    """Open the help dialog
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[bool]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = await client.get_async_httpx_client().request(**kwargs)
+
+    return _build_response(client=client, response=response)
+
+
+async def asyncio(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[bool]:
+    """Open the help dialog
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        bool
+    """
+
+    return (
+        await asyncio_detailed(
+            client=client,
+            directory=directory,
+        )
+    ).parsed

+ 153 - 0
packages/sdk/python/src/opencode_ai/api/default/tui_open_models.py

@@ -0,0 +1,153 @@
+from http import HTTPStatus
+from typing import Any, Optional, Union, cast
+
+import httpx
+
+from ... import errors
+from ...client import AuthenticatedClient, Client
+from ...types import UNSET, Response, Unset
+
+
+def _get_kwargs(
+    *,
+    directory: Union[Unset, str] = UNSET,
+) -> dict[str, Any]:
+    params: dict[str, Any] = {}
+
+    params["directory"] = directory
+
+    params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
+
+    _kwargs: dict[str, Any] = {
+        "method": "post",
+        "url": "/tui/open-models",
+        "params": params,
+    }
+
+    return _kwargs
+
+
+def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[bool]:
+    if response.status_code == 200:
+        response_200 = cast(bool, response.json())
+        return response_200
+
+    if client.raise_on_unexpected_status:
+        raise errors.UnexpectedStatus(response.status_code, response.content)
+    else:
+        return None
+
+
+def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[bool]:
+    return Response(
+        status_code=HTTPStatus(response.status_code),
+        content=response.content,
+        headers=response.headers,
+        parsed=_parse_response(client=client, response=response),
+    )
+
+
+def sync_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[bool]:
+    """Open the model dialog
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[bool]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = client.get_httpx_client().request(
+        **kwargs,
+    )
+
+    return _build_response(client=client, response=response)
+
+
+def sync(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[bool]:
+    """Open the model dialog
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        bool
+    """
+
+    return sync_detailed(
+        client=client,
+        directory=directory,
+    ).parsed
+
+
+async def asyncio_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[bool]:
+    """Open the model dialog
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[bool]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = await client.get_async_httpx_client().request(**kwargs)
+
+    return _build_response(client=client, response=response)
+
+
+async def asyncio(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[bool]:
+    """Open the model dialog
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        bool
+    """
+
+    return (
+        await asyncio_detailed(
+            client=client,
+            directory=directory,
+        )
+    ).parsed

+ 153 - 0
packages/sdk/python/src/opencode_ai/api/default/tui_open_sessions.py

@@ -0,0 +1,153 @@
+from http import HTTPStatus
+from typing import Any, Optional, Union, cast
+
+import httpx
+
+from ... import errors
+from ...client import AuthenticatedClient, Client
+from ...types import UNSET, Response, Unset
+
+
+def _get_kwargs(
+    *,
+    directory: Union[Unset, str] = UNSET,
+) -> dict[str, Any]:
+    params: dict[str, Any] = {}
+
+    params["directory"] = directory
+
+    params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
+
+    _kwargs: dict[str, Any] = {
+        "method": "post",
+        "url": "/tui/open-sessions",
+        "params": params,
+    }
+
+    return _kwargs
+
+
+def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[bool]:
+    if response.status_code == 200:
+        response_200 = cast(bool, response.json())
+        return response_200
+
+    if client.raise_on_unexpected_status:
+        raise errors.UnexpectedStatus(response.status_code, response.content)
+    else:
+        return None
+
+
+def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[bool]:
+    return Response(
+        status_code=HTTPStatus(response.status_code),
+        content=response.content,
+        headers=response.headers,
+        parsed=_parse_response(client=client, response=response),
+    )
+
+
+def sync_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[bool]:
+    """Open the session dialog
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[bool]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = client.get_httpx_client().request(
+        **kwargs,
+    )
+
+    return _build_response(client=client, response=response)
+
+
+def sync(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[bool]:
+    """Open the session dialog
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        bool
+    """
+
+    return sync_detailed(
+        client=client,
+        directory=directory,
+    ).parsed
+
+
+async def asyncio_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[bool]:
+    """Open the session dialog
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[bool]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = await client.get_async_httpx_client().request(**kwargs)
+
+    return _build_response(client=client, response=response)
+
+
+async def asyncio(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[bool]:
+    """Open the session dialog
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        bool
+    """
+
+    return (
+        await asyncio_detailed(
+            client=client,
+            directory=directory,
+        )
+    ).parsed

+ 153 - 0
packages/sdk/python/src/opencode_ai/api/default/tui_open_themes.py

@@ -0,0 +1,153 @@
+from http import HTTPStatus
+from typing import Any, Optional, Union, cast
+
+import httpx
+
+from ... import errors
+from ...client import AuthenticatedClient, Client
+from ...types import UNSET, Response, Unset
+
+
+def _get_kwargs(
+    *,
+    directory: Union[Unset, str] = UNSET,
+) -> dict[str, Any]:
+    params: dict[str, Any] = {}
+
+    params["directory"] = directory
+
+    params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
+
+    _kwargs: dict[str, Any] = {
+        "method": "post",
+        "url": "/tui/open-themes",
+        "params": params,
+    }
+
+    return _kwargs
+
+
+def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[bool]:
+    if response.status_code == 200:
+        response_200 = cast(bool, response.json())
+        return response_200
+
+    if client.raise_on_unexpected_status:
+        raise errors.UnexpectedStatus(response.status_code, response.content)
+    else:
+        return None
+
+
+def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[bool]:
+    return Response(
+        status_code=HTTPStatus(response.status_code),
+        content=response.content,
+        headers=response.headers,
+        parsed=_parse_response(client=client, response=response),
+    )
+
+
+def sync_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[bool]:
+    """Open the theme dialog
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[bool]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = client.get_httpx_client().request(
+        **kwargs,
+    )
+
+    return _build_response(client=client, response=response)
+
+
+def sync(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[bool]:
+    """Open the theme dialog
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        bool
+    """
+
+    return sync_detailed(
+        client=client,
+        directory=directory,
+    ).parsed
+
+
+async def asyncio_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[bool]:
+    """Open the theme dialog
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[bool]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = await client.get_async_httpx_client().request(**kwargs)
+
+    return _build_response(client=client, response=response)
+
+
+async def asyncio(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[bool]:
+    """Open the theme dialog
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        bool
+    """
+
+    return (
+        await asyncio_detailed(
+            client=client,
+            directory=directory,
+        )
+    ).parsed

+ 153 - 0
packages/sdk/python/src/opencode_ai/api/default/tui_submit_prompt.py

@@ -0,0 +1,153 @@
+from http import HTTPStatus
+from typing import Any, Optional, Union, cast
+
+import httpx
+
+from ... import errors
+from ...client import AuthenticatedClient, Client
+from ...types import UNSET, Response, Unset
+
+
+def _get_kwargs(
+    *,
+    directory: Union[Unset, str] = UNSET,
+) -> dict[str, Any]:
+    params: dict[str, Any] = {}
+
+    params["directory"] = directory
+
+    params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
+
+    _kwargs: dict[str, Any] = {
+        "method": "post",
+        "url": "/tui/submit-prompt",
+        "params": params,
+    }
+
+    return _kwargs
+
+
+def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[bool]:
+    if response.status_code == 200:
+        response_200 = cast(bool, response.json())
+        return response_200
+
+    if client.raise_on_unexpected_status:
+        raise errors.UnexpectedStatus(response.status_code, response.content)
+    else:
+        return None
+
+
+def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[bool]:
+    return Response(
+        status_code=HTTPStatus(response.status_code),
+        content=response.content,
+        headers=response.headers,
+        parsed=_parse_response(client=client, response=response),
+    )
+
+
+def sync_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[bool]:
+    """Submit the prompt
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[bool]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = client.get_httpx_client().request(
+        **kwargs,
+    )
+
+    return _build_response(client=client, response=response)
+
+
+def sync(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[bool]:
+    """Submit the prompt
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        bool
+    """
+
+    return sync_detailed(
+        client=client,
+        directory=directory,
+    ).parsed
+
+
+async def asyncio_detailed(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Response[bool]:
+    """Submit the prompt
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        Response[bool]
+    """
+
+    kwargs = _get_kwargs(
+        directory=directory,
+    )
+
+    response = await client.get_async_httpx_client().request(**kwargs)
+
+    return _build_response(client=client, response=response)
+
+
+async def asyncio(
+    *,
+    client: Union[AuthenticatedClient, Client],
+    directory: Union[Unset, str] = UNSET,
+) -> Optional[bool]:
+    """Submit the prompt
+
+    Args:
+        directory (Union[Unset, str]):
+
+    Raises:
+        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+        httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+    Returns:
+        bool
+    """
+
+    return (
+        await asyncio_detailed(
+            client=client,
+            directory=directory,
+        )
+    ).parsed

+ 268 - 0
packages/sdk/python/src/opencode_ai/client.py

@@ -0,0 +1,268 @@
+import ssl
+from typing import Any, Optional, Union
+
+import httpx
+from attrs import define, evolve, field
+
+
+@define
+class Client:
+    """A class for keeping track of data related to the API
+
+    The following are accepted as keyword arguments and will be used to construct httpx Clients internally:
+
+        ``base_url``: The base URL for the API, all requests are made to a relative path to this URL
+
+        ``cookies``: A dictionary of cookies to be sent with every request
+
+        ``headers``: A dictionary of headers to be sent with every request
+
+        ``timeout``: The maximum amount of a time a request can take. API functions will raise
+        httpx.TimeoutException if this is exceeded.
+
+        ``verify_ssl``: Whether or not to verify the SSL certificate of the API server. This should be True in production,
+        but can be set to False for testing purposes.
+
+        ``follow_redirects``: Whether or not to follow redirects. Default value is False.
+
+        ``httpx_args``: A dictionary of additional arguments to be passed to the ``httpx.Client`` and ``httpx.AsyncClient`` constructor.
+
+
+    Attributes:
+        raise_on_unexpected_status: Whether or not to raise an errors.UnexpectedStatus if the API returns a
+            status code that was not documented in the source OpenAPI document. Can also be provided as a keyword
+            argument to the constructor.
+    """
+
+    raise_on_unexpected_status: bool = field(default=False, kw_only=True)
+    _base_url: str = field(alias="base_url")
+    _cookies: dict[str, str] = field(factory=dict, kw_only=True, alias="cookies")
+    _headers: dict[str, str] = field(factory=dict, kw_only=True, alias="headers")
+    _timeout: Optional[httpx.Timeout] = field(default=None, kw_only=True, alias="timeout")
+    _verify_ssl: Union[str, bool, ssl.SSLContext] = field(default=True, kw_only=True, alias="verify_ssl")
+    _follow_redirects: bool = field(default=False, kw_only=True, alias="follow_redirects")
+    _httpx_args: dict[str, Any] = field(factory=dict, kw_only=True, alias="httpx_args")
+    _client: Optional[httpx.Client] = field(default=None, init=False)
+    _async_client: Optional[httpx.AsyncClient] = field(default=None, init=False)
+
+    def with_headers(self, headers: dict[str, str]) -> "Client":
+        """Get a new client matching this one with additional headers"""
+        if self._client is not None:
+            self._client.headers.update(headers)
+        if self._async_client is not None:
+            self._async_client.headers.update(headers)
+        return evolve(self, headers={**self._headers, **headers})
+
+    def with_cookies(self, cookies: dict[str, str]) -> "Client":
+        """Get a new client matching this one with additional cookies"""
+        if self._client is not None:
+            self._client.cookies.update(cookies)
+        if self._async_client is not None:
+            self._async_client.cookies.update(cookies)
+        return evolve(self, cookies={**self._cookies, **cookies})
+
+    def with_timeout(self, timeout: httpx.Timeout) -> "Client":
+        """Get a new client matching this one with a new timeout (in seconds)"""
+        if self._client is not None:
+            self._client.timeout = timeout
+        if self._async_client is not None:
+            self._async_client.timeout = timeout
+        return evolve(self, timeout=timeout)
+
+    def set_httpx_client(self, client: httpx.Client) -> "Client":
+        """Manually set the underlying httpx.Client
+
+        **NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
+        """
+        self._client = client
+        return self
+
+    def get_httpx_client(self) -> httpx.Client:
+        """Get the underlying httpx.Client, constructing a new one if not previously set"""
+        if self._client is None:
+            self._client = httpx.Client(
+                base_url=self._base_url,
+                cookies=self._cookies,
+                headers=self._headers,
+                timeout=self._timeout,
+                verify=self._verify_ssl,
+                follow_redirects=self._follow_redirects,
+                **self._httpx_args,
+            )
+        return self._client
+
+    def __enter__(self) -> "Client":
+        """Enter a context manager for self.client—you cannot enter twice (see httpx docs)"""
+        self.get_httpx_client().__enter__()
+        return self
+
+    def __exit__(self, *args: Any, **kwargs: Any) -> None:
+        """Exit a context manager for internal httpx.Client (see httpx docs)"""
+        self.get_httpx_client().__exit__(*args, **kwargs)
+
+    def set_async_httpx_client(self, async_client: httpx.AsyncClient) -> "Client":
+        """Manually the underlying httpx.AsyncClient
+
+        **NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
+        """
+        self._async_client = async_client
+        return self
+
+    def get_async_httpx_client(self) -> httpx.AsyncClient:
+        """Get the underlying httpx.AsyncClient, constructing a new one if not previously set"""
+        if self._async_client is None:
+            self._async_client = httpx.AsyncClient(
+                base_url=self._base_url,
+                cookies=self._cookies,
+                headers=self._headers,
+                timeout=self._timeout,
+                verify=self._verify_ssl,
+                follow_redirects=self._follow_redirects,
+                **self._httpx_args,
+            )
+        return self._async_client
+
+    async def __aenter__(self) -> "Client":
+        """Enter a context manager for underlying httpx.AsyncClient—you cannot enter twice (see httpx docs)"""
+        await self.get_async_httpx_client().__aenter__()
+        return self
+
+    async def __aexit__(self, *args: Any, **kwargs: Any) -> None:
+        """Exit a context manager for underlying httpx.AsyncClient (see httpx docs)"""
+        await self.get_async_httpx_client().__aexit__(*args, **kwargs)
+
+
+@define
+class AuthenticatedClient:
+    """A Client which has been authenticated for use on secured endpoints
+
+    The following are accepted as keyword arguments and will be used to construct httpx Clients internally:
+
+        ``base_url``: The base URL for the API, all requests are made to a relative path to this URL
+
+        ``cookies``: A dictionary of cookies to be sent with every request
+
+        ``headers``: A dictionary of headers to be sent with every request
+
+        ``timeout``: The maximum amount of a time a request can take. API functions will raise
+        httpx.TimeoutException if this is exceeded.
+
+        ``verify_ssl``: Whether or not to verify the SSL certificate of the API server. This should be True in production,
+        but can be set to False for testing purposes.
+
+        ``follow_redirects``: Whether or not to follow redirects. Default value is False.
+
+        ``httpx_args``: A dictionary of additional arguments to be passed to the ``httpx.Client`` and ``httpx.AsyncClient`` constructor.
+
+
+    Attributes:
+        raise_on_unexpected_status: Whether or not to raise an errors.UnexpectedStatus if the API returns a
+            status code that was not documented in the source OpenAPI document. Can also be provided as a keyword
+            argument to the constructor.
+        token: The token to use for authentication
+        prefix: The prefix to use for the Authorization header
+        auth_header_name: The name of the Authorization header
+    """
+
+    raise_on_unexpected_status: bool = field(default=False, kw_only=True)
+    _base_url: str = field(alias="base_url")
+    _cookies: dict[str, str] = field(factory=dict, kw_only=True, alias="cookies")
+    _headers: dict[str, str] = field(factory=dict, kw_only=True, alias="headers")
+    _timeout: Optional[httpx.Timeout] = field(default=None, kw_only=True, alias="timeout")
+    _verify_ssl: Union[str, bool, ssl.SSLContext] = field(default=True, kw_only=True, alias="verify_ssl")
+    _follow_redirects: bool = field(default=False, kw_only=True, alias="follow_redirects")
+    _httpx_args: dict[str, Any] = field(factory=dict, kw_only=True, alias="httpx_args")
+    _client: Optional[httpx.Client] = field(default=None, init=False)
+    _async_client: Optional[httpx.AsyncClient] = field(default=None, init=False)
+
+    token: str
+    prefix: str = "Bearer"
+    auth_header_name: str = "Authorization"
+
+    def with_headers(self, headers: dict[str, str]) -> "AuthenticatedClient":
+        """Get a new client matching this one with additional headers"""
+        if self._client is not None:
+            self._client.headers.update(headers)
+        if self._async_client is not None:
+            self._async_client.headers.update(headers)
+        return evolve(self, headers={**self._headers, **headers})
+
+    def with_cookies(self, cookies: dict[str, str]) -> "AuthenticatedClient":
+        """Get a new client matching this one with additional cookies"""
+        if self._client is not None:
+            self._client.cookies.update(cookies)
+        if self._async_client is not None:
+            self._async_client.cookies.update(cookies)
+        return evolve(self, cookies={**self._cookies, **cookies})
+
+    def with_timeout(self, timeout: httpx.Timeout) -> "AuthenticatedClient":
+        """Get a new client matching this one with a new timeout (in seconds)"""
+        if self._client is not None:
+            self._client.timeout = timeout
+        if self._async_client is not None:
+            self._async_client.timeout = timeout
+        return evolve(self, timeout=timeout)
+
+    def set_httpx_client(self, client: httpx.Client) -> "AuthenticatedClient":
+        """Manually set the underlying httpx.Client
+
+        **NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
+        """
+        self._client = client
+        return self
+
+    def get_httpx_client(self) -> httpx.Client:
+        """Get the underlying httpx.Client, constructing a new one if not previously set"""
+        if self._client is None:
+            self._headers[self.auth_header_name] = f"{self.prefix} {self.token}" if self.prefix else self.token
+            self._client = httpx.Client(
+                base_url=self._base_url,
+                cookies=self._cookies,
+                headers=self._headers,
+                timeout=self._timeout,
+                verify=self._verify_ssl,
+                follow_redirects=self._follow_redirects,
+                **self._httpx_args,
+            )
+        return self._client
+
+    def __enter__(self) -> "AuthenticatedClient":
+        """Enter a context manager for self.client—you cannot enter twice (see httpx docs)"""
+        self.get_httpx_client().__enter__()
+        return self
+
+    def __exit__(self, *args: Any, **kwargs: Any) -> None:
+        """Exit a context manager for internal httpx.Client (see httpx docs)"""
+        self.get_httpx_client().__exit__(*args, **kwargs)
+
+    def set_async_httpx_client(self, async_client: httpx.AsyncClient) -> "AuthenticatedClient":
+        """Manually the underlying httpx.AsyncClient
+
+        **NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
+        """
+        self._async_client = async_client
+        return self
+
+    def get_async_httpx_client(self) -> httpx.AsyncClient:
+        """Get the underlying httpx.AsyncClient, constructing a new one if not previously set"""
+        if self._async_client is None:
+            self._headers[self.auth_header_name] = f"{self.prefix} {self.token}" if self.prefix else self.token
+            self._async_client = httpx.AsyncClient(
+                base_url=self._base_url,
+                cookies=self._cookies,
+                headers=self._headers,
+                timeout=self._timeout,
+                verify=self._verify_ssl,
+                follow_redirects=self._follow_redirects,
+                **self._httpx_args,
+            )
+        return self._async_client
+
+    async def __aenter__(self) -> "AuthenticatedClient":
+        """Enter a context manager for underlying httpx.AsyncClient—you cannot enter twice (see httpx docs)"""
+        await self.get_async_httpx_client().__aenter__()
+        return self
+
+    async def __aexit__(self, *args: Any, **kwargs: Any) -> None:
+        """Exit a context manager for underlying httpx.AsyncClient (see httpx docs)"""
+        await self.get_async_httpx_client().__aexit__(*args, **kwargs)

+ 16 - 0
packages/sdk/python/src/opencode_ai/errors.py

@@ -0,0 +1,16 @@
+"""Contains shared errors types that can be raised from API functions"""
+
+
+class UnexpectedStatus(Exception):
+    """Raised by api functions when the response status an undocumented status and Client.raise_on_unexpected_status is True"""
+
+    def __init__(self, status_code: int, content: bytes):
+        self.status_code = status_code
+        self.content = content
+
+        super().__init__(
+            f"Unexpected status code: {status_code}\n\nResponse content:\n{content.decode(errors='ignore')}"
+        )
+
+
+__all__ = ["UnexpectedStatus"]

+ 186 - 0
packages/sdk/python/src/opencode_ai/extras.py

@@ -0,0 +1,186 @@
+from __future__ import annotations
+
+import time
+from typing import AsyncIterator, Dict, Iterator, Optional
+
+import httpx
+
+from .api.default import (
+    app_agents,
+    command_list,
+    config_get,
+    config_providers,
+    file_status,
+    path_get,
+    project_current,
+    project_list,
+    session_list,
+    tool_ids,
+)
+from .client import Client
+from .types import UNSET, Unset
+
+
+class OpenCodeClient:
+    """High-level convenience wrapper around the generated Client.
+
+    Provides sensible defaults and a couple of helper methods, with optional retries.
+    """
+
+    def __init__(
+        self,
+        base_url: str = "http://localhost:4096",
+        *,
+        headers: Optional[Dict[str, str]] = None,
+        timeout: Optional[float] = None,
+        verify_ssl: bool | str | httpx.URLTypes | None = True,
+        token: Optional[str] = None,
+        auth_header_name: str = "Authorization",
+        auth_prefix: str = "Bearer",
+        retries: int = 0,
+        backoff_factor: float = 0.5,
+        status_forcelist: tuple[int, ...] = (429, 500, 502, 503, 504),
+    ) -> None:
+        httpx_timeout = None if timeout is None else httpx.Timeout(timeout)
+        all_headers = dict(headers or {})
+        if token:
+            all_headers[auth_header_name] = f"{auth_prefix} {token}".strip()
+        self._client = Client(
+            base_url=base_url,
+            headers=all_headers,
+            timeout=httpx_timeout,
+            verify_ssl=verify_ssl if isinstance(verify_ssl, bool) else True,
+        )
+        self._retries = max(0, int(retries))
+        self._backoff = float(backoff_factor)
+        self._status_forcelist = set(status_forcelist)
+
+    @property
+    def client(self) -> Client:
+        return self._client
+
+    # ---- Internal retry helper ----
+
+    def _call_with_retries(self, fn, *args, **kwargs):
+        attempt = 0
+        while True:
+            try:
+                return fn(*args, **kwargs)
+            except httpx.RequestError:
+                pass
+            except httpx.HTTPStatusError as e:
+                if e.response is None or e.response.status_code not in self._status_forcelist:
+                    raise
+            if attempt >= self._retries:
+                # re-raise last exception if we have one
+                raise
+            sleep = self._backoff * (2**attempt)
+            time.sleep(sleep)
+            attempt += 1
+
+    # ---- Convenience wrappers over generated endpoints ----
+
+    def list_sessions(self, *, directory: str | Unset = UNSET):
+        """Return sessions in the current project.
+
+        Wraps GET /session. Pass `directory` to target a specific project/directory if needed.
+        """
+        return self._call_with_retries(session_list.sync, client=self._client, directory=directory)
+
+    def get_config(self, *, directory: str | Unset = UNSET):
+        """Return opencode configuration for the current project (GET /config)."""
+        return self._call_with_retries(config_get.sync, client=self._client, directory=directory)
+
+    def list_agents(self, *, directory: str | Unset = UNSET):
+        """List configured agents (GET /agent)."""
+        return self._call_with_retries(app_agents.sync, client=self._client, directory=directory)
+
+    def list_projects(self, *, directory: str | Unset = UNSET):
+        """List known projects (GET /project)."""
+        return self._call_with_retries(project_list.sync, client=self._client, directory=directory)
+
+    def current_project(self, *, directory: str | Unset = UNSET):
+        """Return current project (GET /project/current)."""
+        return self._call_with_retries(project_current.sync, client=self._client, directory=directory)
+
+    def file_status(self, *, directory: str | Unset = UNSET):
+        """Return file status list (GET /file/status)."""
+        return self._call_with_retries(file_status.sync, client=self._client, directory=directory)
+
+    def get_path(self, *, directory: str | Unset = UNSET):
+        """Return opencode path info (GET /path)."""
+        return self._call_with_retries(path_get.sync, client=self._client, directory=directory)
+
+    def config_providers(self, *, directory: str | Unset = UNSET):
+        """Return configured providers (GET /config/providers)."""
+        return self._call_with_retries(config_providers.sync, client=self._client, directory=directory)
+
+    def tool_ids(self, *, directory: str | Unset = UNSET):
+        """Return tool identifiers for a provider/model pair (GET /experimental/tool)."""
+        return self._call_with_retries(tool_ids.sync, client=self._client, directory=directory)
+
+    def list_commands(self, *, directory: str | Unset = UNSET):
+        """List commands (GET /command)."""
+        return self._call_with_retries(command_list.sync, client=self._client, directory=directory)
+
+    # ---- Server-Sent Events (SSE) streaming ----
+
+    def subscribe_events(self, *, directory: str | Unset = UNSET) -> Iterator[dict]:
+        """Subscribe to /event SSE endpoint and yield parsed JSON events.
+
+        This is a blocking generator which yields one event dict per message.
+        """
+        client = self._client.get_httpx_client()
+        params: dict[str, str] = {}
+        if directory is not UNSET and directory is not None:
+            params["directory"] = str(directory)
+        with client.stream("GET", "/event", headers={"Accept": "text/event-stream"}, params=params) as r:
+            r.raise_for_status()
+            buf = ""
+            for line_bytes in r.iter_lines():
+                line = line_bytes.decode("utf-8") if isinstance(line_bytes, (bytes, bytearray)) else str(line_bytes)
+                if line.startswith(":"):
+                    # comment/heartbeat
+                    continue
+                if line == "":
+                    if buf:
+                        # end of event
+                        for part in buf.split("\n"):
+                            if part.startswith("data:"):
+                                data = part[5:].strip()
+                                if data:
+                                    try:
+                                        yield httpx._models.jsonlib.loads(data)  # type: ignore[attr-defined]
+                                    except Exception:
+                                        # fall back: skip malformed
+                                        pass
+                        buf = ""
+                    continue
+                buf += line + "\n"
+
+    async def subscribe_events_async(self, *, directory: str | Unset = UNSET) -> AsyncIterator[dict]:
+        """Async variant of subscribe_events using httpx.AsyncClient."""
+        aclient = self._client.get_async_httpx_client()
+        params: dict[str, str] = {}
+        if directory is not UNSET and directory is not None:
+            params["directory"] = str(directory)
+        async with aclient.stream("GET", "/event", headers={"Accept": "text/event-stream"}, params=params) as r:
+            r.raise_for_status()
+            buf = ""
+            async for line_bytes in r.aiter_lines():
+                line = line_bytes
+                if line.startswith(":"):
+                    continue
+                if line == "":
+                    if buf:
+                        for part in buf.split("\n"):
+                            if part.startswith("data:"):
+                                data = part[5:].strip()
+                                if data:
+                                    try:
+                                        yield httpx._models.jsonlib.loads(data)  # type: ignore[attr-defined]
+                                    except Exception:
+                                        pass
+                        buf = ""
+                    continue
+                buf += line + "\n"

+ 367 - 0
packages/sdk/python/src/opencode_ai/models/__init__.py

@@ -0,0 +1,367 @@
+"""Contains all the data models used in inputs/outputs"""
+
+from .agent import Agent
+from .agent_config import AgentConfig
+from .agent_config_permission import AgentConfigPermission
+from .agent_config_permission_bash_type_1 import AgentConfigPermissionBashType1
+from .agent_config_tools import AgentConfigTools
+from .agent_model import AgentModel
+from .agent_options import AgentOptions
+from .agent_part import AgentPart
+from .agent_part_input import AgentPartInput
+from .agent_part_input_source import AgentPartInputSource
+from .agent_part_source import AgentPartSource
+from .agent_permission import AgentPermission
+from .agent_permission_bash import AgentPermissionBash
+from .agent_tools import AgentTools
+from .api_auth import ApiAuth
+from .assistant_message import AssistantMessage
+from .assistant_message_path import AssistantMessagePath
+from .assistant_message_time import AssistantMessageTime
+from .assistant_message_tokens import AssistantMessageTokens
+from .assistant_message_tokens_cache import AssistantMessageTokensCache
+from .command import Command
+from .config import Config
+from .config_agent import ConfigAgent
+from .config_command import ConfigCommand
+from .config_command_additional_property import ConfigCommandAdditionalProperty
+from .config_experimental import ConfigExperimental
+from .config_experimental_hook import ConfigExperimentalHook
+from .config_experimental_hook_file_edited import ConfigExperimentalHookFileEdited
+from .config_experimental_hook_file_edited_additional_property_item import (
+    ConfigExperimentalHookFileEditedAdditionalPropertyItem,
+)
+from .config_experimental_hook_file_edited_additional_property_item_environment import (
+    ConfigExperimentalHookFileEditedAdditionalPropertyItemEnvironment,
+)
+from .config_experimental_hook_session_completed_item import ConfigExperimentalHookSessionCompletedItem
+from .config_experimental_hook_session_completed_item_environment import (
+    ConfigExperimentalHookSessionCompletedItemEnvironment,
+)
+from .config_formatter import ConfigFormatter
+from .config_formatter_additional_property import ConfigFormatterAdditionalProperty
+from .config_formatter_additional_property_environment import ConfigFormatterAdditionalPropertyEnvironment
+from .config_lsp import ConfigLsp
+from .config_lsp_additional_property_type_0 import ConfigLspAdditionalPropertyType0
+from .config_lsp_additional_property_type_1 import ConfigLspAdditionalPropertyType1
+from .config_lsp_additional_property_type_1_env import ConfigLspAdditionalPropertyType1Env
+from .config_lsp_additional_property_type_1_initialization import ConfigLspAdditionalPropertyType1Initialization
+from .config_mcp import ConfigMcp
+from .config_mode import ConfigMode
+from .config_permission import ConfigPermission
+from .config_permission_bash_type_1 import ConfigPermissionBashType1
+from .config_provider import ConfigProvider
+from .config_provider_additional_property import ConfigProviderAdditionalProperty
+from .config_provider_additional_property_models import ConfigProviderAdditionalPropertyModels
+from .config_provider_additional_property_models_additional_property import (
+    ConfigProviderAdditionalPropertyModelsAdditionalProperty,
+)
+from .config_provider_additional_property_models_additional_property_cost import (
+    ConfigProviderAdditionalPropertyModelsAdditionalPropertyCost,
+)
+from .config_provider_additional_property_models_additional_property_limit import (
+    ConfigProviderAdditionalPropertyModelsAdditionalPropertyLimit,
+)
+from .config_provider_additional_property_models_additional_property_options import (
+    ConfigProviderAdditionalPropertyModelsAdditionalPropertyOptions,
+)
+from .config_provider_additional_property_models_additional_property_provider import (
+    ConfigProviderAdditionalPropertyModelsAdditionalPropertyProvider,
+)
+from .config_provider_additional_property_options import ConfigProviderAdditionalPropertyOptions
+from .config_providers_response_200 import ConfigProvidersResponse200
+from .config_providers_response_200_default import ConfigProvidersResponse200Default
+from .config_share import ConfigShare
+from .config_tools import ConfigTools
+from .config_tui import ConfigTui
+from .config_watcher import ConfigWatcher
+from .error import Error
+from .error_data import ErrorData
+from .event_file_edited import EventFileEdited
+from .event_file_edited_properties import EventFileEditedProperties
+from .event_file_watcher_updated import EventFileWatcherUpdated
+from .event_file_watcher_updated_properties import EventFileWatcherUpdatedProperties
+from .event_ide_installed import EventIdeInstalled
+from .event_ide_installed_properties import EventIdeInstalledProperties
+from .event_installation_updated import EventInstallationUpdated
+from .event_installation_updated_properties import EventInstallationUpdatedProperties
+from .event_lsp_client_diagnostics import EventLspClientDiagnostics
+from .event_lsp_client_diagnostics_properties import EventLspClientDiagnosticsProperties
+from .event_message_part_removed import EventMessagePartRemoved
+from .event_message_part_removed_properties import EventMessagePartRemovedProperties
+from .event_message_part_updated import EventMessagePartUpdated
+from .event_message_part_updated_properties import EventMessagePartUpdatedProperties
+from .event_message_removed import EventMessageRemoved
+from .event_message_removed_properties import EventMessageRemovedProperties
+from .event_message_updated import EventMessageUpdated
+from .event_message_updated_properties import EventMessageUpdatedProperties
+from .event_permission_replied import EventPermissionReplied
+from .event_permission_replied_properties import EventPermissionRepliedProperties
+from .event_permission_updated import EventPermissionUpdated
+from .event_server_connected import EventServerConnected
+from .event_server_connected_properties import EventServerConnectedProperties
+from .event_session_compacted import EventSessionCompacted
+from .event_session_compacted_properties import EventSessionCompactedProperties
+from .event_session_deleted import EventSessionDeleted
+from .event_session_deleted_properties import EventSessionDeletedProperties
+from .event_session_error import EventSessionError
+from .event_session_error_properties import EventSessionErrorProperties
+from .event_session_idle import EventSessionIdle
+from .event_session_idle_properties import EventSessionIdleProperties
+from .event_session_updated import EventSessionUpdated
+from .event_session_updated_properties import EventSessionUpdatedProperties
+from .file import File
+from .file_content import FileContent
+from .file_content_patch import FileContentPatch
+from .file_content_patch_hunks_item import FileContentPatchHunksItem
+from .file_node import FileNode
+from .file_node_type import FileNodeType
+from .file_part import FilePart
+from .file_part_input import FilePartInput
+from .file_part_source_text import FilePartSourceText
+from .file_source import FileSource
+from .file_status import FileStatus
+from .keybinds_config import KeybindsConfig
+from .layout_config import LayoutConfig
+from .mcp_local_config import McpLocalConfig
+from .mcp_local_config_environment import McpLocalConfigEnvironment
+from .mcp_remote_config import McpRemoteConfig
+from .mcp_remote_config_headers import McpRemoteConfigHeaders
+from .message_aborted_error import MessageAbortedError
+from .message_aborted_error_data import MessageAbortedErrorData
+from .message_output_length_error import MessageOutputLengthError
+from .message_output_length_error_data import MessageOutputLengthErrorData
+from .model import Model
+from .model_cost import ModelCost
+from .model_limit import ModelLimit
+from .model_options import ModelOptions
+from .model_provider import ModelProvider
+from .o_auth import OAuth
+from .patch_part import PatchPart
+from .path import Path
+from .permission import Permission
+from .permission_metadata import PermissionMetadata
+from .permission_time import PermissionTime
+from .project import Project
+from .project_time import ProjectTime
+from .provider import Provider
+from .provider_auth_error import ProviderAuthError
+from .provider_auth_error_data import ProviderAuthErrorData
+from .provider_models import ProviderModels
+from .range_ import Range
+from .range_end import RangeEnd
+from .range_start import RangeStart
+from .reasoning_part import ReasoningPart
+from .reasoning_part_metadata import ReasoningPartMetadata
+from .reasoning_part_time import ReasoningPartTime
+from .session import Session
+from .session_revert import SessionRevert
+from .session_share import SessionShare
+from .session_time import SessionTime
+from .snapshot_part import SnapshotPart
+from .step_finish_part import StepFinishPart
+from .step_finish_part_tokens import StepFinishPartTokens
+from .step_finish_part_tokens_cache import StepFinishPartTokensCache
+from .step_start_part import StepStartPart
+from .symbol import Symbol
+from .symbol_location import SymbolLocation
+from .symbol_source import SymbolSource
+from .text_part import TextPart
+from .text_part_input import TextPartInput
+from .text_part_input_time import TextPartInputTime
+from .text_part_time import TextPartTime
+from .tool_list_item import ToolListItem
+from .tool_part import ToolPart
+from .tool_state_completed import ToolStateCompleted
+from .tool_state_completed_input import ToolStateCompletedInput
+from .tool_state_completed_metadata import ToolStateCompletedMetadata
+from .tool_state_completed_time import ToolStateCompletedTime
+from .tool_state_error import ToolStateError
+from .tool_state_error_input import ToolStateErrorInput
+from .tool_state_error_metadata import ToolStateErrorMetadata
+from .tool_state_error_time import ToolStateErrorTime
+from .tool_state_pending import ToolStatePending
+from .tool_state_running import ToolStateRunning
+from .tool_state_running_metadata import ToolStateRunningMetadata
+from .tool_state_running_time import ToolStateRunningTime
+from .unknown_error import UnknownError
+from .unknown_error_data import UnknownErrorData
+from .user_message import UserMessage
+from .user_message_time import UserMessageTime
+from .well_known_auth import WellKnownAuth
+
+__all__ = (
+    "Agent",
+    "AgentConfig",
+    "AgentConfigPermission",
+    "AgentConfigPermissionBashType1",
+    "AgentConfigTools",
+    "AgentModel",
+    "AgentOptions",
+    "AgentPart",
+    "AgentPartInput",
+    "AgentPartInputSource",
+    "AgentPartSource",
+    "AgentPermission",
+    "AgentPermissionBash",
+    "AgentTools",
+    "ApiAuth",
+    "AssistantMessage",
+    "AssistantMessagePath",
+    "AssistantMessageTime",
+    "AssistantMessageTokens",
+    "AssistantMessageTokensCache",
+    "Command",
+    "Config",
+    "ConfigAgent",
+    "ConfigCommand",
+    "ConfigCommandAdditionalProperty",
+    "ConfigExperimental",
+    "ConfigExperimentalHook",
+    "ConfigExperimentalHookFileEdited",
+    "ConfigExperimentalHookFileEditedAdditionalPropertyItem",
+    "ConfigExperimentalHookFileEditedAdditionalPropertyItemEnvironment",
+    "ConfigExperimentalHookSessionCompletedItem",
+    "ConfigExperimentalHookSessionCompletedItemEnvironment",
+    "ConfigFormatter",
+    "ConfigFormatterAdditionalProperty",
+    "ConfigFormatterAdditionalPropertyEnvironment",
+    "ConfigLsp",
+    "ConfigLspAdditionalPropertyType0",
+    "ConfigLspAdditionalPropertyType1",
+    "ConfigLspAdditionalPropertyType1Env",
+    "ConfigLspAdditionalPropertyType1Initialization",
+    "ConfigMcp",
+    "ConfigMode",
+    "ConfigPermission",
+    "ConfigPermissionBashType1",
+    "ConfigProvider",
+    "ConfigProviderAdditionalProperty",
+    "ConfigProviderAdditionalPropertyModels",
+    "ConfigProviderAdditionalPropertyModelsAdditionalProperty",
+    "ConfigProviderAdditionalPropertyModelsAdditionalPropertyCost",
+    "ConfigProviderAdditionalPropertyModelsAdditionalPropertyLimit",
+    "ConfigProviderAdditionalPropertyModelsAdditionalPropertyOptions",
+    "ConfigProviderAdditionalPropertyModelsAdditionalPropertyProvider",
+    "ConfigProviderAdditionalPropertyOptions",
+    "ConfigProvidersResponse200",
+    "ConfigProvidersResponse200Default",
+    "ConfigShare",
+    "ConfigTools",
+    "ConfigTui",
+    "ConfigWatcher",
+    "Error",
+    "ErrorData",
+    "EventFileEdited",
+    "EventFileEditedProperties",
+    "EventFileWatcherUpdated",
+    "EventFileWatcherUpdatedProperties",
+    "EventIdeInstalled",
+    "EventIdeInstalledProperties",
+    "EventInstallationUpdated",
+    "EventInstallationUpdatedProperties",
+    "EventLspClientDiagnostics",
+    "EventLspClientDiagnosticsProperties",
+    "EventMessagePartRemoved",
+    "EventMessagePartRemovedProperties",
+    "EventMessagePartUpdated",
+    "EventMessagePartUpdatedProperties",
+    "EventMessageRemoved",
+    "EventMessageRemovedProperties",
+    "EventMessageUpdated",
+    "EventMessageUpdatedProperties",
+    "EventPermissionReplied",
+    "EventPermissionRepliedProperties",
+    "EventPermissionUpdated",
+    "EventServerConnected",
+    "EventServerConnectedProperties",
+    "EventSessionCompacted",
+    "EventSessionCompactedProperties",
+    "EventSessionDeleted",
+    "EventSessionDeletedProperties",
+    "EventSessionError",
+    "EventSessionErrorProperties",
+    "EventSessionIdle",
+    "EventSessionIdleProperties",
+    "EventSessionUpdated",
+    "EventSessionUpdatedProperties",
+    "File",
+    "FileContent",
+    "FileContentPatch",
+    "FileContentPatchHunksItem",
+    "FileNode",
+    "FileNodeType",
+    "FilePart",
+    "FilePartInput",
+    "FilePartSourceText",
+    "FileSource",
+    "FileStatus",
+    "KeybindsConfig",
+    "LayoutConfig",
+    "McpLocalConfig",
+    "McpLocalConfigEnvironment",
+    "McpRemoteConfig",
+    "McpRemoteConfigHeaders",
+    "MessageAbortedError",
+    "MessageAbortedErrorData",
+    "MessageOutputLengthError",
+    "MessageOutputLengthErrorData",
+    "Model",
+    "ModelCost",
+    "ModelLimit",
+    "ModelOptions",
+    "ModelProvider",
+    "OAuth",
+    "PatchPart",
+    "Path",
+    "Permission",
+    "PermissionMetadata",
+    "PermissionTime",
+    "Project",
+    "ProjectTime",
+    "Provider",
+    "ProviderAuthError",
+    "ProviderAuthErrorData",
+    "ProviderModels",
+    "Range",
+    "RangeEnd",
+    "RangeStart",
+    "ReasoningPart",
+    "ReasoningPartMetadata",
+    "ReasoningPartTime",
+    "Session",
+    "SessionRevert",
+    "SessionShare",
+    "SessionTime",
+    "SnapshotPart",
+    "StepFinishPart",
+    "StepFinishPartTokens",
+    "StepFinishPartTokensCache",
+    "StepStartPart",
+    "Symbol",
+    "SymbolLocation",
+    "SymbolSource",
+    "TextPart",
+    "TextPartInput",
+    "TextPartInputTime",
+    "TextPartTime",
+    "ToolListItem",
+    "ToolPart",
+    "ToolStateCompleted",
+    "ToolStateCompletedInput",
+    "ToolStateCompletedMetadata",
+    "ToolStateCompletedTime",
+    "ToolStateError",
+    "ToolStateErrorInput",
+    "ToolStateErrorMetadata",
+    "ToolStateErrorTime",
+    "ToolStatePending",
+    "ToolStateRunning",
+    "ToolStateRunningMetadata",
+    "ToolStateRunningTime",
+    "UnknownError",
+    "UnknownErrorData",
+    "UserMessage",
+    "UserMessageTime",
+    "WellKnownAuth",
+)

+ 180 - 0
packages/sdk/python/src/opencode_ai/models/agent.py

@@ -0,0 +1,180 @@
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, Any, Literal, TypeVar, Union, cast
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+if TYPE_CHECKING:
+    from ..models.agent_model import AgentModel
+    from ..models.agent_options import AgentOptions
+    from ..models.agent_permission import AgentPermission
+    from ..models.agent_tools import AgentTools
+
+
+T = TypeVar("T", bound="Agent")
+
+
+@_attrs_define
+class Agent:
+    """
+    Attributes:
+        name (str):
+        mode (Union[Literal['all'], Literal['primary'], Literal['subagent']]):
+        built_in (bool):
+        permission (AgentPermission):
+        tools (AgentTools):
+        options (AgentOptions):
+        description (Union[Unset, str]):
+        top_p (Union[Unset, float]):
+        temperature (Union[Unset, float]):
+        model (Union[Unset, AgentModel]):
+        prompt (Union[Unset, str]):
+    """
+
+    name: str
+    mode: Union[Literal["all"], Literal["primary"], Literal["subagent"]]
+    built_in: bool
+    permission: "AgentPermission"
+    tools: "AgentTools"
+    options: "AgentOptions"
+    description: Union[Unset, str] = UNSET
+    top_p: Union[Unset, float] = UNSET
+    temperature: Union[Unset, float] = UNSET
+    model: Union[Unset, "AgentModel"] = UNSET
+    prompt: Union[Unset, str] = UNSET
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        name = self.name
+
+        mode: Union[Literal["all"], Literal["primary"], Literal["subagent"]]
+        mode = self.mode
+
+        built_in = self.built_in
+
+        permission = self.permission.to_dict()
+
+        tools = self.tools.to_dict()
+
+        options = self.options.to_dict()
+
+        description = self.description
+
+        top_p = self.top_p
+
+        temperature = self.temperature
+
+        model: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.model, Unset):
+            model = self.model.to_dict()
+
+        prompt = self.prompt
+
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update(
+            {
+                "name": name,
+                "mode": mode,
+                "builtIn": built_in,
+                "permission": permission,
+                "tools": tools,
+                "options": options,
+            }
+        )
+        if description is not UNSET:
+            field_dict["description"] = description
+        if top_p is not UNSET:
+            field_dict["topP"] = top_p
+        if temperature is not UNSET:
+            field_dict["temperature"] = temperature
+        if model is not UNSET:
+            field_dict["model"] = model
+        if prompt is not UNSET:
+            field_dict["prompt"] = prompt
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        from ..models.agent_model import AgentModel
+        from ..models.agent_options import AgentOptions
+        from ..models.agent_permission import AgentPermission
+        from ..models.agent_tools import AgentTools
+
+        d = dict(src_dict)
+        name = d.pop("name")
+
+        def _parse_mode(data: object) -> Union[Literal["all"], Literal["primary"], Literal["subagent"]]:
+            mode_type_0 = cast(Literal["subagent"], data)
+            if mode_type_0 != "subagent":
+                raise ValueError(f"mode_type_0 must match const 'subagent', got '{mode_type_0}'")
+            return mode_type_0
+            mode_type_1 = cast(Literal["primary"], data)
+            if mode_type_1 != "primary":
+                raise ValueError(f"mode_type_1 must match const 'primary', got '{mode_type_1}'")
+            return mode_type_1
+            mode_type_2 = cast(Literal["all"], data)
+            if mode_type_2 != "all":
+                raise ValueError(f"mode_type_2 must match const 'all', got '{mode_type_2}'")
+            return mode_type_2
+
+        mode = _parse_mode(d.pop("mode"))
+
+        built_in = d.pop("builtIn")
+
+        permission = AgentPermission.from_dict(d.pop("permission"))
+
+        tools = AgentTools.from_dict(d.pop("tools"))
+
+        options = AgentOptions.from_dict(d.pop("options"))
+
+        description = d.pop("description", UNSET)
+
+        top_p = d.pop("topP", UNSET)
+
+        temperature = d.pop("temperature", UNSET)
+
+        _model = d.pop("model", UNSET)
+        model: Union[Unset, AgentModel]
+        if isinstance(_model, Unset):
+            model = UNSET
+        else:
+            model = AgentModel.from_dict(_model)
+
+        prompt = d.pop("prompt", UNSET)
+
+        agent = cls(
+            name=name,
+            mode=mode,
+            built_in=built_in,
+            permission=permission,
+            tools=tools,
+            options=options,
+            description=description,
+            top_p=top_p,
+            temperature=temperature,
+            model=model,
+            prompt=prompt,
+        )
+
+        agent.additional_properties = d
+        return agent
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 173 - 0
packages/sdk/python/src/opencode_ai/models/agent_config.py

@@ -0,0 +1,173 @@
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, Any, Literal, TypeVar, Union, cast
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+if TYPE_CHECKING:
+    from ..models.agent_config_permission import AgentConfigPermission
+    from ..models.agent_config_tools import AgentConfigTools
+
+
+T = TypeVar("T", bound="AgentConfig")
+
+
+@_attrs_define
+class AgentConfig:
+    """
+    Attributes:
+        model (Union[Unset, str]):
+        temperature (Union[Unset, float]):
+        top_p (Union[Unset, float]):
+        prompt (Union[Unset, str]):
+        tools (Union[Unset, AgentConfigTools]):
+        disable (Union[Unset, bool]):
+        description (Union[Unset, str]): Description of when to use the agent
+        mode (Union[Literal['all'], Literal['primary'], Literal['subagent'], Unset]):
+        permission (Union[Unset, AgentConfigPermission]):
+    """
+
+    model: Union[Unset, str] = UNSET
+    temperature: Union[Unset, float] = UNSET
+    top_p: Union[Unset, float] = UNSET
+    prompt: Union[Unset, str] = UNSET
+    tools: Union[Unset, "AgentConfigTools"] = UNSET
+    disable: Union[Unset, bool] = UNSET
+    description: Union[Unset, str] = UNSET
+    mode: Union[Literal["all"], Literal["primary"], Literal["subagent"], Unset] = UNSET
+    permission: Union[Unset, "AgentConfigPermission"] = UNSET
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        model = self.model
+
+        temperature = self.temperature
+
+        top_p = self.top_p
+
+        prompt = self.prompt
+
+        tools: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.tools, Unset):
+            tools = self.tools.to_dict()
+
+        disable = self.disable
+
+        description = self.description
+
+        mode: Union[Literal["all"], Literal["primary"], Literal["subagent"], Unset]
+        if isinstance(self.mode, Unset):
+            mode = UNSET
+        else:
+            mode = self.mode
+
+        permission: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.permission, Unset):
+            permission = self.permission.to_dict()
+
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update({})
+        if model is not UNSET:
+            field_dict["model"] = model
+        if temperature is not UNSET:
+            field_dict["temperature"] = temperature
+        if top_p is not UNSET:
+            field_dict["top_p"] = top_p
+        if prompt is not UNSET:
+            field_dict["prompt"] = prompt
+        if tools is not UNSET:
+            field_dict["tools"] = tools
+        if disable is not UNSET:
+            field_dict["disable"] = disable
+        if description is not UNSET:
+            field_dict["description"] = description
+        if mode is not UNSET:
+            field_dict["mode"] = mode
+        if permission is not UNSET:
+            field_dict["permission"] = permission
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        from ..models.agent_config_permission import AgentConfigPermission
+        from ..models.agent_config_tools import AgentConfigTools
+
+        d = dict(src_dict)
+        model = d.pop("model", UNSET)
+
+        temperature = d.pop("temperature", UNSET)
+
+        top_p = d.pop("top_p", UNSET)
+
+        prompt = d.pop("prompt", UNSET)
+
+        _tools = d.pop("tools", UNSET)
+        tools: Union[Unset, AgentConfigTools]
+        if isinstance(_tools, Unset):
+            tools = UNSET
+        else:
+            tools = AgentConfigTools.from_dict(_tools)
+
+        disable = d.pop("disable", UNSET)
+
+        description = d.pop("description", UNSET)
+
+        def _parse_mode(data: object) -> Union[Literal["all"], Literal["primary"], Literal["subagent"], Unset]:
+            if isinstance(data, Unset):
+                return data
+            mode_type_0 = cast(Literal["subagent"], data)
+            if mode_type_0 != "subagent":
+                raise ValueError(f"mode_type_0 must match const 'subagent', got '{mode_type_0}'")
+            return mode_type_0
+            mode_type_1 = cast(Literal["primary"], data)
+            if mode_type_1 != "primary":
+                raise ValueError(f"mode_type_1 must match const 'primary', got '{mode_type_1}'")
+            return mode_type_1
+            mode_type_2 = cast(Literal["all"], data)
+            if mode_type_2 != "all":
+                raise ValueError(f"mode_type_2 must match const 'all', got '{mode_type_2}'")
+            return mode_type_2
+
+        mode = _parse_mode(d.pop("mode", UNSET))
+
+        _permission = d.pop("permission", UNSET)
+        permission: Union[Unset, AgentConfigPermission]
+        if isinstance(_permission, Unset):
+            permission = UNSET
+        else:
+            permission = AgentConfigPermission.from_dict(_permission)
+
+        agent_config = cls(
+            model=model,
+            temperature=temperature,
+            top_p=top_p,
+            prompt=prompt,
+            tools=tools,
+            disable=disable,
+            description=description,
+            mode=mode,
+            permission=permission,
+        )
+
+        agent_config.additional_properties = d
+        return agent_config
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 155 - 0
packages/sdk/python/src/opencode_ai/models/agent_config_permission.py

@@ -0,0 +1,155 @@
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, Any, Literal, TypeVar, Union, cast
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+if TYPE_CHECKING:
+    from ..models.agent_config_permission_bash_type_1 import AgentConfigPermissionBashType1
+
+
+T = TypeVar("T", bound="AgentConfigPermission")
+
+
+@_attrs_define
+class AgentConfigPermission:
+    """
+    Attributes:
+        edit (Union[Literal['allow'], Literal['ask'], Literal['deny'], Unset]):
+        bash (Union['AgentConfigPermissionBashType1', Literal['allow'], Literal['ask'], Literal['deny'], Unset]):
+        webfetch (Union[Literal['allow'], Literal['ask'], Literal['deny'], Unset]):
+    """
+
+    edit: Union[Literal["allow"], Literal["ask"], Literal["deny"], Unset] = UNSET
+    bash: Union["AgentConfigPermissionBashType1", Literal["allow"], Literal["ask"], Literal["deny"], Unset] = UNSET
+    webfetch: Union[Literal["allow"], Literal["ask"], Literal["deny"], Unset] = UNSET
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        from ..models.agent_config_permission_bash_type_1 import AgentConfigPermissionBashType1
+
+        edit: Union[Literal["allow"], Literal["ask"], Literal["deny"], Unset]
+        if isinstance(self.edit, Unset):
+            edit = UNSET
+        else:
+            edit = self.edit
+
+        bash: Union[Literal["allow"], Literal["ask"], Literal["deny"], Unset, dict[str, Any]]
+        if isinstance(self.bash, Unset):
+            bash = UNSET
+        elif isinstance(self.bash, AgentConfigPermissionBashType1):
+            bash = self.bash.to_dict()
+        else:
+            bash = self.bash
+
+        webfetch: Union[Literal["allow"], Literal["ask"], Literal["deny"], Unset]
+        if isinstance(self.webfetch, Unset):
+            webfetch = UNSET
+        else:
+            webfetch = self.webfetch
+
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update({})
+        if edit is not UNSET:
+            field_dict["edit"] = edit
+        if bash is not UNSET:
+            field_dict["bash"] = bash
+        if webfetch is not UNSET:
+            field_dict["webfetch"] = webfetch
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        from ..models.agent_config_permission_bash_type_1 import AgentConfigPermissionBashType1
+
+        d = dict(src_dict)
+
+        def _parse_edit(data: object) -> Union[Literal["allow"], Literal["ask"], Literal["deny"], Unset]:
+            if isinstance(data, Unset):
+                return data
+            edit_type_0 = cast(Literal["ask"], data)
+            if edit_type_0 != "ask":
+                raise ValueError(f"edit_type_0 must match const 'ask', got '{edit_type_0}'")
+            return edit_type_0
+            edit_type_1 = cast(Literal["allow"], data)
+            if edit_type_1 != "allow":
+                raise ValueError(f"edit_type_1 must match const 'allow', got '{edit_type_1}'")
+            return edit_type_1
+            edit_type_2 = cast(Literal["deny"], data)
+            if edit_type_2 != "deny":
+                raise ValueError(f"edit_type_2 must match const 'deny', got '{edit_type_2}'")
+            return edit_type_2
+
+        edit = _parse_edit(d.pop("edit", UNSET))
+
+        def _parse_bash(
+            data: object,
+        ) -> Union["AgentConfigPermissionBashType1", Literal["allow"], Literal["ask"], Literal["deny"], Unset]:
+            if isinstance(data, Unset):
+                return data
+            bash_type_0_type_0 = cast(Literal["ask"], data)
+            if bash_type_0_type_0 != "ask":
+                raise ValueError(f"bash_type_0_type_0 must match const 'ask', got '{bash_type_0_type_0}'")
+            return bash_type_0_type_0
+            bash_type_0_type_1 = cast(Literal["allow"], data)
+            if bash_type_0_type_1 != "allow":
+                raise ValueError(f"bash_type_0_type_1 must match const 'allow', got '{bash_type_0_type_1}'")
+            return bash_type_0_type_1
+            bash_type_0_type_2 = cast(Literal["deny"], data)
+            if bash_type_0_type_2 != "deny":
+                raise ValueError(f"bash_type_0_type_2 must match const 'deny', got '{bash_type_0_type_2}'")
+            return bash_type_0_type_2
+            if not isinstance(data, dict):
+                raise TypeError()
+            bash_type_1 = AgentConfigPermissionBashType1.from_dict(data)
+
+            return bash_type_1
+
+        bash = _parse_bash(d.pop("bash", UNSET))
+
+        def _parse_webfetch(data: object) -> Union[Literal["allow"], Literal["ask"], Literal["deny"], Unset]:
+            if isinstance(data, Unset):
+                return data
+            webfetch_type_0 = cast(Literal["ask"], data)
+            if webfetch_type_0 != "ask":
+                raise ValueError(f"webfetch_type_0 must match const 'ask', got '{webfetch_type_0}'")
+            return webfetch_type_0
+            webfetch_type_1 = cast(Literal["allow"], data)
+            if webfetch_type_1 != "allow":
+                raise ValueError(f"webfetch_type_1 must match const 'allow', got '{webfetch_type_1}'")
+            return webfetch_type_1
+            webfetch_type_2 = cast(Literal["deny"], data)
+            if webfetch_type_2 != "deny":
+                raise ValueError(f"webfetch_type_2 must match const 'deny', got '{webfetch_type_2}'")
+            return webfetch_type_2
+
+        webfetch = _parse_webfetch(d.pop("webfetch", UNSET))
+
+        agent_config_permission = cls(
+            edit=edit,
+            bash=bash,
+            webfetch=webfetch,
+        )
+
+        agent_config_permission.additional_properties = d
+        return agent_config_permission
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 74 - 0
packages/sdk/python/src/opencode_ai/models/agent_config_permission_bash_type_1.py

@@ -0,0 +1,74 @@
+from collections.abc import Mapping
+from typing import Any, Literal, TypeVar, Union, cast
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+T = TypeVar("T", bound="AgentConfigPermissionBashType1")
+
+
+@_attrs_define
+class AgentConfigPermissionBashType1:
+    """ """
+
+    additional_properties: dict[str, Union[Literal["allow"], Literal["ask"], Literal["deny"]]] = _attrs_field(
+        init=False, factory=dict
+    )
+
+    def to_dict(self) -> dict[str, Any]:
+        field_dict: dict[str, Any] = {}
+        for prop_name, prop in self.additional_properties.items():
+            field_dict[prop_name] = prop
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        d = dict(src_dict)
+        agent_config_permission_bash_type_1 = cls()
+
+        additional_properties = {}
+        for prop_name, prop_dict in d.items():
+
+            def _parse_additional_property(data: object) -> Union[Literal["allow"], Literal["ask"], Literal["deny"]]:
+                additional_property_type_0 = cast(Literal["ask"], data)
+                if additional_property_type_0 != "ask":
+                    raise ValueError(
+                        f"AdditionalProperty_type_0 must match const 'ask', got '{additional_property_type_0}'"
+                    )
+                return additional_property_type_0
+                additional_property_type_1 = cast(Literal["allow"], data)
+                if additional_property_type_1 != "allow":
+                    raise ValueError(
+                        f"AdditionalProperty_type_1 must match const 'allow', got '{additional_property_type_1}'"
+                    )
+                return additional_property_type_1
+                additional_property_type_2 = cast(Literal["deny"], data)
+                if additional_property_type_2 != "deny":
+                    raise ValueError(
+                        f"AdditionalProperty_type_2 must match const 'deny', got '{additional_property_type_2}'"
+                    )
+                return additional_property_type_2
+
+            additional_property = _parse_additional_property(prop_dict)
+
+            additional_properties[prop_name] = additional_property
+
+        agent_config_permission_bash_type_1.additional_properties = additional_properties
+        return agent_config_permission_bash_type_1
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Union[Literal["allow"], Literal["ask"], Literal["deny"]]:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Union[Literal["allow"], Literal["ask"], Literal["deny"]]) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 44 - 0
packages/sdk/python/src/opencode_ai/models/agent_config_tools.py

@@ -0,0 +1,44 @@
+from collections.abc import Mapping
+from typing import Any, TypeVar
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+T = TypeVar("T", bound="AgentConfigTools")
+
+
+@_attrs_define
+class AgentConfigTools:
+    """ """
+
+    additional_properties: dict[str, bool] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        d = dict(src_dict)
+        agent_config_tools = cls()
+
+        agent_config_tools.additional_properties = d
+        return agent_config_tools
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> bool:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: bool) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 67 - 0
packages/sdk/python/src/opencode_ai/models/agent_model.py

@@ -0,0 +1,67 @@
+from collections.abc import Mapping
+from typing import Any, TypeVar
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+T = TypeVar("T", bound="AgentModel")
+
+
+@_attrs_define
+class AgentModel:
+    """
+    Attributes:
+        model_id (str):
+        provider_id (str):
+    """
+
+    model_id: str
+    provider_id: str
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        model_id = self.model_id
+
+        provider_id = self.provider_id
+
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update(
+            {
+                "modelID": model_id,
+                "providerID": provider_id,
+            }
+        )
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        d = dict(src_dict)
+        model_id = d.pop("modelID")
+
+        provider_id = d.pop("providerID")
+
+        agent_model = cls(
+            model_id=model_id,
+            provider_id=provider_id,
+        )
+
+        agent_model.additional_properties = d
+        return agent_model
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 44 - 0
packages/sdk/python/src/opencode_ai/models/agent_options.py

@@ -0,0 +1,44 @@
+from collections.abc import Mapping
+from typing import Any, TypeVar
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+T = TypeVar("T", bound="AgentOptions")
+
+
+@_attrs_define
+class AgentOptions:
+    """ """
+
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        d = dict(src_dict)
+        agent_options = cls()
+
+        agent_options.additional_properties = d
+        return agent_options
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 117 - 0
packages/sdk/python/src/opencode_ai/models/agent_part.py

@@ -0,0 +1,117 @@
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, Any, Literal, TypeVar, Union, cast
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+if TYPE_CHECKING:
+    from ..models.agent_part_source import AgentPartSource
+
+
+T = TypeVar("T", bound="AgentPart")
+
+
+@_attrs_define
+class AgentPart:
+    """
+    Attributes:
+        id (str):
+        session_id (str):
+        message_id (str):
+        type_ (Literal['agent']):
+        name (str):
+        source (Union[Unset, AgentPartSource]):
+    """
+
+    id: str
+    session_id: str
+    message_id: str
+    type_: Literal["agent"]
+    name: str
+    source: Union[Unset, "AgentPartSource"] = UNSET
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        id = self.id
+
+        session_id = self.session_id
+
+        message_id = self.message_id
+
+        type_ = self.type_
+
+        name = self.name
+
+        source: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.source, Unset):
+            source = self.source.to_dict()
+
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update(
+            {
+                "id": id,
+                "sessionID": session_id,
+                "messageID": message_id,
+                "type": type_,
+                "name": name,
+            }
+        )
+        if source is not UNSET:
+            field_dict["source"] = source
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        from ..models.agent_part_source import AgentPartSource
+
+        d = dict(src_dict)
+        id = d.pop("id")
+
+        session_id = d.pop("sessionID")
+
+        message_id = d.pop("messageID")
+
+        type_ = cast(Literal["agent"], d.pop("type"))
+        if type_ != "agent":
+            raise ValueError(f"type must match const 'agent', got '{type_}'")
+
+        name = d.pop("name")
+
+        _source = d.pop("source", UNSET)
+        source: Union[Unset, AgentPartSource]
+        if isinstance(_source, Unset):
+            source = UNSET
+        else:
+            source = AgentPartSource.from_dict(_source)
+
+        agent_part = cls(
+            id=id,
+            session_id=session_id,
+            message_id=message_id,
+            type_=type_,
+            name=name,
+            source=source,
+        )
+
+        agent_part.additional_properties = d
+        return agent_part
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 102 - 0
packages/sdk/python/src/opencode_ai/models/agent_part_input.py

@@ -0,0 +1,102 @@
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, Any, Literal, TypeVar, Union, cast
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+if TYPE_CHECKING:
+    from ..models.agent_part_input_source import AgentPartInputSource
+
+
+T = TypeVar("T", bound="AgentPartInput")
+
+
+@_attrs_define
+class AgentPartInput:
+    """
+    Attributes:
+        type_ (Literal['agent']):
+        name (str):
+        id (Union[Unset, str]):
+        source (Union[Unset, AgentPartInputSource]):
+    """
+
+    type_: Literal["agent"]
+    name: str
+    id: Union[Unset, str] = UNSET
+    source: Union[Unset, "AgentPartInputSource"] = UNSET
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        type_ = self.type_
+
+        name = self.name
+
+        id = self.id
+
+        source: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.source, Unset):
+            source = self.source.to_dict()
+
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update(
+            {
+                "type": type_,
+                "name": name,
+            }
+        )
+        if id is not UNSET:
+            field_dict["id"] = id
+        if source is not UNSET:
+            field_dict["source"] = source
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        from ..models.agent_part_input_source import AgentPartInputSource
+
+        d = dict(src_dict)
+        type_ = cast(Literal["agent"], d.pop("type"))
+        if type_ != "agent":
+            raise ValueError(f"type must match const 'agent', got '{type_}'")
+
+        name = d.pop("name")
+
+        id = d.pop("id", UNSET)
+
+        _source = d.pop("source", UNSET)
+        source: Union[Unset, AgentPartInputSource]
+        if isinstance(_source, Unset):
+            source = UNSET
+        else:
+            source = AgentPartInputSource.from_dict(_source)
+
+        agent_part_input = cls(
+            type_=type_,
+            name=name,
+            id=id,
+            source=source,
+        )
+
+        agent_part_input.additional_properties = d
+        return agent_part_input
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 75 - 0
packages/sdk/python/src/opencode_ai/models/agent_part_input_source.py

@@ -0,0 +1,75 @@
+from collections.abc import Mapping
+from typing import Any, TypeVar
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+T = TypeVar("T", bound="AgentPartInputSource")
+
+
+@_attrs_define
+class AgentPartInputSource:
+    """
+    Attributes:
+        value (str):
+        start (int):
+        end (int):
+    """
+
+    value: str
+    start: int
+    end: int
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        value = self.value
+
+        start = self.start
+
+        end = self.end
+
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update(
+            {
+                "value": value,
+                "start": start,
+                "end": end,
+            }
+        )
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        d = dict(src_dict)
+        value = d.pop("value")
+
+        start = d.pop("start")
+
+        end = d.pop("end")
+
+        agent_part_input_source = cls(
+            value=value,
+            start=start,
+            end=end,
+        )
+
+        agent_part_input_source.additional_properties = d
+        return agent_part_input_source
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 75 - 0
packages/sdk/python/src/opencode_ai/models/agent_part_source.py

@@ -0,0 +1,75 @@
+from collections.abc import Mapping
+from typing import Any, TypeVar
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+T = TypeVar("T", bound="AgentPartSource")
+
+
+@_attrs_define
+class AgentPartSource:
+    """
+    Attributes:
+        value (str):
+        start (int):
+        end (int):
+    """
+
+    value: str
+    start: int
+    end: int
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        value = self.value
+
+        start = self.start
+
+        end = self.end
+
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update(
+            {
+                "value": value,
+                "start": start,
+                "end": end,
+            }
+        )
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        d = dict(src_dict)
+        value = d.pop("value")
+
+        start = d.pop("start")
+
+        end = d.pop("end")
+
+        agent_part_source = cls(
+            value=value,
+            start=start,
+            end=end,
+        )
+
+        agent_part_source.additional_properties = d
+        return agent_part_source
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 120 - 0
packages/sdk/python/src/opencode_ai/models/agent_permission.py

@@ -0,0 +1,120 @@
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, Any, Literal, TypeVar, Union, cast
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+if TYPE_CHECKING:
+    from ..models.agent_permission_bash import AgentPermissionBash
+
+
+T = TypeVar("T", bound="AgentPermission")
+
+
+@_attrs_define
+class AgentPermission:
+    """
+    Attributes:
+        edit (Union[Literal['allow'], Literal['ask'], Literal['deny']]):
+        bash (AgentPermissionBash):
+        webfetch (Union[Literal['allow'], Literal['ask'], Literal['deny'], Unset]):
+    """
+
+    edit: Union[Literal["allow"], Literal["ask"], Literal["deny"]]
+    bash: "AgentPermissionBash"
+    webfetch: Union[Literal["allow"], Literal["ask"], Literal["deny"], Unset] = UNSET
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        edit: Union[Literal["allow"], Literal["ask"], Literal["deny"]]
+        edit = self.edit
+
+        bash = self.bash.to_dict()
+
+        webfetch: Union[Literal["allow"], Literal["ask"], Literal["deny"], Unset]
+        if isinstance(self.webfetch, Unset):
+            webfetch = UNSET
+        else:
+            webfetch = self.webfetch
+
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update(
+            {
+                "edit": edit,
+                "bash": bash,
+            }
+        )
+        if webfetch is not UNSET:
+            field_dict["webfetch"] = webfetch
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        from ..models.agent_permission_bash import AgentPermissionBash
+
+        d = dict(src_dict)
+
+        def _parse_edit(data: object) -> Union[Literal["allow"], Literal["ask"], Literal["deny"]]:
+            edit_type_0 = cast(Literal["ask"], data)
+            if edit_type_0 != "ask":
+                raise ValueError(f"edit_type_0 must match const 'ask', got '{edit_type_0}'")
+            return edit_type_0
+            edit_type_1 = cast(Literal["allow"], data)
+            if edit_type_1 != "allow":
+                raise ValueError(f"edit_type_1 must match const 'allow', got '{edit_type_1}'")
+            return edit_type_1
+            edit_type_2 = cast(Literal["deny"], data)
+            if edit_type_2 != "deny":
+                raise ValueError(f"edit_type_2 must match const 'deny', got '{edit_type_2}'")
+            return edit_type_2
+
+        edit = _parse_edit(d.pop("edit"))
+
+        bash = AgentPermissionBash.from_dict(d.pop("bash"))
+
+        def _parse_webfetch(data: object) -> Union[Literal["allow"], Literal["ask"], Literal["deny"], Unset]:
+            if isinstance(data, Unset):
+                return data
+            webfetch_type_0 = cast(Literal["ask"], data)
+            if webfetch_type_0 != "ask":
+                raise ValueError(f"webfetch_type_0 must match const 'ask', got '{webfetch_type_0}'")
+            return webfetch_type_0
+            webfetch_type_1 = cast(Literal["allow"], data)
+            if webfetch_type_1 != "allow":
+                raise ValueError(f"webfetch_type_1 must match const 'allow', got '{webfetch_type_1}'")
+            return webfetch_type_1
+            webfetch_type_2 = cast(Literal["deny"], data)
+            if webfetch_type_2 != "deny":
+                raise ValueError(f"webfetch_type_2 must match const 'deny', got '{webfetch_type_2}'")
+            return webfetch_type_2
+
+        webfetch = _parse_webfetch(d.pop("webfetch", UNSET))
+
+        agent_permission = cls(
+            edit=edit,
+            bash=bash,
+            webfetch=webfetch,
+        )
+
+        agent_permission.additional_properties = d
+        return agent_permission
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 74 - 0
packages/sdk/python/src/opencode_ai/models/agent_permission_bash.py

@@ -0,0 +1,74 @@
+from collections.abc import Mapping
+from typing import Any, Literal, TypeVar, Union, cast
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+T = TypeVar("T", bound="AgentPermissionBash")
+
+
+@_attrs_define
+class AgentPermissionBash:
+    """ """
+
+    additional_properties: dict[str, Union[Literal["allow"], Literal["ask"], Literal["deny"]]] = _attrs_field(
+        init=False, factory=dict
+    )
+
+    def to_dict(self) -> dict[str, Any]:
+        field_dict: dict[str, Any] = {}
+        for prop_name, prop in self.additional_properties.items():
+            field_dict[prop_name] = prop
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        d = dict(src_dict)
+        agent_permission_bash = cls()
+
+        additional_properties = {}
+        for prop_name, prop_dict in d.items():
+
+            def _parse_additional_property(data: object) -> Union[Literal["allow"], Literal["ask"], Literal["deny"]]:
+                additional_property_type_0 = cast(Literal["ask"], data)
+                if additional_property_type_0 != "ask":
+                    raise ValueError(
+                        f"AdditionalProperty_type_0 must match const 'ask', got '{additional_property_type_0}'"
+                    )
+                return additional_property_type_0
+                additional_property_type_1 = cast(Literal["allow"], data)
+                if additional_property_type_1 != "allow":
+                    raise ValueError(
+                        f"AdditionalProperty_type_1 must match const 'allow', got '{additional_property_type_1}'"
+                    )
+                return additional_property_type_1
+                additional_property_type_2 = cast(Literal["deny"], data)
+                if additional_property_type_2 != "deny":
+                    raise ValueError(
+                        f"AdditionalProperty_type_2 must match const 'deny', got '{additional_property_type_2}'"
+                    )
+                return additional_property_type_2
+
+            additional_property = _parse_additional_property(prop_dict)
+
+            additional_properties[prop_name] = additional_property
+
+        agent_permission_bash.additional_properties = additional_properties
+        return agent_permission_bash
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Union[Literal["allow"], Literal["ask"], Literal["deny"]]:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Union[Literal["allow"], Literal["ask"], Literal["deny"]]) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 44 - 0
packages/sdk/python/src/opencode_ai/models/agent_tools.py

@@ -0,0 +1,44 @@
+from collections.abc import Mapping
+from typing import Any, TypeVar
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+T = TypeVar("T", bound="AgentTools")
+
+
+@_attrs_define
+class AgentTools:
+    """ """
+
+    additional_properties: dict[str, bool] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        d = dict(src_dict)
+        agent_tools = cls()
+
+        agent_tools.additional_properties = d
+        return agent_tools
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> bool:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: bool) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 69 - 0
packages/sdk/python/src/opencode_ai/models/api_auth.py

@@ -0,0 +1,69 @@
+from collections.abc import Mapping
+from typing import Any, Literal, TypeVar, cast
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+T = TypeVar("T", bound="ApiAuth")
+
+
+@_attrs_define
+class ApiAuth:
+    """
+    Attributes:
+        type_ (Literal['api']):
+        key (str):
+    """
+
+    type_: Literal["api"]
+    key: str
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        type_ = self.type_
+
+        key = self.key
+
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update(
+            {
+                "type": type_,
+                "key": key,
+            }
+        )
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        d = dict(src_dict)
+        type_ = cast(Literal["api"], d.pop("type"))
+        if type_ != "api":
+            raise ValueError(f"type must match const 'api', got '{type_}'")
+
+        key = d.pop("key")
+
+        api_auth = cls(
+            type_=type_,
+            key=key,
+        )
+
+        api_auth.additional_properties = d
+        return api_auth
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 228 - 0
packages/sdk/python/src/opencode_ai/models/assistant_message.py

@@ -0,0 +1,228 @@
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, Any, Literal, TypeVar, Union, cast
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+if TYPE_CHECKING:
+    from ..models.assistant_message_path import AssistantMessagePath
+    from ..models.assistant_message_time import AssistantMessageTime
+    from ..models.assistant_message_tokens import AssistantMessageTokens
+    from ..models.message_aborted_error import MessageAbortedError
+    from ..models.message_output_length_error import MessageOutputLengthError
+    from ..models.provider_auth_error import ProviderAuthError
+    from ..models.unknown_error import UnknownError
+
+
+T = TypeVar("T", bound="AssistantMessage")
+
+
+@_attrs_define
+class AssistantMessage:
+    """
+    Attributes:
+        id (str):
+        session_id (str):
+        role (Literal['assistant']):
+        time (AssistantMessageTime):
+        system (list[str]):
+        model_id (str):
+        provider_id (str):
+        mode (str):
+        path (AssistantMessagePath):
+        cost (float):
+        tokens (AssistantMessageTokens):
+        error (Union['MessageAbortedError', 'MessageOutputLengthError', 'ProviderAuthError', 'UnknownError', Unset]):
+        summary (Union[Unset, bool]):
+    """
+
+    id: str
+    session_id: str
+    role: Literal["assistant"]
+    time: "AssistantMessageTime"
+    system: list[str]
+    model_id: str
+    provider_id: str
+    mode: str
+    path: "AssistantMessagePath"
+    cost: float
+    tokens: "AssistantMessageTokens"
+    error: Union["MessageAbortedError", "MessageOutputLengthError", "ProviderAuthError", "UnknownError", Unset] = UNSET
+    summary: Union[Unset, bool] = UNSET
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        from ..models.message_output_length_error import MessageOutputLengthError
+        from ..models.provider_auth_error import ProviderAuthError
+        from ..models.unknown_error import UnknownError
+
+        id = self.id
+
+        session_id = self.session_id
+
+        role = self.role
+
+        time = self.time.to_dict()
+
+        system = self.system
+
+        model_id = self.model_id
+
+        provider_id = self.provider_id
+
+        mode = self.mode
+
+        path = self.path.to_dict()
+
+        cost = self.cost
+
+        tokens = self.tokens.to_dict()
+
+        error: Union[Unset, dict[str, Any]]
+        if isinstance(self.error, Unset):
+            error = UNSET
+        elif isinstance(self.error, ProviderAuthError):
+            error = self.error.to_dict()
+        elif isinstance(self.error, UnknownError):
+            error = self.error.to_dict()
+        elif isinstance(self.error, MessageOutputLengthError):
+            error = self.error.to_dict()
+        else:
+            error = self.error.to_dict()
+
+        summary = self.summary
+
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update(
+            {
+                "id": id,
+                "sessionID": session_id,
+                "role": role,
+                "time": time,
+                "system": system,
+                "modelID": model_id,
+                "providerID": provider_id,
+                "mode": mode,
+                "path": path,
+                "cost": cost,
+                "tokens": tokens,
+            }
+        )
+        if error is not UNSET:
+            field_dict["error"] = error
+        if summary is not UNSET:
+            field_dict["summary"] = summary
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        from ..models.assistant_message_path import AssistantMessagePath
+        from ..models.assistant_message_time import AssistantMessageTime
+        from ..models.assistant_message_tokens import AssistantMessageTokens
+        from ..models.message_aborted_error import MessageAbortedError
+        from ..models.message_output_length_error import MessageOutputLengthError
+        from ..models.provider_auth_error import ProviderAuthError
+        from ..models.unknown_error import UnknownError
+
+        d = dict(src_dict)
+        id = d.pop("id")
+
+        session_id = d.pop("sessionID")
+
+        role = cast(Literal["assistant"], d.pop("role"))
+        if role != "assistant":
+            raise ValueError(f"role must match const 'assistant', got '{role}'")
+
+        time = AssistantMessageTime.from_dict(d.pop("time"))
+
+        system = cast(list[str], d.pop("system"))
+
+        model_id = d.pop("modelID")
+
+        provider_id = d.pop("providerID")
+
+        mode = d.pop("mode")
+
+        path = AssistantMessagePath.from_dict(d.pop("path"))
+
+        cost = d.pop("cost")
+
+        tokens = AssistantMessageTokens.from_dict(d.pop("tokens"))
+
+        def _parse_error(
+            data: object,
+        ) -> Union["MessageAbortedError", "MessageOutputLengthError", "ProviderAuthError", "UnknownError", Unset]:
+            if isinstance(data, Unset):
+                return data
+            try:
+                if not isinstance(data, dict):
+                    raise TypeError()
+                error_type_0 = ProviderAuthError.from_dict(data)
+
+                return error_type_0
+            except:  # noqa: E722
+                pass
+            try:
+                if not isinstance(data, dict):
+                    raise TypeError()
+                error_type_1 = UnknownError.from_dict(data)
+
+                return error_type_1
+            except:  # noqa: E722
+                pass
+            try:
+                if not isinstance(data, dict):
+                    raise TypeError()
+                error_type_2 = MessageOutputLengthError.from_dict(data)
+
+                return error_type_2
+            except:  # noqa: E722
+                pass
+            if not isinstance(data, dict):
+                raise TypeError()
+            error_type_3 = MessageAbortedError.from_dict(data)
+
+            return error_type_3
+
+        error = _parse_error(d.pop("error", UNSET))
+
+        summary = d.pop("summary", UNSET)
+
+        assistant_message = cls(
+            id=id,
+            session_id=session_id,
+            role=role,
+            time=time,
+            system=system,
+            model_id=model_id,
+            provider_id=provider_id,
+            mode=mode,
+            path=path,
+            cost=cost,
+            tokens=tokens,
+            error=error,
+            summary=summary,
+        )
+
+        assistant_message.additional_properties = d
+        return assistant_message
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 67 - 0
packages/sdk/python/src/opencode_ai/models/assistant_message_path.py

@@ -0,0 +1,67 @@
+from collections.abc import Mapping
+from typing import Any, TypeVar
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+T = TypeVar("T", bound="AssistantMessagePath")
+
+
+@_attrs_define
+class AssistantMessagePath:
+    """
+    Attributes:
+        cwd (str):
+        root (str):
+    """
+
+    cwd: str
+    root: str
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        cwd = self.cwd
+
+        root = self.root
+
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update(
+            {
+                "cwd": cwd,
+                "root": root,
+            }
+        )
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        d = dict(src_dict)
+        cwd = d.pop("cwd")
+
+        root = d.pop("root")
+
+        assistant_message_path = cls(
+            cwd=cwd,
+            root=root,
+        )
+
+        assistant_message_path.additional_properties = d
+        return assistant_message_path
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 70 - 0
packages/sdk/python/src/opencode_ai/models/assistant_message_time.py

@@ -0,0 +1,70 @@
+from collections.abc import Mapping
+from typing import Any, TypeVar, Union
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+T = TypeVar("T", bound="AssistantMessageTime")
+
+
+@_attrs_define
+class AssistantMessageTime:
+    """
+    Attributes:
+        created (float):
+        completed (Union[Unset, float]):
+    """
+
+    created: float
+    completed: Union[Unset, float] = UNSET
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        created = self.created
+
+        completed = self.completed
+
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update(
+            {
+                "created": created,
+            }
+        )
+        if completed is not UNSET:
+            field_dict["completed"] = completed
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        d = dict(src_dict)
+        created = d.pop("created")
+
+        completed = d.pop("completed", UNSET)
+
+        assistant_message_time = cls(
+            created=created,
+            completed=completed,
+        )
+
+        assistant_message_time.additional_properties = d
+        return assistant_message_time
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 89 - 0
packages/sdk/python/src/opencode_ai/models/assistant_message_tokens.py

@@ -0,0 +1,89 @@
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, Any, TypeVar
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+if TYPE_CHECKING:
+    from ..models.assistant_message_tokens_cache import AssistantMessageTokensCache
+
+
+T = TypeVar("T", bound="AssistantMessageTokens")
+
+
+@_attrs_define
+class AssistantMessageTokens:
+    """
+    Attributes:
+        input_ (float):
+        output (float):
+        reasoning (float):
+        cache (AssistantMessageTokensCache):
+    """
+
+    input_: float
+    output: float
+    reasoning: float
+    cache: "AssistantMessageTokensCache"
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        input_ = self.input_
+
+        output = self.output
+
+        reasoning = self.reasoning
+
+        cache = self.cache.to_dict()
+
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update(
+            {
+                "input": input_,
+                "output": output,
+                "reasoning": reasoning,
+                "cache": cache,
+            }
+        )
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        from ..models.assistant_message_tokens_cache import AssistantMessageTokensCache
+
+        d = dict(src_dict)
+        input_ = d.pop("input")
+
+        output = d.pop("output")
+
+        reasoning = d.pop("reasoning")
+
+        cache = AssistantMessageTokensCache.from_dict(d.pop("cache"))
+
+        assistant_message_tokens = cls(
+            input_=input_,
+            output=output,
+            reasoning=reasoning,
+            cache=cache,
+        )
+
+        assistant_message_tokens.additional_properties = d
+        return assistant_message_tokens
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 67 - 0
packages/sdk/python/src/opencode_ai/models/assistant_message_tokens_cache.py

@@ -0,0 +1,67 @@
+from collections.abc import Mapping
+from typing import Any, TypeVar
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+T = TypeVar("T", bound="AssistantMessageTokensCache")
+
+
+@_attrs_define
+class AssistantMessageTokensCache:
+    """
+    Attributes:
+        read (float):
+        write (float):
+    """
+
+    read: float
+    write: float
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        read = self.read
+
+        write = self.write
+
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update(
+            {
+                "read": read,
+                "write": write,
+            }
+        )
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        d = dict(src_dict)
+        read = d.pop("read")
+
+        write = d.pop("write")
+
+        assistant_message_tokens_cache = cls(
+            read=read,
+            write=write,
+        )
+
+        assistant_message_tokens_cache.additional_properties = d
+        return assistant_message_tokens_cache
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 105 - 0
packages/sdk/python/src/opencode_ai/models/command.py

@@ -0,0 +1,105 @@
+from collections.abc import Mapping
+from typing import Any, TypeVar, Union
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+T = TypeVar("T", bound="Command")
+
+
+@_attrs_define
+class Command:
+    """
+    Attributes:
+        name (str):
+        template (str):
+        description (Union[Unset, str]):
+        agent (Union[Unset, str]):
+        model (Union[Unset, str]):
+        subtask (Union[Unset, bool]):
+    """
+
+    name: str
+    template: str
+    description: Union[Unset, str] = UNSET
+    agent: Union[Unset, str] = UNSET
+    model: Union[Unset, str] = UNSET
+    subtask: Union[Unset, bool] = UNSET
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        name = self.name
+
+        template = self.template
+
+        description = self.description
+
+        agent = self.agent
+
+        model = self.model
+
+        subtask = self.subtask
+
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update(
+            {
+                "name": name,
+                "template": template,
+            }
+        )
+        if description is not UNSET:
+            field_dict["description"] = description
+        if agent is not UNSET:
+            field_dict["agent"] = agent
+        if model is not UNSET:
+            field_dict["model"] = model
+        if subtask is not UNSET:
+            field_dict["subtask"] = subtask
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        d = dict(src_dict)
+        name = d.pop("name")
+
+        template = d.pop("template")
+
+        description = d.pop("description", UNSET)
+
+        agent = d.pop("agent", UNSET)
+
+        model = d.pop("model", UNSET)
+
+        subtask = d.pop("subtask", UNSET)
+
+        command = cls(
+            name=name,
+            template=template,
+            description=description,
+            agent=agent,
+            model=model,
+            subtask=subtask,
+        )
+
+        command.additional_properties = d
+        return command
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 411 - 0
packages/sdk/python/src/opencode_ai/models/config.py

@@ -0,0 +1,411 @@
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, Any, TypeVar, Union, cast
+
+from attrs import define as _attrs_define
+
+from ..models.config_share import ConfigShare
+from ..models.layout_config import LayoutConfig
+from ..types import UNSET, Unset
+
+if TYPE_CHECKING:
+    from ..models.config_agent import ConfigAgent
+    from ..models.config_command import ConfigCommand
+    from ..models.config_experimental import ConfigExperimental
+    from ..models.config_formatter import ConfigFormatter
+    from ..models.config_lsp import ConfigLsp
+    from ..models.config_mcp import ConfigMcp
+    from ..models.config_mode import ConfigMode
+    from ..models.config_permission import ConfigPermission
+    from ..models.config_provider import ConfigProvider
+    from ..models.config_tools import ConfigTools
+    from ..models.config_tui import ConfigTui
+    from ..models.config_watcher import ConfigWatcher
+    from ..models.keybinds_config import KeybindsConfig
+
+
+T = TypeVar("T", bound="Config")
+
+
+@_attrs_define
+class Config:
+    """
+    Attributes:
+        schema (Union[Unset, str]): JSON schema reference for configuration validation
+        theme (Union[Unset, str]): Theme name to use for the interface
+        keybinds (Union[Unset, KeybindsConfig]): Custom keybind configurations
+        tui (Union[Unset, ConfigTui]): TUI specific settings
+        command (Union[Unset, ConfigCommand]): Command configuration, see https://opencode.ai/docs/commands
+        watcher (Union[Unset, ConfigWatcher]):
+        plugin (Union[Unset, list[str]]):
+        snapshot (Union[Unset, bool]):
+        share (Union[Unset, ConfigShare]): Control sharing behavior:'manual' allows manual sharing via commands, 'auto'
+            enables automatic sharing, 'disabled' disables all sharing
+        autoshare (Union[Unset, bool]): @deprecated Use 'share' field instead. Share newly created sessions
+            automatically
+        autoupdate (Union[Unset, bool]): Automatically update to the latest version
+        disabled_providers (Union[Unset, list[str]]): Disable providers that are loaded automatically
+        model (Union[Unset, str]): Model to use in the format of provider/model, eg anthropic/claude-2
+        small_model (Union[Unset, str]): Small model to use for tasks like title generation in the format of
+            provider/model
+        username (Union[Unset, str]): Custom username to display in conversations instead of system username
+        mode (Union[Unset, ConfigMode]): @deprecated Use `agent` field instead.
+        agent (Union[Unset, ConfigAgent]): Agent configuration, see https://opencode.ai/docs/agent
+        provider (Union[Unset, ConfigProvider]): Custom provider configurations and model overrides
+        mcp (Union[Unset, ConfigMcp]): MCP (Model Context Protocol) server configurations
+        formatter (Union[Unset, ConfigFormatter]):
+        lsp (Union[Unset, ConfigLsp]):
+        instructions (Union[Unset, list[str]]): Additional instruction files or patterns to include
+        layout (Union[Unset, LayoutConfig]): @deprecated Always uses stretch layout.
+        permission (Union[Unset, ConfigPermission]):
+        tools (Union[Unset, ConfigTools]):
+        experimental (Union[Unset, ConfigExperimental]):
+    """
+
+    schema: Union[Unset, str] = UNSET
+    theme: Union[Unset, str] = UNSET
+    keybinds: Union[Unset, "KeybindsConfig"] = UNSET
+    tui: Union[Unset, "ConfigTui"] = UNSET
+    command: Union[Unset, "ConfigCommand"] = UNSET
+    watcher: Union[Unset, "ConfigWatcher"] = UNSET
+    plugin: Union[Unset, list[str]] = UNSET
+    snapshot: Union[Unset, bool] = UNSET
+    share: Union[Unset, ConfigShare] = UNSET
+    autoshare: Union[Unset, bool] = UNSET
+    autoupdate: Union[Unset, bool] = UNSET
+    disabled_providers: Union[Unset, list[str]] = UNSET
+    model: Union[Unset, str] = UNSET
+    small_model: Union[Unset, str] = UNSET
+    username: Union[Unset, str] = UNSET
+    mode: Union[Unset, "ConfigMode"] = UNSET
+    agent: Union[Unset, "ConfigAgent"] = UNSET
+    provider: Union[Unset, "ConfigProvider"] = UNSET
+    mcp: Union[Unset, "ConfigMcp"] = UNSET
+    formatter: Union[Unset, "ConfigFormatter"] = UNSET
+    lsp: Union[Unset, "ConfigLsp"] = UNSET
+    instructions: Union[Unset, list[str]] = UNSET
+    layout: Union[Unset, LayoutConfig] = UNSET
+    permission: Union[Unset, "ConfigPermission"] = UNSET
+    tools: Union[Unset, "ConfigTools"] = UNSET
+    experimental: Union[Unset, "ConfigExperimental"] = UNSET
+
+    def to_dict(self) -> dict[str, Any]:
+        schema = self.schema
+
+        theme = self.theme
+
+        keybinds: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.keybinds, Unset):
+            keybinds = self.keybinds.to_dict()
+
+        tui: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.tui, Unset):
+            tui = self.tui.to_dict()
+
+        command: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.command, Unset):
+            command = self.command.to_dict()
+
+        watcher: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.watcher, Unset):
+            watcher = self.watcher.to_dict()
+
+        plugin: Union[Unset, list[str]] = UNSET
+        if not isinstance(self.plugin, Unset):
+            plugin = self.plugin
+
+        snapshot = self.snapshot
+
+        share: Union[Unset, str] = UNSET
+        if not isinstance(self.share, Unset):
+            share = self.share.value
+
+        autoshare = self.autoshare
+
+        autoupdate = self.autoupdate
+
+        disabled_providers: Union[Unset, list[str]] = UNSET
+        if not isinstance(self.disabled_providers, Unset):
+            disabled_providers = self.disabled_providers
+
+        model = self.model
+
+        small_model = self.small_model
+
+        username = self.username
+
+        mode: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.mode, Unset):
+            mode = self.mode.to_dict()
+
+        agent: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.agent, Unset):
+            agent = self.agent.to_dict()
+
+        provider: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.provider, Unset):
+            provider = self.provider.to_dict()
+
+        mcp: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.mcp, Unset):
+            mcp = self.mcp.to_dict()
+
+        formatter: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.formatter, Unset):
+            formatter = self.formatter.to_dict()
+
+        lsp: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.lsp, Unset):
+            lsp = self.lsp.to_dict()
+
+        instructions: Union[Unset, list[str]] = UNSET
+        if not isinstance(self.instructions, Unset):
+            instructions = self.instructions
+
+        layout: Union[Unset, str] = UNSET
+        if not isinstance(self.layout, Unset):
+            layout = self.layout.value
+
+        permission: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.permission, Unset):
+            permission = self.permission.to_dict()
+
+        tools: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.tools, Unset):
+            tools = self.tools.to_dict()
+
+        experimental: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.experimental, Unset):
+            experimental = self.experimental.to_dict()
+
+        field_dict: dict[str, Any] = {}
+
+        field_dict.update({})
+        if schema is not UNSET:
+            field_dict["$schema"] = schema
+        if theme is not UNSET:
+            field_dict["theme"] = theme
+        if keybinds is not UNSET:
+            field_dict["keybinds"] = keybinds
+        if tui is not UNSET:
+            field_dict["tui"] = tui
+        if command is not UNSET:
+            field_dict["command"] = command
+        if watcher is not UNSET:
+            field_dict["watcher"] = watcher
+        if plugin is not UNSET:
+            field_dict["plugin"] = plugin
+        if snapshot is not UNSET:
+            field_dict["snapshot"] = snapshot
+        if share is not UNSET:
+            field_dict["share"] = share
+        if autoshare is not UNSET:
+            field_dict["autoshare"] = autoshare
+        if autoupdate is not UNSET:
+            field_dict["autoupdate"] = autoupdate
+        if disabled_providers is not UNSET:
+            field_dict["disabled_providers"] = disabled_providers
+        if model is not UNSET:
+            field_dict["model"] = model
+        if small_model is not UNSET:
+            field_dict["small_model"] = small_model
+        if username is not UNSET:
+            field_dict["username"] = username
+        if mode is not UNSET:
+            field_dict["mode"] = mode
+        if agent is not UNSET:
+            field_dict["agent"] = agent
+        if provider is not UNSET:
+            field_dict["provider"] = provider
+        if mcp is not UNSET:
+            field_dict["mcp"] = mcp
+        if formatter is not UNSET:
+            field_dict["formatter"] = formatter
+        if lsp is not UNSET:
+            field_dict["lsp"] = lsp
+        if instructions is not UNSET:
+            field_dict["instructions"] = instructions
+        if layout is not UNSET:
+            field_dict["layout"] = layout
+        if permission is not UNSET:
+            field_dict["permission"] = permission
+        if tools is not UNSET:
+            field_dict["tools"] = tools
+        if experimental is not UNSET:
+            field_dict["experimental"] = experimental
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        from ..models.config_agent import ConfigAgent
+        from ..models.config_command import ConfigCommand
+        from ..models.config_experimental import ConfigExperimental
+        from ..models.config_formatter import ConfigFormatter
+        from ..models.config_lsp import ConfigLsp
+        from ..models.config_mcp import ConfigMcp
+        from ..models.config_mode import ConfigMode
+        from ..models.config_permission import ConfigPermission
+        from ..models.config_provider import ConfigProvider
+        from ..models.config_tools import ConfigTools
+        from ..models.config_tui import ConfigTui
+        from ..models.config_watcher import ConfigWatcher
+        from ..models.keybinds_config import KeybindsConfig
+
+        d = dict(src_dict)
+        schema = d.pop("$schema", UNSET)
+
+        theme = d.pop("theme", UNSET)
+
+        _keybinds = d.pop("keybinds", UNSET)
+        keybinds: Union[Unset, KeybindsConfig]
+        if isinstance(_keybinds, Unset):
+            keybinds = UNSET
+        else:
+            keybinds = KeybindsConfig.from_dict(_keybinds)
+
+        _tui = d.pop("tui", UNSET)
+        tui: Union[Unset, ConfigTui]
+        if isinstance(_tui, Unset):
+            tui = UNSET
+        else:
+            tui = ConfigTui.from_dict(_tui)
+
+        _command = d.pop("command", UNSET)
+        command: Union[Unset, ConfigCommand]
+        if isinstance(_command, Unset):
+            command = UNSET
+        else:
+            command = ConfigCommand.from_dict(_command)
+
+        _watcher = d.pop("watcher", UNSET)
+        watcher: Union[Unset, ConfigWatcher]
+        if isinstance(_watcher, Unset):
+            watcher = UNSET
+        else:
+            watcher = ConfigWatcher.from_dict(_watcher)
+
+        plugin = cast(list[str], d.pop("plugin", UNSET))
+
+        snapshot = d.pop("snapshot", UNSET)
+
+        _share = d.pop("share", UNSET)
+        share: Union[Unset, ConfigShare]
+        if isinstance(_share, Unset):
+            share = UNSET
+        else:
+            share = ConfigShare(_share)
+
+        autoshare = d.pop("autoshare", UNSET)
+
+        autoupdate = d.pop("autoupdate", UNSET)
+
+        disabled_providers = cast(list[str], d.pop("disabled_providers", UNSET))
+
+        model = d.pop("model", UNSET)
+
+        small_model = d.pop("small_model", UNSET)
+
+        username = d.pop("username", UNSET)
+
+        _mode = d.pop("mode", UNSET)
+        mode: Union[Unset, ConfigMode]
+        if isinstance(_mode, Unset):
+            mode = UNSET
+        else:
+            mode = ConfigMode.from_dict(_mode)
+
+        _agent = d.pop("agent", UNSET)
+        agent: Union[Unset, ConfigAgent]
+        if isinstance(_agent, Unset):
+            agent = UNSET
+        else:
+            agent = ConfigAgent.from_dict(_agent)
+
+        _provider = d.pop("provider", UNSET)
+        provider: Union[Unset, ConfigProvider]
+        if isinstance(_provider, Unset):
+            provider = UNSET
+        else:
+            provider = ConfigProvider.from_dict(_provider)
+
+        _mcp = d.pop("mcp", UNSET)
+        mcp: Union[Unset, ConfigMcp]
+        if isinstance(_mcp, Unset):
+            mcp = UNSET
+        else:
+            mcp = ConfigMcp.from_dict(_mcp)
+
+        _formatter = d.pop("formatter", UNSET)
+        formatter: Union[Unset, ConfigFormatter]
+        if isinstance(_formatter, Unset):
+            formatter = UNSET
+        else:
+            formatter = ConfigFormatter.from_dict(_formatter)
+
+        _lsp = d.pop("lsp", UNSET)
+        lsp: Union[Unset, ConfigLsp]
+        if isinstance(_lsp, Unset):
+            lsp = UNSET
+        else:
+            lsp = ConfigLsp.from_dict(_lsp)
+
+        instructions = cast(list[str], d.pop("instructions", UNSET))
+
+        _layout = d.pop("layout", UNSET)
+        layout: Union[Unset, LayoutConfig]
+        if isinstance(_layout, Unset):
+            layout = UNSET
+        else:
+            layout = LayoutConfig(_layout)
+
+        _permission = d.pop("permission", UNSET)
+        permission: Union[Unset, ConfigPermission]
+        if isinstance(_permission, Unset):
+            permission = UNSET
+        else:
+            permission = ConfigPermission.from_dict(_permission)
+
+        _tools = d.pop("tools", UNSET)
+        tools: Union[Unset, ConfigTools]
+        if isinstance(_tools, Unset):
+            tools = UNSET
+        else:
+            tools = ConfigTools.from_dict(_tools)
+
+        _experimental = d.pop("experimental", UNSET)
+        experimental: Union[Unset, ConfigExperimental]
+        if isinstance(_experimental, Unset):
+            experimental = UNSET
+        else:
+            experimental = ConfigExperimental.from_dict(_experimental)
+
+        config = cls(
+            schema=schema,
+            theme=theme,
+            keybinds=keybinds,
+            tui=tui,
+            command=command,
+            watcher=watcher,
+            plugin=plugin,
+            snapshot=snapshot,
+            share=share,
+            autoshare=autoshare,
+            autoupdate=autoupdate,
+            disabled_providers=disabled_providers,
+            model=model,
+            small_model=small_model,
+            username=username,
+            mode=mode,
+            agent=agent,
+            provider=provider,
+            mcp=mcp,
+            formatter=formatter,
+            lsp=lsp,
+            instructions=instructions,
+            layout=layout,
+            permission=permission,
+            tools=tools,
+            experimental=experimental,
+        )
+
+        return config

+ 113 - 0
packages/sdk/python/src/opencode_ai/models/config_agent.py

@@ -0,0 +1,113 @@
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, Any, TypeVar, Union
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+if TYPE_CHECKING:
+    from ..models.agent_config import AgentConfig
+
+
+T = TypeVar("T", bound="ConfigAgent")
+
+
+@_attrs_define
+class ConfigAgent:
+    """Agent configuration, see https://opencode.ai/docs/agent
+
+    Attributes:
+        plan (Union[Unset, AgentConfig]):
+        build (Union[Unset, AgentConfig]):
+        general (Union[Unset, AgentConfig]):
+    """
+
+    plan: Union[Unset, "AgentConfig"] = UNSET
+    build: Union[Unset, "AgentConfig"] = UNSET
+    general: Union[Unset, "AgentConfig"] = UNSET
+    additional_properties: dict[str, "AgentConfig"] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        plan: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.plan, Unset):
+            plan = self.plan.to_dict()
+
+        build: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.build, Unset):
+            build = self.build.to_dict()
+
+        general: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.general, Unset):
+            general = self.general.to_dict()
+
+        field_dict: dict[str, Any] = {}
+        for prop_name, prop in self.additional_properties.items():
+            field_dict[prop_name] = prop.to_dict()
+
+        field_dict.update({})
+        if plan is not UNSET:
+            field_dict["plan"] = plan
+        if build is not UNSET:
+            field_dict["build"] = build
+        if general is not UNSET:
+            field_dict["general"] = general
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        from ..models.agent_config import AgentConfig
+
+        d = dict(src_dict)
+        _plan = d.pop("plan", UNSET)
+        plan: Union[Unset, AgentConfig]
+        if isinstance(_plan, Unset):
+            plan = UNSET
+        else:
+            plan = AgentConfig.from_dict(_plan)
+
+        _build = d.pop("build", UNSET)
+        build: Union[Unset, AgentConfig]
+        if isinstance(_build, Unset):
+            build = UNSET
+        else:
+            build = AgentConfig.from_dict(_build)
+
+        _general = d.pop("general", UNSET)
+        general: Union[Unset, AgentConfig]
+        if isinstance(_general, Unset):
+            general = UNSET
+        else:
+            general = AgentConfig.from_dict(_general)
+
+        config_agent = cls(
+            plan=plan,
+            build=build,
+            general=general,
+        )
+
+        additional_properties = {}
+        for prop_name, prop_dict in d.items():
+            additional_property = AgentConfig.from_dict(prop_dict)
+
+            additional_properties[prop_name] = additional_property
+
+        config_agent.additional_properties = additional_properties
+        return config_agent
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> "AgentConfig":
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: "AgentConfig") -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 57 - 0
packages/sdk/python/src/opencode_ai/models/config_command.py

@@ -0,0 +1,57 @@
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, Any, TypeVar
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+if TYPE_CHECKING:
+    from ..models.config_command_additional_property import ConfigCommandAdditionalProperty
+
+
+T = TypeVar("T", bound="ConfigCommand")
+
+
+@_attrs_define
+class ConfigCommand:
+    """Command configuration, see https://opencode.ai/docs/commands"""
+
+    additional_properties: dict[str, "ConfigCommandAdditionalProperty"] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        field_dict: dict[str, Any] = {}
+        for prop_name, prop in self.additional_properties.items():
+            field_dict[prop_name] = prop.to_dict()
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        from ..models.config_command_additional_property import ConfigCommandAdditionalProperty
+
+        d = dict(src_dict)
+        config_command = cls()
+
+        additional_properties = {}
+        for prop_name, prop_dict in d.items():
+            additional_property = ConfigCommandAdditionalProperty.from_dict(prop_dict)
+
+            additional_properties[prop_name] = additional_property
+
+        config_command.additional_properties = additional_properties
+        return config_command
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> "ConfigCommandAdditionalProperty":
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: "ConfigCommandAdditionalProperty") -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 97 - 0
packages/sdk/python/src/opencode_ai/models/config_command_additional_property.py

@@ -0,0 +1,97 @@
+from collections.abc import Mapping
+from typing import Any, TypeVar, Union
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+T = TypeVar("T", bound="ConfigCommandAdditionalProperty")
+
+
+@_attrs_define
+class ConfigCommandAdditionalProperty:
+    """
+    Attributes:
+        template (str):
+        description (Union[Unset, str]):
+        agent (Union[Unset, str]):
+        model (Union[Unset, str]):
+        subtask (Union[Unset, bool]):
+    """
+
+    template: str
+    description: Union[Unset, str] = UNSET
+    agent: Union[Unset, str] = UNSET
+    model: Union[Unset, str] = UNSET
+    subtask: Union[Unset, bool] = UNSET
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        template = self.template
+
+        description = self.description
+
+        agent = self.agent
+
+        model = self.model
+
+        subtask = self.subtask
+
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update(
+            {
+                "template": template,
+            }
+        )
+        if description is not UNSET:
+            field_dict["description"] = description
+        if agent is not UNSET:
+            field_dict["agent"] = agent
+        if model is not UNSET:
+            field_dict["model"] = model
+        if subtask is not UNSET:
+            field_dict["subtask"] = subtask
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        d = dict(src_dict)
+        template = d.pop("template")
+
+        description = d.pop("description", UNSET)
+
+        agent = d.pop("agent", UNSET)
+
+        model = d.pop("model", UNSET)
+
+        subtask = d.pop("subtask", UNSET)
+
+        config_command_additional_property = cls(
+            template=template,
+            description=description,
+            agent=agent,
+            model=model,
+            subtask=subtask,
+        )
+
+        config_command_additional_property.additional_properties = d
+        return config_command_additional_property
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 81 - 0
packages/sdk/python/src/opencode_ai/models/config_experimental.py

@@ -0,0 +1,81 @@
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, Any, TypeVar, Union
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+if TYPE_CHECKING:
+    from ..models.config_experimental_hook import ConfigExperimentalHook
+
+
+T = TypeVar("T", bound="ConfigExperimental")
+
+
+@_attrs_define
+class ConfigExperimental:
+    """
+    Attributes:
+        hook (Union[Unset, ConfigExperimentalHook]):
+        disable_paste_summary (Union[Unset, bool]):
+    """
+
+    hook: Union[Unset, "ConfigExperimentalHook"] = UNSET
+    disable_paste_summary: Union[Unset, bool] = UNSET
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        hook: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.hook, Unset):
+            hook = self.hook.to_dict()
+
+        disable_paste_summary = self.disable_paste_summary
+
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update({})
+        if hook is not UNSET:
+            field_dict["hook"] = hook
+        if disable_paste_summary is not UNSET:
+            field_dict["disable_paste_summary"] = disable_paste_summary
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        from ..models.config_experimental_hook import ConfigExperimentalHook
+
+        d = dict(src_dict)
+        _hook = d.pop("hook", UNSET)
+        hook: Union[Unset, ConfigExperimentalHook]
+        if isinstance(_hook, Unset):
+            hook = UNSET
+        else:
+            hook = ConfigExperimentalHook.from_dict(_hook)
+
+        disable_paste_summary = d.pop("disable_paste_summary", UNSET)
+
+        config_experimental = cls(
+            hook=hook,
+            disable_paste_summary=disable_paste_summary,
+        )
+
+        config_experimental.additional_properties = d
+        return config_experimental
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 93 - 0
packages/sdk/python/src/opencode_ai/models/config_experimental_hook.py

@@ -0,0 +1,93 @@
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, Any, TypeVar, Union
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+if TYPE_CHECKING:
+    from ..models.config_experimental_hook_file_edited import ConfigExperimentalHookFileEdited
+    from ..models.config_experimental_hook_session_completed_item import ConfigExperimentalHookSessionCompletedItem
+
+
+T = TypeVar("T", bound="ConfigExperimentalHook")
+
+
+@_attrs_define
+class ConfigExperimentalHook:
+    """
+    Attributes:
+        file_edited (Union[Unset, ConfigExperimentalHookFileEdited]):
+        session_completed (Union[Unset, list['ConfigExperimentalHookSessionCompletedItem']]):
+    """
+
+    file_edited: Union[Unset, "ConfigExperimentalHookFileEdited"] = UNSET
+    session_completed: Union[Unset, list["ConfigExperimentalHookSessionCompletedItem"]] = UNSET
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        file_edited: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.file_edited, Unset):
+            file_edited = self.file_edited.to_dict()
+
+        session_completed: Union[Unset, list[dict[str, Any]]] = UNSET
+        if not isinstance(self.session_completed, Unset):
+            session_completed = []
+            for session_completed_item_data in self.session_completed:
+                session_completed_item = session_completed_item_data.to_dict()
+                session_completed.append(session_completed_item)
+
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update({})
+        if file_edited is not UNSET:
+            field_dict["file_edited"] = file_edited
+        if session_completed is not UNSET:
+            field_dict["session_completed"] = session_completed
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        from ..models.config_experimental_hook_file_edited import ConfigExperimentalHookFileEdited
+        from ..models.config_experimental_hook_session_completed_item import ConfigExperimentalHookSessionCompletedItem
+
+        d = dict(src_dict)
+        _file_edited = d.pop("file_edited", UNSET)
+        file_edited: Union[Unset, ConfigExperimentalHookFileEdited]
+        if isinstance(_file_edited, Unset):
+            file_edited = UNSET
+        else:
+            file_edited = ConfigExperimentalHookFileEdited.from_dict(_file_edited)
+
+        session_completed = []
+        _session_completed = d.pop("session_completed", UNSET)
+        for session_completed_item_data in _session_completed or []:
+            session_completed_item = ConfigExperimentalHookSessionCompletedItem.from_dict(session_completed_item_data)
+
+            session_completed.append(session_completed_item)
+
+        config_experimental_hook = cls(
+            file_edited=file_edited,
+            session_completed=session_completed,
+        )
+
+        config_experimental_hook.additional_properties = d
+        return config_experimental_hook
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 73 - 0
packages/sdk/python/src/opencode_ai/models/config_experimental_hook_file_edited.py

@@ -0,0 +1,73 @@
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, Any, TypeVar
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+if TYPE_CHECKING:
+    from ..models.config_experimental_hook_file_edited_additional_property_item import (
+        ConfigExperimentalHookFileEditedAdditionalPropertyItem,
+    )
+
+
+T = TypeVar("T", bound="ConfigExperimentalHookFileEdited")
+
+
+@_attrs_define
+class ConfigExperimentalHookFileEdited:
+    """ """
+
+    additional_properties: dict[str, list["ConfigExperimentalHookFileEditedAdditionalPropertyItem"]] = _attrs_field(
+        init=False, factory=dict
+    )
+
+    def to_dict(self) -> dict[str, Any]:
+        field_dict: dict[str, Any] = {}
+        for prop_name, prop in self.additional_properties.items():
+            field_dict[prop_name] = []
+            for additional_property_item_data in prop:
+                additional_property_item = additional_property_item_data.to_dict()
+                field_dict[prop_name].append(additional_property_item)
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        from ..models.config_experimental_hook_file_edited_additional_property_item import (
+            ConfigExperimentalHookFileEditedAdditionalPropertyItem,
+        )
+
+        d = dict(src_dict)
+        config_experimental_hook_file_edited = cls()
+
+        additional_properties = {}
+        for prop_name, prop_dict in d.items():
+            additional_property = []
+            _additional_property = prop_dict
+            for additional_property_item_data in _additional_property:
+                additional_property_item = ConfigExperimentalHookFileEditedAdditionalPropertyItem.from_dict(
+                    additional_property_item_data
+                )
+
+                additional_property.append(additional_property_item)
+
+            additional_properties[prop_name] = additional_property
+
+        config_experimental_hook_file_edited.additional_properties = additional_properties
+        return config_experimental_hook_file_edited
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> list["ConfigExperimentalHookFileEditedAdditionalPropertyItem"]:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: list["ConfigExperimentalHookFileEditedAdditionalPropertyItem"]) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 87 - 0
packages/sdk/python/src/opencode_ai/models/config_experimental_hook_file_edited_additional_property_item.py

@@ -0,0 +1,87 @@
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, Any, TypeVar, Union, cast
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+if TYPE_CHECKING:
+    from ..models.config_experimental_hook_file_edited_additional_property_item_environment import (
+        ConfigExperimentalHookFileEditedAdditionalPropertyItemEnvironment,
+    )
+
+
+T = TypeVar("T", bound="ConfigExperimentalHookFileEditedAdditionalPropertyItem")
+
+
+@_attrs_define
+class ConfigExperimentalHookFileEditedAdditionalPropertyItem:
+    """
+    Attributes:
+        command (list[str]):
+        environment (Union[Unset, ConfigExperimentalHookFileEditedAdditionalPropertyItemEnvironment]):
+    """
+
+    command: list[str]
+    environment: Union[Unset, "ConfigExperimentalHookFileEditedAdditionalPropertyItemEnvironment"] = UNSET
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        command = self.command
+
+        environment: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.environment, Unset):
+            environment = self.environment.to_dict()
+
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update(
+            {
+                "command": command,
+            }
+        )
+        if environment is not UNSET:
+            field_dict["environment"] = environment
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        from ..models.config_experimental_hook_file_edited_additional_property_item_environment import (
+            ConfigExperimentalHookFileEditedAdditionalPropertyItemEnvironment,
+        )
+
+        d = dict(src_dict)
+        command = cast(list[str], d.pop("command"))
+
+        _environment = d.pop("environment", UNSET)
+        environment: Union[Unset, ConfigExperimentalHookFileEditedAdditionalPropertyItemEnvironment]
+        if isinstance(_environment, Unset):
+            environment = UNSET
+        else:
+            environment = ConfigExperimentalHookFileEditedAdditionalPropertyItemEnvironment.from_dict(_environment)
+
+        config_experimental_hook_file_edited_additional_property_item = cls(
+            command=command,
+            environment=environment,
+        )
+
+        config_experimental_hook_file_edited_additional_property_item.additional_properties = d
+        return config_experimental_hook_file_edited_additional_property_item
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 44 - 0
packages/sdk/python/src/opencode_ai/models/config_experimental_hook_file_edited_additional_property_item_environment.py

@@ -0,0 +1,44 @@
+from collections.abc import Mapping
+from typing import Any, TypeVar
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+T = TypeVar("T", bound="ConfigExperimentalHookFileEditedAdditionalPropertyItemEnvironment")
+
+
+@_attrs_define
+class ConfigExperimentalHookFileEditedAdditionalPropertyItemEnvironment:
+    """ """
+
+    additional_properties: dict[str, str] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        d = dict(src_dict)
+        config_experimental_hook_file_edited_additional_property_item_environment = cls()
+
+        config_experimental_hook_file_edited_additional_property_item_environment.additional_properties = d
+        return config_experimental_hook_file_edited_additional_property_item_environment
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> str:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: str) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 87 - 0
packages/sdk/python/src/opencode_ai/models/config_experimental_hook_session_completed_item.py

@@ -0,0 +1,87 @@
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, Any, TypeVar, Union, cast
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+if TYPE_CHECKING:
+    from ..models.config_experimental_hook_session_completed_item_environment import (
+        ConfigExperimentalHookSessionCompletedItemEnvironment,
+    )
+
+
+T = TypeVar("T", bound="ConfigExperimentalHookSessionCompletedItem")
+
+
+@_attrs_define
+class ConfigExperimentalHookSessionCompletedItem:
+    """
+    Attributes:
+        command (list[str]):
+        environment (Union[Unset, ConfigExperimentalHookSessionCompletedItemEnvironment]):
+    """
+
+    command: list[str]
+    environment: Union[Unset, "ConfigExperimentalHookSessionCompletedItemEnvironment"] = UNSET
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        command = self.command
+
+        environment: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.environment, Unset):
+            environment = self.environment.to_dict()
+
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update(
+            {
+                "command": command,
+            }
+        )
+        if environment is not UNSET:
+            field_dict["environment"] = environment
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        from ..models.config_experimental_hook_session_completed_item_environment import (
+            ConfigExperimentalHookSessionCompletedItemEnvironment,
+        )
+
+        d = dict(src_dict)
+        command = cast(list[str], d.pop("command"))
+
+        _environment = d.pop("environment", UNSET)
+        environment: Union[Unset, ConfigExperimentalHookSessionCompletedItemEnvironment]
+        if isinstance(_environment, Unset):
+            environment = UNSET
+        else:
+            environment = ConfigExperimentalHookSessionCompletedItemEnvironment.from_dict(_environment)
+
+        config_experimental_hook_session_completed_item = cls(
+            command=command,
+            environment=environment,
+        )
+
+        config_experimental_hook_session_completed_item.additional_properties = d
+        return config_experimental_hook_session_completed_item
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 44 - 0
packages/sdk/python/src/opencode_ai/models/config_experimental_hook_session_completed_item_environment.py

@@ -0,0 +1,44 @@
+from collections.abc import Mapping
+from typing import Any, TypeVar
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+T = TypeVar("T", bound="ConfigExperimentalHookSessionCompletedItemEnvironment")
+
+
+@_attrs_define
+class ConfigExperimentalHookSessionCompletedItemEnvironment:
+    """ """
+
+    additional_properties: dict[str, str] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        d = dict(src_dict)
+        config_experimental_hook_session_completed_item_environment = cls()
+
+        config_experimental_hook_session_completed_item_environment.additional_properties = d
+        return config_experimental_hook_session_completed_item_environment
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> str:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: str) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 57 - 0
packages/sdk/python/src/opencode_ai/models/config_formatter.py

@@ -0,0 +1,57 @@
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, Any, TypeVar
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+if TYPE_CHECKING:
+    from ..models.config_formatter_additional_property import ConfigFormatterAdditionalProperty
+
+
+T = TypeVar("T", bound="ConfigFormatter")
+
+
+@_attrs_define
+class ConfigFormatter:
+    """ """
+
+    additional_properties: dict[str, "ConfigFormatterAdditionalProperty"] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        field_dict: dict[str, Any] = {}
+        for prop_name, prop in self.additional_properties.items():
+            field_dict[prop_name] = prop.to_dict()
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        from ..models.config_formatter_additional_property import ConfigFormatterAdditionalProperty
+
+        d = dict(src_dict)
+        config_formatter = cls()
+
+        additional_properties = {}
+        for prop_name, prop_dict in d.items():
+            additional_property = ConfigFormatterAdditionalProperty.from_dict(prop_dict)
+
+            additional_properties[prop_name] = additional_property
+
+        config_formatter.additional_properties = additional_properties
+        return config_formatter
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> "ConfigFormatterAdditionalProperty":
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: "ConfigFormatterAdditionalProperty") -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 105 - 0
packages/sdk/python/src/opencode_ai/models/config_formatter_additional_property.py

@@ -0,0 +1,105 @@
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, Any, TypeVar, Union, cast
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+if TYPE_CHECKING:
+    from ..models.config_formatter_additional_property_environment import ConfigFormatterAdditionalPropertyEnvironment
+
+
+T = TypeVar("T", bound="ConfigFormatterAdditionalProperty")
+
+
+@_attrs_define
+class ConfigFormatterAdditionalProperty:
+    """
+    Attributes:
+        disabled (Union[Unset, bool]):
+        command (Union[Unset, list[str]]):
+        environment (Union[Unset, ConfigFormatterAdditionalPropertyEnvironment]):
+        extensions (Union[Unset, list[str]]):
+    """
+
+    disabled: Union[Unset, bool] = UNSET
+    command: Union[Unset, list[str]] = UNSET
+    environment: Union[Unset, "ConfigFormatterAdditionalPropertyEnvironment"] = UNSET
+    extensions: Union[Unset, list[str]] = UNSET
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        disabled = self.disabled
+
+        command: Union[Unset, list[str]] = UNSET
+        if not isinstance(self.command, Unset):
+            command = self.command
+
+        environment: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.environment, Unset):
+            environment = self.environment.to_dict()
+
+        extensions: Union[Unset, list[str]] = UNSET
+        if not isinstance(self.extensions, Unset):
+            extensions = self.extensions
+
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update({})
+        if disabled is not UNSET:
+            field_dict["disabled"] = disabled
+        if command is not UNSET:
+            field_dict["command"] = command
+        if environment is not UNSET:
+            field_dict["environment"] = environment
+        if extensions is not UNSET:
+            field_dict["extensions"] = extensions
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        from ..models.config_formatter_additional_property_environment import (
+            ConfigFormatterAdditionalPropertyEnvironment,
+        )
+
+        d = dict(src_dict)
+        disabled = d.pop("disabled", UNSET)
+
+        command = cast(list[str], d.pop("command", UNSET))
+
+        _environment = d.pop("environment", UNSET)
+        environment: Union[Unset, ConfigFormatterAdditionalPropertyEnvironment]
+        if isinstance(_environment, Unset):
+            environment = UNSET
+        else:
+            environment = ConfigFormatterAdditionalPropertyEnvironment.from_dict(_environment)
+
+        extensions = cast(list[str], d.pop("extensions", UNSET))
+
+        config_formatter_additional_property = cls(
+            disabled=disabled,
+            command=command,
+            environment=environment,
+            extensions=extensions,
+        )
+
+        config_formatter_additional_property.additional_properties = d
+        return config_formatter_additional_property
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 44 - 0
packages/sdk/python/src/opencode_ai/models/config_formatter_additional_property_environment.py

@@ -0,0 +1,44 @@
+from collections.abc import Mapping
+from typing import Any, TypeVar
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+T = TypeVar("T", bound="ConfigFormatterAdditionalPropertyEnvironment")
+
+
+@_attrs_define
+class ConfigFormatterAdditionalPropertyEnvironment:
+    """ """
+
+    additional_properties: dict[str, str] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        d = dict(src_dict)
+        config_formatter_additional_property_environment = cls()
+
+        config_formatter_additional_property_environment.additional_properties = d
+        return config_formatter_additional_property_environment
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> str:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: str) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 86 - 0
packages/sdk/python/src/opencode_ai/models/config_lsp.py

@@ -0,0 +1,86 @@
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, Any, TypeVar, Union
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+if TYPE_CHECKING:
+    from ..models.config_lsp_additional_property_type_0 import ConfigLspAdditionalPropertyType0
+    from ..models.config_lsp_additional_property_type_1 import ConfigLspAdditionalPropertyType1
+
+
+T = TypeVar("T", bound="ConfigLsp")
+
+
+@_attrs_define
+class ConfigLsp:
+    """ """
+
+    additional_properties: dict[str, Union["ConfigLspAdditionalPropertyType0", "ConfigLspAdditionalPropertyType1"]] = (
+        _attrs_field(init=False, factory=dict)
+    )
+
+    def to_dict(self) -> dict[str, Any]:
+        from ..models.config_lsp_additional_property_type_0 import ConfigLspAdditionalPropertyType0
+
+        field_dict: dict[str, Any] = {}
+        for prop_name, prop in self.additional_properties.items():
+            if isinstance(prop, ConfigLspAdditionalPropertyType0):
+                field_dict[prop_name] = prop.to_dict()
+            else:
+                field_dict[prop_name] = prop.to_dict()
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        from ..models.config_lsp_additional_property_type_0 import ConfigLspAdditionalPropertyType0
+        from ..models.config_lsp_additional_property_type_1 import ConfigLspAdditionalPropertyType1
+
+        d = dict(src_dict)
+        config_lsp = cls()
+
+        additional_properties = {}
+        for prop_name, prop_dict in d.items():
+
+            def _parse_additional_property(
+                data: object,
+            ) -> Union["ConfigLspAdditionalPropertyType0", "ConfigLspAdditionalPropertyType1"]:
+                try:
+                    if not isinstance(data, dict):
+                        raise TypeError()
+                    additional_property_type_0 = ConfigLspAdditionalPropertyType0.from_dict(data)
+
+                    return additional_property_type_0
+                except:  # noqa: E722
+                    pass
+                if not isinstance(data, dict):
+                    raise TypeError()
+                additional_property_type_1 = ConfigLspAdditionalPropertyType1.from_dict(data)
+
+                return additional_property_type_1
+
+            additional_property = _parse_additional_property(prop_dict)
+
+            additional_properties[prop_name] = additional_property
+
+        config_lsp.additional_properties = additional_properties
+        return config_lsp
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Union["ConfigLspAdditionalPropertyType0", "ConfigLspAdditionalPropertyType1"]:
+        return self.additional_properties[key]
+
+    def __setitem__(
+        self, key: str, value: Union["ConfigLspAdditionalPropertyType0", "ConfigLspAdditionalPropertyType1"]
+    ) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 59 - 0
packages/sdk/python/src/opencode_ai/models/config_lsp_additional_property_type_0.py

@@ -0,0 +1,59 @@
+from collections.abc import Mapping
+from typing import Any, TypeVar
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+T = TypeVar("T", bound="ConfigLspAdditionalPropertyType0")
+
+
+@_attrs_define
+class ConfigLspAdditionalPropertyType0:
+    """
+    Attributes:
+        disabled (bool):
+    """
+
+    disabled: bool
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        disabled = self.disabled
+
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update(
+            {
+                "disabled": disabled,
+            }
+        )
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        d = dict(src_dict)
+        disabled = d.pop("disabled")
+
+        config_lsp_additional_property_type_0 = cls(
+            disabled=disabled,
+        )
+
+        config_lsp_additional_property_type_0.additional_properties = d
+        return config_lsp_additional_property_type_0
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 125 - 0
packages/sdk/python/src/opencode_ai/models/config_lsp_additional_property_type_1.py

@@ -0,0 +1,125 @@
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, Any, TypeVar, Union, cast
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+if TYPE_CHECKING:
+    from ..models.config_lsp_additional_property_type_1_env import ConfigLspAdditionalPropertyType1Env
+    from ..models.config_lsp_additional_property_type_1_initialization import (
+        ConfigLspAdditionalPropertyType1Initialization,
+    )
+
+
+T = TypeVar("T", bound="ConfigLspAdditionalPropertyType1")
+
+
+@_attrs_define
+class ConfigLspAdditionalPropertyType1:
+    """
+    Attributes:
+        command (list[str]):
+        extensions (Union[Unset, list[str]]):
+        disabled (Union[Unset, bool]):
+        env (Union[Unset, ConfigLspAdditionalPropertyType1Env]):
+        initialization (Union[Unset, ConfigLspAdditionalPropertyType1Initialization]):
+    """
+
+    command: list[str]
+    extensions: Union[Unset, list[str]] = UNSET
+    disabled: Union[Unset, bool] = UNSET
+    env: Union[Unset, "ConfigLspAdditionalPropertyType1Env"] = UNSET
+    initialization: Union[Unset, "ConfigLspAdditionalPropertyType1Initialization"] = UNSET
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        command = self.command
+
+        extensions: Union[Unset, list[str]] = UNSET
+        if not isinstance(self.extensions, Unset):
+            extensions = self.extensions
+
+        disabled = self.disabled
+
+        env: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.env, Unset):
+            env = self.env.to_dict()
+
+        initialization: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.initialization, Unset):
+            initialization = self.initialization.to_dict()
+
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update(
+            {
+                "command": command,
+            }
+        )
+        if extensions is not UNSET:
+            field_dict["extensions"] = extensions
+        if disabled is not UNSET:
+            field_dict["disabled"] = disabled
+        if env is not UNSET:
+            field_dict["env"] = env
+        if initialization is not UNSET:
+            field_dict["initialization"] = initialization
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        from ..models.config_lsp_additional_property_type_1_env import ConfigLspAdditionalPropertyType1Env
+        from ..models.config_lsp_additional_property_type_1_initialization import (
+            ConfigLspAdditionalPropertyType1Initialization,
+        )
+
+        d = dict(src_dict)
+        command = cast(list[str], d.pop("command"))
+
+        extensions = cast(list[str], d.pop("extensions", UNSET))
+
+        disabled = d.pop("disabled", UNSET)
+
+        _env = d.pop("env", UNSET)
+        env: Union[Unset, ConfigLspAdditionalPropertyType1Env]
+        if isinstance(_env, Unset):
+            env = UNSET
+        else:
+            env = ConfigLspAdditionalPropertyType1Env.from_dict(_env)
+
+        _initialization = d.pop("initialization", UNSET)
+        initialization: Union[Unset, ConfigLspAdditionalPropertyType1Initialization]
+        if isinstance(_initialization, Unset):
+            initialization = UNSET
+        else:
+            initialization = ConfigLspAdditionalPropertyType1Initialization.from_dict(_initialization)
+
+        config_lsp_additional_property_type_1 = cls(
+            command=command,
+            extensions=extensions,
+            disabled=disabled,
+            env=env,
+            initialization=initialization,
+        )
+
+        config_lsp_additional_property_type_1.additional_properties = d
+        return config_lsp_additional_property_type_1
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 44 - 0
packages/sdk/python/src/opencode_ai/models/config_lsp_additional_property_type_1_env.py

@@ -0,0 +1,44 @@
+from collections.abc import Mapping
+from typing import Any, TypeVar
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+T = TypeVar("T", bound="ConfigLspAdditionalPropertyType1Env")
+
+
+@_attrs_define
+class ConfigLspAdditionalPropertyType1Env:
+    """ """
+
+    additional_properties: dict[str, str] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        d = dict(src_dict)
+        config_lsp_additional_property_type_1_env = cls()
+
+        config_lsp_additional_property_type_1_env.additional_properties = d
+        return config_lsp_additional_property_type_1_env
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> str:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: str) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 44 - 0
packages/sdk/python/src/opencode_ai/models/config_lsp_additional_property_type_1_initialization.py

@@ -0,0 +1,44 @@
+from collections.abc import Mapping
+from typing import Any, TypeVar
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+T = TypeVar("T", bound="ConfigLspAdditionalPropertyType1Initialization")
+
+
+@_attrs_define
+class ConfigLspAdditionalPropertyType1Initialization:
+    """ """
+
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        d = dict(src_dict)
+        config_lsp_additional_property_type_1_initialization = cls()
+
+        config_lsp_additional_property_type_1_initialization.additional_properties = d
+        return config_lsp_additional_property_type_1_initialization
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 82 - 0
packages/sdk/python/src/opencode_ai/models/config_mcp.py

@@ -0,0 +1,82 @@
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, Any, TypeVar, Union
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+if TYPE_CHECKING:
+    from ..models.mcp_local_config import McpLocalConfig
+    from ..models.mcp_remote_config import McpRemoteConfig
+
+
+T = TypeVar("T", bound="ConfigMcp")
+
+
+@_attrs_define
+class ConfigMcp:
+    """MCP (Model Context Protocol) server configurations"""
+
+    additional_properties: dict[str, Union["McpLocalConfig", "McpRemoteConfig"]] = _attrs_field(
+        init=False, factory=dict
+    )
+
+    def to_dict(self) -> dict[str, Any]:
+        from ..models.mcp_local_config import McpLocalConfig
+
+        field_dict: dict[str, Any] = {}
+        for prop_name, prop in self.additional_properties.items():
+            if isinstance(prop, McpLocalConfig):
+                field_dict[prop_name] = prop.to_dict()
+            else:
+                field_dict[prop_name] = prop.to_dict()
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        from ..models.mcp_local_config import McpLocalConfig
+        from ..models.mcp_remote_config import McpRemoteConfig
+
+        d = dict(src_dict)
+        config_mcp = cls()
+
+        additional_properties = {}
+        for prop_name, prop_dict in d.items():
+
+            def _parse_additional_property(data: object) -> Union["McpLocalConfig", "McpRemoteConfig"]:
+                try:
+                    if not isinstance(data, dict):
+                        raise TypeError()
+                    additional_property_type_0 = McpLocalConfig.from_dict(data)
+
+                    return additional_property_type_0
+                except:  # noqa: E722
+                    pass
+                if not isinstance(data, dict):
+                    raise TypeError()
+                additional_property_type_1 = McpRemoteConfig.from_dict(data)
+
+                return additional_property_type_1
+
+            additional_property = _parse_additional_property(prop_dict)
+
+            additional_properties[prop_name] = additional_property
+
+        config_mcp.additional_properties = additional_properties
+        return config_mcp
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Union["McpLocalConfig", "McpRemoteConfig"]:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Union["McpLocalConfig", "McpRemoteConfig"]) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 97 - 0
packages/sdk/python/src/opencode_ai/models/config_mode.py

@@ -0,0 +1,97 @@
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, Any, TypeVar, Union
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+if TYPE_CHECKING:
+    from ..models.agent_config import AgentConfig
+
+
+T = TypeVar("T", bound="ConfigMode")
+
+
+@_attrs_define
+class ConfigMode:
+    """@deprecated Use `agent` field instead.
+
+    Attributes:
+        build (Union[Unset, AgentConfig]):
+        plan (Union[Unset, AgentConfig]):
+    """
+
+    build: Union[Unset, "AgentConfig"] = UNSET
+    plan: Union[Unset, "AgentConfig"] = UNSET
+    additional_properties: dict[str, "AgentConfig"] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        build: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.build, Unset):
+            build = self.build.to_dict()
+
+        plan: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.plan, Unset):
+            plan = self.plan.to_dict()
+
+        field_dict: dict[str, Any] = {}
+        for prop_name, prop in self.additional_properties.items():
+            field_dict[prop_name] = prop.to_dict()
+
+        field_dict.update({})
+        if build is not UNSET:
+            field_dict["build"] = build
+        if plan is not UNSET:
+            field_dict["plan"] = plan
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        from ..models.agent_config import AgentConfig
+
+        d = dict(src_dict)
+        _build = d.pop("build", UNSET)
+        build: Union[Unset, AgentConfig]
+        if isinstance(_build, Unset):
+            build = UNSET
+        else:
+            build = AgentConfig.from_dict(_build)
+
+        _plan = d.pop("plan", UNSET)
+        plan: Union[Unset, AgentConfig]
+        if isinstance(_plan, Unset):
+            plan = UNSET
+        else:
+            plan = AgentConfig.from_dict(_plan)
+
+        config_mode = cls(
+            build=build,
+            plan=plan,
+        )
+
+        additional_properties = {}
+        for prop_name, prop_dict in d.items():
+            additional_property = AgentConfig.from_dict(prop_dict)
+
+            additional_properties[prop_name] = additional_property
+
+        config_mode.additional_properties = additional_properties
+        return config_mode
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> "AgentConfig":
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: "AgentConfig") -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 155 - 0
packages/sdk/python/src/opencode_ai/models/config_permission.py

@@ -0,0 +1,155 @@
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, Any, Literal, TypeVar, Union, cast
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+if TYPE_CHECKING:
+    from ..models.config_permission_bash_type_1 import ConfigPermissionBashType1
+
+
+T = TypeVar("T", bound="ConfigPermission")
+
+
+@_attrs_define
+class ConfigPermission:
+    """
+    Attributes:
+        edit (Union[Literal['allow'], Literal['ask'], Literal['deny'], Unset]):
+        bash (Union['ConfigPermissionBashType1', Literal['allow'], Literal['ask'], Literal['deny'], Unset]):
+        webfetch (Union[Literal['allow'], Literal['ask'], Literal['deny'], Unset]):
+    """
+
+    edit: Union[Literal["allow"], Literal["ask"], Literal["deny"], Unset] = UNSET
+    bash: Union["ConfigPermissionBashType1", Literal["allow"], Literal["ask"], Literal["deny"], Unset] = UNSET
+    webfetch: Union[Literal["allow"], Literal["ask"], Literal["deny"], Unset] = UNSET
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        from ..models.config_permission_bash_type_1 import ConfigPermissionBashType1
+
+        edit: Union[Literal["allow"], Literal["ask"], Literal["deny"], Unset]
+        if isinstance(self.edit, Unset):
+            edit = UNSET
+        else:
+            edit = self.edit
+
+        bash: Union[Literal["allow"], Literal["ask"], Literal["deny"], Unset, dict[str, Any]]
+        if isinstance(self.bash, Unset):
+            bash = UNSET
+        elif isinstance(self.bash, ConfigPermissionBashType1):
+            bash = self.bash.to_dict()
+        else:
+            bash = self.bash
+
+        webfetch: Union[Literal["allow"], Literal["ask"], Literal["deny"], Unset]
+        if isinstance(self.webfetch, Unset):
+            webfetch = UNSET
+        else:
+            webfetch = self.webfetch
+
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update({})
+        if edit is not UNSET:
+            field_dict["edit"] = edit
+        if bash is not UNSET:
+            field_dict["bash"] = bash
+        if webfetch is not UNSET:
+            field_dict["webfetch"] = webfetch
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        from ..models.config_permission_bash_type_1 import ConfigPermissionBashType1
+
+        d = dict(src_dict)
+
+        def _parse_edit(data: object) -> Union[Literal["allow"], Literal["ask"], Literal["deny"], Unset]:
+            if isinstance(data, Unset):
+                return data
+            edit_type_0 = cast(Literal["ask"], data)
+            if edit_type_0 != "ask":
+                raise ValueError(f"edit_type_0 must match const 'ask', got '{edit_type_0}'")
+            return edit_type_0
+            edit_type_1 = cast(Literal["allow"], data)
+            if edit_type_1 != "allow":
+                raise ValueError(f"edit_type_1 must match const 'allow', got '{edit_type_1}'")
+            return edit_type_1
+            edit_type_2 = cast(Literal["deny"], data)
+            if edit_type_2 != "deny":
+                raise ValueError(f"edit_type_2 must match const 'deny', got '{edit_type_2}'")
+            return edit_type_2
+
+        edit = _parse_edit(d.pop("edit", UNSET))
+
+        def _parse_bash(
+            data: object,
+        ) -> Union["ConfigPermissionBashType1", Literal["allow"], Literal["ask"], Literal["deny"], Unset]:
+            if isinstance(data, Unset):
+                return data
+            bash_type_0_type_0 = cast(Literal["ask"], data)
+            if bash_type_0_type_0 != "ask":
+                raise ValueError(f"bash_type_0_type_0 must match const 'ask', got '{bash_type_0_type_0}'")
+            return bash_type_0_type_0
+            bash_type_0_type_1 = cast(Literal["allow"], data)
+            if bash_type_0_type_1 != "allow":
+                raise ValueError(f"bash_type_0_type_1 must match const 'allow', got '{bash_type_0_type_1}'")
+            return bash_type_0_type_1
+            bash_type_0_type_2 = cast(Literal["deny"], data)
+            if bash_type_0_type_2 != "deny":
+                raise ValueError(f"bash_type_0_type_2 must match const 'deny', got '{bash_type_0_type_2}'")
+            return bash_type_0_type_2
+            if not isinstance(data, dict):
+                raise TypeError()
+            bash_type_1 = ConfigPermissionBashType1.from_dict(data)
+
+            return bash_type_1
+
+        bash = _parse_bash(d.pop("bash", UNSET))
+
+        def _parse_webfetch(data: object) -> Union[Literal["allow"], Literal["ask"], Literal["deny"], Unset]:
+            if isinstance(data, Unset):
+                return data
+            webfetch_type_0 = cast(Literal["ask"], data)
+            if webfetch_type_0 != "ask":
+                raise ValueError(f"webfetch_type_0 must match const 'ask', got '{webfetch_type_0}'")
+            return webfetch_type_0
+            webfetch_type_1 = cast(Literal["allow"], data)
+            if webfetch_type_1 != "allow":
+                raise ValueError(f"webfetch_type_1 must match const 'allow', got '{webfetch_type_1}'")
+            return webfetch_type_1
+            webfetch_type_2 = cast(Literal["deny"], data)
+            if webfetch_type_2 != "deny":
+                raise ValueError(f"webfetch_type_2 must match const 'deny', got '{webfetch_type_2}'")
+            return webfetch_type_2
+
+        webfetch = _parse_webfetch(d.pop("webfetch", UNSET))
+
+        config_permission = cls(
+            edit=edit,
+            bash=bash,
+            webfetch=webfetch,
+        )
+
+        config_permission.additional_properties = d
+        return config_permission
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 74 - 0
packages/sdk/python/src/opencode_ai/models/config_permission_bash_type_1.py

@@ -0,0 +1,74 @@
+from collections.abc import Mapping
+from typing import Any, Literal, TypeVar, Union, cast
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+T = TypeVar("T", bound="ConfigPermissionBashType1")
+
+
+@_attrs_define
+class ConfigPermissionBashType1:
+    """ """
+
+    additional_properties: dict[str, Union[Literal["allow"], Literal["ask"], Literal["deny"]]] = _attrs_field(
+        init=False, factory=dict
+    )
+
+    def to_dict(self) -> dict[str, Any]:
+        field_dict: dict[str, Any] = {}
+        for prop_name, prop in self.additional_properties.items():
+            field_dict[prop_name] = prop
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        d = dict(src_dict)
+        config_permission_bash_type_1 = cls()
+
+        additional_properties = {}
+        for prop_name, prop_dict in d.items():
+
+            def _parse_additional_property(data: object) -> Union[Literal["allow"], Literal["ask"], Literal["deny"]]:
+                additional_property_type_0 = cast(Literal["ask"], data)
+                if additional_property_type_0 != "ask":
+                    raise ValueError(
+                        f"AdditionalProperty_type_0 must match const 'ask', got '{additional_property_type_0}'"
+                    )
+                return additional_property_type_0
+                additional_property_type_1 = cast(Literal["allow"], data)
+                if additional_property_type_1 != "allow":
+                    raise ValueError(
+                        f"AdditionalProperty_type_1 must match const 'allow', got '{additional_property_type_1}'"
+                    )
+                return additional_property_type_1
+                additional_property_type_2 = cast(Literal["deny"], data)
+                if additional_property_type_2 != "deny":
+                    raise ValueError(
+                        f"AdditionalProperty_type_2 must match const 'deny', got '{additional_property_type_2}'"
+                    )
+                return additional_property_type_2
+
+            additional_property = _parse_additional_property(prop_dict)
+
+            additional_properties[prop_name] = additional_property
+
+        config_permission_bash_type_1.additional_properties = additional_properties
+        return config_permission_bash_type_1
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Union[Literal["allow"], Literal["ask"], Literal["deny"]]:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Union[Literal["allow"], Literal["ask"], Literal["deny"]]) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 57 - 0
packages/sdk/python/src/opencode_ai/models/config_provider.py

@@ -0,0 +1,57 @@
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, Any, TypeVar
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+if TYPE_CHECKING:
+    from ..models.config_provider_additional_property import ConfigProviderAdditionalProperty
+
+
+T = TypeVar("T", bound="ConfigProvider")
+
+
+@_attrs_define
+class ConfigProvider:
+    """Custom provider configurations and model overrides"""
+
+    additional_properties: dict[str, "ConfigProviderAdditionalProperty"] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        field_dict: dict[str, Any] = {}
+        for prop_name, prop in self.additional_properties.items():
+            field_dict[prop_name] = prop.to_dict()
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        from ..models.config_provider_additional_property import ConfigProviderAdditionalProperty
+
+        d = dict(src_dict)
+        config_provider = cls()
+
+        additional_properties = {}
+        for prop_name, prop_dict in d.items():
+            additional_property = ConfigProviderAdditionalProperty.from_dict(prop_dict)
+
+            additional_properties[prop_name] = additional_property
+
+        config_provider.additional_properties = additional_properties
+        return config_provider
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> "ConfigProviderAdditionalProperty":
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: "ConfigProviderAdditionalProperty") -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 118 - 0
packages/sdk/python/src/opencode_ai/models/config_provider_additional_property.py

@@ -0,0 +1,118 @@
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, Any, TypeVar, Union, cast
+
+from attrs import define as _attrs_define
+
+from ..types import UNSET, Unset
+
+if TYPE_CHECKING:
+    from ..models.config_provider_additional_property_models import ConfigProviderAdditionalPropertyModels
+    from ..models.config_provider_additional_property_options import ConfigProviderAdditionalPropertyOptions
+
+
+T = TypeVar("T", bound="ConfigProviderAdditionalProperty")
+
+
+@_attrs_define
+class ConfigProviderAdditionalProperty:
+    """
+    Attributes:
+        api (Union[Unset, str]):
+        name (Union[Unset, str]):
+        env (Union[Unset, list[str]]):
+        id (Union[Unset, str]):
+        npm (Union[Unset, str]):
+        models (Union[Unset, ConfigProviderAdditionalPropertyModels]):
+        options (Union[Unset, ConfigProviderAdditionalPropertyOptions]):
+    """
+
+    api: Union[Unset, str] = UNSET
+    name: Union[Unset, str] = UNSET
+    env: Union[Unset, list[str]] = UNSET
+    id: Union[Unset, str] = UNSET
+    npm: Union[Unset, str] = UNSET
+    models: Union[Unset, "ConfigProviderAdditionalPropertyModels"] = UNSET
+    options: Union[Unset, "ConfigProviderAdditionalPropertyOptions"] = UNSET
+
+    def to_dict(self) -> dict[str, Any]:
+        api = self.api
+
+        name = self.name
+
+        env: Union[Unset, list[str]] = UNSET
+        if not isinstance(self.env, Unset):
+            env = self.env
+
+        id = self.id
+
+        npm = self.npm
+
+        models: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.models, Unset):
+            models = self.models.to_dict()
+
+        options: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.options, Unset):
+            options = self.options.to_dict()
+
+        field_dict: dict[str, Any] = {}
+
+        field_dict.update({})
+        if api is not UNSET:
+            field_dict["api"] = api
+        if name is not UNSET:
+            field_dict["name"] = name
+        if env is not UNSET:
+            field_dict["env"] = env
+        if id is not UNSET:
+            field_dict["id"] = id
+        if npm is not UNSET:
+            field_dict["npm"] = npm
+        if models is not UNSET:
+            field_dict["models"] = models
+        if options is not UNSET:
+            field_dict["options"] = options
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        from ..models.config_provider_additional_property_models import ConfigProviderAdditionalPropertyModels
+        from ..models.config_provider_additional_property_options import ConfigProviderAdditionalPropertyOptions
+
+        d = dict(src_dict)
+        api = d.pop("api", UNSET)
+
+        name = d.pop("name", UNSET)
+
+        env = cast(list[str], d.pop("env", UNSET))
+
+        id = d.pop("id", UNSET)
+
+        npm = d.pop("npm", UNSET)
+
+        _models = d.pop("models", UNSET)
+        models: Union[Unset, ConfigProviderAdditionalPropertyModels]
+        if isinstance(_models, Unset):
+            models = UNSET
+        else:
+            models = ConfigProviderAdditionalPropertyModels.from_dict(_models)
+
+        _options = d.pop("options", UNSET)
+        options: Union[Unset, ConfigProviderAdditionalPropertyOptions]
+        if isinstance(_options, Unset):
+            options = UNSET
+        else:
+            options = ConfigProviderAdditionalPropertyOptions.from_dict(_options)
+
+        config_provider_additional_property = cls(
+            api=api,
+            name=name,
+            env=env,
+            id=id,
+            npm=npm,
+            models=models,
+            options=options,
+        )
+
+        return config_provider_additional_property

+ 63 - 0
packages/sdk/python/src/opencode_ai/models/config_provider_additional_property_models.py

@@ -0,0 +1,63 @@
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, Any, TypeVar
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+if TYPE_CHECKING:
+    from ..models.config_provider_additional_property_models_additional_property import (
+        ConfigProviderAdditionalPropertyModelsAdditionalProperty,
+    )
+
+
+T = TypeVar("T", bound="ConfigProviderAdditionalPropertyModels")
+
+
+@_attrs_define
+class ConfigProviderAdditionalPropertyModels:
+    """ """
+
+    additional_properties: dict[str, "ConfigProviderAdditionalPropertyModelsAdditionalProperty"] = _attrs_field(
+        init=False, factory=dict
+    )
+
+    def to_dict(self) -> dict[str, Any]:
+        field_dict: dict[str, Any] = {}
+        for prop_name, prop in self.additional_properties.items():
+            field_dict[prop_name] = prop.to_dict()
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        from ..models.config_provider_additional_property_models_additional_property import (
+            ConfigProviderAdditionalPropertyModelsAdditionalProperty,
+        )
+
+        d = dict(src_dict)
+        config_provider_additional_property_models = cls()
+
+        additional_properties = {}
+        for prop_name, prop_dict in d.items():
+            additional_property = ConfigProviderAdditionalPropertyModelsAdditionalProperty.from_dict(prop_dict)
+
+            additional_properties[prop_name] = additional_property
+
+        config_provider_additional_property_models.additional_properties = additional_properties
+        return config_provider_additional_property_models
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> "ConfigProviderAdditionalPropertyModelsAdditionalProperty":
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: "ConfigProviderAdditionalPropertyModelsAdditionalProperty") -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 214 - 0
packages/sdk/python/src/opencode_ai/models/config_provider_additional_property_models_additional_property.py

@@ -0,0 +1,214 @@
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, Any, TypeVar, Union
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+if TYPE_CHECKING:
+    from ..models.config_provider_additional_property_models_additional_property_cost import (
+        ConfigProviderAdditionalPropertyModelsAdditionalPropertyCost,
+    )
+    from ..models.config_provider_additional_property_models_additional_property_limit import (
+        ConfigProviderAdditionalPropertyModelsAdditionalPropertyLimit,
+    )
+    from ..models.config_provider_additional_property_models_additional_property_options import (
+        ConfigProviderAdditionalPropertyModelsAdditionalPropertyOptions,
+    )
+    from ..models.config_provider_additional_property_models_additional_property_provider import (
+        ConfigProviderAdditionalPropertyModelsAdditionalPropertyProvider,
+    )
+
+
+T = TypeVar("T", bound="ConfigProviderAdditionalPropertyModelsAdditionalProperty")
+
+
+@_attrs_define
+class ConfigProviderAdditionalPropertyModelsAdditionalProperty:
+    """
+    Attributes:
+        id (Union[Unset, str]):
+        name (Union[Unset, str]):
+        release_date (Union[Unset, str]):
+        attachment (Union[Unset, bool]):
+        reasoning (Union[Unset, bool]):
+        temperature (Union[Unset, bool]):
+        tool_call (Union[Unset, bool]):
+        cost (Union[Unset, ConfigProviderAdditionalPropertyModelsAdditionalPropertyCost]):
+        limit (Union[Unset, ConfigProviderAdditionalPropertyModelsAdditionalPropertyLimit]):
+        experimental (Union[Unset, bool]):
+        options (Union[Unset, ConfigProviderAdditionalPropertyModelsAdditionalPropertyOptions]):
+        provider (Union[Unset, ConfigProviderAdditionalPropertyModelsAdditionalPropertyProvider]):
+    """
+
+    id: Union[Unset, str] = UNSET
+    name: Union[Unset, str] = UNSET
+    release_date: Union[Unset, str] = UNSET
+    attachment: Union[Unset, bool] = UNSET
+    reasoning: Union[Unset, bool] = UNSET
+    temperature: Union[Unset, bool] = UNSET
+    tool_call: Union[Unset, bool] = UNSET
+    cost: Union[Unset, "ConfigProviderAdditionalPropertyModelsAdditionalPropertyCost"] = UNSET
+    limit: Union[Unset, "ConfigProviderAdditionalPropertyModelsAdditionalPropertyLimit"] = UNSET
+    experimental: Union[Unset, bool] = UNSET
+    options: Union[Unset, "ConfigProviderAdditionalPropertyModelsAdditionalPropertyOptions"] = UNSET
+    provider: Union[Unset, "ConfigProviderAdditionalPropertyModelsAdditionalPropertyProvider"] = UNSET
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        id = self.id
+
+        name = self.name
+
+        release_date = self.release_date
+
+        attachment = self.attachment
+
+        reasoning = self.reasoning
+
+        temperature = self.temperature
+
+        tool_call = self.tool_call
+
+        cost: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.cost, Unset):
+            cost = self.cost.to_dict()
+
+        limit: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.limit, Unset):
+            limit = self.limit.to_dict()
+
+        experimental = self.experimental
+
+        options: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.options, Unset):
+            options = self.options.to_dict()
+
+        provider: Union[Unset, dict[str, Any]] = UNSET
+        if not isinstance(self.provider, Unset):
+            provider = self.provider.to_dict()
+
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update({})
+        if id is not UNSET:
+            field_dict["id"] = id
+        if name is not UNSET:
+            field_dict["name"] = name
+        if release_date is not UNSET:
+            field_dict["release_date"] = release_date
+        if attachment is not UNSET:
+            field_dict["attachment"] = attachment
+        if reasoning is not UNSET:
+            field_dict["reasoning"] = reasoning
+        if temperature is not UNSET:
+            field_dict["temperature"] = temperature
+        if tool_call is not UNSET:
+            field_dict["tool_call"] = tool_call
+        if cost is not UNSET:
+            field_dict["cost"] = cost
+        if limit is not UNSET:
+            field_dict["limit"] = limit
+        if experimental is not UNSET:
+            field_dict["experimental"] = experimental
+        if options is not UNSET:
+            field_dict["options"] = options
+        if provider is not UNSET:
+            field_dict["provider"] = provider
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        from ..models.config_provider_additional_property_models_additional_property_cost import (
+            ConfigProviderAdditionalPropertyModelsAdditionalPropertyCost,
+        )
+        from ..models.config_provider_additional_property_models_additional_property_limit import (
+            ConfigProviderAdditionalPropertyModelsAdditionalPropertyLimit,
+        )
+        from ..models.config_provider_additional_property_models_additional_property_options import (
+            ConfigProviderAdditionalPropertyModelsAdditionalPropertyOptions,
+        )
+        from ..models.config_provider_additional_property_models_additional_property_provider import (
+            ConfigProviderAdditionalPropertyModelsAdditionalPropertyProvider,
+        )
+
+        d = dict(src_dict)
+        id = d.pop("id", UNSET)
+
+        name = d.pop("name", UNSET)
+
+        release_date = d.pop("release_date", UNSET)
+
+        attachment = d.pop("attachment", UNSET)
+
+        reasoning = d.pop("reasoning", UNSET)
+
+        temperature = d.pop("temperature", UNSET)
+
+        tool_call = d.pop("tool_call", UNSET)
+
+        _cost = d.pop("cost", UNSET)
+        cost: Union[Unset, ConfigProviderAdditionalPropertyModelsAdditionalPropertyCost]
+        if isinstance(_cost, Unset):
+            cost = UNSET
+        else:
+            cost = ConfigProviderAdditionalPropertyModelsAdditionalPropertyCost.from_dict(_cost)
+
+        _limit = d.pop("limit", UNSET)
+        limit: Union[Unset, ConfigProviderAdditionalPropertyModelsAdditionalPropertyLimit]
+        if isinstance(_limit, Unset):
+            limit = UNSET
+        else:
+            limit = ConfigProviderAdditionalPropertyModelsAdditionalPropertyLimit.from_dict(_limit)
+
+        experimental = d.pop("experimental", UNSET)
+
+        _options = d.pop("options", UNSET)
+        options: Union[Unset, ConfigProviderAdditionalPropertyModelsAdditionalPropertyOptions]
+        if isinstance(_options, Unset):
+            options = UNSET
+        else:
+            options = ConfigProviderAdditionalPropertyModelsAdditionalPropertyOptions.from_dict(_options)
+
+        _provider = d.pop("provider", UNSET)
+        provider: Union[Unset, ConfigProviderAdditionalPropertyModelsAdditionalPropertyProvider]
+        if isinstance(_provider, Unset):
+            provider = UNSET
+        else:
+            provider = ConfigProviderAdditionalPropertyModelsAdditionalPropertyProvider.from_dict(_provider)
+
+        config_provider_additional_property_models_additional_property = cls(
+            id=id,
+            name=name,
+            release_date=release_date,
+            attachment=attachment,
+            reasoning=reasoning,
+            temperature=temperature,
+            tool_call=tool_call,
+            cost=cost,
+            limit=limit,
+            experimental=experimental,
+            options=options,
+            provider=provider,
+        )
+
+        config_provider_additional_property_models_additional_property.additional_properties = d
+        return config_provider_additional_property_models_additional_property
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 87 - 0
packages/sdk/python/src/opencode_ai/models/config_provider_additional_property_models_additional_property_cost.py

@@ -0,0 +1,87 @@
+from collections.abc import Mapping
+from typing import Any, TypeVar, Union
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+T = TypeVar("T", bound="ConfigProviderAdditionalPropertyModelsAdditionalPropertyCost")
+
+
+@_attrs_define
+class ConfigProviderAdditionalPropertyModelsAdditionalPropertyCost:
+    """
+    Attributes:
+        input_ (float):
+        output (float):
+        cache_read (Union[Unset, float]):
+        cache_write (Union[Unset, float]):
+    """
+
+    input_: float
+    output: float
+    cache_read: Union[Unset, float] = UNSET
+    cache_write: Union[Unset, float] = UNSET
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        input_ = self.input_
+
+        output = self.output
+
+        cache_read = self.cache_read
+
+        cache_write = self.cache_write
+
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update(
+            {
+                "input": input_,
+                "output": output,
+            }
+        )
+        if cache_read is not UNSET:
+            field_dict["cache_read"] = cache_read
+        if cache_write is not UNSET:
+            field_dict["cache_write"] = cache_write
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        d = dict(src_dict)
+        input_ = d.pop("input")
+
+        output = d.pop("output")
+
+        cache_read = d.pop("cache_read", UNSET)
+
+        cache_write = d.pop("cache_write", UNSET)
+
+        config_provider_additional_property_models_additional_property_cost = cls(
+            input_=input_,
+            output=output,
+            cache_read=cache_read,
+            cache_write=cache_write,
+        )
+
+        config_provider_additional_property_models_additional_property_cost.additional_properties = d
+        return config_provider_additional_property_models_additional_property_cost
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 67 - 0
packages/sdk/python/src/opencode_ai/models/config_provider_additional_property_models_additional_property_limit.py

@@ -0,0 +1,67 @@
+from collections.abc import Mapping
+from typing import Any, TypeVar
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+T = TypeVar("T", bound="ConfigProviderAdditionalPropertyModelsAdditionalPropertyLimit")
+
+
+@_attrs_define
+class ConfigProviderAdditionalPropertyModelsAdditionalPropertyLimit:
+    """
+    Attributes:
+        context (float):
+        output (float):
+    """
+
+    context: float
+    output: float
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        context = self.context
+
+        output = self.output
+
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update(
+            {
+                "context": context,
+                "output": output,
+            }
+        )
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        d = dict(src_dict)
+        context = d.pop("context")
+
+        output = d.pop("output")
+
+        config_provider_additional_property_models_additional_property_limit = cls(
+            context=context,
+            output=output,
+        )
+
+        config_provider_additional_property_models_additional_property_limit.additional_properties = d
+        return config_provider_additional_property_models_additional_property_limit
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 44 - 0
packages/sdk/python/src/opencode_ai/models/config_provider_additional_property_models_additional_property_options.py

@@ -0,0 +1,44 @@
+from collections.abc import Mapping
+from typing import Any, TypeVar
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+T = TypeVar("T", bound="ConfigProviderAdditionalPropertyModelsAdditionalPropertyOptions")
+
+
+@_attrs_define
+class ConfigProviderAdditionalPropertyModelsAdditionalPropertyOptions:
+    """ """
+
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        d = dict(src_dict)
+        config_provider_additional_property_models_additional_property_options = cls()
+
+        config_provider_additional_property_models_additional_property_options.additional_properties = d
+        return config_provider_additional_property_models_additional_property_options
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 59 - 0
packages/sdk/python/src/opencode_ai/models/config_provider_additional_property_models_additional_property_provider.py

@@ -0,0 +1,59 @@
+from collections.abc import Mapping
+from typing import Any, TypeVar
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+T = TypeVar("T", bound="ConfigProviderAdditionalPropertyModelsAdditionalPropertyProvider")
+
+
+@_attrs_define
+class ConfigProviderAdditionalPropertyModelsAdditionalPropertyProvider:
+    """
+    Attributes:
+        npm (str):
+    """
+
+    npm: str
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        npm = self.npm
+
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update(
+            {
+                "npm": npm,
+            }
+        )
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        d = dict(src_dict)
+        npm = d.pop("npm")
+
+        config_provider_additional_property_models_additional_property_provider = cls(
+            npm=npm,
+        )
+
+        config_provider_additional_property_models_additional_property_provider.additional_properties = d
+        return config_provider_additional_property_models_additional_property_provider
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

+ 87 - 0
packages/sdk/python/src/opencode_ai/models/config_provider_additional_property_options.py

@@ -0,0 +1,87 @@
+from collections.abc import Mapping
+from typing import Any, TypeVar, Union, cast
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+T = TypeVar("T", bound="ConfigProviderAdditionalPropertyOptions")
+
+
+@_attrs_define
+class ConfigProviderAdditionalPropertyOptions:
+    """
+    Attributes:
+        api_key (Union[Unset, str]):
+        base_url (Union[Unset, str]):
+        timeout (Union[Unset, bool, int]): Timeout in milliseconds for requests to this provider. Default is 300000 (5
+            minutes). Set to false to disable timeout.
+    """
+
+    api_key: Union[Unset, str] = UNSET
+    base_url: Union[Unset, str] = UNSET
+    timeout: Union[Unset, bool, int] = UNSET
+    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+    def to_dict(self) -> dict[str, Any]:
+        api_key = self.api_key
+
+        base_url = self.base_url
+
+        timeout: Union[Unset, bool, int]
+        if isinstance(self.timeout, Unset):
+            timeout = UNSET
+        else:
+            timeout = self.timeout
+
+        field_dict: dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update({})
+        if api_key is not UNSET:
+            field_dict["apiKey"] = api_key
+        if base_url is not UNSET:
+            field_dict["baseURL"] = base_url
+        if timeout is not UNSET:
+            field_dict["timeout"] = timeout
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+        d = dict(src_dict)
+        api_key = d.pop("apiKey", UNSET)
+
+        base_url = d.pop("baseURL", UNSET)
+
+        def _parse_timeout(data: object) -> Union[Unset, bool, int]:
+            if isinstance(data, Unset):
+                return data
+            return cast(Union[Unset, bool, int], data)
+
+        timeout = _parse_timeout(d.pop("timeout", UNSET))
+
+        config_provider_additional_property_options = cls(
+            api_key=api_key,
+            base_url=base_url,
+            timeout=timeout,
+        )
+
+        config_provider_additional_property_options.additional_properties = d
+        return config_provider_additional_property_options
+
+    @property
+    def additional_keys(self) -> list[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio