@cloudflare/sandbox 0.0.0-db09b4d → 0.0.0-e1fa354

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +187 -0
  2. package/Dockerfile +99 -11
  3. package/README.md +806 -22
  4. package/container_src/bun.lock +122 -0
  5. package/container_src/circuit-breaker.ts +121 -0
  6. package/container_src/control-process.ts +784 -0
  7. package/container_src/handler/exec.ts +185 -0
  8. package/container_src/handler/file.ts +406 -0
  9. package/container_src/handler/git.ts +130 -0
  10. package/container_src/handler/ports.ts +314 -0
  11. package/container_src/handler/process.ts +568 -0
  12. package/container_src/handler/session.ts +92 -0
  13. package/container_src/index.ts +448 -2467
  14. package/container_src/isolation.ts +1038 -0
  15. package/container_src/jupyter-server.ts +579 -0
  16. package/container_src/jupyter-service.ts +461 -0
  17. package/container_src/jupyter_config.py +48 -0
  18. package/container_src/mime-processor.ts +255 -0
  19. package/container_src/package.json +9 -0
  20. package/container_src/shell-escape.ts +42 -0
  21. package/container_src/startup.sh +84 -0
  22. package/container_src/types.ts +131 -0
  23. package/package.json +6 -8
  24. package/src/client.ts +477 -1192
  25. package/src/errors.ts +218 -0
  26. package/src/index.ts +63 -78
  27. package/src/interpreter-types.ts +383 -0
  28. package/src/interpreter.ts +150 -0
  29. package/src/jupyter-client.ts +349 -0
  30. package/src/request-handler.ts +144 -0
  31. package/src/sandbox.ts +747 -0
  32. package/src/security.ts +113 -0
  33. package/src/sse-parser.ts +147 -0
  34. package/src/types.ts +502 -0
  35. package/tsconfig.json +1 -1
  36. package/tests/client.example.ts +0 -308
  37. package/tests/connection-test.ts +0 -81
  38. package/tests/simple-test.ts +0 -81
  39. package/tests/test1.ts +0 -281
  40. package/tests/test2.ts +0 -710
package/src/sandbox.ts ADDED
@@ -0,0 +1,747 @@
1
+ import { Container, getContainer } from "@cloudflare/containers";
2
+ import { CodeInterpreter } from "./interpreter";
3
+ import type {
4
+ CodeContext,
5
+ CreateContextOptions,
6
+ ExecutionResult,
7
+ RunCodeOptions,
8
+ } from "./interpreter-types";
9
+ import { JupyterClient } from "./jupyter-client";
10
+ import { isLocalhostPattern } from "./request-handler";
11
+ import {
12
+ logSecurityEvent,
13
+ SecurityError,
14
+ sanitizeSandboxId,
15
+ validatePort,
16
+ } from "./security";
17
+ import { parseSSEStream } from "./sse-parser";
18
+ import type {
19
+ ExecEvent,
20
+ ExecOptions,
21
+ ExecResult,
22
+ ExecuteResponse,
23
+ ExecutionSession,
24
+ ISandbox,
25
+ Process,
26
+ ProcessOptions,
27
+ ProcessStatus,
28
+ StreamOptions,
29
+ } from "./types";
30
+ import { ProcessNotFoundError, SandboxError } from "./types";
31
+
32
+ export function getSandbox(ns: DurableObjectNamespace<Sandbox>, id: string) {
33
+ const stub = getContainer(ns, id);
34
+
35
+ // Store the name on first access
36
+ stub.setSandboxName?.(id);
37
+
38
+ return stub;
39
+ }
40
+
41
+ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
42
+ defaultPort = 3000; // Default port for the container's Bun server
43
+ sleepAfter = "20m"; // Keep container warm for 20 minutes to avoid cold starts
44
+ client: JupyterClient;
45
+ private sandboxName: string | null = null;
46
+ private codeInterpreter: CodeInterpreter;
47
+ private defaultSession: ExecutionSession | null = null;
48
+
49
+ constructor(ctx: DurableObjectState, env: Env) {
50
+ super(ctx, env);
51
+ this.client = new JupyterClient({
52
+ onCommandComplete: (success, exitCode, _stdout, _stderr, command) => {
53
+ console.log(
54
+ `[Container] Command completed: ${command}, Success: ${success}, Exit code: ${exitCode}`
55
+ );
56
+ },
57
+ onCommandStart: (command) => {
58
+ console.log(`[Container] Command started: ${command}`);
59
+ },
60
+ onError: (error, _command) => {
61
+ console.error(`[Container] Command error: ${error}`);
62
+ },
63
+ onOutput: (stream, data, _command) => {
64
+ console.log(`[Container] [${stream}] ${data}`);
65
+ },
66
+ port: 3000, // Control plane port
67
+ stub: this,
68
+ });
69
+
70
+ // Initialize code interpreter
71
+ this.codeInterpreter = new CodeInterpreter(this);
72
+
73
+ // Load the sandbox name from storage on initialization
74
+ this.ctx.blockConcurrencyWhile(async () => {
75
+ this.sandboxName =
76
+ (await this.ctx.storage.get<string>("sandboxName")) || null;
77
+ });
78
+ }
79
+
80
+ // RPC method to set the sandbox name
81
+ async setSandboxName(name: string): Promise<void> {
82
+ if (!this.sandboxName) {
83
+ this.sandboxName = name;
84
+ await this.ctx.storage.put("sandboxName", name);
85
+ console.log(`[Sandbox] Stored sandbox name via RPC: ${name}`);
86
+ }
87
+ }
88
+
89
+ // RPC method to set environment variables
90
+ async setEnvVars(envVars: Record<string, string>): Promise<void> {
91
+ this.envVars = { ...this.envVars, ...envVars };
92
+ console.log(`[Sandbox] Updated environment variables`);
93
+
94
+ // If we have a default session, update its environment too
95
+ if (this.defaultSession) {
96
+ await this.defaultSession.setEnvVars(envVars);
97
+ }
98
+ }
99
+
100
+ override onStart() {
101
+ console.log("Sandbox successfully started");
102
+ }
103
+
104
+ override onStop() {
105
+ console.log("Sandbox successfully shut down");
106
+ }
107
+
108
+ override onError(error: unknown) {
109
+ console.log("Sandbox error:", error);
110
+ }
111
+
112
+ // Override fetch to route internal container requests to appropriate ports
113
+ override async fetch(request: Request): Promise<Response> {
114
+ const url = new URL(request.url);
115
+
116
+ // Capture and store the sandbox name from the header if present
117
+ if (!this.sandboxName && request.headers.has("X-Sandbox-Name")) {
118
+ const name = request.headers.get("X-Sandbox-Name")!;
119
+ this.sandboxName = name;
120
+ await this.ctx.storage.put("sandboxName", name);
121
+ console.log(`[Sandbox] Stored sandbox name: ${this.sandboxName}`);
122
+ }
123
+
124
+ // Determine which port to route to
125
+ const port = this.determinePort(url);
126
+
127
+ // Route to the appropriate port
128
+ return await this.containerFetch(request, port);
129
+ }
130
+
131
+ private determinePort(url: URL): number {
132
+ // Extract port from proxy requests (e.g., /proxy/8080/*)
133
+ const proxyMatch = url.pathname.match(/^\/proxy\/(\d+)/);
134
+ if (proxyMatch) {
135
+ return parseInt(proxyMatch[1]);
136
+ }
137
+
138
+ if (url.port) {
139
+ return parseInt(url.port);
140
+ }
141
+
142
+ // All other requests go to control plane on port 3000
143
+ // This includes /api/* endpoints and any other control requests
144
+ return 3000;
145
+ }
146
+
147
+ // Helper to ensure default session is initialized
148
+ private async ensureDefaultSession(): Promise<ExecutionSession> {
149
+ if (!this.defaultSession) {
150
+ const sessionId = `sandbox-${this.sandboxName || 'default'}`;
151
+ this.defaultSession = await this.createSession({
152
+ id: sessionId,
153
+ env: this.envVars || {},
154
+ cwd: '/workspace',
155
+ isolation: true
156
+ });
157
+ console.log(`[Sandbox] Default session initialized: ${sessionId}`);
158
+ }
159
+ return this.defaultSession;
160
+ }
161
+
162
+
163
+ async exec(command: string, options?: ExecOptions): Promise<ExecResult> {
164
+ const session = await this.ensureDefaultSession();
165
+ return session.exec(command, options);
166
+ }
167
+
168
+ async startProcess(
169
+ command: string,
170
+ options?: ProcessOptions
171
+ ): Promise<Process> {
172
+ const session = await this.ensureDefaultSession();
173
+ return session.startProcess(command, options);
174
+ }
175
+
176
+ async listProcesses(): Promise<Process[]> {
177
+ const session = await this.ensureDefaultSession();
178
+ return session.listProcesses();
179
+ }
180
+
181
+ async getProcess(id: string): Promise<Process | null> {
182
+ const session = await this.ensureDefaultSession();
183
+ return session.getProcess(id);
184
+ }
185
+
186
+ async killProcess(id: string, signal?: string): Promise<void> {
187
+ const session = await this.ensureDefaultSession();
188
+ return session.killProcess(id, signal);
189
+ }
190
+
191
+ async killAllProcesses(): Promise<number> {
192
+ const session = await this.ensureDefaultSession();
193
+ return session.killAllProcesses();
194
+ }
195
+
196
+ async cleanupCompletedProcesses(): Promise<number> {
197
+ const session = await this.ensureDefaultSession();
198
+ return session.cleanupCompletedProcesses();
199
+ }
200
+
201
+ async getProcessLogs(
202
+ id: string
203
+ ): Promise<{ stdout: string; stderr: string }> {
204
+ const session = await this.ensureDefaultSession();
205
+ return session.getProcessLogs(id);
206
+ }
207
+
208
+ // Streaming methods - delegates to default session
209
+ async execStream(
210
+ command: string,
211
+ options?: StreamOptions
212
+ ): Promise<ReadableStream<Uint8Array>> {
213
+ const session = await this.ensureDefaultSession();
214
+ return session.execStream(command, options);
215
+ }
216
+
217
+ async streamProcessLogs(
218
+ processId: string,
219
+ options?: { signal?: AbortSignal }
220
+ ): Promise<ReadableStream<Uint8Array>> {
221
+ const session = await this.ensureDefaultSession();
222
+ return session.streamProcessLogs(processId, options);
223
+ }
224
+
225
+ async gitCheckout(
226
+ repoUrl: string,
227
+ options: { branch?: string; targetDir?: string }
228
+ ) {
229
+ const session = await this.ensureDefaultSession();
230
+ return session.gitCheckout(repoUrl, options);
231
+ }
232
+
233
+ async mkdir(path: string, options: { recursive?: boolean } = {}) {
234
+ const session = await this.ensureDefaultSession();
235
+ return session.mkdir(path, options);
236
+ }
237
+
238
+ async writeFile(
239
+ path: string,
240
+ content: string,
241
+ options: { encoding?: string } = {}
242
+ ) {
243
+ const session = await this.ensureDefaultSession();
244
+ return session.writeFile(path, content, options);
245
+ }
246
+
247
+ async deleteFile(path: string) {
248
+ const session = await this.ensureDefaultSession();
249
+ return session.deleteFile(path);
250
+ }
251
+
252
+ async renameFile(oldPath: string, newPath: string) {
253
+ const session = await this.ensureDefaultSession();
254
+ return session.renameFile(oldPath, newPath);
255
+ }
256
+
257
+ async moveFile(sourcePath: string, destinationPath: string) {
258
+ const session = await this.ensureDefaultSession();
259
+ return session.moveFile(sourcePath, destinationPath);
260
+ }
261
+
262
+ async readFile(path: string, options: { encoding?: string } = {}) {
263
+ const session = await this.ensureDefaultSession();
264
+ return session.readFile(path, options);
265
+ }
266
+
267
+ async listFiles(
268
+ path: string,
269
+ options: {
270
+ recursive?: boolean;
271
+ includeHidden?: boolean;
272
+ } = {}
273
+ ) {
274
+ const session = await this.ensureDefaultSession();
275
+ return session.listFiles(path, options);
276
+ }
277
+
278
+ async exposePort(port: number, options: { name?: string; hostname: string }) {
279
+ await this.client.exposePort(port, options?.name);
280
+
281
+ // We need the sandbox name to construct preview URLs
282
+ if (!this.sandboxName) {
283
+ throw new Error(
284
+ "Sandbox name not available. Ensure sandbox is accessed through getSandbox()"
285
+ );
286
+ }
287
+
288
+ const url = this.constructPreviewUrl(
289
+ port,
290
+ this.sandboxName,
291
+ options.hostname
292
+ );
293
+
294
+ return {
295
+ url,
296
+ port,
297
+ name: options?.name,
298
+ };
299
+ }
300
+
301
+ async unexposePort(port: number) {
302
+ if (!validatePort(port)) {
303
+ logSecurityEvent(
304
+ "INVALID_PORT_UNEXPOSE",
305
+ {
306
+ port,
307
+ },
308
+ "high"
309
+ );
310
+ throw new SecurityError(
311
+ `Invalid port number: ${port}. Must be between 1024-65535 and not reserved.`
312
+ );
313
+ }
314
+
315
+ await this.client.unexposePort(port);
316
+
317
+ logSecurityEvent(
318
+ "PORT_UNEXPOSED",
319
+ {
320
+ port,
321
+ },
322
+ "low"
323
+ );
324
+ }
325
+
326
+ async getExposedPorts(hostname: string) {
327
+ const response = await this.client.getExposedPorts();
328
+
329
+ // We need the sandbox name to construct preview URLs
330
+ if (!this.sandboxName) {
331
+ throw new Error(
332
+ "Sandbox name not available. Ensure sandbox is accessed through getSandbox()"
333
+ );
334
+ }
335
+
336
+ return response.ports.map((port) => ({
337
+ url: this.constructPreviewUrl(port.port, this.sandboxName!, hostname),
338
+ port: port.port,
339
+ name: port.name,
340
+ exposedAt: port.exposedAt,
341
+ }));
342
+ }
343
+
344
+ private constructPreviewUrl(
345
+ port: number,
346
+ sandboxId: string,
347
+ hostname: string
348
+ ): string {
349
+ if (!validatePort(port)) {
350
+ logSecurityEvent(
351
+ "INVALID_PORT_REJECTED",
352
+ {
353
+ port,
354
+ sandboxId,
355
+ hostname,
356
+ },
357
+ "high"
358
+ );
359
+ throw new SecurityError(
360
+ `Invalid port number: ${port}. Must be between 1024-65535 and not reserved.`
361
+ );
362
+ }
363
+
364
+ let sanitizedSandboxId: string;
365
+ try {
366
+ sanitizedSandboxId = sanitizeSandboxId(sandboxId);
367
+ } catch (error) {
368
+ logSecurityEvent(
369
+ "INVALID_SANDBOX_ID_REJECTED",
370
+ {
371
+ sandboxId,
372
+ port,
373
+ hostname,
374
+ error: error instanceof Error ? error.message : "Unknown error",
375
+ },
376
+ "high"
377
+ );
378
+ throw error;
379
+ }
380
+
381
+ const isLocalhost = isLocalhostPattern(hostname);
382
+
383
+ if (isLocalhost) {
384
+ // Unified subdomain approach for localhost (RFC 6761)
385
+ const [host, portStr] = hostname.split(":");
386
+ const mainPort = portStr || "80";
387
+
388
+ // Use URL constructor for safe URL building
389
+ try {
390
+ const baseUrl = new URL(`http://${host}:${mainPort}`);
391
+ // Construct subdomain safely
392
+ const subdomainHost = `${port}-${sanitizedSandboxId}.${host}`;
393
+ baseUrl.hostname = subdomainHost;
394
+
395
+ const finalUrl = baseUrl.toString();
396
+
397
+ logSecurityEvent(
398
+ "PREVIEW_URL_CONSTRUCTED",
399
+ {
400
+ port,
401
+ sandboxId: sanitizedSandboxId,
402
+ hostname,
403
+ resultUrl: finalUrl,
404
+ environment: "localhost",
405
+ },
406
+ "low"
407
+ );
408
+
409
+ return finalUrl;
410
+ } catch (error) {
411
+ logSecurityEvent(
412
+ "URL_CONSTRUCTION_FAILED",
413
+ {
414
+ port,
415
+ sandboxId: sanitizedSandboxId,
416
+ hostname,
417
+ error: error instanceof Error ? error.message : "Unknown error",
418
+ },
419
+ "high"
420
+ );
421
+ throw new SecurityError(
422
+ `Failed to construct preview URL: ${
423
+ error instanceof Error ? error.message : "Unknown error"
424
+ }`
425
+ );
426
+ }
427
+ }
428
+
429
+ // Production subdomain logic - enforce HTTPS
430
+ try {
431
+ // Always use HTTPS for production (non-localhost)
432
+ const protocol = "https";
433
+ const baseUrl = new URL(`${protocol}://${hostname}`);
434
+
435
+ // Construct subdomain safely
436
+ const subdomainHost = `${port}-${sanitizedSandboxId}.${hostname}`;
437
+ baseUrl.hostname = subdomainHost;
438
+
439
+ const finalUrl = baseUrl.toString();
440
+
441
+ logSecurityEvent(
442
+ "PREVIEW_URL_CONSTRUCTED",
443
+ {
444
+ port,
445
+ sandboxId: sanitizedSandboxId,
446
+ hostname,
447
+ resultUrl: finalUrl,
448
+ environment: "production",
449
+ },
450
+ "low"
451
+ );
452
+
453
+ return finalUrl;
454
+ } catch (error) {
455
+ logSecurityEvent(
456
+ "URL_CONSTRUCTION_FAILED",
457
+ {
458
+ port,
459
+ sandboxId: sanitizedSandboxId,
460
+ hostname,
461
+ error: error instanceof Error ? error.message : "Unknown error",
462
+ },
463
+ "high"
464
+ );
465
+ throw new SecurityError(
466
+ `Failed to construct preview URL: ${
467
+ error instanceof Error ? error.message : "Unknown error"
468
+ }`
469
+ );
470
+ }
471
+ }
472
+
473
+ // Code Interpreter Methods
474
+
475
+ /**
476
+ * Create a new code execution context
477
+ */
478
+ async createCodeContext(
479
+ options?: CreateContextOptions
480
+ ): Promise<CodeContext> {
481
+ return this.codeInterpreter.createCodeContext(options);
482
+ }
483
+
484
+ /**
485
+ * Run code with streaming callbacks
486
+ */
487
+ async runCode(
488
+ code: string,
489
+ options?: RunCodeOptions
490
+ ): Promise<ExecutionResult> {
491
+ const execution = await this.codeInterpreter.runCode(code, options);
492
+ // Convert to plain object for RPC serialization
493
+ return execution.toJSON();
494
+ }
495
+
496
+ /**
497
+ * Run code and return a streaming response
498
+ */
499
+ async runCodeStream(
500
+ code: string,
501
+ options?: RunCodeOptions
502
+ ): Promise<ReadableStream> {
503
+ return this.codeInterpreter.runCodeStream(code, options);
504
+ }
505
+
506
+ /**
507
+ * List all code contexts
508
+ */
509
+ async listCodeContexts(): Promise<CodeContext[]> {
510
+ return this.codeInterpreter.listCodeContexts();
511
+ }
512
+
513
+ /**
514
+ * Delete a code context
515
+ */
516
+ async deleteCodeContext(contextId: string): Promise<void> {
517
+ return this.codeInterpreter.deleteCodeContext(contextId);
518
+ }
519
+
520
+ // ============================================================================
521
+ // Session Management (Simple Isolation)
522
+ // ============================================================================
523
+
524
+ /**
525
+ * Create a new execution session with isolation
526
+ * Returns a session object with exec() method
527
+ */
528
+
529
+ async createSession(options: {
530
+ id?: string;
531
+ env?: Record<string, string>;
532
+ cwd?: string;
533
+ isolation?: boolean;
534
+ }): Promise<ExecutionSession> {
535
+ const sessionId = options.id || `session-${Date.now()}`;
536
+
537
+ await this.client.createSession({
538
+ id: sessionId,
539
+ env: options.env,
540
+ cwd: options.cwd,
541
+ isolation: options.isolation
542
+ });
543
+ // Return comprehensive ExecutionSession object that implements all ISandbox methods
544
+ return {
545
+ id: sessionId,
546
+
547
+ // Command execution - clean method names
548
+ exec: async (command: string, options?: ExecOptions) => {
549
+ const result = await this.client.exec(sessionId, command);
550
+ return {
551
+ ...result,
552
+ command,
553
+ duration: 0,
554
+ timestamp: new Date().toISOString()
555
+ };
556
+ },
557
+
558
+ execStream: async (command: string, options?: StreamOptions) => {
559
+ return await this.client.execStream(sessionId, command);
560
+ },
561
+
562
+ // Process management - route to session-aware methods
563
+ startProcess: async (command: string, options?: ProcessOptions) => {
564
+ // Use session-specific process management
565
+ const response = await this.client.startProcess(command, sessionId, {
566
+ processId: options?.processId,
567
+ timeout: options?.timeout,
568
+ env: options?.env,
569
+ cwd: options?.cwd,
570
+ encoding: options?.encoding,
571
+ autoCleanup: options?.autoCleanup,
572
+ });
573
+
574
+ // Convert response to Process object with bound methods
575
+ const process = response.process;
576
+ return {
577
+ id: process.id,
578
+ pid: process.pid,
579
+ command: process.command,
580
+ status: process.status as ProcessStatus,
581
+ startTime: new Date(process.startTime),
582
+ endTime: process.endTime ? new Date(process.endTime) : undefined,
583
+ exitCode: process.exitCode ?? undefined,
584
+ kill: async (signal?: string) => {
585
+ await this.client.killProcess(process.id);
586
+ },
587
+ getStatus: async () => {
588
+ const resp = await this.client.getProcess(process.id);
589
+ return resp.process?.status as ProcessStatus || "error";
590
+ },
591
+ getLogs: async () => {
592
+ return await this.client.getProcessLogs(process.id);
593
+ },
594
+ };
595
+ },
596
+
597
+ listProcesses: async () => {
598
+ // Get processes for this specific session
599
+ const response = await this.client.listProcesses(sessionId);
600
+
601
+ // Convert to Process objects with bound methods
602
+ return response.processes.map(p => ({
603
+ id: p.id,
604
+ pid: p.pid,
605
+ command: p.command,
606
+ status: p.status as ProcessStatus,
607
+ startTime: new Date(p.startTime),
608
+ endTime: p.endTime ? new Date(p.endTime) : undefined,
609
+ exitCode: p.exitCode ?? undefined,
610
+ kill: async (signal?: string) => {
611
+ await this.client.killProcess(p.id);
612
+ },
613
+ getStatus: async () => {
614
+ const processResp = await this.client.getProcess(p.id);
615
+ return processResp.process?.status as ProcessStatus || "error";
616
+ },
617
+ getLogs: async () => {
618
+ return this.client.getProcessLogs(p.id);
619
+ },
620
+ }));
621
+ },
622
+
623
+ getProcess: async (id: string) => {
624
+ const response = await this.client.getProcess(id);
625
+ if (!response.process) return null;
626
+
627
+ const p = response.process;
628
+ return {
629
+ id: p.id,
630
+ pid: p.pid,
631
+ command: p.command,
632
+ status: p.status as ProcessStatus,
633
+ startTime: new Date(p.startTime),
634
+ endTime: p.endTime ? new Date(p.endTime) : undefined,
635
+ exitCode: p.exitCode ?? undefined,
636
+ kill: async (signal?: string) => {
637
+ await this.client.killProcess(p.id);
638
+ },
639
+ getStatus: async () => {
640
+ const processResp = await this.client.getProcess(p.id);
641
+ return processResp.process?.status as ProcessStatus || "error";
642
+ },
643
+ getLogs: async () => {
644
+ return this.client.getProcessLogs(p.id);
645
+ },
646
+ };
647
+ },
648
+
649
+ killProcess: async (id: string, signal?: string) => {
650
+ await this.client.killProcess(id);
651
+ },
652
+
653
+ killAllProcesses: async () => {
654
+ // Kill all processes for this specific session
655
+ const response = await this.client.killAllProcesses(sessionId);
656
+ return response.killedCount;
657
+ },
658
+
659
+ streamProcessLogs: async (processId: string, options?: { signal?: AbortSignal }) => {
660
+ return await this.client.streamProcessLogs(processId, options);
661
+ },
662
+
663
+ getProcessLogs: async (id: string) => {
664
+ return await this.client.getProcessLogs(id);
665
+ },
666
+
667
+ cleanupCompletedProcesses: async () => {
668
+ // This would need a new endpoint to cleanup processes for a specific session
669
+ // For now, return 0 as no cleanup is performed
670
+ return 0;
671
+ },
672
+
673
+ // File operations - clean method names (no "InSession" suffix)
674
+ writeFile: async (path: string, content: string, options?: { encoding?: string }) => {
675
+ return await this.client.writeFile(path, content, options?.encoding, sessionId);
676
+ },
677
+
678
+ readFile: async (path: string, options?: { encoding?: string }) => {
679
+ return await this.client.readFile(path, options?.encoding, sessionId);
680
+ },
681
+
682
+ mkdir: async (path: string, options?: { recursive?: boolean }) => {
683
+ return await this.client.mkdir(path, options?.recursive, sessionId);
684
+ },
685
+
686
+ deleteFile: async (path: string) => {
687
+ return await this.client.deleteFile(path, sessionId);
688
+ },
689
+
690
+ renameFile: async (oldPath: string, newPath: string) => {
691
+ return await this.client.renameFile(oldPath, newPath, sessionId);
692
+ },
693
+
694
+ moveFile: async (sourcePath: string, destinationPath: string) => {
695
+ return await this.client.moveFile(sourcePath, destinationPath, sessionId);
696
+ },
697
+
698
+ listFiles: async (path: string, options?: { recursive?: boolean; includeHidden?: boolean }) => {
699
+ return await this.client.listFiles(path, sessionId, options);
700
+ },
701
+
702
+ gitCheckout: async (repoUrl: string, options?: { branch?: string; targetDir?: string }) => {
703
+ return await this.client.gitCheckout(repoUrl, sessionId, options?.branch, options?.targetDir);
704
+ },
705
+
706
+ // Port management
707
+ exposePort: async (port: number, options: { name?: string; hostname: string }) => {
708
+ return await this.exposePort(port, options);
709
+ },
710
+
711
+ unexposePort: async (port: number) => {
712
+ return await this.unexposePort(port);
713
+ },
714
+
715
+ getExposedPorts: async (hostname: string) => {
716
+ return await this.getExposedPorts(hostname);
717
+ },
718
+
719
+ // Environment management
720
+ setEnvVars: async (envVars: Record<string, string>) => {
721
+ // TODO: Implement session-specific environment updates
722
+ console.log(`[Session ${sessionId}] Environment variables update not yet implemented`);
723
+ },
724
+
725
+ // Code Interpreter API
726
+ createCodeContext: async (options?: any) => {
727
+ return await this.createCodeContext(options);
728
+ },
729
+
730
+ runCode: async (code: string, options?: any) => {
731
+ return await this.runCode(code, options);
732
+ },
733
+
734
+ runCodeStream: async (code: string, options?: any) => {
735
+ return await this.runCodeStream(code, options);
736
+ },
737
+
738
+ listCodeContexts: async () => {
739
+ return await this.listCodeContexts();
740
+ },
741
+
742
+ deleteCodeContext: async (contextId: string) => {
743
+ return await this.deleteCodeContext(contextId);
744
+ }
745
+ };
746
+ }
747
+ }