@probelabs/probe 0.6.0-rc100

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.
Files changed (115) hide show
  1. package/README.md +583 -0
  2. package/bin/.gitkeep +0 -0
  3. package/bin/probe +158 -0
  4. package/bin/probe-binary +0 -0
  5. package/build/agent/ProbeAgent.d.ts +199 -0
  6. package/build/agent/ProbeAgent.js +1486 -0
  7. package/build/agent/acp/README.md +347 -0
  8. package/build/agent/acp/connection.js +237 -0
  9. package/build/agent/acp/connection.test.js +311 -0
  10. package/build/agent/acp/examples/simple-client.js +212 -0
  11. package/build/agent/acp/examples/tool-lifecycle.js +230 -0
  12. package/build/agent/acp/final-test.js +173 -0
  13. package/build/agent/acp/index.js +5 -0
  14. package/build/agent/acp/integration.test.js +385 -0
  15. package/build/agent/acp/manual-test.js +410 -0
  16. package/build/agent/acp/protocol-test.js +190 -0
  17. package/build/agent/acp/server.js +448 -0
  18. package/build/agent/acp/server.test.js +371 -0
  19. package/build/agent/acp/test-runner.js +216 -0
  20. package/build/agent/acp/test-utils/README.md +315 -0
  21. package/build/agent/acp/test-utils/acp-tester.js +484 -0
  22. package/build/agent/acp/test-utils/mock-acp-client.js +434 -0
  23. package/build/agent/acp/tools.js +368 -0
  24. package/build/agent/acp/tools.test.js +334 -0
  25. package/build/agent/acp/types.js +218 -0
  26. package/build/agent/acp/types.test.js +327 -0
  27. package/build/agent/appTracer.js +360 -0
  28. package/build/agent/fileSpanExporter.js +169 -0
  29. package/build/agent/index.js +7426 -0
  30. package/build/agent/mcp/client.js +338 -0
  31. package/build/agent/mcp/config.js +313 -0
  32. package/build/agent/mcp/index.js +64 -0
  33. package/build/agent/mcp/xmlBridge.js +371 -0
  34. package/build/agent/mockProvider.js +53 -0
  35. package/build/agent/probeTool.js +257 -0
  36. package/build/agent/schemaUtils.js +1726 -0
  37. package/build/agent/simpleTelemetry.js +267 -0
  38. package/build/agent/telemetry.js +225 -0
  39. package/build/agent/tokenCounter.js +395 -0
  40. package/build/agent/tools.js +163 -0
  41. package/build/cli.js +49 -0
  42. package/build/delegate.js +267 -0
  43. package/build/directory-resolver.js +237 -0
  44. package/build/downloader.js +750 -0
  45. package/build/extract.js +149 -0
  46. package/build/index.js +70 -0
  47. package/build/mcp/index.js +514 -0
  48. package/build/mcp/index.ts +608 -0
  49. package/build/query.js +116 -0
  50. package/build/search.js +247 -0
  51. package/build/tools/common.js +410 -0
  52. package/build/tools/index.js +40 -0
  53. package/build/tools/langchain.js +88 -0
  54. package/build/tools/system-message.js +121 -0
  55. package/build/tools/vercel.js +271 -0
  56. package/build/utils/file-lister.js +193 -0
  57. package/build/utils.js +128 -0
  58. package/cjs/agent/ProbeAgent.cjs +5829 -0
  59. package/cjs/index.cjs +6217 -0
  60. package/cjs/package.json +3 -0
  61. package/index.d.ts +401 -0
  62. package/package.json +114 -0
  63. package/scripts/postinstall.js +172 -0
  64. package/src/agent/ProbeAgent.d.ts +199 -0
  65. package/src/agent/ProbeAgent.js +1486 -0
  66. package/src/agent/acp/README.md +347 -0
  67. package/src/agent/acp/connection.js +237 -0
  68. package/src/agent/acp/connection.test.js +311 -0
  69. package/src/agent/acp/examples/simple-client.js +212 -0
  70. package/src/agent/acp/examples/tool-lifecycle.js +230 -0
  71. package/src/agent/acp/final-test.js +173 -0
  72. package/src/agent/acp/index.js +5 -0
  73. package/src/agent/acp/integration.test.js +385 -0
  74. package/src/agent/acp/manual-test.js +410 -0
  75. package/src/agent/acp/protocol-test.js +190 -0
  76. package/src/agent/acp/server.js +448 -0
  77. package/src/agent/acp/server.test.js +371 -0
  78. package/src/agent/acp/test-runner.js +216 -0
  79. package/src/agent/acp/test-utils/README.md +315 -0
  80. package/src/agent/acp/test-utils/acp-tester.js +484 -0
  81. package/src/agent/acp/test-utils/mock-acp-client.js +434 -0
  82. package/src/agent/acp/tools.js +368 -0
  83. package/src/agent/acp/tools.test.js +334 -0
  84. package/src/agent/acp/types.js +218 -0
  85. package/src/agent/acp/types.test.js +327 -0
  86. package/src/agent/appTracer.js +360 -0
  87. package/src/agent/fileSpanExporter.js +169 -0
  88. package/src/agent/index.js +813 -0
  89. package/src/agent/mcp/client.js +338 -0
  90. package/src/agent/mcp/config.js +313 -0
  91. package/src/agent/mcp/index.js +64 -0
  92. package/src/agent/mcp/xmlBridge.js +371 -0
  93. package/src/agent/mockProvider.js +53 -0
  94. package/src/agent/probeTool.js +257 -0
  95. package/src/agent/schemaUtils.js +1726 -0
  96. package/src/agent/simpleTelemetry.js +267 -0
  97. package/src/agent/telemetry.js +225 -0
  98. package/src/agent/tokenCounter.js +395 -0
  99. package/src/agent/tools.js +163 -0
  100. package/src/cli.js +49 -0
  101. package/src/delegate.js +267 -0
  102. package/src/directory-resolver.js +237 -0
  103. package/src/downloader.js +750 -0
  104. package/src/extract.js +149 -0
  105. package/src/index.js +70 -0
  106. package/src/mcp/index.ts +608 -0
  107. package/src/query.js +116 -0
  108. package/src/search.js +247 -0
  109. package/src/tools/common.js +410 -0
  110. package/src/tools/index.js +40 -0
  111. package/src/tools/langchain.js +88 -0
  112. package/src/tools/system-message.js +121 -0
  113. package/src/tools/vercel.js +271 -0
  114. package/src/utils/file-lister.js +193 -0
  115. package/src/utils.js +128 -0
@@ -0,0 +1,1486 @@
1
+ // Core ProbeAgent class adapted from examples/chat/probeChat.js
2
+ import { createAnthropic } from '@ai-sdk/anthropic';
3
+ import { createOpenAI } from '@ai-sdk/openai';
4
+ import { createGoogleGenerativeAI } from '@ai-sdk/google';
5
+ import { streamText } from 'ai';
6
+ import { randomUUID } from 'crypto';
7
+ import { EventEmitter } from 'events';
8
+ import { TokenCounter } from './tokenCounter.js';
9
+ import {
10
+ createTools,
11
+ searchToolDefinition,
12
+ queryToolDefinition,
13
+ extractToolDefinition,
14
+ listFilesToolDefinition,
15
+ searchFilesToolDefinition,
16
+ attemptCompletionToolDefinition,
17
+ implementToolDefinition,
18
+ attemptCompletionSchema,
19
+ parseXmlToolCallWithThinking
20
+ } from './tools.js';
21
+ import { createMessagePreview } from '../tools/common.js';
22
+ import {
23
+ createWrappedTools,
24
+ listFilesToolInstance,
25
+ searchFilesToolInstance,
26
+ clearToolExecutionData
27
+ } from './probeTool.js';
28
+ import { createMockProvider } from './mockProvider.js';
29
+ import { listFilesByLevel } from '../index.js';
30
+ import {
31
+ cleanSchemaResponse,
32
+ isJsonSchema,
33
+ validateJsonResponse,
34
+ createJsonCorrectionPrompt,
35
+ isJsonSchemaDefinition,
36
+ createSchemaDefinitionCorrectionPrompt,
37
+ validateAndFixMermaidResponse
38
+ } from './schemaUtils.js';
39
+ import {
40
+ MCPXmlBridge,
41
+ parseHybridXmlToolCall,
42
+ loadMCPConfigurationFromPath
43
+ } from './mcp/index.js';
44
+
45
+ // Maximum tool iterations to prevent infinite loops - configurable via MAX_TOOL_ITERATIONS env var
46
+ const MAX_TOOL_ITERATIONS = parseInt(process.env.MAX_TOOL_ITERATIONS || '30', 10);
47
+ const MAX_HISTORY_MESSAGES = 100;
48
+
49
+ /**
50
+ * ProbeAgent class to handle AI interactions with code search capabilities
51
+ */
52
+ export class ProbeAgent {
53
+ /**
54
+ * Create a new ProbeAgent instance
55
+ * @param {Object} options - Configuration options
56
+ * @param {string} [options.sessionId] - Optional session ID
57
+ * @param {string} [options.customPrompt] - Custom prompt to replace the default system message
58
+ * @param {string} [options.promptType] - Predefined prompt type (architect, code-review, support)
59
+ * @param {boolean} [options.allowEdit=false] - Allow the use of the 'implement' tool
60
+ * @param {string} [options.path] - Search directory path
61
+ * @param {string} [options.provider] - Force specific AI provider
62
+ * @param {string} [options.model] - Override model name
63
+ * @param {boolean} [options.debug] - Enable debug mode
64
+ * @param {boolean} [options.outline] - Enable outline-xml format for search results
65
+ * @param {number} [options.maxResponseTokens] - Maximum tokens for AI responses
66
+ * @param {boolean} [options.disableMermaidValidation=false] - Disable automatic mermaid diagram validation and fixing
67
+ * @param {boolean} [options.enableMcp=false] - Enable MCP tool integration
68
+ * @param {string} [options.mcpConfigPath] - Path to MCP configuration file
69
+ * @param {Object} [options.mcpConfig] - MCP configuration object (overrides mcpConfigPath)
70
+ * @param {Array} [options.mcpServers] - Deprecated, use mcpConfig instead
71
+ */
72
+ constructor(options = {}) {
73
+ // Basic configuration
74
+ this.sessionId = options.sessionId || randomUUID();
75
+ this.customPrompt = options.customPrompt || null;
76
+ this.promptType = options.promptType || 'code-explorer';
77
+ this.allowEdit = !!options.allowEdit;
78
+ this.debug = options.debug || process.env.DEBUG === '1';
79
+ this.cancelled = false;
80
+ this.tracer = options.tracer || null;
81
+ this.outline = !!options.outline;
82
+ this.maxResponseTokens = options.maxResponseTokens || parseInt(process.env.MAX_RESPONSE_TOKENS || '0', 10) || null;
83
+ this.disableMermaidValidation = !!options.disableMermaidValidation;
84
+
85
+ // Search configuration
86
+ this.allowedFolders = options.path ? [options.path] : [process.cwd()];
87
+
88
+ // API configuration
89
+ this.clientApiProvider = options.provider || null;
90
+ this.clientApiKey = null; // Will be set from environment
91
+ this.clientApiUrl = null;
92
+
93
+ // Initialize token counter
94
+ this.tokenCounter = new TokenCounter();
95
+
96
+ if (this.debug) {
97
+ console.log(`[DEBUG] Generated session ID for agent: ${this.sessionId}`);
98
+ console.log(`[DEBUG] Maximum tool iterations configured: ${MAX_TOOL_ITERATIONS}`);
99
+ console.log(`[DEBUG] Allow Edit (implement tool): ${this.allowEdit}`);
100
+ }
101
+
102
+ // Initialize tools
103
+ this.initializeTools();
104
+
105
+ // Initialize chat history
106
+ this.history = [];
107
+
108
+ // Initialize event emitter for tool execution updates
109
+ this.events = new EventEmitter();
110
+
111
+ // MCP configuration
112
+ this.enableMcp = !!options.enableMcp || process.env.ENABLE_MCP === '1';
113
+ this.mcpConfigPath = options.mcpConfigPath || null;
114
+ this.mcpConfig = options.mcpConfig || null;
115
+ this.mcpServers = options.mcpServers || null; // Deprecated, keep for backward compatibility
116
+ this.mcpBridge = null;
117
+
118
+ // Initialize the AI model
119
+ this.initializeModel();
120
+
121
+ // Initialize MCP if enabled
122
+ if (this.enableMcp) {
123
+ this.initializeMCP().catch(error => {
124
+ console.error('[MCP] Failed to initialize MCP:', error);
125
+ this.mcpBridge = null;
126
+ });
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Initialize tools with configuration
132
+ */
133
+ initializeTools() {
134
+ const configOptions = {
135
+ sessionId: this.sessionId,
136
+ debug: this.debug,
137
+ defaultPath: this.allowedFolders.length > 0 ? this.allowedFolders[0] : process.cwd(),
138
+ allowedFolders: this.allowedFolders,
139
+ outline: this.outline
140
+ };
141
+
142
+ // Create base tools
143
+ const baseTools = createTools(configOptions);
144
+
145
+ // Create wrapped tools with event emission
146
+ const wrappedTools = createWrappedTools(baseTools);
147
+
148
+ // Store tool instances for execution
149
+ this.toolImplementations = {
150
+ search: wrappedTools.searchToolInstance,
151
+ query: wrappedTools.queryToolInstance,
152
+ extract: wrappedTools.extractToolInstance,
153
+ delegate: wrappedTools.delegateToolInstance,
154
+ listFiles: listFilesToolInstance,
155
+ searchFiles: searchFilesToolInstance,
156
+ };
157
+
158
+ // Store wrapped tools for ACP system
159
+ this.wrappedTools = wrappedTools;
160
+ }
161
+
162
+ /**
163
+ * Initialize the AI model based on available API keys and forced provider setting
164
+ */
165
+ initializeModel() {
166
+ // Check if we're in test mode and should use mock provider
167
+ if (process.env.NODE_ENV === 'test' || process.env.USE_MOCK_AI === 'true') {
168
+ this.initializeMockModel();
169
+ return;
170
+ }
171
+
172
+ // Get API keys from environment variables
173
+ const anthropicApiKey = process.env.ANTHROPIC_API_KEY;
174
+ const openaiApiKey = process.env.OPENAI_API_KEY;
175
+ const googleApiKey = process.env.GOOGLE_API_KEY;
176
+
177
+ // Get custom API URLs if provided
178
+ const llmBaseUrl = process.env.LLM_BASE_URL;
179
+ const anthropicApiUrl = process.env.ANTHROPIC_API_URL || llmBaseUrl;
180
+ const openaiApiUrl = process.env.OPENAI_API_URL || llmBaseUrl;
181
+ const googleApiUrl = process.env.GOOGLE_API_URL || llmBaseUrl;
182
+
183
+ // Get model override if provided
184
+ const modelName = process.env.MODEL_NAME;
185
+
186
+ // Use client-forced provider or environment variable
187
+ const forceProvider = this.clientApiProvider || (process.env.FORCE_PROVIDER ? process.env.FORCE_PROVIDER.toLowerCase() : null);
188
+
189
+ if (this.debug) {
190
+ console.log(`[DEBUG] Available API keys: Anthropic=${!!anthropicApiKey}, OpenAI=${!!openaiApiKey}, Google=${!!googleApiKey}`);
191
+ console.log(`[DEBUG] Force provider: ${forceProvider || '(not set)'}`);
192
+ if (modelName) console.log(`[DEBUG] Model override: ${modelName}`);
193
+ }
194
+
195
+ // Check if a specific provider is forced
196
+ if (forceProvider) {
197
+ if (forceProvider === 'anthropic' && anthropicApiKey) {
198
+ this.initializeAnthropicModel(anthropicApiKey, anthropicApiUrl, modelName);
199
+ return;
200
+ } else if (forceProvider === 'openai' && openaiApiKey) {
201
+ this.initializeOpenAIModel(openaiApiKey, openaiApiUrl, modelName);
202
+ return;
203
+ } else if (forceProvider === 'google' && googleApiKey) {
204
+ this.initializeGoogleModel(googleApiKey, googleApiUrl, modelName);
205
+ return;
206
+ }
207
+ console.warn(`WARNING: Forced provider "${forceProvider}" selected but required API key is missing or invalid! Falling back to auto-detection.`);
208
+ }
209
+
210
+ // If no provider is forced or forced provider failed, use the first available API key
211
+ if (anthropicApiKey) {
212
+ this.initializeAnthropicModel(anthropicApiKey, anthropicApiUrl, modelName);
213
+ } else if (openaiApiKey) {
214
+ this.initializeOpenAIModel(openaiApiKey, openaiApiUrl, modelName);
215
+ } else if (googleApiKey) {
216
+ this.initializeGoogleModel(googleApiKey, googleApiUrl, modelName);
217
+ } else {
218
+ throw new Error('No API key provided. Please set ANTHROPIC_API_KEY, OPENAI_API_KEY, or GOOGLE_API_KEY environment variable.');
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Initialize Anthropic model
224
+ */
225
+ initializeAnthropicModel(apiKey, apiUrl, modelName) {
226
+ this.provider = createAnthropic({
227
+ apiKey: apiKey,
228
+ ...(apiUrl && { baseURL: apiUrl }),
229
+ });
230
+ this.model = modelName || 'claude-opus-4-1-20250805';
231
+ this.apiType = 'anthropic';
232
+
233
+ if (this.debug) {
234
+ console.log(`Using Anthropic API with model: ${this.model}${apiUrl ? ` (URL: ${apiUrl})` : ''}`);
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Initialize OpenAI model
240
+ */
241
+ initializeOpenAIModel(apiKey, apiUrl, modelName) {
242
+ this.provider = createOpenAI({
243
+ compatibility: 'strict',
244
+ apiKey: apiKey,
245
+ ...(apiUrl && { baseURL: apiUrl }),
246
+ });
247
+ this.model = modelName || 'gpt-5-thinking';
248
+ this.apiType = 'openai';
249
+
250
+ if (this.debug) {
251
+ console.log(`Using OpenAI API with model: ${this.model}${apiUrl ? ` (URL: ${apiUrl})` : ''}`);
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Initialize Google model
257
+ */
258
+ initializeGoogleModel(apiKey, apiUrl, modelName) {
259
+ this.provider = createGoogleGenerativeAI({
260
+ apiKey: apiKey,
261
+ ...(apiUrl && { baseURL: apiUrl }),
262
+ });
263
+ this.model = modelName || 'gemini-2.5-pro';
264
+ this.apiType = 'google';
265
+
266
+ if (this.debug) {
267
+ console.log(`Using Google API with model: ${this.model}${apiUrl ? ` (URL: ${apiUrl})` : ''}`);
268
+ }
269
+ }
270
+
271
+ /**
272
+ * Initialize mock model for testing
273
+ */
274
+ initializeMockModel(modelName) {
275
+ this.provider = createMockProvider();
276
+ this.model = modelName || 'mock-model';
277
+ this.apiType = 'mock';
278
+
279
+ if (this.debug) {
280
+ console.log(`Using Mock API with model: ${this.model}`);
281
+ }
282
+ }
283
+
284
+ /**
285
+ * Initialize MCP bridge and load tools
286
+ */
287
+ async initializeMCP() {
288
+ if (!this.enableMcp) return;
289
+
290
+ try {
291
+ let mcpConfig = null;
292
+
293
+ // Priority order: mcpConfig > mcpConfigPath > mcpServers (deprecated) > auto-discovery
294
+ if (this.mcpConfig) {
295
+ // Direct config object provided (SDK usage)
296
+ mcpConfig = this.mcpConfig;
297
+ if (this.debug) {
298
+ console.log('[DEBUG] Using provided MCP config object');
299
+ }
300
+ } else if (this.mcpConfigPath) {
301
+ // Explicit config path provided
302
+ try {
303
+ mcpConfig = loadMCPConfigurationFromPath(this.mcpConfigPath);
304
+ if (this.debug) {
305
+ console.log(`[DEBUG] Loaded MCP config from: ${this.mcpConfigPath}`);
306
+ }
307
+ } catch (error) {
308
+ throw new Error(`Failed to load MCP config from ${this.mcpConfigPath}: ${error.message}`);
309
+ }
310
+ } else if (this.mcpServers) {
311
+ // Backward compatibility: convert old mcpServers format
312
+ mcpConfig = { mcpServers: this.mcpServers };
313
+ if (this.debug) {
314
+ console.warn('[DEBUG] Using deprecated mcpServers option. Consider using mcpConfig instead.');
315
+ }
316
+ }
317
+ // Note: auto-discovery fallback is removed - user must explicitly provide config
318
+
319
+ // Initialize the MCP XML bridge
320
+ this.mcpBridge = new MCPXmlBridge({ debug: this.debug });
321
+ await this.mcpBridge.initialize(mcpConfig);
322
+
323
+ const mcpToolCount = this.mcpBridge.getToolNames().length;
324
+ if (mcpToolCount > 0) {
325
+ if (this.debug) {
326
+ console.log(`[DEBUG] Loaded ${mcpToolCount} MCP tools`);
327
+ }
328
+ } else {
329
+ // For backward compatibility: if no tools were loaded, set bridge to null
330
+ // This maintains the behavior expected by existing tests
331
+ if (this.debug) {
332
+ console.log('[DEBUG] No MCP tools loaded, setting bridge to null');
333
+ }
334
+ this.mcpBridge = null;
335
+ }
336
+ } catch (error) {
337
+ console.error('[MCP] Error initializing MCP:', error);
338
+ this.mcpBridge = null;
339
+ }
340
+ }
341
+
342
+ /**
343
+ * Get the system message with instructions for the AI (XML Tool Format)
344
+ */
345
+ async getSystemMessage() {
346
+ // Build tool definitions
347
+ let toolDefinitions = `
348
+ ${searchToolDefinition}
349
+ ${queryToolDefinition}
350
+ ${extractToolDefinition}
351
+ ${listFilesToolDefinition}
352
+ ${searchFilesToolDefinition}
353
+ ${attemptCompletionToolDefinition}
354
+ `;
355
+ if (this.allowEdit) {
356
+ toolDefinitions += `${implementToolDefinition}\n`;
357
+ }
358
+
359
+ // Build XML tool guidelines
360
+ let xmlToolGuidelines = `
361
+ # Tool Use Formatting
362
+
363
+ Tool use MUST be formatted using XML-style tags. Each tool call requires BOTH opening and closing tags with the exact tool name. Each parameter is similarly enclosed within its own set of opening and closing tags. You MUST use exactly ONE tool call per message until you are ready to complete the task.
364
+
365
+ **CRITICAL: Every XML tag MUST have both opening <tag> and closing </tag> parts.**
366
+
367
+ Structure (note the closing tags):
368
+ <tool_name>
369
+ <parameter1_name>value1</parameter1_name>
370
+ <parameter2_name>value2</parameter2_name>
371
+ ...
372
+ </tool_name>
373
+
374
+ Examples:
375
+ <search>
376
+ <query>error handling</query>
377
+ <path>src/search</path>
378
+ </search>
379
+
380
+ <extract>
381
+ <path>src/config.js</path>
382
+ <start_line>15</start_line>
383
+ <end_line>25</end_line>
384
+ </extract>
385
+
386
+ <attempt_completion>
387
+ <result>The configuration is loaded from src/config.js lines 15-25 which contains the database settings.</result>
388
+ </attempt_completion>
389
+
390
+ # Special Case: Quick Completion
391
+ If your previous response was already correct and complete, you may respond with just:
392
+ <attempt_complete>
393
+ This signals to use your previous response as the final answer without repeating content.
394
+
395
+ # Thinking Process
396
+
397
+ Before using a tool, analyze the situation within <thinking></thinking> tags. This helps you organize your thoughts and make better decisions.
398
+
399
+ Example:
400
+ <thinking>
401
+ I need to find code related to error handling in the search module. The most appropriate tool for this is the search tool, which requires a query parameter and a path parameter. I have both the query ("error handling") and the path ("src/search"), so I can proceed with the search.
402
+ </thinking>
403
+
404
+ # Tool Use Guidelines
405
+
406
+ 1. Think step-by-step about how to achieve the user's goal.
407
+ 2. Use <thinking></thinking> tags to analyze the situation and determine the appropriate tool.
408
+ 3. Choose **one** tool that helps achieve the current step.
409
+ 4. Format the tool call using the specified XML format with BOTH opening and closing tags. Ensure all required parameters are included.
410
+ 5. **You MUST respond with exactly one tool call in the specified XML format in each turn.**
411
+ 6. Wait for the tool execution result, which will be provided in the next message (within a <tool_result> block).
412
+ 7. Analyze the tool result and decide the next step. If more tool calls are needed, repeat steps 2-6.
413
+ 8. If the task is fully complete and all previous steps were successful, use the \`<attempt_completion>\` tool to provide the final answer. This is the ONLY way to finish the task.
414
+ 9. If you cannot proceed (e.g., missing information, invalid request), explain the issue clearly before using \`<attempt_completion>\` with an appropriate message in the \`<result>\` tag.
415
+ 10. If your previous response was already correct and complete, you may use \`<attempt_complete>\` as a shorthand.
416
+
417
+ Available Tools:
418
+ - search: Search code using keyword queries.
419
+ - query: Search code using structural AST patterns.
420
+ - extract: Extract specific code blocks or lines from files.
421
+ - listFiles: List files and directories in a specified location.
422
+ - searchFiles: Find files matching a glob pattern with recursive search capability.
423
+ ${this.allowEdit ? '- implement: Implement a feature or fix a bug using aider.\n' : ''}
424
+ - attempt_completion: Finalize the task and provide the result to the user.
425
+ - attempt_complete: Quick completion using previous response (shorthand).
426
+ `;
427
+
428
+ // Common instructions
429
+ const commonInstructions = `<instructions>
430
+ Follow these instructions carefully:
431
+ 1. Analyze the user's request.
432
+ 2. Use <thinking></thinking> tags to analyze the situation and determine the appropriate tool for each step.
433
+ 3. Use the available tools step-by-step to fulfill the request.
434
+ 4. You should always prefer the \`search\` tool for code-related questions. Read full files only if really necessary.
435
+ 5. Ensure to get really deep and understand the full picture before answering.
436
+ 6. You MUST respond with exactly ONE tool call per message, using the specified XML format, until the task is complete.
437
+ 7. Wait for the tool execution result (provided in the next user message in a <tool_result> block) before proceeding to the next step.
438
+ 8. Once the task is fully completed, use the '<attempt_completion>' tool to provide the final result. This is the ONLY way to signal completion.
439
+ 9. Prefer concise and focused search queries. Use specific keywords and phrases to narrow down results.
440
+ </instructions>
441
+ `;
442
+
443
+ // Define predefined prompts (without the common instructions)
444
+ const predefinedPrompts = {
445
+ 'code-explorer': `You are ProbeChat Code Explorer, a specialized AI assistant focused on helping developers, product managers, and QAs understand and navigate codebases. Your primary function is to answer questions based on code, explain how systems work, and provide insights into code functionality using the provided code analysis tools.
446
+
447
+ When exploring code:
448
+ - Provide clear, concise explanations based on user request
449
+ - Find and highlight the most relevant code snippets, if required
450
+ - Trace function calls and data flow through the system
451
+ - Try to understand the user's intent and provide relevant information
452
+ - Understand high level picture
453
+ - Balance detail with clarity in your explanations`,
454
+
455
+ 'architect': `You are ProbeChat Architect, a specialized AI assistant focused on software architecture and design. Your primary function is to help users understand, analyze, and design software systems using the provided code analysis tools.
456
+
457
+ When analyzing code:
458
+ - Focus on high-level design patterns and system organization
459
+ - Identify architectural patterns and component relationships
460
+ - Evaluate system structure and suggest architectural improvements
461
+ - Consider scalability, maintainability, and extensibility in your analysis`,
462
+
463
+ 'code-review': `You are ProbeChat Code Reviewer, a specialized AI assistant focused on code quality and best practices. Your primary function is to help users identify issues, suggest improvements, and ensure code follows best practices using the provided code analysis tools.
464
+
465
+ When reviewing code:
466
+ - Look for bugs, edge cases, and potential issues
467
+ - Identify performance bottlenecks and optimization opportunities
468
+ - Check for security vulnerabilities and best practices
469
+ - Evaluate code style and consistency
470
+ - Provide specific, actionable suggestions with code examples where appropriate`,
471
+
472
+ 'code-review-template': `You are going to perform code review according to provided user rules. Ensure to review only code provided in diff and latest commit, if provided. However you still need to fully understand how modified code works, and read dependencies if something is not clear.`,
473
+
474
+ 'engineer': `You are senior engineer focused on software architecture and design.
475
+ Before jumping on the task you first, in details analyse user request, and try to provide elegant and concise solution.
476
+ If solution is clear, you can jump to implementation right away, if not, you can ask user a clarification question, by calling attempt_completion tool, with required details.
477
+
478
+ Before jumping to implementation:
479
+ - Focus on high-level design patterns and system organization
480
+ - Identify architectural patterns and component relationships
481
+ - Evaluate system structure and suggest architectural improvements
482
+ - Focus on backward compatibility.
483
+ - Consider scalability, maintainability, and extensibility in your analysis
484
+
485
+ During the implementation:
486
+ - Avoid implementing special cases
487
+ - Do not forget to add the tests`,
488
+
489
+ 'support': `You are ProbeChat Support, a specialized AI assistant focused on helping developers troubleshoot issues and solve problems. Your primary function is to help users diagnose errors, understand unexpected behaviors, and find solutions using the provided code analysis tools.
490
+
491
+ When troubleshooting:
492
+ - Focus on finding root causes, not just symptoms
493
+ - Explain concepts clearly with appropriate context
494
+ - Provide step-by-step guidance to solve problems
495
+ - Suggest diagnostic steps to verify solutions
496
+ - Consider edge cases and potential complications
497
+ - Be empathetic and patient in your explanations`
498
+ };
499
+
500
+ let systemMessage = '';
501
+
502
+ // Use custom prompt if provided
503
+ if (this.customPrompt) {
504
+ systemMessage = "<role>" + this.customPrompt + "</role>";
505
+ if (this.debug) {
506
+ console.log(`[DEBUG] Using custom prompt`);
507
+ }
508
+ }
509
+ // Use predefined prompt if specified
510
+ else if (this.promptType && predefinedPrompts[this.promptType]) {
511
+ systemMessage = "<role>" + predefinedPrompts[this.promptType] + "</role>";
512
+ if (this.debug) {
513
+ console.log(`[DEBUG] Using predefined prompt: ${this.promptType}`);
514
+ }
515
+ // Add common instructions to predefined prompts
516
+ systemMessage += commonInstructions;
517
+ } else {
518
+ // Use the default prompt (code explorer) if no prompt type is specified
519
+ systemMessage = "<role>" + predefinedPrompts['code-explorer'] + "</role>";
520
+ if (this.debug) {
521
+ console.log(`[DEBUG] Using default prompt: code explorer`);
522
+ }
523
+ // Add common instructions to the default prompt
524
+ systemMessage += commonInstructions;
525
+ }
526
+
527
+ // Add XML Tool Guidelines
528
+ systemMessage += `\n${xmlToolGuidelines}\n`;
529
+
530
+ // Add Tool Definitions
531
+ systemMessage += `\n# Tools Available\n${toolDefinitions}\n`;
532
+
533
+ // Add MCP tools if available
534
+ if (this.mcpBridge && this.mcpBridge.getToolNames().length > 0) {
535
+ systemMessage += `\n## MCP Tools (JSON parameters in <params> tag)\n`;
536
+ systemMessage += this.mcpBridge.getXmlToolDefinitions();
537
+ systemMessage += `\n\nFor MCP tools, use JSON format within the params tag, e.g.:\n<mcp_tool>\n<params>\n{"key": "value"}\n</params>\n</mcp_tool>\n`;
538
+ }
539
+
540
+ // Add folder information
541
+ const searchDirectory = this.allowedFolders.length > 0 ? this.allowedFolders[0] : process.cwd();
542
+ if (this.debug) {
543
+ console.log(`[DEBUG] Generating file list for base directory: ${searchDirectory}...`);
544
+ }
545
+
546
+ try {
547
+ const files = await listFilesByLevel({
548
+ directory: searchDirectory,
549
+ maxFiles: 100,
550
+ respectGitignore: !process.env.PROBE_NO_GITIGNORE || process.env.PROBE_NO_GITIGNORE === '',
551
+ cwd: process.cwd()
552
+ });
553
+
554
+ systemMessage += `\n# Repository Structure\n\nYou are working with a repository located at: ${searchDirectory}\n\nHere's an overview of the repository structure (showing up to 100 most relevant files):\n\n\`\`\`\n${files}\n\`\`\`\n\n`;
555
+ } catch (error) {
556
+ if (this.debug) {
557
+ console.log(`[DEBUG] Could not generate file list: ${error.message}`);
558
+ }
559
+ systemMessage += `\n# Repository Structure\n\nYou are working with a repository located at: ${searchDirectory}\n\n`;
560
+ }
561
+
562
+ if (this.allowedFolders.length > 0) {
563
+ systemMessage += `\n**Important**: For security reasons, you can only search within these allowed folders: ${this.allowedFolders.join(', ')}\n\n`;
564
+ }
565
+
566
+ return systemMessage;
567
+ }
568
+
569
+ /**
570
+ * Answer a question using the agentic flow
571
+ * @param {string} message - The user's question
572
+ * @param {Array} [images] - Optional array of image data (base64 strings or URLs)
573
+ * @param {Object|string} [schemaOrOptions] - Can be either:
574
+ * - A string: JSON schema for structured output (backwards compatible)
575
+ * - An object: Options object with schema and other options
576
+ * @param {string} [schemaOrOptions.schema] - JSON schema string for structured output
577
+ * @returns {Promise<string>} - The final answer
578
+ */
579
+ async answer(message, images = [], schemaOrOptions = {}) {
580
+ if (!message || typeof message !== 'string' || message.trim().length === 0) {
581
+ throw new Error('Message is required and must be a non-empty string');
582
+ }
583
+
584
+ // Handle backwards compatibility - if third argument is a string, treat it as schema
585
+ let options = {};
586
+ if (typeof schemaOrOptions === 'string') {
587
+ options = { schema: schemaOrOptions };
588
+ } else {
589
+ options = schemaOrOptions || {};
590
+ }
591
+
592
+ try {
593
+ // Generate system message
594
+ const systemMessage = await this.getSystemMessage();
595
+
596
+ // Create user message with optional image support
597
+ let userMessage = { role: 'user', content: message.trim() };
598
+
599
+ // If images are provided, use multi-modal message format
600
+ if (images && images.length > 0) {
601
+ userMessage.content = [
602
+ { type: 'text', text: message.trim() },
603
+ ...images.map(image => ({
604
+ type: 'image',
605
+ image: image
606
+ }))
607
+ ];
608
+ }
609
+
610
+ // Initialize conversation with existing history + new user message
611
+ let currentMessages = [
612
+ { role: 'system', content: systemMessage },
613
+ ...this.history, // Include previous conversation history
614
+ userMessage
615
+ ];
616
+
617
+ let currentIteration = 0;
618
+ let completionAttempted = false;
619
+ let finalResult = 'I was unable to complete your request due to reaching the maximum number of tool iterations.';
620
+
621
+ // Adjust max iterations if schema is provided
622
+ // +1 for schema formatting
623
+ // +2 for potential Mermaid validation retries (can be multiple diagrams)
624
+ // +1 for potential JSON correction
625
+ const maxIterations = options.schema ? MAX_TOOL_ITERATIONS + 4 : MAX_TOOL_ITERATIONS;
626
+
627
+ if (this.debug) {
628
+ console.log(`[DEBUG] Starting agentic flow for question: ${message.substring(0, 100)}...`);
629
+ if (options.schema) {
630
+ console.log(`[DEBUG] Schema provided, using extended iteration limit: ${maxIterations} (base: ${MAX_TOOL_ITERATIONS})`);
631
+ }
632
+ }
633
+
634
+ // Tool iteration loop
635
+ while (currentIteration < maxIterations && !completionAttempted) {
636
+ currentIteration++;
637
+ if (this.cancelled) throw new Error('Request was cancelled by the user');
638
+
639
+ if (this.debug) {
640
+ console.log(`\n[DEBUG] --- Tool Loop Iteration ${currentIteration}/${maxIterations} ---`);
641
+ console.log(`[DEBUG] Current messages count for AI call: ${currentMessages.length}`);
642
+
643
+ // Log preview of the latest user message (helpful for debugging loops)
644
+ const lastUserMessage = [...currentMessages].reverse().find(msg => msg.role === 'user');
645
+ if (lastUserMessage && lastUserMessage.content) {
646
+ const userPreview = createMessagePreview(lastUserMessage.content);
647
+ console.log(`[DEBUG] Latest user message (${lastUserMessage.content.length} chars): ${userPreview}`);
648
+ }
649
+ }
650
+
651
+ // Add iteration tracing event
652
+ if (this.tracer) {
653
+ this.tracer.addEvent('iteration.start', {
654
+ 'iteration': currentIteration,
655
+ 'max_iterations': maxIterations,
656
+ 'message_count': currentMessages.length
657
+ });
658
+ }
659
+
660
+ // Add warning message when reaching the last iteration
661
+ if (currentIteration === maxIterations) {
662
+ const warningMessage = `⚠️ WARNING: You have reached the maximum tool iterations limit (${maxIterations}). This is your final message. Please respond with the data you have so far. If something was not completed, honestly state what was not done and provide any partial results or recommendations you can offer.`;
663
+
664
+ currentMessages.push({
665
+ role: 'user',
666
+ content: warningMessage
667
+ });
668
+
669
+ if (this.debug) {
670
+ console.log(`[DEBUG] Added max iterations warning message at iteration ${currentIteration}`);
671
+ }
672
+ }
673
+
674
+ // Calculate context size
675
+ this.tokenCounter.calculateContextSize(currentMessages);
676
+ if (this.debug) {
677
+ console.log(`[DEBUG] Estimated context tokens BEFORE LLM call (Iter ${currentIteration}): ${this.tokenCounter.contextSize}`);
678
+ }
679
+
680
+ let maxResponseTokens = this.maxResponseTokens;
681
+ if (!maxResponseTokens) {
682
+ // Use model-based defaults if not explicitly configured
683
+ maxResponseTokens = 4000;
684
+ if (this.model.includes('opus') || this.model.includes('sonnet') || this.model.startsWith('gpt-4-')) {
685
+ maxResponseTokens = 8192;
686
+ } else if (this.model.startsWith('gpt-4o')) {
687
+ maxResponseTokens = 8192;
688
+ } else if (this.model.startsWith('gemini')) {
689
+ maxResponseTokens = 32000;
690
+ }
691
+ }
692
+
693
+ // Make AI request
694
+ let assistantResponseContent = '';
695
+ try {
696
+ // Wrap AI request with tracing if available
697
+ const executeAIRequest = async () => {
698
+ const result = await streamText({
699
+ model: this.provider(this.model),
700
+ messages: currentMessages,
701
+ maxTokens: maxResponseTokens,
702
+ temperature: 0.3,
703
+ });
704
+
705
+ // Collect the streamed response
706
+ for await (const delta of result.textStream) {
707
+ assistantResponseContent += delta;
708
+ }
709
+
710
+ // Record token usage
711
+ const usage = await result.usage;
712
+ if (usage) {
713
+ this.tokenCounter.recordUsage(usage, result.experimental_providerMetadata);
714
+ }
715
+
716
+ return result;
717
+ };
718
+
719
+ if (this.tracer) {
720
+ await this.tracer.withSpan('ai.request', executeAIRequest, {
721
+ 'ai.model': this.model,
722
+ 'ai.provider': this.clientApiProvider || 'auto',
723
+ 'iteration': currentIteration,
724
+ 'max_tokens': maxResponseTokens,
725
+ 'temperature': 0.3,
726
+ 'message_count': currentMessages.length
727
+ });
728
+ } else {
729
+ await executeAIRequest();
730
+ }
731
+
732
+ } catch (error) {
733
+ console.error(`Error during streamText (Iter ${currentIteration}):`, error);
734
+ finalResult = `Error: Failed to get response from AI model during iteration ${currentIteration}. ${error.message}`;
735
+ throw new Error(finalResult);
736
+ }
737
+
738
+ // Log preview of assistant response for debugging loops
739
+ if (this.debug && assistantResponseContent) {
740
+ const assistantPreview = createMessagePreview(assistantResponseContent);
741
+ console.log(`[DEBUG] Assistant response (${assistantResponseContent.length} chars): ${assistantPreview}`);
742
+ }
743
+
744
+ // Parse tool call from response with valid tools list
745
+ const validTools = [
746
+ 'search', 'query', 'extract', 'listFiles', 'searchFiles', 'attempt_completion'
747
+ ];
748
+ if (this.allowEdit) {
749
+ validTools.push('implement');
750
+ }
751
+
752
+ // Try parsing with hybrid parser that supports both native and MCP tools
753
+ const nativeTools = validTools;
754
+ const parsedTool = this.mcpBridge
755
+ ? parseHybridXmlToolCall(assistantResponseContent, nativeTools, this.mcpBridge)
756
+ : parseXmlToolCallWithThinking(assistantResponseContent, validTools);
757
+ if (parsedTool) {
758
+ const { toolName, params } = parsedTool;
759
+ if (this.debug) console.log(`[DEBUG] Parsed tool call: ${toolName} with params:`, params);
760
+
761
+ if (toolName === 'attempt_completion') {
762
+ completionAttempted = true;
763
+
764
+ // Handle attempt_complete shorthand - use previous response
765
+ if (params.result === '__PREVIOUS_RESPONSE__') {
766
+ // Find the last assistant message with actual content (not tool calls)
767
+ const lastAssistantMessage = [...currentMessages].reverse().find(msg =>
768
+ msg.role === 'assistant' &&
769
+ msg.content &&
770
+ !(this.mcpBridge
771
+ ? parseHybridXmlToolCall(msg.content, validTools, this.mcpBridge)
772
+ : parseXmlToolCallWithThinking(msg.content, validTools))
773
+ );
774
+
775
+ if (lastAssistantMessage) {
776
+ finalResult = lastAssistantMessage.content;
777
+ if (this.debug) console.log(`[DEBUG] Using previous response as completion: ${finalResult.substring(0, 100)}...`);
778
+ } else {
779
+ finalResult = 'Error: No previous response found to use as completion.';
780
+ if (this.debug) console.log(`[DEBUG] No suitable previous response found for attempt_complete shorthand`);
781
+ }
782
+ } else {
783
+ // Standard attempt_completion handling
784
+ const validation = attemptCompletionSchema.safeParse(params);
785
+ if (validation.success) {
786
+ finalResult = validation.data.result;
787
+ if (this.debug) console.log(`[DEBUG] Task completed successfully with result: ${finalResult.substring(0, 100)}...`);
788
+ } else {
789
+ console.error(`[ERROR] Invalid attempt_completion parameters:`, validation.error);
790
+ finalResult = 'Error: Invalid completion attempt. The task could not be completed properly.';
791
+ }
792
+ }
793
+ break;
794
+ } else {
795
+ // Check tool type and execute accordingly
796
+ const { type } = parsedTool;
797
+
798
+ if (type === 'mcp' && this.mcpBridge && this.mcpBridge.isMcpTool(toolName)) {
799
+ // Execute MCP tool
800
+ try {
801
+ if (this.debug) console.log(`[DEBUG] Executing MCP tool '${toolName}' with params:`, params);
802
+
803
+ // Execute MCP tool through the bridge
804
+ const executionResult = await this.mcpBridge.mcpTools[toolName].execute(params);
805
+
806
+ const toolResultContent = typeof executionResult === 'string' ? executionResult : JSON.stringify(executionResult, null, 2);
807
+ const preview = createMessagePreview(toolResultContent);
808
+ if (this.debug) {
809
+ console.log(`[DEBUG] MCP tool '${toolName}' executed successfully. Result preview: ${preview}`);
810
+ }
811
+
812
+ currentMessages.push({ role: 'user', content: `<tool_result>\n${toolResultContent}\n</tool_result>` });
813
+ } catch (error) {
814
+ console.error(`Error executing MCP tool ${toolName}:`, error);
815
+ const toolResultContent = `Error executing MCP tool ${toolName}: ${error.message}`;
816
+ if (this.debug) console.log(`[DEBUG] MCP tool '${toolName}' execution FAILED.`);
817
+ currentMessages.push({ role: 'user', content: `<tool_result>\n${toolResultContent}\n</tool_result>` });
818
+ }
819
+ } else if (this.toolImplementations[toolName]) {
820
+ // Execute native tool
821
+ try {
822
+ // Add sessionId to params for tool execution
823
+ const toolParams = { ...params, sessionId: this.sessionId };
824
+
825
+ // Emit tool start event
826
+ this.events.emit('toolCall', {
827
+ timestamp: new Date().toISOString(),
828
+ name: toolName,
829
+ args: toolParams,
830
+ status: 'started'
831
+ });
832
+
833
+ // Execute tool with tracing if available
834
+ const executeToolCall = async () => {
835
+ // For delegate tool, pass current iteration and max iterations
836
+ if (toolName === 'delegate') {
837
+ const enhancedParams = {
838
+ ...toolParams,
839
+ currentIteration,
840
+ maxIterations,
841
+ debug: this.debug,
842
+ tracer: this.tracer
843
+ };
844
+
845
+ if (this.debug) {
846
+ console.log(`[DEBUG] Executing delegate tool at iteration ${currentIteration}/${maxIterations}`);
847
+ console.log(`[DEBUG] Delegate task: ${toolParams.task?.substring(0, 100)}...`);
848
+ }
849
+
850
+ // Record delegation start in telemetry
851
+ if (this.tracer) {
852
+ this.tracer.recordDelegationEvent('tool_started', {
853
+ 'delegation.iteration': currentIteration,
854
+ 'delegation.max_iterations': maxIterations,
855
+ 'delegation.task_preview': toolParams.task?.substring(0, 200) + (toolParams.task?.length > 200 ? '...' : '')
856
+ });
857
+ }
858
+
859
+ return await this.toolImplementations[toolName].execute(enhancedParams);
860
+ }
861
+ return await this.toolImplementations[toolName].execute(toolParams);
862
+ };
863
+
864
+ let toolResult;
865
+ try {
866
+ if (this.tracer) {
867
+ toolResult = await this.tracer.withSpan('tool.call', executeToolCall, {
868
+ 'tool.name': toolName,
869
+ 'tool.params': JSON.stringify(toolParams).substring(0, 500),
870
+ 'iteration': currentIteration
871
+ });
872
+ } else {
873
+ toolResult = await executeToolCall();
874
+ }
875
+
876
+ // Emit tool success event
877
+ this.events.emit('toolCall', {
878
+ timestamp: new Date().toISOString(),
879
+ name: toolName,
880
+ args: toolParams,
881
+ resultPreview: typeof toolResult === 'string'
882
+ ? (toolResult.length > 200 ? toolResult.substring(0, 200) + '...' : toolResult)
883
+ : (toolResult ? JSON.stringify(toolResult).substring(0, 200) + '...' : 'No Result'),
884
+ status: 'completed'
885
+ });
886
+
887
+ } catch (toolError) {
888
+ // Emit tool error event
889
+ this.events.emit('toolCall', {
890
+ timestamp: new Date().toISOString(),
891
+ name: toolName,
892
+ args: toolParams,
893
+ error: toolError.message || 'Unknown error',
894
+ status: 'error'
895
+ });
896
+ throw toolError; // Re-throw to be handled by outer catch
897
+ }
898
+
899
+ // Add assistant response and tool result to conversation
900
+ currentMessages.push({ role: 'assistant', content: assistantResponseContent });
901
+ currentMessages.push({
902
+ role: 'user',
903
+ content: `<tool_result>\n${typeof toolResult === 'string' ? toolResult : JSON.stringify(toolResult, null, 2)}\n</tool_result>`
904
+ });
905
+
906
+ if (this.debug) {
907
+ console.log(`[DEBUG] Tool ${toolName} executed successfully. Result length: ${typeof toolResult === 'string' ? toolResult.length : JSON.stringify(toolResult).length}`);
908
+ }
909
+ } catch (error) {
910
+ console.error(`[ERROR] Tool execution failed for ${toolName}:`, error);
911
+ currentMessages.push({ role: 'assistant', content: assistantResponseContent });
912
+ currentMessages.push({
913
+ role: 'user',
914
+ content: `<tool_result>\nError: ${error.message}\n</tool_result>`
915
+ });
916
+ }
917
+ } else {
918
+ console.error(`[ERROR] Unknown tool: ${toolName}`);
919
+ currentMessages.push({ role: 'assistant', content: assistantResponseContent });
920
+
921
+ // Build list of available tools including MCP tools
922
+ const nativeTools = Object.keys(this.toolImplementations);
923
+ const mcpTools = this.mcpBridge ? this.mcpBridge.getToolNames() : [];
924
+ const allAvailableTools = [...nativeTools, ...mcpTools];
925
+
926
+ currentMessages.push({
927
+ role: 'user',
928
+ content: `<tool_result>\nError: Unknown tool '${toolName}'. Available tools: ${allAvailableTools.join(', ')}\n</tool_result>`
929
+ });
930
+ }
931
+ }
932
+ } else {
933
+ // No tool call found, add assistant response and ask for tool usage
934
+ currentMessages.push({ role: 'assistant', content: assistantResponseContent });
935
+
936
+ // Build appropriate reminder message based on whether schema is provided
937
+ let reminderContent;
938
+ if (options.schema) { // Apply for ANY schema, not just JSON schemas
939
+ // When schema is provided, give specific instructions
940
+ reminderContent = `Please use one of the available tools to help answer the question, or use attempt_completion if you have enough information to provide a final answer.
941
+
942
+ Remember: Use proper XML format with BOTH opening and closing tags:
943
+
944
+ <tool_name>
945
+ <parameter>value</parameter>
946
+ </tool_name>
947
+
948
+ IMPORTANT: A schema was provided. You MUST respond with data that matches this schema.
949
+ Use attempt_completion with your response directly inside the tags:
950
+
951
+ <attempt_completion>
952
+ [Your response content matching the provided schema format]
953
+ </attempt_completion>
954
+
955
+ Your response must conform to this schema:
956
+ ${options.schema}`;
957
+ } else {
958
+ // Standard reminder without schema
959
+ reminderContent = `Please use one of the available tools to help answer the question, or use attempt_completion if you have enough information to provide a final answer.
960
+
961
+ Remember: Use proper XML format with BOTH opening and closing tags:
962
+
963
+ <tool_name>
964
+ <parameter>value</parameter>
965
+ </tool_name>
966
+
967
+ Or for quick completion if your previous response was already correct:
968
+ <attempt_complete>`;
969
+ }
970
+
971
+ currentMessages.push({
972
+ role: 'user',
973
+ content: reminderContent
974
+ });
975
+ if (this.debug) {
976
+ console.log(`[DEBUG] No tool call detected in assistant response. Prompting for tool use.`);
977
+ }
978
+ }
979
+
980
+ // Keep message history manageable
981
+ if (currentMessages.length > MAX_HISTORY_MESSAGES) {
982
+ const messagesBefore = currentMessages.length;
983
+ const systemMsg = currentMessages[0]; // Keep system message
984
+ const recentMessages = currentMessages.slice(-MAX_HISTORY_MESSAGES + 1);
985
+ currentMessages = [systemMsg, ...recentMessages];
986
+
987
+ if (this.debug) {
988
+ console.log(`[DEBUG] Trimmed message history from ${messagesBefore} to ${currentMessages.length} messages`);
989
+ }
990
+ }
991
+ }
992
+
993
+ if (currentIteration >= maxIterations && !completionAttempted) {
994
+ console.warn(`[WARN] Max tool iterations (${maxIterations}) reached for session ${this.sessionId}. Returning current error state.`);
995
+ }
996
+
997
+ // Store final history
998
+ this.history = currentMessages.map(msg => ({ ...msg }));
999
+ if (this.history.length > MAX_HISTORY_MESSAGES) {
1000
+ const messagesBefore = this.history.length;
1001
+ this.history = this.history.slice(-MAX_HISTORY_MESSAGES);
1002
+ if (this.debug) {
1003
+ console.log(`[DEBUG] Trimmed stored history from ${messagesBefore} to ${this.history.length} messages`);
1004
+ }
1005
+ }
1006
+
1007
+ // Update token counter with final history
1008
+ this.tokenCounter.updateHistory(this.history);
1009
+
1010
+ // Schema handling - format response according to provided schema
1011
+ // Skip schema processing if result came from attempt_completion tool
1012
+ // Don't apply schema formatting if we failed due to max iterations
1013
+ const reachedMaxIterations = currentIteration >= maxIterations && !completionAttempted;
1014
+ if (options.schema && !options._schemaFormatted && !completionAttempted && !reachedMaxIterations) {
1015
+ if (this.debug) {
1016
+ console.log('[DEBUG] Schema provided, applying automatic formatting...');
1017
+ }
1018
+
1019
+ try {
1020
+ // Step 1: Make a follow-up call to format according to schema
1021
+ const schemaPrompt = `CRITICAL: You MUST respond with ONLY valid JSON DATA that conforms to this schema structure. DO NOT return the schema definition itself.
1022
+
1023
+ Schema to follow (this is just the structure - provide ACTUAL DATA):
1024
+ ${options.schema}
1025
+
1026
+ REQUIREMENTS:
1027
+ - Return ONLY the JSON object/array with REAL DATA that matches the schema structure
1028
+ - DO NOT return the schema definition itself (no "$schema", "$id", "type", "properties", etc.)
1029
+ - NO additional text, explanations, or markdown formatting
1030
+ - NO code blocks or backticks
1031
+ - The JSON must be parseable by JSON.parse()
1032
+ - Fill in actual values that make sense based on your previous response content
1033
+
1034
+ EXAMPLE:
1035
+ If schema defines {type: "object", properties: {name: {type: "string"}, age: {type: "number"}}}
1036
+ Return: {"name": "John Doe", "age": 25}
1037
+ NOT: {"type": "object", "properties": {"name": {"type": "string"}}}
1038
+
1039
+ Convert your previous response content into actual JSON data that follows this schema structure.`;
1040
+
1041
+ // Call answer recursively with _schemaFormatted flag to prevent infinite loop
1042
+ finalResult = await this.answer(schemaPrompt, [], {
1043
+ ...options,
1044
+ _schemaFormatted: true
1045
+ });
1046
+
1047
+ // Step 2: Clean the response (remove code blocks)
1048
+ finalResult = cleanSchemaResponse(finalResult);
1049
+
1050
+ // Step 3: Validate and fix Mermaid diagrams if present
1051
+ if (!this.disableMermaidValidation) {
1052
+ try {
1053
+ if (this.debug) {
1054
+ console.log(`[DEBUG] Mermaid validation: Starting enhanced mermaid validation...`);
1055
+ }
1056
+
1057
+ // Record mermaid validation start in telemetry
1058
+ if (this.tracer) {
1059
+ this.tracer.recordMermaidValidationEvent('schema_processing_started', {
1060
+ 'mermaid_validation.context': 'schema_processing',
1061
+ 'mermaid_validation.response_length': finalResult.length
1062
+ });
1063
+ }
1064
+
1065
+ const mermaidValidation = await validateAndFixMermaidResponse(finalResult, {
1066
+ debug: this.debug,
1067
+ path: this.allowedFolders[0],
1068
+ provider: this.clientApiProvider,
1069
+ model: this.model,
1070
+ tracer: this.tracer
1071
+ });
1072
+
1073
+ if (mermaidValidation.wasFixed) {
1074
+ finalResult = mermaidValidation.fixedResponse;
1075
+ if (this.debug) {
1076
+ console.log(`[DEBUG] Mermaid validation: Diagrams successfully fixed`);
1077
+
1078
+ if (mermaidValidation.performanceMetrics) {
1079
+ const metrics = mermaidValidation.performanceMetrics;
1080
+ console.log(`[DEBUG] Mermaid validation: Performance - total: ${metrics.totalTimeMs}ms, AI fixing: ${metrics.aiFixingTimeMs}ms`);
1081
+ console.log(`[DEBUG] Mermaid validation: Results - ${metrics.diagramsFixed}/${metrics.diagramsProcessed} diagrams fixed`);
1082
+ }
1083
+
1084
+ if (mermaidValidation.fixingResults) {
1085
+ mermaidValidation.fixingResults.forEach((fixResult, index) => {
1086
+ if (fixResult.wasFixed) {
1087
+ const method = fixResult.fixedWithHtmlDecoding ? 'HTML entity decoding' : 'AI correction';
1088
+ const time = fixResult.aiFixingTimeMs ? ` in ${fixResult.aiFixingTimeMs}ms` : '';
1089
+ console.log(`[DEBUG] Mermaid validation: Fixed diagram ${fixResult.diagramIndex + 1} with ${method}${time}`);
1090
+ console.log(`[DEBUG] Mermaid validation: Original error: ${fixResult.originalError}`);
1091
+ } else {
1092
+ console.log(`[DEBUG] Mermaid validation: Failed to fix diagram ${fixResult.diagramIndex + 1}: ${fixResult.fixingError}`);
1093
+ }
1094
+ });
1095
+ }
1096
+ }
1097
+ } else if (this.debug) {
1098
+ console.log(`[DEBUG] Mermaid validation: No fixes needed or fixes unsuccessful`);
1099
+ if (mermaidValidation.diagrams?.length > 0) {
1100
+ console.log(`[DEBUG] Mermaid validation: Found ${mermaidValidation.diagrams.length} diagrams, all valid: ${mermaidValidation.isValid}`);
1101
+ }
1102
+ }
1103
+ } catch (error) {
1104
+ if (this.debug) {
1105
+ console.log(`[DEBUG] Mermaid validation: Process failed with error: ${error.message}`);
1106
+ console.log(`[DEBUG] Mermaid validation: Stack trace: ${error.stack}`);
1107
+ }
1108
+ }
1109
+ } else if (this.debug) {
1110
+ console.log(`[DEBUG] Mermaid validation: Skipped due to disableMermaidValidation option`);
1111
+ }
1112
+
1113
+ // Step 4: Validate and potentially correct JSON responses
1114
+ if (isJsonSchema(options.schema)) {
1115
+ if (this.debug) {
1116
+ console.log(`[DEBUG] JSON validation: Starting validation process for schema response`);
1117
+ console.log(`[DEBUG] JSON validation: Response length: ${finalResult.length} chars`);
1118
+ }
1119
+
1120
+ // Record JSON validation start in telemetry
1121
+ if (this.tracer) {
1122
+ this.tracer.recordJsonValidationEvent('started', {
1123
+ 'json_validation.response_length': finalResult.length,
1124
+ 'json_validation.schema_type': 'JSON'
1125
+ });
1126
+ }
1127
+
1128
+ let validation = validateJsonResponse(finalResult, { debug: this.debug });
1129
+ let retryCount = 0;
1130
+ const maxRetries = 3;
1131
+
1132
+ // First check if the response is valid JSON but is actually a schema definition
1133
+ if (validation.isValid && isJsonSchemaDefinition(finalResult, { debug: this.debug })) {
1134
+ if (this.debug) {
1135
+ console.log(`[DEBUG] JSON validation: Response is a JSON schema definition instead of data, correcting...`);
1136
+ }
1137
+
1138
+ // Use specialized correction prompt for schema definition confusion
1139
+ const schemaDefinitionPrompt = createSchemaDefinitionCorrectionPrompt(
1140
+ finalResult,
1141
+ options.schema,
1142
+ 0
1143
+ );
1144
+
1145
+ finalResult = await this.answer(schemaDefinitionPrompt, [], {
1146
+ ...options,
1147
+ _schemaFormatted: true
1148
+ });
1149
+ finalResult = cleanSchemaResponse(finalResult);
1150
+ validation = validateJsonResponse(finalResult);
1151
+ retryCount = 1; // Start at 1 since we already did one correction
1152
+ }
1153
+
1154
+ while (!validation.isValid && retryCount < maxRetries) {
1155
+ if (this.debug) {
1156
+ console.log(`[DEBUG] JSON validation: Validation failed (attempt ${retryCount + 1}/${maxRetries}):`, validation.error);
1157
+ console.log(`[DEBUG] JSON validation: Invalid response sample: ${finalResult.substring(0, 300)}${finalResult.length > 300 ? '...' : ''}`);
1158
+ }
1159
+
1160
+ // Check if the invalid response is actually a schema definition
1161
+ let correctionPrompt;
1162
+ try {
1163
+ if (isJsonSchemaDefinition(finalResult, { debug: this.debug })) {
1164
+ if (this.debug) {
1165
+ console.log(`[DEBUG] JSON validation: Response is still a schema definition, using specialized correction`);
1166
+ }
1167
+ correctionPrompt = createSchemaDefinitionCorrectionPrompt(
1168
+ finalResult,
1169
+ options.schema,
1170
+ retryCount
1171
+ );
1172
+ } else {
1173
+ correctionPrompt = createJsonCorrectionPrompt(
1174
+ finalResult,
1175
+ options.schema,
1176
+ validation.error,
1177
+ retryCount
1178
+ );
1179
+ }
1180
+ } catch (error) {
1181
+ // If we can't parse to check if it's a schema definition, use regular correction
1182
+ correctionPrompt = createJsonCorrectionPrompt(
1183
+ finalResult,
1184
+ options.schema,
1185
+ validation.error,
1186
+ retryCount
1187
+ );
1188
+ }
1189
+
1190
+ finalResult = await this.answer(correctionPrompt, [], {
1191
+ ...options,
1192
+ _schemaFormatted: true
1193
+ });
1194
+ finalResult = cleanSchemaResponse(finalResult);
1195
+
1196
+ // Validate the corrected response
1197
+ validation = validateJsonResponse(finalResult, { debug: this.debug });
1198
+ retryCount++;
1199
+
1200
+ if (this.debug) {
1201
+ if (!validation.isValid && retryCount < maxRetries) {
1202
+ console.log(`[DEBUG] JSON validation: Still invalid after correction ${retryCount}, retrying...`);
1203
+ console.log(`[DEBUG] JSON validation: Corrected response sample: ${finalResult.substring(0, 300)}${finalResult.length > 300 ? '...' : ''}`);
1204
+ } else if (validation.isValid) {
1205
+ console.log(`[DEBUG] JSON validation: Successfully corrected after ${retryCount} attempts`);
1206
+ }
1207
+ }
1208
+ }
1209
+
1210
+ if (!validation.isValid && this.debug) {
1211
+ console.log(`[DEBUG] JSON validation: Still invalid after ${maxRetries} correction attempts:`, validation.error);
1212
+ console.log(`[DEBUG] JSON validation: Final invalid response: ${finalResult.substring(0, 500)}${finalResult.length > 500 ? '...' : ''}`);
1213
+ } else if (validation.isValid && this.debug) {
1214
+ console.log(`[DEBUG] JSON validation: Final validation successful`);
1215
+ }
1216
+
1217
+ // Record JSON validation completion in telemetry
1218
+ if (this.tracer) {
1219
+ this.tracer.recordJsonValidationEvent('completed', {
1220
+ 'json_validation.success': validation.isValid,
1221
+ 'json_validation.retry_count': retryCount,
1222
+ 'json_validation.max_retries': maxRetries,
1223
+ 'json_validation.final_response_length': finalResult.length,
1224
+ 'json_validation.error': validation.isValid ? null : validation.error
1225
+ });
1226
+ }
1227
+ }
1228
+ } catch (error) {
1229
+ console.error('[ERROR] Schema formatting failed:', error);
1230
+ // Return the original result if schema formatting fails
1231
+ }
1232
+ } else if (reachedMaxIterations && options.schema && this.debug) {
1233
+ console.log('[DEBUG] Skipping schema formatting due to max iterations reached without completion');
1234
+ } else if (completionAttempted && options.schema) {
1235
+ // For attempt_completion results with schema, still clean markdown if needed
1236
+ try {
1237
+ finalResult = cleanSchemaResponse(finalResult);
1238
+
1239
+ // Validate and fix Mermaid diagrams if present
1240
+ if (!this.disableMermaidValidation) {
1241
+ if (this.debug) {
1242
+ console.log(`[DEBUG] Mermaid validation: Validating attempt_completion result...`);
1243
+ }
1244
+
1245
+ const mermaidValidation = await validateAndFixMermaidResponse(finalResult, {
1246
+ debug: this.debug,
1247
+ path: this.allowedFolders[0],
1248
+ provider: this.clientApiProvider,
1249
+ model: this.model,
1250
+ tracer: this.tracer
1251
+ });
1252
+
1253
+ if (mermaidValidation.wasFixed) {
1254
+ finalResult = mermaidValidation.fixedResponse;
1255
+ if (this.debug) {
1256
+ console.log(`[DEBUG] Mermaid validation: attempt_completion diagrams fixed`);
1257
+ if (mermaidValidation.performanceMetrics) {
1258
+ console.log(`[DEBUG] Mermaid validation: Fixed in ${mermaidValidation.performanceMetrics.totalTimeMs}ms`);
1259
+ }
1260
+ }
1261
+ } else if (this.debug) {
1262
+ console.log(`[DEBUG] Mermaid validation: attempt_completion result validation completed (no fixes needed)`);
1263
+ }
1264
+ } else if (this.debug) {
1265
+ console.log(`[DEBUG] Mermaid validation: Skipped for attempt_completion result due to disableMermaidValidation option`);
1266
+ }
1267
+
1268
+ // Validate and potentially correct JSON for attempt_completion results
1269
+ if (isJsonSchema(options.schema)) {
1270
+ if (this.debug) {
1271
+ console.log(`[DEBUG] JSON validation: Starting validation process for attempt_completion result`);
1272
+ console.log(`[DEBUG] JSON validation: Response length: ${finalResult.length} chars`);
1273
+ }
1274
+
1275
+ // Record JSON validation start in telemetry
1276
+ if (this.tracer) {
1277
+ this.tracer.recordJsonValidationEvent('attempt_completion_started', {
1278
+ 'json_validation.response_length': finalResult.length,
1279
+ 'json_validation.schema_type': 'JSON',
1280
+ 'json_validation.context': 'attempt_completion'
1281
+ });
1282
+ }
1283
+
1284
+ let validation = validateJsonResponse(finalResult, { debug: this.debug });
1285
+ let retryCount = 0;
1286
+ const maxRetries = 3;
1287
+
1288
+ // First check if the response is valid JSON but is actually a schema definition
1289
+ if (validation.isValid && isJsonSchemaDefinition(finalResult, { debug: this.debug })) {
1290
+ if (this.debug) {
1291
+ console.log(`[DEBUG] JSON validation: attempt_completion response is a JSON schema definition instead of data, correcting...`);
1292
+ }
1293
+
1294
+ // Use specialized correction prompt for schema definition confusion
1295
+ const schemaDefinitionPrompt = createSchemaDefinitionCorrectionPrompt(
1296
+ finalResult,
1297
+ options.schema,
1298
+ 0
1299
+ );
1300
+
1301
+ finalResult = await this.answer(schemaDefinitionPrompt, [], {
1302
+ ...options,
1303
+ _schemaFormatted: true
1304
+ });
1305
+ finalResult = cleanSchemaResponse(finalResult);
1306
+ validation = validateJsonResponse(finalResult);
1307
+ retryCount = 1; // Start at 1 since we already did one correction
1308
+ }
1309
+
1310
+ while (!validation.isValid && retryCount < maxRetries) {
1311
+ if (this.debug) {
1312
+ console.log(`[DEBUG] JSON validation: attempt_completion validation failed (attempt ${retryCount + 1}/${maxRetries}):`, validation.error);
1313
+ console.log(`[DEBUG] JSON validation: Invalid response sample: ${finalResult.substring(0, 300)}${finalResult.length > 300 ? '...' : ''}`);
1314
+ }
1315
+
1316
+ // Check if the invalid response is actually a schema definition
1317
+ let correctionPrompt;
1318
+ try {
1319
+ if (isJsonSchemaDefinition(finalResult, { debug: this.debug })) {
1320
+ if (this.debug) {
1321
+ console.log(`[DEBUG] JSON validation: attempt_completion response is still a schema definition, using specialized correction`);
1322
+ }
1323
+ correctionPrompt = createSchemaDefinitionCorrectionPrompt(
1324
+ finalResult,
1325
+ options.schema,
1326
+ retryCount
1327
+ );
1328
+ } else {
1329
+ correctionPrompt = createJsonCorrectionPrompt(
1330
+ finalResult,
1331
+ options.schema,
1332
+ validation.error,
1333
+ retryCount
1334
+ );
1335
+ }
1336
+ } catch (error) {
1337
+ // If we can't parse to check if it's a schema definition, use regular correction
1338
+ correctionPrompt = createJsonCorrectionPrompt(
1339
+ finalResult,
1340
+ options.schema,
1341
+ validation.error,
1342
+ retryCount
1343
+ );
1344
+ }
1345
+
1346
+ finalResult = await this.answer(correctionPrompt, [], {
1347
+ ...options,
1348
+ _schemaFormatted: true
1349
+ });
1350
+ finalResult = cleanSchemaResponse(finalResult);
1351
+
1352
+ // Validate the corrected response
1353
+ validation = validateJsonResponse(finalResult, { debug: this.debug });
1354
+ retryCount++;
1355
+
1356
+ if (this.debug) {
1357
+ if (validation.isValid) {
1358
+ console.log(`[DEBUG] JSON validation: attempt_completion correction successful on attempt ${retryCount}`);
1359
+ } else {
1360
+ console.log(`[DEBUG] JSON validation: attempt_completion correction failed on attempt ${retryCount}: ${validation.error}`);
1361
+ }
1362
+ }
1363
+ }
1364
+
1365
+ // Record final validation result
1366
+ if (this.tracer) {
1367
+ this.tracer.recordJsonValidationEvent('attempt_completion_completed', {
1368
+ 'json_validation.success': validation.isValid,
1369
+ 'json_validation.retry_count': retryCount,
1370
+ 'json_validation.final_response_length': finalResult.length
1371
+ });
1372
+ }
1373
+
1374
+ if (!validation.isValid && this.debug) {
1375
+ console.log(`[DEBUG] JSON validation: attempt_completion result validation failed after ${maxRetries} attempts: ${validation.error}`);
1376
+ console.log(`[DEBUG] JSON validation: Final attempt_completion response: ${finalResult.substring(0, 500)}${finalResult.length > 500 ? '...' : ''}`);
1377
+ } else if (validation.isValid && this.debug) {
1378
+ console.log(`[DEBUG] JSON validation: attempt_completion result validation successful`);
1379
+ }
1380
+ }
1381
+ } catch (error) {
1382
+ if (this.debug) {
1383
+ console.log(`[DEBUG] attempt_completion result cleanup failed: ${error.message}`);
1384
+ }
1385
+ }
1386
+ }
1387
+
1388
+ // Final mermaid validation for all responses (regardless of schema or attempt_completion)
1389
+ if (!this.disableMermaidValidation) {
1390
+ try {
1391
+ if (this.debug) {
1392
+ console.log(`[DEBUG] Mermaid validation: Performing final mermaid validation on result...`);
1393
+ }
1394
+
1395
+ const finalMermaidValidation = await validateAndFixMermaidResponse(finalResult, {
1396
+ debug: this.debug,
1397
+ path: this.allowedFolders[0],
1398
+ provider: this.clientApiProvider,
1399
+ model: this.model,
1400
+ tracer: this.tracer
1401
+ });
1402
+
1403
+ if (finalMermaidValidation.wasFixed) {
1404
+ finalResult = finalMermaidValidation.fixedResponse;
1405
+ if (this.debug) {
1406
+ console.log(`[DEBUG] Mermaid validation: Final result diagrams fixed`);
1407
+ if (finalMermaidValidation.performanceMetrics) {
1408
+ console.log(`[DEBUG] Mermaid validation: Final validation took ${finalMermaidValidation.performanceMetrics.totalTimeMs}ms`);
1409
+ }
1410
+ }
1411
+ } else if (this.debug && finalMermaidValidation.diagrams?.length > 0) {
1412
+ console.log(`[DEBUG] Mermaid validation: Final result validation completed (${finalMermaidValidation.diagrams.length} diagrams found, no fixes needed)`);
1413
+ }
1414
+ } catch (error) {
1415
+ if (this.debug) {
1416
+ console.log(`[DEBUG] Mermaid validation: Final validation failed with error: ${error.message}`);
1417
+ }
1418
+ // Don't fail the entire request if final mermaid validation fails
1419
+ }
1420
+ } else if (this.debug) {
1421
+ console.log(`[DEBUG] Mermaid validation: Skipped final validation due to disableMermaidValidation option`);
1422
+ }
1423
+
1424
+ return finalResult;
1425
+
1426
+ } catch (error) {
1427
+ console.error(`[ERROR] ProbeAgent.answer failed:`, error);
1428
+
1429
+ // Clean up tool execution data
1430
+ clearToolExecutionData(this.sessionId);
1431
+
1432
+ throw error;
1433
+ }
1434
+ }
1435
+
1436
+ /**
1437
+ * Get token usage information
1438
+ * @returns {Object} Token usage data
1439
+ */
1440
+ getTokenUsage() {
1441
+ return this.tokenCounter.getTokenUsage();
1442
+ }
1443
+
1444
+ /**
1445
+ * Clear conversation history and reset counters
1446
+ */
1447
+ clearHistory() {
1448
+ this.history = [];
1449
+ this.tokenCounter.clear();
1450
+ clearToolExecutionData(this.sessionId);
1451
+
1452
+ if (this.debug) {
1453
+ console.log(`[DEBUG] Cleared conversation history and reset counters for session ${this.sessionId}`);
1454
+ }
1455
+ }
1456
+
1457
+ /**
1458
+ * Clean up resources (including MCP connections)
1459
+ */
1460
+ async cleanup() {
1461
+ // Clean up MCP bridge
1462
+ if (this.mcpBridge) {
1463
+ try {
1464
+ await this.mcpBridge.cleanup();
1465
+ if (this.debug) {
1466
+ console.log('[DEBUG] MCP bridge cleaned up');
1467
+ }
1468
+ } catch (error) {
1469
+ console.error('Error cleaning up MCP bridge:', error);
1470
+ }
1471
+ }
1472
+
1473
+ // Clear history and other resources
1474
+ this.clearHistory();
1475
+ }
1476
+
1477
+ /**
1478
+ * Cancel the current request
1479
+ */
1480
+ cancel() {
1481
+ this.cancelled = true;
1482
+ if (this.debug) {
1483
+ console.log(`[DEBUG] Agent cancelled for session ${this.sessionId}`);
1484
+ }
1485
+ }
1486
+ }