@cloudflare/sandbox 0.0.0-fddccfd → 0.0.0-ff2fa91

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 +102 -15
  2. package/Dockerfile +84 -31
  3. package/README.md +9 -2
  4. package/dist/index.d.ts +1889 -9
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/index.js +3144 -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 +3 -4
  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 +443 -144
  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 +188 -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-2P3MDMNJ.js +0 -2367
  47. package/dist/chunk-2P3MDMNJ.js.map +0 -1
  48. package/dist/chunk-BFVUNTP4.js +0 -104
  49. package/dist/chunk-BFVUNTP4.js.map +0 -1
  50. package/dist/chunk-EKSWCBCA.js +0 -86
  51. package/dist/chunk-EKSWCBCA.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-CZTMzV2R.d.ts +0 -587
  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
@@ -6,16 +6,20 @@ import {
6
6
  type ExecutionError,
7
7
  type OutputMessage,
8
8
  type Result,
9
- ResultImpl,
9
+ ResultImpl
10
10
  } from '@repo/shared';
11
11
  import type { ErrorResponse } from '../errors';
12
- import { createErrorFromResponse, ErrorCode, InterpreterNotReadyError } from '../errors';
12
+ import {
13
+ createErrorFromResponse,
14
+ ErrorCode,
15
+ InterpreterNotReadyError
16
+ } from '../errors';
13
17
  import { BaseHttpClient } from './base-client.js';
14
18
  import type { HttpClientOptions } from './types.js';
15
19
 
16
20
  // Streaming execution data from the server
17
21
  interface StreamingExecutionData {
18
- type: "result" | "stdout" | "stderr" | "error" | "execution_complete";
22
+ type: 'result' | 'stdout' | 'stderr' | 'error' | 'execution_complete';
19
23
  text?: string;
20
24
  html?: string;
21
25
  png?: string; // base64
@@ -27,13 +31,13 @@ interface StreamingExecutionData {
27
31
  json?: unknown;
28
32
  chart?: {
29
33
  type:
30
- | "line"
31
- | "bar"
32
- | "scatter"
33
- | "pie"
34
- | "histogram"
35
- | "heatmap"
36
- | "unknown";
34
+ | 'line'
35
+ | 'bar'
36
+ | 'scatter'
37
+ | 'pie'
38
+ | 'histogram'
39
+ | 'heatmap'
40
+ | 'unknown';
37
41
  data: unknown;
38
42
  options?: unknown;
39
43
  };
@@ -62,14 +66,14 @@ export class InterpreterClient extends BaseHttpClient {
62
66
  options: CreateContextOptions = {}
63
67
  ): Promise<CodeContext> {
64
68
  return this.executeWithRetry(async () => {
65
- const response = await this.doFetch("/api/contexts", {
66
- method: "POST",
67
- headers: { "Content-Type": "application/json" },
69
+ const response = await this.doFetch('/api/contexts', {
70
+ method: 'POST',
71
+ headers: { 'Content-Type': 'application/json' },
68
72
  body: JSON.stringify({
69
- language: options.language || "python",
70
- cwd: options.cwd || "/workspace",
71
- env_vars: options.envVars,
72
- }),
73
+ language: options.language || 'python',
74
+ cwd: options.cwd || '/workspace',
75
+ env_vars: options.envVars
76
+ })
73
77
  });
74
78
 
75
79
  if (!response.ok) {
@@ -87,7 +91,7 @@ export class InterpreterClient extends BaseHttpClient {
87
91
  language: data.language,
88
92
  cwd: data.cwd || '/workspace',
89
93
  createdAt: new Date(data.timestamp),
90
- lastUsed: new Date(data.timestamp),
94
+ lastUsed: new Date(data.timestamp)
91
95
  };
92
96
  });
93
97
  }
@@ -100,18 +104,18 @@ export class InterpreterClient extends BaseHttpClient {
100
104
  timeoutMs?: number
101
105
  ): Promise<void> {
102
106
  return this.executeWithRetry(async () => {
103
- const response = await this.doFetch("/api/execute/code", {
104
- method: "POST",
107
+ const response = await this.doFetch('/api/execute/code', {
108
+ method: 'POST',
105
109
  headers: {
106
- "Content-Type": "application/json",
107
- Accept: "text/event-stream",
110
+ 'Content-Type': 'application/json',
111
+ Accept: 'text/event-stream'
108
112
  },
109
113
  body: JSON.stringify({
110
114
  context_id: contextId,
111
115
  code,
112
116
  language,
113
117
  ...(timeoutMs !== undefined && { timeout_ms: timeoutMs })
114
- }),
118
+ })
115
119
  });
116
120
 
117
121
  if (!response.ok) {
@@ -120,7 +124,7 @@ export class InterpreterClient extends BaseHttpClient {
120
124
  }
121
125
 
122
126
  if (!response.body) {
123
- throw new Error("No response body for streaming execution");
127
+ throw new Error('No response body for streaming execution');
124
128
  }
125
129
 
126
130
  // Process streaming response
@@ -132,9 +136,9 @@ export class InterpreterClient extends BaseHttpClient {
132
136
 
133
137
  async listCodeContexts(): Promise<CodeContext[]> {
134
138
  return this.executeWithRetry(async () => {
135
- const response = await this.doFetch("/api/contexts", {
136
- method: "GET",
137
- headers: { "Content-Type": "application/json" },
139
+ const response = await this.doFetch('/api/contexts', {
140
+ method: 'GET',
141
+ headers: { 'Content-Type': 'application/json' }
138
142
  });
139
143
 
140
144
  if (!response.ok) {
@@ -152,7 +156,7 @@ export class InterpreterClient extends BaseHttpClient {
152
156
  language: ctx.language,
153
157
  cwd: ctx.cwd || '/workspace',
154
158
  createdAt: new Date(data.timestamp),
155
- lastUsed: new Date(data.timestamp),
159
+ lastUsed: new Date(data.timestamp)
156
160
  }));
157
161
  });
158
162
  }
@@ -160,8 +164,8 @@ export class InterpreterClient extends BaseHttpClient {
160
164
  async deleteCodeContext(contextId: string): Promise<void> {
161
165
  return this.executeWithRetry(async () => {
162
166
  const response = await this.doFetch(`/api/contexts/${contextId}`, {
163
- method: "DELETE",
164
- headers: { "Content-Type": "application/json" },
167
+ method: 'DELETE',
168
+ headers: { 'Content-Type': 'application/json' }
165
169
  });
166
170
 
167
171
  if (!response.ok) {
@@ -201,7 +205,7 @@ export class InterpreterClient extends BaseHttpClient {
201
205
  }
202
206
  }
203
207
 
204
- throw lastError || new Error("Execution failed after retries");
208
+ throw lastError || new Error('Execution failed after retries');
205
209
  }
206
210
 
207
211
  private isRetryableError(error: unknown): boolean {
@@ -211,8 +215,8 @@ export class InterpreterClient extends BaseHttpClient {
211
215
 
212
216
  if (error instanceof Error) {
213
217
  return (
214
- error.message.includes("not ready") ||
215
- error.message.includes("initializing")
218
+ error.message.includes('not ready') ||
219
+ error.message.includes('initializing')
216
220
  );
217
221
  }
218
222
 
@@ -221,7 +225,7 @@ export class InterpreterClient extends BaseHttpClient {
221
225
 
222
226
  private async parseErrorResponse(response: Response): Promise<Error> {
223
227
  try {
224
- const errorData = await response.json() as ErrorResponse;
228
+ const errorData = (await response.json()) as ErrorResponse;
225
229
  return createErrorFromResponse(errorData);
226
230
  } catch {
227
231
  // Fallback if response isn't JSON
@@ -240,7 +244,7 @@ export class InterpreterClient extends BaseHttpClient {
240
244
  stream: ReadableStream<Uint8Array>
241
245
  ): AsyncGenerator<string> {
242
246
  const reader = stream.getReader();
243
- let buffer = "";
247
+ let buffer = '';
244
248
 
245
249
  try {
246
250
  while (true) {
@@ -250,11 +254,11 @@ export class InterpreterClient extends BaseHttpClient {
250
254
  }
251
255
  if (done) break;
252
256
 
253
- let newlineIdx = buffer.indexOf("\n");
257
+ let newlineIdx = buffer.indexOf('\n');
254
258
  while (newlineIdx !== -1) {
255
259
  yield buffer.slice(0, newlineIdx);
256
260
  buffer = buffer.slice(newlineIdx + 1);
257
- newlineIdx = buffer.indexOf("\n");
261
+ newlineIdx = buffer.indexOf('\n');
258
262
  }
259
263
  }
260
264
 
@@ -282,25 +286,25 @@ export class InterpreterClient extends BaseHttpClient {
282
286
  const data = JSON.parse(jsonData) as StreamingExecutionData;
283
287
 
284
288
  switch (data.type) {
285
- case "stdout":
289
+ case 'stdout':
286
290
  if (callbacks.onStdout && data.text) {
287
291
  await callbacks.onStdout({
288
292
  text: data.text,
289
- timestamp: data.timestamp || Date.now(),
293
+ timestamp: data.timestamp || Date.now()
290
294
  });
291
295
  }
292
296
  break;
293
297
 
294
- case "stderr":
298
+ case 'stderr':
295
299
  if (callbacks.onStderr && data.text) {
296
300
  await callbacks.onStderr({
297
301
  text: data.text,
298
- timestamp: data.timestamp || Date.now(),
302
+ timestamp: data.timestamp || Date.now()
299
303
  });
300
304
  }
301
305
  break;
302
306
 
303
- case "result":
307
+ case 'result':
304
308
  if (callbacks.onResult) {
305
309
  // Create a ResultImpl instance from the raw data
306
310
  const result = new ResultImpl(data);
@@ -308,17 +312,17 @@ export class InterpreterClient extends BaseHttpClient {
308
312
  }
309
313
  break;
310
314
 
311
- case "error":
315
+ case 'error':
312
316
  if (callbacks.onError) {
313
317
  await callbacks.onError({
314
- name: data.ename || "Error",
315
- message: data.evalue || "Unknown error",
316
- traceback: data.traceback || [],
318
+ name: data.ename || 'Error',
319
+ message: data.evalue || 'Unknown error',
320
+ traceback: data.traceback || []
317
321
  });
318
322
  }
319
323
  break;
320
324
 
321
- case "execution_complete":
325
+ case 'execution_complete':
322
326
  // Signal completion - callbacks can handle cleanup if needed
323
327
  break;
324
328
  }
@@ -1,17 +1,13 @@
1
1
  import type {
2
2
  PortCloseResult,
3
3
  PortExposeResult,
4
- PortListResult,
4
+ PortListResult
5
5
  } from '@repo/shared';
6
6
  import { BaseHttpClient } from './base-client';
7
7
  import type { HttpClientOptions } from './types';
8
8
 
9
9
  // Re-export for convenience
10
- export type {
11
- PortExposeResult,
12
- PortCloseResult,
13
- PortListResult,
14
- };
10
+ export type { PortExposeResult, PortCloseResult, PortListResult };
15
11
 
16
12
  /**
17
13
  * Request interface for exposing ports
@@ -32,7 +28,6 @@ export interface UnexposePortRequest {
32
28
  * Client for port management and preview URL operations
33
29
  */
34
30
  export class PortClient extends BaseHttpClient {
35
-
36
31
  /**
37
32
  * Expose a port and get a preview URL
38
33
  * @param port - Port number to expose
@@ -69,9 +64,14 @@ export class PortClient extends BaseHttpClient {
69
64
  * @param port - Port number to unexpose
70
65
  * @param sessionId - The session ID for this operation
71
66
  */
72
- async unexposePort(port: number, sessionId: string): Promise<PortCloseResult> {
67
+ async unexposePort(
68
+ port: number,
69
+ sessionId: string
70
+ ): Promise<PortCloseResult> {
73
71
  try {
74
- const url = `/api/exposed-ports/${port}?session=${encodeURIComponent(sessionId)}`;
72
+ const url = `/api/exposed-ports/${port}?session=${encodeURIComponent(
73
+ sessionId
74
+ )}`;
75
75
  const response = await this.delete<PortCloseResult>(url);
76
76
 
77
77
  this.logSuccess('Port unexposed', `${port}`);
@@ -102,4 +102,4 @@ export class PortClient extends BaseHttpClient {
102
102
  throw error;
103
103
  }
104
104
  }
105
- }
105
+ }
@@ -5,7 +5,7 @@ import type {
5
5
  ProcessListResult,
6
6
  ProcessLogsResult,
7
7
  ProcessStartResult,
8
- StartProcessRequest,
8
+ StartProcessRequest
9
9
  } from '@repo/shared';
10
10
  import { BaseHttpClient } from './base-client';
11
11
  import type { HttpClientOptions } from './types';
@@ -18,15 +18,13 @@ export type {
18
18
  ProcessInfoResult,
19
19
  ProcessKillResult,
20
20
  ProcessLogsResult,
21
- ProcessCleanupResult,
21
+ ProcessCleanupResult
22
22
  };
23
23
 
24
-
25
24
  /**
26
25
  * Client for background process management
27
26
  */
28
27
  export class ProcessClient extends BaseHttpClient {
29
-
30
28
  /**
31
29
  * Start a background process
32
30
  * @param command - Command to execute as a background process
@@ -42,7 +40,7 @@ export class ProcessClient extends BaseHttpClient {
42
40
  const data: StartProcessRequest = {
43
41
  command,
44
42
  sessionId,
45
- processId: options?.processId,
43
+ processId: options?.processId
46
44
  };
47
45
 
48
46
  const response = await this.post<ProcessStartResult>(
@@ -70,7 +68,10 @@ export class ProcessClient extends BaseHttpClient {
70
68
  const url = `/api/process/list`;
71
69
  const response = await this.get<ProcessListResult>(url);
72
70
 
73
- this.logSuccess('Processes listed', `${response.processes.length} processes`);
71
+ this.logSuccess(
72
+ 'Processes listed',
73
+ `${response.processes.length} processes`
74
+ );
74
75
  return response;
75
76
  } catch (error) {
76
77
  this.logError('listProcesses', error);
@@ -157,11 +158,13 @@ export class ProcessClient extends BaseHttpClient {
157
158
  * Stream logs from a specific process (sandbox-scoped, not session-scoped)
158
159
  * @param processId - ID of the process to stream logs from
159
160
  */
160
- async streamProcessLogs(processId: string): Promise<ReadableStream<Uint8Array>> {
161
+ async streamProcessLogs(
162
+ processId: string
163
+ ): Promise<ReadableStream<Uint8Array>> {
161
164
  try {
162
165
  const url = `/api/process/${processId}/stream`;
163
166
  const response = await this.doFetch(url, {
164
- method: 'GET',
167
+ method: 'GET'
165
168
  });
166
169
 
167
170
  const stream = await this.handleStreamResponse(response);
@@ -24,7 +24,7 @@ export class SandboxClient {
24
24
  // Ensure baseUrl is provided for all clients
25
25
  const clientOptions: HttpClientOptions = {
26
26
  baseUrl: 'http://localhost:3000',
27
- ...options,
27
+ ...options
28
28
  };
29
29
 
30
30
  // Initialize all domain clients with shared options
@@ -36,6 +36,4 @@ export class SandboxClient {
36
36
  this.interpreter = new InterpreterClient(clientOptions);
37
37
  this.utils = new UtilityClient(clientOptions);
38
38
  }
39
-
40
-
41
- }
39
+ }
@@ -4,7 +4,11 @@ import type { Logger } from '@repo/shared';
4
4
  * Minimal interface for container fetch functionality
5
5
  */
6
6
  export interface ContainerStub {
7
- containerFetch(url: string, options: RequestInit, port?: number): Promise<Response>;
7
+ containerFetch(
8
+ url: string,
9
+ options: RequestInit,
10
+ port?: number
11
+ ): Promise<Response>;
8
12
  }
9
13
 
10
14
  /**
@@ -81,4 +85,4 @@ export type ResponseHandler<T> = (response: Response) => Promise<T>;
81
85
  */
82
86
  export interface SessionRequest {
83
87
  sessionId?: string;
84
- }
88
+ }
@@ -17,6 +17,13 @@ export interface CommandsResponse extends BaseApiResponse {
17
17
  count: number;
18
18
  }
19
19
 
20
+ /**
21
+ * Response interface for getting container version
22
+ */
23
+ export interface VersionResponse extends BaseApiResponse {
24
+ version: string;
25
+ }
26
+
20
27
  /**
21
28
  * Request interface for creating sessions
22
29
  */
@@ -38,14 +45,13 @@ export interface CreateSessionResponse extends BaseApiResponse {
38
45
  * Client for health checks and utility operations
39
46
  */
40
47
  export class UtilityClient extends BaseHttpClient {
41
-
42
48
  /**
43
49
  * Ping the sandbox to check if it's responsive
44
50
  */
45
51
  async ping(): Promise<string> {
46
52
  try {
47
53
  const response = await this.get<PingResponse>('/api/ping');
48
-
54
+
49
55
  this.logSuccess('Ping successful', response.message);
50
56
  return response.message;
51
57
  } catch (error) {
@@ -60,7 +66,7 @@ export class UtilityClient extends BaseHttpClient {
60
66
  async getCommands(): Promise<string[]> {
61
67
  try {
62
68
  const response = await this.get<CommandsResponse>('/api/commands');
63
-
69
+
64
70
  this.logSuccess(
65
71
  'Commands retrieved',
66
72
  `${response.count} commands available`
@@ -77,7 +83,9 @@ export class UtilityClient extends BaseHttpClient {
77
83
  * Create a new execution session
78
84
  * @param options - Session configuration (id, env, cwd)
79
85
  */
80
- async createSession(options: CreateSessionRequest): Promise<CreateSessionResponse> {
86
+ async createSession(
87
+ options: CreateSessionRequest
88
+ ): Promise<CreateSessionResponse> {
81
89
  try {
82
90
  const response = await this.post<CreateSessionResponse>(
83
91
  '/api/session/create',
@@ -91,4 +99,25 @@ export class UtilityClient extends BaseHttpClient {
91
99
  throw error;
92
100
  }
93
101
  }
94
- }
102
+
103
+ /**
104
+ * Get the container version
105
+ * Returns the version embedded in the Docker image during build
106
+ */
107
+ async getVersion(): Promise<string> {
108
+ try {
109
+ const response = await this.get<VersionResponse>('/api/version');
110
+
111
+ this.logSuccess('Version retrieved', response.version);
112
+ return response.version;
113
+ } catch (error) {
114
+ // If version endpoint doesn't exist (old container), return 'unknown'
115
+ // This allows for backward compatibility
116
+ this.logger.debug(
117
+ 'Failed to get container version (may be old container)',
118
+ { error }
119
+ );
120
+ return 'unknown';
121
+ }
122
+ }
123
+ }