UNPKG

@probelabs/probe

Version:

Node.js wrapper for the probe code search tool

216 lines (186 loc) 7.7 kB
/** * Bash command execution tool for Vercel AI SDK * @module tools/bash */ import { tool } from 'ai'; import { resolve } from 'path'; import { BashPermissionChecker } from '../agent/bashPermissions.js'; import { executeBashCommand, formatExecutionResult, validateExecutionOptions } from '../agent/bashExecutor.js'; /** * Bash tool generator * * @param {Object} [options] - Configuration options * @param {Object} [options.bashConfig] - Bash-specific configuration * @param {string[]} [options.bashConfig.allow] - Additional allow patterns * @param {string[]} [options.bashConfig.deny] - Additional deny patterns * @param {boolean} [options.bashConfig.disableDefaultAllow] - Disable default allow list * @param {boolean} [options.bashConfig.disableDefaultDeny] - Disable default deny list * @param {number} [options.bashConfig.timeout=120000] - Command timeout in milliseconds * @param {string} [options.bashConfig.workingDirectory] - Default working directory * @param {Object} [options.bashConfig.env={}] - Default environment variables * @param {number} [options.bashConfig.maxBuffer] - Maximum output buffer size * @param {boolean} [options.debug=false] - Enable debug logging * @param {string} [options.defaultPath] - Default working directory from probe config * @param {string[]} [options.allowedFolders] - Allowed directories for execution * @returns {Object} Configured bash tool */ export const bashTool = (options = {}) => { const { bashConfig = {}, debug = false, defaultPath, allowedFolders = [] } = options; // Create permission checker const permissionChecker = new BashPermissionChecker({ allow: bashConfig.allow, deny: bashConfig.deny, disableDefaultAllow: bashConfig.disableDefaultAllow, disableDefaultDeny: bashConfig.disableDefaultDeny, debug }); // Determine default working directory const getDefaultWorkingDirectory = () => { if (bashConfig.workingDirectory) { return bashConfig.workingDirectory; } if (defaultPath) { return defaultPath; } if (allowedFolders && allowedFolders.length > 0) { return allowedFolders[0]; } return process.cwd(); }; return tool({ name: 'bash', description: `Execute bash commands for system exploration and development tasks. Security: This tool has built-in security with allow/deny lists. By default, only safe read-only commands are allowed for code exploration. Parameters: - command: (required) The bash command to execute - workingDirectory: (optional) Directory to execute command in - timeout: (optional) Command timeout in milliseconds - env: (optional) Additional environment variables Examples of allowed commands by default: - File exploration: ls, cat, head, tail, find, grep - Git operations: git status, git log, git diff, git show - Package info: npm list, pip list, cargo --version - System info: whoami, pwd, uname, date Dangerous commands are blocked by default (rm -rf, sudo, npm install, etc.)`, inputSchema: { type: 'object', properties: { command: { type: 'string', description: 'The bash command to execute' }, workingDirectory: { type: 'string', description: 'Directory to execute the command in (optional)' }, timeout: { type: 'number', description: 'Command timeout in milliseconds (optional)', minimum: 1000, maximum: 600000 }, env: { type: 'object', description: 'Additional environment variables (optional)', additionalProperties: { type: 'string' } } }, required: ['command'], additionalProperties: false }, execute: async ({ command, workingDirectory, timeout, env }) => { try { // Validate command if (command === null || command === undefined || typeof command !== 'string') { return 'Error: Command is required and must be a string'; } if (command.trim().length === 0) { return 'Error: Command cannot be empty'; } // Check permissions const permissionResult = permissionChecker.check(command.trim()); if (!permissionResult.allowed) { if (debug) { console.log(`[BashTool] Permission denied for command: "${command}"`); console.log(`[BashTool] Reason: ${permissionResult.reason}`); } return `Permission denied: ${permissionResult.reason} This command is not allowed by the current security policy. Common reasons: 1. The command is in the deny list (potentially dangerous) 2. The command is not in the allow list (not a recognized safe command) If you believe this command should be allowed, you can: - Use the --bash-allow option to add specific patterns - Use the --no-default-bash-deny flag to remove default restrictions (not recommended) For code exploration, try these safe alternatives: - ls, cat, head, tail for file operations - find, grep, rg for searching - git status, git log, git show for git operations - npm list, pip list for package information`; } // Determine working directory const workingDir = workingDirectory || getDefaultWorkingDirectory(); // Validate working directory is within allowed folders if specified if (allowedFolders && allowedFolders.length > 0) { const resolvedWorkingDir = resolve(workingDir); const isAllowed = allowedFolders.some(folder => { const resolvedFolder = resolve(folder); return resolvedWorkingDir.startsWith(resolvedFolder); }); if (!isAllowed) { return `Error: Working directory "${workingDir}" is not within allowed folders: ${allowedFolders.join(', ')}`; } } // Prepare execution options const executionOptions = { workingDirectory: workingDir, timeout: timeout || bashConfig.timeout || 120000, env: { ...bashConfig.env, ...env }, maxBuffer: bashConfig.maxBuffer, debug }; // Validate execution options const validation = validateExecutionOptions(executionOptions); if (!validation.valid) { return `Error: Invalid execution options: ${validation.errors.join(', ')}`; } if (validation.warnings.length > 0 && debug) { console.log('[BashTool] Warnings:', validation.warnings); } if (debug) { console.log(`[BashTool] Executing command: "${command}"`); console.log(`[BashTool] Working directory: "${workingDir}"`); console.log(`[BashTool] Timeout: ${executionOptions.timeout}ms`); } // Execute command const result = await executeBashCommand(command.trim(), executionOptions); if (debug) { console.log(`[BashTool] Command completed - Success: ${result.success}, Duration: ${result.duration}ms`); } // Format and return result const formattedResult = formatExecutionResult(result, debug); // Add metadata for failed commands if (!result.success) { let errorInfo = `\n\nCommand failed with exit code ${result.exitCode}`; if (result.killed) { errorInfo += ` (${result.error})`; } return formattedResult + errorInfo; } return formattedResult; } catch (error) { if (debug) { console.error('[BashTool] Execution error:', error); } return `Error executing bash command: ${error.message}`; } } }); };