e5f86d0e创建于 2025年7月2日历史提交
import * as path from 'path';
import { ExecutionContext } from '../types/agent';

/**
 * Standard error response structure for all tools
 */
export interface ToolErrorResponse {
  success: false;
  error: string;
  error_type?: 'validation' | 'security' | 'file_not_found' | 'permission' | 'execution' | 'unknown';
  details?: any;
}

/**
 * Standard success response structure for all tools
 */
export interface ToolSuccessResponse {
  success: true;
  [key: string]: any;
}

export type ToolResponse = ToolSuccessResponse | ToolErrorResponse;

/**
 * Generic error handler that converts exceptions/errors to standardized error responses
 */
export function handleToolError(
  error: unknown, 
  context?: string,
  errorType: ToolErrorResponse['error_type'] = 'unknown'
): ToolErrorResponse {
  let errorMessage: string;
  let details: any;

  if (error instanceof Error) {
    errorMessage = error.message;
    details = {
      name: error.name,
      stack: error.stack?.split('\n').slice(0, 3) // Truncated stack trace
    };
  } else if (typeof error === 'string') {
    errorMessage = error;
  } else {
    errorMessage = 'An unknown error occurred';
    details = { originalError: error };
  }

  // Add context if provided
  if (context) {
    errorMessage = `${context}: ${errorMessage}`;
  }

  console.error(`Tool error (${errorType}): ${errorMessage}`);

  return {
    success: false,
    error: errorMessage,
    error_type: errorType,
    details
  };
}

/**
 * Validate if a path is within the workspace directory (supports both absolute and relative paths)
 */
export function validateWorkspacePath(filePath: string, context: ExecutionContext): ToolErrorResponse | null {
  try {
    // Prevent directory traversal attacks
    if (filePath.includes('..')) {
      return handleToolError('Path cannot contain ".." for security reasons', 'Path validation', 'security');
    }

    const normalizedWorkspace = path.normalize(context.workingDirectory);
    
    // Handle both absolute and relative paths
    let resolvedPath: string;
    if (path.isAbsolute(filePath)) {
      resolvedPath = path.normalize(filePath);
    } else {
      resolvedPath = path.resolve(context.workingDirectory, filePath);
    }
    
    // Check if path is within workspace boundary
    if (!resolvedPath.startsWith(normalizedWorkspace)) {
      return handleToolError(
        `Path must be within workspace directory: ${filePath}`, 
        'Security check', 
        'security'
      );
    }

    return null; // No error
  } catch (error) {
    return handleToolError(error, 'Path validation', 'validation');
  }
}

/**
 * Safely resolve a file path (supports both absolute and relative paths)
 */
export function resolveWorkspacePath(filePath: string, context: ExecutionContext): string {
  if (path.isAbsolute(filePath)) {
    return path.normalize(filePath);
  } else {
    return path.resolve(context.workingDirectory, filePath);
  }
}

/**
 * Create a success response
 */
export function createSuccessResponse(data: Record<string, any>): ToolSuccessResponse {
  return {
    success: true,
    ...data
  };
}

/**
 * Validation helper for required string parameters
 */
export function validateRequiredString(value: any, paramName: string): ToolErrorResponse | null {
  if (!value || typeof value !== 'string' || value.trim() === '') {
    return handleToolError(
      `${paramName} is required and must be a non-empty string`, 
      'Parameter validation', 
      'validation'
    );
  }
  return null;
}

/**
 * Validation helper for file existence
 */
export function validateFileExists(absolutePath: string, filePath: string): ToolErrorResponse | null {
  const fs = require('fs');
  
  try {
    if (!fs.existsSync(absolutePath)) {
      return handleToolError(
        `File not found: ${filePath}`, 
        'File existence check', 
        'file_not_found'
      );
    }
    return null;
  } catch (error) {
    return handleToolError(error, 'File existence check', 'permission');
  }
}

/**
 * Validation helper for directory existence
 */
export function validateDirectoryExists(absolutePath: string, dirPath: string): ToolErrorResponse | null {
  const fs = require('fs');
  
  try {
    if (!fs.existsSync(absolutePath)) {
      return handleToolError(
        `Directory not found: ${dirPath}`, 
        'Directory existence check', 
        'file_not_found'
      );
    }

    const stats = fs.statSync(absolutePath);
    if (!stats.isDirectory()) {
      return handleToolError(
        `Path is not a directory: ${dirPath}`, 
        'Directory validation', 
        'validation'
      );
    }

    return null;
  } catch (error) {
    return handleToolError(error, 'Directory validation', 'permission');
  }
}