@probelabs/probe 0.6.0-rc306 → 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-rc306-aarch64-apple-darwin.tar.gz → probe-v0.6.0-rc307-aarch64-apple-darwin.tar.gz} +0 -0
- package/bin/binaries/{probe-v0.6.0-rc306-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-rc306-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-rc306-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-rc306-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 +125 -101
- package/build/agent/mediaConfig.js +122 -0
- package/build/agent/tools.js +2 -0
- package/build/index.js +2 -0
- package/build/tools/common.js +4 -0
- package/build/tools/index.js +1 -0
- package/cjs/agent/ProbeAgent.cjs +128 -81
- package/cjs/index.cjs +131 -81
- package/package.json +1 -1
- package/src/agent/ProbeAgent.js +125 -101
- package/src/agent/mediaConfig.js +122 -0
- package/src/agent/tools.js +2 -0
- package/src/index.js +2 -0
- package/src/tools/common.js +4 -0
- package/src/tools/index.js +1 -0
|
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.
|
|
@@ -958,40 +961,43 @@ export class ProbeAgent {
|
|
|
958
961
|
}
|
|
959
962
|
}
|
|
960
963
|
|
|
961
|
-
//
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
throw new Error('Image path is required');
|
|
968
|
-
}
|
|
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
|
+
}
|
|
969
970
|
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
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();
|
|
974
975
|
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
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
|
+
}
|
|
979
980
|
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
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
|
+
}
|
|
984
985
|
|
|
985
|
-
|
|
986
|
-
|
|
986
|
+
// Load the media file
|
|
987
|
+
const loaded = await this.loadMediaIfValid(mediaPath);
|
|
987
988
|
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
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
|
+
}
|
|
991
992
|
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
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 };
|
|
995
1001
|
}
|
|
996
1002
|
|
|
997
1003
|
// Add bash tool if enabled and allowed
|
|
@@ -2219,9 +2225,9 @@ export class ProbeAgent {
|
|
|
2219
2225
|
schema: searchFilesSchema,
|
|
2220
2226
|
description: 'Find files matching a glob pattern with recursive search capability.'
|
|
2221
2227
|
},
|
|
2222
|
-
|
|
2223
|
-
schema:
|
|
2224
|
-
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.'
|
|
2225
2231
|
},
|
|
2226
2232
|
listSkills: {
|
|
2227
2233
|
schema: listSkillsSchema,
|
|
@@ -2383,7 +2389,7 @@ export class ProbeAgent {
|
|
|
2383
2389
|
|
|
2384
2390
|
// Enhanced pattern to detect image file mentions in various contexts
|
|
2385
2391
|
// Looks for: "image", "file", "screenshot", etc. followed by path-like strings with image extensions
|
|
2386
|
-
const extensionsPattern = `(?:${
|
|
2392
|
+
const extensionsPattern = `(?:${SUPPORTED_MEDIA_EXTENSIONS.join('|')})`;
|
|
2387
2393
|
const imagePatterns = [
|
|
2388
2394
|
// Direct file path mentions: "./screenshot.png", "/path/to/image.jpg", etc.
|
|
2389
2395
|
new RegExp(`(?:\\.?\\.\\/)?[^\\s"'<>\\[\\]]+\\\.${extensionsPattern}(?!\\w)`, 'gi'),
|
|
@@ -2504,43 +2510,36 @@ export class ProbeAgent {
|
|
|
2504
2510
|
}
|
|
2505
2511
|
|
|
2506
2512
|
/**
|
|
2507
|
-
* Load and cache
|
|
2508
|
-
* @param {string}
|
|
2509
|
-
* @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
|
|
2510
2516
|
*/
|
|
2511
|
-
async
|
|
2517
|
+
async loadMediaIfValid(mediaPath) {
|
|
2512
2518
|
try {
|
|
2513
2519
|
// Skip if already loaded
|
|
2514
|
-
if (this.pendingImages.has(
|
|
2520
|
+
if (this.pendingImages.has(mediaPath)) {
|
|
2515
2521
|
if (this.debug) {
|
|
2516
|
-
console.log(`[DEBUG]
|
|
2522
|
+
console.log(`[DEBUG] Media already loaded: ${mediaPath}`);
|
|
2517
2523
|
}
|
|
2518
2524
|
return true;
|
|
2519
2525
|
}
|
|
2520
2526
|
|
|
2521
2527
|
// Security validation: check if path is within any allowed directory
|
|
2522
|
-
// Use safeRealpath() to resolve symlinks and handle path traversal attempts (e.g., '/allowed/../etc/passwd')
|
|
2523
|
-
// This prevents symlink bypass attacks (e.g., /tmp -> /private/tmp on macOS)
|
|
2524
2528
|
const allowedDirs = this.allowedFolders && this.allowedFolders.length > 0 ? this.allowedFolders : [process.cwd()];
|
|
2525
2529
|
|
|
2526
2530
|
let absolutePath;
|
|
2527
2531
|
let isPathAllowed = false;
|
|
2528
2532
|
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
// Use safeRealpath to resolve symlinks for security
|
|
2532
|
-
absolutePath = safeRealpath(resolve(imagePath));
|
|
2533
|
+
if (isAbsolute(mediaPath)) {
|
|
2534
|
+
absolutePath = safeRealpath(resolve(mediaPath));
|
|
2533
2535
|
isPathAllowed = allowedDirs.some(dir => {
|
|
2534
2536
|
const resolvedDir = safeRealpath(dir);
|
|
2535
|
-
// Ensure the path is within the allowed directory (add separator to prevent prefix attacks)
|
|
2536
2537
|
return absolutePath === resolvedDir || absolutePath.startsWith(resolvedDir + sep);
|
|
2537
2538
|
});
|
|
2538
2539
|
} else {
|
|
2539
|
-
// For relative paths, try resolving against each allowed directory
|
|
2540
2540
|
for (const dir of allowedDirs) {
|
|
2541
2541
|
const resolvedDir = safeRealpath(dir);
|
|
2542
|
-
const resolvedPath = safeRealpath(resolve(dir,
|
|
2543
|
-
// Ensure the resolved path is within the allowed directory
|
|
2542
|
+
const resolvedPath = safeRealpath(resolve(dir, mediaPath));
|
|
2544
2543
|
if (resolvedPath === resolvedDir || resolvedPath.startsWith(resolvedDir + sep)) {
|
|
2545
2544
|
absolutePath = resolvedPath;
|
|
2546
2545
|
isPathAllowed = true;
|
|
@@ -2548,137 +2547,162 @@ export class ProbeAgent {
|
|
|
2548
2547
|
}
|
|
2549
2548
|
}
|
|
2550
2549
|
}
|
|
2551
|
-
|
|
2552
|
-
// Security check: ensure path is within at least one allowed directory
|
|
2550
|
+
|
|
2553
2551
|
if (!isPathAllowed) {
|
|
2554
2552
|
if (this.debug) {
|
|
2555
|
-
console.log(`[DEBUG]
|
|
2553
|
+
console.log(`[DEBUG] Media path outside allowed directories: ${mediaPath}`);
|
|
2556
2554
|
}
|
|
2557
2555
|
return false;
|
|
2558
2556
|
}
|
|
2559
2557
|
|
|
2560
|
-
// Check if file exists and get file stats
|
|
2561
2558
|
let fileStats;
|
|
2562
2559
|
try {
|
|
2563
2560
|
fileStats = await stat(absolutePath);
|
|
2564
2561
|
} catch (error) {
|
|
2565
2562
|
if (this.debug) {
|
|
2566
|
-
console.log(`[DEBUG]
|
|
2563
|
+
console.log(`[DEBUG] Media file not found: ${absolutePath}`);
|
|
2567
2564
|
}
|
|
2568
2565
|
return false;
|
|
2569
2566
|
}
|
|
2570
2567
|
|
|
2571
|
-
|
|
2572
|
-
if (
|
|
2568
|
+
const extension = absolutePath.toLowerCase().split('.').pop();
|
|
2569
|
+
if (!SUPPORTED_MEDIA_EXTENSIONS.includes(extension)) {
|
|
2573
2570
|
if (this.debug) {
|
|
2574
|
-
console.log(`[DEBUG]
|
|
2571
|
+
console.log(`[DEBUG] Unsupported media format: ${extension}`);
|
|
2575
2572
|
}
|
|
2576
2573
|
return false;
|
|
2577
2574
|
}
|
|
2578
2575
|
|
|
2579
|
-
//
|
|
2580
|
-
const
|
|
2581
|
-
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) {
|
|
2582
2579
|
if (this.debug) {
|
|
2583
|
-
console.log(`[DEBUG]
|
|
2580
|
+
console.log(`[DEBUG] Media file too large: ${absolutePath} (${fileStats.size} bytes, max: ${maxSize})`);
|
|
2584
2581
|
}
|
|
2585
2582
|
return false;
|
|
2586
2583
|
}
|
|
2587
2584
|
|
|
2588
|
-
|
|
2589
|
-
// is handled by the readImage tool which provides explicit error messages.
|
|
2590
|
-
// loadImageIfValid is a lower-level method that only checks general format support.
|
|
2591
|
-
|
|
2592
|
-
// Determine MIME type (from shared config)
|
|
2593
|
-
const mimeType = IMAGE_MIME_TYPES[extension];
|
|
2594
|
-
|
|
2595
|
-
// Read and encode file asynchronously
|
|
2585
|
+
const mimeType = MEDIA_MIME_TYPES[extension];
|
|
2596
2586
|
const fileBuffer = await readFile(absolutePath);
|
|
2597
2587
|
const base64Data = fileBuffer.toString('base64');
|
|
2598
|
-
const dataUrl = `data:${mimeType};base64,${base64Data}`;
|
|
2599
2588
|
|
|
2600
|
-
|
|
2601
|
-
|
|
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
|
+
}
|
|
2602
2602
|
|
|
2603
2603
|
if (this.debug) {
|
|
2604
|
-
console.log(`[DEBUG] Successfully loaded
|
|
2604
|
+
console.log(`[DEBUG] Successfully loaded media: ${mediaPath} (${fileBuffer.length} bytes, ${mimeType})`);
|
|
2605
2605
|
}
|
|
2606
2606
|
|
|
2607
2607
|
return true;
|
|
2608
2608
|
} catch (error) {
|
|
2609
2609
|
if (this.debug) {
|
|
2610
|
-
console.log(`[DEBUG] Failed to load
|
|
2610
|
+
console.log(`[DEBUG] Failed to load media ${mediaPath}: ${error.message}`);
|
|
2611
2611
|
}
|
|
2612
2612
|
return false;
|
|
2613
2613
|
}
|
|
2614
2614
|
}
|
|
2615
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
|
+
|
|
2616
2625
|
/**
|
|
2617
2626
|
* Get all currently loaded images as an array for AI model consumption
|
|
2618
|
-
* @returns {Array<string>} - Array of base64 data URLs
|
|
2627
|
+
* @returns {Array<string>} - Array of base64 data URLs (images only, for backward compat)
|
|
2619
2628
|
*/
|
|
2620
2629
|
getCurrentImages() {
|
|
2621
|
-
return Array.from(this.pendingImages.values());
|
|
2630
|
+
return Array.from(this.pendingImages.values()).filter(v => typeof v === 'string');
|
|
2622
2631
|
}
|
|
2623
2632
|
|
|
2624
2633
|
/**
|
|
2625
|
-
*
|
|
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)
|
|
2626
2658
|
*/
|
|
2627
2659
|
clearLoadedImages() {
|
|
2628
2660
|
this.pendingImages.clear();
|
|
2629
2661
|
this.currentImages = [];
|
|
2630
2662
|
if (this.debug) {
|
|
2631
|
-
console.log('[DEBUG] Cleared all loaded
|
|
2663
|
+
console.log('[DEBUG] Cleared all loaded media');
|
|
2632
2664
|
}
|
|
2633
2665
|
}
|
|
2634
2666
|
|
|
2635
2667
|
/**
|
|
2636
|
-
* Prepare messages for AI consumption, adding
|
|
2668
|
+
* Prepare messages for AI consumption, adding media to the latest user message if available
|
|
2637
2669
|
* @param {Array} messages - Current conversation messages
|
|
2638
|
-
* @returns {Array} - Messages formatted for AI SDK with potential
|
|
2670
|
+
* @returns {Array} - Messages formatted for AI SDK with potential media content
|
|
2639
2671
|
*/
|
|
2640
2672
|
prepareMessagesWithImages(messages) {
|
|
2641
|
-
const
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
if (loadedImages.length === 0) {
|
|
2673
|
+
const mediaParts = this.getCurrentMedia();
|
|
2674
|
+
|
|
2675
|
+
if (mediaParts.length === 0) {
|
|
2645
2676
|
return messages;
|
|
2646
2677
|
}
|
|
2647
2678
|
|
|
2648
|
-
|
|
2649
|
-
const
|
|
2650
|
-
|
|
2651
|
-
// Find the last user message to attach images to
|
|
2652
|
-
const lastUserMessageIndex = messagesWithImages.map(m => m.role).lastIndexOf('user');
|
|
2653
|
-
|
|
2679
|
+
const messagesWithMedia = [...messages];
|
|
2680
|
+
const lastUserMessageIndex = messagesWithMedia.map(m => m.role).lastIndexOf('user');
|
|
2681
|
+
|
|
2654
2682
|
if (lastUserMessageIndex === -1) {
|
|
2655
2683
|
if (this.debug) {
|
|
2656
|
-
console.log('[DEBUG] No user messages found to attach
|
|
2684
|
+
console.log('[DEBUG] No user messages found to attach media to');
|
|
2657
2685
|
}
|
|
2658
2686
|
return messages;
|
|
2659
2687
|
}
|
|
2660
2688
|
|
|
2661
|
-
const lastUserMessage =
|
|
2662
|
-
|
|
2663
|
-
// Convert to multimodal format if we have images
|
|
2689
|
+
const lastUserMessage = messagesWithMedia[lastUserMessageIndex];
|
|
2690
|
+
|
|
2664
2691
|
if (typeof lastUserMessage.content === 'string') {
|
|
2665
|
-
|
|
2692
|
+
messagesWithMedia[lastUserMessageIndex] = {
|
|
2666
2693
|
...lastUserMessage,
|
|
2667
2694
|
content: [
|
|
2668
2695
|
{ type: 'text', text: lastUserMessage.content },
|
|
2669
|
-
...
|
|
2670
|
-
type: 'image',
|
|
2671
|
-
image: imageData
|
|
2672
|
-
}))
|
|
2696
|
+
...mediaParts
|
|
2673
2697
|
]
|
|
2674
2698
|
};
|
|
2675
2699
|
|
|
2676
2700
|
if (this.debug) {
|
|
2677
|
-
console.log(`[DEBUG] Added ${
|
|
2701
|
+
console.log(`[DEBUG] Added ${mediaParts.length} media items to the latest user message`);
|
|
2678
2702
|
}
|
|
2679
2703
|
}
|
|
2680
2704
|
|
|
2681
|
-
return
|
|
2705
|
+
return messagesWithMedia;
|
|
2682
2706
|
}
|
|
2683
2707
|
|
|
2684
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/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)'),
|