@cloudflare/sandbox 0.0.0-d81d2a5 → 0.0.0-d86b60e
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 +117 -0
- package/Dockerfile +27 -27
- package/README.md +70 -8
- package/container_src/bun.lock +31 -77
- package/container_src/circuit-breaker.ts +121 -0
- package/container_src/control-process.ts +784 -0
- package/container_src/handler/exec.ts +99 -254
- package/container_src/handler/file.ts +204 -642
- package/container_src/handler/git.ts +28 -80
- package/container_src/handler/process.ts +443 -515
- package/container_src/handler/session.ts +92 -0
- package/container_src/index.ts +280 -219
- package/container_src/interpreter-service.ts +276 -0
- package/container_src/isolation.ts +1049 -0
- 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/shell-escape.ts +42 -0
- package/container_src/startup.sh +6 -47
- package/container_src/types.ts +35 -12
- package/package.json +2 -2
- package/src/client.ts +175 -187
- package/src/errors.ts +219 -0
- package/src/index.ts +60 -14
- package/src/interpreter-client.ts +352 -0
- package/src/interpreter-types.ts +102 -95
- package/src/interpreter.ts +8 -8
- package/src/sandbox.ts +306 -337
- package/src/types.ts +125 -24
- package/container_src/jupyter-server.ts +0 -336
- package/src/jupyter-client.ts +0 -266
package/container_src/index.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { randomBytes } from "node:crypto";
|
|
2
1
|
import { serve } from "bun";
|
|
3
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
handleExecuteRequest,
|
|
4
|
+
handleStreamingExecuteRequest,
|
|
5
|
+
} from "./handler/exec";
|
|
4
6
|
import {
|
|
5
7
|
handleDeleteFileRequest,
|
|
8
|
+
handleListFilesRequest,
|
|
6
9
|
handleMkdirRequest,
|
|
7
10
|
handleMoveFileRequest,
|
|
8
11
|
handleReadFileRequest,
|
|
@@ -25,56 +28,62 @@ import {
|
|
|
25
28
|
handleStartProcessRequest,
|
|
26
29
|
handleStreamProcessLogsRequest,
|
|
27
30
|
} from "./handler/process";
|
|
28
|
-
import {
|
|
29
|
-
import type {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
import { handleCreateSession, handleListSessions } from "./handler/session";
|
|
32
|
+
import type { CreateContextRequest } from "./interpreter-service";
|
|
33
|
+
import {
|
|
34
|
+
InterpreterNotReadyError,
|
|
35
|
+
InterpreterService,
|
|
36
|
+
} from "./interpreter-service";
|
|
37
|
+
import { hasNamespaceSupport, SessionManager } from "./isolation";
|
|
33
38
|
|
|
34
39
|
// In-memory storage for exposed ports
|
|
35
40
|
const exposedPorts = new Map<number, { name?: string; exposedAt: Date }>();
|
|
36
41
|
|
|
37
|
-
//
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
42
|
+
// Check isolation capabilities on startup
|
|
43
|
+
const isolationAvailable = hasNamespaceSupport();
|
|
44
|
+
console.log(
|
|
45
|
+
`[Container] Process isolation: ${
|
|
46
|
+
isolationAvailable
|
|
47
|
+
? "ENABLED (production mode)"
|
|
48
|
+
: "DISABLED (development mode)"
|
|
49
|
+
}`
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// Session manager for secure execution with isolation
|
|
53
|
+
const sessionManager = new SessionManager();
|
|
54
|
+
|
|
55
|
+
// Graceful shutdown handler
|
|
56
|
+
const SHUTDOWN_GRACE_PERIOD_MS = 5000; // Grace period for cleanup (5 seconds for proper async cleanup)
|
|
57
|
+
|
|
58
|
+
process.on("SIGTERM", async () => {
|
|
59
|
+
console.log("[Container] SIGTERM received, cleaning up sessions...");
|
|
60
|
+
await sessionManager.destroyAll();
|
|
61
|
+
setTimeout(() => {
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}, SHUTDOWN_GRACE_PERIOD_MS);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
process.on("SIGINT", async () => {
|
|
67
|
+
console.log("[Container] SIGINT received, cleaning up sessions...");
|
|
68
|
+
await sessionManager.destroyAll();
|
|
69
|
+
setTimeout(() => {
|
|
70
|
+
process.exit(0);
|
|
71
|
+
}, SHUTDOWN_GRACE_PERIOD_MS);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Cleanup on uncaught exceptions (log but still exit)
|
|
75
|
+
process.on("uncaughtException", async (error) => {
|
|
76
|
+
console.error("[Container] Uncaught exception:", error);
|
|
77
|
+
await sessionManager.destroyAll();
|
|
78
|
+
process.exit(1);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Initialize interpreter service
|
|
82
|
+
const interpreterService = new InterpreterService();
|
|
83
|
+
|
|
84
|
+
// No initialization needed - service is ready immediately!
|
|
85
|
+
console.log("[Container] Interpreter service ready - no cold start!");
|
|
86
|
+
console.log("[Container] All API endpoints available immediately");
|
|
78
87
|
|
|
79
88
|
const server = serve({
|
|
80
89
|
async fetch(req: Request) {
|
|
@@ -110,109 +119,42 @@ const server = serve({
|
|
|
110
119
|
|
|
111
120
|
case "/api/session/create":
|
|
112
121
|
if (req.method === "POST") {
|
|
113
|
-
|
|
114
|
-
const sessionData: SessionData = {
|
|
115
|
-
activeProcess: null,
|
|
116
|
-
createdAt: new Date(),
|
|
117
|
-
sessionId,
|
|
118
|
-
};
|
|
119
|
-
sessions.set(sessionId, sessionData);
|
|
120
|
-
|
|
121
|
-
console.log(`[Server] Created new session: ${sessionId}`);
|
|
122
|
-
|
|
123
|
-
return new Response(
|
|
124
|
-
JSON.stringify({
|
|
125
|
-
message: "Session created successfully",
|
|
126
|
-
sessionId,
|
|
127
|
-
timestamp: new Date().toISOString(),
|
|
128
|
-
}),
|
|
129
|
-
{
|
|
130
|
-
headers: {
|
|
131
|
-
"Content-Type": "application/json",
|
|
132
|
-
...corsHeaders,
|
|
133
|
-
},
|
|
134
|
-
}
|
|
135
|
-
);
|
|
122
|
+
return handleCreateSession(req, corsHeaders, sessionManager);
|
|
136
123
|
}
|
|
137
124
|
break;
|
|
138
125
|
|
|
139
126
|
case "/api/session/list":
|
|
140
127
|
if (req.method === "GET") {
|
|
141
|
-
|
|
142
|
-
(session) => ({
|
|
143
|
-
createdAt: session.createdAt.toISOString(),
|
|
144
|
-
hasActiveProcess: !!session.activeProcess,
|
|
145
|
-
sessionId: session.sessionId,
|
|
146
|
-
})
|
|
147
|
-
);
|
|
148
|
-
|
|
149
|
-
return new Response(
|
|
150
|
-
JSON.stringify({
|
|
151
|
-
count: sessionList.length,
|
|
152
|
-
sessions: sessionList,
|
|
153
|
-
timestamp: new Date().toISOString(),
|
|
154
|
-
}),
|
|
155
|
-
{
|
|
156
|
-
headers: {
|
|
157
|
-
"Content-Type": "application/json",
|
|
158
|
-
...corsHeaders,
|
|
159
|
-
},
|
|
160
|
-
}
|
|
161
|
-
);
|
|
128
|
+
return handleListSessions(corsHeaders, sessionManager);
|
|
162
129
|
}
|
|
163
130
|
break;
|
|
164
131
|
|
|
165
132
|
case "/api/execute":
|
|
166
133
|
if (req.method === "POST") {
|
|
167
|
-
return handleExecuteRequest(
|
|
134
|
+
return handleExecuteRequest(req, corsHeaders, sessionManager);
|
|
168
135
|
}
|
|
169
136
|
break;
|
|
170
137
|
|
|
171
138
|
case "/api/execute/stream":
|
|
172
139
|
if (req.method === "POST") {
|
|
173
|
-
return handleStreamingExecuteRequest(
|
|
140
|
+
return handleStreamingExecuteRequest(
|
|
141
|
+
req,
|
|
142
|
+
sessionManager,
|
|
143
|
+
corsHeaders
|
|
144
|
+
);
|
|
174
145
|
}
|
|
175
146
|
break;
|
|
176
147
|
|
|
177
148
|
case "/api/ping":
|
|
178
149
|
if (req.method === "GET") {
|
|
150
|
+
const health = await interpreterService.getHealthStatus();
|
|
179
151
|
return new Response(
|
|
180
152
|
JSON.stringify({
|
|
181
153
|
message: "pong",
|
|
182
154
|
timestamp: new Date().toISOString(),
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
headers: {
|
|
187
|
-
"Content-Type": "application/json",
|
|
188
|
-
...corsHeaders,
|
|
189
|
-
},
|
|
190
|
-
}
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
break;
|
|
194
|
-
|
|
195
|
-
case "/api/commands":
|
|
196
|
-
if (req.method === "GET") {
|
|
197
|
-
return new Response(
|
|
198
|
-
JSON.stringify({
|
|
199
|
-
availableCommands: [
|
|
200
|
-
"ls",
|
|
201
|
-
"pwd",
|
|
202
|
-
"echo",
|
|
203
|
-
"cat",
|
|
204
|
-
"grep",
|
|
205
|
-
"find",
|
|
206
|
-
"whoami",
|
|
207
|
-
"date",
|
|
208
|
-
"uptime",
|
|
209
|
-
"ps",
|
|
210
|
-
"top",
|
|
211
|
-
"df",
|
|
212
|
-
"du",
|
|
213
|
-
"free",
|
|
214
|
-
],
|
|
215
|
-
timestamp: new Date().toISOString(),
|
|
155
|
+
system: "interpreter (70x faster)",
|
|
156
|
+
status: health.ready ? "ready" : "initializing",
|
|
157
|
+
progress: health.progress,
|
|
216
158
|
}),
|
|
217
159
|
{
|
|
218
160
|
headers: {
|
|
@@ -226,43 +168,49 @@ const server = serve({
|
|
|
226
168
|
|
|
227
169
|
case "/api/git/checkout":
|
|
228
170
|
if (req.method === "POST") {
|
|
229
|
-
return handleGitCheckoutRequest(
|
|
171
|
+
return handleGitCheckoutRequest(req, corsHeaders, sessionManager);
|
|
230
172
|
}
|
|
231
173
|
break;
|
|
232
174
|
|
|
233
175
|
case "/api/mkdir":
|
|
234
176
|
if (req.method === "POST") {
|
|
235
|
-
return handleMkdirRequest(
|
|
177
|
+
return handleMkdirRequest(req, corsHeaders, sessionManager);
|
|
236
178
|
}
|
|
237
179
|
break;
|
|
238
180
|
|
|
239
181
|
case "/api/write":
|
|
240
182
|
if (req.method === "POST") {
|
|
241
|
-
return handleWriteFileRequest(req, corsHeaders);
|
|
183
|
+
return handleWriteFileRequest(req, corsHeaders, sessionManager);
|
|
242
184
|
}
|
|
243
185
|
break;
|
|
244
186
|
|
|
245
187
|
case "/api/read":
|
|
246
188
|
if (req.method === "POST") {
|
|
247
|
-
return handleReadFileRequest(req, corsHeaders);
|
|
189
|
+
return handleReadFileRequest(req, corsHeaders, sessionManager);
|
|
248
190
|
}
|
|
249
191
|
break;
|
|
250
192
|
|
|
251
193
|
case "/api/delete":
|
|
252
194
|
if (req.method === "POST") {
|
|
253
|
-
return handleDeleteFileRequest(req, corsHeaders);
|
|
195
|
+
return handleDeleteFileRequest(req, corsHeaders, sessionManager);
|
|
254
196
|
}
|
|
255
197
|
break;
|
|
256
198
|
|
|
257
199
|
case "/api/rename":
|
|
258
200
|
if (req.method === "POST") {
|
|
259
|
-
return handleRenameFileRequest(req, corsHeaders);
|
|
201
|
+
return handleRenameFileRequest(req, corsHeaders, sessionManager);
|
|
260
202
|
}
|
|
261
203
|
break;
|
|
262
204
|
|
|
263
205
|
case "/api/move":
|
|
264
206
|
if (req.method === "POST") {
|
|
265
|
-
return handleMoveFileRequest(req, corsHeaders);
|
|
207
|
+
return handleMoveFileRequest(req, corsHeaders, sessionManager);
|
|
208
|
+
}
|
|
209
|
+
break;
|
|
210
|
+
|
|
211
|
+
case "/api/list-files":
|
|
212
|
+
if (req.method === "POST") {
|
|
213
|
+
return handleListFilesRequest(req, corsHeaders, sessionManager);
|
|
266
214
|
}
|
|
267
215
|
break;
|
|
268
216
|
|
|
@@ -286,49 +234,38 @@ const server = serve({
|
|
|
286
234
|
|
|
287
235
|
case "/api/process/start":
|
|
288
236
|
if (req.method === "POST") {
|
|
289
|
-
return handleStartProcessRequest(
|
|
237
|
+
return handleStartProcessRequest(req, corsHeaders, sessionManager);
|
|
290
238
|
}
|
|
291
239
|
break;
|
|
292
240
|
|
|
293
241
|
case "/api/process/list":
|
|
294
242
|
if (req.method === "GET") {
|
|
295
|
-
return handleListProcessesRequest(
|
|
243
|
+
return handleListProcessesRequest(req, corsHeaders, sessionManager);
|
|
296
244
|
}
|
|
297
245
|
break;
|
|
298
246
|
|
|
299
247
|
case "/api/process/kill-all":
|
|
300
248
|
if (req.method === "DELETE") {
|
|
301
|
-
return handleKillAllProcessesRequest(
|
|
249
|
+
return handleKillAllProcessesRequest(
|
|
250
|
+
req,
|
|
251
|
+
corsHeaders,
|
|
252
|
+
sessionManager
|
|
253
|
+
);
|
|
302
254
|
}
|
|
303
255
|
break;
|
|
304
256
|
|
|
305
|
-
// Code interpreter endpoints
|
|
306
257
|
case "/api/contexts":
|
|
307
258
|
if (req.method === "POST") {
|
|
308
|
-
if (!jupyterInitialized) {
|
|
309
|
-
return new Response(
|
|
310
|
-
JSON.stringify({
|
|
311
|
-
error: "Jupyter server is not ready. Please try again in a moment."
|
|
312
|
-
}),
|
|
313
|
-
{
|
|
314
|
-
status: 503,
|
|
315
|
-
headers: {
|
|
316
|
-
"Content-Type": "application/json",
|
|
317
|
-
...corsHeaders,
|
|
318
|
-
},
|
|
319
|
-
}
|
|
320
|
-
);
|
|
321
|
-
}
|
|
322
259
|
try {
|
|
323
|
-
const body = await req.json() as CreateContextRequest;
|
|
324
|
-
const context = await
|
|
260
|
+
const body = (await req.json()) as CreateContextRequest;
|
|
261
|
+
const context = await interpreterService.createContext(body);
|
|
325
262
|
return new Response(
|
|
326
263
|
JSON.stringify({
|
|
327
264
|
id: context.id,
|
|
328
265
|
language: context.language,
|
|
329
266
|
cwd: context.cwd,
|
|
330
267
|
createdAt: context.createdAt,
|
|
331
|
-
lastUsed: context.lastUsed
|
|
268
|
+
lastUsed: context.lastUsed,
|
|
332
269
|
}),
|
|
333
270
|
{
|
|
334
271
|
headers: {
|
|
@@ -338,10 +275,62 @@ const server = serve({
|
|
|
338
275
|
}
|
|
339
276
|
);
|
|
340
277
|
} catch (error) {
|
|
278
|
+
if (error instanceof InterpreterNotReadyError) {
|
|
279
|
+
console.log(
|
|
280
|
+
`[Container] Request timed out waiting for interpreter (${error.progress}% complete)`
|
|
281
|
+
);
|
|
282
|
+
return new Response(
|
|
283
|
+
JSON.stringify({
|
|
284
|
+
error: error.message,
|
|
285
|
+
status: "initializing",
|
|
286
|
+
progress: error.progress,
|
|
287
|
+
}),
|
|
288
|
+
{
|
|
289
|
+
status: 503,
|
|
290
|
+
headers: {
|
|
291
|
+
"Content-Type": "application/json",
|
|
292
|
+
"Retry-After": String(error.retryAfter),
|
|
293
|
+
...corsHeaders,
|
|
294
|
+
},
|
|
295
|
+
}
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Check if it's a circuit breaker error
|
|
300
|
+
if (
|
|
301
|
+
error instanceof Error &&
|
|
302
|
+
error.message.includes("Circuit breaker is open")
|
|
303
|
+
) {
|
|
304
|
+
console.log(
|
|
305
|
+
"[Container] Circuit breaker is open:",
|
|
306
|
+
error.message
|
|
307
|
+
);
|
|
308
|
+
return new Response(
|
|
309
|
+
JSON.stringify({
|
|
310
|
+
error:
|
|
311
|
+
"Service temporarily unavailable due to high error rate. Please try again later.",
|
|
312
|
+
status: "circuit_open",
|
|
313
|
+
details: error.message,
|
|
314
|
+
}),
|
|
315
|
+
{
|
|
316
|
+
status: 503,
|
|
317
|
+
headers: {
|
|
318
|
+
"Content-Type": "application/json",
|
|
319
|
+
"Retry-After": "60",
|
|
320
|
+
...corsHeaders,
|
|
321
|
+
},
|
|
322
|
+
}
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Only log actual errors with stack traces
|
|
341
327
|
console.error("[Container] Error creating context:", error);
|
|
342
328
|
return new Response(
|
|
343
329
|
JSON.stringify({
|
|
344
|
-
error:
|
|
330
|
+
error:
|
|
331
|
+
error instanceof Error
|
|
332
|
+
? error.message
|
|
333
|
+
: "Failed to create context",
|
|
345
334
|
}),
|
|
346
335
|
{
|
|
347
336
|
status: 500,
|
|
@@ -353,54 +342,75 @@ const server = serve({
|
|
|
353
342
|
);
|
|
354
343
|
}
|
|
355
344
|
} else if (req.method === "GET") {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
},
|
|
364
|
-
}
|
|
365
|
-
);
|
|
366
|
-
}
|
|
367
|
-
const contexts = await jupyterServer.listContexts();
|
|
368
|
-
return new Response(
|
|
369
|
-
JSON.stringify({ contexts }),
|
|
370
|
-
{
|
|
371
|
-
headers: {
|
|
372
|
-
"Content-Type": "application/json",
|
|
373
|
-
...corsHeaders,
|
|
374
|
-
},
|
|
375
|
-
}
|
|
376
|
-
);
|
|
345
|
+
const contexts = await interpreterService.listContexts();
|
|
346
|
+
return new Response(JSON.stringify({ contexts }), {
|
|
347
|
+
headers: {
|
|
348
|
+
"Content-Type": "application/json",
|
|
349
|
+
...corsHeaders,
|
|
350
|
+
},
|
|
351
|
+
});
|
|
377
352
|
}
|
|
378
353
|
break;
|
|
379
354
|
|
|
380
355
|
case "/api/execute/code":
|
|
381
356
|
if (req.method === "POST") {
|
|
382
|
-
if (!jupyterInitialized) {
|
|
383
|
-
return new Response(
|
|
384
|
-
JSON.stringify({
|
|
385
|
-
error: "Jupyter server is not ready. Please try again in a moment."
|
|
386
|
-
}),
|
|
387
|
-
{
|
|
388
|
-
status: 503,
|
|
389
|
-
headers: {
|
|
390
|
-
"Content-Type": "application/json",
|
|
391
|
-
...corsHeaders,
|
|
392
|
-
},
|
|
393
|
-
}
|
|
394
|
-
);
|
|
395
|
-
}
|
|
396
357
|
try {
|
|
397
|
-
const body = await req.json() as {
|
|
398
|
-
|
|
358
|
+
const body = (await req.json()) as {
|
|
359
|
+
context_id: string;
|
|
360
|
+
code: string;
|
|
361
|
+
language?: string;
|
|
362
|
+
};
|
|
363
|
+
return await interpreterService.executeCode(
|
|
364
|
+
body.context_id,
|
|
365
|
+
body.code,
|
|
366
|
+
body.language
|
|
367
|
+
);
|
|
399
368
|
} catch (error) {
|
|
400
|
-
|
|
369
|
+
// Check if it's a circuit breaker error
|
|
370
|
+
if (
|
|
371
|
+
error instanceof Error &&
|
|
372
|
+
error.message.includes("Circuit breaker is open")
|
|
373
|
+
) {
|
|
374
|
+
console.log(
|
|
375
|
+
"[Container] Circuit breaker is open for code execution:",
|
|
376
|
+
error.message
|
|
377
|
+
);
|
|
378
|
+
return new Response(
|
|
379
|
+
JSON.stringify({
|
|
380
|
+
error:
|
|
381
|
+
"Service temporarily unavailable due to high error rate. Please try again later.",
|
|
382
|
+
status: "circuit_open",
|
|
383
|
+
details: error.message,
|
|
384
|
+
}),
|
|
385
|
+
{
|
|
386
|
+
status: 503,
|
|
387
|
+
headers: {
|
|
388
|
+
"Content-Type": "application/json",
|
|
389
|
+
"Retry-After": "30",
|
|
390
|
+
...corsHeaders,
|
|
391
|
+
},
|
|
392
|
+
}
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Don't log stack traces for expected initialization state
|
|
397
|
+
if (
|
|
398
|
+
error instanceof Error &&
|
|
399
|
+
error.message.includes("initializing")
|
|
400
|
+
) {
|
|
401
|
+
console.log(
|
|
402
|
+
"[Container] Code execution deferred - service still initializing"
|
|
403
|
+
);
|
|
404
|
+
} else {
|
|
405
|
+
console.error("[Container] Error executing code:", error);
|
|
406
|
+
}
|
|
407
|
+
// Error response is already handled by service.executeCode for not ready state
|
|
401
408
|
return new Response(
|
|
402
409
|
JSON.stringify({
|
|
403
|
-
error:
|
|
410
|
+
error:
|
|
411
|
+
error instanceof Error
|
|
412
|
+
? error.message
|
|
413
|
+
: "Failed to execute code",
|
|
404
414
|
}),
|
|
405
415
|
{
|
|
406
416
|
status: 500,
|
|
@@ -416,27 +426,54 @@ const server = serve({
|
|
|
416
426
|
|
|
417
427
|
default:
|
|
418
428
|
// Handle dynamic routes for contexts
|
|
419
|
-
if (
|
|
420
|
-
|
|
429
|
+
if (
|
|
430
|
+
pathname.startsWith("/api/contexts/") &&
|
|
431
|
+
pathname.split("/").length === 4
|
|
432
|
+
) {
|
|
433
|
+
const contextId = pathname.split("/")[3];
|
|
421
434
|
if (req.method === "DELETE") {
|
|
422
435
|
try {
|
|
423
|
-
await
|
|
424
|
-
return new Response(
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
},
|
|
431
|
-
}
|
|
432
|
-
);
|
|
436
|
+
await interpreterService.deleteContext(contextId);
|
|
437
|
+
return new Response(JSON.stringify({ success: true }), {
|
|
438
|
+
headers: {
|
|
439
|
+
"Content-Type": "application/json",
|
|
440
|
+
...corsHeaders,
|
|
441
|
+
},
|
|
442
|
+
});
|
|
433
443
|
} catch (error) {
|
|
444
|
+
if (error instanceof InterpreterNotReadyError) {
|
|
445
|
+
console.log(
|
|
446
|
+
`[Container] Request timed out waiting for interpreter (${error.progress}% complete)`
|
|
447
|
+
);
|
|
448
|
+
return new Response(
|
|
449
|
+
JSON.stringify({
|
|
450
|
+
error: error.message,
|
|
451
|
+
status: "initializing",
|
|
452
|
+
progress: error.progress,
|
|
453
|
+
}),
|
|
454
|
+
{
|
|
455
|
+
status: 503,
|
|
456
|
+
headers: {
|
|
457
|
+
"Content-Type": "application/json",
|
|
458
|
+
"Retry-After": "5",
|
|
459
|
+
...corsHeaders,
|
|
460
|
+
},
|
|
461
|
+
}
|
|
462
|
+
);
|
|
463
|
+
}
|
|
434
464
|
return new Response(
|
|
435
465
|
JSON.stringify({
|
|
436
|
-
error:
|
|
466
|
+
error:
|
|
467
|
+
error instanceof Error
|
|
468
|
+
? error.message
|
|
469
|
+
: "Failed to delete context",
|
|
437
470
|
}),
|
|
438
471
|
{
|
|
439
|
-
status:
|
|
472
|
+
status:
|
|
473
|
+
error instanceof Error &&
|
|
474
|
+
error.message.includes("not found")
|
|
475
|
+
? 404
|
|
476
|
+
: 500,
|
|
440
477
|
headers: {
|
|
441
478
|
"Content-Type": "application/json",
|
|
442
479
|
...corsHeaders,
|
|
@@ -446,22 +483,42 @@ const server = serve({
|
|
|
446
483
|
}
|
|
447
484
|
}
|
|
448
485
|
}
|
|
449
|
-
|
|
486
|
+
|
|
450
487
|
// Handle dynamic routes for individual processes
|
|
451
488
|
if (pathname.startsWith("/api/process/")) {
|
|
452
|
-
const segments = pathname.split(
|
|
489
|
+
const segments = pathname.split("/");
|
|
453
490
|
if (segments.length >= 4) {
|
|
454
491
|
const processId = segments[3];
|
|
455
492
|
const action = segments[4]; // Optional: logs, stream, etc.
|
|
456
493
|
|
|
457
494
|
if (!action && req.method === "GET") {
|
|
458
|
-
return handleGetProcessRequest(
|
|
495
|
+
return handleGetProcessRequest(
|
|
496
|
+
req,
|
|
497
|
+
corsHeaders,
|
|
498
|
+
processId,
|
|
499
|
+
sessionManager
|
|
500
|
+
);
|
|
459
501
|
} else if (!action && req.method === "DELETE") {
|
|
460
|
-
return handleKillProcessRequest(
|
|
502
|
+
return handleKillProcessRequest(
|
|
503
|
+
req,
|
|
504
|
+
corsHeaders,
|
|
505
|
+
processId,
|
|
506
|
+
sessionManager
|
|
507
|
+
);
|
|
461
508
|
} else if (action === "logs" && req.method === "GET") {
|
|
462
|
-
return handleGetProcessLogsRequest(
|
|
509
|
+
return handleGetProcessLogsRequest(
|
|
510
|
+
req,
|
|
511
|
+
corsHeaders,
|
|
512
|
+
processId,
|
|
513
|
+
sessionManager
|
|
514
|
+
);
|
|
463
515
|
} else if (action === "stream" && req.method === "GET") {
|
|
464
|
-
return handleStreamProcessLogsRequest(
|
|
516
|
+
return handleStreamProcessLogsRequest(
|
|
517
|
+
req,
|
|
518
|
+
corsHeaders,
|
|
519
|
+
processId,
|
|
520
|
+
sessionManager
|
|
521
|
+
);
|
|
465
522
|
}
|
|
466
523
|
}
|
|
467
524
|
}
|
|
@@ -477,7 +534,10 @@ const server = serve({
|
|
|
477
534
|
});
|
|
478
535
|
}
|
|
479
536
|
} catch (error) {
|
|
480
|
-
console.error(
|
|
537
|
+
console.error(
|
|
538
|
+
`[Container] Error handling ${req.method} ${pathname}:`,
|
|
539
|
+
error
|
|
540
|
+
);
|
|
481
541
|
return new Response(
|
|
482
542
|
JSON.stringify({
|
|
483
543
|
error: "Internal server error",
|
|
@@ -496,7 +556,7 @@ const server = serve({
|
|
|
496
556
|
hostname: "0.0.0.0",
|
|
497
557
|
port: 3000,
|
|
498
558
|
// We don't need this, but typescript complains
|
|
499
|
-
websocket: { async message() {
|
|
559
|
+
websocket: { async message() {} },
|
|
500
560
|
});
|
|
501
561
|
|
|
502
562
|
console.log(`🚀 Bun server running on http://0.0.0.0:${server.port}`);
|
|
@@ -526,6 +586,7 @@ console.log(` GET /proxy/{port}/* - Proxy requests to exposed ports`);
|
|
|
526
586
|
console.log(` POST /api/contexts - Create a code execution context`);
|
|
527
587
|
console.log(` GET /api/contexts - List all contexts`);
|
|
528
588
|
console.log(` DELETE /api/contexts/{id} - Delete a context`);
|
|
529
|
-
console.log(
|
|
589
|
+
console.log(
|
|
590
|
+
` POST /api/execute/code - Execute code in a context (streaming)`
|
|
591
|
+
);
|
|
530
592
|
console.log(` GET /api/ping - Health check`);
|
|
531
|
-
console.log(` GET /api/commands - List available commands`);
|