@cloudflare/sandbox 0.7.12 → 0.7.14

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
@@ -199,6 +199,12 @@ ENTRYPOINT ["/container-server/sandbox"]
199
199
  # ============================================================================
200
200
  FROM runtime-base AS opencode
201
201
 
202
+ RUN --mount=type=secret,id=wrangler_ca \
203
+ if [ -f /run/secrets/wrangler_ca ] && [ -s /run/secrets/wrangler_ca ]; then \
204
+ cp /run/secrets/wrangler_ca /usr/local/share/ca-certificates/wrangler-dev-ca.crt && \
205
+ update-ca-certificates; \
206
+ fi
207
+
202
208
  # Install OpenCode CLI via npm (avoids GitHub API rate limits)
203
209
  RUN npm i -g opencode-ai \
204
210
  && opencode --version
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as DirectoryBackup, A as DesktopStopResponse, At as WaitForPortOptions, B as ExecuteResponse, Bt as CreateContextOptions, C as ClickOptions, Ct as ProcessStartResult, D as DesktopStartOptions, Dt as SessionOptions, E as DesktopClient, Et as SandboxOptions, F as ScreenshotRegion, Ft as ExecuteRequest, G as HttpClientOptions, H as BaseApiResponse, Ht as ExecutionResult, I as ScreenshotResponse, It as ExposePortRequest, J as SessionRequest, K as RequestConfig, L as ScrollDirection, Lt as StartProcessRequest, M as ScreenSizeResponse, Mt as isExecResult, N as ScreenshotBytesResponse, Nt as isProcess, O as DesktopStartResponse, Ot as StreamOptions, P as ScreenshotOptions, Pt as isProcessStatus, Q as BucketProvider, R as TypeOptions, Rt as PtyOptions, S as WriteFileRequest, St as ProcessOptions, T as Desktop, Tt as RestoreBackupResult, U as ContainerStub, Ut as RunCodeOptions, V as BackupClient, Vt as Execution, W as ErrorResponse, X as BaseExecOptions, Y as BackupOptions, Z as BucketCredentials, _ as GitClient, _t as ProcessCleanupResult, a as CreateSessionRequest, at as FileMetadata, b as MkdirRequest, bt as ProcessListResult, c as DeleteSessionResponse, ct as GitCheckoutResult, d as ProcessClient, dt as LogEvent, et as ExecEvent, f as PortClient, ft as MountBucketOptions, g as GitCheckoutRequest, gt as Process, h as InterpreterClient, ht as PortListResult, i as CommandsResponse, it as FileChunk, j as KeyInput, jt as WatchOptions, k as DesktopStatusResponse, kt as WaitForLogResult, l as PingResponse, lt as ISandbox, m as ExecutionCallbacks, mt as PortExposeResult, n as getSandbox, nt as ExecResult, o as CreateSessionResponse, ot as FileStreamEvent, p as UnexposePortRequest, pt as PortCloseResult, q as ResponseHandler, r as SandboxClient, rt as ExecutionSession, s as DeleteSessionRequest, st as FileWatchSSEEvent, t as Sandbox, tt as ExecOptions, u as UtilityClient, ut as ListFilesOptions, v as FileClient, vt as ProcessInfoResult, w as CursorPositionResponse, wt as ProcessStatus, x as ReadFileRequest, xt as ProcessLogsResult, y as FileOperationRequest, yt as ProcessKillResult, z as CommandClient, zt as CodeContext } from "./sandbox-Buy5jfCP.js";
1
+ import { $ as DirectoryBackup, A as DesktopStopResponse, At as WaitForPortOptions, B as ExecuteResponse, Bt as CreateContextOptions, C as ClickOptions, Ct as ProcessStartResult, D as DesktopStartOptions, Dt as SessionOptions, E as DesktopClient, Et as SandboxOptions, F as ScreenshotRegion, Ft as ExecuteRequest, G as HttpClientOptions, H as BaseApiResponse, Ht as ExecutionResult, I as ScreenshotResponse, It as ExposePortRequest, J as SessionRequest, K as RequestConfig, L as ScrollDirection, Lt as StartProcessRequest, M as ScreenSizeResponse, Mt as isExecResult, N as ScreenshotBytesResponse, Nt as isProcess, O as DesktopStartResponse, Ot as StreamOptions, P as ScreenshotOptions, Pt as isProcessStatus, Q as BucketProvider, R as TypeOptions, Rt as PtyOptions, S as WriteFileRequest, St as ProcessOptions, T as Desktop, Tt as RestoreBackupResult, U as ContainerStub, Ut as RunCodeOptions, V as BackupClient, Vt as Execution, W as ErrorResponse, X as BaseExecOptions, Y as BackupOptions, Z as BucketCredentials, _ as GitClient, _t as ProcessCleanupResult, a as CreateSessionRequest, at as FileMetadata, b as MkdirRequest, bt as ProcessListResult, c as DeleteSessionResponse, ct as GitCheckoutResult, d as ProcessClient, dt as LogEvent, et as ExecEvent, f as PortClient, ft as MountBucketOptions, g as GitCheckoutRequest, gt as Process, h as InterpreterClient, ht as PortListResult, i as CommandsResponse, it as FileChunk, j as KeyInput, jt as WatchOptions, k as DesktopStatusResponse, kt as WaitForLogResult, l as PingResponse, lt as ISandbox, m as ExecutionCallbacks, mt as PortExposeResult, n as getSandbox, nt as ExecResult, o as CreateSessionResponse, ot as FileStreamEvent, p as UnexposePortRequest, pt as PortCloseResult, q as ResponseHandler, r as SandboxClient, rt as ExecutionSession, s as DeleteSessionRequest, st as FileWatchSSEEvent, t as Sandbox, tt as ExecOptions, u as UtilityClient, ut as ListFilesOptions, v as FileClient, vt as ProcessInfoResult, w as CursorPositionResponse, wt as ProcessStatus, x as ReadFileRequest, xt as ProcessLogsResult, y as FileOperationRequest, yt as ProcessKillResult, z as CommandClient, zt as CodeContext } from "./sandbox-CUee1P3v.js";
2
2
  import { a as DesktopCoordinateErrorContext, d as ErrorResponse$1, f as OperationType, i as BackupRestoreContext, l as ProcessExitedBeforeReadyContext, n as BackupExpiredContext, o as DesktopErrorContext, p as ErrorCode, r as BackupNotFoundContext, s as InvalidBackupConfigContext, t as BackupCreateContext, u as ProcessReadyTimeoutContext } from "./contexts-CeQR115r.js";
3
3
 
4
4
  //#region src/errors/classes.d.ts
package/dist/index.js CHANGED
@@ -1123,7 +1123,22 @@ var WebSocketTransport = class extends BaseTransport {
1123
1123
  if (!firstMessageReceived) {
1124
1124
  firstMessageReceived = true;
1125
1125
  const pending = this.pendingRequests.get(id);
1126
- if (pending) pending.streamController = streamController;
1126
+ if (pending) {
1127
+ pending.streamController = streamController;
1128
+ if (pending.bufferedChunks) {
1129
+ try {
1130
+ for (const buffered of pending.bufferedChunks) streamController.enqueue(buffered);
1131
+ } catch (error) {
1132
+ this.logger.debug("Failed to flush buffered chunks, cleaning up", {
1133
+ id,
1134
+ error: error instanceof Error ? error.message : String(error)
1135
+ });
1136
+ if (pending.timeoutId) clearTimeout(pending.timeoutId);
1137
+ this.pendingRequests.delete(id);
1138
+ }
1139
+ pending.bufferedChunks = void 0;
1140
+ }
1141
+ }
1127
1142
  resolveStream(stream);
1128
1143
  }
1129
1144
  }
@@ -1194,7 +1209,12 @@ var WebSocketTransport = class extends BaseTransport {
1194
1209
  pending.onFirstChunk = void 0;
1195
1210
  }
1196
1211
  if (!pending.streamController) {
1197
- this.logger.warn("Stream chunk received but controller not ready", { id: chunk.id });
1212
+ if (!pending.bufferedChunks) pending.bufferedChunks = [];
1213
+ const encoder$1 = new TextEncoder();
1214
+ let sseData$1;
1215
+ if (chunk.event) sseData$1 = `event: ${chunk.event}\ndata: ${chunk.data}\n\n`;
1216
+ else sseData$1 = `data: ${chunk.data}\n\n`;
1217
+ pending.bufferedChunks.push(encoder$1.encode(sseData$1));
1198
1218
  return;
1199
1219
  }
1200
1220
  const encoder = new TextEncoder();
@@ -2975,6 +2995,7 @@ async function proxyTerminal(stub, sessionId, request, options) {
2975
2995
  const params = new URLSearchParams({ sessionId });
2976
2996
  if (options?.cols) params.set("cols", String(options.cols));
2977
2997
  if (options?.rows) params.set("rows", String(options.rows));
2998
+ if (options?.shell) params.set("shell", options.shell);
2978
2999
  const ptyUrl = `http://localhost/ws/pty?${params}`;
2979
3000
  const ptyRequest = new Request(ptyUrl, request);
2980
3001
  return stub.fetch(switchPort(ptyRequest, 3e3));
@@ -3328,7 +3349,7 @@ function buildS3fsSource(bucket, prefix) {
3328
3349
  * This file is auto-updated by .github/changeset-version.ts during releases
3329
3350
  * DO NOT EDIT MANUALLY - Changes will be overwritten on the next version bump
3330
3351
  */
3331
- const SDK_VERSION = "0.7.12";
3352
+ const SDK_VERSION = "0.7.14";
3332
3353
 
3333
3354
  //#endregion
3334
3355
  //#region src/sandbox.ts
@@ -3569,6 +3590,7 @@ var Sandbox = class Sandbox extends Container {
3569
3590
  async setKeepAlive(keepAlive) {
3570
3591
  this.keepAliveEnabled = keepAlive;
3571
3592
  await this.ctx.storage.put("keepAliveEnabled", keepAlive);
3593
+ if (!keepAlive) this.renewActivityTimeout();
3572
3594
  }
3573
3595
  async setEnvVars(envVars) {
3574
3596
  const { toSet, toUnset } = partitionEnvVars(envVars);
@@ -3848,22 +3870,84 @@ var Sandbox = class Sandbox extends Container {
3848
3870
  }
3849
3871
  });
3850
3872
  } catch (e) {
3851
- if (this.isNoInstanceError(e)) return new Response("Container is currently provisioning. This can take several minutes on first deployment. Please retry in a moment.", {
3852
- status: 503,
3853
- headers: { "Retry-After": "10" }
3854
- });
3873
+ if (this.isNoInstanceError(e)) {
3874
+ const errorBody$1 = {
3875
+ code: ErrorCode.INTERNAL_ERROR,
3876
+ message: "Container is currently provisioning. This can take several minutes on first deployment.",
3877
+ context: { phase: "provisioning" },
3878
+ httpStatus: 503,
3879
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3880
+ suggestion: "This is expected during first deployment. The SDK will retry automatically."
3881
+ };
3882
+ return new Response(JSON.stringify(errorBody$1), {
3883
+ status: 503,
3884
+ headers: {
3885
+ "Content-Type": "application/json",
3886
+ "Retry-After": "10"
3887
+ }
3888
+ });
3889
+ }
3890
+ if (this.isPermanentStartupError(e)) {
3891
+ this.logger.error("Permanent container startup error, returning 500", e instanceof Error ? e : new Error(String(e)));
3892
+ const errorBody$1 = {
3893
+ code: ErrorCode.INTERNAL_ERROR,
3894
+ message: "Container failed to start due to a permanent error. Check your container configuration.",
3895
+ context: {
3896
+ phase: "startup",
3897
+ error: e instanceof Error ? e.message : String(e)
3898
+ },
3899
+ httpStatus: 500,
3900
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3901
+ suggestion: "This error will not resolve with retries. Check container logs, image name, and resource limits."
3902
+ };
3903
+ return new Response(JSON.stringify(errorBody$1), {
3904
+ status: 500,
3905
+ headers: { "Content-Type": "application/json" }
3906
+ });
3907
+ }
3855
3908
  if (this.isTransientStartupError(e)) {
3856
3909
  if (staleStateDetected) {
3857
3910
  this.logger.warn("Container startup failed after stale state detection, aborting DO for recovery", { error: e instanceof Error ? e.message : String(e) });
3858
3911
  this.ctx.abort();
3859
3912
  } else this.logger.debug("Transient container startup error, returning 503", { error: e instanceof Error ? e.message : String(e) });
3860
- return new Response("Container is starting. Please retry in a moment.", {
3913
+ const errorBody$1 = {
3914
+ code: ErrorCode.INTERNAL_ERROR,
3915
+ message: "Container is starting. Please retry in a moment.",
3916
+ context: {
3917
+ phase: "startup",
3918
+ error: e instanceof Error ? e.message : String(e)
3919
+ },
3920
+ httpStatus: 503,
3921
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3922
+ suggestion: "The container is booting. The SDK will retry automatically."
3923
+ };
3924
+ return new Response(JSON.stringify(errorBody$1), {
3861
3925
  status: 503,
3862
- headers: { "Retry-After": "3" }
3926
+ headers: {
3927
+ "Content-Type": "application/json",
3928
+ "Retry-After": "3"
3929
+ }
3863
3930
  });
3864
3931
  }
3865
- this.logger.error("Container startup failed with permanent error", e instanceof Error ? e : new Error(String(e)));
3866
- return new Response(`Failed to start container: ${e instanceof Error ? e.message : String(e)}`, { status: 500 });
3932
+ this.logger.warn("Unrecognized container startup error, returning 503 for retry", { error: e instanceof Error ? e.message : String(e) });
3933
+ const errorBody = {
3934
+ code: ErrorCode.INTERNAL_ERROR,
3935
+ message: "Container is starting. Please retry in a moment.",
3936
+ context: {
3937
+ phase: "startup",
3938
+ error: e instanceof Error ? e.message : String(e)
3939
+ },
3940
+ httpStatus: 503,
3941
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3942
+ suggestion: "The SDK will retry automatically. If this persists, the container may need redeployment."
3943
+ };
3944
+ return new Response(JSON.stringify(errorBody), {
3945
+ status: 503,
3946
+ headers: {
3947
+ "Content-Type": "application/json",
3948
+ "Retry-After": "5"
3949
+ }
3950
+ });
3867
3951
  }
3868
3952
  }
3869
3953
  return await super.containerFetch(requestOrUrl, portOrInit, portParam);
@@ -3909,6 +3993,30 @@ var Sandbox = class Sandbox extends Container {
3909
3993
  ].some((pattern) => msg.includes(pattern));
3910
3994
  }
3911
3995
  /**
3996
+ * Helper: Check if error is a permanent startup failure that will never recover
3997
+ *
3998
+ * These errors indicate resource exhaustion, misconfiguration, or missing images.
3999
+ * Retrying will never succeed, so the SDK should fail fast with HTTP 500.
4000
+ *
4001
+ * Error sources (traced from platform internals):
4002
+ * - Container runtime: OOM, PID limit
4003
+ * - Scheduling/provisioning: no matching app, no namespace configured
4004
+ * - workerd container-client.c++: no such image
4005
+ * - @cloudflare/containers: did not call start
4006
+ */
4007
+ isPermanentStartupError(error) {
4008
+ if (!(error instanceof Error)) return false;
4009
+ const msg = error.message.toLowerCase();
4010
+ return [
4011
+ "ran out of memory",
4012
+ "too many subprocesses",
4013
+ "no application that matches",
4014
+ "no container application assigned",
4015
+ "no such image",
4016
+ "did not call start"
4017
+ ].some((pattern) => msg.includes(pattern));
4018
+ }
4019
+ /**
3912
4020
  * Helper: Parse containerFetch arguments (supports multiple signatures)
3913
4021
  */
3914
4022
  parseContainerFetchArgs(requestOrUrl, portOrInit, portParam) {
@@ -4168,9 +4276,6 @@ var Sandbox = class Sandbox extends Container {
4168
4276
  }
4169
4277
  try {
4170
4278
  const streamProcessor = async () => {
4171
- const DEBOUNCE_MS = 50;
4172
- let lastCheckTime = 0;
4173
- let pendingCheck = false;
4174
4279
  const checkPattern = () => {
4175
4280
  const stdoutResult = this.matchPattern(collectedStdout, pattern);
4176
4281
  if (stdoutResult) return stdoutResult;
@@ -4183,27 +4288,17 @@ var Sandbox = class Sandbox extends Container {
4183
4288
  const data = event.data || "";
4184
4289
  if (event.type === "stdout") collectedStdout += data;
4185
4290
  else collectedStderr += data;
4186
- pendingCheck = true;
4187
- const now = Date.now();
4188
- if (now - lastCheckTime >= DEBOUNCE_MS) {
4189
- lastCheckTime = now;
4190
- pendingCheck = false;
4191
- const result = checkPattern();
4192
- if (result) return result;
4193
- }
4291
+ const result = checkPattern();
4292
+ if (result) return result;
4194
4293
  }
4195
4294
  if (event.type === "exit") {
4196
- if (pendingCheck) {
4197
- const result = checkPattern();
4198
- if (result) return result;
4199
- }
4295
+ const result = checkPattern();
4296
+ if (result) return result;
4200
4297
  throw this.createExitedBeforeReadyError(processId, command, conditionStr, event.exitCode ?? 1);
4201
4298
  }
4202
4299
  }
4203
- if (pendingCheck) {
4204
- const result = checkPattern();
4205
- if (result) return result;
4206
- }
4300
+ const finalResult = checkPattern();
4301
+ if (finalResult) return finalResult;
4207
4302
  throw this.createExitedBeforeReadyError(processId, command, conditionStr, 0);
4208
4303
  };
4209
4304
  if (timeoutPromise) return await Promise.race([streamProcessor(), timeoutPromise]);