langmart-gateway-type3 3.0.41 → 3.0.43
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/.env.example +3 -3
- package/README.md +38 -38
- package/dist/automation-tools.d.ts +18 -57
- package/dist/automation-tools.d.ts.map +1 -1
- package/dist/automation-tools.js +348 -497
- package/dist/automation-tools.js.map +1 -1
- package/dist/gateway-server.d.ts +52 -26
- package/dist/gateway-server.d.ts.map +1 -1
- package/dist/gateway-server.js +423 -145
- package/dist/gateway-server.js.map +1 -1
- package/dist/headless-session.d.ts +22 -2
- package/dist/headless-session.d.ts.map +1 -1
- package/dist/headless-session.js +209 -7
- package/dist/headless-session.js.map +1 -1
- package/dist/index-server.d.ts.map +1 -1
- package/dist/index-server.js +11 -21
- package/dist/index-server.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -20
- package/dist/index.js.map +1 -1
- package/dist/{marketplace-tools.d.ts → registry-tools.d.ts} +30 -8
- package/dist/registry-tools.d.ts.map +1 -0
- package/dist/{marketplace-tools.js → registry-tools.js} +565 -144
- package/dist/registry-tools.js.map +1 -0
- package/package.json +8 -7
- package/scripts/install.ps1 +14 -14
- package/scripts/install.sh +9 -9
- package/scripts/start.ps1 +5 -5
- package/scripts/start.sh +4 -4
- package/dist/marketplace-tools.d.ts.map +0 -1
- package/dist/marketplace-tools.js.map +0 -1
package/dist/gateway-server.js
CHANGED
|
@@ -16,25 +16,13 @@ const local_vault_1 = require("./local-vault");
|
|
|
16
16
|
const gateway_config_1 = require("./gateway-config");
|
|
17
17
|
const headless_session_1 = require("./headless-session");
|
|
18
18
|
const gateway_mode_1 = require("./gateway-mode");
|
|
19
|
-
/**
|
|
20
|
-
* Type 3 Gateway Server
|
|
21
|
-
* Seller-managed gateway deployed on seller's infrastructure
|
|
22
|
-
*
|
|
23
|
-
* Key Differences from Type 2:
|
|
24
|
-
* - Uses local vault for provider credentials (NOT central database)
|
|
25
|
-
* - Seller manages their own API keys
|
|
26
|
-
* - More privacy and control for sellers
|
|
27
|
-
*
|
|
28
|
-
* Similarities to Type 2:
|
|
29
|
-
* - Same WebSocket communication protocol with Type 1
|
|
30
|
-
* - Same request/response format
|
|
31
|
-
* - Same registration and heartbeat mechanisms
|
|
32
|
-
*/
|
|
33
19
|
class Type3GatewayServer extends events_1.EventEmitter {
|
|
34
20
|
constructor(config) {
|
|
35
21
|
super();
|
|
36
22
|
this.wss = null;
|
|
37
|
-
this.
|
|
23
|
+
this.registryWs = null; // Primary connection (backward compat)
|
|
24
|
+
// Multi-connect: Map of server connections (serverId -> connection)
|
|
25
|
+
this.serverConnections = new Map();
|
|
38
26
|
this.activeRequests = new Map();
|
|
39
27
|
this.healthCheckInterval = null;
|
|
40
28
|
// Version loaded dynamically - works from both source and dist folders
|
|
@@ -66,8 +54,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
66
54
|
this.isShuttingDown = false;
|
|
67
55
|
this.headlessSessionManager = null; // For remote automation sessions
|
|
68
56
|
this.port = config.port;
|
|
69
|
-
this.
|
|
70
|
-
this.
|
|
57
|
+
this.registryUrl = config.registryUrl || 'wss://control.registry.ai';
|
|
58
|
+
this.registryApiUrl = config.registryUrl?.replace('wss://', 'https://').replace('control.', 'api.') || 'https://api.registry.ai';
|
|
71
59
|
// Set gateway mode (defaults to FULL for backward compatibility)
|
|
72
60
|
this.gatewayMode = config.mode ?? gateway_mode_1.GatewayMode.FULL;
|
|
73
61
|
this.enableRemoteLLMSession = config.enableRemoteLLMSession ?? true;
|
|
@@ -142,8 +130,9 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
142
130
|
console.log(`[${this.nodeId}] Starting Type 3 Gateway Server on port ${this.port}`);
|
|
143
131
|
// 1. Start management REST API server for CLI
|
|
144
132
|
await this.startManagementServer();
|
|
145
|
-
// 2. Connect to
|
|
146
|
-
|
|
133
|
+
// 2. Connect to all inference servers (Multi-Connect architecture)
|
|
134
|
+
// This discovers all Type 0/Type 1 servers and connects to each
|
|
135
|
+
await this.connectToAllServers();
|
|
147
136
|
// 3. Load credentials from local vault (only if LLM routing is enabled)
|
|
148
137
|
if (this.enableLLMRouting) {
|
|
149
138
|
await this.loadCredentials();
|
|
@@ -155,11 +144,234 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
155
144
|
}
|
|
156
145
|
// 5. Start health monitoring
|
|
157
146
|
this.startHealthMonitoring();
|
|
158
|
-
// 6. Register with
|
|
147
|
+
// 6. Register with registry (includes tool discovery)
|
|
159
148
|
await this.registerGateway();
|
|
160
149
|
console.log(`[${this.nodeId}] Type 3 Gateway ready and registered`);
|
|
161
150
|
console.log(`[${this.nodeId}] Management API: http://localhost:${this.port}`);
|
|
162
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* Discover all available inference servers from the registry
|
|
154
|
+
* This enables Type 3 Multi-Connect architecture
|
|
155
|
+
*/
|
|
156
|
+
async discoverInferenceServers() {
|
|
157
|
+
try {
|
|
158
|
+
// Convert WebSocket URL to HTTP for API call
|
|
159
|
+
const httpUrl = this.registryUrl
|
|
160
|
+
.replace('ws://', 'http://')
|
|
161
|
+
.replace('wss://', 'https://');
|
|
162
|
+
const discoveryUrl = `${httpUrl}/api/public/inference-servers`;
|
|
163
|
+
console.log(`[${this.nodeId}] Discovering inference servers from ${discoveryUrl}...`);
|
|
164
|
+
const response = await axios_1.default.get(discoveryUrl, {
|
|
165
|
+
timeout: 5000,
|
|
166
|
+
headers: {
|
|
167
|
+
'Authorization': `Bearer ${this.apiKey}`
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
if (response.data?.success && Array.isArray(response.data.servers)) {
|
|
171
|
+
const servers = response.data.servers;
|
|
172
|
+
console.log(`[${this.nodeId}] Discovered ${servers.length} inference servers:`);
|
|
173
|
+
servers.forEach(s => {
|
|
174
|
+
console.log(`[${this.nodeId}] - ${s.name} (${s.serverType}:${s.port}) ${s.isActive ? '[ACTIVE]' : ''}`);
|
|
175
|
+
});
|
|
176
|
+
return servers;
|
|
177
|
+
}
|
|
178
|
+
console.log(`[${this.nodeId}] No servers discovered, falling back to single connection`);
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
console.log(`[${this.nodeId}] Server discovery failed: ${error.message}, using single connection`);
|
|
183
|
+
return [];
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Connect to all discovered inference servers (Multi-Connect)
|
|
188
|
+
*/
|
|
189
|
+
async connectToAllServers() {
|
|
190
|
+
const servers = await this.discoverInferenceServers();
|
|
191
|
+
if (servers.length === 0) {
|
|
192
|
+
// Fallback to single connection using registryUrl
|
|
193
|
+
console.log(`[${this.nodeId}] No servers discovered, using single connection to ${this.registryUrl}`);
|
|
194
|
+
await this.connectToRegistry();
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
// Connect to ALL servers in parallel
|
|
198
|
+
console.log(`[${this.nodeId}] Connecting to ${servers.length} inference servers...`);
|
|
199
|
+
const connectionPromises = servers.map(async (server) => {
|
|
200
|
+
try {
|
|
201
|
+
await this.connectToServer(server);
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
console.error(`[${this.nodeId}] Failed to connect to ${server.name}: ${error.message}`);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
await Promise.allSettled(connectionPromises);
|
|
208
|
+
// Check how many connections succeeded
|
|
209
|
+
const connectedCount = Array.from(this.serverConnections.values())
|
|
210
|
+
.filter(c => c.ws?.readyState === ws_1.default.OPEN).length;
|
|
211
|
+
console.log(`[${this.nodeId}] Connected to ${connectedCount}/${servers.length} inference servers`);
|
|
212
|
+
// If no connections succeeded, try the primary registryUrl
|
|
213
|
+
if (connectedCount === 0) {
|
|
214
|
+
console.log(`[${this.nodeId}] No multi-connect succeeded, falling back to ${this.registryUrl}`);
|
|
215
|
+
await this.connectToRegistry();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Connect to a specific inference server
|
|
220
|
+
*/
|
|
221
|
+
async connectToServer(server) {
|
|
222
|
+
return new Promise((resolve, reject) => {
|
|
223
|
+
let settled = false;
|
|
224
|
+
console.log(`[${this.nodeId}] Connecting to ${server.name} at ${server.websocketUrl}...`);
|
|
225
|
+
const ws = new ws_1.default(server.websocketUrl, {
|
|
226
|
+
headers: {
|
|
227
|
+
'X-Gateway-Type': '3',
|
|
228
|
+
'X-Instance-ID': this.instanceId,
|
|
229
|
+
'X-Node-ID': this.nodeId,
|
|
230
|
+
'X-Version': this.currentVersion
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
// Preserve reconnect attempts from existing connection (for proper backoff)
|
|
234
|
+
const existingConnection = this.serverConnections.get(server.id);
|
|
235
|
+
const reconnectAttempts = existingConnection?.reconnectAttempts || 0;
|
|
236
|
+
const reconnectInterval = existingConnection?.reconnectInterval || null;
|
|
237
|
+
// Track this connection (preserving reconnect state)
|
|
238
|
+
const connection = {
|
|
239
|
+
serverId: server.id,
|
|
240
|
+
serverType: server.serverType,
|
|
241
|
+
port: server.port,
|
|
242
|
+
ws: ws,
|
|
243
|
+
isAuthenticated: false,
|
|
244
|
+
reconnectAttempts: reconnectAttempts,
|
|
245
|
+
reconnectInterval: reconnectInterval
|
|
246
|
+
};
|
|
247
|
+
this.serverConnections.set(server.id, connection);
|
|
248
|
+
ws.on('open', () => {
|
|
249
|
+
console.log(`[${this.nodeId}] ✅ Connected to ${server.name} (${server.serverType}:${server.port})`);
|
|
250
|
+
// Reset reconnect state on successful connection
|
|
251
|
+
connection.reconnectAttempts = 0;
|
|
252
|
+
if (connection.reconnectInterval) {
|
|
253
|
+
clearTimeout(connection.reconnectInterval);
|
|
254
|
+
connection.reconnectInterval = null;
|
|
255
|
+
}
|
|
256
|
+
// Set as primary if this is the active server or first connection
|
|
257
|
+
if (server.isActive || !this.registryWs) {
|
|
258
|
+
this.registryWs = ws;
|
|
259
|
+
}
|
|
260
|
+
if (!settled) {
|
|
261
|
+
settled = true;
|
|
262
|
+
resolve();
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
ws.on('message', (data) => {
|
|
266
|
+
this.handleRegistryMessage(data.toString(), server.id);
|
|
267
|
+
});
|
|
268
|
+
ws.on('error', (error) => {
|
|
269
|
+
console.error(`[${this.nodeId}] ❌ Connection error to ${server.name}:`, error.message);
|
|
270
|
+
if (!settled) {
|
|
271
|
+
settled = true;
|
|
272
|
+
reject(error);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
ws.on('close', (code, reason) => {
|
|
276
|
+
const reasonStr = reason ? (Buffer.isBuffer(reason) ? reason.toString() : String(reason)) : 'unknown';
|
|
277
|
+
console.log(`[${this.nodeId}] 🔌 Disconnected from ${server.name} (code: ${code}, reason: ${reasonStr})`);
|
|
278
|
+
connection.isAuthenticated = false;
|
|
279
|
+
// Handle reconnection for this specific server
|
|
280
|
+
this.handleServerDisconnection(server);
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Handle disconnection from a specific server and schedule reconnection
|
|
286
|
+
*/
|
|
287
|
+
handleServerDisconnection(server) {
|
|
288
|
+
const connection = this.serverConnections.get(server.id);
|
|
289
|
+
if (!connection || this.isShuttingDown)
|
|
290
|
+
return;
|
|
291
|
+
// Clear existing reconnect interval
|
|
292
|
+
if (connection.reconnectInterval) {
|
|
293
|
+
clearTimeout(connection.reconnectInterval);
|
|
294
|
+
}
|
|
295
|
+
connection.reconnectAttempts++;
|
|
296
|
+
const delay = Math.min(this.reconnectDelay * connection.reconnectAttempts, 60000);
|
|
297
|
+
console.log(`[${this.nodeId}] Scheduling reconnection to ${server.name} in ${delay / 1000}s (attempt ${connection.reconnectAttempts})`);
|
|
298
|
+
connection.reconnectInterval = setTimeout(async () => {
|
|
299
|
+
try {
|
|
300
|
+
await this.connectToServer(server);
|
|
301
|
+
await this.registerWithServer(server.id);
|
|
302
|
+
// Note: reconnectAttempts is reset in the 'open' handler of connectToServer
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
console.error(`[${this.nodeId}] Reconnection to ${server.name} failed:`, error.message);
|
|
306
|
+
this.handleServerDisconnection(server);
|
|
307
|
+
}
|
|
308
|
+
}, delay);
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Register gateway with a specific server
|
|
312
|
+
*/
|
|
313
|
+
async registerWithServer(serverId) {
|
|
314
|
+
const connection = this.serverConnections.get(serverId);
|
|
315
|
+
if (!connection?.ws || connection.ws.readyState !== ws_1.default.OPEN) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
// Use same registration message format
|
|
319
|
+
const registrationMessage = await this.buildRegistrationMessage();
|
|
320
|
+
connection.ws.send(JSON.stringify(registrationMessage));
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Register gateway with all connected servers
|
|
324
|
+
*/
|
|
325
|
+
async registerWithAllServers() {
|
|
326
|
+
const registrationMessage = await this.buildRegistrationMessage();
|
|
327
|
+
for (const [serverId, connection] of this.serverConnections) {
|
|
328
|
+
if (connection.ws?.readyState === ws_1.default.OPEN) {
|
|
329
|
+
console.log(`[${this.nodeId}] Registering with server ${serverId}...`);
|
|
330
|
+
connection.ws.send(JSON.stringify(registrationMessage));
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Build registration message (extracted for reuse)
|
|
336
|
+
*/
|
|
337
|
+
async buildRegistrationMessage() {
|
|
338
|
+
const os = require('os');
|
|
339
|
+
// Detect run mode and container environment
|
|
340
|
+
const isContainer = this.detectContainerEnvironment();
|
|
341
|
+
const runMode = this.detectRunMode();
|
|
342
|
+
const dockerImage = process.env.DOCKER_IMAGE_NAME || null;
|
|
343
|
+
// Discover available MCP tools
|
|
344
|
+
let availableTools = [];
|
|
345
|
+
if (this.enableRemoteLLMSession && this.headlessSessionManager) {
|
|
346
|
+
availableTools = this.headlessSessionManager.discoverAvailableTools?.() || [];
|
|
347
|
+
}
|
|
348
|
+
return {
|
|
349
|
+
type: 'gateway_register',
|
|
350
|
+
gateway_type: 3,
|
|
351
|
+
gateway_id: this.gatewayId,
|
|
352
|
+
instance_id: this.instanceId,
|
|
353
|
+
node_id: this.nodeId,
|
|
354
|
+
version: this.currentVersion,
|
|
355
|
+
api_key: this.apiKey,
|
|
356
|
+
capabilities: {
|
|
357
|
+
maxConcurrent: 10,
|
|
358
|
+
supportsStreaming: true,
|
|
359
|
+
supportsFunctions: true,
|
|
360
|
+
supportedModels: this.vault.listAccessPoints().map((ap) => ap.model_id),
|
|
361
|
+
availableTools: availableTools
|
|
362
|
+
},
|
|
363
|
+
system_info: {
|
|
364
|
+
osPlatform: os.platform(),
|
|
365
|
+
osRelease: os.release(),
|
|
366
|
+
nodeVersion: process.version,
|
|
367
|
+
arch: os.arch(),
|
|
368
|
+
hostname: os.hostname(),
|
|
369
|
+
isContainer: isContainer,
|
|
370
|
+
runMode: runMode,
|
|
371
|
+
dockerImage: dockerImage
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
}
|
|
163
375
|
/**
|
|
164
376
|
* Initialize the headless session manager for remote automation
|
|
165
377
|
*/
|
|
@@ -167,7 +379,7 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
167
379
|
this.headlessSessionManager = new headless_session_1.HeadlessSessionManager({
|
|
168
380
|
gatewayId: this.instanceId,
|
|
169
381
|
vault: this.vault,
|
|
170
|
-
|
|
382
|
+
registryUrl: this.registryUrl,
|
|
171
383
|
apiKey: this.apiKey
|
|
172
384
|
});
|
|
173
385
|
// Listen for session events
|
|
@@ -201,8 +413,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
201
413
|
node_id: this.nodeId,
|
|
202
414
|
uptime_ms: uptime,
|
|
203
415
|
uptime_human: this.formatUptime(uptime),
|
|
204
|
-
|
|
205
|
-
|
|
416
|
+
registry_connected: this.registryWs?.readyState === ws_1.default.OPEN,
|
|
417
|
+
registry_authenticated: this.isAuthenticated,
|
|
206
418
|
mode: {
|
|
207
419
|
value: this.gatewayMode,
|
|
208
420
|
name: modeNames[this.gatewayMode],
|
|
@@ -336,16 +548,16 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
336
548
|
code: 'vault_error'
|
|
337
549
|
});
|
|
338
550
|
}
|
|
339
|
-
// Query
|
|
340
|
-
const
|
|
341
|
-
const connectionResponse = await axios_1.default.get(`${
|
|
551
|
+
// Query registry for connection details
|
|
552
|
+
const registryUrl = this.registryUrl.replace('ws://', 'http://').replace('wss://', 'https://');
|
|
553
|
+
const connectionResponse = await axios_1.default.get(`${registryUrl}/api/connections`, {
|
|
342
554
|
headers: { 'Authorization': `Bearer ${this.apiKey}` },
|
|
343
555
|
timeout: 5000
|
|
344
556
|
});
|
|
345
557
|
const connection = connectionResponse.data.connections?.find((c) => c.id === connectionId);
|
|
346
558
|
if (!connection) {
|
|
347
559
|
return res.status(404).json({
|
|
348
|
-
error: 'Connection not found in
|
|
560
|
+
error: 'Connection not found in registry',
|
|
349
561
|
code: 'connection_not_found'
|
|
350
562
|
});
|
|
351
563
|
}
|
|
@@ -470,7 +682,7 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
470
682
|
// Automatically trigger reconnection after response is sent
|
|
471
683
|
setTimeout(async () => {
|
|
472
684
|
console.log(`[${this.nodeId}] Auto-reconnecting with new API key...`);
|
|
473
|
-
await this.
|
|
685
|
+
await this.connectToRegistry();
|
|
474
686
|
await this.registerGateway();
|
|
475
687
|
}, 500);
|
|
476
688
|
}
|
|
@@ -479,17 +691,17 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
479
691
|
res.status(500).json({ error: error.message });
|
|
480
692
|
}
|
|
481
693
|
});
|
|
482
|
-
// POST /reconnect - Reconnect to
|
|
694
|
+
// POST /reconnect - Reconnect to registry
|
|
483
695
|
this.managementApp.post('/reconnect', async (req, res) => {
|
|
484
696
|
try {
|
|
485
697
|
console.log(`[${this.nodeId}] Reconnect requested via management API`);
|
|
486
698
|
res.json({
|
|
487
699
|
status: 'reconnecting',
|
|
488
|
-
message: 'Disconnecting and reconnecting to
|
|
700
|
+
message: 'Disconnecting and reconnecting to registry'
|
|
489
701
|
});
|
|
490
702
|
// Reconnect after sending response
|
|
491
703
|
setTimeout(async () => {
|
|
492
|
-
await this.
|
|
704
|
+
await this.connectToRegistry();
|
|
493
705
|
await this.registerGateway();
|
|
494
706
|
}, 500);
|
|
495
707
|
}
|
|
@@ -503,7 +715,7 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
503
715
|
res.json({
|
|
504
716
|
status: 'healthy',
|
|
505
717
|
timestamp: new Date().toISOString(),
|
|
506
|
-
connected: this.
|
|
718
|
+
connected: this.registryWs?.readyState === ws_1.default.OPEN,
|
|
507
719
|
authenticated: this.isAuthenticated,
|
|
508
720
|
current_user: this.currentUser ? {
|
|
509
721
|
user_id: this.currentUser.id,
|
|
@@ -546,9 +758,9 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
546
758
|
return `${seconds}s`;
|
|
547
759
|
}
|
|
548
760
|
/**
|
|
549
|
-
* Connect to
|
|
761
|
+
* Connect to registry control plane
|
|
550
762
|
*/
|
|
551
|
-
async
|
|
763
|
+
async connectToRegistry() {
|
|
552
764
|
return new Promise((resolve, reject) => {
|
|
553
765
|
// Track if this promise has been settled to prevent double resolve/reject
|
|
554
766
|
let settled = false;
|
|
@@ -556,15 +768,15 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
556
768
|
clearTimeout(this.reconnectInterval);
|
|
557
769
|
this.reconnectInterval = null;
|
|
558
770
|
}
|
|
559
|
-
if (this.
|
|
560
|
-
this.
|
|
561
|
-
if (this.
|
|
562
|
-
this.
|
|
771
|
+
if (this.registryWs) {
|
|
772
|
+
this.registryWs.removeAllListeners();
|
|
773
|
+
if (this.registryWs.readyState === ws_1.default.OPEN) {
|
|
774
|
+
this.registryWs.close();
|
|
563
775
|
}
|
|
564
|
-
this.
|
|
776
|
+
this.registryWs = null;
|
|
565
777
|
}
|
|
566
|
-
console.log(`[${this.nodeId}] Connecting to
|
|
567
|
-
this.
|
|
778
|
+
console.log(`[${this.nodeId}] Connecting to registry at ${this.registryUrl}...`);
|
|
779
|
+
this.registryWs = new ws_1.default(this.registryUrl, {
|
|
568
780
|
headers: {
|
|
569
781
|
'X-Gateway-Type': '3',
|
|
570
782
|
'X-Instance-ID': this.instanceId,
|
|
@@ -572,19 +784,19 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
572
784
|
'X-Version': this.currentVersion
|
|
573
785
|
}
|
|
574
786
|
});
|
|
575
|
-
this.
|
|
576
|
-
console.log(`[${this.nodeId}] ✅ Connected to
|
|
787
|
+
this.registryWs.on('open', () => {
|
|
788
|
+
console.log(`[${this.nodeId}] ✅ Connected to registry control plane`);
|
|
577
789
|
this.isAuthenticated = false;
|
|
578
790
|
if (!settled) {
|
|
579
791
|
settled = true;
|
|
580
792
|
resolve();
|
|
581
793
|
}
|
|
582
794
|
});
|
|
583
|
-
this.
|
|
584
|
-
this.
|
|
795
|
+
this.registryWs.on('message', (data) => {
|
|
796
|
+
this.handleRegistryMessage(data.toString());
|
|
585
797
|
});
|
|
586
|
-
this.
|
|
587
|
-
console.error(`[${this.nodeId}] ❌
|
|
798
|
+
this.registryWs.on('error', (error) => {
|
|
799
|
+
console.error(`[${this.nodeId}] ❌ Registry connection error:`, error.message);
|
|
588
800
|
this.emit('error', error);
|
|
589
801
|
// Only reject on initial connection attempt (before promise is settled)
|
|
590
802
|
if (!settled) {
|
|
@@ -594,13 +806,13 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
594
806
|
// For errors after connection, trigger reconnection
|
|
595
807
|
// (close event should also fire, but just in case)
|
|
596
808
|
});
|
|
597
|
-
this.
|
|
809
|
+
this.registryWs.on('close', (code, reason) => {
|
|
598
810
|
// reason can be Buffer or string depending on ws version - handle both
|
|
599
811
|
const reasonStr = reason ? (Buffer.isBuffer(reason) ? reason.toString() : String(reason)) : 'unknown';
|
|
600
|
-
console.log(`[${this.nodeId}] 🔌 Disconnected from
|
|
812
|
+
console.log(`[${this.nodeId}] 🔌 Disconnected from registry (code: ${code}, reason: ${reasonStr})`);
|
|
601
813
|
this.isAuthenticated = false;
|
|
602
814
|
this.currentUser = null; // Clear user info on disconnect
|
|
603
|
-
this.
|
|
815
|
+
this.handleRegistryDisconnection();
|
|
604
816
|
});
|
|
605
817
|
});
|
|
606
818
|
}
|
|
@@ -631,7 +843,7 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
631
843
|
}
|
|
632
844
|
}
|
|
633
845
|
/**
|
|
634
|
-
* Register gateway with
|
|
846
|
+
* Register gateway with registry
|
|
635
847
|
* Includes tool discovery for remote automation sessions (if enabled)
|
|
636
848
|
*/
|
|
637
849
|
async registerGateway() {
|
|
@@ -696,14 +908,29 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
696
908
|
if (this.enableLLMRouting) {
|
|
697
909
|
console.log(`[${this.nodeId}] Registering with ${connections.length} connections for LLM routing`);
|
|
698
910
|
}
|
|
699
|
-
|
|
700
|
-
|
|
911
|
+
// Multi-connect: Register with ALL connected servers
|
|
912
|
+
if (this.serverConnections.size > 0) {
|
|
913
|
+
let registeredCount = 0;
|
|
914
|
+
for (const [serverId, connection] of this.serverConnections) {
|
|
915
|
+
if (connection.ws?.readyState === ws_1.default.OPEN) {
|
|
916
|
+
console.log(`[${this.nodeId}] Registering with server ${connection.serverType}:${connection.port}...`);
|
|
917
|
+
connection.ws.send(JSON.stringify(registration));
|
|
918
|
+
registeredCount++;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
console.log(`[${this.nodeId}] Registered with ${registeredCount}/${this.serverConnections.size} inference servers`);
|
|
922
|
+
}
|
|
923
|
+
else if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
924
|
+
// Fallback: Single connection mode
|
|
925
|
+
this.registryWs.send(JSON.stringify(registration));
|
|
701
926
|
}
|
|
702
927
|
}
|
|
703
928
|
/**
|
|
704
|
-
* Handle messages from
|
|
929
|
+
* Handle messages from registry control plane
|
|
930
|
+
* @param data - Message data as string
|
|
931
|
+
* @param serverId - Optional server ID for multi-connect tracking
|
|
705
932
|
*/
|
|
706
|
-
async
|
|
933
|
+
async handleRegistryMessage(data, serverId) {
|
|
707
934
|
try {
|
|
708
935
|
const message = JSON.parse(data);
|
|
709
936
|
// Gateway Type 1 sends both 'event' and 'type' properties for compatibility
|
|
@@ -747,8 +974,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
747
974
|
console.log(`[${this.nodeId}] Scheduling reconnection with new gateway ID in 2 seconds...`);
|
|
748
975
|
setTimeout(async () => {
|
|
749
976
|
try {
|
|
750
|
-
console.log(`[${this.nodeId}] Reconnecting to
|
|
751
|
-
await this.
|
|
977
|
+
console.log(`[${this.nodeId}] Reconnecting to registry with fresh identity...`);
|
|
978
|
+
await this.connectToRegistry();
|
|
752
979
|
await this.registerGateway();
|
|
753
980
|
console.log(`[${this.nodeId}] ✅ Successfully reconnected with new gateway ID`);
|
|
754
981
|
}
|
|
@@ -761,8 +988,15 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
761
988
|
case 'auth_confirmed':
|
|
762
989
|
// Gateway Type 1 sends auth_confirmed after successful authentication
|
|
763
990
|
// It also includes the server-assigned gateway_id and gateway_name
|
|
764
|
-
console.log(`[${this.nodeId}] Authentication confirmed by Gateway Type 1`);
|
|
991
|
+
console.log(`[${this.nodeId}] Authentication confirmed by Gateway Type 1${serverId ? ` (server: ${serverId})` : ''}`);
|
|
765
992
|
this.isAuthenticated = true;
|
|
993
|
+
// Update connection-specific auth status for multi-connect
|
|
994
|
+
if (serverId) {
|
|
995
|
+
const connection = this.serverConnections.get(serverId);
|
|
996
|
+
if (connection) {
|
|
997
|
+
connection.isAuthenticated = true;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
766
1000
|
// IMPORTANT: Store server-assigned gateway_id for future reconnections
|
|
767
1001
|
// NOTE: gateway_name is NOT stored locally - it's managed server-side only
|
|
768
1002
|
const serverGatewayId = message.gatewayId || message.gateway_id;
|
|
@@ -806,7 +1040,7 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
806
1040
|
this.sendErrorResponse(message.request_id, 'llm_routing_disabled', 'This gateway is not configured for LLM routing');
|
|
807
1041
|
break;
|
|
808
1042
|
}
|
|
809
|
-
await this.handleHttpRequest(message);
|
|
1043
|
+
await this.handleHttpRequest(message, serverId);
|
|
810
1044
|
break;
|
|
811
1045
|
// ============= REMOTE SESSION EVENTS =============
|
|
812
1046
|
case 'session_start':
|
|
@@ -852,15 +1086,15 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
852
1086
|
await this.handleUpdateAvailable(message);
|
|
853
1087
|
break;
|
|
854
1088
|
case 'update_now':
|
|
855
|
-
// Mandatory update command from
|
|
1089
|
+
// Mandatory update command from registry
|
|
856
1090
|
await this.handleUpdateNow(message);
|
|
857
1091
|
break;
|
|
858
1092
|
default:
|
|
859
|
-
console.log(`[${this.nodeId}] Unknown
|
|
1093
|
+
console.log(`[${this.nodeId}] Unknown registry event: ${eventType}`);
|
|
860
1094
|
}
|
|
861
1095
|
}
|
|
862
1096
|
catch (error) {
|
|
863
|
-
console.error(`[${this.nodeId}]
|
|
1097
|
+
console.error(`[${this.nodeId}] Registry message error:`, error);
|
|
864
1098
|
}
|
|
865
1099
|
}
|
|
866
1100
|
/**
|
|
@@ -887,6 +1121,7 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
887
1121
|
console.log(`[Type3-${this.nodeId}] Step 2: Extracted parameters:`, {
|
|
888
1122
|
provider,
|
|
889
1123
|
endpoint,
|
|
1124
|
+
endpointUrl: endpointUrl || 'NOT PROVIDED',
|
|
890
1125
|
model,
|
|
891
1126
|
hasMessages: !!messages,
|
|
892
1127
|
messageCount: messages?.length || 0,
|
|
@@ -954,7 +1189,7 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
954
1189
|
}
|
|
955
1190
|
if (!stream) {
|
|
956
1191
|
console.log(`[Type3-${this.nodeId}] Step 7: Received response from provider`);
|
|
957
|
-
if (this.
|
|
1192
|
+
if (this.registryWs && this.registryWs.readyState === ws_1.default.OPEN) {
|
|
958
1193
|
const responseMessage = {
|
|
959
1194
|
event: 'inference_response',
|
|
960
1195
|
request_id: requestId,
|
|
@@ -965,7 +1200,7 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
965
1200
|
node_id: this.nodeId
|
|
966
1201
|
}
|
|
967
1202
|
};
|
|
968
|
-
this.
|
|
1203
|
+
this.registryWs.send(JSON.stringify(responseMessage));
|
|
969
1204
|
console.log(`[Type3-${this.nodeId}] Step 8: Response sent successfully`);
|
|
970
1205
|
}
|
|
971
1206
|
}
|
|
@@ -978,8 +1213,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
978
1213
|
}
|
|
979
1214
|
catch (error) {
|
|
980
1215
|
console.error(`[Type3-${this.nodeId}] ERROR in request ${requestId}:`, error.message);
|
|
981
|
-
if (this.
|
|
982
|
-
this.
|
|
1216
|
+
if (this.registryWs && this.registryWs.readyState === ws_1.default.OPEN) {
|
|
1217
|
+
this.registryWs.send(JSON.stringify({
|
|
983
1218
|
event: 'inference_error',
|
|
984
1219
|
request_id: requestId,
|
|
985
1220
|
error: {
|
|
@@ -1081,8 +1316,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1081
1316
|
if (line.startsWith('data: ')) {
|
|
1082
1317
|
const data = line.substring(6);
|
|
1083
1318
|
if (data === '[DONE]') {
|
|
1084
|
-
if (this.
|
|
1085
|
-
this.
|
|
1319
|
+
if (this.registryWs && this.registryWs.readyState === ws_1.default.OPEN) {
|
|
1320
|
+
this.registryWs.send(JSON.stringify({
|
|
1086
1321
|
event: 'stream_end',
|
|
1087
1322
|
request_id: requestId,
|
|
1088
1323
|
timestamp: new Date().toISOString()
|
|
@@ -1092,8 +1327,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1092
1327
|
else {
|
|
1093
1328
|
try {
|
|
1094
1329
|
const parsed = JSON.parse(data);
|
|
1095
|
-
if (this.
|
|
1096
|
-
this.
|
|
1330
|
+
if (this.registryWs && this.registryWs.readyState === ws_1.default.OPEN) {
|
|
1331
|
+
this.registryWs.send(JSON.stringify({
|
|
1097
1332
|
event: 'stream_chunk',
|
|
1098
1333
|
request_id: requestId,
|
|
1099
1334
|
data: parsed,
|
|
@@ -1111,8 +1346,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1111
1346
|
return new Promise((resolve, reject) => {
|
|
1112
1347
|
response.data.on('end', () => {
|
|
1113
1348
|
console.log(`[${this.nodeId}] Streaming completed for request ${requestId}`);
|
|
1114
|
-
if (this.
|
|
1115
|
-
this.
|
|
1349
|
+
if (this.registryWs && this.registryWs.readyState === ws_1.default.OPEN) {
|
|
1350
|
+
this.registryWs.send(JSON.stringify({
|
|
1116
1351
|
event: 'stream_complete',
|
|
1117
1352
|
request_id: requestId,
|
|
1118
1353
|
stream_completed: true,
|
|
@@ -1123,8 +1358,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1123
1358
|
});
|
|
1124
1359
|
response.data.on('error', (error) => {
|
|
1125
1360
|
console.error(`[${this.nodeId}] Stream error for request ${requestId}:`, error);
|
|
1126
|
-
if (this.
|
|
1127
|
-
this.
|
|
1361
|
+
if (this.registryWs && this.registryWs.readyState === ws_1.default.OPEN) {
|
|
1362
|
+
this.registryWs.send(JSON.stringify({
|
|
1128
1363
|
event: 'stream_error',
|
|
1129
1364
|
request_id: requestId,
|
|
1130
1365
|
error: {
|
|
@@ -1194,10 +1429,10 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1194
1429
|
* body: { ... } (optional)
|
|
1195
1430
|
* }
|
|
1196
1431
|
*/
|
|
1197
|
-
async handleHttpRequest(message) {
|
|
1432
|
+
async handleHttpRequest(message, sourceServerId) {
|
|
1198
1433
|
const requestId = message.requestId || (0, uuid_1.v4)();
|
|
1199
1434
|
const { connection_id, method, url, headers = {}, body } = message;
|
|
1200
|
-
console.log(`[${this.nodeId}] Forwarding HTTP ${method} request to: ${url}`);
|
|
1435
|
+
console.log(`[${this.nodeId}] Forwarding HTTP ${method} request to: ${url}${sourceServerId ? ` (from server: ${sourceServerId})` : ''}`);
|
|
1201
1436
|
try {
|
|
1202
1437
|
// Get API key from local vault if connection_id provided
|
|
1203
1438
|
let requestHeaders = { ...headers };
|
|
@@ -1232,7 +1467,7 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1232
1467
|
catch {
|
|
1233
1468
|
responseData = responseText;
|
|
1234
1469
|
}
|
|
1235
|
-
// Send RAW HTTP response back to
|
|
1470
|
+
// Send RAW HTTP response back to the server that sent the request
|
|
1236
1471
|
const wsResponse = {
|
|
1237
1472
|
event: 'http_response',
|
|
1238
1473
|
requestId: requestId,
|
|
@@ -1242,34 +1477,67 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1242
1477
|
body: responseData, // RAW response body
|
|
1243
1478
|
timestamp: new Date().toISOString()
|
|
1244
1479
|
};
|
|
1245
|
-
if (this.
|
|
1246
|
-
this.
|
|
1247
|
-
|
|
1480
|
+
if (this.sendToServer(sourceServerId, wsResponse)) {
|
|
1481
|
+
console.log(`[${this.nodeId}] Sent HTTP response (${response.status}) to server${sourceServerId ? ` ${sourceServerId}` : ''}`);
|
|
1482
|
+
}
|
|
1483
|
+
else {
|
|
1484
|
+
console.error(`[${this.nodeId}] Failed to send HTTP response - no connection available`);
|
|
1248
1485
|
}
|
|
1249
1486
|
}
|
|
1250
1487
|
catch (error) {
|
|
1251
1488
|
console.error(`[${this.nodeId}] HTTP forwarding error:`, error.message);
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
}));
|
|
1260
|
-
}
|
|
1489
|
+
this.sendToServer(sourceServerId, {
|
|
1490
|
+
event: 'http_response',
|
|
1491
|
+
requestId: requestId,
|
|
1492
|
+
success: false,
|
|
1493
|
+
error: error.message,
|
|
1494
|
+
timestamp: new Date().toISOString()
|
|
1495
|
+
});
|
|
1261
1496
|
}
|
|
1262
1497
|
}
|
|
1263
1498
|
/**
|
|
1264
|
-
* Send
|
|
1499
|
+
* Send message to a specific server connection
|
|
1500
|
+
* Falls back to registryWs if serverId not found or not connected
|
|
1501
|
+
* @param serverId - The server ID to send to
|
|
1502
|
+
* @param message - The message object to send
|
|
1503
|
+
* @returns true if message was sent, false otherwise
|
|
1504
|
+
*/
|
|
1505
|
+
sendToServer(serverId, message) {
|
|
1506
|
+
// Try to find the specific server connection
|
|
1507
|
+
if (serverId) {
|
|
1508
|
+
const connection = this.serverConnections.get(serverId);
|
|
1509
|
+
if (connection?.ws?.readyState === ws_1.default.OPEN) {
|
|
1510
|
+
connection.ws.send(JSON.stringify(message));
|
|
1511
|
+
return true;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
// Fallback to registryWs (backward compatibility)
|
|
1515
|
+
if (this.registryWs && this.registryWs.readyState === ws_1.default.OPEN) {
|
|
1516
|
+
this.registryWs.send(JSON.stringify(message));
|
|
1517
|
+
return true;
|
|
1518
|
+
}
|
|
1519
|
+
return false;
|
|
1520
|
+
}
|
|
1521
|
+
/**
|
|
1522
|
+
* Send health status to all connected servers (multi-connect)
|
|
1265
1523
|
*/
|
|
1266
1524
|
sendHealthStatus() {
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1525
|
+
const heartbeat = {
|
|
1526
|
+
type: 'heartbeat',
|
|
1527
|
+
timestamp: new Date().toISOString()
|
|
1528
|
+
};
|
|
1529
|
+
const heartbeatStr = JSON.stringify(heartbeat);
|
|
1530
|
+
// Send to all connected servers (multi-connect)
|
|
1531
|
+
let sentCount = 0;
|
|
1532
|
+
for (const [serverId, connection] of this.serverConnections) {
|
|
1533
|
+
if (connection.ws?.readyState === ws_1.default.OPEN) {
|
|
1534
|
+
connection.ws.send(heartbeatStr);
|
|
1535
|
+
sentCount++;
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
// Fallback to registryWs if no server connections (legacy mode)
|
|
1539
|
+
if (sentCount === 0 && this.registryWs && this.registryWs.readyState === ws_1.default.OPEN) {
|
|
1540
|
+
this.registryWs.send(heartbeatStr);
|
|
1273
1541
|
}
|
|
1274
1542
|
const status = {
|
|
1275
1543
|
event: 'health_status',
|
|
@@ -1288,8 +1556,18 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1288
1556
|
},
|
|
1289
1557
|
timestamp: new Date().toISOString()
|
|
1290
1558
|
};
|
|
1291
|
-
|
|
1292
|
-
|
|
1559
|
+
const statusStr = JSON.stringify(status);
|
|
1560
|
+
// Send health status to all connected servers (multi-connect)
|
|
1561
|
+
let statusSentCount = 0;
|
|
1562
|
+
for (const [, connection] of this.serverConnections) {
|
|
1563
|
+
if (connection.ws?.readyState === ws_1.default.OPEN) {
|
|
1564
|
+
connection.ws.send(statusStr);
|
|
1565
|
+
statusSentCount++;
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
// Fallback to registryWs if no server connections (legacy mode)
|
|
1569
|
+
if (statusSentCount === 0 && this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
1570
|
+
this.registryWs.send(statusStr);
|
|
1293
1571
|
}
|
|
1294
1572
|
this.metrics.lastHealthCheck = new Date();
|
|
1295
1573
|
}
|
|
@@ -1322,8 +1600,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1322
1600
|
},
|
|
1323
1601
|
timestamp: new Date().toISOString()
|
|
1324
1602
|
};
|
|
1325
|
-
if (this.
|
|
1326
|
-
this.
|
|
1603
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
1604
|
+
this.registryWs.send(JSON.stringify(rateLimits));
|
|
1327
1605
|
}
|
|
1328
1606
|
}
|
|
1329
1607
|
/**
|
|
@@ -1333,25 +1611,25 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1333
1611
|
startHealthMonitoring() {
|
|
1334
1612
|
this.healthCheckInterval = setInterval(() => {
|
|
1335
1613
|
// Only send health status if connected
|
|
1336
|
-
if (this.
|
|
1614
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
1337
1615
|
this.sendHealthStatus();
|
|
1338
1616
|
}
|
|
1339
1617
|
else if (!this.reconnectInterval && !this.isShuttingDown) {
|
|
1340
1618
|
// Connection lost and no reconnection scheduled - trigger reconnection
|
|
1341
|
-
console.warn(`[${this.nodeId}] Health check: Lost connection to
|
|
1619
|
+
console.warn(`[${this.nodeId}] Health check: Lost connection to registry, triggering reconnection...`);
|
|
1342
1620
|
// Reset reconnect attempts for health-check triggered reconnection
|
|
1343
1621
|
// This ensures we start fresh when health check detects a problem
|
|
1344
1622
|
this.reconnectAttempts = 0;
|
|
1345
|
-
this.
|
|
1623
|
+
this.handleRegistryDisconnection();
|
|
1346
1624
|
}
|
|
1347
1625
|
}, 30000);
|
|
1348
1626
|
}
|
|
1349
1627
|
/**
|
|
1350
|
-
* Handle
|
|
1628
|
+
* Handle registry disconnection with robust retry logic
|
|
1351
1629
|
* This method schedules reconnection attempts with exponential backoff
|
|
1352
1630
|
* and ensures registerGateway() is always called after successful reconnection
|
|
1353
1631
|
*/
|
|
1354
|
-
|
|
1632
|
+
handleRegistryDisconnection() {
|
|
1355
1633
|
if (this.isShuttingDown) {
|
|
1356
1634
|
console.log(`[${this.nodeId}] Shutdown in progress, skipping reconnection`);
|
|
1357
1635
|
return;
|
|
@@ -1376,9 +1654,9 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1376
1654
|
this.reconnectInterval = null; // Clear so we can schedule next attempt if needed
|
|
1377
1655
|
console.log(`[${this.nodeId}] 🔄 Attempting to reconnect (attempt ${this.reconnectAttempts})...`);
|
|
1378
1656
|
try {
|
|
1379
|
-
await this.
|
|
1657
|
+
await this.connectToRegistry();
|
|
1380
1658
|
// Check if connection was successful
|
|
1381
|
-
if (this.
|
|
1659
|
+
if (this.registryWs && this.registryWs.readyState === ws_1.default.OPEN) {
|
|
1382
1660
|
console.log(`[${this.nodeId}] ✅ Reconnected successfully!`);
|
|
1383
1661
|
console.log(`[${this.nodeId}] 📝 Re-registering after reconnection...`);
|
|
1384
1662
|
try {
|
|
@@ -1390,18 +1668,18 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1390
1668
|
catch (regError) {
|
|
1391
1669
|
console.error(`[${this.nodeId}] ❌ Registration failed after reconnect:`, regError.message);
|
|
1392
1670
|
// Schedule another attempt since registration failed
|
|
1393
|
-
this.
|
|
1671
|
+
this.handleRegistryDisconnection();
|
|
1394
1672
|
}
|
|
1395
1673
|
}
|
|
1396
1674
|
else {
|
|
1397
|
-
console.warn(`[${this.nodeId}] ⚠️ WebSocket not open after
|
|
1398
|
-
this.
|
|
1675
|
+
console.warn(`[${this.nodeId}] ⚠️ WebSocket not open after connectToRegistry, scheduling retry...`);
|
|
1676
|
+
this.handleRegistryDisconnection();
|
|
1399
1677
|
}
|
|
1400
1678
|
}
|
|
1401
1679
|
catch (error) {
|
|
1402
1680
|
console.error(`[${this.nodeId}] ❌ Reconnection attempt failed:`, error.message);
|
|
1403
1681
|
// Schedule another reconnection attempt
|
|
1404
|
-
this.
|
|
1682
|
+
this.handleRegistryDisconnection();
|
|
1405
1683
|
}
|
|
1406
1684
|
}, delay);
|
|
1407
1685
|
}
|
|
@@ -1665,8 +1943,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1665
1943
|
// Send acknowledgment to Type 1
|
|
1666
1944
|
// NOTE: The initialMessage is the assistant's greeting, already added to session history
|
|
1667
1945
|
// We do NOT call processSessionMessageInternal because the greeting doesn't need an LLM response
|
|
1668
|
-
if (this.
|
|
1669
|
-
this.
|
|
1946
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
1947
|
+
this.registryWs.send(JSON.stringify({
|
|
1670
1948
|
event: 'session_started',
|
|
1671
1949
|
requestId,
|
|
1672
1950
|
sessionId: session.sessionId,
|
|
@@ -1735,8 +2013,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1735
2013
|
try {
|
|
1736
2014
|
// Stream handler to send chunks to Type 1
|
|
1737
2015
|
const onChunk = (chunk) => {
|
|
1738
|
-
if (this.
|
|
1739
|
-
this.
|
|
2016
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
2017
|
+
this.registryWs.send(JSON.stringify({
|
|
1740
2018
|
event: 'session_chunk',
|
|
1741
2019
|
requestId,
|
|
1742
2020
|
sessionId,
|
|
@@ -1748,8 +2026,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1748
2026
|
const response = await this.headlessSessionManager.processMessage(sessionId, userMessage, onChunk, modelId // Pass per-message model ID
|
|
1749
2027
|
);
|
|
1750
2028
|
// Send final response to Type 1
|
|
1751
|
-
if (this.
|
|
1752
|
-
this.
|
|
2029
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
2030
|
+
this.registryWs.send(JSON.stringify({
|
|
1753
2031
|
event: 'session_response',
|
|
1754
2032
|
requestId,
|
|
1755
2033
|
sessionId,
|
|
@@ -1789,8 +2067,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1789
2067
|
return;
|
|
1790
2068
|
}
|
|
1791
2069
|
const success = await this.headlessSessionManager.cancelSession(sessionId);
|
|
1792
|
-
if (this.
|
|
1793
|
-
this.
|
|
2070
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
2071
|
+
this.registryWs.send(JSON.stringify({
|
|
1794
2072
|
event: 'session_cancelled',
|
|
1795
2073
|
requestId,
|
|
1796
2074
|
sessionId,
|
|
@@ -1806,8 +2084,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1806
2084
|
const requestId = message.requestId || message.request_id;
|
|
1807
2085
|
console.log(`[${this.nodeId}] Received get_tools request`);
|
|
1808
2086
|
const tools = this.headlessSessionManager?.getAvailableTools() || [];
|
|
1809
|
-
if (this.
|
|
1810
|
-
this.
|
|
2087
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
2088
|
+
this.registryWs.send(JSON.stringify({
|
|
1811
2089
|
event: 'tools_response',
|
|
1812
2090
|
requestId,
|
|
1813
2091
|
gatewayId: this.instanceId,
|
|
@@ -1820,8 +2098,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1820
2098
|
* Send session error to Type 1
|
|
1821
2099
|
*/
|
|
1822
2100
|
sendSessionError(requestId, error, sessionId) {
|
|
1823
|
-
if (this.
|
|
1824
|
-
this.
|
|
2101
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
2102
|
+
this.registryWs.send(JSON.stringify({
|
|
1825
2103
|
event: 'session_error',
|
|
1826
2104
|
requestId,
|
|
1827
2105
|
sessionId,
|
|
@@ -1834,8 +2112,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1834
2112
|
* Send inference error response to Type 1
|
|
1835
2113
|
*/
|
|
1836
2114
|
sendErrorResponse(requestId, code, message) {
|
|
1837
|
-
if (this.
|
|
1838
|
-
this.
|
|
2115
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
2116
|
+
this.registryWs.send(JSON.stringify({
|
|
1839
2117
|
event: 'inference_error',
|
|
1840
2118
|
request_id: requestId,
|
|
1841
2119
|
error: {
|
|
@@ -2039,8 +2317,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
2039
2317
|
});
|
|
2040
2318
|
console.log(`[${this.nodeId}] Script execution completed: ${result.success ? 'success' : 'failed'} (exit code: ${result.exit_code})`);
|
|
2041
2319
|
// Send result back to Type 1
|
|
2042
|
-
if (this.
|
|
2043
|
-
this.
|
|
2320
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
2321
|
+
this.registryWs.send(JSON.stringify({
|
|
2044
2322
|
event: 'script_result',
|
|
2045
2323
|
requestId,
|
|
2046
2324
|
success: true,
|
|
@@ -2064,8 +2342,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
2064
2342
|
* Send script execution error to Type 1
|
|
2065
2343
|
*/
|
|
2066
2344
|
sendScriptError(requestId, code, message) {
|
|
2067
|
-
if (this.
|
|
2068
|
-
this.
|
|
2345
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
2346
|
+
this.registryWs.send(JSON.stringify({
|
|
2069
2347
|
event: 'script_result',
|
|
2070
2348
|
requestId,
|
|
2071
2349
|
success: false,
|
|
@@ -2097,8 +2375,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
2097
2375
|
// autoUpdate defaults to true if not explicitly set to false
|
|
2098
2376
|
const shouldAutoUpdate = mandatory || (autoUpdate !== false);
|
|
2099
2377
|
// Send acknowledgment to Type 1
|
|
2100
|
-
if (this.
|
|
2101
|
-
this.
|
|
2378
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
2379
|
+
this.registryWs.send(JSON.stringify({
|
|
2102
2380
|
event: 'update_acknowledged',
|
|
2103
2381
|
gatewayId: this.instanceId,
|
|
2104
2382
|
currentVersion: this.currentVersion,
|
|
@@ -2133,8 +2411,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
2133
2411
|
console.log(`[${this.nodeId}] 🚨 Immediate update requested: v${version}`);
|
|
2134
2412
|
console.log(`[${this.nodeId}] Reason: ${reason || 'No reason provided'}`);
|
|
2135
2413
|
// Acknowledge the update command
|
|
2136
|
-
if (this.
|
|
2137
|
-
this.
|
|
2414
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
2415
|
+
this.registryWs.send(JSON.stringify({
|
|
2138
2416
|
event: 'update_starting',
|
|
2139
2417
|
gatewayId: this.instanceId,
|
|
2140
2418
|
currentVersion: this.currentVersion,
|
|
@@ -2245,8 +2523,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
2245
2523
|
if (resolvedTargetVersion && this.currentVersion === resolvedTargetVersion) {
|
|
2246
2524
|
console.log(`[${this.nodeId}] ✅ Already at version v${this.currentVersion}, skipping update`);
|
|
2247
2525
|
// Notify Type 1 that no update is needed
|
|
2248
|
-
if (this.
|
|
2249
|
-
this.
|
|
2526
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
2527
|
+
this.registryWs.send(JSON.stringify({
|
|
2250
2528
|
event: 'already_up_to_date',
|
|
2251
2529
|
gatewayId: this.instanceId,
|
|
2252
2530
|
currentVersion: this.currentVersion,
|
|
@@ -2288,8 +2566,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
2288
2566
|
}
|
|
2289
2567
|
console.log(`[${this.nodeId}] ✓ Package installed successfully`);
|
|
2290
2568
|
// Step 2: Notify Type 1 of successful install
|
|
2291
|
-
if (this.
|
|
2292
|
-
this.
|
|
2569
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
2570
|
+
this.registryWs.send(JSON.stringify({
|
|
2293
2571
|
event: 'update_installed',
|
|
2294
2572
|
gatewayId: this.instanceId,
|
|
2295
2573
|
previousVersion: this.currentVersion,
|
|
@@ -2301,8 +2579,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
2301
2579
|
// Step 3: Graceful restart
|
|
2302
2580
|
console.log(`[${this.nodeId}] Step 2: Initiating graceful restart...`);
|
|
2303
2581
|
// Close WebSocket cleanly
|
|
2304
|
-
if (this.
|
|
2305
|
-
this.
|
|
2582
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
2583
|
+
this.registryWs.send(JSON.stringify({
|
|
2306
2584
|
event: 'gateway_restarting',
|
|
2307
2585
|
gatewayId: this.instanceId,
|
|
2308
2586
|
reason: 'update',
|
|
@@ -2351,8 +2629,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
2351
2629
|
console.error(`[${this.nodeId}] ❌ Update failed:`, error.message);
|
|
2352
2630
|
this.isUpdating = false;
|
|
2353
2631
|
// Notify Type 1 of failure
|
|
2354
|
-
if (this.
|
|
2355
|
-
this.
|
|
2632
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
2633
|
+
this.registryWs.send(JSON.stringify({
|
|
2356
2634
|
event: 'update_failed',
|
|
2357
2635
|
gatewayId: this.instanceId,
|
|
2358
2636
|
currentVersion: this.currentVersion,
|
|
@@ -2402,9 +2680,9 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
2402
2680
|
console.log(`[${this.nodeId}] Waiting for ${this.activeRequests.size} active requests...`);
|
|
2403
2681
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
2404
2682
|
}
|
|
2405
|
-
// Close
|
|
2406
|
-
if (this.
|
|
2407
|
-
this.
|
|
2683
|
+
// Close registry WebSocket
|
|
2684
|
+
if (this.registryWs) {
|
|
2685
|
+
this.registryWs.close();
|
|
2408
2686
|
}
|
|
2409
2687
|
// Close management API server
|
|
2410
2688
|
if (this.managementServer) {
|
|
@@ -2425,7 +2703,7 @@ exports.Type3GatewayServer = Type3GatewayServer;
|
|
|
2425
2703
|
if (require.main === module) {
|
|
2426
2704
|
const gateway = new Type3GatewayServer({
|
|
2427
2705
|
port: parseInt(process.env.GATEWAY_PORT || '8083'),
|
|
2428
|
-
|
|
2706
|
+
registryUrl: process.env.REGISTRY_URL,
|
|
2429
2707
|
instanceId: process.env.INSTANCE_ID,
|
|
2430
2708
|
apiKey: process.env.GATEWAY_API_KEY,
|
|
2431
2709
|
vaultPath: process.env.VAULT_PATH,
|