@probelabs/probe-chat 0.6.0-rc191 → 0.6.0-rc198

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.
@@ -63,15 +63,18 @@ export class ChatSessionManager {
63
63
  }
64
64
 
65
65
  /**
66
- * Initialize the ChatSessionManager by loading history
66
+ * Initialize the ChatSessionManager by initializing ProbeAgent and loading history
67
67
  * This must be called before using chat()
68
68
  */
69
69
  async initialize() {
70
70
  if (this._ready) return; // Already initialized
71
-
71
+
72
+ // Initialize ProbeAgent (handles API key detection and CLI fallback)
73
+ await this.agent.initialize();
74
+
72
75
  await this.loadHistory();
73
76
  this._ready = true;
74
-
77
+
75
78
  if (this.debug) {
76
79
  console.log(`[ChatSessionManager] Initialized session ${this.sessionId} with ${this.agent.history.length} messages from storage`);
77
80
  }
package/index.js CHANGED
@@ -346,12 +346,6 @@ export function main() {
346
346
  process.env.PORT = options.port;
347
347
  }
348
348
 
349
- // Check for API keys
350
- const anthropicApiKey = process.env.ANTHROPIC_API_KEY;
351
- const openaiApiKey = process.env.OPENAI_API_KEY;
352
- const googleApiKey = process.env.GOOGLE_API_KEY;
353
- const hasApiKeys = !!(anthropicApiKey || openaiApiKey || googleApiKey);
354
-
355
349
  // --- Bash Configuration Processing ---
356
350
  let bashConfig = null;
357
351
  if (options.enableBash) {
@@ -406,16 +400,13 @@ export function main() {
406
400
 
407
401
  // --- Web Mode (check before non-interactive to override) ---
408
402
  if (options.web) {
409
- if (!hasApiKeys) {
410
- // Use logWarn for web mode warning
411
- logWarn(chalk.yellow('Warning: No API key provided. The web interface will show instructions on how to set up API keys.'));
412
- }
413
- // Import and start web server
403
+ // Note: API key / CLI availability is checked lazily in ProbeAgent.initialize()
404
+ // when the first chat message is sent. This allows fallback to Claude Code or Codex CLI.
414
405
  import('./webServer.js')
415
406
  .then(async module => {
416
407
  const { startWebServer } = module;
417
408
  logInfo(`Starting web server on port ${process.env.PORT || 8080}...`);
418
- await startWebServer(version, hasApiKeys, { allowEdit: options.allowEdit });
409
+ await startWebServer(version, { allowEdit: options.allowEdit });
419
410
  })
420
411
  .catch(error => {
421
412
  logError(chalk.red(`Error starting web server: ${error.message}`));
@@ -427,11 +418,6 @@ export function main() {
427
418
 
428
419
  // --- Non-Interactive Mode ---
429
420
  if (isNonInteractive) {
430
- if (!hasApiKeys) {
431
- logError(chalk.red('No API key provided. Please set ANTHROPIC_API_KEY, OPENAI_API_KEY, or GOOGLE_API_KEY environment variable.'));
432
- process.exit(1);
433
- }
434
-
435
421
  let chat;
436
422
  try {
437
423
  // Pass session ID if provided, ProbeChat generates one otherwise
@@ -445,8 +431,6 @@ export function main() {
445
431
  bashConfig: bashConfig,
446
432
  completionPrompt: completionPrompt
447
433
  });
448
- // Model/Provider info is logged via logInfo above if debug enabled
449
- logInfo(chalk.blue(`Using Session ID: ${chat.getSessionId()}`)); // Log the actual session ID being used
450
434
  } catch (error) {
451
435
  logError(chalk.red(`Initializing chat failed: ${error.message}`));
452
436
  process.exit(1);
@@ -468,6 +452,19 @@ export function main() {
468
452
  // Async function to handle the single chat request
469
453
  const runNonInteractiveChat = async () => {
470
454
  try {
455
+ // Initialize the chat (handles API key validation, claude-code/codex fallback)
456
+ await chat.initialize();
457
+
458
+ // Validate provider after initialization
459
+ const providerInfo = chat.getProviderInfo();
460
+ if (providerInfo.provider === 'unknown' || providerInfo.provider === 'uninitialized') {
461
+ throw new Error('No valid AI provider available. Please set an API key or install claude/codex CLI.');
462
+ }
463
+
464
+ // Log provider info after successful initialization
465
+ logInfo(chalk.green(`Using provider: ${providerInfo.provider} with model: ${providerInfo.model}`));
466
+ logInfo(chalk.blue(`Using Session ID: ${chat.getSessionId()}`));
467
+
471
468
  // Get message from command line argument or stdin
472
469
  let message = options.message;
473
470
 
@@ -533,16 +530,6 @@ export function main() {
533
530
  // --- Interactive CLI Mode ---
534
531
  // (This block only runs if not non-interactive and not web mode)
535
532
 
536
- if (!hasApiKeys) {
537
- // Use logError and standard console.log for setup instructions
538
- logError(chalk.red('No API key provided. Please set ANTHROPIC_API_KEY, OPENAI_API_KEY, or GOOGLE_API_KEY environment variable.'));
539
- console.log(chalk.cyan('You can find these instructions in the .env.example file:'));
540
- console.log(chalk.cyan('1. Create a .env file by copying .env.example'));
541
- console.log(chalk.cyan('2. Add your API key to the .env file'));
542
- console.log(chalk.cyan('3. Restart the application'));
543
- process.exit(1);
544
- }
545
-
546
533
  // Initialize ProbeChat for CLI mode
547
534
  let chat;
548
535
  try {
@@ -557,26 +544,44 @@ export function main() {
557
544
  bashConfig: bashConfig,
558
545
  completionPrompt: completionPrompt
559
546
  });
560
-
561
- // Log model/provider info using logInfo
562
- if (chat.apiType === 'anthropic') {
563
- logInfo(chalk.green(`Using Anthropic API with model: ${chat.model}`));
564
- } else if (chat.apiType === 'openai') {
565
- logInfo(chalk.green(`Using OpenAI API with model: ${chat.model}`));
566
- } else if (chat.apiType === 'google') {
567
- logInfo(chalk.green(`Using Google API with model: ${chat.model}`));
568
- }
569
-
570
- logInfo(chalk.blue(`Session ID: ${chat.getSessionId()}`));
571
- logInfo(chalk.cyan('Type "exit" or "quit" to end the chat'));
572
- logInfo(chalk.cyan('Type "usage" to see token usage statistics'));
573
- logInfo(chalk.cyan('Type "clear" to clear the chat history'));
574
- logInfo(chalk.cyan('-------------------------------------------'));
575
547
  } catch (error) {
576
- logError(chalk.red(`Error initializing chat: ${error.message}`));
548
+ logError(chalk.red(`Error creating chat instance: ${error.message}`));
577
549
  process.exit(1);
578
550
  }
579
551
 
552
+ // Async initialization wrapper for interactive mode
553
+ async function initializeAndStartChat() {
554
+ try {
555
+ // Initialize the chat (handles API key validation, claude-code/codex fallback)
556
+ await chat.initialize();
557
+
558
+ // Validate provider after initialization
559
+ const providerInfo = chat.getProviderInfo();
560
+ if (providerInfo.provider === 'unknown' || providerInfo.provider === 'uninitialized') {
561
+ throw new Error('No valid AI provider available. Please set an API key or install claude/codex CLI.');
562
+ }
563
+
564
+ // Log provider info after successful initialization
565
+ if (providerInfo.isCliProvider) {
566
+ logInfo(chalk.green(`Using ${providerInfo.provider} CLI`));
567
+ } else {
568
+ logInfo(chalk.green(`Using ${providerInfo.provider} API with model: ${providerInfo.model}`));
569
+ }
570
+
571
+ logInfo(chalk.blue(`Session ID: ${chat.getSessionId()}`));
572
+ logInfo(chalk.cyan('Type "exit" or "quit" to end the chat'));
573
+ logInfo(chalk.cyan('Type "usage" to see token usage statistics'));
574
+ logInfo(chalk.cyan('Type "clear" to clear the chat history'));
575
+ logInfo(chalk.cyan('-------------------------------------------'));
576
+
577
+ // Start the interactive chat loop
578
+ await runInteractiveChat();
579
+ } catch (error) {
580
+ logError(chalk.red(`Error initializing chat: ${error.message}`));
581
+ process.exit(1);
582
+ }
583
+ }
584
+
580
585
  // Format AI response for interactive mode
581
586
  function formatResponseInteractive(response) {
582
587
  // Check if response is a structured object with response and tokenUsage properties
@@ -598,7 +603,7 @@ export function main() {
598
603
  }
599
604
 
600
605
  // Main interactive chat loop
601
- async function startChat() {
606
+ async function runInteractiveChat() {
602
607
  while (true) {
603
608
  const { message } = await inquirer.prompt([
604
609
  {
@@ -613,7 +618,7 @@ export function main() {
613
618
  logInfo(chalk.yellow('Goodbye!'));
614
619
  break;
615
620
  } else if (message.toLowerCase() === 'usage') {
616
- const usage = chat.getTokenUsage();
621
+ const usage = chat.getUsageSummary();
617
622
  const display = new TokenUsageDisplay();
618
623
  const formatted = display.format(usage);
619
624
 
@@ -630,9 +635,9 @@ export function main() {
630
635
  process.stdout.write('\x1B]0;Context: ' + formatted.contextWindow + '\x07');
631
636
  continue;
632
637
  } else if (message.toLowerCase() === 'clear') {
633
- const newSessionId = chat.clearHistory();
638
+ chat.clearHistory();
634
639
  logInfo(chalk.yellow('Chat history cleared'));
635
- logInfo(chalk.blue(`New session ID: ${newSessionId}`));
640
+ logInfo(chalk.blue(`New session ID: ${chat.getSessionId()}`));
636
641
  continue;
637
642
  }
638
643
 
@@ -656,7 +661,7 @@ export function main() {
656
661
  }
657
662
  }
658
663
 
659
- startChat().catch((error) => {
664
+ initializeAndStartChat().catch((error) => {
660
665
  logError(chalk.red(`Fatal error in interactive chat: ${error.message}`));
661
666
  process.exit(1);
662
667
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@probelabs/probe-chat",
3
- "version": "0.6.0-rc191",
3
+ "version": "0.6.0-rc198",
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,13 +32,13 @@
32
32
  "@ai-sdk/anthropic": "^2.0.8",
33
33
  "@ai-sdk/google": "^2.0.14",
34
34
  "@ai-sdk/openai": "^2.0.10",
35
- "@probelabs/probe": "latest",
35
+ "@modelcontextprotocol/sdk": "^1.0.0",
36
36
  "@opentelemetry/api": "^1.9.0",
37
37
  "@opentelemetry/exporter-trace-otlp-http": "^0.203.0",
38
38
  "@opentelemetry/resources": "^2.0.1",
39
39
  "@opentelemetry/sdk-node": "^0.203.0",
40
40
  "@opentelemetry/semantic-conventions": "^1.36.0",
41
- "@modelcontextprotocol/sdk": "^1.0.0",
41
+ "@probelabs/probe": "latest",
42
42
  "ai": "^5.0.0",
43
43
  "chalk": "^5.3.0",
44
44
  "commander": "^11.1.0",
package/probeChat.js CHANGED
@@ -266,6 +266,9 @@ export class ProbeChat {
266
266
  constructor(options = {}) {
267
267
  this.isNonInteractive = options.isNonInteractive || process.env.PROBE_NON_INTERACTIVE === '1';
268
268
  this.debug = options.debug || process.env.DEBUG_CHAT === '1';
269
+ this._initialized = false;
270
+ this._initializing = null; // Promise to prevent concurrent initialization
271
+ this._initError = null; // Store initialization error to prevent infinite retries
269
272
 
270
273
  // Initialize ProbeAgent with MCP support
271
274
  const agentOptions = {
@@ -283,11 +286,81 @@ export class ProbeChat {
283
286
 
284
287
  if (this.debug) {
285
288
  console.log(`[DEBUG] ProbeChat initialized with MCP ${agentOptions.enableMcp ? 'enabled' : 'disabled'}`);
289
+ }
290
+ }
291
+
292
+ /**
293
+ * Initialize the chat agent asynchronously.
294
+ *
295
+ * This method is safe to call multiple times - subsequent calls will return
296
+ * immediately if already initialized, or wait for the ongoing initialization.
297
+ *
298
+ * If initialization fails, the error is stored and rethrown on subsequent calls
299
+ * to prevent infinite retry loops.
300
+ *
301
+ * This initializes the ProbeAgent which handles:
302
+ * - API key validation
303
+ * - Auto-detection of claude-code or codex CLI as fallback
304
+ * - MCP initialization
305
+ * - History loading from storage
306
+ *
307
+ * @returns {Promise<void>}
308
+ * @throws {Error} If no valid AI provider is available
309
+ */
310
+ async initialize() {
311
+ // If previous initialization failed, rethrow the stored error
312
+ if (this._initError) {
313
+ throw this._initError;
314
+ }
315
+
316
+ // Return existing initialization promise if in progress (prevents concurrent init)
317
+ if (this._initializing) {
318
+ return this._initializing;
319
+ }
286
320
 
287
- // Log available tools after a short delay to allow MCP initialization
288
- setTimeout(() => {
289
- this.logAvailableTools();
290
- }, 100);
321
+ // Already initialized successfully
322
+ if (this._initialized) {
323
+ return;
324
+ }
325
+
326
+ this._initializing = (async () => {
327
+ try {
328
+ await this.agent.initialize();
329
+
330
+ // Mark as initialized immediately after agent init succeeds
331
+ this._initialized = true;
332
+
333
+ // Debug logging wrapped in try-catch to not affect initialization state
334
+ if (this.debug) {
335
+ try {
336
+ const provider = this.agent.clientApiProvider || this.agent.apiType || 'unknown';
337
+ console.log(`[DEBUG] ProbeChat agent initialized with provider: ${provider}`);
338
+ this.logAvailableTools();
339
+ } catch (logError) {
340
+ console.error('[DEBUG] Logging failed:', logError.message);
341
+ }
342
+ }
343
+ } catch (error) {
344
+ // Store error to prevent infinite retry loops
345
+ this._initError = error;
346
+ throw error;
347
+ } finally {
348
+ // Always clear the initializing promise
349
+ this._initializing = null;
350
+ }
351
+ })();
352
+
353
+ return this._initializing;
354
+ }
355
+
356
+ /**
357
+ * Ensure the agent is initialized before use.
358
+ * Called automatically by chat() - you don't need to call this directly.
359
+ * @private
360
+ */
361
+ async _ensureInitialized() {
362
+ if (!this._initialized) {
363
+ await this.initialize();
291
364
  }
292
365
  }
293
366
 
@@ -333,18 +406,27 @@ export class ProbeChat {
333
406
  }
334
407
 
335
408
  /**
336
- * Answer a question using the agentic flow with optional image support
409
+ * Send a chat message and get AI response.
410
+ *
411
+ * This method automatically initializes the agent on first call if not already
412
+ * initialized. For explicit control over initialization (e.g., to handle errors
413
+ * separately), call initialize() first.
414
+ *
337
415
  * @param {string} message - The user's question
338
416
  * @param {Object} [options] - Optional configuration
339
417
  * @param {string} [options.schema] - JSON schema for structured output
340
418
  * @param {Array} [options.images] - Array of image data (base64 strings or URLs)
341
- * @returns {Promise<string>} - The final answer
419
+ * @returns {Promise<Object>} - The AI response with token usage
420
+ * @throws {Error} If message is empty or if no valid AI provider is available
342
421
  */
343
422
  async chat(message, options = {}) {
344
423
  if (!message || typeof message !== 'string' || message.trim().length === 0) {
345
424
  throw new Error('Message is required and must be a non-empty string');
346
425
  }
347
426
 
427
+ // Ensure the agent is initialized (handles API key validation, claude-code/codex fallback)
428
+ await this._ensureInitialized();
429
+
348
430
  // Extract images from the message text if not provided in options
349
431
  let images = options.images || [];
350
432
  let cleanedMessage = message;
@@ -379,6 +461,26 @@ export class ProbeChat {
379
461
  return this.agent.sessionId;
380
462
  }
381
463
 
464
+ /**
465
+ * Get provider information
466
+ * @returns {Object} - Provider info with type and model
467
+ */
468
+ getProviderInfo() {
469
+ return {
470
+ provider: this.agent.clientApiProvider || this.agent.apiType || 'unknown',
471
+ model: this.agent.model || 'unknown',
472
+ isCliProvider: ['claude-code', 'codex'].includes(this.agent.clientApiProvider || this.agent.apiType)
473
+ };
474
+ }
475
+
476
+ /**
477
+ * Check if the agent is initialized
478
+ * @returns {boolean}
479
+ */
480
+ isInitialized() {
481
+ return this._initialized;
482
+ }
483
+
382
484
  /**
383
485
  * Get usage summary for the current session
384
486
  */
@@ -387,11 +489,13 @@ export class ProbeChat {
387
489
  }
388
490
 
389
491
  /**
390
- * Clear conversation history
492
+ * Clear conversation history and return the new session ID
493
+ * @returns {string} - The new session ID
391
494
  */
392
495
  clearHistory() {
393
496
  this.agent.clearHistory();
394
497
  this.tokenUsage.clear();
498
+ return this.agent.sessionId;
395
499
  }
396
500
 
397
501
  /**
@@ -0,0 +1,86 @@
1
+ import { describe, it, beforeEach, afterEach, mock } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { ChatSessionManager } from '../../ChatSessionManager.js';
4
+
5
+ // Store original environment
6
+ let originalEnv;
7
+
8
+ describe('ChatSessionManager Tests', () => {
9
+ beforeEach(() => {
10
+ // Store original environment
11
+ originalEnv = { ...process.env };
12
+
13
+ // Set test mode
14
+ process.env.NODE_ENV = 'test';
15
+ process.env.USE_MOCK_AI = 'true';
16
+ });
17
+
18
+ afterEach(() => {
19
+ // Restore original environment
20
+ Object.keys(process.env).forEach(key => {
21
+ if (!(key in originalEnv)) {
22
+ delete process.env[key];
23
+ }
24
+ });
25
+ Object.assign(process.env, originalEnv);
26
+ });
27
+
28
+ describe('initialize()', () => {
29
+ it('should call agent.initialize() when initializing', async () => {
30
+ // Create a ChatSessionManager
31
+ const session = new ChatSessionManager({
32
+ sessionId: 'test-session-123',
33
+ debug: false
34
+ });
35
+
36
+ // Track if agent.initialize was called
37
+ let agentInitializeCalled = false;
38
+ const originalInitialize = session.agent.initialize.bind(session.agent);
39
+ session.agent.initialize = async function() {
40
+ agentInitializeCalled = true;
41
+ return originalInitialize();
42
+ };
43
+
44
+ // Initialize the session
45
+ await session.initialize();
46
+
47
+ // Verify agent.initialize was called
48
+ assert.strictEqual(agentInitializeCalled, true, 'agent.initialize() should be called during ChatSessionManager.initialize()');
49
+ });
50
+
51
+ it('should only initialize once even if called multiple times', async () => {
52
+ const session = new ChatSessionManager({
53
+ sessionId: 'test-session-456',
54
+ debug: false
55
+ });
56
+
57
+ let initializeCallCount = 0;
58
+ const originalInitialize = session.agent.initialize.bind(session.agent);
59
+ session.agent.initialize = async function() {
60
+ initializeCallCount++;
61
+ return originalInitialize();
62
+ };
63
+
64
+ // Call initialize multiple times
65
+ await session.initialize();
66
+ await session.initialize();
67
+ await session.initialize();
68
+
69
+ // Should only be called once due to _ready flag
70
+ assert.strictEqual(initializeCallCount, 1, 'agent.initialize() should only be called once');
71
+ });
72
+
73
+ it('should set _ready flag after successful initialization', async () => {
74
+ const session = new ChatSessionManager({
75
+ sessionId: 'test-session-789',
76
+ debug: false
77
+ });
78
+
79
+ assert.strictEqual(session._ready, false, '_ready should be false before initialize');
80
+
81
+ await session.initialize();
82
+
83
+ assert.strictEqual(session._ready, true, '_ready should be true after initialize');
84
+ });
85
+ });
86
+ });
package/webServer.js CHANGED
@@ -98,13 +98,12 @@ function getOrCreateChat(sessionId, apiCredentials = null) {
98
98
  /**
99
99
  * Start the web server
100
100
  * @param {string} version - The version of the application
101
- * @param {boolean} hasApiKeys - Whether any API keys are configured
102
101
  * @param {Object} options - Additional options
103
102
  * @param {boolean} options.allowEdit - Whether to allow editing files via the implement tool
104
103
  */
105
- export async function startWebServer(version, hasApiKeys = true, options = {}) {
104
+ export async function startWebServer(version, options = {}) {
106
105
  const allowEdit = options?.allowEdit || false;
107
-
106
+
108
107
  if (allowEdit) {
109
108
  console.log('Edit mode enabled: implement tool is available');
110
109
  }
@@ -120,11 +119,11 @@ export async function startWebServer(version, hasApiKeys = true, options = {}) {
120
119
  }
121
120
 
122
121
  // Initialize persistent storage for web mode
123
- globalStorage = new JsonChatStorage({
124
- webMode: true,
125
- verbose: process.env.DEBUG_CHAT === '1'
122
+ globalStorage = new JsonChatStorage({
123
+ webMode: true,
124
+ verbose: process.env.DEBUG_CHAT === '1'
126
125
  });
127
-
126
+
128
127
  // Initialize storage synchronously before server starts
129
128
  try {
130
129
  await globalStorage.initialize();
@@ -144,13 +143,9 @@ export async function startWebServer(version, hasApiKeys = true, options = {}) {
144
143
  ? process.env.ALLOWED_FOLDERS.split(',').map(folder => folder.trim()).filter(Boolean)
145
144
  : [];
146
145
 
147
-
148
- let noApiKeysMode = !hasApiKeys;
149
- if (noApiKeysMode) {
150
- console.log('Running in No API Keys mode - will show setup instructions to users');
151
- } else {
152
- console.log('API keys detected. Chat functionality enabled.');
153
- }
146
+ // Note: API key / CLI availability is checked lazily in ProbeAgent.initialize()
147
+ // when the first chat message is sent. This allows fallback to Claude Code or Codex CLI.
148
+ console.log('Chat functionality enabled (API availability checked on first message)');
154
149
 
155
150
 
156
151
  // Define the tools available for direct API calls (bypassing LLM loop)
@@ -419,7 +414,7 @@ export async function startWebServer(version, hasApiKeys = true, options = {}) {
419
414
  // UI Routes
420
415
  'GET /': (req, res) => {
421
416
  const htmlPath = join(__dirname, 'index.html');
422
- serveHtml(res, htmlPath, { 'data-no-api-keys': noApiKeysMode ? 'true' : 'false' });
417
+ serveHtml(res, htmlPath, { 'data-no-api-keys': 'false' });
423
418
  },
424
419
 
425
420
  // Chat session route - serves HTML with injected session ID
@@ -428,15 +423,15 @@ export async function startWebServer(version, hasApiKeys = true, options = {}) {
428
423
  if (!sessionId) {
429
424
  return sendError(res, 400, 'Invalid session ID in URL');
430
425
  }
431
-
426
+
432
427
  // Validate that session exists or at least has a valid UUID format
433
428
  if (!isValidUUID(sessionId)) {
434
429
  return sendError(res, 400, 'Invalid session ID format');
435
430
  }
436
431
 
437
432
  const htmlPath = join(__dirname, 'index.html');
438
- serveHtml(res, htmlPath, {
439
- 'data-no-api-keys': noApiKeysMode ? 'true' : 'false',
433
+ serveHtml(res, htmlPath, {
434
+ 'data-no-api-keys': 'false',
440
435
  'data-session-id': sessionId
441
436
  });
442
437
  },
@@ -451,7 +446,7 @@ export async function startWebServer(version, hasApiKeys = true, options = {}) {
451
446
  sendJson(res, 200, {
452
447
  folders: folders,
453
448
  currentDir: currentDir,
454
- noApiKeysMode: noApiKeysMode
449
+ noApiKeysMode: false
455
450
  });
456
451
  },
457
452
 
@@ -700,12 +695,6 @@ export async function startWebServer(version, hasApiKeys = true, options = {}) {
700
695
  // Update last activity timestamp
701
696
  chatInstance.lastActivity = Date.now();
702
697
 
703
- // Check if API keys are needed but missing
704
- if (chatInstance.noApiKeysMode) {
705
- console.warn(`[WARN] Chat request for session ${chatSessionId} cannot proceed: No API keys configured.`);
706
- return sendError(res, 503, 'Chat service unavailable: API key not configured on server.');
707
- }
708
-
709
698
  // Register this request as active for cancellation
710
699
  registerRequest(chatSessionId, { abort: () => chatInstance.abort() });
711
700
  if (DEBUG) console.log(`[DEBUG] Registered cancellable request for session: ${chatSessionId}`);
@@ -918,9 +907,6 @@ export async function startWebServer(version, hasApiKeys = true, options = {}) {
918
907
  console.log(`Probe Web Interface v${version}`);
919
908
  console.log(`Server running on http://localhost:${PORT}`);
920
909
  console.log(`Environment: ${process.env.NODE_ENV || 'development'}`);
921
- if (noApiKeysMode) {
922
- console.log('*** Running in NO API KEYS mode. Chat functionality disabled. ***');
923
- }
924
910
  });
925
911
  }
926
912