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