@cloudflare/sandbox 0.0.0-d81d2a5 → 0.0.0-d951819
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 +129 -0
- package/Dockerfile +34 -27
- package/README.md +127 -12
- 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 +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 +289 -219
- package/container_src/interpreter-service.ts +276 -0
- package/container_src/isolation.ts +1213 -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 +214 -187
- package/src/errors.ts +219 -0
- package/src/file-stream.ts +162 -0
- package/src/index.ts +66 -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 +315 -337
- package/src/types.ts +194 -24
- package/container_src/jupyter-server.ts +0 -336
- package/src/jupyter-client.ts +0 -266
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,58 +29,65 @@ import {
|
|
|
25
29
|
handleStartProcessRequest,
|
|
26
30
|
handleStreamProcessLogsRequest,
|
|
27
31
|
} from "./handler/process";
|
|
28
|
-
import {
|
|
29
|
-
import type {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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";
|
|
33
39
|
|
|
34
40
|
// In-memory storage for exposed ports
|
|
35
41
|
const exposedPorts = new Map<number, { name?: string; exposedAt: Date }>();
|
|
36
42
|
|
|
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
|
-
|
|
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
|
+
});
|
|
66
|
+
|
|
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");
|
|
78
88
|
|
|
79
89
|
const server = serve({
|
|
90
|
+
idleTimeout: 255,
|
|
80
91
|
async fetch(req: Request) {
|
|
81
92
|
const url = new URL(req.url);
|
|
82
93
|
const pathname = url.pathname;
|
|
@@ -110,109 +121,42 @@ const server = serve({
|
|
|
110
121
|
|
|
111
122
|
case "/api/session/create":
|
|
112
123
|
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
|
-
);
|
|
124
|
+
return handleCreateSession(req, corsHeaders, sessionManager);
|
|
136
125
|
}
|
|
137
126
|
break;
|
|
138
127
|
|
|
139
128
|
case "/api/session/list":
|
|
140
129
|
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
|
-
);
|
|
130
|
+
return handleListSessions(corsHeaders, sessionManager);
|
|
162
131
|
}
|
|
163
132
|
break;
|
|
164
133
|
|
|
165
134
|
case "/api/execute":
|
|
166
135
|
if (req.method === "POST") {
|
|
167
|
-
return handleExecuteRequest(
|
|
136
|
+
return handleExecuteRequest(req, corsHeaders, sessionManager);
|
|
168
137
|
}
|
|
169
138
|
break;
|
|
170
139
|
|
|
171
140
|
case "/api/execute/stream":
|
|
172
141
|
if (req.method === "POST") {
|
|
173
|
-
return handleStreamingExecuteRequest(
|
|
142
|
+
return handleStreamingExecuteRequest(
|
|
143
|
+
req,
|
|
144
|
+
sessionManager,
|
|
145
|
+
corsHeaders
|
|
146
|
+
);
|
|
174
147
|
}
|
|
175
148
|
break;
|
|
176
149
|
|
|
177
150
|
case "/api/ping":
|
|
178
151
|
if (req.method === "GET") {
|
|
152
|
+
const health = await interpreterService.getHealthStatus();
|
|
179
153
|
return new Response(
|
|
180
154
|
JSON.stringify({
|
|
181
155
|
message: "pong",
|
|
182
156
|
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(),
|
|
157
|
+
system: "interpreter (70x faster)",
|
|
158
|
+
status: health.ready ? "ready" : "initializing",
|
|
159
|
+
progress: health.progress,
|
|
216
160
|
}),
|
|
217
161
|
{
|
|
218
162
|
headers: {
|
|
@@ -226,43 +170,55 @@ const server = serve({
|
|
|
226
170
|
|
|
227
171
|
case "/api/git/checkout":
|
|
228
172
|
if (req.method === "POST") {
|
|
229
|
-
return handleGitCheckoutRequest(
|
|
173
|
+
return handleGitCheckoutRequest(req, corsHeaders, sessionManager);
|
|
230
174
|
}
|
|
231
175
|
break;
|
|
232
176
|
|
|
233
177
|
case "/api/mkdir":
|
|
234
178
|
if (req.method === "POST") {
|
|
235
|
-
return handleMkdirRequest(
|
|
179
|
+
return handleMkdirRequest(req, corsHeaders, sessionManager);
|
|
236
180
|
}
|
|
237
181
|
break;
|
|
238
182
|
|
|
239
183
|
case "/api/write":
|
|
240
184
|
if (req.method === "POST") {
|
|
241
|
-
return handleWriteFileRequest(req, corsHeaders);
|
|
185
|
+
return handleWriteFileRequest(req, corsHeaders, sessionManager);
|
|
242
186
|
}
|
|
243
187
|
break;
|
|
244
188
|
|
|
245
189
|
case "/api/read":
|
|
246
190
|
if (req.method === "POST") {
|
|
247
|
-
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);
|
|
248
198
|
}
|
|
249
199
|
break;
|
|
250
200
|
|
|
251
201
|
case "/api/delete":
|
|
252
202
|
if (req.method === "POST") {
|
|
253
|
-
return handleDeleteFileRequest(req, corsHeaders);
|
|
203
|
+
return handleDeleteFileRequest(req, corsHeaders, sessionManager);
|
|
254
204
|
}
|
|
255
205
|
break;
|
|
256
206
|
|
|
257
207
|
case "/api/rename":
|
|
258
208
|
if (req.method === "POST") {
|
|
259
|
-
return handleRenameFileRequest(req, corsHeaders);
|
|
209
|
+
return handleRenameFileRequest(req, corsHeaders, sessionManager);
|
|
260
210
|
}
|
|
261
211
|
break;
|
|
262
212
|
|
|
263
213
|
case "/api/move":
|
|
264
214
|
if (req.method === "POST") {
|
|
265
|
-
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);
|
|
266
222
|
}
|
|
267
223
|
break;
|
|
268
224
|
|
|
@@ -286,49 +242,38 @@ const server = serve({
|
|
|
286
242
|
|
|
287
243
|
case "/api/process/start":
|
|
288
244
|
if (req.method === "POST") {
|
|
289
|
-
return handleStartProcessRequest(
|
|
245
|
+
return handleStartProcessRequest(req, corsHeaders, sessionManager);
|
|
290
246
|
}
|
|
291
247
|
break;
|
|
292
248
|
|
|
293
249
|
case "/api/process/list":
|
|
294
250
|
if (req.method === "GET") {
|
|
295
|
-
return handleListProcessesRequest(
|
|
251
|
+
return handleListProcessesRequest(req, corsHeaders, sessionManager);
|
|
296
252
|
}
|
|
297
253
|
break;
|
|
298
254
|
|
|
299
255
|
case "/api/process/kill-all":
|
|
300
256
|
if (req.method === "DELETE") {
|
|
301
|
-
return handleKillAllProcessesRequest(
|
|
257
|
+
return handleKillAllProcessesRequest(
|
|
258
|
+
req,
|
|
259
|
+
corsHeaders,
|
|
260
|
+
sessionManager
|
|
261
|
+
);
|
|
302
262
|
}
|
|
303
263
|
break;
|
|
304
264
|
|
|
305
|
-
// Code interpreter endpoints
|
|
306
265
|
case "/api/contexts":
|
|
307
266
|
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
267
|
try {
|
|
323
|
-
const body = await req.json() as CreateContextRequest;
|
|
324
|
-
const context = await
|
|
268
|
+
const body = (await req.json()) as CreateContextRequest;
|
|
269
|
+
const context = await interpreterService.createContext(body);
|
|
325
270
|
return new Response(
|
|
326
271
|
JSON.stringify({
|
|
327
272
|
id: context.id,
|
|
328
273
|
language: context.language,
|
|
329
274
|
cwd: context.cwd,
|
|
330
275
|
createdAt: context.createdAt,
|
|
331
|
-
lastUsed: context.lastUsed
|
|
276
|
+
lastUsed: context.lastUsed,
|
|
332
277
|
}),
|
|
333
278
|
{
|
|
334
279
|
headers: {
|
|
@@ -338,10 +283,62 @@ const server = serve({
|
|
|
338
283
|
}
|
|
339
284
|
);
|
|
340
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
|
|
341
335
|
console.error("[Container] Error creating context:", error);
|
|
342
336
|
return new Response(
|
|
343
337
|
JSON.stringify({
|
|
344
|
-
error:
|
|
338
|
+
error:
|
|
339
|
+
error instanceof Error
|
|
340
|
+
? error.message
|
|
341
|
+
: "Failed to create context",
|
|
345
342
|
}),
|
|
346
343
|
{
|
|
347
344
|
status: 500,
|
|
@@ -353,54 +350,75 @@ const server = serve({
|
|
|
353
350
|
);
|
|
354
351
|
}
|
|
355
352
|
} 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
|
-
);
|
|
353
|
+
const contexts = await interpreterService.listContexts();
|
|
354
|
+
return new Response(JSON.stringify({ contexts }), {
|
|
355
|
+
headers: {
|
|
356
|
+
"Content-Type": "application/json",
|
|
357
|
+
...corsHeaders,
|
|
358
|
+
},
|
|
359
|
+
});
|
|
377
360
|
}
|
|
378
361
|
break;
|
|
379
362
|
|
|
380
363
|
case "/api/execute/code":
|
|
381
364
|
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
365
|
try {
|
|
397
|
-
const body = await req.json() as {
|
|
398
|
-
|
|
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
|
+
);
|
|
399
376
|
} catch (error) {
|
|
400
|
-
|
|
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
|
|
401
416
|
return new Response(
|
|
402
417
|
JSON.stringify({
|
|
403
|
-
error:
|
|
418
|
+
error:
|
|
419
|
+
error instanceof Error
|
|
420
|
+
? error.message
|
|
421
|
+
: "Failed to execute code",
|
|
404
422
|
}),
|
|
405
423
|
{
|
|
406
424
|
status: 500,
|
|
@@ -416,27 +434,54 @@ const server = serve({
|
|
|
416
434
|
|
|
417
435
|
default:
|
|
418
436
|
// Handle dynamic routes for contexts
|
|
419
|
-
if (
|
|
420
|
-
|
|
437
|
+
if (
|
|
438
|
+
pathname.startsWith("/api/contexts/") &&
|
|
439
|
+
pathname.split("/").length === 4
|
|
440
|
+
) {
|
|
441
|
+
const contextId = pathname.split("/")[3];
|
|
421
442
|
if (req.method === "DELETE") {
|
|
422
443
|
try {
|
|
423
|
-
await
|
|
424
|
-
return new Response(
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
},
|
|
431
|
-
}
|
|
432
|
-
);
|
|
444
|
+
await interpreterService.deleteContext(contextId);
|
|
445
|
+
return new Response(JSON.stringify({ success: true }), {
|
|
446
|
+
headers: {
|
|
447
|
+
"Content-Type": "application/json",
|
|
448
|
+
...corsHeaders,
|
|
449
|
+
},
|
|
450
|
+
});
|
|
433
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
|
+
}
|
|
434
472
|
return new Response(
|
|
435
473
|
JSON.stringify({
|
|
436
|
-
error:
|
|
474
|
+
error:
|
|
475
|
+
error instanceof Error
|
|
476
|
+
? error.message
|
|
477
|
+
: "Failed to delete context",
|
|
437
478
|
}),
|
|
438
479
|
{
|
|
439
|
-
status:
|
|
480
|
+
status:
|
|
481
|
+
error instanceof Error &&
|
|
482
|
+
error.message.includes("not found")
|
|
483
|
+
? 404
|
|
484
|
+
: 500,
|
|
440
485
|
headers: {
|
|
441
486
|
"Content-Type": "application/json",
|
|
442
487
|
...corsHeaders,
|
|
@@ -446,22 +491,42 @@ const server = serve({
|
|
|
446
491
|
}
|
|
447
492
|
}
|
|
448
493
|
}
|
|
449
|
-
|
|
494
|
+
|
|
450
495
|
// Handle dynamic routes for individual processes
|
|
451
496
|
if (pathname.startsWith("/api/process/")) {
|
|
452
|
-
const segments = pathname.split(
|
|
497
|
+
const segments = pathname.split("/");
|
|
453
498
|
if (segments.length >= 4) {
|
|
454
499
|
const processId = segments[3];
|
|
455
500
|
const action = segments[4]; // Optional: logs, stream, etc.
|
|
456
501
|
|
|
457
502
|
if (!action && req.method === "GET") {
|
|
458
|
-
return handleGetProcessRequest(
|
|
503
|
+
return handleGetProcessRequest(
|
|
504
|
+
req,
|
|
505
|
+
corsHeaders,
|
|
506
|
+
processId,
|
|
507
|
+
sessionManager
|
|
508
|
+
);
|
|
459
509
|
} else if (!action && req.method === "DELETE") {
|
|
460
|
-
return handleKillProcessRequest(
|
|
510
|
+
return handleKillProcessRequest(
|
|
511
|
+
req,
|
|
512
|
+
corsHeaders,
|
|
513
|
+
processId,
|
|
514
|
+
sessionManager
|
|
515
|
+
);
|
|
461
516
|
} else if (action === "logs" && req.method === "GET") {
|
|
462
|
-
return handleGetProcessLogsRequest(
|
|
517
|
+
return handleGetProcessLogsRequest(
|
|
518
|
+
req,
|
|
519
|
+
corsHeaders,
|
|
520
|
+
processId,
|
|
521
|
+
sessionManager
|
|
522
|
+
);
|
|
463
523
|
} else if (action === "stream" && req.method === "GET") {
|
|
464
|
-
return handleStreamProcessLogsRequest(
|
|
524
|
+
return handleStreamProcessLogsRequest(
|
|
525
|
+
req,
|
|
526
|
+
corsHeaders,
|
|
527
|
+
processId,
|
|
528
|
+
sessionManager
|
|
529
|
+
);
|
|
465
530
|
}
|
|
466
531
|
}
|
|
467
532
|
}
|
|
@@ -477,7 +542,10 @@ const server = serve({
|
|
|
477
542
|
});
|
|
478
543
|
}
|
|
479
544
|
} catch (error) {
|
|
480
|
-
console.error(
|
|
545
|
+
console.error(
|
|
546
|
+
`[Container] Error handling ${req.method} ${pathname}:`,
|
|
547
|
+
error
|
|
548
|
+
);
|
|
481
549
|
return new Response(
|
|
482
550
|
JSON.stringify({
|
|
483
551
|
error: "Internal server error",
|
|
@@ -496,7 +564,7 @@ const server = serve({
|
|
|
496
564
|
hostname: "0.0.0.0",
|
|
497
565
|
port: 3000,
|
|
498
566
|
// We don't need this, but typescript complains
|
|
499
|
-
websocket: { async message() {
|
|
567
|
+
websocket: { async message() {} },
|
|
500
568
|
});
|
|
501
569
|
|
|
502
570
|
console.log(`🚀 Bun server running on http://0.0.0.0:${server.port}`);
|
|
@@ -509,6 +577,7 @@ console.log(` POST /api/git/checkout - Checkout a git repository`);
|
|
|
509
577
|
console.log(` POST /api/mkdir - Create a directory`);
|
|
510
578
|
console.log(` POST /api/write - Write a file`);
|
|
511
579
|
console.log(` POST /api/read - Read a file`);
|
|
580
|
+
console.log(` POST /api/read/stream - Stream a file (SSE)`);
|
|
512
581
|
console.log(` POST /api/delete - Delete a file`);
|
|
513
582
|
console.log(` POST /api/rename - Rename a file`);
|
|
514
583
|
console.log(` POST /api/move - Move a file`);
|
|
@@ -526,6 +595,7 @@ console.log(` GET /proxy/{port}/* - Proxy requests to exposed ports`);
|
|
|
526
595
|
console.log(` POST /api/contexts - Create a code execution context`);
|
|
527
596
|
console.log(` GET /api/contexts - List all contexts`);
|
|
528
597
|
console.log(` DELETE /api/contexts/{id} - Delete a context`);
|
|
529
|
-
console.log(
|
|
598
|
+
console.log(
|
|
599
|
+
` POST /api/execute/code - Execute code in a context (streaming)`
|
|
600
|
+
);
|
|
530
601
|
console.log(` GET /api/ping - Health check`);
|
|
531
|
-
console.log(` GET /api/commands - List available commands`);
|