pydantic-claude-code 0.2.0__tar.gz → 0.3.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_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/PKG-INFO +12 -10
- {pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/README.md +11 -9
- {pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/examples/basic.py +2 -3
- {pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/pyproject.toml +1 -1
- {pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/src/pydantic_ai_claude_code/__init__.py +2 -3
- {pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/src/pydantic_ai_claude_code/model.py +23 -0
- {pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/src/pydantic_ai_claude_code/provider.py +0 -10
- {pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/tests/test_integration.py +2 -1
- {pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/tests/test_provider.py +11 -2
- {pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/.gitignore +0 -0
- {pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/LICENSE +0 -0
- {pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/src/pydantic_ai_claude_code/__main__.py +0 -0
- {pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/src/pydantic_ai_claude_code/auth.py +0 -0
- {pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/src/pydantic_ai_claude_code/config.py +0 -0
- {pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/src/pydantic_ai_claude_code/credentials.py +0 -0
- {pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/src/pydantic_ai_claude_code/flow.py +0 -0
- {pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/src/pydantic_ai_claude_code/py.typed +0 -0
- {pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/src/pydantic_ai_claude_code/storage.py +0 -0
- {pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/tests/test_callback_server.py +0 -0
- {pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/tests/test_credentials.py +0 -0
- {pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/tests/test_flow.py +0 -0
- {pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/tests/test_keyring_store.py +0 -0
- {pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/tests/test_model.py +0 -0
- {pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/tests/test_storage.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pydantic-claude-code
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Use your Claude Code subscription from a Pydantic AI Agent, with full tool support
|
|
5
5
|
Project-URL: Repository, https://github.com/mpfaffenberger/pydantic-ai-claude-code
|
|
6
6
|
Project-URL: Homepage, https://pypi.org/project/pydantic-claude-code/
|
|
@@ -47,8 +47,9 @@ Two deliberate design choices distinguish this from a fork of pydantic-ai:
|
|
|
47
47
|
executes your tools, and validates structured output. The wheel is a model
|
|
48
48
|
+ provider, not a second agent fighting for control.
|
|
49
49
|
2. **Object-only resolution.** Instead of a `claude-code:` model-name string
|
|
50
|
-
(which would require patching pydantic-ai's internals),
|
|
51
|
-
|
|
50
|
+
(which would require patching pydantic-ai's internals), construct a
|
|
51
|
+
`ClaudeCodeModel('claude-fable-5-1')` directly; it wires itself up to a
|
|
52
|
+
`ClaudeCodeProvider` by default.
|
|
52
53
|
|
|
53
54
|
## Quick start
|
|
54
55
|
|
|
@@ -57,15 +58,14 @@ import asyncio
|
|
|
57
58
|
|
|
58
59
|
from pydantic_ai import Agent
|
|
59
60
|
|
|
60
|
-
from pydantic_ai_claude_code import
|
|
61
|
+
from pydantic_ai_claude_code import ClaudeCodeModel, login
|
|
61
62
|
|
|
62
63
|
async def main() -> None:
|
|
63
64
|
# One-time: opens your browser, mints tokens, stores them
|
|
64
65
|
# (only needs to run again when tokens are revoked).
|
|
65
66
|
await login()
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
agent = Agent(provider.model('claude-fable-5-1'))
|
|
68
|
+
agent = Agent(ClaudeCodeModel('claude-fable-5-1'))
|
|
69
69
|
|
|
70
70
|
result = await agent.run('Say hi in three words.')
|
|
71
71
|
print(result.data)
|
|
@@ -77,9 +77,9 @@ asyncio.run(main())
|
|
|
77
77
|
|
|
78
78
|
```python
|
|
79
79
|
from pydantic_ai import Agent
|
|
80
|
+
from pydantic_ai_claude_code import ClaudeCodeModel
|
|
80
81
|
|
|
81
|
-
|
|
82
|
-
agent = Agent(provider.model('claude-fable-5-1'))
|
|
82
|
+
agent = Agent(ClaudeCodeModel('claude-fable-5-1'))
|
|
83
83
|
|
|
84
84
|
@agent.tool_plain
|
|
85
85
|
def add(a: int, b: int) -> int:
|
|
@@ -96,12 +96,13 @@ definitions, and structured output works the same way as with the built-in
|
|
|
96
96
|
```python
|
|
97
97
|
from pydantic import BaseModel
|
|
98
98
|
from pydantic_ai import Agent
|
|
99
|
+
from pydantic_ai_claude_code import ClaudeCodeModel
|
|
99
100
|
|
|
100
101
|
class Weather(BaseModel):
|
|
101
102
|
city: str
|
|
102
103
|
temperature_c: float
|
|
103
104
|
|
|
104
|
-
agent = Agent(
|
|
105
|
+
agent = Agent(ClaudeCodeModel('claude-fable-5-1'), output_type=Weather)
|
|
105
106
|
result = await agent.run('Weather in Paris right now?')
|
|
106
107
|
assert result.output.city == 'Paris'
|
|
107
108
|
```
|
|
@@ -110,8 +111,9 @@ assert result.output.city == 'Paris'
|
|
|
110
111
|
|
|
111
112
|
```python
|
|
112
113
|
from pydantic_ai import Agent
|
|
114
|
+
from pydantic_ai_claude_code import ClaudeCodeModel
|
|
113
115
|
|
|
114
|
-
agent = Agent(
|
|
116
|
+
agent = Agent(ClaudeCodeModel('claude-fable-5-1'))
|
|
115
117
|
async with agent.run_stream('Count from 1 to 3.') as stream:
|
|
116
118
|
async for chunk in stream.stream_text():
|
|
117
119
|
print(chunk, end='', flush=True)
|
|
@@ -23,8 +23,9 @@ Two deliberate design choices distinguish this from a fork of pydantic-ai:
|
|
|
23
23
|
executes your tools, and validates structured output. The wheel is a model
|
|
24
24
|
+ provider, not a second agent fighting for control.
|
|
25
25
|
2. **Object-only resolution.** Instead of a `claude-code:` model-name string
|
|
26
|
-
(which would require patching pydantic-ai's internals),
|
|
27
|
-
|
|
26
|
+
(which would require patching pydantic-ai's internals), construct a
|
|
27
|
+
`ClaudeCodeModel('claude-fable-5-1')` directly; it wires itself up to a
|
|
28
|
+
`ClaudeCodeProvider` by default.
|
|
28
29
|
|
|
29
30
|
## Quick start
|
|
30
31
|
|
|
@@ -33,15 +34,14 @@ import asyncio
|
|
|
33
34
|
|
|
34
35
|
from pydantic_ai import Agent
|
|
35
36
|
|
|
36
|
-
from pydantic_ai_claude_code import
|
|
37
|
+
from pydantic_ai_claude_code import ClaudeCodeModel, login
|
|
37
38
|
|
|
38
39
|
async def main() -> None:
|
|
39
40
|
# One-time: opens your browser, mints tokens, stores them
|
|
40
41
|
# (only needs to run again when tokens are revoked).
|
|
41
42
|
await login()
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
agent = Agent(provider.model('claude-fable-5-1'))
|
|
44
|
+
agent = Agent(ClaudeCodeModel('claude-fable-5-1'))
|
|
45
45
|
|
|
46
46
|
result = await agent.run('Say hi in three words.')
|
|
47
47
|
print(result.data)
|
|
@@ -53,9 +53,9 @@ asyncio.run(main())
|
|
|
53
53
|
|
|
54
54
|
```python
|
|
55
55
|
from pydantic_ai import Agent
|
|
56
|
+
from pydantic_ai_claude_code import ClaudeCodeModel
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
agent = Agent(provider.model('claude-fable-5-1'))
|
|
58
|
+
agent = Agent(ClaudeCodeModel('claude-fable-5-1'))
|
|
59
59
|
|
|
60
60
|
@agent.tool_plain
|
|
61
61
|
def add(a: int, b: int) -> int:
|
|
@@ -72,12 +72,13 @@ definitions, and structured output works the same way as with the built-in
|
|
|
72
72
|
```python
|
|
73
73
|
from pydantic import BaseModel
|
|
74
74
|
from pydantic_ai import Agent
|
|
75
|
+
from pydantic_ai_claude_code import ClaudeCodeModel
|
|
75
76
|
|
|
76
77
|
class Weather(BaseModel):
|
|
77
78
|
city: str
|
|
78
79
|
temperature_c: float
|
|
79
80
|
|
|
80
|
-
agent = Agent(
|
|
81
|
+
agent = Agent(ClaudeCodeModel('claude-fable-5-1'), output_type=Weather)
|
|
81
82
|
result = await agent.run('Weather in Paris right now?')
|
|
82
83
|
assert result.output.city == 'Paris'
|
|
83
84
|
```
|
|
@@ -86,8 +87,9 @@ assert result.output.city == 'Paris'
|
|
|
86
87
|
|
|
87
88
|
```python
|
|
88
89
|
from pydantic_ai import Agent
|
|
90
|
+
from pydantic_ai_claude_code import ClaudeCodeModel
|
|
89
91
|
|
|
90
|
-
agent = Agent(
|
|
92
|
+
agent = Agent(ClaudeCodeModel('claude-fable-5-1'))
|
|
91
93
|
async with agent.run_stream('Count from 1 to 3.') as stream:
|
|
92
94
|
async for chunk in stream.stream_text():
|
|
93
95
|
print(chunk, end='', flush=True)
|
|
@@ -11,7 +11,7 @@ import asyncio
|
|
|
11
11
|
|
|
12
12
|
from pydantic_ai import Agent
|
|
13
13
|
|
|
14
|
-
from pydantic_ai_claude_code import
|
|
14
|
+
from pydantic_ai_claude_code import ClaudeCodeModel, default_store, login
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
async def main() -> None:
|
|
@@ -19,8 +19,7 @@ async def main() -> None:
|
|
|
19
19
|
if default_store().load() is None:
|
|
20
20
|
await login()
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
agent = Agent(provider.model("claude-fable-5-1"))
|
|
22
|
+
agent = Agent(ClaudeCodeModel("claude-fable-5-1"))
|
|
24
23
|
|
|
25
24
|
result = await agent.run("Say hi in exactly three words.")
|
|
26
25
|
print("Agent said:", result.output)
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pydantic-claude-code"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Use your Claude Code subscription from a Pydantic AI Agent, with full tool support"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11,<3.15"
|
{pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/src/pydantic_ai_claude_code/__init__.py
RENAMED
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
Quick start:
|
|
4
4
|
|
|
5
5
|
from pydantic_ai import Agent
|
|
6
|
-
from pydantic_ai_claude_code import
|
|
6
|
+
from pydantic_ai_claude_code import ClaudeCodeModel
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
agent = Agent(provider.model('claude-fable-5-1'))
|
|
8
|
+
agent = Agent(ClaudeCodeModel('claude-fable-5-1'))
|
|
10
9
|
"""
|
|
11
10
|
|
|
12
11
|
from .credentials import ClaudeCodeCredentials
|
{pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/src/pydantic_ai_claude_code/model.py
RENAMED
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
5
7
|
from pydantic_ai.messages import ModelMessage, ModelRequest, SystemPromptPart
|
|
6
8
|
from pydantic_ai.models.anthropic import AnthropicModel
|
|
9
|
+
from pydantic_ai.providers import Provider
|
|
7
10
|
|
|
8
11
|
from . import config
|
|
9
12
|
|
|
@@ -17,6 +20,26 @@ class ClaudeCodeModel(AnthropicModel):
|
|
|
17
20
|
duplicate it.
|
|
18
21
|
"""
|
|
19
22
|
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
model_name: str,
|
|
26
|
+
*,
|
|
27
|
+
provider: Provider[Any] | None = None,
|
|
28
|
+
**kwargs: Any,
|
|
29
|
+
) -> None:
|
|
30
|
+
"""Initialize a Claude Code model.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
model_name: The model name to use, e.g. `'claude-fable-5-1'`.
|
|
34
|
+
provider: The provider to authenticate with. Defaults to a
|
|
35
|
+
`ClaudeCodeProvider` backed by the default token store.
|
|
36
|
+
"""
|
|
37
|
+
if provider is None:
|
|
38
|
+
from .provider import ClaudeCodeProvider
|
|
39
|
+
|
|
40
|
+
provider = ClaudeCodeProvider()
|
|
41
|
+
super().__init__(model_name, provider=provider, **kwargs)
|
|
42
|
+
|
|
20
43
|
def prepare_messages(
|
|
21
44
|
self,
|
|
22
45
|
messages: list[ModelMessage],
|
{pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/src/pydantic_ai_claude_code/provider.py
RENAMED
|
@@ -12,7 +12,6 @@ from pydantic_ai.exceptions import UserError
|
|
|
12
12
|
from pydantic_ai.providers.anthropic import AnthropicProvider
|
|
13
13
|
|
|
14
14
|
from . import config
|
|
15
|
-
from .model import ClaudeCodeModel
|
|
16
15
|
from .auth import _ClaudeCodeAuth
|
|
17
16
|
from .credentials import ClaudeCodeCredentials
|
|
18
17
|
from .storage import TokenStore, default_store
|
|
@@ -85,15 +84,6 @@ class ClaudeCodeProvider(AnthropicProvider):
|
|
|
85
84
|
"""The current credentials; refreshed in place by the auth shim."""
|
|
86
85
|
return self._claude_code_auth.credentials
|
|
87
86
|
|
|
88
|
-
def model(self, model_name: str) -> ClaudeCodeModel:
|
|
89
|
-
"""Build a `ClaudeCodeModel` bound to this provider.
|
|
90
|
-
|
|
91
|
-
This is the object-only entry point agreed for the wheel: pass its result
|
|
92
|
-
to `Agent(model=...)` without modifying pydantic-ai. The persona is
|
|
93
|
-
prepended to the system context automatically.
|
|
94
|
-
"""
|
|
95
|
-
return ClaudeCodeModel(model_name, provider=self)
|
|
96
|
-
|
|
97
87
|
@classmethod
|
|
98
88
|
def from_token_store(
|
|
99
89
|
cls,
|
|
@@ -16,6 +16,7 @@ import pytest
|
|
|
16
16
|
from pydantic_ai import Agent
|
|
17
17
|
|
|
18
18
|
from pydantic_ai_claude_code.credentials import ClaudeCodeCredentials
|
|
19
|
+
from pydantic_ai_claude_code.model import ClaudeCodeModel
|
|
19
20
|
from pydantic_ai_claude_code.provider import ClaudeCodeProvider
|
|
20
21
|
|
|
21
22
|
|
|
@@ -58,7 +59,7 @@ def messages_stub(monkeypatch) -> ThreadingHTTPServer:
|
|
|
58
59
|
def test_agent_round_trip(messages_stub, model_name: str) -> None:
|
|
59
60
|
creds = ClaudeCodeCredentials(access_token="sub-token", refresh_token="refresh")
|
|
60
61
|
provider = ClaudeCodeProvider(credentials=creds, base_url=f"http://127.0.0.1:{messages_stub.server_address[1]}")
|
|
61
|
-
agent = Agent(
|
|
62
|
+
agent = Agent(ClaudeCodeModel(model_name, provider=provider))
|
|
62
63
|
|
|
63
64
|
result = asyncio_run(agent.run("Say hi."))
|
|
64
65
|
|
|
@@ -8,6 +8,7 @@ import httpx2
|
|
|
8
8
|
from pydantic_ai.exceptions import UserError
|
|
9
9
|
|
|
10
10
|
from pydantic_ai_claude_code.credentials import ClaudeCodeCredentials
|
|
11
|
+
from pydantic_ai_claude_code.model import ClaudeCodeModel
|
|
11
12
|
from pydantic_ai_claude_code.provider import ClaudeCodeProvider
|
|
12
13
|
from pydantic_ai_claude_code.storage import ClaudeCodeTokenStore
|
|
13
14
|
|
|
@@ -23,13 +24,21 @@ def test_credentials_property() -> None:
|
|
|
23
24
|
assert provider.credentials.token == "a"
|
|
24
25
|
|
|
25
26
|
|
|
26
|
-
def
|
|
27
|
+
def test_model_binds_explicit_provider() -> None:
|
|
27
28
|
provider = ClaudeCodeProvider(credentials=ClaudeCodeCredentials(access_token="a", refresh_token="b"))
|
|
28
|
-
model =
|
|
29
|
+
model = ClaudeCodeModel("claude-sonnet-4-5", provider=provider)
|
|
29
30
|
assert model.model_name == "claude-sonnet-4-5"
|
|
30
31
|
assert model._provider is provider # type: ignore[attr-defined]
|
|
31
32
|
|
|
32
33
|
|
|
34
|
+
def test_model_defaults_provider(tmp_path, monkeypatch) -> None:
|
|
35
|
+
monkeypatch.setenv("CLAUDE_CODE_CREDENTIALS", "file")
|
|
36
|
+
monkeypatch.setenv("CLAUDE_CODE_AUTH_FILE", str(tmp_path / "auth.json"))
|
|
37
|
+
ClaudeCodeTokenStore().save(ClaudeCodeCredentials(access_token="a", refresh_token="b"))
|
|
38
|
+
model = ClaudeCodeModel("claude-sonnet-4-5")
|
|
39
|
+
assert isinstance(model._provider, ClaudeCodeProvider) # type: ignore[attr-defined]
|
|
40
|
+
|
|
41
|
+
|
|
33
42
|
def test_missing_credentials_raises(tmp_path) -> None:
|
|
34
43
|
store = ClaudeCodeTokenStore(path=tmp_path / "missing.json")
|
|
35
44
|
with pytest.raises(UserError):
|
|
File without changes
|
|
File without changes
|
{pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/src/pydantic_ai_claude_code/__main__.py
RENAMED
|
File without changes
|
{pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/src/pydantic_ai_claude_code/auth.py
RENAMED
|
File without changes
|
{pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/src/pydantic_ai_claude_code/config.py
RENAMED
|
File without changes
|
{pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/src/pydantic_ai_claude_code/credentials.py
RENAMED
|
File without changes
|
{pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/src/pydantic_ai_claude_code/flow.py
RENAMED
|
File without changes
|
{pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/src/pydantic_ai_claude_code/py.typed
RENAMED
|
File without changes
|
{pydantic_claude_code-0.2.0 → pydantic_claude_code-0.3.0}/src/pydantic_ai_claude_code/storage.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|