@cloudflare/sandbox 0.2.4 → 0.3.1

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 (43) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/Dockerfile +9 -11
  3. package/README.md +69 -7
  4. package/container_src/control-process.ts +784 -0
  5. package/container_src/handler/exec.ts +99 -254
  6. package/container_src/handler/file.ts +179 -837
  7. package/container_src/handler/git.ts +28 -80
  8. package/container_src/handler/process.ts +443 -515
  9. package/container_src/handler/session.ts +92 -0
  10. package/container_src/index.ts +68 -130
  11. package/container_src/isolation.ts +1038 -0
  12. package/container_src/shell-escape.ts +42 -0
  13. package/container_src/types.ts +27 -13
  14. package/dist/{chunk-HHUDRGPY.js → chunk-BEQUGUY4.js} +2 -2
  15. package/dist/{chunk-CKIGERRS.js → chunk-LFLJGISB.js} +240 -264
  16. package/dist/chunk-LFLJGISB.js.map +1 -0
  17. package/dist/{chunk-3CQ6THKA.js → chunk-SMUEY5JR.js} +85 -103
  18. package/dist/chunk-SMUEY5JR.js.map +1 -0
  19. package/dist/{client-Ce40ujDF.d.ts → client-Dny_ro_v.d.ts} +41 -25
  20. package/dist/client.d.ts +1 -1
  21. package/dist/client.js +1 -1
  22. package/dist/index.d.ts +2 -2
  23. package/dist/index.js +8 -9
  24. package/dist/interpreter.d.ts +1 -1
  25. package/dist/jupyter-client.d.ts +1 -1
  26. package/dist/jupyter-client.js +2 -2
  27. package/dist/request-handler.d.ts +1 -1
  28. package/dist/request-handler.js +3 -5
  29. package/dist/sandbox.d.ts +1 -1
  30. package/dist/sandbox.js +3 -5
  31. package/dist/types.d.ts +10 -21
  32. package/dist/types.js +35 -9
  33. package/dist/types.js.map +1 -1
  34. package/package.json +2 -2
  35. package/src/client.ts +120 -135
  36. package/src/index.ts +8 -0
  37. package/src/sandbox.ts +290 -331
  38. package/src/types.ts +15 -24
  39. package/dist/chunk-3CQ6THKA.js.map +0 -1
  40. package/dist/chunk-6EWSYSO7.js +0 -46
  41. package/dist/chunk-6EWSYSO7.js.map +0 -1
  42. package/dist/chunk-CKIGERRS.js.map +0 -1
  43. /package/dist/{chunk-HHUDRGPY.js.map → chunk-BEQUGUY4.js.map} +0 -0
@@ -4,19 +4,12 @@ import {
4
4
  sanitizeSandboxId,
5
5
  validatePort
6
6
  } from "./chunk-6UAWTJ5S.js";
7
- import {
8
- parseSSEStream
9
- } from "./chunk-NNGBXDMY.js";
10
- import {
11
- ProcessNotFoundError,
12
- SandboxError
13
- } from "./chunk-6EWSYSO7.js";
14
7
  import {
15
8
  CodeInterpreter
16
9
  } from "./chunk-FKBV7CZS.js";
17
10
  import {
18
11
  JupyterClient
19
- } from "./chunk-HHUDRGPY.js";
12
+ } from "./chunk-BEQUGUY4.js";
20
13
 
21
14
  // src/sandbox.ts
22
15
  import { Container, getContainer } from "@cloudflare/containers";
@@ -134,6 +127,7 @@ var Sandbox = class extends Container {
134
127
  client;
135
128
  sandboxName = null;
136
129
  codeInterpreter;
130
+ defaultSession = null;
137
131
  constructor(ctx, env) {
138
132
  super(ctx, env);
139
133
  this.client = new JupyterClient({
@@ -172,15 +166,15 @@ var Sandbox = class extends Container {
172
166
  async setEnvVars(envVars) {
173
167
  this.envVars = { ...this.envVars, ...envVars };
174
168
  console.log(`[Sandbox] Updated environment variables`);
169
+ if (this.defaultSession) {
170
+ await this.defaultSession.setEnvVars(envVars);
171
+ }
175
172
  }
176
173
  onStart() {
177
174
  console.log("Sandbox successfully started");
178
175
  }
179
176
  onStop() {
180
177
  console.log("Sandbox successfully shut down");
181
- if (this.client) {
182
- this.client.clearSession();
183
- }
184
178
  }
185
179
  onError(error) {
186
180
  console.log("Sandbox error:", error);
@@ -202,295 +196,97 @@ var Sandbox = class extends Container {
202
196
  if (proxyMatch) {
203
197
  return parseInt(proxyMatch[1]);
204
198
  }
205
- return 3e3;
206
- }
207
- // Enhanced exec method - always returns ExecResult with optional streaming
208
- // This replaces the old exec method to match ISandbox interface
209
- async exec(command, options) {
210
- const startTime = Date.now();
211
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
212
- let timeoutId;
213
- try {
214
- if (options?.signal?.aborted) {
215
- throw new Error("Operation was aborted");
216
- }
217
- let result;
218
- if (options?.stream && options?.onOutput) {
219
- result = await this.executeWithStreaming(
220
- command,
221
- options,
222
- startTime,
223
- timestamp
224
- );
225
- } else {
226
- const response = await this.client.execute(command, {
227
- sessionId: options?.sessionId,
228
- cwd: options?.cwd,
229
- env: options?.env
230
- });
231
- const duration = Date.now() - startTime;
232
- result = this.mapExecuteResponseToExecResult(
233
- response,
234
- duration,
235
- options?.sessionId
236
- );
237
- }
238
- if (options?.onComplete) {
239
- options.onComplete(result);
240
- }
241
- return result;
242
- } catch (error) {
243
- if (options?.onError && error instanceof Error) {
244
- options.onError(error);
245
- }
246
- throw error;
247
- } finally {
248
- if (timeoutId) {
249
- clearTimeout(timeoutId);
250
- }
199
+ if (url.port) {
200
+ return parseInt(url.port);
251
201
  }
202
+ return 3e3;
252
203
  }
253
- async executeWithStreaming(command, options, startTime, timestamp) {
254
- let stdout = "";
255
- let stderr = "";
256
- try {
257
- const stream = await this.client.executeCommandStream(
258
- command,
259
- options.sessionId
260
- );
261
- for await (const event of parseSSEStream(stream)) {
262
- if (options.signal?.aborted) {
263
- throw new Error("Operation was aborted");
264
- }
265
- switch (event.type) {
266
- case "stdout":
267
- case "stderr":
268
- if (event.data) {
269
- if (event.type === "stdout") stdout += event.data;
270
- if (event.type === "stderr") stderr += event.data;
271
- if (options.onOutput) {
272
- options.onOutput(event.type, event.data);
273
- }
274
- }
275
- break;
276
- case "complete": {
277
- const duration = Date.now() - startTime;
278
- return event.result || {
279
- success: event.exitCode === 0,
280
- exitCode: event.exitCode || 0,
281
- stdout,
282
- stderr,
283
- command,
284
- duration,
285
- timestamp,
286
- sessionId: options.sessionId
287
- };
288
- }
289
- case "error":
290
- throw new Error(event.error || "Command execution failed");
291
- }
292
- }
293
- throw new Error("Stream ended without completion event");
294
- } catch (error) {
295
- if (options.signal?.aborted) {
296
- throw new Error("Operation was aborted");
297
- }
298
- throw error;
204
+ // Helper to ensure default session is initialized
205
+ async ensureDefaultSession() {
206
+ if (!this.defaultSession) {
207
+ const sessionId = `sandbox-${this.sandboxName || "default"}`;
208
+ this.defaultSession = await this.createSession({
209
+ id: sessionId,
210
+ env: this.envVars || {},
211
+ cwd: "/workspace",
212
+ isolation: true
213
+ });
214
+ console.log(`[Sandbox] Default session initialized: ${sessionId}`);
299
215
  }
216
+ return this.defaultSession;
300
217
  }
301
- mapExecuteResponseToExecResult(response, duration, sessionId) {
302
- return {
303
- success: response.success,
304
- exitCode: response.exitCode,
305
- stdout: response.stdout,
306
- stderr: response.stderr,
307
- command: response.command,
308
- duration,
309
- timestamp: response.timestamp,
310
- sessionId
311
- };
218
+ async exec(command, options) {
219
+ const session = await this.ensureDefaultSession();
220
+ return session.exec(command, options);
312
221
  }
313
- // Background process management
314
222
  async startProcess(command, options) {
315
- try {
316
- const response = await this.client.startProcess(command, {
317
- processId: options?.processId,
318
- sessionId: options?.sessionId,
319
- timeout: options?.timeout,
320
- env: options?.env,
321
- cwd: options?.cwd,
322
- encoding: options?.encoding,
323
- autoCleanup: options?.autoCleanup
324
- });
325
- const process = response.process;
326
- const processObj = {
327
- id: process.id,
328
- pid: process.pid,
329
- command: process.command,
330
- status: process.status,
331
- startTime: new Date(process.startTime),
332
- endTime: void 0,
333
- exitCode: void 0,
334
- sessionId: process.sessionId,
335
- async kill() {
336
- throw new Error("Method will be replaced");
337
- },
338
- async getStatus() {
339
- throw new Error("Method will be replaced");
340
- },
341
- async getLogs() {
342
- throw new Error("Method will be replaced");
343
- }
344
- };
345
- processObj.kill = async (signal) => {
346
- await this.killProcess(process.id, signal);
347
- };
348
- processObj.getStatus = async () => {
349
- const current = await this.getProcess(process.id);
350
- return current?.status || "error";
351
- };
352
- processObj.getLogs = async () => {
353
- const logs = await this.getProcessLogs(process.id);
354
- return { stdout: logs.stdout, stderr: logs.stderr };
355
- };
356
- if (options?.onStart) {
357
- options.onStart(processObj);
358
- }
359
- return processObj;
360
- } catch (error) {
361
- if (options?.onError && error instanceof Error) {
362
- options.onError(error);
363
- }
364
- throw error;
365
- }
223
+ const session = await this.ensureDefaultSession();
224
+ return session.startProcess(command, options);
366
225
  }
367
226
  async listProcesses() {
368
- const response = await this.client.listProcesses();
369
- return response.processes.map((processData) => ({
370
- id: processData.id,
371
- pid: processData.pid,
372
- command: processData.command,
373
- status: processData.status,
374
- startTime: new Date(processData.startTime),
375
- endTime: processData.endTime ? new Date(processData.endTime) : void 0,
376
- exitCode: processData.exitCode,
377
- sessionId: processData.sessionId,
378
- kill: async (signal) => {
379
- await this.killProcess(processData.id, signal);
380
- },
381
- getStatus: async () => {
382
- const current = await this.getProcess(processData.id);
383
- return current?.status || "error";
384
- },
385
- getLogs: async () => {
386
- const logs = await this.getProcessLogs(processData.id);
387
- return { stdout: logs.stdout, stderr: logs.stderr };
388
- }
389
- }));
227
+ const session = await this.ensureDefaultSession();
228
+ return session.listProcesses();
390
229
  }
391
230
  async getProcess(id) {
392
- const response = await this.client.getProcess(id);
393
- if (!response.process) {
394
- return null;
395
- }
396
- const processData = response.process;
397
- return {
398
- id: processData.id,
399
- pid: processData.pid,
400
- command: processData.command,
401
- status: processData.status,
402
- startTime: new Date(processData.startTime),
403
- endTime: processData.endTime ? new Date(processData.endTime) : void 0,
404
- exitCode: processData.exitCode,
405
- sessionId: processData.sessionId,
406
- kill: async (signal) => {
407
- await this.killProcess(processData.id, signal);
408
- },
409
- getStatus: async () => {
410
- const current = await this.getProcess(processData.id);
411
- return current?.status || "error";
412
- },
413
- getLogs: async () => {
414
- const logs = await this.getProcessLogs(processData.id);
415
- return { stdout: logs.stdout, stderr: logs.stderr };
416
- }
417
- };
231
+ const session = await this.ensureDefaultSession();
232
+ return session.getProcess(id);
418
233
  }
419
- async killProcess(id, _signal) {
420
- try {
421
- await this.client.killProcess(id);
422
- } catch (error) {
423
- if (error instanceof Error && error.message.includes("Process not found")) {
424
- throw new ProcessNotFoundError(id);
425
- }
426
- throw new SandboxError(
427
- `Failed to kill process ${id}: ${error instanceof Error ? error.message : "Unknown error"}`,
428
- "KILL_PROCESS_FAILED"
429
- );
430
- }
234
+ async killProcess(id, signal) {
235
+ const session = await this.ensureDefaultSession();
236
+ return session.killProcess(id, signal);
431
237
  }
432
238
  async killAllProcesses() {
433
- const response = await this.client.killAllProcesses();
434
- return response.killedCount;
239
+ const session = await this.ensureDefaultSession();
240
+ return session.killAllProcesses();
435
241
  }
436
242
  async cleanupCompletedProcesses() {
437
- return 0;
243
+ const session = await this.ensureDefaultSession();
244
+ return session.cleanupCompletedProcesses();
438
245
  }
439
246
  async getProcessLogs(id) {
440
- try {
441
- const response = await this.client.getProcessLogs(id);
442
- return {
443
- stdout: response.stdout,
444
- stderr: response.stderr
445
- };
446
- } catch (error) {
447
- if (error instanceof Error && error.message.includes("Process not found")) {
448
- throw new ProcessNotFoundError(id);
449
- }
450
- throw error;
451
- }
247
+ const session = await this.ensureDefaultSession();
248
+ return session.getProcessLogs(id);
452
249
  }
453
- // Streaming methods - return ReadableStream for RPC compatibility
250
+ // Streaming methods - delegates to default session
454
251
  async execStream(command, options) {
455
- if (options?.signal?.aborted) {
456
- throw new Error("Operation was aborted");
457
- }
458
- const stream = await this.client.executeCommandStream(
459
- command,
460
- options?.sessionId
461
- );
462
- return stream;
252
+ const session = await this.ensureDefaultSession();
253
+ return session.execStream(command, options);
463
254
  }
464
255
  async streamProcessLogs(processId, options) {
465
- if (options?.signal?.aborted) {
466
- throw new Error("Operation was aborted");
467
- }
468
- const stream = await this.client.streamProcessLogs(processId);
469
- return stream;
256
+ const session = await this.ensureDefaultSession();
257
+ return session.streamProcessLogs(processId, options);
470
258
  }
471
259
  async gitCheckout(repoUrl, options) {
472
- return this.client.gitCheckout(repoUrl, options.branch, options.targetDir);
260
+ const session = await this.ensureDefaultSession();
261
+ return session.gitCheckout(repoUrl, options);
473
262
  }
474
263
  async mkdir(path, options = {}) {
475
- return this.client.mkdir(path, options.recursive);
264
+ const session = await this.ensureDefaultSession();
265
+ return session.mkdir(path, options);
476
266
  }
477
267
  async writeFile(path, content, options = {}) {
478
- return this.client.writeFile(path, content, options.encoding);
268
+ const session = await this.ensureDefaultSession();
269
+ return session.writeFile(path, content, options);
479
270
  }
480
271
  async deleteFile(path) {
481
- return this.client.deleteFile(path);
272
+ const session = await this.ensureDefaultSession();
273
+ return session.deleteFile(path);
482
274
  }
483
275
  async renameFile(oldPath, newPath) {
484
- return this.client.renameFile(oldPath, newPath);
276
+ const session = await this.ensureDefaultSession();
277
+ return session.renameFile(oldPath, newPath);
485
278
  }
486
279
  async moveFile(sourcePath, destinationPath) {
487
- return this.client.moveFile(sourcePath, destinationPath);
280
+ const session = await this.ensureDefaultSession();
281
+ return session.moveFile(sourcePath, destinationPath);
488
282
  }
489
283
  async readFile(path, options = {}) {
490
- return this.client.readFile(path, options.encoding);
284
+ const session = await this.ensureDefaultSession();
285
+ return session.readFile(path, options);
491
286
  }
492
287
  async listFiles(path, options = {}) {
493
- return this.client.listFiles(path, options);
288
+ const session = await this.ensureDefaultSession();
289
+ return session.listFiles(path, options);
494
290
  }
495
291
  async exposePort(port, options) {
496
292
  await this.client.exposePort(port, options?.name);
@@ -680,6 +476,186 @@ var Sandbox = class extends Container {
680
476
  async deleteCodeContext(contextId) {
681
477
  return this.codeInterpreter.deleteCodeContext(contextId);
682
478
  }
479
+ // ============================================================================
480
+ // Session Management (Simple Isolation)
481
+ // ============================================================================
482
+ /**
483
+ * Create a new execution session with isolation
484
+ * Returns a session object with exec() method
485
+ */
486
+ async createSession(options) {
487
+ const sessionId = options.id || `session-${Date.now()}`;
488
+ await this.client.createSession({
489
+ id: sessionId,
490
+ env: options.env,
491
+ cwd: options.cwd,
492
+ isolation: options.isolation
493
+ });
494
+ return {
495
+ id: sessionId,
496
+ // Command execution - clean method names
497
+ exec: async (command, options2) => {
498
+ const result = await this.client.exec(sessionId, command);
499
+ return {
500
+ ...result,
501
+ command,
502
+ duration: 0,
503
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
504
+ };
505
+ },
506
+ execStream: async (command, options2) => {
507
+ return await this.client.execStream(sessionId, command);
508
+ },
509
+ // Process management - route to session-aware methods
510
+ startProcess: async (command, options2) => {
511
+ const response = await this.client.startProcess(command, sessionId, {
512
+ processId: options2?.processId,
513
+ timeout: options2?.timeout,
514
+ env: options2?.env,
515
+ cwd: options2?.cwd,
516
+ encoding: options2?.encoding,
517
+ autoCleanup: options2?.autoCleanup
518
+ });
519
+ const process = response.process;
520
+ return {
521
+ id: process.id,
522
+ pid: process.pid,
523
+ command: process.command,
524
+ status: process.status,
525
+ startTime: new Date(process.startTime),
526
+ endTime: process.endTime ? new Date(process.endTime) : void 0,
527
+ exitCode: process.exitCode ?? void 0,
528
+ kill: async (signal) => {
529
+ await this.client.killProcess(process.id);
530
+ },
531
+ getStatus: async () => {
532
+ const resp = await this.client.getProcess(process.id);
533
+ return resp.process?.status || "error";
534
+ },
535
+ getLogs: async () => {
536
+ return await this.client.getProcessLogs(process.id);
537
+ }
538
+ };
539
+ },
540
+ listProcesses: async () => {
541
+ const response = await this.client.listProcesses(sessionId);
542
+ return response.processes.map((p) => ({
543
+ id: p.id,
544
+ pid: p.pid,
545
+ command: p.command,
546
+ status: p.status,
547
+ startTime: new Date(p.startTime),
548
+ endTime: p.endTime ? new Date(p.endTime) : void 0,
549
+ exitCode: p.exitCode ?? void 0,
550
+ kill: async (signal) => {
551
+ await this.client.killProcess(p.id);
552
+ },
553
+ getStatus: async () => {
554
+ const processResp = await this.client.getProcess(p.id);
555
+ return processResp.process?.status || "error";
556
+ },
557
+ getLogs: async () => {
558
+ return this.client.getProcessLogs(p.id);
559
+ }
560
+ }));
561
+ },
562
+ getProcess: async (id) => {
563
+ const response = await this.client.getProcess(id);
564
+ if (!response.process) return null;
565
+ const p = response.process;
566
+ return {
567
+ id: p.id,
568
+ pid: p.pid,
569
+ command: p.command,
570
+ status: p.status,
571
+ startTime: new Date(p.startTime),
572
+ endTime: p.endTime ? new Date(p.endTime) : void 0,
573
+ exitCode: p.exitCode ?? void 0,
574
+ kill: async (signal) => {
575
+ await this.client.killProcess(p.id);
576
+ },
577
+ getStatus: async () => {
578
+ const processResp = await this.client.getProcess(p.id);
579
+ return processResp.process?.status || "error";
580
+ },
581
+ getLogs: async () => {
582
+ return this.client.getProcessLogs(p.id);
583
+ }
584
+ };
585
+ },
586
+ killProcess: async (id, signal) => {
587
+ await this.client.killProcess(id);
588
+ },
589
+ killAllProcesses: async () => {
590
+ const response = await this.client.killAllProcesses(sessionId);
591
+ return response.killedCount;
592
+ },
593
+ streamProcessLogs: async (processId, options2) => {
594
+ return await this.client.streamProcessLogs(processId, options2);
595
+ },
596
+ getProcessLogs: async (id) => {
597
+ return await this.client.getProcessLogs(id);
598
+ },
599
+ cleanupCompletedProcesses: async () => {
600
+ return 0;
601
+ },
602
+ // File operations - clean method names (no "InSession" suffix)
603
+ writeFile: async (path, content, options2) => {
604
+ return await this.client.writeFile(path, content, options2?.encoding, sessionId);
605
+ },
606
+ readFile: async (path, options2) => {
607
+ return await this.client.readFile(path, options2?.encoding, sessionId);
608
+ },
609
+ mkdir: async (path, options2) => {
610
+ return await this.client.mkdir(path, options2?.recursive, sessionId);
611
+ },
612
+ deleteFile: async (path) => {
613
+ return await this.client.deleteFile(path, sessionId);
614
+ },
615
+ renameFile: async (oldPath, newPath) => {
616
+ return await this.client.renameFile(oldPath, newPath, sessionId);
617
+ },
618
+ moveFile: async (sourcePath, destinationPath) => {
619
+ return await this.client.moveFile(sourcePath, destinationPath, sessionId);
620
+ },
621
+ listFiles: async (path, options2) => {
622
+ return await this.client.listFiles(path, sessionId, options2);
623
+ },
624
+ gitCheckout: async (repoUrl, options2) => {
625
+ return await this.client.gitCheckout(repoUrl, sessionId, options2?.branch, options2?.targetDir);
626
+ },
627
+ // Port management
628
+ exposePort: async (port, options2) => {
629
+ return await this.exposePort(port, options2);
630
+ },
631
+ unexposePort: async (port) => {
632
+ return await this.unexposePort(port);
633
+ },
634
+ getExposedPorts: async (hostname) => {
635
+ return await this.getExposedPorts(hostname);
636
+ },
637
+ // Environment management
638
+ setEnvVars: async (envVars) => {
639
+ console.log(`[Session ${sessionId}] Environment variables update not yet implemented`);
640
+ },
641
+ // Code Interpreter API
642
+ createCodeContext: async (options2) => {
643
+ return await this.createCodeContext(options2);
644
+ },
645
+ runCode: async (code, options2) => {
646
+ return await this.runCode(code, options2);
647
+ },
648
+ runCodeStream: async (code, options2) => {
649
+ return await this.runCodeStream(code, options2);
650
+ },
651
+ listCodeContexts: async () => {
652
+ return await this.listCodeContexts();
653
+ },
654
+ deleteCodeContext: async (contextId) => {
655
+ return await this.deleteCodeContext(contextId);
656
+ }
657
+ };
658
+ }
683
659
  };
684
660
 
685
661
  export {
@@ -688,4 +664,4 @@ export {
688
664
  proxyToSandbox,
689
665
  isLocalhostPattern
690
666
  };
691
- //# sourceMappingURL=chunk-CKIGERRS.js.map
667
+ //# sourceMappingURL=chunk-LFLJGISB.js.map