@cloudflare/sandbox 0.0.0-598205f → 0.0.0-603d05f
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.
- package/CHANGELOG.md +105 -0
- package/Dockerfile +44 -19
- package/README.md +298 -14
- package/container_src/bun.lock +122 -0
- package/container_src/circuit-breaker.ts +121 -0
- package/container_src/control-process.ts +784 -0
- package/container_src/handler/exec.ts +99 -251
- package/container_src/handler/file.ts +204 -642
- package/container_src/handler/git.ts +28 -80
- package/container_src/handler/process.ts +443 -515
- package/container_src/handler/session.ts +92 -0
- package/container_src/index.ts +363 -123
- package/container_src/isolation.ts +1039 -0
- package/container_src/jupyter-server.ts +579 -0
- package/container_src/jupyter-service.ts +461 -0
- package/container_src/jupyter_config.py +48 -0
- package/container_src/mime-processor.ts +255 -0
- package/container_src/package.json +9 -0
- package/container_src/shell-escape.ts +42 -0
- package/container_src/startup.sh +84 -0
- package/container_src/types.ts +42 -14
- package/package.json +1 -1
- package/src/client.ts +206 -235
- package/src/errors.ts +218 -0
- package/src/index.ts +59 -15
- package/src/interpreter-types.ts +383 -0
- package/src/interpreter.ts +150 -0
- package/src/jupyter-client.ts +349 -0
- package/src/sandbox.ts +497 -400
- package/src/types.ts +140 -24
package/src/sandbox.ts
CHANGED
|
@@ -1,25 +1,33 @@
|
|
|
1
1
|
import { Container, getContainer } from "@cloudflare/containers";
|
|
2
|
-
import {
|
|
2
|
+
import { CodeInterpreter } from "./interpreter";
|
|
3
|
+
import type {
|
|
4
|
+
CodeContext,
|
|
5
|
+
CreateContextOptions,
|
|
6
|
+
ExecutionResult,
|
|
7
|
+
RunCodeOptions,
|
|
8
|
+
} from "./interpreter-types";
|
|
9
|
+
import { JupyterClient } from "./jupyter-client";
|
|
3
10
|
import { isLocalhostPattern } from "./request-handler";
|
|
4
11
|
import {
|
|
5
12
|
logSecurityEvent,
|
|
6
13
|
SecurityError,
|
|
7
14
|
sanitizeSandboxId,
|
|
8
|
-
validatePort
|
|
15
|
+
validatePort,
|
|
9
16
|
} from "./security";
|
|
17
|
+
import { parseSSEStream } from "./sse-parser";
|
|
10
18
|
import type {
|
|
19
|
+
ExecEvent,
|
|
11
20
|
ExecOptions,
|
|
12
21
|
ExecResult,
|
|
22
|
+
ExecuteResponse,
|
|
23
|
+
ExecutionSession,
|
|
13
24
|
ISandbox,
|
|
14
25
|
Process,
|
|
15
26
|
ProcessOptions,
|
|
16
27
|
ProcessStatus,
|
|
17
|
-
StreamOptions
|
|
18
|
-
} from "./types";
|
|
19
|
-
import {
|
|
20
|
-
ProcessNotFoundError,
|
|
21
|
-
SandboxError
|
|
28
|
+
StreamOptions,
|
|
22
29
|
} from "./types";
|
|
30
|
+
import { ProcessNotFoundError, SandboxError } from "./types";
|
|
23
31
|
|
|
24
32
|
export function getSandbox(ns: DurableObjectNamespace<Sandbox>, id: string) {
|
|
25
33
|
const stub = getContainer(ns, id);
|
|
@@ -32,22 +40,22 @@ export function getSandbox(ns: DurableObjectNamespace<Sandbox>, id: string) {
|
|
|
32
40
|
|
|
33
41
|
export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
|
|
34
42
|
defaultPort = 3000; // Default port for the container's Bun server
|
|
35
|
-
sleepAfter = "
|
|
36
|
-
client:
|
|
43
|
+
sleepAfter = "20m"; // Keep container warm for 20 minutes to avoid cold starts
|
|
44
|
+
client: JupyterClient;
|
|
37
45
|
private sandboxName: string | null = null;
|
|
46
|
+
private codeInterpreter: CodeInterpreter;
|
|
47
|
+
private defaultSession: ExecutionSession | null = null;
|
|
38
48
|
|
|
39
49
|
constructor(ctx: DurableObjectState, env: Env) {
|
|
40
50
|
super(ctx, env);
|
|
41
|
-
this.client = new
|
|
51
|
+
this.client = new JupyterClient({
|
|
42
52
|
onCommandComplete: (success, exitCode, _stdout, _stderr, command) => {
|
|
43
53
|
console.log(
|
|
44
54
|
`[Container] Command completed: ${command}, Success: ${success}, Exit code: ${exitCode}`
|
|
45
55
|
);
|
|
46
56
|
},
|
|
47
57
|
onCommandStart: (command) => {
|
|
48
|
-
console.log(
|
|
49
|
-
`[Container] Command started: ${command}`
|
|
50
|
-
);
|
|
58
|
+
console.log(`[Container] Command started: ${command}`);
|
|
51
59
|
},
|
|
52
60
|
onError: (error, _command) => {
|
|
53
61
|
console.error(`[Container] Command error: ${error}`);
|
|
@@ -59,9 +67,13 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
|
|
|
59
67
|
stub: this,
|
|
60
68
|
});
|
|
61
69
|
|
|
70
|
+
// Initialize code interpreter
|
|
71
|
+
this.codeInterpreter = new CodeInterpreter(this);
|
|
72
|
+
|
|
62
73
|
// Load the sandbox name from storage on initialization
|
|
63
74
|
this.ctx.blockConcurrencyWhile(async () => {
|
|
64
|
-
this.sandboxName =
|
|
75
|
+
this.sandboxName =
|
|
76
|
+
(await this.ctx.storage.get<string>("sandboxName")) || null;
|
|
65
77
|
});
|
|
66
78
|
}
|
|
67
79
|
|
|
@@ -69,7 +81,7 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
|
|
|
69
81
|
async setSandboxName(name: string): Promise<void> {
|
|
70
82
|
if (!this.sandboxName) {
|
|
71
83
|
this.sandboxName = name;
|
|
72
|
-
await this.ctx.storage.put(
|
|
84
|
+
await this.ctx.storage.put("sandboxName", name);
|
|
73
85
|
console.log(`[Sandbox] Stored sandbox name via RPC: ${name}`);
|
|
74
86
|
}
|
|
75
87
|
}
|
|
@@ -78,6 +90,11 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
|
|
|
78
90
|
async setEnvVars(envVars: Record<string, string>): Promise<void> {
|
|
79
91
|
this.envVars = { ...this.envVars, ...envVars };
|
|
80
92
|
console.log(`[Sandbox] Updated environment variables`);
|
|
93
|
+
|
|
94
|
+
// If we have a default session, update its environment too
|
|
95
|
+
if (this.defaultSession) {
|
|
96
|
+
await this.defaultSession.setEnvVars(envVars);
|
|
97
|
+
}
|
|
81
98
|
}
|
|
82
99
|
|
|
83
100
|
override onStart() {
|
|
@@ -86,9 +103,6 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
|
|
|
86
103
|
|
|
87
104
|
override onStop() {
|
|
88
105
|
console.log("Sandbox successfully shut down");
|
|
89
|
-
if (this.client) {
|
|
90
|
-
this.client.clearSession();
|
|
91
|
-
}
|
|
92
106
|
}
|
|
93
107
|
|
|
94
108
|
override onError(error: unknown) {
|
|
@@ -100,10 +114,10 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
|
|
|
100
114
|
const url = new URL(request.url);
|
|
101
115
|
|
|
102
116
|
// Capture and store the sandbox name from the header if present
|
|
103
|
-
if (!this.sandboxName && request.headers.has(
|
|
104
|
-
const name = request.headers.get(
|
|
117
|
+
if (!this.sandboxName && request.headers.has("X-Sandbox-Name")) {
|
|
118
|
+
const name = request.headers.get("X-Sandbox-Name")!;
|
|
105
119
|
this.sandboxName = name;
|
|
106
|
-
await this.ctx.storage.put(
|
|
120
|
+
await this.ctx.storage.put("sandboxName", name);
|
|
107
121
|
console.log(`[Sandbox] Stored sandbox name: ${this.sandboxName}`);
|
|
108
122
|
}
|
|
109
123
|
|
|
@@ -126,349 +140,95 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
|
|
|
126
140
|
return 3000;
|
|
127
141
|
}
|
|
128
142
|
|
|
129
|
-
//
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (options?.signal?.aborted) {
|
|
141
|
-
throw new Error('Operation was aborted');
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
let result: ExecResult;
|
|
145
|
-
|
|
146
|
-
if (options?.stream && options?.onOutput) {
|
|
147
|
-
// Streaming with callbacks - we need to collect the final result
|
|
148
|
-
result = await this.executeWithStreaming(command, options, startTime, timestamp);
|
|
149
|
-
} else {
|
|
150
|
-
// Regular execution
|
|
151
|
-
const response = await this.client.execute(
|
|
152
|
-
command,
|
|
153
|
-
options?.sessionId
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
const duration = Date.now() - startTime;
|
|
157
|
-
result = this.mapExecuteResponseToExecResult(response, duration, options?.sessionId);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Call completion callback if provided
|
|
161
|
-
if (options?.onComplete) {
|
|
162
|
-
options.onComplete(result);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return result;
|
|
166
|
-
} catch (error) {
|
|
167
|
-
if (options?.onError && error instanceof Error) {
|
|
168
|
-
options.onError(error);
|
|
169
|
-
}
|
|
170
|
-
throw error;
|
|
171
|
-
} finally {
|
|
172
|
-
if (timeoutId) {
|
|
173
|
-
clearTimeout(timeoutId);
|
|
174
|
-
}
|
|
143
|
+
// Helper to ensure default session is initialized
|
|
144
|
+
private async ensureDefaultSession(): Promise<ExecutionSession> {
|
|
145
|
+
if (!this.defaultSession) {
|
|
146
|
+
const sessionId = `sandbox-${this.sandboxName || 'default'}`;
|
|
147
|
+
this.defaultSession = await this.createSession({
|
|
148
|
+
id: sessionId,
|
|
149
|
+
env: this.envVars || {},
|
|
150
|
+
cwd: '/workspace',
|
|
151
|
+
isolation: true
|
|
152
|
+
});
|
|
153
|
+
console.log(`[Sandbox] Default session initialized: ${sessionId}`);
|
|
175
154
|
}
|
|
155
|
+
return this.defaultSession;
|
|
176
156
|
}
|
|
177
157
|
|
|
178
|
-
private async executeWithStreaming(
|
|
179
|
-
command: string,
|
|
180
|
-
options: ExecOptions,
|
|
181
|
-
startTime: number,
|
|
182
|
-
timestamp: string
|
|
183
|
-
): Promise<ExecResult> {
|
|
184
|
-
let stdout = '';
|
|
185
|
-
let stderr = '';
|
|
186
|
-
|
|
187
|
-
try {
|
|
188
|
-
const stream = await this.client.executeCommandStream(command, options.sessionId);
|
|
189
|
-
const { parseSSEStream } = await import('./sse-parser');
|
|
190
|
-
|
|
191
|
-
for await (const event of parseSSEStream<import('./types').ExecEvent>(stream)) {
|
|
192
|
-
// Check for cancellation
|
|
193
|
-
if (options.signal?.aborted) {
|
|
194
|
-
throw new Error('Operation was aborted');
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
switch (event.type) {
|
|
198
|
-
case 'stdout':
|
|
199
|
-
case 'stderr':
|
|
200
|
-
if (event.data) {
|
|
201
|
-
// Update accumulated output
|
|
202
|
-
if (event.type === 'stdout') stdout += event.data;
|
|
203
|
-
if (event.type === 'stderr') stderr += event.data;
|
|
204
|
-
|
|
205
|
-
// Call user's callback
|
|
206
|
-
if (options.onOutput) {
|
|
207
|
-
options.onOutput(event.type, event.data);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
break;
|
|
211
|
-
|
|
212
|
-
case 'complete': {
|
|
213
|
-
// Use result from complete event if available
|
|
214
|
-
const duration = Date.now() - startTime;
|
|
215
|
-
return event.result || {
|
|
216
|
-
success: event.exitCode === 0,
|
|
217
|
-
exitCode: event.exitCode || 0,
|
|
218
|
-
stdout,
|
|
219
|
-
stderr,
|
|
220
|
-
command,
|
|
221
|
-
duration,
|
|
222
|
-
timestamp,
|
|
223
|
-
sessionId: options.sessionId
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
case 'error':
|
|
228
|
-
throw new Error(event.error || 'Command execution failed');
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// If we get here without a complete event, something went wrong
|
|
233
|
-
throw new Error('Stream ended without completion event');
|
|
234
158
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
throw error;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
private mapExecuteResponseToExecResult(
|
|
244
|
-
response: import('./client').ExecuteResponse,
|
|
245
|
-
duration: number,
|
|
246
|
-
sessionId?: string
|
|
247
|
-
): ExecResult {
|
|
248
|
-
return {
|
|
249
|
-
success: response.success,
|
|
250
|
-
exitCode: response.exitCode,
|
|
251
|
-
stdout: response.stdout,
|
|
252
|
-
stderr: response.stderr,
|
|
253
|
-
command: response.command,
|
|
254
|
-
duration,
|
|
255
|
-
timestamp: response.timestamp,
|
|
256
|
-
sessionId
|
|
257
|
-
};
|
|
159
|
+
async exec(command: string, options?: ExecOptions): Promise<ExecResult> {
|
|
160
|
+
const session = await this.ensureDefaultSession();
|
|
161
|
+
return session.exec(command, options);
|
|
258
162
|
}
|
|
259
163
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
processId: options?.processId,
|
|
267
|
-
sessionId: options?.sessionId,
|
|
268
|
-
timeout: options?.timeout,
|
|
269
|
-
env: options?.env,
|
|
270
|
-
cwd: options?.cwd,
|
|
271
|
-
encoding: options?.encoding,
|
|
272
|
-
autoCleanup: options?.autoCleanup
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
const process = response.process;
|
|
276
|
-
const processObj: Process = {
|
|
277
|
-
id: process.id,
|
|
278
|
-
pid: process.pid,
|
|
279
|
-
command: process.command,
|
|
280
|
-
status: process.status as ProcessStatus,
|
|
281
|
-
startTime: new Date(process.startTime),
|
|
282
|
-
endTime: undefined,
|
|
283
|
-
exitCode: undefined,
|
|
284
|
-
sessionId: process.sessionId,
|
|
285
|
-
|
|
286
|
-
async kill(): Promise<void> {
|
|
287
|
-
throw new Error('Method will be replaced');
|
|
288
|
-
},
|
|
289
|
-
async getStatus(): Promise<ProcessStatus> {
|
|
290
|
-
throw new Error('Method will be replaced');
|
|
291
|
-
},
|
|
292
|
-
async getLogs(): Promise<{ stdout: string; stderr: string }> {
|
|
293
|
-
throw new Error('Method will be replaced');
|
|
294
|
-
}
|
|
295
|
-
};
|
|
296
|
-
|
|
297
|
-
// Bind context properly
|
|
298
|
-
processObj.kill = async (signal?: string) => {
|
|
299
|
-
await this.killProcess(process.id, signal);
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
processObj.getStatus = async () => {
|
|
303
|
-
const current = await this.getProcess(process.id);
|
|
304
|
-
return current?.status || 'error';
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
processObj.getLogs = async () => {
|
|
308
|
-
const logs = await this.getProcessLogs(process.id);
|
|
309
|
-
return { stdout: logs.stdout, stderr: logs.stderr };
|
|
310
|
-
};
|
|
311
|
-
|
|
312
|
-
// Call onStart callback if provided
|
|
313
|
-
if (options?.onStart) {
|
|
314
|
-
options.onStart(processObj);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
return processObj;
|
|
318
|
-
|
|
319
|
-
} catch (error) {
|
|
320
|
-
if (options?.onError && error instanceof Error) {
|
|
321
|
-
options.onError(error);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
throw error;
|
|
325
|
-
}
|
|
164
|
+
async startProcess(
|
|
165
|
+
command: string,
|
|
166
|
+
options?: ProcessOptions
|
|
167
|
+
): Promise<Process> {
|
|
168
|
+
const session = await this.ensureDefaultSession();
|
|
169
|
+
return session.startProcess(command, options);
|
|
326
170
|
}
|
|
327
171
|
|
|
328
172
|
async listProcesses(): Promise<Process[]> {
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
return response.processes.map(processData => ({
|
|
332
|
-
id: processData.id,
|
|
333
|
-
pid: processData.pid,
|
|
334
|
-
command: processData.command,
|
|
335
|
-
status: processData.status,
|
|
336
|
-
startTime: new Date(processData.startTime),
|
|
337
|
-
endTime: processData.endTime ? new Date(processData.endTime) : undefined,
|
|
338
|
-
exitCode: processData.exitCode,
|
|
339
|
-
sessionId: processData.sessionId,
|
|
340
|
-
|
|
341
|
-
kill: async (signal?: string) => {
|
|
342
|
-
await this.killProcess(processData.id, signal);
|
|
343
|
-
},
|
|
344
|
-
|
|
345
|
-
getStatus: async () => {
|
|
346
|
-
const current = await this.getProcess(processData.id);
|
|
347
|
-
return current?.status || 'error';
|
|
348
|
-
},
|
|
349
|
-
|
|
350
|
-
getLogs: async () => {
|
|
351
|
-
const logs = await this.getProcessLogs(processData.id);
|
|
352
|
-
return { stdout: logs.stdout, stderr: logs.stderr };
|
|
353
|
-
}
|
|
354
|
-
}));
|
|
173
|
+
const session = await this.ensureDefaultSession();
|
|
174
|
+
return session.listProcesses();
|
|
355
175
|
}
|
|
356
176
|
|
|
357
177
|
async getProcess(id: string): Promise<Process | null> {
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
return null;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
const processData = response.process;
|
|
364
|
-
return {
|
|
365
|
-
id: processData.id,
|
|
366
|
-
pid: processData.pid,
|
|
367
|
-
command: processData.command,
|
|
368
|
-
status: processData.status,
|
|
369
|
-
startTime: new Date(processData.startTime),
|
|
370
|
-
endTime: processData.endTime ? new Date(processData.endTime) : undefined,
|
|
371
|
-
exitCode: processData.exitCode,
|
|
372
|
-
sessionId: processData.sessionId,
|
|
373
|
-
|
|
374
|
-
kill: async (signal?: string) => {
|
|
375
|
-
await this.killProcess(processData.id, signal);
|
|
376
|
-
},
|
|
377
|
-
|
|
378
|
-
getStatus: async () => {
|
|
379
|
-
const current = await this.getProcess(processData.id);
|
|
380
|
-
return current?.status || 'error';
|
|
381
|
-
},
|
|
382
|
-
|
|
383
|
-
getLogs: async () => {
|
|
384
|
-
const logs = await this.getProcessLogs(processData.id);
|
|
385
|
-
return { stdout: logs.stdout, stderr: logs.stderr };
|
|
386
|
-
}
|
|
387
|
-
};
|
|
178
|
+
const session = await this.ensureDefaultSession();
|
|
179
|
+
return session.getProcess(id);
|
|
388
180
|
}
|
|
389
181
|
|
|
390
|
-
async killProcess(id: string,
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
await this.client.killProcess(id);
|
|
394
|
-
} catch (error) {
|
|
395
|
-
if (error instanceof Error && error.message.includes('Process not found')) {
|
|
396
|
-
throw new ProcessNotFoundError(id);
|
|
397
|
-
}
|
|
398
|
-
throw new SandboxError(
|
|
399
|
-
`Failed to kill process ${id}: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
400
|
-
'KILL_PROCESS_FAILED'
|
|
401
|
-
);
|
|
402
|
-
}
|
|
182
|
+
async killProcess(id: string, signal?: string): Promise<void> {
|
|
183
|
+
const session = await this.ensureDefaultSession();
|
|
184
|
+
return session.killProcess(id, signal);
|
|
403
185
|
}
|
|
404
186
|
|
|
405
187
|
async killAllProcesses(): Promise<number> {
|
|
406
|
-
const
|
|
407
|
-
return
|
|
188
|
+
const session = await this.ensureDefaultSession();
|
|
189
|
+
return session.killAllProcesses();
|
|
408
190
|
}
|
|
409
191
|
|
|
410
192
|
async cleanupCompletedProcesses(): Promise<number> {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
// We'll return 0 as a placeholder until the container endpoint is added
|
|
414
|
-
return 0;
|
|
193
|
+
const session = await this.ensureDefaultSession();
|
|
194
|
+
return session.cleanupCompletedProcesses();
|
|
415
195
|
}
|
|
416
196
|
|
|
417
|
-
async getProcessLogs(
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
stderr: response.stderr
|
|
423
|
-
};
|
|
424
|
-
} catch (error) {
|
|
425
|
-
if (error instanceof Error && error.message.includes('Process not found')) {
|
|
426
|
-
throw new ProcessNotFoundError(id);
|
|
427
|
-
}
|
|
428
|
-
throw error;
|
|
429
|
-
}
|
|
197
|
+
async getProcessLogs(
|
|
198
|
+
id: string
|
|
199
|
+
): Promise<{ stdout: string; stderr: string }> {
|
|
200
|
+
const session = await this.ensureDefaultSession();
|
|
201
|
+
return session.getProcessLogs(id);
|
|
430
202
|
}
|
|
431
203
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
// Get the stream from HttpClient (need to add this method)
|
|
441
|
-
const stream = await this.client.executeCommandStream(command, options?.sessionId);
|
|
442
|
-
|
|
443
|
-
// Return the ReadableStream directly - can be converted to AsyncIterable by consumers
|
|
444
|
-
return stream;
|
|
204
|
+
// Streaming methods - delegates to default session
|
|
205
|
+
async execStream(
|
|
206
|
+
command: string,
|
|
207
|
+
options?: StreamOptions
|
|
208
|
+
): Promise<ReadableStream<Uint8Array>> {
|
|
209
|
+
const session = await this.ensureDefaultSession();
|
|
210
|
+
return session.execStream(command, options);
|
|
445
211
|
}
|
|
446
212
|
|
|
447
|
-
async streamProcessLogs(
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
// Get the stream from HttpClient
|
|
454
|
-
const stream = await this.client.streamProcessLogs(processId);
|
|
455
|
-
|
|
456
|
-
// Return the ReadableStream directly - can be converted to AsyncIterable by consumers
|
|
457
|
-
return stream;
|
|
213
|
+
async streamProcessLogs(
|
|
214
|
+
processId: string,
|
|
215
|
+
options?: { signal?: AbortSignal }
|
|
216
|
+
): Promise<ReadableStream<Uint8Array>> {
|
|
217
|
+
const session = await this.ensureDefaultSession();
|
|
218
|
+
return session.streamProcessLogs(processId, options);
|
|
458
219
|
}
|
|
459
220
|
|
|
460
221
|
async gitCheckout(
|
|
461
222
|
repoUrl: string,
|
|
462
223
|
options: { branch?: string; targetDir?: string }
|
|
463
224
|
) {
|
|
464
|
-
|
|
225
|
+
const session = await this.ensureDefaultSession();
|
|
226
|
+
return session.gitCheckout(repoUrl, options);
|
|
465
227
|
}
|
|
466
228
|
|
|
467
|
-
async mkdir(
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
) {
|
|
471
|
-
return this.client.mkdir(path, options.recursive);
|
|
229
|
+
async mkdir(path: string, options: { recursive?: boolean } = {}) {
|
|
230
|
+
const session = await this.ensureDefaultSession();
|
|
231
|
+
return session.mkdir(path, options);
|
|
472
232
|
}
|
|
473
233
|
|
|
474
234
|
async writeFile(
|
|
@@ -476,32 +236,39 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
|
|
|
476
236
|
content: string,
|
|
477
237
|
options: { encoding?: string } = {}
|
|
478
238
|
) {
|
|
479
|
-
|
|
239
|
+
const session = await this.ensureDefaultSession();
|
|
240
|
+
return session.writeFile(path, content, options);
|
|
480
241
|
}
|
|
481
242
|
|
|
482
243
|
async deleteFile(path: string) {
|
|
483
|
-
|
|
244
|
+
const session = await this.ensureDefaultSession();
|
|
245
|
+
return session.deleteFile(path);
|
|
484
246
|
}
|
|
485
247
|
|
|
486
|
-
async renameFile(
|
|
487
|
-
|
|
488
|
-
newPath
|
|
489
|
-
) {
|
|
490
|
-
return this.client.renameFile(oldPath, newPath);
|
|
248
|
+
async renameFile(oldPath: string, newPath: string) {
|
|
249
|
+
const session = await this.ensureDefaultSession();
|
|
250
|
+
return session.renameFile(oldPath, newPath);
|
|
491
251
|
}
|
|
492
252
|
|
|
493
|
-
async moveFile(
|
|
494
|
-
|
|
495
|
-
destinationPath
|
|
496
|
-
) {
|
|
497
|
-
return this.client.moveFile(sourcePath, destinationPath);
|
|
253
|
+
async moveFile(sourcePath: string, destinationPath: string) {
|
|
254
|
+
const session = await this.ensureDefaultSession();
|
|
255
|
+
return session.moveFile(sourcePath, destinationPath);
|
|
498
256
|
}
|
|
499
257
|
|
|
500
|
-
async readFile(
|
|
258
|
+
async readFile(path: string, options: { encoding?: string } = {}) {
|
|
259
|
+
const session = await this.ensureDefaultSession();
|
|
260
|
+
return session.readFile(path, options);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async listFiles(
|
|
501
264
|
path: string,
|
|
502
|
-
options: {
|
|
265
|
+
options: {
|
|
266
|
+
recursive?: boolean;
|
|
267
|
+
includeHidden?: boolean;
|
|
268
|
+
} = {}
|
|
503
269
|
) {
|
|
504
|
-
|
|
270
|
+
const session = await this.ensureDefaultSession();
|
|
271
|
+
return session.listFiles(path, options);
|
|
505
272
|
}
|
|
506
273
|
|
|
507
274
|
async exposePort(port: number, options: { name?: string; hostname: string }) {
|
|
@@ -509,10 +276,16 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
|
|
|
509
276
|
|
|
510
277
|
// We need the sandbox name to construct preview URLs
|
|
511
278
|
if (!this.sandboxName) {
|
|
512
|
-
throw new Error(
|
|
279
|
+
throw new Error(
|
|
280
|
+
"Sandbox name not available. Ensure sandbox is accessed through getSandbox()"
|
|
281
|
+
);
|
|
513
282
|
}
|
|
514
283
|
|
|
515
|
-
const url = this.constructPreviewUrl(
|
|
284
|
+
const url = this.constructPreviewUrl(
|
|
285
|
+
port,
|
|
286
|
+
this.sandboxName,
|
|
287
|
+
options.hostname
|
|
288
|
+
);
|
|
516
289
|
|
|
517
290
|
return {
|
|
518
291
|
url,
|
|
@@ -523,17 +296,27 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
|
|
|
523
296
|
|
|
524
297
|
async unexposePort(port: number) {
|
|
525
298
|
if (!validatePort(port)) {
|
|
526
|
-
logSecurityEvent(
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
299
|
+
logSecurityEvent(
|
|
300
|
+
"INVALID_PORT_UNEXPOSE",
|
|
301
|
+
{
|
|
302
|
+
port,
|
|
303
|
+
},
|
|
304
|
+
"high"
|
|
305
|
+
);
|
|
306
|
+
throw new SecurityError(
|
|
307
|
+
`Invalid port number: ${port}. Must be between 1024-65535 and not reserved.`
|
|
308
|
+
);
|
|
530
309
|
}
|
|
531
310
|
|
|
532
311
|
await this.client.unexposePort(port);
|
|
533
312
|
|
|
534
|
-
logSecurityEvent(
|
|
535
|
-
|
|
536
|
-
|
|
313
|
+
logSecurityEvent(
|
|
314
|
+
"PORT_UNEXPOSED",
|
|
315
|
+
{
|
|
316
|
+
port,
|
|
317
|
+
},
|
|
318
|
+
"low"
|
|
319
|
+
);
|
|
537
320
|
}
|
|
538
321
|
|
|
539
322
|
async getExposedPorts(hostname: string) {
|
|
@@ -541,10 +324,12 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
|
|
|
541
324
|
|
|
542
325
|
// We need the sandbox name to construct preview URLs
|
|
543
326
|
if (!this.sandboxName) {
|
|
544
|
-
throw new Error(
|
|
327
|
+
throw new Error(
|
|
328
|
+
"Sandbox name not available. Ensure sandbox is accessed through getSandbox()"
|
|
329
|
+
);
|
|
545
330
|
}
|
|
546
331
|
|
|
547
|
-
return response.ports.map(port => ({
|
|
332
|
+
return response.ports.map((port) => ({
|
|
548
333
|
url: this.constructPreviewUrl(port.port, this.sandboxName!, hostname),
|
|
549
334
|
port: port.port,
|
|
550
335
|
name: port.name,
|
|
@@ -552,27 +337,40 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
|
|
|
552
337
|
}));
|
|
553
338
|
}
|
|
554
339
|
|
|
555
|
-
|
|
556
|
-
|
|
340
|
+
private constructPreviewUrl(
|
|
341
|
+
port: number,
|
|
342
|
+
sandboxId: string,
|
|
343
|
+
hostname: string
|
|
344
|
+
): string {
|
|
557
345
|
if (!validatePort(port)) {
|
|
558
|
-
logSecurityEvent(
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
346
|
+
logSecurityEvent(
|
|
347
|
+
"INVALID_PORT_REJECTED",
|
|
348
|
+
{
|
|
349
|
+
port,
|
|
350
|
+
sandboxId,
|
|
351
|
+
hostname,
|
|
352
|
+
},
|
|
353
|
+
"high"
|
|
354
|
+
);
|
|
355
|
+
throw new SecurityError(
|
|
356
|
+
`Invalid port number: ${port}. Must be between 1024-65535 and not reserved.`
|
|
357
|
+
);
|
|
564
358
|
}
|
|
565
359
|
|
|
566
360
|
let sanitizedSandboxId: string;
|
|
567
361
|
try {
|
|
568
362
|
sanitizedSandboxId = sanitizeSandboxId(sandboxId);
|
|
569
363
|
} catch (error) {
|
|
570
|
-
logSecurityEvent(
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
364
|
+
logSecurityEvent(
|
|
365
|
+
"INVALID_SANDBOX_ID_REJECTED",
|
|
366
|
+
{
|
|
367
|
+
sandboxId,
|
|
368
|
+
port,
|
|
369
|
+
hostname,
|
|
370
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
371
|
+
},
|
|
372
|
+
"high"
|
|
373
|
+
);
|
|
576
374
|
throw error;
|
|
577
375
|
}
|
|
578
376
|
|
|
@@ -580,8 +378,8 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
|
|
|
580
378
|
|
|
581
379
|
if (isLocalhost) {
|
|
582
380
|
// Unified subdomain approach for localhost (RFC 6761)
|
|
583
|
-
const [host, portStr] = hostname.split(
|
|
584
|
-
const mainPort = portStr ||
|
|
381
|
+
const [host, portStr] = hostname.split(":");
|
|
382
|
+
const mainPort = portStr || "80";
|
|
585
383
|
|
|
586
384
|
// Use URL constructor for safe URL building
|
|
587
385
|
try {
|
|
@@ -592,23 +390,35 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
|
|
|
592
390
|
|
|
593
391
|
const finalUrl = baseUrl.toString();
|
|
594
392
|
|
|
595
|
-
logSecurityEvent(
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
393
|
+
logSecurityEvent(
|
|
394
|
+
"PREVIEW_URL_CONSTRUCTED",
|
|
395
|
+
{
|
|
396
|
+
port,
|
|
397
|
+
sandboxId: sanitizedSandboxId,
|
|
398
|
+
hostname,
|
|
399
|
+
resultUrl: finalUrl,
|
|
400
|
+
environment: "localhost",
|
|
401
|
+
},
|
|
402
|
+
"low"
|
|
403
|
+
);
|
|
602
404
|
|
|
603
405
|
return finalUrl;
|
|
604
406
|
} catch (error) {
|
|
605
|
-
logSecurityEvent(
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
407
|
+
logSecurityEvent(
|
|
408
|
+
"URL_CONSTRUCTION_FAILED",
|
|
409
|
+
{
|
|
410
|
+
port,
|
|
411
|
+
sandboxId: sanitizedSandboxId,
|
|
412
|
+
hostname,
|
|
413
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
414
|
+
},
|
|
415
|
+
"high"
|
|
416
|
+
);
|
|
417
|
+
throw new SecurityError(
|
|
418
|
+
`Failed to construct preview URL: ${
|
|
419
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
420
|
+
}`
|
|
421
|
+
);
|
|
612
422
|
}
|
|
613
423
|
}
|
|
614
424
|
|
|
@@ -624,23 +434,310 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
|
|
|
624
434
|
|
|
625
435
|
const finalUrl = baseUrl.toString();
|
|
626
436
|
|
|
627
|
-
logSecurityEvent(
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
437
|
+
logSecurityEvent(
|
|
438
|
+
"PREVIEW_URL_CONSTRUCTED",
|
|
439
|
+
{
|
|
440
|
+
port,
|
|
441
|
+
sandboxId: sanitizedSandboxId,
|
|
442
|
+
hostname,
|
|
443
|
+
resultUrl: finalUrl,
|
|
444
|
+
environment: "production",
|
|
445
|
+
},
|
|
446
|
+
"low"
|
|
447
|
+
);
|
|
634
448
|
|
|
635
449
|
return finalUrl;
|
|
636
450
|
} catch (error) {
|
|
637
|
-
logSecurityEvent(
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
451
|
+
logSecurityEvent(
|
|
452
|
+
"URL_CONSTRUCTION_FAILED",
|
|
453
|
+
{
|
|
454
|
+
port,
|
|
455
|
+
sandboxId: sanitizedSandboxId,
|
|
456
|
+
hostname,
|
|
457
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
458
|
+
},
|
|
459
|
+
"high"
|
|
460
|
+
);
|
|
461
|
+
throw new SecurityError(
|
|
462
|
+
`Failed to construct preview URL: ${
|
|
463
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
464
|
+
}`
|
|
465
|
+
);
|
|
644
466
|
}
|
|
645
467
|
}
|
|
468
|
+
|
|
469
|
+
// Code Interpreter Methods
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Create a new code execution context
|
|
473
|
+
*/
|
|
474
|
+
async createCodeContext(
|
|
475
|
+
options?: CreateContextOptions
|
|
476
|
+
): Promise<CodeContext> {
|
|
477
|
+
return this.codeInterpreter.createCodeContext(options);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Run code with streaming callbacks
|
|
482
|
+
*/
|
|
483
|
+
async runCode(
|
|
484
|
+
code: string,
|
|
485
|
+
options?: RunCodeOptions
|
|
486
|
+
): Promise<ExecutionResult> {
|
|
487
|
+
const execution = await this.codeInterpreter.runCode(code, options);
|
|
488
|
+
// Convert to plain object for RPC serialization
|
|
489
|
+
return execution.toJSON();
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Run code and return a streaming response
|
|
494
|
+
*/
|
|
495
|
+
async runCodeStream(
|
|
496
|
+
code: string,
|
|
497
|
+
options?: RunCodeOptions
|
|
498
|
+
): Promise<ReadableStream> {
|
|
499
|
+
return this.codeInterpreter.runCodeStream(code, options);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* List all code contexts
|
|
504
|
+
*/
|
|
505
|
+
async listCodeContexts(): Promise<CodeContext[]> {
|
|
506
|
+
return this.codeInterpreter.listCodeContexts();
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Delete a code context
|
|
511
|
+
*/
|
|
512
|
+
async deleteCodeContext(contextId: string): Promise<void> {
|
|
513
|
+
return this.codeInterpreter.deleteCodeContext(contextId);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// ============================================================================
|
|
517
|
+
// Session Management (Simple Isolation)
|
|
518
|
+
// ============================================================================
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Create a new execution session with isolation
|
|
522
|
+
* Returns a session object with exec() method
|
|
523
|
+
*/
|
|
524
|
+
|
|
525
|
+
async createSession(options: {
|
|
526
|
+
id?: string;
|
|
527
|
+
env?: Record<string, string>;
|
|
528
|
+
cwd?: string;
|
|
529
|
+
isolation?: boolean;
|
|
530
|
+
}): Promise<ExecutionSession> {
|
|
531
|
+
const sessionId = options.id || `session-${Date.now()}`;
|
|
532
|
+
|
|
533
|
+
await this.client.createSession({
|
|
534
|
+
id: sessionId,
|
|
535
|
+
env: options.env,
|
|
536
|
+
cwd: options.cwd,
|
|
537
|
+
isolation: options.isolation
|
|
538
|
+
});
|
|
539
|
+
// Return comprehensive ExecutionSession object that implements all ISandbox methods
|
|
540
|
+
return {
|
|
541
|
+
id: sessionId,
|
|
542
|
+
|
|
543
|
+
// Command execution - clean method names
|
|
544
|
+
exec: async (command: string, options?: ExecOptions) => {
|
|
545
|
+
const result = await this.client.exec(sessionId, command);
|
|
546
|
+
return {
|
|
547
|
+
...result,
|
|
548
|
+
command,
|
|
549
|
+
duration: 0,
|
|
550
|
+
timestamp: new Date().toISOString()
|
|
551
|
+
};
|
|
552
|
+
},
|
|
553
|
+
|
|
554
|
+
execStream: async (command: string, options?: StreamOptions) => {
|
|
555
|
+
return await this.client.execStream(sessionId, command);
|
|
556
|
+
},
|
|
557
|
+
|
|
558
|
+
// Process management - route to session-aware methods
|
|
559
|
+
startProcess: async (command: string, options?: ProcessOptions) => {
|
|
560
|
+
// Use session-specific process management
|
|
561
|
+
const response = await this.client.startProcess(command, sessionId, {
|
|
562
|
+
processId: options?.processId,
|
|
563
|
+
timeout: options?.timeout,
|
|
564
|
+
env: options?.env,
|
|
565
|
+
cwd: options?.cwd,
|
|
566
|
+
encoding: options?.encoding,
|
|
567
|
+
autoCleanup: options?.autoCleanup,
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
// Convert response to Process object with bound methods
|
|
571
|
+
const process = response.process;
|
|
572
|
+
return {
|
|
573
|
+
id: process.id,
|
|
574
|
+
pid: process.pid,
|
|
575
|
+
command: process.command,
|
|
576
|
+
status: process.status as ProcessStatus,
|
|
577
|
+
startTime: new Date(process.startTime),
|
|
578
|
+
endTime: process.endTime ? new Date(process.endTime) : undefined,
|
|
579
|
+
exitCode: process.exitCode ?? undefined,
|
|
580
|
+
kill: async (signal?: string) => {
|
|
581
|
+
await this.client.killProcess(process.id);
|
|
582
|
+
},
|
|
583
|
+
getStatus: async () => {
|
|
584
|
+
const resp = await this.client.getProcess(process.id);
|
|
585
|
+
return resp.process?.status as ProcessStatus || "error";
|
|
586
|
+
},
|
|
587
|
+
getLogs: async () => {
|
|
588
|
+
return await this.client.getProcessLogs(process.id);
|
|
589
|
+
},
|
|
590
|
+
};
|
|
591
|
+
},
|
|
592
|
+
|
|
593
|
+
listProcesses: async () => {
|
|
594
|
+
// Get processes for this specific session
|
|
595
|
+
const response = await this.client.listProcesses(sessionId);
|
|
596
|
+
|
|
597
|
+
// Convert to Process objects with bound methods
|
|
598
|
+
return response.processes.map(p => ({
|
|
599
|
+
id: p.id,
|
|
600
|
+
pid: p.pid,
|
|
601
|
+
command: p.command,
|
|
602
|
+
status: p.status as ProcessStatus,
|
|
603
|
+
startTime: new Date(p.startTime),
|
|
604
|
+
endTime: p.endTime ? new Date(p.endTime) : undefined,
|
|
605
|
+
exitCode: p.exitCode ?? undefined,
|
|
606
|
+
kill: async (signal?: string) => {
|
|
607
|
+
await this.client.killProcess(p.id);
|
|
608
|
+
},
|
|
609
|
+
getStatus: async () => {
|
|
610
|
+
const processResp = await this.client.getProcess(p.id);
|
|
611
|
+
return processResp.process?.status as ProcessStatus || "error";
|
|
612
|
+
},
|
|
613
|
+
getLogs: async () => {
|
|
614
|
+
return this.client.getProcessLogs(p.id);
|
|
615
|
+
},
|
|
616
|
+
}));
|
|
617
|
+
},
|
|
618
|
+
|
|
619
|
+
getProcess: async (id: string) => {
|
|
620
|
+
const response = await this.client.getProcess(id);
|
|
621
|
+
if (!response.process) return null;
|
|
622
|
+
|
|
623
|
+
const p = response.process;
|
|
624
|
+
return {
|
|
625
|
+
id: p.id,
|
|
626
|
+
pid: p.pid,
|
|
627
|
+
command: p.command,
|
|
628
|
+
status: p.status as ProcessStatus,
|
|
629
|
+
startTime: new Date(p.startTime),
|
|
630
|
+
endTime: p.endTime ? new Date(p.endTime) : undefined,
|
|
631
|
+
exitCode: p.exitCode ?? undefined,
|
|
632
|
+
kill: async (signal?: string) => {
|
|
633
|
+
await this.client.killProcess(p.id);
|
|
634
|
+
},
|
|
635
|
+
getStatus: async () => {
|
|
636
|
+
const processResp = await this.client.getProcess(p.id);
|
|
637
|
+
return processResp.process?.status as ProcessStatus || "error";
|
|
638
|
+
},
|
|
639
|
+
getLogs: async () => {
|
|
640
|
+
return this.client.getProcessLogs(p.id);
|
|
641
|
+
},
|
|
642
|
+
};
|
|
643
|
+
},
|
|
644
|
+
|
|
645
|
+
killProcess: async (id: string, signal?: string) => {
|
|
646
|
+
await this.client.killProcess(id);
|
|
647
|
+
},
|
|
648
|
+
|
|
649
|
+
killAllProcesses: async () => {
|
|
650
|
+
// Kill all processes for this specific session
|
|
651
|
+
const response = await this.client.killAllProcesses(sessionId);
|
|
652
|
+
return response.killedCount;
|
|
653
|
+
},
|
|
654
|
+
|
|
655
|
+
streamProcessLogs: async (processId: string, options?: { signal?: AbortSignal }) => {
|
|
656
|
+
return await this.client.streamProcessLogs(processId, options);
|
|
657
|
+
},
|
|
658
|
+
|
|
659
|
+
getProcessLogs: async (id: string) => {
|
|
660
|
+
return await this.client.getProcessLogs(id);
|
|
661
|
+
},
|
|
662
|
+
|
|
663
|
+
cleanupCompletedProcesses: async () => {
|
|
664
|
+
// This would need a new endpoint to cleanup processes for a specific session
|
|
665
|
+
// For now, return 0 as no cleanup is performed
|
|
666
|
+
return 0;
|
|
667
|
+
},
|
|
668
|
+
|
|
669
|
+
// File operations - clean method names (no "InSession" suffix)
|
|
670
|
+
writeFile: async (path: string, content: string, options?: { encoding?: string }) => {
|
|
671
|
+
return await this.client.writeFile(path, content, options?.encoding, sessionId);
|
|
672
|
+
},
|
|
673
|
+
|
|
674
|
+
readFile: async (path: string, options?: { encoding?: string }) => {
|
|
675
|
+
return await this.client.readFile(path, options?.encoding, sessionId);
|
|
676
|
+
},
|
|
677
|
+
|
|
678
|
+
mkdir: async (path: string, options?: { recursive?: boolean }) => {
|
|
679
|
+
return await this.client.mkdir(path, options?.recursive, sessionId);
|
|
680
|
+
},
|
|
681
|
+
|
|
682
|
+
deleteFile: async (path: string) => {
|
|
683
|
+
return await this.client.deleteFile(path, sessionId);
|
|
684
|
+
},
|
|
685
|
+
|
|
686
|
+
renameFile: async (oldPath: string, newPath: string) => {
|
|
687
|
+
return await this.client.renameFile(oldPath, newPath, sessionId);
|
|
688
|
+
},
|
|
689
|
+
|
|
690
|
+
moveFile: async (sourcePath: string, destinationPath: string) => {
|
|
691
|
+
return await this.client.moveFile(sourcePath, destinationPath, sessionId);
|
|
692
|
+
},
|
|
693
|
+
|
|
694
|
+
listFiles: async (path: string, options?: { recursive?: boolean; includeHidden?: boolean }) => {
|
|
695
|
+
return await this.client.listFiles(path, sessionId, options);
|
|
696
|
+
},
|
|
697
|
+
|
|
698
|
+
gitCheckout: async (repoUrl: string, options?: { branch?: string; targetDir?: string }) => {
|
|
699
|
+
return await this.client.gitCheckout(repoUrl, sessionId, options?.branch, options?.targetDir);
|
|
700
|
+
},
|
|
701
|
+
|
|
702
|
+
// Port management
|
|
703
|
+
exposePort: async (port: number, options: { name?: string; hostname: string }) => {
|
|
704
|
+
return await this.exposePort(port, options);
|
|
705
|
+
},
|
|
706
|
+
|
|
707
|
+
unexposePort: async (port: number) => {
|
|
708
|
+
return await this.unexposePort(port);
|
|
709
|
+
},
|
|
710
|
+
|
|
711
|
+
getExposedPorts: async (hostname: string) => {
|
|
712
|
+
return await this.getExposedPorts(hostname);
|
|
713
|
+
},
|
|
714
|
+
|
|
715
|
+
// Environment management
|
|
716
|
+
setEnvVars: async (envVars: Record<string, string>) => {
|
|
717
|
+
// TODO: Implement session-specific environment updates
|
|
718
|
+
console.log(`[Session ${sessionId}] Environment variables update not yet implemented`);
|
|
719
|
+
},
|
|
720
|
+
|
|
721
|
+
// Code Interpreter API
|
|
722
|
+
createCodeContext: async (options?: any) => {
|
|
723
|
+
return await this.createCodeContext(options);
|
|
724
|
+
},
|
|
725
|
+
|
|
726
|
+
runCode: async (code: string, options?: any) => {
|
|
727
|
+
return await this.runCode(code, options);
|
|
728
|
+
},
|
|
729
|
+
|
|
730
|
+
runCodeStream: async (code: string, options?: any) => {
|
|
731
|
+
return await this.runCodeStream(code, options);
|
|
732
|
+
},
|
|
733
|
+
|
|
734
|
+
listCodeContexts: async () => {
|
|
735
|
+
return await this.listCodeContexts();
|
|
736
|
+
},
|
|
737
|
+
|
|
738
|
+
deleteCodeContext: async (contextId: string) => {
|
|
739
|
+
return await this.deleteCodeContext(contextId);
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
}
|
|
646
743
|
}
|