oneshot-python 0.19.0__tar.gz → 0.23.1__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.
- {oneshot_python-0.19.0 → oneshot_python-0.23.1}/.gitignore +3 -0
- {oneshot_python-0.19.0 → oneshot_python-0.23.1}/PKG-INFO +2 -2
- {oneshot_python-0.19.0 → oneshot_python-0.23.1}/README.md +41 -0
- {oneshot_python-0.19.0 → oneshot_python-0.23.1}/oneshot/__init__.py +11 -0
- {oneshot_python-0.19.0 → oneshot_python-0.23.1}/oneshot/_errors.py +46 -0
- {oneshot_python-0.19.0 → oneshot_python-0.23.1}/oneshot/_types.py +40 -0
- {oneshot_python-0.19.0 → oneshot_python-0.23.1}/oneshot/client.py +324 -19
- oneshot_python-0.23.1/oneshot/physical_mail.py +168 -0
- {oneshot_python-0.19.0 → oneshot_python-0.23.1}/pyproject.toml +2 -2
- oneshot_python-0.23.1/tests/test_budgets.py +184 -0
- oneshot_python-0.23.1/tests/test_physical_mail.py +43 -0
- oneshot_python-0.23.1/tests/test_poll_backoff.py +107 -0
- {oneshot_python-0.19.0 → oneshot_python-0.23.1}/tests/test_request_id.py +1 -4
- oneshot_python-0.23.1/tests/test_wait_false.py +88 -0
- {oneshot_python-0.19.0 → oneshot_python-0.23.1}/uv.lock +1 -1
- {oneshot_python-0.19.0 → oneshot_python-0.23.1}/oneshot/x402.py +0 -0
- {oneshot_python-0.19.0 → oneshot_python-0.23.1}/tests/__init__.py +0 -0
- {oneshot_python-0.19.0 → oneshot_python-0.23.1}/tests/test_balance.py +0 -0
- {oneshot_python-0.19.0 → oneshot_python-0.23.1}/tests/test_charge_amount.py +0 -0
- {oneshot_python-0.19.0 → oneshot_python-0.23.1}/tests/test_compute.py +0 -0
- {oneshot_python-0.19.0 → oneshot_python-0.23.1}/tests/test_domains.py +0 -0
- {oneshot_python-0.19.0 → oneshot_python-0.23.1}/tests/test_email_payload.py +0 -0
- {oneshot_python-0.19.0 → oneshot_python-0.23.1}/tests/test_emergency_error.py +0 -0
- {oneshot_python-0.19.0 → oneshot_python-0.23.1}/tests/test_max_cost_header.py +0 -0
- {oneshot_python-0.19.0 → oneshot_python-0.23.1}/tests/test_payment_rejection.py +0 -0
- {oneshot_python-0.19.0 → oneshot_python-0.23.1}/tests/test_phones_pending.py +0 -0
- {oneshot_python-0.19.0 → oneshot_python-0.23.1}/tests/test_tag_receipt_value.py +0 -0
- {oneshot_python-0.19.0 → oneshot_python-0.23.1}/tests/test_x402.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: oneshot-python
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Core Python SDK for the OneShot API — HTTP client with x402 payment handling
|
|
3
|
+
Version: 0.23.1
|
|
4
|
+
Summary: Core Python SDK for the OneShot API — HTTP client with x402 payment handling — 35 tools
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Requires-Python: >=3.10
|
|
7
7
|
Requires-Dist: eth-account>=0.13.0
|
|
@@ -66,6 +66,43 @@ Read endpoints (inbox, SMS inbox, notifications, balance, browser profiles) retu
|
|
|
66
66
|
|
|
67
67
|
This is automatic — no extra code beyond `private_key`. **Requires `oneshot-python >= 0.17.0`.** Older versions keep working until the API enables enforcement, after which they must send a valid `x-agent-proof`.
|
|
68
68
|
|
|
69
|
+
## Spend Budgets
|
|
70
|
+
|
|
71
|
+
Cap what the agent can spend so a runaway loop can't drain the wallet overnight:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
client = OneShotClient(
|
|
75
|
+
"0x...",
|
|
76
|
+
budgets={
|
|
77
|
+
"daily": 50, # max USDC per UTC day
|
|
78
|
+
"per_transaction": 5, # max USDC for any single call
|
|
79
|
+
"alert_at": 0.8, # warn at 80% of daily
|
|
80
|
+
"pause_at": 1.0, # stop paying at 100%
|
|
81
|
+
},
|
|
82
|
+
alert_email="you@example.com",
|
|
83
|
+
)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Unlike `max_cost` (a per-call client-side check), budgets are **enforced server-side** against your receipt ledger. The config is synced once before your first paid call, so the daily figure is a true per-agent total — shared across every process and restart using the same wallet.
|
|
87
|
+
|
|
88
|
+
At `alert_at` you get a `budget_warning` notification (in-app via `/v1/tools/notifications`, plus email if set). At `pause_at` paid calls raise `BudgetExceededError` until the window resets at the next UTC midnight:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from oneshot import BudgetExceededError
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
client.research(topic="...")
|
|
95
|
+
except BudgetExceededError as e:
|
|
96
|
+
print(f"{e.reason} budget hit — ${e.spent} spent, resumes {e.resets_at}")
|
|
97
|
+
|
|
98
|
+
client.get_budgets()
|
|
99
|
+
# {"daily_usdc": 50, "spent_today_usdc": "41.20", "pct_used": 0.824, "resets_at": "..."}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Omit `budgets` to leave whatever is stored server-side untouched — agents without a budget are unlimited. **Requires `oneshot-python >= 0.20.0`.**
|
|
103
|
+
|
|
104
|
+
The sync **fails closed**: if the budget can't be confirmed with the server, the paid call raises `BudgetSyncError` and is not made (retried on the next call). An invalid config (`daily=-1`, `alert_at=2`) raises `ValidationError` at construction.
|
|
105
|
+
|
|
69
106
|
## Configuration
|
|
70
107
|
|
|
71
108
|
```python
|
|
@@ -164,3 +201,7 @@ Every method has an `a*` async mirror (`acompute`, `aget_compute_goal`, …).
|
|
|
164
201
|
## License
|
|
165
202
|
|
|
166
203
|
MIT
|
|
204
|
+
|
|
205
|
+
### Physical mail
|
|
206
|
+
|
|
207
|
+
`client.physical_mail` supports U.S. letters and 4×6 postcards through `upload_artwork`, `validate_address`, `preview`, `get_quote`, `approve`, `send`, `get_order`, `recover`, and `cancel`. Async variants prefix the method with `a`. Review each proof and price, then call `approve(..., approved=True)` explicitly. Persist `idempotency_key` before sending. `recover(key)` retrieves the original order without creating another charge. Postal delivery does not prove readership; paid receipts and fulfillment events remain separate. Lob credentials stay on OneShot's server.
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
"""oneshot-python — Core Python SDK for the OneShot API."""
|
|
2
2
|
|
|
3
|
+
from oneshot.physical_mail import PhysicalMail, PostalAddress, MailQuote, MailOrder
|
|
4
|
+
|
|
3
5
|
from oneshot._errors import (
|
|
6
|
+
BudgetExceededError,
|
|
7
|
+
BudgetSyncError,
|
|
4
8
|
ContentBlockedError,
|
|
5
9
|
EmergencyNumberError,
|
|
6
10
|
JobError,
|
|
@@ -11,6 +15,8 @@ from oneshot._errors import (
|
|
|
11
15
|
ValidationError,
|
|
12
16
|
)
|
|
13
17
|
from oneshot._types import (
|
|
18
|
+
AgentBudgetConfig,
|
|
19
|
+
AgentBudgetStatus,
|
|
14
20
|
ComputeBudgetStatus,
|
|
15
21
|
ComputeCancelResult,
|
|
16
22
|
ComputeFundResult,
|
|
@@ -30,16 +36,21 @@ from oneshot.client import OneShotClient
|
|
|
30
36
|
from oneshot.x402 import sign_payment_authorization
|
|
31
37
|
|
|
32
38
|
__all__ = [
|
|
39
|
+
"PhysicalMail", "PostalAddress", "MailQuote", "MailOrder",
|
|
33
40
|
"OneShotClient",
|
|
34
41
|
"OneShotError",
|
|
35
42
|
"ToolError",
|
|
36
43
|
"PaymentError",
|
|
44
|
+
"BudgetExceededError",
|
|
45
|
+
"BudgetSyncError",
|
|
37
46
|
"JobError",
|
|
38
47
|
"JobTimeoutError",
|
|
39
48
|
"ValidationError",
|
|
40
49
|
"ContentBlockedError",
|
|
41
50
|
"EmergencyNumberError",
|
|
42
51
|
"sign_payment_authorization",
|
|
52
|
+
"AgentBudgetConfig",
|
|
53
|
+
"AgentBudgetStatus",
|
|
43
54
|
"ComputeSchedule",
|
|
44
55
|
"ComputeGoalResult",
|
|
45
56
|
"ComputeGoalStatus",
|
|
@@ -81,3 +81,49 @@ class EmergencyNumberError(OneShotError):
|
|
|
81
81
|
def __init__(self, message: str, blocked_number: str) -> None:
|
|
82
82
|
super().__init__(message)
|
|
83
83
|
self.blocked_number = blocked_number
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class BudgetExceededError(OneShotError):
|
|
87
|
+
"""The agent's own spend budget stopped this call (HTTP 403 ``budget_exceeded``).
|
|
88
|
+
|
|
89
|
+
Distinct from :class:`PaymentError`: nothing was signed and nothing was
|
|
90
|
+
charged. A retry cannot succeed until the daily window resets
|
|
91
|
+
(``resets_at``) or the budget is raised — which is why the server answers
|
|
92
|
+
403 rather than 402. ``reason`` is ``"daily"`` when cumulative UTC-day
|
|
93
|
+
spend would cross the budget, or ``"per_transaction"`` when this single
|
|
94
|
+
call is larger than the per-call cap.
|
|
95
|
+
|
|
96
|
+
Mirrors ``BudgetExceededError`` in ``libs/agent-sdk/src/errors.ts``.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
def __init__(
|
|
100
|
+
self,
|
|
101
|
+
message: str,
|
|
102
|
+
reason: str,
|
|
103
|
+
cap: float | None = None,
|
|
104
|
+
spent: float | None = None,
|
|
105
|
+
charge: float | None = None,
|
|
106
|
+
resets_at: str | None = None,
|
|
107
|
+
) -> None:
|
|
108
|
+
super().__init__(message)
|
|
109
|
+
self.reason = reason
|
|
110
|
+
self.cap = cap
|
|
111
|
+
self.spent = spent
|
|
112
|
+
self.charge = charge
|
|
113
|
+
self.resets_at = resets_at
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class BudgetSyncError(OneShotError):
|
|
117
|
+
"""The configured spend budget could not be confirmed with the server, so
|
|
118
|
+
the paid call was NOT made.
|
|
119
|
+
|
|
120
|
+
Budgets are enforced server-side; if the sync fails (network error, 5xx,
|
|
121
|
+
rate limit, rejected config) proceeding would silently drop the guardrail
|
|
122
|
+
the developer configured. The sync is retried on the next paid call.
|
|
123
|
+
``status`` is the HTTP status when the server answered, else None.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
def __init__(self, message: str, status: int | None = None, body: str | None = None) -> None:
|
|
127
|
+
super().__init__(message)
|
|
128
|
+
self.status = status
|
|
129
|
+
self.body = body
|
|
@@ -181,6 +181,12 @@ class DomainPoolEntry(TypedDict, total=False):
|
|
|
181
181
|
|
|
182
182
|
domain: str
|
|
183
183
|
default_from: str
|
|
184
|
+
# 'mailbox' = a real mailbox is provisioned per address, so every address not
|
|
185
|
+
# already active in addresses[] bills the one-time mailbox_provisioning_fee on
|
|
186
|
+
# its first send — the canonical default_from included. 'relay' = header-only,
|
|
187
|
+
# no per-address fee. A property of the DOMAIN, fixed at setup: omitting
|
|
188
|
+
# mailbox_mode on a quote does not make the send relay.
|
|
189
|
+
mailbox_mode: str # relay | mailbox
|
|
184
190
|
addresses: list[DomainAddressEntry]
|
|
185
191
|
pool_status: str # active | paused | removed
|
|
186
192
|
warmup_state: str # warming | warmed | degraded
|
|
@@ -227,3 +233,37 @@ __all__ = [
|
|
|
227
233
|
"DomainPoolListResult",
|
|
228
234
|
"DomainPoolStatusResult",
|
|
229
235
|
]
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
# ── Agent spend budgets ──────────────────────────────────────────────────
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class AgentBudgetConfig(TypedDict, total=False):
|
|
242
|
+
"""Spend budget passed to ``OneShotClient(budgets=...)``.
|
|
243
|
+
|
|
244
|
+
Mirrors ``AgentBudgetConfig`` in TS (snake_case here; camelCase keys are
|
|
245
|
+
accepted too and normalised). Synced to the server once, before the first
|
|
246
|
+
paid call, and enforced there against the receipt ledger.
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
daily: float
|
|
250
|
+
"""Max USDC per UTC day."""
|
|
251
|
+
per_transaction: float
|
|
252
|
+
"""Max USDC for any single call."""
|
|
253
|
+
alert_at: float
|
|
254
|
+
"""Fraction of ``daily`` that triggers a ``budget_warning`` (default 0.8)."""
|
|
255
|
+
pause_at: float
|
|
256
|
+
"""Fraction of ``daily`` at which paid calls stop (default 1.0)."""
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class AgentBudgetStatus(TypedDict):
|
|
260
|
+
"""Live utilization from ``GET /v1/agents/me/budgets``. Mirrors ``AgentBudgetStatus`` in TS."""
|
|
261
|
+
|
|
262
|
+
daily_usdc: Optional[float]
|
|
263
|
+
per_transaction_usdc: Optional[float]
|
|
264
|
+
alert_at: Optional[float]
|
|
265
|
+
pause_at: Optional[float]
|
|
266
|
+
spent_today_usdc: str
|
|
267
|
+
remaining_usdc: Optional[str]
|
|
268
|
+
pct_used: Optional[float]
|
|
269
|
+
resets_at: str
|