@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.
@@ -1,11 +1,15 @@
1
1
  import { serve } from "bun";
2
- import { handleExecuteRequest, handleStreamingExecuteRequest } from "./handler/exec";
2
+ import {
3
+ handleExecuteRequest,
4
+ handleStreamingExecuteRequest,
5
+ } from "./handler/exec";
3
6
  import {
4
7
  handleDeleteFileRequest,
5
8
  handleListFilesRequest,
6
9
  handleMkdirRequest,
7
10
  handleMoveFileRequest,
8
11
  handleReadFileRequest,
12
+ handleReadFileStreamRequest,
9
13
  handleRenameFileRequest,
10
14
  handleWriteFileRequest,
11
15
  } from "./handler/file";
@@ -26,9 +30,12 @@ import {
26
30
  handleStreamProcessLogsRequest,
27
31
  } from "./handler/process";
28
32
  import { handleCreateSession, handleListSessions } from "./handler/session";
33
+ import type { CreateContextRequest } from "./interpreter-service";
34
+ import {
35
+ InterpreterNotReadyError,
36
+ InterpreterService,
37
+ } from "./interpreter-service";
29
38
  import { hasNamespaceSupport, SessionManager } from "./isolation";
30
- import type { CreateContextRequest } from "./jupyter-server";
31
- import { JupyterNotReadyError, JupyterService } from "./jupyter-service";
32
39
 
33
40
  // In-memory storage for exposed ports
34
41
  const exposedPorts = new Map<number, { name?: string; exposedAt: Date }>();
@@ -72,30 +79,15 @@ process.on("uncaughtException", async (error) => {
72
79
  process.exit(1);
73
80
  });
74
81
 
75
- // Initialize Jupyter service with graceful degradation
76
- const jupyterService = new JupyterService();
77
-
78
- // Start Jupyter initialization in background (non-blocking)
79
- console.log("[Container] Starting Jupyter initialization in background...");
80
- console.log(
81
- "[Container] API endpoints are available immediately. Jupyter-dependent features will be available shortly."
82
- );
82
+ // Initialize interpreter service
83
+ const interpreterService = new InterpreterService();
83
84
 
84
- jupyterService
85
- .initialize()
86
- .then(() => {
87
- console.log(
88
- "[Container] Jupyter fully initialized - all features available"
89
- );
90
- })
91
- .catch((error) => {
92
- console.error("[Container] Jupyter initialization failed:", error.message);
93
- console.error(
94
- "[Container] The API will continue in degraded mode without code execution capabilities"
95
- );
96
- });
85
+ // No initialization needed - service is ready immediately!
86
+ console.log("[Container] Interpreter service ready - no cold start!");
87
+ console.log("[Container] All API endpoints available immediately");
97
88
 
98
89
  const server = serve({
90
+ idleTimeout: 255,
99
91
  async fetch(req: Request) {
100
92
  const url = new URL(req.url);
101
93
  const pathname = url.pathname;
@@ -144,26 +136,27 @@ const server = serve({
144
136
  return handleExecuteRequest(req, corsHeaders, sessionManager);
145
137
  }
146
138
  break;
147
-
139
+
148
140
  case "/api/execute/stream":
149
141
  if (req.method === "POST") {
150
- return handleStreamingExecuteRequest(req, sessionManager, corsHeaders);
142
+ return handleStreamingExecuteRequest(
143
+ req,
144
+ sessionManager,
145
+ corsHeaders
146
+ );
151
147
  }
152
148
  break;
153
149
 
154
150
  case "/api/ping":
155
151
  if (req.method === "GET") {
156
- const health = await jupyterService.getHealthStatus();
152
+ const health = await interpreterService.getHealthStatus();
157
153
  return new Response(
158
154
  JSON.stringify({
159
155
  message: "pong",
160
156
  timestamp: new Date().toISOString(),
161
- jupyter: health.ready
162
- ? "ready"
163
- : health.initializing
164
- ? "initializing"
165
- : "not ready",
166
- jupyterHealth: health,
157
+ system: "interpreter (70x faster)",
158
+ status: health.ready ? "ready" : "initializing",
159
+ progress: health.progress,
167
160
  }),
168
161
  {
169
162
  headers: {
@@ -199,6 +192,12 @@ const server = serve({
199
192
  }
200
193
  break;
201
194
 
195
+ case "/api/read/stream":
196
+ if (req.method === "POST") {
197
+ return handleReadFileStreamRequest(req, corsHeaders, sessionManager);
198
+ }
199
+ break;
200
+
202
201
  case "/api/delete":
203
202
  if (req.method === "POST") {
204
203
  return handleDeleteFileRequest(req, corsHeaders, sessionManager);
@@ -267,7 +266,7 @@ const server = serve({
267
266
  if (req.method === "POST") {
268
267
  try {
269
268
  const body = (await req.json()) as CreateContextRequest;
270
- const context = await jupyterService.createContext(body);
269
+ const context = await interpreterService.createContext(body);
271
270
  return new Response(
272
271
  JSON.stringify({
273
272
  id: context.id,
@@ -284,9 +283,9 @@ const server = serve({
284
283
  }
285
284
  );
286
285
  } catch (error) {
287
- if (error instanceof JupyterNotReadyError) {
286
+ if (error instanceof InterpreterNotReadyError) {
288
287
  console.log(
289
- `[Container] Request timed out waiting for Jupyter (${error.progress}% complete)`
288
+ `[Container] Request timed out waiting for interpreter (${error.progress}% complete)`
290
289
  );
291
290
  return new Response(
292
291
  JSON.stringify({
@@ -351,7 +350,7 @@ const server = serve({
351
350
  );
352
351
  }
353
352
  } else if (req.method === "GET") {
354
- const contexts = await jupyterService.listContexts();
353
+ const contexts = await interpreterService.listContexts();
355
354
  return new Response(JSON.stringify({ contexts }), {
356
355
  headers: {
357
356
  "Content-Type": "application/json",
@@ -369,7 +368,7 @@ const server = serve({
369
368
  code: string;
370
369
  language?: string;
371
370
  };
372
- return await jupyterService.executeCode(
371
+ return await interpreterService.executeCode(
373
372
  body.context_id,
374
373
  body.code,
375
374
  body.language
@@ -408,12 +407,12 @@ const server = serve({
408
407
  error.message.includes("initializing")
409
408
  ) {
410
409
  console.log(
411
- "[Container] Code execution deferred - Jupyter still initializing"
410
+ "[Container] Code execution deferred - service still initializing"
412
411
  );
413
412
  } else {
414
413
  console.error("[Container] Error executing code:", error);
415
414
  }
416
- // Error response is already handled by jupyterService.executeCode for not ready state
415
+ // Error response is already handled by service.executeCode for not ready state
417
416
  return new Response(
418
417
  JSON.stringify({
419
418
  error:
@@ -442,7 +441,7 @@ const server = serve({
442
441
  const contextId = pathname.split("/")[3];
443
442
  if (req.method === "DELETE") {
444
443
  try {
445
- await jupyterService.deleteContext(contextId);
444
+ await interpreterService.deleteContext(contextId);
446
445
  return new Response(JSON.stringify({ success: true }), {
447
446
  headers: {
448
447
  "Content-Type": "application/json",
@@ -450,9 +449,9 @@ const server = serve({
450
449
  },
451
450
  });
452
451
  } catch (error) {
453
- if (error instanceof JupyterNotReadyError) {
452
+ if (error instanceof InterpreterNotReadyError) {
454
453
  console.log(
455
- `[Container] Request timed out waiting for Jupyter (${error.progress}% complete)`
454
+ `[Container] Request timed out waiting for interpreter (${error.progress}% complete)`
456
455
  );
457
456
  return new Response(
458
457
  JSON.stringify({
@@ -578,6 +577,7 @@ console.log(` POST /api/git/checkout - Checkout a git repository`);
578
577
  console.log(` POST /api/mkdir - Create a directory`);
579
578
  console.log(` POST /api/write - Write a file`);
580
579
  console.log(` POST /api/read - Read a file`);
580
+ console.log(` POST /api/read/stream - Stream a file (SSE)`);
581
581
  console.log(` POST /api/delete - Delete a file`);
582
582
  console.log(` POST /api/rename - Rename a file`);
583
583
  console.log(` POST /api/move - Move a file`);
@@ -0,0 +1,276 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { type InterpreterLanguage, processPool, type RichOutput } from "./runtime/process-pool";
3
+
4
+ export interface CreateContextRequest {
5
+ language?: string;
6
+ cwd?: string;
7
+ }
8
+
9
+ export interface Context {
10
+ id: string;
11
+ language: string;
12
+ cwd: string;
13
+ createdAt: string;
14
+ lastUsed: string;
15
+ }
16
+
17
+ export interface HealthStatus {
18
+ ready: boolean;
19
+ initializing: boolean;
20
+ progress: number;
21
+ }
22
+
23
+ export class InterpreterNotReadyError extends Error {
24
+ progress: number;
25
+ retryAfter: number;
26
+
27
+ constructor(message: string, progress: number = 100, retryAfter: number = 1) {
28
+ super(message);
29
+ this.progress = progress;
30
+ this.retryAfter = retryAfter;
31
+ this.name = "InterpreterNotReadyError";
32
+ }
33
+ }
34
+
35
+ export class InterpreterService {
36
+ private contexts: Map<string, Context> = new Map();
37
+
38
+ async getHealthStatus(): Promise<HealthStatus> {
39
+ return {
40
+ ready: true,
41
+ initializing: false,
42
+ progress: 100,
43
+ };
44
+ }
45
+
46
+ async createContext(request: CreateContextRequest): Promise<Context> {
47
+ const id = randomUUID();
48
+ const language = this.mapLanguage(request.language || "python");
49
+
50
+ const context: Context = {
51
+ id,
52
+ language,
53
+ cwd: request.cwd || "/workspace",
54
+ createdAt: new Date().toISOString(),
55
+ lastUsed: new Date().toISOString(),
56
+ };
57
+
58
+ this.contexts.set(id, context);
59
+ console.log(`[InterpreterService] Created context ${id} for ${language}`);
60
+
61
+ return context;
62
+ }
63
+
64
+ async listContexts(): Promise<Context[]> {
65
+ return Array.from(this.contexts.values());
66
+ }
67
+
68
+ async deleteContext(contextId: string): Promise<void> {
69
+ if (!this.contexts.has(contextId)) {
70
+ throw new Error(`Context ${contextId} not found`);
71
+ }
72
+
73
+ this.contexts.delete(contextId);
74
+ console.log(`[InterpreterService] Deleted context ${contextId}`);
75
+ }
76
+
77
+ async executeCode(
78
+ contextId: string,
79
+ code: string,
80
+ language?: string
81
+ ): Promise<Response> {
82
+ const context = this.contexts.get(contextId);
83
+ if (!context) {
84
+ return new Response(
85
+ JSON.stringify({
86
+ error: `Context ${contextId} not found`,
87
+ }),
88
+ {
89
+ status: 404,
90
+ headers: { "Content-Type": "application/json" },
91
+ }
92
+ );
93
+ }
94
+
95
+ context.lastUsed = new Date().toISOString();
96
+
97
+ const execLanguage = this.mapLanguage(language || context.language);
98
+
99
+ // Store reference to this for use in async function
100
+ const self = this;
101
+
102
+ const stream = new ReadableStream({
103
+ async start(controller) {
104
+ const encoder = new TextEncoder();
105
+ const startTime = Date.now();
106
+
107
+ try {
108
+ const result = await processPool.execute(
109
+ execLanguage,
110
+ code,
111
+ contextId,
112
+ 30000
113
+ );
114
+
115
+ const totalTime = Date.now() - startTime;
116
+ console.log(`[InterpreterService] Code execution completed in ${totalTime}ms`);
117
+
118
+ if (result.stdout) {
119
+ controller.enqueue(
120
+ encoder.encode(
121
+ `${JSON.stringify({
122
+ type: "stdout",
123
+ text: result.stdout,
124
+ })}\n`
125
+ )
126
+ );
127
+ }
128
+
129
+ if (result.stderr) {
130
+ controller.enqueue(
131
+ encoder.encode(
132
+ `${JSON.stringify({
133
+ type: "stderr",
134
+ text: result.stderr,
135
+ })}\n`
136
+ )
137
+ );
138
+ }
139
+
140
+ if (result.outputs && result.outputs.length > 0) {
141
+ for (const output of result.outputs) {
142
+ const outputData = self.formatOutputData(output);
143
+ controller.enqueue(
144
+ encoder.encode(
145
+ `${JSON.stringify({
146
+ type: "result",
147
+ ...outputData,
148
+ metadata: output.metadata || {},
149
+ })}\n`
150
+ )
151
+ );
152
+ }
153
+ }
154
+
155
+ if (result.success) {
156
+ controller.enqueue(
157
+ encoder.encode(
158
+ `${JSON.stringify({
159
+ type: "execution_complete",
160
+ execution_count: 1,
161
+ })}\n`
162
+ )
163
+ );
164
+ } else if (result.error) {
165
+ controller.enqueue(
166
+ encoder.encode(
167
+ `${JSON.stringify({
168
+ type: "error",
169
+ ename: result.error.type || "ExecutionError",
170
+ evalue: result.error.message || "Code execution failed",
171
+ traceback: result.error.traceback ? result.error.traceback.split('\n') : [],
172
+ })}\n`
173
+ )
174
+ );
175
+ } else {
176
+ controller.enqueue(
177
+ encoder.encode(
178
+ `${JSON.stringify({
179
+ type: "error",
180
+ ename: "ExecutionError",
181
+ evalue: result.stderr || "Code execution failed",
182
+ traceback: [],
183
+ })}\n`
184
+ )
185
+ );
186
+ }
187
+
188
+ controller.close();
189
+ } catch (error) {
190
+ console.error(`[InterpreterService] Code execution failed:`, error);
191
+
192
+ controller.enqueue(
193
+ encoder.encode(
194
+ `${JSON.stringify({
195
+ type: "error",
196
+ ename: "InternalError",
197
+ evalue: error instanceof Error ? error.message : String(error),
198
+ traceback: [],
199
+ })}\n`
200
+ )
201
+ );
202
+
203
+ controller.close();
204
+ }
205
+ },
206
+ });
207
+
208
+ return new Response(stream, {
209
+ headers: {
210
+ "Content-Type": "text/event-stream",
211
+ "Cache-Control": "no-cache",
212
+ Connection: "keep-alive",
213
+ },
214
+ });
215
+ }
216
+
217
+ private mapLanguage(language: string): InterpreterLanguage {
218
+ const normalized = language.toLowerCase();
219
+
220
+ switch (normalized) {
221
+ case "python":
222
+ case "python3":
223
+ return "python";
224
+ case "javascript":
225
+ case "js":
226
+ case "node":
227
+ return "javascript";
228
+ case "typescript":
229
+ case "ts":
230
+ return "typescript";
231
+ default:
232
+ console.warn(
233
+ `[InterpreterService] Unknown language ${language}, defaulting to python`
234
+ );
235
+ return "python";
236
+ }
237
+ }
238
+
239
+ private formatOutputData(output: RichOutput): Record<string, unknown> {
240
+ const result: Record<string, unknown> = {};
241
+
242
+ switch (output.type) {
243
+ case "image":
244
+ result.png = output.data;
245
+ break;
246
+ case "jpeg":
247
+ result.jpeg = output.data;
248
+ break;
249
+ case "svg":
250
+ result.svg = output.data;
251
+ break;
252
+ case "html":
253
+ result.html = output.data;
254
+ break;
255
+ case "json":
256
+ result.json = typeof output.data === 'string' ? JSON.parse(output.data) : output.data;
257
+ break;
258
+ case "latex":
259
+ result.latex = output.data;
260
+ break;
261
+ case "markdown":
262
+ result.markdown = output.data;
263
+ break;
264
+ case "javascript":
265
+ result.javascript = output.data;
266
+ break;
267
+ case "text":
268
+ result.text = output.data;
269
+ break;
270
+ default:
271
+ result.text = output.data || '';
272
+ }
273
+
274
+ return result;
275
+ }
276
+ }