@cloudflare/sandbox 0.6.4 → 0.6.6
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 +20 -25
- package/README.md +6 -2
- package/dist/{contexts-DeQQjsXq.d.ts → contexts-CdrlvHWK.d.ts} +2 -1
- package/dist/contexts-CdrlvHWK.d.ts.map +1 -0
- package/dist/{errors-BHN41iBd.js → errors-BCXUmJUn.js} +3 -1
- package/dist/errors-BCXUmJUn.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +80 -46
- package/dist/index.js.map +1 -1
- package/dist/openai/index.d.ts +1 -1
- package/dist/opencode/index.d.ts +2 -2
- package/dist/opencode/index.js +1 -1
- package/dist/{sandbox-C9WRqWBO.d.ts → sandbox-09Ce7yli.d.ts} +44 -7
- package/dist/sandbox-09Ce7yli.d.ts.map +1 -0
- package/package.json +4 -3
- package/dist/contexts-DeQQjsXq.d.ts.map +0 -1
- package/dist/errors-BHN41iBd.js.map +0 -1
- package/dist/sandbox-C9WRqWBO.d.ts.map +0 -1
- package/startup.sh +0 -3
package/Dockerfile
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
# Sandbox container
|
|
1
|
+
# Sandbox container images (default and python variants)
|
|
2
2
|
# Multi-stage build optimized for Turborepo monorepo
|
|
3
3
|
|
|
4
4
|
# ============================================================================
|
|
5
5
|
# Stage 1: Prune monorepo to only include necessary packages
|
|
6
6
|
# ============================================================================
|
|
7
|
-
FROM node:20-
|
|
7
|
+
FROM node:20-slim AS pruner
|
|
8
8
|
|
|
9
9
|
WORKDIR /app
|
|
10
10
|
|
|
11
|
-
# Install Turborepo globally
|
|
12
11
|
RUN npm install -g turbo
|
|
13
12
|
|
|
14
|
-
# Copy entire monorepo
|
|
15
13
|
COPY . .
|
|
16
14
|
|
|
17
15
|
# Prune to only @repo/sandbox-container and its dependencies (@repo/shared)
|
|
@@ -20,13 +18,15 @@ RUN turbo prune @repo/sandbox-container --docker
|
|
|
20
18
|
|
|
21
19
|
# ============================================================================
|
|
22
20
|
# Stage 2: Install dependencies and build packages
|
|
21
|
+
# Using glibc-based images (not Alpine) so the standalone binary works on
|
|
22
|
+
# standard Linux distributions (Debian, Ubuntu, RHEL, etc.)
|
|
23
23
|
# ============================================================================
|
|
24
|
-
FROM node:20-
|
|
24
|
+
FROM node:20-slim AS builder
|
|
25
25
|
|
|
26
26
|
WORKDIR /app
|
|
27
27
|
|
|
28
|
-
# Install Bun runtime (
|
|
29
|
-
COPY --from=oven/bun:1
|
|
28
|
+
# Install Bun runtime (glibc version for glibc-compatible standalone binary)
|
|
29
|
+
COPY --from=oven/bun:1 /usr/local/bin/bun /usr/local/bin/bun
|
|
30
30
|
|
|
31
31
|
# Copy pruned lockfile and package.json files (for Docker layer caching)
|
|
32
32
|
COPY --from=pruner /app/out/json/ .
|
|
@@ -36,11 +36,10 @@ COPY --from=pruner /app/out/package-lock.json ./package-lock.json
|
|
|
36
36
|
RUN --mount=type=cache,target=/root/.npm \
|
|
37
37
|
CI=true npm ci
|
|
38
38
|
|
|
39
|
-
# Copy pruned source code
|
|
40
39
|
COPY --from=pruner /app/out/full/ .
|
|
41
40
|
|
|
42
41
|
# Build all packages (Turborepo handles dependency order automatically)
|
|
43
|
-
# This builds @repo/shared first, then @repo/sandbox-container
|
|
42
|
+
# This builds @repo/shared first, then @repo/sandbox-container (including standalone binary)
|
|
44
43
|
RUN npx turbo run build
|
|
45
44
|
|
|
46
45
|
# ============================================================================
|
|
@@ -96,7 +95,6 @@ ARG SANDBOX_VERSION=unknown
|
|
|
96
95
|
# Prevent interactive prompts during package installation
|
|
97
96
|
ENV DEBIAN_FRONTEND=noninteractive
|
|
98
97
|
|
|
99
|
-
# Set the sandbox version as an environment variable for version checking
|
|
100
98
|
ENV SANDBOX_VERSION=${SANDBOX_VERSION}
|
|
101
99
|
|
|
102
100
|
# Install runtime packages and S3FS-FUSE for bucket mounting
|
|
@@ -123,27 +121,24 @@ RUN ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm && \
|
|
|
123
121
|
# Install Bun runtime from official image
|
|
124
122
|
COPY --from=oven/bun:1 /usr/local/bin/bun /usr/local/bin/bun
|
|
125
123
|
|
|
126
|
-
#
|
|
127
|
-
|
|
124
|
+
# Copy standalone binary
|
|
125
|
+
COPY --from=builder /app/packages/sandbox-container/dist/sandbox /container-server/sandbox
|
|
128
126
|
|
|
129
|
-
#
|
|
130
|
-
|
|
131
|
-
COPY --from=builder /app/packages/sandbox-container/dist/index.js.map ./dist/
|
|
127
|
+
# Set up container server directory for executors
|
|
128
|
+
WORKDIR /container-server
|
|
132
129
|
|
|
133
|
-
# Copy bundled JavaScript executor
|
|
130
|
+
# Copy bundled JavaScript executor (runs on Node or Bun for code interpreter)
|
|
134
131
|
COPY --from=builder /app/packages/sandbox-container/dist/runtime/executors/javascript/node_executor.js ./dist/runtime/executors/javascript/
|
|
135
|
-
COPY --from=builder /app/packages/sandbox-container/dist/runtime/executors/javascript/node_executor.js.map ./dist/runtime/executors/javascript/
|
|
136
132
|
|
|
137
|
-
#
|
|
133
|
+
# Copy legacy JS bundle for backwards compatibility
|
|
134
|
+
# Users with custom startup scripts that call `bun /container-server/dist/index.js` need this
|
|
135
|
+
COPY --from=builder /app/packages/sandbox-container/dist/index.js ./dist/
|
|
136
|
+
|
|
138
137
|
RUN mkdir -p /workspace
|
|
139
138
|
|
|
140
139
|
# Expose the application port (3000 for control)
|
|
141
140
|
EXPOSE 3000
|
|
142
141
|
|
|
143
|
-
# Copy and make startup script executable
|
|
144
|
-
COPY packages/sandbox/startup.sh ./
|
|
145
|
-
RUN chmod +x startup.sh
|
|
146
|
-
|
|
147
142
|
# ============================================================================
|
|
148
143
|
# Stage 5a: Default image - lean, no Python
|
|
149
144
|
# ============================================================================
|
|
@@ -154,7 +149,7 @@ ENV PYTHON_POOL_MIN_SIZE=0
|
|
|
154
149
|
ENV JAVASCRIPT_POOL_MIN_SIZE=3
|
|
155
150
|
ENV TYPESCRIPT_POOL_MIN_SIZE=3
|
|
156
151
|
|
|
157
|
-
|
|
152
|
+
ENTRYPOINT ["/container-server/sandbox"]
|
|
158
153
|
|
|
159
154
|
# ============================================================================
|
|
160
155
|
# Stage 5b: Python image - full, with Python + data science packages
|
|
@@ -186,7 +181,7 @@ ENV PYTHON_POOL_MIN_SIZE=3
|
|
|
186
181
|
ENV JAVASCRIPT_POOL_MIN_SIZE=3
|
|
187
182
|
ENV TYPESCRIPT_POOL_MIN_SIZE=3
|
|
188
183
|
|
|
189
|
-
|
|
184
|
+
ENTRYPOINT ["/container-server/sandbox"]
|
|
190
185
|
|
|
191
186
|
# ============================================================================
|
|
192
187
|
# Stage 5c: OpenCode image - with OpenCode CLI for AI coding agent
|
|
@@ -210,4 +205,4 @@ ENV TYPESCRIPT_POOL_MIN_SIZE=3
|
|
|
210
205
|
# Expose OpenCode server port (in addition to 3000 from runtime-base)
|
|
211
206
|
EXPOSE 4096
|
|
212
207
|
|
|
213
|
-
|
|
208
|
+
ENTRYPOINT ["/container-server/sandbox"]
|
package/README.md
CHANGED
|
@@ -150,8 +150,12 @@ npm run check
|
|
|
150
150
|
|
|
151
151
|
See the [examples directory](./examples) for complete working examples:
|
|
152
152
|
|
|
153
|
-
- [Minimal](./examples/minimal) -
|
|
154
|
-
- [Code Interpreter](./examples/code-interpreter) -
|
|
153
|
+
- [Minimal](./examples/minimal) - Start here: exec commands, read/write files
|
|
154
|
+
- [Code Interpreter](./examples/code-interpreter) - Give [gpt-oss](https://developers.cloudflare.com/workers-ai/models/gpt-oss-120b/) on Workers AI a Python REPL
|
|
155
|
+
- [Claude Code](./examples/claude-code) - Run [Claude Code](https://claude.ai/code) headless on any repo
|
|
156
|
+
- [OpenAI Agents](./examples/openai-agents) - `Shell` and `Editor` tools for [OpenAI Agents SDK](https://openai.github.io/openai-agents-js/)
|
|
157
|
+
- [OpenCode](./examples/opencode) - [OpenCode](https://github.com/sst/opencode) web UI or [SDK](https://opencode.ai/docs/sdk/) in a sandbox
|
|
158
|
+
- [TypeScript Validator](./examples/typescript-validator) - Build with npm in sandbox, execute in [isolates](https://developers.cloudflare.com/workers/runtime-apis/bindings/worker-loader/)
|
|
155
159
|
|
|
156
160
|
## Status
|
|
157
161
|
|
|
@@ -49,6 +49,7 @@ declare const ErrorCode: {
|
|
|
49
49
|
readonly CONTEXT_NOT_FOUND: "CONTEXT_NOT_FOUND";
|
|
50
50
|
readonly CODE_EXECUTION_ERROR: "CODE_EXECUTION_ERROR";
|
|
51
51
|
readonly PYTHON_NOT_AVAILABLE: "PYTHON_NOT_AVAILABLE";
|
|
52
|
+
readonly JAVASCRIPT_NOT_AVAILABLE: "JAVASCRIPT_NOT_AVAILABLE";
|
|
52
53
|
readonly OPENCODE_STARTUP_FAILED: "OPENCODE_STARTUP_FAILED";
|
|
53
54
|
readonly PROCESS_READY_TIMEOUT: "PROCESS_READY_TIMEOUT";
|
|
54
55
|
readonly PROCESS_EXITED_BEFORE_READY: "PROCESS_EXITED_BEFORE_READY";
|
|
@@ -158,4 +159,4 @@ interface OpencodeStartupContext {
|
|
|
158
159
|
}
|
|
159
160
|
//#endregion
|
|
160
161
|
export { OperationType as a, ErrorResponse as i, ProcessExitedBeforeReadyContext as n, ErrorCode as o, ProcessReadyTimeoutContext as r, OpencodeStartupContext as t };
|
|
161
|
-
//# sourceMappingURL=contexts-
|
|
162
|
+
//# sourceMappingURL=contexts-CdrlvHWK.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contexts-CdrlvHWK.d.ts","names":["ErrorCode","ErrorCode","Operation","OperationType","ErrorResponse","TContext","Record","ServiceError","ServiceResult","T","OperationType","FileNotFoundContext","FileExistsContext","FileSystemContext","CommandNotFoundContext","CommandErrorContext","ProcessNotFoundContext","ProcessErrorContext","SessionAlreadyExistsContext","ProcessReadyTimeoutContext","ProcessExitedBeforeReadyContext","PortAlreadyExposedContext","PortNotExposedContext","InvalidPortContext","PortErrorContext","GitRepositoryNotFoundContext","GitAuthFailedContext","GitBranchNotFoundContext","GitErrorContext","InterpreterNotReadyContext","ContextNotFoundContext","CodeExecutionContext","ValidationFailedContext","Array","BucketMountContext","MissingCredentialsContext","InvalidMountConfigContext","OpencodeStartupContext","InternalErrorContext"],"sources":["../../shared/dist/errors/codes.d.ts","../../shared/dist/errors/types.d.ts","../../shared/dist/errors/contexts.d.ts"],"sourcesContent":["/**\n * Centralized error code registry\n * Each code maps to a specific error type with consistent semantics\n */\nexport declare const ErrorCode: {\n readonly FILE_NOT_FOUND: \"FILE_NOT_FOUND\";\n readonly PERMISSION_DENIED: \"PERMISSION_DENIED\";\n readonly FILE_EXISTS: \"FILE_EXISTS\";\n readonly IS_DIRECTORY: \"IS_DIRECTORY\";\n readonly NOT_DIRECTORY: \"NOT_DIRECTORY\";\n readonly NO_SPACE: \"NO_SPACE\";\n readonly TOO_MANY_FILES: \"TOO_MANY_FILES\";\n readonly RESOURCE_BUSY: \"RESOURCE_BUSY\";\n readonly READ_ONLY: \"READ_ONLY\";\n readonly NAME_TOO_LONG: \"NAME_TOO_LONG\";\n readonly TOO_MANY_LINKS: \"TOO_MANY_LINKS\";\n readonly FILESYSTEM_ERROR: \"FILESYSTEM_ERROR\";\n readonly COMMAND_NOT_FOUND: \"COMMAND_NOT_FOUND\";\n readonly COMMAND_PERMISSION_DENIED: \"COMMAND_PERMISSION_DENIED\";\n readonly INVALID_COMMAND: \"INVALID_COMMAND\";\n readonly COMMAND_EXECUTION_ERROR: \"COMMAND_EXECUTION_ERROR\";\n readonly STREAM_START_ERROR: \"STREAM_START_ERROR\";\n readonly PROCESS_NOT_FOUND: \"PROCESS_NOT_FOUND\";\n readonly PROCESS_PERMISSION_DENIED: \"PROCESS_PERMISSION_DENIED\";\n readonly PROCESS_ERROR: \"PROCESS_ERROR\";\n readonly SESSION_ALREADY_EXISTS: \"SESSION_ALREADY_EXISTS\";\n readonly PORT_ALREADY_EXPOSED: \"PORT_ALREADY_EXPOSED\";\n readonly PORT_IN_USE: \"PORT_IN_USE\";\n readonly PORT_NOT_EXPOSED: \"PORT_NOT_EXPOSED\";\n readonly INVALID_PORT_NUMBER: \"INVALID_PORT_NUMBER\";\n readonly INVALID_PORT: \"INVALID_PORT\";\n readonly SERVICE_NOT_RESPONDING: \"SERVICE_NOT_RESPONDING\";\n readonly PORT_OPERATION_ERROR: \"PORT_OPERATION_ERROR\";\n readonly CUSTOM_DOMAIN_REQUIRED: \"CUSTOM_DOMAIN_REQUIRED\";\n readonly GIT_REPOSITORY_NOT_FOUND: \"GIT_REPOSITORY_NOT_FOUND\";\n readonly GIT_BRANCH_NOT_FOUND: \"GIT_BRANCH_NOT_FOUND\";\n readonly GIT_AUTH_FAILED: \"GIT_AUTH_FAILED\";\n readonly GIT_NETWORK_ERROR: \"GIT_NETWORK_ERROR\";\n readonly INVALID_GIT_URL: \"INVALID_GIT_URL\";\n readonly GIT_CLONE_FAILED: \"GIT_CLONE_FAILED\";\n readonly GIT_CHECKOUT_FAILED: \"GIT_CHECKOUT_FAILED\";\n readonly GIT_OPERATION_FAILED: \"GIT_OPERATION_FAILED\";\n readonly BUCKET_MOUNT_ERROR: \"BUCKET_MOUNT_ERROR\";\n readonly S3FS_MOUNT_ERROR: \"S3FS_MOUNT_ERROR\";\n readonly MISSING_CREDENTIALS: \"MISSING_CREDENTIALS\";\n readonly INVALID_MOUNT_CONFIG: \"INVALID_MOUNT_CONFIG\";\n readonly INTERPRETER_NOT_READY: \"INTERPRETER_NOT_READY\";\n readonly CONTEXT_NOT_FOUND: \"CONTEXT_NOT_FOUND\";\n readonly CODE_EXECUTION_ERROR: \"CODE_EXECUTION_ERROR\";\n readonly PYTHON_NOT_AVAILABLE: \"PYTHON_NOT_AVAILABLE\";\n readonly JAVASCRIPT_NOT_AVAILABLE: \"JAVASCRIPT_NOT_AVAILABLE\";\n readonly OPENCODE_STARTUP_FAILED: \"OPENCODE_STARTUP_FAILED\";\n readonly PROCESS_READY_TIMEOUT: \"PROCESS_READY_TIMEOUT\";\n readonly PROCESS_EXITED_BEFORE_READY: \"PROCESS_EXITED_BEFORE_READY\";\n readonly VALIDATION_FAILED: \"VALIDATION_FAILED\";\n readonly INVALID_JSON_RESPONSE: \"INVALID_JSON_RESPONSE\";\n readonly UNKNOWN_ERROR: \"UNKNOWN_ERROR\";\n readonly INTERNAL_ERROR: \"INTERNAL_ERROR\";\n};\nexport type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];\n//# sourceMappingURL=codes.d.ts.map","import type { ErrorCode } from './codes';\n/**\n * Standard operation types\n */\nexport declare const Operation: {\n readonly FILE_READ: \"file.read\";\n readonly FILE_WRITE: \"file.write\";\n readonly FILE_DELETE: \"file.delete\";\n readonly FILE_MOVE: \"file.move\";\n readonly FILE_RENAME: \"file.rename\";\n readonly FILE_STAT: \"file.stat\";\n readonly DIRECTORY_CREATE: \"directory.create\";\n readonly DIRECTORY_LIST: \"directory.list\";\n readonly COMMAND_EXECUTE: \"command.execute\";\n readonly COMMAND_STREAM: \"command.stream\";\n readonly PROCESS_START: \"process.start\";\n readonly PROCESS_KILL: \"process.kill\";\n readonly PROCESS_LIST: \"process.list\";\n readonly PROCESS_GET: \"process.get\";\n readonly PROCESS_LOGS: \"process.logs\";\n readonly PORT_EXPOSE: \"port.expose\";\n readonly PORT_UNEXPOSE: \"port.unexpose\";\n readonly PORT_LIST: \"port.list\";\n readonly PORT_PROXY: \"port.proxy\";\n readonly GIT_CLONE: \"git.clone\";\n readonly GIT_CHECKOUT: \"git.checkout\";\n readonly GIT_OPERATION: \"git.operation\";\n readonly CODE_EXECUTE: \"code.execute\";\n readonly CODE_CONTEXT_CREATE: \"code.context.create\";\n readonly CODE_CONTEXT_DELETE: \"code.context.delete\";\n};\nexport type OperationType = (typeof Operation)[keyof typeof Operation];\n/**\n * Standard error response format with generic context type\n * TContext allows type-safe access to error-specific context\n */\nexport interface ErrorResponse<TContext = Record<string, unknown>> {\n /**\n * Error type code (machine-readable)\n */\n code: ErrorCode;\n /**\n * Human-readable error message\n */\n message: string;\n /**\n * Operation that was attempted (useful for debugging and logging)\n */\n operation?: OperationType;\n /**\n * Structured error context with relevant details\n * Type varies based on error code\n */\n context: TContext;\n /**\n * HTTP status code (for client SDK)\n */\n httpStatus: number;\n /**\n * Timestamp when error occurred\n */\n timestamp: string;\n /**\n * Actionable suggestion for fixing the error\n */\n suggestion?: string;\n /**\n * Link to documentation\n */\n documentation?: string;\n}\n/**\n * Container ServiceError (lightweight, enriched by handlers)\n */\nexport interface ServiceError {\n message: string;\n code: ErrorCode;\n details?: Record<string, unknown>;\n}\n/**\n * ServiceResult type for container services\n */\nexport type ServiceResult<T> = {\n success: true;\n data: T;\n} | {\n success: false;\n error: ServiceError;\n};\n//# sourceMappingURL=types.d.ts.map","import type { OperationType } from './types';\n/**\n * File system error contexts\n */\nexport interface FileNotFoundContext {\n path: string;\n operation: OperationType;\n}\nexport interface FileExistsContext {\n path: string;\n operation: OperationType;\n}\nexport interface FileSystemContext {\n path: string;\n operation: OperationType;\n stderr?: string;\n exitCode?: number;\n}\n/**\n * Command error contexts\n */\nexport interface CommandNotFoundContext {\n command: string;\n}\nexport interface CommandErrorContext {\n command: string;\n exitCode?: number;\n stdout?: string;\n stderr?: string;\n}\n/**\n * Process error contexts\n */\nexport interface ProcessNotFoundContext {\n processId: string;\n}\nexport interface ProcessErrorContext {\n processId: string;\n pid?: number;\n exitCode?: number;\n stderr?: string;\n}\nexport interface SessionAlreadyExistsContext {\n sessionId: string;\n}\n/**\n * Process readiness error contexts\n */\nexport interface ProcessReadyTimeoutContext {\n processId: string;\n command: string;\n condition: string;\n timeout: number;\n}\nexport interface ProcessExitedBeforeReadyContext {\n processId: string;\n command: string;\n condition: string;\n exitCode: number;\n}\n/**\n * Port error contexts\n */\nexport interface PortAlreadyExposedContext {\n port: number;\n portName?: string;\n}\nexport interface PortNotExposedContext {\n port: number;\n}\nexport interface InvalidPortContext {\n port: number;\n reason: string;\n}\nexport interface PortErrorContext {\n port: number;\n portName?: string;\n stderr?: string;\n}\n/**\n * Git error contexts\n */\nexport interface GitRepositoryNotFoundContext {\n repository: string;\n}\nexport interface GitAuthFailedContext {\n repository: string;\n}\nexport interface GitBranchNotFoundContext {\n branch: string;\n repository?: string;\n}\nexport interface GitErrorContext {\n repository?: string;\n branch?: string;\n targetDir?: string;\n stderr?: string;\n exitCode?: number;\n}\n/**\n * Code interpreter error contexts\n */\nexport interface InterpreterNotReadyContext {\n retryAfter?: number;\n progress?: number;\n}\nexport interface ContextNotFoundContext {\n contextId: string;\n}\nexport interface CodeExecutionContext {\n contextId?: string;\n ename?: string;\n evalue?: string;\n traceback?: string[];\n}\n/**\n * Validation error contexts\n */\nexport interface ValidationFailedContext {\n validationErrors: Array<{\n field: string;\n message: string;\n code?: string;\n }>;\n}\n/**\n * Bucket mounting error contexts\n */\nexport interface BucketMountContext {\n bucket: string;\n mountPath: string;\n endpoint: string;\n stderr?: string;\n exitCode?: number;\n}\nexport interface MissingCredentialsContext {\n bucket: string;\n endpoint: string;\n}\nexport interface InvalidMountConfigContext {\n bucket?: string;\n mountPath?: string;\n endpoint?: string;\n reason?: string;\n}\n/**\n * OpenCode error contexts\n */\nexport interface OpencodeStartupContext {\n port: number;\n stderr?: string;\n command?: string;\n}\n/**\n * Generic error contexts\n */\nexport interface InternalErrorContext {\n originalError?: string;\n stack?: string;\n [key: string]: unknown;\n}\n//# sourceMappingURL=contexts.d.ts.map"],"mappings":";;AAIA;AAuDA;;cAvDqBA;;ECAAE,SAAAA,iBA0BpB,EAAA,mBAAA;EACWC,SAAAA,WAAa,EAAA,aAAWD;EAKnBE,SAAAA,YAAa,EAAAC,cAAA;EAAYC,SAAAA,aAAAA,EAAAA,eAAAA;EAIhCL,SAAAA,QAAAA,EAAAA,UAAAA;EAQME,SAAAA,cAAAA,EAAAA,gBAAAA;EAKHE,SAAAA,aAAAA,EAAAA,eAAAA;EAAQ,SAAA,SAAA,EAAA,WAAA;;;;ECLJc,SAAAA,iBAAAA,EAAAA,mBAA0B;EAM1BC,SAAAA,yBAA+B,EAAA,2BAAA;EA8F/BiB,SAAAA,eAAAA,EAAsB,iBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KFzF3BrC,SAAAA,WAAoBA,wBAAwBA;;;AAvDxD;AAuDA;;cCvDqBE;;EAAAA,SAAAA,UA0BpB,EAAA,YAAA;EACWC,SAAAA,WAAa,EAAA,aAAWD;EAKnBE,SAAAA,SAAa,EAAA,WAAAC;EAAYC,SAAAA,WAAAA,EAAAA,aAAAA;EAIhCL,SAAAA,SAAAA,EAAAA,WAAAA;EAQME,SAAAA,gBAAAA,EAAAA,kBAAAA;EAKHE,SAAAA,cAAAA,EAAAA,gBAAAA;EAAQ,SAAA,eAAA,EAAA,iBAAA;;;;ECLJc,SAAAA,YAAAA,EAAAA,cAA0B;EAM1BC,SAAAA,WAAAA,EAAAA,aAA+B;EA8F/BiB,SAAAA,YAAAA,EAAAA,cAAsB;;;;;;;;;;;;KDrH3BlC,aAAAA,WAAwBD,wBAAwBA;;;;;UAK3CE,yBAAyBE;;;;QAIhCL;;;;;;;;cAQME;;;;;WAKHE;;;;;;;;;;;;;;;;;;;;;;;;UCLIc,0BAAAA;;;;;;UAMAC,+BAAAA;;;;;;;;;UA8FAiB,sBAAAA"}
|
|
@@ -49,6 +49,7 @@ const ErrorCode = {
|
|
|
49
49
|
CONTEXT_NOT_FOUND: "CONTEXT_NOT_FOUND",
|
|
50
50
|
CODE_EXECUTION_ERROR: "CODE_EXECUTION_ERROR",
|
|
51
51
|
PYTHON_NOT_AVAILABLE: "PYTHON_NOT_AVAILABLE",
|
|
52
|
+
JAVASCRIPT_NOT_AVAILABLE: "JAVASCRIPT_NOT_AVAILABLE",
|
|
52
53
|
OPENCODE_STARTUP_FAILED: "OPENCODE_STARTUP_FAILED",
|
|
53
54
|
PROCESS_READY_TIMEOUT: "PROCESS_READY_TIMEOUT",
|
|
54
55
|
PROCESS_EXITED_BEFORE_READY: "PROCESS_EXITED_BEFORE_READY",
|
|
@@ -97,6 +98,7 @@ const ERROR_STATUS_MAP = {
|
|
|
97
98
|
[ErrorCode.SERVICE_NOT_RESPONDING]: 502,
|
|
98
99
|
[ErrorCode.GIT_NETWORK_ERROR]: 502,
|
|
99
100
|
[ErrorCode.PYTHON_NOT_AVAILABLE]: 501,
|
|
101
|
+
[ErrorCode.JAVASCRIPT_NOT_AVAILABLE]: 501,
|
|
100
102
|
[ErrorCode.INTERPRETER_NOT_READY]: 503,
|
|
101
103
|
[ErrorCode.OPENCODE_STARTUP_FAILED]: 503,
|
|
102
104
|
[ErrorCode.PROCESS_READY_TIMEOUT]: 408,
|
|
@@ -121,4 +123,4 @@ const ERROR_STATUS_MAP = {
|
|
|
121
123
|
|
|
122
124
|
//#endregion
|
|
123
125
|
export { ErrorCode as t };
|
|
124
|
-
//# sourceMappingURL=errors-
|
|
126
|
+
//# sourceMappingURL=errors-BCXUmJUn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors-BCXUmJUn.js","names":[],"sources":["../../shared/dist/errors/codes.js","../../shared/dist/errors/status-map.js"],"sourcesContent":["/**\n * Centralized error code registry\n * Each code maps to a specific error type with consistent semantics\n */\nexport const ErrorCode = {\n // File System Errors (404)\n FILE_NOT_FOUND: 'FILE_NOT_FOUND',\n // Permission Errors (403)\n PERMISSION_DENIED: 'PERMISSION_DENIED',\n // File System Errors (409)\n FILE_EXISTS: 'FILE_EXISTS',\n // File System Errors (400)\n IS_DIRECTORY: 'IS_DIRECTORY',\n NOT_DIRECTORY: 'NOT_DIRECTORY',\n // File System Errors (500)\n NO_SPACE: 'NO_SPACE',\n TOO_MANY_FILES: 'TOO_MANY_FILES',\n RESOURCE_BUSY: 'RESOURCE_BUSY',\n READ_ONLY: 'READ_ONLY',\n NAME_TOO_LONG: 'NAME_TOO_LONG',\n TOO_MANY_LINKS: 'TOO_MANY_LINKS',\n FILESYSTEM_ERROR: 'FILESYSTEM_ERROR',\n // Command Errors (404)\n COMMAND_NOT_FOUND: 'COMMAND_NOT_FOUND',\n // Command Errors (403/400)\n COMMAND_PERMISSION_DENIED: 'COMMAND_PERMISSION_DENIED',\n INVALID_COMMAND: 'INVALID_COMMAND',\n // Command Errors (500)\n COMMAND_EXECUTION_ERROR: 'COMMAND_EXECUTION_ERROR',\n STREAM_START_ERROR: 'STREAM_START_ERROR',\n // Process Errors (404)\n PROCESS_NOT_FOUND: 'PROCESS_NOT_FOUND',\n // Process Errors (403/500)\n PROCESS_PERMISSION_DENIED: 'PROCESS_PERMISSION_DENIED',\n PROCESS_ERROR: 'PROCESS_ERROR',\n // Session Errors (409)\n SESSION_ALREADY_EXISTS: 'SESSION_ALREADY_EXISTS',\n // Port Errors (409)\n PORT_ALREADY_EXPOSED: 'PORT_ALREADY_EXPOSED',\n PORT_IN_USE: 'PORT_IN_USE',\n // Port Errors (404)\n PORT_NOT_EXPOSED: 'PORT_NOT_EXPOSED',\n // Port Errors (400)\n INVALID_PORT_NUMBER: 'INVALID_PORT_NUMBER',\n INVALID_PORT: 'INVALID_PORT',\n // Port Errors (502/500)\n SERVICE_NOT_RESPONDING: 'SERVICE_NOT_RESPONDING',\n PORT_OPERATION_ERROR: 'PORT_OPERATION_ERROR',\n // Port Errors (400)\n CUSTOM_DOMAIN_REQUIRED: 'CUSTOM_DOMAIN_REQUIRED',\n // Git Errors (404)\n GIT_REPOSITORY_NOT_FOUND: 'GIT_REPOSITORY_NOT_FOUND',\n GIT_BRANCH_NOT_FOUND: 'GIT_BRANCH_NOT_FOUND',\n // Git Errors (401)\n GIT_AUTH_FAILED: 'GIT_AUTH_FAILED',\n // Git Errors (502)\n GIT_NETWORK_ERROR: 'GIT_NETWORK_ERROR',\n // Git Errors (400)\n INVALID_GIT_URL: 'INVALID_GIT_URL',\n // Git Errors (500)\n GIT_CLONE_FAILED: 'GIT_CLONE_FAILED',\n GIT_CHECKOUT_FAILED: 'GIT_CHECKOUT_FAILED',\n GIT_OPERATION_FAILED: 'GIT_OPERATION_FAILED',\n // Bucket mounting errors\n BUCKET_MOUNT_ERROR: 'BUCKET_MOUNT_ERROR',\n S3FS_MOUNT_ERROR: 'S3FS_MOUNT_ERROR',\n MISSING_CREDENTIALS: 'MISSING_CREDENTIALS',\n INVALID_MOUNT_CONFIG: 'INVALID_MOUNT_CONFIG',\n // Code Interpreter Errors (503)\n INTERPRETER_NOT_READY: 'INTERPRETER_NOT_READY',\n // Code Interpreter Errors (404)\n CONTEXT_NOT_FOUND: 'CONTEXT_NOT_FOUND',\n // Code Interpreter Errors (500)\n CODE_EXECUTION_ERROR: 'CODE_EXECUTION_ERROR',\n // Code Interpreter Errors (501) - Feature not available in image variant\n PYTHON_NOT_AVAILABLE: 'PYTHON_NOT_AVAILABLE',\n JAVASCRIPT_NOT_AVAILABLE: 'JAVASCRIPT_NOT_AVAILABLE',\n // OpenCode Errors (503)\n OPENCODE_STARTUP_FAILED: 'OPENCODE_STARTUP_FAILED',\n // Process Readiness Errors (408/500)\n PROCESS_READY_TIMEOUT: 'PROCESS_READY_TIMEOUT',\n PROCESS_EXITED_BEFORE_READY: 'PROCESS_EXITED_BEFORE_READY',\n // Validation Errors (400)\n VALIDATION_FAILED: 'VALIDATION_FAILED',\n // Generic Errors (400/500)\n INVALID_JSON_RESPONSE: 'INVALID_JSON_RESPONSE',\n UNKNOWN_ERROR: 'UNKNOWN_ERROR',\n INTERNAL_ERROR: 'INTERNAL_ERROR'\n};\n","import { ErrorCode } from './codes';\n/**\n * Maps error codes to HTTP status codes\n * Centralized mapping ensures consistency across SDK\n */\nexport const ERROR_STATUS_MAP = {\n // 404 Not Found\n [ErrorCode.FILE_NOT_FOUND]: 404,\n [ErrorCode.COMMAND_NOT_FOUND]: 404,\n [ErrorCode.PROCESS_NOT_FOUND]: 404,\n [ErrorCode.PORT_NOT_EXPOSED]: 404,\n [ErrorCode.GIT_REPOSITORY_NOT_FOUND]: 404,\n [ErrorCode.GIT_BRANCH_NOT_FOUND]: 404,\n [ErrorCode.CONTEXT_NOT_FOUND]: 404,\n // 400 Bad Request\n [ErrorCode.IS_DIRECTORY]: 400,\n [ErrorCode.NOT_DIRECTORY]: 400,\n [ErrorCode.INVALID_COMMAND]: 400,\n [ErrorCode.INVALID_PORT_NUMBER]: 400,\n [ErrorCode.INVALID_PORT]: 400,\n [ErrorCode.INVALID_GIT_URL]: 400,\n [ErrorCode.CUSTOM_DOMAIN_REQUIRED]: 400,\n [ErrorCode.INVALID_JSON_RESPONSE]: 400,\n [ErrorCode.NAME_TOO_LONG]: 400,\n [ErrorCode.VALIDATION_FAILED]: 400,\n [ErrorCode.MISSING_CREDENTIALS]: 400,\n [ErrorCode.INVALID_MOUNT_CONFIG]: 400,\n // 401 Unauthorized\n [ErrorCode.GIT_AUTH_FAILED]: 401,\n // 403 Forbidden\n [ErrorCode.PERMISSION_DENIED]: 403,\n [ErrorCode.COMMAND_PERMISSION_DENIED]: 403,\n [ErrorCode.PROCESS_PERMISSION_DENIED]: 403,\n [ErrorCode.READ_ONLY]: 403,\n // 409 Conflict\n [ErrorCode.FILE_EXISTS]: 409,\n [ErrorCode.PORT_ALREADY_EXPOSED]: 409,\n [ErrorCode.PORT_IN_USE]: 409,\n [ErrorCode.RESOURCE_BUSY]: 409,\n [ErrorCode.SESSION_ALREADY_EXISTS]: 409,\n // 502 Bad Gateway\n [ErrorCode.SERVICE_NOT_RESPONDING]: 502,\n [ErrorCode.GIT_NETWORK_ERROR]: 502,\n // 501 Not Implemented (feature not available in image variant)\n [ErrorCode.PYTHON_NOT_AVAILABLE]: 501,\n [ErrorCode.JAVASCRIPT_NOT_AVAILABLE]: 501,\n // 503 Service Unavailable\n [ErrorCode.INTERPRETER_NOT_READY]: 503,\n [ErrorCode.OPENCODE_STARTUP_FAILED]: 503,\n // 408 Request Timeout\n [ErrorCode.PROCESS_READY_TIMEOUT]: 408,\n // 500 Internal Server Error\n [ErrorCode.PROCESS_EXITED_BEFORE_READY]: 500,\n [ErrorCode.NO_SPACE]: 500,\n [ErrorCode.TOO_MANY_FILES]: 500,\n [ErrorCode.TOO_MANY_LINKS]: 500,\n [ErrorCode.FILESYSTEM_ERROR]: 500,\n [ErrorCode.COMMAND_EXECUTION_ERROR]: 500,\n [ErrorCode.STREAM_START_ERROR]: 500,\n [ErrorCode.PROCESS_ERROR]: 500,\n [ErrorCode.PORT_OPERATION_ERROR]: 500,\n [ErrorCode.GIT_CLONE_FAILED]: 500,\n [ErrorCode.GIT_CHECKOUT_FAILED]: 500,\n [ErrorCode.GIT_OPERATION_FAILED]: 500,\n [ErrorCode.CODE_EXECUTION_ERROR]: 500,\n [ErrorCode.BUCKET_MOUNT_ERROR]: 500,\n [ErrorCode.S3FS_MOUNT_ERROR]: 500,\n [ErrorCode.UNKNOWN_ERROR]: 500,\n [ErrorCode.INTERNAL_ERROR]: 500\n};\n/**\n * Get HTTP status code for an error code\n * Falls back to 500 for unknown errors\n */\nexport function getHttpStatus(code) {\n return ERROR_STATUS_MAP[code] || 500;\n}\n"],"mappings":";;;;;AAIA,MAAa,YAAY;CAErB,gBAAgB;CAEhB,mBAAmB;CAEnB,aAAa;CAEb,cAAc;CACd,eAAe;CAEf,UAAU;CACV,gBAAgB;CAChB,eAAe;CACf,WAAW;CACX,eAAe;CACf,gBAAgB;CAChB,kBAAkB;CAElB,mBAAmB;CAEnB,2BAA2B;CAC3B,iBAAiB;CAEjB,yBAAyB;CACzB,oBAAoB;CAEpB,mBAAmB;CAEnB,2BAA2B;CAC3B,eAAe;CAEf,wBAAwB;CAExB,sBAAsB;CACtB,aAAa;CAEb,kBAAkB;CAElB,qBAAqB;CACrB,cAAc;CAEd,wBAAwB;CACxB,sBAAsB;CAEtB,wBAAwB;CAExB,0BAA0B;CAC1B,sBAAsB;CAEtB,iBAAiB;CAEjB,mBAAmB;CAEnB,iBAAiB;CAEjB,kBAAkB;CAClB,qBAAqB;CACrB,sBAAsB;CAEtB,oBAAoB;CACpB,kBAAkB;CAClB,qBAAqB;CACrB,sBAAsB;CAEtB,uBAAuB;CAEvB,mBAAmB;CAEnB,sBAAsB;CAEtB,sBAAsB;CACtB,0BAA0B;CAE1B,yBAAyB;CAEzB,uBAAuB;CACvB,6BAA6B;CAE7B,mBAAmB;CAEnB,uBAAuB;CACvB,eAAe;CACf,gBAAgB;CACnB;;;;;;;;ACnFD,MAAa,mBAAmB;EAE3B,UAAU,iBAAiB;EAC3B,UAAU,oBAAoB;EAC9B,UAAU,oBAAoB;EAC9B,UAAU,mBAAmB;EAC7B,UAAU,2BAA2B;EACrC,UAAU,uBAAuB;EACjC,UAAU,oBAAoB;EAE9B,UAAU,eAAe;EACzB,UAAU,gBAAgB;EAC1B,UAAU,kBAAkB;EAC5B,UAAU,sBAAsB;EAChC,UAAU,eAAe;EACzB,UAAU,kBAAkB;EAC5B,UAAU,yBAAyB;EACnC,UAAU,wBAAwB;EAClC,UAAU,gBAAgB;EAC1B,UAAU,oBAAoB;EAC9B,UAAU,sBAAsB;EAChC,UAAU,uBAAuB;EAEjC,UAAU,kBAAkB;EAE5B,UAAU,oBAAoB;EAC9B,UAAU,4BAA4B;EACtC,UAAU,4BAA4B;EACtC,UAAU,YAAY;EAEtB,UAAU,cAAc;EACxB,UAAU,uBAAuB;EACjC,UAAU,cAAc;EACxB,UAAU,gBAAgB;EAC1B,UAAU,yBAAyB;EAEnC,UAAU,yBAAyB;EACnC,UAAU,oBAAoB;EAE9B,UAAU,uBAAuB;EACjC,UAAU,2BAA2B;EAErC,UAAU,wBAAwB;EAClC,UAAU,0BAA0B;EAEpC,UAAU,wBAAwB;EAElC,UAAU,8BAA8B;EACxC,UAAU,WAAW;EACrB,UAAU,iBAAiB;EAC3B,UAAU,iBAAiB;EAC3B,UAAU,mBAAmB;EAC7B,UAAU,0BAA0B;EACpC,UAAU,qBAAqB;EAC/B,UAAU,gBAAgB;EAC1B,UAAU,uBAAuB;EACjC,UAAU,mBAAmB;EAC7B,UAAU,sBAAsB;EAChC,UAAU,uBAAuB;EACjC,UAAU,uBAAuB;EACjC,UAAU,qBAAqB;EAC/B,UAAU,mBAAmB;EAC7B,UAAU,gBAAgB;EAC1B,UAAU,iBAAiB;CAC/B"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { $ as ProcessInfoResult, A as RequestConfig, B as FileChunk, C as WriteFileRequest, D as ContainerStub, E as BaseApiResponse, F as BucketProvider, G as ListFilesOptions, H as FileStreamEvent, I as ExecEvent, J as PortCloseResult, K as LogEvent, L as ExecOptions, M as SessionRequest, N as BaseExecOptions, O as ErrorResponse, P as BucketCredentials, Q as ProcessCleanupResult, R as ExecResult, S as ReadFileRequest, T as ExecuteResponse, U as GitCheckoutResult, V as FileMetadata, W as ISandbox, X as PortListResult, Y as PortExposeResult, Z as Process, _ as GitCheckoutRequest, _t as ExecutionResult, a as CreateSessionRequest, at as ProcessStatus, b as FileOperationRequest, c as DeleteSessionResponse, ct as StreamOptions, d as ProcessClient, dt as isExecResult, et as ProcessKillResult, f as ExposePortRequest, ft as isProcess, g as InterpreterClient, gt as Execution, h as ExecutionCallbacks, ht as CreateContextOptions, i as CommandsResponse, it as ProcessStartResult, j as ResponseHandler, k as HttpClientOptions, l as PingResponse, lt as WaitForLogResult, m as UnexposePortRequest, mt as CodeContext, n as getSandbox, nt as ProcessLogsResult, o as CreateSessionResponse, ot as SandboxOptions, p as PortClient, pt as isProcessStatus, q as MountBucketOptions, r as SandboxClient, rt as ProcessOptions, s as DeleteSessionRequest, st as SessionOptions, t as Sandbox, tt as ProcessListResult, u as UtilityClient, ut as WaitForPortOptions, v as GitClient, vt as RunCodeOptions, w as CommandClient, x as MkdirRequest, y as FileClient, z as ExecutionSession } from "./sandbox-
|
|
2
|
-
import { a as OperationType, i as ErrorResponse$1, n as ProcessExitedBeforeReadyContext, o as ErrorCode, r as ProcessReadyTimeoutContext } from "./contexts-
|
|
1
|
+
import { $ as ProcessInfoResult, A as RequestConfig, B as FileChunk, C as WriteFileRequest, D as ContainerStub, E as BaseApiResponse, F as BucketProvider, G as ListFilesOptions, H as FileStreamEvent, I as ExecEvent, J as PortCloseResult, K as LogEvent, L as ExecOptions, M as SessionRequest, N as BaseExecOptions, O as ErrorResponse, P as BucketCredentials, Q as ProcessCleanupResult, R as ExecResult, S as ReadFileRequest, T as ExecuteResponse, U as GitCheckoutResult, V as FileMetadata, W as ISandbox, X as PortListResult, Y as PortExposeResult, Z as Process, _ as GitCheckoutRequest, _t as ExecutionResult, a as CreateSessionRequest, at as ProcessStatus, b as FileOperationRequest, c as DeleteSessionResponse, ct as StreamOptions, d as ProcessClient, dt as isExecResult, et as ProcessKillResult, f as ExposePortRequest, ft as isProcess, g as InterpreterClient, gt as Execution, h as ExecutionCallbacks, ht as CreateContextOptions, i as CommandsResponse, it as ProcessStartResult, j as ResponseHandler, k as HttpClientOptions, l as PingResponse, lt as WaitForLogResult, m as UnexposePortRequest, mt as CodeContext, n as getSandbox, nt as ProcessLogsResult, o as CreateSessionResponse, ot as SandboxOptions, p as PortClient, pt as isProcessStatus, q as MountBucketOptions, r as SandboxClient, rt as ProcessOptions, s as DeleteSessionRequest, st as SessionOptions, t as Sandbox, tt as ProcessListResult, u as UtilityClient, ut as WaitForPortOptions, v as GitClient, vt as RunCodeOptions, w as CommandClient, x as MkdirRequest, y as FileClient, z as ExecutionSession } from "./sandbox-09Ce7yli.js";
|
|
2
|
+
import { a as OperationType, i as ErrorResponse$1, n as ProcessExitedBeforeReadyContext, o as ErrorCode, r as ProcessReadyTimeoutContext } from "./contexts-CdrlvHWK.js";
|
|
3
3
|
|
|
4
4
|
//#region ../shared/dist/request-types.d.ts
|
|
5
5
|
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as shellEscape, c as TraceContext, d as GitLogger, f as getEnvString, i as isTerminalStatus, l as Execution, n as isProcess, o as createLogger, r as isProcessStatus, s as createNoOpLogger, t as isExecResult, u as ResultImpl } from "./dist-2SF6oOaz.js";
|
|
2
|
-
import { t as ErrorCode } from "./errors-
|
|
2
|
+
import { t as ErrorCode } from "./errors-BCXUmJUn.js";
|
|
3
3
|
import { Container, getContainer, switchPort } from "@cloudflare/containers";
|
|
4
4
|
|
|
5
5
|
//#region src/errors/classes.ts
|
|
@@ -586,14 +586,14 @@ var BaseHttpClient = class {
|
|
|
586
586
|
}
|
|
587
587
|
/**
|
|
588
588
|
* Core HTTP request method with automatic retry for container startup delays
|
|
589
|
-
* Retries
|
|
589
|
+
* Retries on 503 (Service Unavailable) which indicates container is starting
|
|
590
590
|
*/
|
|
591
591
|
async doFetch(path, options) {
|
|
592
592
|
const startTime = Date.now();
|
|
593
593
|
let attempt = 0;
|
|
594
594
|
while (true) {
|
|
595
595
|
const response = await this.executeFetch(path, options);
|
|
596
|
-
if (
|
|
596
|
+
if (this.isRetryableContainerError(response)) {
|
|
597
597
|
const elapsed = Date.now() - startTime;
|
|
598
598
|
const remaining = TIMEOUT_MS - elapsed;
|
|
599
599
|
if (remaining > MIN_TIME_FOR_RETRY_MS) {
|
|
@@ -706,51 +706,19 @@ var BaseHttpClient = class {
|
|
|
706
706
|
}
|
|
707
707
|
/**
|
|
708
708
|
* Check if response indicates a retryable container error
|
|
709
|
-
* Uses fail-safe strategy: only retry known transient errors
|
|
710
709
|
*
|
|
711
|
-
*
|
|
712
|
-
*
|
|
713
|
-
*
|
|
710
|
+
* The Sandbox DO returns proper HTTP status codes:
|
|
711
|
+
* - 503 Service Unavailable: Transient errors (container starting, port not ready)
|
|
712
|
+
* - 500 Internal Server Error: Permanent errors (bad config, missing image)
|
|
713
|
+
*
|
|
714
|
+
* We only retry on 503, which indicates the container is starting up.
|
|
715
|
+
* The Retry-After header suggests how long to wait.
|
|
714
716
|
*
|
|
715
717
|
* @param response - HTTP response to check
|
|
716
|
-
* @returns true if error is retryable
|
|
718
|
+
* @returns true if error is retryable (503), false otherwise
|
|
717
719
|
*/
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
try {
|
|
721
|
-
const text = await response.clone().text();
|
|
722
|
-
const textLower = text.toLowerCase();
|
|
723
|
-
if ([
|
|
724
|
-
"no such image",
|
|
725
|
-
"container already exists",
|
|
726
|
-
"malformed containerinspect"
|
|
727
|
-
].some((err) => textLower.includes(err))) {
|
|
728
|
-
this.logger.debug("Detected permanent error, not retrying", { text });
|
|
729
|
-
return false;
|
|
730
|
-
}
|
|
731
|
-
const shouldRetry = [
|
|
732
|
-
"no container instance available",
|
|
733
|
-
"currently provisioning",
|
|
734
|
-
"container port not found",
|
|
735
|
-
"connection refused: container port",
|
|
736
|
-
"the container is not listening",
|
|
737
|
-
"failed to verify port",
|
|
738
|
-
"container did not start",
|
|
739
|
-
"network connection lost",
|
|
740
|
-
"container suddenly disconnected",
|
|
741
|
-
"monitor failed to find container",
|
|
742
|
-
"timed out",
|
|
743
|
-
"timeout"
|
|
744
|
-
].some((err) => textLower.includes(err));
|
|
745
|
-
if (!shouldRetry) this.logger.debug("Unknown error pattern, not retrying", {
|
|
746
|
-
status: response.status,
|
|
747
|
-
text: text.substring(0, 200)
|
|
748
|
-
});
|
|
749
|
-
return shouldRetry;
|
|
750
|
-
} catch (error) {
|
|
751
|
-
this.logger.error("Error checking if response is retryable", error instanceof Error ? error : new Error(String(error)));
|
|
752
|
-
return false;
|
|
753
|
-
}
|
|
720
|
+
isRetryableContainerError(response) {
|
|
721
|
+
return response.status === 503;
|
|
754
722
|
}
|
|
755
723
|
async executeFetch(path, options) {
|
|
756
724
|
const url = this.options.stub ? `http://localhost:${this.options.port}${path}` : `${this.baseUrl}${path}`;
|
|
@@ -2019,7 +1987,7 @@ function resolveS3fsOptions(provider, userOptions) {
|
|
|
2019
1987
|
* This file is auto-updated by .github/changeset-version.ts during releases
|
|
2020
1988
|
* DO NOT EDIT MANUALLY - Changes will be overwritten on the next version bump
|
|
2021
1989
|
*/
|
|
2022
|
-
const SDK_VERSION = "0.6.
|
|
1990
|
+
const SDK_VERSION = "0.6.6";
|
|
2023
1991
|
|
|
2024
1992
|
//#endregion
|
|
2025
1993
|
//#region src/sandbox.ts
|
|
@@ -2379,18 +2347,57 @@ var Sandbox = class extends Container {
|
|
|
2379
2347
|
status: 503,
|
|
2380
2348
|
headers: { "Retry-After": "10" }
|
|
2381
2349
|
});
|
|
2382
|
-
this.
|
|
2350
|
+
if (this.isTransientStartupError(e)) {
|
|
2351
|
+
this.logger.debug("Transient container startup error, returning 503", { error: e instanceof Error ? e.message : String(e) });
|
|
2352
|
+
return new Response("Container is starting. Please retry in a moment.", {
|
|
2353
|
+
status: 503,
|
|
2354
|
+
headers: { "Retry-After": "3" }
|
|
2355
|
+
});
|
|
2356
|
+
}
|
|
2357
|
+
this.logger.error("Container startup failed with permanent error", e instanceof Error ? e : new Error(String(e)));
|
|
2383
2358
|
return new Response(`Failed to start container: ${e instanceof Error ? e.message : String(e)}`, { status: 500 });
|
|
2384
2359
|
}
|
|
2385
2360
|
return await super.containerFetch(requestOrUrl, portOrInit, portParam);
|
|
2386
2361
|
}
|
|
2387
2362
|
/**
|
|
2388
2363
|
* Helper: Check if error is "no container instance available"
|
|
2364
|
+
* This indicates the container VM is still being provisioned.
|
|
2389
2365
|
*/
|
|
2390
2366
|
isNoInstanceError(error) {
|
|
2391
2367
|
return error instanceof Error && error.message.toLowerCase().includes("no container instance");
|
|
2392
2368
|
}
|
|
2393
2369
|
/**
|
|
2370
|
+
* Helper: Check if error is a transient startup error that should trigger retry
|
|
2371
|
+
*
|
|
2372
|
+
* These errors occur during normal container startup and are recoverable:
|
|
2373
|
+
* - Port not yet mapped (container starting, app not listening yet)
|
|
2374
|
+
* - Connection refused (port mapped but app not ready)
|
|
2375
|
+
* - Timeouts during startup (recoverable with retry)
|
|
2376
|
+
* - Network transients (temporary connectivity issues)
|
|
2377
|
+
*
|
|
2378
|
+
* Errors NOT included (permanent failures):
|
|
2379
|
+
* - "no such image" - missing Docker image
|
|
2380
|
+
* - "container already exists" - name collision
|
|
2381
|
+
* - Configuration errors
|
|
2382
|
+
*/
|
|
2383
|
+
isTransientStartupError(error) {
|
|
2384
|
+
if (!(error instanceof Error)) return false;
|
|
2385
|
+
const msg = error.message.toLowerCase();
|
|
2386
|
+
return [
|
|
2387
|
+
"container port not found",
|
|
2388
|
+
"connection refused: container port",
|
|
2389
|
+
"the container is not listening",
|
|
2390
|
+
"failed to verify port",
|
|
2391
|
+
"container did not start",
|
|
2392
|
+
"network connection lost",
|
|
2393
|
+
"container suddenly disconnected",
|
|
2394
|
+
"monitor failed to find container",
|
|
2395
|
+
"timed out",
|
|
2396
|
+
"timeout",
|
|
2397
|
+
"the operation was aborted"
|
|
2398
|
+
].some((pattern) => msg.includes(pattern));
|
|
2399
|
+
}
|
|
2400
|
+
/**
|
|
2394
2401
|
* Helper: Parse containerFetch arguments (supports multiple signatures)
|
|
2395
2402
|
*/
|
|
2396
2403
|
parseContainerFetchArgs(requestOrUrl, portOrInit, portParam) {
|
|
@@ -2606,6 +2613,9 @@ var Sandbox = class extends Container {
|
|
|
2606
2613
|
},
|
|
2607
2614
|
waitForPort: async (port, options) => {
|
|
2608
2615
|
await this.waitForPortReady(data.id, data.command, port, options);
|
|
2616
|
+
},
|
|
2617
|
+
waitForExit: async (timeout) => {
|
|
2618
|
+
return this.waitForProcessExit(data.id, data.command, timeout);
|
|
2609
2619
|
}
|
|
2610
2620
|
};
|
|
2611
2621
|
}
|
|
@@ -2731,6 +2741,30 @@ var Sandbox = class extends Container {
|
|
|
2731
2741
|
}
|
|
2732
2742
|
}
|
|
2733
2743
|
/**
|
|
2744
|
+
* Wait for a process to exit
|
|
2745
|
+
* Returns the exit code
|
|
2746
|
+
*/
|
|
2747
|
+
async waitForProcessExit(processId, command, timeout) {
|
|
2748
|
+
const stream = await this.streamProcessLogs(processId);
|
|
2749
|
+
let timeoutId;
|
|
2750
|
+
let timeoutPromise;
|
|
2751
|
+
if (timeout !== void 0) timeoutPromise = new Promise((_, reject) => {
|
|
2752
|
+
timeoutId = setTimeout(() => {
|
|
2753
|
+
reject(this.createReadyTimeoutError(processId, command, "process exit", timeout));
|
|
2754
|
+
}, timeout);
|
|
2755
|
+
});
|
|
2756
|
+
try {
|
|
2757
|
+
const streamProcessor = async () => {
|
|
2758
|
+
for await (const event of parseSSEStream(stream)) if (event.type === "exit") return { exitCode: event.exitCode ?? 1 };
|
|
2759
|
+
throw new Error(`Process ${processId} stream ended unexpectedly without exit event`);
|
|
2760
|
+
};
|
|
2761
|
+
if (timeoutPromise) return await Promise.race([streamProcessor(), timeoutPromise]);
|
|
2762
|
+
return await streamProcessor();
|
|
2763
|
+
} finally {
|
|
2764
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
/**
|
|
2734
2768
|
* Match a pattern against text
|
|
2735
2769
|
*/
|
|
2736
2770
|
matchPattern(text, pattern) {
|