All files / agent/src/utils capabilities.ts

0% Statements 0/223
0% Branches 0/1
0% Functions 0/1
0% Lines 0/223

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223                                                                                                                                                                                                                                                                                                                                                                                                                                                             
/**
 * Capabilities Detection
 *
 * Detects system capabilities for agent registration using systeminformation library
 * for accurate cross-platform system metrics.
 *
 * Requirements: MVP.4.1.2
 */

import os from 'os';
import si from 'systeminformation';
import { exec } from 'child_process';
import { promisify } from 'util';
import { AgentCapabilities, DetailedCapabilities } from '../types.js';
import { createLogger } from './logger.js';
import { scanInstalledSDKs, generateTagsFromSDKs } from './sdkScanner.js';

const execAsync = promisify(exec);
const logger = createLogger('capabilities');

/**
 * Detect system capabilities with systeminformation
 */
export async function detectCapabilities(): Promise<AgentCapabilities> {
  logger.info('Detecting system capabilities using systeminformation...');

  try {
    // Gather all system information in parallel
    const [cpu, mem, osInfo, disk, dockerVersion] = await Promise.all([
      si.cpu(),
      si.mem(),
      si.osInfo(),
      si.fsSize(),
      detectDockerVersion(),
    ]);

    const capabilities: AgentCapabilities = {
      os: normalizeOsName(osInfo.platform),
      arch: normalizeArchitecture(osInfo.arch),
      cpuCores: cpu.physicalCores || cpu.cores,
      memoryGB: Math.round(mem.total / (1024 * 1024 * 1024)),
      diskSpaceGB: Math.round((disk[0]?.size || 0) / (1024 * 1024 * 1024)),
    };

    if (dockerVersion) {
      capabilities.dockerVersion = dockerVersion;
    }

    logger.info('Detected system capabilities:', capabilities);
    return capabilities;
  } catch (error) {
    logger.error('Failed to detect capabilities, using fallback:', error);
    // Fallback to basic os module if systeminformation fails
    return detectCapabilitiesFallback();
  }
}

/**
 * Fallback capability detection using Node.js os module
 */
function detectCapabilitiesFallback(): AgentCapabilities {
  logger.warn('Using fallback capability detection');

  const capabilities: AgentCapabilities = {
    os: getOperatingSystem(),
    arch: getArchitecture(),
    cpuCores: getCpuCores(),
    memoryGB: getMemoryGB(),
    diskSpaceGB: 100, // Fallback value
  };

  // Try to detect Docker version asynchronously
  detectDockerVersion()
    .then(version => {
      if (version) {
        capabilities.dockerVersion = version;
      }
    })
    .catch(() => {
      // Ignore errors in fallback
    });

  return capabilities;
}

/**
 * Normalize OS name to standard format
 */
function normalizeOsName(platform: string): string {
  const normalized = platform.toLowerCase();
  if (normalized.includes('darwin') || normalized.includes('mac')) {
    return 'macos';
  } else if (normalized.includes('win')) {
    return 'windows';
  } else if (normalized.includes('linux')) {
    return 'linux';
  }
  return normalized;
}

/**
 * Normalize architecture to standard format
 */
function normalizeArchitecture(arch: string): string {
  const normalized = arch.toLowerCase();
  if (normalized.includes('x64') || normalized.includes('amd64')) {
    return 'x64';
  } else if (normalized.includes('arm64') || normalized.includes('aarch64')) {
    return 'arm64';
  } else if (normalized.includes('arm')) {
    return 'arm';
  }
  return normalized;
}

/**
 * Get operating system
 */
function getOperatingSystem(): string {
  const platform = os.platform();
  
  switch (platform) {
    case 'darwin':
      return 'macos';
    case 'linux':
      return 'linux';
    case 'win32':
      return 'windows';
    default:
      return platform;
  }
}

/**
 * Get system architecture
 */
function getArchitecture(): string {
  const arch = os.arch();
  
  switch (arch) {
    case 'x64':
      return 'x64';
    case 'arm64':
      return 'arm64';
    case 'arm':
      return 'arm';
    default:
      return arch;
  }
}

/**
 * Get CPU core count
 */
function getCpuCores(): number {
  return os.cpus().length;
}

/**
 * Get total memory in GB
 */
function getMemoryGB(): number {
  const totalMemory = os.totalmem();
  return Math.round(totalMemory / (1024 * 1024 * 1024));
}

/**
 * Get available disk space in GB
 */
function getDiskSpaceGB(): number {
  // This is a simplified implementation
  // In a real scenario, you'd want to check the actual disk space
  // For now, we'll return a reasonable default
  return 100; // GB
}

/**
 * Detect Docker version
 */
async function detectDockerVersion(): Promise<string | null> {
  try {
    const { stdout } = await execAsync('docker --version');
    const match = stdout.match(/Docker version ([^,\s]+)/);
    return match ? match[1] : null;
  } catch (error) {
    return null;
  }
}

/**
 * Detect detailed capabilities including SDK/toolchain information and
 * auto-generated tags.  This extends the basic {@link detectCapabilities}
 * with an SDK scan and tag generation pass.
 *
 * @param manualTags - Optional tags supplied by the user configuration that
 *   will be merged (and deduplicated) with auto-detected tags.
 */
export async function detectDetailedCapabilities(
  manualTags: string[] = [],
): Promise<DetailedCapabilities> {
  logger.info('Detecting detailed capabilities (base + SDKs)...');

  // Gather base capabilities and SDK scan in parallel
  const [base, sdks] = await Promise.all([
    detectCapabilities(),
    scanInstalledSDKs(),
  ]);

  const autoTags = generateTagsFromSDKs(sdks);

  // Merge platform tags (os, arch) with SDK tags and manual tags
  const platformTags = [base.os, base.arch].filter(Boolean);
  const allTags = [...new Set([...platformTags, ...autoTags, ...manualTags])];

  const detailed: DetailedCapabilities = {
    ...base,
    sdks,
    tags: allTags,
  };

  logger.info(`Detailed capabilities: ${allTags.length} tags generated`);
  return detailed;
}