feat: cloud adapters E2B/Modal and billing export (SAND-WP-0010)
Add credentialed E2B and Modal extensions, burst routing fallback, fin-hub meter export hook, BYOK docs, and 77 tests.
This commit is contained in:
parent
6d0a1a8b1e
commit
15f031fd65
26 changed files with 859 additions and 75 deletions
49
src/sandboxer/payments/billing_export.py
Normal file
49
src/sandboxer/payments/billing_export.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
"""Optional fin-hub billing export for metered sandbox usage."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
|
||||
from sandboxer.models import MeterRecord, SandboxStatus
|
||||
|
||||
|
||||
def fin_hub_url() -> str | None:
|
||||
return os.environ.get("SANDBOXER_FIN_HUB_URL") or None
|
||||
|
||||
|
||||
def export_meter_usage(
|
||||
status: SandboxStatus,
|
||||
*,
|
||||
extension_id: str,
|
||||
meter: MeterRecord,
|
||||
) -> dict[str, Any] | None:
|
||||
"""POST usage record to fin-hub when SANDBOXER_FIN_HUB_URL is set."""
|
||||
if os.environ.get("SANDBOXER_NO_FIN_HUB", "").lower() in ("1", "true", "yes"):
|
||||
return None
|
||||
if meter.pricing_model != "metered" or not meter.actual_usd:
|
||||
return None
|
||||
|
||||
base = fin_hub_url()
|
||||
if not base:
|
||||
return None
|
||||
|
||||
payload = {
|
||||
"sandbox_id": status.sandbox_id,
|
||||
"extension_id": extension_id,
|
||||
"profile_id": status.profile_id,
|
||||
"consumer": status.consumer.model_dump(),
|
||||
"duration_s": meter.duration_s,
|
||||
"actual_usd": meter.actual_usd,
|
||||
"estimate_usd": meter.estimate_usd,
|
||||
"currency": meter.currency,
|
||||
}
|
||||
|
||||
try:
|
||||
response = httpx.post(f"{base.rstrip('/')}/usage/sandbox", json=payload, timeout=10.0)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except httpx.HTTPError:
|
||||
return None
|
||||
Loading…
Add table
Add a link
Reference in a new issue