onstoa 0.2.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.
- onstoa-0.2.0/.gitignore +1 -0
- onstoa-0.2.0/CLAUDE.md +35 -0
- onstoa-0.2.0/PKG-INFO +256 -0
- onstoa-0.2.0/README.md +210 -0
- onstoa-0.2.0/justfile +17 -0
- onstoa-0.2.0/pyproject.toml +90 -0
- onstoa-0.2.0/stoa/__init__.py +82 -0
- onstoa-0.2.0/stoa/client.py +372 -0
- onstoa-0.2.0/stoa/connect.py +171 -0
- onstoa-0.2.0/stoa/embedded.py +97 -0
- onstoa-0.2.0/stoa/events.py +452 -0
- onstoa-0.2.0/stoa/exceptions.py +161 -0
- onstoa-0.2.0/stoa/http.py +375 -0
- onstoa-0.2.0/stoa/install.py +136 -0
- onstoa-0.2.0/stoa/providers/__init__.py +42 -0
- onstoa-0.2.0/stoa/providers/anthropic.py +65 -0
- onstoa-0.2.0/stoa/providers/base.py +75 -0
- onstoa-0.2.0/stoa/providers/deepgram.py +90 -0
- onstoa-0.2.0/stoa/providers/elevenlabs.py +93 -0
- onstoa-0.2.0/stoa/providers/openai.py +182 -0
- onstoa-0.2.0/stoa/providers/openrouter.py +67 -0
- onstoa-0.2.0/stoa/proxy.py +101 -0
- onstoa-0.2.0/stoa/py.typed +0 -0
- onstoa-0.2.0/tests/__init__.py +0 -0
- onstoa-0.2.0/tests/conftest.py +12 -0
- onstoa-0.2.0/tests/integration/conftest.py +175 -0
- onstoa-0.2.0/tests/integration/test_full_flow.py +352 -0
- onstoa-0.2.0/tests/test_client.py +329 -0
- onstoa-0.2.0/tests/test_connect.py +99 -0
- onstoa-0.2.0/tests/test_events.py +492 -0
- onstoa-0.2.0/tests/test_exceptions.py +220 -0
- onstoa-0.2.0/tests/test_http.py +700 -0
- onstoa-0.2.0/tests/test_install.py +269 -0
- onstoa-0.2.0/tests/test_install_client.py +105 -0
- onstoa-0.2.0/tests/test_providers.py +576 -0
- onstoa-0.2.0/tests/test_proxy.py +271 -0
- onstoa-0.2.0/tests/test_types.py +283 -0
- onstoa-0.2.0/uv.lock +1331 -0
onstoa-0.2.0/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__pycache__/
|
onstoa-0.2.0/CLAUDE.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Stoa SDK
|
|
2
|
+
|
|
3
|
+
Usage-based billing SDK for AI applications.
|
|
4
|
+
|
|
5
|
+
## Build System
|
|
6
|
+
|
|
7
|
+
Use `just` for all build, test, and lint commands. Run `just` to see available recipes.
|
|
8
|
+
|
|
9
|
+
### Key Recipes
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
just lint # Run all linting (ruff + mypy strict)
|
|
13
|
+
just test # Run tests
|
|
14
|
+
just check # Run all checks (lint + test)
|
|
15
|
+
just fmt # Format code with ruff
|
|
16
|
+
just ruff-fix # Auto-fix ruff issues
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Other Recipes
|
|
20
|
+
|
|
21
|
+
- `just install` - Install all dependencies with uv
|
|
22
|
+
- `just mypy` - Run mypy type checker only
|
|
23
|
+
- `just ruff` - Run ruff linter only
|
|
24
|
+
- `just test-cov` - Run tests with coverage
|
|
25
|
+
- `just build` - Build package
|
|
26
|
+
- `just clean` - Clean build artifacts
|
|
27
|
+
|
|
28
|
+
## Conventions
|
|
29
|
+
|
|
30
|
+
- Python 3.10+ required
|
|
31
|
+
- Use `orjson` instead of `json` for serialization
|
|
32
|
+
- Use function-based tests (no class-based pytest tests)
|
|
33
|
+
- mypy strict mode is enforced
|
|
34
|
+
- Line length: 100 characters
|
|
35
|
+
- asyncio_mode is auto for pytest-asyncio
|
onstoa-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: onstoa
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Usage-based billing SDK for AI applications
|
|
5
|
+
Project-URL: Homepage, https://onstoa.com
|
|
6
|
+
Project-URL: Repository, https://github.com/stoa-org/stoa
|
|
7
|
+
Project-URL: Documentation, https://docs.onstoa.com
|
|
8
|
+
Author-email: Stoa <hello@stoa.dev>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Requires-Dist: httpx>=0.25.0
|
|
19
|
+
Requires-Dist: orjson>=3.9.0
|
|
20
|
+
Requires-Dist: pydantic>=2.0.0
|
|
21
|
+
Requires-Dist: pyjwt[crypto]>=2.0.0
|
|
22
|
+
Requires-Dist: tenacity>=8.0.0
|
|
23
|
+
Requires-Dist: uuid-utils>=0.6.0
|
|
24
|
+
Provides-Extra: all
|
|
25
|
+
Requires-Dist: anthropic>=0.18.0; extra == 'all'
|
|
26
|
+
Requires-Dist: elevenlabs>=1.0.0; extra == 'all'
|
|
27
|
+
Requires-Dist: openai>=1.0.0; extra == 'all'
|
|
28
|
+
Provides-Extra: anthropic
|
|
29
|
+
Requires-Dist: anthropic>=0.18.0; extra == 'anthropic'
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: anthropic>=0.18.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: elevenlabs>=1.0.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: openai>=1.0.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: pre-commit>=3.0.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: pytest-httpx>=0.30.0; extra == 'dev'
|
|
37
|
+
Requires-Dist: pytest-xdist>=3.5.0; extra == 'dev'
|
|
38
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
39
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
40
|
+
Requires-Dist: ty>=0.0.7; extra == 'dev'
|
|
41
|
+
Provides-Extra: elevenlabs
|
|
42
|
+
Requires-Dist: elevenlabs>=1.0.0; extra == 'elevenlabs'
|
|
43
|
+
Provides-Extra: openai
|
|
44
|
+
Requires-Dist: openai>=1.0.0; extra == 'openai'
|
|
45
|
+
Description-Content-Type: text/markdown
|
|
46
|
+
|
|
47
|
+
# Stoa SDK
|
|
48
|
+
|
|
49
|
+
Usage-based billing SDK for AI applications. Wrap your AI provider calls to automatically meter usage and bill your users.
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install stoa
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Quick Start
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from stoa import Stoa, InsufficientBalanceError
|
|
61
|
+
|
|
62
|
+
# Reads STOA_API_KEY and STOA_APP_ID from environment
|
|
63
|
+
stoa = Stoa()
|
|
64
|
+
|
|
65
|
+
# 1. Register or reuse the member from your backend
|
|
66
|
+
stoa.register_member(
|
|
67
|
+
connect_secret=STOA_CONNECT_SECRET,
|
|
68
|
+
user_id="user_123",
|
|
69
|
+
email="ada@example.com",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# 2. Use your app's own user ID for billing calls
|
|
73
|
+
client = stoa.openai(user_id="user_123")
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
response = client.chat.completions.create(
|
|
77
|
+
model="gpt-4",
|
|
78
|
+
messages=[{"role": "user", "content": "Hello!"}]
|
|
79
|
+
)
|
|
80
|
+
except InsufficientBalanceError as e:
|
|
81
|
+
print(f"Please top up: {e.topup_url}")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Typical flow:
|
|
85
|
+
1. call `stoa.register_member(...)` from your backend
|
|
86
|
+
2. keep using your own app `user_id` in SDK billing calls
|
|
87
|
+
3. send the user to `e.topup_url` if they need funds
|
|
88
|
+
|
|
89
|
+
## Onboard Users
|
|
90
|
+
|
|
91
|
+
Stoa can stay invisible to the customer while still owning the canonical user
|
|
92
|
+
and membership records.
|
|
93
|
+
|
|
94
|
+
### User onboards via your app
|
|
95
|
+
|
|
96
|
+
From your backend, call the SDK once with your connect secret and the user's
|
|
97
|
+
verified app identity:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from stoa import Stoa
|
|
101
|
+
|
|
102
|
+
stoa = Stoa(api_key=STOA_API_KEY, base_url=STOA_BASE_URL, app_id=STOA_APP_ID)
|
|
103
|
+
|
|
104
|
+
result = stoa.register_member(
|
|
105
|
+
connect_secret=STOA_CONNECT_SECRET,
|
|
106
|
+
user_id=user.id,
|
|
107
|
+
email=user.email,
|
|
108
|
+
name=user.name,
|
|
109
|
+
avatar_url=user.avatar_url,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Optional: store the membership ID for debugging or richer account UX.
|
|
113
|
+
persist_membership_binding(
|
|
114
|
+
user_id=user.id,
|
|
115
|
+
membership_id=result.membership_id,
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Stoa registers the user for billing in your app.
|
|
120
|
+
|
|
121
|
+
Your app can continue billing that user with its own `user_id`.
|
|
122
|
+
|
|
123
|
+
#### Optional: open a hosted top-up page
|
|
124
|
+
|
|
125
|
+
If you want an explicit "Add funds" action in your product:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
topup_url = stoa.get_topup_url(user_id=user.id)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
This returns a hosted Stoa payment page for that app user.
|
|
132
|
+
|
|
133
|
+
### User installs via Stoa marketplace
|
|
134
|
+
|
|
135
|
+
Marketplace installs use the install flow below.
|
|
136
|
+
|
|
137
|
+
#### Install callback
|
|
138
|
+
|
|
139
|
+
Add a callback endpoint that verifies the Stoa install token and activates the
|
|
140
|
+
membership for your app user. Both flows should converge on the same stored
|
|
141
|
+
membership binding in your app.
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from stoa import Stoa
|
|
145
|
+
|
|
146
|
+
stoa = Stoa()
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def stoa_callback(token: str):
|
|
150
|
+
claims = stoa.verify_install_callback(token=token)
|
|
151
|
+
|
|
152
|
+
user = find_or_create_user(
|
|
153
|
+
email=claims["email"],
|
|
154
|
+
name=claims["name"],
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
install = stoa.exchange_install_token(
|
|
158
|
+
token=token,
|
|
159
|
+
user_id=user.id,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
persist_membership_binding(
|
|
163
|
+
user_id=user.id,
|
|
164
|
+
membership_id=install.membership_id,
|
|
165
|
+
)
|
|
166
|
+
return create_session_and_redirect(user)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The SDK fetches Stoa's public key for you and keeps the server-side activation
|
|
170
|
+
step in place, so the secure flow does not become app boilerplate.
|
|
171
|
+
|
|
172
|
+
## Supported Providers
|
|
173
|
+
|
|
174
|
+
### OpenAI
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
client = stoa.openai(user_id="user_123")
|
|
178
|
+
|
|
179
|
+
# Chat completions
|
|
180
|
+
response = client.chat.completions.create(
|
|
181
|
+
model="gpt-4",
|
|
182
|
+
messages=[{"role": "user", "content": "Explain quantum computing"}]
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
# Embeddings
|
|
186
|
+
embeddings = client.embeddings.create(
|
|
187
|
+
model="text-embedding-3-small",
|
|
188
|
+
input="Hello world"
|
|
189
|
+
)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Anthropic
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
client = stoa.anthropic(user_id="user_123")
|
|
196
|
+
|
|
197
|
+
response = client.messages.create(
|
|
198
|
+
model="claude-3-5-sonnet-20241022",
|
|
199
|
+
max_tokens=1024,
|
|
200
|
+
messages=[{"role": "user", "content": "Write a haiku about Python"}]
|
|
201
|
+
)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### OpenRouter
|
|
205
|
+
|
|
206
|
+
Access 100+ models through a single API:
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
client = stoa.openrouter(user_id="user_123")
|
|
210
|
+
|
|
211
|
+
# Use any model available on OpenRouter
|
|
212
|
+
response = client.chat.completions.create(
|
|
213
|
+
model="meta-llama/llama-3.1-70b-instruct",
|
|
214
|
+
messages=[{"role": "user", "content": "Hello!"}]
|
|
215
|
+
)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### ElevenLabs
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
client = stoa.elevenlabs(user_id="user_123")
|
|
222
|
+
|
|
223
|
+
audio = client.text_to_speech.convert(
|
|
224
|
+
voice_id="JBFqnCBsd6RMkjVDRZzb",
|
|
225
|
+
text="Hello, welcome to our application!"
|
|
226
|
+
)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Environment Variables
|
|
230
|
+
|
|
231
|
+
| Variable | Required | Default | Description |
|
|
232
|
+
|----------|----------|---------|-------------|
|
|
233
|
+
| `STOA_API_KEY` | Yes | - | Your Stoa application API key |
|
|
234
|
+
| `STOA_APP_ID` | No | - | Your Stoa application ID (required for member registration unless passed explicitly) |
|
|
235
|
+
| `STOA_BASE_URL` | No | `https://api.onstoa.com` | Stoa API base URL (for self-hosted or testing) |
|
|
236
|
+
|
|
237
|
+
### Example .env file
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
# Required
|
|
241
|
+
STOA_API_KEY=stoa_app_xxx
|
|
242
|
+
STOA_APP_ID=app_xxx
|
|
243
|
+
|
|
244
|
+
# Optional - override API endpoint
|
|
245
|
+
STOA_BASE_URL=https://api.onstoa.com
|
|
246
|
+
|
|
247
|
+
# Provider keys (only needed for providers you use)
|
|
248
|
+
OPENAI_API_KEY=sk-...
|
|
249
|
+
ANTHROPIC_API_KEY=sk-ant-...
|
|
250
|
+
OPENROUTER_API_KEY=sk-or-...
|
|
251
|
+
ELEVENLABS_API_KEY=...
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Documentation
|
|
255
|
+
|
|
256
|
+
See [docs.onstoa.com](https://docs.onstoa.com) for full documentation.
|
onstoa-0.2.0/README.md
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Stoa SDK
|
|
2
|
+
|
|
3
|
+
Usage-based billing SDK for AI applications. Wrap your AI provider calls to automatically meter usage and bill your users.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install stoa
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from stoa import Stoa, InsufficientBalanceError
|
|
15
|
+
|
|
16
|
+
# Reads STOA_API_KEY and STOA_APP_ID from environment
|
|
17
|
+
stoa = Stoa()
|
|
18
|
+
|
|
19
|
+
# 1. Register or reuse the member from your backend
|
|
20
|
+
stoa.register_member(
|
|
21
|
+
connect_secret=STOA_CONNECT_SECRET,
|
|
22
|
+
user_id="user_123",
|
|
23
|
+
email="ada@example.com",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# 2. Use your app's own user ID for billing calls
|
|
27
|
+
client = stoa.openai(user_id="user_123")
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
response = client.chat.completions.create(
|
|
31
|
+
model="gpt-4",
|
|
32
|
+
messages=[{"role": "user", "content": "Hello!"}]
|
|
33
|
+
)
|
|
34
|
+
except InsufficientBalanceError as e:
|
|
35
|
+
print(f"Please top up: {e.topup_url}")
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Typical flow:
|
|
39
|
+
1. call `stoa.register_member(...)` from your backend
|
|
40
|
+
2. keep using your own app `user_id` in SDK billing calls
|
|
41
|
+
3. send the user to `e.topup_url` if they need funds
|
|
42
|
+
|
|
43
|
+
## Onboard Users
|
|
44
|
+
|
|
45
|
+
Stoa can stay invisible to the customer while still owning the canonical user
|
|
46
|
+
and membership records.
|
|
47
|
+
|
|
48
|
+
### User onboards via your app
|
|
49
|
+
|
|
50
|
+
From your backend, call the SDK once with your connect secret and the user's
|
|
51
|
+
verified app identity:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from stoa import Stoa
|
|
55
|
+
|
|
56
|
+
stoa = Stoa(api_key=STOA_API_KEY, base_url=STOA_BASE_URL, app_id=STOA_APP_ID)
|
|
57
|
+
|
|
58
|
+
result = stoa.register_member(
|
|
59
|
+
connect_secret=STOA_CONNECT_SECRET,
|
|
60
|
+
user_id=user.id,
|
|
61
|
+
email=user.email,
|
|
62
|
+
name=user.name,
|
|
63
|
+
avatar_url=user.avatar_url,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Optional: store the membership ID for debugging or richer account UX.
|
|
67
|
+
persist_membership_binding(
|
|
68
|
+
user_id=user.id,
|
|
69
|
+
membership_id=result.membership_id,
|
|
70
|
+
)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Stoa registers the user for billing in your app.
|
|
74
|
+
|
|
75
|
+
Your app can continue billing that user with its own `user_id`.
|
|
76
|
+
|
|
77
|
+
#### Optional: open a hosted top-up page
|
|
78
|
+
|
|
79
|
+
If you want an explicit "Add funds" action in your product:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
topup_url = stoa.get_topup_url(user_id=user.id)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
This returns a hosted Stoa payment page for that app user.
|
|
86
|
+
|
|
87
|
+
### User installs via Stoa marketplace
|
|
88
|
+
|
|
89
|
+
Marketplace installs use the install flow below.
|
|
90
|
+
|
|
91
|
+
#### Install callback
|
|
92
|
+
|
|
93
|
+
Add a callback endpoint that verifies the Stoa install token and activates the
|
|
94
|
+
membership for your app user. Both flows should converge on the same stored
|
|
95
|
+
membership binding in your app.
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from stoa import Stoa
|
|
99
|
+
|
|
100
|
+
stoa = Stoa()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def stoa_callback(token: str):
|
|
104
|
+
claims = stoa.verify_install_callback(token=token)
|
|
105
|
+
|
|
106
|
+
user = find_or_create_user(
|
|
107
|
+
email=claims["email"],
|
|
108
|
+
name=claims["name"],
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
install = stoa.exchange_install_token(
|
|
112
|
+
token=token,
|
|
113
|
+
user_id=user.id,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
persist_membership_binding(
|
|
117
|
+
user_id=user.id,
|
|
118
|
+
membership_id=install.membership_id,
|
|
119
|
+
)
|
|
120
|
+
return create_session_and_redirect(user)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
The SDK fetches Stoa's public key for you and keeps the server-side activation
|
|
124
|
+
step in place, so the secure flow does not become app boilerplate.
|
|
125
|
+
|
|
126
|
+
## Supported Providers
|
|
127
|
+
|
|
128
|
+
### OpenAI
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
client = stoa.openai(user_id="user_123")
|
|
132
|
+
|
|
133
|
+
# Chat completions
|
|
134
|
+
response = client.chat.completions.create(
|
|
135
|
+
model="gpt-4",
|
|
136
|
+
messages=[{"role": "user", "content": "Explain quantum computing"}]
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Embeddings
|
|
140
|
+
embeddings = client.embeddings.create(
|
|
141
|
+
model="text-embedding-3-small",
|
|
142
|
+
input="Hello world"
|
|
143
|
+
)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Anthropic
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
client = stoa.anthropic(user_id="user_123")
|
|
150
|
+
|
|
151
|
+
response = client.messages.create(
|
|
152
|
+
model="claude-3-5-sonnet-20241022",
|
|
153
|
+
max_tokens=1024,
|
|
154
|
+
messages=[{"role": "user", "content": "Write a haiku about Python"}]
|
|
155
|
+
)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### OpenRouter
|
|
159
|
+
|
|
160
|
+
Access 100+ models through a single API:
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
client = stoa.openrouter(user_id="user_123")
|
|
164
|
+
|
|
165
|
+
# Use any model available on OpenRouter
|
|
166
|
+
response = client.chat.completions.create(
|
|
167
|
+
model="meta-llama/llama-3.1-70b-instruct",
|
|
168
|
+
messages=[{"role": "user", "content": "Hello!"}]
|
|
169
|
+
)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### ElevenLabs
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
client = stoa.elevenlabs(user_id="user_123")
|
|
176
|
+
|
|
177
|
+
audio = client.text_to_speech.convert(
|
|
178
|
+
voice_id="JBFqnCBsd6RMkjVDRZzb",
|
|
179
|
+
text="Hello, welcome to our application!"
|
|
180
|
+
)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Environment Variables
|
|
184
|
+
|
|
185
|
+
| Variable | Required | Default | Description |
|
|
186
|
+
|----------|----------|---------|-------------|
|
|
187
|
+
| `STOA_API_KEY` | Yes | - | Your Stoa application API key |
|
|
188
|
+
| `STOA_APP_ID` | No | - | Your Stoa application ID (required for member registration unless passed explicitly) |
|
|
189
|
+
| `STOA_BASE_URL` | No | `https://api.onstoa.com` | Stoa API base URL (for self-hosted or testing) |
|
|
190
|
+
|
|
191
|
+
### Example .env file
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
# Required
|
|
195
|
+
STOA_API_KEY=stoa_app_xxx
|
|
196
|
+
STOA_APP_ID=app_xxx
|
|
197
|
+
|
|
198
|
+
# Optional - override API endpoint
|
|
199
|
+
STOA_BASE_URL=https://api.onstoa.com
|
|
200
|
+
|
|
201
|
+
# Provider keys (only needed for providers you use)
|
|
202
|
+
OPENAI_API_KEY=sk-...
|
|
203
|
+
ANTHROPIC_API_KEY=sk-ant-...
|
|
204
|
+
OPENROUTER_API_KEY=sk-or-...
|
|
205
|
+
ELEVENLABS_API_KEY=...
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Documentation
|
|
209
|
+
|
|
210
|
+
See [docs.onstoa.com](https://docs.onstoa.com) for full documentation.
|
onstoa-0.2.0/justfile
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Default recipe runs all checks
|
|
2
|
+
default: check
|
|
3
|
+
|
|
4
|
+
# Run all checks (lint, typecheck, test)
|
|
5
|
+
check: lint typecheck test
|
|
6
|
+
|
|
7
|
+
# Run ruff linter
|
|
8
|
+
lint:
|
|
9
|
+
uv run ruff check stoa
|
|
10
|
+
|
|
11
|
+
# Run ty type checker
|
|
12
|
+
typecheck:
|
|
13
|
+
uv run ty check stoa
|
|
14
|
+
|
|
15
|
+
# Run pytest with parallel execution
|
|
16
|
+
test:
|
|
17
|
+
uv run pytest -n auto
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "onstoa"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Usage-based billing SDK for AI applications"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Stoa", email = "hello@stoa.dev" },
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"httpx>=0.25.0",
|
|
26
|
+
"orjson>=3.9.0",
|
|
27
|
+
"pydantic>=2.0.0",
|
|
28
|
+
"PyJWT[crypto]>=2.0.0",
|
|
29
|
+
"tenacity>=8.0.0",
|
|
30
|
+
"uuid-utils>=0.6.0",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
openai = ["openai>=1.0.0"]
|
|
35
|
+
anthropic = ["anthropic>=0.18.0"]
|
|
36
|
+
elevenlabs = ["elevenlabs>=1.0.0"]
|
|
37
|
+
all = [
|
|
38
|
+
"openai>=1.0.0",
|
|
39
|
+
"anthropic>=0.18.0",
|
|
40
|
+
"elevenlabs>=1.0.0",
|
|
41
|
+
]
|
|
42
|
+
dev = [
|
|
43
|
+
"pytest>=7.0.0",
|
|
44
|
+
"pytest-asyncio>=0.21.0",
|
|
45
|
+
"pytest-httpx>=0.30.0",
|
|
46
|
+
"pytest-xdist>=3.5.0",
|
|
47
|
+
"ruff>=0.1.0",
|
|
48
|
+
"ty>=0.0.7",
|
|
49
|
+
"pre-commit>=3.0.0",
|
|
50
|
+
"openai>=1.0.0",
|
|
51
|
+
"anthropic>=0.18.0",
|
|
52
|
+
"elevenlabs>=1.0.0",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
[project.urls]
|
|
56
|
+
Homepage = "https://onstoa.com"
|
|
57
|
+
Repository = "https://github.com/stoa-org/stoa"
|
|
58
|
+
Documentation = "https://docs.onstoa.com"
|
|
59
|
+
|
|
60
|
+
[tool.hatch.build.targets.wheel]
|
|
61
|
+
packages = ["stoa"]
|
|
62
|
+
|
|
63
|
+
[tool.mypy]
|
|
64
|
+
python_version = "3.10"
|
|
65
|
+
strict = true
|
|
66
|
+
warn_return_any = true
|
|
67
|
+
warn_unused_ignores = true
|
|
68
|
+
plugins = ["pydantic.mypy"]
|
|
69
|
+
|
|
70
|
+
[tool.pydantic-mypy]
|
|
71
|
+
init_forbid_extra = true
|
|
72
|
+
init_typed = true
|
|
73
|
+
warn_required_dynamic_aliases = true
|
|
74
|
+
|
|
75
|
+
[[tool.mypy.overrides]]
|
|
76
|
+
module = ["elevenlabs.*", "uuid_utils"]
|
|
77
|
+
ignore_missing_imports = true
|
|
78
|
+
|
|
79
|
+
[tool.ruff]
|
|
80
|
+
target-version = "py310"
|
|
81
|
+
line-length = 100
|
|
82
|
+
|
|
83
|
+
[tool.ruff.lint]
|
|
84
|
+
select = ["E", "F", "I", "N", "W", "UP"]
|
|
85
|
+
|
|
86
|
+
[tool.pytest.ini_options]
|
|
87
|
+
asyncio_mode = "auto"
|
|
88
|
+
markers = [
|
|
89
|
+
"live: tests that require real services (control on :8000, events on :8001)",
|
|
90
|
+
]
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from stoa.client import Stoa
|
|
2
|
+
from stoa.connect import (
|
|
3
|
+
ConnectTokenClaims,
|
|
4
|
+
verify_connect_token,
|
|
5
|
+
)
|
|
6
|
+
from stoa.embedded import (
|
|
7
|
+
EmbeddedMembershipResult,
|
|
8
|
+
)
|
|
9
|
+
from stoa.events import (
|
|
10
|
+
AnthropicMessagesPayload,
|
|
11
|
+
CustomPayload,
|
|
12
|
+
DeepgramTranscriptionPayload,
|
|
13
|
+
DeepgramTTSPayload,
|
|
14
|
+
ElevenLabsSTSPayload,
|
|
15
|
+
ElevenLabsTTSPayload,
|
|
16
|
+
ElevenLabsVoiceClonePayload,
|
|
17
|
+
EventPayload,
|
|
18
|
+
EventType,
|
|
19
|
+
OpenAIAudioSpeechPayload,
|
|
20
|
+
OpenAIAudioTranscriptionPayload,
|
|
21
|
+
OpenAIChatCompletionPayload,
|
|
22
|
+
OpenAICompletionPayload,
|
|
23
|
+
OpenAIEmbeddingPayload,
|
|
24
|
+
OpenAIImageGenerationPayload,
|
|
25
|
+
OpenRouterChatCompletionPayload,
|
|
26
|
+
UsageEvent,
|
|
27
|
+
UsageEventBatch,
|
|
28
|
+
)
|
|
29
|
+
from stoa.exceptions import (
|
|
30
|
+
InsufficientBalanceError,
|
|
31
|
+
StoaAPIError,
|
|
32
|
+
StoaError,
|
|
33
|
+
UserNotFoundError,
|
|
34
|
+
)
|
|
35
|
+
from stoa.install import (
|
|
36
|
+
InstallExchangeResult,
|
|
37
|
+
InstallPublicKey,
|
|
38
|
+
InstallTokenClaims,
|
|
39
|
+
verify_install_token,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
# Client
|
|
44
|
+
"Stoa",
|
|
45
|
+
# Events
|
|
46
|
+
"EventType",
|
|
47
|
+
"EventPayload",
|
|
48
|
+
"UsageEvent",
|
|
49
|
+
"UsageEventBatch",
|
|
50
|
+
# Payloads
|
|
51
|
+
"OpenAIChatCompletionPayload",
|
|
52
|
+
"OpenAICompletionPayload",
|
|
53
|
+
"OpenAIEmbeddingPayload",
|
|
54
|
+
"OpenAIImageGenerationPayload",
|
|
55
|
+
"OpenAIAudioTranscriptionPayload",
|
|
56
|
+
"OpenAIAudioSpeechPayload",
|
|
57
|
+
"AnthropicMessagesPayload",
|
|
58
|
+
"OpenRouterChatCompletionPayload",
|
|
59
|
+
"ElevenLabsTTSPayload",
|
|
60
|
+
"ElevenLabsVoiceClonePayload",
|
|
61
|
+
"ElevenLabsSTSPayload",
|
|
62
|
+
"DeepgramTranscriptionPayload",
|
|
63
|
+
"DeepgramTTSPayload",
|
|
64
|
+
"CustomPayload",
|
|
65
|
+
# Exceptions
|
|
66
|
+
"StoaError",
|
|
67
|
+
"StoaAPIError",
|
|
68
|
+
"InsufficientBalanceError",
|
|
69
|
+
"UserNotFoundError",
|
|
70
|
+
# Install flow
|
|
71
|
+
"verify_install_token",
|
|
72
|
+
"InstallTokenClaims",
|
|
73
|
+
"InstallPublicKey",
|
|
74
|
+
"InstallExchangeResult",
|
|
75
|
+
# Connect flow
|
|
76
|
+
"verify_connect_token",
|
|
77
|
+
"ConnectTokenClaims",
|
|
78
|
+
# Vetted member registration
|
|
79
|
+
"EmbeddedMembershipResult",
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
__version__ = "0.2.0"
|