@cloudflare/sandbox 0.13.0-next.681.1 → 0.13.0-next.709.1
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.
- package/Dockerfile +6 -6
- package/README.md +49 -3
- package/dist/bridge/index.d.ts.map +1 -1
- package/dist/bridge/index.js +1364 -1072
- package/dist/bridge/index.js.map +1 -1
- package/dist/{contexts-DY1LHU1v.d.ts → contexts-1EsLHByO.d.ts} +156 -20
- package/dist/contexts-1EsLHByO.d.ts.map +1 -0
- package/dist/{dist-BStBkGIC.js → dist-Duor5GbS.js} +7 -56
- package/dist/dist-Duor5GbS.js.map +1 -0
- package/dist/errors/index.d.ts +4 -4
- package/dist/errors/index.js +3 -3
- package/dist/{errors-k3B8orjH.js → errors-CXR0xBpw.js} +42 -10
- package/dist/errors-CXR0xBpw.js.map +1 -0
- package/dist/{errors-ewgSNicb.js → errors-QYlSkVGz.js} +402 -250
- package/dist/errors-QYlSkVGz.js.map +1 -0
- package/dist/extensions/index.d.ts +4 -2
- package/dist/extensions/index.js +5 -3
- package/dist/extensions-CFB2xHqY.js +1023 -0
- package/dist/extensions-CFB2xHqY.js.map +1 -0
- package/dist/filesystem-BWAZCZER.d.ts +732 -0
- package/dist/filesystem-BWAZCZER.d.ts.map +1 -0
- package/dist/git/index.d.ts +8 -19
- package/dist/git/index.d.ts.map +1 -1
- package/dist/git/index.js +16 -23
- package/dist/git/index.js.map +1 -1
- package/dist/index-Bs4bqXDR.d.ts +438 -0
- package/dist/index-Bs4bqXDR.d.ts.map +1 -0
- package/dist/{index-Dy6u9r60.d.ts → index-HNYBk-az.d.ts} +233 -179
- package/dist/index-HNYBk-az.d.ts.map +1 -0
- package/dist/index.d.ts +480 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -6
- package/dist/interpreter/index.d.ts +3 -1
- package/dist/interpreter/index.d.ts.map +1 -1
- package/dist/interpreter/index.js +46 -18
- package/dist/interpreter/index.js.map +1 -1
- package/dist/openai/index.d.ts +5 -5
- package/dist/openai/index.d.ts.map +1 -1
- package/dist/openai/index.js +11 -6
- package/dist/openai/index.js.map +1 -1
- package/dist/opencode/index.d.ts +7 -11
- package/dist/opencode/index.d.ts.map +1 -1
- package/dist/opencode/index.js +56 -85
- package/dist/opencode/index.js.map +1 -1
- package/dist/process-types-GStiZ8f8.d.ts +73 -0
- package/dist/process-types-GStiZ8f8.d.ts.map +1 -0
- package/dist/{sandbox-boKWPIcd.js → sandbox-Auuwfnur.js} +4152 -3359
- package/dist/sandbox-Auuwfnur.js.map +1 -0
- package/dist/sandbox-BbAabq93.d.ts +42 -0
- package/dist/sandbox-BbAabq93.d.ts.map +1 -0
- package/dist/xterm/index.d.ts +5 -1
- package/dist/xterm/index.d.ts.map +1 -1
- package/dist/xterm/index.js +55 -12
- package/dist/xterm/index.js.map +1 -1
- package/package.json +3 -2
- package/dist/codes-y-U76vnQ.d.ts +0 -79
- package/dist/codes-y-U76vnQ.d.ts.map +0 -1
- package/dist/contexts-DY1LHU1v.d.ts.map +0 -1
- package/dist/dist-BStBkGIC.js.map +0 -1
- package/dist/errors-ewgSNicb.js.map +0 -1
- package/dist/errors-k3B8orjH.js.map +0 -1
- package/dist/extensions-CepYdzro.js +0 -191
- package/dist/extensions-CepYdzro.js.map +0 -1
- package/dist/index-B7QgIs0N.d.ts +0 -1841
- package/dist/index-B7QgIs0N.d.ts.map +0 -1
- package/dist/index-Dy6u9r60.d.ts.map +0 -1
- package/dist/sandbox-DHNO89IF.d.ts +0 -828
- package/dist/sandbox-DHNO89IF.d.ts.map +0 -1
- package/dist/sandbox-boKWPIcd.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sandbox-boKWPIcd.js","names":["currentEvent: SSEPartialEvent","metadata: FileMetadata | null","chunks: Array<string | Uint8Array>","createOptions: TerminalCreateOptions","code?: string","sanitizedSandboxId: string","currentRuntime: CurrentRuntimeIdentity","currentLifetime: CurrentSandboxLifetime","release!: () => void","gitCredentialProxyHandler: OutboundHandler<\n Cloudflare.Env,\n GitAuthInterceptorParams\n>","env","parts: UploadedPart[]","meta: R2HTTPMetadata","listOpts: R2ListOptions","result","part: R2UploadedPart","r2Parts: R2UploadedPart[]","r2EgressHandler: OutboundHandler<\n Cloudflare.Env,\n R2EgressParams\n>","credentialProxyDiagnosticEvents: CredentialProxyDiagnosticEvent[]","env","headers: [string, string][]","headerEntries: [string, string][]","s3CredentialProxyHandler: OutboundHandler<\n Cloudflare.Env,\n S3CredentialProxyParams\n>","mountId: string | null","realPath: string","requestInfo: CredentialProxyRequestInfo","ContainerProxy","BaseContainerProxy","hostOverrides: Record<\n string,\n { method: string; params: GitAuthInterceptorParams }\n >","hostOverrides: Record<\n string,\n { method: string; params: S3CredentialProxyParams }\n >","S3_CREDENTIAL_PROXY_SELF_TEST_PATH","R2_DEFAULT_S3FS_OPTIONS: Readonly<\n Record<string, string | boolean>\n>","result: Record<string, string | boolean>","s3fsOptions: Record<string, string | boolean>","buckets: R2EgressParams['buckets']","params: S3CredentialProxyParams['mounts']","cleanedMountPaths: string[]","cleanedMountIds: string[]","currentEvent: SSEPartialEvent","changed: Array<{ key: string; action: 'created' | 'modified' }>","results: Array<{ key: string; etag: string; size: number }>","cursor: string | undefined","mountOutcome: 'success' | 'error'","mountError: Error | undefined","mountInfo: LocalSyncMountInfo","mountOutcome: 'success' | 'error'","mountError: Error | undefined","passwordFilePath: string | undefined","additionalHeaderFilePath: string | undefined","mountInfo: R2BindingMountInfo | undefined","mountOutcome: 'success' | 'error'","mountError: Error | undefined","passwordFilePath: string | undefined","additionalHeaderFilePath: string | undefined","provider: BucketProvider | null","credentialProxyMountId: string | undefined","mountInfo: FuseMountInfo | undefined","effectiveOptions: RemoteMountBucketOptions","unmountOutcome: 'success' | 'error'","unmountError: Error | undefined","deps: BucketMountServiceDeps","normalizedExcludes: string[]","deps: BackupCreatorDeps","backupId: string | undefined","sizeBytes: number | undefined","outcome: 'success' | 'error'","caughtError: Error | undefined","backupSession: string | undefined","storage: DurableObjectStorage","getEnv: () => unknown","storage: DurableObjectState['storage']","getContainerState: () => Promise<{ status: string }>","isContainerRunning: () => boolean","record: RuntimeIdentityRecord","record: SandboxLifetimeRecord","storage: SandboxLifetimeStorage","storage: OperationRecordStorage","deps: RestoreLifecycleDeps","operation: BackupRestoreOperationRecord","runtime: RuntimeIdentity | undefined","context: RestoreLifecycleContext","deps: BackupTransferDeps","parsed: URL","missing: string[]","uploadResult: Awaited<\n ReturnType<typeof this.client.backup.uploadParts>\n >","deps: BackupServiceDeps","outcome: 'success' | 'error'","caughtError: Error | undefined","backupSession: string | undefined","#ws","#error","#receiveResolver","#receiveRejecter","#receiveQueue","#fail","#sendQueue","context","payload: RPCErrorPayload | undefined","kind: RPCTransportErrorKind","closeCode: number | undefined","closeReason: string | undefined","context: RPCTransportContext","interruptedContext: OperationInterruptedContext","normalized: Record<string, PortTokenEntry>","deps: PreviewServiceDeps","outcome: 'success' | 'error'","caughtError: Error | undefined","activePorts: CurrentPreviewPort[]","runtime","controllers: {\n stdout: ReadableStreamDefaultController<Uint8Array> | null;\n stderr: ReadableStreamDefaultController<Uint8Array> | null;\n }","exitResolve!: (code: number) => void","exitReject!: (err: Error) => void","resolve!: (code: number) => void","reject!: (err: Error) => void","reader: ReadableStreamDefaultReader<Uint8Array> | null","output: SandboxProcessPromise['output']","SIGNAL_NUMBERS: Record<string, number>","chunks: Uint8Array[]","output: SandboxProcess['output']","env","body: TokenVerifyResponse","listBody: AccountsListResponse","verifyBody: AccountTokenVerifyResponse","body: ZonesListResponse","env","#getEnv","#fetcher","#env","#getAccountId","#getZoneId","#accountIdPromise","#zoneIdPromise","init: RequestInit","response: Response","envelope: CloudflareResponse<T>","state: TunnelPortState","writes: Array<Promise<void>>","config: { token: string; accountId: string; zoneId: string }","#host","#captureRuntime","#host","#ensureTunnelRun","#getZoneName","tunnelId: string","tunnelToken: string","info: NamedTunnelInfo","#zoneNamePromise","nextMap: TunnelMap","nextMeta: TunnelMetaMap","#host","#portLocks","#lifecycle","#provisioner","outcome: 'success' | 'error'","cacheState: TunnelGetCacheState","caughtError: Error | undefined","recovery: TunnelGetRecoveryState","#withPortLock","#getLocked","#assertSameOptions","#provisionNamedTunnel","#provisionQuickTunnel","#resumePortCleanup","#updatePortCleanup","#requireCleanupEntry","tunnelId: string | undefined","RpcTarget","service: TunnelService","getHandler: () => TunnelExitHandler | null","logger: Logger","configuration: SandboxConfiguration","operations: Promise<void>[]","context: OperationInterruptedContext","options","id","path","env","outcome: 'success' | 'error'","caughtError: Error | undefined","containerVersion: string | undefined","outcome: string","errorBody: ErrorResponse","errorBody","request: Request","port: number | undefined","procDto: SandboxProcess","execOutcome: { exitCode: number; success: boolean } | undefined","execError: Error | undefined","commandOptions: CommandExecuteOptions | undefined","timeoutId: ReturnType<typeof setTimeout> | undefined","timeoutPromise: Promise<never> | undefined"],"sources":["../src/file-stream.ts","../src/platform-errors.ts","../src/pty/proxy.ts","../src/security.ts","../src/preview/protocol.ts","../src/preview/url.ts","../src/preview/route.ts","../src/storage-mount/errors.ts","../src/storage-mount/lifecycle.ts","../src/storage-mount/operation-queue.ts","../src/storage-mount/outbound/git-credential-proxy-handler.ts","../src/storage-mount/validation/index.ts","../src/storage-mount/outbound/r2-egress-handler.ts","../src/storage-mount/outbound/s3-credential-proxy-handler.ts","../src/storage-mount/outbound/container-proxy.ts","../src/storage-mount/outbound/index.ts","../src/storage-mount/registry.ts","../src/storage-mount/validation/provider.ts","../src/storage-mount/s3fs/support-files.ts","../src/storage-mount/s3fs/index.ts","../src/storage-mount/outbound/params.ts","../src/storage-mount/lifecycle-cleanup.ts","../src/sse-parser.ts","../src/local-mount-sync.ts","../src/storage-mount/operations/local-sync-mount.ts","../src/storage-mount/operations/fuse-cleanup.ts","../src/storage-mount/operations/r2-egress-mount.ts","../src/storage-mount/validation/credentials.ts","../src/storage-mount/operations/remote-fuse-mount.ts","../src/storage-mount/operations/unmount.ts","../src/storage-mount/service.ts","../src/backup/constants.ts","../../shared/src/backup.ts","../src/backup/validation.ts","../src/backup/create.ts","../src/backup/restore-fault-injection.ts","../src/current-runtime-identity.ts","../src/sandbox-lifetime.ts","../src/backup/restore-operation-store.ts","../src/backup/restore-lifecycle.ts","../src/backup/transfer.ts","../src/backup/backup-service.ts","../src/response-retry.ts","../src/container-control/connection.ts","../src/container-control/client.ts","../src/preview/forwarding.ts","../src/preview/proxy-request.ts","../src/preview/state.ts","../src/preview/token.ts","../src/preview/service.ts","../src/process.ts","../src/tunnels/credentials.ts","../src/tunnels/named-tunnel-config.ts","../src/tunnels/cloudflare-api.ts","../src/tunnels/storage.ts","../src/tunnels/cleanup.ts","../src/tunnels/lifecycle.ts","../src/tunnels/random-id.ts","../src/tunnels/provisioner.ts","../src/tunnels/restart.ts","../src/tunnels/tunnel-service.ts","../src/tunnels/rpc-target.ts","../src/tunnels/sandbox-control-callback.ts","../src/version.ts","../src/sandbox.ts"],"sourcesContent":["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","const SUPERSEDED_ISOLATE_PATTERN =\n /reset because its code was updated|this script has been upgraded/i;\nconst CONNECTION_LOST_PATTERN = /network connection lost/i;\nconst DO_STORAGE_STARTUP_RESET_PATTERN =\n /internal error while starting up durable object storage caused object to be reset/i;\n\nfunction errorMessageOf(error: unknown): string {\n return error instanceof Error\n ? error.message\n : typeof error === 'string'\n ? error\n : '';\n}\n\nfunction* selfAndCauses(error: unknown): Generator<unknown> {\n let current = error;\n for (let depth = 0; depth < 8 && current != null; depth += 1) {\n yield current;\n current =\n typeof current === 'object'\n ? (current as { cause?: unknown }).cause\n : undefined;\n }\n}\n\nfunction isErrorRetryable(error: unknown): boolean {\n if (typeof error !== 'object' || error === null) return false;\n const message = String(error);\n const typed = error as { retryable?: boolean; overloaded?: boolean };\n return (\n typed.retryable === true &&\n typed.overloaded !== true &&\n !message.includes('Durable Object is overloaded')\n );\n}\n\n/**\n * Whether an error was raised because the current Durable Object isolate was\n * superseded by a deploy/code update. In-process retries are futile for this\n * class because the invocation keeps running on the old isolate; callers\n * should return control to the platform or retry from a fresh request.\n */\nexport function isDurableObjectCodeUpdateReset(error: unknown): boolean {\n for (const candidate of selfAndCauses(error)) {\n if (SUPERSEDED_ISOLATE_PATTERN.test(errorMessageOf(candidate))) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Whether an error represents a transient platform lifecycle/storage failure,\n * not an application-level sandbox failure.\n */\nexport function isPlatformTransientError(error: unknown): boolean {\n for (const candidate of selfAndCauses(error)) {\n const message = errorMessageOf(candidate);\n if (SUPERSEDED_ISOLATE_PATTERN.test(message)) return true;\n if (CONNECTION_LOST_PATTERN.test(message)) return true;\n if (DO_STORAGE_STARTUP_RESET_PATTERN.test(message)) return true;\n if (isErrorRetryable(candidate)) return true;\n }\n return false;\n}\n","import { switchPort } from '@cloudflare/containers';\nimport type {\n SandboxTerminal,\n TerminalConnectOptions,\n TerminalCreateOptions,\n TerminalOptions\n} from '@repo/shared';\n\ninterface SandboxTerminalStub {\n fetch(request: Request): Promise<Response>;\n createTerminal(options: TerminalCreateOptions): Promise<unknown>;\n destroyTerminal(id: string): Promise<void>;\n}\n\nfunction resolveTerminalId(options?: TerminalOptions): string {\n if (options?.id !== undefined) {\n if (typeof options.id !== 'string' || options.id.length === 0) {\n throw new Error('terminal id must be a non-empty string');\n }\n\n return options.id;\n }\n\n return `terminal-${crypto.randomUUID()}`;\n}\n\nexport function createSandboxTerminal(\n stub: SandboxTerminalStub,\n options?: TerminalOptions\n): SandboxTerminal {\n const id = resolveTerminalId(options);\n\n return {\n id,\n connect: (request, connectOptions) =>\n proxyTerminal(stub, id, request, options, connectOptions),\n destroy: () => stub.destroyTerminal(id)\n };\n}\n\nexport async function proxyTerminal(\n stub: SandboxTerminalStub,\n terminalId: string,\n request: Request,\n options?: TerminalOptions,\n connectOptions?: TerminalConnectOptions\n): Promise<Response> {\n const upgradeHeader = request.headers.get('Upgrade');\n if (upgradeHeader?.toLowerCase() !== 'websocket') {\n throw new Error('terminal.connect() requires a WebSocket upgrade request');\n }\n\n const createOptions: TerminalCreateOptions = { id: terminalId };\n if (options?.cwd !== undefined) createOptions.cwd = options.cwd;\n if (options?.shell !== undefined) createOptions.shell = options.shell;\n if (connectOptions?.cols !== undefined)\n createOptions.cols = connectOptions.cols;\n if (connectOptions?.rows !== undefined)\n createOptions.rows = connectOptions.rows;\n await stub.createTerminal(createOptions);\n\n const params = new URLSearchParams({ terminalId });\n if (connectOptions?.cols) params.set('cols', String(connectOptions.cols));\n if (connectOptions?.rows) params.set('rows', String(connectOptions.rows));\n\n const terminalURL = `http://localhost/ws/terminal?${params}`;\n const terminalRequest = new Request(terminalURL, request);\n\n return stub.fetch(switchPort(terminalRequest, 3000));\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\n/**\n * Validates a single DNS label for use as a Cloudflare Tunnel hostname.\n *\n * Used by `sandbox.tunnels.get(port, { name })` to reject obviously-bad\n * input client-side before any network call. Whether the chosen label is\n * actually available under the configured zone is left to the Cloudflare\n * API (returned as a typed error).\n *\n * Rules:\n * - 1–63 characters\n * - Lowercase letters, digits, and internal hyphens only\n * - No leading or trailing hyphen\n * - No dots — multi-label hostnames need a delegated subdomain zone or\n * Advanced Certificate Manager, which are out of scope for this\n * feature. Universal SSL only covers `<label>.<zone>`.\n *\n * Throws `SandboxSecurityError` on any violation. Designed to be called\n * before any other tunnel work so callers see a fast, deterministic\n * failure.\n */\nconst TUNNEL_NAME_REGEX = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;\n\nexport function validateTunnelName(name: string): void {\n if (typeof name !== 'string') {\n throw new SandboxSecurityError(\n `Tunnel name must be a string. Received: ${typeof name}`,\n 'INVALID_TUNNEL_NAME'\n );\n }\n if (name.length === 0 || name.length > 63) {\n throw new SandboxSecurityError(\n `Tunnel name '${name}' must be 1–63 characters long.`,\n 'INVALID_TUNNEL_NAME_LENGTH'\n );\n }\n if (!TUNNEL_NAME_REGEX.test(name)) {\n throw new SandboxSecurityError(\n `Tunnel name '${name}' is not a valid DNS label. Use lowercase ` +\n 'letters, digits, and internal hyphens only (no dots, no ' +\n 'leading/trailing hyphens).',\n 'INVALID_TUNNEL_NAME_FORMAT'\n );\n }\n}\n","import { validatePort } from '../security';\n\n/** @internal */\nexport const PREVIEW_PROXY_HEADER = 'x-sandbox-preview-proxy';\n/** @internal */\nexport const PREVIEW_PROXY_PORT_HEADER = 'x-sandbox-preview-port';\n/** @internal */\nexport const PREVIEW_PROXY_TOKEN_HEADER = 'x-sandbox-preview-token';\n/** @internal */\nexport const PREVIEW_PROXY_SANDBOX_ID_HEADER = 'x-sandbox-preview-sandbox-id';\n\n/** @internal */\nexport const PREVIEW_PROXY_HEADERS = [\n PREVIEW_PROXY_HEADER,\n PREVIEW_PROXY_PORT_HEADER,\n PREVIEW_PROXY_TOKEN_HEADER,\n PREVIEW_PROXY_SANDBOX_ID_HEADER\n] as const;\n\nexport interface PreviewProxyMetadata {\n port: number;\n token: string;\n sandboxId: string;\n}\n\nexport function isPreviewProxyRequest(request: Request): boolean {\n return request.headers.get(PREVIEW_PROXY_HEADER) === '1';\n}\n\nexport function readPreviewProxyMetadata(\n request: Request\n): PreviewProxyMetadata | null {\n if (!isPreviewProxyRequest(request)) {\n return null;\n }\n\n const portValue = request.headers.get(PREVIEW_PROXY_PORT_HEADER);\n const token = request.headers.get(PREVIEW_PROXY_TOKEN_HEADER);\n const sandboxId = request.headers.get(PREVIEW_PROXY_SANDBOX_ID_HEADER);\n const port = portValue === null ? Number.NaN : Number.parseInt(portValue, 10);\n\n if (!Number.isFinite(port) || !validatePort(port) || !token || !sandboxId) {\n return null;\n }\n\n return { port, token, sandboxId };\n}\n\nexport function withPreviewProxyMetadata(\n request: Request,\n { port, token, sandboxId }: PreviewProxyMetadata\n): Request {\n const headers = new Headers(request.headers);\n for (const header of PREVIEW_PROXY_HEADERS) {\n headers.delete(header);\n }\n headers.set(PREVIEW_PROXY_HEADER, '1');\n headers.set(PREVIEW_PROXY_PORT_HEADER, port.toString());\n headers.set(PREVIEW_PROXY_TOKEN_HEADER, token);\n headers.set(PREVIEW_PROXY_SANDBOX_ID_HEADER, sandboxId);\n\n return new Request(request, { headers });\n}\n","export 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 }\n\n // [::1] format without port\n return hostname === '[::1]';\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","import {\n SandboxSecurityError,\n sanitizeSandboxId,\n validatePort\n} from '../security';\nimport { isLocalhostPattern } from './url';\n\nexport interface PreviewRouteInfo {\n port: number;\n sandboxId: string;\n token: string;\n}\n\nexport interface ConstructPreviewURLOptions {\n port: number;\n sandboxId: string;\n effectiveId: string;\n hostname: string;\n token: string;\n normalizeId: boolean;\n}\n\nexport function parsePreviewRoute(url: URL): PreviewRouteInfo | null {\n // URL format: {port}-{sandboxId}-{token}.{domain}\n // Tokens are [a-z0-9_]+, so split at the last hyphen to handle sandbox IDs\n // with hyphens such as 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\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 = Number.parseInt(portStr, 10);\n if (!validatePort(port)) {\n return null;\n }\n\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 if (!/^[a-z0-9_]+$/.test(token) || token.length === 0 || token.length > 63) {\n return null;\n }\n\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 token\n };\n}\n\nexport function constructPreviewURL({\n port,\n sandboxId,\n effectiveId,\n hostname,\n token,\n normalizeId\n}: ConstructPreviewURLOptions): 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 const hasUppercase = /[A-Z]/.test(effectiveId);\n if (!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 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 * 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 {\n CurrentRuntimeIdentity,\n RuntimeIdentity\n} from '../current-runtime-identity';\nimport type {\n CurrentSandboxLifetime,\n SandboxLifetime\n} from '../sandbox-lifetime';\n\nexport type MountLifecycleSnapshot = {\n runtime: RuntimeIdentity | null;\n lifetime: SandboxLifetime;\n};\n\nexport class MountLifecycle {\n constructor(\n private readonly currentRuntime: CurrentRuntimeIdentity,\n private readonly currentLifetime: CurrentSandboxLifetime\n ) {}\n\n async capture(): Promise<MountLifecycleSnapshot> {\n return {\n runtime: await this.currentRuntime.get(),\n lifetime: await this.currentLifetime.getOrCreate()\n };\n }\n\n async assertCurrent(snapshot: MountLifecycleSnapshot): Promise<void> {\n if (snapshot.runtime) {\n await this.currentRuntime.assertActive(snapshot.runtime);\n }\n await this.currentLifetime.assertCurrent(snapshot.lifetime);\n }\n}\n","export class MountOperationQueue {\n private tail: Promise<void> = Promise.resolve();\n\n async run<T>(operation: () => Promise<T>): Promise<T> {\n const previous = this.tail;\n let release!: () => void;\n this.tail = new Promise<void>((resolve) => {\n release = resolve;\n });\n\n await previous.catch(() => {});\n try {\n return await operation();\n } finally {\n release();\n }\n }\n}\n","import type {\n OutboundHandler,\n OutboundHandlerContext\n} from '@cloudflare/containers';\nimport type {\n HTTPAuthHostConfig,\n HTTPAuthInterceptorParams\n} from '../../extensions/index.js';\n\n// Types re-declared here so the core outbound-handler stack stays independent\n// of any specific extension. The git extension package uses matching aliases\n// (`GitHostAuth`, `GitAuthInterceptorParams`) but structurally these are just\n// the generic HTTP-auth-interceptor params from the extension framework.\ntype GitHostAuth = HTTPAuthHostConfig;\ntype GitAuthInterceptorParams = HTTPAuthInterceptorParams;\n\nfunction encodeBasicAuth(username: string, token: string): string {\n return btoa(`${username}:${token}`);\n}\n\nfunction authorizationHeader(auth: GitHostAuth): string {\n if (auth.type === 'bearer') {\n return `Bearer ${auth.token}`;\n }\n return `Basic ${encodeBasicAuth(auth.username ?? 'x-access-token', auth.token)}`;\n}\n\nexport const gitCredentialProxyHandler: OutboundHandler<\n Cloudflare.Env,\n GitAuthInterceptorParams\n> = async (\n request: Request,\n _env: Cloudflare.Env,\n ctx: OutboundHandlerContext<GitAuthInterceptorParams>\n): Promise<Response> => {\n const url = new URL(request.url);\n const auth = ctx.params?.hosts[url.hostname];\n if (!auth) {\n return fetch(request);\n }\n\n const headers = new Headers(request.headers);\n if (!headers.has('authorization')) {\n headers.set('authorization', authorizationHeader(auth));\n }\n\n return fetch(new Request(request, { headers }));\n};\n","import type { RemoteMountBucketOptions } from '@repo/shared';\nimport { InvalidMountConfigError } from '../errors';\nimport type { MountInfo } from '../types';\n\n/**\n * Type guard for R2Bucket binding.\n * Checks for the minimal R2Bucket interface methods we use.\n */\nexport function 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 'list' in value &&\n typeof (value as Record<string, unknown>).list === 'function'\n );\n}\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\nexport function validateMountPath(\n activeMounts: Map<string, MountInfo>,\n mountPath: string\n): void {\n if (!mountPath.startsWith('/')) {\n throw new InvalidMountConfigError(\n `Mount path must be absolute (start with /): \"${mountPath}\"`\n );\n }\n\n if (activeMounts.has(mountPath)) {\n const existingMount = 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\nexport function validateRemoteMountOptions(\n activeMounts: Map<string, MountInfo>,\n bucket: string,\n mountPath: string,\n options: RemoteMountBucketOptions\n): void {\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 validateMountPath(activeMounts, mountPath);\n}\n\nexport function validateBucketBindingName(\n bucketBinding: string,\n mountPath: string\n): void {\n if (bucketBinding.includes(':')) {\n const [bucketName, prefixPart] = bucketBinding.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 // Worker binding names follow JavaScript identifier syntax.\n const bindingNameRegex = /^[A-Za-z_][A-Za-z0-9_]*$/;\n if (!bindingNameRegex.test(bucketBinding)) {\n throw new InvalidMountConfigError(\n `Invalid R2 binding name: \"${bucketBinding}\". Binding names must start with a letter or underscore and contain only letters, numbers, or underscores.`\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","/**\n * Egress handler that translates S3 API requests from s3fs into R2 binding calls.\n *\n * When s3fs inside the container makes requests to http://r2.internal/<bucket>/...,\n * the Cloudflare egress interception layer routes them here. The bucket name is read\n * from the first path segment, resolved to a Worker R2 binding, and the request is\n * executed via the R2 binding API — no S3 credentials ever touch the container.\n *\n * S3 path-style layout: /bucketName/objectKey\n * Query params drive operation selection (list-type=2, uploads, uploadId, location).\n */\n\nimport type {\n OutboundHandler,\n OutboundHandlerContext\n} from '@cloudflare/containers';\nimport { isR2Bucket } from '../validation';\n\n// ---------------------------------------------------------------------------\n// Per-mount bucket params\n//\n// Serialized into ContainerProxy props and transmitted with every intercepted\n// request so each Worker isolate receives the active mount configuration.\n// ---------------------------------------------------------------------------\n\nexport type R2EgressParams = {\n buckets: Record<string, { prefix?: string; readOnly?: boolean }>;\n};\n\n// ---------------------------------------------------------------------------\n// XML helpers\n// ---------------------------------------------------------------------------\n\nconst XML_NS = 'xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"';\nconst XML_DECL = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n';\n\nfunction escapeXML(s: string): string {\n return s\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n}\n\nfunction xmlResponse(body: string, status = 200): Response {\n return new Response(XML_DECL + body, {\n status,\n headers: { 'Content-Type': 'application/xml' }\n });\n}\n\n// ---------------------------------------------------------------------------\n// Path / query parsing\n// ---------------------------------------------------------------------------\n\ninterface ParsedPath {\n bucket: string;\n key: string;\n}\n\nfunction normalizeObjectKey(value: string): string {\n return value.replace(/^\\/+/, '');\n}\n\nfunction trimTrailingSlashes(s: string): string {\n let end = s.length;\n while (end > 0 && s[end - 1] === '/') end--;\n return s.slice(0, end);\n}\n\nfunction parsePath(pathname: string): ParsedPath | null {\n const stripped = pathname.startsWith('/') ? pathname.slice(1) : pathname;\n if (!stripped) return null;\n const slash = stripped.indexOf('/');\n if (slash === -1) return { bucket: stripped, key: '' };\n return {\n bucket: stripped.slice(0, slash),\n key: normalizeObjectKey(stripped.slice(slash + 1))\n };\n}\n\n// ---------------------------------------------------------------------------\n// R2 binding resolution\n// ---------------------------------------------------------------------------\n\nfunction resolveR2Bucket(env: unknown, name: string): R2Bucket | null {\n if (typeof env !== 'object' || env === null) return null;\n const val = (env as Record<string, unknown>)[name];\n return isR2Bucket(val) ? val : null;\n}\n\n// ---------------------------------------------------------------------------\n// Range header parsing\n// ---------------------------------------------------------------------------\n\nfunction parseRange(header: string | null): R2Range | undefined {\n if (!header) return undefined;\n const m = header.match(/^bytes=(\\d*)-(\\d*)$/);\n if (!m) return undefined;\n const start = m[1] ? parseInt(m[1], 10) : undefined;\n const end = m[2] ? parseInt(m[2], 10) : undefined;\n\n if (start === undefined && end !== undefined) {\n return { suffix: end };\n }\n if (start !== undefined && end !== undefined) {\n return { offset: start, length: end - start + 1 };\n }\n if (start !== undefined) {\n return { offset: start };\n }\n return undefined;\n}\n\n// ---------------------------------------------------------------------------\n// S3 XML response builders\n// ---------------------------------------------------------------------------\n\ninterface ListDisplayObject {\n key: string;\n uploaded: Date;\n httpEtag: string;\n size: number;\n}\n\ninterface ListDisplayResult {\n objects: ListDisplayObject[];\n delimitedPrefixes: string[];\n truncated: boolean;\n cursor?: string;\n}\n\nfunction buildListObjectsV2Xml(\n bucketName: string,\n prefix: string,\n delimiter: string,\n maxKeys: number,\n result: ListDisplayResult\n): string {\n const contents = result.objects\n .map(\n (obj) =>\n `<Contents><Key>${escapeXML(obj.key)}</Key><LastModified>${obj.uploaded.toISOString()}</LastModified><ETag>${escapeXML(obj.httpEtag)}</ETag><Size>${obj.size}</Size><StorageClass>STANDARD</StorageClass></Contents>`\n )\n .join('');\n\n const commonPrefixes = result.delimitedPrefixes\n .map(\n (p) => `<CommonPrefixes><Prefix>${escapeXML(p)}</Prefix></CommonPrefixes>`\n )\n .join('');\n\n const nextToken =\n result.truncated && result.cursor\n ? `<NextContinuationToken>${escapeXML(result.cursor)}</NextContinuationToken>`\n : '';\n\n const keyCount = result.objects.length + result.delimitedPrefixes.length;\n\n return (\n `<ListBucketResult ${XML_NS}>` +\n `<Name>${escapeXML(bucketName)}</Name>` +\n `<Prefix>${escapeXML(prefix)}</Prefix>` +\n `<KeyCount>${keyCount}</KeyCount>` +\n `<MaxKeys>${maxKeys}</MaxKeys>` +\n (delimiter ? `<Delimiter>${escapeXML(delimiter)}</Delimiter>` : '') +\n `<IsTruncated>${result.truncated}</IsTruncated>` +\n nextToken +\n contents +\n commonPrefixes +\n `</ListBucketResult>`\n );\n}\n\nfunction buildLocationXml(): string {\n return `<LocationConstraint ${XML_NS}/>`;\n}\n\nfunction buildInitiateMultipartUploadXml(\n bucketName: string,\n key: string,\n uploadId: string\n): string {\n return (\n `<InitiateMultipartUploadResult ${XML_NS}>` +\n `<Bucket>${escapeXML(bucketName)}</Bucket>` +\n `<Key>${escapeXML(key)}</Key>` +\n `<UploadId>${escapeXML(uploadId)}</UploadId>` +\n `</InitiateMultipartUploadResult>`\n );\n}\n\nfunction buildCompleteMultipartUploadXml(\n bucketName: string,\n key: string,\n etag: string\n): string {\n return (\n `<CompleteMultipartUploadResult ${XML_NS}>` +\n `<Location>http://r2.internal/${escapeXML(bucketName)}/${escapeXML(key)}</Location>` +\n `<Bucket>${escapeXML(bucketName)}</Bucket>` +\n `<Key>${escapeXML(key)}</Key>` +\n `<ETag>${escapeXML(etag)}</ETag>` +\n `</CompleteMultipartUploadResult>`\n );\n}\n\nfunction buildCopyObjectXml(etag: string, uploaded: Date): string {\n return (\n `<CopyObjectResult ${XML_NS}>` +\n `<LastModified>${uploaded.toISOString()}</LastModified>` +\n `<ETag>${escapeXML(etag)}</ETag>` +\n `</CopyObjectResult>`\n );\n}\n\n// ---------------------------------------------------------------------------\n// CompleteMultipartUpload XML body parser\n// ---------------------------------------------------------------------------\n\ninterface UploadedPart {\n partNumber: number;\n etag: string;\n}\n\nfunction extractXmlTagContent(segment: string, tagName: string): string | null {\n const openTag = `<${tagName}>`;\n const closeTag = `</${tagName}>`;\n const start = segment.indexOf(openTag);\n if (start === -1) return null;\n const contentStart = start + openTag.length;\n const end = segment.indexOf(closeTag, contentStart);\n if (end === -1) return null;\n return segment.slice(contentStart, end);\n}\n\nfunction parseCompleteMultipartUploadBody(body: string): UploadedPart[] {\n const parts: UploadedPart[] = [];\n let pos = 0;\n while (pos < body.length) {\n const start = body.indexOf('<Part>', pos);\n if (start === -1) break;\n const end = body.indexOf('</Part>', start + 6);\n if (end === -1) break;\n const segment = body.slice(start, end + 7);\n pos = end + 7;\n const partNumberText = extractXmlTagContent(segment, 'PartNumber');\n const etagText = extractXmlTagContent(segment, 'ETag');\n const partNumber = partNumberText ? parseInt(partNumberText, 10) : NaN;\n if (Number.isFinite(partNumber) && etagText) {\n parts.push({\n partNumber,\n etag: etagText.replace(/^\"|\"$/g, '')\n });\n }\n }\n return parts;\n}\n\n// ---------------------------------------------------------------------------\n// HTTP metadata helpers\n// ---------------------------------------------------------------------------\n\nfunction buildResponseHeaders(obj: R2Object): Headers {\n const headers = new Headers();\n headers.set('ETag', obj.httpEtag);\n headers.set('Content-Length', String(obj.size));\n headers.set('Last-Modified', obj.uploaded.toUTCString());\n headers.set('Accept-Ranges', 'bytes');\n if (obj.httpMetadata?.contentType) {\n headers.set('Content-Type', obj.httpMetadata.contentType);\n }\n if (obj.httpMetadata?.contentDisposition) {\n headers.set('Content-Disposition', obj.httpMetadata.contentDisposition);\n }\n if (obj.httpMetadata?.contentEncoding) {\n headers.set('Content-Encoding', obj.httpMetadata.contentEncoding);\n }\n if (obj.httpMetadata?.contentLanguage) {\n headers.set('Content-Language', obj.httpMetadata.contentLanguage);\n }\n if (obj.httpMetadata?.cacheControl) {\n headers.set('Cache-Control', obj.httpMetadata.cacheControl);\n }\n return headers;\n}\n\nfunction buildContentRange(range: R2Range, totalSize: number): string {\n if ('suffix' in range) {\n const start = Math.max(0, totalSize - range.suffix);\n return `bytes ${start}-${totalSize - 1}/${totalSize}`;\n }\n const start = range.offset ?? 0;\n const end =\n range.length !== undefined ? start + range.length - 1 : totalSize - 1;\n return `bytes ${start}-${end}/${totalSize}`;\n}\n\nfunction getRangeContentLength(range: R2Range, totalSize: number): number {\n if ('suffix' in range) {\n return Math.min(range.suffix, totalSize);\n }\n const start = range.offset ?? 0;\n if (start >= totalSize) {\n return 0;\n }\n const requestedLength =\n range.length !== undefined ? range.length : totalSize - start;\n return Math.min(requestedLength, totalSize - start);\n}\n\nfunction extractHttpMetadata(request: Request): R2HTTPMetadata {\n const meta: R2HTTPMetadata = {};\n const ct = request.headers.get('Content-Type');\n if (ct) meta.contentType = ct;\n const cd = request.headers.get('Content-Disposition');\n if (cd) meta.contentDisposition = cd;\n const ce = request.headers.get('Content-Encoding');\n if (ce) meta.contentEncoding = ce;\n const cl = request.headers.get('Content-Language');\n if (cl) meta.contentLanguage = cl;\n const cc = request.headers.get('Cache-Control');\n if (cc) meta.cacheControl = cc;\n return meta;\n}\n\nfunction parseCopySource(header: string): ParsedPath | null {\n const sourcePath = header.split('?')[0] ?? '';\n if (!sourcePath) return null;\n const decoded = decodeURIComponent(sourcePath);\n const parsed = parsePath(decoded.startsWith('/') ? decoded : `/${decoded}`);\n return parsed\n ? {\n bucket: parsed.bucket,\n key: normalizeObjectKey(parsed.key)\n }\n : null;\n}\n\nfunction normalizeStorageClass(\n storageClass: string | undefined\n): 'Standard' | 'InfrequentAccess' | undefined {\n if (storageClass === 'Standard' || storageClass === 'InfrequentAccess') {\n return storageClass;\n }\n return undefined;\n}\n\nasync function putRequestBody(\n r2: R2Bucket,\n key: string,\n request: Request,\n options: R2PutOptions\n): Promise<R2Object | Response> {\n const contentLength = request.headers.get('Content-Length');\n const length = contentLength ? Number.parseInt(contentLength, 10) : NaN;\n if (!Number.isFinite(length) || length < 0) {\n return new Response('Bad Request: missing or invalid Content-Length', {\n status: 400\n });\n }\n\n if (length === 0) {\n return r2.put(key, new Uint8Array(0), options);\n }\n\n if (!request.body) {\n return new Response('Bad Request: missing request body', { status: 400 });\n }\n\n const { readable, writable } = new FixedLengthStream(length);\n const pipe = request.body.pipeTo(writable);\n const result = await r2.put(key, readable, options);\n await pipe;\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Operation handlers\n// ---------------------------------------------------------------------------\n\nasync function handleListObjects(\n r2: R2Bucket,\n bucketName: string,\n url: URL,\n mountPrefix?: string\n): Promise<Response> {\n const queryPrefix = normalizeObjectKey(url.searchParams.get('prefix') ?? '');\n const delimiter = url.searchParams.get('delimiter') ?? '';\n const maxKeys = Math.min(\n parseInt(url.searchParams.get('max-keys') ?? '1000', 10) || 1000,\n 1000\n );\n const continuationToken =\n url.searchParams.get('continuation-token') ?? undefined;\n\n const r2Prefix = mountPrefix ? `${mountPrefix}/${queryPrefix}` : queryPrefix;\n\n const listOpts: R2ListOptions = {\n prefix: r2Prefix || undefined,\n delimiter: delimiter || undefined,\n limit: maxKeys,\n cursor: continuationToken\n };\n\n const result = await r2.list(listOpts);\n\n const stripKey = mountPrefix\n ? (k: string): string =>\n k.startsWith(`${mountPrefix}/`) ? k.slice(mountPrefix.length + 1) : k\n : (k: string): string => k;\n\n const displayResult: ListDisplayResult = {\n objects: result.objects.map((obj) => ({\n key: stripKey(obj.key),\n uploaded: obj.uploaded,\n httpEtag: obj.httpEtag,\n size: obj.size\n })),\n delimitedPrefixes: result.delimitedPrefixes.map(stripKey),\n truncated: result.truncated,\n cursor: result.truncated ? result.cursor : undefined\n };\n\n return xmlResponse(\n buildListObjectsV2Xml(\n bucketName,\n queryPrefix,\n delimiter,\n maxKeys,\n displayResult\n )\n );\n}\n\nasync function handleHeadObject(r2: R2Bucket, key: string): Promise<Response> {\n const obj = await r2.head(key);\n if (!obj) {\n return new Response(null, { status: 404 });\n }\n // NOTE: The outbound proxy rewrites Content-Length to 0 for bodiless\n // responses. s3fs stat cache must be pre-populated from LIST (readdir)\n // so that getattr never falls through to HEAD for file size information.\n return new Response(null, {\n status: 200,\n headers: buildResponseHeaders(obj)\n });\n}\n\nasync function handleGetObject(\n r2: R2Bucket,\n key: string,\n request: Request\n): Promise<Response> {\n const range = parseRange(request.headers.get('Range'));\n\n if (!range) {\n const obj = await r2.get(key);\n if (!obj) {\n return new Response(null, { status: 404 });\n }\n return new Response(obj.body, {\n status: 200,\n headers: buildResponseHeaders(obj)\n });\n }\n\n // R2 range bodies do not include the full object size needed for Content-Range.\n const [headObj, rangeObj] = await Promise.all([\n r2.head(key),\n r2.get(key, { range })\n ]);\n if (!headObj || !rangeObj) {\n return new Response(null, { status: 404 });\n }\n\n const headers = buildResponseHeaders(rangeObj);\n headers.set('Content-Range', buildContentRange(range, headObj.size));\n headers.set(\n 'Content-Length',\n String(getRangeContentLength(range, headObj.size))\n );\n\n return new Response(rangeObj.body, { status: 206, headers });\n}\n\nasync function handlePutObject(\n r2: R2Bucket,\n bucketName: string,\n key: string,\n request: Request,\n env: Cloudflare.Env,\n permitted: Set<string>,\n mountPrefix?: string\n): Promise<Response> {\n const copySourceHeader = request.headers.get('x-amz-copy-source');\n if (copySourceHeader) {\n const copySource = parseCopySource(copySourceHeader);\n if (!copySource || !copySource.key) {\n return new Response('Bad Request: invalid x-amz-copy-source', {\n status: 400\n });\n }\n\n if (!permitted.has(copySource.bucket)) {\n return new Response(\n `Access to R2 bucket \"${copySource.bucket}\" is not permitted. ` +\n 'Call mountBucket() with this bucket before accessing it.',\n { status: 403 }\n );\n }\n\n const sourceBucket =\n copySource.bucket === bucketName\n ? r2\n : resolveR2Bucket(env, copySource.bucket);\n if (!sourceBucket) {\n return new Response(\n `R2 binding \"${copySource.bucket}\" not found in Worker env. ` +\n 'Ensure the binding name matches the bucket name passed to mountBucket().',\n { status: 500 }\n );\n }\n\n const sourceKey =\n mountPrefix && copySource.bucket === bucketName\n ? `${mountPrefix}/${copySource.key}`\n : copySource.key;\n const sourceObject = await sourceBucket.get(sourceKey);\n if (!sourceObject) {\n return new Response(null, { status: 404 });\n }\n\n const metadataDirective = request.headers.get('x-amz-metadata-directive');\n const httpMetadata =\n metadataDirective?.toUpperCase() === 'REPLACE'\n ? extractHttpMetadata(request)\n : sourceObject.httpMetadata;\n const result = await r2.put(key, sourceObject.body, {\n httpMetadata,\n customMetadata: sourceObject.customMetadata,\n storageClass: normalizeStorageClass(sourceObject.storageClass)\n });\n return xmlResponse(buildCopyObjectXml(result.httpEtag, result.uploaded));\n }\n\n const httpMetadata = extractHttpMetadata(request);\n const result = await putRequestBody(r2, key, request, { httpMetadata });\n if (result instanceof Response) return result;\n const headers = new Headers();\n headers.set('ETag', result.httpEtag);\n return new Response(null, { status: 200, headers });\n}\n\nasync function handleDeleteObject(\n r2: R2Bucket,\n key: string\n): Promise<Response> {\n await r2.delete(key);\n return new Response(null, { status: 204 });\n}\n\nasync function handleCreateMultipartUpload(\n r2: R2Bucket,\n bucketName: string,\n key: string,\n request: Request\n): Promise<Response> {\n const httpMetadata = extractHttpMetadata(request);\n const upload = await r2.createMultipartUpload(key, { httpMetadata });\n return xmlResponse(\n buildInitiateMultipartUploadXml(bucketName, key, upload.uploadId)\n );\n}\n\nasync function handleUploadPart(\n r2: R2Bucket,\n key: string,\n url: URL,\n request: Request\n): Promise<Response> {\n const uploadId = url.searchParams.get('uploadId') ?? '';\n const partNumber = parseInt(url.searchParams.get('partNumber') ?? '0', 10);\n if (!uploadId || !partNumber) {\n return new Response('Bad Request: missing uploadId or partNumber', {\n status: 400\n });\n }\n if (!request.body) {\n return new Response('Bad Request: missing request body', { status: 400 });\n }\n const contentLength = request.headers.get('Content-Length');\n const partLength = contentLength ? Number.parseInt(contentLength, 10) : NaN;\n if (!Number.isFinite(partLength) || partLength < 0) {\n return new Response('Bad Request: missing or invalid Content-Length', {\n status: 400\n });\n }\n const upload = r2.resumeMultipartUpload(key, uploadId);\n let part: R2UploadedPart;\n if (partLength === 0) {\n part = await upload.uploadPart(partNumber, new Uint8Array(0));\n } else {\n const { readable, writable } = new FixedLengthStream(partLength);\n const pipe = request.body.pipeTo(writable);\n part = await upload.uploadPart(partNumber, readable);\n await pipe;\n }\n const headers = new Headers();\n headers.set('ETag', `\"${part.etag}\"`);\n return new Response(null, { status: 200, headers });\n}\n\nasync function handleCompleteMultipartUpload(\n r2: R2Bucket,\n bucketName: string,\n key: string,\n url: URL,\n request: Request\n): Promise<Response> {\n const uploadId = url.searchParams.get('uploadId') ?? '';\n if (!uploadId) {\n return new Response('Bad Request: missing uploadId', { status: 400 });\n }\n const bodyText = await request.text();\n const parsedParts = parseCompleteMultipartUploadBody(bodyText);\n const r2Parts: R2UploadedPart[] = parsedParts.map((p) => ({\n partNumber: p.partNumber,\n etag: p.etag\n }));\n const upload = r2.resumeMultipartUpload(key, uploadId);\n const result = await upload.complete(r2Parts);\n return xmlResponse(\n buildCompleteMultipartUploadXml(bucketName, key, result.httpEtag)\n );\n}\n\nasync function handleAbortMultipartUpload(\n r2: R2Bucket,\n key: string,\n url: URL\n): Promise<Response> {\n const uploadId = url.searchParams.get('uploadId') ?? '';\n if (!uploadId) {\n return new Response('Bad Request: missing uploadId', { status: 400 });\n }\n const upload = r2.resumeMultipartUpload(key, uploadId);\n await upload.abort();\n return new Response(null, { status: 204 });\n}\n\n// ---------------------------------------------------------------------------\n// Main handler\n// ---------------------------------------------------------------------------\n\nexport const r2EgressHandler: OutboundHandler<\n Cloudflare.Env,\n R2EgressParams\n> = async (\n request: Request,\n env: Cloudflare.Env,\n ctx: OutboundHandlerContext<R2EgressParams>\n): Promise<Response> => {\n const url = new URL(request.url);\n const parsed = parsePath(url.pathname);\n\n if (!parsed) {\n return new Response('Bad Request: empty path', { status: 400 });\n }\n\n const { bucket: bucketName, key } = parsed;\n\n if (!ctx.params?.buckets || !(bucketName in ctx.params.buckets)) {\n return new Response(\n `Access to R2 bucket \"${bucketName}\" is not permitted. ` +\n 'Call mountBucket() with this bucket before accessing it.',\n { status: 403 }\n );\n }\n\n const bucketParams = ctx.params.buckets[bucketName];\n const rawPrefix = bucketParams.prefix;\n const mountPrefix = rawPrefix\n ? trimTrailingSlashes(normalizeObjectKey(rawPrefix))\n : undefined;\n const readOnly = bucketParams.readOnly ?? false;\n\n const r2 = resolveR2Bucket(env, bucketName);\n if (!r2) {\n return new Response(\n `R2 binding \"${bucketName}\" not found in Worker env. ` +\n 'Ensure the binding name matches the bucket name passed to mountBucket().',\n { status: 500 }\n );\n }\n\n const { method } = request;\n\n // Bucket-level operations (no key)\n if (!key) {\n if (method === 'GET' && url.searchParams.has('location')) {\n return xmlResponse(buildLocationXml());\n }\n if (method === 'GET' && url.searchParams.get('list-type') === '2') {\n return handleListObjects(r2, bucketName, url, mountPrefix);\n }\n // Legacy ListObjects (v1) — s3fs may use this for some operations\n if (method === 'GET') {\n return handleListObjects(r2, bucketName, url, mountPrefix);\n }\n return new Response('Method Not Allowed', { status: 405 });\n }\n\n const fullKey = mountPrefix ? `${mountPrefix}/${key}` : key;\n const permitted = new Set(Object.keys(ctx.params.buckets));\n\n if (\n readOnly &&\n (method === 'PUT' ||\n method === 'DELETE' ||\n (method === 'POST' &&\n (url.searchParams.has('uploads') || url.searchParams.has('uploadId'))))\n ) {\n return new Response('Forbidden: bucket mount is read-only', {\n status: 403\n });\n }\n\n // Multipart upload: POST /bucket/key?uploads — initiate\n if (method === 'POST' && url.searchParams.has('uploads')) {\n return handleCreateMultipartUpload(r2, bucketName, fullKey, request);\n }\n\n // Multipart upload: POST /bucket/key?uploadId=X — complete\n if (method === 'POST' && url.searchParams.has('uploadId')) {\n return handleCompleteMultipartUpload(r2, bucketName, fullKey, url, request);\n }\n\n // Multipart upload: PUT /bucket/key?partNumber=N&uploadId=X — upload part\n if (\n method === 'PUT' &&\n url.searchParams.has('partNumber') &&\n url.searchParams.has('uploadId')\n ) {\n return handleUploadPart(r2, fullKey, url, request);\n }\n\n // Multipart upload: DELETE /bucket/key?uploadId=X — abort\n if (method === 'DELETE' && url.searchParams.has('uploadId')) {\n return handleAbortMultipartUpload(r2, fullKey, url);\n }\n\n // Standard object operations\n switch (method) {\n case 'HEAD':\n return handleHeadObject(r2, fullKey);\n case 'GET':\n return handleGetObject(r2, fullKey, request);\n case 'PUT':\n return handlePutObject(\n r2,\n bucketName,\n fullKey,\n request,\n env,\n permitted,\n mountPrefix\n );\n case 'DELETE':\n return handleDeleteObject(r2, fullKey);\n default:\n return new Response('Method Not Allowed', { status: 405 });\n }\n};\n","/**\n * Outbound handler that signs and forwards S3-compatible requests from s3fs\n * to the configured endpoint using real credentials held in the Durable Object.\n *\n * s3fs inside the container sends requests to the stable internal proxy host\n * `s3-credential-proxy.internal`. The mount ID is encoded as the first path\n * segment so the handler can resolve the correct mount config without per-mount\n * subdomain registration.\n *\n * Request path layout: /${mountId}/${bucket}/${objectKey}\n * Real upstream URL: ${endpoint}/${bucket}/${objectKey}\n *\n * The handler also accepts the legacy per-mount-host shape\n * (${mountId}.s3-credential-proxy.internal) for backward compatibility.\n */\n\nimport type {\n OutboundHandler,\n OutboundHandlerContext\n} from '@cloudflare/containers';\nimport type { BucketProvider } from '@repo/shared';\nimport { AwsClient } from 'aws4fetch';\nimport type { S3CredentialProxyParams } from '../types';\n\nconst PER_MOUNT_SUFFIX = '.s3-credential-proxy.internal';\nexport const SELF_TEST_PATH = '/__sandbox_credential_proxy_self_test__';\nexport const DIAGNOSTICS_PATH = '/__sandbox_credential_proxy_diagnostics__';\nconst DEFAULT_SLOW_REQUEST_MS = 1000;\nconst ERROR_RESPONSE_BODY_LIMIT = 2048;\nconst MAX_DIAGNOSTIC_EVENTS = 500;\n\nexport const DUMMY_AUTH_HEADERS = new Set([\n 'authorization',\n 'x-amz-date',\n 'x-amz-content-sha256',\n 'x-amz-security-token',\n 'x-goog-date',\n 'x-goog-content-sha256'\n]);\n\ntype CredentialProxyDebugConfig = {\n diagnosticsEndpointEnabled: boolean;\n enabled: boolean;\n slowRequestMs: number;\n};\n\ntype CredentialProxyRequestInfo = {\n authStrategy: S3CredentialProxyParams['mounts'][string]['authStrategy'];\n bucket: string;\n bodyPresent?: boolean;\n contentLength: string | null;\n method: string;\n mountId: string;\n payloadHashMode?: 'signed' | 'unsigned';\n query: string[];\n clientSetupMs?: number;\n upstreamMs?: number;\n};\n\ntype CredentialProxyDiagnosticEvent = CredentialProxyRequestInfo & {\n containerId: string;\n durationMs: number;\n ok: boolean;\n path: string;\n status: number;\n timestamp: string;\n};\n\ntype SigV4ClientCacheEntry = {\n client: AwsClient;\n accessKeyId: string;\n secretAccessKey: string;\n endpoint: string;\n provider: BucketProvider | null;\n region: string;\n};\n\nconst sigV4ClientCache = new Map<string, SigV4ClientCacheEntry>();\nconst directoryMarkerCache = new Map<string, [string, string][]>();\nconst credentialProxyDiagnosticEvents: CredentialProxyDiagnosticEvent[] = [];\nlet credentialProxyDiagnosticEventCount = 0;\n\nexport function evictSigV4ClientCacheEntry(mountId: string): void {\n sigV4ClientCache.delete(mountId);\n}\n\nexport function evictDirectoryMarkerCacheForMount(mountId: string): void {\n const prefix = `${mountId}:`;\n for (const key of directoryMarkerCache.keys()) {\n if (key.startsWith(prefix)) {\n directoryMarkerCache.delete(key);\n }\n }\n}\n\nfunction toHex(buffer: ArrayBuffer): string {\n return Array.from(new Uint8Array(buffer))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n\nasync function sha256Hex(data: string): Promise<string> {\n return toHex(\n await crypto.subtle.digest('SHA-256', new TextEncoder().encode(data))\n );\n}\n\nasync function hmacSHA256(\n key: BufferSource,\n data: string\n): Promise<ArrayBuffer> {\n const cryptoKey = await crypto.subtle.importKey(\n 'raw',\n key,\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n );\n return crypto.subtle.sign('HMAC', cryptoKey, new TextEncoder().encode(data));\n}\n\nfunction detectS3Region(\n provider: BucketProvider | null,\n endpoint: string\n): string {\n if (provider === 'r2') return 'auto';\n try {\n const host = new URL(endpoint).hostname;\n const m = host.match(/s3[.-]([a-z0-9-]+)\\.amazonaws\\.com/);\n if (m && m[1] !== 'amazonaws') return m[1];\n if (host === 's3.amazonaws.com') return 'us-east-1';\n } catch {\n // ignore\n }\n return 'auto';\n}\n\nfunction buildCleanHeaders(original: Headers): Headers {\n const clean = new Headers();\n for (const [k, v] of original as unknown as Iterable<[string, string]>) {\n const lower = k.toLowerCase();\n // Strip dummy signing headers and the intercepted proxy host; x-amz-content-sha256\n // is stripped here too (it's in DUMMY_AUTH_HEADERS) so it can be re-added below\n // only when the original value is a genuine payload hash (not a dummy signing value).\n if (!DUMMY_AUTH_HEADERS.has(lower) && lower !== 'host') {\n clean.set(k, v);\n }\n }\n const contentSHA256 = original.get('x-amz-content-sha256');\n if (contentSHA256 && isValidContentSHA256(contentSHA256)) {\n clean.set('x-amz-content-sha256', contentSHA256);\n }\n return clean;\n}\n\nfunction isValidContentSHA256(value: string): boolean {\n return value === 'UNSIGNED-PAYLOAD' || /^[a-fA-F0-9]{64}$/.test(value);\n}\n\nfunction getCredentialProxyDebugConfig(\n env: Cloudflare.Env\n): CredentialProxyDebugConfig {\n const envRecord = env as Record<string, unknown>;\n const enabled = envRecord.SANDBOX_CREDENTIAL_PROXY_DEBUG === 'true';\n const diagnosticsEndpointEnabled =\n envRecord.SANDBOX_CREDENTIAL_PROXY_DIAGNOSTICS_ENDPOINT === 'true';\n const configuredSlowRequestMs = Number(\n envRecord.SANDBOX_CREDENTIAL_PROXY_SLOW_REQUEST_MS\n );\n const slowRequestMs =\n Number.isFinite(configuredSlowRequestMs) && configuredSlowRequestMs >= 0\n ? configuredSlowRequestMs\n : DEFAULT_SLOW_REQUEST_MS;\n return { diagnosticsEndpointEnabled, enabled, slowRequestMs };\n}\n\nfunction recordCredentialProxyDiagnosticEvent(\n event: CredentialProxyDiagnosticEvent\n): void {\n credentialProxyDiagnosticEvents.push(event);\n credentialProxyDiagnosticEventCount++;\n while (credentialProxyDiagnosticEvents.length > MAX_DIAGNOSTIC_EVENTS) {\n credentialProxyDiagnosticEvents.shift();\n }\n}\n\nfunction getCredentialProxyDiagnosticsResponse(\n url: URL,\n containerId: string\n): Response {\n const since = Number(url.searchParams.get('since') ?? '0');\n const bufferStartCount =\n credentialProxyDiagnosticEventCount -\n credentialProxyDiagnosticEvents.length;\n const events = credentialProxyDiagnosticEvents.filter((event, index) => {\n if (event.containerId !== containerId) return false;\n return !Number.isFinite(since) || bufferStartCount + index >= since;\n });\n return Response.json({\n nextCursor: credentialProxyDiagnosticEventCount,\n events\n });\n}\n\nasync function withCredentialProxyDiagnostics(\n requestInfo: CredentialProxyRequestInfo,\n debugConfig: CredentialProxyDebugConfig,\n containerId: string,\n path: string,\n operation: () => Promise<Response>\n): Promise<Response> {\n const started = Date.now();\n try {\n const response = await operation();\n const durationMs = Date.now() - started;\n if (debugConfig.enabled) {\n recordCredentialProxyDiagnosticEvent({\n ...requestInfo,\n containerId,\n durationMs,\n ok: response.ok,\n path,\n status: response.status,\n timestamp: new Date().toISOString()\n });\n }\n if (debugConfig.enabled || durationMs >= debugConfig.slowRequestMs) {\n console.info('sandbox.s3_credential_proxy.request', {\n ...requestInfo,\n durationMs,\n ok: response.ok,\n status: response.status,\n responseContentLength: response.headers.get('content-length')\n });\n }\n if (!response.ok) {\n // Read the error body asynchronously so we don't delay returning the\n // response to s3fs (important for 4xx/throttle responses).\n const responseForLog = response.clone();\n const requestInfoSnapshot = { ...requestInfo };\n responseForLog\n .text()\n .then((body) => {\n console.warn('sandbox.s3_credential_proxy.upstream_error', {\n ...requestInfoSnapshot,\n durationMs,\n status: response.status,\n statusText: response.statusText,\n errorBody: body.slice(0, ERROR_RESPONSE_BODY_LIMIT)\n });\n })\n .catch(() => {});\n }\n return response;\n } catch (error) {\n const durationMs = Date.now() - started;\n console.warn('sandbox.s3_credential_proxy.request_error', {\n ...requestInfo,\n durationMs,\n error: error instanceof Error ? error.message : String(error)\n });\n throw error;\n }\n}\n\nfunction getSigV4Client(\n mountId: string,\n endpoint: string,\n provider: BucketProvider | null,\n credentials: { accessKeyId: string; secretAccessKey: string },\n region: string\n): AwsClient {\n const cached = sigV4ClientCache.get(mountId);\n if (\n cached &&\n cached.accessKeyId === credentials.accessKeyId &&\n cached.secretAccessKey === credentials.secretAccessKey &&\n cached.endpoint === endpoint &&\n cached.provider === provider &&\n cached.region === region\n ) {\n return cached.client;\n }\n\n const client = new AwsClient({\n accessKeyId: credentials.accessKeyId,\n secretAccessKey: credentials.secretAccessKey,\n service: 's3',\n region,\n retries: 0\n });\n sigV4ClientCache.set(mountId, {\n client,\n accessKeyId: credentials.accessKeyId,\n secretAccessKey: credentials.secretAccessKey,\n endpoint,\n provider,\n region\n });\n return client;\n}\n\nfunction encodeCanonicalQueryPart(value: string): string {\n return encodeURIComponent(value).replace(\n /[!'()*]/g,\n (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`\n );\n}\n\nfunction getCanonicalURI(url: URL): string {\n return url.pathname\n .split('/')\n .map((segment) =>\n segment\n .split(/(%[0-9A-Fa-f]{2})/g)\n .map((part) =>\n /^%[0-9A-Fa-f]{2}$/.test(part)\n ? part.toUpperCase()\n : encodeCanonicalQueryPart(part)\n )\n .join('')\n )\n .join('/');\n}\n\nfunction getCanonicalQueryString(url: URL): string {\n const query = url.search.startsWith('?') ? url.search.slice(1) : url.search;\n if (!query) return '';\n return query\n .split('&')\n .map((part): [string, string] => {\n const separatorIndex = part.indexOf('=');\n if (separatorIndex === -1) return [part, ''];\n return [part.slice(0, separatorIndex), part.slice(separatorIndex + 1)];\n })\n .map(([key, value]): [string, string] => [\n encodeCanonicalQueryPart(decodeURIEncodedQueryPart(key)),\n encodeCanonicalQueryPart(decodeURIEncodedQueryPart(value))\n ])\n .sort(([leftKey, leftValue], [rightKey, rightValue]) => {\n if (leftKey < rightKey) return -1;\n if (leftKey > rightKey) return 1;\n if (leftValue < rightValue) return -1;\n if (leftValue > rightValue) return 1;\n return 0;\n })\n .map(([key, value]) => `${key}=${value}`)\n .join('&');\n}\n\nfunction decodeURIEncodedQueryPart(value: string): string {\n try {\n return decodeURIComponent(value);\n } catch {\n return value;\n }\n}\n\nfunction getSigV4PayloadHash(headers: Headers): {\n hash: string;\n mode: 'signed' | 'unsigned';\n} {\n const existingHash = headers.get('x-amz-content-sha256');\n if (existingHash && existingHash !== 'UNSIGNED-PAYLOAD') {\n return { hash: existingHash, mode: 'signed' };\n }\n return { hash: 'UNSIGNED-PAYLOAD', mode: 'unsigned' };\n}\n\nfunction isZeroLengthDirectoryMarkerPUT(\n request: Request,\n realPath: string\n): boolean {\n return (\n request.method.toUpperCase() === 'PUT' &&\n request.headers.get('content-length') === '0' &&\n realPath.endsWith('/')\n );\n}\n\nfunction isDirectoryMarkerHEAD(request: Request): boolean {\n return request.method.toUpperCase() === 'HEAD';\n}\n\nfunction getDirectoryMarkerCacheKey(mountId: string, realPath: string): string {\n return `${mountId}:${realPath.replace(/\\/+$/, '')}`;\n}\n\nfunction getDirectoryMarkerResponseHeaders(\n request: Request\n): [string, string][] {\n const headers: [string, string][] = [\n ['Accept-Ranges', 'bytes'],\n ['Content-Length', '0'],\n ['ETag', '\"d41d8cd98f00b204e9800998ecf8427e\"'],\n ['Last-Modified', new Date().toUTCString()]\n ];\n const contentType = request.headers.get('content-type');\n if (contentType) {\n headers.push(['Content-Type', contentType]);\n }\n for (const [name, value] of request.headers as unknown as Iterable<\n [string, string]\n >) {\n if (name.toLowerCase().startsWith('x-amz-meta-')) {\n headers.push([name, value]);\n }\n }\n return headers;\n}\n\nfunction normalizePrefix(prefix: string | undefined): string | undefined {\n if (!prefix) return undefined;\n return prefix.replace(/^\\/+/, '').replace(/\\/+$/, '');\n}\n\nfunction getObjectKeyForPath(realPath: string, bucket: string): string | null {\n const pathSegments = realPath.split('/').filter(Boolean);\n const requestBucket = pathSegments[0];\n if (requestBucket !== bucket) {\n return null;\n }\n return pathSegments.slice(1).join('/');\n}\n\nfunction isObjectKeyWithinPrefix(\n objectKey: string,\n prefix: string | undefined\n): boolean {\n const normalizedPrefix = normalizePrefix(prefix);\n if (!normalizedPrefix) {\n return true;\n }\n return (\n objectKey === normalizedPrefix ||\n objectKey.startsWith(`${normalizedPrefix}/`)\n );\n}\n\nfunction isRequestWithinMountScope(\n realPath: string,\n url: URL,\n bucket: string,\n prefix: string | undefined\n): boolean {\n const objectKey = getObjectKeyForPath(realPath, bucket);\n if (objectKey === null) {\n return false;\n }\n\n const requestedPrefix = url.searchParams.get('prefix');\n if (objectKey !== '' && !isObjectKeyWithinPrefix(objectKey, prefix)) {\n return false;\n }\n\n if (\n objectKey === '' &&\n normalizePrefix(prefix) !== undefined &&\n url.search !== '' &&\n requestedPrefix === null\n ) {\n return false;\n }\n\n if (requestedPrefix !== null) {\n return isObjectKeyWithinPrefix(requestedPrefix, prefix);\n }\n\n return true;\n}\n\nfunction isBucketRootProbe(\n request: Request,\n realPath: string,\n url: URL,\n bucket: string\n): boolean {\n const method = request.method.toUpperCase();\n return (\n (method === 'GET' || method === 'HEAD') &&\n url.search === '' &&\n getObjectKeyForPath(realPath, bucket) === ''\n );\n}\n\nfunction deleteDirectoryMarkerCacheEntry(\n mountId: string,\n realPath: string\n): void {\n directoryMarkerCache.delete(getDirectoryMarkerCacheKey(mountId, realPath));\n}\n\nfunction getContentLength(request: Request): number | null {\n const contentLength = request.headers.get('content-length');\n if (contentLength === null) {\n return null;\n }\n const parsed = Number(contentLength);\n if (!Number.isSafeInteger(parsed) || parsed < 0) {\n return null;\n }\n return parsed;\n}\n\nfunction getSigV4ForwardInit(request: Request): RequestInit {\n const contentLength = getContentLength(request);\n if (contentLength === 0) {\n return { body: new Uint8Array(0) };\n }\n if (contentLength === null || request.body === null) {\n return {};\n }\n\n const { readable, writable } = new FixedLengthStream(contentLength);\n request.body.pipeTo(writable).catch((error) => {\n writable.abort(error).catch(() => {});\n });\n return { body: readable };\n}\n\nfunction getGCSHeaders(request: Request): Headers {\n const headers = new Headers();\n for (const [k, v] of request.headers as unknown as Iterable<\n [string, string]\n >) {\n const lower = k.toLowerCase();\n if (\n DUMMY_AUTH_HEADERS.has(lower) ||\n lower === 'host' ||\n lower === 'content-length' ||\n lower === 'expect'\n ) {\n continue;\n }\n if (lower.startsWith('x-amz-meta-')) {\n headers.set(`x-goog-meta-${lower.slice('x-amz-meta-'.length)}`, v);\n continue;\n }\n if (lower.startsWith('x-amz-')) {\n continue;\n }\n headers.set(k, v);\n }\n return headers;\n}\n\nasync function signAndForwardSigV4(\n request: Request,\n mountId: string,\n endpoint: string,\n provider: BucketProvider | null,\n credentials: { accessKeyId: string; secretAccessKey: string },\n requestInfo: CredentialProxyRequestInfo\n): Promise<Response> {\n const signingStarted = Date.now();\n const region = detectS3Region(provider, endpoint);\n const payload = getSigV4PayloadHash(request.headers);\n const client = getSigV4Client(\n mountId,\n endpoint,\n provider,\n credentials,\n region\n );\n requestInfo.payloadHashMode = payload.mode;\n // clientSetupMs captures region detection + client cache lookup + payload hash;\n // the actual SigV4 signing happens inside client.fetch() and is included in upstreamMs.\n requestInfo.clientSetupMs = Date.now() - signingStarted;\n\n const upstreamStarted = Date.now();\n const forwardInit = getSigV4ForwardInit(request);\n requestInfo.bodyPresent =\n forwardInit?.body !== undefined || request.body !== null;\n const response = await client.fetch(request, forwardInit);\n requestInfo.upstreamMs = Date.now() - upstreamStarted;\n return response;\n}\n\nasync function signAndForwardGCS(\n request: Request,\n credentials: { accessKeyId: string; secretAccessKey: string },\n requestInfo: CredentialProxyRequestInfo\n): Promise<Response> {\n const url = new URL(request.url);\n const gcsHeaders = getGCSHeaders(request);\n const now = new Date();\n const dateStr = `${now\n .toISOString()\n .replace(/[:-]/g, '')\n .replace(/\\.\\d+Z$/, '')}Z`;\n const dateOnly = dateStr.slice(0, 8);\n const location = 'auto';\n const service = 'storage';\n const credentialScope = `${dateOnly}/${location}/${service}/goog4_request`;\n\n const bodyHash = 'UNSIGNED-PAYLOAD';\n\n const headerEntries: [string, string][] = [\n ['host', url.host],\n ['x-goog-content-sha256', bodyHash],\n ['x-goog-date', dateStr]\n ];\n\n for (const [k, v] of gcsHeaders as unknown as Iterable<[string, string]>) {\n headerEntries.push([k.toLowerCase(), v.trim()]);\n }\n\n headerEntries.sort((a, b) => a[0].localeCompare(b[0]));\n const signedHeaders = headerEntries.map(([k]) => k).join(';');\n const canonicalHeaders = headerEntries\n .map(([k, v]) => `${k}:${v}\\n`)\n .join('');\n\n const canonicalRequest = [\n request.method,\n getCanonicalURI(url),\n getCanonicalQueryString(url),\n canonicalHeaders,\n signedHeaders,\n bodyHash\n ].join('\\n');\n\n const canonicalRequestHash = await sha256Hex(canonicalRequest);\n\n const stringToSign = [\n 'GOOG4-HMAC-SHA256',\n dateStr,\n credentialScope,\n canonicalRequestHash\n ].join('\\n');\n\n const enc = new TextEncoder();\n const kDate = await hmacSHA256(\n enc.encode(`GOOG4${credentials.secretAccessKey}`) as unknown as ArrayBuffer,\n dateOnly\n );\n const kRegion = await hmacSHA256(kDate, location);\n const kService = await hmacSHA256(kRegion, service);\n const kSigning = await hmacSHA256(kService, 'goog4_request');\n const sigBytes = await hmacSHA256(kSigning, stringToSign);\n const signature = toHex(sigBytes);\n\n const authorization = `GOOG4-HMAC-SHA256 Credential=${credentials.accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;\n\n const newHeaders = new Headers(gcsHeaders);\n newHeaders.set('x-goog-date', dateStr);\n newHeaders.set('x-goog-content-sha256', bodyHash);\n newHeaders.set('Authorization', authorization);\n\n const contentLength = getContentLength(request);\n const gcsBody = contentLength === 0 ? new Uint8Array(0) : request.body;\n const upstreamStarted = Date.now();\n const response = await fetch(\n new Request(request.url, {\n method: request.method,\n headers: newHeaders,\n body: gcsBody\n })\n );\n requestInfo.upstreamMs = Date.now() - upstreamStarted;\n return response;\n}\n\nexport const s3CredentialProxyHandler: OutboundHandler<\n Cloudflare.Env,\n S3CredentialProxyParams\n> = async (\n request: Request,\n env: Cloudflare.Env,\n ctx: OutboundHandlerContext<S3CredentialProxyParams>\n): Promise<Response> => {\n const url = new URL(request.url);\n\n if (url.pathname === SELF_TEST_PATH) {\n return new Response('OK', { status: 200 });\n }\n\n const debugConfig = getCredentialProxyDebugConfig(env);\n\n if (url.pathname === DIAGNOSTICS_PATH) {\n if (!debugConfig.enabled || !debugConfig.diagnosticsEndpointEnabled) {\n return new Response('Not Found', { status: 404 });\n }\n return getCredentialProxyDiagnosticsResponse(url, ctx.containerId);\n }\n\n const segments = url.pathname.split('/').filter(Boolean);\n const hostname = url.hostname;\n\n let mountId: string | null;\n let realPath: string;\n\n if (hostname.endsWith(PER_MOUNT_SUFFIX)) {\n // Legacy per-mount-host shape: mountId is the subdomain\n mountId = hostname.slice(0, -PER_MOUNT_SUFFIX.length);\n realPath = url.pathname;\n } else {\n // Primary path-based shape: /${mountId}/${bucket}/${objectKey}\n mountId = segments[0] ?? null;\n realPath = mountId ? url.pathname.slice(`/${mountId}`.length) || '/' : '/';\n }\n\n if (!mountId) {\n return new Response('Bad Request: missing mount ID', { status: 400 });\n }\n\n const mount = ctx.params?.mounts[mountId];\n if (!mount) {\n return new Response(`Forbidden: unknown mount ID \"${mountId}\"`, {\n status: 403\n });\n }\n\n if (mount.readOnly) {\n const method = request.method.toUpperCase();\n if (method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS') {\n return new Response('Forbidden: bucket mount is read-only', {\n status: 403\n });\n }\n }\n\n const realUrl = new URL(realPath + (url.search || ''), mount.endpoint);\n if (isBucketRootProbe(request, realPath, url, mount.bucket)) {\n return new Response(null, { status: 200 });\n }\n if (!isRequestWithinMountScope(realPath, url, mount.bucket, mount.prefix)) {\n return new Response('Forbidden: request is outside mounted bucket scope', {\n status: 403\n });\n }\n const cleanHeaders = buildCleanHeaders(request.headers);\n const cleanRequest = new Request(realUrl.toString(), {\n method: request.method,\n headers: cleanHeaders,\n body: request.body\n });\n const requestInfo: CredentialProxyRequestInfo = {\n authStrategy: mount.authStrategy,\n bucket: mount.bucket,\n contentLength: request.headers.get('content-length'),\n method: request.method,\n mountId,\n query: [...url.searchParams.keys()].sort()\n };\n\n if (isZeroLengthDirectoryMarkerPUT(cleanRequest, realPath)) {\n const responseHeaders = getDirectoryMarkerResponseHeaders(cleanRequest);\n requestInfo.bodyPresent = request.body !== null;\n if (mount.authStrategy === 's3-sigv4') {\n requestInfo.payloadHashMode = getSigV4PayloadHash(\n cleanRequest.headers\n ).mode;\n }\n return withCredentialProxyDiagnostics(\n requestInfo,\n debugConfig,\n ctx.containerId,\n realPath,\n async () => {\n const response =\n mount.authStrategy === 'gcs'\n ? await signAndForwardGCS(\n cleanRequest,\n mount.credentials,\n requestInfo\n )\n : await signAndForwardSigV4(\n cleanRequest,\n mountId,\n mount.endpoint,\n mount.provider,\n mount.credentials,\n requestInfo\n );\n\n if (response.ok) {\n directoryMarkerCache.set(\n getDirectoryMarkerCacheKey(mountId, realPath),\n responseHeaders\n );\n }\n\n return response;\n }\n );\n }\n\n if (isDirectoryMarkerHEAD(cleanRequest)) {\n const responseHeaders = directoryMarkerCache.get(\n getDirectoryMarkerCacheKey(mountId, realPath)\n );\n if (responseHeaders) {\n requestInfo.bodyPresent = false;\n if (mount.authStrategy === 's3-sigv4') {\n requestInfo.payloadHashMode = getSigV4PayloadHash(\n cleanRequest.headers\n ).mode;\n }\n return withCredentialProxyDiagnostics(\n requestInfo,\n debugConfig,\n ctx.containerId,\n realPath,\n () =>\n Promise.resolve(\n new Response(null, {\n status: 200,\n headers: responseHeaders\n })\n )\n );\n }\n }\n\n if (cleanRequest.method.toUpperCase() !== 'HEAD') {\n deleteDirectoryMarkerCacheEntry(mountId, realPath);\n }\n\n if (mount.authStrategy === 'gcs') {\n return withCredentialProxyDiagnostics(\n requestInfo,\n debugConfig,\n ctx.containerId,\n realPath,\n () => signAndForwardGCS(cleanRequest, mount.credentials, requestInfo)\n );\n }\n\n return withCredentialProxyDiagnostics(\n requestInfo,\n debugConfig,\n ctx.containerId,\n realPath,\n () =>\n signAndForwardSigV4(\n cleanRequest,\n mountId,\n mount.endpoint,\n mount.provider,\n mount.credentials,\n requestInfo\n )\n );\n};\n","import {\n ContainerProxy as BaseContainerProxy,\n type OutboundHandlerContext\n} from '@cloudflare/containers';\nimport type { HTTPAuthInterceptorParams } from '../../extensions/index.js';\nimport { gitCredentialProxyHandler } from './git-credential-proxy-handler';\n\ntype GitAuthInterceptorParams = HTTPAuthInterceptorParams;\n\nimport type { S3CredentialProxyParams } from '../types';\nimport { type R2EgressParams, r2EgressHandler } from './r2-egress-handler';\nimport { s3CredentialProxyHandler } from './s3-credential-proxy-handler';\n\nexport const CONTAINER_PROXY_CLASS_NAME = 'ContainerProxy';\nexport const S3_CREDENTIAL_PROXY_HOST = 's3-credential-proxy.internal';\nexport const S3_CREDENTIAL_PROXY_DIAGNOSTIC_HOST =\n 's3-credential-proxy.sandbox.test';\n\nexport type SDKOutboundMethod = keyof typeof SDK_OUTBOUND_HANDLERS;\n\nexport type OutboundHandlerRegistry = {\n outboundHandlers?: Record<string, unknown>;\n};\n\nexport const SDK_OUTBOUND_HANDLERS = {\n r2EgressMount: r2EgressHandler,\n s3CredentialProxyMount: s3CredentialProxyHandler,\n gitCredentialProxy: gitCredentialProxyHandler\n};\n\nexport function installSDKOutboundHandlers(constructorRef: Function): void {\n const registry = constructorRef as unknown as OutboundHandlerRegistry;\n registry.outboundHandlers = {\n ...(registry.outboundHandlers ?? {}),\n ...SDK_OUTBOUND_HANDLERS\n };\n}\n\ntype ContainerProxyProps = {\n outboundByHostOverrides?: Record<\n string,\n { method: SDKOutboundMethod | string; params?: unknown }\n >;\n containerId?: string;\n className?: string;\n};\n\nexport class ContainerProxy extends BaseContainerProxy {\n async fetch(request: Request): Promise<Response> {\n const hostname = new URL(request.url).hostname;\n const props = this.ctx.props as ContainerProxyProps;\n const override = props.outboundByHostOverrides?.[hostname];\n if (!override) return super.fetch(request);\n\n const handlerCtx = {\n containerId: props.containerId ?? '',\n className: props.className ?? '',\n params: override.params\n };\n\n switch (override.method) {\n case 'r2EgressMount':\n return r2EgressHandler(\n request,\n this.env as Cloudflare.Env,\n handlerCtx as OutboundHandlerContext<R2EgressParams>\n );\n case 's3CredentialProxyMount':\n return s3CredentialProxyHandler(\n request,\n this.env as Cloudflare.Env,\n handlerCtx as OutboundHandlerContext<S3CredentialProxyParams>\n );\n case 'gitCredentialProxy':\n return gitCredentialProxyHandler(\n request,\n this.env as Cloudflare.Env,\n handlerCtx as OutboundHandlerContext<GitAuthInterceptorParams>\n );\n default:\n return super.fetch(request);\n }\n }\n}\n\n(ContainerProxy as unknown as OutboundHandlerRegistry).outboundHandlers =\n SDK_OUTBOUND_HANDLERS;\n","import type { Logger } from '@repo/shared';\nimport type { HTTPAuthInterceptorParams } from '../../extensions/index.js';\n\ntype GitAuthInterceptorParams = HTTPAuthInterceptorParams;\n\nimport { InvalidMountConfigError } from '../errors';\nimport type { S3CredentialProxyParams } from '../types';\nimport {\n CONTAINER_PROXY_CLASS_NAME,\n installSDKOutboundHandlers,\n S3_CREDENTIAL_PROXY_DIAGNOSTIC_HOST,\n S3_CREDENTIAL_PROXY_HOST\n} from './container-proxy';\nimport type { R2EgressParams } from './r2-egress-handler';\nimport { SELF_TEST_PATH as S3_CREDENTIAL_PROXY_SELF_TEST_PATH } from './s3-credential-proxy-handler';\n\nexport type EgressContainerState = DurableObjectState<{}> & {\n exports?: {\n ContainerProxy?: (options: {\n props: {\n enableInternet?: boolean;\n containerId: string;\n className: string;\n outboundByHostOverrides: Record<\n string,\n {\n method: string;\n params:\n | R2EgressParams\n | S3CredentialProxyParams\n | GitAuthInterceptorParams;\n }\n >;\n };\n }) => Fetcher;\n };\n container?: {\n interceptOutboundHttp(host: string, fetcher: Fetcher): Promise<void>;\n };\n};\n\nexport interface MountOutboundHost {\n ctx: EgressContainerState;\n constructorRef: Function;\n enableInternet?: boolean;\n logger: Logger;\n setOutboundByHost<T>(host: string, method: string, params: T): Promise<void>;\n removeOutboundByHost(host: string): Promise<void>;\n}\n\nfunction isFetcher(value: unknown): value is Fetcher {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'fetch' in value &&\n typeof value.fetch === 'function'\n );\n}\n\nexport async function configureR2EgressOutbound(\n host: MountOutboundHost,\n params: R2EgressParams\n): Promise<void> {\n const ctx = host.ctx;\n if (!ctx.container?.interceptOutboundHttp) {\n throw new InvalidMountConfigError(\n 'R2 binding mounts require container outbound interception support'\n );\n }\n if (!ctx.exports?.ContainerProxy) {\n throw new InvalidMountConfigError(\n 'R2 binding mounts require exporting ContainerProxy from the Worker entrypoint'\n );\n }\n\n installSDKOutboundHandlers(host.constructorRef);\n if (Object.keys(params.buckets).length > 0) {\n await host.setOutboundByHost<R2EgressParams>(\n 'r2.internal',\n 'r2EgressMount',\n params\n );\n } else {\n await host.removeOutboundByHost('r2.internal');\n }\n\n host.logger.debug('r2 egress: registering host interception', {\n host: 'r2.internal',\n method: 'r2EgressMount',\n targetClassName: CONTAINER_PROXY_CLASS_NAME\n });\n\n const fetcher = ctx.exports.ContainerProxy({\n props: {\n enableInternet: host.enableInternet,\n containerId: ctx.id.toString(),\n className: CONTAINER_PROXY_CLASS_NAME,\n outboundByHostOverrides: {\n 'r2.internal': {\n method: 'r2EgressMount',\n params\n }\n }\n }\n });\n if (!isFetcher(fetcher)) {\n throw new InvalidMountConfigError(\n 'R2 binding mounts require ContainerProxy to return a valid Fetcher'\n );\n }\n\n await ctx.container.interceptOutboundHttp('r2.internal', fetcher);\n}\n\nexport async function configureGitAuthInterceptor(\n host: MountOutboundHost,\n params: GitAuthInterceptorParams\n): Promise<void> {\n const ctx = host.ctx;\n if (!ctx.container?.interceptOutboundHttp) {\n throw new InvalidMountConfigError(\n 'Git extension authentication requires container outbound interception support'\n );\n }\n if (!ctx.exports?.ContainerProxy) {\n throw new InvalidMountConfigError(\n 'Git extension authentication requires exporting ContainerProxy from the Worker entrypoint. Import ContainerProxy from @cloudflare/sandbox and export it from your Worker to use git auth interception.'\n );\n }\n\n installSDKOutboundHandlers(host.constructorRef);\n\n const hostOverrides: Record<\n string,\n { method: string; params: GitAuthInterceptorParams }\n > = {};\n for (const outboundHost of Object.keys(params.hosts)) {\n hostOverrides[outboundHost] = { method: 'gitCredentialProxy', params };\n await host.setOutboundByHost<GitAuthInterceptorParams>(\n outboundHost,\n 'gitCredentialProxy',\n params\n );\n }\n\n const fetcher = ctx.exports.ContainerProxy({\n props: {\n enableInternet: host.enableInternet,\n containerId: ctx.id.toString(),\n className: CONTAINER_PROXY_CLASS_NAME,\n outboundByHostOverrides: hostOverrides\n }\n });\n if (!isFetcher(fetcher)) {\n throw new InvalidMountConfigError(\n 'Git extension authentication requires ContainerProxy to return a valid Fetcher'\n );\n }\n\n for (const outboundHost of Object.keys(params.hosts)) {\n await ctx.container.interceptOutboundHttp(outboundHost, fetcher);\n }\n}\n\nexport async function configureS3CredentialProxyOutbound(\n host: MountOutboundHost,\n params: S3CredentialProxyParams\n): Promise<void> {\n const ctx = host.ctx;\n if (!ctx.container?.interceptOutboundHttp) {\n throw new InvalidMountConfigError(\n 'Credential proxy bucket mounts require container outbound interception support'\n );\n }\n if (!ctx.exports?.ContainerProxy) {\n throw new InvalidMountConfigError(\n 'Credential proxy bucket mounts require exporting ContainerProxy from the Worker entrypoint'\n );\n }\n\n const hosts = [S3_CREDENTIAL_PROXY_HOST, S3_CREDENTIAL_PROXY_DIAGNOSTIC_HOST];\n\n installSDKOutboundHandlers(host.constructorRef);\n if (Object.keys(params.mounts).length > 0) {\n for (const outboundHost of hosts) {\n await host.setOutboundByHost<S3CredentialProxyParams>(\n outboundHost,\n 's3CredentialProxyMount',\n params\n );\n }\n } else {\n for (const outboundHost of hosts) {\n await host.removeOutboundByHost(outboundHost);\n }\n }\n\n const hostOverrides: Record<\n string,\n { method: string; params: S3CredentialProxyParams }\n > = {};\n for (const outboundHost of hosts) {\n hostOverrides[outboundHost] = {\n method: 's3CredentialProxyMount',\n params\n };\n }\n\n host.logger.debug('s3 credential proxy: registering host interception', {\n hosts,\n method: 's3CredentialProxyMount',\n targetClassName: CONTAINER_PROXY_CLASS_NAME\n });\n\n const fetcher = ctx.exports.ContainerProxy({\n props: {\n enableInternet: host.enableInternet,\n containerId: ctx.id.toString(),\n className: CONTAINER_PROXY_CLASS_NAME,\n outboundByHostOverrides: hostOverrides\n }\n });\n if (!isFetcher(fetcher)) {\n throw new InvalidMountConfigError(\n 'Credential proxy bucket mounts require ContainerProxy to return a valid Fetcher'\n );\n }\n\n try {\n const selfTest = await fetcher.fetch(\n new Request(\n `http://${S3_CREDENTIAL_PROXY_HOST}${S3_CREDENTIAL_PROXY_SELF_TEST_PATH}`\n )\n );\n await selfTest.text();\n host.logger.debug('s3 credential proxy: fetcher self-test complete', {\n status: selfTest.status\n });\n } catch (error) {\n host.logger.warn('s3 credential proxy: fetcher self-test failed', {\n error: error instanceof Error ? error.message : String(error)\n });\n }\n\n for (const outboundHost of hosts) {\n await ctx.container.interceptOutboundHttp(outboundHost, fetcher);\n }\n}\n","import type { MountInfo } from './types';\n\nexport class MountRegistry {\n private readonly mounts = new Map<string, MountInfo>();\n\n get activeMounts(): Map<string, MountInfo> {\n return this.mounts;\n }\n\n has(mountPath: string): boolean {\n return this.mounts.has(mountPath);\n }\n\n get(mountPath: string): MountInfo | undefined {\n return this.mounts.get(mountPath);\n }\n\n set(mountPath: string, mountInfo: MountInfo): void {\n this.mounts.set(mountPath, mountInfo);\n }\n\n delete(mountPath: string): void {\n this.mounts.delete(mountPath);\n }\n\n clear(): void {\n this.mounts.clear();\n }\n\n entries(): IterableIterator<[string, MountInfo]> {\n return this.mounts.entries();\n }\n\n [Symbol.iterator](): IterableIterator<[string, MountInfo]> {\n return this.mounts.entries();\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 type { BucketCredentials } from '@repo/shared';\nimport { shellEscape } from '@repo/shared';\nimport type { S3FSHost } from './host';\n\nconst S3FS_DISABLE_EXPECT_HEADER_CONFIG = ' Expect:\\n';\n\nexport function generatePasswordFilePath(): string {\n return `/tmp/.passwd-s3fs-${crypto.randomUUID()}`;\n}\n\nexport function generateS3FSAdditionalHeaderFilePath(): string {\n return `/tmp/.s3fs-ahbe-${crypto.randomUUID()}.conf`;\n}\n\nexport async function createDisableExpectHeaderFile(\n host: S3FSHost,\n headerFilePath: string\n): Promise<void> {\n await host.client.files.writeFile(\n headerFilePath,\n S3FS_DISABLE_EXPECT_HEADER_CONFIG\n );\n await host.execInternal(`chmod 0600 ${shellEscape(headerFilePath)}`);\n}\n\nexport async function createPasswordFile(\n host: S3FSHost,\n passwordFilePath: string,\n bucket: string,\n credentials: BucketCredentials\n): Promise<void> {\n const content = `${bucket}:${credentials.accessKeyId}:${credentials.secretAccessKey}`;\n await host.client.files.writeFile(passwordFilePath, content);\n await host.execInternal(`chmod 0600 ${shellEscape(passwordFilePath)}`);\n}\n\nexport async function deletePasswordFile(\n host: S3FSHost,\n passwordFilePath: string\n): Promise<void> {\n try {\n await host.execInternal(`rm -f ${shellEscape(passwordFilePath)}`);\n } catch (error) {\n host.logger.warn('password file cleanup failed', {\n passwordFilePath,\n error: error instanceof Error ? error.message : String(error)\n });\n }\n}\n\nexport async function deleteAdditionalHeaderFile(\n host: S3FSHost,\n headerFilePath: string\n): Promise<void> {\n try {\n await host.execInternal(`rm -f ${shellEscape(headerFilePath)}`);\n } catch (error) {\n host.logger.warn('s3fs additional header file cleanup failed', {\n headerFilePath,\n error: error instanceof Error ? error.message : String(error)\n });\n }\n}\n","import type { BucketProvider, RemoteMountBucketOptions } from '@repo/shared';\nimport { shellEscape } from '@repo/shared';\nimport { InvalidMountConfigError, S3FSMountError } from '../errors';\nimport type { FuseMountInfo, R2BindingMountInfo } from '../types';\nimport { resolveS3fsOptions } from '../validation/provider';\n\nexport type { S3FSHost } from './host';\n\nimport type { S3FSHost } from './host';\n\nexport {\n createDisableExpectHeaderFile,\n createPasswordFile,\n deleteAdditionalHeaderFile,\n deletePasswordFile,\n generatePasswordFilePath,\n generateS3FSAdditionalHeaderFilePath\n} from './support-files';\nexport const R2_DEFAULT_S3FS_OPTIONS: Readonly<\n Record<string, string | boolean>\n> = {\n stat_cache_expire: '60',\n enable_noobj_cache: true,\n multipart_size: '5'\n};\n\nexport const R2_DEFAULT_S3FS_OPTION_ENTRIES = Object.entries(\n R2_DEFAULT_S3FS_OPTIONS\n).map(([key, value]) => (value === true ? key : `${key}=${value}`));\n\nexport function sh(\n strings: TemplateStringsArray,\n ...values: unknown[]\n): string {\n let out = strings[0];\n for (let i = 0; i < values.length; i++) {\n out += shellEscape(String(values[i])) + strings[i + 1];\n }\n return out;\n}\n\nexport function randomHex(bytes: number): string {\n const buf = new Uint8Array(bytes);\n crypto.getRandomValues(buf);\n return Array.from(buf, (b) => b.toString(16).padStart(2, '0')).join('');\n}\n\nexport function parseS3fsOptions(\n entries: string[]\n): Record<string, string | boolean> {\n const result: Record<string, string | boolean> = {};\n for (const entry of entries) {\n const eq = entry.indexOf('=');\n if (eq === -1) {\n result[entry] = true;\n } else {\n result[entry.slice(0, eq)] = entry.slice(eq + 1);\n }\n }\n return result;\n}\n\nexport function serializeS3fsOptions(\n options: Record<string, string | boolean>\n): string {\n return Object.entries(options)\n .filter(([, value]) => value !== false)\n .map(([key, value]) => (value === true ? key : `${key}=${value}`))\n .join(',');\n}\n\nexport function validateProtectedS3fsOptions(\n options: string[] | undefined,\n mountLabel: string,\n extraProtected: string[] = []\n): void {\n if (!options) return;\n const protectedOptions = new Set(['passwd_file', 'url', ...extraProtected]);\n for (const option of options) {\n const [key] = option.split('=');\n if (protectedOptions.has(key)) {\n throw new InvalidMountConfigError(\n `s3fs option \"${key}\" cannot be overridden for ${mountLabel} mounts`\n );\n }\n }\n}\n\nexport async function executeS3FSMount(\n host: S3FSHost,\n params: {\n bucket: string;\n mountPath: string;\n options: RemoteMountBucketOptions;\n provider: BucketProvider | null;\n passwordFilePath: string;\n sessionId?: string;\n }\n): Promise<void> {\n const logSuffix = randomHex(4);\n const sdkDefaults: Record<string, string | boolean> = {\n logfile: `/tmp/.s3fs-log-${logSuffix}`\n };\n const s3fsOptions: Record<string, string | boolean> = {\n ...sdkDefaults,\n ...parseS3fsOptions(resolveS3fsOptions(params.provider)),\n ...parseS3fsOptions(params.options.s3fsOptions ?? []),\n passwd_file: params.passwordFilePath,\n url: params.options.endpoint,\n ...(params.options.readOnly ? { ro: true } : {})\n };\n const logFile = s3fsOptions.logfile as string;\n const optionsStr = serializeS3fsOptions(s3fsOptions);\n\n const script = sh`(\n s3fs ${params.bucket} ${params.mountPath} -o ${optionsStr} >${logFile} 2>&1\n rc=$?\n if [ \"$rc\" -ne 0 ]; then tail -n 20 ${logFile} 2>/dev/null || true; exit 2; fi\n for _ in $(seq 1 60); do\n if mountpoint -q ${params.mountPath}; then exit 0; fi\n sleep 0.1\n done\n tail -n 20 ${logFile} 2>/dev/null || true\n exit 3\n )`;\n\n const exec =\n params.sessionId && host.executeCommand\n ? (command: string) =>\n host.executeCommand!(command, params.sessionId!, {\n origin: 'internal'\n })\n : (command: string) => host.execInternal(command);\n\n const result = await exec(script);\n if (result.exitCode === 0) return;\n\n const detail = result.stdout?.trim() || result.stderr?.trim() || '';\n if (result.exitCode === 2) {\n throw new S3FSMountError(`S3FS mount failed: ${detail || 'Unknown error'}`);\n }\n\n const diagMessage = detail\n ? `s3fs log: ${detail}`\n : 'No s3fs log output captured. The s3fs daemon may have exited before writing logs.';\n throw new S3FSMountError(\n `S3FS mount failed: FUSE filesystem never appeared at ${params.mountPath}. ${diagMessage}`\n );\n}\n\nexport async function unmountTrackedFuseMount(\n host: S3FSHost,\n mountPath: string,\n mountInfo: FuseMountInfo | R2BindingMountInfo\n): Promise<boolean> {\n if (!mountInfo.mounted) return true;\n\n host.logger.debug(`Unmounting bucket ${mountInfo.bucket} from ${mountPath}`);\n const result = await host.execInternal(\n `fusermount -u ${shellEscape(mountPath)}`\n );\n if (result.exitCode !== 0) {\n throw new Error(\n `fusermount -u failed (exit ${result.exitCode}): ${result.stderr || 'unknown error'}`\n );\n }\n mountInfo.mounted = false;\n return true;\n}\n","import type {\n FuseMountInfo,\n MountInfo,\n S3CredentialProxyParams\n} from '../types';\nimport type { R2EgressParams } from './r2-egress-handler';\n\nfunction hasExcludedID(\n excludedIDs: Iterable<string> | undefined,\n mountId: string\n): boolean {\n if (!excludedIDs) return false;\n for (const excludedID of excludedIDs) {\n if (excludedID === mountId) return true;\n }\n return false;\n}\n\nexport function buildR2EgressParams(\n mounts: Iterable<[string, MountInfo]>,\n options?: {\n excludeMountId?: string;\n excludeMountIds?: Iterable<string>;\n }\n): R2EgressParams {\n const buckets: R2EgressParams['buckets'] = {};\n for (const [, mountInfo] of mounts) {\n if (\n mountInfo.mountType === 'r2-egress' &&\n mountInfo.mountId !== options?.excludeMountId &&\n !hasExcludedID(options?.excludeMountIds, mountInfo.mountId)\n ) {\n buckets[mountInfo.bucket] = {\n prefix: mountInfo.prefix,\n readOnly: mountInfo.readOnly\n };\n }\n }\n return { buckets };\n}\n\nexport function buildS3CredentialProxyParams(\n mounts: Iterable<[string, MountInfo]>,\n options?: {\n excludeMountId?: string;\n excludeMountIds?: Iterable<string>;\n includeMount?: FuseMountInfo;\n }\n): S3CredentialProxyParams {\n const params: S3CredentialProxyParams['mounts'] = {};\n const addMount = (mountInfo: FuseMountInfo) => {\n if (\n !mountInfo.credentialProxy ||\n mountInfo.mountId === options?.excludeMountId ||\n hasExcludedID(options?.excludeMountIds, mountInfo.mountId)\n ) {\n return;\n }\n params[mountInfo.mountId] = {\n endpoint: mountInfo.credentialProxy.endpoint,\n bucket: mountInfo.credentialProxy.bucket,\n ...(mountInfo.credentialProxy.prefix !== undefined\n ? { prefix: mountInfo.credentialProxy.prefix }\n : {}),\n credentials: mountInfo.credentialProxy.credentials,\n readOnly: mountInfo.credentialProxy.readOnly,\n provider: mountInfo.credentialProxy.provider,\n authStrategy: mountInfo.credentialProxy.authStrategy\n };\n };\n\n for (const [, mountInfo] of mounts) {\n if (mountInfo.mountType === 'fuse') addMount(mountInfo);\n }\n if (options?.includeMount) addMount(options.includeMount);\n return { mounts: params };\n}\n","import type { Logger } from '@repo/shared';\nimport {\n configureR2EgressOutbound,\n configureS3CredentialProxyOutbound,\n type MountOutboundHost\n} from './outbound';\nimport {\n buildR2EgressParams,\n buildS3CredentialProxyParams\n} from './outbound/params';\nimport {\n evictDirectoryMarkerCacheForMount,\n evictSigV4ClientCacheEntry\n} from './outbound/s3-credential-proxy-handler';\nimport type { MountRegistry } from './registry';\nimport {\n deleteAdditionalHeaderFile,\n deletePasswordFile,\n type S3FSHost,\n unmountTrackedFuseMount\n} from './s3fs';\n\nexport interface BucketMountDestroyCleanupResult {\n mountsProcessed: number;\n mountFailures: number;\n}\n\nexport interface BucketMountLifecycleCleanupHost {\n registry: MountRegistry;\n logger: Logger;\n getS3FSHost(): S3FSHost;\n getOutboundHost(): MountOutboundHost;\n runMountOperation<T>(operation: () => Promise<T>): Promise<T>;\n}\n\nexport async function cleanupBucketMountsForDestroy(\n host: BucketMountLifecycleCleanupHost\n): Promise<BucketMountDestroyCleanupResult> {\n return host.runMountOperation(async () => {\n let mountsProcessed = 0;\n let mountFailures = 0;\n\n const cleanedMountPaths: string[] = [];\n const cleanedMountIds: string[] = [];\n let hadR2EgressMount = false;\n let hadCredentialProxyMount = false;\n\n for (const [mountPath, mountInfo] of host.registry.entries()) {\n mountsProcessed++;\n if (mountInfo.mountType === 'local-sync') {\n try {\n await mountInfo.syncManager.stop();\n mountInfo.mounted = false;\n cleanedMountPaths.push(mountPath);\n cleanedMountIds.push(mountInfo.mountId);\n } catch (error) {\n mountFailures++;\n host.logger.warn(\n `Failed to stop local sync for ${mountPath}: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n } else {\n if (mountInfo.mountType === 'r2-egress') {\n hadR2EgressMount = true;\n } else if (\n mountInfo.mountType === 'fuse' &&\n mountInfo.credentialProxy\n ) {\n hadCredentialProxyMount = true;\n }\n\n let supportFilesSafeToDelete = false;\n try {\n supportFilesSafeToDelete = await unmountTrackedFuseMount(\n host.getS3FSHost(),\n mountPath,\n mountInfo\n );\n } catch (error) {\n mountFailures++;\n host.logger.warn(\n `Failed to unmount bucket ${mountInfo.bucket} from ${mountPath}: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n if (supportFilesSafeToDelete) {\n await deletePasswordFile(\n host.getS3FSHost(),\n mountInfo.passwordFilePath\n );\n if (mountInfo.additionalHeaderFilePath) {\n await deleteAdditionalHeaderFile(\n host.getS3FSHost(),\n mountInfo.additionalHeaderFilePath\n );\n }\n cleanedMountPaths.push(mountPath);\n cleanedMountIds.push(mountInfo.mountId);\n\n if (mountInfo.mountType === 'fuse' && mountInfo.credentialProxy) {\n evictSigV4ClientCacheEntry(mountInfo.mountId);\n evictDirectoryMarkerCacheForMount(mountInfo.mountId);\n }\n }\n }\n }\n\n if (hadR2EgressMount) {\n try {\n await configureR2EgressOutbound(\n host.getOutboundHost(),\n buildR2EgressParams(host.registry, {\n excludeMountIds: cleanedMountIds\n })\n );\n } catch (error) {\n mountFailures++;\n host.logger.warn(\n `Failed to update R2 egress outbound configuration during destroy cleanup: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n if (hadCredentialProxyMount) {\n try {\n await configureS3CredentialProxyOutbound(\n host.getOutboundHost(),\n buildS3CredentialProxyParams(host.registry, {\n excludeMountIds: cleanedMountIds\n })\n );\n } catch (error) {\n mountFailures++;\n host.logger.warn(\n `Failed to update credential proxy outbound configuration during destroy cleanup: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n if (mountFailures === 0) {\n for (const mountPath of cleanedMountPaths) {\n host.registry.delete(mountPath);\n }\n }\n\n return { mountsProcessed, mountFailures };\n });\n}\n\nexport async function cleanupBucketMountsForStop(\n host: BucketMountLifecycleCleanupHost\n): Promise<void> {\n return host.runMountOperation(async () => {\n let hadR2EgressMount = false;\n let hadCredentialProxyMount = false;\n for (const [, mountInfo] of host.registry) {\n if (mountInfo.mountType === 'local-sync') {\n await mountInfo.syncManager.stop().catch(() => {});\n } else if (mountInfo.mountType === 'r2-egress') {\n hadR2EgressMount = true;\n } else if (mountInfo.mountType === 'fuse' && mountInfo.credentialProxy) {\n hadCredentialProxyMount = true;\n evictSigV4ClientCacheEntry(mountInfo.mountId);\n evictDirectoryMarkerCacheForMount(mountInfo.mountId);\n }\n }\n if (hadR2EgressMount) {\n await configureR2EgressOutbound(host.getOutboundHost(), {\n buckets: {}\n }).catch(() => {});\n }\n if (hadCredentialProxyMount) {\n await configureS3CredentialProxyOutbound(host.getOutboundHost(), {\n mounts: {}\n }).catch(() => {});\n }\n host.registry.clear();\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","import path from 'node:path/posix';\nimport type { FileWatchSSEEvent, Logger } from '@repo/shared';\nimport type { ContainerControlClient } from './container-control';\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: ContainerControlClient;\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: ContainerControlClient;\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.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, {\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);\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, {\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, {\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 });\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(containerPath, {\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 type { LocalMountBucketOptions } from '@repo/shared';\nimport { logCanonicalEvent } from '@repo/shared';\nimport type { ContainerControlClient } from '../../container-control';\nimport { LocalMountSyncManager } from '../../local-mount-sync';\nimport { InvalidMountConfigError } from '../errors';\nimport type { MountLifecycle } from '../lifecycle';\nimport type { MountRegistry } from '../registry';\nimport type { LocalSyncMountInfo } from '../types';\nimport { isR2Bucket } from '../validation';\nimport type { BucketMountOperationContext } from './context';\n\nexport interface LocalSyncMountContext extends BucketMountOperationContext {\n getEnv(): unknown;\n getClient(): ContainerControlClient;\n lifecycle: MountLifecycle;\n}\n\nexport async function mountLocalSyncBucket(\n context: LocalSyncMountContext,\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 try {\n const envObj = context.getEnv() 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 (context.registry.has(mountPath)) {\n throw new InvalidMountConfigError(\n `Mount path already in use: ${mountPath}`\n );\n }\n\n const syncManager = new LocalMountSyncManager({\n bucket: r2Binding,\n mountPath,\n prefix: options.prefix,\n readOnly: options.readOnly ?? false,\n client: context.getClient(),\n logger: context.logger\n });\n\n const mountInfo: LocalSyncMountInfo = {\n mountId: crypto.randomUUID(),\n mountType: 'local-sync',\n bucket,\n mountPath,\n syncManager,\n mounted: false\n };\n const lifecycle = await context.lifecycle.capture();\n try {\n await syncManager.start();\n await context.lifecycle.assertCurrent(lifecycle);\n mountInfo.mounted = true;\n context.registry.set(mountPath, mountInfo);\n } catch (error) {\n await syncManager.stop();\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(context.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","import { shellEscape } from '@repo/shared';\nimport type { BucketMountOperationContext } from './context';\n\nexport async function unmountFuseIfMountedForCleanup(\n context: BucketMountOperationContext,\n mountPath: string\n): Promise<boolean> {\n const result = await context.execInternal(\n `if mountpoint -q ${shellEscape(mountPath)}; then fusermount -u ${shellEscape(mountPath)}; else exit 0; fi`\n );\n if (result.exitCode === 0) return true;\n\n context.logger.warn('FUSE mount cleanup unmount failed', {\n mountPath,\n exitCode: result.exitCode,\n stderr: result.stderr\n });\n return false;\n}\n","import type { R2BindingMountBucketOptions } from '@repo/shared';\nimport { logCanonicalEvent, shellEscape } from '@repo/shared';\nimport { InvalidMountConfigError } from '../errors';\nimport type { MountLifecycle } from '../lifecycle';\nimport { configureR2EgressOutbound } from '../outbound';\nimport { buildR2EgressParams } from '../outbound/params';\nimport {\n createDisableExpectHeaderFile,\n createPasswordFile,\n deleteAdditionalHeaderFile,\n deletePasswordFile,\n executeS3FSMount,\n generatePasswordFilePath,\n generateS3FSAdditionalHeaderFilePath,\n R2_DEFAULT_S3FS_OPTION_ENTRIES,\n validateProtectedS3fsOptions\n} from '../s3fs';\nimport type { R2BindingMountInfo } from '../types';\nimport { validateBucketBindingName, validateMountPath } from '../validation';\nimport type { BucketMountOperationContext } from './context';\nimport { unmountFuseIfMountedForCleanup } from './fuse-cleanup';\n\nexport interface R2EgressMountContext extends BucketMountOperationContext {\n lifecycle: MountLifecycle;\n}\n\nexport async function mountR2EgressBucket(\n context: R2EgressMountContext,\n bucket: string,\n mountPath: string,\n options: R2BindingMountBucketOptions\n): Promise<void> {\n const mountStartTime = Date.now();\n const prefix = options.prefix;\n let mountOutcome: 'success' | 'error' = 'error';\n let mountError: Error | undefined;\n let passwordFilePath: string | undefined;\n let additionalHeaderFilePath: string | undefined;\n let s3fsStarted = false;\n let mountInfo: R2BindingMountInfo | undefined;\n\n try {\n validateBucketBindingName(bucket, mountPath);\n validateMountPath(context.registry.activeMounts, mountPath);\n validateProtectedS3fsOptions(options.s3fsOptions, 'R2 binding');\n\n for (const [existingMountPath, existingMount] of context.registry) {\n if (\n existingMount.mountType === 'r2-egress' &&\n existingMount.bucket === bucket &&\n existingMount.prefix !== prefix\n ) {\n throw new InvalidMountConfigError(\n `R2 binding \"${bucket}\" is already mounted at ${existingMountPath} with a different prefix. ` +\n 'Mount the same binding only once, or use the same prefix for additional mounts.'\n );\n }\n if (\n existingMount.mountType === 'r2-egress' &&\n existingMount.bucket === bucket &&\n existingMount.readOnly !== (options.readOnly ?? false)\n ) {\n throw new InvalidMountConfigError(\n `R2 binding \"${bucket}\" is already mounted at ${existingMountPath} with a different readOnly setting. ` +\n 'Mount the same binding only once, or use the same readOnly value for additional mounts.'\n );\n }\n }\n\n const lifecycle = await context.lifecycle.capture();\n passwordFilePath = generatePasswordFilePath();\n additionalHeaderFilePath = generateS3FSAdditionalHeaderFilePath();\n await createPasswordFile(context.getS3FSHost(), passwordFilePath, bucket, {\n accessKeyId: 'x',\n secretAccessKey: 'x'\n });\n await createDisableExpectHeaderFile(\n context.getS3FSHost(),\n additionalHeaderFilePath\n );\n\n mountInfo = {\n mountId: crypto.randomUUID(),\n mountType: 'r2-egress',\n bucket,\n mountPath,\n passwordFilePath,\n additionalHeaderFilePath,\n mounted: false,\n prefix,\n readOnly: options.readOnly ?? false\n };\n await configureR2EgressOutbound(context.getOutboundHost(), {\n buckets: {\n ...buildR2EgressParams(context.registry).buckets,\n [bucket]: {\n prefix,\n readOnly: options.readOnly ?? false\n }\n }\n });\n\n await context.execInternal(`mkdir -p ${shellEscape(mountPath)}`);\n\n s3fsStarted = true;\n await executeS3FSMount(context.getS3FSHost(), {\n bucket,\n mountPath,\n provider: 'r2',\n passwordFilePath,\n options: {\n ...options,\n endpoint: 'http://r2.internal',\n s3fsOptions: [\n ...R2_DEFAULT_S3FS_OPTION_ENTRIES,\n ...(options.s3fsOptions ?? []),\n 'use_path_request_style',\n `ahbe_conf=${additionalHeaderFilePath}`\n ]\n }\n });\n\n await context.lifecycle.assertCurrent(lifecycle);\n mountInfo.mounted = true;\n context.registry.set(mountPath, mountInfo);\n mountOutcome = 'success';\n } catch (error) {\n mountError = error instanceof Error ? error : new Error(String(error));\n const failedMount = context.registry.get(mountPath);\n context.registry.delete(mountPath);\n let supportFilesSafeToDelete = !s3fsStarted;\n if (s3fsStarted) {\n try {\n supportFilesSafeToDelete = await unmountFuseIfMountedForCleanup(\n context,\n mountPath\n );\n } catch (cleanupError) {\n context.logger.warn('R2 egress FUSE mount cleanup check failed', {\n mountPath,\n error:\n cleanupError instanceof Error\n ? cleanupError.message\n : String(cleanupError)\n });\n }\n }\n\n if (supportFilesSafeToDelete) {\n const cleanupPasswordFilePath =\n failedMount?.mountType === 'r2-egress'\n ? failedMount.passwordFilePath\n : passwordFilePath;\n const cleanupAdditionalHeaderFilePath =\n failedMount?.mountType === 'r2-egress'\n ? failedMount.additionalHeaderFilePath\n : additionalHeaderFilePath;\n\n if (cleanupPasswordFilePath) {\n await deletePasswordFile(\n context.getS3FSHost(),\n cleanupPasswordFilePath\n ).catch(() => {});\n }\n if (cleanupAdditionalHeaderFilePath) {\n await deleteAdditionalHeaderFile(\n context.getS3FSHost(),\n cleanupAdditionalHeaderFilePath\n ).catch(() => {});\n }\n }\n\n try {\n await configureR2EgressOutbound(\n context.getOutboundHost(),\n buildR2EgressParams(context.registry)\n );\n } catch (cleanupError) {\n if (mountInfo) {\n mountInfo.mounted = !supportFilesSafeToDelete;\n context.registry.set(mountPath, mountInfo);\n }\n throw new Error(\n `R2 egress mount failed and outbound cleanup failed: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}; original mount error: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n throw error;\n } finally {\n logCanonicalEvent(context.logger, {\n event: 'bucket.mount',\n outcome: mountOutcome,\n durationMs: Date.now() - mountStartTime,\n bucket,\n mountPath,\n provider: 'r2',\n prefix,\n error: mountError\n });\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","import type { BucketProvider, RemoteMountBucketOptions } from '@repo/shared';\nimport { getEnvString, logCanonicalEvent, shellEscape } from '@repo/shared';\nimport type { MountLifecycle } from '../lifecycle';\nimport { configureS3CredentialProxyOutbound } from '../outbound';\nimport { S3_CREDENTIAL_PROXY_HOST } from '../outbound/container-proxy';\nimport { buildS3CredentialProxyParams } from '../outbound/params';\nimport {\n evictDirectoryMarkerCacheForMount,\n evictSigV4ClientCacheEntry\n} from '../outbound/s3-credential-proxy-handler';\nimport {\n createDisableExpectHeaderFile,\n createPasswordFile,\n deleteAdditionalHeaderFile,\n deletePasswordFile,\n executeS3FSMount,\n generatePasswordFilePath,\n generateS3FSAdditionalHeaderFilePath,\n R2_DEFAULT_S3FS_OPTION_ENTRIES,\n validateProtectedS3fsOptions\n} from '../s3fs';\nimport type { CredentialProxyAuthStrategy, FuseMountInfo } from '../types';\nimport { buildS3fsSource, validateRemoteMountOptions } from '../validation';\nimport { detectCredentials } from '../validation/credentials';\nimport { detectProviderFromUrl } from '../validation/provider';\nimport type { BucketMountOperationContext } from './context';\nimport { unmountFuseIfMountedForCleanup } from './fuse-cleanup';\n\nexport interface RemoteFuseMountContext extends BucketMountOperationContext {\n getEnv(): unknown;\n getEnvVars(): Record<string, string>;\n getR2AccessKeyID(): string | null;\n getR2SecretAccessKey(): string | null;\n lifecycle: MountLifecycle;\n}\n\nexport async function mountRemoteFuseBucket(\n context: RemoteFuseMountContext,\n bucket: string,\n mountPath: string,\n options: RemoteMountBucketOptions\n): Promise<void> {\n const mountStartTime = Date.now();\n const prefix = options.prefix;\n let mountOutcome: 'success' | 'error' = 'error';\n let mountError: Error | undefined;\n let passwordFilePath: string | undefined;\n let additionalHeaderFilePath: string | undefined;\n let provider: BucketProvider | null = null;\n let dirExisted = true;\n let credentialProxyEnabled = false;\n let credentialProxyMountId: string | undefined;\n let mountInfo: FuseMountInfo | undefined;\n try {\n validateRemoteMountOptions(\n context.registry.activeMounts,\n bucket,\n mountPath,\n {\n ...options,\n prefix\n }\n );\n\n const s3fsSource = buildS3fsSource(bucket, prefix);\n provider = options.provider || detectProviderFromUrl(options.endpoint);\n\n context.logger.debug(`Detected provider: ${provider || 'unknown'}`, {\n explicitProvider: options.provider,\n prefix\n });\n\n const envObj = context.getEnv() 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: context.getR2AccessKeyID() || undefined,\n R2_SECRET_ACCESS_KEY: context.getR2SecretAccessKey() || undefined\n };\n const credentials = detectCredentials(options, {\n ...envCredentials,\n ...context.getEnvVars()\n });\n\n credentialProxyEnabled = options.credentialProxy === true;\n if (credentialProxyEnabled) {\n validateProtectedS3fsOptions(options.s3fsOptions, 'credential proxy', [\n 'ahbe_conf',\n 'use_path_request_style'\n ]);\n }\n\n passwordFilePath = generatePasswordFilePath();\n if (credentialProxyEnabled) {\n additionalHeaderFilePath = generateS3FSAdditionalHeaderFilePath();\n }\n\n const mountId = crypto.randomUUID();\n credentialProxyMountId = mountId;\n mountInfo = {\n mountId,\n mountType: 'fuse',\n bucket: s3fsSource,\n mountPath,\n endpoint: options.endpoint,\n provider,\n passwordFilePath,\n ...(additionalHeaderFilePath ? { additionalHeaderFilePath } : {}),\n mounted: false,\n ...(credentialProxyEnabled\n ? {\n credentialProxy: {\n endpoint: options.endpoint,\n bucket,\n ...(prefix !== undefined ? { prefix } : {}),\n credentials,\n readOnly: options.readOnly ?? false,\n provider,\n authStrategy: resolveCredentialProxyAuthStrategy(provider)\n }\n }\n : {})\n };\n const lifecycle = await context.lifecycle.capture();\n\n await createPasswordFile(\n context.getS3FSHost(),\n passwordFilePath,\n bucket,\n credentialProxyEnabled\n ? { accessKeyId: 'x', secretAccessKey: 'x' }\n : credentials\n );\n if (credentialProxyEnabled) {\n if (additionalHeaderFilePath) {\n await createDisableExpectHeaderFile(\n context.getS3FSHost(),\n additionalHeaderFilePath\n );\n }\n await configureS3CredentialProxyOutbound(\n context.getOutboundHost(),\n buildS3CredentialProxyParams(context.registry, {\n includeMount: mountInfo\n })\n );\n }\n\n dirExisted =\n (await context.execInternal(`test -d ${shellEscape(mountPath)}`))\n .exitCode === 0;\n await context.execInternal(`mkdir -p ${shellEscape(mountPath)}`);\n\n const effectiveOptions: RemoteMountBucketOptions = credentialProxyEnabled\n ? {\n ...options,\n endpoint: `http://${S3_CREDENTIAL_PROXY_HOST}/${mountId}`,\n s3fsOptions: [\n ...(provider === 'r2' ? R2_DEFAULT_S3FS_OPTION_ENTRIES : []),\n ...(options.s3fsOptions ?? []),\n ...(additionalHeaderFilePath\n ? [`ahbe_conf=${additionalHeaderFilePath}`]\n : []),\n 'use_path_request_style'\n ]\n }\n : options;\n await executeS3FSMount(context.getS3FSHost(), {\n bucket: s3fsSource,\n mountPath,\n options: effectiveOptions,\n provider,\n passwordFilePath\n });\n\n await context.lifecycle.assertCurrent(lifecycle);\n mountInfo.mounted = true;\n context.registry.set(mountPath, mountInfo);\n mountOutcome = 'success';\n } catch (error) {\n mountError = error instanceof Error ? error : new Error(String(error));\n let supportFilesSafeToDelete = false;\n try {\n supportFilesSafeToDelete = await unmountFuseIfMountedForCleanup(\n context,\n mountPath\n );\n } catch (cleanupError) {\n context.logger.warn('FUSE mount cleanup check failed', {\n mountPath,\n error:\n cleanupError instanceof Error\n ? cleanupError.message\n : String(cleanupError)\n });\n }\n\n if (supportFilesSafeToDelete) {\n if (passwordFilePath) {\n await deletePasswordFile(context.getS3FSHost(), passwordFilePath);\n }\n if (additionalHeaderFilePath) {\n await deleteAdditionalHeaderFile(\n context.getS3FSHost(),\n additionalHeaderFilePath\n );\n }\n }\n\n if (!dirExisted) {\n try {\n await context.execInternal(\n `rmdir ${shellEscape(mountPath)} 2>/dev/null`\n );\n } catch {\n // best-effort cleanup\n }\n }\n\n const failedMount = context.registry.get(mountPath);\n if (failedMount?.mountType === 'fuse' && failedMount.credentialProxy) {\n await configureS3CredentialProxyOutbound(\n context.getOutboundHost(),\n buildS3CredentialProxyParams(context.registry, {\n excludeMountId: failedMount.mountId\n })\n );\n context.registry.delete(mountPath);\n evictSigV4ClientCacheEntry(failedMount.mountId);\n evictDirectoryMarkerCacheForMount(failedMount.mountId);\n } else {\n if (credentialProxyEnabled) {\n try {\n await configureS3CredentialProxyOutbound(\n context.getOutboundHost(),\n buildS3CredentialProxyParams(context.registry)\n );\n } catch (cleanupError) {\n if (mountInfo) {\n mountInfo.mounted = !supportFilesSafeToDelete;\n context.registry.set(mountPath, mountInfo);\n }\n throw new Error(\n `Credential proxy mount failed and outbound cleanup failed: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}; original mount error: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n if (credentialProxyMountId) {\n evictSigV4ClientCacheEntry(credentialProxyMountId);\n evictDirectoryMarkerCacheForMount(credentialProxyMountId);\n }\n }\n context.registry.delete(mountPath);\n }\n throw error;\n } finally {\n logCanonicalEvent(context.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\nfunction resolveCredentialProxyAuthStrategy(\n provider: BucketProvider | null\n): CredentialProxyAuthStrategy {\n return provider === 'gcs' ? 'gcs' : 's3-sigv4';\n}\n","import { logCanonicalEvent, shellEscape } from '@repo/shared';\nimport { BucketUnmountError, InvalidMountConfigError } from '../errors';\nimport {\n configureR2EgressOutbound,\n configureS3CredentialProxyOutbound\n} from '../outbound';\nimport {\n buildR2EgressParams,\n buildS3CredentialProxyParams\n} from '../outbound/params';\nimport {\n evictDirectoryMarkerCacheForMount,\n evictSigV4ClientCacheEntry\n} from '../outbound/s3-credential-proxy-handler';\nimport { deleteAdditionalHeaderFile, deletePasswordFile } from '../s3fs';\nimport type { BucketMountOperationContext } from './context';\n\nexport async function unmountBucketOperation(\n context: BucketMountOperationContext,\n mountPath: string\n): Promise<void> {\n const unmountStartTime = Date.now();\n let unmountOutcome: 'success' | 'error' = 'error';\n let unmountError: Error | undefined;\n const mountInfo = context.registry.get(mountPath);\n\n try {\n if (!mountInfo) {\n throw new InvalidMountConfigError(\n `No active mount found at path: ${mountPath}`\n );\n }\n\n if (mountInfo.mountType === 'local-sync') {\n await mountInfo.syncManager.stop();\n mountInfo.mounted = false;\n context.registry.delete(mountPath);\n } else {\n let unmounted = !mountInfo.mounted;\n if (mountInfo.mounted) {\n const result = await context.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 unmounted = true;\n }\n\n if (mountInfo.mountType === 'r2-egress') {\n await configureR2EgressOutbound(\n context.getOutboundHost(),\n buildR2EgressParams(context.registry, {\n excludeMountId: mountInfo.mountId\n })\n );\n } else if (mountInfo.mountType === 'fuse' && mountInfo.credentialProxy) {\n await configureS3CredentialProxyOutbound(\n context.getOutboundHost(),\n buildS3CredentialProxyParams(context.registry, {\n excludeMountId: mountInfo.mountId\n })\n );\n evictSigV4ClientCacheEntry(mountInfo.mountId);\n evictDirectoryMarkerCacheForMount(mountInfo.mountId);\n }\n\n context.registry.delete(mountPath);\n\n try {\n const cleanup = await context.execInternal(\n `mountpoint -q ${shellEscape(mountPath)} || rmdir ${shellEscape(mountPath)}`\n );\n if (cleanup.exitCode !== 0) {\n context.logger.warn('mount directory removal failed', {\n mountPath,\n exitCode: cleanup.exitCode,\n stderr: cleanup.stderr\n });\n }\n } catch (err) {\n context.logger.warn('mount directory removal failed', {\n mountPath,\n error: err instanceof Error ? err.message : String(err)\n });\n }\n\n if (unmounted) {\n await deletePasswordFile(\n context.getS3FSHost(),\n mountInfo.passwordFilePath\n );\n if (mountInfo.additionalHeaderFilePath) {\n await deleteAdditionalHeaderFile(\n context.getS3FSHost(),\n mountInfo.additionalHeaderFilePath\n );\n }\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(context.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","import type {\n ExecResult,\n LocalMountBucketOptions,\n Logger,\n MountBucketOptions,\n R2BindingMountBucketOptions,\n RemoteMountBucketOptions\n} from '@repo/shared';\nimport type { ContainerControlClient } from '../container-control';\nimport type { CurrentRuntimeIdentity } from '../current-runtime-identity';\nimport type { CurrentSandboxLifetime } from '../sandbox-lifetime';\nimport { InvalidMountConfigError } from './errors';\nimport { MountLifecycle } from './lifecycle';\nimport {\n type BucketMountDestroyCleanupResult,\n cleanupBucketMountsForDestroy,\n cleanupBucketMountsForStop\n} from './lifecycle-cleanup';\nimport { MountOperationQueue } from './operation-queue';\nimport { mountLocalSyncBucket } from './operations/local-sync-mount';\nimport { mountR2EgressBucket } from './operations/r2-egress-mount';\nimport { mountRemoteFuseBucket } from './operations/remote-fuse-mount';\nimport { unmountBucketOperation } from './operations/unmount';\nimport type { MountOutboundHost } from './outbound';\nimport { MountRegistry } from './registry';\nimport type { S3FSHost } from './s3fs';\nimport { isR2Bucket, validateBucketName, validatePrefix } from './validation';\n\nexport interface BucketMountServiceDeps {\n getEnv(): unknown;\n getEnvVars(): Record<string, string>;\n getClient(): ContainerControlClient;\n logger: Logger;\n currentRuntime: CurrentRuntimeIdentity;\n currentLifetime: CurrentSandboxLifetime;\n getR2AccessKeyID(): string | null;\n getR2SecretAccessKey(): string | null;\n execInternal(command: string): Promise<ExecResult>;\n getOutboundHost(): MountOutboundHost;\n}\n\nexport class BucketMountService {\n private readonly registry = new MountRegistry();\n private readonly operations = new MountOperationQueue();\n private readonly lifecycle: MountLifecycle;\n\n constructor(private readonly deps: BucketMountServiceDeps) {\n this.lifecycle = new MountLifecycle(\n deps.currentRuntime,\n deps.currentLifetime\n );\n }\n\n private get client(): ContainerControlClient {\n return this.deps.getClient();\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 return this.operations.run(async () => {\n await this.mountBucketUnlocked(bucket, mountPath, options);\n });\n }\n\n private async mountBucketUnlocked(\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 const remoteOptions = options as RemoteMountBucketOptions;\n if (remoteOptions.endpoint === undefined) {\n const envObj = this.deps.getEnv() as Record<string, unknown>;\n const binding = envObj[bucket];\n if (isR2Bucket(binding)) {\n await this.mountBucketR2Egress(\n bucket,\n mountPath,\n options as R2BindingMountBucketOptions\n );\n return;\n }\n throw new InvalidMountConfigError(\n `R2 binding \"${bucket}\" not found in Worker env. ` +\n 'Ensure the binding name matches the bucket binding configured in wrangler.jsonc.'\n );\n }\n\n await this.mountBucketFuse(bucket, mountPath, remoteOptions);\n }\n\n private async mountBucketLocal(\n bucket: string,\n mountPath: string,\n options: LocalMountBucketOptions\n ): Promise<void> {\n await mountLocalSyncBucket(\n {\n registry: this.registry,\n logger: this.deps.logger,\n execInternal: (command) => this.deps.execInternal(command),\n getOutboundHost: () => this.deps.getOutboundHost(),\n getS3FSHost: () => this.getS3FSHost(),\n getEnv: () => this.deps.getEnv(),\n getClient: () => this.client,\n lifecycle: this.lifecycle\n },\n bucket,\n mountPath,\n options\n );\n }\n\n private getS3FSHost(): S3FSHost {\n return {\n client: this.client,\n logger: this.deps.logger,\n execInternal: (command: string) => this.deps.execInternal(command)\n };\n }\n\n private async mountBucketR2Egress(\n bucket: string,\n mountPath: string,\n options: R2BindingMountBucketOptions\n ): Promise<void> {\n await mountR2EgressBucket(\n {\n registry: this.registry,\n logger: this.deps.logger,\n execInternal: (command) => this.deps.execInternal(command),\n getOutboundHost: () => this.deps.getOutboundHost(),\n getS3FSHost: () => this.getS3FSHost(),\n lifecycle: this.lifecycle\n },\n bucket,\n mountPath,\n options\n );\n }\n\n private async mountBucketFuse(\n bucket: string,\n mountPath: string,\n options: RemoteMountBucketOptions\n ): Promise<void> {\n await mountRemoteFuseBucket(\n {\n registry: this.registry,\n logger: this.deps.logger,\n execInternal: (command) => this.deps.execInternal(command),\n getOutboundHost: () => this.deps.getOutboundHost(),\n getS3FSHost: () => this.getS3FSHost(),\n getEnv: () => this.deps.getEnv(),\n getEnvVars: () => this.deps.getEnvVars(),\n getR2AccessKeyID: () => this.deps.getR2AccessKeyID(),\n getR2SecretAccessKey: () => this.deps.getR2SecretAccessKey(),\n lifecycle: this.lifecycle\n },\n bucket,\n mountPath,\n options\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 return this.operations.run(async () => {\n await this.unmountBucketUnlocked(mountPath);\n });\n }\n\n private async unmountBucketUnlocked(mountPath: string): Promise<void> {\n await unmountBucketOperation(\n {\n registry: this.registry,\n logger: this.deps.logger,\n execInternal: (command) => this.deps.execInternal(command),\n getOutboundHost: () => this.deps.getOutboundHost(),\n getS3FSHost: () => this.getS3FSHost()\n },\n mountPath\n );\n }\n\n async cleanupForDestroy(): Promise<BucketMountDestroyCleanupResult> {\n return cleanupBucketMountsForDestroy({\n registry: this.registry,\n logger: this.deps.logger,\n getS3FSHost: () => this.getS3FSHost(),\n getOutboundHost: () => this.deps.getOutboundHost(),\n runMountOperation: (operation) => this.operations.run(operation)\n });\n }\n\n async cleanupForStop(): Promise<void> {\n return cleanupBucketMountsForStop({\n registry: this.registry,\n logger: this.deps.logger,\n getS3FSHost: () => this.getS3FSHost(),\n getOutboundHost: () => this.deps.getOutboundHost(),\n runMountOperation: (operation) => this.operations.run(operation)\n });\n }\n}\n","export const BACKUP_DEFAULT_TTL_SECONDS = 259200;\nexport const BACKUP_MAX_NAME_LENGTH = 256;\nexport const BACKUP_CONTAINER_DIR = '/var/backups';\nexport const BACKUP_STORAGE_PREFIX = 'backups';\nexport const BACKUP_ARCHIVE_OBJECT_NAME = 'data.sqsh';\nexport const BACKUP_METADATA_OBJECT_NAME = 'meta.json';\nexport const BACKUP_DEFAULT_COMPRESSION = 'lz4';\nexport const BACKUP_DEFAULT_COMPRESS_THREADS = 8;\nexport const BACKUP_MULTIPART_MIN_SIZE = 10 * 1024 * 1024;\nexport const BACKUP_MULTIPART_TARGET_PARTS = 16;\nexport const BACKUP_MULTIPART_MIN_PART_SIZE = 5 * 1024 * 1024;\nexport const BACKUP_MULTIPART_MAX_PARTS = 64;\nexport const BACKUP_DOWNLOAD_PARALLEL_PARTS = 8;\nexport const BACKUP_DOWNLOAD_PARALLEL_MIN_SIZE = 10 * 1024 * 1024;\nexport const BACKUP_DOWNLOAD_MAX_PARTS = 64;\nexport const BACKUP_RESTORE_MAX_RECOVERY_ATTEMPTS = 2;\n\nexport function calculatePartCount(\n sizeBytes: number,\n defaultParts: number,\n maxParts: number\n): number {\n if (sizeBytes < 100 * 1024 * 1024) {\n return defaultParts;\n }\n if (sizeBytes < 1024 * 1024 * 1024) {\n return Math.min(32, defaultParts * 2);\n }\n return maxParts;\n}\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\nexport function normalizeBackupExcludePattern(pattern: string): string | null {\n let normalized = pattern;\n\n while (normalized.startsWith('**/')) {\n normalized = normalized.slice(3);\n }\n\n while (normalized.includes('/**/')) {\n normalized = normalized.replace(/\\/\\*\\*\\//g, '/');\n }\n\n if (normalized.endsWith('/**')) {\n normalized = normalized.slice(0, -3);\n }\n\n if (!normalized || normalized === '**') {\n return null;\n }\n\n return normalized;\n}\n","import type { createLogger } from '@repo/shared';\nimport {\n BACKUP_ALLOWED_PREFIXES,\n normalizeBackupExcludePattern\n} from '@repo/shared/backup';\nimport { ErrorCode, InvalidBackupConfigError } from '../errors';\nimport {\n BACKUP_DEFAULT_COMPRESS_THREADS,\n BACKUP_DEFAULT_COMPRESSION\n} from './constants';\n\nexport function 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\nexport function normalizeBackupExcludes(\n excludes: string[],\n logger: ReturnType<typeof createLogger>\n): string[] {\n const normalizedExcludes: string[] = [];\n\n for (const pattern of excludes) {\n const normalized = normalizeBackupExcludePattern(pattern);\n if (normalized === null) {\n logger.warn(\n 'Exclude pattern reduced to empty after globstar normalization; skipping',\n { original: pattern }\n );\n continue;\n }\n if (normalized !== pattern) {\n logger.warn(\n 'Exclude pattern contained ** (globstar) which mksquashfs does not support; normalized automatically',\n { original: pattern, normalized }\n );\n }\n normalizedExcludes.push(normalized);\n }\n\n return normalizedExcludes;\n}\n\nexport function resolveBackupCompression(compression: unknown): {\n format: 'gzip' | 'lz4' | 'zstd';\n threads: number;\n} {\n if (compression !== undefined) {\n if (typeof compression !== 'object' || compression === null) {\n throw new InvalidBackupConfigError({\n message: 'BackupOptions.compression must be an object',\n code: ErrorCode.INVALID_BACKUP_CONFIG,\n httpStatus: 400,\n context: { reason: 'compression must be an object' },\n timestamp: new Date().toISOString()\n });\n }\n }\n\n const compressionOptions = compression as\n | { format?: unknown; threads?: unknown }\n | undefined;\n const format = compressionOptions?.format ?? BACKUP_DEFAULT_COMPRESSION;\n const threads =\n compressionOptions?.threads ?? BACKUP_DEFAULT_COMPRESS_THREADS;\n const allowedCompressions = ['gzip', 'lz4', 'zstd'];\n\n if (\n typeof format !== 'string' ||\n !allowedCompressions.includes(\n format as (typeof allowedCompressions)[number]\n )\n ) {\n throw new InvalidBackupConfigError({\n message:\n 'BackupOptions.compression.format must be one of: gzip, lz4, zstd',\n code: ErrorCode.INVALID_BACKUP_CONFIG,\n httpStatus: 400,\n context: {\n reason: 'compression.format must be one of: gzip, lz4, zstd'\n },\n timestamp: new Date().toISOString()\n });\n }\n\n if (\n typeof threads !== 'number' ||\n !Number.isInteger(threads) ||\n threads < 1\n ) {\n throw new InvalidBackupConfigError({\n message: 'BackupOptions.compression.threads must be a positive integer',\n code: ErrorCode.INVALID_BACKUP_CONFIG,\n httpStatus: 400,\n context: {\n reason: 'compression.threads must be a positive integer'\n },\n timestamp: new Date().toISOString()\n });\n }\n\n return {\n format: format as 'gzip' | 'lz4' | 'zstd',\n threads\n };\n}\n","import type {\n BackupOptions,\n DirectoryBackup,\n ExecOptions,\n ExecResult\n} from '@repo/shared';\nimport {\n type createLogger,\n logCanonicalEvent,\n shellEscape\n} from '@repo/shared';\nimport type { ContainerControlClient } from '../container-control';\nimport {\n BackupCreateError,\n ErrorCode,\n InvalidBackupConfigError\n} from '../errors';\nimport { streamFile } from '../file-stream';\nimport { isR2Bucket } from '../storage-mount';\nimport {\n BACKUP_ARCHIVE_OBJECT_NAME,\n BACKUP_CONTAINER_DIR,\n BACKUP_DEFAULT_TTL_SECONDS,\n BACKUP_MAX_NAME_LENGTH,\n BACKUP_METADATA_OBJECT_NAME,\n BACKUP_MULTIPART_MIN_SIZE,\n BACKUP_STORAGE_PREFIX\n} from './constants';\nimport type { BackupTransfer } from './transfer';\nimport {\n normalizeBackupExcludes,\n resolveBackupCompression,\n validateBackupDir\n} from './validation';\n\ntype BackupCreatorDeps = {\n getEnv: () => unknown;\n getClient: () => ContainerControlClient;\n logger: ReturnType<typeof createLogger>;\n transfer: BackupTransfer;\n ensureBackupSession: () => Promise<string>;\n execWithSession: (\n command: string,\n sessionId: string,\n options?: ExecOptions\n ) => Promise<ExecResult>;\n};\n\nexport class BackupCreator {\n constructor(private readonly deps: BackupCreatorDeps) {}\n\n private get env(): unknown {\n return this.deps.getEnv();\n }\n\n private get client(): ContainerControlClient {\n return this.deps.getClient();\n }\n\n private get logger(): ReturnType<typeof createLogger> {\n return this.deps.logger;\n }\n\n private get transfer(): BackupTransfer {\n return this.deps.transfer;\n }\n\n private ensureBackupSession(): Promise<string> {\n return this.deps.ensureBackupSession();\n }\n\n private execWithSession(\n command: string,\n sessionId: string,\n options?: ExecOptions\n ): Promise<ExecResult> {\n return this.deps.execWithSession(command, sessionId, options);\n }\n\n async createBackup(options: BackupOptions): Promise<DirectoryBackup> {\n if (options.localBucket) {\n return await this.doCreateBackupLocal(options);\n }\n this.transfer.requireBackupBucket();\n return await this.doCreateBackup(options);\n }\n\n private async doCreateBackup(\n options: BackupOptions\n ): Promise<DirectoryBackup> {\n const bucket = this.transfer.requireBackupBucket();\n this.transfer.requirePresignedURLSupport();\n const {\n dir,\n name,\n ttl = BACKUP_DEFAULT_TTL_SECONDS,\n gitignore = false,\n excludes = [],\n compression,\n multipart = true\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 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 const resolvedCompression = resolveBackupCompression(compression);\n\n const normalizedExcludes = normalizeBackupExcludes(excludes, this.logger);\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 {\n sessionId: backupSession,\n gitignore,\n excludes: normalizedExcludes,\n compression: resolvedCompression\n }\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\n if (multipart && createResult.sizeBytes >= BACKUP_MULTIPART_MIN_SIZE) {\n await this.transfer.uploadBackupMultipart(\n archivePath,\n r2Key,\n createResult.sizeBytes,\n backupId,\n dir,\n backupSession\n );\n } else {\n await this.transfer.uploadBackupPresigned(\n archivePath,\n r2Key,\n createResult.sizeBytes,\n backupId,\n dir,\n backupSession\n );\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 compression\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 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 const resolvedCompression = resolveBackupCompression(compression);\n\n const normalizedExcludes = normalizeBackupExcludes(excludes, this.logger);\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 {\n sessionId: backupSession,\n gitignore,\n excludes: normalizedExcludes,\n compression: resolvedCompression\n }\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 and stream it into R2 via binding.\n // readFileStream returns SSE-framed base64 chunks, so we pipe it through\n // streamFile (which decodes SSE frames + base64 on the fly) into a\n // FixedLengthStream backed by the known archive size. This avoids\n // buffering the whole archive in Worker memory.\n const archiveStream = await this.client.files.readFileStream(\n archivePath,\n { sessionId: backupSession }\n );\n const sseDecoded = new ReadableStream<Uint8Array>({\n async start(controller) {\n try {\n for await (const chunk of streamFile(archiveStream)) {\n if (chunk instanceof Uint8Array) {\n controller.enqueue(chunk);\n }\n }\n controller.close();\n } catch (err) {\n controller.error(err);\n }\n }\n });\n const fixedStream = new FixedLengthStream(createResult.sizeBytes);\n sseDecoded.pipeTo(fixedStream.writable).catch(() => {});\n await bucket.put(r2Key, fixedStream.readable);\n\n // Verify upload — size comes from createArchive result, not the stream.\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}\n","import type { BackupRestoreOperationRecord } from './restore-operation-store';\n\nexport type BackupRestoreFaultPhase = 'after_archive_ready';\n\nexport type BackupRestoreTestFault = {\n phase: BackupRestoreFaultPhase;\n mode: 'transport_disposed';\n times: number;\n};\n\nexport type BackupRestoreFaultDecision = {\n reason: 'transport_disposed';\n admitted: 'unknown';\n};\n\nexport interface BackupRestoreFaultInjector {\n maybeFault(\n phase: BackupRestoreFaultPhase,\n operation: BackupRestoreOperationRecord\n ): Promise<BackupRestoreFaultDecision | null>;\n}\n\nconst BACKUP_RESTORE_TEST_FAULT_STORAGE_KEY = 'test:backupRestoreFault';\n\nexport class StorageBackedBackupRestoreFaultInjector implements BackupRestoreFaultInjector {\n constructor(\n private readonly storage: DurableObjectStorage,\n private readonly getEnv: () => unknown\n ) {}\n\n async setFaultForTesting(\n fault: BackupRestoreTestFault | null\n ): Promise<void> {\n const envObj = this.getEnv() as Record<string, unknown>;\n if (envObj.SANDBOX_ENABLE_TEST_HOOKS !== 'true') {\n throw new Error('Sandbox test hooks are not enabled');\n }\n\n if (fault === null || fault.times <= 0) {\n await this.storage.delete(BACKUP_RESTORE_TEST_FAULT_STORAGE_KEY);\n return;\n }\n\n await this.storage.put(BACKUP_RESTORE_TEST_FAULT_STORAGE_KEY, fault);\n }\n\n async maybeFault(\n phase: BackupRestoreFaultPhase,\n _operation: BackupRestoreOperationRecord\n ): Promise<BackupRestoreFaultDecision | null> {\n const envObj = this.getEnv() as Record<string, unknown>;\n if (envObj.SANDBOX_ENABLE_TEST_HOOKS !== 'true') {\n return null;\n }\n\n const fault =\n (await this.storage.get<BackupRestoreTestFault>(\n BACKUP_RESTORE_TEST_FAULT_STORAGE_KEY\n )) ?? null;\n if (!fault || fault.phase !== phase || fault.times <= 0) {\n return null;\n }\n\n const nextTimes = fault.times - 1;\n if (nextTimes > 0) {\n await this.storage.put(BACKUP_RESTORE_TEST_FAULT_STORAGE_KEY, {\n ...fault,\n times: nextTimes\n });\n } else {\n await this.storage.delete(BACKUP_RESTORE_TEST_FAULT_STORAGE_KEY);\n }\n\n return {\n reason: fault.mode,\n admitted: 'unknown'\n };\n }\n}\n","export type RuntimeIdentityID = string & {\n readonly __runtimeIdentityID: unique symbol;\n};\n\nexport type RuntimeIdentityRecord = {\n id: RuntimeIdentityID;\n};\n\nexport type RuntimeScoped<T extends object> = T & {\n readonly runtimeIdentityID: RuntimeIdentityID;\n};\n\nexport type CurrentRuntimeStatus =\n | { status: 'active'; runtime: RuntimeIdentity; containerStatus: string }\n | {\n status: 'inactive';\n reason:\n | 'runtime-not-healthy'\n | 'runtime-not-running'\n | 'missing-runtime-id';\n containerStatus?: string;\n };\n\ntype RuntimeIdentityStorage = Pick<\n DurableObjectStorage | DurableObjectTransaction,\n 'get'\n>;\n\nconst CURRENT_RUNTIME_IDENTITY_STORAGE_KEY = 'currentRuntimeIdentity';\n\nexport class RuntimeIdentityInactiveError extends Error {\n constructor() {\n super('Runtime identity is no longer active');\n this.name = 'RuntimeIdentityInactiveError';\n }\n}\n\nexport class RuntimeIdentity {\n readonly id: RuntimeIdentityID;\n\n constructor(record: RuntimeIdentityRecord) {\n this.id = record.id;\n }\n\n owns(record: { readonly runtimeIdentityID: RuntimeIdentityID }): boolean {\n return record.runtimeIdentityID === this.id;\n }\n\n scope<T extends object>(value: T): RuntimeScoped<T> {\n return {\n ...value,\n runtimeIdentityID: this.id\n };\n }\n}\n\nexport class CurrentRuntimeIdentity {\n /**\n * Runtime identity is stored in Durable Object storage so a reconstructed DO\n * can still recognize the live container runtime it owns. In-memory state is\n * only a cache and cannot define runtime-scoped correctness.\n */\n constructor(\n private readonly storage: DurableObjectState['storage'],\n private readonly getContainerState: () => Promise<{ status: string }>,\n private readonly isContainerRunning: () => boolean\n ) {}\n\n async get(): Promise<RuntimeIdentity | null> {\n const status = await this.getStatus();\n return status.status === 'active' ? status.runtime : null;\n }\n\n async getStatus(): Promise<CurrentRuntimeStatus> {\n const state = await this.getContainerState();\n if (state.status !== 'healthy') {\n return {\n status: 'inactive',\n reason: 'runtime-not-healthy',\n containerStatus: state.status\n };\n }\n\n if (!this.isContainerRunning()) {\n return {\n status: 'inactive',\n reason: 'runtime-not-running',\n containerStatus: state.status\n };\n }\n\n const runtime = await this.getStored();\n if (!runtime) {\n return {\n status: 'inactive',\n reason: 'missing-runtime-id',\n containerStatus: state.status\n };\n }\n\n return {\n status: 'active',\n runtime,\n containerStatus: state.status\n };\n }\n\n async getStored(\n storage: RuntimeIdentityStorage = this.storage\n ): Promise<RuntimeIdentity | null> {\n const record =\n (await storage.get<RuntimeIdentityRecord>(\n CURRENT_RUNTIME_IDENTITY_STORAGE_KEY\n )) ?? null;\n return record ? new RuntimeIdentity(record) : null;\n }\n\n async markStarted(): Promise<RuntimeIdentity> {\n const record: RuntimeIdentityRecord = {\n id: crypto.randomUUID() as RuntimeIdentityID\n };\n await this.storage.put(CURRENT_RUNTIME_IDENTITY_STORAGE_KEY, record);\n return new RuntimeIdentity(record);\n }\n\n async clear(): Promise<void> {\n await this.storage.delete(CURRENT_RUNTIME_IDENTITY_STORAGE_KEY);\n }\n\n async isActive(runtime: RuntimeIdentity): Promise<boolean> {\n const current = await this.get();\n return current?.id === runtime.id;\n }\n\n async assertActive(runtime: RuntimeIdentity): Promise<void> {\n if (!(await this.isActive(runtime))) {\n throw new RuntimeIdentityInactiveError();\n }\n }\n}\n","export type SandboxLifetimeID = string & {\n readonly __sandboxLifetimeID: unique symbol;\n};\n\nexport type SandboxLifetimeRecord = {\n id: SandboxLifetimeID;\n generation: number;\n createdAt: string;\n updatedAt: string;\n};\n\ntype SandboxLifetimeStorage = Pick<\n DurableObjectStorage | DurableObjectTransaction,\n 'get' | 'put'\n>;\n\nconst SANDBOX_LIFETIME_STORAGE_KEY = 'sandbox:lifetime';\n\nexport class SandboxLifetimeChangedError extends Error {\n constructor() {\n super('Sandbox lifetime is no longer current');\n this.name = 'SandboxLifetimeChangedError';\n }\n}\n\nexport class SandboxLifetime {\n readonly id: SandboxLifetimeID;\n readonly generation: number;\n readonly createdAt: string;\n readonly updatedAt: string;\n\n constructor(readonly record: SandboxLifetimeRecord) {\n this.id = record.id;\n this.generation = record.generation;\n this.createdAt = record.createdAt;\n this.updatedAt = record.updatedAt;\n }\n\n owns(record: { readonly sandboxLifetimeID: SandboxLifetimeID }): boolean {\n return record.sandboxLifetimeID === this.id;\n }\n\n scope<T extends object>(\n value: T\n ): T & { sandboxLifetimeID: SandboxLifetimeID } {\n return {\n ...value,\n sandboxLifetimeID: this.id\n };\n }\n}\n\nexport class CurrentSandboxLifetime {\n constructor(private readonly storage: SandboxLifetimeStorage) {}\n\n async get(): Promise<SandboxLifetime | null> {\n const record =\n (await this.storage.get<SandboxLifetimeRecord>(\n SANDBOX_LIFETIME_STORAGE_KEY\n )) ?? null;\n return record ? new SandboxLifetime(record) : null;\n }\n\n async getOrCreate(): Promise<SandboxLifetime> {\n const existing = await this.get();\n if (existing) {\n return existing;\n }\n\n const now = new Date().toISOString();\n const record: SandboxLifetimeRecord = {\n id: crypto.randomUUID() as SandboxLifetimeID,\n generation: 1,\n createdAt: now,\n updatedAt: now\n };\n await this.storage.put(SANDBOX_LIFETIME_STORAGE_KEY, record);\n return new SandboxLifetime(record);\n }\n\n async rotate(): Promise<SandboxLifetime> {\n const existing = await this.get();\n const now = new Date().toISOString();\n const record: SandboxLifetimeRecord = {\n id: crypto.randomUUID() as SandboxLifetimeID,\n generation: (existing?.generation ?? 0) + 1,\n createdAt: now,\n updatedAt: now\n };\n await this.storage.put(SANDBOX_LIFETIME_STORAGE_KEY, record);\n return new SandboxLifetime(record);\n }\n\n async isCurrent(lifetime: SandboxLifetime): Promise<boolean> {\n const current = await this.get();\n return current?.id === lifetime.id;\n }\n\n async assertCurrent(lifetime: SandboxLifetime): Promise<void> {\n if (!(await this.isCurrent(lifetime))) {\n throw new SandboxLifetimeChangedError();\n }\n }\n}\n","import type { RuntimeIdentityID } from '../current-runtime-identity';\nimport type { SandboxLifetimeID } from '../sandbox-lifetime';\n\nexport type BackupRestoreOperationStatus =\n | 'running'\n | 'committed'\n | 'failed'\n | 'interrupted';\n\nexport type BackupRestoreOperationPhase =\n | 'validating'\n | 'runtime_ready'\n | 'archive_ready'\n | 'verified'\n | 'failed'\n | 'interrupted';\n\nexport type BackupRestoreOperationError = {\n code: string;\n message: string;\n retryable: boolean;\n};\n\nexport type BackupRestoreOperationPayload = {\n backupId: string;\n dir: string;\n archiveSize?: number;\n};\n\nexport type BackupRestoreOperationResult = {\n success: true;\n id: string;\n dir: string;\n};\n\nexport type BackupRestoreOperationRecord = {\n operationId: string;\n operationKey: string;\n kind: 'backup.restore';\n sandboxLifetimeID: SandboxLifetimeID;\n phase: BackupRestoreOperationPhase;\n status: BackupRestoreOperationStatus;\n runtimeIdentityID?: RuntimeIdentityID;\n payload: BackupRestoreOperationPayload;\n result?: BackupRestoreOperationResult;\n error?: BackupRestoreOperationError;\n /** The 1-based attempt number for this operation record. */\n attempt: number;\n createdAt: string;\n updatedAt: string;\n completedAt?: string;\n /** ISO timestamp of the most recent interruption, if any. */\n lastInterruptedAt?: string;\n};\n\ntype OperationRecordStorage = Pick<DurableObjectStorage, 'get' | 'put'>;\n\nconst OPERATION_STORAGE_PREFIX = 'operations:';\n\nexport function backupRestoreOperationKey(\n backupId: string,\n dir: string\n): string {\n return `restore:${backupId}:${dir}`;\n}\n\nexport function createBackupRestoreOperationRecord(params: {\n operationId: string;\n sandboxLifetimeID: SandboxLifetimeID;\n backupId: string;\n dir: string;\n now: string;\n}): BackupRestoreOperationRecord {\n return {\n operationId: params.operationId,\n operationKey: backupRestoreOperationKey(params.backupId, params.dir),\n kind: 'backup.restore',\n sandboxLifetimeID: params.sandboxLifetimeID,\n phase: 'validating',\n status: 'running',\n attempt: 1,\n payload: {\n backupId: params.backupId,\n dir: params.dir\n },\n createdAt: params.now,\n updatedAt: params.now\n };\n}\n\n/**\n * Produce a next-attempt record that resets phase and status while\n * preserving the operationId so callers can reconcile restore history.\n */\nexport function nextBackupRestoreAttempt(\n record: BackupRestoreOperationRecord,\n now: string\n): BackupRestoreOperationRecord {\n return {\n ...record,\n phase: 'validating',\n status: 'running',\n error: undefined,\n completedAt: undefined,\n updatedAt: now,\n attempt: (record.attempt ?? 0) + 1\n };\n}\n\nexport class BackupRestoreOperationStore {\n constructor(private readonly storage: OperationRecordStorage) {}\n\n async get(\n operationKey: string\n ): Promise<BackupRestoreOperationRecord | null> {\n return (\n (await this.storage.get<BackupRestoreOperationRecord>(\n this.storageKey(operationKey)\n )) ?? null\n );\n }\n\n async getCurrent(\n operationKey: string,\n sandboxLifetimeID: SandboxLifetimeID\n ): Promise<BackupRestoreOperationRecord | null> {\n const record = await this.get(operationKey);\n if (!record || record.sandboxLifetimeID !== sandboxLifetimeID) {\n return null;\n }\n return record;\n }\n\n async put(record: BackupRestoreOperationRecord): Promise<void> {\n await this.storage.put(this.storageKey(record.operationKey), record);\n }\n\n storageKey(operationKey: string): string {\n return `${OPERATION_STORAGE_PREFIX}${operationKey}`;\n }\n}\n","import type {\n CurrentRuntimeIdentity,\n RuntimeIdentity\n} from '../current-runtime-identity';\nimport { RuntimeIdentityInactiveError } from '../current-runtime-identity';\nimport {\n ErrorCode,\n OperationInterruptedError,\n RPCTransportError\n} from '../errors';\nimport type {\n CurrentSandboxLifetime,\n SandboxLifetime\n} from '../sandbox-lifetime';\nimport { SandboxLifetimeChangedError } from '../sandbox-lifetime';\nimport { BACKUP_RESTORE_MAX_RECOVERY_ATTEMPTS } from './constants';\nimport type { BackupRestoreFaultInjector } from './restore-fault-injection';\nimport {\n type BackupRestoreOperationPhase,\n type BackupRestoreOperationRecord,\n type BackupRestoreOperationResult,\n BackupRestoreOperationStore,\n backupRestoreOperationKey,\n createBackupRestoreOperationRecord,\n nextBackupRestoreAttempt\n} from './restore-operation-store';\n\ntype RestoreLifecycleDeps = {\n storage: DurableObjectStorage;\n currentRuntime: CurrentRuntimeIdentity;\n currentLifetime: CurrentSandboxLifetime;\n faultInjector: BackupRestoreFaultInjector;\n};\n\nexport type RestoreLifecycleContext = {\n lifetime: SandboxLifetime;\n runtimeReady: (archiveSize?: number) => Promise<{\n runtime: RuntimeIdentity;\n operation: BackupRestoreOperationRecord;\n }>;\n archiveReady: (archiveSize?: number) => Promise<{\n runtime: RuntimeIdentity;\n operation: BackupRestoreOperationRecord;\n }>;\n verify: (\n result: BackupRestoreOperationResult,\n archiveSize?: number\n ) => Promise<BackupRestoreOperationRecord>;\n};\n\nexport class RestoreLifecycleRunner {\n private readonly operationRecords: BackupRestoreOperationStore;\n\n constructor(private readonly deps: RestoreLifecycleDeps) {\n this.operationRecords = new BackupRestoreOperationStore(deps.storage);\n }\n\n async execute(params: {\n backupId: string;\n dir: string;\n attempt: (\n context: RestoreLifecycleContext\n ) => Promise<BackupRestoreOperationResult>;\n }): Promise<BackupRestoreOperationResult> {\n const lifetime = await this.deps.currentLifetime.getOrCreate();\n const operationKey = backupRestoreOperationKey(params.backupId, params.dir);\n const existing = await this.operationRecords.getCurrent(\n operationKey,\n lifetime.id\n );\n\n // Short-circuit: return the stored result without restoring again.\n if (existing?.status === 'committed' && existing.result) {\n return existing.result;\n }\n\n const now = new Date().toISOString();\n let operation: BackupRestoreOperationRecord;\n\n const isRetryable =\n existing !== null &&\n (existing.status === 'interrupted' || existing.status === 'running') &&\n existing.error?.retryable !== false;\n\n if (isRetryable) {\n // Resume with the same operationId so callers can reconcile the record.\n operation = nextBackupRestoreAttempt(existing, now);\n } else {\n operation = createBackupRestoreOperationRecord({\n operationId: crypto.randomUUID(),\n sandboxLifetimeID: lifetime.id,\n backupId: params.backupId,\n dir: params.dir,\n now\n });\n }\n await this.operationRecords.put(operation);\n\n return await this.runWithRecovery(operation, lifetime, params.attempt);\n }\n\n private async runAttempt(\n operation: BackupRestoreOperationRecord,\n lifetime: SandboxLifetime,\n attempt: (\n context: RestoreLifecycleContext\n ) => Promise<BackupRestoreOperationResult>\n ): Promise<BackupRestoreOperationResult> {\n let currentOperation = operation;\n let runtime: RuntimeIdentity | undefined;\n\n const context: RestoreLifecycleContext = {\n lifetime,\n runtimeReady: async (archiveSize) => {\n try {\n runtime = await this.captureRuntime();\n await this.deps.currentLifetime.assertCurrent(lifetime);\n } catch (error) {\n const interrupted = await this.translateFenceError(\n error,\n currentOperation,\n 'validating',\n 'unknown'\n );\n throw interrupted ?? error;\n }\n currentOperation = await this.markRuntimeReady(\n currentOperation,\n runtime,\n archiveSize\n );\n return { runtime, operation: currentOperation };\n },\n archiveReady: async (archiveSize) => {\n if (!runtime) {\n throw new Error(\n 'Backup restore archiveReady requires runtimeReady()'\n );\n }\n try {\n await this.assertFences(runtime, lifetime);\n } catch (error) {\n const interrupted = await this.translateFenceError(\n error,\n currentOperation,\n currentOperation.phase,\n true\n );\n throw interrupted ?? error;\n }\n currentOperation = await this.markArchiveReady(\n currentOperation,\n runtime,\n archiveSize\n );\n const fault = await this.deps.faultInjector.maybeFault(\n 'after_archive_ready',\n currentOperation\n );\n if (fault) {\n const message =\n 'Backup restore was interrupted before completion could be verified';\n const interrupted = await this.markInterrupted(\n currentOperation,\n message\n );\n throw this.createInterruptedError({\n operation: interrupted,\n reason: fault.reason,\n phase: currentOperation.phase,\n admitted: fault.admitted,\n message\n });\n }\n return { runtime, operation: currentOperation };\n },\n verify: async (result, archiveSize) => {\n if (!runtime) {\n throw new Error(\n 'Backup restore verification requires archiveReady()'\n );\n }\n try {\n await this.assertFences(runtime, lifetime);\n } catch (error) {\n const interrupted = await this.translateFenceError(\n error,\n currentOperation,\n 'archive_ready'\n );\n throw interrupted ?? error;\n }\n currentOperation = await this.markVerified(\n currentOperation,\n runtime,\n result,\n archiveSize\n );\n return currentOperation;\n }\n };\n\n try {\n return await attempt(context);\n } catch (error) {\n const translated = await this.translateRPCError(error, currentOperation);\n if (translated) {\n throw translated;\n }\n if (!(error instanceof OperationInterruptedError)) {\n await this.markFailed(currentOperation, error);\n }\n throw error;\n }\n }\n\n private async runWithRecovery(\n initialOperation: BackupRestoreOperationRecord,\n lifetime: SandboxLifetime,\n attempt: (\n context: RestoreLifecycleContext\n ) => Promise<BackupRestoreOperationResult>\n ): Promise<BackupRestoreOperationResult> {\n let recoveryAttempts = 0;\n let currentOperation = initialOperation;\n\n while (true) {\n try {\n return await this.runAttempt(currentOperation, lifetime, attempt);\n } catch (error) {\n if (!(error instanceof OperationInterruptedError)) {\n throw error;\n }\n\n if (!error.context.retryable) {\n throw error;\n }\n\n if (recoveryAttempts >= BACKUP_RESTORE_MAX_RECOVERY_ATTEMPTS) {\n throw this.createRecoveryExhaustedError(error, recoveryAttempts);\n }\n\n recoveryAttempts++;\n // Re-read the interrupted record written by markInterrupted() and\n // advance to the next attempt while preserving the operationId.\n const interrupted =\n (await this.operationRecords.get(currentOperation.operationKey)) ??\n currentOperation;\n const now = new Date().toISOString();\n currentOperation = nextBackupRestoreAttempt(interrupted, now);\n await this.operationRecords.put(currentOperation);\n }\n }\n }\n\n async captureRuntime(): Promise<RuntimeIdentity> {\n let runtime = await this.deps.currentRuntime.get();\n runtime = runtime ?? (await this.deps.currentRuntime.markStarted());\n await this.deps.currentRuntime.assertActive(runtime);\n return runtime;\n }\n\n async markRuntimeReady(\n operation: BackupRestoreOperationRecord,\n runtime: RuntimeIdentity,\n archiveSize?: number\n ): Promise<BackupRestoreOperationRecord> {\n const next = {\n ...operation,\n phase: 'runtime_ready' as const,\n runtimeIdentityID: runtime.id,\n payload: {\n ...operation.payload,\n ...(archiveSize !== undefined && { archiveSize })\n },\n updatedAt: new Date().toISOString()\n };\n await this.operationRecords.put(next);\n return next;\n }\n\n async markArchiveReady(\n operation: BackupRestoreOperationRecord,\n runtime: RuntimeIdentity,\n archiveSize?: number\n ): Promise<BackupRestoreOperationRecord> {\n const next = {\n ...operation,\n phase: 'archive_ready' as const,\n runtimeIdentityID: runtime.id,\n payload: {\n ...operation.payload,\n ...(archiveSize !== undefined && { archiveSize })\n },\n updatedAt: new Date().toISOString()\n };\n await this.operationRecords.put(next);\n return next;\n }\n\n async assertFences(\n runtime: RuntimeIdentity,\n lifetime: SandboxLifetime\n ): Promise<void> {\n await this.deps.currentRuntime.assertActive(runtime);\n await this.deps.currentLifetime.assertCurrent(lifetime);\n }\n\n async markVerified(\n operation: BackupRestoreOperationRecord,\n runtime: RuntimeIdentity,\n result: BackupRestoreOperationResult,\n archiveSize?: number\n ): Promise<BackupRestoreOperationRecord> {\n const completedAt = new Date().toISOString();\n const next = {\n ...operation,\n phase: 'verified' as const,\n status: 'committed' as const,\n runtimeIdentityID: runtime.id,\n payload: {\n ...operation.payload,\n ...(archiveSize !== undefined && { archiveSize })\n },\n result,\n completedAt,\n updatedAt: completedAt\n };\n await this.operationRecords.put(next);\n return next;\n }\n\n async translateFenceError(\n error: unknown,\n operation: BackupRestoreOperationRecord,\n phase: BackupRestoreOperationPhase,\n admitted: true | 'unknown' = true\n ): Promise<OperationInterruptedError | null> {\n if (\n !(\n error instanceof RuntimeIdentityInactiveError ||\n error instanceof SandboxLifetimeChangedError\n )\n ) {\n return null;\n }\n\n const reason =\n error instanceof RuntimeIdentityInactiveError\n ? 'runtime_replaced'\n : 'sandbox_lifetime_changed';\n const message =\n 'Backup restore was interrupted before completion could be verified';\n const interrupted = await this.markInterrupted(\n operation,\n message,\n reason !== 'sandbox_lifetime_changed'\n );\n return this.createInterruptedError({\n operation: interrupted,\n reason,\n phase,\n admitted,\n message\n });\n }\n\n async translateRPCError(\n error: unknown,\n operation: BackupRestoreOperationRecord\n ): Promise<OperationInterruptedError | null> {\n if (!(error instanceof RPCTransportError)) {\n return null;\n }\n\n const message =\n 'Backup restore was interrupted before completion could be verified';\n const interruptedPhase = operation.phase;\n const interrupted = await this.markInterrupted(operation, message);\n return this.createInterruptedError({\n operation: interrupted,\n reason: 'transport_disposed',\n phase: interruptedPhase,\n admitted: 'unknown',\n message\n });\n }\n\n private async markFailed(\n operation: BackupRestoreOperationRecord,\n error: unknown\n ): Promise<BackupRestoreOperationRecord> {\n const completedAt = new Date().toISOString();\n const message = error instanceof Error ? error.message : String(error);\n const code =\n error instanceof Error &&\n 'code' in error &&\n typeof error.code === 'string'\n ? error.code\n : 'UNKNOWN';\n const next = {\n ...operation,\n phase: 'failed' as const,\n status: 'failed' as const,\n error: {\n code,\n message,\n retryable: false\n },\n completedAt,\n updatedAt: completedAt\n };\n await this.operationRecords.put(next);\n return next;\n }\n\n private async markInterrupted(\n operation: BackupRestoreOperationRecord,\n message: string,\n retryable = true\n ): Promise<BackupRestoreOperationRecord> {\n const now = new Date().toISOString();\n const next = {\n ...operation,\n phase: 'interrupted' as const,\n status: 'interrupted' as const,\n error: {\n code: ErrorCode.OPERATION_INTERRUPTED,\n message,\n retryable\n },\n lastInterruptedAt: now,\n updatedAt: now\n };\n await this.operationRecords.put(next);\n return next;\n }\n\n private createInterruptedError(params: {\n operation: BackupRestoreOperationRecord;\n reason:\n | 'runtime_replaced'\n | 'sandbox_lifetime_changed'\n | 'transport_disposed';\n phase: BackupRestoreOperationPhase;\n admitted: true | 'unknown';\n message: string;\n }): OperationInterruptedError {\n const retryable = params.reason !== 'sandbox_lifetime_changed';\n const suggestion = retryable\n ? 'Retry restoreBackup() with the same backup handle so the SDK can reconcile the restore operation.'\n : 'Start a new restoreBackup() call only if restoring this backup is still desired for the current sandbox.';\n\n return new OperationInterruptedError({\n message: params.message,\n code: ErrorCode.OPERATION_INTERRUPTED,\n httpStatus: 409,\n context: {\n reason: params.reason,\n operation: 'backup.restore',\n operationId: params.operation.operationId,\n operationKey: params.operation.operationKey,\n idempotencyKey: params.operation.operationKey,\n backupId: params.operation.payload.backupId,\n dir: params.operation.payload.dir,\n phase: params.phase,\n admitted: params.admitted,\n retryable\n },\n timestamp: new Date().toISOString(),\n suggestion\n });\n }\n\n private createRecoveryExhaustedError(\n error: OperationInterruptedError,\n recoveryAttempts: number\n ): OperationInterruptedError {\n const context = error.context;\n return new OperationInterruptedError({\n message: 'Backup restore recovery attempts were exhausted',\n code: ErrorCode.OPERATION_INTERRUPTED,\n httpStatus: 409,\n context: {\n reason: 'recovery_exhausted',\n operation: context.operation,\n operationId: context.operationId,\n operationKey: context.operationKey,\n idempotencyKey: context.idempotencyKey,\n backupId: context.backupId,\n dir: context.dir,\n phase: 'interrupted',\n admitted: context.admitted,\n retryable: true,\n recoveryAttempts,\n maxRecoveryAttempts: BACKUP_RESTORE_MAX_RECOVERY_ATTEMPTS\n },\n timestamp: new Date().toISOString(),\n suggestion:\n 'Retry restoreBackup() with the same backup handle so the SDK can reconcile the restore operation.'\n });\n }\n}\n","import type { ExecOptions, ExecResult } from '@repo/shared';\nimport { type createLogger, getEnvString, shellEscape } from '@repo/shared';\nimport { AwsClient } from 'aws4fetch';\nimport type { ContainerControlClient } from '../container-control';\nimport {\n BackupCreateError,\n BackupRestoreError,\n ErrorCode,\n InvalidBackupConfigError,\n SandboxError\n} from '../errors';\nimport { isR2Bucket } from '../storage-mount';\nimport {\n BACKUP_CONTAINER_DIR,\n BACKUP_DOWNLOAD_MAX_PARTS,\n BACKUP_DOWNLOAD_PARALLEL_MIN_SIZE,\n BACKUP_DOWNLOAD_PARALLEL_PARTS,\n BACKUP_MULTIPART_MAX_PARTS,\n BACKUP_MULTIPART_MIN_PART_SIZE,\n BACKUP_MULTIPART_TARGET_PARTS,\n calculatePartCount\n} from './constants';\n\ntype BackupTransferDeps = {\n getEnv: () => unknown;\n getClient: () => ContainerControlClient;\n logger: ReturnType<typeof createLogger>;\n execWithSession: (\n command: string,\n sessionId: string,\n options?: ExecOptions\n ) => Promise<ExecResult>;\n};\n\nexport class BackupTransfer {\n private static readonly PRESIGNED_URL_EXPIRY_SECONDS = 3600;\n\n constructor(private readonly deps: BackupTransferDeps) {\n this.parseBackupBucketEndpoint(this.env as Record<string, unknown>);\n }\n\n private get env(): unknown {\n return this.deps.getEnv();\n }\n\n private get client(): ContainerControlClient {\n return this.deps.getClient();\n }\n\n private execWithSession(\n command: string,\n sessionId: string,\n options?: ExecOptions\n ): Promise<ExecResult> {\n return this.deps.execWithSession(command, sessionId, options);\n }\n\n private parseBackupBucketEndpoint(\n envObj: Record<string, unknown>\n ): string | null {\n const rawEndpoint = getEnvString(envObj, 'BACKUP_BUCKET_ENDPOINT') ?? null;\n if (rawEndpoint === null) {\n return null;\n }\n\n let parsed: URL;\n try {\n parsed = new URL(rawEndpoint);\n } catch {\n const msg = `BACKUP_BUCKET_ENDPOINT is not a valid URL: \"${rawEndpoint}\". Expected format: https://<account_id>.eu.r2.cloudflarestorage.com`;\n throw new InvalidBackupConfigError({\n message: msg,\n code: ErrorCode.INVALID_BACKUP_CONFIG,\n httpStatus: 400,\n context: { reason: msg },\n timestamp: new Date().toISOString()\n });\n }\n if (parsed.protocol !== 'https:') {\n const msg = `BACKUP_BUCKET_ENDPOINT must use https://, got \"${parsed.protocol.slice(0, -1)}://\"`;\n throw new InvalidBackupConfigError({\n message: msg,\n code: ErrorCode.INVALID_BACKUP_CONFIG,\n httpStatus: 400,\n context: { reason: msg },\n timestamp: new Date().toISOString()\n });\n }\n if (parsed.pathname !== '/') {\n const msg = `BACKUP_BUCKET_ENDPOINT must not include a path (got \"${parsed.pathname}\"). Provide only the origin, e.g. https://<account_id>.eu.r2.cloudflarestorage.com`;\n throw new InvalidBackupConfigError({\n message: msg,\n code: ErrorCode.INVALID_BACKUP_CONFIG,\n httpStatus: 400,\n context: { reason: msg },\n timestamp: new Date().toISOString()\n });\n }\n if (parsed.search !== '' || parsed.hash !== '') {\n const msg =\n 'BACKUP_BUCKET_ENDPOINT must not include query parameters or fragments. Provide only the origin, e.g. https://<account_id>.eu.r2.cloudflarestorage.com';\n throw new InvalidBackupConfigError({\n message: msg,\n code: ErrorCode.INVALID_BACKUP_CONFIG,\n httpStatus: 400,\n context: { reason: msg },\n timestamp: new Date().toISOString()\n });\n }\n return parsed.origin;\n }\n\n /** Validate that the BACKUP_BUCKET R2 binding is present and return it. */\n requireBackupBucket(): R2Bucket {\n const bucket = (this.env as Record<string, unknown>).BACKUP_BUCKET;\n if (!isR2Bucket(bucket)) {\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 bucket;\n }\n\n requirePresignedURLSupport(): {\n client: AwsClient;\n accountId: string;\n bucketName: string;\n } {\n const envObj = this.env as Record<string, unknown>;\n const r2AccountId =\n getEnvString(envObj, 'CLOUDFLARE_R2_ACCOUNT_ID') ??\n getEnvString(envObj, 'CLOUDFLARE_ACCOUNT_ID') ??\n null;\n const r2AccessKeyId = getEnvString(envObj, 'R2_ACCESS_KEY_ID') ?? null;\n const r2SecretAccessKey =\n getEnvString(envObj, 'R2_SECRET_ACCESS_KEY') ?? null;\n const backupBucketName = getEnvString(envObj, 'BACKUP_BUCKET_NAME') ?? null;\n\n if (\n !r2AccessKeyId ||\n !r2SecretAccessKey ||\n !r2AccountId ||\n !backupBucketName\n ) {\n const missing: string[] = [];\n if (!r2AccountId)\n missing.push('CLOUDFLARE_R2_ACCOUNT_ID or CLOUDFLARE_ACCOUNT_ID');\n if (!r2AccessKeyId) missing.push('R2_ACCESS_KEY_ID');\n if (!r2SecretAccessKey) missing.push('R2_SECRET_ACCESS_KEY');\n if (!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: new AwsClient({\n accessKeyId: r2AccessKeyId,\n secretAccessKey: r2SecretAccessKey\n }),\n accountId: r2AccountId,\n bucketName: backupBucketName\n };\n }\n\n private getBackupBucketEndpoint(accountId: string): string {\n return (\n this.parseBackupBucketEndpoint(this.env as Record<string, unknown>) ??\n `https://${accountId}.r2.cloudflarestorage.com`\n );\n }\n\n getBackupObjectURL(\n accountId: string,\n bucketName: string,\n r2Key: string\n ): URL {\n const encodedBucket = encodeURIComponent(bucketName);\n const encodedKey = r2Key\n .split('/')\n .map((seg) => encodeURIComponent(seg))\n .join('/');\n\n return new URL(\n `${this.getBackupBucketEndpoint(accountId)}/${encodedBucket}/${encodedKey}`\n );\n }\n\n /**\n * Generate a presigned GET URL for downloading an object from R2.\n * The container can curl this URL directly without credentials.\n */\n async generatePresignedGetURL(r2Key: string): Promise<string> {\n const { client, accountId, bucketName } = this.requirePresignedURLSupport();\n\n const url = this.getBackupObjectURL(accountId, bucketName, r2Key);\n url.searchParams.set(\n 'X-Amz-Expires',\n String(BackupTransfer.PRESIGNED_URL_EXPIRY_SECONDS)\n );\n\n const signed = await client.sign(new Request(url), {\n aws: { signQuery: true }\n });\n\n return signed.url;\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 url = this.getBackupObjectURL(accountId, bucketName, r2Key);\n url.searchParams.set(\n 'X-Amz-Expires',\n String(BackupTransfer.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 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 * Generate a presigned PUT URL for a single part in a multipart upload.\n */\n private async generatePresignedPartURL(\n r2Key: string,\n uploadId: string,\n partNumber: number\n ): Promise<string> {\n const { client, accountId, bucketName } = this.requirePresignedURLSupport();\n\n const url = this.getBackupObjectURL(accountId, bucketName, r2Key);\n url.searchParams.set(\n 'X-Amz-Expires',\n String(BackupTransfer.PRESIGNED_URL_EXPIRY_SECONDS)\n );\n url.searchParams.set('partNumber', String(partNumber));\n url.searchParams.set('uploadId', uploadId);\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 to R2 using parallel multipart upload.\n * Uses the S3-compatible API exclusively for create/complete/abort so that\n * the uploadId is in the same namespace as the presigned part PUT URLs.\n */\n async uploadBackupMultipart(\n archivePath: string,\n r2Key: string,\n sizeBytes: number,\n backupId: string,\n dir: string,\n backupSession: string\n ): Promise<void> {\n const targetParts = calculatePartCount(\n sizeBytes,\n BACKUP_MULTIPART_TARGET_PARTS,\n BACKUP_MULTIPART_MAX_PARTS\n );\n const numParts = Math.min(\n targetParts,\n Math.floor(sizeBytes / BACKUP_MULTIPART_MIN_PART_SIZE)\n );\n\n if (numParts <= 1) {\n return this.uploadBackupPresigned(\n archivePath,\n r2Key,\n sizeBytes,\n backupId,\n dir,\n backupSession\n );\n }\n\n const { client, accountId, bucketName } = this.requirePresignedURLSupport();\n const objectURL = this.getBackupObjectURL(\n accountId,\n bucketName,\n r2Key\n ).toString();\n\n const createResp = await client.fetch(`${objectURL}?uploads`, {\n method: 'POST'\n });\n if (!createResp.ok) {\n throw new BackupCreateError({\n message: `Failed to initiate multipart upload: HTTP ${createResp.status}`,\n code: ErrorCode.BACKUP_CREATE_FAILED,\n httpStatus: 500,\n context: { dir, backupId },\n timestamp: new Date().toISOString()\n });\n }\n\n const createXml = await createResp.text();\n const uploadIdMatch = createXml.match(/<UploadId>([^<]+)<\\/UploadId>/);\n const uploadId = uploadIdMatch?.[1];\n if (!uploadId) {\n throw new BackupCreateError({\n message: 'Multipart upload response did not contain an UploadId',\n code: ErrorCode.BACKUP_CREATE_FAILED,\n httpStatus: 500,\n context: { dir, backupId },\n timestamp: new Date().toISOString()\n });\n }\n\n const abortMultipart = async () => {\n await client\n .fetch(`${objectURL}?uploadId=${encodeURIComponent(uploadId)}`, {\n method: 'DELETE'\n })\n .catch(() => {});\n };\n\n try {\n const partSize = Math.ceil(sizeBytes / numParts);\n const parts = await Promise.all(\n Array.from({ length: numParts }, (_, i) => ({\n partNumber: i + 1,\n url: '',\n offset: i * partSize,\n size: i === numParts - 1 ? sizeBytes - i * partSize : partSize\n })).map(async (part) => ({\n ...part,\n url: await this.generatePresignedPartURL(\n r2Key,\n uploadId,\n part.partNumber\n )\n }))\n );\n\n let uploadResult: Awaited<\n ReturnType<typeof this.client.backup.uploadParts>\n >;\n try {\n uploadResult = await this.client.backup.uploadParts({\n archivePath,\n parts,\n sessionId: backupSession\n });\n } catch (err) {\n if (\n err instanceof SandboxError &&\n err.errorResponse.httpStatus === 404\n ) {\n await abortMultipart();\n return this.uploadBackupPresigned(\n archivePath,\n r2Key,\n sizeBytes,\n backupId,\n dir,\n backupSession\n );\n }\n throw err;\n }\n\n if (!uploadResult.success || uploadResult.parts.length !== numParts) {\n throw new BackupCreateError({\n message: `Multipart upload returned ${uploadResult.parts.length} of ${numParts} parts`,\n code: ErrorCode.BACKUP_CREATE_FAILED,\n httpStatus: 500,\n context: { dir, backupId },\n timestamp: new Date().toISOString()\n });\n }\n\n const completeXml = [\n '<CompleteMultipartUpload>',\n ...uploadResult.parts.map(\n (p: { partNumber: number; etag: string }) =>\n `<Part><PartNumber>${p.partNumber}</PartNumber><ETag>${p.etag}</ETag></Part>`\n ),\n '</CompleteMultipartUpload>'\n ].join('');\n\n const completeResp = await client.fetch(\n `${objectURL}?uploadId=${encodeURIComponent(uploadId)}`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/xml' },\n body: completeXml\n }\n );\n\n if (!completeResp.ok) {\n const body = await completeResp.text().catch(() => '');\n throw new BackupCreateError({\n message: `Multipart upload completion failed: HTTP ${completeResp.status} ${body}`,\n code: ErrorCode.BACKUP_CREATE_FAILED,\n httpStatus: 500,\n context: { dir, backupId },\n timestamp: new Date().toISOString()\n });\n }\n\n const head = await this.requireBackupBucket().head(r2Key);\n if (!head || head.size !== sizeBytes) {\n throw new BackupCreateError({\n message: `Multipart upload verification failed: expected ${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 } catch (error) {\n await abortMultipart();\n throw error;\n }\n }\n\n /**\n * Download a backup archive from R2 via presigned GET URL.\n * For archives >= BACKUP_DOWNLOAD_PARALLEL_MIN_SIZE, uses BACKUP_DOWNLOAD_PARALLEL_PARTS\n * concurrent curl processes (each downloading a byte-range) to maximise both\n * network and disk-write throughput. Parts are written into a pre-sized file\n * with dd using byte offsets, then atomically moved to the final path.\n */\n async downloadBackupParallel(\n archivePath: string,\n r2Key: string,\n expectedSize: number,\n backupId: string,\n dir: string,\n backupSession: string\n ): Promise<void> {\n const presignedURL = await this.generatePresignedGetURL(r2Key);\n await this.execWithSession(\n `mkdir -p ${BACKUP_CONTAINER_DIR}`,\n backupSession,\n { origin: 'internal' }\n );\n\n const tmpPath = `${archivePath}.tmp`;\n\n if (expectedSize < BACKUP_DOWNLOAD_PARALLEL_MIN_SIZE) {\n const curlCmd = [\n 'curl -sSf',\n '--connect-timeout 10',\n '--max-time 1800',\n '--retry 2',\n '--retry-max-time 60',\n `-o ${shellEscape(tmpPath)}`,\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 await this.execWithSession(\n `rm -f ${shellEscape(tmpPath)}`,\n backupSession,\n { origin: 'internal' }\n ).catch(() => {});\n throw new BackupRestoreError({\n message: `Presigned URL download failed (exit code ${result.exitCode}): ${result.stderr}`,\n code: ErrorCode.BACKUP_RESTORE_FAILED,\n httpStatus: 500,\n context: { dir, backupId },\n timestamp: new Date().toISOString()\n });\n }\n } else {\n const numParts = calculatePartCount(\n expectedSize,\n BACKUP_DOWNLOAD_PARALLEL_PARTS,\n BACKUP_DOWNLOAD_MAX_PARTS\n );\n const partSize = Math.floor(expectedSize / numParts);\n const ranges = Array.from({ length: numParts }, (_, i) => {\n const start = i * partSize;\n const end = i < numParts - 1 ? start + partSize - 1 : expectedSize - 1;\n return { start, range: `${start}-${end}` };\n });\n\n const curlCmds = ranges.map(({ start, range }) =>\n [\n 'curl -sSf',\n '--connect-timeout 10',\n '--max-time 1800',\n `-H ${shellEscape(`Range: bytes=${range}`)}`,\n shellEscape(presignedURL),\n '|',\n 'dd',\n `of=${shellEscape(tmpPath)}`,\n 'oflag=seek_bytes',\n `seek=${start}`,\n 'conv=notrunc',\n '2>/dev/null'\n ].join(' ')\n );\n\n const startLines = curlCmds.map(\n (cmd, i) => `(set -o pipefail; ${cmd}) & J${i}=$!`\n );\n const waitLines = Array.from(\n { length: numParts },\n (_, i) => `wait $J${i}; E${i}=$?`\n );\n const exitVars = Array.from({ length: numParts }, (_, i) => `$E${i}`);\n\n const script = [\n `rm -f ${shellEscape(tmpPath)}`,\n `truncate -s ${expectedSize} ${shellEscape(tmpPath)}`,\n ...startLines,\n ...waitLines,\n `FAILED=$(( ${exitVars.join(' + ')} ))`,\n `if [ \"$FAILED\" -ne 0 ]; then rm -f ${shellEscape(tmpPath)}; exit 1; fi`\n ].join('; ');\n\n const result = await this.execWithSession(script, backupSession, {\n timeout: 1810_000,\n origin: 'internal'\n });\n\n if (result.exitCode !== 0) {\n await this.execWithSession(\n `rm -f ${shellEscape(tmpPath)}`,\n backupSession,\n { origin: 'internal' }\n ).catch(() => {});\n throw new BackupRestoreError({\n message: `Parallel download failed (exit code ${result.exitCode}): ${result.stderr}`,\n code: ErrorCode.BACKUP_RESTORE_FAILED,\n httpStatus: 500,\n context: { dir, backupId },\n timestamp: new Date().toISOString()\n });\n }\n }\n\n const sizeCheck = await this.execWithSession(\n `stat -c %s ${shellEscape(tmpPath)}`,\n backupSession,\n { origin: 'internal' }\n );\n const actualSize = parseInt(sizeCheck.stdout.trim(), 10);\n if (actualSize !== expectedSize) {\n await this.execWithSession(\n `rm -f ${shellEscape(tmpPath)}`,\n backupSession,\n { origin: 'internal' }\n ).catch(() => {});\n throw new BackupRestoreError({\n message: `Downloaded archive size mismatch: expected ${expectedSize}, got ${actualSize}`,\n code: ErrorCode.BACKUP_RESTORE_FAILED,\n httpStatus: 500,\n context: { dir, backupId },\n timestamp: new Date().toISOString()\n });\n }\n\n const mvResult = await this.execWithSession(\n `mv ${shellEscape(tmpPath)} ${shellEscape(archivePath)}`,\n backupSession,\n { origin: 'internal' }\n );\n if (mvResult.exitCode !== 0) {\n await this.execWithSession(\n `rm -f ${shellEscape(tmpPath)}`,\n backupSession,\n { origin: 'internal' }\n ).catch(() => {});\n throw new BackupRestoreError({\n message: `Failed to finalize downloaded archive: ${mvResult.stderr}`,\n code: ErrorCode.BACKUP_RESTORE_FAILED,\n httpStatus: 500,\n context: { dir, backupId },\n timestamp: new Date().toISOString()\n });\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}\n","import type {\n BackupOptions,\n DirectoryBackup,\n ExecOptions,\n ExecResult,\n RestoreBackupResult\n} from '@repo/shared';\nimport {\n type createLogger,\n logCanonicalEvent,\n shellEscape\n} from '@repo/shared';\nimport type { ContainerControlClient } from '../container-control';\nimport type { CurrentRuntimeIdentity } from '../current-runtime-identity';\nimport {\n BackupCreateError,\n BackupExpiredError,\n BackupNotFoundError,\n BackupRestoreError,\n ErrorCode,\n InvalidBackupConfigError\n} from '../errors';\nimport type { CurrentSandboxLifetime } from '../sandbox-lifetime';\nimport { isR2Bucket } from '../storage-mount';\nimport {\n BACKUP_ARCHIVE_OBJECT_NAME,\n BACKUP_CONTAINER_DIR,\n BACKUP_METADATA_OBJECT_NAME,\n BACKUP_STORAGE_PREFIX\n} from './constants';\nimport { BackupCreator } from './create';\nimport {\n type BackupRestoreTestFault,\n StorageBackedBackupRestoreFaultInjector\n} from './restore-fault-injection';\nimport {\n type RestoreLifecycleContext,\n RestoreLifecycleRunner\n} from './restore-lifecycle';\nimport type { BackupRestoreOperationResult } from './restore-operation-store';\nimport { BackupTransfer } from './transfer';\nimport { validateBackupDir } from './validation';\n\nexport type { BackupRestoreTestFault } from './restore-fault-injection';\n\ntype BackupServiceDeps = {\n ctx: DurableObjectState<{}>;\n getEnv: () => unknown;\n logger: ReturnType<typeof createLogger>;\n getClient: () => ContainerControlClient;\n execWithSession: (\n command: string,\n sessionId: string,\n options?: ExecOptions\n ) => Promise<ExecResult>;\n currentRuntime: CurrentRuntimeIdentity;\n currentLifetime: CurrentSandboxLifetime;\n};\n\nexport class BackupService {\n private readonly ctx: DurableObjectState<{}>;\n private readonly logger: ReturnType<typeof createLogger>;\n private readonly creator: BackupCreator;\n private readonly restoreFaults: StorageBackedBackupRestoreFaultInjector;\n private readonly restoreLifecycle: RestoreLifecycleRunner;\n private readonly transfer: BackupTransfer;\n private backupInProgress: Promise<unknown> = Promise.resolve();\n\n constructor(private readonly deps: BackupServiceDeps) {\n this.ctx = deps.ctx;\n this.logger = deps.logger;\n this.restoreFaults = new StorageBackedBackupRestoreFaultInjector(\n this.ctx.storage,\n deps.getEnv\n );\n this.restoreLifecycle = new RestoreLifecycleRunner({\n storage: this.ctx.storage,\n currentRuntime: deps.currentRuntime,\n currentLifetime: deps.currentLifetime,\n faultInjector: this.restoreFaults\n });\n this.transfer = new BackupTransfer({\n getEnv: deps.getEnv,\n getClient: deps.getClient,\n logger: deps.logger,\n execWithSession: deps.execWithSession\n });\n this.creator = new BackupCreator({\n getEnv: deps.getEnv,\n getClient: deps.getClient,\n logger: deps.logger,\n transfer: this.transfer,\n ensureBackupSession: () => this.ensureBackupSession(),\n execWithSession: deps.execWithSession\n });\n }\n\n private get env(): unknown {\n return this.deps.getEnv();\n }\n\n private get client(): ContainerControlClient {\n return this.deps.getClient();\n }\n\n private execWithSession(\n command: string,\n sessionId: string,\n options?: ExecOptions\n ): Promise<ExecResult> {\n return this.deps.execWithSession(command, sessionId, options);\n }\n\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 * 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 * Serialize backup operations so concurrent calls run one at a time.\n */\n private async enqueueBackupOp<T>(fn: () => Promise<T>): Promise<T> {\n try {\n await this.backupInProgress;\n } catch {\n // Previous backup/restore failure should not poison later operations.\n }\n\n const next = fn();\n this.backupInProgress = next.catch(() => {});\n return await 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 return await this.enqueueBackupOp(() => this.creator.createBackup(options));\n }\n\n async restoreBackup(backup: DirectoryBackup): Promise<RestoreBackupResult> {\n const { id, dir } = this.validateRestoreBackup(backup);\n if (backup.localBucket) {\n return await this.enqueueBackupOp(() =>\n this.restoreLifecycle.execute({\n backupId: id,\n dir,\n attempt: (context) => this.doRestoreBackupLocal(backup, context)\n })\n );\n }\n this.transfer.requireBackupBucket();\n return await this.enqueueBackupOp(() =>\n this.restoreLifecycle.execute({\n backupId: id,\n dir,\n attempt: (context) => this.doRestoreBackup(backup, context)\n })\n );\n }\n\n private validateRestoreBackup(backup: DirectoryBackup): {\n id: string;\n dir: string;\n } {\n const { id, dir } = backup;\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 (!BackupService.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 validateBackupDir(dir, 'Invalid backup: dir');\n return { id, dir };\n }\n\n async setRestoreFaultForTesting(\n fault: BackupRestoreTestFault | null\n ): Promise<void> {\n await this.restoreFaults.setFaultForTesting(fault);\n }\n\n private async doRestoreBackup(\n backup: DirectoryBackup,\n lifecycle: RestoreLifecycleContext\n ): Promise<BackupRestoreOperationResult> {\n const restoreStartTime = Date.now();\n const bucket = this.transfer.requireBackupBucket();\n this.transfer.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 // 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 and get its size 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 await lifecycle.runtimeReady(archiveHead.size);\n const archivePath = `${BACKUP_CONTAINER_DIR}/${id}.sqsh`;\n\n // Step 3: Tear down existing FUSE mounts before overwriting the archive.\n // squashfuse holds the .sqsh file open; writing a new archive to the same\n // path while the old mount is active corrupts the backing store.\n // Unmount the overlay on dir, then iterate over all mount bases for this\n // backup (both suffixed UUID_* and legacy unsuffixed UUID) and unmount\n // their squashfuse lower dirs.\n const mountGlob = `${BACKUP_CONTAINER_DIR}/mounts/${id}`;\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\n // Step 4: Write archive to the container (skip if already present and\n // same size — avoids overwriting a file that a lazily-unmounted\n // squashfuse may still hold open).\n const sizeCheck = await this.execWithSession(\n `stat -c %s ${shellEscape(archivePath)} 2>/dev/null || echo 0`,\n backupSession,\n { origin: 'internal' }\n ).catch(() => ({ stdout: '0' }));\n const existingSize = Number.parseInt(\n (sizeCheck.stdout ?? '0').trim(),\n 10\n );\n\n if (existingSize !== archiveHead.size) {\n await this.transfer.downloadBackupParallel(\n archivePath,\n r2Key,\n archiveHead.size,\n id,\n dir,\n backupSession\n );\n }\n\n await lifecycle.archiveReady(archiveHead.size);\n\n const restoreResult = await this.client.backup.restoreArchive(\n dir,\n archivePath,\n { sessionId: 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 const result = {\n success: true as const,\n dir,\n id\n };\n await lifecycle.verify(result, archiveHead.size);\n\n outcome = 'success';\n\n return result;\n } catch (error) {\n caughtError = error instanceof Error ? error : new Error(String(error));\n if (id && backupSession) {\n const cleanupPath = `${BACKUP_CONTAINER_DIR}/${id}.sqsh`;\n await this.execWithSession(\n `rm -f ${shellEscape(cleanupPath)}`,\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 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 lifecycle: RestoreLifecycleContext\n ): Promise<BackupRestoreOperationResult> {\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 // 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 sizeBytes?: number;\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 await lifecycle.runtimeReady(metadata.sizeBytes);\n const archivePath = `${BACKUP_CONTAINER_DIR}/${id}.sqsh`;\n\n // Ensure backup directory exists\n await this.execWithSession(\n `mkdir -p ${BACKUP_CONTAINER_DIR}`,\n backupSession,\n { origin: 'internal' }\n );\n\n // Stream the archive into the container to avoid base64-encoding the\n // whole archive in Worker memory and hitting workerd's 32 MiB RPC\n // payload cap.\n const body = archiveObject.body;\n if (!body) {\n throw new BackupRestoreError({\n message: `R2 archive object has no body stream for backup ${id}`,\n code: ErrorCode.BACKUP_RESTORE_FAILED,\n httpStatus: 500,\n context: { dir, backupId: id },\n timestamp: new Date().toISOString()\n });\n }\n await this.client.files.writeFileStream(archivePath, body, {\n sessionId: backupSession\n });\n\n await lifecycle.archiveReady(metadata.sizeBytes);\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 const result = {\n success: true as const,\n dir,\n id\n };\n await lifecycle.verify(result, metadata.sizeBytes);\n\n outcome = 'success';\n\n return result;\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","import type { LogContext, Logger } from '@repo/shared';\n\nconst DEFAULT_INITIAL_RETRY_DELAY_MS = 3_000;\nconst DEFAULT_MAX_RETRY_DELAY_MS = 30_000;\nconst RETRYABLE_WEBSOCKET_UPGRADE_STATUSES = new Set([500, 502, 503, 504]);\n\nexport function isRetryableWebSocketUpgradeResponse(\n response: Response\n): boolean {\n return RETRYABLE_WEBSOCKET_UPGRADE_STATUSES.has(response.status);\n}\n\nexport interface ResponseRetryOptions {\n retryTimeoutMs: number;\n minTimeForRetryMs: number;\n logger: Logger;\n retryLogMessage: string;\n shouldRetry(response: Response): boolean;\n getRetryLogContext?: (response: Response) => Partial<LogContext>;\n onRetryExhausted?: (params: {\n attempts: number;\n elapsedMs: number;\n response: Response;\n }) => void;\n}\n\n/**\n * Retry Response-returning operations while their response remains retryable.\n * The retry budget covers the whole operation; each attempt owns any\n * per-request timeout inside the caller-provided `fetchResponse` function.\n */\nexport async function fetchWithResponseRetry(\n fetchResponse: () => Promise<Response>,\n options: ResponseRetryOptions\n): Promise<Response> {\n const startTime = Date.now();\n let attempt = 0;\n\n while (true) {\n const response = await fetchResponse();\n\n if (!options.shouldRetry(response)) {\n return response;\n }\n\n const elapsed = Date.now() - startTime;\n const remaining = options.retryTimeoutMs - elapsed;\n\n if (remaining <= options.minTimeForRetryMs) {\n options.onRetryExhausted?.({\n attempts: attempt + 1,\n elapsedMs: elapsed,\n response\n });\n return response;\n }\n\n const delay = Math.min(\n DEFAULT_INITIAL_RETRY_DELAY_MS * 2 ** attempt,\n DEFAULT_MAX_RETRY_DELAY_MS\n );\n\n options.logger.info(options.retryLogMessage, {\n status: response.status,\n attempt: attempt + 1,\n delayMs: delay,\n remainingSec: Math.floor(remaining / 1000),\n ...options.getRetryLogContext?.(response)\n });\n\n await new Promise<void>((resolve) => setTimeout(resolve, delay));\n attempt++;\n }\n}\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 directly\n * over the control channel.\n */\n\nimport type {\n Logger,\n SandboxAPI,\n SandboxBackupAPI,\n SandboxCommandsAPI,\n SandboxControlCallback,\n SandboxFilesAPI,\n SandboxPortsAPI,\n SandboxProcessesAPI,\n SandboxUtilsAPI,\n SandboxWatchAPI\n} from '@repo/shared';\nimport { createNoOpLogger } from '@repo/shared';\nimport {\n ErrorCode,\n type ErrorResponse,\n getHttpStatus,\n getSuggestion\n} from '@repo/shared/errors';\nimport {\n RpcSession,\n type RpcStub,\n type RpcTarget,\n type RpcTransport\n} from 'capnweb';\nimport { createErrorFromResponse } from '../errors/adapter';\nimport {\n fetchWithResponseRetry,\n isRetryableWebSocketUpgradeResponse\n} from '../response-retry';\n\n// ---------------------------------------------------------------------------\n// Connection manager\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_CONNECT_TIMEOUT_MS = 30_000;\nconst DEFAULT_RETRY_TIMEOUT_MS = 120_000; // 2 minute total budget for upgrade retries\nconst MIN_TIME_FOR_RETRY_MS = 15_000; // Need at least 15s remaining to attempt a retry\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\ninterface StructuredErrorBody {\n code?: string;\n message?: string;\n context?: Record<string, unknown>;\n httpStatus?: number;\n timestamp?: string;\n suggestion?: string;\n}\n\nfunction isErrorResponse(value: unknown): value is ErrorResponse {\n if (typeof value !== 'object' || value === null) return false;\n const candidate = value as Partial<ErrorResponse>;\n return (\n typeof candidate.code === 'string' &&\n Object.values(ErrorCode).includes(candidate.code as ErrorCode) &&\n typeof candidate.message === 'string' &&\n typeof candidate.httpStatus === 'number' &&\n typeof candidate.timestamp === 'string' &&\n typeof candidate.context === 'object' &&\n candidate.context !== null\n );\n}\n\nfunction normalizeContainerUnavailableReason(\n reason: unknown\n):\n | 'container_starting'\n | 'container_unhealthy'\n | 'container_replaced'\n | 'rpc_upgrade_failed' {\n switch (reason) {\n case 'container_starting':\n case 'container_unhealthy':\n case 'container_replaced':\n case 'rpc_upgrade_failed':\n return reason;\n case 'provisioning':\n case 'startup':\n return 'container_starting';\n case 'container_restarted':\n return 'container_replaced';\n default:\n return 'container_replaced';\n }\n}\n\nexport interface ContainerControlConnectionOptions {\n stub: ContainerFetchStub;\n port?: number;\n logger?: Logger;\n /**\n * Total retry budget (ms) for retryable upgrade responses while the\n * container is unavailable. Defaults to 120 000 (2 minutes). Set to 0 to\n * disable retries.\n */\n retryTimeoutMs?: number;\n /**\n * Optional `localMain` exposed to the container side of the capnweb\n * session. The container reaches it via\n * `session.getRemoteMain()` and uses it for control-plane callbacks\n * (e.g. notifying the DO when a tunnel's cloudflared process has\n * exited). When omitted, the container sees an empty remote main.\n */\n localMain?: SandboxControlCallback & RpcTarget;\n /**\n * Invoked when an active WebSocket transitions to closed/errored.\n * Fired at most once per successful connection from the WS event\n * handlers in `doConnect`. Gives owners a synchronous teardown\n * signal so recovery doesn't depend on a periodic poller running\n * inside what may be an idle isolate.\n *\n * Also fired for `doConnect` failures after the deferred transport is\n * aborted. A failed upgrade poisons the transport, so owners must discard\n * the connection and create a fresh one for subsequent calls. Not fired for\n * `disconnect()`.\n */\n onClose?: () => void;\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 ContainerControlConnection {\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 private retryTimeoutMs: number;\n private readonly onClose: (() => void) | undefined;\n\n constructor(options: ContainerControlConnectionOptions) {\n this.containerStub = options.stub;\n this.port = options.port ?? 3000;\n this.logger = options.logger ?? createNoOpLogger();\n this.retryTimeoutMs = options.retryTimeoutMs ?? DEFAULT_RETRY_TIMEOUT_MS;\n this.onClose = options.onClose;\n\n this.transport = new DeferredTransport();\n this.session = new RpcSession<SandboxAPI>(\n this.transport,\n options.localMain\n );\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 // Unbind first so a late `close` / `error` event dispatched by\n // the runtime after we've decided this connection is dead can't\n // reach a successor that the owner installed in our place — see\n // the WebSocket-listener-unbinding tests in container-connection\n // for the race this prevents.\n this.ws.removeEventListener('close', this.onWebSocketClose);\n this.ws.removeEventListener('error', this.onWebSocketError);\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 * Update the upgrade retry budget without recreating the connection. Takes\n * effect on the next `connect()`; an in-flight connect uses the value\n * captured at start.\n */\n setRetryTimeoutMs(ms: number): void {\n this.retryTimeoutMs = ms;\n }\n\n // -----------------------------------------------------------------------\n // Internal\n // -----------------------------------------------------------------------\n\n /**\n * Run the owner-provided `onClose` callback exactly once per call,\n * swallowing any errors so a buggy listener can't keep the connection\n * object in a half-torn-down state.\n */\n private fireOnClose(): void {\n if (!this.onClose) return;\n try {\n this.onClose();\n } catch (err) {\n this.logger.warn('ContainerControlConnection onClose handler threw', {\n error: err instanceof Error ? err.message : String(err)\n });\n }\n }\n\n /**\n * WebSocket `close` listener. Defined as a bound arrow field so the\n * same reference can be passed to both `addEventListener` and\n * `removeEventListener` — a fresh anonymous lambda would silently\n * fail to unbind.\n */\n private onWebSocketClose = (): void => {\n const wasConnected = this.connected;\n this.connected = false;\n this.ws = null;\n this.logger.debug('ContainerControlConnection WebSocket closed');\n if (wasConnected) this.fireOnClose();\n };\n\n /**\n * WebSocket `error` listener. Same field-form rationale as\n * {@link onWebSocketClose}.\n */\n private onWebSocketError = (): void => {\n const wasConnected = this.connected;\n this.connected = false;\n this.ws = null;\n if (wasConnected) this.fireOnClose();\n };\n\n private async doConnect(): Promise<void> {\n try {\n const response = await this.fetchUpgradeWithRetry();\n\n if (response.status !== 101) {\n const containerUnavailable =\n await this.parseContainerUnavailableUpgradeError(response);\n if (containerUnavailable) {\n throw createErrorFromResponse(containerUnavailable);\n }\n const structuredError =\n await this.parseStructuredUpgradeError(response);\n if (structuredError) {\n throw createErrorFromResponse(structuredError);\n }\n if (isRetryableWebSocketUpgradeResponse(response)) {\n const context = {\n reason: 'rpc_upgrade_failed' as const,\n retryable: true as const\n };\n throw createErrorFromResponse({\n code: ErrorCode.CONTAINER_UNAVAILABLE,\n message:\n 'Container was unavailable after exhausting upgrade retry budget.',\n context,\n httpStatus: getHttpStatus(ErrorCode.CONTAINER_UNAVAILABLE),\n timestamp: new Date().toISOString(),\n suggestion: getSuggestion(ErrorCode.CONTAINER_UNAVAILABLE, context)\n });\n }\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', this.onWebSocketClose);\n ws.addEventListener('error', this.onWebSocketError);\n\n this.ws = ws;\n this.transport.activate(ws);\n this.connected = true;\n\n this.logger.debug('ContainerControlConnection established', {\n port: this.port\n });\n } catch (error) {\n this.connected = false;\n this.transport.abort(error);\n this.logger.error(\n 'ContainerControlConnection failed',\n error instanceof Error ? error : new Error(String(error))\n );\n this.fireOnClose();\n throw error;\n }\n }\n\n private async parseStructuredUpgradeError(\n response: Response\n ): Promise<ErrorResponse | null> {\n const body = await this.parseJSONBody(response);\n if (!isErrorResponse(body)) return null;\n return body;\n }\n\n private async parseContainerUnavailableUpgradeError(\n response: Response\n ): Promise<ErrorResponse | null> {\n const body = await this.parseJSONBody(response);\n if (!body || body.code !== ErrorCode.CONTAINER_UNAVAILABLE) return null;\n\n const reason = normalizeContainerUnavailableReason(body.context?.reason);\n const context = {\n reason,\n retryable: true as const,\n ...(typeof body.context?.retryAfterMs === 'number' && {\n retryAfterMs: body.context.retryAfterMs\n })\n };\n\n return {\n code: ErrorCode.CONTAINER_UNAVAILABLE,\n message: body.message ?? 'Container is unavailable',\n context,\n httpStatus:\n typeof body.httpStatus === 'number'\n ? body.httpStatus\n : getHttpStatus(ErrorCode.CONTAINER_UNAVAILABLE),\n timestamp:\n typeof body.timestamp === 'string'\n ? body.timestamp\n : new Date().toISOString(),\n suggestion:\n typeof body.suggestion === 'string'\n ? body.suggestion\n : getSuggestion(ErrorCode.CONTAINER_UNAVAILABLE, context)\n };\n }\n\n private async parseJSONBody(\n response: Response\n ): Promise<StructuredErrorBody | null> {\n const contentType = response.headers.get('Content-Type') ?? '';\n if (!contentType.includes('application/json')) return null;\n\n try {\n const body = (await response.clone().json()) as StructuredErrorBody;\n return body && typeof body === 'object' ? body : null;\n } catch {\n return null;\n }\n }\n\n /**\n * Issue WebSocket upgrade fetches, retrying transient control-plane\n * unavailability responses until either the upgrade succeeds, a\n * non-retryable status is returned, or the retry budget runs out.\n */\n private async fetchUpgradeWithRetry(): Promise<Response> {\n return fetchWithResponseRetry(() => this.fetchUpgradeAttempt(), {\n retryTimeoutMs: this.retryTimeoutMs,\n minTimeForRetryMs: MIN_TIME_FOR_RETRY_MS,\n logger: this.logger,\n retryLogMessage:\n 'ContainerControlConnection upgrade returned retryable status, retrying',\n shouldRetry: isRetryableWebSocketUpgradeResponse\n });\n }\n\n /**\n * Single WebSocket-upgrade fetch attempt. Owns its own AbortController so\n * each retry gets a fresh per-attempt connect timeout independent of the\n * total retry budget.\n */\n private async fetchUpgradeAttempt(): Promise<Response> {\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 return await this.containerStub.fetch(request);\n } finally {\n clearTimeout(timeout);\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 */\nexport class 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 } else {\n // Capnweb's wire format is strictly text (JSON), so a binary\n // frame indicates a misbehaving\n // peer. Failing the transport here surfaces the problem to in-flight\n // RPC calls; without it `receive()` would hang forever waiting for\n // a string that is never going to arrive.\n this.#fail(\n new TypeError('Received non-string message from WebSocket.')\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 * Sandbox control client 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 ContainerControlConnection\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. `processes.streamProcessLogs`),\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 * `processes.streamProcessLogs(...)` 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 SandboxExtensionsAPI,\n SandboxFilesAPI,\n SandboxPortsAPI,\n SandboxProcessesAPI,\n SandboxTerminalsAPI,\n SandboxTunnelsAPI,\n SandboxUtilsAPI,\n SandboxWatchAPI\n} from '@repo/shared';\nimport { createNoOpLogger } from '@repo/shared';\nimport {\n ErrorCode,\n type ErrorResponse,\n getHttpStatus,\n getSuggestion,\n type OperationInterruptedContext,\n type RPCTransportContext,\n type RPCTransportErrorKind\n} from '@repo/shared/errors';\nimport { createErrorFromResponse } from '../errors/adapter';\nimport { SandboxError } from '../errors/classes';\nimport {\n ContainerControlConnection,\n type ContainerControlConnectionOptions\n} from './connection';\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\n/** Legacy JSON-in-message payload shape — see `translateRPCError`. */\ninterface RPCErrorPayload {\n code: string;\n message: string;\n context?: Record<string, unknown>;\n}\n\nfunction isLocalSandboxError(\n error: Error\n): error is Error & { errorResponse: ErrorResponse } {\n if (!('errorResponse' in error)) return false;\n const response = error.errorResponse as Partial<ErrorResponse> | undefined;\n return (\n typeof response?.code === 'string' &&\n Object.hasOwn(ErrorCode, response.code) &&\n typeof response.message === 'string' &&\n typeof response.httpStatus === 'number' &&\n typeof response.timestamp === 'string' &&\n typeof response.context === 'object' &&\n response.context !== null\n );\n}\n\n/**\n * Translate a capnweb-propagated error into a typed SandboxError.\n *\n * Two wire formats are supported for backward compatibility with older\n * container images:\n *\n * 1. Propagated error properties (capnweb >= 0.8.0). The container throws a\n * `ServiceError`-shaped object with own enumerable `code` and `details`\n * properties. capnweb walks `Object.keys()` and reconstructs those fields\n * on the SDK side.\n * 2. Legacy JSON-encoded message. Older containers encoded the structured\n * payload as a JSON string in `error.message`.\n *\n * The JSON-fallback branch can be removed once all older container images are\n * no longer in service.\n */\nexport interface RPCTranslationContext {\n /** Public operation name, e.g. `commands.execute` or `files.writeFile`. */\n operation?: string;\n}\n\nexport function translateRPCError(\n error: unknown,\n context: RPCTranslationContext = {}\n): never {\n if (error instanceof SandboxError) throw error;\n\n if (error instanceof Error) {\n if (isLocalSandboxError(error)) {\n throw error;\n }\n\n // Format (1): propagated error properties. Distinguish from arbitrary\n // Node/system errors (e.g. `Error.code === 'ENOENT'`) by checking the\n // code against the ErrorCode registry.\n const propagated = error as Error & {\n code?: unknown;\n details?: unknown;\n };\n if (\n typeof propagated.code === 'string' &&\n Object.hasOwn(ErrorCode, propagated.code)\n ) {\n const code = propagated.code as ErrorCode;\n const context =\n propagated.details && typeof propagated.details === 'object'\n ? (propagated.details as Record<string, unknown>)\n : {};\n throw createErrorFromResponse({\n code,\n message: error.message,\n context,\n httpStatus: getHttpStatus(code),\n timestamp: new Date().toISOString()\n });\n }\n\n // Format (2): legacy JSON-encoded structured error in `message`.\n let payload: RPCErrorPayload | undefined;\n try {\n payload = JSON.parse(error.message) as RPCErrorPayload;\n } catch {\n // Not a JSON-encoded structured error. Fall through to transport-\n // level classification below.\n }\n if (\n payload &&\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 // Map capnweb / DeferredTransport messages onto structured SDK errors\n // so consumers get public `code` and context fields.\n const transportResponse = buildTransportErrorResponse(error);\n const interruptedResponse = buildInterruptedOperationResponse(\n transportResponse,\n context\n );\n throw createErrorFromResponse(\n (interruptedResponse ?? transportResponse) as unknown as ErrorResponse,\n { cause: error }\n );\n }\n // Non-Error throw (rare — capnweb's deserializer always constructs Error\n // instances, but defensively handle anything else that bubbles up).\n // Coerce to an Error so the kind=unknown context still has a usable\n // originalMessage, and preserve the raw value as `cause`.\n const wrapped = new Error(String(error));\n throw createErrorFromResponse(\n buildTransportErrorResponse(wrapped) as unknown as ErrorResponse,\n { cause: error }\n );\n}\n\n/**\n * Inspect a transport-level Error's message and produce the ErrorResponse\n * that becomes an RPCTransportError. Pattern strings are pinned to the exact\n * messages emitted by capnweb's WebSocket transport (see capnweb's\n * src/websocket.ts) and our DeferredTransport in container-control/connection.ts —\n * notably the trailing period in `WebSocket connection failed.` matches\n * capnweb verbatim. The DeferredTransport tests in\n * tests/container-connection.test.ts pin the literal strings.\n */\nfunction buildTransportErrorResponse(\n error: Error\n): ErrorResponse<RPCTransportContext> {\n const message = error.message;\n const errorName = error.name;\n let kind: RPCTransportErrorKind = 'unknown';\n let closeCode: number | undefined;\n let closeReason: string | undefined;\n\n // First pass: classify by `error.name`. capnweb preserves the name\n // across the wire for the standard built-ins (see ERROR_TYPES in\n // capnweb's serialize.ts), and an unambiguous name beats substring\n // matching on a free-form message. It also handles the cross-realm\n // `instanceof` trap: a TypeError raised inside capnweb's serializer lives\n // in capnweb's realm, not the SDK's.\n if (errorName === 'TypeError') {\n // Only DeferredTransport / capnweb's WebSocket transport raises a\n // TypeError on the receive path — always a non-string frame.\n kind = 'invalid_frame';\n } else if (errorName === 'SyntaxError') {\n // capnweb's readLoop calls JSON.parse on each incoming frame; if the\n // peer sends garbage that's not parseable JSON, the SyntaxError flows\n // through abort() to every in-flight call.\n kind = 'protocol_error';\n } else {\n // Second pass: plain Errors. capnweb's transport layer and our\n // DeferredTransport both emit unnamed Errors with these specific\n // messages; the message is the only signal we have.\n const peerCloseMatch = message.match(\n /^Peer closed WebSocket: (\\d+) ?(.*)$/\n );\n if (peerCloseMatch) {\n kind = 'peer_closed';\n closeCode = Number(peerCloseMatch[1]);\n closeReason = peerCloseMatch[2] || undefined;\n } else if (message === 'WebSocket connection failed.') {\n kind = 'connection_failed';\n } else if (message.startsWith('WebSocket upgrade failed')) {\n // ContainerControlConnection.doConnect throws this when the HTTP upgrade\n // returns a non-101 status.\n kind = 'upgrade_failed';\n } else if (message === 'No WebSocket in upgrade response') {\n kind = 'upgrade_failed';\n } else if (\n message === 'RPC session was shut down by disposing the main stub' ||\n message === 'RPC was canceled because the RpcPromise was disposed.'\n ) {\n kind = 'session_disposed';\n }\n }\n\n const context: RPCTransportContext = {\n kind,\n originalMessage: message,\n errorName,\n ...(closeCode !== undefined ? { closeCode } : {}),\n ...(closeReason !== undefined ? { closeReason } : {})\n };\n return {\n code: ErrorCode.RPC_TRANSPORT_ERROR,\n message,\n context,\n httpStatus: getHttpStatus(ErrorCode.RPC_TRANSPORT_ERROR),\n suggestion: getSuggestion(\n ErrorCode.RPC_TRANSPORT_ERROR,\n context as unknown as Record<string, unknown>\n ),\n timestamp: new Date().toISOString()\n };\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 * ContainerControlClient 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 buildInterruptedOperationResponse(\n transportResponse: ErrorResponse<RPCTransportContext>,\n context: RPCTranslationContext\n): ErrorResponse<OperationInterruptedContext> | null {\n if (!context.operation) return null;\n const { kind } = transportResponse.context;\n if (\n kind !== 'session_disposed' &&\n kind !== 'peer_closed' &&\n kind !== 'connection_failed'\n ) {\n return null;\n }\n\n const interruptedContext: OperationInterruptedContext = {\n reason:\n kind === 'session_disposed' ? 'transport_disposed' : 'runtime_replaced',\n operation: context.operation,\n phase: 'rpc_call',\n admitted: 'unknown',\n retryable: false\n };\n const action =\n kind === 'session_disposed' ? 'was closing' : 'closed unexpectedly';\n\n return {\n code: ErrorCode.OPERATION_INTERRUPTED,\n message: `Sandbox operation ${context.operation} was interrupted while the runtime connection ${action}`,\n context: interruptedContext,\n httpStatus: getHttpStatus(ErrorCode.OPERATION_INTERRUPTED),\n suggestion: getSuggestion(\n ErrorCode.OPERATION_INTERRUPTED,\n interruptedContext as unknown as Record<string, unknown>\n ),\n timestamp: new Date().toISOString()\n };\n}\n\nfunction wrapStub<T extends object>(\n stub: T,\n domain: string,\n onCallStarted: () => void\n): 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 // Reflect.apply preserves the method call target because capnweb\n // stubs are Proxies that interpret .apply as an RPC property access.\n return (...args: unknown[]) => {\n onCallStarted();\n const operation =\n typeof prop === 'string' ? `${domain}.${prop}` : domain;\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((err: unknown) =>\n translateRPCError(err, { operation })\n );\n }\n return result;\n } catch (err) {\n translateRPCError(err, { operation });\n }\n };\n }\n });\n}\n\n// ---------------------------------------------------------------------------\n// Public client\n// ---------------------------------------------------------------------------\n\nexport interface ContainerControlClientOptions extends ContainerControlConnectionOptions {\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 increment the Container base class's\n * in-flight request counter, which makes `isActivityExpired()` skip the\n * 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 * Sandbox control facade backed by direct capnweb RPC.\n *\n * All operations call the container's SandboxAPI control interface directly\n * over capnweb, bypassing the HTTP handler/router layer entirely.\n *\n * Manages its own WebSocket lifecycle: a fresh `ContainerControlConnection` 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 ContainerControlClient {\n private readonly connOptions: ContainerControlConnectionOptions;\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: ContainerControlConnection | 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 constructor(options: ContainerControlClientOptions) {\n this.connOptions = {\n stub: options.stub,\n port: options.port,\n localMain: options.localMain,\n logger: options.logger,\n retryTimeoutMs: options.retryTimeoutMs,\n // Event-driven failure recovery: when the live WebSocket closes\n // or errors, tear the connection down inside the same turn of\n // the event loop so the next RPC call builds a fresh one. The\n // 1Hz busy-poll fallback can't be relied on here — `setInterval`\n // callbacks don't fire while the DO isolate sits idle between\n // requests, which is exactly the state the isolate enters after\n // every in-flight RPC rejects with a peer-closed error.\n onClose: () => {\n if (this.conn) this.destroyConnection();\n }\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 one when the client is disconnected.\n * Starts the busy-poll timer the first time a connection is materialized.\n */\n private getConnection(): ContainerControlConnection {\n if (!this.conn) {\n this.conn = new ContainerControlConnection(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 // The WebSocket upgrade is still in progress (or a freshly\n // recreated connection hasn't finished doConnect() yet). Sends\n // are queued in the deferred transport and will flush once the\n // upgrade resolves — don't tear down here.\n //\n // Failure recovery (a peer that disconnected after we connected)\n // is handled synchronously by `ContainerControlConnection`'s\n // `onClose` callback firing `destroyConnection()`, which nulls\n // `this.conn` before the poller could observe it.\n return;\n }\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 RPC 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 }\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 keep capnweb's recursive\n // type machinery out of .d.ts output.\n\n get commands(): SandboxCommandsAPI {\n return wrapStub(\n this.getConnection().rpc().commands,\n 'commands',\n this.renewActivity\n );\n }\n get files(): SandboxFilesAPI {\n return wrapStub(\n this.getConnection().rpc().files,\n 'files',\n this.renewActivity\n ) as unknown as SandboxFilesAPI;\n }\n get processes(): SandboxProcessesAPI {\n return wrapStub(\n this.getConnection().rpc().processes,\n 'processes',\n this.renewActivity\n );\n }\n get ports(): SandboxPortsAPI {\n return wrapStub(\n this.getConnection().rpc().ports,\n 'ports',\n this.renewActivity\n );\n }\n get utils(): SandboxUtilsAPI {\n return wrapStub(\n this.getConnection().rpc().utils,\n 'utils',\n this.renewActivity\n );\n }\n get backup(): SandboxBackupAPI {\n return wrapStub(\n this.getConnection().rpc().backup,\n 'backup',\n this.renewActivity\n );\n }\n get watch(): SandboxWatchAPI {\n return wrapStub(\n this.getConnection().rpc().watch,\n 'watch',\n this.renewActivity\n );\n }\n get tunnels(): SandboxTunnelsAPI {\n return wrapStub(\n this.getConnection().rpc().tunnels,\n 'tunnels',\n this.renewActivity\n );\n }\n get terminals(): SandboxTerminalsAPI {\n return wrapStub(\n this.getConnection().rpc().terminals,\n 'terminals',\n this.renewActivity\n );\n }\n get extensions(): SandboxExtensionsAPI {\n return wrapStub(\n this.getConnection().rpc().extensions,\n 'extensions',\n this.renewActivity\n );\n }\n\n /**\n * Update the upgrade retry budget. Applies to the current connection\n * (if any) and is remembered for any future connections created after the\n * client is torn down and reconnected.\n */\n setRetryTimeoutMs(ms: number): void {\n this.connOptions.retryTimeoutMs = ms;\n this.conn?.setRetryTimeoutMs(ms);\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","export type PreviewTCPPort = {\n fetch(\n input: Request | string,\n init?: Request | RequestInit\n ): Promise<Response>;\n};\n\nexport type PreviewForwardingLifecycle = {\n beginForward(): () => void;\n renewActivity(): void;\n};\n\nexport type PreviewForwardingResult =\n | { status: 'response'; response: Response }\n | { status: 'network-lost' };\n\nexport async function forwardPreviewRequest(\n tcpPort: PreviewTCPPort,\n request: Request,\n lifecycle: PreviewForwardingLifecycle\n): Promise<PreviewForwardingResult> {\n const containerURL = request.url.replace('https:', 'http:');\n const settleForward = lifecycle.beginForward();\n\n try {\n const response = await tcpPort.fetch(containerURL, request);\n\n if (response.webSocket !== null) {\n return {\n status: 'response',\n response: bridgePreviewWebSocket(response, lifecycle, settleForward)\n };\n }\n\n if (response.body !== null) {\n const { readable, writable } = new TransformStream();\n response.body\n .pipeTo(writable)\n .finally(settleForward)\n .catch(() => {});\n return { status: 'response', response: new Response(readable, response) };\n }\n\n settleForward();\n return { status: 'response', response };\n } catch (error) {\n settleForward();\n if (\n error instanceof Error &&\n error.message.includes('Network connection lost.')\n ) {\n return { status: 'network-lost' };\n }\n throw error;\n }\n}\n\nfunction bridgePreviewWebSocket(\n response: Response,\n lifecycle: PreviewForwardingLifecycle,\n settleForward: () => void\n): Response {\n const containerWebSocket = response.webSocket;\n if (containerWebSocket === null) {\n settleForward();\n return response;\n }\n\n const [client, server] = Object.values(new WebSocketPair());\n let settled = false;\n const settle = () => {\n if (!settled) {\n settled = true;\n settleForward();\n }\n };\n\n containerWebSocket.accept();\n server.accept();\n\n server.addEventListener('message', async (event) => {\n lifecycle.renewActivity();\n try {\n const data =\n event.data instanceof Blob\n ? await event.data.arrayBuffer()\n : event.data;\n containerWebSocket.send(data);\n } catch {\n server.close(1011, 'Failed to forward message to container');\n }\n });\n\n containerWebSocket.addEventListener('message', async (event) => {\n lifecycle.renewActivity();\n try {\n const data =\n event.data instanceof Blob\n ? await event.data.arrayBuffer()\n : event.data;\n server.send(data);\n } catch {\n containerWebSocket.close(1011, 'Failed to forward message to client');\n }\n });\n\n server.addEventListener('close', (event) => {\n settle();\n const code = event.code === 1005 || event.code === 1006 ? 1000 : event.code;\n containerWebSocket.close(code, event.reason);\n });\n\n containerWebSocket.addEventListener('close', (event) => {\n settle();\n const code = event.code === 1005 || event.code === 1006 ? 1000 : event.code;\n server.close(code, event.reason);\n });\n\n server.addEventListener('error', () => {\n settle();\n containerWebSocket.close(1011, 'Client WebSocket error');\n });\n\n containerWebSocket.addEventListener('error', () => {\n settle();\n server.close(1011, 'Container WebSocket error');\n });\n\n return new Response(null, {\n status: response.status,\n webSocket: client,\n headers: response.headers\n });\n}\n","import { PREVIEW_PROXY_HEADERS } from './protocol';\n\nexport interface BuildPreviewProxyRequestOptions {\n port: number;\n sandboxId: string;\n sandboxName: string | null;\n}\n\nexport function buildPreviewProxyRequest(\n request: Request,\n { port, sandboxId, sandboxName }: BuildPreviewProxyRequestOptions\n): Request {\n const url = new URL(request.url);\n const proxyURL = `http://localhost:${port}${url.pathname}${url.search}`;\n const headers = stripPreviewProxyHeaders(request.headers);\n\n headers.set('X-Original-URL', request.url);\n headers.set('X-Forwarded-Host', url.hostname);\n headers.set('X-Forwarded-Proto', url.protocol.replace(':', ''));\n headers.set('X-Sandbox-Name', sandboxName ?? sandboxId);\n\n const upgradeHeader = request.headers.get('Upgrade');\n if (upgradeHeader?.toLowerCase() === 'websocket') {\n return new Request(request, {\n headers,\n redirect: 'manual'\n });\n }\n\n return 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'\n });\n}\n\nfunction stripPreviewProxyHeaders(source: Headers): Headers {\n const headers = new Headers(source);\n for (const header of PREVIEW_PROXY_HEADERS) {\n headers.delete(header);\n }\n return headers;\n}\n","import type { RuntimeScoped } from '../current-runtime-identity';\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 */\nexport type PortTokenEntry = {\n token: string;\n name?: string;\n};\n\nexport type PreviewPortActivation = RuntimeScoped<{\n token: string;\n}>;\n\nexport type PreviewPortActivations = Record<string, PreviewPortActivation>;\n\nexport type CurrentPreviewPort = {\n port: number;\n entry: PortTokenEntry;\n};\n\nexport type PreviewStateStorage = Pick<\n DurableObjectStorage | DurableObjectTransaction,\n 'get' | 'put' | 'delete'\n>;\n\nexport const PORT_TOKENS_STORAGE_KEY = 'portTokens';\nexport const ACTIVE_PREVIEW_PORTS_STORAGE_KEY = 'activePreviewPorts';\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 */\nexport async function readPortTokens(\n storage: PreviewStateStorage\n): Promise<Record<string, PortTokenEntry>> {\n const raw =\n (await storage.get<Record<string, string | PortTokenEntry>>(\n PORT_TOKENS_STORAGE_KEY\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\nexport async function readActivePreviewPorts(\n storage: PreviewStateStorage\n): Promise<PreviewPortActivations> {\n return (\n (await storage.get<PreviewPortActivations>(\n ACTIVE_PREVIEW_PORTS_STORAGE_KEY\n )) ?? {}\n );\n}\n\nexport async function writeActivePreviewPorts(\n activations: PreviewPortActivations,\n storage: PreviewStateStorage\n): Promise<void> {\n if (Object.keys(activations).length === 0) {\n await storage.delete(ACTIVE_PREVIEW_PORTS_STORAGE_KEY);\n return;\n }\n\n await storage.put(ACTIVE_PREVIEW_PORTS_STORAGE_KEY, activations);\n}\n\nexport async function readPreviewState(storage: PreviewStateStorage): Promise<{\n tokens: Record<string, PortTokenEntry>;\n activations: PreviewPortActivations;\n}> {\n const [tokens, activations] = await Promise.all([\n readPortTokens(storage),\n readActivePreviewPorts(storage)\n ]);\n return { tokens, activations };\n}\n\nexport async function clearActivePreviewPorts(\n storage: PreviewStateStorage\n): Promise<void> {\n await storage.delete(ACTIVE_PREVIEW_PORTS_STORAGE_KEY);\n}\n","import { SandboxSecurityError } from '../security';\n\nconst CUSTOM_TOKEN_PATTERN = /^[a-z0-9_]+$/;\n\nexport function assertValidCustomPreviewToken(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 (!CUSTOM_TOKEN_PATTERN.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\nexport function generatePreviewToken(): string {\n const array = new Uint8Array(12);\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\nexport function previewTokensMatch(expected: string, actual: string): boolean {\n const encoder = new TextEncoder();\n const a = encoder.encode(expected);\n const b = encoder.encode(actual);\n\n try {\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","import { type Logger, logCanonicalEvent } from '@repo/shared';\nimport type {\n CurrentRuntimeIdentity,\n RuntimeIdentity\n} from '../current-runtime-identity';\nimport type { ErrorResponse } from '../errors';\nimport { CustomDomainRequiredError, ErrorCode } from '../errors';\nimport { SandboxSecurityError, validatePort } from '../security';\nimport { forwardPreviewRequest, type PreviewTCPPort } from './forwarding';\nimport { readPreviewProxyMetadata } from './protocol';\nimport { buildPreviewProxyRequest } from './proxy-request';\nimport { constructPreviewURL } from './route';\nimport {\n type CurrentPreviewPort,\n clearActivePreviewPorts,\n PORT_TOKENS_STORAGE_KEY,\n type PortTokenEntry,\n type PreviewPortActivations,\n readActivePreviewPorts,\n readPortTokens,\n readPreviewState,\n writeActivePreviewPorts\n} from './state';\nimport {\n assertValidCustomPreviewToken,\n generatePreviewToken,\n previewTokensMatch\n} from './token';\n\nexport type PreviewForwardingContainer = {\n running?: boolean;\n getTcpPort(port: number): PreviewTCPPort;\n};\n\ntype StalePreviewRuntime = {\n status: 'stale';\n reason:\n | 'runtime-not-healthy'\n | 'runtime-not-running'\n | 'missing-runtime-id'\n | 'missing-activation'\n | 'runtime-mismatch'\n | 'token-mismatch';\n containerStatus?: string;\n};\n\ntype PreviewURLRuntimeValidation =\n | { status: 'invalid' }\n | StalePreviewRuntime\n | { status: 'active'; runtime: RuntimeIdentity };\n\ntype PreviewRuntimeAvailability =\n | StalePreviewRuntime\n | { status: 'active'; runtime: RuntimeIdentity };\n\ntype PreviewRuntimeSnapshot = {\n containerStatus: string;\n containerRunning: boolean;\n tokens: Record<string, PortTokenEntry>;\n activations: PreviewPortActivations;\n runtime: RuntimeIdentity | null;\n};\n\nexport interface PreviewServiceDeps {\n storage: DurableObjectStorage;\n logger: Logger;\n currentRuntime: CurrentRuntimeIdentity;\n getContainerState(): Promise<{ status: string }>;\n getForwardingContainer(): PreviewForwardingContainer | undefined;\n ensureRuntimeActiveForPreview(): Promise<RuntimeIdentity>;\n getSandboxName(): string | null;\n getNormalizeID(): boolean;\n beginForward(): () => void;\n renewActivity(): void;\n}\n\nexport class PreviewService {\n constructor(private readonly deps: PreviewServiceDeps) {}\n\n async clearActivePreviewPorts(): Promise<void> {\n await clearActivePreviewPorts(this.deps.storage);\n }\n\n async clearPreviewState(): Promise<void> {\n await this.deps.storage.transaction(async (txn) => {\n await txn.delete(PORT_TOKENS_STORAGE_KEY);\n await clearActivePreviewPorts(txn);\n });\n }\n\n async exposePort(\n port: number,\n options: { name?: string; hostname: string; token?: string }\n ): Promise<{ url: string; port: number; name?: string }> {\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 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 const sandboxName = this.deps.getSandboxName();\n if (!sandboxName) {\n throw new Error(\n 'Sandbox name not available. Ensure sandbox is accessed through getSandbox()'\n );\n }\n\n if (options.token !== undefined) {\n assertValidCustomPreviewToken(options.token);\n }\n\n const runtime = await this.deps.ensureRuntimeActiveForPreview();\n await this.deps.currentRuntime.assertActive(runtime);\n\n const token = await this.deps.storage.transaction(async (txn) => {\n const tokens = await readPortTokens(txn);\n const existingEntry = tokens[port.toString()];\n const nextToken =\n options.token ?? existingEntry?.token ?? generatePreviewToken();\n\n const existingPort = Object.entries(tokens).find(\n ([p, entry]) => entry.token === nextToken && p !== port.toString()\n );\n if (existingPort) {\n throw new SandboxSecurityError(\n `Token '${nextToken}' is already in use by port ${existingPort[0]}. Please use a different token.`\n );\n }\n\n const activations = await readActivePreviewPorts(txn);\n\n tokens[port.toString()] = { token: nextToken, name: options.name };\n activations[port.toString()] = runtime.scope({ token: nextToken });\n await Promise.all([\n txn.put(PORT_TOKENS_STORAGE_KEY, tokens),\n writeActivePreviewPorts(activations, txn)\n ]);\n\n return nextToken;\n });\n\n await this.deps.currentRuntime.assertActive(runtime);\n\n const url = constructPreviewURL({\n port,\n sandboxId: sandboxName,\n effectiveId: sandboxName,\n hostname: options.hostname,\n token,\n normalizeId: this.deps.getNormalizeID()\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.deps.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): Promise<void> {\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 await this.deps.storage.transaction(async (txn) => {\n const tokens = await readPortTokens(txn);\n if (tokens[port.toString()]) {\n delete tokens[port.toString()];\n await txn.put(PORT_TOKENS_STORAGE_KEY, tokens);\n }\n\n const activations = await readActivePreviewPorts(txn);\n if (activations[port.toString()]) {\n delete activations[port.toString()];\n await writeActivePreviewPorts(activations, txn);\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.deps.logger, {\n event: 'port.unexpose',\n outcome,\n port,\n durationMs: Date.now() - unexposeStartTime,\n error: caughtError\n });\n }\n }\n\n async getExposedPorts(\n hostname: string\n ): Promise<Array<{ url: string; port: number; status: 'active' }>> {\n const sandboxName = this.deps.getSandboxName();\n if (!sandboxName) {\n throw new Error(\n 'Sandbox name not available. Ensure sandbox is accessed through getSandbox()'\n );\n }\n\n const activePorts = await this.getCurrentPreviewPorts();\n return activePorts.map(({ port, entry }) => ({\n url: constructPreviewURL({\n port,\n sandboxId: sandboxName,\n effectiveId: sandboxName,\n hostname,\n token: entry.token,\n normalizeId: this.deps.getNormalizeID()\n }),\n port,\n status: 'active' as const\n }));\n }\n\n async isPortExposed(port: number): Promise<boolean> {\n if (!validatePort(port)) {\n return false;\n }\n\n const activePorts = await this.getCurrentPreviewPorts();\n return activePorts.some((activePort) => activePort.port === port);\n }\n\n async validatePortToken(port: number, token: string): Promise<boolean> {\n const tokens = await readPortTokens(this.deps.storage);\n const entry = tokens[port.toString()];\n if (!entry) {\n return false;\n }\n\n return previewTokensMatch(entry.token, token);\n }\n\n async proxyPreviewRequest(request: Request): Promise<Response> {\n const target = readPreviewProxyMetadata(request);\n if (!target) {\n return this.invalidPreviewTokenResponse();\n }\n\n const { port, token, sandboxId } = target;\n const proxyRequest = buildPreviewProxyRequest(request, {\n port,\n sandboxId,\n sandboxName: this.deps.getSandboxName()\n });\n\n const validation = await this.validatePreviewURLForRuntime(port, token);\n if (validation.status === 'invalid') {\n return this.invalidPreviewTokenResponse();\n }\n\n if (validation.status === 'stale') {\n this.deps.logger.warn('Stale preview URL blocked', {\n port,\n sandboxId,\n containerStatus: validation.containerStatus,\n reason: validation.reason,\n method: request.method\n });\n return this.stalePreviewURLResponse();\n }\n\n return await this.fetchPreviewIfRunning(\n proxyRequest,\n port,\n validation.runtime\n );\n }\n\n private invalidPreviewTokenResponse(): Response {\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 private stalePreviewURLResponse(): Response {\n return new Response(\n JSON.stringify({\n error: 'Preview URL is stale because the sandbox runtime is not active',\n code: 'STALE_PREVIEW_URL'\n }),\n {\n status: 410,\n headers: {\n 'Content-Type': 'application/json'\n }\n }\n );\n }\n\n private async fetchPreviewIfRunning(\n request: Request,\n port: number,\n runtime: RuntimeIdentity\n ): Promise<Response> {\n const container = this.deps.getForwardingContainer();\n const state = await this.deps.getContainerState();\n\n if (!container?.running || state.status !== 'healthy') {\n return this.stalePreviewURLResponse();\n }\n\n if (!(await this.deps.currentRuntime.isActive(runtime))) {\n return this.stalePreviewURLResponse();\n }\n\n const tcpPort = container.getTcpPort(port);\n\n const result = await forwardPreviewRequest(tcpPort, request, {\n beginForward: () => this.deps.beginForward(),\n renewActivity: () => this.deps.renewActivity()\n });\n\n if (result.status === 'network-lost') {\n if (!(await this.deps.currentRuntime.isActive(runtime))) {\n return this.stalePreviewURLResponse();\n }\n\n return new Response('Container suddenly disconnected, try again', {\n status: 500\n });\n }\n\n return result.response;\n }\n\n private async validatePreviewURLForRuntime(\n port: number,\n token: string\n ): Promise<PreviewURLRuntimeValidation> {\n const snapshot = await this.readRuntimeSnapshot();\n const entry = snapshot.tokens[port.toString()];\n if (!entry) {\n return { status: 'invalid' };\n }\n\n const tokenMatches = previewTokensMatch(entry.token, token);\n if (!tokenMatches) {\n return { status: 'invalid' };\n }\n\n const availability = this.getRuntimeAvailability(snapshot);\n if (availability.status === 'stale') {\n return availability;\n }\n\n const activation = snapshot.activations[port.toString()];\n if (!activation) {\n return {\n status: 'stale',\n reason: 'missing-activation',\n containerStatus: snapshot.containerStatus\n };\n }\n\n if (!availability.runtime.owns(activation)) {\n return {\n status: 'stale',\n reason: 'runtime-mismatch',\n containerStatus: snapshot.containerStatus\n };\n }\n\n const activationTokenMatches = previewTokensMatch(activation.token, token);\n if (!activationTokenMatches) {\n this.deps.logger.warn('Preview URL activation token mismatch', {\n port,\n runtimeIdentityID: availability.runtime.id\n });\n return {\n status: 'stale',\n reason: 'token-mismatch',\n containerStatus: snapshot.containerStatus\n };\n }\n\n return { status: 'active', runtime: availability.runtime };\n }\n\n private async getCurrentPreviewPorts(): Promise<CurrentPreviewPort[]> {\n const snapshot = await this.readRuntimeSnapshot();\n const availability = this.getRuntimeAvailability(snapshot);\n if (availability.status === 'stale') {\n return [];\n }\n\n const activePorts: CurrentPreviewPort[] = [];\n\n for (const [portKey, activation] of Object.entries(snapshot.activations)) {\n const port = Number.parseInt(portKey, 10);\n const entry = snapshot.tokens[portKey];\n if (!entry || !Number.isInteger(port) || !validatePort(port)) {\n continue;\n }\n\n if (!availability.runtime.owns(activation)) {\n continue;\n }\n\n if (!previewTokensMatch(entry.token, activation.token)) {\n continue;\n }\n\n activePorts.push({ port, entry });\n }\n\n return activePorts.sort((a, b) => a.port - b.port);\n }\n\n private async readRuntimeSnapshot(): Promise<PreviewRuntimeSnapshot> {\n const containerState = await this.deps.getContainerState();\n const containerRunning =\n this.deps.getForwardingContainer()?.running === true;\n const { tokens, activations, runtime } =\n await this.deps.storage.transaction(async (txn) => {\n const [previewState, runtime] = await Promise.all([\n readPreviewState(txn),\n this.deps.currentRuntime.getStored(txn)\n ]);\n return { ...previewState, runtime };\n });\n\n return {\n containerStatus: containerState.status,\n containerRunning,\n tokens,\n activations,\n runtime\n };\n }\n\n private getRuntimeAvailability(\n snapshot: PreviewRuntimeSnapshot\n ): PreviewRuntimeAvailability {\n if (snapshot.containerStatus !== 'healthy') {\n return {\n status: 'stale',\n reason: 'runtime-not-healthy',\n containerStatus: snapshot.containerStatus\n };\n }\n\n if (!snapshot.containerRunning) {\n return {\n status: 'stale',\n reason: 'runtime-not-running',\n containerStatus: snapshot.containerStatus\n };\n }\n\n if (!snapshot.runtime) {\n return {\n status: 'stale',\n reason: 'missing-runtime-id',\n containerStatus: snapshot.containerStatus\n };\n }\n\n return { status: 'active', runtime: snapshot.runtime };\n }\n}\n","/**\n * Unified `SandboxProcess` implementation.\n *\n * Wraps the existing `ProcessService` plumbing (start / kill / getProcess /\n * getProcessLogs / streamProcessLogs / waitForLog / waitForPort /\n * waitForExit) into a single handle whose shape mirrors `ExecProcess` from\n * the Cloudflare Containers runtime contract.\n *\n */\n\nimport type {\n LogEvent,\n ProcessStatus,\n SandboxExecBufferOutput,\n SandboxExecOutput,\n SandboxExecStringOutput,\n SandboxOutputEncoding,\n SandboxOutputMode,\n SandboxProcess,\n SandboxProcessPromise,\n SandboxStderrMode,\n WaitForExitResult,\n WaitForLogResult,\n WaitForPortOptions\n} from '@repo/shared';\nimport { parseSSEStream } from './sse-parser';\n\n// ---------------------------------------------------------------------------\n// Dependency surface\n//\n// The set of operations `SandboxProcessImpl` needs from the owning Sandbox.\n// Decoupled into an interface so the class can be unit-tested without\n// instantiating a Durable Object, and so we don't grow `sandbox.ts` further.\n// ---------------------------------------------------------------------------\n\nexport interface SandboxProcessDeps {\n /** Open the multiplexed log stream for a process (replay-then-tail). */\n openLogStream(id: string): Promise<ReadableStream<Uint8Array>>;\n /** Read accumulated logs as decoded strings. */\n readLogs(id: string): Promise<{ stdout: string; stderr: string }>;\n /** Fetch the current `ProcessStatus`. */\n fetchStatus(id: string): Promise<ProcessStatus>;\n /** Terminate the process via the container. */\n killProcess(id: string, signal: number): Promise<void>;\n /** Wait for a port to become ready, scoped to the process lifetime. */\n waitForPort(\n processId: string,\n command: string,\n port: number,\n options?: WaitForPortOptions\n ): Promise<void>;\n /** Match a log pattern against the live stream (and replay). */\n waitForLogPattern(\n processId: string,\n command: string,\n pattern: string | RegExp,\n timeoutMs?: number\n ): Promise<WaitForLogResult>;\n /** Wait for the process to reach a terminal state. */\n waitForProcessExit(\n processId: string,\n command: string,\n timeoutMs?: number\n ): Promise<WaitForExitResult>;\n}\n\n// ---------------------------------------------------------------------------\n// Demultiplexer: ReadableStream<Uint8Array> of SSE log frames →\n// { stdout, stderr } ReadableStream<Uint8Array> + exitCode Promise.\n//\n// Frame schema (see packages/sandbox-container/src/control-plane/api.ts\n// `streamProcessLogs`):\n// { type: 'stdout', data, processId, timestamp }\n// { type: 'stderr', data, processId, timestamp }\n// { type: 'exit', exitCode, processId, timestamp }\n// { type: 'error', data?, processId, timestamp } // optional\n//\n// The two byte streams emit decoded `data` strings as Uint8Array chunks.\n// The exitCode promise resolves on the first 'exit' frame; if the stream\n// closes without one, it rejects.\n// ---------------------------------------------------------------------------\n\ninterface DemuxResult {\n stdout: ReadableStream<Uint8Array> | null;\n stderr: ReadableStream<Uint8Array> | null;\n exitCode: Promise<number>;\n}\n\nfunction demultiplexLogStream(\n source: ReadableStream<Uint8Array>,\n options: {\n stdout: 'pipe' | 'ignore';\n stderr: 'pipe' | 'ignore' | 'combined';\n }\n): DemuxResult {\n const encoder = new TextEncoder();\n\n const controllers: {\n stdout: ReadableStreamDefaultController<Uint8Array> | null;\n stderr: ReadableStreamDefaultController<Uint8Array> | null;\n } = { stdout: null, stderr: null };\n let exitResolve!: (code: number) => void;\n let exitReject!: (err: Error) => void;\n const exitCode = new Promise<number>((res, rej) => {\n exitResolve = res;\n exitReject = rej;\n });\n let settled = false;\n const resolveExit = (code: number) => {\n if (settled) return;\n settled = true;\n exitResolve(code);\n };\n const rejectExit = (err: Error) => {\n if (settled) return;\n settled = true;\n exitReject(err);\n };\n\n const wantStdout = options.stdout === 'pipe';\n const wantStderr = options.stderr === 'pipe';\n const combined = options.stderr === 'combined';\n\n const stdoutStream = wantStdout\n ? new ReadableStream<Uint8Array>({\n start(c) {\n controllers.stdout = c;\n }\n })\n : null;\n const stderrStream = wantStderr\n ? new ReadableStream<Uint8Array>({\n start(c) {\n controllers.stderr = c;\n }\n })\n : null;\n\n void (async () => {\n try {\n for await (const event of parseSSEStream<LogEvent>(source)) {\n if (event.type === 'stdout' && event.data) {\n controllers.stdout?.enqueue(encoder.encode(event.data));\n } else if (event.type === 'stderr' && event.data) {\n if (combined) {\n controllers.stdout?.enqueue(encoder.encode(event.data));\n } else {\n controllers.stderr?.enqueue(encoder.encode(event.data));\n }\n } else if (event.type === 'exit') {\n resolveExit(event.exitCode ?? 0);\n } else if (event.type === 'error') {\n rejectExit(\n new Error(event.data || 'Process stream emitted error frame')\n );\n }\n }\n // Stream ended; if no exit frame was seen, treat as abnormal.\n if (!settled) {\n rejectExit(new Error('Process stream closed without exit event'));\n }\n } catch (err) {\n rejectExit(err instanceof Error ? err : new Error(String(err)));\n } finally {\n try {\n controllers.stdout?.close();\n } catch {\n /* already closed */\n }\n try {\n controllers.stderr?.close();\n } catch {\n /* already closed */\n }\n }\n })();\n\n return { stdout: stdoutStream, stderr: stderrStream, exitCode };\n}\n\n/**\n * Wrap `inner` as a Promise that fires `onFirstAwait` exactly once, the\n * first time any of `.then` / `.catch` / `.finally` is called. Plain field\n * access (e.g. `obj.exitCode` evaluated by a property spread) does NOT\n * trigger it.\n *\n * Used to make `SandboxProcess.exitCode` lazily kick off the demux\n * subscription only when a caller actually awaits it.\n */\nfunction makeLazyExitPromise(\n inner: Promise<number>,\n onFirstAwait: () => void\n): Promise<number> {\n let triggered = false;\n const trigger = () => {\n if (triggered) return;\n triggered = true;\n onFirstAwait();\n };\n // The whole point of this helper is that `.then` has a side effect of\n // triggering the demux subscription; the Biome rule against `then` on\n // plain objects doesn't apply because the result IS a thenable used as\n // a Promise.\n const lazy = {\n // biome-ignore lint/suspicious/noThenProperty: lazy thenable by design.\n then(onFulfilled, onRejected) {\n trigger();\n return inner.then(onFulfilled, onRejected);\n },\n catch(onRejected) {\n trigger();\n return inner.catch(onRejected);\n },\n finally(onFinally) {\n trigger();\n return inner.finally(onFinally);\n },\n [Symbol.toStringTag]: 'Promise'\n } as Promise<number>;\n return lazy;\n}\n\n// ---------------------------------------------------------------------------\n// SandboxProcessImpl\n// ---------------------------------------------------------------------------\n\nexport interface SandboxProcessInit {\n id: string;\n pid: number;\n command: string;\n sessionId?: string;\n startTime: Date;\n /** Initial status reported by the container after `startProcess`. */\n status: ProcessStatus;\n /** Indicates whether this handle was created by `exec()` or `getProcess()`. */\n ownership: 'owner' | 'attached';\n stdout: SandboxOutputMode;\n stderr: SandboxStderrMode;\n /**\n * Stdin handle exposed to the caller. `null` when no stdin was\n * requested (the container redirects from `/dev/null`); a\n * `WritableStream<Uint8Array>` when `stdin: \"pipe\"` was requested.\n *\n * The SDK constructs a `TransformStream` whose readable half is sent\n * over RPC as the process's stdin source, and whose writable half is\n * surfaced here so callers can write bytes incrementally. Closing the\n * writer flushes EOF into the spawned command.\n */\n stdin: WritableStream<Uint8Array> | null;\n}\n\n/**\n * Internal-only implementation. Class instances cannot cross the workerd\n * JsRpc boundary directly (workerd refuses with \"Could not serialize object\n * of type 'SandboxProcessImpl'\"). Callers in the Durable Object that need\n * to send a `SandboxProcess` to a Worker must go through\n * `toRpcSandboxProcess()`, which produces a plain object literal whose\n * methods workerd auto-stubs and whose fields/streams cross by value.\n */\nexport class SandboxProcessImpl implements SandboxProcess {\n readonly id: string;\n readonly pid: number;\n readonly command: string;\n readonly sessionId?: string;\n readonly startTime: Date;\n /**\n * Writer-side of the SDK-managed stdin `TransformStream`. Not declared\n * `readonly` because the field is assigned in the constructor from\n * `init.stdin`; the public `SandboxProcess` contract narrows it to\n * `readonly` via the interface.\n */\n readonly stdin: WritableStream<Uint8Array> | null;\n readonly stdout: ReadableStream<Uint8Array> | null;\n readonly stderr: ReadableStream<Uint8Array> | null;\n\n private readonly deps: SandboxProcessDeps;\n private readonly stdoutMode: SandboxOutputMode;\n private readonly stderrMode: SandboxStderrMode;\n /**\n * Memoised demultiplex result. Initialised lazily on first access to\n * `stdout` / `stderr` / `exitCode` / `output()` so handles whose caller\n * only uses `kill()`, `status()`, `getLogs()`, or `waitForLog/Port/Exit`\n * never open the log SSE stream.\n */\n private demuxPromise?: Promise<DemuxResult>;\n private readonly exitResolvers: {\n promise: Promise<number>;\n resolve: (code: number) => void;\n reject: (err: Error) => void;\n };\n private terminalStatus?: ProcessStatus;\n\n constructor(init: SandboxProcessInit, deps: SandboxProcessDeps) {\n this.id = init.id;\n this.pid = init.pid;\n this.command = init.command;\n this.sessionId = init.sessionId;\n this.startTime = init.startTime;\n this.deps = deps;\n this.stdoutMode = init.stdout;\n this.stderrMode = init.stderr;\n // `init.status` is the at-spawn snapshot; we only retain the terminal\n // status once exit settles.\n void init.status;\n // Owner and re-attached handles both consume the same replay-then-tail\n // process log stream.\n void init.ownership;\n\n // Writer-side of the SDK-managed stdin `TransformStream` (see\n // `SandboxProcessInit.stdin`). `null` for handles that didn't request\n // stdin (`stdin === \"ignore\"` semantically) and for re-attached\n // handles (the original owner holds the writer).\n this.stdin = init.stdin;\n\n // Lazy `stdout` / `stderr`: a `ReadableStream` whose `start()` triggers\n // the demux on first read. ReadableStream's `start()` is invoked when\n // a consumer first attaches a reader, so the underlying RPC stays cold\n // until somebody reads.\n this.stdout =\n this.stdoutMode === 'ignore' ? null : this.makeLazyChannel('stdout');\n this.stderr =\n this.stderrMode === 'ignore' || this.stderrMode === 'combined'\n ? null\n : this.makeLazyChannel('stderr');\n\n // Exit code is a Promise that settles when the underlying demux's\n // `exit` event fires. We expose our own deferred so callers can\n // `await proc.exitCode` without first touching a stream.\n let resolve!: (code: number) => void;\n let reject!: (err: Error) => void;\n const promise = new Promise<number>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n // Mark as observed so node doesn't yell about unhandled rejections\n // for callers who never await it (e.g. fire-and-forget kill paths).\n promise.catch(() => {});\n this.exitResolvers = { promise, resolve, reject };\n // Lazy thenable: triggers `ensureDemux()` on first `.then` /\n // `.catch` / `.finally`. Plain property reads (e.g. spreading into\n // a DTO for RPC) do not trigger anything.\n this.exitCode = makeLazyExitPromise(promise, () => void this.ensureDemux());\n }\n\n /**\n * Promise resolving to the process exit code.\n *\n * Wraps the underlying deferred so the first call to `.then` / `.catch` /\n * `.finally` (i.e. `await proc.exitCode`) lazily triggers the demux\n * subscription. Plain property reads (e.g. `proc.exitCode` evaluated by\n * `toRpcSandboxProcess` while marshalling the DTO across the DO RPC\n * boundary) do NOT trigger the demux — only awaits do. This is required\n * so handles that only call `kill()` / `waitForLog()` / `waitForPort()`\n * never open the log SSE.\n */\n readonly exitCode: Promise<number>;\n\n // ---- lazy demux plumbing -------------------------------------------------\n\n private ensureDemux(): Promise<DemuxResult> {\n if (!this.demuxPromise) {\n this.demuxPromise = (async () => {\n const source = await this.deps.openLogStream(this.id);\n const demux = demultiplexLogStream(source, {\n stdout: this.stdoutMode,\n stderr: this.stderrMode\n });\n // Wire the demux's exit event into our deferred so consumers\n // awaiting `proc.exitCode` don't need to also drain a stream.\n demux.exitCode.then(\n (code) => {\n this.terminalStatus =\n code === 0 ? 'completed' : code < 0 ? 'killed' : 'failed';\n this.exitResolvers.resolve(code);\n },\n (err) => {\n this.terminalStatus = 'error';\n this.exitResolvers.reject(\n err instanceof Error ? err : new Error(String(err))\n );\n }\n );\n return demux;\n })();\n }\n return this.demuxPromise;\n }\n\n /**\n * Build the user-facing `proc.stdout` / `proc.stderr` stream as a thin\n * proxy that initiates demultiplexing on first read. The proxy reader\n * forwards from the demuxed source stream and propagates close/cancel.\n */\n private makeLazyChannel(\n which: 'stdout' | 'stderr'\n ): ReadableStream<Uint8Array> {\n let reader: ReadableStreamDefaultReader<Uint8Array> | null = null;\n\n const acquireReader =\n async (): Promise<ReadableStreamDefaultReader<Uint8Array> | null> => {\n if (reader) return reader;\n const demux = await this.ensureDemux();\n const source = which === 'stdout' ? demux.stdout : demux.stderr;\n if (!source) return null;\n reader = source.getReader();\n return reader;\n };\n\n return new ReadableStream<Uint8Array>(\n {\n pull: async (controller) => {\n const r = await acquireReader();\n if (!r) {\n controller.close();\n return;\n }\n try {\n const { value, done } = await r.read();\n if (done) controller.close();\n else if (value) controller.enqueue(value);\n } catch (err) {\n controller.error(err);\n }\n },\n cancel: async (reason) => {\n try {\n await reader?.cancel(reason);\n } catch {\n /* already cancelled */\n }\n }\n },\n // highWaterMark: 0 keeps the queue empty until a consumer reads, so the\n // underlying log SSE connection is never opened for handles that nobody\n // reads (e.g. callers using only `kill()` / `waitForPort()`). Without\n // this, the default highWaterMark of 1 triggers `pull` at construction\n // time.\n new CountQueuingStrategy({ highWaterMark: 0 })\n );\n }\n\n // ---- ExecProcess parity --------------------------------------------------\n\n kill(signal: number | string = 15 /* SIGTERM */): void {\n const signalNumber = normalizeSignal(signal);\n // Fire-and-forget to match the synchronous `ExecProcess.kill` shape.\n void this.deps.killProcess(this.id, signalNumber).catch(() => {\n /* swallow; container-side already logs */\n });\n }\n\n output(options: { encoding: 'buffer' }): Promise<SandboxExecBufferOutput>;\n output(options?: { encoding?: 'utf8' }): Promise<SandboxExecStringOutput>;\n async output(options?: {\n encoding?: SandboxOutputEncoding;\n }): Promise<SandboxExecOutput> {\n const enc = options?.encoding ?? 'utf8';\n const startedAt = Date.now();\n\n const [stdoutBytes, stderrBytes, exitCode] = await Promise.all([\n this.stdout ? drain(this.stdout) : Promise.resolve(EMPTY),\n this.stderr ? drain(this.stderr) : Promise.resolve(EMPTY),\n this.exitCode\n ]);\n\n const base = {\n exitCode,\n success: exitCode === 0,\n duration: Date.now() - startedAt,\n command: this.command,\n timestamp: this.startTime.toISOString(),\n sessionId: this.sessionId\n };\n\n if (enc === 'buffer') {\n return {\n ...base,\n stdout: asArrayBuffer(stdoutBytes),\n stderr: asArrayBuffer(stderrBytes)\n };\n }\n\n const decoder = new TextDecoder();\n return {\n ...base,\n stdout: decoder.decode(stdoutBytes),\n stderr: decoder.decode(stderrBytes)\n };\n }\n\n /**\n * RPC-safe alternative to `output()` for cross-DO-boundary calls.\n *\n * The standard `output()` drains the live `stdout` / `stderr` streams\n * that workerd has already shipped to the calling Worker. When invoked\n * over RPC, those streams are remote-owned and cannot also be read\n * locally on the DO without colliding (\"This ReadableStream is\n * currently locked to a reader.\").\n *\n * `outputViaLogs` sidesteps this by reading the per-process log file\n * (the same source `getLogs()` returns) plus awaiting `waitForExit()`\n * for the exit code. The data is identical to what the live streams\n * carry but the source is independent.\n *\n * The `encoding` option works the same way as `output()`; the default\n * is utf-8 strings.\n */\n async text(): Promise<string> {\n const out = await this.output({ encoding: 'utf8' });\n return out.stdout;\n }\n\n async json<T = unknown>(): Promise<T> {\n return JSON.parse(await this.text()) as T;\n }\n\n outputViaLogs(options: {\n encoding: 'buffer';\n }): Promise<SandboxExecBufferOutput>;\n outputViaLogs(options?: {\n encoding?: 'utf8';\n }): Promise<SandboxExecStringOutput>;\n async outputViaLogs(options?: {\n encoding?: SandboxOutputEncoding;\n }): Promise<SandboxExecOutput> {\n const enc = options?.encoding ?? 'utf8';\n const startedAt = Date.now();\n\n // Wait for the process to exit before reading logs — otherwise the\n // log file may be missing the tail of output that's still being\n // flushed by the labelers.\n const { exitCode } = await this.deps.waitForProcessExit(\n this.id,\n this.command\n );\n const logs = await this.deps.readLogs(this.id);\n const stdout = this.stdoutMode === 'pipe' ? logs.stdout : '';\n const stderr = this.stderrMode === 'pipe' ? logs.stderr : '';\n\n const base = {\n exitCode,\n success: exitCode === 0,\n duration: Date.now() - startedAt,\n command: this.command,\n timestamp: this.startTime.toISOString(),\n sessionId: this.sessionId\n };\n\n if (enc === 'buffer') {\n const encoder = new TextEncoder();\n return {\n ...base,\n stdout: asArrayBuffer(encoder.encode(stdout)),\n stderr: asArrayBuffer(encoder.encode(stderr))\n };\n }\n\n return {\n ...base,\n stdout,\n stderr\n };\n }\n\n async textViaLogs(): Promise<string> {\n const out = await this.outputViaLogs({ encoding: 'utf8' });\n return out.stdout;\n }\n\n async jsonViaLogs<T = unknown>(): Promise<T> {\n return JSON.parse(await this.textViaLogs()) as T;\n }\n\n // ---- sandbox extensions --------------------------------------------------\n\n async status(): Promise<ProcessStatus> {\n if (this.terminalStatus) return this.terminalStatus;\n return this.deps.fetchStatus(this.id);\n }\n\n /** @deprecated Use `status()` (alias kept for one release for `Process` parity). */\n getStatus(): Promise<ProcessStatus> {\n return this.status();\n }\n\n getLogs(): Promise<{ stdout: string; stderr: string }> {\n return this.deps.readLogs(this.id);\n }\n\n waitForLog(\n pattern: string | RegExp,\n timeoutMs?: number\n ): Promise<WaitForLogResult> {\n return this.deps.waitForLogPattern(\n this.id,\n this.command,\n pattern,\n timeoutMs\n );\n }\n\n waitForPort(port: number, options?: WaitForPortOptions): Promise<void> {\n return this.deps.waitForPort(this.id, this.command, port, options);\n }\n\n waitForExit(timeoutMs?: number): Promise<WaitForExitResult> {\n return this.deps.waitForProcessExit(this.id, this.command, timeoutMs);\n }\n}\n\n// ---------------------------------------------------------------------------\n// SandboxProcessPromise — Bun.spawn-style thenable wrapper.\n// ---------------------------------------------------------------------------\n\nexport function createSandboxProcessPromise(\n spawn: Promise<SandboxProcess>\n): SandboxProcessPromise {\n // Decorate the underlying promise so callers can write either of:\n // const proc = await sandbox.exec(cmd); // SandboxProcess\n // const out = await sandbox.exec(cmd).output(); // SandboxExecOutput\n // const text = await sandbox.exec(cmd).text(); // string\n const promise = spawn as SandboxProcessPromise;\n const output: SandboxProcessPromise['output'] = ((opts?: {\n encoding?: SandboxOutputEncoding;\n }) => {\n const run = async (): Promise<SandboxExecOutput> => {\n const proc = await spawn;\n return opts?.encoding === 'buffer'\n ? proc.output({ encoding: 'buffer' })\n : proc.output({ encoding: 'utf8' });\n };\n return run();\n }) as SandboxProcessPromise['output'];\n promise.output = output;\n promise.text = async () => {\n const out = await (await spawn).output({ encoding: 'utf8' });\n return out.stdout;\n };\n promise.json = async <T>() => JSON.parse(await promise.text()) as T;\n promise.kill = async (signal) => {\n (await spawn).kill(signal);\n };\n return promise;\n}\n\n// ---------------------------------------------------------------------------\n// helpers\n// ---------------------------------------------------------------------------\n\nconst EMPTY = new Uint8Array();\n\nconst SIGNAL_NUMBERS: Record<string, number> = {\n SIGHUP: 1,\n SIGINT: 2,\n SIGQUIT: 3,\n SIGKILL: 9,\n SIGTERM: 15,\n SIGUSR1: 10,\n SIGUSR2: 12,\n SIGSTOP: 19,\n SIGCONT: 18\n};\n\nfunction normalizeSignal(signal: number | string): number {\n if (typeof signal === 'number') return signal;\n const n = SIGNAL_NUMBERS[signal];\n if (n === undefined) {\n throw new Error(`Unknown signal: ${signal}`);\n }\n return n;\n}\n\nasync function drain(stream: ReadableStream<Uint8Array>): Promise<Uint8Array> {\n const reader = stream.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n if (value) {\n chunks.push(value);\n total += value.byteLength;\n }\n }\n } finally {\n try {\n reader.releaseLock();\n } catch {\n /* already released */\n }\n }\n const out = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n out.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return out;\n}\n\nfunction asArrayBuffer(u8: Uint8Array): ArrayBuffer {\n // Slice to detach from any larger backing buffer.\n return u8.buffer.slice(\n u8.byteOffset,\n u8.byteOffset + u8.byteLength\n ) as ArrayBuffer;\n}\n\n/** Normalize a signal that came in as `number | string` for external use. */\nexport { normalizeSignal };\n\n// ---------------------------------------------------------------------------\n// stdin resolution\n//\n// `SandboxExecOptions.stdin` accepts three convenience forms:\n// - `\"pipe\"` — SDK exposes `proc.stdin` as a `WritableStream`.\n// - `ReadableStream<Uint8Array>` — piped directly into the process.\n// - `string` — utf-8 encoded into a single-chunk stream.\n//\n// The container RPC only accepts a `ReadableStream<Uint8Array>`. This\n// helper produces both halves: the readable side to send across RPC, and\n// (for the `\"pipe\"` case only) the writable side to surface back to the\n// caller on `proc.stdin`.\n// ---------------------------------------------------------------------------\n\nexport interface ResolvedStdin {\n stdinSource: ReadableStream<Uint8Array> | undefined;\n stdinWriter: WritableStream<Uint8Array> | null;\n}\n\nexport function resolveStdinForRpc(\n stdin: ReadableStream<Uint8Array> | string | 'pipe' | undefined\n): ResolvedStdin {\n if (stdin === undefined) {\n return { stdinSource: undefined, stdinWriter: null };\n }\n\n if (stdin === 'pipe') {\n // `TransformStream` gives us a back-pressuring pair: caller writes\n // into `writable`, container reads from `readable`.\n const transform = new TransformStream<Uint8Array, Uint8Array>();\n return {\n stdinSource: transform.readable,\n stdinWriter: transform.writable\n };\n }\n\n if (typeof stdin === 'string') {\n const bytes = new TextEncoder().encode(stdin);\n const source = new ReadableStream<Uint8Array>({\n start(controller) {\n controller.enqueue(bytes);\n controller.close();\n }\n });\n return { stdinSource: source, stdinWriter: null };\n }\n\n // Already a `ReadableStream<Uint8Array>` — pass through; user already\n // owns the stream so we don't expose a writer.\n return { stdinSource: stdin, stdinWriter: null };\n}\n\n// ---------------------------------------------------------------------------\n// toRpcSandboxProcess\n//\n// Convert a `SandboxProcessImpl` (class instance, not workerd-serializable)\n// into a plain `SandboxProcess`-shaped object that survives a Durable\n// Object RPC return. Mirrors the legacy `createProcessFromDTO` pattern:\n//\n// - Data fields are copied by value.\n// - Streams and the `exitCode` thenable are copied by reference and cross\n// the JsRpc boundary using workerd's built-in support for them.\n// - Methods are bound to the underlying impl and become RPC callbacks\n// when the object is shipped across the boundary.\n//\n// The returned object is `SandboxProcess`-shaped (sync property access for\n// `stdin/stdout/stderr/pid/exitCode/id/...`), so consumers can use it the\n// same way whether they receive it locally (inside the DO) or across RPC.\n// ---------------------------------------------------------------------------\n\nexport function toRpcSandboxProcess(impl: SandboxProcessImpl): SandboxProcess {\n // Capture the streams once. workerd ships ReadableStream / WritableStream\n // across the JsRpc boundary as remote streams; reads on the Worker side\n // pull bytes through the transport from the DO source. The DO must NOT\n // also try to read these streams locally (e.g. via `impl.output()`)\n // because workerd has effectively locked them as the RPC source.\n //\n // To avoid the conflict we route `output()` to a separate path:\n // `__outputViaLogs` runs on the DO, drains the per-process log file\n // (which is independent of the live SSE stream that workerd is\n // shipping), and returns a buffered `SandboxExecOutput` to the Worker.\n // The Worker still sees the live `stdout` / `stderr` streams for\n // incremental reading.\n const output: SandboxProcess['output'] = ((opts?: {\n encoding?: SandboxOutputEncoding;\n }) => {\n return opts?.encoding === 'buffer'\n ? impl.outputViaLogs({ encoding: 'buffer' })\n : impl.outputViaLogs({ encoding: 'utf8' });\n }) as SandboxProcess['output'];\n\n return {\n id: impl.id,\n pid: impl.pid,\n command: impl.command,\n sessionId: impl.sessionId,\n startTime: impl.startTime,\n stdin: impl.stdin,\n stdout: impl.stdout,\n stderr: impl.stderr,\n exitCode: impl.exitCode,\n output,\n text: () => impl.textViaLogs(),\n json: <T = unknown>() => impl.jsonViaLogs<T>(),\n kill: (signal) => impl.kill(signal),\n status: () => impl.status(),\n getLogs: () => impl.getLogs(),\n waitForLog: (pattern, timeoutMs) => impl.waitForLog(pattern, timeoutMs),\n waitForPort: (port, options) => impl.waitForPort(port, options),\n waitForExit: (timeoutMs) => impl.waitForExit(timeoutMs)\n };\n}\n","/**\n * Resolve a Cloudflare account id from environment with documented\n * precedence. Used by features that need to address a specific account\n * (Cloudflare Tunnel, R2 backup) to find their account id without\n * forcing every caller to set the same env var.\n *\n * Precedence (first non-empty wins):\n * 1. The feature-specific override env var (e.g. `CLOUDFLARE_TUNNEL_ACCOUNT_ID`).\n * 2. `CLOUDFLARE_ACCOUNT_ID`.\n * 3. The single account `CLOUDFLARE_API_TOKEN` is scoped to, via\n * `GET /user/tokens/verify`. Multi-account tokens are rejected.\n *\n * The resolver is feature-agnostic; only the `overrideKey` differs per\n * caller. Throws on any failure with a message that names the env vars\n * the caller can set for an explicit configuration.\n */\n\nimport { getEnvString } from '@repo/shared';\n\nconst TOKEN_VERIFY_URL =\n 'https://api.cloudflare.com/client/v4/user/tokens/verify';\nconst ACCOUNTS_LIST_URL = 'https://api.cloudflare.com/client/v4/accounts';\n\n/**\n * Per-request timeout for the credential introspection calls below.\n * Without one a hung Cloudflare control-plane call wedges every\n * first-time named-tunnel `get()` on the DO (the resolver promises are\n * memoised on `Sandbox`, so the first caller's hang is everyone's hang).\n */\nconst CREDENTIALS_TIMEOUT_MS = 10_000;\n\n/**\n * Fetch wrapper that adds an `AbortSignal.timeout` and surfaces a\n * timeout as a labelled `Error` so the caller can blame the right URL.\n */\nasync function fetchWithTimeout(\n fetcher: typeof fetch,\n url: string,\n init: RequestInit,\n timeoutMs: number = CREDENTIALS_TIMEOUT_MS\n): Promise<Response> {\n try {\n return await fetcher(url, {\n ...init,\n signal: AbortSignal.timeout(timeoutMs)\n });\n } catch (err) {\n if (err instanceof Error && err.name === 'TimeoutError') {\n throw new Error(\n `Cloudflare API request to ${url} timed out after ${timeoutMs}ms`\n );\n }\n throw err;\n }\n}\n\n/**\n * Cloudflare error code returned by `GET /user/tokens/verify` when the\n * presented token is an account-owned (`cfat-`) token rather than a\n * user-owned one. Matches the heuristic wrangler uses in\n * `src/user/whoami.ts` (`getTokenType`).\n */\nconst ACCOUNT_OWNED_TOKEN_CODE = 1000;\n\nexport interface ResolveAccountIdOptions {\n /**\n * The feature-specific override env var name. The caller's \"preferred\"\n * source; checked first.\n */\n overrideKey: 'CLOUDFLARE_TUNNEL_ACCOUNT_ID' | 'CLOUDFLARE_R2_ACCOUNT_ID';\n /**\n * Override the `fetch` implementation for the token-verify call.\n * Defaults to the global `fetch`. Tests inject a mock here.\n */\n fetcher?: typeof fetch;\n}\n\n/**\n * Cloudflare's `result_info` shape for `/user/tokens/verify`. The token\n * is \"account-scoped\" iff `result_info.account.id` is present.\n */\ninterface TokenVerifyResponse {\n success?: boolean;\n errors?: Array<{ code?: number; message?: string }>;\n result_info?: {\n account?: { id?: string };\n };\n}\n\ninterface AccountsListResponse {\n success?: boolean;\n errors?: Array<{ code?: number; message?: string }>;\n result?: Array<{ id?: string; name?: string }>;\n}\n\ninterface AccountTokenVerifyResponse {\n success?: boolean;\n errors?: Array<{ code?: number; message?: string }>;\n result?: { id?: string; status?: string };\n}\n\nexport async function resolveAccountId(\n env: Record<string, unknown>,\n options: ResolveAccountIdOptions\n): Promise<string> {\n // Step 1: feature-specific override.\n const override = getEnvString(env, options.overrideKey);\n if (override) return override;\n\n // Step 2: generic account fallback.\n const generic = getEnvString(env, 'CLOUDFLARE_ACCOUNT_ID');\n if (generic) return generic;\n\n // Step 3: derive from the API token. Requires a token to be present.\n const token = getEnvString(env, 'CLOUDFLARE_API_TOKEN');\n if (!token) {\n throw new Error(\n `Cloudflare account id could not be resolved. Set one of: ` +\n `${options.overrideKey}, CLOUDFLARE_ACCOUNT_ID, or ` +\n `CLOUDFLARE_API_TOKEN (a token scoped to a single account).`\n );\n }\n\n const fetcher = options.fetcher ?? fetch;\n const response = await fetchWithTimeout(fetcher, TOKEN_VERIFY_URL, {\n method: 'GET',\n headers: {\n authorization: `Bearer ${token}`,\n 'content-type': 'application/json'\n }\n });\n\n // Drain the body whether we succeed or not so the caller-facing error\n // can report the API error code (e.g. 1000 = account-owned token).\n let body: TokenVerifyResponse;\n try {\n body = (await response.json()) as TokenVerifyResponse;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(\n `Cloudflare token verification returned malformed JSON: ${message}`\n );\n }\n\n if (response.ok && body?.success) {\n const derived = body.result_info?.account?.id;\n if (!derived) {\n throw new Error(\n 'Cloudflare token is not scoped to a single account (ambiguous). ' +\n `Set ${options.overrideKey} or CLOUDFLARE_ACCOUNT_ID explicitly.`\n );\n }\n return derived;\n }\n\n // Wrangler's `getTokenType` uses the same heuristic: code 1000 on\n // `/user/tokens/verify` means the token is account-owned (cfat-...)\n // and can't be introspected via the user-scoped endpoint. Fall through\n // to `/accounts` + `/accounts/:id/tokens/verify` to derive the id.\n const isAccountOwned = body?.errors?.some(\n (e) => e.code === ACCOUNT_OWNED_TOKEN_CODE\n );\n if (isAccountOwned) {\n return await deriveAccountIdViaAccountToken(token, fetcher, options);\n }\n\n throw new Error(\n `Cloudflare token verification failed with status ${response.status}. ` +\n `Check that CLOUDFLARE_API_TOKEN is valid or set ${options.overrideKey} ` +\n `/ CLOUDFLARE_ACCOUNT_ID explicitly.`\n );\n}\n\n/**\n * Account-owned token (cfat-) fallback: list the accounts the token can\n * see, and — if there's exactly one — confirm with the account-scoped\n * verify endpoint before returning the id.\n *\n * Common failure modes get specific, actionable error messages:\n * - `/accounts` 403 (token lacks `account:read`): tell the caller to\n * set `CLOUDFLARE_ACCOUNT_ID` explicitly.\n * - multiple accounts: same.\n * - zero accounts: same.\n * - confirm step fails: surface the API error code verbatim.\n */\nasync function deriveAccountIdViaAccountToken(\n token: string,\n fetcher: typeof fetch,\n options: ResolveAccountIdOptions\n): Promise<string> {\n // per_page=2 is the cheapest probe that still distinguishes one-vs-many.\n const listResponse = await fetchWithTimeout(\n fetcher,\n `${ACCOUNTS_LIST_URL}?per_page=2`,\n {\n method: 'GET',\n headers: {\n authorization: `Bearer ${token}`,\n 'content-type': 'application/json'\n }\n }\n );\n\n let listBody: AccountsListResponse;\n try {\n listBody = (await listResponse.json()) as AccountsListResponse;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(\n `Cloudflare account-owned token: /accounts returned malformed JSON: ${message}`\n );\n }\n\n if (!listResponse.ok || !listBody?.success) {\n throw new Error(\n 'Cloudflare account-owned token (cfat-...) detected, but ' +\n `/accounts returned status ${listResponse.status}. The token may ` +\n 'lack account:read scope. Set CLOUDFLARE_ACCOUNT_ID explicitly to ' +\n 'skip introspection.'\n );\n }\n\n const accounts = listBody.result ?? [];\n if (accounts.length === 0) {\n throw new Error(\n 'Cloudflare account-owned token has access to no accounts. ' +\n 'Set CLOUDFLARE_ACCOUNT_ID explicitly.'\n );\n }\n if (accounts.length > 1) {\n throw new Error(\n 'Cloudflare account-owned token has access to multiple accounts ' +\n '(ambiguous). Set CLOUDFLARE_ACCOUNT_ID explicitly to disambiguate.'\n );\n }\n const accountId = accounts[0]?.id;\n if (!accountId) {\n throw new Error(\n 'Cloudflare /accounts returned a result without an id field.'\n );\n }\n\n // Confirm via the account-scoped verify endpoint. This is the canonical\n // check for account-owned tokens, and it doubles as proof that the token\n // is actually valid for the account we picked.\n const verifyResponse = await fetchWithTimeout(\n fetcher,\n `${ACCOUNTS_LIST_URL}/${encodeURIComponent(accountId)}/tokens/verify`,\n {\n method: 'GET',\n headers: {\n authorization: `Bearer ${token}`,\n 'content-type': 'application/json'\n }\n }\n );\n let verifyBody: AccountTokenVerifyResponse;\n try {\n verifyBody = (await verifyResponse.json()) as AccountTokenVerifyResponse;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(\n `Cloudflare account token verify returned malformed JSON: ${message}`\n );\n }\n if (!verifyResponse.ok || !verifyBody?.success) {\n const detail =\n verifyBody?.errors?.map((e) => `${e.code}: ${e.message}`).join('; ') ??\n `HTTP ${verifyResponse.status}`;\n throw new Error(\n `Cloudflare account token verify failed for account ${accountId}: ${detail}`\n );\n }\n return accountId;\n}\n\n// ---------------------------------------------------------------------------\n// Zone id resolution\n// ---------------------------------------------------------------------------\n\nconst ZONES_LIST_URL = 'https://api.cloudflare.com/client/v4/zones';\n\nexport interface ResolveZoneIdOptions {\n /** Cloudflare API token. Must be account- or zone-scoped.*/\n token: string;\n /**\n * Account id the zone must belong to. The resolver scopes the zones\n * list query to this account so a single multi-account token isn't\n * tripped up by zones belonging to a different account.\n */\n accountId: string;\n /**\n * Override the `fetch` implementation for the zones list call.\n * Defaults to the global `fetch`. Tests inject a mock here.\n */\n fetcher?: typeof fetch;\n}\n\ninterface ZonesListResponse {\n success?: boolean;\n result?: Array<{ id?: string; name?: string }>;\n errors?: Array<{ code?: number; message?: string }>;\n}\n\n/**\n * Resolve a Cloudflare zone id.\n *\n * Precedence:\n * 1. `CLOUDFLARE_ZONE_ID` env var.\n * 2. The single zone the token can see under `accountId`, via\n * `GET /zones?account.id=<accountId>&per_page=2`.\n *\n * Step 2 deliberately fetches at most two results: one is the happy path,\n * two (or more) means the token is ambiguous and we refuse to guess.\n * Multi-zone tokens must set `CLOUDFLARE_ZONE_ID` explicitly so the\n * caller's intent is unambiguous.\n */\nexport async function resolveZoneId(\n env: Record<string, unknown>,\n options: ResolveZoneIdOptions\n): Promise<string> {\n const envZone = getEnvString(env, 'CLOUDFLARE_ZONE_ID');\n if (envZone) return envZone;\n\n const fetcher = options.fetcher ?? fetch;\n const url = `${ZONES_LIST_URL}?account.id=${encodeURIComponent(options.accountId)}&per_page=2`;\n const response = await fetchWithTimeout(fetcher, url, {\n method: 'GET',\n headers: {\n authorization: `Bearer ${options.token}`,\n 'content-type': 'application/json'\n }\n });\n\n if (!response.ok) {\n throw new Error(\n `Cloudflare zones lookup failed with status ${response.status}. ` +\n 'Set CLOUDFLARE_ZONE_ID explicitly or grant the API token Zone:Read.'\n );\n }\n\n let body: ZonesListResponse;\n try {\n body = (await response.json()) as ZonesListResponse;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(\n `Cloudflare zones lookup returned malformed JSON: ${message}`\n );\n }\n\n const zones = body.result ?? [];\n if (zones.length === 0) {\n throw new Error(\n 'Cloudflare API token has access to no zones in account ' +\n `${options.accountId}. Set CLOUDFLARE_ZONE_ID explicitly or ` +\n 'grant the token Zone:Read on the intended zone.'\n );\n }\n if (zones.length > 1) {\n throw new Error(\n 'Cloudflare API token has access to multiple zones in account ' +\n `${options.accountId} (ambiguous). Set CLOUDFLARE_ZONE_ID ` +\n 'explicitly to disambiguate.'\n );\n }\n const zoneId = zones[0]?.id;\n if (!zoneId) {\n throw new Error(\n 'Cloudflare zones lookup returned a result without an id field.'\n );\n }\n return zoneId;\n}\n","import { getEnvString } from '@repo/shared';\nimport { resolveAccountId, resolveZoneId } from './credentials';\n\nexport interface NamedTunnelConfig {\n token: string;\n accountId: string;\n zoneId: string;\n}\n\nexport interface NamedTunnelConfigResolverOptions {\n getEnv: () => unknown;\n fetcher?: typeof fetch;\n}\n\nfunction asEnvRecord(env: unknown): Record<string, unknown> {\n return env && typeof env === 'object' ? (env as Record<string, unknown>) : {};\n}\n\nexport class NamedTunnelConfigResolver {\n readonly #getEnv: () => unknown;\n readonly #fetcher?: typeof fetch;\n #accountIdPromise: Promise<string> | null = null;\n #zoneIdPromise: Promise<string> | null = null;\n\n constructor(options: NamedTunnelConfigResolverOptions) {\n this.#getEnv = options.getEnv;\n this.#fetcher = options.fetcher;\n }\n\n async getConfig(): Promise<NamedTunnelConfig> {\n const env = this.#env();\n const token = getEnvString(env, 'CLOUDFLARE_API_TOKEN');\n if (!token) {\n throw new Error(\n 'Named tunnels require CLOUDFLARE_API_TOKEN. ' +\n 'Set it as a secret in your wrangler.jsonc.'\n );\n }\n\n const accountId = await this.#getAccountId();\n const zoneId = await this.#getZoneId(token, accountId);\n return { token, accountId, zoneId };\n }\n\n #env(): Record<string, unknown> {\n return asEnvRecord(this.#getEnv());\n }\n\n #getAccountId(): Promise<string> {\n if (!this.#accountIdPromise) {\n const pending = resolveAccountId(this.#env(), {\n overrideKey: 'CLOUDFLARE_TUNNEL_ACCOUNT_ID',\n fetcher: this.#fetcher\n });\n this.#accountIdPromise = pending;\n pending.catch(() => {\n if (this.#accountIdPromise === pending) {\n this.#accountIdPromise = null;\n }\n });\n }\n return this.#accountIdPromise;\n }\n\n #getZoneId(token: string, accountId: string): Promise<string> {\n if (!this.#zoneIdPromise) {\n const pending = resolveZoneId(this.#env(), {\n token,\n accountId,\n fetcher: this.#fetcher\n });\n this.#zoneIdPromise = pending;\n pending.catch(() => {\n if (this.#zoneIdPromise === pending) {\n this.#zoneIdPromise = null;\n }\n });\n }\n return this.#zoneIdPromise;\n }\n}\n","/**\n * Cloudflare API client for named-tunnel orchestration.\n *\n * Design notes:\n *\n * - The Cloudflare API envelope is `{ success, result, errors }`. We\n * unwrap `result` on success and surface a thrown `Error` with the\n * API error code/message on failure. Transport-level errors\n * propagate unchanged.\n * - Delete endpoints are idempotent from the caller's perspective:\n * a 404 (already gone) resolves successfully so destroy() can run\n * without special-casing.\n * - `upsertCNAME` is the most subtle wrapper: it lists existing\n * records, reuses a matching one, and refuses to mutate a record\n * whose content differs from what we want. This is the fence that\n * stops two sandboxes from racing on the same hostname.\n */\n\nconst API_BASE = 'https://api.cloudflare.com/client/v4';\n\n/** Cloudflare's standard envelope around every response. */\ninterface CloudflareResponse<T> {\n success: boolean;\n result?: T;\n errors?: Array<{ code?: number; message?: string }>;\n}\n\ntype Fetcher = typeof fetch;\n\ninterface BaseArgs {\n token: string;\n fetcher?: Fetcher;\n}\n\n/**\n * Tag attached to every tunnel resource the SDK creates. Survives\n * round-tripping through the Cloudflare API so `findTunnelByName` can\n * reconcile orphaned resources from a previous failed attempt.\n */\nexport interface TunnelMetadata {\n sandboxId: string;\n createdBy: 'sandbox-sdk';\n name: string;\n port: number;\n}\n\ninterface RequestOptions {\n method?: 'GET' | 'POST' | 'PUT' | 'DELETE';\n body?: unknown;\n /** Treat these HTTP statuses as success and skip envelope parsing. */\n acceptStatuses?: number[];\n /**\n * Per-request timeout in milliseconds. Defaults to `DEFAULT_TIMEOUT_MS`.\n * Without a timeout a hung Cloudflare call wedges the per-port lock in\n * `rpc-target.ts` indefinitely, which then blocks every subsequent\n * `get(port)` / `destroy(port)` on that port. The shared\n * `#zoneNamePromise` makes the impact span every port for named\n * tunnels.\n */\n timeoutMs?: number;\n}\n\n/**\n * Default request timeout. Cloudflare API P99 latency is well under\n * this; values much smaller risk false positives on cold control-plane\n * paths (e.g. first `cfd_tunnel` POST in a new account).\n */\nconst DEFAULT_TIMEOUT_MS = 10_000;\n\n/**\n * Internal request helper. Centralises auth header, JSON encoding,\n * timeout enforcement, and envelope unwrapping so each wrapper above\n * stays declarative.\n */\nasync function cfRequest<T>(\n url: string,\n token: string,\n fetcher: Fetcher,\n options: RequestOptions = {}\n): Promise<T | undefined> {\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const init: RequestInit = {\n method: options.method ?? 'GET',\n headers: {\n authorization: `Bearer ${token}`,\n 'content-type': 'application/json'\n },\n signal: AbortSignal.timeout(timeoutMs)\n };\n if (options.body !== undefined) {\n init.body = JSON.stringify(options.body);\n }\n\n let response: Response;\n try {\n response = await fetcher(url, init);\n } catch (err) {\n // `AbortSignal.timeout` rejects with a DOMException whose name is\n // 'TimeoutError'. Surface it as a clearly-labelled error so callers\n // can distinguish a transport hang from a Cloudflare-side failure;\n // a SandboxSecurityError-shaped class would be better but we keep\n // the error shape consistent with the rest of this module.\n if (err instanceof Error && err.name === 'TimeoutError') {\n throw new Error(\n `Cloudflare API request to ${url} timed out after ${timeoutMs}ms`\n );\n }\n throw err;\n }\n if (options.acceptStatuses?.includes(response.status)) {\n return undefined;\n }\n\n let envelope: CloudflareResponse<T>;\n try {\n envelope = (await response.json()) as CloudflareResponse<T>;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(\n `Cloudflare API returned non-JSON response (status ${response.status}): ${message}`\n );\n }\n\n if (!response.ok || envelope.success === false) {\n const errs = envelope.errors ?? [];\n const summary = errs.length\n ? errs\n .map((e) => `${e.code ?? '???'}: ${e.message ?? 'unknown'}`)\n .join(', ')\n : `HTTP ${response.status}`;\n throw new Error(`Cloudflare API error: ${summary}`);\n }\n\n return envelope.result;\n}\n\n/**\n * Heuristic for the \"tags are an Enterprise-only feature\" error class.\n * Empirically grounded against a non-Enterprise account:\n *\n * - DNS create with `tags: [...]` on a non-Enterprise zone rejects with\n * Cloudflare error code 9300 and the message \"DNS record has N tags,\n * exceeding the quota of 0.\". The error string `cfRequest` constructs\n * embeds both the code and the message, so we match on either signal.\n * - Tunnel create with `tags: [...]` silently succeeds and drops the\n * field on the floor (no error to retry on). The fallback wrapper\n * therefore costs nothing on tunnel writes.\n *\n * Generic \"requires Enterprise\" phrasing is also matched as a forward-\n * compatibility hedge in case Cloudflare changes the response shape on\n * future endpoints.\n */\nfunction isEnterpriseOnlyTagError(error: unknown): boolean {\n if (!(error instanceof Error)) return false;\n const msg = error.message.toLowerCase();\n // Code 9300 is the observed DNS \"tags exceed quota\" code.\n if (msg.includes('9300') && msg.includes('tag')) return true;\n // Generic phrasings used by Cloudflare's documented Enterprise gates.\n if (!msg.includes('tag')) return false;\n return (\n msg.includes('quota') ||\n msg.includes('enterprise') ||\n msg.includes('not allowed') ||\n msg.includes('not entitled') ||\n msg.includes('not available') ||\n msg.includes('not supported')\n );\n}\n\n/**\n * Build the `tags` field attached to created Cloudflare resources. The\n * tag is `sandboxId:<id>`, the same key used in DNS comments / tunnel\n * metadata; together they let an operator find every resource a given\n * sandbox owns from the Cloudflare dashboard.\n *\n * Tags are an Enterprise-only feature. The wrapper `createWithTagFallback`\n * automatically retries the request without tags on the documented\n * \"requires Enterprise\" error so non-enterprise accounts succeed without\n * any configuration.\n */\nfunction buildSandboxTags(sandboxId: string | undefined): string[] | undefined {\n if (!sandboxId) return undefined;\n return [`sandboxId:${sandboxId}`];\n}\n\n/**\n * Wrap a tagged-create request with an automatic tag-strip retry. The\n * callback receives `tags`: pass it through to the request body as-is on\n * the first call (`undefined` on the retry). The retry only fires for\n * the Enterprise-only tag error class; any other failure surfaces\n * verbatim.\n */\nasync function createWithTagFallback<T>(\n sandboxId: string | undefined,\n send: (tags: string[] | undefined) => Promise<T>\n): Promise<T> {\n const tags = buildSandboxTags(sandboxId);\n if (!tags) return send(undefined);\n try {\n return await send(tags);\n } catch (err) {\n if (!isEnterpriseOnlyTagError(err)) throw err;\n return send(undefined);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tunnels\n// ---------------------------------------------------------------------------\n\nexport interface CreateTunnelArgs extends BaseArgs {\n accountId: string;\n /**\n * The on-Cloudflare display name for the tunnel resource. Conventionally\n * `sandbox-<sandboxId>-<userName>` so it's stable per (sandbox, name).\n */\n tunnelName: string;\n metadata: TunnelMetadata;\n}\n\nexport interface CreatedTunnel {\n id: string;\n /** Opaque `--token` for `cloudflared tunnel run --token <T>`. */\n token: string;\n}\n\nexport async function createTunnel(\n args: CreateTunnelArgs\n): Promise<CreatedTunnel> {\n const fetcher = args.fetcher ?? fetch;\n // The `/cfd_tunnel` endpoint silently ignores unknown body fields,\n // including `tags`, so today this is effectively a no-op on the wire\n // — the tagging story for tunnels lives on the Resource Tagging API\n // (PUT /accounts/{id}/tags). We keep the inline field in case the\n // endpoint adopts it later; the wrapper handles the Enterprise-only\n // fallback for DNS (see upsertCNAME), where it does fire.\n const result = await createWithTagFallback(args.metadata.sandboxId, (tags) =>\n cfRequest<{ id: string; token: string }>(\n `${API_BASE}/accounts/${encodeURIComponent(args.accountId)}/cfd_tunnel`,\n args.token,\n fetcher,\n {\n method: 'POST',\n body: {\n name: args.tunnelName,\n // `cloudflare` lets cloudflared run with just --token, no local\n // config file. The alternative `local` requires a YAML config.\n config_src: 'cloudflare',\n metadata: args.metadata,\n ...(tags ? { tags } : {})\n }\n }\n )\n );\n if (!result) {\n throw new Error('Cloudflare tunnel create returned no result body');\n }\n return { id: result.id, token: result.token };\n}\n\nexport interface FindTunnelArgs extends BaseArgs {\n accountId: string;\n tunnelName: string;\n /**\n * When set, only return tunnels whose `metadata.sandboxId` equals this\n * value. Otherwise the function matches by name alone.\n *\n * Use this to defend against the case where two sandboxes happen to\n * mint the same tunnel name (the name conventionally encodes the\n * sandbox id, but the API does not enforce that): without the\n * metadata check, sandbox B's `findTunnelByName` would happily claim\n * sandbox A's tunnel and start managing it.\n */\n expectedSandboxId?: string;\n}\n\nexport interface ExistingTunnel {\n id: string;\n name: string;\n}\n\n/**\n * Look up an existing tunnel by exact name match. Filters out tunnels\n * marked `deleted_at != null` defensively in case the API ignores the\n * `is_deleted=false` query parameter.\n *\n * When `expectedSandboxId` is provided, also verify that the tunnel's\n * `metadata.sandboxId` tag matches — this is the authoritative \"this\n * resource was created by this sandbox\" check, and the tag is set by\n * `createTunnel`. Mismatches are treated as \"not found\" so the caller\n * falls through to creating a fresh tunnel.\n */\nexport async function findTunnelByName(\n args: FindTunnelArgs\n): Promise<ExistingTunnel | null> {\n const fetcher = args.fetcher ?? fetch;\n const url =\n `${API_BASE}/accounts/${encodeURIComponent(args.accountId)}/cfd_tunnel` +\n `?name=${encodeURIComponent(args.tunnelName)}&is_deleted=false`;\n const result = await cfRequest<\n Array<{\n id: string;\n name: string;\n deleted_at?: string | null;\n metadata?: unknown;\n }>\n >(url, args.token, fetcher);\n if (!result) return null;\n const live = result.find((t) => !t.deleted_at);\n if (!live) return null;\n if (args.expectedSandboxId !== undefined) {\n const meta = live.metadata as { sandboxId?: unknown } | undefined;\n if (meta?.sandboxId !== args.expectedSandboxId) return null;\n }\n return { id: live.id, name: live.name };\n}\n\nexport interface DeleteTunnelArgs extends BaseArgs {\n accountId: string;\n tunnelId: string;\n}\n\nexport async function deleteTunnel(args: DeleteTunnelArgs): Promise<void> {\n const fetcher = args.fetcher ?? fetch;\n await cfRequest<unknown>(\n `${API_BASE}/accounts/${encodeURIComponent(args.accountId)}/cfd_tunnel/${encodeURIComponent(args.tunnelId)}`,\n args.token,\n fetcher,\n {\n method: 'DELETE',\n // 404 is a successful (already-gone) outcome so destroy() can\n // chain other cleanup steps without surfacing benign races.\n acceptStatuses: [404]\n }\n );\n}\n\nexport interface GetTunnelTokenArgs extends BaseArgs {\n accountId: string;\n tunnelId: string;\n}\n\n/**\n * Fetch the opaque `--token` for an existing tunnel. Used on the retry\n * path: when `findTunnelByName` discovers a tunnel left behind from a\n * previous failed attempt, we need its token to run `cloudflared` again.\n *\n * The Cloudflare API returns the token as a bare quoted string in the\n * `result` envelope (e.g. `\"<base64-token>\"`).\n */\nexport async function getTunnelToken(\n args: GetTunnelTokenArgs\n): Promise<string> {\n const fetcher = args.fetcher ?? fetch;\n const result = await cfRequest<string>(\n `${API_BASE}/accounts/${encodeURIComponent(args.accountId)}/cfd_tunnel/${encodeURIComponent(args.tunnelId)}/token`,\n args.token,\n fetcher\n );\n if (typeof result !== 'string' || result.length === 0) {\n throw new Error(\n `Cloudflare did not return a token for tunnel ${args.tunnelId}`\n );\n }\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Zones\n// ---------------------------------------------------------------------------\n\nexport interface GetZoneNameArgs extends BaseArgs {\n zoneId: string;\n}\n\nexport async function getZoneName(args: GetZoneNameArgs): Promise<string> {\n const fetcher = args.fetcher ?? fetch;\n const result = await cfRequest<{ id: string; name: string }>(\n `${API_BASE}/zones/${encodeURIComponent(args.zoneId)}`,\n args.token,\n fetcher\n );\n if (!result?.name) {\n throw new Error(`Cloudflare zone ${args.zoneId} did not return a name`);\n }\n return result.name;\n}\n\n// ---------------------------------------------------------------------------\n// DNS records\n// ---------------------------------------------------------------------------\n\nexport interface UpsertCNAMEArgs extends BaseArgs {\n zoneId: string;\n hostname: string;\n /** `<tunnel-id>.cfargotunnel.com`. */\n cnameTarget: string;\n /** `sandbox-<sandbox-id>` — used both for tagging and reuse matching. */\n comment: string;\n /**\n * Sandbox id used to build the `sandboxId:<id>` Cloudflare tag\n * attached to the created DNS record. Tags are an Enterprise-only\n * feature; the create call falls back to an untagged record on the\n * documented \"requires Enterprise\" error. Omit to skip tagging.\n */\n sandboxId?: string;\n}\n\nexport interface UpsertCNAMEResult {\n recordId: string;\n /** True when an existing matching record was reused; false when created. */\n reused: boolean;\n}\n\ninterface DNSRecordEntry {\n id: string;\n type: string;\n name: string;\n content: string;\n comment?: string | null;\n proxied?: boolean;\n}\n\nasync function listCNAMERecords(args: {\n token: string;\n zoneId: string;\n hostname: string;\n fetcher: Fetcher;\n}): Promise<DNSRecordEntry[]> {\n const listUrl =\n `${API_BASE}/zones/${encodeURIComponent(args.zoneId)}/dns_records` +\n `?type=CNAME&name=${encodeURIComponent(args.hostname)}`;\n return (\n (await cfRequest<DNSRecordEntry[]>(listUrl, args.token, args.fetcher)) ?? []\n );\n}\n\nexport interface FindCNAMEArgs extends BaseArgs {\n zoneId: string;\n hostname: string;\n cnameTarget: string;\n}\n\nexport interface FoundCNAMEResult {\n recordId: string;\n}\n\nexport async function findCNAME(\n args: FindCNAMEArgs\n): Promise<FoundCNAMEResult | null> {\n const fetcher = args.fetcher ?? fetch;\n const records = await listCNAMERecords({\n token: args.token,\n zoneId: args.zoneId,\n hostname: args.hostname,\n fetcher\n });\n const existing = records.find(\n (r) =>\n r.type === 'CNAME' &&\n r.name === args.hostname &&\n r.content === args.cnameTarget\n );\n return existing ? { recordId: existing.id } : null;\n}\n\nexport async function upsertCNAME(\n args: UpsertCNAMEArgs\n): Promise<UpsertCNAMEResult> {\n const fetcher = args.fetcher ?? fetch;\n const records = await listCNAMERecords({\n token: args.token,\n zoneId: args.zoneId,\n hostname: args.hostname,\n fetcher\n });\n\n const existing = records.find(\n (r) => r.type === 'CNAME' && r.name === args.hostname\n );\n if (existing) {\n // The CNAME content `<tunnel-id>.cfargotunnel.com` is the\n // authoritative \"ours\" check: only the holder of the tunnel id\n // could have asked Cloudflare to mint that target. Comment is\n // free text that operators commonly edit through the dashboard,\n // so we deliberately do not key reuse on it.\n if (existing.content === args.cnameTarget) {\n return { recordId: existing.id, reused: true };\n }\n throw new Error(\n `DNS record for ${args.hostname} already exists with different content ` +\n `(owned by you, not us): existing content=\"${existing.content}\", ` +\n `existing comment=\"${existing.comment ?? ''}\". Delete the record ` +\n 'manually to allow the sandbox to manage it.'\n );\n }\n\n const createResult = await createWithTagFallback(args.sandboxId, (tags) =>\n cfRequest<{ id: string }>(\n `${API_BASE}/zones/${encodeURIComponent(args.zoneId)}/dns_records`,\n args.token,\n fetcher,\n {\n method: 'POST',\n body: {\n type: 'CNAME',\n name: args.hostname,\n content: args.cnameTarget,\n proxied: true,\n comment: args.comment,\n ...(tags ? { tags } : {})\n }\n }\n )\n );\n if (!createResult) {\n throw new Error('Cloudflare DNS create returned no result body');\n }\n return { recordId: createResult.id, reused: false };\n}\n\nexport interface DeleteDNSRecordArgs extends BaseArgs {\n zoneId: string;\n recordId: string;\n}\n\nexport async function deleteDNSRecord(\n args: DeleteDNSRecordArgs\n): Promise<void> {\n const fetcher = args.fetcher ?? fetch;\n await cfRequest<unknown>(\n `${API_BASE}/zones/${encodeURIComponent(args.zoneId)}/dns_records/${encodeURIComponent(args.recordId)}`,\n args.token,\n fetcher,\n {\n method: 'DELETE',\n acceptStatuses: [404]\n }\n );\n}\n","import type { NamedTunnelInfo, TunnelInfo, TunnelOptions } from '@repo/shared';\nimport type { RuntimeIdentityID } from '../current-runtime-identity';\nimport type { SandboxLifetimeID } from '../sandbox-lifetime';\n\n/** DO storage key for the `port → TunnelInfo` map. */\nexport const STORAGE_KEY = 'tunnels';\n\n/**\n * Sidecar storage key for per-port metadata the handler needs but the\n * public `TunnelInfo` shape does not carry.\n */\nexport const META_STORAGE_KEY = 'tunnels:meta';\n\nexport const CLEANUP_STORAGE_KEY = 'tunnels:cleanup';\n\nexport type TunnelMap = Record<string, TunnelInfo>;\n\nexport interface TunnelMetaEntry {\n /** Stable hash of the `options` object the tunnel was created with. */\n optionsHash: string;\n /** Cloudflare DNS record id for named tunnels; absent for quick. */\n dnsRecordId?: string;\n /** Runtime identity that owns the current cloudflared process. */\n runtimeIdentityID?: RuntimeIdentityID;\n /** Sandbox lifetime that owns this tunnel record. */\n sandboxLifetimeID?: SandboxLifetimeID;\n /** Runtime-local cloudflared run that owns current process callbacks. */\n tunnelRunId?: string;\n /** Cloudflare tunnel id for hidden named records that can respawn or clean up. */\n tunnelId?: string;\n /** User-provided named-tunnel label for hidden respawn/cleanup records. */\n name?: string;\n /** Public named-tunnel hostname for hidden respawn/cleanup records. */\n hostname?: string;\n /**\n * Resolved `(accountId, zoneId)` the named tunnel was provisioned\n * against. Compared on cache hit to detect env-var changes that\n * would otherwise silently serve a stale URL.\n */\n accountId?: string;\n zoneId?: string;\n /**\n * Set for hidden named tunnels whose Cloudflare resources outlived\n * the runtime-local cloudflared process.\n */\n needsRespawn?: boolean;\n}\n\nexport type TunnelMetaMap = Record<string, TunnelMetaEntry>;\n\nexport type TunnelCleanupPhase = 'planned' | 'tunnel_ready' | 'claimed';\n\nexport interface TunnelCleanupEntry {\n tunnelId?: string;\n port: number;\n name: string;\n hostname: string;\n tunnelName?: string;\n sandboxId?: string;\n dnsRecordId?: string;\n accountId?: string;\n zoneId?: string;\n phase: TunnelCleanupPhase;\n updatedAt: string;\n}\n\nexport type TunnelCleanupMap = Record<string, TunnelCleanupEntry>;\n\n/**\n * Subset of `DurableObjectTransaction` (and `DurableObjectStorage`) used\n * inside a transaction closure — no nested `transaction()`.\n */\nexport interface TunnelsStorageTxn {\n get<T>(key: string): Promise<T | undefined>;\n put<T>(key: string, value: T): Promise<void>;\n delete(key: string): Promise<boolean>;\n}\n\n/**\n * Subset of `DurableObjectStorage` the handler uses.\n *\n * `transaction` gives optimistic concurrency for read-modify-write paths.\n */\nexport interface TunnelsStorage extends TunnelsStorageTxn {\n transaction<T>(closure: (txn: TunnelsStorageTxn) => Promise<T>): Promise<T>;\n}\n\nexport async function readMap(storage: TunnelsStorageTxn): Promise<TunnelMap> {\n return (await storage.get<TunnelMap>(STORAGE_KEY)) ?? {};\n}\n\nexport async function readMetaMap(\n storage: TunnelsStorageTxn\n): Promise<TunnelMetaMap> {\n return (await storage.get<TunnelMetaMap>(META_STORAGE_KEY)) ?? {};\n}\n\nexport async function readCleanupMap(\n storage: TunnelsStorageTxn\n): Promise<TunnelCleanupMap> {\n return (await storage.get<TunnelCleanupMap>(CLEANUP_STORAGE_KEY)) ?? {};\n}\n\nexport interface TunnelPortState {\n info?: TunnelInfo;\n meta?: TunnelMetaEntry;\n cleanup?: TunnelCleanupEntry;\n}\n\nexport async function readPortState(\n storage: TunnelsStorageTxn,\n port: number\n): Promise<TunnelPortState> {\n const portKey = port.toString();\n const [map, meta, cleanup] = await Promise.all([\n readMap(storage),\n readMetaMap(storage),\n readCleanupMap(storage)\n ]);\n return {\n info: map[portKey],\n meta: meta[portKey],\n cleanup: cleanup[portKey]\n };\n}\n\nfunction setOptionalMapValue<T>(\n map: Record<string, T>,\n key: string,\n value: T | undefined\n): void {\n if (value === undefined) {\n delete map[key];\n return;\n }\n map[key] = value;\n}\n\nexport interface TunnelPortStatePatch {\n info?: TunnelInfo;\n meta?: TunnelMetaEntry;\n cleanup?: TunnelCleanupEntry;\n}\n\nexport async function updatePortState(\n storage: TunnelsStorage,\n port: number,\n updater: (\n state: Readonly<TunnelPortState>\n ) =>\n | TunnelPortStatePatch\n | undefined\n | Promise<TunnelPortStatePatch | undefined>\n): Promise<void> {\n await storage.transaction(async (txn) => {\n const portKey = port.toString();\n const [map, meta, cleanup] = await Promise.all([\n readMap(txn),\n readMetaMap(txn),\n readCleanupMap(txn)\n ]);\n const state: TunnelPortState = {\n info: map[portKey],\n meta: meta[portKey],\n cleanup: cleanup[portKey]\n };\n const patch = await updater(state);\n\n const writes: Array<Promise<void>> = [];\n if (patch && 'info' in patch && patch.info !== state.info) {\n setOptionalMapValue(map, portKey, patch.info);\n writes.push(txn.put(STORAGE_KEY, map));\n }\n if (patch && 'meta' in patch && patch.meta !== state.meta) {\n setOptionalMapValue(meta, portKey, patch.meta);\n writes.push(txn.put(META_STORAGE_KEY, meta));\n }\n if (patch && 'cleanup' in patch && patch.cleanup !== state.cleanup) {\n setOptionalMapValue(cleanup, portKey, patch.cleanup);\n writes.push(txn.put(CLEANUP_STORAGE_KEY, cleanup));\n }\n await Promise.all(writes);\n });\n}\n\n/**\n * Stable hash of `options`. Empty/undefined options collapse to the same\n * hash so `get(port)`, `get(port, {})`, and `get(port, { name: undefined })`\n * all hit the same cache entry. Named tunnels hash on `name` alone.\n */\nexport function computeOptionsHash(options?: TunnelOptions): string {\n if (!options || !options.name) return 'v1:quick';\n return `v1:named:${options.name}`;\n}\n\n/** Strip the optional `v1:` prefix so legacy hashes compare equal. */\nfunction normaliseHash(hash: string): string {\n return hash.startsWith('v1:') ? hash.slice(3) : hash;\n}\n\nexport function optionsHashesEqual(a: string, b: string): boolean {\n return normaliseHash(a) === normaliseHash(b);\n}\n\nexport function fallbackOptionsHash(info: TunnelInfo): string {\n return info.name ? computeOptionsHash({ name: info.name }) : 'v1:quick';\n}\n\nexport function effectiveOptionsHash(\n info: TunnelInfo,\n meta: TunnelMetaEntry | undefined\n): string {\n return meta?.optionsHash ?? fallbackOptionsHash(info);\n}\n\nexport function tunnelConfigChanged(\n meta: TunnelMetaEntry | undefined,\n config: { accountId: string; zoneId: string }\n): boolean {\n return (\n (meta?.accountId !== undefined && meta.accountId !== config.accountId) ||\n (meta?.zoneId !== undefined && meta.zoneId !== config.zoneId)\n );\n}\n\nexport function namedTunnelInfoFromMeta(\n port: number,\n meta: TunnelMetaEntry | undefined\n): NamedTunnelInfo | undefined {\n if (!meta?.needsRespawn || !meta.tunnelId || !meta.name || !meta.hostname) {\n return undefined;\n }\n return {\n id: meta.tunnelId,\n port,\n name: meta.name,\n hostname: meta.hostname,\n url: `https://${meta.hostname}`,\n createdAt: new Date().toISOString()\n };\n}\n\nexport function createNamedTunnelCleanupEntry(\n info: TunnelInfo,\n meta: TunnelMetaEntry | undefined\n): TunnelCleanupEntry | undefined {\n if (!info.name || !meta?.dnsRecordId) return undefined;\n return {\n tunnelId: info.id,\n port: info.port,\n name: info.name,\n hostname: info.hostname,\n dnsRecordId: meta.dnsRecordId,\n accountId: meta.accountId,\n zoneId: meta.zoneId,\n phase: 'claimed',\n updatedAt: new Date().toISOString()\n };\n}\n\nexport function createNamedTunnelResourceIntent(args: {\n port: number;\n name: string;\n hostname: string;\n tunnelName: string;\n sandboxId: string;\n accountId: string;\n zoneId: string;\n}): TunnelCleanupEntry {\n return {\n port: args.port,\n name: args.name,\n hostname: args.hostname,\n tunnelName: args.tunnelName,\n sandboxId: args.sandboxId,\n accountId: args.accountId,\n zoneId: args.zoneId,\n phase: 'planned',\n updatedAt: new Date().toISOString()\n };\n}\n\nexport function namedRespawnMeta(\n info: NamedTunnelInfo,\n existing?: TunnelMetaEntry\n): TunnelMetaEntry {\n return {\n optionsHash:\n existing?.optionsHash ?? computeOptionsHash({ name: info.name }),\n dnsRecordId: existing?.dnsRecordId,\n accountId: existing?.accountId,\n zoneId: existing?.zoneId,\n tunnelId: info.id,\n name: info.name,\n hostname: info.hostname,\n needsRespawn: true\n };\n}\n\nexport function markCleanupTunnelReady(\n entry: TunnelCleanupEntry,\n tunnelId: string\n): TunnelCleanupEntry {\n return {\n ...entry,\n tunnelId,\n phase: 'tunnel_ready',\n updatedAt: new Date().toISOString()\n };\n}\n\nexport function markCleanupDNSReady(\n entry: TunnelCleanupEntry,\n dnsRecordId: string\n): TunnelCleanupEntry {\n return {\n ...entry,\n dnsRecordId,\n phase: 'claimed',\n updatedAt: new Date().toISOString()\n };\n}\n","import type { Logger } from '@repo/shared';\nimport {\n deleteDNSRecord,\n deleteTunnel,\n findCNAME,\n findTunnelByName\n} from './cloudflare-api';\nimport {\n CLEANUP_STORAGE_KEY,\n readCleanupMap,\n type TunnelCleanupEntry,\n type TunnelsStorage\n} from './storage';\n\nexport interface NamedTunnelCleanupHost {\n logger: Logger;\n getNamedTunnelConfig?: () => Promise<{\n token: string;\n accountId: string;\n zoneId: string;\n }>;\n fetcher?: typeof fetch;\n}\n\nexport async function cleanupNamedTunnelResources(\n host: NamedTunnelCleanupHost,\n entry: TunnelCleanupEntry,\n options?: {\n logPrefix?: string;\n credentialsUnavailableMessage?: string;\n onResolved?: (entry: TunnelCleanupEntry) => Promise<void>;\n }\n): Promise<boolean> {\n const logPrefix = options?.logPrefix ?? 'tunnel.cleanup';\n if (!host.getNamedTunnelConfig) {\n host.logger.warn(\n options?.credentialsUnavailableMessage ??\n `${logPrefix}: credentials unavailable`,\n {\n port: entry.port,\n tunnelId: entry.tunnelId,\n dnsRecordId: entry.dnsRecordId,\n error: 'Named tunnel configuration resolver is unavailable'\n }\n );\n return false;\n }\n\n let config: { token: string; accountId: string; zoneId: string };\n try {\n config = await host.getNamedTunnelConfig();\n } catch (err) {\n host.logger.warn(\n options?.credentialsUnavailableMessage ??\n `${logPrefix}: credentials unavailable`,\n {\n port: entry.port,\n tunnelId: entry.tunnelId,\n dnsRecordId: entry.dnsRecordId,\n error: err instanceof Error ? err.message : String(err)\n }\n );\n return false;\n }\n\n const accountId = entry.accountId ?? config.accountId;\n const zoneId = entry.zoneId ?? config.zoneId;\n const fetcher = host.fetcher;\n let tunnelId = entry.tunnelId;\n if (!tunnelId && entry.tunnelName && entry.sandboxId) {\n const tunnel = await findTunnelByName({\n token: config.token,\n accountId,\n tunnelName: entry.tunnelName,\n expectedSandboxId: entry.sandboxId,\n fetcher\n });\n tunnelId = tunnel?.id;\n }\n\n let dnsRecordId = entry.dnsRecordId;\n if (!dnsRecordId && tunnelId) {\n const dnsRecord = await findCNAME({\n token: config.token,\n zoneId,\n hostname: entry.hostname,\n cnameTarget: `${tunnelId}.cfargotunnel.com`,\n fetcher\n });\n dnsRecordId = dnsRecord?.recordId;\n }\n\n if (tunnelId || dnsRecordId) {\n await options?.onResolved?.({\n ...entry,\n ...(tunnelId && { tunnelId }),\n ...(dnsRecordId && { dnsRecordId }),\n phase: dnsRecordId ? 'claimed' : 'tunnel_ready',\n updatedAt: new Date().toISOString()\n });\n }\n\n if (!tunnelId && !dnsRecordId) return true;\n\n let cleanupFailed = false;\n await Promise.allSettled([\n dnsRecordId\n ? deleteDNSRecord({\n token: config.token,\n zoneId,\n recordId: dnsRecordId,\n fetcher\n }).catch((err) => {\n cleanupFailed = true;\n host.logger.warn(`${logPrefix}: dns delete failed`, {\n port: entry.port,\n tunnelId,\n recordId: dnsRecordId,\n zoneId,\n error: err instanceof Error ? err.message : String(err)\n });\n })\n : Promise.resolve(),\n tunnelId\n ? deleteTunnel({\n token: config.token,\n accountId,\n tunnelId,\n fetcher\n }).catch((err) => {\n cleanupFailed = true;\n host.logger.warn(`${logPrefix}: tunnel delete failed`, {\n port: entry.port,\n tunnelId,\n accountId,\n error: err instanceof Error ? err.message : String(err)\n });\n })\n : Promise.resolve()\n ]);\n\n return !cleanupFailed;\n}\n\nexport async function resumeNamedTunnelCleanupEntry(\n host: NamedTunnelCleanupHost,\n storage: TunnelsStorage,\n portKey: string,\n entry: TunnelCleanupEntry,\n options?: {\n logPrefix?: string;\n credentialsUnavailableMessage?: string;\n onResolved?: (entry: TunnelCleanupEntry) => Promise<void>;\n }\n): Promise<boolean> {\n if (\n !(await cleanupNamedTunnelResources(host, entry, {\n ...options,\n onResolved: async (resolved) => {\n await storage.transaction(async (txn) => {\n const cleanup = await readCleanupMap(txn);\n cleanup[portKey] = resolved;\n await txn.put(CLEANUP_STORAGE_KEY, cleanup);\n });\n await options?.onResolved?.(resolved);\n }\n }))\n ) {\n return false;\n }\n\n await storage.transaction(async (txn) => {\n const cleanup = await readCleanupMap(txn);\n delete cleanup[portKey];\n await txn.put(CLEANUP_STORAGE_KEY, cleanup);\n });\n return true;\n}\n\nexport async function resumeNamedTunnelCleanupRecords(\n host: NamedTunnelCleanupHost,\n storage: TunnelsStorage\n): Promise<void> {\n const cleanup = await readCleanupMap(storage);\n for (const [portKey, entry] of Object.entries(cleanup)) {\n try {\n await resumeNamedTunnelCleanupEntry(host, storage, portKey, entry);\n } catch (err) {\n host.logger.warn('tunnels.resumeCleanup: cleanup record failed', {\n port: entry.port,\n tunnelId: entry.tunnelId,\n error: err instanceof Error ? err.message : String(err)\n });\n }\n }\n}\n","import type {\n CurrentRuntimeIdentity,\n RuntimeIdentity\n} from '../current-runtime-identity';\nimport { RuntimeIdentityInactiveError } from '../current-runtime-identity';\nimport { ErrorCode, OperationInterruptedError } from '../errors';\nimport type {\n CurrentSandboxLifetime,\n SandboxLifetime\n} from '../sandbox-lifetime';\nimport { SandboxLifetimeChangedError } from '../sandbox-lifetime';\n\nexport interface TunnelLifecycleHost {\n currentRuntime?: Pick<CurrentRuntimeIdentity, 'get' | 'assertActive'>;\n currentLifetime?: Pick<\n CurrentSandboxLifetime,\n 'getOrCreate' | 'assertCurrent'\n >;\n}\n\nexport interface TunnelLifecycleSnapshot {\n runtime?: RuntimeIdentity;\n lifetime?: SandboxLifetime;\n}\n\nconst TUNNEL_GET_MAX_RECOVERY_ATTEMPTS = 2;\n\nexport class TunnelOperationLifecycle {\n readonly #host: TunnelLifecycleHost;\n\n constructor(host: TunnelLifecycleHost) {\n this.#host = host;\n }\n\n async capture(): Promise<TunnelLifecycleSnapshot> {\n return {\n runtime: await this.#captureRuntime(),\n lifetime: await this.#host.currentLifetime?.getOrCreate()\n };\n }\n\n async requireRuntime(\n snapshot: TunnelLifecycleSnapshot,\n phase: string,\n admitted: true | 'unknown'\n ): Promise<TunnelLifecycleSnapshot> {\n if (snapshot.runtime) return snapshot;\n const runtime = await this.#captureRuntime();\n if (runtime) {\n return { ...snapshot, runtime };\n }\n if (this.#host.currentRuntime) {\n throw createTunnelInterruptedError({\n reason: 'runtime_replaced',\n phase,\n admitted,\n retryable: true,\n message: 'Tunnel operation was interrupted by a runtime replacement'\n });\n }\n return snapshot;\n }\n\n async assertActive(\n snapshot: TunnelLifecycleSnapshot,\n phase: string,\n admitted: true | 'unknown'\n ): Promise<void> {\n try {\n if (snapshot.runtime) {\n await this.#host.currentRuntime?.assertActive(snapshot.runtime);\n }\n if (snapshot.lifetime) {\n await this.#host.currentLifetime?.assertCurrent(snapshot.lifetime);\n }\n } catch (error) {\n if (error instanceof RuntimeIdentityInactiveError) {\n throw createTunnelInterruptedError({\n reason: 'runtime_replaced',\n phase,\n admitted,\n retryable: true,\n message: 'Tunnel operation was interrupted by a runtime replacement'\n });\n }\n if (error instanceof SandboxLifetimeChangedError) {\n throw createTunnelInterruptedError({\n reason: 'sandbox_lifetime_changed',\n phase,\n admitted,\n retryable: false,\n message:\n 'Tunnel operation was interrupted by a sandbox lifetime change'\n });\n }\n throw error;\n }\n }\n\n async runGetWithRecovery<T>(attempt: () => Promise<T>): Promise<T> {\n let recoveryAttempts = 0;\n while (true) {\n try {\n return await attempt();\n } catch (error) {\n if (!(error instanceof OperationInterruptedError)) throw error;\n if (!error.context.retryable) throw error;\n if (recoveryAttempts >= TUNNEL_GET_MAX_RECOVERY_ATTEMPTS) {\n throw createTunnelRecoveryExhaustedError(error, recoveryAttempts);\n }\n recoveryAttempts += 1;\n }\n }\n }\n\n async #captureRuntime(): Promise<RuntimeIdentity | undefined> {\n const currentRuntime = this.#host.currentRuntime;\n if (!currentRuntime) return undefined;\n const runtime = await currentRuntime.get();\n if (!runtime) return undefined;\n await currentRuntime.assertActive(runtime);\n return runtime;\n }\n}\n\nfunction createTunnelInterruptedError(params: {\n reason: 'runtime_replaced' | 'sandbox_lifetime_changed';\n phase: string;\n admitted: true | 'unknown';\n retryable: boolean;\n message: string;\n}): OperationInterruptedError {\n return new OperationInterruptedError({\n message: params.message,\n code: ErrorCode.OPERATION_INTERRUPTED,\n httpStatus: 409,\n context: {\n reason: params.reason,\n operation: 'tunnel.get',\n phase: params.phase,\n admitted: params.admitted,\n retryable: params.retryable\n },\n timestamp: new Date().toISOString(),\n suggestion: 'Retry tunnels.get() with the same port and options.'\n });\n}\n\nfunction createTunnelRecoveryExhaustedError(\n error: OperationInterruptedError,\n recoveryAttempts: number\n): OperationInterruptedError {\n return new OperationInterruptedError({\n message: 'Tunnel operation recovery attempts were exhausted',\n code: ErrorCode.OPERATION_INTERRUPTED,\n httpStatus: 409,\n context: {\n ...error.context,\n reason: 'recovery_exhausted',\n retryable: true,\n recoveryAttempts,\n maxRecoveryAttempts: TUNNEL_GET_MAX_RECOVERY_ATTEMPTS\n },\n timestamp: new Date().toISOString(),\n suggestion: 'Retry tunnels.get() with the same port and options.'\n });\n}\n","// Crockford base32 alphabet (lowercase), excluding i, l, o, u to avoid\n// visual ambiguity. 32 symbols means each character carries exactly 5 bits\n// of entropy drawn uniformly from crypto.getRandomValues, and the alphabet\n// is safe to use as a DNS label or URL path segment.\nconst ID_ALPHABET = '0123456789abcdefghjkmnpqrstvwxyz';\n\n/**\n * Generate a URL- and DNS-safe random id. The default 20 characters give\n * ~100 bits of entropy, which keeps collisions negligible at any realistic\n * volume without a uniqueness retry loop. 32 divides 256 evenly, so masking\n * the low 5 bits of each random byte stays uniform.\n */\nexport function randomId(size = 20): string {\n const bytes = new Uint8Array(size);\n crypto.getRandomValues(bytes);\n let id = '';\n for (let i = 0; i < size; i += 1) {\n id += ID_ALPHABET[bytes[i] & 31];\n }\n return id;\n}\n","import type {\n EnsureTunnelRunRequest,\n EnsureTunnelRunResult,\n NamedTunnelInfo,\n QuickTunnelInfo,\n SandboxTunnelsAPI\n} from '@repo/shared';\nimport { RPCTransportError } from '../errors';\nimport {\n createTunnel,\n findTunnelByName,\n getTunnelToken,\n getZoneName,\n upsertCNAME\n} from './cloudflare-api';\nimport { randomId } from './random-id';\nimport {\n computeOptionsHash,\n createNamedTunnelResourceIntent,\n type TunnelCleanupEntry,\n type TunnelMetaEntry\n} from './storage';\n\ninterface TunnelsRPCClient {\n tunnels: SandboxTunnelsAPI;\n}\n\nexport interface TunnelProvisionerHost {\n client: TunnelsRPCClient;\n sandboxId?: string;\n getNamedTunnelConfig?: () => Promise<{\n token: string;\n accountId: string;\n zoneId: string;\n }>;\n fetcher?: typeof fetch;\n}\n\nexport interface PreparedNamedTunnel {\n tunnelId: string;\n tunnelToken: string;\n info: NamedTunnelInfo;\n meta: TunnelMetaEntry;\n}\n\nexport interface NamedTunnelPreparationHooks {\n onIntentReady?: (entry: TunnelCleanupEntry) => Promise<void>;\n onTunnelReady?: (tunnelId: string) => Promise<void>;\n onDNSReady?: (dnsRecordId: string) => Promise<void>;\n}\n\nfunction createQuickTunnelId(): string {\n return `quick-${randomId()}`;\n}\n\n// Replays use the same request so container runId idempotency can resolve\n// an ambiguous transport failure.\nconst TUNNEL_RUN_TRANSPORT_REPLAY_ATTEMPTS = 1;\n\nexport class TunnelProvisioner {\n readonly #host: TunnelProvisionerHost;\n #zoneNamePromise: Promise<string> | null = null;\n\n constructor(host: TunnelProvisionerHost) {\n this.#host = host;\n }\n\n async provisionQuickTunnel(\n port: number,\n tunnelRunId: string,\n tunnelId = createQuickTunnelId()\n ): Promise<QuickTunnelInfo> {\n const result = await this.#ensureTunnelRun({\n mode: 'quick',\n tunnelId,\n runId: tunnelRunId,\n port\n });\n if (result.run.mode !== 'quick') {\n throw new Error('Container returned a non-quick tunnel run');\n }\n return {\n id: result.run.tunnelId,\n port: result.run.port,\n url: result.run.url,\n hostname: result.run.hostname,\n createdAt: result.run.startedAt\n };\n }\n\n async prepareNamedTunnel(\n port: number,\n name: string,\n hooks: NamedTunnelPreparationHooks = {}\n ): Promise<PreparedNamedTunnel> {\n if (!this.#host.sandboxId) {\n throw new Error(\n 'Named tunnels require host.sandboxId on the tunnel service.'\n );\n }\n if (!this.#host.getNamedTunnelConfig) {\n throw new Error(\n 'Named tunnels require host.getNamedTunnelConfig on the tunnel service.'\n );\n }\n\n const config = await this.#host.getNamedTunnelConfig();\n const zoneName = await this.#getZoneName({\n token: config.token,\n zoneId: config.zoneId\n });\n const hostname = `${name}.${zoneName}`;\n const sandboxId = this.#host.sandboxId;\n const tunnelName = `sandbox-${sandboxId}-${name}`;\n await hooks.onIntentReady?.(\n createNamedTunnelResourceIntent({\n port,\n name,\n hostname,\n tunnelName,\n sandboxId,\n accountId: config.accountId,\n zoneId: config.zoneId\n })\n );\n\n let tunnelId: string;\n let tunnelToken: string;\n const existingTunnel = await findTunnelByName({\n token: config.token,\n accountId: config.accountId,\n tunnelName,\n expectedSandboxId: sandboxId,\n fetcher: this.#host.fetcher\n });\n if (existingTunnel) {\n tunnelId = existingTunnel.id;\n tunnelToken = await getTunnelToken({\n token: config.token,\n accountId: config.accountId,\n tunnelId,\n fetcher: this.#host.fetcher\n });\n } else {\n const created = await createTunnel({\n token: config.token,\n accountId: config.accountId,\n tunnelName,\n metadata: {\n sandboxId,\n createdBy: 'sandbox-sdk',\n name,\n port\n },\n fetcher: this.#host.fetcher\n });\n tunnelId = created.id;\n tunnelToken = created.token;\n }\n await hooks.onTunnelReady?.(tunnelId);\n\n const dnsResult = await upsertCNAME({\n token: config.token,\n zoneId: config.zoneId,\n hostname,\n cnameTarget: `${tunnelId}.cfargotunnel.com`,\n comment: `sandbox-${sandboxId}`,\n sandboxId,\n fetcher: this.#host.fetcher\n });\n await hooks.onDNSReady?.(dnsResult.recordId);\n\n const info: NamedTunnelInfo = {\n id: tunnelId,\n port,\n name,\n hostname,\n url: `https://${hostname}`,\n createdAt: new Date().toISOString()\n };\n\n return {\n tunnelId,\n tunnelToken,\n info,\n meta: {\n optionsHash: computeOptionsHash({ name }),\n dnsRecordId: dnsResult.recordId,\n accountId: config.accountId,\n zoneId: config.zoneId,\n tunnelId,\n name,\n hostname\n }\n };\n }\n\n async startNamedTunnelRun(\n prepared: PreparedNamedTunnel,\n tunnelRunId: string\n ): Promise<void> {\n const result = await this.#ensureTunnelRun({\n mode: 'named',\n tunnelId: prepared.tunnelId,\n runId: tunnelRunId,\n port: prepared.info.port,\n cloudflaredToken: prepared.tunnelToken\n });\n if (result.run.mode !== 'named') {\n throw new Error('Container returned a non-named tunnel run');\n }\n }\n\n clearCachedZoneName(): void {\n this.#zoneNamePromise = null;\n }\n\n async #ensureTunnelRun(\n request: EnsureTunnelRunRequest\n ): Promise<EnsureTunnelRunResult> {\n let replays = 0;\n while (true) {\n try {\n return await this.#host.client.tunnels.ensureTunnelRun(request);\n } catch (error) {\n if (\n !(error instanceof RPCTransportError) ||\n replays >= TUNNEL_RUN_TRANSPORT_REPLAY_ATTEMPTS\n ) {\n throw error;\n }\n replays += 1;\n }\n }\n }\n\n async #getZoneName(config: {\n token: string;\n zoneId: string;\n }): Promise<string> {\n if (!this.#zoneNamePromise) {\n const pending = getZoneName({\n token: config.token,\n zoneId: config.zoneId,\n fetcher: this.#host.fetcher\n });\n this.#zoneNamePromise = pending;\n pending.catch(() => {\n if (this.#zoneNamePromise === pending) {\n this.#zoneNamePromise = null;\n }\n });\n }\n return this.#zoneNamePromise;\n }\n}\n","import {\n META_STORAGE_KEY,\n namedRespawnMeta,\n readMap,\n readMetaMap,\n STORAGE_KEY,\n type TunnelMap,\n type TunnelMetaEntry,\n type TunnelMetaMap,\n type TunnelsStorage\n} from './storage';\n\n/**\n * Reconcile storage with a fresh container.\n *\n * Called from `Sandbox.onStart()` after every container restart. The\n * `cloudflared` processes the container was running all died with it, so\n * any stored record is not currently backed by a running tunnel.\n *\n * Quick tunnels are dropped because the `*.trycloudflare.com` URL is bound\n * to the dead process. Named tunnels keep private metadata so a later\n * `get(port, { name })` can respawn cloudflared against the existing\n * Cloudflare tunnel and DNS record.\n */\nfunction isHiddenNamedRespawnMeta(\n entry: TunnelMetaEntry | undefined\n): entry is TunnelMetaEntry & {\n needsRespawn: true;\n tunnelId: string;\n name: string;\n hostname: string;\n} {\n return (\n entry?.needsRespawn === true &&\n typeof entry.tunnelId === 'string' &&\n typeof entry.name === 'string' &&\n typeof entry.hostname === 'string'\n );\n}\n\nexport async function pruneTunnelsForRestart(\n storage: TunnelsStorage\n): Promise<void> {\n await storage.transaction(async (txn) => {\n const map = await readMap(txn);\n const meta = await readMetaMap(txn);\n const nextMap: TunnelMap = {};\n const nextMeta: TunnelMetaMap = {};\n for (const [portKey, info] of Object.entries(map)) {\n // Discriminate by the public `name` field on `TunnelInfo`: named\n // tunnels carry the user-provided label, quick tunnels omit it.\n if (info.name) {\n nextMeta[portKey] = namedRespawnMeta(info, meta[portKey]);\n }\n }\n for (const [portKey, entry] of Object.entries(meta)) {\n if (nextMeta[portKey] || !isHiddenNamedRespawnMeta(entry)) continue;\n nextMeta[portKey] = entry;\n }\n await txn.put(STORAGE_KEY, nextMap);\n await txn.put(META_STORAGE_KEY, nextMeta);\n });\n}\n","/**\n * Tunnel domain service. Owns public tunnel operations, runtime restart\n * reconciliation, process-exit handling, and durable named-resource cleanup.\n */\n\nimport type {\n Logger,\n NamedTunnelInfo,\n QuickTunnelInfo,\n SandboxTunnelsAPI,\n TunnelInfo,\n TunnelOptions\n} from '@repo/shared';\nimport { logCanonicalEvent } from '@repo/shared';\nimport type { CurrentRuntimeIdentity } from '../current-runtime-identity';\nimport type { CurrentSandboxLifetime } from '../sandbox-lifetime';\nimport {\n SandboxSecurityError,\n validatePort,\n validateTunnelName\n} from '../security';\nimport {\n cleanupNamedTunnelResources,\n resumeNamedTunnelCleanupEntry,\n resumeNamedTunnelCleanupRecords\n} from './cleanup';\nimport { TunnelOperationLifecycle } from './lifecycle';\nimport { TunnelProvisioner } from './provisioner';\nimport { randomId } from './random-id';\nimport { pruneTunnelsForRestart } from './restart';\nimport type { TunnelCleanupEntry, TunnelsStorage } from './storage';\nimport {\n CLEANUP_STORAGE_KEY,\n computeOptionsHash,\n createNamedTunnelCleanupEntry,\n effectiveOptionsHash,\n META_STORAGE_KEY,\n markCleanupDNSReady,\n markCleanupTunnelReady,\n namedRespawnMeta,\n namedTunnelInfoFromMeta,\n optionsHashesEqual,\n readMap,\n readMetaMap,\n readPortState,\n STORAGE_KEY,\n tunnelConfigChanged,\n updatePortState\n} from './storage';\n\nexport type { TunnelsStorage, TunnelsStorageTxn } from './storage';\n\n/** Subset of the RPC client this service depends on. */\ninterface TunnelsRPCClient {\n tunnels: SandboxTunnelsAPI;\n}\n\n/** Subset of the Sandbox DO the service reads from. */\nexport interface TunnelServiceHost {\n client: TunnelsRPCClient;\n storage: TunnelsStorage;\n logger: Logger;\n /**\n * Sandbox identifier used for tagging Cloudflare resources\n * (`metadata.sandboxId` on tunnels, `comment: 'sandbox-<id>'` on DNS).\n * Required only when callers exercise `get(port, { name })`; quick\n * tunnels do not touch the Cloudflare API.\n */\n sandboxId?: string;\n /**\n * Lazy provider of the three credentials needed for named-tunnel\n * provisioning. The service calls it when validating named cache\n * entries and when preparing named tunnel resources.\n *\n * Throws (via the underlying resolver) when any required value is\n * missing or unresolvable. The service surfaces that error verbatim.\n */\n getNamedTunnelConfig?: () => Promise<{\n token: string;\n accountId: string;\n zoneId: string;\n }>;\n /**\n * Override the global `fetch` used for Cloudflare API calls. Defaults\n * to the global `fetch`. Tests inject a mock here.\n */\n fetcher?: typeof fetch;\n /** Runtime fence for records backed by a current container process. */\n currentRuntime?: Pick<CurrentRuntimeIdentity, 'get' | 'assertActive'>;\n /** Sandbox lifetime fence for operations that must not cross destroy(). */\n currentLifetime?: Pick<\n CurrentSandboxLifetime,\n 'getOrCreate' | 'assertCurrent'\n >;\n}\n\nexport interface TunnelsHandler {\n get(port: number, options?: TunnelOptions): Promise<TunnelInfo>;\n list(): Promise<TunnelInfo[]>;\n destroy(portOrInfo: number | TunnelInfo): Promise<void>;\n}\n\n/**\n * Container-driven exit hook. Invoked by `SandboxControlCallbackImpl`\n * when the container reports that a `cloudflared` process has\n * exited. NOT part of the public `TunnelsHandler` interface —\n * exposed only through the factory's return shape so the public\n * `sandbox.tunnels` API stays narrow.\n */\nexport type TunnelExitHandler = (\n id: string,\n port: number,\n exitCode: number | null,\n tunnelRunId?: string\n) => Promise<void>;\n\nexport interface TunnelsHandle {\n tunnels: TunnelsHandler;\n handleTunnelExit: TunnelExitHandler;\n /**\n * Tear down every tunnel currently stored. Called by the Sandbox DO's\n * `destroy()` so the Cloudflare-side resources don't outlive the\n * sandbox that provisioned them.\n *\n * Best-effort: a failure on one port is logged but doesn't abort the\n * rest. NOT part of the public `TunnelsHandler` surface — users don't\n * call this; they call `destroy(port)` for an individual tunnel.\n */\n destroyAll: () => Promise<void>;\n /** Resume retained Cloudflare-side cleanup records. Internal lifecycle hook. */\n resumeCleanup: () => Promise<void>;\n /** Reconcile durable tunnel state after a fresh container runtime starts. */\n onRuntimeStart: () => Promise<void>;\n /** Reconcile durable tunnel state after the container runtime stops. */\n onRuntimeStop: () => Promise<void>;\n /** Clear public tunnel state after sandbox destroy processing completes. */\n clearDurableStateAfterDestroy: () => Promise<void>;\n}\n\n/** Per-port serializer shared by public operations and exit hooks. */\ntype WithPortLock = <T>(port: number, fn: () => Promise<T>) => Promise<T>;\n\ntype TunnelGetCacheState = 'hit' | 'miss';\n\ninterface TunnelGetResult {\n info: TunnelInfo;\n cacheState: TunnelGetCacheState;\n}\n\nfunction validateTunnelPort(port: number): void {\n if (!validatePort(port)) {\n throw new SandboxSecurityError(\n `Invalid port number: ${port}. Must be 1024-65535, excluding reserved ports.`\n );\n }\n}\n\n/**\n * Match a structured SandboxError code anywhere on the error — translated\n * SandboxErrors expose the code both as a top-level `code` field and on\n * the nested `errorResponse.code`. Used for SDK-recognized container\n * errors that have operation-specific recovery behavior.\n *\n * Structured matching keeps quoted code tokens in human-readable\n * messages from being treated as machine-readable error codes.\n */\nfunction hasErrorCode(error: unknown, code: string): boolean {\n if (!error || typeof error !== 'object') return false;\n const e = error as {\n code?: unknown;\n errorResponse?: { code?: unknown };\n };\n if (e.code === code) return true;\n if (e.errorResponse?.code === code) return true;\n return false;\n}\n\nfunction isTunnelNotFoundError(error: unknown): boolean {\n return hasErrorCode(error, 'TUNNEL_NOT_FOUND');\n}\n\ninterface TunnelGetRecoveryState {\n quickRun?: {\n tunnelId: string;\n runId: string;\n };\n}\n\nfunction createTunnelRunId(): string {\n return `run-${randomId()}`;\n}\n\n/** Tunnel domain façade for public operations and lifecycle hooks. */\nexport class TunnelService implements TunnelsHandler {\n readonly #host: TunnelServiceHost;\n readonly #portLocks = new Map<number, Promise<unknown>>();\n readonly #lifecycle: TunnelOperationLifecycle;\n readonly #provisioner: TunnelProvisioner;\n\n constructor(host: TunnelServiceHost) {\n this.#host = host;\n this.#lifecycle = new TunnelOperationLifecycle(host);\n this.#provisioner = new TunnelProvisioner(host);\n }\n\n #withPortLock: WithPortLock = <T>(\n port: number,\n fn: () => Promise<T>\n ): Promise<T> => {\n const previous = this.#portLocks.get(port) ?? Promise.resolve();\n const next = previous.then(fn, fn);\n this.#portLocks.set(\n port,\n next.catch(() => undefined)\n );\n return next;\n };\n\n async get(port: number, options?: TunnelOptions): Promise<TunnelInfo> {\n const startTime = Date.now();\n let outcome: 'success' | 'error' = 'error';\n let cacheState: TunnelGetCacheState = 'miss';\n let caughtError: Error | undefined;\n try {\n validateTunnelPort(port);\n if (options?.name !== undefined) validateTunnelName(options.name);\n const requestedHash = computeOptionsHash(options);\n\n const recovery: TunnelGetRecoveryState = {};\n const result = await this.#withPortLock(port, () =>\n this.#lifecycle.runGetWithRecovery(() =>\n this.#getLocked(port, options, requestedHash, recovery)\n )\n );\n cacheState = result.cacheState;\n outcome = 'success';\n return result.info;\n } catch (error) {\n caughtError = error instanceof Error ? error : new Error(String(error));\n throw error;\n } finally {\n logCanonicalEvent(this.#host.logger, {\n event: 'tunnel.get',\n outcome,\n port,\n cacheState,\n durationMs: Date.now() - startTime,\n error: caughtError\n });\n }\n }\n\n async #getLocked(\n port: number,\n options: TunnelOptions | undefined,\n requestedHash: string,\n recovery: TunnelGetRecoveryState\n ): Promise<TunnelGetResult> {\n const state = await readPortState(this.#host.storage, port);\n const existing = state.info;\n const metaEntry = state.meta;\n\n if (existing) {\n this.#assertSameOptions(\n port,\n effectiveOptionsHash(existing, metaEntry),\n requestedHash\n );\n\n if (metaEntry?.needsRespawn && existing.name) {\n return {\n info: await this.#provisionNamedTunnel(port, existing.name),\n cacheState: 'miss'\n };\n }\n\n if (existing.name && this.#host.getNamedTunnelConfig) {\n const currentConfig = await this.#host.getNamedTunnelConfig();\n if (tunnelConfigChanged(metaEntry, currentConfig)) {\n this.#provisioner.clearCachedZoneName();\n return {\n info: await this.#provisionNamedTunnel(port, existing.name),\n cacheState: 'miss'\n };\n }\n }\n\n if (this.#host.currentRuntime) {\n const currentRuntime = await this.#host.currentRuntime.get();\n if (\n !metaEntry?.runtimeIdentityID ||\n currentRuntime?.id !== metaEntry.runtimeIdentityID\n ) {\n return {\n info: existing.name\n ? await this.#provisionNamedTunnel(port, existing.name)\n : await this.#provisionQuickTunnel(port, recovery),\n cacheState: 'miss'\n };\n }\n }\n\n return { info: existing, cacheState: 'hit' };\n }\n\n if (metaEntry?.needsRespawn) {\n this.#assertSameOptions(port, metaEntry.optionsHash, requestedHash);\n }\n\n return {\n info: options?.name\n ? await this.#provisionNamedTunnel(port, options.name)\n : await this.#provisionQuickTunnel(port, recovery),\n cacheState: 'miss'\n };\n }\n\n #assertSameOptions(\n port: number,\n existingHash: string,\n requestedHash: string\n ): void {\n if (optionsHashesEqual(existingHash, requestedHash)) return;\n throw new Error(\n `Tunnel on port ${port} was created with different options. ` +\n `Call destroy(${port}) before changing tunnel options.`\n );\n }\n\n async #resumePortCleanup(port: number): Promise<void> {\n const entry = (await readPortState(this.#host.storage, port)).cleanup;\n if (!entry) return;\n\n if (\n !(await resumeNamedTunnelCleanupEntry(\n this.#host,\n this.#host.storage,\n port.toString(),\n entry\n ))\n ) {\n throw new Error(\n `Pending named tunnel cleanup for port ${port} could not complete.`\n );\n }\n }\n\n async #updatePortCleanup(\n port: number,\n next: (entry: TunnelCleanupEntry | undefined) => TunnelCleanupEntry\n ): Promise<void> {\n await updatePortState(this.#host.storage, port, (state) => ({\n cleanup: next(state.cleanup)\n }));\n }\n\n #requireCleanupEntry(\n port: number,\n entry: TunnelCleanupEntry | undefined\n ): TunnelCleanupEntry {\n if (entry) return entry;\n throw new Error(`Missing named tunnel cleanup intent for port ${port}`);\n }\n\n /** Provision a fresh quick tunnel and persist it. Caller holds the per-port lock. */\n async #provisionQuickTunnel(\n port: number,\n recovery: TunnelGetRecoveryState\n ): Promise<QuickTunnelInfo> {\n let lifecycle = await this.#lifecycle.capture();\n recovery.quickRun ??= {\n tunnelId: `quick-${randomId()}`,\n runId: createTunnelRunId()\n };\n const { tunnelId, runId } = recovery.quickRun;\n const spawned = await this.#provisioner.provisionQuickTunnel(\n port,\n runId,\n tunnelId\n );\n lifecycle = await this.#lifecycle.requireRuntime(\n lifecycle,\n 'process_ready',\n true\n );\n await this.#lifecycle.assertActive(lifecycle, 'process_ready', true);\n await updatePortState(this.#host.storage, port, () => ({\n info: spawned,\n meta: {\n optionsHash: 'v1:quick',\n ...(lifecycle.runtime && {\n runtimeIdentityID: lifecycle.runtime.id\n }),\n ...(lifecycle.lifetime && {\n sandboxLifetimeID: lifecycle.lifetime.id\n }),\n tunnelRunId: runId\n }\n }));\n await this.#lifecycle.assertActive(lifecycle, 'committing', true);\n return spawned;\n }\n\n async #provisionNamedTunnel(\n port: number,\n name: string\n ): Promise<NamedTunnelInfo> {\n let lifecycle = await this.#lifecycle.capture();\n\n await this.#resumePortCleanup(port);\n\n const prepared = await this.#provisioner.prepareNamedTunnel(port, name, {\n onIntentReady: (entry) => this.#updatePortCleanup(port, () => entry),\n onTunnelReady: (tunnelId) =>\n this.#updatePortCleanup(port, (entry) =>\n markCleanupTunnelReady(\n this.#requireCleanupEntry(port, entry),\n tunnelId\n )\n ),\n onDNSReady: (dnsRecordId) =>\n this.#updatePortCleanup(port, (entry) =>\n markCleanupDNSReady(\n this.#requireCleanupEntry(port, entry),\n dnsRecordId\n )\n )\n });\n const cleanupEntry = createNamedTunnelCleanupEntry(\n prepared.info,\n prepared.meta\n );\n if (cleanupEntry) {\n await updatePortState(this.#host.storage, port, () => ({\n cleanup: cleanupEntry\n }));\n }\n await this.#lifecycle.assertActive(\n lifecycle,\n 'cloudflare_ready',\n 'unknown'\n );\n\n const tunnelRunId = createTunnelRunId();\n await this.#provisioner.startNamedTunnelRun(prepared, tunnelRunId);\n lifecycle = await this.#lifecycle.requireRuntime(\n lifecycle,\n 'process_ready',\n true\n );\n await this.#lifecycle.assertActive(lifecycle, 'process_ready', true);\n\n await updatePortState(this.#host.storage, port, () => ({\n info: prepared.info,\n meta: {\n ...prepared.meta,\n ...(lifecycle.runtime && {\n runtimeIdentityID: lifecycle.runtime.id\n }),\n ...(lifecycle.lifetime && {\n sandboxLifetimeID: lifecycle.lifetime.id\n }),\n tunnelRunId\n },\n cleanup: undefined\n }));\n await this.#lifecycle.assertActive(lifecycle, 'committing', true);\n return prepared.info;\n }\n\n async destroy(portOrInfo: number | TunnelInfo): Promise<void> {\n const port = typeof portOrInfo === 'number' ? portOrInfo : portOrInfo.port;\n const startTime = Date.now();\n let outcome: 'success' | 'error' = 'error';\n let caughtError: Error | undefined;\n let tunnelId: string | undefined;\n try {\n await this.#withPortLock(port, async () => {\n const portKey = port.toString();\n const current = await readPortState(this.#host.storage, port);\n const existing =\n current.info ?? namedTunnelInfoFromMeta(port, current.meta);\n if (!existing) {\n if (current.cleanup) {\n await resumeNamedTunnelCleanupEntry(\n this.#host,\n this.#host.storage,\n portKey,\n current.cleanup,\n {\n logPrefix: 'tunnel.destroy',\n credentialsUnavailableMessage:\n 'tunnel.destroy: skipping CF cleanup, credentials unavailable'\n }\n );\n }\n return;\n }\n tunnelId = existing.id;\n const cleanupEntry = createNamedTunnelCleanupEntry(\n existing,\n current.meta\n );\n\n await updatePortState(this.#host.storage, port, () => ({\n info: undefined,\n meta: undefined,\n ...(cleanupEntry && { cleanup: cleanupEntry })\n }));\n\n try {\n if (current.meta?.tunnelRunId) {\n await this.#host.client.tunnels.stopTunnelRun({\n tunnelId: existing.id,\n runId: current.meta.tunnelRunId\n });\n }\n } catch (error) {\n if (isTunnelNotFoundError(error)) {\n // Container already forgot — fall through to CF cleanup.\n } else if (current.meta?.dnsRecordId) {\n this.#host.logger.warn(\n 'tunnel.destroy: container tunnel cleanup failed',\n {\n port,\n tunnelId,\n error: error instanceof Error ? error.message : String(error)\n }\n );\n } else {\n throw error;\n }\n }\n\n if (!current.meta?.dnsRecordId || !existing.name || !cleanupEntry) {\n return;\n }\n const cleaned = await cleanupNamedTunnelResources(\n this.#host,\n cleanupEntry,\n {\n logPrefix: 'tunnel.destroy',\n credentialsUnavailableMessage:\n 'tunnel.destroy: skipping CF cleanup, credentials unavailable'\n }\n );\n if (!cleaned) return;\n\n await updatePortState(this.#host.storage, port, () => ({\n cleanup: undefined\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.#host.logger, {\n event: 'tunnel.destroy',\n outcome,\n port,\n tunnelId,\n durationMs: Date.now() - startTime,\n error: caughtError\n });\n }\n }\n\n async list(): Promise<TunnelInfo[]> {\n const map = await readMap(this.#host.storage);\n const meta = await readMetaMap(this.#host.storage);\n return Object.entries(map)\n .filter(([port]) => !meta[port]?.needsRespawn)\n .map(([, info]) => info);\n }\n\n async onTunnelExit(\n id: string,\n port: number,\n exitCode: number | null,\n tunnelRunId?: string\n ): Promise<void> {\n const startTime = Date.now();\n let outcome: 'success' | 'error' = 'error';\n let caughtError: Error | undefined;\n try {\n await this.#withPortLock(port, async () => {\n await updatePortState(this.#host.storage, port, (state) => {\n const existing = state.info;\n const meta = state.meta;\n if (existing?.id !== id) return undefined;\n if (meta?.tunnelRunId && tunnelRunId !== meta.tunnelRunId) {\n return undefined;\n }\n\n return {\n info: undefined,\n meta: existing.name ? namedRespawnMeta(existing, meta) : undefined\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.#host.logger, {\n event: 'tunnel.exit',\n outcome,\n port,\n tunnelId: id,\n exitCode: exitCode ?? undefined,\n durationMs: Date.now() - startTime,\n error: caughtError\n });\n }\n }\n\n async resumeCleanup(): Promise<void> {\n await resumeNamedTunnelCleanupRecords(this.#host, this.#host.storage);\n }\n\n async destroyAll(): Promise<void> {\n const map = await readMap(this.#host.storage);\n const meta = await readMetaMap(this.#host.storage);\n const ports = new Set(Object.keys(map).map((p) => Number(p)));\n for (const [portKey, entry] of Object.entries(meta)) {\n const port = Number(portKey);\n if (Number.isFinite(port) && namedTunnelInfoFromMeta(port, entry)) {\n ports.add(port);\n }\n }\n\n for (const port of ports) {\n try {\n await this.destroy(port);\n } catch (err) {\n this.#host.logger.warn('tunnels.destroyAll: destroy(port) failed', {\n port,\n error: err instanceof Error ? err.message : String(err)\n });\n }\n }\n\n await this.resumeCleanup();\n }\n\n async onRuntimeStart(): Promise<void> {\n await pruneTunnelsForRestart(this.#host.storage);\n await this.resumeCleanup();\n }\n\n async onRuntimeStop(): Promise<void> {\n await pruneTunnelsForRestart(this.#host.storage);\n }\n\n async clearDurableStateAfterDestroy(): Promise<void> {\n await Promise.all([\n this.#host.storage.delete(STORAGE_KEY),\n this.#host.storage.delete(META_STORAGE_KEY),\n this.#host.storage.delete(CLEANUP_STORAGE_KEY)\n ]);\n }\n}\n","import { RpcTarget } from 'cloudflare:workers';\nimport type { TunnelInfo, TunnelOptions } from '@repo/shared';\nimport {\n type TunnelExitHandler,\n TunnelService,\n type TunnelServiceHost,\n type TunnelsHandle,\n type TunnelsHandler\n} from './tunnel-service';\n\nexport { pruneTunnelsForRestart } from './restart';\nexport type { TunnelsStorage, TunnelsStorageTxn } from './storage';\nexport type {\n TunnelExitHandler,\n TunnelServiceHost,\n TunnelsHandle,\n TunnelsHandler\n} from './tunnel-service';\n\n/**\n * Workers RPC adapter for the public `sandbox.tunnels` namespace.\n * The service owns tunnel lifecycle and resource invariants; this class\n * only provides a `RpcTarget` shape for Workers RPC compatibility.\n */\nclass TunnelsRpcTarget extends RpcTarget implements TunnelsHandler {\n constructor(private readonly service: TunnelService) {\n super();\n }\n\n get(port: number, options?: TunnelOptions): Promise<TunnelInfo> {\n return this.service.get(port, options);\n }\n\n list(): Promise<TunnelInfo[]> {\n return this.service.list();\n }\n\n destroy(portOrInfo: number | TunnelInfo): Promise<void> {\n return this.service.destroy(portOrInfo);\n }\n}\n\nexport function createTunnelsHandle(host: TunnelServiceHost): TunnelsHandle {\n const service = new TunnelService(host);\n const tunnels = new TunnelsRpcTarget(service);\n\n return {\n tunnels,\n handleTunnelExit: (id, port, exitCode, tunnelRunId) =>\n service.onTunnelExit(id, port, exitCode, tunnelRunId),\n destroyAll: () => service.destroyAll(),\n resumeCleanup: () => service.resumeCleanup(),\n onRuntimeStart: () => service.onRuntimeStart(),\n onRuntimeStop: () => service.onRuntimeStop(),\n clearDurableStateAfterDestroy: () => service.clearDurableStateAfterDestroy()\n };\n}\n","/**\n * `SandboxControlCallbackImpl` — capnweb `RpcTarget` the DO exposes as\n * `localMain` on the container-control session. The container reaches\n * it via `session.getRemoteMain<SandboxControlCallback>()` to push\n * tunnel-run events back to the DO.\n */\n\nimport type {\n Logger,\n SandboxControlCallback,\n TunnelRunExitEvent\n} from '@repo/shared';\nimport { RpcTarget } from 'capnweb';\nimport type { TunnelExitHandler } from './rpc-target';\n\nexport class SandboxControlCallbackImpl\n extends RpcTarget\n implements SandboxControlCallback\n{\n constructor(\n /**\n * Accessor (not a direct reference) so eviction can swap the handler\n * while keeping the capnweb session bound. Returns `null` if the\n * handler has not been constructed yet; callback methods no-op in\n * that window.\n */\n private readonly getHandler: () => TunnelExitHandler | null,\n private readonly logger: Logger\n ) {\n super();\n }\n\n async onTunnelRunExit(event: TunnelRunExitEvent): Promise<void> {\n const handler = this.getHandler();\n if (!handler) {\n this.logger.debug('onTunnelRunExit: no handler bound; ignoring', {\n tunnelId: event.tunnelId,\n runId: event.runId,\n mode: event.mode,\n port: event.port,\n exitCode: event.exitCode\n });\n return;\n }\n await handler(event.tunnelId, event.port, event.exitCode, event.runId);\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.13.0-next.681.1';\n","import { Container, getContainer, switchPort } from '@cloudflare/containers';\nimport type {\n BackupOptions,\n CheckChangesOptions,\n CheckChangesResult,\n CommandExecuteOptions,\n DirectoryBackup,\n ExecOptions,\n ExecResult,\n ExecutionSession,\n FileEncoding,\n ISandbox,\n ListFilesOptions,\n LogEvent,\n MountBucketOptions,\n PortWatchEvent,\n Process,\n ProcessOptions,\n ProcessQueryOptions,\n ProcessStatus,\n ReadFileResult,\n ReadFileStreamResult,\n RestoreBackupResult,\n SandboxExecOptions,\n SandboxOptions,\n SandboxProcess,\n SandboxProcessPromise,\n SandboxTerminal,\n SessionOptions,\n TerminalCreateOptions,\n TerminalOptions,\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 {\n getHttpStatus,\n getSuggestion,\n type OperationInterruptedContext\n} from '@repo/shared/errors';\nimport {\n type BackupRestoreTestFault,\n BackupService\n} from './backup/backup-service';\nimport { ContainerControlClient } from './container-control';\nimport {\n CurrentRuntimeIdentity,\n type RuntimeIdentity\n} from './current-runtime-identity';\nimport type { ErrorResponse } from './errors';\nimport {\n ContainerUnavailableError,\n ErrorCode,\n OperationInterruptedError,\n ProcessExitedBeforeReadyError,\n ProcessNotFoundError,\n ProcessReadyTimeoutError,\n SandboxError\n} from './errors';\nimport type { HTTPAuthInterceptorParams as GitAuthInterceptorParams } from './extensions';\nimport { SandboxExtension } from './extensions';\nimport { collectFile, streamFile } from './file-stream';\nimport { isPlatformTransientError } from './platform-errors';\nimport { isPreviewProxyRequest } from './preview/protocol';\nimport {\n type PreviewForwardingContainer,\n PreviewService\n} from './preview/service';\nimport {\n createSandboxProcessPromise,\n resolveStdinForRpc,\n type SandboxProcessDeps,\n SandboxProcessImpl,\n toRpcSandboxProcess\n} from './process';\nimport { createSandboxTerminal, proxyTerminal } from './pty';\nimport { CurrentSandboxLifetime } from './sandbox-lifetime';\nimport {\n SandboxSecurityError,\n sanitizeSandboxId,\n validatePort\n} from './security';\nimport { parseSSEStream } from './sse-parser';\nimport {\n BucketMountService,\n ContainerProxy,\n configureGitAuthInterceptor,\n type EgressContainerState,\n type MountOutboundHost\n} from './storage-mount';\nimport { NamedTunnelConfigResolver } from './tunnels/named-tunnel-config';\nimport {\n createTunnelsHandle,\n type TunnelExitHandler,\n type TunnelsHandle,\n type TunnelsHandler\n} from './tunnels/rpc-target';\nimport { SandboxControlCallbackImpl } from './tunnels/sandbox-control-callback';\nimport { SDK_VERSION } from './version';\n\nexport { ContainerProxy };\n\ntype ExecuteResponse = Awaited<\n ReturnType<ContainerControlClient['commands']['execute']>\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};\n\ntype CachedSandboxConfiguration = {\n sandboxName?: string;\n normalizeId?: boolean;\n sleepAfter?: string | number;\n keepAlive?: boolean;\n containerTimeouts?: NonNullable<SandboxOptions['containerTimeouts']>;\n};\n\ntype PreviewForwardingContainerState = DurableObjectState<{}> & {\n container?: PreviewForwardingContainer;\n};\n\ntype PreviewForwardingLifecycleState = {\n inflightRequests?: number;\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};\n\ntype SandboxProxyStub = ConfigurableSandboxStub & {\n fetch: (request: Request) => Promise<Response>;\n createSession: (opts?: SessionOptions) => Promise<ExecutionSession>;\n getSession: (sessionId: string) => Promise<ExecutionSession>;\n callExtension: (\n extensionName: string,\n method: string,\n args: unknown[]\n ) => Promise<unknown>;\n createTerminal: (options: TerminalCreateOptions) => Promise<void>;\n destroyTerminal: (id: string) => Promise<void>;\n /**\n * Unified exec primitive. Returns the resolved `SandboxProcess` (the\n * client-side `enhancedMethods.exec` wraps this in a\n * `SandboxProcessPromise` so the thenable convenience methods are\n * available at the call site).\n */\n exec: (\n command: string | string[],\n options?: SandboxExecOptions\n ) => Promise<SandboxProcess>;\n getProcess: (\n id: string,\n sessionId?: string\n ) => Promise<SandboxProcess | null>;\n run: (\n command: string,\n options?: ExecOptions & { sessionId?: string }\n ) => Promise<ExecResult>;\n runWithSessionToken: (\n command: string,\n sessionId: string,\n options?: ExecOptions\n ) => Promise<ExecResult>;\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';\nconst BACKUP_DEFAULT_COMPRESSION = 'lz4';\nconst BACKUP_DEFAULT_COMPRESS_THREADS = 8;\nconst BACKUP_MULTIPART_MIN_SIZE = 10 * 1024 * 1024;\nconst BACKUP_MULTIPART_TARGET_PARTS = 16;\nconst BACKUP_MULTIPART_MIN_PART_SIZE = 5 * 1024 * 1024;\nconst BACKUP_MULTIPART_MAX_PARTS = 64;\nconst BACKUP_DOWNLOAD_PARALLEL_PARTS = 8;\nconst BACKUP_DOWNLOAD_PARALLEL_MIN_SIZE = 10 * 1024 * 1024;\nconst BACKUP_DOWNLOAD_MAX_PARTS = 64;\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 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 );\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 };\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 return Promise.all(operations).then(() => undefined);\n}\n\nfunction createPlatformInterruptedError(\n error: unknown,\n operation: string\n): OperationInterruptedError | null {\n if (!isPlatformTransientError(error)) return null;\n\n const context: OperationInterruptedContext = {\n reason: 'runtime_replaced',\n operation,\n phase: 'durable_object_call',\n admitted: 'unknown',\n retryable: false\n };\n\n return new OperationInterruptedError({\n code: ErrorCode.OPERATION_INTERRUPTED,\n message: `Sandbox operation ${operation} was interrupted while the platform was updating the sandbox runtime`,\n context,\n httpStatus: getHttpStatus(ErrorCode.OPERATION_INTERRUPTED),\n suggestion: getSuggestion(\n ErrorCode.OPERATION_INTERRUPTED,\n context as unknown as Record<string, unknown>\n ),\n timestamp: new Date().toISOString()\n });\n}\n\nfunction translatePlatformInterruption(\n error: unknown,\n operation: string\n): never {\n throw createPlatformInterruptedError(error, operation) ?? error;\n}\n\nfunction withSandboxOperationContext<TArgs extends unknown[], TResult>(\n operation: string,\n fn: (...args: TArgs) => TResult\n): (...args: TArgs) => TResult {\n return (...args: TArgs): TResult => {\n try {\n const result = fn(...args);\n if (\n result != null &&\n typeof (result as { then?: unknown }).then === 'function'\n ) {\n const caught = (result as unknown as Promise<unknown>).catch(\n (error: unknown) => translatePlatformInterruption(error, operation)\n );\n // Preserve any own properties the source thenable attached to itself\n // (e.g. `SandboxProcessPromise.output` / `.text` / `.json` / `.kill`).\n // `Promise.prototype.catch` returns a bare promise, so without this\n // copy those thenable-augmenting methods disappear.\n Object.assign(caught, result);\n return caught as TResult;\n }\n return result;\n } catch (error) {\n translatePlatformInterruption(error, operation);\n }\n };\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 & SandboxProxyStub;\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 enhancedMethods = {\n fetch: (request: Request) => stub.fetch(request),\n\n // Unified exec (mirrors `ctx.container.exec()` contract). Wrapped in\n // `createSandboxProcessPromise` so callers get the Bun.spawn-style\n // thenable that exposes `.output()` / `.text()` / `.json()` / `.kill()`\n // directly on the returned promise.\n exec: (command: string | string[], execOptions?: SandboxExecOptions) =>\n createSandboxProcessPromise(stub.exec(command, execOptions)),\n run: (command: string, runOptions?: ExecOptions & { sessionId?: string }) =>\n stub.run(command, runOptions),\n getProcess: (id: string, options?: ProcessQueryOptions) =>\n options?.sessionId === undefined\n ? stub.getProcess(id)\n : stub.getProcess(id, { sessionId: options.sessionId }),\n listProcesses: (options?: ProcessQueryOptions) =>\n options?.sessionId === undefined\n ? stub.listProcesses()\n : stub.listProcesses({ sessionId: options.sessionId }),\n writeFile: (\n path: string,\n content: string | ReadableStream<Uint8Array>,\n fileOptions: { encoding?: string; sessionId?: string } = {}\n ) =>\n stub.writeFile(path, content, {\n ...fileOptions,\n ...(fileOptions.sessionId !== undefined && {\n sessionId: fileOptions.sessionId\n })\n }),\n readFile: (\n path: string,\n fileOptions:\n | { encoding: 'none'; sessionId?: string }\n | { encoding?: Exclude<FileEncoding, 'none'>; sessionId?: string } = {}\n ) => {\n const options = {\n ...fileOptions,\n ...(fileOptions.sessionId !== undefined && {\n sessionId: fileOptions.sessionId\n })\n };\n\n if (options.encoding === 'none') {\n return stub.readFile(path, options);\n }\n\n return stub.readFile(path, options);\n },\n readFileStream: (path: string, fileOptions: { sessionId?: string } = {}) =>\n stub.readFileStream(path, {\n ...fileOptions,\n ...(fileOptions.sessionId !== undefined && {\n sessionId: fileOptions.sessionId\n })\n }),\n mkdir: (\n path: string,\n mkdirOptions: { recursive?: boolean; sessionId?: string } = {}\n ) =>\n stub.mkdir(path, {\n ...mkdirOptions,\n ...(mkdirOptions.sessionId !== undefined && {\n sessionId: mkdirOptions.sessionId\n })\n }),\n deleteFile: (path: string, options: { sessionId?: string } = {}) =>\n options.sessionId === undefined\n ? stub.deleteFile(path)\n : stub.deleteFile(path, { sessionId: options.sessionId }),\n renameFile: (\n oldPath: string,\n newPath: string,\n options: { sessionId?: string } = {}\n ) =>\n options.sessionId === undefined\n ? stub.renameFile(oldPath, newPath)\n : stub.renameFile(oldPath, newPath, { sessionId: options.sessionId }),\n moveFile: (\n sourcePath: string,\n destinationPath: string,\n options: { sessionId?: string } = {}\n ) =>\n options.sessionId === undefined\n ? stub.moveFile(sourcePath, destinationPath)\n : stub.moveFile(sourcePath, destinationPath, {\n sessionId: options.sessionId\n }),\n listFiles: (path: string, listOptions?: ListFilesOptions) =>\n stub.listFiles(path, {\n ...listOptions,\n sessionId: listOptions?.sessionId\n }),\n exists: (path: string, options: { sessionId?: string } = {}) =>\n options.sessionId === undefined\n ? stub.exists(path)\n : stub.exists(path, { sessionId: options.sessionId }),\n createSession: async (opts?: SessionOptions): Promise<ExecutionSession> => {\n const rpcSession = await stub.createSession(opts);\n return rpcSession as ExecutionSession;\n },\n getSession: async (sessionId: string): Promise<ExecutionSession> => {\n const rpcSession = await stub.getSession(sessionId);\n return rpcSession as ExecutionSession;\n },\n watch: (path: string, options: WatchOptions = {}) =>\n stub.watch(path, {\n ...options,\n sessionId: options.sessionId\n }),\n checkChanges: (path: string, options: CheckChangesOptions = {}) =>\n stub.checkChanges(path, {\n ...options,\n sessionId: options.sessionId\n }),\n terminal: (opts?: TerminalOptions) => createSandboxTerminal(stub, opts),\n wsConnect: connect(stub),\n tunnels: new Proxy({} as TunnelsHandler, {\n get: (_, method) => {\n if (typeof method !== 'string' || method === 'then') return undefined;\n return withSandboxOperationContext(\n `sandbox.tunnels.${method}`,\n (...args: unknown[]) => stub.callTunnels(method, args)\n );\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 const method = enhancedMethods[prop as keyof typeof enhancedMethods];\n if (typeof method === 'function') {\n return withSandboxOperationContext(\n `sandbox.${prop}`,\n method as (...args: unknown[]) => unknown\n );\n }\n return method;\n }\n if (typeof prop !== 'string' || prop === 'then') {\n // @ts-expect-error - RPC stub methods are Proxy-trapped, not visible to TypeScript\n return target[prop];\n }\n // @ts-expect-error - RPC stub methods are Proxy-trapped, not visible to TypeScript\n const value = target[prop];\n // Plain data properties (e.g. sleepAfter) pass through unchanged.\n if (value !== undefined && typeof value !== 'function') {\n return value;\n }\n // Methods and extension namespaces become a callable proxy: invoking it\n // forwards to the stub method (sandbox.method(...)), while a nested\n // access dispatches sandbox.<ext>.<method>(...) through callExtension.\n return new Proxy(\n (...args: unknown[]) => {\n // @ts-expect-error - RPC stub methods are Proxy-trapped, not visible to TypeScript\n return target[prop](...args);\n },\n {\n get: (_, method) => {\n if (typeof method !== 'string' || method === 'then') {\n return undefined;\n }\n return (...args: unknown[]) =>\n stub.callExtension(prop, method, args);\n }\n }\n );\n }\n }) as T;\n}\n\nfunction getConcreteExtensionMethod(\n extension: SandboxExtension,\n method: string\n): ((...args: unknown[]) => unknown) | undefined {\n const ownValue = (extension as unknown as Record<string, unknown>)[method];\n if (Object.hasOwn(extension, method) && typeof ownValue === 'function') {\n return ownValue as (...args: unknown[]) => unknown;\n }\n\n let prototype = Object.getPrototypeOf(extension) as object | null;\n while (\n prototype &&\n prototype !== SandboxExtension.prototype &&\n prototype !== Object.prototype\n ) {\n const descriptor = Object.getOwnPropertyDescriptor(prototype, method);\n if (typeof descriptor?.value === 'function') {\n return descriptor.value as (...args: unknown[]) => unknown;\n }\n prototype = Object.getPrototypeOf(prototype) as object | null;\n }\n\n return undefined;\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\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: ContainerControlClient;\n\n private sandboxName: string | null = null;\n // Tunnels subsystem handle. Lazily constructed on first access via the\n // `tunnels` getter or when sandbox lifecycle hooks need tunnel cleanup.\n // The public `tunnelsHandler` stays narrow while `tunnelServiceHandle`\n // carries internal runtime/destroy callbacks.\n private tunnelServiceHandle: TunnelsHandle | null = null;\n private tunnelsHandler: TunnelsHandler | null = null;\n private tunnelExitHandler: TunnelExitHandler | null = null;\n // capnweb localMain exposed to the container side of the RPC\n // session. Constructed once in the constructor (the lazy accessor\n // keeps it pointing at the current `tunnelExitHandler`), so the\n // container can call back into the DO without us re-binding the session.\n private readonly controlCallback: SandboxControlCallbackImpl;\n private normalizeId: boolean = false;\n envVars: Record<string, string> = {};\n private logger: ReturnType<typeof createLogger>;\n private keepAliveEnabled: boolean = false;\n private bucketMounts: BucketMountService;\n private currentRuntime: CurrentRuntimeIdentity;\n private currentLifetime: CurrentSandboxLifetime;\n private backupService: BackupService;\n private previewService: PreviewService;\n\n private r2AccessKeyId: string | null = null;\n private r2SecretAccessKey: string | null = null;\n private namedTunnelConfigResolver: NamedTunnelConfigResolver;\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 * Dispatch method for tunnel operations.\n * Called by the client-side proxy created in getSandbox() to provide\n * the `sandbox.tunnels` API without relying on RPC pipelining through\n * property getters, which vite-plugin does not currently support. This\n * local method dispatch uses standard function application, so new\n * Workers RPC pipelining traits on `TunnelsRpcTarget` also need an\n * explicit path here before the proxy can expose them.\n */\n async callTunnels(method: string, args: unknown[]): Promise<unknown> {\n if (!['get', 'list', 'destroy'].includes(method)) {\n throw new Error(`Unknown tunnels method: ${method}`);\n }\n const client = this.tunnels;\n const fn = client[method as keyof typeof client];\n if (typeof fn !== 'function') {\n throw new Error(`sandbox.tunnels missing method: ${method}`);\n }\n return (fn as (...a: unknown[]) => unknown).apply(client, args);\n }\n\n async callExtension(\n extensionName: string,\n method: string,\n args: unknown[]\n ): Promise<unknown> {\n if (method === 'constructor' || method === 'then') {\n throw new Error(`Unknown extension method: ${extensionName}.${method}`);\n }\n const extension = (this as unknown as Record<string, unknown>)[\n extensionName\n ];\n if (!(extension instanceof SandboxExtension)) {\n throw new Error(`Unknown sandbox extension: ${extensionName}`);\n }\n const fn = getConcreteExtensionMethod(extension, method);\n if (!fn) {\n throw new Error(`Unknown extension method: ${extensionName}.${method}`);\n }\n return fn.apply(extension, args);\n }\n\n /**\n * Compute the control-channel upgrade retry budget from current container\n * 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 * The 120s floor preserves the 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 the single control-plane client used for all SDK operations.\n */\n private createClient(): ContainerControlClient {\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 ContainerControlClient({\n stub: this,\n port: 3000,\n logger: this.logger,\n retryTimeoutMs: this.computeRetryTimeoutMs(),\n // localMain exposes the DO-side control callback (tunnel-exit\n // notifications, etc.) to the container side of the session.\n localMain: this.controlCallback,\n // The control channel multiplexes all work over a single capnweb\n // WebSocket, so we can't bracket per-request — and a method that\n // returns a ReadableStream resolves its promise long before the\n // stream is actually drained. Instead, ContainerControlClient polls\n // capnweb's session stats and reports busy/idle *transitions* of the\n // whole session. We treat one transition as equivalent to one\n // in-flight request: increment on busy, decrement on idle. See the\n // file-level comment in container-control/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 the\n // top of containerFetch(): push the sleepAfter deadline forward.\n this.renewActivityTimeout();\n },\n onSessionBusy: () => {\n // Idle → busy: a new RPC call started or a stream return is now\n // in flight. Mark the DO busy so isActivityExpired() returns false\n // until the session goes idle again.\n self.inflightRequests = (self.inflightRequests ?? 0) + 1;\n },\n onSessionIdle: () => {\n // Busy → idle: all RPC promises have settled and all stream exports\n // have been released. Equivalent to containerFetch's finally block —\n // decrement and restart the inactivity window from now.\n self.inflightRequests = Math.max(0, (self.inflightRequests ?? 0) - 1);\n if (self.inflightRequests === 0) {\n this.renewActivityTimeout();\n }\n }\n });\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 this.currentRuntime = new CurrentRuntimeIdentity(\n this.ctx.storage,\n () => this.getState(),\n () => this.ctx.container?.running === true\n );\n this.currentLifetime = new CurrentSandboxLifetime(this.ctx.storage);\n this.namedTunnelConfigResolver = new NamedTunnelConfigResolver({\n getEnv: () => this.env\n });\n this.backupService = new BackupService({\n ctx: this.ctx,\n getEnv: () => this.env,\n logger: this.logger,\n getClient: () => this.client,\n execWithSession: (command, sessionId, options) =>\n this.execWithSession(command, sessionId, options),\n currentRuntime: this.currentRuntime,\n currentLifetime: this.currentLifetime\n });\n\n this.r2AccessKeyId = getEnvString(envObj, 'R2_ACCESS_KEY_ID') ?? null;\n this.r2SecretAccessKey =\n getEnvString(envObj, 'R2_SECRET_ACCESS_KEY') ?? null;\n\n // Construct the control callback BEFORE the client — RPC clients\n // capture it as `localMain` on the capnweb session, and the\n // session is created eagerly in the connection's constructor.\n this.controlCallback = new SandboxControlCallbackImpl(\n () => this.tunnelExitHandler,\n this.logger\n );\n\n this.client = this.createClient();\n this.bucketMounts = new BucketMountService({\n getEnv: () => this.env,\n getEnvVars: () => this.envVars,\n getClient: () => this.client,\n logger: this.logger,\n currentRuntime: this.currentRuntime,\n currentLifetime: this.currentLifetime,\n getR2AccessKeyID: () => this.r2AccessKeyId,\n getR2SecretAccessKey: () => this.r2SecretAccessKey,\n execInternal: (command) => this.execInternal(command),\n getOutboundHost: () => this.getMountOutboundHost()\n });\n this.previewService = new PreviewService({\n storage: this.ctx.storage,\n logger: this.logger,\n currentRuntime: this.currentRuntime,\n getContainerState: () => this.getState(),\n getForwardingContainer: () => this.getPreviewForwardingContainer(),\n ensureRuntimeActiveForPreview: () => this.ensureRuntimeActiveForPreview(),\n getSandboxName: () => this.sandboxName,\n getNormalizeID: () => this.normalizeId,\n beginForward: () => this.beginPreviewForward(),\n renewActivity: () => this.renewActivityTimeout()\n });\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.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 control-channel 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 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\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\n async setContainerTimeouts(\n timeouts: NonNullable<SandboxOptions['containerTimeouts']>\n ): Promise<void> {\n const validated = { ...this.containerTimeouts };\n\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 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 this.client.setRetryTimeoutMs(this.computeRetryTimeoutMs());\n this.logger.debug('Container timeouts updated', this.containerTimeouts);\n }\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 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,\n 300_000\n ),\n portReadyTimeoutMS: parseAndValidate(\n getEnvString(env, 'SANDBOX_PORT_TIMEOUT_MS'),\n 'portReadyTimeoutMS',\n 10_000,\n 600_000\n ),\n waitIntervalMS: parseAndValidate(\n getEnvString(env, 'SANDBOX_POLL_INTERVAL_MS'),\n 'waitIntervalMS',\n 100,\n 5_000\n )\n };\n }\n\n /**\n * Mount an S3-compatible bucket as a local directory.\n */\n async mountBucket(\n bucket: string,\n mountPath: string,\n options: MountBucketOptions\n ): Promise<void> {\n return this.bucketMounts.mountBucket(bucket, mountPath, options);\n }\n\n /**\n * Manually unmount a bucket filesystem.\n */\n async unmountBucket(mountPath: string): Promise<void> {\n return this.bucketMounts.unmountBucket(mountPath);\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 // Preview URL auth and activation are cleared before await-heavy\n // teardown work. Concurrent preview traffic should observe missing\n // auth or runtime state and fail from DO-owned state without reaching\n // the container.\n await this.previewService.clearPreviewState();\n await this.currentLifetime.rotate();\n await this.currentRuntime.clear();\n\n // Unmount all mounted buckets and cleanup. This runs before disconnecting\n // the control client because FUSE teardown uses execInternal.\n ({ mountsProcessed, mountFailures } =\n await this.bucketMounts.cleanupForDestroy());\n if (mountFailures > 0) {\n throw new Error(\n `Failed to clean up ${mountFailures} bucket mount${mountFailures === 1 ? '' : 's'} during destroy()`\n );\n }\n\n // Tear down every tunnel this sandbox created — stops the\n // container-side cloudflared processes and removes the Cloudflare\n // tunnel + DNS resources for named tunnels. Runs before disconnect\n // because destroyAll needs the container RPC. Best-effort per port;\n // a failure on one doesn't block the rest of teardown.\n //\n // Lazily build the handler so destroyAll runs even on a sandbox\n // that never accessed `tunnels` during its lifetime — storage may\n // hold records from a prior lifetime under the same DO id.\n try {\n this.ensureTunnelsBuilt();\n await this.tunnelServiceHandle?.destroyAll();\n } catch (error) {\n this.logger.warn('Failed to tear down tunnels during destroy()', {\n error: error instanceof Error ? error.message : String(error)\n });\n }\n await this.tunnelServiceHandle?.clearDurableStateAfterDestroy();\n\n // Disconnect the control client after all cleanup commands complete.\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 await this.currentRuntime.markStarted();\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 // Reconcile tunnel storage with the fresh container inside\n // onStart's blockConcurrencyWhile gate so any get() that arrived\n // during startup sees tunnel state for the current runtime.\n try {\n this.ensureTunnelsBuilt();\n await this.tunnelServiceHandle?.onRuntimeStart();\n } catch (error) {\n this.logger.error(\n 'Failed to reconcile tunnel storage after container start',\n error instanceof Error ? error : new Error(String(error))\n );\n }\n }\n\n override async stop(\n signal?: Parameters<Container<Env>['stop']>[0]\n ): Promise<void> {\n await this.currentRuntime.clear();\n await this.previewService.clearActivePreviewPorts();\n await super.stop(signal);\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 await this.currentRuntime.clear();\n await this.previewService.clearActivePreviewPorts();\n\n try {\n this.ensureTunnelsBuilt();\n await this.tunnelServiceHandle?.onRuntimeStop();\n } catch (error) {\n this.logger.error(\n 'Failed to reconcile tunnel storage after container stop',\n error instanceof Error ? error : new Error(String(error))\n );\n }\n\n // Stop local sync managers and clear runtime-scoped mount state before\n // closing the control client; FUSE cleanup may need container RPC.\n await this.bucketMounts.cleanupForStop();\n\n // Disconnect the active client so open sockets do not hold the DO alive.\n this.client.disconnect();\n\n // Port tokens are durable authorization and survive container restarts;\n // runtime-scoped preview activation is cleared separately above.\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.CONTAINER_UNAVAILABLE,\n message:\n 'Container is currently provisioning. This can take several minutes on first deployment.',\n context: { reason: 'container_starting', retryable: true },\n httpStatus: 503,\n timestamp: new Date().toISOString(),\n suggestion:\n 'The container is still being provisioned. Retry the operation in a moment.'\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.CONTAINER_UNAVAILABLE,\n message: 'Container is starting. Please retry in a moment.',\n context: { reason: 'container_starting', retryable: true },\n httpStatus: 503,\n timestamp: new Date().toISOString(),\n suggestion:\n 'The container is not ready yet. Retry the operation in a moment.'\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.CONTAINER_UNAVAILABLE,\n message: 'Container is starting. Please retry in a moment.',\n context: { reason: 'container_starting', retryable: true },\n httpStatus: 503,\n timestamp: new Date().toISOString(),\n suggestion:\n 'The container is not ready yet. Retry the operation in a moment.'\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 private getPreviewForwardingContainer(): PreviewForwardingContainerState['container'] {\n return (this.ctx as PreviewForwardingContainerState).container;\n }\n\n private beginPreviewForward(): () => void {\n const lifecycle = this as unknown as PreviewForwardingLifecycleState;\n lifecycle.inflightRequests = (lifecycle.inflightRequests ?? 0) + 1;\n this.renewActivityTimeout();\n\n let settled = false;\n return () => {\n if (settled) {\n return;\n }\n settled = true;\n lifecycle.inflightRequests = Math.max(\n 0,\n (lifecycle.inflightRequests ?? 0) - 1\n );\n if (lifecycle.inflightRequests === 0) {\n this.renewActivityTimeout();\n }\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 if (isPreviewProxyRequest(request)) {\n return await this.previewService.proxyPreviewRequest(request);\n }\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 terminal(_options?: TerminalOptions): SandboxTerminal {\n throw new Error(\n 'terminal must be called on the stub returned by getSandbox()'\n );\n }\n\n async createTerminal(options: TerminalCreateOptions): Promise<void> {\n await this.client.terminals.createTerminal(options);\n }\n\n async destroyTerminal(id: string): Promise<void> {\n await this.client.terminals.destroyTerminal(id);\n }\n\n private determinePort(url: URL): number {\n // Direct DO fetch compatibility path used by switchPort()/wsConnect().\n // Public preview URL traffic enters through proxyPreviewRequest() instead.\n const proxyMatch = url.pathname.match(/^\\/proxy\\/(\\d+)/);\n if (proxyMatch) {\n return parseInt(proxyMatch[1], 10);\n }\n\n // Direct fetch compatibility defaults to the container server port.\n // SDK control operations use ContainerControlClient over /rpc instead.\n return 3000;\n }\n\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 private validateExplicitSessionId(sessionId: string): void {\n if (sessionId.trim().length === 0) {\n throw new Error('sessionId must not be empty or whitespace');\n }\n }\n\n /**\n * Validate an optional explicit session id without creating a session.\n *\n * Top-level process reads are sandbox-scoped, so they only carry a public\n * session annotation when the caller provides an explicit session id. The\n * resolved value is only used to populate `Process.sessionId` on the returned\n * object — it is never sent to the container API.\n */\n private validateOptionalSessionId(\n explicitSessionId?: string\n ): string | undefined {\n if (explicitSessionId !== undefined) {\n this.validateExplicitSessionId(explicitSessionId);\n }\n return explicitSessionId;\n }\n\n private resolveExecutionEnv(\n sessionId: string | undefined,\n env?: Record<string, string | undefined>\n ): Record<string, string | undefined> | undefined {\n if (sessionId === undefined) {\n const mergedEnv = filterEnvVars({ ...this.envVars, ...(env ?? {}) });\n return Object.keys(mergedEnv).length > 0 ? mergedEnv : undefined;\n }\n\n if (env === undefined) {\n return undefined;\n }\n\n const filteredEnv = filterEnvVars(env);\n return Object.keys(filteredEnv).length > 0 ? filteredEnv : undefined;\n }\n\n private buildExecutionRequestOptions(\n sessionId: string | undefined,\n options?: {\n timeout?: number;\n env?: Record<string, string | undefined>;\n cwd?: string;\n origin?: 'user' | 'internal';\n }\n ): CommandExecuteOptions | undefined {\n const env = this.resolveExecutionEnv(sessionId, options?.env);\n\n if (\n options?.timeout === undefined &&\n env === undefined &&\n options?.cwd === undefined &&\n options?.origin === undefined\n ) {\n return undefined;\n }\n\n return {\n ...(options?.timeout !== undefined && { timeoutMs: options.timeout }),\n ...(env !== undefined && { env }),\n ...(options?.cwd !== undefined && { cwd: options.cwd }),\n ...(options?.origin !== undefined && { origin: options.origin })\n };\n }\n\n // -------------------------------------------------------------------------\n // Unified exec surface (mirrors `ctx.container.exec()` from workers-types).\n // -------------------------------------------------------------------------\n\n exec(\n command: string | string[],\n options?: SandboxExecOptions\n ): SandboxProcessPromise {\n return createSandboxProcessPromise(\n this.spawnSandboxProcess(command, options)\n );\n }\n\n /**\n * Buffered, session-state-preserving execution helper. Uses the foreground\n * session command path, preserving shell state such as cwd, aliases,\n * functions, and exported variables.\n */\n async run(\n command: string,\n options?: ExecOptions & { sessionId?: string }\n ): Promise<ExecResult> {\n return this.executeCommand(command, options?.sessionId, options);\n }\n\n async runWithSessionToken(\n command: string,\n sessionId: string,\n options?: ExecOptions\n ): Promise<ExecResult> {\n this.validateExplicitSessionId(sessionId);\n return this.executeCommand(command, sessionId, options);\n }\n\n /**\n * Internal: start a process via `client.processes.startProcess` and\n * construct a `SandboxProcess` handle that demultiplexes the log stream\n * into `stdout` / `stderr` and resolves `exitCode` on the `exit` event.\n */\n private async spawnSandboxProcess(\n command: string | string[],\n options?: SandboxExecOptions\n ): Promise<SandboxProcess> {\n if (options?.sessionId !== undefined) {\n this.validateExplicitSessionId(options.sessionId);\n }\n if (options?.signal?.aborted) {\n throw new Error('Operation was aborted');\n }\n\n // Wire-level command sent to the container's session shell. Strings pass\n // through verbatim (the container already shell-executes them); arrays\n // get shell-quoted so argv-form input survives the shell hop. The two\n // shapes produce the same command on the wire as `ctx.container.exec()`\n // would interpret — future iteration can move array-form straight to\n // `ctx.container.exec(argv, ...)` without going through the shell.\n const display = Array.isArray(command)\n ? command.map((arg) => shellEscape(arg)).join(' ')\n : command;\n\n const session = this.validateOptionalSessionId(options?.sessionId);\n const processSession = session;\n\n const executionOptions = this.buildExecutionRequestOptions(session, {\n timeout: options?.timeout,\n env: options?.env,\n cwd: options?.cwd,\n origin: options?.origin\n });\n\n // Resolve the caller-supplied `stdin` (`\"pipe\"` | `ReadableStream` |\n // `string` | undefined) into the pair of streams the wire & SDK need:\n // - `stdinSource`: a `ReadableStream<Uint8Array>` passed across RPC\n // to the container, which pumps it into the per-command FIFO.\n // - `stdinWriter`: the `WritableStream<Uint8Array>` exposed to the\n // caller as `proc.stdin` when they asked for `\"pipe\"`.\n const { stdinSource, stdinWriter } = resolveStdinForRpc(options?.stdin);\n const stderrMode = options?.stderr ?? 'pipe';\n const stdoutMode = options?.stdout ?? 'pipe';\n\n // Normalize to `undefined` when no options were provided. Keeps wire\n // compatibility with the legacy `execWithSession` shape and avoids\n // sending a noisy empty object across RPC.\n const hasProcessOption =\n session !== undefined ||\n options?.processId !== undefined ||\n options?.autoCleanup !== undefined ||\n options?.stdout !== undefined ||\n options?.stderr !== undefined;\n const requestOptions =\n executionOptions === undefined && !hasProcessOption\n ? undefined\n : {\n ...executionOptions,\n ...(session !== undefined && { sessionId: session }),\n ...(options?.processId !== undefined && {\n processId: options.processId\n }),\n ...(options?.autoCleanup !== undefined && {\n autoCleanup: options.autoCleanup\n }),\n ...(options?.stdout !== undefined && { stdout: stdoutMode }),\n ...(options?.stderr !== undefined && { stderr: stderrMode })\n };\n\n const response =\n requestOptions !== undefined || stdinSource !== undefined\n ? await this.client.processes.startProcess(\n display,\n requestOptions,\n stdinSource\n )\n : await this.client.processes.startProcess(display);\n\n const proc = new SandboxProcessImpl(\n {\n id: response.processId,\n pid: response.pid ?? -1,\n command: response.command ?? display,\n sessionId: processSession,\n startTime: new Date(),\n status: 'running' as ProcessStatus,\n ownership: 'owner',\n stdout: stdoutMode,\n stderr: stderrMode,\n stdin: stdinWriter\n },\n this.buildSandboxProcessDeps()\n );\n // Wrap in a plain object literal that workerd can serialize across\n // the DO RPC boundary. See `toRpcSandboxProcess` for the rationale.\n const procDto: SandboxProcess = toRpcSandboxProcess(proc);\n\n // Wire AbortSignal → kill (best-effort; matches `ExecProcess` lifetime).\n if (options?.signal) {\n const onAbort = () => proc.kill(15 /* SIGTERM */);\n if (options.signal.aborted) onAbort();\n else options.signal.addEventListener('abort', onAbort, { once: true });\n }\n\n // Wall-clock timeout → SIGTERM. Container-side already has its own\n // timeoutMs honored via executionOptions; this is the SDK-side belt to\n // make `proc.exitCode` settle even if the container is wedged.\n if (options?.timeout && Number.isFinite(options.timeout)) {\n const handle = setTimeout(() => proc.kill(15), options.timeout);\n void proc.exitCode.finally(() => clearTimeout(handle));\n }\n\n return procDto;\n }\n\n private buildSandboxProcessDeps(): SandboxProcessDeps {\n // Keep these tied to the live `this` so re-entrant RPCs use the same\n // capnweb session as the spawn that produced the handle.\n return {\n openLogStream: (id) => this.client.processes.streamProcessLogs(id),\n readLogs: async (id) => {\n const r = await this.client.processes.getProcessLogs(id);\n return { stdout: r.stdout, stderr: r.stderr };\n },\n fetchStatus: async (id) => {\n try {\n const r = await this.client.processes.getProcess(id);\n return r.process?.status ?? 'error';\n } catch (error) {\n if (error instanceof ProcessNotFoundError) return 'error';\n throw error;\n }\n },\n killProcess: async (id, signal) => {\n await this.client.processes.killProcess(id, signal);\n },\n waitForPort: (id, command, port, opts) =>\n this.waitForPortReady(id, command, port, opts),\n waitForLogPattern: (id, command, pattern, timeout) =>\n this.waitForLogPattern(id, command, pattern, timeout),\n waitForProcessExit: (id, command, timeout) =>\n this.waitForProcessExit(id, command, timeout)\n };\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 return this.executeCommand(command, undefined, {\n origin: 'internal'\n });\n }\n\n private async execWithSession(\n command: string,\n sessionId: string,\n options?: ExecOptions\n ): Promise<ExecResult> {\n return this.executeCommand(command, sessionId, options);\n }\n\n /**\n * Internal command execution implementation used by public exec() and\n * explicit session wrappers.\n */\n private async executeCommand(\n command: string,\n sessionId: string | undefined,\n options?: ExecOptions\n ): Promise<ExecResult> {\n const startTime = Date.now();\n let execOutcome: { exitCode: number; success: boolean } | undefined;\n let execError: Error | undefined;\n\n try {\n const executionOptions = this.buildExecutionRequestOptions(\n sessionId,\n options\n );\n const commandOptions: CommandExecuteOptions | undefined =\n sessionId === undefined\n ? executionOptions\n : { ...(executionOptions ?? {}), sessionId };\n\n const response = commandOptions\n ? await this.client.commands.execute(command, commandOptions)\n : await this.client.commands.execute(command);\n\n const duration = Date.now() - startTime;\n const result = this.mapExecuteResponseToExecResult(\n response,\n duration,\n sessionId\n );\n\n execOutcome = { exitCode: result.exitCode, success: result.success };\n\n return result;\n } catch (error) {\n execError = error instanceof Error ? error : new Error(String(error));\n throw error;\n } finally {\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 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 * 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 =\n await this.client.processes.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.client.processes.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.client.processes.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\n /** Re-attach to an existing process by id. */\n async getProcess(\n id: string,\n options?: ProcessQueryOptions\n ): Promise<SandboxProcess | null> {\n const session = this.validateOptionalSessionId(options?.sessionId);\n try {\n const response = await this.client.processes.getProcess(id);\n if (!response.process) return null;\n const data = response.process;\n return toRpcSandboxProcess(\n new SandboxProcessImpl(\n {\n id: data.id,\n pid: data.pid ?? -1,\n command: data.command,\n sessionId: session,\n startTime:\n typeof data.startTime === 'string'\n ? new Date(data.startTime)\n : (data.startTime ?? new Date()),\n status: data.status,\n ownership: 'attached',\n stdout: data.stdout ?? 'pipe',\n stderr: data.stderr ?? 'pipe',\n stdin: null\n },\n this.buildSandboxProcessDeps()\n )\n );\n } catch (error) {\n if (error instanceof ProcessNotFoundError) return null;\n throw error;\n }\n }\n\n /** Return lightweight process snapshots. */\n async listProcesses(\n options?: ProcessQueryOptions\n ): Promise<SandboxProcess[]> {\n const session = this.validateOptionalSessionId(options?.sessionId);\n const response = await this.client.processes.listProcesses();\n const deps = this.buildSandboxProcessDeps();\n return response.processes.map((data) =>\n toRpcSandboxProcess(\n new SandboxProcessImpl(\n {\n id: data.id,\n pid: data.pid ?? -1,\n command: data.command,\n sessionId: session,\n startTime:\n typeof data.startTime === 'string'\n ? new Date(data.startTime)\n : (data.startTime ?? new Date()),\n status: data.status,\n ownership: 'attached',\n stdout: data.stdout ?? 'ignore',\n stderr: data.stderr ?? 'ignore',\n stdin: null\n },\n deps\n )\n )\n );\n }\n\n async killAllProcesses(): Promise<number> {\n const response = await this.client.processes.killAllProcesses();\n return response.cleanedCount;\n }\n\n async cleanupCompletedProcesses(): Promise<number> {\n // Not yet implemented — requires container endpoint.\n return 0;\n }\n\n async getProcessLogs(\n id: 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 /**\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 mkdir(\n path: string,\n options: { recursive?: boolean; sessionId?: string } = {}\n ) {\n const session = this.validateOptionalSessionId(options.sessionId);\n return this.client.files.mkdir(path, {\n ...(session !== undefined && { sessionId: 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 = this.validateOptionalSessionId(options.sessionId);\n\n if (content instanceof ReadableStream) {\n return this.client.files.writeFileStream(path, content, {\n ...(session !== undefined && { sessionId: session })\n });\n }\n\n return this.client.files.writeFile(path, content, {\n ...(session !== undefined && { sessionId: session }),\n encoding: options.encoding\n });\n }\n\n async deleteFile(path: string, options: { sessionId?: string } = {}) {\n const session = this.validateOptionalSessionId(options.sessionId);\n return session === undefined\n ? this.client.files.deleteFile(path)\n : this.client.files.deleteFile(path, { sessionId: session });\n }\n\n async renameFile(\n oldPath: string,\n newPath: string,\n options: { sessionId?: string } = {}\n ) {\n const session = this.validateOptionalSessionId(options.sessionId);\n return session === undefined\n ? this.client.files.renameFile(oldPath, newPath)\n : this.client.files.renameFile(oldPath, newPath, { sessionId: session });\n }\n\n async moveFile(\n sourcePath: string,\n destinationPath: string,\n options: { sessionId?: string } = {}\n ) {\n const session = this.validateOptionalSessionId(options.sessionId);\n return session === undefined\n ? this.client.files.moveFile(sourcePath, destinationPath)\n : this.client.files.moveFile(sourcePath, destinationPath, {\n sessionId: session\n });\n }\n\n /**\n * Read a file from the sandbox.\n *\n * @param encoding - How to encode the returned content:\n * - `undefined` (default): auto-detect from MIME type (text → UTF-8 string, binary → base64 string)\n * - `'utf-8'` / `'utf8'`: always return as UTF-8 string\n * - `'base64'`: always return as base64-encoded string\n * - `'none'`: return a result whose `content` is a raw binary `ReadableStream<Uint8Array>`\n * with no encoding overhead.\n */\n async readFile(\n path: string,\n options: { encoding: 'none'; sessionId?: string }\n ): Promise<ReadFileStreamResult>;\n async readFile(\n path: string,\n options?: { encoding?: Exclude<FileEncoding, 'none'>; sessionId?: string }\n ): Promise<ReadFileResult>;\n async readFile(\n path: string,\n options: { encoding?: FileEncoding; sessionId?: string } = {}\n ): Promise<ReadFileResult | ReadFileStreamResult> {\n const session = this.validateOptionalSessionId(options.sessionId);\n if (options.encoding === 'none') {\n return this.client.files.readFile(path, {\n ...(session !== undefined && { sessionId: session }),\n encoding: 'none'\n });\n }\n return this.client.files.readFile(path, {\n ...(session !== undefined && { sessionId: 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 = this.validateOptionalSessionId(options.sessionId);\n return this.client.files.readFileStream(path, {\n ...(session !== undefined && { sessionId: session })\n });\n }\n\n async listFiles(path: string, options?: ListFilesOptions) {\n const session = this.validateOptionalSessionId(options?.sessionId);\n return this.client.files.listFiles(path, {\n ...options,\n ...(session !== undefined && { sessionId: session })\n });\n }\n\n async exists(path: string, options: { sessionId?: string } = {}) {\n const session = this.validateOptionalSessionId(options.sessionId);\n return session === undefined\n ? this.client.files.exists(path)\n : this.client.files.exists(path, { sessionId: session });\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 = this.validateOptionalSessionId(options.sessionId);\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 = this.validateOptionalSessionId(options.sessionId);\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 private async ensureRuntimeActiveForPreview(): Promise<RuntimeIdentity> {\n await this.startAndWaitForPorts({\n ports: this.defaultPort,\n cancellationOptions: {\n instanceGetTimeoutMS: this.containerTimeouts.instanceGetTimeoutMS,\n portReadyTimeoutMS: this.containerTimeouts.portReadyTimeoutMS,\n waitInterval: this.containerTimeouts.waitIntervalMS\n }\n });\n\n const runtime = await this.currentRuntime.get();\n return runtime ?? (await this.currentRuntime.markStarted());\n }\n\n /**\n * Expose a port and get a preview URL for accessing services running in the sandbox\n *\n * Preview URL authorization survives transient container restarts, but\n * forwarding is active only for the runtime where `exposePort()` was last\n * called. Call `exposePort()` again after a restart to reactivate an\n * existing URL for the current runtime.\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 return await this.previewService.exposePort(port, options);\n }\n\n /**\n * Revoke preview URL authorization and current-runtime activation for a port.\n *\n * Revocation is idempotent: calling this for a port with no preview state is\n * still successful. The operation clears Durable Object-owned preview state\n * only and does not contact, probe, wake, or clean up the container runtime.\n */\n async unexposePort(port: number): Promise<void> {\n return await this.previewService.unexposePort(port);\n }\n\n /**\n * Returns preview URLs that are currently forwardable in the active runtime.\n * Durable authorization without current-runtime activation is omitted.\n */\n async getExposedPorts(hostname: string) {\n return await this.previewService.getExposedPorts(hostname);\n }\n\n /**\n * Returns whether a port is currently preview-forwardable.\n * This checks Durable Object-owned auth and runtime activation without\n * contacting or waking the container.\n */\n async isPortExposed(port: number): Promise<boolean> {\n return await this.previewService.isPortExposed(port);\n }\n\n /**\n * Checks durable preview URL authorization for a port/token pair.\n *\n * This does not check whether the port is activated for the current runtime\n * and is not sufficient to decide whether preview traffic may forward.\n */\n async validatePortToken(port: number, token: string): Promise<boolean> {\n return await this.previewService.validatePortToken(port, token);\n }\n\n /**\n * Namespaced tunnel API. Quick tunnels are zero-config preview URLs\n * backed by Cloudflare's trycloudflare service. Named tunnels bind a\n * stable hostname under the configured Cloudflare zone.\n *\n * - `tunnels.get(port)` — idempotent. Returns the cached tunnel for\n * `port` if one exists in DO storage, otherwise spawns a fresh\n * cloudflared process and persists the record.\n * - `tunnels.list()` — returns tunnels currently usable through this\n * sandbox runtime.\n * - `tunnels.destroy(portOrInfo)` — tear down by port number or by\n * the record returned from `get()`.\n *\n * Container restarts drop quick-tunnel records because their\n * `*.trycloudflare.com` URLs are tied to the dead cloudflared process.\n * Named-tunnel records stay in storage and are marked for respawn so the\n * next `get(port, { name })` call reuses the Cloudflare tunnel and DNS\n * record while starting a fresh cloudflared process.\n */\n get tunnels(): TunnelsHandler {\n this.ensureTunnelsBuilt();\n // Non-null after ensureTunnelsBuilt(); cast for the type system.\n return this.tunnelsHandler as TunnelsHandler;\n }\n\n /**\n * Lazily construct the tunnel subsystem handle. Called from the\n * `tunnels` getter on first access and from sandbox lifecycle hooks\n * when tunnel reconciliation or cleanup is needed.\n */\n private ensureTunnelsBuilt(): void {\n if (this.tunnelsHandler) return;\n const built = createTunnelsHandle({\n client: this.client,\n storage: this.ctx.storage,\n logger: this.logger,\n sandboxId: this.ctx.id.toString(),\n currentRuntime: this.currentRuntime,\n currentLifetime: this.currentLifetime,\n getNamedTunnelConfig: () => this.namedTunnelConfigResolver.getConfig()\n });\n this.tunnelServiceHandle = built;\n this.tunnelsHandler = built.tunnels;\n this.tunnelExitHandler = built.handleTunnelExit;\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 *\n * Cleans up explicit session resources and removes them from the container.\n *\n * @param sessionId - The ID of the session to delete\n * @returns Result with success status, sessionId, and timestamp\n */\n async deleteSession(sessionId: string): Promise<SessionDeleteResult> {\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 explicit session-create handshake has been\n * observed yet on this sandbox. Call `createSession()` 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 // Unified exec (mirrors `ctx.container.exec()` contract). Session-scoped:\n // caller-provided `options.sessionId` is overridden by this wrapper's\n // sessionId to enforce session pinning.\n exec: (command: string | string[], execOptions?: SandboxExecOptions) =>\n createSandboxProcessPromise(\n this.spawnSandboxProcess(command, { ...execOptions, sessionId })\n ),\n\n run: (command, options) =>\n this.executeCommand(command, sessionId, options),\n\n // Process management\n listProcesses: () => this.listProcesses({ sessionId }),\n getProcess: (id) => this.getProcess(id, { sessionId }),\n killAllProcesses: () => this.killAllProcesses(),\n cleanupCompletedProcesses: () => this.cleanupCompletedProcesses(),\n\n // File operations - pass sessionId via options\n writeFile: (path, content, options) =>\n this.writeFile(path, content, { ...options, sessionId }),\n readFile: ((\n path: string,\n options?: { encoding?: FileEncoding; sessionId?: string }\n ) => {\n const encoding = options?.encoding;\n if (encoding === 'none') {\n return this.readFile(path, { encoding: 'none', sessionId });\n }\n return this.readFile(path, { encoding, sessionId });\n }) as ExecutionSession['readFile'],\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.listFiles(path, { ...options, sessionId }),\n exists: (path) => this.exists(path, { 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(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(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 // 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 // Backup methods — squashfs archive + R2 storage\n // ============================================================================\n\n /**\n * Create a backup of a directory and upload it to R2.\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 async createBackup(options: BackupOptions): Promise<DirectoryBackup> {\n return this.backupService.createBackup(options);\n }\n\n /**\n * Restore a backup from R2 into a directory.\n *\n * Production restores use a FUSE overlay mount. Local-bucket restores stream\n * the archive through the R2 binding and extract it with unsquashfs.\n *\n * Concurrent backup/restore calls on the same sandbox are serialized.\n */\n async restoreBackup(backup: DirectoryBackup): Promise<RestoreBackupResult> {\n return await this.backupService.restoreBackup(backup);\n }\n\n async __setBackupRestoreFaultForTesting(\n fault: BackupRestoreTestFault | null\n ): Promise<void> {\n await this.backupService.setRestoreFaultForTesting(fault);\n }\n\n async registerGitAuthInterceptor(\n params: GitAuthInterceptorParams\n ): Promise<void> {\n await configureGitAuthInterceptor(this.getMountOutboundHost(), params);\n }\n\n private getMountOutboundHost(): MountOutboundHost {\n return {\n ctx: this.ctx as EgressContainerState,\n constructorRef: this.constructor,\n enableInternet: this.enableInternet,\n logger: this.logger,\n setOutboundByHost: (host, method, params) =>\n this.setOutboundByHost<unknown>(host, method, params),\n removeOutboundByHost: (host) => this.removeOutboundByHost(host)\n };\n }\n}\n"],"mappings":"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"}
|