Share the lifecycle
Keep create, get, runCommand, and delete in one application-facing module. Handle normalized sandbox state, routeable IDs, command results, error codes, and request IDs there.
Keep creation policy explicit
Images and lifetimes do not mean the same thing everywhere. Model the provider choice as a discriminated policy rather than a bag of optional values.
type SandboxRoute = | { provider: "e2b"; image: string; timeout: number } | { provider: "daytona"; image: string; timeout: number } | { provider: "runloop"; image?: string; timeout?: number } | { provider: "sprites" } | { provider: "blaxel"; image?: string; timeout?: number; workspace: string; };// Shared lifecycle code consumes an explicit route policy.const sandbox = await createSandbox(route);await executeWork(sandbox.id);await deleteSandbox(sandbox.id);Keep credentials outside request bodies
Inline provider credentials belong in X-Provider-Api-Key, separate from both the Wildflower Computer bearer key and JSON creation inputs. Connected project profiles avoid sending those secrets from application code. Inline Blaxel workspace context belongs in its generated connection header.
Treat provider choice as immutable per sandbox
A routeable ID identifies the selected provider and native sandbox. Do not reinterpret that ID for another adapter. To change provider, create a new sandbox and reconstruct only the state your application knows how to reproduce.
Test the common denominator and the exceptions
- Run the same create, get, runCommand, and delete contract for each adapter.
- Test each provider's accepted and rejected creation inputs.
- Verify stdout and stderr handling for each provider.
- Exercise credential redaction and normalized error codes.
- Keep deletion in a finally/defer path.
