@probelabs/probe 0.6.0-rc166 → 0.6.0-rc168
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 +39 -0
- package/build/agent/ProbeAgent.d.ts +7 -1
- package/build/agent/ProbeAgent.js +589 -4
- package/build/agent/engines/codex.js +347 -0
- package/build/agent/engines/enhanced-claude-code.js +556 -0
- package/build/agent/engines/enhanced-vercel.js +83 -0
- package/build/agent/engines/vercel.js +62 -0
- package/build/agent/index.js +2228 -174
- package/build/agent/mcp/built-in-server.js +790 -0
- package/build/agent/shared/Session.js +53 -0
- package/build/agent/shared/prompts.js +129 -0
- package/cjs/agent/ProbeAgent.cjs +13760 -10554
- package/cjs/index.cjs +13840 -10634
- package/index.d.ts +7 -1
- package/package.json +2 -1
- package/src/agent/ProbeAgent.d.ts +7 -1
- package/src/agent/ProbeAgent.js +589 -4
- package/src/agent/engines/codex.js +347 -0
- package/src/agent/engines/enhanced-claude-code.js +556 -0
- package/src/agent/engines/enhanced-vercel.js +83 -0
- package/src/agent/engines/vercel.js +62 -0
- package/src/agent/mcp/built-in-server.js +790 -0
- package/src/agent/shared/Session.js +53 -0
- package/src/agent/shared/prompts.js +129 -0
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
validateAndFixMermaidResponse
|
|
56
56
|
} from './schemaUtils.js';
|
|
57
57
|
import { removeThinkingTags } from './xmlParsingUtils.js';
|
|
58
|
+
import { predefinedPrompts } from './shared/prompts.js';
|
|
58
59
|
import {
|
|
59
60
|
MCPXmlBridge,
|
|
60
61
|
parseHybridXmlToolCall,
|
|
@@ -89,6 +90,7 @@ export class ProbeAgent {
|
|
|
89
90
|
* @param {Object} options - Configuration options
|
|
90
91
|
* @param {string} [options.sessionId] - Optional session ID
|
|
91
92
|
* @param {string} [options.customPrompt] - Custom prompt to replace the default system message
|
|
93
|
+
* @param {string} [options.systemPrompt] - Alias for customPrompt; takes precedence when both are provided
|
|
92
94
|
* @param {string} [options.promptType] - Predefined prompt type (architect, code-review, support)
|
|
93
95
|
* @param {boolean} [options.allowEdit=false] - Allow the use of the 'implement' tool
|
|
94
96
|
* @param {boolean} [options.enableDelegate=false] - Enable the delegate tool for task distribution to subagents
|
|
@@ -125,7 +127,8 @@ export class ProbeAgent {
|
|
|
125
127
|
constructor(options = {}) {
|
|
126
128
|
// Basic configuration
|
|
127
129
|
this.sessionId = options.sessionId || randomUUID();
|
|
128
|
-
|
|
130
|
+
// Support systemPrompt alias (overrides customPrompt when both are provided)
|
|
131
|
+
this.customPrompt = options.systemPrompt || options.customPrompt || null;
|
|
129
132
|
this.promptType = options.promptType || 'code-explorer';
|
|
130
133
|
this.allowEdit = !!options.allowEdit;
|
|
131
134
|
this.enableDelegate = !!options.enableDelegate;
|
|
@@ -221,6 +224,9 @@ export class ProbeAgent {
|
|
|
221
224
|
this.fallbackConfig = options.fallback || null;
|
|
222
225
|
this.fallbackManager = null; // Will be initialized in initializeModel
|
|
223
226
|
|
|
227
|
+
// Engine support - minimal interface for multi-engine compatibility
|
|
228
|
+
this.engine = null; // Will be set in initializeModel or getEngine
|
|
229
|
+
|
|
224
230
|
// Initialize the AI model
|
|
225
231
|
this.initializeModel();
|
|
226
232
|
|
|
@@ -301,6 +307,44 @@ export class ProbeAgent {
|
|
|
301
307
|
* This method initializes MCP and merges MCP tools into the tool list, and loads history from storage
|
|
302
308
|
*/
|
|
303
309
|
async initialize() {
|
|
310
|
+
// Check if we need to auto-detect claude-code or codex provider
|
|
311
|
+
// This happens when no API keys are set and no provider is specified
|
|
312
|
+
if (!this.provider && !this.clientApiProvider && this.apiType !== 'claude-code' && this.apiType !== 'codex') {
|
|
313
|
+
// Check if initializeModel marked as uninitialized (no API keys)
|
|
314
|
+
if (this.apiType === 'uninitialized') {
|
|
315
|
+
const claudeAvailable = await this.isClaudeCommandAvailable();
|
|
316
|
+
const codexAvailable = await this.isCodexCommandAvailable();
|
|
317
|
+
|
|
318
|
+
if (claudeAvailable) {
|
|
319
|
+
if (this.debug) {
|
|
320
|
+
console.log('[DEBUG] No API keys found, but claude command detected');
|
|
321
|
+
console.log('[DEBUG] Auto-switching to claude-code provider');
|
|
322
|
+
}
|
|
323
|
+
// Set provider to claude-code
|
|
324
|
+
this.clientApiProvider = 'claude-code';
|
|
325
|
+
this.provider = null;
|
|
326
|
+
this.model = this.clientApiModel || 'claude-3-5-sonnet-20241022';
|
|
327
|
+
this.apiType = 'claude-code';
|
|
328
|
+
} else if (codexAvailable) {
|
|
329
|
+
if (this.debug) {
|
|
330
|
+
console.log('[DEBUG] No API keys found, but codex command detected');
|
|
331
|
+
console.log('[DEBUG] Auto-switching to codex provider');
|
|
332
|
+
}
|
|
333
|
+
// Set provider to codex
|
|
334
|
+
this.clientApiProvider = 'codex';
|
|
335
|
+
this.provider = null;
|
|
336
|
+
this.model = this.clientApiModel || 'gpt-4o';
|
|
337
|
+
this.apiType = 'codex';
|
|
338
|
+
} else {
|
|
339
|
+
// Neither API keys nor CLI commands available
|
|
340
|
+
throw new Error('No API key provided and neither claude nor codex command found. Please either:\n' +
|
|
341
|
+
'1. Set an API key: ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY, or AWS credentials\n' +
|
|
342
|
+
'2. Install claude command from https://docs.claude.com/en/docs/claude-code\n' +
|
|
343
|
+
'3. Install codex command from https://openai.com/codex');
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
304
348
|
// Load history from storage adapter
|
|
305
349
|
try {
|
|
306
350
|
const history = await this.storageAdapter.loadHistory(this.sessionId);
|
|
@@ -452,6 +496,42 @@ export class ProbeAgent {
|
|
|
452
496
|
}
|
|
453
497
|
}
|
|
454
498
|
|
|
499
|
+
/**
|
|
500
|
+
* Check if claude command is available on the system
|
|
501
|
+
* Uses execFile instead of exec to avoid shell injection risks
|
|
502
|
+
* @returns {Promise<boolean>} True if claude command is available
|
|
503
|
+
* @private
|
|
504
|
+
*/
|
|
505
|
+
async isClaudeCommandAvailable() {
|
|
506
|
+
try {
|
|
507
|
+
const { execFile } = await import('child_process');
|
|
508
|
+
const { promisify } = await import('util');
|
|
509
|
+
const execFileAsync = promisify(execFile);
|
|
510
|
+
await execFileAsync('claude', ['--version'], { timeout: 5000 });
|
|
511
|
+
return true;
|
|
512
|
+
} catch (error) {
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Check if codex command is available on the system
|
|
519
|
+
* Uses execFile instead of exec to avoid shell injection risks
|
|
520
|
+
* @returns {Promise<boolean>} True if codex command is available
|
|
521
|
+
* @private
|
|
522
|
+
*/
|
|
523
|
+
async isCodexCommandAvailable() {
|
|
524
|
+
try {
|
|
525
|
+
const { execFile } = await import('child_process');
|
|
526
|
+
const { promisify } = await import('util');
|
|
527
|
+
const execFileAsync = promisify(execFile);
|
|
528
|
+
await execFileAsync('codex', ['--version'], { timeout: 5000 });
|
|
529
|
+
return true;
|
|
530
|
+
} catch (error) {
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
455
535
|
/**
|
|
456
536
|
* Initialize the AI model based on available API keys and forced provider setting
|
|
457
537
|
*/
|
|
@@ -465,6 +545,38 @@ export class ProbeAgent {
|
|
|
465
545
|
return;
|
|
466
546
|
}
|
|
467
547
|
|
|
548
|
+
// Skip API key requirement for Claude Code (uses built-in access in Claude Code)
|
|
549
|
+
if (this.clientApiProvider === 'claude-code' || process.env.USE_CLAUDE_CODE === 'true') {
|
|
550
|
+
// Claude Code engine will be initialized lazily in getEngine()
|
|
551
|
+
// Set minimal defaults for compatibility
|
|
552
|
+
this.provider = null;
|
|
553
|
+
this.model = modelName || 'claude-3-5-sonnet-20241022';
|
|
554
|
+
this.apiType = 'claude-code';
|
|
555
|
+
if (this.debug) {
|
|
556
|
+
console.log('[DEBUG] Claude Code engine selected - will use built-in access if available');
|
|
557
|
+
}
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Skip API key requirement for Codex CLI (uses built-in access in Codex CLI)
|
|
562
|
+
if (this.clientApiProvider === 'codex' || process.env.USE_CODEX === 'true') {
|
|
563
|
+
// Codex CLI engine will be initialized lazily in getEngine()
|
|
564
|
+
// Set minimal defaults for compatibility
|
|
565
|
+
this.provider = null;
|
|
566
|
+
// Only set model if explicitly provided, otherwise let Codex use account default
|
|
567
|
+
this.model = modelName || null;
|
|
568
|
+
this.apiType = 'codex';
|
|
569
|
+
if (this.debug) {
|
|
570
|
+
console.log('[DEBUG] Codex CLI engine selected - will use built-in access if available');
|
|
571
|
+
if (this.model) {
|
|
572
|
+
console.log(`[DEBUG] Using model: ${this.model}`);
|
|
573
|
+
} else {
|
|
574
|
+
console.log('[DEBUG] Using Codex account default model');
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
|
|
468
580
|
// Get API keys from environment variables
|
|
469
581
|
// Support both ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN (used by Z.AI)
|
|
470
582
|
const anthropicApiKey = process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN;
|
|
@@ -534,7 +646,12 @@ export class ProbeAgent {
|
|
|
534
646
|
this.initializeBedrockModel(awsAccessKeyId, awsSecretAccessKey, awsRegion, awsSessionToken, awsApiKey, awsBedrockBaseUrl, modelName);
|
|
535
647
|
this.initializeFallbackManager('bedrock', modelName);
|
|
536
648
|
} else {
|
|
537
|
-
|
|
649
|
+
// No API keys found - mark for potential claude-code auto-detection in initialize()
|
|
650
|
+
this.apiType = 'uninitialized';
|
|
651
|
+
if (this.debug) {
|
|
652
|
+
console.log('[DEBUG] No API keys found - will check for claude command in initialize()');
|
|
653
|
+
}
|
|
654
|
+
// Don't throw error yet - will be checked in initialize() method
|
|
538
655
|
}
|
|
539
656
|
}
|
|
540
657
|
|
|
@@ -609,6 +726,114 @@ export class ProbeAgent {
|
|
|
609
726
|
* @private
|
|
610
727
|
*/
|
|
611
728
|
async streamTextWithRetryAndFallback(options) {
|
|
729
|
+
// Check if we should use Claude Code engine
|
|
730
|
+
if (this.clientApiProvider === 'claude-code' || process.env.USE_CLAUDE_CODE === 'true') {
|
|
731
|
+
try {
|
|
732
|
+
const engine = await this.getEngine();
|
|
733
|
+
if (engine && engine.query) {
|
|
734
|
+
// Convert Vercel AI SDK format to engine format
|
|
735
|
+
// Extract the ORIGINAL user message as the main prompt (skip any warning messages)
|
|
736
|
+
// Look for user messages that are NOT the warning message
|
|
737
|
+
const userMessages = options.messages.filter(m =>
|
|
738
|
+
m.role === 'user' &&
|
|
739
|
+
!m.content.includes('WARNING: You have reached the maximum tool iterations limit')
|
|
740
|
+
);
|
|
741
|
+
const lastUserMessage = userMessages[userMessages.length - 1];
|
|
742
|
+
const prompt = lastUserMessage ? lastUserMessage.content : '';
|
|
743
|
+
|
|
744
|
+
// Pass system message and other options
|
|
745
|
+
const engineOptions = {
|
|
746
|
+
maxTokens: options.maxTokens,
|
|
747
|
+
temperature: options.temperature,
|
|
748
|
+
messages: options.messages,
|
|
749
|
+
systemPrompt: options.messages.find(m => m.role === 'system')?.content
|
|
750
|
+
};
|
|
751
|
+
|
|
752
|
+
// Get the engine's query result (async generator)
|
|
753
|
+
const engineStream = engine.query(prompt, engineOptions);
|
|
754
|
+
|
|
755
|
+
// Create a text stream that extracts text from engine messages
|
|
756
|
+
async function* createTextStream() {
|
|
757
|
+
for await (const message of engineStream) {
|
|
758
|
+
if (message.type === 'text' && message.content) {
|
|
759
|
+
yield message.content;
|
|
760
|
+
} else if (typeof message === 'string') {
|
|
761
|
+
// If engine returns plain strings, pass them through
|
|
762
|
+
yield message;
|
|
763
|
+
}
|
|
764
|
+
// Ignore other message types for the text stream
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// Wrap the engine result to match streamText interface
|
|
769
|
+
return {
|
|
770
|
+
textStream: createTextStream(),
|
|
771
|
+
usage: Promise.resolve({}), // Engine should handle its own usage tracking
|
|
772
|
+
// Add other streamText-compatible properties as needed
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
} catch (error) {
|
|
776
|
+
if (this.debug) {
|
|
777
|
+
console.log(`[DEBUG] Failed to use Claude Code engine, falling back to Vercel:`, error.message);
|
|
778
|
+
}
|
|
779
|
+
// Fall through to use Vercel engine as fallback
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// Check if we should use Codex engine
|
|
784
|
+
if (this.clientApiProvider === 'codex' || process.env.USE_CODEX === 'true') {
|
|
785
|
+
try {
|
|
786
|
+
const engine = await this.getEngine();
|
|
787
|
+
if (engine && engine.query) {
|
|
788
|
+
// Convert Vercel AI SDK format to engine format
|
|
789
|
+
// Extract the ORIGINAL user message as the main prompt (skip any warning messages)
|
|
790
|
+
// Look for user messages that are NOT the warning message
|
|
791
|
+
const userMessages = options.messages.filter(m =>
|
|
792
|
+
m.role === 'user' &&
|
|
793
|
+
!m.content.includes('WARNING: You have reached the maximum tool iterations limit')
|
|
794
|
+
);
|
|
795
|
+
const lastUserMessage = userMessages[userMessages.length - 1];
|
|
796
|
+
const prompt = lastUserMessage ? lastUserMessage.content : '';
|
|
797
|
+
|
|
798
|
+
// Pass system message and other options
|
|
799
|
+
const engineOptions = {
|
|
800
|
+
maxTokens: options.maxTokens,
|
|
801
|
+
temperature: options.temperature,
|
|
802
|
+
messages: options.messages,
|
|
803
|
+
systemPrompt: options.messages.find(m => m.role === 'system')?.content
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
// Get the engine's query result (async generator)
|
|
807
|
+
const engineStream = engine.query(prompt, engineOptions);
|
|
808
|
+
|
|
809
|
+
// Create a text stream that extracts text from engine messages
|
|
810
|
+
async function* createTextStream() {
|
|
811
|
+
for await (const message of engineStream) {
|
|
812
|
+
if (message.type === 'text' && message.content) {
|
|
813
|
+
yield message.content;
|
|
814
|
+
} else if (typeof message === 'string') {
|
|
815
|
+
// If engine returns plain strings, pass them through
|
|
816
|
+
yield message;
|
|
817
|
+
}
|
|
818
|
+
// Ignore other message types for the text stream
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// Wrap the engine result to match streamText interface
|
|
823
|
+
return {
|
|
824
|
+
textStream: createTextStream(),
|
|
825
|
+
usage: Promise.resolve({}), // Engine should handle its own usage tracking
|
|
826
|
+
// Add other streamText-compatible properties as needed
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
} catch (error) {
|
|
830
|
+
if (this.debug) {
|
|
831
|
+
console.log(`[DEBUG] Failed to use Codex engine, falling back to Vercel:`, error.message);
|
|
832
|
+
}
|
|
833
|
+
// Fall through to use Vercel engine as fallback
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
612
837
|
// Initialize retry manager if not already created
|
|
613
838
|
if (!this.retryManager) {
|
|
614
839
|
this.retryManager = new RetryManager({
|
|
@@ -752,6 +977,118 @@ export class ProbeAgent {
|
|
|
752
977
|
}
|
|
753
978
|
}
|
|
754
979
|
|
|
980
|
+
/**
|
|
981
|
+
* Get or create the AI engine based on configuration
|
|
982
|
+
* @returns {Promise<Object>} Engine interface
|
|
983
|
+
* @private
|
|
984
|
+
*/
|
|
985
|
+
async getEngine() {
|
|
986
|
+
// If engine already created, return it
|
|
987
|
+
if (this.engine) {
|
|
988
|
+
return this.engine;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
// Try Claude Code engine if requested
|
|
992
|
+
if (this.clientApiProvider === 'claude-code' || process.env.USE_CLAUDE_CODE === 'true') {
|
|
993
|
+
try {
|
|
994
|
+
const { createEnhancedClaudeCLIEngine } = await import('./engines/enhanced-claude-code.js');
|
|
995
|
+
|
|
996
|
+
// For Claude Code, use a cleaner system prompt without XML formatting
|
|
997
|
+
// since it has native MCP support for tools
|
|
998
|
+
const systemPrompt = this.customPrompt || this.getClaudeNativeSystemPrompt();
|
|
999
|
+
|
|
1000
|
+
this.engine = await createEnhancedClaudeCLIEngine({
|
|
1001
|
+
agent: this, // Pass reference to ProbeAgent for tool access
|
|
1002
|
+
systemPrompt: systemPrompt,
|
|
1003
|
+
customPrompt: this.customPrompt,
|
|
1004
|
+
sessionId: this.options?.sessionId,
|
|
1005
|
+
debug: this.debug,
|
|
1006
|
+
allowedTools: this.allowedTools // Pass tool filtering configuration
|
|
1007
|
+
});
|
|
1008
|
+
if (this.debug) {
|
|
1009
|
+
console.log('[DEBUG] Using Claude Code engine with Probe tools');
|
|
1010
|
+
if (this.customPrompt) {
|
|
1011
|
+
console.log('[DEBUG] Using custom prompt/persona');
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
return this.engine;
|
|
1015
|
+
} catch (error) {
|
|
1016
|
+
console.warn('[WARNING] Failed to load Claude Code engine:', error.message);
|
|
1017
|
+
console.warn('[WARNING] Falling back to Vercel AI SDK');
|
|
1018
|
+
this.clientApiProvider = null;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
// Try Codex CLI engine if requested
|
|
1023
|
+
if (this.clientApiProvider === 'codex' || process.env.USE_CODEX === 'true') {
|
|
1024
|
+
try {
|
|
1025
|
+
const { createCodexEngine } = await import('./engines/codex.js');
|
|
1026
|
+
|
|
1027
|
+
// For Codex CLI, use a cleaner system prompt without XML formatting
|
|
1028
|
+
// since it has native MCP support for tools
|
|
1029
|
+
const systemPrompt = this.customPrompt || this.getCodexNativeSystemPrompt();
|
|
1030
|
+
|
|
1031
|
+
this.engine = await createCodexEngine({
|
|
1032
|
+
agent: this, // Pass reference to ProbeAgent for tool access
|
|
1033
|
+
systemPrompt: systemPrompt,
|
|
1034
|
+
customPrompt: this.customPrompt,
|
|
1035
|
+
sessionId: this.options?.sessionId,
|
|
1036
|
+
debug: this.debug,
|
|
1037
|
+
allowedTools: this.allowedTools, // Pass tool filtering configuration
|
|
1038
|
+
model: this.model // Pass model name (e.g., gpt-4o, o3, etc.)
|
|
1039
|
+
});
|
|
1040
|
+
if (this.debug) {
|
|
1041
|
+
console.log('[DEBUG] Using Codex CLI engine with Probe tools');
|
|
1042
|
+
if (this.customPrompt) {
|
|
1043
|
+
console.log('[DEBUG] Using custom prompt/persona');
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
return this.engine;
|
|
1047
|
+
} catch (error) {
|
|
1048
|
+
console.warn('[WARNING] Failed to load Codex CLI engine:', error.message);
|
|
1049
|
+
console.warn('[WARNING] Falling back to Vercel AI SDK');
|
|
1050
|
+
this.clientApiProvider = null;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
// Default to enhanced Vercel AI SDK (wraps existing logic)
|
|
1055
|
+
const { createEnhancedVercelEngine } = await import('./engines/enhanced-vercel.js');
|
|
1056
|
+
this.engine = createEnhancedVercelEngine(this);
|
|
1057
|
+
if (this.debug) {
|
|
1058
|
+
console.log('[DEBUG] Using Vercel AI SDK engine');
|
|
1059
|
+
}
|
|
1060
|
+
return this.engine;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
/**
|
|
1064
|
+
* Get session information including thread ID for resumability
|
|
1065
|
+
* @returns {Object} Session info with sessionId, threadId, messageCount
|
|
1066
|
+
*/
|
|
1067
|
+
getSessionInfo() {
|
|
1068
|
+
if (this.engine && this.engine.getSession) {
|
|
1069
|
+
return this.engine.getSession();
|
|
1070
|
+
}
|
|
1071
|
+
return {
|
|
1072
|
+
id: this.sessionId,
|
|
1073
|
+
threadId: null,
|
|
1074
|
+
messageCount: 0
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
/**
|
|
1079
|
+
* Close the agent and clean up resources (e.g., MCP servers)
|
|
1080
|
+
* @returns {Promise<void>}
|
|
1081
|
+
*/
|
|
1082
|
+
async close() {
|
|
1083
|
+
if (this.engine && this.engine.close) {
|
|
1084
|
+
await this.engine.close();
|
|
1085
|
+
}
|
|
1086
|
+
if (this.mcpBridge) {
|
|
1087
|
+
// Clean up MCP bridge if needed
|
|
1088
|
+
this.mcpBridge = null;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
755
1092
|
/**
|
|
756
1093
|
* Process assistant response content and detect/load image references
|
|
757
1094
|
* @param {string} content - The assistant's response content
|
|
@@ -1138,6 +1475,109 @@ export class ProbeAgent {
|
|
|
1138
1475
|
}
|
|
1139
1476
|
}
|
|
1140
1477
|
|
|
1478
|
+
/**
|
|
1479
|
+
* Get system prompt for Claude native engines (CLI/SDK) without XML formatting
|
|
1480
|
+
* These engines have native MCP support and don't need XML instructions
|
|
1481
|
+
*/
|
|
1482
|
+
getClaudeNativeSystemPrompt() {
|
|
1483
|
+
let systemPrompt = '';
|
|
1484
|
+
|
|
1485
|
+
// Add persona/role if configured
|
|
1486
|
+
if (this.customPrompt) {
|
|
1487
|
+
systemPrompt += this.customPrompt + '\n\n';
|
|
1488
|
+
} else if (this.promptType && predefinedPrompts[this.promptType]) {
|
|
1489
|
+
systemPrompt += predefinedPrompts[this.promptType] + '\n\n';
|
|
1490
|
+
} else {
|
|
1491
|
+
// Use default code-explorer prompt
|
|
1492
|
+
systemPrompt += predefinedPrompts['code-explorer'] + '\n\n';
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
// Add high-level instructions about when to use tools
|
|
1496
|
+
systemPrompt += `You have access to powerful code search and analysis tools through MCP:
|
|
1497
|
+
- search: Find code patterns using semantic search
|
|
1498
|
+
- extract: Extract specific code sections with context
|
|
1499
|
+
- query: Use AST patterns for structural code matching
|
|
1500
|
+
- listFiles: Browse directory contents
|
|
1501
|
+
- searchFiles: Find files by name patterns`;
|
|
1502
|
+
|
|
1503
|
+
if (this.enableBash) {
|
|
1504
|
+
systemPrompt += `\n- bash: Execute bash commands for system operations`;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
systemPrompt += `\n
|
|
1508
|
+
When exploring code:
|
|
1509
|
+
1. Start with search to find relevant code patterns
|
|
1510
|
+
2. Use extract to get detailed context when needed
|
|
1511
|
+
3. Prefer focused, specific searches over broad queries
|
|
1512
|
+
4. Combine multiple tools to build complete understanding`;
|
|
1513
|
+
|
|
1514
|
+
// Add workspace context
|
|
1515
|
+
if (this.allowedFolders && this.allowedFolders.length > 0) {
|
|
1516
|
+
systemPrompt += `\n\nWorkspace: ${this.allowedFolders.join(', ')}`;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
// Add repository structure if available
|
|
1520
|
+
if (this.fileList) {
|
|
1521
|
+
systemPrompt += `\n\n# Repository Structure\n`;
|
|
1522
|
+
systemPrompt += `You are working with a repository located at: ${this.allowedFolders[0]}\n\n`;
|
|
1523
|
+
systemPrompt += `Here's an overview of the repository structure (showing up to 100 most relevant files):\n\n`;
|
|
1524
|
+
systemPrompt += '```\n' + this.fileList + '\n```\n';
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
return systemPrompt;
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
/**
|
|
1531
|
+
* Get system prompt for Codex CLI (similar to Claude but optimized for Codex)
|
|
1532
|
+
*/
|
|
1533
|
+
getCodexNativeSystemPrompt() {
|
|
1534
|
+
let systemPrompt = '';
|
|
1535
|
+
|
|
1536
|
+
// Add persona/role if configured
|
|
1537
|
+
if (this.customPrompt) {
|
|
1538
|
+
systemPrompt += this.customPrompt + '\n\n';
|
|
1539
|
+
} else if (this.promptType && predefinedPrompts[this.promptType]) {
|
|
1540
|
+
systemPrompt += predefinedPrompts[this.promptType] + '\n\n';
|
|
1541
|
+
} else {
|
|
1542
|
+
// Use default code-explorer prompt
|
|
1543
|
+
systemPrompt += predefinedPrompts['code-explorer'] + '\n\n';
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
// Add high-level instructions about when to use tools
|
|
1547
|
+
systemPrompt += `You have access to powerful code search and analysis tools through MCP:
|
|
1548
|
+
- search: Find code patterns using semantic search
|
|
1549
|
+
- extract: Extract specific code sections with context
|
|
1550
|
+
- query: Use AST patterns for structural code matching
|
|
1551
|
+
- listFiles: Browse directory contents
|
|
1552
|
+
- searchFiles: Find files by name patterns`;
|
|
1553
|
+
|
|
1554
|
+
if (this.enableBash) {
|
|
1555
|
+
systemPrompt += `\n- bash: Execute bash commands for system operations`;
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
systemPrompt += `\n
|
|
1559
|
+
When exploring code:
|
|
1560
|
+
1. Start with search to find relevant code patterns
|
|
1561
|
+
2. Use extract to get detailed context when needed
|
|
1562
|
+
3. Prefer focused, specific searches over broad queries
|
|
1563
|
+
4. Combine multiple tools to build complete understanding`;
|
|
1564
|
+
|
|
1565
|
+
// Add workspace context
|
|
1566
|
+
if (this.allowedFolders && this.allowedFolders.length > 0) {
|
|
1567
|
+
systemPrompt += `\n\nWorkspace: ${this.allowedFolders.join(', ')}`;
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
// Add repository structure if available
|
|
1571
|
+
if (this.fileList) {
|
|
1572
|
+
systemPrompt += `\n\n# Repository Structure\n`;
|
|
1573
|
+
systemPrompt += `You are working with a repository located at: ${this.allowedFolders[0]}\n\n`;
|
|
1574
|
+
systemPrompt += `Here's an overview of the repository structure (showing up to 100 most relevant files):\n\n`;
|
|
1575
|
+
systemPrompt += '```\n' + this.fileList + '\n```\n';
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
return systemPrompt;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1141
1581
|
/**
|
|
1142
1582
|
* Get the system message with instructions for the AI (XML Tool Format)
|
|
1143
1583
|
*/
|
|
@@ -1532,6 +1972,151 @@ When troubleshooting:
|
|
|
1532
1972
|
const baseMaxIterations = this.maxIterations || MAX_TOOL_ITERATIONS;
|
|
1533
1973
|
const maxIterations = options.schema ? baseMaxIterations + 4 : baseMaxIterations;
|
|
1534
1974
|
|
|
1975
|
+
// Check if we're using CLI-based engines which handle their own agentic loop
|
|
1976
|
+
const isClaudeCode = this.clientApiProvider === 'claude-code' || process.env.USE_CLAUDE_CODE === 'true';
|
|
1977
|
+
const isCodex = this.clientApiProvider === 'codex' || process.env.USE_CODEX === 'true';
|
|
1978
|
+
|
|
1979
|
+
if (isClaudeCode) {
|
|
1980
|
+
// For Claude Code, bypass the tool loop entirely - it handles its own internal dialogue
|
|
1981
|
+
if (this.debug) {
|
|
1982
|
+
console.log(`[DEBUG] Using Claude Code engine - bypassing tool loop (black box mode)`);
|
|
1983
|
+
console.log(`[DEBUG] Sending question directly to Claude Code: ${message.substring(0, 100)}...`);
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
// Send the message directly to Claude Code and collect the response
|
|
1987
|
+
try {
|
|
1988
|
+
const engine = await this.getEngine();
|
|
1989
|
+
if (engine && engine.query) {
|
|
1990
|
+
let assistantResponseContent = '';
|
|
1991
|
+
let toolBatch = null;
|
|
1992
|
+
|
|
1993
|
+
// Query Claude Code directly with the message and schema
|
|
1994
|
+
for await (const chunk of engine.query(message, options)) {
|
|
1995
|
+
if (chunk.type === 'text' && chunk.content) {
|
|
1996
|
+
assistantResponseContent += chunk.content;
|
|
1997
|
+
if (options.onStream) {
|
|
1998
|
+
options.onStream(chunk.content);
|
|
1999
|
+
}
|
|
2000
|
+
} else if (chunk.type === 'toolBatch' && chunk.tools) {
|
|
2001
|
+
// Store tool batch for processing after response
|
|
2002
|
+
toolBatch = chunk.tools;
|
|
2003
|
+
if (this.debug) {
|
|
2004
|
+
console.log(`[DEBUG] Received batch of ${chunk.tools.length} tool events from Claude Code`);
|
|
2005
|
+
}
|
|
2006
|
+
} else if (chunk.type === 'error') {
|
|
2007
|
+
throw chunk.error;
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
// Emit tool events after response is complete (batch mode)
|
|
2012
|
+
if (toolBatch && toolBatch.length > 0 && this.events) {
|
|
2013
|
+
if (this.debug) {
|
|
2014
|
+
console.log(`[DEBUG] Emitting ${toolBatch.length} tool events from Claude Code batch`);
|
|
2015
|
+
}
|
|
2016
|
+
for (const toolEvent of toolBatch) {
|
|
2017
|
+
this.events.emit('toolCall', toolEvent);
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
// Update history with the exchange
|
|
2022
|
+
this.history.push(userMessage);
|
|
2023
|
+
this.history.push({
|
|
2024
|
+
role: 'assistant',
|
|
2025
|
+
content: assistantResponseContent
|
|
2026
|
+
});
|
|
2027
|
+
|
|
2028
|
+
// Store conversation history
|
|
2029
|
+
// TODO: storeConversationHistory is not yet implemented for Claude Code
|
|
2030
|
+
// await this.storeConversationHistory(this.history, oldHistoryLength);
|
|
2031
|
+
|
|
2032
|
+
// Emit completion hook
|
|
2033
|
+
await this.hooks.emit(HOOK_TYPES.COMPLETION, {
|
|
2034
|
+
sessionId: this.sessionId,
|
|
2035
|
+
prompt: message,
|
|
2036
|
+
response: assistantResponseContent
|
|
2037
|
+
});
|
|
2038
|
+
|
|
2039
|
+
return assistantResponseContent;
|
|
2040
|
+
}
|
|
2041
|
+
} catch (error) {
|
|
2042
|
+
if (this.debug) {
|
|
2043
|
+
console.error('[DEBUG] Claude Code error:', error);
|
|
2044
|
+
}
|
|
2045
|
+
throw error;
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
// Handle Codex engine (same pattern as Claude Code)
|
|
2050
|
+
if (isCodex) {
|
|
2051
|
+
// For Codex, bypass the tool loop entirely - it handles its own internal dialogue
|
|
2052
|
+
if (this.debug) {
|
|
2053
|
+
console.log(`[DEBUG] Using Codex engine - bypassing tool loop (black box mode)`);
|
|
2054
|
+
console.log(`[DEBUG] Sending question directly to Codex: ${message.substring(0, 100)}...`);
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
// Send the message directly to Codex and collect the response
|
|
2058
|
+
try {
|
|
2059
|
+
const engine = await this.getEngine();
|
|
2060
|
+
if (engine && engine.query) {
|
|
2061
|
+
let assistantResponseContent = '';
|
|
2062
|
+
let toolBatch = null;
|
|
2063
|
+
|
|
2064
|
+
// Query Codex directly with the message and schema
|
|
2065
|
+
for await (const chunk of engine.query(message, options)) {
|
|
2066
|
+
if (chunk.type === 'text' && chunk.content) {
|
|
2067
|
+
assistantResponseContent += chunk.content;
|
|
2068
|
+
if (options.onStream) {
|
|
2069
|
+
options.onStream(chunk.content);
|
|
2070
|
+
}
|
|
2071
|
+
} else if (chunk.type === 'toolBatch' && chunk.tools) {
|
|
2072
|
+
// Store tool batch for processing after response
|
|
2073
|
+
toolBatch = chunk.tools;
|
|
2074
|
+
if (this.debug) {
|
|
2075
|
+
console.log(`[DEBUG] Received batch of ${chunk.tools.length} tool events from Codex`);
|
|
2076
|
+
}
|
|
2077
|
+
} else if (chunk.type === 'error') {
|
|
2078
|
+
throw chunk.error;
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
// Emit tool events after response is complete (batch mode)
|
|
2083
|
+
if (toolBatch && toolBatch.length > 0 && this.events) {
|
|
2084
|
+
if (this.debug) {
|
|
2085
|
+
console.log(`[DEBUG] Emitting ${toolBatch.length} tool events from Codex batch`);
|
|
2086
|
+
}
|
|
2087
|
+
for (const toolEvent of toolBatch) {
|
|
2088
|
+
this.events.emit('toolCall', toolEvent);
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
// Update history with the exchange
|
|
2093
|
+
this.history.push(userMessage);
|
|
2094
|
+
this.history.push({
|
|
2095
|
+
role: 'assistant',
|
|
2096
|
+
content: assistantResponseContent
|
|
2097
|
+
});
|
|
2098
|
+
|
|
2099
|
+
// Store conversation history
|
|
2100
|
+
// TODO: storeConversationHistory is not yet implemented for Codex
|
|
2101
|
+
// await this.storeConversationHistory(this.history, oldHistoryLength);
|
|
2102
|
+
|
|
2103
|
+
// Emit completion hook
|
|
2104
|
+
await this.hooks.emit(HOOK_TYPES.COMPLETION, {
|
|
2105
|
+
sessionId: this.sessionId,
|
|
2106
|
+
prompt: message,
|
|
2107
|
+
response: assistantResponseContent
|
|
2108
|
+
});
|
|
2109
|
+
|
|
2110
|
+
return assistantResponseContent;
|
|
2111
|
+
}
|
|
2112
|
+
} catch (error) {
|
|
2113
|
+
if (this.debug) {
|
|
2114
|
+
console.error('[DEBUG] Codex error:', error);
|
|
2115
|
+
}
|
|
2116
|
+
throw error;
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
|
|
1535
2120
|
if (this.debug) {
|
|
1536
2121
|
console.log(`[DEBUG] Starting agentic flow for question: ${message.substring(0, 100)}...`);
|
|
1537
2122
|
if (options.schema) {
|
|
@@ -1539,7 +2124,7 @@ When troubleshooting:
|
|
|
1539
2124
|
}
|
|
1540
2125
|
}
|
|
1541
2126
|
|
|
1542
|
-
// Tool iteration loop
|
|
2127
|
+
// Tool iteration loop (only for non-CLI engines like Vercel/Anthropic/OpenAI)
|
|
1543
2128
|
while (currentIteration < maxIterations && !completionAttempted) {
|
|
1544
2129
|
currentIteration++;
|
|
1545
2130
|
if (this.cancelled) throw new Error('Request was cancelled by the user');
|
|
@@ -1612,7 +2197,7 @@ When troubleshooting:
|
|
|
1612
2197
|
const messagesForAI = this.prepareMessagesWithImages(currentMessages);
|
|
1613
2198
|
|
|
1614
2199
|
const result = await this.streamTextWithRetryAndFallback({
|
|
1615
|
-
model: this.provider(this.model),
|
|
2200
|
+
model: this.provider ? this.provider(this.model) : this.model,
|
|
1616
2201
|
messages: messagesForAI,
|
|
1617
2202
|
maxTokens: maxResponseTokens,
|
|
1618
2203
|
temperature: 0.3,
|