@probelabs/probe-chat 0.6.0-rc56 → 0.6.0-rc58

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/index.html CHANGED
@@ -1735,15 +1735,67 @@
1735
1735
  const response = await fetch(`/api/session/${sessionId}/history`);
1736
1736
  const data = await response.json();
1737
1737
 
1738
+ console.log(`[DEBUG] Session restoration data:`, {
1739
+ exists: data.exists,
1740
+ historyLength: data.history ? data.history.length : 'null/undefined',
1741
+ condition: data.exists && data.history && data.history.length > 0
1742
+ });
1743
+
1738
1744
  if (data.exists && data.history && data.history.length > 0) {
1739
1745
  console.log(`Restored ${data.history.length} messages for session: ${sessionId}`);
1740
1746
 
1747
+ // Count message types for debugging
1748
+ const messageTypes = {};
1749
+ data.history.forEach(msg => {
1750
+ messageTypes[msg.role] = (messageTypes[msg.role] || 0) + 1;
1751
+ });
1752
+ console.log(`[DEBUG] Message types to restore:`, messageTypes);
1753
+
1741
1754
  // Render restored messages
1742
- data.history.forEach(message => {
1755
+ let currentAiMessage = null;
1756
+ data.history.forEach((message, index) => {
1757
+ console.log(`[DEBUG] Processing message ${index + 1}/${data.history.length}: role=${message.role}`);
1743
1758
  if (message.role === 'user') {
1744
- addUserMessage(message.content, message.images || []);
1759
+ // Ensure images is always an array
1760
+ const images = Array.isArray(message.images) ? message.images : [];
1761
+ addUserMessage(message.content, images);
1762
+ currentAiMessage = null; // Reset for new conversation turn
1745
1763
  } else if (message.role === 'assistant') {
1746
1764
  addAssistantMessage(message.content);
1765
+ // Get the most recent AI message for tool call rendering
1766
+ const messagesDiv = document.getElementById('messages');
1767
+ const aiMessages = messagesDiv.querySelectorAll('.ai-message');
1768
+ currentAiMessage = aiMessages[aiMessages.length - 1];
1769
+ } else if (message.role === 'toolCall') {
1770
+ console.log(`[DEBUG] Rendering toolCall message`);
1771
+ // Handle tool call messages during restoration
1772
+ try {
1773
+ // Parse the tool call from the stored message
1774
+ const toolCall = {
1775
+ name: message.metadata?.name || 'unknown',
1776
+ args: message.metadata?.args || {},
1777
+ timestamp: message.timestamp,
1778
+ status: 'completed'
1779
+ };
1780
+
1781
+ // If we have a current AI message, add the tool call to it
1782
+ if (currentAiMessage) {
1783
+ addToolCallToMessage(currentAiMessage, toolCall);
1784
+ } else {
1785
+ console.log(`[DEBUG] No current AI message for tool call, creating temporary message`);
1786
+ // Create a temporary AI message for the tool call
1787
+ const tempDiv = document.createElement('div');
1788
+ tempDiv.className = 'ai-message';
1789
+ const messagesDiv = document.getElementById('messages');
1790
+ messagesDiv.appendChild(tempDiv);
1791
+ addToolCallToMessage(tempDiv, toolCall);
1792
+ currentAiMessage = tempDiv;
1793
+ }
1794
+ } catch (error) {
1795
+ console.error('[DEBUG] Error rendering tool call:', error, message);
1796
+ }
1797
+ } else {
1798
+ console.log(`[DEBUG] Skipping message with role: ${message.role}`);
1747
1799
  }
1748
1800
  });
1749
1801
 
@@ -1778,12 +1830,17 @@
1778
1830
  }
1779
1831
  }, 100); // Small delay to ensure DOM is fully updated
1780
1832
  } else {
1781
- console.log(`No history found for session: ${sessionId}`);
1833
+ console.log(`[DEBUG] No history found for session: ${sessionId}`, {
1834
+ exists: data.exists,
1835
+ historyExists: !!data.history,
1836
+ historyLength: data.history ? data.history.length : 'N/A'
1837
+ });
1782
1838
  // Show user-friendly message for session not found
1783
1839
  showSessionNotFoundMessage(sessionId);
1784
1840
  }
1785
1841
  } catch (error) {
1786
- console.error('Error restoring session history:', error);
1842
+ console.error('[DEBUG] Error restoring session history:', error);
1843
+ console.error('[DEBUG] Error stack:', error.stack);
1787
1844
  // Show error message for network or other errors
1788
1845
  showSessionNotFoundMessage(sessionId);
1789
1846
  }
@@ -2571,6 +2628,14 @@
2571
2628
  }
2572
2629
 
2573
2630
  toolDescription = `Extracting code from ${filePath}${lineInfo}`;
2631
+ } else if (toolCall.name === 'searchFiles') {
2632
+ const pattern = toolCall.args.pattern || '';
2633
+ const directory = toolCall.args.directory || '.';
2634
+ toolDescription = `Searching for files matching "${pattern}"${directory !== '.' ? ` in ${directory}` : ''}`;
2635
+ } else if (toolCall.name === 'listFiles') {
2636
+ const directory = toolCall.args.directory || '.';
2637
+ const pattern = toolCall.args.pattern || '';
2638
+ toolDescription = `Listing files${pattern ? ` matching "${pattern}"` : ''}${directory !== '.' ? ` in ${directory}` : ''}`;
2574
2639
  } else {
2575
2640
  toolDescription = `Using ${toolCall.name} tool`;
2576
2641
  }
package/index.js CHANGED
@@ -58,6 +58,7 @@ export function main() {
58
58
  .option('-p, --port <port>', 'Port to run web server on (default: 8080)')
59
59
  .option('-m, --message <message>', 'Send a single message and exit (non-interactive mode)')
60
60
  .option('-s, --session-id <sessionId>', 'Specify a session ID for the chat (optional)')
61
+ .option('--images <urls>', 'Comma-separated list of image URLs to include in the message')
61
62
  .option('--json', 'Output the response as JSON in non-interactive mode')
62
63
  .option('--max-iterations <number>', 'Maximum number of tool iterations allowed (default: 30)')
63
64
  .option('--prompt <value>', 'Use a custom prompt (values: architect, code-review, support, path to a file, or arbitrary string)')
@@ -76,6 +77,13 @@ export function main() {
76
77
  const options = program.opts();
77
78
  const pathArg = program.args[0];
78
79
 
80
+ // Parse image URLs if provided
81
+ let imageUrls = [];
82
+ if (options.images) {
83
+ imageUrls = options.images.split(',').map(url => url.trim()).filter(url => url.length > 0);
84
+ console.log(`Using ${imageUrls.length} image(s):`, imageUrls);
85
+ }
86
+
79
87
  // --- Logging Configuration ---
80
88
  const isPipedInput = process.env.PROBE_STDIN_PIPED === '1';
81
89
  const isNonInteractive = !!options.message || isPipedInput;
@@ -315,6 +323,27 @@ export function main() {
315
323
  const googleApiKey = process.env.GOOGLE_API_KEY;
316
324
  const hasApiKeys = !!(anthropicApiKey || openaiApiKey || googleApiKey);
317
325
 
326
+ // --- Web Mode (check before non-interactive to override) ---
327
+ if (options.web) {
328
+ if (!hasApiKeys) {
329
+ // Use logWarn for web mode warning
330
+ logWarn(chalk.yellow('Warning: No API key provided. The web interface will show instructions on how to set up API keys.'));
331
+ }
332
+ // Import and start web server
333
+ import('./webServer.js')
334
+ .then(async module => {
335
+ const { startWebServer } = module;
336
+ logInfo(`Starting web server on port ${process.env.PORT || 8080}...`);
337
+ await startWebServer(version, hasApiKeys, { allowEdit: options.allowEdit });
338
+ })
339
+ .catch(error => {
340
+ logError(chalk.red(`Error starting web server: ${error.message}`));
341
+ process.exit(1);
342
+ });
343
+ return; // Exit main function
344
+ }
345
+ // --- End Web Mode ---
346
+
318
347
  // --- Non-Interactive Mode ---
319
348
  if (isNonInteractive) {
320
349
  if (!hasApiKeys) {
@@ -370,7 +399,7 @@ export function main() {
370
399
  }
371
400
 
372
401
  logInfo('Sending message...'); // Log only if debug
373
- const result = await chat.chat(message, chat.getSessionId()); // Use the chat's current session ID
402
+ const result = await chat.chat(message, chat.getSessionId(), null, imageUrls); // Use the chat's current session ID and pass images
374
403
 
375
404
  if (result && typeof result === 'object' && result.response !== undefined) {
376
405
  if (options.json) {
@@ -417,28 +446,6 @@ export function main() {
417
446
  // --- End Non-Interactive Mode ---
418
447
 
419
448
 
420
- // --- Web Mode ---
421
- if (options.web) {
422
- if (!hasApiKeys) {
423
- // Use logWarn for web mode warning
424
- logWarn(chalk.yellow('Warning: No API key provided. The web interface will show instructions on how to set up API keys.'));
425
- }
426
- // Import and start web server
427
- import('./webServer.js')
428
- .then(module => {
429
- const { startWebServer } = module;
430
- logInfo(`Starting web server on port ${process.env.PORT || 8080}...`);
431
- startWebServer(version, hasApiKeys, { allowEdit: options.allowEdit });
432
- })
433
- .catch(error => {
434
- logError(chalk.red(`Error starting web server: ${error.message}`));
435
- process.exit(1);
436
- });
437
- return; // Exit main function
438
- }
439
- // --- End Web Mode ---
440
-
441
-
442
449
  // --- Interactive CLI Mode ---
443
450
  // (This block only runs if not non-interactive and not web mode)
444
451
 
@@ -544,7 +551,7 @@ export function main() {
544
551
 
545
552
  const spinner = ora('Thinking...').start(); // Spinner is ok for interactive mode
546
553
  try {
547
- const result = await chat.chat(message); // Uses internal session ID
554
+ const result = await chat.chat(message, null, null, imageUrls); // Uses internal session ID and pass images
548
555
  spinner.stop();
549
556
 
550
557
  logInfo(chalk.green('Assistant:'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@probelabs/probe-chat",
3
- "version": "0.6.0-rc56",
3
+ "version": "0.6.0-rc58",
4
4
  "description": "CLI and web interface for Probe code search (formerly @probelabs/probe-web and @probelabs/probe-chat)",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -32,7 +32,7 @@
32
32
  "@ai-sdk/anthropic": "^1.2.0",
33
33
  "@ai-sdk/google": "^1.2.1",
34
34
  "@ai-sdk/openai": "^1.3.0",
35
- "@buger/probe": "^0.6.0-rc56",
35
+ "@buger/probe": "^0.6.0-rc58",
36
36
  "@opentelemetry/api": "^1.9.0",
37
37
  "@opentelemetry/exporter-trace-otlp-http": "^0.203.0",
38
38
  "@opentelemetry/resources": "^2.0.1",
package/webServer.js CHANGED
@@ -5,7 +5,7 @@ import { readFileSync, existsSync } from 'fs';
5
5
  import { resolve, dirname, join } from 'path';
6
6
  import { fileURLToPath } from 'url';
7
7
  import { randomUUID } from 'crypto';
8
- import { ProbeChat } from './probeChat.js';
8
+ import { ChatSessionManager } from './ChatSessionManager.js';
9
9
  import { TokenUsageDisplay } from './tokenUsageDisplay.js';
10
10
  import { authMiddleware, withAuth } from './auth.js';
11
11
  import {
@@ -32,7 +32,7 @@ let globalStorage = null;
32
32
  const chatSessions = new Map();
33
33
 
34
34
  /**
35
- * Retrieve or create a ProbeChat instance keyed by sessionId.
35
+ * Retrieve or create a ChatSessionManager instance keyed by sessionId.
36
36
  */
37
37
  function getOrCreateChat(sessionId, apiCredentials = null) {
38
38
  if (!sessionId) {
@@ -54,24 +54,19 @@ function getOrCreateChat(sessionId, apiCredentials = null) {
54
54
  }
55
55
 
56
56
  // Create options object with sessionId and API credentials if provided
57
- const options = { sessionId };
57
+ const options = {
58
+ sessionId,
59
+ storage: globalStorage,
60
+ debug: process.env.DEBUG_CHAT === '1'
61
+ };
62
+
58
63
  if (apiCredentials) {
59
64
  options.apiProvider = apiCredentials.apiProvider;
60
65
  options.apiKey = apiCredentials.apiKey;
61
66
  options.apiUrl = apiCredentials.apiUrl;
62
67
  }
63
-
64
- // Pass storage instance for persistent storage
65
- if (globalStorage) {
66
- options.storage = globalStorage;
67
- }
68
68
 
69
- const newChat = new ProbeChat(options);
70
-
71
- // Add timestamps for session tracking
72
- const now = Date.now();
73
- newChat.createdAt = now;
74
- newChat.lastActivity = now;
69
+ const newChat = new ChatSessionManager(options);
75
70
 
76
71
  // Store in memory cache
77
72
  chatSessions.set(sessionId, newChat);
@@ -80,8 +75,8 @@ function getOrCreateChat(sessionId, apiCredentials = null) {
80
75
  if (globalStorage) {
81
76
  globalStorage.saveSession({
82
77
  id: sessionId,
83
- createdAt: now,
84
- lastActivity: now,
78
+ createdAt: newChat.createdAt,
79
+ lastActivity: newChat.lastActivity,
85
80
  firstMessagePreview: null, // Will be updated when first message is sent
86
81
  metadata: {
87
82
  apiProvider: apiCredentials?.apiProvider || null
@@ -92,7 +87,7 @@ function getOrCreateChat(sessionId, apiCredentials = null) {
92
87
  }
93
88
 
94
89
  if (process.env.DEBUG_CHAT === '1') {
95
- console.log(`[DEBUG] Created and stored new chat instance for session: ${sessionId}. Total sessions: ${chatSessions.size}`);
90
+ console.log(`[DEBUG] Created and stored new ChatSessionManager instance for session: ${sessionId}. Total sessions: ${chatSessions.size}`);
96
91
  if (apiCredentials && apiCredentials.apiKey) {
97
92
  console.log(`[DEBUG] Chat instance created with client-provided API credentials (provider: ${apiCredentials.apiProvider})`);
98
93
  }
@@ -107,7 +102,7 @@ function getOrCreateChat(sessionId, apiCredentials = null) {
107
102
  * @param {Object} options - Additional options
108
103
  * @param {boolean} options.allowEdit - Whether to allow editing files via the implement tool
109
104
  */
110
- export function startWebServer(version, hasApiKeys = true, options = {}) {
105
+ export async function startWebServer(version, hasApiKeys = true, options = {}) {
111
106
  const allowEdit = options?.allowEdit || false;
112
107
 
113
108
  if (allowEdit) {
@@ -130,16 +125,14 @@ export function startWebServer(version, hasApiKeys = true, options = {}) {
130
125
  verbose: process.env.DEBUG_CHAT === '1'
131
126
  });
132
127
 
133
- // Initialize storage asynchronously
134
- (async () => {
135
- try {
136
- await globalStorage.initialize();
137
- const stats = await globalStorage.getStats();
138
- console.log(`Chat history storage: ${stats.storage_type} (${stats.session_count} sessions, ${stats.visible_message_count} messages)`);
139
- } catch (error) {
140
- console.warn('Failed to initialize chat history storage:', error.message);
141
- }
142
- })();
128
+ // Initialize storage synchronously before server starts
129
+ try {
130
+ await globalStorage.initialize();
131
+ const stats = await globalStorage.getStats();
132
+ console.log(`Chat history storage: ${stats.storage_type} (${stats.session_count} sessions, ${stats.visible_message_count} messages)`);
133
+ } catch (error) {
134
+ console.warn('Failed to initialize chat history storage:', error.message);
135
+ }
143
136
 
144
137
  // Map to store SSE clients by session ID
145
138
  const sseClients = new Map();
@@ -763,9 +756,9 @@ export function startWebServer(version, hasApiKeys = true, options = {}) {
763
756
  // The loop is inside chatInstance.chat now.
764
757
  // We expect the *final* result string back.
765
758
  try {
766
- // Pass API credentials to the chat method if provided
767
- const apiCredentials = apiKey ? { apiProvider, apiKey, apiUrl } : null;
768
- const result = await chatInstance.chat(message, chatSessionId, apiCredentials, images); // Pass session ID, API credentials, and images
759
+ // ChatSessionManager handles session ID and API credentials internally
760
+ // Only pass the message and images
761
+ const result = await chatInstance.chat(message, images);
769
762
 
770
763
  // Check if cancelled *during* the chat call (ProbeChat throws error)
771
764
  // Error handled in catch block