@cloudflare/sandbox 0.9.1 → 0.9.3

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.
@@ -1 +0,0 @@
1
- {"version":3,"file":"sandbox-PAYx1CcU.js","names":["errorResponse: ErrorResponse<TContext>","path","init: RequestInit","response: Response","path","DEFAULT_CONNECT_TIMEOUT_MS","DEFAULT_IDLE_DISCONNECT_MS","path","normalized: Record<string, string>","request: WSRequest","streamController: ReadableStreamDefaultController<Uint8Array>","encoder","sseData: string","sseData","path","errorData: NewErrorResponse","data: CreateBackupRequest","data: RestoreBackupRequest","data: ExecuteRequest","path","data: GitCheckoutRequest","lastError: Error | undefined","data: StartProcessRequest","bufferedChunks: Uint8Array[]","currentEvent: SSEPartialEvent","event: FileWatchSSEEvent | undefined","clientOptions: HttpClientOptions","#ws","#error","#receiveResolver","#receiveRejecter","#receiveQueue","#fail","#sendQueue","path","currentEvent: SSEPartialEvent","metadata: FileMetadata | null","chunks: Array<string | Uint8Array>","code?: string","currentEvent: SSEPartialEvent","changed: Array<{ key: string; action: 'created' | 'modified' }>","results: Array<{ key: string; etag: string; size: number }>","cursor: string | undefined","proxyUrl: string","path","headers: Record<string, string>","sanitizedSandboxId: string","configuration: SandboxConfiguration","operations: Promise<void>[]","mountOutcome: 'success' | 'error'","mountError: Error | undefined","mountInfo: LocalSyncMountInfo","passwordFilePath: string | undefined","provider: BucketProvider | null","mountInfo: FuseMountInfo","unmountOutcome: 'success' | 'error'","unmountError: Error | undefined","s3fsArgs: string[]","outcome: 'success' | 'error'","caughtError: Error | undefined","normalized: Record<string, PortTokenEntry>","containerVersion: string | undefined","outcome: string","errorBody: ErrorResponse","errorBody","request: Request","port: number | undefined","placementId: string | null | undefined","error: unknown","execOutcome: { exitCode: number; success: boolean } | undefined","execError: Error | undefined","result: ExecResult","timeoutId: ReturnType<typeof setTimeout> | undefined","timeoutPromise: Promise<never> | undefined","path","url: string","token: string","missing: string[]","options: RemoteMountBucketOptions","backupId: string | undefined","sizeBytes: number | undefined","backupSession: string | undefined"],"sources":["../src/errors/classes.ts","../src/errors/adapter.ts","../src/clients/transport/base-transport.ts","../src/clients/transport/http-transport.ts","../src/clients/transport/ws-transport.ts","../src/clients/transport/factory.ts","../src/clients/base-client.ts","../src/clients/backup-client.ts","../src/clients/command-client.ts","../src/clients/desktop-client.ts","../src/clients/file-client.ts","../src/clients/git-client.ts","../src/clients/interpreter-client.ts","../src/clients/port-client.ts","../src/clients/process-client.ts","../src/clients/utility-client.ts","../src/clients/watch-client.ts","../src/clients/sandbox-client.ts","../../shared/src/backup.ts","../src/container-connection.ts","../src/clients/rpc-sandbox-client.ts","../src/file-stream.ts","../src/security.ts","../src/interpreter.ts","../src/sse-parser.ts","../src/storage-mount/errors.ts","../src/storage-mount/credential-detection.ts","../src/storage-mount/provider-detection.ts","../src/storage-mount/validation.ts","../src/local-mount-sync.ts","../src/pty/proxy.ts","../src/request-handler.ts","../src/version.ts","../src/sandbox.ts"],"sourcesContent":["/**\n * Type-safe error classes that wrap ErrorResponse from container\n *\n * All error classes extend SandboxError<TContext> which wraps the full ErrorResponse\n * and provides type-safe accessors for error properties.\n */\n\nimport type {\n BackupCreateContext,\n BackupExpiredContext,\n BackupNotFoundContext,\n BackupRestoreContext,\n CodeExecutionContext,\n CommandErrorContext,\n CommandNotFoundContext,\n ContextNotFoundContext,\n DesktopCoordinateErrorContext,\n DesktopErrorContext,\n ErrorResponse,\n FileExistsContext,\n FileNotFoundContext,\n FileSystemContext,\n FileTooLargeContext,\n GitAuthFailedContext,\n GitBranchNotFoundContext,\n GitErrorContext,\n GitRepositoryNotFoundContext,\n InternalErrorContext,\n InterpreterNotReadyContext,\n InvalidBackupConfigContext,\n InvalidPortContext,\n PortAlreadyExposedContext,\n PortErrorContext,\n PortNotExposedContext,\n ProcessErrorContext,\n ProcessExitedBeforeReadyContext,\n ProcessNotFoundContext,\n ProcessReadyTimeoutContext,\n SessionAlreadyExistsContext,\n SessionDestroyedContext,\n SessionTerminatedContext,\n ValidationFailedContext\n} from '@repo/shared/errors';\n\n/**\n * Base SDK error that wraps ErrorResponse\n * Preserves all error information from container\n */\nexport class SandboxError<TContext = Record<string, unknown>> extends Error {\n constructor(public readonly errorResponse: ErrorResponse<TContext>) {\n super(errorResponse.message);\n this.name = 'SandboxError';\n }\n\n // Convenience accessors\n get code() {\n return this.errorResponse.code;\n }\n get context() {\n return this.errorResponse.context;\n }\n get httpStatus() {\n return this.errorResponse.httpStatus;\n }\n get operation() {\n return this.errorResponse.operation;\n }\n get suggestion() {\n return this.errorResponse.suggestion;\n }\n get timestamp() {\n return this.errorResponse.timestamp;\n }\n get documentation() {\n return this.errorResponse.documentation;\n }\n\n // Custom serialization for logging\n toJSON() {\n return {\n name: this.name,\n message: this.message,\n code: this.code,\n context: this.context,\n httpStatus: this.httpStatus,\n operation: this.operation,\n suggestion: this.suggestion,\n timestamp: this.timestamp,\n documentation: this.documentation,\n stack: this.stack\n };\n }\n}\n\n// ============================================================================\n// File System Errors\n// ============================================================================\n\n/**\n * Error thrown when a file or directory is not found\n */\nexport class FileNotFoundError extends SandboxError<FileNotFoundContext> {\n constructor(errorResponse: ErrorResponse<FileNotFoundContext>) {\n super(errorResponse);\n this.name = 'FileNotFoundError';\n }\n\n // Type-safe accessors\n get path() {\n return this.context.path;\n }\n}\n\n/**\n * Error thrown when a file already exists\n */\nexport class FileExistsError extends SandboxError<FileExistsContext> {\n constructor(errorResponse: ErrorResponse<FileExistsContext>) {\n super(errorResponse);\n this.name = 'FileExistsError';\n }\n\n // Type-safe accessor\n get path() {\n return this.context.path;\n }\n}\n\n/**\n * Error thrown when a file is too large\n */\nexport class FileTooLargeError extends SandboxError<FileTooLargeContext> {\n constructor(errorResponse: ErrorResponse<FileTooLargeContext>) {\n super(errorResponse);\n this.name = 'FileTooLargeError';\n }\n\n // Type-safe accessor\n get path() {\n return this.context.path;\n }\n}\n\n/**\n * Generic file system error (permissions, disk full, etc.)\n */\nexport class FileSystemError extends SandboxError<FileSystemContext> {\n constructor(errorResponse: ErrorResponse<FileSystemContext>) {\n super(errorResponse);\n this.name = 'FileSystemError';\n }\n\n // Type-safe accessors\n get path() {\n return this.context.path;\n }\n get stderr() {\n return this.context.stderr;\n }\n get exitCode() {\n return this.context.exitCode;\n }\n}\n\n/**\n * Error thrown when permission is denied\n */\nexport class PermissionDeniedError extends SandboxError<FileSystemContext> {\n constructor(errorResponse: ErrorResponse<FileSystemContext>) {\n super(errorResponse);\n this.name = 'PermissionDeniedError';\n }\n\n get path() {\n return this.context.path;\n }\n}\n\n// ============================================================================\n// Command Errors\n// ============================================================================\n\n/**\n * Error thrown when a command is not found\n */\nexport class CommandNotFoundError extends SandboxError<CommandNotFoundContext> {\n constructor(errorResponse: ErrorResponse<CommandNotFoundContext>) {\n super(errorResponse);\n this.name = 'CommandNotFoundError';\n }\n\n // Type-safe accessor\n get command() {\n return this.context.command;\n }\n}\n\n/**\n * Generic command execution error\n */\nexport class CommandError extends SandboxError<CommandErrorContext> {\n constructor(errorResponse: ErrorResponse<CommandErrorContext>) {\n super(errorResponse);\n this.name = 'CommandError';\n }\n\n // Type-safe accessors\n get command() {\n return this.context.command;\n }\n get exitCode() {\n return this.context.exitCode;\n }\n get stdout() {\n return this.context.stdout;\n }\n get stderr() {\n return this.context.stderr;\n }\n}\n\n// ============================================================================\n// Process Errors\n// ============================================================================\n\n/**\n * Error thrown when a process is not found\n */\nexport class ProcessNotFoundError extends SandboxError<ProcessNotFoundContext> {\n constructor(errorResponse: ErrorResponse<ProcessNotFoundContext>) {\n super(errorResponse);\n this.name = 'ProcessNotFoundError';\n }\n\n // Type-safe accessor\n get processId() {\n return this.context.processId;\n }\n}\n\n/**\n * Generic process error\n */\nexport class ProcessError extends SandboxError<ProcessErrorContext> {\n constructor(errorResponse: ErrorResponse<ProcessErrorContext>) {\n super(errorResponse);\n this.name = 'ProcessError';\n }\n\n // Type-safe accessors\n get processId() {\n return this.context.processId;\n }\n get pid() {\n return this.context.pid;\n }\n get exitCode() {\n return this.context.exitCode;\n }\n get stderr() {\n return this.context.stderr;\n }\n}\n\n// ============================================================================\n// Session Errors\n// ============================================================================\n\n/**\n * Error thrown when a session already exists\n */\nexport class SessionAlreadyExistsError extends SandboxError<SessionAlreadyExistsContext> {\n constructor(errorResponse: ErrorResponse<SessionAlreadyExistsContext>) {\n super(errorResponse);\n this.name = 'SessionAlreadyExistsError';\n }\n\n // Type-safe accessors\n get sessionId() {\n return this.context.sessionId;\n }\n\n get containerPlacementId(): string | null | undefined {\n return this.context.containerPlacementId;\n }\n}\n\n/**\n * Error thrown when a session was destroyed while a command was executing\n */\nexport class SessionDestroyedError extends SandboxError<SessionDestroyedContext> {\n constructor(errorResponse: ErrorResponse<SessionDestroyedContext>) {\n super(errorResponse);\n this.name = 'SessionDestroyedError';\n }\n\n // Type-safe accessor\n get sessionId() {\n return this.context.sessionId;\n }\n}\n\n/**\n * Error thrown when a session's underlying shell exited without an explicit\n * `destroy()` call (user ran `exit`, the shell crashed, or a child process\n * took the shell down). The session-local state is gone, but the next call\n * with the same sessionId will transparently start a fresh session.\n */\nexport class SessionTerminatedError extends SandboxError<SessionTerminatedContext> {\n constructor(errorResponse: ErrorResponse<SessionTerminatedContext>) {\n super(errorResponse);\n this.name = 'SessionTerminatedError';\n }\n\n get sessionId() {\n return this.context.sessionId;\n }\n\n get exitCode() {\n return this.context.exitCode;\n }\n}\n\n// ============================================================================\n// Port Errors\n// ============================================================================\n\n/**\n * Error thrown when a port is already exposed\n */\nexport class PortAlreadyExposedError extends SandboxError<PortAlreadyExposedContext> {\n constructor(errorResponse: ErrorResponse<PortAlreadyExposedContext>) {\n super(errorResponse);\n this.name = 'PortAlreadyExposedError';\n }\n\n // Type-safe accessors\n get port() {\n return this.context.port;\n }\n get portName() {\n return this.context.portName;\n }\n}\n\n/**\n * Error thrown when a port is not exposed\n */\nexport class PortNotExposedError extends SandboxError<PortNotExposedContext> {\n constructor(errorResponse: ErrorResponse<PortNotExposedContext>) {\n super(errorResponse);\n this.name = 'PortNotExposedError';\n }\n\n // Type-safe accessor\n get port() {\n return this.context.port;\n }\n}\n\n/**\n * Error thrown when a port number is invalid\n */\nexport class InvalidPortError extends SandboxError<InvalidPortContext> {\n constructor(errorResponse: ErrorResponse<InvalidPortContext>) {\n super(errorResponse);\n this.name = 'InvalidPortError';\n }\n\n // Type-safe accessors\n get port() {\n return this.context.port;\n }\n get reason() {\n return this.context.reason;\n }\n}\n\n/**\n * Error thrown when a service on a port is not responding\n */\nexport class ServiceNotRespondingError extends SandboxError<PortErrorContext> {\n constructor(errorResponse: ErrorResponse<PortErrorContext>) {\n super(errorResponse);\n this.name = 'ServiceNotRespondingError';\n }\n\n // Type-safe accessors\n get port() {\n return this.context.port;\n }\n get portName() {\n return this.context.portName;\n }\n}\n\n/**\n * Error thrown when a port is already in use\n */\nexport class PortInUseError extends SandboxError<PortErrorContext> {\n constructor(errorResponse: ErrorResponse<PortErrorContext>) {\n super(errorResponse);\n this.name = 'PortInUseError';\n }\n\n // Type-safe accessor\n get port() {\n return this.context.port;\n }\n}\n\n/**\n * Generic port operation error\n */\nexport class PortError extends SandboxError<PortErrorContext> {\n constructor(errorResponse: ErrorResponse<PortErrorContext>) {\n super(errorResponse);\n this.name = 'PortError';\n }\n\n // Type-safe accessors\n get port() {\n return this.context.port;\n }\n get portName() {\n return this.context.portName;\n }\n get stderr() {\n return this.context.stderr;\n }\n}\n\n/**\n * Error thrown when port exposure requires a custom domain\n */\nexport class CustomDomainRequiredError extends SandboxError<InternalErrorContext> {\n constructor(errorResponse: ErrorResponse<InternalErrorContext>) {\n super(errorResponse);\n this.name = 'CustomDomainRequiredError';\n }\n}\n\n// ============================================================================\n// Git Errors\n// ============================================================================\n\n/**\n * Error thrown when a git repository is not found\n */\nexport class GitRepositoryNotFoundError extends SandboxError<GitRepositoryNotFoundContext> {\n constructor(errorResponse: ErrorResponse<GitRepositoryNotFoundContext>) {\n super(errorResponse);\n this.name = 'GitRepositoryNotFoundError';\n }\n\n // Type-safe accessor\n get repository() {\n return this.context.repository;\n }\n}\n\n/**\n * Error thrown when git authentication fails\n */\nexport class GitAuthenticationError extends SandboxError<GitAuthFailedContext> {\n constructor(errorResponse: ErrorResponse<GitAuthFailedContext>) {\n super(errorResponse);\n this.name = 'GitAuthenticationError';\n }\n\n // Type-safe accessor\n get repository() {\n return this.context.repository;\n }\n}\n\n/**\n * Error thrown when a git branch is not found\n */\nexport class GitBranchNotFoundError extends SandboxError<GitBranchNotFoundContext> {\n constructor(errorResponse: ErrorResponse<GitBranchNotFoundContext>) {\n super(errorResponse);\n this.name = 'GitBranchNotFoundError';\n }\n\n // Type-safe accessors\n get branch() {\n return this.context.branch;\n }\n get repository() {\n return this.context.repository;\n }\n}\n\n/**\n * Error thrown when a git network operation fails\n */\nexport class GitNetworkError extends SandboxError<GitErrorContext> {\n constructor(errorResponse: ErrorResponse<GitErrorContext>) {\n super(errorResponse);\n this.name = 'GitNetworkError';\n }\n\n // Type-safe accessors\n get repository() {\n return this.context.repository;\n }\n get branch() {\n return this.context.branch;\n }\n get targetDir() {\n return this.context.targetDir;\n }\n}\n\n/**\n * Error thrown when git clone fails\n */\nexport class GitCloneError extends SandboxError<GitErrorContext> {\n constructor(errorResponse: ErrorResponse<GitErrorContext>) {\n super(errorResponse);\n this.name = 'GitCloneError';\n }\n\n // Type-safe accessors\n get repository() {\n return this.context.repository;\n }\n get targetDir() {\n return this.context.targetDir;\n }\n get stderr() {\n return this.context.stderr;\n }\n get exitCode() {\n return this.context.exitCode;\n }\n}\n\n/**\n * Error thrown when git checkout fails\n */\nexport class GitCheckoutError extends SandboxError<GitErrorContext> {\n constructor(errorResponse: ErrorResponse<GitErrorContext>) {\n super(errorResponse);\n this.name = 'GitCheckoutError';\n }\n\n // Type-safe accessors\n get branch() {\n return this.context.branch;\n }\n get repository() {\n return this.context.repository;\n }\n get stderr() {\n return this.context.stderr;\n }\n}\n\n/**\n * Error thrown when a git URL is invalid\n */\nexport class InvalidGitUrlError extends SandboxError<ValidationFailedContext> {\n constructor(errorResponse: ErrorResponse<ValidationFailedContext>) {\n super(errorResponse);\n this.name = 'InvalidGitUrlError';\n }\n\n // Type-safe accessor\n get validationErrors() {\n return this.context.validationErrors;\n }\n}\n\n/**\n * Generic git operation error\n */\nexport class GitError extends SandboxError<GitErrorContext> {\n constructor(errorResponse: ErrorResponse<GitErrorContext>) {\n super(errorResponse);\n this.name = 'GitError';\n }\n\n // Type-safe accessors\n get repository() {\n return this.context.repository;\n }\n get branch() {\n return this.context.branch;\n }\n get targetDir() {\n return this.context.targetDir;\n }\n get stderr() {\n return this.context.stderr;\n }\n get exitCode() {\n return this.context.exitCode;\n }\n}\n\n// ============================================================================\n// Code Interpreter Errors\n// ============================================================================\n\n/**\n * Error thrown when interpreter is not ready\n */\nexport class InterpreterNotReadyError extends SandboxError<InterpreterNotReadyContext> {\n constructor(errorResponse: ErrorResponse<InterpreterNotReadyContext>) {\n super(errorResponse);\n this.name = 'InterpreterNotReadyError';\n }\n\n // Type-safe accessors\n get retryAfter() {\n return this.context.retryAfter;\n }\n get progress() {\n return this.context.progress;\n }\n}\n\n/**\n * Error thrown when a context is not found\n */\nexport class ContextNotFoundError extends SandboxError<ContextNotFoundContext> {\n constructor(errorResponse: ErrorResponse<ContextNotFoundContext>) {\n super(errorResponse);\n this.name = 'ContextNotFoundError';\n }\n\n // Type-safe accessor\n get contextId() {\n return this.context.contextId;\n }\n}\n\n/**\n * Error thrown when code execution fails\n */\nexport class CodeExecutionError extends SandboxError<CodeExecutionContext> {\n constructor(errorResponse: ErrorResponse<CodeExecutionContext>) {\n super(errorResponse);\n this.name = 'CodeExecutionError';\n }\n\n // Type-safe accessors\n get contextId() {\n return this.context.contextId;\n }\n get ename() {\n return this.context.ename;\n }\n get evalue() {\n return this.context.evalue;\n }\n get traceback() {\n return this.context.traceback;\n }\n}\n\n// ============================================================================\n// Validation Errors\n// ============================================================================\n\n/**\n * Error thrown when validation fails\n */\nexport class ValidationFailedError extends SandboxError<ValidationFailedContext> {\n constructor(errorResponse: ErrorResponse<ValidationFailedContext>) {\n super(errorResponse);\n this.name = 'ValidationFailedError';\n }\n\n // Type-safe accessor\n get validationErrors() {\n return this.context.validationErrors;\n }\n}\n\n// ============================================================================\n// Process Readiness Errors\n// ============================================================================\n\n/**\n * Error thrown when a process does not become ready within the timeout period\n */\nexport class ProcessReadyTimeoutError extends SandboxError<ProcessReadyTimeoutContext> {\n constructor(errorResponse: ErrorResponse<ProcessReadyTimeoutContext>) {\n super(errorResponse);\n this.name = 'ProcessReadyTimeoutError';\n }\n\n // Type-safe accessors\n get processId() {\n return this.context.processId;\n }\n get command() {\n return this.context.command;\n }\n get condition() {\n return this.context.condition;\n }\n get timeout() {\n return this.context.timeout;\n }\n}\n\n/**\n * Error thrown when a process exits before becoming ready\n */\nexport class ProcessExitedBeforeReadyError extends SandboxError<ProcessExitedBeforeReadyContext> {\n constructor(errorResponse: ErrorResponse<ProcessExitedBeforeReadyContext>) {\n super(errorResponse);\n this.name = 'ProcessExitedBeforeReadyError';\n }\n\n // Type-safe accessors\n get processId() {\n return this.context.processId;\n }\n get command() {\n return this.context.command;\n }\n get condition() {\n return this.context.condition;\n }\n get exitCode() {\n return this.context.exitCode;\n }\n}\n\n// ============================================================================\n// Backup Errors\n// ============================================================================\n\n/**\n * Error thrown when a backup is not found in R2\n */\nexport class BackupNotFoundError extends SandboxError<BackupNotFoundContext> {\n constructor(errorResponse: ErrorResponse<BackupNotFoundContext>) {\n super(errorResponse);\n this.name = 'BackupNotFoundError';\n }\n\n get backupId() {\n return this.context.backupId;\n }\n}\n\n/**\n * Error thrown when a backup has expired (past its TTL)\n */\nexport class BackupExpiredError extends SandboxError<BackupExpiredContext> {\n constructor(errorResponse: ErrorResponse<BackupExpiredContext>) {\n super(errorResponse);\n this.name = 'BackupExpiredError';\n }\n\n get backupId() {\n return this.context.backupId;\n }\n get expiredAt() {\n return this.context.expiredAt;\n }\n}\n\n/**\n * Error thrown when backup configuration or inputs are invalid\n */\nexport class InvalidBackupConfigError extends SandboxError<InvalidBackupConfigContext> {\n constructor(errorResponse: ErrorResponse<InvalidBackupConfigContext>) {\n super(errorResponse);\n this.name = 'InvalidBackupConfigError';\n }\n\n get reason() {\n return this.context.reason;\n }\n}\n\n/**\n * Error thrown when backup creation fails\n */\nexport class BackupCreateError extends SandboxError<BackupCreateContext> {\n constructor(errorResponse: ErrorResponse<BackupCreateContext>) {\n super(errorResponse);\n this.name = 'BackupCreateError';\n }\n\n get dir() {\n return this.context.dir;\n }\n get backupId() {\n return this.context.backupId;\n }\n}\n\n/**\n * Error thrown when backup restoration fails\n */\nexport class BackupRestoreError extends SandboxError<BackupRestoreContext> {\n constructor(errorResponse: ErrorResponse<BackupRestoreContext>) {\n super(errorResponse);\n this.name = 'BackupRestoreError';\n }\n\n get dir() {\n return this.context.dir;\n }\n get backupId() {\n return this.context.backupId;\n }\n}\n\n// ============================================================================\n// Desktop Errors\n// ============================================================================\n\nexport class DesktopNotStartedError extends SandboxError<DesktopErrorContext> {\n constructor(errorResponse: ErrorResponse<DesktopErrorContext>) {\n super(errorResponse);\n this.name = 'DesktopNotStartedError';\n }\n}\n\nexport class DesktopStartFailedError extends SandboxError<DesktopErrorContext> {\n constructor(errorResponse: ErrorResponse<DesktopErrorContext>) {\n super(errorResponse);\n this.name = 'DesktopStartFailedError';\n }\n}\n\nexport class DesktopUnavailableError extends SandboxError<DesktopErrorContext> {\n constructor(errorResponse: ErrorResponse<DesktopErrorContext>) {\n super(errorResponse);\n this.name = 'DesktopUnavailableError';\n }\n}\n\nexport class DesktopProcessCrashedError extends SandboxError<DesktopErrorContext> {\n constructor(errorResponse: ErrorResponse<DesktopErrorContext>) {\n super(errorResponse);\n this.name = 'DesktopProcessCrashedError';\n }\n}\n\nexport class DesktopInvalidOptionsError extends SandboxError<DesktopErrorContext> {\n constructor(errorResponse: ErrorResponse<DesktopErrorContext>) {\n super(errorResponse);\n this.name = 'DesktopInvalidOptionsError';\n }\n}\n\nexport class DesktopInvalidCoordinatesError extends SandboxError<DesktopCoordinateErrorContext> {\n constructor(errorResponse: ErrorResponse<DesktopCoordinateErrorContext>) {\n super(errorResponse);\n this.name = 'DesktopInvalidCoordinatesError';\n }\n}\n","/**\n * Error adapter that converts ErrorResponse to appropriate Error class\n *\n * Simple switch statement - we trust the container sends correct context\n * No validation overhead since we control both sides\n */\n\nimport type {\n BackupCreateContext,\n BackupExpiredContext,\n BackupNotFoundContext,\n BackupRestoreContext,\n CodeExecutionContext,\n CommandErrorContext,\n CommandNotFoundContext,\n ContextNotFoundContext,\n DesktopCoordinateErrorContext,\n DesktopErrorContext,\n ErrorResponse,\n FileExistsContext,\n FileNotFoundContext,\n FileSystemContext,\n FileTooLargeContext,\n GitAuthFailedContext,\n GitBranchNotFoundContext,\n GitErrorContext,\n GitRepositoryNotFoundContext,\n InternalErrorContext,\n InterpreterNotReadyContext,\n InvalidBackupConfigContext,\n InvalidPortContext,\n PortAlreadyExposedContext,\n PortErrorContext,\n PortNotExposedContext,\n ProcessErrorContext,\n ProcessNotFoundContext,\n SessionAlreadyExistsContext,\n SessionDestroyedContext,\n SessionTerminatedContext,\n ValidationFailedContext\n} from '@repo/shared/errors';\nimport { ErrorCode } from '@repo/shared/errors';\n\nimport {\n BackupCreateError,\n BackupExpiredError,\n BackupNotFoundError,\n BackupRestoreError,\n CodeExecutionError,\n CommandError,\n CommandNotFoundError,\n ContextNotFoundError,\n CustomDomainRequiredError,\n DesktopInvalidCoordinatesError,\n DesktopInvalidOptionsError,\n DesktopNotStartedError,\n DesktopProcessCrashedError,\n DesktopStartFailedError,\n DesktopUnavailableError,\n FileExistsError,\n FileNotFoundError,\n FileSystemError,\n FileTooLargeError,\n GitAuthenticationError,\n GitBranchNotFoundError,\n GitCheckoutError,\n GitCloneError,\n GitError,\n GitNetworkError,\n GitRepositoryNotFoundError,\n InterpreterNotReadyError,\n InvalidBackupConfigError,\n InvalidGitUrlError,\n InvalidPortError,\n PermissionDeniedError,\n PortAlreadyExposedError,\n PortError,\n PortInUseError,\n PortNotExposedError,\n ProcessError,\n ProcessNotFoundError,\n SandboxError,\n ServiceNotRespondingError,\n SessionAlreadyExistsError,\n SessionDestroyedError,\n SessionTerminatedError,\n ValidationFailedError\n} from './classes';\n\n/**\n * Convert ErrorResponse to appropriate Error class\n * Simple switch statement - we trust the container sends correct context\n */\nexport function createErrorFromResponse(errorResponse: ErrorResponse): Error {\n // We trust the container sends correct context, use type assertions\n switch (errorResponse.code) {\n // File System Errors\n case ErrorCode.FILE_NOT_FOUND:\n return new FileNotFoundError(\n errorResponse as unknown as ErrorResponse<FileNotFoundContext>\n );\n\n case ErrorCode.FILE_EXISTS:\n return new FileExistsError(\n errorResponse as unknown as ErrorResponse<FileExistsContext>\n );\n\n case ErrorCode.FILE_TOO_LARGE:\n return new FileTooLargeError(\n errorResponse as unknown as ErrorResponse<FileTooLargeContext>\n );\n\n case ErrorCode.PERMISSION_DENIED:\n return new PermissionDeniedError(\n errorResponse as unknown as ErrorResponse<FileSystemContext>\n );\n\n case ErrorCode.IS_DIRECTORY:\n case ErrorCode.NOT_DIRECTORY:\n case ErrorCode.NO_SPACE:\n case ErrorCode.TOO_MANY_FILES:\n case ErrorCode.RESOURCE_BUSY:\n case ErrorCode.READ_ONLY:\n case ErrorCode.NAME_TOO_LONG:\n case ErrorCode.TOO_MANY_LINKS:\n case ErrorCode.FILESYSTEM_ERROR:\n return new FileSystemError(\n errorResponse as unknown as ErrorResponse<FileSystemContext>\n );\n\n // Command Errors\n case ErrorCode.COMMAND_NOT_FOUND:\n return new CommandNotFoundError(\n errorResponse as unknown as ErrorResponse<CommandNotFoundContext>\n );\n\n case ErrorCode.COMMAND_PERMISSION_DENIED:\n case ErrorCode.COMMAND_EXECUTION_ERROR:\n case ErrorCode.INVALID_COMMAND:\n case ErrorCode.STREAM_START_ERROR:\n return new CommandError(\n errorResponse as unknown as ErrorResponse<CommandErrorContext>\n );\n\n // Process Errors\n case ErrorCode.PROCESS_NOT_FOUND:\n return new ProcessNotFoundError(\n errorResponse as unknown as ErrorResponse<ProcessNotFoundContext>\n );\n\n case ErrorCode.PROCESS_PERMISSION_DENIED:\n case ErrorCode.PROCESS_ERROR:\n return new ProcessError(\n errorResponse as unknown as ErrorResponse<ProcessErrorContext>\n );\n\n // Session Errors\n case ErrorCode.SESSION_ALREADY_EXISTS:\n return new SessionAlreadyExistsError(\n errorResponse as unknown as ErrorResponse<SessionAlreadyExistsContext>\n );\n\n case ErrorCode.SESSION_DESTROYED:\n return new SessionDestroyedError(\n errorResponse as unknown as ErrorResponse<SessionDestroyedContext>\n );\n\n case ErrorCode.SESSION_TERMINATED:\n return new SessionTerminatedError(\n errorResponse as unknown as ErrorResponse<SessionTerminatedContext>\n );\n\n // Port Errors\n case ErrorCode.PORT_ALREADY_EXPOSED:\n return new PortAlreadyExposedError(\n errorResponse as unknown as ErrorResponse<PortAlreadyExposedContext>\n );\n\n case ErrorCode.PORT_NOT_EXPOSED:\n return new PortNotExposedError(\n errorResponse as unknown as ErrorResponse<PortNotExposedContext>\n );\n\n case ErrorCode.INVALID_PORT_NUMBER:\n case ErrorCode.INVALID_PORT:\n return new InvalidPortError(\n errorResponse as unknown as ErrorResponse<InvalidPortContext>\n );\n\n case ErrorCode.SERVICE_NOT_RESPONDING:\n return new ServiceNotRespondingError(\n errorResponse as unknown as ErrorResponse<PortErrorContext>\n );\n\n case ErrorCode.PORT_IN_USE:\n return new PortInUseError(\n errorResponse as unknown as ErrorResponse<PortErrorContext>\n );\n\n case ErrorCode.PORT_OPERATION_ERROR:\n return new PortError(\n errorResponse as unknown as ErrorResponse<PortErrorContext>\n );\n\n case ErrorCode.CUSTOM_DOMAIN_REQUIRED:\n return new CustomDomainRequiredError(\n errorResponse as unknown as ErrorResponse<InternalErrorContext>\n );\n\n // Git Errors\n case ErrorCode.GIT_REPOSITORY_NOT_FOUND:\n return new GitRepositoryNotFoundError(\n errorResponse as unknown as ErrorResponse<GitRepositoryNotFoundContext>\n );\n\n case ErrorCode.GIT_AUTH_FAILED:\n return new GitAuthenticationError(\n errorResponse as unknown as ErrorResponse<GitAuthFailedContext>\n );\n\n case ErrorCode.GIT_BRANCH_NOT_FOUND:\n return new GitBranchNotFoundError(\n errorResponse as unknown as ErrorResponse<GitBranchNotFoundContext>\n );\n\n case ErrorCode.GIT_NETWORK_ERROR:\n return new GitNetworkError(\n errorResponse as unknown as ErrorResponse<GitErrorContext>\n );\n\n case ErrorCode.GIT_CLONE_FAILED:\n return new GitCloneError(\n errorResponse as unknown as ErrorResponse<GitErrorContext>\n );\n\n case ErrorCode.GIT_CHECKOUT_FAILED:\n return new GitCheckoutError(\n errorResponse as unknown as ErrorResponse<GitErrorContext>\n );\n\n case ErrorCode.INVALID_GIT_URL:\n return new InvalidGitUrlError(\n errorResponse as unknown as ErrorResponse<ValidationFailedContext>\n );\n\n case ErrorCode.GIT_OPERATION_FAILED:\n return new GitError(\n errorResponse as unknown as ErrorResponse<GitErrorContext>\n );\n\n // Backup Errors\n case ErrorCode.BACKUP_NOT_FOUND:\n return new BackupNotFoundError(\n errorResponse as unknown as ErrorResponse<BackupNotFoundContext>\n );\n\n case ErrorCode.BACKUP_EXPIRED:\n return new BackupExpiredError(\n errorResponse as unknown as ErrorResponse<BackupExpiredContext>\n );\n\n case ErrorCode.INVALID_BACKUP_CONFIG:\n return new InvalidBackupConfigError(\n errorResponse as unknown as ErrorResponse<InvalidBackupConfigContext>\n );\n\n case ErrorCode.BACKUP_CREATE_FAILED:\n return new BackupCreateError(\n errorResponse as unknown as ErrorResponse<BackupCreateContext>\n );\n\n case ErrorCode.BACKUP_RESTORE_FAILED:\n return new BackupRestoreError(\n errorResponse as unknown as ErrorResponse<BackupRestoreContext>\n );\n\n // Code Interpreter Errors\n case ErrorCode.INTERPRETER_NOT_READY:\n return new InterpreterNotReadyError(\n errorResponse as unknown as ErrorResponse<InterpreterNotReadyContext>\n );\n\n case ErrorCode.CONTEXT_NOT_FOUND:\n return new ContextNotFoundError(\n errorResponse as unknown as ErrorResponse<ContextNotFoundContext>\n );\n\n case ErrorCode.CODE_EXECUTION_ERROR:\n return new CodeExecutionError(\n errorResponse as unknown as ErrorResponse<CodeExecutionContext>\n );\n\n // Desktop Errors\n case ErrorCode.DESKTOP_NOT_STARTED:\n return new DesktopNotStartedError(\n errorResponse as unknown as ErrorResponse<DesktopErrorContext>\n );\n case ErrorCode.DESKTOP_START_FAILED:\n return new DesktopStartFailedError(\n errorResponse as unknown as ErrorResponse<DesktopErrorContext>\n );\n case ErrorCode.DESKTOP_UNAVAILABLE:\n return new DesktopUnavailableError(\n errorResponse as unknown as ErrorResponse<DesktopErrorContext>\n );\n case ErrorCode.DESKTOP_PROCESS_CRASHED:\n return new DesktopProcessCrashedError(\n errorResponse as unknown as ErrorResponse<DesktopErrorContext>\n );\n case ErrorCode.DESKTOP_INVALID_OPTIONS:\n return new DesktopInvalidOptionsError(\n errorResponse as unknown as ErrorResponse<DesktopErrorContext>\n );\n case ErrorCode.DESKTOP_INVALID_COORDINATES:\n return new DesktopInvalidCoordinatesError(\n errorResponse as unknown as ErrorResponse<DesktopCoordinateErrorContext>\n );\n\n // Validation Errors\n case ErrorCode.VALIDATION_FAILED:\n return new ValidationFailedError(\n errorResponse as unknown as ErrorResponse<ValidationFailedContext>\n );\n\n // Generic Errors\n case ErrorCode.INVALID_JSON_RESPONSE:\n case ErrorCode.UNKNOWN_ERROR:\n case ErrorCode.INTERNAL_ERROR:\n return new SandboxError(\n errorResponse as unknown as ErrorResponse<InternalErrorContext>\n );\n\n default:\n // Fallback for unknown error codes\n return new SandboxError(errorResponse);\n }\n}\n","import type { Logger } from '@repo/shared';\nimport { createNoOpLogger } from '@repo/shared';\nimport type {\n ITransport,\n TransportConfig,\n TransportMode,\n TransportRequestInit\n} from './types';\n\n/**\n * Container startup retry configuration\n */\nconst DEFAULT_RETRY_TIMEOUT_MS = 120_000; // 2 minutes total retry budget\nconst MIN_TIME_FOR_RETRY_MS = 15_000; // Need at least 15s remaining to retry\n\n/**\n * Abstract base transport with shared retry logic\n *\n * Handles 503 retry for container startup - shared by all transports.\n * Subclasses implement the transport-specific fetch and stream logic.\n */\nexport abstract class BaseTransport implements ITransport {\n protected config: TransportConfig;\n protected logger: Logger;\n private retryTimeoutMs: number;\n\n constructor(config: TransportConfig) {\n this.config = config;\n this.logger = config.logger ?? createNoOpLogger();\n this.retryTimeoutMs = config.retryTimeoutMs ?? DEFAULT_RETRY_TIMEOUT_MS;\n }\n\n abstract getMode(): TransportMode;\n abstract connect(): Promise<void>;\n abstract disconnect(): void;\n abstract isConnected(): boolean;\n\n setRetryTimeoutMs(ms: number): void {\n this.retryTimeoutMs = ms;\n }\n\n protected getRetryTimeoutMs(): number {\n return this.retryTimeoutMs;\n }\n\n /**\n * Fetch with automatic retry for 503 (container starting)\n *\n * This is the primary entry point for making requests. It wraps the\n * transport-specific doFetch() with retry logic for container startup.\n */\n async fetch(path: string, options?: TransportRequestInit): Promise<Response> {\n const startTime = Date.now();\n let attempt = 0;\n\n while (true) {\n const response = await this.doFetch(path, options);\n\n // Check for retryable 503 (container starting)\n if (response.status === 503) {\n const elapsed = Date.now() - startTime;\n const remaining = this.retryTimeoutMs - elapsed;\n\n if (remaining > MIN_TIME_FOR_RETRY_MS) {\n const delay = Math.min(3000 * 2 ** attempt, 30000);\n\n this.logger.info('Container not ready, retrying', {\n status: response.status,\n attempt: attempt + 1,\n delayMs: delay,\n remainingSec: Math.floor(remaining / 1000),\n mode: this.getMode()\n });\n\n await this.sleep(delay);\n attempt++;\n continue;\n }\n\n this.logger.error(\n 'Container failed to become ready',\n new Error(\n `Failed after ${attempt + 1} attempts over ${Math.floor(elapsed / 1000)}s`\n )\n );\n }\n\n return response;\n }\n }\n\n /**\n * Transport-specific fetch implementation (no retry)\n * Subclasses implement the actual HTTP or WebSocket fetch.\n */\n protected abstract doFetch(\n path: string,\n options?: TransportRequestInit\n ): Promise<Response>;\n\n /**\n * Transport-specific stream implementation\n * Subclasses implement HTTP SSE or WebSocket streaming.\n */\n abstract fetchStream(\n path: string,\n body?: unknown,\n method?: 'GET' | 'POST',\n headers?: Record<string, string>\n ): Promise<ReadableStream<Uint8Array>>;\n\n // ---------------------------------------------------------------------------\n // Shared HTTP primitives — used by HttpTransport as its primary path and by\n // WebSocketTransport as a fallback during connection establishment.\n // ---------------------------------------------------------------------------\n\n /**\n * Build a URL targeting the container's HTTP server.\n */\n protected buildContainerUrl(path: string): string {\n if (this.config.stub) {\n return `http://localhost:${this.config.port || 3000}${path}`;\n }\n const baseUrl =\n this.config.baseUrl ?? `http://localhost:${this.config.port || 3000}`;\n return `${baseUrl}${path}`;\n }\n\n /**\n * Single HTTP request to the container — no WebSocket, no 503 retry.\n */\n protected httpFetch(path: string, options?: RequestInit): Promise<Response> {\n const url = this.buildContainerUrl(path);\n if (this.config.stub) {\n return this.config.stub.containerFetch(\n url,\n options || {},\n this.config.port\n );\n }\n return globalThis.fetch(url, options);\n }\n\n /**\n * Streaming HTTP request to the container — no WebSocket, no 503 retry.\n */\n protected async httpFetchStream(\n path: string,\n body?: unknown,\n method: 'GET' | 'POST' = 'POST',\n headers?: Record<string, string>\n ): Promise<ReadableStream<Uint8Array>> {\n const url = this.buildContainerUrl(path);\n const init: RequestInit = {\n method,\n headers:\n body && method === 'POST'\n ? { ...headers, 'Content-Type': 'application/json' }\n : headers,\n body: body && method === 'POST' ? JSON.stringify(body) : undefined\n };\n\n let response: Response;\n if (this.config.stub) {\n response = await this.config.stub.containerFetch(\n url,\n init,\n this.config.port\n );\n } else {\n response = await globalThis.fetch(url, init);\n }\n\n if (!response.ok) {\n const errorBody = await response.text();\n throw new Error(`HTTP error! status: ${response.status} - ${errorBody}`);\n }\n if (!response.body) {\n throw new Error('No response body for streaming');\n }\n return response.body;\n }\n\n /**\n * Sleep utility for retry delays\n */\n protected sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n","import { BaseTransport } from './base-transport';\nimport type { TransportMode, TransportRequestInit } from './types';\n\n/**\n * HTTP transport implementation\n *\n * Uses standard fetch API for communication with the container.\n * HTTP is stateless, so connect/disconnect are no-ops.\n *\n * All HTTP request logic lives in {@link BaseTransport.httpFetch} and\n * {@link BaseTransport.httpFetchStream}; this subclass simply wires\n * the abstract `doFetch` / `fetchStream` hooks to those shared helpers.\n */\nexport class HttpTransport extends BaseTransport {\n getMode(): TransportMode {\n return 'http';\n }\n\n async connect(): Promise<void> {\n // No-op for HTTP - stateless protocol\n }\n\n disconnect(): void {\n // No-op for HTTP - stateless protocol\n }\n\n isConnected(): boolean {\n return true; // HTTP is always \"connected\"\n }\n\n protected async doFetch(\n path: string,\n options?: TransportRequestInit\n ): Promise<Response> {\n return this.httpFetch(path, options);\n }\n\n async fetchStream(\n path: string,\n body?: unknown,\n method: 'GET' | 'POST' = 'POST',\n headers?: Record<string, string>\n ): Promise<ReadableStream<Uint8Array>> {\n return this.httpFetchStream(path, body, method, headers);\n }\n}\n","import {\n generateRequestId,\n isWSError,\n isWSResponse,\n isWSStreamChunk,\n type WSMethod,\n type WSRequest,\n type WSResponse,\n type WSServerMessage,\n type WSStreamChunk\n} from '@repo/shared';\nimport { BaseTransport } from './base-transport';\nimport type {\n TransportConfig,\n TransportMode,\n TransportRequestInit\n} from './types';\n\n/**\n * Default timeout values (all in milliseconds)\n */\nconst DEFAULT_REQUEST_TIMEOUT_MS = 120_000; // 2 minutes for non-streaming requests\nconst DEFAULT_STREAM_IDLE_TIMEOUT_MS = 300_000; // 5 minutes idle timeout for streams\nconst DEFAULT_CONNECT_TIMEOUT_MS = 30_000; // 30 seconds for WebSocket connection\nconst DEFAULT_IDLE_DISCONNECT_MS = 1_000; // Close idle control socket promptly\nconst MIN_TIME_FOR_CONNECT_RETRY_MS = 15_000; // Need 15s remaining to retry\n\n/**\n * Pending request tracker for response matching\n */\ninterface PendingRequest {\n resolve: (response: WSResponse) => void;\n reject: (error: Error) => void;\n streamController?: ReadableStreamDefaultController<Uint8Array>;\n bufferedChunks?: Uint8Array[];\n isStreaming: boolean;\n timeoutId?: ReturnType<typeof setTimeout>;\n /** Called when first stream chunk is received (for deferred stream return) */\n onFirstChunk?: () => void;\n}\n\n/**\n * WebSocket transport state\n */\ntype WSTransportState = 'disconnected' | 'connecting' | 'connected' | 'error';\n\n/**\n * WebSocket transport implementation\n *\n * Multiplexes HTTP-like requests over a single WebSocket connection.\n * Useful when running inside Workers/DO where sub-request limits apply.\n */\nexport class WebSocketTransport extends BaseTransport {\n private ws: WebSocket | null = null;\n private state: WSTransportState = 'disconnected';\n private pendingRequests: Map<string, PendingRequest> = new Map();\n private connectPromise: Promise<void> | null = null;\n private idleDisconnectTimer: ReturnType<typeof setTimeout> | null = null;\n\n // Bound event handlers for proper add/remove\n private boundHandleMessage: (event: MessageEvent) => void;\n private boundHandleClose: (event: CloseEvent) => void;\n\n constructor(config: TransportConfig) {\n super(config);\n\n if (!config.wsUrl) {\n throw new Error('wsUrl is required for WebSocket transport');\n }\n\n // Bind handlers once in constructor\n this.boundHandleMessage = this.handleMessage.bind(this);\n this.boundHandleClose = this.handleClose.bind(this);\n }\n\n getMode(): TransportMode {\n return 'websocket';\n }\n\n /**\n * Check if WebSocket is connected\n */\n isConnected(): boolean {\n return this.state === 'connected' && this.ws?.readyState === WebSocket.OPEN;\n }\n\n /**\n * Connect to the WebSocket server\n *\n * The connection promise is assigned synchronously so concurrent\n * callers share the same connection attempt.\n */\n async connect(): Promise<void> {\n this.clearIdleDisconnectTimer();\n\n // Already connected\n if (this.isConnected()) {\n return;\n }\n\n // Connection in progress - wait for it\n if (this.connectPromise) {\n return this.connectPromise;\n }\n\n // Assign synchronously so concurrent callers await the same promise\n this.connectPromise = this.doConnect();\n\n try {\n await this.connectPromise;\n } finally {\n // Clear promise AFTER await so concurrent callers share the same\n // connection attempt, but future reconnects can start a new one.\n this.connectPromise = null;\n }\n }\n\n /**\n * Disconnect from the WebSocket server\n */\n disconnect(): void {\n this.cleanup();\n }\n\n /**\n * Whether a WebSocket connection is currently being established.\n *\n * When true, awaiting `connectPromise` from a nested call would deadlock:\n * the outer `connectViaFetch → stub.fetch → containerFetch →\n * startAndWaitForPorts → blockConcurrencyWhile(onStart)` chain may call\n * back into the SDK (e.g. `exec()`), which would await the same\n * `connectPromise` that cannot resolve until `onStart` returns.\n *\n * Callers use this to fall back to a direct HTTP request, which is safe\n * because `startAndWaitForPorts()` calls `setHealthy()` before invoking\n * `onStart()`, so `containerFetch()` routes directly to the container.\n */\n private isWebSocketConnecting(): boolean {\n return this.state === 'connecting';\n }\n\n /**\n * Transport-specific fetch implementation.\n * Converts WebSocket response to standard Response object.\n *\n * Falls back to HTTP while a WebSocket connection is being established\n * to avoid the re-entrant deadlock described in `isWebSocketConnecting()`.\n */\n protected async doFetch(\n path: string,\n options?: TransportRequestInit\n ): Promise<Response> {\n if (this.isWebSocketConnecting()) {\n return this.httpFetch(path, options);\n }\n\n await this.connect();\n\n const method = (options?.method || 'GET') as WSMethod;\n const body = this.parseBody(options?.body);\n const headers = this.normalizeHeaders(options?.headers);\n\n const result = await this.request(\n method,\n path,\n body,\n headers,\n options?.requestTimeoutMs\n );\n\n return new Response(JSON.stringify(result.body), {\n status: result.status,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n /**\n * Streaming fetch implementation.\n *\n * Delegates to `requestStream()`, which applies the re-entrancy guard.\n */\n async fetchStream(\n path: string,\n body?: unknown,\n method: 'GET' | 'POST' = 'POST',\n headers?: Record<string, string>\n ): Promise<ReadableStream<Uint8Array>> {\n return this.requestStream(method, path, body, headers);\n }\n\n /**\n * Parse request body from RequestInit\n */\n private parseBody(body: RequestInit['body']): unknown {\n if (!body) {\n return undefined;\n }\n\n if (typeof body === 'string') {\n try {\n return JSON.parse(body);\n } catch (error) {\n throw new Error(\n `Request body must be valid JSON: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n throw new Error(\n `WebSocket transport only supports string bodies. Got: ${typeof body}`\n );\n }\n\n /**\n * Normalize RequestInit headers into a plain object for WSRequest.\n */\n private normalizeHeaders(\n headers?: HeadersInit\n ): Record<string, string> | undefined {\n if (!headers) {\n return undefined;\n }\n\n const normalized: Record<string, string> = {};\n new Headers(headers).forEach((value, key) => {\n normalized[key] = value;\n });\n\n return Object.keys(normalized).length > 0 ? normalized : undefined;\n }\n\n /**\n * Internal connection logic\n */\n private async doConnect(): Promise<void> {\n this.state = 'connecting';\n // Use fetch-based WebSocket for DO context (Workers style)\n if (this.config.stub) {\n await this.connectViaFetch();\n } else {\n // Use standard WebSocket for browser/Node\n await this.connectViaWebSocket();\n }\n }\n\n private async fetchUpgradeWithRetry(\n attemptUpgrade: () => Promise<Response>\n ): Promise<Response> {\n const retryTimeoutMs = this.getRetryTimeoutMs();\n const startTime = Date.now();\n let attempt = 0;\n\n while (true) {\n const response = await attemptUpgrade();\n\n if (response.status !== 503) {\n return response;\n }\n\n const elapsed = Date.now() - startTime;\n const remaining = retryTimeoutMs - elapsed;\n\n if (remaining <= MIN_TIME_FOR_CONNECT_RETRY_MS) {\n return response;\n }\n\n const delay = Math.min(3000 * 2 ** attempt, 30000);\n\n this.logger.info('WebSocket container not ready, retrying', {\n status: response.status,\n attempt: attempt + 1,\n delayMs: delay,\n remainingSec: Math.floor(remaining / 1000)\n });\n\n await this.sleep(delay);\n attempt++;\n }\n }\n\n /**\n * Connect using fetch-based WebSocket (Cloudflare Workers style)\n * This is required when running inside a Durable Object.\n *\n * Uses stub.fetch() which routes WebSocket upgrade requests through the\n * parent Container class that supports the WebSocket protocol.\n */\n private async connectViaFetch(): Promise<void> {\n const timeoutMs =\n this.config.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;\n\n try {\n // Build the WebSocket URL for the container\n const wsPath = new URL(this.config.wsUrl!).pathname;\n const httpUrl = `http://localhost:${this.config.port || 3000}${wsPath}`;\n\n const response = await this.fetchUpgradeWithRetry(() =>\n this.fetchUpgradeAttempt(httpUrl, timeoutMs)\n );\n\n // Check if upgrade was successful\n if (response.status !== 101) {\n throw new Error(\n `WebSocket upgrade failed: ${response.status} ${response.statusText}`\n );\n }\n\n // Get the WebSocket from the response (Workers-specific API)\n const ws = (response as unknown as { webSocket?: WebSocket }).webSocket;\n if (!ws) {\n throw new Error('No WebSocket in upgrade response');\n }\n\n // Accept the WebSocket connection (Workers-specific)\n (ws as unknown as { accept: () => void }).accept();\n\n this.ws = ws;\n this.state = 'connected';\n\n // Set up event handlers\n this.ws.addEventListener('close', this.boundHandleClose);\n this.ws.addEventListener('message', this.boundHandleMessage);\n\n this.logger.debug('WebSocket connected via fetch', {\n url: this.config.wsUrl\n });\n } catch (error) {\n this.state = 'error';\n this.logger.error(\n 'WebSocket fetch connection failed',\n error instanceof Error ? error : new Error(String(error))\n );\n throw error;\n }\n }\n\n private async fetchUpgradeAttempt(\n httpUrl: string,\n timeoutMs: number\n ): Promise<Response> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const request = new Request(httpUrl, {\n headers: {\n Upgrade: 'websocket',\n Connection: 'Upgrade'\n },\n signal: controller.signal\n });\n\n return await this.config.stub!.fetch(request);\n } finally {\n clearTimeout(timeout);\n }\n }\n\n /**\n * Connect using standard WebSocket API (browser/Node style)\n */\n private connectViaWebSocket(): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const timeoutMs =\n this.config.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;\n const timeout = setTimeout(() => {\n this.cleanup();\n reject(new Error(`WebSocket connection timeout after ${timeoutMs}ms`));\n }, timeoutMs);\n\n try {\n this.ws = new WebSocket(this.config.wsUrl!);\n\n // One-time open handler for connection\n const onOpen = () => {\n clearTimeout(timeout);\n this.ws?.removeEventListener('open', onOpen);\n this.ws?.removeEventListener('error', onConnectError);\n this.state = 'connected';\n this.logger.debug('WebSocket connected', { url: this.config.wsUrl });\n resolve();\n };\n\n // One-time error handler for connection\n const onConnectError = () => {\n clearTimeout(timeout);\n this.ws?.removeEventListener('open', onOpen);\n this.ws?.removeEventListener('error', onConnectError);\n this.state = 'error';\n this.logger.error(\n 'WebSocket error',\n new Error('WebSocket connection failed')\n );\n reject(new Error('WebSocket connection failed'));\n };\n\n this.ws.addEventListener('open', onOpen);\n this.ws.addEventListener('error', onConnectError);\n this.ws.addEventListener('close', this.boundHandleClose);\n this.ws.addEventListener('message', this.boundHandleMessage);\n } catch (error) {\n clearTimeout(timeout);\n this.state = 'error';\n reject(error);\n }\n });\n }\n\n /**\n * Send a request and wait for response.\n *\n * Only reachable from `doFetch()`, which already applies the re-entrancy\n * guard via `isWebSocketConnecting()`. The `connect()` call here handles\n * the case where the WebSocket was closed between `doFetch` and `request`\n * (idle disconnect).\n */\n private async request<T>(\n method: WSMethod,\n path: string,\n body?: unknown,\n headers?: Record<string, string>,\n requestTimeoutMs?: number\n ): Promise<{ status: number; body: T }> {\n await this.connect();\n this.clearIdleDisconnectTimer();\n\n const id = generateRequestId();\n const request: WSRequest = {\n type: 'request',\n id,\n method,\n path,\n body,\n headers\n };\n\n return new Promise((resolve, reject) => {\n const timeoutMs =\n requestTimeoutMs ??\n this.config.requestTimeoutMs ??\n DEFAULT_REQUEST_TIMEOUT_MS;\n const timeoutId = setTimeout(() => {\n this.pendingRequests.delete(id);\n this.scheduleIdleDisconnect();\n reject(\n new Error(`Request timeout after ${timeoutMs}ms: ${method} ${path}`)\n );\n }, timeoutMs);\n\n this.pendingRequests.set(id, {\n resolve: (response: WSResponse) => {\n clearTimeout(timeoutId);\n this.pendingRequests.delete(id);\n this.scheduleIdleDisconnect();\n resolve({ status: response.status, body: response.body as T });\n },\n reject: (error: Error) => {\n clearTimeout(timeoutId);\n this.pendingRequests.delete(id);\n this.scheduleIdleDisconnect();\n reject(error);\n },\n isStreaming: false,\n timeoutId\n });\n\n try {\n this.send(request);\n } catch (error) {\n clearTimeout(timeoutId);\n this.pendingRequests.delete(id);\n this.scheduleIdleDisconnect();\n reject(error instanceof Error ? error : new Error(String(error)));\n }\n });\n }\n\n /**\n * Send a streaming request and return a ReadableStream.\n *\n * The stream will receive data chunks as they arrive over the WebSocket.\n * Format matches SSE for compatibility with existing streaming code.\n *\n * This method waits for the first message before returning. If the server\n * responds with an error (non-streaming response), it throws immediately\n * rather than returning a stream that will error later.\n *\n * Uses an inactivity timeout instead of a total-duration timeout so that\n * long-running streams (e.g. execStream from an agent) stay alive as long\n * as data is flowing. The timer resets on every chunk or response message.\n *\n * Falls back to HTTP while a WebSocket connection is being established\n * to avoid the re-entrant deadlock described in `isWebSocketConnecting()`.\n */\n private async requestStream(\n method: WSMethod,\n path: string,\n body?: unknown,\n headers?: Record<string, string>\n ): Promise<ReadableStream<Uint8Array>> {\n if (this.isWebSocketConnecting()) {\n return this.httpFetchStream(\n path,\n body,\n method as 'GET' | 'POST',\n headers\n );\n }\n await this.connect();\n this.clearIdleDisconnectTimer();\n\n const id = generateRequestId();\n const request: WSRequest = {\n type: 'request',\n id,\n method,\n path,\n body,\n headers\n };\n\n const idleTimeoutMs =\n this.config.streamIdleTimeoutMs ?? DEFAULT_STREAM_IDLE_TIMEOUT_MS;\n\n // We need to wait for the first message to determine if this is a streaming\n // response or an immediate error. This prevents returning a stream that will\n // error on first read.\n return new Promise((resolveStream, rejectStream) => {\n let streamController: ReadableStreamDefaultController<Uint8Array>;\n let firstMessageReceived = false;\n\n const createIdleTimeout = (): ReturnType<typeof setTimeout> => {\n return setTimeout(() => {\n this.pendingRequests.delete(id);\n this.scheduleIdleDisconnect();\n const error = new Error(\n `Stream idle timeout after ${idleTimeoutMs}ms: ${method} ${path}`\n );\n if (firstMessageReceived) {\n try {\n streamController?.error(error);\n } catch {\n // Stream controller may already be closed/errored\n }\n } else {\n rejectStream(error);\n }\n }, idleTimeoutMs);\n };\n\n const timeoutId = createIdleTimeout();\n\n // Create the stream but don't return it until we get the first message\n const stream = new ReadableStream<Uint8Array>({\n start: (controller) => {\n streamController = controller;\n },\n cancel: () => {\n const pending = this.pendingRequests.get(id);\n if (pending?.timeoutId) {\n clearTimeout(pending.timeoutId);\n }\n\n // Best-effort server-side cancellation for active streaming requests.\n try {\n this.send({ type: 'cancel', id });\n } catch (error) {\n this.logger.debug('Failed to send stream cancel message', {\n id,\n error: error instanceof Error ? error.message : String(error)\n });\n }\n\n this.pendingRequests.delete(id);\n this.scheduleIdleDisconnect();\n }\n });\n\n this.pendingRequests.set(id, {\n resolve: (response: WSResponse) => {\n const pending = this.pendingRequests.get(id);\n if (pending?.timeoutId) {\n clearTimeout(pending.timeoutId);\n }\n this.pendingRequests.delete(id);\n this.scheduleIdleDisconnect();\n\n if (!firstMessageReceived) {\n // First message is a final response (not streaming) - this is an error case\n firstMessageReceived = true;\n if (response.status >= 400) {\n rejectStream(\n new Error(\n `Stream error: ${response.status} - ${JSON.stringify(response.body)}`\n )\n );\n } else {\n // Successful non-streaming response - close immediately\n streamController?.close();\n resolveStream(stream);\n }\n } else {\n // Stream was already returned, now closing\n if (response.status >= 400) {\n try {\n streamController?.error(\n new Error(\n `Stream error: ${response.status} - ${JSON.stringify(response.body)}`\n )\n );\n } catch {\n // Stream controller may already be closed/errored\n }\n } else {\n streamController?.close();\n }\n }\n },\n reject: (error: Error) => {\n const pending = this.pendingRequests.get(id);\n if (pending?.timeoutId) {\n clearTimeout(pending.timeoutId);\n }\n this.pendingRequests.delete(id);\n this.scheduleIdleDisconnect();\n if (firstMessageReceived) {\n try {\n streamController?.error(error);\n } catch {\n // Stream controller may already be closed/errored\n }\n } else {\n rejectStream(error);\n }\n },\n streamController: undefined, // Set after first chunk\n isStreaming: true,\n timeoutId,\n // Custom handler for first stream chunk\n onFirstChunk: () => {\n if (!firstMessageReceived) {\n firstMessageReceived = true;\n // Update the pending request with the actual controller\n const pending = this.pendingRequests.get(id);\n if (pending) {\n pending.streamController = streamController;\n // Flush any chunks that arrived before the controller was set\n if (pending.bufferedChunks) {\n try {\n for (const buffered of pending.bufferedChunks) {\n streamController.enqueue(buffered);\n }\n } catch (error) {\n this.logger.debug(\n 'Failed to flush buffered chunks, cleaning up',\n {\n id,\n error:\n error instanceof Error ? error.message : String(error)\n }\n );\n if (pending.timeoutId) {\n clearTimeout(pending.timeoutId);\n }\n this.pendingRequests.delete(id);\n this.scheduleIdleDisconnect();\n }\n pending.bufferedChunks = undefined;\n }\n }\n resolveStream(stream);\n }\n }\n });\n\n try {\n this.send(request);\n } catch (error) {\n clearTimeout(timeoutId);\n this.pendingRequests.delete(id);\n this.scheduleIdleDisconnect();\n rejectStream(error instanceof Error ? error : new Error(String(error)));\n }\n });\n }\n\n /**\n * Send a message over the WebSocket\n */\n private send(message: WSRequest | { type: 'cancel'; id: string }): void {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n throw new Error('WebSocket not connected');\n }\n\n this.ws.send(JSON.stringify(message));\n this.logger.debug('WebSocket sent', {\n id: message.id,\n type: message.type,\n method: message.type === 'request' ? message.method : undefined,\n path: message.type === 'request' ? message.path : undefined\n });\n }\n\n /**\n * Handle incoming WebSocket messages\n */\n private handleMessage(event: MessageEvent): void {\n try {\n const message = JSON.parse(event.data) as WSServerMessage;\n\n if (isWSResponse(message)) {\n this.handleResponse(message);\n } else if (isWSStreamChunk(message)) {\n this.handleStreamChunk(message);\n } else if (isWSError(message)) {\n this.handleError(message);\n } else {\n this.logger.warn('Unknown WebSocket message type', { message });\n }\n } catch (error) {\n this.logger.error(\n 'Failed to parse WebSocket message',\n error instanceof Error ? error : new Error(String(error))\n );\n }\n }\n\n /**\n * Handle a response message\n */\n private handleResponse(response: WSResponse): void {\n const pending = this.pendingRequests.get(response.id);\n if (!pending) {\n this.logger.warn('Received response for unknown request', {\n id: response.id\n });\n return;\n }\n\n this.logger.debug('WebSocket response', {\n id: response.id,\n status: response.status,\n done: response.done\n });\n\n // Only resolve when done is true\n if (response.done) {\n pending.resolve(response);\n }\n }\n\n /**\n * Handle a stream chunk message\n *\n * Resets the idle timeout on every chunk so that long-running streams\n * with continuous output are not killed by the inactivity timer.\n */\n private handleStreamChunk(chunk: WSStreamChunk): void {\n const pending = this.pendingRequests.get(chunk.id);\n if (!pending) {\n this.logger.warn('Received stream chunk for unknown request', {\n id: chunk.id\n });\n return;\n }\n\n // Call onFirstChunk FIRST to set up the stream controller\n if (pending.onFirstChunk) {\n pending.onFirstChunk();\n pending.onFirstChunk = undefined; // Only call once\n }\n\n // NOW reset the idle timeout - controller is guaranteed to exist\n if (pending.isStreaming) {\n this.resetStreamIdleTimeout(chunk.id, pending);\n }\n\n // Buffer chunks if controller not set yet (race between onFirstChunk and enqueue)\n if (!pending.streamController) {\n if (!pending.bufferedChunks) {\n pending.bufferedChunks = [];\n }\n const encoder = new TextEncoder();\n let sseData: string;\n if (chunk.event) {\n sseData = `event: ${chunk.event}\\ndata: ${chunk.data}\\n\\n`;\n } else {\n sseData = `data: ${chunk.data}\\n\\n`;\n }\n pending.bufferedChunks.push(encoder.encode(sseData));\n return;\n }\n\n // Convert to SSE format for compatibility with existing parsers\n const encoder = new TextEncoder();\n let sseData: string;\n if (chunk.event) {\n sseData = `event: ${chunk.event}\\ndata: ${chunk.data}\\n\\n`;\n } else {\n sseData = `data: ${chunk.data}\\n\\n`;\n }\n\n try {\n pending.streamController.enqueue(encoder.encode(sseData));\n } catch (error) {\n // Stream was cancelled or errored - clean up the pending request\n this.logger.debug('Failed to enqueue stream chunk, cleaning up', {\n id: chunk.id,\n error: error instanceof Error ? error.message : String(error)\n });\n // Clear timeout and remove from pending requests\n if (pending.timeoutId) {\n clearTimeout(pending.timeoutId);\n }\n this.pendingRequests.delete(chunk.id);\n this.scheduleIdleDisconnect();\n }\n }\n\n /**\n * Reset the idle timeout for a streaming request.\n * Called on every incoming chunk to keep the stream alive while data flows.\n */\n private resetStreamIdleTimeout(id: string, pending: PendingRequest): void {\n if (pending.timeoutId) {\n clearTimeout(pending.timeoutId);\n }\n\n const idleTimeoutMs =\n this.config.streamIdleTimeoutMs ?? DEFAULT_STREAM_IDLE_TIMEOUT_MS;\n pending.timeoutId = setTimeout(() => {\n this.pendingRequests.delete(id);\n this.scheduleIdleDisconnect();\n if (pending.streamController) {\n try {\n pending.streamController.error(\n new Error(`Stream idle timeout after ${idleTimeoutMs}ms`)\n );\n } catch {\n // Stream may already be closed/errored\n }\n }\n }, idleTimeoutMs);\n }\n\n /**\n * Handle an error message\n */\n private handleError(error: {\n id?: string;\n code: string;\n message: string;\n status: number;\n }): void {\n if (error.id) {\n const pending = this.pendingRequests.get(error.id);\n if (pending) {\n pending.reject(new Error(`${error.code}: ${error.message}`));\n return;\n }\n }\n\n // Global error - log it\n this.logger.error('WebSocket error message', new Error(error.message), {\n code: error.code,\n status: error.status\n });\n }\n\n /**\n * Handle WebSocket close\n */\n private handleClose(event: CloseEvent): void {\n this.state = 'disconnected';\n this.ws = null;\n this.connectPromise = null;\n\n const closeError = new Error(\n `WebSocket closed: ${event.code} ${event.reason || 'No reason'}`\n );\n\n // Reject all pending requests, clear their timeouts, and error their stream controllers\n for (const [, pending] of this.pendingRequests) {\n // Clear timeout first to prevent memory leak\n if (pending.timeoutId) {\n clearTimeout(pending.timeoutId);\n }\n // Error stream controller if it exists\n if (pending.streamController) {\n try {\n pending.streamController.error(closeError);\n } catch {\n // Stream may already be closed/errored\n }\n }\n pending.reject(closeError);\n }\n this.pendingRequests.clear();\n }\n\n /**\n * Cleanup resources\n */\n private cleanup(): void {\n this.clearIdleDisconnectTimer();\n\n if (this.ws) {\n this.ws.removeEventListener('close', this.boundHandleClose);\n this.ws.removeEventListener('message', this.boundHandleMessage);\n this.ws.close();\n this.ws = null;\n }\n this.state = 'disconnected';\n this.connectPromise = null;\n // Clear all pending request timeouts before clearing the map\n for (const pending of this.pendingRequests.values()) {\n if (pending.timeoutId) {\n clearTimeout(pending.timeoutId);\n }\n }\n this.pendingRequests.clear();\n }\n\n private scheduleIdleDisconnect(): void {\n if (!this.isConnected() || this.pendingRequests.size > 0) {\n return;\n }\n\n this.clearIdleDisconnectTimer();\n this.idleDisconnectTimer = setTimeout(() => {\n this.idleDisconnectTimer = null;\n\n if (this.pendingRequests.size === 0 && this.isConnected()) {\n this.logger.debug('Disconnecting idle WebSocket transport');\n this.cleanup();\n }\n }, DEFAULT_IDLE_DISCONNECT_MS);\n }\n\n private clearIdleDisconnectTimer(): void {\n if (this.idleDisconnectTimer) {\n clearTimeout(this.idleDisconnectTimer);\n this.idleDisconnectTimer = null;\n }\n }\n}\n","import { HttpTransport } from './http-transport';\nimport type { ITransport, TransportConfig, TransportMode } from './types';\nimport { WebSocketTransport } from './ws-transport';\n\n/**\n * Transport options with mode selection\n */\nexport interface TransportOptions extends TransportConfig {\n /** Transport mode */\n mode: TransportMode;\n}\n\n/**\n * Create a transport instance based on mode\n *\n * This is the primary API for creating transports. It handles\n * the selection of HTTP or WebSocket transport based on the mode.\n *\n * @example\n * ```typescript\n * // HTTP transport (default)\n * const http = createTransport({\n * mode: 'http',\n * baseUrl: 'http://localhost:3000'\n * });\n *\n * // WebSocket transport\n * const ws = createTransport({\n * mode: 'websocket',\n * wsUrl: 'ws://localhost:3000/ws'\n * });\n * ```\n */\nexport function createTransport(options: TransportOptions): ITransport {\n switch (options.mode) {\n case 'websocket':\n return new WebSocketTransport(options);\n\n default:\n return new HttpTransport(options);\n }\n}\n","import type { Logger } from '@repo/shared';\nimport { createNoOpLogger } from '@repo/shared';\nimport type { ErrorResponse as NewErrorResponse } from '../errors';\nimport { createErrorFromResponse, ErrorCode } from '../errors';\nimport {\n createTransport,\n type ITransport,\n type TransportRequestInit\n} from './transport';\nimport type { HttpClientOptions, ResponseHandler } from './types';\n\n/**\n * Abstract base class providing common HTTP/WebSocket functionality for all domain clients\n *\n * All requests go through the Transport abstraction layer, which handles:\n * - HTTP and WebSocket modes transparently\n * - Automatic retry for 503 errors (container starting)\n * - Streaming responses\n *\n * WebSocket mode is useful when running inside Workers/Durable Objects\n * where sub-request limits apply.\n */\nexport abstract class BaseHttpClient {\n protected options: HttpClientOptions;\n protected logger: Logger;\n protected transport: ITransport;\n\n constructor(options: HttpClientOptions = {}) {\n this.options = options;\n this.logger = options.logger ?? createNoOpLogger();\n\n // Always create a Transport - it handles both HTTP and WebSocket modes\n if (options.transport) {\n this.transport = options.transport;\n } else {\n const mode = options.transportMode ?? 'http';\n this.transport = createTransport({\n mode,\n baseUrl: options.baseUrl ?? 'http://localhost:3000',\n wsUrl: options.wsUrl,\n logger: this.logger,\n stub: options.stub,\n port: options.port,\n retryTimeoutMs: options.retryTimeoutMs\n });\n }\n }\n\n /**\n * Update the transport's 503 retry budget\n */\n setRetryTimeoutMs(ms: number): void {\n this.transport.setRetryTimeoutMs(ms);\n }\n\n /**\n * Check if using WebSocket transport\n */\n protected isWebSocketMode(): boolean {\n return this.transport.getMode() === 'websocket';\n }\n\n /**\n * Core fetch method - delegates to Transport which handles retry logic\n */\n protected async doFetch(\n path: string,\n options?: TransportRequestInit\n ): Promise<Response> {\n const { defaultHeaders } = this.options;\n if (defaultHeaders) {\n options = {\n ...options,\n headers: {\n ...defaultHeaders,\n ...(options?.headers as Record<string, string> | undefined)\n }\n };\n }\n return this.transport.fetch(path, options);\n }\n\n /**\n * Make a POST request with JSON body\n */\n protected async post<T>(\n endpoint: string,\n data: unknown,\n responseHandler?: ResponseHandler<T>,\n requestOptions?: Pick<TransportRequestInit, 'requestTimeoutMs'>\n ): Promise<T> {\n const response = await this.doFetch(endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(data),\n ...requestOptions\n });\n\n return this.handleResponse(response, responseHandler);\n }\n\n /**\n * Make a GET request\n */\n protected async get<T>(\n endpoint: string,\n responseHandler?: ResponseHandler<T>\n ): Promise<T> {\n const response = await this.doFetch(endpoint, {\n method: 'GET'\n });\n\n return this.handleResponse(response, responseHandler);\n }\n\n /**\n * Make a DELETE request\n */\n protected async delete<T>(\n endpoint: string,\n responseHandler?: ResponseHandler<T>\n ): Promise<T> {\n const response = await this.doFetch(endpoint, {\n method: 'DELETE'\n });\n\n return this.handleResponse(response, responseHandler);\n }\n\n /**\n * Handle HTTP response with error checking and parsing\n */\n protected async handleResponse<T>(\n response: Response,\n customHandler?: ResponseHandler<T>\n ): Promise<T> {\n if (!response.ok) {\n await this.handleErrorResponse(response);\n }\n\n if (customHandler) {\n return customHandler(response);\n }\n\n try {\n return await response.json();\n } catch (error) {\n // Handle malformed JSON responses gracefully\n const errorResponse: NewErrorResponse = {\n code: ErrorCode.INVALID_JSON_RESPONSE,\n message: `Invalid JSON response: ${\n error instanceof Error ? error.message : 'Unknown parsing error'\n }`,\n context: {},\n httpStatus: response.status,\n timestamp: new Date().toISOString()\n };\n throw createErrorFromResponse(errorResponse);\n }\n }\n\n /**\n * Handle error responses with consistent error throwing\n */\n protected async handleErrorResponse(response: Response): Promise<never> {\n let errorData: NewErrorResponse;\n\n try {\n errorData = await response.json();\n } catch {\n // Fallback if response isn't JSON or parsing fails\n errorData = {\n code: ErrorCode.INTERNAL_ERROR,\n message: `HTTP error! status: ${response.status}`,\n context: { statusText: response.statusText },\n httpStatus: response.status,\n timestamp: new Date().toISOString()\n };\n }\n\n // Convert ErrorResponse to appropriate Error class\n const error = createErrorFromResponse(errorData);\n\n // Call error callback if provided\n this.options.onError?.(errorData.message, undefined);\n\n throw error;\n }\n\n /**\n * Create a streaming response handler for Server-Sent Events\n */\n protected async handleStreamResponse(\n response: Response\n ): Promise<ReadableStream<Uint8Array>> {\n if (!response.ok) {\n await this.handleErrorResponse(response);\n }\n\n if (!response.body) {\n throw new Error('No response body for streaming');\n }\n\n return response.body;\n }\n\n /**\n * Stream request handler\n *\n * For HTTP mode, uses doFetch + handleStreamResponse to get proper error typing.\n * For WebSocket mode, uses Transport's streaming support.\n *\n * @param path - The API path to call\n * @param body - Optional request body (for POST requests)\n * @param method - HTTP method (default: POST, use GET for process logs)\n */\n protected async doStreamFetch(\n path: string,\n body?: unknown,\n method: 'GET' | 'POST' = 'POST'\n ): Promise<ReadableStream<Uint8Array>> {\n const streamHeaders =\n method === 'POST'\n ? {\n ...this.options.defaultHeaders,\n 'Content-Type': 'application/json'\n }\n : this.options.defaultHeaders;\n\n // WebSocket mode uses Transport's streaming directly\n if (this.transport.getMode() === 'websocket') {\n return this.transport.fetchStream(path, body, method, streamHeaders);\n }\n\n // HTTP mode: use doFetch + handleStreamResponse for proper error typing\n const response = await this.doFetch(path, {\n method,\n headers: { 'Content-Type': 'application/json' },\n body: body && method === 'POST' ? JSON.stringify(body) : undefined\n });\n\n return this.handleStreamResponse(response);\n }\n}\n","import type {\n CreateBackupRequest,\n CreateBackupResponse,\n RestoreBackupRequest,\n RestoreBackupResponse\n} from '@repo/shared';\nimport { BaseHttpClient } from './base-client';\n\n/**\n * Client for backup operations.\n *\n * Handles communication with the container's backup endpoints.\n * The container creates/extracts squashfs archives locally.\n * R2 upload/download is handled by the Sandbox DO, not by this client.\n */\nexport class BackupClient extends BaseHttpClient {\n /**\n * Tell the container to create a squashfs archive from a directory.\n * @param dir - Directory to back up\n * @param archivePath - Where the container should write the archive\n * @param sessionId - Session context\n */\n async createArchive(\n dir: string,\n archivePath: string,\n sessionId: string,\n options?: { excludes?: string[]; gitignore?: boolean }\n ): Promise<CreateBackupResponse> {\n const data: CreateBackupRequest = {\n dir,\n archivePath,\n gitignore: options?.gitignore ?? false,\n excludes: options?.excludes ?? [],\n sessionId\n };\n\n const response = await this.post<CreateBackupResponse>(\n '/api/backup/create',\n data\n );\n\n return response;\n }\n\n /**\n * Tell the container to restore a squashfs archive into a directory.\n * @param dir - Target directory\n * @param archivePath - Path to the archive file in the container\n * @param sessionId - Session context\n */\n async restoreArchive(\n dir: string,\n archivePath: string,\n sessionId: string\n ): Promise<RestoreBackupResponse> {\n const data: RestoreBackupRequest = {\n dir,\n archivePath,\n sessionId\n };\n\n const response = await this.post<RestoreBackupResponse>(\n '/api/backup/restore',\n data\n );\n\n return response;\n }\n}\n","import type { ExecuteRequest } from '@repo/shared';\nimport { BaseHttpClient } from './base-client';\nimport type { BaseApiResponse } from './types';\n\n/**\n * Request interface for command execution\n */\nexport type { ExecuteRequest };\n\n/**\n * Response interface for command execution\n */\nexport interface ExecuteResponse extends BaseApiResponse {\n stdout: string;\n stderr: string;\n exitCode: number;\n command: string;\n}\n\n/**\n * Client for command execution operations\n */\nexport class CommandClient extends BaseHttpClient {\n /**\n * Execute a command and return the complete result\n * @param command - The command to execute\n * @param sessionId - The session ID for this command execution\n * @param timeoutMs - Optional timeout in milliseconds (unlimited by default)\n * @param env - Optional environment variables for this command\n * @param cwd - Optional working directory for this command\n */\n async execute(\n command: string,\n sessionId: string,\n options?: {\n timeoutMs?: number;\n env?: Record<string, string | undefined>;\n cwd?: string;\n origin?: 'user' | 'internal';\n }\n ): Promise<ExecuteResponse> {\n try {\n const data: ExecuteRequest = {\n command,\n sessionId,\n ...(options?.timeoutMs !== undefined && {\n timeoutMs: options.timeoutMs\n }),\n ...(options?.env !== undefined && { env: options.env }),\n ...(options?.cwd !== undefined && { cwd: options.cwd }),\n ...(options?.origin !== undefined && { origin: options.origin })\n };\n\n const response = await this.post<ExecuteResponse>('/api/execute', data);\n\n // Call the callback if provided\n this.options.onCommandComplete?.(\n response.success,\n response.exitCode,\n response.stdout,\n response.stderr,\n response.command\n );\n\n return response;\n } catch (error) {\n // Call error callback if provided\n this.options.onError?.(\n error instanceof Error ? error.message : String(error),\n command\n );\n\n throw error;\n }\n }\n\n /**\n * Execute a command and return a stream of events\n * @param command - The command to execute\n * @param sessionId - The session ID for this command execution\n * @param options - Optional per-command execution settings\n */\n async executeStream(\n command: string,\n sessionId: string,\n options?: {\n timeoutMs?: number;\n env?: Record<string, string | undefined>;\n cwd?: string;\n origin?: 'user' | 'internal';\n }\n ): Promise<ReadableStream<Uint8Array>> {\n try {\n const data = {\n command,\n sessionId,\n ...(options?.timeoutMs !== undefined && {\n timeoutMs: options.timeoutMs\n }),\n ...(options?.env !== undefined && { env: options.env }),\n ...(options?.cwd !== undefined && { cwd: options.cwd }),\n ...(options?.origin !== undefined && { origin: options.origin })\n };\n\n // Use doStreamFetch which handles both WebSocket and HTTP streaming\n const stream = await this.doStreamFetch('/api/execute/stream', data);\n\n return stream;\n } catch (error) {\n // Call error callback if provided\n this.options.onError?.(\n error instanceof Error ? error.message : String(error),\n command\n );\n\n throw error;\n }\n }\n}\n","import { BaseHttpClient } from './base-client';\nimport type { BaseApiResponse } from './types';\n\nexport interface DesktopStartOptions {\n resolution?: [number, number];\n dpi?: number;\n}\n\nexport interface ScreenshotOptions {\n format?: 'base64' | 'bytes';\n imageFormat?: 'png' | 'jpeg' | 'webp';\n quality?: number;\n showCursor?: boolean;\n}\n\nexport interface ScreenshotRegion {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface ClickOptions {\n button?: 'left' | 'right' | 'middle';\n}\n\nexport type ScrollDirection = 'up' | 'down' | 'left' | 'right';\nexport type KeyInput = string;\n\nexport interface TypeOptions {\n delayMs?: number;\n}\n\nexport interface DesktopStartResponse extends BaseApiResponse {\n resolution: [number, number];\n dpi: number;\n}\n\nexport interface DesktopStopResponse extends BaseApiResponse {}\n\nexport interface DesktopStatusResponse extends BaseApiResponse {\n status: 'active' | 'partial' | 'inactive';\n processes: Record<\n string,\n { running: boolean; pid?: number; uptime?: number }\n >;\n resolution: [number, number] | null;\n dpi: number | null;\n}\n\nexport interface ScreenshotResponse extends BaseApiResponse {\n data: string;\n imageFormat: 'png' | 'jpeg' | 'webp';\n width: number;\n height: number;\n}\n\nexport interface ScreenshotBytesResponse extends BaseApiResponse {\n data: Uint8Array;\n imageFormat: 'png' | 'jpeg' | 'webp';\n width: number;\n height: number;\n}\n\nexport interface CursorPositionResponse extends BaseApiResponse {\n x: number;\n y: number;\n}\n\nexport interface ScreenSizeResponse extends BaseApiResponse {\n width: number;\n height: number;\n}\n\n/**\n * Public interface for desktop operations.\n * Returned by `sandbox.desktop` via an RpcTarget wrapper so that pipelined\n * method calls work across the Durable Object RPC boundary.\n */\nexport interface Desktop {\n start(options?: DesktopStartOptions): Promise<DesktopStartResponse>;\n stop(): Promise<DesktopStopResponse>;\n status(): Promise<DesktopStatusResponse>;\n screenshot(\n options?: ScreenshotOptions & { format?: 'base64' }\n ): Promise<ScreenshotResponse>;\n screenshot(\n options: ScreenshotOptions & { format: 'bytes' }\n ): Promise<ScreenshotBytesResponse>;\n screenshot(\n options?: ScreenshotOptions\n ): Promise<ScreenshotResponse | ScreenshotBytesResponse>;\n screenshotRegion(\n region: ScreenshotRegion,\n options?: ScreenshotOptions & { format?: 'base64' }\n ): Promise<ScreenshotResponse>;\n screenshotRegion(\n region: ScreenshotRegion,\n options: ScreenshotOptions & { format: 'bytes' }\n ): Promise<ScreenshotBytesResponse>;\n screenshotRegion(\n region: ScreenshotRegion,\n options?: ScreenshotOptions\n ): Promise<ScreenshotResponse | ScreenshotBytesResponse>;\n click(x: number, y: number, options?: ClickOptions): Promise<void>;\n doubleClick(x: number, y: number, options?: ClickOptions): Promise<void>;\n tripleClick(x: number, y: number, options?: ClickOptions): Promise<void>;\n rightClick(x: number, y: number): Promise<void>;\n middleClick(x: number, y: number): Promise<void>;\n mouseDown(x?: number, y?: number, options?: ClickOptions): Promise<void>;\n mouseUp(x?: number, y?: number, options?: ClickOptions): Promise<void>;\n moveMouse(x: number, y: number): Promise<void>;\n drag(\n startX: number,\n startY: number,\n endX: number,\n endY: number,\n options?: ClickOptions\n ): Promise<void>;\n scroll(\n x: number,\n y: number,\n direction: ScrollDirection,\n amount?: number\n ): Promise<void>;\n getCursorPosition(): Promise<CursorPositionResponse>;\n type(text: string, options?: TypeOptions): Promise<void>;\n press(key: KeyInput): Promise<void>;\n keyDown(key: KeyInput): Promise<void>;\n keyUp(key: KeyInput): Promise<void>;\n getScreenSize(): Promise<ScreenSizeResponse>;\n getProcessStatus(\n name: string\n ): Promise<\n BaseApiResponse & { running: boolean; pid?: number; uptime?: number }\n >;\n}\n\n/**\n * Client for desktop environment lifecycle, input, and screen operations\n */\nexport class DesktopClient extends BaseHttpClient {\n /**\n * Start the desktop environment with optional resolution and DPI.\n */\n async start(options?: DesktopStartOptions): Promise<DesktopStartResponse> {\n try {\n const data = {\n ...(options?.resolution !== undefined && {\n resolution: options.resolution\n }),\n ...(options?.dpi !== undefined && { dpi: options.dpi })\n };\n\n const response = await this.post<DesktopStartResponse>(\n '/api/desktop/start',\n data\n );\n\n return response;\n } catch (error) {\n this.options.onError?.(\n error instanceof Error ? error.message : String(error)\n );\n throw error;\n }\n }\n\n /**\n * Stop the desktop environment and all related processes.\n */\n async stop(): Promise<DesktopStopResponse> {\n try {\n const response = await this.post<DesktopStopResponse>(\n '/api/desktop/stop',\n {}\n );\n return response;\n } catch (error) {\n this.options.onError?.(\n error instanceof Error ? error.message : String(error)\n );\n throw error;\n }\n }\n\n /**\n * Get desktop lifecycle and process health status.\n */\n async status(): Promise<DesktopStatusResponse> {\n const response = await this.get<DesktopStatusResponse>(\n '/api/desktop/status'\n );\n return response;\n }\n\n /**\n * Capture a full-screen screenshot as base64 (default).\n */\n async screenshot(\n options?: ScreenshotOptions & { format?: 'base64' }\n ): Promise<ScreenshotResponse>;\n /**\n * Capture a full-screen screenshot as bytes.\n */\n async screenshot(\n options: ScreenshotOptions & { format: 'bytes' }\n ): Promise<ScreenshotBytesResponse>;\n async screenshot(\n options?: ScreenshotOptions\n ): Promise<ScreenshotResponse | ScreenshotBytesResponse> {\n const wantsBytes = options?.format === 'bytes';\n const data = {\n format: 'base64',\n ...(options?.imageFormat !== undefined && {\n imageFormat: options.imageFormat\n }),\n ...(options?.quality !== undefined && { quality: options.quality }),\n ...(options?.showCursor !== undefined && {\n showCursor: options.showCursor\n })\n };\n\n const response = await this.post<ScreenshotResponse>(\n '/api/desktop/screenshot',\n data\n );\n\n if (wantsBytes) {\n const binaryString = atob(response.data);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n\n return {\n ...response,\n data: bytes\n } as ScreenshotBytesResponse;\n }\n\n return response;\n }\n\n /**\n * Capture a region screenshot as base64 (default).\n */\n async screenshotRegion(\n region: ScreenshotRegion,\n options?: ScreenshotOptions & { format?: 'base64' }\n ): Promise<ScreenshotResponse>;\n /**\n * Capture a region screenshot as bytes.\n */\n async screenshotRegion(\n region: ScreenshotRegion,\n options: ScreenshotOptions & { format: 'bytes' }\n ): Promise<ScreenshotBytesResponse>;\n async screenshotRegion(\n region: ScreenshotRegion,\n options?: ScreenshotOptions\n ): Promise<ScreenshotResponse | ScreenshotBytesResponse> {\n const wantsBytes = options?.format === 'bytes';\n const data = {\n region,\n format: 'base64',\n ...(options?.imageFormat !== undefined && {\n imageFormat: options.imageFormat\n }),\n ...(options?.quality !== undefined && { quality: options.quality }),\n ...(options?.showCursor !== undefined && {\n showCursor: options.showCursor\n })\n };\n\n const response = await this.post<ScreenshotResponse>(\n '/api/desktop/screenshot/region',\n data\n );\n\n if (wantsBytes) {\n const binaryString = atob(response.data);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n\n return {\n ...response,\n data: bytes\n } as ScreenshotBytesResponse;\n }\n\n return response;\n }\n\n /**\n * Single-click at the given coordinates.\n */\n async click(x: number, y: number, options?: ClickOptions): Promise<void> {\n await this.post<BaseApiResponse>('/api/desktop/mouse/click', {\n x,\n y,\n button: options?.button ?? 'left',\n clickCount: 1\n });\n }\n\n /**\n * Double-click at the given coordinates.\n */\n async doubleClick(\n x: number,\n y: number,\n options?: ClickOptions\n ): Promise<void> {\n await this.post<BaseApiResponse>('/api/desktop/mouse/click', {\n x,\n y,\n button: options?.button ?? 'left',\n clickCount: 2\n });\n }\n\n /**\n * Triple-click at the given coordinates.\n */\n async tripleClick(\n x: number,\n y: number,\n options?: ClickOptions\n ): Promise<void> {\n await this.post<BaseApiResponse>('/api/desktop/mouse/click', {\n x,\n y,\n button: options?.button ?? 'left',\n clickCount: 3\n });\n }\n\n /**\n * Right-click at the given coordinates.\n */\n async rightClick(x: number, y: number): Promise<void> {\n await this.post<BaseApiResponse>('/api/desktop/mouse/click', {\n x,\n y,\n button: 'right',\n clickCount: 1\n });\n }\n\n /**\n * Middle-click at the given coordinates.\n */\n async middleClick(x: number, y: number): Promise<void> {\n await this.post<BaseApiResponse>('/api/desktop/mouse/click', {\n x,\n y,\n button: 'middle',\n clickCount: 1\n });\n }\n\n /**\n * Press and hold a mouse button.\n */\n async mouseDown(\n x?: number,\n y?: number,\n options?: ClickOptions\n ): Promise<void> {\n await this.post<BaseApiResponse>('/api/desktop/mouse/down', {\n ...(x !== undefined && { x }),\n ...(y !== undefined && { y }),\n button: options?.button ?? 'left'\n });\n }\n\n /**\n * Release a held mouse button.\n */\n async mouseUp(x?: number, y?: number, options?: ClickOptions): Promise<void> {\n await this.post<BaseApiResponse>('/api/desktop/mouse/up', {\n ...(x !== undefined && { x }),\n ...(y !== undefined && { y }),\n button: options?.button ?? 'left'\n });\n }\n\n /**\n * Move the mouse cursor to coordinates.\n */\n async moveMouse(x: number, y: number): Promise<void> {\n await this.post<BaseApiResponse>('/api/desktop/mouse/move', { x, y });\n }\n\n /**\n * Drag from start coordinates to end coordinates.\n */\n async drag(\n startX: number,\n startY: number,\n endX: number,\n endY: number,\n options?: ClickOptions\n ): Promise<void> {\n await this.post<BaseApiResponse>('/api/desktop/mouse/drag', {\n startX,\n startY,\n endX,\n endY,\n button: options?.button ?? 'left'\n });\n }\n\n /**\n * Scroll at coordinates in the specified direction.\n */\n async scroll(\n x: number,\n y: number,\n direction: ScrollDirection,\n amount = 3\n ): Promise<void> {\n await this.post<BaseApiResponse>('/api/desktop/mouse/scroll', {\n x,\n y,\n direction,\n amount\n });\n }\n\n /**\n * Get the current cursor coordinates.\n */\n async getCursorPosition(): Promise<CursorPositionResponse> {\n const response = await this.get<CursorPositionResponse>(\n '/api/desktop/mouse/position'\n );\n return response;\n }\n\n /**\n * Type text into the focused element.\n */\n async type(text: string, options?: TypeOptions): Promise<void> {\n await this.post<BaseApiResponse>('/api/desktop/keyboard/type', {\n text,\n ...(options?.delayMs !== undefined && { delayMs: options.delayMs })\n });\n }\n\n /**\n * Press and release a key or key combination.\n */\n async press(key: KeyInput): Promise<void> {\n await this.post<BaseApiResponse>('/api/desktop/keyboard/press', { key });\n }\n\n /**\n * Press and hold a key.\n */\n async keyDown(key: KeyInput): Promise<void> {\n await this.post<BaseApiResponse>('/api/desktop/keyboard/down', { key });\n }\n\n /**\n * Release a held key.\n */\n async keyUp(key: KeyInput): Promise<void> {\n await this.post<BaseApiResponse>('/api/desktop/keyboard/up', { key });\n }\n\n /**\n * Get the active desktop screen size.\n */\n async getScreenSize(): Promise<ScreenSizeResponse> {\n const response = await this.get<ScreenSizeResponse>(\n '/api/desktop/screen/size'\n );\n return response;\n }\n\n /**\n * Get health status for a specific desktop process.\n */\n async getProcessStatus(\n name: string\n ): Promise<\n BaseApiResponse & { running: boolean; pid?: number; uptime?: number }\n > {\n const response = await this.get<\n BaseApiResponse & { running: boolean; pid?: number; uptime?: number }\n >(`/api/desktop/process/${encodeURIComponent(name)}/status`);\n\n return response;\n }\n}\n","import type {\n DeleteFileResult,\n FileExistsResult,\n ListFilesOptions,\n ListFilesResult,\n MkdirResult,\n MoveFileResult,\n ReadFileResult,\n RenameFileResult,\n WriteFileResult\n} from '@repo/shared';\nimport { BaseHttpClient } from './base-client';\nimport type { HttpClientOptions, SessionRequest } from './types';\n\n/**\n * Request interface for creating directories\n */\nexport interface MkdirRequest extends SessionRequest {\n path: string;\n recursive?: boolean;\n}\n\n/**\n * Request interface for writing files\n */\nexport interface WriteFileRequest extends SessionRequest {\n path: string;\n content: string;\n encoding?: string;\n}\n\n/**\n * Request interface for reading files\n */\nexport interface ReadFileRequest extends SessionRequest {\n path: string;\n encoding?: string;\n}\n\n/**\n * Request interface for file operations (delete, rename, move)\n */\nexport interface FileOperationRequest extends SessionRequest {\n path: string;\n newPath?: string; // For rename/move operations\n}\n\n/**\n * Client for file system operations\n */\nexport class FileClient extends BaseHttpClient {\n /**\n * Create a directory\n * @param path - Directory path to create\n * @param sessionId - The session ID for this operation\n * @param options - Optional settings (recursive)\n */\n async mkdir(\n path: string,\n sessionId: string,\n options?: { recursive?: boolean }\n ): Promise<MkdirResult> {\n const data = {\n path,\n sessionId,\n recursive: options?.recursive ?? false\n };\n\n const response = await this.post<MkdirResult>('/api/mkdir', data);\n\n return response;\n }\n\n /**\n * Write content to a file\n * @param path - File path to write to\n * @param content - Content to write\n * @param sessionId - The session ID for this operation\n * @param options - Optional settings (encoding)\n */\n async writeFile(\n path: string,\n content: string,\n sessionId: string,\n options?: { encoding?: string }\n ): Promise<WriteFileResult> {\n const data = {\n path,\n content,\n sessionId,\n encoding: options?.encoding\n };\n\n const response = await this.post<WriteFileResult>('/api/write', data);\n\n return response;\n }\n\n /**\n * Read content from a file\n * @param path - File path to read from\n * @param sessionId - The session ID for this operation\n * @param options - Optional settings (encoding)\n */\n async readFile(\n path: string,\n sessionId: string,\n options?: { encoding?: string }\n ): Promise<ReadFileResult> {\n const data = {\n path,\n sessionId,\n encoding: options?.encoding\n };\n\n const response = await this.post<ReadFileResult>('/api/read', data);\n\n return response;\n }\n\n /**\n * Stream a file using Server-Sent Events\n * Returns a ReadableStream of SSE events containing metadata, chunks, and completion\n * @param path - File path to stream\n * @param sessionId - The session ID for this operation\n */\n async readFileStream(\n path: string,\n sessionId: string\n ): Promise<ReadableStream<Uint8Array>> {\n const data = {\n path,\n sessionId\n };\n\n // Use doStreamFetch which handles both WebSocket and HTTP streaming\n const stream = await this.doStreamFetch('/api/read/stream', data);\n return stream;\n }\n\n /**\n * Delete a file\n * @param path - File path to delete\n * @param sessionId - The session ID for this operation\n */\n async deleteFile(path: string, sessionId: string): Promise<DeleteFileResult> {\n const data = { path, sessionId };\n\n const response = await this.post<DeleteFileResult>('/api/delete', data);\n\n return response;\n }\n\n /**\n * Rename a file\n * @param path - Current file path\n * @param newPath - New file path\n * @param sessionId - The session ID for this operation\n */\n async renameFile(\n path: string,\n newPath: string,\n sessionId: string\n ): Promise<RenameFileResult> {\n const data = { oldPath: path, newPath, sessionId };\n\n const response = await this.post<RenameFileResult>('/api/rename', data);\n\n return response;\n }\n\n /**\n * Move a file\n * @param path - Current file path\n * @param newPath - Destination file path\n * @param sessionId - The session ID for this operation\n */\n async moveFile(\n path: string,\n newPath: string,\n sessionId: string\n ): Promise<MoveFileResult> {\n const data = { sourcePath: path, destinationPath: newPath, sessionId };\n\n const response = await this.post<MoveFileResult>('/api/move', data);\n\n return response;\n }\n\n /**\n * List files in a directory\n * @param path - Directory path to list\n * @param sessionId - The session ID for this operation\n * @param options - Optional settings (recursive, includeHidden)\n */\n async listFiles(\n path: string,\n sessionId: string,\n options?: ListFilesOptions\n ): Promise<ListFilesResult> {\n const data = {\n path,\n sessionId,\n options: options || {}\n };\n\n const response = await this.post<ListFilesResult>('/api/list-files', data);\n\n return response;\n }\n\n /**\n * Check if a file or directory exists\n * @param path - Path to check\n * @param sessionId - The session ID for this operation\n */\n async exists(path: string, sessionId: string): Promise<FileExistsResult> {\n const data = {\n path,\n sessionId\n };\n\n const response = await this.post<FileExistsResult>('/api/exists', data);\n\n return response;\n }\n}\n","import type { GitCheckoutResult } from '@repo/shared';\nimport {\n DEFAULT_GIT_CLONE_TIMEOUT_MS,\n extractRepoName,\n GitLogger\n} from '@repo/shared';\nimport { BaseHttpClient } from './base-client';\nimport type { HttpClientOptions, SessionRequest } from './types';\n\n// Re-export for convenience\nexport type { GitCheckoutResult };\n\n/**\n * Request interface for Git checkout operations\n */\nexport interface GitCheckoutRequest extends SessionRequest {\n repoUrl: string;\n branch?: string;\n targetDir?: string;\n /** Clone depth for shallow clones (e.g., 1 for latest commit only) */\n depth?: number;\n /** Maximum wall-clock time for the git clone subprocess in milliseconds */\n timeoutMs?: number;\n}\n\n/**\n * Client for Git repository operations\n */\nexport class GitClient extends BaseHttpClient {\n private static readonly REQUEST_TIMEOUT_BUFFER_MS = 30_000;\n\n constructor(options: HttpClientOptions = {}) {\n super(options);\n // Wrap logger with GitLogger to auto-redact credentials\n this.logger = new GitLogger(this.logger);\n }\n\n /**\n * Clone a Git repository\n * @param repoUrl - URL of the Git repository to clone\n * @param sessionId - The session ID for this operation\n * @param options - Optional settings (branch, targetDir, depth, timeoutMs)\n */\n async checkout(\n repoUrl: string,\n sessionId: string,\n options?: {\n branch?: string;\n targetDir?: string;\n /** Clone depth for shallow clones (e.g., 1 for latest commit only) */\n depth?: number;\n /** Maximum wall-clock time for the git clone subprocess in milliseconds */\n timeoutMs?: number;\n }\n ): Promise<GitCheckoutResult> {\n const timeoutMs = options?.timeoutMs ?? DEFAULT_GIT_CLONE_TIMEOUT_MS;\n\n // Determine target directory - use provided path or generate from repo name\n let targetDir = options?.targetDir;\n if (!targetDir) {\n targetDir = `/workspace/${extractRepoName(repoUrl)}`;\n }\n\n const data: GitCheckoutRequest = {\n repoUrl,\n sessionId,\n targetDir\n };\n\n // Only include branch if explicitly specified\n // This allows Git to use the repository's default branch\n if (options?.branch) {\n data.branch = options.branch;\n }\n\n if (options?.depth !== undefined) {\n if (!Number.isInteger(options.depth) || options.depth <= 0) {\n throw new Error(\n `Invalid depth value: ${options.depth}. Must be a positive integer (e.g., 1, 5, 10).`\n );\n }\n data.depth = options.depth;\n }\n\n if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {\n throw new Error(\n `Invalid timeout value: ${timeoutMs}. Must be a positive integer number of milliseconds.`\n );\n }\n\n data.timeoutMs = timeoutMs;\n\n const response = await this.post<GitCheckoutResult>(\n '/api/git/checkout',\n data,\n undefined,\n {\n requestTimeoutMs: timeoutMs + GitClient.REQUEST_TIMEOUT_BUFFER_MS\n }\n );\n\n return response;\n }\n}\n","import {\n type CodeContext,\n type ContextCreateResult,\n type ContextListResult,\n type CreateContextOptions,\n type ExecutionError,\n type OutputMessage,\n type Result,\n ResultImpl\n} from '@repo/shared';\nimport type { ErrorResponse } from '../errors';\nimport {\n createErrorFromResponse,\n ErrorCode,\n InterpreterNotReadyError\n} from '../errors';\nimport { BaseHttpClient } from './base-client.js';\nimport type { HttpClientOptions } from './types.js';\n\n// Streaming execution data from the server\ninterface StreamingExecutionData {\n type: 'result' | 'stdout' | 'stderr' | 'error' | 'execution_complete';\n text?: string;\n html?: string;\n png?: string; // base64\n jpeg?: string; // base64\n svg?: string;\n latex?: string;\n markdown?: string;\n javascript?: string;\n json?: unknown;\n chart?: {\n type:\n | 'line'\n | 'bar'\n | 'scatter'\n | 'pie'\n | 'histogram'\n | 'heatmap'\n | 'unknown';\n data: unknown;\n options?: unknown;\n };\n data?: unknown;\n metadata?: Record<string, unknown>;\n execution_count?: number;\n ename?: string;\n evalue?: string;\n traceback?: string[];\n lineNumber?: number;\n timestamp?: number;\n}\n\nexport interface ExecutionCallbacks {\n onStdout?: (output: OutputMessage) => void | Promise<void>;\n onStderr?: (output: OutputMessage) => void | Promise<void>;\n onResult?: (result: Result) => void | Promise<void>;\n onError?: (error: ExecutionError) => void | Promise<void>;\n}\n\nexport class InterpreterClient extends BaseHttpClient {\n private readonly maxRetries = 3;\n private readonly retryDelayMs = 1000;\n\n async createCodeContext(\n options: CreateContextOptions = {}\n ): Promise<CodeContext> {\n return this.executeWithRetry(async () => {\n const response = await this.doFetch('/api/contexts', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n language: options.language || 'python',\n cwd: options.cwd || '/workspace',\n env_vars: options.envVars\n })\n });\n\n if (!response.ok) {\n const error = await this.parseErrorResponse(response);\n throw error;\n }\n\n const data = (await response.json()) as ContextCreateResult;\n if (!data.success) {\n throw new Error(`Failed to create context: ${JSON.stringify(data)}`);\n }\n\n return {\n id: data.contextId,\n language: data.language,\n cwd: data.cwd || '/workspace',\n createdAt: new Date(data.timestamp),\n lastUsed: new Date(data.timestamp)\n };\n });\n }\n\n async runCodeStream(\n contextId: string | undefined,\n code: string,\n language: string | undefined,\n callbacks: ExecutionCallbacks,\n timeoutMs?: number\n ): Promise<void> {\n return this.executeWithRetry(async () => {\n // Use doStreamFetch which handles both WebSocket and HTTP streaming\n const stream = await this.doStreamFetch('/api/execute/code', {\n context_id: contextId,\n code,\n language,\n ...(timeoutMs !== undefined && { timeout_ms: timeoutMs })\n });\n\n // Process streaming response\n for await (const chunk of this.readLines(stream)) {\n await this.parseExecutionResult(chunk, callbacks);\n }\n });\n }\n\n async listCodeContexts(): Promise<CodeContext[]> {\n return this.executeWithRetry(async () => {\n const response = await this.doFetch('/api/contexts', {\n method: 'GET',\n headers: { 'Content-Type': 'application/json' }\n });\n\n if (!response.ok) {\n const error = await this.parseErrorResponse(response);\n throw error;\n }\n\n const data = (await response.json()) as ContextListResult;\n if (!data.success) {\n throw new Error(`Failed to list contexts: ${JSON.stringify(data)}`);\n }\n\n return data.contexts.map((ctx) => ({\n id: ctx.id,\n language: ctx.language,\n cwd: ctx.cwd || '/workspace',\n createdAt: new Date(data.timestamp),\n lastUsed: new Date(data.timestamp)\n }));\n });\n }\n\n async deleteCodeContext(contextId: string): Promise<void> {\n return this.executeWithRetry(async () => {\n const response = await this.doFetch(`/api/contexts/${contextId}`, {\n method: 'DELETE',\n headers: { 'Content-Type': 'application/json' }\n });\n\n if (!response.ok) {\n const error = await this.parseErrorResponse(response);\n throw error;\n }\n });\n }\n\n /**\n * Get a raw stream for code execution.\n * Used by CodeInterpreter.runCodeStreaming() for direct stream access.\n */\n async streamCode(\n contextId: string,\n code: string,\n language?: string\n ): Promise<ReadableStream<Uint8Array>> {\n return this.doStreamFetch('/api/execute/code', {\n context_id: contextId,\n code,\n language\n });\n }\n\n /**\n * Execute an operation with automatic retry for transient errors\n */\n private async executeWithRetry<T>(operation: () => Promise<T>): Promise<T> {\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt < this.maxRetries; attempt++) {\n try {\n return await operation();\n } catch (error) {\n lastError = error as Error;\n\n // Check if it's a retryable error (interpreter not ready)\n if (this.isRetryableError(error)) {\n // Don't retry on the last attempt\n if (attempt < this.maxRetries - 1) {\n // Exponential backoff with jitter\n const delay =\n this.retryDelayMs * 2 ** attempt + Math.random() * 1000;\n await new Promise((resolve) => setTimeout(resolve, delay));\n continue;\n }\n }\n\n // Not retryable or last attempt - throw the error\n throw error;\n }\n }\n\n throw lastError || new Error('Execution failed after retries');\n }\n\n private isRetryableError(error: unknown): boolean {\n if (error instanceof InterpreterNotReadyError) {\n return true;\n }\n\n if (error instanceof Error) {\n return (\n error.message.includes('not ready') ||\n error.message.includes('initializing')\n );\n }\n\n return false;\n }\n\n private async parseErrorResponse(response: Response): Promise<Error> {\n try {\n const errorData = (await response.json()) as ErrorResponse;\n return createErrorFromResponse(errorData);\n } catch {\n // Fallback if response isn't JSON\n const errorResponse: ErrorResponse = {\n code: ErrorCode.INTERNAL_ERROR,\n message: `HTTP ${response.status}: ${response.statusText}`,\n context: {},\n httpStatus: response.status,\n timestamp: new Date().toISOString()\n };\n return createErrorFromResponse(errorResponse);\n }\n }\n\n private async *readLines(\n stream: ReadableStream<Uint8Array>\n ): AsyncGenerator<string> {\n const reader = stream.getReader();\n let buffer = '';\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (value) {\n buffer += new TextDecoder().decode(value);\n }\n if (done) break;\n\n let newlineIdx = buffer.indexOf('\\n');\n while (newlineIdx !== -1) {\n yield buffer.slice(0, newlineIdx);\n buffer = buffer.slice(newlineIdx + 1);\n newlineIdx = buffer.indexOf('\\n');\n }\n }\n\n // Yield any remaining data\n if (buffer.length > 0) {\n yield buffer;\n }\n } finally {\n // Cancel the stream first to properly terminate HTTP connections when breaking early\n try {\n await reader.cancel();\n } catch {\n // Ignore cancel errors (stream may already be closed)\n }\n reader.releaseLock();\n }\n }\n\n private async parseExecutionResult(\n line: string,\n callbacks: ExecutionCallbacks\n ) {\n if (!line.trim()) return;\n\n // Skip lines that don't start with \"data: \" (SSE format)\n if (!line.startsWith('data: ')) return;\n\n try {\n // Strip \"data: \" prefix and parse JSON\n const jsonData = line.substring(6); // \"data: \" is 6 characters\n const data = JSON.parse(jsonData) as StreamingExecutionData;\n\n switch (data.type) {\n case 'stdout':\n if (callbacks.onStdout && data.text) {\n await callbacks.onStdout({\n text: data.text,\n timestamp: data.timestamp || Date.now()\n });\n }\n break;\n\n case 'stderr':\n if (callbacks.onStderr && data.text) {\n await callbacks.onStderr({\n text: data.text,\n timestamp: data.timestamp || Date.now()\n });\n }\n break;\n\n case 'result':\n if (callbacks.onResult) {\n // Create a ResultImpl instance from the raw data\n const result = new ResultImpl(data);\n await callbacks.onResult(result);\n }\n break;\n\n case 'error':\n if (callbacks.onError) {\n await callbacks.onError({\n name: data.ename || 'Error',\n message: data.evalue || 'Unknown error',\n traceback: data.traceback || []\n });\n }\n break;\n\n case 'execution_complete':\n // Signal completion - callbacks can handle cleanup if needed\n break;\n }\n } catch {\n // Silently ignore unparseable SSE lines\n }\n }\n}\n","import type {\n ExposePortRequest,\n PortCloseResult,\n PortExposeResult,\n PortListResult,\n PortWatchRequest\n} from '@repo/shared';\nimport { BaseHttpClient } from './base-client';\n\n// Re-export for convenience\nexport type {\n ExposePortRequest,\n PortExposeResult,\n PortCloseResult,\n PortListResult\n};\n\n/**\n * Request interface for unexposing ports\n */\nexport interface UnexposePortRequest {\n port: number;\n}\n\n/**\n * Client for port management and preview URL operations\n */\nexport class PortClient extends BaseHttpClient {\n /**\n * Expose a port and get a preview URL\n * @param port - Port number to expose\n * @param sessionId - The session ID for this operation\n * @param name - Optional name for the port\n */\n async exposePort(\n port: number,\n sessionId: string,\n name?: string\n ): Promise<PortExposeResult> {\n const data = { port, sessionId, name };\n\n const response = await this.post<PortExposeResult>(\n '/api/expose-port',\n data\n );\n\n return response;\n }\n\n /**\n * Unexpose a port and remove its preview URL\n * @param port - Port number to unexpose\n * @param sessionId - The session ID for this operation\n */\n async unexposePort(\n port: number,\n sessionId: string\n ): Promise<PortCloseResult> {\n const url = `/api/exposed-ports/${port}?session=${encodeURIComponent(\n sessionId\n )}`;\n const response = await this.delete<PortCloseResult>(url);\n\n return response;\n }\n\n /**\n * Get all currently exposed ports\n * @param sessionId - The session ID for this operation\n */\n async getExposedPorts(sessionId: string): Promise<PortListResult> {\n const url = `/api/exposed-ports?session=${encodeURIComponent(sessionId)}`;\n const response = await this.get<PortListResult>(url);\n\n return response;\n }\n\n /**\n * Watch a port for readiness via SSE stream\n * @param request - Port watch configuration\n * @returns SSE stream that emits PortWatchEvent objects\n */\n async watchPort(\n request: PortWatchRequest\n ): Promise<ReadableStream<Uint8Array>> {\n const stream = await this.doStreamFetch('/api/port-watch', request);\n return stream;\n }\n}\n","import type {\n ProcessCleanupResult,\n ProcessInfoResult,\n ProcessKillResult,\n ProcessListResult,\n ProcessLogsResult,\n ProcessStartResult,\n StartProcessRequest\n} from '@repo/shared';\nimport { BaseHttpClient } from './base-client';\nimport type { HttpClientOptions } from './types';\n\n// Re-export for convenience\nexport type {\n StartProcessRequest,\n ProcessStartResult,\n ProcessListResult,\n ProcessInfoResult,\n ProcessKillResult,\n ProcessLogsResult,\n ProcessCleanupResult\n};\n\n/**\n * Client for background process management\n */\nexport class ProcessClient extends BaseHttpClient {\n /**\n * Start a background process\n * @param command - Command to execute as a background process\n * @param sessionId - The session ID for this operation\n * @param options - Optional settings (processId)\n */\n async startProcess(\n command: string,\n sessionId: string,\n options?: {\n processId?: string;\n timeoutMs?: number;\n env?: Record<string, string | undefined>;\n cwd?: string;\n encoding?: string;\n autoCleanup?: boolean;\n origin?: 'user' | 'internal';\n }\n ): Promise<ProcessStartResult> {\n const data: StartProcessRequest = {\n command,\n sessionId,\n ...(options?.origin !== undefined && { origin: options.origin }),\n ...(options?.processId !== undefined && {\n processId: options.processId\n }),\n ...(options?.timeoutMs !== undefined && {\n timeoutMs: options.timeoutMs\n }),\n ...(options?.env !== undefined && { env: options.env }),\n ...(options?.cwd !== undefined && { cwd: options.cwd }),\n ...(options?.encoding !== undefined && { encoding: options.encoding }),\n ...(options?.autoCleanup !== undefined && {\n autoCleanup: options.autoCleanup\n })\n };\n\n const response = await this.post<ProcessStartResult>(\n '/api/process/start',\n data\n );\n\n return response;\n }\n\n /**\n * List all processes (sandbox-scoped, not session-scoped)\n */\n async listProcesses(): Promise<ProcessListResult> {\n const url = `/api/process/list`;\n const response = await this.get<ProcessListResult>(url);\n\n return response;\n }\n\n /**\n * Get information about a specific process (sandbox-scoped, not session-scoped)\n * @param processId - ID of the process to retrieve\n */\n async getProcess(processId: string): Promise<ProcessInfoResult> {\n const url = `/api/process/${processId}`;\n const response = await this.get<ProcessInfoResult>(url);\n\n return response;\n }\n\n /**\n * Kill a specific process (sandbox-scoped, not session-scoped)\n * @param processId - ID of the process to kill\n */\n async killProcess(processId: string): Promise<ProcessKillResult> {\n const url = `/api/process/${processId}`;\n const response = await this.delete<ProcessKillResult>(url);\n\n return response;\n }\n\n /**\n * Kill all running processes (sandbox-scoped, not session-scoped)\n */\n async killAllProcesses(): Promise<ProcessCleanupResult> {\n const url = `/api/process/kill-all`;\n const response = await this.delete<ProcessCleanupResult>(url);\n\n return response;\n }\n\n /**\n * Get logs from a specific process (sandbox-scoped, not session-scoped)\n * @param processId - ID of the process to get logs from\n */\n async getProcessLogs(processId: string): Promise<ProcessLogsResult> {\n const url = `/api/process/${processId}/logs`;\n const response = await this.get<ProcessLogsResult>(url);\n\n return response;\n }\n\n /**\n * Stream logs from a specific process (sandbox-scoped, not session-scoped)\n * @param processId - ID of the process to stream logs from\n */\n async streamProcessLogs(\n processId: string\n ): Promise<ReadableStream<Uint8Array>> {\n const url = `/api/process/${processId}/stream`;\n // Use doStreamFetch with GET method (process log streaming is GET)\n const stream = await this.doStreamFetch(url, undefined, 'GET');\n\n return stream;\n }\n}\n","import { BaseHttpClient } from './base-client';\nimport type { BaseApiResponse, HttpClientOptions } from './types';\n\n/**\n * Response interface for ping operations\n */\nexport interface PingResponse extends BaseApiResponse {\n message: string;\n uptime?: number;\n}\n\n/**\n * Response interface for getting available commands\n */\nexport interface CommandsResponse extends BaseApiResponse {\n availableCommands: string[];\n count: number;\n}\n\n/**\n * Response interface for getting container version\n */\nexport interface VersionResponse extends BaseApiResponse {\n version: string;\n}\n\n/**\n * Request interface for creating sessions\n */\nexport interface CreateSessionRequest {\n id: string;\n env?: Record<string, string | undefined>;\n cwd?: string;\n commandTimeoutMs?: number;\n}\n\n/**\n * Response interface for creating sessions.\n *\n * `containerPlacementId` carries the container's `CLOUDFLARE_PLACEMENT_ID` at session\n * creation time so the DO can capture it without a separate request. It is\n * `null` when the environment variable is not set, such as in local dev.\n */\nexport interface CreateSessionResponse extends BaseApiResponse {\n id: string;\n message: string;\n containerPlacementId?: string | null;\n}\n\n/**\n * Request interface for deleting sessions\n */\nexport interface DeleteSessionRequest {\n sessionId: string;\n}\n\n/**\n * Response interface for deleting sessions\n */\nexport interface DeleteSessionResponse extends BaseApiResponse {\n sessionId: string;\n}\n\n/**\n * Client for health checks and utility operations\n */\nexport class UtilityClient extends BaseHttpClient {\n /**\n * Ping the sandbox to check if it's responsive\n */\n async ping(): Promise<string> {\n const response = await this.get<PingResponse>('/api/ping');\n\n return response.message;\n }\n\n /**\n * Get list of available commands in the sandbox environment\n */\n async getCommands(): Promise<string[]> {\n const response = await this.get<CommandsResponse>('/api/commands');\n\n return response.availableCommands;\n }\n\n /**\n * Create a new execution session\n * @param options - Session configuration (id, env, cwd)\n */\n async createSession(\n options: CreateSessionRequest\n ): Promise<CreateSessionResponse> {\n const response = await this.post<CreateSessionResponse>(\n '/api/session/create',\n options\n );\n\n return response;\n }\n\n /**\n * Delete an execution session\n * @param sessionId - Session ID to delete\n */\n async deleteSession(sessionId: string): Promise<DeleteSessionResponse> {\n const response = await this.post<DeleteSessionResponse>(\n '/api/session/delete',\n { sessionId }\n );\n\n return response;\n }\n\n /**\n * Get the container version\n * Returns the version embedded in the Docker image during build\n */\n async getVersion(): Promise<string> {\n try {\n const response = await this.get<VersionResponse>('/api/version');\n\n return response.version;\n } catch (error) {\n // If version endpoint doesn't exist (old container), return 'unknown'\n // This allows for backward compatibility\n this.logger.debug(\n 'Failed to get container version (may be old container)',\n { error }\n );\n return 'unknown';\n }\n }\n}\n","import {\n type CheckChangesRequest,\n type CheckChangesResult,\n type FileWatchSSEEvent,\n parseSSEFrames,\n type SSEPartialEvent,\n type WatchRequest\n} from '@repo/shared';\nimport { BaseHttpClient } from './base-client';\n\n/**\n * Client for file watch operations\n * Uses inotify under the hood for native filesystem event notifications\n *\n * @internal This client is used internally by the SDK.\n * Users should use `sandbox.watch()` or `sandbox.checkChanges()` instead.\n */\nexport class WatchClient extends BaseHttpClient {\n /**\n * Check whether a path changed since a previously returned version.\n */\n async checkChanges(\n request: CheckChangesRequest\n ): Promise<CheckChangesResult> {\n return this.post<CheckChangesResult>('/api/watch/check', request);\n }\n\n /**\n * Start watching a directory for changes.\n * The returned promise resolves only after the watcher is established\n * on the filesystem (i.e. the `watching` SSE event has been received).\n * The returned stream still contains the `watching` event so consumers\n * using `parseSSEStream` will see the full event sequence.\n *\n * @param request - Watch request with path and options\n */\n async watch(request: WatchRequest): Promise<ReadableStream<Uint8Array>> {\n const stream = await this.doStreamFetch('/api/watch', request);\n const readyStream = await this.waitForReadiness(stream);\n\n return readyStream;\n }\n\n /**\n * Read SSE chunks until the `watching` event appears, then return a\n * wrapper stream that replays the buffered chunks followed by the\n * remaining original stream data.\n */\n private async waitForReadiness(\n stream: ReadableStream<Uint8Array>\n ): Promise<ReadableStream<Uint8Array>> {\n const reader = stream.getReader();\n const bufferedChunks: Uint8Array[] = [];\n const decoder = new TextDecoder();\n let buffer = '';\n let currentEvent: SSEPartialEvent = { data: [] };\n let watcherReady = false;\n\n const processEventData = (eventData: string) => {\n let event: FileWatchSSEEvent | undefined;\n try {\n event = JSON.parse(eventData) as FileWatchSSEEvent;\n } catch {\n return;\n }\n\n if (event.type === 'watching') {\n watcherReady = true;\n }\n\n if (event.type === 'error') {\n throw new Error(event.error || 'Watch failed to establish');\n }\n };\n\n try {\n while (!watcherReady) {\n const { done, value } = await reader.read();\n if (done) {\n const finalParsed = parseSSEFrames(`${buffer}\\n\\n`, currentEvent);\n for (const frame of finalParsed.events) {\n processEventData(frame.data);\n if (watcherReady) {\n break;\n }\n }\n\n if (watcherReady) {\n break;\n }\n\n throw new Error('Watch stream ended before watcher was established');\n }\n\n bufferedChunks.push(value);\n buffer += decoder.decode(value, { stream: true });\n const parsed = parseSSEFrames(buffer, currentEvent);\n buffer = parsed.remaining;\n currentEvent = parsed.currentEvent;\n\n for (const frame of parsed.events) {\n processEventData(frame.data);\n if (watcherReady) {\n break;\n }\n }\n }\n } catch (error) {\n reader.cancel().catch(() => {});\n throw error;\n }\n\n // Return a stream that replays buffered chunks, then forwards the rest.\n let replayIndex = 0;\n return new ReadableStream<Uint8Array>({\n pull(controller) {\n if (replayIndex < bufferedChunks.length) {\n controller.enqueue(bufferedChunks[replayIndex++]);\n return;\n }\n return reader.read().then(({ done: d, value: v }) => {\n if (d) {\n controller.close();\n return;\n }\n controller.enqueue(v);\n });\n },\n cancel() {\n return reader.cancel();\n }\n });\n }\n}\n","import type { SandboxAPI } from '@repo/shared';\nimport { BackupClient } from './backup-client';\nimport { CommandClient } from './command-client';\nimport { DesktopClient } from './desktop-client';\nimport { FileClient } from './file-client';\nimport { GitClient } from './git-client';\nimport { InterpreterClient } from './interpreter-client';\nimport { PortClient } from './port-client';\nimport { ProcessClient } from './process-client';\nimport {\n createTransport,\n type ITransport,\n type TransportMode\n} from './transport';\nimport type { HttpClientOptions } from './types';\nimport { UtilityClient } from './utility-client';\nimport { WatchClient } from './watch-client';\n\n/**\n * Main sandbox client that composes all domain-specific clients.\n * Provides organized access to all sandbox functionality.\n *\n * Supports two transport modes:\n * - HTTP (default): Each request is a separate HTTP call\n * - WebSocket: All requests multiplexed over a single connection,\n * reducing sub-request count inside Workers/Durable Objects\n */\nexport class SandboxClient {\n public readonly backup: BackupClient;\n public readonly commands: CommandClient;\n public readonly files: FileClient;\n public readonly processes: ProcessClient;\n public readonly ports: PortClient;\n public readonly git: GitClient;\n public readonly interpreter: InterpreterClient;\n public readonly utils: UtilityClient;\n public readonly desktop: DesktopClient;\n public readonly watch: WatchClient;\n\n private transport: ITransport | null = null;\n\n constructor(options: HttpClientOptions) {\n // Create shared transport if WebSocket mode is enabled\n if (options.transportMode === 'websocket' && options.wsUrl) {\n this.transport = createTransport({\n mode: options.transportMode,\n wsUrl: options.wsUrl,\n baseUrl: options.baseUrl,\n logger: options.logger,\n stub: options.stub,\n port: options.port,\n retryTimeoutMs: options.retryTimeoutMs\n });\n }\n\n // Ensure baseUrl is provided for all clients\n const clientOptions: HttpClientOptions = {\n baseUrl: 'http://localhost:3000',\n ...options,\n // Share transport across all clients\n transport: this.transport ?? options.transport\n };\n\n // Initialize all domain clients with shared options\n this.backup = new BackupClient(clientOptions);\n this.commands = new CommandClient(clientOptions);\n this.files = new FileClient(clientOptions);\n this.processes = new ProcessClient(clientOptions);\n this.ports = new PortClient(clientOptions);\n this.git = new GitClient(clientOptions);\n this.interpreter = new InterpreterClient(clientOptions);\n this.utils = new UtilityClient(clientOptions);\n this.desktop = new DesktopClient(clientOptions);\n this.watch = new WatchClient(clientOptions);\n }\n\n /**\n * Update the 503 retry budget on all transports without recreating the client.\n *\n * In WebSocket mode a single shared transport is used, so one update covers\n * every sub-client. In HTTP mode each sub-client owns its own transport, so\n * all of them are updated individually.\n */\n setRetryTimeoutMs(ms: number): void {\n if (this.transport) {\n // WebSocket mode — single shared transport\n this.transport.setRetryTimeoutMs(ms);\n } else {\n // HTTP mode — each sub-client has its own transport\n this.backup.setRetryTimeoutMs(ms);\n this.commands.setRetryTimeoutMs(ms);\n this.files.setRetryTimeoutMs(ms);\n this.processes.setRetryTimeoutMs(ms);\n this.ports.setRetryTimeoutMs(ms);\n this.git.setRetryTimeoutMs(ms);\n this.interpreter.setRetryTimeoutMs(ms);\n this.utils.setRetryTimeoutMs(ms);\n this.desktop.setRetryTimeoutMs(ms);\n this.watch.setRetryTimeoutMs(ms);\n }\n }\n\n /**\n * Get the current transport mode\n */\n getTransportMode(): TransportMode {\n return this.transport?.getMode() ?? 'http';\n }\n\n /**\n * Check if WebSocket is connected (only relevant in WebSocket mode)\n */\n isWebSocketConnected(): boolean {\n return this.transport?.isConnected() ?? false;\n }\n\n /**\n * Stream a file directly to the container over a binary RPC channel.\n *\n * Requires the capnweb transport (`useWebSocket: 'rpc'`). Calling this\n * method with the HTTP or WebSocket transports throws an error because those\n * transports do not support binary streaming.\n */\n writeFileStream(\n _path: string,\n _content: ReadableStream<Uint8Array>,\n _sessionId: string\n ): Promise<{\n success: boolean;\n path: string;\n bytesWritten: number;\n timestamp: string;\n }> {\n throw new Error(\n 'writeFileStream requires the RPC transport. Enable it with transport: \"rpc\" in sandbox options.'\n );\n }\n\n /**\n * Connect WebSocket transport (no-op in HTTP mode)\n * Called automatically on first request, but can be called explicitly\n * to establish connection upfront.\n */\n async connect(): Promise<void> {\n if (this.transport) {\n await this.transport.connect();\n }\n }\n\n /**\n * Disconnect WebSocket transport (no-op in HTTP mode)\n * Should be called when the sandbox is destroyed.\n */\n disconnect(): void {\n if (this.transport) {\n this.transport.disconnect();\n }\n }\n}\n\n// Compile-time check: SandboxClient exposes every top-level field that SandboxAPI requires.\n// Deep structural compatibility is not enforced because the HTTP sub-clients\n// (e.g. FileClient) lack streaming methods only available via capnweb.\ntype PublicKeys<T> = { [K in keyof T]: unknown };\nvoid (0 as unknown as PublicKeys<SandboxClient> satisfies PublicKeys<SandboxAPI>);\n","/**\n * Absolute directory prefixes supported by backup and restore operations.\n */\nexport const BACKUP_ALLOWED_PREFIXES = [\n '/workspace',\n '/home',\n '/tmp',\n '/var/tmp',\n '/app'\n] as const;\n","/**\n * Capnweb RPC connection to the container.\n *\n * Manages a single WebSocket session and exposes typed methods that map\n * 1:1 to the container's SandboxAPI. The Sandbox DO calls these\n * directly instead of going through the HTTP client layer.\n */\n\nimport type {\n Logger,\n SandboxAPI,\n SandboxBackupAPI,\n SandboxCommandsAPI,\n SandboxDesktopAPI,\n SandboxFilesAPI,\n SandboxGitAPI,\n SandboxInterpreterAPI,\n SandboxPortsAPI,\n SandboxProcessesAPI,\n SandboxUtilsAPI,\n SandboxWatchAPI\n} from '@repo/shared';\nimport { createNoOpLogger } from '@repo/shared';\nimport { RpcSession, type RpcStub, type RpcTransport } from 'capnweb';\n\n// ---------------------------------------------------------------------------\n// Connection manager\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_CONNECT_TIMEOUT_MS = 30_000;\n\n/** Stub that can issue a WebSocket-upgrade fetch through the DO's Container base class. */\nexport interface ContainerFetchStub {\n fetch(request: Request): Promise<Response>;\n}\n\nexport interface ContainerConnectionOptions {\n stub: ContainerFetchStub;\n port?: number;\n logger?: Logger;\n}\n\n/**\n * Manages a capnweb WebSocket RPC session to the container.\n *\n * The RPC stub is created eagerly in the constructor using a deferred\n * transport. Calls made before `connect()` completes are queued in the\n * transport and flushed once the WebSocket is established.\n */\nexport class ContainerConnection {\n private readonly stub: RpcStub<SandboxAPI>;\n private readonly session: RpcSession<SandboxAPI>;\n private readonly transport: DeferredTransport;\n private ws: WebSocket | null = null;\n private connected = false;\n private connectPromise: Promise<void> | null = null;\n private readonly containerStub: ContainerFetchStub;\n private readonly port: number;\n private readonly logger: Logger;\n\n constructor(options: ContainerConnectionOptions) {\n this.containerStub = options.stub;\n this.port = options.port ?? 3000;\n this.logger = options.logger ?? createNoOpLogger();\n\n this.transport = new DeferredTransport();\n this.session = new RpcSession<SandboxAPI>(this.transport);\n this.stub = this.session.getRemoteMain();\n }\n\n /**\n * Get the typed RPC stub.\n *\n * The stub is available immediately — calls made before connect()\n * completes are queued in the deferred transport and flushed once\n * the WebSocket is established.\n */\n rpc(): RpcStub<SandboxAPI> {\n if (!this.connected && !this.connectPromise) {\n this.connect().catch(() => {});\n }\n return this.stub;\n }\n\n /**\n * Return capnweb session statistics. The `imports` and `exports` counts\n * reflect all in-flight RPC calls, streams, and peer-held references.\n * An idle session has imports <= 1 && exports <= 1 (the bootstrap stubs).\n */\n getStats(): { imports: number; exports: number } {\n return this.session.getStats();\n }\n\n isConnected(): boolean {\n return this.connected;\n }\n\n async connect(): Promise<void> {\n if (this.connected) return;\n\n if (this.connectPromise) {\n return this.connectPromise;\n }\n\n this.connectPromise = this.doConnect();\n try {\n await this.connectPromise;\n } finally {\n this.connectPromise = null;\n }\n }\n\n disconnect(): void {\n try {\n (this.stub as unknown as Disposable)[Symbol.dispose]?.();\n } catch {\n // Stub may already be disposed\n }\n if (this.ws) {\n try {\n this.ws.close();\n } catch {\n // WebSocket may already be closed\n }\n this.ws = null;\n }\n this.connected = false;\n this.connectPromise = null;\n }\n\n // -----------------------------------------------------------------------\n // Internal\n // -----------------------------------------------------------------------\n\n private async doConnect(): Promise<void> {\n const controller = new AbortController();\n const timeout = setTimeout(\n () => controller.abort(),\n DEFAULT_CONNECT_TIMEOUT_MS\n );\n\n try {\n const url = `http://localhost:${this.port}/rpc`;\n const request = new Request(url, {\n headers: {\n Upgrade: 'websocket',\n Connection: 'Upgrade'\n },\n signal: controller.signal\n });\n\n const response = await this.containerStub.fetch(request);\n clearTimeout(timeout);\n\n if (response.status !== 101) {\n throw new Error(\n `WebSocket upgrade failed: ${response.status} ${response.statusText}`\n );\n }\n\n // The Container base class returns the WebSocket on the response object\n // (Cloudflare Workers runtime convention, not standard fetch)\n const ws = (response as unknown as { webSocket?: WebSocket }).webSocket;\n if (!ws) {\n throw new Error('No WebSocket in upgrade response');\n }\n\n // Workers WebSockets require explicit accept() before use\n (ws as unknown as { accept: () => void }).accept();\n\n ws.addEventListener('close', () => {\n this.connected = false;\n this.ws = null;\n this.logger.debug('ContainerConnection WebSocket closed');\n });\n\n ws.addEventListener('error', () => {\n this.connected = false;\n this.ws = null;\n });\n\n this.ws = ws;\n this.transport.activate(ws);\n this.connected = true;\n\n this.logger.debug('ContainerConnection established', {\n port: this.port\n });\n } catch (error) {\n clearTimeout(timeout);\n this.connected = false;\n this.transport.abort(error);\n this.logger.error(\n 'ContainerConnection failed',\n error instanceof Error ? error : new Error(String(error))\n );\n throw error;\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Deferred WebSocket transport\n// ---------------------------------------------------------------------------\n\n/**\n * RPC transport that queues sends and blocks receives until a WebSocket\n * is provided via `activate()`. Allows the RPC stub to be created before\n * the connection is established — queued calls flush automatically.\n */\nclass DeferredTransport implements RpcTransport {\n #ws: WebSocket | null = null;\n #sendQueue: string[] = [];\n #receiveQueue: string[] = [];\n #receiveResolver?: (msg: string) => void;\n #receiveRejecter?: (err: unknown) => void;\n #error?: unknown;\n\n activate(ws: WebSocket): void {\n this.#ws = ws;\n\n ws.addEventListener('message', (event: MessageEvent) => {\n if (this.#error) return;\n if (typeof event.data === 'string') {\n if (this.#receiveResolver) {\n this.#receiveResolver(event.data);\n this.#receiveResolver = undefined;\n this.#receiveRejecter = undefined;\n } else {\n this.#receiveQueue.push(event.data);\n }\n }\n });\n ws.addEventListener('close', (event: CloseEvent) => {\n this.#fail(\n new Error(`Peer closed WebSocket: ${event.code} ${event.reason}`)\n );\n });\n ws.addEventListener('error', () => {\n this.#fail(new Error('WebSocket connection failed'));\n });\n\n // Flush queued sends\n for (const msg of this.#sendQueue) {\n ws.send(msg);\n }\n this.#sendQueue = [];\n }\n\n async send(message: string): Promise<void> {\n if (this.#ws) {\n this.#ws.send(message);\n } else {\n this.#sendQueue.push(message);\n }\n }\n\n async receive(): Promise<string> {\n if (this.#receiveQueue.length > 0) return this.#receiveQueue.shift()!;\n if (this.#error) throw this.#error;\n return new Promise<string>((resolve, reject) => {\n this.#receiveResolver = resolve;\n this.#receiveRejecter = reject;\n });\n }\n\n abort(reason: unknown): void {\n this.#fail(reason instanceof Error ? reason : new Error(String(reason)));\n if (this.#ws) {\n const message = reason instanceof Error ? reason.message : String(reason);\n this.#ws.close(3000, message);\n }\n }\n\n #fail(err: unknown): void {\n if (this.#error) return;\n this.#error = err;\n this.#receiveRejecter?.(err);\n this.#receiveResolver = undefined;\n this.#receiveRejecter = undefined;\n }\n}\n","/**\n * SandboxClient implementation backed by direct capnweb RPC calls.\n *\n * The server exposes each domain (commands, files, processes, etc.) as a\n * nested RpcTarget. capnweb returns typed stubs for these so the client\n * can use `rpc.commands`, `rpc.files`, etc. directly without any\n * per-method boilerplate.\n *\n * Manages its own connection lifecycle: creates a fresh ContainerConnection\n * on demand and disconnects it after a configurable idle period. Idle\n * detection uses capnweb's `RpcSession.getStats()` which naturally tracks\n * all in-flight RPC calls, streams, and peer-held references — no manual\n * operation counting required.\n *\n * ---------------------------------------------------------------------------\n * How capnweb tracks in-flight work (and why we poll getStats)\n * ---------------------------------------------------------------------------\n *\n * Every capnweb session maintains two tables: `imports` (references the\n * peer is exposing to us) and `exports` (references we are exposing to the\n * peer). `getStats()` returns the live count of each.\n *\n * At rest, both contain exactly one entry — the bootstrap \"main\" stub each\n * side exposes to reach the other. We treat `imports <= 1 && exports <= 1`\n * as the idle baseline.\n *\n * Each kind of in-flight work bumps these counts:\n *\n * - **Pending RPC call.** `sendCall()` allocates a new import slot for\n * the return value; the slot is released when the response arrives and\n * the caller disposes the promise. So a regular call shows up as\n * `imports = 2` for its lifetime.\n *\n * - **Returned ReadableStream.** When the peer (the container) returns a\n * `ReadableStream` from an RPC method (e.g. `commands.executeStream`),\n * capnweb serializes it via `createPipe()`: the *server* allocates an\n * import slot, pumps `readable.pipeTo(writable)` over the wire, and\n * only releases the slot in `pipeTo().finally(() => hook.dispose())`\n * once the source stream ends or is canceled. On *our* side this\n * materializes as an export entry held for the same duration. So an\n * active stream return keeps `exports = 2` even after the RPC promise\n * that delivered the stream has already resolved.\n *\n * - **Stubs / RpcTargets passed across the wire.** Anything the peer\n * hands us (or we hand the peer) that isn't a plain value adds an\n * entry until both sides dispose it.\n *\n * The practical consequence for sleepAfter: the per-call promise lifecycle\n * is *not* a reliable signal of \"the container is done with this work\".\n * `commands.executeStream(...)` resolves in milliseconds with a stream\n * reference, but the container then writes to that stream for seconds. The\n * only signal that survives across the promise boundary is the export\n * entry — i.e. `getStats()`.\n *\n * So the strategy is:\n *\n * 1. Run a periodic poll while the WebSocket is connected.\n * 2. While `imports > 1 || exports > 1`, treat the session as busy:\n * hold the DO's `inflightRequests` counter at >= 1 and renew the\n * activity timeout each tick so the sleepAfter alarm gets pushed\n * forward.\n * 3. When the poll observes idle, decrement back to 0, renew once more\n * to reset the inactivity window from now, and schedule the WS\n * disconnect.\n *\n * On top of that, every RPC method invocation also fires `onActivity`\n * synchronously at call start. That keeps fast calls from racing the\n * poll cadence: even if a call begins and ends entirely between two\n * polls, the activity timeout was renewed at the start.\n */\n\nimport type {\n Logger,\n SandboxBackupAPI,\n SandboxCommandsAPI,\n SandboxDesktopAPI,\n SandboxFilesAPI,\n SandboxGitAPI,\n SandboxInterpreterAPI,\n SandboxPortsAPI,\n SandboxProcessesAPI,\n SandboxUtilsAPI,\n SandboxWatchAPI\n} from '@repo/shared';\nimport { createNoOpLogger } from '@repo/shared';\nimport {\n type ErrorCode,\n type ErrorResponse,\n getHttpStatus\n} from '@repo/shared/errors';\nimport {\n ContainerConnection,\n type ContainerConnectionOptions\n} from '../container-connection';\nimport { createErrorFromResponse } from '../errors/adapter';\nimport type { SandboxClient } from './sandbox-client';\nimport type { TransportMode } from './transport';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Close the idle capnweb WebSocket promptly so the DO can sleep. */\nconst DEFAULT_IDLE_DISCONNECT_MS = 1_000;\n\n/**\n * How often the busy/idle poller samples `getStats()`.\n *\n * Sets two worst-case bounds:\n *\n * 1. **Idle-detection lag.** Time between the session going idle on\n * the wire and the DO observing it (and arming the disconnect).\n * Bounded by `pollInterval`.\n * 2. **Activity-renewal lag while busy.** While a stream is active we\n * renew the DO's activity timeout once per tick. The alarm could\n * fire as late as `sleepAfter` after the last renew, so the\n * effective margin against a mid-stream sleep is\n * `sleepAfter - pollInterval`.\n *\n * **Invariant: `pollInterval` must be comfortably less than the\n * smallest configurable `sleepAfter`.** Aim for at least 2-3× headroom.\n * The minimum `sleepAfter` exercised by the E2E suite is 3s, so 1s gives\n * 3× margin and at least two renewals during a 3s window. If a smaller\n * `sleepAfter` is ever supported, drop this proportionally.\n */\nconst BUSY_POLL_INTERVAL_MS = 1_000;\n\n/**\n * Baseline getStats() values for an idle session. The bootstrap stub on each\n * side accounts for 1 import and 1 export.\n */\nconst IDLE_IMPORT_THRESHOLD = 1;\nconst IDLE_EXPORT_THRESHOLD = 1;\n\n// ---------------------------------------------------------------------------\n// Error translation\n// ---------------------------------------------------------------------------\n\ninterface RPCErrorPayload {\n code: string;\n message: string;\n context?: Record<string, unknown>;\n}\n\n/**\n * Translate a capnweb-propagated error into a typed SandboxError.\n *\n * capnweb only preserves `error.name` and `error.message` across the wire.\n * The container encodes the full error as a JSON object in the message\n * string: `{\"code\":\"...\",\"message\":\"...\",\"context\":{...}}`.\n */\nfunction translateRPCError(error: unknown): never {\n if (error instanceof Error) {\n try {\n const payload = JSON.parse(error.message) as RPCErrorPayload;\n if (\n typeof payload.code === 'string' &&\n typeof payload.message === 'string'\n ) {\n throw createErrorFromResponse({\n code: payload.code as ErrorCode,\n message: payload.message,\n context: payload.context ?? {},\n httpStatus: getHttpStatus(payload.code as ErrorCode),\n timestamp: new Date().toISOString()\n });\n }\n } catch (e) {\n if (e instanceof Error && e !== error) throw e;\n }\n }\n throw error;\n}\n\n/**\n * Wrap a capnweb RPC stub so that every method call translates errors\n * from the JSON wire format into typed SandboxError instances and signals\n * activity at call start.\n *\n * `onCallStarted` fires synchronously when an RPC method is invoked. The\n * RPCSandboxClient uses this to renew the DO's activity timeout\n * immediately, so even a call that completes entirely between two\n * busy-poll ticks still pushes the sleepAfter deadline forward.\n *\n * Note: there is no `onCallSettled` hook. A method whose returned promise\n * resolves with a `ReadableStream` is *not* finished when the promise\n * settles — capnweb keeps the export alive until the stream ends. The\n * busy/idle poll on `getStats()` is the source of truth for that.\n */\nfunction wrapStub<T extends object>(stub: T, onCallStarted: () => void): T {\n return new Proxy(stub, {\n get(target, prop, receiver) {\n const value = Reflect.get(target, prop, receiver);\n if (typeof value !== 'function') return value;\n // Use Reflect.apply instead of value.apply() because capnweb\n // stubs are Proxies that interpret .apply as an RPC property access.\n return (...args: unknown[]) => {\n onCallStarted();\n try {\n const result = Reflect.apply(\n value as (...a: unknown[]) => unknown,\n target,\n args\n );\n // capnweb RpcPromise is a Proxy with typeof 'function',\n // so check for .then directly rather than typeof 'object'.\n if (\n result != null &&\n typeof (result as { then?: unknown }).then === 'function'\n ) {\n return (result as Promise<unknown>).catch(translateRPCError);\n }\n return result;\n } catch (err) {\n translateRPCError(err);\n }\n };\n }\n });\n}\n\n// ---------------------------------------------------------------------------\n// Public client\n// ---------------------------------------------------------------------------\n\nexport interface RPCSandboxClientOptions extends ContainerConnectionOptions {\n /** Idle timeout before disconnecting the WebSocket (ms). Defaults to 1 000. */\n idleDisconnectMs?: number;\n /** Busy/idle poll interval (ms). Defaults to 1 000. */\n busyPollIntervalMs?: number;\n /**\n * Renew the DO's activity timeout. Fires at the start of every RPC call\n * and on every busy-poll tick while the session has work in flight.\n * Mirrors what `containerFetch()` does at the top of each HTTP request.\n */\n onActivity?: () => void;\n /**\n * Fires once when the capnweb session transitions from idle to busy\n * (an RPC call was started or a stream return is now in flight). The\n * Sandbox DO wires this to `inflightRequests++`, which makes\n * `isActivityExpired()` skip the sleepAfter comparison.\n */\n onSessionBusy?: () => void;\n /**\n * Fires once when the session transitions from busy back to idle\n * (all RPC promises settled and all stream exports released). The\n * Sandbox DO wires this to `inflightRequests = max(0, n-1)` and a\n * final `renewActivityTimeout()`, matching containerFetch's finally\n * block.\n */\n onSessionIdle?: () => void;\n}\n\n/**\n * SandboxClient backed by direct capnweb RPC.\n *\n * Drop-in replacement for SandboxClient when the capnweb transport is active.\n * All operations call the container's SandboxRPCAPI directly over capnweb,\n * bypassing the HTTP handler/router layer entirely.\n *\n * Manages its own WebSocket lifecycle: a fresh `ContainerConnection` is\n * created on demand and torn down after `idleDisconnectMs` of inactivity.\n * Busy/idle detection relies on `RpcSession.getStats()` which tracks all\n * in-flight RPC calls and stream exports — including long-lived streaming\n * RPCs that would be invisible to a simple per-call request counter (see\n * the file-level comment for the full rationale).\n */\nexport class RPCSandboxClient {\n private readonly connOptions: ContainerConnectionOptions;\n private readonly idleDisconnectMs: number;\n private readonly busyPollIntervalMs: number;\n private readonly logger: Logger;\n private readonly onActivity: (() => void) | undefined;\n private readonly onSessionBusy: (() => void) | undefined;\n private readonly onSessionIdle: (() => void) | undefined;\n\n private conn: ContainerConnection | null = null;\n private idleTimer: ReturnType<typeof setTimeout> | null = null;\n private busyPollTimer: ReturnType<typeof setInterval> | null = null;\n /** Tracks whether we currently believe the session is busy. */\n private busy = false;\n /**\n * Set the first time the poller observes `conn.isConnected() === true`,\n * cleared in `destroyConnection()`. Lets us distinguish \"the WebSocket\n * upgrade is still in progress\" (don't tear down) from \"we were\n * connected and the peer went away\" (do tear down).\n */\n private wasEverConnected = false;\n\n constructor(options: RPCSandboxClientOptions) {\n this.connOptions = {\n stub: options.stub,\n port: options.port,\n logger: options.logger\n };\n this.idleDisconnectMs =\n options.idleDisconnectMs ?? DEFAULT_IDLE_DISCONNECT_MS;\n this.busyPollIntervalMs =\n options.busyPollIntervalMs ?? BUSY_POLL_INTERVAL_MS;\n this.logger = options.logger ?? createNoOpLogger();\n this.onActivity = options.onActivity;\n this.onSessionBusy = options.onSessionBusy;\n this.onSessionIdle = options.onSessionIdle;\n }\n\n // -------------------------------------------------------------------------\n // Connection factory\n // -------------------------------------------------------------------------\n\n /**\n * Return the current connection, creating a new one if none exists or the\n * previous one was torn down by an idle disconnect. Starts the busy-poll\n * timer the first time a connection is materialized.\n */\n private getConnection(): ContainerConnection {\n if (!this.conn) {\n this.conn = new ContainerConnection(this.connOptions);\n this.startBusyPoll();\n }\n return this.conn;\n }\n\n // -------------------------------------------------------------------------\n // Activity & busy/idle tracking\n // -------------------------------------------------------------------------\n\n /**\n * Called synchronously at the start of each RPC method invocation.\n * Renews the DO activity timeout so the sleepAfter alarm is pushed\n * forward before the container processes the call.\n */\n private renewActivity = (): void => {\n this.onActivity?.();\n };\n\n /**\n * Sample `getStats()` and update busy/idle state. While busy, renews the\n * activity timeout each tick so an in-flight stream keeps pushing the\n * sleepAfter deadline forward. On the busy → idle edge, fires\n * `onSessionIdle` and schedules the WebSocket disconnect.\n *\n * If the WebSocket has dropped underneath us (container crash, network\n * blip) we tear the connection down here. `destroyConnection()` fires\n * `onSessionIdle` if we were busy, so the DO's inflight counter doesn't\n * stay pinned forever waiting for a peer that's never going to reply.\n */\n private pollBusyState = (): void => {\n const conn = this.conn;\n if (!conn) return;\n if (!conn.isConnected()) {\n // Two distinct cases share the same `isConnected() === false`\n // signal:\n // 1. The WebSocket upgrade is still in progress — we constructed\n // the connection in getConnection() but doConnect() hasn't\n // resolved yet. Sends are queued in the deferred transport.\n // Tearing down here would drop those queued calls on the floor.\n // 2. We were connected and the peer went away (container crash,\n // network blip). The session is dead, we must release\n // inflight and stop polling.\n // `wasEverConnected` distinguishes them: it flips to true the first\n // time we observe a live connection below.\n if (this.wasEverConnected) {\n this.destroyConnection();\n }\n return;\n }\n this.wasEverConnected = true;\n\n const { imports, exports } = conn.getStats();\n const isBusy =\n imports > IDLE_IMPORT_THRESHOLD || exports > IDLE_EXPORT_THRESHOLD;\n\n if (isBusy) {\n if (!this.busy) {\n this.busy = true;\n this.onSessionBusy?.();\n }\n // Renew on every busy tick — this is what keeps a long-lived stream\n // alive past sleepAfter.\n this.onActivity?.();\n this.clearIdleTimer();\n } else if (this.busy) {\n this.busy = false;\n this.onSessionIdle?.();\n this.scheduleIdleDisconnect();\n } else {\n // Already idle, no state change. Still ensure the disconnect timer\n // is armed (covers the case where we connected but never observed\n // any activity).\n if (!this.idleTimer) this.scheduleIdleDisconnect();\n }\n };\n\n private startBusyPoll(): void {\n if (this.busyPollTimer) return;\n this.busyPollTimer = setInterval(\n this.pollBusyState,\n this.busyPollIntervalMs\n );\n }\n\n private stopBusyPoll(): void {\n if (this.busyPollTimer) {\n clearInterval(this.busyPollTimer);\n this.busyPollTimer = null;\n }\n }\n\n private scheduleIdleDisconnect(): void {\n this.clearIdleTimer();\n this.idleTimer = setTimeout(() => {\n this.idleTimer = null;\n const conn = this.conn;\n if (!conn || !conn.isConnected()) return;\n\n // Re-check before disconnecting — a new call may have started.\n const { imports, exports } = conn.getStats();\n if (\n imports <= IDLE_IMPORT_THRESHOLD &&\n exports <= IDLE_EXPORT_THRESHOLD\n ) {\n this.logger.debug('Disconnecting idle capnweb connection');\n this.destroyConnection();\n }\n }, this.idleDisconnectMs);\n }\n\n private clearIdleTimer(): void {\n if (this.idleTimer) {\n clearTimeout(this.idleTimer);\n this.idleTimer = null;\n }\n }\n\n private destroyConnection(): void {\n this.stopBusyPoll();\n this.clearIdleTimer();\n // If we tear down while still believing the session is busy, fire the\n // idle transition so the DO's inflight counter doesn't leak.\n if (this.busy) {\n this.busy = false;\n this.onSessionIdle?.();\n }\n if (this.conn) {\n this.conn.disconnect();\n this.conn = null;\n }\n this.wasEverConnected = false;\n }\n\n // -------------------------------------------------------------------------\n // Sub-client getters\n // -------------------------------------------------------------------------\n\n // Each getter returns the corresponding nested RpcTarget stub\n // wrapped in a Proxy that translates RPC errors into SandboxError\n // subclasses. Explicit return types prevent capnweb's recursive\n // type machinery from expanding in .d.ts output (causes OOM).\n\n get commands(): SandboxCommandsAPI {\n return wrapStub(this.getConnection().rpc().commands, this.renewActivity);\n }\n get files(): SandboxFilesAPI {\n return wrapStub(this.getConnection().rpc().files, this.renewActivity);\n }\n get processes(): SandboxProcessesAPI {\n return wrapStub(this.getConnection().rpc().processes, this.renewActivity);\n }\n get ports(): SandboxPortsAPI {\n return wrapStub(this.getConnection().rpc().ports, this.renewActivity);\n }\n get git(): SandboxGitAPI {\n return wrapStub(this.getConnection().rpc().git, this.renewActivity);\n }\n get utils(): SandboxUtilsAPI {\n return wrapStub(this.getConnection().rpc().utils, this.renewActivity);\n }\n get backup(): SandboxBackupAPI {\n return wrapStub(this.getConnection().rpc().backup, this.renewActivity);\n }\n get desktop(): SandboxDesktopAPI {\n return wrapStub(this.getConnection().rpc().desktop, this.renewActivity);\n }\n get watch(): SandboxWatchAPI {\n return wrapStub(this.getConnection().rpc().watch, this.renewActivity);\n }\n get interpreter(): SandboxInterpreterAPI {\n return wrapStub(this.getConnection().rpc().interpreter, this.renewActivity);\n }\n\n setRetryTimeoutMs(_ms: number): void {\n // RPC transport does not use HTTP retry budgets\n }\n\n getTransportMode(): TransportMode {\n return 'rpc';\n }\n\n isWebSocketConnected(): boolean {\n return this.conn?.isConnected() ?? false;\n }\n\n async connect(): Promise<void> {\n await this.getConnection().connect();\n }\n\n disconnect(): void {\n this.destroyConnection();\n }\n\n async writeFileStream(\n path: string,\n stream: ReadableStream<Uint8Array>,\n sessionId: string\n ): Promise<{\n success: boolean;\n path: string;\n bytesWritten: number;\n timestamp: string;\n }> {\n return this.files.writeFileStream(path, stream, sessionId);\n }\n}\n\n/**\n * Extracts the public key set of a type. Used to verify that\n * RPCSandboxClient exposes the same top-level properties and methods\n * as SandboxClient without requiring deep structural compatibility\n * (sub-clients are capnweb stubs, not HTTP client class instances).\n */\ntype PublicKeys<T> = { [K in keyof T]: unknown };\n\n// Compile-time check: RPCSandboxClient has every public key that SandboxClient has.\nvoid (0 as unknown as PublicKeys<RPCSandboxClient> satisfies PublicKeys<SandboxClient>);\n","import {\n type FileChunk,\n type FileMetadata,\n type FileStreamEvent,\n parseSSEFrames,\n type SSEPartialEvent\n} from '@repo/shared';\n\n/**\n * Parse SSE (Server-Sent Events) lines from a stream\n */\nasync function* parseSSE(\n stream: ReadableStream<Uint8Array>\n): AsyncGenerator<FileStreamEvent> {\n const reader = stream.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n let currentEvent: SSEPartialEvent = { data: [] };\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n\n if (done) {\n break;\n }\n\n buffer += decoder.decode(value, { stream: true });\n const parsed = parseSSEFrames(buffer, currentEvent);\n buffer = parsed.remaining;\n currentEvent = parsed.currentEvent;\n\n for (const frame of parsed.events) {\n try {\n const event = JSON.parse(frame.data) as FileStreamEvent;\n yield event;\n } catch {\n // Skip invalid JSON events and continue processing\n }\n }\n }\n\n // Flush complete frame from final trailing buffer\n const finalParsed = parseSSEFrames(`${buffer}\\n\\n`, currentEvent);\n for (const frame of finalParsed.events) {\n try {\n const event = JSON.parse(frame.data) as FileStreamEvent;\n yield event;\n } catch {\n // Skip invalid JSON events and continue processing\n }\n }\n } finally {\n // Cancel the stream first to properly terminate HTTP connections when breaking early\n try {\n await reader.cancel();\n } catch {\n // Ignore cancel errors (stream may already be closed)\n }\n reader.releaseLock();\n }\n}\n\n/**\n * Stream a file from the sandbox with automatic base64 decoding for binary files\n *\n * @param stream - The ReadableStream from readFileStream()\n * @returns AsyncGenerator that yields FileChunk (string for text, Uint8Array for binary)\n *\n * @example\n * ```ts\n * const stream = await sandbox.readFileStream('/path/to/file.png');\n * for await (const chunk of streamFile(stream)) {\n * if (chunk instanceof Uint8Array) {\n * // Binary chunk\n * console.log('Binary chunk:', chunk.length, 'bytes');\n * } else {\n * // Text chunk\n * console.log('Text chunk:', chunk);\n * }\n * }\n * ```\n */\nexport async function* streamFile(\n stream: ReadableStream<Uint8Array>\n): AsyncGenerator<FileChunk, FileMetadata> {\n let metadata: FileMetadata | null = null;\n\n for await (const event of parseSSE(stream)) {\n switch (event.type) {\n case 'metadata':\n metadata = {\n mimeType: event.mimeType,\n size: event.size,\n isBinary: event.isBinary,\n encoding: event.encoding\n };\n break;\n\n case 'chunk':\n if (!metadata) {\n throw new Error('Received chunk before metadata');\n }\n\n if (metadata.isBinary && metadata.encoding === 'base64') {\n // Decode base64 to Uint8Array for binary files\n const binaryString = atob(event.data);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n yield bytes;\n } else {\n // Text files - yield as-is\n yield event.data;\n }\n break;\n\n case 'complete':\n if (!metadata) {\n throw new Error('Stream completed without metadata');\n }\n return metadata;\n\n case 'error':\n throw new Error(`File streaming error: ${event.error}`);\n }\n }\n\n throw new Error('Stream ended unexpectedly');\n}\n\n/**\n * Collect an entire file into memory from a stream\n *\n * @param stream - The ReadableStream from readFileStream()\n * @returns Object containing the file content and metadata\n *\n * @example\n * ```ts\n * const stream = await sandbox.readFileStream('/path/to/file.txt');\n * const { content, metadata } = await collectFile(stream);\n * console.log('Content:', content);\n * console.log('MIME type:', metadata.mimeType);\n * ```\n */\nexport async function collectFile(stream: ReadableStream<Uint8Array>): Promise<{\n content: string | Uint8Array;\n metadata: FileMetadata;\n}> {\n const chunks: Array<string | Uint8Array> = [];\n\n // Iterate through the generator and get the return value (metadata)\n const generator = streamFile(stream);\n let result = await generator.next();\n\n while (!result.done) {\n chunks.push(result.value);\n result = await generator.next();\n }\n\n const metadata = result.value;\n\n if (!metadata) {\n throw new Error('Failed to get file metadata');\n }\n\n // Combine chunks based on type\n if (metadata.isBinary) {\n // Binary file - combine Uint8Arrays\n const totalLength = chunks.reduce(\n (sum, chunk) => sum + (chunk instanceof Uint8Array ? chunk.length : 0),\n 0\n );\n const combined = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n if (chunk instanceof Uint8Array) {\n combined.set(chunk, offset);\n offset += chunk.length;\n }\n }\n return { content: combined, metadata };\n } else {\n // Text file - combine strings\n const combined = chunks.filter((c) => typeof c === 'string').join('');\n return { content: combined, metadata };\n }\n}\n","/**\n * Security utilities for URL construction and input validation\n *\n * This module contains critical security functions to prevent:\n * - URL injection attacks\n * - SSRF (Server-Side Request Forgery) attacks\n * - DNS rebinding attacks\n * - Host header injection\n * - Open redirect vulnerabilities\n */\n\nexport class SandboxSecurityError extends Error {\n constructor(\n message: string,\n public readonly code?: string\n ) {\n super(message);\n this.name = 'SandboxSecurityError';\n }\n}\n\n/**\n * Validates port numbers for sandbox services.\n *\n * Rules:\n * - Range: 1024-65535 (privileged ports require root, which containers don't have)\n * - Reserved: 3000 (sandbox control plane)\n */\nexport function validatePort(port: number): boolean {\n // Must be a valid integer\n if (!Number.isInteger(port)) {\n return false;\n }\n\n // Only allow non-system ports (1024-65535)\n if (port < 1024 || port > 65535) {\n return false;\n }\n\n const reservedPorts = [3000];\n\n if (reservedPorts.includes(port)) {\n return false;\n }\n\n return true;\n}\n\n/**\n * Sanitizes and validates sandbox IDs for DNS compliance and security\n * Only enforces critical requirements - allows maximum developer flexibility\n */\nexport function sanitizeSandboxId(id: string): string {\n // Basic validation: not empty, reasonable length limit (DNS subdomain limit is 63 chars)\n if (!id || id.length > 63) {\n throw new SandboxSecurityError(\n 'Sandbox ID must be 1-63 characters long.',\n 'INVALID_SANDBOX_ID_LENGTH'\n );\n }\n\n // DNS compliance: cannot start or end with hyphens (RFC requirement)\n if (id.startsWith('-') || id.endsWith('-')) {\n throw new SandboxSecurityError(\n 'Sandbox ID cannot start or end with hyphens (DNS requirement).',\n 'INVALID_SANDBOX_ID_HYPHENS'\n );\n }\n\n // Prevent reserved names that cause technical conflicts\n const reservedNames = [\n 'www',\n 'api',\n 'admin',\n 'root',\n 'system',\n 'cloudflare',\n 'workers'\n ];\n\n const lowerCaseId = id.toLowerCase();\n if (reservedNames.includes(lowerCaseId)) {\n throw new SandboxSecurityError(\n `Reserved sandbox ID '${id}' is not allowed.`,\n 'RESERVED_SANDBOX_ID'\n );\n }\n\n return id;\n}\n\n/**\n * Validates language for code interpreter\n * Only allows supported languages\n */\nexport function validateLanguage(language: string | undefined): void {\n if (!language) {\n return; // undefined is valid, will default to python\n }\n\n const supportedLanguages = [\n 'python',\n 'python3',\n 'javascript',\n 'js',\n 'node',\n 'typescript',\n 'ts'\n ];\n const normalized = language.toLowerCase();\n\n if (!supportedLanguages.includes(normalized)) {\n throw new SandboxSecurityError(\n `Unsupported language '${language}'. Supported languages: python, javascript, typescript`,\n 'INVALID_LANGUAGE'\n );\n }\n}\n","import {\n type CodeContext,\n type CreateContextOptions,\n Execution,\n type ExecutionError,\n type OutputMessage,\n type Result,\n ResultImpl,\n type RunCodeOptions,\n type SandboxInterpreterAPI\n} from '@repo/shared';\nimport { validateLanguage } from './security.js';\n\nexport class CodeInterpreter {\n private getInterpreterClient: () => SandboxInterpreterAPI;\n private contexts = new Map<string, CodeContext>();\n\n constructor(\n interpreterClient: SandboxInterpreterAPI | (() => SandboxInterpreterAPI)\n ) {\n this.getInterpreterClient =\n typeof interpreterClient === 'function'\n ? interpreterClient\n : () => interpreterClient;\n }\n\n /**\n * Create a new code execution context\n */\n async createCodeContext(\n options: CreateContextOptions = {}\n ): Promise<CodeContext> {\n // Validate language before sending to container\n validateLanguage(options.language);\n\n const context =\n await this.getInterpreterClient().createCodeContext(options);\n this.contexts.set(context.id, context);\n return context;\n }\n\n /**\n * Run code with optional context\n */\n async runCode(\n code: string,\n options: RunCodeOptions = {}\n ): Promise<Execution> {\n // Get or create context\n let context = options.context;\n if (!context) {\n // Try to find or create a default context for the language\n const language = options.language || 'python';\n context = await this.getOrCreateDefaultContext(language);\n }\n\n // Create execution object to collect results\n const execution = new Execution(code, context);\n\n // Stream execution\n await this.getInterpreterClient().runCodeStream(\n context.id,\n code,\n options.language,\n {\n onStdout: (output: OutputMessage) => {\n execution.logs.stdout.push(output.text);\n if (options.onStdout) return options.onStdout(output);\n },\n onStderr: (output: OutputMessage) => {\n execution.logs.stderr.push(output.text);\n if (options.onStderr) return options.onStderr(output);\n },\n onResult: async (result: Result) => {\n execution.results.push(new ResultImpl(result) as any);\n if (options.onResult) return options.onResult(result);\n },\n onError: (error: ExecutionError) => {\n execution.error = error;\n if (options.onError) return options.onError(error);\n }\n }\n );\n\n return execution;\n }\n\n /**\n * Run code and return a streaming response\n */\n async runCodeStream(\n code: string,\n options: RunCodeOptions = {}\n ): Promise<ReadableStream> {\n // Get or create context\n let context = options.context;\n if (!context) {\n const language = options.language || 'python';\n context = await this.getOrCreateDefaultContext(language);\n }\n\n // Use streamCode which handles both HTTP and WebSocket streaming\n return this.getInterpreterClient().streamCode(\n context.id,\n code,\n options.language\n );\n }\n\n /**\n * List all code contexts\n */\n async listCodeContexts(): Promise<CodeContext[]> {\n const contexts = await this.getInterpreterClient().listCodeContexts();\n\n // Update local cache\n for (const context of contexts) {\n this.contexts.set(context.id, context);\n }\n\n return contexts;\n }\n\n /**\n * Delete a code context\n */\n async deleteCodeContext(contextId: string): Promise<void> {\n await this.getInterpreterClient().deleteCodeContext(contextId);\n this.contexts.delete(contextId);\n }\n\n private async getOrCreateDefaultContext(\n language: 'python' | 'javascript' | 'typescript'\n ): Promise<CodeContext> {\n // Check if we have a cached context for this language\n for (const context of this.contexts.values()) {\n if (context.language === language) {\n return context;\n }\n }\n\n // Create new default context\n return this.createCodeContext({ language });\n }\n}\n","import { parseSSEFrames, type SSEPartialEvent } from '@repo/shared';\n\n/**\n * Server-Sent Events (SSE) parser for streaming responses\n * Converts ReadableStream<Uint8Array> to typed AsyncIterable<T>\n */\n\n/**\n * Parse a ReadableStream of SSE events into typed AsyncIterable\n * @param stream - The ReadableStream from fetch response\n * @param signal - Optional AbortSignal for cancellation\n */\nexport async function* parseSSEStream<T>(\n stream: ReadableStream<Uint8Array>,\n signal?: AbortSignal\n): AsyncIterable<T> {\n const reader = stream.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n let currentEvent: SSEPartialEvent = { data: [] };\n let isAborted = signal?.aborted ?? false;\n\n const emitEvent = (data: string): T | undefined => {\n if (data === '[DONE]' || data.trim() === '') {\n return undefined;\n }\n\n try {\n return JSON.parse(data) as T;\n } catch {\n return undefined;\n }\n };\n\n const onAbort = () => {\n isAborted = true;\n reader.cancel().catch(() => {\n // Reader may already be closed or canceled.\n });\n };\n\n if (signal && !signal.aborted) {\n signal.addEventListener('abort', onAbort);\n }\n\n try {\n while (true) {\n if (isAborted) {\n throw new Error('Operation was aborted');\n }\n\n const { done, value } = await reader.read();\n\n if (isAborted) {\n throw new Error('Operation was aborted');\n }\n\n if (done) break;\n\n // Decode chunk and parse complete SSE frames\n buffer += decoder.decode(value, { stream: true });\n const parsed = parseSSEFrames(buffer, currentEvent);\n buffer = parsed.remaining;\n currentEvent = parsed.currentEvent;\n\n for (const frame of parsed.events) {\n const event = emitEvent(frame.data);\n if (event !== undefined) {\n yield event;\n }\n }\n }\n\n if (isAborted) {\n throw new Error('Operation was aborted');\n }\n\n // Flush any complete frame that can be parsed from remaining data\n const finalParsed = parseSSEFrames(`${buffer}\\n\\n`, currentEvent);\n for (const frame of finalParsed.events) {\n const event = emitEvent(frame.data);\n if (event !== undefined) {\n yield event;\n }\n }\n } finally {\n if (signal) {\n signal.removeEventListener('abort', onAbort);\n }\n\n // Clean up resources\n try {\n await reader.cancel();\n } catch {}\n reader.releaseLock();\n }\n}\n\n/**\n * Helper to convert a Response with SSE stream directly to AsyncIterable\n * @param response - Response object with SSE stream\n * @param signal - Optional AbortSignal for cancellation\n */\nexport async function* responseToAsyncIterable<T>(\n response: Response,\n signal?: AbortSignal\n): AsyncIterable<T> {\n if (!response.ok) {\n throw new Error(\n `Response not ok: ${response.status} ${response.statusText}`\n );\n }\n\n if (!response.body) {\n throw new Error('No response body');\n }\n\n yield* parseSSEStream<T>(response.body, signal);\n}\n\n/**\n * Create an SSE-formatted ReadableStream from an AsyncIterable\n * (Useful for Worker endpoints that need to forward AsyncIterable as SSE)\n * @param events - AsyncIterable of events\n * @param options - Stream options\n */\nexport function asyncIterableToSSEStream<T>(\n events: AsyncIterable<T>,\n options?: {\n signal?: AbortSignal;\n serialize?: (event: T) => string;\n }\n): ReadableStream<Uint8Array> {\n const encoder = new TextEncoder();\n const serialize = options?.serialize || JSON.stringify;\n\n return new ReadableStream({\n async start(controller) {\n try {\n for await (const event of events) {\n if (options?.signal?.aborted) {\n controller.error(new Error('Operation was aborted'));\n break;\n }\n\n const data = serialize(event);\n const sseEvent = `data: ${data}\\n\\n`;\n controller.enqueue(encoder.encode(sseEvent));\n }\n\n // Send completion marker\n controller.enqueue(encoder.encode('data: [DONE]\\n\\n'));\n } catch (error) {\n controller.error(error);\n } finally {\n controller.close();\n }\n },\n\n cancel() {\n // Handle stream cancellation\n }\n });\n}\n","/**\n * Bucket mount and unmount error classes\n *\n * Validation errors (InvalidMountConfigError, MissingCredentialsError) are thrown\n * before any container interaction. BucketUnmountError is thrown after a failed\n * fusermount call inside the container.\n */\n\nimport { ErrorCode } from '@repo/shared/errors';\n\n/**\n * Base error for bucket mounting operations\n */\nexport class BucketMountError extends Error {\n public readonly code: ErrorCode;\n\n constructor(message: string, code: ErrorCode = ErrorCode.BUCKET_MOUNT_ERROR) {\n super(message);\n this.name = 'BucketMountError';\n this.code = code;\n }\n}\n\n/**\n * Thrown when S3FS mount command fails\n */\nexport class S3FSMountError extends BucketMountError {\n constructor(message: string) {\n super(message, ErrorCode.S3FS_MOUNT_ERROR);\n this.name = 'S3FSMountError';\n }\n}\n\n/**\n * Thrown when fusermount -u fails to unmount a FUSE filesystem\n */\nexport class BucketUnmountError extends BucketMountError {\n constructor(message: string) {\n super(message, ErrorCode.BUCKET_UNMOUNT_ERROR);\n this.name = 'BucketUnmountError';\n }\n}\n\n/**\n * Thrown when no credentials found in environment\n */\nexport class MissingCredentialsError extends BucketMountError {\n constructor(message: string) {\n super(message, ErrorCode.MISSING_CREDENTIALS);\n this.name = 'MissingCredentialsError';\n }\n}\n\n/**\n * Thrown when bucket name, mount path, or options are invalid\n */\nexport class InvalidMountConfigError extends BucketMountError {\n constructor(message: string) {\n super(message, ErrorCode.INVALID_MOUNT_CONFIG);\n this.name = 'InvalidMountConfigError';\n }\n}\n","import type { BucketCredentials, RemoteMountBucketOptions } from '@repo/shared';\nimport { MissingCredentialsError } from './errors';\n\n/**\n * Detect credentials for bucket mounting from environment variables\n * Priority order:\n * 1. Explicit options.credentials\n * 2. Standard AWS env vars: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY\n * 3. Standard R2 env vars: R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY\n * 4. Error: no credentials found\n *\n * @param options - Mount options\n * @param envVars - Environment variables\n * @returns Detected credentials\n * @throws MissingCredentialsError if no credentials found\n */\nexport function detectCredentials(\n options: RemoteMountBucketOptions,\n envVars: Record<string, string | undefined>\n): BucketCredentials {\n // Priority 1: Explicit credentials in options\n if (options.credentials) {\n return options.credentials;\n }\n\n // Priority 2: Standard AWS env vars\n const awsAccessKeyId = envVars.AWS_ACCESS_KEY_ID;\n const awsSecretAccessKey = envVars.AWS_SECRET_ACCESS_KEY;\n\n if (awsAccessKeyId && awsSecretAccessKey) {\n return {\n accessKeyId: awsAccessKeyId,\n secretAccessKey: awsSecretAccessKey\n };\n }\n\n /**\n * Priority 3: Standard R2 env vars\n *\n * AWS vars still take precedence over R2 vars in case both are set\n */\n const r2AccessKeyId = envVars.R2_ACCESS_KEY_ID;\n const r2SecretAccessKey = envVars.R2_SECRET_ACCESS_KEY;\n\n if (r2AccessKeyId && r2SecretAccessKey) {\n return {\n accessKeyId: r2AccessKeyId,\n secretAccessKey: r2SecretAccessKey\n };\n }\n\n // No credentials found - throw error with helpful message\n throw new MissingCredentialsError(\n `No credentials found. Set R2_ACCESS_KEY_ID and R2_SECRET_ACCESS_KEY ` +\n `or AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY ` +\n `environment variables, or pass explicit credentials in options.`\n );\n}\n","/**\n * Provider detection and s3fs flag configuration\n *\n * Based on s3fs-fuse documentation:\n * https://github.com/s3fs-fuse/s3fs-fuse/wiki/Non-Amazon-S3\n */\n\nimport type { BucketProvider } from '@repo/shared';\n\n/**\n * Detect provider from endpoint URL using pattern matching\n */\nexport function detectProviderFromUrl(endpoint: string): BucketProvider | null {\n try {\n const url = new URL(endpoint);\n const hostname = url.hostname.toLowerCase();\n\n if (hostname.endsWith('.r2.cloudflarestorage.com')) {\n return 'r2';\n }\n\n // Match AWS S3: *.amazonaws.com or s3.amazonaws.com\n if (\n hostname.endsWith('.amazonaws.com') ||\n hostname === 's3.amazonaws.com'\n ) {\n return 's3';\n }\n\n if (hostname === 'storage.googleapis.com') {\n return 'gcs';\n }\n\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Get s3fs flags for a given provider\n *\n * Based on s3fs-fuse wiki recommendations:\n * https://github.com/s3fs-fuse/s3fs-fuse/wiki/Non-Amazon-S3\n */\nexport function getProviderFlags(provider: BucketProvider | null): string[] {\n if (!provider) {\n return ['use_path_request_style'];\n }\n\n switch (provider) {\n case 'r2':\n return ['nomixupload'];\n\n case 's3':\n return [];\n\n case 'gcs':\n return [];\n\n default:\n return ['use_path_request_style'];\n }\n}\n\n/**\n * Resolve s3fs options by combining provider defaults with user overrides\n */\nexport function resolveS3fsOptions(\n provider: BucketProvider | null,\n userOptions?: string[]\n): string[] {\n const providerFlags = getProviderFlags(provider);\n\n if (!userOptions || userOptions.length === 0) {\n return providerFlags;\n }\n\n // Merge provider flags with user options\n // User options take precedence (come last in the array)\n const allFlags = [...providerFlags, ...userOptions];\n\n // Deduplicate flags (keep last occurrence)\n const flagMap = new Map<string, string>();\n\n for (const flag of allFlags) {\n // Split on '=' to get the flag name\n const [flagName] = flag.split('=');\n flagMap.set(flagName, flag);\n }\n\n return Array.from(flagMap.values());\n}\n","import { InvalidMountConfigError } from './errors';\n\nexport function validatePrefix(prefix: string): void {\n if (!prefix.startsWith('/')) {\n throw new InvalidMountConfigError(\n `Prefix must start with '/': \"${prefix}\"`\n );\n }\n}\n\nexport function validateBucketName(bucket: string, mountPath: string): void {\n if (bucket.includes(':')) {\n const [bucketName, prefixPart] = bucket.split(':');\n throw new InvalidMountConfigError(\n `Bucket name cannot contain ':'. To mount a prefix, use the 'prefix' option:\\n` +\n ` mountBucket('${bucketName}', '${mountPath}', { ...options, prefix: '${prefixPart}' })`\n );\n }\n\n const bucketNameRegex = /^[a-z0-9]([a-z0-9.-]{0,61}[a-z0-9])?$/;\n if (!bucketNameRegex.test(bucket)) {\n throw new InvalidMountConfigError(\n `Invalid bucket name: \"${bucket}\". Bucket names must be 3-63 characters, ` +\n `lowercase alphanumeric, dots, or hyphens, and cannot start/end with dots or hyphens.`\n );\n }\n}\n\n/**\n * Builds the s3fs source string from bucket name and optional prefix.\n * Format: \"bucket\" or \"bucket:/prefix/\" for subdirectory mounts.\n *\n * @param bucket - The bucket name\n * @param prefix - Optional prefix/subdirectory path\n * @returns The s3fs source string\n */\nexport function buildS3fsSource(bucket: string, prefix?: string): string {\n return prefix ? `${bucket}:${prefix}` : bucket;\n}\n","import path from 'node:path/posix';\nimport type { FileWatchSSEEvent, Logger } from '@repo/shared';\nimport type { SandboxClient } from './clients';\nimport type { RPCSandboxClient } from './clients/rpc-sandbox-client';\nimport { parseSSEStream } from './sse-parser';\nimport { validatePrefix } from './storage-mount';\n\nconst DEFAULT_POLL_INTERVAL_MS = 1000;\nconst DEFAULT_ECHO_SUPPRESS_TTL_MS = 2000;\nconst MAX_BACKOFF_MS = 30_000;\nconst SYNC_CONCURRENCY = 5;\n\ninterface R2ObjectSnapshot {\n etag: string;\n size: number;\n}\n\ninterface LocalMountSyncOptions {\n bucket: R2Bucket;\n mountPath: string;\n prefix: string | undefined;\n readOnly: boolean;\n client: SandboxClient | RPCSandboxClient;\n sessionId: string;\n logger: Logger;\n pollIntervalMs?: number;\n echoSuppressTtlMs?: number;\n}\n\n/**\n * Manages bidirectional sync between an R2 binding and a container directory.\n *\n * R2 -> Container: polls bucket.list() to detect changes, then transfers diffs.\n * Container -> R2: uses inotifywait via the watch API to detect file changes.\n */\nexport class LocalMountSyncManager {\n private readonly bucket: R2Bucket;\n private readonly mountPath: string;\n private readonly prefix: string | undefined;\n private readonly readOnly: boolean;\n private readonly client: SandboxClient | RPCSandboxClient;\n private readonly sessionId: string;\n private readonly logger: Logger;\n private readonly pollIntervalMs: number;\n\n private readonly echoSuppressTtlMs: number;\n\n private snapshot: Map<string, R2ObjectSnapshot> = new Map();\n private echoSuppressSet: Set<string> = new Set();\n private pollTimer: ReturnType<typeof setTimeout> | null = null;\n private watchReconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private watchAbortController: AbortController | null = null;\n private running = false;\n private consecutivePollFailures = 0;\n private consecutiveWatchFailures = 0;\n\n constructor(options: LocalMountSyncOptions) {\n this.bucket = options.bucket;\n this.mountPath = options.mountPath;\n if (options.prefix !== undefined) {\n validatePrefix(options.prefix);\n }\n // R2 keys never have leading slashes. Convert the validated '/'-prefixed\n // value into bare R2 key format for list() and put().\n this.prefix = options.prefix?.replace(/^\\//, '') || undefined;\n this.readOnly = options.readOnly;\n this.client = options.client;\n this.sessionId = options.sessionId;\n this.logger = options.logger.child({ operation: 'local-mount-sync' });\n this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n this.echoSuppressTtlMs =\n options.echoSuppressTtlMs ?? DEFAULT_ECHO_SUPPRESS_TTL_MS;\n }\n\n /**\n * Start bidirectional sync. Performs initial full sync, then starts\n * the R2 poll loop and (if not readOnly) the container watch loop.\n */\n async start(): Promise<void> {\n this.running = true;\n\n await this.client.files.mkdir(this.mountPath, this.sessionId, {\n recursive: true\n });\n\n await this.fullSyncR2ToContainer();\n this.schedulePoll();\n\n if (!this.readOnly) {\n this.startContainerWatch();\n }\n\n this.logger.info('Local mount sync started', {\n mountPath: this.mountPath,\n prefix: this.prefix,\n readOnly: this.readOnly,\n pollIntervalMs: this.pollIntervalMs\n });\n }\n\n /**\n * Stop all sync activity and clean up resources.\n */\n async stop(): Promise<void> {\n this.running = false;\n\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = null;\n }\n\n if (this.watchReconnectTimer) {\n clearTimeout(this.watchReconnectTimer);\n this.watchReconnectTimer = null;\n }\n\n if (this.watchAbortController) {\n this.watchAbortController.abort();\n this.watchAbortController = null;\n }\n\n this.snapshot.clear();\n this.echoSuppressSet.clear();\n\n this.logger.info('Local mount sync stopped', {\n mountPath: this.mountPath\n });\n }\n\n private async fullSyncR2ToContainer(): Promise<void> {\n const objects = await this.listAllR2Objects();\n const newSnapshot = new Map<string, R2ObjectSnapshot>();\n\n // No echo suppression needed: this runs before startContainerWatch() in start().\n // Process in batches to limit concurrent HTTP requests\n for (let i = 0; i < objects.length; i += SYNC_CONCURRENCY) {\n const batch = objects.slice(i, i + SYNC_CONCURRENCY);\n await Promise.all(\n batch.map(async (obj) => {\n const containerPath = this.r2KeyToContainerPath(obj.key);\n newSnapshot.set(obj.key, { etag: obj.etag, size: obj.size });\n await this.ensureParentDir(containerPath);\n await this.transferR2ObjectToContainer(obj.key, containerPath);\n })\n );\n }\n\n this.snapshot = newSnapshot;\n this.logger.debug('Initial R2 -> Container sync complete', {\n objectCount: objects.length\n });\n }\n\n private schedulePoll(): void {\n if (!this.running) return;\n\n const backoffMs =\n this.consecutivePollFailures > 0\n ? Math.min(\n this.pollIntervalMs * 2 ** this.consecutivePollFailures,\n MAX_BACKOFF_MS\n )\n : this.pollIntervalMs;\n\n this.pollTimer = setTimeout(async () => {\n try {\n await this.pollR2ForChanges();\n this.consecutivePollFailures = 0;\n } catch (error) {\n this.consecutivePollFailures++;\n this.logger.error(\n 'R2 poll cycle failed',\n error instanceof Error ? error : new Error(String(error))\n );\n }\n this.schedulePoll();\n }, backoffMs);\n }\n\n private async pollR2ForChanges(): Promise<void> {\n const objects = await this.listAllR2Objects();\n const newSnapshot = new Map<string, R2ObjectSnapshot>();\n\n // Collect changed objects first, then transfer in batches\n const changed: Array<{ key: string; action: 'created' | 'modified' }> = [];\n for (const obj of objects) {\n newSnapshot.set(obj.key, { etag: obj.etag, size: obj.size });\n const existing = this.snapshot.get(obj.key);\n if (!existing || existing.etag !== obj.etag) {\n changed.push({\n key: obj.key,\n action: existing ? 'modified' : 'created'\n });\n }\n }\n\n for (let i = 0; i < changed.length; i += SYNC_CONCURRENCY) {\n const batch = changed.slice(i, i + SYNC_CONCURRENCY);\n await Promise.all(\n batch.map(async ({ key, action }) => {\n try {\n const containerPath = this.r2KeyToContainerPath(key);\n await this.ensureParentDir(containerPath);\n this.suppressEcho(containerPath);\n await this.transferR2ObjectToContainer(key, containerPath);\n this.logger.debug('R2 -> Container: synced object', {\n key,\n action\n });\n } catch (error) {\n this.logger.error(\n `R2 -> Container: failed to sync object ${key}`,\n error instanceof Error ? error : new Error(String(error))\n );\n }\n })\n );\n }\n\n for (const [key] of this.snapshot) {\n if (!newSnapshot.has(key)) {\n const containerPath = this.r2KeyToContainerPath(key);\n this.suppressEcho(containerPath);\n\n try {\n await this.client.files.deleteFile(containerPath, this.sessionId);\n this.logger.debug('R2 -> Container: deleted file', { key });\n } catch (error) {\n this.logger.error(\n 'R2 -> Container: failed to delete',\n error instanceof Error ? error : new Error(String(error))\n );\n }\n }\n }\n\n this.snapshot = newSnapshot;\n }\n\n private async listAllR2Objects(): Promise<\n Array<{ key: string; etag: string; size: number }>\n > {\n const results: Array<{ key: string; etag: string; size: number }> = [];\n let cursor: string | undefined;\n\n do {\n const listResult = await this.bucket.list({\n ...(this.prefix && { prefix: this.prefix }),\n ...(cursor && { cursor })\n });\n\n for (const obj of listResult.objects) {\n results.push({ key: obj.key, etag: obj.etag, size: obj.size });\n }\n\n cursor = listResult.truncated ? listResult.cursor : undefined;\n } while (cursor);\n\n return results;\n }\n\n private async transferR2ObjectToContainer(\n key: string,\n containerPath: string\n ): Promise<void> {\n const obj = await this.bucket.get(key);\n if (!obj) return;\n\n const arrayBuffer = await obj.arrayBuffer();\n const base64 = uint8ArrayToBase64(new Uint8Array(arrayBuffer));\n\n await this.client.files.writeFile(containerPath, base64, this.sessionId, {\n encoding: 'base64'\n });\n }\n\n private async ensureParentDir(containerPath: string): Promise<void> {\n const parentDir = containerPath.substring(\n 0,\n containerPath.lastIndexOf('/')\n );\n if (parentDir && parentDir !== this.mountPath) {\n await this.client.files.mkdir(parentDir, this.sessionId, {\n recursive: true\n });\n }\n }\n\n private startContainerWatch(): void {\n this.watchAbortController = new AbortController();\n this.runWatchWithRetry();\n }\n\n private runWatchWithRetry(): void {\n if (!this.running) return;\n\n this.runContainerWatchLoop()\n .then(() => {\n // Stream ended cleanly (e.g. server closed it). Reconnect unless stopped.\n this.consecutiveWatchFailures = 0;\n this.scheduleWatchReconnect();\n })\n .catch((error) => {\n if (!this.running) return;\n this.consecutiveWatchFailures++;\n this.logger.error(\n 'Container watch loop failed',\n error instanceof Error ? error : new Error(String(error))\n );\n this.scheduleWatchReconnect();\n });\n }\n\n private scheduleWatchReconnect(): void {\n if (!this.running) return;\n\n const backoffMs =\n this.consecutiveWatchFailures > 0\n ? Math.min(\n this.pollIntervalMs * 2 ** this.consecutiveWatchFailures,\n MAX_BACKOFF_MS\n )\n : this.pollIntervalMs;\n\n this.logger.debug('Reconnecting container watch', {\n backoffMs,\n failures: this.consecutiveWatchFailures\n });\n\n this.watchReconnectTimer = setTimeout(() => {\n this.watchReconnectTimer = null;\n if (!this.running) return;\n this.watchAbortController = new AbortController();\n this.runWatchWithRetry();\n }, backoffMs);\n }\n\n private async runContainerWatchLoop(): Promise<void> {\n const stream = await this.client.watch.watch({\n path: this.mountPath,\n recursive: true,\n sessionId: this.sessionId\n });\n\n for await (const event of parseSSEStream<FileWatchSSEEvent>(\n stream,\n this.watchAbortController?.signal\n )) {\n if (!this.running) break;\n\n // Successful event received — reset failure counter\n this.consecutiveWatchFailures = 0;\n\n if (event.type !== 'event') continue;\n if (event.isDirectory) continue;\n\n const containerPath = event.path;\n\n // Skip echo from our own R2 -> Container writes\n if (this.echoSuppressSet.has(containerPath)) continue;\n\n const r2Key = this.containerPathToR2Key(containerPath);\n if (!r2Key) continue;\n\n try {\n switch (event.eventType) {\n case 'create':\n case 'modify':\n case 'move_to': {\n await this.uploadFileToR2(containerPath, r2Key);\n this.logger.debug('Container -> R2: synced file', {\n path: containerPath,\n key: r2Key,\n action: event.eventType\n });\n break;\n }\n\n case 'delete':\n case 'move_from': {\n await this.bucket.delete(r2Key);\n this.snapshot.delete(r2Key);\n this.logger.debug('Container -> R2: deleted object', {\n path: containerPath,\n key: r2Key\n });\n break;\n }\n }\n } catch (error) {\n this.logger.error(\n `Container -> R2 sync failed for ${containerPath}`,\n error instanceof Error ? error : new Error(String(error))\n );\n }\n }\n }\n\n /**\n * Read a container file and upload it to R2, then update the local\n * snapshot so the next poll cycle doesn't echo the write back.\n */\n private async uploadFileToR2(\n containerPath: string,\n r2Key: string\n ): Promise<void> {\n const result = await this.client.files.readFile(\n containerPath,\n this.sessionId,\n { encoding: 'base64' }\n );\n const bytes = base64ToUint8Array(result.content);\n await this.bucket.put(r2Key, bytes);\n\n const head = await this.bucket.head(r2Key);\n if (head) {\n this.snapshot.set(r2Key, { etag: head.etag, size: head.size });\n }\n }\n\n private suppressEcho(containerPath: string): void {\n this.echoSuppressSet.add(containerPath);\n setTimeout(() => {\n this.echoSuppressSet.delete(containerPath);\n }, this.echoSuppressTtlMs);\n }\n\n private r2KeyToContainerPath(key: string): string {\n let relativePath = key;\n if (this.prefix) {\n relativePath = key.startsWith(this.prefix)\n ? key.slice(this.prefix.length)\n : key;\n }\n return path.join(this.mountPath, relativePath);\n }\n\n private containerPathToR2Key(containerPath: string): string | null {\n const resolved = path.resolve(containerPath);\n const mount = path.resolve(this.mountPath);\n\n if (!resolved.startsWith(mount)) return null;\n\n const relativePath = path.relative(mount, resolved);\n if (!relativePath || relativePath.startsWith('..')) return null;\n\n return this.prefix ? path.join(this.prefix, relativePath) : relativePath;\n }\n}\n\nfunction uint8ArrayToBase64(bytes: Uint8Array): string {\n return Buffer.from(bytes).toString('base64');\n}\n\nfunction base64ToUint8Array(base64: string): Uint8Array {\n return new Uint8Array(Buffer.from(base64, 'base64'));\n}\n","import { switchPort } from '@cloudflare/containers';\nimport type { PtyOptions } from '@repo/shared';\n\nexport async function proxyTerminal(\n stub: { fetch: (request: Request) => Promise<Response> },\n sessionId: string,\n request: Request,\n options?: PtyOptions\n): Promise<Response> {\n if (!sessionId || typeof sessionId !== 'string') {\n throw new Error('sessionId is required for terminal access');\n }\n\n const upgradeHeader = request.headers.get('Upgrade');\n if (upgradeHeader?.toLowerCase() !== 'websocket') {\n throw new Error('terminal() requires a WebSocket upgrade request');\n }\n\n const params = new URLSearchParams({ sessionId });\n if (options?.cols) params.set('cols', String(options.cols));\n if (options?.rows) params.set('rows', String(options.rows));\n if (options?.shell) params.set('shell', options.shell);\n\n const ptyUrl = `http://localhost/ws/pty?${params}`;\n const ptyRequest = new Request(ptyUrl, request);\n\n return stub.fetch(switchPort(ptyRequest, 3000));\n}\n","import { switchPort } from '@cloudflare/containers';\nimport { createLogger, type LogContext, TraceContext } from '@repo/shared';\nimport { getSandbox, type Sandbox } from './sandbox';\nimport { sanitizeSandboxId, validatePort } from './security';\n\nexport interface SandboxEnv<T extends Sandbox<any> = Sandbox<any>> {\n Sandbox: DurableObjectNamespace<T>;\n}\n\nexport interface RouteInfo {\n port: number;\n sandboxId: string;\n path: string;\n token: string;\n}\n\nexport async function proxyToSandbox<\n T extends Sandbox<any>,\n E extends SandboxEnv<T>\n>(request: Request, env: E): Promise<Response | null> {\n // Create logger context for this request\n const traceId =\n TraceContext.fromHeaders(request.headers) || TraceContext.generate();\n const logger = createLogger({\n component: 'sandbox-do',\n traceId,\n operation: 'proxy'\n });\n\n try {\n const url = new URL(request.url);\n const routeInfo = extractSandboxRoute(url);\n\n if (!routeInfo) {\n return null; // Not a request to an exposed container port\n }\n\n const { sandboxId, port, path, token } = routeInfo;\n // Preview URLs always use normalized (lowercase) IDs\n const sandbox = getSandbox(env.Sandbox, sandboxId, { normalizeId: true });\n\n // Critical security check: Validate token (mandatory for all user ports)\n // Skip check for control plane port 3000\n if (port !== 3000) {\n // Validate the token matches the port\n const isValidToken = await sandbox.validatePortToken(port, token);\n if (!isValidToken) {\n logger.warn('Invalid token access blocked', {\n port,\n sandboxId,\n path,\n hostname: url.hostname,\n url: request.url,\n method: request.method,\n userAgent: request.headers.get('User-Agent') || 'unknown'\n });\n\n return new Response(\n JSON.stringify({\n error: `Access denied: Invalid token or port not exposed`,\n code: 'INVALID_TOKEN'\n }),\n {\n status: 404,\n headers: {\n 'Content-Type': 'application/json'\n }\n }\n );\n }\n }\n\n // Detect WebSocket upgrade request\n const upgradeHeader = request.headers.get('Upgrade');\n if (upgradeHeader?.toLowerCase() === 'websocket') {\n // WebSocket path: Must use fetch() not containerFetch()\n // This bypasses JSRPC serialization boundary which cannot handle WebSocket upgrades\n return await sandbox.fetch(switchPort(request, port));\n }\n\n // Build proxy request with proper headers\n let proxyUrl: string;\n\n // Route based on the target port\n if (port !== 3000) {\n // Route directly to user's service on the specified port\n proxyUrl = `http://localhost:${port}${path}${url.search}`;\n } else {\n // Port 3000 is our control plane - route normally\n proxyUrl = `http://localhost:3000${path}${url.search}`;\n }\n\n const headers: Record<string, string> = {\n 'X-Original-URL': request.url,\n 'X-Forwarded-Host': url.hostname,\n 'X-Forwarded-Proto': url.protocol.replace(':', ''),\n 'X-Sandbox-Name': sandboxId\n };\n request.headers.forEach((value, key) => {\n headers[key] = value;\n });\n\n const proxyRequest = new Request(proxyUrl, {\n method: request.method,\n headers,\n body: request.body,\n // @ts-expect-error - duplex required for body streaming in modern runtimes\n duplex: 'half',\n redirect: 'manual' // Do not follow redirects, return them to the client to handle\n });\n\n return await sandbox.containerFetch(proxyRequest, port);\n } catch (error) {\n logger.error(\n 'Proxy routing error',\n error instanceof Error ? error : new Error(String(error))\n );\n return new Response('Proxy routing error', { status: 500 });\n }\n}\n\nfunction extractSandboxRoute(url: URL): RouteInfo | null {\n // URL format: {port}-{sandboxId}-{token}.{domain}\n // Tokens are [a-z0-9_]+, so we split at the last hyphen to handle sandboxIds with hyphens (UUIDs)\n const dotIndex = url.hostname.indexOf('.');\n if (dotIndex === -1) {\n return null;\n }\n\n const subdomain = url.hostname.slice(0, dotIndex);\n const domain = url.hostname.slice(dotIndex + 1);\n\n // Extract port (digits at start followed by hyphen)\n const firstHyphen = subdomain.indexOf('-');\n if (firstHyphen === -1) {\n return null;\n }\n\n const portStr = subdomain.slice(0, firstHyphen);\n if (!/^\\d{4,5}$/.test(portStr)) {\n return null;\n }\n\n const port = parseInt(portStr, 10);\n if (!validatePort(port)) {\n return null;\n }\n\n // Extract token (last hyphen-delimited segment) and sandboxId (everything between port and token)\n const rest = subdomain.slice(firstHyphen + 1);\n const lastHyphen = rest.lastIndexOf('-');\n if (lastHyphen === -1) {\n return null;\n }\n\n const sandboxId = rest.slice(0, lastHyphen);\n const token = rest.slice(lastHyphen + 1);\n\n // No hyphens in tokens: URL is {port}-{sandboxId}-{token}.{domain}\n // We split at the LAST hyphen, so hyphens in tokens would be ambiguous\n if (!/^[a-z0-9_]+$/.test(token) || token.length === 0 || token.length > 63) {\n return null;\n }\n\n // Validate and sanitize sandboxId\n if (sandboxId.length === 0 || sandboxId.length > 63) {\n return null;\n }\n\n let sanitizedSandboxId: string;\n try {\n sanitizedSandboxId = sanitizeSandboxId(sandboxId);\n } catch {\n return null;\n }\n\n return {\n port,\n sandboxId: sanitizedSandboxId,\n path: url.pathname || '/',\n token\n };\n}\n\nexport function isLocalhostPattern(hostname: string): boolean {\n // Handle IPv6 addresses in brackets (with or without port)\n if (hostname.startsWith('[')) {\n if (hostname.includes(']:')) {\n // [::1]:port format\n const ipv6Part = hostname.substring(0, hostname.indexOf(']:') + 1);\n return ipv6Part === '[::1]';\n } else {\n // [::1] format without port\n return hostname === '[::1]';\n }\n }\n\n // Handle bare IPv6 without brackets\n if (hostname === '::1') {\n return true;\n }\n\n // For IPv4 and regular hostnames, split on colon to remove port\n const hostPart = hostname.split(':')[0];\n\n return (\n hostPart === 'localhost' ||\n hostPart === '127.0.0.1' ||\n hostPart === '0.0.0.0'\n );\n}\n","/**\n * SDK version - automatically synchronized with package.json by Changesets\n * This file is auto-updated by .github/changeset-version.ts during releases\n * DO NOT EDIT MANUALLY - Changes will be overwritten on the next version bump\n */\nexport const SDK_VERSION = '0.9.1';\n","import { Container, getContainer, switchPort } from '@cloudflare/containers';\nimport type {\n BackupOptions,\n BucketCredentials,\n BucketProvider,\n CheckChangesOptions,\n CheckChangesResult,\n CodeContext,\n CreateContextOptions,\n DirectoryBackup,\n ExecEvent,\n ExecOptions,\n ExecResult,\n ExecutionResult,\n ExecutionSession,\n ISandbox,\n LocalMountBucketOptions,\n LogEvent,\n MountBucketOptions,\n PortWatchEvent,\n Process,\n ProcessOptions,\n ProcessStatus,\n PtyOptions,\n RemoteMountBucketOptions,\n RestoreBackupResult,\n RunCodeOptions,\n SandboxOptions,\n SessionOptions,\n StreamOptions,\n WaitForExitResult,\n WaitForLogResult,\n WaitForPortOptions,\n WatchOptions\n} from '@repo/shared';\nimport {\n createLogger,\n filterEnvVars,\n getEnvString,\n logCanonicalEvent,\n partitionEnvVars,\n type SessionDeleteResult,\n shellEscape,\n TraceContext\n} from '@repo/shared';\nimport { BACKUP_ALLOWED_PREFIXES } from '@repo/shared/backup';\nimport { AwsClient } from 'aws4fetch';\nimport { type Desktop, type ExecuteResponse, SandboxClient } from './clients';\nimport { RPCSandboxClient } from './clients/rpc-sandbox-client';\nimport type { ErrorResponse } from './errors';\nimport {\n BackupCreateError,\n BackupExpiredError,\n BackupNotFoundError,\n BackupRestoreError,\n CustomDomainRequiredError,\n ErrorCode,\n InvalidBackupConfigError,\n PortNotExposedError,\n ProcessExitedBeforeReadyError,\n ProcessReadyTimeoutError,\n SessionAlreadyExistsError\n} from './errors';\nimport { collectFile } from './file-stream';\nimport { CodeInterpreter } from './interpreter';\nimport { LocalMountSyncManager } from './local-mount-sync';\nimport { proxyTerminal } from './pty';\nimport { isLocalhostPattern } from './request-handler';\nimport {\n SandboxSecurityError,\n sanitizeSandboxId,\n validatePort\n} from './security';\nimport { parseSSEStream } from './sse-parser';\nimport {\n buildS3fsSource,\n detectCredentials,\n detectProviderFromUrl,\n resolveS3fsOptions,\n validateBucketName,\n validatePrefix\n} from './storage-mount';\nimport {\n BucketUnmountError,\n InvalidMountConfigError,\n S3FSMountError\n} from './storage-mount/errors';\nimport type {\n FuseMountInfo,\n LocalSyncMountInfo,\n MountInfo\n} from './storage-mount/types';\nimport { SDK_VERSION } from './version';\n\n/**\n * Persisted record for a single exposed port. `token` authorizes preview\n * URL requests; `name` is the optional friendly name the caller passed to\n * `exposePort()` and is preserved across container restarts.\n */\ntype PortTokenEntry = {\n token: string;\n name?: string;\n};\n\ntype SandboxConfiguration = {\n sandboxName?: {\n name: string;\n normalizeId?: boolean;\n };\n sleepAfter?: string | number;\n keepAlive?: boolean;\n containerTimeouts?: NonNullable<SandboxOptions['containerTimeouts']>;\n transport?: 'http' | 'websocket' | 'rpc';\n};\n\ntype CachedSandboxConfiguration = {\n sandboxName?: string;\n normalizeId?: boolean;\n sleepAfter?: string | number;\n keepAlive?: boolean;\n containerTimeouts?: NonNullable<SandboxOptions['containerTimeouts']>;\n transport?: 'http' | 'websocket' | 'rpc';\n};\n\ntype ConfigurableSandboxStub = {\n configure?: (configuration: SandboxConfiguration) => Promise<void>;\n setSandboxName?: (name: string, normalizeId?: boolean) => Promise<void>;\n setSleepAfter?: (sleepAfter: string | number) => Promise<void>;\n setKeepAlive?: (keepAlive: boolean) => Promise<void>;\n setContainerTimeouts?: (\n timeouts: NonNullable<SandboxOptions['containerTimeouts']>\n ) => Promise<void>;\n setTransport?: (transport: 'http' | 'websocket' | 'rpc') => Promise<void>;\n};\n\nconst sandboxConfigurationCache = new WeakMap<\n object,\n Map<string, CachedSandboxConfiguration>\n>();\n\nconst BACKUP_DEFAULT_TTL_SECONDS = 259200;\nconst BACKUP_MAX_NAME_LENGTH = 256;\nconst BACKUP_CONTAINER_DIR = '/var/backups';\nconst BACKUP_STORAGE_PREFIX = 'backups';\nconst BACKUP_ARCHIVE_OBJECT_NAME = 'data.sqsh';\nconst BACKUP_METADATA_OBJECT_NAME = 'meta.json';\n\nfunction getNamespaceConfigurationCache(\n namespace: object\n): Map<string, CachedSandboxConfiguration> {\n const existing = sandboxConfigurationCache.get(namespace);\n if (existing) {\n return existing;\n }\n\n const created = new Map<string, CachedSandboxConfiguration>();\n sandboxConfigurationCache.set(namespace, created);\n return created;\n}\n\nfunction sameContainerTimeouts(\n left?: NonNullable<SandboxOptions['containerTimeouts']>,\n right?: NonNullable<SandboxOptions['containerTimeouts']>\n): boolean {\n return (\n left?.instanceGetTimeoutMS === right?.instanceGetTimeoutMS &&\n left?.portReadyTimeoutMS === right?.portReadyTimeoutMS &&\n left?.waitIntervalMS === right?.waitIntervalMS\n );\n}\n\nfunction buildSandboxConfiguration(\n effectiveId: string,\n options: SandboxOptions | undefined,\n cached: CachedSandboxConfiguration | undefined\n): SandboxConfiguration {\n const configuration: SandboxConfiguration = {};\n\n if (\n cached?.sandboxName !== effectiveId ||\n cached.normalizeId !== options?.normalizeId\n ) {\n configuration.sandboxName = {\n name: effectiveId,\n normalizeId: options?.normalizeId\n };\n }\n\n if (\n options?.sleepAfter !== undefined &&\n cached?.sleepAfter !== options.sleepAfter\n ) {\n configuration.sleepAfter = options.sleepAfter;\n }\n\n if (\n options?.keepAlive !== undefined &&\n cached?.keepAlive !== options.keepAlive\n ) {\n configuration.keepAlive = options.keepAlive;\n }\n\n if (\n options?.containerTimeouts &&\n !sameContainerTimeouts(cached?.containerTimeouts, options.containerTimeouts)\n ) {\n configuration.containerTimeouts = options.containerTimeouts;\n }\n\n if (\n options?.transport !== undefined &&\n cached?.transport !== options.transport\n ) {\n configuration.transport = options.transport;\n }\n\n return configuration;\n}\n\nfunction hasSandboxConfiguration(configuration: SandboxConfiguration): boolean {\n return (\n configuration.sandboxName !== undefined ||\n configuration.sleepAfter !== undefined ||\n configuration.keepAlive !== undefined ||\n configuration.containerTimeouts !== undefined ||\n configuration.transport !== undefined\n );\n}\n\nfunction mergeSandboxConfiguration(\n cached: CachedSandboxConfiguration | undefined,\n configuration: SandboxConfiguration\n): CachedSandboxConfiguration {\n return {\n ...cached,\n ...(configuration.sandboxName && {\n sandboxName: configuration.sandboxName.name,\n normalizeId: configuration.sandboxName.normalizeId\n }),\n ...(configuration.sleepAfter !== undefined && {\n sleepAfter: configuration.sleepAfter\n }),\n ...(configuration.keepAlive !== undefined && {\n keepAlive: configuration.keepAlive\n }),\n ...(configuration.containerTimeouts !== undefined && {\n containerTimeouts: configuration.containerTimeouts\n }),\n ...(configuration.transport !== undefined && {\n transport: configuration.transport\n })\n };\n}\n\nfunction applySandboxConfiguration(\n stub: ConfigurableSandboxStub,\n configuration: SandboxConfiguration\n): Promise<void> {\n if (stub.configure) {\n return stub.configure(configuration);\n }\n\n const operations: Promise<void>[] = [];\n\n if (configuration.sandboxName) {\n operations.push(\n stub.setSandboxName?.(\n configuration.sandboxName.name,\n configuration.sandboxName.normalizeId\n ) ?? Promise.resolve()\n );\n }\n\n if (configuration.sleepAfter !== undefined) {\n operations.push(\n stub.setSleepAfter?.(configuration.sleepAfter) ?? Promise.resolve()\n );\n }\n\n if (configuration.keepAlive !== undefined) {\n operations.push(\n stub.setKeepAlive?.(configuration.keepAlive) ?? Promise.resolve()\n );\n }\n\n if (configuration.containerTimeouts !== undefined) {\n operations.push(\n stub.setContainerTimeouts?.(configuration.containerTimeouts) ??\n Promise.resolve()\n );\n }\n\n if (configuration.transport !== undefined) {\n operations.push(\n stub.setTransport?.(configuration.transport) ?? Promise.resolve()\n );\n }\n\n return Promise.all(operations).then(() => undefined);\n}\n\nexport function getSandbox<T extends Sandbox<any>>(\n ns: DurableObjectNamespace<T>,\n id: string,\n options?: SandboxOptions\n): T {\n const sanitizedId = sanitizeSandboxId(id);\n const effectiveId = options?.normalizeId\n ? sanitizedId.toLowerCase()\n : sanitizedId;\n\n const hasUppercase = /[A-Z]/.test(sanitizedId);\n if (!options?.normalizeId && hasUppercase) {\n const logger = createLogger({ component: 'sandbox-do' });\n logger.warn(\n `Sandbox ID \"${sanitizedId}\" contains uppercase letters, which causes issues with preview URLs (hostnames are case-insensitive). ` +\n `normalizeId will default to true in a future version to prevent this. ` +\n `Use lowercase IDs or pass { normalizeId: true } to prepare.`\n );\n }\n\n const stub = getContainer(\n ns as unknown as DurableObjectNamespace<Container<Cloudflare.Env>>,\n effectiveId\n ) as unknown as T & ConfigurableSandboxStub;\n\n const namespaceCache = getNamespaceConfigurationCache(ns);\n const cachedConfiguration = namespaceCache.get(effectiveId);\n const configuration = buildSandboxConfiguration(\n effectiveId,\n options,\n cachedConfiguration\n );\n\n if (hasSandboxConfiguration(configuration)) {\n const nextConfiguration = mergeSandboxConfiguration(\n cachedConfiguration,\n configuration\n );\n namespaceCache.set(effectiveId, nextConfiguration);\n\n void applySandboxConfiguration(stub, configuration).catch(() => {\n if (cachedConfiguration) {\n namespaceCache.set(effectiveId, cachedConfiguration);\n return;\n }\n\n namespaceCache.delete(effectiveId);\n });\n }\n\n const defaultSessionId = `sandbox-${effectiveId}`;\n\n // IMPORTANT: Any method that returns ExecutionSession must be listed here\n // to ensure the returned session uses proxyTerminal instead of RPC's terminal.\n const enhancedMethods = {\n fetch: (request: Request) => stub.fetch(request),\n createSession: async (opts?: SessionOptions): Promise<ExecutionSession> => {\n const rpcSession = await stub.createSession(opts);\n return enhanceSession(stub, rpcSession as ExecutionSession);\n },\n getSession: async (sessionId: string): Promise<ExecutionSession> => {\n const rpcSession = await stub.getSession(sessionId);\n return enhanceSession(stub, rpcSession as ExecutionSession);\n },\n terminal: (request: Request, opts?: PtyOptions) =>\n proxyTerminal(stub, defaultSessionId, request, opts),\n wsConnect: connect(stub),\n // Client-side proxy for desktop operations. Each method call is dispatched\n // to the DO's callDesktop() method, avoiding RPC pipelining through getters.\n desktop: new Proxy({} as Desktop, {\n get(_, method) {\n if (typeof method !== 'string' || method === 'then') return undefined;\n return (...args: unknown[]) => stub.callDesktop(method, args);\n }\n })\n };\n\n // Proxy intercepts enhanced methods, passes all others to stub directly.\n // We must access target[prop] directly (not via Reflect.get with receiver)\n // to preserve the RPC stub's internal Proxy handling.\n return new Proxy(stub, {\n get(target, prop) {\n if (typeof prop === 'string' && prop in enhancedMethods) {\n return enhancedMethods[prop as keyof typeof enhancedMethods];\n }\n // @ts-expect-error - RPC stub methods are Proxy-trapped, not visible to TypeScript\n return target[prop];\n }\n }) as T;\n}\n\nfunction enhanceSession(\n stub: { fetch: (request: Request) => Promise<Response> },\n rpcSession: ExecutionSession\n): ExecutionSession {\n return {\n ...rpcSession,\n terminal: (request: Request, opts?: PtyOptions) =>\n proxyTerminal(stub, rpcSession.id, request, opts)\n };\n}\n\nexport function connect(stub: {\n fetch: (request: Request) => Promise<Response>;\n}) {\n return async (request: Request, port: number) => {\n if (!validatePort(port)) {\n throw new SandboxSecurityError(\n `Invalid port number: ${port}. Must be 1024-65535, excluding 3000 (sandbox control plane).`\n );\n }\n const portSwitchedRequest = switchPort(request, port);\n return await stub.fetch(portSwitchedRequest);\n };\n}\n\n/**\n * Type guard for R2Bucket binding.\n * Checks for the minimal R2Bucket interface methods we use.\n */\nfunction isR2Bucket(value: unknown): value is R2Bucket {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'put' in value &&\n typeof (value as Record<string, unknown>).put === 'function' &&\n 'get' in value &&\n typeof (value as Record<string, unknown>).get === 'function' &&\n 'head' in value &&\n typeof (value as Record<string, unknown>).head === 'function' &&\n 'delete' in value &&\n typeof (value as Record<string, unknown>).delete === 'function'\n );\n}\n\nexport class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {\n defaultPort = 3000; // Default port for the container's Bun server\n sleepAfter: string | number = '10m'; // Sleep the sandbox if no requests are made in this timeframe\n\n client: SandboxClient | RPCSandboxClient;\n\n private codeInterpreter: CodeInterpreter;\n private sandboxName: string | null = null;\n private normalizeId: boolean = false;\n private defaultSession: string | null = null;\n // Incremented whenever the container stops. Used to invalidate\n // in-flight default-session initialization that started against a\n // now-dead container.\n private containerGeneration = 0;\n private defaultSessionInit: {\n sessionId: string;\n generation: number;\n promise: Promise<string>;\n } | null = null;\n envVars: Record<string, string> = {};\n private logger: ReturnType<typeof createLogger>;\n private keepAliveEnabled: boolean = false;\n private activeMounts: Map<string, MountInfo> = new Map();\n private transport: 'http' | 'websocket' | 'rpc' = 'http';\n\n /**\n * True once transport has been written to storage at least once (either\n * via setTransport or restored on cold start). Gates the idempotency\n * check so a first explicit call persists even when the requested value\n * already equals the env-derived in-memory default.\n */\n private hasStoredTransport = false;\n\n // R2 bucket binding for backup storage (optional — only set if user configures BACKUP_BUCKET)\n private backupBucket: R2Bucket | null = null;\n /**\n * Serializes backup operations to prevent concurrent create/restore on the same sandbox.\n *\n * This is in-memory state — it resets if the Durable Object is evicted and\n * re-instantiated (e.g. after sleep). This is acceptable because the container\n * filesystem is also lost on eviction, so there is no archive to race on.\n */\n private backupInProgress: Promise<unknown> = Promise.resolve();\n\n /**\n * R2 presigned URL credentials for direct container-to-R2 transfers.\n * All four fields plus the R2 binding must be configured for backup to work.\n */\n private r2AccessKeyId: string | null = null;\n private r2SecretAccessKey: string | null = null;\n private r2AccountId: string | null = null;\n private backupBucketName: string | null = null;\n private r2Client: AwsClient | null = null;\n\n /**\n * Default container startup timeouts (conservative for production)\n * Based on Cloudflare docs: \"Containers take several minutes to provision\"\n */\n private readonly DEFAULT_CONTAINER_TIMEOUTS = {\n // Time to get container instance and launch VM\n // @cloudflare/containers default: 8s (too short for cold starts)\n instanceGetTimeoutMS: 30_000, // 30 seconds\n\n // Time for application to start and ports to be ready\n // @cloudflare/containers default: 20s\n portReadyTimeoutMS: 90_000, // 90 seconds (allows for heavy containers)\n\n // Polling interval for checking container readiness\n waitIntervalMS: 300\n };\n\n /**\n * Active container timeout configuration\n * Can be set via options, env vars, or defaults\n */\n private containerTimeouts = { ...this.DEFAULT_CONTAINER_TIMEOUTS };\n\n /**\n * True once containerTimeouts has been written to storage at least once\n * (either via setContainerTimeouts or restored on cold start). Gates the\n * idempotency check in setContainerTimeouts so a first explicit call\n * persists even when the requested values already equal the in-memory\n * defaults, distinguishing \"user intent recorded\" from \"running on\n * env/SDK defaults\".\n */\n private hasStoredContainerTimeouts = false;\n\n /**\n * Desktop environment operations.\n * Within the DO, this getter provides direct access to DesktopClient.\n * Over RPC, the getSandbox() proxy intercepts this property and routes\n * calls through callDesktop() instead.\n */\n get desktop(): Desktop {\n return this.client.desktop as unknown as Desktop;\n }\n\n /**\n * Allowed desktop methods — derived from the Desktop interface.\n * Restricts callDesktop() to a known set of operations.\n */\n private static readonly DESKTOP_METHODS = new Set([\n 'start',\n 'stop',\n 'status',\n 'screenshot',\n 'screenshotRegion',\n 'click',\n 'doubleClick',\n 'tripleClick',\n 'rightClick',\n 'middleClick',\n 'mouseDown',\n 'mouseUp',\n 'moveMouse',\n 'drag',\n 'scroll',\n 'getCursorPosition',\n 'type',\n 'press',\n 'keyDown',\n 'keyUp',\n 'getScreenSize',\n 'getProcessStatus'\n ]);\n\n /**\n * Dispatch method for desktop operations.\n * Called by the client-side proxy created in getSandbox() to provide\n * the `sandbox.desktop.status()` API without relying on RPC pipelining\n * through property getters.\n */\n async callDesktop(method: string, args: unknown[]): Promise<unknown> {\n if (!Sandbox.DESKTOP_METHODS.has(method)) {\n throw new Error(`Unknown desktop method: ${method}`);\n }\n const client = this.client.desktop;\n const fn = client[method as keyof typeof client];\n if (typeof fn !== 'function') {\n throw new Error(`Unknown desktop method: ${method}`);\n }\n return (fn as (...a: unknown[]) => unknown).apply(client, args);\n }\n\n /**\n * Compute the transport retry budget from current container timeouts.\n *\n * The budget covers the full container startup window (instance provisioning\n * + port readiness) plus a 30s margin for the maximum single backoff delay\n * (capped at 30s in BaseTransport). The 120s floor preserves the previous\n * default for short timeout configurations.\n */\n private computeRetryTimeoutMs(): number {\n const startupBudgetMs =\n this.containerTimeouts.instanceGetTimeoutMS +\n this.containerTimeouts.portReadyTimeoutMS;\n return Math.max(120_000, startupBudgetMs + 30_000);\n }\n\n /**\n * Create a SandboxClient with current transport settings\n */\n private createSandboxClient(): SandboxClient {\n return new SandboxClient({\n logger: this.logger,\n port: 3000,\n stub: this,\n retryTimeoutMs: this.computeRetryTimeoutMs(),\n defaultHeaders: {\n 'X-Sandbox-Id': this.ctx.id.toString()\n },\n ...(this.transport === 'websocket' && {\n transportMode: 'websocket' as const,\n wsUrl: 'ws://localhost:3000/ws'\n })\n });\n }\n\n /**\n * Create the appropriate client for a given transport protocol.\n */\n private createClientForTransport(\n transport: 'http' | 'websocket' | 'rpc'\n ): SandboxClient | RPCSandboxClient {\n if (transport === 'rpc') {\n // Access the base Container's private inflightRequests counter so\n // the alarm loop's isActivityExpired() check sees active work and\n // skips the sleepAfterMs comparison while RPC calls or returned\n // streams are still active over the capnweb session.\n const self = this as unknown as { inflightRequests: number };\n return new RPCSandboxClient({\n stub: this,\n port: 3000,\n logger: this.logger,\n // Mirrors containerFetch()'s request lifecycle for the RPC transport.\n //\n // The HTTP transport bumps inflightRequests at the top of each\n // containerFetch() call and decrements in `finally`. The RPC\n // transport multiplexes all work over a single capnweb WebSocket,\n // so we can't bracket per-request — and a method that returns a\n // ReadableStream resolves its promise long before the stream is\n // actually drained. Instead, RPCSandboxClient polls capnweb's\n // session stats and reports busy/idle *transitions* of the whole\n // session. We treat one transition as equivalent to one in-flight\n // request: increment on busy, decrement on idle. See the\n // file-level comment in rpc-sandbox-client.ts for details.\n onActivity: () => {\n // Called at the start of each RPC call AND on every busy-poll\n // tick while the session has work in flight. Equivalent to\n // the top of containerFetch(): push the sleepAfter deadline\n // forward.\n this.renewActivityTimeout();\n },\n onSessionBusy: () => {\n // Idle → busy: a new RPC call started or a stream return is\n // now in flight. Mark the DO busy so isActivityExpired()\n // returns false until the session goes idle again.\n self.inflightRequests++;\n },\n onSessionIdle: () => {\n // Busy → idle: all RPC promises have settled and all stream\n // exports have been released. Equivalent to containerFetch's\n // finally block — decrement and restart the inactivity window\n // from now.\n self.inflightRequests = Math.max(0, self.inflightRequests - 1);\n if (self.inflightRequests === 0) {\n this.renewActivityTimeout();\n }\n }\n });\n }\n return this.createSandboxClient();\n }\n\n constructor(ctx: DurableObjectState<{}>, env: Env) {\n super(ctx, env);\n\n const envObj = env as Record<string, unknown>;\n const sandboxEnvKeys = ['SANDBOX_LOG_LEVEL', 'SANDBOX_LOG_FORMAT'] as const;\n sandboxEnvKeys.forEach((key) => {\n if (envObj?.[key]) {\n this.envVars[key] = String(envObj[key]);\n }\n });\n\n // Initialize timeouts with env var fallbacks\n this.containerTimeouts = this.getDefaultTimeouts(envObj);\n\n this.logger = createLogger({\n component: 'sandbox-do',\n sandboxId: this.ctx.id.toString()\n });\n\n // Read transport setting from env var\n const transportEnv = envObj?.SANDBOX_TRANSPORT;\n if (transportEnv === 'websocket' || transportEnv === 'rpc') {\n this.transport = transportEnv;\n } else if (transportEnv != null && transportEnv !== 'http') {\n this.logger.warn(\n `Invalid SANDBOX_TRANSPORT value: \"${transportEnv}\". Must be \"http\", \"websocket\", or \"rpc\". Defaulting to \"http\".`\n );\n }\n\n this.logger.info(`Using ${this.transport} transport`);\n\n // Read R2 backup bucket binding if configured\n const backupBucket = envObj?.BACKUP_BUCKET;\n if (isR2Bucket(backupBucket)) {\n this.backupBucket = backupBucket;\n }\n\n // Read R2 presigned URL credentials for direct container-to-R2 backup transfers\n this.r2AccountId = getEnvString(envObj, 'CLOUDFLARE_ACCOUNT_ID') ?? null;\n this.r2AccessKeyId = getEnvString(envObj, 'R2_ACCESS_KEY_ID') ?? null;\n this.r2SecretAccessKey =\n getEnvString(envObj, 'R2_SECRET_ACCESS_KEY') ?? null;\n this.backupBucketName = getEnvString(envObj, 'BACKUP_BUCKET_NAME') ?? null;\n\n if (this.r2AccessKeyId && this.r2SecretAccessKey) {\n this.r2Client = new AwsClient({\n accessKeyId: this.r2AccessKeyId,\n secretAccessKey: this.r2SecretAccessKey\n });\n }\n\n this.client = this.createClientForTransport(this.transport);\n\n this.codeInterpreter = new CodeInterpreter(() => this.client.interpreter);\n\n this.ctx.blockConcurrencyWhile(async () => {\n this.sandboxName =\n (await this.ctx.storage.get<string>('sandboxName')) ?? null;\n this.normalizeId =\n (await this.ctx.storage.get<boolean>('normalizeId')) ?? false;\n this.defaultSession =\n (await this.ctx.storage.get<string>('defaultSession')) ?? null;\n this.keepAliveEnabled =\n (await this.ctx.storage.get<boolean>('keepAliveEnabled')) ?? false;\n\n // Load saved timeout configuration (highest priority)\n const storedTimeouts =\n await this.ctx.storage.get<\n NonNullable<SandboxOptions['containerTimeouts']>\n >('containerTimeouts');\n if (storedTimeouts) {\n this.containerTimeouts = {\n ...this.containerTimeouts,\n ...storedTimeouts\n };\n this.hasStoredContainerTimeouts = true;\n // Update the transport retry budget to reflect stored timeouts\n this.client.setRetryTimeoutMs(this.computeRetryTimeoutMs());\n }\n\n // Restore sleep timeout if previously set via RPC\n const storedSleepAfter = await this.ctx.storage.get<string | number>(\n 'sleepAfter'\n );\n if (storedSleepAfter !== undefined) {\n this.sleepAfter = storedSleepAfter;\n this.renewActivityTimeout();\n }\n\n // Restore transport setting from storage (overrides env var default)\n const storedTransport = await this.ctx.storage.get<\n 'http' | 'websocket' | 'rpc'\n >('transport');\n if (storedTransport && storedTransport !== this.transport) {\n this.transport = storedTransport;\n const previousClient = this.client;\n this.client = this.createClientForTransport(storedTransport);\n this.codeInterpreter = new CodeInterpreter(\n () => this.client.interpreter\n );\n previousClient.disconnect();\n }\n if (storedTransport) {\n this.hasStoredTransport = true;\n }\n\n if (this.interceptHttps) {\n this.envVars = { ...this.envVars, SANDBOX_INTERCEPT_HTTPS: '1' };\n }\n });\n }\n\n // RPC method to set the sandbox name and normalizeId. Set-once — a\n // subsequent call is a no-op.\n //\n // sandboxName and normalizeId are one logical unit. Both storage.put\n // calls run without an intervening await, so they land in the same\n // in-memory write buffer and flush as a single atomic transaction on\n // SQLite-backed Durable Objects. In-memory state is updated only after\n // both writes commit.\n async setSandboxName(name: string, normalizeId?: boolean): Promise<void> {\n if (this.sandboxName !== null) return;\n const effectiveNormalizeId = normalizeId ?? false;\n\n await Promise.all([\n this.ctx.storage.put('sandboxName', name),\n this.ctx.storage.put('normalizeId', effectiveNormalizeId)\n ]);\n\n this.sandboxName = name;\n this.normalizeId = effectiveNormalizeId;\n }\n\n async configure(configuration: SandboxConfiguration): Promise<void> {\n if (configuration.sandboxName) {\n await this.setSandboxName(\n configuration.sandboxName.name,\n configuration.sandboxName.normalizeId\n );\n }\n\n if (configuration.sleepAfter !== undefined) {\n await this.setSleepAfter(configuration.sleepAfter);\n }\n\n if (configuration.keepAlive !== undefined) {\n await this.setKeepAlive(configuration.keepAlive);\n }\n\n if (configuration.containerTimeouts !== undefined) {\n await this.setContainerTimeouts(configuration.containerTimeouts);\n }\n\n if (configuration.transport !== undefined) {\n await this.setTransport(configuration.transport);\n }\n }\n\n // RPC method to set the sleep timeout. Idempotent: re-applying the same\n // value returns early with no storage write and no timer reset. A real\n // change persists, then reschedules the activity timer against the new\n // window length.\n async setSleepAfter(sleepAfter: string | number): Promise<void> {\n if (this.sleepAfter === sleepAfter) return;\n await this.ctx.storage.put('sleepAfter', sleepAfter);\n this.sleepAfter = sleepAfter;\n this.renewActivityTimeout();\n }\n\n // RPC method to enable keepAlive mode. Idempotent: re-applying the same\n // value returns early. When disabling (true to false), the activity\n // timer is renewed so the inactivity window counts from now.\n async setKeepAlive(keepAlive: boolean): Promise<void> {\n if (this.keepAliveEnabled === keepAlive) return;\n await this.ctx.storage.put('keepAliveEnabled', keepAlive);\n this.keepAliveEnabled = keepAlive;\n\n if (!keepAlive) {\n this.renewActivityTimeout();\n }\n }\n\n async setEnvVars(envVars: Record<string, string | undefined>): Promise<void> {\n const { toSet, toUnset } = partitionEnvVars(envVars);\n\n for (const key of toUnset) {\n delete this.envVars[key];\n }\n this.envVars = { ...this.envVars, ...toSet };\n\n if (this.defaultSession) {\n for (const key of toUnset) {\n const unsetCommand = `unset ${key}`;\n\n const result = await this.client.commands.execute(\n unsetCommand,\n this.defaultSession,\n { origin: 'internal' }\n );\n\n if (result.exitCode !== 0) {\n throw new Error(\n `Failed to unset ${key}: ${result.stderr || 'Unknown error'}`\n );\n }\n }\n\n for (const [key, value] of Object.entries(toSet)) {\n const exportCommand = `export ${key}=${shellEscape(value)}`;\n\n const result = await this.client.commands.execute(\n exportCommand,\n this.defaultSession,\n { origin: 'internal' }\n );\n\n if (result.exitCode !== 0) {\n throw new Error(\n `Failed to set ${key}: ${result.stderr || 'Unknown error'}`\n );\n }\n }\n }\n }\n\n /**\n * RPC method to configure container startup timeouts. Idempotent once\n * the values have been persisted: re-applying the same timeout set is a\n * no-op. The transport retry budget is recomputed only when at least\n * one timeout actually changes. Storage is written before the in-memory\n * mirror and derived state are updated.\n */\n async setContainerTimeouts(\n timeouts: NonNullable<SandboxOptions['containerTimeouts']>\n ): Promise<void> {\n const validated = { ...this.containerTimeouts };\n\n // Validate each timeout if provided\n if (timeouts.instanceGetTimeoutMS !== undefined) {\n validated.instanceGetTimeoutMS = this.validateTimeout(\n timeouts.instanceGetTimeoutMS,\n 'instanceGetTimeoutMS',\n 5_000,\n 300_000\n );\n }\n\n if (timeouts.portReadyTimeoutMS !== undefined) {\n validated.portReadyTimeoutMS = this.validateTimeout(\n timeouts.portReadyTimeoutMS,\n 'portReadyTimeoutMS',\n 10_000,\n 600_000\n );\n }\n\n if (timeouts.waitIntervalMS !== undefined) {\n validated.waitIntervalMS = this.validateTimeout(\n timeouts.waitIntervalMS,\n 'waitIntervalMS',\n 100,\n 5_000\n );\n }\n\n // No-op only once the values have been written to storage. A first\n // explicit call persists even when the values match the env/default-\n // derived in-memory state so user intent is recorded independently of\n // how those defaults resolve later.\n if (\n this.hasStoredContainerTimeouts &&\n validated.instanceGetTimeoutMS ===\n this.containerTimeouts.instanceGetTimeoutMS &&\n validated.portReadyTimeoutMS ===\n this.containerTimeouts.portReadyTimeoutMS &&\n validated.waitIntervalMS === this.containerTimeouts.waitIntervalMS\n ) {\n return;\n }\n\n await this.ctx.storage.put('containerTimeouts', validated);\n this.containerTimeouts = validated;\n this.hasStoredContainerTimeouts = true;\n\n // Update the transport retry budget to reflect new timeouts\n this.client.setRetryTimeoutMs(this.computeRetryTimeoutMs());\n\n this.logger.debug('Container timeouts updated', this.containerTimeouts);\n }\n\n /**\n * RPC method to set the transport protocol. Idempotent once the value\n * has been persisted: re-applying the same transport is a no-op.\n * Storage is written before the in-memory state and client are updated.\n */\n async setTransport(transport: 'http' | 'websocket' | 'rpc'): Promise<void> {\n if (\n transport !== 'http' &&\n transport !== 'websocket' &&\n transport !== 'rpc'\n ) {\n this.logger.warn(\n `Invalid transport value: \"${transport}\". Must be \"http\", \"websocket\", or \"rpc\". Ignoring.`\n );\n return;\n }\n\n if (this.hasStoredTransport && this.transport === transport) {\n return;\n }\n\n await this.ctx.storage.put('transport', transport);\n\n const previousClient = this.client;\n this.transport = transport;\n this.hasStoredTransport = true;\n this.client = this.createClientForTransport(transport);\n this.codeInterpreter = new CodeInterpreter(() => this.client.interpreter);\n previousClient.disconnect();\n this.renewActivityTimeout();\n this.logger.debug('Transport updated', { transport });\n }\n\n /**\n * Validate a timeout value is within acceptable range\n * Throws error if invalid - used for user-provided values\n */\n private validateTimeout(\n value: number,\n name: string,\n min: number,\n max: number\n ): number {\n if (\n typeof value !== 'number' ||\n Number.isNaN(value) ||\n !Number.isFinite(value)\n ) {\n throw new Error(`${name} must be a valid finite number, got ${value}`);\n }\n\n if (value < min || value > max) {\n throw new Error(\n `${name} must be between ${min}-${max}ms, got ${value}ms`\n );\n }\n\n return value;\n }\n\n /**\n * Get default timeouts with env var fallbacks and validation\n * Precedence: SDK defaults < Env vars < User config\n */\n private getDefaultTimeouts(\n env: Record<string, unknown>\n ): typeof this.DEFAULT_CONTAINER_TIMEOUTS {\n const parseAndValidate = (\n envVar: string | undefined,\n name: keyof typeof this.DEFAULT_CONTAINER_TIMEOUTS,\n min: number,\n max: number\n ): number => {\n const defaultValue = this.DEFAULT_CONTAINER_TIMEOUTS[name];\n\n if (envVar === undefined) {\n return defaultValue;\n }\n\n const parsed = parseInt(envVar, 10);\n\n if (Number.isNaN(parsed)) {\n this.logger.warn(\n `Invalid ${name}: \"${envVar}\" is not a number. Using default: ${defaultValue}ms`\n );\n return defaultValue;\n }\n\n if (parsed < min || parsed > max) {\n this.logger.warn(\n `Invalid ${name}: ${parsed}ms. Must be ${min}-${max}ms. Using default: ${defaultValue}ms`\n );\n return defaultValue;\n }\n\n return parsed;\n };\n\n return {\n instanceGetTimeoutMS: parseAndValidate(\n getEnvString(env, 'SANDBOX_INSTANCE_TIMEOUT_MS'),\n 'instanceGetTimeoutMS',\n 5_000, // Min 5s\n 300_000 // Max 5min\n ),\n portReadyTimeoutMS: parseAndValidate(\n getEnvString(env, 'SANDBOX_PORT_TIMEOUT_MS'),\n 'portReadyTimeoutMS',\n 10_000, // Min 10s\n 600_000 // Max 10min\n ),\n waitIntervalMS: parseAndValidate(\n getEnvString(env, 'SANDBOX_POLL_INTERVAL_MS'),\n 'waitIntervalMS',\n 100, // Min 100ms\n 5_000 // Max 5s\n )\n };\n }\n\n /**\n * Mount an S3-compatible bucket as a local directory.\n *\n * Requires explicit endpoint URL for production. Credentials are auto-detected from environment\n * variables or can be provided explicitly.\n *\n * @param bucket - Bucket name (or R2 binding name when localBucket is true)\n * @param mountPath - Absolute path in container to mount at\n * @param options - Mount configuration\n * @throws MissingCredentialsError if no credentials found in environment\n * @throws S3FSMountError if S3FS mount command fails\n * @throws InvalidMountConfigError if bucket name, mount path, or endpoint is invalid\n */\n async mountBucket(\n bucket: string,\n mountPath: string,\n options: MountBucketOptions\n ): Promise<void> {\n if (options.prefix !== undefined) {\n validatePrefix(options.prefix);\n }\n\n if ('localBucket' in options && options.localBucket) {\n await this.mountBucketLocal(bucket, mountPath, options);\n return;\n }\n\n await this.mountBucketFuse(\n bucket,\n mountPath,\n options as RemoteMountBucketOptions\n );\n }\n\n /**\n * Local dev mount: bidirectional sync via R2 binding + file/watch APIs\n */\n private async mountBucketLocal(\n bucket: string,\n mountPath: string,\n options: LocalMountBucketOptions\n ): Promise<void> {\n const mountStartTime = Date.now();\n let mountOutcome: 'success' | 'error' = 'error';\n let mountError: Error | undefined;\n\n try {\n const envObj = this.env as Record<string, unknown>;\n const r2Binding = envObj[bucket];\n if (!r2Binding || !isR2Bucket(r2Binding)) {\n throw new InvalidMountConfigError(\n `R2 binding \"${bucket}\" not found in env or is not an R2Bucket. ` +\n 'Make sure the binding name matches your wrangler.jsonc R2 binding.'\n );\n }\n\n if (!mountPath || !mountPath.startsWith('/')) {\n throw new InvalidMountConfigError(\n `Invalid mount path: \"${mountPath}\". Must be an absolute path starting with /`\n );\n }\n\n if (this.activeMounts.has(mountPath)) {\n throw new InvalidMountConfigError(\n `Mount path already in use: ${mountPath}`\n );\n }\n\n const sessionId = await this.ensureDefaultSession();\n\n const syncManager = new LocalMountSyncManager({\n bucket: r2Binding,\n mountPath,\n prefix: options.prefix,\n readOnly: options.readOnly ?? false,\n client: this.client,\n sessionId,\n logger: this.logger\n });\n\n const mountInfo: LocalSyncMountInfo = {\n mountType: 'local-sync',\n bucket,\n mountPath,\n syncManager,\n mounted: false\n };\n this.activeMounts.set(mountPath, mountInfo);\n\n try {\n await syncManager.start();\n mountInfo.mounted = true;\n } catch (error) {\n await syncManager.stop();\n this.activeMounts.delete(mountPath);\n throw error;\n }\n\n mountOutcome = 'success';\n } catch (error) {\n mountError = error instanceof Error ? error : new Error(String(error));\n throw error;\n } finally {\n logCanonicalEvent(this.logger, {\n event: 'bucket.mount',\n outcome: mountOutcome,\n durationMs: Date.now() - mountStartTime,\n bucket,\n mountPath,\n provider: 'local-sync',\n prefix: options.prefix,\n error: mountError\n });\n }\n }\n\n /**\n * Production mount: S3FS-FUSE inside the container\n */\n private async mountBucketFuse(\n bucket: string,\n mountPath: string,\n options: RemoteMountBucketOptions\n ): Promise<void> {\n const mountStartTime = Date.now();\n const prefix = options.prefix || undefined;\n let mountOutcome: 'success' | 'error' = 'error';\n let mountError: Error | undefined;\n let passwordFilePath: string | undefined;\n let provider: BucketProvider | null = null;\n try {\n this.validateMountOptions(bucket, mountPath, { ...options, prefix });\n\n // Build s3fs source: bucket name with optional prefix (e.g., \"mybucket:/prefix/\")\n const s3fsSource = buildS3fsSource(bucket, prefix);\n provider = options.provider || detectProviderFromUrl(options.endpoint);\n\n this.logger.debug(`Detected provider: ${provider || 'unknown'}`, {\n explicitProvider: options.provider,\n prefix\n });\n\n // Attempt to load credentials from the DO env\n const envObj = this.env as Record<string, unknown>;\n const envCredentials = {\n AWS_ACCESS_KEY_ID: getEnvString(envObj, 'AWS_ACCESS_KEY_ID'),\n AWS_SECRET_ACCESS_KEY: getEnvString(envObj, 'AWS_SECRET_ACCESS_KEY'),\n R2_ACCESS_KEY_ID: this.r2AccessKeyId || undefined,\n R2_SECRET_ACCESS_KEY: this.r2SecretAccessKey || undefined\n };\n\n // Detect credentials\n const credentials = detectCredentials(options, {\n ...envCredentials,\n ...this.envVars\n });\n\n // Generate unique password file path\n passwordFilePath = this.generatePasswordFilePath();\n\n // Reserve mount path before async operations so concurrent mounts see it\n const mountInfo: FuseMountInfo = {\n mountType: 'fuse',\n bucket: s3fsSource,\n mountPath,\n endpoint: options.endpoint,\n provider,\n passwordFilePath,\n mounted: false\n };\n this.activeMounts.set(mountPath, mountInfo);\n\n // Create password file with credentials (uses bucket name only, not prefix)\n await this.createPasswordFile(passwordFilePath, bucket, credentials);\n\n // Create mount directory\n await this.execInternal(`mkdir -p ${shellEscape(mountPath)}`);\n\n // Execute S3FS mount with password file (uses full s3fs source with prefix)\n await this.executeS3FSMount(\n s3fsSource,\n mountPath,\n options,\n provider,\n passwordFilePath\n );\n\n mountInfo.mounted = true;\n mountOutcome = 'success';\n } catch (error) {\n mountError = error instanceof Error ? error : new Error(String(error));\n // Clean up password file on failure\n if (passwordFilePath) {\n await this.deletePasswordFile(passwordFilePath);\n }\n\n // Clean up reservation on failure\n this.activeMounts.delete(mountPath);\n throw error;\n } finally {\n logCanonicalEvent(this.logger, {\n event: 'bucket.mount',\n outcome: mountOutcome,\n durationMs: Date.now() - mountStartTime,\n bucket,\n mountPath,\n provider: provider || 'unknown',\n prefix,\n error: mountError\n });\n }\n }\n\n /**\n * Manually unmount a bucket filesystem\n *\n * @param mountPath - Absolute path where the bucket is mounted\n * @throws InvalidMountConfigError if mount path doesn't exist or isn't mounted\n */\n async unmountBucket(mountPath: string): Promise<void> {\n const unmountStartTime = Date.now();\n let unmountOutcome: 'success' | 'error' = 'error';\n let unmountError: Error | undefined;\n\n // Look up mount by path\n const mountInfo = this.activeMounts.get(mountPath);\n\n try {\n // Throw error if mount doesn't exist\n if (!mountInfo) {\n throw new InvalidMountConfigError(\n `No active mount found at path: ${mountPath}`\n );\n }\n // Unmount the filesystem\n if (mountInfo.mountType === 'local-sync') {\n await mountInfo.syncManager.stop();\n mountInfo.mounted = false;\n this.activeMounts.delete(mountPath);\n } else {\n // FUSE unmount\n try {\n const result = await this.execInternal(\n `fusermount -u ${shellEscape(mountPath)}`\n );\n if (result.exitCode !== 0) {\n const stderr = result.stderr || 'unknown error';\n throw new BucketUnmountError(\n `fusermount -u failed (exit ${result.exitCode}): ${stderr}`\n );\n }\n mountInfo.mounted = false;\n\n // Only remove from tracking if unmount succeeded\n this.activeMounts.delete(mountPath);\n\n // Remove the now-empty mount directory\n try {\n const cleanup = await this.execInternal(\n `mountpoint -q ${shellEscape(mountPath)} || rmdir ${shellEscape(mountPath)}`\n );\n if (cleanup.exitCode !== 0) {\n this.logger.warn('mount directory removal failed', {\n mountPath,\n exitCode: cleanup.exitCode,\n stderr: cleanup.stderr\n });\n }\n } catch (err) {\n this.logger.warn('mount directory removal failed', {\n mountPath,\n error: err instanceof Error ? err.message : String(err)\n });\n }\n } finally {\n // Always cleanup password file, even if unmount fails\n await this.deletePasswordFile(mountInfo.passwordFilePath);\n }\n }\n\n unmountOutcome = 'success';\n } catch (error) {\n unmountError = error instanceof Error ? error : new Error(String(error));\n throw error;\n } finally {\n logCanonicalEvent(this.logger, {\n event: 'bucket.unmount',\n outcome: unmountOutcome,\n durationMs: Date.now() - unmountStartTime,\n mountPath,\n bucket: mountInfo?.bucket,\n error: unmountError\n });\n }\n }\n\n /**\n * Validate mount options\n */\n private validateMountOptions(\n bucket: string,\n mountPath: string,\n options: RemoteMountBucketOptions\n ): void {\n // Basic URL validation\n try {\n new URL(options.endpoint);\n } catch (error) {\n throw new InvalidMountConfigError(\n `Invalid endpoint URL: \"${options.endpoint}\". Must be a valid HTTP(S) URL.`\n );\n }\n\n validateBucketName(bucket, mountPath);\n\n // Validate mount path is absolute\n if (!mountPath.startsWith('/')) {\n throw new InvalidMountConfigError(\n `Mount path must be absolute (start with /): \"${mountPath}\"`\n );\n }\n\n // Check for duplicate mount path\n if (this.activeMounts.has(mountPath)) {\n const existingMount = this.activeMounts.get(mountPath);\n throw new InvalidMountConfigError(\n `Mount path \"${mountPath}\" is already in use by bucket \"${existingMount?.bucket}\". ` +\n `Unmount the existing bucket first or use a different mount path.`\n );\n }\n\n // Prefix validation is handled centrally in mountBucket()\n }\n\n /**\n * Generate unique password file path for s3fs credentials\n */\n private generatePasswordFilePath(): string {\n const uuid = crypto.randomUUID();\n return `/tmp/.passwd-s3fs-${uuid}`;\n }\n\n /**\n * Create password file with s3fs credentials\n * Format: bucket:accessKeyId:secretAccessKey\n */\n private async createPasswordFile(\n passwordFilePath: string,\n bucket: string,\n credentials: BucketCredentials\n ): Promise<void> {\n const content = `${bucket}:${credentials.accessKeyId}:${credentials.secretAccessKey}`;\n\n await this.writeFile(passwordFilePath, content);\n\n await this.execInternal(`chmod 0600 ${shellEscape(passwordFilePath)}`);\n }\n\n /**\n * Delete password file\n */\n private async deletePasswordFile(passwordFilePath: string): Promise<void> {\n try {\n await this.execInternal(`rm -f ${shellEscape(passwordFilePath)}`);\n } catch (error) {\n this.logger.warn('password file cleanup failed', {\n passwordFilePath,\n error: error instanceof Error ? error.message : String(error)\n });\n }\n }\n\n /**\n * Execute S3FS mount command\n */\n private async executeS3FSMount(\n bucket: string,\n mountPath: string,\n options: RemoteMountBucketOptions,\n provider: BucketProvider | null,\n passwordFilePath: string,\n sessionId?: string\n ): Promise<void> {\n // Resolve s3fs options (provider defaults + user overrides)\n const resolvedOptions = resolveS3fsOptions(provider, options.s3fsOptions);\n\n // Build s3fs mount command\n const s3fsArgs: string[] = [];\n\n // Add password file option FIRST\n s3fsArgs.push(`passwd_file=${passwordFilePath}`);\n\n // Add resolved provider-specific and user options\n s3fsArgs.push(...resolvedOptions);\n\n // Add read-only flag if requested\n if (options.readOnly) {\n s3fsArgs.push('ro');\n }\n\n // Add endpoint URL\n s3fsArgs.push(`url=${options.endpoint}`);\n\n // Build final command with escaped options\n const optionsStr = shellEscape(s3fsArgs.join(','));\n const mountCmd = `s3fs ${shellEscape(bucket)} ${shellEscape(mountPath)} -o ${optionsStr}`;\n\n // Execute mount command\n const result = sessionId\n ? await this.execWithSession(mountCmd, sessionId, { origin: 'internal' })\n : await this.execInternal(mountCmd);\n\n if (result.exitCode !== 0) {\n throw new S3FSMountError(\n `S3FS mount failed: ${result.stderr || result.stdout || 'Unknown error'}`\n );\n }\n }\n\n /**\n * In-flight `destroy()` promise. While set, concurrent callers coalesce\n * onto the same teardown instead of triggering a second one. Cleared when\n * the underlying work settles, so a later call that genuinely needs to\n * recreate a destroyed sandbox still runs.\n *\n * If the underlying teardown hangs (e.g. `super.destroy()` never resolves\n * because the Containers control plane is unresponsive), every coalesced\n * caller hangs on the same promise until the Durable Object is evicted.\n * This is deliberate: a second concurrent teardown would not make a stuck\n * control plane unstuck, and spawning one would defeat the point of\n * coalescing. Callers that need bounded waits must apply their own\n * timeout around `destroy()`.\n */\n private inflightDestroy: Promise<void> | null = null;\n\n /**\n * Cleanup and destroy the sandbox container.\n *\n * Concurrent calls coalesce: if a previous `destroy()` is still in flight,\n * subsequent calls await the same underlying work instead of starting a\n * second teardown. A canonical `sandbox.destroy.coalesced` event is logged\n * per coalesced call so repeated destroy traffic is observable.\n */\n override async destroy(): Promise<void> {\n if (this.inflightDestroy) {\n logCanonicalEvent(this.logger, {\n event: 'sandbox.destroy.coalesced',\n outcome: 'success',\n durationMs: 0\n });\n return this.inflightDestroy;\n }\n\n // Assigned synchronously so concurrent callers observe the promise\n // before any await point inside doDestroy().\n const work = this.doDestroy();\n this.inflightDestroy = work;\n try {\n await work;\n } finally {\n // Clears only if the field still references this teardown.\n if (this.inflightDestroy === work) {\n this.inflightDestroy = null;\n }\n }\n }\n\n private async doDestroy(): Promise<void> {\n const startTime = Date.now();\n let mountsProcessed = 0;\n let mountFailures = 0;\n let outcome: 'success' | 'error' = 'error';\n let caughtError: Error | undefined;\n\n try {\n // Best-effort desktop stop — only when container is already running\n if (this.ctx.container?.running) {\n try {\n await this.client.desktop.stop();\n } catch {\n // Desktop may not be running or available — continue cleanup\n }\n }\n\n // Unmount all mounted buckets and cleanup (requires an active connection\n // for execInternal calls, so this runs before disconnecting the transport)\n for (const [mountPath, mountInfo] of this.activeMounts.entries()) {\n mountsProcessed++;\n if (mountInfo.mountType === 'local-sync') {\n try {\n await mountInfo.syncManager.stop();\n mountInfo.mounted = false;\n } catch (error) {\n mountFailures++;\n const errorMsg =\n error instanceof Error ? error.message : String(error);\n this.logger.warn(\n `Failed to stop local sync for ${mountPath}: ${errorMsg}`\n );\n }\n } else {\n if (mountInfo.mounted) {\n try {\n this.logger.debug(\n `Unmounting bucket ${mountInfo.bucket} from ${mountPath}`\n );\n await this.execInternal(\n `fusermount -u ${shellEscape(mountPath)}`\n );\n mountInfo.mounted = false;\n } catch (error) {\n mountFailures++;\n const errorMsg =\n error instanceof Error ? error.message : String(error);\n this.logger.warn(\n `Failed to unmount bucket ${mountInfo.bucket} from ${mountPath}: ${errorMsg}`\n );\n }\n }\n\n // Always cleanup password file for FUSE mounts\n await this.deletePasswordFile(mountInfo.passwordFilePath);\n }\n }\n\n // portTokens is cleared while still inside destroy()'s try block:\n // super.destroy() is not serialized by blockConcurrencyWhile, so\n // other DO RPCs run during the await. With storage already cleared,\n // a concurrent validatePortToken() from the preview URL proxy sees\n // no token and returns unauthorized, and a concurrent startup path\n // finds nothing to rehydrate via restoreExposedPorts(). Teardown is\n // still not atomic against concurrent writers, but the preview URL\n // authorization path is race-free.\n await this.ctx.storage.delete('portTokens');\n\n // Disconnect transport after all cleanup commands have completed\n this.client.disconnect();\n\n outcome = 'success';\n await super.destroy();\n } catch (error) {\n caughtError = error instanceof Error ? error : new Error(String(error));\n throw error;\n } finally {\n logCanonicalEvent(this.logger, {\n event: 'sandbox.destroy',\n outcome,\n durationMs: Date.now() - startTime,\n mountsProcessed,\n mountFailures,\n error: caughtError\n });\n }\n }\n\n override async onStart() {\n this.logger.debug('Sandbox started');\n\n // Fire-and-forget: version check is observability, not load-bearing.\n this.checkVersionCompatibility().catch((error) => {\n this.logger.error(\n 'Version compatibility check failed',\n error instanceof Error ? error : new Error(String(error))\n );\n });\n\n // Re-expose ports that were exposed before the container restarted.\n // Tokens persist in DO storage across restarts (see onStop), but the\n // container runtime has no memory of which ports were exposed. The base\n // @cloudflare/containers class wraps onStart in blockConcurrencyWhile,\n // so awaiting restore here keeps the DO gate held until restore\n // completes — requests that arrive during the startup window (including\n // validatePortToken calls from the Worker preview-URL proxy) queue\n // behind it.\n try {\n await this.restoreExposedPorts();\n } catch (error) {\n this.logger.error(\n 'Failed to restore exposed ports after container start',\n error instanceof Error ? error : new Error(String(error))\n );\n }\n }\n\n /**\n * Re-expose ports on the container runtime using tokens persisted in DO\n * storage. Called from onStart() after a container (re)start.\n *\n * The DO storage holds the source of truth for which ports should be\n * exposed, which tokens authorize them, and the friendly name (if any)\n * that the caller set when first exposing the port. If a port is already\n * exposed on the container this is a no-op for that port. Individual port\n * failures are logged but do not abort the overall restore — a transient\n * failure for one port must not prevent the others from being restored.\n */\n private async restoreExposedPorts(): Promise<void> {\n const savedTokens = await this.readPortTokens();\n const portEntries = Object.entries(savedTokens);\n if (portEntries.length === 0) {\n return;\n }\n\n const startTime = Date.now();\n let restored = 0;\n let skipped = 0;\n let failed = 0;\n\n // Resolving the session ensures the container HTTP API is reachable\n // before we start firing exposePort requests at it.\n const sessionId = await this.ensureDefaultSession();\n\n // Fetch the container's current exposed-port list once, then check\n // membership in the loop. On a fresh restart this is empty; on a\n // retry path (re-entering onStart) it may already contain entries\n // that should be skipped.\n const exposedSet = await this.client.ports\n .getExposedPorts(sessionId)\n .then((response) => new Set(response.ports.map((p) => p.port)))\n .catch((error) => {\n this.logger.warn(\n 'Failed to fetch exposed ports for restore; assuming none exposed',\n { error: error instanceof Error ? error.message : String(error) }\n );\n return new Set<number>();\n });\n\n for (const [portStr, entry] of portEntries) {\n const port = Number.parseInt(portStr, 10);\n if (!Number.isFinite(port) || !validatePort(port)) {\n this.logger.warn('Skipping restore of invalid port in storage', {\n port: portStr\n });\n failed++;\n continue;\n }\n\n if (exposedSet.has(port)) {\n skipped++;\n continue;\n }\n\n try {\n await this.client.ports.exposePort(port, sessionId, entry.name);\n restored++;\n } catch (error) {\n failed++;\n this.logger.warn('Failed to re-expose port on container restart', {\n port,\n error: error instanceof Error ? error.message : String(error)\n });\n }\n }\n\n logCanonicalEvent(this.logger, {\n event: 'port.restore',\n outcome: failed === 0 ? 'success' : 'error',\n durationMs: Date.now() - startTime,\n restored,\n skipped,\n failed,\n total: portEntries.length\n });\n }\n\n /**\n * Read the `portTokens` map from DO storage, normalizing the legacy\n * string-valued format (just a token) to the current object format\n * ({ token, name? }). The legacy format predates port-name persistence and\n * can appear on any DO whose storage was written before that change.\n */\n private async readPortTokens(): Promise<Record<string, PortTokenEntry>> {\n const raw =\n (await this.ctx.storage.get<Record<string, string | PortTokenEntry>>(\n 'portTokens'\n )) ?? {};\n const normalized: Record<string, PortTokenEntry> = {};\n for (const [port, value] of Object.entries(raw)) {\n normalized[port] = typeof value === 'string' ? { token: value } : value;\n }\n return normalized;\n }\n\n /**\n * Check if the container version matches the SDK version\n * Logs a warning if there's a mismatch\n */\n private async checkVersionCompatibility(): Promise<void> {\n const sdkVersion = SDK_VERSION;\n let containerVersion: string | undefined;\n let outcome: string;\n\n try {\n containerVersion = await this.client.utils.getVersion();\n\n if (containerVersion === 'unknown') {\n outcome = 'container_version_unknown';\n } else if (containerVersion !== sdkVersion) {\n outcome = 'version_mismatch';\n } else {\n outcome = 'compatible';\n }\n } catch (error) {\n outcome = 'check_failed';\n containerVersion = undefined;\n }\n\n const successLevel =\n outcome === 'compatible'\n ? ('debug' as const)\n : outcome === 'container_version_unknown'\n ? ('info' as const)\n : ('warn' as const); // version_mismatch or check_failed\n\n logCanonicalEvent(\n this.logger,\n {\n event: 'version.check',\n outcome: 'success',\n durationMs: 0,\n sdkVersion,\n containerVersion: containerVersion ?? 'unknown',\n versionOutcome: outcome\n },\n { successLevel }\n );\n }\n\n override async onStop() {\n this.logger.debug('Sandbox stopped');\n\n // Invalidate default-session state before the first await. Bumping\n // containerGeneration signals any in-flight initializeDefaultSession\n // that a new container generation begins next; it observes the\n // mismatch at its post-createSession check and fails. Clearing the\n // slot means later callers start a new init against the next\n // container.\n this.containerGeneration++;\n this.defaultSession = null;\n this.defaultSessionInit = null;\n\n // Disconnect capnweb transport so the WebSocket doesn't hold the DO alive\n this.client.disconnect();\n\n // Stop local sync managers before clearing the map.\n for (const [, m] of this.activeMounts) {\n if (m.mountType === 'local-sync')\n await m.syncManager.stop().catch(() => {});\n }\n\n this.activeMounts.clear();\n\n // Persist cleanup to storage so state is clean on next container start.\n // Port tokens are preserved so preview URLs survive container restarts;\n // they are only removed on explicit unexposePort() or full sandbox\n // destroy(). onStart's restoreExposedPorts() replays those tokens into\n // the container on the next start, which lets validatePortToken()\n // answer from storage alone.\n await this.ctx.storage.delete('defaultSession');\n }\n\n override onError(error: unknown) {\n this.logger.error(\n 'Sandbox error',\n error instanceof Error ? error : new Error(String(error))\n );\n }\n\n /**\n * Override Container.containerFetch to use production-friendly timeouts\n * Automatically starts container with longer timeouts if not running\n */\n override async containerFetch(\n requestOrUrl: Request | string | URL,\n portOrInit?: number | RequestInit,\n portParam?: number\n ): Promise<Response> {\n // Parse arguments to extract request and port\n const { request, port } = this.parseContainerFetchArgs(\n requestOrUrl,\n portOrInit,\n portParam\n );\n\n const state = await this.getState();\n const containerRunning = this.ctx.container?.running;\n\n // Start container if persisted state is not healthy OR if runtime reports container is not running.\n // The runtime check catches stale persisted state (e.g., state says 'healthy' after DO recreation\n // but Docker container is gone).\n const staleStateDetected =\n state.status === 'healthy' && containerRunning === false;\n if (state.status !== 'healthy' || containerRunning === false) {\n try {\n await this.startAndWaitForPorts({\n ports: port,\n cancellationOptions: {\n instanceGetTimeoutMS: this.containerTimeouts.instanceGetTimeoutMS,\n portReadyTimeoutMS: this.containerTimeouts.portReadyTimeoutMS,\n waitInterval: this.containerTimeouts.waitIntervalMS,\n abort: request.signal\n }\n });\n } catch (e) {\n // 1. Provisioning: Container VM not yet available\n if (this.isNoInstanceError(e)) {\n const errorBody: ErrorResponse = {\n code: ErrorCode.INTERNAL_ERROR,\n message:\n 'Container is currently provisioning. This can take several minutes on first deployment.',\n context: { phase: 'provisioning' },\n httpStatus: 503,\n timestamp: new Date().toISOString(),\n suggestion:\n 'This is expected during first deployment. The SDK will retry automatically.'\n };\n return new Response(JSON.stringify(errorBody), {\n status: 503,\n headers: {\n 'Content-Type': 'application/json',\n 'Retry-After': '10'\n }\n });\n }\n\n // 2. Permanent errors: Resource exhaustion, misconfiguration, bad image\n // These will never recover on retry — fail fast so the caller gets a clear signal.\n // Checked before transient to avoid broad transient patterns (e.g., \"container did not\n // start\") masking specific permanent causes in wrapped error messages.\n if (this.isPermanentStartupError(e)) {\n this.logger.error(\n 'Permanent container startup error, returning 500',\n e instanceof Error ? e : new Error(String(e))\n );\n const errorBody: ErrorResponse = {\n code: ErrorCode.INTERNAL_ERROR,\n message:\n 'Container failed to start due to a permanent error. Check your container configuration.',\n context: {\n phase: 'startup',\n error: e instanceof Error ? e.message : String(e)\n },\n httpStatus: 500,\n timestamp: new Date().toISOString(),\n suggestion:\n 'This error will not resolve with retries. Check container logs, image name, and resource limits.'\n };\n return new Response(JSON.stringify(errorBody), {\n status: 500,\n headers: {\n 'Content-Type': 'application/json'\n }\n });\n }\n\n // 3. Transient startup errors: Container starting, port not ready yet\n if (this.isTransientStartupError(e)) {\n // If startup failed after detecting stale state, the container runtime is likely stuck\n // (e.g., workerd can't restart after an unexpected container death). Abort the DO so the\n // next request gets a fresh instance with a clean container binding. This mirrors the\n // recovery pattern in the base Container class for 'Network connection lost' errors.\n if (staleStateDetected) {\n this.logger.warn('container.startup', {\n outcome: 'stale_state_abort',\n staleStateDetected: true,\n error: e instanceof Error ? e.message : String(e)\n });\n this.ctx.abort();\n } else {\n this.logger.debug('container.startup', {\n outcome: 'transient_error',\n staleStateDetected,\n error: e instanceof Error ? e.message : String(e)\n });\n }\n const errorBody: ErrorResponse = {\n code: ErrorCode.INTERNAL_ERROR,\n message: 'Container is starting. Please retry in a moment.',\n context: {\n phase: 'startup',\n error: e instanceof Error ? e.message : String(e)\n },\n httpStatus: 503,\n timestamp: new Date().toISOString(),\n suggestion:\n 'The container is booting. The SDK will retry automatically.'\n };\n return new Response(JSON.stringify(errorBody), {\n status: 503,\n headers: {\n 'Content-Type': 'application/json',\n 'Retry-After': '3'\n }\n });\n }\n\n // 4. Unrecognized errors: Treat as transient since retries are safe\n // and new platform error messages may not yet be in our pattern list.\n this.logger.warn('container.startup', {\n outcome: 'unrecognized_error',\n staleStateDetected,\n error: e instanceof Error ? e.message : String(e)\n });\n const errorBody: ErrorResponse = {\n code: ErrorCode.INTERNAL_ERROR,\n message: 'Container is starting. Please retry in a moment.',\n context: {\n phase: 'startup',\n error: e instanceof Error ? e.message : String(e)\n },\n httpStatus: 503,\n timestamp: new Date().toISOString(),\n suggestion:\n 'The SDK will retry automatically. If this persists, the container may need redeployment.'\n };\n return new Response(JSON.stringify(errorBody), {\n status: 503,\n headers: {\n 'Content-Type': 'application/json',\n 'Retry-After': '5'\n }\n });\n }\n }\n\n // Delegate to parent for the actual fetch (handles TCP port access internally)\n return await super.containerFetch(requestOrUrl, portOrInit, portParam);\n }\n\n /**\n * Helper: Check if error is \"no container instance available\"\n * This indicates the container VM is still being provisioned.\n */\n private isNoInstanceError(error: unknown): boolean {\n return (\n error instanceof Error &&\n error.message.toLowerCase().includes('no container instance')\n );\n }\n\n /**\n * Helper: Check if error is a transient startup error that should trigger retry\n *\n * These errors occur during normal container startup and are recoverable:\n * - Port not yet mapped (container starting, app not listening yet)\n * - Connection refused (port mapped but app not ready)\n * - Timeouts during startup (recoverable with retry)\n * - Network transients (temporary connectivity issues)\n *\n * Errors NOT included (permanent failures):\n * - \"no such image\" - missing Docker image\n * - \"container already exists\" - name collision\n * - Configuration errors\n */\n private isTransientStartupError(error: unknown): boolean {\n if (!(error instanceof Error)) return false;\n\n const msg = error.message.toLowerCase();\n\n // Transient errors from workerd container-client.c++ and @cloudflare/containers\n const transientPatterns = [\n // Port mapping race conditions (workerd DockerPort::connect)\n 'container port not found',\n 'connection refused: container port',\n\n // Application startup delays (@cloudflare/containers)\n 'the container is not listening',\n 'failed to verify port',\n 'container did not start',\n\n // Network transients (workerd)\n 'network connection lost',\n 'container suddenly disconnected',\n\n // Monitor race conditions (workerd)\n 'monitor failed to find container',\n\n // Container crashed during startup or from previous run (@cloudflare/containers)\n 'container exited with unexpected exit code',\n 'container exited before we could determine',\n\n // Timeouts (various layers)\n 'timed out',\n 'timeout',\n 'the operation was aborted'\n ];\n\n return transientPatterns.some((pattern) => msg.includes(pattern));\n }\n\n /**\n * Helper: Check if error is a permanent startup failure that will never recover\n *\n * These errors indicate resource exhaustion, misconfiguration, or missing images.\n * Retrying will never succeed, so the SDK should fail fast with HTTP 500.\n *\n * Error sources (traced from platform internals):\n * - Container runtime: OOM, PID limit\n * - Scheduling/provisioning: no matching app, no namespace configured\n * - workerd container-client.c++: no such image\n * - @cloudflare/containers: did not call start\n */\n private isPermanentStartupError(error: unknown): boolean {\n if (!(error instanceof Error)) return false;\n\n const msg = error.message.toLowerCase();\n\n const permanentPatterns = [\n // Resource exhaustion (container runtime)\n 'ran out of memory',\n 'too many subprocesses',\n\n // Misconfiguration (scheduling/provisioning)\n 'no application that matches',\n 'no container application assigned',\n\n // Missing image (workerd container-client.c++)\n 'no such image',\n\n // User error (@cloudflare/containers)\n 'did not call start'\n ];\n\n return permanentPatterns.some((pattern) => msg.includes(pattern));\n }\n\n /**\n * Helper: Parse containerFetch arguments (supports multiple signatures)\n */\n private parseContainerFetchArgs(\n requestOrUrl: Request | string | URL,\n portOrInit?: number | RequestInit,\n portParam?: number\n ): { request: Request; port: number } {\n let request: Request;\n let port: number | undefined;\n\n if (requestOrUrl instanceof Request) {\n request = requestOrUrl;\n port = typeof portOrInit === 'number' ? portOrInit : undefined;\n } else {\n const url =\n typeof requestOrUrl === 'string'\n ? requestOrUrl\n : requestOrUrl.toString();\n const init = typeof portOrInit === 'number' ? {} : portOrInit || {};\n port =\n typeof portOrInit === 'number'\n ? portOrInit\n : typeof portParam === 'number'\n ? portParam\n : undefined;\n request = new Request(url, init);\n }\n\n port ??= this.defaultPort;\n\n if (port === undefined) {\n throw new Error('No port specified for container fetch');\n }\n\n return { request, port };\n }\n\n /**\n * Override onActivityExpired to prevent automatic shutdown when keepAlive is enabled\n * When keepAlive is disabled, calls parent implementation which stops the container\n */\n override async onActivityExpired(): Promise<void> {\n if (this.keepAliveEnabled) {\n this.logger.debug(\n 'Activity expired but keepAlive is enabled - container will stay alive'\n );\n // Do nothing - don't call stop(), container stays alive\n } else {\n // Default behavior: stop the container\n this.logger.debug('Activity expired - stopping container');\n await super.onActivityExpired();\n }\n }\n\n // Override fetch to route internal container requests to appropriate ports\n override async fetch(request: Request): Promise<Response> {\n // Extract or generate trace ID from request\n const traceId =\n TraceContext.fromHeaders(request.headers) || TraceContext.generate();\n\n // Create request-specific logger with trace ID\n const requestLogger = this.logger.child({ traceId, operation: 'fetch' });\n\n const url = new URL(request.url);\n\n // Capture and store the sandbox name from the header if present\n if (!this.sandboxName && request.headers.has('X-Sandbox-Name')) {\n const name = request.headers.get('X-Sandbox-Name')!;\n this.sandboxName = name;\n await this.ctx.storage.put('sandboxName', name);\n }\n\n // Detect WebSocket upgrade request (RFC 6455 compliant)\n const upgradeHeader = request.headers.get('Upgrade');\n const connectionHeader = request.headers.get('Connection');\n const isWebSocket =\n upgradeHeader?.toLowerCase() === 'websocket' &&\n connectionHeader?.toLowerCase().includes('upgrade');\n\n if (isWebSocket) {\n // WebSocket path: Let parent Container class handle WebSocket proxying\n // This bypasses containerFetch() which uses JSRPC and cannot handle WebSocket upgrades\n try {\n requestLogger.debug('WebSocket upgrade requested', {\n path: url.pathname,\n port: this.determinePort(url)\n });\n return await super.fetch(request);\n } catch (error) {\n requestLogger.error(\n 'WebSocket connection failed',\n error instanceof Error ? error : new Error(String(error)),\n { path: url.pathname }\n );\n throw error;\n }\n }\n\n // Non-WebSocket: Use existing port determination and HTTP routing logic\n const port = this.determinePort(url);\n\n // Route to the appropriate port\n return await this.containerFetch(request, port);\n }\n\n wsConnect(request: Request, port: number): Promise<Response> {\n // Stub - actual implementation is attached by getSandbox() on the stub object\n throw new Error(\n 'wsConnect must be called on the stub returned by getSandbox()'\n );\n }\n\n private determinePort(url: URL): number {\n // Extract port from proxy requests (e.g., /proxy/8080/*)\n const proxyMatch = url.pathname.match(/^\\/proxy\\/(\\d+)/);\n if (proxyMatch) {\n return parseInt(proxyMatch[1], 10);\n }\n\n // All other requests go to control plane on port 3000\n // This includes /api/* endpoints and any other control requests\n return 3000;\n }\n\n /**\n * Return the default session id, lazily creating the container session\n * on first use. Called by every public method that needs a session.\n * Concurrent callers that target the same sessionId share one\n * in-flight initialization promise.\n */\n private async ensureDefaultSession(): Promise<string> {\n const sessionId = `sandbox-${this.sandboxName || 'default'}`;\n\n // Fast path: session already initialized in this instance\n if (this.defaultSession === sessionId) {\n return this.defaultSession;\n }\n\n // The in-flight slot is keyed by (sessionId, generation). A caller\n // whose sessionId matches the current slot AND runs against the same\n // container generation awaits that shared promise. sandboxName\n // changing mid-flight yields a sessionId mismatch; a container stop\n // advances the generation and an older slot becomes non-joinable.\n const generation = this.containerGeneration;\n const pending = this.defaultSessionInit;\n if (pending?.sessionId === sessionId && pending.generation === generation) {\n return pending.promise;\n }\n\n const promise = this.initializeDefaultSession(sessionId, generation);\n const init = { sessionId, generation, promise };\n this.defaultSessionInit = init;\n try {\n return await promise;\n } finally {\n // Identity guard: only clear the slot if it still holds this\n // attempt. A newer mismatching caller or an onStop may have\n // taken the slot.\n if (this.defaultSessionInit === init) {\n this.defaultSessionInit = null;\n }\n }\n }\n\n private async initializeDefaultSession(\n sessionId: string,\n generation: number\n ): Promise<string> {\n let placementId: string | null | undefined;\n try {\n const response = await this.client.utils.createSession({\n id: sessionId,\n env: this.envVars || {},\n cwd: '/workspace'\n });\n placementId = response.containerPlacementId;\n } catch (error: unknown) {\n // The container can outlive this DO instance, so an existing session\n // means the container is already in the state we need.\n if (!(error instanceof SessionAlreadyExistsError)) {\n throw error;\n }\n placementId = error.containerPlacementId;\n this.logger.debug(\n 'Session exists in container but not in DO state, syncing',\n { sessionId }\n );\n }\n\n // Generation check before writing state: if onStop ran while the\n // container RPC was in flight, this init targets a dead container.\n // Fail the attempt so the next caller starts fresh against the new\n // container.\n if (generation !== this.containerGeneration) {\n throw new Error(\n 'Default session initialization was invalidated by a container stop'\n );\n }\n\n // Durable storage is the cross-eviction source of truth for the default\n // session identity. Update the in-memory cache only after persistence.\n await this.ctx.storage.put('defaultSession', sessionId);\n await this.capturePlacementId(placementId);\n this.defaultSession = sessionId;\n this.logger.debug('Default session initialized', { sessionId });\n return sessionId;\n }\n\n /**\n * Persist the container's placement ID in DO storage.\n *\n * Called from the session-create handshake so subsequent reads via\n * `getContainerPlacementId()` do not require a round-trip to the container. The value\n * is overwritten on every handshake so that container replacements (which\n * assign a new placement ID) are reflected on the next session-create.\n *\n * A value of `undefined` means the handshake response omitted the field\n * (older container, unexpected error shape) and the stored value is left\n * untouched. `null` means the env var is not set in the container and is\n * stored as-is so callers can distinguish \"observed and absent\" from \"not\n * yet observed.\"\n */\n private async capturePlacementId(\n containerPlacementId: string | null | undefined\n ): Promise<void> {\n if (containerPlacementId === undefined) return;\n await this.ctx.storage.put('containerPlacementId', containerPlacementId);\n }\n\n // Enhanced exec method - always returns ExecResult with optional streaming\n // This replaces the old exec method to match ISandbox interface\n async exec(command: string, options?: ExecOptions): Promise<ExecResult> {\n const session = await this.ensureDefaultSession();\n return this.execWithSession(command, session, options);\n }\n\n /**\n * Execute an infrastructure command (backup, mount, env setup, etc.)\n * tagged with origin: 'internal' so logging demotes it to debug level.\n */\n private async execInternal(command: string): Promise<ExecResult> {\n const session = await this.ensureDefaultSession();\n return this.execWithSession(command, session, { origin: 'internal' });\n }\n\n /**\n * Internal session-aware exec implementation\n * Used by both public exec() and session wrappers\n */\n private async execWithSession(\n command: string,\n sessionId: string,\n options?: ExecOptions\n ): Promise<ExecResult> {\n const startTime = Date.now();\n const timestamp = new Date().toISOString();\n\n let timeoutId: NodeJS.Timeout | undefined;\n let execOutcome: { exitCode: number; success: boolean } | undefined;\n let execError: Error | undefined;\n\n try {\n // Handle cancellation\n if (options?.signal?.aborted) {\n throw new Error('Operation was aborted');\n }\n\n let result: ExecResult;\n\n if (options?.stream && options?.onOutput) {\n // Streaming with callbacks - we need to collect the final result\n result = await this.executeWithStreaming(\n command,\n sessionId,\n options,\n startTime,\n timestamp\n );\n } else {\n // Regular execution with session\n const commandOptions =\n options &&\n (options.timeout !== undefined ||\n options.env !== undefined ||\n options.cwd !== undefined ||\n options.origin !== undefined)\n ? {\n timeoutMs: options.timeout,\n env: options.env,\n cwd: options.cwd,\n origin: options.origin\n }\n : undefined;\n\n const response = await this.client.commands.execute(\n command,\n sessionId,\n commandOptions\n );\n\n const duration = Date.now() - startTime;\n result = this.mapExecuteResponseToExecResult(\n response,\n duration,\n sessionId\n );\n }\n\n execOutcome = { exitCode: result.exitCode, success: result.success };\n\n // Call completion callback if provided\n if (options?.onComplete) {\n options.onComplete(result);\n }\n\n return result;\n } catch (error) {\n execError = error instanceof Error ? error : new Error(String(error));\n if (options?.onError && error instanceof Error) {\n options.onError(error);\n }\n throw error;\n } finally {\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n logCanonicalEvent(this.logger, {\n event: 'sandbox.exec',\n outcome: execError ? 'error' : 'success',\n command,\n exitCode: execOutcome?.exitCode,\n durationMs: Date.now() - startTime,\n sessionId,\n origin: options?.origin ?? 'user',\n error: execError ?? undefined,\n errorMessage: execError?.message\n });\n }\n }\n\n private async executeWithStreaming(\n command: string,\n sessionId: string,\n options: ExecOptions,\n startTime: number,\n timestamp: string\n ): Promise<ExecResult> {\n let stdout = '';\n let stderr = '';\n\n try {\n const stream = await this.client.commands.executeStream(\n command,\n sessionId,\n {\n timeoutMs: options.timeout,\n env: options.env,\n cwd: options.cwd,\n origin: options.origin\n }\n );\n\n for await (const event of parseSSEStream<ExecEvent>(stream)) {\n // Check for cancellation\n if (options.signal?.aborted) {\n throw new Error('Operation was aborted');\n }\n\n switch (event.type) {\n case 'stdout':\n case 'stderr':\n if (event.data) {\n // Update accumulated output\n if (event.type === 'stdout') stdout += event.data;\n if (event.type === 'stderr') stderr += event.data;\n\n // Call user's callback\n if (options.onOutput) {\n options.onOutput(event.type, event.data);\n }\n }\n break;\n\n case 'complete': {\n // Use result from complete event if available\n const duration = Date.now() - startTime;\n return {\n success: (event.exitCode ?? 0) === 0,\n exitCode: event.exitCode ?? 0,\n stdout,\n stderr,\n command,\n duration,\n timestamp,\n sessionId\n };\n }\n\n case 'error':\n throw new Error(event.data || 'Command execution failed');\n }\n }\n\n // If we get here without a complete event, something went wrong\n throw new Error('Stream ended without completion event');\n } catch (error) {\n if (options.signal?.aborted) {\n throw new Error('Operation was aborted');\n }\n throw error;\n }\n }\n\n private mapExecuteResponseToExecResult(\n response: ExecuteResponse,\n duration: number,\n sessionId?: string\n ): ExecResult {\n return {\n success: response.success,\n exitCode: response.exitCode,\n stdout: response.stdout,\n stderr: response.stderr,\n command: response.command,\n duration,\n timestamp: response.timestamp,\n sessionId\n };\n }\n\n /**\n * Create a Process domain object from HTTP client DTO\n * Centralizes process object creation with bound methods\n * This eliminates duplication across startProcess, listProcesses, getProcess, and session wrappers\n */\n private createProcessFromDTO(\n data: {\n id: string;\n pid?: number;\n command: string;\n status: ProcessStatus;\n startTime: string | Date;\n endTime?: string | Date;\n exitCode?: number;\n },\n sessionId: string\n ): Process {\n return {\n id: data.id,\n pid: data.pid,\n command: data.command,\n status: data.status,\n startTime:\n typeof data.startTime === 'string'\n ? new Date(data.startTime)\n : data.startTime,\n endTime: data.endTime\n ? typeof data.endTime === 'string'\n ? new Date(data.endTime)\n : data.endTime\n : undefined,\n exitCode: data.exitCode,\n sessionId,\n\n kill: async (signal?: string) => {\n await this.killProcess(data.id, signal);\n },\n\n getStatus: async () => {\n const current = await this.getProcess(data.id);\n return current?.status || 'error';\n },\n\n getLogs: async () => {\n const logs = await this.getProcessLogs(data.id);\n return { stdout: logs.stdout, stderr: logs.stderr };\n },\n\n waitForLog: async (\n pattern: string | RegExp,\n timeout?: number\n ): Promise<WaitForLogResult> => {\n return this.waitForLogPattern(data.id, data.command, pattern, timeout);\n },\n\n waitForPort: async (\n port: number,\n options?: WaitForPortOptions\n ): Promise<void> => {\n await this.waitForPortReady(data.id, data.command, port, options);\n },\n\n waitForExit: async (timeout?: number): Promise<WaitForExitResult> => {\n return this.waitForProcessExit(data.id, data.command, timeout);\n }\n };\n }\n\n /**\n * Wait for a log pattern to appear in process output\n */\n private async waitForLogPattern(\n processId: string,\n command: string,\n pattern: string | RegExp,\n timeout?: number\n ): Promise<WaitForLogResult> {\n const startTime = Date.now();\n const conditionStr = this.conditionToString(pattern);\n let collectedStdout = '';\n let collectedStderr = '';\n\n // First check existing logs\n try {\n const existingLogs = await this.getProcessLogs(processId);\n // Ensure existing logs end with newline for proper line separation from streamed output\n collectedStdout = existingLogs.stdout;\n if (collectedStdout && !collectedStdout.endsWith('\\n')) {\n collectedStdout += '\\n';\n }\n collectedStderr = existingLogs.stderr;\n if (collectedStderr && !collectedStderr.endsWith('\\n')) {\n collectedStderr += '\\n';\n }\n\n // Check stdout\n const stdoutResult = this.matchPattern(existingLogs.stdout, pattern);\n if (stdoutResult) {\n return stdoutResult;\n }\n\n // Check stderr\n const stderrResult = this.matchPattern(existingLogs.stderr, pattern);\n if (stderrResult) {\n return stderrResult;\n }\n } catch (error) {\n // Process might have already exited, continue to streaming\n this.logger.debug('Could not get existing logs, will stream', {\n processId,\n error: error instanceof Error ? error.message : String(error)\n });\n }\n\n // Stream new logs and check for pattern\n const stream = await this.streamProcessLogs(processId);\n\n // Set up timeout if specified\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n let timeoutPromise: Promise<never> | undefined;\n\n if (timeout !== undefined) {\n const remainingTime = timeout - (Date.now() - startTime);\n if (remainingTime <= 0) {\n throw this.createReadyTimeoutError(\n processId,\n command,\n conditionStr,\n timeout\n );\n }\n\n timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n reject(\n this.createReadyTimeoutError(\n processId,\n command,\n conditionStr,\n timeout\n )\n );\n }, remainingTime);\n });\n }\n\n try {\n // Process stream\n const streamProcessor = async (): Promise<WaitForLogResult> => {\n const checkPattern = (): WaitForLogResult | null => {\n const stdoutResult = this.matchPattern(collectedStdout, pattern);\n if (stdoutResult) return stdoutResult;\n const stderrResult = this.matchPattern(collectedStderr, pattern);\n if (stderrResult) return stderrResult;\n return null;\n };\n\n for await (const event of parseSSEStream<LogEvent>(stream)) {\n if (event.type === 'stdout' || event.type === 'stderr') {\n const data = event.data || '';\n\n if (event.type === 'stdout') {\n collectedStdout += data;\n } else {\n collectedStderr += data;\n }\n\n const result = checkPattern();\n if (result) return result;\n }\n\n // Process exited - do final check before throwing\n if (event.type === 'exit') {\n // Final check in case pattern arrived in last chunk\n const result = checkPattern();\n if (result) return result;\n throw this.createExitedBeforeReadyError(\n processId,\n command,\n conditionStr,\n event.exitCode ?? 1\n );\n }\n }\n\n // Stream ended without exit event — do final check\n const finalResult = checkPattern();\n if (finalResult) return finalResult;\n // Stream ended without finding pattern - this indicates process exited\n throw this.createExitedBeforeReadyError(\n processId,\n command,\n conditionStr,\n 0\n );\n };\n\n // Race with timeout if specified, otherwise just run stream processor\n if (timeoutPromise) {\n return await Promise.race([streamProcessor(), timeoutPromise]);\n }\n return await streamProcessor();\n } finally {\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n }\n }\n\n /**\n * Wait for a port to become available (for process readiness checking)\n */\n private async waitForPortReady(\n processId: string,\n command: string,\n port: number,\n options?: WaitForPortOptions\n ): Promise<void> {\n const {\n mode = 'http',\n path = '/',\n status = { min: 200, max: 399 },\n timeout,\n interval = 500\n } = options ?? {};\n\n const conditionStr =\n mode === 'http' ? `port ${port} (HTTP ${path})` : `port ${port} (TCP)`;\n\n // Normalize status to min/max\n const statusMin = typeof status === 'number' ? status : status.min;\n const statusMax = typeof status === 'number' ? status : status.max;\n\n // Open streaming watch - container handles internal polling\n const stream = await this.client.ports.watchPort({\n port,\n mode,\n path,\n statusMin,\n statusMax,\n processId,\n interval\n });\n\n // Set up timeout if specified\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n let timeoutPromise: Promise<never> | undefined;\n\n if (timeout !== undefined) {\n timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n reject(\n this.createReadyTimeoutError(\n processId,\n command,\n conditionStr,\n timeout\n )\n );\n }, timeout);\n });\n }\n\n try {\n const streamProcessor = async (): Promise<void> => {\n for await (const event of parseSSEStream<PortWatchEvent>(stream)) {\n switch (event.type) {\n case 'ready':\n return; // Success!\n case 'process_exited':\n throw this.createExitedBeforeReadyError(\n processId,\n command,\n conditionStr,\n event.exitCode ?? 1\n );\n case 'error':\n throw new Error(event.error || 'Port watch failed');\n // 'watching' - continue\n }\n }\n throw new Error('Port watch stream ended unexpectedly');\n };\n\n if (timeoutPromise) {\n await Promise.race([streamProcessor(), timeoutPromise]);\n } else {\n await streamProcessor();\n }\n } finally {\n if (timeoutId) clearTimeout(timeoutId);\n // Cancel the stream to stop container-side polling\n try {\n await stream.cancel();\n } catch {\n // Stream may already be closed\n }\n }\n }\n\n /**\n * Wait for a process to exit\n * Returns the exit code\n */\n private async waitForProcessExit(\n processId: string,\n command: string,\n timeout?: number\n ): Promise<WaitForExitResult> {\n const stream = await this.streamProcessLogs(processId);\n\n // Set up timeout if specified\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n let timeoutPromise: Promise<never> | undefined;\n\n if (timeout !== undefined) {\n timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n reject(\n this.createReadyTimeoutError(\n processId,\n command,\n 'process exit',\n timeout\n )\n );\n }, timeout);\n });\n }\n\n try {\n const streamProcessor = async (): Promise<WaitForExitResult> => {\n for await (const event of parseSSEStream<LogEvent>(stream)) {\n if (event.type === 'exit') {\n return {\n exitCode: event.exitCode ?? 1\n };\n }\n }\n\n // Stream ended without exit event - shouldn't happen, but handle gracefully\n throw new Error(\n `Process ${processId} stream ended unexpectedly without exit event`\n );\n };\n\n if (timeoutPromise) {\n return await Promise.race([streamProcessor(), timeoutPromise]);\n }\n return await streamProcessor();\n } finally {\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n }\n }\n\n /**\n * Match a pattern against text\n */\n private matchPattern(\n text: string,\n pattern: string | RegExp\n ): WaitForLogResult | null {\n if (typeof pattern === 'string') {\n // Simple substring match\n if (text.includes(pattern)) {\n // Find the line containing the pattern\n const lines = text.split('\\n');\n for (const line of lines) {\n if (line.includes(pattern)) {\n return { line };\n }\n }\n return { line: pattern };\n }\n } else {\n const safePattern = new RegExp(\n pattern.source,\n pattern.flags.replace('g', '')\n );\n const match = text.match(safePattern);\n if (match) {\n // Find the full line containing the match\n const lines = text.split('\\n');\n for (const line of lines) {\n const lineMatch = line.match(safePattern);\n if (lineMatch) {\n return { line, match: lineMatch };\n }\n }\n return { line: match[0], match };\n }\n }\n return null;\n }\n\n /**\n * Convert a log pattern to a human-readable string\n */\n private conditionToString(pattern: string | RegExp): string {\n if (typeof pattern === 'string') {\n return `\"${pattern}\"`;\n }\n return pattern.toString();\n }\n\n /**\n * Create a ProcessReadyTimeoutError\n */\n private createReadyTimeoutError(\n processId: string,\n command: string,\n condition: string,\n timeout: number\n ): ProcessReadyTimeoutError {\n return new ProcessReadyTimeoutError({\n code: ErrorCode.PROCESS_READY_TIMEOUT,\n message: `Process did not become ready within ${timeout}ms. Waiting for: ${condition}`,\n context: {\n processId,\n command,\n condition,\n timeout\n },\n httpStatus: 408,\n timestamp: new Date().toISOString(),\n suggestion: `Check if your process outputs ${condition}. You can increase the timeout parameter.`\n });\n }\n\n /**\n * Create a ProcessExitedBeforeReadyError\n */\n private createExitedBeforeReadyError(\n processId: string,\n command: string,\n condition: string,\n exitCode: number\n ): ProcessExitedBeforeReadyError {\n return new ProcessExitedBeforeReadyError({\n code: ErrorCode.PROCESS_EXITED_BEFORE_READY,\n message: `Process exited with code ${exitCode} before becoming ready. Waiting for: ${condition}`,\n context: {\n processId,\n command,\n condition,\n exitCode\n },\n httpStatus: 500,\n timestamp: new Date().toISOString(),\n suggestion: 'Check process logs with getLogs() for error messages'\n });\n }\n\n // Background process management\n async startProcess(\n command: string,\n options?: ProcessOptions,\n sessionId?: string\n ): Promise<Process> {\n // Use the new HttpClient method to start the process\n try {\n const session = sessionId ?? (await this.ensureDefaultSession());\n const requestOptions = {\n ...(options?.processId !== undefined && {\n processId: options.processId\n }),\n ...(options?.timeout !== undefined && { timeoutMs: options.timeout }),\n ...(options?.env !== undefined && { env: filterEnvVars(options.env) }),\n ...(options?.cwd !== undefined && { cwd: options.cwd }),\n ...(options?.encoding !== undefined && { encoding: options.encoding }),\n ...(options?.autoCleanup !== undefined && {\n autoCleanup: options.autoCleanup\n })\n };\n\n const response = await this.client.processes.startProcess(\n command,\n session,\n requestOptions\n );\n\n const processObj = this.createProcessFromDTO(\n {\n id: response.processId,\n pid: response.pid,\n command: response.command,\n status: 'running' as ProcessStatus,\n startTime: new Date(),\n endTime: undefined,\n exitCode: undefined\n },\n session\n );\n\n // Call onStart callback if provided\n if (options?.onStart) {\n options.onStart(processObj);\n }\n\n // Start background streaming if output/exit callbacks are provided\n if (options?.onOutput || options?.onExit) {\n // Fire and forget - don't await, let it run in background\n this.startProcessCallbackStream(response.processId, options).catch(\n () => {\n // Error already handled in startProcessCallbackStream\n }\n );\n }\n\n return processObj;\n } catch (error) {\n if (options?.onError && error instanceof Error) {\n options.onError(error);\n }\n\n throw error;\n }\n }\n\n /**\n * Start background streaming for process callbacks\n * Opens SSE stream to container and routes events to callbacks\n */\n private async startProcessCallbackStream(\n processId: string,\n options: ProcessOptions\n ): Promise<void> {\n try {\n const stream = await this.client.processes.streamProcessLogs(processId);\n\n for await (const event of parseSSEStream<{\n type: string;\n data?: string;\n exitCode?: number;\n processId?: string;\n }>(stream)) {\n switch (event.type) {\n case 'stdout':\n if (event.data && options.onOutput) {\n options.onOutput('stdout', event.data);\n }\n break;\n case 'stderr':\n if (event.data && options.onOutput) {\n options.onOutput('stderr', event.data);\n }\n break;\n case 'exit':\n case 'complete':\n if (options.onExit) {\n options.onExit(event.exitCode ?? null);\n }\n return; // Stream complete\n }\n }\n } catch (error) {\n // Call onError if streaming fails\n if (options.onError && error instanceof Error) {\n options.onError(error);\n }\n // Don't rethrow - background streaming failure shouldn't crash the caller\n this.logger.error(\n 'Background process streaming failed',\n error instanceof Error ? error : new Error(String(error)),\n { processId }\n );\n }\n }\n\n async listProcesses(sessionId?: string): Promise<Process[]> {\n const session = sessionId ?? (await this.ensureDefaultSession());\n const response = await this.client.processes.listProcesses();\n\n return response.processes.map((processData) =>\n this.createProcessFromDTO(\n {\n id: processData.id,\n pid: processData.pid,\n command: processData.command,\n status: processData.status,\n startTime: processData.startTime,\n endTime: processData.endTime,\n exitCode: processData.exitCode\n },\n session\n )\n );\n }\n\n async getProcess(id: string, sessionId?: string): Promise<Process | null> {\n const session = sessionId ?? (await this.ensureDefaultSession());\n const response = await this.client.processes.getProcess(id);\n if (!response.process) {\n return null;\n }\n\n const processData = response.process;\n return this.createProcessFromDTO(\n {\n id: processData.id,\n pid: processData.pid,\n command: processData.command,\n status: processData.status,\n startTime: processData.startTime,\n endTime: processData.endTime,\n exitCode: processData.exitCode\n },\n session\n );\n }\n\n async killProcess(\n id: string,\n signal?: string,\n sessionId?: string\n ): Promise<void> {\n // Note: signal parameter is not currently supported by the HTTP client\n await this.client.processes.killProcess(id);\n }\n\n async killAllProcesses(sessionId?: string): Promise<number> {\n const response = await this.client.processes.killAllProcesses();\n return response.cleanedCount;\n }\n\n async cleanupCompletedProcesses(sessionId?: string): Promise<number> {\n // Not yet implemented - requires container endpoint\n return 0;\n }\n\n async getProcessLogs(\n id: string,\n sessionId?: string\n ): Promise<{ stdout: string; stderr: string; processId: string }> {\n const response = await this.client.processes.getProcessLogs(id);\n return {\n stdout: response.stdout,\n stderr: response.stderr,\n processId: response.processId\n };\n }\n\n // Streaming methods - return ReadableStream for RPC compatibility\n async execStream(\n command: string,\n options?: StreamOptions\n ): Promise<ReadableStream<Uint8Array>> {\n // Check for cancellation\n if (options?.signal?.aborted) {\n throw new Error('Operation was aborted');\n }\n\n const session = await this.ensureDefaultSession();\n // Get the stream from CommandClient\n return this.client.commands.executeStream(command, session, {\n timeoutMs: options?.timeout,\n env: options?.env,\n cwd: options?.cwd\n });\n }\n\n /**\n * Internal session-aware execStream implementation\n */\n private async execStreamWithSession(\n command: string,\n sessionId: string,\n options?: StreamOptions\n ): Promise<ReadableStream<Uint8Array>> {\n // Check for cancellation\n if (options?.signal?.aborted) {\n throw new Error('Operation was aborted');\n }\n\n return this.client.commands.executeStream(command, sessionId, {\n timeoutMs: options?.timeout,\n env: options?.env,\n cwd: options?.cwd\n });\n }\n\n /**\n * Stream logs from a background process as a ReadableStream.\n */\n async streamProcessLogs(\n processId: string,\n options?: { signal?: AbortSignal }\n ): Promise<ReadableStream<Uint8Array>> {\n // Check for cancellation\n if (options?.signal?.aborted) {\n throw new Error('Operation was aborted');\n }\n\n return this.client.processes.streamProcessLogs(processId);\n }\n\n async gitCheckout(\n repoUrl: string,\n options?: {\n branch?: string;\n targetDir?: string;\n sessionId?: string;\n /** Clone depth for shallow clones (e.g., 1 for latest commit only) */\n depth?: number;\n /** Maximum wall-clock time for the git clone subprocess in milliseconds */\n cloneTimeoutMs?: number;\n }\n ) {\n const session = options?.sessionId ?? (await this.ensureDefaultSession());\n return this.client.git.checkout(repoUrl, session, {\n branch: options?.branch,\n targetDir: options?.targetDir,\n depth: options?.depth,\n timeoutMs: options?.cloneTimeoutMs\n });\n }\n\n async mkdir(\n path: string,\n options: { recursive?: boolean; sessionId?: string } = {}\n ) {\n const session = options.sessionId ?? (await this.ensureDefaultSession());\n return this.client.files.mkdir(path, session, {\n recursive: options.recursive\n });\n }\n\n async writeFile(\n path: string,\n content: string | ReadableStream<Uint8Array>,\n options: { encoding?: string; sessionId?: string } = {}\n ) {\n const session = options.sessionId ?? (await this.ensureDefaultSession());\n\n if (content instanceof ReadableStream) {\n return this.client.writeFileStream(path, content, session);\n }\n\n return this.client.files.writeFile(path, content, session, {\n encoding: options.encoding\n });\n }\n\n async deleteFile(path: string, sessionId?: string) {\n const session = sessionId ?? (await this.ensureDefaultSession());\n return this.client.files.deleteFile(path, session);\n }\n\n async renameFile(oldPath: string, newPath: string, sessionId?: string) {\n const session = sessionId ?? (await this.ensureDefaultSession());\n return this.client.files.renameFile(oldPath, newPath, session);\n }\n\n async moveFile(\n sourcePath: string,\n destinationPath: string,\n sessionId?: string\n ) {\n const session = sessionId ?? (await this.ensureDefaultSession());\n return this.client.files.moveFile(sourcePath, destinationPath, session);\n }\n\n async readFile(\n path: string,\n options: { encoding?: string; sessionId?: string } = {}\n ) {\n const session = options.sessionId ?? (await this.ensureDefaultSession());\n return this.client.files.readFile(path, session, {\n encoding: options.encoding\n });\n }\n\n /**\n * Stream a file from the sandbox using Server-Sent Events\n * Returns a ReadableStream that can be consumed with streamFile() or collectFile() utilities\n * @param path - Path to the file to stream\n * @param options - Optional session ID\n */\n async readFileStream(\n path: string,\n options: { sessionId?: string } = {}\n ): Promise<ReadableStream<Uint8Array>> {\n const session = options.sessionId ?? (await this.ensureDefaultSession());\n return this.client.files.readFileStream(path, session);\n }\n\n async listFiles(\n path: string,\n options?: { recursive?: boolean; includeHidden?: boolean }\n ) {\n const session = await this.ensureDefaultSession();\n return this.client.files.listFiles(path, session, options);\n }\n\n async exists(path: string, sessionId?: string) {\n const session = sessionId ?? (await this.ensureDefaultSession());\n return this.client.files.exists(path, session);\n }\n\n /**\n * Get the noVNC preview URL for browser-based desktop viewing.\n * Confirms desktop is active, then uses exposePort() to generate\n * a token-authenticated preview URL for the noVNC port (6080).\n *\n * @param hostname - The custom domain hostname for preview URLs\n * (e.g., 'preview.example.com'). Required because preview URLs\n * use subdomain patterns that .workers.dev doesn't support.\n * @param options - Optional settings\n * @param options.token - Reuse an existing token instead of generating a new one\n * @returns The authenticated noVNC preview URL\n */\n async getDesktopStreamUrl(\n hostname: string,\n options?: { token?: string }\n ): Promise<{ url: string }> {\n // Confirm desktop is running before generating a URL\n const status = await this.client.desktop.status();\n if (status.status === 'inactive') {\n throw new Error(\n 'Desktop is not running. Call sandbox.desktop.start() first.'\n );\n }\n\n let url: string;\n\n // Try exposing port 6080; if already exposed, construct the URL from stored token\n try {\n const result = await this.exposePort(6080, {\n hostname,\n token: options?.token\n });\n url = result.url;\n } catch {\n // Port may already be exposed — look up the existing token from DO storage\n const tokens = await this.readPortTokens();\n const existingEntry = tokens['6080'];\n if (existingEntry && this.sandboxName) {\n url = this.constructPreviewUrl(\n 6080,\n this.sandboxName,\n hostname,\n existingEntry.token\n );\n } else {\n throw new Error(\n 'Failed to get desktop stream URL: port 6080 could not be exposed and no existing token found.'\n );\n }\n }\n\n // Wait for the platform to detect port 6080 using the Containers runtime's\n // built-in port readiness mechanism (getTcpPort polling). This ensures the\n // preview URL is routable before returning it to the caller.\n try {\n await this.waitForPort({\n portToCheck: 6080,\n retries: 30,\n waitInterval: 500\n });\n } catch {\n // Best-effort: if detection times out after ~15s, return the URL anyway.\n // noVNC's WebSocket auto-connect will retry on the client side.\n }\n\n return { url };\n }\n\n /**\n * Watch a directory for file system changes using native inotify.\n *\n * The returned promise resolves only after the watcher is established on the\n * filesystem, so callers can immediately perform actions that depend on the\n * watch being active. The returned stream contains the full event sequence\n * starting with the `watching` event.\n *\n * Consume the stream with `parseSSEStream<FileWatchSSEEvent>(stream)`.\n *\n * @param path - Path to watch (absolute or relative to /workspace)\n * @param options - Watch options\n */\n async watch(\n path: string,\n options: WatchOptions = {}\n ): Promise<ReadableStream<Uint8Array>> {\n const sessionId = options.sessionId ?? (await this.ensureDefaultSession());\n return this.client.watch.watch({\n path,\n recursive: options.recursive,\n include: options.include,\n exclude: options.exclude,\n sessionId\n });\n }\n\n /**\n * Check whether a path changed while this caller was disconnected.\n *\n * Pass the `version` returned from a prior call in `options.since` to learn\n * whether the path is unchanged, changed, or needs a full resync because the\n * retained change state was reset.\n *\n * @param path - Path to check (absolute or relative to /workspace)\n * @param options - Change-check options\n */\n async checkChanges(\n path: string,\n options: CheckChangesOptions = {}\n ): Promise<CheckChangesResult> {\n const sessionId = options.sessionId ?? (await this.ensureDefaultSession());\n return this.client.watch.checkChanges({\n path,\n recursive: options.recursive,\n include: options.include,\n exclude: options.exclude,\n since: options.since,\n sessionId\n });\n }\n\n /**\n * Expose a port and get a preview URL for accessing services running in the sandbox\n *\n * Preview URLs survive transient container restarts: the token and any\n * friendly name are persisted in Durable Object storage, and the port is\n * automatically re-exposed on the container when it comes back up. Tokens\n * are cleared only on explicit `unexposePort()` or full sandbox\n * `destroy()`.\n *\n * @param port - Port number to expose (1024-65535)\n * @param options - Configuration options\n * @param options.hostname - Your Worker's domain name (required for preview URL construction)\n * @param options.name - Optional friendly name for the port\n * @param options.token - Optional custom token for the preview URL (1-16 characters: lowercase letters, numbers, underscores)\n * If not provided, a random 16-character token will be generated automatically\n * @returns Preview URL information including the full URL, port number, and optional name\n *\n * @example\n * // With auto-generated token\n * const { url } = await sandbox.exposePort(8080, { hostname: 'example.com' });\n * // url: https://8080-sandbox-id-abc123random4567.example.com\n *\n * @example\n * // With custom token for stable URLs across deployments\n * const { url } = await sandbox.exposePort(8080, {\n * hostname: 'example.com',\n * token: 'my_token_v1'\n * });\n * // url: https://8080-sandbox-id-my_token_v1.example.com\n */\n async exposePort(\n port: number,\n options: { name?: string; hostname: string; token?: string }\n ) {\n const exposeStartTime = Date.now();\n let outcome: 'success' | 'error' = 'error';\n let caughtError: Error | undefined;\n try {\n if (!validatePort(port)) {\n throw new SandboxSecurityError(\n `Invalid port number: ${port}. Must be 1024-65535, excluding 3000 (sandbox control plane).`\n );\n }\n\n // Check if hostname is workers.dev domain (doesn't support wildcard subdomains)\n if (options.hostname.endsWith('.workers.dev')) {\n const errorResponse: ErrorResponse = {\n code: ErrorCode.CUSTOM_DOMAIN_REQUIRED,\n message: `Port exposure requires a custom domain. .workers.dev domains do not support wildcard subdomains required for port proxying.`,\n context: { originalError: options.hostname },\n httpStatus: 400,\n timestamp: new Date().toISOString()\n };\n throw new CustomDomainRequiredError(errorResponse);\n }\n\n // We need the sandbox name to construct preview URLs\n if (!this.sandboxName) {\n throw new Error(\n 'Sandbox name not available. Ensure sandbox is accessed through getSandbox()'\n );\n }\n\n let token: string;\n if (options.token !== undefined) {\n this.validateCustomToken(options.token);\n token = options.token;\n } else {\n token = this.generatePortToken();\n }\n\n // Allow re-exposing same port with same token, but reject if another port uses this token\n const tokens = await this.readPortTokens();\n const existingPort = Object.entries(tokens).find(\n ([p, entry]) => entry.token === token && p !== port.toString()\n );\n if (existingPort) {\n throw new SandboxSecurityError(\n `Token '${token}' is already in use by port ${existingPort[0]}. Please use a different token.`\n );\n }\n const sessionId = await this.ensureDefaultSession();\n await this.client.ports.exposePort(port, sessionId, options?.name);\n\n tokens[port.toString()] = { token, name: options?.name };\n await this.ctx.storage.put('portTokens', tokens);\n\n const url = this.constructPreviewUrl(\n port,\n this.sandboxName,\n options.hostname,\n token\n );\n\n outcome = 'success';\n\n return {\n url,\n port,\n name: options?.name\n };\n } catch (error) {\n caughtError = error instanceof Error ? error : new Error(String(error));\n throw error;\n } finally {\n logCanonicalEvent(this.logger, {\n event: 'port.expose',\n outcome,\n port,\n durationMs: Date.now() - exposeStartTime,\n name: options?.name,\n hostname: options.hostname,\n error: caughtError\n });\n }\n }\n\n async unexposePort(port: number) {\n const unexposeStartTime = Date.now();\n let outcome: 'success' | 'error' = 'error';\n let caughtError: Error | undefined;\n try {\n if (!validatePort(port)) {\n throw new SandboxSecurityError(\n `Invalid port number: ${port}. Must be 1024-65535, excluding 3000 (sandbox control plane).`\n );\n }\n\n // Storage is the source of truth for preview-URL auth, so clear\n // the token before the container RPC. A preview request that\n // arrives during the container call sees no token, fails auth,\n // and is rejected before containerFetch() can route it to the\n // process running inside the sandbox. (containerFetch() does not\n // gate on the container's exposed-port registry; it connects to\n // the port number directly.)\n const tokens = await this.readPortTokens();\n if (tokens[port.toString()]) {\n delete tokens[port.toString()];\n await this.ctx.storage.put('portTokens', tokens);\n }\n\n const sessionId = await this.ensureDefaultSession();\n try {\n await this.client.ports.unexposePort(port, sessionId);\n } catch (error) {\n // A container that was asleep when we entered wakes with an\n // empty exposed-port registry; restoreExposedPorts() has\n // nothing to replay because we just cleared the token. The\n // container then reports the port was never exposed, which is\n // the state we wanted.\n if (!(error instanceof PortNotExposedError)) {\n throw error;\n }\n }\n\n outcome = 'success';\n } catch (error) {\n caughtError = error instanceof Error ? error : new Error(String(error));\n throw error;\n } finally {\n logCanonicalEvent(this.logger, {\n event: 'port.unexpose',\n outcome,\n port,\n durationMs: Date.now() - unexposeStartTime,\n error: caughtError\n });\n }\n }\n\n async getExposedPorts(hostname: string) {\n const sessionId = await this.ensureDefaultSession();\n const response = await this.client.ports.getExposedPorts(sessionId);\n\n // We need the sandbox name to construct preview URLs\n if (!this.sandboxName) {\n throw new Error(\n 'Sandbox name not available. Ensure sandbox is accessed through getSandbox()'\n );\n }\n\n // Read all tokens from storage (protected by input gates)\n const tokens = await this.readPortTokens();\n\n // A port reported by the container with no corresponding token in\n // storage is an orphan. It cannot produce a valid preview URL (the\n // token is what the URL binds to), so it is omitted from the\n // result. The next container restart reconciles the inconsistency\n // because restoreExposedPorts() rebuilds the container registry\n // from storage.\n return response.ports.flatMap((port) => {\n const entry = tokens[port.port.toString()];\n if (!entry) {\n this.logger.warn(\n 'Port exposed on container but no token in storage; omitting from preview URL list',\n { port: port.port }\n );\n return [];\n }\n\n return [\n {\n url: this.constructPreviewUrl(\n port.port,\n this.sandboxName!,\n hostname,\n entry.token\n ),\n port: port.port,\n status: port.status\n }\n ];\n });\n }\n\n async isPortExposed(port: number): Promise<boolean> {\n try {\n const sessionId = await this.ensureDefaultSession();\n const response = await this.client.ports.getExposedPorts(sessionId);\n return response.ports.some((exposedPort) => exposedPort.port === port);\n } catch (error) {\n this.logger.error(\n 'Error checking if port is exposed',\n error instanceof Error ? error : new Error(String(error)),\n { port }\n );\n return false;\n }\n }\n\n async validatePortToken(port: number, token: string): Promise<boolean> {\n // Preview-URL auth answers from DO storage alone. onStart's\n // restoreExposedPorts() replays storage into the container on restart,\n // and destroy() clears portTokens before super.destroy() runs, so a\n // storage read gives the right answer for the interleavings this\n // method can race.\n const tokens = await this.readPortTokens();\n const entry = tokens[port.toString()];\n if (!entry) {\n return false;\n }\n\n const encoder = new TextEncoder();\n const a = encoder.encode(entry.token);\n const b = encoder.encode(token);\n\n try {\n // Workers runtime extends SubtleCrypto with timingSafeEqual\n return (\n crypto.subtle as SubtleCrypto & {\n timingSafeEqual(a: ArrayBufferView, b: ArrayBufferView): boolean;\n }\n ).timingSafeEqual(a, b);\n } catch {\n return false;\n }\n }\n\n private validateCustomToken(token: string): void {\n if (token.length === 0) {\n throw new SandboxSecurityError(`Custom token cannot be empty.`);\n }\n\n if (token.length > 16) {\n throw new SandboxSecurityError(\n `Custom token too long. Maximum 16 characters allowed. Received: ${token.length} characters.`\n );\n }\n\n if (!/^[a-z0-9_]+$/.test(token)) {\n throw new SandboxSecurityError(\n `Custom token must contain only lowercase letters (a-z), numbers (0-9), and underscores (_). Invalid token provided.`\n );\n }\n }\n\n private generatePortToken(): string {\n // Generate cryptographically secure 16-character token using Web Crypto API\n // Available in Cloudflare Workers runtime\n const array = new Uint8Array(12); // 12 bytes = 16 base64url chars (after padding removal)\n crypto.getRandomValues(array);\n\n const base64 = btoa(String.fromCharCode(...array));\n return base64\n .replace(/\\+/g, '_')\n .replace(/\\//g, '_')\n .replace(/=/g, '')\n .toLowerCase();\n }\n\n private constructPreviewUrl(\n port: number,\n sandboxId: string,\n hostname: string,\n token: string\n ): string {\n if (!validatePort(port)) {\n throw new SandboxSecurityError(\n `Invalid port number: ${port}. Must be 1024-65535, excluding 3000 (sandbox control plane).`\n );\n }\n\n // Hostnames are case-insensitive, routing requests to wrong DO instance when keys contain uppercase letters\n const effectiveId = this.sandboxName || sandboxId;\n const hasUppercase = /[A-Z]/.test(effectiveId);\n if (!this.normalizeId && hasUppercase) {\n throw new SandboxSecurityError(\n `Preview URLs require lowercase sandbox IDs. Your ID \"${effectiveId}\" contains uppercase letters.\\n\\n` +\n `To fix this:\\n` +\n `1. Create a new sandbox with: getSandbox(ns, \"${effectiveId}\", { normalizeId: true })\\n` +\n `2. This will create a sandbox with ID: \"${effectiveId.toLowerCase()}\"\\n\\n` +\n `Note: Due to DNS case-insensitivity, IDs with uppercase letters cannot be used with preview URLs.`\n );\n }\n\n const sanitizedSandboxId = sanitizeSandboxId(sandboxId).toLowerCase();\n\n const isLocalhost = isLocalhostPattern(hostname);\n\n if (isLocalhost) {\n const [host, portStr] = hostname.split(':');\n const mainPort = portStr || '80';\n\n try {\n const baseUrl = new URL(`http://${host}:${mainPort}`);\n const subdomainHost = `${port}-${sanitizedSandboxId}-${token}.${host}`;\n baseUrl.hostname = subdomainHost;\n\n return baseUrl.toString();\n } catch (error) {\n throw new SandboxSecurityError(\n `Failed to construct preview URL: ${\n error instanceof Error ? error.message : 'Unknown error'\n }`\n );\n }\n }\n\n try {\n const baseUrl = new URL(`https://${hostname}`);\n const subdomainHost = `${port}-${sanitizedSandboxId}-${token}.${hostname}`;\n baseUrl.hostname = subdomainHost;\n\n return baseUrl.toString();\n } catch (error) {\n throw new SandboxSecurityError(\n `Failed to construct preview URL: ${\n error instanceof Error ? error.message : 'Unknown error'\n }`\n );\n }\n }\n\n // ============================================================================\n // Session Management - Advanced Use Cases\n // ============================================================================\n\n /**\n * Create isolated execution session for advanced use cases\n * Returns ExecutionSession with full sandbox API bound to specific session\n */\n async createSession(options?: SessionOptions): Promise<ExecutionSession> {\n const sessionId = options?.id || `session-${Date.now()}`;\n\n const mergedEnv = {\n ...this.envVars,\n ...(options?.env ?? {})\n };\n const filteredEnv = filterEnvVars(mergedEnv);\n const envPayload =\n Object.keys(filteredEnv).length > 0 ? filteredEnv : undefined;\n\n // Create session in container\n const response = await this.client.utils.createSession({\n id: sessionId,\n ...(envPayload && { env: envPayload }),\n ...(options?.cwd && { cwd: options.cwd }),\n ...(options?.commandTimeoutMs !== undefined && {\n commandTimeoutMs: options.commandTimeoutMs\n })\n });\n\n await this.capturePlacementId(response.containerPlacementId);\n\n // Return wrapper that binds sessionId to all operations\n return this.getSessionWrapper(sessionId);\n }\n\n /**\n * Get an existing session by ID\n * Returns ExecutionSession wrapper bound to the specified session\n *\n * This is useful for retrieving sessions across different requests/contexts\n * without storing the ExecutionSession object (which has RPC lifecycle limitations)\n *\n * @param sessionId - The ID of an existing session\n * @returns ExecutionSession wrapper bound to the session\n */\n async getSession(sessionId: string): Promise<ExecutionSession> {\n // No need to verify session exists in container - operations will fail naturally if it doesn't\n return this.getSessionWrapper(sessionId);\n }\n\n /**\n * Delete an execution session\n * Cleans up session resources and removes it from the container\n * Note: Cannot delete the default session. To reset the default session,\n * use sandbox.destroy() to terminate the entire sandbox.\n *\n * @param sessionId - The ID of the session to delete\n * @returns Result with success status, sessionId, and timestamp\n * @throws Error if attempting to delete the default session\n */\n async deleteSession(sessionId: string): Promise<SessionDeleteResult> {\n // Prevent deletion of default session\n if (this.defaultSession && sessionId === this.defaultSession) {\n throw new Error(\n `Cannot delete default session '${sessionId}'. Use sandbox.destroy() to terminate the sandbox.`\n );\n }\n\n const response = await this.client.utils.deleteSession(sessionId);\n\n // Map HTTP response to result type\n return {\n success: response.success,\n sessionId: response.sessionId,\n timestamp: response.timestamp\n };\n }\n\n /**\n * Get the Cloudflare placement ID observed for the underlying container.\n *\n * The placement ID is captured during the first session-create handshake\n * after a container start and stored in Durable Object storage, so this\n * method returns the cached value without contacting the container. A new\n * placement ID is captured on each subsequent session-create handshake,\n * which occurs whenever the container has been replaced.\n *\n * Returns `null` when a handshake has completed but the container's\n * `CLOUDFLARE_PLACEMENT_ID` environment variable is not set (for example,\n * in local development).\n *\n * Returns `undefined` when no handshake has been observed yet on this\n * sandbox. Call any method that triggers session creation (such as\n * `exec()`) to populate the value.\n */\n async getContainerPlacementId(): Promise<string | null | undefined> {\n return this.ctx.storage.get<string | null>('containerPlacementId');\n }\n\n private getSessionWrapper(sessionId: string): ExecutionSession {\n // terminal: null here, added client-side by getSandbox() (WebSockets can't cross RPC)\n return {\n id: sessionId,\n terminal: null as unknown as ExecutionSession['terminal'],\n\n exec: (command, options) =>\n this.execWithSession(command, sessionId, options),\n execStream: (command, options) =>\n this.execStreamWithSession(command, sessionId, options),\n\n // Process management\n startProcess: (command, options) =>\n this.startProcess(command, options, sessionId),\n listProcesses: () => this.listProcesses(sessionId),\n getProcess: (id) => this.getProcess(id, sessionId),\n killProcess: (id, signal) => this.killProcess(id, signal),\n killAllProcesses: () => this.killAllProcesses(),\n cleanupCompletedProcesses: () => this.cleanupCompletedProcesses(),\n getProcessLogs: (id) => this.getProcessLogs(id),\n streamProcessLogs: (processId, options) =>\n this.streamProcessLogs(processId, options),\n\n // File operations - pass sessionId via options or parameter\n writeFile: (path, content, options) =>\n this.writeFile(path, content, { ...options, sessionId }),\n readFile: (path, options) =>\n this.readFile(path, { ...options, sessionId }),\n readFileStream: (path) => this.readFileStream(path, { sessionId }),\n watch: (path, options) => this.watch(path, { ...options, sessionId }),\n checkChanges: (path, options) =>\n this.checkChanges(path, { ...options, sessionId }),\n mkdir: (path, options) => this.mkdir(path, { ...options, sessionId }),\n deleteFile: (path) => this.deleteFile(path, sessionId),\n renameFile: (oldPath, newPath) =>\n this.renameFile(oldPath, newPath, sessionId),\n moveFile: (sourcePath, destPath) =>\n this.moveFile(sourcePath, destPath, sessionId),\n listFiles: (path, options) =>\n this.client.files.listFiles(path, sessionId, options),\n exists: (path) => this.exists(path, sessionId),\n\n // Git operations\n gitCheckout: (repoUrl, options) =>\n this.gitCheckout(repoUrl, { ...options, sessionId }),\n\n setEnvVars: async (envVars: Record<string, string | undefined>) => {\n const { toSet, toUnset } = partitionEnvVars(envVars);\n\n try {\n for (const key of toUnset) {\n const unsetCommand = `unset ${key}`;\n\n const result = await this.client.commands.execute(\n unsetCommand,\n sessionId,\n { origin: 'internal' }\n );\n\n if (result.exitCode !== 0) {\n throw new Error(\n `Failed to unset ${key}: ${result.stderr || 'Unknown error'}`\n );\n }\n }\n\n for (const [key, value] of Object.entries(toSet)) {\n const exportCommand = `export ${key}=${shellEscape(value)}`;\n\n const result = await this.client.commands.execute(\n exportCommand,\n sessionId,\n { origin: 'internal' }\n );\n\n if (result.exitCode !== 0) {\n throw new Error(\n `Failed to set ${key}: ${result.stderr || 'Unknown error'}`\n );\n }\n }\n } catch (error) {\n this.logger.error(\n 'Failed to set environment variables',\n error instanceof Error ? error : new Error(String(error)),\n { sessionId }\n );\n throw error;\n }\n },\n\n // Code interpreter methods - delegate to sandbox's code interpreter\n createCodeContext: (options) =>\n this.codeInterpreter.createCodeContext(options),\n runCode: async (code, options) => {\n const execution = await this.codeInterpreter.runCode(code, options);\n return execution.toJSON();\n },\n runCodeStream: (code, options) =>\n this.codeInterpreter.runCodeStream(code, options),\n listCodeContexts: () => this.codeInterpreter.listCodeContexts(),\n deleteCodeContext: (contextId) =>\n this.codeInterpreter.deleteCodeContext(contextId),\n\n // Bucket mounting - sandbox-level operations\n mountBucket: (bucket, mountPath, options) =>\n this.mountBucket(bucket, mountPath, options),\n unmountBucket: (mountPath) => this.unmountBucket(mountPath),\n\n // Backup operations - sandbox-level, uses R2 binding\n createBackup: (options) => this.createBackup(options),\n restoreBackup: (backup: DirectoryBackup) => this.restoreBackup(backup)\n };\n }\n\n // ============================================================================\n // Code interpreter methods - delegate to CodeInterpreter wrapper\n // ============================================================================\n\n async createCodeContext(\n options?: CreateContextOptions\n ): Promise<CodeContext> {\n return this.codeInterpreter.createCodeContext(options);\n }\n\n async runCode(\n code: string,\n options?: RunCodeOptions\n ): Promise<ExecutionResult> {\n const execution = await this.codeInterpreter.runCode(code, options);\n return execution.toJSON();\n }\n\n async runCodeStream(\n code: string,\n options?: RunCodeOptions\n ): Promise<ReadableStream> {\n return this.codeInterpreter.runCodeStream(code, options);\n }\n\n async listCodeContexts(): Promise<CodeContext[]> {\n return this.codeInterpreter.listCodeContexts();\n }\n\n async deleteCodeContext(contextId: string): Promise<void> {\n return this.codeInterpreter.deleteCodeContext(contextId);\n }\n\n // ============================================================================\n // Backup methods — squashfs archive + R2 storage\n // ============================================================================\n\n /** UUID v4 format validator for backup IDs */\n private static readonly UUID_REGEX =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n /**\n * Validate that a directory path is safe for backup operations.\n * Rejects empty, relative, traversal, null-byte, and unsupported-root paths.\n */\n private static validateBackupDir(dir: string, label: string): void {\n if (!dir || !dir.startsWith('/')) {\n throw new InvalidBackupConfigError({\n message: `${label} must be an absolute path`,\n code: ErrorCode.INVALID_BACKUP_CONFIG,\n httpStatus: 400,\n context: { reason: `${label} must be an absolute path` },\n timestamp: new Date().toISOString()\n });\n }\n if (dir.includes('\\0')) {\n throw new InvalidBackupConfigError({\n message: `${label} must not contain null bytes`,\n code: ErrorCode.INVALID_BACKUP_CONFIG,\n httpStatus: 400,\n context: { reason: `${label} must not contain null bytes` },\n timestamp: new Date().toISOString()\n });\n }\n if (dir.split('/').includes('..')) {\n throw new InvalidBackupConfigError({\n message: `${label} must not contain \"..\" path segments`,\n code: ErrorCode.INVALID_BACKUP_CONFIG,\n httpStatus: 400,\n context: { reason: `${label} must not contain \"..\" path segments` },\n timestamp: new Date().toISOString()\n });\n }\n const isAllowed = BACKUP_ALLOWED_PREFIXES.some(\n (prefix) => dir === prefix || dir.startsWith(`${prefix}/`)\n );\n if (!isAllowed) {\n throw new InvalidBackupConfigError({\n message: `${label} must be inside one of the supported backup roots (${BACKUP_ALLOWED_PREFIXES.join(', ')})`,\n code: ErrorCode.INVALID_BACKUP_CONFIG,\n httpStatus: 400,\n context: {\n reason: `${label} must be inside one of the supported backup roots`\n },\n timestamp: new Date().toISOString()\n });\n }\n }\n\n /**\n * Returns the R2 bucket or throws if backup is not configured.\n */\n private requireBackupBucket(): R2Bucket {\n if (!this.backupBucket) {\n throw new InvalidBackupConfigError({\n message:\n 'Backup not configured. Add a BACKUP_BUCKET R2 binding to your wrangler.jsonc.',\n code: ErrorCode.INVALID_BACKUP_CONFIG,\n httpStatus: 400,\n context: { reason: 'Missing BACKUP_BUCKET R2 binding' },\n timestamp: new Date().toISOString()\n });\n }\n return this.backupBucket;\n }\n\n private static readonly PRESIGNED_URL_EXPIRY_SECONDS = 3600;\n\n /**\n * Create a unique, dedicated session for a single backup operation.\n * Each call produces a fresh session ID so concurrent or sequential\n * operations never share shell state. Callers must destroy the session\n * in a finally block via `client.utils.deleteSession()`.\n */\n private async ensureBackupSession(): Promise<string> {\n const sessionId = `__sandbox_backup_${crypto.randomUUID()}`;\n await this.client.utils.createSession({ id: sessionId, cwd: '/' });\n return sessionId;\n }\n\n /**\n * Returns validated presigned URL configuration or throws if not configured.\n * All credential fields plus the R2 binding are required for backup to work.\n */\n private requirePresignedUrlSupport(): {\n client: AwsClient;\n accountId: string;\n bucketName: string;\n } {\n if (!this.r2Client || !this.r2AccountId || !this.backupBucketName) {\n const missing: string[] = [];\n if (!this.r2AccountId) missing.push('CLOUDFLARE_ACCOUNT_ID');\n if (!this.r2AccessKeyId) missing.push('R2_ACCESS_KEY_ID');\n if (!this.r2SecretAccessKey) missing.push('R2_SECRET_ACCESS_KEY');\n if (!this.backupBucketName) missing.push('BACKUP_BUCKET_NAME');\n\n throw new InvalidBackupConfigError({\n message:\n `Backup requires R2 presigned URL credentials. ` +\n `Missing: ${missing.join(', ')}. ` +\n 'Set these as environment variables or secrets in your wrangler.jsonc.',\n code: ErrorCode.INVALID_BACKUP_CONFIG,\n httpStatus: 400,\n context: { reason: `Missing env vars: ${missing.join(', ')}` },\n timestamp: new Date().toISOString()\n });\n }\n\n return {\n client: this.r2Client,\n accountId: this.r2AccountId,\n bucketName: this.backupBucketName\n };\n }\n\n /**\n * Generate a presigned PUT URL for uploading an object to R2.\n * The container can curl PUT to this URL directly without credentials.\n */\n private async generatePresignedPutUrl(r2Key: string): Promise<string> {\n const { client, accountId, bucketName } = this.requirePresignedUrlSupport();\n\n const encodedBucket = encodeURIComponent(bucketName);\n const encodedKey = r2Key\n .split('/')\n .map((seg) => encodeURIComponent(seg))\n .join('/');\n const url = new URL(\n `https://${accountId}.r2.cloudflarestorage.com/${encodedBucket}/${encodedKey}`\n );\n url.searchParams.set(\n 'X-Amz-Expires',\n String(Sandbox.PRESIGNED_URL_EXPIRY_SECONDS)\n );\n\n const signed = await client.sign(new Request(url, { method: 'PUT' }), {\n aws: { signQuery: true }\n });\n\n return signed.url;\n }\n\n /**\n * Upload a backup archive via presigned PUT URL.\n * The container curls the archive directly to R2, bypassing the DO.\n * ~24 MB/s throughput vs ~0.6 MB/s for base64 readFile.\n */\n private async uploadBackupPresigned(\n archivePath: string,\n r2Key: string,\n archiveSize: number,\n backupId: string,\n dir: string,\n backupSession: string\n ): Promise<void> {\n const presignedUrl = await this.generatePresignedPutUrl(r2Key);\n\n const curlCmd = [\n 'curl -sSf',\n '-X PUT',\n \"-H 'Content-Type: application/octet-stream'\",\n '--connect-timeout 10',\n '--max-time 1800',\n '--retry 2',\n '--retry-max-time 60',\n `-T ${shellEscape(archivePath)}`,\n shellEscape(presignedUrl)\n ].join(' ');\n\n const result = await this.execWithSession(curlCmd, backupSession, {\n timeout: 1810_000,\n origin: 'internal'\n });\n\n if (result.exitCode !== 0) {\n throw new BackupCreateError({\n message: `Presigned URL upload failed (exit code ${result.exitCode}): ${result.stderr}`,\n code: ErrorCode.BACKUP_CREATE_FAILED,\n httpStatus: 500,\n context: { dir, backupId },\n timestamp: new Date().toISOString()\n });\n }\n\n // Verify the upload landed correctly in R2\n const bucket = this.requireBackupBucket();\n const head = await bucket.head(r2Key);\n if (!head || head.size !== archiveSize) {\n const actualSize = head?.size ?? 0;\n // curl succeeded but R2 binding sees nothing — almost certainly a\n // local-dev mismatch where presigned URLs target real R2 while the\n // BACKUP_BUCKET binding points to local (miniflare) storage.\n const localDevHint =\n result.exitCode === 0 && actualSize === 0\n ? ' This usually means the BACKUP_BUCKET R2 binding is using local storage ' +\n 'while presigned URLs upload to remote R2. Add `\"remote\": true` to your ' +\n 'BACKUP_BUCKET R2 binding in wrangler.jsonc to fix this.'\n : '';\n throw new BackupCreateError({\n message: `Upload verification failed: expected ${archiveSize} bytes, got ${actualSize}.${localDevHint}`,\n code: ErrorCode.BACKUP_CREATE_FAILED,\n httpStatus: 500,\n context: { dir, backupId },\n timestamp: new Date().toISOString()\n });\n }\n }\n\n /**\n * Mount a backup archive from R2 via s3fs so squashfuse can read it lazily.\n */\n private async mountBackupR2(\n mountPath: string,\n prefix: string,\n backupSession: string\n ): Promise<void> {\n const { accountId, bucketName } = this.requirePresignedUrlSupport();\n const endpoint = `https://${accountId}.r2.cloudflarestorage.com`;\n const normalizedPrefix = prefix.startsWith('/') ? prefix : `/${prefix}`;\n const options: RemoteMountBucketOptions = {\n endpoint,\n provider: 'r2',\n readOnly: true,\n prefix: normalizedPrefix,\n s3fsOptions: ['use_path_request_style']\n };\n const passwordFilePath = this.generatePasswordFilePath();\n const s3fsSource = buildS3fsSource(bucketName, normalizedPrefix);\n const envObj = this.env as Record<string, unknown>;\n const envCredentials = {\n AWS_ACCESS_KEY_ID: getEnvString(envObj, 'AWS_ACCESS_KEY_ID'),\n AWS_SECRET_ACCESS_KEY: getEnvString(envObj, 'AWS_SECRET_ACCESS_KEY'),\n R2_ACCESS_KEY_ID: this.r2AccessKeyId || undefined,\n R2_SECRET_ACCESS_KEY: this.r2SecretAccessKey || undefined\n };\n const credentials = detectCredentials(options, {\n ...envCredentials,\n ...this.envVars\n });\n const mountInfo: FuseMountInfo = {\n mountType: 'fuse',\n bucket: s3fsSource,\n mountPath,\n endpoint,\n provider: 'r2',\n passwordFilePath,\n mounted: false\n };\n\n this.activeMounts.set(mountPath, mountInfo);\n\n try {\n await this.createPasswordFile(passwordFilePath, bucketName, credentials);\n await this.execWithSession(\n `mkdir -p ${shellEscape(mountPath)}`,\n backupSession,\n { origin: 'internal' }\n );\n await this.executeS3FSMount(\n s3fsSource,\n mountPath,\n options,\n 'r2',\n passwordFilePath,\n backupSession\n );\n mountInfo.mounted = true;\n } catch (error) {\n await this.deletePasswordFile(passwordFilePath);\n this.activeMounts.delete(mountPath);\n throw error;\n }\n }\n\n /**\n * Serialize backup operations on this sandbox instance.\n * Concurrent backup/restore calls are queued so the multi-step\n * create-archive → read → upload (or mount → extract) flow\n * is not interleaved with another backup operation on the same directory.\n */\n private enqueueBackupOp<T>(fn: () => Promise<T>): Promise<T> {\n const next = this.backupInProgress.then(fn, () => fn());\n this.backupInProgress = next.catch(() => {});\n return next;\n }\n\n /**\n * Create a backup of a directory and upload it to R2.\n *\n * Flow:\n * 1. Container creates squashfs archive from the directory\n * 2. Container uploads the archive directly to R2 via presigned URL\n * 3. DO writes metadata to R2\n * 4. Container cleans up the local archive\n *\n * The returned DirectoryBackup handle is serializable. Store it anywhere\n * (KV, D1, DO storage) and pass it to restoreBackup() later.\n *\n * Concurrent backup/restore calls on the same sandbox are serialized.\n *\n * Partially-written files in the target directory may not be captured\n * consistently. Completed writes are captured.\n *\n * NOTE: Expired backups are not automatically deleted from R2. Configure\n * R2 lifecycle rules on the BACKUP_BUCKET to garbage-collect objects\n * under the `backups/` prefix after the desired retention period.\n */\n async createBackup(options: BackupOptions): Promise<DirectoryBackup> {\n if (options.localBucket) {\n return this.enqueueBackupOp(() => this.doCreateBackupLocal(options));\n }\n this.requireBackupBucket();\n return this.enqueueBackupOp(() => this.doCreateBackup(options));\n }\n\n private async doCreateBackup(\n options: BackupOptions\n ): Promise<DirectoryBackup> {\n const bucket = this.requireBackupBucket();\n this.requirePresignedUrlSupport();\n const {\n dir,\n name,\n ttl = BACKUP_DEFAULT_TTL_SECONDS,\n gitignore = false,\n excludes = []\n } = options;\n\n const backupStartTime = Date.now();\n let backupId: string | undefined;\n let sizeBytes: number | undefined;\n let outcome: 'success' | 'error' = 'error';\n let caughtError: Error | undefined;\n let backupSession: string | undefined;\n\n try {\n Sandbox.validateBackupDir(dir, 'BackupOptions.dir');\n if (name !== undefined) {\n if (typeof name !== 'string' || name.length > BACKUP_MAX_NAME_LENGTH) {\n throw new InvalidBackupConfigError({\n message: `BackupOptions.name must be a string of at most ${BACKUP_MAX_NAME_LENGTH} characters`,\n code: ErrorCode.INVALID_BACKUP_CONFIG,\n httpStatus: 400,\n context: {\n reason: `name must be a string of at most ${BACKUP_MAX_NAME_LENGTH} characters`\n },\n timestamp: new Date().toISOString()\n });\n }\n // Reject control characters (could cause issues in R2 metadata or downstream systems)\n // biome-ignore lint/suspicious/noControlCharactersInRegex: intentionally matching control chars\n if (/[\\u0000-\\u001f\\u007f]/.test(name)) {\n throw new InvalidBackupConfigError({\n message: 'BackupOptions.name must not contain control characters',\n code: ErrorCode.INVALID_BACKUP_CONFIG,\n httpStatus: 400,\n context: { reason: 'name must not contain control characters' },\n timestamp: new Date().toISOString()\n });\n }\n }\n if (ttl <= 0) {\n throw new InvalidBackupConfigError({\n message: 'BackupOptions.ttl must be a positive number of seconds',\n code: ErrorCode.INVALID_BACKUP_CONFIG,\n httpStatus: 400,\n context: { reason: 'ttl must be a positive number of seconds' },\n timestamp: new Date().toISOString()\n });\n }\n\n if (typeof gitignore !== 'boolean') {\n throw new InvalidBackupConfigError({\n message: 'BackupOptions.gitignore must be a boolean',\n code: ErrorCode.INVALID_BACKUP_CONFIG,\n httpStatus: 400,\n context: { reason: 'gitignore must be a boolean' },\n timestamp: new Date().toISOString()\n });\n }\n\n if (\n !Array.isArray(excludes) ||\n !excludes.every((e: unknown) => typeof e === 'string')\n ) {\n throw new InvalidBackupConfigError({\n message: 'BackupOptions.excludes must be an array of strings',\n code: ErrorCode.INVALID_BACKUP_CONFIG,\n httpStatus: 400,\n context: { reason: 'excludes must be an array of strings' },\n timestamp: new Date().toISOString()\n });\n }\n\n backupSession = await this.ensureBackupSession();\n backupId = crypto.randomUUID();\n const archivePath = `${BACKUP_CONTAINER_DIR}/${backupId}.sqsh`;\n\n const createResult = await this.client.backup.createArchive(\n dir,\n archivePath,\n backupSession,\n { gitignore, excludes }\n );\n\n if (!createResult.success) {\n throw new BackupCreateError({\n message: 'Container failed to create backup archive',\n code: ErrorCode.BACKUP_CREATE_FAILED,\n httpStatus: 500,\n context: { dir, backupId },\n timestamp: new Date().toISOString()\n });\n }\n\n sizeBytes = createResult.sizeBytes;\n const r2Key = `${BACKUP_STORAGE_PREFIX}/${backupId}/${BACKUP_ARCHIVE_OBJECT_NAME}`;\n const metaKey = `${BACKUP_STORAGE_PREFIX}/${backupId}/${BACKUP_METADATA_OBJECT_NAME}`;\n\n // Step 2: Upload archive to R2 via presigned URL (isolated backup session)\n await this.uploadBackupPresigned(\n archivePath,\n r2Key,\n createResult.sizeBytes,\n backupId,\n dir,\n backupSession\n );\n\n // Step 3: Write metadata alongside the archive\n const metadata = {\n id: backupId,\n dir,\n name: name || null,\n sizeBytes: createResult.sizeBytes,\n ttl,\n createdAt: new Date().toISOString()\n };\n await bucket.put(metaKey, JSON.stringify(metadata));\n\n outcome = 'success';\n\n // Clean up the local archive in the container\n await this.execWithSession(\n `rm -f ${shellEscape(archivePath)}`,\n backupSession,\n { origin: 'internal' }\n ).catch(() => {});\n\n return { id: backupId, dir };\n } catch (error) {\n caughtError = error instanceof Error ? error : new Error(String(error));\n // Clean up local archive and any partially-uploaded R2 objects\n if (backupId && backupSession) {\n const archivePath = `${BACKUP_CONTAINER_DIR}/${backupId}.sqsh`;\n const r2Key = `${BACKUP_STORAGE_PREFIX}/${backupId}/${BACKUP_ARCHIVE_OBJECT_NAME}`;\n const metaKey = `${BACKUP_STORAGE_PREFIX}/${backupId}/${BACKUP_METADATA_OBJECT_NAME}`;\n await this.execWithSession(\n `rm -f ${shellEscape(archivePath)}`,\n backupSession,\n { origin: 'internal' }\n ).catch(() => {});\n await bucket.delete(r2Key).catch(() => {});\n await bucket.delete(metaKey).catch(() => {});\n }\n throw error;\n } finally {\n if (backupSession) {\n await this.client.utils.deleteSession(backupSession).catch(() => {});\n }\n logCanonicalEvent(this.logger, {\n event: 'backup.create',\n outcome,\n durationMs: Date.now() - backupStartTime,\n backupId,\n dir,\n name,\n sizeBytes,\n error: caughtError\n });\n }\n }\n\n /**\n * Local-dev implementation of createBackup.\n * Uses the R2 binding directly instead of presigned URLs.\n * Archive format is identical to production (squashfs + meta.json).\n */\n private async doCreateBackupLocal(\n options: BackupOptions\n ): Promise<DirectoryBackup> {\n const {\n dir,\n name,\n ttl = BACKUP_DEFAULT_TTL_SECONDS,\n gitignore = false,\n excludes = []\n } = options;\n\n const backupStartTime = Date.now();\n let backupId: string | undefined;\n let sizeBytes: number | undefined;\n let outcome: 'success' | 'error' = 'error';\n let caughtError: Error | undefined;\n let backupSession: string | undefined;\n\n // Resolve backup bucket from env as an R2 binding\n const envObj = this.env as Record<string, unknown>;\n const bucket = envObj.BACKUP_BUCKET;\n if (!bucket || !isR2Bucket(bucket)) {\n throw new InvalidBackupConfigError({\n message:\n 'BACKUP_BUCKET R2 binding not found in env. ' +\n 'Add a BACKUP_BUCKET R2 binding to your wrangler.jsonc for local backup support.',\n code: ErrorCode.INVALID_BACKUP_CONFIG,\n httpStatus: 400,\n context: { reason: 'Missing BACKUP_BUCKET R2 binding' },\n timestamp: new Date().toISOString()\n });\n }\n\n try {\n Sandbox.validateBackupDir(dir, 'BackupOptions.dir');\n if (name !== undefined) {\n if (typeof name !== 'string' || name.length > BACKUP_MAX_NAME_LENGTH) {\n throw new InvalidBackupConfigError({\n message: `BackupOptions.name must be a string of at most ${BACKUP_MAX_NAME_LENGTH} characters`,\n code: ErrorCode.INVALID_BACKUP_CONFIG,\n httpStatus: 400,\n context: {\n reason: `name must be a string of at most ${BACKUP_MAX_NAME_LENGTH} characters`\n },\n timestamp: new Date().toISOString()\n });\n }\n // biome-ignore lint/suspicious/noControlCharactersInRegex: intentionally matching control chars\n if (/[\\u0000-\\u001f\\u007f]/.test(name)) {\n throw new InvalidBackupConfigError({\n message: 'BackupOptions.name must not contain control characters',\n code: ErrorCode.INVALID_BACKUP_CONFIG,\n httpStatus: 400,\n context: { reason: 'name must not contain control characters' },\n timestamp: new Date().toISOString()\n });\n }\n }\n if (ttl <= 0) {\n throw new InvalidBackupConfigError({\n message: 'BackupOptions.ttl must be a positive number of seconds',\n code: ErrorCode.INVALID_BACKUP_CONFIG,\n httpStatus: 400,\n context: { reason: 'ttl must be a positive number of seconds' },\n timestamp: new Date().toISOString()\n });\n }\n if (typeof gitignore !== 'boolean') {\n throw new InvalidBackupConfigError({\n message: 'BackupOptions.gitignore must be a boolean',\n code: ErrorCode.INVALID_BACKUP_CONFIG,\n httpStatus: 400,\n context: { reason: 'gitignore must be a boolean' },\n timestamp: new Date().toISOString()\n });\n }\n if (\n !Array.isArray(excludes) ||\n !excludes.every((e: unknown) => typeof e === 'string')\n ) {\n throw new InvalidBackupConfigError({\n message: 'BackupOptions.excludes must be an array of strings',\n code: ErrorCode.INVALID_BACKUP_CONFIG,\n httpStatus: 400,\n context: { reason: 'excludes must be an array of strings' },\n timestamp: new Date().toISOString()\n });\n }\n\n backupSession = await this.ensureBackupSession();\n backupId = crypto.randomUUID();\n const archivePath = `${BACKUP_CONTAINER_DIR}/${backupId}.sqsh`;\n\n // Step 1: Create squashfs archive in the container (same as production)\n const createResult = await this.client.backup.createArchive(\n dir,\n archivePath,\n backupSession,\n { gitignore, excludes }\n );\n\n if (!createResult.success) {\n throw new BackupCreateError({\n message: 'Container failed to create backup archive',\n code: ErrorCode.BACKUP_CREATE_FAILED,\n httpStatus: 500,\n context: { dir, backupId },\n timestamp: new Date().toISOString()\n });\n }\n\n sizeBytes = createResult.sizeBytes;\n const r2Key = `${BACKUP_STORAGE_PREFIX}/${backupId}/${BACKUP_ARCHIVE_OBJECT_NAME}`;\n const metaKey = `${BACKUP_STORAGE_PREFIX}/${backupId}/${BACKUP_METADATA_OBJECT_NAME}`;\n\n // Step 2: Read archive from container via file streaming, upload to R2 via binding\n const archiveStream = await this.client.files.readFileStream(\n archivePath,\n backupSession\n );\n const { content } = await collectFile(archiveStream);\n const archiveData =\n content instanceof Uint8Array\n ? content\n : new TextEncoder().encode(content);\n await bucket.put(r2Key, archiveData);\n\n // Verify upload\n const head = await bucket.head(r2Key);\n if (!head || head.size !== createResult.sizeBytes) {\n throw new BackupCreateError({\n message: `Upload verification failed: expected ${createResult.sizeBytes} bytes, got ${head?.size ?? 0}`,\n code: ErrorCode.BACKUP_CREATE_FAILED,\n httpStatus: 500,\n context: { dir, backupId },\n timestamp: new Date().toISOString()\n });\n }\n\n // Step 3: Write metadata\n const metadata = {\n id: backupId,\n dir,\n name: name || null,\n sizeBytes: createResult.sizeBytes,\n ttl,\n createdAt: new Date().toISOString()\n };\n await bucket.put(metaKey, JSON.stringify(metadata));\n\n outcome = 'success';\n\n // Clean up local archive\n await this.execWithSession(\n `rm -f ${shellEscape(archivePath)}`,\n backupSession,\n { origin: 'internal' }\n ).catch(() => {});\n\n return { id: backupId, dir, localBucket: true };\n } catch (error) {\n caughtError = error instanceof Error ? error : new Error(String(error));\n if (backupId && backupSession) {\n const archivePath = `${BACKUP_CONTAINER_DIR}/${backupId}.sqsh`;\n const r2Key = `${BACKUP_STORAGE_PREFIX}/${backupId}/${BACKUP_ARCHIVE_OBJECT_NAME}`;\n const metaKey = `${BACKUP_STORAGE_PREFIX}/${backupId}/${BACKUP_METADATA_OBJECT_NAME}`;\n await this.execWithSession(\n `rm -f ${shellEscape(archivePath)}`,\n backupSession,\n { origin: 'internal' }\n ).catch(() => {});\n await bucket.delete(r2Key).catch(() => {});\n await bucket.delete(metaKey).catch(() => {});\n }\n throw error;\n } finally {\n if (backupSession) {\n await this.client.utils.deleteSession(backupSession).catch(() => {});\n }\n logCanonicalEvent(this.logger, {\n event: 'backup.create',\n outcome,\n durationMs: Date.now() - backupStartTime,\n backupId,\n dir,\n name,\n sizeBytes,\n provider: 'local-binding',\n error: caughtError\n });\n }\n }\n\n /**\n * Restore a backup from R2 into a directory.\n *\n * **Production flow** (`localBucket` not set):\n * 1. DO reads metadata from R2 and checks TTL\n * 2. Container mounts the backup archive from R2 via s3fs\n * 3. Container mounts the squashfs archive with FUSE overlayfs\n *\n * The target directory becomes an overlay mount with the backup as a\n * read-only lower layer and a writable upper layer for copy-on-write.\n * Any processes writing to the directory should be stopped first.\n *\n * **Mount Lifecycle**: The FUSE overlay mount persists only while the\n * container is running. When the sandbox sleeps or the container restarts,\n * the mount is lost and the directory becomes empty. Re-restore from the\n * backup handle to recover. This is an ephemeral restore, not a persistent\n * extraction.\n *\n * **Local-dev flow** (`localBucket: true` on the originating `createBackup` call):\n * 1. DO reads metadata and checks TTL via R2 binding\n * 2. DO downloads the archive from R2 and writes it to the container\n * 3. Container extracts the archive with `unsquashfs` (no FUSE needed)\n *\n * The backup is restored into `backup.dir`. This may differ from the\n * directory that was originally backed up, allowing cross-directory restore.\n *\n * Overlapping backups are independent: restoring a parent directory\n * overwrites everything inside it, including subdirectories that were\n * backed up separately. When restoring both, restore the parent first.\n *\n * Concurrent backup/restore calls on the same sandbox are serialized.\n */\n async restoreBackup(backup: DirectoryBackup): Promise<RestoreBackupResult> {\n if (backup.localBucket) {\n return this.enqueueBackupOp(() => this.doRestoreBackupLocal(backup));\n }\n this.requireBackupBucket();\n return this.enqueueBackupOp(() => this.doRestoreBackup(backup));\n }\n\n private async doRestoreBackup(\n backup: DirectoryBackup\n ): Promise<RestoreBackupResult> {\n const restoreStartTime = Date.now();\n const bucket = this.requireBackupBucket();\n this.requirePresignedUrlSupport();\n const { id, dir } = backup;\n\n let outcome: 'success' | 'error' = 'error';\n let caughtError: Error | undefined;\n let backupSession: string | undefined;\n\n try {\n // Validate user-provided inputs (DirectoryBackup is deserialized from external storage)\n if (!id || typeof id !== 'string') {\n throw new InvalidBackupConfigError({\n message: 'Invalid backup: missing or invalid id',\n code: ErrorCode.INVALID_BACKUP_CONFIG,\n httpStatus: 400,\n context: { reason: 'missing or invalid id' },\n timestamp: new Date().toISOString()\n });\n }\n if (!Sandbox.UUID_REGEX.test(id)) {\n throw new InvalidBackupConfigError({\n message:\n 'Invalid backup: id must be a valid UUID (e.g. from createBackup)',\n code: ErrorCode.INVALID_BACKUP_CONFIG,\n httpStatus: 400,\n context: { reason: 'id must be a valid UUID' },\n timestamp: new Date().toISOString()\n });\n }\n Sandbox.validateBackupDir(dir, 'Invalid backup: dir');\n\n // Step 1: Read metadata to check TTL\n const metaKey = `${BACKUP_STORAGE_PREFIX}/${id}/${BACKUP_METADATA_OBJECT_NAME}`;\n const metaObject = await bucket.get(metaKey);\n if (!metaObject) {\n throw new BackupNotFoundError({\n message:\n `Backup not found: ${id}. ` +\n 'Verify the backup ID is correct and the backup has not been deleted.',\n code: ErrorCode.BACKUP_NOT_FOUND,\n httpStatus: 404,\n context: { backupId: id },\n timestamp: new Date().toISOString()\n });\n }\n\n const metadata = await metaObject.json<{\n ttl: number;\n createdAt: string;\n dir: string;\n }>();\n\n // Check TTL with 60-second buffer to prevent race between check and restore completion\n const TTL_BUFFER_MS = 60 * 1000;\n const createdAt = new Date(metadata.createdAt).getTime();\n if (Number.isNaN(createdAt)) {\n throw new BackupRestoreError({\n message: `Backup metadata has invalid createdAt timestamp: ${metadata.createdAt}`,\n code: ErrorCode.BACKUP_RESTORE_FAILED,\n httpStatus: 500,\n context: { dir, backupId: id },\n timestamp: new Date().toISOString()\n });\n }\n const expiresAt = createdAt + metadata.ttl * 1000;\n if (Date.now() + TTL_BUFFER_MS > expiresAt) {\n throw new BackupExpiredError({\n message:\n `Backup ${id} has expired ` +\n `(created: ${metadata.createdAt}, TTL: ${metadata.ttl}s). ` +\n 'Create a new backup.',\n code: ErrorCode.BACKUP_EXPIRED,\n httpStatus: 400,\n context: {\n backupId: id,\n expiredAt: new Date(expiresAt).toISOString()\n },\n timestamp: new Date().toISOString()\n });\n }\n\n // Step 2: Check archive exists via HEAD (no body stream)\n const r2Key = `${BACKUP_STORAGE_PREFIX}/${id}/${BACKUP_ARCHIVE_OBJECT_NAME}`;\n const archiveHead = await bucket.head(r2Key);\n if (!archiveHead) {\n throw new BackupNotFoundError({\n message:\n `Backup archive not found in R2: ${id}. ` +\n 'The archive may have been deleted by R2 lifecycle rules.',\n code: ErrorCode.BACKUP_NOT_FOUND,\n httpStatus: 404,\n context: { backupId: id },\n timestamp: new Date().toISOString()\n });\n }\n\n backupSession = await this.ensureBackupSession();\n const r2MountPath = `${BACKUP_CONTAINER_DIR}/r2mount/${id}`;\n const archivePath = `${r2MountPath}/data.sqsh`;\n\n // Step 3: Tear down existing restore mounts before replacing the backing\n // R2 mount. squashfuse reads the archive lazily through the s3fs mount,\n // so the overlay and lower mounts must come down before the bucket mount.\n const mountGlob = `/var/backups/mounts/r2mount/${id}/data`;\n await this.execWithSession(\n `/usr/bin/fusermount3 -uz ${shellEscape(dir)} 2>/dev/null || true`,\n backupSession,\n { origin: 'internal' }\n ).catch(() => {});\n await this.execWithSession(\n `for d in ${shellEscape(mountGlob)}_*/lower ${shellEscape(mountGlob)}/lower; do [ -d \"$d\" ] && /usr/bin/fusermount3 -uz \"$d\" 2>/dev/null; done; true`,\n backupSession,\n { origin: 'internal' }\n ).catch(() => {});\n await this.execWithSession(\n `/usr/bin/fusermount3 -u ${shellEscape(r2MountPath)} 2>/dev/null; /usr/bin/fusermount3 -uz ${shellEscape(r2MountPath)} 2>/dev/null; true`,\n backupSession,\n { origin: 'internal' }\n ).catch(() => {});\n\n const previousBackupMount = this.activeMounts.get(r2MountPath);\n if (previousBackupMount?.mountType === 'fuse') {\n previousBackupMount.mounted = false;\n this.activeMounts.delete(r2MountPath);\n await this.deletePasswordFile(previousBackupMount.passwordFilePath);\n }\n\n // Step 4: Mount the backup archive from R2 and restore directly from it.\n await this.mountBackupR2(r2MountPath, `backups/${id}/`, backupSession);\n\n const restoreResult = await this.client.backup.restoreArchive(\n dir,\n archivePath,\n backupSession\n );\n\n if (!restoreResult.success) {\n throw new BackupRestoreError({\n message: 'Container failed to restore backup archive',\n code: ErrorCode.BACKUP_RESTORE_FAILED,\n httpStatus: 500,\n context: { dir, backupId: id },\n timestamp: new Date().toISOString()\n });\n }\n\n outcome = 'success';\n\n return {\n success: true,\n dir,\n id\n };\n } catch (error) {\n caughtError = error instanceof Error ? error : new Error(String(error));\n throw error;\n } finally {\n if (backupSession) {\n await this.client.utils.deleteSession(backupSession).catch(() => {});\n }\n logCanonicalEvent(this.logger, {\n event: 'backup.restore',\n outcome,\n durationMs: Date.now() - restoreStartTime,\n backupId: id,\n dir,\n error: caughtError\n });\n }\n }\n\n /**\n * Local-dev implementation of restoreBackup.\n * Uses the R2 binding directly instead of presigned URLs, and\n * unsquashfs for extraction instead of squashfuse + fuse-overlayfs.\n */\n private async doRestoreBackupLocal(\n backup: DirectoryBackup\n ): Promise<RestoreBackupResult> {\n const restoreStartTime = Date.now();\n const { id, dir } = backup;\n\n let outcome: 'success' | 'error' = 'error';\n let caughtError: Error | undefined;\n let backupSession: string | undefined;\n\n // Resolve backup bucket from env as an R2 binding\n const envObj = this.env as Record<string, unknown>;\n const bucket = envObj.BACKUP_BUCKET;\n if (!bucket || !isR2Bucket(bucket)) {\n throw new InvalidBackupConfigError({\n message:\n 'BACKUP_BUCKET R2 binding not found in env. ' +\n 'Add a BACKUP_BUCKET R2 binding to your wrangler.jsonc for local backup support.',\n code: ErrorCode.INVALID_BACKUP_CONFIG,\n httpStatus: 400,\n context: { reason: 'Missing BACKUP_BUCKET R2 binding' },\n timestamp: new Date().toISOString()\n });\n }\n\n try {\n // Validate user-provided inputs\n if (!id || typeof id !== 'string') {\n throw new InvalidBackupConfigError({\n message: 'Invalid backup: missing or invalid id',\n code: ErrorCode.INVALID_BACKUP_CONFIG,\n httpStatus: 400,\n context: { reason: 'missing or invalid id' },\n timestamp: new Date().toISOString()\n });\n }\n if (!Sandbox.UUID_REGEX.test(id)) {\n throw new InvalidBackupConfigError({\n message:\n 'Invalid backup: id must be a valid UUID (e.g. from createBackup)',\n code: ErrorCode.INVALID_BACKUP_CONFIG,\n httpStatus: 400,\n context: { reason: 'id must be a valid UUID' },\n timestamp: new Date().toISOString()\n });\n }\n Sandbox.validateBackupDir(dir, 'Invalid backup: dir');\n\n // Step 1: Read metadata to check TTL\n const metaKey = `${BACKUP_STORAGE_PREFIX}/${id}/${BACKUP_METADATA_OBJECT_NAME}`;\n const metaObject = await bucket.get(metaKey);\n if (!metaObject) {\n throw new BackupNotFoundError({\n message:\n `Backup not found: ${id}. ` +\n 'Verify the backup ID is correct and the backup has not been deleted.',\n code: ErrorCode.BACKUP_NOT_FOUND,\n httpStatus: 404,\n context: { backupId: id },\n timestamp: new Date().toISOString()\n });\n }\n\n const metadata = await metaObject.json<{\n ttl: number;\n createdAt: string;\n dir: string;\n }>();\n\n // Check TTL with 60-second buffer\n const TTL_BUFFER_MS = 60 * 1000;\n const createdAt = new Date(metadata.createdAt).getTime();\n if (Number.isNaN(createdAt)) {\n throw new BackupRestoreError({\n message: `Backup metadata has invalid createdAt timestamp: ${metadata.createdAt}`,\n code: ErrorCode.BACKUP_RESTORE_FAILED,\n httpStatus: 500,\n context: { dir, backupId: id },\n timestamp: new Date().toISOString()\n });\n }\n const expiresAt = createdAt + metadata.ttl * 1000;\n if (Date.now() + TTL_BUFFER_MS > expiresAt) {\n throw new BackupExpiredError({\n message:\n `Backup ${id} has expired ` +\n `(created: ${metadata.createdAt}, TTL: ${metadata.ttl}s). ` +\n 'Create a new backup.',\n code: ErrorCode.BACKUP_EXPIRED,\n httpStatus: 400,\n context: {\n backupId: id,\n expiredAt: new Date(expiresAt).toISOString()\n },\n timestamp: new Date().toISOString()\n });\n }\n\n // Step 2: Download archive from R2 via binding and write to container\n const r2Key = `${BACKUP_STORAGE_PREFIX}/${id}/${BACKUP_ARCHIVE_OBJECT_NAME}`;\n const archiveObject = await bucket.get(r2Key);\n if (!archiveObject) {\n throw new BackupNotFoundError({\n message:\n `Backup archive not found in R2: ${id}. ` +\n 'The archive may have been deleted by R2 lifecycle rules.',\n code: ErrorCode.BACKUP_NOT_FOUND,\n httpStatus: 404,\n context: { backupId: id },\n timestamp: new Date().toISOString()\n });\n }\n\n backupSession = await this.ensureBackupSession();\n const archivePath = `${BACKUP_CONTAINER_DIR}/${id}.sqsh`;\n\n const archiveBuffer = await archiveObject.arrayBuffer();\n const base64Content = Buffer.from(archiveBuffer).toString('base64');\n\n // Ensure backup directory exists\n await this.execWithSession(\n `mkdir -p ${BACKUP_CONTAINER_DIR}`,\n backupSession,\n {\n origin: 'internal'\n }\n );\n\n const writeResult = await this.client.files.writeFile(\n archivePath,\n base64Content,\n backupSession,\n { encoding: 'base64' }\n );\n\n if (!writeResult.success) {\n const writeErrorMessage =\n 'error' in writeResult &&\n typeof writeResult.error === 'object' &&\n writeResult.error !== null &&\n 'message' in writeResult.error &&\n typeof writeResult.error.message === 'string'\n ? writeResult.error.message\n : `File write returned success: false for '${archivePath}'`;\n\n throw new BackupRestoreError({\n message: `Failed to write backup archive to ${archivePath}: ${writeErrorMessage}`,\n code: ErrorCode.BACKUP_RESTORE_FAILED,\n httpStatus: 500,\n context: { dir, backupId: id },\n timestamp: new Date().toISOString()\n });\n }\n\n // Step 3: Extract archive using unsquashfs (no FUSE needed)\n const extractResult = await this.execWithSession(\n `/usr/bin/unsquashfs -f -d ${shellEscape(dir)} ${shellEscape(archivePath)}`,\n backupSession,\n { origin: 'internal' }\n );\n\n if (extractResult.exitCode !== 0) {\n throw new BackupRestoreError({\n message: `unsquashfs extraction failed (exit code ${extractResult.exitCode}): ${extractResult.stderr}`,\n code: ErrorCode.BACKUP_RESTORE_FAILED,\n httpStatus: 500,\n context: { dir, backupId: id },\n timestamp: new Date().toISOString()\n });\n }\n\n // Clean up archive after extraction (no FUSE mount holds it open)\n await this.execWithSession(\n `rm -f ${shellEscape(archivePath)}`,\n backupSession,\n { origin: 'internal' }\n ).catch(() => {});\n\n outcome = 'success';\n\n return {\n success: true,\n dir,\n id\n };\n } catch (error) {\n caughtError = error instanceof Error ? error : new Error(String(error));\n if (id && backupSession) {\n const archivePath = `${BACKUP_CONTAINER_DIR}/${id}.sqsh`;\n await this.execWithSession(\n `rm -f ${shellEscape(archivePath)}`,\n backupSession,\n { origin: 'internal' }\n ).catch(() => {});\n }\n throw error;\n } finally {\n if (backupSession) {\n await this.client.utils.deleteSession(backupSession).catch(() => {});\n }\n logCanonicalEvent(this.logger, {\n event: 'backup.restore',\n outcome,\n durationMs: Date.now() - restoreStartTime,\n backupId: id,\n dir,\n provider: 'local-binding',\n error: caughtError\n });\n }\n }\n}\n"],"mappings":"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"}