@probelabs/probe 0.6.0-rc102 → 0.6.0-rc104
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/agent/ProbeAgent.js +330 -11
- package/build/agent/index.js +28142 -448
- package/build/agent/probeTool.js +23 -5
- package/build/agent/tools.js +57 -5
- package/build/mcp/index.js +23 -144
- package/build/mcp/index.ts +22 -161
- package/build/utils.js +1 -1
- package/cjs/agent/ProbeAgent.cjs +27948 -270
- package/cjs/index.cjs +27996 -318
- package/package.json +2 -1
- package/src/agent/ProbeAgent.js +330 -11
- package/src/agent/index.js +39 -45
- package/src/agent/probeTool.js +23 -5
- package/src/agent/tools.js +57 -5
- package/src/mcp/index.ts +22 -161
- package/src/utils.js +1 -1
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
import { createAnthropic } from '@ai-sdk/anthropic';
|
|
3
3
|
import { createOpenAI } from '@ai-sdk/openai';
|
|
4
4
|
import { createGoogleGenerativeAI } from '@ai-sdk/google';
|
|
5
|
+
import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock';
|
|
5
6
|
import { streamText } from 'ai';
|
|
6
7
|
import { randomUUID } from 'crypto';
|
|
7
8
|
import { EventEmitter } from 'events';
|
|
9
|
+
import { existsSync } from 'fs';
|
|
10
|
+
import { readFile, stat } from 'fs/promises';
|
|
11
|
+
import { resolve, isAbsolute } from 'path';
|
|
8
12
|
import { TokenCounter } from './tokenCounter.js';
|
|
9
13
|
import {
|
|
10
14
|
createTools,
|
|
@@ -46,6 +50,12 @@ import {
|
|
|
46
50
|
const MAX_TOOL_ITERATIONS = parseInt(process.env.MAX_TOOL_ITERATIONS || '30', 10);
|
|
47
51
|
const MAX_HISTORY_MESSAGES = 100;
|
|
48
52
|
|
|
53
|
+
// Supported image file extensions
|
|
54
|
+
const SUPPORTED_IMAGE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'webp', 'gif', 'bmp', 'svg'];
|
|
55
|
+
|
|
56
|
+
// Maximum image file size (20MB) to prevent OOM attacks
|
|
57
|
+
const MAX_IMAGE_FILE_SIZE = 20 * 1024 * 1024;
|
|
58
|
+
|
|
49
59
|
/**
|
|
50
60
|
* ProbeAgent class to handle AI interactions with code search capabilities
|
|
51
61
|
*/
|
|
@@ -82,8 +92,14 @@ export class ProbeAgent {
|
|
|
82
92
|
this.maxResponseTokens = options.maxResponseTokens || parseInt(process.env.MAX_RESPONSE_TOKENS || '0', 10) || null;
|
|
83
93
|
this.disableMermaidValidation = !!options.disableMermaidValidation;
|
|
84
94
|
|
|
85
|
-
// Search configuration
|
|
86
|
-
|
|
95
|
+
// Search configuration - support both path (single) and allowedFolders (array)
|
|
96
|
+
if (options.allowedFolders && Array.isArray(options.allowedFolders)) {
|
|
97
|
+
this.allowedFolders = options.allowedFolders;
|
|
98
|
+
} else if (options.path) {
|
|
99
|
+
this.allowedFolders = [options.path];
|
|
100
|
+
} else {
|
|
101
|
+
this.allowedFolders = [process.cwd()];
|
|
102
|
+
}
|
|
87
103
|
|
|
88
104
|
// API configuration
|
|
89
105
|
this.clientApiProvider = options.provider || null;
|
|
@@ -105,6 +121,10 @@ export class ProbeAgent {
|
|
|
105
121
|
// Initialize chat history
|
|
106
122
|
this.history = [];
|
|
107
123
|
|
|
124
|
+
// Initialize image tracking for agentic loop
|
|
125
|
+
this.pendingImages = new Map(); // Map<imagePath, base64Data> to avoid reloading
|
|
126
|
+
this.currentImages = []; // Currently active images for AI calls
|
|
127
|
+
|
|
108
128
|
// Initialize event emitter for tool execution updates
|
|
109
129
|
this.events = new EventEmitter();
|
|
110
130
|
|
|
@@ -173,12 +193,18 @@ export class ProbeAgent {
|
|
|
173
193
|
const anthropicApiKey = process.env.ANTHROPIC_API_KEY;
|
|
174
194
|
const openaiApiKey = process.env.OPENAI_API_KEY;
|
|
175
195
|
const googleApiKey = process.env.GOOGLE_API_KEY;
|
|
196
|
+
const awsAccessKeyId = process.env.AWS_ACCESS_KEY_ID;
|
|
197
|
+
const awsSecretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
|
|
198
|
+
const awsRegion = process.env.AWS_REGION;
|
|
199
|
+
const awsSessionToken = process.env.AWS_SESSION_TOKEN;
|
|
200
|
+
const awsApiKey = process.env.AWS_BEDROCK_API_KEY;
|
|
176
201
|
|
|
177
202
|
// Get custom API URLs if provided
|
|
178
203
|
const llmBaseUrl = process.env.LLM_BASE_URL;
|
|
179
204
|
const anthropicApiUrl = process.env.ANTHROPIC_API_URL || llmBaseUrl;
|
|
180
205
|
const openaiApiUrl = process.env.OPENAI_API_URL || llmBaseUrl;
|
|
181
206
|
const googleApiUrl = process.env.GOOGLE_API_URL || llmBaseUrl;
|
|
207
|
+
const awsBedrockBaseUrl = process.env.AWS_BEDROCK_BASE_URL || llmBaseUrl;
|
|
182
208
|
|
|
183
209
|
// Get model override if provided
|
|
184
210
|
const modelName = process.env.MODEL_NAME;
|
|
@@ -187,7 +213,12 @@ export class ProbeAgent {
|
|
|
187
213
|
const forceProvider = this.clientApiProvider || (process.env.FORCE_PROVIDER ? process.env.FORCE_PROVIDER.toLowerCase() : null);
|
|
188
214
|
|
|
189
215
|
if (this.debug) {
|
|
190
|
-
|
|
216
|
+
const hasAwsCredentials = !!(awsAccessKeyId && awsSecretAccessKey && awsRegion);
|
|
217
|
+
const hasAwsApiKey = !!awsApiKey;
|
|
218
|
+
console.log(`[DEBUG] Available API keys: Anthropic=${!!anthropicApiKey}, OpenAI=${!!openaiApiKey}, Google=${!!googleApiKey}, AWS Bedrock=${hasAwsCredentials || hasAwsApiKey}`);
|
|
219
|
+
if (hasAwsCredentials) console.log(`[DEBUG] AWS credentials: AccessKey=${!!awsAccessKeyId}, SecretKey=${!!awsSecretAccessKey}, Region=${awsRegion}, SessionToken=${!!awsSessionToken}`);
|
|
220
|
+
if (hasAwsApiKey) console.log(`[DEBUG] AWS API Key provided`);
|
|
221
|
+
if (awsBedrockBaseUrl) console.log(`[DEBUG] AWS Bedrock base URL: ${awsBedrockBaseUrl}`);
|
|
191
222
|
console.log(`[DEBUG] Force provider: ${forceProvider || '(not set)'}`);
|
|
192
223
|
if (modelName) console.log(`[DEBUG] Model override: ${modelName}`);
|
|
193
224
|
}
|
|
@@ -203,6 +234,9 @@ export class ProbeAgent {
|
|
|
203
234
|
} else if (forceProvider === 'google' && googleApiKey) {
|
|
204
235
|
this.initializeGoogleModel(googleApiKey, googleApiUrl, modelName);
|
|
205
236
|
return;
|
|
237
|
+
} else if (forceProvider === 'bedrock' && ((awsAccessKeyId && awsSecretAccessKey && awsRegion) || awsApiKey)) {
|
|
238
|
+
this.initializeBedrockModel(awsAccessKeyId, awsSecretAccessKey, awsRegion, awsSessionToken, awsApiKey, awsBedrockBaseUrl, modelName);
|
|
239
|
+
return;
|
|
206
240
|
}
|
|
207
241
|
console.warn(`WARNING: Forced provider "${forceProvider}" selected but required API key is missing or invalid! Falling back to auto-detection.`);
|
|
208
242
|
}
|
|
@@ -214,8 +248,10 @@ export class ProbeAgent {
|
|
|
214
248
|
this.initializeOpenAIModel(openaiApiKey, openaiApiUrl, modelName);
|
|
215
249
|
} else if (googleApiKey) {
|
|
216
250
|
this.initializeGoogleModel(googleApiKey, googleApiUrl, modelName);
|
|
251
|
+
} else if ((awsAccessKeyId && awsSecretAccessKey && awsRegion) || awsApiKey) {
|
|
252
|
+
this.initializeBedrockModel(awsAccessKeyId, awsSecretAccessKey, awsRegion, awsSessionToken, awsApiKey, awsBedrockBaseUrl, modelName);
|
|
217
253
|
} else {
|
|
218
|
-
throw new Error('No API key provided. Please set ANTHROPIC_API_KEY, OPENAI_API_KEY, or
|
|
254
|
+
throw new Error('No API key provided. Please set ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY, AWS credentials (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION), or AWS_BEDROCK_API_KEY environment variables.');
|
|
219
255
|
}
|
|
220
256
|
}
|
|
221
257
|
|
|
@@ -268,6 +304,266 @@ export class ProbeAgent {
|
|
|
268
304
|
}
|
|
269
305
|
}
|
|
270
306
|
|
|
307
|
+
/**
|
|
308
|
+
* Initialize AWS Bedrock model
|
|
309
|
+
*/
|
|
310
|
+
initializeBedrockModel(accessKeyId, secretAccessKey, region, sessionToken, apiKey, baseURL, modelName) {
|
|
311
|
+
// Build configuration object, only including defined values
|
|
312
|
+
const config = {};
|
|
313
|
+
|
|
314
|
+
// Authentication - prefer API key if provided, otherwise use AWS credentials
|
|
315
|
+
if (apiKey) {
|
|
316
|
+
config.apiKey = apiKey;
|
|
317
|
+
} else if (accessKeyId && secretAccessKey) {
|
|
318
|
+
config.accessKeyId = accessKeyId;
|
|
319
|
+
config.secretAccessKey = secretAccessKey;
|
|
320
|
+
if (sessionToken) {
|
|
321
|
+
config.sessionToken = sessionToken;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Region is required for AWS credentials but optional for API key
|
|
326
|
+
if (region) {
|
|
327
|
+
config.region = region;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Optional base URL
|
|
331
|
+
if (baseURL) {
|
|
332
|
+
config.baseURL = baseURL;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
this.provider = createAmazonBedrock(config);
|
|
336
|
+
this.model = modelName || 'anthropic.claude-sonnet-4-20250514-v1:0';
|
|
337
|
+
this.apiType = 'bedrock';
|
|
338
|
+
|
|
339
|
+
if (this.debug) {
|
|
340
|
+
const authMethod = apiKey ? 'API Key' : 'AWS Credentials';
|
|
341
|
+
const regionInfo = region ? ` (Region: ${region})` : '';
|
|
342
|
+
const baseUrlInfo = baseURL ? ` (Base URL: ${baseURL})` : '';
|
|
343
|
+
console.log(`Using AWS Bedrock API with model: ${this.model}${regionInfo} [Auth: ${authMethod}]${baseUrlInfo}`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Process assistant response content and detect/load image references
|
|
349
|
+
* @param {string} content - The assistant's response content
|
|
350
|
+
* @returns {Promise<void>}
|
|
351
|
+
*/
|
|
352
|
+
async processImageReferences(content) {
|
|
353
|
+
if (!content) return;
|
|
354
|
+
|
|
355
|
+
// Enhanced pattern to detect image file mentions in various contexts
|
|
356
|
+
// Looks for: "image", "file", "screenshot", etc. followed by path-like strings with image extensions
|
|
357
|
+
const extensionsPattern = `(?:${SUPPORTED_IMAGE_EXTENSIONS.join('|')})`;
|
|
358
|
+
const imagePatterns = [
|
|
359
|
+
// Direct file path mentions: "./screenshot.png", "/path/to/image.jpg", etc.
|
|
360
|
+
new RegExp(`(?:\\.?\\.\\/)?[^\\s"'<>\\[\\]]+\\\.${extensionsPattern}(?!\\w)`, 'gi'),
|
|
361
|
+
// Contextual mentions: "look at image.png", "the file screenshot.jpg shows"
|
|
362
|
+
new RegExp(`(?:image|file|screenshot|diagram|photo|picture|graphic)\\s*:?\\s*([^\\s"'<>\\[\\]]+\\.${extensionsPattern})(?!\\w)`, 'gi'),
|
|
363
|
+
// Tool result mentions: often contain file paths
|
|
364
|
+
new RegExp(`(?:found|saved|created|generated).*?([^\\s"'<>\\[\\]]+\\.${extensionsPattern})(?!\\w)`, 'gi')
|
|
365
|
+
];
|
|
366
|
+
|
|
367
|
+
const foundPaths = new Set();
|
|
368
|
+
|
|
369
|
+
// Extract potential image paths using all patterns
|
|
370
|
+
for (const pattern of imagePatterns) {
|
|
371
|
+
let match;
|
|
372
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
373
|
+
// For patterns with capture groups, use the captured path; otherwise use the full match
|
|
374
|
+
const imagePath = match[1] || match[0];
|
|
375
|
+
if (imagePath && imagePath.length > 0) {
|
|
376
|
+
foundPaths.add(imagePath.trim());
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (foundPaths.size === 0) return;
|
|
382
|
+
|
|
383
|
+
if (this.debug) {
|
|
384
|
+
console.log(`[DEBUG] Found ${foundPaths.size} potential image references:`, Array.from(foundPaths));
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Process each found path
|
|
388
|
+
for (const imagePath of foundPaths) {
|
|
389
|
+
await this.loadImageIfValid(imagePath);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Load and cache an image if it's valid and accessible
|
|
395
|
+
* @param {string} imagePath - Path to the image file
|
|
396
|
+
* @returns {Promise<boolean>} - True if image was loaded successfully
|
|
397
|
+
*/
|
|
398
|
+
async loadImageIfValid(imagePath) {
|
|
399
|
+
try {
|
|
400
|
+
// Skip if already loaded
|
|
401
|
+
if (this.pendingImages.has(imagePath)) {
|
|
402
|
+
if (this.debug) {
|
|
403
|
+
console.log(`[DEBUG] Image already loaded: ${imagePath}`);
|
|
404
|
+
}
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Security validation: check if path is within any allowed directory
|
|
409
|
+
const allowedDirs = this.allowedFolders && this.allowedFolders.length > 0 ? this.allowedFolders : [process.cwd()];
|
|
410
|
+
|
|
411
|
+
let absolutePath;
|
|
412
|
+
let isPathAllowed = false;
|
|
413
|
+
|
|
414
|
+
// If absolute path, check if it's within any allowed directory
|
|
415
|
+
if (isAbsolute(imagePath)) {
|
|
416
|
+
absolutePath = imagePath;
|
|
417
|
+
isPathAllowed = allowedDirs.some(dir => absolutePath.startsWith(resolve(dir)));
|
|
418
|
+
} else {
|
|
419
|
+
// For relative paths, try resolving against each allowed directory
|
|
420
|
+
for (const dir of allowedDirs) {
|
|
421
|
+
const resolvedPath = resolve(dir, imagePath);
|
|
422
|
+
if (resolvedPath.startsWith(resolve(dir))) {
|
|
423
|
+
absolutePath = resolvedPath;
|
|
424
|
+
isPathAllowed = true;
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Security check: ensure path is within at least one allowed directory
|
|
431
|
+
if (!isPathAllowed) {
|
|
432
|
+
if (this.debug) {
|
|
433
|
+
console.log(`[DEBUG] Image path outside allowed directories: ${imagePath}`);
|
|
434
|
+
}
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Check if file exists and get file stats
|
|
439
|
+
let fileStats;
|
|
440
|
+
try {
|
|
441
|
+
fileStats = await stat(absolutePath);
|
|
442
|
+
} catch (error) {
|
|
443
|
+
if (this.debug) {
|
|
444
|
+
console.log(`[DEBUG] Image file not found: ${absolutePath}`);
|
|
445
|
+
}
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Validate file size to prevent OOM attacks
|
|
450
|
+
if (fileStats.size > MAX_IMAGE_FILE_SIZE) {
|
|
451
|
+
if (this.debug) {
|
|
452
|
+
console.log(`[DEBUG] Image file too large: ${absolutePath} (${fileStats.size} bytes, max: ${MAX_IMAGE_FILE_SIZE})`);
|
|
453
|
+
}
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Validate file extension
|
|
458
|
+
const extension = absolutePath.toLowerCase().split('.').pop();
|
|
459
|
+
if (!SUPPORTED_IMAGE_EXTENSIONS.includes(extension)) {
|
|
460
|
+
if (this.debug) {
|
|
461
|
+
console.log(`[DEBUG] Unsupported image format: ${extension}`);
|
|
462
|
+
}
|
|
463
|
+
return false;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Determine MIME type
|
|
467
|
+
const mimeTypes = {
|
|
468
|
+
'png': 'image/png',
|
|
469
|
+
'jpg': 'image/jpeg',
|
|
470
|
+
'jpeg': 'image/jpeg',
|
|
471
|
+
'webp': 'image/webp',
|
|
472
|
+
'gif': 'image/gif',
|
|
473
|
+
'bmp': 'image/bmp',
|
|
474
|
+
'svg': 'image/svg+xml'
|
|
475
|
+
};
|
|
476
|
+
const mimeType = mimeTypes[extension];
|
|
477
|
+
|
|
478
|
+
// Read and encode file asynchronously
|
|
479
|
+
const fileBuffer = await readFile(absolutePath);
|
|
480
|
+
const base64Data = fileBuffer.toString('base64');
|
|
481
|
+
const dataUrl = `data:${mimeType};base64,${base64Data}`;
|
|
482
|
+
|
|
483
|
+
// Cache the loaded image
|
|
484
|
+
this.pendingImages.set(imagePath, dataUrl);
|
|
485
|
+
|
|
486
|
+
if (this.debug) {
|
|
487
|
+
console.log(`[DEBUG] Successfully loaded image: ${imagePath} (${fileBuffer.length} bytes)`);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return true;
|
|
491
|
+
} catch (error) {
|
|
492
|
+
if (this.debug) {
|
|
493
|
+
console.log(`[DEBUG] Failed to load image ${imagePath}: ${error.message}`);
|
|
494
|
+
}
|
|
495
|
+
return false;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Get all currently loaded images as an array for AI model consumption
|
|
501
|
+
* @returns {Array<string>} - Array of base64 data URLs
|
|
502
|
+
*/
|
|
503
|
+
getCurrentImages() {
|
|
504
|
+
return Array.from(this.pendingImages.values());
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Clear loaded images (useful for new conversations)
|
|
509
|
+
*/
|
|
510
|
+
clearLoadedImages() {
|
|
511
|
+
this.pendingImages.clear();
|
|
512
|
+
this.currentImages = [];
|
|
513
|
+
if (this.debug) {
|
|
514
|
+
console.log('[DEBUG] Cleared all loaded images');
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Prepare messages for AI consumption, adding images to the latest user message if available
|
|
520
|
+
* @param {Array} messages - Current conversation messages
|
|
521
|
+
* @returns {Array} - Messages formatted for AI SDK with potential image content
|
|
522
|
+
*/
|
|
523
|
+
prepareMessagesWithImages(messages) {
|
|
524
|
+
const loadedImages = this.getCurrentImages();
|
|
525
|
+
|
|
526
|
+
// If no images loaded, return messages as-is
|
|
527
|
+
if (loadedImages.length === 0) {
|
|
528
|
+
return messages;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Clone messages to avoid mutating the original
|
|
532
|
+
const messagesWithImages = [...messages];
|
|
533
|
+
|
|
534
|
+
// Find the last user message to attach images to
|
|
535
|
+
const lastUserMessageIndex = messagesWithImages.map(m => m.role).lastIndexOf('user');
|
|
536
|
+
|
|
537
|
+
if (lastUserMessageIndex === -1) {
|
|
538
|
+
if (this.debug) {
|
|
539
|
+
console.log('[DEBUG] No user messages found to attach images to');
|
|
540
|
+
}
|
|
541
|
+
return messages;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const lastUserMessage = messagesWithImages[lastUserMessageIndex];
|
|
545
|
+
|
|
546
|
+
// Convert to multimodal format if we have images
|
|
547
|
+
if (typeof lastUserMessage.content === 'string') {
|
|
548
|
+
messagesWithImages[lastUserMessageIndex] = {
|
|
549
|
+
...lastUserMessage,
|
|
550
|
+
content: [
|
|
551
|
+
{ type: 'text', text: lastUserMessage.content },
|
|
552
|
+
...loadedImages.map(imageData => ({
|
|
553
|
+
type: 'image',
|
|
554
|
+
image: imageData
|
|
555
|
+
}))
|
|
556
|
+
]
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
if (this.debug) {
|
|
560
|
+
console.log(`[DEBUG] Added ${loadedImages.length} images to the latest user message`);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
return messagesWithImages;
|
|
565
|
+
}
|
|
566
|
+
|
|
271
567
|
/**
|
|
272
568
|
* Initialize mock model for testing
|
|
273
569
|
*/
|
|
@@ -695,9 +991,12 @@ When troubleshooting:
|
|
|
695
991
|
try {
|
|
696
992
|
// Wrap AI request with tracing if available
|
|
697
993
|
const executeAIRequest = async () => {
|
|
994
|
+
// Prepare messages with potential image content
|
|
995
|
+
const messagesForAI = this.prepareMessagesWithImages(currentMessages);
|
|
996
|
+
|
|
698
997
|
const result = await streamText({
|
|
699
998
|
model: this.provider(this.model),
|
|
700
|
-
messages:
|
|
999
|
+
messages: messagesForAI,
|
|
701
1000
|
maxTokens: maxResponseTokens,
|
|
702
1001
|
temperature: 0.3,
|
|
703
1002
|
});
|
|
@@ -741,6 +1040,11 @@ When troubleshooting:
|
|
|
741
1040
|
console.log(`[DEBUG] Assistant response (${assistantResponseContent.length} chars): ${assistantPreview}`);
|
|
742
1041
|
}
|
|
743
1042
|
|
|
1043
|
+
// Process image references in assistant response for next iteration
|
|
1044
|
+
if (assistantResponseContent) {
|
|
1045
|
+
await this.processImageReferences(assistantResponseContent);
|
|
1046
|
+
}
|
|
1047
|
+
|
|
744
1048
|
// Parse tool call from response with valid tools list
|
|
745
1049
|
const validTools = [
|
|
746
1050
|
'search', 'query', 'extract', 'listFiles', 'searchFiles', 'attempt_completion'
|
|
@@ -819,8 +1123,12 @@ When troubleshooting:
|
|
|
819
1123
|
} else if (this.toolImplementations[toolName]) {
|
|
820
1124
|
// Execute native tool
|
|
821
1125
|
try {
|
|
822
|
-
// Add sessionId to params for tool execution
|
|
823
|
-
const toolParams = {
|
|
1126
|
+
// Add sessionId and workingDirectory to params for tool execution
|
|
1127
|
+
const toolParams = {
|
|
1128
|
+
...params,
|
|
1129
|
+
sessionId: this.sessionId,
|
|
1130
|
+
workingDirectory: (this.allowedFolders && this.allowedFolders[0]) || process.cwd()
|
|
1131
|
+
};
|
|
824
1132
|
|
|
825
1133
|
// Emit tool start event
|
|
826
1134
|
this.events.emit('toolCall', {
|
|
@@ -898,11 +1206,20 @@ When troubleshooting:
|
|
|
898
1206
|
|
|
899
1207
|
// Add assistant response and tool result to conversation
|
|
900
1208
|
currentMessages.push({ role: 'assistant', content: assistantResponseContent });
|
|
1209
|
+
|
|
1210
|
+
const toolResultContent = typeof toolResult === 'string' ? toolResult : JSON.stringify(toolResult, null, 2);
|
|
1211
|
+
const toolResultMessage = `<tool_result>\n${toolResultContent}\n</tool_result>`;
|
|
1212
|
+
|
|
901
1213
|
currentMessages.push({
|
|
902
1214
|
role: 'user',
|
|
903
|
-
content:
|
|
1215
|
+
content: toolResultMessage
|
|
904
1216
|
});
|
|
905
1217
|
|
|
1218
|
+
// Process tool result for image references
|
|
1219
|
+
if (toolResultContent) {
|
|
1220
|
+
await this.processImageReferences(toolResultContent);
|
|
1221
|
+
}
|
|
1222
|
+
|
|
906
1223
|
if (this.debug) {
|
|
907
1224
|
console.log(`[DEBUG] Tool ${toolName} executed successfully. Result length: ${typeof toolResult === 'string' ? toolResult.length : JSON.stringify(toolResult).length}`);
|
|
908
1225
|
}
|
|
@@ -964,8 +1281,10 @@ Remember: Use proper XML format with BOTH opening and closing tags:
|
|
|
964
1281
|
<parameter>value</parameter>
|
|
965
1282
|
</tool_name>
|
|
966
1283
|
|
|
967
|
-
Or for quick completion if your previous response was already correct:
|
|
968
|
-
<attempt_complete
|
|
1284
|
+
Or for quick completion if your previous response was already correct and complete:
|
|
1285
|
+
<attempt_complete>
|
|
1286
|
+
|
|
1287
|
+
IMPORTANT: When using <attempt_complete>, this must be the ONLY content in your response. No additional text, explanations, or other content should be included. This tag signals to reuse your previous response as the final answer.`;
|
|
969
1288
|
}
|
|
970
1289
|
|
|
971
1290
|
currentMessages.push({
|
|
@@ -1483,4 +1802,4 @@ Convert your previous response content into actual JSON data that follows this s
|
|
|
1483
1802
|
console.log(`[DEBUG] Agent cancelled for session ${this.sessionId}`);
|
|
1484
1803
|
}
|
|
1485
1804
|
}
|
|
1486
|
-
}
|
|
1805
|
+
}
|