@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/src/types.ts
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
// Core Types
|
|
2
2
|
|
|
3
3
|
export interface BaseExecOptions {
|
|
4
|
-
/**
|
|
5
|
-
* Session ID for grouping related commands
|
|
6
|
-
*/
|
|
7
|
-
sessionId?: string;
|
|
8
|
-
|
|
9
4
|
/**
|
|
10
5
|
* Maximum execution time in milliseconds
|
|
11
6
|
*/
|
|
@@ -90,11 +85,6 @@ export interface ExecResult {
|
|
|
90
85
|
* ISO timestamp when command started
|
|
91
86
|
*/
|
|
92
87
|
timestamp: string;
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Session ID if provided
|
|
96
|
-
*/
|
|
97
|
-
sessionId?: string;
|
|
98
88
|
}
|
|
99
89
|
|
|
100
90
|
// Background Process Types
|
|
@@ -177,11 +167,6 @@ export interface Process {
|
|
|
177
167
|
*/
|
|
178
168
|
readonly exitCode?: number;
|
|
179
169
|
|
|
180
|
-
/**
|
|
181
|
-
* Session ID if provided
|
|
182
|
-
*/
|
|
183
|
-
readonly sessionId?: string;
|
|
184
|
-
|
|
185
170
|
/**
|
|
186
171
|
* Kill the process
|
|
187
172
|
*/
|
|
@@ -208,7 +193,6 @@ export interface ExecEvent {
|
|
|
208
193
|
exitCode?: number;
|
|
209
194
|
result?: ExecResult;
|
|
210
195
|
error?: string; // Changed to string for serialization
|
|
211
|
-
sessionId?: string;
|
|
212
196
|
}
|
|
213
197
|
|
|
214
198
|
export interface LogEvent {
|
|
@@ -216,7 +200,6 @@ export interface LogEvent {
|
|
|
216
200
|
timestamp: string;
|
|
217
201
|
data: string;
|
|
218
202
|
processId: string;
|
|
219
|
-
sessionId?: string;
|
|
220
203
|
exitCode?: number; // For 'exit' events
|
|
221
204
|
}
|
|
222
205
|
|
|
@@ -272,10 +255,8 @@ export interface ProcessRecord {
|
|
|
272
255
|
startTime: Date;
|
|
273
256
|
endTime?: Date;
|
|
274
257
|
exitCode?: number;
|
|
275
|
-
sessionId?: string;
|
|
276
258
|
|
|
277
259
|
// Internal fields
|
|
278
|
-
childProcess?: any; // Node.js ChildProcess
|
|
279
260
|
stdout: string; // Accumulated output (ephemeral)
|
|
280
261
|
stderr: string; // Accumulated output (ephemeral)
|
|
281
262
|
|
|
@@ -290,7 +271,6 @@ export interface StartProcessRequest {
|
|
|
290
271
|
command: string;
|
|
291
272
|
options?: {
|
|
292
273
|
processId?: string;
|
|
293
|
-
sessionId?: string;
|
|
294
274
|
timeout?: number;
|
|
295
275
|
env?: Record<string, string>;
|
|
296
276
|
cwd?: string;
|
|
@@ -304,9 +284,11 @@ export interface StartProcessResponse {
|
|
|
304
284
|
id: string;
|
|
305
285
|
pid?: number;
|
|
306
286
|
command: string;
|
|
307
|
-
|
|
287
|
+
status: ProcessStatus;
|
|
308
288
|
startTime: string;
|
|
309
|
-
|
|
289
|
+
endTime?: string | null;
|
|
290
|
+
exitCode?: number | null;
|
|
291
|
+
sessionId: string;
|
|
310
292
|
};
|
|
311
293
|
}
|
|
312
294
|
|
|
@@ -319,7 +301,6 @@ export interface ListProcessesResponse {
|
|
|
319
301
|
startTime: string;
|
|
320
302
|
endTime?: string;
|
|
321
303
|
exitCode?: number;
|
|
322
|
-
sessionId?: string;
|
|
323
304
|
}>;
|
|
324
305
|
}
|
|
325
306
|
|
|
@@ -332,7 +313,6 @@ export interface GetProcessResponse {
|
|
|
332
313
|
startTime: string;
|
|
333
314
|
endTime?: string;
|
|
334
315
|
exitCode?: number;
|
|
335
|
-
sessionId?: string;
|
|
336
316
|
} | null;
|
|
337
317
|
}
|
|
338
318
|
|
|
@@ -371,6 +351,25 @@ export interface ISandbox {
|
|
|
371
351
|
cleanupCompletedProcesses(): Promise<number>;
|
|
372
352
|
getProcessLogs(id: string): Promise<{ stdout: string; stderr: string }>;
|
|
373
353
|
|
|
354
|
+
// File operations
|
|
355
|
+
gitCheckout(repoUrl: string, options: { branch?: string; targetDir?: string }): Promise<GitCheckoutResponse>;
|
|
356
|
+
mkdir(path: string, options?: { recursive?: boolean }): Promise<MkdirResponse>;
|
|
357
|
+
writeFile(path: string, content: string, options?: { encoding?: string }): Promise<WriteFileResponse>;
|
|
358
|
+
deleteFile(path: string): Promise<DeleteFileResponse>;
|
|
359
|
+
renameFile(oldPath: string, newPath: string): Promise<RenameFileResponse>;
|
|
360
|
+
moveFile(sourcePath: string, destinationPath: string): Promise<MoveFileResponse>;
|
|
361
|
+
readFile(path: string, options?: { encoding?: string }): Promise<ReadFileResponse>;
|
|
362
|
+
listFiles(path: string, options?: { recursive?: boolean; includeHidden?: boolean }): Promise<ListFilesResponse>;
|
|
363
|
+
|
|
364
|
+
// Port management
|
|
365
|
+
exposePort(port: number, options: { name?: string; hostname: string }): Promise<{ url: string; port: number; name?: string }>;
|
|
366
|
+
unexposePort(port: number): Promise<void>;
|
|
367
|
+
getExposedPorts(hostname: string): Promise<Array<{ url: string; port: number; name?: string; exposedAt: string }>>;
|
|
368
|
+
|
|
369
|
+
// Environment management
|
|
370
|
+
setEnvVars(envVars: Record<string, string>): Promise<void>;
|
|
371
|
+
setSandboxName(name: string): Promise<void>;
|
|
372
|
+
|
|
374
373
|
// Code Interpreter API
|
|
375
374
|
createCodeContext(options?: CreateContextOptions): Promise<CodeContext>;
|
|
376
375
|
runCode(code: string, options?: RunCodeOptions): Promise<ExecutionResult>;
|
|
@@ -379,6 +378,108 @@ export interface ISandbox {
|
|
|
379
378
|
deleteCodeContext(contextId: string): Promise<void>;
|
|
380
379
|
}
|
|
381
380
|
|
|
381
|
+
// Execution session returned by createSession()
|
|
382
|
+
// Sessions are full-featured sandbox objects with scoped execution context
|
|
383
|
+
// Inherits all ISandbox methods except createSession (sessions can't create sub-sessions),
|
|
384
|
+
// and setSandboxName (sessions inherit sandbox name).
|
|
385
|
+
export interface ExecutionSession extends Omit<ISandbox, 'createSession' | 'setSandboxName'> {
|
|
386
|
+
/**
|
|
387
|
+
* Session ID
|
|
388
|
+
*/
|
|
389
|
+
id: string;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// API Response Types
|
|
393
|
+
|
|
394
|
+
export interface ExecuteResponse {
|
|
395
|
+
success: boolean;
|
|
396
|
+
stdout: string;
|
|
397
|
+
stderr: string;
|
|
398
|
+
exitCode: number;
|
|
399
|
+
command: string;
|
|
400
|
+
timestamp: string;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export interface GitCheckoutResponse {
|
|
404
|
+
success: boolean;
|
|
405
|
+
stdout: string;
|
|
406
|
+
stderr: string;
|
|
407
|
+
exitCode: number;
|
|
408
|
+
repoUrl: string;
|
|
409
|
+
branch: string;
|
|
410
|
+
targetDir: string;
|
|
411
|
+
timestamp: string;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export interface MkdirResponse {
|
|
415
|
+
success: boolean;
|
|
416
|
+
stdout: string;
|
|
417
|
+
stderr: string;
|
|
418
|
+
exitCode: number;
|
|
419
|
+
path: string;
|
|
420
|
+
recursive: boolean;
|
|
421
|
+
timestamp: string;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export interface WriteFileResponse {
|
|
425
|
+
success: boolean;
|
|
426
|
+
exitCode: number;
|
|
427
|
+
path: string;
|
|
428
|
+
timestamp: string;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export interface ReadFileResponse {
|
|
432
|
+
success: boolean;
|
|
433
|
+
exitCode: number;
|
|
434
|
+
path: string;
|
|
435
|
+
content: string;
|
|
436
|
+
timestamp: string;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
export interface DeleteFileResponse {
|
|
440
|
+
success: boolean;
|
|
441
|
+
exitCode: number;
|
|
442
|
+
path: string;
|
|
443
|
+
timestamp: string;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
export interface RenameFileResponse {
|
|
447
|
+
success: boolean;
|
|
448
|
+
exitCode: number;
|
|
449
|
+
oldPath: string;
|
|
450
|
+
newPath: string;
|
|
451
|
+
timestamp: string;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export interface MoveFileResponse {
|
|
455
|
+
success: boolean;
|
|
456
|
+
exitCode: number;
|
|
457
|
+
sourcePath: string;
|
|
458
|
+
destinationPath: string;
|
|
459
|
+
timestamp: string;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
export interface ListFilesResponse {
|
|
463
|
+
success: boolean;
|
|
464
|
+
exitCode: number;
|
|
465
|
+
path: string;
|
|
466
|
+
files: Array<{
|
|
467
|
+
name: string;
|
|
468
|
+
absolutePath: string;
|
|
469
|
+
relativePath: string;
|
|
470
|
+
type: 'file' | 'directory' | 'symlink' | 'other';
|
|
471
|
+
size: number;
|
|
472
|
+
modifiedAt: string;
|
|
473
|
+
mode: string;
|
|
474
|
+
permissions: {
|
|
475
|
+
readable: boolean;
|
|
476
|
+
writable: boolean;
|
|
477
|
+
executable: boolean;
|
|
478
|
+
};
|
|
479
|
+
}>;
|
|
480
|
+
timestamp: string;
|
|
481
|
+
}
|
|
482
|
+
|
|
382
483
|
// Type Guards
|
|
383
484
|
|
|
384
485
|
export function isExecResult(value: any): value is ExecResult {
|
|
@@ -1,336 +0,0 @@
|
|
|
1
|
-
import { type Kernel, KernelManager, ServerConnection } from "@jupyterlab/services";
|
|
2
|
-
import type {
|
|
3
|
-
IDisplayDataMsg,
|
|
4
|
-
IErrorMsg,
|
|
5
|
-
IExecuteResultMsg,
|
|
6
|
-
IIOPubMessage,
|
|
7
|
-
IStreamMsg
|
|
8
|
-
} from "@jupyterlab/services/lib/kernel/messages";
|
|
9
|
-
import {
|
|
10
|
-
isDisplayDataMsg,
|
|
11
|
-
isErrorMsg,
|
|
12
|
-
isExecuteResultMsg,
|
|
13
|
-
isStreamMsg
|
|
14
|
-
} from "@jupyterlab/services/lib/kernel/messages";
|
|
15
|
-
import { v4 as uuidv4 } from "uuid";
|
|
16
|
-
import type { ExecutionResult } from "./mime-processor";
|
|
17
|
-
import { processJupyterMessage } from "./mime-processor";
|
|
18
|
-
|
|
19
|
-
export interface JupyterContext {
|
|
20
|
-
id: string;
|
|
21
|
-
language: string;
|
|
22
|
-
connection: Kernel.IKernelConnection;
|
|
23
|
-
cwd: string;
|
|
24
|
-
createdAt: Date;
|
|
25
|
-
lastUsed: Date;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface CreateContextRequest {
|
|
29
|
-
language?: string;
|
|
30
|
-
cwd?: string;
|
|
31
|
-
envVars?: Record<string, string>;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface ExecuteCodeRequest {
|
|
35
|
-
context_id?: string;
|
|
36
|
-
code: string;
|
|
37
|
-
language?: string;
|
|
38
|
-
env_vars?: Record<string, string>;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export class JupyterServer {
|
|
42
|
-
private kernelManager: KernelManager;
|
|
43
|
-
private contexts = new Map<string, JupyterContext>();
|
|
44
|
-
private defaultContexts = new Map<string, string>(); // language -> context_id
|
|
45
|
-
|
|
46
|
-
constructor() {
|
|
47
|
-
// Configure connection to local Jupyter server
|
|
48
|
-
const serverSettings = ServerConnection.makeSettings({
|
|
49
|
-
baseUrl: "http://localhost:8888",
|
|
50
|
-
token: "",
|
|
51
|
-
appUrl: "",
|
|
52
|
-
wsUrl: "ws://localhost:8888",
|
|
53
|
-
appendToken: false,
|
|
54
|
-
init: {
|
|
55
|
-
headers: {
|
|
56
|
-
'Content-Type': 'application/json'
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
this.kernelManager = new KernelManager({ serverSettings });
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async initialize() {
|
|
65
|
-
await this.kernelManager.ready;
|
|
66
|
-
console.log("[JupyterServer] Kernel manager initialized");
|
|
67
|
-
|
|
68
|
-
// Create default Python context
|
|
69
|
-
const pythonContext = await this.createContext({ language: "python" });
|
|
70
|
-
this.defaultContexts.set("python", pythonContext.id);
|
|
71
|
-
console.log(
|
|
72
|
-
"[JupyterServer] Default Python context created:",
|
|
73
|
-
pythonContext.id
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async createContext(req: CreateContextRequest): Promise<JupyterContext> {
|
|
78
|
-
const language = req.language || "python";
|
|
79
|
-
const cwd = req.cwd || "/workspace";
|
|
80
|
-
|
|
81
|
-
const kernelModel = await this.kernelManager.startNew({
|
|
82
|
-
name: this.getKernelName(language),
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
const connection = this.kernelManager.connectTo({ model: kernelModel });
|
|
86
|
-
|
|
87
|
-
const context: JupyterContext = {
|
|
88
|
-
id: uuidv4(),
|
|
89
|
-
language,
|
|
90
|
-
connection,
|
|
91
|
-
cwd,
|
|
92
|
-
createdAt: new Date(),
|
|
93
|
-
lastUsed: new Date(),
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
this.contexts.set(context.id, context);
|
|
97
|
-
|
|
98
|
-
// Set working directory
|
|
99
|
-
if (cwd !== "/workspace") {
|
|
100
|
-
await this.changeWorkingDirectory(context, cwd);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Set environment variables if provided
|
|
104
|
-
if (req.envVars) {
|
|
105
|
-
await this.setEnvironmentVariables(context, req.envVars);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return context;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
async executeCode(
|
|
112
|
-
contextId: string | undefined,
|
|
113
|
-
code: string,
|
|
114
|
-
language?: string
|
|
115
|
-
): Promise<Response> {
|
|
116
|
-
let context: JupyterContext | undefined;
|
|
117
|
-
|
|
118
|
-
if (contextId) {
|
|
119
|
-
context = this.contexts.get(contextId);
|
|
120
|
-
if (!context) {
|
|
121
|
-
return new Response(
|
|
122
|
-
JSON.stringify({ error: `Context ${contextId} not found` }),
|
|
123
|
-
{
|
|
124
|
-
status: 404,
|
|
125
|
-
headers: { "Content-Type": "application/json" },
|
|
126
|
-
}
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
} else if (language) {
|
|
130
|
-
// Use default context for the language
|
|
131
|
-
const defaultContextId = this.defaultContexts.get(language);
|
|
132
|
-
if (defaultContextId) {
|
|
133
|
-
context = this.contexts.get(defaultContextId);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Create new default context if needed
|
|
137
|
-
if (!context) {
|
|
138
|
-
context = await this.createContext({ language });
|
|
139
|
-
this.defaultContexts.set(language, context.id);
|
|
140
|
-
}
|
|
141
|
-
} else {
|
|
142
|
-
// Use default Python context
|
|
143
|
-
const pythonContextId = this.defaultContexts.get("python");
|
|
144
|
-
context = pythonContextId
|
|
145
|
-
? this.contexts.get(pythonContextId)
|
|
146
|
-
: undefined;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (!context) {
|
|
150
|
-
return new Response(JSON.stringify({ error: "No context available" }), {
|
|
151
|
-
status: 400,
|
|
152
|
-
headers: { "Content-Type": "application/json" },
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Update last used
|
|
157
|
-
context.lastUsed = new Date();
|
|
158
|
-
|
|
159
|
-
// Execute with streaming
|
|
160
|
-
return this.streamExecution(context.connection, code);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
private async streamExecution(
|
|
164
|
-
connection: Kernel.IKernelConnection,
|
|
165
|
-
code: string
|
|
166
|
-
): Promise<Response> {
|
|
167
|
-
const stream = new ReadableStream({
|
|
168
|
-
async start(controller) {
|
|
169
|
-
const future = connection.requestExecute({
|
|
170
|
-
code,
|
|
171
|
-
stop_on_error: false,
|
|
172
|
-
store_history: true,
|
|
173
|
-
silent: false,
|
|
174
|
-
allow_stdin: false,
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
// Handle different message types
|
|
178
|
-
future.onIOPub = (msg: IIOPubMessage) => {
|
|
179
|
-
const result = processJupyterMessage(msg);
|
|
180
|
-
if (result) {
|
|
181
|
-
controller.enqueue(
|
|
182
|
-
new TextEncoder().encode(`${JSON.stringify(result)}\n`)
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
future.onReply = (msg: any) => {
|
|
188
|
-
if (msg.content.status === "ok") {
|
|
189
|
-
controller.enqueue(
|
|
190
|
-
new TextEncoder().encode(
|
|
191
|
-
`${JSON.stringify({
|
|
192
|
-
type: "execution_complete",
|
|
193
|
-
execution_count: msg.content.execution_count,
|
|
194
|
-
})}\n`
|
|
195
|
-
)
|
|
196
|
-
);
|
|
197
|
-
} else if (msg.content.status === "error") {
|
|
198
|
-
controller.enqueue(
|
|
199
|
-
new TextEncoder().encode(
|
|
200
|
-
`${JSON.stringify({
|
|
201
|
-
type: "error",
|
|
202
|
-
ename: msg.content.ename,
|
|
203
|
-
evalue: msg.content.evalue,
|
|
204
|
-
traceback: msg.content.traceback,
|
|
205
|
-
})}\n`
|
|
206
|
-
)
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
controller.close();
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
future.onStdin = (msg: any) => {
|
|
213
|
-
// We don't support stdin for now
|
|
214
|
-
console.warn("[JupyterServer] Stdin requested but not supported");
|
|
215
|
-
};
|
|
216
|
-
},
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
return new Response(stream, {
|
|
220
|
-
headers: {
|
|
221
|
-
"Content-Type": "text/event-stream",
|
|
222
|
-
"Cache-Control": "no-cache",
|
|
223
|
-
Connection: "keep-alive",
|
|
224
|
-
},
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
private getKernelName(language: string): string {
|
|
229
|
-
const kernelMap: Record<string, string> = {
|
|
230
|
-
python: "python3",
|
|
231
|
-
javascript: "javascript",
|
|
232
|
-
typescript: "javascript",
|
|
233
|
-
js: "javascript",
|
|
234
|
-
ts: "javascript",
|
|
235
|
-
};
|
|
236
|
-
return kernelMap[language.toLowerCase()] || "python3";
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
private async changeWorkingDirectory(context: JupyterContext, cwd: string) {
|
|
240
|
-
const code =
|
|
241
|
-
context.language === "python"
|
|
242
|
-
? `import os; os.chdir('${cwd}')`
|
|
243
|
-
: `process.chdir('${cwd}')`;
|
|
244
|
-
|
|
245
|
-
const future = context.connection.requestExecute({
|
|
246
|
-
code,
|
|
247
|
-
silent: true,
|
|
248
|
-
store_history: false,
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
return future.done;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
private async setEnvironmentVariables(
|
|
255
|
-
context: JupyterContext,
|
|
256
|
-
envVars: Record<string, string>
|
|
257
|
-
) {
|
|
258
|
-
const commands: string[] = [];
|
|
259
|
-
|
|
260
|
-
for (const [key, value] of Object.entries(envVars)) {
|
|
261
|
-
if (context.language === "python") {
|
|
262
|
-
commands.push(`import os; os.environ['${key}'] = '${value}'`);
|
|
263
|
-
} else if (
|
|
264
|
-
context.language === "javascript" ||
|
|
265
|
-
context.language === "typescript"
|
|
266
|
-
) {
|
|
267
|
-
commands.push(`process.env['${key}'] = '${value}'`);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
if (commands.length > 0) {
|
|
272
|
-
const code = commands.join("\n");
|
|
273
|
-
const future = context.connection.requestExecute({
|
|
274
|
-
code,
|
|
275
|
-
silent: true,
|
|
276
|
-
store_history: false,
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
return future.done;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
async listContexts(): Promise<
|
|
284
|
-
Array<{
|
|
285
|
-
id: string;
|
|
286
|
-
language: string;
|
|
287
|
-
cwd: string;
|
|
288
|
-
createdAt: Date;
|
|
289
|
-
lastUsed: Date;
|
|
290
|
-
}>
|
|
291
|
-
> {
|
|
292
|
-
return Array.from(this.contexts.values()).map((ctx) => ({
|
|
293
|
-
id: ctx.id,
|
|
294
|
-
language: ctx.language,
|
|
295
|
-
cwd: ctx.cwd,
|
|
296
|
-
createdAt: ctx.createdAt,
|
|
297
|
-
lastUsed: ctx.lastUsed,
|
|
298
|
-
}));
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
async deleteContext(contextId: string): Promise<void> {
|
|
302
|
-
const context = this.contexts.get(contextId);
|
|
303
|
-
if (!context) {
|
|
304
|
-
throw new Error(`Context ${contextId} not found`);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// Shutdown the kernel
|
|
308
|
-
await context.connection.shutdown();
|
|
309
|
-
|
|
310
|
-
// Remove from maps
|
|
311
|
-
this.contexts.delete(contextId);
|
|
312
|
-
|
|
313
|
-
// Remove from default contexts if it was a default
|
|
314
|
-
for (const [lang, id] of this.defaultContexts.entries()) {
|
|
315
|
-
if (id === contextId) {
|
|
316
|
-
this.defaultContexts.delete(lang);
|
|
317
|
-
break;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
async shutdown() {
|
|
323
|
-
// Shutdown all kernels
|
|
324
|
-
for (const context of this.contexts.values()) {
|
|
325
|
-
try {
|
|
326
|
-
await context.connection.shutdown();
|
|
327
|
-
} catch (error) {
|
|
328
|
-
console.error("[JupyterServer] Error shutting down kernel:", error);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
this.contexts.clear();
|
|
333
|
-
this.defaultContexts.clear();
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|