@probelabs/probe 0.6.0-rc165 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@probelabs/probe",
3
- "version": "0.6.0-rc165",
3
+ "version": "0.6.0-rc167",
4
4
  "description": "Node.js wrapper for the probe code search tool",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
@@ -77,6 +77,7 @@
77
77
  "@ai-sdk/anthropic": "^2.0.8",
78
78
  "@ai-sdk/google": "^2.0.14",
79
79
  "@ai-sdk/openai": "^2.0.10",
80
+ "@anthropic-ai/claude-agent-sdk": "^0.1.46",
80
81
  "@modelcontextprotocol/sdk": "^1.0.0",
81
82
  "@probelabs/maid": "^0.0.21",
82
83
  "adm-zip": "^0.5.16",
@@ -27,6 +27,7 @@ import {
27
27
  bashToolDefinition,
28
28
  listFilesToolDefinition,
29
29
  searchFilesToolDefinition,
30
+ readImageToolDefinition,
30
31
  attemptCompletionToolDefinition,
31
32
  implementToolDefinition,
32
33
  editToolDefinition,
@@ -220,6 +221,9 @@ export class ProbeAgent {
220
221
  this.fallbackConfig = options.fallback || null;
221
222
  this.fallbackManager = null; // Will be initialized in initializeModel
222
223
 
224
+ // Engine support - minimal interface for multi-engine compatibility
225
+ this.engine = null; // Will be set in initializeModel or getEngine
226
+
223
227
  // Initialize the AI model
224
228
  this.initializeModel();
225
229
 
@@ -300,6 +304,31 @@ export class ProbeAgent {
300
304
  * This method initializes MCP and merges MCP tools into the tool list, and loads history from storage
301
305
  */
302
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
+
303
332
  // Load history from storage adapter
304
333
  try {
305
334
  const history = await this.storageAdapter.loadHistory(this.sessionId);
@@ -399,6 +428,23 @@ export class ProbeAgent {
399
428
  delegate: wrappedTools.delegateToolInstance,
400
429
  listFiles: listFilesToolInstance,
401
430
  searchFiles: searchFilesToolInstance,
431
+ readImage: {
432
+ execute: async (params) => {
433
+ const imagePath = params.path;
434
+ if (!imagePath) {
435
+ throw new Error('Image path is required');
436
+ }
437
+
438
+ // Load the image using the existing loadImageIfValid method
439
+ const loaded = await this.loadImageIfValid(imagePath);
440
+
441
+ if (!loaded) {
442
+ throw new Error(`Failed to load image: ${imagePath}. The file may not exist, be too large, have an unsupported format, or be outside allowed directories.`);
443
+ }
444
+
445
+ return `Image loaded successfully: ${imagePath}. The image is now available for analysis in the conversation.`;
446
+ }
447
+ }
402
448
  };
403
449
 
404
450
  // Add bash tool if enabled
@@ -434,6 +480,21 @@ export class ProbeAgent {
434
480
  }
435
481
  }
436
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
+
437
498
  /**
438
499
  * Initialize the AI model based on available API keys and forced provider setting
439
500
  */
@@ -447,6 +508,19 @@ export class ProbeAgent {
447
508
  return;
448
509
  }
449
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
+
450
524
  // Get API keys from environment variables
451
525
  // Support both ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN (used by Z.AI)
452
526
  const anthropicApiKey = process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN;
@@ -516,7 +590,12 @@ export class ProbeAgent {
516
590
  this.initializeBedrockModel(awsAccessKeyId, awsSecretAccessKey, awsRegion, awsSessionToken, awsApiKey, awsBedrockBaseUrl, modelName);
517
591
  this.initializeFallbackManager('bedrock', modelName);
518
592
  } else {
519
- 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
520
599
  }
521
600
  }
522
601
 
@@ -591,6 +670,60 @@ export class ProbeAgent {
591
670
  * @private
592
671
  */
593
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
+
594
727
  // Initialize retry manager if not already created
595
728
  if (!this.retryManager) {
596
729
  this.retryManager = new RetryManager({
@@ -734,6 +867,56 @@ export class ProbeAgent {
734
867
  }
735
868
  }
736
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
+
737
920
  /**
738
921
  * Process assistant response content and detect/load image references
739
922
  * @param {string} content - The assistant's response content
@@ -1120,6 +1303,56 @@ export class ProbeAgent {
1120
1303
  }
1121
1304
  }
1122
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
+
1123
1356
  /**
1124
1357
  * Get the system message with instructions for the AI (XML Tool Format)
1125
1358
  */
@@ -1172,6 +1405,9 @@ export class ProbeAgent {
1172
1405
  if (isToolAllowed('searchFiles')) {
1173
1406
  toolDefinitions += `${searchFilesToolDefinition}\n`;
1174
1407
  }
1408
+ if (isToolAllowed('readImage')) {
1409
+ toolDefinitions += `${readImageToolDefinition}\n`;
1410
+ }
1175
1411
 
1176
1412
  // Edit tools (require both allowEdit flag AND allowedTools permission)
1177
1413
  if (this.allowEdit && isToolAllowed('implement')) {
@@ -1262,6 +1498,7 @@ Available Tools:
1262
1498
  - extract: Extract specific code blocks or lines from files.
1263
1499
  - listFiles: List files and directories in a specified location.
1264
1500
  - searchFiles: Find files matching a glob pattern with recursive search capability.
1501
+ - readImage: Read and load an image file for AI analysis.
1265
1502
  ${this.allowEdit ? '- implement: Implement a feature or fix a bug using aider.\n- edit: Edit files using exact string replacement.\n- create: Create new files with specified content.\n' : ''}${this.enableDelegate ? '- delegate: Delegate big distinct tasks to specialized probe subagents.\n' : ''}${this.enableBash ? '- bash: Execute bash commands for system operations.\n' : ''}
1266
1503
  - attempt_completion: Finalize the task and provide the result to the user.
1267
1504
  - attempt_complete: Quick completion using previous response (shorthand).
@@ -1510,6 +1747,79 @@ When troubleshooting:
1510
1747
  const baseMaxIterations = this.maxIterations || MAX_TOOL_ITERATIONS;
1511
1748
  const maxIterations = options.schema ? baseMaxIterations + 4 : baseMaxIterations;
1512
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
+
1513
1823
  if (this.debug) {
1514
1824
  console.log(`[DEBUG] Starting agentic flow for question: ${message.substring(0, 100)}...`);
1515
1825
  if (options.schema) {
@@ -1517,7 +1827,7 @@ When troubleshooting:
1517
1827
  }
1518
1828
  }
1519
1829
 
1520
- // Tool iteration loop
1830
+ // Tool iteration loop (only for non-Claude Code engines)
1521
1831
  while (currentIteration < maxIterations && !completionAttempted) {
1522
1832
  currentIteration++;
1523
1833
  if (this.cancelled) throw new Error('Request was cancelled by the user');
@@ -1590,7 +1900,7 @@ When troubleshooting:
1590
1900
  const messagesForAI = this.prepareMessagesWithImages(currentMessages);
1591
1901
 
1592
1902
  const result = await this.streamTextWithRetryAndFallback({
1593
- model: this.provider(this.model),
1903
+ model: this.provider ? this.provider(this.model) : this.model,
1594
1904
  messages: messagesForAI,
1595
1905
  maxTokens: maxResponseTokens,
1596
1906
  temperature: 0.3,
@@ -1700,10 +2010,8 @@ When troubleshooting:
1700
2010
  console.log(`[DEBUG] Assistant response (${assistantResponseContent.length} chars): ${assistantPreview}`);
1701
2011
  }
1702
2012
 
1703
- // Process image references in assistant response for next iteration
1704
- if (assistantResponseContent) {
1705
- await this.processImageReferences(assistantResponseContent);
1706
- }
2013
+ // Images in assistant responses are not automatically processed
2014
+ // AI can use the readImage tool to explicitly request reading an image
1707
2015
 
1708
2016
  // Parse tool call from response with valid tools list
1709
2017
  // Build validTools based on allowedTools configuration (same pattern as getSystemMessage)
@@ -1713,6 +2021,7 @@ When troubleshooting:
1713
2021
  if (this.allowedTools.isEnabled('extract')) validTools.push('extract');
1714
2022
  if (this.allowedTools.isEnabled('listFiles')) validTools.push('listFiles');
1715
2023
  if (this.allowedTools.isEnabled('searchFiles')) validTools.push('searchFiles');
2024
+ if (this.allowedTools.isEnabled('readImage')) validTools.push('readImage');
1716
2025
  if (this.allowedTools.isEnabled('attempt_completion')) validTools.push('attempt_completion');
1717
2026
 
1718
2027
  // Edit tools (require both allowEdit flag AND allowedTools permission)