@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.
@@ -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
- this.customPrompt = options.customPrompt || null;
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
- 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.');
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,