@cloudflare/sandbox 0.0.0-603d05f → 0.0.0-66cc85b
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/CHANGELOG.md +36 -0
- package/Dockerfile +23 -24
- package/README.md +59 -6
- package/container_src/bun.lock +31 -77
- package/container_src/control-process.ts +1 -1
- package/container_src/handler/exec.ts +2 -2
- package/container_src/handler/file.ts +51 -0
- package/container_src/handler/session.ts +2 -2
- package/container_src/index.ts +43 -43
- package/container_src/interpreter-service.ts +276 -0
- package/container_src/isolation.ts +207 -33
- package/container_src/mime-processor.ts +1 -1
- package/container_src/package.json +4 -4
- package/container_src/runtime/executors/javascript/node_executor.ts +123 -0
- package/container_src/runtime/executors/python/ipython_executor.py +338 -0
- package/container_src/runtime/executors/typescript/ts_executor.ts +138 -0
- package/container_src/runtime/process-pool.ts +464 -0
- package/container_src/startup.sh +6 -79
- package/package.json +2 -2
- package/src/client.ts +39 -0
- package/src/errors.ts +15 -14
- package/src/file-stream.ts +162 -0
- package/src/index.ts +22 -5
- package/src/{jupyter-client.ts → interpreter-client.ts} +6 -3
- package/src/interpreter-types.ts +102 -95
- package/src/interpreter.ts +8 -8
- package/src/sandbox.ts +18 -5
- package/src/types.ts +69 -0
- package/container_src/jupyter-server.ts +0 -579
- package/container_src/jupyter-service.ts +0 -461
- package/container_src/jupyter_config.py +0 -48
package/src/interpreter.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { InterpreterClient } from "./interpreter-client.js";
|
|
1
2
|
import {
|
|
2
3
|
type CodeContext,
|
|
3
4
|
type CreateContextOptions,
|
|
@@ -5,15 +6,14 @@ import {
|
|
|
5
6
|
ResultImpl,
|
|
6
7
|
type RunCodeOptions,
|
|
7
8
|
} from "./interpreter-types.js";
|
|
8
|
-
import type { JupyterClient } from "./jupyter-client.js";
|
|
9
9
|
import type { Sandbox } from "./sandbox.js";
|
|
10
10
|
|
|
11
11
|
export class CodeInterpreter {
|
|
12
|
-
private
|
|
12
|
+
private interpreterClient: InterpreterClient;
|
|
13
13
|
private contexts = new Map<string, CodeContext>();
|
|
14
14
|
|
|
15
15
|
constructor(sandbox: Sandbox) {
|
|
16
|
-
this.
|
|
16
|
+
this.interpreterClient = sandbox.client as InterpreterClient;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/**
|
|
@@ -22,7 +22,7 @@ export class CodeInterpreter {
|
|
|
22
22
|
async createCodeContext(
|
|
23
23
|
options: CreateContextOptions = {}
|
|
24
24
|
): Promise<CodeContext> {
|
|
25
|
-
const context = await this.
|
|
25
|
+
const context = await this.interpreterClient.createCodeContext(options);
|
|
26
26
|
this.contexts.set(context.id, context);
|
|
27
27
|
return context;
|
|
28
28
|
}
|
|
@@ -46,7 +46,7 @@ export class CodeInterpreter {
|
|
|
46
46
|
const execution = new Execution(code, context);
|
|
47
47
|
|
|
48
48
|
// Stream execution
|
|
49
|
-
await this.
|
|
49
|
+
await this.interpreterClient.runCodeStream(context.id, code, options.language, {
|
|
50
50
|
onStdout: (output) => {
|
|
51
51
|
execution.logs.stdout.push(output.text);
|
|
52
52
|
if (options.onStdout) return options.onStdout(output);
|
|
@@ -83,7 +83,7 @@ export class CodeInterpreter {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
// Create streaming response
|
|
86
|
-
const response = await this.
|
|
86
|
+
const response = await this.interpreterClient.doFetch("/api/execute/code", {
|
|
87
87
|
method: "POST",
|
|
88
88
|
headers: {
|
|
89
89
|
"Content-Type": "application/json",
|
|
@@ -116,7 +116,7 @@ export class CodeInterpreter {
|
|
|
116
116
|
* List all code contexts
|
|
117
117
|
*/
|
|
118
118
|
async listCodeContexts(): Promise<CodeContext[]> {
|
|
119
|
-
const contexts = await this.
|
|
119
|
+
const contexts = await this.interpreterClient.listCodeContexts();
|
|
120
120
|
|
|
121
121
|
// Update local cache
|
|
122
122
|
for (const context of contexts) {
|
|
@@ -130,7 +130,7 @@ export class CodeInterpreter {
|
|
|
130
130
|
* Delete a code context
|
|
131
131
|
*/
|
|
132
132
|
async deleteCodeContext(contextId: string): Promise<void> {
|
|
133
|
-
await this.
|
|
133
|
+
await this.interpreterClient.deleteCodeContext(contextId);
|
|
134
134
|
this.contexts.delete(contextId);
|
|
135
135
|
}
|
|
136
136
|
|
package/src/sandbox.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Container, getContainer } from "@cloudflare/containers";
|
|
2
2
|
import { CodeInterpreter } from "./interpreter";
|
|
3
|
+
import { InterpreterClient } from "./interpreter-client";
|
|
3
4
|
import type {
|
|
4
5
|
CodeContext,
|
|
5
6
|
CreateContextOptions,
|
|
6
7
|
ExecutionResult,
|
|
7
8
|
RunCodeOptions,
|
|
8
9
|
} from "./interpreter-types";
|
|
9
|
-
import { JupyterClient } from "./jupyter-client";
|
|
10
10
|
import { isLocalhostPattern } from "./request-handler";
|
|
11
11
|
import {
|
|
12
12
|
logSecurityEvent,
|
|
@@ -41,14 +41,14 @@ export function getSandbox(ns: DurableObjectNamespace<Sandbox>, id: string) {
|
|
|
41
41
|
export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
|
|
42
42
|
defaultPort = 3000; // Default port for the container's Bun server
|
|
43
43
|
sleepAfter = "20m"; // Keep container warm for 20 minutes to avoid cold starts
|
|
44
|
-
client:
|
|
44
|
+
client: InterpreterClient;
|
|
45
45
|
private sandboxName: string | null = null;
|
|
46
46
|
private codeInterpreter: CodeInterpreter;
|
|
47
47
|
private defaultSession: ExecutionSession | null = null;
|
|
48
48
|
|
|
49
|
-
constructor(ctx: DurableObjectState
|
|
49
|
+
constructor(ctx: DurableObjectState<{}>, env: Env) {
|
|
50
50
|
super(ctx, env);
|
|
51
|
-
this.client = new
|
|
51
|
+
this.client = new InterpreterClient({
|
|
52
52
|
onCommandComplete: (success, exitCode, _stdout, _stderr, command) => {
|
|
53
53
|
console.log(
|
|
54
54
|
`[Container] Command completed: ${command}, Success: ${success}, Exit code: ${exitCode}`
|
|
@@ -135,6 +135,10 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
|
|
|
135
135
|
return parseInt(proxyMatch[1]);
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
if (url.port) {
|
|
139
|
+
return parseInt(url.port);
|
|
140
|
+
}
|
|
141
|
+
|
|
138
142
|
// All other requests go to control plane on port 3000
|
|
139
143
|
// This includes /api/* endpoints and any other control requests
|
|
140
144
|
return 3000;
|
|
@@ -260,6 +264,11 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
|
|
|
260
264
|
return session.readFile(path, options);
|
|
261
265
|
}
|
|
262
266
|
|
|
267
|
+
async readFileStream(path: string): Promise<ReadableStream<Uint8Array>> {
|
|
268
|
+
const session = await this.ensureDefaultSession();
|
|
269
|
+
return session.readFileStream(path);
|
|
270
|
+
}
|
|
271
|
+
|
|
263
272
|
async listFiles(
|
|
264
273
|
path: string,
|
|
265
274
|
options: {
|
|
@@ -674,7 +683,11 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
|
|
|
674
683
|
readFile: async (path: string, options?: { encoding?: string }) => {
|
|
675
684
|
return await this.client.readFile(path, options?.encoding, sessionId);
|
|
676
685
|
},
|
|
677
|
-
|
|
686
|
+
|
|
687
|
+
readFileStream: async (path: string) => {
|
|
688
|
+
return await this.client.readFileStream(path, sessionId);
|
|
689
|
+
},
|
|
690
|
+
|
|
678
691
|
mkdir: async (path: string, options?: { recursive?: boolean }) => {
|
|
679
692
|
return await this.client.mkdir(path, options?.recursive, sessionId);
|
|
680
693
|
},
|
package/src/types.ts
CHANGED
|
@@ -215,6 +215,54 @@ export interface StreamOptions extends BaseExecOptions {
|
|
|
215
215
|
signal?: AbortSignal;
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
// File Streaming Types
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* SSE events for file streaming
|
|
222
|
+
*/
|
|
223
|
+
export type FileStreamEvent =
|
|
224
|
+
| {
|
|
225
|
+
type: 'metadata';
|
|
226
|
+
mimeType: string;
|
|
227
|
+
size: number;
|
|
228
|
+
isBinary: boolean;
|
|
229
|
+
encoding: 'utf-8' | 'base64';
|
|
230
|
+
}
|
|
231
|
+
| {
|
|
232
|
+
type: 'chunk';
|
|
233
|
+
data: string; // base64 for binary, UTF-8 for text
|
|
234
|
+
}
|
|
235
|
+
| {
|
|
236
|
+
type: 'complete';
|
|
237
|
+
bytesRead: number;
|
|
238
|
+
}
|
|
239
|
+
| {
|
|
240
|
+
type: 'error';
|
|
241
|
+
error: string;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* File metadata from streaming
|
|
246
|
+
*/
|
|
247
|
+
export interface FileMetadata {
|
|
248
|
+
mimeType: string;
|
|
249
|
+
size: number;
|
|
250
|
+
isBinary: boolean;
|
|
251
|
+
encoding: 'utf-8' | 'base64';
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* File stream chunk - either string (text) or Uint8Array (binary, auto-decoded)
|
|
256
|
+
*/
|
|
257
|
+
export type FileChunk = string | Uint8Array;
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* AsyncIterable of file chunks with metadata
|
|
261
|
+
*/
|
|
262
|
+
export interface FileStream extends AsyncIterable<FileChunk> {
|
|
263
|
+
metadata?: FileMetadata;
|
|
264
|
+
}
|
|
265
|
+
|
|
218
266
|
// Error Types
|
|
219
267
|
|
|
220
268
|
export class SandboxError extends Error {
|
|
@@ -359,6 +407,7 @@ export interface ISandbox {
|
|
|
359
407
|
renameFile(oldPath: string, newPath: string): Promise<RenameFileResponse>;
|
|
360
408
|
moveFile(sourcePath: string, destinationPath: string): Promise<MoveFileResponse>;
|
|
361
409
|
readFile(path: string, options?: { encoding?: string }): Promise<ReadFileResponse>;
|
|
410
|
+
readFileStream(path: string): Promise<ReadableStream<Uint8Array>>;
|
|
362
411
|
listFiles(path: string, options?: { recursive?: boolean; includeHidden?: boolean }): Promise<ListFilesResponse>;
|
|
363
412
|
|
|
364
413
|
// Port management
|
|
@@ -434,6 +483,26 @@ export interface ReadFileResponse {
|
|
|
434
483
|
path: string;
|
|
435
484
|
content: string;
|
|
436
485
|
timestamp: string;
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Encoding used for content (utf-8 for text, base64 for binary)
|
|
489
|
+
*/
|
|
490
|
+
encoding?: 'utf-8' | 'base64';
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Whether the file is detected as binary
|
|
494
|
+
*/
|
|
495
|
+
isBinary?: boolean;
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* MIME type of the file (e.g., 'image/png', 'text/plain')
|
|
499
|
+
*/
|
|
500
|
+
mimeType?: string;
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* File size in bytes
|
|
504
|
+
*/
|
|
505
|
+
size?: number;
|
|
437
506
|
}
|
|
438
507
|
|
|
439
508
|
export interface DeleteFileResponse {
|