nitrostack 1.0.15 → 1.0.16

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 (40) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/commands/dev.d.ts.map +1 -1
  3. package/dist/cli/commands/dev.js +2 -1
  4. package/dist/cli/commands/dev.js.map +1 -1
  5. package/dist/cli/mcp-dev-wrapper.js +30 -17
  6. package/dist/cli/mcp-dev-wrapper.js.map +1 -1
  7. package/dist/core/transports/http-server.d.ts.map +1 -1
  8. package/dist/core/transports/http-server.js +21 -1
  9. package/dist/core/transports/http-server.js.map +1 -1
  10. package/package.json +1 -1
  11. package/src/studio/app/api/chat/route.ts +12 -1
  12. package/src/studio/app/api/init/route.ts +28 -4
  13. package/src/studio/app/auth/page.tsx +13 -9
  14. package/src/studio/app/chat/page.tsx +544 -133
  15. package/src/studio/app/health/page.tsx +101 -99
  16. package/src/studio/app/layout.tsx +23 -3
  17. package/src/studio/app/page.tsx +61 -56
  18. package/src/studio/app/ping/page.tsx +13 -8
  19. package/src/studio/app/prompts/page.tsx +72 -70
  20. package/src/studio/app/resources/page.tsx +88 -86
  21. package/src/studio/app/settings/page.tsx +270 -0
  22. package/src/studio/components/Sidebar.tsx +197 -35
  23. package/src/studio/lib/http-client-transport.ts +222 -0
  24. package/src/studio/lib/llm-service.ts +97 -0
  25. package/src/studio/lib/log-manager.ts +76 -0
  26. package/src/studio/lib/mcp-client.ts +103 -13
  27. package/src/studio/package-lock.json +3129 -0
  28. package/src/studio/package.json +1 -0
  29. package/templates/typescript-auth/README.md +3 -1
  30. package/templates/typescript-auth/src/db/database.ts +5 -8
  31. package/templates/typescript-auth/src/index.ts +13 -2
  32. package/templates/typescript-auth/src/modules/addresses/addresses.tools.ts +49 -6
  33. package/templates/typescript-auth/src/modules/cart/cart.tools.ts +13 -17
  34. package/templates/typescript-auth/src/modules/orders/orders.tools.ts +38 -16
  35. package/templates/typescript-auth/src/modules/products/products.tools.ts +4 -4
  36. package/templates/typescript-auth/src/widgets/app/order-confirmation/page.tsx +25 -0
  37. package/templates/typescript-auth/src/widgets/app/products-grid/page.tsx +26 -1
  38. package/templates/typescript-auth-api-key/README.md +3 -1
  39. package/templates/typescript-auth-api-key/src/index.ts +11 -3
  40. package/templates/typescript-starter/README.md +3 -1
@@ -12,17 +12,22 @@ import {
12
12
  Activity,
13
13
  Shield,
14
14
  Wifi,
15
- Zap
15
+ Zap,
16
+ Settings,
17
+ Sparkles,
18
+ Terminal
16
19
  } from 'lucide-react';
17
20
 
18
- const navItems: Array<{ id: TabType; label: string; icon: any; path: string }> = [
21
+ const navItems: Array<{ id: TabType | 'settings' | 'logs'; label: string; icon: any; path: string }> = [
19
22
  { id: 'tools', label: 'Tools', icon: Wrench, path: '/' },
20
23
  { id: 'chat', label: 'AI Chat', icon: MessageSquare, path: '/chat' },
21
24
  { id: 'resources', label: 'Resources', icon: Package, path: '/resources' },
22
25
  { id: 'prompts', label: 'Prompts', icon: FileText, path: '/prompts' },
23
26
  { id: 'health', label: 'Health', icon: Activity, path: '/health' },
27
+ { id: 'logs', label: 'Logs', icon: Terminal, path: '/logs' },
24
28
  { id: 'auth', label: 'OAuth 2.1', icon: Shield, path: '/auth' },
25
29
  { id: 'ping', label: 'Ping', icon: Wifi, path: '/ping' },
30
+ { id: 'settings', label: 'Settings', icon: Settings, path: '/settings' },
26
31
  ];
27
32
 
28
33
  export function Sidebar() {
@@ -30,11 +35,15 @@ export function Sidebar() {
30
35
  const router = useRouter();
31
36
  const pathname = usePathname();
32
37
  const [mounted, setMounted] = useState(false);
38
+ const [isCollapsed, setIsCollapsed] = useState(false);
33
39
 
34
40
  useEffect(() => {
35
41
  setMounted(true);
36
42
  // Force dark mode
37
43
  document.documentElement.className = 'dark antialiased';
44
+ // Load collapse state from localStorage
45
+ const saved = localStorage.getItem('sidebar_collapsed');
46
+ if (saved !== null) setIsCollapsed(saved === 'true');
38
47
  }, []);
39
48
 
40
49
  const handleNavigation = (path: string) => {
@@ -47,53 +56,157 @@ export function Sidebar() {
47
56
  return 'disconnected';
48
57
  };
49
58
 
59
+ const toggleSidebar = () => {
60
+ const newState = !isCollapsed;
61
+ setIsCollapsed(newState);
62
+ localStorage.setItem('sidebar_collapsed', String(newState));
63
+ // Dispatch custom event to update layout
64
+ window.dispatchEvent(new Event('sidebar-toggle'));
65
+ };
66
+
50
67
  if (!mounted) return null;
51
68
 
52
69
  return (
53
- <nav className="fixed left-0 top-0 h-screen w-64 glass flex flex-col z-50">
54
- {/* Header */}
55
- <div className="p-6 border-b border-border">
56
- <div className="flex items-center gap-3 mb-4">
57
- <div className="w-10 h-10 rounded-lg bg-gradient-to-br from-primary to-amber-500 flex items-center justify-center shadow-lg">
58
- <Zap className="w-6 h-6 text-black" />
70
+ <nav className={`fixed left-0 top-0 h-screen glass flex flex-col z-50 border-r border-border/50 transition-all duration-300 ease-in-out ${
71
+ isCollapsed ? 'w-16' : 'w-60'
72
+ }`}>
73
+ {/* Compact Professional Header */}
74
+ <div className="relative p-3 border-b border-border/50 bg-gradient-to-b from-card/80 to-transparent">
75
+ <div className="flex items-center justify-between mb-2">
76
+ {/* Minimalist Professional Logo */}
77
+ <div
78
+ className="flex items-center gap-2 group cursor-pointer flex-1"
79
+ onClick={() => handleNavigation('/')}
80
+ >
81
+ <div className="relative flex-shrink-0">
82
+ <div className="absolute inset-0 rounded-lg bg-gradient-to-br from-primary to-amber-500 blur opacity-30 group-hover:opacity-50 transition-opacity duration-300" />
83
+ <div className="relative w-9 h-9 rounded-lg bg-gradient-to-br from-slate-900 to-slate-800 border border-primary/30 flex items-center justify-center shadow-lg group-hover:shadow-primary/30 transition-all duration-300 overflow-hidden">
84
+ {/* Geometric N logo */}
85
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none" className="relative z-10">
86
+ <path d="M4 16V4L16 16V4" stroke="url(#gradient)" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"/>
87
+ <defs>
88
+ <linearGradient id="gradient" x1="4" y1="4" x2="16" y2="16">
89
+ <stop offset="0%" stopColor="#F59E0B" />
90
+ <stop offset="100%" stopColor="#F97316" />
91
+ </linearGradient>
92
+ </defs>
93
+ </svg>
94
+ {/* Subtle glow effect */}
95
+ <div className="absolute inset-0 bg-gradient-to-tr from-primary/20 to-amber-500/20 group-hover:opacity-100 opacity-0 transition-opacity duration-300" />
96
+ </div>
59
97
  </div>
60
- <div>
61
- <h1 className="text-xl font-bold bg-gradient-to-r from-primary to-amber-500 bg-clip-text text-transparent">
62
- NitroStack
98
+
99
+ {!isCollapsed && (
100
+ <div className="flex-1 overflow-hidden">
101
+ <h1 className="text-base font-bold bg-gradient-to-r from-primary to-amber-500 bg-clip-text text-transparent tracking-tight whitespace-nowrap">
102
+ NitroStudio
63
103
  </h1>
64
- <p className="text-xs text-muted-foreground font-medium">Studio v3.1</p>
104
+ <p className="text-[9px] text-muted-foreground font-medium uppercase tracking-wider">
105
+ MCP Suite
106
+ </p>
107
+ </div>
108
+ )}
65
109
  </div>
110
+
111
+ {/* Toggle Button */}
112
+ <button
113
+ onClick={toggleSidebar}
114
+ className="flex-shrink-0 w-7 h-7 rounded-md bg-muted/50 hover:bg-muted border border-border/50 hover:border-primary/30 flex items-center justify-center transition-all duration-300 group/toggle"
115
+ title={isCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
116
+ >
117
+ <svg
118
+ width="14"
119
+ height="14"
120
+ viewBox="0 0 24 24"
121
+ fill="none"
122
+ className={`text-muted-foreground group-hover/toggle:text-primary transition-all duration-300 ${
123
+ isCollapsed ? 'rotate-180' : ''
124
+ }`}
125
+ >
126
+ <path d="M15 18L9 12L15 6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
127
+ </svg>
128
+ </button>
66
129
  </div>
67
130
 
131
+ {/* Compact Status Indicators */}
132
+ {!isCollapsed ? (
133
+ <div className="space-y-1.5">
68
134
  {/* Connection Status */}
69
- <div className="flex items-center gap-2 px-3 py-2 rounded-lg bg-muted/50 border border-border/50">
70
- <div className="relative flex items-center justify-center">
135
+ <div className="relative group">
136
+ <div className="relative flex items-center gap-2 px-2 py-1.5 rounded-md bg-card/60 border border-border/50 backdrop-blur-sm group-hover:border-primary/30 transition-all duration-300">
137
+ <div className="relative flex items-center justify-center flex-shrink-0">
138
+ {getConnectionStatus() === 'connected' && (
139
+ <div className="absolute inset-0 rounded-full bg-emerald-500/30 animate-ping" />
140
+ )}
71
141
  <div
72
- className={`w-2.5 h-2.5 rounded-full ${
142
+ className={`relative w-2 h-2 rounded-full transition-all duration-300 ${
73
143
  getConnectionStatus() === 'connected'
74
144
  ? 'bg-emerald-500'
75
145
  : getConnectionStatus() === 'connecting'
76
- ? 'bg-amber-500'
146
+ ? 'bg-amber-500 animate-pulse'
77
147
  : 'bg-rose-500'
78
148
  }`}
79
149
  style={{
80
150
  boxShadow: getConnectionStatus() === 'connected'
81
- ? '0 0 12px rgba(34, 197, 94, 0.6)'
151
+ ? '0 0 8px rgba(34, 197, 94, 0.6)'
82
152
  : getConnectionStatus() === 'connecting'
83
- ? '0 0 12px rgba(245, 158, 11, 0.6)'
84
- : '0 0 12px rgba(239, 68, 68, 0.6)'
153
+ ? '0 0 8px rgba(245, 158, 11, 0.6)'
154
+ : '0 0 8px rgba(239, 68, 68, 0.6)'
85
155
  }}
86
156
  />
87
157
  </div>
88
- <span className="text-xs font-semibold uppercase tracking-wide text-foreground">
158
+ <span className="text-[10px] font-semibold uppercase tracking-wide text-foreground">
89
159
  {connection.status}
90
160
  </span>
91
161
  </div>
92
162
  </div>
93
163
 
94
- {/* Navigation */}
95
- <div className="flex-1 overflow-y-auto py-4 px-2">
96
- <div className="space-y-1">
164
+ {/* Transport Mode */}
165
+ <div className="relative group">
166
+ <div className="relative px-2 py-1.5 rounded-md bg-primary/5 border border-primary/20 backdrop-blur-sm group-hover:border-primary/30 transition-all duration-300">
167
+ <div className="flex items-center justify-between text-[10px]">
168
+ <span className="font-medium text-muted-foreground">Transport:</span>
169
+ <span className="font-bold text-primary uppercase">
170
+ {typeof localStorage !== 'undefined' ? localStorage.getItem('mcp_transport') || 'STDIO' : 'STDIO'}
171
+ </span>
172
+ </div>
173
+ </div>
174
+ </div>
175
+ </div>
176
+ ) : (
177
+ <div className="flex flex-col items-center gap-1.5">
178
+ {/* Collapsed: Just the status dot */}
179
+ <div
180
+ className="relative w-7 h-7 rounded-md bg-card/60 border border-border/50 flex items-center justify-center group hover:border-primary/30 transition-all"
181
+ title={`Status: ${connection.status}`}
182
+ >
183
+ {getConnectionStatus() === 'connected' && (
184
+ <div className="absolute inset-0 rounded-md bg-emerald-500/20 animate-ping" />
185
+ )}
186
+ <div
187
+ className={`relative w-2 h-2 rounded-full ${
188
+ getConnectionStatus() === 'connected'
189
+ ? 'bg-emerald-500'
190
+ : getConnectionStatus() === 'connecting'
191
+ ? 'bg-amber-500 animate-pulse'
192
+ : 'bg-rose-500'
193
+ }`}
194
+ style={{
195
+ boxShadow: getConnectionStatus() === 'connected'
196
+ ? '0 0 8px rgba(34, 197, 94, 0.8)'
197
+ : getConnectionStatus() === 'connecting'
198
+ ? '0 0 8px rgba(245, 158, 11, 0.8)'
199
+ : '0 0 8px rgba(239, 68, 68, 0.8)'
200
+ }}
201
+ />
202
+ </div>
203
+ </div>
204
+ )}
205
+ </div>
206
+
207
+ {/* Compact Navigation */}
208
+ <div className="flex-1 overflow-y-auto py-2 px-2 scrollbar-thin scrollbar-thumb-border scrollbar-track-transparent">
209
+ <div className={`space-y-0.5 ${isCollapsed ? 'flex flex-col items-center' : ''}`}>
97
210
  {navItems.map((item) => {
98
211
  const Icon = item.icon;
99
212
  const isActive = pathname === item.path;
@@ -102,31 +215,80 @@ export function Sidebar() {
102
215
  <button
103
216
  key={item.id}
104
217
  onClick={() => handleNavigation(item.path)}
218
+ title={isCollapsed ? item.label : undefined}
105
219
  className={`
106
- w-full flex items-center gap-3 px-4 py-2.5 text-sm font-medium rounded-lg transition-all group
220
+ relative flex items-center gap-2 text-sm font-medium rounded-lg transition-all duration-300 group overflow-hidden
221
+ ${isCollapsed ? 'w-10 h-10 justify-center' : 'w-full px-3 py-2'}
107
222
  ${isActive
108
- ? 'bg-primary/10 text-primary shadow-sm ring-1 ring-primary/20'
109
- : 'text-foreground hover:bg-muted hover:text-primary'
223
+ ? 'bg-gradient-to-r from-primary/15 to-amber-500/10 text-primary shadow-md ring-1 ring-primary/30'
224
+ : 'text-foreground/70 hover:bg-primary/5 hover:text-primary'
110
225
  }
111
226
  `}
112
227
  >
113
- <Icon className={`w-5 h-5 ${isActive ? 'text-primary' : 'text-muted-foreground group-hover:text-primary'}`} />
114
- <span>{item.label}</span>
228
+ {/* Active indicator */}
229
+ {isActive && !isCollapsed && (
230
+ <div className="absolute left-0 top-1/2 -translate-y-1/2 w-0.5 h-6 bg-gradient-to-b from-primary to-amber-500 rounded-r-full" />
231
+ )}
232
+
233
+ {/* Icon */}
234
+ <div className={`relative flex-shrink-0 ${isActive ? 'scale-105' : 'group-hover:scale-105'} transition-transform duration-300`}>
235
+ <Icon
236
+ className={`w-4 h-4 ${
237
+ isActive
238
+ ? 'text-primary'
239
+ : 'text-muted-foreground group-hover:text-primary'
240
+ }`}
241
+ strokeWidth={isActive ? 2.5 : 2}
242
+ />
243
+ </div>
244
+
245
+ {/* Label - only show when expanded */}
246
+ {!isCollapsed && (
247
+ <span className="text-xs whitespace-nowrap overflow-hidden">
248
+ {item.label}
249
+ </span>
250
+ )}
251
+
252
+ {/* Subtle shine effect */}
253
+ <div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/5 to-transparent -translate-x-full group-hover:translate-x-full transition-transform duration-700 ease-out pointer-events-none" />
115
254
  </button>
116
255
  );
117
256
  })}
118
257
  </div>
119
258
  </div>
120
259
 
121
- {/* Footer */}
122
- <div className="p-4 border-t border-border bg-card/50 backdrop-blur">
123
- {/* Version */}
124
- <div className="px-4 py-2 text-xs text-muted-foreground">
125
- <div className="flex items-center justify-between">
126
- <span>MCP Protocol</span>
127
- <span className="font-mono font-semibold text-primary">v1.0</span>
260
+ {/* Compact Footer */}
261
+ <div className="p-2 border-t border-border/50 bg-gradient-to-t from-card/60 to-transparent backdrop-blur-sm">
262
+ {!isCollapsed ? (
263
+ <div className="space-y-1.5">
264
+ {/* Version info */}
265
+ <div className="px-2 py-1.5 rounded-md bg-muted/30 border border-border/30">
266
+ <div className="flex items-center justify-between text-[9px]">
267
+ <span className="font-medium text-muted-foreground uppercase tracking-wide">MCP v1.0</span>
268
+ <span className="font-bold text-foreground">NitroStack</span>
269
+ </div>
270
+ </div>
271
+
272
+ {/* Copyright */}
273
+ <div className="text-center">
274
+ <p className="text-[8px] text-muted-foreground/50 font-medium">
275
+ © 2025 NitroStudio
276
+ </p>
277
+ </div>
278
+ </div>
279
+ ) : (
280
+ <div className="flex flex-col items-center">
281
+ {/* Collapsed: Minimal version indicator */}
282
+ <div
283
+ className="w-10 h-10 rounded-md bg-muted/30 border border-border/30 flex items-center justify-center group hover:border-primary/30 transition-all"
284
+ title="MCP v1.0 • NitroStack"
285
+ >
286
+ <span className="text-[9px] font-bold text-primary group-hover:scale-110 transition-transform">
287
+ v1
288
+ </span>
128
289
  </div>
129
290
  </div>
291
+ )}
130
292
  </div>
131
293
  </nav>
132
294
  );
@@ -0,0 +1,222 @@
1
+ /**
2
+ * HTTP Client Transport for MCP
3
+ *
4
+ * Implements HTTP-based communication with MCP servers using SSE for server-to-client messages
5
+ * and POST for client-to-server messages.
6
+ *
7
+ * Note: This uses EventSource which is browser-only. For server-side usage, you need eventsource polyfill.
8
+ */
9
+
10
+ import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
11
+ import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
12
+
13
+ // EventSource type that works in both browser and Node.js
14
+ type EventSourceType = typeof EventSource extends { prototype: infer T } ? T : any;
15
+
16
+ export interface HttpClientTransportOptions {
17
+ /**
18
+ * Base URL of the MCP server (e.g., http://localhost:3000)
19
+ */
20
+ baseUrl: string;
21
+
22
+ /**
23
+ * Base path for MCP endpoints (default: '/mcp')
24
+ */
25
+ basePath?: string;
26
+
27
+ /**
28
+ * Optional headers to include in requests (e.g., Authorization)
29
+ */
30
+ headers?: Record<string, string>;
31
+ }
32
+
33
+ /**
34
+ * HTTP Client Transport
35
+ *
36
+ * Connects to an MCP server over HTTP using:
37
+ * - SSE (Server-Sent Events) for receiving messages from server
38
+ * - HTTP POST for sending messages to server
39
+ */
40
+ export class HttpClientTransport implements Transport {
41
+ private baseUrl: string;
42
+ private basePath: string;
43
+ private headers: Record<string, string>;
44
+ private eventSource: EventSourceType | null = null;
45
+ private clientId: string;
46
+ private messageHandler?: (message: JSONRPCMessage) => Promise<void>;
47
+ private closeHandler?: () => void;
48
+ private errorHandler?: (error: Error) => void;
49
+ private isConnected = false;
50
+ private EventSourceImpl: any;
51
+
52
+ constructor(options: HttpClientTransportOptions) {
53
+ this.baseUrl = options.baseUrl.replace(/\/$/, ''); // Remove trailing slash
54
+ this.basePath = options.basePath || '/mcp';
55
+ this.headers = options.headers || {};
56
+ this.clientId = `client_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
57
+
58
+ // Use native EventSource in browser, require polyfill in Node.js
59
+ if (typeof EventSource !== 'undefined') {
60
+ this.EventSourceImpl = EventSource;
61
+ } else {
62
+ // In Node.js environment, try to load eventsource package
63
+ try {
64
+ this.EventSourceImpl = require('eventsource');
65
+ } catch (e) {
66
+ throw new Error(
67
+ 'EventSource is not available. In Node.js, install "eventsource" package: npm install eventsource'
68
+ );
69
+ }
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Start the transport by connecting to SSE endpoint
75
+ */
76
+ async start(): Promise<void> {
77
+ if (this.isConnected) {
78
+ console.warn('⚠️ HTTP transport already connected');
79
+ return;
80
+ }
81
+
82
+ return new Promise((resolve, reject) => {
83
+ try {
84
+ const sseUrl = `${this.baseUrl}${this.basePath}/sse?clientId=${this.clientId}`;
85
+ console.log('🔌 Connecting to SSE endpoint:', sseUrl);
86
+
87
+ // Create EventSource for receiving server messages
88
+ this.eventSource = new this.EventSourceImpl(sseUrl) as EventSourceType;
89
+
90
+ this.eventSource.onopen = () => {
91
+ console.log('✅ SSE connection established');
92
+ this.isConnected = true;
93
+ resolve();
94
+ };
95
+
96
+ this.eventSource.onmessage = (event) => {
97
+ try {
98
+ const message = JSON.parse(event.data) as JSONRPCMessage;
99
+ console.log('📨 Received message from server:', message);
100
+
101
+ if (this.messageHandler) {
102
+ this.messageHandler(message).catch((error) => {
103
+ console.error('Error handling message:', error);
104
+ if (this.errorHandler) {
105
+ this.errorHandler(error);
106
+ }
107
+ });
108
+ }
109
+ } catch (error) {
110
+ console.error('Failed to parse SSE message:', error);
111
+ if (this.errorHandler) {
112
+ this.errorHandler(error as Error);
113
+ }
114
+ }
115
+ };
116
+
117
+ this.eventSource.onerror = (error) => {
118
+ console.error('❌ SSE connection error:', error);
119
+ this.isConnected = false;
120
+
121
+ if (this.errorHandler) {
122
+ this.errorHandler(new Error('SSE connection failed'));
123
+ }
124
+
125
+ if (!this.isConnected) {
126
+ // Connection failed during initial setup
127
+ reject(new Error('Failed to establish SSE connection'));
128
+ }
129
+ };
130
+
131
+ // Set a timeout for connection establishment
132
+ setTimeout(() => {
133
+ if (!this.isConnected) {
134
+ this.eventSource?.close();
135
+ reject(new Error('SSE connection timeout'));
136
+ }
137
+ }, 10000); // 10 second timeout
138
+
139
+ } catch (error) {
140
+ reject(error);
141
+ }
142
+ });
143
+ }
144
+
145
+ /**
146
+ * Send a message to the server
147
+ */
148
+ async send(message: JSONRPCMessage): Promise<void> {
149
+ if (!this.isConnected) {
150
+ throw new Error('HTTP transport not connected');
151
+ }
152
+
153
+ try {
154
+ const url = `${this.baseUrl}${this.basePath}/message`;
155
+ console.log('📤 Sending message to server:', message);
156
+
157
+ const response = await fetch(url, {
158
+ method: 'POST',
159
+ headers: {
160
+ 'Content-Type': 'application/json',
161
+ ...this.headers,
162
+ },
163
+ body: JSON.stringify({
164
+ clientId: this.clientId,
165
+ message,
166
+ }),
167
+ });
168
+
169
+ if (!response.ok) {
170
+ const errorText = await response.text();
171
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
172
+ }
173
+
174
+ console.log('✅ Message sent successfully');
175
+ } catch (error) {
176
+ console.error('Failed to send message:', error);
177
+ throw error;
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Close the transport
183
+ */
184
+ async close(): Promise<void> {
185
+ console.log('🛑 Closing HTTP transport...');
186
+
187
+ if (this.eventSource) {
188
+ this.eventSource.close();
189
+ this.eventSource = null;
190
+ }
191
+
192
+ this.isConnected = false;
193
+
194
+ if (this.closeHandler) {
195
+ this.closeHandler();
196
+ }
197
+
198
+ console.log('✅ HTTP transport closed');
199
+ }
200
+
201
+ /**
202
+ * Set handler for incoming messages
203
+ */
204
+ onmessage = (handler: (message: JSONRPCMessage) => Promise<void>): void => {
205
+ this.messageHandler = handler;
206
+ };
207
+
208
+ /**
209
+ * Set handler for connection close
210
+ */
211
+ onclose = (handler: () => void): void => {
212
+ this.closeHandler = handler;
213
+ };
214
+
215
+ /**
216
+ * Set handler for errors
217
+ */
218
+ onerror = (handler: (error: Error) => void): void => {
219
+ this.errorHandler = handler;
220
+ };
221
+ }
222
+
@@ -24,12 +24,109 @@ export interface ChatResponse {
24
24
  }
25
25
 
26
26
  export class LLMService {
27
+ // System prompt to improve tool usage (especially for Gemini)
28
+ private getSystemPrompt(tools: any[]): string {
29
+ return `You are an intelligent AI assistant with access to ${tools.length} powerful tools. Your goal is to help users accomplish their tasks efficiently and intelligently.
30
+
31
+ **CORE PRINCIPLES FOR TOOL USAGE:**
32
+
33
+ 1. **Be Proactive & Infer Context**:
34
+ - Infer obvious information instead of asking (e.g., "Bangalore" → Karnataka, India; "New York" → NY, USA)
35
+ - Use common sense defaults when reasonable
36
+ - Don't ask for information you can deduce from context
37
+
38
+ 2. **Chain Tools Intelligently**:
39
+ - Use multiple tools in sequence to accomplish complex tasks
40
+ - If a task requires multiple steps, execute them automatically
41
+ - Example: login → fetch data → process → display results
42
+ - Don't ask permission for each step in an obvious workflow
43
+
44
+ 3. **Maintain Context Awareness**:
45
+ - Remember information from previous tool calls in THIS conversation
46
+ - Extract and reuse data (IDs, names, values) from previous tool results
47
+ - Example: If browse_products returned products with IDs, use those IDs for add_to_cart
48
+ - Match user requests to previous data (e.g., "apple" → find product with name "Apple" → use its ID)
49
+ - Track state across the conversation (logged in status, product IDs, order IDs, etc.)
50
+ - NEVER ask for information you already have from a previous tool call
51
+
52
+ 4. **Use Smart Defaults**:
53
+ - Apply sensible default values when parameters are optional
54
+ - Common defaults: page=1, limit=10, sort=recent, etc.
55
+ - Only ask for clarification when truly ambiguous
56
+
57
+ 5. **Minimize User Friction**:
58
+ - Don't ask for every detail - use inference and defaults
59
+ - Chain related operations seamlessly
60
+ - Provide concise summaries after multi-step operations
61
+ - Be conversational but efficient
62
+
63
+ 6. **Handle Errors Gracefully**:
64
+ - If authentication required, guide user to login
65
+ - If prerequisite missing, suggest the required step
66
+ - If operation fails, explain why and suggest alternatives
67
+ - Always provide a helpful next step
68
+
69
+ 7. **Tool Call Best Practices**:
70
+ - Read tool descriptions carefully to understand their purpose
71
+ - Use exact parameter names as specified in the schema
72
+ - Pay attention to required vs optional parameters
73
+ - If a tool says "Requires authentication" - CALL IT ANYWAY! Auth is handled automatically
74
+ - Don't ask for credentials preemptively - only if a tool explicitly fails
75
+ - Look for examples in tool schemas for guidance
76
+
77
+ **AUTHENTICATION HANDLING:**
78
+
79
+ - Authentication tokens are AUTOMATICALLY handled in the background
80
+ - If a tool says "Requires authentication", you can STILL call it directly - auth is transparent
81
+ - NEVER ask users for credentials unless a tool explicitly fails with an auth error
82
+ - If you get an auth error, THEN suggest the user login using the login tool
83
+ - Once a user logs in (or if already logged in), the session persists automatically
84
+ - You don't need to check if user is authenticated - just call the tool and let the system handle it
85
+
86
+ **EXAMPLES:**
87
+
88
+ **Authentication:**
89
+ User: "whoami" → Call whoami tool directly (don't ask for login)
90
+ User: "show my orders" → Call get_order_history directly (don't ask for login)
91
+ User: "what's in my cart" → Call view_cart directly (don't ask for login)
92
+
93
+ **Context Awareness:**
94
+ 1. browse_products returns: [{id: "prod-3", name: "Apple", price: 0.99}, ...]
95
+ 2. User: "add apple to cart" → Extract ID "prod-3" from previous result → Call add_to_cart({product_id: "prod-3", quantity: 1})
96
+ 3. User: "add 2 more" → Remember prod-3 → Call add_to_cart({product_id: "prod-3", quantity: 2})
97
+
98
+ **NEVER do this:**
99
+ ❌ User: "add apple to cart" → "What is the product ID?" (You already have it!)
100
+ ❌ User: "add to cart" → "Which product?" (Look at conversation context!)
101
+
102
+ Only if tool returns auth error → THEN suggest: "Please login with your credentials"
103
+
104
+ **REMEMBER:**
105
+
106
+ - You have access to real, functional tools - use them!
107
+ - Call tools directly - don't ask for permission or credentials first
108
+ - Your goal is to be helpful, efficient, and reduce user friction
109
+ - Think through multi-step workflows and execute them seamlessly
110
+ - Use your intelligence to fill gaps rather than always asking questions`;
111
+ }
112
+
27
113
  async chat(
28
114
  provider: LLMProvider,
29
115
  messages: ChatMessage[],
30
116
  tools: any[],
31
117
  apiKey: string
32
118
  ): Promise<ChatResponse> {
119
+ // Inject system prompt at the beginning if not already present
120
+ if (messages.length > 0 && messages[0].role !== 'system') {
121
+ messages = [
122
+ {
123
+ role: 'system',
124
+ content: this.getSystemPrompt(tools),
125
+ },
126
+ ...messages,
127
+ ];
128
+ }
129
+
33
130
  if (provider === 'openai') {
34
131
  return this.chatOpenAI(messages, tools, apiKey);
35
132
  } else if (provider === 'gemini') {