@juspay/neurolink 3.0.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -4
- package/README.md +235 -2
- package/dist/agent/direct-tools.d.ts +6 -6
- package/dist/chat/client-utils.d.ts +92 -0
- package/dist/chat/client-utils.js +298 -0
- package/dist/chat/index.d.ts +27 -0
- package/dist/chat/index.js +41 -0
- package/dist/chat/session-storage.d.ts +77 -0
- package/dist/chat/session-storage.js +233 -0
- package/dist/chat/session.d.ts +95 -0
- package/dist/chat/session.js +257 -0
- package/dist/chat/sse-handler.d.ts +49 -0
- package/dist/chat/sse-handler.js +266 -0
- package/dist/chat/types.d.ts +73 -0
- package/dist/chat/types.js +5 -0
- package/dist/chat/websocket-chat-handler.d.ts +36 -0
- package/dist/chat/websocket-chat-handler.js +262 -0
- package/dist/cli/commands/config.js +12 -12
- package/dist/cli/commands/mcp.js +3 -4
- package/dist/cli/index.d.ts +0 -7
- package/dist/cli/index.js +256 -27
- package/dist/config/configManager.d.ts +60 -0
- package/dist/config/configManager.js +300 -0
- package/dist/config/types.d.ts +136 -0
- package/dist/config/types.js +43 -0
- package/dist/core/analytics.d.ts +23 -0
- package/dist/core/analytics.js +131 -0
- package/dist/core/constants.d.ts +41 -0
- package/dist/core/constants.js +50 -0
- package/dist/core/defaults.d.ts +18 -0
- package/dist/core/defaults.js +29 -0
- package/dist/core/evaluation-config.d.ts +29 -0
- package/dist/core/evaluation-config.js +144 -0
- package/dist/core/evaluation-providers.d.ts +30 -0
- package/dist/core/evaluation-providers.js +187 -0
- package/dist/core/evaluation.d.ts +117 -0
- package/dist/core/evaluation.js +528 -0
- package/dist/core/factory.js +33 -25
- package/dist/core/types.d.ts +165 -6
- package/dist/core/types.js +3 -4
- package/dist/index.d.ts +9 -4
- package/dist/index.js +25 -4
- package/dist/lib/agent/direct-tools.d.ts +6 -6
- package/dist/lib/chat/client-utils.d.ts +92 -0
- package/dist/lib/chat/client-utils.js +298 -0
- package/dist/lib/chat/index.d.ts +27 -0
- package/dist/lib/chat/index.js +41 -0
- package/dist/lib/chat/session-storage.d.ts +77 -0
- package/dist/lib/chat/session-storage.js +233 -0
- package/dist/lib/chat/session.d.ts +95 -0
- package/dist/lib/chat/session.js +257 -0
- package/dist/lib/chat/sse-handler.d.ts +49 -0
- package/dist/lib/chat/sse-handler.js +266 -0
- package/dist/lib/chat/types.d.ts +73 -0
- package/dist/lib/chat/types.js +5 -0
- package/dist/lib/chat/websocket-chat-handler.d.ts +36 -0
- package/dist/lib/chat/websocket-chat-handler.js +262 -0
- package/dist/lib/config/configManager.d.ts +60 -0
- package/dist/lib/config/configManager.js +300 -0
- package/dist/lib/config/types.d.ts +136 -0
- package/dist/lib/config/types.js +43 -0
- package/dist/lib/core/analytics.d.ts +23 -0
- package/dist/lib/core/analytics.js +131 -0
- package/dist/lib/core/constants.d.ts +41 -0
- package/dist/lib/core/constants.js +50 -0
- package/dist/lib/core/defaults.d.ts +18 -0
- package/dist/lib/core/defaults.js +29 -0
- package/dist/lib/core/evaluation-config.d.ts +29 -0
- package/dist/lib/core/evaluation-config.js +144 -0
- package/dist/lib/core/evaluation-providers.d.ts +30 -0
- package/dist/lib/core/evaluation-providers.js +187 -0
- package/dist/lib/core/evaluation.d.ts +117 -0
- package/dist/lib/core/evaluation.js +528 -0
- package/dist/lib/core/factory.js +33 -26
- package/dist/lib/core/types.d.ts +165 -6
- package/dist/lib/core/types.js +3 -4
- package/dist/lib/index.d.ts +9 -4
- package/dist/lib/index.js +25 -4
- package/dist/lib/mcp/contracts/mcpContract.d.ts +118 -0
- package/dist/lib/mcp/contracts/mcpContract.js +5 -0
- package/dist/lib/mcp/function-calling.js +11 -3
- package/dist/lib/mcp/logging.js +5 -0
- package/dist/lib/mcp/neurolink-mcp-client.js +2 -1
- package/dist/lib/mcp/orchestrator.js +18 -9
- package/dist/lib/mcp/registry.d.ts +49 -16
- package/dist/lib/mcp/registry.js +80 -6
- package/dist/lib/mcp/servers/ai-providers/ai-workflow-tools.js +5 -4
- package/dist/lib/mcp/tool-integration.js +1 -1
- package/dist/lib/mcp/tool-registry.d.ts +55 -34
- package/dist/lib/mcp/tool-registry.js +111 -97
- package/dist/lib/mcp/unified-mcp.js +6 -1
- package/dist/lib/mcp/unified-registry.d.ts +12 -4
- package/dist/lib/mcp/unified-registry.js +17 -4
- package/dist/lib/neurolink.d.ts +28 -0
- package/dist/lib/neurolink.js +48 -4
- package/dist/lib/providers/agent-enhanced-provider.d.ts +11 -2
- package/dist/lib/providers/agent-enhanced-provider.js +86 -15
- package/dist/lib/providers/amazonBedrock.d.ts +9 -1
- package/dist/lib/providers/amazonBedrock.js +26 -2
- package/dist/lib/providers/analytics-helper.d.ts +53 -0
- package/dist/lib/providers/analytics-helper.js +151 -0
- package/dist/lib/providers/anthropic.d.ts +11 -1
- package/dist/lib/providers/anthropic.js +29 -4
- package/dist/lib/providers/azureOpenAI.d.ts +3 -1
- package/dist/lib/providers/azureOpenAI.js +28 -4
- package/dist/lib/providers/function-calling-provider.d.ts +9 -1
- package/dist/lib/providers/function-calling-provider.js +14 -1
- package/dist/lib/providers/googleAIStudio.d.ts +15 -1
- package/dist/lib/providers/googleAIStudio.js +32 -2
- package/dist/lib/providers/googleVertexAI.d.ts +9 -1
- package/dist/lib/providers/googleVertexAI.js +31 -2
- package/dist/lib/providers/huggingFace.d.ts +3 -1
- package/dist/lib/providers/huggingFace.js +26 -3
- package/dist/lib/providers/mcp-provider.d.ts +9 -1
- package/dist/lib/providers/mcp-provider.js +12 -0
- package/dist/lib/providers/mistralAI.d.ts +3 -1
- package/dist/lib/providers/mistralAI.js +25 -2
- package/dist/lib/providers/ollama.d.ts +3 -1
- package/dist/lib/providers/ollama.js +27 -4
- package/dist/lib/providers/openAI.d.ts +15 -1
- package/dist/lib/providers/openAI.js +32 -2
- package/dist/lib/proxy/proxy-fetch.js +8 -7
- package/dist/lib/services/streaming/streaming-manager.d.ts +29 -0
- package/dist/lib/services/streaming/streaming-manager.js +244 -0
- package/dist/lib/services/types.d.ts +155 -0
- package/dist/lib/services/types.js +2 -0
- package/dist/lib/services/websocket/websocket-server.d.ts +34 -0
- package/dist/lib/services/websocket/websocket-server.js +304 -0
- package/dist/lib/telemetry/index.d.ts +15 -0
- package/dist/lib/telemetry/index.js +22 -0
- package/dist/lib/telemetry/telemetry-service.d.ts +47 -0
- package/dist/lib/telemetry/telemetry-service.js +259 -0
- package/dist/lib/utils/streaming-utils.d.ts +67 -0
- package/dist/lib/utils/streaming-utils.js +201 -0
- package/dist/mcp/contracts/mcpContract.d.ts +118 -0
- package/dist/mcp/contracts/mcpContract.js +5 -0
- package/dist/mcp/function-calling.js +11 -3
- package/dist/mcp/logging.js +5 -0
- package/dist/mcp/neurolink-mcp-client.js +2 -1
- package/dist/mcp/orchestrator.js +18 -9
- package/dist/mcp/registry.d.ts +49 -16
- package/dist/mcp/registry.js +80 -6
- package/dist/mcp/servers/ai-providers/ai-workflow-tools.d.ts +2 -2
- package/dist/mcp/servers/ai-providers/ai-workflow-tools.js +5 -4
- package/dist/mcp/tool-integration.js +1 -1
- package/dist/mcp/tool-registry.d.ts +55 -34
- package/dist/mcp/tool-registry.js +111 -97
- package/dist/mcp/unified-mcp.js +6 -1
- package/dist/mcp/unified-registry.d.ts +12 -4
- package/dist/mcp/unified-registry.js +17 -4
- package/dist/neurolink.d.ts +28 -0
- package/dist/neurolink.js +48 -4
- package/dist/providers/agent-enhanced-provider.d.ts +11 -2
- package/dist/providers/agent-enhanced-provider.js +86 -15
- package/dist/providers/amazonBedrock.d.ts +9 -1
- package/dist/providers/amazonBedrock.js +26 -2
- package/dist/providers/analytics-helper.d.ts +53 -0
- package/dist/providers/analytics-helper.js +151 -0
- package/dist/providers/anthropic.d.ts +11 -1
- package/dist/providers/anthropic.js +29 -4
- package/dist/providers/azureOpenAI.d.ts +3 -1
- package/dist/providers/azureOpenAI.js +29 -4
- package/dist/providers/function-calling-provider.d.ts +9 -1
- package/dist/providers/function-calling-provider.js +14 -1
- package/dist/providers/googleAIStudio.d.ts +15 -1
- package/dist/providers/googleAIStudio.js +32 -2
- package/dist/providers/googleVertexAI.d.ts +9 -1
- package/dist/providers/googleVertexAI.js +31 -2
- package/dist/providers/huggingFace.d.ts +3 -1
- package/dist/providers/huggingFace.js +26 -3
- package/dist/providers/mcp-provider.d.ts +9 -1
- package/dist/providers/mcp-provider.js +12 -0
- package/dist/providers/mistralAI.d.ts +3 -1
- package/dist/providers/mistralAI.js +25 -2
- package/dist/providers/ollama.d.ts +3 -1
- package/dist/providers/ollama.js +27 -4
- package/dist/providers/openAI.d.ts +15 -1
- package/dist/providers/openAI.js +33 -2
- package/dist/proxy/proxy-fetch.js +8 -7
- package/dist/services/streaming/streaming-manager.d.ts +29 -0
- package/dist/services/streaming/streaming-manager.js +244 -0
- package/dist/services/types.d.ts +155 -0
- package/dist/services/types.js +2 -0
- package/dist/services/websocket/websocket-server.d.ts +34 -0
- package/dist/services/websocket/websocket-server.js +304 -0
- package/dist/telemetry/index.d.ts +15 -0
- package/dist/telemetry/index.js +22 -0
- package/dist/telemetry/telemetry-service.d.ts +47 -0
- package/dist/telemetry/telemetry-service.js +261 -0
- package/dist/utils/streaming-utils.d.ts +67 -0
- package/dist/utils/streaming-utils.js +201 -0
- package/package.json +18 -2
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { WebSocketServer, WebSocket } from "ws";
|
|
2
|
+
import { EventEmitter } from "events";
|
|
3
|
+
import { randomUUID } from "crypto";
|
|
4
|
+
export class NeuroLinkWebSocketServer extends EventEmitter {
|
|
5
|
+
wss;
|
|
6
|
+
connections = new Map();
|
|
7
|
+
connectionInfo = new Map();
|
|
8
|
+
rooms = new Map();
|
|
9
|
+
streamingChannels = new Map();
|
|
10
|
+
heartbeatInterval;
|
|
11
|
+
options;
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
super();
|
|
14
|
+
this.options = {
|
|
15
|
+
port: options.port || 8080,
|
|
16
|
+
maxConnections: options.maxConnections || 1000,
|
|
17
|
+
heartbeatInterval: options.heartbeatInterval || 30000,
|
|
18
|
+
enableCompression: options.enableCompression ?? true,
|
|
19
|
+
enableBackpressure: options.enableBackpressure ?? true,
|
|
20
|
+
bufferSize: options.bufferSize || 8192,
|
|
21
|
+
timeoutMs: options.timeoutMs || 30000,
|
|
22
|
+
};
|
|
23
|
+
this.wss = new WebSocketServer({
|
|
24
|
+
port: this.options.port,
|
|
25
|
+
perMessageDeflate: this.options.enableCompression,
|
|
26
|
+
});
|
|
27
|
+
this.setupEventHandlers();
|
|
28
|
+
this.startHeartbeat();
|
|
29
|
+
}
|
|
30
|
+
setupEventHandlers() {
|
|
31
|
+
this.wss.on("connection", (ws, request) => {
|
|
32
|
+
this.handleConnection(ws, request);
|
|
33
|
+
});
|
|
34
|
+
this.wss.on("error", (error) => {
|
|
35
|
+
console.error("[WebSocket Server] Error:", error);
|
|
36
|
+
this.emit("error", error);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
handleConnection(ws, request) {
|
|
40
|
+
if (this.connections.size >= this.options.maxConnections) {
|
|
41
|
+
ws.close(1013, "Server at capacity");
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const connectionId = randomUUID();
|
|
45
|
+
const userAgent = request.headers["user-agent"];
|
|
46
|
+
const ipAddress = request.socket.remoteAddress;
|
|
47
|
+
// Store connection
|
|
48
|
+
this.connections.set(connectionId, ws);
|
|
49
|
+
this.connectionInfo.set(connectionId, {
|
|
50
|
+
id: connectionId,
|
|
51
|
+
userAgent,
|
|
52
|
+
ipAddress,
|
|
53
|
+
connectedAt: Date.now(),
|
|
54
|
+
lastActivity: Date.now(),
|
|
55
|
+
rooms: new Set(),
|
|
56
|
+
subscriptions: new Set(),
|
|
57
|
+
metadata: {},
|
|
58
|
+
});
|
|
59
|
+
// Setup WebSocket event handlers
|
|
60
|
+
ws.on("message", (data) => {
|
|
61
|
+
this.handleMessage(connectionId, data);
|
|
62
|
+
});
|
|
63
|
+
ws.on("close", () => {
|
|
64
|
+
this.handleDisconnection(connectionId);
|
|
65
|
+
});
|
|
66
|
+
ws.on("error", (error) => {
|
|
67
|
+
console.error(`[WebSocket] Connection ${connectionId} error:`, error);
|
|
68
|
+
this.handleDisconnection(connectionId);
|
|
69
|
+
});
|
|
70
|
+
// Send connection confirmation
|
|
71
|
+
this.sendMessage(connectionId, {
|
|
72
|
+
id: randomUUID(),
|
|
73
|
+
type: "system",
|
|
74
|
+
connectionId,
|
|
75
|
+
timestamp: Date.now(),
|
|
76
|
+
data: {
|
|
77
|
+
event: "connected",
|
|
78
|
+
connectionId,
|
|
79
|
+
serverInfo: {
|
|
80
|
+
version: "3.0.1",
|
|
81
|
+
features: ["streaming", "rooms", "tools"],
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
console.log(`[WebSocket] New connection: ${connectionId} (${this.connections.size}/${this.options.maxConnections})`);
|
|
86
|
+
this.emit("connection", { connectionId, userAgent, ipAddress });
|
|
87
|
+
}
|
|
88
|
+
handleMessage(connectionId, data) {
|
|
89
|
+
try {
|
|
90
|
+
const message = JSON.parse(data.toString());
|
|
91
|
+
this.updateLastActivity(connectionId);
|
|
92
|
+
switch (message.type) {
|
|
93
|
+
case "heartbeat":
|
|
94
|
+
this.handleHeartbeat(connectionId);
|
|
95
|
+
break;
|
|
96
|
+
case "chat":
|
|
97
|
+
this.handleChatMessage(connectionId, message);
|
|
98
|
+
break;
|
|
99
|
+
default:
|
|
100
|
+
this.emit("message", { connectionId, message });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
console.error(`[WebSocket] Invalid message from ${connectionId}:`, error);
|
|
105
|
+
this.sendError(connectionId, "Invalid message format");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
handleDisconnection(connectionId) {
|
|
109
|
+
const connection = this.connectionInfo.get(connectionId);
|
|
110
|
+
if (!connection) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
// Leave all rooms
|
|
114
|
+
connection.rooms.forEach((roomId) => {
|
|
115
|
+
this.leaveRoom(connectionId, roomId);
|
|
116
|
+
});
|
|
117
|
+
// Close streaming channels
|
|
118
|
+
this.streamingChannels.forEach((channel, channelId) => {
|
|
119
|
+
if (channel.connectionId === connectionId) {
|
|
120
|
+
channel.onClose();
|
|
121
|
+
this.streamingChannels.delete(channelId);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
// Clean up
|
|
125
|
+
this.connections.delete(connectionId);
|
|
126
|
+
this.connectionInfo.delete(connectionId);
|
|
127
|
+
console.log(`[WebSocket] Disconnected: ${connectionId} (${this.connections.size}/${this.options.maxConnections})`);
|
|
128
|
+
this.emit("disconnection", { connectionId });
|
|
129
|
+
}
|
|
130
|
+
// Room Management
|
|
131
|
+
joinRoom(connectionId, roomId) {
|
|
132
|
+
const connection = this.connectionInfo.get(connectionId);
|
|
133
|
+
if (!connection) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
if (!this.rooms.has(roomId)) {
|
|
137
|
+
this.rooms.set(roomId, new Set());
|
|
138
|
+
}
|
|
139
|
+
this.rooms.get(roomId).add(connectionId);
|
|
140
|
+
connection.rooms.add(roomId);
|
|
141
|
+
this.sendMessage(connectionId, {
|
|
142
|
+
id: randomUUID(),
|
|
143
|
+
type: "system",
|
|
144
|
+
connectionId,
|
|
145
|
+
roomId,
|
|
146
|
+
timestamp: Date.now(),
|
|
147
|
+
data: {
|
|
148
|
+
event: "room_joined",
|
|
149
|
+
roomId,
|
|
150
|
+
memberCount: this.rooms.get(roomId).size,
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
console.log(`[WebSocket] ${connectionId} joined room ${roomId}`);
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
leaveRoom(connectionId, roomId) {
|
|
157
|
+
const connection = this.connectionInfo.get(connectionId);
|
|
158
|
+
if (!connection) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
const room = this.rooms.get(roomId);
|
|
162
|
+
if (!room) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
room.delete(connectionId);
|
|
166
|
+
connection.rooms.delete(roomId);
|
|
167
|
+
if (room.size === 0) {
|
|
168
|
+
this.rooms.delete(roomId);
|
|
169
|
+
}
|
|
170
|
+
this.sendMessage(connectionId, {
|
|
171
|
+
id: randomUUID(),
|
|
172
|
+
type: "system",
|
|
173
|
+
connectionId,
|
|
174
|
+
roomId,
|
|
175
|
+
timestamp: Date.now(),
|
|
176
|
+
data: {
|
|
177
|
+
event: "room_left",
|
|
178
|
+
roomId,
|
|
179
|
+
memberCount: room.size,
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
console.log(`[WebSocket] ${connectionId} left room ${roomId}`);
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
broadcastToRoom(roomId, message) {
|
|
186
|
+
const room = this.rooms.get(roomId);
|
|
187
|
+
if (!room) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
room.forEach((connectionId) => {
|
|
191
|
+
this.sendMessage(connectionId, { ...message, roomId });
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
// Streaming Support
|
|
195
|
+
createStreamingChannel(connectionId, channelId) {
|
|
196
|
+
const channel = {
|
|
197
|
+
id: channelId,
|
|
198
|
+
connectionId,
|
|
199
|
+
type: "ai-response",
|
|
200
|
+
status: "open",
|
|
201
|
+
buffer: {
|
|
202
|
+
data: [],
|
|
203
|
+
maxSize: this.options.bufferSize,
|
|
204
|
+
currentSize: 0,
|
|
205
|
+
flushThreshold: Math.floor(this.options.bufferSize * 0.8),
|
|
206
|
+
lastFlush: Date.now(),
|
|
207
|
+
},
|
|
208
|
+
onData: (data) => this.handleChannelData(channelId, data),
|
|
209
|
+
onError: (error) => this.handleChannelError(channelId, error),
|
|
210
|
+
onClose: () => this.handleChannelClose(channelId),
|
|
211
|
+
};
|
|
212
|
+
this.streamingChannels.set(channelId, channel);
|
|
213
|
+
return channel;
|
|
214
|
+
}
|
|
215
|
+
destroyStreamingChannel(channelId) {
|
|
216
|
+
const channel = this.streamingChannels.get(channelId);
|
|
217
|
+
if (channel) {
|
|
218
|
+
channel.status = "closed";
|
|
219
|
+
channel.onClose();
|
|
220
|
+
this.streamingChannels.delete(channelId);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// Helper methods
|
|
224
|
+
sendMessage(connectionId, message) {
|
|
225
|
+
const ws = this.connections.get(connectionId);
|
|
226
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
try {
|
|
230
|
+
ws.send(JSON.stringify(message));
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
console.error(`[WebSocket] Failed to send message to ${connectionId}:`, error);
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
sendError(connectionId, errorMessage) {
|
|
239
|
+
this.sendMessage(connectionId, {
|
|
240
|
+
id: randomUUID(),
|
|
241
|
+
type: "error",
|
|
242
|
+
connectionId,
|
|
243
|
+
timestamp: Date.now(),
|
|
244
|
+
data: { error: errorMessage },
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
updateLastActivity(connectionId) {
|
|
248
|
+
const connection = this.connectionInfo.get(connectionId);
|
|
249
|
+
if (connection) {
|
|
250
|
+
connection.lastActivity = Date.now();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
handleHeartbeat(connectionId) {
|
|
254
|
+
this.sendMessage(connectionId, {
|
|
255
|
+
id: randomUUID(),
|
|
256
|
+
type: "heartbeat",
|
|
257
|
+
connectionId,
|
|
258
|
+
timestamp: Date.now(),
|
|
259
|
+
data: { pong: true },
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
handleChatMessage(connectionId, message) {
|
|
263
|
+
this.emit("chat-message", { connectionId, message });
|
|
264
|
+
}
|
|
265
|
+
startHeartbeat() {
|
|
266
|
+
this.heartbeatInterval = setInterval(() => {
|
|
267
|
+
this.connections.forEach((ws, connectionId) => {
|
|
268
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
269
|
+
const connection = this.connectionInfo.get(connectionId);
|
|
270
|
+
if (connection &&
|
|
271
|
+
Date.now() - connection.lastActivity > this.options.timeoutMs) {
|
|
272
|
+
console.log(`[WebSocket] Timeout for connection ${connectionId}`);
|
|
273
|
+
ws.terminate();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
}, this.options.heartbeatInterval);
|
|
278
|
+
}
|
|
279
|
+
// Public getters
|
|
280
|
+
getConnectionCount() {
|
|
281
|
+
return this.connections.size;
|
|
282
|
+
}
|
|
283
|
+
getRoomCount() {
|
|
284
|
+
return this.rooms.size;
|
|
285
|
+
}
|
|
286
|
+
getActiveChannels() {
|
|
287
|
+
return this.streamingChannels.size;
|
|
288
|
+
}
|
|
289
|
+
close() {
|
|
290
|
+
if (this.heartbeatInterval) {
|
|
291
|
+
clearInterval(this.heartbeatInterval);
|
|
292
|
+
}
|
|
293
|
+
this.wss.close();
|
|
294
|
+
}
|
|
295
|
+
handleChannelData(channelId, data) {
|
|
296
|
+
// Implementation for channel data handling
|
|
297
|
+
}
|
|
298
|
+
handleChannelError(channelId, error) {
|
|
299
|
+
console.error(`[Streaming Channel] ${channelId} error:`, error);
|
|
300
|
+
}
|
|
301
|
+
handleChannelClose(channelId) {
|
|
302
|
+
console.log(`[Streaming Channel] ${channelId} closed`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { TelemetryService, type HealthMetrics } from "./telemetry-service.js";
|
|
2
|
+
/**
|
|
3
|
+
* Initialize telemetry for NeuroLink
|
|
4
|
+
* OPTIONAL - Only works when NEUROLINK_TELEMETRY_ENABLED=true
|
|
5
|
+
*/
|
|
6
|
+
export declare function initializeTelemetry(): Promise<import("./telemetry-service.js").TelemetryService>;
|
|
7
|
+
/**
|
|
8
|
+
* Get telemetry status
|
|
9
|
+
*/
|
|
10
|
+
export declare function getTelemetryStatus(): Promise<{
|
|
11
|
+
enabled: boolean;
|
|
12
|
+
endpoint?: string;
|
|
13
|
+
service?: string;
|
|
14
|
+
version?: string;
|
|
15
|
+
}>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Optional Telemetry Infrastructure (Phase 2)
|
|
2
|
+
export { TelemetryService } from "./telemetry-service.js";
|
|
3
|
+
/**
|
|
4
|
+
* Initialize telemetry for NeuroLink
|
|
5
|
+
* OPTIONAL - Only works when NEUROLINK_TELEMETRY_ENABLED=true
|
|
6
|
+
*/
|
|
7
|
+
export async function initializeTelemetry() {
|
|
8
|
+
const { TelemetryService } = await import("./telemetry-service.js");
|
|
9
|
+
const telemetry = TelemetryService.getInstance();
|
|
10
|
+
if (telemetry.isEnabled()) {
|
|
11
|
+
await telemetry.initialize();
|
|
12
|
+
console.log("[NeuroLink] Telemetry initialized");
|
|
13
|
+
}
|
|
14
|
+
return telemetry;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Get telemetry status
|
|
18
|
+
*/
|
|
19
|
+
export async function getTelemetryStatus() {
|
|
20
|
+
const { TelemetryService } = await import("./telemetry-service.js");
|
|
21
|
+
return TelemetryService.getInstance().getStatus();
|
|
22
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export interface HealthMetrics {
|
|
2
|
+
timestamp: number;
|
|
3
|
+
memoryUsage: NodeJS.MemoryUsage;
|
|
4
|
+
uptime: number;
|
|
5
|
+
activeConnections: number;
|
|
6
|
+
errorRate: number;
|
|
7
|
+
averageResponseTime: number;
|
|
8
|
+
}
|
|
9
|
+
export declare class TelemetryService {
|
|
10
|
+
private static instance;
|
|
11
|
+
private sdk?;
|
|
12
|
+
private enabled;
|
|
13
|
+
private meter?;
|
|
14
|
+
private tracer?;
|
|
15
|
+
private aiRequestCounter?;
|
|
16
|
+
private aiRequestDuration?;
|
|
17
|
+
private aiTokensUsed?;
|
|
18
|
+
private aiProviderErrors?;
|
|
19
|
+
private mcpToolCalls?;
|
|
20
|
+
private connectionCounter?;
|
|
21
|
+
private responseTimeHistogram?;
|
|
22
|
+
private constructor();
|
|
23
|
+
static getInstance(): TelemetryService;
|
|
24
|
+
private isTelemetryEnabled;
|
|
25
|
+
private initializeTelemetry;
|
|
26
|
+
private initializeMetrics;
|
|
27
|
+
initialize(): Promise<void>;
|
|
28
|
+
traceAIRequest<T>(provider: string, operation: () => Promise<T>): Promise<T>;
|
|
29
|
+
recordAIRequest(provider: string, model: string, tokens: number, duration: number): void;
|
|
30
|
+
recordAIError(provider: string, error: Error): void;
|
|
31
|
+
recordMCPToolCall(toolName: string, duration: number, success: boolean): void;
|
|
32
|
+
recordConnection(type: "websocket" | "sse" | "http"): void;
|
|
33
|
+
recordResponseTime(endpoint: string, method: string, duration: number): void;
|
|
34
|
+
recordCustomMetric(name: string, value: number, labels?: Record<string, string>): void;
|
|
35
|
+
recordCustomHistogram(name: string, value: number, labels?: Record<string, string>): void;
|
|
36
|
+
getHealthMetrics(): Promise<HealthMetrics>;
|
|
37
|
+
isEnabled(): boolean;
|
|
38
|
+
getStatus(): {
|
|
39
|
+
enabled: boolean;
|
|
40
|
+
endpoint?: string;
|
|
41
|
+
service?: string;
|
|
42
|
+
version?: string;
|
|
43
|
+
};
|
|
44
|
+
private getDurationBucket;
|
|
45
|
+
private getStatusBucket;
|
|
46
|
+
shutdown(): Promise<void>;
|
|
47
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { NodeSDK } from "@opentelemetry/sdk-node";
|
|
2
|
+
import { metrics, trace } from "@opentelemetry/api";
|
|
3
|
+
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
|
|
4
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
5
|
+
import { Resource } from "@opentelemetry/resources";
|
|
6
|
+
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION, } from "@opentelemetry/semantic-conventions";
|
|
7
|
+
export class TelemetryService {
|
|
8
|
+
static instance;
|
|
9
|
+
sdk;
|
|
10
|
+
enabled = false;
|
|
11
|
+
meter;
|
|
12
|
+
tracer;
|
|
13
|
+
// Optional Metrics (only created when enabled)
|
|
14
|
+
aiRequestCounter;
|
|
15
|
+
aiRequestDuration;
|
|
16
|
+
aiTokensUsed;
|
|
17
|
+
aiProviderErrors;
|
|
18
|
+
mcpToolCalls;
|
|
19
|
+
connectionCounter;
|
|
20
|
+
responseTimeHistogram;
|
|
21
|
+
constructor() {
|
|
22
|
+
// Check if telemetry is enabled
|
|
23
|
+
this.enabled = this.isTelemetryEnabled();
|
|
24
|
+
if (this.enabled) {
|
|
25
|
+
this.initializeTelemetry();
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
console.log("[Telemetry] Disabled - set NEUROLINK_TELEMETRY_ENABLED=true or configure OTEL_EXPORTER_OTLP_ENDPOINT to enable");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
static getInstance() {
|
|
32
|
+
if (!TelemetryService.instance) {
|
|
33
|
+
TelemetryService.instance = new TelemetryService();
|
|
34
|
+
}
|
|
35
|
+
return TelemetryService.instance;
|
|
36
|
+
}
|
|
37
|
+
isTelemetryEnabled() {
|
|
38
|
+
return (process.env.NEUROLINK_TELEMETRY_ENABLED === "true" ||
|
|
39
|
+
process.env.OTEL_EXPORTER_OTLP_ENDPOINT !== undefined);
|
|
40
|
+
}
|
|
41
|
+
initializeTelemetry() {
|
|
42
|
+
try {
|
|
43
|
+
const resource = new Resource({
|
|
44
|
+
[ATTR_SERVICE_NAME]: process.env.OTEL_SERVICE_NAME || "neurolink-ai",
|
|
45
|
+
[ATTR_SERVICE_VERSION]: process.env.OTEL_SERVICE_VERSION || "3.0.1",
|
|
46
|
+
});
|
|
47
|
+
this.sdk = new NodeSDK({
|
|
48
|
+
resource,
|
|
49
|
+
traceExporter: new OTLPTraceExporter({
|
|
50
|
+
url: process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||
|
|
51
|
+
`${process.env.OTEL_EXPORTER_OTLP_ENDPOINT}/v1/traces`,
|
|
52
|
+
}),
|
|
53
|
+
// Note: Metric reader configured separately
|
|
54
|
+
instrumentations: [getNodeAutoInstrumentations()],
|
|
55
|
+
});
|
|
56
|
+
this.meter = metrics.getMeter("neurolink-ai");
|
|
57
|
+
this.tracer = trace.getTracer("neurolink-ai");
|
|
58
|
+
this.initializeMetrics();
|
|
59
|
+
console.log("[Telemetry] Initialized with endpoint:", process.env.OTEL_EXPORTER_OTLP_ENDPOINT);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
console.error("[Telemetry] Failed to initialize:", error);
|
|
63
|
+
this.enabled = false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
initializeMetrics() {
|
|
67
|
+
if (!this.enabled || !this.meter) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
this.aiRequestCounter = this.meter.createCounter("ai_requests_total", {
|
|
71
|
+
description: "Total number of AI requests",
|
|
72
|
+
});
|
|
73
|
+
this.aiRequestDuration = this.meter.createHistogram("ai_request_duration_ms", {
|
|
74
|
+
description: "AI request duration in milliseconds",
|
|
75
|
+
});
|
|
76
|
+
this.aiTokensUsed = this.meter.createCounter("ai_tokens_used_total", {
|
|
77
|
+
description: "Total number of AI tokens used",
|
|
78
|
+
});
|
|
79
|
+
this.aiProviderErrors = this.meter.createCounter("ai_provider_errors_total", {
|
|
80
|
+
description: "Total number of AI provider errors",
|
|
81
|
+
});
|
|
82
|
+
this.mcpToolCalls = this.meter.createCounter("mcp_tool_calls_total", {
|
|
83
|
+
description: "Total number of MCP tool calls",
|
|
84
|
+
});
|
|
85
|
+
this.connectionCounter = this.meter.createCounter("connections_total", {
|
|
86
|
+
description: "Total number of connections",
|
|
87
|
+
});
|
|
88
|
+
this.responseTimeHistogram = this.meter.createHistogram("response_time_ms", {
|
|
89
|
+
description: "Response time in milliseconds",
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
async initialize() {
|
|
93
|
+
if (!this.enabled) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
await this.sdk?.start();
|
|
98
|
+
console.log("[Telemetry] SDK started successfully");
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
console.error("[Telemetry] Failed to start SDK:", error);
|
|
102
|
+
this.enabled = false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// AI Operation Tracing (NO-OP when disabled)
|
|
106
|
+
async traceAIRequest(provider, operation) {
|
|
107
|
+
if (!this.enabled || !this.tracer) {
|
|
108
|
+
return await operation(); // Direct execution when disabled
|
|
109
|
+
}
|
|
110
|
+
const span = this.tracer.startSpan(`ai.${provider}.generate_text`, {
|
|
111
|
+
attributes: {
|
|
112
|
+
"ai.provider": provider,
|
|
113
|
+
"ai.operation": "generate_text",
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
try {
|
|
117
|
+
const result = await operation();
|
|
118
|
+
span.setStatus({ code: 1 }); // OK
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
span.setStatus({
|
|
123
|
+
code: 2,
|
|
124
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
125
|
+
}); // ERROR
|
|
126
|
+
span.recordException(error);
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
span.end();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Metrics Recording (NO-OP when disabled)
|
|
134
|
+
recordAIRequest(provider, model, tokens, duration) {
|
|
135
|
+
if (!this.enabled || !this.aiRequestCounter) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const labels = { provider, model };
|
|
139
|
+
this.aiRequestCounter.add(1, labels);
|
|
140
|
+
this.aiRequestDuration?.record(duration, labels);
|
|
141
|
+
this.aiTokensUsed?.add(tokens, labels);
|
|
142
|
+
}
|
|
143
|
+
recordAIError(provider, error) {
|
|
144
|
+
if (!this.enabled || !this.aiProviderErrors) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
this.aiProviderErrors.add(1, {
|
|
148
|
+
provider,
|
|
149
|
+
error: error.name,
|
|
150
|
+
message: error.message.substring(0, 100), // Limit message length
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
recordMCPToolCall(toolName, duration, success) {
|
|
154
|
+
if (!this.enabled || !this.mcpToolCalls) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
this.mcpToolCalls.add(1, {
|
|
158
|
+
tool: toolName,
|
|
159
|
+
success: success.toString(),
|
|
160
|
+
duration_bucket: this.getDurationBucket(duration),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
recordConnection(type) {
|
|
164
|
+
if (!this.enabled || !this.connectionCounter) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
this.connectionCounter.add(1, { connection_type: type });
|
|
168
|
+
}
|
|
169
|
+
recordResponseTime(endpoint, method, duration) {
|
|
170
|
+
if (!this.enabled || !this.responseTimeHistogram) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
this.responseTimeHistogram.record(duration, {
|
|
174
|
+
endpoint,
|
|
175
|
+
method,
|
|
176
|
+
status_bucket: this.getStatusBucket(duration),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
// Custom Metrics
|
|
180
|
+
recordCustomMetric(name, value, labels) {
|
|
181
|
+
if (!this.enabled || !this.meter) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const counter = this.meter.createCounter(`custom_${name}`, {
|
|
185
|
+
description: `Custom metric: ${name}`,
|
|
186
|
+
});
|
|
187
|
+
counter.add(value, labels || {});
|
|
188
|
+
}
|
|
189
|
+
recordCustomHistogram(name, value, labels) {
|
|
190
|
+
if (!this.enabled || !this.meter) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const histogram = this.meter.createHistogram(`custom_${name}_histogram`, {
|
|
194
|
+
description: `Custom histogram: ${name}`,
|
|
195
|
+
});
|
|
196
|
+
histogram.record(value, labels || {});
|
|
197
|
+
}
|
|
198
|
+
// Health Checks
|
|
199
|
+
async getHealthMetrics() {
|
|
200
|
+
const memoryUsage = process.memoryUsage();
|
|
201
|
+
return {
|
|
202
|
+
timestamp: Date.now(),
|
|
203
|
+
memoryUsage,
|
|
204
|
+
uptime: process.uptime(),
|
|
205
|
+
activeConnections: 0, // Would need to be provided by calling code
|
|
206
|
+
errorRate: 0, // Would need to be calculated from metrics
|
|
207
|
+
averageResponseTime: 0, // Would need to be calculated from metrics
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
// Telemetry Status
|
|
211
|
+
isEnabled() {
|
|
212
|
+
return this.enabled;
|
|
213
|
+
}
|
|
214
|
+
getStatus() {
|
|
215
|
+
return {
|
|
216
|
+
enabled: this.enabled,
|
|
217
|
+
endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
|
|
218
|
+
service: process.env.OTEL_SERVICE_NAME || "neurolink-ai",
|
|
219
|
+
version: process.env.OTEL_SERVICE_VERSION || "3.0.1",
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
// Helper methods
|
|
223
|
+
getDurationBucket(duration) {
|
|
224
|
+
if (duration < 100) {
|
|
225
|
+
return "fast";
|
|
226
|
+
}
|
|
227
|
+
if (duration < 500) {
|
|
228
|
+
return "medium";
|
|
229
|
+
}
|
|
230
|
+
if (duration < 1000) {
|
|
231
|
+
return "slow";
|
|
232
|
+
}
|
|
233
|
+
return "very_slow";
|
|
234
|
+
}
|
|
235
|
+
getStatusBucket(duration) {
|
|
236
|
+
if (duration < 200) {
|
|
237
|
+
return "excellent";
|
|
238
|
+
}
|
|
239
|
+
if (duration < 500) {
|
|
240
|
+
return "good";
|
|
241
|
+
}
|
|
242
|
+
if (duration < 1000) {
|
|
243
|
+
return "acceptable";
|
|
244
|
+
}
|
|
245
|
+
return "poor";
|
|
246
|
+
}
|
|
247
|
+
// Cleanup
|
|
248
|
+
async shutdown() {
|
|
249
|
+
if (this.enabled && this.sdk) {
|
|
250
|
+
try {
|
|
251
|
+
await this.sdk.shutdown();
|
|
252
|
+
console.log("[Telemetry] SDK shutdown completed");
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
console.error("[Telemetry] Error during shutdown:", error);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|