@probelabs/probe 0.6.0-rc167 → 0.6.0-rc169

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.
@@ -11,6 +11,8 @@ export interface ProbeAgentOptions {
11
11
  sessionId?: string;
12
12
  /** Custom system prompt to replace the default system message */
13
13
  customPrompt?: string;
14
+ /** Alias for customPrompt. More intuitive naming for system prompts. */
15
+ systemPrompt?: string;
14
16
  /** Predefined prompt type (persona) */
15
17
  promptType?: 'code-explorer' | 'engineer' | 'code-review' | 'support' | 'architect';
16
18
  /** Allow the use of the 'implement' tool for code editing */
@@ -35,6 +37,10 @@ export interface ProbeAgentOptions {
35
37
  mcpConfig?: any;
36
38
  /** @deprecated Use mcpConfig instead */
37
39
  mcpServers?: any[];
40
+ /** List of allowed tool names. Use ['*'] for all tools (default), [] or null for no tools (raw AI mode), or specific tool names like ['search', 'query', 'extract']. Supports exclusion with '!' prefix (e.g., ['*', '!bash']). */
41
+ allowedTools?: string[] | null;
42
+ /** Convenience flag to disable all tools (equivalent to allowedTools: []). Takes precedence over allowedTools if set. */
43
+ disableTools?: boolean;
38
44
  /** Retry configuration for handling transient API failures */
39
45
  retry?: RetryOptions;
40
46
  /** Fallback configuration for multi-provider support */
@@ -227,4 +233,4 @@ export interface ProbeAgentEvents {
227
233
  }
228
234
 
229
235
  // Default export
230
- export { ProbeAgent as default };
236
+ export { ProbeAgent as default };
@@ -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;
@@ -304,12 +307,14 @@ export class ProbeAgent {
304
307
  * This method initializes MCP and merges MCP tools into the tool list, and loads history from storage
305
308
  */
306
309
  async initialize() {
307
- // Check if we need to auto-detect claude-code provider
310
+ // Check if we need to auto-detect claude-code or codex provider
308
311
  // This happens when no API keys are set and no provider is specified
309
- if (!this.provider && !this.clientApiProvider && this.apiType !== 'claude-code') {
312
+ if (!this.provider && !this.clientApiProvider && this.apiType !== 'claude-code' && this.apiType !== 'codex') {
310
313
  // Check if initializeModel marked as uninitialized (no API keys)
311
314
  if (this.apiType === 'uninitialized') {
312
315
  const claudeAvailable = await this.isClaudeCommandAvailable();
316
+ const codexAvailable = await this.isCodexCommandAvailable();
317
+
313
318
  if (claudeAvailable) {
314
319
  if (this.debug) {
315
320
  console.log('[DEBUG] No API keys found, but claude command detected');
@@ -320,11 +325,22 @@ export class ProbeAgent {
320
325
  this.provider = null;
321
326
  this.model = this.clientApiModel || 'claude-3-5-sonnet-20241022';
322
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';
323
338
  } 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' +
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' +
326
341
  '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');
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');
328
344
  }
329
345
  }
330
346
  }
@@ -404,14 +420,20 @@ export class ProbeAgent {
404
420
  * Initialize tools with configuration
405
421
  */
406
422
  initializeTools() {
423
+ const isToolAllowed = (toolName) => this.allowedTools.isEnabled(toolName);
424
+
407
425
  const configOptions = {
408
426
  sessionId: this.sessionId,
409
427
  debug: this.debug,
410
428
  defaultPath: this.allowedFolders.length > 0 ? this.allowedFolders[0] : process.cwd(),
411
429
  allowedFolders: this.allowedFolders,
412
430
  outline: this.outline,
431
+ allowEdit: this.allowEdit,
432
+ enableDelegate: this.enableDelegate,
413
433
  enableBash: this.enableBash,
414
- bashConfig: this.bashConfig
434
+ bashConfig: this.bashConfig,
435
+ allowedTools: this.allowedTools,
436
+ isToolAllowed
415
437
  };
416
438
 
417
439
  // Create base tools
@@ -420,15 +442,33 @@ export class ProbeAgent {
420
442
  // Create wrapped tools with event emission
421
443
  const wrappedTools = createWrappedTools(baseTools);
422
444
 
423
- // Store tool instances for execution
424
- this.toolImplementations = {
425
- search: wrappedTools.searchToolInstance,
426
- query: wrappedTools.queryToolInstance,
427
- extract: wrappedTools.extractToolInstance,
428
- delegate: wrappedTools.delegateToolInstance,
429
- listFiles: listFilesToolInstance,
430
- searchFiles: searchFilesToolInstance,
431
- readImage: {
445
+ // Store tool instances for execution (respect allowedTools + feature flags)
446
+ this.toolImplementations = {};
447
+
448
+ if (wrappedTools.searchToolInstance && isToolAllowed('search')) {
449
+ this.toolImplementations.search = wrappedTools.searchToolInstance;
450
+ }
451
+ if (wrappedTools.queryToolInstance && isToolAllowed('query')) {
452
+ this.toolImplementations.query = wrappedTools.queryToolInstance;
453
+ }
454
+ if (wrappedTools.extractToolInstance && isToolAllowed('extract')) {
455
+ this.toolImplementations.extract = wrappedTools.extractToolInstance;
456
+ }
457
+ if (this.enableDelegate && wrappedTools.delegateToolInstance && isToolAllowed('delegate')) {
458
+ this.toolImplementations.delegate = wrappedTools.delegateToolInstance;
459
+ }
460
+
461
+ // File browsing tools
462
+ if (isToolAllowed('listFiles')) {
463
+ this.toolImplementations.listFiles = listFilesToolInstance;
464
+ }
465
+ if (isToolAllowed('searchFiles')) {
466
+ this.toolImplementations.searchFiles = searchFilesToolInstance;
467
+ }
468
+
469
+ // Image loading tool
470
+ if (isToolAllowed('readImage')) {
471
+ this.toolImplementations.readImage = {
432
472
  execute: async (params) => {
433
473
  const imagePath = params.path;
434
474
  if (!imagePath) {
@@ -444,20 +484,20 @@ export class ProbeAgent {
444
484
 
445
485
  return `Image loaded successfully: ${imagePath}. The image is now available for analysis in the conversation.`;
446
486
  }
447
- }
448
- };
487
+ };
488
+ }
449
489
 
450
- // Add bash tool if enabled
451
- if (this.enableBash && wrappedTools.bashToolInstance) {
490
+ // Add bash tool if enabled and allowed
491
+ if (this.enableBash && wrappedTools.bashToolInstance && isToolAllowed('bash')) {
452
492
  this.toolImplementations.bash = wrappedTools.bashToolInstance;
453
493
  }
454
494
 
455
- // Add edit and create tools if enabled
495
+ // Add edit and create tools if enabled and allowed
456
496
  if (this.allowEdit) {
457
- if (wrappedTools.editToolInstance) {
497
+ if (wrappedTools.editToolInstance && isToolAllowed('edit')) {
458
498
  this.toolImplementations.edit = wrappedTools.editToolInstance;
459
499
  }
460
- if (wrappedTools.createToolInstance) {
500
+ if (wrappedTools.createToolInstance && isToolAllowed('create')) {
461
501
  this.toolImplementations.create = wrappedTools.createToolInstance;
462
502
  }
463
503
  }
@@ -482,13 +522,34 @@ export class ProbeAgent {
482
522
 
483
523
  /**
484
524
  * Check if claude command is available on the system
525
+ * Uses execFile instead of exec to avoid shell injection risks
485
526
  * @returns {Promise<boolean>} True if claude command is available
486
527
  * @private
487
528
  */
488
529
  async isClaudeCommandAvailable() {
489
530
  try {
490
- const { execSync } = await import('child_process');
491
- execSync('claude --version', { stdio: 'ignore' });
531
+ const { execFile } = await import('child_process');
532
+ const { promisify } = await import('util');
533
+ const execFileAsync = promisify(execFile);
534
+ await execFileAsync('claude', ['--version'], { timeout: 5000 });
535
+ return true;
536
+ } catch (error) {
537
+ return false;
538
+ }
539
+ }
540
+
541
+ /**
542
+ * Check if codex command is available on the system
543
+ * Uses execFile instead of exec to avoid shell injection risks
544
+ * @returns {Promise<boolean>} True if codex command is available
545
+ * @private
546
+ */
547
+ async isCodexCommandAvailable() {
548
+ try {
549
+ const { execFile } = await import('child_process');
550
+ const { promisify } = await import('util');
551
+ const execFileAsync = promisify(execFile);
552
+ await execFileAsync('codex', ['--version'], { timeout: 5000 });
492
553
  return true;
493
554
  } catch (error) {
494
555
  return false;
@@ -521,6 +582,25 @@ export class ProbeAgent {
521
582
  return;
522
583
  }
523
584
 
585
+ // Skip API key requirement for Codex CLI (uses built-in access in Codex CLI)
586
+ if (this.clientApiProvider === 'codex' || process.env.USE_CODEX === 'true') {
587
+ // Codex CLI engine will be initialized lazily in getEngine()
588
+ // Set minimal defaults for compatibility
589
+ this.provider = null;
590
+ // Only set model if explicitly provided, otherwise let Codex use account default
591
+ this.model = modelName || null;
592
+ this.apiType = 'codex';
593
+ if (this.debug) {
594
+ console.log('[DEBUG] Codex CLI engine selected - will use built-in access if available');
595
+ if (this.model) {
596
+ console.log(`[DEBUG] Using model: ${this.model}`);
597
+ } else {
598
+ console.log('[DEBUG] Using Codex account default model');
599
+ }
600
+ }
601
+ return;
602
+ }
603
+
524
604
  // Get API keys from environment variables
525
605
  // Support both ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN (used by Z.AI)
526
606
  const anthropicApiKey = process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN;
@@ -724,6 +804,60 @@ export class ProbeAgent {
724
804
  }
725
805
  }
726
806
 
807
+ // Check if we should use Codex engine
808
+ if (this.clientApiProvider === 'codex' || process.env.USE_CODEX === 'true') {
809
+ try {
810
+ const engine = await this.getEngine();
811
+ if (engine && engine.query) {
812
+ // Convert Vercel AI SDK format to engine format
813
+ // Extract the ORIGINAL user message as the main prompt (skip any warning messages)
814
+ // Look for user messages that are NOT the warning message
815
+ const userMessages = options.messages.filter(m =>
816
+ m.role === 'user' &&
817
+ !m.content.includes('WARNING: You have reached the maximum tool iterations limit')
818
+ );
819
+ const lastUserMessage = userMessages[userMessages.length - 1];
820
+ const prompt = lastUserMessage ? lastUserMessage.content : '';
821
+
822
+ // Pass system message and other options
823
+ const engineOptions = {
824
+ maxTokens: options.maxTokens,
825
+ temperature: options.temperature,
826
+ messages: options.messages,
827
+ systemPrompt: options.messages.find(m => m.role === 'system')?.content
828
+ };
829
+
830
+ // Get the engine's query result (async generator)
831
+ const engineStream = engine.query(prompt, engineOptions);
832
+
833
+ // Create a text stream that extracts text from engine messages
834
+ async function* createTextStream() {
835
+ for await (const message of engineStream) {
836
+ if (message.type === 'text' && message.content) {
837
+ yield message.content;
838
+ } else if (typeof message === 'string') {
839
+ // If engine returns plain strings, pass them through
840
+ yield message;
841
+ }
842
+ // Ignore other message types for the text stream
843
+ }
844
+ }
845
+
846
+ // Wrap the engine result to match streamText interface
847
+ return {
848
+ textStream: createTextStream(),
849
+ usage: Promise.resolve({}), // Engine should handle its own usage tracking
850
+ // Add other streamText-compatible properties as needed
851
+ };
852
+ }
853
+ } catch (error) {
854
+ if (this.debug) {
855
+ console.log(`[DEBUG] Failed to use Codex engine, falling back to Vercel:`, error.message);
856
+ }
857
+ // Fall through to use Vercel engine as fallback
858
+ }
859
+ }
860
+
727
861
  // Initialize retry manager if not already created
728
862
  if (!this.retryManager) {
729
863
  this.retryManager = new RetryManager({
@@ -908,6 +1042,39 @@ export class ProbeAgent {
908
1042
  this.clientApiProvider = null;
909
1043
  }
910
1044
  }
1045
+
1046
+ // Try Codex CLI engine if requested
1047
+ if (this.clientApiProvider === 'codex' || process.env.USE_CODEX === 'true') {
1048
+ try {
1049
+ const { createCodexEngine } = await import('./engines/codex.js');
1050
+
1051
+ // For Codex CLI, use a cleaner system prompt without XML formatting
1052
+ // since it has native MCP support for tools
1053
+ const systemPrompt = this.customPrompt || this.getCodexNativeSystemPrompt();
1054
+
1055
+ this.engine = await createCodexEngine({
1056
+ agent: this, // Pass reference to ProbeAgent for tool access
1057
+ systemPrompt: systemPrompt,
1058
+ customPrompt: this.customPrompt,
1059
+ sessionId: this.options?.sessionId,
1060
+ debug: this.debug,
1061
+ allowedTools: this.allowedTools, // Pass tool filtering configuration
1062
+ model: this.model // Pass model name (e.g., gpt-4o, o3, etc.)
1063
+ });
1064
+ if (this.debug) {
1065
+ console.log('[DEBUG] Using Codex CLI engine with Probe tools');
1066
+ if (this.customPrompt) {
1067
+ console.log('[DEBUG] Using custom prompt/persona');
1068
+ }
1069
+ }
1070
+ return this.engine;
1071
+ } catch (error) {
1072
+ console.warn('[WARNING] Failed to load Codex CLI engine:', error.message);
1073
+ console.warn('[WARNING] Falling back to Vercel AI SDK');
1074
+ this.clientApiProvider = null;
1075
+ }
1076
+ }
1077
+
911
1078
  // Default to enhanced Vercel AI SDK (wraps existing logic)
912
1079
  const { createEnhancedVercelEngine } = await import('./engines/enhanced-vercel.js');
913
1080
  this.engine = createEnhancedVercelEngine(this);
@@ -917,6 +1084,35 @@ export class ProbeAgent {
917
1084
  return this.engine;
918
1085
  }
919
1086
 
1087
+ /**
1088
+ * Get session information including thread ID for resumability
1089
+ * @returns {Object} Session info with sessionId, threadId, messageCount
1090
+ */
1091
+ getSessionInfo() {
1092
+ if (this.engine && this.engine.getSession) {
1093
+ return this.engine.getSession();
1094
+ }
1095
+ return {
1096
+ id: this.sessionId,
1097
+ threadId: null,
1098
+ messageCount: 0
1099
+ };
1100
+ }
1101
+
1102
+ /**
1103
+ * Close the agent and clean up resources (e.g., MCP servers)
1104
+ * @returns {Promise<void>}
1105
+ */
1106
+ async close() {
1107
+ if (this.engine && this.engine.close) {
1108
+ await this.engine.close();
1109
+ }
1110
+ if (this.mcpBridge) {
1111
+ // Clean up MCP bridge if needed
1112
+ this.mcpBridge = null;
1113
+ }
1114
+ }
1115
+
920
1116
  /**
921
1117
  * Process assistant response content and detect/load image references
922
1118
  * @param {string} content - The assistant's response content
@@ -1311,11 +1507,64 @@ export class ProbeAgent {
1311
1507
  let systemPrompt = '';
1312
1508
 
1313
1509
  // 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
- }
1510
+ if (this.customPrompt) {
1511
+ systemPrompt += this.customPrompt + '\n\n';
1512
+ } else if (this.promptType && predefinedPrompts[this.promptType]) {
1513
+ systemPrompt += predefinedPrompts[this.promptType] + '\n\n';
1514
+ } else {
1515
+ // Use default code-explorer prompt
1516
+ systemPrompt += predefinedPrompts['code-explorer'] + '\n\n';
1517
+ }
1518
+
1519
+ // Add high-level instructions about when to use tools
1520
+ systemPrompt += `You have access to powerful code search and analysis tools through MCP:
1521
+ - search: Find code patterns using semantic search
1522
+ - extract: Extract specific code sections with context
1523
+ - query: Use AST patterns for structural code matching
1524
+ - listFiles: Browse directory contents
1525
+ - searchFiles: Find files by name patterns`;
1526
+
1527
+ if (this.enableBash) {
1528
+ systemPrompt += `\n- bash: Execute bash commands for system operations`;
1529
+ }
1530
+
1531
+ systemPrompt += `\n
1532
+ When exploring code:
1533
+ 1. Start with search to find relevant code patterns
1534
+ 2. Use extract to get detailed context when needed
1535
+ 3. Prefer focused, specific searches over broad queries
1536
+ 4. Combine multiple tools to build complete understanding`;
1537
+
1538
+ // Add workspace context
1539
+ if (this.allowedFolders && this.allowedFolders.length > 0) {
1540
+ systemPrompt += `\n\nWorkspace: ${this.allowedFolders.join(', ')}`;
1541
+ }
1542
+
1543
+ // Add repository structure if available
1544
+ if (this.fileList) {
1545
+ systemPrompt += `\n\n# Repository Structure\n`;
1546
+ systemPrompt += `You are working with a repository located at: ${this.allowedFolders[0]}\n\n`;
1547
+ systemPrompt += `Here's an overview of the repository structure (showing up to 100 most relevant files):\n\n`;
1548
+ systemPrompt += '```\n' + this.fileList + '\n```\n';
1549
+ }
1550
+
1551
+ return systemPrompt;
1552
+ }
1553
+
1554
+ /**
1555
+ * Get system prompt for Codex CLI (similar to Claude but optimized for Codex)
1556
+ */
1557
+ getCodexNativeSystemPrompt() {
1558
+ let systemPrompt = '';
1559
+
1560
+ // Add persona/role if configured
1561
+ if (this.customPrompt) {
1562
+ systemPrompt += this.customPrompt + '\n\n';
1563
+ } else if (this.promptType && predefinedPrompts[this.promptType]) {
1564
+ systemPrompt += predefinedPrompts[this.promptType] + '\n\n';
1565
+ } else {
1566
+ // Use default code-explorer prompt
1567
+ systemPrompt += predefinedPrompts['code-explorer'] + '\n\n';
1319
1568
  }
1320
1569
 
1321
1570
  // Add high-level instructions about when to use tools
@@ -1747,8 +1996,9 @@ When troubleshooting:
1747
1996
  const baseMaxIterations = this.maxIterations || MAX_TOOL_ITERATIONS;
1748
1997
  const maxIterations = options.schema ? baseMaxIterations + 4 : baseMaxIterations;
1749
1998
 
1750
- // Check if we're using Claude Code engine which handles its own agentic loop
1999
+ // Check if we're using CLI-based engines which handle their own agentic loop
1751
2000
  const isClaudeCode = this.clientApiProvider === 'claude-code' || process.env.USE_CLAUDE_CODE === 'true';
2001
+ const isCodex = this.clientApiProvider === 'codex' || process.env.USE_CODEX === 'true';
1752
2002
 
1753
2003
  if (isClaudeCode) {
1754
2004
  // For Claude Code, bypass the tool loop entirely - it handles its own internal dialogue
@@ -1820,6 +2070,77 @@ When troubleshooting:
1820
2070
  }
1821
2071
  }
1822
2072
 
2073
+ // Handle Codex engine (same pattern as Claude Code)
2074
+ if (isCodex) {
2075
+ // For Codex, bypass the tool loop entirely - it handles its own internal dialogue
2076
+ if (this.debug) {
2077
+ console.log(`[DEBUG] Using Codex engine - bypassing tool loop (black box mode)`);
2078
+ console.log(`[DEBUG] Sending question directly to Codex: ${message.substring(0, 100)}...`);
2079
+ }
2080
+
2081
+ // Send the message directly to Codex and collect the response
2082
+ try {
2083
+ const engine = await this.getEngine();
2084
+ if (engine && engine.query) {
2085
+ let assistantResponseContent = '';
2086
+ let toolBatch = null;
2087
+
2088
+ // Query Codex directly with the message and schema
2089
+ for await (const chunk of engine.query(message, options)) {
2090
+ if (chunk.type === 'text' && chunk.content) {
2091
+ assistantResponseContent += chunk.content;
2092
+ if (options.onStream) {
2093
+ options.onStream(chunk.content);
2094
+ }
2095
+ } else if (chunk.type === 'toolBatch' && chunk.tools) {
2096
+ // Store tool batch for processing after response
2097
+ toolBatch = chunk.tools;
2098
+ if (this.debug) {
2099
+ console.log(`[DEBUG] Received batch of ${chunk.tools.length} tool events from Codex`);
2100
+ }
2101
+ } else if (chunk.type === 'error') {
2102
+ throw chunk.error;
2103
+ }
2104
+ }
2105
+
2106
+ // Emit tool events after response is complete (batch mode)
2107
+ if (toolBatch && toolBatch.length > 0 && this.events) {
2108
+ if (this.debug) {
2109
+ console.log(`[DEBUG] Emitting ${toolBatch.length} tool events from Codex batch`);
2110
+ }
2111
+ for (const toolEvent of toolBatch) {
2112
+ this.events.emit('toolCall', toolEvent);
2113
+ }
2114
+ }
2115
+
2116
+ // Update history with the exchange
2117
+ this.history.push(userMessage);
2118
+ this.history.push({
2119
+ role: 'assistant',
2120
+ content: assistantResponseContent
2121
+ });
2122
+
2123
+ // Store conversation history
2124
+ // TODO: storeConversationHistory is not yet implemented for Codex
2125
+ // await this.storeConversationHistory(this.history, oldHistoryLength);
2126
+
2127
+ // Emit completion hook
2128
+ await this.hooks.emit(HOOK_TYPES.COMPLETION, {
2129
+ sessionId: this.sessionId,
2130
+ prompt: message,
2131
+ response: assistantResponseContent
2132
+ });
2133
+
2134
+ return assistantResponseContent;
2135
+ }
2136
+ } catch (error) {
2137
+ if (this.debug) {
2138
+ console.error('[DEBUG] Codex error:', error);
2139
+ }
2140
+ throw error;
2141
+ }
2142
+ }
2143
+
1823
2144
  if (this.debug) {
1824
2145
  console.log(`[DEBUG] Starting agentic flow for question: ${message.substring(0, 100)}...`);
1825
2146
  if (options.schema) {
@@ -1827,7 +2148,7 @@ When troubleshooting:
1827
2148
  }
1828
2149
  }
1829
2150
 
1830
- // Tool iteration loop (only for non-Claude Code engines)
2151
+ // Tool iteration loop (only for non-CLI engines like Vercel/Anthropic/OpenAI)
1831
2152
  while (currentIteration < maxIterations && !completionAttempted) {
1832
2153
  currentIteration++;
1833
2154
  if (this.cancelled) throw new Error('Request was cancelled by the user');