pydantic-ai-opencode 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,18 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+ .coverage
9
+ htmlcov/
10
+ .pytest_cache/
11
+ .ruff_cache/
12
+ .mypy_cache/
13
+
14
+ # Virtual environments
15
+ .venv
16
+
17
+ # OS files
18
+ .DS_Store
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Joey Chilson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,167 @@
1
+ Metadata-Version: 2.4
2
+ Name: pydantic-ai-opencode
3
+ Version: 0.1.0
4
+ Summary: Pydantic AI providers and model factory for OpenCode Zen and OpenCode Go.
5
+ Project-URL: Homepage, https://github.com/joeychilson/pydantic_ai_opencode
6
+ Project-URL: Repository, https://github.com/joeychilson/pydantic_ai_opencode
7
+ Project-URL: Issues, https://github.com/joeychilson/pydantic_ai_opencode/issues
8
+ Author-email: Joey Chilson <joeychilson@outlook.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: ai,llm,opencode,providers,pydantic-ai
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: pydantic-ai-slim[anthropic,google,openai]<2.0.0,>=1.0.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
26
+ Requires-Dist: ruff>=0.14.0; extra == 'dev'
27
+ Requires-Dist: ty>=0.0.34; extra == 'dev'
28
+ Description-Content-Type: text/markdown
29
+
30
+ # pydantic-ai-opencode
31
+
32
+ Pydantic AI providers and a model factory for [OpenCode Zen](https://opencode.ai/docs/zen/) and [OpenCode Go](https://opencode.ai/docs/go/).
33
+
34
+ OpenCode exposes multiple model families through one gateway per service, but those families use different wire formats. This package chooses the right Pydantic AI model class and provider for each OpenCode model id.
35
+
36
+ ## Install
37
+
38
+ ```bash
39
+ pip install pydantic-ai-opencode
40
+ ```
41
+
42
+ or from this checkout:
43
+
44
+ ```bash
45
+ uv sync
46
+ ```
47
+
48
+ ## Authentication
49
+
50
+ Set an API key:
51
+
52
+ ```bash
53
+ export OPENCODE_API_KEY="..."
54
+ ```
55
+
56
+ or log in with the OpenCode CLI:
57
+
58
+ ```bash
59
+ opencode auth login
60
+ ```
61
+
62
+ The library checks `OPENCODE_API_KEY` first, then the matching entry in
63
+ `~/.local/share/opencode/auth.json`, for example:
64
+
65
+ ```json
66
+ {
67
+ "opencode-go": {
68
+ "type": "api",
69
+ "key": "..."
70
+ }
71
+ }
72
+ ```
73
+
74
+ ## Quick Start
75
+
76
+ ```python
77
+ from pydantic_ai import Agent
78
+ from pydantic_ai_opencode import opencode_model
79
+
80
+ agent = Agent(
81
+ opencode_model("opencode-go/glm-5.1"),
82
+ instructions="You are concise and helpful.",
83
+ )
84
+
85
+ result = agent.run_sync("Explain Pydantic AI in one sentence.")
86
+ print(result.output)
87
+ ```
88
+
89
+ ## Model Factory
90
+
91
+ Use `opencode_model()` with OpenCode model ids:
92
+
93
+ ```python
94
+ from pydantic_ai_opencode import opencode_model
95
+
96
+ model = opencode_model("opencode/gpt-5.5")
97
+ model = opencode_model("opencode/claude-opus-4-7")
98
+ model = opencode_model("opencode/gemini-3.1-pro")
99
+ model = opencode_model("opencode-go/glm-5.1")
100
+ model = opencode_model("opencode-go/minimax-m2.7")
101
+ ```
102
+
103
+ You can pass an explicit API key, a custom OpenCode auth file, Pydantic AI model settings, a profile override, or a shared `httpx.AsyncClient`:
104
+
105
+ ```python
106
+ from httpx import AsyncClient
107
+ from pydantic_ai_opencode import opencode_model
108
+
109
+ client = AsyncClient(timeout=30)
110
+ model = opencode_model(
111
+ "opencode-go/qwen3.6-plus",
112
+ api_key="...",
113
+ http_client=client,
114
+ )
115
+ ```
116
+
117
+ ## Direct Providers
118
+
119
+ Use provider classes directly when you need to build the Pydantic AI model yourself or provide a custom SDK client:
120
+
121
+ ```python
122
+ from pydantic_ai import Agent
123
+ from pydantic_ai.models.openai import OpenAIChatModel
124
+ from pydantic_ai_opencode import OpenCodeGoProvider
125
+
126
+ model = OpenAIChatModel(
127
+ "glm-5.1",
128
+ provider=OpenCodeGoProvider(api_key="..."),
129
+ )
130
+ agent = Agent(model)
131
+ ```
132
+
133
+ Public providers:
134
+
135
+ - `OpenCodeZenProvider`: Zen OpenAI client for GPT Responses and chat models
136
+ - `OpenCodeZenAnthropicProvider`: Zen Anthropic client for Claude models
137
+ - `OpenCodeZenGoogleProvider`: Zen Google client for Gemini models
138
+ - `OpenCodeGoProvider`: Go OpenAI client for chat models
139
+ - `OpenCodeGoAnthropicProvider`: Go Anthropic client for MiniMax messages models
140
+
141
+ ## Routing
142
+
143
+ The factory routes models according to OpenCode service and wire format:
144
+
145
+ - `opencode/gpt-*`: `OpenAIResponsesModel`
146
+ - `opencode/claude-*`: `AnthropicModel`
147
+ - `opencode/gemini-*`: `GoogleModel`
148
+ - `opencode/<chat-model>`: `OpenAIChatModel`
149
+ - `opencode-go/<chat-model>`: `OpenAIChatModel`
150
+ - `opencode-go/minimax-*`: `AnthropicModel`
151
+
152
+ List known models:
153
+
154
+ ```python
155
+ from pydantic_ai_opencode import list_models
156
+
157
+ print(list_models("opencode-go"))
158
+ ```
159
+
160
+ ## Development
161
+
162
+ ```bash
163
+ uv sync --extra dev
164
+ uv run --extra dev pytest
165
+ uv run --extra dev ruff check .
166
+ uv build
167
+ ```
@@ -0,0 +1,138 @@
1
+ # pydantic-ai-opencode
2
+
3
+ Pydantic AI providers and a model factory for [OpenCode Zen](https://opencode.ai/docs/zen/) and [OpenCode Go](https://opencode.ai/docs/go/).
4
+
5
+ OpenCode exposes multiple model families through one gateway per service, but those families use different wire formats. This package chooses the right Pydantic AI model class and provider for each OpenCode model id.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install pydantic-ai-opencode
11
+ ```
12
+
13
+ or from this checkout:
14
+
15
+ ```bash
16
+ uv sync
17
+ ```
18
+
19
+ ## Authentication
20
+
21
+ Set an API key:
22
+
23
+ ```bash
24
+ export OPENCODE_API_KEY="..."
25
+ ```
26
+
27
+ or log in with the OpenCode CLI:
28
+
29
+ ```bash
30
+ opencode auth login
31
+ ```
32
+
33
+ The library checks `OPENCODE_API_KEY` first, then the matching entry in
34
+ `~/.local/share/opencode/auth.json`, for example:
35
+
36
+ ```json
37
+ {
38
+ "opencode-go": {
39
+ "type": "api",
40
+ "key": "..."
41
+ }
42
+ }
43
+ ```
44
+
45
+ ## Quick Start
46
+
47
+ ```python
48
+ from pydantic_ai import Agent
49
+ from pydantic_ai_opencode import opencode_model
50
+
51
+ agent = Agent(
52
+ opencode_model("opencode-go/glm-5.1"),
53
+ instructions="You are concise and helpful.",
54
+ )
55
+
56
+ result = agent.run_sync("Explain Pydantic AI in one sentence.")
57
+ print(result.output)
58
+ ```
59
+
60
+ ## Model Factory
61
+
62
+ Use `opencode_model()` with OpenCode model ids:
63
+
64
+ ```python
65
+ from pydantic_ai_opencode import opencode_model
66
+
67
+ model = opencode_model("opencode/gpt-5.5")
68
+ model = opencode_model("opencode/claude-opus-4-7")
69
+ model = opencode_model("opencode/gemini-3.1-pro")
70
+ model = opencode_model("opencode-go/glm-5.1")
71
+ model = opencode_model("opencode-go/minimax-m2.7")
72
+ ```
73
+
74
+ You can pass an explicit API key, a custom OpenCode auth file, Pydantic AI model settings, a profile override, or a shared `httpx.AsyncClient`:
75
+
76
+ ```python
77
+ from httpx import AsyncClient
78
+ from pydantic_ai_opencode import opencode_model
79
+
80
+ client = AsyncClient(timeout=30)
81
+ model = opencode_model(
82
+ "opencode-go/qwen3.6-plus",
83
+ api_key="...",
84
+ http_client=client,
85
+ )
86
+ ```
87
+
88
+ ## Direct Providers
89
+
90
+ Use provider classes directly when you need to build the Pydantic AI model yourself or provide a custom SDK client:
91
+
92
+ ```python
93
+ from pydantic_ai import Agent
94
+ from pydantic_ai.models.openai import OpenAIChatModel
95
+ from pydantic_ai_opencode import OpenCodeGoProvider
96
+
97
+ model = OpenAIChatModel(
98
+ "glm-5.1",
99
+ provider=OpenCodeGoProvider(api_key="..."),
100
+ )
101
+ agent = Agent(model)
102
+ ```
103
+
104
+ Public providers:
105
+
106
+ - `OpenCodeZenProvider`: Zen OpenAI client for GPT Responses and chat models
107
+ - `OpenCodeZenAnthropicProvider`: Zen Anthropic client for Claude models
108
+ - `OpenCodeZenGoogleProvider`: Zen Google client for Gemini models
109
+ - `OpenCodeGoProvider`: Go OpenAI client for chat models
110
+ - `OpenCodeGoAnthropicProvider`: Go Anthropic client for MiniMax messages models
111
+
112
+ ## Routing
113
+
114
+ The factory routes models according to OpenCode service and wire format:
115
+
116
+ - `opencode/gpt-*`: `OpenAIResponsesModel`
117
+ - `opencode/claude-*`: `AnthropicModel`
118
+ - `opencode/gemini-*`: `GoogleModel`
119
+ - `opencode/<chat-model>`: `OpenAIChatModel`
120
+ - `opencode-go/<chat-model>`: `OpenAIChatModel`
121
+ - `opencode-go/minimax-*`: `AnthropicModel`
122
+
123
+ List known models:
124
+
125
+ ```python
126
+ from pydantic_ai_opencode import list_models
127
+
128
+ print(list_models("opencode-go"))
129
+ ```
130
+
131
+ ## Development
132
+
133
+ ```bash
134
+ uv sync --extra dev
135
+ uv run --extra dev pytest
136
+ uv run --extra dev ruff check .
137
+ uv build
138
+ ```
@@ -0,0 +1,13 @@
1
+ from __future__ import annotations
2
+
3
+ from pydantic_ai import Agent
4
+
5
+ from pydantic_ai_opencode import opencode_model
6
+
7
+ agent = Agent(
8
+ opencode_model("opencode-go/kimi-k2.6"),
9
+ instructions="You are concise and helpful.",
10
+ )
11
+
12
+ result = agent.run_sync("Explain Pydantic AI in one sentence.")
13
+ print(result.output)
@@ -0,0 +1,55 @@
1
+ """Pydantic AI integration for OpenCode Zen and OpenCode Go."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from importlib.metadata import PackageNotFoundError, version
6
+
7
+ from .auth import OpenCodeAuthError, get_opencode_api_key
8
+ from .models import opencode_model
9
+ from .providers import (
10
+ GO_BASE_URL,
11
+ ZEN_BASE_URL,
12
+ OpenCodeGoAnthropicProvider,
13
+ OpenCodeGoProvider,
14
+ OpenCodeZenAnthropicProvider,
15
+ OpenCodeZenGoogleProvider,
16
+ OpenCodeZenProvider,
17
+ )
18
+ from .registry import (
19
+ GO_MODELS,
20
+ ZEN_MODELS,
21
+ ModelInfo,
22
+ ServiceName,
23
+ WireFormat,
24
+ list_model_info,
25
+ list_models,
26
+ model_info,
27
+ )
28
+
29
+ try:
30
+ __version__ = version("pydantic-ai-opencode")
31
+ except PackageNotFoundError: # pragma: no cover - only used from an unpackaged tree
32
+ __version__ = "0.1.0"
33
+
34
+
35
+ __all__ = [
36
+ "GO_BASE_URL",
37
+ "GO_MODELS",
38
+ "OpenCodeAuthError",
39
+ "OpenCodeGoAnthropicProvider",
40
+ "OpenCodeGoProvider",
41
+ "OpenCodeZenAnthropicProvider",
42
+ "OpenCodeZenGoogleProvider",
43
+ "OpenCodeZenProvider",
44
+ "ModelInfo",
45
+ "ServiceName",
46
+ "WireFormat",
47
+ "ZEN_BASE_URL",
48
+ "ZEN_MODELS",
49
+ "__version__",
50
+ "get_opencode_api_key",
51
+ "list_model_info",
52
+ "list_models",
53
+ "model_info",
54
+ "opencode_model",
55
+ ]
@@ -0,0 +1,84 @@
1
+ """Authentication helpers for OpenCode API keys."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ from os import PathLike
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ DEFAULT_OPENCODE_API_KEY_ENV_VAR = "OPENCODE_API_KEY"
12
+ OPENCODE_AUTH_FILE = Path.home() / ".local" / "share" / "opencode" / "auth.json"
13
+ OPENCODE_SERVICES = ("opencode", "opencode-go")
14
+
15
+
16
+ class OpenCodeAuthError(RuntimeError):
17
+ """Raised when no OpenCode API key can be found."""
18
+
19
+
20
+ def read_opencode_auth_file(
21
+ auth_file: str | PathLike[str] | None = None,
22
+ ) -> dict[str, Any]:
23
+ """Read the OpenCode CLI auth file.
24
+
25
+ Invalid or missing auth files are treated as absent so callers can fall back
26
+ to other auth sources without handling filesystem details.
27
+ """
28
+ path = Path(auth_file) if auth_file is not None else OPENCODE_AUTH_FILE
29
+ try:
30
+ data = json.loads(path.read_text(encoding="utf-8"))
31
+ except (FileNotFoundError, OSError, json.JSONDecodeError):
32
+ return {}
33
+
34
+ return data if isinstance(data, dict) else {}
35
+
36
+
37
+ def get_opencode_api_key(
38
+ api_key: str | None = None,
39
+ *,
40
+ service: str = "opencode",
41
+ env_var: str = DEFAULT_OPENCODE_API_KEY_ENV_VAR,
42
+ auth_file: str | PathLike[str] | None = None,
43
+ ) -> str:
44
+ """Return an OpenCode API key from an explicit value, env var, or CLI auth.
45
+
46
+ Args:
47
+ api_key: Explicit API key. Takes precedence when provided.
48
+ service: OpenCode service auth entry to read from the CLI auth file.
49
+ env_var: Environment variable to check after `api_key`.
50
+ auth_file: Optional path to an OpenCode CLI auth file. Defaults to the
51
+ standard OpenCode auth location.
52
+
53
+ Raises:
54
+ OpenCodeAuthError: If no non-empty API key is available.
55
+ """
56
+ if api_key:
57
+ return api_key
58
+
59
+ if value := os.environ.get(env_var):
60
+ return value
61
+
62
+ auth_data = read_opencode_auth_file(auth_file)
63
+ auth_services = (service, "opencode") if service == "opencode-go" else (service,)
64
+ for auth_service in auth_services:
65
+ entry = auth_data.get(auth_service)
66
+ if isinstance(entry, dict):
67
+ key = entry.get("key")
68
+ if isinstance(key, str) and key:
69
+ return key
70
+
71
+ raise OpenCodeAuthError(
72
+ f"No OpenCode API key found for {service!r}. Set ${env_var} or run "
73
+ "`opencode auth login`."
74
+ )
75
+
76
+
77
+ __all__ = [
78
+ "DEFAULT_OPENCODE_API_KEY_ENV_VAR",
79
+ "OPENCODE_AUTH_FILE",
80
+ "OPENCODE_SERVICES",
81
+ "OpenCodeAuthError",
82
+ "get_opencode_api_key",
83
+ "read_opencode_auth_file",
84
+ ]
@@ -0,0 +1,131 @@
1
+ """Model factory for OpenCode model ids."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from os import PathLike
6
+ from typing import TYPE_CHECKING
7
+
8
+ from pydantic_ai.models import Model
9
+ from pydantic_ai.models.anthropic import AnthropicModel
10
+ from pydantic_ai.models.google import GoogleModel
11
+ from pydantic_ai.models.openai import OpenAIChatModel, OpenAIResponsesModel
12
+ from pydantic_ai.profiles import ModelProfileSpec
13
+ from pydantic_ai.settings import ModelSettings
14
+
15
+ from .auth import get_opencode_api_key
16
+ from .providers import (
17
+ OpenCodeGoAnthropicProvider,
18
+ OpenCodeGoProvider,
19
+ OpenCodeZenAnthropicProvider,
20
+ OpenCodeZenGoogleProvider,
21
+ OpenCodeZenProvider,
22
+ )
23
+ from .registry import ModelInfo, model_info
24
+
25
+ if TYPE_CHECKING:
26
+ import httpx
27
+
28
+
29
+ def _build_model(
30
+ info: ModelInfo,
31
+ *,
32
+ api_key: str,
33
+ http_client: httpx.AsyncClient | None = None,
34
+ profile: ModelProfileSpec | None = None,
35
+ settings: ModelSettings | None = None,
36
+ ) -> Model:
37
+ if info.service == "opencode":
38
+ if info.wire == "responses":
39
+ return OpenAIResponsesModel(
40
+ info.name,
41
+ provider=OpenCodeZenProvider(api_key=api_key, http_client=http_client),
42
+ profile=profile,
43
+ settings=settings,
44
+ )
45
+ if info.wire == "chat":
46
+ return OpenAIChatModel(
47
+ info.name,
48
+ provider=OpenCodeZenProvider(api_key=api_key, http_client=http_client),
49
+ profile=profile,
50
+ settings=settings,
51
+ )
52
+ if info.wire == "messages":
53
+ return AnthropicModel(
54
+ info.name,
55
+ provider=OpenCodeZenAnthropicProvider(
56
+ api_key=api_key,
57
+ http_client=http_client,
58
+ ),
59
+ profile=profile,
60
+ settings=settings,
61
+ )
62
+ if info.wire == "google":
63
+ return GoogleModel(
64
+ info.name,
65
+ provider=OpenCodeZenGoogleProvider(
66
+ api_key=api_key,
67
+ http_client=http_client,
68
+ ),
69
+ profile=profile,
70
+ settings=settings,
71
+ )
72
+
73
+ if info.service == "opencode-go":
74
+ if info.wire == "chat":
75
+ return OpenAIChatModel(
76
+ info.name,
77
+ provider=OpenCodeGoProvider(api_key=api_key, http_client=http_client),
78
+ profile=profile,
79
+ settings=settings,
80
+ )
81
+ if info.wire == "messages":
82
+ return AnthropicModel(
83
+ info.name,
84
+ provider=OpenCodeGoAnthropicProvider(
85
+ api_key=api_key,
86
+ http_client=http_client,
87
+ ),
88
+ profile=profile,
89
+ settings=settings,
90
+ )
91
+
92
+ raise ValueError(f"Unsupported OpenCode model route: {info!r}")
93
+
94
+
95
+ def opencode_model(
96
+ model_id: str,
97
+ *,
98
+ api_key: str | None = None,
99
+ auth_file: str | PathLike[str] | None = None,
100
+ http_client: httpx.AsyncClient | None = None,
101
+ profile: ModelProfileSpec | None = None,
102
+ settings: ModelSettings | None = None,
103
+ ) -> Model:
104
+ """Build a Pydantic AI model from an OpenCode model id.
105
+
106
+ Args:
107
+ model_id: OpenCode model id, for example `opencode/gpt-5.5` or
108
+ `opencode-go/glm-5.1`.
109
+ api_key: Optional OpenCode API key. If omitted, `$OPENCODE_API_KEY` and
110
+ the OpenCode CLI auth file are checked.
111
+ auth_file: Optional OpenCode CLI auth file path.
112
+ http_client: Optional shared `httpx.AsyncClient` for requests.
113
+ profile: Optional Pydantic AI model profile override.
114
+ settings: Optional default Pydantic AI model settings.
115
+ """
116
+ info = model_info(model_id)
117
+ resolved_api_key = get_opencode_api_key(
118
+ api_key,
119
+ service=info.service,
120
+ auth_file=auth_file,
121
+ )
122
+ return _build_model(
123
+ info,
124
+ api_key=resolved_api_key,
125
+ http_client=http_client,
126
+ profile=profile,
127
+ settings=settings,
128
+ )
129
+
130
+
131
+ __all__ = ["opencode_model"]