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:
2026-06-24 12:50:19 +02:00
parent 6d0a1a8b1e
commit 15f031fd65
26 changed files with 859 additions and 75 deletions

View File

@@ -21,6 +21,7 @@ from sandboxer.models import (
SandboxStatus,
SnapshotRecord,
)
from sandboxer.payments.billing_export import export_meter_usage
from sandboxer.payments.credits import CreditsStore
from sandboxer.payments.metering import estimate_cost, settle_usage
from sandboxer.placement import resolve_host
@@ -60,6 +61,8 @@ class SandboxManager:
"vm_target": status.inputs.get("vm_target", ""),
"vm_host": status.inputs.get("vm_host", ""),
"endpoint": status.inputs.get("endpoint", ""),
"provider_sandbox_id": status.inputs.get("provider_sandbox_id", ""),
"provider": status.inputs.get("provider", ""),
}
def _resolved_host(self, profile, extension, host_override: str | None) -> str:
@@ -133,6 +136,8 @@ class SandboxManager:
status.inputs["vm_target"] = handle.get("vm_target", "")
status.inputs["vm_host"] = handle.get("vm_host", "")
status.inputs["endpoint"] = handle.get("endpoint", "")
status.inputs["provider_sandbox_id"] = handle.get("provider_sandbox_id", "")
status.inputs["provider"] = handle.get("provider", "")
reach = backend.wait_ready(handle)
status.reachability = Reachability(**reach)
status.state = SandboxState.READY
@@ -209,6 +214,7 @@ class SandboxManager:
if settled and settled.pricing_model == "metered" and settled.actual_usd:
self.credits.debit(settled.actual_usd)
status.meter = settled
export_meter_usage(status, extension_id=extension.id, meter=settled)
emit_lifecycle_event(
status,
summary=(