@cloudflare/sandbox 0.6.11 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +2 -7
- package/dist/{contexts-CdrlvHWK.d.ts → contexts-uY_burk0.d.ts} +1 -1
- package/dist/{contexts-CdrlvHWK.d.ts.map → contexts-uY_burk0.d.ts.map} +1 -1
- package/dist/{dist-c_xYW5i_.js → dist-D9B_6gn_.js} +41 -2
- package/dist/dist-D9B_6gn_.js.map +1 -0
- package/dist/{errors-BCXUmJUn.js → errors-Bzl0ZNia.js} +1 -1
- package/dist/{errors-BCXUmJUn.js.map → errors-Bzl0ZNia.js.map} +1 -1
- package/dist/index.d.ts +17 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +135 -40
- package/dist/index.js.map +1 -1
- package/dist/openai/index.d.ts +1 -1
- package/dist/openai/index.js +1 -1
- package/dist/opencode/index.d.ts +47 -5
- package/dist/opencode/index.d.ts.map +1 -1
- package/dist/opencode/index.js +63 -11
- package/dist/opencode/index.js.map +1 -1
- package/dist/{sandbox-HCG7Oeg0.d.ts → sandbox-CgjQQZGw.d.ts} +53 -27
- package/dist/sandbox-CgjQQZGw.d.ts.map +1 -0
- package/dist/xterm/index.d.ts +89 -0
- package/dist/xterm/index.d.ts.map +1 -0
- package/dist/xterm/index.js +177 -0
- package/dist/xterm/index.js.map +1 -0
- package/package.json +16 -4
- package/dist/dist-c_xYW5i_.js.map +0 -1
- package/dist/sandbox-HCG7Oeg0.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as isExecResult, c as shellEscape, d as TraceContext, f as Execution, g as
|
|
2
|
-
import { t as ErrorCode } from "./errors-
|
|
1
|
+
import { _ as getEnvString, a as isExecResult, c as shellEscape, d as TraceContext, f as Execution, g as filterEnvVars, h as extractRepoName, i as isWSStreamChunk, l as createLogger, m as GitLogger, n as isWSError, o as isProcess, p as ResultImpl, r as isWSResponse, s as isProcessStatus, t as generateRequestId, u as createNoOpLogger, v as partitionEnvVars } from "./dist-D9B_6gn_.js";
|
|
2
|
+
import { t as ErrorCode } from "./errors-Bzl0ZNia.js";
|
|
3
3
|
import { Container, getContainer, switchPort } from "@cloudflare/containers";
|
|
4
4
|
|
|
5
5
|
//#region src/errors/classes.ts
|
|
@@ -2216,6 +2216,19 @@ var CodeInterpreter = class {
|
|
|
2216
2216
|
}
|
|
2217
2217
|
};
|
|
2218
2218
|
|
|
2219
|
+
//#endregion
|
|
2220
|
+
//#region src/pty/proxy.ts
|
|
2221
|
+
async function proxyTerminal(stub, sessionId, request, options) {
|
|
2222
|
+
if (!sessionId || typeof sessionId !== "string") throw new Error("sessionId is required for terminal access");
|
|
2223
|
+
if (request.headers.get("Upgrade")?.toLowerCase() !== "websocket") throw new Error("terminal() requires a WebSocket upgrade request");
|
|
2224
|
+
const params = new URLSearchParams({ sessionId });
|
|
2225
|
+
if (options?.cols) params.set("cols", String(options.cols));
|
|
2226
|
+
if (options?.rows) params.set("rows", String(options.rows));
|
|
2227
|
+
const ptyUrl = `http://localhost/ws/pty?${params}`;
|
|
2228
|
+
const ptyRequest = new Request(ptyUrl, request);
|
|
2229
|
+
return stub.fetch(switchPort(ptyRequest, 3e3));
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2219
2232
|
//#endregion
|
|
2220
2233
|
//#region src/request-handler.ts
|
|
2221
2234
|
async function proxyToSandbox(request, env) {
|
|
@@ -2254,15 +2267,18 @@ async function proxyToSandbox(request, env) {
|
|
|
2254
2267
|
let proxyUrl;
|
|
2255
2268
|
if (port !== 3e3) proxyUrl = `http://localhost:${port}${path}${url.search}`;
|
|
2256
2269
|
else proxyUrl = `http://localhost:3000${path}${url.search}`;
|
|
2270
|
+
const headers = {
|
|
2271
|
+
"X-Original-URL": request.url,
|
|
2272
|
+
"X-Forwarded-Host": url.hostname,
|
|
2273
|
+
"X-Forwarded-Proto": url.protocol.replace(":", ""),
|
|
2274
|
+
"X-Sandbox-Name": sandboxId
|
|
2275
|
+
};
|
|
2276
|
+
request.headers.forEach((value, key) => {
|
|
2277
|
+
headers[key] = value;
|
|
2278
|
+
});
|
|
2257
2279
|
const proxyRequest = new Request(proxyUrl, {
|
|
2258
2280
|
method: request.method,
|
|
2259
|
-
headers
|
|
2260
|
-
...Object.fromEntries(request.headers),
|
|
2261
|
-
"X-Original-URL": request.url,
|
|
2262
|
-
"X-Forwarded-Host": url.hostname,
|
|
2263
|
-
"X-Forwarded-Proto": url.protocol.replace(":", ""),
|
|
2264
|
-
"X-Sandbox-Name": sandboxId
|
|
2265
|
-
},
|
|
2281
|
+
headers,
|
|
2266
2282
|
body: request.body,
|
|
2267
2283
|
duplex: "half"
|
|
2268
2284
|
});
|
|
@@ -2273,21 +2289,29 @@ async function proxyToSandbox(request, env) {
|
|
|
2273
2289
|
}
|
|
2274
2290
|
}
|
|
2275
2291
|
function extractSandboxRoute(url) {
|
|
2276
|
-
const
|
|
2277
|
-
if (
|
|
2278
|
-
const
|
|
2279
|
-
|
|
2280
|
-
const
|
|
2281
|
-
|
|
2292
|
+
const dotIndex = url.hostname.indexOf(".");
|
|
2293
|
+
if (dotIndex === -1) return null;
|
|
2294
|
+
const subdomain = url.hostname.slice(0, dotIndex);
|
|
2295
|
+
url.hostname.slice(dotIndex + 1);
|
|
2296
|
+
const firstHyphen = subdomain.indexOf("-");
|
|
2297
|
+
if (firstHyphen === -1) return null;
|
|
2298
|
+
const portStr = subdomain.slice(0, firstHyphen);
|
|
2299
|
+
if (!/^\d{4,5}$/.test(portStr)) return null;
|
|
2282
2300
|
const port = parseInt(portStr, 10);
|
|
2283
2301
|
if (!validatePort(port)) return null;
|
|
2302
|
+
const rest = subdomain.slice(firstHyphen + 1);
|
|
2303
|
+
const lastHyphen = rest.lastIndexOf("-");
|
|
2304
|
+
if (lastHyphen === -1) return null;
|
|
2305
|
+
const sandboxId = rest.slice(0, lastHyphen);
|
|
2306
|
+
const token = rest.slice(lastHyphen + 1);
|
|
2307
|
+
if (!/^[a-z0-9_]+$/.test(token) || token.length === 0 || token.length > 63) return null;
|
|
2308
|
+
if (sandboxId.length === 0 || sandboxId.length > 63) return null;
|
|
2284
2309
|
let sanitizedSandboxId;
|
|
2285
2310
|
try {
|
|
2286
2311
|
sanitizedSandboxId = sanitizeSandboxId(sandboxId);
|
|
2287
|
-
} catch
|
|
2312
|
+
} catch {
|
|
2288
2313
|
return null;
|
|
2289
2314
|
}
|
|
2290
|
-
if (sandboxId.length > 63) return null;
|
|
2291
2315
|
return {
|
|
2292
2316
|
port,
|
|
2293
2317
|
sandboxId: sanitizedSandboxId,
|
|
@@ -2540,7 +2564,7 @@ function buildS3fsSource(bucket, prefix) {
|
|
|
2540
2564
|
* This file is auto-updated by .github/changeset-version.ts during releases
|
|
2541
2565
|
* DO NOT EDIT MANUALLY - Changes will be overwritten on the next version bump
|
|
2542
2566
|
*/
|
|
2543
|
-
const SDK_VERSION = "0.
|
|
2567
|
+
const SDK_VERSION = "0.7.1";
|
|
2544
2568
|
|
|
2545
2569
|
//#endregion
|
|
2546
2570
|
//#region src/sandbox.ts
|
|
@@ -2555,7 +2579,27 @@ function getSandbox(ns, id, options) {
|
|
|
2555
2579
|
if (options?.sleepAfter !== void 0) stub.setSleepAfter(options.sleepAfter);
|
|
2556
2580
|
if (options?.keepAlive !== void 0) stub.setKeepAlive(options.keepAlive);
|
|
2557
2581
|
if (options?.containerTimeouts) stub.setContainerTimeouts(options.containerTimeouts);
|
|
2558
|
-
|
|
2582
|
+
const defaultSessionId = `sandbox-${effectiveId}`;
|
|
2583
|
+
const enhancedMethods = {
|
|
2584
|
+
createSession: async (opts) => {
|
|
2585
|
+
return enhanceSession(stub, await stub.createSession(opts));
|
|
2586
|
+
},
|
|
2587
|
+
getSession: async (sessionId) => {
|
|
2588
|
+
return enhanceSession(stub, await stub.getSession(sessionId));
|
|
2589
|
+
},
|
|
2590
|
+
terminal: (request, opts) => proxyTerminal(stub, defaultSessionId, request, opts),
|
|
2591
|
+
wsConnect: connect(stub)
|
|
2592
|
+
};
|
|
2593
|
+
return new Proxy(stub, { get(target, prop) {
|
|
2594
|
+
if (typeof prop === "string" && prop in enhancedMethods) return enhancedMethods[prop];
|
|
2595
|
+
return target[prop];
|
|
2596
|
+
} });
|
|
2597
|
+
}
|
|
2598
|
+
function enhanceSession(stub, rpcSession) {
|
|
2599
|
+
return {
|
|
2600
|
+
...rpcSession,
|
|
2601
|
+
terminal: (request, opts) => proxyTerminal(stub, rpcSession.id, request, opts)
|
|
2602
|
+
};
|
|
2559
2603
|
}
|
|
2560
2604
|
function connect(stub) {
|
|
2561
2605
|
return async (request, port) => {
|
|
@@ -2657,14 +2701,23 @@ var Sandbox = class extends Container {
|
|
|
2657
2701
|
await this.ctx.storage.put("keepAliveEnabled", keepAlive);
|
|
2658
2702
|
}
|
|
2659
2703
|
async setEnvVars(envVars) {
|
|
2704
|
+
const { toSet, toUnset } = partitionEnvVars(envVars);
|
|
2705
|
+
for (const key of toUnset) delete this.envVars[key];
|
|
2660
2706
|
this.envVars = {
|
|
2661
2707
|
...this.envVars,
|
|
2662
|
-
...
|
|
2708
|
+
...toSet
|
|
2663
2709
|
};
|
|
2664
|
-
if (this.defaultSession)
|
|
2665
|
-
const
|
|
2666
|
-
|
|
2667
|
-
|
|
2710
|
+
if (this.defaultSession) {
|
|
2711
|
+
for (const key of toUnset) {
|
|
2712
|
+
const unsetCommand = `unset ${key}`;
|
|
2713
|
+
const result = await this.client.commands.execute(unsetCommand, this.defaultSession);
|
|
2714
|
+
if (result.exitCode !== 0) throw new Error(`Failed to unset ${key}: ${result.stderr || "Unknown error"}`);
|
|
2715
|
+
}
|
|
2716
|
+
for (const [key, value] of Object.entries(toSet)) {
|
|
2717
|
+
const exportCommand = `export ${key}=${shellEscape(value)}`;
|
|
2718
|
+
const result = await this.client.commands.execute(exportCommand, this.defaultSession);
|
|
2719
|
+
if (result.exitCode !== 0) throw new Error(`Failed to set ${key}: ${result.stderr || "Unknown error"}`);
|
|
2720
|
+
}
|
|
2668
2721
|
}
|
|
2669
2722
|
}
|
|
2670
2723
|
/**
|
|
@@ -3422,7 +3475,7 @@ var Sandbox = class extends Container {
|
|
|
3422
3475
|
const requestOptions = {
|
|
3423
3476
|
...options?.processId !== void 0 && { processId: options.processId },
|
|
3424
3477
|
...options?.timeout !== void 0 && { timeoutMs: options.timeout },
|
|
3425
|
-
...options?.env !== void 0 && { env: options.env },
|
|
3478
|
+
...options?.env !== void 0 && { env: filterEnvVars(options.env) },
|
|
3426
3479
|
...options?.cwd !== void 0 && { cwd: options.cwd },
|
|
3427
3480
|
...options?.encoding !== void 0 && { encoding: options.encoding },
|
|
3428
3481
|
...options?.autoCleanup !== void 0 && { autoCleanup: options.autoCleanup }
|
|
@@ -3590,6 +3643,30 @@ var Sandbox = class extends Container {
|
|
|
3590
3643
|
const session = sessionId ?? await this.ensureDefaultSession();
|
|
3591
3644
|
return this.client.files.exists(path, session);
|
|
3592
3645
|
}
|
|
3646
|
+
/**
|
|
3647
|
+
* Expose a port and get a preview URL for accessing services running in the sandbox
|
|
3648
|
+
*
|
|
3649
|
+
* @param port - Port number to expose (1024-65535)
|
|
3650
|
+
* @param options - Configuration options
|
|
3651
|
+
* @param options.hostname - Your Worker's domain name (required for preview URL construction)
|
|
3652
|
+
* @param options.name - Optional friendly name for the port
|
|
3653
|
+
* @param options.token - Optional custom token for the preview URL (1-16 characters: lowercase letters, numbers, hyphens, underscores)
|
|
3654
|
+
* If not provided, a random 16-character token will be generated automatically
|
|
3655
|
+
* @returns Preview URL information including the full URL, port number, and optional name
|
|
3656
|
+
*
|
|
3657
|
+
* @example
|
|
3658
|
+
* // With auto-generated token
|
|
3659
|
+
* const { url } = await sandbox.exposePort(8080, { hostname: 'example.com' });
|
|
3660
|
+
* // url: https://8080-sandbox-id-abc123random4567.example.com
|
|
3661
|
+
*
|
|
3662
|
+
* @example
|
|
3663
|
+
* // With custom token for stable URLs across deployments
|
|
3664
|
+
* const { url } = await sandbox.exposePort(8080, {
|
|
3665
|
+
* hostname: 'example.com',
|
|
3666
|
+
* token: 'my-token-v1'
|
|
3667
|
+
* });
|
|
3668
|
+
* // url: https://8080-sandbox-id-my-token-v1.example.com
|
|
3669
|
+
*/
|
|
3593
3670
|
async exposePort(port, options) {
|
|
3594
3671
|
if (options.hostname.endsWith(".workers.dev")) throw new CustomDomainRequiredError({
|
|
3595
3672
|
code: ErrorCode.CUSTOM_DOMAIN_REQUIRED,
|
|
@@ -3598,11 +3675,17 @@ var Sandbox = class extends Container {
|
|
|
3598
3675
|
httpStatus: 400,
|
|
3599
3676
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3600
3677
|
});
|
|
3601
|
-
const sessionId = await this.ensureDefaultSession();
|
|
3602
|
-
await this.client.ports.exposePort(port, sessionId, options?.name);
|
|
3603
3678
|
if (!this.sandboxName) throw new Error("Sandbox name not available. Ensure sandbox is accessed through getSandbox()");
|
|
3604
|
-
|
|
3679
|
+
let token;
|
|
3680
|
+
if (options.token !== void 0) {
|
|
3681
|
+
this.validateCustomToken(options.token);
|
|
3682
|
+
token = options.token;
|
|
3683
|
+
} else token = this.generatePortToken();
|
|
3605
3684
|
const tokens = await this.ctx.storage.get("portTokens") || {};
|
|
3685
|
+
const existingPort = Object.entries(tokens).find(([p, t]) => t === token && p !== port.toString());
|
|
3686
|
+
if (existingPort) throw new SecurityError(`Token '${token}' is already in use by port ${existingPort[0]}. Please use a different token.`);
|
|
3687
|
+
const sessionId = await this.ensureDefaultSession();
|
|
3688
|
+
await this.client.ports.exposePort(port, sessionId, options?.name);
|
|
3606
3689
|
tokens[port.toString()] = token;
|
|
3607
3690
|
await this.ctx.storage.put("portTokens", tokens);
|
|
3608
3691
|
return {
|
|
@@ -3652,12 +3735,21 @@ var Sandbox = class extends Container {
|
|
|
3652
3735
|
this.logger.error("Port is exposed but has no token - bug detected", void 0, { port });
|
|
3653
3736
|
return false;
|
|
3654
3737
|
}
|
|
3655
|
-
|
|
3738
|
+
if (storedToken.length !== token.length) return false;
|
|
3739
|
+
const encoder = new TextEncoder();
|
|
3740
|
+
const a = encoder.encode(storedToken);
|
|
3741
|
+
const b = encoder.encode(token);
|
|
3742
|
+
return crypto.subtle.timingSafeEqual(a, b);
|
|
3743
|
+
}
|
|
3744
|
+
validateCustomToken(token) {
|
|
3745
|
+
if (token.length === 0) throw new SecurityError(`Custom token cannot be empty.`);
|
|
3746
|
+
if (token.length > 16) throw new SecurityError(`Custom token too long. Maximum 16 characters allowed. Received: ${token.length} characters.`);
|
|
3747
|
+
if (!/^[a-z0-9_]+$/.test(token)) throw new SecurityError(`Custom token must contain only lowercase letters (a-z), numbers (0-9), and underscores (_). Invalid token provided.`);
|
|
3656
3748
|
}
|
|
3657
3749
|
generatePortToken() {
|
|
3658
3750
|
const array = new Uint8Array(12);
|
|
3659
3751
|
crypto.getRandomValues(array);
|
|
3660
|
-
return btoa(String.fromCharCode(...array)).replace(/\+/g, "
|
|
3752
|
+
return btoa(String.fromCharCode(...array)).replace(/\+/g, "_").replace(/\//g, "_").replace(/=/g, "").toLowerCase();
|
|
3661
3753
|
}
|
|
3662
3754
|
constructPreviewUrl(port, sandboxId, hostname, token) {
|
|
3663
3755
|
if (!validatePort(port)) throw new SecurityError(`Invalid port number: ${port}. Must be between 1024-65535 and not reserved.`);
|
|
@@ -3690,11 +3782,11 @@ var Sandbox = class extends Container {
|
|
|
3690
3782
|
*/
|
|
3691
3783
|
async createSession(options) {
|
|
3692
3784
|
const sessionId = options?.id || `session-${Date.now()}`;
|
|
3693
|
-
const
|
|
3785
|
+
const filteredEnv = filterEnvVars({
|
|
3694
3786
|
...this.envVars,
|
|
3695
3787
|
...options?.env ?? {}
|
|
3696
|
-
};
|
|
3697
|
-
const envPayload = Object.keys(
|
|
3788
|
+
});
|
|
3789
|
+
const envPayload = Object.keys(filteredEnv).length > 0 ? filteredEnv : void 0;
|
|
3698
3790
|
await this.client.utils.createSession({
|
|
3699
3791
|
id: sessionId,
|
|
3700
3792
|
...envPayload && { env: envPayload },
|
|
@@ -3734,13 +3826,10 @@ var Sandbox = class extends Container {
|
|
|
3734
3826
|
timestamp: response.timestamp
|
|
3735
3827
|
};
|
|
3736
3828
|
}
|
|
3737
|
-
/**
|
|
3738
|
-
* Internal helper to create ExecutionSession wrapper for a given sessionId
|
|
3739
|
-
* Used by both createSession and getSession
|
|
3740
|
-
*/
|
|
3741
3829
|
getSessionWrapper(sessionId) {
|
|
3742
3830
|
return {
|
|
3743
3831
|
id: sessionId,
|
|
3832
|
+
terminal: null,
|
|
3744
3833
|
exec: (command, options) => this.execWithSession(command, sessionId, options),
|
|
3745
3834
|
execStream: (command, options) => this.execStreamWithSession(command, sessionId, options),
|
|
3746
3835
|
startProcess: (command, options) => this.startProcess(command, options, sessionId),
|
|
@@ -3774,9 +3863,15 @@ var Sandbox = class extends Container {
|
|
|
3774
3863
|
sessionId
|
|
3775
3864
|
}),
|
|
3776
3865
|
setEnvVars: async (envVars) => {
|
|
3866
|
+
const { toSet, toUnset } = partitionEnvVars(envVars);
|
|
3777
3867
|
try {
|
|
3778
|
-
for (const
|
|
3779
|
-
const
|
|
3868
|
+
for (const key of toUnset) {
|
|
3869
|
+
const unsetCommand = `unset ${key}`;
|
|
3870
|
+
const result = await this.client.commands.execute(unsetCommand, sessionId);
|
|
3871
|
+
if (result.exitCode !== 0) throw new Error(`Failed to unset ${key}: ${result.stderr || "Unknown error"}`);
|
|
3872
|
+
}
|
|
3873
|
+
for (const [key, value] of Object.entries(toSet)) {
|
|
3874
|
+
const exportCommand = `export ${key}=${shellEscape(value)}`;
|
|
3780
3875
|
const result = await this.client.commands.execute(exportCommand, sessionId);
|
|
3781
3876
|
if (result.exitCode !== 0) throw new Error(`Failed to set ${key}: ${result.stderr || "Unknown error"}`);
|
|
3782
3877
|
}
|
|
@@ -3933,5 +4028,5 @@ async function collectFile(stream) {
|
|
|
3933
4028
|
}
|
|
3934
4029
|
|
|
3935
4030
|
//#endregion
|
|
3936
|
-
export { BucketMountError, CodeInterpreter, CommandClient, FileClient, GitClient, InvalidMountConfigError, MissingCredentialsError, PortClient, ProcessClient, ProcessExitedBeforeReadyError, ProcessReadyTimeoutError, S3FSMountError, Sandbox, SandboxClient, UtilityClient, asyncIterableToSSEStream, collectFile, getSandbox, isExecResult, isProcess, isProcessStatus, parseSSEStream, proxyToSandbox, responseToAsyncIterable, streamFile };
|
|
4031
|
+
export { BucketMountError, CodeInterpreter, CommandClient, FileClient, GitClient, InvalidMountConfigError, MissingCredentialsError, PortClient, ProcessClient, ProcessExitedBeforeReadyError, ProcessReadyTimeoutError, S3FSMountError, Sandbox, SandboxClient, UtilityClient, asyncIterableToSSEStream, collectFile, getSandbox, isExecResult, isProcess, isProcessStatus, parseSSEStream, proxyTerminal, proxyToSandbox, responseToAsyncIterable, streamFile };
|
|
3937
4032
|
//# sourceMappingURL=index.js.map
|