@blaxel/core 0.2.49-preview.112 → 0.2.50-dev.215
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/agents/index.js +2 -2
- package/dist/cjs/authentication/clientcredentials.js +4 -1
- package/dist/cjs/common/internal.js +40 -0
- package/dist/cjs/common/settings.js +5 -2
- package/dist/cjs/jobs/jobs.js +2 -2
- package/dist/cjs/jobs/start.js +32 -1
- package/dist/cjs/sandbox/action.js +1 -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/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 +269 -0
- package/dist/cjs/sandbox/websocket/index.js +17 -0
- package/dist/cjs/tools/mcpTool.js +2 -2
- package/dist/cjs/types/client/types.gen.d.ts +1 -1
- package/dist/cjs/types/common/internal.d.ts +2 -0
- package/dist/cjs/types/common/settings.d.ts +1 -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 +48 -0
- package/dist/cjs/types/sandbox/websocket/index.d.ts +1 -0
- package/dist/cjs-browser/.tsbuildinfo +1 -1
- package/dist/cjs-browser/agents/index.js +2 -2
- package/dist/cjs-browser/authentication/clientcredentials.js +4 -1
- package/dist/cjs-browser/common/internal.js +40 -0
- package/dist/cjs-browser/common/settings.js +5 -2
- package/dist/cjs-browser/jobs/jobs.js +2 -2
- package/dist/cjs-browser/jobs/start.js +32 -1
- package/dist/cjs-browser/sandbox/action.js +1 -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/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 +269 -0
- package/dist/cjs-browser/sandbox/websocket/index.js +17 -0
- package/dist/cjs-browser/tools/mcpTool.js +2 -2
- package/dist/cjs-browser/types/client/types.gen.d.ts +1 -1
- package/dist/cjs-browser/types/common/internal.d.ts +2 -0
- package/dist/cjs-browser/types/common/settings.d.ts +1 -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 +48 -0
- package/dist/cjs-browser/types/sandbox/websocket/index.d.ts +1 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/agents/index.js +3 -3
- package/dist/esm/authentication/clientcredentials.js +4 -1
- package/dist/esm/common/internal.js +38 -0
- package/dist/esm/common/settings.js +5 -2
- package/dist/esm/jobs/jobs.js +3 -3
- package/dist/esm/jobs/start.js +32 -1
- package/dist/esm/sandbox/action.js +2 -3
- package/dist/esm/sandbox/codegen/codegen-ws.js +26 -0
- package/dist/esm/sandbox/filesystem/filesystem-ws.js +102 -0
- 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 +265 -0
- package/dist/esm/sandbox/websocket/index.js +1 -0
- package/dist/esm/tools/mcpTool.js +3 -3
- package/dist/esm-browser/.tsbuildinfo +1 -1
- package/dist/esm-browser/agents/index.js +3 -3
- package/dist/esm-browser/authentication/clientcredentials.js +4 -1
- package/dist/esm-browser/common/internal.js +38 -0
- package/dist/esm-browser/common/settings.js +5 -2
- package/dist/esm-browser/jobs/jobs.js +3 -3
- package/dist/esm-browser/jobs/start.js +32 -1
- package/dist/esm-browser/sandbox/action.js +2 -3
- 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/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 +265 -0
- package/dist/esm-browser/sandbox/websocket/index.js +1 -0
- package/dist/esm-browser/tools/mcpTool.js +3 -3
- package/package.json +2 -2
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { v4 as uuidv4 } from "uuid";
|
|
2
|
+
import { getWebSocket } from "../../common/node.js";
|
|
3
|
+
import { settings } from "../../common/settings.js";
|
|
4
|
+
export class WebSocketClient {
|
|
5
|
+
ws = null;
|
|
6
|
+
WebSocketClass = null;
|
|
7
|
+
url;
|
|
8
|
+
reconnect;
|
|
9
|
+
reconnectInterval;
|
|
10
|
+
maxReconnectAttempts;
|
|
11
|
+
reconnectAttempts = 0;
|
|
12
|
+
pendingRequests = new Map();
|
|
13
|
+
streamHandlers = new Map();
|
|
14
|
+
isClosing = false;
|
|
15
|
+
connectionPromise = null;
|
|
16
|
+
heartbeatInterval = null;
|
|
17
|
+
reconnectTimeout = null;
|
|
18
|
+
lastPongReceived = Date.now();
|
|
19
|
+
constructor(options) {
|
|
20
|
+
this.url = options.url;
|
|
21
|
+
this.reconnect = options.reconnect ?? true;
|
|
22
|
+
this.reconnectInterval = options.reconnectInterval ?? 5000;
|
|
23
|
+
this.maxReconnectAttempts = options.maxReconnectAttempts ?? 5;
|
|
24
|
+
}
|
|
25
|
+
async connect() {
|
|
26
|
+
if (this.connectionPromise) {
|
|
27
|
+
return this.connectionPromise;
|
|
28
|
+
}
|
|
29
|
+
this.connectionPromise = this.initializeConnection();
|
|
30
|
+
return this.connectionPromise;
|
|
31
|
+
}
|
|
32
|
+
async initializeConnection() {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
// Get WebSocket class and connect
|
|
35
|
+
void (async () => {
|
|
36
|
+
try {
|
|
37
|
+
// Get WebSocket class if not already loaded
|
|
38
|
+
if (!this.WebSocketClass) {
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
40
|
+
this.WebSocketClass = await getWebSocket();
|
|
41
|
+
}
|
|
42
|
+
// Convert http/https URL to ws/wss
|
|
43
|
+
let wsUrl = this.url;
|
|
44
|
+
if (wsUrl.startsWith("http://")) {
|
|
45
|
+
wsUrl = wsUrl.replace("http://", "ws://");
|
|
46
|
+
}
|
|
47
|
+
else if (wsUrl.startsWith("https://")) {
|
|
48
|
+
wsUrl = wsUrl.replace("https://", "wss://");
|
|
49
|
+
}
|
|
50
|
+
// Add /ws endpoint if not present
|
|
51
|
+
if (!wsUrl.endsWith("/ws")) {
|
|
52
|
+
wsUrl = `${wsUrl}/ws`;
|
|
53
|
+
}
|
|
54
|
+
wsUrl = `${wsUrl}?token=${settings.token}`;
|
|
55
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
|
|
56
|
+
this.ws = new this.WebSocketClass(wsUrl);
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
58
|
+
this.ws.onopen = () => {
|
|
59
|
+
this.reconnectAttempts = 0;
|
|
60
|
+
this.startHeartbeat();
|
|
61
|
+
resolve();
|
|
62
|
+
};
|
|
63
|
+
this.ws.onmessage = (event) => {
|
|
64
|
+
this.handleMessage(event);
|
|
65
|
+
};
|
|
66
|
+
this.ws.onerror = (error) => {
|
|
67
|
+
console.error("WebSocket error:", error);
|
|
68
|
+
reject(new Error("WebSocket connection error"));
|
|
69
|
+
};
|
|
70
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
71
|
+
this.ws.onclose = () => {
|
|
72
|
+
this.stopHeartbeat();
|
|
73
|
+
this.connectionPromise = null;
|
|
74
|
+
if (!this.isClosing && this.reconnect && this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
75
|
+
this.reconnectAttempts++;
|
|
76
|
+
this.reconnectTimeout = setTimeout(() => {
|
|
77
|
+
this.connect().catch(console.error);
|
|
78
|
+
}, this.reconnectInterval);
|
|
79
|
+
// Allow process to exit even if reconnect timeout is pending
|
|
80
|
+
if (this.reconnectTimeout.unref) {
|
|
81
|
+
this.reconnectTimeout.unref();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// Reject all pending requests
|
|
86
|
+
this.pendingRequests.forEach(({ reject }) => {
|
|
87
|
+
reject(new Error("WebSocket connection closed"));
|
|
88
|
+
});
|
|
89
|
+
this.pendingRequests.clear();
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
// Handle pong messages for heartbeat
|
|
93
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
94
|
+
if (typeof this.ws.on === 'function') {
|
|
95
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
|
96
|
+
this.ws.on('pong', () => {
|
|
97
|
+
this.lastPongReceived = Date.now();
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
103
|
+
}
|
|
104
|
+
})();
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
startHeartbeat() {
|
|
108
|
+
// Send ping every 30 seconds
|
|
109
|
+
this.heartbeatInterval = setInterval(() => {
|
|
110
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
111
|
+
if (this.ws && this.WebSocketClass && this.ws.readyState === this.WebSocketClass.OPEN) {
|
|
112
|
+
// Check if we received a pong recently (within 60 seconds)
|
|
113
|
+
if (Date.now() - this.lastPongReceived > 60000) {
|
|
114
|
+
console.warn("WebSocket heartbeat timeout, closing connection");
|
|
115
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
|
116
|
+
this.ws.close();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
// Send ping
|
|
120
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
121
|
+
if (typeof this.ws.ping === 'function') {
|
|
122
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
|
123
|
+
this.ws.ping();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}, 30000);
|
|
127
|
+
// Allow process to exit even if heartbeat interval is active
|
|
128
|
+
if (this.heartbeatInterval.unref) {
|
|
129
|
+
this.heartbeatInterval.unref();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
stopHeartbeat() {
|
|
133
|
+
if (this.heartbeatInterval) {
|
|
134
|
+
clearInterval(this.heartbeatInterval);
|
|
135
|
+
this.heartbeatInterval = null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
handleMessage(event) {
|
|
139
|
+
try {
|
|
140
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
|
141
|
+
const response = JSON.parse(String(event.data));
|
|
142
|
+
// Check if this is a streaming response
|
|
143
|
+
if (response.stream) {
|
|
144
|
+
const streamHandler = this.streamHandlers.get(response.id);
|
|
145
|
+
if (streamHandler) {
|
|
146
|
+
// Call the data handler with the response data
|
|
147
|
+
streamHandler.onData(response.data);
|
|
148
|
+
// If stream is done, call end handler and clean up
|
|
149
|
+
if (response.done) {
|
|
150
|
+
streamHandler.onEnd();
|
|
151
|
+
this.streamHandlers.delete(response.id);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
// Regular request-response handling
|
|
157
|
+
const pending = this.pendingRequests.get(response.id);
|
|
158
|
+
if (pending) {
|
|
159
|
+
this.pendingRequests.delete(response.id);
|
|
160
|
+
if (response.success) {
|
|
161
|
+
pending.resolve(response.data);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
pending.reject(new Error(response.error || "Unknown error"));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
console.error("Failed to parse WebSocket message:", error);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
async send(operation, data = {}) {
|
|
173
|
+
// Ensure we're connected
|
|
174
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
175
|
+
if (!this.ws || !this.WebSocketClass || this.ws.readyState !== this.WebSocketClass.OPEN) {
|
|
176
|
+
await this.connect();
|
|
177
|
+
}
|
|
178
|
+
return new Promise((resolve, reject) => {
|
|
179
|
+
const id = uuidv4();
|
|
180
|
+
const message = {
|
|
181
|
+
id,
|
|
182
|
+
operation,
|
|
183
|
+
data,
|
|
184
|
+
};
|
|
185
|
+
// Store the promise handlers
|
|
186
|
+
this.pendingRequests.set(id, { resolve, reject });
|
|
187
|
+
// Set a timeout for the request (60 seconds)
|
|
188
|
+
setTimeout(() => {
|
|
189
|
+
if (this.pendingRequests.has(id)) {
|
|
190
|
+
this.pendingRequests.delete(id);
|
|
191
|
+
reject(new Error("Request timeout"));
|
|
192
|
+
}
|
|
193
|
+
}, 60000);
|
|
194
|
+
// Send the message
|
|
195
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
196
|
+
if (this.ws && this.WebSocketClass && this.ws.readyState === this.WebSocketClass.OPEN) {
|
|
197
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
|
198
|
+
this.ws.send(JSON.stringify(message));
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
this.pendingRequests.delete(id);
|
|
202
|
+
reject(new Error("WebSocket not connected"));
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
sendStream(operation, data, onData, onEnd) {
|
|
207
|
+
// Ensure we're connected
|
|
208
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
209
|
+
if (!this.ws || !this.WebSocketClass || this.ws.readyState !== this.WebSocketClass.OPEN) {
|
|
210
|
+
throw new Error("WebSocket not connected");
|
|
211
|
+
}
|
|
212
|
+
const id = uuidv4();
|
|
213
|
+
const message = {
|
|
214
|
+
id,
|
|
215
|
+
operation,
|
|
216
|
+
data,
|
|
217
|
+
};
|
|
218
|
+
// Store the stream handlers
|
|
219
|
+
this.streamHandlers.set(id, { onData, onEnd });
|
|
220
|
+
// Send the message
|
|
221
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
|
222
|
+
this.ws.send(JSON.stringify(message));
|
|
223
|
+
return id;
|
|
224
|
+
}
|
|
225
|
+
cancelStream(id) {
|
|
226
|
+
this.streamHandlers.delete(id);
|
|
227
|
+
}
|
|
228
|
+
close() {
|
|
229
|
+
this.isClosing = true;
|
|
230
|
+
this.reconnect = false;
|
|
231
|
+
this.stopHeartbeat();
|
|
232
|
+
// Clear reconnect timeout if any
|
|
233
|
+
if (this.reconnectTimeout) {
|
|
234
|
+
clearTimeout(this.reconnectTimeout);
|
|
235
|
+
this.reconnectTimeout = null;
|
|
236
|
+
}
|
|
237
|
+
if (this.ws) {
|
|
238
|
+
// In Node.js (ws package), use terminate() to forcefully close the connection
|
|
239
|
+
// This immediately closes the socket without waiting for the close handshake
|
|
240
|
+
// In browser, terminate() doesn't exist, so we fall back to close()
|
|
241
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
242
|
+
if (typeof this.ws.terminate === 'function') {
|
|
243
|
+
// Node.js ws package - force immediate close
|
|
244
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
|
245
|
+
this.ws.terminate();
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
// Browser WebSocket - graceful close
|
|
249
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
|
250
|
+
this.ws.close();
|
|
251
|
+
}
|
|
252
|
+
this.ws = null;
|
|
253
|
+
}
|
|
254
|
+
// Reject all pending requests
|
|
255
|
+
this.pendingRequests.forEach(({ reject }) => {
|
|
256
|
+
reject(new Error("WebSocket client closed"));
|
|
257
|
+
});
|
|
258
|
+
this.pendingRequests.clear();
|
|
259
|
+
this.connectionPromise = null;
|
|
260
|
+
}
|
|
261
|
+
get isConnected() {
|
|
262
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
263
|
+
return this.ws !== null && this.WebSocketClass !== null && this.ws.readyState === this.WebSocketClass.OPEN;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./client.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Client as ModelContextProtocolClient } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
2
|
import { env } from "../common/env.js";
|
|
3
|
-
import {
|
|
3
|
+
import { generateInternalUrl, getForcedUrl } from "../common/internal.js";
|
|
4
4
|
import { logger } from "../common/logger.js";
|
|
5
5
|
import { settings } from "../common/settings.js";
|
|
6
6
|
import { authenticate } from "../index.js";
|
|
@@ -51,8 +51,8 @@ export class McpTool {
|
|
|
51
51
|
return new URL(`${settings.runUrl}/${settings.workspace}/${this.pluralType}/${this.name}`);
|
|
52
52
|
}
|
|
53
53
|
get internalUrl() {
|
|
54
|
-
const
|
|
55
|
-
return new URL(
|
|
54
|
+
const url = generateInternalUrl(settings.workspace, this.type, this.name, settings.env, settings.runInternalProtocol, settings.runInternalHostname, settings.blCloud, settings.workspaceId);
|
|
55
|
+
return new URL(url);
|
|
56
56
|
}
|
|
57
57
|
get forcedUrl() {
|
|
58
58
|
return getForcedUrl(this.type, this.name);
|