@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.
Files changed (192) hide show
  1. package/CHANGELOG.md +62 -4
  2. package/README.md +235 -2
  3. package/dist/agent/direct-tools.d.ts +6 -6
  4. package/dist/chat/client-utils.d.ts +92 -0
  5. package/dist/chat/client-utils.js +298 -0
  6. package/dist/chat/index.d.ts +27 -0
  7. package/dist/chat/index.js +41 -0
  8. package/dist/chat/session-storage.d.ts +77 -0
  9. package/dist/chat/session-storage.js +233 -0
  10. package/dist/chat/session.d.ts +95 -0
  11. package/dist/chat/session.js +257 -0
  12. package/dist/chat/sse-handler.d.ts +49 -0
  13. package/dist/chat/sse-handler.js +266 -0
  14. package/dist/chat/types.d.ts +73 -0
  15. package/dist/chat/types.js +5 -0
  16. package/dist/chat/websocket-chat-handler.d.ts +36 -0
  17. package/dist/chat/websocket-chat-handler.js +262 -0
  18. package/dist/cli/commands/config.js +12 -12
  19. package/dist/cli/commands/mcp.js +3 -4
  20. package/dist/cli/index.d.ts +0 -7
  21. package/dist/cli/index.js +256 -27
  22. package/dist/config/configManager.d.ts +60 -0
  23. package/dist/config/configManager.js +300 -0
  24. package/dist/config/types.d.ts +136 -0
  25. package/dist/config/types.js +43 -0
  26. package/dist/core/analytics.d.ts +23 -0
  27. package/dist/core/analytics.js +131 -0
  28. package/dist/core/constants.d.ts +41 -0
  29. package/dist/core/constants.js +50 -0
  30. package/dist/core/defaults.d.ts +18 -0
  31. package/dist/core/defaults.js +29 -0
  32. package/dist/core/evaluation-config.d.ts +29 -0
  33. package/dist/core/evaluation-config.js +144 -0
  34. package/dist/core/evaluation-providers.d.ts +30 -0
  35. package/dist/core/evaluation-providers.js +187 -0
  36. package/dist/core/evaluation.d.ts +117 -0
  37. package/dist/core/evaluation.js +528 -0
  38. package/dist/core/factory.js +33 -25
  39. package/dist/core/types.d.ts +165 -6
  40. package/dist/core/types.js +3 -4
  41. package/dist/index.d.ts +9 -4
  42. package/dist/index.js +25 -4
  43. package/dist/lib/agent/direct-tools.d.ts +6 -6
  44. package/dist/lib/chat/client-utils.d.ts +92 -0
  45. package/dist/lib/chat/client-utils.js +298 -0
  46. package/dist/lib/chat/index.d.ts +27 -0
  47. package/dist/lib/chat/index.js +41 -0
  48. package/dist/lib/chat/session-storage.d.ts +77 -0
  49. package/dist/lib/chat/session-storage.js +233 -0
  50. package/dist/lib/chat/session.d.ts +95 -0
  51. package/dist/lib/chat/session.js +257 -0
  52. package/dist/lib/chat/sse-handler.d.ts +49 -0
  53. package/dist/lib/chat/sse-handler.js +266 -0
  54. package/dist/lib/chat/types.d.ts +73 -0
  55. package/dist/lib/chat/types.js +5 -0
  56. package/dist/lib/chat/websocket-chat-handler.d.ts +36 -0
  57. package/dist/lib/chat/websocket-chat-handler.js +262 -0
  58. package/dist/lib/config/configManager.d.ts +60 -0
  59. package/dist/lib/config/configManager.js +300 -0
  60. package/dist/lib/config/types.d.ts +136 -0
  61. package/dist/lib/config/types.js +43 -0
  62. package/dist/lib/core/analytics.d.ts +23 -0
  63. package/dist/lib/core/analytics.js +131 -0
  64. package/dist/lib/core/constants.d.ts +41 -0
  65. package/dist/lib/core/constants.js +50 -0
  66. package/dist/lib/core/defaults.d.ts +18 -0
  67. package/dist/lib/core/defaults.js +29 -0
  68. package/dist/lib/core/evaluation-config.d.ts +29 -0
  69. package/dist/lib/core/evaluation-config.js +144 -0
  70. package/dist/lib/core/evaluation-providers.d.ts +30 -0
  71. package/dist/lib/core/evaluation-providers.js +187 -0
  72. package/dist/lib/core/evaluation.d.ts +117 -0
  73. package/dist/lib/core/evaluation.js +528 -0
  74. package/dist/lib/core/factory.js +33 -26
  75. package/dist/lib/core/types.d.ts +165 -6
  76. package/dist/lib/core/types.js +3 -4
  77. package/dist/lib/index.d.ts +9 -4
  78. package/dist/lib/index.js +25 -4
  79. package/dist/lib/mcp/contracts/mcpContract.d.ts +118 -0
  80. package/dist/lib/mcp/contracts/mcpContract.js +5 -0
  81. package/dist/lib/mcp/function-calling.js +11 -3
  82. package/dist/lib/mcp/logging.js +5 -0
  83. package/dist/lib/mcp/neurolink-mcp-client.js +2 -1
  84. package/dist/lib/mcp/orchestrator.js +18 -9
  85. package/dist/lib/mcp/registry.d.ts +49 -16
  86. package/dist/lib/mcp/registry.js +80 -6
  87. package/dist/lib/mcp/servers/ai-providers/ai-workflow-tools.js +5 -4
  88. package/dist/lib/mcp/tool-integration.js +1 -1
  89. package/dist/lib/mcp/tool-registry.d.ts +55 -34
  90. package/dist/lib/mcp/tool-registry.js +111 -97
  91. package/dist/lib/mcp/unified-mcp.js +6 -1
  92. package/dist/lib/mcp/unified-registry.d.ts +12 -4
  93. package/dist/lib/mcp/unified-registry.js +17 -4
  94. package/dist/lib/neurolink.d.ts +28 -0
  95. package/dist/lib/neurolink.js +48 -4
  96. package/dist/lib/providers/agent-enhanced-provider.d.ts +11 -2
  97. package/dist/lib/providers/agent-enhanced-provider.js +86 -15
  98. package/dist/lib/providers/amazonBedrock.d.ts +9 -1
  99. package/dist/lib/providers/amazonBedrock.js +26 -2
  100. package/dist/lib/providers/analytics-helper.d.ts +53 -0
  101. package/dist/lib/providers/analytics-helper.js +151 -0
  102. package/dist/lib/providers/anthropic.d.ts +11 -1
  103. package/dist/lib/providers/anthropic.js +29 -4
  104. package/dist/lib/providers/azureOpenAI.d.ts +3 -1
  105. package/dist/lib/providers/azureOpenAI.js +28 -4
  106. package/dist/lib/providers/function-calling-provider.d.ts +9 -1
  107. package/dist/lib/providers/function-calling-provider.js +14 -1
  108. package/dist/lib/providers/googleAIStudio.d.ts +15 -1
  109. package/dist/lib/providers/googleAIStudio.js +32 -2
  110. package/dist/lib/providers/googleVertexAI.d.ts +9 -1
  111. package/dist/lib/providers/googleVertexAI.js +31 -2
  112. package/dist/lib/providers/huggingFace.d.ts +3 -1
  113. package/dist/lib/providers/huggingFace.js +26 -3
  114. package/dist/lib/providers/mcp-provider.d.ts +9 -1
  115. package/dist/lib/providers/mcp-provider.js +12 -0
  116. package/dist/lib/providers/mistralAI.d.ts +3 -1
  117. package/dist/lib/providers/mistralAI.js +25 -2
  118. package/dist/lib/providers/ollama.d.ts +3 -1
  119. package/dist/lib/providers/ollama.js +27 -4
  120. package/dist/lib/providers/openAI.d.ts +15 -1
  121. package/dist/lib/providers/openAI.js +32 -2
  122. package/dist/lib/proxy/proxy-fetch.js +8 -7
  123. package/dist/lib/services/streaming/streaming-manager.d.ts +29 -0
  124. package/dist/lib/services/streaming/streaming-manager.js +244 -0
  125. package/dist/lib/services/types.d.ts +155 -0
  126. package/dist/lib/services/types.js +2 -0
  127. package/dist/lib/services/websocket/websocket-server.d.ts +34 -0
  128. package/dist/lib/services/websocket/websocket-server.js +304 -0
  129. package/dist/lib/telemetry/index.d.ts +15 -0
  130. package/dist/lib/telemetry/index.js +22 -0
  131. package/dist/lib/telemetry/telemetry-service.d.ts +47 -0
  132. package/dist/lib/telemetry/telemetry-service.js +259 -0
  133. package/dist/lib/utils/streaming-utils.d.ts +67 -0
  134. package/dist/lib/utils/streaming-utils.js +201 -0
  135. package/dist/mcp/contracts/mcpContract.d.ts +118 -0
  136. package/dist/mcp/contracts/mcpContract.js +5 -0
  137. package/dist/mcp/function-calling.js +11 -3
  138. package/dist/mcp/logging.js +5 -0
  139. package/dist/mcp/neurolink-mcp-client.js +2 -1
  140. package/dist/mcp/orchestrator.js +18 -9
  141. package/dist/mcp/registry.d.ts +49 -16
  142. package/dist/mcp/registry.js +80 -6
  143. package/dist/mcp/servers/ai-providers/ai-workflow-tools.d.ts +2 -2
  144. package/dist/mcp/servers/ai-providers/ai-workflow-tools.js +5 -4
  145. package/dist/mcp/tool-integration.js +1 -1
  146. package/dist/mcp/tool-registry.d.ts +55 -34
  147. package/dist/mcp/tool-registry.js +111 -97
  148. package/dist/mcp/unified-mcp.js +6 -1
  149. package/dist/mcp/unified-registry.d.ts +12 -4
  150. package/dist/mcp/unified-registry.js +17 -4
  151. package/dist/neurolink.d.ts +28 -0
  152. package/dist/neurolink.js +48 -4
  153. package/dist/providers/agent-enhanced-provider.d.ts +11 -2
  154. package/dist/providers/agent-enhanced-provider.js +86 -15
  155. package/dist/providers/amazonBedrock.d.ts +9 -1
  156. package/dist/providers/amazonBedrock.js +26 -2
  157. package/dist/providers/analytics-helper.d.ts +53 -0
  158. package/dist/providers/analytics-helper.js +151 -0
  159. package/dist/providers/anthropic.d.ts +11 -1
  160. package/dist/providers/anthropic.js +29 -4
  161. package/dist/providers/azureOpenAI.d.ts +3 -1
  162. package/dist/providers/azureOpenAI.js +29 -4
  163. package/dist/providers/function-calling-provider.d.ts +9 -1
  164. package/dist/providers/function-calling-provider.js +14 -1
  165. package/dist/providers/googleAIStudio.d.ts +15 -1
  166. package/dist/providers/googleAIStudio.js +32 -2
  167. package/dist/providers/googleVertexAI.d.ts +9 -1
  168. package/dist/providers/googleVertexAI.js +31 -2
  169. package/dist/providers/huggingFace.d.ts +3 -1
  170. package/dist/providers/huggingFace.js +26 -3
  171. package/dist/providers/mcp-provider.d.ts +9 -1
  172. package/dist/providers/mcp-provider.js +12 -0
  173. package/dist/providers/mistralAI.d.ts +3 -1
  174. package/dist/providers/mistralAI.js +25 -2
  175. package/dist/providers/ollama.d.ts +3 -1
  176. package/dist/providers/ollama.js +27 -4
  177. package/dist/providers/openAI.d.ts +15 -1
  178. package/dist/providers/openAI.js +33 -2
  179. package/dist/proxy/proxy-fetch.js +8 -7
  180. package/dist/services/streaming/streaming-manager.d.ts +29 -0
  181. package/dist/services/streaming/streaming-manager.js +244 -0
  182. package/dist/services/types.d.ts +155 -0
  183. package/dist/services/types.js +2 -0
  184. package/dist/services/websocket/websocket-server.d.ts +34 -0
  185. package/dist/services/websocket/websocket-server.js +304 -0
  186. package/dist/telemetry/index.d.ts +15 -0
  187. package/dist/telemetry/index.js +22 -0
  188. package/dist/telemetry/telemetry-service.d.ts +47 -0
  189. package/dist/telemetry/telemetry-service.js +261 -0
  190. package/dist/utils/streaming-utils.d.ts +67 -0
  191. package/dist/utils/streaming-utils.js +201 -0
  192. package/package.json +18 -2
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Phase 3: SSE Chat Utilities
3
+ * Chat session management with persistence
4
+ */
5
+ import type { ChatMessage, SessionOptions, ChatSessionState } from "./types.js";
6
+ export declare class ChatSession {
7
+ private sessionId;
8
+ private messages;
9
+ private options;
10
+ private storage;
11
+ private createdAt;
12
+ private lastActivity;
13
+ private metadata;
14
+ constructor(sessionId: string, options?: SessionOptions);
15
+ /**
16
+ * Add message to session
17
+ */
18
+ addMessage(role: "user" | "assistant" | "system", content: string, metadata?: any): ChatMessage;
19
+ /**
20
+ * Get conversation history
21
+ */
22
+ getHistory(limit?: number): ChatMessage[];
23
+ /**
24
+ * Get messages for a specific role
25
+ */
26
+ getMessagesByRole(role: "user" | "assistant" | "system"): ChatMessage[];
27
+ /**
28
+ * Get session metadata
29
+ */
30
+ getMetadata(): Record<string, any>;
31
+ /**
32
+ * Set session metadata
33
+ */
34
+ setMetadata(key: string, value: any): void;
35
+ /**
36
+ * Clear session history
37
+ */
38
+ clearHistory(): void;
39
+ /**
40
+ * Get session statistics
41
+ */
42
+ getStats(): {
43
+ messageCount: number;
44
+ userMessages: number;
45
+ assistantMessages: number;
46
+ systemMessages: number;
47
+ totalTokens: number;
48
+ sessionAge: number;
49
+ lastActivity: number;
50
+ };
51
+ /**
52
+ * Trim context to fit within token limits
53
+ */
54
+ trimContext(maxTokens: number): number;
55
+ /**
56
+ * Estimate total tokens in session
57
+ */
58
+ estimateTokens(): number;
59
+ /**
60
+ * Get session context for AI prompts
61
+ */
62
+ getContextForPrompt(includeSystem?: boolean): string;
63
+ /**
64
+ * Persist session to storage
65
+ */
66
+ persist(): Promise<void>;
67
+ /**
68
+ * Load session from storage
69
+ */
70
+ private loadSession;
71
+ /**
72
+ * Delete session from storage
73
+ */
74
+ delete(): Promise<void>;
75
+ /**
76
+ * Create storage adapter based on options
77
+ */
78
+ private createStorageAdapter;
79
+ /**
80
+ * Export session data
81
+ */
82
+ export(): ChatSessionState;
83
+ /**
84
+ * Import session data
85
+ */
86
+ import(state: ChatSessionState): void;
87
+ /**
88
+ * Get session ID
89
+ */
90
+ getId(): string;
91
+ /**
92
+ * Check if session is active (not expired)
93
+ */
94
+ isActive(): boolean;
95
+ }
@@ -0,0 +1,257 @@
1
+ /**
2
+ * Phase 3: SSE Chat Utilities
3
+ * Chat session management with persistence
4
+ */
5
+ import { MemorySessionStorage } from "./session-storage.js";
6
+ export class ChatSession {
7
+ sessionId;
8
+ messages = [];
9
+ options;
10
+ storage;
11
+ createdAt;
12
+ lastActivity;
13
+ metadata = {};
14
+ constructor(sessionId, options = {}) {
15
+ this.sessionId = sessionId;
16
+ this.createdAt = Date.now();
17
+ this.lastActivity = Date.now();
18
+ this.options = {
19
+ maxHistory: options.maxHistory ?? 100,
20
+ persistenceAdapter: options.persistenceAdapter ?? "memory",
21
+ contextWindowSize: options.contextWindowSize ?? 4000,
22
+ autoSave: options.autoSave ?? true,
23
+ ttl: options.ttl ?? 3600, // 1 hour default
24
+ };
25
+ // Initialize storage adapter
26
+ this.storage = this.createStorageAdapter();
27
+ // Load existing session if available
28
+ this.loadSession().catch(() => { });
29
+ }
30
+ /**
31
+ * Add message to session
32
+ */
33
+ addMessage(role, content, metadata) {
34
+ const message = {
35
+ id: `${this.sessionId}_${Date.now()}_${role}`,
36
+ role,
37
+ content,
38
+ timestamp: Date.now(),
39
+ metadata,
40
+ };
41
+ this.messages.push(message);
42
+ this.lastActivity = Date.now();
43
+ // Trim history if needed
44
+ if (this.messages.length > this.options.maxHistory) {
45
+ this.messages = this.messages.slice(-this.options.maxHistory);
46
+ }
47
+ // Auto-save if enabled
48
+ if (this.options.autoSave) {
49
+ this.persist().catch((error) => {
50
+ console.error("Auto-save failed:", error);
51
+ });
52
+ }
53
+ return message;
54
+ }
55
+ /**
56
+ * Get conversation history
57
+ */
58
+ getHistory(limit) {
59
+ const messages = limit ? this.messages.slice(-limit) : this.messages;
60
+ return [...messages]; // Return copy to prevent mutations
61
+ }
62
+ /**
63
+ * Get messages for a specific role
64
+ */
65
+ getMessagesByRole(role) {
66
+ return this.messages.filter((msg) => msg.role === role);
67
+ }
68
+ /**
69
+ * Get session metadata
70
+ */
71
+ getMetadata() {
72
+ return { ...this.metadata };
73
+ }
74
+ /**
75
+ * Set session metadata
76
+ */
77
+ setMetadata(key, value) {
78
+ this.metadata[key] = value;
79
+ this.lastActivity = Date.now();
80
+ }
81
+ /**
82
+ * Clear session history
83
+ */
84
+ clearHistory() {
85
+ this.messages = [];
86
+ this.lastActivity = Date.now();
87
+ if (this.options.autoSave) {
88
+ this.persist().catch((error) => {
89
+ console.error("Auto-save after clear failed:", error);
90
+ });
91
+ }
92
+ }
93
+ /**
94
+ * Get session statistics
95
+ */
96
+ getStats() {
97
+ const userMessages = this.getMessagesByRole("user").length;
98
+ const assistantMessages = this.getMessagesByRole("assistant").length;
99
+ const systemMessages = this.getMessagesByRole("system").length;
100
+ // Rough token estimation (characters / 4)
101
+ const totalTokens = this.messages.reduce((sum, msg) => {
102
+ return sum + Math.ceil(msg.content.length / 4);
103
+ }, 0);
104
+ return {
105
+ messageCount: this.messages.length,
106
+ userMessages,
107
+ assistantMessages,
108
+ systemMessages,
109
+ totalTokens,
110
+ sessionAge: Date.now() - this.createdAt,
111
+ lastActivity: this.lastActivity,
112
+ };
113
+ }
114
+ /**
115
+ * Trim context to fit within token limits
116
+ */
117
+ trimContext(maxTokens) {
118
+ let currentTokens = this.estimateTokens();
119
+ let removedMessages = 0;
120
+ // Keep the most recent messages within token limit
121
+ while (currentTokens > maxTokens && this.messages.length > 1) {
122
+ // Remove oldest message (but keep system messages if possible)
123
+ const oldestNonSystem = this.messages.findIndex((msg) => msg.role !== "system");
124
+ const indexToRemove = oldestNonSystem >= 0 ? oldestNonSystem : 0;
125
+ const removedMessage = this.messages.splice(indexToRemove, 1)[0];
126
+ currentTokens -= Math.ceil(removedMessage.content.length / 4);
127
+ removedMessages++;
128
+ }
129
+ this.lastActivity = Date.now();
130
+ if (this.options.autoSave && removedMessages > 0) {
131
+ this.persist().catch((error) => {
132
+ console.error("Auto-save after trim failed:", error);
133
+ });
134
+ }
135
+ return removedMessages;
136
+ }
137
+ /**
138
+ * Estimate total tokens in session
139
+ */
140
+ estimateTokens() {
141
+ return this.messages.reduce((sum, msg) => {
142
+ return sum + Math.ceil(msg.content.length / 4);
143
+ }, 0);
144
+ }
145
+ /**
146
+ * Get session context for AI prompts
147
+ */
148
+ getContextForPrompt(includeSystem = true) {
149
+ const relevantMessages = includeSystem
150
+ ? this.messages
151
+ : this.messages.filter((msg) => msg.role !== "system");
152
+ return relevantMessages
153
+ .map((msg) => `${msg.role}: ${msg.content}`)
154
+ .join("\n\n");
155
+ }
156
+ /**
157
+ * Persist session to storage
158
+ */
159
+ async persist() {
160
+ const state = {
161
+ id: this.sessionId,
162
+ messages: this.messages,
163
+ createdAt: this.createdAt,
164
+ lastActivity: this.lastActivity,
165
+ metadata: this.metadata,
166
+ };
167
+ await this.storage.set(this.sessionId, state);
168
+ }
169
+ /**
170
+ * Load session from storage
171
+ */
172
+ async loadSession() {
173
+ try {
174
+ const state = await this.storage.get(this.sessionId);
175
+ if (state) {
176
+ this.messages = state.messages || [];
177
+ this.createdAt = state.createdAt || Date.now();
178
+ this.lastActivity = state.lastActivity || Date.now();
179
+ this.metadata = state.metadata || {};
180
+ // Check if session has expired
181
+ const age = Date.now() - this.lastActivity;
182
+ if (age > this.options.ttl * 1000) {
183
+ // Session expired, start fresh
184
+ this.messages = [];
185
+ this.metadata = {};
186
+ this.createdAt = Date.now();
187
+ this.lastActivity = Date.now();
188
+ }
189
+ }
190
+ }
191
+ catch (error) {
192
+ console.error("Failed to load session:", error);
193
+ // Continue with fresh session
194
+ }
195
+ }
196
+ /**
197
+ * Delete session from storage
198
+ */
199
+ async delete() {
200
+ await this.storage.delete(this.sessionId);
201
+ this.messages = [];
202
+ this.metadata = {};
203
+ }
204
+ /**
205
+ * Create storage adapter based on options
206
+ */
207
+ createStorageAdapter() {
208
+ switch (this.options.persistenceAdapter) {
209
+ case "memory":
210
+ return new MemorySessionStorage();
211
+ case "redis":
212
+ // TODO: Implement Redis adapter
213
+ console.warn("Redis adapter not yet implemented, falling back to memory");
214
+ return new MemorySessionStorage();
215
+ case "file":
216
+ // TODO: Implement File adapter
217
+ console.warn("File adapter not yet implemented, falling back to memory");
218
+ return new MemorySessionStorage();
219
+ default:
220
+ return new MemorySessionStorage();
221
+ }
222
+ }
223
+ /**
224
+ * Export session data
225
+ */
226
+ export() {
227
+ return {
228
+ id: this.sessionId,
229
+ messages: [...this.messages],
230
+ createdAt: this.createdAt,
231
+ lastActivity: this.lastActivity,
232
+ metadata: { ...this.metadata },
233
+ };
234
+ }
235
+ /**
236
+ * Import session data
237
+ */
238
+ import(state) {
239
+ this.messages = state.messages || [];
240
+ this.createdAt = state.createdAt || Date.now();
241
+ this.lastActivity = state.lastActivity || Date.now();
242
+ this.metadata = state.metadata || {};
243
+ }
244
+ /**
245
+ * Get session ID
246
+ */
247
+ getId() {
248
+ return this.sessionId;
249
+ }
250
+ /**
251
+ * Check if session is active (not expired)
252
+ */
253
+ isActive() {
254
+ const age = Date.now() - this.lastActivity;
255
+ return age <= this.options.ttl * 1000;
256
+ }
257
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Phase 3: SSE Chat Utilities
3
+ * Server-Sent Events handler for real-time chat
4
+ */
5
+ import type { AIProvider } from "../core/types.js";
6
+ import type { ChatRequest, SSEOptions } from "./types.js";
7
+ export declare class SSEChatHandler {
8
+ private connections;
9
+ protected provider: AIProvider;
10
+ protected options: Required<SSEOptions>;
11
+ private heartbeatInterval?;
12
+ constructor(provider: AIProvider, options?: SSEOptions);
13
+ /**
14
+ * Handle incoming chat request and return SSE response
15
+ */
16
+ handleChatRequest(request: ChatRequest): Promise<Response>;
17
+ /**
18
+ * Create event stream for a session
19
+ */
20
+ createEventStream(sessionId: string, handler?: (writer: WritableStreamDefaultWriter) => Promise<void>): ReadableStream;
21
+ /**
22
+ * Send SSE event to client
23
+ */
24
+ private sendEvent;
25
+ /**
26
+ * Get SSE response headers
27
+ */
28
+ private getSSEHeaders;
29
+ /**
30
+ * Close connection and cleanup
31
+ */
32
+ private closeConnection;
33
+ /**
34
+ * Start heartbeat to keep connections alive
35
+ */
36
+ private startHeartbeat;
37
+ /**
38
+ * Cleanup and stop handler
39
+ */
40
+ destroy(): void;
41
+ /**
42
+ * Get handler statistics
43
+ */
44
+ getStats(): {
45
+ activeConnections: number;
46
+ maxConnections: number;
47
+ uptime: number;
48
+ };
49
+ }
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Phase 3: SSE Chat Utilities
3
+ * Server-Sent Events handler for real-time chat
4
+ */
5
+ import { ChatSession } from "./session.js";
6
+ export class SSEChatHandler {
7
+ connections = new Map();
8
+ provider;
9
+ options;
10
+ heartbeatInterval;
11
+ constructor(provider, options = {}) {
12
+ this.provider = provider;
13
+ this.options = {
14
+ maxConnections: options.maxConnections ?? 100,
15
+ heartbeatInterval: options.heartbeatInterval ?? 30000,
16
+ connectionTimeout: options.connectionTimeout ?? 300000,
17
+ enableCors: options.enableCors ?? true,
18
+ corsOrigins: options.corsOrigins ?? ["*"],
19
+ };
20
+ this.startHeartbeat();
21
+ }
22
+ /**
23
+ * Handle incoming chat request and return SSE response
24
+ */
25
+ async handleChatRequest(request) {
26
+ const { sessionId, message, options = {} } = request;
27
+ const session = new ChatSession(sessionId);
28
+ // Add user message to session
29
+ const userMessage = {
30
+ id: `msg_${Date.now()}_user`,
31
+ role: "user",
32
+ content: message,
33
+ timestamp: Date.now(),
34
+ };
35
+ session.addMessage(userMessage.role, userMessage.content);
36
+ // Create SSE stream
37
+ const stream = this.createEventStream(sessionId, async (writer) => {
38
+ try {
39
+ // Send initial event
40
+ await this.sendEvent(writer, {
41
+ type: "data",
42
+ data: { type: "start", sessionId, messageId: userMessage.id },
43
+ });
44
+ // Generate AI response with streaming
45
+ const aiResponse = await this.provider.streamText({
46
+ prompt: message,
47
+ temperature: options.temperature,
48
+ maxTokens: options.maxTokens,
49
+ systemPrompt: options.systemPrompt,
50
+ });
51
+ if (aiResponse?.textStream) {
52
+ const reader = aiResponse.textStream.getReader();
53
+ let fullResponse = "";
54
+ try {
55
+ while (true) {
56
+ const { done, value } = await reader.read();
57
+ if (done) {
58
+ break;
59
+ }
60
+ fullResponse += value;
61
+ // Send chunk to client
62
+ await this.sendEvent(writer, {
63
+ type: "data",
64
+ data: {
65
+ type: "chunk",
66
+ content: value,
67
+ sessionId,
68
+ },
69
+ });
70
+ }
71
+ }
72
+ finally {
73
+ reader.releaseLock();
74
+ }
75
+ // Add AI response to session
76
+ const assistantMessage = {
77
+ id: `msg_${Date.now()}_assistant`,
78
+ role: "assistant",
79
+ content: fullResponse,
80
+ timestamp: Date.now(),
81
+ metadata: {
82
+ provider: this.provider.constructor.name || "unknown",
83
+ model: "default",
84
+ },
85
+ };
86
+ session.addMessage(assistantMessage.role, assistantMessage.content);
87
+ // Send completion event
88
+ await this.sendEvent(writer, {
89
+ type: "complete",
90
+ data: {
91
+ type: "complete",
92
+ sessionId,
93
+ messageId: assistantMessage.id,
94
+ totalTokens: fullResponse.length, // Rough estimate
95
+ },
96
+ });
97
+ }
98
+ // Persist session
99
+ await session.persist();
100
+ }
101
+ catch (error) {
102
+ await this.sendEvent(writer, {
103
+ type: "error",
104
+ data: {
105
+ type: "error",
106
+ message: error instanceof Error ? error.message : "Unknown error",
107
+ sessionId,
108
+ },
109
+ });
110
+ }
111
+ });
112
+ return new Response(stream, {
113
+ headers: this.getSSEHeaders(),
114
+ });
115
+ }
116
+ /**
117
+ * Create event stream for a session
118
+ */
119
+ createEventStream(sessionId, handler) {
120
+ const encoder = new TextEncoder();
121
+ return new ReadableStream({
122
+ start: async (controller) => {
123
+ // Check connection limits
124
+ if (this.connections.size >= this.options.maxConnections) {
125
+ controller.error(new Error("Maximum connections exceeded"));
126
+ return;
127
+ }
128
+ const { writable, readable } = new TransformStream();
129
+ const writer = writable.getWriter();
130
+ // Store connection
131
+ this.connections.set(sessionId, writer);
132
+ // Setup connection timeout
133
+ const timeout = setTimeout(() => {
134
+ this.closeConnection(sessionId);
135
+ }, this.options.connectionTimeout);
136
+ try {
137
+ if (handler) {
138
+ await handler(writer);
139
+ }
140
+ }
141
+ catch (error) {
142
+ console.error("SSE Handler error:", error);
143
+ }
144
+ finally {
145
+ clearTimeout(timeout);
146
+ this.closeConnection(sessionId);
147
+ }
148
+ // Pipe the readable side to controller
149
+ const reader = readable.getReader();
150
+ const pump = async () => {
151
+ try {
152
+ while (true) {
153
+ const { done, value } = await reader.read();
154
+ if (done) {
155
+ controller.close();
156
+ break;
157
+ }
158
+ controller.enqueue(value);
159
+ }
160
+ }
161
+ catch (error) {
162
+ controller.error(error);
163
+ }
164
+ finally {
165
+ reader.releaseLock();
166
+ }
167
+ };
168
+ pump();
169
+ },
170
+ cancel: () => {
171
+ this.closeConnection(sessionId);
172
+ },
173
+ });
174
+ }
175
+ /**
176
+ * Send SSE event to client
177
+ */
178
+ async sendEvent(writer, event) {
179
+ const encoder = new TextEncoder();
180
+ let eventText = "";
181
+ if (event.id) {
182
+ eventText += `id: ${event.id}\n`;
183
+ }
184
+ if (event.retry) {
185
+ eventText += `retry: ${event.retry}\n`;
186
+ }
187
+ eventText += `event: ${event.type}\n`;
188
+ eventText += `data: ${JSON.stringify(event.data)}\n\n`;
189
+ await writer.write(encoder.encode(eventText));
190
+ }
191
+ /**
192
+ * Get SSE response headers
193
+ */
194
+ getSSEHeaders() {
195
+ const headers = {
196
+ "Content-Type": "text/event-stream",
197
+ "Cache-Control": "no-cache",
198
+ Connection: "keep-alive",
199
+ };
200
+ if (this.options.enableCors) {
201
+ headers["Access-Control-Allow-Origin"] =
202
+ this.options.corsOrigins.join(", ");
203
+ headers["Access-Control-Allow-Headers"] = "Cache-Control";
204
+ }
205
+ return headers;
206
+ }
207
+ /**
208
+ * Close connection and cleanup
209
+ */
210
+ async closeConnection(sessionId) {
211
+ const writer = this.connections.get(sessionId);
212
+ if (writer) {
213
+ try {
214
+ await writer.close();
215
+ }
216
+ catch (error) {
217
+ // Ignore close errors
218
+ }
219
+ this.connections.delete(sessionId);
220
+ }
221
+ }
222
+ /**
223
+ * Start heartbeat to keep connections alive
224
+ */
225
+ startHeartbeat() {
226
+ this.heartbeatInterval = setInterval(async () => {
227
+ const heartbeatEvent = {
228
+ type: "heartbeat",
229
+ data: { timestamp: Date.now() },
230
+ };
231
+ // Send heartbeat to all connections
232
+ for (const [sessionId, writer] of this.connections) {
233
+ try {
234
+ await this.sendEvent(writer, heartbeatEvent);
235
+ }
236
+ catch (error) {
237
+ // Remove failed connections
238
+ this.closeConnection(sessionId);
239
+ }
240
+ }
241
+ }, this.options.heartbeatInterval);
242
+ }
243
+ /**
244
+ * Cleanup and stop handler
245
+ */
246
+ destroy() {
247
+ if (this.heartbeatInterval) {
248
+ clearInterval(this.heartbeatInterval);
249
+ }
250
+ // Close all connections
251
+ for (const sessionId of this.connections.keys()) {
252
+ this.closeConnection(sessionId);
253
+ }
254
+ this.connections.clear();
255
+ }
256
+ /**
257
+ * Get handler statistics
258
+ */
259
+ getStats() {
260
+ return {
261
+ activeConnections: this.connections.size,
262
+ maxConnections: this.options.maxConnections,
263
+ uptime: process.uptime() * 1000,
264
+ };
265
+ }
266
+ }