# Adapter authoring

Build an adapter around the smallest provider shape that is both real and portable. The shared surface is for files, bounded commands, lifecycle-safe background processes, ports, and snapshot operations with matching semantics. Keep everything else on the adapter's typed `raw` value.

The maintained local adapter is the reference implementation: https://github.com/dancer/sandbox/blob/main/packages/local/src/index.ts

## Start from provider facts

- Verify the current official provider documentation, installed declarations, and provider source before deciding which features belong on the normalized surface.
- Keep native features such as PTY, storage, GPU, custom networking, terminal sessions, and provider-specific snapshot workflows behind `raw` unless multiple providers can support one truthful contract.
- Let explicit factory options win over environment variables. Validate missing or conflicting configuration before making a provider request.
- Never copy adapter credentials into sandbox environment values. Only pass secrets that the sandbox workload is explicitly allowed to use.

## Implement the low-level runtime

Use `SandboxRuntime<Raw>` with `fromSandboxRuntime()` for adapters that can map the core contract directly.

- Implement `files.read()` as `ReadableStream<Uint8Array>` when the provider exposes a native stream. The helper derives `files.read()`, `files.text()`, and `files.stream()` from that stream. When an SDK only returns a complete file, advertise `fileStreaming: "buffered"` rather than implying incremental delivery.
- Resolve relative paths before calling low-level file methods when the provider needs a concrete working directory. `fromSandboxRuntime()` preserves runtime paths unchanged; `sandboxPath(cwd, path)` is the shared resolver, not a security boundary.
- Provide direct bounded `process.exec()` and `process.shell()` results when the provider has one-shot command APIs. Provide `spawn()` and `spawnShell()` only when the provider can return a real `Running` handle with output, a final result, and lifecycle-safe `kill()` behavior.
- Return a serializable URL from low-level `ports.expose()`, with optional provider headers when preview access requires them. `fromSandboxRuntime()` keeps those headers inside the public non-enumerable `Preview.request()` wrapper, so they never appear in preview URLs or serialized data.
- Expose the native client as `raw` with its actual provider type. Do not erase it to `unknown` or accept arbitrary structural test doubles as a public raw client.

## Advertise capability truthfully

`capabilities` is a contract, not a feature wishlist. Every advertised flag needs an implementation and a deterministic test. Keep snapshot creation, deletion, in-place restore, and create-from-snapshot separate because providers rarely implement the same lifecycle semantics. Keep `processSpawn` false when a provider cannot offer a reliable process handle, even if it can run one-shot commands.

Use `SandboxError` with a stable code for configuration, unsupported, path, timeout, abort, and normalized provider failures. Do not silently discard unsupported options or make a capability appear supported just because a related native method exists.

## Verify the adapter

1. Add configuration tests that prove invalid input fails before provider work.
2. Add sanitized fixture replay for normalized inputs and outputs. Fixtures prove contract mapping, not live provider behavior.
3. Add a credential-gated `verify:<provider>` workflow that creates a sandbox, exercises the advertised capabilities, and always cleans up in `finally`.
4. Build the package, typecheck a consumer example against built declarations, run the deterministic suite, regenerate API docs, and dry-run the package before publishing.

Do not ship fake provider support. An explicit unsupported error with typed `raw` access is better DX than a broad interface that fails after creating a billable sandbox.