@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
package/src/client.ts CHANGED
@@ -28,14 +28,14 @@ interface GitCheckoutRequest {
28
28
  repoUrl: string;
29
29
  branch?: string;
30
30
  targetDir?: string;
31
- sessionId?: string;
31
+ sessionId: string;
32
32
  }
33
33
 
34
34
 
35
35
  interface MkdirRequest {
36
36
  path: string;
37
37
  recursive?: boolean;
38
- sessionId?: string;
38
+ sessionId: string;
39
39
  }
40
40
 
41
41
 
@@ -43,34 +43,34 @@ interface WriteFileRequest {
43
43
  path: string;
44
44
  content: string;
45
45
  encoding?: string;
46
- sessionId?: string;
46
+ sessionId: string;
47
47
  }
48
48
 
49
49
 
50
50
  interface ReadFileRequest {
51
51
  path: string;
52
52
  encoding?: string;
53
- sessionId?: string;
53
+ sessionId: string;
54
54
  }
55
55
 
56
56
 
57
57
  interface DeleteFileRequest {
58
58
  path: string;
59
- sessionId?: string;
59
+ sessionId: string;
60
60
  }
61
61
 
62
62
 
63
63
  interface RenameFileRequest {
64
64
  oldPath: string;
65
65
  newPath: string;
66
- sessionId?: string;
66
+ sessionId: string;
67
67
  }
68
68
 
69
69
 
70
70
  interface MoveFileRequest {
71
71
  sourcePath: string;
72
72
  destinationPath: string;
73
- sessionId?: string;
73
+ sessionId: string;
74
74
  }
75
75
 
76
76
 
@@ -80,7 +80,7 @@ interface ListFilesRequest {
80
80
  recursive?: boolean;
81
81
  includeHidden?: boolean;
82
82
  };
83
- sessionId?: string;
83
+ sessionId: string;
84
84
  }
85
85
 
86
86
 
@@ -143,7 +143,6 @@ interface HttpClientOptions {
143
143
  export class HttpClient {
144
144
  private baseUrl: string;
145
145
  private options: HttpClientOptions;
146
- private sessionId: string | null = null;
147
146
 
148
147
  constructor(options: HttpClientOptions = {}) {
149
148
  this.options = {
@@ -193,25 +192,52 @@ export class HttpClient {
193
192
  }
194
193
  }
195
194
 
196
- async execute(
195
+ async createSession(options: {
196
+ id: string;
197
+ env?: Record<string, string>;
198
+ cwd?: string;
199
+ isolation?: boolean;
200
+ }): Promise<{ success: boolean; id: string; message: string }> {
201
+ try {
202
+ const response = await this.doFetch(`/api/session/create`, {
203
+ method: "POST",
204
+ headers: {
205
+ "Content-Type": "application/json",
206
+ },
207
+ body: JSON.stringify(options),
208
+ });
209
+
210
+ if (!response.ok) {
211
+ const errorData = (await response.json().catch(() => ({}))) as {
212
+ error?: string;
213
+ };
214
+ throw new Error(
215
+ errorData.error || `Failed to create session: ${response.status}`
216
+ );
217
+ }
218
+
219
+ const data = await response.json() as { success: boolean; id: string; message: string };
220
+ console.log(`[HTTP Client] Session created: ${options.id}`);
221
+ return data;
222
+ } catch (error) {
223
+ console.error("[HTTP Client] Error creating session:", error);
224
+ throw error;
225
+ }
226
+ }
227
+
228
+ async exec(
229
+ sessionId: string,
197
230
  command: string,
198
- options: Pick<BaseExecOptions, "sessionId" | "cwd" | "env">
231
+ options?: Pick<BaseExecOptions, "cwd" | "env">
199
232
  ): Promise<ExecuteResponse> {
200
233
  try {
201
- const targetSessionId = options.sessionId || this.sessionId;
202
- const executeRequest = {
203
- command,
204
- sessionId: targetSessionId,
205
- cwd: options.cwd,
206
- env: options.env,
207
- } satisfies ExecuteRequest;
208
-
234
+ // Always use session-specific endpoint
209
235
  const response = await this.doFetch(`/api/execute`, {
210
- body: JSON.stringify(executeRequest),
236
+ method: "POST",
211
237
  headers: {
212
238
  "Content-Type": "application/json",
213
239
  },
214
- method: "POST",
240
+ body: JSON.stringify({ id: sessionId, command }),
215
241
  });
216
242
 
217
243
  if (!response.ok) {
@@ -219,27 +245,34 @@ export class HttpClient {
219
245
  error?: string;
220
246
  };
221
247
  throw new Error(
222
- errorData.error || `HTTP error! status: ${response.status}`
248
+ errorData.error || `Failed to execute in session: ${response.status}`
223
249
  );
224
250
  }
225
251
 
226
- const data: ExecuteResponse = await response.json();
252
+ const data = await response.json() as { stdout: string; stderr: string; exitCode: number; success: boolean };
227
253
  console.log(
228
- `[HTTP Client] Command executed: ${command}, Success: ${data.success}`
254
+ `[HTTP Client] Command executed in session ${sessionId}: ${command}`
229
255
  );
256
+
257
+ // Convert to ExecuteResponse format for consistency
258
+ const executeResponse: ExecuteResponse = {
259
+ ...data,
260
+ command,
261
+ timestamp: new Date().toISOString()
262
+ };
230
263
 
231
264
  // Call the callback if provided
232
265
  this.options.onCommandComplete?.(
233
- data.success,
234
- data.exitCode,
235
- data.stdout,
236
- data.stderr,
237
- data.command
266
+ executeResponse.success,
267
+ executeResponse.exitCode,
268
+ executeResponse.stdout,
269
+ executeResponse.stderr,
270
+ executeResponse.command
238
271
  );
239
272
 
240
- return data;
273
+ return executeResponse;
241
274
  } catch (error) {
242
- console.error("[HTTP Client] Error executing command:", error);
275
+ console.error("[HTTP Client] Error executing in session:", error);
243
276
  this.options.onError?.(
244
277
  error instanceof Error ? error.message : "Unknown error",
245
278
  command
@@ -248,23 +281,21 @@ export class HttpClient {
248
281
  }
249
282
  }
250
283
 
251
- async executeCommandStream(
252
- command: string,
253
- sessionId?: string
284
+ async execStream(
285
+ sessionId: string,
286
+ command: string
254
287
  ): Promise<ReadableStream<Uint8Array>> {
255
288
  try {
256
- const targetSessionId = sessionId || this.sessionId;
257
-
289
+ // Always use session-specific streaming endpoint
258
290
  const response = await this.doFetch(`/api/execute/stream`, {
259
- body: JSON.stringify({
260
- command,
261
- sessionId: targetSessionId,
262
- }),
291
+ method: "POST",
263
292
  headers: {
264
293
  "Content-Type": "application/json",
265
- Accept: "text/event-stream",
266
294
  },
267
- method: "POST",
295
+ body: JSON.stringify({
296
+ id: sessionId,
297
+ command
298
+ }),
268
299
  });
269
300
 
270
301
  if (!response.ok) {
@@ -272,38 +303,37 @@ export class HttpClient {
272
303
  error?: string;
273
304
  };
274
305
  throw new Error(
275
- errorData.error || `HTTP error! status: ${response.status}`
306
+ errorData.error || `Failed to stream execute in session: ${response.status}`
276
307
  );
277
308
  }
278
309
 
279
310
  if (!response.body) {
280
- throw new Error("No response body for streaming request");
311
+ throw new Error("No response body for streaming execution");
281
312
  }
282
313
 
283
- console.log(`[HTTP Client] Started command stream: ${command}`);
284
-
314
+ console.log(
315
+ `[HTTP Client] Started streaming command in session ${sessionId}: ${command}`
316
+ );
285
317
  return response.body;
286
318
  } catch (error) {
287
- console.error("[HTTP Client] Error in command stream:", error);
319
+ console.error("[HTTP Client] Error streaming execute in session:", error);
288
320
  throw error;
289
321
  }
290
322
  }
291
323
 
292
324
  async gitCheckout(
293
325
  repoUrl: string,
326
+ sessionId: string,
294
327
  branch: string = "main",
295
- targetDir?: string,
296
- sessionId?: string
328
+ targetDir?: string
297
329
  ): Promise<GitCheckoutResponse> {
298
330
  try {
299
- const targetSessionId = sessionId || this.sessionId;
300
-
301
331
  const response = await this.doFetch(`/api/git/checkout`, {
302
332
  body: JSON.stringify({
303
333
  branch,
304
334
  repoUrl,
305
- sessionId: targetSessionId,
306
335
  targetDir,
336
+ sessionId,
307
337
  } as GitCheckoutRequest),
308
338
  headers: {
309
339
  "Content-Type": "application/json",
@@ -335,16 +365,14 @@ export class HttpClient {
335
365
  async mkdir(
336
366
  path: string,
337
367
  recursive: boolean = false,
338
- sessionId?: string
368
+ sessionId: string
339
369
  ): Promise<MkdirResponse> {
340
370
  try {
341
- const targetSessionId = sessionId || this.sessionId;
342
-
343
371
  const response = await this.doFetch(`/api/mkdir`, {
344
372
  body: JSON.stringify({
345
373
  path,
346
374
  recursive,
347
- sessionId: targetSessionId,
375
+ sessionId,
348
376
  } as MkdirRequest),
349
377
  headers: {
350
378
  "Content-Type": "application/json",
@@ -363,7 +391,7 @@ export class HttpClient {
363
391
 
364
392
  const data: MkdirResponse = await response.json();
365
393
  console.log(
366
- `[HTTP Client] Directory created: ${path}, Success: ${data.success}, Recursive: ${data.recursive}`
394
+ `[HTTP Client] Directory created: ${path}, Success: ${data.success}, Recursive: ${data.recursive}${sessionId ? ` in session: ${sessionId}` : ''}`
367
395
  );
368
396
 
369
397
  return data;
@@ -377,17 +405,15 @@ export class HttpClient {
377
405
  path: string,
378
406
  content: string,
379
407
  encoding: string = "utf-8",
380
- sessionId?: string
408
+ sessionId: string
381
409
  ): Promise<WriteFileResponse> {
382
410
  try {
383
- const targetSessionId = sessionId || this.sessionId;
384
-
385
411
  const response = await this.doFetch(`/api/write`, {
386
412
  body: JSON.stringify({
387
413
  content,
388
414
  encoding,
389
415
  path,
390
- sessionId: targetSessionId,
416
+ sessionId,
391
417
  } as WriteFileRequest),
392
418
  headers: {
393
419
  "Content-Type": "application/json",
@@ -406,7 +432,7 @@ export class HttpClient {
406
432
 
407
433
  const data: WriteFileResponse = await response.json();
408
434
  console.log(
409
- `[HTTP Client] File written: ${path}, Success: ${data.success}`
435
+ `[HTTP Client] File written: ${path}, Success: ${data.success}${sessionId ? ` in session: ${sessionId}` : ''}`
410
436
  );
411
437
 
412
438
  return data;
@@ -419,16 +445,14 @@ export class HttpClient {
419
445
  async readFile(
420
446
  path: string,
421
447
  encoding: string = "utf-8",
422
- sessionId?: string
448
+ sessionId: string
423
449
  ): Promise<ReadFileResponse> {
424
450
  try {
425
- const targetSessionId = sessionId || this.sessionId;
426
-
427
451
  const response = await this.doFetch(`/api/read`, {
428
452
  body: JSON.stringify({
429
453
  encoding,
430
454
  path,
431
- sessionId: targetSessionId,
455
+ sessionId,
432
456
  } as ReadFileRequest),
433
457
  headers: {
434
458
  "Content-Type": "application/json",
@@ -447,7 +471,7 @@ export class HttpClient {
447
471
 
448
472
  const data: ReadFileResponse = await response.json();
449
473
  console.log(
450
- `[HTTP Client] File read: ${path}, Success: ${data.success}, Content length: ${data.content.length}`
474
+ `[HTTP Client] File read: ${path}, Success: ${data.success}, Content length: ${data.content.length}${sessionId ? ` in session: ${sessionId}` : ''}`
451
475
  );
452
476
 
453
477
  return data;
@@ -459,15 +483,13 @@ export class HttpClient {
459
483
 
460
484
  async deleteFile(
461
485
  path: string,
462
- sessionId?: string
486
+ sessionId: string
463
487
  ): Promise<DeleteFileResponse> {
464
488
  try {
465
- const targetSessionId = sessionId || this.sessionId;
466
-
467
489
  const response = await this.doFetch(`/api/delete`, {
468
490
  body: JSON.stringify({
469
491
  path,
470
- sessionId: targetSessionId,
492
+ sessionId,
471
493
  } as DeleteFileRequest),
472
494
  headers: {
473
495
  "Content-Type": "application/json",
@@ -486,7 +508,7 @@ export class HttpClient {
486
508
 
487
509
  const data: DeleteFileResponse = await response.json();
488
510
  console.log(
489
- `[HTTP Client] File deleted: ${path}, Success: ${data.success}`
511
+ `[HTTP Client] File deleted: ${path}, Success: ${data.success}${sessionId ? ` in session: ${sessionId}` : ''}`
490
512
  );
491
513
 
492
514
  return data;
@@ -499,16 +521,14 @@ export class HttpClient {
499
521
  async renameFile(
500
522
  oldPath: string,
501
523
  newPath: string,
502
- sessionId?: string
524
+ sessionId: string
503
525
  ): Promise<RenameFileResponse> {
504
526
  try {
505
- const targetSessionId = sessionId || this.sessionId;
506
-
507
527
  const response = await this.doFetch(`/api/rename`, {
508
528
  body: JSON.stringify({
509
529
  newPath,
510
530
  oldPath,
511
- sessionId: targetSessionId,
531
+ sessionId,
512
532
  } as RenameFileRequest),
513
533
  headers: {
514
534
  "Content-Type": "application/json",
@@ -527,7 +547,7 @@ export class HttpClient {
527
547
 
528
548
  const data: RenameFileResponse = await response.json();
529
549
  console.log(
530
- `[HTTP Client] File renamed: ${oldPath} -> ${newPath}, Success: ${data.success}`
550
+ `[HTTP Client] File renamed: ${oldPath} -> ${newPath}, Success: ${data.success}${sessionId ? ` in session: ${sessionId}` : ''}`
531
551
  );
532
552
 
533
553
  return data;
@@ -540,16 +560,14 @@ export class HttpClient {
540
560
  async moveFile(
541
561
  sourcePath: string,
542
562
  destinationPath: string,
543
- sessionId?: string
563
+ sessionId: string
544
564
  ): Promise<MoveFileResponse> {
545
565
  try {
546
- const targetSessionId = sessionId || this.sessionId;
547
-
548
566
  const response = await this.doFetch(`/api/move`, {
549
567
  body: JSON.stringify({
550
568
  destinationPath,
551
- sessionId: targetSessionId,
552
569
  sourcePath,
570
+ sessionId,
553
571
  } as MoveFileRequest),
554
572
  headers: {
555
573
  "Content-Type": "application/json",
@@ -568,7 +586,7 @@ export class HttpClient {
568
586
 
569
587
  const data: MoveFileResponse = await response.json();
570
588
  console.log(
571
- `[HTTP Client] File moved: ${sourcePath} -> ${destinationPath}, Success: ${data.success}`
589
+ `[HTTP Client] File moved: ${sourcePath} -> ${destinationPath}, Success: ${data.success}${sessionId ? ` in session: ${sessionId}` : ''}`
572
590
  );
573
591
 
574
592
  return data;
@@ -580,20 +598,18 @@ export class HttpClient {
580
598
 
581
599
  async listFiles(
582
600
  path: string,
601
+ sessionId: string,
583
602
  options?: {
584
603
  recursive?: boolean;
585
604
  includeHidden?: boolean;
586
- },
587
- sessionId?: string
605
+ }
588
606
  ): Promise<ListFilesResponse> {
589
607
  try {
590
- const targetSessionId = sessionId || this.sessionId;
591
-
592
608
  const response = await this.doFetch(`/api/list-files`, {
593
609
  body: JSON.stringify({
594
610
  path,
595
611
  options,
596
- sessionId: targetSessionId,
612
+ sessionId,
597
613
  } as ListFilesRequest),
598
614
  headers: {
599
615
  "Content-Type": "application/json",
@@ -612,7 +628,7 @@ export class HttpClient {
612
628
 
613
629
  const data: ListFilesResponse = await response.json();
614
630
  console.log(
615
- `[HTTP Client] Listed ${data.files.length} files in: ${path}, Success: ${data.success}`
631
+ `[HTTP Client] Listed ${data.files.length} files in: ${path}, Success: ${data.success}${sessionId ? ` in session: ${sessionId}` : ''}`
616
632
  );
617
633
 
618
634
  return data;
@@ -742,48 +758,13 @@ export class HttpClient {
742
758
  }
743
759
  }
744
760
 
745
- async getCommands(): Promise<string[]> {
746
- try {
747
- const response = await fetch(`${this.baseUrl}/api/commands`, {
748
- headers: {
749
- "Content-Type": "application/json",
750
- },
751
- method: "GET",
752
- });
753
-
754
- if (!response.ok) {
755
- throw new Error(`HTTP error! status: ${response.status}`);
756
- }
757
-
758
- const data: CommandsResponse = await response.json();
759
- console.log(
760
- `[HTTP Client] Available commands: ${data.availableCommands.length}`
761
- );
762
- return data.availableCommands;
763
- } catch (error) {
764
- console.error("[HTTP Client] Error getting commands:", error);
765
- throw error;
766
- }
767
- }
768
-
769
- getSessionId(): string | null {
770
- return this.sessionId;
771
- }
772
-
773
- setSessionId(sessionId: string): void {
774
- this.sessionId = sessionId;
775
- }
776
-
777
- clearSession(): void {
778
- this.sessionId = null;
779
- }
780
761
 
781
762
  // Process management methods
782
763
  async startProcess(
783
764
  command: string,
765
+ sessionId: string,
784
766
  options?: {
785
767
  processId?: string;
786
- sessionId?: string;
787
768
  timeout?: number;
788
769
  env?: Record<string, string>;
789
770
  cwd?: string;
@@ -792,15 +773,11 @@ export class HttpClient {
792
773
  }
793
774
  ): Promise<StartProcessResponse> {
794
775
  try {
795
- const targetSessionId = options?.sessionId || this.sessionId;
796
-
797
776
  const response = await this.doFetch("/api/process/start", {
798
777
  body: JSON.stringify({
799
778
  command,
800
- options: {
801
- ...options,
802
- sessionId: targetSessionId,
803
- },
779
+ sessionId,
780
+ options,
804
781
  } as StartProcessRequest),
805
782
  headers: {
806
783
  "Content-Type": "application/json",
@@ -829,9 +806,12 @@ export class HttpClient {
829
806
  }
830
807
  }
831
808
 
832
- async listProcesses(): Promise<ListProcessesResponse> {
809
+ async listProcesses(sessionId?: string): Promise<ListProcessesResponse> {
833
810
  try {
834
- const response = await this.doFetch("/api/process/list", {
811
+ const url = sessionId
812
+ ? `/api/process/list?session=${encodeURIComponent(sessionId)}`
813
+ : "/api/process/list";
814
+ const response = await this.doFetch(url, {
835
815
  headers: {
836
816
  "Content-Type": "application/json",
837
817
  },
@@ -922,13 +902,16 @@ export class HttpClient {
922
902
  }
923
903
  }
924
904
 
925
- async killAllProcesses(): Promise<{
905
+ async killAllProcesses(sessionId?: string): Promise<{
926
906
  success: boolean;
927
907
  killedCount: number;
928
908
  message: string;
929
909
  }> {
930
910
  try {
931
- const response = await this.doFetch("/api/process/kill-all", {
911
+ const url = sessionId
912
+ ? `/api/process/kill-all?session=${encodeURIComponent(sessionId)}`
913
+ : "/api/process/kill-all";
914
+ const response = await this.doFetch(url, {
932
915
  headers: {
933
916
  "Content-Type": "application/json",
934
917
  },
@@ -987,7 +970,8 @@ export class HttpClient {
987
970
  }
988
971
 
989
972
  async streamProcessLogs(
990
- processId: string
973
+ processId: string,
974
+ options?: { signal?: AbortSignal }
991
975
  ): Promise<ReadableStream<Uint8Array>> {
992
976
  try {
993
977
  const response = await this.doFetch(`/api/process/${processId}/stream`, {
@@ -996,6 +980,7 @@ export class HttpClient {
996
980
  "Cache-Control": "no-cache",
997
981
  },
998
982
  method: "GET",
983
+ signal: options?.signal,
999
984
  });
1000
985
 
1001
986
  if (!response.ok) {
package/src/index.ts CHANGED
@@ -44,13 +44,21 @@ export {
44
44
  export type {
45
45
  DeleteFileResponse,
46
46
  ExecEvent,
47
+ ExecOptions,
48
+ ExecResult,
47
49
  ExecuteResponse,
50
+ ExecutionSession,
48
51
  GitCheckoutResponse,
52
+ ISandbox,
49
53
  ListFilesResponse,
50
54
  LogEvent,
51
55
  MkdirResponse,
52
56
  MoveFileResponse,
57
+ Process,
58
+ ProcessOptions,
59
+ ProcessStatus,
53
60
  ReadFileResponse,
54
61
  RenameFileResponse,
62
+ StreamOptions,
55
63
  WriteFileResponse
56
64
  } from "./types";