langmart-gateway-type3 3.0.40 → 3.0.42
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/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 +446 -150
- package/dist/gateway-server.js.map +1 -1
- package/dist/headless-session.d.ts +58 -9
- package/dist/headless-session.d.ts.map +1 -1
- package/dist/headless-session.js +338 -26
- 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 +9 -7
- 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,31 +784,35 @@ 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) {
|
|
591
803
|
settled = true;
|
|
592
804
|
reject(error);
|
|
593
805
|
}
|
|
806
|
+
// For errors after connection, trigger reconnection
|
|
807
|
+
// (close event should also fire, but just in case)
|
|
594
808
|
});
|
|
595
|
-
this.
|
|
596
|
-
|
|
809
|
+
this.registryWs.on('close', (code, reason) => {
|
|
810
|
+
// reason can be Buffer or string depending on ws version - handle both
|
|
811
|
+
const reasonStr = reason ? (Buffer.isBuffer(reason) ? reason.toString() : String(reason)) : 'unknown';
|
|
812
|
+
console.log(`[${this.nodeId}] 🔌 Disconnected from registry (code: ${code}, reason: ${reasonStr})`);
|
|
597
813
|
this.isAuthenticated = false;
|
|
598
814
|
this.currentUser = null; // Clear user info on disconnect
|
|
599
|
-
this.
|
|
815
|
+
this.handleRegistryDisconnection();
|
|
600
816
|
});
|
|
601
817
|
});
|
|
602
818
|
}
|
|
@@ -627,7 +843,7 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
627
843
|
}
|
|
628
844
|
}
|
|
629
845
|
/**
|
|
630
|
-
* Register gateway with
|
|
846
|
+
* Register gateway with registry
|
|
631
847
|
* Includes tool discovery for remote automation sessions (if enabled)
|
|
632
848
|
*/
|
|
633
849
|
async registerGateway() {
|
|
@@ -692,14 +908,29 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
692
908
|
if (this.enableLLMRouting) {
|
|
693
909
|
console.log(`[${this.nodeId}] Registering with ${connections.length} connections for LLM routing`);
|
|
694
910
|
}
|
|
695
|
-
|
|
696
|
-
|
|
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));
|
|
697
926
|
}
|
|
698
927
|
}
|
|
699
928
|
/**
|
|
700
|
-
* 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
|
|
701
932
|
*/
|
|
702
|
-
async
|
|
933
|
+
async handleRegistryMessage(data, serverId) {
|
|
703
934
|
try {
|
|
704
935
|
const message = JSON.parse(data);
|
|
705
936
|
// Gateway Type 1 sends both 'event' and 'type' properties for compatibility
|
|
@@ -743,8 +974,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
743
974
|
console.log(`[${this.nodeId}] Scheduling reconnection with new gateway ID in 2 seconds...`);
|
|
744
975
|
setTimeout(async () => {
|
|
745
976
|
try {
|
|
746
|
-
console.log(`[${this.nodeId}] Reconnecting to
|
|
747
|
-
await this.
|
|
977
|
+
console.log(`[${this.nodeId}] Reconnecting to registry with fresh identity...`);
|
|
978
|
+
await this.connectToRegistry();
|
|
748
979
|
await this.registerGateway();
|
|
749
980
|
console.log(`[${this.nodeId}] ✅ Successfully reconnected with new gateway ID`);
|
|
750
981
|
}
|
|
@@ -757,8 +988,15 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
757
988
|
case 'auth_confirmed':
|
|
758
989
|
// Gateway Type 1 sends auth_confirmed after successful authentication
|
|
759
990
|
// It also includes the server-assigned gateway_id and gateway_name
|
|
760
|
-
console.log(`[${this.nodeId}] Authentication confirmed by Gateway Type 1`);
|
|
991
|
+
console.log(`[${this.nodeId}] Authentication confirmed by Gateway Type 1${serverId ? ` (server: ${serverId})` : ''}`);
|
|
761
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
|
+
}
|
|
762
1000
|
// IMPORTANT: Store server-assigned gateway_id for future reconnections
|
|
763
1001
|
// NOTE: gateway_name is NOT stored locally - it's managed server-side only
|
|
764
1002
|
const serverGatewayId = message.gatewayId || message.gateway_id;
|
|
@@ -802,7 +1040,7 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
802
1040
|
this.sendErrorResponse(message.request_id, 'llm_routing_disabled', 'This gateway is not configured for LLM routing');
|
|
803
1041
|
break;
|
|
804
1042
|
}
|
|
805
|
-
await this.handleHttpRequest(message);
|
|
1043
|
+
await this.handleHttpRequest(message, serverId);
|
|
806
1044
|
break;
|
|
807
1045
|
// ============= REMOTE SESSION EVENTS =============
|
|
808
1046
|
case 'session_start':
|
|
@@ -848,15 +1086,15 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
848
1086
|
await this.handleUpdateAvailable(message);
|
|
849
1087
|
break;
|
|
850
1088
|
case 'update_now':
|
|
851
|
-
// Mandatory update command from
|
|
1089
|
+
// Mandatory update command from registry
|
|
852
1090
|
await this.handleUpdateNow(message);
|
|
853
1091
|
break;
|
|
854
1092
|
default:
|
|
855
|
-
console.log(`[${this.nodeId}] Unknown
|
|
1093
|
+
console.log(`[${this.nodeId}] Unknown registry event: ${eventType}`);
|
|
856
1094
|
}
|
|
857
1095
|
}
|
|
858
1096
|
catch (error) {
|
|
859
|
-
console.error(`[${this.nodeId}]
|
|
1097
|
+
console.error(`[${this.nodeId}] Registry message error:`, error);
|
|
860
1098
|
}
|
|
861
1099
|
}
|
|
862
1100
|
/**
|
|
@@ -883,6 +1121,7 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
883
1121
|
console.log(`[Type3-${this.nodeId}] Step 2: Extracted parameters:`, {
|
|
884
1122
|
provider,
|
|
885
1123
|
endpoint,
|
|
1124
|
+
endpointUrl: endpointUrl || 'NOT PROVIDED',
|
|
886
1125
|
model,
|
|
887
1126
|
hasMessages: !!messages,
|
|
888
1127
|
messageCount: messages?.length || 0,
|
|
@@ -950,7 +1189,7 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
950
1189
|
}
|
|
951
1190
|
if (!stream) {
|
|
952
1191
|
console.log(`[Type3-${this.nodeId}] Step 7: Received response from provider`);
|
|
953
|
-
if (this.
|
|
1192
|
+
if (this.registryWs && this.registryWs.readyState === ws_1.default.OPEN) {
|
|
954
1193
|
const responseMessage = {
|
|
955
1194
|
event: 'inference_response',
|
|
956
1195
|
request_id: requestId,
|
|
@@ -961,7 +1200,7 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
961
1200
|
node_id: this.nodeId
|
|
962
1201
|
}
|
|
963
1202
|
};
|
|
964
|
-
this.
|
|
1203
|
+
this.registryWs.send(JSON.stringify(responseMessage));
|
|
965
1204
|
console.log(`[Type3-${this.nodeId}] Step 8: Response sent successfully`);
|
|
966
1205
|
}
|
|
967
1206
|
}
|
|
@@ -974,8 +1213,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
974
1213
|
}
|
|
975
1214
|
catch (error) {
|
|
976
1215
|
console.error(`[Type3-${this.nodeId}] ERROR in request ${requestId}:`, error.message);
|
|
977
|
-
if (this.
|
|
978
|
-
this.
|
|
1216
|
+
if (this.registryWs && this.registryWs.readyState === ws_1.default.OPEN) {
|
|
1217
|
+
this.registryWs.send(JSON.stringify({
|
|
979
1218
|
event: 'inference_error',
|
|
980
1219
|
request_id: requestId,
|
|
981
1220
|
error: {
|
|
@@ -1077,8 +1316,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1077
1316
|
if (line.startsWith('data: ')) {
|
|
1078
1317
|
const data = line.substring(6);
|
|
1079
1318
|
if (data === '[DONE]') {
|
|
1080
|
-
if (this.
|
|
1081
|
-
this.
|
|
1319
|
+
if (this.registryWs && this.registryWs.readyState === ws_1.default.OPEN) {
|
|
1320
|
+
this.registryWs.send(JSON.stringify({
|
|
1082
1321
|
event: 'stream_end',
|
|
1083
1322
|
request_id: requestId,
|
|
1084
1323
|
timestamp: new Date().toISOString()
|
|
@@ -1088,8 +1327,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1088
1327
|
else {
|
|
1089
1328
|
try {
|
|
1090
1329
|
const parsed = JSON.parse(data);
|
|
1091
|
-
if (this.
|
|
1092
|
-
this.
|
|
1330
|
+
if (this.registryWs && this.registryWs.readyState === ws_1.default.OPEN) {
|
|
1331
|
+
this.registryWs.send(JSON.stringify({
|
|
1093
1332
|
event: 'stream_chunk',
|
|
1094
1333
|
request_id: requestId,
|
|
1095
1334
|
data: parsed,
|
|
@@ -1107,8 +1346,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1107
1346
|
return new Promise((resolve, reject) => {
|
|
1108
1347
|
response.data.on('end', () => {
|
|
1109
1348
|
console.log(`[${this.nodeId}] Streaming completed for request ${requestId}`);
|
|
1110
|
-
if (this.
|
|
1111
|
-
this.
|
|
1349
|
+
if (this.registryWs && this.registryWs.readyState === ws_1.default.OPEN) {
|
|
1350
|
+
this.registryWs.send(JSON.stringify({
|
|
1112
1351
|
event: 'stream_complete',
|
|
1113
1352
|
request_id: requestId,
|
|
1114
1353
|
stream_completed: true,
|
|
@@ -1119,8 +1358,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1119
1358
|
});
|
|
1120
1359
|
response.data.on('error', (error) => {
|
|
1121
1360
|
console.error(`[${this.nodeId}] Stream error for request ${requestId}:`, error);
|
|
1122
|
-
if (this.
|
|
1123
|
-
this.
|
|
1361
|
+
if (this.registryWs && this.registryWs.readyState === ws_1.default.OPEN) {
|
|
1362
|
+
this.registryWs.send(JSON.stringify({
|
|
1124
1363
|
event: 'stream_error',
|
|
1125
1364
|
request_id: requestId,
|
|
1126
1365
|
error: {
|
|
@@ -1190,10 +1429,10 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1190
1429
|
* body: { ... } (optional)
|
|
1191
1430
|
* }
|
|
1192
1431
|
*/
|
|
1193
|
-
async handleHttpRequest(message) {
|
|
1432
|
+
async handleHttpRequest(message, sourceServerId) {
|
|
1194
1433
|
const requestId = message.requestId || (0, uuid_1.v4)();
|
|
1195
1434
|
const { connection_id, method, url, headers = {}, body } = message;
|
|
1196
|
-
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})` : ''}`);
|
|
1197
1436
|
try {
|
|
1198
1437
|
// Get API key from local vault if connection_id provided
|
|
1199
1438
|
let requestHeaders = { ...headers };
|
|
@@ -1228,7 +1467,7 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1228
1467
|
catch {
|
|
1229
1468
|
responseData = responseText;
|
|
1230
1469
|
}
|
|
1231
|
-
// Send RAW HTTP response back to
|
|
1470
|
+
// Send RAW HTTP response back to the server that sent the request
|
|
1232
1471
|
const wsResponse = {
|
|
1233
1472
|
event: 'http_response',
|
|
1234
1473
|
requestId: requestId,
|
|
@@ -1238,34 +1477,67 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1238
1477
|
body: responseData, // RAW response body
|
|
1239
1478
|
timestamp: new Date().toISOString()
|
|
1240
1479
|
};
|
|
1241
|
-
if (this.
|
|
1242
|
-
this.
|
|
1243
|
-
|
|
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`);
|
|
1244
1485
|
}
|
|
1245
1486
|
}
|
|
1246
1487
|
catch (error) {
|
|
1247
1488
|
console.error(`[${this.nodeId}] HTTP forwarding error:`, error.message);
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1489
|
+
this.sendToServer(sourceServerId, {
|
|
1490
|
+
event: 'http_response',
|
|
1491
|
+
requestId: requestId,
|
|
1492
|
+
success: false,
|
|
1493
|
+
error: error.message,
|
|
1494
|
+
timestamp: new Date().toISOString()
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
/**
|
|
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;
|
|
1257
1518
|
}
|
|
1519
|
+
return false;
|
|
1258
1520
|
}
|
|
1259
1521
|
/**
|
|
1260
|
-
* Send health status to
|
|
1522
|
+
* Send health status to all connected servers (multi-connect)
|
|
1261
1523
|
*/
|
|
1262
1524
|
sendHealthStatus() {
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
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);
|
|
1269
1541
|
}
|
|
1270
1542
|
const status = {
|
|
1271
1543
|
event: 'health_status',
|
|
@@ -1284,8 +1556,18 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1284
1556
|
},
|
|
1285
1557
|
timestamp: new Date().toISOString()
|
|
1286
1558
|
};
|
|
1287
|
-
|
|
1288
|
-
|
|
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);
|
|
1289
1571
|
}
|
|
1290
1572
|
this.metrics.lastHealthCheck = new Date();
|
|
1291
1573
|
}
|
|
@@ -1318,8 +1600,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1318
1600
|
},
|
|
1319
1601
|
timestamp: new Date().toISOString()
|
|
1320
1602
|
};
|
|
1321
|
-
if (this.
|
|
1322
|
-
this.
|
|
1603
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
1604
|
+
this.registryWs.send(JSON.stringify(rateLimits));
|
|
1323
1605
|
}
|
|
1324
1606
|
}
|
|
1325
1607
|
/**
|
|
@@ -1329,25 +1611,25 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1329
1611
|
startHealthMonitoring() {
|
|
1330
1612
|
this.healthCheckInterval = setInterval(() => {
|
|
1331
1613
|
// Only send health status if connected
|
|
1332
|
-
if (this.
|
|
1614
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
1333
1615
|
this.sendHealthStatus();
|
|
1334
1616
|
}
|
|
1335
1617
|
else if (!this.reconnectInterval && !this.isShuttingDown) {
|
|
1336
1618
|
// Connection lost and no reconnection scheduled - trigger reconnection
|
|
1337
|
-
console.warn(`[${this.nodeId}] Health check: Lost connection to
|
|
1619
|
+
console.warn(`[${this.nodeId}] Health check: Lost connection to registry, triggering reconnection...`);
|
|
1338
1620
|
// Reset reconnect attempts for health-check triggered reconnection
|
|
1339
1621
|
// This ensures we start fresh when health check detects a problem
|
|
1340
1622
|
this.reconnectAttempts = 0;
|
|
1341
|
-
this.
|
|
1623
|
+
this.handleRegistryDisconnection();
|
|
1342
1624
|
}
|
|
1343
1625
|
}, 30000);
|
|
1344
1626
|
}
|
|
1345
1627
|
/**
|
|
1346
|
-
* Handle
|
|
1628
|
+
* Handle registry disconnection with robust retry logic
|
|
1347
1629
|
* This method schedules reconnection attempts with exponential backoff
|
|
1348
1630
|
* and ensures registerGateway() is always called after successful reconnection
|
|
1349
1631
|
*/
|
|
1350
|
-
|
|
1632
|
+
handleRegistryDisconnection() {
|
|
1351
1633
|
if (this.isShuttingDown) {
|
|
1352
1634
|
console.log(`[${this.nodeId}] Shutdown in progress, skipping reconnection`);
|
|
1353
1635
|
return;
|
|
@@ -1372,9 +1654,9 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1372
1654
|
this.reconnectInterval = null; // Clear so we can schedule next attempt if needed
|
|
1373
1655
|
console.log(`[${this.nodeId}] 🔄 Attempting to reconnect (attempt ${this.reconnectAttempts})...`);
|
|
1374
1656
|
try {
|
|
1375
|
-
await this.
|
|
1657
|
+
await this.connectToRegistry();
|
|
1376
1658
|
// Check if connection was successful
|
|
1377
|
-
if (this.
|
|
1659
|
+
if (this.registryWs && this.registryWs.readyState === ws_1.default.OPEN) {
|
|
1378
1660
|
console.log(`[${this.nodeId}] ✅ Reconnected successfully!`);
|
|
1379
1661
|
console.log(`[${this.nodeId}] 📝 Re-registering after reconnection...`);
|
|
1380
1662
|
try {
|
|
@@ -1386,18 +1668,18 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1386
1668
|
catch (regError) {
|
|
1387
1669
|
console.error(`[${this.nodeId}] ❌ Registration failed after reconnect:`, regError.message);
|
|
1388
1670
|
// Schedule another attempt since registration failed
|
|
1389
|
-
this.
|
|
1671
|
+
this.handleRegistryDisconnection();
|
|
1390
1672
|
}
|
|
1391
1673
|
}
|
|
1392
1674
|
else {
|
|
1393
|
-
console.warn(`[${this.nodeId}] ⚠️ WebSocket not open after
|
|
1394
|
-
this.
|
|
1675
|
+
console.warn(`[${this.nodeId}] ⚠️ WebSocket not open after connectToRegistry, scheduling retry...`);
|
|
1676
|
+
this.handleRegistryDisconnection();
|
|
1395
1677
|
}
|
|
1396
1678
|
}
|
|
1397
1679
|
catch (error) {
|
|
1398
1680
|
console.error(`[${this.nodeId}] ❌ Reconnection attempt failed:`, error.message);
|
|
1399
1681
|
// Schedule another reconnection attempt
|
|
1400
|
-
this.
|
|
1682
|
+
this.handleRegistryDisconnection();
|
|
1401
1683
|
}
|
|
1402
1684
|
}, delay);
|
|
1403
1685
|
}
|
|
@@ -1636,6 +1918,7 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1636
1918
|
async handleSessionStart(message) {
|
|
1637
1919
|
const requestId = message.requestId || message.request_id;
|
|
1638
1920
|
console.log(`[${this.nodeId}] Received session_start request: ${requestId}`);
|
|
1921
|
+
console.log(`[${this.nodeId}] session_start received includeEnvironmentInfo: ${message.includeEnvironmentInfo}, systemPrompt defined: ${message.systemPrompt !== undefined}, systemPrompt length: ${message.systemPrompt?.length || 0}`);
|
|
1639
1922
|
if (!this.headlessSessionManager) {
|
|
1640
1923
|
this.sendSessionError(requestId, 'Headless session manager not initialized');
|
|
1641
1924
|
return;
|
|
@@ -1647,6 +1930,7 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1647
1930
|
organizationId: message.organizationId,
|
|
1648
1931
|
templateId: message.templateId,
|
|
1649
1932
|
systemPrompt: message.systemPrompt,
|
|
1933
|
+
includeEnvironmentInfo: message.includeEnvironmentInfo !== false, // Default to true
|
|
1650
1934
|
initialMessage: message.initialMessage,
|
|
1651
1935
|
modelId: message.modelId,
|
|
1652
1936
|
connectionId: message.connectionId,
|
|
@@ -1659,8 +1943,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1659
1943
|
// Send acknowledgment to Type 1
|
|
1660
1944
|
// NOTE: The initialMessage is the assistant's greeting, already added to session history
|
|
1661
1945
|
// We do NOT call processSessionMessageInternal because the greeting doesn't need an LLM response
|
|
1662
|
-
if (this.
|
|
1663
|
-
this.
|
|
1946
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
1947
|
+
this.registryWs.send(JSON.stringify({
|
|
1664
1948
|
event: 'session_started',
|
|
1665
1949
|
requestId,
|
|
1666
1950
|
sessionId: session.sessionId,
|
|
@@ -1699,13 +1983,17 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1699
1983
|
if (needsRestore && sessionRestore) {
|
|
1700
1984
|
const reason = !existingSession ? 'not found in memory' : `in ${existingSession.status} state`;
|
|
1701
1985
|
console.log(`[${this.nodeId}] Session ${sessionId} ${reason}, auto-restoring from Type 1 data`);
|
|
1986
|
+
console.log(`[${this.nodeId}] Session restore data - systemPrompt: ${sessionRestore.systemPrompt ? 'provided' : 'not provided'}, includeEnvironmentInfo: ${sessionRestore.includeEnvironmentInfo}`);
|
|
1702
1987
|
try {
|
|
1703
1988
|
await this.headlessSessionManager.restoreSession(sessionId, {
|
|
1704
1989
|
userId: sessionRestore.userId,
|
|
1705
1990
|
organizationId: sessionRestore.organizationId,
|
|
1706
1991
|
sessionType: sessionRestore.sessionType,
|
|
1707
1992
|
modelId: modelId,
|
|
1708
|
-
messages: sessionRestore.messages || []
|
|
1993
|
+
messages: sessionRestore.messages || [],
|
|
1994
|
+
// Pass system prompt configuration from session metadata for proper restore
|
|
1995
|
+
systemPrompt: sessionRestore.systemPrompt,
|
|
1996
|
+
includeEnvironmentInfo: sessionRestore.includeEnvironmentInfo
|
|
1709
1997
|
});
|
|
1710
1998
|
console.log(`[${this.nodeId}] Session ${sessionId} restored/reactivated successfully`);
|
|
1711
1999
|
}
|
|
@@ -1725,8 +2013,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1725
2013
|
try {
|
|
1726
2014
|
// Stream handler to send chunks to Type 1
|
|
1727
2015
|
const onChunk = (chunk) => {
|
|
1728
|
-
if (this.
|
|
1729
|
-
this.
|
|
2016
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
2017
|
+
this.registryWs.send(JSON.stringify({
|
|
1730
2018
|
event: 'session_chunk',
|
|
1731
2019
|
requestId,
|
|
1732
2020
|
sessionId,
|
|
@@ -1738,18 +2026,26 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1738
2026
|
const response = await this.headlessSessionManager.processMessage(sessionId, userMessage, onChunk, modelId // Pass per-message model ID
|
|
1739
2027
|
);
|
|
1740
2028
|
// Send final response to Type 1
|
|
1741
|
-
if (this.
|
|
1742
|
-
this.
|
|
2029
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
2030
|
+
this.registryWs.send(JSON.stringify({
|
|
1743
2031
|
event: 'session_response',
|
|
1744
2032
|
requestId,
|
|
1745
2033
|
sessionId,
|
|
1746
2034
|
status: response.status,
|
|
1747
|
-
message: response.message,
|
|
1748
|
-
toolResults: response.toolResults,
|
|
1749
2035
|
isComplete: response.isComplete,
|
|
1750
2036
|
error: response.error,
|
|
1751
|
-
|
|
1752
|
-
|
|
2037
|
+
// New structured fields for Type 1
|
|
2038
|
+
assistant_message: response.assistant_message,
|
|
2039
|
+
tool_calls: response.tool_calls,
|
|
2040
|
+
tools_available: response.tools_available,
|
|
2041
|
+
tools_enabled: response.tools_enabled,
|
|
2042
|
+
llm_iterations: response.llm_iterations,
|
|
2043
|
+
request_log_ids: response.request_log_ids,
|
|
2044
|
+
usage: response.usage,
|
|
2045
|
+
// Legacy fields for backward compatibility
|
|
2046
|
+
message: response.message,
|
|
2047
|
+
toolResults: response.toolResults,
|
|
2048
|
+
requestIds: response.requestIds || response.request_log_ids,
|
|
1753
2049
|
timestamp: new Date().toISOString()
|
|
1754
2050
|
}));
|
|
1755
2051
|
}
|
|
@@ -1771,8 +2067,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1771
2067
|
return;
|
|
1772
2068
|
}
|
|
1773
2069
|
const success = await this.headlessSessionManager.cancelSession(sessionId);
|
|
1774
|
-
if (this.
|
|
1775
|
-
this.
|
|
2070
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
2071
|
+
this.registryWs.send(JSON.stringify({
|
|
1776
2072
|
event: 'session_cancelled',
|
|
1777
2073
|
requestId,
|
|
1778
2074
|
sessionId,
|
|
@@ -1788,8 +2084,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1788
2084
|
const requestId = message.requestId || message.request_id;
|
|
1789
2085
|
console.log(`[${this.nodeId}] Received get_tools request`);
|
|
1790
2086
|
const tools = this.headlessSessionManager?.getAvailableTools() || [];
|
|
1791
|
-
if (this.
|
|
1792
|
-
this.
|
|
2087
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
2088
|
+
this.registryWs.send(JSON.stringify({
|
|
1793
2089
|
event: 'tools_response',
|
|
1794
2090
|
requestId,
|
|
1795
2091
|
gatewayId: this.instanceId,
|
|
@@ -1802,8 +2098,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1802
2098
|
* Send session error to Type 1
|
|
1803
2099
|
*/
|
|
1804
2100
|
sendSessionError(requestId, error, sessionId) {
|
|
1805
|
-
if (this.
|
|
1806
|
-
this.
|
|
2101
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
2102
|
+
this.registryWs.send(JSON.stringify({
|
|
1807
2103
|
event: 'session_error',
|
|
1808
2104
|
requestId,
|
|
1809
2105
|
sessionId,
|
|
@@ -1816,8 +2112,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
1816
2112
|
* Send inference error response to Type 1
|
|
1817
2113
|
*/
|
|
1818
2114
|
sendErrorResponse(requestId, code, message) {
|
|
1819
|
-
if (this.
|
|
1820
|
-
this.
|
|
2115
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
2116
|
+
this.registryWs.send(JSON.stringify({
|
|
1821
2117
|
event: 'inference_error',
|
|
1822
2118
|
request_id: requestId,
|
|
1823
2119
|
error: {
|
|
@@ -2021,8 +2317,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
2021
2317
|
});
|
|
2022
2318
|
console.log(`[${this.nodeId}] Script execution completed: ${result.success ? 'success' : 'failed'} (exit code: ${result.exit_code})`);
|
|
2023
2319
|
// Send result back to Type 1
|
|
2024
|
-
if (this.
|
|
2025
|
-
this.
|
|
2320
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
2321
|
+
this.registryWs.send(JSON.stringify({
|
|
2026
2322
|
event: 'script_result',
|
|
2027
2323
|
requestId,
|
|
2028
2324
|
success: true,
|
|
@@ -2046,8 +2342,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
2046
2342
|
* Send script execution error to Type 1
|
|
2047
2343
|
*/
|
|
2048
2344
|
sendScriptError(requestId, code, message) {
|
|
2049
|
-
if (this.
|
|
2050
|
-
this.
|
|
2345
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
2346
|
+
this.registryWs.send(JSON.stringify({
|
|
2051
2347
|
event: 'script_result',
|
|
2052
2348
|
requestId,
|
|
2053
2349
|
success: false,
|
|
@@ -2079,8 +2375,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
2079
2375
|
// autoUpdate defaults to true if not explicitly set to false
|
|
2080
2376
|
const shouldAutoUpdate = mandatory || (autoUpdate !== false);
|
|
2081
2377
|
// Send acknowledgment to Type 1
|
|
2082
|
-
if (this.
|
|
2083
|
-
this.
|
|
2378
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
2379
|
+
this.registryWs.send(JSON.stringify({
|
|
2084
2380
|
event: 'update_acknowledged',
|
|
2085
2381
|
gatewayId: this.instanceId,
|
|
2086
2382
|
currentVersion: this.currentVersion,
|
|
@@ -2115,8 +2411,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
2115
2411
|
console.log(`[${this.nodeId}] 🚨 Immediate update requested: v${version}`);
|
|
2116
2412
|
console.log(`[${this.nodeId}] Reason: ${reason || 'No reason provided'}`);
|
|
2117
2413
|
// Acknowledge the update command
|
|
2118
|
-
if (this.
|
|
2119
|
-
this.
|
|
2414
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
2415
|
+
this.registryWs.send(JSON.stringify({
|
|
2120
2416
|
event: 'update_starting',
|
|
2121
2417
|
gatewayId: this.instanceId,
|
|
2122
2418
|
currentVersion: this.currentVersion,
|
|
@@ -2227,8 +2523,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
2227
2523
|
if (resolvedTargetVersion && this.currentVersion === resolvedTargetVersion) {
|
|
2228
2524
|
console.log(`[${this.nodeId}] ✅ Already at version v${this.currentVersion}, skipping update`);
|
|
2229
2525
|
// Notify Type 1 that no update is needed
|
|
2230
|
-
if (this.
|
|
2231
|
-
this.
|
|
2526
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
2527
|
+
this.registryWs.send(JSON.stringify({
|
|
2232
2528
|
event: 'already_up_to_date',
|
|
2233
2529
|
gatewayId: this.instanceId,
|
|
2234
2530
|
currentVersion: this.currentVersion,
|
|
@@ -2270,8 +2566,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
2270
2566
|
}
|
|
2271
2567
|
console.log(`[${this.nodeId}] ✓ Package installed successfully`);
|
|
2272
2568
|
// Step 2: Notify Type 1 of successful install
|
|
2273
|
-
if (this.
|
|
2274
|
-
this.
|
|
2569
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
2570
|
+
this.registryWs.send(JSON.stringify({
|
|
2275
2571
|
event: 'update_installed',
|
|
2276
2572
|
gatewayId: this.instanceId,
|
|
2277
2573
|
previousVersion: this.currentVersion,
|
|
@@ -2283,8 +2579,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
2283
2579
|
// Step 3: Graceful restart
|
|
2284
2580
|
console.log(`[${this.nodeId}] Step 2: Initiating graceful restart...`);
|
|
2285
2581
|
// Close WebSocket cleanly
|
|
2286
|
-
if (this.
|
|
2287
|
-
this.
|
|
2582
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
2583
|
+
this.registryWs.send(JSON.stringify({
|
|
2288
2584
|
event: 'gateway_restarting',
|
|
2289
2585
|
gatewayId: this.instanceId,
|
|
2290
2586
|
reason: 'update',
|
|
@@ -2333,8 +2629,8 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
2333
2629
|
console.error(`[${this.nodeId}] ❌ Update failed:`, error.message);
|
|
2334
2630
|
this.isUpdating = false;
|
|
2335
2631
|
// Notify Type 1 of failure
|
|
2336
|
-
if (this.
|
|
2337
|
-
this.
|
|
2632
|
+
if (this.registryWs?.readyState === ws_1.default.OPEN) {
|
|
2633
|
+
this.registryWs.send(JSON.stringify({
|
|
2338
2634
|
event: 'update_failed',
|
|
2339
2635
|
gatewayId: this.instanceId,
|
|
2340
2636
|
currentVersion: this.currentVersion,
|
|
@@ -2384,9 +2680,9 @@ class Type3GatewayServer extends events_1.EventEmitter {
|
|
|
2384
2680
|
console.log(`[${this.nodeId}] Waiting for ${this.activeRequests.size} active requests...`);
|
|
2385
2681
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
2386
2682
|
}
|
|
2387
|
-
// Close
|
|
2388
|
-
if (this.
|
|
2389
|
-
this.
|
|
2683
|
+
// Close registry WebSocket
|
|
2684
|
+
if (this.registryWs) {
|
|
2685
|
+
this.registryWs.close();
|
|
2390
2686
|
}
|
|
2391
2687
|
// Close management API server
|
|
2392
2688
|
if (this.managementServer) {
|
|
@@ -2407,7 +2703,7 @@ exports.Type3GatewayServer = Type3GatewayServer;
|
|
|
2407
2703
|
if (require.main === module) {
|
|
2408
2704
|
const gateway = new Type3GatewayServer({
|
|
2409
2705
|
port: parseInt(process.env.GATEWAY_PORT || '8083'),
|
|
2410
|
-
|
|
2706
|
+
registryUrl: process.env.REGISTRY_URL,
|
|
2411
2707
|
instanceId: process.env.INSTANCE_ID,
|
|
2412
2708
|
apiKey: process.env.GATEWAY_API_KEY,
|
|
2413
2709
|
vaultPath: process.env.VAULT_PATH,
|