@cloudflare/sandbox 0.0.0-7fd1847 → 0.0.0-80fd0f2

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 (74) hide show
  1. package/CHANGELOG.md +110 -15
  2. package/Dockerfile +84 -31
  3. package/README.md +9 -2
  4. package/dist/index.d.ts +1924 -9
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/index.js +3245 -64
  7. package/dist/index.js.map +1 -1
  8. package/package.json +9 -9
  9. package/src/clients/base-client.ts +39 -24
  10. package/src/clients/command-client.ts +8 -8
  11. package/src/clients/file-client.ts +51 -20
  12. package/src/clients/git-client.ts +9 -3
  13. package/src/clients/index.ts +12 -15
  14. package/src/clients/interpreter-client.ts +51 -47
  15. package/src/clients/port-client.ts +10 -10
  16. package/src/clients/process-client.ts +11 -8
  17. package/src/clients/sandbox-client.ts +2 -4
  18. package/src/clients/types.ts +6 -2
  19. package/src/clients/utility-client.ts +34 -5
  20. package/src/errors/adapter.ts +90 -32
  21. package/src/errors/classes.ts +189 -64
  22. package/src/errors/index.ts +9 -5
  23. package/src/file-stream.ts +11 -6
  24. package/src/index.ts +22 -15
  25. package/src/interpreter.ts +50 -41
  26. package/src/request-handler.ts +34 -21
  27. package/src/sandbox.ts +469 -150
  28. package/src/security.ts +21 -6
  29. package/src/sse-parser.ts +4 -3
  30. package/src/version.ts +6 -0
  31. package/tests/base-client.test.ts +116 -80
  32. package/tests/command-client.test.ts +149 -112
  33. package/tests/file-client.test.ts +373 -185
  34. package/tests/file-stream.test.ts +24 -20
  35. package/tests/get-sandbox.test.ts +149 -0
  36. package/tests/git-client.test.ts +260 -101
  37. package/tests/port-client.test.ts +100 -108
  38. package/tests/process-client.test.ts +204 -179
  39. package/tests/request-handler.test.ts +292 -0
  40. package/tests/sandbox.test.ts +303 -62
  41. package/tests/sse-parser.test.ts +17 -16
  42. package/tests/utility-client.test.ts +129 -56
  43. package/tests/version.test.ts +16 -0
  44. package/tsdown.config.ts +12 -0
  45. package/vitest.config.ts +6 -6
  46. package/dist/chunk-BFVUNTP4.js +0 -104
  47. package/dist/chunk-BFVUNTP4.js.map +0 -1
  48. package/dist/chunk-EKSWCBCA.js +0 -86
  49. package/dist/chunk-EKSWCBCA.js.map +0 -1
  50. package/dist/chunk-EXQOIRZI.js +0 -2351
  51. package/dist/chunk-EXQOIRZI.js.map +0 -1
  52. package/dist/chunk-JXZMAU2C.js +0 -559
  53. package/dist/chunk-JXZMAU2C.js.map +0 -1
  54. package/dist/chunk-Z532A7QC.js +0 -78
  55. package/dist/chunk-Z532A7QC.js.map +0 -1
  56. package/dist/file-stream.d.ts +0 -43
  57. package/dist/file-stream.js +0 -9
  58. package/dist/file-stream.js.map +0 -1
  59. package/dist/interpreter.d.ts +0 -33
  60. package/dist/interpreter.js +0 -8
  61. package/dist/interpreter.js.map +0 -1
  62. package/dist/request-handler.d.ts +0 -18
  63. package/dist/request-handler.js +0 -12
  64. package/dist/request-handler.js.map +0 -1
  65. package/dist/sandbox-D9K2ypln.d.ts +0 -583
  66. package/dist/sandbox.d.ts +0 -4
  67. package/dist/sandbox.js +0 -12
  68. package/dist/sandbox.js.map +0 -1
  69. package/dist/security.d.ts +0 -31
  70. package/dist/security.js +0 -13
  71. package/dist/security.js.map +0 -1
  72. package/dist/sse-parser.d.ts +0 -28
  73. package/dist/sse-parser.js +0 -11
  74. package/dist/sse-parser.js.map +0 -1
package/src/sandbox.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { DurableObject } from 'cloudflare:workers';
2
- import { Container, getContainer } from "@cloudflare/containers";
2
+ import { Container, getContainer, switchPort } from '@cloudflare/containers';
3
3
  import type {
4
4
  CodeContext,
5
5
  CreateContextOptions,
@@ -13,40 +13,65 @@ import type {
13
13
  ProcessOptions,
14
14
  ProcessStatus,
15
15
  RunCodeOptions,
16
+ SandboxOptions,
16
17
  SessionOptions,
17
18
  StreamOptions
18
- } from "@repo/shared";
19
- import { createLogger, runWithLogger, TraceContext } from "@repo/shared";
20
- import { type ExecuteResponse, SandboxClient } from "./clients";
19
+ } from '@repo/shared';
20
+ import { createLogger, runWithLogger, TraceContext } from '@repo/shared';
21
+ import { type ExecuteResponse, SandboxClient } from './clients';
21
22
  import type { ErrorResponse } from './errors';
22
23
  import { CustomDomainRequiredError, ErrorCode } from './errors';
23
- import { CodeInterpreter } from "./interpreter";
24
- import { isLocalhostPattern } from "./request-handler";
25
- import {
26
- SecurityError,
27
- sanitizeSandboxId,
28
- validatePort
29
- } from "./security";
30
- import { parseSSEStream } from "./sse-parser";
31
-
32
- export function getSandbox(ns: DurableObjectNamespace<Sandbox>, id: string, options?: {
33
- baseUrl: string
34
- }) {
35
- const stub = getContainer(ns, id);
24
+ import { CodeInterpreter } from './interpreter';
25
+ import { isLocalhostPattern } from './request-handler';
26
+ import { SecurityError, sanitizeSandboxId, validatePort } from './security';
27
+ import { parseSSEStream } from './sse-parser';
28
+ import { SDK_VERSION } from './version';
29
+
30
+ export function getSandbox(
31
+ ns: DurableObjectNamespace<Sandbox>,
32
+ id: string,
33
+ options?: SandboxOptions
34
+ ): Sandbox {
35
+ const stub = getContainer(ns, id) as unknown as Sandbox;
36
36
 
37
37
  // Store the name on first access
38
38
  stub.setSandboxName?.(id);
39
39
 
40
- if(options?.baseUrl) {
40
+ if (options?.baseUrl) {
41
41
  stub.setBaseUrl(options.baseUrl);
42
42
  }
43
43
 
44
- return stub;
44
+ if (options?.sleepAfter !== undefined) {
45
+ stub.setSleepAfter(options.sleepAfter);
46
+ }
47
+
48
+ if (options?.keepAlive !== undefined) {
49
+ stub.setKeepAlive(options.keepAlive);
50
+ }
51
+
52
+ return Object.assign(stub, {
53
+ wsConnect: connect(stub)
54
+ });
55
+ }
56
+
57
+ export function connect(stub: {
58
+ fetch: (request: Request) => Promise<Response>;
59
+ }) {
60
+ return async (request: Request, port: number) => {
61
+ // Validate port before routing
62
+ if (!validatePort(port)) {
63
+ throw new SecurityError(
64
+ `Invalid or restricted port: ${port}. Ports must be in range 1024-65535 and not reserved.`
65
+ );
66
+ }
67
+ const portSwitchedRequest = switchPort(request, port);
68
+ return await stub.fetch(portSwitchedRequest);
69
+ };
45
70
  }
46
71
 
47
72
  export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
48
73
  defaultPort = 3000; // Default port for the container's Bun server
49
- sleepAfter = "3m"; // Sleep the sandbox if no requests are made in this timeframe
74
+ sleepAfter: string | number = '10m'; // Sleep the sandbox if no requests are made in this timeframe
50
75
 
51
76
  client: SandboxClient;
52
77
  private codeInterpreter: CodeInterpreter;
@@ -56,14 +81,15 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
56
81
  private defaultSession: string | null = null;
57
82
  envVars: Record<string, string> = {};
58
83
  private logger: ReturnType<typeof createLogger>;
84
+ private keepAliveEnabled: boolean = false;
59
85
 
60
- constructor(ctx: DurableObject['ctx'], env: Env) {
86
+ constructor(ctx: DurableObjectState<{}>, env: Env) {
61
87
  super(ctx, env);
62
88
 
63
89
  const envObj = env as any;
64
90
  // Set sandbox environment variables from env object
65
91
  const sandboxEnvKeys = ['SANDBOX_LOG_LEVEL', 'SANDBOX_LOG_FORMAT'] as const;
66
- sandboxEnvKeys.forEach(key => {
92
+ sandboxEnvKeys.forEach((key) => {
67
93
  if (envObj?.[key]) {
68
94
  this.envVars[key] = envObj[key];
69
95
  }
@@ -77,17 +103,22 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
77
103
  this.client = new SandboxClient({
78
104
  logger: this.logger,
79
105
  port: 3000, // Control plane port
80
- stub: this,
106
+ stub: this
81
107
  });
82
108
 
83
109
  // Initialize code interpreter - pass 'this' after client is ready
84
110
  // The CodeInterpreter extracts client.interpreter from the sandbox
85
111
  this.codeInterpreter = new CodeInterpreter(this);
86
112
 
87
- // Load the sandbox name and port tokens from storage on initialization
113
+ // Load the sandbox name, port tokens, and default session from storage on initialization
88
114
  this.ctx.blockConcurrencyWhile(async () => {
89
- this.sandboxName = await this.ctx.storage.get<string>('sandboxName') || null;
90
- const storedTokens = await this.ctx.storage.get<Record<string, string>>('portTokens') || {};
115
+ this.sandboxName =
116
+ (await this.ctx.storage.get<string>('sandboxName')) || null;
117
+ this.defaultSession =
118
+ (await this.ctx.storage.get<string>('defaultSession')) || null;
119
+ const storedTokens =
120
+ (await this.ctx.storage.get<Record<string, string>>('portTokens')) ||
121
+ {};
91
122
 
92
123
  // Convert stored tokens back to Map
93
124
  this.portTokens = new Map();
@@ -111,12 +142,33 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
111
142
  this.baseUrl = baseUrl;
112
143
  await this.ctx.storage.put('baseUrl', baseUrl);
113
144
  } else {
114
- if(this.baseUrl !== baseUrl) {
115
- throw new Error('Base URL already set and different from one previously provided');
145
+ if (this.baseUrl !== baseUrl) {
146
+ throw new Error(
147
+ 'Base URL already set and different from one previously provided'
148
+ );
116
149
  }
117
150
  }
118
151
  }
119
152
 
153
+ // RPC method to set the sleep timeout
154
+ async setSleepAfter(sleepAfter: string | number): Promise<void> {
155
+ this.sleepAfter = sleepAfter;
156
+ }
157
+
158
+ // RPC method to enable keepAlive mode
159
+ async setKeepAlive(keepAlive: boolean): Promise<void> {
160
+ this.keepAliveEnabled = keepAlive;
161
+ if (keepAlive) {
162
+ this.logger.info(
163
+ 'KeepAlive mode enabled - container will stay alive until explicitly destroyed'
164
+ );
165
+ } else {
166
+ this.logger.info(
167
+ 'KeepAlive mode disabled - container will timeout normally'
168
+ );
169
+ }
170
+ }
171
+
120
172
  // RPC method to set environment variables
121
173
  async setEnvVars(envVars: Record<string, string>): Promise<void> {
122
174
  // Update local state for new sessions
@@ -129,10 +181,15 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
129
181
  const escapedValue = value.replace(/'/g, "'\\''");
130
182
  const exportCommand = `export ${key}='${escapedValue}'`;
131
183
 
132
- const result = await this.client.commands.execute(exportCommand, this.defaultSession);
184
+ const result = await this.client.commands.execute(
185
+ exportCommand,
186
+ this.defaultSession
187
+ );
133
188
 
134
189
  if (result.exitCode !== 0) {
135
- throw new Error(`Failed to set ${key}: ${result.stderr || 'Unknown error'}`);
190
+ throw new Error(
191
+ `Failed to set ${key}: ${result.stderr || 'Unknown error'}`
192
+ );
136
193
  }
137
194
  }
138
195
  }
@@ -148,6 +205,61 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
148
205
 
149
206
  override onStart() {
150
207
  this.logger.debug('Sandbox started');
208
+
209
+ // Check version compatibility asynchronously (don't block startup)
210
+ this.checkVersionCompatibility().catch((error) => {
211
+ this.logger.error(
212
+ 'Version compatibility check failed',
213
+ error instanceof Error ? error : new Error(String(error))
214
+ );
215
+ });
216
+ }
217
+
218
+ /**
219
+ * Check if the container version matches the SDK version
220
+ * Logs a warning if there's a mismatch
221
+ */
222
+ private async checkVersionCompatibility(): Promise<void> {
223
+ try {
224
+ // Get the SDK version (imported from version.ts)
225
+ const sdkVersion = SDK_VERSION;
226
+
227
+ // Get container version
228
+ const containerVersion = await this.client.utils.getVersion();
229
+
230
+ // If container version is unknown, it's likely an old container without the endpoint
231
+ if (containerVersion === 'unknown') {
232
+ this.logger.warn(
233
+ 'Container version check: Container version could not be determined. ' +
234
+ 'This may indicate an outdated container image. ' +
235
+ 'Please update your container to match SDK version ' +
236
+ sdkVersion
237
+ );
238
+ return;
239
+ }
240
+
241
+ // Check if versions match
242
+ if (containerVersion !== sdkVersion) {
243
+ const message =
244
+ `Version mismatch detected! SDK version (${sdkVersion}) does not match ` +
245
+ `container version (${containerVersion}). This may cause compatibility issues. ` +
246
+ `Please update your container image to version ${sdkVersion}`;
247
+
248
+ // Log warning - we can't reliably detect dev vs prod environment in Durable Objects
249
+ // so we always use warning level as requested by the user
250
+ this.logger.warn(message);
251
+ } else {
252
+ this.logger.debug('Version check passed', {
253
+ sdkVersion,
254
+ containerVersion
255
+ });
256
+ }
257
+ } catch (error) {
258
+ // Don't fail the sandbox initialization if version check fails
259
+ this.logger.debug('Version compatibility check encountered an error', {
260
+ error: error instanceof Error ? error.message : String(error)
261
+ });
262
+ }
151
263
  }
152
264
 
153
265
  override onStop() {
@@ -155,13 +267,34 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
155
267
  }
156
268
 
157
269
  override onError(error: unknown) {
158
- this.logger.error('Sandbox error', error instanceof Error ? error : new Error(String(error)));
270
+ this.logger.error(
271
+ 'Sandbox error',
272
+ error instanceof Error ? error : new Error(String(error))
273
+ );
274
+ }
275
+
276
+ /**
277
+ * Override onActivityExpired to prevent automatic shutdown when keepAlive is enabled
278
+ * When keepAlive is disabled, calls parent implementation which stops the container
279
+ */
280
+ override async onActivityExpired(): Promise<void> {
281
+ if (this.keepAliveEnabled) {
282
+ this.logger.debug(
283
+ 'Activity expired but keepAlive is enabled - container will stay alive'
284
+ );
285
+ // Do nothing - don't call stop(), container stays alive
286
+ } else {
287
+ // Default behavior: stop the container
288
+ this.logger.debug('Activity expired - stopping container');
289
+ await super.onActivityExpired();
290
+ }
159
291
  }
160
292
 
161
293
  // Override fetch to route internal container requests to appropriate ports
162
294
  override async fetch(request: Request): Promise<Response> {
163
295
  // Extract or generate trace ID from request
164
- const traceId = TraceContext.fromHeaders(request.headers) || TraceContext.generate();
296
+ const traceId =
297
+ TraceContext.fromHeaders(request.headers) || TraceContext.generate();
165
298
 
166
299
  // Create request-specific logger with trace ID
167
300
  const requestLogger = this.logger.child({ traceId, operation: 'fetch' });
@@ -176,7 +309,33 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
176
309
  await this.ctx.storage.put('sandboxName', name);
177
310
  }
178
311
 
179
- // Determine which port to route to
312
+ // Detect WebSocket upgrade request (RFC 6455 compliant)
313
+ const upgradeHeader = request.headers.get('Upgrade');
314
+ const connectionHeader = request.headers.get('Connection');
315
+ const isWebSocket =
316
+ upgradeHeader?.toLowerCase() === 'websocket' &&
317
+ connectionHeader?.toLowerCase().includes('upgrade');
318
+
319
+ if (isWebSocket) {
320
+ // WebSocket path: Let parent Container class handle WebSocket proxying
321
+ // This bypasses containerFetch() which uses JSRPC and cannot handle WebSocket upgrades
322
+ try {
323
+ requestLogger.debug('WebSocket upgrade requested', {
324
+ path: url.pathname,
325
+ port: this.determinePort(url)
326
+ });
327
+ return await super.fetch(request);
328
+ } catch (error) {
329
+ requestLogger.error(
330
+ 'WebSocket connection failed',
331
+ error instanceof Error ? error : new Error(String(error)),
332
+ { path: url.pathname }
333
+ );
334
+ throw error;
335
+ }
336
+ }
337
+
338
+ // Non-WebSocket: Use existing port determination and HTTP routing logic
180
339
  const port = this.determinePort(url);
181
340
 
182
341
  // Route to the appropriate port
@@ -184,6 +343,11 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
184
343
  });
185
344
  }
186
345
 
346
+ wsConnect(request: Request, port: number): Promise<Response> {
347
+ // Dummy implementation that will be overridden by the stub
348
+ throw new Error('Not implemented here to avoid RPC serialization issues');
349
+ }
350
+
187
351
  private determinePort(url: URL): number {
188
352
  // Extract port from proxy requests (e.g., /proxy/8080/*)
189
353
  const proxyMatch = url.pathname.match(/^\/proxy\/(\d+)/);
@@ -199,20 +363,41 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
199
363
  /**
200
364
  * Ensure default session exists - lazy initialization
201
365
  * This is called automatically by all public methods that need a session
366
+ *
367
+ * The session is persisted to Durable Object storage to survive hot reloads
368
+ * during development. If a session already exists in the container after reload,
369
+ * we reuse it instead of trying to create a new one.
202
370
  */
203
371
  private async ensureDefaultSession(): Promise<string> {
204
372
  if (!this.defaultSession) {
205
373
  const sessionId = `sandbox-${this.sandboxName || 'default'}`;
206
374
 
207
- // Create session in container
208
- await this.client.utils.createSession({
209
- id: sessionId,
210
- env: this.envVars || {},
211
- cwd: '/workspace',
212
- });
213
-
214
- this.defaultSession = sessionId;
215
- this.logger.debug('Default session initialized', { sessionId });
375
+ try {
376
+ // Try to create session in container
377
+ await this.client.utils.createSession({
378
+ id: sessionId,
379
+ env: this.envVars || {},
380
+ cwd: '/workspace'
381
+ });
382
+
383
+ this.defaultSession = sessionId;
384
+ // Persist to storage so it survives hot reloads
385
+ await this.ctx.storage.put('defaultSession', sessionId);
386
+ this.logger.debug('Default session initialized', { sessionId });
387
+ } catch (error: any) {
388
+ // If session already exists (e.g., after hot reload), reuse it
389
+ if (error?.message?.includes('already exists')) {
390
+ this.logger.debug('Reusing existing session after reload', {
391
+ sessionId
392
+ });
393
+ this.defaultSession = sessionId;
394
+ // Persist to storage in case it wasn't saved before
395
+ await this.ctx.storage.put('defaultSession', sessionId);
396
+ } else {
397
+ // Re-throw other errors
398
+ throw error;
399
+ }
400
+ }
216
401
  }
217
402
  return this.defaultSession;
218
403
  }
@@ -236,7 +421,6 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
236
421
  const startTime = Date.now();
237
422
  const timestamp = new Date().toISOString();
238
423
 
239
- // Handle timeout
240
424
  let timeoutId: NodeJS.Timeout | undefined;
241
425
 
242
426
  try {
@@ -249,13 +433,23 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
249
433
 
250
434
  if (options?.stream && options?.onOutput) {
251
435
  // Streaming with callbacks - we need to collect the final result
252
- result = await this.executeWithStreaming(command, sessionId, options, startTime, timestamp);
436
+ result = await this.executeWithStreaming(
437
+ command,
438
+ sessionId,
439
+ options,
440
+ startTime,
441
+ timestamp
442
+ );
253
443
  } else {
254
444
  // Regular execution with session
255
445
  const response = await this.client.commands.execute(command, sessionId);
256
446
 
257
447
  const duration = Date.now() - startTime;
258
- result = this.mapExecuteResponseToExecResult(response, duration, sessionId);
448
+ result = this.mapExecuteResponseToExecResult(
449
+ response,
450
+ duration,
451
+ sessionId
452
+ );
259
453
  }
260
454
 
261
455
  // Call completion callback if provided
@@ -287,7 +481,10 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
287
481
  let stderr = '';
288
482
 
289
483
  try {
290
- const stream = await this.client.commands.executeStream(command, sessionId);
484
+ const stream = await this.client.commands.executeStream(
485
+ command,
486
+ sessionId
487
+ );
291
488
 
292
489
  for await (const event of parseSSEStream<ExecEvent>(stream)) {
293
490
  // Check for cancellation
@@ -332,7 +529,6 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
332
529
 
333
530
  // If we get here without a complete event, something went wrong
334
531
  throw new Error('Stream ended without completion event');
335
-
336
532
  } catch (error) {
337
533
  if (options.signal?.aborted) {
338
534
  throw new Error('Operation was aborted');
@@ -380,8 +576,15 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
380
576
  pid: data.pid,
381
577
  command: data.command,
382
578
  status: data.status,
383
- startTime: typeof data.startTime === 'string' ? new Date(data.startTime) : data.startTime,
384
- endTime: data.endTime ? (typeof data.endTime === 'string' ? new Date(data.endTime) : data.endTime) : undefined,
579
+ startTime:
580
+ typeof data.startTime === 'string'
581
+ ? new Date(data.startTime)
582
+ : data.startTime,
583
+ endTime: data.endTime
584
+ ? typeof data.endTime === 'string'
585
+ ? new Date(data.endTime)
586
+ : data.endTime
587
+ : undefined,
385
588
  exitCode: data.exitCode,
386
589
  sessionId,
387
590
 
@@ -401,25 +604,35 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
401
604
  };
402
605
  }
403
606
 
404
-
405
607
  // Background process management
406
- async startProcess(command: string, options?: ProcessOptions, sessionId?: string): Promise<Process> {
608
+ async startProcess(
609
+ command: string,
610
+ options?: ProcessOptions,
611
+ sessionId?: string
612
+ ): Promise<Process> {
407
613
  // Use the new HttpClient method to start the process
408
614
  try {
409
- const session = sessionId ?? await this.ensureDefaultSession();
410
- const response = await this.client.processes.startProcess(command, session, {
411
- processId: options?.processId
412
- });
413
-
414
- const processObj = this.createProcessFromDTO({
415
- id: response.processId,
416
- pid: response.pid,
417
- command: response.command,
418
- status: 'running' as ProcessStatus,
419
- startTime: new Date(),
420
- endTime: undefined,
421
- exitCode: undefined
422
- }, session);
615
+ const session = sessionId ?? (await this.ensureDefaultSession());
616
+ const response = await this.client.processes.startProcess(
617
+ command,
618
+ session,
619
+ {
620
+ processId: options?.processId
621
+ }
622
+ );
623
+
624
+ const processObj = this.createProcessFromDTO(
625
+ {
626
+ id: response.processId,
627
+ pid: response.pid,
628
+ command: response.command,
629
+ status: 'running' as ProcessStatus,
630
+ startTime: new Date(),
631
+ endTime: undefined,
632
+ exitCode: undefined
633
+ },
634
+ session
635
+ );
423
636
 
424
637
  // Call onStart callback if provided
425
638
  if (options?.onStart) {
@@ -427,7 +640,6 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
427
640
  }
428
641
 
429
642
  return processObj;
430
-
431
643
  } catch (error) {
432
644
  if (options?.onError && error instanceof Error) {
433
645
  options.onError(error);
@@ -438,42 +650,52 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
438
650
  }
439
651
 
440
652
  async listProcesses(sessionId?: string): Promise<Process[]> {
441
- const session = sessionId ?? await this.ensureDefaultSession();
653
+ const session = sessionId ?? (await this.ensureDefaultSession());
442
654
  const response = await this.client.processes.listProcesses();
443
655
 
444
- return response.processes.map(processData =>
445
- this.createProcessFromDTO({
446
- id: processData.id,
447
- pid: processData.pid,
448
- command: processData.command,
449
- status: processData.status,
450
- startTime: processData.startTime,
451
- endTime: processData.endTime,
452
- exitCode: processData.exitCode
453
- }, session)
656
+ return response.processes.map((processData) =>
657
+ this.createProcessFromDTO(
658
+ {
659
+ id: processData.id,
660
+ pid: processData.pid,
661
+ command: processData.command,
662
+ status: processData.status,
663
+ startTime: processData.startTime,
664
+ endTime: processData.endTime,
665
+ exitCode: processData.exitCode
666
+ },
667
+ session
668
+ )
454
669
  );
455
670
  }
456
671
 
457
672
  async getProcess(id: string, sessionId?: string): Promise<Process | null> {
458
- const session = sessionId ?? await this.ensureDefaultSession();
673
+ const session = sessionId ?? (await this.ensureDefaultSession());
459
674
  const response = await this.client.processes.getProcess(id);
460
675
  if (!response.process) {
461
676
  return null;
462
677
  }
463
678
 
464
679
  const processData = response.process;
465
- return this.createProcessFromDTO({
466
- id: processData.id,
467
- pid: processData.pid,
468
- command: processData.command,
469
- status: processData.status,
470
- startTime: processData.startTime,
471
- endTime: processData.endTime,
472
- exitCode: processData.exitCode
473
- }, session);
474
- }
475
-
476
- async killProcess(id: string, signal?: string, sessionId?: string): Promise<void> {
680
+ return this.createProcessFromDTO(
681
+ {
682
+ id: processData.id,
683
+ pid: processData.pid,
684
+ command: processData.command,
685
+ status: processData.status,
686
+ startTime: processData.startTime,
687
+ endTime: processData.endTime,
688
+ exitCode: processData.exitCode
689
+ },
690
+ session
691
+ );
692
+ }
693
+
694
+ async killProcess(
695
+ id: string,
696
+ signal?: string,
697
+ sessionId?: string
698
+ ): Promise<void> {
477
699
  // Note: signal parameter is not currently supported by the HttpClient implementation
478
700
  // The HTTP client already throws properly typed errors, so we just let them propagate
479
701
  await this.client.processes.killProcess(id);
@@ -491,7 +713,10 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
491
713
  return 0;
492
714
  }
493
715
 
494
- async getProcessLogs(id: string, sessionId?: string): Promise<{ stdout: string; stderr: string; processId: string }> {
716
+ async getProcessLogs(
717
+ id: string,
718
+ sessionId?: string
719
+ ): Promise<{ stdout: string; stderr: string; processId: string }> {
495
720
  // The HTTP client already throws properly typed errors, so we just let them propagate
496
721
  const response = await this.client.processes.getProcessLogs(id);
497
722
  return {
@@ -501,9 +726,11 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
501
726
  };
502
727
  }
503
728
 
504
-
505
729
  // Streaming methods - return ReadableStream for RPC compatibility
506
- async execStream(command: string, options?: StreamOptions): Promise<ReadableStream<Uint8Array>> {
730
+ async execStream(
731
+ command: string,
732
+ options?: StreamOptions
733
+ ): Promise<ReadableStream<Uint8Array>> {
507
734
  // Check for cancellation
508
735
  if (options?.signal?.aborted) {
509
736
  throw new Error('Operation was aborted');
@@ -517,7 +744,11 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
517
744
  /**
518
745
  * Internal session-aware execStream implementation
519
746
  */
520
- private async execStreamWithSession(command: string, sessionId: string, options?: StreamOptions): Promise<ReadableStream<Uint8Array>> {
747
+ private async execStreamWithSession(
748
+ command: string,
749
+ sessionId: string,
750
+ options?: StreamOptions
751
+ ): Promise<ReadableStream<Uint8Array>> {
521
752
  // Check for cancellation
522
753
  if (options?.signal?.aborted) {
523
754
  throw new Error('Operation was aborted');
@@ -526,7 +757,13 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
526
757
  return this.client.commands.executeStream(command, sessionId);
527
758
  }
528
759
 
529
- async streamProcessLogs(processId: string, options?: { signal?: AbortSignal }): Promise<ReadableStream<Uint8Array>> {
760
+ /**
761
+ * Stream logs from a background process as a ReadableStream.
762
+ */
763
+ async streamProcessLogs(
764
+ processId: string,
765
+ options?: { signal?: AbortSignal }
766
+ ): Promise<ReadableStream<Uint8Array>> {
530
767
  // Check for cancellation
531
768
  if (options?.signal?.aborted) {
532
769
  throw new Error('Operation was aborted');
@@ -539,7 +776,7 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
539
776
  repoUrl: string,
540
777
  options: { branch?: string; targetDir?: string; sessionId?: string }
541
778
  ) {
542
- const session = options.sessionId ?? await this.ensureDefaultSession();
779
+ const session = options.sessionId ?? (await this.ensureDefaultSession());
543
780
  return this.client.git.checkout(repoUrl, session, {
544
781
  branch: options.branch,
545
782
  targetDir: options.targetDir
@@ -550,8 +787,10 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
550
787
  path: string,
551
788
  options: { recursive?: boolean; sessionId?: string } = {}
552
789
  ) {
553
- const session = options.sessionId ?? await this.ensureDefaultSession();
554
- return this.client.files.mkdir(path, session, { recursive: options.recursive });
790
+ const session = options.sessionId ?? (await this.ensureDefaultSession());
791
+ return this.client.files.mkdir(path, session, {
792
+ recursive: options.recursive
793
+ });
555
794
  }
556
795
 
557
796
  async writeFile(
@@ -559,21 +798,19 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
559
798
  content: string,
560
799
  options: { encoding?: string; sessionId?: string } = {}
561
800
  ) {
562
- const session = options.sessionId ?? await this.ensureDefaultSession();
563
- return this.client.files.writeFile(path, content, session, { encoding: options.encoding });
801
+ const session = options.sessionId ?? (await this.ensureDefaultSession());
802
+ return this.client.files.writeFile(path, content, session, {
803
+ encoding: options.encoding
804
+ });
564
805
  }
565
806
 
566
807
  async deleteFile(path: string, sessionId?: string) {
567
- const session = sessionId ?? await this.ensureDefaultSession();
808
+ const session = sessionId ?? (await this.ensureDefaultSession());
568
809
  return this.client.files.deleteFile(path, session);
569
810
  }
570
811
 
571
- async renameFile(
572
- oldPath: string,
573
- newPath: string,
574
- sessionId?: string
575
- ) {
576
- const session = sessionId ?? await this.ensureDefaultSession();
812
+ async renameFile(oldPath: string, newPath: string, sessionId?: string) {
813
+ const session = sessionId ?? (await this.ensureDefaultSession());
577
814
  return this.client.files.renameFile(oldPath, newPath, session);
578
815
  }
579
816
 
@@ -582,7 +819,7 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
582
819
  destinationPath: string,
583
820
  sessionId?: string
584
821
  ) {
585
- const session = sessionId ?? await this.ensureDefaultSession();
822
+ const session = sessionId ?? (await this.ensureDefaultSession());
586
823
  return this.client.files.moveFile(sourcePath, destinationPath, session);
587
824
  }
588
825
 
@@ -590,8 +827,10 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
590
827
  path: string,
591
828
  options: { encoding?: string; sessionId?: string } = {}
592
829
  ) {
593
- const session = options.sessionId ?? await this.ensureDefaultSession();
594
- return this.client.files.readFile(path, session, { encoding: options.encoding });
830
+ const session = options.sessionId ?? (await this.ensureDefaultSession());
831
+ return this.client.files.readFile(path, session, {
832
+ encoding: options.encoding
833
+ });
595
834
  }
596
835
 
597
836
  /**
@@ -604,7 +843,7 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
604
843
  path: string,
605
844
  options: { sessionId?: string } = {}
606
845
  ): Promise<ReadableStream<Uint8Array>> {
607
- const session = options.sessionId ?? await this.ensureDefaultSession();
846
+ const session = options.sessionId ?? (await this.ensureDefaultSession());
608
847
  return this.client.files.readFileStream(path, session);
609
848
  }
610
849
 
@@ -616,6 +855,11 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
616
855
  return this.client.files.listFiles(path, session, options);
617
856
  }
618
857
 
858
+ async exists(path: string, sessionId?: string) {
859
+ const session = sessionId ?? (await this.ensureDefaultSession());
860
+ return this.client.files.exists(path, session);
861
+ }
862
+
619
863
  async exposePort(port: number, options: { name?: string; hostname: string }) {
620
864
  // Check if hostname is workers.dev domain (doesn't support wildcard subdomains)
621
865
  if (options.hostname.endsWith('.workers.dev')) {
@@ -634,7 +878,9 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
634
878
 
635
879
  // We need the sandbox name to construct preview URLs
636
880
  if (!this.sandboxName) {
637
- throw new Error('Sandbox name not available. Ensure sandbox is accessed through getSandbox()');
881
+ throw new Error(
882
+ 'Sandbox name not available. Ensure sandbox is accessed through getSandbox()'
883
+ );
638
884
  }
639
885
 
640
886
  // Generate and store token for this port
@@ -642,18 +888,25 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
642
888
  this.portTokens.set(port, token);
643
889
  await this.persistPortTokens();
644
890
 
645
- const url = this.constructPreviewUrl(port, this.sandboxName, options.hostname, token);
891
+ const url = this.constructPreviewUrl(
892
+ port,
893
+ this.sandboxName,
894
+ options.hostname,
895
+ token
896
+ );
646
897
 
647
898
  return {
648
899
  url,
649
900
  port,
650
- name: options?.name,
901
+ name: options?.name
651
902
  };
652
903
  }
653
904
 
654
905
  async unexposePort(port: number) {
655
906
  if (!validatePort(port)) {
656
- throw new SecurityError(`Invalid port number: ${port}. Must be between 1024-65535 and not reserved.`);
907
+ throw new SecurityError(
908
+ `Invalid port number: ${port}. Must be between 1024-65535 and not reserved.`
909
+ );
657
910
  }
658
911
 
659
912
  const sessionId = await this.ensureDefaultSession();
@@ -672,32 +925,44 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
672
925
 
673
926
  // We need the sandbox name to construct preview URLs
674
927
  if (!this.sandboxName) {
675
- throw new Error('Sandbox name not available. Ensure sandbox is accessed through getSandbox()');
928
+ throw new Error(
929
+ 'Sandbox name not available. Ensure sandbox is accessed through getSandbox()'
930
+ );
676
931
  }
677
932
 
678
- return response.ports.map(port => {
933
+ return response.ports.map((port) => {
679
934
  // Get token for this port - must exist for all exposed ports
680
935
  const token = this.portTokens.get(port.port);
681
936
  if (!token) {
682
- throw new Error(`Port ${port.port} is exposed but has no token. This should not happen.`);
937
+ throw new Error(
938
+ `Port ${port.port} is exposed but has no token. This should not happen.`
939
+ );
683
940
  }
684
941
 
685
942
  return {
686
- url: this.constructPreviewUrl(port.port, this.sandboxName!, hostname, token),
943
+ url: this.constructPreviewUrl(
944
+ port.port,
945
+ this.sandboxName!,
946
+ hostname,
947
+ token
948
+ ),
687
949
  port: port.port,
688
- status: port.status,
950
+ status: port.status
689
951
  };
690
952
  });
691
953
  }
692
954
 
693
-
694
955
  async isPortExposed(port: number): Promise<boolean> {
695
956
  try {
696
957
  const sessionId = await this.ensureDefaultSession();
697
958
  const response = await this.client.ports.getExposedPorts(sessionId);
698
- return response.ports.some(exposedPort => exposedPort.port === port);
959
+ return response.ports.some((exposedPort) => exposedPort.port === port);
699
960
  } catch (error) {
700
- this.logger.error('Error checking if port is exposed', error instanceof Error ? error : new Error(String(error)), { port });
961
+ this.logger.error(
962
+ 'Error checking if port is exposed',
963
+ error instanceof Error ? error : new Error(String(error)),
964
+ { port }
965
+ );
701
966
  return false;
702
967
  }
703
968
  }
@@ -713,7 +978,11 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
713
978
  const storedToken = this.portTokens.get(port);
714
979
  if (!storedToken) {
715
980
  // This should not happen - all exposed ports must have tokens
716
- this.logger.error('Port is exposed but has no token - bug detected', undefined, { port });
981
+ this.logger.error(
982
+ 'Port is exposed but has no token - bug detected',
983
+ undefined,
984
+ { port }
985
+ );
717
986
  return false;
718
987
  }
719
988
 
@@ -729,7 +998,11 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
729
998
 
730
999
  // Convert to base64url format (URL-safe, no padding, lowercase)
731
1000
  const base64 = btoa(String.fromCharCode(...array));
732
- return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '').toLowerCase();
1001
+ return base64
1002
+ .replace(/\+/g, '-')
1003
+ .replace(/\//g, '_')
1004
+ .replace(/=/g, '')
1005
+ .toLowerCase();
733
1006
  }
734
1007
 
735
1008
  private async persistPortTokens(): Promise<void> {
@@ -741,9 +1014,16 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
741
1014
  await this.ctx.storage.put('portTokens', tokensObj);
742
1015
  }
743
1016
 
744
- private constructPreviewUrl(port: number, sandboxId: string, hostname: string, token: string): string {
1017
+ private constructPreviewUrl(
1018
+ port: number,
1019
+ sandboxId: string,
1020
+ hostname: string,
1021
+ token: string
1022
+ ): string {
745
1023
  if (!validatePort(port)) {
746
- throw new SecurityError(`Invalid port number: ${port}. Must be between 1024-65535 and not reserved.`);
1024
+ throw new SecurityError(
1025
+ `Invalid port number: ${port}. Must be between 1024-65535 and not reserved.`
1026
+ );
747
1027
  }
748
1028
 
749
1029
  // Validate sandbox ID (will throw SecurityError if invalid)
@@ -765,14 +1045,18 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
765
1045
 
766
1046
  return baseUrl.toString();
767
1047
  } catch (error) {
768
- throw new SecurityError(`Failed to construct preview URL: ${error instanceof Error ? error.message : 'Unknown error'}`);
1048
+ throw new SecurityError(
1049
+ `Failed to construct preview URL: ${
1050
+ error instanceof Error ? error.message : 'Unknown error'
1051
+ }`
1052
+ );
769
1053
  }
770
1054
  }
771
1055
 
772
1056
  // Production subdomain logic - enforce HTTPS
773
1057
  try {
774
1058
  // Always use HTTPS for production (non-localhost)
775
- const protocol = "https";
1059
+ const protocol = 'https';
776
1060
  const baseUrl = new URL(`${protocol}://${hostname}`);
777
1061
 
778
1062
  // Construct subdomain safely with mandatory token
@@ -781,7 +1065,11 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
781
1065
 
782
1066
  return baseUrl.toString();
783
1067
  } catch (error) {
784
- throw new SecurityError(`Failed to construct preview URL: ${error instanceof Error ? error.message : 'Unknown error'}`);
1068
+ throw new SecurityError(
1069
+ `Failed to construct preview URL: ${
1070
+ error instanceof Error ? error.message : 'Unknown error'
1071
+ }`
1072
+ );
785
1073
  }
786
1074
  }
787
1075
 
@@ -800,7 +1088,7 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
800
1088
  await this.client.utils.createSession({
801
1089
  id: sessionId,
802
1090
  env: options?.env,
803
- cwd: options?.cwd,
1091
+ cwd: options?.cwd
804
1092
  });
805
1093
 
806
1094
  // Return wrapper that binds sessionId to all operations
@@ -831,31 +1119,42 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
831
1119
  id: sessionId,
832
1120
 
833
1121
  // Command execution - delegate to internal session-aware methods
834
- exec: (command, options) => this.execWithSession(command, sessionId, options),
835
- execStream: (command, options) => this.execStreamWithSession(command, sessionId, options),
1122
+ exec: (command, options) =>
1123
+ this.execWithSession(command, sessionId, options),
1124
+ execStream: (command, options) =>
1125
+ this.execStreamWithSession(command, sessionId, options),
836
1126
 
837
1127
  // Process management
838
- startProcess: (command, options) => this.startProcess(command, options, sessionId),
1128
+ startProcess: (command, options) =>
1129
+ this.startProcess(command, options, sessionId),
839
1130
  listProcesses: () => this.listProcesses(sessionId),
840
1131
  getProcess: (id) => this.getProcess(id, sessionId),
841
1132
  killProcess: (id, signal) => this.killProcess(id, signal),
842
1133
  killAllProcesses: () => this.killAllProcesses(),
843
1134
  cleanupCompletedProcesses: () => this.cleanupCompletedProcesses(),
844
1135
  getProcessLogs: (id) => this.getProcessLogs(id),
845
- streamProcessLogs: (processId, options) => this.streamProcessLogs(processId, options),
1136
+ streamProcessLogs: (processId, options) =>
1137
+ this.streamProcessLogs(processId, options),
846
1138
 
847
1139
  // File operations - pass sessionId via options or parameter
848
- writeFile: (path, content, options) => this.writeFile(path, content, { ...options, sessionId }),
849
- readFile: (path, options) => this.readFile(path, { ...options, sessionId }),
1140
+ writeFile: (path, content, options) =>
1141
+ this.writeFile(path, content, { ...options, sessionId }),
1142
+ readFile: (path, options) =>
1143
+ this.readFile(path, { ...options, sessionId }),
850
1144
  readFileStream: (path) => this.readFileStream(path, { sessionId }),
851
1145
  mkdir: (path, options) => this.mkdir(path, { ...options, sessionId }),
852
1146
  deleteFile: (path) => this.deleteFile(path, sessionId),
853
- renameFile: (oldPath, newPath) => this.renameFile(oldPath, newPath, sessionId),
854
- moveFile: (sourcePath, destPath) => this.moveFile(sourcePath, destPath, sessionId),
855
- listFiles: (path, options) => this.client.files.listFiles(path, sessionId, options),
1147
+ renameFile: (oldPath, newPath) =>
1148
+ this.renameFile(oldPath, newPath, sessionId),
1149
+ moveFile: (sourcePath, destPath) =>
1150
+ this.moveFile(sourcePath, destPath, sessionId),
1151
+ listFiles: (path, options) =>
1152
+ this.client.files.listFiles(path, sessionId, options),
1153
+ exists: (path) => this.exists(path, sessionId),
856
1154
 
857
1155
  // Git operations
858
- gitCheckout: (repoUrl, options) => this.gitCheckout(repoUrl, { ...options, sessionId }),
1156
+ gitCheckout: (repoUrl, options) =>
1157
+ this.gitCheckout(repoUrl, { ...options, sessionId }),
859
1158
 
860
1159
  // Environment management - needs special handling
861
1160
  setEnvVars: async (envVars: Record<string, string>) => {
@@ -865,27 +1164,39 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
865
1164
  const escapedValue = value.replace(/'/g, "'\\''");
866
1165
  const exportCommand = `export ${key}='${escapedValue}'`;
867
1166
 
868
- const result = await this.client.commands.execute(exportCommand, sessionId);
1167
+ const result = await this.client.commands.execute(
1168
+ exportCommand,
1169
+ sessionId
1170
+ );
869
1171
 
870
1172
  if (result.exitCode !== 0) {
871
- throw new Error(`Failed to set ${key}: ${result.stderr || 'Unknown error'}`);
1173
+ throw new Error(
1174
+ `Failed to set ${key}: ${result.stderr || 'Unknown error'}`
1175
+ );
872
1176
  }
873
1177
  }
874
1178
  } catch (error) {
875
- this.logger.error('Failed to set environment variables', error instanceof Error ? error : new Error(String(error)), { sessionId });
1179
+ this.logger.error(
1180
+ 'Failed to set environment variables',
1181
+ error instanceof Error ? error : new Error(String(error)),
1182
+ { sessionId }
1183
+ );
876
1184
  throw error;
877
1185
  }
878
1186
  },
879
1187
 
880
1188
  // Code interpreter methods - delegate to sandbox's code interpreter
881
- createCodeContext: (options) => this.codeInterpreter.createCodeContext(options),
1189
+ createCodeContext: (options) =>
1190
+ this.codeInterpreter.createCodeContext(options),
882
1191
  runCode: async (code, options) => {
883
1192
  const execution = await this.codeInterpreter.runCode(code, options);
884
1193
  return execution.toJSON();
885
1194
  },
886
- runCodeStream: (code, options) => this.codeInterpreter.runCodeStream(code, options),
1195
+ runCodeStream: (code, options) =>
1196
+ this.codeInterpreter.runCodeStream(code, options),
887
1197
  listCodeContexts: () => this.codeInterpreter.listCodeContexts(),
888
- deleteCodeContext: (contextId) => this.codeInterpreter.deleteCodeContext(contextId),
1198
+ deleteCodeContext: (contextId) =>
1199
+ this.codeInterpreter.deleteCodeContext(contextId)
889
1200
  };
890
1201
  }
891
1202
 
@@ -893,16 +1204,24 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
893
1204
  // Code interpreter methods - delegate to CodeInterpreter wrapper
894
1205
  // ============================================================================
895
1206
 
896
- async createCodeContext(options?: CreateContextOptions): Promise<CodeContext> {
1207
+ async createCodeContext(
1208
+ options?: CreateContextOptions
1209
+ ): Promise<CodeContext> {
897
1210
  return this.codeInterpreter.createCodeContext(options);
898
1211
  }
899
1212
 
900
- async runCode(code: string, options?: RunCodeOptions): Promise<ExecutionResult> {
1213
+ async runCode(
1214
+ code: string,
1215
+ options?: RunCodeOptions
1216
+ ): Promise<ExecutionResult> {
901
1217
  const execution = await this.codeInterpreter.runCode(code, options);
902
1218
  return execution.toJSON();
903
1219
  }
904
1220
 
905
- async runCodeStream(code: string, options?: RunCodeOptions): Promise<ReadableStream> {
1221
+ async runCodeStream(
1222
+ code: string,
1223
+ options?: RunCodeOptions
1224
+ ): Promise<ReadableStream> {
906
1225
  return this.codeInterpreter.runCodeStream(code, options);
907
1226
  }
908
1227