@cloudflare/sandbox 0.0.0-dc66e8e → 0.0.0-e943505
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 +161 -0
- package/Dockerfile +46 -24
- package/README.md +846 -0
- package/container_src/bun.lock +76 -0
- package/container_src/circuit-breaker.ts +121 -0
- package/container_src/control-process.ts +784 -0
- package/container_src/handler/exec.ts +99 -251
- 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 +354 -123
- package/container_src/interpreter-service.ts +276 -0
- package/container_src/isolation.ts +1038 -0
- package/container_src/mime-processor.ts +255 -0
- package/container_src/package.json +9 -0
- 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 +11 -0
- package/container_src/types.ts +42 -14
- package/package.json +5 -4
- package/src/client.ts +206 -235
- package/src/errors.ts +219 -0
- package/src/index.ts +70 -15
- package/src/interpreter-client.ts +352 -0
- package/src/interpreter-types.ts +390 -0
- package/src/interpreter.ts +150 -0
- package/src/sandbox.ts +502 -400
- package/src/types.ts +140 -24
- package/tsconfig.json +1 -1
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,38 +28,65 @@ import {
|
|
|
25
28
|
handleStartProcessRequest,
|
|
26
29
|
handleStreamProcessLogsRequest,
|
|
27
30
|
} from "./handler/process";
|
|
28
|
-
import
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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";
|
|
32
38
|
|
|
33
39
|
// In-memory storage for exposed ports
|
|
34
40
|
const exposedPorts = new Map<number, { name?: string; exposedAt: Date }>();
|
|
35
41
|
|
|
36
|
-
//
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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();
|
|
54
83
|
|
|
55
|
-
//
|
|
56
|
-
|
|
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");
|
|
57
87
|
|
|
58
88
|
const server = serve({
|
|
59
|
-
fetch(req: Request) {
|
|
89
|
+
async fetch(req: Request) {
|
|
60
90
|
const url = new URL(req.url);
|
|
61
91
|
const pathname = url.pathname;
|
|
62
92
|
|
|
@@ -89,108 +119,42 @@ const server = serve({
|
|
|
89
119
|
|
|
90
120
|
case "/api/session/create":
|
|
91
121
|
if (req.method === "POST") {
|
|
92
|
-
|
|
93
|
-
const sessionData: SessionData = {
|
|
94
|
-
activeProcess: null,
|
|
95
|
-
createdAt: new Date(),
|
|
96
|
-
sessionId,
|
|
97
|
-
};
|
|
98
|
-
sessions.set(sessionId, sessionData);
|
|
99
|
-
|
|
100
|
-
console.log(`[Server] Created new session: ${sessionId}`);
|
|
101
|
-
|
|
102
|
-
return new Response(
|
|
103
|
-
JSON.stringify({
|
|
104
|
-
message: "Session created successfully",
|
|
105
|
-
sessionId,
|
|
106
|
-
timestamp: new Date().toISOString(),
|
|
107
|
-
}),
|
|
108
|
-
{
|
|
109
|
-
headers: {
|
|
110
|
-
"Content-Type": "application/json",
|
|
111
|
-
...corsHeaders,
|
|
112
|
-
},
|
|
113
|
-
}
|
|
114
|
-
);
|
|
122
|
+
return handleCreateSession(req, corsHeaders, sessionManager);
|
|
115
123
|
}
|
|
116
124
|
break;
|
|
117
125
|
|
|
118
126
|
case "/api/session/list":
|
|
119
127
|
if (req.method === "GET") {
|
|
120
|
-
|
|
121
|
-
(session) => ({
|
|
122
|
-
createdAt: session.createdAt.toISOString(),
|
|
123
|
-
hasActiveProcess: !!session.activeProcess,
|
|
124
|
-
sessionId: session.sessionId,
|
|
125
|
-
})
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
return new Response(
|
|
129
|
-
JSON.stringify({
|
|
130
|
-
count: sessionList.length,
|
|
131
|
-
sessions: sessionList,
|
|
132
|
-
timestamp: new Date().toISOString(),
|
|
133
|
-
}),
|
|
134
|
-
{
|
|
135
|
-
headers: {
|
|
136
|
-
"Content-Type": "application/json",
|
|
137
|
-
...corsHeaders,
|
|
138
|
-
},
|
|
139
|
-
}
|
|
140
|
-
);
|
|
128
|
+
return handleListSessions(corsHeaders, sessionManager);
|
|
141
129
|
}
|
|
142
130
|
break;
|
|
143
131
|
|
|
144
132
|
case "/api/execute":
|
|
145
133
|
if (req.method === "POST") {
|
|
146
|
-
return handleExecuteRequest(
|
|
134
|
+
return handleExecuteRequest(req, corsHeaders, sessionManager);
|
|
147
135
|
}
|
|
148
136
|
break;
|
|
149
137
|
|
|
150
138
|
case "/api/execute/stream":
|
|
151
139
|
if (req.method === "POST") {
|
|
152
|
-
return handleStreamingExecuteRequest(
|
|
140
|
+
return handleStreamingExecuteRequest(
|
|
141
|
+
req,
|
|
142
|
+
sessionManager,
|
|
143
|
+
corsHeaders
|
|
144
|
+
);
|
|
153
145
|
}
|
|
154
146
|
break;
|
|
155
147
|
|
|
156
148
|
case "/api/ping":
|
|
157
149
|
if (req.method === "GET") {
|
|
150
|
+
const health = await interpreterService.getHealthStatus();
|
|
158
151
|
return new Response(
|
|
159
152
|
JSON.stringify({
|
|
160
153
|
message: "pong",
|
|
161
154
|
timestamp: new Date().toISOString(),
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
"Content-Type": "application/json",
|
|
166
|
-
...corsHeaders,
|
|
167
|
-
},
|
|
168
|
-
}
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
break;
|
|
172
|
-
|
|
173
|
-
case "/api/commands":
|
|
174
|
-
if (req.method === "GET") {
|
|
175
|
-
return new Response(
|
|
176
|
-
JSON.stringify({
|
|
177
|
-
availableCommands: [
|
|
178
|
-
"ls",
|
|
179
|
-
"pwd",
|
|
180
|
-
"echo",
|
|
181
|
-
"cat",
|
|
182
|
-
"grep",
|
|
183
|
-
"find",
|
|
184
|
-
"whoami",
|
|
185
|
-
"date",
|
|
186
|
-
"uptime",
|
|
187
|
-
"ps",
|
|
188
|
-
"top",
|
|
189
|
-
"df",
|
|
190
|
-
"du",
|
|
191
|
-
"free",
|
|
192
|
-
],
|
|
193
|
-
timestamp: new Date().toISOString(),
|
|
155
|
+
system: "interpreter (70x faster)",
|
|
156
|
+
status: health.ready ? "ready" : "initializing",
|
|
157
|
+
progress: health.progress,
|
|
194
158
|
}),
|
|
195
159
|
{
|
|
196
160
|
headers: {
|
|
@@ -204,43 +168,49 @@ const server = serve({
|
|
|
204
168
|
|
|
205
169
|
case "/api/git/checkout":
|
|
206
170
|
if (req.method === "POST") {
|
|
207
|
-
return handleGitCheckoutRequest(
|
|
171
|
+
return handleGitCheckoutRequest(req, corsHeaders, sessionManager);
|
|
208
172
|
}
|
|
209
173
|
break;
|
|
210
174
|
|
|
211
175
|
case "/api/mkdir":
|
|
212
176
|
if (req.method === "POST") {
|
|
213
|
-
return handleMkdirRequest(
|
|
177
|
+
return handleMkdirRequest(req, corsHeaders, sessionManager);
|
|
214
178
|
}
|
|
215
179
|
break;
|
|
216
180
|
|
|
217
181
|
case "/api/write":
|
|
218
182
|
if (req.method === "POST") {
|
|
219
|
-
return handleWriteFileRequest(req, corsHeaders);
|
|
183
|
+
return handleWriteFileRequest(req, corsHeaders, sessionManager);
|
|
220
184
|
}
|
|
221
185
|
break;
|
|
222
186
|
|
|
223
187
|
case "/api/read":
|
|
224
188
|
if (req.method === "POST") {
|
|
225
|
-
return handleReadFileRequest(req, corsHeaders);
|
|
189
|
+
return handleReadFileRequest(req, corsHeaders, sessionManager);
|
|
226
190
|
}
|
|
227
191
|
break;
|
|
228
192
|
|
|
229
193
|
case "/api/delete":
|
|
230
194
|
if (req.method === "POST") {
|
|
231
|
-
return handleDeleteFileRequest(req, corsHeaders);
|
|
195
|
+
return handleDeleteFileRequest(req, corsHeaders, sessionManager);
|
|
232
196
|
}
|
|
233
197
|
break;
|
|
234
198
|
|
|
235
199
|
case "/api/rename":
|
|
236
200
|
if (req.method === "POST") {
|
|
237
|
-
return handleRenameFileRequest(req, corsHeaders);
|
|
201
|
+
return handleRenameFileRequest(req, corsHeaders, sessionManager);
|
|
238
202
|
}
|
|
239
203
|
break;
|
|
240
204
|
|
|
241
205
|
case "/api/move":
|
|
242
206
|
if (req.method === "POST") {
|
|
243
|
-
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);
|
|
244
214
|
}
|
|
245
215
|
break;
|
|
246
216
|
|
|
@@ -264,38 +234,291 @@ const server = serve({
|
|
|
264
234
|
|
|
265
235
|
case "/api/process/start":
|
|
266
236
|
if (req.method === "POST") {
|
|
267
|
-
return handleStartProcessRequest(
|
|
237
|
+
return handleStartProcessRequest(req, corsHeaders, sessionManager);
|
|
268
238
|
}
|
|
269
239
|
break;
|
|
270
240
|
|
|
271
241
|
case "/api/process/list":
|
|
272
242
|
if (req.method === "GET") {
|
|
273
|
-
return handleListProcessesRequest(
|
|
243
|
+
return handleListProcessesRequest(req, corsHeaders, sessionManager);
|
|
274
244
|
}
|
|
275
245
|
break;
|
|
276
246
|
|
|
277
247
|
case "/api/process/kill-all":
|
|
278
248
|
if (req.method === "DELETE") {
|
|
279
|
-
return handleKillAllProcessesRequest(
|
|
249
|
+
return handleKillAllProcessesRequest(
|
|
250
|
+
req,
|
|
251
|
+
corsHeaders,
|
|
252
|
+
sessionManager
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
break;
|
|
256
|
+
|
|
257
|
+
case "/api/contexts":
|
|
258
|
+
if (req.method === "POST") {
|
|
259
|
+
try {
|
|
260
|
+
const body = (await req.json()) as CreateContextRequest;
|
|
261
|
+
const context = await interpreterService.createContext(body);
|
|
262
|
+
return new Response(
|
|
263
|
+
JSON.stringify({
|
|
264
|
+
id: context.id,
|
|
265
|
+
language: context.language,
|
|
266
|
+
cwd: context.cwd,
|
|
267
|
+
createdAt: context.createdAt,
|
|
268
|
+
lastUsed: context.lastUsed,
|
|
269
|
+
}),
|
|
270
|
+
{
|
|
271
|
+
headers: {
|
|
272
|
+
"Content-Type": "application/json",
|
|
273
|
+
...corsHeaders,
|
|
274
|
+
},
|
|
275
|
+
}
|
|
276
|
+
);
|
|
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
|
|
327
|
+
console.error("[Container] Error creating context:", error);
|
|
328
|
+
return new Response(
|
|
329
|
+
JSON.stringify({
|
|
330
|
+
error:
|
|
331
|
+
error instanceof Error
|
|
332
|
+
? error.message
|
|
333
|
+
: "Failed to create context",
|
|
334
|
+
}),
|
|
335
|
+
{
|
|
336
|
+
status: 500,
|
|
337
|
+
headers: {
|
|
338
|
+
"Content-Type": "application/json",
|
|
339
|
+
...corsHeaders,
|
|
340
|
+
},
|
|
341
|
+
}
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
} else if (req.method === "GET") {
|
|
345
|
+
const contexts = await interpreterService.listContexts();
|
|
346
|
+
return new Response(JSON.stringify({ contexts }), {
|
|
347
|
+
headers: {
|
|
348
|
+
"Content-Type": "application/json",
|
|
349
|
+
...corsHeaders,
|
|
350
|
+
},
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
break;
|
|
354
|
+
|
|
355
|
+
case "/api/execute/code":
|
|
356
|
+
if (req.method === "POST") {
|
|
357
|
+
try {
|
|
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
|
+
);
|
|
368
|
+
} catch (error) {
|
|
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
|
|
408
|
+
return new Response(
|
|
409
|
+
JSON.stringify({
|
|
410
|
+
error:
|
|
411
|
+
error instanceof Error
|
|
412
|
+
? error.message
|
|
413
|
+
: "Failed to execute code",
|
|
414
|
+
}),
|
|
415
|
+
{
|
|
416
|
+
status: 500,
|
|
417
|
+
headers: {
|
|
418
|
+
"Content-Type": "application/json",
|
|
419
|
+
...corsHeaders,
|
|
420
|
+
},
|
|
421
|
+
}
|
|
422
|
+
);
|
|
423
|
+
}
|
|
280
424
|
}
|
|
281
425
|
break;
|
|
282
426
|
|
|
283
427
|
default:
|
|
428
|
+
// Handle dynamic routes for contexts
|
|
429
|
+
if (
|
|
430
|
+
pathname.startsWith("/api/contexts/") &&
|
|
431
|
+
pathname.split("/").length === 4
|
|
432
|
+
) {
|
|
433
|
+
const contextId = pathname.split("/")[3];
|
|
434
|
+
if (req.method === "DELETE") {
|
|
435
|
+
try {
|
|
436
|
+
await interpreterService.deleteContext(contextId);
|
|
437
|
+
return new Response(JSON.stringify({ success: true }), {
|
|
438
|
+
headers: {
|
|
439
|
+
"Content-Type": "application/json",
|
|
440
|
+
...corsHeaders,
|
|
441
|
+
},
|
|
442
|
+
});
|
|
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
|
+
}
|
|
464
|
+
return new Response(
|
|
465
|
+
JSON.stringify({
|
|
466
|
+
error:
|
|
467
|
+
error instanceof Error
|
|
468
|
+
? error.message
|
|
469
|
+
: "Failed to delete context",
|
|
470
|
+
}),
|
|
471
|
+
{
|
|
472
|
+
status:
|
|
473
|
+
error instanceof Error &&
|
|
474
|
+
error.message.includes("not found")
|
|
475
|
+
? 404
|
|
476
|
+
: 500,
|
|
477
|
+
headers: {
|
|
478
|
+
"Content-Type": "application/json",
|
|
479
|
+
...corsHeaders,
|
|
480
|
+
},
|
|
481
|
+
}
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
284
487
|
// Handle dynamic routes for individual processes
|
|
285
488
|
if (pathname.startsWith("/api/process/")) {
|
|
286
|
-
const segments = pathname.split(
|
|
489
|
+
const segments = pathname.split("/");
|
|
287
490
|
if (segments.length >= 4) {
|
|
288
491
|
const processId = segments[3];
|
|
289
492
|
const action = segments[4]; // Optional: logs, stream, etc.
|
|
290
493
|
|
|
291
494
|
if (!action && req.method === "GET") {
|
|
292
|
-
return handleGetProcessRequest(
|
|
495
|
+
return handleGetProcessRequest(
|
|
496
|
+
req,
|
|
497
|
+
corsHeaders,
|
|
498
|
+
processId,
|
|
499
|
+
sessionManager
|
|
500
|
+
);
|
|
293
501
|
} else if (!action && req.method === "DELETE") {
|
|
294
|
-
return handleKillProcessRequest(
|
|
502
|
+
return handleKillProcessRequest(
|
|
503
|
+
req,
|
|
504
|
+
corsHeaders,
|
|
505
|
+
processId,
|
|
506
|
+
sessionManager
|
|
507
|
+
);
|
|
295
508
|
} else if (action === "logs" && req.method === "GET") {
|
|
296
|
-
return handleGetProcessLogsRequest(
|
|
509
|
+
return handleGetProcessLogsRequest(
|
|
510
|
+
req,
|
|
511
|
+
corsHeaders,
|
|
512
|
+
processId,
|
|
513
|
+
sessionManager
|
|
514
|
+
);
|
|
297
515
|
} else if (action === "stream" && req.method === "GET") {
|
|
298
|
-
return handleStreamProcessLogsRequest(
|
|
516
|
+
return handleStreamProcessLogsRequest(
|
|
517
|
+
req,
|
|
518
|
+
corsHeaders,
|
|
519
|
+
processId,
|
|
520
|
+
sessionManager
|
|
521
|
+
);
|
|
299
522
|
}
|
|
300
523
|
}
|
|
301
524
|
}
|
|
@@ -311,7 +534,10 @@ const server = serve({
|
|
|
311
534
|
});
|
|
312
535
|
}
|
|
313
536
|
} catch (error) {
|
|
314
|
-
console.error(
|
|
537
|
+
console.error(
|
|
538
|
+
`[Container] Error handling ${req.method} ${pathname}:`,
|
|
539
|
+
error
|
|
540
|
+
);
|
|
315
541
|
return new Response(
|
|
316
542
|
JSON.stringify({
|
|
317
543
|
error: "Internal server error",
|
|
@@ -330,7 +556,7 @@ const server = serve({
|
|
|
330
556
|
hostname: "0.0.0.0",
|
|
331
557
|
port: 3000,
|
|
332
558
|
// We don't need this, but typescript complains
|
|
333
|
-
websocket: { async message() {
|
|
559
|
+
websocket: { async message() {} },
|
|
334
560
|
});
|
|
335
561
|
|
|
336
562
|
console.log(`🚀 Bun server running on http://0.0.0.0:${server.port}`);
|
|
@@ -357,5 +583,10 @@ console.log(` GET /api/process/{id}/logs - Get process logs`);
|
|
|
357
583
|
console.log(` GET /api/process/{id}/stream - Stream process logs (SSE)`);
|
|
358
584
|
console.log(` DELETE /api/process/kill-all - Kill all processes`);
|
|
359
585
|
console.log(` GET /proxy/{port}/* - Proxy requests to exposed ports`);
|
|
586
|
+
console.log(` POST /api/contexts - Create a code execution context`);
|
|
587
|
+
console.log(` GET /api/contexts - List all contexts`);
|
|
588
|
+
console.log(` DELETE /api/contexts/{id} - Delete a context`);
|
|
589
|
+
console.log(
|
|
590
|
+
` POST /api/execute/code - Execute code in a context (streaming)`
|
|
591
|
+
);
|
|
360
592
|
console.log(` GET /api/ping - Health check`);
|
|
361
|
-
console.log(` GET /api/commands - List available commands`);
|