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.
Files changed (28) hide show
  1. {oneshot_python-0.19.0 → oneshot_python-0.23.1}/.gitignore +3 -0
  2. {oneshot_python-0.19.0 → oneshot_python-0.23.1}/PKG-INFO +2 -2
  3. {oneshot_python-0.19.0 → oneshot_python-0.23.1}/README.md +41 -0
  4. {oneshot_python-0.19.0 → oneshot_python-0.23.1}/oneshot/__init__.py +11 -0
  5. {oneshot_python-0.19.0 → oneshot_python-0.23.1}/oneshot/_errors.py +46 -0
  6. {oneshot_python-0.19.0 → oneshot_python-0.23.1}/oneshot/_types.py +40 -0
  7. {oneshot_python-0.19.0 → oneshot_python-0.23.1}/oneshot/client.py +324 -19
  8. oneshot_python-0.23.1/oneshot/physical_mail.py +168 -0
  9. {oneshot_python-0.19.0 → oneshot_python-0.23.1}/pyproject.toml +2 -2
  10. oneshot_python-0.23.1/tests/test_budgets.py +184 -0
  11. oneshot_python-0.23.1/tests/test_physical_mail.py +43 -0
  12. oneshot_python-0.23.1/tests/test_poll_backoff.py +107 -0
  13. {oneshot_python-0.19.0 → oneshot_python-0.23.1}/tests/test_request_id.py +1 -4
  14. oneshot_python-0.23.1/tests/test_wait_false.py +88 -0
  15. {oneshot_python-0.19.0 → oneshot_python-0.23.1}/uv.lock +1 -1
  16. {oneshot_python-0.19.0 → oneshot_python-0.23.1}/oneshot/x402.py +0 -0
  17. {oneshot_python-0.19.0 → oneshot_python-0.23.1}/tests/__init__.py +0 -0
  18. {oneshot_python-0.19.0 → oneshot_python-0.23.1}/tests/test_balance.py +0 -0
  19. {oneshot_python-0.19.0 → oneshot_python-0.23.1}/tests/test_charge_amount.py +0 -0
  20. {oneshot_python-0.19.0 → oneshot_python-0.23.1}/tests/test_compute.py +0 -0
  21. {oneshot_python-0.19.0 → oneshot_python-0.23.1}/tests/test_domains.py +0 -0
  22. {oneshot_python-0.19.0 → oneshot_python-0.23.1}/tests/test_email_payload.py +0 -0
  23. {oneshot_python-0.19.0 → oneshot_python-0.23.1}/tests/test_emergency_error.py +0 -0
  24. {oneshot_python-0.19.0 → oneshot_python-0.23.1}/tests/test_max_cost_header.py +0 -0
  25. {oneshot_python-0.19.0 → oneshot_python-0.23.1}/tests/test_payment_rejection.py +0 -0
  26. {oneshot_python-0.19.0 → oneshot_python-0.23.1}/tests/test_phones_pending.py +0 -0
  27. {oneshot_python-0.19.0 → oneshot_python-0.23.1}/tests/test_tag_receipt_value.py +0 -0
  28. {oneshot_python-0.19.0 → oneshot_python-0.23.1}/tests/test_x402.py +0 -0
@@ -90,3 +90,6 @@ output/
90
90
 
91
91
  # Claude Code agent metadata (lockfile + local settings)
92
92
  .claude/
93
+
94
+ # generated by scripts/deploy-blog-generator.sh
95
+ scripts/humanizer.vendored.ts
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: oneshot-python
3
- Version: 0.19.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