codespar 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.
- codespar-0.1.0/.gitignore +12 -0
- codespar-0.1.0/PKG-INFO +187 -0
- codespar-0.1.0/README.md +156 -0
- codespar-0.1.0/pyproject.toml +65 -0
- codespar-0.1.0/src/codespar/__init__.py +107 -0
- codespar-0.1.0/src/codespar/_async_client.py +178 -0
- codespar-0.1.0/src/codespar/_async_session.py +420 -0
- codespar-0.1.0/src/codespar/_http.py +140 -0
- codespar-0.1.0/src/codespar/_presets.py +41 -0
- codespar-0.1.0/src/codespar/_sync_client.py +232 -0
- codespar-0.1.0/src/codespar/errors.py +49 -0
- codespar-0.1.0/src/codespar/types.py +209 -0
- codespar-0.1.0/tests/__init__.py +0 -0
- codespar-0.1.0/tests/test_config.py +62 -0
- codespar-0.1.0/tests/test_session.py +244 -0
- codespar-0.1.0/tests/test_streaming.py +157 -0
codespar-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: codespar
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for CodeSpar — commerce infrastructure for AI agents in Latin America.
|
|
5
|
+
Project-URL: Homepage, https://codespar.dev
|
|
6
|
+
Project-URL: Documentation, https://docs.codespar.dev
|
|
7
|
+
Project-URL: Repository, https://github.com/codespar/codespar
|
|
8
|
+
Project-URL: Issues, https://github.com/codespar/codespar/issues
|
|
9
|
+
Author-email: CodeSpar <hello@codespar.dev>
|
|
10
|
+
License: MIT
|
|
11
|
+
Keywords: agents,ai,commerce,latam,mcp,nfe,pix,stripe
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: httpx>=0.27.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-httpx>=0.30; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# codespar — Python SDK
|
|
33
|
+
|
|
34
|
+
Commerce infrastructure for AI agents in Latin America. Pix, NF-e,
|
|
35
|
+
WhatsApp, shipping, banking — one API, no provider-key boilerplate.
|
|
36
|
+
|
|
37
|
+
[](https://pypi.org/project/codespar/)
|
|
38
|
+
[](https://pypi.org/project/codespar/)
|
|
39
|
+
[](https://github.com/codespar/codespar/blob/main/LICENSE)
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install codespar
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Python 3.10+ required.
|
|
48
|
+
|
|
49
|
+
## Quick start
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from codespar import CodeSpar
|
|
53
|
+
|
|
54
|
+
cs = CodeSpar(api_key="csk_live_...")
|
|
55
|
+
|
|
56
|
+
session = cs.create(
|
|
57
|
+
"user_123",
|
|
58
|
+
preset="brazilian", # zoop, nuvem-fiscal, melhor-envio, z-api, omie
|
|
59
|
+
# project_id="prj_...", # optional — defaults to the org's default project
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
result = session.send(
|
|
63
|
+
"Charge R$500 via Pix to +5511999887766 and send the QR code by WhatsApp."
|
|
64
|
+
)
|
|
65
|
+
print(result.message)
|
|
66
|
+
for call in result.tool_calls:
|
|
67
|
+
print(f" → {call.tool_name} ({call.duration_ms}ms)")
|
|
68
|
+
|
|
69
|
+
session.close()
|
|
70
|
+
cs.close()
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Or as a context manager:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
with CodeSpar(api_key="csk_live_...") as cs:
|
|
77
|
+
session = cs.create("user_123", preset="brazilian")
|
|
78
|
+
print(session.send("Quero pagar R$125 via Pix").message)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Streaming
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
for event in session.send_stream("Process order #BR-7721"):
|
|
85
|
+
if event.type == "assistant_text":
|
|
86
|
+
print(event.content, end="", flush=True)
|
|
87
|
+
elif event.type == "tool_use":
|
|
88
|
+
print(f"\n→ calling {event.name}...")
|
|
89
|
+
elif event.type == "tool_result":
|
|
90
|
+
print(f" {event.tool_call.status} in {event.tool_call.duration_ms}ms")
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Async
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
import asyncio
|
|
97
|
+
from codespar import AsyncCodeSpar
|
|
98
|
+
|
|
99
|
+
async def main():
|
|
100
|
+
async with AsyncCodeSpar(api_key="csk_live_...") as cs:
|
|
101
|
+
session = await cs.create("user_123", preset="brazilian")
|
|
102
|
+
result = await session.send("charge R$500 via Pix")
|
|
103
|
+
print(result.message)
|
|
104
|
+
await session.close()
|
|
105
|
+
|
|
106
|
+
asyncio.run(main())
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Multi-environment (projects)
|
|
110
|
+
|
|
111
|
+
CodeSpar scopes every session to an environment (`prj_<id>`). Pass a
|
|
112
|
+
project id on the client for the whole lifetime, or per-session when
|
|
113
|
+
you want to target a different environment:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
# Pin every session this client spawns to the staging project
|
|
117
|
+
cs = CodeSpar(api_key="csk_live_...", project_id="prj_staging0123abcd")
|
|
118
|
+
|
|
119
|
+
# Override per session
|
|
120
|
+
session = cs.create("user_123", preset="brazilian", project_id="prj_prod0123abcd")
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
When you omit `project_id`, CodeSpar routes to the org's **default
|
|
124
|
+
project** — always defined, self-healed on first read.
|
|
125
|
+
|
|
126
|
+
## Raw HTTP proxy
|
|
127
|
+
|
|
128
|
+
Skip the agent loop and hit a provider API directly through CodeSpar's
|
|
129
|
+
credential vault:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from codespar import ProxyRequest
|
|
133
|
+
|
|
134
|
+
response = session.proxy_execute(ProxyRequest(
|
|
135
|
+
server="stripe-acp",
|
|
136
|
+
endpoint="/v1/charges",
|
|
137
|
+
method="POST",
|
|
138
|
+
body={"amount": 2000, "currency": "brl"},
|
|
139
|
+
))
|
|
140
|
+
print(response.status, response.data)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
No API key leaves your machine — CodeSpar injects it server-side.
|
|
144
|
+
|
|
145
|
+
## Connect Links (OAuth)
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
from codespar import AuthConfig
|
|
149
|
+
|
|
150
|
+
link = session.authorize(
|
|
151
|
+
"stripe-acp",
|
|
152
|
+
AuthConfig(redirect_uri="https://your.app/connected"),
|
|
153
|
+
)
|
|
154
|
+
print(f"Open this URL to connect Stripe: {link.authorize_url}")
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
After the user completes the OAuth flow, CodeSpar stores the tokens in
|
|
158
|
+
the per-project vault and forwards them back to `redirect_uri` with
|
|
159
|
+
`?status=connected&connection_id=<id>` appended.
|
|
160
|
+
|
|
161
|
+
## Errors
|
|
162
|
+
|
|
163
|
+
Every failure is wrapped:
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
from codespar import ApiError, ConfigError, StreamError
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
session = cs.create("user_123", preset="brazilian")
|
|
170
|
+
except ConfigError as exc:
|
|
171
|
+
print(f"Bad config: {exc}")
|
|
172
|
+
except ApiError as exc:
|
|
173
|
+
print(f"Backend said {exc.status}: {exc.code}")
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Design parity with the JS SDK
|
|
177
|
+
|
|
178
|
+
This package mirrors [`@codespar/sdk`](https://www.npmjs.com/package/@codespar/sdk)
|
|
179
|
+
method-for-method. Same backend, same payloads, same preset names — pick
|
|
180
|
+
the language that fits your stack without giving anything up.
|
|
181
|
+
|
|
182
|
+
## Links
|
|
183
|
+
|
|
184
|
+
- [Documentation](https://docs.codespar.dev)
|
|
185
|
+
- [Dashboard](https://dashboard.codespar.dev)
|
|
186
|
+
- [JS SDK (npm)](https://www.npmjs.com/package/@codespar/sdk)
|
|
187
|
+
- [Report a bug](https://github.com/codespar/codespar/issues)
|
codespar-0.1.0/README.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# codespar — Python SDK
|
|
2
|
+
|
|
3
|
+
Commerce infrastructure for AI agents in Latin America. Pix, NF-e,
|
|
4
|
+
WhatsApp, shipping, banking — one API, no provider-key boilerplate.
|
|
5
|
+
|
|
6
|
+
[](https://pypi.org/project/codespar/)
|
|
7
|
+
[](https://pypi.org/project/codespar/)
|
|
8
|
+
[](https://github.com/codespar/codespar/blob/main/LICENSE)
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install codespar
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Python 3.10+ required.
|
|
17
|
+
|
|
18
|
+
## Quick start
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from codespar import CodeSpar
|
|
22
|
+
|
|
23
|
+
cs = CodeSpar(api_key="csk_live_...")
|
|
24
|
+
|
|
25
|
+
session = cs.create(
|
|
26
|
+
"user_123",
|
|
27
|
+
preset="brazilian", # zoop, nuvem-fiscal, melhor-envio, z-api, omie
|
|
28
|
+
# project_id="prj_...", # optional — defaults to the org's default project
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
result = session.send(
|
|
32
|
+
"Charge R$500 via Pix to +5511999887766 and send the QR code by WhatsApp."
|
|
33
|
+
)
|
|
34
|
+
print(result.message)
|
|
35
|
+
for call in result.tool_calls:
|
|
36
|
+
print(f" → {call.tool_name} ({call.duration_ms}ms)")
|
|
37
|
+
|
|
38
|
+
session.close()
|
|
39
|
+
cs.close()
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Or as a context manager:
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
with CodeSpar(api_key="csk_live_...") as cs:
|
|
46
|
+
session = cs.create("user_123", preset="brazilian")
|
|
47
|
+
print(session.send("Quero pagar R$125 via Pix").message)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Streaming
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
for event in session.send_stream("Process order #BR-7721"):
|
|
54
|
+
if event.type == "assistant_text":
|
|
55
|
+
print(event.content, end="", flush=True)
|
|
56
|
+
elif event.type == "tool_use":
|
|
57
|
+
print(f"\n→ calling {event.name}...")
|
|
58
|
+
elif event.type == "tool_result":
|
|
59
|
+
print(f" {event.tool_call.status} in {event.tool_call.duration_ms}ms")
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Async
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
import asyncio
|
|
66
|
+
from codespar import AsyncCodeSpar
|
|
67
|
+
|
|
68
|
+
async def main():
|
|
69
|
+
async with AsyncCodeSpar(api_key="csk_live_...") as cs:
|
|
70
|
+
session = await cs.create("user_123", preset="brazilian")
|
|
71
|
+
result = await session.send("charge R$500 via Pix")
|
|
72
|
+
print(result.message)
|
|
73
|
+
await session.close()
|
|
74
|
+
|
|
75
|
+
asyncio.run(main())
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Multi-environment (projects)
|
|
79
|
+
|
|
80
|
+
CodeSpar scopes every session to an environment (`prj_<id>`). Pass a
|
|
81
|
+
project id on the client for the whole lifetime, or per-session when
|
|
82
|
+
you want to target a different environment:
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
# Pin every session this client spawns to the staging project
|
|
86
|
+
cs = CodeSpar(api_key="csk_live_...", project_id="prj_staging0123abcd")
|
|
87
|
+
|
|
88
|
+
# Override per session
|
|
89
|
+
session = cs.create("user_123", preset="brazilian", project_id="prj_prod0123abcd")
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
When you omit `project_id`, CodeSpar routes to the org's **default
|
|
93
|
+
project** — always defined, self-healed on first read.
|
|
94
|
+
|
|
95
|
+
## Raw HTTP proxy
|
|
96
|
+
|
|
97
|
+
Skip the agent loop and hit a provider API directly through CodeSpar's
|
|
98
|
+
credential vault:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from codespar import ProxyRequest
|
|
102
|
+
|
|
103
|
+
response = session.proxy_execute(ProxyRequest(
|
|
104
|
+
server="stripe-acp",
|
|
105
|
+
endpoint="/v1/charges",
|
|
106
|
+
method="POST",
|
|
107
|
+
body={"amount": 2000, "currency": "brl"},
|
|
108
|
+
))
|
|
109
|
+
print(response.status, response.data)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
No API key leaves your machine — CodeSpar injects it server-side.
|
|
113
|
+
|
|
114
|
+
## Connect Links (OAuth)
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from codespar import AuthConfig
|
|
118
|
+
|
|
119
|
+
link = session.authorize(
|
|
120
|
+
"stripe-acp",
|
|
121
|
+
AuthConfig(redirect_uri="https://your.app/connected"),
|
|
122
|
+
)
|
|
123
|
+
print(f"Open this URL to connect Stripe: {link.authorize_url}")
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
After the user completes the OAuth flow, CodeSpar stores the tokens in
|
|
127
|
+
the per-project vault and forwards them back to `redirect_uri` with
|
|
128
|
+
`?status=connected&connection_id=<id>` appended.
|
|
129
|
+
|
|
130
|
+
## Errors
|
|
131
|
+
|
|
132
|
+
Every failure is wrapped:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from codespar import ApiError, ConfigError, StreamError
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
session = cs.create("user_123", preset="brazilian")
|
|
139
|
+
except ConfigError as exc:
|
|
140
|
+
print(f"Bad config: {exc}")
|
|
141
|
+
except ApiError as exc:
|
|
142
|
+
print(f"Backend said {exc.status}: {exc.code}")
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Design parity with the JS SDK
|
|
146
|
+
|
|
147
|
+
This package mirrors [`@codespar/sdk`](https://www.npmjs.com/package/@codespar/sdk)
|
|
148
|
+
method-for-method. Same backend, same payloads, same preset names — pick
|
|
149
|
+
the language that fits your stack without giving anything up.
|
|
150
|
+
|
|
151
|
+
## Links
|
|
152
|
+
|
|
153
|
+
- [Documentation](https://docs.codespar.dev)
|
|
154
|
+
- [Dashboard](https://dashboard.codespar.dev)
|
|
155
|
+
- [JS SDK (npm)](https://www.npmjs.com/package/@codespar/sdk)
|
|
156
|
+
- [Report a bug](https://github.com/codespar/codespar/issues)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "codespar"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python SDK for CodeSpar — commerce infrastructure for AI agents in Latin America."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "CodeSpar", email = "hello@codespar.dev" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["ai", "agents", "commerce", "latam", "mcp", "stripe", "pix", "nfe"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
27
|
+
]
|
|
28
|
+
dependencies = [
|
|
29
|
+
"httpx>=0.27.0",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = [
|
|
34
|
+
"pytest>=8.0",
|
|
35
|
+
"pytest-asyncio>=0.23",
|
|
36
|
+
"pytest-httpx>=0.30",
|
|
37
|
+
"ruff>=0.6",
|
|
38
|
+
"mypy>=1.11",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[project.urls]
|
|
42
|
+
Homepage = "https://codespar.dev"
|
|
43
|
+
Documentation = "https://docs.codespar.dev"
|
|
44
|
+
Repository = "https://github.com/codespar/codespar"
|
|
45
|
+
Issues = "https://github.com/codespar/codespar/issues"
|
|
46
|
+
|
|
47
|
+
[tool.hatch.build.targets.wheel]
|
|
48
|
+
packages = ["src/codespar"]
|
|
49
|
+
|
|
50
|
+
[tool.pytest.ini_options]
|
|
51
|
+
testpaths = ["tests"]
|
|
52
|
+
asyncio_mode = "auto"
|
|
53
|
+
|
|
54
|
+
[tool.ruff]
|
|
55
|
+
line-length = 100
|
|
56
|
+
target-version = "py310"
|
|
57
|
+
|
|
58
|
+
[tool.ruff.lint]
|
|
59
|
+
select = ["E", "F", "W", "I", "UP", "B", "SIM", "RUF"]
|
|
60
|
+
|
|
61
|
+
[tool.mypy]
|
|
62
|
+
python_version = "3.10"
|
|
63
|
+
strict = true
|
|
64
|
+
warn_return_any = true
|
|
65
|
+
warn_unused_ignores = true
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CodeSpar Python SDK — commerce infrastructure for AI agents in Latin America.
|
|
3
|
+
|
|
4
|
+
Two import surfaces:
|
|
5
|
+
|
|
6
|
+
* ``CodeSpar`` — sync client. Use from scripts, Jupyter, sync web
|
|
7
|
+
frameworks (Flask, Django views).
|
|
8
|
+
* ``AsyncCodeSpar`` — async client. Use from FastAPI, LangChain,
|
|
9
|
+
anything already running on asyncio.
|
|
10
|
+
|
|
11
|
+
Both wrap the same backend (``api.codespar.dev``) and expose the same
|
|
12
|
+
session API, so you can start with sync and upgrade to async without
|
|
13
|
+
changing the surrounding code.
|
|
14
|
+
|
|
15
|
+
Quick start::
|
|
16
|
+
|
|
17
|
+
from codespar import CodeSpar
|
|
18
|
+
|
|
19
|
+
cs = CodeSpar(api_key="csk_live_...")
|
|
20
|
+
session = cs.create("user_123", preset="brazilian")
|
|
21
|
+
result = session.send("Charge R$500 via Pix to +5511999887766")
|
|
22
|
+
print(result.message)
|
|
23
|
+
session.close()
|
|
24
|
+
cs.close()
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
from ._async_client import AsyncCodeSpar
|
|
30
|
+
from ._async_session import AsyncSession
|
|
31
|
+
from ._sync_client import CodeSpar, Session
|
|
32
|
+
from .errors import (
|
|
33
|
+
ApiError,
|
|
34
|
+
CodeSparError,
|
|
35
|
+
ConfigError,
|
|
36
|
+
NotConnectedError,
|
|
37
|
+
StreamError,
|
|
38
|
+
)
|
|
39
|
+
from .types import (
|
|
40
|
+
AssistantTextEvent,
|
|
41
|
+
AuthConfig,
|
|
42
|
+
AuthResult,
|
|
43
|
+
DoneEvent,
|
|
44
|
+
ErrorEvent,
|
|
45
|
+
HttpMethod,
|
|
46
|
+
ManageConnections,
|
|
47
|
+
Preset,
|
|
48
|
+
ProxyRequest,
|
|
49
|
+
ProxyResult,
|
|
50
|
+
SendResult,
|
|
51
|
+
ServerConnection,
|
|
52
|
+
SessionConfig,
|
|
53
|
+
SessionInfo,
|
|
54
|
+
SessionStatus,
|
|
55
|
+
StreamEvent,
|
|
56
|
+
Tool,
|
|
57
|
+
ToolCallRecord,
|
|
58
|
+
ToolResult,
|
|
59
|
+
ToolResultEvent,
|
|
60
|
+
ToolUseEvent,
|
|
61
|
+
UserMessageEvent,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
__version__ = "0.1.0"
|
|
65
|
+
|
|
66
|
+
__all__ = [
|
|
67
|
+
"ApiError",
|
|
68
|
+
"AssistantTextEvent",
|
|
69
|
+
"AsyncCodeSpar",
|
|
70
|
+
"AsyncSession",
|
|
71
|
+
# Connect Links
|
|
72
|
+
"AuthConfig",
|
|
73
|
+
"AuthResult",
|
|
74
|
+
# Clients
|
|
75
|
+
"CodeSpar",
|
|
76
|
+
# Errors
|
|
77
|
+
"CodeSparError",
|
|
78
|
+
"ConfigError",
|
|
79
|
+
"DoneEvent",
|
|
80
|
+
"ErrorEvent",
|
|
81
|
+
"HttpMethod",
|
|
82
|
+
"ManageConnections",
|
|
83
|
+
"NotConnectedError",
|
|
84
|
+
"Preset",
|
|
85
|
+
# Proxy
|
|
86
|
+
"ProxyRequest",
|
|
87
|
+
"ProxyResult",
|
|
88
|
+
"SendResult",
|
|
89
|
+
"ServerConnection",
|
|
90
|
+
"Session",
|
|
91
|
+
# Configuration
|
|
92
|
+
"SessionConfig",
|
|
93
|
+
# Session output
|
|
94
|
+
"SessionInfo",
|
|
95
|
+
"SessionStatus",
|
|
96
|
+
"StreamError",
|
|
97
|
+
# Streaming events
|
|
98
|
+
"StreamEvent",
|
|
99
|
+
"Tool",
|
|
100
|
+
"ToolCallRecord",
|
|
101
|
+
"ToolResult",
|
|
102
|
+
"ToolResultEvent",
|
|
103
|
+
"ToolUseEvent",
|
|
104
|
+
"UserMessageEvent",
|
|
105
|
+
# Version
|
|
106
|
+
"__version__",
|
|
107
|
+
]
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""
|
|
2
|
+
``AsyncCodeSpar`` — the canonical client class.
|
|
3
|
+
|
|
4
|
+
Holds an httpx.AsyncClient, exposes ``create(user_id, ...)`` to start a
|
|
5
|
+
session, and mirrors the TS ``CodeSpar`` constructor 1:1. The sync
|
|
6
|
+
``CodeSpar`` in ``_sync_client.py`` wraps every call through
|
|
7
|
+
``asyncio.run`` so the lightweight use-case works without the caller
|
|
8
|
+
having to write ``async def``.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from types import TracebackType
|
|
14
|
+
|
|
15
|
+
import httpx
|
|
16
|
+
|
|
17
|
+
from ._async_session import (
|
|
18
|
+
AsyncSession,
|
|
19
|
+
build_session_info,
|
|
20
|
+
wait_for_connections,
|
|
21
|
+
)
|
|
22
|
+
from ._http import DEFAULT_BASE_URL, request_json
|
|
23
|
+
from ._presets import preset_to_servers
|
|
24
|
+
from .errors import ApiError, ConfigError
|
|
25
|
+
from .types import SessionConfig
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class AsyncCodeSpar:
|
|
29
|
+
"""
|
|
30
|
+
Async CodeSpar client. Pass an API key, create sessions, run them,
|
|
31
|
+
close them. One client can spawn many sessions in parallel.
|
|
32
|
+
|
|
33
|
+
Example::
|
|
34
|
+
|
|
35
|
+
async with AsyncCodeSpar(api_key="csk_live_...") as cs:
|
|
36
|
+
session = await cs.create("user_123", preset="brazilian")
|
|
37
|
+
result = await session.send("charge R$500 via Pix")
|
|
38
|
+
print(result.message)
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
*,
|
|
44
|
+
api_key: str,
|
|
45
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
46
|
+
project_id: str | None = None,
|
|
47
|
+
timeout: float = 60.0,
|
|
48
|
+
client: httpx.AsyncClient | None = None,
|
|
49
|
+
) -> None:
|
|
50
|
+
if not api_key or not api_key.startswith("csk_"):
|
|
51
|
+
raise ConfigError(
|
|
52
|
+
"api_key is required and must start with 'csk_'. "
|
|
53
|
+
"Get one from https://dashboard.codespar.dev."
|
|
54
|
+
)
|
|
55
|
+
self._api_key = api_key
|
|
56
|
+
self._base_url = base_url.rstrip("/")
|
|
57
|
+
self._project_id = project_id
|
|
58
|
+
# Share one transport across every session spawned by this
|
|
59
|
+
# client. Closing the client closes every in-flight request.
|
|
60
|
+
self._owns_client = client is None
|
|
61
|
+
self._client = client or httpx.AsyncClient(
|
|
62
|
+
base_url=self._base_url,
|
|
63
|
+
timeout=timeout,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def base_url(self) -> str:
|
|
68
|
+
return self._base_url
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def project_id(self) -> str | None:
|
|
72
|
+
return self._project_id
|
|
73
|
+
|
|
74
|
+
async def create(
|
|
75
|
+
self,
|
|
76
|
+
user_id: str,
|
|
77
|
+
config: SessionConfig | None = None,
|
|
78
|
+
/,
|
|
79
|
+
**kwargs: object,
|
|
80
|
+
) -> AsyncSession:
|
|
81
|
+
"""
|
|
82
|
+
Start a session scoped to ``user_id``.
|
|
83
|
+
|
|
84
|
+
``config`` can be passed as a ``SessionConfig`` dataclass or as
|
|
85
|
+
keyword arguments — both shapes work::
|
|
86
|
+
|
|
87
|
+
await cs.create("user_123", preset="brazilian")
|
|
88
|
+
await cs.create("user_123", SessionConfig(preset="brazilian"))
|
|
89
|
+
"""
|
|
90
|
+
resolved = self._resolve_config(config, kwargs)
|
|
91
|
+
servers = resolved.servers or preset_to_servers(resolved.preset)
|
|
92
|
+
project_id = resolved.project_id or self._project_id
|
|
93
|
+
|
|
94
|
+
body: dict[str, object] = {"servers": servers, "user_id": user_id}
|
|
95
|
+
if resolved.metadata:
|
|
96
|
+
body["metadata"] = resolved.metadata
|
|
97
|
+
|
|
98
|
+
data = await request_json(
|
|
99
|
+
self._client,
|
|
100
|
+
"POST",
|
|
101
|
+
"/v1/sessions",
|
|
102
|
+
api_key=self._api_key,
|
|
103
|
+
project_id=project_id,
|
|
104
|
+
body=body,
|
|
105
|
+
)
|
|
106
|
+
if not isinstance(data, dict):
|
|
107
|
+
raise ApiError("create: malformed response", status=0, body=data)
|
|
108
|
+
|
|
109
|
+
info = build_session_info(
|
|
110
|
+
data,
|
|
111
|
+
base_url=self._base_url,
|
|
112
|
+
api_key=self._api_key,
|
|
113
|
+
project_id=project_id,
|
|
114
|
+
)
|
|
115
|
+
session = AsyncSession(
|
|
116
|
+
info=info,
|
|
117
|
+
client=self._client,
|
|
118
|
+
api_key=self._api_key,
|
|
119
|
+
project_id=project_id,
|
|
120
|
+
base_url=self._base_url,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
if resolved.manage_connections and resolved.manage_connections.wait_for_connections:
|
|
124
|
+
await wait_for_connections(
|
|
125
|
+
session,
|
|
126
|
+
timeout_ms=resolved.manage_connections.timeout,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
return session
|
|
130
|
+
|
|
131
|
+
# ── lifecycle ───────────────────────────────────────────────────────
|
|
132
|
+
|
|
133
|
+
async def aclose(self) -> None:
|
|
134
|
+
"""Close the underlying httpx transport."""
|
|
135
|
+
if self._owns_client:
|
|
136
|
+
await self._client.aclose()
|
|
137
|
+
|
|
138
|
+
async def __aenter__(self) -> AsyncCodeSpar:
|
|
139
|
+
return self
|
|
140
|
+
|
|
141
|
+
async def __aexit__(
|
|
142
|
+
self,
|
|
143
|
+
exc_type: type[BaseException] | None,
|
|
144
|
+
exc: BaseException | None,
|
|
145
|
+
tb: TracebackType | None,
|
|
146
|
+
) -> None:
|
|
147
|
+
await self.aclose()
|
|
148
|
+
|
|
149
|
+
# ── internals ───────────────────────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
def _resolve_config(
|
|
152
|
+
self,
|
|
153
|
+
config: SessionConfig | None,
|
|
154
|
+
kwargs: dict[str, object],
|
|
155
|
+
) -> SessionConfig:
|
|
156
|
+
"""Accept either a SessionConfig dataclass or kwargs, never both."""
|
|
157
|
+
if config is not None and kwargs:
|
|
158
|
+
raise ConfigError(
|
|
159
|
+
"Pass SessionConfig or keyword arguments, not both."
|
|
160
|
+
)
|
|
161
|
+
if config is not None:
|
|
162
|
+
return config
|
|
163
|
+
if not kwargs:
|
|
164
|
+
return SessionConfig()
|
|
165
|
+
|
|
166
|
+
allowed = {
|
|
167
|
+
"servers",
|
|
168
|
+
"preset",
|
|
169
|
+
"manage_connections",
|
|
170
|
+
"metadata",
|
|
171
|
+
"project_id",
|
|
172
|
+
}
|
|
173
|
+
unknown = set(kwargs) - allowed
|
|
174
|
+
if unknown:
|
|
175
|
+
raise ConfigError(
|
|
176
|
+
f"create(): unknown keyword argument(s): {', '.join(sorted(unknown))}"
|
|
177
|
+
)
|
|
178
|
+
return SessionConfig(**kwargs) # type: ignore[arg-type]
|