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

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 (3) hide show
  1. package/index.js +52 -38
  2. package/package.json +3 -3
  3. package/probeChat.js +111 -7
package/index.js CHANGED
@@ -427,11 +427,6 @@ export function main() {
427
427
 
428
428
  // --- Non-Interactive Mode ---
429
429
  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
430
  let chat;
436
431
  try {
437
432
  // Pass session ID if provided, ProbeChat generates one otherwise
@@ -445,8 +440,6 @@ export function main() {
445
440
  bashConfig: bashConfig,
446
441
  completionPrompt: completionPrompt
447
442
  });
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
443
  } catch (error) {
451
444
  logError(chalk.red(`Initializing chat failed: ${error.message}`));
452
445
  process.exit(1);
@@ -468,6 +461,19 @@ export function main() {
468
461
  // Async function to handle the single chat request
469
462
  const runNonInteractiveChat = async () => {
470
463
  try {
464
+ // Initialize the chat (handles API key validation, claude-code/codex fallback)
465
+ await chat.initialize();
466
+
467
+ // Validate provider after initialization
468
+ const providerInfo = chat.getProviderInfo();
469
+ if (providerInfo.provider === 'unknown' || providerInfo.provider === 'uninitialized') {
470
+ throw new Error('No valid AI provider available. Please set an API key or install claude/codex CLI.');
471
+ }
472
+
473
+ // Log provider info after successful initialization
474
+ logInfo(chalk.green(`Using provider: ${providerInfo.provider} with model: ${providerInfo.model}`));
475
+ logInfo(chalk.blue(`Using Session ID: ${chat.getSessionId()}`));
476
+
471
477
  // Get message from command line argument or stdin
472
478
  let message = options.message;
473
479
 
@@ -533,16 +539,6 @@ export function main() {
533
539
  // --- Interactive CLI Mode ---
534
540
  // (This block only runs if not non-interactive and not web mode)
535
541
 
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
542
  // Initialize ProbeChat for CLI mode
547
543
  let chat;
548
544
  try {
@@ -557,26 +553,44 @@ export function main() {
557
553
  bashConfig: bashConfig,
558
554
  completionPrompt: completionPrompt
559
555
  });
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
556
  } catch (error) {
576
- logError(chalk.red(`Error initializing chat: ${error.message}`));
557
+ logError(chalk.red(`Error creating chat instance: ${error.message}`));
577
558
  process.exit(1);
578
559
  }
579
560
 
561
+ // Async initialization wrapper for interactive mode
562
+ async function initializeAndStartChat() {
563
+ try {
564
+ // Initialize the chat (handles API key validation, claude-code/codex fallback)
565
+ await chat.initialize();
566
+
567
+ // Validate provider after initialization
568
+ const providerInfo = chat.getProviderInfo();
569
+ if (providerInfo.provider === 'unknown' || providerInfo.provider === 'uninitialized') {
570
+ throw new Error('No valid AI provider available. Please set an API key or install claude/codex CLI.');
571
+ }
572
+
573
+ // Log provider info after successful initialization
574
+ if (providerInfo.isCliProvider) {
575
+ logInfo(chalk.green(`Using ${providerInfo.provider} CLI`));
576
+ } else {
577
+ logInfo(chalk.green(`Using ${providerInfo.provider} API with model: ${providerInfo.model}`));
578
+ }
579
+
580
+ logInfo(chalk.blue(`Session ID: ${chat.getSessionId()}`));
581
+ logInfo(chalk.cyan('Type "exit" or "quit" to end the chat'));
582
+ logInfo(chalk.cyan('Type "usage" to see token usage statistics'));
583
+ logInfo(chalk.cyan('Type "clear" to clear the chat history'));
584
+ logInfo(chalk.cyan('-------------------------------------------'));
585
+
586
+ // Start the interactive chat loop
587
+ await runInteractiveChat();
588
+ } catch (error) {
589
+ logError(chalk.red(`Error initializing chat: ${error.message}`));
590
+ process.exit(1);
591
+ }
592
+ }
593
+
580
594
  // Format AI response for interactive mode
581
595
  function formatResponseInteractive(response) {
582
596
  // Check if response is a structured object with response and tokenUsage properties
@@ -598,7 +612,7 @@ export function main() {
598
612
  }
599
613
 
600
614
  // Main interactive chat loop
601
- async function startChat() {
615
+ async function runInteractiveChat() {
602
616
  while (true) {
603
617
  const { message } = await inquirer.prompt([
604
618
  {
@@ -613,7 +627,7 @@ export function main() {
613
627
  logInfo(chalk.yellow('Goodbye!'));
614
628
  break;
615
629
  } else if (message.toLowerCase() === 'usage') {
616
- const usage = chat.getTokenUsage();
630
+ const usage = chat.getUsageSummary();
617
631
  const display = new TokenUsageDisplay();
618
632
  const formatted = display.format(usage);
619
633
 
@@ -630,9 +644,9 @@ export function main() {
630
644
  process.stdout.write('\x1B]0;Context: ' + formatted.contextWindow + '\x07');
631
645
  continue;
632
646
  } else if (message.toLowerCase() === 'clear') {
633
- const newSessionId = chat.clearHistory();
647
+ chat.clearHistory();
634
648
  logInfo(chalk.yellow('Chat history cleared'));
635
- logInfo(chalk.blue(`New session ID: ${newSessionId}`));
649
+ logInfo(chalk.blue(`New session ID: ${chat.getSessionId()}`));
636
650
  continue;
637
651
  }
638
652
 
@@ -656,7 +670,7 @@ export function main() {
656
670
  }
657
671
  }
658
672
 
659
- startChat().catch((error) => {
673
+ initializeAndStartChat().catch((error) => {
660
674
  logError(chalk.red(`Fatal error in interactive chat: ${error.message}`));
661
675
  process.exit(1);
662
676
  });
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-rc197",
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
  /**