@probelabs/probe 0.6.0-rc305 → 0.6.0-rc307
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/bin/binaries/{probe-v0.6.0-rc305-aarch64-apple-darwin.tar.gz → probe-v0.6.0-rc307-aarch64-apple-darwin.tar.gz} +0 -0
- package/bin/binaries/{probe-v0.6.0-rc305-aarch64-unknown-linux-musl.tar.gz → probe-v0.6.0-rc307-aarch64-unknown-linux-musl.tar.gz} +0 -0
- package/bin/binaries/{probe-v0.6.0-rc305-x86_64-apple-darwin.tar.gz → probe-v0.6.0-rc307-x86_64-apple-darwin.tar.gz} +0 -0
- package/bin/binaries/{probe-v0.6.0-rc305-x86_64-pc-windows-msvc.zip → probe-v0.6.0-rc307-x86_64-pc-windows-msvc.zip} +0 -0
- package/bin/binaries/{probe-v0.6.0-rc305-x86_64-unknown-linux-musl.tar.gz → probe-v0.6.0-rc307-x86_64-unknown-linux-musl.tar.gz} +0 -0
- package/build/agent/ProbeAgent.js +137 -101
- package/build/agent/mediaConfig.js +122 -0
- package/build/agent/tools.js +2 -0
- package/build/delegate.js +6 -2
- package/build/index.js +2 -0
- package/build/tools/common.js +4 -0
- package/build/tools/index.js +1 -0
- package/build/tools/vercel.js +4 -1
- package/cjs/agent/ProbeAgent.cjs +157 -83
- package/cjs/index.cjs +160 -83
- package/package.json +1 -1
- package/src/agent/ProbeAgent.js +137 -101
- package/src/agent/mediaConfig.js +122 -0
- package/src/agent/tools.js +2 -0
- package/src/delegate.js +6 -2
- package/src/index.js +2 -0
- package/src/tools/common.js +4 -0
- package/src/tools/index.js +1 -0
- package/src/tools/vercel.js +4 -1
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -38,7 +38,7 @@ import { TokenCounter } from './tokenCounter.js';
|
|
|
38
38
|
import { truncateForSpan } from './simpleTelemetry.js';
|
|
39
39
|
import { InMemoryStorageAdapter } from './storage/InMemoryStorageAdapter.js';
|
|
40
40
|
import { HookManager, HOOK_TYPES } from './hooks/HookManager.js';
|
|
41
|
-
import { SUPPORTED_IMAGE_EXTENSIONS, IMAGE_MIME_TYPES, isFormatSupportedByProvider } from './
|
|
41
|
+
import { SUPPORTED_IMAGE_EXTENSIONS, SUPPORTED_MEDIA_EXTENSIONS, MEDIA_MIME_TYPES, IMAGE_MIME_TYPES, isFormatSupportedByProvider, isImageExtension, isDocumentExtension } from './mediaConfig.js';
|
|
42
42
|
import {
|
|
43
43
|
createTools,
|
|
44
44
|
searchSchema,
|
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
listFilesSchema,
|
|
56
56
|
searchFilesSchema,
|
|
57
57
|
readImageSchema,
|
|
58
|
+
readMediaSchema,
|
|
58
59
|
listSkillsSchema,
|
|
59
60
|
useSkillSchema
|
|
60
61
|
} from './tools.js';
|
|
@@ -121,6 +122,8 @@ const MAX_HISTORY_MESSAGES = 100;
|
|
|
121
122
|
|
|
122
123
|
// Maximum image file size (20MB) to prevent OOM attacks
|
|
123
124
|
const MAX_IMAGE_FILE_SIZE = 20 * 1024 * 1024;
|
|
125
|
+
// Maximum document file size (32MB) — Claude's limit is 32MB, OpenAI 50MB, Gemini 50MB
|
|
126
|
+
const MAX_DOCUMENT_FILE_SIZE = 32 * 1024 * 1024;
|
|
124
127
|
|
|
125
128
|
/**
|
|
126
129
|
* Truncate a string for debug logging, showing first and last portion.
|
|
@@ -882,6 +885,18 @@ export class ProbeAgent {
|
|
|
882
885
|
outputBuffer: this._outputBuffer,
|
|
883
886
|
concurrencyLimiter: this.concurrencyLimiter, // Global AI concurrency limiter
|
|
884
887
|
isToolAllowed,
|
|
888
|
+
// MCP config for delegate subagents — these are set in constructor before tools init,
|
|
889
|
+
// so they're available here. The delegate tool closure needs them to pass to subagents
|
|
890
|
+
// so they can create their own MCPXmlBridge instances.
|
|
891
|
+
enableMcp: this.enableMcp,
|
|
892
|
+
mcpConfig: this.mcpConfig,
|
|
893
|
+
mcpConfigPath: this.mcpConfigPath,
|
|
894
|
+
// Pass parent's prompt settings so delegate subagents inherit the same persona/capabilities.
|
|
895
|
+
// Without promptType, delegate() defaulted to 'code-researcher' which doesn't exist,
|
|
896
|
+
// causing fallback to the read-only 'code-explorer' prompt.
|
|
897
|
+
promptType: this.promptType,
|
|
898
|
+
customPrompt: this.customPrompt,
|
|
899
|
+
completionPrompt: this.completionPrompt,
|
|
885
900
|
// Lazy MCP getters — MCP is initialized after tools are created, so we use
|
|
886
901
|
// getter functions that resolve at call-time to get the current MCP state
|
|
887
902
|
getMcpBridge: () => this.mcpBridge,
|
|
@@ -946,40 +961,43 @@ export class ProbeAgent {
|
|
|
946
961
|
}
|
|
947
962
|
}
|
|
948
963
|
|
|
949
|
-
//
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
throw new Error('Image path is required');
|
|
956
|
-
}
|
|
964
|
+
// Media loading tool (images + PDFs)
|
|
965
|
+
const readMediaExecute = async (params) => {
|
|
966
|
+
const mediaPath = params.path;
|
|
967
|
+
if (!mediaPath) {
|
|
968
|
+
throw new Error('File path is required');
|
|
969
|
+
}
|
|
957
970
|
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
971
|
+
// Validate extension before attempting to load
|
|
972
|
+
// Use basename to prevent path traversal attacks (e.g., 'malicious.jpg/../../../etc/passwd')
|
|
973
|
+
const filename = basename(mediaPath);
|
|
974
|
+
const extension = filename.toLowerCase().split('.').pop();
|
|
962
975
|
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
976
|
+
// Always validate extension is in allowed list (defense-in-depth)
|
|
977
|
+
if (!extension || !SUPPORTED_MEDIA_EXTENSIONS.includes(extension)) {
|
|
978
|
+
throw new Error(`Unsupported file format: ${extension}. Supported formats: ${SUPPORTED_MEDIA_EXTENSIONS.join(', ')}`);
|
|
979
|
+
}
|
|
967
980
|
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
981
|
+
// Check provider-specific format restrictions (e.g., SVG not supported by Google Gemini)
|
|
982
|
+
if (this.apiType && !isFormatSupportedByProvider(extension, this.apiType)) {
|
|
983
|
+
throw new Error(`File format '${extension}' is not supported by the current AI provider (${this.apiType}). Try converting to a different format.`);
|
|
984
|
+
}
|
|
972
985
|
|
|
973
|
-
|
|
974
|
-
|
|
986
|
+
// Load the media file
|
|
987
|
+
const loaded = await this.loadMediaIfValid(mediaPath);
|
|
975
988
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
989
|
+
if (!loaded) {
|
|
990
|
+
throw new Error(`Failed to load file: ${mediaPath}. The file may not exist, be too large, have an unsupported format, or be outside allowed directories.`);
|
|
991
|
+
}
|
|
979
992
|
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
993
|
+
const mediaType = isDocumentExtension(extension) ? 'Document' : 'Image';
|
|
994
|
+
return `${mediaType} loaded successfully: ${mediaPath}. The file is now available for analysis in the conversation.`;
|
|
995
|
+
};
|
|
996
|
+
|
|
997
|
+
if (isToolAllowed('readMedia') || isToolAllowed('readImage')) {
|
|
998
|
+
this.toolImplementations.readMedia = { execute: readMediaExecute };
|
|
999
|
+
// Keep readImage as backward-compatible alias
|
|
1000
|
+
this.toolImplementations.readImage = { execute: readMediaExecute };
|
|
983
1001
|
}
|
|
984
1002
|
|
|
985
1003
|
// Add bash tool if enabled and allowed
|
|
@@ -2207,9 +2225,9 @@ export class ProbeAgent {
|
|
|
2207
2225
|
schema: searchFilesSchema,
|
|
2208
2226
|
description: 'Find files matching a glob pattern with recursive search capability.'
|
|
2209
2227
|
},
|
|
2210
|
-
|
|
2211
|
-
schema:
|
|
2212
|
-
description: 'Read and load
|
|
2228
|
+
readMedia: {
|
|
2229
|
+
schema: readMediaSchema,
|
|
2230
|
+
description: 'Read and load a media file (image or PDF document) for AI analysis. Supports: png, jpg, jpeg, webp, bmp, svg, pdf.'
|
|
2213
2231
|
},
|
|
2214
2232
|
listSkills: {
|
|
2215
2233
|
schema: listSkillsSchema,
|
|
@@ -2371,7 +2389,7 @@ export class ProbeAgent {
|
|
|
2371
2389
|
|
|
2372
2390
|
// Enhanced pattern to detect image file mentions in various contexts
|
|
2373
2391
|
// Looks for: "image", "file", "screenshot", etc. followed by path-like strings with image extensions
|
|
2374
|
-
const extensionsPattern = `(?:${
|
|
2392
|
+
const extensionsPattern = `(?:${SUPPORTED_MEDIA_EXTENSIONS.join('|')})`;
|
|
2375
2393
|
const imagePatterns = [
|
|
2376
2394
|
// Direct file path mentions: "./screenshot.png", "/path/to/image.jpg", etc.
|
|
2377
2395
|
new RegExp(`(?:\\.?\\.\\/)?[^\\s"'<>\\[\\]]+\\\.${extensionsPattern}(?!\\w)`, 'gi'),
|
|
@@ -2492,43 +2510,36 @@ export class ProbeAgent {
|
|
|
2492
2510
|
}
|
|
2493
2511
|
|
|
2494
2512
|
/**
|
|
2495
|
-
* Load and cache
|
|
2496
|
-
* @param {string}
|
|
2497
|
-
* @returns {Promise<boolean>} - True if
|
|
2513
|
+
* Load and cache a media file (image or PDF) if it's valid and accessible
|
|
2514
|
+
* @param {string} mediaPath - Path to the media file
|
|
2515
|
+
* @returns {Promise<boolean>} - True if file was loaded successfully
|
|
2498
2516
|
*/
|
|
2499
|
-
async
|
|
2517
|
+
async loadMediaIfValid(mediaPath) {
|
|
2500
2518
|
try {
|
|
2501
2519
|
// Skip if already loaded
|
|
2502
|
-
if (this.pendingImages.has(
|
|
2520
|
+
if (this.pendingImages.has(mediaPath)) {
|
|
2503
2521
|
if (this.debug) {
|
|
2504
|
-
console.log(`[DEBUG]
|
|
2522
|
+
console.log(`[DEBUG] Media already loaded: ${mediaPath}`);
|
|
2505
2523
|
}
|
|
2506
2524
|
return true;
|
|
2507
2525
|
}
|
|
2508
2526
|
|
|
2509
2527
|
// Security validation: check if path is within any allowed directory
|
|
2510
|
-
// Use safeRealpath() to resolve symlinks and handle path traversal attempts (e.g., '/allowed/../etc/passwd')
|
|
2511
|
-
// This prevents symlink bypass attacks (e.g., /tmp -> /private/tmp on macOS)
|
|
2512
2528
|
const allowedDirs = this.allowedFolders && this.allowedFolders.length > 0 ? this.allowedFolders : [process.cwd()];
|
|
2513
2529
|
|
|
2514
2530
|
let absolutePath;
|
|
2515
2531
|
let isPathAllowed = false;
|
|
2516
2532
|
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
// Use safeRealpath to resolve symlinks for security
|
|
2520
|
-
absolutePath = safeRealpath(resolve(imagePath));
|
|
2533
|
+
if (isAbsolute(mediaPath)) {
|
|
2534
|
+
absolutePath = safeRealpath(resolve(mediaPath));
|
|
2521
2535
|
isPathAllowed = allowedDirs.some(dir => {
|
|
2522
2536
|
const resolvedDir = safeRealpath(dir);
|
|
2523
|
-
// Ensure the path is within the allowed directory (add separator to prevent prefix attacks)
|
|
2524
2537
|
return absolutePath === resolvedDir || absolutePath.startsWith(resolvedDir + sep);
|
|
2525
2538
|
});
|
|
2526
2539
|
} else {
|
|
2527
|
-
// For relative paths, try resolving against each allowed directory
|
|
2528
2540
|
for (const dir of allowedDirs) {
|
|
2529
2541
|
const resolvedDir = safeRealpath(dir);
|
|
2530
|
-
const resolvedPath = safeRealpath(resolve(dir,
|
|
2531
|
-
// Ensure the resolved path is within the allowed directory
|
|
2542
|
+
const resolvedPath = safeRealpath(resolve(dir, mediaPath));
|
|
2532
2543
|
if (resolvedPath === resolvedDir || resolvedPath.startsWith(resolvedDir + sep)) {
|
|
2533
2544
|
absolutePath = resolvedPath;
|
|
2534
2545
|
isPathAllowed = true;
|
|
@@ -2536,137 +2547,162 @@ export class ProbeAgent {
|
|
|
2536
2547
|
}
|
|
2537
2548
|
}
|
|
2538
2549
|
}
|
|
2539
|
-
|
|
2540
|
-
// Security check: ensure path is within at least one allowed directory
|
|
2550
|
+
|
|
2541
2551
|
if (!isPathAllowed) {
|
|
2542
2552
|
if (this.debug) {
|
|
2543
|
-
console.log(`[DEBUG]
|
|
2553
|
+
console.log(`[DEBUG] Media path outside allowed directories: ${mediaPath}`);
|
|
2544
2554
|
}
|
|
2545
2555
|
return false;
|
|
2546
2556
|
}
|
|
2547
2557
|
|
|
2548
|
-
// Check if file exists and get file stats
|
|
2549
2558
|
let fileStats;
|
|
2550
2559
|
try {
|
|
2551
2560
|
fileStats = await stat(absolutePath);
|
|
2552
2561
|
} catch (error) {
|
|
2553
2562
|
if (this.debug) {
|
|
2554
|
-
console.log(`[DEBUG]
|
|
2563
|
+
console.log(`[DEBUG] Media file not found: ${absolutePath}`);
|
|
2555
2564
|
}
|
|
2556
2565
|
return false;
|
|
2557
2566
|
}
|
|
2558
2567
|
|
|
2559
|
-
|
|
2560
|
-
if (
|
|
2568
|
+
const extension = absolutePath.toLowerCase().split('.').pop();
|
|
2569
|
+
if (!SUPPORTED_MEDIA_EXTENSIONS.includes(extension)) {
|
|
2561
2570
|
if (this.debug) {
|
|
2562
|
-
console.log(`[DEBUG]
|
|
2571
|
+
console.log(`[DEBUG] Unsupported media format: ${extension}`);
|
|
2563
2572
|
}
|
|
2564
2573
|
return false;
|
|
2565
2574
|
}
|
|
2566
2575
|
|
|
2567
|
-
//
|
|
2568
|
-
const
|
|
2569
|
-
if (
|
|
2576
|
+
// Apply size limit based on media type
|
|
2577
|
+
const maxSize = isDocumentExtension(extension) ? MAX_DOCUMENT_FILE_SIZE : MAX_IMAGE_FILE_SIZE;
|
|
2578
|
+
if (fileStats.size > maxSize) {
|
|
2570
2579
|
if (this.debug) {
|
|
2571
|
-
console.log(`[DEBUG]
|
|
2580
|
+
console.log(`[DEBUG] Media file too large: ${absolutePath} (${fileStats.size} bytes, max: ${maxSize})`);
|
|
2572
2581
|
}
|
|
2573
2582
|
return false;
|
|
2574
2583
|
}
|
|
2575
2584
|
|
|
2576
|
-
|
|
2577
|
-
// is handled by the readImage tool which provides explicit error messages.
|
|
2578
|
-
// loadImageIfValid is a lower-level method that only checks general format support.
|
|
2579
|
-
|
|
2580
|
-
// Determine MIME type (from shared config)
|
|
2581
|
-
const mimeType = IMAGE_MIME_TYPES[extension];
|
|
2582
|
-
|
|
2583
|
-
// Read and encode file asynchronously
|
|
2585
|
+
const mimeType = MEDIA_MIME_TYPES[extension];
|
|
2584
2586
|
const fileBuffer = await readFile(absolutePath);
|
|
2585
2587
|
const base64Data = fileBuffer.toString('base64');
|
|
2586
|
-
const dataUrl = `data:${mimeType};base64,${base64Data}`;
|
|
2587
2588
|
|
|
2588
|
-
|
|
2589
|
-
|
|
2589
|
+
if (isDocumentExtension(extension)) {
|
|
2590
|
+
// Store documents as objects with metadata for the 'file' content part
|
|
2591
|
+
this.pendingImages.set(mediaPath, {
|
|
2592
|
+
type: 'document',
|
|
2593
|
+
mimeType,
|
|
2594
|
+
data: base64Data,
|
|
2595
|
+
filename: basename(mediaPath)
|
|
2596
|
+
});
|
|
2597
|
+
} else {
|
|
2598
|
+
// Store images as data URLs (backward compatible)
|
|
2599
|
+
const dataUrl = `data:${mimeType};base64,${base64Data}`;
|
|
2600
|
+
this.pendingImages.set(mediaPath, dataUrl);
|
|
2601
|
+
}
|
|
2590
2602
|
|
|
2591
2603
|
if (this.debug) {
|
|
2592
|
-
console.log(`[DEBUG] Successfully loaded
|
|
2604
|
+
console.log(`[DEBUG] Successfully loaded media: ${mediaPath} (${fileBuffer.length} bytes, ${mimeType})`);
|
|
2593
2605
|
}
|
|
2594
2606
|
|
|
2595
2607
|
return true;
|
|
2596
2608
|
} catch (error) {
|
|
2597
2609
|
if (this.debug) {
|
|
2598
|
-
console.log(`[DEBUG] Failed to load
|
|
2610
|
+
console.log(`[DEBUG] Failed to load media ${mediaPath}: ${error.message}`);
|
|
2599
2611
|
}
|
|
2600
2612
|
return false;
|
|
2601
2613
|
}
|
|
2602
2614
|
}
|
|
2603
2615
|
|
|
2616
|
+
/**
|
|
2617
|
+
* Backward-compatible alias for loadMediaIfValid
|
|
2618
|
+
* @param {string} imagePath - Path to the image file
|
|
2619
|
+
* @returns {Promise<boolean>}
|
|
2620
|
+
*/
|
|
2621
|
+
async loadImageIfValid(imagePath) {
|
|
2622
|
+
return this.loadMediaIfValid(imagePath);
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2604
2625
|
/**
|
|
2605
2626
|
* Get all currently loaded images as an array for AI model consumption
|
|
2606
|
-
* @returns {Array<string>} - Array of base64 data URLs
|
|
2627
|
+
* @returns {Array<string>} - Array of base64 data URLs (images only, for backward compat)
|
|
2607
2628
|
*/
|
|
2608
2629
|
getCurrentImages() {
|
|
2609
|
-
return Array.from(this.pendingImages.values());
|
|
2630
|
+
return Array.from(this.pendingImages.values()).filter(v => typeof v === 'string');
|
|
2610
2631
|
}
|
|
2611
2632
|
|
|
2612
2633
|
/**
|
|
2613
|
-
*
|
|
2634
|
+
* Get all currently loaded media as an array of content parts
|
|
2635
|
+
* @returns {Array<Object>} - Array of Vercel AI SDK content parts
|
|
2636
|
+
*/
|
|
2637
|
+
getCurrentMedia() {
|
|
2638
|
+
const parts = [];
|
|
2639
|
+
for (const entry of this.pendingImages.values()) {
|
|
2640
|
+
if (typeof entry === 'string') {
|
|
2641
|
+
// Image data URL
|
|
2642
|
+
parts.push({ type: 'image', image: entry });
|
|
2643
|
+
} else if (entry && entry.type === 'document') {
|
|
2644
|
+
// Document (PDF) — use Vercel AI SDK 'file' content part
|
|
2645
|
+
parts.push({
|
|
2646
|
+
type: 'file',
|
|
2647
|
+
mediaType: entry.mimeType,
|
|
2648
|
+
data: entry.data,
|
|
2649
|
+
filename: entry.filename
|
|
2650
|
+
});
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
return parts;
|
|
2654
|
+
}
|
|
2655
|
+
|
|
2656
|
+
/**
|
|
2657
|
+
* Clear loaded media (useful for new conversations)
|
|
2614
2658
|
*/
|
|
2615
2659
|
clearLoadedImages() {
|
|
2616
2660
|
this.pendingImages.clear();
|
|
2617
2661
|
this.currentImages = [];
|
|
2618
2662
|
if (this.debug) {
|
|
2619
|
-
console.log('[DEBUG] Cleared all loaded
|
|
2663
|
+
console.log('[DEBUG] Cleared all loaded media');
|
|
2620
2664
|
}
|
|
2621
2665
|
}
|
|
2622
2666
|
|
|
2623
2667
|
/**
|
|
2624
|
-
* Prepare messages for AI consumption, adding
|
|
2668
|
+
* Prepare messages for AI consumption, adding media to the latest user message if available
|
|
2625
2669
|
* @param {Array} messages - Current conversation messages
|
|
2626
|
-
* @returns {Array} - Messages formatted for AI SDK with potential
|
|
2670
|
+
* @returns {Array} - Messages formatted for AI SDK with potential media content
|
|
2627
2671
|
*/
|
|
2628
2672
|
prepareMessagesWithImages(messages) {
|
|
2629
|
-
const
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
if (loadedImages.length === 0) {
|
|
2673
|
+
const mediaParts = this.getCurrentMedia();
|
|
2674
|
+
|
|
2675
|
+
if (mediaParts.length === 0) {
|
|
2633
2676
|
return messages;
|
|
2634
2677
|
}
|
|
2635
2678
|
|
|
2636
|
-
|
|
2637
|
-
const
|
|
2638
|
-
|
|
2639
|
-
// Find the last user message to attach images to
|
|
2640
|
-
const lastUserMessageIndex = messagesWithImages.map(m => m.role).lastIndexOf('user');
|
|
2641
|
-
|
|
2679
|
+
const messagesWithMedia = [...messages];
|
|
2680
|
+
const lastUserMessageIndex = messagesWithMedia.map(m => m.role).lastIndexOf('user');
|
|
2681
|
+
|
|
2642
2682
|
if (lastUserMessageIndex === -1) {
|
|
2643
2683
|
if (this.debug) {
|
|
2644
|
-
console.log('[DEBUG] No user messages found to attach
|
|
2684
|
+
console.log('[DEBUG] No user messages found to attach media to');
|
|
2645
2685
|
}
|
|
2646
2686
|
return messages;
|
|
2647
2687
|
}
|
|
2648
2688
|
|
|
2649
|
-
const lastUserMessage =
|
|
2650
|
-
|
|
2651
|
-
// Convert to multimodal format if we have images
|
|
2689
|
+
const lastUserMessage = messagesWithMedia[lastUserMessageIndex];
|
|
2690
|
+
|
|
2652
2691
|
if (typeof lastUserMessage.content === 'string') {
|
|
2653
|
-
|
|
2692
|
+
messagesWithMedia[lastUserMessageIndex] = {
|
|
2654
2693
|
...lastUserMessage,
|
|
2655
2694
|
content: [
|
|
2656
2695
|
{ type: 'text', text: lastUserMessage.content },
|
|
2657
|
-
...
|
|
2658
|
-
type: 'image',
|
|
2659
|
-
image: imageData
|
|
2660
|
-
}))
|
|
2696
|
+
...mediaParts
|
|
2661
2697
|
]
|
|
2662
2698
|
};
|
|
2663
2699
|
|
|
2664
2700
|
if (this.debug) {
|
|
2665
|
-
console.log(`[DEBUG] Added ${
|
|
2701
|
+
console.log(`[DEBUG] Added ${mediaParts.length} media items to the latest user message`);
|
|
2666
2702
|
}
|
|
2667
2703
|
}
|
|
2668
2704
|
|
|
2669
|
-
return
|
|
2705
|
+
return messagesWithMedia;
|
|
2670
2706
|
}
|
|
2671
2707
|
|
|
2672
2708
|
/**
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared media format configuration for Probe agent
|
|
3
|
+
*
|
|
4
|
+
* This module centralizes supported media formats (images + documents)
|
|
5
|
+
* and their MIME types to ensure consistency across all components.
|
|
6
|
+
*
|
|
7
|
+
* Supports:
|
|
8
|
+
* - Images: png, jpg, jpeg, webp, bmp, svg
|
|
9
|
+
* - Documents: pdf (native support in Claude, Gemini, OpenAI via Vercel AI SDK)
|
|
10
|
+
*
|
|
11
|
+
* Note: GIF support was intentionally removed for compatibility with
|
|
12
|
+
* AI models like Google Gemini that don't support animated images.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// Supported image file extensions (without leading dot)
|
|
16
|
+
export const SUPPORTED_IMAGE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'webp', 'bmp', 'svg'];
|
|
17
|
+
|
|
18
|
+
// Supported document file extensions (without leading dot)
|
|
19
|
+
export const SUPPORTED_DOCUMENT_EXTENSIONS = ['pdf'];
|
|
20
|
+
|
|
21
|
+
// All supported media extensions (images + documents)
|
|
22
|
+
export const SUPPORTED_MEDIA_EXTENSIONS = [...SUPPORTED_IMAGE_EXTENSIONS, ...SUPPORTED_DOCUMENT_EXTENSIONS];
|
|
23
|
+
|
|
24
|
+
// MIME type mapping for all supported media formats
|
|
25
|
+
export const MEDIA_MIME_TYPES = {
|
|
26
|
+
'png': 'image/png',
|
|
27
|
+
'jpg': 'image/jpeg',
|
|
28
|
+
'jpeg': 'image/jpeg',
|
|
29
|
+
'webp': 'image/webp',
|
|
30
|
+
'bmp': 'image/bmp',
|
|
31
|
+
'svg': 'image/svg+xml',
|
|
32
|
+
'pdf': 'application/pdf'
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Legacy aliases for backward compatibility
|
|
36
|
+
export const IMAGE_MIME_TYPES = MEDIA_MIME_TYPES;
|
|
37
|
+
|
|
38
|
+
// Provider-specific unsupported media formats
|
|
39
|
+
export const PROVIDER_UNSUPPORTED_FORMATS = {
|
|
40
|
+
'google': ['svg'], // Google Gemini doesn't support image/svg+xml
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Check if a file extension is an image type
|
|
45
|
+
* @param {string} extension - File extension (without dot)
|
|
46
|
+
* @returns {boolean}
|
|
47
|
+
*/
|
|
48
|
+
export function isImageExtension(extension) {
|
|
49
|
+
return SUPPORTED_IMAGE_EXTENSIONS.includes(extension?.toLowerCase());
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Check if a file extension is a document type (PDF, etc.)
|
|
54
|
+
* @param {string} extension - File extension (without dot)
|
|
55
|
+
* @returns {boolean}
|
|
56
|
+
*/
|
|
57
|
+
export function isDocumentExtension(extension) {
|
|
58
|
+
return SUPPORTED_DOCUMENT_EXTENSIONS.includes(extension?.toLowerCase());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Generate a regex pattern string for matching media file extensions
|
|
63
|
+
* @param {string[]} extensions - Array of extensions (without dots)
|
|
64
|
+
* @returns {string} Regex pattern string like "png|jpg|jpeg|webp|bmp|svg|pdf"
|
|
65
|
+
*/
|
|
66
|
+
export function getExtensionPattern(extensions = SUPPORTED_MEDIA_EXTENSIONS) {
|
|
67
|
+
return extensions.join('|');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get MIME type for a file extension
|
|
72
|
+
* @param {string} extension - File extension (without dot)
|
|
73
|
+
* @returns {string|undefined} MIME type or undefined if not supported
|
|
74
|
+
*/
|
|
75
|
+
export function getMimeType(extension) {
|
|
76
|
+
return MEDIA_MIME_TYPES[extension?.toLowerCase()];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Check if a media extension is supported by a specific provider
|
|
81
|
+
* @param {string} extension - File extension (without dot)
|
|
82
|
+
* @param {string} provider - Provider name (e.g., 'google', 'anthropic', 'openai')
|
|
83
|
+
* @returns {boolean} True if the format is supported by the provider
|
|
84
|
+
*/
|
|
85
|
+
export function isFormatSupportedByProvider(extension, provider) {
|
|
86
|
+
if (!extension || typeof extension !== 'string') {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
if (extension.includes('/') || extension.includes('\\') || extension.includes('..')) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const ext = extension.toLowerCase();
|
|
94
|
+
|
|
95
|
+
if (!SUPPORTED_MEDIA_EXTENSIONS.includes(ext)) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!provider || typeof provider !== 'string') {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const unsupportedFormats = PROVIDER_UNSUPPORTED_FORMATS[provider];
|
|
104
|
+
if (unsupportedFormats && unsupportedFormats.includes(ext)) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get supported media extensions for a specific provider
|
|
113
|
+
* @param {string} provider - Provider name (e.g., 'google', 'anthropic', 'openai')
|
|
114
|
+
* @returns {string[]} Array of supported extensions for this provider
|
|
115
|
+
*/
|
|
116
|
+
export function getSupportedExtensionsForProvider(provider) {
|
|
117
|
+
if (!provider || typeof provider !== 'string') {
|
|
118
|
+
return [...SUPPORTED_MEDIA_EXTENSIONS];
|
|
119
|
+
}
|
|
120
|
+
const unsupportedFormats = PROVIDER_UNSUPPORTED_FORMATS[provider] || [];
|
|
121
|
+
return SUPPORTED_MEDIA_EXTENSIONS.filter(ext => !unsupportedFormats.includes(ext));
|
|
122
|
+
}
|
package/build/agent/tools.js
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
listFilesSchema,
|
|
27
27
|
searchFilesSchema,
|
|
28
28
|
readImageSchema,
|
|
29
|
+
readMediaSchema,
|
|
29
30
|
listSkillsSchema,
|
|
30
31
|
useSkillSchema
|
|
31
32
|
} from '../index.js';
|
|
@@ -109,6 +110,7 @@ export {
|
|
|
109
110
|
listFilesSchema,
|
|
110
111
|
searchFilesSchema,
|
|
111
112
|
readImageSchema,
|
|
113
|
+
readMediaSchema,
|
|
112
114
|
listSkillsSchema,
|
|
113
115
|
useSkillSchema
|
|
114
116
|
};
|
package/build/delegate.js
CHANGED
|
@@ -387,7 +387,9 @@ export async function delegate({
|
|
|
387
387
|
bashConfig = null,
|
|
388
388
|
allowEdit = false,
|
|
389
389
|
architectureFileName = null,
|
|
390
|
-
promptType =
|
|
390
|
+
promptType = undefined,
|
|
391
|
+
customPrompt = null,
|
|
392
|
+
completionPrompt = null,
|
|
391
393
|
allowedTools = null,
|
|
392
394
|
disableTools = false,
|
|
393
395
|
searchDelegate = undefined,
|
|
@@ -474,7 +476,9 @@ export async function delegate({
|
|
|
474
476
|
// Tasks do not propagate back to the parent - each subagent has its own scope.
|
|
475
477
|
const subagent = new ProbeAgent({
|
|
476
478
|
sessionId,
|
|
477
|
-
promptType, //
|
|
479
|
+
promptType, // Inherit from parent (or use parent's default)
|
|
480
|
+
customPrompt, // Inherit custom system prompt from parent
|
|
481
|
+
completionPrompt, // Inherit completion prompt from parent
|
|
478
482
|
enableDelegate: false, // Explicitly disable delegation to prevent recursion
|
|
479
483
|
disableMermaidValidation: true, // Faster processing
|
|
480
484
|
disableJsonValidation: true, // Simpler responses
|
package/build/index.js
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
listFilesSchema,
|
|
33
33
|
searchFilesSchema,
|
|
34
34
|
readImageSchema,
|
|
35
|
+
readMediaSchema,
|
|
35
36
|
listSkillsSchema,
|
|
36
37
|
useSkillSchema
|
|
37
38
|
} from './tools/common.js';
|
|
@@ -115,6 +116,7 @@ export {
|
|
|
115
116
|
listFilesSchema,
|
|
116
117
|
searchFilesSchema,
|
|
117
118
|
readImageSchema,
|
|
119
|
+
readMediaSchema,
|
|
118
120
|
listSkillsSchema,
|
|
119
121
|
useSkillSchema,
|
|
120
122
|
// Export task management
|
package/build/tools/common.js
CHANGED
|
@@ -68,6 +68,10 @@ export const readImageSchema = z.object({
|
|
|
68
68
|
path: z.string().describe('Path to the image file to read. Supports png, jpg, jpeg, webp, bmp, and svg formats.')
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
+
export const readMediaSchema = z.object({
|
|
72
|
+
path: z.string().describe('Path to the media file to read. Supports images (png, jpg, jpeg, webp, bmp, svg) and documents (pdf).')
|
|
73
|
+
});
|
|
74
|
+
|
|
71
75
|
export const bashSchema = z.object({
|
|
72
76
|
command: z.string().describe('The bash command to execute'),
|
|
73
77
|
workingDirectory: z.string().optional().describe('Directory to execute the command in (optional)'),
|
package/build/tools/index.js
CHANGED
package/build/tools/vercel.js
CHANGED
|
@@ -1135,7 +1135,7 @@ export const extractTool = (options = {}) => {
|
|
|
1135
1135
|
* @returns {Object} Configured delegate tool
|
|
1136
1136
|
*/
|
|
1137
1137
|
export const delegateTool = (options = {}) => {
|
|
1138
|
-
const { debug = false, timeout = 300, cwd, allowedFolders, workspaceRoot, enableBash = false, bashConfig, allowEdit = false, architectureFileName, enableMcp = false, mcpConfig = null, mcpConfigPath = null, delegationManager = null,
|
|
1138
|
+
const { debug = false, timeout = 300, cwd, allowedFolders, workspaceRoot, enableBash = false, bashConfig, allowEdit = false, architectureFileName, enableMcp = false, mcpConfig = null, mcpConfigPath = null, promptType: parentPromptType, customPrompt: parentCustomPrompt = null, completionPrompt: parentCompletionPrompt = null, delegationManager = null,
|
|
1139
1139
|
// Timeout settings inherited from parent agent
|
|
1140
1140
|
timeoutBehavior, maxOperationTimeout, requestTimeout, gracefulTimeoutBonusSteps,
|
|
1141
1141
|
negotiatedTimeoutBudget, negotiatedTimeoutMaxRequests, negotiatedTimeoutMaxPerRequest,
|
|
@@ -1257,6 +1257,9 @@ export const delegateTool = (options = {}) => {
|
|
|
1257
1257
|
allowEdit,
|
|
1258
1258
|
bashConfig,
|
|
1259
1259
|
architectureFileName,
|
|
1260
|
+
promptType: parentPromptType, // Inherit parent's prompt type
|
|
1261
|
+
customPrompt: parentCustomPrompt, // Inherit parent's custom system prompt
|
|
1262
|
+
completionPrompt: parentCompletionPrompt, // Inherit parent's completion prompt
|
|
1260
1263
|
searchDelegate,
|
|
1261
1264
|
enableMcp,
|
|
1262
1265
|
mcpConfig,
|