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