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.
- pydantic_ai_opencode-0.1.0/.gitignore +18 -0
- pydantic_ai_opencode-0.1.0/LICENSE +21 -0
- pydantic_ai_opencode-0.1.0/PKG-INFO +167 -0
- pydantic_ai_opencode-0.1.0/README.md +138 -0
- pydantic_ai_opencode-0.1.0/examples/basic_agent.py +13 -0
- pydantic_ai_opencode-0.1.0/pydantic_ai_opencode/__init__.py +55 -0
- pydantic_ai_opencode-0.1.0/pydantic_ai_opencode/auth.py +84 -0
- pydantic_ai_opencode-0.1.0/pydantic_ai_opencode/models.py +131 -0
- pydantic_ai_opencode-0.1.0/pydantic_ai_opencode/providers.py +331 -0
- pydantic_ai_opencode-0.1.0/pydantic_ai_opencode/py.typed +0 -0
- pydantic_ai_opencode-0.1.0/pydantic_ai_opencode/registry.py +178 -0
- pydantic_ai_opencode-0.1.0/pyproject.toml +56 -0
- pydantic_ai_opencode-0.1.0/tests/test_auth.py +72 -0
- pydantic_ai_opencode-0.1.0/tests/test_models.py +37 -0
- pydantic_ai_opencode-0.1.0/tests/test_providers.py +97 -0
- pydantic_ai_opencode-0.1.0/tests/test_registry.py +38 -0
- pydantic_ai_opencode-0.1.0/uv.lock +1325 -0
|
@@ -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"]
|