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