@cloudflare/sandbox 0.0.0-44e584c → 0.0.0-485cf61

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 (79) hide show
  1. package/CHANGELOG.md +185 -0
  2. package/Dockerfile +160 -9
  3. package/README.md +149 -50
  4. package/dist/chunk-3NEP4CNV.js +99 -0
  5. package/dist/chunk-3NEP4CNV.js.map +1 -0
  6. package/dist/chunk-6IYG2RIN.js +117 -0
  7. package/dist/chunk-6IYG2RIN.js.map +1 -0
  8. package/dist/chunk-HB44YO2A.js +2331 -0
  9. package/dist/chunk-HB44YO2A.js.map +1 -0
  10. package/dist/chunk-KPVMMMIP.js +105 -0
  11. package/dist/chunk-KPVMMMIP.js.map +1 -0
  12. package/dist/chunk-NNGBXDMY.js +89 -0
  13. package/dist/chunk-NNGBXDMY.js.map +1 -0
  14. package/dist/file-stream.d.ts +43 -0
  15. package/dist/file-stream.js +9 -0
  16. package/dist/file-stream.js.map +1 -0
  17. package/dist/index.d.ts +9 -0
  18. package/dist/index.js +55 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/interpreter.d.ts +33 -0
  21. package/dist/interpreter.js +8 -0
  22. package/dist/interpreter.js.map +1 -0
  23. package/dist/request-handler.d.ts +18 -0
  24. package/dist/request-handler.js +12 -0
  25. package/dist/request-handler.js.map +1 -0
  26. package/dist/sandbox-CtlKjZwf.d.ts +583 -0
  27. package/dist/sandbox.d.ts +4 -0
  28. package/dist/sandbox.js +12 -0
  29. package/dist/sandbox.js.map +1 -0
  30. package/dist/security.d.ts +35 -0
  31. package/dist/security.js +15 -0
  32. package/dist/security.js.map +1 -0
  33. package/dist/sse-parser.d.ts +28 -0
  34. package/dist/sse-parser.js +11 -0
  35. package/dist/sse-parser.js.map +1 -0
  36. package/package.json +11 -9
  37. package/src/clients/base-client.ts +297 -0
  38. package/src/clients/command-client.ts +118 -0
  39. package/src/clients/file-client.ts +272 -0
  40. package/src/clients/git-client.ts +95 -0
  41. package/src/clients/index.ts +63 -0
  42. package/src/clients/interpreter-client.ts +332 -0
  43. package/src/clients/port-client.ts +108 -0
  44. package/src/clients/process-client.ts +180 -0
  45. package/src/clients/sandbox-client.ts +41 -0
  46. package/src/clients/types.ts +81 -0
  47. package/src/clients/utility-client.ts +97 -0
  48. package/src/errors/adapter.ts +180 -0
  49. package/src/errors/classes.ts +469 -0
  50. package/src/errors/index.ts +105 -0
  51. package/src/file-stream.ts +164 -0
  52. package/src/index.ts +83 -119
  53. package/src/interpreter.ts +159 -0
  54. package/src/request-handler.ts +198 -0
  55. package/src/sandbox.ts +959 -0
  56. package/src/security.ts +133 -0
  57. package/src/sse-parser.ts +147 -0
  58. package/startup.sh +7 -0
  59. package/tests/base-client.test.ts +328 -0
  60. package/tests/command-client.test.ts +407 -0
  61. package/tests/file-client.test.ts +643 -0
  62. package/tests/file-stream.test.ts +306 -0
  63. package/tests/git-client.test.ts +328 -0
  64. package/tests/port-client.test.ts +301 -0
  65. package/tests/process-client.test.ts +658 -0
  66. package/tests/sandbox.test.ts +465 -0
  67. package/tests/sse-parser.test.ts +291 -0
  68. package/tests/utility-client.test.ts +266 -0
  69. package/tests/wrangler.jsonc +35 -0
  70. package/tsconfig.json +9 -1
  71. package/vitest.config.ts +31 -0
  72. package/container_src/index.ts +0 -2906
  73. package/container_src/package.json +0 -9
  74. package/src/client.ts +0 -1950
  75. package/tests/client.example.ts +0 -308
  76. package/tests/connection-test.ts +0 -81
  77. package/tests/simple-test.ts +0 -81
  78. package/tests/test1.ts +0 -281
  79. package/tests/test2.ts +0 -929
@@ -0,0 +1,332 @@
1
+ import {
2
+ type CodeContext,
3
+ type ContextCreateResult,
4
+ type ContextListResult,
5
+ type CreateContextOptions,
6
+ type ExecutionError,
7
+ type OutputMessage,
8
+ type Result,
9
+ ResultImpl,
10
+ } from '@repo/shared';
11
+ import type { ErrorResponse } from '../errors';
12
+ import { createErrorFromResponse, ErrorCode, InterpreterNotReadyError } from '../errors';
13
+ import { BaseHttpClient } from './base-client.js';
14
+ import type { HttpClientOptions } from './types.js';
15
+
16
+ // Streaming execution data from the server
17
+ interface StreamingExecutionData {
18
+ type: "result" | "stdout" | "stderr" | "error" | "execution_complete";
19
+ text?: string;
20
+ html?: string;
21
+ png?: string; // base64
22
+ jpeg?: string; // base64
23
+ svg?: string;
24
+ latex?: string;
25
+ markdown?: string;
26
+ javascript?: string;
27
+ json?: unknown;
28
+ chart?: {
29
+ type:
30
+ | "line"
31
+ | "bar"
32
+ | "scatter"
33
+ | "pie"
34
+ | "histogram"
35
+ | "heatmap"
36
+ | "unknown";
37
+ data: unknown;
38
+ options?: unknown;
39
+ };
40
+ data?: unknown;
41
+ metadata?: Record<string, unknown>;
42
+ execution_count?: number;
43
+ ename?: string;
44
+ evalue?: string;
45
+ traceback?: string[];
46
+ lineNumber?: number;
47
+ timestamp?: number;
48
+ }
49
+
50
+ export interface ExecutionCallbacks {
51
+ onStdout?: (output: OutputMessage) => void | Promise<void>;
52
+ onStderr?: (output: OutputMessage) => void | Promise<void>;
53
+ onResult?: (result: Result) => void | Promise<void>;
54
+ onError?: (error: ExecutionError) => void | Promise<void>;
55
+ }
56
+
57
+ export class InterpreterClient extends BaseHttpClient {
58
+ private readonly maxRetries = 3;
59
+ private readonly retryDelayMs = 1000;
60
+
61
+ constructor(options: HttpClientOptions = {}) {
62
+ super(options);
63
+ }
64
+
65
+ async createCodeContext(
66
+ options: CreateContextOptions = {}
67
+ ): Promise<CodeContext> {
68
+ return this.executeWithRetry(async () => {
69
+ const response = await this.doFetch("/api/contexts", {
70
+ method: "POST",
71
+ headers: { "Content-Type": "application/json" },
72
+ body: JSON.stringify({
73
+ language: options.language || "python",
74
+ cwd: options.cwd || "/workspace",
75
+ env_vars: options.envVars,
76
+ }),
77
+ });
78
+
79
+ if (!response.ok) {
80
+ const error = await this.parseErrorResponse(response);
81
+ throw error;
82
+ }
83
+
84
+ const data = (await response.json()) as ContextCreateResult;
85
+ if (!data.success) {
86
+ throw new Error(`Failed to create context: ${JSON.stringify(data)}`);
87
+ }
88
+
89
+ return {
90
+ id: data.contextId,
91
+ language: data.language,
92
+ cwd: data.cwd || '/workspace',
93
+ createdAt: new Date(data.timestamp),
94
+ lastUsed: new Date(data.timestamp),
95
+ };
96
+ });
97
+ }
98
+
99
+ async runCodeStream(
100
+ contextId: string | undefined,
101
+ code: string,
102
+ language: string | undefined,
103
+ callbacks: ExecutionCallbacks,
104
+ timeoutMs?: number
105
+ ): Promise<void> {
106
+ return this.executeWithRetry(async () => {
107
+ const response = await this.doFetch("/api/execute/code", {
108
+ method: "POST",
109
+ headers: {
110
+ "Content-Type": "application/json",
111
+ Accept: "text/event-stream",
112
+ },
113
+ body: JSON.stringify({
114
+ context_id: contextId,
115
+ code,
116
+ language,
117
+ ...(timeoutMs !== undefined && { timeout_ms: timeoutMs })
118
+ }),
119
+ });
120
+
121
+ if (!response.ok) {
122
+ const error = await this.parseErrorResponse(response);
123
+ throw error;
124
+ }
125
+
126
+ if (!response.body) {
127
+ throw new Error("No response body for streaming execution");
128
+ }
129
+
130
+ // Process streaming response
131
+ for await (const chunk of this.readLines(response.body)) {
132
+ await this.parseExecutionResult(chunk, callbacks);
133
+ }
134
+ });
135
+ }
136
+
137
+ async listCodeContexts(): Promise<CodeContext[]> {
138
+ return this.executeWithRetry(async () => {
139
+ const response = await this.doFetch("/api/contexts", {
140
+ method: "GET",
141
+ headers: { "Content-Type": "application/json" },
142
+ });
143
+
144
+ if (!response.ok) {
145
+ const error = await this.parseErrorResponse(response);
146
+ throw error;
147
+ }
148
+
149
+ const data = (await response.json()) as ContextListResult;
150
+ if (!data.success) {
151
+ throw new Error(`Failed to list contexts: ${JSON.stringify(data)}`);
152
+ }
153
+
154
+ return data.contexts.map((ctx) => ({
155
+ id: ctx.id,
156
+ language: ctx.language,
157
+ cwd: ctx.cwd || '/workspace',
158
+ createdAt: new Date(data.timestamp),
159
+ lastUsed: new Date(data.timestamp),
160
+ }));
161
+ });
162
+ }
163
+
164
+ async deleteCodeContext(contextId: string): Promise<void> {
165
+ return this.executeWithRetry(async () => {
166
+ const response = await this.doFetch(`/api/contexts/${contextId}`, {
167
+ method: "DELETE",
168
+ headers: { "Content-Type": "application/json" },
169
+ });
170
+
171
+ if (!response.ok) {
172
+ const error = await this.parseErrorResponse(response);
173
+ throw error;
174
+ }
175
+ });
176
+ }
177
+
178
+ /**
179
+ * Execute an operation with automatic retry for transient errors
180
+ */
181
+ private async executeWithRetry<T>(operation: () => Promise<T>): Promise<T> {
182
+ let lastError: Error | undefined;
183
+
184
+ for (let attempt = 0; attempt < this.maxRetries; attempt++) {
185
+ try {
186
+ return await operation();
187
+ } catch (error) {
188
+ lastError = error as Error;
189
+
190
+ // Check if it's a retryable error (interpreter not ready)
191
+ if (this.isRetryableError(error)) {
192
+ // Don't retry on the last attempt
193
+ if (attempt < this.maxRetries - 1) {
194
+ // Exponential backoff with jitter
195
+ const delay =
196
+ this.retryDelayMs * 2 ** attempt + Math.random() * 1000;
197
+ await new Promise((resolve) => setTimeout(resolve, delay));
198
+ continue;
199
+ }
200
+ }
201
+
202
+ // Not retryable or last attempt - throw the error
203
+ throw error;
204
+ }
205
+ }
206
+
207
+ throw lastError || new Error("Execution failed after retries");
208
+ }
209
+
210
+ private isRetryableError(error: unknown): boolean {
211
+ if (error instanceof InterpreterNotReadyError) {
212
+ return true;
213
+ }
214
+
215
+ if (error instanceof Error) {
216
+ return (
217
+ error.message.includes("not ready") ||
218
+ error.message.includes("initializing")
219
+ );
220
+ }
221
+
222
+ return false;
223
+ }
224
+
225
+ private async parseErrorResponse(response: Response): Promise<Error> {
226
+ try {
227
+ const errorData = await response.json() as ErrorResponse;
228
+ return createErrorFromResponse(errorData);
229
+ } catch {
230
+ // Fallback if response isn't JSON
231
+ const errorResponse: ErrorResponse = {
232
+ code: ErrorCode.INTERNAL_ERROR,
233
+ message: `HTTP ${response.status}: ${response.statusText}`,
234
+ context: {},
235
+ httpStatus: response.status,
236
+ timestamp: new Date().toISOString()
237
+ };
238
+ return createErrorFromResponse(errorResponse);
239
+ }
240
+ }
241
+
242
+ private async *readLines(
243
+ stream: ReadableStream<Uint8Array>
244
+ ): AsyncGenerator<string> {
245
+ const reader = stream.getReader();
246
+ let buffer = "";
247
+
248
+ try {
249
+ while (true) {
250
+ const { done, value } = await reader.read();
251
+ if (value) {
252
+ buffer += new TextDecoder().decode(value);
253
+ }
254
+ if (done) break;
255
+
256
+ let newlineIdx = buffer.indexOf("\n");
257
+ while (newlineIdx !== -1) {
258
+ yield buffer.slice(0, newlineIdx);
259
+ buffer = buffer.slice(newlineIdx + 1);
260
+ newlineIdx = buffer.indexOf("\n");
261
+ }
262
+ }
263
+
264
+ // Yield any remaining data
265
+ if (buffer.length > 0) {
266
+ yield buffer;
267
+ }
268
+ } finally {
269
+ reader.releaseLock();
270
+ }
271
+ }
272
+
273
+ private async parseExecutionResult(
274
+ line: string,
275
+ callbacks: ExecutionCallbacks
276
+ ) {
277
+ if (!line.trim()) return;
278
+
279
+ // Skip lines that don't start with "data: " (SSE format)
280
+ if (!line.startsWith('data: ')) return;
281
+
282
+ try {
283
+ // Strip "data: " prefix and parse JSON
284
+ const jsonData = line.substring(6); // "data: " is 6 characters
285
+ const data = JSON.parse(jsonData) as StreamingExecutionData;
286
+
287
+ switch (data.type) {
288
+ case "stdout":
289
+ if (callbacks.onStdout && data.text) {
290
+ await callbacks.onStdout({
291
+ text: data.text,
292
+ timestamp: data.timestamp || Date.now(),
293
+ });
294
+ }
295
+ break;
296
+
297
+ case "stderr":
298
+ if (callbacks.onStderr && data.text) {
299
+ await callbacks.onStderr({
300
+ text: data.text,
301
+ timestamp: data.timestamp || Date.now(),
302
+ });
303
+ }
304
+ break;
305
+
306
+ case "result":
307
+ if (callbacks.onResult) {
308
+ // Create a ResultImpl instance from the raw data
309
+ const result = new ResultImpl(data);
310
+ await callbacks.onResult(result);
311
+ }
312
+ break;
313
+
314
+ case "error":
315
+ if (callbacks.onError) {
316
+ await callbacks.onError({
317
+ name: data.ename || "Error",
318
+ message: data.evalue || "Unknown error",
319
+ traceback: data.traceback || [],
320
+ });
321
+ }
322
+ break;
323
+
324
+ case "execution_complete":
325
+ // Signal completion - callbacks can handle cleanup if needed
326
+ break;
327
+ }
328
+ } catch (error) {
329
+ console.error("Failed to parse execution result:", error);
330
+ }
331
+ }
332
+ }
@@ -0,0 +1,108 @@
1
+ import type {
2
+ PortCloseResult,
3
+ PortExposeResult,
4
+ PortListResult,
5
+ } from '@repo/shared';
6
+ import { BaseHttpClient } from './base-client';
7
+ import type { HttpClientOptions } from './types';
8
+
9
+ // Re-export for convenience
10
+ export type {
11
+ PortExposeResult,
12
+ PortCloseResult,
13
+ PortListResult,
14
+ };
15
+
16
+ /**
17
+ * Request interface for exposing ports
18
+ */
19
+ export interface ExposePortRequest {
20
+ port: number;
21
+ name?: string;
22
+ }
23
+
24
+ /**
25
+ * Request interface for unexposing ports
26
+ */
27
+ export interface UnexposePortRequest {
28
+ port: number;
29
+ }
30
+
31
+ /**
32
+ * Client for port management and preview URL operations
33
+ */
34
+ export class PortClient extends BaseHttpClient {
35
+ constructor(options: HttpClientOptions = {}) {
36
+ super(options);
37
+ }
38
+
39
+ /**
40
+ * Expose a port and get a preview URL
41
+ * @param port - Port number to expose
42
+ * @param sessionId - The session ID for this operation
43
+ * @param name - Optional name for the port
44
+ */
45
+ async exposePort(
46
+ port: number,
47
+ sessionId: string,
48
+ name?: string
49
+ ): Promise<PortExposeResult> {
50
+ try {
51
+ const data = { port, sessionId, name };
52
+
53
+ const response = await this.post<PortExposeResult>(
54
+ '/api/expose-port',
55
+ data
56
+ );
57
+
58
+ this.logSuccess(
59
+ 'Port exposed',
60
+ `${port} exposed at ${response.url}${name ? ` (${name})` : ''}`
61
+ );
62
+
63
+ return response;
64
+ } catch (error) {
65
+ this.logError('exposePort', error);
66
+ throw error;
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Unexpose a port and remove its preview URL
72
+ * @param port - Port number to unexpose
73
+ * @param sessionId - The session ID for this operation
74
+ */
75
+ async unexposePort(port: number, sessionId: string): Promise<PortCloseResult> {
76
+ try {
77
+ const url = `/api/exposed-ports/${port}?session=${encodeURIComponent(sessionId)}`;
78
+ const response = await this.delete<PortCloseResult>(url);
79
+
80
+ this.logSuccess('Port unexposed', `${port}`);
81
+ return response;
82
+ } catch (error) {
83
+ this.logError('unexposePort', error);
84
+ throw error;
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Get all currently exposed ports
90
+ * @param sessionId - The session ID for this operation
91
+ */
92
+ async getExposedPorts(sessionId: string): Promise<PortListResult> {
93
+ try {
94
+ const url = `/api/exposed-ports?session=${encodeURIComponent(sessionId)}`;
95
+ const response = await this.get<PortListResult>(url);
96
+
97
+ this.logSuccess(
98
+ 'Exposed ports retrieved',
99
+ `${response.ports.length} ports exposed`
100
+ );
101
+
102
+ return response;
103
+ } catch (error) {
104
+ this.logError('getExposedPorts', error);
105
+ throw error;
106
+ }
107
+ }
108
+ }
@@ -0,0 +1,180 @@
1
+ import type {
2
+ ProcessCleanupResult,
3
+ ProcessInfoResult,
4
+ ProcessKillResult,
5
+ ProcessListResult,
6
+ ProcessLogsResult,
7
+ ProcessStartResult,
8
+ StartProcessRequest,
9
+ } from '@repo/shared';
10
+ import { BaseHttpClient } from './base-client';
11
+ import type { HttpClientOptions } from './types';
12
+
13
+ // Re-export for convenience
14
+ export type {
15
+ StartProcessRequest,
16
+ ProcessStartResult,
17
+ ProcessListResult,
18
+ ProcessInfoResult,
19
+ ProcessKillResult,
20
+ ProcessLogsResult,
21
+ ProcessCleanupResult,
22
+ };
23
+
24
+
25
+ /**
26
+ * Client for background process management
27
+ */
28
+ export class ProcessClient extends BaseHttpClient {
29
+ constructor(options: HttpClientOptions = {}) {
30
+ super(options);
31
+ }
32
+
33
+ /**
34
+ * Start a background process
35
+ * @param command - Command to execute as a background process
36
+ * @param sessionId - The session ID for this operation
37
+ * @param options - Optional settings (processId)
38
+ */
39
+ async startProcess(
40
+ command: string,
41
+ sessionId: string,
42
+ options?: { processId?: string }
43
+ ): Promise<ProcessStartResult> {
44
+ try {
45
+ const data: StartProcessRequest = {
46
+ command,
47
+ sessionId,
48
+ processId: options?.processId,
49
+ };
50
+
51
+ const response = await this.post<ProcessStartResult>(
52
+ '/api/process/start',
53
+ data
54
+ );
55
+
56
+ this.logSuccess(
57
+ 'Process started',
58
+ `${command} (ID: ${response.processId})`
59
+ );
60
+
61
+ return response;
62
+ } catch (error) {
63
+ this.logError('startProcess', error);
64
+ throw error;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * List all processes (sandbox-scoped, not session-scoped)
70
+ */
71
+ async listProcesses(): Promise<ProcessListResult> {
72
+ try {
73
+ const url = `/api/process/list`;
74
+ const response = await this.get<ProcessListResult>(url);
75
+
76
+ this.logSuccess('Processes listed', `${response.processes.length} processes`);
77
+ return response;
78
+ } catch (error) {
79
+ this.logError('listProcesses', error);
80
+ throw error;
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Get information about a specific process (sandbox-scoped, not session-scoped)
86
+ * @param processId - ID of the process to retrieve
87
+ */
88
+ async getProcess(processId: string): Promise<ProcessInfoResult> {
89
+ try {
90
+ const url = `/api/process/${processId}`;
91
+ const response = await this.get<ProcessInfoResult>(url);
92
+
93
+ this.logSuccess('Process retrieved', `ID: ${processId}`);
94
+ return response;
95
+ } catch (error) {
96
+ this.logError('getProcess', error);
97
+ throw error;
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Kill a specific process (sandbox-scoped, not session-scoped)
103
+ * @param processId - ID of the process to kill
104
+ */
105
+ async killProcess(processId: string): Promise<ProcessKillResult> {
106
+ try {
107
+ const url = `/api/process/${processId}`;
108
+ const response = await this.delete<ProcessKillResult>(url);
109
+
110
+ this.logSuccess('Process killed', `ID: ${processId}`);
111
+ return response;
112
+ } catch (error) {
113
+ this.logError('killProcess', error);
114
+ throw error;
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Kill all running processes (sandbox-scoped, not session-scoped)
120
+ */
121
+ async killAllProcesses(): Promise<ProcessCleanupResult> {
122
+ try {
123
+ const url = `/api/process/kill-all`;
124
+ const response = await this.delete<ProcessCleanupResult>(url);
125
+
126
+ this.logSuccess(
127
+ 'All processes killed',
128
+ `${response.cleanedCount} processes terminated`
129
+ );
130
+
131
+ return response;
132
+ } catch (error) {
133
+ this.logError('killAllProcesses', error);
134
+ throw error;
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Get logs from a specific process (sandbox-scoped, not session-scoped)
140
+ * @param processId - ID of the process to get logs from
141
+ */
142
+ async getProcessLogs(processId: string): Promise<ProcessLogsResult> {
143
+ try {
144
+ const url = `/api/process/${processId}/logs`;
145
+ const response = await this.get<ProcessLogsResult>(url);
146
+
147
+ this.logSuccess(
148
+ 'Process logs retrieved',
149
+ `ID: ${processId}, stdout: ${response.stdout.length} chars, stderr: ${response.stderr.length} chars`
150
+ );
151
+
152
+ return response;
153
+ } catch (error) {
154
+ this.logError('getProcessLogs', error);
155
+ throw error;
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Stream logs from a specific process (sandbox-scoped, not session-scoped)
161
+ * @param processId - ID of the process to stream logs from
162
+ */
163
+ async streamProcessLogs(processId: string): Promise<ReadableStream<Uint8Array>> {
164
+ try {
165
+ const url = `/api/process/${processId}/stream`;
166
+ const response = await this.doFetch(url, {
167
+ method: 'GET',
168
+ });
169
+
170
+ const stream = await this.handleStreamResponse(response);
171
+
172
+ this.logSuccess('Process log stream started', `ID: ${processId}`);
173
+
174
+ return stream;
175
+ } catch (error) {
176
+ this.logError('streamProcessLogs', error);
177
+ throw error;
178
+ }
179
+ }
180
+ }
@@ -0,0 +1,41 @@
1
+ import { CommandClient } from './command-client';
2
+ import { FileClient } from './file-client';
3
+ import { GitClient } from './git-client';
4
+ import { InterpreterClient } from './interpreter-client';
5
+ import { PortClient } from './port-client';
6
+ import { ProcessClient } from './process-client';
7
+ import type { HttpClientOptions } from './types';
8
+ import { UtilityClient } from './utility-client';
9
+
10
+ /**
11
+ * Main sandbox client that composes all domain-specific clients
12
+ * Provides organized access to all sandbox functionality
13
+ */
14
+ export class SandboxClient {
15
+ public readonly commands: CommandClient;
16
+ public readonly files: FileClient;
17
+ public readonly processes: ProcessClient;
18
+ public readonly ports: PortClient;
19
+ public readonly git: GitClient;
20
+ public readonly interpreter: InterpreterClient;
21
+ public readonly utils: UtilityClient;
22
+
23
+ constructor(options: HttpClientOptions = {}) {
24
+ // Ensure baseUrl is provided for all clients
25
+ const clientOptions = {
26
+ baseUrl: 'http://localhost:3000',
27
+ ...options,
28
+ };
29
+
30
+ // Initialize all domain clients with shared options
31
+ this.commands = new CommandClient(clientOptions);
32
+ this.files = new FileClient(clientOptions);
33
+ this.processes = new ProcessClient(clientOptions);
34
+ this.ports = new PortClient(clientOptions);
35
+ this.git = new GitClient(clientOptions);
36
+ this.interpreter = new InterpreterClient(clientOptions);
37
+ this.utils = new UtilityClient(clientOptions);
38
+ }
39
+
40
+
41
+ }