@cloudflare/sandbox 0.9.3 → 0.10.0

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 CHANGED
@@ -1,6 +1,9 @@
1
1
  # Bun version — override via --build-arg BUN_VERSION=$(cat .bun-version)
2
2
  ARG BUN_VERSION=1
3
+ # Node version — override via --build-arg NODE_VERSION=24
4
+ ARG NODE_VERSION=24
3
5
  FROM oven/bun:${BUN_VERSION} AS bun-binary
6
+ FROM node:${NODE_VERSION}-slim AS node-runtime
4
7
 
5
8
  # Sandbox container images (default and python variants)
6
9
  # Multi-stage build optimized for Turborepo monorepo
@@ -8,7 +11,7 @@ FROM oven/bun:${BUN_VERSION} AS bun-binary
8
11
  # ============================================================================
9
12
  # Stage 1: Prune monorepo to only include necessary packages
10
13
  # ============================================================================
11
- FROM node:20-slim AS pruner
14
+ FROM node-runtime AS pruner
12
15
 
13
16
  WORKDIR /app
14
17
 
@@ -25,7 +28,7 @@ RUN turbo prune @repo/sandbox-container --docker
25
28
  # Using glibc-based images (not Alpine) so the standalone binary works on
26
29
  # standard Linux distributions (Debian, Ubuntu, RHEL, etc.)
27
30
  # ============================================================================
28
- FROM node:20-slim AS builder
31
+ FROM node-runtime AS builder
29
32
 
30
33
  WORKDIR /app
31
34
 
@@ -126,9 +129,10 @@ RUN sed -i 's/#user_allow_other/user_allow_other/' /etc/fuse.conf
126
129
  # fusermount requires /etc/mtab to locate active mounts
127
130
  RUN ln -sf /proc/mounts /etc/mtab
128
131
 
129
- # Install Node.js 20 LTS from official Node image
130
- COPY --from=node:20-slim /usr/local/bin/node /usr/local/bin/node
131
- COPY --from=node:20-slim /usr/local/lib/node_modules /usr/local/lib/node_modules
132
+ # Install Node.js from official Node image (defaults to 24 LTS,
133
+ # override via --build-arg NODE_VERSION=<version>)
134
+ COPY --from=node-runtime /usr/local/bin/node /usr/local/bin/node
135
+ COPY --from=node-runtime /usr/local/lib/node_modules /usr/local/lib/node_modules
132
136
  RUN ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm && \
133
137
  ln -s /usr/local/lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx
134
138
 
@@ -1,6 +1,6 @@
1
1
  import "../dist-B_eXrP83.js";
2
2
  import "../errors-CBi-O-pF.js";
3
- import { h as streamFile, n as getSandbox } from "../sandbox-BAuU-2a0.js";
3
+ import { h as streamFile, n as getSandbox } from "../sandbox-2bHZZmy5.js";
4
4
  import { DurableObject, env } from "cloudflare:workers";
5
5
  import { Hono } from "hono";
6
6
 
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as BackupOptions, A as DesktopStopResponse, At as ProcessStartResult, B as ExecuteResponse, Bt as WatchOptions, C as ClickOptions, Ct as Process, D as DesktopStartOptions, Dt as ProcessListResult, E as DesktopClient, Et as ProcessKillResult, F as ScreenshotRegion, Ft as SandboxTransport, G as HttpClientOptions, Gt as CodeContext, H as BaseApiResponse, Ht as isProcess, I as ScreenshotResponse, It as SessionOptions, J as SessionRequest, Jt as ExecutionResult, K as RequestConfig, Kt as CreateContextOptions, L as ScrollDirection, Lt as StreamOptions, M as ScreenSizeResponse, Mt as RemoteMountBucketOptions, N as ScreenshotBytesResponse, Nt as RestoreBackupResult, O as DesktopStartResponse, Ot as ProcessLogsResult, P as ScreenshotOptions, Pt as SandboxOptions, Q as StartProcessRequest, R as TypeOptions, Rt as WaitForLogResult, S as WriteFileRequest, St as PortListResult, T as Desktop, Tt as ProcessInfoResult, U as ContainerStub, Ut as isProcessStatus, V as BackupClient, Vt as isExecResult, W as ErrorResponse, Wt as PtyOptions, X as ExecuteRequest, Y as SandboxInterpreterAPI, Yt as RunCodeOptions, Z as ExposePortRequest, _ as GitClient, _t as LocalMountBucketOptions, a as CreateSessionRequest, at as DirectoryBackup, b as MkdirRequest, bt as PortCloseResult, c as DeleteSessionResponse, ct as ExecResult, d as ProcessClient, dt as FileMetadata, et as BaseExecOptions, f as PortClient, ft as FileStreamEvent, g as GitCheckoutRequest, gt as ListFilesOptions, h as InterpreterClient, ht as ISandbox, i as CommandsResponse, it as CheckChangesResult, j as KeyInput, jt as ProcessStatus, k as DesktopStatusResponse, kt as ProcessOptions, l as PingResponse, lt as ExecutionSession, m as ExecutionCallbacks, mt as GitCheckoutResult, n as getSandbox, nt as BucketProvider, o as CreateSessionResponse, ot as ExecEvent, p as UnexposePortRequest, pt as FileWatchSSEEvent, q as ResponseHandler, qt as Execution, r as SandboxClient, rt as CheckChangesOptions, s as DeleteSessionRequest, st as ExecOptions, t as Sandbox, tt as BucketCredentials, u as UtilityClient, ut as FileChunk, v as FileClient, vt as LogEvent, w as CursorPositionResponse, wt as ProcessCleanupResult, x as ReadFileRequest, xt as PortExposeResult, y as FileOperationRequest, yt as MountBucketOptions, z as CommandClient, zt as WaitForPortOptions } from "./sandbox-CW4QeITP.js";
1
+ import { $ as BackupOptions, A as DesktopStopResponse, At as ProcessStartResult, B as ExecuteResponse, Bt as WatchOptions, C as ClickOptions, Ct as Process, D as DesktopStartOptions, Dt as ProcessListResult, E as DesktopClient, Et as ProcessKillResult, F as ScreenshotRegion, Ft as SandboxTransport, G as HttpClientOptions, Gt as CodeContext, H as BaseApiResponse, Ht as isProcess, I as ScreenshotResponse, It as SessionOptions, J as SessionRequest, Jt as ExecutionResult, K as RequestConfig, Kt as CreateContextOptions, L as ScrollDirection, Lt as StreamOptions, M as ScreenSizeResponse, Mt as RemoteMountBucketOptions, N as ScreenshotBytesResponse, Nt as RestoreBackupResult, O as DesktopStartResponse, Ot as ProcessLogsResult, P as ScreenshotOptions, Pt as SandboxOptions, Q as StartProcessRequest, R as TypeOptions, Rt as WaitForLogResult, S as WriteFileRequest, St as PortListResult, T as Desktop, Tt as ProcessInfoResult, U as ContainerStub, Ut as isProcessStatus, V as BackupClient, Vt as isExecResult, W as ErrorResponse, Wt as PtyOptions, X as ExecuteRequest, Y as SandboxInterpreterAPI, Yt as RunCodeOptions, Z as ExposePortRequest, _ as GitClient, _t as LocalMountBucketOptions, a as CreateSessionRequest, at as DirectoryBackup, b as MkdirRequest, bt as PortCloseResult, c as DeleteSessionResponse, ct as ExecResult, d as ProcessClient, dt as FileMetadata, et as BaseExecOptions, f as PortClient, ft as FileStreamEvent, g as GitCheckoutRequest, gt as ListFilesOptions, h as InterpreterClient, ht as ISandbox, i as CommandsResponse, it as CheckChangesResult, j as KeyInput, jt as ProcessStatus, k as DesktopStatusResponse, kt as ProcessOptions, l as PingResponse, lt as ExecutionSession, m as ExecutionCallbacks, mt as GitCheckoutResult, n as getSandbox, nt as BucketProvider, o as CreateSessionResponse, ot as ExecEvent, p as UnexposePortRequest, pt as FileWatchSSEEvent, q as ResponseHandler, qt as Execution, r as SandboxClient, rt as CheckChangesOptions, s as DeleteSessionRequest, st as ExecOptions, t as Sandbox, tt as BucketCredentials, u as UtilityClient, ut as FileChunk, v as FileClient, vt as LogEvent, w as CursorPositionResponse, wt as ProcessCleanupResult, x as ReadFileRequest, xt as PortExposeResult, y as FileOperationRequest, yt as MountBucketOptions, z as CommandClient, zt as WaitForPortOptions } from "./sandbox-C-AzrX_L.js";
2
2
  import { a as DesktopCoordinateErrorContext, d as RPCTransportContext, f as RPCTransportErrorKind, g as ErrorCode, h as OperationType, i as BackupRestoreContext, l as ProcessExitedBeforeReadyContext, m as ErrorResponse$1, n as BackupExpiredContext, o as DesktopErrorContext, p as SessionTerminatedContext, r as BackupNotFoundContext, s as InvalidBackupConfigContext, t as BackupCreateContext, u as ProcessReadyTimeoutContext } from "./contexts-D_shbnJs.js";
3
3
  import { ContainerProxy } from "@cloudflare/containers";
4
4
 
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { a as isExecResult, o as isProcess, s as isProcessStatus } from "./dist-B_eXrP83.js";
2
2
  import "./errors-CBi-O-pF.js";
3
- import { A as DesktopInvalidOptionsError, C as CommandClient, D as BackupNotFoundError, E as BackupExpiredError, F as InvalidBackupConfigError, I as ProcessExitedBeforeReadyError, L as ProcessReadyTimeoutError, M as DesktopProcessCrashedError, N as DesktopStartFailedError, O as BackupRestoreError, P as DesktopUnavailableError, R as RPCTransportError, S as DesktopClient, T as BackupCreateError, _ as UtilityClient, a as BucketMountError, b as GitClient, c as MissingCredentialsError, d as parseSSEStream, f as responseToAsyncIterable, g as SandboxClient, h as streamFile, i as proxyTerminal, j as DesktopNotStartedError, k as DesktopInvalidCoordinatesError, l as S3FSMountError, m as collectFile, n as getSandbox, o as BucketUnmountError, p as CodeInterpreter, r as proxyToSandbox, s as InvalidMountConfigError, t as Sandbox, u as asyncIterableToSSEStream, v as ProcessClient, w as BackupClient, x as FileClient, y as PortClient, z as SessionTerminatedError } from "./sandbox-BAuU-2a0.js";
3
+ import { A as DesktopInvalidOptionsError, C as CommandClient, D as BackupNotFoundError, E as BackupExpiredError, F as InvalidBackupConfigError, I as ProcessExitedBeforeReadyError, L as ProcessReadyTimeoutError, M as DesktopProcessCrashedError, N as DesktopStartFailedError, O as BackupRestoreError, P as DesktopUnavailableError, R as RPCTransportError, S as DesktopClient, T as BackupCreateError, _ as UtilityClient, a as BucketMountError, b as GitClient, c as MissingCredentialsError, d as parseSSEStream, f as responseToAsyncIterable, g as SandboxClient, h as streamFile, i as proxyTerminal, j as DesktopNotStartedError, k as DesktopInvalidCoordinatesError, l as S3FSMountError, m as collectFile, n as getSandbox, o as BucketUnmountError, p as CodeInterpreter, r as proxyToSandbox, s as InvalidMountConfigError, t as Sandbox, u as asyncIterableToSSEStream, v as ProcessClient, w as BackupClient, x as FileClient, y as PortClient, z as SessionTerminatedError } from "./sandbox-2bHZZmy5.js";
4
4
  import { ContainerProxy } from "@cloudflare/containers";
5
5
 
6
6
  export { BackupClient, BackupCreateError, BackupExpiredError, BackupNotFoundError, BackupRestoreError, BucketMountError, BucketUnmountError, CodeInterpreter, CommandClient, ContainerProxy, DesktopClient, DesktopInvalidCoordinatesError, DesktopInvalidOptionsError, DesktopNotStartedError, DesktopProcessCrashedError, DesktopStartFailedError, DesktopUnavailableError, FileClient, GitClient, InvalidBackupConfigError, InvalidMountConfigError, MissingCredentialsError, PortClient, ProcessClient, ProcessExitedBeforeReadyError, ProcessReadyTimeoutError, RPCTransportError, S3FSMountError, Sandbox, SandboxClient, SessionTerminatedError, UtilityClient, asyncIterableToSSEStream, collectFile, getSandbox, isExecResult, isProcess, isProcessStatus, parseSSEStream, proxyTerminal, proxyToSandbox, responseToAsyncIterable, streamFile };
@@ -1,4 +1,4 @@
1
- import { t as Sandbox } from "../sandbox-CW4QeITP.js";
1
+ import { t as Sandbox } from "../sandbox-C-AzrX_L.js";
2
2
  import { ApplyPatchOperation, ApplyPatchResult, Editor as Editor$1, Shell as Shell$1, ShellAction, ShellResult } from "@openai/agents";
3
3
 
4
4
  //#region src/openai/index.d.ts
@@ -1,4 +1,4 @@
1
- import { t as Sandbox } from "../sandbox-CW4QeITP.js";
1
+ import { t as Sandbox } from "../sandbox-C-AzrX_L.js";
2
2
  import { c as OpencodeStartupContext } from "../contexts-D_shbnJs.js";
3
3
  import { OpencodeClient } from "@opencode-ai/sdk/v2/client";
4
4
  import { Config } from "@opencode-ai/sdk/v2";
@@ -765,8 +765,8 @@ function createErrorFromResponse(errorResponse, options) {
765
765
  /**
766
766
  * Container startup retry configuration
767
767
  */
768
- const DEFAULT_RETRY_TIMEOUT_MS = 12e4;
769
- const MIN_TIME_FOR_RETRY_MS = 15e3;
768
+ const DEFAULT_RETRY_TIMEOUT_MS$1 = 12e4;
769
+ const MIN_TIME_FOR_RETRY_MS$1 = 15e3;
770
770
  /**
771
771
  * Abstract base transport with shared retry logic
772
772
  *
@@ -780,7 +780,7 @@ var BaseTransport = class {
780
780
  constructor(config) {
781
781
  this.config = config;
782
782
  this.logger = config.logger ?? createNoOpLogger();
783
- this.retryTimeoutMs = config.retryTimeoutMs ?? DEFAULT_RETRY_TIMEOUT_MS;
783
+ this.retryTimeoutMs = config.retryTimeoutMs ?? DEFAULT_RETRY_TIMEOUT_MS$1;
784
784
  }
785
785
  setRetryTimeoutMs(ms) {
786
786
  this.retryTimeoutMs = ms;
@@ -802,7 +802,7 @@ var BaseTransport = class {
802
802
  if (response.status === 503) {
803
803
  const elapsed = Date.now() - startTime;
804
804
  const remaining = this.retryTimeoutMs - elapsed;
805
- if (remaining > MIN_TIME_FOR_RETRY_MS) {
805
+ if (remaining > MIN_TIME_FOR_RETRY_MS$1) {
806
806
  const delay = Math.min(3e3 * 2 ** attempt, 3e4);
807
807
  this.logger.info("Container not ready, retrying", {
808
808
  status: response.status,
@@ -2756,6 +2756,9 @@ function normalizeBackupExcludePattern(pattern) {
2756
2756
  //#endregion
2757
2757
  //#region src/container-control/connection.ts
2758
2758
  const DEFAULT_CONNECT_TIMEOUT_MS = 3e4;
2759
+ const DEFAULT_RETRY_TIMEOUT_MS = 12e4;
2760
+ const MIN_TIME_FOR_RETRY_MS = 15e3;
2761
+ const MAX_RETRY_BACKOFF_MS = 3e4;
2759
2762
  /**
2760
2763
  * Manages a capnweb WebSocket RPC session to the container.
2761
2764
  *
@@ -2773,10 +2776,12 @@ var ContainerControlConnection = class {
2773
2776
  containerStub;
2774
2777
  port;
2775
2778
  logger;
2779
+ retryTimeoutMs;
2776
2780
  constructor(options) {
2777
2781
  this.containerStub = options.stub;
2778
2782
  this.port = options.port ?? 3e3;
2779
2783
  this.logger = options.logger ?? createNoOpLogger();
2784
+ this.retryTimeoutMs = options.retryTimeoutMs ?? DEFAULT_RETRY_TIMEOUT_MS;
2780
2785
  this.transport = new DeferredTransport();
2781
2786
  this.session = new RpcSession(this.transport);
2782
2787
  this.stub = this.session.getRemoteMain();
@@ -2826,20 +2831,17 @@ var ContainerControlConnection = class {
2826
2831
  this.connected = false;
2827
2832
  this.connectPromise = null;
2828
2833
  }
2834
+ /**
2835
+ * Update the 503 retry budget without recreating the connection. Takes
2836
+ * effect on the next `connect()`; an in-flight connect uses the value
2837
+ * captured at start. Mirrors `WebSocketTransport.setRetryTimeoutMs`.
2838
+ */
2839
+ setRetryTimeoutMs(ms) {
2840
+ this.retryTimeoutMs = ms;
2841
+ }
2829
2842
  async doConnect() {
2830
- const controller = new AbortController();
2831
- const timeout = setTimeout(() => controller.abort(), DEFAULT_CONNECT_TIMEOUT_MS);
2832
2843
  try {
2833
- const url = `http://localhost:${this.port}/rpc`;
2834
- const request = new Request(url, {
2835
- headers: {
2836
- Upgrade: "websocket",
2837
- Connection: "Upgrade"
2838
- },
2839
- signal: controller.signal
2840
- });
2841
- const response = await this.containerStub.fetch(request);
2842
- clearTimeout(timeout);
2844
+ const response = await this.fetchUpgradeWithRetry();
2843
2845
  if (response.status !== 101) throw new Error(`WebSocket upgrade failed: ${response.status} ${response.statusText}`);
2844
2846
  const ws = response.webSocket;
2845
2847
  if (!ws) throw new Error("No WebSocket in upgrade response");
@@ -2858,13 +2860,63 @@ var ContainerControlConnection = class {
2858
2860
  this.connected = true;
2859
2861
  this.logger.debug("ContainerControlConnection established", { port: this.port });
2860
2862
  } catch (error) {
2861
- clearTimeout(timeout);
2862
2863
  this.connected = false;
2863
2864
  this.transport.abort(error);
2864
2865
  this.logger.error("ContainerControlConnection failed", error instanceof Error ? error : new Error(String(error)));
2865
2866
  throw error;
2866
2867
  }
2867
2868
  }
2869
+ /**
2870
+ * Issue WebSocket upgrade fetches, retrying transient 503 responses with
2871
+ * exponential backoff (3s → 6s → 12s → … capped at 30s) until either
2872
+ * the upgrade succeeds, a non-503 status is returned, or the retry budget
2873
+ * runs out. Mirrors `WebSocketTransport.fetchUpgradeWithRetry` so both
2874
+ * transports behave the same way during container startup.
2875
+ */
2876
+ async fetchUpgradeWithRetry() {
2877
+ const retryTimeoutMs = this.retryTimeoutMs;
2878
+ const startTime = Date.now();
2879
+ let attempt = 0;
2880
+ while (true) {
2881
+ const response = await this.fetchUpgradeAttempt();
2882
+ if (response.status !== 503) return response;
2883
+ const remaining = retryTimeoutMs - (Date.now() - startTime);
2884
+ if (remaining <= MIN_TIME_FOR_RETRY_MS) return response;
2885
+ const delay = Math.min(3e3 * 2 ** attempt, MAX_RETRY_BACKOFF_MS);
2886
+ this.logger.info("ContainerControlConnection upgrade returned 503, retrying", {
2887
+ attempt: attempt + 1,
2888
+ delayMs: delay,
2889
+ remainingSec: Math.floor(remaining / 1e3)
2890
+ });
2891
+ await this.sleep(delay);
2892
+ attempt++;
2893
+ }
2894
+ }
2895
+ /**
2896
+ * Single WebSocket-upgrade fetch attempt. Owns its own AbortController so
2897
+ * each retry gets a fresh per-attempt connect timeout independent of the
2898
+ * total retry budget.
2899
+ */
2900
+ async fetchUpgradeAttempt() {
2901
+ const controller = new AbortController();
2902
+ const timeout = setTimeout(() => controller.abort(), DEFAULT_CONNECT_TIMEOUT_MS);
2903
+ try {
2904
+ const url = `http://localhost:${this.port}/rpc`;
2905
+ const request = new Request(url, {
2906
+ headers: {
2907
+ Upgrade: "websocket",
2908
+ Connection: "Upgrade"
2909
+ },
2910
+ signal: controller.signal
2911
+ });
2912
+ return await this.containerStub.fetch(request);
2913
+ } finally {
2914
+ clearTimeout(timeout);
2915
+ }
2916
+ }
2917
+ sleep(ms) {
2918
+ return new Promise((resolve) => setTimeout(resolve, ms));
2919
+ }
2868
2920
  };
2869
2921
  /**
2870
2922
  * RPC transport that queues sends and blocks receives until a WebSocket
@@ -3093,7 +3145,8 @@ var ContainerControlClient = class {
3093
3145
  this.connOptions = {
3094
3146
  stub: options.stub,
3095
3147
  port: options.port,
3096
- logger: options.logger
3148
+ logger: options.logger,
3149
+ retryTimeoutMs: options.retryTimeoutMs
3097
3150
  };
3098
3151
  this.idleDisconnectMs = options.idleDisconnectMs ?? DEFAULT_IDLE_DISCONNECT_MS;
3099
3152
  this.busyPollIntervalMs = options.busyPollIntervalMs ?? BUSY_POLL_INTERVAL_MS;
@@ -3226,7 +3279,15 @@ var ContainerControlClient = class {
3226
3279
  get interpreter() {
3227
3280
  return wrapStub(this.getConnection().rpc().interpreter, this.renewActivity);
3228
3281
  }
3229
- setRetryTimeoutMs(_ms) {}
3282
+ /**
3283
+ * Update the 503 upgrade-retry budget. Applies to the current connection
3284
+ * (if any) and is remembered for any future connections created after the
3285
+ * client is torn down and reconnected.
3286
+ */
3287
+ setRetryTimeoutMs(ms) {
3288
+ this.connOptions.retryTimeoutMs = ms;
3289
+ this.conn?.setRetryTimeoutMs(ms);
3290
+ }
3230
3291
  getTransportMode() {
3231
3292
  return "rpc";
3232
3293
  }
@@ -4196,7 +4257,7 @@ function isLocalhostPattern(hostname) {
4196
4257
  * This file is auto-updated by .github/changeset-version.ts during releases
4197
4258
  * DO NOT EDIT MANUALLY - Changes will be overwritten on the next version bump
4198
4259
  */
4199
- const SDK_VERSION = "0.9.3";
4260
+ const SDK_VERSION = "0.10.0";
4200
4261
 
4201
4262
  //#endregion
4202
4263
  //#region src/sandbox.ts
@@ -4528,6 +4589,7 @@ var Sandbox = class Sandbox extends Container {
4528
4589
  stub: this,
4529
4590
  port: 3e3,
4530
4591
  logger: this.logger,
4592
+ retryTimeoutMs: this.computeRetryTimeoutMs(),
4531
4593
  onActivity: () => {
4532
4594
  this.renewActivityTimeout();
4533
4595
  },
@@ -7170,9 +7232,18 @@ var Sandbox = class Sandbox extends Container {
7170
7232
  sizeBytes = createResult.sizeBytes;
7171
7233
  const r2Key = `${BACKUP_STORAGE_PREFIX}/${backupId}/${BACKUP_ARCHIVE_OBJECT_NAME}`;
7172
7234
  const metaKey = `${BACKUP_STORAGE_PREFIX}/${backupId}/${BACKUP_METADATA_OBJECT_NAME}`;
7173
- const { content } = await collectFile(await this.client.files.readFileStream(archivePath, backupSession));
7174
- const archiveData = content instanceof Uint8Array ? content : new TextEncoder().encode(content);
7175
- await bucket.put(r2Key, archiveData);
7235
+ const archiveStream = await this.client.files.readFileStream(archivePath, backupSession);
7236
+ const sseDecoded = new ReadableStream({ async start(controller) {
7237
+ try {
7238
+ for await (const chunk of streamFile(archiveStream)) if (chunk instanceof Uint8Array) controller.enqueue(chunk);
7239
+ controller.close();
7240
+ } catch (err) {
7241
+ controller.error(err);
7242
+ }
7243
+ } });
7244
+ const fixedStream = new FixedLengthStream(createResult.sizeBytes);
7245
+ sseDecoded.pipeTo(fixedStream.writable).catch(() => {});
7246
+ await bucket.put(r2Key, fixedStream.readable);
7176
7247
  const head = await bucket.head(r2Key);
7177
7248
  if (!head || head.size !== createResult.sizeBytes) throw new BackupCreateError({
7178
7249
  message: `Upload verification failed: expected ${createResult.sizeBytes} bytes, got ${head?.size ?? 0}`,
@@ -7450,20 +7521,35 @@ var Sandbox = class Sandbox extends Container {
7450
7521
  });
7451
7522
  backupSession = await this.ensureBackupSession();
7452
7523
  const archivePath = `${BACKUP_CONTAINER_DIR}/${id}.sqsh`;
7453
- const archiveBuffer = await archiveObject.arrayBuffer();
7454
- const base64Content = Buffer.from(archiveBuffer).toString("base64");
7455
7524
  await this.execWithSession(`mkdir -p ${BACKUP_CONTAINER_DIR}`, backupSession, { origin: "internal" });
7456
- const writeResult = await this.client.files.writeFile(archivePath, base64Content, backupSession, { encoding: "base64" });
7457
- if (!writeResult.success) throw new BackupRestoreError({
7458
- message: `Failed to write backup archive to ${archivePath}: ${"error" in writeResult && typeof writeResult.error === "object" && writeResult.error !== null && "message" in writeResult.error && typeof writeResult.error.message === "string" ? writeResult.error.message : `File write returned success: false for '${archivePath}'`}`,
7459
- code: ErrorCode.BACKUP_RESTORE_FAILED,
7460
- httpStatus: 500,
7461
- context: {
7462
- dir,
7463
- backupId: id
7464
- },
7465
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
7466
- });
7525
+ if (this.transport === "rpc") {
7526
+ const body = archiveObject.body;
7527
+ if (!body) throw new BackupRestoreError({
7528
+ message: `R2 archive object has no body stream for backup ${id}`,
7529
+ code: ErrorCode.BACKUP_RESTORE_FAILED,
7530
+ httpStatus: 500,
7531
+ context: {
7532
+ dir,
7533
+ backupId: id
7534
+ },
7535
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
7536
+ });
7537
+ await this.client.writeFileStream(archivePath, body, backupSession);
7538
+ } else {
7539
+ const archiveBuffer = await archiveObject.arrayBuffer();
7540
+ const base64Content = Buffer.from(archiveBuffer).toString("base64");
7541
+ const writeResult = await this.client.files.writeFile(archivePath, base64Content, backupSession, { encoding: "base64" });
7542
+ if (!writeResult.success) throw new BackupRestoreError({
7543
+ message: `Failed to write backup archive to ${archivePath}: ${"error" in writeResult && typeof writeResult.error === "object" && writeResult.error !== null && "message" in writeResult.error && typeof writeResult.error.message === "string" ? writeResult.error.message : `File write returned success: false for '${archivePath}'`}`,
7544
+ code: ErrorCode.BACKUP_RESTORE_FAILED,
7545
+ httpStatus: 500,
7546
+ context: {
7547
+ dir,
7548
+ backupId: id
7549
+ },
7550
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
7551
+ });
7552
+ }
7467
7553
  const extractResult = await this.execWithSession(`/usr/bin/unsquashfs -f -d ${shellEscape(dir)} ${shellEscape(archivePath)}`, backupSession, { origin: "internal" });
7468
7554
  if (extractResult.exitCode !== 0) throw new BackupRestoreError({
7469
7555
  message: `unsquashfs extraction failed (exit code ${extractResult.exitCode}): ${extractResult.stderr}`,
@@ -7506,4 +7592,4 @@ var Sandbox = class Sandbox extends Container {
7506
7592
 
7507
7593
  //#endregion
7508
7594
  export { DesktopInvalidOptionsError as A, CommandClient as C, BackupNotFoundError as D, BackupExpiredError as E, InvalidBackupConfigError as F, ProcessExitedBeforeReadyError as I, ProcessReadyTimeoutError as L, DesktopProcessCrashedError as M, DesktopStartFailedError as N, BackupRestoreError as O, DesktopUnavailableError as P, RPCTransportError as R, DesktopClient as S, BackupCreateError as T, UtilityClient as _, BucketMountError as a, GitClient as b, MissingCredentialsError as c, parseSSEStream as d, responseToAsyncIterable as f, SandboxClient as g, streamFile as h, proxyTerminal as i, DesktopNotStartedError as j, DesktopInvalidCoordinatesError as k, S3FSMountError as l, collectFile as m, getSandbox as n, BucketUnmountError as o, CodeInterpreter as p, proxyToSandbox as r, InvalidMountConfigError as s, Sandbox as t, asyncIterableToSSEStream as u, ProcessClient as v, BackupClient as w, FileClient as x, PortClient as y, SessionTerminatedError as z };
7509
- //# sourceMappingURL=sandbox-BAuU-2a0.js.map
7595
+ //# sourceMappingURL=sandbox-2bHZZmy5.js.map