@cloudflare/sandbox 0.9.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,65 @@
1
1
  import { Container } from "@cloudflare/containers";
2
+ import "capnweb";
2
3
 
3
- //#region ../shared/dist/logger/types.d.ts
4
+ //#region ../shared/dist/desktop-types.d.ts
4
5
 
6
+ interface DesktopStartResult {
7
+ success: boolean;
8
+ resolution: [number, number];
9
+ dpi: number;
10
+ }
11
+ interface DesktopStopResult {
12
+ success: boolean;
13
+ }
14
+ interface DesktopStatusResult {
15
+ success: boolean;
16
+ status: 'active' | 'partial' | 'inactive';
17
+ processes: Record<string, DesktopProcessHealth>;
18
+ resolution: [number, number] | null;
19
+ dpi: number | null;
20
+ }
21
+ interface DesktopProcessHealth {
22
+ running: boolean;
23
+ pid?: number;
24
+ uptime?: number;
25
+ }
26
+ type DesktopImageFormat = 'png' | 'jpeg' | 'webp';
27
+ interface DesktopScreenshotRequest {
28
+ format?: 'base64';
29
+ imageFormat?: DesktopImageFormat;
30
+ quality?: number;
31
+ showCursor?: boolean;
32
+ }
33
+ interface DesktopScreenshotRegionRequest extends DesktopScreenshotRequest {
34
+ region: DesktopScreenshotRegion;
35
+ }
36
+ interface DesktopScreenshotRegion {
37
+ x: number;
38
+ y: number;
39
+ width: number;
40
+ height: number;
41
+ }
42
+ interface DesktopScreenshotResult {
43
+ success: boolean;
44
+ data: string;
45
+ imageFormat: DesktopImageFormat;
46
+ width: number;
47
+ height: number;
48
+ }
49
+ type DesktopMouseButton = 'left' | 'right' | 'middle';
50
+ type DesktopScrollDirection = 'up' | 'down' | 'left' | 'right';
51
+ interface DesktopCursorPosition {
52
+ success: boolean;
53
+ x: number;
54
+ y: number;
55
+ }
56
+ interface DesktopScreenSize {
57
+ success: boolean;
58
+ width: number;
59
+ height: number;
60
+ }
61
+ //#endregion
62
+ //#region ../shared/dist/logger/types.d.ts
5
63
  type LogComponent = 'container' | 'sandbox-do' | 'executor';
6
64
  /**
7
65
  * Context metadata included in every log entry
@@ -826,6 +884,8 @@ interface SandboxOptions {
826
884
  * - `"http"` (default): Standard HTTP request/response. Works everywhere.
827
885
  * - `"websocket"`: Multiplexes requests over a single WebSocket connection,
828
886
  * avoiding sub-request limits in Workers and Durable Objects.
887
+ * - `"capnweb"`: Direct RPC over a single WebSocket using the capnweb protocol.
888
+ * Lowest latency; automatically disconnects when idle to respect `sleepAfter`.
829
889
  *
830
890
  * When set via `getSandbox()` options, this overrides the `SANDBOX_TRANSPORT` env var.
831
891
  *
@@ -836,7 +896,7 @@ interface SandboxOptions {
836
896
  *
837
897
  * @default "http"
838
898
  */
839
- transport?: 'http' | 'websocket';
899
+ transport?: 'http' | 'websocket' | 'rpc';
840
900
  }
841
901
  /**
842
902
  * Execution session - isolated execution context within a sandbox
@@ -1166,7 +1226,7 @@ interface ExecutionSession {
1166
1226
  streamProcessLogs(processId: string, options?: {
1167
1227
  signal?: AbortSignal;
1168
1228
  }): Promise<ReadableStream<Uint8Array>>;
1169
- writeFile(path: string, content: string, options?: {
1229
+ writeFile(path: string, content: string | ReadableStream<Uint8Array>, options?: {
1170
1230
  encoding?: string;
1171
1231
  }): Promise<WriteFileResult>;
1172
1232
  readFile(path: string, options?: {
@@ -1371,7 +1431,7 @@ interface ISandbox {
1371
1431
  stderr: string;
1372
1432
  processId: string;
1373
1433
  }>;
1374
- writeFile(path: string, content: string, options?: {
1434
+ writeFile(path: string, content: string | ReadableStream<Uint8Array>, options?: {
1375
1435
  encoding?: string;
1376
1436
  }): Promise<WriteFileResult>;
1377
1437
  readFile(path: string, options?: {
@@ -1402,6 +1462,13 @@ interface ISandbox {
1402
1462
  unmountBucket(mountPath: string): Promise<void>;
1403
1463
  createSession(options?: SessionOptions): Promise<ExecutionSession>;
1404
1464
  deleteSession(sessionId: string): Promise<SessionDeleteResult>;
1465
+ /**
1466
+ * Returns the Cloudflare placement ID observed during the most recent
1467
+ * session-create handshake with the container. `null` when the container
1468
+ * does not expose `CLOUDFLARE_PLACEMENT_ID` (local development). `undefined`
1469
+ * when no handshake has been observed yet on this sandbox.
1470
+ */
1471
+ getContainerPlacementId(): Promise<string | null | undefined>;
1405
1472
  createCodeContext(options?: CreateContextOptions): Promise<CodeContext>;
1406
1473
  runCode(code: string, options?: RunCodeOptions): Promise<ExecutionResult>;
1407
1474
  runCodeStream(code: string, options?: RunCodeOptions): Promise<ReadableStream>;
@@ -1415,6 +1482,162 @@ declare function isExecResult(value: any): value is ExecResult;
1415
1482
  declare function isProcess(value: any): value is Process;
1416
1483
  declare function isProcessStatus(value: string): value is ProcessStatus;
1417
1484
  //#endregion
1485
+ //#region ../shared/dist/rpc-types.d.ts
1486
+ interface SandboxCommandsAPI {
1487
+ execute(command: string, sessionId: string, options?: {
1488
+ timeoutMs?: number;
1489
+ env?: Record<string, string | undefined>;
1490
+ cwd?: string;
1491
+ }): Promise<{
1492
+ success: boolean;
1493
+ exitCode: number;
1494
+ stdout: string;
1495
+ stderr: string;
1496
+ command: string;
1497
+ timestamp: string;
1498
+ }>;
1499
+ executeStream(command: string, sessionId: string, options?: {
1500
+ timeoutMs?: number;
1501
+ env?: Record<string, string | undefined>;
1502
+ cwd?: string;
1503
+ }): Promise<ReadableStream<Uint8Array>>;
1504
+ }
1505
+ interface SandboxFilesAPI {
1506
+ readFile(path: string, sessionId: string, options?: {
1507
+ encoding?: string;
1508
+ }): Promise<ReadFileResult>;
1509
+ readFileStream(path: string, sessionId: string): Promise<ReadableStream<Uint8Array>>;
1510
+ writeFile(path: string, content: string, sessionId: string, options?: {
1511
+ encoding?: string;
1512
+ permissions?: string;
1513
+ }): Promise<WriteFileResult>;
1514
+ writeFileStream(path: string, stream: ReadableStream<Uint8Array>, sessionId: string): Promise<{
1515
+ success: boolean;
1516
+ path: string;
1517
+ bytesWritten: number;
1518
+ timestamp: string;
1519
+ }>;
1520
+ deleteFile(path: string, sessionId: string): Promise<DeleteFileResult>;
1521
+ renameFile(oldPath: string, newPath: string, sessionId: string): Promise<RenameFileResult>;
1522
+ moveFile(sourcePath: string, destinationPath: string, sessionId: string): Promise<MoveFileResult>;
1523
+ mkdir(path: string, sessionId: string, options?: {
1524
+ recursive?: boolean;
1525
+ }): Promise<MkdirResult>;
1526
+ listFiles(path: string, sessionId: string, options?: ListFilesOptions): Promise<ListFilesResult>;
1527
+ exists(path: string, sessionId: string): Promise<FileExistsResult>;
1528
+ }
1529
+ interface SandboxProcessesAPI {
1530
+ startProcess(command: string, sessionId: string, options?: {
1531
+ processId?: string;
1532
+ timeoutMs?: number;
1533
+ }): Promise<ProcessStartResult>;
1534
+ listProcesses(): Promise<ProcessListResult>;
1535
+ getProcess(id: string): Promise<ProcessInfoResult>;
1536
+ killProcess(id: string): Promise<ProcessKillResult>;
1537
+ killAllProcesses(): Promise<ProcessCleanupResult>;
1538
+ getProcessLogs(id: string): Promise<ProcessLogsResult>;
1539
+ streamProcessLogs(id: string): Promise<ReadableStream<Uint8Array>>;
1540
+ }
1541
+ interface SandboxPortsAPI {
1542
+ exposePort(port: number, sessionId: string, name?: string): Promise<PortExposeResult>;
1543
+ getExposedPorts(sessionId: string): Promise<PortListResult>;
1544
+ unexposePort(port: number, sessionId: string): Promise<PortCloseResult>;
1545
+ watchPort(request: PortWatchRequest): Promise<ReadableStream<Uint8Array>>;
1546
+ }
1547
+ interface SandboxGitAPI {
1548
+ checkout(repoUrl: string, sessionId: string, options?: {
1549
+ branch?: string;
1550
+ targetDir?: string;
1551
+ depth?: number;
1552
+ timeoutMs?: number;
1553
+ }): Promise<GitCheckoutResult>;
1554
+ }
1555
+ interface SandboxInterpreterAPI {
1556
+ createCodeContext(options?: CreateContextOptions): Promise<CodeContext>;
1557
+ streamCode(contextId: string, code: string, language?: string): Promise<ReadableStream<Uint8Array>>;
1558
+ runCodeStream(contextId: string | undefined, code: string, language: string | undefined, callbacks: {
1559
+ onStdout?: (output: OutputMessage) => void | Promise<void>;
1560
+ onStderr?: (output: OutputMessage) => void | Promise<void>;
1561
+ onResult?: (result: Result) => void | Promise<void>;
1562
+ onError?: (error: ExecutionError) => void | Promise<void>;
1563
+ }, timeoutMs?: number): Promise<void>;
1564
+ listCodeContexts(): Promise<CodeContext[]>;
1565
+ deleteCodeContext(contextId: string): Promise<void>;
1566
+ }
1567
+ interface SandboxUtilsAPI {
1568
+ ping(): Promise<string>;
1569
+ getVersion(): Promise<string>;
1570
+ getCommands(): Promise<string[]>;
1571
+ createSession(options: {
1572
+ id: string;
1573
+ env?: Record<string, string | undefined>;
1574
+ cwd?: string;
1575
+ }): Promise<{
1576
+ success: boolean;
1577
+ id: string;
1578
+ message: string;
1579
+ timestamp: string;
1580
+ containerPlacementId?: string | null;
1581
+ }>;
1582
+ deleteSession(sessionId: string): Promise<{
1583
+ success: boolean;
1584
+ sessionId: string;
1585
+ timestamp: string;
1586
+ }>;
1587
+ listSessions(): Promise<{
1588
+ sessions: string[];
1589
+ }>;
1590
+ }
1591
+ interface SandboxBackupAPI {
1592
+ createArchive(dir: string, archivePath: string, sessionId: string, options?: {
1593
+ excludes?: string[];
1594
+ gitignore?: boolean;
1595
+ }): Promise<CreateBackupResponse>;
1596
+ restoreArchive(dir: string, archivePath: string, sessionId: string): Promise<RestoreBackupResponse>;
1597
+ }
1598
+ interface SandboxDesktopAPI {
1599
+ start(options?: {
1600
+ resolution?: [number, number];
1601
+ dpi?: number;
1602
+ }): Promise<DesktopStartResult>;
1603
+ stop(): Promise<DesktopStopResult>;
1604
+ status(): Promise<DesktopStatusResult>;
1605
+ screenshot(options?: DesktopScreenshotRequest): Promise<DesktopScreenshotResult>;
1606
+ screenshotRegion(request: DesktopScreenshotRegionRequest): Promise<DesktopScreenshotResult>;
1607
+ click(x: number, y: number, options?: {
1608
+ button?: DesktopMouseButton;
1609
+ clickCount?: number;
1610
+ }): Promise<void>;
1611
+ doubleClick(x: number, y: number): Promise<void>;
1612
+ tripleClick(x: number, y: number): Promise<void>;
1613
+ rightClick(x: number, y: number): Promise<void>;
1614
+ middleClick(x: number, y: number): Promise<void>;
1615
+ mouseDown(x?: number, y?: number, options?: {
1616
+ button?: DesktopMouseButton;
1617
+ }): Promise<void>;
1618
+ mouseUp(x?: number, y?: number, options?: {
1619
+ button?: DesktopMouseButton;
1620
+ }): Promise<void>;
1621
+ moveMouse(x: number, y: number): Promise<void>;
1622
+ drag(startX: number, startY: number, endX: number, endY: number, options?: {
1623
+ button?: DesktopMouseButton;
1624
+ }): Promise<void>;
1625
+ scroll(x: number, y: number, direction: DesktopScrollDirection, amount?: number): Promise<void>;
1626
+ getCursorPosition(): Promise<DesktopCursorPosition>;
1627
+ type(text: string, options?: {
1628
+ delay?: number;
1629
+ }): Promise<void>;
1630
+ press(key: string): Promise<void>;
1631
+ keyDown(key: string): Promise<void>;
1632
+ keyUp(key: string): Promise<void>;
1633
+ getScreenSize(): Promise<DesktopScreenSize>;
1634
+ getProcessStatus(name: string): Promise<DesktopStatusResult>;
1635
+ }
1636
+ interface SandboxWatchAPI {
1637
+ watch(request: WatchRequest): Promise<ReadableStream<Uint8Array>>;
1638
+ checkChanges(request: CheckChangesRequest): Promise<CheckChangesResult>;
1639
+ }
1640
+ //#endregion
1418
1641
  //#region src/clients/types.d.ts
1419
1642
  /**
1420
1643
  * Minimal interface for container fetch functionality
@@ -1502,7 +1725,7 @@ interface SessionRequest {
1502
1725
  /**
1503
1726
  * Transport mode for SDK communication
1504
1727
  */
1505
- type TransportMode = 'http' | 'websocket';
1728
+ type TransportMode = 'http' | 'websocket' | 'rpc';
1506
1729
  interface TransportRequestInit extends RequestInit {
1507
1730
  /** Override the non-streaming request timeout for this single request. */
1508
1731
  requestTimeoutMs?: number;
@@ -1627,7 +1850,10 @@ declare class BackupClient extends BaseHttpClient {
1627
1850
  * @param archivePath - Where the container should write the archive
1628
1851
  * @param sessionId - Session context
1629
1852
  */
1630
- createArchive(dir: string, archivePath: string, sessionId: string, gitignore?: boolean, excludes?: string[]): Promise<CreateBackupResponse>;
1853
+ createArchive(dir: string, archivePath: string, sessionId: string, options?: {
1854
+ excludes?: string[];
1855
+ gitignore?: boolean;
1856
+ }): Promise<CreateBackupResponse>;
1631
1857
  /**
1632
1858
  * Tell the container to restore a squashfs archive into a directory.
1633
1859
  * @param dir - Target directory
@@ -2178,11 +2404,16 @@ interface CreateSessionRequest {
2178
2404
  commandTimeoutMs?: number;
2179
2405
  }
2180
2406
  /**
2181
- * Response interface for creating sessions
2407
+ * Response interface for creating sessions.
2408
+ *
2409
+ * `containerPlacementId` carries the container's `CLOUDFLARE_PLACEMENT_ID` at session
2410
+ * creation time so the DO can capture it without a separate request. It is
2411
+ * `null` when the environment variable is not set, such as in local dev.
2182
2412
  */
2183
2413
  interface CreateSessionResponse extends BaseApiResponse {
2184
2414
  id: string;
2185
2415
  message: string;
2416
+ containerPlacementId?: string | null;
2186
2417
  }
2187
2418
  /**
2188
2419
  * Request interface for deleting sessions
@@ -2258,14 +2489,13 @@ declare class WatchClient extends BaseHttpClient {
2258
2489
  //#endregion
2259
2490
  //#region src/clients/sandbox-client.d.ts
2260
2491
  /**
2261
- * Main sandbox client that composes all domain-specific clients
2262
- * Provides organized access to all sandbox functionality
2492
+ * Main sandbox client that composes all domain-specific clients.
2493
+ * Provides organized access to all sandbox functionality.
2263
2494
  *
2264
2495
  * Supports two transport modes:
2265
2496
  * - HTTP (default): Each request is a separate HTTP call
2266
- * - WebSocket: All requests multiplexed over a single connection
2267
- *
2268
- * WebSocket mode reduces sub-request count when running inside Workers/Durable Objects.
2497
+ * - WebSocket: All requests multiplexed over a single connection,
2498
+ * reducing sub-request count inside Workers/Durable Objects
2269
2499
  */
2270
2500
  declare class SandboxClient {
2271
2501
  readonly backup: BackupClient;
@@ -2296,6 +2526,19 @@ declare class SandboxClient {
2296
2526
  * Check if WebSocket is connected (only relevant in WebSocket mode)
2297
2527
  */
2298
2528
  isWebSocketConnected(): boolean;
2529
+ /**
2530
+ * Stream a file directly to the container over a binary RPC channel.
2531
+ *
2532
+ * Requires the capnweb transport (`useWebSocket: 'rpc'`). Calling this
2533
+ * method with the HTTP or WebSocket transports throws an error because those
2534
+ * transports do not support binary streaming.
2535
+ */
2536
+ writeFileStream(_path: string, _content: ReadableStream<Uint8Array>, _sessionId: string): Promise<{
2537
+ success: boolean;
2538
+ path: string;
2539
+ bytesWritten: number;
2540
+ timestamp: string;
2541
+ }>;
2299
2542
  /**
2300
2543
  * Connect WebSocket transport (no-op in HTTP mode)
2301
2544
  * Called automatically on first request, but can be called explicitly
@@ -2309,6 +2552,132 @@ declare class SandboxClient {
2309
2552
  disconnect(): void;
2310
2553
  }
2311
2554
  //#endregion
2555
+ //#region src/container-connection.d.ts
2556
+ /** Stub that can issue a WebSocket-upgrade fetch through the DO's Container base class. */
2557
+ interface ContainerFetchStub {
2558
+ fetch(request: Request): Promise<Response>;
2559
+ }
2560
+ interface ContainerConnectionOptions {
2561
+ stub: ContainerFetchStub;
2562
+ port?: number;
2563
+ logger?: Logger;
2564
+ }
2565
+ //#endregion
2566
+ //#region src/clients/rpc-sandbox-client.d.ts
2567
+ interface RPCSandboxClientOptions extends ContainerConnectionOptions {
2568
+ /** Idle timeout before disconnecting the WebSocket (ms). Defaults to 1 000. */
2569
+ idleDisconnectMs?: number;
2570
+ /** Busy/idle poll interval (ms). Defaults to 1 000. */
2571
+ busyPollIntervalMs?: number;
2572
+ /**
2573
+ * Renew the DO's activity timeout. Fires at the start of every RPC call
2574
+ * and on every busy-poll tick while the session has work in flight.
2575
+ * Mirrors what `containerFetch()` does at the top of each HTTP request.
2576
+ */
2577
+ onActivity?: () => void;
2578
+ /**
2579
+ * Fires once when the capnweb session transitions from idle to busy
2580
+ * (an RPC call was started or a stream return is now in flight). The
2581
+ * Sandbox DO wires this to `inflightRequests++`, which makes
2582
+ * `isActivityExpired()` skip the sleepAfter comparison.
2583
+ */
2584
+ onSessionBusy?: () => void;
2585
+ /**
2586
+ * Fires once when the session transitions from busy back to idle
2587
+ * (all RPC promises settled and all stream exports released). The
2588
+ * Sandbox DO wires this to `inflightRequests = max(0, n-1)` and a
2589
+ * final `renewActivityTimeout()`, matching containerFetch's finally
2590
+ * block.
2591
+ */
2592
+ onSessionIdle?: () => void;
2593
+ }
2594
+ /**
2595
+ * SandboxClient backed by direct capnweb RPC.
2596
+ *
2597
+ * Drop-in replacement for SandboxClient when the capnweb transport is active.
2598
+ * All operations call the container's SandboxRPCAPI directly over capnweb,
2599
+ * bypassing the HTTP handler/router layer entirely.
2600
+ *
2601
+ * Manages its own WebSocket lifecycle: a fresh `ContainerConnection` is
2602
+ * created on demand and torn down after `idleDisconnectMs` of inactivity.
2603
+ * Busy/idle detection relies on `RpcSession.getStats()` which tracks all
2604
+ * in-flight RPC calls and stream exports — including long-lived streaming
2605
+ * RPCs that would be invisible to a simple per-call request counter (see
2606
+ * the file-level comment for the full rationale).
2607
+ */
2608
+ declare class RPCSandboxClient {
2609
+ private readonly connOptions;
2610
+ private readonly idleDisconnectMs;
2611
+ private readonly busyPollIntervalMs;
2612
+ private readonly logger;
2613
+ private readonly onActivity;
2614
+ private readonly onSessionBusy;
2615
+ private readonly onSessionIdle;
2616
+ private conn;
2617
+ private idleTimer;
2618
+ private busyPollTimer;
2619
+ /** Tracks whether we currently believe the session is busy. */
2620
+ private busy;
2621
+ /**
2622
+ * Set the first time the poller observes `conn.isConnected() === true`,
2623
+ * cleared in `destroyConnection()`. Lets us distinguish "the WebSocket
2624
+ * upgrade is still in progress" (don't tear down) from "we were
2625
+ * connected and the peer went away" (do tear down).
2626
+ */
2627
+ private wasEverConnected;
2628
+ constructor(options: RPCSandboxClientOptions);
2629
+ /**
2630
+ * Return the current connection, creating a new one if none exists or the
2631
+ * previous one was torn down by an idle disconnect. Starts the busy-poll
2632
+ * timer the first time a connection is materialized.
2633
+ */
2634
+ private getConnection;
2635
+ /**
2636
+ * Called synchronously at the start of each RPC method invocation.
2637
+ * Renews the DO activity timeout so the sleepAfter alarm is pushed
2638
+ * forward before the container processes the call.
2639
+ */
2640
+ private renewActivity;
2641
+ /**
2642
+ * Sample `getStats()` and update busy/idle state. While busy, renews the
2643
+ * activity timeout each tick so an in-flight stream keeps pushing the
2644
+ * sleepAfter deadline forward. On the busy → idle edge, fires
2645
+ * `onSessionIdle` and schedules the WebSocket disconnect.
2646
+ *
2647
+ * If the WebSocket has dropped underneath us (container crash, network
2648
+ * blip) we tear the connection down here. `destroyConnection()` fires
2649
+ * `onSessionIdle` if we were busy, so the DO's inflight counter doesn't
2650
+ * stay pinned forever waiting for a peer that's never going to reply.
2651
+ */
2652
+ private pollBusyState;
2653
+ private startBusyPoll;
2654
+ private stopBusyPoll;
2655
+ private scheduleIdleDisconnect;
2656
+ private clearIdleTimer;
2657
+ private destroyConnection;
2658
+ get commands(): SandboxCommandsAPI;
2659
+ get files(): SandboxFilesAPI;
2660
+ get processes(): SandboxProcessesAPI;
2661
+ get ports(): SandboxPortsAPI;
2662
+ get git(): SandboxGitAPI;
2663
+ get utils(): SandboxUtilsAPI;
2664
+ get backup(): SandboxBackupAPI;
2665
+ get desktop(): SandboxDesktopAPI;
2666
+ get watch(): SandboxWatchAPI;
2667
+ get interpreter(): SandboxInterpreterAPI;
2668
+ setRetryTimeoutMs(_ms: number): void;
2669
+ getTransportMode(): TransportMode;
2670
+ isWebSocketConnected(): boolean;
2671
+ connect(): Promise<void>;
2672
+ disconnect(): void;
2673
+ writeFileStream(path: string, stream: ReadableStream<Uint8Array>, sessionId: string): Promise<{
2674
+ success: boolean;
2675
+ path: string;
2676
+ bytesWritten: number;
2677
+ timestamp: string;
2678
+ }>;
2679
+ }
2680
+ //#endregion
2312
2681
  //#region src/sandbox.d.ts
2313
2682
  type SandboxConfiguration = {
2314
2683
  sandboxName?: {
@@ -2318,13 +2687,13 @@ type SandboxConfiguration = {
2318
2687
  sleepAfter?: string | number;
2319
2688
  keepAlive?: boolean;
2320
2689
  containerTimeouts?: NonNullable<SandboxOptions['containerTimeouts']>;
2321
- transport?: 'http' | 'websocket';
2690
+ transport?: 'http' | 'websocket' | 'rpc';
2322
2691
  };
2323
2692
  declare function getSandbox<T extends Sandbox<any>>(ns: DurableObjectNamespace<T>, id: string, options?: SandboxOptions): T;
2324
2693
  declare class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
2325
2694
  defaultPort: number;
2326
2695
  sleepAfter: string | number;
2327
- client: SandboxClient;
2696
+ client: SandboxClient | RPCSandboxClient;
2328
2697
  private codeInterpreter;
2329
2698
  private sandboxName;
2330
2699
  private normalizeId;
@@ -2412,6 +2781,10 @@ declare class Sandbox<Env = unknown> extends Container<Env> implements ISandbox
2412
2781
  * Create a SandboxClient with current transport settings
2413
2782
  */
2414
2783
  private createSandboxClient;
2784
+ /**
2785
+ * Create the appropriate client for a given transport protocol.
2786
+ */
2787
+ private createClientForTransport;
2415
2788
  constructor(ctx: DurableObjectState<{}>, env: Env);
2416
2789
  setSandboxName(name: string, normalizeId?: boolean): Promise<void>;
2417
2790
  configure(configuration: SandboxConfiguration): Promise<void>;
@@ -2431,7 +2804,7 @@ declare class Sandbox<Env = unknown> extends Container<Env> implements ISandbox
2431
2804
  * has been persisted: re-applying the same transport is a no-op.
2432
2805
  * Storage is written before the in-memory state and client are updated.
2433
2806
  */
2434
- setTransport(transport: 'http' | 'websocket'): Promise<void>;
2807
+ setTransport(transport: 'http' | 'websocket' | 'rpc'): Promise<void>;
2435
2808
  /**
2436
2809
  * Validate a timeout value is within acceptable range
2437
2810
  * Throws error if invalid - used for user-provided values
@@ -2493,9 +2866,30 @@ declare class Sandbox<Env = unknown> extends Container<Env> implements ISandbox
2493
2866
  */
2494
2867
  private executeS3FSMount;
2495
2868
  /**
2496
- * Cleanup and destroy the sandbox container
2869
+ * In-flight `destroy()` promise. While set, concurrent callers coalesce
2870
+ * onto the same teardown instead of triggering a second one. Cleared when
2871
+ * the underlying work settles, so a later call that genuinely needs to
2872
+ * recreate a destroyed sandbox still runs.
2873
+ *
2874
+ * If the underlying teardown hangs (e.g. `super.destroy()` never resolves
2875
+ * because the Containers control plane is unresponsive), every coalesced
2876
+ * caller hangs on the same promise until the Durable Object is evicted.
2877
+ * This is deliberate: a second concurrent teardown would not make a stuck
2878
+ * control plane unstuck, and spawning one would defeat the point of
2879
+ * coalescing. Callers that need bounded waits must apply their own
2880
+ * timeout around `destroy()`.
2881
+ */
2882
+ private inflightDestroy;
2883
+ /**
2884
+ * Cleanup and destroy the sandbox container.
2885
+ *
2886
+ * Concurrent calls coalesce: if a previous `destroy()` is still in flight,
2887
+ * subsequent calls await the same underlying work instead of starting a
2888
+ * second teardown. A canonical `sandbox.destroy.coalesced` event is logged
2889
+ * per coalesced call so repeated destroy traffic is observable.
2497
2890
  */
2498
2891
  destroy(): Promise<void>;
2892
+ private doDestroy;
2499
2893
  onStart(): Promise<void>;
2500
2894
  /**
2501
2895
  * Re-expose ports on the container runtime using tokens persisted in DO
@@ -2581,6 +2975,21 @@ declare class Sandbox<Env = unknown> extends Container<Env> implements ISandbox
2581
2975
  */
2582
2976
  private ensureDefaultSession;
2583
2977
  private initializeDefaultSession;
2978
+ /**
2979
+ * Persist the container's placement ID in DO storage.
2980
+ *
2981
+ * Called from the session-create handshake so subsequent reads via
2982
+ * `getContainerPlacementId()` do not require a round-trip to the container. The value
2983
+ * is overwritten on every handshake so that container replacements (which
2984
+ * assign a new placement ID) are reflected on the next session-create.
2985
+ *
2986
+ * A value of `undefined` means the handshake response omitted the field
2987
+ * (older container, unexpected error shape) and the stored value is left
2988
+ * untouched. `null` means the env var is not set in the container and is
2989
+ * stored as-is so callers can distinguish "observed and absent" from "not
2990
+ * yet observed."
2991
+ */
2992
+ private capturePlacementId;
2584
2993
  exec(command: string, options?: ExecOptions): Promise<ExecResult>;
2585
2994
  /**
2586
2995
  * Execute an infrastructure command (backup, mount, env setup, etc.)
@@ -2669,10 +3078,15 @@ declare class Sandbox<Env = unknown> extends Container<Env> implements ISandbox
2669
3078
  recursive?: boolean;
2670
3079
  sessionId?: string;
2671
3080
  }): Promise<MkdirResult>;
2672
- writeFile(path: string, content: string, options?: {
3081
+ writeFile(path: string, content: string | ReadableStream<Uint8Array>, options?: {
2673
3082
  encoding?: string;
2674
3083
  sessionId?: string;
2675
- }): Promise<WriteFileResult>;
3084
+ }): Promise<{
3085
+ success: boolean;
3086
+ path: string;
3087
+ bytesWritten: number;
3088
+ timestamp: string;
3089
+ } | WriteFileResult>;
2676
3090
  deleteFile(path: string, sessionId?: string): Promise<DeleteFileResult>;
2677
3091
  renameFile(oldPath: string, newPath: string, sessionId?: string): Promise<RenameFileResult>;
2678
3092
  moveFile(sourcePath: string, destinationPath: string, sessionId?: string): Promise<MoveFileResult>;
@@ -2813,6 +3227,24 @@ declare class Sandbox<Env = unknown> extends Container<Env> implements ISandbox
2813
3227
  * @throws Error if attempting to delete the default session
2814
3228
  */
2815
3229
  deleteSession(sessionId: string): Promise<SessionDeleteResult>;
3230
+ /**
3231
+ * Get the Cloudflare placement ID observed for the underlying container.
3232
+ *
3233
+ * The placement ID is captured during the first session-create handshake
3234
+ * after a container start and stored in Durable Object storage, so this
3235
+ * method returns the cached value without contacting the container. A new
3236
+ * placement ID is captured on each subsequent session-create handshake,
3237
+ * which occurs whenever the container has been replaced.
3238
+ *
3239
+ * Returns `null` when a handshake has completed but the container's
3240
+ * `CLOUDFLARE_PLACEMENT_ID` environment variable is not set (for example,
3241
+ * in local development).
3242
+ *
3243
+ * Returns `undefined` when no handshake has been observed yet on this
3244
+ * sandbox. Call any method that triggers session creation (such as
3245
+ * `exec()`) to populate the value.
3246
+ */
3247
+ getContainerPlacementId(): Promise<string | null | undefined>;
2816
3248
  private getSessionWrapper;
2817
3249
  createCodeContext(options?: CreateContextOptions): Promise<CodeContext>;
2818
3250
  runCode(code: string, options?: RunCodeOptions): Promise<ExecutionResult>;
@@ -2830,6 +3262,7 @@ declare class Sandbox<Env = unknown> extends Container<Env> implements ISandbox
2830
3262
  * Returns the R2 bucket or throws if backup is not configured.
2831
3263
  */
2832
3264
  private requireBackupBucket;
3265
+ private normalizeBackupExcludes;
2833
3266
  private static readonly PRESIGNED_URL_EXPIRY_SECONDS;
2834
3267
  /**
2835
3268
  * Create a unique, dedicated session for a single backup operation.
@@ -2936,5 +3369,5 @@ declare class Sandbox<Env = unknown> extends Container<Env> implements ISandbox
2936
3369
  private doRestoreBackupLocal;
2937
3370
  }
2938
3371
  //#endregion
2939
- export { CheckChangesOptions as $, DesktopStopResponse as A, SandboxOptions as At, ExecuteResponse as B, ExposePortRequest as Bt, ClickOptions as C, ProcessListResult as Ct, DesktopStartOptions as D, ProcessStatus as Dt, DesktopClient as E, ProcessStartResult as Et, ScreenshotRegion as F, WatchOptions as Ft, HttpClientOptions as G, Execution as Gt, BaseApiResponse as H, PtyOptions as Ht, ScreenshotResponse as I, isExecResult as It, SessionRequest as J, RequestConfig as K, ExecutionResult as Kt, ScrollDirection as L, isProcess as Lt, ScreenSizeResponse as M, StreamOptions as Mt, ScreenshotBytesResponse as N, WaitForLogResult as Nt, DesktopStartResponse as O, RemoteMountBucketOptions as Ot, ScreenshotOptions as P, WaitForPortOptions as Pt, BucketProvider as Q, TypeOptions as R, isProcessStatus as Rt, WriteFileRequest as S, ProcessKillResult as St, Desktop as T, ProcessOptions as Tt, ContainerStub as U, CodeContext as Ut, BackupClient as V, StartProcessRequest as Vt, ErrorResponse as W, CreateContextOptions as Wt, BaseExecOptions as X, BackupOptions as Y, BucketCredentials as Z, GitClient as _, PortExposeResult as _t, CreateSessionRequest as a, ExecutionSession as at, MkdirRequest as b, ProcessCleanupResult as bt, DeleteSessionResponse as c, FileStreamEvent as ct, ProcessClient as d, ISandbox as dt, CheckChangesResult as et, PortClient as f, ListFilesOptions as ft, GitCheckoutRequest as g, PortCloseResult as gt, InterpreterClient as h, MountBucketOptions as ht, CommandsResponse as i, ExecResult as it, KeyInput as j, SessionOptions as jt, DesktopStatusResponse as k, RestoreBackupResult as kt, PingResponse as l, FileWatchSSEEvent as lt, ExecutionCallbacks as m, LogEvent as mt, getSandbox as n, ExecEvent as nt, CreateSessionResponse as o, FileChunk as ot, UnexposePortRequest as p, LocalMountBucketOptions as pt, ResponseHandler as q, RunCodeOptions as qt, SandboxClient as r, ExecOptions as rt, DeleteSessionRequest as s, FileMetadata as st, Sandbox as t, DirectoryBackup as tt, UtilityClient as u, GitCheckoutResult as ut, FileClient as v, PortListResult as vt, CursorPositionResponse as w, ProcessLogsResult as wt, ReadFileRequest as x, ProcessInfoResult as xt, FileOperationRequest as y, Process as yt, CommandClient as z, ExecuteRequest as zt };
2940
- //# sourceMappingURL=sandbox-Chr1Ebo-.d.ts.map
3372
+ export { BucketProvider as $, DesktopStopResponse as A, RestoreBackupResult as At, ExecuteResponse as B, ExecuteRequest as Bt, ClickOptions as C, ProcessKillResult as Ct, DesktopStartOptions as D, ProcessStartResult as Dt, DesktopClient as E, ProcessOptions as Et, ScreenshotRegion as F, WaitForPortOptions as Ft, HttpClientOptions as G, CreateContextOptions as Gt, BaseApiResponse as H, StartProcessRequest as Ht, ScreenshotResponse as I, WatchOptions as It, SessionRequest as J, RunCodeOptions as Jt, RequestConfig as K, Execution as Kt, ScrollDirection as L, isExecResult as Lt, ScreenSizeResponse as M, SessionOptions as Mt, ScreenshotBytesResponse as N, StreamOptions as Nt, DesktopStartResponse as O, ProcessStatus as Ot, ScreenshotOptions as P, WaitForLogResult as Pt, BucketCredentials as Q, TypeOptions as R, isProcess as Rt, WriteFileRequest as S, ProcessInfoResult as St, Desktop as T, ProcessLogsResult as Tt, ContainerStub as U, PtyOptions as Ut, BackupClient as V, ExposePortRequest as Vt, ErrorResponse as W, CodeContext as Wt, BackupOptions as X, SandboxInterpreterAPI as Y, BaseExecOptions as Z, GitClient as _, PortCloseResult as _t, CreateSessionRequest as a, ExecResult as at, MkdirRequest as b, Process as bt, DeleteSessionResponse as c, FileMetadata as ct, ProcessClient as d, GitCheckoutResult as dt, CheckChangesOptions as et, PortClient as f, ISandbox as ft, GitCheckoutRequest as g, MountBucketOptions as gt, InterpreterClient as h, LogEvent as ht, CommandsResponse as i, ExecOptions as it, KeyInput as j, SandboxOptions as jt, DesktopStatusResponse as k, RemoteMountBucketOptions as kt, PingResponse as l, FileStreamEvent as lt, ExecutionCallbacks as m, LocalMountBucketOptions as mt, getSandbox as n, DirectoryBackup as nt, CreateSessionResponse as o, ExecutionSession as ot, UnexposePortRequest as p, ListFilesOptions as pt, ResponseHandler as q, ExecutionResult as qt, SandboxClient as r, ExecEvent as rt, DeleteSessionRequest as s, FileChunk as st, Sandbox as t, CheckChangesResult as tt, UtilityClient as u, FileWatchSSEEvent as ut, FileClient as v, PortExposeResult as vt, CursorPositionResponse as w, ProcessListResult as wt, ReadFileRequest as x, ProcessCleanupResult as xt, FileOperationRequest as y, PortListResult as yt, CommandClient as z, isProcessStatus as zt };
3373
+ //# sourceMappingURL=sandbox-YMrVC62F.d.ts.map