@probelabs/probe 0.6.0-rc166 → 0.6.0-rc167

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/README.md CHANGED
@@ -352,6 +352,45 @@ const agent = new ProbeAgent({
352
352
 
353
353
  **Note:** MCP tools are automatically initialized when needed (lazy initialization), so you don't need to call `agent.initialize()` when using the SDK.
354
354
 
355
+ ## Claude Code Integration
356
+
357
+ ProbeAgent now supports Claude Code's `claude` command for zero-configuration usage in Claude Code environments. See the [Claude Code Integration Guide](./docs/CLAUDE_CODE_INTEGRATION.md) for full details.
358
+
359
+ ### Quick Start
360
+
361
+ ```javascript
362
+ import { ProbeAgent } from '@probelabs/probe';
363
+
364
+ // Works automatically if claude command is installed!
365
+ const agent = new ProbeAgent({
366
+ allowedFolders: ['/path/to/your/code']
367
+ });
368
+
369
+ await agent.initialize();
370
+ const response = await agent.answer('Explain how this codebase works');
371
+ ```
372
+
373
+ ### Auto-Fallback
374
+
375
+ ProbeAgent automatically detects and uses Claude Code when:
376
+ - No API keys are configured (no ANTHROPIC_API_KEY, OPENAI_API_KEY, etc.)
377
+ - The `claude` command is available on your system
378
+
379
+ Priority order:
380
+ 1. Explicit `provider: 'claude-code'`
381
+ 2. API keys (Anthropic, OpenAI, Google, AWS)
382
+ 3. Claude command (auto-detected)
383
+
384
+ ### Features
385
+
386
+ - **Zero Configuration**: No API keys needed in Claude Code environments
387
+ - **Black-box Operation**: Claude Code handles its own agentic loop
388
+ - **Tool Event Extraction**: Visibility into internal tool usage
389
+ - **Built-in MCP Server**: Provides Probe's semantic search tools
390
+ - **Auto-fallback**: Seamlessly switches based on environment
391
+
392
+ For complete documentation, examples, and troubleshooting, see [docs/CLAUDE_CODE_INTEGRATION.md](./docs/CLAUDE_CODE_INTEGRATION.md).
393
+
355
394
  ## API Reference
356
395
 
357
396
  ### Search
@@ -221,6 +221,9 @@ export class ProbeAgent {
221
221
  this.fallbackConfig = options.fallback || null;
222
222
  this.fallbackManager = null; // Will be initialized in initializeModel
223
223
 
224
+ // Engine support - minimal interface for multi-engine compatibility
225
+ this.engine = null; // Will be set in initializeModel or getEngine
226
+
224
227
  // Initialize the AI model
225
228
  this.initializeModel();
226
229
 
@@ -301,6 +304,31 @@ export class ProbeAgent {
301
304
  * This method initializes MCP and merges MCP tools into the tool list, and loads history from storage
302
305
  */
303
306
  async initialize() {
307
+ // Check if we need to auto-detect claude-code provider
308
+ // This happens when no API keys are set and no provider is specified
309
+ if (!this.provider && !this.clientApiProvider && this.apiType !== 'claude-code') {
310
+ // Check if initializeModel marked as uninitialized (no API keys)
311
+ if (this.apiType === 'uninitialized') {
312
+ const claudeAvailable = await this.isClaudeCommandAvailable();
313
+ if (claudeAvailable) {
314
+ if (this.debug) {
315
+ console.log('[DEBUG] No API keys found, but claude command detected');
316
+ console.log('[DEBUG] Auto-switching to claude-code provider');
317
+ }
318
+ // Set provider to claude-code
319
+ this.clientApiProvider = 'claude-code';
320
+ this.provider = null;
321
+ this.model = this.clientApiModel || 'claude-3-5-sonnet-20241022';
322
+ this.apiType = 'claude-code';
323
+ } else {
324
+ // Neither API keys nor claude command available
325
+ throw new Error('No API key provided and claude command not found. Please either:\n' +
326
+ '1. Set an API key: ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY, or AWS credentials\n' +
327
+ '2. Install claude command from https://docs.claude.com/en/docs/claude-code');
328
+ }
329
+ }
330
+ }
331
+
304
332
  // Load history from storage adapter
305
333
  try {
306
334
  const history = await this.storageAdapter.loadHistory(this.sessionId);
@@ -452,6 +480,21 @@ export class ProbeAgent {
452
480
  }
453
481
  }
454
482
 
483
+ /**
484
+ * Check if claude command is available on the system
485
+ * @returns {Promise<boolean>} True if claude command is available
486
+ * @private
487
+ */
488
+ async isClaudeCommandAvailable() {
489
+ try {
490
+ const { execSync } = await import('child_process');
491
+ execSync('claude --version', { stdio: 'ignore' });
492
+ return true;
493
+ } catch (error) {
494
+ return false;
495
+ }
496
+ }
497
+
455
498
  /**
456
499
  * Initialize the AI model based on available API keys and forced provider setting
457
500
  */
@@ -465,6 +508,19 @@ export class ProbeAgent {
465
508
  return;
466
509
  }
467
510
 
511
+ // Skip API key requirement for Claude Code (uses built-in access in Claude Code)
512
+ if (this.clientApiProvider === 'claude-code' || process.env.USE_CLAUDE_CODE === 'true') {
513
+ // Claude Code engine will be initialized lazily in getEngine()
514
+ // Set minimal defaults for compatibility
515
+ this.provider = null;
516
+ this.model = modelName || 'claude-3-5-sonnet-20241022';
517
+ this.apiType = 'claude-code';
518
+ if (this.debug) {
519
+ console.log('[DEBUG] Claude Code engine selected - will use built-in access if available');
520
+ }
521
+ return;
522
+ }
523
+
468
524
  // Get API keys from environment variables
469
525
  // Support both ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN (used by Z.AI)
470
526
  const anthropicApiKey = process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN;
@@ -534,7 +590,12 @@ export class ProbeAgent {
534
590
  this.initializeBedrockModel(awsAccessKeyId, awsSecretAccessKey, awsRegion, awsSessionToken, awsApiKey, awsBedrockBaseUrl, modelName);
535
591
  this.initializeFallbackManager('bedrock', modelName);
536
592
  } else {
537
- throw new Error('No API key provided. Please set ANTHROPIC_API_KEY (or ANTHROPIC_AUTH_TOKEN), OPENAI_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY (or GOOGLE_API_KEY), AWS credentials (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION), or AWS_BEDROCK_API_KEY environment variables.');
593
+ // No API keys found - mark for potential claude-code auto-detection in initialize()
594
+ this.apiType = 'uninitialized';
595
+ if (this.debug) {
596
+ console.log('[DEBUG] No API keys found - will check for claude command in initialize()');
597
+ }
598
+ // Don't throw error yet - will be checked in initialize() method
538
599
  }
539
600
  }
540
601
 
@@ -609,6 +670,60 @@ export class ProbeAgent {
609
670
  * @private
610
671
  */
611
672
  async streamTextWithRetryAndFallback(options) {
673
+ // Check if we should use Claude Code engine
674
+ if (this.clientApiProvider === 'claude-code' || process.env.USE_CLAUDE_CODE === 'true') {
675
+ try {
676
+ const engine = await this.getEngine();
677
+ if (engine && engine.query) {
678
+ // Convert Vercel AI SDK format to engine format
679
+ // Extract the ORIGINAL user message as the main prompt (skip any warning messages)
680
+ // Look for user messages that are NOT the warning message
681
+ const userMessages = options.messages.filter(m =>
682
+ m.role === 'user' &&
683
+ !m.content.includes('WARNING: You have reached the maximum tool iterations limit')
684
+ );
685
+ const lastUserMessage = userMessages[userMessages.length - 1];
686
+ const prompt = lastUserMessage ? lastUserMessage.content : '';
687
+
688
+ // Pass system message and other options
689
+ const engineOptions = {
690
+ maxTokens: options.maxTokens,
691
+ temperature: options.temperature,
692
+ messages: options.messages,
693
+ systemPrompt: options.messages.find(m => m.role === 'system')?.content
694
+ };
695
+
696
+ // Get the engine's query result (async generator)
697
+ const engineStream = engine.query(prompt, engineOptions);
698
+
699
+ // Create a text stream that extracts text from engine messages
700
+ async function* createTextStream() {
701
+ for await (const message of engineStream) {
702
+ if (message.type === 'text' && message.content) {
703
+ yield message.content;
704
+ } else if (typeof message === 'string') {
705
+ // If engine returns plain strings, pass them through
706
+ yield message;
707
+ }
708
+ // Ignore other message types for the text stream
709
+ }
710
+ }
711
+
712
+ // Wrap the engine result to match streamText interface
713
+ return {
714
+ textStream: createTextStream(),
715
+ usage: Promise.resolve({}), // Engine should handle its own usage tracking
716
+ // Add other streamText-compatible properties as needed
717
+ };
718
+ }
719
+ } catch (error) {
720
+ if (this.debug) {
721
+ console.log(`[DEBUG] Failed to use Claude Code engine, falling back to Vercel:`, error.message);
722
+ }
723
+ // Fall through to use Vercel engine as fallback
724
+ }
725
+ }
726
+
612
727
  // Initialize retry manager if not already created
613
728
  if (!this.retryManager) {
614
729
  this.retryManager = new RetryManager({
@@ -752,6 +867,56 @@ export class ProbeAgent {
752
867
  }
753
868
  }
754
869
 
870
+ /**
871
+ * Get or create the AI engine based on configuration
872
+ * @returns {Promise<Object>} Engine interface
873
+ * @private
874
+ */
875
+ async getEngine() {
876
+ // If engine already created, return it
877
+ if (this.engine) {
878
+ return this.engine;
879
+ }
880
+
881
+ // Try Claude Code engine if requested
882
+ if (this.clientApiProvider === 'claude-code' || process.env.USE_CLAUDE_CODE === 'true') {
883
+ try {
884
+ const { createEnhancedClaudeCLIEngine } = await import('./engines/enhanced-claude-code.js');
885
+
886
+ // For Claude Code, use a cleaner system prompt without XML formatting
887
+ // since it has native MCP support for tools
888
+ const systemPrompt = this.customPrompt || this.getClaudeNativeSystemPrompt();
889
+
890
+ this.engine = await createEnhancedClaudeCLIEngine({
891
+ agent: this, // Pass reference to ProbeAgent for tool access
892
+ systemPrompt: systemPrompt,
893
+ customPrompt: this.customPrompt,
894
+ sessionId: this.options?.sessionId,
895
+ debug: this.debug,
896
+ allowedTools: this.allowedTools // Pass tool filtering configuration
897
+ });
898
+ if (this.debug) {
899
+ console.log('[DEBUG] Using Claude Code engine with Probe tools');
900
+ if (this.customPrompt) {
901
+ console.log('[DEBUG] Using custom prompt/persona');
902
+ }
903
+ }
904
+ return this.engine;
905
+ } catch (error) {
906
+ console.warn('[WARNING] Failed to load Claude Code engine:', error.message);
907
+ console.warn('[WARNING] Falling back to Vercel AI SDK');
908
+ this.clientApiProvider = null;
909
+ }
910
+ }
911
+ // Default to enhanced Vercel AI SDK (wraps existing logic)
912
+ const { createEnhancedVercelEngine } = await import('./engines/enhanced-vercel.js');
913
+ this.engine = createEnhancedVercelEngine(this);
914
+ if (this.debug) {
915
+ console.log('[DEBUG] Using Vercel AI SDK engine');
916
+ }
917
+ return this.engine;
918
+ }
919
+
755
920
  /**
756
921
  * Process assistant response content and detect/load image references
757
922
  * @param {string} content - The assistant's response content
@@ -1138,6 +1303,56 @@ export class ProbeAgent {
1138
1303
  }
1139
1304
  }
1140
1305
 
1306
+ /**
1307
+ * Get system prompt for Claude native engines (CLI/SDK) without XML formatting
1308
+ * These engines have native MCP support and don't need XML instructions
1309
+ */
1310
+ getClaudeNativeSystemPrompt() {
1311
+ let systemPrompt = '';
1312
+
1313
+ // Add persona/role if configured
1314
+ if (this.predefinedPrompt) {
1315
+ const personaPrompt = getPromptByType(this.predefinedPrompt);
1316
+ if (personaPrompt?.system) {
1317
+ systemPrompt += personaPrompt.system + '\n\n';
1318
+ }
1319
+ }
1320
+
1321
+ // Add high-level instructions about when to use tools
1322
+ systemPrompt += `You have access to powerful code search and analysis tools through MCP:
1323
+ - search: Find code patterns using semantic search
1324
+ - extract: Extract specific code sections with context
1325
+ - query: Use AST patterns for structural code matching
1326
+ - listFiles: Browse directory contents
1327
+ - searchFiles: Find files by name patterns`;
1328
+
1329
+ if (this.enableBash) {
1330
+ systemPrompt += `\n- bash: Execute bash commands for system operations`;
1331
+ }
1332
+
1333
+ systemPrompt += `\n
1334
+ When exploring code:
1335
+ 1. Start with search to find relevant code patterns
1336
+ 2. Use extract to get detailed context when needed
1337
+ 3. Prefer focused, specific searches over broad queries
1338
+ 4. Combine multiple tools to build complete understanding`;
1339
+
1340
+ // Add workspace context
1341
+ if (this.allowedFolders && this.allowedFolders.length > 0) {
1342
+ systemPrompt += `\n\nWorkspace: ${this.allowedFolders.join(', ')}`;
1343
+ }
1344
+
1345
+ // Add repository structure if available
1346
+ if (this.fileList) {
1347
+ systemPrompt += `\n\n# Repository Structure\n`;
1348
+ systemPrompt += `You are working with a repository located at: ${this.allowedFolders[0]}\n\n`;
1349
+ systemPrompt += `Here's an overview of the repository structure (showing up to 100 most relevant files):\n\n`;
1350
+ systemPrompt += '```\n' + this.fileList + '\n```\n';
1351
+ }
1352
+
1353
+ return systemPrompt;
1354
+ }
1355
+
1141
1356
  /**
1142
1357
  * Get the system message with instructions for the AI (XML Tool Format)
1143
1358
  */
@@ -1532,6 +1747,79 @@ When troubleshooting:
1532
1747
  const baseMaxIterations = this.maxIterations || MAX_TOOL_ITERATIONS;
1533
1748
  const maxIterations = options.schema ? baseMaxIterations + 4 : baseMaxIterations;
1534
1749
 
1750
+ // Check if we're using Claude Code engine which handles its own agentic loop
1751
+ const isClaudeCode = this.clientApiProvider === 'claude-code' || process.env.USE_CLAUDE_CODE === 'true';
1752
+
1753
+ if (isClaudeCode) {
1754
+ // For Claude Code, bypass the tool loop entirely - it handles its own internal dialogue
1755
+ if (this.debug) {
1756
+ console.log(`[DEBUG] Using Claude Code engine - bypassing tool loop (black box mode)`);
1757
+ console.log(`[DEBUG] Sending question directly to Claude Code: ${message.substring(0, 100)}...`);
1758
+ }
1759
+
1760
+ // Send the message directly to Claude Code and collect the response
1761
+ try {
1762
+ const engine = await this.getEngine();
1763
+ if (engine && engine.query) {
1764
+ let assistantResponseContent = '';
1765
+ let toolBatch = null;
1766
+
1767
+ // Query Claude Code directly with the message and schema
1768
+ for await (const chunk of engine.query(message, options)) {
1769
+ if (chunk.type === 'text' && chunk.content) {
1770
+ assistantResponseContent += chunk.content;
1771
+ if (options.onStream) {
1772
+ options.onStream(chunk.content);
1773
+ }
1774
+ } else if (chunk.type === 'toolBatch' && chunk.tools) {
1775
+ // Store tool batch for processing after response
1776
+ toolBatch = chunk.tools;
1777
+ if (this.debug) {
1778
+ console.log(`[DEBUG] Received batch of ${chunk.tools.length} tool events from Claude Code`);
1779
+ }
1780
+ } else if (chunk.type === 'error') {
1781
+ throw chunk.error;
1782
+ }
1783
+ }
1784
+
1785
+ // Emit tool events after response is complete (batch mode)
1786
+ if (toolBatch && toolBatch.length > 0 && this.events) {
1787
+ if (this.debug) {
1788
+ console.log(`[DEBUG] Emitting ${toolBatch.length} tool events from Claude Code batch`);
1789
+ }
1790
+ for (const toolEvent of toolBatch) {
1791
+ this.events.emit('toolCall', toolEvent);
1792
+ }
1793
+ }
1794
+
1795
+ // Update history with the exchange
1796
+ this.history.push(userMessage);
1797
+ this.history.push({
1798
+ role: 'assistant',
1799
+ content: assistantResponseContent
1800
+ });
1801
+
1802
+ // Store conversation history
1803
+ // TODO: storeConversationHistory is not yet implemented for Claude Code
1804
+ // await this.storeConversationHistory(this.history, oldHistoryLength);
1805
+
1806
+ // Emit completion hook
1807
+ await this.hooks.emit(HOOK_TYPES.COMPLETION, {
1808
+ sessionId: this.sessionId,
1809
+ prompt: message,
1810
+ response: assistantResponseContent
1811
+ });
1812
+
1813
+ return assistantResponseContent;
1814
+ }
1815
+ } catch (error) {
1816
+ if (this.debug) {
1817
+ console.error('[DEBUG] Claude Code error:', error);
1818
+ }
1819
+ throw error;
1820
+ }
1821
+ }
1822
+
1535
1823
  if (this.debug) {
1536
1824
  console.log(`[DEBUG] Starting agentic flow for question: ${message.substring(0, 100)}...`);
1537
1825
  if (options.schema) {
@@ -1539,7 +1827,7 @@ When troubleshooting:
1539
1827
  }
1540
1828
  }
1541
1829
 
1542
- // Tool iteration loop
1830
+ // Tool iteration loop (only for non-Claude Code engines)
1543
1831
  while (currentIteration < maxIterations && !completionAttempted) {
1544
1832
  currentIteration++;
1545
1833
  if (this.cancelled) throw new Error('Request was cancelled by the user');
@@ -1612,7 +1900,7 @@ When troubleshooting:
1612
1900
  const messagesForAI = this.prepareMessagesWithImages(currentMessages);
1613
1901
 
1614
1902
  const result = await this.streamTextWithRetryAndFallback({
1615
- model: this.provider(this.model),
1903
+ model: this.provider ? this.provider(this.model) : this.model,
1616
1904
  messages: messagesForAI,
1617
1905
  maxTokens: maxResponseTokens,
1618
1906
  temperature: 0.3,