@blaxel/core 0.2.49-dev.210 → 0.2.49-dev.212
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/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/common/settings.js +2 -2
- package/dist/cjs/sandbox/codegen/codegen-ws.js +30 -0
- package/dist/cjs/sandbox/filesystem/filesystem-ws.js +106 -0
- package/dist/cjs/sandbox/filesystem/filesystem.js +4 -15
- package/dist/cjs/sandbox/network/network-ws.js +12 -0
- package/dist/cjs/sandbox/process/process-ws.js +139 -0
- package/dist/cjs/sandbox/sandbox.js +67 -10
- package/dist/cjs/sandbox/websocket/client.js +275 -0
- package/dist/cjs/sandbox/websocket/index.js +17 -0
- package/dist/cjs/types/sandbox/codegen/codegen-ws.d.ts +10 -0
- package/dist/cjs/types/sandbox/filesystem/filesystem-ws.d.ts +35 -0
- package/dist/cjs/types/sandbox/network/network-ws.d.ts +7 -0
- package/dist/cjs/types/sandbox/process/process-ws.d.ts +27 -0
- package/dist/cjs/types/sandbox/sandbox.d.ts +12 -6
- package/dist/cjs/types/sandbox/types.d.ts +3 -0
- package/dist/cjs/types/sandbox/websocket/client.d.ts +49 -0
- package/dist/cjs/types/sandbox/websocket/index.d.ts +1 -0
- package/dist/cjs-browser/.tsbuildinfo +1 -1
- package/dist/cjs-browser/common/settings.js +2 -2
- package/dist/cjs-browser/sandbox/codegen/codegen-ws.js +30 -0
- package/dist/cjs-browser/sandbox/filesystem/filesystem-ws.js +106 -0
- package/dist/cjs-browser/sandbox/filesystem/filesystem.js +4 -15
- package/dist/cjs-browser/sandbox/network/network-ws.js +12 -0
- package/dist/cjs-browser/sandbox/process/process-ws.js +139 -0
- package/dist/cjs-browser/sandbox/sandbox.js +67 -10
- package/dist/cjs-browser/sandbox/websocket/client.js +275 -0
- package/dist/cjs-browser/sandbox/websocket/index.js +17 -0
- package/dist/cjs-browser/types/sandbox/codegen/codegen-ws.d.ts +10 -0
- package/dist/cjs-browser/types/sandbox/filesystem/filesystem-ws.d.ts +35 -0
- package/dist/cjs-browser/types/sandbox/network/network-ws.d.ts +7 -0
- package/dist/cjs-browser/types/sandbox/process/process-ws.d.ts +27 -0
- package/dist/cjs-browser/types/sandbox/sandbox.d.ts +12 -6
- package/dist/cjs-browser/types/sandbox/types.d.ts +3 -0
- package/dist/cjs-browser/types/sandbox/websocket/client.d.ts +49 -0
- package/dist/cjs-browser/types/sandbox/websocket/index.d.ts +1 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/common/settings.js +2 -2
- package/dist/esm/sandbox/codegen/codegen-ws.js +26 -0
- package/dist/esm/sandbox/filesystem/filesystem-ws.js +102 -0
- package/dist/esm/sandbox/filesystem/filesystem.js +4 -15
- package/dist/esm/sandbox/network/network-ws.js +8 -0
- package/dist/esm/sandbox/process/process-ws.js +135 -0
- package/dist/esm/sandbox/sandbox.js +67 -10
- package/dist/esm/sandbox/websocket/client.js +271 -0
- package/dist/esm/sandbox/websocket/index.js +1 -0
- package/dist/esm-browser/.tsbuildinfo +1 -1
- package/dist/esm-browser/common/settings.js +2 -2
- package/dist/esm-browser/sandbox/codegen/codegen-ws.js +26 -0
- package/dist/esm-browser/sandbox/filesystem/filesystem-ws.js +102 -0
- package/dist/esm-browser/sandbox/filesystem/filesystem.js +4 -15
- package/dist/esm-browser/sandbox/network/network-ws.js +8 -0
- package/dist/esm-browser/sandbox/process/process-ws.js +135 -0
- package/dist/esm-browser/sandbox/sandbox.js +67 -10
- package/dist/esm-browser/sandbox/websocket/client.js +271 -0
- package/dist/esm-browser/sandbox/websocket/index.js +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { SandboxAction } from "../action.js";
|
|
2
|
+
import { SandboxFileSystem } from "./filesystem.js";
|
|
3
|
+
export class SandboxFileSystemWebSocket extends SandboxAction {
|
|
4
|
+
process;
|
|
5
|
+
wsClient;
|
|
6
|
+
httpClient;
|
|
7
|
+
constructor(sandbox, process, wsClient) {
|
|
8
|
+
super(sandbox);
|
|
9
|
+
this.process = process;
|
|
10
|
+
this.wsClient = wsClient;
|
|
11
|
+
// Create HTTP client for fallback operations
|
|
12
|
+
this.httpClient = new SandboxFileSystem(sandbox, process);
|
|
13
|
+
}
|
|
14
|
+
async mkdir(path, permissions = "0755") {
|
|
15
|
+
path = this.formatPath(path);
|
|
16
|
+
const data = await this.wsClient.send("filesystem:create", {
|
|
17
|
+
path,
|
|
18
|
+
isDirectory: true,
|
|
19
|
+
permissions,
|
|
20
|
+
});
|
|
21
|
+
return data;
|
|
22
|
+
}
|
|
23
|
+
async write(path, content) {
|
|
24
|
+
path = this.formatPath(path);
|
|
25
|
+
const data = await this.wsClient.send("filesystem:create", {
|
|
26
|
+
path,
|
|
27
|
+
content,
|
|
28
|
+
isDirectory: false,
|
|
29
|
+
});
|
|
30
|
+
return data;
|
|
31
|
+
}
|
|
32
|
+
async writeBinary(path, content) {
|
|
33
|
+
return this.httpClient.writeBinary(path, content);
|
|
34
|
+
}
|
|
35
|
+
async writeTree(files, destinationPath = null) {
|
|
36
|
+
const path = this.formatPath(destinationPath ?? "");
|
|
37
|
+
const filesMap = files.reduce((acc, file) => {
|
|
38
|
+
acc[file.path] = file.content;
|
|
39
|
+
return acc;
|
|
40
|
+
}, {});
|
|
41
|
+
const data = await this.wsClient.send("filesystem:tree:create", {
|
|
42
|
+
path,
|
|
43
|
+
files: filesMap,
|
|
44
|
+
});
|
|
45
|
+
return data;
|
|
46
|
+
}
|
|
47
|
+
async read(path) {
|
|
48
|
+
path = this.formatPath(path);
|
|
49
|
+
const data = await this.wsClient.send("filesystem:get", { path });
|
|
50
|
+
return data.content;
|
|
51
|
+
}
|
|
52
|
+
async readBinary(path) {
|
|
53
|
+
// Binary downloads are better suited for HTTP
|
|
54
|
+
// Fall back to HTTP client for binary operations
|
|
55
|
+
return this.httpClient.readBinary(path);
|
|
56
|
+
}
|
|
57
|
+
async download(src, destinationPath, options = {}) {
|
|
58
|
+
// File downloads are better suited for HTTP
|
|
59
|
+
// Fall back to HTTP client
|
|
60
|
+
return this.httpClient.download(src, destinationPath, options);
|
|
61
|
+
}
|
|
62
|
+
async rm(path, recursive = false) {
|
|
63
|
+
path = this.formatPath(path);
|
|
64
|
+
const data = await this.wsClient.send("filesystem:delete", {
|
|
65
|
+
path,
|
|
66
|
+
recursive,
|
|
67
|
+
});
|
|
68
|
+
return data;
|
|
69
|
+
}
|
|
70
|
+
async ls(path) {
|
|
71
|
+
path = this.formatPath(path);
|
|
72
|
+
const data = await this.wsClient.send("filesystem:get", { path });
|
|
73
|
+
return data;
|
|
74
|
+
}
|
|
75
|
+
async cp(source, destination, { maxWait = 180000 } = {}) {
|
|
76
|
+
// Copy operation is typically done via process execution
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
78
|
+
let process = await this.process.exec({
|
|
79
|
+
command: `cp -r ${source} ${destination}`,
|
|
80
|
+
});
|
|
81
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
|
|
82
|
+
process = await this.process.wait(process.pid, { maxWait, interval: 100 });
|
|
83
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
84
|
+
if (process.status === "failed") {
|
|
85
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
86
|
+
throw new Error(`Could not copy ${source} to ${destination} cause: ${process.logs}`);
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
message: "Files copied",
|
|
90
|
+
source,
|
|
91
|
+
destination,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
watch(path, callback, options) {
|
|
95
|
+
// File watching uses HTTP streaming which is already optimized
|
|
96
|
+
// Fall back to HTTP client
|
|
97
|
+
return this.httpClient.watch(path, callback, options);
|
|
98
|
+
}
|
|
99
|
+
formatPath(path) {
|
|
100
|
+
return path;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -5,7 +5,7 @@ import { deleteFilesystemByPath, getFilesystemByPath, getWatchFilesystemByPath,
|
|
|
5
5
|
// Multipart upload constants
|
|
6
6
|
const MULTIPART_THRESHOLD = 5 * 1024 * 1024; // 5MB
|
|
7
7
|
const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB per part
|
|
8
|
-
const MAX_PARALLEL_UPLOADS =
|
|
8
|
+
const MAX_PARALLEL_UPLOADS = 20; // Number of parallel part uploads
|
|
9
9
|
export class SandboxFileSystem extends SandboxAction {
|
|
10
10
|
process;
|
|
11
11
|
constructor(sandbox, process) {
|
|
@@ -301,7 +301,6 @@ export class SandboxFileSystem extends SandboxAction {
|
|
|
301
301
|
// Multipart upload helper methods
|
|
302
302
|
async initiateMultipartUpload(path, permissions = "0644") {
|
|
303
303
|
path = this.formatPath(path);
|
|
304
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
|
|
305
304
|
const { data } = await postFilesystemMultipartInitiateByPath({
|
|
306
305
|
path: { path },
|
|
307
306
|
body: { permissions },
|
|
@@ -312,7 +311,6 @@ export class SandboxFileSystem extends SandboxAction {
|
|
|
312
311
|
return data;
|
|
313
312
|
}
|
|
314
313
|
async uploadPart(uploadId, partNumber, fileBlob) {
|
|
315
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
|
|
316
314
|
const { data } = await putFilesystemMultipartByUploadIdPart({
|
|
317
315
|
path: { uploadId },
|
|
318
316
|
query: { partNumber },
|
|
@@ -324,7 +322,6 @@ export class SandboxFileSystem extends SandboxAction {
|
|
|
324
322
|
return data;
|
|
325
323
|
}
|
|
326
324
|
async completeMultipartUpload(uploadId, parts) {
|
|
327
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
|
|
328
325
|
const { data } = await postFilesystemMultipartByUploadIdComplete({
|
|
329
326
|
path: { uploadId },
|
|
330
327
|
body: { parts },
|
|
@@ -335,19 +332,17 @@ export class SandboxFileSystem extends SandboxAction {
|
|
|
335
332
|
return data;
|
|
336
333
|
}
|
|
337
334
|
async abortMultipartUpload(uploadId) {
|
|
338
|
-
|
|
339
|
-
await deleteFilesystemMultipartByUploadIdAbort({
|
|
335
|
+
const { data } = await deleteFilesystemMultipartByUploadIdAbort({
|
|
340
336
|
path: { uploadId },
|
|
341
337
|
baseUrl: this.url,
|
|
342
338
|
client: this.client,
|
|
343
339
|
throwOnError: true,
|
|
344
340
|
});
|
|
341
|
+
return data;
|
|
345
342
|
}
|
|
346
343
|
async uploadWithMultipart(path, blob, permissions = "0644") {
|
|
347
344
|
// Initiate multipart upload
|
|
348
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
349
345
|
const initResponse = await this.initiateMultipartUpload(path, permissions);
|
|
350
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
|
351
346
|
const uploadId = initResponse.uploadId;
|
|
352
347
|
if (!uploadId) {
|
|
353
348
|
throw new Error("Failed to get upload ID from initiate response");
|
|
@@ -364,26 +359,20 @@ export class SandboxFileSystem extends SandboxAction {
|
|
|
364
359
|
const start = (partNumber - 1) * CHUNK_SIZE;
|
|
365
360
|
const end = Math.min(start + CHUNK_SIZE, size);
|
|
366
361
|
const chunk = blob.slice(start, end);
|
|
367
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
368
362
|
batch.push(this.uploadPart(uploadId, partNumber, chunk));
|
|
369
363
|
}
|
|
370
364
|
// Wait for batch to complete
|
|
371
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
372
365
|
const batchResults = await Promise.all(batch);
|
|
373
|
-
|
|
374
|
-
parts.push(...batchResults.map((r) => ({ partNumber: r.partNumber, etag: r.etag })));
|
|
366
|
+
parts.push(...batchResults);
|
|
375
367
|
}
|
|
376
368
|
// Sort parts by partNumber to ensure correct order
|
|
377
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
378
369
|
parts.sort((a, b) => (a.partNumber ?? 0) - (b.partNumber ?? 0));
|
|
379
370
|
// Complete the upload
|
|
380
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
381
371
|
return await this.completeMultipartUpload(uploadId, parts);
|
|
382
372
|
}
|
|
383
373
|
catch (error) {
|
|
384
374
|
// Abort the upload on failure
|
|
385
375
|
try {
|
|
386
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
387
376
|
await this.abortMultipartUpload(uploadId);
|
|
388
377
|
}
|
|
389
378
|
catch (abortError) {
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { SandboxAction } from "../action.js";
|
|
2
|
+
import { SandboxProcess } from "./process.js";
|
|
3
|
+
export class SandboxProcessWebSocket extends SandboxAction {
|
|
4
|
+
wsClient;
|
|
5
|
+
httpClient;
|
|
6
|
+
constructor(sandbox, wsClient) {
|
|
7
|
+
super(sandbox);
|
|
8
|
+
this.wsClient = wsClient;
|
|
9
|
+
// Create HTTP client for fallback operations
|
|
10
|
+
this.httpClient = new SandboxProcess(sandbox);
|
|
11
|
+
}
|
|
12
|
+
streamLogs(identifier, options) {
|
|
13
|
+
const streamId = this.wsClient.sendStream("process:logs:stream:start", { identifier }, (data) => {
|
|
14
|
+
// Handle streaming log data
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
16
|
+
if (data && data.log) {
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
18
|
+
const log = String(data.log);
|
|
19
|
+
// Parse log format: "stdout:" or "stderr:" prefix
|
|
20
|
+
if (log.startsWith('stdout:')) {
|
|
21
|
+
const stdout = log.slice(7);
|
|
22
|
+
options.onStdout?.(stdout);
|
|
23
|
+
options.onLog?.(stdout);
|
|
24
|
+
}
|
|
25
|
+
else if (log.startsWith('stderr:')) {
|
|
26
|
+
const stderr = log.slice(7);
|
|
27
|
+
options.onStderr?.(stderr);
|
|
28
|
+
options.onLog?.(stderr);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
options.onLog?.(log);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}, () => {
|
|
35
|
+
// Stream ended
|
|
36
|
+
});
|
|
37
|
+
return {
|
|
38
|
+
close: () => this.wsClient.cancelStream(streamId),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
async exec(process) {
|
|
42
|
+
let onLog;
|
|
43
|
+
if ('onLog' in process && process.onLog) {
|
|
44
|
+
onLog = process.onLog;
|
|
45
|
+
delete process.onLog;
|
|
46
|
+
}
|
|
47
|
+
// Store original wait_for_completion setting
|
|
48
|
+
const shouldWaitForCompletion = process.waitForCompletion;
|
|
49
|
+
// Always start process without wait_for_completion to avoid server-side blocking
|
|
50
|
+
if (shouldWaitForCompletion && onLog) {
|
|
51
|
+
process.waitForCompletion = false;
|
|
52
|
+
}
|
|
53
|
+
const data = await this.wsClient.send("process:execute", process);
|
|
54
|
+
let result = data;
|
|
55
|
+
// Handle wait_for_completion with parallel log streaming
|
|
56
|
+
if (shouldWaitForCompletion && onLog) {
|
|
57
|
+
const streamControl = this.streamLogs(result.pid, { onLog });
|
|
58
|
+
try {
|
|
59
|
+
// Wait for process completion
|
|
60
|
+
result = await this.wait(result.pid, { interval: 500, maxWait: 1000 * 60 * 60 });
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
// Clean up log streaming
|
|
64
|
+
if (streamControl) {
|
|
65
|
+
streamControl.close();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
// For non-blocking execution, set up log streaming immediately if requested
|
|
71
|
+
if (onLog) {
|
|
72
|
+
const streamControl = this.streamLogs(result.pid, { onLog });
|
|
73
|
+
return {
|
|
74
|
+
...result,
|
|
75
|
+
close() {
|
|
76
|
+
if (streamControl) {
|
|
77
|
+
streamControl.close();
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return { ...result, close: () => { } };
|
|
84
|
+
}
|
|
85
|
+
async wait(identifier, { maxWait = 60000, interval = 1000 } = {}) {
|
|
86
|
+
const startTime = Date.now();
|
|
87
|
+
let status = "running";
|
|
88
|
+
let data = await this.get(identifier);
|
|
89
|
+
while (status === "running") {
|
|
90
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
91
|
+
try {
|
|
92
|
+
data = await this.get(identifier);
|
|
93
|
+
status = data.status ?? "running";
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
if (Date.now() - startTime > maxWait) {
|
|
99
|
+
throw new Error("Process did not finish in time");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return data;
|
|
103
|
+
}
|
|
104
|
+
async get(identifier) {
|
|
105
|
+
const data = await this.wsClient.send("process:get", { identifier });
|
|
106
|
+
return data;
|
|
107
|
+
}
|
|
108
|
+
async list() {
|
|
109
|
+
const data = await this.wsClient.send("process:list", {});
|
|
110
|
+
return data;
|
|
111
|
+
}
|
|
112
|
+
async stop(identifier) {
|
|
113
|
+
const data = await this.wsClient.send("process:stop", { identifier });
|
|
114
|
+
return data;
|
|
115
|
+
}
|
|
116
|
+
async kill(identifier) {
|
|
117
|
+
const data = await this.wsClient.send("process:kill", { identifier });
|
|
118
|
+
return data;
|
|
119
|
+
}
|
|
120
|
+
async logs(identifier, type = "all") {
|
|
121
|
+
const data = await this.wsClient.send("process:logs", {
|
|
122
|
+
identifier,
|
|
123
|
+
});
|
|
124
|
+
if (type === "all") {
|
|
125
|
+
return data.logs || "";
|
|
126
|
+
}
|
|
127
|
+
else if (type === "stdout") {
|
|
128
|
+
return data.stdout || "";
|
|
129
|
+
}
|
|
130
|
+
else if (type === "stderr") {
|
|
131
|
+
return data.stderr || "";
|
|
132
|
+
}
|
|
133
|
+
throw new Error("Unsupported log type");
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -2,11 +2,16 @@ import { v4 as uuidv4 } from "uuid";
|
|
|
2
2
|
import { createSandbox, deleteSandbox, getSandbox, listSandboxes, updateSandbox } from "../client/index.js";
|
|
3
3
|
import { logger } from "../common/logger.js";
|
|
4
4
|
import { SandboxFileSystem } from "./filesystem/index.js";
|
|
5
|
+
import { SandboxFileSystemWebSocket } from "./filesystem/filesystem-ws.js";
|
|
5
6
|
import { SandboxNetwork } from "./network/index.js";
|
|
7
|
+
import { SandboxNetworkWebSocket } from "./network/network-ws.js";
|
|
6
8
|
import { SandboxPreviews } from "./preview.js";
|
|
7
9
|
import { SandboxProcess } from "./process/index.js";
|
|
10
|
+
import { SandboxProcessWebSocket } from "./process/process-ws.js";
|
|
8
11
|
import { SandboxCodegen } from "./codegen/index.js";
|
|
12
|
+
import { SandboxCodegenWebSocket } from "./codegen/codegen-ws.js";
|
|
9
13
|
import { SandboxSessions } from "./session.js";
|
|
14
|
+
import { WebSocketClient } from "./websocket/index.js";
|
|
10
15
|
import { normalizeEnvs, normalizePorts, normalizeVolumes } from "./types.js";
|
|
11
16
|
export class SandboxInstance {
|
|
12
17
|
sandbox;
|
|
@@ -16,14 +21,32 @@ export class SandboxInstance {
|
|
|
16
21
|
previews;
|
|
17
22
|
sessions;
|
|
18
23
|
codegen;
|
|
24
|
+
wsClient;
|
|
19
25
|
constructor(sandbox) {
|
|
20
26
|
this.sandbox = sandbox;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
// If connection type is websocket, initialize WebSocket client and use WebSocket transport layers
|
|
28
|
+
if (sandbox.connectionType === "websocket") {
|
|
29
|
+
const url = sandbox.forceUrl || sandbox.metadata?.url || "";
|
|
30
|
+
this.wsClient = new WebSocketClient({
|
|
31
|
+
url,
|
|
32
|
+
headers: sandbox.headers,
|
|
33
|
+
});
|
|
34
|
+
// Initialize WebSocket-based action handlers
|
|
35
|
+
this.process = new SandboxProcessWebSocket(sandbox, this.wsClient);
|
|
36
|
+
this.fs = new SandboxFileSystemWebSocket(sandbox, this.process, this.wsClient);
|
|
37
|
+
this.network = new SandboxNetworkWebSocket(sandbox, this.wsClient);
|
|
38
|
+
this.codegen = new SandboxCodegenWebSocket(sandbox, this.wsClient);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// Default to HTTP-based action handlers
|
|
42
|
+
this.process = new SandboxProcess(sandbox);
|
|
43
|
+
this.fs = new SandboxFileSystem(sandbox, this.process);
|
|
44
|
+
this.network = new SandboxNetwork(sandbox);
|
|
45
|
+
this.codegen = new SandboxCodegen(sandbox);
|
|
46
|
+
}
|
|
47
|
+
// These are always HTTP-based
|
|
24
48
|
this.previews = new SandboxPreviews(sandbox);
|
|
25
49
|
this.sessions = new SandboxSessions(sandbox);
|
|
26
|
-
this.codegen = new SandboxCodegen(sandbox);
|
|
27
50
|
}
|
|
28
51
|
get metadata() {
|
|
29
52
|
return this.sandbox.metadata;
|
|
@@ -42,10 +65,17 @@ export class SandboxInstance {
|
|
|
42
65
|
logger.warn("⚠️ Warning: sandbox.wait() is deprecated. You don't need to wait for the sandbox to be deployed anymore.");
|
|
43
66
|
return this;
|
|
44
67
|
}
|
|
68
|
+
closeConnection() {
|
|
69
|
+
if (this.wsClient) {
|
|
70
|
+
this.wsClient.close();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
45
73
|
static async create(sandbox, { safe = true } = {}) {
|
|
46
74
|
const defaultName = `sandbox-${uuidv4().replace(/-/g, '').substring(0, 8)}`;
|
|
47
75
|
const defaultImage = `blaxel/base-image:latest`;
|
|
48
76
|
const defaultMemory = 4096;
|
|
77
|
+
// Store connection type if provided
|
|
78
|
+
let connectionType;
|
|
49
79
|
// Handle SandboxCreateConfiguration or simple dict with name/image/memory/ports/envs/volumes keys
|
|
50
80
|
if (!sandbox ||
|
|
51
81
|
'name' in sandbox ||
|
|
@@ -55,7 +85,8 @@ export class SandboxInstance {
|
|
|
55
85
|
'envs' in sandbox ||
|
|
56
86
|
'volumes' in sandbox ||
|
|
57
87
|
'lifecycle' in sandbox ||
|
|
58
|
-
'snapshotEnabled' in sandbox
|
|
88
|
+
'snapshotEnabled' in sandbox ||
|
|
89
|
+
'connectionType' in sandbox) {
|
|
59
90
|
if (!sandbox)
|
|
60
91
|
sandbox = {};
|
|
61
92
|
if (!sandbox.name)
|
|
@@ -64,6 +95,7 @@ export class SandboxInstance {
|
|
|
64
95
|
sandbox.image = defaultImage;
|
|
65
96
|
if (!sandbox.memory)
|
|
66
97
|
sandbox.memory = defaultMemory;
|
|
98
|
+
connectionType = sandbox.connectionType;
|
|
67
99
|
const ports = normalizePorts(sandbox.ports);
|
|
68
100
|
const envs = normalizeEnvs(sandbox.envs);
|
|
69
101
|
const volumes = normalizeVolumes(sandbox.volumes);
|
|
@@ -112,7 +144,16 @@ export class SandboxInstance {
|
|
|
112
144
|
body: sandbox,
|
|
113
145
|
throwOnError: true,
|
|
114
146
|
});
|
|
115
|
-
|
|
147
|
+
// Add connection type to configuration
|
|
148
|
+
const config = {
|
|
149
|
+
...data,
|
|
150
|
+
connectionType: connectionType || "http",
|
|
151
|
+
};
|
|
152
|
+
const instance = new SandboxInstance(config);
|
|
153
|
+
// Connect WebSocket if needed
|
|
154
|
+
if (connectionType === "websocket" && instance.wsClient) {
|
|
155
|
+
await instance.wsClient.connect();
|
|
156
|
+
}
|
|
116
157
|
// TODO remove this part once we have a better way to handle this
|
|
117
158
|
if (safe) {
|
|
118
159
|
try {
|
|
@@ -122,20 +163,34 @@ export class SandboxInstance {
|
|
|
122
163
|
}
|
|
123
164
|
return instance;
|
|
124
165
|
}
|
|
125
|
-
static async get(sandboxName) {
|
|
166
|
+
static async get(sandboxName, connectionType) {
|
|
126
167
|
const { data } = await getSandbox({
|
|
127
168
|
path: {
|
|
128
169
|
sandboxName,
|
|
129
170
|
},
|
|
130
171
|
throwOnError: true,
|
|
131
172
|
});
|
|
132
|
-
|
|
173
|
+
// Add connection type to configuration
|
|
174
|
+
const config = {
|
|
175
|
+
...data,
|
|
176
|
+
connectionType: connectionType || "http",
|
|
177
|
+
};
|
|
178
|
+
const instance = new SandboxInstance(config);
|
|
179
|
+
// Connect WebSocket if needed
|
|
180
|
+
if (connectionType === "websocket" && instance.wsClient) {
|
|
181
|
+
await instance.wsClient.connect();
|
|
182
|
+
}
|
|
183
|
+
return instance;
|
|
133
184
|
}
|
|
134
185
|
static async list() {
|
|
135
186
|
const { data } = await listSandboxes({ throwOnError: true });
|
|
136
187
|
return data.map((sandbox) => new SandboxInstance(sandbox));
|
|
137
188
|
}
|
|
138
|
-
static async delete(sandboxName) {
|
|
189
|
+
static async delete(sandboxName, instance) {
|
|
190
|
+
// Close WebSocket connection if instance is provided
|
|
191
|
+
if (instance && instance.wsClient) {
|
|
192
|
+
instance.closeConnection();
|
|
193
|
+
}
|
|
139
194
|
const { data } = await deleteSandbox({
|
|
140
195
|
path: {
|
|
141
196
|
sandboxName,
|
|
@@ -165,8 +220,10 @@ export class SandboxInstance {
|
|
|
165
220
|
if (!name) {
|
|
166
221
|
throw new Error("Sandbox name is required");
|
|
167
222
|
}
|
|
223
|
+
// Get connection type if specified
|
|
224
|
+
const connectionType = 'connectionType' in sandbox ? sandbox.connectionType : undefined;
|
|
168
225
|
// Get the existing sandbox to check its status
|
|
169
|
-
const sandboxInstance = await SandboxInstance.get(name);
|
|
226
|
+
const sandboxInstance = await SandboxInstance.get(name, connectionType);
|
|
170
227
|
// If the sandbox is TERMINATED, treat it as not existing
|
|
171
228
|
if (sandboxInstance.status === "TERMINATED") {
|
|
172
229
|
// Create a new sandbox - backend will handle cleanup of the terminated one
|